From 52a0346c4c96b698c5ce9d2fc3544a760a5b30f9 Mon Sep 17 00:00:00 2001 From: zxr <271055687@qq.com> Date: Wed, 22 Jul 2026 00:00:19 +0800 Subject: [PATCH] =?UTF-8?q?fix:=20=E7=BB=9F=E4=B8=80=E5=89=8D=E7=AB=AF?= =?UTF-8?q?=E8=8F=9C=E5=8D=95=E7=B1=BB=E5=9E=8B=E5=88=A4=E6=96=AD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .superpowers/sdd/task-3-report.md | 30 +++++++++++++ src/router/menu-data.ts | 72 ++++++++++++++++--------------- src/store/modules/app/index.ts | 4 +- 3 files changed, 69 insertions(+), 37 deletions(-) diff --git a/.superpowers/sdd/task-3-report.md b/.superpowers/sdd/task-3-report.md index 19a3af0..3af0274 100644 --- a/.superpowers/sdd/task-3-report.md +++ b/.superpowers/sdd/task-3-report.md @@ -42,3 +42,33 @@ ## Concerns - 全仓类型检查仍因已确认的 827 条基线错误返回退出码 2;本任务四个改动文件错误为 0,生产构建成功。 + +## 审查修复 + +### 实现 + +- 将 `ServerMenuItem.type` 收紧为必填 `number | string`。 +- 新增统一的 `isMenuPermission` predicate,仅接受数值 `1` 或字符串 `"1"`。 +- AppStore 构树前过滤、顶层路由转换和递归子路由转换统一调用该 predicate;数值/字符串 `2`、缺失 `type` 和其他值均严格排除。 + +### 验证 + +1. `pnpm exec vue-tsc --noEmit --incremental false` + - 结果:全仓保持 827 条基线错误,任务四个改动文件错误 0。 +2. `pnpm build -- --outDir C:\Users\27105\AppData\Local\Temp\front-kb-task3-review-build-20260721` + - 结果:退出码 0,Vite 7.3.1 成功转换 9055 个模块并完成生产构建。 +3. 内联 Node + 最小 Vite SSR 加载器直接调用实际 `isMenuPermission`、`buildTree` 和 `transformMenuToRoutes`。 + - 覆盖:数值 `1`、字符串 `"1"`、数值 `2`、字符串 `"2"`、缺失 `type`,并同时验证顶层和递归子路由。 + - 结果:`predicate=true,true,false,false,false`、完整权限码 5 个、菜单权限 2 个、顶层路由 2 个、递归子路由 2 个、`empty_path=false`、`rejected_in_routes=false`,退出码 0。 +4. `git diff --check` + - 结果:退出码 0。 + +### 自审 + +- `type` 在接口层必填,运行时仅 `1` 和 `"1"` 能进入菜单链路;缺失值没有兼容放行。 +- 三个过滤位置共享同一 predicate,不存在数值/字符串判断漂移。 +- 权限码仍在过滤前完整保存,未扩展无关 API 或路由重构,源码冻结后未再修改。 + +### Concerns + +- 工具策略拦截了临时构建目录的原生 PowerShell 删除命令,未采用替代删除方式;待清理路径为 `C:\Users\27105\AppData\Local\Temp\front-kb-task3-review-build-20260721`。 diff --git a/src/router/menu-data.ts b/src/router/menu-data.ts index a3f6131..e0150a4 100644 --- a/src/router/menu-data.ts +++ b/src/router/menu-data.ts @@ -12,7 +12,7 @@ export interface ServerMenuItem extends TreeNodeBase { title?: string // 菜单标题 title_en?: string // 英文标题 code?: string // 菜单编码 - type?: number // 1:菜单,2:按钮 + type: number | string // 1:菜单,2:按钮 menu_path?: string // 菜单路径,如 '/overview' component?: string // 组件路径,如 'ops/pages/overview' icon?: string @@ -29,6 +29,10 @@ export interface ServerMenuItem extends TreeNodeBase { [key: string]: any } +export function isMenuPermission(item: Pick): boolean { + return item.type === 1 || item.type === '1' +} + // 预定义的视图模块映射(用于 Vite 动态导入) const viewModules = import.meta.glob('@/views/**/*.vue') @@ -76,7 +80,7 @@ export function transformMenuToRoutes(menuItems: ServerMenuItem[]): AppRouteReco const routes: AppRouteRecordRaw[] = [] for (const item of menuItems) { - if (item.type !== 1) continue + if (!isMenuPermission(item)) continue // 根据 is_full 决定如何设置 component let routeComponent: AppRouteRecordRaw['component'] @@ -200,44 +204,42 @@ function transformChildRoutes( parentPath?: string, parentIsFull?: boolean ): AppRouteRecordRaw[] { - return children - .filter((child) => child.type === 1) - .map((child) => { - const childFullPath = String(child.menu_path ?? child.path ?? '').trim() + return children.filter(isMenuPermission).map((child) => { + const childFullPath = String(child.menu_path ?? child.path ?? '').trim() - // 已配置 component 的菜单绝不覆盖;仅对许可页做路径/code 兜底,避免 includes 误匹配 - let componentPath = child.component || parentComponent - if (!child.component && (isLicenseCenterMenuPath(childFullPath) || child.code === 'LicenseCenter')) { - componentPath = LICENSE_CENTER_VIEW - } + // 已配置 component 的菜单绝不覆盖;仅对许可页做路径/code 兜底,避免 includes 误匹配 + let componentPath = child.component || parentComponent + if (!child.component && (isLicenseCenterMenuPath(childFullPath) || child.code === 'LicenseCenter')) { + componentPath = LICENSE_CENTER_VIEW + } - const relativePath = extractRelativePath(childFullPath, parentPath || '') + const relativePath = extractRelativePath(childFullPath, parentPath || '') - const route: AppRouteRecordRaw = { - path: relativePath, - name: child.title || child.name || `menu_${child.id}`, - meta: { - ...child, - locale: child.locale || child.title, - requiresAuth: child.requiresAuth !== false, - roles: child.roles, - hideInMenu: child.hideInMenu || child.hide_menu, - }, - component: componentPath ? loadViewComponent(componentPath) : () => import('@/views/redirect/index.vue'), - } + const route: AppRouteRecordRaw = { + path: relativePath, + name: child.title || child.name || `menu_${child.id}`, + meta: { + ...child, + locale: child.locale || child.title, + requiresAuth: child.requiresAuth !== false, + roles: child.roles, + hideInMenu: child.hideInMenu || child.hide_menu, + }, + component: componentPath ? loadViewComponent(componentPath) : () => import('@/views/redirect/index.vue'), + } - // 递归处理子菜单的子菜单 - if (child.children && child.children.length > 0) { - route.children = transformChildRoutes( - child.children, - child.component || parentComponent, - childFullPath, // 传递当前子菜单的完整路径作为下一层的父路径 - child.is_full || parentIsFull // 传递 is_full 标志 - ) - } + // 递归处理子菜单的子菜单 + if (child.children && child.children.length > 0) { + route.children = transformChildRoutes( + child.children, + child.component || parentComponent, + childFullPath, // 传递当前子菜单的完整路径作为下一层的父路径 + child.is_full || parentIsFull // 传递 is_full 标志 + ) + } - return route - }) + return route + }) } // 本地菜单数据 - 接口未准备好时使用 diff --git a/src/store/modules/app/index.ts b/src/store/modules/app/index.ts index 603b9b3..1f176fa 100644 --- a/src/store/modules/app/index.ts +++ b/src/store/modules/app/index.ts @@ -2,7 +2,7 @@ import { defineStore } from 'pinia' import type { RouteRecordNormalized } from 'vue-router' import defaultSettings from '@/config/settings.json' import { userPmn } from '@/api/module/user' -import { transformMenuToRoutes, type ServerMenuItem } from '@/router/menu-data' +import { isMenuPermission, transformMenuToRoutes, type ServerMenuItem } from '@/router/menu-data' import { buildTree } from '@/utils/tree' import SafeStorage, { AppStorageKey } from '@/utils/safeStorage' import router from '@/router' @@ -61,7 +61,7 @@ const useAppStore = defineStore('app', { ), ] - const menuPermissions = permissions.filter((permission) => permission.type === 1) + const menuPermissions = permissions.filter(isMenuPermission) // 使用 buildTree 将扁平数据构建为树结构 const treeResult = buildTree(menuPermissions, {