chore: initial commit

This commit is contained in:
2026-07-30 17:59:42 +08:00
commit 155f06846d
62 changed files with 7852 additions and 0 deletions
+117
View File
@@ -0,0 +1,117 @@
import type { Component } from 'vue'
import {
Building2,
CircleUserRound,
ClipboardList,
KeyRound,
LayoutGrid,
Settings,
ShieldCheck,
Truck,
Users,
} from '@lucide/vue'
export interface ClientNavigationBaseNode {
key: string
title: string
icon: Component
}
export interface ClientNavigationLeaf extends ClientNavigationBaseNode {
path: string
permission: string
children?: never
}
export interface ClientNavigationGroup extends ClientNavigationBaseNode {
path?: never
permission?: never
children: readonly [ClientNavigationNode, ...ClientNavigationNode[]]
}
export type ClientNavigationNode = ClientNavigationLeaf | ClientNavigationGroup
export const clientNavigation: ClientNavigationNode[] = [
{ key: 'dashboard', title: '业务总览', icon: LayoutGrid, path: '/', permission: 'dashboard:view' },
{ key: 'account-profile', title: '我的信息', icon: CircleUserRound, path: '/account/profile', permission: 'account:profile:view' },
{
key: 'first-leg',
title: '头程业务',
icon: Truck,
children: [
{ key: 'first-leg-orders', title: '头程订单', icon: ClipboardList, path: '/orders/first-leg', permission: 'order:first-leg:list' },
],
},
{
key: 'company',
title: '企业管理',
icon: Settings,
children: [
{ key: 'company-profile', title: '企业资料', icon: Building2, path: '/company/profile', permission: 'company:profile:view' },
{ key: 'company-members', title: '企业成员', icon: Users, path: '/organization/members', permission: 'company:member:list' },
{ key: 'company-roles', title: '角色管理', icon: ShieldCheck, path: '/organization/roles', permission: 'company:role:list' },
{ key: 'company-permissions', title: '菜单权限', icon: KeyRound, path: '/organization/permissions', permission: 'company:permission:list' },
],
},
]
function isNavigationGroup(node: ClientNavigationNode): node is ClientNavigationGroup {
return 'children' in node && node.children !== undefined
}
export function getNavigationLeaves(nodes: readonly ClientNavigationNode[]): ClientNavigationLeaf[] {
return nodes.flatMap((node) => {
if (isNavigationGroup(node)) return getNavigationLeaves(node.children)
return [node]
})
}
export function filterNavigationByPermissions(
nodes: readonly ClientNavigationNode[],
permissions: readonly string[],
): ClientNavigationNode[] {
const allowed = new Set(permissions)
const result: ClientNavigationNode[] = []
for (const node of nodes) {
if (!isNavigationGroup(node)) {
if (allowed.has(node.permission)) result.push(node)
continue
}
const children = filterNavigationByPermissions(node.children, permissions)
if (children.length > 0) result.push({ ...node, children: children as [ClientNavigationNode, ...ClientNavigationNode[]] })
}
return result
}
export function getFirstAccessiblePath(permissions: readonly string[]) {
return getNavigationLeaves(filterNavigationByPermissions(clientNavigation, permissions))[0]?.path ?? '/forbidden'
}
function findNavigationParentKeys(
nodes: readonly ClientNavigationNode[],
path: string,
parentKeys: readonly string[],
): string[] | undefined {
for (const node of nodes) {
if (isNavigationGroup(node)) {
const result = findNavigationParentKeys(node.children, path, [...parentKeys, node.key])
if (result) return result
} else if (node.path === path) {
return [...parentKeys]
}
}
}
export function getNavigationParentKeys(nodes: readonly ClientNavigationNode[], path: string): string[] {
return findNavigationParentKeys(nodes, path, []) ?? []
}
export function openNavigationParentKeys(
path: string,
nodes: readonly ClientNavigationNode[],
open: (key: string) => void,
) {
for (const key of getNavigationParentKeys(nodes, path)) {
open(key)
}
}