import ageis from '@utils/aegis'; import { AegisEnum } from '@config/emun'; import { RouteQuery, RouteRecord, RouteRecordName, RouteRecordRaw, RouteNavigateOption, RouteNavigateBackOption, RouteNavigatCallbackResult, Router, CurrentRouteNavigate, BaseRouteNavigateOption, } from './types'; export type RouterOptions = { routes: RouteRecordRaw[]; }; /** * 小程序路由类型 */ export enum RouteType { /** * 小程序路由 navigateTo 类型 */ NAVIGATE_TO = 'navigateTo', /** * 小程序路由 switchTab 类型 */ SWITCH_TAB = 'switchTab', /** * 小程序路由 redirectTo 类型 */ REDIRECT_TO = 'redirectTo', /** * 小程序路由 reLaunch 类型 */ RELAUNCH = 'reLaunch', /** * 小程序路由 navigateBack 类型 */ NAVIGATE_BACK = 'navigateBack', } // eslint-disable-next-line prefer-destructuring export const assign = Object.assign; /** * Parse URL query parameters * * @param obj query * @returns */ export function serializeQuery(obj: RouteQuery = {}) { return Object.keys(obj) .map(key => `${encodeURIComponent(key)}=${encodeURIComponent(obj[key])}`) .join('&'); } export function asNavigateObject(to: RouteNavigateOption | string) { const type = RouteType.NAVIGATE_TO; return typeof to === 'string' ? { url: to, type } : { url: '', type, ...to }; } /** * Creates a Router Matcher. * * @param routes - array of initial routes */ export function createRouterMatcher(routes: RouteRecordRaw[]) { const matcherMap = new Map>(); routes.forEach((route) => { if (route?.root && Array.isArray(route?.pages)) { route?.pages.forEach((subRoute) => { subRoute.name && matcherMap.set(subRoute.name, route); }); } else { route.name && matcherMap.set(route.name, route); } }); return matcherMap; } /** * Creates a Router instance that can be used by a WechatMiniprogram app. * * @param options - {@link RouterOptions} */ export function createRouter(options: RouterOptions): Router { const { routes } = options; const matcher = createRouterMatcher(routes); function findPageInHistory(url: string) { const pageStack = getCurrentPages(); let delta = -1; for (let i = 0; i < pageStack.length; i++) { const reg = /^\//; const { route } = pageStack[i]; if (route && url && route.replace(reg, '') === url.replace(reg, '')) { delta = i + 1; // 目标页在栈中的位置 break; } } return delta; } function getNavigateUrl(navigate: BaseRouteNavigateOption): string { if (navigate.name && !navigate.url) { const url = matcher.get(navigate.name)?.url; if (!!url) return url; throw new Error('page route is not found'); } if (!navigate?.url) { throw new Error('page route is not found'); } return navigate.url as string; } function hasTabBarNavigate(navigate: BaseRouteNavigateOption): boolean { let isTabBar = false; if (navigate?.name) { isTabBar = !!matcher.get(navigate.name)?.tabBar; } if (navigate?.url) { matcher.forEach((item) => { const url = navigate?.url?.split('?')[0]; if (item.url === url) { isTabBar = !!item?.tabBar; } }); return isTabBar; } return isTabBar; } function getCurrentRoute(): CurrentRouteNavigate { const pages = getCurrentPages(); const currPage = pages.length ? pages[pages.length - 1] : null; let currentRoute = {}; if (currPage) { const { route, options } = currPage; matcher.forEach((item) => { const query = serializeQuery(options); const fullPath = query ? `${item?.url}?${query}` : `${item?.url}`; if (item?.url === `/${route}`) { currentRoute = { ...item, route, fullPath, query: options, }; } }); } return currentRoute; } async function go(to: RouteNavigateOption | string): Promise { try { const navigate = asNavigateObject(to); const url = getNavigateUrl(navigate); const query = serializeQuery(navigate?.query); const maxDeep = 10; // 页面栈最大深度 const pageStack = getCurrentPages(); navigate.url = url.indexOf('?') >= 0 ? `${url}${query}` : `${url}?${query}`; // 页面栈已达上限 if (pageStack.length >= maxDeep) { const curDelta = findPageInHistory(navigate.url); // 当前页面:在页面栈中 if (curDelta > -1) return await back({ delta: pageStack.length - curDelta, data: navigate?.query, }); // 当前页面:不在页面栈中 return wx.redirectTo(navigate); } // tabBar路由 or switchTab 类型 if (hasTabBarNavigate(navigate) || navigate?.type === RouteType.SWITCH_TAB) { const navigateTab = assign(navigate, { url: navigate.url.split('?')[0], // wx.switchTab: url 不支持 queryString }); return await wx.switchTab(navigateTab); } if (navigate?.type === RouteType.REDIRECT_TO) { return await wx.redirectTo(navigate); } if (navigate?.type === RouteType.RELAUNCH) { return await wx.reLaunch(navigate); } return await wx.navigateTo(navigate); } catch (error) { ageis.infoAll(AegisEnum.ROUTE_ERROR_KEY, error.errMsg); throw error; } } async function push(to: RouteNavigateOption | string) { return await go(assign(to, { type: RouteType.NAVIGATE_TO })); } async function tab(to: RouteNavigateOption | string) { return await go(assign(to, { type: RouteType.SWITCH_TAB })); } async function replace(to: RouteNavigateOption | string) { return await go(assign(to, { type: RouteType.REDIRECT_TO })); } async function relaunch(to: RouteNavigateOption | string) { return await go(assign(to, { type: RouteType.RELAUNCH })); } async function back(to: RouteNavigateBackOption | number) { const pageStack = getCurrentPages(); const { delta = 1, data = {} } = typeof to === 'number' ? { delta: to } : to; // 设置上一页面的数据 if (Object.keys(data).length > 0) { const backPage = pageStack[pageStack.length - 1 - delta]; backPage?.setData(data); } return await wx.navigateBack({ delta }); } return { routes, matcher, getCurrentRoute, go, push, tab, replace, relaunch, back, }; }