feat(platform): load authorized menus from backend

This commit is contained in:
david
2026-07-30 10:30:01 +08:00
parent 1d5ac6470c
commit 268c8d062c
8 changed files with 597 additions and 491 deletions

View File

@@ -11,6 +11,6 @@
"menuWidth": 220,
"device": "desktop",
"tabBar": false,
"menuFromServer": false,
"menuFromServer": true,
"serverMenu": []
}

View File

@@ -1,10 +1,63 @@
import { Notification } from '@arco-design/web-vue';
import type { NotificationReturn } from '@arco-design/web-vue/es/notification/interface';
import { defineStore } from 'pinia';
import type { RouteRecordNormalized } from 'vue-router';
import type { RouteRecordNormalized, RouteRecordRaw } from 'vue-router';
import { platformApi, type PlatformMenu } from '@/api/platform';
import defaultSettings from '@/config/settings.json';
import appClientMenus from '@/router/app-menus';
import type { AppState } from './types';
function indexClientMenus(
routes: RouteRecordRaw[],
index = new Map<string, RouteRecordRaw>(),
) {
for (const route of routes) {
const menuCode = route.meta?.menuCode;
if (menuCode && route.meta?.hideInMenu !== true && !index.has(menuCode)) {
index.set(menuCode, route);
}
if (route.children) indexClientMenus(route.children, index);
}
return index;
}
function buildServerMenuTree(menus: PlatformMenu[]): RouteRecordRaw[] {
const clientMenus = indexClientMenus(
appClientMenus as unknown as RouteRecordRaw[],
);
const nodes = new Map<string, RouteRecordRaw>();
for (const menu of [...menus].sort((a, b) => a.sort_no - b.sort_no)) {
const clientRoute = clientMenus.get(menu.identity);
if (!clientRoute) continue;
nodes.set(menu.identity, {
...clientRoute,
path: menu.path,
meta: {
...clientRoute.meta,
title: menu.name,
icon: menu.icon || clientRoute.meta?.icon,
order: menu.sort_no,
menuCode: menu.identity,
requiresAuth: clientRoute.meta?.requiresAuth ?? true,
},
children: [],
});
}
const roots: RouteRecordRaw[] = [];
for (const menu of menus) {
const node = nodes.get(menu.identity);
if (!node) continue;
if (menu.parent_identity) {
const parent = nodes.get(menu.parent_identity);
if (parent) parent.children?.push(node);
} else {
roots.push(node);
}
}
return roots;
}
const useAppStore = defineStore('app', {
state: (): AppState => ({ ...defaultSettings }),
@@ -44,21 +97,23 @@ const useAppStore = defineStore('app', {
this.hideMenu = value;
},
async fetchServerMenuConfig() {
let notifyInstance: NotificationReturn | null = null;
try {
notifyInstance = Notification.info({
Notification.info({
id: 'menuNotice', // Keep the instance id the same
content: 'loading',
closable: true,
});
this.serverMenu = [];
notifyInstance = Notification.success({
const response = await platformApi.listMenu();
this.serverMenu = buildServerMenuTree(
response.list,
) as unknown as RouteRecordNormalized[];
Notification.success({
id: 'menuNotice',
content: 'success',
closable: true,
});
} catch {
notifyInstance = Notification.error({
Notification.error({
id: 'menuNotice',
content: 'error',
closable: true,

View File

@@ -56,6 +56,7 @@ const useUserStore = defineStore('user', {
// Get user's information
async info() {
const profile = await authApi.profile();
const appStore = useAppStore();
this.setInfo({
name: profile.display_name || profile.username,
avatar: profile.avatar,
@@ -63,6 +64,9 @@ const useUserStore = defineStore('user', {
role: profile.role_code,
menuCodes: profile.menu_codes,
});
if (appStore.menuFromServer) {
await appStore.fetchServerMenuConfig();
}
},
// Login