menu.ts 7.6 KB

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