import { defineStore } from 'pinia' import { computed, ref } from 'vue' export interface AppMenuItem { id: string key: string label: string icon?: string type: 'pagetype' | 'route' | 'url' | 'workspace' | 'group' // 菜单类型,新增 group pagetype?: string // 页面类型名称(如:Local AI Agent) routeName?: string // 路由名 url?: string // URL路径 workspaceName?: string // Workspace 名称(工作区文档名) // 层级关系 parentId?: string | null order?: number hidden?: boolean children?: AppMenuItem[] page?: { pagetype: string list?: boolean detail?: boolean order_by?: string } } const STORAGE_KEY = 'app.menu.items' function loadFromStorage(): AppMenuItem[] { try { const raw = localStorage.getItem(STORAGE_KEY) if (!raw) return [] const parsed = JSON.parse(raw) if (Array.isArray(parsed)) return parsed return [] } catch { return [] } } function saveToStorage(items: AppMenuItem[]) { localStorage.setItem(STORAGE_KEY, JSON.stringify(items)) } // 默认菜单,与现有路由对应 function getDefaultMenus(): AppMenuItem[] { return [ { id: 'dashboard', key: 'Dashboard', label: 'Dashboard', icon: 'tabler:dashboard', routeName: 'Dashboard', order: 1, type: 'route' }, { id: 'work', key: 'work', label: 'Work', icon: 'tabler:device-desktop', type: 'workspace', workspaceName: 'work', url: '/workspace/work', order: 2 }, { id: 'design', key: 'design', label: 'Design', icon: 'tabler:pencil', type: 'workspace', workspaceName: 'design', url: '/workspace/design', order: 3 }, { id: 'website', key: 'website', label: 'Website', icon: 'tabler:world', type: 'workspace', workspaceName: 'jsite', url: '/workspace/jsite', order: 4 }, { id: 'agents', key: 'local-ai-agent', label: 'Agents', icon: 'hugeicons:robotic', type: 'pagetype', pagetype: 'Local Ai Agent', order: 5 }, { id: 'nodes', key: 'local-ai-node', label: 'Nodes', icon: 'carbon:add-child-node', type: 'pagetype', pagetype: 'Local Ai Node', order: 6 }, { id: 'localJobs', key: 'LocalJobList', label: 'Task Queue', icon: 'iconoir:task-list', type: 'route', routeName: 'LocalJobList', order: 7 }, { id: 'scheduledJobs', key: 'ScheduledJobList', label: 'Scheduled Jobs', icon: 'carbon:event-schedule', type: 'route', routeName: 'ScheduledJobList', order: 8 }, { id: 'dev-group', key: 'dev-group', label: 'Development', icon: 'tabler:code', type: 'group', order: 9 }, { id: 'dev-template', key: 'dev-template', label: 'PageType Template', icon: 'tabler:file-code', type: 'route', routeName: 'CreatePagetypeTemplate', parentId: 'dev-group', order: 1 }, { id: 'dev-app-template', key: 'dev-app-template', label: 'App Template', icon: 'tabler:app-window', type: 'route', routeName: 'CreateAppTemplate', parentId: 'dev-group', order: 1.5 }, { id: 'package', key: 'package', label: 'Package', icon: 'tabler:package', type: 'pagetype', pagetype: 'Package', parentId: 'dev-group', order: 2 }, { id: 'package-release', key: 'package-release', label: 'Package Release', icon: 'tabler:package-export', type: 'pagetype', pagetype: 'Package Release', parentId: 'dev-group', order: 3 }, { id: 'app-installer', key: 'AppInstaller', label: 'App Installer', icon: 'tabler:upload', type: 'route', routeName: 'AppInstaller', parentId: 'dev-group', order: 5 }, { id: 'installed-apps', key: 'InstalledApps', label: 'Installed Apps', icon: 'tabler:apps', type: 'route', routeName: 'InstalledApps', parentId: 'dev-group', order: 6 }, { id: 'my-published-apps', key: 'MyPublishedApps', label: 'My Published Apps', icon: 'tabler:cloud-upload', type: 'route', routeName: 'MyPublishedApps', parentId: 'dev-group', order: 7 }, { id: 'my-published-nodes', key: 'MyPublishedNodes', label: '已发布节点', icon: 'carbon:add-child-node', type: 'route', routeName: 'MyPublishedNodes', parentId: 'dev-group', order: 7.1 }, { id: 'my-published-agents', key: 'MyPublishedAgents', label: '已发布智能体', icon: 'hugeicons:robotic', type: 'route', routeName: 'MyPublishedAgents', parentId: 'dev-group', order: 7.2 }, { id: 'app-marketplace', key: 'AppMarketplace', label: 'App Marketplace', icon: 'tabler:shopping-cart', type: 'route', routeName: 'AppMarketplace', parentId: 'dev-group', order: 7.5 }, { id: 'node-marketplace', key: 'NodeMarketplace', label: 'Node Marketplace', icon: 'carbon:add-child-node', type: 'route', routeName: 'NodeMarketplace', parentId: 'dev-group', order: 8 }, { id: 'agent-marketplace', key: 'AgentMarketplace', label: 'Agent Marketplace', icon: 'hugeicons:robotic', type: 'route', routeName: 'AgentMarketplace', parentId: 'dev-group', order: 8.5 }, { id: 'menuManager', key: 'MenuManager', label: 'Menu Management', icon: 'tabler:menu-2', type: 'route', routeName: 'MenuManager', order: 10 }, { id: 'settings', key: 'Settings', label: 'Settings', icon: 'tabler:settings', routeName: 'Settings', order: 11, type: 'route' } ] } export const useMenuStore = defineStore('menu', () => { // 正确初始化为数组,而不是函数 const initItems = () => { const cached = loadFromStorage() if (Array.isArray(cached) && cached.length) return cached as AppMenuItem[] const defaults = getDefaultMenus() saveToStorage(defaults) return defaults } const items = ref(initItems()) // 统一保存 function persist() { // 分层排序:先按层级分组,再分别排序 const rootMenus = items.value.filter(m => !m.parentId) const childMenus = items.value.filter(m => m.parentId) // 对根菜单排序 rootMenus.sort((a, b) => (a.order ?? 0) - (b.order ?? 0)) // 对子菜单按父菜单分组排序 const groupedChildren = childMenus.reduce((acc, child) => { const parentId = child.parentId! if (!acc[parentId]) acc[parentId] = [] acc[parentId].push(child) return acc }, {} as Record) // 对每个父菜单的子菜单排序 Object.values(groupedChildren).forEach(children => { children.sort((a, b) => (a.order ?? 0) - (b.order ?? 0)) }) // 重新组合:先放根菜单,再按父菜单顺序放子菜单 const sorted: AppMenuItem[] = [] rootMenus.forEach(root => { sorted.push(root) if (groupedChildren[root.id]) { sorted.push(...groupedChildren[root.id]) } }) saveToStorage(sorted) items.value = sorted } function addMenu(item: AppMenuItem) { const newItem = { ...item, id: item.id || crypto.randomUUID() } // 如果没有指定order,自动分配 if (newItem.order === undefined || newItem.order === null) { if (newItem.parentId) { // 子菜单:在父菜单下分配独立的order const siblings = items.value.filter(m => m.parentId === newItem.parentId) const maxSiblingOrder = Math.max(...siblings.map(s => s.order ?? 0), 0) newItem.order = maxSiblingOrder + 1 } else { // 父菜单:在根级别分配独立的order const rootMenus = items.value.filter(m => !m.parentId) const maxRootOrder = Math.max(...rootMenus.map(r => r.order ?? 0), 0) newItem.order = maxRootOrder + 1 } } items.value.push(newItem) persist() } function updateMenu(id: string, patch: Partial) { const idx = items.value.findIndex(m => m.id === id) if (idx >= 0) { items.value[idx] = { ...items.value[idx], ...patch } persist() } } function removeMenu(id: string) { items.value = items.value.filter(m => m.id !== id) persist() } function resetDefault() { items.value = getDefaultMenus() persist() } function clearCache() { localStorage.removeItem(STORAGE_KEY) items.value = getDefaultMenus() persist() } const visibleItems = computed(() => items.value.filter(m => !m.hidden)) return { items, visibleItems, addMenu, updateMenu, removeMenu, resetDefault, clearCache, persist } })