feat(xdfx): 行动历史查询与删除(与 rwfx 共用 dropSixOrSevenData 删除接口)

- 删除接口统一为 dropSixOrSevenData:rwfx 传 ?taskId=、xdfx 传 ?actionId=,另一参数直接不出现在 URL
- xdfx 工作区新增「清除记录」按钮(按行动id 删除),删除前预检「行动历史是否存在」
- action_history_url 配置位(地址待提供,留空则不查询/不阻断删除)

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
吉鑫升andClaude Opus 4.8 committed 2026-06-23 19:14:16 +08:00
1 parent 5aa66fc07d
commit a1fbfaf864
7 files changed
+182 -12

No files matched your search

@@ -23,8 +23,10 @@ export interface TaskDeeplinkConfig {
actionTest: boolean;
/** 目的树查询完整 URL(调用追加 ?taskId=)。 */
purposeDetailUrl: string;
/** 删除完整 URL(调用追加 ?taskId=)。 */
/** 删除完整 URL(rwfx 与 xdfx 共用同一接口):rwfx 追加 ?taskId=、xdfx 追加 ?actionId=(另一参数不传)。 */
deleteUrl: string;
/** xdfx「行动历史是否存在」查询完整 URL(调用追加 ?actionId=);留空 → 前端视作无历史、不阻断删除(地址待提供)。 */
actionHistoryUrl: string;
/** B 系统树形完整编辑页 URL(跳转追加 ?id=)。 */
treemapEditUrl: string;
/** B 系统树形下一步页 URL(跳转追加 ?id=)。 */
@@ -50,10 +52,12 @@ export const DEFAULT_TASK_DEEPLINK_CONFIG: TaskDeeplinkConfig = {
deleteTest: false,
actionTest: false,
purposeDetailUrl: "https://ch1.b.uat.4.cn/consumer/taskAnalyseSearch/getTaskPurposeDetail",
deleteUrl: "https://ch1.b.uat.4.cn/consumer/taskAnalyseSearch/delete",
deleteUrl: "https://ch1.b.uat.4.cn/bwjt/dropSixOrSevenData",
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",
// 行动历史查询地址待提供:空字符串 → 前端不查询、视作无历史(不阻断删除)。
actionHistoryUrl: "",
detail3qUrl: "",
mock: {},
};
@@ -74,6 +78,8 @@ function normalize(body: unknown): TaskDeeplinkConfig {
treemapEditUrl: str(o.treemap_edit_url, d.treemapEditUrl),
treemapNextUrl: str(o.treemap_next_url, d.treemapNextUrl),
actionDetailUrl: str(o.action_detail_url, d.actionDetailUrl),
// action_history_url 允许为空(地址待提供):空 → 前端不查询、视作无历史。不回退默认。
actionHistoryUrl: typeof o.action_history_url === "string" ? o.action_history_url : d.actionHistoryUrl,
// detail_3q_url 允许为空(地址待定),不回退默认;只在配了非空时才生效。
detail3qUrl: typeof o.detail_3q_url === "string" ? o.detail_3q_url : d.detail3qUrl,
mock: {
+50 -5
View File
@@ -254,20 +254,35 @@ export interface DeleteTaskPurposeResult {
}
/**
* 删除任务目的树(覆盖前清空 / 手动清除任务记录都走这里)。``delete_test=true`` 时
* 用后端配置里的假数据(接口未开发完时用);否则打真实接口。
* 删除记录的「按哪种 id 删」:
* - ``task``(默认,rwfx):传 ``?taskId=``,不传 actionId;
* - ``action``(xdfx 行动分析):传 ``?actionId=``,不传 taskId。
* rwfx 与 xdfx 共用同一个删除接口(``delete_url`` = dropSixOrSevenData);
* 「不传」= 该参数**根本不出现在 URL 里**(只把要传的那个 key 放进 params,axios 自然省略另一个),
* 不是传空值/None。
*/
export type DeleteIdKind = "task" | "action";
/**
* 删除任务/行动记录(rwfx 覆盖前清空 / 手动清除 / xdfx 清除行动记录都走这里)。
* ``delete_test=true`` 时用后端配置里的假数据(接口未开发完时用);否则打真实接口
* (rwfx 传 ``taskId``、xdfx 传 ``actionId``,见 ``DeleteIdKind``)。
*/
export async function deleteTaskPurpose(
taskId: string,
id: string,
opts?: { idKind?: DeleteIdKind },
): Promise<DeleteTaskPurposeResult> {
if (!taskId) return { message: "缺少 taskId", success: false };
const idKind = opts?.idKind ?? "task";
if (!id) return { message: idKind === "action" ? "缺少 actionId" : "缺少 taskId", success: false };
const cfg = await fetchTaskDeeplinkConfig();
if (cfg.deleteTest) {
const m = cfg.mock.delete ?? {};
return { message: typeof m.message === "string" ? m.message : "", success: m.success === true };
}
if (!cfg.deleteUrl) return { message: "缺少删除接口地址", success: false };
const body = await requestCrudService.get(cfg.deleteUrl, { taskId });
// 只放要传的那个 id key:另一参数直接不出现在 URL(axios 不会序列化缺省 key)。
const params = idKind === "action" ? { actionId: id } : { taskId: id };
const body = await requestCrudService.get(cfg.deleteUrl, params);
const obj =
body && typeof body === "object" ? (body as unknown as Record<string, unknown>) : {};
return {
@@ -276,6 +291,36 @@ export async function deleteTaskPurpose(
};
}
export interface ActionHistoryResult {
/** 是否存在可删除的「行动历史」记录。 */
exists: boolean;
/** 是否成功判定。接口未配置 / 请求失败 → false(调用方据此**不**做「无历史」拦截,仍允许删除)。 */
known: boolean;
}
/**
* 查询 xdfx「行动历史是否存在」(清除行动记录前的预检),按「行动id」调用。
*
* 查询接口(``action_history_url``)地址**目前待提供**:为空时直接返回
* ``{ exists: false, known: false }``——视作空白、不查询,也不阻断删除;后端配上真实地址后自动生效。
*
* ⚠️ 真实返回结构待定:拿到接口后按实际字段调整「是否存在」的判定(当前暂按 ``data`` 非空兜底)。
*/
export async function fetchActionHistoryExists(actionId: string): Promise<ActionHistoryResult> {
if (!actionId) return { exists: false, known: false };
const cfg = await fetchTaskDeeplinkConfig();
if (!cfg.actionHistoryUrl) return { exists: false, known: false };
try {
const body = await requestCrudService.get(cfg.actionHistoryUrl, { actionId });
// TODO(接口就绪):按真实返回结构判定。暂按整包 body.data 非空作「存在历史」。
const data = body && typeof body === "object" ? (body as { data?: unknown }).data : null;
const exists = Array.isArray(data) ? data.length > 0 : data != null && data !== "";
return { exists, known: true };
} catch {
return { exists: false, known: false };
}
}
/* ----------------------------- 跳转地址 ----------------------------- */
/** B 系统「树形完整编辑」页地址(问答后「编辑」按钮,新标签打开)。用同步缓存配置。 */
@@ -29,7 +29,12 @@ import {
DialogHeader,
DialogTitle,
} from "@/components/ui/dialog";
import { deleteTaskPurpose, readTaskContext } from "@/core/auth/task-purpose";
import {
deleteTaskPurpose,
fetchActionHistoryExists,
readTaskContext,
type ActionHistoryResult,
} from "@/core/auth/task-purpose";
import {
fetchActionDetail,
readCopTaskDetail,
@@ -195,6 +200,8 @@ function TaskConversationsList({
<PlusSquare className="h-4 w-4" /> 新建对话
</Button>
{!lite && <TaskClearButton taskId={taskId} />}
{/* xdfx:按「行动id」清除行动记录(删除前查历史是否存在;与 rwfx 共用同一删除接口)。 */}
{business === "xdfx" && actionId && <ActionClearButton actionId={actionId} />}
</div>
<div className="min-h-0 flex-1 overflow-y-auto p-2">
@@ -292,6 +299,103 @@ function TaskClearButton({ taskId }: { taskId: string }) {
);
}
/**
* 「清除记录」按钮(xdfx 行动分析):放在「新建对话」下面,按「行动id」清除该行动在 B 系统里的
* 策划数据(聊天对话保留),与 rwfx 共用同一个删除接口(仅传 ``actionId``、不传 taskId)。
*
* 打开弹框时先「查询历史是否存在」(``fetchActionHistoryExists``):
* - 接口未就绪(地址未配置)→ ``known=false``:当作未知,仍可删除(不拦截);
* - 查到「无历史」(``known && !exists``)→ 禁用「确认清除」(没有可删的数据)。
*/
function ActionClearButton({ actionId }: { actionId: string }) {
const [open, setOpen] = useState(false);
const [clearing, setClearing] = useState(false);
const [checking, setChecking] = useState(false);
const [history, setHistory] = useState<ActionHistoryResult | null>(null);
// 打开弹框时查历史是否存在(接口未配置 → known=false,不影响删除)。
useEffect(() => {
if (!open) return;
let alive = true;
setChecking(true);
setHistory(null);
void fetchActionHistoryExists(actionId)
.then((r) => {
if (alive) setHistory(r);
})
.finally(() => {
if (alive) setChecking(false);
});
return () => {
alive = false;
};
}, [open, actionId]);
const handleClear = async () => {
setClearing(true);
try {
const r = await deleteTaskPurpose(actionId, { idKind: "action" });
if (r.success) toast.success("已清除行动记录");
else toast.error(r.message || "清除失败");
} catch {
toast.error("清除失败,请稍后再试");
} finally {
setClearing(false);
setOpen(false);
}
};
// 仅当「明确查到无历史」时禁用删除;接口未就绪(known=false)仍允许删除。
const noHistory = history?.known === true && history.exists === false;
return (
<>
<Button
size="sm"
variant="ghost"
className="text-muted-foreground hover:bg-destructive/10 hover:text-destructive dark:text-[#ccd2da] dark:hover:bg-[#5c0011]/35 mt-2 h-9 w-full"
onClick={() => setOpen(true)}
disabled={!actionId}
title="清除行动记录"
>
<Trash2 className="h-3.5 w-3.5" /> 清除记录
</Button>
<Dialog open={open} onOpenChange={(o) => !clearing && setOpen(o)}>
<DialogContent>
<DialogHeader>
<DialogTitle>清除行动记录</DialogTitle>
<DialogDescription>
将清除该行动在 B 系统中的策划数据(聊天对话保留),是否确认?
</DialogDescription>
</DialogHeader>
{checking ? (
<div className="text-muted-foreground flex items-center gap-1.5 text-xs">
<Loader2 className="h-3.5 w-3.5 animate-spin" /> 正在查询历史…
</div>
) : history?.known === true ? (
<div className="text-muted-foreground text-xs">
{history.exists ? "检测到该行动的历史记录。" : "未检测到该行动的历史记录,无需清除。"}
</div>
) : null}
<DialogFooter>
<Button variant="outline" onClick={() => setOpen(false)} disabled={clearing}>
取消
</Button>
<Button
variant="destructive"
onClick={handleClear}
disabled={clearing || checking || noHistory}
>
{clearing ? <Loader2 className="h-4 w-4 animate-spin" /> : "确认清除"}
</Button>
</DialogFooter>
</DialogContent>
</Dialog>
</>
);
}
/**
* 任务工作区布局:左列表 + 右 Outlet。
* ``lite``(3qfx/xdfx)= 精简版:左列表无跳转按钮、无底部任务操作面板。