feat: 补齐今日视图待办组、卡片键盘操作、深色模式

今日视图
- 补上「待办」组。此前只收 逾期 / 今天到期 / 进行中,导致待办列里没设
  截止日期的任务在今日视图里完全消失 —— 用户明明有一堆事没做,却看到
  「今天没有待办事项」。现在只要任务未完成且未归档,就必定出现在某个
  分组里(归属优先级:逾期 > 今天到期 > 进行中 > 待办)。
- 统计卡片改为与分组一一对应(逾期 / 今天到期 / 进行中 / 本周完成),
  不再用「今日待办」这种把四组混在一起、与列表长度对不上的指标。

键盘操作(可访问性)
- 卡片可聚焦(Tab / 点击),← → 换列、↑ ↓ 列内调序、Enter 打开详情。
- 换列时追加到目标列末尾:用全量长度而不是可见下标,筛选态下也安全;
  列内调序要求该列渲染的是全量,否则禁用(与拖拽的规则一致)。
- 移动后手动恢复焦点 —— 卡片跨列会被重建,不恢复的话连续按键会失效。

深色模式
- 三态:跟随系统 / 浅色 / 深色,选择持久化到 localStorage。
- index.html 内联脚本在首帧之前应用主题,避免深色下闪白。
- 用 @custom-variant dark 做 class 控制(而不是纯 media query),
  才能支持「强制浅色」;html 的 color-scheme 让原生控件与滚动条跟随。

验证:单元测试 480 个、端到端测试 25 个全部通过,lint 与构建无错误。
截图脚本新增深色与移动端深色两组,便于后续对照。
This commit is contained in:
chenshoulin
2026-10-18 17:48:55 +08:00
parent 442cbbd734
commit 8721e8ab0b
32 changed files with 1022 additions and 158 deletions
+13 -13
View File
@@ -82,29 +82,29 @@ function remove(): void {
<SideDrawer :open="task !== null" title="任务详情" @close="emit('close')">
<form v-if="task" class="flex flex-col gap-4" @submit.prevent="save">
<label class="flex flex-col gap-1.5">
<span class="text-sm font-medium text-slate-600">标题</span>
<span class="text-sm font-medium text-slate-600 dark:text-slate-400">标题</span>
<input
v-model="title"
type="text"
class="rounded-md border border-slate-300 px-2.5 py-1.5 text-sm text-slate-700 outline-none focus:border-sky-400"
class="rounded-md border border-slate-300 px-2.5 py-1.5 text-sm text-slate-700 outline-none focus:border-sky-400 dark:border-slate-700 dark:bg-slate-800 dark:text-slate-200 dark:focus:border-sky-600"
aria-label="标题"
@input="titleError = null"
/>
<span v-if="titleError" class="text-xs text-rose-600">{{ titleError }}</span>
<span v-if="titleError" class="text-xs text-rose-600 dark:text-rose-400">{{ titleError }}</span>
</label>
<label class="flex flex-col gap-1.5">
<span class="text-sm font-medium text-slate-600">描述</span>
<span class="text-sm font-medium text-slate-600 dark:text-slate-400">描述</span>
<textarea
v-model="description"
rows="4"
class="resize-y rounded-md border border-slate-300 px-2.5 py-1.5 text-sm text-slate-700 outline-none focus:border-sky-400"
class="resize-y rounded-md border border-slate-300 px-2.5 py-1.5 text-sm text-slate-700 outline-none focus:border-sky-400 dark:border-slate-700 dark:bg-slate-800 dark:text-slate-200 dark:focus:border-sky-600"
aria-label="描述"
/>
</label>
<fieldset class="flex flex-col gap-1.5">
<legend class="mb-1.5 text-sm font-medium text-slate-600">优先级</legend>
<legend class="mb-1.5 text-sm font-medium text-slate-600 dark:text-slate-400">优先级</legend>
<div class="flex gap-2">
<button
v-for="value in PRIORITIES"
@@ -113,8 +113,8 @@ function remove(): void {
class="rounded-md border px-3 py-1 text-sm transition"
:class="
priority === value
? 'border-sky-400 bg-sky-50 text-sky-700'
: 'border-slate-300 text-slate-600 hover:bg-slate-50'
? 'border-sky-400 bg-sky-50 text-sky-700 dark:border-sky-600 dark:bg-sky-950 dark:text-sky-300'
: 'border-slate-300 text-slate-600 hover:bg-slate-50 dark:border-slate-600 dark:text-slate-400 dark:hover:bg-slate-800'
"
:aria-pressed="priority === value"
@click="priority = value"
@@ -125,12 +125,12 @@ function remove(): void {
</fieldset>
<div class="flex flex-col gap-1.5">
<span class="text-sm font-medium text-slate-600">截止日期</span>
<span class="text-sm font-medium text-slate-600 dark:text-slate-400">截止日期</span>
<DatePicker v-model="dueDate" />
</div>
<div class="flex flex-col gap-1.5">
<span class="text-sm font-medium text-slate-600">标签</span>
<span class="text-sm font-medium text-slate-600 dark:text-slate-400">标签</span>
<TagInput v-model="tags" />
</div>
</form>
@@ -139,7 +139,7 @@ function remove(): void {
<div class="flex items-center justify-between">
<button
type="button"
class="rounded-md px-2.5 py-1.5 text-sm text-rose-600 transition hover:bg-rose-50"
class="rounded-md px-2.5 py-1.5 text-sm text-rose-600 transition hover:bg-rose-50 dark:text-rose-400 dark:hover:bg-rose-950"
@click="remove"
>
删除
@@ -148,14 +148,14 @@ function remove(): void {
<div class="flex gap-2">
<button
type="button"
class="rounded-md border border-slate-300 px-3 py-1.5 text-sm text-slate-600 transition hover:bg-slate-50"
class="rounded-md border border-slate-300 px-3 py-1.5 text-sm text-slate-600 transition hover:bg-slate-50 dark:border-slate-600 dark:text-slate-300 dark:hover:bg-slate-800"
@click="emit('close')"
>
取消
</button>
<button
type="button"
class="rounded-md bg-sky-600 px-3 py-1.5 text-sm font-medium text-white transition hover:bg-sky-700"
class="rounded-md bg-sky-600 px-3 py-1.5 text-sm font-medium text-white transition hover:bg-sky-700 dark:hover:bg-sky-500"
@click="save"
>
保存