109 lines
3.6 KiB
TypeScript
109 lines
3.6 KiB
TypeScript
/**
|
||
* 管理端"选择页"回传协议
|
||
*
|
||
* 旧 WebVue 的做法:Operation 页面里塞一个 `<dragModal>`,内嵌 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<string, string>;
|
||
}
|
||
|
||
function buildQuery(params: Record<string, string>): 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<T extends AdminRow = AdminRow>(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<string, string | undefined>): 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 };
|
||
}
|