refactor(script): 优化部分弹窗提示的交互界面样式

This commit is contained in:
Quan 2025-08-10 13:41:48 +08:00
parent 9ad005c381
commit 120e5d65c3
2 changed files with 374 additions and 372 deletions

View File

@ -24,4 +24,5 @@
2. 添加未登录状态下无法提取作品链接的提示
3. 脚本设置新增 `菜单始终显示` 配置项
4. 修复用户脚本 CSS 样式冲突问题
5. 优化提取作品 ID 的正则表达式
5. 优化部分弹窗提示的交互界面样式
6. 优化提取作品 ID 的正则表达式

View File

@ -1,7 +1,7 @@
// ==UserScript==
// @name XHS-Downloader
// @namespace https://github.com/JoeanAmier/XHS-Downloader
// @version 2.1.2
// @version 2.1.3
// @description 提取小红书作品/用户链接,下载小红书无水印图文/视频作品文件
// @author JoeanAmier
// @match http*://xhslink.com/*
@ -45,17 +45,17 @@
},
}, // 位置配置
position: {
bottom: '8rem', left: '2rem'
bottom: '6rem', left: '1rem'
}, // 动画配置
animation: {
duration: 0.35, // 动画时长(s)
duration: 0.25, // 动画时长(s)
easing: 'cubic-bezier(0.4, 0, 0.2, 1)'
}
};
const readme = () => {
const readme = async () => {
const instructions = `
XHS-Downloader 用户脚本 功能清单
功能清单
1. 下载小红书无水印作品文件
2. 提取推荐页面作品链接
3. 提取账号发布作品链接
@ -65,7 +65,7 @@ XHS-Downloader 用户脚本 功能清单:
7. 提取搜索结果作品链接
8. 提取搜索结果用户链接
XHS-Downloader 用户脚本 详细说明
注意事项
1. 下载小红书无水印作品文件时脚本需要花费时间处理文件请等待片刻请勿多次点击下载按钮
2. 无水印作品文件较大可能需要较长的时间处理页面跳转可能会导致下载失败
3. 提取账号发布收藏点赞专辑作品链接时脚本可以自动滚动页面直至加载全部作品
@ -77,30 +77,35 @@ XHS-Downloader 用户脚本 详细说明:
项目开源地址https://github.com/JoeanAmier/XHS-Downloader
`
const disclaimer_content = `
关于 XHS-Downloader 免责声明
1. 使用者对本项目的使用由使用者自行决定并自行承担风险作者对使用者使用本项目所产生的任何损失责任或风险概不负责
2. 本项目的作者提供的代码和功能是基于现有知识和技术的开发成果作者力确保代码的正确性和安全性但不保证代码完全没有错误或缺陷
3. 使用者在使用本项目时必须严格遵守 GNU General Public License v3.0 的要求并在适当的地方注明使用了 GNU General Public License v3.0 的代码
4. 使用者在任何情况下均不得将本项目的作者贡献者或其他相关方与使用者的使用行为联系起来或要求其对使用者使用本项目所产生的任何损失或损害负责
2. 本项目的作者提供的代码和功能是基于现有知识和技术的开发成果作者按现有技术水平努力确保代码的正确性和安全性但不保证代码完全没有错误或缺陷
3. 本项目依赖的所有第三方库插件或服务各自遵循其原始开源或商业许可使用者需自行查阅并遵守相应协议作者不对第三方组件的稳定性安全性及合规性承担任何责任
4. 使用者在使用本项目时必须严格遵守 GNU General Public License v3.0 的要求并在适当的地方注明使用了 GNU General Public License v3.0 的代码
5. 使用者在使用本项目的代码和功能时必须自行研究相关法律法规并确保其使用行为合法合规任何因违反法律法规而导致的法律责任和风险均由使用者自行承担
6. 本项目的作者不会提供 XHS-Downloader 项目的付费版本也不会提供与 XHS-Downloader 项目相关的任何商业服务
7. 基于本项目进行的任何二次开发修改或编译的程序与原创作者无关原创作者不承担与二次开发行为或其结果相关的任何责任使用者应自行对因二次开发可能带来的各种情况负全部责任
6. 使用者不得使用本工具从事任何侵犯知识产权的行为包括但不限于未经授权下载传播受版权保护的内容开发者不参与不支持不认可任何非法内容的获取或分发
7. 本项目不对使用者涉及的数据收集存储传输等处理活动的合规性承担责任使用者应自行遵守相关法律法规确保处理行为合法正当因违规操作导致的法律责任由使用者自行承担
8. 使用者在任何情况下均不得将本项目的作者贡献者或其他相关方与使用者的使用行为联系起来或要求其对使用者使用本项目所产生的任何损失或损害负责
9. 本项目的作者不会提供 XHS-Downloader 项目的付费版本也不会提供与 XHS-Downloader 项目相关的任何商业服务
10. 基于本项目进行的任何二次开发修改或编译的程序与原创作者无关原创作者不承担与二次开发行为或其结果相关的任何责任使用者应自行对因二次开发可能带来的各种情况负全部责任
11. 本项目不授予使用者任何专利许可若使用本项目导致专利纠纷或侵权使用者自行承担全部风险和责任未经作者或权利人书面授权不得使用本项目进行任何商业宣传推广或再授权
12. 作者保留随时终止向任何违反本声明的使用者提供服务的权利并可能要求其销毁已获取的代码及衍生作品
13. 作者保留在不另行通知的情况下更新本声明的权利使用者持续使用即视为接受修订后的条款
在使用本项目的代码和功能之前请您认真考虑并接受以上免责声明如果您对上述声明有任何疑问或不同意请不要使用本项目的代码和功能如果您使用了本项目的代码和功能则视为您已完全理解并接受上述免责声明并自愿承担使用本项目的一切风险和后果
是否已阅读 XHS-Downloader 功能说明与免责声明(YES/NO)
`
alert(instructions);
await showTextModal({
title: 'XHS-Downloader 脚本说明', text: instructions, mode: 'info', // info: 仅关闭
closeText: '关闭'
});
if (!config.disclaimer) {
const answer = prompt(disclaimer_content, "");
if (!answer) {
GM_setValue("disclaimer", false);
config.disclaimer = false;
} else {
GM_setValue("disclaimer", answer.toUpperCase() === "YES" || answer.toUpperCase() === "Y");
config.disclaimer = GM_getValue("disclaimer");
}
showTextModal({
title: 'XHS-Downloader 免责声明', text: disclaimer_content, mode: 'confirm', // confirm: 确认+关闭
confirmText: '我已知晓', closeText: '关闭'
}).then(answer => {
GM_setValue("disclaimer", answer);
config.disclaimer = answer;
});
}
};
@ -149,9 +154,21 @@ XHS-Downloader 用户脚本 详细说明:
}
const abnormal = (text) => {
alert(`${text}请向作者反馈!\n项目开源地址https://github.com/JoeanAmier/XHS-Downloader`);
showTextModal({
title: '发生异常',
text: `${text}请向作者反馈!\n项目开源地址https://github.com/JoeanAmier/XHS-Downloader`,
mode: 'info', // info: 仅关闭
closeText: '关闭'
});
};
const runTips = (text) => {
showTextModal({
title: '脚本提示', text: text, mode: 'info', // info: 仅关闭
closeText: '关闭'
});
}
const generateVideoUrl = note => {
try {
return [`https://sns-video-bd.xhscdn.com/${note.video.consumer.originVideoKey}`];
@ -518,7 +535,7 @@ XHS-Downloader 用户脚本 详细说明:
if (order >= 0 && order <= 2) {
data = extractNotesInfo(order);
if (!invalidDetection(data)) {
alert("提取作品链接失败!受平台限制,未登录状态下无法通过账号主页浏览作品");
runTips("提取作品链接失败!受平台限制,未登录状态下无法通过账号主页浏览作品详情!请登录后重试");
return;
}
} else if (order === 3) {
@ -550,217 +567,282 @@ XHS-Downloader 用户脚本 详细说明:
};
if (typeof JSZip === 'undefined') {
alert("XHS-Downloader 用户脚本依赖库 JSZip 加载失败,作品文件打包下载功能无法使用,请尝试刷新网页或者向作者反馈!");
runTips("XHS-Downloader 用户脚本依赖库 JSZip 加载失败,作品文件打包下载功能无法使用,请尝试刷新网页或者向作者反馈!");
}
/* ==================== 样式定义 ==================== */
let style = document.createElement('style');
style.textContent = `
/* 弹窗基础样式 */
#SettingsOverlay {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: rgba(0,0,0,0.32);
backdrop-filter: blur(4px);
display: flex;
justify-content: center;
align-items: center;
z-index: 10000;
animation: fadeIn 0.3s;
}
/* 通用 Overlay三个弹窗共用 */
#SettingsOverlay,
#imageSelectionOverlay,
#textGenericOverlay {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: rgba(0,0,0,0.32);
backdrop-filter: blur(4px);
display: flex;
justify-content: center;
align-items: center;
z-index: 10000;
animation: fadeIn 0.3s;
}
.optimized-scroll-modal {
background: white;
border-radius: 16px;
width: 380px; /* 缩小窗口宽度 */
max-width: 95vw;
box-shadow: 0 10px 20px rgba(0,0,0,0.19), 0 6px 6px rgba(0,0,0,0.23);
overflow: hidden;
animation: scaleUp 0.3s;
}
/* Settings 容器,仅本块特有尺寸 */
.optimized-scroll-modal {
background: white;
border-radius: 16px;
width: 380px; /* 缩小窗口宽度 */
max-width: 95vw;
box-shadow: 0 10px 20px rgba(0,0,0,0.19), 0 6px 6px rgba(0,0,0,0.23);
overflow: hidden;
animation: scaleUp 0.3s;
}
/* 头部样式 */
.modal-header {
padding: 1rem;
border-bottom: 1px solid #eee;
text-align: center;
}
/* 通用头部/内容/底部/按钮(三个弹窗共用) */
.modal-header {
padding: 1rem;
border-bottom: 1px solid #eee;
text-align: center;
}
.modal-header span {
font-size: 1.25rem;
font-weight: 500;
color: #212121;
}
.modal-body {
flex: 1;
padding: 1rem;
overflow-y: auto;
}
.modal-footer {
padding: 1rem;
border-top: 1px solid #eee;
display: flex;
justify-content: flex-end;
gap: 12px;
}
.primary-btn {
background: #2196F3;
color: white;
padding: 8px 24px;
border-radius: 24px;
cursor: pointer;
transition: all 0.2s;
}
.secondary-btn {
background: #f0f0f0;
color: #666;
padding: 8px 24px;
border-radius: 24px;
cursor: pointer;
transition: all 0.2s;
}
.modal-header span {
font-size: 1.25rem;
font-weight: 500;
color: #212121;
}
/* Settings 专用的设置项样式(保持不变) */
.setting-item {
margin: 0.5rem 0;
padding: 10px;
border-radius: 8px;
transition: background 0.2s;
}
.setting-item:hover { background: #f0f0f0; }
.setting-item label {
display: flex;
justify-content: space-between;
align-items: center;
width: 100%;
}
.setting-item label span {
font-size: 1rem;
font-weight: 500;
color: #333;
}
/* 内容区域 */
.modal-body {
padding: 1rem; /* 减小内边距 */
}
.toggle-switch {
position: relative;
width: 40px;
height: 20px;
}
.toggle-switch input { opacity: 0; width: 0; height: 0; }
.slider {
position: absolute;
cursor: pointer;
top: 0; left: 0; right: 0; bottom: 0;
background: #ccc;
transition: 0.4s;
border-radius: 34px;
}
.slider:before {
content: "";
position: absolute;
height: 16px; width: 16px;
left: 2px; bottom: 2px;
background: white;
border-radius: 50%;
transition: 0.4s;
}
input:checked + .slider { background: #2196F3; }
input:checked + .slider:before { transform: translateX(20px); }
/* 设置项样式 */
.setting-item {
margin: 0.5rem 0; /* 减少设置项间距 */
padding: 10px;
border-radius: 8px;
transition: background 0.2s;
}
.number-input {
display: flex;
align-items: center;
border: 1px solid #ddd;
border-radius: 8px;
overflow: hidden;
margin: 6px 0;
}
.number-input input {
width: 60px;
text-align: center;
border: none;
}
.number-button {
padding: 4px 8px;
background: #f0f0f0;
border: none;
cursor: pointer;
transition: all 0.2s;
}
.text-input {
width: 100%;
padding: 8px;
border: 1px solid #ddd;
border-radius: 4px;
font-size: 0.9rem;
margin-top: 8px;
transition: border-color 0.2s;
}
.text-input:focus {
outline: none;
border-color: #2196F3;
box-shadow: 0 0 4px rgba(33, 150, 243, 0.3);
}
.setting-description {
font-size: 0.875rem;
color: #757575;
margin-top: 4px;
line-height: 1.4;
text-align: left;
}
.setting-item:hover {
background: #f0f0f0;
}
.setting-item label {
display: flex;
justify-content: space-between;
align-items: center;
width: 100%;
}
/* 设置项标题 */
.setting-item label span {
font-size: 1rem; /* 增大标题文字 */
font-weight: 500;
color: #333;
}
/* 开关样式 */
.toggle-switch {
position: relative;
width: 40px;
height: 20px;
}
.toggle-switch input {
opacity: 0;
width: 0;
height: 0;
}
.slider {
position: absolute;
cursor: pointer;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: #ccc;
transition: 0.4s;
border-radius: 34px;
}
.slider:before {
content: "";
position: absolute;
height: 16px;
width: 16px;
left: 2px;
bottom: 2px;
background: white;
border-radius: 50%;
transition: 0.4s;
}
input:checked + .slider {
background: #2196F3;
}
input:checked + .slider:before {
transform: translateX(20px);
}
/* 数值输入 */
.number-input {
display: flex;
align-items: center;
border: 1px solid #ddd;
border-radius: 8px;
overflow: hidden;
margin: 6px 0;
}
.number-input input {
width: 60px;
text-align: center;
border: none;
}
.number-button {
padding: 4px 8px;
background: #f0f0f0;
border: none;
cursor: pointer;
transition: all 0.2s;
}
/* 文本输入框 */
.text-input {
width: 100%;
padding: 8px;
border: 1px solid #ddd;
border-radius: 4px;
font-size: 0.9rem;
margin-top: 8px; /* 增加与标题的距离 */
transition: border-color 0.2s;
}
.text-input:focus {
outline: none;
border-color: #2196F3;
box-shadow: 0 0 4px rgba(33, 150, 243, 0.3);
}
/* 设置项说明 */
.setting-description {
font-size: 0.875rem;
color: #757575;
margin-top: 4px;
line-height: 1.4;
text-align: left; /* 左对齐 */
}
/* 底部按钮 */
.modal-footer {
padding: 1rem;
border-top: 1px solid #eee;
display: flex;
justify-content: flex-end;
gap: 12px;
}
.primary-btn {
background: #2196F3;
color: white;
padding: 8px 24px;
border-radius: 24px;
cursor: pointer;
transition: all 0.2s;
}
.secondary-btn {
background: #f0f0f0;
color: #666;
padding: 8px 24px;
border-radius: 24px;
cursor: pointer;
transition: all 0.2s;
}
/* 动画 */
@keyframes fadeIn {
from { opacity: 0; }
to { opacity: 1; }
}
@keyframes scaleUp {
from { transform: scale(0.98); }
to { transform: scale(1); }
}
/* 通用动画:统一定义一次 */
@keyframes fadeIn { from { opacity: 0; } to { opacity: 1; } }
@keyframes scaleUp { from { transform: scale(0.98); } to { transform: scale(1); } }
@keyframes fadeOut { from { opacity: 1; } to { opacity: 0; } }
`;
document.head.appendChild(style);
// 覆盖修改:文本弹窗样式
(() => {
if (!document.getElementById('textModalStyle')) {
const style = document.createElement('style');
style.id = 'textModalStyle';
style.textContent = `
/* 仅文本弹窗容器特有的尺寸与外观 */
.text-generic-modal {
background: #fff;
border-radius: 16px;
width: 80%;
max-width: 700px;
max-height: 80vh;
box-shadow: 0 10px 20px rgba(0,0,0,0.19), 0 6px 6px rgba(0,0,0,0.23);
overflow: hidden;
animation: scaleUp 0.3s;
display: flex;
flex-direction: column;
}
/* 仅该弹窗使用的文本内容样式 */
.text-content {
white-space: pre-wrap;
word-break: break-word;
color: #1e272e;
line-height: 1.6;
font-size: 0.95rem;
user-select: text;
}
`;
document.head.appendChild(style);
}
})();
/**
* 显示文本弹窗手动调用
* @param {Object} opts
* @param {string} opts.title 标题
* @param {string} opts.text 文本内容
* @param {'confirm'|'info'} opts.mode 模式confirm=确认+关闭info=仅关闭
* @param {string} [opts.confirmText='确认'] 确认按钮文案 confirm 模式生效
* @param {string} [opts.closeText='关闭'] 关闭按钮文案
* @returns {Promise<boolean>} confirm 返回 true关闭/点遮罩返回 false
*/
function showTextModal(opts) {
const {
title = '提示', text = '', mode = 'info', confirmText = '确认', closeText = '关闭',
} = opts || {};
if (document.getElementById('textGenericOverlay')) {
return Promise.resolve(false);
}
return new Promise((resolve) => {
const overlay = document.createElement('div');
overlay.id = 'textGenericOverlay';
const modal = document.createElement('div');
modal.className = 'text-generic-modal';
const header = document.createElement('div');
header.className = 'modal-header';
header.innerHTML = `<span>${title}</span>`;
const body = document.createElement('div');
body.className = 'modal-body';
const content = document.createElement('div');
content.className = 'text-content';
content.textContent = text ?? '';
body.appendChild(content);
const footer = document.createElement('div');
footer.className = 'modal-footer';
if (mode === 'confirm') {
const okBtn = document.createElement('button');
okBtn.className = 'primary-btn';
okBtn.textContent = confirmText;
okBtn.addEventListener('click', () => close(true));
footer.appendChild(okBtn);
}
const closeBtn = document.createElement('button');
closeBtn.className = 'secondary-btn';
closeBtn.textContent = closeText;
closeBtn.addEventListener('click', () => close(false));
footer.appendChild(closeBtn);
modal.appendChild(header);
modal.appendChild(body);
modal.appendChild(footer);
overlay.appendChild(modal);
document.body.appendChild(overlay);
function close(result) {
overlay.style.animation = 'fadeOut 0.2s';
setTimeout(() => {
overlay.remove();
resolve(result);
}, 200);
}
overlay.addEventListener('click', (e) => {
if (e.target === overlay) close(false);
});
});
}
// 创建开关项
const createSettingItem = ({label, description, checked}) => {
const item = document.createElement('div');
@ -962,155 +1044,74 @@ XHS-Downloader 用户脚本 详细说明:
/* ==================== 样式定义 ==================== */
style = document.createElement('style');
style.textContent = `
/* 弹窗基础样式 */
#imageSelectionOverlay {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: rgba(0,0,0,0.32);
backdrop-filter: blur(4px);
display: flex;
justify-content: center;
align-items: center;
z-index: 10000;
animation: fadeIn 0.3s;
}
/* 图片选择弹窗:仅容器尺寸与自身网格等特有样式 */
.image-selection-modal {
background: white;
border-radius: 16px;
width: 80%;
max-width: 900px;
max-height: 90vh;
box-shadow: 0 10px 20px rgba(0,0,0,0.19), 0 6px 6px rgba(0,0,0,0.23);
overflow: hidden;
animation: scaleUp 0.3s;
display: flex;
flex-direction: column;
}
.image-selection-modal {
background: white;
border-radius: 16px;
width: 80%;
max-width: 900px;
max-height: 90vh;
box-shadow: 0 10px 20px rgba(0,0,0,0.19), 0 6px 6px rgba(0,0,0,0.23);
overflow: hidden;
animation: scaleUp 0.3s;
display: flex;
flex-direction: column;
}
/* 图片网格等仅此弹窗拥有的样式 */
.image-grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(120px, 1fr));
gap: 12px;
}
.image-item {
position: relative;
border-radius: 8px;
overflow: hidden;
cursor: pointer;
transition: all 0.2s;
border: 2px solid transparent;
}
.image-item img {
width: 100%;
height: 100px;
object-fit: cover;
border-radius: 6px;
}
.image-item.selected { border-color: #2196F3; }
/* 头部样式 */
.modal-header {
padding: 1rem;
border-bottom: 1px solid #eee;
text-align: center;
}
.modal-header span {
font-size: 1.25rem;
font-weight: 500;
color: #212121;
}
/* 内容区域 */
.modal-body {
flex: 1;
padding: 1rem;
overflow-y: auto;
}
/* 图片网格 */
.image-grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(120px, 1fr));
gap: 12px;
}
.image-item {
position: relative;
border-radius: 8px;
overflow: hidden;
cursor: pointer;
transition: all 0.2s;
border: 2px solid transparent;
}
.image-item img {
width: 100%;
height: 100px;
object-fit: cover;
border-radius: 6px;
}
.image-item.selected {
border-color: #2196F3;
}
.image-checkbox {
position: absolute;
top: 8px;
right: 8px;
width: 20px;
height: 20px;
opacity: 0;
}
.image-checkbox + label {
position: absolute;
top: 8px;
right: 8px;
width: 20px;
height: 20px;
background: white;
border: 1px solid #ccc;
border-radius: 50%;
cursor: pointer;
display: flex;
justify-content: center;
align-items: center;
transition: all 0.2s;
}
.image-checkbox:checked + label {
background: #2196F3;
border-color: #2196F3;
}
.image-checkbox:checked + label::after {
content: "✓";
color: white;
font-size: 12px;
}
/* 底部按钮 */
.modal-footer {
padding: 1rem;
border-top: 1px solid #eee;
display: flex;
justify-content: flex-end;
gap: 12px;
}
.primary-btn {
background: #2196F3;
color: white;
padding: 8px 24px;
border-radius: 24px;
cursor: pointer;
transition: all 0.2s;
}
.secondary-btn {
background: #f0f0f0;
color: #666;
padding: 8px 24px;
border-radius: 24px;
cursor: pointer;
transition: all 0.2s;
}
/* 动画 */
@keyframes fadeIn {
from { opacity: 0; }
to { opacity: 1; }
}
@keyframes scaleUp {
from { transform: scale(0.98); }
to { transform: scale(1); }
}
.image-checkbox {
position: absolute;
top: 8px;
right: 8px;
width: 20px;
height: 20px;
opacity: 0;
}
.image-checkbox + label {
position: absolute;
top: 8px;
right: 8px;
width: 20px;
height: 20px;
background: white;
border: 1px solid #ccc;
border-radius: 50%;
cursor: pointer;
display: flex;
justify-content: center;
align-items: center;
transition: all 0.2s;
}
.image-checkbox:checked + label {
background: #2196F3;
border-color: #2196F3;
}
.image-checkbox:checked + label::after {
content: "✓";
color: white;
font-size: 12px;
}
`;
document.head.appendChild(style);