Skip to content

Commit cc6cdad

Browse files
committed
refactor(panels): migrate 4 workbenches to modal
1 parent 09c4994 commit cc6cdad

6 files changed

Lines changed: 698 additions & 526 deletions

File tree

‎app/src/components/KnowledgeSystemWizard.tsx‎

Lines changed: 119 additions & 126 deletions
Original file line numberDiff line numberDiff line change
@@ -9,9 +9,16 @@
99
* @ai-context: 最后落库顺序——先 create_knowledge_system(global),再逐条
1010
* add_knowledge_node(domain_entry),最后第一条输出 add_knowledge_node
1111
* (scenario)。三条 invoke 顺序在测试中有断言,勿改顺序。
12+
* @ai-context: 批 4 T7 迁移:自建遮罩/面板几何(原 560 px 面板 → `Modal` 的 `l` 档 720,
13+
* +160)交给 `Modal`(barrel 导入,B5;规格 §5.2「L = 向导/工作台」)——
14+
* 遮罩、ESC、焦点陷阱、层级、body 滚动锁都是它的独占职责(ADR-033 §7)。
15+
* 开合态仍由父层持有(`KnowledgePage` 的 `wizardOpen` 条件挂载)⇒ `open` 恒为 `true`。
16+
* **关闭语义一行未改**:`Modal` 的三条关闭通道(ESC / 点遮罩 / 关闭钮)全部
17+
* 走同一个 `onClose` = `doClose`(含 `saving` 挡板与「未创建内容」确认),
18+
* 底栏「取消」仍直连 `onClose`(原行为:取消不确认)。
1219
*/
1320
import { useMemo, useState } from "react";
14-
import { zIndex } from "../ui/zIndex";
21+
import { Modal } from "../ui/primitives";
1522
import { invoke } from "@tauri-apps/api/core";
1623
import { confirm } from "@tauri-apps/plugin-dialog";
1724
import type { KnowledgeSystem, KnowledgeNodeType } from "../types/knowledge";
@@ -140,139 +147,125 @@ export default function KnowledgeSystemWizard({ onClose, onCreated }: Props) {
140147

141148
const stepTitle = (i: number) => `第 ${i} 步 · ${STEP_TITLES[i - 1]}`;
142149

143-
return (
144-
<div
145-
style={{
146-
position: "fixed", inset: 0, background: "rgba(0,0,0,0.45)",
147-
display: "flex", alignItems: "center", justifyContent: "center", zIndex: zIndex("modal"),
148-
}}
149-
onClick={doClose}
150-
>
151-
<div
152-
data-testid="knowledge-wizard"
153-
onClick={(e) => e.stopPropagation()}
154-
style={{ width: 560, maxWidth: "92vw", background: "#fff", borderRadius: 10, border: "1px solid #e5e7eb", boxShadow: "0 10px 40px rgba(0,0,0,0.15)", overflow: "hidden" }}
155-
>
156-
{/* 头部:标题 + 关闭 */}
157-
<div style={{ display: "flex", alignItems: "center", padding: "14px 18px", borderBottom: "1px solid #e5e7eb" }}>
158-
<span style={{ fontWeight: 700, fontSize: 15, color: "#0f766e" }}>🧠 创建全局体系</span>
159-
<button data-testid="wizard-close" onClick={() => void doClose()} style={{ marginLeft: "auto", border: "none", background: "none", cursor: "pointer", fontSize: 14, color: "#9ca3af" }} title="关闭">
160-
✕
161-
</button>
162-
</div>
150+
/** 底部行动区:原自绘底栏三枚按钮**逐字**搬进 `Modal` 的 `footer` 槽。
151+
* `flex: 1` 间隔器保留 —— `Modal` 的 footer 是 `justify-content: flex-end`,
152+
* 没有它「上一步」会被挤到右侧(原布局是「上一步」左置)。 */
153+
const footer = (
154+
<>
155+
{step > 1 && (
156+
<button data-testid="wizard-back" onClick={back} style={{ fontSize: 13, cursor: "pointer", padding: "6px 14px", borderRadius: 6, border: "1px solid #d1d5db", background: "#fff" }}>
157+
← 上一步
158+
</button>
159+
)}
160+
<span style={{ flex: 1 }} />
161+
<button onClick={onClose} style={{ fontSize: 13, cursor: "pointer", padding: "6px 14px", borderRadius: 6, border: "1px solid #d1d5db", background: "#fff" }}>
162+
取消
163+
</button>
164+
{step < 3 ? (
165+
<button data-testid="wizard-next" onClick={next} style={{ fontSize: 13, cursor: "pointer", padding: "6px 14px", borderRadius: 6, border: "1px solid #0f766e", background: "#f0fdfa", color: "#0f766e" }}>
166+
下一步 →
167+
</button>
168+
) : (
169+
<button data-testid="wizard-finish" onClick={() => void runCreate()} disabled={saving} style={{ fontSize: 13, cursor: "pointer", padding: "6px 14px", borderRadius: 6, border: "1px solid #0f766e", background: "#f0fdfa", color: "#0f766e" }}>
170+
{saving ? "创建中…" : "✓ 完成,创建体系"}
171+
</button>
172+
)}
173+
</>
174+
);
163175

164-
{/* 步骤指示器(三段) */}
165-
<div style={{ display: "flex", padding: "10px 18px", gap: 6, borderBottom: "1px solid #f3f4f6" }}>
166-
{STEP_TITLES.map((t, i) => {
167-
const n = i + 1;
168-
const active = step === n;
169-
const done = step > n;
170-
return (
171-
<span key={t} data-testid={`wizard-step-${n}`} style={{ fontSize: 11, padding: "3px 8px", borderRadius: 8, background: active ? "#f0fdfa" : done ? "#ecfdf5" : "#f9fafb", color: active ? "#0f766e" : done ? "#047857" : "#9ca3af", border: active ? "1px solid #14b8a6" : "1px solid #e5e7eb" }}>
172-
{done ? "✓" : n}. {t}
173-
</span>
174-
);
175-
})}
176-
</div>
176+
return (
177+
<Modal open onClose={doClose} title="创建全局体系" size="l" testId="knowledge-wizard" footer={footer}>
178+
{/* 自绘头部(标题 + 关闭钮)与原面板几何已删:标题文本交给 `Modal` 的 head,
179+
关闭钮由 `${testId}-close` 契约提供;步骤指示器以下内容原样搬入 */}
180+
{/* 步骤指示器(三段) */}
181+
<div style={{ display: "flex", padding: "10px 18px", gap: 6, borderBottom: "1px solid #f3f4f6" }}>
182+
{STEP_TITLES.map((t, i) => {
183+
const n = i + 1;
184+
const active = step === n;
185+
const done = step > n;
186+
return (
187+
<span key={t} data-testid={`wizard-step-${n}`} style={{ fontSize: 11, padding: "3px 8px", borderRadius: 8, background: active ? "#f0fdfa" : done ? "#ecfdf5" : "#f9fafb", color: active ? "#0f766e" : done ? "#047857" : "#9ca3af", border: active ? "1px solid #14b8a6" : "1px solid #e5e7eb" }}>
188+
{done ? "✓" : n}. {t}
189+
</span>
190+
);
191+
})}
192+
</div>
177193

178-
<div style={{ padding: "16px 18px" }}>
179-
<div style={{ fontSize: 13, fontWeight: 600, marginBottom: 8 }}>{stepTitle(step)}</div>
180-
<p style={{ fontSize: 12, color: "#6b7280", margin: "0 0 12px", lineHeight: 1.6 }}>{GUIDE[`step${step}`]}</p>
181-
<p data-testid={`wizard-example-${step}`} style={{ fontSize: 11, color: "#0f766e", background: "#f0fdfa", border: "1px dashed #99f6e4", borderRadius: 6, padding: "6px 10px", margin: "0 0 12px" }}>
182-
{EXAMPLES[step]}
183-
</p>
194+
<div style={{ padding: "16px 18px" }}>
195+
<div style={{ fontSize: 13, fontWeight: 600, marginBottom: 8 }}>{stepTitle(step)}</div>
196+
<p style={{ fontSize: 12, color: "#6b7280", margin: "0 0 12px", lineHeight: 1.6 }}>{GUIDE[`step${step}`]}</p>
197+
<p data-testid={`wizard-example-${step}`} style={{ fontSize: 11, color: "#0f766e", background: "#f0fdfa", border: "1px dashed #99f6e4", borderRadius: 6, padding: "6px 10px", margin: "0 0 12px" }}>
198+
{EXAMPLES[step]}
199+
</p>
184200

185-
{step === 1 && (
186-
<div style={{ display: "flex", flexDirection: "column", gap: 10 }}>
187-
<div>
188-
<label style={{ fontSize: 12, fontWeight: 600, display: "block", marginBottom: 4 }}>体系名称(可选)</label>
189-
<input
190-
data-testid="wizard-name"
191-
value={name}
192-
onChange={(e) => setName(e.target.value)}
193-
placeholder="默认:全局体系"
194-
style={{ width: "100%", fontSize: 13, padding: "7px 10px", border: "1px solid #e5e7eb", borderRadius: 6, boxSizing: "border-box" }}
195-
/>
196-
</div>
197-
<div>
198-
<label style={{ fontSize: 12, fontWeight: 600, display: "block", marginBottom: 4 }}>核心问题 *</label>
199-
<textarea
200-
data-testid="wizard-core-question"
201-
value={coreQuestion}
202-
onChange={(e) => setCoreQuestion(e.target.value)}
203-
placeholder="一句话写下真正卡住你的那个问题"
204-
rows={3}
205-
style={{ width: "100%", fontSize: 13, padding: "7px 10px", border: "1px solid #e5e7eb", borderRadius: 6, resize: "vertical", boxSizing: "border-box" }}
206-
/>
207-
</div>
201+
{step === 1 && (
202+
<div style={{ display: "flex", flexDirection: "column", gap: 10 }}>
203+
<div>
204+
<label style={{ fontSize: 12, fontWeight: 600, display: "block", marginBottom: 4 }}>体系名称(可选)</label>
205+
<input
206+
data-testid="wizard-name"
207+
value={name}
208+
onChange={(e) => setName(e.target.value)}
209+
placeholder="默认:全局体系"
210+
style={{ width: "100%", fontSize: 13, padding: "7px 10px", border: "1px solid #e5e7eb", borderRadius: 6, boxSizing: "border-box" }}
211+
/>
208212
</div>
209-
)}
210-
211-
{step === 2 && (
212-
<div style={{ display: "flex", flexDirection: "column", gap: 6 }}>
213-
<div style={{ fontSize: 11, color: "#9ca3af", marginBottom: 4 }}>每个入口一行;可全部跳过(空行自动忽略)。</div>
214-
{domainEntries.map((val, i) => (
215-
<input
216-
key={i}
217-
data-testid={`wizard-domain-${i}`}
218-
value={val}
219-
onChange={(e) => setDomainEntry(i, e.target.value)}
220-
placeholder={`领域入口 ${i + 1}(可空)`}
221-
style={{ width: "100%", fontSize: 13, padding: "6px 10px", border: "1px solid #e5e7eb", borderRadius: 6, boxSizing: "border-box" }}
222-
/>
223-
))}
224-
<button
225-
data-testid="wizard-add-domain"
226-
onClick={() => setDomainEntries((prev) => prev.length >= 5 ? prev : [...prev, ""])}
227-
disabled={domainEntries.length >= 5}
228-
style={{ alignSelf: "flex-start", fontSize: 12, cursor: "pointer", padding: "3px 10px", borderRadius: 4, border: "1px solid #d1d5db", background: "#fff", color: domainEntries.length >= 5 ? "#9ca3af" : "#374151" }}
229-
>
230-
+ 加一行
231-
</button>
213+
<div>
214+
<label style={{ fontSize: 12, fontWeight: 600, display: "block", marginBottom: 4 }}>核心问题 *</label>
215+
<textarea
216+
data-testid="wizard-core-question"
217+
value={coreQuestion}
218+
onChange={(e) => setCoreQuestion(e.target.value)}
219+
placeholder="一句话写下真正卡住你的那个问题"
220+
rows={3}
221+
style={{ width: "100%", fontSize: 13, padding: "7px 10px", border: "1px solid #e5e7eb", borderRadius: 6, resize: "vertical", boxSizing: "border-box" }}
222+
/>
232223
</div>
233-
)}
224+
</div>
225+
)}
234226

235-
{step === 3 && (
236-
<textarea
237-
data-testid="wizard-first-output"
238-
value={firstOutput}
239-
onChange={(e) => setFirstOutput(e.target.value)}
240-
placeholder="可选:本周要产出的第一个可验证输出"
241-
rows={3}
242-
style={{ width: "100%", fontSize: 13, padding: "7px 10px", border: "1px solid #e5e7eb", borderRadius: 6, resize: "vertical", boxSizing: "border-box" }}
243-
/>
244-
)}
227+
{step === 2 && (
228+
<div style={{ display: "flex", flexDirection: "column", gap: 6 }}>
229+
<div style={{ fontSize: 11, color: "#9ca3af", marginBottom: 4 }}>每个入口一行;可全部跳过(空行自动忽略)。</div>
230+
{domainEntries.map((val, i) => (
231+
<input
232+
key={i}
233+
data-testid={`wizard-domain-${i}`}
234+
value={val}
235+
onChange={(e) => setDomainEntry(i, e.target.value)}
236+
placeholder={`领域入口 ${i + 1}(可空)`}
237+
style={{ width: "100%", fontSize: 13, padding: "6px 10px", border: "1px solid #e5e7eb", borderRadius: 6, boxSizing: "border-box" }}
238+
/>
239+
))}
240+
<button
241+
data-testid="wizard-add-domain"
242+
onClick={() => setDomainEntries((prev) => prev.length >= 5 ? prev : [...prev, ""])}
243+
disabled={domainEntries.length >= 5}
244+
style={{ alignSelf: "flex-start", fontSize: 12, cursor: "pointer", padding: "3px 10px", borderRadius: 4, border: "1px solid #d1d5db", background: "#fff", color: domainEntries.length >= 5 ? "#9ca3af" : "#374151" }}
245+
>
246+
+ 加一行
247+
</button>
248+
</div>
249+
)}
245250

246-
{/* 逐行红色错误提示 */}
247-
{error && (
248-
<div data-testid="wizard-error" style={{ fontSize: 12, color: "#dc2626", marginTop: 10, background: "#fef2f2", border: "1px solid #fecaca", borderRadius: 6, padding: "6px 10px", lineHeight: 1.5 }}>
249-
{error}
250-
</div>
251-
)}
252-
</div>
251+
{step === 3 && (
252+
<textarea
253+
data-testid="wizard-first-output"
254+
value={firstOutput}
255+
onChange={(e) => setFirstOutput(e.target.value)}
256+
placeholder="可选:本周要产出的第一个可验证输出"
257+
rows={3}
258+
style={{ width: "100%", fontSize: 13, padding: "7px 10px", border: "1px solid #e5e7eb", borderRadius: 6, resize: "vertical", boxSizing: "border-box" }}
259+
/>
260+
)}
253261

254-
{/* 底部:上一步 / 下一步 / 完成 */}
255-
<div style={{ display: "flex", alignItems: "center", gap: 8, padding: "12px 18px", borderTop: "1px solid #e5e7eb", background: "#fafafa" }}>
256-
{step > 1 && (
257-
<button data-testid="wizard-back" onClick={back} style={{ fontSize: 13, cursor: "pointer", padding: "6px 14px", borderRadius: 6, border: "1px solid #d1d5db", background: "#fff" }}>
258-
← 上一步
259-
</button>
260-
)}
261-
<span style={{ flex: 1 }} />
262-
<button onClick={onClose} style={{ fontSize: 13, cursor: "pointer", padding: "6px 14px", borderRadius: 6, border: "1px solid #d1d5db", background: "#fff" }}>
263-
取消
264-
</button>
265-
{step < 3 ? (
266-
<button data-testid="wizard-next" onClick={next} style={{ fontSize: 13, cursor: "pointer", padding: "6px 14px", borderRadius: 6, border: "1px solid #0f766e", background: "#f0fdfa", color: "#0f766e" }}>
267-
下一步 →
268-
</button>
269-
) : (
270-
<button data-testid="wizard-finish" onClick={() => void runCreate()} disabled={saving} style={{ fontSize: 13, cursor: "pointer", padding: "6px 14px", borderRadius: 6, border: "1px solid #0f766e", background: "#f0fdfa", color: "#0f766e" }}>
271-
{saving ? "创建中…" : "✓ 完成,创建体系"}
272-
</button>
273-
)}
274-
</div>
262+
{/* 逐行红色错误提示 */}
263+
{error && (
264+
<div data-testid="wizard-error" style={{ fontSize: 12, color: "#dc2626", marginTop: 10, background: "#fef2f2", border: "1px solid #fecaca", borderRadius: 6, padding: "6px 10px", lineHeight: 1.5 }}>
265+
{error}
266+
</div>
267+
)}
275268
</div>
276-
</div>
269+
</Modal>
277270
);
278271
}

0 commit comments

Comments
 (0)