refactor(ui): 前端图标统一为自绘线性 SVG 图标,替换原有 emoji 图标 (#473)
* refactor(ui): 界面图标统一为自绘线性 SVG 图标,替换原有 emoji 图标 界面中充当图标的 emoji 除底部彩色的 TG 链接外,全部替换为统一的线性图标。新增 ui/js/icons.js 图标库(32 个图标,24×24 网格、2px 圆头描边),以 SVG sprite 内联于 页面并通过 <use> 引用;图标尺寸随所在文字字号缩放,描边取 currentColor,颜色随容器、 八种强调色主题与明暗模式适配。 - 按钮、配置分类导航、搜索分组标题、状态行、徽标与映射行的图标全部替换 - 图标与文字并列处改用 flex 容器居中对齐 - 「重新部署」使用云与上行箭头,「弹幕配置」使用评论气泡 - 合并模式徽标切换改为只更新标签文本 - 为 .confirm-merge-btn:disabled 设置文字颜色 * refactor(ui): 配置分类导航统一渲染并补齐配置界面细节 - 映射表编辑界面(TITLE_MAPPING_TABLE、AUTO_MATCH_MAPPING_TABLE)增加「查看最近数据」按钮,仅查看缓存、不参与自动填充 - 配置预览与环境变量配置的分类按钮、搜索分组标题统一为同一渲染函数,消除两份平行实现 - 页头「当前版本」「最新版本」「API 端点」徽标补齐图标并对齐风格,当前版本为主视觉、最新版本为次要 - 各样式表头注释中「ACG 设计系统」描述改为「柔和圆角设计风格」 * feat(ui): 映射表查看数据入口调整与移动端头部、徽标动效优化 - 映射表编辑界面「查看最近数据」按钮移到「添加映射项」右侧并右对齐 - 移动端主标题「LogVar弹幕API」放大,突出主标题层级 - 「最新版本」徽标改为持续呼吸缩放动效,引导点击而不盖过「当前版本」 * feat(ui): 移动端标题微调、系统配置按钮缩小并补充弹幕词库贡献者 - 移动端主标题字号调整为 21px - 移动端系统配置的导出/导入/清理缓存/重新部署按钮缩小 - 弹幕词库新增贡献者 @xlmc、@mz289、@sugarbliss
This commit is contained in:
1 parent
c81f63993c
commit
96602d6a7e
9 files changed
+262
-151
No files matched your search
@@ -1,6 +1,6 @@
|
||||
// language=CSS
|
||||
export const baseCssContent = /* css */ `
|
||||
/* 基础布局样式 — 参照 Bangumi-syncer 设计系统 */
|
||||
/* 基础布局样式 — 参照 Bangumi-syncer 柔和圆角设计风格 */
|
||||
|
||||
/* ============ 全局重置 ============ */
|
||||
* {
|
||||
@@ -206,8 +206,8 @@ body::before {
|
||||
}
|
||||
|
||||
.version-badge {
|
||||
background: var(--theme-accent-soft);
|
||||
color: var(--theme-accent);
|
||||
background: var(--theme-accent);
|
||||
color: #ffffff;
|
||||
padding: 3px 12px;
|
||||
border-radius: 999px;
|
||||
font-weight: 600;
|
||||
@@ -219,16 +219,16 @@ body::before {
|
||||
}
|
||||
|
||||
.update-badge {
|
||||
background: var(--theme-accent);
|
||||
color: #ffffff;
|
||||
background: var(--theme-accent-soft);
|
||||
color: var(--theme-accent);
|
||||
padding: 3px 12px;
|
||||
border-radius: 999px;
|
||||
font-weight: 600;
|
||||
font-size: 11px;
|
||||
animation: pulse 2s infinite;
|
||||
cursor: pointer;
|
||||
text-decoration: none;
|
||||
transition: transform 0.2s ease, box-shadow 0.2s ease;
|
||||
animation: badge-breathe 2.4s ease-in-out infinite;
|
||||
transition: box-shadow 0.2s ease;
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
-webkit-backdrop-filter: blur(8px);
|
||||
@@ -236,10 +236,14 @@ body::before {
|
||||
}
|
||||
|
||||
.update-badge:hover {
|
||||
transform: scale(1.04);
|
||||
box-shadow: var(--app-shadow-sm);
|
||||
}
|
||||
|
||||
@keyframes badge-breathe {
|
||||
0%, 100% { transform: scale(1); }
|
||||
50% { transform: scale(1.05); }
|
||||
}
|
||||
|
||||
.api-endpoint-badge {
|
||||
background: rgba(107, 138, 255, 0.10);
|
||||
color: #6B8AFF;
|
||||
@@ -254,11 +258,6 @@ body::before {
|
||||
backdrop-filter: blur(8px);
|
||||
}
|
||||
|
||||
@keyframes pulse {
|
||||
0%, 100% { opacity: 1; }
|
||||
50% { opacity: 0.6; }
|
||||
}
|
||||
|
||||
/* ============ 内容区 ============ */
|
||||
.content {
|
||||
padding: 24px 28px;
|
||||
@@ -376,32 +375,5 @@ body::before {
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
body {
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.container {
|
||||
border-radius: 0;
|
||||
box-shadow: none;
|
||||
}
|
||||
|
||||
.header {
|
||||
padding: 12px 14px;
|
||||
}
|
||||
|
||||
.content {
|
||||
padding: 16px;
|
||||
}
|
||||
|
||||
.logo {
|
||||
width: 36px;
|
||||
height: 36px;
|
||||
font-size: 20px;
|
||||
}
|
||||
|
||||
.header h1 {
|
||||
font-size: 17px;
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
@@ -1,6 +1,30 @@
|
||||
// language=CSS
|
||||
export const componentsCssContent = /* css */ `
|
||||
/* 组件样式 — 参照 Bangumi-syncer ACG 设计系统 */
|
||||
/* 组件样式 — 参照 Bangumi-syncer 柔和圆角设计风格 */
|
||||
|
||||
/* ============ 图标 ============ */
|
||||
/* 统一线性图标:尺寸随字号(1em),描边取 currentColor */
|
||||
.ui-icon-sprite {
|
||||
position: absolute;
|
||||
width: 0;
|
||||
height: 0;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.ui-icon {
|
||||
display: inline-block;
|
||||
flex: none;
|
||||
width: 1em;
|
||||
height: 1em;
|
||||
vertical-align: -0.125em;
|
||||
}
|
||||
|
||||
/* 图标与文字成对出现时的对齐容器(flex 居中) */
|
||||
.ui-icon-label {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 0.4em;
|
||||
}
|
||||
|
||||
/* ============ 标签导航 ============ */
|
||||
.nav-buttons {
|
||||
@@ -152,7 +176,10 @@ export const componentsCssContent = /* css */ `
|
||||
font-size: 13px;
|
||||
font-weight: 500;
|
||||
transition: all 0.22s var(--app-ease-smooth);
|
||||
display: inline-block;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 6px;
|
||||
text-align: center;
|
||||
line-height: 1.4;
|
||||
}
|
||||
@@ -705,7 +732,7 @@ body[data-theme] .favorite-schedule-btn:disabled:hover {
|
||||
display: grid;
|
||||
place-items: center;
|
||||
color: var(--theme-muted);
|
||||
font-size: 24px;
|
||||
font-size: 16px;
|
||||
line-height: 1;
|
||||
}
|
||||
|
||||
@@ -716,7 +743,7 @@ body[data-theme] .favorite-schedule-btn:disabled:hover {
|
||||
.preview-group-heading {
|
||||
min-height: 32px;
|
||||
display: flex;
|
||||
align-items: baseline;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: 10px;
|
||||
padding: 0 2px 8px;
|
||||
@@ -826,6 +853,9 @@ body[data-theme] .favorite-schedule-btn:disabled:hover {
|
||||
|
||||
.preview-copy-btn {
|
||||
padding: 0;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
@@ -1737,6 +1767,7 @@ body[data-theme] input[type="checkbox"].app-checkbox:focus-visible {
|
||||
|
||||
.confirm-merge-btn:disabled {
|
||||
background: var(--theme-panel-strong);
|
||||
color: var(--theme-muted);
|
||||
cursor: not-allowed;
|
||||
box-shadow: none;
|
||||
}
|
||||
|
||||
@@ -49,7 +49,7 @@ export const responsiveCssContent = /* css */ `
|
||||
}
|
||||
|
||||
.header h1 {
|
||||
font-size: 19px;
|
||||
font-size: 21px;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
@@ -214,7 +214,7 @@ export const responsiveCssContent = /* css */ `
|
||||
/* ============ 小屏手机 (<= 480px) ============ */
|
||||
@media (max-width: 480px) {
|
||||
.header h1 {
|
||||
font-size: 18px;
|
||||
font-size: 21px;
|
||||
}
|
||||
|
||||
.nav-btn {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
// language=CSS
|
||||
export const themesCssContent = /* css */ `
|
||||
/* 设计令牌与 ACG 七色主题 + 独立明暗切换 — 参照 Bangumi-syncer 设计系统 */
|
||||
/* 设计令牌与七色主题 + 独立明暗切换 — 参照 Bangumi-syncer 柔和圆角设计风格 */
|
||||
|
||||
/* ============ 全局设计令牌 ============ */
|
||||
:root {
|
||||
@@ -216,7 +216,6 @@ body[data-theme] .theme-option:focus-visible, body[data-theme] .btn:focus-visibl
|
||||
.theme-option-label { font-size: 11px; font-weight: 600; white-space: nowrap; }
|
||||
.config-transfer-btn { display: inline-flex; align-items: center; gap: 7px; }
|
||||
.env-toolbar-actions .btn { align-items: center; display: inline-flex; justify-content: center; gap: 7px; line-height: 1.2; min-height: 38px; white-space: nowrap; }
|
||||
.config-transfer-icon { font-size: 20px; font-weight: 700; line-height: 1; }
|
||||
|
||||
/* ============ 自定义滚动条 ============ */
|
||||
::-webkit-scrollbar { width: 8px; height: 8px; }
|
||||
@@ -235,7 +234,7 @@ body[data-color-scheme="dark"] #bg-danmaku-layer span { opacity: 0.08 !important
|
||||
.env-section-header { align-items: stretch !important; }
|
||||
.env-section-header > div:first-child { min-width: 0; width: 100%; }
|
||||
.env-toolbar-actions { display: grid !important; grid-template-columns: repeat(2, minmax(0, 1fr)); width: 100%; }
|
||||
.env-toolbar-actions .btn { flex: none; min-height: 44px; padding: 10px 8px; width: 100%; }
|
||||
.env-toolbar-actions .btn { flex: none; min-height: 34px; padding: 6px 10px; width: 100%; }
|
||||
.theme-settings { align-items: stretch; flex-direction: column; }
|
||||
.theme-options { grid-template-columns: repeat(3, minmax(0, 1fr)); }
|
||||
}
|
||||
|
||||
@@ -0,0 +1,107 @@
|
||||
// language=JavaScript
|
||||
// 界面图标库:集中定义界面使用的线性图标,统一 24×24 网格、2px 圆头描边。
|
||||
// UI_ICON_PATHS —— 图标名到 SVG 图形的映射;
|
||||
// iconsSpriteContent —— 内联 SVG sprite,供 <use> 引用;
|
||||
// renderIcon / iconJsContent —— 分别供服务端模板与浏览器动态内容生成 <use> 引用。
|
||||
// 图标颜色与尺寸统一由 components.css.js 的 .ui-icon 控制。
|
||||
|
||||
// 图标几何数据:名称 -> symbol 内部图形
|
||||
const UI_ICON_PATHS = {
|
||||
// ===== 明暗模式切换 =====
|
||||
// 太阳:明暗切换按钮在暗色模式下显示
|
||||
sun: '<circle cx="12" cy="12" r="4"/><path d="M12 2v2M12 20v2M4.93 4.93l1.42 1.42M17.65 17.65l1.42 1.42M2 12h2M20 12h2M6.35 17.65l-1.42 1.42M19.07 4.93l-1.42 1.42"/>',
|
||||
// 月亮:明暗切换按钮在亮色模式下显示
|
||||
moon: '<path d="M21 12.79A9 9 0 1 1 11.21 3a7 7 0 0 0 9.79 9.79z"/>',
|
||||
|
||||
// ===== 状态提示 =====
|
||||
// 星光:页头「最新版本」徽标
|
||||
sparkles: '<path d="M12 3l1.8 5.2L19 10l-5.2 1.8L12 17l-1.8-5.2L5 10l5.2-1.8z"/><path d="M18.4 15.3l.7 2 2 .7-2 .7-.7 2-.7-2-2-.7 2-.7z"/>',
|
||||
// 标签:页头「当前版本」徽标
|
||||
tag: '<path d="M20.59 13.41l-7.17 7.17a2 2 0 0 1-2.83 0L2 12V2h10l8.59 8.59a2 2 0 0 1 0 2.82z"/><path d="M7 7h.01"/>',
|
||||
// 警告三角:配置获取失败、Cookie/API Key 未配置、检测失败
|
||||
'alert-triangle': '<path d="M10.29 3.86L1.82 18a2 2 0 0 0 1.71 3h16.94a2 2 0 0 0 1.71-3L13.71 3.86a2 2 0 0 0-3.42 0z"/><path d="M12 9v4M12 17h.01"/>',
|
||||
// 成功圆:已登录、扫码登录成功、连通性测试通过
|
||||
'check-circle': '<circle cx="12" cy="12" r="9"/><path d="M8.4 12.4l2.5 2.5 4.7-4.9"/>',
|
||||
// 失败圆:Cookie 失效、二维码过期、连通性测试失败
|
||||
'x-circle': '<circle cx="12" cy="12" r="9"/><path d="M15 9l-6 6M9 9l6 6"/>',
|
||||
// 时钟:等待扫码、正在生成二维码
|
||||
clock: '<circle cx="12" cy="12" r="9"/><path d="M12 7v5.2l3.2 1.9"/>',
|
||||
|
||||
// ===== 通用操作 =====
|
||||
// 刷新:刷新日志、刷新请求记录
|
||||
'refresh-cw': '<path d="M21 3v5h-5"/><path d="M3 21v-5h5"/><path d="M3.51 9a9 9 0 0 1 14.85-3.36L21 8"/><path d="M20.49 15a9 9 0 0 1-14.85 3.36L3 16"/>',
|
||||
// 垃圾桶:清空日志、清理缓存
|
||||
'trash-2': '<path d="M3 6h18"/><path d="M19 6v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6"/><path d="M8 6V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2"/><path d="M10 11v6M14 11v6"/>',
|
||||
// 上传(托盘 + 向上箭头):导出配置文件
|
||||
upload: '<path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"/><path d="M17 8l-5-5-5 5"/><path d="M12 3v12"/>',
|
||||
// 下载(托盘 + 向下箭头):导入配置文件
|
||||
download: '<path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"/><path d="M7 10l5 5 5-5"/><path d="M12 15V3"/>',
|
||||
// 云 + 上行箭头:重新部署系统
|
||||
'cloud-up': '<path d="M17.5 11H6.5a3 3 0 0 1-.5-5A6 6 0 0 1 17 6a3 3 0 0 1 .5 5z"/><path d="M12 21v-6.6"/><path d="M8.7 17.7l3.3-3.3 3.3 3.3"/>',
|
||||
// 复制:复制配置值
|
||||
copy: '<rect x="9" y="9" width="12" height="12" rx="2"/><path d="M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1"/>',
|
||||
// 软盘:Bilibili Cookie 待保存提示
|
||||
save: '<path d="M19 21H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h11l5 5v11a2 2 0 0 1-2 2z"/><path d="M17 21v-8H7v8"/><path d="M7 3v5h8"/>',
|
||||
|
||||
// ===== 勾选与方向 =====
|
||||
// 对勾:复制成功、合并确认、映射「匹配」状态
|
||||
check: '<path d="M20 6L9 17l-5-5"/>',
|
||||
// 叉:映射「落单」状态
|
||||
x: '<path d="M18 6L6 18M6 6l12 12"/>',
|
||||
// 向上箭头:数字步进器增大
|
||||
'chevron-up': '<path d="M18 15l-6-6-6 6"/>',
|
||||
// 向下箭头:数字步进器减小
|
||||
'chevron-down': '<path d="M6 9l6 6 6-6"/>',
|
||||
// 向右箭头:总览卡片进入指示
|
||||
'chevron-right': '<path d="M9 18l6-6-6-6"/>',
|
||||
|
||||
// ===== 导航与分类 =====
|
||||
// 链接:API 配置分类、开启合并模式、被合并源数量徽标
|
||||
link: '<path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71"/><path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71"/>',
|
||||
// 断开链接:关闭合并模式
|
||||
unlink: '<path d="M18.84 12.25l1.72-1.71a5 5 0 0 0-7.07-7.07l-1.72 1.71"/><path d="M5.17 11.75l-1.71 1.71a5 5 0 0 0 7.07 7.07l1.71-1.71"/><path d="M8 2v3M2 8h3M16 22v-3M22 16h-3"/>',
|
||||
// 叠层:源配置分类
|
||||
layers: '<path d="M12 2L2 7l10 5 10-5-10-5z"/><path d="M2 17l10 5 10-5"/><path d="M2 12l10 5 10-5"/>',
|
||||
// 放大镜:匹配配置分类、检测中状态
|
||||
search: '<circle cx="11" cy="11" r="7"/><path d="M20.5 20.5l-4.4-4.4"/>',
|
||||
// 评论气泡(方形 + 文本行):弹幕配置分类
|
||||
comment: '<path d="M21 15a2 2 0 0 1-2 2H7l-4 4V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2z"/><path d="M8 8h8M8 12h5"/>',
|
||||
// 数据库(圆柱):缓存配置分类
|
||||
database: '<ellipse cx="12" cy="5" rx="9" ry="3"/><path d="M3 5v14c0 1.66 4.03 3 9 3s9-1.34 9-3V5"/><path d="M3 12c0 1.66 4.03 3 9 3s9-1.34 9-3"/>',
|
||||
// 齿轮:系统配置分类
|
||||
settings: '<circle cx="12" cy="12" r="3"/><path d="M19.4 15a1.65 1.65 0 0 0 .33 1.82l.06.06a2 2 0 1 1-2.83 2.83l-.06-.06a1.65 1.65 0 0 0-1.82-.33 1.65 1.65 0 0 0-1 1.51V21a2 2 0 1 1-4 0v-.09A1.65 1.65 0 0 0 9 19.4a1.65 1.65 0 0 0-1.82.33l-.06.06a2 2 0 1 1-2.83-2.83l.06-.06a1.65 1.65 0 0 0 .33-1.82 1.65 1.65 0 0 0-1.51-1H3a2 2 0 1 1 0-4h.09A1.65 1.65 0 0 0 4.6 9a1.65 1.65 0 0 0-.33-1.82l-.06-.06a2 2 0 1 1 2.83-2.83l.06.06A1.65 1.65 0 0 0 9 4.6a1.65 1.65 0 0 0 1-1.51V3a2 2 0 1 1 4 0v.09a1.65 1.65 0 0 0 1 1.51 1.65 1.65 0 0 0 1.82-.33l.06-.06a2 2 0 1 1 2.83 2.83l-.06.06a1.65 1.65 0 0 0-.33 1.82V9a1.65 1.65 0 0 0 1.51 1H21a2 2 0 1 1 0 4h-.09a1.65 1.65 0 0 0-1.51 1z"/>',
|
||||
// 四宫格:配置总览按钮
|
||||
'layout-grid': '<rect x="3" y="3" width="7" height="7" rx="1.5"/><rect x="14" y="3" width="7" height="7" rx="1.5"/><rect x="14" y="14" width="7" height="7" rx="1.5"/><rect x="3" y="14" width="7" height="7" rx="1.5"/>',
|
||||
// 列表:展开 / 收起映射详情
|
||||
list: '<path d="M8 6h13M8 12h13M8 18h13"/><path d="M3.5 6h.01M3.5 12h.01M3.5 18h.01"/>',
|
||||
// 柱状图:查看最近数据
|
||||
'bar-chart': '<path d="M3 21h18"/><path d="M6 21v-9M12 21V5M18 21v-6"/>',
|
||||
// 胶片:剧集数量徽标
|
||||
film: '<rect x="2" y="3" width="20" height="18" rx="2"/><path d="M7 3v18M17 3v18M2 9h5M2 15h5M17 9h5M17 15h5"/>',
|
||||
// 二维码:扫码登录
|
||||
'qr-code': '<rect x="3" y="3" width="7" height="7" rx="1"/><rect x="14" y="3" width="7" height="7" rx="1"/><rect x="3" y="14" width="7" height="7" rx="1"/><path d="M14 14h3v3h-3z"/><path d="M21 14v3M21 21h.01M14 21h3"/>',
|
||||
// 锥形瓶:AI API Key 连通性测试
|
||||
flask: '<path d="M10 2v7.3L4.6 18.9A1.5 1.5 0 0 0 5.9 21h12.2a1.5 1.5 0 0 0 1.3-2.1L14 9.3V2"/><path d="M8.5 2h7"/><path d="M7.2 14h9.6"/>',
|
||||
};
|
||||
|
||||
// 内联 SVG sprite:集中所有 <symbol> 定义,供 <use href="#icon-..."> 引用
|
||||
export const iconsSpriteContent = `<svg class="ui-icon-sprite" aria-hidden="true" focusable="false">${Object.entries(UI_ICON_PATHS).map(([name, paths]) => `<symbol id="icon-${name}" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">${paths}</symbol>`).join('')}</svg>`;
|
||||
|
||||
// 服务端模板用(template.js 在 Node 中拼接 HTML 时调用):生成引用 sprite 的图标
|
||||
export function renderIcon(name, className = 'ui-icon') {
|
||||
return `<svg class="${className}" aria-hidden="true" focusable="false"><use href="#icon-${name}"></use></svg>`;
|
||||
}
|
||||
|
||||
// 浏览器动态内容用(注入页面 <script>):与 renderIcon 保持同一份标记结构
|
||||
export const iconJsContent = /* javascript */ `
|
||||
function uiIcon(name, className = 'ui-icon') {
|
||||
return '<svg class="' + className + '" aria-hidden="true" focusable="false"><use href="#icon-' + name + '"></use></svg>';
|
||||
}
|
||||
|
||||
// 以图标 + 纯文本渲染状态行,图标与文字由 flex 容器居中,文本用 textContent 写入
|
||||
function uiSetStatus(el, name, text) {
|
||||
if (!el) return;
|
||||
el.innerHTML = '<span class="ui-icon-label">' + uiIcon(name) + '<span class="ui-status-text"></span></span>';
|
||||
el.querySelector('.ui-status-text').textContent = text;
|
||||
}
|
||||
`;
|
||||
@@ -692,7 +692,7 @@ function updateColorSchemeToggle() {
|
||||
const btn = document.getElementById('theme-corner-toggle');
|
||||
if (!btn) return;
|
||||
const scheme = document.body.dataset.colorScheme || 'light';
|
||||
btn.textContent = scheme === 'dark' ? '☀' : '🌙';
|
||||
btn.innerHTML = uiIcon(scheme === 'dark' ? 'sun' : 'moon');
|
||||
btn.title = scheme === 'dark' ? '切换浅色模式' : '切换暗色模式';
|
||||
}
|
||||
|
||||
@@ -719,6 +719,7 @@ const DANMAKU_DICT = [
|
||||
'@Celestials316', '@dyphire', '@piaoyizy', '@xiaoQQya', '@liixing',
|
||||
'@goodcommunication', '@Mr-Quin', '@chason-zhao', '@DemoJameson',
|
||||
'@rinnein', '@Lampon', '@zcw199604', 'Mashiro', '@wade6716',
|
||||
'@xlmc', '@mz289', '@sugarbliss',
|
||||
'请合理使用', '公益服务请适当调高缓存避免滥用',
|
||||
'有弹幕才有氛围~', '弹幕陪你看', 'LogVar可能会倒闭但绝对不会变质',
|
||||
];
|
||||
|
||||
+35
-33
@@ -3,14 +3,34 @@ export const previewJsContent = /* javascript */ `
|
||||
const previewCategoryOrder = ['api', 'source', 'match', 'danmu', 'cache', 'system'];
|
||||
|
||||
const previewCategoryMeta = {
|
||||
api: { label: '🔗 API 配置', description: '访问凭证与请求控制' },
|
||||
source: { label: '📜 源配置', description: '弹幕源、VOD 服务与平台凭证' },
|
||||
match: { label: '🔍 匹配配置', description: '标题处理、匹配策略与 AI 服务' },
|
||||
danmu: { label: '🔣 弹幕配置', description: '过滤、转换、输出与时间调整' },
|
||||
cache: { label: '💾 缓存配置', description: '缓存时效、容量与 Redis 服务' },
|
||||
system: { label: '⚙️ 系统配置', description: '界面、网络、部署与安全设置' }
|
||||
api: { icon: 'link', label: 'API 配置', description: '访问凭证与请求控制' },
|
||||
source: { icon: 'layers', label: '源配置', description: '弹幕源、VOD 服务与平台凭证' },
|
||||
match: { icon: 'search', label: '匹配配置', description: '标题处理、匹配策略与 AI 服务' },
|
||||
danmu: { icon: 'comment', label: '弹幕配置', description: '过滤、转换、输出与时间调整' },
|
||||
cache: { icon: 'database', label: '缓存配置', description: '缓存时效、容量与 Redis 服务' },
|
||||
system: { icon: 'settings', label: '系统配置', description: '界面、网络、部署与安全设置' }
|
||||
};
|
||||
|
||||
// 分类按钮(图标 + 名称 + 计数):配置预览与环境变量配置两个导航共用
|
||||
function renderCategoryNavButton(category, count, isActive, onClickExpr) {
|
||||
return \`
|
||||
<button
|
||||
type="button"
|
||||
class="preview-category-btn\${isActive ? ' active' : ''}"
|
||||
onclick="\${onClickExpr}"
|
||||
aria-pressed="\${isActive}"
|
||||
>
|
||||
<span class="ui-icon-label">\${uiIcon(previewCategoryMeta[category].icon)}\${previewCategoryMeta[category].label}</span>
|
||||
<span class="preview-category-count">\${count}</span>
|
||||
</button>
|
||||
\`;
|
||||
}
|
||||
|
||||
// 分类分组标题(图标 + 名称):搜索结果分组标题共用
|
||||
function renderCategoryHeading(category) {
|
||||
return \`<h3 class="ui-icon-label">\${uiIcon(previewCategoryMeta[category].icon)} \${previewCategoryMeta[category].label}</h3>\`;
|
||||
}
|
||||
|
||||
const previewGroupDefinitions = {
|
||||
api: [
|
||||
{ name: '访问认证', keys: ['TOKEN', 'ADMIN_TOKEN', 'FAVORITE_REQUIRE_ADMIN'] },
|
||||
@@ -123,32 +143,14 @@ function renderPreviewNavigation() {
|
||||
aria-pressed="\${inOverview}"
|
||||
\${inOverview ? '' : 'title="返回总览"'}
|
||||
>
|
||||
<span>🗂\uFE0E 总览</span>
|
||||
<span class="ui-icon-label">\${uiIcon('layout-grid')}总览</span>
|
||||
<span class="preview-category-count">\${totalCount}</span>
|
||||
</button>
|
||||
\`;
|
||||
|
||||
const categories = [
|
||||
...previewCategoryOrder.map(category => ({
|
||||
key: category,
|
||||
label: previewCategoryMeta[category].label,
|
||||
count: (previewState.categorizedVars[category] || []).length
|
||||
}))
|
||||
];
|
||||
|
||||
navigation.innerHTML = overviewBtn + categories.map(category => {
|
||||
const isActive = !previewState.query && previewState.activeCategory === category.key;
|
||||
return \`
|
||||
<button
|
||||
type="button"
|
||||
class="preview-category-btn\${isActive ? ' active' : ''}"
|
||||
onclick="selectPreviewCategory('\${category.key}')"
|
||||
aria-pressed="\${isActive}"
|
||||
>
|
||||
<span>\${category.label}</span>
|
||||
<span class="preview-category-count">\${category.count}</span>
|
||||
</button>
|
||||
\`;
|
||||
navigation.innerHTML = overviewBtn + previewCategoryOrder.map(category => {
|
||||
const isActive = !previewState.query && previewState.activeCategory === category;
|
||||
return renderCategoryNavButton(category, (previewState.categorizedVars[category] || []).length, isActive, "selectPreviewCategory('" + category + "')");
|
||||
}).join('');
|
||||
}
|
||||
|
||||
@@ -182,11 +184,11 @@ function renderPreviewOverview() {
|
||||
const count = (previewState.categorizedVars[category] || []).length;
|
||||
return \`
|
||||
<button type="button" class="preview-summary" onclick="selectPreviewCategory('\${category}')">
|
||||
<span class="preview-summary-title">\${meta.label}</span>
|
||||
<span class="preview-summary-title ui-icon-label">\${uiIcon(meta.icon)}\${meta.label}</span>
|
||||
<span class="preview-summary-description">\${meta.description}</span>
|
||||
<span class="preview-summary-side">
|
||||
<span class="preview-summary-count">\${count}</span>
|
||||
<span class="preview-summary-arrow" aria-hidden="true">›</span>
|
||||
<span class="preview-summary-arrow" aria-hidden="true">\${uiIcon('chevron-right')}</span>
|
||||
</span>
|
||||
</button>
|
||||
\`;
|
||||
@@ -248,7 +250,7 @@ function renderPreviewSearchResults(query) {
|
||||
html += \`
|
||||
<section class="preview-group preview-search-group">
|
||||
<div class="preview-group-heading">
|
||||
<h3>\${previewCategoryMeta[category].label}</h3>
|
||||
\${renderCategoryHeading(category)}
|
||||
<span>\${matches.length} 项</span>
|
||||
</div>
|
||||
<div class="preview-list">
|
||||
@@ -277,7 +279,7 @@ function renderPreviewItem(item, category, index) {
|
||||
<code class="preview-value\${isLong ? ' is-collapsed' : ''}" id="\${valueId}">\${escapeHtml(value)}</code>
|
||||
<div class="preview-value-actions">
|
||||
\${isLong ? \`<button type="button" class="preview-action-btn preview-expand-btn" onclick="togglePreviewValue(this)" aria-controls="\${valueId}" aria-expanded="false">展开</button>\` : ''}
|
||||
<button type="button" class="preview-action-btn preview-copy-btn" onclick="copyPreviewValue('\${category}', \${index}, this)" title="复制配置值" aria-label="复制 \${escapeHtml(item.key)} 的值"><span aria-hidden="true">⧉</span></button>
|
||||
<button type="button" class="preview-action-btn preview-copy-btn" onclick="copyPreviewValue('\${category}', \${index}, this)" title="复制配置值" aria-label="复制 \${escapeHtml(item.key)} 的值">\${uiIcon('copy')}</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -381,7 +383,7 @@ async function copyPreviewValue(category, index, button) {
|
||||
}
|
||||
|
||||
const originalContent = button.innerHTML;
|
||||
button.textContent = '✓';
|
||||
button.innerHTML = uiIcon('check');
|
||||
button.classList.add('is-copied');
|
||||
button.setAttribute('title', '已复制');
|
||||
setTimeout(() => {
|
||||
|
||||
@@ -765,8 +765,8 @@ function renderValueInput(item) {
|
||||
<label>值 (\${min}-\${max})</label>
|
||||
<div class="number-picker">
|
||||
<div class="number-controls">
|
||||
<button type="button" class="number-btn" onclick="adjustNumber(1)">▲</button>
|
||||
<button type="button" class="number-btn" onclick="adjustNumber(-1)">▼</button>
|
||||
<button type="button" class="number-btn" onclick="adjustNumber(1)">\${uiIcon('chevron-up')}</button>
|
||||
<button type="button" class="number-btn" onclick="adjustNumber(-1)">\${uiIcon('chevron-down')}</button>
|
||||
</div>
|
||||
<div class="number-display" id="num-value">\${currentValue}</div>
|
||||
</div>
|
||||
@@ -841,7 +841,7 @@ function renderValueInput(item) {
|
||||
\${shouldShowMergeMode ? \`
|
||||
<div class="merge-mode-controls">
|
||||
<div class="merge-mode-btn" id="merge-mode-toggle" onclick="toggleMergeMode()">
|
||||
<span class="icon">🔗️</span> 开启合并模式
|
||||
\${uiIcon('link')} 开启合并模式
|
||||
</div>
|
||||
<div class="form-help" style="margin: 0; margin-left: 10px;">
|
||||
开启后点击下方选项将添加到暂存区,组合后点击 √ 确认
|
||||
@@ -849,7 +849,7 @@ function renderValueInput(item) {
|
||||
</div>
|
||||
|
||||
<div class="staging-area" id="staging-area">
|
||||
<button type="button" class="confirm-merge-btn" onclick="confirmMergeGroup()" title="确认添加该组">✓</button>
|
||||
<button type="button" class="confirm-merge-btn" onclick="confirmMergeGroup()" title="确认添加该组">\${uiIcon('check')}</button>
|
||||
</div>
|
||||
\` : ''}
|
||||
|
||||
@@ -869,7 +869,7 @@ function renderValueInput(item) {
|
||||
\${currentKey === 'MERGE_SOURCE_PAIRS' ? \`
|
||||
<div style="margin-top: 15px; margin-bottom: 8px;">
|
||||
<button type="button" class="btn btn-primary btn-sm" onclick="fetchAndShowRecentData()">
|
||||
📊 查看最近数据
|
||||
\${uiIcon('bar-chart')} 查看最近数据
|
||||
</button>
|
||||
</div>
|
||||
<div id="recent-data-panel" class="recent-data-panel">
|
||||
@@ -914,7 +914,15 @@ function renderValueInput(item) {
|
||||
<button type="button" class="btn btn-danger map-remove-btn" onclick="removeMapItem(this)">删除</button>
|
||||
</div>
|
||||
</div>
|
||||
<button type="button" class="btn btn-primary" onclick="addMapItem()">添加映射项</button>
|
||||
<div style="display: flex; align-items: center; justify-content: space-between;">
|
||||
<button type="button" class="btn btn-primary" onclick="addMapItem()">添加映射项</button>
|
||||
<button type="button" class="btn btn-primary btn-sm" onclick="fetchAndShowRecentData()">
|
||||
\${uiIcon('bar-chart')} 查看最近数据
|
||||
</button>
|
||||
</div>
|
||||
<div id="recent-data-panel" class="recent-data-panel">
|
||||
<div id="recent-data-list"></div>
|
||||
</div>
|
||||
\`;
|
||||
|
||||
bindMapInputSync();
|
||||
@@ -977,7 +985,7 @@ function renderValueInput(item) {
|
||||
添加规则
|
||||
</button>
|
||||
<button type="button" class="btn btn-primary btn-sm" onclick="fetchAndShowRecentData()">
|
||||
📊 查看最近数据
|
||||
\${uiIcon('bar-chart')} 查看最近数据
|
||||
</button>
|
||||
</div>
|
||||
<div id="recent-data-panel" class="recent-data-panel">
|
||||
@@ -1037,12 +1045,12 @@ function renderValueInput(item) {
|
||||
<div class="form-help">支持 OpenAI 兼容的 API,需配合 AI_BASE_URL 和 AI_MODEL 配置使用</div>
|
||||
|
||||
<div class="ai-apikey-status" id="ai-apikey-status">
|
||||
<span class="ai-status-icon">🔍</span>
|
||||
<span class="ai-status-icon">\${uiIcon('search')}</span>
|
||||
<span class="ai-status-text">点击下方按钮测试连通性</span>
|
||||
</div>
|
||||
<div class="ai-apikey-actions" style="margin-bottom: 15px;">
|
||||
<button type="button" class="btn btn-primary btn-sm" id="ai-verify-btn" onclick="verifyAiConnection()">
|
||||
🧪 测试连通性
|
||||
\${uiIcon('flask')} 测试连通性
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
@@ -1053,13 +1061,13 @@ function renderValueInput(item) {
|
||||
container.innerHTML = \`
|
||||
<div class="bili-cookie-editor">
|
||||
<div class="bili-cookie-status" id="bili-cookie-status">
|
||||
<span class="bili-status-icon">🔍</span>
|
||||
<span class="bili-status-icon">\${uiIcon('search')}</span>
|
||||
<span class="bili-status-text">检测中...</span>
|
||||
</div>
|
||||
|
||||
<div class="bili-cookie-actions">
|
||||
<button type="button" class="btn btn-primary btn-sm" onclick="startBilibiliQRLogin()">
|
||||
📱 扫码登录
|
||||
\${uiIcon('qr-code')} 扫码登录
|
||||
</button>
|
||||
</div>
|
||||
|
||||
@@ -1095,7 +1103,7 @@ function renderValueInput(item) {
|
||||
添加规则
|
||||
</button>
|
||||
<button type="button" class="btn btn-primary btn-sm" onclick="fetchAndShowRecentData()">
|
||||
📊 查看最近数据
|
||||
\${uiIcon('bar-chart')} 查看最近数据
|
||||
</button>
|
||||
</div>
|
||||
<div id="recent-data-panel" class="recent-data-panel">
|
||||
@@ -1747,12 +1755,12 @@ function toggleMergeMode() {
|
||||
|
||||
if (isMergeMode) {
|
||||
btn.classList.add('active');
|
||||
btn.innerHTML = '<span class="icon">⛓💥</span> 合并模式已开启,点击关闭';
|
||||
btn.innerHTML = uiIcon('unlink') + ' 合并模式已开启,点击关闭';
|
||||
stagingArea.classList.add('active');
|
||||
renderStagingArea();
|
||||
} else {
|
||||
btn.classList.remove('active');
|
||||
btn.innerHTML = '<span class="icon">🔗️</span> 点击开启合并模式';
|
||||
btn.innerHTML = uiIcon('link') + ' 点击开启合并模式';
|
||||
stagingArea.classList.remove('active');
|
||||
stagingTags = [];
|
||||
}
|
||||
@@ -2380,18 +2388,7 @@ function renderEnvNavigation() {
|
||||
|
||||
navigation.innerHTML = previewCategoryOrder.map(category => {
|
||||
const isActive = !envSearchQuery && currentCategory === category;
|
||||
const count = (envVariables[category] || []).length;
|
||||
return \`
|
||||
<button
|
||||
type="button"
|
||||
class="preview-category-btn\${isActive ? ' active' : ''}"
|
||||
onclick="switchCategory('\${category}')"
|
||||
aria-pressed="\${isActive}"
|
||||
>
|
||||
<span>\${previewCategoryMeta[category].label}</span>
|
||||
<span class="preview-category-count">\${count}</span>
|
||||
</button>
|
||||
\`;
|
||||
return renderCategoryNavButton(category, (envVariables[category] || []).length, isActive, "switchCategory('" + category + "')");
|
||||
}).join('');
|
||||
}
|
||||
|
||||
@@ -2486,7 +2483,7 @@ function renderEnvList() {
|
||||
html += \`
|
||||
<section class="preview-group env-search-group">
|
||||
<div class="preview-group-heading">
|
||||
<h3>\${previewCategoryMeta[category].label}</h3>
|
||||
\${renderCategoryHeading(category)}
|
||||
<span>\${regularMatches.length} 项</span>
|
||||
</div>
|
||||
<div>
|
||||
@@ -2782,7 +2779,7 @@ async function startBilibiliQRLogin() {
|
||||
<div class="modal" id="bili-qr-modal">
|
||||
<div class="modal-content" style="max-width: 400px;">
|
||||
<div class="modal-header">
|
||||
<h3>📱 扫码登录 Bilibili</h3>
|
||||
<h3 class="ui-icon-label">\${uiIcon('qr-code')} 扫码登录 Bilibili</h3>
|
||||
<button class="close-btn" onclick="closeBiliQRModal()">×</button>
|
||||
</div>
|
||||
<div class="modal-body" style="text-align: center;">
|
||||
@@ -2835,7 +2832,7 @@ async function startBilibiliQRLogin() {
|
||||
}
|
||||
} catch (error) {
|
||||
qrLoading.style.display = 'none';
|
||||
qrStatus.textContent = '❌ ' + error.message;
|
||||
uiSetStatus(qrStatus, 'x-circle', error.message);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2859,17 +2856,17 @@ function startBiliQRCheck() {
|
||||
|
||||
switch (code) {
|
||||
case 86101:
|
||||
qrStatus.textContent = '⏳ 等待扫码...';
|
||||
uiSetStatus(qrStatus, 'clock', '等待扫码...');
|
||||
break;
|
||||
case 86090:
|
||||
qrStatus.textContent = '📱 已扫码,请确认';
|
||||
uiSetStatus(qrStatus, 'qr-code', '已扫码,请确认');
|
||||
break;
|
||||
case 86038:
|
||||
qrStatus.textContent = '❌ 二维码已过期';
|
||||
uiSetStatus(qrStatus, 'x-circle', '二维码已过期');
|
||||
clearInterval(biliQRCheckInterval);
|
||||
break;
|
||||
case 0:
|
||||
qrStatus.textContent = '✅ 登录成功!';
|
||||
uiSetStatus(qrStatus, 'check-circle', '登录成功!');
|
||||
clearInterval(biliQRCheckInterval);
|
||||
|
||||
if (result.data.cookie) {
|
||||
@@ -2924,11 +2921,11 @@ async function autoCheckBilibiliCookieStatus() {
|
||||
|
||||
// 如果输入框为空,提示未配置
|
||||
if (!cookie) {
|
||||
statusEl.innerHTML = '<span class="bili-status-icon">⚠️</span><span class="bili-status-text">未配置</span>';
|
||||
statusEl.innerHTML = \`<span class="bili-status-icon">\${uiIcon('alert-triangle')}</span><span class="bili-status-text">未配置</span>\`;
|
||||
return;
|
||||
}
|
||||
|
||||
statusEl.innerHTML = '<span class="bili-status-icon">🔍</span><span class="bili-status-text">检测中...</span>';
|
||||
statusEl.innerHTML = \`<span class="bili-status-icon">\${uiIcon('search')}</span><span class="bili-status-text">检测中...</span>\`;
|
||||
|
||||
// 脱敏后的 *...* 无法直接校验,后端会自动改为校验“已保存”的 Cookie
|
||||
const isMasked = /^[*]+$/.test(cookie);
|
||||
@@ -2957,20 +2954,20 @@ async function autoCheckBilibiliCookieStatus() {
|
||||
|
||||
// 用户手动输入/扫码填入的 Cookie → 提示保存
|
||||
if (!isMasked) {
|
||||
statusEl.innerHTML = \`<span class="bili-status-icon">✅</span><span class="bili-status-text">\${uname}\${leftText} · 请点击保存按钮(Vercel等平台需重新部署后生效)</span>\`;
|
||||
statusEl.innerHTML = \`<span class="bili-status-icon">\${uiIcon('check-circle')}</span><span class="bili-status-text">\${uname}\${leftText} · 请点击保存按钮(Vercel等平台需重新部署后生效)</span>\`;
|
||||
} else {
|
||||
// 脱敏显示时只展示当前已保存 Cookie 的状态
|
||||
statusEl.innerHTML = \`<span class="bili-status-icon">✅</span><span class="bili-status-text">\${uname}\${leftText}</span>\`;
|
||||
statusEl.innerHTML = \`<span class="bili-status-icon">\${uiIcon('check-circle')}</span><span class="bili-status-text">\${uname}\${leftText}</span>\`;
|
||||
}
|
||||
} else {
|
||||
const err = result.data.error || 'Cookie无效或已失效';
|
||||
statusEl.innerHTML = \`<span class="bili-status-icon">❌</span><span class="bili-status-text">\${err},请重新扫码登录并保存</span>\`;
|
||||
statusEl.innerHTML = \`<span class="bili-status-icon">\${uiIcon('x-circle')}</span><span class="bili-status-text">\${err},请重新扫码登录并保存</span>\`;
|
||||
}
|
||||
} else {
|
||||
statusEl.innerHTML = '<span class="bili-status-icon">⚠️</span><span class="bili-status-text">检测失败</span>';
|
||||
statusEl.innerHTML = \`<span class="bili-status-icon">\${uiIcon('alert-triangle')}</span><span class="bili-status-text">检测失败</span>\`;
|
||||
}
|
||||
} catch (error) {
|
||||
statusEl.innerHTML = '<span class="bili-status-icon">⚠️</span><span class="bili-status-text">检测失败</span>';
|
||||
statusEl.innerHTML = \`<span class="bili-status-icon">\${uiIcon('alert-triangle')}</span><span class="bili-status-text">检测失败</span>\`;
|
||||
}
|
||||
}
|
||||
// 显示 Bilibili Cookie 保存提示
|
||||
@@ -2979,7 +2976,7 @@ function showBilibiliCookieSaveHint(text) {
|
||||
if (!statusEl) return;
|
||||
|
||||
const msg = text || '请点击保存按钮,Vercel等平台需重新部署后生效';
|
||||
statusEl.innerHTML = \`<span class="bili-status-icon">💾</span><span class="bili-status-text">\${msg}</span>\`;
|
||||
statusEl.innerHTML = \`<span class="bili-status-icon">\${uiIcon('save')}</span><span class="bili-status-text">\${msg}</span>\`;
|
||||
}
|
||||
|
||||
/* ========================================
|
||||
@@ -2996,7 +2993,7 @@ async function verifyAiConnection() {
|
||||
|
||||
// 如果输入框为空,提示未配置
|
||||
if (!apiKey) {
|
||||
statusEl.innerHTML = '<span class="ai-status-icon">⚠️</span><span class="ai-status-text">请先输入 API Key</span>';
|
||||
statusEl.innerHTML = \`<span class="ai-status-icon">\${uiIcon('alert-triangle')}</span><span class="ai-status-text">请先输入 API Key</span>\`;
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -3005,7 +3002,7 @@ async function verifyAiConnection() {
|
||||
btn.innerHTML = '<span class="loading-spinner-small"></span>';
|
||||
btn.disabled = true;
|
||||
|
||||
statusEl.innerHTML = '<span class="ai-status-icon">🔍</span><span class="ai-status-text">正在测试连通性...</span>';
|
||||
statusEl.innerHTML = \`<span class="ai-status-icon">\${uiIcon('search')}</span><span class="ai-status-text">正在测试连通性...</span>\`;
|
||||
|
||||
// 检查是否为脱敏后的 *...*
|
||||
const isMasked = /^[*]+$/.test(apiKey);
|
||||
@@ -3020,14 +3017,14 @@ async function verifyAiConnection() {
|
||||
const result = await response.json();
|
||||
|
||||
if (result.ok) {
|
||||
statusEl.innerHTML = '<span class="ai-status-icon">✅</span><span class="ai-status-text">' + (result.message || 'AI 服务连通性测试成功') + '</span>';
|
||||
statusEl.innerHTML = \`<span class="ai-status-icon">\${uiIcon('check-circle')}</span><span class="ai-status-text">\${result.message || 'AI 服务连通性测试成功'}</span>\`;
|
||||
statusEl.style.color = 'var(--success-color, #28a745)';
|
||||
} else {
|
||||
statusEl.innerHTML = '<span class="ai-status-icon">❌</span><span class="ai-status-text">' + (result.message || '连通性测试失败') + '</span>';
|
||||
statusEl.innerHTML = \`<span class="ai-status-icon">\${uiIcon('x-circle')}</span><span class="ai-status-text">\${result.message || '连通性测试失败'}</span>\`;
|
||||
statusEl.style.color = 'var(--danger-color, #dc3545)';
|
||||
}
|
||||
} catch (error) {
|
||||
statusEl.innerHTML = '<span class="ai-status-icon">⚠️</span><span class="ai-status-text">测试请求失败: ' + error.message + '</span>';
|
||||
statusEl.innerHTML = \`<span class="ai-status-icon">\${uiIcon('alert-triangle')}</span><span class="ai-status-text">测试请求失败: \${error.message}</span>\`;
|
||||
statusEl.style.color = 'var(--warning-color, #ffc107)';
|
||||
} finally {
|
||||
// 恢复按钮状态
|
||||
@@ -3049,14 +3046,13 @@ function toggleCardSection(btnEl, targetSelector, openText, closeText) {
|
||||
if (!container) return;
|
||||
|
||||
const isHidden = window.getComputedStyle(container).display === 'none';
|
||||
btnEl.querySelector('.cache-badge-label').textContent = isHidden ? closeText : openText;
|
||||
|
||||
if (isHidden) {
|
||||
container.style.display = 'flex';
|
||||
btnEl.innerHTML = closeText;
|
||||
btnEl.classList.add('active');
|
||||
} else {
|
||||
container.style.display = 'none';
|
||||
btnEl.innerHTML = openText;
|
||||
btnEl.classList.remove('active');
|
||||
}
|
||||
}
|
||||
@@ -3072,11 +3068,11 @@ function toggleMapping(btnEl) {
|
||||
const isHidden = window.getComputedStyle(container).display === 'none';
|
||||
if (isHidden) {
|
||||
container.style.display = 'flex';
|
||||
btnEl.innerHTML = '📊 收起映射详情';
|
||||
btnEl.innerHTML = '<span class="ui-icon-label">' + uiIcon('list') + ' 收起映射详情</span>';
|
||||
btnEl.classList.add('active');
|
||||
} else {
|
||||
container.style.display = 'none';
|
||||
btnEl.innerHTML = '📊 展开映射详情';
|
||||
btnEl.innerHTML = '<span class="ui-icon-label">' + uiIcon('list') + ' 展开映射详情</span>';
|
||||
btnEl.classList.remove('active');
|
||||
}
|
||||
}
|
||||
@@ -3213,9 +3209,9 @@ function renderAnimeCachePanel(data, listContainer) {
|
||||
|
||||
let rowHtml = '';
|
||||
if (hasMainMatch && hasChildMatch) {
|
||||
rowHtml = \`<div class="mapping-row"><span class="mapping-status success">✓ 匹配</span> <span class="mapping-text">\${mainSide} ↔ \${childSide}</span></div>\`;
|
||||
rowHtml = \`<div class="mapping-row"><span class="mapping-status success ui-icon-label">\${uiIcon('check')} 匹配</span> <span class="mapping-text">\${mainSide} ↔ \${childSide}</span></div>\`;
|
||||
} else {
|
||||
rowHtml = \`<div class="mapping-row"><span class="mapping-status warning">✗ 落单</span> <span class="mapping-text">\${mainSide} ↔ \${childSide}</span></div>\`;
|
||||
rowHtml = \`<div class="mapping-row"><span class="mapping-status warning ui-icon-label">\${uiIcon('x')} 落单</span> <span class="mapping-text">\${mainSide} ↔ \${childSide}</span></div>\`;
|
||||
}
|
||||
|
||||
parsedRows.push({
|
||||
@@ -3240,7 +3236,7 @@ function renderAnimeCachePanel(data, listContainer) {
|
||||
|
||||
if (mappingRowsHtml) {
|
||||
mappingHtml = \`
|
||||
<div class="child-mapping-toggle" onclick="toggleMapping(this)">📊 展开映射详情</div>
|
||||
<div class="child-mapping-toggle" onclick="toggleMapping(this)"><span class="ui-icon-label">\${uiIcon('list')} 展开映射详情</span></div>
|
||||
<div class="child-mapping-container">
|
||||
\${mappingRowsHtml}
|
||||
</div>
|
||||
@@ -3302,10 +3298,10 @@ function renderAnimeCachePanel(data, listContainer) {
|
||||
if (childrenCount > 0 || episodesCount > 0) {
|
||||
let badges = '';
|
||||
if (episodesCount > 0) {
|
||||
badges += \`<div class="cache-badge badge-episodes" onclick="toggleCardSection(this, '.episodes-list-container', '📺 \${episodesCount} 个剧集', '📺 收起剧集')">📺 \${episodesCount} 个剧集</div>\`;
|
||||
badges += \`<div class="cache-badge badge-episodes" onclick="toggleCardSection(this, '.episodes-list-container', '\${episodesCount} 个剧集', '收起剧集')">\${uiIcon('film')}<span class="cache-badge-label">\${episodesCount} 个剧集</span></div>\`;
|
||||
}
|
||||
if (childrenCount > 0) {
|
||||
badges += \`<div class="cache-badge badge-sources" onclick="toggleCardSection(this, '.merged-children-container', '🔗 \${childrenCount} 个被合并源', '🔗 收起被合并源')">🔗 \${childrenCount} 个被合并源</div>\`;
|
||||
badges += \`<div class="cache-badge badge-sources" onclick="toggleCardSection(this, '.merged-children-container', '\${childrenCount} 个被合并源', '收起被合并源')">\${uiIcon('link')}<span class="cache-badge-label">\${childrenCount} 个被合并源</span></div>\`;
|
||||
}
|
||||
footerHtml = \`<div class="anime-cache-footer">\${badges}</div>\`;
|
||||
}
|
||||
|
||||
+15
-12
@@ -4,6 +4,7 @@ import { componentsCssContent } from "./css/components.css.js";
|
||||
import { formsCssContent } from "./css/forms.css.js";
|
||||
import { responsiveCssContent } from "./css/responsive.css.js";
|
||||
import { themesCssContent } from "./css/themes.css.js";
|
||||
import { iconJsContent, iconsSpriteContent, renderIcon } from "./js/icons.js";
|
||||
import { mainJsContent } from "./js/main.js";
|
||||
import { previewJsContent } from "./js/preview.js";
|
||||
import { logviewJsContent } from "./js/logview.js";
|
||||
@@ -39,6 +40,7 @@ export const HTML_TEMPLATE = /* html */ `
|
||||
|
||||
</head>
|
||||
<body data-theme="globals.uiTheme">
|
||||
${iconsSpriteContent}
|
||||
<script>
|
||||
try {
|
||||
var storedTheme = localStorage.getItem('logvar_ui_theme');
|
||||
@@ -54,7 +56,7 @@ export const HTML_TEMPLATE = /* html */ `
|
||||
</script>
|
||||
<div class="container">
|
||||
<div class="corner-fold"></div>
|
||||
<button class="theme-corner-toggle" id="theme-corner-toggle" onclick="toggleColorScheme()" title="切换明暗模式" aria-label="切换明暗模式">🌙</button>
|
||||
<button class="theme-corner-toggle" id="theme-corner-toggle" onclick="toggleColorScheme()" title="切换明暗模式" aria-label="切换明暗模式">${renderIcon('moon')}</button>
|
||||
<!-- 进度条 -->
|
||||
<div class="progress-container" id="progress-container">
|
||||
<div class="progress-bar" id="progress-bar"></div>
|
||||
@@ -67,12 +69,12 @@ export const HTML_TEMPLATE = /* html */ `
|
||||
<h1>LogVar弹幕API</h1>
|
||||
</div>
|
||||
<div class="version-info">
|
||||
<span class="version-badge">当前版本: <span id="current-version">v${globals.version}</span></span>
|
||||
<span class="version-badge">${renderIcon('tag')} 当前版本: <span id="current-version">v${globals.version}</span></span>
|
||||
<a class="update-badge" id="update-badge" href="https://t.me/s/logvar_danmu_channel" target="_blank" rel="noopener" title="查看更新通知">
|
||||
🎉 最新版本: <span id="latest-version">加载中...</span>
|
||||
${renderIcon('sparkles')} 最新版本: <span id="latest-version">加载中...</span>
|
||||
</a>
|
||||
<span class="api-endpoint-badge" onclick="copyApiEndpoint()" title="点击复制API端点" style="cursor: pointer;">
|
||||
API端点: <span id="api-endpoint" style="color: #4CAF50; font-weight: bold;">加载中...</span>
|
||||
${renderIcon('link')} API端点: <span id="api-endpoint" style="color: #4CAF50; font-weight: bold;">加载中...</span>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
@@ -93,7 +95,7 @@ export const HTML_TEMPLATE = /* html */ `
|
||||
<h2>配置预览</h2>
|
||||
|
||||
<div id="proxy-config-container" class="error-config-banner" style="display: none;">
|
||||
<h3 class="error-config-title">⚠️ 获取配置失败</h3>
|
||||
<h3 class="error-config-title ui-icon-label">${renderIcon('alert-triangle')} 获取配置失败</h3>
|
||||
<p class="error-config-text">
|
||||
检测到无法获取配置。如果您使用了复杂的反向代理:例如将 <code>http://{ip}:9321/</code> 代理到了 <code>http://{ip}:9321/danmu_api/</code>,请在此处手动输入完整的反代后链接(不包含TOKEN和ADMIN_TOKEN的)
|
||||
</p>
|
||||
@@ -138,8 +140,8 @@ export const HTML_TEMPLATE = /* html */ `
|
||||
<h2>日志查看</h2>
|
||||
<div class="log-controls">
|
||||
<div>
|
||||
<button class="btn btn-primary" onclick="refreshLogs()">🔄 刷新日志</button>
|
||||
<button class="btn btn-danger" onclick="clearLogs()">🗑️ 清空日志</button>
|
||||
<button class="btn btn-primary" onclick="refreshLogs()">${renderIcon('refresh-cw')} 刷新日志</button>
|
||||
<button class="btn btn-danger" onclick="clearLogs()">${renderIcon('trash-2')} 清空日志</button>
|
||||
</div>
|
||||
<span style="color: #666;">实时日志监控</span>
|
||||
</div>
|
||||
@@ -291,7 +293,7 @@ export const HTML_TEMPLATE = /* html */ `
|
||||
<h2>请求记录</h2>
|
||||
<div class="log-controls">
|
||||
<div>
|
||||
<button class="btn btn-primary" id="refresh-request-records">🔄 刷新记录</button>
|
||||
<button class="btn btn-primary" id="refresh-request-records">${renderIcon('refresh-cw')} 刷新记录</button>
|
||||
<span id="total-requests-today" style="color: #ff5722; margin-left: 15px; vertical-align: middle; font-size: 1.2em; font-weight: bold;"></span>
|
||||
</div>
|
||||
<span style="color: #666;">云服务部署需要配置redis</span>
|
||||
@@ -343,17 +345,17 @@ export const HTML_TEMPLATE = /* html */ `
|
||||
</div>
|
||||
<div class="env-toolbar-actions" style="display: flex; gap: 10px; flex-wrap: wrap;">
|
||||
<button class="btn btn-primary config-transfer-btn" onclick="exportSystemConfig()" title="下载当前环境变量配置文件">
|
||||
<span class="config-transfer-icon" aria-hidden="true">📤</span> 导出配置
|
||||
${renderIcon('upload')} 导出配置
|
||||
</button>
|
||||
<button class="btn btn-primary config-transfer-btn" onclick="triggerConfigImport()" title="上传 JSON 文件并导入环境变量配置">
|
||||
<span class="config-transfer-icon" aria-hidden="true">📥</span> 导入配置
|
||||
${renderIcon('download')} 导入配置
|
||||
</button>
|
||||
<input type="file" id="config-import-file" accept=".json,application/json" style="display: none;" onchange="importSystemConfigFile(this.files[0])">
|
||||
<button class="btn btn-danger" onclick="showClearCacheModal()" title="清理系统缓存">
|
||||
🗑️ 清理缓存
|
||||
${renderIcon('trash-2')} 清理缓存
|
||||
</button>
|
||||
<button class="btn btn-success" onclick="showDeploySystemModal()" title="重新部署系统">
|
||||
🚀 重新部署
|
||||
${renderIcon('cloud-up')} 重新部署
|
||||
</button>
|
||||
</div>
|
||||
|
||||
@@ -547,6 +549,7 @@ export const HTML_TEMPLATE = /* html */ `
|
||||
</nav>
|
||||
|
||||
<script>
|
||||
${iconJsContent}
|
||||
${mainJsContent}
|
||||
${previewJsContent}
|
||||
${logviewJsContent}
|
||||
|
||||
Reference in new issue
Block a user