Commit 31e2715e674fc7d5144f39d8d5e595d904585af8
1 parent
74378960
chore: detail optimization
Showing
27 changed files
with
304 additions
and
93 deletions
build/script/updateHtml.ts
... | ... | @@ -74,9 +74,7 @@ function injectCdnjs(html: string) { |
74 | 74 | export async function runUpdateHtml() { |
75 | 75 | const outDir = viteConfig.outDir || 'dist'; |
76 | 76 | const indexPath = getCwdPath(outDir, 'index.html'); |
77 | - if (!existsSync(`${indexPath}`)) { | |
78 | - return; | |
79 | - } | |
77 | + if (!existsSync(indexPath)) return; | |
80 | 78 | try { |
81 | 79 | let processedHtml = ''; |
82 | 80 | const rawHtml = readFileSync(indexPath, 'utf-8'); |
... | ... | @@ -92,7 +90,9 @@ export async function runUpdateHtml() { |
92 | 90 | } |
93 | 91 | if (minify) { |
94 | 92 | const { enable, ...miniOpt } = minify; |
95 | - processedHtml = HtmlMinifier.minify(processedHtml, miniOpt); | |
93 | + if (enable) { | |
94 | + processedHtml = HtmlMinifier.minify(processedHtml, miniOpt); | |
95 | + } | |
96 | 96 | } |
97 | 97 | |
98 | 98 | writeFileSync(indexPath, processedHtml); | ... | ... |
build/transform/require-context/index.ts
0 → 100644
1 | +// https://github.com/luxueyan/vite-transform-globby-import/blob/master/src/index.ts | |
2 | + | |
3 | +// TODO 目前还不能监听文件新增及删除 内容已经改变,缓存问题? | |
4 | +// 可以使用,先不打算集成 | |
5 | +import { join } from 'path'; | |
6 | +import { lstatSync } from 'fs'; | |
7 | +import glob from 'glob'; | |
8 | +import { createResolver, Resolver } from 'vite/dist/node/resolver.js'; | |
9 | +import { Transform } from 'vite/dist/node/transform.js'; | |
10 | + | |
11 | +const modulesDir: string = join(process.cwd(), '/node_modules/'); | |
12 | + | |
13 | +interface SharedConfig { | |
14 | + root?: string; | |
15 | + alias?: Record<string, string>; | |
16 | + resolvers?: Resolver[]; | |
17 | +} | |
18 | + | |
19 | +function template(template: string) { | |
20 | + return (data: { [x: string]: any }) => { | |
21 | + return template.replace(/#([^#]+)#/g, (_, g1) => data[g1] || g1); | |
22 | + }; | |
23 | +} | |
24 | + | |
25 | +const globbyTransform = function (config: SharedConfig): Transform { | |
26 | + const resolver = createResolver( | |
27 | + config.root || process.cwd(), | |
28 | + config.resolvers || [], | |
29 | + config.alias || {} | |
30 | + ); | |
31 | + const cache = new Map(); | |
32 | + | |
33 | + const urlMap = new Map(); | |
34 | + return { | |
35 | + test({ path }) { | |
36 | + const filePath = path.replace('\u0000', ''); // why some path startsWith '\u0000'? | |
37 | + try { | |
38 | + return ( | |
39 | + !filePath.startsWith(modulesDir) && | |
40 | + /\.(vue|js|jsx|ts|tsx)$/.test(filePath) && | |
41 | + lstatSync(filePath).isFile() | |
42 | + ); | |
43 | + } catch { | |
44 | + return false; | |
45 | + } | |
46 | + }, | |
47 | + transform({ code, path, isBuild }) { | |
48 | + let result = cache.get(path); | |
49 | + if (!result) { | |
50 | + const reg = /import\s+([\w\s{}*]+)\s+from\s+(['"])globby(\?path)?!([^'"]+)\2/g; | |
51 | + const lastImport = urlMap.get(path); | |
52 | + const match = code.match(reg); | |
53 | + if (lastImport && match) { | |
54 | + code = code.replace(lastImport, match[0]); | |
55 | + } | |
56 | + result = code.replace(reg, (_, g1, g2, g3, g4) => { | |
57 | + const filePath = path.replace('\u0000', ''); // why some path startsWith '\u0000'? | |
58 | + // resolve path | |
59 | + const resolvedFilePath = g4.startsWith('.') | |
60 | + ? resolver.resolveRelativeRequest(filePath, g4) | |
61 | + : { pathname: resolver.requestToFile(g4) }; | |
62 | + const files = glob.sync(resolvedFilePath.pathname, { dot: true }); | |
63 | + let templateStr = 'import #name# from #file#'; // import default | |
64 | + let name = g1; | |
65 | + const m = g1.match(/\{\s*(\w+)(\s+as\s+(\w+))?\s*\}/); // import module | |
66 | + const m2 = g1.match(/\*\s+as\s+(\w+)/); // import * as all module | |
67 | + if (m) { | |
68 | + templateStr = `import { ${m[1]} as #name# } from #file#`; | |
69 | + name = m[3] || m[1]; | |
70 | + } else if (m2) { | |
71 | + templateStr = 'import * as #name# from #file#'; | |
72 | + name = m2[1]; | |
73 | + } | |
74 | + const temRender = template(templateStr); | |
75 | + | |
76 | + const groups: Array<string>[] = []; | |
77 | + const replaceFiles = files.map((f, i) => { | |
78 | + const file = g2 + resolver.fileToRequest(f) + g2; | |
79 | + groups.push([name + i, file]); | |
80 | + return temRender({ name: name + i, file }); | |
81 | + }); | |
82 | + urlMap.set(path, replaceFiles.join('\n')); | |
83 | + return ( | |
84 | + replaceFiles.join('\n') + | |
85 | + (g3 ? '\n' + groups.map((v) => `${v[0]}._path = ${v[1]}`).join('\n') : '') + | |
86 | + `\nconst ${name} = { ${groups.map((v) => v[0]).join(',')} }\n` | |
87 | + ); | |
88 | + }); | |
89 | + if (isBuild) cache.set(path, result); | |
90 | + } | |
91 | + return result; | |
92 | + }, | |
93 | + }; | |
94 | +}; | |
95 | +export default globbyTransform; | ... | ... |
build/utils.ts
... | ... | @@ -124,28 +124,24 @@ export function getEnvConfig(match = 'VITE_GLOB_', confFiles = ['.env', '.env.pr |
124 | 124 | return envConfig; |
125 | 125 | } |
126 | 126 | |
127 | -export function successConsole(message: any) { | |
127 | +function consoleFn(color: string, message: any) { | |
128 | 128 | console.log( |
129 | 129 | chalk.blue.bold('**************** ') + |
130 | - chalk.green.bold('✨ ' + message) + | |
130 | + (chalk as any)[color].bold(message) + | |
131 | 131 | chalk.blue.bold(' ****************') |
132 | 132 | ); |
133 | 133 | } |
134 | 134 | |
135 | +export function successConsole(message: any) { | |
136 | + consoleFn('green', '✨ ' + message); | |
137 | +} | |
138 | + | |
135 | 139 | export function errorConsole(message: any) { |
136 | - console.log( | |
137 | - chalk.blue.bold('**************** ') + | |
138 | - chalk.red.bold('✨ ' + message) + | |
139 | - chalk.blue.bold(' ****************') | |
140 | - ); | |
140 | + consoleFn('red', '✨ ' + message); | |
141 | 141 | } |
142 | 142 | |
143 | 143 | export function warnConsole(message: any) { |
144 | - console.log( | |
145 | - chalk.blue.bold('**************** ') + | |
146 | - chalk.yellow.bold('✨ ' + message) + | |
147 | - chalk.blue.bold(' ****************') | |
148 | - ); | |
144 | + consoleFn('yellow', '✨ ' + message); | |
149 | 145 | } |
150 | 146 | |
151 | 147 | export function getCwdPath(...dir: string[]) { | ... | ... |
src/components/CountTo/src/index.vue
... | ... | @@ -5,7 +5,6 @@ |
5 | 5 | </template> |
6 | 6 | <script lang="ts"> |
7 | 7 | import { defineComponent, reactive, computed, watch, onMounted, unref, toRef } from 'vue'; |
8 | - | |
9 | 8 | import { countToProps } from './props'; |
10 | 9 | import { useRaf } from '/@/hooks/event/useRaf'; |
11 | 10 | import { isNumber } from '/@/utils/is'; |
... | ... | @@ -37,12 +36,14 @@ |
37 | 36 | remaining: null, |
38 | 37 | rAF: null, |
39 | 38 | }); |
39 | + | |
40 | 40 | onMounted(() => { |
41 | 41 | if (props.autoplay) { |
42 | 42 | start(); |
43 | 43 | } |
44 | 44 | emit('mounted'); |
45 | 45 | }); |
46 | + | |
46 | 47 | const getCountDown = computed(() => { |
47 | 48 | return props.startVal > props.endVal; |
48 | 49 | }); |
... | ... | @@ -61,6 +62,7 @@ |
61 | 62 | state.paused = false; |
62 | 63 | state.rAF = requestAnimationFrame(count); |
63 | 64 | } |
65 | + | |
64 | 66 | function pauseResume() { |
65 | 67 | if (state.paused) { |
66 | 68 | resume(); |
... | ... | @@ -70,6 +72,7 @@ |
70 | 72 | state.paused = true; |
71 | 73 | } |
72 | 74 | } |
75 | + | |
73 | 76 | function pause() { |
74 | 77 | cancelAnimationFrame(state.rAF); |
75 | 78 | } | ... | ... |
src/components/Menu/src/BasicMenu.tsx
... | ... | @@ -7,7 +7,7 @@ import { Menu } from 'ant-design-vue'; |
7 | 7 | import { MenuModeEnum, MenuTypeEnum } from '/@/enums/menuEnum'; |
8 | 8 | import { menuStore } from '/@/store/modules/menu'; |
9 | 9 | import { getSlot } from '/@/utils/helper/tsxHelper'; |
10 | -import { ScrollContainer } from '/@/components/Container/index'; | |
10 | +// import { ScrollContainer } from '/@/components/Container/index'; | |
11 | 11 | import SearchInput from './SearchInput.vue'; |
12 | 12 | import './index.less'; |
13 | 13 | import { menuHasChildren } from './helper'; |
... | ... | @@ -67,6 +67,7 @@ export default defineComponent({ |
67 | 67 | return { |
68 | 68 | height: `calc(100% - ${offset}px)`, |
69 | 69 | position: 'relative', |
70 | + overflow: 'auto', | |
70 | 71 | }; |
71 | 72 | }); |
72 | 73 | |
... | ... | @@ -246,8 +247,9 @@ export default defineComponent({ |
246 | 247 | onClick={handleInputClick} |
247 | 248 | collapsed={getCollapsedState} |
248 | 249 | /> |
249 | - <section style={unref(getMenuWrapStyle)}> | |
250 | - <ScrollContainer>{() => renderMenu()}</ScrollContainer> | |
250 | + <section style={unref(getMenuWrapStyle)} class="basic-menu__wrap"> | |
251 | + {renderMenu()} | |
252 | + {/* <ScrollContainer>{() => renderMenu()}</ScrollContainer> */} | |
251 | 253 | </section> |
252 | 254 | </section> |
253 | 255 | ); | ... | ... |
src/components/Menu/src/index.less
... | ... | @@ -14,6 +14,30 @@ |
14 | 14 | } |
15 | 15 | |
16 | 16 | .basic-menu { |
17 | + &__wrap { | |
18 | + /* 滚动槽 */ | |
19 | + &::-webkit-scrollbar { | |
20 | + width: 6px; | |
21 | + height: 6px; | |
22 | + } | |
23 | + | |
24 | + // TODO 滚动条样式-待修改 | |
25 | + &::-webkit-scrollbar-track { | |
26 | + background: rgba(0, 0, 0, 0); | |
27 | + } | |
28 | + | |
29 | + /* 滚动条滑块 */ | |
30 | + &::-webkit-scrollbar-thumb { | |
31 | + background: rgba(255, 255, 255, 0.3); | |
32 | + border-radius: 4px; | |
33 | + box-shadow: inset 0 0 10px rgba(0, 0, 0, 0.1); | |
34 | + } | |
35 | + | |
36 | + ::-webkit-scrollbar-thumb:hover { | |
37 | + background: @border-color-dark; | |
38 | + } | |
39 | + } | |
40 | + | |
17 | 41 | .ant-menu-submenu:first-of-type { |
18 | 42 | margin-top: 4px; |
19 | 43 | } |
... | ... | @@ -95,14 +119,14 @@ |
95 | 119 | .ant-menu-submenu-active, |
96 | 120 | .ant-menu-submenu-title:hover { |
97 | 121 | background: @top-menu-active-bg-color; |
98 | - border-radius: 6px 6px 0 0; | |
122 | + // border-radius: 6px 6px 0 0; | |
99 | 123 | } |
100 | 124 | |
101 | 125 | .basic-menu-item__level1 { |
102 | 126 | &.ant-menu-item-selected, |
103 | 127 | &.ant-menu-submenu-selected { |
104 | 128 | background: @top-menu-active-bg-color; |
105 | - border-radius: 6px 6px 0 0; | |
129 | + // border-radius: 6px 6px 0 0; | |
106 | 130 | } |
107 | 131 | } |
108 | 132 | |
... | ... | @@ -148,6 +172,10 @@ |
148 | 172 | } |
149 | 173 | |
150 | 174 | .basic-menu-item__level1 { |
175 | + > .ant-menu-sub > li { | |
176 | + background-color: @sub-menu-item-dark-bg-color; | |
177 | + } | |
178 | + | |
151 | 179 | margin-bottom: 0; |
152 | 180 | |
153 | 181 | &.top-active-menu { |
... | ... | @@ -179,7 +207,7 @@ |
179 | 207 | |
180 | 208 | .ant-menu-submenu-title { |
181 | 209 | height: @app-menu-item-height; |
182 | - margin: 0; | |
210 | + // margin: 0; | |
183 | 211 | line-height: @app-menu-item-height; |
184 | 212 | } |
185 | 213 | } | ... | ... |
src/design/var/index.less
src/hooks/web/useTabs.ts
1 | -import type { AppRouteRecordRaw } from '/@/router/types.d'; | |
2 | 1 | import { useTimeout } from '/@/hooks/core/useTimeout'; |
3 | 2 | import { PageEnum } from '/@/enums/pageEnum'; |
4 | 3 | import { TabItem, tabStore } from '/@/store/modules/tab'; |
5 | 4 | import { appStore } from '/@/store/modules/app'; |
6 | 5 | import router from '/@/router'; |
7 | 6 | import { ref } from 'vue'; |
7 | +import { pathToRegexp } from 'path-to-regexp'; | |
8 | 8 | |
9 | 9 | const activeKeyRef = ref<string>(''); |
10 | 10 | |
... | ... | @@ -68,7 +68,11 @@ export function useTabs() { |
68 | 68 | function getTo(path: string): any { |
69 | 69 | const routes = router.getRoutes(); |
70 | 70 | const fn = (p: string): any => { |
71 | - const to = routes.find((item) => item.path === p); | |
71 | + const to = routes.find((item) => { | |
72 | + if (item.path === '/:path(.*)*') return; | |
73 | + const regexp = pathToRegexp(item.path); | |
74 | + return regexp.test(p); | |
75 | + }); | |
72 | 76 | if (!to) return ''; |
73 | 77 | if (!to.redirect) return to; |
74 | 78 | if (to.redirect) { |
... | ... | @@ -88,12 +92,13 @@ export function useTabs() { |
88 | 92 | resetCache: () => canIUseFn() && resetCache(), |
89 | 93 | addTab: (path: PageEnum, goTo = false, replace = false) => { |
90 | 94 | const to = getTo(path); |
95 | + | |
91 | 96 | if (!to) return; |
92 | 97 | useTimeout(() => { |
93 | - tabStore.addTabByPathAction((to as unknown) as AppRouteRecordRaw); | |
98 | + tabStore.addTabByPathAction(); | |
94 | 99 | }, 0); |
95 | - activeKeyRef.value = to.path; | |
96 | - goTo && replace ? router.replace : router.push(to.path); | |
100 | + activeKeyRef.value = path; | |
101 | + goTo && replace ? router.replace : router.push(path); | |
97 | 102 | }, |
98 | 103 | activeKeyRef, |
99 | 104 | }; | ... | ... |
src/layouts/default/index.tsx
... | ... | @@ -82,10 +82,13 @@ export default defineComponent({ |
82 | 82 | {() => ( |
83 | 83 | <> |
84 | 84 | {isLock && <LockPage />} |
85 | + | |
85 | 86 | {!unref(getFullContent) && unref(isShowMixHeaderRef) && unref(showHeaderRef) && ( |
86 | 87 | <LayoutHeader /> |
87 | 88 | )} |
89 | + | |
88 | 90 | {showSettingButton && <SettingBtn />} |
91 | + | |
89 | 92 | <Layout> |
90 | 93 | {() => ( |
91 | 94 | <> |
... | ... | @@ -102,7 +105,9 @@ export default defineComponent({ |
102 | 105 | {() => <MultipleTabs />} |
103 | 106 | </Layout.Header> |
104 | 107 | )} |
108 | + | |
105 | 109 | {useOpenBackTop && <BackTop target={getTarget} />} |
110 | + | |
106 | 111 | <div class={[`default-layout__main`, fixedHeaderCls]}> |
107 | 112 | {openPageLoading && ( |
108 | 113 | <FullLoading | ... | ... |
src/layouts/default/multitabs/index.tsx
... | ... | @@ -9,6 +9,7 @@ import { |
9 | 9 | // ref, |
10 | 10 | unref, |
11 | 11 | onMounted, |
12 | + toRaw, | |
12 | 13 | } from 'vue'; |
13 | 14 | import { Tabs } from 'ant-design-vue'; |
14 | 15 | import TabContent from './TabContent'; |
... | ... | @@ -73,11 +74,7 @@ export default defineComponent({ |
73 | 74 | routes && |
74 | 75 | routes.forEach((route) => { |
75 | 76 | if (route.meta && route.meta.affix) { |
76 | - tabs.push({ | |
77 | - path: route.path, | |
78 | - name: route.name, | |
79 | - meta: { ...route.meta }, | |
80 | - }); | |
77 | + tabs.push(toRaw(route) as TabItem); | |
81 | 78 | } |
82 | 79 | }); |
83 | 80 | return tabs; |
... | ... | @@ -114,7 +111,7 @@ export default defineComponent({ |
114 | 111 | }; |
115 | 112 | return ( |
116 | 113 | <span> |
117 | - <TabContent {...tabContentProps} /> | |
114 | + <TabContent {...(tabContentProps as any)} /> | |
118 | 115 | </span> |
119 | 116 | ); |
120 | 117 | } | ... | ... |
src/layouts/page/index.tsx
... | ... | @@ -40,7 +40,8 @@ export default defineComponent({ |
40 | 40 | <RouterView> |
41 | 41 | {{ |
42 | 42 | default: ({ Component, route }: { Component: any; route: RouteLocation }) => { |
43 | - const name = route.meta.inTab ? ' ' : null; | |
43 | + // 已经位于tab内的不再显示动画 | |
44 | + const name = route.meta.inTab ? 'fade' : null; | |
44 | 45 | const Content = openCache ? ( |
45 | 46 | <KeepAlive max={max} include={cacheTabs}> |
46 | 47 | <Component {...route.params} /> | ... | ... |
src/router/guard/index.ts
... | ... | @@ -7,7 +7,7 @@ import { createProgressGuard } from './progressGuard'; |
7 | 7 | import { createPermissionGuard } from './permissionGuard'; |
8 | 8 | import { createPageLoadingGuard } from './pageLoadingGuard'; |
9 | 9 | import { useSetting } from '/@/hooks/core/useSetting'; |
10 | -import { getIsOpenTab } from '/@/utils/helper/routeHelper'; | |
10 | +import { getIsOpenTab, setCurrentTo } from '/@/utils/helper/routeHelper'; | |
11 | 11 | |
12 | 12 | const { projectSetting } = useSetting(); |
13 | 13 | export function createGuard(router: Router) { |
... | ... | @@ -17,7 +17,7 @@ export function createGuard(router: Router) { |
17 | 17 | axiosCanceler = new AxiosCanceler(); |
18 | 18 | } |
19 | 19 | router.beforeEach(async (to) => { |
20 | - const isOpen = getIsOpenTab(to.path); | |
20 | + const isOpen = getIsOpenTab(to.fullPath); | |
21 | 21 | to.meta.inTab = isOpen; |
22 | 22 | try { |
23 | 23 | if (closeMessageOnSwitch) { |
... | ... | @@ -30,6 +30,8 @@ export function createGuard(router: Router) { |
30 | 30 | } catch (error) { |
31 | 31 | console.warn('basic guard error:' + error); |
32 | 32 | } |
33 | + setCurrentTo(to); | |
34 | + return true; | |
33 | 35 | }); |
34 | 36 | openNProgress && createProgressGuard(router); |
35 | 37 | createPermissionGuard(router); | ... | ... |
src/router/guard/pageLoadingGuard.ts
... | ... | @@ -2,6 +2,7 @@ import type { Router } from 'vue-router'; |
2 | 2 | import { tabStore } from '/@/store/modules/tab'; |
3 | 3 | import { appStore } from '/@/store/modules/app'; |
4 | 4 | import { userStore } from '/@/store/modules/user'; |
5 | +import { getParams } from '/@/utils/helper/routeHelper'; | |
5 | 6 | |
6 | 7 | export function createPageLoadingGuard(router: Router) { |
7 | 8 | let isFirstLoad = true; |
... | ... | @@ -29,9 +30,16 @@ export function createPageLoadingGuard(router: Router) { |
29 | 30 | } |
30 | 31 | return true; |
31 | 32 | }); |
32 | - router.afterEach(async (to) => { | |
33 | + router.afterEach(async (to, from) => { | |
33 | 34 | const { openRouterTransition, openPageLoading } = appStore.getProjectConfig; |
34 | - if ((!openRouterTransition && openPageLoading) || isFirstLoad || to.meta.afterCloseLoading) { | |
35 | + const realToPath = to.path.replace(getParams(to), ''); | |
36 | + const realFormPath = from.path.replace(getParams(from), ''); | |
37 | + if ( | |
38 | + (!openRouterTransition && openPageLoading) || | |
39 | + isFirstLoad || | |
40 | + to.meta.afterCloseLoading || | |
41 | + realToPath === realFormPath | |
42 | + ) { | |
35 | 43 | setTimeout(() => { |
36 | 44 | appStore.commitPageLoadingState(false); |
37 | 45 | }, 110); | ... | ... |
src/router/menus/index.ts
... | ... | @@ -107,7 +107,8 @@ export async function getFlatChildrenMenus(children: Menu[]) { |
107 | 107 | function basicFilter(routes: RouteRecordNormalized[]) { |
108 | 108 | return (menu: Menu) => { |
109 | 109 | const matchRoute = routes.find((route) => route.path === menu.path); |
110 | - if (!matchRoute) return false; | |
110 | + | |
111 | + if (!matchRoute) return true; | |
111 | 112 | menu.icon = menu.icon || matchRoute.meta.icon; |
112 | 113 | menu.meta = matchRoute.meta; |
113 | 114 | return true; | ... | ... |
src/router/menus/modules/demo/feat.ts
... | ... | @@ -45,6 +45,20 @@ const menu: MenuModule = { |
45 | 45 | path: '/full-screen', |
46 | 46 | name: '全屏', |
47 | 47 | }, |
48 | + { | |
49 | + path: '/testTab', | |
50 | + name: '带参Tab', | |
51 | + children: [ | |
52 | + { | |
53 | + path: '/id1', | |
54 | + name: '带参tab1', | |
55 | + }, | |
56 | + { | |
57 | + path: '/id2', | |
58 | + name: '带参tab2', | |
59 | + }, | |
60 | + ], | |
61 | + }, | |
48 | 62 | ], |
49 | 63 | }, |
50 | 64 | }; | ... | ... |
src/router/routes/index.ts
... | ... | @@ -3,8 +3,6 @@ import type { AppRouteRecordRaw, AppRouteModule } from '/@/router/types'; |
3 | 3 | import { DEFAULT_LAYOUT_COMPONENT, PAGE_NOT_FOUND_ROUTE, REDIRECT_ROUTE } from '../constant'; |
4 | 4 | import { genRouteModule } from '/@/utils/helper/routeHelper'; |
5 | 5 | |
6 | -import LoginRoute from './modules/sys'; | |
7 | - | |
8 | 6 | import dashboard from './modules/dashboard'; |
9 | 7 | |
10 | 8 | // demo |
... | ... | @@ -48,5 +46,14 @@ export const RootRoute: AppRouteRecordRaw = { |
48 | 46 | children: [], |
49 | 47 | }; |
50 | 48 | |
49 | +export const LoginRoute: AppRouteRecordRaw = { | |
50 | + path: '/login', | |
51 | + name: 'Login', | |
52 | + component: () => import('/@/views/sys/login/Login.vue'), | |
53 | + meta: { | |
54 | + title: '登录', | |
55 | + }, | |
56 | +}; | |
57 | + | |
51 | 58 | // 基础路由 不用权限 |
52 | 59 | export const basicRoutes = [LoginRoute, RootRoute]; | ... | ... |
src/router/routes/modules/demo/feat.ts
... | ... | @@ -96,5 +96,13 @@ export default { |
96 | 96 | title: '全屏', |
97 | 97 | }, |
98 | 98 | }, |
99 | + { | |
100 | + path: '/testTab/:id', | |
101 | + name: 'TestTab', | |
102 | + component: () => import('/@/views/demo/feat/tab-params/index.vue'), | |
103 | + meta: { | |
104 | + title: 'Tab带参', | |
105 | + }, | |
106 | + }, | |
99 | 107 | ], |
100 | 108 | } as AppRouteModule; | ... | ... |
src/router/routes/modules/sys.ts deleted
100644 → 0
src/router/types.d.ts
src/store/modules/tab.ts
... | ... | @@ -11,6 +11,7 @@ import { appStore } from '/@/store/modules/app'; |
11 | 11 | import store from '/@/store'; |
12 | 12 | import router from '/@/router'; |
13 | 13 | import { PAGE_NOT_FOUND_ROUTE, REDIRECT_ROUTE } from '/@/router/constant'; |
14 | +import { getCurrentTo } from '/@/utils/helper/routeHelper'; | |
14 | 15 | |
15 | 16 | type CacheName = string | symbol | null | undefined; |
16 | 17 | /** |
... | ... | @@ -18,7 +19,10 @@ type CacheName = string | symbol | null | undefined; |
18 | 19 | */ |
19 | 20 | // declare namespace TabsStore { |
20 | 21 | export interface TabItem { |
21 | - path: string; | |
22 | + fullPath: string; | |
23 | + path?: string; | |
24 | + params?: any; | |
25 | + query?: any; | |
22 | 26 | name?: CacheName; |
23 | 27 | meta?: RouteMeta; |
24 | 28 | } |
... | ... | @@ -86,20 +90,21 @@ class Tab extends VuexModule { |
86 | 90 | */ |
87 | 91 | @Mutation |
88 | 92 | commitAddTab(route: AppRouteRecordRaw | TabItem): void { |
89 | - const { path, name, meta } = route; | |
93 | + const { path, name, meta, fullPath, params, query } = route as TabItem; | |
90 | 94 | // 404 页面不需要添加tab |
91 | 95 | if (path === PageEnum.ERROR_PAGE) { |
92 | 96 | return; |
93 | 97 | } else if ([REDIRECT_ROUTE.name, PAGE_NOT_FOUND_ROUTE.name].includes(name as string)) { |
94 | 98 | return; |
95 | 99 | } |
100 | + | |
96 | 101 | // 已经存在的页面,不重复添加tab |
97 | 102 | const hasTab = this.tabsState.some((tab) => { |
98 | - return tab.path === path; | |
103 | + return tab.fullPath === fullPath; | |
99 | 104 | }); |
100 | 105 | if (hasTab) return; |
101 | 106 | |
102 | - this.tabsState.push({ path, name, meta }); | |
107 | + this.tabsState.push({ path, fullPath, name, meta, params, query }); | |
103 | 108 | if (unref(getOpenKeepAliveRef) && name) { |
104 | 109 | const noKeepAlive = meta && meta.ignoreKeepAlive; |
105 | 110 | const hasName = this.keepAliveTabsState.includes(name); |
... | ... | @@ -113,9 +118,9 @@ class Tab extends VuexModule { |
113 | 118 | @Mutation |
114 | 119 | commitCloseTab(route: AppRouteRecordRaw | TabItem): void { |
115 | 120 | try { |
116 | - const { path, name, meta: { affix } = {} } = route; | |
121 | + const { fullPath, name, meta: { affix } = {} } = route; | |
117 | 122 | if (affix) return; |
118 | - const index = this.tabsState.findIndex((item) => item.path === path); | |
123 | + const index = this.tabsState.findIndex((item) => item.fullPath === fullPath); | |
119 | 124 | index !== -1 && this.tabsState.splice(index, 1); |
120 | 125 | |
121 | 126 | if (unref(getOpenKeepAliveRef) && name) { |
... | ... | @@ -153,7 +158,7 @@ class Tab extends VuexModule { |
153 | 158 | |
154 | 159 | @Mutation |
155 | 160 | closeMultipleTab({ pathList, nameList }: { pathList: string[]; nameList: string[] }): void { |
156 | - this.tabsState = toRaw(this.tabsState).filter((item) => !pathList.includes(item.path)); | |
161 | + this.tabsState = toRaw(this.tabsState).filter((item) => !pathList.includes(item.fullPath)); | |
157 | 162 | if (unref(getOpenKeepAliveRef) && nameList) { |
158 | 163 | this.keepAliveTabsState = toRaw(this.keepAliveTabsState).filter( |
159 | 164 | (item) => !nameList.includes(item as string) |
... | ... | @@ -172,7 +177,7 @@ class Tab extends VuexModule { |
172 | 177 | for (const item of leftTabs) { |
173 | 178 | const affix = item.meta ? item.meta.affix : false; |
174 | 179 | if (!affix) { |
175 | - pathList.push(item.path); | |
180 | + pathList.push(item.fullPath); | |
176 | 181 | nameList.push(item.name as string); |
177 | 182 | } |
178 | 183 | } |
... | ... | @@ -181,13 +186,19 @@ class Tab extends VuexModule { |
181 | 186 | } |
182 | 187 | |
183 | 188 | @Action |
184 | - addTabByPathAction(to: AppRouteRecordRaw): void { | |
185 | - to && this.commitAddTab((to as unknown) as AppRouteRecordRaw); | |
189 | + addTabByPathAction(): void { | |
190 | + const toRoute = getCurrentTo(); | |
191 | + if (!toRoute) return; | |
192 | + const { meta } = toRoute; | |
193 | + if (meta && meta.affix) { | |
194 | + return; | |
195 | + } | |
196 | + this.commitAddTab((toRoute as unknown) as AppRouteRecordRaw); | |
186 | 197 | } |
187 | 198 | |
188 | 199 | @Action |
189 | 200 | closeRightTabAction(route: AppRouteRecordRaw | TabItem): void { |
190 | - const index = this.tabsState.findIndex((item) => item.path === route.path); | |
201 | + const index = this.tabsState.findIndex((item) => item.fullPath === route.fullPath); | |
191 | 202 | |
192 | 203 | if (index >= 0 && index < this.tabsState.length - 1) { |
193 | 204 | const rightTabs = this.tabsState.slice(index + 1, this.tabsState.length); |
... | ... | @@ -197,7 +208,7 @@ class Tab extends VuexModule { |
197 | 208 | for (const item of rightTabs) { |
198 | 209 | const affix = item.meta ? item.meta.affix : false; |
199 | 210 | if (!affix) { |
200 | - pathList.push(item.path); | |
211 | + pathList.push(item.fullPath); | |
201 | 212 | nameList.push(item.name as string); |
202 | 213 | } |
203 | 214 | } |
... | ... | @@ -207,16 +218,16 @@ class Tab extends VuexModule { |
207 | 218 | |
208 | 219 | @Action |
209 | 220 | closeOtherTabAction(route: AppRouteRecordRaw | TabItem): void { |
210 | - const closePathList = this.tabsState.map((item) => item.path); | |
221 | + const closePathList = this.tabsState.map((item) => item.fullPath); | |
211 | 222 | const pathList: string[] = []; |
212 | 223 | const nameList: string[] = []; |
213 | 224 | closePathList.forEach((path) => { |
214 | - if (path !== route.path) { | |
225 | + if (path !== route.fullPath) { | |
215 | 226 | const closeItem = this.tabsState.find((item) => item.path === path); |
216 | 227 | if (!closeItem) return; |
217 | 228 | const affix = closeItem.meta ? closeItem.meta.affix : false; |
218 | 229 | if (!affix) { |
219 | - pathList.push(closeItem.path); | |
230 | + pathList.push(closeItem.fullPath); | |
220 | 231 | nameList.push(closeItem.name as string); |
221 | 232 | } |
222 | 233 | } | ... | ... |
src/types/source.d.ts
1 | 1 | declare module 'ant-design-vue/es/locale/zh_CN'; |
2 | -declare module 'vue-draggable-resizable'; | |
2 | +declare module 'globby!/@/router/routes/modules/**/*.@(ts)'; | |
3 | +declare module 'globby!/@/router/menus/modules/**/*.@(ts)'; | |
4 | + | |
3 | 5 | declare const React: string; |
4 | 6 | declare module '*.bmp' { |
5 | 7 | const src: string; | ... | ... |
src/utils/eventHub.ts
... | ... | @@ -16,9 +16,6 @@ class EventHub { |
16 | 16 | |
17 | 17 | emit(eventName: string, data?: any) { |
18 | 18 | if (this.cache[eventName] === undefined) return; |
19 | - console.log('======================'); | |
20 | - console.log(this.cache, eventName); | |
21 | - console.log('======================'); | |
22 | 19 | this.cache[eventName].forEach((fn) => fn(data)); |
23 | 20 | } |
24 | 21 | off(eventName: string, fn: (data: any) => void) { | ... | ... |
src/utils/helper/menuHelper.ts
... | ... | @@ -4,7 +4,6 @@ import type { MenuModule, Menu, AppRouteRecordRaw } from '/@/router/types'; |
4 | 4 | import { findPath, forEach, treeMap, treeToList } from './treeHelper'; |
5 | 5 | import { cloneDeep } from 'lodash-es'; |
6 | 6 | |
7 | -// | |
8 | 7 | export function getAllParentPath(treeData: any[], path: string) { |
9 | 8 | const menuList = findPath(treeData, (n) => n.path === path) as Menu[]; |
10 | 9 | return (menuList || []).map((item) => item.path); |
... | ... | @@ -14,6 +13,7 @@ export function flatMenus(menus: Menu[]) { |
14 | 13 | return treeToList(menus); |
15 | 14 | } |
16 | 15 | |
16 | +// 拼接父级路径 | |
17 | 17 | function joinParentPath(list: any, node: any) { |
18 | 18 | let allPaths = getAllParentPath(list, node.path); |
19 | 19 | |
... | ... | @@ -26,7 +26,6 @@ function joinParentPath(list: any, node: any) { |
26 | 26 | parentPath += /^\//.test(p) ? p : `/${p}`; |
27 | 27 | }); |
28 | 28 | } |
29 | - | |
30 | 29 | node.path = `${parentPath}${/^\//.test(node.path) ? node.path : `/${node.path}`}`.replace( |
31 | 30 | /\/\//g, |
32 | 31 | '/' |
... | ... | @@ -34,6 +33,7 @@ function joinParentPath(list: any, node: any) { |
34 | 33 | return node; |
35 | 34 | } |
36 | 35 | |
36 | +// 解析菜单模块 | |
37 | 37 | export function transformMenuModule(menuModule: MenuModule): Menu { |
38 | 38 | const { menu } = menuModule; |
39 | 39 | ... | ... |
src/utils/helper/routeHelper.ts
1 | 1 | import type { AppRouteModule, AppRouteRecordRaw } from '/@/router/types'; |
2 | -import type { RouteRecordRaw } from 'vue-router'; | |
2 | +import type { RouteLocationNormalized, RouteRecordRaw } from 'vue-router'; | |
3 | 3 | |
4 | 4 | import { appStore } from '/@/store/modules/app'; |
5 | 5 | import { tabStore } from '/@/store/modules/tab'; |
6 | 6 | import { createRouter, createWebHashHistory } from 'vue-router'; |
7 | 7 | import { toRaw } from 'vue'; |
8 | 8 | import { PAGE_LAYOUT_COMPONENT } from '/@/router/constant'; |
9 | + | |
10 | +let currentTo: RouteLocationNormalized | null = null; | |
11 | + | |
12 | +export function getCurrentTo() { | |
13 | + return currentTo; | |
14 | +} | |
15 | + | |
16 | +export function setCurrentTo(to: RouteLocationNormalized) { | |
17 | + currentTo = to; | |
18 | +} | |
19 | +// 转化路由模块 | |
20 | +// 将多级转成2层。keepAlive问题 | |
9 | 21 | export function genRouteModule(moduleList: AppRouteModule[]) { |
10 | 22 | const ret: AppRouteRecordRaw[] = []; |
11 | 23 | for (const routeMod of moduleList) { |
... | ... | @@ -27,6 +39,7 @@ export function genRouteModule(moduleList: AppRouteModule[]) { |
27 | 39 | return ret as RouteRecordRaw[]; |
28 | 40 | } |
29 | 41 | |
42 | +// 动态引入 | |
30 | 43 | function asyncImportRoute(routes: AppRouteRecordRaw[]) { |
31 | 44 | routes.forEach((item) => { |
32 | 45 | const { component, children } = item; |
... | ... | @@ -37,6 +50,7 @@ function asyncImportRoute(routes: AppRouteRecordRaw[]) { |
37 | 50 | }); |
38 | 51 | } |
39 | 52 | |
53 | +// 将后台对象转成路由对象 | |
40 | 54 | export function transformObjToRoute(routeList: AppRouteModule[]) { |
41 | 55 | routeList.forEach((route) => { |
42 | 56 | asyncImportRoute(route.routes); |
... | ... | @@ -48,6 +62,7 @@ export function transformObjToRoute(routeList: AppRouteModule[]) { |
48 | 62 | return routeList; |
49 | 63 | } |
50 | 64 | |
65 | +// | |
51 | 66 | export function getIsOpenTab(toPath: string) { |
52 | 67 | const { openKeepAlive, multiTabsSetting: { show } = {} } = appStore.getProjectConfig; |
53 | 68 | |
... | ... | @@ -57,3 +72,13 @@ export function getIsOpenTab(toPath: string) { |
57 | 72 | } |
58 | 73 | return false; |
59 | 74 | } |
75 | + | |
76 | +export function getParams(data: any = {}) { | |
77 | + const { params = {} } = data; | |
78 | + let ret = ''; | |
79 | + Object.keys(params).forEach((key) => { | |
80 | + const p = params[key]; | |
81 | + ret += `/${p}`; | |
82 | + }); | |
83 | + return ret; | |
84 | +} | ... | ... |
src/views/demo/feat/tab-params/index.vue
0 → 100644
1 | +<template> | |
2 | + <div class="p-4"> Current Param : {{ params }} </div> | |
3 | +</template> | |
4 | +<script lang="ts"> | |
5 | + import { computed, defineComponent, unref } from 'vue'; | |
6 | + import { useRouter } from 'vue-router'; | |
7 | + export default defineComponent({ | |
8 | + setup() { | |
9 | + const { currentRoute } = useRouter(); | |
10 | + return { | |
11 | + params: computed(() => { | |
12 | + return unref(currentRoute).params; | |
13 | + }), | |
14 | + }; | |
15 | + }, | |
16 | + }); | |
17 | +</script> | ... | ... |
src/views/sys/redirect/index.vue
... | ... | @@ -2,7 +2,7 @@ |
2 | 2 | <div /> |
3 | 3 | </template> |
4 | 4 | <script lang="ts"> |
5 | - import { defineComponent, onBeforeMount, unref } from 'vue'; | |
5 | + import { defineComponent, unref } from 'vue'; | |
6 | 6 | |
7 | 7 | import { appStore } from '/@/store/modules/app'; |
8 | 8 | |
... | ... | @@ -11,21 +11,19 @@ |
11 | 11 | name: 'Redirect', |
12 | 12 | setup() { |
13 | 13 | const { currentRoute, replace } = useRouter(); |
14 | - onBeforeMount(() => { | |
15 | - const { params, query } = unref(currentRoute); | |
16 | - const { path } = params; | |
17 | - const _path = Array.isArray(path) ? path.join('/') : path; | |
18 | - replace({ | |
19 | - path: '/' + _path, | |
20 | - query, | |
21 | - }); | |
22 | - const { openRouterTransition, openPageLoading } = appStore.getProjectConfig; | |
23 | - if (openRouterTransition && openPageLoading) { | |
24 | - setTimeout(() => { | |
25 | - appStore.setPageLoadingAction(false); | |
26 | - }, 0); | |
27 | - } | |
14 | + const { params, query } = unref(currentRoute); | |
15 | + const { path } = params; | |
16 | + const _path = Array.isArray(path) ? path.join('/') : path; | |
17 | + replace({ | |
18 | + path: '/' + _path, | |
19 | + query, | |
28 | 20 | }); |
21 | + const { openRouterTransition, openPageLoading } = appStore.getProjectConfig; | |
22 | + if (openRouterTransition && openPageLoading) { | |
23 | + setTimeout(() => { | |
24 | + appStore.setPageLoadingAction(false); | |
25 | + }, 0); | |
26 | + } | |
29 | 27 | return {}; |
30 | 28 | }, |
31 | 29 | }); | ... | ... |