记录--有关uni-app如何实现路由拦截的知识分享
这里给大家分享我在网上总结出来的一些知识,希望对大家有所帮助

前言
随着业务的需求,项目需要支持H5、各类小程序以及IOS和Android,这就需要涉及到跨端技术,不然每一端都开发一套,人力成本和维护成本太高了。团队的技术栈主要以Vue为主,最终的选型是以uni-app+uview2.0作为跨端技术栈。以前一直听别人吐槽uni-app怎么怎么不好,但是没什么概念,这一次需要为团队开发一个项目的基础框架和一些示例页面,主要是支持路由拦截、http请求多实例、请求数据加密以及登录功能封装,发现uni-app的生态不怎么健全,比如我们项目很需要的路由拦截,http请求拦截,这些都没有提供,对于跨端的兼容问题也挺多的。这篇文章聊聊的路由拦截的调研,以及最终的选择和实现。
实现路由拦截的方式
- 使用uni-simple-router
- 重写uni-app跳转方法
- 对uni-app跳转方法做进一步的封装
使用uni-simple-router
uni-simple-router是为uni-app专门提供的路由管理器,使用方式跟vue-router的API一致,可以很方便的上手,Github 也有了六百多的start,它可以说是uni-app用来做路由管理很好的选择,但是我没有选择使用它,个人认为开发h5是可以的,但是如果做跨端,可能会有一些后患,接下来我们聊聊为什么不使用它的原因。
无法拦截switchTab、navigateBack
这个其实也不算是一个缺点,目前也没找到可以拦截这两个事件的路由插件,如果确实需要实现这两种跳转方式的拦截,也是可以实现的,可以使用下一种方式,对这两种方法进行暴力重写。
没有解决全部的跨端兼容问题
这个其实是我不选择它的主要原因,根据官方文档的说明,根据文档去配置和编写,基本上能解决所有端上的95%的问题,其他的5%的问题需要去查看编译到端的说明。代码还是严谨的,缺少1%都是不完美的,更何况是5%。这会导致在以后的使用过程中,可能因为兼容问题,导致自己没办法去解决,或者为了解决这个问题,需要花费大量的时间和精力,有可能得不偿失。
编译app时,不能用'nvue'作为启动页面
nvue 不能直接作为启动页面。因为在启动时 uni-app 会检测启动页面是否为原生渲染,原生渲染时不会执行路由跳转,插件无法正确捕捉页面挂载。这也是一个问题,我们可以尽量的去避免,但以后有未知的情况,可能我们的启动页必须就是以nvue来实现。
暴力重写uni-app跳转方法
这种方式虽然有点简单粗暴,但是效果挺好的,代码也很简短,Vue2.0对于数组的响应式监听也是采用这种方式。虽然实现了,但可能有些同学不知道怎么使用,直接把这段代码写在main.js就可以了,或者也可以在单独的文件里封装一个封装一个函数,然后在main.js引入,然后执行该方法。
const routeInterceptor = () => {
const methodToPatch = ["navigateTo", "redirectTo", "switchTab", "navigateBack"];
methodToPatch.map((type) => {
// 通过遍历的方式分别取出,uni.navigateTo、uni.redirectTo、uni.switchTab、uni.navigateBack
// 并且对相应的方法做重写
const original = uni[type];
uni[item] = function (options = {}) {
if (!token) {
// 判断是否存在token,不存在重定向到登录页
uni.navigateTo({
url: "/login",
});
} else {
return original.call(this, opt);
}
};
});
}
routeInterceptor()
这是一个最极简的方式,需要添加其他参数和判断逻辑,大家可以自行添加,这里只是抛砖引玉,给大家提供一个思路。
使用方式
handleDetail() {
uni.navigateTo({
url: '/detail?id=11111111111'
})
}
对uni-app跳转方法做进一步的封装
这个是 uView提供的一种路由封装方式,对于路由传参做了进一步的封装,使用起来更加方便,但是不涉及到uni-app跳转方式的重写,所以也谈不上改了路由跳转的跨端兼容,所以还是具有uni-app一致的兼容性。但是官方文档没有说明提供了路由拦截,但这个还是我们特别需要的功能,去查看源码,发现还是提供了这个功能。现在还存在的一个问题是,这个功能是跟uView强耦合的,可能我们并不想使用uView,所以我们可以将这个功能独立抽离。

目录结构

/router/index.js
这个文件主要提供路由拦截函数,具体的实现,可以大家可以根据自己的需求实现,最后向外暴露一个包含install方法的对象,使用的时候可以直接用Vue.use进行注册。
routeConfig这个参数是路由相关的配置,resolve 传递一个true或者false表示是否允许跳转。
routeConfig属性
| 参数名 | 类型 | 默认值 | 是否必填 | 说明 |
|---|---|---|---|---|
| type | String | navigateTo | false | navigateTo或to对应uni.navigateTo,redirect或redirectTo对应uni.redirectTo,switchTab或tab对应uni.switchTab,reLaunch对应uni.reLaunch,navigateBack或back对应uni.navigateBack |
| url | String | - | false | type为navigateTo,redirectTo,switchTab,reLaunch时为必填 |
| delta | Number | 1 | false | type为navigateBack时用到,表示返回的页面数 |
| params | Object | - | false | 传递的对象形式的参数,如{name: 'lisa', age: 18} |
| animationType | String | pop-in | false | 只在APP生效,详见窗口动画(opens new window) |
| animationDuration | Number | 300 | false | 动画持续时间,单位ms |
import route from "./route";
// 配置白名单
const whiteList = ["/pages/login/index"];
const install = function (Vue, options) {
uni.$e = { route };
Vue.prototype.route = route;
uni.$e.routeIntercept = (routeConfig, resolve) => {
const path = routeConfig.url.split("?")[0];
if (!whiteList.includes(path) && !uni.getStorageSync("token")) {
uni.$e.route("/pages/login/index");
return;
}
resolve(true);
};
};
export default {
install,
};
/router/route.js
这个文件,主要是对于uni-app跳转做了封装,主要做的还是传参部分,实现跟vue-router一致的传参方式,使用起来更加方便优雅,同时提供一个uni.$e.routeIntercept路由拦截方法。
/**
* 路由跳转方法,该方法相对于直接使用uni.xxx的好处是使用更加简单快捷
* 并且带有路由拦截功能
*/ import { queryParams, deepClone, deepMerge, page } from "./utils";
class Router {
constructor() {
// 原始属性定义
this.config = {
type: "navigateTo",
url: "",
delta: 1, // navigateBack页面后退时,回退的层数
params: {}, // 传递的参数
animationType: "pop-in", // 窗口动画,只在APP有效
animationDuration: 300, // 窗口动画持续时间,单位毫秒,只在APP有效
intercept: false, // 是否需要拦截
};
// 因为route方法是需要对外赋值给另外的对象使用,同时route内部有使用this,会导致route失去上下文
// 这里在构造函数中进行this绑定
this.route = this.route.bind(this);
} // 判断url前面是否有"/",如果没有则加上,否则无法跳转
addRootPath(url) {
return url[0] === "/" ? url : `/${url}`;
} // 整合路由参数
mixinParam(url, params) {
url = url && this.addRootPath(url); // 使用正则匹配,主要依据是判断是否有"/","?","="等,如“/page/index/index?name=mary"
// 如果有url中有get参数,转换后无需带上"?"
let query = "";
if (/.*\/.*\?.*=.*/.test(url)) {
// object对象转为get类型的参数
query = queryParams(params, false);
// 因为已有get参数,所以后面拼接的参数需要带上"&"隔开
return (url += `&${query}`);
}
// 直接拼接参数,因为此处url中没有后面的query参数,也就没有"?/&"之类的符号
query = queryParams(params);
return (url += query);
} // 对外的方法名称
async route(options = {}, params = {}) {
// 合并用户的配置和内部的默认配置
let mergeConfig = {}; if (typeof options === "string") {
// 如果options为字符串,则为route(url, params)的形式
mergeConfig.url = this.mixinParam(options, params);
mergeConfig.type = "navigateTo";
} else {
mergeConfig = deepClone(options, this.config);
// 否则正常使用mergeConfig中的url和params进行拼接
mergeConfig.url = this.mixinParam(options.url, options.params);
} // 如果本次跳转的路径和本页面路径一致,不执行跳转,防止用户快速点击跳转按钮,造成多次跳转同一个页面的问题
if (mergeConfig.url === page()) return; if (params.intercept) {
this.config.intercept = params.intercept;
}
// params参数也带给拦截器
mergeConfig.params = params;
// 合并内外部参数
mergeConfig = deepMerge(this.config, mergeConfig);
// 判断用户是否定义了拦截器
if (typeof uni.$e.routeIntercept === "function") {
// 定一个promise,根据用户执行resolve(true)或者resolve(false)来决定是否进行路由跳转
const isNext = await new Promise((resolve, reject) => {
uni.$e.routeIntercept(mergeConfig, resolve);
});
// 如果isNext为true,则执行路由跳转
isNext && this.openPage(mergeConfig);
} else {
this.openPage(mergeConfig);
}
} // 执行路由跳转
openPage(config) {
// 解构参数
const { url, type, delta, animationType, animationDuration } = config;
if (config.type == "navigateTo" || config.type == "to") {
uni.navigateTo({
url,
animationType,
animationDuration,
});
}
if (config.type == "redirectTo" || config.type == "redirect") {
uni.redirectTo({
url,
});
}
if (config.type == "switchTab" || config.type == "tab") {
uni.switchTab({
url,
});
}
if (config.type == "reLaunch" || config.type == "launch") {
uni.reLaunch({
url,
});
}
if (config.type == "navigateBack" || config.type == "back") {
uni.navigateBack({
delta,
});
}
}
} export default new Router().route;
/router/uitls.js
这个文件主要是为路由封装提供一些工具函数
/**
* @description 对象转url参数
* @param {object} data,对象
* @param {Boolean} isPrefix,是否自动加上"?"
* @param {string} arrayFormat 规则 indices|brackets|repeat|comma
*/
export const queryParams = (
data = {},
isPrefix = true,
arrayFormat = "brackets"
) => {
const prefix = isPrefix ? "?" : "";
const _result = [];
if (["indices", "brackets", "repeat", "comma"].indexOf(arrayFormat) == -1)
arrayFormat = "brackets";
for (const key in data) {
const value = data[key];
// 去掉为空的参数
if (["", undefined, null].indexOf(value) >= 0) {
continue;
}
// 如果值为数组,另行处理
if (value.constructor === Array) {
// e.g. {ids: [1, 2, 3]}
switch (arrayFormat) {
case "indices":
// 结果: ids[0]=1&ids[1]=2&ids[2]=3
for (let i = 0; i < value.length; i++) {
_result.push(`${key}[${i}]=${value[i]}`);
}
break;
case "brackets":
// 结果: ids[]=1&ids[]=2&ids[]=3
value.forEach((_value) => {
_result.push(`${key}[]=${_value}`);
});
break;
case "repeat":
// 结果: ids=1&ids=2&ids=3
value.forEach((_value) => {
_result.push(`${key}=${_value}`);
});
break;
case "comma":
// 结果: ids=1,2,3
let commaStr = "";
value.forEach((_value) => {
commaStr += (commaStr ? "," : "") + _value;
});
_result.push(`${key}=${commaStr}`);
break;
default:
value.forEach((_value) => {
_result.push(`${key}[]=${_value}`);
});
}
} else {
_result.push(`${key}=${value}`);
}
}
return _result.length ? prefix + _result.join("&") : "";
}; /**
* 是否数组
*/
function isArray(value) {
if (typeof Array.isArray === "function") {
return Array.isArray(value);
}
return Object.prototype.toString.call(value) === "[object Array]";
} /**
* @description 深度克隆
* @param {object} obj 需要深度克隆的对象
* @returns {*} 克隆后的对象或者原值(不是对象)
*/
export const deepClone = (obj) => {
// 对常见的“非”值,直接返回原来值
if ([null, undefined, NaN, false].includes(obj)) return obj;
if (typeof obj !== "object" && typeof obj !== "function") {
// 原始类型直接返回
return obj;
}
const o = isArray(obj) ? [] : {};
for (const i in obj) {
if (obj.hasOwnProperty(i)) {
o[i] = typeof obj[i] === "object" ? deepClone(obj[i]) : obj[i];
}
}
return o;
}; /**
* @description JS对象深度合并
* @param {object} target 需要拷贝的对象
* @param {object} source 拷贝的来源对象
* @returns {object|boolean} 深度合并后的对象或者false(入参有不是对象)
*/
export const deepMerge = (target = {}, source = {}) => {
target = deepClone(target);
if (typeof target !== "object" || typeof source !== "object") return false;
for (const prop in source) {
if (!source.hasOwnProperty(prop)) continue;
if (prop in target) {
if (typeof target[prop] !== "object") {
target[prop] = source[prop];
} else if (typeof source[prop] !== "object") {
target[prop] = source[prop];
} else if (target[prop].concat && source[prop].concat) {
target[prop] = target[prop].concat(source[prop]);
} else {
target[prop] = deepMerge(target[prop], source[prop]);
}
} else {
target[prop] = source[prop];
}
}
return target;
}; /**
* @description 获取当前页面路径
*/
export const page = () => {
const pages = getCurrentPages();
// 某些特殊情况下(比如页面进行redirectTo时的一些时机),pages可能为空数组
return `/${pages[pages.length - 1]?.route ?? ""}`;
};
路由配置
在main.js引入
import router from "./router"; Vue.use(router);
使用方式
更全的使用方式可以查看 uView路由跳转文档
全局使用
uni.$e.route('/pages/info/index');
vue文件中使用
this.route('/pages/info/index');
拦截switchTab、navigateBack
现在的方式还是没办法支持拦截switchTab、navigateBack,所以需要借助第二种方式,重写这两种方法,具体实现,完善 /router/index.js
// /router/index.js import route from "./route";
// 配置白名单
const whiteList = ["/pages/login/index"]; const handleOverwirteRoute = () => {
// 重写switchTab、navigateBack
const methodToPatch = ["switchTab", "navigateBack"];
methodToPatch.map((type) => {
// 通过遍历的方式分别取出,uni.switchTab、uni.navigateBack
// 并且对相应的方法做重写
const original = uni[type];
uni[type] = function (options = {}) {
const { url: path } = options;
if (!whiteList.includes(path) && !uni.getStorageSync("token")) {
// 判断是否存在token,不存在重定向到登录页
uni.$e.route("/pages/login/index");
} else {
return original.call(this, options);
}
};
});
}; const install = function (Vue, options) {
uni.$e = { route };
Vue.prototype.route = route;
// 重写uni方法
handleOverwirteRoute();
// 路由拦截器
uni.$e.routeIntercept = (routeConfig, resolve) => {
const path = routeConfig.url.split("?")[0];
if (!whiteList.includes(path) && !uni.getStorageSync("token")) {
uni.$e.route("/pages/login/index");
return;
}
resolve(true);
};
};
export default {
install,
};
补充
在系统第一进入的时候,是不会触发拦截事件的,需要在App.js的onLanch去做进一步的实现。
onLaunch: function () {
if (!uni.getStorageSync("token")) {
uni.navigateTo({ url: "/pages/login/index" });
}
},

本文转载于:
https://juejin.cn/post/7119274924149047327
如果对您有所帮助,欢迎您点个关注,我会定时更新技术文档,大家一起讨论学习,一起进步。

记录--有关uni-app如何实现路由拦截的知识分享的更多相关文章
- Vue 路由拦截(对某些页面需要登陆才能访问)
前言 做项目的时候有个需求,就是发现没有登录,竟然也可以进入我的主页,这样肯定是不能容忍的.于是就要让他进入主页的时候,加个判断是否有登录,若没有登录,则返回登录界面,登录成功后还可以跳转到之前进入的 ...
- vue 路由拦截、axios请求拦截
路由拦截 项目中,有些页面需要登录后才能进入,例如,在某页面A,用户在操作前需要先进入登录页(此时需要将上一页的地址(/survey/start)作为query存入login页面的地址中,如: htt ...
- 一、数据库表中字段的增删改查,二、路由基础.三、有名无名分组.四、多app共存的路由分配.五、多app共存时模板冲突问题.六、创建app流程.七、路由分发.八、路由别名,九、名称空间.十、反向解析.十一、2.x新特性.十二、自定义转换器
一.数据库表中字段的增删改查 ''' 直接在modules中对字段进行增删改查 然后在tools下点击Run manage.py Task执行makemigrations和migrate 注意在执行字 ...
- vue中怎样实现 路由拦截器
vue中怎样实现 路由拦截器(当用户没有登录的时候,跳转到登录页面,已经登录的时候,不能跳转到登录页,除非后台token失效) 在 我们需要实现这样 一个功能,登录拦截 其实就是 路由拦截,首先在定义 ...
- Vue+axios 实现http拦截及路由拦截
现如今,每个前端对于Vue都不会陌生,Vue框架是如今最流行的前端框架之一,其势头直追react.最近我用vue做了一个项目,下面便是我从中取得的一点收获. 基于现在用vue+webpack搭建项目的 ...
- vue+axios 前端实现登录拦截(路由拦截、http拦截)
一.路由拦截 登录拦截逻辑 第一步:路由拦截 首先在定义路由的时候就需要多添加一个自定义字段requireAuth,用于判断该路由的访问是否需要登录.如果用户已经登录,则顺利进入路由, 否则就进入登录 ...
- vue+axios完美实现前端路由拦截
一.路由拦截 1.首先在router的index.js里配置一个自定义字段requireAuth,用该字段来判断进入该路由是否需要登录.如果已经登陆则进入该路由,反之则进入登录页面. 如图是路由配置: ...
- 【转】vue+axios 前端实现登录拦截(路由拦截、http拦截)
一.路由拦截 登录拦截逻辑 第一步:路由拦截 首先在定义路由的时候就需要多添加一个自定义字段requireAuth,用于判断该路由的访问是否需要登录.如果用户已经登录,则顺利进入路由, 否则就进入登录 ...
- vue-router做路由拦截时陷入死循环
今天分享一下使用vue-router做路由拦截时遇到的坑. 需要提前了解的api 1:router.beforeEach( to , from ,next) ; to: Route: 即将要进入的目标 ...
- vue中路由拦截无限循环的情况
router.beforeEach(async (to, from, next) => { if (token) { if (whiteList.indexOf(to.path) != -1) ...
随机推荐
- js 手动实现bind方法,超详细思路分析!
壹 ❀ 引 在 js 实现call和apply方法 一文中,我们详细分析并模拟实现了call/apply方法,由于篇幅问题,关于bind方法实现只能另起一篇. 在模拟bind之前,我们先了解bind的 ...
- InnoDB中不同SQL语句设置的锁
锁定读(locking read).更新(UPDATE)或删除(DELETE)通常会在SQL语句处理过程中扫描的每个索引记录上设置记录锁.语句中是否存在排除行的WHERE条件并不重要.InnoDB不记 ...
- Sora文生视频模型深度剖析:全网独家指南,洞悉98%关键信息,纯干货
Sora文生视频模型深度剖析:全网独家指南,洞悉98%关键信息,纯干货 Sora是一个以视频生成为核心的多能力模型,具备以下能力: 文/图生成视频 视频生成视频 1分钟超长高质量视频生成 视频裂变多视 ...
- 【Unity3D】基于模板测试和顶点膨胀的描边方法
1 前言 选中物体描边特效 中介绍了基于模板纹理模糊膨胀的描边方法,该方法实现了软描边,效果较好,但是为了得到模糊纹理,对屏幕像素进行了多次渲染,效率欠佳.本文将介绍另一种描边方法:基于模板测试和 ...
- 基于Android的“哲学家就餐问题”防死锁的设计与实现
1问题描述 如图1.1,有5个哲学家围着一个大圆桌就餐.哲学家和筷子都按逆时针顺序编号,每个哲学家的编号与他左边的筷子的编号一致. 规定如下: (1)哲学家之间不相互交流: (2)哲学家只做两件事:吃 ...
- windows 程序启动后挂到后台
开发中遇到一个问题,程序启动后,并没有出现在前台,而是自动挂到后台,程序处于卡死状态,基本任何模块都没加载进来. 后面排查发现跟一个功能的第三方 dll 有关系,在那个 dll 加载时导致程序卡死,因 ...
- Hdf5开发笔记(一):hdf5介绍,在windows上编译msvc2015x64版本
前言 matlab的matio库需要使用到hdf5,编译hdf5的msvc2015x64版本. HDF5介绍 HDF(Hierarchical Data Format)是一种设计用于存储和组织 ...
- tempfile创建临时文件或目录
import tempfile tempfile.TemporaryFile() # 创建文件,返回文件对象 tempfile.NamedTemporaryFile() # 同上,不过会生成带有文件名 ...
- chrony同步时间
chrony文件组成 包:chrony 两个主要程序:chronyd和chronyc - chronyd:后台运行的守护进程,用于调整内核中运行的系统时钟和时钟服务同步.它确定计算机增减时间的比率,并 ...
- java+文件实现的超市管理系统
一.需求 1.使用java语言实现系统 2.数据存储使用io读写文件 3.超市的商品管理+销售功能 二.效果 商品列表 商品增删改查 购买 三.说明 1.开发工具/技术 java eclipse 2 ...