class Router { constructor(arg) { if (arg && arg.constructor !== Object) { return console.error(`Routing configuration must be an Object`) } Router.$root = this; } // 路由跳转方法映射 routeMap = { push: 'navigateTo', replace: 'redirectTo', replaceAll: 'reLaunch', pushTab: 'switchTab' } /** * 执行路由跳转 */ _pushTo() { return new Promise((resolve, reject) => { let { page, params, method } = this.tempRoute // 对首次进入页面执行路由守卫时如果放行,method page params都为空 此时应该直接中断流程,无需抛出异常 if (!method && !page && !params) { return } let urlParams = '?' if (!page) { reject(new Error('参数page未填写')) return } else if (params && typeof(params) === 'object') { // 处理参数,转换为url字符串 Object.keys(params).forEach(k => { // 深度对象转为json字符串(包含数组) if (typeof(params[k]) === 'object') { if (params[k]) { const json = JSON.stringify(params[k]) urlParams += `${k}=${json}&` } else { urlParams += `${k}=&` } } else if (typeof(params[k]) === 'number' || typeof(params[k]) === 'string' || typeof(params[k]) === 'boolean') { // 基础值直接写入 urlParams += `${k}=${params[k]}&` } else if (typeof(params[k]) === 'undefined') { urlParams += `${k}=&` } }) } // 参数组装 if (urlParams.length === 1) { urlParams = '' } else { urlParams = urlParams.substr(0, urlParams.length - 1) } // 设置路由跳转方式 if (!method) { method = 'navigateTo' } if (this.routeMap[method]) { method = this.routeMap[method] } // 调用系统跳转方法 uni[method]({ url: page + urlParams, success: () => { // 执行路由后置守卫 if (this._afterEach && typeof(this._afterEach) === 'function') { this._afterEach.call(this, this.tempRoute, this.route) } // 更新路由信息 this.route = { path: page + urlParams, params: params || {}, page } this.tempRoute = null resolve() }, fail: (e) => { reject(new Error('路由跳转失败!')) } }) }) } /**动态的导航到一个新 URL 保留浏览历史 * navigateTo * @param {Object} rule */ push(arg) { const rule = { method: 'navigateTo' } if (typeof(arg) === 'string') { rule.page = arg } else if (typeof(arg) === 'object') { rule.page = arg.page rule.params = arg.params } this.next(rule) } /**动态的导航到一个新 URL 关闭当前页面,跳转到的某个页面。 * redirectTo * @param {Object} rule */ replace(arg) { const rule = { method: 'redirectTo' } if (typeof(arg) === 'string') { rule.page = arg } else if (typeof(arg) === 'object') { rule.page = arg.page rule.params = arg.params } this.next(rule) } /**动态的导航到一个新 URL 关闭所有页面,打开到应用内的某个页面 * reLaunch * @param {Object} rule */ replaceAll(arg) { const rule = { method: 'reLaunch' } if (typeof(arg) === 'string') { rule.page = arg } else if (typeof(arg) === 'object') { rule.page = arg.page rule.params = arg.params } this.next(rule) } /** 跳转Tabbar * switchTab * @param {Object} rule */ pushTab(arg) { const rule = { method: 'switchTab' } if (typeof(arg) === 'string') { rule.page = arg } else if (typeof(arg) === 'object') { rule.page = arg.page rule.params = arg.params } this.next(rule) } /** * 返回到指定层级页面上 */ back(delta = 1) { // 返回上级 if (delta.constructor != Number) { this._errorHandler(new Error('返回层级参数必须是一个Number类型且必须大于0:')) return } uni.navigateBack({ delta }) } /** * 分发路由 * @param {Object} args */ _next() { return new Promise((resolve, reject) => { if (this._beforeEach && typeof(this._beforeEach) === 'function') { // 需要传给守卫 to from next this._beforeEach.call(this, this.tempRoute, this.route, resolve) } else { this._pushTo().catch(e => { reject(e) }) } }) } next(args) { if (args) { // 保存临时数据 if (typeof(args) === 'object') { this.tempRoute = { // 第一次调用next一定存在method,后续循环调用可能不会存在,不存在时使用上次缓存的method method: args.method || this.tempRoute.method, page: args.page, params: args.params } } else if (typeof(args) === 'string') { this.tempRoute = { page: args } } else if (!args) { // 中断路由 args = false this.tempRoute = null return } if (!this.route) { this.route = { page: '/' + getCurrentPages()[0].route } } this._next().then(args => { this.next(args) }).catch(e => { this.tempRoute = null this._errorHandler(e) }) } else { this._pushTo().catch(e => { this.tempRoute = null this._errorHandler(e) }) } } /** * 应用启动时执行一次路由检查(前置守卫,若通过则不做事情) */ doBeforeHooks() { this.tempRoute = {} this.next({}) } // 设置路由前置/后置守卫 beforeEach(fn) { this._beforeEach = fn } afterEach(fn) { this._afterEach = fn } // 设置路由跳转错误处理 onError(fn) { if (fn && typeof(fn) === 'function') { this._errorHandler = fn } } // 获取当前路由信息 getCurrentRoute() { return this.route } } // 路由对象属性定义 Router.$root = null // 当前路由内容 Router.route = null // 临时路由信息 Router.tempRoute = null // 路由前置后置守卫 Router._beforeEach = null Router._afterEach = null // 路由跳转错误处理 Router._errorHandler = function(e) { console.error(e) } Router.install = function(Vue) { Vue.mixin({ onLaunch: function() {}, onLoad: function(props) { // 首次进入页面时,缓存中不存在当前路由信息,需要初始化路由信息 if (!Router.$root.getCurrentRoute()) { const rt = { params: {}, page: '/' + getCurrentPages()[0].route } if (props) { Object.keys(props).forEach(k => { // url转的对象全部都是字符串,需要识别其中的对象和基本数据类型 try { const obj = JSON.parse(props[k]) if (typeof(obj) === 'string') { // 只有字符串还会是字符串,数字、布尔、数组均会转换为正常类型 rt.params[k] = props[k] } else { rt.params[k] = obj } } catch (e) { rt.params[k] = props[k] } }) } Router.$root.route = rt // 执行路由前置守卫 Router.$root.doBeforeHooks() } // 自动获取页面标题(app端可能获取不到) const pages = getCurrentPages() let pageTitle = pages[pages.length - 1].pageTitle Router.$root.route.pageTitle = pageTitle }, onShow() { if (!getCurrentPages().length) { return } // 获取当前路由信息 const pages = getCurrentPages() const page = pages[pages.length - 1] let pageTitle = page.pageTitle const rt = { params: {}, page: '/' + page.route, } if (!pageTitle) { // #ifdef H5 rt.pageTitle = document.title // #endif } else { rt.pageTitle = pageTitle } const route = Router.$root.route // 若当前页面地址不等于缓存中地址,则更新缓存路由信息 if (!route || route.page !== rt.page) { Router.$root.route = rt } } }) Object.defineProperty(Vue.prototype, "$Router", { get: function() { return Router.$root } }) Object.defineProperty(Vue.prototype, "$Route", { get: function() { return Router.$root.getCurrentRoute() } }) } export default Router