将 access_token 纳入 scoped storage 迁移,yUserId 登录优先并独立持久化 token;轻应用/按钮管理默认改用 access_token 参数名,支持 hash 路由占位符替换与自动追加。
159 lines
4.6 KiB
TypeScript
159 lines
4.6 KiB
TypeScript
/**
|
|
* 任务工作区可配置跳转按钮(按钮管理)API 客户端 + React Query hooks。
|
|
*
|
|
* 后端:`/api/task-buttons`(见 app/gateway/routers/task_buttons.py)。
|
|
* 全局共享、无用户隔离;读对所有登录用户开放(工作区要用),写仅 admin(整体替换)。
|
|
* snake_case ⇄ camelCase 在此边界收敛。见 [[task-buttons]]。
|
|
*/
|
|
|
|
import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
|
|
|
|
import { apiFetch } from "@/core/api/fetch-client";
|
|
import { getBackendBaseURL } from "@/core/config";
|
|
import { OptType, writeYLog } from "@/core/log/y-log";
|
|
import type {
|
|
TaskBusiness,
|
|
TaskButton,
|
|
TaskButtonIdKind,
|
|
TaskButtonLinkType,
|
|
TaskButtonLoginParam,
|
|
TaskButtonOpenMode,
|
|
} from "@/core/auth/task-buttons";
|
|
|
|
const BASE = () => `${getBackendBaseURL()}/api/task-buttons`;
|
|
|
|
interface ServerLoginParam {
|
|
key: string;
|
|
source: string;
|
|
custom_value: string;
|
|
}
|
|
|
|
interface ServerButton {
|
|
id: string;
|
|
business: string;
|
|
label: string;
|
|
link_type: string;
|
|
target: string;
|
|
append_task_id: boolean;
|
|
id_param: string;
|
|
id_kind: string;
|
|
open_mode: string;
|
|
login_params: ServerLoginParam[];
|
|
append_auth: boolean;
|
|
auth_token_param: string;
|
|
auth_name_param: string;
|
|
enabled: boolean;
|
|
sort_order: number;
|
|
}
|
|
|
|
function toLoginParam(p: ServerLoginParam): TaskButtonLoginParam {
|
|
return {
|
|
key: p.key ?? "",
|
|
source: p.source || "access_token",
|
|
customValue: p.custom_value ?? "",
|
|
};
|
|
}
|
|
|
|
function toButton(s: ServerButton): TaskButton {
|
|
return {
|
|
id: s.id,
|
|
business: (s.business as TaskBusiness) ?? "rwfx",
|
|
label: s.label ?? "",
|
|
linkType: (s.link_type as TaskButtonLinkType) ?? "url",
|
|
target: s.target ?? "",
|
|
appendTaskId: s.append_task_id ?? true,
|
|
idParam: s.id_param || "id",
|
|
idKind: (s.id_kind as TaskButtonIdKind) ?? "task",
|
|
openMode: (s.open_mode as TaskButtonOpenMode) ?? "blank",
|
|
loginParams: (s.login_params ?? []).map(toLoginParam),
|
|
appendAuth: s.append_auth ?? false,
|
|
authTokenParam: s.auth_token_param || "token",
|
|
authNameParam: s.auth_name_param || "username",
|
|
enabled: s.enabled ?? true,
|
|
sortOrder: s.sort_order ?? 0,
|
|
};
|
|
}
|
|
|
|
function toBody(b: TaskButton): ServerButton {
|
|
return {
|
|
id: b.id,
|
|
business: b.business,
|
|
label: b.label,
|
|
link_type: b.linkType,
|
|
target: b.target,
|
|
append_task_id: b.appendTaskId,
|
|
id_param: b.idParam,
|
|
id_kind: b.idKind,
|
|
open_mode: b.openMode,
|
|
login_params: (b.loginParams ?? []).map((p) => ({
|
|
key: p.key,
|
|
source: p.source,
|
|
custom_value: p.customValue,
|
|
})),
|
|
append_auth: b.appendAuth,
|
|
auth_token_param: b.authTokenParam,
|
|
auth_name_param: b.authNameParam,
|
|
enabled: b.enabled,
|
|
sort_order: b.sortOrder,
|
|
};
|
|
}
|
|
|
|
async function readJson<T>(res: Response, ctx: string): Promise<T> {
|
|
if (!res.ok) {
|
|
const data = await res.json().catch(() => null as unknown);
|
|
const detail =
|
|
data && typeof data === "object" && "detail" in data
|
|
? String((data as { detail: unknown }).detail)
|
|
: `${ctx} failed (${res.status})`;
|
|
throw new Error(detail);
|
|
}
|
|
return (await res.json()) as T;
|
|
}
|
|
|
|
export async function listTaskButtons(): Promise<TaskButton[]> {
|
|
const res = await apiFetch(BASE(), { method: "GET" });
|
|
const data = await readJson<{ buttons: ServerButton[] }>(res, "list task buttons");
|
|
return (data.buttons ?? []).map(toButton);
|
|
}
|
|
|
|
export async function replaceTaskButtons(buttons: TaskButton[]): Promise<TaskButton[]> {
|
|
const res = await apiFetch(BASE(), {
|
|
method: "PUT",
|
|
headers: { "Content-Type": "application/json" },
|
|
body: JSON.stringify({ buttons: buttons.map(toBody) }),
|
|
});
|
|
const data = await readJson<{ buttons: ServerButton[] }>(res, "save task buttons");
|
|
const saved = (data.buttons ?? []).map(toButton);
|
|
writeYLog({
|
|
optType: OptType.edit,
|
|
optDetail: "CM助手-全量-按钮管理-保存",
|
|
url: "/api/task-buttons",
|
|
reqParam: { buttons },
|
|
returnParam: saved,
|
|
});
|
|
return saved;
|
|
}
|
|
|
|
// ── React Query hooks(工作区 + 管理页共用缓存) ────────────────────────────
|
|
|
|
const TASK_BUTTONS_KEY = "task-buttons";
|
|
|
|
export function useTaskButtons() {
|
|
return useQuery({
|
|
queryKey: [TASK_BUTTONS_KEY],
|
|
queryFn: listTaskButtons,
|
|
refetchOnWindowFocus: false,
|
|
staleTime: 60_000,
|
|
});
|
|
}
|
|
|
|
export function useTaskButtonActions() {
|
|
const queryClient = useQueryClient();
|
|
return {
|
|
save: useMutation({
|
|
mutationFn: replaceTaskButtons,
|
|
onSuccess: () => queryClient.invalidateQueries({ queryKey: [TASK_BUTTONS_KEY] }),
|
|
}),
|
|
};
|
|
}
|