快捷入口已经挪到顶部菜单栏了。

顶部在「工具」和「账号」之间多了一个 快捷 菜单:已钉的入口会列在里面(带图标和未读数),点一项就能跳转。底下还有「添加快捷入口」,用来打开原来的选择面板。有未读时,菜单名旁边会有一个小红点。

概览侧栏里的快捷入口和「添加」按钮已经去掉,之前钉过的项会继续出现在顶部菜单里。
This commit is contained in:
李琦
2026-08-17 14:38:36 +08:00
parent e71ce451ff
commit 684a79be6f
8 changed files with 152 additions and 125 deletions

View File

@@ -2,7 +2,7 @@
import { computed, onMounted, onUnmounted, ref, watch } from 'vue'
import { useRoute, useRouter } from 'vue-router'
import { useI18n } from 'vue-i18n'
import { LayoutDashboard, ScrollText, Settings, X, Database, SlidersHorizontal, Home, ListTodo, TicketCheck, CalendarDays, CalendarCheck2, Sparkles, UserRound, ClipboardList, Wrench, Rocket, ChevronDown, Bell, StickyNote, ListFilter, Palette, Bot, Users, NotebookPen, CloudUpload, Power, Image, Activity, Package, Shield, Plus, Pin, PinOff } from 'lucide-vue-next'
import { LayoutDashboard, ScrollText, Settings, X, Database, SlidersHorizontal, Home, ListTodo, TicketCheck, CalendarDays, CalendarCheck2, Sparkles, UserRound, ClipboardList, Wrench, Rocket, ChevronDown, Bell, StickyNote, ListFilter, Palette, Bot, Users, NotebookPen, CloudUpload, Power, Image, Activity, Package, Shield, Plus, Pin } from 'lucide-vue-next'
import { useAppStore } from './store'
import DatabaseSetup from './components/DatabaseSetup.vue'
import BrowserBlocked from './components/BrowserBlocked.vue'
@@ -108,7 +108,7 @@ const navGroups = [
] }
]
// ---- 概览快捷入口:把其它分区菜单钉到「概览」二级导航 ----
// ---- 顶栏快捷入口:把常用菜单钉到 TitleBar「快捷」下拉 ----
const SHORTCUT_KEY = 'cc-nav-shortcuts'
const loadShortcuts = () => {
try {
@@ -128,7 +128,7 @@ const catalogByTo = computed(() => {
}
return map
})
// 可钉到概览的项:非概览内置、且当前用户可见
// 可钉到顶栏的项:非概览内置、且当前用户可见
const pinnableItems = computed(() => {
const overviewTos = new Set(navGroups[0].items.map(it => it.to))
const out = []
@@ -146,7 +146,6 @@ function addShortcut(to) {
if (navShortcuts.value.includes(to)) return
navShortcuts.value = [...navShortcuts.value, to]
saveShortcuts()
shortcutPicker.value = false
}
function removeShortcut(to) {
navShortcuts.value = navShortcuts.value.filter(x => x !== to)
@@ -157,18 +156,19 @@ function toggleShortcut(to) {
else addShortcut(to)
}
// 钉到顶栏「快捷」菜单的项(带徽标数字,供 TitleBar 渲染)
const pinnedShortcuts = computed(() => navShortcuts.value
.map(to => catalogByTo.value.get(to))
.filter(it => it && (!it.adminOnly || store.syncStatus.userId === 1))
.map(it => ({
to: it.to,
label: it.label,
icon: it.icon,
badge: it.badge ? (store.badges[it.badge] || 0) : 0
})))
// adminOnly 项只对云端管理员id=1展示
const visibleItems = g => {
let items = g.items.filter(it => !it.adminOnly || store.syncStatus.userId === 1)
if (g.id === 'overview') {
const extras = navShortcuts.value
.map(to => catalogByTo.value.get(to))
.filter(it => it && (!it.adminOnly || store.syncStatus.userId === 1))
.map(it => ({ ...it, shortcut: true }))
items = [...items, ...extras]
}
return items
}
const visibleItems = g => g.items.filter(it => !it.adminOnly || store.syncStatus.userId === 1)
const itemActive = it => it.match ? it.match(route) : route.path === it.to.split('?')[0]
// 导航徽标badge 显示数字dot 只显示小圆点;一级 rail 在组内任一非零时亮点
const badgeVal = it => it.badge ? (store.badges[it.badge] || 0) : 0
@@ -186,7 +186,6 @@ watch(() => route.fullPath, () => {
const g = groupOfRoute()
if (g) activeGroupId.value = g
railFlyout.value = null
if (g !== 'overview') shortcutPicker.value = false
for (const grp of navGroups) {
for (const it of grp.items) if (it.children && itemActive(it)) expandedParents.value[it.label] = true
}
@@ -288,7 +287,7 @@ watch(activeTask, task => {
<template>
<BrowserBlocked v-if="!native" />
<template v-else>
<TitleBar :app-version="appVersion" @about="aboutOpen = true" />
<TitleBar :app-version="appVersion" :shortcuts="pinnedShortcuts" @about="aboutOpen = true" @manage-shortcuts="shortcutPicker = true" />
<DatabaseSetup v-if="store.bootstrap.state !== 'ready' && store.bootstrap.state !== 'loading'" :status="store.bootstrap" class="with-titlebar" />
<div v-else-if="store.bootstrap.state === 'ready'" class="shell">
<aside class="sidebar">
@@ -324,29 +323,16 @@ watch(activeTask, task => {
<div class="sub-title">{{ t(activeGroup.label) }}</div>
<nav class="sub-nav" :aria-label="t(activeGroup.label)">
<template v-for="it in visibleItems(activeGroup)" :key="it.to">
<RouterLink :to="it.to" :class="{ active: itemActive(it), shortcut: it.shortcut }">
<RouterLink :to="it.to" :class="{ active: itemActive(it) }">
<component :is="it.icon" /><span>{{ t(it.label) }}</span>
<em v-if="badgeVal(it)" class="nav-badge">{{ badgeText(it) }}</em>
<i v-else-if="dotVal(it)" class="nav-dot" aria-hidden="true" />
<button
v-if="it.shortcut"
type="button"
class="nav-unpin"
:title="t('navUnpinShortcut')"
@click.prevent.stop="removeShortcut(it.to)"
><PinOff /></button>
<button v-if="it.children" type="button" class="sub-caret" :class="{ open: expandedParents[it.label] }" :aria-label="t(it.label)" @click="toggleParent(it, $event)"><ChevronDown /></button>
</RouterLink>
<div v-if="it.children && expandedParents[it.label]" class="sub-children">
<RouterLink v-for="c in it.children" :key="c.to" :to="c.to" :class="{ active: childActive(c) }">{{ t(c.label) }}</RouterLink>
</div>
</template>
<button
v-if="activeGroup.id === 'overview'"
type="button"
class="nav-add-shortcut"
@click="shortcutPicker = !shortcutPicker"
><Plus />{{ t('navAddShortcut') }}</button>
</nav>
<div class="sidebar-bottom">
<span class="version"><i />v{{ appVersion }}</span>