2026-07-10 16:44:49 +08:00
|
|
|
|
import { createRouter, createWebHistory, type RouteRecordRaw } from 'vue-router'
|
2026-08-03 09:34:08 +08:00
|
|
|
|
import { ref } from 'vue'
|
2026-07-10 16:44:49 +08:00
|
|
|
|
import { useAuthStore } from '@/stores/auth'
|
2026-07-14 16:10:50 +08:00
|
|
|
|
import type { PermissionCode } from '@/types'
|
2026-07-10 16:44:49 +08:00
|
|
|
|
|
2026-08-03 09:34:08 +08:00
|
|
|
|
/** 路由切换时的全局加载态,供 App.vue 显示全屏转圈遮罩,消除懒加载时的空白卡顿感 */
|
|
|
|
|
|
export const routeLoading = ref(false)
|
|
|
|
|
|
|
2026-07-10 16:44:49 +08:00
|
|
|
|
const routes: RouteRecordRaw[] = [
|
|
|
|
|
|
{
|
|
|
|
|
|
path: '/login',
|
|
|
|
|
|
name: 'login',
|
|
|
|
|
|
component: () => import('@/views/login/LoginView.vue'),
|
|
|
|
|
|
meta: { title: '登录', public: true },
|
|
|
|
|
|
},
|
2026-07-16 09:36:39 +08:00
|
|
|
|
{
|
|
|
|
|
|
path: '/guide',
|
|
|
|
|
|
name: 'guide',
|
|
|
|
|
|
component: () => import('@/views/guide/GuideView.vue'),
|
|
|
|
|
|
meta: { title: '使用文档', skipPermission: true },
|
|
|
|
|
|
},
|
2026-07-10 16:44:49 +08:00
|
|
|
|
{
|
|
|
|
|
|
path: '/',
|
|
|
|
|
|
component: () => import('@/layouts/MainLayout.vue'),
|
2026-07-11 14:49:37 +08:00
|
|
|
|
redirect: '/dashboard',
|
2026-07-10 16:44:49 +08:00
|
|
|
|
children: [
|
|
|
|
|
|
// 服务看板
|
|
|
|
|
|
{
|
|
|
|
|
|
path: 'dashboard',
|
|
|
|
|
|
name: 'dashboard',
|
2026-07-11 14:49:37 +08:00
|
|
|
|
component: () => import('@/views/dashboard/DashboardView.vue'),
|
2026-07-10 16:44:49 +08:00
|
|
|
|
meta: { title: '服务看板' },
|
|
|
|
|
|
},
|
2026-08-03 09:34:08 +08:00
|
|
|
|
// 平台治理
|
|
|
|
|
|
{
|
|
|
|
|
|
path: 'tenants',
|
|
|
|
|
|
name: 'tenants',
|
|
|
|
|
|
component: () => import('@/views/tenants/TenantListView.vue'),
|
|
|
|
|
|
meta: { title: '租户管理', permission: 'user-settings' },
|
|
|
|
|
|
},
|
|
|
|
|
|
{
|
|
|
|
|
|
path: 'tenants/:id',
|
|
|
|
|
|
name: 'tenant-detail',
|
|
|
|
|
|
component: () => import('@/views/tenants/TenantDetailView.vue'),
|
|
|
|
|
|
meta: { title: '租户详情', permission: 'user-settings' },
|
|
|
|
|
|
},
|
|
|
|
|
|
{
|
|
|
|
|
|
path: 'projects',
|
|
|
|
|
|
name: 'projects',
|
|
|
|
|
|
component: () => import('@/views/projects/ProjectListView.vue'),
|
|
|
|
|
|
meta: { title: '项目空间', permission: 'user-settings' },
|
|
|
|
|
|
},
|
|
|
|
|
|
{
|
|
|
|
|
|
path: 'projects/:id',
|
|
|
|
|
|
name: 'project-detail',
|
|
|
|
|
|
component: () => import('@/views/projects/ProjectDetailView.vue'),
|
|
|
|
|
|
meta: { title: '项目详情', permission: 'user-settings' },
|
|
|
|
|
|
},
|
|
|
|
|
|
{
|
|
|
|
|
|
path: 'audit-logs',
|
|
|
|
|
|
name: 'audit-logs',
|
|
|
|
|
|
component: () => import('@/views/audit/AuditLogView.vue'),
|
|
|
|
|
|
meta: { title: '审计日志', permission: 'user-settings' },
|
|
|
|
|
|
},
|
2026-08-18 14:49:12 +08:00
|
|
|
|
{
|
|
|
|
|
|
path: 'operation-logs',
|
|
|
|
|
|
name: 'operation-logs',
|
|
|
|
|
|
component: () => import('@/views/audit/OperationLogView.vue'),
|
|
|
|
|
|
meta: { title: '操作日志', permission: 'user-settings' },
|
|
|
|
|
|
},
|
2026-08-03 09:34:08 +08:00
|
|
|
|
{
|
|
|
|
|
|
path: 'approval-templates',
|
|
|
|
|
|
name: 'approval-templates',
|
|
|
|
|
|
component: () => import('@/views/approvals/ApprovalTemplateView.vue'),
|
|
|
|
|
|
meta: { title: '审批模板', permission: 'user-settings' },
|
|
|
|
|
|
},
|
|
|
|
|
|
{
|
|
|
|
|
|
path: 'approval-instances',
|
|
|
|
|
|
name: 'approval-instances',
|
|
|
|
|
|
component: () => import('@/views/approvals/ApprovalInstanceView.vue'),
|
|
|
|
|
|
meta: { title: '审批中心', permission: 'user-settings' },
|
|
|
|
|
|
},
|
2026-08-17 09:15:36 +08:00
|
|
|
|
{
|
|
|
|
|
|
path: 'resource-acl',
|
|
|
|
|
|
name: 'resource-acl',
|
|
|
|
|
|
component: () => import('@/views/governance/ResourceAclView.vue'),
|
|
|
|
|
|
meta: { title: '资源授权', permission: 'user-settings' },
|
|
|
|
|
|
},
|
2026-07-10 16:44:49 +08:00
|
|
|
|
// 模型调优
|
|
|
|
|
|
{
|
|
|
|
|
|
path: 'fine-tune',
|
|
|
|
|
|
name: 'fine-tune',
|
|
|
|
|
|
component: () => import('@/views/fine-tune/FineTuneListView.vue'),
|
2026-07-13 16:43:37 +08:00
|
|
|
|
meta: { title: '模型训练', pageSurface: 'self' },
|
2026-07-10 16:44:49 +08:00
|
|
|
|
},
|
|
|
|
|
|
{
|
|
|
|
|
|
path: 'fine-tune/create',
|
|
|
|
|
|
name: 'fine-tune-create',
|
|
|
|
|
|
component: () => import('@/views/fine-tune/FineTuneCreateView.vue'),
|
|
|
|
|
|
meta: { title: '创建训练任务' },
|
|
|
|
|
|
},
|
|
|
|
|
|
// 模型评测
|
|
|
|
|
|
{
|
|
|
|
|
|
path: 'model-eval',
|
|
|
|
|
|
name: 'model-eval',
|
|
|
|
|
|
component: () => import('@/views/eval/EvalView.vue'),
|
|
|
|
|
|
meta: { title: '模型评测', pageSurface: 'self' },
|
|
|
|
|
|
},
|
|
|
|
|
|
{
|
|
|
|
|
|
path: 'model-eval/create',
|
|
|
|
|
|
name: 'model-eval-create',
|
|
|
|
|
|
component: () => import('@/views/eval/EvalCreateView.vue'),
|
|
|
|
|
|
meta: { title: '新建评测' },
|
|
|
|
|
|
},
|
2026-07-12 15:39:59 +08:00
|
|
|
|
{
|
|
|
|
|
|
path: 'model-eval/:id',
|
|
|
|
|
|
name: 'model-eval-detail',
|
|
|
|
|
|
component: () => import('@/views/eval/EvalDetailView.vue'),
|
|
|
|
|
|
meta: { title: '评测详情' },
|
|
|
|
|
|
},
|
2026-07-10 16:44:49 +08:00
|
|
|
|
{
|
|
|
|
|
|
path: 'model-eval/dimension/create',
|
|
|
|
|
|
name: 'dimension-create',
|
2026-07-12 15:39:59 +08:00
|
|
|
|
redirect: '/model-eval/create',
|
2026-07-10 16:44:49 +08:00
|
|
|
|
},
|
|
|
|
|
|
{
|
|
|
|
|
|
path: 'model-eval/dimension/:id/edit',
|
|
|
|
|
|
name: 'dimension-edit',
|
|
|
|
|
|
component: () => import('@/views/eval/DimensionCreateView.vue'),
|
|
|
|
|
|
meta: { title: '编辑评测维度' },
|
|
|
|
|
|
},
|
|
|
|
|
|
// 模型推理
|
|
|
|
|
|
{
|
|
|
|
|
|
path: 'model-inference',
|
|
|
|
|
|
name: 'model-inference',
|
|
|
|
|
|
component: () => import('@/views/inference/InferenceListView.vue'),
|
|
|
|
|
|
meta: { title: '模型推理', pageSurface: 'self' },
|
|
|
|
|
|
},
|
|
|
|
|
|
{
|
|
|
|
|
|
path: 'model-inference/create',
|
|
|
|
|
|
name: 'model-inference-create',
|
|
|
|
|
|
component: () => import('@/views/inference/InferenceCreateView.vue'),
|
|
|
|
|
|
meta: { title: '新建推理' },
|
|
|
|
|
|
},
|
|
|
|
|
|
{
|
|
|
|
|
|
path: 'model-inference/chat/:id',
|
|
|
|
|
|
name: 'inference-chat',
|
|
|
|
|
|
component: () => import('@/views/inference/InferenceChatView.vue'),
|
|
|
|
|
|
meta: { title: '模型对话', pageSurface: 'self' },
|
|
|
|
|
|
},
|
|
|
|
|
|
// 模型对比
|
|
|
|
|
|
{
|
|
|
|
|
|
path: 'model-compare/chat/:id',
|
|
|
|
|
|
name: 'compare-chat',
|
|
|
|
|
|
component: () => import('@/views/compare/CompareChatView.vue'),
|
|
|
|
|
|
meta: { title: '模型对比' },
|
|
|
|
|
|
},
|
|
|
|
|
|
{
|
|
|
|
|
|
path: 'model-compare/result',
|
|
|
|
|
|
name: 'compare-result',
|
|
|
|
|
|
component: () => import('@/views/compare/CompareResultView.vue'),
|
|
|
|
|
|
meta: { title: '对比结果' },
|
|
|
|
|
|
},
|
|
|
|
|
|
// 模型管理
|
|
|
|
|
|
{
|
|
|
|
|
|
path: 'model-manage',
|
|
|
|
|
|
name: 'model-manage',
|
|
|
|
|
|
component: () => import('@/views/model/ModelManageView.vue'),
|
|
|
|
|
|
meta: { title: '模型管理', pageSurface: 'self' },
|
|
|
|
|
|
},
|
|
|
|
|
|
{
|
|
|
|
|
|
path: 'model-manage/create',
|
|
|
|
|
|
name: 'model-manage-create',
|
|
|
|
|
|
component: () => import('@/views/model/ModelCreateView.vue'),
|
|
|
|
|
|
meta: { title: '添加模型' },
|
|
|
|
|
|
},
|
|
|
|
|
|
{
|
|
|
|
|
|
path: 'model-manage/:id/edit',
|
|
|
|
|
|
name: 'model-manage-edit',
|
|
|
|
|
|
component: () => import('@/views/model/ModelCreateView.vue'),
|
|
|
|
|
|
meta: { title: '编辑模型' },
|
|
|
|
|
|
},
|
|
|
|
|
|
{
|
|
|
|
|
|
path: 'model-manage/merge',
|
|
|
|
|
|
name: 'merge-weights',
|
|
|
|
|
|
component: () => import('@/views/model/MergeWeightsView.vue'),
|
|
|
|
|
|
meta: { title: '合并权重' },
|
|
|
|
|
|
},
|
|
|
|
|
|
// 数据处理
|
|
|
|
|
|
{
|
|
|
|
|
|
path: 'data-process',
|
|
|
|
|
|
name: 'data-process',
|
|
|
|
|
|
component: () => import('@/views/data-process/DataProcessListView.vue'),
|
|
|
|
|
|
meta: { title: '数据处理', pageSurface: 'self' },
|
|
|
|
|
|
},
|
|
|
|
|
|
{
|
|
|
|
|
|
path: 'data-process/create',
|
|
|
|
|
|
name: 'data-process-create',
|
|
|
|
|
|
component: () => import('@/views/data-process/DataProcessCreateView.vue'),
|
2026-07-28 10:56:05 +08:00
|
|
|
|
meta: { title: '新建数据处理任务', pageSurface: 'self', backRouteName: 'data-process' },
|
2026-07-10 16:44:49 +08:00
|
|
|
|
},
|
2026-07-25 22:41:06 +08:00
|
|
|
|
{
|
|
|
|
|
|
path: 'data-process/:id/regenerate',
|
|
|
|
|
|
name: 'data-process-regenerate',
|
|
|
|
|
|
component: () => import('@/views/data-process/DataProcessCreateView.vue'),
|
2026-07-28 10:56:05 +08:00
|
|
|
|
meta: { title: '重新生成数据处理任务', pageSurface: 'self', backRouteName: 'data-process' },
|
|
|
|
|
|
},
|
|
|
|
|
|
{
|
|
|
|
|
|
path: 'data-process/:id/workflow',
|
|
|
|
|
|
name: 'data-process-workflow',
|
|
|
|
|
|
component: () => import('@/views/data-process/DataProcessCreateView.vue'),
|
|
|
|
|
|
meta: { title: '数据处理任务进度', pageSurface: 'self', backRouteName: 'data-process' },
|
2026-07-25 22:41:06 +08:00
|
|
|
|
},
|
2026-07-13 11:04:55 +08:00
|
|
|
|
{
|
|
|
|
|
|
path: 'data-process/:id',
|
|
|
|
|
|
name: 'data-process-detail',
|
|
|
|
|
|
component: () => import('@/views/data-process/DataProcessDetailView.vue'),
|
|
|
|
|
|
meta: { title: '数据处理详情' },
|
|
|
|
|
|
},
|
2026-07-10 16:44:49 +08:00
|
|
|
|
// 数据集管理
|
|
|
|
|
|
{
|
|
|
|
|
|
path: 'dataset',
|
|
|
|
|
|
name: 'dataset',
|
|
|
|
|
|
component: () => import('@/views/dataset/DatasetListView.vue'),
|
|
|
|
|
|
meta: { title: '数据集管理', pageSurface: 'self' },
|
|
|
|
|
|
},
|
|
|
|
|
|
{
|
|
|
|
|
|
path: 'dataset/create',
|
|
|
|
|
|
name: 'dataset-create',
|
|
|
|
|
|
component: () => import('@/views/dataset/DatasetCreateView.vue'),
|
|
|
|
|
|
meta: { title: '上传数据集' },
|
|
|
|
|
|
},
|
|
|
|
|
|
{
|
|
|
|
|
|
path: 'dataset/:id/edit',
|
|
|
|
|
|
name: 'dataset-edit',
|
|
|
|
|
|
component: () => import('@/views/dataset/DatasetCreateView.vue'),
|
|
|
|
|
|
meta: { title: '编辑数据集' },
|
|
|
|
|
|
},
|
|
|
|
|
|
{
|
|
|
|
|
|
path: 'dataset/:id/preview',
|
|
|
|
|
|
name: 'dataset-preview',
|
|
|
|
|
|
component: () => import('@/views/dataset/DatasetPreviewView.vue'),
|
|
|
|
|
|
meta: { title: '数据集预览' },
|
|
|
|
|
|
},
|
|
|
|
|
|
// 其他工具
|
|
|
|
|
|
{
|
|
|
|
|
|
path: 'tools',
|
|
|
|
|
|
name: 'tools',
|
|
|
|
|
|
component: () => import('@/views/tools/ToolsView.vue'),
|
|
|
|
|
|
meta: { title: '其他工具' },
|
|
|
|
|
|
},
|
|
|
|
|
|
{
|
|
|
|
|
|
path: 'tools/create',
|
|
|
|
|
|
name: 'tools-create',
|
|
|
|
|
|
component: () => import('@/views/tools/ToolCreateView.vue'),
|
|
|
|
|
|
meta: { title: '添加自定义工具' },
|
|
|
|
|
|
},
|
|
|
|
|
|
{
|
|
|
|
|
|
path: 'tools/:id/edit',
|
|
|
|
|
|
name: 'tools-edit',
|
|
|
|
|
|
component: () => import('@/views/tools/ToolCreateView.vue'),
|
|
|
|
|
|
meta: { title: '编辑自定义工具' },
|
|
|
|
|
|
},
|
|
|
|
|
|
// 数据类型转换
|
|
|
|
|
|
{
|
|
|
|
|
|
path: 'data-convert',
|
|
|
|
|
|
name: 'data-convert',
|
2026-07-13 11:04:08 +08:00
|
|
|
|
component: () => import('@/views/data-convert/DataConvertView.vue'),
|
2026-07-10 16:44:49 +08:00
|
|
|
|
meta: { title: '数据类型转换' },
|
|
|
|
|
|
},
|
2026-07-21 09:23:43 +08:00
|
|
|
|
{
|
|
|
|
|
|
path: 'compute',
|
|
|
|
|
|
name: 'compute',
|
|
|
|
|
|
component: () => import('@/views/compute/ComputeNodesView.vue'),
|
|
|
|
|
|
meta: { title: '算力节点', pageSurface: 'self', permission: 'compute' },
|
|
|
|
|
|
},
|
|
|
|
|
|
{
|
|
|
|
|
|
path: 'compute/gpus',
|
|
|
|
|
|
redirect: '/compute?tab=gpus',
|
|
|
|
|
|
},
|
|
|
|
|
|
{
|
|
|
|
|
|
path: 'compute/queue',
|
|
|
|
|
|
redirect: '/compute?tab=queue',
|
|
|
|
|
|
},
|
|
|
|
|
|
{
|
|
|
|
|
|
path: 'compute/nodes',
|
|
|
|
|
|
redirect: '/compute?tab=nodes',
|
|
|
|
|
|
},
|
2026-07-10 16:44:49 +08:00
|
|
|
|
// 系统设置
|
2026-07-14 16:10:50 +08:00
|
|
|
|
{
|
|
|
|
|
|
path: 'permission-denied',
|
|
|
|
|
|
name: 'permission-denied',
|
|
|
|
|
|
component: () => import('@/views/system/PermissionDeniedView.vue'),
|
|
|
|
|
|
meta: { title: '无权访问', skipPermission: true },
|
|
|
|
|
|
},
|
2026-07-10 16:44:49 +08:00
|
|
|
|
{
|
|
|
|
|
|
path: 'hardware',
|
|
|
|
|
|
name: 'hardware',
|
|
|
|
|
|
component: () => import('@/views/system/HardwareView.vue'),
|
|
|
|
|
|
meta: { title: '平台性能' },
|
|
|
|
|
|
},
|
|
|
|
|
|
{
|
|
|
|
|
|
path: 'logs',
|
|
|
|
|
|
name: 'logs',
|
|
|
|
|
|
component: () => import('@/views/system/LogsView.vue'),
|
|
|
|
|
|
meta: { title: '查看日志' },
|
|
|
|
|
|
},
|
2026-07-14 16:10:50 +08:00
|
|
|
|
{
|
|
|
|
|
|
path: 'user-settings',
|
|
|
|
|
|
name: 'user-settings',
|
|
|
|
|
|
component: () => import('@/views/system/UserSettingsView.vue'),
|
|
|
|
|
|
meta: { title: '用户设置', pageSurface: 'self', permission: 'user-settings' },
|
|
|
|
|
|
},
|
|
|
|
|
|
{
|
|
|
|
|
|
path: 'user-settings/create',
|
|
|
|
|
|
name: 'user-create',
|
|
|
|
|
|
component: () => import('@/views/system/UserCreateView.vue'),
|
|
|
|
|
|
meta: { title: '创建用户', permission: 'user-settings' },
|
|
|
|
|
|
},
|
|
|
|
|
|
{
|
|
|
|
|
|
path: 'user-settings/:id/permission',
|
|
|
|
|
|
name: 'user-permission',
|
|
|
|
|
|
component: () => import('@/views/system/UserPermissionView.vue'),
|
|
|
|
|
|
meta: { title: '权限设置', permission: 'user-settings' },
|
|
|
|
|
|
},
|
2026-07-10 16:44:49 +08:00
|
|
|
|
{
|
|
|
|
|
|
path: 'training-log/:id',
|
|
|
|
|
|
name: 'training-log',
|
|
|
|
|
|
component: () => import('@/views/system/TrainingLogView.vue'),
|
|
|
|
|
|
meta: { title: '训练日志' },
|
|
|
|
|
|
},
|
|
|
|
|
|
],
|
|
|
|
|
|
},
|
|
|
|
|
|
{
|
|
|
|
|
|
path: '/:pathMatch(.*)*',
|
2026-07-11 14:49:37 +08:00
|
|
|
|
redirect: '/dashboard',
|
2026-07-10 16:44:49 +08:00
|
|
|
|
},
|
|
|
|
|
|
]
|
|
|
|
|
|
|
|
|
|
|
|
const router = createRouter({
|
|
|
|
|
|
history: createWebHistory(),
|
|
|
|
|
|
routes,
|
|
|
|
|
|
})
|
|
|
|
|
|
|
2026-07-14 16:10:50 +08:00
|
|
|
|
const permissionBySegment: Record<string, PermissionCode> = {
|
|
|
|
|
|
dashboard: 'dashboard',
|
|
|
|
|
|
'fine-tune': 'fine-tune',
|
|
|
|
|
|
'training-log': 'fine-tune',
|
|
|
|
|
|
'model-eval': 'model-eval',
|
|
|
|
|
|
'model-inference': 'model-inference',
|
|
|
|
|
|
'model-compare': 'model-inference',
|
|
|
|
|
|
'model-manage': 'model-manage',
|
|
|
|
|
|
dataset: 'dataset',
|
|
|
|
|
|
'data-process': 'data-process',
|
|
|
|
|
|
'data-convert': 'data-convert',
|
2026-07-21 09:23:43 +08:00
|
|
|
|
compute: 'compute',
|
2026-07-14 16:10:50 +08:00
|
|
|
|
tools: 'data-convert',
|
|
|
|
|
|
hardware: 'hardware',
|
|
|
|
|
|
logs: 'logs',
|
|
|
|
|
|
'user-settings': 'user-settings',
|
2026-08-03 09:34:08 +08:00
|
|
|
|
tenants: 'user-settings',
|
|
|
|
|
|
projects: 'user-settings',
|
|
|
|
|
|
'audit-logs': 'user-settings',
|
2026-08-18 14:49:12 +08:00
|
|
|
|
'operation-logs': 'user-settings',
|
2026-08-03 09:34:08 +08:00
|
|
|
|
'approval-templates': 'user-settings',
|
|
|
|
|
|
'approval-instances': 'user-settings',
|
2026-08-17 09:15:36 +08:00
|
|
|
|
'resource-acl': 'user-settings',
|
2026-07-14 16:10:50 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
function requiredPermission(path: string, explicit?: unknown) {
|
|
|
|
|
|
if (explicit) return explicit as PermissionCode
|
|
|
|
|
|
const segment = path.split('/')[1]
|
|
|
|
|
|
return permissionBySegment[segment]
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-08-03 09:34:08 +08:00
|
|
|
|
// 全局守卫:登录校验
|
|
|
|
|
|
// 离开页面超时由 App.vue 的 visibilitychange 监听接管
|
2026-08-17 09:15:36 +08:00
|
|
|
|
//
|
|
|
|
|
|
// 权限控制规则(基于 governance-user-guide.md 设计):
|
|
|
|
|
|
// - admin 用户:可以访问所有页面
|
|
|
|
|
|
// - 非 admin 用户:默认可访问所有业务页面(训练、评测、推理、数据等)
|
|
|
|
|
|
// 仅以下页面限制 admin 访问:user-settings、compute(算力节点)
|
2026-07-10 16:44:49 +08:00
|
|
|
|
router.beforeEach((to, _from, next) => {
|
2026-08-03 09:34:08 +08:00
|
|
|
|
if (!to.meta.public) routeLoading.value = true
|
2026-07-10 16:44:49 +08:00
|
|
|
|
const auth = useAuthStore()
|
|
|
|
|
|
document.title = to.meta.title ? `${to.meta.title} - 远光软件微调平台` : '远光软件微调平台'
|
|
|
|
|
|
|
|
|
|
|
|
if (to.meta.public) {
|
|
|
|
|
|
// 已登录访问登录页则跳转主页
|
|
|
|
|
|
if (to.name === 'login' && auth.isLoggedIn) {
|
2026-07-11 14:49:37 +08:00
|
|
|
|
next('/dashboard')
|
2026-07-10 16:44:49 +08:00
|
|
|
|
return
|
|
|
|
|
|
}
|
|
|
|
|
|
next()
|
|
|
|
|
|
return
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (!auth.isLoggedIn) {
|
2026-08-03 16:20:21 +08:00
|
|
|
|
auth.logout() // fire-and-forget,无需阻塞跳转
|
2026-07-10 16:44:49 +08:00
|
|
|
|
next({ name: 'login' })
|
|
|
|
|
|
return
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-07-14 16:10:50 +08:00
|
|
|
|
if (!to.meta.skipPermission) {
|
|
|
|
|
|
const permission = requiredPermission(to.path, to.meta.permission)
|
2026-08-17 09:15:36 +08:00
|
|
|
|
// 仅限制管理员专属页面的访问权限
|
|
|
|
|
|
// user-settings(用户设置、租户管理、项目空间、审批、审计日志)仅 admin 可访问
|
2026-08-10 11:41:16 +08:00
|
|
|
|
if (permission === 'user-settings' && !auth.isAdmin) {
|
|
|
|
|
|
next({ name: 'permission-denied', replace: true })
|
|
|
|
|
|
return
|
|
|
|
|
|
}
|
2026-08-17 09:15:36 +08:00
|
|
|
|
// compute(算力节点/GPU 分配)仅 admin 可访问
|
2026-08-10 11:41:16 +08:00
|
|
|
|
if (permission === 'compute' && !auth.isAdmin) {
|
|
|
|
|
|
next({ name: 'permission-denied', replace: true })
|
|
|
|
|
|
return
|
|
|
|
|
|
}
|
2026-08-17 09:15:36 +08:00
|
|
|
|
// 其他所有业务页面对已登录用户开放,不再检查权限码
|
2026-07-14 16:10:50 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2026-08-04 17:18:50 +08:00
|
|
|
|
// 路由切换时记录业务模块访问(用于看板用户操作分布统计)
|
|
|
|
|
|
const ROUTE_TO_MODULE: Record<string, string> = {
|
|
|
|
|
|
'/fine-tune': 'fine-tune',
|
|
|
|
|
|
'/model-eval': 'model-eval',
|
|
|
|
|
|
'/model-inference': 'model-inference',
|
|
|
|
|
|
'/model-compare': 'model-inference',
|
|
|
|
|
|
'/data-process': 'data-process',
|
|
|
|
|
|
'/data-convert': 'data-convert',
|
|
|
|
|
|
'/model-manage': 'model-manage',
|
|
|
|
|
|
'/dataset-manage': 'dataset',
|
|
|
|
|
|
}
|
|
|
|
|
|
for (const [prefix, module] of Object.entries(ROUTE_TO_MODULE)) {
|
|
|
|
|
|
if (to.path.startsWith(prefix)) {
|
|
|
|
|
|
const key = `route-visit:${module}`
|
|
|
|
|
|
const last = Number(sessionStorage.getItem(key) || 0)
|
|
|
|
|
|
if (Date.now() - last >= 60000) {
|
|
|
|
|
|
sessionStorage.setItem(key, String(Date.now()))
|
|
|
|
|
|
import('@/api/modules/audit-visit').then(({ recordModuleVisit }) => {
|
|
|
|
|
|
recordModuleVisit(module, to.fullPath).catch(() => {})
|
|
|
|
|
|
}).catch(() => {})
|
|
|
|
|
|
}
|
|
|
|
|
|
break
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-07-10 16:44:49 +08:00
|
|
|
|
next()
|
|
|
|
|
|
})
|
|
|
|
|
|
|
2026-08-03 09:34:08 +08:00
|
|
|
|
router.afterEach(() => {
|
|
|
|
|
|
routeLoading.value = false
|
|
|
|
|
|
})
|
|
|
|
|
|
|
2026-07-10 16:44:49 +08:00
|
|
|
|
export default router
|