mirror of
https://github.com/vbenjs/vue-vben-admin.git
synced 2025-08-26 16:46:19 +08:00
chore: detail optimization
This commit is contained in:
@@ -74,9 +74,7 @@ function injectCdnjs(html: string) {
|
||||
export async function runUpdateHtml() {
|
||||
const outDir = viteConfig.outDir || 'dist';
|
||||
const indexPath = getCwdPath(outDir, 'index.html');
|
||||
if (!existsSync(`${indexPath}`)) {
|
||||
return;
|
||||
}
|
||||
if (!existsSync(indexPath)) return;
|
||||
try {
|
||||
let processedHtml = '';
|
||||
const rawHtml = readFileSync(indexPath, 'utf-8');
|
||||
@@ -92,7 +90,9 @@ export async function runUpdateHtml() {
|
||||
}
|
||||
if (minify) {
|
||||
const { enable, ...miniOpt } = minify;
|
||||
processedHtml = HtmlMinifier.minify(processedHtml, miniOpt);
|
||||
if (enable) {
|
||||
processedHtml = HtmlMinifier.minify(processedHtml, miniOpt);
|
||||
}
|
||||
}
|
||||
|
||||
writeFileSync(indexPath, processedHtml);
|
||||
|
95
build/transform/require-context/index.ts
Normal file
95
build/transform/require-context/index.ts
Normal file
@@ -0,0 +1,95 @@
|
||||
// https://github.com/luxueyan/vite-transform-globby-import/blob/master/src/index.ts
|
||||
|
||||
// TODO 目前还不能监听文件新增及删除 内容已经改变,缓存问题?
|
||||
// 可以使用,先不打算集成
|
||||
import { join } from 'path';
|
||||
import { lstatSync } from 'fs';
|
||||
import glob from 'glob';
|
||||
import { createResolver, Resolver } from 'vite/dist/node/resolver.js';
|
||||
import { Transform } from 'vite/dist/node/transform.js';
|
||||
|
||||
const modulesDir: string = join(process.cwd(), '/node_modules/');
|
||||
|
||||
interface SharedConfig {
|
||||
root?: string;
|
||||
alias?: Record<string, string>;
|
||||
resolvers?: Resolver[];
|
||||
}
|
||||
|
||||
function template(template: string) {
|
||||
return (data: { [x: string]: any }) => {
|
||||
return template.replace(/#([^#]+)#/g, (_, g1) => data[g1] || g1);
|
||||
};
|
||||
}
|
||||
|
||||
const globbyTransform = function (config: SharedConfig): Transform {
|
||||
const resolver = createResolver(
|
||||
config.root || process.cwd(),
|
||||
config.resolvers || [],
|
||||
config.alias || {}
|
||||
);
|
||||
const cache = new Map();
|
||||
|
||||
const urlMap = new Map();
|
||||
return {
|
||||
test({ path }) {
|
||||
const filePath = path.replace('\u0000', ''); // why some path startsWith '\u0000'?
|
||||
try {
|
||||
return (
|
||||
!filePath.startsWith(modulesDir) &&
|
||||
/\.(vue|js|jsx|ts|tsx)$/.test(filePath) &&
|
||||
lstatSync(filePath).isFile()
|
||||
);
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
},
|
||||
transform({ code, path, isBuild }) {
|
||||
let result = cache.get(path);
|
||||
if (!result) {
|
||||
const reg = /import\s+([\w\s{}*]+)\s+from\s+(['"])globby(\?path)?!([^'"]+)\2/g;
|
||||
const lastImport = urlMap.get(path);
|
||||
const match = code.match(reg);
|
||||
if (lastImport && match) {
|
||||
code = code.replace(lastImport, match[0]);
|
||||
}
|
||||
result = code.replace(reg, (_, g1, g2, g3, g4) => {
|
||||
const filePath = path.replace('\u0000', ''); // why some path startsWith '\u0000'?
|
||||
// resolve path
|
||||
const resolvedFilePath = g4.startsWith('.')
|
||||
? resolver.resolveRelativeRequest(filePath, g4)
|
||||
: { pathname: resolver.requestToFile(g4) };
|
||||
const files = glob.sync(resolvedFilePath.pathname, { dot: true });
|
||||
let templateStr = 'import #name# from #file#'; // import default
|
||||
let name = g1;
|
||||
const m = g1.match(/\{\s*(\w+)(\s+as\s+(\w+))?\s*\}/); // import module
|
||||
const m2 = g1.match(/\*\s+as\s+(\w+)/); // import * as all module
|
||||
if (m) {
|
||||
templateStr = `import { ${m[1]} as #name# } from #file#`;
|
||||
name = m[3] || m[1];
|
||||
} else if (m2) {
|
||||
templateStr = 'import * as #name# from #file#';
|
||||
name = m2[1];
|
||||
}
|
||||
const temRender = template(templateStr);
|
||||
|
||||
const groups: Array<string>[] = [];
|
||||
const replaceFiles = files.map((f, i) => {
|
||||
const file = g2 + resolver.fileToRequest(f) + g2;
|
||||
groups.push([name + i, file]);
|
||||
return temRender({ name: name + i, file });
|
||||
});
|
||||
urlMap.set(path, replaceFiles.join('\n'));
|
||||
return (
|
||||
replaceFiles.join('\n') +
|
||||
(g3 ? '\n' + groups.map((v) => `${v[0]}._path = ${v[1]}`).join('\n') : '') +
|
||||
`\nconst ${name} = { ${groups.map((v) => v[0]).join(',')} }\n`
|
||||
);
|
||||
});
|
||||
if (isBuild) cache.set(path, result);
|
||||
}
|
||||
return result;
|
||||
},
|
||||
};
|
||||
};
|
||||
export default globbyTransform;
|
@@ -124,28 +124,24 @@ export function getEnvConfig(match = 'VITE_GLOB_', confFiles = ['.env', '.env.pr
|
||||
return envConfig;
|
||||
}
|
||||
|
||||
export function successConsole(message: any) {
|
||||
function consoleFn(color: string, message: any) {
|
||||
console.log(
|
||||
chalk.blue.bold('**************** ') +
|
||||
chalk.green.bold('✨ ' + message) +
|
||||
(chalk as any)[color].bold(message) +
|
||||
chalk.blue.bold(' ****************')
|
||||
);
|
||||
}
|
||||
|
||||
export function successConsole(message: any) {
|
||||
consoleFn('green', '✨ ' + message);
|
||||
}
|
||||
|
||||
export function errorConsole(message: any) {
|
||||
console.log(
|
||||
chalk.blue.bold('**************** ') +
|
||||
chalk.red.bold('✨ ' + message) +
|
||||
chalk.blue.bold(' ****************')
|
||||
);
|
||||
consoleFn('red', '✨ ' + message);
|
||||
}
|
||||
|
||||
export function warnConsole(message: any) {
|
||||
console.log(
|
||||
chalk.blue.bold('**************** ') +
|
||||
chalk.yellow.bold('✨ ' + message) +
|
||||
chalk.blue.bold(' ****************')
|
||||
);
|
||||
consoleFn('yellow', '✨ ' + message);
|
||||
}
|
||||
|
||||
export function getCwdPath(...dir: string[]) {
|
||||
|
@@ -5,7 +5,6 @@
|
||||
</template>
|
||||
<script lang="ts">
|
||||
import { defineComponent, reactive, computed, watch, onMounted, unref, toRef } from 'vue';
|
||||
|
||||
import { countToProps } from './props';
|
||||
import { useRaf } from '/@/hooks/event/useRaf';
|
||||
import { isNumber } from '/@/utils/is';
|
||||
@@ -37,12 +36,14 @@
|
||||
remaining: null,
|
||||
rAF: null,
|
||||
});
|
||||
|
||||
onMounted(() => {
|
||||
if (props.autoplay) {
|
||||
start();
|
||||
}
|
||||
emit('mounted');
|
||||
});
|
||||
|
||||
const getCountDown = computed(() => {
|
||||
return props.startVal > props.endVal;
|
||||
});
|
||||
@@ -61,6 +62,7 @@
|
||||
state.paused = false;
|
||||
state.rAF = requestAnimationFrame(count);
|
||||
}
|
||||
|
||||
function pauseResume() {
|
||||
if (state.paused) {
|
||||
resume();
|
||||
@@ -70,6 +72,7 @@
|
||||
state.paused = true;
|
||||
}
|
||||
}
|
||||
|
||||
function pause() {
|
||||
cancelAnimationFrame(state.rAF);
|
||||
}
|
||||
|
@@ -7,7 +7,7 @@ import { Menu } from 'ant-design-vue';
|
||||
import { MenuModeEnum, MenuTypeEnum } from '/@/enums/menuEnum';
|
||||
import { menuStore } from '/@/store/modules/menu';
|
||||
import { getSlot } from '/@/utils/helper/tsxHelper';
|
||||
import { ScrollContainer } from '/@/components/Container/index';
|
||||
// import { ScrollContainer } from '/@/components/Container/index';
|
||||
import SearchInput from './SearchInput.vue';
|
||||
import './index.less';
|
||||
import { menuHasChildren } from './helper';
|
||||
@@ -67,6 +67,7 @@ export default defineComponent({
|
||||
return {
|
||||
height: `calc(100% - ${offset}px)`,
|
||||
position: 'relative',
|
||||
overflow: 'auto',
|
||||
};
|
||||
});
|
||||
|
||||
@@ -246,8 +247,9 @@ export default defineComponent({
|
||||
onClick={handleInputClick}
|
||||
collapsed={getCollapsedState}
|
||||
/>
|
||||
<section style={unref(getMenuWrapStyle)}>
|
||||
<ScrollContainer>{() => renderMenu()}</ScrollContainer>
|
||||
<section style={unref(getMenuWrapStyle)} class="basic-menu__wrap">
|
||||
{renderMenu()}
|
||||
{/* <ScrollContainer>{() => renderMenu()}</ScrollContainer> */}
|
||||
</section>
|
||||
</section>
|
||||
);
|
||||
|
@@ -14,6 +14,30 @@
|
||||
}
|
||||
|
||||
.basic-menu {
|
||||
&__wrap {
|
||||
/* 滚动槽 */
|
||||
&::-webkit-scrollbar {
|
||||
width: 6px;
|
||||
height: 6px;
|
||||
}
|
||||
|
||||
// TODO 滚动条样式-待修改
|
||||
&::-webkit-scrollbar-track {
|
||||
background: rgba(0, 0, 0, 0);
|
||||
}
|
||||
|
||||
/* 滚动条滑块 */
|
||||
&::-webkit-scrollbar-thumb {
|
||||
background: rgba(255, 255, 255, 0.3);
|
||||
border-radius: 4px;
|
||||
box-shadow: inset 0 0 10px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
::-webkit-scrollbar-thumb:hover {
|
||||
background: @border-color-dark;
|
||||
}
|
||||
}
|
||||
|
||||
.ant-menu-submenu:first-of-type {
|
||||
margin-top: 4px;
|
||||
}
|
||||
@@ -95,14 +119,14 @@
|
||||
.ant-menu-submenu-active,
|
||||
.ant-menu-submenu-title:hover {
|
||||
background: @top-menu-active-bg-color;
|
||||
border-radius: 6px 6px 0 0;
|
||||
// border-radius: 6px 6px 0 0;
|
||||
}
|
||||
|
||||
.basic-menu-item__level1 {
|
||||
&.ant-menu-item-selected,
|
||||
&.ant-menu-submenu-selected {
|
||||
background: @top-menu-active-bg-color;
|
||||
border-radius: 6px 6px 0 0;
|
||||
// border-radius: 6px 6px 0 0;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -148,6 +172,10 @@
|
||||
}
|
||||
|
||||
.basic-menu-item__level1 {
|
||||
> .ant-menu-sub > li {
|
||||
background-color: @sub-menu-item-dark-bg-color;
|
||||
}
|
||||
|
||||
margin-bottom: 0;
|
||||
|
||||
&.top-active-menu {
|
||||
@@ -179,7 +207,7 @@
|
||||
|
||||
.ant-menu-submenu-title {
|
||||
height: @app-menu-item-height;
|
||||
margin: 0;
|
||||
// margin: 0;
|
||||
line-height: @app-menu-item-height;
|
||||
}
|
||||
}
|
||||
|
@@ -18,4 +18,4 @@
|
||||
// app menu
|
||||
|
||||
// left-menu
|
||||
@app-menu-item-height: 44px;
|
||||
@app-menu-item-height: 48px;
|
||||
|
@@ -1,10 +1,10 @@
|
||||
import type { AppRouteRecordRaw } from '/@/router/types.d';
|
||||
import { useTimeout } from '/@/hooks/core/useTimeout';
|
||||
import { PageEnum } from '/@/enums/pageEnum';
|
||||
import { TabItem, tabStore } from '/@/store/modules/tab';
|
||||
import { appStore } from '/@/store/modules/app';
|
||||
import router from '/@/router';
|
||||
import { ref } from 'vue';
|
||||
import { pathToRegexp } from 'path-to-regexp';
|
||||
|
||||
const activeKeyRef = ref<string>('');
|
||||
|
||||
@@ -68,7 +68,11 @@ export function useTabs() {
|
||||
function getTo(path: string): any {
|
||||
const routes = router.getRoutes();
|
||||
const fn = (p: string): any => {
|
||||
const to = routes.find((item) => item.path === p);
|
||||
const to = routes.find((item) => {
|
||||
if (item.path === '/:path(.*)*') return;
|
||||
const regexp = pathToRegexp(item.path);
|
||||
return regexp.test(p);
|
||||
});
|
||||
if (!to) return '';
|
||||
if (!to.redirect) return to;
|
||||
if (to.redirect) {
|
||||
@@ -88,12 +92,13 @@ export function useTabs() {
|
||||
resetCache: () => canIUseFn() && resetCache(),
|
||||
addTab: (path: PageEnum, goTo = false, replace = false) => {
|
||||
const to = getTo(path);
|
||||
|
||||
if (!to) return;
|
||||
useTimeout(() => {
|
||||
tabStore.addTabByPathAction((to as unknown) as AppRouteRecordRaw);
|
||||
tabStore.addTabByPathAction();
|
||||
}, 0);
|
||||
activeKeyRef.value = to.path;
|
||||
goTo && replace ? router.replace : router.push(to.path);
|
||||
activeKeyRef.value = path;
|
||||
goTo && replace ? router.replace : router.push(path);
|
||||
},
|
||||
activeKeyRef,
|
||||
};
|
||||
|
@@ -82,10 +82,13 @@ export default defineComponent({
|
||||
{() => (
|
||||
<>
|
||||
{isLock && <LockPage />}
|
||||
|
||||
{!unref(getFullContent) && unref(isShowMixHeaderRef) && unref(showHeaderRef) && (
|
||||
<LayoutHeader />
|
||||
)}
|
||||
|
||||
{showSettingButton && <SettingBtn />}
|
||||
|
||||
<Layout>
|
||||
{() => (
|
||||
<>
|
||||
@@ -102,7 +105,9 @@ export default defineComponent({
|
||||
{() => <MultipleTabs />}
|
||||
</Layout.Header>
|
||||
)}
|
||||
|
||||
{useOpenBackTop && <BackTop target={getTarget} />}
|
||||
|
||||
<div class={[`default-layout__main`, fixedHeaderCls]}>
|
||||
{openPageLoading && (
|
||||
<FullLoading
|
||||
|
@@ -9,6 +9,7 @@ import {
|
||||
// ref,
|
||||
unref,
|
||||
onMounted,
|
||||
toRaw,
|
||||
} from 'vue';
|
||||
import { Tabs } from 'ant-design-vue';
|
||||
import TabContent from './TabContent';
|
||||
@@ -73,11 +74,7 @@ export default defineComponent({
|
||||
routes &&
|
||||
routes.forEach((route) => {
|
||||
if (route.meta && route.meta.affix) {
|
||||
tabs.push({
|
||||
path: route.path,
|
||||
name: route.name,
|
||||
meta: { ...route.meta },
|
||||
});
|
||||
tabs.push(toRaw(route) as TabItem);
|
||||
}
|
||||
});
|
||||
return tabs;
|
||||
@@ -114,7 +111,7 @@ export default defineComponent({
|
||||
};
|
||||
return (
|
||||
<span>
|
||||
<TabContent {...tabContentProps} />
|
||||
<TabContent {...(tabContentProps as any)} />
|
||||
</span>
|
||||
);
|
||||
}
|
||||
|
@@ -40,7 +40,8 @@ export default defineComponent({
|
||||
<RouterView>
|
||||
{{
|
||||
default: ({ Component, route }: { Component: any; route: RouteLocation }) => {
|
||||
const name = route.meta.inTab ? ' ' : null;
|
||||
// 已经位于tab内的不再显示动画
|
||||
const name = route.meta.inTab ? 'fade' : null;
|
||||
const Content = openCache ? (
|
||||
<KeepAlive max={max} include={cacheTabs}>
|
||||
<Component {...route.params} />
|
||||
|
@@ -7,7 +7,7 @@ import { createProgressGuard } from './progressGuard';
|
||||
import { createPermissionGuard } from './permissionGuard';
|
||||
import { createPageLoadingGuard } from './pageLoadingGuard';
|
||||
import { useSetting } from '/@/hooks/core/useSetting';
|
||||
import { getIsOpenTab } from '/@/utils/helper/routeHelper';
|
||||
import { getIsOpenTab, setCurrentTo } from '/@/utils/helper/routeHelper';
|
||||
|
||||
const { projectSetting } = useSetting();
|
||||
export function createGuard(router: Router) {
|
||||
@@ -17,7 +17,7 @@ export function createGuard(router: Router) {
|
||||
axiosCanceler = new AxiosCanceler();
|
||||
}
|
||||
router.beforeEach(async (to) => {
|
||||
const isOpen = getIsOpenTab(to.path);
|
||||
const isOpen = getIsOpenTab(to.fullPath);
|
||||
to.meta.inTab = isOpen;
|
||||
try {
|
||||
if (closeMessageOnSwitch) {
|
||||
@@ -30,6 +30,8 @@ export function createGuard(router: Router) {
|
||||
} catch (error) {
|
||||
console.warn('basic guard error:' + error);
|
||||
}
|
||||
setCurrentTo(to);
|
||||
return true;
|
||||
});
|
||||
openNProgress && createProgressGuard(router);
|
||||
createPermissionGuard(router);
|
||||
|
@@ -2,6 +2,7 @@ import type { Router } from 'vue-router';
|
||||
import { tabStore } from '/@/store/modules/tab';
|
||||
import { appStore } from '/@/store/modules/app';
|
||||
import { userStore } from '/@/store/modules/user';
|
||||
import { getParams } from '/@/utils/helper/routeHelper';
|
||||
|
||||
export function createPageLoadingGuard(router: Router) {
|
||||
let isFirstLoad = true;
|
||||
@@ -29,9 +30,16 @@ export function createPageLoadingGuard(router: Router) {
|
||||
}
|
||||
return true;
|
||||
});
|
||||
router.afterEach(async (to) => {
|
||||
router.afterEach(async (to, from) => {
|
||||
const { openRouterTransition, openPageLoading } = appStore.getProjectConfig;
|
||||
if ((!openRouterTransition && openPageLoading) || isFirstLoad || to.meta.afterCloseLoading) {
|
||||
const realToPath = to.path.replace(getParams(to), '');
|
||||
const realFormPath = from.path.replace(getParams(from), '');
|
||||
if (
|
||||
(!openRouterTransition && openPageLoading) ||
|
||||
isFirstLoad ||
|
||||
to.meta.afterCloseLoading ||
|
||||
realToPath === realFormPath
|
||||
) {
|
||||
setTimeout(() => {
|
||||
appStore.commitPageLoadingState(false);
|
||||
}, 110);
|
||||
|
@@ -107,7 +107,8 @@ export async function getFlatChildrenMenus(children: Menu[]) {
|
||||
function basicFilter(routes: RouteRecordNormalized[]) {
|
||||
return (menu: Menu) => {
|
||||
const matchRoute = routes.find((route) => route.path === menu.path);
|
||||
if (!matchRoute) return false;
|
||||
|
||||
if (!matchRoute) return true;
|
||||
menu.icon = menu.icon || matchRoute.meta.icon;
|
||||
menu.meta = matchRoute.meta;
|
||||
return true;
|
||||
|
@@ -45,6 +45,20 @@ const menu: MenuModule = {
|
||||
path: '/full-screen',
|
||||
name: '全屏',
|
||||
},
|
||||
{
|
||||
path: '/testTab',
|
||||
name: '带参Tab',
|
||||
children: [
|
||||
{
|
||||
path: '/id1',
|
||||
name: '带参tab1',
|
||||
},
|
||||
{
|
||||
path: '/id2',
|
||||
name: '带参tab2',
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
};
|
||||
|
@@ -3,8 +3,6 @@ import type { AppRouteRecordRaw, AppRouteModule } from '/@/router/types';
|
||||
import { DEFAULT_LAYOUT_COMPONENT, PAGE_NOT_FOUND_ROUTE, REDIRECT_ROUTE } from '../constant';
|
||||
import { genRouteModule } from '/@/utils/helper/routeHelper';
|
||||
|
||||
import LoginRoute from './modules/sys';
|
||||
|
||||
import dashboard from './modules/dashboard';
|
||||
|
||||
// demo
|
||||
@@ -48,5 +46,14 @@ export const RootRoute: AppRouteRecordRaw = {
|
||||
children: [],
|
||||
};
|
||||
|
||||
export const LoginRoute: AppRouteRecordRaw = {
|
||||
path: '/login',
|
||||
name: 'Login',
|
||||
component: () => import('/@/views/sys/login/Login.vue'),
|
||||
meta: {
|
||||
title: '登录',
|
||||
},
|
||||
};
|
||||
|
||||
// 基础路由 不用权限
|
||||
export const basicRoutes = [LoginRoute, RootRoute];
|
||||
|
@@ -96,5 +96,13 @@ export default {
|
||||
title: '全屏',
|
||||
},
|
||||
},
|
||||
{
|
||||
path: '/testTab/:id',
|
||||
name: 'TestTab',
|
||||
component: () => import('/@/views/demo/feat/tab-params/index.vue'),
|
||||
meta: {
|
||||
title: 'Tab带参',
|
||||
},
|
||||
},
|
||||
],
|
||||
} as AppRouteModule;
|
||||
|
@@ -1,12 +0,0 @@
|
||||
import type { AppRouteRecordRaw } from '/@/router/types';
|
||||
|
||||
const routes: AppRouteRecordRaw = {
|
||||
path: '/login',
|
||||
name: 'Login',
|
||||
component: () => import('/@/views/sys/login/Login.vue'),
|
||||
meta: {
|
||||
title: '登录',
|
||||
},
|
||||
};
|
||||
|
||||
export default routes;
|
1
src/router/types.d.ts
vendored
1
src/router/types.d.ts
vendored
@@ -39,6 +39,7 @@ export interface AppRouteRecordRaw extends Omit<RouteRecordRaw, 'meta'> {
|
||||
components?: any;
|
||||
children?: AppRouteRecordRaw[];
|
||||
props?: any;
|
||||
fullPath?: string;
|
||||
}
|
||||
|
||||
export interface Menu {
|
||||
|
@@ -11,6 +11,7 @@ import { appStore } from '/@/store/modules/app';
|
||||
import store from '/@/store';
|
||||
import router from '/@/router';
|
||||
import { PAGE_NOT_FOUND_ROUTE, REDIRECT_ROUTE } from '/@/router/constant';
|
||||
import { getCurrentTo } from '/@/utils/helper/routeHelper';
|
||||
|
||||
type CacheName = string | symbol | null | undefined;
|
||||
/**
|
||||
@@ -18,7 +19,10 @@ type CacheName = string | symbol | null | undefined;
|
||||
*/
|
||||
// declare namespace TabsStore {
|
||||
export interface TabItem {
|
||||
path: string;
|
||||
fullPath: string;
|
||||
path?: string;
|
||||
params?: any;
|
||||
query?: any;
|
||||
name?: CacheName;
|
||||
meta?: RouteMeta;
|
||||
}
|
||||
@@ -86,20 +90,21 @@ class Tab extends VuexModule {
|
||||
*/
|
||||
@Mutation
|
||||
commitAddTab(route: AppRouteRecordRaw | TabItem): void {
|
||||
const { path, name, meta } = route;
|
||||
const { path, name, meta, fullPath, params, query } = route as TabItem;
|
||||
// 404 页面不需要添加tab
|
||||
if (path === PageEnum.ERROR_PAGE) {
|
||||
return;
|
||||
} else if ([REDIRECT_ROUTE.name, PAGE_NOT_FOUND_ROUTE.name].includes(name as string)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// 已经存在的页面,不重复添加tab
|
||||
const hasTab = this.tabsState.some((tab) => {
|
||||
return tab.path === path;
|
||||
return tab.fullPath === fullPath;
|
||||
});
|
||||
if (hasTab) return;
|
||||
|
||||
this.tabsState.push({ path, name, meta });
|
||||
this.tabsState.push({ path, fullPath, name, meta, params, query });
|
||||
if (unref(getOpenKeepAliveRef) && name) {
|
||||
const noKeepAlive = meta && meta.ignoreKeepAlive;
|
||||
const hasName = this.keepAliveTabsState.includes(name);
|
||||
@@ -113,9 +118,9 @@ class Tab extends VuexModule {
|
||||
@Mutation
|
||||
commitCloseTab(route: AppRouteRecordRaw | TabItem): void {
|
||||
try {
|
||||
const { path, name, meta: { affix } = {} } = route;
|
||||
const { fullPath, name, meta: { affix } = {} } = route;
|
||||
if (affix) return;
|
||||
const index = this.tabsState.findIndex((item) => item.path === path);
|
||||
const index = this.tabsState.findIndex((item) => item.fullPath === fullPath);
|
||||
index !== -1 && this.tabsState.splice(index, 1);
|
||||
|
||||
if (unref(getOpenKeepAliveRef) && name) {
|
||||
@@ -153,7 +158,7 @@ class Tab extends VuexModule {
|
||||
|
||||
@Mutation
|
||||
closeMultipleTab({ pathList, nameList }: { pathList: string[]; nameList: string[] }): void {
|
||||
this.tabsState = toRaw(this.tabsState).filter((item) => !pathList.includes(item.path));
|
||||
this.tabsState = toRaw(this.tabsState).filter((item) => !pathList.includes(item.fullPath));
|
||||
if (unref(getOpenKeepAliveRef) && nameList) {
|
||||
this.keepAliveTabsState = toRaw(this.keepAliveTabsState).filter(
|
||||
(item) => !nameList.includes(item as string)
|
||||
@@ -172,7 +177,7 @@ class Tab extends VuexModule {
|
||||
for (const item of leftTabs) {
|
||||
const affix = item.meta ? item.meta.affix : false;
|
||||
if (!affix) {
|
||||
pathList.push(item.path);
|
||||
pathList.push(item.fullPath);
|
||||
nameList.push(item.name as string);
|
||||
}
|
||||
}
|
||||
@@ -181,13 +186,19 @@ class Tab extends VuexModule {
|
||||
}
|
||||
|
||||
@Action
|
||||
addTabByPathAction(to: AppRouteRecordRaw): void {
|
||||
to && this.commitAddTab((to as unknown) as AppRouteRecordRaw);
|
||||
addTabByPathAction(): void {
|
||||
const toRoute = getCurrentTo();
|
||||
if (!toRoute) return;
|
||||
const { meta } = toRoute;
|
||||
if (meta && meta.affix) {
|
||||
return;
|
||||
}
|
||||
this.commitAddTab((toRoute as unknown) as AppRouteRecordRaw);
|
||||
}
|
||||
|
||||
@Action
|
||||
closeRightTabAction(route: AppRouteRecordRaw | TabItem): void {
|
||||
const index = this.tabsState.findIndex((item) => item.path === route.path);
|
||||
const index = this.tabsState.findIndex((item) => item.fullPath === route.fullPath);
|
||||
|
||||
if (index >= 0 && index < this.tabsState.length - 1) {
|
||||
const rightTabs = this.tabsState.slice(index + 1, this.tabsState.length);
|
||||
@@ -197,7 +208,7 @@ class Tab extends VuexModule {
|
||||
for (const item of rightTabs) {
|
||||
const affix = item.meta ? item.meta.affix : false;
|
||||
if (!affix) {
|
||||
pathList.push(item.path);
|
||||
pathList.push(item.fullPath);
|
||||
nameList.push(item.name as string);
|
||||
}
|
||||
}
|
||||
@@ -207,16 +218,16 @@ class Tab extends VuexModule {
|
||||
|
||||
@Action
|
||||
closeOtherTabAction(route: AppRouteRecordRaw | TabItem): void {
|
||||
const closePathList = this.tabsState.map((item) => item.path);
|
||||
const closePathList = this.tabsState.map((item) => item.fullPath);
|
||||
const pathList: string[] = [];
|
||||
const nameList: string[] = [];
|
||||
closePathList.forEach((path) => {
|
||||
if (path !== route.path) {
|
||||
if (path !== route.fullPath) {
|
||||
const closeItem = this.tabsState.find((item) => item.path === path);
|
||||
if (!closeItem) return;
|
||||
const affix = closeItem.meta ? closeItem.meta.affix : false;
|
||||
if (!affix) {
|
||||
pathList.push(closeItem.path);
|
||||
pathList.push(closeItem.fullPath);
|
||||
nameList.push(closeItem.name as string);
|
||||
}
|
||||
}
|
||||
|
4
src/types/source.d.ts
vendored
4
src/types/source.d.ts
vendored
@@ -1,5 +1,7 @@
|
||||
declare module 'ant-design-vue/es/locale/zh_CN';
|
||||
declare module 'vue-draggable-resizable';
|
||||
declare module 'globby!/@/router/routes/modules/**/*.@(ts)';
|
||||
declare module 'globby!/@/router/menus/modules/**/*.@(ts)';
|
||||
|
||||
declare const React: string;
|
||||
declare module '*.bmp' {
|
||||
const src: string;
|
||||
|
@@ -16,9 +16,6 @@ class EventHub {
|
||||
|
||||
emit(eventName: string, data?: any) {
|
||||
if (this.cache[eventName] === undefined) return;
|
||||
console.log('======================');
|
||||
console.log(this.cache, eventName);
|
||||
console.log('======================');
|
||||
this.cache[eventName].forEach((fn) => fn(data));
|
||||
}
|
||||
off(eventName: string, fn: (data: any) => void) {
|
||||
|
@@ -4,7 +4,6 @@ import type { MenuModule, Menu, AppRouteRecordRaw } from '/@/router/types';
|
||||
import { findPath, forEach, treeMap, treeToList } from './treeHelper';
|
||||
import { cloneDeep } from 'lodash-es';
|
||||
|
||||
//
|
||||
export function getAllParentPath(treeData: any[], path: string) {
|
||||
const menuList = findPath(treeData, (n) => n.path === path) as Menu[];
|
||||
return (menuList || []).map((item) => item.path);
|
||||
@@ -14,6 +13,7 @@ export function flatMenus(menus: Menu[]) {
|
||||
return treeToList(menus);
|
||||
}
|
||||
|
||||
// 拼接父级路径
|
||||
function joinParentPath(list: any, node: any) {
|
||||
let allPaths = getAllParentPath(list, node.path);
|
||||
|
||||
@@ -26,7 +26,6 @@ function joinParentPath(list: any, node: any) {
|
||||
parentPath += /^\//.test(p) ? p : `/${p}`;
|
||||
});
|
||||
}
|
||||
|
||||
node.path = `${parentPath}${/^\//.test(node.path) ? node.path : `/${node.path}`}`.replace(
|
||||
/\/\//g,
|
||||
'/'
|
||||
@@ -34,6 +33,7 @@ function joinParentPath(list: any, node: any) {
|
||||
return node;
|
||||
}
|
||||
|
||||
// 解析菜单模块
|
||||
export function transformMenuModule(menuModule: MenuModule): Menu {
|
||||
const { menu } = menuModule;
|
||||
|
||||
|
@@ -1,11 +1,23 @@
|
||||
import type { AppRouteModule, AppRouteRecordRaw } from '/@/router/types';
|
||||
import type { RouteRecordRaw } from 'vue-router';
|
||||
import type { RouteLocationNormalized, RouteRecordRaw } from 'vue-router';
|
||||
|
||||
import { appStore } from '/@/store/modules/app';
|
||||
import { tabStore } from '/@/store/modules/tab';
|
||||
import { createRouter, createWebHashHistory } from 'vue-router';
|
||||
import { toRaw } from 'vue';
|
||||
import { PAGE_LAYOUT_COMPONENT } from '/@/router/constant';
|
||||
|
||||
let currentTo: RouteLocationNormalized | null = null;
|
||||
|
||||
export function getCurrentTo() {
|
||||
return currentTo;
|
||||
}
|
||||
|
||||
export function setCurrentTo(to: RouteLocationNormalized) {
|
||||
currentTo = to;
|
||||
}
|
||||
// 转化路由模块
|
||||
// 将多级转成2层。keepAlive问题
|
||||
export function genRouteModule(moduleList: AppRouteModule[]) {
|
||||
const ret: AppRouteRecordRaw[] = [];
|
||||
for (const routeMod of moduleList) {
|
||||
@@ -27,6 +39,7 @@ export function genRouteModule(moduleList: AppRouteModule[]) {
|
||||
return ret as RouteRecordRaw[];
|
||||
}
|
||||
|
||||
// 动态引入
|
||||
function asyncImportRoute(routes: AppRouteRecordRaw[]) {
|
||||
routes.forEach((item) => {
|
||||
const { component, children } = item;
|
||||
@@ -37,6 +50,7 @@ function asyncImportRoute(routes: AppRouteRecordRaw[]) {
|
||||
});
|
||||
}
|
||||
|
||||
// 将后台对象转成路由对象
|
||||
export function transformObjToRoute(routeList: AppRouteModule[]) {
|
||||
routeList.forEach((route) => {
|
||||
asyncImportRoute(route.routes);
|
||||
@@ -48,6 +62,7 @@ export function transformObjToRoute(routeList: AppRouteModule[]) {
|
||||
return routeList;
|
||||
}
|
||||
|
||||
//
|
||||
export function getIsOpenTab(toPath: string) {
|
||||
const { openKeepAlive, multiTabsSetting: { show } = {} } = appStore.getProjectConfig;
|
||||
|
||||
@@ -57,3 +72,13 @@ export function getIsOpenTab(toPath: string) {
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
export function getParams(data: any = {}) {
|
||||
const { params = {} } = data;
|
||||
let ret = '';
|
||||
Object.keys(params).forEach((key) => {
|
||||
const p = params[key];
|
||||
ret += `/${p}`;
|
||||
});
|
||||
return ret;
|
||||
}
|
||||
|
17
src/views/demo/feat/tab-params/index.vue
Normal file
17
src/views/demo/feat/tab-params/index.vue
Normal file
@@ -0,0 +1,17 @@
|
||||
<template>
|
||||
<div class="p-4"> Current Param : {{ params }} </div>
|
||||
</template>
|
||||
<script lang="ts">
|
||||
import { computed, defineComponent, unref } from 'vue';
|
||||
import { useRouter } from 'vue-router';
|
||||
export default defineComponent({
|
||||
setup() {
|
||||
const { currentRoute } = useRouter();
|
||||
return {
|
||||
params: computed(() => {
|
||||
return unref(currentRoute).params;
|
||||
}),
|
||||
};
|
||||
},
|
||||
});
|
||||
</script>
|
@@ -2,7 +2,7 @@
|
||||
<div />
|
||||
</template>
|
||||
<script lang="ts">
|
||||
import { defineComponent, onBeforeMount, unref } from 'vue';
|
||||
import { defineComponent, unref } from 'vue';
|
||||
|
||||
import { appStore } from '/@/store/modules/app';
|
||||
|
||||
@@ -11,21 +11,19 @@
|
||||
name: 'Redirect',
|
||||
setup() {
|
||||
const { currentRoute, replace } = useRouter();
|
||||
onBeforeMount(() => {
|
||||
const { params, query } = unref(currentRoute);
|
||||
const { path } = params;
|
||||
const _path = Array.isArray(path) ? path.join('/') : path;
|
||||
replace({
|
||||
path: '/' + _path,
|
||||
query,
|
||||
});
|
||||
const { openRouterTransition, openPageLoading } = appStore.getProjectConfig;
|
||||
if (openRouterTransition && openPageLoading) {
|
||||
setTimeout(() => {
|
||||
appStore.setPageLoadingAction(false);
|
||||
}, 0);
|
||||
}
|
||||
const { params, query } = unref(currentRoute);
|
||||
const { path } = params;
|
||||
const _path = Array.isArray(path) ? path.join('/') : path;
|
||||
replace({
|
||||
path: '/' + _path,
|
||||
query,
|
||||
});
|
||||
const { openRouterTransition, openPageLoading } = appStore.getProjectConfig;
|
||||
if (openRouterTransition && openPageLoading) {
|
||||
setTimeout(() => {
|
||||
appStore.setPageLoadingAction(false);
|
||||
}, 0);
|
||||
}
|
||||
return {};
|
||||
},
|
||||
});
|
||||
|
@@ -71,7 +71,7 @@ const viteConfig: UserConfig = {
|
||||
* boolean | 'terser' | 'esbuild'
|
||||
* @default 'terser'
|
||||
*/
|
||||
minify: isDevFn() ? false : 'terser',
|
||||
minify: 'terser',
|
||||
/**
|
||||
* 基本公共路径
|
||||
* @default '/'
|
||||
|
Reference in New Issue
Block a user