feat: 兼容前端按钮权限数据

This commit is contained in:
zxr
2026-07-21 23:51:18 +08:00
parent b4dba77229
commit 4b9a87638c
5 changed files with 109 additions and 41 deletions

View File

@@ -0,0 +1,14 @@
import { computed } from 'vue'
import { useAppStore } from '@/store'
export default function usePermissionCodes() {
const appStore = useAppStore()
const permissionCodes = computed(() => appStore.permissionCodes as string[])
const hasPermission = (code: string) => permissionCodes.value.includes(code)
return {
permissionCodes,
hasPermission,
}
}

View File

@@ -12,6 +12,7 @@ export interface ServerMenuItem extends TreeNodeBase {
title?: string // 菜单标题
title_en?: string // 英文标题
code?: string // 菜单编码
type?: number // 1菜单2按钮
menu_path?: string // 菜单路径,如 '/overview'
component?: string // 组件路径,如 'ops/pages/overview'
icon?: string
@@ -31,7 +32,6 @@ export interface ServerMenuItem extends TreeNodeBase {
// 预定义的视图模块映射(用于 Vite 动态导入)
const viewModules = import.meta.glob('@/views/**/*.vue')
console.log('viewModules', viewModules)
/**
* 动态加载视图组件
* @param componentPath 组件路径,如 'ops/pages/overview' 或 'ops/pages/overview/index'
@@ -76,6 +76,8 @@ export function transformMenuToRoutes(menuItems: ServerMenuItem[]): AppRouteReco
const routes: AppRouteRecordRaw[] = []
for (const item of menuItems) {
if (item.type !== 1) continue
// 根据 is_full 决定如何设置 component
let routeComponent: AppRouteRecordRaw['component']
@@ -198,42 +200,44 @@ function transformChildRoutes(
parentPath?: string,
parentIsFull?: boolean
): AppRouteRecordRaw[] {
return children.map((child) => {
const childFullPath = String(child.menu_path ?? child.path ?? '').trim()
return children
.filter((child) => child.type === 1)
.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
})
}
// 本地菜单数据 - 接口未准备好时使用

View File

@@ -1,17 +1,15 @@
import { defineStore } from 'pinia'
import { Notification } from '@arco-design/web-vue'
import type { NotificationReturn } from '@arco-design/web-vue/es/notification/interface'
import type { RouteRecordNormalized } from 'vue-router'
import defaultSettings from '@/config/settings.json'
import { userPmn } from '@/api/module/user'
import { localMenuData, transformMenuToRoutes, type ServerMenuItem } from '@/router/menu-data'
import { transformMenuToRoutes, type ServerMenuItem } from '@/router/menu-data'
import { buildTree } from '@/utils/tree'
import SafeStorage, { AppStorageKey } from '@/utils/safeStorage'
import router from '@/router'
import { AppState } from './types'
const useAppStore = defineStore('app', {
state: (): AppState => ({ ...defaultSettings }),
state: (): AppState => ({ ...defaultSettings, permissionCodes: [] }),
getters: {
appCurrentSetting(state: AppState): AppState {
@@ -50,22 +48,28 @@ const useAppStore = defineStore('app', {
},
async fetchServerMenuConfig() {
const userInfo = SafeStorage.get(AppStorageKey.USER_INFO) as any
let notifyInstance: NotificationReturn | null = null
try {
// 使用本地菜单数据(接口未准备好)
// TODO: 接口准备好后,取消下面的注释,使用真实接口数据
const res = await userPmn({ id: userInfo.user_id, workspace: import.meta.env.VITE_APP_WORKSPACE })
console.log('res', res)
if (res.code === 0 && res?.details?.length) {
const permissions = (res.details[0].permissions ?? []) as ServerMenuItem[]
this.permissionCodes = [
...new Set(
permissions.map((permission) => permission.code).filter((code): code is string => typeof code === 'string' && code.length > 0)
),
]
const menuPermissions = permissions.filter((permission) => permission.type === 1)
// 使用 buildTree 将扁平数据构建为树结构
const treeResult = buildTree(res.details[0].permissions as ServerMenuItem[], {
const treeResult = buildTree(menuPermissions, {
orderKey: 'order',
})
console.log('buildTree', treeResult)
// 使用 transformMenuToRoutes 将树结构转换为路由配置
const routes = transformMenuToRoutes(treeResult.rootItems as ServerMenuItem[])
console.log('transformMenuToRoutes', routes)
// 动态注册路由
routes.forEach((route) => {
@@ -105,6 +109,7 @@ const useAppStore = defineStore('app', {
})
}
this.serverMenu = []
this.permissionCodes = []
},
},
})

View File

@@ -16,6 +16,7 @@ export interface AppState {
tabBar: boolean
menuFromServer: boolean
serverMenu: RouteRecordNormalized[]
permissionCodes: string[]
workspace?: string
[key: string]: unknown
}