This commit is contained in:
owenchen committed 2026-10-13 14:23:10 +08:00
1 parent 3c0d389fcd
commit 32f237c4bf
22 files changed
+3351 -24

No files matched your search

@@ -0,0 +1,108 @@
/**
* 管理端"选择页"回传协议
*
* 旧 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 };
}