index.js 35 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006
  1. import Vue from 'vue'
  2. import VueRouter from 'vue-router'
  3. import Layout from '@/views/Layout.vue'
  4. import axios from 'axios'
  5. import { getEffectivePermissions, hasPermission, getRoleHomePage } from '@/utils/permissions'
  6. Vue.use(VueRouter)
  7. const routes = [
  8. {
  9. path: '/login',
  10. name: 'Login',
  11. component: () => import('@/views/Login.vue')
  12. },
  13. {
  14. path: '/403',
  15. name: 'Forbidden',
  16. component: { template: '<div style="text-align:center;padding:100px"><h1>403</h1><p>权限不足</p><router-link to="/">返回首页</router-link></div>' },
  17. meta: { title: '权限不足' }
  18. },
  19. {
  20. path: '/',
  21. component: Layout,
  22. redirect: '/dashboard',
  23. children: [
  24. {
  25. path: 'dashboard',
  26. name: 'Dashboard',
  27. component: () => import('@/views/Dashboard.vue'),
  28. meta: { title: '首页', perm: 'dashboard' }
  29. },
  30. {
  31. path: 'teacher-dashboard',
  32. name: 'TeacherDashboard',
  33. component: () => import('@/views/TeacherDashboard.vue'),
  34. meta: { title: '规划师首页', requiresTeacher: true, perm: 'dashboard' }
  35. },
  36. {
  37. path: 'admin/dashboard',
  38. name: 'AdminDashboard',
  39. component: () => import('@/views/admin/Dashboard'),
  40. meta: { title: '数据大屏', roles: ['admin'] }
  41. },
  42. // 家庭管理
  43. {
  44. path: 'families',
  45. name: 'Families',
  46. component: () => import('@/views/Families.vue'),
  47. meta: { title: '家庭列表', perm: 'family:list' }
  48. },
  49. {
  50. path: 'children',
  51. name: 'Children',
  52. component: () => import('@/views/Children.vue'),
  53. meta: { title: '孩子管理', perm: 'family:children' }
  54. },
  55. {
  56. path: 'points',
  57. name: 'Points',
  58. component: () => import('@/views/Points.vue'),
  59. meta: { title: '积分管理', perm: 'family:points' }
  60. },
  61. {
  62. path: 'points-log',
  63. name: 'PointsLog',
  64. component: () => import('@/views/admin/PointsLog.vue'),
  65. meta: { title: '积分记录', perm: 'family:points' }
  66. },
  67. {
  68. path: 'membership-center',
  69. name: 'MembershipCenter',
  70. component: () => import('@/views/admin/MembershipCenter.vue'),
  71. meta: { title: '会员中心', perm: 'config:member' }
  72. },
  73. // 方案中心
  74. {
  75. path: 'health-plan-manage',
  76. name: 'HealthPlanManage',
  77. component: () => import('@/views/admin/HealthPlanManage.vue'),
  78. meta: { title: '健康方案管理', perm: 'plan:health' }
  79. },
  80. {
  81. path: 'assessment-plan-manage',
  82. name: 'AssessmentPlanManage',
  83. component: () => import('@/views/admin/AssessmentPlanManage.vue'),
  84. meta: { title: '测评方案管理', perm: 'plan:assessment' }
  85. },
  86. // 任务管理
  87. {
  88. path: 'tasks',
  89. name: 'Tasks',
  90. component: () => import('@/views/Tasks.vue'),
  91. meta: { title: '任务列表', perm: 'task:list' }
  92. },
  93. {
  94. path: 'task-templates',
  95. name: 'TaskTemplates',
  96. component: () => import('@/views/TaskTemplates.vue'),
  97. meta: { title: '任务模板', perm: 'task:templates' }
  98. },
  99. {
  100. path: 'orchestration',
  101. name: 'OrchestrationFlow',
  102. component: () => import('@/views/orchestration/OrchestrationFlow.vue'),
  103. meta: { title: '编排流管理', perm: 'task:templates' }
  104. },
  105. {
  106. path: 'orchestration/edit/:id?',
  107. name: 'FlowEditor',
  108. component: () => import('@/views/orchestration/FlowEditor.vue'),
  109. meta: { title: '编排画布', perm: 'task:templates' }
  110. },
  111. {
  112. path: 'orchestration/execution/:id',
  113. name: 'ExecutionDetail',
  114. component: () => import('@/views/orchestration/ExecutionDetail.vue'),
  115. meta: { title: '执行详情', perm: 'task:templates' }
  116. },
  117. {
  118. path: 'growth-task',
  119. name: 'GrowthTaskManagement',
  120. component: () => import('@/views/admin/GrowthTaskManagement'),
  121. meta: { title: '成长任务管理', perm: 'task:list' }
  122. },
  123. // 奖励管理
  124. {
  125. path: 'rewards',
  126. name: 'Rewards',
  127. component: () => import('@/views/Rewards.vue'),
  128. meta: { title: '奖励列表', perm: 'family:rewards' }
  129. },
  130. {
  131. path: 'wishes',
  132. name: 'Wishes',
  133. component: () => import('@/views/Wishes.vue'),
  134. meta: { title: '心愿管理', perm: 'family:wishes' }
  135. },
  136. // 成长规划师管理
  137. {
  138. path: 'guide-packages',
  139. name: 'GuidePackages',
  140. component: () => import('@/views/GuidePackages.vue'),
  141. meta: { title: '成长规划师任务模板', requiresTeacher: true, perm: 'biz:packages' }
  142. },
  143. {
  144. path: 'guide-family-task',
  145. name: 'GuideFamilyTask',
  146. component: () => import('@/views/GuideFamilyTask.vue'),
  147. meta: { title: '家庭任务管理', requiresTeacher: true, perm: 'service:families' }
  148. },
  149. {
  150. path: 'teacher-consult',
  151. name: 'TeacherConsult',
  152. component: () => import('@/views/TeacherConsult.vue'),
  153. meta: { title: '家长咨询', requiresTeacher: true, perm: 'assessment:consult' }
  154. },
  155. {
  156. path: 'teacher-families',
  157. name: 'TeacherFamilies',
  158. component: () => import('@/views/teacher/TeacherFamilies.vue'),
  159. meta: { title: '我的家庭', requiresTeacher: true, perm: 'service:families' }
  160. },
  161. {
  162. path: 'teacher-team',
  163. name: 'TeacherTeam',
  164. component: () => import('@/views/teacher/TeacherTeam.vue'),
  165. meta: { title: '我的团队', requiresTeacher: true, perm: 'service:team' }
  166. },
  167. {
  168. path: 'teacher-packages',
  169. name: 'TeacherPackages',
  170. component: () => import('@/views/teacher/TeacherPackages.vue'),
  171. meta: { title: '任务模板管理', requiresTeacher: true, perm: 'biz:packages' }
  172. },
  173. {
  174. path: 'teacher-orders',
  175. name: 'TeacherOrders',
  176. component: () => import('@/views/teacher/TeacherOrders.vue'),
  177. meta: { title: '订单与佣金', requiresTeacher: true, perm: 'biz:orders' }
  178. },
  179. // 系统管理
  180. {
  181. path: 'users',
  182. name: 'Users',
  183. component: () => import('@/views/Users.vue'),
  184. meta: { title: '用户管理', perm: 'system:users' }
  185. },
  186. {
  187. path: 'review-center',
  188. name: 'ReviewCenter',
  189. component: () => import('@/views/admin/ReviewCenter.vue'),
  190. meta: { title: '审核中心', perm: 'audit' }
  191. },
  192. {
  193. path: 'butler-assignments',
  194. name: 'ButlerAssignments',
  195. component: () => import('@/views/admin/ButlerAssignments.vue'),
  196. meta: { title: '管家分配管理', perm: 'audit' }
  197. },
  198. {
  199. path: 'operation-logs',
  200. name: 'OperationLogs',
  201. component: () => import('@/views/OperationLogs.vue'),
  202. meta: { title: '操作日志', perm: 'system:logs' }
  203. },
  204. {
  205. path: 'menu-manage',
  206. name: 'MenuManage',
  207. component: () => import('@/views/admin/MenuManage.vue'),
  208. meta: { title: '菜单管理', perm: 'system:menu' }
  209. },
  210. // 服务角色申请审核已合并进审核中心,旧路由重定向
  211. {
  212. path: 'service-role-applications',
  213. name: 'ServiceRoleApplications',
  214. redirect: '/review-center?tab=vendor'
  215. },
  216. {
  217. path: 'teacher-messages',
  218. name: 'TeacherMessages',
  219. component: () => import('@/views/teacher/TeacherMessages.vue'),
  220. meta: { title: '消息中心', requiresTeacher: true, perm: 'messages' }
  221. },
  222. {
  223. path: 'teacher-assessment',
  224. name: 'TeacherAssessment',
  225. component: () => import('@/views/teacher/TeacherAssessment.vue'),
  226. meta: { title: 'DAN测评管理', requiresTeacher: true, perm: 'assessment:dan' }
  227. },
  228. {
  229. path: 'teacher-proxy-report',
  230. name: 'TeacherProxyReport',
  231. component: () => import('@/views/teacher/ProxyReport.vue'),
  232. meta: { title: '代上传报告', requiresTeacher: true, perm: 'health:reports' }
  233. },
  234. {
  235. path: 'teacher-health-plan-review',
  236. name: 'TeacherHealthPlanReview',
  237. component: () => import('@/views/teacher/HealthPlanReview.vue'),
  238. meta: { title: '健康方案审核', requiresTeacher: true, perm: 'health:plan:review' }
  239. },
  240. {
  241. path: 'assessment-admin',
  242. name: 'AssessmentAdmin',
  243. component: () => import('@/views/admin/Assessment.vue'),
  244. meta: { title: '测评管理', perm: 'assessment:dan' }
  245. },
  246. {
  247. path: 'assessment-assign',
  248. name: 'AssessmentAssign',
  249. component: () => import('@/views/admin/AssessmentAssign.vue'),
  250. meta: { title: '待分配规划师', perm: 'assessment:dan' }
  251. },
  252. {
  253. path: 'assessment-orders',
  254. name: 'AssessmentOrders',
  255. component: () => import('@/views/admin/AssessmentOrders.vue'),
  256. meta: { title: '测评订单', perm: 'assessment:dan' }
  257. },
  258. {
  259. path: 'dan-execution',
  260. name: 'DanExecution',
  261. component: () => import('@/views/admin/DanExecution.vue'),
  262. meta: { title: 'DAN服务执行', perm: 'assessment:dan' }
  263. },
  264. {
  265. path: 'dan-settlement',
  266. name: 'DanSettlement',
  267. component: () => import('@/views/admin/DanSettlement.vue'),
  268. meta: { title: 'DAN结算管理', perm: 'assessment:dan' }
  269. },
  270. {
  271. path: 'product-manage',
  272. name: 'ProductManage',
  273. component: () => import('@/views/admin/ProductManage.vue'),
  274. meta: { title: '商品管理', perm: 'commerce:products,commerce:self' }
  275. },
  276. {
  277. path: 'inventory',
  278. name: 'InventoryManage',
  279. component: () => import('@/views/admin/InventoryManage.vue'),
  280. meta: { title: '库存管理', perm: 'commerce:products' }
  281. },
  282. {
  283. path: 'inventory/inbound-create',
  284. name: 'InventoryInboundCreate',
  285. component: () => import('@/views/admin/InventoryInboundCreate.vue'),
  286. meta: { title: '创建入库单', perm: 'commerce:products' }
  287. },
  288. {
  289. path: 'product-edit',
  290. name: 'ProductEdit',
  291. component: () => import('@/views/admin/ProductEdit.vue'),
  292. meta: { title: '商品编辑', perm: 'commerce:products' }
  293. },
  294. {
  295. path: 'assessment-products',
  296. name: 'AssessmentProducts',
  297. component: () => import('@/views/admin/AssessmentProducts.vue'),
  298. meta: { title: '测评商品管理', perm: 'commerce:products' }
  299. },
  300. {
  301. path: 'assessment-plan-rules',
  302. name: 'AssessmentPlanRules',
  303. component: () => import('@/views/admin/AssessmentPlanRules.vue'),
  304. meta: { title: '方案生成规则', perm: 'plan:rules' }
  305. },
  306. {
  307. path: 'plan-management',
  308. name: 'PlanManagement',
  309. component: () => import('@/views/admin/PlanManagement.vue'),
  310. meta: { title: '方案管理', perm: 'plan:manage' }
  311. },
  312. {
  313. path: 'category-manage',
  314. name: 'CategoryManage',
  315. component: () => import('@/views/admin/CategoryManage.vue'),
  316. meta: { title: '商品分类', perm: 'commerce:category' }
  317. },
  318. {
  319. path: 'order-manage',
  320. name: 'OrderManage',
  321. component: () => import('@/views/admin/OrderManage.vue'),
  322. meta: { title: '订单管理', perm: 'commerce:orders' }
  323. },
  324. {
  325. path: 'pending-refund',
  326. name: 'PendingRefund',
  327. component: () => import('@/views/admin/PendingRefund.vue'),
  328. meta: { title: '待退款管理', perm: 'commerce:orders' }
  329. },
  330. {
  331. path: 'order-detail/:orderNo',
  332. name: 'OrderDetail',
  333. component: () => import('@/views/admin/OrderDetail.vue'),
  334. meta: { title: '订单详情', perm: 'commerce:orders' }
  335. },
  336. {
  337. path: 'sys-config',
  338. name: 'SysConfig',
  339. component: () => import('@/views/admin/SysConfig.vue'),
  340. meta: { title: '系统配置', perm: 'system:base' }
  341. },
  342. {
  343. path: 'guide-config',
  344. name: 'GuideConfig',
  345. component: () => import('@/views/admin/GuideConfig.vue'),
  346. meta: { title: '引导配置', perm: 'system:base' }
  347. },
  348. {
  349. path: 'guide-popup-config',
  350. name: 'GuidePopupConfig',
  351. component: () => import('@/views/admin/GuidePopupConfig.vue'),
  352. meta: { title: '引导弹窗配置', icon: 'el-icon-s-opportunity' }
  353. },
  354. {
  355. path: 'guide-popup-stats',
  356. name: 'GuidePopupStats',
  357. component: () => import('@/views/admin/GuidePopupStats.vue'),
  358. meta: { title: '引导弹窗数据', icon: 'el-icon-data-analysis' }
  359. },
  360. {
  361. path: 'menu-manage',
  362. name: 'MenuManage',
  363. component: () => import('@/views/admin/MenuManage.vue'),
  364. meta: { title: '菜单管理', perm: 'system:menu' }
  365. },
  366. {
  367. path: 'energy-sandbox',
  368. name: 'EnergySandbox',
  369. component: () => import('@/views/admin/EnergySandbox.vue'),
  370. meta: { title: '五维能量', perm: 'energy' }
  371. },
  372. {
  373. path: 'energy-rule',
  374. name: 'EnergyRuleManagement',
  375. component: () => import('@/views/admin/EnergyRuleManagement.vue'),
  376. meta: { title: '能量规则管理', perm: 'energy' }
  377. },
  378. {
  379. path: 'energy-behavior-config',
  380. name: 'EnergyBehaviorConfig',
  381. component: () => import('@/views/admin/EnergyBehaviorConfig.vue'),
  382. meta: { title: '能量行为配置', perm: 'energy' }
  383. },
  384. {
  385. path: 'challenge-score-config',
  386. name: 'ChallengeScoreConfig',
  387. component: () => import('@/views/admin/ChallengeScoreConfig.vue'),
  388. meta: { title: '挑战评分配置', perm: 'energy' }
  389. },
  390. // ========== 文章管理 ==========
  391. {
  392. path: 'article-categories',
  393. name: 'ArticleCategory',
  394. component: () => import('@/views/admin/ArticleCategory.vue'),
  395. meta: { title: '文章分类', perm: 'articles:categories' }
  396. },
  397. {
  398. path: 'article-manage',
  399. name: 'ArticleManage',
  400. component: () => import('@/views/admin/ArticleManage.vue'),
  401. meta: { title: '文章管理', perm: 'articles:manage' }
  402. },
  403. {
  404. path: 'article-edit',
  405. name: 'ArticleEdit',
  406. component: () => import('@/views/admin/ArticleEdit.vue'),
  407. meta: { title: '文章编辑', perm: 'articles:manage' }
  408. },
  409. {
  410. path: 'comment-review',
  411. name: 'CommentReview',
  412. component: () => import('@/views/admin/CommentReview.vue'),
  413. meta: { title: '评论审核', perm: 'articles:manage' }
  414. },
  415. {
  416. path: 'notice-manage',
  417. name: 'NoticeManage',
  418. component: () => import('@/views/admin/NoticeManage.vue'),
  419. meta: { title: '公告管理', perm: 'admin' }
  420. },
  421. {
  422. path: 'periodic-service-config',
  423. name: 'PeriodicServiceConfig',
  424. component: () => import('@/views/admin/PeriodicServiceConfig.vue'),
  425. meta: { title: '周期性服务配置', perm: 'admin' }
  426. },
  427. {
  428. path: 'knowledge-tags',
  429. name: 'KnowledgeTag',
  430. component: () => import('@/views/admin/KnowledgeTag.vue'),
  431. meta: { title: '知识标签', perm: 'articles:categories' }
  432. },
  433. {
  434. path: 'vector-knowledge',
  435. name: 'VectorKnowledgeBase',
  436. component: () => import('@/views/admin/VectorKnowledgeBase.vue'),
  437. meta: { title: '向量知识库', perm: 'knowledge:base' }
  438. },
  439. {
  440. path: 'virtual-goods-config',
  441. name: 'VirtualGoodsConfig',
  442. component: () => import('@/views/admin/VirtualGoodsConfig.vue'),
  443. meta: { title: '虚拟支付道具', perm: 'config:member' }
  444. },
  445. {
  446. path: 'gates',
  447. name: 'Gates',
  448. component: () => import('@/views/admin/gates.vue'),
  449. meta: { title: '关卡配置', perm: 'system:base' }
  450. },
  451. {
  452. path: 'system-prompt',
  453. name: 'SystemPromptManagement',
  454. component: () => import('@/views/admin/SystemPromptManagement.vue'),
  455. meta: { title: '系统提示词', perm: 'system:base' }
  456. },
  457. // ========== 佣金推荐系统 ==========
  458. {
  459. path: 'product-profit-rate',
  460. name: 'ProductProfitRate',
  461. component: () => import('@/views/admin/ProductProfitRate.vue'),
  462. meta: { title: '产品利润率', perm: 'commerce:profit-rate' }
  463. },
  464. {
  465. path: 'product-ppoint',
  466. name: 'ProductPpointManage',
  467. component: () => import('@/views/admin/ProductPpointManage.vue'),
  468. meta: { title: 'P点配置', perm: 'commerce:ppoint' }
  469. },
  470. {
  471. path: 'cf-value',
  472. name: 'CfValueManage',
  473. component: () => import('@/views/admin/CfValueManage.vue'),
  474. meta: { title: 'CF值管理', perm: 'finance:cf-value' }
  475. },
  476. {
  477. path: 'supply-system',
  478. name: 'SupplySystemList',
  479. component: () => import('@/views/admin/supply-system/List.vue'),
  480. meta: { title: '供应商体系', perm: 'system:supply' }
  481. },
  482. {
  483. path: 'supply-system/create',
  484. name: 'SupplySystemCreate',
  485. component: () => import('@/views/admin/supply-system/Form.vue'),
  486. meta: { title: '新建体系', perm: 'system:supply' }
  487. },
  488. {
  489. path: 'supply-system/:id',
  490. name: 'SupplySystemDetail',
  491. component: () => import('@/views/admin/supply-system/Detail.vue'),
  492. meta: { title: '体系详情', perm: 'system:supply' },
  493. props: true
  494. },
  495. {
  496. path: 'supplier-products',
  497. name: 'SupplierProducts',
  498. component: () => import('@/views/admin/SupplierProductManage.vue'),
  499. meta: { title: '商品管理', perm: 'commerce:product' }
  500. },
  501. {
  502. path: 'supply-system/:id/edit',
  503. name: 'SupplySystemEdit',
  504. component: () => import('@/views/admin/supply-system/Form.vue'),
  505. meta: { title: '编辑体系', perm: 'system:supply' },
  506. props: true
  507. },
  508. {
  509. path: 'supply-hierarchy/change-requests',
  510. name: 'SupplyHierarchyChangeRequests',
  511. component: () => import('@/views/admin/supply-hierarchy/ChangeRequest.vue'),
  512. meta: { title: '更换上级审核', perm: 'system:supply' }
  513. },
  514. // ========== 供应商管理 (supplier_admin) ==========
  515. {
  516. path: 'supply-manage',
  517. name: 'SupplyManage',
  518. component: () => import('@/views/admin/supply-manage/List.vue'),
  519. meta: { title: '供应商管理', perm: 'supply:manage' }
  520. },
  521. {
  522. path: 'supply-manage/create',
  523. name: 'SupplyManageCreate',
  524. component: () => import('@/views/admin/supply-manage/Form.vue'),
  525. meta: { title: '新增供应商', perm: 'supply:manage' }
  526. },
  527. {
  528. path: 'supply-manage/:id',
  529. name: 'SupplyManageDetail',
  530. component: () => import('@/views/admin/supply-manage/Detail.vue'),
  531. meta: { title: '供应商详情', perm: 'supply:manage' },
  532. props: true
  533. },
  534. {
  535. path: 'supply-manage/:id/edit',
  536. name: 'SupplyManageEdit',
  537. component: () => import('@/views/admin/supply-manage/Form.vue'),
  538. meta: { title: '编辑供应商', perm: 'supply:manage' },
  539. props: true
  540. },
  541. {
  542. path: 'zodiac-configs',
  543. name: 'ZodiacConfigs',
  544. component: () => import('@/views/admin/ZodiacConfigs.vue'),
  545. meta: { title: '星座配置', perm: 'config:dimension' }
  546. },
  547. {
  548. path: 'bazi-configs',
  549. name: 'BaziConfigs',
  550. component: () => import('@/views/admin/BaziConfigs.vue'),
  551. meta: { title: '八字配置', perm: 'config:dimension' }
  552. },
  553. {
  554. path: 'blood-type-configs',
  555. name: 'BloodTypeConfigs',
  556. component: () => import('@/views/admin/BloodTypeConfigs.vue'),
  557. meta: { title: '血型配置', perm: 'config:dimension' }
  558. },
  559. {
  560. path: 'innate-portrait-config',
  561. name: 'InnatePortraitConfig',
  562. component: () => import('@/views/admin/InnatePortraitConfig.vue'),
  563. meta: { title: '先天画像权重配置', perm: 'config:dimension' }
  564. },
  565. {
  566. path: 'numsoul-detail-config',
  567. name: 'NumSoulDetailConfig',
  568. component: () => import('@/views/admin/NumSoulDetailConfig.vue'),
  569. meta: { title: '数字能量配置', perm: 'config:dimension' }
  570. },
  571. // ========== 饮食管理 (admin) ==========
  572. {
  573. path: 'foods',
  574. name: 'Foods',
  575. component: () => import('@/views/admin/Foods.vue'),
  576. meta: { title: '食材管理', perm: 'diet:foods' }
  577. },
  578. {
  579. path: 'recipes',
  580. name: 'Recipes',
  581. component: () => import('@/views/admin/Recipes.vue'),
  582. meta: { title: '食谱管理', perm: 'diet:recipes' }
  583. },
  584. {
  585. path: 'my-families',
  586. name: 'MyFamilies',
  587. component: () => import('@/views/teacher/FamilyList.vue'),
  588. meta: { title: '我的家庭', perm: 'service:family' }
  589. },
  590. {
  591. path: 'growth-records',
  592. name: 'GrowthRecords',
  593. component: () => import('@/views/teacher/GrowthRecords.vue'),
  594. meta: { title: '成长记录', perm: 'growth:records' }
  595. },
  596. {
  597. path: 'growth-plans',
  598. name: 'GrowthPlans',
  599. component: () => import('@/views/teacher/GrowthPlans.vue'),
  600. meta: { title: '成长计划', perm: 'growth:plans' }
  601. },
  602. {
  603. path: 'health-indicators',
  604. name: 'HealthIndicators',
  605. component: () => import('@/views/nutritionist/HealthIndicators.vue'),
  606. meta: { title: '健康指标', perm: 'health:indicators' }
  607. },
  608. {
  609. path: 'health-checkins',
  610. name: 'HealthCheckinManage',
  611. component: () => import('@/views/admin/HealthCheckinManage.vue'),
  612. meta: { title: '健康打卡管理', perm: 'health:checkins' }
  613. },
  614. {
  615. path: 'emotion-alert',
  616. name: 'EmotionAlertManage',
  617. component: () => import('@/views/admin/EmotionAlertManage.vue'),
  618. meta: { title: '情绪告警管理', perm: 'health:checkins' }
  619. },
  620. {
  621. path: 'nutrition-mappings',
  622. name: 'NutritionMappings',
  623. component: () => import('@/views/nutritionist/NutritionMappings.vue'),
  624. meta: { title: '营养素映射管理', perm: 'health:mappings' }
  625. },
  626. {
  627. path: 'report-management',
  628. name: 'ReportManagement',
  629. component: () => import('@/views/admin/ReportManagement.vue'),
  630. meta: { title: '报告管理', perm: 'health:reports' }
  631. },
  632. {
  633. path: 'report-management/:id',
  634. name: 'HealthReportEdit',
  635. component: () => import('@/views/admin/HealthReportEdit.vue'),
  636. meta: { title: '报告详情/编辑', perm: 'health:reports' },
  637. props: true
  638. },
  639. {
  640. path: 'report-proxy-upload',
  641. name: 'ProxyReportUpload',
  642. component: () => import('@/views/admin/ProxyReportUpload.vue'),
  643. meta: { title: '代传报告', perm: 'health:reports' }
  644. },
  645. {
  646. path: 'report-parse-status',
  647. name: 'ReportParseStatus',
  648. component: () => import('@/views/admin/ReportParseStatus.vue'),
  649. meta: { title: '解析进度', perm: 'health:reports' }
  650. },
  651. {
  652. path: 'seasonal-foods',
  653. name: 'SeasonalFoods',
  654. component: () => import('@/views/admin/SeasonalFoods.vue'),
  655. meta: { title: '应季食材', perm: 'diet:foods' }
  656. },
  657. {
  658. path: 'activities',
  659. name: 'Activities',
  660. component: () => import('@/views/admin/Activities.vue'),
  661. meta: { title: '活动列表', perm: 'activity:list,activity:self' }
  662. },
  663. {
  664. path: 'activity-edit',
  665. name: 'ActivityEdit',
  666. component: () => import('@/views/admin/ActivityEdit.vue'),
  667. meta: { title: '活动编辑', perm: 'activity:list' }
  668. },
  669. {
  670. path: 'home-banner-manage',
  671. name: 'HomeBannerManage',
  672. component: () => import('@/views/admin/HomeBannerManage.vue'),
  673. meta: { title: '首页广告栏', perm: 'marketing:promotion' }
  674. },
  675. {
  676. path: 'survey-templates',
  677. name: 'SurveyTemplates',
  678. component: () => import('@/views/admin/SurveyTemplates.vue'),
  679. meta: { title: '调研模板管理', perm: 'admin:all' }
  680. },
  681. {
  682. path: 'problem-domain-manage',
  683. name: 'ProblemDomainManage',
  684. component: () => import('@/views/admin/ProblemDomainManage.vue'),
  685. meta: { title: '问题域管理', perm: 'admin:all' }
  686. },
  687. {
  688. path: 'notification-template-manage',
  689. name: 'NotificationTemplateManage',
  690. component: () => import('@/views/admin/NotificationTemplateManage.vue'),
  691. meta: { title: '通知模板管理', perm: 'admin:all' }
  692. },
  693. {
  694. // 旧版活动审核页已由审核中心统一承接
  695. path: 'activity-review',
  696. redirect: '/review-center?tab=activity'
  697. },
  698. {
  699. path: 'activity-registration-review',
  700. name: 'ActivityRegistrationReview',
  701. component: () => import('@/views/admin/ActivityRegistrationReview.vue'),
  702. meta: { title: '活动报名审核', perm: 'activity:review' }
  703. },
  704. {
  705. path: 'poster-template-manage',
  706. name: 'PosterTemplateManage',
  707. component: () => import('@/views/admin/PosterTemplateManage.vue'),
  708. meta: { title: '海报模板管理', perm: 'activity:poster_template' }
  709. },
  710. // ========== 维度配置 + 知识库管理 ==========
  711. {
  712. path: 'coupon',
  713. name: 'CouponManagement',
  714. component: () => import('@/views/admin/CouponManagement'),
  715. meta: { title: '优惠券管理', perm: 'marketing:coupon' }
  716. },
  717. {
  718. path: 'coupon-grant-log',
  719. name: 'CouponGrantLog',
  720. component: () => import('@/views/admin/CouponGrantLog.vue'),
  721. meta: { title: '发券记录', perm: 'marketing:coupon' }
  722. },
  723. {
  724. path: 'discount-rules',
  725. name: 'DiscountRuleManagement',
  726. component: () => import('@/views/admin/DiscountRuleManagement.vue'),
  727. meta: { title: '限时折扣管理', perm: 'marketing:discount' }
  728. },
  729. {
  730. path: 'recommendation-rules',
  731. name: 'RecommendationRules',
  732. component: () => import('@/views/admin/RecommendationRules.vue'),
  733. meta: { title: '商品推荐规则管理', perm: 'config:recommendation' }
  734. },
  735. {
  736. path: 'promotion',
  737. name: 'PromotionManagement',
  738. component: () => import('@/views/admin/PromotionManagement'),
  739. meta: { title: '推广管理', perm: 'marketing:promotion' }
  740. },
  741. {
  742. path: 'family-earnings',
  743. name: 'FamilyEarnings',
  744. component: () => import('@/views/admin/FamilyEarnings.vue'),
  745. meta: { title: '家庭收益管理', perm: 'marketing:promotion' }
  746. },
  747. {
  748. path: 'family-earnings-withdraw',
  749. name: 'FamilyEarningsWithdraw',
  750. component: () => import('@/views/admin/FamilyEarningsWithdraw.vue'),
  751. meta: { title: '家庭收益提现审核', perm: 'audit:withdraw' }
  752. },
  753. {
  754. path: 'dimension-config',
  755. name: 'DimensionConfig',
  756. component: () => import('@/views/admin/dimension'),
  757. meta: { title: '维度配置', perm: 'config:dimension' }
  758. },
  759. {
  760. path: 'knowledge-base',
  761. name: 'KnowledgeBase',
  762. component: () => import('@/views/admin/knowledge'),
  763. meta: { title: '知识库管理', perm: 'knowledge:base' }
  764. },
  765. {
  766. path: 'health-knowledge',
  767. name: 'HealthKnowledge',
  768. component: () => import('@/views/admin/health-knowledge'),
  769. meta: { title: '健康知识库管理', perm: 'knowledge:base' }
  770. },
  771. {
  772. path: 'knowledge-base/unified',
  773. name: 'UnifiedKnowledgeBase',
  774. component: () => import('@/views/admin/unified-knowledge.vue'),
  775. meta: { title: '统一知识库管理', perm: 'knowledge:base' }
  776. },
  777. // ========== 健康维度配置 ==========
  778. {
  779. path: 'health-norm-config',
  780. name: 'HealthNormConfig',
  781. component: () => import('@/views/admin/dimension-config'),
  782. meta: { title: '健康常模配置', perm: 'config:health' }
  783. },
  784. {
  785. path: 'health-data-source',
  786. name: 'HealthDataSource',
  787. component: () => import('@/views/admin/data-source-config'),
  788. meta: { title: '健康数据源配置', perm: 'config:health' }
  789. },
  790. {
  791. path: 'health-energy-config',
  792. name: 'HealthEnergyConfig',
  793. component: () => import('@/views/admin/HealthEnergyConfig'),
  794. meta: { title: '五维能量配置', perm: 'config:health' }
  795. },
  796. {
  797. path: 'energy-config',
  798. name: 'EnergyConfig',
  799. component: () => import('@/views/admin/energy-config/WuxingConfig'),
  800. meta: { title: '五行能量配置', perm: 'config:health' }
  801. },
  802. {
  803. path: 'indicators',
  804. name: 'IndicatorManage',
  805. component: () => import('@/views/admin/indicators'),
  806. meta: { title: '指标管理', perm: 'config:health' }
  807. },
  808. {
  809. path: 'indicator-correlations',
  810. name: 'IndicatorCorrelations',
  811. component: () => import('@/views/admin/IndicatorCorrelations.vue'),
  812. meta: { title: '指标关联配置', perm: 'config:health' }
  813. },
  814. {
  815. path: 'indicator-mappings',
  816. name: 'IndicatorMappings',
  817. component: () => import('@/views/admin/IndicatorMappings.vue'),
  818. meta: { title: '指标映射管理', perm: 'config:health' }
  819. },
  820. // ========== 虚拟服务商团队管理 ==========
  821. {
  822. path: 'virtual-teams',
  823. name: 'VirtualTeamList',
  824. component: () => import('@/views/admin/VirtualTeamList'),
  825. meta: { title: '虚拟团队管理', perm: 'system:base' }
  826. },
  827. {
  828. path: 'virtual-team-detail/:systemId',
  829. name: 'VirtualTeamDetail',
  830. component: () => import('@/views/admin/VirtualTeamDetail'),
  831. meta: { title: '团队成员管理', perm: 'system:base' },
  832. props: true
  833. },
  834. // ========== 电商供应商管理(已合并至供应商体系,路由指向 /supply-system)==========
  835. {
  836. path: 'ecom-supplier',
  837. redirect: '/admin/supply-system',
  838. name: 'EcomSupplierManage',
  839. meta: { title: '电商供应商管理', perm: 'system:supply', hidden: true }
  840. },
  841. {
  842. path: 'badge-manage',
  843. name: 'BadgeManage',
  844. component: () => import('@/views/admin/BadgeManage'),
  845. meta: { title: '勋章管理', perm: 'config:member' }
  846. },
  847. // ========== 报告指纹解析系统 ==========
  848. {
  849. path: 'report-types',
  850. name: 'ReportTypeManagement',
  851. component: () => import('@/views/admin/ReportTypeManagement.vue'),
  852. meta: { title: '报告类型管理', perm: 'config:report-parser' }
  853. },
  854. {
  855. path: 'report-parser-import',
  856. name: 'ReportParserImport',
  857. component: () => import('@/views/admin/ReportParserImport.vue'),
  858. meta: { title: '解析器导入管理', perm: 'config:report-parser' }
  859. },
  860. {
  861. path: 'report-collector-trainer',
  862. name: 'ReportCollectorTrainer',
  863. component: () => import('@/views/admin/ReportCollectorTrainer.vue'),
  864. meta: { title: '报告采集训练', perm: 'config:report-parser' }
  865. },
  866. {
  867. path: 'report-template',
  868. name: 'ReportTemplateManage',
  869. component: () => import('@/views/admin/ReportTemplateManage.vue'),
  870. meta: { title: '报告模板管理', perm: 'config:report-parser' }
  871. },
  872. {
  873. path: 'report-unknown-clusters',
  874. name: 'ReportUnknownCluster',
  875. component: () => import('@/views/admin/ReportUnknownCluster.vue'),
  876. meta: { title: '未知报告审核', perm: 'config:report-parser' }
  877. },
  878. {
  879. path: 'report-auto-learn',
  880. name: 'ReportAutoLearn',
  881. component: () => import('@/views/admin/ReportAutoLearn.vue'),
  882. meta: { title: '报告自学习', perm: 'config:report-parser' }
  883. },
  884. // ========== LangGraph AI 服务管理 ==========
  885. {
  886. path: 'langgraph-admin',
  887. name: 'LangGraphAdmin',
  888. component: () => import('@/views/admin/LangGraphAdmin.vue'),
  889. meta: { title: 'LangGraph 管理', perm: 'config:report-parser' }
  890. },
  891. // ========== 营养产品管理 ==========
  892. {
  893. path: 'nutrition-products',
  894. name: 'NutritionProducts',
  895. component: () => import('@/views/admin/NutritionProducts.vue'),
  896. meta: { title: '营养产品管理', perm: 'health:*' }
  897. },
  898. {
  899. path: 'file-manage',
  900. name: 'FileManage',
  901. component: () => import('@/views/admin/FileManage'),
  902. meta: { title: '文件管理', perm: 'system:base' }
  903. }
  904. ]
  905. }
  906. ]
  907. const router = new VueRouter({
  908. mode: 'history',
  909. base: process.env.BASE_URL,
  910. routes
  911. })
  912. // Token 验证缓存:避免 redirect 链中重复调用 /api/admin-auth/info
  913. let tokenValidationCache = { token: '', timestamp: 0 }
  914. const TOKEN_CACHE_TTL = 30000 // 30秒内不重复验证
  915. async function validateToken(token) {
  916. try {
  917. const baseURL = process.env.VUE_APP_BASE_API || ''
  918. const res = await axios.post(`${baseURL}/api/admin-auth/info`, {}, {
  919. headers: { Authorization: `Bearer ${token}` },
  920. validateStatus: () => true
  921. })
  922. return res.status === 200 && res.data?.code === 200
  923. } catch (e) {
  924. return false
  925. }
  926. }
  927. router.beforeEach(async (to, from, next) => {
  928. const token = localStorage.getItem('token')
  929. const role = localStorage.getItem('role')
  930. // 1. 登录页允许访问
  931. if (to.path === '/login') {
  932. // 如果已有token和role,直接跳转到对应角色首页
  933. if (token && role) {
  934. const rolesStr = localStorage.getItem('roles')
  935. const roleList = rolesStr ? JSON.parse(rolesStr) : [role]
  936. const home = getRoleHomePage(roleList)
  937. if (home !== '/') {
  938. return next(home)
  939. }
  940. }
  941. return next()
  942. }
  943. // 2. 无Token强制跳转
  944. if (!token) {
  945. return next('/login')
  946. }
  947. // 3. Token有效性验证 (使用缓存避免 redirect 链重复调用)
  948. const now = Date.now()
  949. let isValid = false
  950. if (tokenValidationCache.token === token && (now - tokenValidationCache.timestamp) < TOKEN_CACHE_TTL) {
  951. isValid = true
  952. } else {
  953. isValid = await validateToken(token)
  954. if (isValid) {
  955. tokenValidationCache = { token, timestamp: now }
  956. }
  957. }
  958. if (!isValid) {
  959. tokenValidationCache = { token: '', timestamp: 0 }
  960. localStorage.clear()
  961. return next('/login')
  962. }
  963. // 3.5 入驻角色访问 /dashboard → 跳转各自工作台(B-6 入驻角色默认首页)
  964. if (to.path === '/dashboard') {
  965. const rolesStr = localStorage.getItem('roles')
  966. const roleList = rolesStr ? JSON.parse(rolesStr) : [role]
  967. const home = getRoleHomePage(roleList)
  968. if (home !== '/') {
  969. return next(home)
  970. }
  971. }
  972. // 4. 权限检查:如果路由有 perm 要求,检查用户权限
  973. const requiredPerm = to.meta && to.meta.perm
  974. if (requiredPerm) {
  975. const rolesStr = localStorage.getItem('roles')
  976. const roles = rolesStr ? JSON.parse(rolesStr) : [localStorage.getItem('role')]
  977. const perms = getEffectivePermissions(roles)
  978. if (!hasPermission(perms, requiredPerm)) {
  979. console.warn('Access denied: insufficient permissions for', to.name)
  980. return next({ path: '/403', replace: true })
  981. }
  982. }
  983. return next()
  984. })
  985. // Suppress navigation guard redirect errors (teacher/admin route cross-access)
  986. const originalPush = VueRouter.prototype.push
  987. VueRouter.prototype.push = function push(location) {
  988. return originalPush.call(this, location).catch(err => err)
  989. }
  990. export default router