优化构架
This commit is contained in:
@@ -0,0 +1,88 @@
|
||||
import { mount } from '@vue/test-utils'
|
||||
import { describe, expect, it } from 'vitest'
|
||||
import { adminNavigation, filterAdminNavigation, type AdminNavigationNode } from '@/core/navigation/adminNavigation'
|
||||
import AdminSidebarMenu from './AdminSidebarMenu.vue'
|
||||
|
||||
const stubs = {
|
||||
'el-menu': {
|
||||
props: ['defaultActive', 'collapse'],
|
||||
template: '<nav :data-active="defaultActive" :data-collapsed="String(collapse)"><slot /></nav>',
|
||||
},
|
||||
'el-sub-menu': {
|
||||
props: ['index'],
|
||||
template: '<section data-kind="group" :data-index="index"><header><slot name="title" /></header><slot /></section>',
|
||||
},
|
||||
'el-menu-item': {
|
||||
props: ['index'],
|
||||
template: '<a data-kind="item" :data-index="index"><slot name="title" /><slot /></a>',
|
||||
},
|
||||
}
|
||||
|
||||
describe('AdminSidebarMenu', () => {
|
||||
it('renders the visible phase-one modules and forwards the active route', () => {
|
||||
const wrapper = mount(AdminSidebarMenu, {
|
||||
props: { items: adminNavigation, activePath: '/system/users', collapsed: false },
|
||||
global: { stubs },
|
||||
})
|
||||
|
||||
expect(wrapper.get('nav').attributes('data-active')).toBe('/system/users')
|
||||
const groupTitles = wrapper.findAll('[data-kind="group"] header').map((node) => node.text())
|
||||
const paths = wrapper.findAll('[data-kind="item"]').map((node) => node.attributes('data-index'))
|
||||
|
||||
expect(groupTitles).toContain('客户管理')
|
||||
expect(groupTitles).toContain('系统管理')
|
||||
expect(groupTitles).not.toContain('财务管理')
|
||||
expect(paths).not.toContain('/dashboard')
|
||||
expect(paths).toContain('/orders/first-leg')
|
||||
expect(paths).not.toContain('/prototype/reports/overview')
|
||||
expect(paths).toContain('/system/permissions')
|
||||
})
|
||||
|
||||
it('forwards desktop collapse state to Element Plus', () => {
|
||||
const wrapper = mount(AdminSidebarMenu, {
|
||||
props: { items: adminNavigation, activePath: '/', collapsed: true },
|
||||
global: { stubs },
|
||||
})
|
||||
|
||||
expect(wrapper.get('nav').attributes('data-collapsed')).toBe('true')
|
||||
})
|
||||
|
||||
it('renders only entries included in the filtered navigation input', () => {
|
||||
const items = filterAdminNavigation(adminNavigation, new Set(['platform:permission:list']))
|
||||
const wrapper = mount(AdminSidebarMenu, {
|
||||
props: { items, activePath: '/', collapsed: false },
|
||||
global: { stubs },
|
||||
})
|
||||
|
||||
expect(wrapper.text()).toContain('菜单权限')
|
||||
expect(wrapper.text()).not.toContain('首页数据看板')
|
||||
expect(wrapper.text()).not.toContain('客户列表')
|
||||
})
|
||||
|
||||
it('recursively renders three navigation levels', () => {
|
||||
const items: AdminNavigationNode[] = [{
|
||||
key: 'operations',
|
||||
title: '运营管理',
|
||||
icon: adminNavigation[0].icon,
|
||||
children: [{
|
||||
key: 'shipment',
|
||||
title: '运输管理',
|
||||
icon: adminNavigation[0].icon,
|
||||
children: [{
|
||||
key: 'tracking',
|
||||
title: '轨迹管理',
|
||||
icon: adminNavigation[0].icon,
|
||||
path: '/tracking',
|
||||
permission: 'tracking:list',
|
||||
}],
|
||||
}],
|
||||
}]
|
||||
const wrapper = mount(AdminSidebarMenu, {
|
||||
props: { items, activePath: '/tracking', collapsed: false },
|
||||
global: { stubs },
|
||||
})
|
||||
|
||||
expect(wrapper.findAll('[data-kind="group"]')).toHaveLength(2)
|
||||
expect(wrapper.get('[data-kind="item"]').attributes('data-index')).toBe('/tracking')
|
||||
})
|
||||
})
|
||||
@@ -0,0 +1,31 @@
|
||||
<script setup lang="ts">
|
||||
import { computed } from 'vue'
|
||||
import type { AdminNavigationNode } from '@/core/navigation/adminNavigation'
|
||||
import AdminSidebarMenuNode from './AdminSidebarMenuNode.vue'
|
||||
|
||||
const props = defineProps<{
|
||||
items: AdminNavigationNode[]
|
||||
activePath: string
|
||||
collapsed: boolean
|
||||
}>()
|
||||
|
||||
function getGroupKeys(nodes: AdminNavigationNode[]): string[] {
|
||||
return nodes.flatMap((node) => node.children?.length
|
||||
? [node.key, ...getGroupKeys(node.children)]
|
||||
: [])
|
||||
}
|
||||
|
||||
const defaultOpeneds = computed(() => getGroupKeys(props.items))
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<el-menu
|
||||
class="side-nav-menu"
|
||||
:default-active="activePath"
|
||||
:collapse="collapsed"
|
||||
:default-openeds="defaultOpeneds"
|
||||
router
|
||||
>
|
||||
<AdminSidebarMenuNode v-for="item in items" :key="item.key" :node="item" :level="0" />
|
||||
</el-menu>
|
||||
</template>
|
||||
@@ -0,0 +1,40 @@
|
||||
<script setup lang="ts">
|
||||
import type { AdminNavigationNode } from '@/core/navigation/adminNavigation'
|
||||
|
||||
defineOptions({ name: 'AdminSidebarMenuNode' })
|
||||
|
||||
defineProps<{
|
||||
node: AdminNavigationNode
|
||||
level?: number
|
||||
}>()
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<el-sub-menu v-if="node.children?.length" :index="node.key">
|
||||
<template #title>
|
||||
<component
|
||||
v-if="!level"
|
||||
:is="node.icon"
|
||||
class="menu-icon"
|
||||
:style="{ color: node.iconColor }"
|
||||
:size="20"
|
||||
:stroke-width="1.9"
|
||||
/>
|
||||
<span>{{ node.title }}</span>
|
||||
</template>
|
||||
|
||||
<AdminSidebarMenuNode v-for="child in node.children" :key="child.key" :node="child" :level="(level ?? 0) + 1" />
|
||||
</el-sub-menu>
|
||||
|
||||
<el-menu-item v-else :index="node.path || node.key">
|
||||
<component
|
||||
v-if="!level"
|
||||
:is="node.icon"
|
||||
class="menu-icon"
|
||||
:style="{ color: node.iconColor }"
|
||||
:size="20"
|
||||
:stroke-width="1.9"
|
||||
/>
|
||||
<template #title>{{ node.title }}</template>
|
||||
</el-menu-item>
|
||||
</template>
|
||||
Reference in New Issue
Block a user