这里给大家分享我在网上总结出来的一些知识,希望对大家有所帮助

前言

随着业务的需求,项目需要支持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是可以的,但是如果做跨端,可能会有一些后患,接下来我们聊聊为什么不使用它的原因。

无法拦截switchTabnavigateBack

这个其实也不算是一个缺点,目前也没找到可以拦截这两个事件的路由插件,如果确实需要实现这两种跳转方式的拦截,也是可以实现的,可以使用下一种方式,对这两种方法进行暴力重写。

没有解决全部的跨端兼容问题

这个其实是我不选择它的主要原因,根据官方文档的说明,根据文档去配置和编写,基本上能解决所有端上的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 navigateToto对应uni.navigateToredirectredirectTo对应uni.redirectToswitchTabtab对应uni.switchTabreLaunch对应uni.reLaunchnavigateBackback对应uni.navigateBack
url String - false typenavigateToredirectToswitchTabreLaunch时为必填
delta Number 1 false typenavigateBack时用到,表示返回的页面数
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如何实现路由拦截的知识分享的更多相关文章

  1. Vue 路由拦截(对某些页面需要登陆才能访问)

    前言 做项目的时候有个需求,就是发现没有登录,竟然也可以进入我的主页,这样肯定是不能容忍的.于是就要让他进入主页的时候,加个判断是否有登录,若没有登录,则返回登录界面,登录成功后还可以跳转到之前进入的 ...

  2. vue 路由拦截、axios请求拦截

    路由拦截 项目中,有些页面需要登录后才能进入,例如,在某页面A,用户在操作前需要先进入登录页(此时需要将上一页的地址(/survey/start)作为query存入login页面的地址中,如: htt ...

  3. 一、数据库表中字段的增删改查,二、路由基础.三、有名无名分组.四、多app共存的路由分配.五、多app共存时模板冲突问题.六、创建app流程.七、路由分发.八、路由别名,九、名称空间.十、反向解析.十一、2.x新特性.十二、自定义转换器

    一.数据库表中字段的增删改查 ''' 直接在modules中对字段进行增删改查 然后在tools下点击Run manage.py Task执行makemigrations和migrate 注意在执行字 ...

  4. vue中怎样实现 路由拦截器

    vue中怎样实现 路由拦截器(当用户没有登录的时候,跳转到登录页面,已经登录的时候,不能跳转到登录页,除非后台token失效) 在 我们需要实现这样 一个功能,登录拦截 其实就是 路由拦截,首先在定义 ...

  5. Vue+axios 实现http拦截及路由拦截

    现如今,每个前端对于Vue都不会陌生,Vue框架是如今最流行的前端框架之一,其势头直追react.最近我用vue做了一个项目,下面便是我从中取得的一点收获. 基于现在用vue+webpack搭建项目的 ...

  6. vue+axios 前端实现登录拦截(路由拦截、http拦截)

    一.路由拦截 登录拦截逻辑 第一步:路由拦截 首先在定义路由的时候就需要多添加一个自定义字段requireAuth,用于判断该路由的访问是否需要登录.如果用户已经登录,则顺利进入路由, 否则就进入登录 ...

  7. vue+axios完美实现前端路由拦截

    一.路由拦截 1.首先在router的index.js里配置一个自定义字段requireAuth,用该字段来判断进入该路由是否需要登录.如果已经登陆则进入该路由,反之则进入登录页面. 如图是路由配置: ...

  8. 【转】vue+axios 前端实现登录拦截(路由拦截、http拦截)

    一.路由拦截 登录拦截逻辑 第一步:路由拦截 首先在定义路由的时候就需要多添加一个自定义字段requireAuth,用于判断该路由的访问是否需要登录.如果用户已经登录,则顺利进入路由, 否则就进入登录 ...

  9. vue-router做路由拦截时陷入死循环

    今天分享一下使用vue-router做路由拦截时遇到的坑. 需要提前了解的api 1:router.beforeEach( to , from ,next) ; to: Route: 即将要进入的目标 ...

  10. vue中路由拦截无限循环的情况

    router.beforeEach(async (to, from, next) => { if (token) { if (whiteList.indexOf(to.path) != -1) ...

随机推荐

  1. 开源.NetCore通用工具库Xmtool使用连载 - 散列算法篇

    [Github源码] <上一篇>详细介绍了Xmtool工具库中的加解密类库,今天我们继续为大家介绍其中的散列算法类库. 散列算法在某些特殊场景也可以当做加密方法使用:其特点是不可逆,同一内 ...

  2. ABC 305

    题目列表 前三题过水,第四题分类讨论两个端点之间的距离和所在位置是清醒或睡眠 即可. E 题意:一张图上有一些结点有保安,每个保安有不同的警戒度 \(h_i\),定义 一个结点是安全的 为这个结点可以 ...

  3. 介绍 ComPDFKit 转换 SDK 1.5.0

    介绍 ComPDFKit 转换 SDK 1.5.0 了解有关 ComPDFKit PDF SDK 的更多信息:https ://www.compdf.com/ ComPDFKit Conversion ...

  4. 【Unity3D】MonoBehaviour的生命周期

    1 前言 ​ Unity3D 中可以给每个游戏对象添加脚本,这些脚本必须继承 MonoBehaviour,用户可以根据需要重写 MonoBehaviour 的部分生命周期函数,这些生命周期函数由系统自 ...

  5. 基于keras的时域卷积网络(TCN)

    1 前言 时域卷积网络(Temporal Convolutional Network,TCN)属于卷积神经网络(CNN)家族,于2017年被提出,目前已在多项时间序列数据任务中击败循环神经网络(RNN ...

  6. spring boot与junit集成测试

    先创建一个REST接口 package com.laoxu.gamedog.controller; import org.springframework.web.bind.annotation.Req ...

  7. Android里使用AspectJ实现双击自定义注解

    创建注解 首先创建一个双击注解. import java.lang.annotation.ElementType; import java.lang.annotation.Retention; imp ...

  8. 亲测可行,Android Studio 查看源码出现 Source for ‘Android API xxx Platform’ not found 的解决方法

    亲测可行,Android Studio 查看源码出现 Source for 'Android API xxx Platform' not found 的解决方法 如标题中的问题,产生的原因就是 SDK ...

  9. 【Azure Function】Azure Function中使用 Java 8 的安全性问题

    问题描述 使用Azure Function, 环境是Linux的Java8.目前 Oracle Java JDK8,11,17 和 OpenJDK 8/11/17 都在存在漏洞受影响版本的范围内. O ...

  10. TLS数据包重组

    TLS解密 参考以下链接:Wireshark 解密 TLS报文_在线tls解密-CSDN博客 总结: 配置环境变量 wireshark首选项配置 TLS解密例子 Frame 2700 Frame 27 ...