menu.ts 7.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227
  1. import type { Menu, Route } from '#/global'
  2. import type { RouteRecordRaw } from 'vue-router'
  3. import menu from '@/menu'
  4. import { resolveRoutePath } from '@/utils'
  5. import { cloneDeep } from 'lodash-es'
  6. import useRouteStore from './route'
  7. import useSettingsStore from './settings'
  8. import useUserStore from './user'
  9. const useMenuStore = defineStore(
  10. // 唯一ID
  11. 'menu',
  12. () => {
  13. const settingsStore = useSettingsStore()
  14. const userStore = useUserStore()
  15. const routeStore = useRouteStore()
  16. const filesystemMenusRaw = ref<Menu.recordMainRaw[]>([])
  17. const actived = ref(0)
  18. // 将原始路由转换成导航菜单
  19. function convertRouteToMenu(routes: Route.recordMainRaw[]): Menu.recordMainRaw[] {
  20. const returnMenus: Menu.recordMainRaw[] = []
  21. routes.forEach((item) => {
  22. if (item.children.length > 0) {
  23. if (settingsStore.settings.menu.mode === 'single') {
  24. returnMenus.length === 0 && returnMenus.push({
  25. meta: {},
  26. children: [],
  27. })
  28. returnMenus[0].children.push(...convertRouteToMenuRecursive(item.children))
  29. }
  30. else {
  31. const menuItem: Menu.recordMainRaw = {
  32. meta: {
  33. title: item?.meta?.title,
  34. icon: item?.meta?.icon,
  35. auth: item?.meta?.auth,
  36. },
  37. children: [],
  38. }
  39. menuItem.children = convertRouteToMenuRecursive(item.children)
  40. returnMenus.push(menuItem)
  41. }
  42. }
  43. })
  44. return returnMenus
  45. }
  46. function convertRouteToMenuRecursive(routes: RouteRecordRaw[], basePath = ''): Menu.recordRaw[] {
  47. const returnMenus: Menu.recordRaw[] = []
  48. routes.forEach((item) => {
  49. const menuItem: Menu.recordRaw = {
  50. path: resolveRoutePath(basePath, item.path),
  51. meta: {
  52. title: item?.meta?.title,
  53. icon: item?.meta?.icon,
  54. defaultOpened: item?.meta?.defaultOpened,
  55. auth: item?.meta?.auth,
  56. menu: item?.meta?.menu,
  57. link: item?.meta?.link,
  58. },
  59. }
  60. if (item.children) {
  61. menuItem.children = convertRouteToMenuRecursive(item.children, menuItem.path)
  62. }
  63. returnMenus.push(menuItem)
  64. })
  65. return returnMenus
  66. }
  67. // 完整导航数据
  68. const allMenus = computed(() => {
  69. let returnMenus: Menu.recordMainRaw[] = []
  70. if (settingsStore.settings.app.routeBaseOn !== 'filesystem') {
  71. returnMenus = convertRouteToMenu(routeStore.routesRaw)
  72. }
  73. else {
  74. returnMenus = filesystemMenusRaw.value
  75. }
  76. // 如果权限功能开启,则需要对导航数据进行筛选过滤
  77. if (settingsStore.settings.app.enablePermission) {
  78. returnMenus = filterAsyncMenus(returnMenus, userStore.permissions)
  79. }
  80. return returnMenus
  81. })
  82. // 次导航数据
  83. const sidebarMenus = computed<Menu.recordMainRaw['children']>(() => {
  84. return allMenus.value.length > 0
  85. ? allMenus.value.length > 1
  86. ? allMenus.value[actived.value].children
  87. : allMenus.value[0].children
  88. : []
  89. })
  90. // 次导航第一层最深路径
  91. const sidebarMenusFirstDeepestPath = computed(() => {
  92. return sidebarMenus.value.length > 0
  93. ? getDeepestPath(sidebarMenus.value[0])
  94. : settingsStore.settings.home.fullPath
  95. })
  96. function getDeepestPath(menu: Menu.recordRaw, rootPath = '') {
  97. let retnPath = ''
  98. if (menu.children) {
  99. const item = menu.children.find(item => item.meta?.menu !== false)
  100. if (item) {
  101. retnPath = getDeepestPath(item, resolveRoutePath(rootPath, menu.path))
  102. }
  103. else {
  104. retnPath = getDeepestPath(menu.children[0], resolveRoutePath(rootPath, menu.path))
  105. }
  106. }
  107. else {
  108. retnPath = resolveRoutePath(rootPath, menu.path)
  109. }
  110. return retnPath
  111. }
  112. // 默认展开的导航路径
  113. const defaultOpenedPaths = computed(() => {
  114. const defaultOpenedPaths: string[] = []
  115. if (settingsStore.settings.app.routeBaseOn !== 'filesystem') {
  116. allMenus.value.forEach((item) => {
  117. defaultOpenedPaths.push(...getDefaultOpenedPaths(item.children))
  118. })
  119. }
  120. return defaultOpenedPaths
  121. })
  122. function getDefaultOpenedPaths(menus: Menu.recordRaw[], rootPath = '') {
  123. const defaultOpenedPaths: string[] = []
  124. menus.forEach((item) => {
  125. if (item.meta?.defaultOpened && item.children) {
  126. defaultOpenedPaths.push(resolveRoutePath(rootPath, item.path))
  127. const childrenDefaultOpenedPaths = getDefaultOpenedPaths(item.children, resolveRoutePath(rootPath, item.path))
  128. if (childrenDefaultOpenedPaths.length > 0) {
  129. defaultOpenedPaths.push(...childrenDefaultOpenedPaths)
  130. }
  131. }
  132. })
  133. return defaultOpenedPaths
  134. }
  135. // 判断是否有权限
  136. function hasPermission(permissions: string[], menu: Menu.recordMainRaw | Menu.recordRaw) {
  137. let isAuth = false
  138. if (menu.meta?.auth) {
  139. isAuth = permissions?.some((auth) => {
  140. if (typeof menu.meta?.auth === 'string') {
  141. return menu.meta.auth !== '' ? menu.meta.auth === auth : true
  142. }
  143. else if (typeof menu.meta?.auth === 'object') {
  144. return menu.meta.auth.length > 0 ? menu.meta.auth.includes(auth) : true
  145. }
  146. else {
  147. return false
  148. }
  149. })
  150. }
  151. else {
  152. isAuth = true
  153. }
  154. return isAuth
  155. }
  156. // 根据权限过滤导航
  157. function filterAsyncMenus<T extends Menu.recordMainRaw[] | Menu.recordRaw[]>(menus: T, permissions: string[]): T {
  158. const res: any = []
  159. menus.forEach((menu) => {
  160. if (hasPermission(permissions, menu)) {
  161. const tmpMenu = cloneDeep(menu)
  162. if (tmpMenu.children && tmpMenu.children.length > 0) {
  163. tmpMenu.children = filterAsyncMenus(tmpMenu.children, permissions) as Menu.recordRaw[]
  164. tmpMenu.children.length > 0 && res.push(tmpMenu)
  165. }
  166. else {
  167. delete tmpMenu.children
  168. res.push(tmpMenu)
  169. }
  170. }
  171. })
  172. return res
  173. }
  174. // 生成导航(前端生成)
  175. async function generateMenusAtFront() {
  176. filesystemMenusRaw.value = menu.filter(item => item.children.length !== 0)
  177. }
  178. // 生成导航(后端生成)
  179. async function generateMenusAtBack() {
  180. // await api.appApi.menuList().then(async (res) => {
  181. // filesystemMenusRaw.value = (res.data as Menu.recordMainRaw[]).filter(item => item.children.length !== 0)
  182. // }).catch(() => {})
  183. }
  184. // 设置主导航
  185. function isPathInMenus(menus: Menu.recordRaw[], path: string) {
  186. let flag = false
  187. flag = menus.some((item) => {
  188. if (item.children) {
  189. return isPathInMenus(item.children, path)
  190. }
  191. return path.indexOf(`${item.path}/`) === 0 || path === item.path
  192. })
  193. return flag
  194. }
  195. function setActived(data: number | string) {
  196. if (typeof data === 'number') {
  197. // 如果是 number 类型,则认为是主导航的索引
  198. actived.value = data
  199. }
  200. else {
  201. // 如果是 string 类型,则认为是路由,需要查找对应的主导航索引
  202. const findIndex = allMenus.value.findIndex(item => isPathInMenus(item.children, data))
  203. if (findIndex >= 0) {
  204. actived.value = findIndex
  205. }
  206. }
  207. }
  208. return {
  209. actived,
  210. allMenus,
  211. sidebarMenus,
  212. sidebarMenusFirstDeepestPath,
  213. defaultOpenedPaths,
  214. generateMenusAtFront,
  215. generateMenusAtBack,
  216. setActived,
  217. }
  218. },
  219. )
  220. export default useMenuStore