style: 重做界面视觉与信息层级

先截图审视现状,再针对性修改。主要问题与改动如下。

工具栏
- 标签筛选从「平铺全部标签」改为下拉面板,标签多时不再换行挤爆工具栏
- 备份与快捷键帮助移到导航栏 —— 它们是应用级操作,不该挤在看板工具栏里,
  顺带修好了「? 帮助只在看板可用」的问题
- 到期统计(今日到期 / 逾期)合并为可点击的筛选入口,去掉两个独立控件
- 移动端工具栏从占半屏降到三行紧凑布局

卡片
- 优先级从彩色胶囊改为左侧色条,扫视一列时比读文字更快分辨轻重
- 元信息改为「优先级 · 日期 · 标签」的轻量文本行,不再三种胶囊叠在一起
- 完成态用勾选图标 + 降低不透明度

布局
- 今日视图限宽居中(max-w-3xl):此前 1440px 下卡片被拉满整屏,
  标题与元信息之间出现大片空白
- 修复移动端卡片被压成细条:TaskCard 的 overflow-hidden(用于裁切左侧色条)
  会让 flex 项的最小尺寸解析为 0,补 shrink-0 修正;
  同时改为移动端按内容高度堆叠、桌面端列内滚动

其他
- 导航栏品牌改用看板图标,消除「看板」重复出现两次
- 统一卡片阴影、圆角与键盘焦点样式
- 新增 scripts/screenshot.mjs,便于后续对照审视视觉改动

验证:单元测试 452 个、端到端测试 23 个全部通过,lint 与构建无错误。
This commit is contained in:
chenshoulin
2026-10-18 14:05:06 +08:00
parent 4e064e82ea
commit 442cbbd734
23 changed files with 981 additions and 333 deletions
+11 -49
View File
@@ -2,15 +2,14 @@
import { storeToRefs } from 'pinia'
import { computed, ref } from 'vue'
import BackupPanel from '@/components/BackupPanel.vue'
import BoardColumn from '@/components/BoardColumn.vue'
import BoardToolbar from '@/components/BoardToolbar.vue'
import SideDrawer from '@/components/SideDrawer.vue'
import TaskDetailDrawer from '@/components/TaskDetailDrawer.vue'
import { useKeyboardShortcuts } from '@/composables/useKeyboardShortcuts'
import { useFilterStore } from '@/stores/filters'
import { useTaskStore } from '@/stores/tasks'
import { useToastStore } from '@/stores/toast'
import { useUiStore } from '@/stores/ui'
import { COLUMNS, type ColumnId } from '@/types/task'
import { findArchivableTasks } from '@/utils/archive'
import { filterTasks } from '@/utils/filter'
@@ -19,23 +18,14 @@ import { groupByColumn } from '@/utils/sortOrder'
const store = useTaskStore()
const filters = useFilterStore()
const toast = useToastStore()
const ui = useUiStore()
const { notice } = storeToRefs(store)
const { filter } = storeToRefs(filters)
const SHORTCUTS = [
{ keys: 'N', description: '聚焦「待办」列的快速新增输入框' },
{ keys: '/', description: '聚焦搜索框' },
{ keys: '?', description: '打开本帮助' },
{ keys: 'Esc', description: '关闭当前面板' },
] as const
/** 详情抽屉当前编辑的任务 id;任务被删除后自动变为 null,抽屉随之关闭 */
const editingId = ref<string | null>(null)
const editingTask = computed(() => store.tasks.find((task) => task.id === editingId.value) ?? null)
const backupOpen = ref(false)
const helpOpen = ref(false)
const toolbarRef = ref<InstanceType<typeof BoardToolbar> | null>(null)
const columnRefs = new Map<number, InstanceType<typeof BoardColumn>>()
@@ -47,10 +37,6 @@ function setColumnRef(element: unknown, index: number): void {
/** 实际渲染的看板:先按条件筛选,再按列分组,列计数随之反映结果 */
const visibleColumns = computed(() => groupByColumn(filterTasks(store.tasks, filter.value)))
const visibleCount = computed(() =>
COLUMNS.reduce((total, column) => total + visibleColumns.value[column.id].length, 0),
)
/**
* 每列被隐藏的数量(被筛选掉 + 已归档)。
* 大于 0 说明该列渲染的是子集,此时必须禁用列内排序,
@@ -84,14 +70,10 @@ function archiveCompleted(): void {
})
}
function anyPanelOpen(): boolean {
return editingId.value !== null || backupOpen.value || helpOpen.value
}
/** 有面板打开时不响应全局快捷键,避免误触 */
function whenNoPanel(action: () => void): () => void {
return () => {
if (anyPanelOpen()) return
if (editingId.value !== null || ui.anyPanelOpen) return
action()
}
}
@@ -99,9 +81,6 @@ function whenNoPanel(action: () => void): () => void {
useKeyboardShortcuts({
n: whenNoPanel(() => columnRefs.get(0)?.focusQuickAdd()),
'/': whenNoPanel(() => toolbarRef.value?.focusSearch()),
'?': whenNoPanel(() => {
helpOpen.value = true
}),
})
function toggleTask(id: string): void {
@@ -124,26 +103,25 @@ function handleMove(payload: { id: string; toColumn: ColumnId; toIndex: number }
</script>
<template>
<div class="flex min-h-0 flex-1 flex-col gap-4 p-4 md:p-6">
<div class="flex min-h-0 flex-1 flex-col gap-3 p-4 md:p-6">
<BoardToolbar
ref="toolbarRef"
@open-backup="backupOpen = true"
@open-help="helpOpen = true"
:hidden-note="hiddenTotal > 0 ? `· 已隐藏 ${hiddenTotal} 个` : undefined"
@archive="archiveCompleted"
/>
<p
v-if="notice"
class="rounded-md border border-amber-300 bg-amber-50 px-3 py-2 text-sm text-amber-800"
class="rounded-lg border border-amber-300 bg-amber-50 px-3 py-2 text-sm text-amber-800"
>
{{ notice }}
</p>
<p v-if="hiddenTotal > 0" class="text-xs text-slate-500">
显示 {{ visibleCount }} / {{ store.tasks.length }} 个任务
</p>
<div class="grid min-h-0 flex-1 gap-4 overflow-y-auto md:grid-cols-3 md:overflow-hidden">
<!--
移动端:整块区域滚动,各列按内容自然高度排布;
桌面端:容器固定高度,每列内部各自滚动。
-->
<div class="grid flex-1 gap-3 overflow-y-auto md:min-h-0 md:grid-cols-3 md:overflow-hidden">
<BoardColumn
v-for="(column, index) in COLUMNS"
:key="column.id"
@@ -158,21 +136,5 @@ function handleMove(payload: { id: string; toColumn: ColumnId; toIndex: number }
</div>
<TaskDetailDrawer :task="editingTask" @close="closeDetail" />
<BackupPanel :open="backupOpen" @close="backupOpen = false" />
<SideDrawer :open="helpOpen" title="快捷键" @close="helpOpen = false">
<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 border border-slate-300 bg-slate-50 px-2 py-0.5 text-center font-mono text-xs text-slate-600"
>
{{ item.keys }}
</dt>
<dd class="text-slate-600">{{ item.description }}</dd>
</div>
</dl>
<p class="mt-4 text-xs text-slate-400">在输入框内输入时不会触发快捷键。</p>
</SideDrawer>
</div>
</template>