Skip to content

Commit 65ab6d0

Browse files
authored
fix(ui): UI 细节与响应式优化 (#440)
* fix(ui): 七项细节打磨 + 两项响应式优化 - 配置错误 banner 迁移至主题变量,支持暗浅色切换 - custom-alert 访问方式前换行,长 URL 添加 word-break 防溢出 - applyInputFeedback 移除 focus() 避免快捷填充时视点跳转 - map-remove-btn hover rotate→translateY,修复删除按钮旋转 - 手动匹配收藏按钮移至搜索按钮左侧 - episode-item flex 布局+hover 背景+左边框 accent,改善辨识度 - 弹幕词库新增 @wade6716,「大家陪你看」→「弹幕陪你看」 - 响应式:搜索栏手机端输入独占上行、收藏搜索下行 - 响应式:剧集按钮三段宽度 PC:120px、平板:160px、手机:100px * fix(ui): 关闭移动端点按高亮以避免默认蓝色闪现 根因:移动端点击任意可点元素出现的蓝色方框/闪现来自浏览器默认的 -webkit-tap-highlight-color(点按高亮),属 WebKit/Blink 触摸专属机制,PC 端不触发,故表现为仅手机端出现。项目此前未设置该属性,一直沿用 UA 默认蓝色高亮。 该高亮并非无障碍焦点轮廓:键盘焦点由项目既有 :focus-visible 规则提供,仅键盘聚焦生效、鼠标/触摸点按不触发,PC 与手机行为一致;移除点按高亮不会损害键盘导航。 修复:在全局重置的 * 选择器中加入 -webkit-tap-highlight-color: transparent,一次性覆盖所有可点元素。按钮自身已具备 :active 按压反馈(.btn:active / .btn-primary:active / .nav-btn:active / .number-btn:active),触摸按压反馈保留。 * fix(ui): 修复 customAlert 使用 innerHTML 导致的 XSS 漏洞 customAlert 此前通过 messageElement.innerHTML = message.replace(/\n/g, '<br>') 渲染提示消息,会执行 message 中的任意 HTML。其调用方大量拼接不可信内容(error.message、接口返回的 result.message、配置校验 message 等),上游或代理响应若含 <script> 或 <img onerror> 即触发 XSS,可窃取管理页 ADMIN_TOKEN 与 localStorage。 修复方式:改用 messageElement.textContent = message 使 HTML 转义为纯文本;换行需求由 #custom-alert-message 元素的 white-space: pre-line 经 CSS 渲染,与原有 <br> 视觉一致。该元素同时被 customConfirm 复用,两者行为统一。 枚举全部 customAlert/customConfirm 调用点确认:仅含换行符的两条消息(访问方式提示、导入失败项)依赖换行,其余消息均不含换行符,pre-line 对其渲染与 white-space: normal 完全一致,不对其它文字造成负面影响。 * fix: 修复跳转输入框占位符居中、窄屏溢出与字号对齐 手动匹配测试与推送弹幕测试的跳转工具:修复「输入集数」输入框的视觉问题。 - ui/css/components.css.js:.jump-episode-input 新增 max-width:100% 与 min-width:0,使极窄屏下输入框可收缩而不顶出视图;text-align:center 使占位符「输入集数」水平居中。 - 新增 .jump-to-episode > span { font-size:13px },将「跳转到第」「集」提示文字与输入框字号统一(原继承浏览器默认 16px),整行视觉真正对齐。 - 该 input 类仅挂在两个跳转输入框上,text-align 不影响其他文字或容器。 * fix: 统一跳转输入框提示文字字号实现整行视觉对齐 ui/css/components.css.js:补充 .jump-to-episode > span { font-size:13px },将「跳转到第」「集」提示文字与输入框字号统一(原继承浏览器默认 16px),整行视觉真正居中。仅此一处规则,其他跳转框改动已在 e5b2c90 推送。 * fix(ui): 最近数据面板分页展示、标题换行与封面防盗链修复 - 最近数据改为分页渲染,默认展示 5 条,底部加载更多按钮逐步展开 - 番剧标题去除单行省略,改为换行完整显示 - 封面由 background-image 改为 img 并加 referrerpolicy="no-referrer" 规避源站防盗链 - 封面 URL 经 escapeHtml 转义以防 XSS * fix(ui): 子源标题换行与最近数据面板 XSS 修复 - 被合并子源标题去除单行省略,与主标题一致换行完整显示 - 番剧标题、来源名、剧集链接标题统一经 escapeHtml 转义,修复 innerHTML 注入 - 快捷填入按钮由内联 onclick 改为 data-* 属性 + 事件委托,修复 onclick JS 注入
1 parent 62fd0af commit 65ab6d0

7 files changed

Lines changed: 220 additions & 46 deletions

File tree

danmu_api/ui/css/base.css.js

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ export const baseCssContent = /* css */ `
77
margin: 0;
88
padding: 0;
99
box-sizing: border-box;
10+
-webkit-tap-highlight-color: transparent; /* 消除移动端点击时的蓝色框 */
1011
}
1112
1213
:root {
@@ -403,4 +404,4 @@ body::before {
403404
font-size: 17px;
404405
}
405406
}
406-
`;
407+
`;

danmu_api/ui/css/components.css.js

Lines changed: 80 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -969,6 +969,44 @@ body.modal-open {
969969
font-size: 13px;
970970
}
971971
972+
.error-config-banner {
973+
background: var(--theme-panel-strong);
974+
border: 1px solid var(--theme-border);
975+
padding: 15px;
976+
border-radius: var(--app-radius-card-sm);
977+
margin-bottom: 20px;
978+
}
979+
980+
.error-config-title {
981+
color: var(--theme-accent);
982+
margin-top: 0;
983+
font-size: 16px;
984+
}
985+
986+
.error-config-text {
987+
color: var(--theme-muted);
988+
margin-bottom: 10px;
989+
font-size: 14px;
990+
}
991+
992+
.error-config-banner input {
993+
padding: 8px 10px;
994+
border: 1px solid var(--theme-border);
995+
border-radius: 8px;
996+
background: var(--theme-input-bg);
997+
color: var(--theme-text);
998+
}
999+
1000+
.error-config-banner code {
1001+
background: var(--theme-panel-bg);
1002+
border: 1px solid var(--theme-border);
1003+
border-radius: 4px;
1004+
padding: 0.1em 0.35em;
1005+
font-size: 0.85em;
1006+
font-family: ui-monospace, SFMono-Regular, Consolas, monospace;
1007+
word-break: break-all;
1008+
}
1009+
9721010
.close-btn {
9731011
background: var(--theme-panel-strong);
9741012
border: none;
@@ -1955,6 +1993,32 @@ body.modal-open {
19551993
font-weight: 600;
19561994
}
19571995
1996+
.episode-item {
1997+
display: flex;
1998+
justify-content: space-between;
1999+
align-items: center;
2000+
padding: 8px 12px;
2001+
border-radius: 8px;
2002+
gap: 16px;
2003+
transition: background 0.15s;
2004+
}
2005+
2006+
.episode-item:hover {
2007+
background: var(--theme-panel-bg);
2008+
box-shadow: inset 3px 0 0 var(--theme-accent);
2009+
}
2010+
2011+
.episode-item-content {
2012+
min-width: 0;
2013+
font-size: 14px;
2014+
color: var(--theme-text);
2015+
}
2016+
2017+
.episode-item .btn {
2018+
flex-shrink: 0;
2019+
margin-left: auto;
2020+
}
2021+
19582022
.danmu-mode-scroll { background: rgba(94, 196, 219, 0.15); color: #3aafc8; }
19592023
.danmu-mode-top { background: rgba(224, 112, 122, 0.15); color: #d4626c; }
19602024
.danmu-mode-bottom { background: rgba(82, 166, 125, 0.15); color: #459670; }
@@ -1987,12 +2051,19 @@ body.modal-open {
19872051
border: 1px solid var(--theme-border);
19882052
}
19892053
2054+
.jump-to-episode > span {
2055+
font-size: 13px;
2056+
}
2057+
19902058
.jump-episode-input {
19912059
padding: 7px 12px;
19922060
width: 110px;
2061+
max-width: 100%;
2062+
min-width: 0;
19932063
border: 1px solid var(--theme-border);
19942064
border-radius: var(--app-radius-input);
19952065
font-size: 13px;
2066+
text-align: center;
19962067
background: var(--theme-input-bg);
19972068
color: var(--theme-text);
19982069
}
@@ -2522,6 +2593,11 @@ body[data-theme$="-dark"] .heatmap-bar {
25222593
gap: 10px;
25232594
}
25242595
2596+
.recent-data-load-more {
2597+
width: 100%;
2598+
margin-top: 10px;
2599+
}
2600+
25252601
.anime-cache-card {
25262602
background: var(--theme-container-bg);
25272603
border: 1px solid var(--theme-border);
@@ -2545,8 +2621,7 @@ body[data-theme$="-dark"] .heatmap-bar {
25452621
width: 56px;
25462622
height: 76px;
25472623
border-radius: 8px;
2548-
background-size: cover;
2549-
background-position: center;
2624+
object-fit: cover;
25502625
background-color: var(--theme-panel-strong);
25512626
flex-shrink: 0;
25522627
}
@@ -2561,9 +2636,7 @@ body[data-theme$="-dark"] .heatmap-bar {
25612636
font-weight: 700;
25622637
color: var(--theme-text);
25632638
margin-bottom: 4px;
2564-
overflow: hidden;
2565-
text-overflow: ellipsis;
2566-
white-space: nowrap;
2639+
overflow-wrap: break-word;
25672640
}
25682641
25692642
.anime-cache-meta {
@@ -2593,8 +2666,7 @@ body[data-theme$="-dark"] .heatmap-bar {
25932666
width: 36px;
25942667
height: 48px;
25952668
border-radius: 6px;
2596-
background-size: cover;
2597-
background-position: center;
2669+
object-fit: cover;
25982670
background-color: var(--theme-panel-strong);
25992671
flex-shrink: 0;
26002672
}
@@ -2608,9 +2680,7 @@ body[data-theme$="-dark"] .heatmap-bar {
26082680
font-size: 12px;
26092681
font-weight: 600;
26102682
color: var(--theme-text);
2611-
overflow: hidden;
2612-
text-overflow: ellipsis;
2613-
white-space: nowrap;
2683+
overflow-wrap: break-word;
26142684
}
26152685
26162686
.anime-cache-child-actions {

danmu_api/ui/css/forms.css.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -540,7 +540,7 @@ input:checked + .slider:before {
540540
transition: transform 0.22s var(--app-ease-smooth);
541541
}
542542
.map-remove-btn:hover {
543-
transform: rotate(90deg);
543+
transform: translateY(-1px);
544544
}
545545
546546
.map-item-template {

danmu_api/ui/css/responsive.css.js

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -238,4 +238,48 @@ export const responsiveCssContent = /* css */ `
238238
border-radius: 16px;
239239
}
240240
}
241+
242+
.search-actions {
243+
display: flex;
244+
gap: 10px;
245+
margin-top: 5px;
246+
flex-wrap: wrap;
247+
}
248+
249+
/* 手机端:输入框独占第一行,收藏和搜索按钮并排 */
250+
@media (max-width: 500px) {
251+
.search-actions input {
252+
flex-basis: 100%;
253+
min-width: 100%;
254+
order: 1;
255+
}
256+
.search-actions .favorite-action-btn {
257+
flex: 1;
258+
order: 2;
259+
}
260+
.search-actions #manual-search-btn {
261+
flex: 1;
262+
order: 3;
263+
}
264+
}
265+
266+
/* 剧集操作按钮:PC 窄 120px、平板适中 160px、手机窄 100px */
267+
.episode-item .btn,
268+
.jump-episode-btn {
269+
max-width: 120px;
270+
}
271+
272+
@media (min-width: 501px) and (max-width: 768px) {
273+
.episode-item .btn,
274+
.jump-episode-btn {
275+
max-width: 160px;
276+
}
277+
}
278+
279+
@media (max-width: 500px) {
280+
.episode-item .btn,
281+
.jump-episode-btn {
282+
max-width: 100px;
283+
}
284+
}
241285
`;

danmu_api/ui/js/main.js

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ function createCustomAlert() {
88
}
99
1010
// 创建弹窗HTML元素
11-
const alertHTML = '<div class="modal" id="custom-alert-overlay"><div class="modal-content" id="custom-alert-content"><div class="modal-header"><h3 id="custom-alert-title">提示</h3><button class="close-btn" id="custom-alert-close">&times;</button></div><div class="modal-body"><p id="custom-alert-message"></p></div><div class="modal-footer"><button class="btn btn-primary" id="custom-alert-confirm">确定</button></div></div></div>';
11+
const alertHTML = '<div class="modal" id="custom-alert-overlay"><div class="modal-content" id="custom-alert-content"><div class="modal-header"><h3 id="custom-alert-title">提示</h3><button class="close-btn" id="custom-alert-close">&times;</button></div><div class="modal-body"><p id="custom-alert-message" style="word-break: break-all; white-space: pre-line;"></p></div><div class="modal-footer"><button class="btn btn-primary" id="custom-alert-confirm">确定</button></div></div></div>';
1212
1313
// 添加到body
1414
document.body.insertAdjacentHTML('beforeend', alertHTML);
@@ -49,7 +49,7 @@ function customAlert(message, title = '提示') {
4949
5050
// 设置标题和消息
5151
titleElement.textContent = title;
52-
messageElement.textContent = message;
52+
messageElement.textContent = message; // 使用 textContent 避免 message 中的 HTML 被解析执行;换行由元素 CSS white-space: pre-line 渲染
5353
5454
// 显示弹窗
5555
overlay.classList.add('active');
@@ -717,9 +717,9 @@ const DANMAKU_DICT = [
717717
'@huangxd-', '@wan0ge', '@woleigedouer', '@Wo254992', '@lilixu3',
718718
'@Celestials316', '@dyphire', '@piaoyizy', '@xiaoQQya', '@liixing',
719719
'@goodcommunication', '@Mr-Quin', '@chason-zhao', '@DemoJameson',
720-
'@rinnein', '@Lampon', '@zcw199604', 'Mashiro',
720+
'@rinnein', '@Lampon', '@zcw199604', 'Mashiro', '@wade6716',
721721
'请合理使用', '公益服务请适当调高缓存避免滥用',
722-
'有弹幕才有氛围~', '大家陪你看', 'LogVar可能会倒闭但绝对不会变质',
722+
'有弹幕才有氛围~', '弹幕陪你看', 'LogVar可能会倒闭但绝对不会变质',
723723
];
724724
725725
// 共享弹幕发射间隔 0.5~1.5s

0 commit comments

Comments
 (0)