You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

309 lines
12 KiB

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

import { warn, err } from '../helpers/warn';
import {
diffRouter, vueDevRouteProxy, getRouterNextInfo, formatUserRule, nameToRute, encodeURLQuery, strPathToObjPath, getPages,
} from './util';
import { formatURLQuery } from '../helpers/util';
import { vuelifeHooks, vueMount } from './base';
import { lifeCycle, Global } from '../helpers/config';
let beforeEachCount = 0;
let afterEachCount = 0;
let resolveLaunch = null;
let beforeEnterDep = [];// 记录当前是否有重复的页面进入 避免重复触发
const beforeEachLaunch = new Promise((resolve) => resolveLaunch = resolve);
/**
* 把vue实例进行挂载到dom下
* @param {Router} Router uni-simple-router实例对象
*/
export const appMount = function () {
if (vueMount.length == 0) {
return err('检测到您未进行dom模型挂载操作请调用api\r\n\r\n RouterMount(Vim: any, el: any): void');
}
const {
Vim,
el,
} = vueMount[0];
let formatEl = el;
if (el == null) {
formatEl = '#app'; // 这是uni-app在h5中的官方节点
}
try {
Vim.$mount(formatEl);
} catch (error) {
warn(`挂载vue节点时错误啦${error}`);
}
};
/**
* 格式化 next传递过来的参数 作为vue-router可用的
* @param {Object} to//即将跳转到的路由页面
* @param {*} Intercept
* @param {Funtion} next//路由连接管道
* @param {Router} Router//路由对象
*/
export const forMatNext = function (to, Intercept, next, Router) {
const { CONFIG, selfRoutes } = Router;
if (CONFIG.h5.vueRouterDev) { // 完全使用vue-router开发的时候 vueRouterDev:true 不用做啥直接略过
next(Intercept);
return Intercept;
}
if (typeof Intercept === 'object') { // 只有是对象类型的时候 我们才进行格式化
const navType = Reflect.get(Intercept, 'NAVTYPE');
delete Intercept.NAVTYPE;
if (navType == 'push') {
Intercept.replace = false;
Intercept.type = 'navigateTo';
} else {
Intercept.replace = true; // uni-app导航api所谓的NAVTYPE取值在h5都是replace:true
Intercept.type = 'reLaunch';
}
const name = Reflect.get(Intercept, 'name'); // 统一格式化path
Intercept.query = Intercept.params || Intercept.query;
delete Intercept.name;
delete Intercept.params;
if (Intercept.query == null) {
Intercept.query = {};
}
if (name != null) {
const { aliasPath, path } = nameToRute(name, selfRoutes);
Intercept.path = aliasPath || path;
} else { // 当设置别名时可以是别名跳转也可以path跳转
Intercept.path = Reflect.get(Intercept, 'path');
const rute = formatUserRule(Intercept.path, selfRoutes, CONFIG);
if (rute == null) {
return false;
}
Intercept.path = rute;
}
if (CONFIG.encodeURI) { // 如果设置的编码传递则进行编码后传递
const query = encodeURIComponent(JSON.stringify(Intercept.query));
const formatQuery = formatURLQuery(query);
Intercept.query = {};
if (formatQuery != '') {
Intercept.query.query = formatQuery;
}
}
} else if (Intercept != null && Intercept.constructor === String) {
Intercept = formatUserRule(Intercept, selfRoutes, CONFIG);
}
let objPath = Intercept;
if (Intercept != null && Intercept.constructor !== Boolean) {
objPath = strPathToObjPath(Intercept);
if (objPath != null) {
const type = Reflect.get(objPath, 'type');
if (type == null) { // 当next()是一个路径时
objPath.type = 'navigateTo';
}
}
} else if (Intercept === false) {
Router.lifeCycle.routerAfterHooks[0].call(Router, { H5Intercept: true });
}
next(objPath);// 统一格式化为对象的方式传递
return Intercept;
};
/**
* v1.5.4+
* beforeRouteLeave 生命周期
* @param {Object} to 将要去的那个页面 vue-router to对象
* @param {Object} from 从那个页面触发的 vue-router from对象
* @param {Object} next vue-router beforeEach next管道函数
* @param {Object} Router Router路由对象
*/
const beforeRouteLeaveHooks = function (to, from, next, Router) {
return new Promise((resolve) => {
const { currentRoute } = Router.$route;
if (currentRoute.path == to.path) { // 如果是同一个页面直接返回 不执行页面中的Leave事件
return resolve();
}
const page = getPages(); // 获取到当前的页面对象
if (page == null || page._HHYANGbeforeRouteLeaveCalled) {
warn('当前环境下无须执行 beforeRouteLeave。 原因1.page等于null 2.真的的无须执行');
return resolve();
}
const beforeRouteLeaveArray = page.$options.beforeRouteLeave; // 获取到页面下的 beforeRouteLeave 路由守卫
if (beforeRouteLeaveArray == null) { // 当前页面没有预设 beforeRouteLeave 啥都不做
return resolve();
}
const { toRoute, fromRoute } = getRouterNextInfo(to, from, Router);
const beforeRouteLeave = beforeRouteLeaveArray[0]; // 不管怎么样 只执行第一个钩子 其他都不管
beforeRouteLeave.call(page, toRoute, fromRoute, (Intercept) => { // 开始执行生命周期
if (Intercept == null) { // 放行状态 直接返回
return resolve();
}
page._HHYANGbeforeRouteLeaveCalled = true; // 标记一下当前已经做过 beforeRouteLeave 啦
forMatNext(to, Intercept, next, Router); // 直接交给vue-router 处理
});
});
};
/**
* 修复首页beforeEnter执行两次的问题 https://github.com/SilurianYang/uni-simple-router/issues/67
*
* beforeEnter 生命周期
* @param {Object} to
* @param {Object} from
* @param {Object} next
* @param {Object} userHooks
* @param {Object} Router
*/
export const beforeEnterHooks = function (to, from, next, userHooks, Router) {
return new Promise(async (resolve) => {
// 修复 (#67)
if (beforeEnterDep.includes(to.path)) {
next();
return resolve();
}
beforeEnterDep = [to.path];
if (Reflect.get(Router, 'H5RouterReady')) {
const res = await new Promise(async (resolveNext) => {
const {
toRoute,
fromRoute,
} = getRouterNextInfo(to, from, Router);
await userHooks(toRoute, fromRoute, resolveNext);
});
forMatNext(to, res, next, Router);
} else {
next();
}
resolve();
});
};
/**
* vueAfter 生命周期
* @param {Object} to
* @param {Object} from
* @param {Object} next
* @param {Object} Router
*/
export const afterHooks = async function (to, from, next, Router) {
vuelifeHooks.afterHooks[0](to, from);
if (lifeCycle.afterHooks[0]) {
if (afterEachCount === 0) {
await beforeEachLaunch;
appMount(Router);
}
const {
toRoute,
fromRoute,
} = getRouterNextInfo(to, from, Router);
lifeCycle.afterHooks[0](toRoute, fromRoute);
} else if (afterEachCount === 0) {
appMount(Router);
}
afterEachCount += 1;
Router.lifeCycle.routerAfterHooks[0].call(Router);
};
/**
* vueBefore 生命周期
* @param {Object} to 将要去的那个页面 vue-router to对象
* @param {Object} from 从那个页面触发的 vue-router from对象
* @param {Object} next vue-router beforeEach next管道函数
* @param {Object} H5Config
*/
export const beforeHooks = function (to, from, next, Router) {
return new Promise(async (resolve) => {
await Router.lifeCycle.routerbeforeHooks[0].call(Router); // 触发Router内置前置生命周期
await beforeRouteLeaveHooks(to, from, next, Router); // 执行1.5.4+ beforeRouteLeave生命钩子
const H5 = Router.CONFIG.h5;
vuelifeHooks.beforeHooks[0](to, from, async (Intercept) => {
if (Intercept != null && H5.keepUniIntercept === true && H5.vueRouterDev === false) {
next(Intercept);
warn('uni-app 内部强制触发跳转拦截');
beforeEachCount += 1;
return resolve();
}
// 顺序问题 没有触发uni-app里面的方法 修复[#44](https://github.com/SilurianYang/uni-simple-router/issues/44)
if (!lifeCycle.beforeHooks[0]) {
next();
beforeEachCount += 1;
resolveLaunch();
return resolve();
}
const res = await new Promise(async (resolveNext) => {
const {
toRoute,
fromRoute,
} = getRouterNextInfo(to, from, Router);
await lifeCycle.beforeHooks[0](toRoute, fromRoute, resolveNext);
});
const beforeIntercept = forMatNext(to, res, next, Router);
if (beforeEachCount == 0 && beforeIntercept == null && to.meta.isTabBar) { // 首次触发beforeEach并且首页没有进行跳转的情况下才触发beforeEnter 主要是keep-alive
const {
selfRoutes,
} = Router;
const beforeEnter = Reflect.get(selfRoutes[`/${to.meta.pagePath}`], 'beforeEnter');
if (beforeEnter) {
await beforeEnterHooks(to, from, next, beforeEnter, Router);
}
}
beforeEachCount += 1;
resolveLaunch();
resolve();
});
});
};
/**
* 通过自动调用router api来完成触发生命周期
* 修复 history 模式下报错的问题 https://github.com/SilurianYang/uni-simple-router/issues/38
* 修复 history 模式下刷新页面参数丢失的问题 https://github.com/SilurianYang/uni-simple-router/issues/45
* 修复 history 模式下首次打开页面url错误时不走 path:* 的匹配项 https://github.com/SilurianYang/uni-simple-router/issues/58
*
* @param {Object} Router //当前simple-router 对象
* @param {Object} vueRouter vue-router对象
*/
export const triggerLifeCycle = function (Router, vueRouter) {
const { CONFIG } = Router;
const currRoute = vueRouter.currentRoute;
if (vueRouter.mode === 'hash') {
const {
query,
path,
} = currRoute;
const URLQuery = encodeURLQuery(CONFIG, query, 'hash');
vueRouter.replace(`${path}${URLQuery}`);
} else {
const {
toRoute,
} = getRouterNextInfo(currRoute, currRoute, Router);
const URLQuery = encodeURLQuery(CONFIG, currRoute.query, 'history');
vueRouter.replace({
path: toRoute.aliasPath || toRoute.path || currRoute.path,
query: URLQuery,
type: 'redirectTo',
});
}
};
/** 注册自定义的路由到vue-router中 前提是必须使用vueRouter开发模式
* @param {Object} Router
* @param {Object} vueRouter
* @param {Boolean} vueRouterDev
*/
export const registerRouter = function (Router, vueRouter, vueRouterDev) {
let routeMap = [];
if (!vueRouterDev) { // 则进行对比两个路由表 主要工作是做路径的优化
routeMap = diffRouter(Router, vueRouter, Router.CONFIG.h5.useUniConfig);
} else { // 完全使用vue-router开发时直接采用开发者的路由表
routeMap = vueDevRouteProxy(Router.CONFIG.routes, Router);
}
const createRouter = () => new vueRouter.constructor({
base: vueRouter.options.base,
mode: vueRouter.options.mode,
routes: routeMap,
});
const router = createRouter();
vueRouter.matcher = router.matcher;
Global.vueRouter = vueRouter; // 把当前vueRouter缓存到全局对象中
Global.RouterReadyPromise(); // router已经准备就绪 调用promise.resolve();
Router.H5RouterReady = true; // 并挂载到Router对象下
// 注册完成所有的钩子及相关参数手动触发Router的生命周期
setTimeout(() => {
triggerLifeCycle(Router, vueRouter);
});
};