/** * 任务深链(``goPath=rwfx``)的运行时配置:从后端公开只读接口 * ``GET /api/public/config/task-deeplink`` 读取(地址、测试开关、测试假数据全在后端 * ``config.yaml`` 的 ``task_deeplink`` 段,改完即时生效,前端无需重建)。 * * 两种用法: * - 命令式(登录深链流程,非 React 环境):``fetchTaskDeeplinkConfig()`` 拉取并模块级缓存; * ``getCachedTaskDeeplinkConfig()`` 同步读缓存(拼跳转地址用)。 * - React 组件:``useTaskDeeplinkConfig()``(见 [[task-purpose]] 的面板/列表)。 * * 拉取失败 → 用内置默认值(``test=false`` + 真实 URL)兜底,功能不整体挂掉。 */ import { apiFetch } from "@/core/api/fetch-client"; import { getBackendBaseURL } from "@/core/config"; export interface TaskDeeplinkConfig { /** 目的树查询走假数据。 */ test: boolean; /** 删除接口走假数据(该接口未开发完时打开)。 */ deleteTest: boolean; /** 行动→任务解析走假数据(xdfx 行动id→任务id)。 */ actionTest: boolean; /** 目的树查询完整 URL(调用追加 ?taskId=)。 */ purposeDetailUrl: string; /** 删除完整 URL(调用追加 ?taskId=)。 */ deleteUrl: string; /** B 系统树形完整编辑页 URL(跳转追加 ?id=)。 */ treemapEditUrl: string; /** B 系统树形下一步页 URL(跳转追加 ?id=)。 */ treemapNextUrl: string; /** 行动详情接口基址(xdfx 行动→任务):调用追加 /{actionId},返回 data.taskId 为真正任务id。 */ actionDetailUrl: string; /** 「3q详情」详情页 URL(rwfx 目的树面板链接,跳转追加 ?id= 任务id);留空则前端提示未配置。 */ detail3qUrl: string; /** 测试假数据:purposeDetail = 原始 {state,msg,data};delete = {message,success}; * actionDetail = 原始 {state,msg,data:{taskId,...}}。 */ mock: { purposeDetail?: unknown; delete?: { message?: string; success?: boolean }; actionDetail?: unknown; }; } /** 内置默认值(后端拿不到时兜底;真实 URL + 关闭测试)。 */ export const DEFAULT_TASK_DEEPLINK_CONFIG: TaskDeeplinkConfig = { test: false, deleteTest: false, actionTest: false, purposeDetailUrl: "https://ch1.b.uat.4.cn/consumer/taskAnalyseSearch/getTaskPurposeDetail", deleteUrl: "https://ch1.b.uat.4.cn/consumer/taskAnalyseSearch/delete", treemapEditUrl: "https://ch1.b.uat.4.cn/web/#/task/item-aicoh/step-mode-treemap-full-edit", treemapNextUrl: "https://ch1.b.uat.4.cn/web/#/task/item-aicoh/step-mode-treemap-full-next", actionDetailUrl: "https://ch1.b.uat.4.cn/consumer/plan/action", detail3qUrl: "", mock: {}, }; /** 后端 snake_case 响应 → 前端 camelCase;缺字段回退默认值。 */ function normalize(body: unknown): TaskDeeplinkConfig { const o = body && typeof body === "object" ? (body as Record) : {}; const d = DEFAULT_TASK_DEEPLINK_CONFIG; const str = (v: unknown, fallback: string) => typeof v === "string" && v.trim() ? v : fallback; const mock = o.mock && typeof o.mock === "object" ? (o.mock as Record) : {}; return { test: o.test === true, deleteTest: o.delete_test === true, actionTest: o.action_test === true, purposeDetailUrl: str(o.purpose_detail_url, d.purposeDetailUrl), deleteUrl: str(o.delete_url, d.deleteUrl), treemapEditUrl: str(o.treemap_edit_url, d.treemapEditUrl), treemapNextUrl: str(o.treemap_next_url, d.treemapNextUrl), actionDetailUrl: str(o.action_detail_url, d.actionDetailUrl), // detail_3q_url 允许为空(地址待定),不回退默认;只在配了非空时才生效。 detail3qUrl: typeof o.detail_3q_url === "string" ? o.detail_3q_url : d.detail3qUrl, mock: { purposeDetail: mock.purpose_detail, delete: mock.delete && typeof mock.delete === "object" ? (mock.delete as { message?: string; success?: boolean }) : undefined, actionDetail: mock.action_detail, }, }; } let cached: TaskDeeplinkConfig | null = null; let inflight: Promise | null = null; /** 拉取(带模块级缓存 + 去重);失败回退默认值。 */ export async function fetchTaskDeeplinkConfig(): Promise { if (cached) return cached; if (inflight) return inflight; inflight = (async () => { try { const res = await apiFetch(`${getBackendBaseURL()}/api/public/config/task-deeplink`); if (!res.ok) throw new Error(`task-deeplink config ${res.status}`); cached = normalize(await res.json()); } catch { cached = { ...DEFAULT_TASK_DEEPLINK_CONFIG }; } finally { inflight = null; } return cached; })(); return inflight; } /** 同步读缓存(未拉取过则返回默认值)。 */ export function getCachedTaskDeeplinkConfig(): TaskDeeplinkConfig { return cached ?? DEFAULT_TASK_DEEPLINK_CONFIG; } /** 在完整 URL(可能含 `#` 片段)后追加查询参数,供跳转/调用拼 id/taskId。 */ export function appendQueryParam(url: string, key: string, value: string): string { if (!url) return ""; const sep = url.includes("?") ? "&" : "?"; return `${url}${sep}${key}=${encodeURIComponent(value)}`; }