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,6 +81,9 @@ export default { | ||
81 | tab: 'Tab with parameters', | 81 | tab: 'Tab with parameters', |
82 | tab1: 'Tab with parameter 1', | 82 | tab1: 'Tab with parameter 1', |
83 | tab2: 'Tab with parameter 2', | 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 | ws: 'Websocket test', | 88 | ws: 'Websocket test', |
86 | 89 |
src/locales/lang/zh_CN/routes/demo.ts
@@ -80,6 +80,9 @@ export default { | @@ -80,6 +80,9 @@ export default { | ||
80 | tab: 'Tab带参', | 80 | tab: 'Tab带参', |
81 | tab1: 'Tab带参1', | 81 | tab1: 'Tab带参1', |
82 | tab2: 'Tab带参2', | 82 | tab2: 'Tab带参2', |
83 | + menu: 'Menu带参', | ||
84 | + menu1: 'Menu带参1', | ||
85 | + menu2: 'Menu带参2', | ||
83 | ws: 'websocket测试', | 86 | ws: 'websocket测试', |
84 | breadcrumb: '面包屑导航', | 87 | breadcrumb: '面包屑导航', |
85 | breadcrumbFlat: '平级模式', | 88 | breadcrumbFlat: '平级模式', |
src/router/guard/index.ts
@@ -11,6 +11,7 @@ import { createPermissionGuard } from './permissionGuard'; | @@ -11,6 +11,7 @@ import { createPermissionGuard } from './permissionGuard'; | ||
11 | import { createStateGuard } from './stateGuard'; | 11 | import { createStateGuard } from './stateGuard'; |
12 | import nProgress from 'nprogress'; | 12 | import nProgress from 'nprogress'; |
13 | import projectSetting from '/@/settings/projectSetting'; | 13 | import projectSetting from '/@/settings/projectSetting'; |
14 | +import { createParamMenuGuard } from './paramMenuGuard'; | ||
14 | 15 | ||
15 | // Don't change the order of creation | 16 | // Don't change the order of creation |
16 | export function setupRouterGuard(router: Router) { | 17 | export function setupRouterGuard(router: Router) { |
@@ -21,6 +22,7 @@ export function setupRouterGuard(router: Router) { | @@ -21,6 +22,7 @@ export function setupRouterGuard(router: Router) { | ||
21 | createMessageGuard(router); | 22 | createMessageGuard(router); |
22 | createProgressGuard(router); | 23 | createProgressGuard(router); |
23 | createPermissionGuard(router); | 24 | createPermissionGuard(router); |
25 | + createParamMenuGuard(router); // must after createPermissionGuard (menu has been built.) | ||
24 | createStateGuard(router); | 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,6 +3,8 @@ import type { MenuModule, Menu, AppRouteRecordRaw } from '/@/router/types'; | ||
3 | import { findPath, treeMap } from '/@/utils/helper/treeHelper'; | 3 | import { findPath, treeMap } from '/@/utils/helper/treeHelper'; |
4 | import { cloneDeep } from 'lodash-es'; | 4 | import { cloneDeep } from 'lodash-es'; |
5 | import { isUrl } from '/@/utils/is'; | 5 | import { isUrl } from '/@/utils/is'; |
6 | +import { RouteParams } from 'vue-router'; | ||
7 | +import { toRaw } from 'vue'; | ||
6 | 8 | ||
7 | export function getAllParentPath<T = Recordable>(treeData: T[], path: string) { | 9 | export function getAllParentPath<T = Recordable>(treeData: T[], path: string) { |
8 | const menuList = findPath(treeData, (n) => n.path === path) as Menu[]; | 10 | const menuList = findPath(treeData, (n) => n.path === path) as Menu[]; |
@@ -66,3 +68,25 @@ export function transformRouteToMenu(routeModList: AppRouteModule[], routerMappi | @@ -66,3 +68,25 @@ export function transformRouteToMenu(routeModList: AppRouteModule[], routerMappi | ||
66 | joinParentPath(list); | 68 | joinParentPath(list); |
67 | return cloneDeep(list); | 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,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
@@ -31,6 +31,9 @@ export interface Menu { | @@ -31,6 +31,9 @@ export interface Menu { | ||
31 | 31 | ||
32 | path: string; | 32 | path: string; |
33 | 33 | ||
34 | + // path contains param, auto assignment. | ||
35 | + paramPath?: string; | ||
36 | + | ||
34 | disabled?: boolean; | 37 | disabled?: boolean; |
35 | 38 | ||
36 | children?: Menu[]; | 39 | children?: Menu[]; |
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> |