Instantly share code, notes, and snippets.
Last active
April 29, 2022 08:12
-
Save luo3house/abd0dce1aa110436b274bb61bc6fda2a to your computer and use it in GitHub Desktop.
小程序页面跳转库
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// promisifychannel 库 | |
import { | |
ChannelCreateOptions, | |
createChannel, | |
} from '../../utils/promisifychannel' | |
// 小程序可能没有带 btoa 方法,请自行寻找 base64 实现库 | |
import { btoa, atob } from '../../utils/base64' | |
// protocol types | |
export type NavigationCmd = { | |
/** 执行方法 */ | |
fn: string | |
/** 来源页,如果为空,则push,否则back */ | |
fromPageRoute?: string | |
/** 目标页 */ | |
targetPageRoute?: string | |
/** 数据编码方式, 默认 plain, jsonbase64 适用于json数据传输 */ | |
encoding?: 'plain' | 'jsonbase64' | |
/** 请求数据 */ | |
data: any | |
} | |
export type NavigationRsp = { | |
/** 来源页 */ | |
fromPageRoute?: NavigationCmd['fromPageRoute'] | |
/** 目标页 */ | |
targetPageRoute?: NavigationCmd['targetPageRoute'] | |
/** 数据编码方式, 默认 plain, jsonbase64 适用于json数据传输 */ | |
encoding?: NavigationCmd['encoding'] | |
/** 响应数据 */ | |
data: any | |
} | |
export type NavigationRaw = { | |
/** 调用过程标识 */ | |
key: string | |
/** 数据类型 */ | |
type: 'cmd' | 'rsp' | |
/** 来源页 */ | |
fromPageRoute?: NavigationCmd['fromPageRoute'] | |
/** 目标页 */ | |
targetPageRoute?: NavigationCmd['targetPageRoute'] | |
/** 数据编码方式, 默认 plain, jsonbase64 适用于json数据传输 */ | |
encoding?: NavigationCmd['encoding'] | |
/** 执行方法,如果这个 raw 表示响应,则 fn 为空 */ | |
fn?: string | |
/** 数据 */ | |
data: any | |
} | |
// utils types | |
export type NavigationChannel = ReturnType<typeof createNavigationChannel> | |
export type NavigationChannelCreateOption = Pick< | |
ChannelCreateOptions<NavigationRaw, NavigationCmd, NavigationRsp, any>, | |
'handleCmdFunc' | 'onHandleError' | |
> & { | |
// 如何获取某个页面的 Channel?可以返回 null 表示该页面没有配置 Channel | |
getNavigationChannelFromPage: ( | |
wxPageObject: WechatMiniprogram.Page.Instance<any, any> | |
) => NavigationChannel | null | |
// 该页面的 route,开头带不带斜杠都可,会自动带上 | |
currentPageRoute: string | |
} | |
// navigation encoding | |
const Encoding = { | |
decodejsonbase64(data, defaults = {}) { | |
let res = defaults | |
try { | |
res = JSON.parse(atob(data)) | |
} catch (e) { | |
// ign | |
} | |
return res | |
}, | |
encodejsonbase64(data) { | |
return `${btoa(JSON.stringify(data))}`.replace(/=+$/g, '') | |
}, | |
autoEncode(data: any, typ: NavigationCmd['encoding'] = 'plain') { | |
switch (typ) { | |
case 'plain': | |
case undefined: | |
case null: | |
return data | |
case 'jsonbase64': | |
return this.encodejsonbase64(data) | |
} | |
}, | |
autoDecode(data: any, typ: NavigationCmd['encoding'] = 'plain') { | |
switch (typ) { | |
case 'plain': | |
case undefined: | |
case null: | |
return data | |
case 'jsonbase64': | |
return this.decodejsonbase64(data) | |
} | |
}, | |
} | |
// channel encoding | |
const ChannelEncoding: Pick< | |
ChannelCreateOptions<NavigationRaw, NavigationCmd, NavigationRsp, any>, | |
| 'decodeCmd' | |
| 'encodeCmd' | |
| 'decodeRsp' | |
| 'encodeRsp' | |
| 'encodeEvent' | |
| 'decodeEvent' | |
| 'probe' | |
> = { | |
encodeCmd: (key, cmd) => | |
new Promise((resolve) => { | |
resolve({ | |
key, | |
type: 'cmd', | |
fromPageRoute: cmd.fromPageRoute, | |
targetPageRoute: cmd.targetPageRoute, | |
encoding: cmd.encoding, | |
fn: cmd.fn, | |
data: Encoding.autoEncode(cmd.data, cmd.encoding), | |
}) | |
}), | |
decodeCmd: (cmdRaw) => | |
new Promise((resolve) => | |
resolve({ | |
key: cmdRaw.key, | |
cmd: { | |
fn: cmdRaw.fn ?? '', | |
fromPageRoute: cmdRaw.fromPageRoute, | |
targetPageRoute: cmdRaw.targetPageRoute, | |
encoding: cmdRaw.encoding, | |
data: Encoding.autoDecode(cmdRaw.data, cmdRaw.encoding), | |
}, | |
}) | |
), | |
encodeRsp: (key, rsp) => | |
new Promise((resolve) => { | |
resolve({ | |
key, | |
type: 'rsp', | |
fromPageRoute: rsp.fromPageRoute, | |
targetPageRoute: rsp.targetPageRoute, | |
encoding: rsp.encoding, | |
data: Encoding.autoEncode(rsp.data, rsp.encoding), | |
}) | |
}), | |
decodeRsp: (rspRaw) => | |
new Promise((resolve) => { | |
resolve({ | |
key: rspRaw.key, | |
rsp: { | |
fromPageRoute: rspRaw.fromPageRoute, | |
targetPageRoute: rspRaw.targetPageRoute, | |
encoding: rspRaw.encoding, | |
data: Encoding.autoDecode(rspRaw.data, rspRaw.encoding), | |
}, | |
}) | |
}), | |
probe: (raw) => | |
new Promise((resolve) => { | |
resolve({ | |
type: raw.type, | |
preDecoded: null, | |
}) | |
}), | |
encodeEvent: () => new Promise((_, reject) => reject('not support')), | |
decodeEvent: () => new Promise((_, reject) => reject('not support')), | |
} | |
// wechat page utils | |
module WxPageUtils { | |
export function buildWxQueryString(kv: Record<string, any>) { | |
const pairs: string[] = [] | |
for (let k in kv) { | |
const v = kv[k] | |
if (v) pairs.push(`${k}=${v}`) | |
} | |
return pairs.join('&') | |
} | |
/** 页面前缀自动加上斜杠 */ | |
export function escapeWxPageRoute(pageRoute: string) { | |
return pageRoute.replace(/^\/*/g, '/') | |
} | |
/** 页面前缀自动去掉斜杠 */ | |
export function unescapeWxPageRoute(pageRoute: string) { | |
return pageRoute.replace(/^\/+/g, '') | |
} | |
} | |
function createNavigationChannel(options: NavigationChannelCreateOption) { | |
let sendRaw: | |
| ChannelCreateOptions< | |
NavigationRaw, | |
NavigationCmd, | |
NavigationRsp, | |
any | |
>['sendRaw'] | |
| null = null | |
const channel = createChannel< | |
NavigationRaw, | |
NavigationCmd, | |
NavigationRsp, | |
any | |
>({ | |
...options, | |
...ChannelEncoding, | |
onHandleError: (e) => { | |
console.warn( | |
`[${options.currentPageRoute}] navigate channel error: ${JSON.stringify( | |
e | |
)}` | |
) | |
}, | |
sendRaw: (raw, payload) => | |
new Promise((resolve, reject) => { | |
if (!sendRaw) { | |
resolve(0) | |
return | |
} | |
sendRaw(raw, payload).then(resolve).catch(reject) | |
}), | |
}) | |
sendRaw = (raw) => | |
new Promise((resolve) => { | |
const currentPageRoute = WxPageUtils.escapeWxPageRoute( | |
options.currentPageRoute | |
) | |
if (raw.fromPageRoute) { | |
raw.fromPageRoute = WxPageUtils.escapeWxPageRoute(raw.fromPageRoute) | |
} | |
if (raw.targetPageRoute) { | |
raw.targetPageRoute = WxPageUtils.escapeWxPageRoute(raw.targetPageRoute) | |
} | |
switch (raw.type) { | |
case 'cmd': { | |
if (!raw.targetPageRoute) { | |
// 没有目标, 自己处理 | |
channel.handleRaw(raw) | |
} else if (currentPageRoute === raw.targetPageRoute) { | |
// 目标 = 自己, 处理 | |
channel.handleRaw(raw) | |
} else if (!raw.fromPageRoute) { | |
// 没有来源, 自己处理 | |
channel.handleRaw(raw) | |
} else if (currentPageRoute === raw.fromPageRoute) { | |
// 来源 = 自己, 目标 <> 自己, 带参跳转 | |
wx.navigateTo({ | |
url: `${raw.targetPageRoute}?${WxPageUtils.buildWxQueryString( | |
raw | |
)}`, | |
}) | |
} else if (currentPageRoute !== raw.fromPageRoute) { | |
// 来源 <> 自己, 目标 <> 自己, 继续跳转 | |
wx.navigateTo({ | |
url: `${raw.targetPageRoute}?${WxPageUtils.buildWxQueryString( | |
raw | |
)}`, | |
}) | |
} | |
break | |
} | |
case 'rsp': { | |
if (!raw.fromPageRoute) { | |
// 没有来源,不发 | |
} else if (currentPageRoute === raw.fromPageRoute) { | |
// 来源 = 自己, 处理 | |
channel.handleRaw(raw) | |
} else if (currentPageRoute === raw.targetPageRoute) { | |
// 目标 = 自己, 来源 <> 自己, 带参后退 | |
let reachFrom = false | |
getCurrentPages() | |
.reverse() | |
.forEach((page) => { | |
if (reachFrom === true) return | |
const pageRoute = WxPageUtils.escapeWxPageRoute(page.route) | |
reachFrom = pageRoute === raw.fromPageRoute | |
const navigationChannel = | |
options.getNavigationChannelFromPage(page) | |
navigationChannel?.setRaw?.(raw) | |
}) | |
wx.navigateBack() | |
} else if (currentPageRoute !== raw.fromPageRoute) { | |
// 来源 <> 自己, 目标 <> 自己, 继续后退 | |
wx.navigateBack() | |
} | |
break | |
} | |
} | |
resolve(0) | |
}) | |
let transientRaw: NavigationRaw | null = null | |
const handleRaw = () => { | |
if (transientRaw === null) return | |
if (!transientRaw.type) transientRaw.type = 'cmd' | |
if (!transientRaw.key) transientRaw.key = '' | |
if (!transientRaw.data) transientRaw.data = '' | |
channel.handleRaw(transientRaw) | |
transientRaw = null | |
} | |
const handleOnLoad = (query: Record<string, any>) => { | |
transientRaw = { | |
...query, | |
type: query.cmd ?? 'cmd', | |
key: query.key ?? '', | |
data: query.data ?? '', | |
} | |
} | |
return { | |
/** 内部使用,设置回调数据 */ | |
setRaw: (raw) => (transientRaw = raw), | |
handleRaw, | |
handleOnShow: handleRaw, | |
handleOnLoad, | |
invoke: (cmd: NavigationCmd) => | |
channel.invoke({ | |
...cmd, | |
fromPageRoute: options.currentPageRoute, | |
}), | |
sendRsp: (key, rsp: NavigationRsp) => channel.sendRsp(key, rsp), | |
sendRspWithCmd: (key, cmd: NavigationCmd, rsp: NavigationRsp) => | |
channel.sendRsp(key, { | |
fromPageRoute: cmd.fromPageRoute, | |
targetPageRoute: cmd.targetPageRoute, | |
...rsp, | |
}), | |
} | |
} | |
/** | |
* 用法|注入: Page({ ...withNavigationChannel() }) | |
* 初始化 channel: this.initNavigationChannel({}) | |
* 获得 channel: this.getNavigationChannel() | |
* 处理 onShow: this.getNavigationChannel().handleOnShow() | |
* 处理 onLoad : this.getNavigationChannel().handleOnShow(query) | |
*/ | |
export function withNavigationChannel() { | |
return { | |
navigationChannel: createNavigationChannel({ | |
currentPageRoute: '', | |
getNavigationChannelFromPage: (page) => page.navigationChannel ?? null, | |
}), | |
getNavigationChannel() { | |
return this.navigationChannel | |
}, | |
initNavigationChannel(options: Partial<NavigationChannelCreateOption>) { | |
if (!options.currentPageRoute) { | |
// 尝试自己获取当前调用的页面路由 | |
const pages = getCurrentPages() | |
const currentPage = pages[pages.length - 1] | |
options.currentPageRoute = currentPage.route | |
} | |
this.navigationChannel = createNavigationChannel({ | |
...options, | |
currentPageRoute: options.currentPageRoute ?? '', | |
getNavigationChannelFromPage: (page) => page.navigationChannel ?? null, | |
}) | |
return this.navigationChannel | |
}, | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment