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
+37 -4
View File
@@ -9,6 +9,7 @@ import SideDrawer from '@/components/SideDrawer.vue'
import ToastHost from '@/components/ToastHost.vue'
import { useKeyboardShortcuts } from '@/composables/useKeyboardShortcuts'
import { useTaskStore } from '@/stores/tasks'
import { useThemeStore } from '@/stores/theme'
import { useUiStore } from '@/stores/ui'
const SHORTCUTS = [
@@ -18,11 +19,21 @@ const SHORTCUTS = [
{ keys: 'Esc', description: '关闭当前面板' },
] as const
/** 这些只在卡片获得焦点后生效 */
const CARD_SHORTCUTS = [
{ keys: 'Tab', description: '依次聚焦到任务卡片' },
{ keys: '← →', description: '把卡片移到上一列 / 下一列' },
{ keys: '↑ ↓', description: '在列内上移 / 下移' },
{ keys: 'Enter', description: '打开任务详情' },
] as const
const store = useTaskStore()
const ui = useUiStore()
const theme = useThemeStore()
onMounted(() => {
store.init()
theme.init()
})
// 帮助面板是应用级的,因此在任何视图下都能用 ?
@@ -34,7 +45,7 @@ useKeyboardShortcuts({
</script>
<template>
<div class="flex h-full min-h-0 flex-col bg-slate-100">
<div class="flex h-full min-h-0 flex-col bg-slate-100 dark:bg-slate-950">
<AppNav />
<main class="flex min-h-0 flex-1 flex-col">
@@ -45,17 +56,39 @@ useKeyboardShortcuts({
<BackupPanel :open="ui.backupOpen" @close="ui.closeBackup()" />
<SideDrawer :open="ui.helpOpen" title="快捷键" @close="ui.closeHelp()">
<h3 class="mb-2 text-xs font-medium tracking-wide text-slate-400 uppercase dark:text-slate-500">
全局
</h3>
<dl class="flex flex-col gap-3 text-sm">
<div v-for="item in SHORTCUTS" :key="item.keys" class="flex items-center gap-3">
<dt
class="w-14 shrink-0 rounded-md border border-slate-200 bg-slate-50 px-2 py-0.5 text-center font-mono text-xs text-slate-600"
class="w-14 shrink-0 rounded-md border border-slate-200 bg-slate-50 px-2 py-0.5 text-center font-mono text-xs text-slate-600 dark:border-slate-700 dark:bg-slate-800 dark:text-slate-300"
>
{{ item.keys }}
</dt>
<dd class="text-slate-600">{{ item.description }}</dd>
<dd class="text-slate-600 dark:text-slate-300">{{ item.description }}</dd>
</div>
</dl>
<p class="mt-4 text-xs text-slate-400">在输入框内输入时不会触发快捷键。</p>
<h3
class="mt-6 mb-2 text-xs font-medium tracking-wide text-slate-400 uppercase dark:text-slate-500"
>
卡片聚焦后
</h3>
<dl class="flex flex-col gap-3 text-sm">
<div v-for="item in CARD_SHORTCUTS" :key="item.keys" class="flex items-center gap-3">
<dt
class="w-14 shrink-0 rounded-md border border-slate-200 bg-slate-50 px-2 py-0.5 text-center font-mono text-xs text-slate-600 dark:border-slate-700 dark:bg-slate-800 dark:text-slate-300"
>
{{ item.keys }}
</dt>
<dd class="text-slate-600 dark:text-slate-300">{{ item.description }}</dd>
</div>
</dl>
<p class="mt-4 text-xs text-slate-400 dark:text-slate-500">
在输入框内输入时不会触发快捷键。
</p>
</SideDrawer>
<ToastHost />