/** * 管理端"选择页"回传协议 * * 旧 WebVue 的做法:Operation 页面里塞一个 ``,内嵌 Enquiry 组件, * 用 provide/inject(operationToEnquiryMethodKey + isModalUser/selectionTypeUser) * 双向通信,确定时读 `enquiry.value.keys`。 * 移动端不能照搬:屏幕容不下"表单 + 弹窗 + 表格"三层,而且 uni 的页面之间 * 没有 provide/inject(每个页面是独立实例)。所以改成"真的跳一个选择页, * 选完 emit + navigateBack",通道名由调用方给定。 * * 参数一律不做 encodeURIComponent:H5 侧 uni/vue-router 拼 URL 用的是 encodeURI, * 预编码会把 % 再编成 %25(这个坑在 nav-guard 的 ?from= 上踩过一次, * 见 src/md/page-routing.md)。因此 channel 与 params 的值只允许 * 字母、数字、下划线、连字符、点、斜杠这类安全字符。 */ import type { AdminRow } from "@/admin/admin-types"; import type { PageName } from "@/router/routes"; import { onUnmounted, ref } from "vue"; import { pageUrl } from "@/router/routes"; /** 事件名前缀:uni.$emit 是全局总线,加前缀避免与业务事件撞名 */ const EVENT_PREFIX = "admin:pick:"; /** 打开发起方要的东西:目标页 + 透传给它的 query */ export interface AdminPickRequest { page: PageName; /** 简单标量参数,如 { multiple: "1" };不允许放 JSON 串或含特殊字符的值 */ params?: Record; } function buildQuery(params: Record): string { const parts: string[] = []; for (const [key, value] of Object.entries(params)) { parts.push(`${key}=${value}`); } return parts.join("&"); } /** * 调用方侧:打开选择页并登记"选好了怎么办"。 * @param channel 通道名,同一页面的多个选择器用不同通道(如 actor、manager) */ export function useAdminPick(channel: string) { const eventName = `${EVENT_PREFIX}${channel}`; let handler: ((rows: T[]) => void) | null = null; function detach(): void { if (handler) { uni.$off(eventName, handler); handler = null; } } function open( request: AdminPickRequest, onPicked: (rows: T[]) => void, ): void { // 先退订再订阅:同一通道重复打开时若留着旧闭包,上一次的选择逻辑会被再执行一遍 detach(); handler = (rows: T[]) => { onPicked(rows ?? []); }; uni.$on(eventName, handler); const query = buildQuery({ channel, ...(request.params ?? {}) }); uni.navigateTo({ url: `${pageUrl(request.page)}?${query}` }); } onUnmounted(detach); return { channel, open, detach }; } /** * 选择页侧:onLoad 里把 channel 存下来,确定时回传。 * 没带 channel 参数时按 "default" 处理——手敲地址进来也不会报错, * 只是没人接收这次选择。 */ export function useAdminPickTarget() { const channel = ref("default"); const multiple = ref(false); /** 从 onLoad 的 options 里读协议参数 */ function readOptions(options?: Record): void { const raw = String(options?.channel ?? "").trim(); if (raw) { channel.value = raw; } multiple.value = String(options?.multiple ?? "") === "1"; } /** 回传并退回上一页 */ function deliver(rows: AdminRow[]): void { uni.$emit(`${EVENT_PREFIX}${channel.value}`, rows); uni.navigateBack(); } /** 单选语义:只认第一条 */ function deliverOne(row: AdminRow): void { deliver([row]); } function cancel(): void { uni.navigateBack(); } return { channel, multiple, readOptions, deliver, deliverOne, cancel }; }