feat: 补齐今日视图待办组、卡片键盘操作、深色模式
今日视图 - 补上「待办」组。此前只收 逾期 / 今天到期 / 进行中,导致待办列里没设 截止日期的任务在今日视图里完全消失 —— 用户明明有一堆事没做,却看到 「今天没有待办事项」。现在只要任务未完成且未归档,就必定出现在某个 分组里(归属优先级:逾期 > 今天到期 > 进行中 > 待办)。 - 统计卡片改为与分组一一对应(逾期 / 今天到期 / 进行中 / 本周完成), 不再用「今日待办」这种把四组混在一起、与列表长度对不上的指标。 键盘操作(可访问性) - 卡片可聚焦(Tab / 点击),← → 换列、↑ ↓ 列内调序、Enter 打开详情。 - 换列时追加到目标列末尾:用全量长度而不是可见下标,筛选态下也安全; 列内调序要求该列渲染的是全量,否则禁用(与拖拽的规则一致)。 - 移动后手动恢复焦点 —— 卡片跨列会被重建,不恢复的话连续按键会失效。 深色模式 - 三态:跟随系统 / 浅色 / 深色,选择持久化到 localStorage。 - index.html 内联脚本在首帧之前应用主题,避免深色下闪白。 - 用 @custom-variant dark 做 class 控制(而不是纯 media query), 才能支持「强制浅色」;html 的 color-scheme 让原生控件与滚动条跟随。 验证:单元测试 480 个、端到端测试 25 个全部通过,lint 与构建无错误。 截图脚本新增深色与移动端深色两组,便于后续对照。
This commit is contained in:
@@ -425,3 +425,132 @@ describe('BoardView 快捷键', () => {
|
||||
expect(document.activeElement).not.toBe(wrapper.find('input[aria-label="添加任务"]').element)
|
||||
})
|
||||
})
|
||||
|
||||
describe('BoardView 键盘移动', () => {
|
||||
type Direction = 'left' | 'right' | 'up' | 'down'
|
||||
|
||||
/** 模拟卡片发出 keymove(任意一列转发都会到达 BoardView 的处理函数) */
|
||||
function emitKeyMove(
|
||||
wrapper: ReturnType<typeof mountBoard>,
|
||||
id: string,
|
||||
direction: Direction,
|
||||
): void {
|
||||
wrapper.findAllComponents(BoardColumn)[0]?.vm.$emit('keymove', { id, direction })
|
||||
}
|
||||
|
||||
it('右移把任务移到下一列末尾', async () => {
|
||||
const { pinia, store } = setup()
|
||||
const task = store.createTask({ title: 'A' })
|
||||
const wrapper = mountBoard(pinia)
|
||||
|
||||
emitKeyMove(wrapper, task?.id ?? '', 'right')
|
||||
await nextTick()
|
||||
|
||||
expect(store.byColumn.todo).toHaveLength(0)
|
||||
expect(store.byColumn.doing.map((item) => item.title)).toEqual(['A'])
|
||||
})
|
||||
|
||||
it('左移把任务移回上一列', async () => {
|
||||
const { pinia, store } = setup()
|
||||
const task = store.createTask({ title: 'A', status: 'doing' })
|
||||
const wrapper = mountBoard(pinia)
|
||||
|
||||
emitKeyMove(wrapper, task?.id ?? '', 'left')
|
||||
await nextTick()
|
||||
|
||||
expect(store.byColumn.doing).toHaveLength(0)
|
||||
expect(store.byColumn.todo.map((item) => item.title)).toEqual(['A'])
|
||||
})
|
||||
|
||||
it('最左列再左移无效果', async () => {
|
||||
const { pinia, store } = setup()
|
||||
const task = store.createTask({ title: 'A' })
|
||||
const wrapper = mountBoard(pinia)
|
||||
|
||||
emitKeyMove(wrapper, task?.id ?? '', 'left')
|
||||
await nextTick()
|
||||
|
||||
expect(store.byColumn.todo.map((item) => item.title)).toEqual(['A'])
|
||||
})
|
||||
|
||||
it('最右列再右移无效果', async () => {
|
||||
const { pinia, store } = setup()
|
||||
const task = store.createTask({ title: 'A', status: 'done' })
|
||||
const wrapper = mountBoard(pinia)
|
||||
|
||||
emitKeyMove(wrapper, task?.id ?? '', 'right')
|
||||
await nextTick()
|
||||
|
||||
expect(store.byColumn.done.map((item) => item.title)).toEqual(['A'])
|
||||
})
|
||||
|
||||
it('上移调整列内顺序', async () => {
|
||||
const { pinia, store } = setup()
|
||||
const a = store.createTask({ title: 'A' })
|
||||
store.createTask({ title: 'B' })
|
||||
store.createTask({ title: 'C' })
|
||||
// 快速新增插入列首,当前顺序为 C, B, A
|
||||
const wrapper = mountBoard(pinia)
|
||||
|
||||
emitKeyMove(wrapper, a?.id ?? '', 'up')
|
||||
await nextTick()
|
||||
|
||||
expect(store.byColumn.todo.map((item) => item.title)).toEqual(['C', 'A', 'B'])
|
||||
})
|
||||
|
||||
it('下移调整列内顺序', async () => {
|
||||
const { pinia, store } = setup()
|
||||
store.createTask({ title: 'A' })
|
||||
store.createTask({ title: 'B' })
|
||||
const c = store.createTask({ title: 'C' })
|
||||
// 当前顺序 C, B, A
|
||||
const wrapper = mountBoard(pinia)
|
||||
|
||||
emitKeyMove(wrapper, c?.id ?? '', 'down')
|
||||
await nextTick()
|
||||
|
||||
expect(store.byColumn.todo.map((item) => item.title)).toEqual(['B', 'C', 'A'])
|
||||
})
|
||||
|
||||
it('已在列首时上移无效果', async () => {
|
||||
const { pinia, store } = setup()
|
||||
store.createTask({ title: 'A' })
|
||||
const first = store.createTask({ title: 'B' })
|
||||
const wrapper = mountBoard(pinia)
|
||||
|
||||
emitKeyMove(wrapper, first?.id ?? '', 'up')
|
||||
await nextTick()
|
||||
|
||||
expect(store.byColumn.todo.map((item) => item.title)).toEqual(['B', 'A'])
|
||||
})
|
||||
|
||||
it('列内渲染子集时禁止上下移动(避免写坏全量顺序)', async () => {
|
||||
const { pinia, store } = setup()
|
||||
const a = store.createTask({ title: 'A', priority: 3 })
|
||||
store.createTask({ title: 'B', priority: 1 })
|
||||
const wrapper = mountBoard(pinia)
|
||||
|
||||
useFilterStore().togglePriority(3)
|
||||
await nextTick()
|
||||
|
||||
emitKeyMove(wrapper, a?.id ?? '', 'down')
|
||||
await nextTick()
|
||||
|
||||
// 顺序保持原样
|
||||
expect(store.byColumn.todo.map((item) => item.title)).toEqual(['B', 'A'])
|
||||
})
|
||||
|
||||
it('跨列移动在筛选态下依然可用', async () => {
|
||||
const { pinia, store } = setup()
|
||||
const task = store.createTask({ title: 'A', priority: 3 })
|
||||
const wrapper = mountBoard(pinia)
|
||||
|
||||
useFilterStore().togglePriority(3)
|
||||
await nextTick()
|
||||
|
||||
emitKeyMove(wrapper, task?.id ?? '', 'right')
|
||||
await nextTick()
|
||||
|
||||
expect(store.byColumn.doing.map((item) => item.title)).toEqual(['A'])
|
||||
})
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user