style: 重做界面视觉与信息层级
先截图审视现状,再针对性修改。主要问题与改动如下。 工具栏 - 标签筛选从「平铺全部标签」改为下拉面板,标签多时不再换行挤爆工具栏 - 备份与快捷键帮助移到导航栏 —— 它们是应用级操作,不该挤在看板工具栏里, 顺带修好了「? 帮助只在看板可用」的问题 - 到期统计(今日到期 / 逾期)合并为可点击的筛选入口,去掉两个独立控件 - 移动端工具栏从占半屏降到三行紧凑布局 卡片 - 优先级从彩色胶囊改为左侧色条,扫视一列时比读文字更快分辨轻重 - 元信息改为「优先级 · 日期 · 标签」的轻量文本行,不再三种胶囊叠在一起 - 完成态用勾选图标 + 降低不透明度 布局 - 今日视图限宽居中(max-w-3xl):此前 1440px 下卡片被拉满整屏, 标题与元信息之间出现大片空白 - 修复移动端卡片被压成细条:TaskCard 的 overflow-hidden(用于裁切左侧色条) 会让 flex 项的最小尺寸解析为 0,补 shrink-0 修正; 同时改为移动端按内容高度堆叠、桌面端列内滚动 其他 - 导航栏品牌改用看板图标,消除「看板」重复出现两次 - 统一卡片阴影、圆角与键盘焦点样式 - 新增 scripts/screenshot.mjs,便于后续对照审视视觉改动 验证:单元测试 452 个、端到端测试 23 个全部通过,lint 与构建无错误。
This commit is contained in:
@@ -5,10 +5,14 @@ import { computed, ref } from 'vue'
|
||||
import { useFilterStore } from '@/stores/filters'
|
||||
import { useTaskStore } from '@/stores/tasks'
|
||||
import { PRIORITY_LABELS, type Priority } from '@/types/task'
|
||||
import { isDueToday, isOverdue } from '@/utils/date'
|
||||
import { findArchivableTasks } from '@/utils/archive'
|
||||
import { isDueToday, isOverdue } from '@/utils/date'
|
||||
|
||||
const emit = defineEmits<{ 'open-backup': []; 'open-help': []; archive: [] }>()
|
||||
import TagFilter from './TagFilter.vue'
|
||||
|
||||
defineProps<{ hiddenNote?: string }>()
|
||||
|
||||
const emit = defineEmits<{ archive: [] }>()
|
||||
|
||||
const store = useTaskStore()
|
||||
const filters = useFilterStore()
|
||||
@@ -16,7 +20,7 @@ const { keyword, priorities, tags, due, active, includeArchived } = storeToRefs(
|
||||
|
||||
const searchRef = ref<HTMLInputElement | null>(null)
|
||||
|
||||
/** 由高到低排列,与看板中「高优先级优先处理」的心智一致 */
|
||||
/** 由高到低排列,与「高优先级优先处理」的心智一致 */
|
||||
const PRIORITY_ORDER: readonly Priority[] = [3, 2, 1]
|
||||
|
||||
const dueTodayCount = computed(
|
||||
@@ -47,77 +51,42 @@ defineExpose({ focusSearch })
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<header class="flex flex-col gap-3">
|
||||
<div class="flex flex-wrap items-baseline gap-x-3 gap-y-1">
|
||||
<span class="text-sm text-slate-500">共 {{ store.tasks.length }} 个任务</span>
|
||||
|
||||
<button
|
||||
type="button"
|
||||
class="rounded px-1.5 py-0.5 text-sm transition"
|
||||
:class="
|
||||
due === 'today' ? 'bg-amber-100 text-amber-800' : 'text-slate-500 hover:bg-slate-200'
|
||||
"
|
||||
:aria-pressed="due === 'today'"
|
||||
@click="filters.toggleDue('today')"
|
||||
>
|
||||
今日到期 {{ dueTodayCount }}
|
||||
</button>
|
||||
|
||||
<button
|
||||
type="button"
|
||||
class="rounded px-1.5 py-0.5 text-sm transition"
|
||||
:class="
|
||||
due === 'overdue'
|
||||
? 'bg-rose-100 text-rose-700'
|
||||
: overdueCount > 0
|
||||
? 'text-rose-600 hover:bg-slate-200'
|
||||
: 'text-slate-500 hover:bg-slate-200'
|
||||
"
|
||||
:aria-pressed="due === 'overdue'"
|
||||
@click="filters.toggleDue('overdue')"
|
||||
>
|
||||
逾期 {{ overdueCount }}
|
||||
</button>
|
||||
|
||||
<div class="ml-auto flex items-center gap-2">
|
||||
<button
|
||||
type="button"
|
||||
class="rounded-md border border-slate-300 px-2 py-0.5 text-sm text-slate-500 transition hover:bg-white"
|
||||
aria-label="快捷键帮助"
|
||||
@click="emit('open-help')"
|
||||
>
|
||||
?
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
class="rounded-md border border-slate-300 px-2.5 py-1 text-sm text-slate-600 transition hover:bg-white"
|
||||
@click="emit('open-backup')"
|
||||
>
|
||||
备份
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<header class="flex flex-col gap-2.5">
|
||||
<div class="flex flex-wrap items-center gap-2">
|
||||
<input
|
||||
ref="searchRef"
|
||||
v-model="keyword"
|
||||
type="search"
|
||||
class="w-full max-w-xs rounded-md border border-slate-300 px-2.5 py-1.5 text-sm text-slate-700 outline-none placeholder:text-slate-400 focus:border-sky-400"
|
||||
placeholder="搜索标题或描述(/ 聚焦)"
|
||||
aria-label="搜索任务"
|
||||
/>
|
||||
<div class="relative min-w-56 flex-1 sm:max-w-xs">
|
||||
<svg
|
||||
viewBox="0 0 20 20"
|
||||
class="pointer-events-none absolute top-1/2 left-2.5 size-4 -translate-y-1/2 text-slate-400"
|
||||
fill="none"
|
||||
aria-hidden="true"
|
||||
>
|
||||
<circle cx="9" cy="9" r="5.25" stroke="currentColor" stroke-width="1.5" />
|
||||
<path d="m13 13 3.5 3.5" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" />
|
||||
</svg>
|
||||
<input
|
||||
ref="searchRef"
|
||||
v-model="keyword"
|
||||
type="search"
|
||||
class="w-full rounded-md border border-slate-200 bg-white py-1.5 pr-2.5 pl-8 text-sm text-slate-700 outline-none transition placeholder:text-slate-400 focus:border-sky-400 focus:ring-2 focus:ring-sky-100"
|
||||
placeholder="搜索标题或描述"
|
||||
aria-label="搜索任务"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="flex items-center gap-1">
|
||||
<div
|
||||
class="flex items-center gap-0.5 rounded-md border border-slate-200 bg-white p-0.5"
|
||||
role="group"
|
||||
aria-label="按优先级筛选"
|
||||
>
|
||||
<button
|
||||
v-for="value in PRIORITY_ORDER"
|
||||
:key="value"
|
||||
type="button"
|
||||
class="rounded-md border px-2 py-1 text-xs transition"
|
||||
class="rounded px-2.5 py-1 text-xs transition"
|
||||
:class="
|
||||
priorities.includes(value)
|
||||
? 'border-sky-400 bg-sky-50 text-sky-700'
|
||||
: 'border-slate-300 text-slate-600 hover:bg-white'
|
||||
? 'bg-slate-900 font-medium text-white'
|
||||
: 'text-slate-600 hover:bg-slate-100'
|
||||
"
|
||||
:aria-pressed="priorities.includes(value)"
|
||||
@click="filters.togglePriority(value)"
|
||||
@@ -126,36 +95,57 @@ defineExpose({ focusSearch })
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<button
|
||||
v-for="tag in allTags"
|
||||
:key="tag"
|
||||
type="button"
|
||||
class="rounded-full border px-2 py-0.5 text-xs transition"
|
||||
:class="
|
||||
tags.includes(tag)
|
||||
? 'border-sky-400 bg-sky-50 text-sky-700'
|
||||
: 'border-slate-300 text-slate-600 hover:bg-white'
|
||||
"
|
||||
:aria-pressed="tags.includes(tag)"
|
||||
@click="filters.toggleTag(tag)"
|
||||
>
|
||||
#{{ tag }}
|
||||
</button>
|
||||
<TagFilter
|
||||
:tags="allTags"
|
||||
:selected="tags"
|
||||
@toggle="filters.toggleTag($event)"
|
||||
@clear="filters.clearTags()"
|
||||
/>
|
||||
|
||||
<button
|
||||
v-if="active"
|
||||
type="button"
|
||||
class="rounded-md px-2 py-1 text-xs text-slate-500 underline-offset-2 transition hover:text-slate-700 hover:underline"
|
||||
@click="filters.clear()"
|
||||
>
|
||||
清空筛选
|
||||
</button>
|
||||
<div class="ml-auto flex items-center gap-1.5">
|
||||
<button
|
||||
type="button"
|
||||
class="flex items-center gap-1.5 rounded-md border px-2.5 py-1.5 text-xs transition"
|
||||
:class="
|
||||
due === 'today'
|
||||
? 'border-amber-300 bg-amber-50 text-amber-800'
|
||||
: 'border-slate-200 bg-white text-slate-600 hover:border-slate-300 hover:bg-slate-50'
|
||||
"
|
||||
:aria-pressed="due === 'today'"
|
||||
@click="filters.toggleDue('today')"
|
||||
>
|
||||
今日到期
|
||||
<span class="font-semibold">{{ dueTodayCount }}</span>
|
||||
</button>
|
||||
|
||||
<div class="ml-auto flex items-center gap-2">
|
||||
<button
|
||||
type="button"
|
||||
class="flex items-center gap-1.5 rounded-md border px-2.5 py-1.5 text-xs transition"
|
||||
:class="
|
||||
due === 'overdue'
|
||||
? 'border-rose-300 bg-rose-50 text-rose-700'
|
||||
: overdueCount > 0
|
||||
? 'border-slate-200 bg-white text-rose-600 hover:border-rose-200 hover:bg-rose-50'
|
||||
: 'border-slate-200 bg-white text-slate-600 hover:border-slate-300 hover:bg-slate-50'
|
||||
"
|
||||
:aria-pressed="due === 'overdue'"
|
||||
@click="filters.toggleDue('overdue')"
|
||||
>
|
||||
逾期
|
||||
<span class="font-semibold">{{ overdueCount }}</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="flex flex-wrap items-center gap-x-2 gap-y-1.5 text-xs text-slate-500">
|
||||
<span>共 {{ store.tasks.length }} 个任务</span>
|
||||
<span v-if="hiddenNote" class="text-slate-400">{{ hiddenNote }}</span>
|
||||
|
||||
<div class="ml-auto flex items-center gap-1.5">
|
||||
<button
|
||||
v-if="archivableCount > 0"
|
||||
type="button"
|
||||
class="rounded-md border border-slate-300 px-2 py-1 text-xs text-slate-600 transition hover:bg-white"
|
||||
class="rounded-md border border-slate-200 bg-white px-2.5 py-1 text-xs text-slate-600 transition hover:border-slate-300 hover:bg-slate-50"
|
||||
@click="emit('archive')"
|
||||
>
|
||||
归档 {{ archivableCount }} 个已完成
|
||||
@@ -163,17 +153,26 @@ defineExpose({ focusSearch })
|
||||
|
||||
<button
|
||||
type="button"
|
||||
class="rounded-md border px-2 py-1 text-xs transition"
|
||||
class="rounded-md border px-2.5 py-1 text-xs transition"
|
||||
:class="
|
||||
includeArchived
|
||||
? 'border-sky-400 bg-sky-50 text-sky-700'
|
||||
: 'border-slate-300 text-slate-600 hover:bg-white'
|
||||
? 'border-sky-300 bg-sky-50 text-sky-700'
|
||||
: 'border-slate-200 bg-white text-slate-600 hover:border-slate-300 hover:bg-slate-50'
|
||||
"
|
||||
:aria-pressed="includeArchived"
|
||||
@click="filters.toggleArchived()"
|
||||
>
|
||||
显示归档
|
||||
</button>
|
||||
|
||||
<button
|
||||
v-if="active"
|
||||
type="button"
|
||||
class="rounded-md px-2.5 py-1 text-xs text-slate-500 underline-offset-2 transition hover:text-slate-800 hover:underline"
|
||||
@click="filters.clear()"
|
||||
>
|
||||
清空筛选
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
Reference in New Issue
Block a user