From a4adb63e458da2a72cfbee60349c1927f7df7edf Mon Sep 17 00:00:00 2001 From: wjl2023 Date: Wed, 27 May 2026 16:36:41 +0800 Subject: [PATCH] fix(token/flatpickr): dark mode calendar + friendlier date display - Add !important to all Flatpickr CSS overrides to beat CDN specificity - Change dateFormat from ISO 'Y-m-d\TH:i' to 'Y-m-d H:i' (double space) - Update tokGetFiltered to use .replace(/\s+/,'T') for Safari compat --- FRONTEND_BEAUTY_NOTEBOOK.md | 55 +++++++++++++++++++++++++++++ frontends/desktop/static/app.js | 7 ++-- frontends/desktop/static/styles.css | 32 ++++++++--------- 3 files changed, 75 insertions(+), 19 deletions(-) diff --git a/FRONTEND_BEAUTY_NOTEBOOK.md b/FRONTEND_BEAUTY_NOTEBOOK.md index 338d5935..373f474d 100644 --- a/FRONTEND_BEAUTY_NOTEBOOK.md +++ b/FRONTEND_BEAUTY_NOTEBOOK.md @@ -225,3 +225,58 @@ CSS: `.task-elapsed { color: var(--accent); font-size: 12px; font-weight: 600; } 2. `restoreElapsedBadges(sess, box)` 从 `renderAllMessages` 移到 `pollSession` finally 块 — 确保消息已加载 3. `startTaskTimer(sess)` 从最后一条 user 消息的 ts 恢复 taskStartedAt — 运行中任务继续计时 - 验证结果:刷新后 badge 正确显示"已运行 17s"/"已运行 14s",已完成任务静态显示,运行中任务实时计时 + + +--- + +## 9. Feature: Flatpickr 日期选择器 (Token 统计页) + +**状态**:✅ 完成 +**分支**:`feat/flatpickr-datepicker` +**Commit**:5e20cc7 + +### 9.1 改动摘要 + +| # | 文件 | 改动 | 行数 | +|---|------|------|------| +| 1 | index.html | +Flatpickr CDN (CSS + JS + zh locale);input type="text" + class="tok-date" + readonly | +6/-2 | +| 2 | styles.css | .tok-date 样式 + Flatpickr 主题覆盖(全用 CSS 变量,含暗色模式) | +30/-3 | +| 3 | app.js | 替换原生 change 事件为 Flatpickr 初始化 + reset 用 fp.clear() | +6/-4 | + +**总计**:3 files, +44/-8 + +### 9.2 关键决策 + +- **dateFormat: `'Y-m-d\\TH:i'`** — 输出 ISO 格式(如 `2026-05-27T14:30`),Safari 兼容 +- **locale 判断**:`document.documentElement.lang==='en' ? 'default' : 'zh'` +- **readonly**:禁止手动输入,只能通过日历选择 +- **Flatpickr 主题覆盖**:所有颜色走 CSS 变量(--bg, --txt, --blue, --hover-2 等),暗色用 `html[data-appearance="dark"]` 选择器 + +### 9.3 关联代码图 + +``` +index.html:199-201 → +styles.css:729-760 → .tok-date + .flatpickr-calendar 主题覆盖 +app.js:2265-2266 → tokSince / tokUntil 声明(未改) +app.js:2353-2360 → tokGetFiltered() 用 new Date(input.value) 解析(未改) +app.js:2408-2413 → Flatpickr 初始化 + reset 逻辑(新) +``` + +### 9.4 验证结果 + +- ✅ Flatpickr 正常初始化(input 获得 `flatpickr-input` class) +- ✅ 点击弹出日历 +- ✅ 用户手动测试选择日期成功 +- ✅ dateFormat ISO 兼容 tokGetFiltered 的 `new Date()` 解析 +- ✅ 无硬编码颜色/文本 + +### 9.5 Code Review 自检 + +| 检查项 | 通过 | +|--------|------| +| 无硬编码颜色 | ✅ 全走 CSS 变量 | +| 无硬编码文本 | ✅ placeholder 走 data-i18n-ph | +| 暗色模式兼容 | ✅ html[data-appearance="dark"] 覆盖 | +| Safari 兼容 | ✅ ISO dateFormat | +| 原有逻辑不变 | ✅ tokGetFiltered 未修改 | +| 单分支隔离 | ✅ feat/flatpickr-datepicker | diff --git a/frontends/desktop/static/app.js b/frontends/desktop/static/app.js index 333831e5..7bead081 100644 --- a/frontends/desktop/static/app.js +++ b/frontends/desktop/static/app.js @@ -2352,8 +2352,9 @@ async function tokPollBridge() { function tokGetFiltered() { let records = tokLoadHistory(); - const since = tokSince?.value ? new Date(tokSince.value).getTime()/1000 : 0; - const until = tokUntil?.value ? new Date(tokUntil.value).getTime()/1000 : 0; + const parseD = v => v ? new Date(v.replace(/\s+/,'T')).getTime()/1000 : 0; + const since = parseD(tokSince?.value); + const until = parseD(tokUntil?.value); if (since) records = records.filter(r=>r.ts>=since); if (until) records = records.filter(r=>r.ts<=until); return records; @@ -2406,7 +2407,7 @@ function tokRenderTable(records) { async function loadTokenPage(){await tokPollBridge();const f=tokGetFiltered();const all=tokLoadHistory();tokRenderStats(f,all);tokRenderTable(f);} /* Flatpickr 初始化 */ -const _fpOpts = { enableTime:true, time_24hr:true, dateFormat:'Y-m-d\\TH:i', locale:window.flatpickr?.l10ns?.[document.documentElement.lang==='en'?'default':'zh']||'default', allowInput:false, onChange(){ _tokPage=0; loadTokenPage(); } }; +const _fpOpts = { enableTime:true, time_24hr:true, dateFormat:'Y-m-d H:i', locale:window.flatpickr?.l10ns?.[document.documentElement.lang==='en'?'default':'zh']||'default', allowInput:false, onChange(){ _tokPage=0; loadTokenPage(); } }; const fpSince = tokSince ? flatpickr(tokSince, _fpOpts) : null; const fpUntil = tokUntil ? flatpickr(tokUntil, _fpOpts) : null; const tokResetBtn=document.getElementById('tok-reset'); diff --git a/frontends/desktop/static/styles.css b/frontends/desktop/static/styles.css index a059d1dc..17adb173 100644 --- a/frontends/desktop/static/styles.css +++ b/frontends/desktop/static/styles.css @@ -736,32 +736,32 @@ html[data-boot-has-sessions] .chat-start{ display:none !important; } .tok-date:focus{ border-color:var(--blue); outline:none; } /* Flatpickr 主题覆盖 — 全部使用项目 CSS 变量 */ .flatpickr-calendar{ - background:var(--card); border:1px solid var(--line); border-radius:var(--radius); - box-shadow:var(--shadow-pop); font-size:13px; + background:var(--card) !important; border:1px solid var(--line) !important; border-radius:var(--radius) !important; + box-shadow:var(--shadow-pop) !important; font-size:13px; } .flatpickr-months .flatpickr-month, -.flatpickr-current-month{ color:var(--txt); fill:var(--txt); } +.flatpickr-current-month{ color:var(--txt) !important; fill:var(--txt) !important; } .flatpickr-current-month .flatpickr-monthDropdown-months, -.flatpickr-current-month input.cur-year{ color:var(--txt); background:var(--field-bg); } -.flatpickr-weekdays{ background:var(--field-bg); } -span.flatpickr-weekday{ color:var(--muted); font-size:11px; } +.flatpickr-current-month input.cur-year{ color:var(--txt) !important; background:var(--field-bg) !important; } +.flatpickr-weekdays{ background:var(--field-bg) !important; } +span.flatpickr-weekday{ color:var(--muted) !important; font-size:11px; } .flatpickr-day{ - color:var(--txt); border-radius:6px; + color:var(--txt) !important; border-radius:6px; } -.flatpickr-day:hover{ background:var(--hover-2); border-color:transparent; } -.flatpickr-day.today{ border-color:var(--blue); } -.flatpickr-day.selected{ background:var(--blue); color:var(--on-accent); border-color:var(--blue); } +.flatpickr-day:hover{ background:var(--hover-2) !important; border-color:transparent !important; } +.flatpickr-day.today{ border-color:var(--blue) !important; } +.flatpickr-day.selected{ background:var(--blue) !important; color:var(--on-accent) !important; border-color:var(--blue) !important; } .flatpickr-day.prevMonthDay, -.flatpickr-day.nextMonthDay{ color:var(--muted2); } +.flatpickr-day.nextMonthDay{ color:var(--muted2) !important; } .flatpickr-time input, -.flatpickr-time .flatpickr-am-pm{ color:var(--txt); background:var(--field-bg); } +.flatpickr-time .flatpickr-am-pm{ color:var(--txt) !important; background:var(--field-bg) !important; } .flatpickr-time input:hover, -.flatpickr-time input:focus{ background:var(--hover-2); } -.numInputWrapper:hover{ background:var(--hover-2); } +.flatpickr-time input:focus{ background:var(--hover-2) !important; } +.numInputWrapper:hover{ background:var(--hover-2) !important; } .flatpickr-months .flatpickr-prev-month, -.flatpickr-months .flatpickr-next-month{ fill:var(--txt-2); } +.flatpickr-months .flatpickr-next-month{ fill:var(--txt-2) !important; } .flatpickr-months .flatpickr-prev-month:hover svg, -.flatpickr-months .flatpickr-next-month:hover svg{ fill:var(--txt); } +.flatpickr-months .flatpickr-next-month:hover svg{ fill:var(--txt) !important; } .tok-reset{ border:1px solid var(--line); border-radius:6px; padding:5px 10px; font-size:12px; background:var(--field-bg); color:var(--muted); cursor:pointer;