Commit 48fcd7684cabff66e8648b71527c6cb4ce7d03be
Committed by
GitHub
1 parent
791b323d
feat(param-menu): feature: menu with params (#845)
Showing
8 changed files
with
153 additions
and
0 deletions
src/locales/lang/en/routes/demo.ts
... | ... | @@ -81,6 +81,9 @@ export default { |
81 | 81 | tab: 'Tab with parameters', |
82 | 82 | tab1: 'Tab with parameter 1', |
83 | 83 | tab2: 'Tab with parameter 2', |
84 | + menu: 'Menu with parameters', | |
85 | + menu1: 'Menu with parameters 1', | |
86 | + menu2: 'Menu with parameters 2', | |
84 | 87 | |
85 | 88 | ws: 'Websocket test', |
86 | 89 | ... | ... |
src/locales/lang/zh_CN/routes/demo.ts
src/router/guard/index.ts
... | ... | @@ -11,6 +11,7 @@ import { createPermissionGuard } from './permissionGuard'; |
11 | 11 | import { createStateGuard } from './stateGuard'; |
12 | 12 | import nProgress from 'nprogress'; |
13 | 13 | import projectSetting from '/@/settings/projectSetting'; |
14 | +import { createParamMenuGuard } from './paramMenuGuard'; | |
14 | 15 | |
15 | 16 | // Don't change the order of creation |
16 | 17 | export function setupRouterGuard(router: Router) { |
... | ... | @@ -21,6 +22,7 @@ export function setupRouterGuard(router: Router) { |
21 | 22 | createMessageGuard(router); |
22 | 23 | createProgressGuard(router); |
23 | 24 | createPermissionGuard(router); |
25 | + createParamMenuGuard(router); // must after createPermissionGuard (menu has been built.) | |
24 | 26 | createStateGuard(router); |
25 | 27 | } |
26 | 28 | ... | ... |
src/router/guard/paramMenuGuard.ts
0 → 100644
1 | +import type { Router } from 'vue-router'; | |
2 | +import { configureDynamicParamsMenu } from '../helper/menuHelper'; | |
3 | +import { Menu } from '../types'; | |
4 | +import { PermissionModeEnum } from '/@/enums/appEnum'; | |
5 | +import { useAppStoreWithOut } from '/@/store/modules/app'; | |
6 | + | |
7 | +import { usePermissionStoreWithOut } from '/@/store/modules/permission'; | |
8 | + | |
9 | +export function createParamMenuGuard(router: Router) { | |
10 | + const permissionStore = usePermissionStoreWithOut(); | |
11 | + router.beforeEach(async (to, _, next) => { | |
12 | + // filter no name route | |
13 | + if (!to.name) { | |
14 | + next(); | |
15 | + return; | |
16 | + } | |
17 | + | |
18 | + // menu has been built. | |
19 | + if (!permissionStore.getIsDynamicAddedRoute) { | |
20 | + next(); | |
21 | + return; | |
22 | + } | |
23 | + | |
24 | + let menus: Menu[] = []; | |
25 | + if (isBackMode()) { | |
26 | + menus = permissionStore.getBackMenuList; | |
27 | + } else if (isRouteMappingMode()) { | |
28 | + menus = permissionStore.getFrontMenuList; | |
29 | + } | |
30 | + menus.forEach((item) => configureDynamicParamsMenu(item, to.params)); | |
31 | + | |
32 | + next(); | |
33 | + }); | |
34 | +} | |
35 | + | |
36 | +const getPermissionMode = () => { | |
37 | + const appStore = useAppStoreWithOut(); | |
38 | + return appStore.getProjectConfig.permissionMode; | |
39 | +}; | |
40 | + | |
41 | +const isBackMode = () => { | |
42 | + return getPermissionMode() === PermissionModeEnum.BACK; | |
43 | +}; | |
44 | + | |
45 | +const isRouteMappingMode = () => { | |
46 | + return getPermissionMode() === PermissionModeEnum.ROUTE_MAPPING; | |
47 | +}; | ... | ... |
src/router/helper/menuHelper.ts
... | ... | @@ -3,6 +3,8 @@ import type { MenuModule, Menu, AppRouteRecordRaw } from '/@/router/types'; |
3 | 3 | import { findPath, treeMap } from '/@/utils/helper/treeHelper'; |
4 | 4 | import { cloneDeep } from 'lodash-es'; |
5 | 5 | import { isUrl } from '/@/utils/is'; |
6 | +import { RouteParams } from 'vue-router'; | |
7 | +import { toRaw } from 'vue'; | |
6 | 8 | |
7 | 9 | export function getAllParentPath<T = Recordable>(treeData: T[], path: string) { |
8 | 10 | const menuList = findPath(treeData, (n) => n.path === path) as Menu[]; |
... | ... | @@ -66,3 +68,25 @@ export function transformRouteToMenu(routeModList: AppRouteModule[], routerMappi |
66 | 68 | joinParentPath(list); |
67 | 69 | return cloneDeep(list); |
68 | 70 | } |
71 | + | |
72 | +/** | |
73 | + * config menu with given params | |
74 | + */ | |
75 | +const menuParamRegex = /(?<=:)([\s\S]+?)((?=\/)|$)/g; | |
76 | +export function configureDynamicParamsMenu(menu: Menu, params: RouteParams) { | |
77 | + const { path, paramPath } = toRaw(menu); | |
78 | + let realPath = paramPath ? paramPath : path; | |
79 | + const matchArr = realPath.match(menuParamRegex); | |
80 | + matchArr?.forEach((it) => { | |
81 | + if (params[it]) { | |
82 | + realPath = realPath.replace(`:${it}`, params[it] as string); | |
83 | + } | |
84 | + }); | |
85 | + // save original param path. | |
86 | + if (!paramPath && matchArr && matchArr.length > 0) { | |
87 | + menu.paramPath = path; | |
88 | + } | |
89 | + menu.path = realPath; | |
90 | + // children | |
91 | + menu.children?.forEach((item) => configureDynamicParamsMenu(item, params)); | |
92 | +} | ... | ... |
src/router/routes/modules/demo/feat.ts
... | ... | @@ -265,6 +265,35 @@ const feat: AppRouteModule = { |
265 | 265 | }, |
266 | 266 | ], |
267 | 267 | }, |
268 | + { | |
269 | + path: 'testParam/:id', | |
270 | + name: 'TestParam', | |
271 | + component: getParentLayout('TestParam'), | |
272 | + meta: { | |
273 | + title: t('routes.demo.feat.menu'), | |
274 | + ignoreKeepAlive: true, | |
275 | + }, | |
276 | + children: [ | |
277 | + { | |
278 | + path: 'sub1', | |
279 | + name: 'TestParam_1', | |
280 | + component: () => import('/@/views/demo/feat/menu-params/index.vue'), | |
281 | + meta: { | |
282 | + title: t('routes.demo.feat.menu1'), | |
283 | + ignoreKeepAlive: true, | |
284 | + }, | |
285 | + }, | |
286 | + { | |
287 | + path: 'sub2', | |
288 | + name: 'TestParam_2', | |
289 | + component: () => import('/@/views/demo/feat/menu-params/index.vue'), | |
290 | + meta: { | |
291 | + title: t('routes.demo.feat.menu2'), | |
292 | + ignoreKeepAlive: true, | |
293 | + }, | |
294 | + }, | |
295 | + ], | |
296 | + }, | |
268 | 297 | ], |
269 | 298 | }; |
270 | 299 | ... | ... |
src/router/types.ts
src/views/demo/feat/menu-params/index.vue
0 → 100644
1 | +<template> | |
2 | + <PageWrapper title="带参数菜单(路由)" content="支持多级参数"> | |
3 | + 当前参数:{{ params }} | |
4 | + <br /> | |
5 | + 输入参数切换路由: | |
6 | + <Input v-model:value="value" placeholder="建议为url标准字符,输入后点击切换" /> | |
7 | + <a-button type="primary" @click="handleClickGo">切换路由</a-button> | |
8 | + <br /> | |
9 | + 切换路由后 | |
10 | + <ul> | |
11 | + <li>可刷新页面测试路由参数情况是否正常。</li> | |
12 | + <li>可于左侧菜单中展开子菜单,点击测试参数是否携带正常。</li> | |
13 | + </ul> | |
14 | + </PageWrapper> | |
15 | +</template> | |
16 | +<script lang="ts"> | |
17 | + import { Input } from 'ant-design-vue'; | |
18 | + import { computed, defineComponent, ref, unref } from 'vue'; | |
19 | + import { useRouter } from 'vue-router'; | |
20 | + import { PageWrapper } from '/@/components/Page'; | |
21 | + | |
22 | + export default defineComponent({ | |
23 | + name: 'TestMenu', | |
24 | + components: { PageWrapper, Input }, | |
25 | + setup() { | |
26 | + const { currentRoute, replace } = useRouter(); | |
27 | + const value = ref<string>(''); | |
28 | + | |
29 | + const handleClickGo = () => { | |
30 | + const { name } = unref(currentRoute); | |
31 | + replace({ name: name!, params: { id: unref(value) } }); | |
32 | + }; | |
33 | + return { | |
34 | + value, | |
35 | + handleClickGo, | |
36 | + params: computed(() => { | |
37 | + return unref(currentRoute).params; | |
38 | + }), | |
39 | + }; | |
40 | + }, | |
41 | + }); | |
42 | +</script> | ... | ... |