This commit is contained in:
lijianyou
2025-09-30 15:00:32 +08:00
parent 1b262f06b8
commit b4e759cf69
202 changed files with 22035 additions and 0 deletions
+248
View File
@@ -0,0 +1,248 @@
// 引入 src/pages 文件夹下所有组件作为动态组件
import Pages from '@/pages';
import { RouteRecordRaw } from 'vue-router';
import { RouteOption, LazyRouteComponent, RouteRecordLink } from './interface';
import router from './index';
import { initUndefined } from '@/utils/helpers';
// 注册 IframeBox、BlankView 组件
Pages['iframe'] = () => import('stepin/es/iframe-box');
Pages['blankView'] = () => import('@/components/layout/BlankView.vue');
Pages['link'] = () => import('@/components/layout/LinkView.vue');
/**
* 解析路由组件
* @param component
* @returns
*/
const parseComponent = (component: null | undefined | string | Record<string, string>) => {
if (component === null || component === undefined) {
return component;
}
if (typeof component === 'string') {
return Pages[component];
} else {
return Object.entries(component).reduce((p, [key, val]) => {
p[key] = Pages[val];
return p;
}, {} as Record<string, LazyRouteComponent>);
}
};
/**
* 解析路由
* @param routes
* @returns
*/
function parseRoutes(routes: RouteOption[]): RouteRecordRaw[] {
return routes.map<RouteRecordRaw>((route) => {
// 初始化meta
route.meta = route.meta ?? {};
initUndefined(route.meta, {
cacheable: true,
renderMenu: true,
link: (route as RouteRecordLink).link,
});
// 解析组件 及 子路由
const _route = {
...route,
children: route.children && parseRoutes(route.children),
component: route.component && parseComponent(route.component),
components: route.components && parseComponent(route.components),
} as any;
// 删除 undefined 属性
Object.keys(_route).forEach((key) => {
if (_route[key] === undefined) {
delete _route[key];
}
});
return _route as RouteRecordRaw;
});
}
/**
* 提取嵌套路由所有name
* @param recordList
* @returns
*/
const extractRouteNames = (recordList: RouteRecordRaw[]): string[] => {
const result: string[] = [];
recordList.forEach((record) => {
if (typeof record.name === 'string') {
result.push(record.name);
}
if (record.children) {
result.push(...extractRouteNames(record.children));
}
});
return result;
};
/**
* 合并路由
* @param target
* @param source
*/
function mergeRoutes(target: readonly RouteRecordRaw[], source: RouteRecordRaw[]): RouteRecordRaw[] {
interface RouteRecordMap extends Omit<RouteRecordRaw, 'children'> {
children?: Map<string, RouteRecordMap>;
}
type Filter = (record: RouteRecordRaw) => Boolean;
/**
* 转换成 map, 不满足过滤条件的 route 值设置为 undefined
* @param routes
* @param filter 过滤器
* @param parentPath
* @returns
*/
const toRoutesMap = (
routes: readonly RouteRecordRaw[],
filter?: Filter,
parentPath?: string
): Map<string, RouteRecordMap> => {
parentPath = parentPath ?? '';
const _map = new Map<string, RouteRecordMap>();
routes.forEach((route) => {
const fullPath = /^\//.test(route.path) ? route.path : `${parentPath}/${route.path}`;
if (!filter || filter(route)) {
_map.set(fullPath, {
...route,
children: route.children && toRoutesMap(route.children, filter, fullPath),
});
} else {
_map.set(fullPath, undefined as never);
}
});
return _map;
};
// 合并
const mergeMap = (
target?: Map<string, RouteRecordMap>,
source?: Map<string, RouteRecordMap>
): Map<string, RouteRecordMap> | undefined => {
if (!target || !source) {
return target ?? source;
}
const resultMap = new Map<string, RouteRecordMap>();
// 保证新路由数据顺序
for (const key of source.keys()) {
resultMap.set(key, void 0);
}
for (const key of target.keys()) {
resultMap.set(key, void 0);
}
target.forEach((v, k) => {
resultMap.set(k, v);
});
source.forEach((v, k) => {
const t = resultMap.get(k);
if (t !== undefined) {
v.children = mergeMap(t.children, v.children);
}
resultMap.set(k, v);
});
return resultMap;
};
// map 转换成 routes
const toRoutes = (routesMap: Map<string, RouteRecordMap>): RouteRecordRaw[] => {
const _routes: RouteRecordRaw[] = [];
routesMap.forEach((record, path) => {
if (record) {
const _route = { ...record } as RouteRecordRaw;
if (record.children) {
_route.children = toRoutes(record.children);
} else {
delete _route.children;
}
_routes.push(_route);
}
});
return _routes;
};
const names = extractRouteNames(source);
const targetMap = toRoutesMap(target, (record) => !names.includes(record.name as string));
const sourceMap = toRoutesMap(source);
const routesMap = mergeMap(targetMap, sourceMap);
return toRoutes(routesMap);
}
/**
* 查找符合条件的路由
* @param routes 路由集合
* @param filter 过滤器
* @returns
*/
function findRoute(
routes: readonly RouteRecordRaw[],
filter: (route: RouteRecordRaw) => boolean
): RouteRecordRaw | undefined {
if (routes.length === 0) {
return undefined;
}
return (
routes.find(filter) ??
findRoute(
routes.flatMap((route) => route.children ?? []),
filter
)
);
}
/**
* 添加路由
* @param routes
*/
export function addRoutes(routes: RouteOption[]) {
const routesRaw: RouteRecordRaw[] = parseRoutes(routes);
routesRaw.forEach((routeRaw) => router.addRoute(routeRaw));
router.options.routes = mergeRoutes(router.options.routes, routesRaw);
}
/**
* 过滤路由配置
* @param routes 路由配置数组
* @param filter 过滤条件
* @returns
*/
function filterRoutes(routes: Readonly<RouteRecordRaw[]>, filter: (route: RouteRecordRaw) => boolean) {
return routes.filter((route) => {
if (route.children && route.children.length > 0) {
route.children = filterRoutes(route.children, filter);
}
return filter(route);
});
}
/**
* 移出路由
* @param routeName
*/
export function removeRoute(routeName: string) {
router.removeRoute(routeName);
router.options.routes = filterRoutes(router.options.routes, (route) => route.name !== routeName);
}
/**
* 添加路由
* @param routes
* @param parentName
* @returns
*/
export function appendRoutes(routes: RouteOption[], parentName: string) {
const parent = findRoute(router.options.routes, (route) => route.name === parentName);
if (!parent) {
console.error(`name为${parentName}的父级路由不存在,请检查`);
return false;
}
const routesRaw: RouteRecordRaw[] = parseRoutes(routes);
routesRaw.forEach((routeRaw) => router.addRoute(parentName, routeRaw));
parent.children = mergeRoutes(router.options.routes, mergeRoutes(parent.children ?? [], routesRaw));
}
+110
View File
@@ -0,0 +1,110 @@
import { NavigationGuard, NavigationHookAfter } from 'vue-router';
import http from '@/store/http';
import { useAccountStore, useMenuStore, useApiStore } from '@/store';
import { useAuthStore } from '@/plugins';
import NProgress from 'nprogress';
import 'nprogress/nprogress.css';
import router from '@/router';
NProgress.configure({ showSpinner: false });
interface NaviGuard {
before?: NavigationGuard;
after?: NavigationHookAfter;
}
const loginGuard: NavigationGuard = function (to, from) {
// console.log('Authorization', http.checkAuthorization())
const account = useAccountStore();
if (!http.checkAuthorization() && !/^\/(login|home|init)?$/.test(to.fullPath)) {
account.setLogged(false)
return '/login';
} else {
}
};
const dynamicinitRoute =
{
path: '/',
name: 'login',
redirect: '/login',
meta: {
title: '登录',
renderMenu: false,
icon: 'CreditCardOutlined',
},
children: null,
component: () => import('@/pages/login'),
};
const InitGuard: NavigationGuard = function (to, from) {
if (to.fullPath != '/login') {
useApiStore()
.apiCheckInitStatus()
.then((res) => {
// console.log(to.fullPath)
if (res.code === 0) {
} else {
if (!router.hasRoute('login')) {
router.addRoute(dynamicinitRoute)
}
router.push('/login')
// return '/init'
}
});
}
};
// 进度条
const ProgressGuard: NaviGuard = {
before(to, from) {
NProgress.start();
},
after(to, from) {
NProgress.done();
},
};
const AuthGuard: NaviGuard = {
before(to, from) {
const { hasAuthority } = useAuthStore();
if (to.meta?.permission && !hasAuthority(to.meta?.permission)) {
return { name: '403', query: { permission: to.meta.permission, path: to.fullPath } };
}
},
};
const ForbiddenGuard: NaviGuard = {
before(to) {
if (to.name === '403' && (to.query.permission || to.query.path)) {
to.fullPath = to.fullPath
.replace(/permission=[^&=]*&?/, '')
.replace(/&?path=[^&=]*&?/, '')
.replace(/\?$/, '');
to.params.permission = to.query.permission;
to.params.path = to.query.path;
delete to.query.permission;
delete to.query.path;
}
},
};
// 404 not found
const NotFoundGuard: NaviGuard = {
before(to, from) {
const { loading } = useMenuStore();
if (to.meta._is404Page && loading) {
to.params.loading = true as any;
}
},
};
export default {
// before: [ProgressGuard.before, InitGuard, loginGuard, AuthGuard.before, ForbiddenGuard.before, NotFoundGuard.before],
before: [ProgressGuard.before, loginGuard, AuthGuard.before, ForbiddenGuard.before, NotFoundGuard.before],
after: [ProgressGuard.after],
};
+17
View File
@@ -0,0 +1,17 @@
import { createRouter, createWebHashHistory } from 'vue-router';
import { reactive } from 'vue';
import routes from './routes';
import guards from './guards';
const router = createRouter(
{
history: createWebHashHistory(),
routes,
}
);
console.log(router)
// 注册导航守卫
guards.before.forEach(router.beforeEach);
guards.after.forEach(router.afterEach);
export default router;
+63
View File
@@ -0,0 +1,63 @@
import { _RouteRecordBase, RouteLocationNormalized } from 'vue-router';
import { Component, DefineComponent } from 'vue';
export type RouteComponent = Component | DefineComponent;
export type LazyRouteComponent = () => Promise<RouteComponent>;
declare type _RouteRecordProps = boolean | Record<string, any> | ((to: RouteLocationNormalized) => Record<string, any>);
declare type RedirectType = Pick<_RouteRecordBase, 'redirect'>;
export interface RouteMeta {
renderMenu?: boolean;
permission?: string | number;
icon?: Component | string;
cacheable?: boolean;
link?: string;
title?: string;
}
declare interface RouteRecordBase extends Omit<_RouteRecordBase, 'redirect'> {
children?: never;
component?: never;
components?: never;
meta: RouteMeta;
}
export interface RouteRecordSingleView extends RouteRecordBase {
component: string;
}
redirect: never;
export interface RouteRecordSingleViewWithChildren extends RouteRecordBase, RedirectType {
component?: string | null | undefined;
children: RouteOption[];
props?: _RouteRecordProps;
}
export interface RouteRecordMultipleViews extends RouteRecordBase {
components: Record<string, string>;
props?: Record<string, _RouteRecordProps> | boolean;
}
export interface RouteRecordMultipleViewsWithChildren extends RouteRecordBase, RedirectType {
components?: Record<string, string> | null | undefined;
children: RouteOption[];
props?: Record<string, _RouteRecordProps> | boolean;
}
export interface RouteRecordRedirect extends RouteRecordBase, Required<RedirectType> {
children?: RouteOption[];
}
export interface RouteRecordLink extends RouteRecordBase {
link: string;
children?: RouteOption[];
}
export type RouteOption =
| RouteRecordSingleView
| RouteRecordSingleViewWithChildren
| RouteRecordMultipleViews
| RouteRecordMultipleViewsWithChildren
| RouteRecordRedirect
| RouteRecordLink;
+175
View File
@@ -0,0 +1,175 @@
import { RouteRecordRaw } from 'vue-router';
const routes: RouteRecordRaw[] = [
{
path: '/',
name: 'login',
redirect: '/login',
meta: {
title: '登录',
renderMenu: false,
icon: 'CreditCardOutlined',
},
children: null,
component: () => import('@/pages/login'),
},
// {
// path: '/',
// name: 'init',
// redirect: '/init',
// meta: {
// title: '初始化',
// renderMenu: false,
// icon: 'CreditCardOutlined',
// },
// children: null,
// component: () => import('@/pages/init'),
// },
{
path: '/front',
name: '前端',
meta: {
renderMenu: false,
},
component: () => import('@/components/layout/FrontView.vue'),
children: [
{
path: '/login',
name: '登录',
meta: {
icon: 'LoginOutlined',
view: 'blank',
target: '_blank',
cacheable: false,
},
component: () => import('@/pages/login'),
},
// {
// path: '/init',
// name: '初始化',
// meta: {
// icon: 'LoginOutlined',
// view: 'blank',
// target: '_blank',
// cacheable: false,
// },
// component: () => import('@/pages/init'),
// },
],
},
{
path: '/403',
name: '403',
props: true,
meta: {
renderMenu: false,
},
component: () => import('@/pages/Exp403.vue'),
},
// {
// id: 1,
// name: '解析记录',
// title: '解析记录',
// icon: 'DashboardOutlined',
// badge: '',
// target: '_self',
// path: '/workplace',
// component: () => import('@/pages/workplace/Records.vue'),
// renderMenu: true,
// parent: null,
// permission: null,
// cacheable: false,
// },
{
path: '/dashboard',
name: '数据看板',
meta: {
icon: 'SettingOutlined',
view: 'self',
target: '_self',
renderMenu: true,
cacheable: false,
},
component: () => import('@/pages/workplace/statics.vue'),
},
{
path: '/workplace',
name: '同步记录',
meta: {
icon: 'SettingOutlined',
view: 'self',
target: '_self',
renderMenu: true,
cacheable: false,
},
component: () => import('@/pages/workplace/Workplace.vue'),
},
{
path: '/cok',
name: '抖音授权',
meta: {
icon: 'SettingOutlined',
view: 'self',
target: '_self',
renderMenu: true,
cacheable: false,
},
component: () => import('@/pages/cok/Table.vue'),
},
{
path: '/set',
name: '系统配置',
meta: {
icon: 'SettingOutlined',
view: 'self',
target: '_self',
renderMenu: true,
cacheable: false,
},
component: () => import('@/pages/set/AppSet.vue'),
},
// {
// // id: 3,
// name: '系统日志',
// // title: '系统日志',
// icon: 'UnorderedListOutlined',
// badge: '',
// target: '_self',
// path: '/logs',
// component: () => import('@/pages/mylogs/MyLogs.vue'),
// renderMenu: true,
// parent: null,
// permission: null,
// cacheable: false,
// },
{
path: '/logs',
name: '系统日志',
meta: {
icon: 'UnorderedListOutlined',
view: 'self',
target: '_self',
renderMenu: true,
cacheable: false,
},
component: () => import('@/pages/mylogs/MyLogs.vue'),
},
{
path: '/:pathMatch(.*)*',
name: '404',
props: true,
meta: {
icon: 'CreditCardOutlined',
renderMenu: false,
cacheable: false,
_is404Page: true,
},
component: () => import('@/pages/Exp404.vue'),
},
];
export default routes;