- Toast 自动关闭支持悬停/聚焦暂停,移开后按剩余时间继续 - 存储异常提示移到 App 级,看板与今日视图都能看到 - 归档隐藏与筛选隐藏分开说明,并提示列内排序暂停原因
109 lines
3.7 KiB
Vue
109 lines
3.7 KiB
Vue
<script setup lang="ts">
|
||
import { storeToRefs } from 'pinia'
|
||
import { onMounted } from 'vue'
|
||
import { RouterView } from 'vue-router'
|
||
|
||
import AppNav from '@/components/AppNav.vue'
|
||
import BackupPanel from '@/components/BackupPanel.vue'
|
||
import PwaUpdatePrompt from '@/components/PwaUpdatePrompt.vue'
|
||
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 = [
|
||
{ keys: 'N', description: '聚焦「待办」列的快速新增输入框' },
|
||
{ keys: '/', description: '聚焦搜索框' },
|
||
{ keys: '?', description: '打开本帮助' },
|
||
{ 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()
|
||
const { notice } = storeToRefs(store)
|
||
|
||
onMounted(() => {
|
||
store.init()
|
||
theme.init()
|
||
})
|
||
|
||
// 帮助面板是应用级的,因此在任何视图下都能用 ?
|
||
useKeyboardShortcuts({
|
||
'?': () => {
|
||
if (!ui.anyPanelOpen) ui.openHelp()
|
||
},
|
||
})
|
||
</script>
|
||
|
||
<template>
|
||
<div class="flex h-full min-h-0 flex-col bg-slate-100 dark:bg-zinc-950">
|
||
<AppNav />
|
||
|
||
<!--
|
||
存储异常必须在两个视图都可见:今日视图同样会丢数据,
|
||
把提示放在应用级而不是看板里,避免「切到今日就看不见告警」。
|
||
-->
|
||
<p
|
||
v-if="notice"
|
||
role="alert"
|
||
class="border-b border-amber-300 bg-amber-50 px-4 py-2 text-sm text-amber-800 dark:border-amber-800 dark:bg-amber-950/60 dark:text-amber-300 md:px-6"
|
||
>
|
||
{{ notice }}
|
||
</p>
|
||
|
||
<main class="flex min-h-0 flex-1 flex-col">
|
||
<RouterView />
|
||
</main>
|
||
</div>
|
||
|
||
<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-zinc-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 dark:border-zinc-700 dark:bg-zinc-800 dark:text-zinc-300"
|
||
>
|
||
{{ item.keys }}
|
||
</dt>
|
||
<dd class="text-slate-600 dark:text-zinc-300">{{ item.description }}</dd>
|
||
</div>
|
||
</dl>
|
||
|
||
<h3
|
||
class="mt-6 mb-2 text-xs font-medium tracking-wide text-slate-400 uppercase dark:text-zinc-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-zinc-700 dark:bg-zinc-800 dark:text-zinc-300"
|
||
>
|
||
{{ item.keys }}
|
||
</dt>
|
||
<dd class="text-slate-600 dark:text-zinc-300">{{ item.description }}</dd>
|
||
</div>
|
||
</dl>
|
||
|
||
<p class="mt-4 text-xs text-slate-400 dark:text-zinc-500">在输入框内输入时不会触发快捷键。</p>
|
||
</SideDrawer>
|
||
|
||
<ToastHost />
|
||
<PwaUpdatePrompt />
|
||
</template>
|