index.ts 7.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223
  1. import { createRouter, createWebHistory } from 'vue-router'
  2. import { useUserStore } from '@/stores/user-store'
  3. import { getToken } from '@/utils/auth'
  4. const router = createRouter({
  5. history: createWebHistory(),
  6. routes: [
  7. {
  8. path: '/login',
  9. name: 'Login',
  10. component: () => import('@/views/login/index.vue'),
  11. meta: { hidden: true }
  12. },
  13. {
  14. path: '/profile',
  15. component: () => import('@/components/Layout/index.vue'),
  16. meta: { hidden: true },
  17. children: [
  18. {
  19. path: '',
  20. name: 'Profile',
  21. component: () => import('@/views/profile/index.vue'),
  22. meta: { title: '个人中心' }
  23. }
  24. ]
  25. },
  26. {
  27. path: '/:pathMatch(.*)*',
  28. name: 'NotFound',
  29. component: () => import('@/views/error/404.vue'),
  30. meta: { hidden: true }
  31. },
  32. {
  33. path: '/',
  34. component: () => import('@/components/Layout/index.vue'),
  35. redirect: '/dashboard',
  36. children: [
  37. {
  38. path: 'dashboard',
  39. name: 'Dashboard',
  40. component: () => import('@/views/dashboard/index.vue'),
  41. meta: { title: '首页', icon: 'HomeFilled' }
  42. }
  43. ]
  44. },
  45. {
  46. path: '/workbench',
  47. component: () => import('@/components/Layout/index.vue'),
  48. meta: { title: '工作台', icon: 'Monitor' },
  49. children: [
  50. {
  51. path: '',
  52. name: 'Workbench',
  53. component: () => import('@/views/workbench/index.vue'),
  54. meta: { title: '智能工作台', icon: 'Monitor' }
  55. }
  56. ]
  57. },
  58. {
  59. path: '/analysis',
  60. component: () => import('@/components/Layout/index.vue'),
  61. redirect: '/analysis/flow',
  62. meta: { title: '数据看板', icon: 'TrendCharts' },
  63. children: [
  64. {
  65. path: 'flow',
  66. name: 'FlowAnalysis',
  67. component: () => import('@/views/analysis/index.vue'),
  68. meta: { title: '流程分析看板', icon: 'TrendCharts' }
  69. }
  70. ]
  71. },
  72. {
  73. path: '/system',
  74. component: () => import('@/components/Layout/index.vue'),
  75. redirect: '/system/user',
  76. meta: { title: '系统管理', icon: 'Setting' },
  77. children: [
  78. {
  79. path: 'user',
  80. name: 'User',
  81. component: () => import('@/views/system/user/index.vue'),
  82. meta: { title: '用户管理', icon: 'UserFilled' }
  83. },
  84. {
  85. path: 'role',
  86. name: 'Role',
  87. component: () => import('@/views/system/role/index.vue'),
  88. meta: { title: '审批角色', icon: 'User' }
  89. },
  90. ]
  91. },
  92. {
  93. path: '/flow',
  94. component: () => import('@/components/Layout/index.vue'),
  95. redirect: '/flow/definition',
  96. meta: { title: '流程管理', icon: 'Connection' },
  97. children: [
  98. {
  99. path: 'designer',
  100. name: 'FlowDesigner',
  101. component: () => import('@/views/flow/designer/index.vue'),
  102. meta: { title: '流程设计器', icon: 'EditPen' }
  103. },
  104. {
  105. path: 'definition',
  106. name: 'FlowDefinition',
  107. component: () => import('@/views/flow/definition/index.vue'),
  108. meta: { title: '流程管理', icon: 'Document' }
  109. }
  110. ]
  111. },
  112. {
  113. path: '/approval',
  114. component: () => import('@/components/Layout/index.vue'),
  115. redirect: '/approval/todo',
  116. meta: { title: '审批中心', icon: 'Stamp' },
  117. children: [
  118. {
  119. path: 'todo',
  120. name: 'Todo',
  121. component: () => import('@/views/flow/task/todo.vue'),
  122. meta: { title: '我的待办', icon: 'Bell' }
  123. },
  124. {
  125. path: 'handled',
  126. name: 'Handled',
  127. component: () => import('@/views/flow/task/handled.vue'),
  128. meta: { title: '我的已办', icon: 'CircleCheck' }
  129. },
  130. {
  131. path: 'cc',
  132. name: 'Cc',
  133. component: () => import('@/views/flow/task/cc.vue'),
  134. meta: { title: '我的抄送', icon: 'Message' }
  135. },
  136. {
  137. path: 'mine',
  138. name: 'MyInstance',
  139. component: () => import('@/views/flow/instance/mine.vue'),
  140. meta: { title: '我的流程', icon: 'List' }
  141. },
  142. {
  143. path: 'execute',
  144. name: 'FlowExecute',
  145. component: () => import('@/views/flow/execute/index.vue'),
  146. meta: { title: '流程执行', icon: 'Pointer' }
  147. },
  148. {
  149. path: 'all',
  150. name: 'AllInstance',
  151. component: () => import('@/views/flow/instance/all.vue'),
  152. meta: { title: '流程明细', icon: 'Document' }
  153. }
  154. ]
  155. }
  156. ]
  157. })
  158. const publicPaths = new Set(['/login'])
  159. // 按员工类型限制的路径
  160. const permissionPaths: Record<string, Set<string>> = {
  161. common_user: new Set(['/', '/dashboard', '/login', '/profile', '/approval/todo', '/approval/handled', '/approval/mine', '/approval/execute', '/approval/cc']),
  162. dept_manager: new Set(['/', '/dashboard', '/login', '/profile', '/system/user', '/system/role', '/approval/todo', '/approval/handled', '/approval/mine', '/approval/execute', '/approval/cc']),
  163. flow_manager: new Set(['/', '/dashboard', '/login', '/profile', '/flow/designer', '/flow/definition', '/flow/instance/all', '/approval/todo', '/approval/handled', '/approval/mine', '/approval/execute', '/approval/cc', '/analysis']),
  164. super_admin: new Set(['/', '/dashboard', '/login', '/profile', '/system/user', '/system/role', '/flow/designer', '/flow/definition', '/flow/instance/all', '/approval/todo', '/approval/handled', '/approval/mine', '/approval/execute', '/approval/cc', '/analysis'])
  165. }
  166. function isPathAllowed(allowedSet: Set<string>, path: string): boolean {
  167. if (allowedSet.has(path)) return true
  168. // 允许子路径,但父路径必须以 / 结尾或精确匹配
  169. for (const allowed of allowedSet) {
  170. if (allowed !== '/' && path.startsWith(allowed + '/')) {
  171. return true
  172. }
  173. }
  174. return false
  175. }
  176. // 防止快速切换 tab 时并发多次拉取用户信息
  177. let fetchingUserInfo: Promise<unknown> | null = null
  178. router.beforeEach(async (to, from, next) => {
  179. const token = getToken()
  180. // 404、登录页等公开页面直接放行
  181. if (publicPaths.has(to.path) || to.name === 'NotFound') {
  182. next()
  183. return
  184. }
  185. if (token) {
  186. const userStore = useUserStore()
  187. if (!userStore.userInfo) {
  188. try {
  189. if (!fetchingUserInfo) {
  190. fetchingUserInfo = userStore.fetchUserInfo().catch(async (err) => {
  191. await userStore.logoutAction()
  192. throw err
  193. })
  194. }
  195. await fetchingUserInfo
  196. } catch {
  197. next(`/login?redirect=${to.path}`)
  198. return
  199. } finally {
  200. fetchingUserInfo = null
  201. }
  202. }
  203. // 按 employeeType 限制
  204. const employeeType = userStore.userInfo?.employeeType || 'common_user'
  205. const allowedPaths = permissionPaths[employeeType] || permissionPaths['common_user']
  206. if (!isPathAllowed(allowedPaths, to.path)) {
  207. next('/dashboard')
  208. return
  209. }
  210. next()
  211. } else {
  212. next(`/login?redirect=${to.path}`)
  213. }
  214. })
  215. export default router