初始版本提交

This commit is contained in:
yaoyn
2024-02-05 09:15:37 +08:00
parent b52d4414be
commit 445292105f
1848 changed files with 236859 additions and 75 deletions

24
src/router/constant.ts Normal file
View File

@ -0,0 +1,24 @@
export const REDIRECT_NAME = 'Redirect';
export const PARENT_LAYOUT_NAME = 'ParentLayout';
export const PAGE_NOT_FOUND_NAME = 'PageNotFound';
export const EXCEPTION_COMPONENT = () => import('/@/views/sys/exception/Exception.vue');
/**
* @description: default layout
*/
export const LAYOUT = () => import('/@/layouts/default/index.vue');
/**
* @description: parent-layout
*/
export const getParentLayout = (_name?: string) => {
return () =>
new Promise((resolve) => {
resolve({
name: PARENT_LAYOUT_NAME,
});
});
};

147
src/router/guard/index.ts Normal file
View File

@ -0,0 +1,147 @@
import type { Router, RouteLocationNormalized } from 'vue-router';
import { useAppStoreWithOut } from '/@/store/modules/app';
import { useUserStoreWithOut } from '/@/store/modules/user';
import { useTransitionSetting } from '/@/hooks/setting/useTransitionSetting';
import { AxiosCanceler } from '/@/utils/http/axios/axiosCancel';
import { Modal, notification } from 'ant-design-vue';
import { warn } from '/@/utils/log';
import { unref } from 'vue';
import { setRouteChange } from '/@/logics/mitt/routeChange';
import { createPermissionGuard } from './permissionGuard';
import { createStateGuard } from './stateGuard';
import nProgress from 'nprogress';
import projectSetting from '/@/settings/projectSetting';
import { createParamMenuGuard } from './paramMenuGuard';
// Don't change the order of creation
export function setupRouterGuard(router: Router) {
createPageGuard(router);
createPageLoadingGuard(router);
createHttpGuard(router);
createScrollGuard(router);
createMessageGuard(router);
createProgressGuard(router);
createPermissionGuard(router);
createParamMenuGuard(router); // must after createPermissionGuard (menu has been built.)
createStateGuard(router);
}
/**
* Hooks for handling page state
*/
function createPageGuard(router: Router) {
const loadedPageMap = new Map<string, boolean>();
router.beforeEach(async (to) => {
// The page has already been loaded, it will be faster to open it again, you dont need to do loading and other processing
to.meta.loaded = !!loadedPageMap.get(to.path);
// Notify routing changes
setRouteChange(to);
return true;
});
router.afterEach((to) => {
loadedPageMap.set(to.path, true);
});
}
// Used to handle page loading status
function createPageLoadingGuard(router: Router) {
const userStore = useUserStoreWithOut();
const appStore = useAppStoreWithOut();
const { getOpenPageLoading } = useTransitionSetting();
router.beforeEach(async (to) => {
if (!userStore.getToken) {
return true;
}
if (to.meta.loaded) {
return true;
}
if (unref(getOpenPageLoading)) {
appStore.setPageLoadingAction(true);
return true;
}
return true;
});
router.afterEach(async () => {
if (unref(getOpenPageLoading)) {
// TODO Looking for a better way
// The timer simulates the loading time to prevent flashing too fast,
setTimeout(() => {
appStore.setPageLoading(false);
}, 220);
}
return true;
});
}
/**
* The interface used to close the current page to complete the request when the route is switched
* @param router
*/
function createHttpGuard(router: Router) {
const { removeAllHttpPending } = projectSetting;
let axiosCanceler: Nullable<AxiosCanceler>;
if (removeAllHttpPending) {
axiosCanceler = new AxiosCanceler();
}
router.beforeEach(async () => {
// Switching the route will delete the previous request
axiosCanceler?.removeAllPending();
return true;
});
}
// Routing switch back to the top
function createScrollGuard(router: Router) {
const isHash = (href: string) => {
return /^#/.test(href);
};
const body = document.body;
router.afterEach(async (to) => {
// scroll top
isHash((to as RouteLocationNormalized & { href: string })?.href) && body.scrollTo(0, 0);
return true;
});
}
/**
* Used to close the message instance when the route is switched
* @param router
*/
export function createMessageGuard(router: Router) {
const { closeMessageOnSwitch } = projectSetting;
router.beforeEach(async () => {
try {
if (closeMessageOnSwitch) {
Modal.destroyAll();
notification.destroy();
}
} catch (error) {
warn('message guard error:' + error);
}
return true;
});
}
export function createProgressGuard(router: Router) {
const { getOpenNProgress } = useTransitionSetting();
router.beforeEach(async (to) => {
if (to.meta.loaded) {
return true;
}
unref(getOpenNProgress) && nProgress.start();
return true;
});
router.afterEach(async () => {
unref(getOpenNProgress) && nProgress.done();
return true;
});
}

View File

@ -0,0 +1,68 @@
import type { Router } from 'vue-router';
import { configureDynamicParamsMenu } from '../helper/menuHelper';
import { Menu } from '../types';
import { PermissionModeEnum } from '/@/enums/appEnum';
import { useAppStoreWithOut } from '/@/store/modules/app';
import { usePermissionStoreWithOut } from '/@/store/modules/permission';
export function createParamMenuGuard(router: Router) {
const permissionStore = usePermissionStoreWithOut();
router.beforeEach(async (to, _, next) => {
console.log('createParamMenuGuard', to);
to.fullPath = (to.fullPath as string) + convertQuery(to);
// filter no name route
if (!to.name) {
next();
return;
}
// menu has been built.
if (!permissionStore.getIsDynamicAddedRoute) {
next();
return;
}
let menus: Menu[] = [];
if (isBackMode()) {
menus = permissionStore.getBackMenuList;
} else if (isRouteMappingMode()) {
menus = permissionStore.getFrontMenuList;
}
menus.forEach((item) => configureDynamicParamsMenu(item, to.params));
next();
});
}
const convertQuery = (to) => {
try {
if (to.meta.remark && to.meta.remark.indexOf('?') > -1 && to.fullPath.indexOf('?') <= -1) {
const str = to.meta.remark.substring(1);
const paramArray = str.split('&');
for (const param of paramArray) {
const p = param.split('=');
to.query[p[0]] = p[1];
}
return to.meta.remark;
}
return '';
} catch {
return '';
}
};
const getPermissionMode = () => {
const appStore = useAppStoreWithOut();
return appStore.getProjectConfig.permissionMode;
};
const isBackMode = () => {
return getPermissionMode() === PermissionModeEnum.BACK;
};
const isRouteMappingMode = () => {
return getPermissionMode() === PermissionModeEnum.ROUTE_MAPPING;
};

View File

@ -0,0 +1,117 @@
import type { Router, RouteRecordRaw } from 'vue-router';
import { usePermissionStoreWithOut } from '/@/store/modules/permission';
import { PageEnum } from '/@/enums/pageEnum';
import { useUserStoreWithOut } from '/@/store/modules/user';
import { PAGE_NOT_FOUND_ROUTE } from '/@/router/routes/basic';
import { RootRoute } from '/@/router/routes';
const LOGIN_PATH = PageEnum.BASE_LOGIN;
const ROOT_PATH = RootRoute.path;
const whitePathList: PageEnum[] = [LOGIN_PATH];
export function createPermissionGuard(router: Router) {
const userStore = useUserStoreWithOut();
const permissionStore = usePermissionStoreWithOut();
router.beforeEach(async (to, from, next) => {
if (
from.path === ROOT_PATH &&
to.path === PageEnum.BASE_HOME &&
userStore.getUserInfo.homePath &&
userStore.getUserInfo.homePath !== PageEnum.BASE_HOME
) {
next(userStore.getUserInfo.homePath);
return;
}
const token = userStore.getToken;
// Whitelist can be directly entered
if (whitePathList.includes(to.path as PageEnum)) {
if (to.path === LOGIN_PATH && token) {
const isSessionTimeout = userStore.getSessionTimeout;
try {
await userStore.afterLoginAction();
if (!isSessionTimeout) {
next((to.query?.redirect as string) || '/');
return;
}
} catch {}
}
next();
return;
}
// token does not exist
if (!token) {
// You can access without permission. You need to set the routing meta.ignoreAuth to true
if (to.meta.ignoreAuth) {
next();
return;
}
// redirect login page
const redirectData: { path: string; replace: boolean; query?: Recordable<string> } = {
path: LOGIN_PATH,
replace: true,
};
if (to.path) {
redirectData.query = {
...redirectData.query,
redirect: to.path,
};
}
next(redirectData);
return;
}
// Jump to the 404 page after processing the login
if (
from.path === LOGIN_PATH &&
to.name === PAGE_NOT_FOUND_ROUTE.name &&
to.fullPath !== (userStore.getUserInfo.homePath || PageEnum.BASE_HOME)
) {
next(userStore.getUserInfo.homePath || PageEnum.BASE_HOME);
return;
}
// get userinfo while last fetch time is empty
if (userStore.getLastUpdateTime === 0) {
try {
await userStore.getUserInfoAction();
} catch (err) {
next();
return;
}
}
if (permissionStore.getIsDynamicAddedRoute) {
next();
return;
}
const routes = await permissionStore.buildRoutesAction(false);
routes.forEach((route) => {
router.addRoute(route as unknown as RouteRecordRaw);
});
router.addRoute(PAGE_NOT_FOUND_ROUTE as unknown as RouteRecordRaw);
permissionStore.setDynamicAddedRoute(true);
if (to.name === PAGE_NOT_FOUND_ROUTE.name) {
// 动态添加路由后此处应当重定向到fullPath否则会加载404页面内容
next({ path: to.fullPath, replace: true, query: to.query });
} else {
const redirectPath = (from.query.redirect || to.path) as string;
const redirect = decodeURIComponent(redirectPath);
const nextData = to.path === redirect ? { ...to, replace: true } : { path: redirect };
next(nextData);
}
});
}

View File

@ -0,0 +1,24 @@
import type { Router } from 'vue-router';
import { useAppStore } from '/@/store/modules/app';
import { useMultipleTabStore } from '/@/store/modules/multipleTab';
import { useUserStore } from '/@/store/modules/user';
import { usePermissionStore } from '/@/store/modules/permission';
import { PageEnum } from '/@/enums/pageEnum';
import { removeTabChangeListener } from '/@/logics/mitt/routeChange';
export function createStateGuard(router: Router) {
router.afterEach((to) => {
// Just enter the login page and clear the authentication information
if (to.path === PageEnum.BASE_LOGIN) {
const tabStore = useMultipleTabStore();
const userStore = useUserStore();
const appStore = useAppStore();
const permissionStore = usePermissionStore();
appStore.resetAllState();
permissionStore.resetState();
tabStore.resetState();
userStore.resetState();
removeTabChangeListener();
}
});
}

View File

@ -0,0 +1,95 @@
import { AppRouteModule } from '/@/router/types';
import type { MenuModule, Menu, AppRouteRecordRaw } from '/@/router/types';
import { findPath, treeMap } from '/@/utils/helper/treeHelper';
import { cloneDeep } from 'lodash-es';
import { isUrl } from '/@/utils/is';
import { RouteParams } from 'vue-router';
import { toRaw } from 'vue';
export function getAllParentPath<T = Recordable>(treeData: T[], path: string) {
const menuList = findPath(treeData, (n) => n.path === path) as Menu[];
return (menuList || []).map((item) => item.path);
}
function joinParentPath(menus: Menu[], parentPath = '') {
for (let index = 0; index < menus.length; index++) {
const menu = menus[index];
// https://next.router.vuejs.org/guide/essentials/nested-routes.html
// Note that nested paths that start with / will be treated as a root path.
// This allows you to leverage the component nesting without having to use a nested URL.
if (!(menu.path.startsWith('/') || isUrl(menu.path))) {
// path doesn't start with /, nor is it a url, join parent path
menu.path = `${parentPath}/${menu.path}`;
}
if (menu?.children?.length) {
joinParentPath(menu.children, menu.meta?.hidePathForChildren ? parentPath : menu.path);
}
}
}
// Parsing the menu module
export function transformMenuModule(menuModule: MenuModule): Menu {
const { menu } = menuModule;
const menuList = [menu];
joinParentPath(menuList);
return menuList[0];
}
export function transformRouteToMenu(routeModList: AppRouteModule[], routerMapping = false) {
const cloneRouteModList = cloneDeep(routeModList);
const routeList: AppRouteRecordRaw[] = [];
cloneRouteModList.forEach((item) => {
if (routerMapping && item.meta.hideChildrenInMenu && typeof item.redirect === 'string') {
item.path = item.redirect;
}
if (item.meta?.single) {
const realItem = item?.children?.[0];
realItem && routeList.push(realItem);
} else {
routeList.push(item);
}
});
const list = treeMap(routeList, {
conversion: (node: AppRouteRecordRaw) => {
const { meta: { title, hideMenu = false } = {} } = node;
return {
...(node.meta || {}),
meta: node.meta,
name: title,
hideMenu,
path: node.path,
...(node.redirect ? { redirect: node.redirect } : {}),
};
},
});
joinParentPath(list);
return cloneDeep(list);
}
/**
* config menu with given params
*/
const menuParamRegex = /(?::)([\s\S]+?)((?=\/)|$)/g;
export function configureDynamicParamsMenu(menu: Menu, params: RouteParams) {
const { path, paramPath } = toRaw(menu);
let realPath = paramPath ? paramPath : path;
const matchArr = realPath.match(menuParamRegex);
matchArr?.forEach((it) => {
const realIt = it.substr(1);
if (params[realIt]) {
realPath = realPath.replace(`:${realIt}`, params[realIt] as string);
}
});
// save original param path.
if (!paramPath && matchArr && matchArr.length > 0) {
menu.paramPath = path;
}
menu.path = realPath;
// children
menu.children?.forEach((item) => configureDynamicParamsMenu(item, params));
}

View File

@ -0,0 +1,165 @@
import type { AppRouteModule, AppRouteRecordRaw } from '/@/router/types';
import type { Router, RouteRecordNormalized } from 'vue-router';
import { getParentLayout, LAYOUT, EXCEPTION_COMPONENT } from '/@/router/constant';
import { cloneDeep, omit } from 'lodash-es';
import { warn } from '/@/utils/log';
import { createRouter, createWebHashHistory } from 'vue-router';
export type LayoutMapKey = 'LAYOUT';
const IFRAME = () => import('/@/views/sys/iframe/FrameBlank.vue');
const LayoutMap = new Map<string, () => Promise<typeof import('*.vue')>>();
LayoutMap.set('LAYOUT', LAYOUT);
LayoutMap.set('IFRAME', IFRAME);
let dynamicViewsModules: Record<string, () => Promise<Recordable>>;
// Dynamic introduction
function asyncImportRoute(routes: AppRouteRecordRaw[] | undefined) {
dynamicViewsModules = dynamicViewsModules || import.meta.glob('../../views/**/*.{vue,tsx}');
if (!routes) return;
routes.forEach((item) => {
if (!item.component && item.meta?.frameSrc) {
item.component = 'IFRAME';
}
const { component, name } = item;
const { children } = item;
if (component) {
const layoutFound = LayoutMap.get(component.toUpperCase());
if (layoutFound) {
item.component = layoutFound;
} else {
item.component = dynamicImport(dynamicViewsModules, component as string);
}
} else if (name) {
item.component = getParentLayout();
}
children && asyncImportRoute(children);
});
}
function dynamicImport(
dynamicViewsModules: Record<string, () => Promise<Recordable>>,
component: string,
) {
const keys = Object.keys(dynamicViewsModules);
const matchKeys = keys.filter((key) => {
const k = key.replace('../../views', '');
const startFlag = component.startsWith('/');
const endFlag = component.endsWith('.vue') || component.endsWith('.tsx');
const startIndex = startFlag ? 0 : 1;
const lastIndex = endFlag ? k.length : k.lastIndexOf('.');
return k.substring(startIndex, lastIndex) === component;
});
if (matchKeys?.length === 1) {
const matchKey = matchKeys[0];
return dynamicViewsModules[matchKey];
} else if (matchKeys?.length > 1) {
warn(
'Please do not create `.vue` and `.TSX` files with the same file name in the same hierarchical directory under the views folder. This will cause dynamic introduction failure',
);
return;
} else {
warn('在src/views/下找不到`' + component + '.vue` 或 `' + component + '.tsx`, 请自行创建!');
return EXCEPTION_COMPONENT;
}
}
// Turn background objects into routing objects
export function transformObjToRoute<T = AppRouteModule>(routeList: AppRouteModule[]): T[] {
routeList.forEach((route) => {
const component = route.component as string;
if (component) {
if (component.toUpperCase() === 'LAYOUT') {
route.component = LayoutMap.get(component.toUpperCase());
} else {
route.children = [cloneDeep(route)];
route.component = LAYOUT;
route.name = `${route.name}Parent`;
route.path = '';
const meta = route.meta || {};
meta.single = true;
meta.affix = false;
route.meta = meta;
}
} else {
warn('请正确配置路由:' + route?.name + '的component属性');
}
route.children && asyncImportRoute(route.children);
});
return routeList as unknown as T[];
}
/**
* Convert multi-level routing to level 2 routing
*/
export function flatMultiLevelRoutes(routeModules: AppRouteModule[]) {
const modules: AppRouteModule[] = cloneDeep(routeModules);
for (let index = 0; index < modules.length; index++) {
const routeModule = modules[index];
if (!isMultipleRoute(routeModule)) {
continue;
}
promoteRouteLevel(routeModule);
}
return modules;
}
// Routing level upgrade
function promoteRouteLevel(routeModule: AppRouteModule) {
// Use vue-router to splice menus
let router: Router | null = createRouter({
routes: [routeModule as unknown as RouteRecordNormalized],
history: createWebHashHistory(),
});
const routes = router.getRoutes();
addToChildren(routes, routeModule.children || [], routeModule);
router = null;
routeModule.children = routeModule.children?.map((item) => omit(item, 'children'));
}
// Add all sub-routes to the secondary route
function addToChildren(
routes: RouteRecordNormalized[],
children: AppRouteRecordRaw[],
routeModule: AppRouteModule,
) {
for (let index = 0; index < children.length; index++) {
const child = children[index];
const route = routes.find((item) => item.name === child.name);
if (!route) {
continue;
}
routeModule.children = routeModule.children || [];
if (!routeModule.children.find((item) => item.name === route.name)) {
routeModule.children?.push(route as unknown as AppRouteModule);
}
if (child.children?.length) {
addToChildren(routes, child.children, routeModule);
}
}
}
// Determine whether the level exceeds 2 levels
function isMultipleRoute(routeModule: AppRouteModule) {
if (!routeModule || !Reflect.has(routeModule, 'children') || !routeModule.children?.length) {
return false;
}
const children = routeModule.children;
let flag = false;
for (let index = 0; index < children.length; index++) {
const child = children[index];
if (child.children?.length) {
flag = true;
break;
}
}
return flag;
}

41
src/router/index.ts Normal file
View File

@ -0,0 +1,41 @@
import type { RouteRecordRaw } from 'vue-router';
import type { App } from 'vue';
import { createRouter, createWebHashHistory } from 'vue-router';
import { basicRoutes } from './routes';
// 白名单应该包含基本静态路由
const WHITE_NAME_LIST: string[] = [];
const getRouteNames = (array: any[]) =>
array.forEach((item) => {
WHITE_NAME_LIST.push(item.name);
getRouteNames(item.children || []);
});
getRouteNames(basicRoutes);
// app router
// 创建一个可以被 Vue 应用程序使用的路由实例
export const router = createRouter({
// 创建一个 hash 历史记录。
history: createWebHashHistory(import.meta.env.VITE_PUBLIC_PATH),
// 应该添加到路由的初始路由列表。
routes: basicRoutes as unknown as RouteRecordRaw[],
// 是否应该禁止尾部斜杠。默认为假
strict: true,
scrollBehavior: () => ({ left: 0, top: 0 }),
});
// reset router
export function resetRouter() {
router.getRoutes().forEach((route) => {
const { name } = route;
if (name && !WHITE_NAME_LIST.includes(name as string)) {
router.hasRoute(name) && router.removeRoute(name);
}
});
}
// config router
export function setupRouter(app: App<Element>) {
app.use(router);
}

126
src/router/menus/index.ts Normal file
View File

@ -0,0 +1,126 @@
import type { Menu, MenuModule } from '/@/router/types';
import type { RouteRecordNormalized } from 'vue-router';
import { useAppStoreWithOut } from '/@/store/modules/app';
import { usePermissionStore } from '/@/store/modules/permission';
import { transformMenuModule, getAllParentPath } from '/@/router/helper/menuHelper';
import { filter } from '/@/utils/helper/treeHelper';
import { isUrl } from '/@/utils/is';
import { router } from '/@/router';
import { PermissionModeEnum } from '/@/enums/appEnum';
import { pathToRegexp } from 'path-to-regexp';
const modules = import.meta.glob('./modules/**/*.ts', { eager: true });
const menuModules: MenuModule[] = [];
Object.keys(modules).forEach((key) => {
const mod = modules[key].default || {};
const modList = Array.isArray(mod) ? [...mod] : [mod];
menuModules.push(...modList);
});
// ===========================
// ==========Helper===========
// ===========================
const getPermissionMode = () => {
const appStore = useAppStoreWithOut();
return appStore.getProjectConfig.permissionMode;
};
const isBackMode = () => {
return getPermissionMode() === PermissionModeEnum.BACK;
};
const isRouteMappingMode = () => {
return getPermissionMode() === PermissionModeEnum.ROUTE_MAPPING;
};
const isRoleMode = () => {
return getPermissionMode() === PermissionModeEnum.ROLE;
};
const staticMenus: Menu[] = [];
(() => {
menuModules.sort((a, b) => {
return (a.orderNo || 0) - (b.orderNo || 0);
});
for (const menu of menuModules) {
staticMenus.push(transformMenuModule(menu));
}
})();
async function getAsyncMenus() {
const permissionStore = usePermissionStore();
if (isBackMode()) {
return permissionStore.getBackMenuList.filter((item) => !item.meta?.hideMenu && !item.hideMenu);
}
if (isRouteMappingMode()) {
return permissionStore.getFrontMenuList.filter((item) => !item.hideMenu);
}
return staticMenus;
}
export const getMenus = async (): Promise<Menu[]> => {
const menus = await getAsyncMenus();
if (isRoleMode()) {
const routes = router.getRoutes();
return filter(menus, basicFilter(routes));
}
return menus;
};
export async function getCurrentParentPath(currentPath: string) {
const menus = await getAsyncMenus();
const allParentPath = await getAllParentPath(menus, currentPath);
return allParentPath?.[0];
}
// Get the level 1 menu, delete children
export async function getShallowMenus(): Promise<Menu[]> {
const menus = await getAsyncMenus();
const shallowMenuList = menus.map((item) => ({ ...item, children: undefined }));
if (isRoleMode()) {
const routes = router.getRoutes();
return shallowMenuList.filter(basicFilter(routes));
}
return shallowMenuList;
}
// Get the children of the menu
export async function getChildrenMenus(parentPath: string) {
const menus = await getMenus();
const parent = menus.find((item) => item.path === parentPath);
if (!parent || !parent.children || !!parent?.meta?.hideChildrenInMenu) {
return [] as Menu[];
}
if (isRoleMode()) {
const routes = router.getRoutes();
return filter(parent.children, basicFilter(routes));
}
return parent.children;
}
function basicFilter(routes: RouteRecordNormalized[]) {
return (menu: Menu) => {
const matchRoute = routes.find((route) => {
if (isUrl(menu.path)) return true;
if (route.meta?.carryParam) {
return pathToRegexp(route.path).test(menu.path);
}
const isSame = route.path === menu.path;
if (!isSame) return false;
if (route.meta?.ignoreAuth) return true;
return isSame || pathToRegexp(route.path).test(menu.path);
});
if (!matchRoute) return false;
menu.icon = (menu.icon || matchRoute.meta.icon) as string;
menu.meta = matchRoute.meta;
return true;
};
}

152
src/router/routes/basic.ts Normal file
View File

@ -0,0 +1,152 @@
import type { AppRouteRecordRaw } from '/@/router/types';
import { t } from '/@/hooks/web/useI18n';
import {
REDIRECT_NAME,
LAYOUT,
EXCEPTION_COMPONENT,
PAGE_NOT_FOUND_NAME,
} from '/@/router/constant';
import { PageEnum } from '/@/enums/pageEnum';
// 404 on a page
export const PAGE_NOT_FOUND_ROUTE: AppRouteRecordRaw = {
path: '/:path(.*)*',
name: PAGE_NOT_FOUND_NAME,
component: LAYOUT,
meta: {
title: 'ErrorPage',
hideBreadcrumb: true,
hideMenu: true,
},
children: [
{
path: '/:path(.*)*',
name: PAGE_NOT_FOUND_NAME,
component: EXCEPTION_COMPONENT,
meta: {
title: 'ErrorPage',
hideBreadcrumb: true,
hideMenu: true,
},
},
],
};
export const REDIRECT_ROUTE: AppRouteRecordRaw = {
path: '/redirect',
component: LAYOUT,
name: 'RedirectTo',
meta: {
title: REDIRECT_NAME,
hideBreadcrumb: true,
hideMenu: true,
},
children: [
{
path: '/redirect/:path(.*)',
name: REDIRECT_NAME,
component: () => import('/@/views/sys/redirect/index.vue'),
meta: {
title: REDIRECT_NAME,
hideBreadcrumb: true,
},
},
],
};
export const ERROR_LOG_ROUTE: AppRouteRecordRaw = {
path: '/error-log',
name: 'ErrorLog',
component: LAYOUT,
redirect: '/error-log/list',
meta: {
title: 'ErrorLog',
hideBreadcrumb: true,
hideChildrenInMenu: true,
},
children: [
{
path: 'list',
name: 'ErrorLogList',
component: () => import('/@/views/sys/error-log/index.vue'),
meta: {
title: t('错误日志列表'),
hideBreadcrumb: true,
currentActiveMenu: '/error-log',
},
},
],
};
export const SYSTEM_ROUTE: AppRouteRecordRaw = {
path: '/dashboard',
name: 'Dashboard',
component: LAYOUT,
redirect: '/dashboard/analysis',
meta: {
orderNo: 10,
icon: 'ion:grid-outline',
title: t('分析页'),
},
children: [
{
path: 'analysis',
name: 'Analysis',
component: () => import('/@/views/dashboard/analysis/index.vue'),
meta: {
affix: true,
title: t('分析页'),
},
},
{
path: 'workbench',
name: 'Workbench',
component: () => import('/@/views/dashboard/workbench/index.vue'),
meta: {
title: t('工作台'),
},
},
],
};
export const USERCENTER_ROUTE: AppRouteRecordRaw = {
path: '/user-center',
name: 'UserCenter',
component: LAYOUT,
redirect: PageEnum.USER_CENTER,
meta: {
title: t('用户中心'),
},
children: [
{
path: 'info',
name: 'UserInfo',
component: () => import('/@/views/system/setting/index.vue'),
meta: {
title: t('用户中心'),
icon: 'ant-design:user-outlined',
},
},
],
};
// export const CUSTOMFORM_ROUTE: AppRouteRecordRaw = {
// path: '/custom-form/:id',
// name: 'CustomForm',
// component: LAYOUT,
// // redirect: PageEnum.CUSTOM_FORM,
// meta: {
// title: t('自定义表单'),
// },
// children: [
// {
// path: 'list',
// name: 'CustomFormList',
// component: () => import('/@/views/form/template/index.vue'),
// meta: {
// title: `自定义表单`,
// icon: 'ant-design:user-outlined',
// },
// },
// ],
// };

View File

@ -0,0 +1,56 @@
import type { AppRouteRecordRaw, AppRouteModule } from '/@/router/types';
import {
PAGE_NOT_FOUND_ROUTE,
REDIRECT_ROUTE,
SYSTEM_ROUTE,
USERCENTER_ROUTE,
// CUSTOMFORM_ROUTE,
} from '/@/router/routes/basic';
import { mainOutRoutes } from './mainOut';
import { PageEnum } from '/@/enums/pageEnum';
import { t } from '/@/hooks/web/useI18n';
const modules = import.meta.glob('./modules/**/*.ts', { eager: true });
const routeModuleList: AppRouteModule[] = [];
Object.keys(modules).forEach((key) => {
const mod = modules[key].default || {};
const modList = Array.isArray(mod) ? [...mod] : [mod];
routeModuleList.push(...modList);
});
export const asyncRoutes = [PAGE_NOT_FOUND_ROUTE, ...routeModuleList];
export const RootRoute: AppRouteRecordRaw = {
path: '/',
name: 'Root',
redirect: PageEnum.BASE_HOME,
meta: {
title: 'Root',
},
};
export const LoginRoute: AppRouteRecordRaw = {
path: '/login',
name: 'Login',
component: () => import('/@/views/sys/login/Login.vue'),
meta: {
title: t('登录页'),
},
};
// Basic routing without permissions
// 未经许可的基本路由
export const basicRoutes = [
LoginRoute,
RootRoute,
...mainOutRoutes,
REDIRECT_ROUTE,
PAGE_NOT_FOUND_ROUTE,
SYSTEM_ROUTE,
USERCENTER_ROUTE,
// CUSTOMFORM_ROUTE,
];

View File

@ -0,0 +1,22 @@
/**
The routing of this file will not show the layout.
It is an independent new page.
the contents of the file still need to log in to access
*/
import type { AppRouteModule } from '/@/router/types';
// test
// http:ip:port/main-out
export const mainOutRoutes: AppRouteModule[] = [
{
path: '/main-out',
name: 'MainOut',
component: () => import('/@/views/demo/main-out/index.vue'),
meta: {
title: 'MainOut',
ignoreAuth: true,
},
},
];
export const mainOutRouteNames = mainOutRoutes.map((item) => item.name);

View File

@ -0,0 +1,31 @@
import type { AppRouteModule } from '/@/router/types';
import { LAYOUT } from '/@/router/constant';
import { t } from '/@/hooks/web/useI18n';
const about: AppRouteModule = {
path: '/about',
name: 'About',
component: LAYOUT,
redirect: '/about/index',
meta: {
hideChildrenInMenu: true,
icon: 'simple-icons:about-dot-me',
title: t('routes.dashboard.about'),
orderNo: 100000,
},
children: [
{
path: 'index',
name: 'AboutPage',
component: () => import('/@/views/sys/about/index.vue'),
meta: {
title: t('routes.dashboard.about'),
icon: 'simple-icons:about-dot-me',
hideMenu: true,
},
},
],
};
export default about;

View File

@ -0,0 +1,37 @@
import type { AppRouteModule } from '/@/router/types';
import { LAYOUT } from '/@/router/constant';
import { t } from '/@/hooks/web/useI18n';
const dashboard: AppRouteModule = {
path: '/dashboard',
name: 'Dashboard',
component: LAYOUT,
redirect: '/dashboard/analysis',
meta: {
orderNo: 10,
icon: 'ion:grid-outline',
title: t('Dashboard'),
},
children: [
{
path: 'analysis',
name: 'Analysis',
component: () => import('/@/views/dashboard/analysis/index.vue'),
meta: {
// affix: true,
title: t('分析页'),
},
},
{
path: 'workbench',
name: 'Workbench',
component: () => import('/@/views/dashboard/workbench/index.vue'),
meta: {
title: t('工作台'),
},
},
],
};
export default dashboard;

View File

@ -0,0 +1,80 @@
import type { AppRouteModule } from '/@/router/types';
import { getParentLayout, LAYOUT } from '/@/router/constant';
import { t } from '/@/hooks/web/useI18n';
const charts: AppRouteModule = {
path: '/charts',
name: 'Charts',
component: LAYOUT,
redirect: '/charts/echarts/map',
meta: {
orderNo: 500,
icon: 'ion:bar-chart-outline',
title: t('routes.demo.charts.charts'),
},
children: [
{
path: 'baiduMap',
name: 'BaiduMap',
meta: {
title: t('routes.demo.charts.baiduMap'),
},
component: () => import('/@/views/demo/charts/map/Baidu.vue'),
},
{
path: 'aMap',
name: 'AMap',
meta: {
title: t('routes.demo.charts.aMap'),
},
component: () => import('/@/views/demo/charts/map/Gaode.vue'),
},
{
path: 'googleMap',
name: 'GoogleMap',
meta: {
title: t('routes.demo.charts.googleMap'),
},
component: () => import('/@/views/demo/charts/map/Google.vue'),
},
{
path: 'echarts',
name: 'Echarts',
component: getParentLayout('Echarts'),
meta: {
title: 'Echarts',
},
redirect: '/charts/echarts/map',
children: [
{
path: 'map',
name: 'Map',
component: () => import('/@/views/demo/charts/Map.vue'),
meta: {
title: t('routes.demo.charts.map'),
},
},
{
path: 'line',
name: 'Line',
component: () => import('/@/views/demo/charts/Line.vue'),
meta: {
title: t('routes.demo.charts.line'),
},
},
{
path: 'pie',
name: 'Pie',
component: () => import('/@/views/demo/charts/Pie.vue'),
meta: {
title: t('routes.demo.charts.pie'),
},
},
],
},
],
};
export default charts;

View File

@ -0,0 +1,564 @@
import type { AppRouteModule } from '/@/router/types';
import { getParentLayout, LAYOUT } from '/@/router/constant';
import { t } from '/@/hooks/web/useI18n';
const comp: AppRouteModule = {
path: '/comp',
name: 'Comp',
component: LAYOUT,
redirect: '/comp/basic',
meta: {
orderNo: 30,
icon: 'ion:layers-outline',
title: t('routes.demo.comp.comp'),
},
children: [
{
path: 'basic',
name: 'BasicDemo',
component: () => import('/@/views/demo/comp/button/index.vue'),
meta: {
title: t('routes.demo.comp.basic'),
},
},
{
path: 'form',
name: 'FormDemo',
redirect: '/comp/form/basic',
component: getParentLayout('FormDemo'),
meta: {
// icon: 'mdi:form-select',
title: t('routes.demo.form.form'),
},
children: [
{
path: 'basic',
name: 'FormBasicDemo',
component: () => import('/@/views/demo/form/index.vue'),
meta: {
title: t('routes.demo.form.basic'),
},
},
{
path: 'useForm',
name: 'UseFormDemo',
component: () => import('/@/views/demo/form/UseForm.vue'),
meta: {
title: t('routes.demo.form.useForm'),
},
},
{
path: 'refForm',
name: 'RefFormDemo',
component: () => import('/@/views/demo/form/RefForm.vue'),
meta: {
title: t('routes.demo.form.refForm'),
},
},
{
path: 'advancedForm',
name: 'AdvancedFormDemo',
component: () => import('/@/views/demo/form/AdvancedForm.vue'),
meta: {
title: t('routes.demo.form.advancedForm'),
},
},
{
path: 'ruleForm',
name: 'RuleFormDemo',
component: () => import('/@/views/demo/form/RuleForm.vue'),
meta: {
title: t('routes.demo.form.ruleForm'),
},
},
{
path: 'dynamicForm',
name: 'DynamicFormDemo',
component: () => import('/@/views/demo/form/DynamicForm.vue'),
meta: {
title: t('routes.demo.form.dynamicForm'),
},
},
{
path: 'customerForm',
name: 'CustomerFormDemo',
component: () => import('/@/views/demo/form/CustomerForm.vue'),
meta: {
title: t('routes.demo.form.customerForm'),
},
},
{
path: 'appendForm',
name: 'appendFormDemo',
component: () => import('/@/views/demo/form/AppendForm.vue'),
meta: {
title: t('routes.demo.form.appendForm'),
},
},
{
path: 'tabsForm',
name: 'tabsFormDemo',
component: () => import('/@/views/demo/form/TabsForm.vue'),
meta: {
title: t('routes.demo.form.tabsForm'),
},
},
],
},
{
path: 'table',
name: 'TableDemo',
redirect: '/comp/table/basic',
component: getParentLayout('TableDemo'),
meta: {
// icon: 'carbon:table-split',
title: t('routes.demo.table.table'),
},
children: [
{
path: 'basic',
name: 'TableBasicDemo',
component: () => import('/@/views/demo/table/Basic.vue'),
meta: {
title: t('routes.demo.table.basic'),
},
},
{
path: 'treeTable',
name: 'TreeTableDemo',
component: () => import('/@/views/demo/table/TreeTable.vue'),
meta: {
title: t('routes.demo.table.treeTable'),
},
},
{
path: 'fetchTable',
name: 'FetchTableDemo',
component: () => import('/@/views/demo/table/FetchTable.vue'),
meta: {
title: t('routes.demo.table.fetchTable'),
},
},
{
path: 'fixedColumn',
name: 'FixedColumnDemo',
component: () => import('/@/views/demo/table/FixedColumn.vue'),
meta: {
title: t('routes.demo.table.fixedColumn'),
},
},
{
path: 'customerCell',
name: 'CustomerCellDemo',
component: () => import('/@/views/demo/table/CustomerCell.vue'),
meta: {
title: t('routes.demo.table.customerCell'),
},
},
{
path: 'formTable',
name: 'FormTableDemo',
component: () => import('/@/views/demo/table/FormTable.vue'),
meta: {
title: t('routes.demo.table.formTable'),
},
},
{
path: 'useTable',
name: 'UseTableDemo',
component: () => import('/@/views/demo/table/UseTable.vue'),
meta: {
title: t('routes.demo.table.useTable'),
},
},
{
path: 'refTable',
name: 'RefTableDemo',
component: () => import('/@/views/demo/table/RefTable.vue'),
meta: {
title: t('routes.demo.table.refTable'),
},
},
{
path: 'multipleHeader',
name: 'MultipleHeaderDemo',
component: () => import('/@/views/demo/table/MultipleHeader.vue'),
meta: {
title: t('routes.demo.table.multipleHeader'),
},
},
{
path: 'mergeHeader',
name: 'MergeHeaderDemo',
component: () => import('/@/views/demo/table/MergeHeader.vue'),
meta: {
title: t('routes.demo.table.mergeHeader'),
},
},
{
path: 'expandTable',
name: 'ExpandTableDemo',
component: () => import('/@/views/demo/table/ExpandTable.vue'),
meta: {
title: t('routes.demo.table.expandTable'),
},
},
{
path: 'fixedHeight',
name: 'FixedHeightDemo',
component: () => import('/@/views/demo/table/FixedHeight.vue'),
meta: {
title: t('routes.demo.table.fixedHeight'),
},
},
{
path: 'footerTable',
name: 'FooterTableDemo',
component: () => import('/@/views/demo/table/FooterTable.vue'),
meta: {
title: t('routes.demo.table.footerTable'),
},
},
{
path: 'editCellTable',
name: 'EditCellTableDemo',
component: () => import('/@/views/demo/table/EditCellTable.vue'),
meta: {
title: t('routes.demo.table.editCellTable'),
},
},
{
path: 'editRowTable',
name: 'EditRowTableDemo',
component: () => import('/@/views/demo/table/EditRowTable.vue'),
meta: {
title: t('routes.demo.table.editRowTable'),
},
},
{
path: 'authColumn',
name: 'AuthColumnDemo',
component: () => import('/@/views/demo/table/AuthColumn.vue'),
meta: {
title: t('routes.demo.table.authColumn'),
},
},
{
path: 'resizeParentHeightTable',
name: 'ResizeParentHeightTable',
component: () => import('/@/views/demo/table/ResizeParentHeightTable.vue'),
meta: {
title: t('routes.demo.table.resizeParentHeightTable'),
},
},
],
},
{
path: 'transition',
name: 'transitionDemo',
component: () => import('/@/views/demo/comp/transition/index.vue'),
meta: {
title: t('routes.demo.comp.transition'),
},
},
{
path: 'cropper',
name: 'CropperDemo',
component: () => import('/@/views/demo/comp/cropper/index.vue'),
meta: {
title: t('routes.demo.comp.cropperImage'),
},
},
{
path: 'timestamp',
name: 'TimeDemo',
component: () => import('/@/views/demo/comp/time/index.vue'),
meta: {
title: t('routes.demo.comp.time'),
},
},
{
path: 'countTo',
name: 'CountTo',
component: () => import('/@/views/demo/comp/count-to/index.vue'),
meta: {
title: t('routes.demo.comp.countTo'),
},
},
{
path: 'tree',
name: 'TreeDemo',
redirect: '/comp/tree/basic',
component: getParentLayout('TreeDemo'),
meta: {
// icon: 'clarity:tree-view-line',
title: t('routes.demo.comp.tree'),
},
children: [
{
path: 'basic',
name: 'BasicTreeDemo',
component: () => import('/@/views/demo/tree/index.vue'),
meta: {
title: t('routes.demo.comp.treeBasic'),
},
},
{
path: 'editTree',
name: 'EditTreeDemo',
component: () => import('/@/views/demo/tree/EditTree.vue'),
meta: {
title: t('routes.demo.comp.editTree'),
},
},
{
path: 'actionTree',
name: 'ActionTreeDemo',
component: () => import('/@/views/demo/tree/ActionTree.vue'),
meta: {
title: t('routes.demo.comp.actionTree'),
},
},
],
},
{
path: 'editor',
name: 'EditorDemo',
redirect: '/comp/editor/markdown',
component: getParentLayout('EditorDemo'),
meta: {
// icon: 'carbon:table-split',
title: t('routes.demo.editor.editor'),
},
children: [
{
path: 'json',
component: () => import('/@/views/demo/editor/json/index.vue'),
name: 'JsonEditorDemo',
meta: {
title: t('routes.demo.editor.jsonEditor'),
},
},
{
path: 'markdown',
component: getParentLayout('MarkdownDemo'),
name: 'MarkdownDemo',
meta: {
title: t('routes.demo.editor.markdown'),
},
redirect: '/comp/editor/markdown/index',
children: [
{
path: 'index',
name: 'MarkDownBasicDemo',
component: () => import('/@/views/demo/editor/markdown/index.vue'),
meta: {
title: t('routes.demo.editor.tinymceBasic'),
},
},
{
path: 'editor',
name: 'MarkDownFormDemo',
component: () => import('/@/views/demo/editor/markdown/Editor.vue'),
meta: {
title: t('routes.demo.editor.tinymceForm'),
},
},
],
},
{
path: 'tinymce',
component: getParentLayout('TinymceDemo'),
name: 'TinymceDemo',
meta: {
title: t('routes.demo.editor.tinymce'),
},
redirect: '/comp/editor/tinymce/index',
children: [
{
path: 'index',
name: 'TinymceBasicDemo',
component: () => import('/@/views/demo/editor/tinymce/index.vue'),
meta: {
title: t('routes.demo.editor.tinymceBasic'),
},
},
{
path: 'editor',
name: 'TinymceFormDemo',
component: () => import('/@/views/demo/editor/tinymce/Editor.vue'),
meta: {
title: t('routes.demo.editor.tinymceForm'),
},
},
],
},
],
},
{
path: 'scroll',
name: 'ScrollDemo',
redirect: '/comp/scroll/basic',
component: getParentLayout('ScrollDemo'),
meta: {
title: t('routes.demo.comp.scroll'),
},
children: [
{
path: 'basic',
name: 'BasicScrollDemo',
component: () => import('/@/views/demo/comp/scroll/index.vue'),
meta: {
title: t('routes.demo.comp.scrollBasic'),
},
},
{
path: 'action',
name: 'ActionScrollDemo',
component: () => import('/@/views/demo/comp/scroll/Action.vue'),
meta: {
title: t('routes.demo.comp.scrollAction'),
},
},
{
path: 'virtualScroll',
name: 'VirtualScrollDemo',
component: () => import('/@/views/demo/comp/scroll/VirtualScroll.vue'),
meta: {
title: t('routes.demo.comp.virtualScroll'),
},
},
],
},
{
path: 'modal',
name: 'ModalDemo',
component: () => import('/@/views/demo/comp/modal/index.vue'),
meta: {
title: t('routes.demo.comp.modal'),
},
},
{
path: 'drawer',
name: 'DrawerDemo',
component: () => import('/@/views/demo/comp/drawer/index.vue'),
meta: {
title: t('routes.demo.comp.drawer'),
},
},
{
path: 'desc',
name: 'DescDemo',
component: () => import('/@/views/demo/comp/desc/index.vue'),
meta: {
title: t('routes.demo.comp.desc'),
},
},
{
path: 'lazy',
name: 'LazyDemo',
component: getParentLayout('LazyDemo'),
redirect: '/comp/lazy/basic',
meta: {
title: t('routes.demo.comp.lazy'),
},
children: [
{
path: 'basic',
name: 'BasicLazyDemo',
component: () => import('/@/views/demo/comp/lazy/index.vue'),
meta: {
title: t('routes.demo.comp.lazyBasic'),
},
},
{
path: 'transition',
name: 'BasicTransitionDemo',
component: () => import('/@/views/demo/comp/lazy/Transition.vue'),
meta: {
title: t('routes.demo.comp.lazyTransition'),
},
},
],
},
{
path: 'verify',
name: 'VerifyDemo',
component: getParentLayout('VerifyDemo'),
redirect: '/comp/verify/drag',
meta: {
title: t('routes.demo.comp.verify'),
},
children: [
{
path: 'drag',
name: 'VerifyDragDemo',
component: () => import('/@/views/demo/comp/verify/index.vue'),
meta: {
title: t('routes.demo.comp.verifyDrag'),
},
},
{
path: 'rotate',
name: 'VerifyRotateDemo',
component: () => import('/@/views/demo/comp/verify/Rotate.vue'),
meta: {
title: t('routes.demo.comp.verifyRotate'),
},
},
],
},
//
{
path: 'qrcode',
name: 'QrCodeDemo',
component: () => import('/@/views/demo/comp/qrcode/index.vue'),
meta: {
title: t('routes.demo.comp.qrcode'),
},
},
{
path: 'strength-meter',
name: 'StrengthMeterDemo',
component: () => import('/@/views/demo/comp/strength-meter/index.vue'),
meta: {
title: t('routes.demo.comp.strength'),
},
},
{
path: 'upload',
name: 'UploadDemo',
component: () => import('/@/views/demo/comp/upload/index.vue'),
meta: {
title: t('routes.demo.comp.upload'),
},
},
{
path: 'loading',
name: 'LoadingDemo',
component: () => import('/@/views/demo/comp/loading/index.vue'),
meta: {
title: t('routes.demo.comp.loading'),
},
},
{
path: 'cardList',
name: 'CardListDemo',
component: () => import('/@/views/demo/comp/card-list/index.vue'),
meta: {
title: t('routes.demo.comp.cardList'),
},
},
],
};
export default comp;

View File

@ -0,0 +1,324 @@
import type { AppRouteModule } from '/@/router/types';
import { getParentLayout, LAYOUT } from '/@/router/constant';
import { t } from '/@/hooks/web/useI18n';
const feat: AppRouteModule = {
path: '/feat',
name: 'FeatDemo',
component: LAYOUT,
redirect: '/feat/icon',
meta: {
orderNo: 19,
icon: 'ion:git-compare-outline',
title: t('routes.demo.feat.feat'),
},
children: [
{
path: 'icon',
name: 'IconDemo',
component: () => import('/@/views/demo/feat/icon/index.vue'),
meta: {
title: t('routes.demo.feat.icon'),
},
},
{
path: 'ws',
name: 'WebSocket',
component: () => import('/@/views/demo/feat/ws/index.vue'),
meta: {
title: t('routes.demo.feat.ws'),
},
},
{
path: 'request',
name: 'RequestDemo',
// @ts-ignore
component: () => import('/@/views/demo/feat/request-demo/index.vue'),
meta: {
title: t('routes.demo.feat.requestDemo'),
},
},
{
path: 'session-timeout',
name: 'SessionTimeout',
component: () => import('/@/views/demo/feat/session-timeout/index.vue'),
meta: {
title: t('routes.demo.feat.sessionTimeout'),
},
},
{
path: 'print',
name: 'Print',
component: () => import('/@/views/demo/feat/print/index.vue'),
meta: {
title: t('routes.demo.feat.print'),
},
},
{
path: 'tabs',
name: 'TabsDemo',
component: () => import('/@/views/demo/feat/tabs/index.vue'),
meta: {
title: t('routes.demo.feat.tabs'),
hideChildrenInMenu: true,
},
children: [
{
path: 'detail/:id',
name: 'TabDetail',
component: () => import('/@/views/demo/feat/tabs/TabDetail.vue'),
meta: {
currentActiveMenu: '/feat/tabs',
title: t('routes.demo.feat.tabDetail'),
hideMenu: true,
dynamicLevel: 3,
realPath: '/feat/tabs/detail',
},
},
],
},
{
path: 'breadcrumb',
name: 'BreadcrumbDemo',
redirect: '/feat/breadcrumb/flat',
component: getParentLayout('BreadcrumbDemo'),
meta: {
title: t('routes.demo.feat.breadcrumb'),
},
children: [
{
path: 'flat',
name: 'BreadcrumbFlatDemo',
component: () => import('/@/views/demo/feat/breadcrumb/FlatList.vue'),
meta: {
title: t('routes.demo.feat.breadcrumbFlat'),
},
},
{
path: 'flatDetail',
name: 'BreadcrumbFlatDetailDemo',
component: () => import('/@/views/demo/feat/breadcrumb/FlatListDetail.vue'),
meta: {
title: t('routes.demo.feat.breadcrumbFlatDetail'),
hideMenu: true,
hideTab: true,
currentActiveMenu: '/feat/breadcrumb/flat',
},
},
{
path: 'children',
name: 'BreadcrumbChildrenDemo',
component: () => import('/@/views/demo/feat/breadcrumb/ChildrenList.vue'),
meta: {
title: t('routes.demo.feat.breadcrumbChildren'),
},
children: [
{
path: 'childrenDetail',
name: 'BreadcrumbChildrenDetailDemo',
component: () => import('/@/views/demo/feat/breadcrumb/ChildrenListDetail.vue'),
meta: {
currentActiveMenu: '/feat/breadcrumb/children',
title: t('routes.demo.feat.breadcrumbChildrenDetail'),
//hideTab: true,
// hideMenu: true,
},
},
],
},
],
},
{
path: 'context-menu',
name: 'ContextMenuDemo',
component: () => import('/@/views/demo/feat/context-menu/index.vue'),
meta: {
title: t('routes.demo.feat.contextMenu'),
},
},
{
path: 'download',
name: 'DownLoadDemo',
component: () => import('/@/views/demo/feat/download/index.vue'),
meta: {
title: t('routes.demo.feat.download'),
},
},
{
path: 'click-out-side',
name: 'ClickOutSideDemo',
component: () => import('/@/views/demo/feat/click-out-side/index.vue'),
meta: {
title: t('routes.demo.feat.clickOutSide'),
},
},
{
path: 'img-preview',
name: 'ImgPreview',
component: () => import('/@/views/demo/feat/img-preview/index.vue'),
meta: {
title: t('routes.demo.feat.imgPreview'),
},
},
{
path: 'copy',
name: 'CopyDemo',
component: () => import('/@/views/demo/feat/copy/index.vue'),
meta: {
title: t('routes.demo.feat.copy'),
},
},
{
path: 'msg',
name: 'MsgDemo',
component: () => import('/@/views/demo/feat/msg/index.vue'),
meta: {
title: t('routes.demo.feat.msg'),
},
},
{
path: 'watermark',
name: 'WatermarkDemo',
component: () => import('/@/views/demo/feat/watermark/index.vue'),
meta: {
title: t('routes.demo.feat.watermark'),
},
},
{
path: 'ripple',
name: 'RippleDemo',
component: () => import('/@/views/demo/feat/ripple/index.vue'),
meta: {
title: t('routes.demo.feat.ripple'),
},
},
{
path: 'full-screen',
name: 'FullScreenDemo',
component: () => import('/@/views/demo/feat/full-screen/index.vue'),
meta: {
title: t('routes.demo.feat.fullScreen'),
},
},
{
path: '/error-log',
name: 'ErrorLog',
component: () => import('/@/views/sys/error-log/index.vue'),
meta: {
title: t('routes.demo.feat.errorLog'),
},
},
{
path: 'excel',
name: 'Excel',
redirect: '/feat/excel/customExport',
component: getParentLayout('Excel'),
meta: {
// icon: 'mdi:microsoft-excel',
title: t('routes.demo.excel.excel'),
},
children: [
{
path: 'customExport',
name: 'CustomExport',
component: () => import('/@/views/demo/excel/CustomExport.vue'),
meta: {
title: t('routes.demo.excel.customExport'),
},
},
{
path: 'jsonExport',
name: 'JsonExport',
component: () => import('/@/views/demo/excel/JsonExport.vue'),
meta: {
title: t('routes.demo.excel.jsonExport'),
},
},
{
path: 'arrayExport',
name: 'ArrayExport',
component: () => import('/@/views/demo/excel/ArrayExport.vue'),
meta: {
title: t('routes.demo.excel.arrayExport'),
},
},
{
path: 'importExcel',
name: 'ImportExcel',
component: () => import('/@/views/demo/excel/ImportExcel.vue'),
meta: {
title: t('routes.demo.excel.importExcel'),
},
},
],
},
{
path: 'testTab/:id',
name: 'TestTab',
component: () => import('/@/views/demo/feat/tab-params/index.vue'),
meta: {
title: t('routes.demo.feat.tab'),
carryParam: true,
hidePathForChildren: true,
},
children: [
{
path: 'testTab/id1',
name: 'TestTab1',
component: () => import('/@/views/demo/feat/tab-params/index.vue'),
meta: {
title: t('routes.demo.feat.tab1'),
carryParam: true,
ignoreRoute: true,
},
},
{
path: 'testTab/id2',
name: 'TestTab2',
component: () => import('/@/views/demo/feat/tab-params/index.vue'),
meta: {
title: t('routes.demo.feat.tab2'),
carryParam: true,
ignoreRoute: true,
},
},
],
},
{
path: 'testParam/:id',
name: 'TestParam',
component: getParentLayout('TestParam'),
meta: {
title: t('routes.demo.feat.menu'),
ignoreKeepAlive: true,
},
children: [
{
path: 'sub1',
name: 'TestParam_1',
component: () => import('/@/views/demo/feat/menu-params/index.vue'),
meta: {
title: t('routes.demo.feat.menu1'),
ignoreKeepAlive: true,
},
},
{
path: 'sub2',
name: 'TestParam_2',
component: () => import('/@/views/demo/feat/menu-params/index.vue'),
meta: {
title: t('routes.demo.feat.menu2'),
ignoreKeepAlive: true,
},
},
],
},
],
};
export default feat;

View File

@ -0,0 +1,28 @@
import type { AppRouteModule } from '/@/router/types';
import { LAYOUT } from '/@/router/constant';
import { t } from '/@/hooks/web/useI18n';
const charts: AppRouteModule = {
path: '/flow',
name: 'FlowDemo',
component: LAYOUT,
redirect: '/flow/flowChart',
meta: {
orderNo: 5000,
icon: 'tabler:chart-dots',
title: t('routes.demo.flow.name'),
},
children: [
{
path: 'flowChart',
name: 'flowChartDemo',
component: () => import('/@/views/demo/comp/flow-chart/index.vue'),
meta: {
title: t('routes.demo.flow.flowChart'),
},
},
],
};
export default charts;

View File

@ -0,0 +1,48 @@
import type { AppRouteModule } from '/@/router/types';
import { LAYOUT } from '/@/router/constant';
const IFrame = () => import('/@/views/sys/iframe/FrameBlank.vue');
import { t } from '/@/hooks/web/useI18n';
const iframe: AppRouteModule = {
path: '/frame',
name: 'Frame',
component: LAYOUT,
redirect: '/frame/doc',
meta: {
orderNo: 1000,
icon: 'ion:tv-outline',
title: t('routes.demo.iframe.frame'),
},
children: [
{
path: 'doc',
name: 'Doc',
component: IFrame,
meta: {
frameSrc: 'https://vvbin.cn/doc-next/',
title: t('routes.demo.iframe.doc'),
},
},
{
path: 'antv',
name: 'Antv',
component: IFrame,
meta: {
frameSrc: 'https://2x.antdv.com/docs/vue/introduce-cn/',
title: t('routes.demo.iframe.antv'),
},
},
{
path: 'https://vvbin.cn/doc-next/',
name: 'DocExternal',
component: IFrame,
meta: {
title: t('routes.demo.iframe.docExternal'),
},
},
],
};
export default iframe;

View File

@ -0,0 +1,68 @@
import type { AppRouteModule } from '/@/router/types';
import { getParentLayout, LAYOUT } from '/@/router/constant';
import { t } from '/@/hooks/web/useI18n';
const permission: AppRouteModule = {
path: '/level',
name: 'Level',
component: LAYOUT,
redirect: '/level/menu1/menu1-1/menu1-1-1',
meta: {
orderNo: 2000,
icon: 'ion:menu-outline',
title: t('routes.demo.level.level'),
},
children: [
{
path: 'menu1',
name: 'Menu1Demo',
component: getParentLayout('Menu1Demo'),
meta: {
title: 'Menu1',
},
redirect: '/level/menu1/menu1-1/menu1-1-1',
children: [
{
path: 'menu1-1',
name: 'Menu11Demo',
component: getParentLayout('Menu11Demo'),
meta: {
title: 'Menu1-1',
},
redirect: '/level/menu1/menu1-1/menu1-1-1',
children: [
{
path: 'menu1-1-1',
name: 'Menu111Demo',
component: () => import('/@/views/demo/level/Menu111.vue'),
meta: {
title: 'Menu111',
},
},
],
},
{
path: 'menu1-2',
name: 'Menu12Demo',
component: () => import('/@/views/demo/level/Menu12.vue'),
meta: {
title: 'Menu1-2',
},
},
],
},
{
path: 'menu2',
name: 'Menu2Demo',
component: () => import('/@/views/demo/level/Menu2.vue'),
meta: {
title: 'Menu2',
// ignoreKeepAlive: true,
},
},
],
};
export default permission;

View File

@ -0,0 +1,255 @@
import type { AppRouteModule } from '/@/router/types';
import { getParentLayout, LAYOUT } from '/@/router/constant';
import { ExceptionEnum } from '/@/enums/exceptionEnum';
import { t } from '/@/hooks/web/useI18n';
const ExceptionPage = () => import('/@/views/sys/exception/Exception.vue');
const page: AppRouteModule = {
path: '/page-demo',
name: 'PageDemo',
component: LAYOUT,
redirect: '/page-demo/form/basic',
meta: {
orderNo: 20,
icon: 'ion:aperture-outline',
title: t('页面'),
},
children: [
// =============================form start=============================
{
path: 'form',
name: 'FormPage',
redirect: '/page-demo/form/basic',
component: getParentLayout('FormPage'),
meta: {
title: t('表单页'),
},
children: [
{
path: 'basic',
name: 'FormBasicPage',
component: () => import('/@/views/demo/page/form/basic/index.vue'),
meta: {
title: t('基础表单'),
},
},
{
path: 'step',
name: 'FormStepPage',
component: () => import('/@/views/demo/page/form/step/index.vue'),
meta: {
title: t('分步表单'),
},
},
{
path: 'high',
name: 'FormHightPage',
component: () => import('/@/views/demo/page/form/high/index.vue'),
meta: {
title: t('高级表单'),
},
},
],
},
// =============================form end=============================
// =============================desc start=============================
{
path: 'desc',
name: 'DescPage',
component: getParentLayout('DescPage'),
redirect: '/page-demo/desc/basic',
meta: {
title: t('详情页'),
},
children: [
{
path: 'basic',
name: 'DescBasicPage',
component: () => import('/@/views/demo/page/desc/basic/index.vue'),
meta: {
title: t('基础详情页'),
},
},
{
path: 'high',
name: 'DescHighPage',
component: () => import('/@/views/demo/page/desc/high/index.vue'),
meta: {
title: t('高级详情页'),
},
},
],
},
// =============================desc end=============================
// =============================result start=============================
{
path: 'result',
name: 'ResultPage',
redirect: '/page-demo/result/success',
component: getParentLayout('ResultPage'),
meta: {
title: t('结果页'),
},
children: [
{
path: 'success',
name: 'ResultSuccessPage',
component: () => import('/@/views/demo/page/result/success/index.vue'),
meta: {
title: t('成功页'),
},
},
{
path: 'fail',
name: 'ResultFailPage',
component: () => import('/@/views/demo/page/result/fail/index.vue'),
meta: {
title: t('失败页'),
},
},
],
},
// =============================result end=============================
// =============================account start=============================
{
path: 'account',
name: 'AccountPage',
component: getParentLayout('AccountPage'),
redirect: '/page-demo/account/setting',
meta: {
title: t('个人页'),
},
children: [
{
path: 'center',
name: 'AccountCenterPage',
component: () => import('/@/views/demo/page/account/center/index.vue'),
meta: {
title: t('个人中心'),
},
},
{
path: 'setting',
name: 'AccountSettingPage',
component: () => import('/@/views/demo/page/account/setting/index.vue'),
meta: {
title: t('个人设置'),
},
},
],
},
// =============================account end=============================
// =============================exception start=============================
{
path: 'exception',
name: 'ExceptionPage',
component: getParentLayout('ExceptionPage'),
redirect: '/page-demo/exception/404',
meta: {
title: t('异常页'),
},
children: [
{
path: '403',
name: 'PageNotAccess',
component: ExceptionPage,
props: {
status: ExceptionEnum.PAGE_NOT_ACCESS,
},
meta: {
title: '403',
},
},
{
path: '404',
name: 'PageNotFound',
component: ExceptionPage,
props: {
status: ExceptionEnum.PAGE_NOT_FOUND,
},
meta: {
title: '404',
},
},
{
path: '500',
name: 'ServiceError',
component: ExceptionPage,
props: {
status: ExceptionEnum.ERROR,
},
meta: {
title: '500',
},
},
{
path: 'net-work-error',
name: 'NetWorkError',
component: ExceptionPage,
props: {
status: ExceptionEnum.NET_WORK_ERROR,
},
meta: {
title: t('网络错误'),
},
},
{
path: 'not-data',
name: 'NotData',
component: ExceptionPage,
props: {
status: ExceptionEnum.PAGE_NOT_DATA,
},
meta: {
title: t('无数据'),
},
},
],
},
// =============================exception end=============================
// =============================list start=============================
{
path: 'list',
name: 'ListPage',
component: getParentLayout('ListPage'),
redirect: '/page-demo/list/card',
meta: {
title: t('列表页'),
},
children: [
{
path: 'basic',
name: 'ListBasicPage',
component: () => import('/@/views/demo/page/list/basic/index.vue'),
meta: {
title: t('标准列表'),
},
},
{
path: 'card',
name: 'ListCardPage',
component: () => import('/@/views/demo/page/list/card/index.vue'),
meta: {
title: t('卡片列表'),
},
},
{
path: 'search',
name: 'ListSearchPage',
component: () => import('/@/views/demo/page/list/search/index.vue'),
meta: {
title: t('搜索列表'),
},
},
],
},
// =============================list end=============================
],
};
export default page;

View File

@ -0,0 +1,92 @@
import type { AppRouteModule } from '/@/router/types';
import { getParentLayout, LAYOUT } from '/@/router/constant';
import { RoleEnum } from '/@/enums/roleEnum';
import { t } from '/@/hooks/web/useI18n';
const permission: AppRouteModule = {
path: '/permission',
name: 'Permission',
component: LAYOUT,
redirect: '/permission/front/page',
meta: {
orderNo: 15,
icon: 'ion:key-outline',
title: t('routes.demo.permission.permission'),
},
children: [
{
path: 'front',
name: 'PermissionFrontDemo',
component: getParentLayout('PermissionFrontDemo'),
meta: {
title: t('routes.demo.permission.front'),
},
children: [
{
path: 'page',
name: 'FrontPageAuth',
component: () => import('/@/views/demo/permission/front/index.vue'),
meta: {
title: t('routes.demo.permission.frontPage'),
},
},
{
path: 'btn',
name: 'FrontBtnAuth',
component: () => import('/@/views/demo/permission/front/Btn.vue'),
meta: {
title: t('routes.demo.permission.frontBtn'),
},
},
{
path: 'auth-pageA',
name: 'FrontAuthPageA',
component: () => import('/@/views/demo/permission/front/AuthPageA.vue'),
meta: {
title: t('routes.demo.permission.frontTestA'),
roles: [RoleEnum.SUPER],
},
},
{
path: 'auth-pageB',
name: 'FrontAuthPageB',
component: () => import('/@/views/demo/permission/front/AuthPageB.vue'),
meta: {
title: t('routes.demo.permission.frontTestB'),
roles: [RoleEnum.TEST],
},
},
],
},
{
path: 'back',
name: 'PermissionBackDemo',
component: getParentLayout('PermissionBackDemo'),
meta: {
title: t('routes.demo.permission.back'),
},
children: [
{
path: 'page',
name: 'BackAuthPage',
component: () => import('/@/views/demo/permission/back/index.vue'),
meta: {
title: t('routes.demo.permission.backPage'),
},
},
{
path: 'btn',
name: 'BackAuthBtn',
component: () => import('/@/views/demo/permission/back/Btn.vue'),
meta: {
title: t('routes.demo.permission.backBtn'),
},
},
],
},
],
};
export default permission;

View File

@ -0,0 +1,31 @@
import type { AppRouteModule } from '/@/router/types';
import { LAYOUT } from '/@/router/constant';
import { t } from '/@/hooks/web/useI18n';
const setup: AppRouteModule = {
path: '/setup',
name: 'SetupDemo',
component: LAYOUT,
redirect: '/setup/index',
meta: {
orderNo: 90000,
hideChildrenInMenu: true,
icon: 'whh:paintroll',
title: t('routes.demo.setup.page'),
},
children: [
{
path: 'index',
name: 'SetupDemoPage',
component: () => import('/@/views/demo/setup/index.vue'),
meta: {
title: t('routes.demo.setup.page'),
icon: 'whh:paintroll',
hideMenu: true,
},
},
],
};
export default setup;

View File

@ -0,0 +1,78 @@
import type { AppRouteModule } from '/@/router/types';
import { LAYOUT } from '/@/router/constant';
import { t } from '/@/hooks/web/useI18n';
const system: AppRouteModule = {
path: '/system',
name: 'System',
component: LAYOUT,
redirect: '/system/account',
meta: {
orderNo: 2000,
icon: 'ion:settings-outline',
title: t('routes.demo.system.moduleName'),
},
children: [
{
path: 'account',
name: 'AccountManagement',
meta: {
title: t('routes.demo.system.account'),
ignoreKeepAlive: false,
},
component: () => import('/@/views/demo/system/account/index.vue'),
},
{
path: 'account_detail/:id',
name: 'AccountDetail',
meta: {
hideMenu: true,
title: t('routes.demo.system.account_detail'),
ignoreKeepAlive: true,
showMenu: false,
currentActiveMenu: '/system/account',
},
component: () => import('/@/views/demo/system/account/AccountDetail.vue'),
},
{
path: 'role',
name: 'RoleManagement',
meta: {
title: t('routes.demo.system.role'),
ignoreKeepAlive: true,
},
component: () => import('/@/views/demo/system/role/index.vue'),
},
{
path: 'menu',
name: 'MenuManagement',
meta: {
title: t('routes.demo.system.menu'),
ignoreKeepAlive: true,
},
component: () => import('/@/views/demo/system/menu/index.vue'),
},
{
path: 'dept',
name: 'DeptManagement',
meta: {
title: t('routes.demo.system.dept'),
ignoreKeepAlive: true,
},
component: () => import('/@/views/demo/system/dept/index.vue'),
},
{
path: 'changePassword',
name: 'ChangePassword',
meta: {
title: t('routes.demo.system.password'),
ignoreKeepAlive: true,
},
component: () => import('/@/views/demo/system/password/index.vue'),
},
],
};
export default system;

65
src/router/types.ts Normal file
View File

@ -0,0 +1,65 @@
import type { RouteRecordRaw, RouteMeta } from 'vue-router';
import { RoleEnum } from '/@/enums/roleEnum';
import { defineComponent } from 'vue';
export type Component<T = any> =
| ReturnType<typeof defineComponent>
| (() => Promise<typeof import('*.vue')>)
| (() => Promise<T>);
// @ts-ignore
export interface AppRouteRecordRaw extends Omit<RouteRecordRaw, 'meta'> {
name: string;
meta: RouteMeta;
component?: Component | string;
components?: Component;
children?: AppRouteRecordRaw[];
props?: Recordable;
fullPath?: string;
}
export interface MenuTag {
type?: 'primary' | 'error' | 'warn' | 'success';
content?: string;
dot?: boolean;
}
export interface Menu {
name: string;
icon?: string;
path: string;
// path contains param, auto assignment.
paramPath?: string;
disabled?: boolean;
children?: Menu[];
orderNo?: number;
roles?: RoleEnum[];
meta?: Partial<RouteMeta>;
tag?: MenuTag;
hideMenu?: boolean;
}
export interface MenuModule {
orderNo?: number;
menu: Menu;
}
export interface subSys {
name: string;
icon?: string;
code: string;
description: string;
id: string;
sortCode: number;
}
// export type AppRouteModule = RouteModule | AppRouteRecordRaw;
export type AppRouteModule = AppRouteRecordRaw;