import Vue from 'vue' import VueRouter from 'vue-router' import Layout from '@/views/Layout.vue' import axios from 'axios' import { getEffectivePermissions, hasPermission } from '@/utils/permissions' Vue.use(VueRouter) const routes = [ { path: '/login', name: 'Login', component: () => import('@/views/Login.vue') }, { path: '/403', name: 'Forbidden', component: { template: '

403

权限不足

返回首页
' }, meta: { title: '权限不足' } }, { path: '/', component: Layout, redirect: '/dashboard', children: [ { path: 'dashboard', name: 'Dashboard', component: () => import('@/views/Dashboard.vue'), meta: { title: '首页', perm: 'dashboard' } }, { path: 'teacher-dashboard', name: 'TeacherDashboard', component: () => import('@/views/TeacherDashboard.vue'), meta: { title: '规划师首页', requiresTeacher: true, perm: 'dashboard' } }, { path: 'admin/dashboard', name: 'AdminDashboard', component: () => import('@/views/admin/Dashboard'), meta: { title: '数据大屏', roles: ['admin'] } }, // 家庭管理 { path: 'families', name: 'Families', component: () => import('@/views/Families.vue'), meta: { title: '家庭列表', perm: 'family:list' } }, { path: 'children', name: 'Children', component: () => import('@/views/Children.vue'), meta: { title: '孩子管理', perm: 'family:children' } }, { path: 'points', name: 'Points', component: () => import('@/views/Points.vue'), meta: { title: '积分管理', perm: 'family:points' } }, { path: 'points-log', name: 'PointsLog', component: () => import('@/views/admin/PointsLog.vue'), meta: { title: '积分记录', perm: 'family:points' } }, { path: 'membership-center', name: 'MembershipCenter', component: () => import('@/views/admin/MembershipCenter.vue'), meta: { title: '会员中心', perm: 'system:config' } }, // 任务管理 { path: 'tasks', name: 'Tasks', component: () => import('@/views/Tasks.vue'), meta: { title: '任务列表', perm: 'task:list' } }, { path: 'task-templates', name: 'TaskTemplates', component: () => import('@/views/TaskTemplates.vue'), meta: { title: '任务模板', perm: 'task:templates' } }, { path: 'growth-task', name: 'GrowthTaskManagement', component: () => import('@/views/admin/GrowthTaskManagement'), meta: { title: '成长任务管理', perm: 'task:list' } }, // 奖励管理 { path: 'rewards', name: 'Rewards', component: () => import('@/views/Rewards.vue'), meta: { title: '奖励列表', perm: 'family:rewards' } }, { path: 'wishes', name: 'Wishes', component: () => import('@/views/Wishes.vue'), meta: { title: '心愿管理', perm: 'family:wishes' } }, // 成长规划师管理 { path: 'guide-packages', name: 'GuidePackages', component: () => import('@/views/GuidePackages.vue'), meta: { title: '成长规划师任务模板', requiresTeacher: true, perm: 'biz:packages' } }, { path: 'guide-family-task', name: 'GuideFamilyTask', component: () => import('@/views/GuideFamilyTask.vue'), meta: { title: '家庭任务管理', requiresTeacher: true, perm: 'service:families' } }, { path: 'teacher-consult', name: 'TeacherConsult', component: () => import('@/views/TeacherConsult.vue'), meta: { title: '家长咨询', requiresTeacher: true, perm: 'assessment:consult' } }, { path: 'teacher-families', name: 'TeacherFamilies', component: () => import('@/views/teacher/TeacherFamilies.vue'), meta: { title: '我的家庭', requiresTeacher: true, perm: 'service:families' } }, { path: 'teacher-team', name: 'TeacherTeam', component: () => import('@/views/teacher/TeacherTeam.vue'), meta: { title: '我的团队', requiresTeacher: true, perm: 'service:team' } }, { path: 'teacher-packages', name: 'TeacherPackages', component: () => import('@/views/teacher/TeacherPackages.vue'), meta: { title: '任务模板管理', requiresTeacher: true, perm: 'biz:packages' } }, { path: 'teacher-orders', name: 'TeacherOrders', component: () => import('@/views/teacher/TeacherOrders.vue'), meta: { title: '订单与佣金', requiresTeacher: true, perm: 'biz:orders' } }, // 系统管理 { path: 'users', name: 'Users', component: () => import('@/views/Users.vue'), meta: { title: '用户管理', perm: 'system:users' } }, { path: 'review-center', name: 'ReviewCenter', component: () => import('@/views/admin/ReviewCenter.vue'), meta: { title: '审核中心', perm: 'audit' } }, { path: 'operation-logs', name: 'OperationLogs', component: () => import('@/views/OperationLogs.vue'), meta: { title: '操作日志', perm: 'system:logs' } }, // 服务管理(管理员) { path: 'service-role-applications', name: 'ServiceRoleApplications', component: () => import('@/views/service/ServiceRoleApplications.vue'), meta: { title: '服务角色申请审核', perm: 'audit' } }, { path: 'teacher-messages', name: 'TeacherMessages', component: () => import('@/views/teacher/TeacherMessages.vue'), meta: { title: '消息中心', requiresTeacher: true, perm: 'messages' } }, { path: 'teacher-assessment', name: 'TeacherAssessment', component: () => import('@/views/teacher/TeacherAssessment.vue'), meta: { title: 'DAN测评管理', requiresTeacher: true, perm: 'assessment:dan' } }, { path: 'teacher-proxy-report', name: 'TeacherProxyReport', component: () => import('@/views/teacher/ProxyReport.vue'), meta: { title: '代上传报告', requiresTeacher: true, perm: 'health:reports' } }, { path: 'assessment-admin', name: 'AssessmentAdmin', component: () => import('@/views/admin/Assessment.vue'), meta: { title: '测评管理', perm: 'assessment:dan' } }, { path: 'assessment-assign', name: 'AssessmentAssign', component: () => import('@/views/admin/AssessmentAssign.vue'), meta: { title: '待分配规划师', perm: 'assessment:dan' } }, { path: 'assessment-orders', name: 'AssessmentOrders', component: () => import('@/views/admin/AssessmentOrders.vue'), meta: { title: '测评订单', perm: 'assessment:dan' } }, { path: 'dan-execution', name: 'DanExecution', component: () => import('@/views/admin/DanExecution.vue'), meta: { title: 'DAN服务执行', perm: 'assessment:dan' } }, { path: 'dan-settlement', name: 'DanSettlement', component: () => import('@/views/admin/DanSettlement.vue'), meta: { title: 'DAN结算管理', perm: 'assessment:dan' } }, { path: 'product-manage', name: 'ProductManage', component: () => import('@/views/admin/ProductManage.vue'), meta: { title: '商品管理', perm: 'commerce:products' } }, { path: 'inventory', name: 'InventoryManage', component: () => import('@/views/admin/InventoryManage.vue'), meta: { title: '库存管理', perm: 'commerce:products' } }, { path: 'inventory/inbound-create', name: 'InventoryInboundCreate', component: () => import('@/views/admin/InventoryInboundCreate.vue'), meta: { title: '创建入库单', perm: 'commerce:products' } }, { path: 'product-edit', name: 'ProductEdit', component: () => import('@/views/admin/ProductEdit.vue'), meta: { title: '商品编辑', perm: 'commerce:products' } }, { path: 'assessment-products', name: 'AssessmentProducts', component: () => import('@/views/admin/AssessmentProducts.vue'), meta: { title: '测评商品管理', perm: 'commerce:products' } }, { path: 'assessment-plan-rules', name: 'AssessmentPlanRules', component: () => import('@/views/admin/AssessmentPlanRules.vue'), meta: { title: '方案生成规则', perm: 'plan:rules' } }, { path: 'plan-management', name: 'PlanManagement', component: () => import('@/views/admin/PlanManagement.vue'), meta: { title: '方案管理', perm: 'plan:manage' } }, { path: 'category-manage', name: 'CategoryManage', component: () => import('@/views/admin/CategoryManage.vue'), meta: { title: '商品分类', perm: 'commerce:category' } }, { path: 'order-manage', name: 'OrderManage', component: () => import('@/views/admin/OrderManage.vue'), meta: { title: '订单管理', perm: 'commerce:orders' } }, { path: 'pending-refund', name: 'PendingRefund', component: () => import('@/views/admin/PendingRefund.vue'), meta: { title: '待退款管理', perm: 'commerce:orders' } }, { path: 'order-detail/:orderNo', name: 'OrderDetail', component: () => import('@/views/admin/OrderDetail.vue'), meta: { title: '订单详情', perm: 'commerce:orders' } }, { path: 'sys-config', name: 'SysConfig', component: () => import('@/views/admin/SysConfig.vue'), meta: { title: '系统配置', perm: 'system:config' } }, { path: 'energy-sandbox', name: 'EnergySandbox', component: () => import('@/views/admin/EnergySandbox.vue'), meta: { title: '五维能量', perm: 'energy' } }, { path: 'energy-rule', name: 'EnergyRuleManagement', component: () => import('@/views/admin/EnergyRuleManagement.vue'), meta: { title: '能量规则管理', perm: 'energy' } }, { path: 'energy-behavior-config', name: 'EnergyBehaviorConfig', component: () => import('@/views/admin/EnergyBehaviorConfig.vue'), meta: { title: '能量行为配置', perm: 'energy' } }, { path: 'challenge-score-config', name: 'ChallengeScoreConfig', component: () => import('@/views/admin/ChallengeScoreConfig.vue'), meta: { title: '挑战评分配置', perm: 'energy' } }, // ========== 文章管理 ========== { path: 'article-categories', name: 'ArticleCategory', component: () => import('@/views/admin/ArticleCategory.vue'), meta: { title: '文章分类', perm: 'articles:categories' } }, { path: 'article-manage', name: 'ArticleManage', component: () => import('@/views/admin/ArticleManage.vue'), meta: { title: '文章管理', perm: 'articles:manage' } }, { path: 'article-edit', name: 'ArticleEdit', component: () => import('@/views/admin/ArticleEdit.vue'), meta: { title: '文章编辑', perm: 'articles:manage' } }, { path: 'comment-review', name: 'CommentReview', component: () => import('@/views/admin/CommentReview.vue'), meta: { title: '评论审核', perm: 'articles:manage' } }, { path: 'notice-manage', name: 'NoticeManage', component: () => import('@/views/admin/NoticeManage.vue'), meta: { title: '公告管理', perm: 'admin' } }, { path: 'periodic-service-config', name: 'PeriodicServiceConfig', component: () => import('@/views/admin/PeriodicServiceConfig.vue'), meta: { title: '周期性服务配置', perm: 'admin' } }, { path: 'knowledge-tags', name: 'KnowledgeTag', component: () => import('@/views/admin/KnowledgeTag.vue'), meta: { title: '知识标签', perm: 'articles:categories' } }, { path: 'virtual-goods-config', name: 'VirtualGoodsConfig', component: () => import('@/views/admin/VirtualGoodsConfig.vue'), meta: { title: '虚拟支付道具', perm: 'system:config' } }, { path: 'gates', name: 'Gates', component: () => import('@/views/admin/gates.vue'), meta: { title: '关卡配置', perm: 'system:config' } }, // ========== 佣金推荐系统 ========== { path: 'product-profit-rate', name: 'ProductProfitRate', component: () => import('@/views/admin/ProductProfitRate.vue'), meta: { title: '产品利润率', perm: 'commerce:profit-rate' } }, { path: 'product-ppoint', name: 'ProductPpointManage', component: () => import('@/views/admin/ProductPpointManage.vue'), meta: { title: 'P点配置', perm: 'commerce:ppoint' } }, { path: 'cf-value', name: 'CfValueManage', component: () => import('@/views/admin/CfValueManage.vue'), meta: { title: 'CF值管理', perm: 'finance:cf-value' } }, { path: 'supply-system', name: 'SupplySystemList', component: () => import('@/views/admin/supply-system/List.vue'), meta: { title: '供应商体系', perm: 'system:supply' } }, { path: 'supply-system/create', name: 'SupplySystemCreate', component: () => import('@/views/admin/supply-system/Form.vue'), meta: { title: '新建体系', perm: 'system:supply' } }, { path: 'supply-system/:id', name: 'SupplySystemDetail', component: () => import('@/views/admin/supply-system/Detail.vue'), meta: { title: '体系详情', perm: 'system:supply' }, props: true }, { path: 'supplier-products', name: 'SupplierProducts', component: () => import('@/views/admin/SupplierProductManage.vue'), meta: { title: '商品管理', perm: 'commerce:product' } }, { path: 'supply-system/:id/edit', name: 'SupplySystemEdit', component: () => import('@/views/admin/supply-system/Form.vue'), meta: { title: '编辑体系', perm: 'system:supply' }, props: true }, { path: 'supply-hierarchy/change-requests', name: 'SupplyHierarchyChangeRequests', component: () => import('@/views/admin/supply-hierarchy/ChangeRequest.vue'), meta: { title: '更换上级审核', perm: 'system:supply' } }, // ========== 供应商管理 (supplier_admin) ========== { path: 'supply-manage', name: 'SupplyManage', component: () => import('@/views/admin/supply-manage/List.vue'), meta: { title: '供应商管理', perm: 'supply:manage' } }, { path: 'supply-manage/create', name: 'SupplyManageCreate', component: () => import('@/views/admin/supply-manage/Form.vue'), meta: { title: '新增供应商', perm: 'supply:manage' } }, { path: 'supply-manage/:id', name: 'SupplyManageDetail', component: () => import('@/views/admin/supply-manage/Detail.vue'), meta: { title: '供应商详情', perm: 'supply:manage' }, props: true }, { path: 'supply-manage/:id/edit', name: 'SupplyManageEdit', component: () => import('@/views/admin/supply-manage/Form.vue'), meta: { title: '编辑供应商', perm: 'supply:manage' }, props: true }, { path: 'zodiac-configs', name: 'ZodiacConfigs', component: () => import('@/views/admin/ZodiacConfigs.vue'), meta: { title: '星座配置', perm: 'system:config' } }, { path: 'bazi-configs', name: 'BaziConfigs', component: () => import('@/views/admin/BaziConfigs.vue'), meta: { title: '八字配置', perm: 'system:config' } }, { path: 'blood-type-configs', name: 'BloodTypeConfigs', component: () => import('@/views/admin/BloodTypeConfigs.vue'), meta: { title: '血型配置', perm: 'system:config' } }, // ========== 饮食管理 (admin) ========== { path: 'foods', name: 'Foods', component: () => import('@/views/admin/Foods.vue'), meta: { title: '食材管理', perm: 'diet:foods' } }, { path: 'recipes', name: 'Recipes', component: () => import('@/views/admin/Recipes.vue'), meta: { title: '食谱管理', perm: 'diet:recipes' } }, { path: 'my-families', name: 'MyFamilies', component: () => import('@/views/teacher/FamilyList.vue'), meta: { title: '我的家庭', perm: 'service:family' } }, { path: 'growth-records', name: 'GrowthRecords', component: () => import('@/views/teacher/GrowthRecords.vue'), meta: { title: '成长记录', perm: 'growth:records' } }, { path: 'growth-plans', name: 'GrowthPlans', component: () => import('@/views/teacher/GrowthPlans.vue'), meta: { title: '成长计划', perm: 'growth:plans' } }, { path: 'health-reports', name: 'HealthReports', component: () => import('@/views/admin/UnifiedHealthReports.vue'), meta: { title: '健康报告', perm: 'health:reports' } }, { path: 'health-indicators', name: 'HealthIndicators', component: () => import('@/views/nutritionist/HealthIndicators.vue'), meta: { title: '健康指标', perm: 'health:indicators' } }, { path: 'health-checkins', name: 'HealthCheckinManage', component: () => import('@/views/admin/HealthCheckinManage.vue'), meta: { title: '健康打卡管理', perm: 'health:checkins' } }, { path: 'emotion-alert', name: 'EmotionAlertManage', component: () => import('@/views/admin/EmotionAlertManage.vue'), meta: { title: '情绪告警管理', perm: 'health:checkins' } }, { path: 'nutrition-mappings', name: 'NutritionMappings', component: () => import('@/views/nutritionist/NutritionMappings.vue'), meta: { title: '营养素映射管理', perm: 'health:mappings' } }, { path: 'activities', name: 'Activities', component: () => import('@/views/admin/Activities.vue'), meta: { title: '活动列表', perm: 'activity:list' } }, { path: 'activity-edit', name: 'ActivityEdit', component: () => import('@/views/admin/ActivityEdit.vue'), meta: { title: '活动编辑', perm: 'activity:list' } }, { path: 'survey-templates', name: 'SurveyTemplates', component: () => import('@/views/admin/SurveyTemplates.vue'), meta: { title: '调研模板管理', perm: 'admin:all' } }, { path: 'activity-review', name: 'ActivityReview', component: () => import('@/views/admin/ActivityReview.vue'), meta: { title: '活动审核', perm: 'activity:review' } }, { path: 'activity-registration-review', name: 'ActivityRegistrationReview', component: () => import('@/views/admin/ActivityRegistrationReview.vue'), meta: { title: '活动报名审核', perm: 'activity:review' } }, // ========== 维度配置 + 知识库管理 ========== { path: 'coupon', name: 'CouponManagement', component: () => import('@/views/admin/CouponManagement'), meta: { title: '优惠券管理', perm: 'marketing:coupon' } }, { path: 'coupon-grant-log', name: 'CouponGrantLog', component: () => import('@/views/admin/CouponGrantLog.vue'), meta: { title: '发券记录', perm: 'marketing:coupon' } }, { path: 'promotion', name: 'PromotionManagement', component: () => import('@/views/admin/PromotionManagement'), meta: { title: '推广管理', perm: 'marketing:promotion' } }, { path: 'family-earnings', name: 'FamilyEarnings', component: () => import('@/views/admin/FamilyEarnings.vue'), meta: { title: '家庭收益管理', perm: 'marketing:promotion' } }, { path: 'family-earnings-withdraw', name: 'FamilyEarningsWithdraw', component: () => import('@/views/admin/FamilyEarningsWithdraw.vue'), meta: { title: '家庭收益提现审核', perm: 'audit:withdraw' } }, { path: 'dimension-config', name: 'DimensionConfig', component: () => import('@/views/admin/dimension'), meta: { title: '维度配置', perm: 'system:config' } }, { path: 'knowledge-base', name: 'KnowledgeBase', component: () => import('@/views/admin/knowledge'), meta: { title: '知识库管理', perm: 'system:config' } }, { path: 'health-knowledge', name: 'HealthKnowledge', component: () => import('@/views/admin/health-knowledge'), meta: { title: '健康知识库管理', perm: 'system:config' } }, // ========== 健康维度配置 ========== { path: 'health-norm-config', name: 'HealthNormConfig', component: () => import('@/views/admin/dimension-config'), meta: { title: '健康常模配置', perm: 'system:config' } }, { path: 'health-data-source', name: 'HealthDataSource', component: () => import('@/views/admin/data-source-config'), meta: { title: '健康数据源配置', perm: 'system:config' } }, { path: 'health-energy-config', name: 'HealthEnergyConfig', component: () => import('@/views/admin/HealthEnergyConfig'), meta: { title: '七维能量配置', perm: 'system:config' } }, { path: 'energy-config', name: 'EnergyConfig', component: () => import('@/views/admin/energy-config/WuxingConfig'), meta: { title: '五行能量配置', perm: 'system:config' } }, { path: 'indicators', name: 'IndicatorManage', component: () => import('@/views/admin/indicators'), meta: { title: '指标管理', perm: 'system:config' } }, // ========== 虚拟服务商团队管理 ========== { path: 'virtual-teams', name: 'VirtualTeamList', component: () => import('@/views/admin/VirtualTeamList'), meta: { title: '虚拟团队管理', perm: 'system:config' } }, { path: 'virtual-team-detail/:systemId', name: 'VirtualTeamDetail', component: () => import('@/views/admin/VirtualTeamDetail'), meta: { title: '团队成员管理', perm: 'system:config' }, props: true }, // ========== 电商供应商管理 ========== { path: 'ecom-supplier', name: 'EcomSupplierManage', component: () => import('@/views/admin/EcomSupplierManage'), meta: { title: '电商供应商管理', perm: 'ecom:supplier' } }, { path: 'badge-manage', name: 'BadgeManage', component: () => import('@/views/admin/BadgeManage'), meta: { title: '勋章管理', perm: 'system:config' } }, // ========== 报告指纹解析系统 ========== { path: 'report-types', name: 'ReportTypeManagement', component: () => import('@/views/admin/ReportTypeManagement.vue'), meta: { title: '报告类型管理', perm: 'system:config' } }, { path: 'report-parser-import', name: 'ReportParserImport', component: () => import('@/views/admin/ReportParserImport.vue'), meta: { title: '解析器导入管理', perm: 'system:config' } }, { path: 'report-unknown-clusters', name: 'ReportUnknownCluster', component: () => import('@/views/admin/ReportUnknownCluster.vue'), meta: { title: '未知报告审核', perm: 'system:config' } }, { path: 'report-auto-learn', name: 'ReportAutoLearn', component: () => import('@/views/admin/ReportAutoLearn.vue'), meta: { title: '报告自学习', perm: 'system:config' } }, // ========== LangGraph AI 服务管理 ========== { path: 'langgraph-admin', name: 'LangGraphAdmin', component: () => import('@/views/admin/LangGraphAdmin.vue'), meta: { title: 'LangGraph 管理', perm: 'system:config' } }, // ========== 营养产品管理 ========== { path: 'nutrition-products', name: 'NutritionProducts', component: () => import('@/views/admin/NutritionProducts.vue'), meta: { title: '营养产品管理', perm: 'health:*' } }, { path: 'file-manage', name: 'FileManage', component: () => import('@/views/admin/FileManage'), meta: { title: '文件管理', perm: 'system:config' } } ] } ] const router = new VueRouter({ mode: 'history', base: process.env.BASE_URL, routes }) // Token 验证缓存:避免 redirect 链中重复调用 /api/admin-auth/info let tokenValidationCache = { token: '', timestamp: 0 } const TOKEN_CACHE_TTL = 30000 // 30秒内不重复验证 async function validateToken(token) { try { const baseURL = process.env.VUE_APP_BASE_API || '' const res = await axios.post(`${baseURL}/api/admin-auth/info`, {}, { headers: { Authorization: `Bearer ${token}` }, validateStatus: () => true }) return res.status === 200 && res.data?.code === 200 } catch (e) { return false } } router.beforeEach(async (to, from, next) => { const token = localStorage.getItem('token') const role = localStorage.getItem('role') // 1. 登录页允许访问 if (to.path === '/login') { // 如果已有token和role,直接跳转到对应首页 if (token && role === 'teacher') { return next('/teacher-dashboard') } return next() } // 2. 无Token强制跳转 if (!token) { return next('/login') } // 3. Token有效性验证 (使用缓存避免 redirect 链重复调用) const now = Date.now() let isValid = false if (tokenValidationCache.token === token && (now - tokenValidationCache.timestamp) < TOKEN_CACHE_TTL) { isValid = true } else { isValid = await validateToken(token) if (isValid) { tokenValidationCache = { token, timestamp: now } } } if (!isValid) { tokenValidationCache = { token: '', timestamp: 0 } localStorage.clear() return next('/login') } // 3.5 规划师访问 /dashboard → 跳转规划师仪表板 if (role === 'teacher' && to.path === '/dashboard') { return next('/teacher-dashboard') } // 4. 权限检查:如果路由有 perm 要求,检查用户权限 const requiredPerm = to.meta && to.meta.perm if (requiredPerm) { const rolesStr = localStorage.getItem('roles') const roles = rolesStr ? JSON.parse(rolesStr) : [localStorage.getItem('role')] const perms = getEffectivePermissions(roles) if (!hasPermission(perms, requiredPerm)) { console.warn('Access denied: insufficient permissions for', to.name) return next({ path: '/403', replace: true }) } } return next() }) // Suppress navigation guard redirect errors (teacher/admin route cross-access) const originalPush = VueRouter.prototype.push VueRouter.prototype.push = function push(location) { return originalPush.call(this, location).catch(err => err) } export default router