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.

320 lines
13 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 { uniAppHook, Global } from '../helpers/config';
import {
callAppHook, getPageVmOrMp, ruleToUniNavInfo, formatTo, formatFrom, getPages,
} from './util';
import appletsUniPushTo from './appletsNav';
import { noop } from '../helpers/util';
import { warn } from '../helpers/warn';
/**
*
* @param {String} key
* @param {Function} hook 需要执行及还原的生命周期函数
*/
const toutiaoIndexHookCall = function (key, hook) {
const { indexVue } = uniAppHook;
const indeHooks = indexVue[key];
indeHooks.splice(indeHooks.length - 1, 1, hook);
};
/**
* 还原并执行所有 拦截下来的生命周期 app.vue 及 index 下的生命周期
* @param {Boolean} callHome // 是否触发首页的生命周期
*
* this 为当前 page 对象
*/
const callwaitHooks = function (callHome) {
return new Promise(async (resolve) => {
const variation = []; // 存储一下在uni-app上的变异生命钩子 奇葩的要死
const {
appVue, onLaunch, onShow, waitHooks, variationFuns, indexCallHooks,
} = uniAppHook;
const app = appVue.$options;
await onLaunch.fun[onLaunch.fun.length - 1].call(appVue, onLaunch.args); // 确保只执行最后一个 并且强化异步操作
onShow.fun[onShow.fun.length - 1].call(appVue, onShow.args); // onshow 不保证异步 直接确保执行最后一个
if (callHome) { // 触发首页生命周期
// eslint-disable-next-line
for (const key in waitHooks) {
if (indexCallHooks.includes(key)) { // 只有在被包含的情况下才执行
callAppHook.call(this, waitHooks[key].fun);
}
}
}
if (onLaunch.isHijack) { // 还原 onLaunch生命钩子
app.onLaunch.splice(app.onLaunch.length - 1, 1, onLaunch.fun[0]);
}
if (onShow.isHijack) { // 继续还原 onShow
app.onShow.splice(app.onShow.length - 1, 1, onShow.fun[0]);
}
// eslint-disable-next-line
for (const key in waitHooks) { // 还原 首页下的生命钩子
const item = waitHooks[key];
if (item.isHijack) {
if (variationFuns.includes(key)) { // 变异方法
variation.push({ key, fun: item.fun[0] });
} else {
toutiaoIndexHookCall(key, item.fun[0]);
}
}
}
resolve(variation);
});
};
/**
* 还原剩下的奇葩生命钩子
* @param {Object} variation 当前uni-app中的一些变异方法 奇葩生命钩子
*/
const callVariationHooks = function (variation) {
for (let i = 0; i < variation.length; i += 1) {
const { key, fun } = variation[i];
toutiaoIndexHookCall(key, fun);
}
};
/**
* 主要是对app.vue下onLaunch和onShow生命周期进行劫持
*
* this 为当前 page 对象
*/
export const proxyLaunchHook = function () {
const {
onLaunch,
onShow,
} = this.$options;
uniAppHook.appVue = this; // 缓存 当前app.vue组件对象
if (onLaunch.length > 1) { // 确保有写 onLaunch 可能有其他混入 那也办法
uniAppHook.onLaunch.isHijack = true;
uniAppHook.onLaunch.fun = onLaunch.splice(onLaunch.length - 1, 1, (arg) => {
uniAppHook.onLaunch.args = arg;
}); // 替换uni-app自带的生命周期
}
if (onShow.length > 0) {
uniAppHook.onShow.isHijack = true;
uniAppHook.onShow.fun = onShow.splice(onShow.length - 1, 1, (arg) => {
uniAppHook.onShow.args = arg;
if (uniAppHook.pageReady) { // 因为还有app切前台后台的操作
callAppHook.call(this, uniAppHook.onShow.fun, arg);
}
}); // 替换替换 都替换
}
};
/**
* 触发全局beforeHooks 生命钩子
* @param {Object} _from // from 参数
* @param {Object} _to // to 参数
*
* this 为当前 Router 对象
*/
const beforeHooks = function (_from, _to) {
return new Promise(async (resolve) => {
const beforeHooksFun = this.lifeCycle.beforeHooks[0];
if (beforeHooksFun == null) {
return resolve();
}
await beforeHooksFun.call(this, _to, _from, resolve);
});
};
/**
* 触发全局afterEachHooks 生命钩子
* @param {Object} _from // from 参数
* @param {Object} _to // to 参数
*
* this 为当前 Router 对象
*/
const afterEachHooks = function (_from, _to) {
const afterHooks = this.lifeCycle.afterHooks[0];
if (afterHooks != null && afterHooks.constructor === Function) {
afterHooks.call(this, _to, _from);
}
};
/**
* 触发全局 beforeEnter 生命钩子
* @param {Object} finalRoute // 当前格式化后的路由参数
* @param {Object} _from // from 参数
* @param {Object} _to // to 参数
*
* this 为当前 Router 对象
*/
const beforeEnterHooks = function (finalRoute, _from, _to) {
return new Promise(async (resolve) => {
const { beforeEnter } = finalRoute.route;
if (beforeEnter == null || beforeEnter.constructor !== Function) { // 当前这个beforeEnter不存在 或者类型错误
return resolve();
}
await beforeEnter.call(this, _to, _from, resolve);
});
};
/**
* v1.5.4+
* beforeRouteLeave 生命周期
* @param {Object} to 将要去的那个页面 to对象
* @param {Object} from 从那个页面触发的 from对象
* @param {Boolean} leaveHook:? 是否为 beforeRouteLeave 触发的next 到别处 如果是则不再触发 beforeRouteLeave 生命钩子
* this 为当前 Router 对象
*/
const beforeRouteLeaveHooks = function (from, to, leaveHook) {
return new Promise(async (resolve) => {
if (leaveHook) { // 我们知道这个是来自页面beforeRouteLeave next到其他地方所有不必再执行啦
warn('beforeRouteLeave next到其他地方无须再执行');
return resolve();
}
if (from.path == to.path) { // 进入首页的时候不触发
return resolve();
}
const currentPage = getPages(-2); // 获取到全部的页面对象
const callThis = getPageVmOrMp(currentPage); // 获取到页面的 $vm 对象 及 page页面的this对象
const { beforeRouteLeave } = callThis.$options; // 查看当前是否有开发者声明
if (beforeRouteLeave == null) {
warn('当前页面下无 beforeRouteLeave 钩子声明,无须执行!');
return resolve();
}
if (beforeRouteLeave != null && beforeRouteLeave.constructor !== Function) {
warn('beforeRouteLeave 生命钩子声明错误,必须是一个函数!');
return resolve();
}
await beforeRouteLeave.call(callThis, to, from, resolve); // 执行生命钩子
});
};
/**
* 核心方法 处理一系列的跳转配置
* @param {Object} rule 当前跳转规则
* @param {Object} fnType 跳转页面的类型方法
* @param {Object} navCB:? 回调函数
* @param {Boolean} leaveHook:? 是否为 beforeRouteLeave 触发的next 到别处 如果是则不再触发 beforeRouteLeave 生命钩子
* this 为当前 Router 对象
*
*/
export const appletsTransitionTo = async function (rule, fnType, navCB, leaveHook = false) {
await this.lifeCycle.routerbeforeHooks[0].call(this); // 触发内部跳转前的生命周期
const finalRoute = ruleToUniNavInfo(rule, this.CONFIG.routes); // 获得到最终的 route 对象
const _from = formatFrom(this.CONFIG.routes); // 先根据跳转类型获取 from 数据
const _to = formatTo(finalRoute); // 再根据跳转类型获取 to 数据
try {
const leaveResult = await beforeRouteLeaveHooks.call(this, _from, _to, leaveHook); // 执行页面中的 beforeRouteLeave 生命周期 v1.5.4+
// eslint-disable-next-line
await isNext.call(this, leaveResult, fnType, navCB,true); // 验证当前是否继续 可能需要递归 那么 我们把参数传递过去
const beforeResult = await beforeHooks.call(this, _from, _to); // 执行 beforeEach 生命周期
// eslint-disable-next-line
await isNext.call(this, beforeResult, fnType, navCB); // 验证当前是否继续 可能需要递归 那么 我们把参数传递过去
const enterResult = await beforeEnterHooks.call(this, finalRoute, _from, _to); // 接着执行 beforeEnter 生命周期
// eslint-disable-next-line
await isNext.call(this, enterResult, fnType, navCB); // 再次验证 如果生命钩子多的话应该写成递归或者循环
} catch (e) {
warn(e); // 打印开发者操作的日志
return false;
}
if (navCB) {
navCB.call(this, finalRoute, fnType); // 执行当前回调生命周期
}
afterEachHooks.call(this, _from, _to);
await this.lifeCycle.routerAfterHooks[0].call(this); // 触发内部跳转前的生命周期
};
/**
* 触发全局 返回事件
* @param {Number} backLayer 需要返回的页面层级
* @param {Function} next 正真的回调函数
*
* this 为当前 Router 对象
*/
export const backCallHook = function (backLayer, next) {
const pages = getPages(); // 获取到全部的页面对象
const toPage = pages.reverse()[backLayer];
if (toPage == null) { // 没有匹配到的时候
return warn('亲爱的开发者,你确定页面栈中有这么多历史记录给你返回?');
}
const { query, page } = getPageVmOrMp(toPage, false);
const beforeFntype = 'RouterBack';
appletsTransitionTo.call(this, { path: page.route, query }, beforeFntype, (finalRoute, fnType) => {
const toPath = finalRoute.uniRoute.url;
if (`/${page.route}` == toPath || page.route == toPath) { // 直接调用返回api
next();
} else { // 有拦截到其他页面时
if (fnType == beforeFntype) {
return warn('调用返回api被拦截到其他页面需要指定合理的 NAVTYPE ');
}
appletsUniPushTo(finalRoute, fnType);
}
});
};
/**
* 主动触发导航守卫
* @param {Object} Router 当前路由对象
*
*/
export const triggerLifeCycle = function (Router) {
const topPage = getCurrentPages()[0];
if (topPage == null) {
return warn('打扰了,当前一个页面也没有 这不是官方的bug是什么??');
}
const { query, page } = getPageVmOrMp(topPage, false);
appletsTransitionTo.call(Router, { path: page.route, query }, 'push', async (finalRoute, fnType) => {
let variation = [];
if (`/${page.route}` == finalRoute.route.path || page.route == finalRoute.route.path) { // 在首页不动的情况下
uniAppHook.pageReady = true; // 标致着路由已经就绪 可能准备起飞
await callwaitHooks.call(this, true);
} else { // 需要跳转
variation = await callwaitHooks.call(this, false); // 只触发app.vue中的生命周期
await appletsUniPushTo(finalRoute, fnType);
}
uniAppHook.pageReady = true; // 标致着路由已经就绪 可能准备起飞
callVariationHooks(variation);
});
};
/**
* 把指定页面的生命钩子函数保存并替换
* this 为当前 page 对象
*/
export const appletsProxyIndexHook = function (Router) {
if (process.env.VUE_APP_PLATFORM == 'mp-toutiao') { // 头条小程序首页生命周期由我们手动触发缓存this
uniAppHook.toutiaoIndexThis = this;
}
const { needHooks, waitHooks } = uniAppHook;
const options = this.$options;
uniAppHook.indexVue = options;
for (let i = 0; i < needHooks.length; i += 1) {
const key = needHooks[i];
if (options[key] != null) { // 只劫持开发者声明的生命周期
const { length } = options[key];
// eslint-disable-next-line
const whObject= waitHooks[key]={};
whObject.fun = options[key].splice(length - 1, 1, noop); // 把实际的页面生命钩子函数缓存起来,替换原有的生命钩子
whObject.isHijack = true;
}
}
triggerLifeCycle.call(this, Router); // 接着 主动我们触发导航守卫
};
/**
* 验证当前 next() 管道函数是否支持下一步
*
* @param {Object} Intercept 拦截到的新路由规则
* @param {Object} fnType 跳转页面的类型方法 原始的
* @param {Object} navCB 回调函数 原始的
* @param {Boolean} leaveHookCall:? 是否为 beforeRouteLeave 触发的next 做拦截判断
* this 为当前 Router 对象
*
*/
const isNext = function (Intercept, fnType, navCB, leaveHookCall = false) {
return new Promise((resolve, reject) => {
if (Intercept == null) { // 什么也不做 直接执行下一个钩子
return resolve();
}
if (Intercept === false) { // 路由中断 我们需要把防抖设置为false
Global.LockStatus = false; // 解锁跳转状态
return reject('路由终止');
}
if (Intercept.constructor === String) { // 说明 开发者直接传的path 并且没有指定 NAVTYPE 那么采用原来的navType
reject('next到其他页面');
return appletsTransitionTo.call(this, Intercept, fnType, navCB, leaveHookCall);
}
if (Intercept.constructor === Object) { // 有一系列的配置 包括页面切换动画什么的
reject('next到其他页面');
return appletsTransitionTo.call(this, Intercept, Intercept.NAVTYPE || fnType, navCB, leaveHookCall);
}
});
};