Skip to content

Commit 9c1d5fe

Browse files
committed
fix(knowledge): 笔记挂接体系切换残留与组行引用聚合(审查修复)
1 parent e459cf1 commit 9c1d5fe

4 files changed

Lines changed: 54 additions & 11 deletions

File tree

‎app/src/components/GroupSidebar.test.tsx‎

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ const groupB: NoteGroup = {
3131

3232
beforeEach(() => {
3333
invokeMock.mockReset();
34-
invokeMock.mockImplementation(async (cmd: string) => {
34+
invokeMock.mockImplementation(async (cmd: string, args?: Record<string, unknown>) => {
3535
switch (cmd) {
3636
case "list_note_groups": return [groupA, groupB];
3737
case "count_due_cards": return 0;
@@ -42,7 +42,10 @@ beforeEach(() => {
4242
case "list_knowledge_systems":
4343
return [{ id: 10, parentSystemId: null, name: "摄影", kind: "domain", coreQuestion: null, status: "active", createdAt: 0, updatedAt: 0 }];
4444
case "list_knowledge_links":
45-
return [{ id: 1, systemId: 10, nodeId: null, conceptId: null, modelId: null, targetType: "note_group", targetId: 1, createdAt: 0 }];
45+
// 后端强制 system_id(无全局查询)——仅体系 10 返回该组引用
46+
return args?.systemId === 10
47+
? [{ id: 1, systemId: 10, nodeId: null, conceptId: null, modelId: null, targetType: "note_group", targetId: 1, createdAt: 0 }]
48+
: [];
4649
case "override_group_route": return true;
4750
default:
4851
throw new Error(`unexpected: ${cmd}`);
@@ -129,6 +132,8 @@ describe("GroupSidebar ⓘ 弹层", () => {
129132
await screen.findByTestId("group-row-1");
130133
const badge = await screen.findByTestId("system-badge");
131134
expect(badge.textContent).toContain("摄影");
135+
// 按体系聚合调用(后端 list_knowledge_links 强制 system_id)
136+
expect(invokeMock).toHaveBeenCalledWith("list_knowledge_links", { systemId: 10 });
132137
// 点击徽标跳体系页(stopPropagation——不触发组过滤)
133138
fireEvent.click(badge);
134139
expect(onOpenSystem).toHaveBeenCalledWith(10);

‎app/src/components/GroupSidebar.tsx‎

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -82,11 +82,15 @@ export default function GroupSidebar({
8282
const frags = await invoke<Fragment[]>("list_fragments", { status: "active", limit: 500 });
8383
setInboxCount(frags.length);
8484
// v0.13.7 触点①:体系 + 引用拉取(并行——徽标数据与组列表无依赖)
85-
const [sysList, links] = await Promise.all([
86-
invoke<KnowledgeSystem[]>("list_knowledge_systems"),
87-
invoke<KnowledgeLink[]>("list_knowledge_links"),
88-
]);
85+
const sysList = await invoke<KnowledgeSystem[]>("list_knowledge_systems");
8986
setSystems(sysList);
87+
// 后端 list_knowledge_links 强制 system_id(无全局查询)——
88+
// 按非归档体系逐次查询后聚合(审查修复:原无参调用必报"必须指定体系")
89+
const activeSystems = sysList.filter((s) => s.status !== "archived");
90+
const linkArrays = await Promise.all(
91+
activeSystems.map((s) => invoke<KnowledgeLink[]>("list_knowledge_links", { systemId: s.id })),
92+
);
93+
const links = linkArrays.flat();
9094
const map: Record<number, { systemId: number; count: number }[]> = {};
9195
for (const l of links) {
9296
if (l.targetType !== "note_group") continue;

‎app/src/components/NoteLinkToSystem.test.tsx‎

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,37 @@ describe("NoteLinkToSystem", () => {
5151
});
5252
});
5353

54+
it("未选节点时确认按钮禁用(默认项「选择节点…」——不挂体系根)", async () => {
55+
render(<NoteLinkToSystem noteId={7} onChanged={vi.fn()} />);
56+
fireEvent.click(await screen.findByTestId("note-link-open"));
57+
// 选体系但未选节点
58+
fireEvent.change(await screen.findByTestId("note-link-system"), { target: { value: "2" } });
59+
const nodeSelect = (await screen.findByTestId("note-link-node")) as HTMLSelectElement;
60+
expect(nodeSelect.value).toBe("");
61+
const confirm = screen.getByTestId("note-link-confirm") as HTMLButtonElement;
62+
expect(confirm.disabled).toBe(true);
63+
// 选节点后确认可用(后端拒绝空实体——nodeId 必选)
64+
fireEvent.change(nodeSelect, { target: { value: "11" } });
65+
expect((screen.getByTestId("note-link-confirm") as HTMLButtonElement).disabled).toBe(false);
66+
});
67+
68+
it("切换体系时重置节点选择(防 nodeId 残留串体系——后端报「引用实体不属于该体系」)", async () => {
69+
render(<NoteLinkToSystem noteId={7} onChanged={vi.fn()} />);
70+
fireEvent.click(await screen.findByTestId("note-link-open"));
71+
// 体系 2 + 节点 11
72+
fireEvent.change(await screen.findByTestId("note-link-system"), { target: { value: "2" } });
73+
fireEvent.change(await screen.findByTestId("note-link-node"), { target: { value: "11" } });
74+
// 切到体系 1(无节点)——nodeId 必须重置
75+
fireEvent.change(await screen.findByTestId("note-link-system"), { target: { value: "1" } });
76+
const nodeSelect = (await screen.findByTestId("note-link-node")) as HTMLSelectElement;
77+
expect(nodeSelect.value).toBe("");
78+
const confirm = screen.getByTestId("note-link-confirm") as HTMLButtonElement;
79+
expect(confirm.disabled).toBe(true);
80+
// 再切回体系 2 仍须重新选节点(残留 11 不复活)
81+
fireEvent.change(await screen.findByTestId("note-link-system"), { target: { value: "2" } });
82+
expect(((await screen.findByTestId("note-link-node")) as HTMLSelectElement).value).toBe("");
83+
});
84+
5485
it("已挂接时显示「已挂 · 体系名」并可取消", async () => {
5586
invokeMock.mockImplementation(async (cmd: string) => {
5687
switch (cmd) {

‎app/src/components/NoteLinkToSystem.tsx‎

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -41,14 +41,17 @@ export default function NoteLinkToSystem({ noteId, onChanged }: Props) {
4141
);
4242
const links = linkGroups.flat();
4343
setLinked(links.find((l) => l.targetType === "note" && l.targetId === noteId) ?? null);
44+
setErr(""); // 成功后清掉陈旧错误(审查修复)
4445
} catch (e) { setErr(`体系加载失败: ${e}`); }
4546
}, [noteId]);
4647

4748
useEffect(() => { void load(); }, [load]);
4849

49-
// 选体系后拉取该体系节点树
50+
// 选体系后拉取该体系节点树(体系一变即重置节点选择——
51+
// 防残留 nodeId 串体系被后端拒「引用实体不属于该体系」,审查修复)
5052
useEffect(() => {
51-
if (systemId == null) { setNodes([]); setNodeId(null); return; }
53+
setNodeId(null);
54+
if (systemId == null) { setNodes([]); return; }
5255
invoke<KnowledgeNode[]>("list_knowledge_nodes", { systemId })
5356
.then(setNodes)
5457
.catch((e) => setErr(`节点加载失败: ${e}`));
@@ -87,7 +90,7 @@ export default function NoteLinkToSystem({ noteId, onChanged }: Props) {
8790
const linkedSystem = systems.find((s) => s.id === linked?.systemId) ?? null;
8891

8992
return (
90-
<span style={{ display: "inline-flex", flexDirection: "column", alignItems: "flex-start", gap: 4 }}>
93+
<span style={{ display: "inline-flex", flexDirection: "column", alignItems: "flex-start", gap: 4, position: "relative" }}>
9194
<button
9295
data-testid="note-link-open"
9396
onClick={() => setOpen((v) => !v)}
@@ -129,7 +132,7 @@ export default function NoteLinkToSystem({ noteId, onChanged }: Props) {
129132
style={{ fontSize: 12, padding: "4px 6px", border: "1px solid #e5e7eb", borderRadius: 4 }}
130133
disabled={effectiveSystemId == null}
131134
>
132-
<option value="">挂到体系根(不选节点)</option>
135+
<option value="">选择节点…</option>
133136
{nodes.map((n) => (
134137
<option key={n.id} value={n.id}>{n.text.slice(0, 24)}</option>
135138
))}
@@ -147,7 +150,7 @@ export default function NoteLinkToSystem({ noteId, onChanged }: Props) {
147150
<button
148151
data-testid="note-link-confirm"
149152
onClick={() => void confirmLink()}
150-
disabled={busy || effectiveSystemId == null}
153+
disabled={busy || effectiveSystemId == null || nodeId == null}
151154
style={{ fontSize: 12, cursor: "pointer", padding: "4px 0", borderRadius: 4, border: "1px solid #0f766e", background: "#f0fdfa", color: "#0f766e" }}
152155
>
153156
{busy ? "挂接中…" : "确认挂接"}

0 commit comments

Comments
 (0)