553 lines
16 KiB
Markdown
553 lines
16 KiB
Markdown
# Admin Grouped Navigation Implementation Plan
|
|||
|
|
|
||
|
|
> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.
|
||
|
|
|
||
|
|
**Goal:** Replace the flat admin sidebar with maintainable two-level navigation while preserving every existing static page and interaction.
|
||
|
|
|
||
|
|
**Architecture:** Keep Vue Router responsible only for routes and move admin menu metadata into a local navigation module. Render the configuration through a focused Element Plus sidebar component, while all business pages continue to use the current Pinia mock store and make no Java API calls.
|
||
|
|
|
||
|
|
**Tech Stack:** Vue 3, TypeScript, Vue Router, Pinia, Element Plus, Lucide Vue, Vitest, Vue Test Utils, jsdom, pnpm
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
The repository is not a Git worktree. Commit steps are intentionally omitted; each green test/build checkpoint is the recovery boundary.
|
||
|
|
|
||
|
|
## File map
|
||
|
|
|
||
|
|
- Create `apps/admin-web/vitest.config.ts`: admin-only unit-test configuration.
|
||
|
|
- Create `apps/admin-web/src/navigation/adminNavigation.ts`: admin-local two-level menu types and data.
|
||
|
|
- Create `apps/admin-web/src/navigation/adminNavigation.test.ts`: navigation structure and uniqueness contract.
|
||
|
|
- Create `apps/admin-web/src/components/AdminSidebarMenu.vue`: Element Plus menu renderer.
|
||
|
|
- Create `apps/admin-web/src/components/AdminSidebarMenu.test.ts`: grouped labels, paths and collapsed-state behavior.
|
||
|
|
- Modify `apps/admin-web/src/layouts/AppLayout.vue`: delegate menu rendering to `AdminSidebarMenu`.
|
||
|
|
- Modify `apps/admin-web/src/router/index.ts`: stop exporting flat menu metadata.
|
||
|
|
- Modify `apps/admin-web/src/styles/index.css`: style Element Plus menu within the existing navy sidebar.
|
||
|
|
- Modify `apps/admin-web/package.json` and `apps/admin-web/pnpm-lock.yaml`: add admin-only test dependencies and script.
|
||
|
|
- Modify `apps/admin-web/README.md`: document grouped menu and test command.
|
||
|
|
- Modify `apps/admin-web/AGENTS.md`: add the new admin-local test quality gate.
|
||
|
|
|
||
|
|
### Task 1: Install the admin-only test harness
|
||
|
|
|
||
|
|
**Files:**
|
||
|
|
- Modify: `apps/admin-web/package.json`
|
||
|
|
- Modify: `apps/admin-web/pnpm-lock.yaml`
|
||
|
|
- Create: `apps/admin-web/vitest.config.ts`
|
||
|
|
|
||
|
|
- [ ] **Step 1: Install test dependencies inside admin-web**
|
||
|
|
|
||
|
|
Run:
|
||
|
|
|
||
|
|
```powershell
|
||
|
|
cd C:\Users\BY\Desktop\code\chongchong-global\apps\admin-web
|
||
|
|
pnpm add -D vitest @vue/test-utils jsdom
|
||
|
|
```
|
||
|
|
|
||
|
|
Expected: only `apps/admin-web/package.json`, `apps/admin-web/pnpm-lock.yaml` and `apps/admin-web/node_modules` change.
|
||
|
|
|
||
|
|
- [ ] **Step 2: Add the test script**
|
||
|
|
|
||
|
|
Add to `scripts` in `apps/admin-web/package.json`:
|
||
|
|
|
||
|
|
```json
|
||
|
|
"test": "vitest run"
|
||
|
|
```
|
||
|
|
|
||
|
|
- [ ] **Step 3: Add the Vitest configuration**
|
||
|
|
|
||
|
|
Create `apps/admin-web/vitest.config.ts`:
|
||
|
|
|
||
|
|
```ts
|
||
|
|
import { fileURLToPath, URL } from 'node:url'
|
||
|
|
import { defineConfig } from 'vitest/config'
|
||
|
|
import vue from '@vitejs/plugin-vue'
|
||
|
|
|
||
|
|
export default defineConfig({
|
||
|
|
plugins: [vue()],
|
||
|
|
resolve: {
|
||
|
|
alias: {
|
||
|
|
'@': fileURLToPath(new URL('./src', import.meta.url)),
|
||
|
|
},
|
||
|
|
},
|
||
|
|
test: {
|
||
|
|
environment: 'jsdom',
|
||
|
|
include: ['src/**/*.test.ts'],
|
||
|
|
clearMocks: true,
|
||
|
|
},
|
||
|
|
})
|
||
|
|
```
|
||
|
|
|
||
|
|
- [ ] **Step 4: Verify the empty harness**
|
||
|
|
|
||
|
|
Run:
|
||
|
|
|
||
|
|
```powershell
|
||
|
|
pnpm test -- --passWithNoTests
|
||
|
|
```
|
||
|
|
|
||
|
|
Expected: PASS with no test files and no dependency-resolution errors.
|
||
|
|
|
||
|
|
### Task 2: Define the grouped navigation with TDD
|
||
|
|
|
||
|
|
**Files:**
|
||
|
|
- Create: `apps/admin-web/src/navigation/adminNavigation.test.ts`
|
||
|
|
- Create: `apps/admin-web/src/navigation/adminNavigation.ts`
|
||
|
|
|
||
|
|
- [ ] **Step 1: Write the failing navigation contract**
|
||
|
|
|
||
|
|
Create `apps/admin-web/src/navigation/adminNavigation.test.ts`:
|
||
|
|
|
||
|
|
```ts
|
||
|
|
import { describe, expect, it } from 'vitest'
|
||
|
|
import { adminNavigation, getNavigationLeaves } from './adminNavigation'
|
||
|
|
|
||
|
|
describe('adminNavigation', () => {
|
||
|
|
it('keeps only the dashboard as a direct first-level page', () => {
|
||
|
|
expect(adminNavigation.map((item) => item.title)).toEqual([
|
||
|
|
'工作台',
|
||
|
|
'业务管理',
|
||
|
|
'系统管理',
|
||
|
|
])
|
||
|
|
expect(adminNavigation[0].path).toBe('/')
|
||
|
|
expect(adminNavigation[1].children?.map((item) => item.title)).toEqual([
|
||
|
|
'客户管理',
|
||
|
|
'头程订单',
|
||
|
|
])
|
||
|
|
expect(adminNavigation[2].children?.map((item) => item.title)).toEqual([
|
||
|
|
'内部用户',
|
||
|
|
'角色管理',
|
||
|
|
'菜单权限',
|
||
|
|
])
|
||
|
|
})
|
||
|
|
|
||
|
|
it('assigns every page a unique path and permission code', () => {
|
||
|
|
const leaves = getNavigationLeaves(adminNavigation)
|
||
|
|
const paths = leaves.map((item) => item.path)
|
||
|
|
const permissions = leaves.map((item) => item.permission)
|
||
|
|
|
||
|
|
expect(paths).toEqual([
|
||
|
|
'/',
|
||
|
|
'/customers',
|
||
|
|
'/orders/first-leg',
|
||
|
|
'/system/users',
|
||
|
|
'/system/roles',
|
||
|
|
'/system/permissions',
|
||
|
|
])
|
||
|
|
expect(new Set(paths).size).toBe(paths.length)
|
||
|
|
expect(new Set(permissions).size).toBe(permissions.length)
|
||
|
|
expect(leaves.every((item) => Boolean(item.icon))).toBe(true)
|
||
|
|
})
|
||
|
|
})
|
||
|
|
```
|
||
|
|
|
||
|
|
- [ ] **Step 2: Run the test and verify RED**
|
||
|
|
|
||
|
|
Run:
|
||
|
|
|
||
|
|
```powershell
|
||
|
|
pnpm test -- src/navigation/adminNavigation.test.ts
|
||
|
|
```
|
||
|
|
|
||
|
|
Expected: FAIL because `src/navigation/adminNavigation.ts` does not exist.
|
||
|
|
|
||
|
|
- [ ] **Step 3: Implement the minimal local navigation model**
|
||
|
|
|
||
|
|
Create `apps/admin-web/src/navigation/adminNavigation.ts`:
|
||
|
|
|
||
|
|
```ts
|
||
|
|
import type { Component } from 'vue'
|
||
|
|
import {
|
||
|
|
BriefcaseBusiness,
|
||
|
|
Building2,
|
||
|
|
ClipboardList,
|
||
|
|
KeyRound,
|
||
|
|
LayoutDashboard,
|
||
|
|
Settings,
|
||
|
|
ShieldCheck,
|
||
|
|
Users,
|
||
|
|
} from '@lucide/vue'
|
||
|
|
|
||
|
|
export interface AdminNavigationNode {
|
||
|
|
key: string
|
||
|
|
title: string
|
||
|
|
icon: Component
|
||
|
|
path?: string
|
||
|
|
permission?: string
|
||
|
|
children?: AdminNavigationNode[]
|
||
|
|
}
|
||
|
|
|
||
|
|
export interface AdminNavigationLeaf extends AdminNavigationNode {
|
||
|
|
path: string
|
||
|
|
permission: string
|
||
|
|
children?: never
|
||
|
|
}
|
||
|
|
|
||
|
|
export const adminNavigation: AdminNavigationNode[] = [
|
||
|
|
{
|
||
|
|
key: 'dashboard',
|
||
|
|
title: '工作台',
|
||
|
|
icon: LayoutDashboard,
|
||
|
|
path: '/',
|
||
|
|
permission: 'dashboard:view',
|
||
|
|
},
|
||
|
|
{
|
||
|
|
key: 'business',
|
||
|
|
title: '业务管理',
|
||
|
|
icon: BriefcaseBusiness,
|
||
|
|
children: [
|
||
|
|
{ key: 'customers', title: '客户管理', icon: Building2, path: '/customers', permission: 'customer:list' },
|
||
|
|
{ key: 'first-leg-orders', title: '头程订单', icon: ClipboardList, path: '/orders/first-leg', permission: 'first-leg:list' },
|
||
|
|
],
|
||
|
|
},
|
||
|
|
{
|
||
|
|
key: 'system',
|
||
|
|
title: '系统管理',
|
||
|
|
icon: Settings,
|
||
|
|
children: [
|
||
|
|
{ key: 'internal-users', title: '内部用户', icon: Users, path: '/system/users', permission: 'system:user:list' },
|
||
|
|
{ key: 'roles', title: '角色管理', icon: ShieldCheck, path: '/system/roles', permission: 'system:role:list' },
|
||
|
|
{ key: 'permissions', title: '菜单权限', icon: KeyRound, path: '/system/permissions', permission: 'system:permission:list' },
|
||
|
|
],
|
||
|
|
},
|
||
|
|
]
|
||
|
|
|
||
|
|
export function getNavigationLeaves(nodes: AdminNavigationNode[]): AdminNavigationLeaf[] {
|
||
|
|
return nodes.flatMap((node) => {
|
||
|
|
if (node.children?.length) return getNavigationLeaves(node.children)
|
||
|
|
if (!node.path || !node.permission) return []
|
||
|
|
return [node as AdminNavigationLeaf]
|
||
|
|
})
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
- [ ] **Step 4: Run the test and verify GREEN**
|
||
|
|
|
||
|
|
Run:
|
||
|
|
|
||
|
|
```powershell
|
||
|
|
pnpm test -- src/navigation/adminNavigation.test.ts
|
||
|
|
```
|
||
|
|
|
||
|
|
Expected: 2 tests PASS.
|
||
|
|
|
||
|
|
### Task 3: Render the two-level menu with TDD
|
||
|
|
|
||
|
|
**Files:**
|
||
|
|
- Create: `apps/admin-web/src/components/AdminSidebarMenu.test.ts`
|
||
|
|
- Create: `apps/admin-web/src/components/AdminSidebarMenu.vue`
|
||
|
|
|
||
|
|
- [ ] **Step 1: Write the failing sidebar component test**
|
||
|
|
|
||
|
|
Create `apps/admin-web/src/components/AdminSidebarMenu.test.ts`:
|
||
|
|
|
||
|
|
```ts
|
||
|
|
import { mount } from '@vue/test-utils'
|
||
|
|
import { describe, expect, it } from 'vitest'
|
||
|
|
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 /></a>',
|
||
|
|
},
|
||
|
|
}
|
||
|
|
|
||
|
|
describe('AdminSidebarMenu', () => {
|
||
|
|
it('renders grouped page entries and forwards the active route', () => {
|
||
|
|
const wrapper = mount(AdminSidebarMenu, {
|
||
|
|
props: { activePath: '/system/users', collapsed: false },
|
||
|
|
global: { stubs },
|
||
|
|
})
|
||
|
|
|
||
|
|
expect(wrapper.get('nav').attributes('data-active')).toBe('/system/users')
|
||
|
|
expect(wrapper.findAll('[data-kind="group"] header').map((node) => node.text())).toEqual([
|
||
|
|
'业务管理',
|
||
|
|
'系统管理',
|
||
|
|
])
|
||
|
|
expect(wrapper.findAll('[data-kind="item"]').map((node) => node.attributes('data-index'))).toEqual([
|
||
|
|
'/',
|
||
|
|
'/customers',
|
||
|
|
'/orders/first-leg',
|
||
|
|
'/system/users',
|
||
|
|
'/system/roles',
|
||
|
|
'/system/permissions',
|
||
|
|
])
|
||
|
|
})
|
||
|
|
|
||
|
|
it('forwards desktop collapse state to Element Plus', () => {
|
||
|
|
const wrapper = mount(AdminSidebarMenu, {
|
||
|
|
props: { activePath: '/', collapsed: true },
|
||
|
|
global: { stubs },
|
||
|
|
})
|
||
|
|
|
||
|
|
expect(wrapper.get('nav').attributes('data-collapsed')).toBe('true')
|
||
|
|
})
|
||
|
|
})
|
||
|
|
```
|
||
|
|
|
||
|
|
- [ ] **Step 2: Run the component test and verify RED**
|
||
|
|
|
||
|
|
Run:
|
||
|
|
|
||
|
|
```powershell
|
||
|
|
pnpm test -- src/components/AdminSidebarMenu.test.ts
|
||
|
|
```
|
||
|
|
|
||
|
|
Expected: FAIL because `AdminSidebarMenu.vue` does not exist.
|
||
|
|
|
||
|
|
- [ ] **Step 3: Implement the minimal menu component**
|
||
|
|
|
||
|
|
Create `apps/admin-web/src/components/AdminSidebarMenu.vue`:
|
||
|
|
|
||
|
|
```vue
|
||
|
|
<script setup lang="ts">
|
||
|
|
import { adminNavigation } from '@/navigation/adminNavigation'
|
||
|
|
|
||
|
|
defineProps<{
|
||
|
|
activePath: string
|
||
|
|
collapsed: boolean
|
||
|
|
}>()
|
||
|
|
</script>
|
||
|
|
|
||
|
|
<template>
|
||
|
|
<el-menu
|
||
|
|
class="side-nav-menu"
|
||
|
|
:default-active="activePath"
|
||
|
|
:collapse="collapsed"
|
||
|
|
:default-openeds="['business', 'system']"
|
||
|
|
router
|
||
|
|
>
|
||
|
|
<template v-for="item in adminNavigation" :key="item.key">
|
||
|
|
<el-sub-menu v-if="item.children?.length" :index="item.key">
|
||
|
|
<template #title>
|
||
|
|
<component :is="item.icon" :size="19" :stroke-width="1.8" />
|
||
|
|
<span>{{ item.title }}</span>
|
||
|
|
</template>
|
||
|
|
|
||
|
|
<el-menu-item v-for="child in item.children" :key="child.key" :index="child.path || child.key">
|
||
|
|
<component :is="child.icon" :size="17" :stroke-width="1.8" />
|
||
|
|
<template #title>{{ child.title }}</template>
|
||
|
|
</el-menu-item>
|
||
|
|
</el-sub-menu>
|
||
|
|
|
||
|
|
<el-menu-item v-else :index="item.path || item.key">
|
||
|
|
<component :is="item.icon" :size="19" :stroke-width="1.8" />
|
||
|
|
<template #title>{{ item.title }}</template>
|
||
|
|
</el-menu-item>
|
||
|
|
</template>
|
||
|
|
</el-menu>
|
||
|
|
</template>
|
||
|
|
```
|
||
|
|
|
||
|
|
- [ ] **Step 4: Run both focused tests and verify GREEN**
|
||
|
|
|
||
|
|
Run:
|
||
|
|
|
||
|
|
```powershell
|
||
|
|
pnpm test -- src/navigation/adminNavigation.test.ts src/components/AdminSidebarMenu.test.ts
|
||
|
|
```
|
||
|
|
|
||
|
|
Expected: 4 tests PASS.
|
||
|
|
|
||
|
|
### Task 4: Integrate the component into AppLayout
|
||
|
|
|
||
|
|
**Files:**
|
||
|
|
- Modify: `apps/admin-web/src/layouts/AppLayout.vue`
|
||
|
|
- Modify: `apps/admin-web/src/router/index.ts`
|
||
|
|
|
||
|
|
- [ ] **Step 1: Simplify AppLayout imports and state**
|
||
|
|
|
||
|
|
Remove the six page icon imports, `adminNavigation`, and `iconMap`. Import the focused component:
|
||
|
|
|
||
|
|
```ts
|
||
|
|
import AdminSidebarMenu from '@/components/AdminSidebarMenu.vue'
|
||
|
|
```
|
||
|
|
|
||
|
|
Add the computed collapse state so the mobile drawer never renders the desktop collapsed menu:
|
||
|
|
|
||
|
|
```ts
|
||
|
|
const menuCollapsed = computed(() => collapsed.value && !mobileMenuOpen.value)
|
||
|
|
```
|
||
|
|
|
||
|
|
- [ ] **Step 2: Replace the flat RouterLink loop**
|
||
|
|
|
||
|
|
Replace the contents of `<nav class="side-nav" aria-label="主导航">` with:
|
||
|
|
|
||
|
|
```vue
|
||
|
|
<AdminSidebarMenu :active-path="route.path" :collapsed="menuCollapsed" />
|
||
|
|
```
|
||
|
|
|
||
|
|
Keep the existing route watcher so selecting a menu item closes the mobile drawer.
|
||
|
|
|
||
|
|
- [ ] **Step 3: Remove flat navigation metadata from the router**
|
||
|
|
|
||
|
|
Delete the `NavigationItem` import and `adminNavigation` array from `apps/admin-web/src/router/index.ts`. Do not change any route path, name, component or permission metadata.
|
||
|
|
|
||
|
|
- [ ] **Step 4: Verify all unit tests remain green**
|
||
|
|
|
||
|
|
Run:
|
||
|
|
|
||
|
|
```powershell
|
||
|
|
pnpm test
|
||
|
|
```
|
||
|
|
|
||
|
|
Expected: 4 tests PASS with no warnings.
|
||
|
|
|
||
|
|
### Task 5: Adapt the existing sidebar styling
|
||
|
|
|
||
|
|
**Files:**
|
||
|
|
- Modify: `apps/admin-web/src/styles/index.css`
|
||
|
|
|
||
|
|
- [ ] **Step 1: Replace the flat link styles**
|
||
|
|
|
||
|
|
Replace `.side-nav`, `.side-nav-item` and its hover/active rules with:
|
||
|
|
|
||
|
|
```css
|
||
|
|
.side-nav { flex: 1; min-height: 0; overflow-y: auto; padding: 14px 10px; }
|
||
|
|
.side-nav-menu {
|
||
|
|
width: 100%;
|
||
|
|
border-right: 0 !important;
|
||
|
|
background: transparent !important;
|
||
|
|
--el-menu-bg-color: transparent;
|
||
|
|
--el-menu-text-color: #bdc7d7;
|
||
|
|
--el-menu-active-color: var(--cc-navy);
|
||
|
|
--el-menu-hover-bg-color: rgba(255,255,255,.08);
|
||
|
|
}
|
||
|
|
.side-nav-menu.el-menu--collapse { width: 56px; }
|
||
|
|
.side-nav-menu .el-menu-item,
|
||
|
|
.side-nav-menu .el-sub-menu__title {
|
||
|
|
height: 44px;
|
||
|
|
margin: 3px 0;
|
||
|
|
border-radius: 8px;
|
||
|
|
line-height: 44px;
|
||
|
|
}
|
||
|
|
.side-nav-menu .el-menu-item:hover,
|
||
|
|
.side-nav-menu .el-sub-menu__title:hover { color: #fff; }
|
||
|
|
.side-nav-menu .el-menu-item.is-active { background: #fff; color: var(--cc-navy); font-weight: 650; }
|
||
|
|
.side-nav-menu .el-sub-menu .el-menu-item { min-width: 0; padding-left: 48px !important; }
|
||
|
|
.side-nav-menu .el-menu-item svg,
|
||
|
|
.side-nav-menu .el-sub-menu__title svg { flex: 0 0 auto; margin-right: 12px; }
|
||
|
|
```
|
||
|
|
|
||
|
|
- [ ] **Step 2: Update the mobile override**
|
||
|
|
|
||
|
|
Remove mobile rules that reference `.side-nav-item` and add:
|
||
|
|
|
||
|
|
```css
|
||
|
|
@media (max-width: 720px) {
|
||
|
|
.side-nav-menu,
|
||
|
|
.side-nav-menu.el-menu--collapse { width: 100%; }
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
Do not alter the existing sidebar transform, scrim, 44px touch targets or reduced-motion behavior.
|
||
|
|
|
||
|
|
- [ ] **Step 3: Run static verification**
|
||
|
|
|
||
|
|
Run:
|
||
|
|
|
||
|
|
```powershell
|
||
|
|
pnpm test
|
||
|
|
pnpm typecheck
|
||
|
|
pnpm build
|
||
|
|
```
|
||
|
|
|
||
|
|
Expected: tests, Vue TypeScript checks and production build all PASS.
|
||
|
|
|
||
|
|
### Task 6: Browser and Impeccable verification
|
||
|
|
|
||
|
|
**Files:**
|
||
|
|
- Modify only if a verified defect is found: `apps/admin-web/src/components/AdminSidebarMenu.vue`, `apps/admin-web/src/layouts/AppLayout.vue`, `apps/admin-web/src/styles/index.css`
|
||
|
|
|
||
|
|
- [ ] **Step 1: Start the admin development server**
|
||
|
|
|
||
|
|
Run:
|
||
|
|
|
||
|
|
```powershell
|
||
|
|
pnpm dev
|
||
|
|
```
|
||
|
|
|
||
|
|
Expected: admin is available at `http://localhost:5173`.
|
||
|
|
|
||
|
|
- [ ] **Step 2: Verify desktop expanded and collapsed states in one browser pass**
|
||
|
|
|
||
|
|
Check:
|
||
|
|
|
||
|
|
- Workbench remains a direct entry.
|
||
|
|
- Business management contains customers and first-leg orders.
|
||
|
|
- System management contains users, roles and permissions.
|
||
|
|
- Active route remains highlighted after direct URL navigation.
|
||
|
|
- Collapsing the sidebar exposes grouped entries through Element Plus popovers.
|
||
|
|
- Existing customer, order, user, role and permission interactions still work.
|
||
|
|
|
||
|
|
- [ ] **Step 3: Verify the 390px mobile state in the same pass**
|
||
|
|
|
||
|
|
Check:
|
||
|
|
|
||
|
|
- The menu button opens the drawer.
|
||
|
|
- Both groups can expand and every leaf is reachable.
|
||
|
|
- Selecting a leaf closes the drawer.
|
||
|
|
- Clicking the right-side scrim closes the drawer.
|
||
|
|
- The page itself has no horizontal viewport overflow.
|
||
|
|
|
||
|
|
- [ ] **Step 4: Run the Impeccable detector once**
|
||
|
|
|
||
|
|
Run:
|
||
|
|
|
||
|
|
```powershell
|
||
|
|
node C:\Users\BY\.codex\skills\impeccable\scripts\detect.mjs --json src/components/AdminSidebarMenu.vue src/layouts/AppLayout.vue src/styles/index.css
|
||
|
|
```
|
||
|
|
|
||
|
|
Expected: no unverified detector findings remain. Fix verified findings in one batch, then perform at most one confirmation browser pass.
|
||
|
|
|
||
|
|
### Task 7: Update project documentation and complete verification
|
||
|
|
|
||
|
|
**Files:**
|
||
|
|
- Modify: `apps/admin-web/README.md`
|
||
|
|
- Modify: `apps/admin-web/AGENTS.md`
|
||
|
|
|
||
|
|
- [ ] **Step 1: Document tests and grouped navigation**
|
||
|
|
|
||
|
|
Add:
|
||
|
|
|
||
|
|
````markdown
|
||
|
|
测试:
|
||
|
|
|
||
|
|
```bash
|
||
|
|
pnpm test
|
||
|
|
```
|
||
|
|
|
||
|
|
后台菜单按“业务管理”和“系统管理”分组;当前页面使用 Pinia 模拟数据,不请求尚未确定的 Java 业务接口。
|
||
|
|
````
|
||
|
|
|
||
|
|
Update the quality-gate section of `apps/admin-web/AGENTS.md` to include `pnpm test`, and replace the obsolete statement that the project has no automated test script with a requirement to run unit tests for navigation or other tested behavior changes.
|
||
|
|
|
||
|
|
- [ ] **Step 2: Run final verification from admin-web**
|
||
|
|
|
||
|
|
Run:
|
||
|
|
|
||
|
|
```powershell
|
||
|
|
pnpm test
|
||
|
|
pnpm typecheck
|
||
|
|
pnpm build
|
||
|
|
```
|
||
|
|
|
||
|
|
Expected: all commands exit with code 0 and `apps/admin-web/dist` is refreshed.
|
||
|
|
|
||
|
|
- [ ] **Step 3: Confirm repository isolation**
|
||
|
|
|
||
|
|
Run from the repository root:
|
||
|
|
|
||
|
|
```powershell
|
||
|
|
Get-ChildItem -Force -File | Where-Object { $_.Name -in @('package.json','pnpm-workspace.yaml','pnpm-lock.yaml') }
|
||
|
|
```
|
||
|
|
|
||
|
|
Expected: no Node package or workspace files exist at the repository root.
|