Houjie
2025-04-11 1bf977929dd324f3ac64b70debd8a79443c54392
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
import { uniAppHook, Global } from '../helpers/config';
import {
    callAppHook, getPages, getPageVmOrMp, ruleToUniNavInfo, formatTo, formatFrom, APPGetPageRoute, getPageOnBeforeBack,
} from './util';
import { noop } from '../helpers/util';
import { warn } from '../helpers/warn';
import uniPushTo from './uniNav';
 
let startBack = false;    // 主要是兼容低端手机返回卡 然后多次返回直接提示退出的问题
 
/**
 * 还原并执行所有 拦截下来的生命周期 app.vue 及 index 下的生命周期
 * @param {Boolean} callHome // 是否触发首页的生命周期
 *
 * this 为当前 page 对象
 */
const callwaitHooks = function (callHome) {
    return new Promise(async (resolve) => {
        const variation = [];    // 存储一下在uni-app上的变异生命钩子  奇葩的要死
        const {
            appVue, indexVue, 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 {
                    const indeHooks = indexVue[key];
                    // 修复 https://github.com/SilurianYang/uni-simple-router/issues/76
                    setTimeout(() => {    // 异步延迟还原 不然 uni-app 给给触发了
                        indeHooks.splice(indeHooks.length - 1, 1, item.fun[0]);
                    }, 50);
                }
            }
        }
        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];
        const indeHooks = uniAppHook.indexVue[key];
        indeHooks.splice(indeHooks.length - 1, 1, 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);
            }
        });    // 替换替换 都替换
    }
};
 
/**
 * 把指定页面的生命钩子函数保存并替换
 * this 为当前 page 对象
 */
export const proxyIndexHook = function (Router) {
    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;
        }
    }
    // eslint-disable-next-line
    triggerLifeCycle.call(this, Router);    // 接着 主动我们触发导航守卫
};
/**
 * 触发全局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);
    });
};
/**
 * 触发返回事件公共方法
 * @param {Object} page    用getPages获取到的页面栈对象
 * @param {Object} options     当前vue页面对象
 * @param {Object} backLayerC    需要返回页面的层级
   *
 * this 为当前 Router 对象
 */
const backCallHook = function (page, options, backLayerC = 1) {
    const route = APPGetPageRoute([page]);
    const NAVTYPE = 'RouterBack';
    // eslint-disable-next-line
    transitionTo.call(this, { path: route.path, query: route.query }, NAVTYPE, (finalRoute, fnType) => {
        if (fnType != NAVTYPE) { // 返回时的api如果有next到其他页面 那么必须带上NAVTYPE  不相同则表示需要跳转到其他页面
            return uniPushTo(finalRoute, fnType);
        }
        if (startBack) { // 如果当前处于正在返回的状态
            return warn('当前处于正在返回的状态,请稍后再试!');
        }
        startBack = true;    // 标记开始返回
        options.onBackPress = [noop];    // 改回uni-app可执行的状态
        setTimeout(() => {
            this.back(backLayerC, undefined, true); // 越过加锁验证
            startBack = false;    // 返回结束
        });
    });
};
/**
 * 处理返回按钮的生命钩子
 * @param {Object} options 当前 vue 组件对象下的$options对象
 * @param {Array} args  当前页面是点击头部返回还是底部返回
 *
 * this 为当前 Router 对象
 */
export const beforeBackHooks = async function (options, args) {
    const isNext = await getPageOnBeforeBack(args); // 执行onBeforeBack
    if (isNext === false) { // onBeforeBack  返回了true 阻止了跳转
        Global.LockStatus = false; // 也需要解锁
        return false;
    }
    const page = getPages(-3);    // 上一个页面对象
    backCallHook.call(this, page, options);
};
/**
 * 处理back api的生命钩子
 * @param {Object} options 当前 vue 组件对象下的$options对象
 * @param {Array} args  当前页面是点击头部返回还是底部返回
 *
 * this 为当前 Router 对象
 */
export const backApiCallHook = async function (options, args) {
    await getPageOnBeforeBack(args);
    const { backLayerC } = Global;
    const pages = getPages();
    let page = null;
    if (backLayerC > pages.length - 1 || backLayerC == pages.length - 1) {    // 返回的首页 我们需要显示tabbar拦截
        // eslint-disable-next-line
        page = pages[0];
    } else {
        page = pages[pages.length - 2];
    }
    backCallHook.call(this, page, options, backLayerC);
};
/**
 *  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((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();
        }
        beforeRouteLeave.call(callThis, to, from, resolve); // 执行生命钩子
    });
};
 
/**
 * 验证当前 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) {        // 路由中断
            Global.LockStatus = false; // 解锁跳转状态
            return reject('路由终止');
        }
        if (Intercept.constructor === String) {        // 说明 开发者直接传的path 并且没有指定 NAVTYPE 那么采用原来的navType
            reject('next到其他页面');
            // eslint-disable-next-line
            return transitionTo.call(this, Intercept, fnType, navCB,leaveHookCall);
        }
        if (Intercept.constructor === Object) {    // 有一系列的配置 包括页面切换动画什么的
            reject('next到其他页面');
            // eslint-disable-next-line
            return transitionTo.call(this, Intercept, Intercept.NAVTYPE || fnType, navCB,leaveHookCall);
        }
    });
};
/**
 * 核心方法 处理一系列的跳转配置
 * @param {Object} rule 当前跳转规则
 * @param {Object} fnType 跳转页面的类型方法
 * @param {Object} navCB:? 回调函数
 * @param {Boolean} leaveHook:? 是否为 beforeRouteLeave 触发的next 到别处 如果是则不再触发 beforeRouteLeave 生命钩子
 *
 * this 为当前 Router 对象
 */
export const transitionTo = 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+
        await isNext.call(this, leaveResult, fnType, navCB, true);    // 验证当前是否继续  可能需要递归  那么 我们把参数传递过去
 
        const beforeResult = await beforeHooks.call(this, _from, _to);        // 执行 beforeEach 生命周期
        await isNext.call(this, beforeResult, fnType, navCB);    // 验证当前是否继续  可能需要递归  那么 我们把参数传递过去
 
        const enterResult = await beforeEnterHooks.call(this, finalRoute, _from, _to);    // 接着执行 beforeEnter 生命周期
        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 {Object} Router 当前路由对象
 *
 * this  当前vue页面组件对象
 */
export const triggerLifeCycle = function (Router) {
    const topPage = getCurrentPages()[0];
    if (topPage == null) {
        return warn('打扰了,当前一个页面也没有 这不是官方的bug是什么??');
    }
    const { query, page } = getPageVmOrMp(topPage, false);
    transitionTo.call(Router, { path: page.route, query }, 'push', async (finalRoute, fnType) => {
        let variation = [];
        if (`/${page.route}` == finalRoute.route.path) {        // 在首页不动的情况下
            uniAppHook.pageReady = true;        // 标致着路由已经就绪 可能准备起飞
            await callwaitHooks.call(this, true);
        } else {    // 需要跳转
            variation = await callwaitHooks.call(this, false);    // 只触发app.vue中的生命周期
            await uniPushTo(finalRoute, fnType);
        }
        plus.nativeObj.View.getViewById('router-loadding').close();
        callVariationHooks(variation);
        uniAppHook.pageReady = true;        // 标致着路由已经就绪 可能准备起飞
    });
};
 
/**
 * 处理tabbar点击拦截事件
 * @param {Object} path 当前需要跳转的tab页面路径
 *
 * this 为当前 Router 对象
 */
export const beforeTabHooks = function (path) {
    transitionTo.call(this, { path: `/${path}`, query: {} }, 'pushTab', (finalRoute, fnType) => {
        uniPushTo(finalRoute, fnType);
    });
};