Skip to content

Instantly share code, notes, and snippets.

@luo3house
Last active April 29, 2022 08:12
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save luo3house/abd0dce1aa110436b274bb61bc6fda2a to your computer and use it in GitHub Desktop.
Save luo3house/abd0dce1aa110436b274bb61bc6fda2a to your computer and use it in GitHub Desktop.
小程序页面跳转库
// 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