import {H5Config, InstantiateConfig} from '../options/config'; import {RoutesRule, routesMapRule, routesMapKeysRule, Router, totalNextRoute, objectAny, navErrorRule, NAVTYPE, navRoute, uniBackApiRule, uniBackRule} from '../options/base'; import {baseConfig} from '../helpers/config'; import {ERRORHOOK} from '../public/hooks' import {warnLock} from '../helpers/warn' import { createRoute, navjump } from '../public/methods'; const Regexp = require('path-to-regexp'); export function voidFun(...args:any):void{} export function def( defObject:objectAny, key:string, getValue:Function ) { Object.defineProperty(defObject, key, { get() { return getValue(); } }) } export function timeOut(time:number):Promise { return new Promise(resolve => { setTimeout(() => { resolve(); }, time) }) } export function mergeConfig(baseConfig: T, userConfig: T): T { const config: {[key: string]: any} = Object.create(null); const baseConfigKeys: Array = Object.keys(baseConfig).concat(['resolveQuery', 'parseQuery']); for (let i = 0; i < baseConfigKeys.length; i += 1) { const key = baseConfigKeys[i]; if (userConfig[key] != null) { if (userConfig[key].constructor === Object) { config[key] = { ...baseConfig[key], ...userConfig[key] }; } else if (key === 'routes') { config[key] = [ ...baseConfig[key], ...userConfig[key] ]; } else { config[key] = userConfig[key]; } } else { config[key] = baseConfig[key]; } } return config as T; } export function notDeepClearNull(object:T):T { for (const key in object) { if (object[key] == null) { delete object[key]; } } return object; } export function getRoutePath(route: RoutesRule, router:Router): { finallyPath: string | string[]; aliasPath: string; path: string; alias: string | string[] | undefined; } { let finallyPath = route.aliasPath || route.alias || route.path; if (router.options.platform !== 'h5') { finallyPath = route.path } return { finallyPath, aliasPath: route.aliasPath || route.path, path: route.path, alias: route.alias } } export function assertNewOptions( options: T ): T | never { const {platform, routes} = options; if (platform == null) { throw new Error(`你在实例化路由时必须传递 'platform'`); } if (routes == null || routes.length === 0) { throw new Error(`你在实例化路由时必须传递 routes 为空,这是无意义的。`); } if (options.platform === 'h5') { if (options.h5?.vueRouterDev) { baseConfig.routes = []; } } const mergeOptions = mergeConfig(baseConfig as T, options); return mergeOptions; } export function getWildcardRule( router:Router, msg?:navErrorRule ):RoutesRule|never { const routesMap = (router.routesMap as routesMapRule); const route = routesMap.finallyPathMap['*']; if (route) { // 有写通配符 return route } if (msg) { ERRORHOOK[0](msg, router); } throw new Error(`当前路由表匹配规则已全部匹配完成,未找到满足的匹配规则。你可以使用 '*' 通配符捕捉最后的异常`); } export function notRouteTo404( router:Router, toRoute:RoutesRule|{ redirect:any; path:string }, parseToRule:totalNextRoute, navType:NAVTYPE ):RoutesRule|totalNextRoute|never { if (toRoute.path !== '*') { // 不是通配符 正常匹配成功 return (toRoute as RoutesRule); } const redirect = toRoute.redirect; if (typeof redirect === 'undefined') { throw new Error(` * 通配符必须配合 redirect 使用。redirect: string | Location | Function`); } let newRoute = redirect; if (typeof newRoute === 'function') { newRoute = newRoute(parseToRule) as totalNextRoute; } const redirectRule = navjump(newRoute as totalNextRoute, router, navType, undefined, undefined, undefined, false); return (redirectRule as totalNextRoute); } export function routesForMapRoute( router: Router, path: string, mapArrayKey:Array, deepFind:boolean|undefined = false ):RoutesRule|never { if (router.options.h5?.vueRouterDev) { return {path} } // 【Fixe】 https://github.com/SilurianYang/uni-simple-router/issues/252 const startPath = path.split('?')[0]; let wildcard = ''; const routesMap = (router.routesMap as routesMapRule); for (let i = 0; i < mapArrayKey.length; i++) { const mapKey = mapArrayKey[i]; const mapList = routesMap[mapKey]; for (const [key, value] of Object.entries(mapList)) { if (key === '*') { if (wildcard === '') { wildcard = '*' } continue; } const route:string|RoutesRule = value; let rule:string = key; if (getDataType|objectAny>(mapList) === '[object Array]') { rule = (route as string); } const pathRule:RegExp = Regexp(rule); const result = pathRule.exec(startPath); if (result != null) { if (getDataType(route) === '[object String]') { return routesMap.finallyPathMap[(route as string)]; } return (route as RoutesRule); } } } // 【Fixe】 https://github.com/SilurianYang/uni-simple-router/issues/302 2021-8-4 16:38:44 if (deepFind) { return ({} as RoutesRule); } if (routesMap['aliasPathMap']) { const results = routesForMapRoute(router, path, ['aliasPathMap'], true); if (Object.keys(results).length > 0) { return results; } } if (wildcard !== '') { return getWildcardRule(router); } throw new Error(`${path} 路径无法在路由表中找到!检查跳转路径及路由表`); } export function getDataType(data:T):string { return Object.prototype.toString.call(data) } export function copyData(object:T): T { return JSON.parse(JSON.stringify(object)) } export function getUniCachePage(pageIndex?:number):T|[] { const pages:T = getCurrentPages(); if (pageIndex == null) { return pages } if (pages.length === 0) { return pages; } const page = pages.reverse()[pageIndex]; if (page == null) { return [] } return page; } export function urlToJson(url :string):{ path:string; query:objectAny } { const query:objectAny = {}; const [path, params] = url.split('?'); if (params != null) { const parr = params.split('&'); for (const i of parr) { const arr = i.split('='); query[arr[0]] = arr[1]; } } return { path, query } } export function forMatNextToFrom( router:Router, to:T, from:T ):{ matTo:T; matFrom: T; } { let [matTo, matFrom] = [to, from]; if (router.options.platform === 'h5') { const {vueNext, vueRouterDev} = (router.options.h5 as H5Config); if (!vueNext && !vueRouterDev) { matTo = createRoute(router, undefined, matTo) as T; matFrom = createRoute(router, undefined, matFrom) as T; } } else { matTo = createRoute(router, undefined, deepClone(matTo)) as T; matFrom = createRoute(router, undefined, deepClone(matFrom)) as T; } return { matTo: matTo, matFrom: matFrom } } export function paramsToQuery( router:Router, toRule:totalNextRoute|string ):totalNextRoute|string { if (router.options.platform === 'h5' && !router.options.h5?.paramsToQuery) { return toRule; } if (getDataType(toRule) === '[object Object]') { const {name, params, ...moreToRule} = (toRule as totalNextRoute); let paramsQuery = params; if (router.options.platform !== 'h5' && paramsQuery == null) { paramsQuery = {}; } if (name != null && paramsQuery != null) { let route = (router.routesMap as routesMapRule).nameMap[name]; if (route == null) { route = getWildcardRule(router, { type: 2, msg: `命名路由为:${name} 的路由,无法在路由表中找到!`, toRule}); } const {finallyPath} = getRoutePath(route, router); if (finallyPath.includes(':')) { // 动态路由无法使用 paramsToQuery ERRORHOOK[0]({ type: 2, msg: `动态路由:${finallyPath} 无法使用 paramsToQuery!`, toRule}, router); } else { return { ...moreToRule, path: finallyPath as string, query: paramsQuery } } } } return toRule } export function assertDeepObject(object:objectAny):boolean { let arrMark = null; try { arrMark = JSON.stringify(object).match(/\{|\[|\}|\]/g); } catch (error) { warnLock(`传递的参数解析对象失败。` + error) } if (arrMark == null) { return false } if (arrMark.length > 3) { return true; } return false } export function baseClone< T extends { [key:string]:any }, K extends keyof T >( source:T, target:Array|objectAny ):Array|objectAny|null { // 【Fixe】 https://github.com/SilurianYang/uni-simple-router/issues/292 // 小程序会将null解析为字符串 undefined 建议不要在参数中传递 null if (source == null) { target = source; } else { for (const key of Object.keys(source)) { const dyKey = key as T[K]; if (source[key] === source) continue if (typeof source[key] === 'object') { target[dyKey] = getDataType(source[key]) === '[object Array]' ? ([] as Array) : ({} as objectAny) target[dyKey] = baseClone(source[key], target[dyKey]) } else { target[dyKey] = source[key] } } } return target; } export function deepClone(source:T):T { const __ob__ = getDataType(source) === '[object Array]' ? ([] as Array) : ({} as objectAny); baseClone(source, __ob__) return __ob__ as T } export function lockDetectWarn( router:Router, to:string|number|totalNextRoute|navRoute, navType:NAVTYPE, next:Function, uniActualData:uniBackApiRule|uniBackRule|undefined = {}, passiveType?:'beforeHooks'| 'afterHooks' ):void{ if (passiveType === 'afterHooks') { next(); } else { const {detectBeforeLock} = router.options; detectBeforeLock && detectBeforeLock(router, to, navType); if (router.$lockStatus) { (router.options.routerErrorEach as (error: navErrorRule, router:Router) => void)({ type: 2, msg: '当前页面正在处于跳转状态,请稍后再进行跳转....', NAVTYPE: navType, uniActualData }, router); } else { next(); } } } export function assertParentChild( parentPath:string, vueVim:any, ):boolean { while (vueVim.$parent != null) { const mpPage = vueVim.$parent.$mp; if (mpPage.page && mpPage.page.is === parentPath) { return true; } vueVim = vueVim.$parent; } try { if (vueVim.$mp.page.is === parentPath || vueVim.$mp.page.route === parentPath) { return true } } catch (error) { return false } return false } export function resolveAbsolutePath( path:string, router:Router ):string|never { const reg = /^\/?([^\?\s]+)(\?.+)?$/; const trimPath = path.trim(); if (!reg.test(trimPath)) { throw new Error(`【${path}】 路径错误,请提供完整的路径(10001)。`); } const paramsArray = trimPath.match(reg); if (paramsArray == null) { throw new Error(`【${path}】 路径错误,请提供完整的路径(10002)。`); } const query:string = paramsArray[2] || ''; if (/^\.\/[^\.]+/.test(trimPath)) { // 当前路径下 const navPath:string = router.currentRoute.path + path; return navPath.replace(/[^\/]+\.\//, ''); } const relative = paramsArray[1].replace(/\//g, `\\/`).replace(/\.\./g, `[^\\/]+`).replace(/\./g, '\\.'); const relativeReg = new RegExp(`^\\/${relative}$`); const route = router.options.routes.filter(it => relativeReg.test(it.path)); if (route.length !== 1) { throw new Error(`【${path}】 路径错误,尝试转成绝对路径失败,请手动转成绝对路径(10003)。`); } return route[0].path + query; } export function deepDecodeQuery( query:objectAny ):objectAny { const formatQuery:objectAny = getDataType(query) === '[object Array]' ? [] : {}; const keys = Object.keys(query); for (let i = 0; i < keys.length; i++) { const key = keys[i]; const it = query[key]; if (typeof it === 'string') { try { let json = JSON.parse(decodeURIComponent(it)); if (typeof json !== 'object') { json = it; } formatQuery[key] = json; } catch (error) { try { formatQuery[key] = decodeURIComponent(it) } catch (error) { formatQuery[key] = it } } } else if (typeof it === 'object') { const childQuery = deepDecodeQuery(it); formatQuery[key] = childQuery } else { formatQuery[key] = it } } return formatQuery }