@truenewx/tnxvue3
Version:
互联网技术解决方案:Vue3扩展支持
184 lines (167 loc) • 6.66 kB
text/typescript
/**
* 基于Vue的路由器构建函数
*/
import {
createRouter,
createWebHashHistory,
Router,
RouteRecordRaw,
RouterHistory,
RouteLocationNormalized,
RouteLocationNormalizedLoaded,
RouteLocationNormalizedLoadedGeneric,
NavigationGuardNext,
} from 'vue-router';
import * as NetUtil from '../../tnxcore/src/util/net.ts';
export type RouteItem = {
caption: string;
path: string;
icon?: string;
page?: string;
component?: () => Promise<any>;
redirect?: never;
alias?: string;
subs?: RouteItem[];
}
function addRoute(routes: RouteRecordRaw[], superiorPath: string, item: RouteItem, fnImportPage?: (page: string) => Promise<any>): void {
if (item && item.path) {
let page = item.page || item.path.replace(/\/:[a-zA-Z0-9_]+/g, '');
let route: RouteRecordRaw = {
path: item.path,
meta: {
superiorPath: superiorPath,
page: page,
cache: {}, // 路由级缓存
isHistory() { // 通过setTimeout()方式调用才能确保获得正确结果
return this.historyFrom !== undefined;
},
},
component: item.component,
};
if (!route.component && fnImportPage) {
route.component = () => {
return fnImportPage(page);
};
}
// 如果直接定义route的redirect/alias字段,则item的redirect/alias为undefined时,route仍然有redirect/alias字段,只是其值为undefined,这将导致VueRouter报错
if (item.redirect) {
route.redirect = item.redirect;
}
if (item.alias) {
route.alias = item.alias;
}
routes.push(route);
}
}
function applyItemsToRoutes(superiorPath: string, items: RouteItem[], routes: RouteRecordRaw[], fnImportPage?: (page: string) => Promise<any>): void {
if (items && items.length) {
items.forEach(item => {
if (item) {
addRoute(routes, superiorPath, item, fnImportPage);
applyItemsToRoutes(item.path, item.subs, routes, fnImportPage);
}
});
}
}
function instantiatePath(path: string, params?: Record<string, any>): string {
if (path && path.includes('/:')) {
if (params) {
Object.keys(params).forEach(key => {
path = path.replace('/:' + key + '/', '/' + params[key] + '/');
});
}
if (path.includes('/:')) { // 参数替换完之后,还有路径参数,则为无效路径,返回首页
console.warn(
'路径中的参数无法获得参数值,请确保具有参数的路径所属菜单项的下级菜单路径包含相同的参数:' + path);
return '/';
}
}
return path;
}
function getCurrentRoute(router: Router): RouteLocationNormalizedLoadedGeneric {
return router.currentRoute.value;
}
export type VueRouter = Router & {
history: RouterHistory;
prev?: RouteLocationNormalizedLoaded;
$beforeLeaveHandlers: Record<string, (to: RouteLocationNormalized) => boolean>;
beforeLeave: (handler: (to: RouteLocationNormalized) => boolean) => void;
pushState: (path: string) => boolean;
replaceState: (path: string) => boolean;
backTo: (path?: string) => void;
}
export default function (items: RouteItem[], fnImportPage?: (page: string) => Promise<any>): VueRouter {
const routes: RouteRecordRaw[] = [];
applyItemsToRoutes('', items, routes, fnImportPage);
const routerHistory = createWebHashHistory();
const router: VueRouter = createRouter({
history: routerHistory,
routes,
}) as VueRouter;
router.history = routerHistory;
// 浏览器的返回事件触发位于VueRouter的钩子执行和页面渲染之后,这意味着$route.meta.historyFrom必须在页面渲染完之后才具有正确的值
// if (window.history && window.history.pushState) {
// window.history.pushState(null, null, document.URL);
// }
window.addEventListener('popstate', () => {
let $route = getCurrentRoute(router);
if ($route) {
$route.meta.historyFrom = router.history.state.forward;
}
}, false);
// 注册离开页面前事件处理支持
router.$beforeLeaveHandlers = {};
router.beforeLeave = function (handler: (to: RouteLocationNormalized) => boolean): void {
let $route = getCurrentRoute(router);
let path = $route.path;
router.$beforeLeaveHandlers[path] = handler;
};
router.beforeEach((to: RouteLocationNormalized, from: RouteLocationNormalizedLoaded, next: NavigationGuardNext): void => {
let allow = true;
let beforeLeaveHandler = router.$beforeLeaveHandlers[from.path];
if (beforeLeaveHandler) {
if (beforeLeaveHandler(to) === false) {
allow = false;
}
}
if (allow) {
next();
}
});
router.afterEach((to: RouteLocationNormalized, from: RouteLocationNormalizedLoaded): void => {
router.prev = from;
// 前后路径相同,但全路径不同(意味着参数不同),则需要刷新页面,否则页面不会刷新
if (to.path === from.path && to.fullPath !== from.fullPath) {
window.location.reload();
}
});
router.backTo = function (path?: string): void {
if (!router.prev?.path) { // 没有path,说明当前页面为刷新后进入的第一个页面,无法简单返回
let $route = getCurrentRoute(router);
if (!path) { // 未指定默认返回路径,则返回上一级页面
path = $route.meta.superiorPath as string;
}
path = instantiatePath(path, $route.params);
if (path) {
router.replace(path);
return;
}
}
router.back.call(router);
}
router.pushState = function (path: string): boolean {
let success = NetUtil.pushState('#' + path);
if (!success) {
this.push(path);
}
return success;
};
router.replaceState = function (path: string): boolean {
let success = NetUtil.replaceState('#' + path);
if (!success) {
this.replace(path);
}
return success;
};
return router;
}