This commit is contained in:
2026-08-03 12:02:15 +08:00
parent 4edc2c1907
commit a9283f2bf8
2 changed files with 57 additions and 106 deletions
+10 -55
View File
@@ -41,7 +41,7 @@ test('keeps the confirmed top-level sequence from the client workbench prototype
) )
}) })
test('keeps the confirmed navigation groups and leaf order', () => { test('keeps only implemented navigation groups visible', () => {
assert.deepEqual( assert.deepEqual(
clientNavigation.map((node) => ({ clientNavigation.map((node) => ({
title: node.title, title: node.title,
@@ -51,23 +51,7 @@ test('keeps the confirmed navigation groups and leaf order', () => {
{ title: '业务总览', children: undefined }, { title: '业务总览', children: undefined },
{ {
title: '头程物流', title: '头程物流',
children: ['头程下单', '一键导入', '快速下单'], children: ['头程下单'],
},
{
title: '出海下单',
children: ['海外采购', '美国本土卡', '美本土散板'],
},
{
title: '海外仓',
children: ['贴标申请', '退货入库', '退货换标', '退货出库'],
},
{
title: '订单管理',
children: ['我的订单', '入库订单列表', '派送订单列表'],
},
{
title: '财务结算',
children: ['应付账单'],
}, },
{ {
title: '企业管理', title: '企业管理',
@@ -78,7 +62,7 @@ test('keeps the confirmed navigation groups and leaf order', () => {
) )
}) })
test('provides icons and unique keys recursively for every prototype item', () => { test('provides icons and unique keys recursively for every visible item', () => {
const nodes = collectNavigationNodes(clientNavigation) const nodes = collectNavigationNodes(clientNavigation)
for (const node of nodes) { for (const node of nodes) {
@@ -87,26 +71,13 @@ test('provides icons and unique keys recursively for every prototype item', () =
assert.equal(new Set(nodes.map((node) => node.key)).size, nodes.length) assert.equal(new Set(nodes.map((node) => node.key)).size, nodes.length)
}) })
test('flattens the complete prototype tree with unique paths', () => { test('flattens visible leaves with unique paths', () => {
const leaves = getNavigationLeaves(clientNavigation) const leaves = getNavigationLeaves(clientNavigation)
const paths = leaves.map((leaf) => leaf.path) const paths = leaves.map((leaf) => leaf.path)
assert.deepEqual(paths, [ assert.deepEqual(paths, [
'/', '/',
'/orders/first-leg', '/orders/first-leg',
'/first-leg/import',
'/first-leg/quick-order',
'/cross-border/procurement',
'/cross-border/domestic-trucking',
'/cross-border/pallet-shipping',
'/overseas-warehouse/labeling',
'/overseas-warehouse/return-inbound',
'/overseas-warehouse/return-relabel',
'/overseas-warehouse/return-outbound',
'/orders/my',
'/orders/inbound',
'/orders/delivery',
'/finance/payables',
'/company/profile', '/company/profile',
'/organization/members', '/organization/members',
'/organization/roles', '/organization/roles',
@@ -114,39 +85,23 @@ test('flattens the complete prototype tree with unique paths', () => {
'/account/profile', '/account/profile',
]) ])
assert.equal(new Set(paths).size, paths.length) assert.equal(new Set(paths).size, paths.length)
assert.ok(leaves.filter((leaf) => leaf.permission).every((leaf) => leaf.permission?.trim().length > 0)) assert.ok(leaves.every((leaf) => leaf.permission.trim().length > 0))
}) })
test('filters inaccessible leaves and empty navigation groups', () => { test('filters inaccessible leaves and empty navigation groups', () => {
const visible = filterNavigationByPermissions(clientNavigation, ['account:profile:view', 'order:first-leg:list']) const visible = filterNavigationByPermissions(clientNavigation, ['account:profile:view', 'order:first-leg:list'])
assert.deepEqual(getNavigationLeaves(visible).map((item) => item.path), [ assert.deepEqual(getNavigationLeaves(visible).map((item) => item.path), ['/orders/first-leg', '/account/profile'])
'/orders/first-leg', assert.deepEqual(visible.map((item) => item.title), ['头程物流', '我的信息'])
'/first-leg/import',
'/first-leg/quick-order',
'/cross-border/procurement',
'/cross-border/domestic-trucking',
'/cross-border/pallet-shipping',
'/overseas-warehouse/labeling',
'/overseas-warehouse/return-inbound',
'/overseas-warehouse/return-relabel',
'/overseas-warehouse/return-outbound',
'/orders/my',
'/orders/inbound',
'/orders/delivery',
'/finance/payables',
'/account/profile',
])
assert.deepEqual(visible.map((item) => item.title), ['头程物流', '出海下单', '海外仓', '订单管理', '财务结算', '我的信息'])
assert.equal(getFirstAccessiblePath(['account:profile:view']), '/account/profile') assert.equal(getFirstAccessiblePath(['account:profile:view']), '/account/profile')
assert.equal(getFirstAccessiblePath([]), '/forbidden') assert.equal(getFirstAccessiblePath([]), '/forbidden')
}) })
test('excludes entries marked for manual hiding without affecting other visible items', () => { test('excludes entries marked for manual hiding without affecting other visible items', () => {
const visible = filterNavigationByPermissions([ const visible = filterNavigationByPermissions([
{ key: 'shown', title: '显示入口', icon: LayoutGrid, path: '/shown' }, { key: 'shown', title: '显示入口', icon: LayoutGrid, path: '/shown', permission: 'shown:view' },
{ key: 'hidden', title: '隐藏入口', icon: LayoutGrid, path: '/hidden', hidden: true }, { key: 'hidden', title: '隐藏入口', icon: LayoutGrid, path: '/hidden', permission: 'hidden:view', hidden: true },
], []) ], ['shown:view', 'hidden:view'])
assert.deepEqual(getNavigationLeaves(visible).map((item) => item.path), ['/shown']) assert.deepEqual(getNavigationLeaves(visible).map((item) => item.path), ['/shown'])
}) })
+47 -51
View File
@@ -5,17 +5,10 @@ import {
ClipboardList, ClipboardList,
KeyRound, KeyRound,
LayoutGrid, LayoutGrid,
Package,
PlaneTakeoff,
ReceiptText,
Settings, Settings,
Ship,
ShieldCheck, ShieldCheck,
Tags,
Truck, Truck,
Users, Users,
Warehouse,
Zap,
} from '@lucide/vue' } from '@lucide/vue'
export interface ClientNavigationBaseNode { export interface ClientNavigationBaseNode {
@@ -28,7 +21,7 @@ export interface ClientNavigationBaseNode {
export interface ClientNavigationLeaf extends ClientNavigationBaseNode { export interface ClientNavigationLeaf extends ClientNavigationBaseNode {
path: string path: string
permission?: string permission: string
children?: never children?: never
} }
@@ -48,49 +41,52 @@ export const clientNavigation: ClientNavigationNode[] = [
icon: Truck, icon: Truck,
children: [ children: [
{ key: 'first-leg-orders', title: '头程下单', icon: ClipboardList, path: '/orders/first-leg', permission: 'order:first-leg:list' }, { key: 'first-leg-orders', title: '头程下单', icon: ClipboardList, path: '/orders/first-leg', permission: 'order:first-leg:list' },
{ key: 'first-leg-import', title: '一键导入', icon: Package, path: '/first-leg/import' }, // 原型入口:接入批量导入接口与权限资源后再放开。
{ key: 'first-leg-quick-order', title: '快速下单', icon: Zap, path: '/first-leg/quick-order' }, // { key: 'first-leg-import', title: '一键导入', icon: Package, path: '/first-leg/import', permission: 'merchant:first-leg:import' },
], // 原型入口:接入快速下单接口与权限资源后再放开。
}, // { key: 'first-leg-quick-order', title: '快速下单', icon: Zap, path: '/first-leg/quick-order', permission: 'merchant:first-leg:quick-create' },
{
key: 'cross-border',
title: '出海下单',
icon: PlaneTakeoff,
children: [
{ key: 'cross-border-procurement', title: '海外采购', icon: Package, path: '/cross-border/procurement' },
{ key: 'cross-border-domestic-trucking', title: '美国本土卡', icon: Truck, path: '/cross-border/domestic-trucking' },
{ key: 'cross-border-pallet-shipping', title: '美本土散板', icon: Ship, path: '/cross-border/pallet-shipping' },
],
},
{
key: 'overseas-warehouse',
title: '海外仓',
icon: Warehouse,
children: [
{ key: 'overseas-labeling', title: '贴标申请', icon: Tags, path: '/overseas-warehouse/labeling' },
{ key: 'overseas-return-inbound', title: '退货入库', icon: Package, path: '/overseas-warehouse/return-inbound' },
{ key: 'overseas-return-relabel', title: '退货换标', icon: Tags, path: '/overseas-warehouse/return-relabel' },
{ key: 'overseas-return-outbound', title: '退货出库', icon: Truck, path: '/overseas-warehouse/return-outbound' },
],
},
{
key: 'order-management',
title: '订单管理',
icon: ClipboardList,
children: [
{ key: 'orders-my', title: '我的订单', icon: ReceiptText, path: '/orders/my' },
{ key: 'orders-inbound', title: '入库订单列表', icon: Package, path: '/orders/inbound' },
{ key: 'orders-delivery', title: '派送订单列表', icon: Truck, path: '/orders/delivery' },
],
},
{
key: 'finance',
title: '财务结算',
icon: ReceiptText,
children: [
{ key: 'finance-payables', title: '应付账单', icon: ReceiptText, path: '/finance/payables' },
], ],
}, },
// 原型菜单:对应页面目录与路由已预留,接入后端接口、权限资源后取消注释。
// {
// key: 'cross-border',
// title: '出海下单',
// icon: PlaneTakeoff,
// children: [
// { key: 'cross-border-procurement', title: '海外采购', icon: Package, path: '/cross-border/procurement', permission: 'merchant:cross-border:procurement' },
// { key: 'cross-border-domestic-trucking', title: '美国本土卡', icon: Truck, path: '/cross-border/domestic-trucking', permission: 'merchant:cross-border:domestic-trucking' },
// { key: 'cross-border-pallet-shipping', title: '美本土散板', icon: Ship, path: '/cross-border/pallet-shipping', permission: 'merchant:cross-border:pallet-shipping' },
// ],
// },
// {
// key: 'overseas-warehouse',
// title: '海外仓',
// icon: Warehouse,
// children: [
// { key: 'overseas-labeling', title: '贴标申请', icon: Tags, path: '/overseas-warehouse/labeling', permission: 'merchant:overseas-warehouse:labeling' },
// { key: 'overseas-return-inbound', title: '退货入库', icon: Package, path: '/overseas-warehouse/return-inbound', permission: 'merchant:overseas-warehouse:return-inbound' },
// { key: 'overseas-return-relabel', title: '退货换标', icon: Tags, path: '/overseas-warehouse/return-relabel', permission: 'merchant:overseas-warehouse:return-relabel' },
// { key: 'overseas-return-outbound', title: '退货出库', icon: Truck, path: '/overseas-warehouse/return-outbound', permission: 'merchant:overseas-warehouse:return-outbound' },
// ],
// },
// {
// key: 'order-management',
// title: '订单管理',
// icon: ClipboardList,
// children: [
// { key: 'orders-my', title: '我的订单', icon: ReceiptText, path: '/orders/my', permission: 'merchant:order:list' },
// { key: 'orders-inbound', title: '入库订单列表', icon: Package, path: '/orders/inbound', permission: 'merchant:warehouse:inbound:list' },
// { key: 'orders-delivery', title: '派送订单列表', icon: Truck, path: '/orders/delivery', permission: 'merchant:warehouse:delivery:list' },
// ],
// },
// {
// key: 'finance',
// title: '财务结算',
// icon: ReceiptText,
// children: [
// { key: 'finance-payables', title: '应付账单', icon: ReceiptText, path: '/finance/payables', permission: 'merchant:finance:payables:list' },
// ],
// },
{ {
key: 'company', key: 'company',
title: '企业管理', title: '企业管理',
@@ -126,7 +122,7 @@ export function filterNavigationByPermissions(
for (const node of nodes) { for (const node of nodes) {
if (node.hidden) continue if (node.hidden) continue
if (!isNavigationGroup(node)) { if (!isNavigationGroup(node)) {
if (!node.permission || allowed.has(node.permission)) result.push(node) if (allowed.has(node.permission)) result.push(node)
continue continue
} }
const children = filterNavigationByPermissions(node.children, permissions) const children = filterNavigationByPermissions(node.children, permissions)
@@ -136,7 +132,7 @@ export function filterNavigationByPermissions(
} }
export function getFirstAccessiblePath(permissions: readonly string[]) { export function getFirstAccessiblePath(permissions: readonly string[]) {
return getNavigationLeaves(filterNavigationByPermissions(clientNavigation, permissions)).find((leaf) => leaf.permission)?.path ?? '/forbidden' return getNavigationLeaves(filterNavigationByPermissions(clientNavigation, permissions))[0]?.path ?? '/forbidden'
} }
function findNavigationParentKeys( function findNavigationParentKeys(