feat: 任务工作区可配置跳转按钮(按钮管理) + 底部快捷跳转栏 + 目的树收起/空自动问答
- 新增「按钮管理」admin 页(/admin/task-buttons):按业务(rwfx/3qfx/xdfx)配置跳转按钮, 支持改名称/类型(外链·切换业务·目的树弹框)/目标/追加参数名(id·task_id)/任务id·行动id/启停/排序 - 跳转按钮从左侧栏移到工作区底部「快捷跳转」栏:红底白字、靠右、变大;rwfx 历史 4 按钮纳入配置 - 后端新增全局共享 store /api/task-buttons (table task_buttons,admin 整体替换) - xdfx 行动id/任务id:登录暂存原始行动id,按钮可选追加任一;id 参数名可自定义 - rwfx 清除记录移到「新建对话」下、去掉任务操作框;查询任务目的为空时自动发第一次问答 - 目的树面板默认收起;「弹框查看」抽成可复用 TaskPurposeDialog,可作为按钮配置到任意页面 - 文档(根/后端 CLAUDE.md) + 后端测试 tests/test_task_buttons.py Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
1 parent
ecb79ce49c
commit
16afa9bcbb
22 files changed
+1341
-130
No files matched your search
@@ -0,0 +1,114 @@
|
||||
/**
|
||||
* 任务工作区可配置跳转按钮(按钮管理)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 type {
|
||||
TaskBusiness,
|
||||
TaskButton,
|
||||
TaskButtonIdKind,
|
||||
TaskButtonLinkType,
|
||||
} from "@/core/auth/task-buttons";
|
||||
|
||||
const BASE = () => `${getBackendBaseURL()}/api/task-buttons`;
|
||||
|
||||
interface ServerButton {
|
||||
id: string;
|
||||
business: string;
|
||||
label: string;
|
||||
link_type: string;
|
||||
target: string;
|
||||
append_task_id: boolean;
|
||||
id_param: string;
|
||||
id_kind: string;
|
||||
enabled: boolean;
|
||||
sort_order: number;
|
||||
}
|
||||
|
||||
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",
|
||||
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,
|
||||
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");
|
||||
return (data.buttons ?? []).map(toButton);
|
||||
}
|
||||
|
||||
// ── 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] }),
|
||||
}),
|
||||
};
|
||||
}
|
||||
Reference in new issue
Block a user