Commit 31e2715e674fc7d5144f39d8d5e595d904585af8

Authored by vben
1 parent 74378960

chore: detail optimization

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 = &#39;VITE_GLOB_&#39;, confFiles = [&#39;.env&#39;, &#39;.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 &#39;ant-design-vue&#39;;
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
... ... @@ -18,4 +18,4 @@
18 18 // app menu
19 19  
20 20 // left-menu
21   -@app-menu-item-height: 44px;
  21 +@app-menu-item-height: 48px;
... ...
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 &#39;./progressGuard&#39;;
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 &#39;vue-router&#39;;
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 &#39;/@/router/types&#39;;
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
1   -import type { AppRouteRecordRaw } from '/@/router/types';
2   -
3   -const routes: AppRouteRecordRaw = {
4   - path: '/login',
5   - name: 'Login',
6   - component: () => import('/@/views/sys/login/Login.vue'),
7   - meta: {
8   - title: '登录',
9   - },
10   -};
11   -
12   -export default routes;
src/router/types.d.ts
... ... @@ -39,6 +39,7 @@ export interface AppRouteRecordRaw extends Omit&lt;RouteRecordRaw, &#39;meta&#39;&gt; {
39 39 components?: any;
40 40 children?: AppRouteRecordRaw[];
41 41 props?: any;
  42 + fullPath?: string;
42 43 }
43 44  
44 45 export interface Menu {
... ...
src/store/modules/tab.ts
... ... @@ -11,6 +11,7 @@ import { appStore } from &#39;/@/store/modules/app&#39;;
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 &#39;/@/router/types&#39;;
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 });
... ...
vite.config.ts
... ... @@ -71,7 +71,7 @@ const viteConfig: UserConfig = {
71 71 * boolean | 'terser' | 'esbuild'
72 72 * @default 'terser'
73 73 */
74   - minify: isDevFn() ? false : 'terser',
  74 + minify: 'terser',
75 75 /**
76 76 * 基本公共路径
77 77 * @default '/'
... ...