From e09c6e81dffa0f24996486149cc4fad4492c26ac Mon Sep 17 00:00:00 2001 From: caoxiaozhu Date: Tue, 14 Jul 2026 16:10:50 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E7=94=A8=E6=88=B7=E4=B8=8E=E6=9D=83?= =?UTF-8?q?=E9=99=90=E7=AE=A1=E7=90=86?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 新增 PermissionCode 权限类型与用户/权限 API,路由注册用户设置/创建/权限页与无权访问页并接入权限守卫,AppSidebar 按权限过滤菜单并新增用户设置入口,auth store 与 mock 适配器同步支持用户管理与新登录认证,回归脚本与 npm 脚本注册。 --- frontend/package.json | 2 + frontend/scripts/regression-page-surface.mjs | 1 + frontend/src/api/modules/system.ts | 30 +++++++- frontend/src/components/AppSidebar.vue | 55 +++++++++---- frontend/src/mock/adapter.ts | 48 +++++++++++- frontend/src/router/index.ts | 56 ++++++++++++++ frontend/src/stores/auth.ts | 81 ++++++++++++++++++-- frontend/src/types/index.ts | 51 ++++++++++++ 8 files changed, 296 insertions(+), 28 deletions(-) diff --git a/frontend/package.json b/frontend/package.json index fb2b390..dc2037e 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -9,6 +9,7 @@ "preview": "vite preview", "type-check": "vue-tsc -b --noEmit", "test": "node scripts/run-regressions.mjs", + "test:login-layout": "node scripts/regression-login-layout.mjs", "test:default-dashboard": "node scripts/regression-default-dashboard.mjs", "test:dashboard": "node scripts/regression-dashboard.mjs", "test:data-process-list": "node scripts/regression-data-process-list.mjs", @@ -21,6 +22,7 @@ "test:eval-detail": "node scripts/regression-eval-detail.mjs", "test:model-manage": "node scripts/regression-model-manage.mjs", "test:hardware": "node scripts/regression-hardware-dashboard.mjs", + "test:user-settings": "node scripts/regression-user-settings.mjs", "test:training-log-layout": "node scripts/regression-training-log-layout.mjs", "test:fine-tune-create": "node scripts/regression-fine-tune-create-ui.mjs", "test:page-surface": "node scripts/regression-page-surface.mjs" diff --git a/frontend/scripts/regression-page-surface.mjs b/frontend/scripts/regression-page-surface.mjs index 81822e5..e297fd8 100644 --- a/frontend/scripts/regression-page-surface.mjs +++ b/frontend/scripts/regression-page-surface.mjs @@ -76,6 +76,7 @@ const selfSurfaceRoutes = [ 'data-process', 'data-process/create', 'dataset', + 'user-settings', ] for (const routePath of selfSurfaceRoutes) { const routeBlock = extractRouteBlock(routerSource, routePath) diff --git a/frontend/src/api/modules/system.ts b/frontend/src/api/modules/system.ts index 8d8e6f8..4df2a4d 100644 --- a/frontend/src/api/modules/system.ts +++ b/frontend/src/api/modules/system.ts @@ -1,5 +1,12 @@ -import { get, post } from '../request' -import type { SystemInfo, HealthMetrics } from '@/types' +import { del, get, post, put } from '../request' +import type { + CreateUserPayload, + HealthMetrics, + LoginResponse, + SystemInfo, + SystemUser, + UpdateUserAccessPayload, +} from '@/types' /** 系统信息(CPU/内存/磁盘/GPU/网络/系统) */ export const getSystemInfo = () => get('/system-info') @@ -9,4 +16,21 @@ export const getHealth = () => get('/health') /** 登录 */ export const login = (username: string, password: string) => - post('/login', { username, password }) + post('/login', { username, password }) + +/** 用户列表 */ +export const getUsers = () => get('/users') + +/** 创建用户 */ +export const createUser = (payload: CreateUserPayload) => + post('/users', payload) + +/** 删除用户,currentUsername 用于防止删除当前登录账号 */ +export const deleteUser = (id: string, currentUsername: string) => + del<{ deleted: string }>(`/users/${encodeURIComponent(id)}`, { + current_username: currentUsername, + }) + +/** 更新用户角色、状态及页面权限 */ +export const updateUserAccess = (id: string, payload: UpdateUserAccessPayload) => + put(`/users/${encodeURIComponent(id)}`, payload) diff --git a/frontend/src/components/AppSidebar.vue b/frontend/src/components/AppSidebar.vue index af5fa6b..97372f5 100644 --- a/frontend/src/components/AppSidebar.vue +++ b/frontend/src/components/AppSidebar.vue @@ -2,6 +2,7 @@ import { computed, nextTick, ref, watch } from 'vue' import { useRoute, useRouter } from 'vue-router' import { useAuthStore } from '@/stores/auth' +import type { PermissionCode } from '@/types' const route = useRoute() const router = useRouter() @@ -27,41 +28,65 @@ const activeMenu = computed(() => { return seg }) -const menuGroups = [ +interface MenuItem { + key: string + label: string + icon: string + to: string + permission: PermissionCode +} + +interface MenuGroup { + title?: string + showTitle?: boolean + items: MenuItem[] +} + +const menuGroups: MenuGroup[] = [ { // 服务看板:独立入口,不显示分类小标题 showTitle: false, - items: [{ key: 'dashboard', label: '服务看板', icon: 'fa-tachometer', to: '/dashboard' }], + items: [{ key: 'dashboard', label: '服务看板', icon: 'fa-tachometer', to: '/dashboard', permission: 'dashboard' }], }, { title: '模型服务', items: [ - { key: 'fine-tune', label: '模型训练', icon: 'fa-cogs', to: '/fine-tune' }, - { key: 'model-eval', label: '模型评测', icon: 'fa-line-chart', to: '/model-eval' }, - { key: 'model-inference', label: '模型推理', icon: 'fa-server', to: '/model-inference' }, - { key: 'model-manage', label: '模型管理', icon: 'fa-cube', to: '/model-manage' }, + { key: 'fine-tune', label: '模型训练', icon: 'fa-cogs', to: '/fine-tune', permission: 'fine-tune' }, + { key: 'model-eval', label: '模型评测', icon: 'fa-line-chart', to: '/model-eval', permission: 'model-eval' }, + { key: 'model-inference', label: '模型推理', icon: 'fa-server', to: '/model-inference', permission: 'model-inference' }, + { key: 'model-manage', label: '模型管理', icon: 'fa-cube', to: '/model-manage', permission: 'model-manage' }, ], }, { title: '数据治理', items: [ - { key: 'dataset', label: '数据集管理', icon: 'fa-file-text', to: '/dataset' }, - { key: 'data-process', label: '数据处理', icon: 'fa-filter', to: '/data-process' }, + { key: 'dataset', label: '数据集管理', icon: 'fa-file-text', to: '/dataset', permission: 'dataset' }, + { key: 'data-process', label: '数据处理', icon: 'fa-filter', to: '/data-process', permission: 'data-process' }, ], }, { title: '其他工具', - items: [{ key: 'data-convert', label: '数据类型转换', icon: 'fa-exchange', to: '/data-convert' }], + items: [{ key: 'data-convert', label: '数据类型转换', icon: 'fa-exchange', to: '/data-convert', permission: 'data-convert' }], }, { title: '系统设置', items: [ - { key: 'hardware', label: '平台性能', icon: 'fa-bar-chart', to: '/hardware' }, - { key: 'logs', label: '查看日志', icon: 'fa-file-text', to: '/logs' }, + { key: 'user-settings', label: '用户设置', icon: 'fa-users', to: '/user-settings', permission: 'user-settings' }, + { key: 'hardware', label: '平台性能', icon: 'fa-bar-chart', to: '/hardware', permission: 'hardware' }, + { key: 'logs', label: '查看日志', icon: 'fa-file-text', to: '/logs', permission: 'logs' }, ], }, ] +const visibleMenuGroups = computed(() => + menuGroups + .map((group) => ({ + ...group, + items: group.items.filter((item) => auth.hasPermission(item.permission)), + })) + .filter((group) => group.items.length > 0), +) + /** * 传给 el-menu 的激活值。 * @@ -78,7 +103,7 @@ watch(activeMenu, (value) => { }) async function handleSelect(key: string) { - const all = menuGroups.flatMap((g) => g.items) + const all = visibleMenuGroups.value.flatMap((g) => g.items) const target = all.find((i) => i.key === key) if (!target) return @@ -115,7 +140,7 @@ function handleLogout() { active-text-color="var(--sidebar-active-text)" @select="handleSelect" > -