桌面 GUI 外链修复 + 主题顺序调整(含结构扁平化进行中的工作区基线)
- 集成 tauri-plugin-opener:Cargo/lib.rs/capabilities/npm 四处接线,新增 src/openExternal.ts(桌面走 opener,浏览器回退 window.open,相对路径绝对化) - 关于页 4 个链接、活动栏官网菜单、代理错误横幅 2 处健康检查链接全部改走 openExternal,桌面端可正常跳转 - 设置→外观 主题顺序调整为 深色/浅色/跟随系统 - release 二进制重编并验证(浏览器实测关于页链接可开文档页) - 附带工作区中未提交的扁平化重构现状(另一会话进行中) - homepage 落地页与文档终检通过(8787 / 与 /docs 双 200)
This commit is contained in:
1 parent
6452026fd6
commit
2bb4ef7a95
299 files changed
+6059
-3322
No files matched your search
@@ -0,0 +1,120 @@
|
||||
import { useTestActions } from "./useTestActions";
|
||||
import type { TestRun } from "../../core/index";
|
||||
import { useEffect, useState } from "react";
|
||||
import { useWorkspace } from "../../workspaceStore";
|
||||
import { cx } from "../../components/common";
|
||||
import { Icon } from "../../components/icons";
|
||||
import { Button } from "../../components/primitives";
|
||||
import { AiCaseDialog } from "../../components/dialogs/AiCaseDialog";
|
||||
import { ScheduleDialog } from "../../components/dialogs/ScheduleDialog";
|
||||
import { CaseDetail } from "./CaseDetail";
|
||||
import { installHttp8Regression } from "./regression";
|
||||
import { WorkspaceSidebar } from "../../components/WorkspaceSidebar";
|
||||
export function TestingView() {
|
||||
const testCases = useWorkspace((s) => s.testCases);
|
||||
const testRuns = useWorkspace((s) => s.testRuns);
|
||||
const schedules = useWorkspace((s) => s.schedules);
|
||||
const addTestCase = useWorkspace((s) => s.addTestCase);
|
||||
|
||||
const [selected, setSelected] = useState<string | null>(testCases[0]?.id ?? null);
|
||||
const [showAi, setShowAi] = useState(false);
|
||||
const [showSchedule, setShowSchedule] = useState(false);
|
||||
const { busy: runAllBusy, runAll } = useTestActions();
|
||||
|
||||
const tc = testCases.find((c) => c.id === selected) ?? null;
|
||||
const lastRunOf = (caseId: string): TestRun | undefined => testRuns.find((r) => r.caseId === caseId);
|
||||
|
||||
useEffect(() => {
|
||||
if (!testCases.some((c) => c.id === selected)) setSelected(testCases[0]?.id ?? null);
|
||||
}, [testCases, selected]);
|
||||
|
||||
return (
|
||||
<div className="flex min-h-0 min-w-0 flex-1">
|
||||
{/* 左栏:用例列表 */}
|
||||
<WorkspaceSidebar label="测试" className="test-case-sidebar">
|
||||
<div className="flex items-center gap-1.5 p-2.5" style={{ borderBottom: "1px solid var(--border)" }}>
|
||||
<Icon name="list-checks" size={15} className="ops-accent" />
|
||||
<span className="text-[13px] font-semibold">场景用例</span>
|
||||
<span className="ops-faint ml-auto text-[11px]">{testCases.length}</span>
|
||||
</div>
|
||||
<div className="p-2.5" style={{ borderBottom: "1px solid var(--border)" }}>
|
||||
<div className="grid grid-cols-2 gap-1.5">
|
||||
<button className="ops-btn ops-btn-secondary !px-2" onClick={() => setSelected(addTestCase())}>
|
||||
<Icon name="plus" size={13} /> 新建
|
||||
</button>
|
||||
<button className="ops-btn ops-btn-secondary !px-2" onClick={() => setShowAi(true)}>
|
||||
<Icon name="bot" size={13} /> AI 生成
|
||||
</button>
|
||||
</div>
|
||||
<details className="mt-2 text-xs"><summary className="ops-faint cursor-pointer px-1 py-1">示例与学习</summary><button className="ops-btn ops-btn-ghost mt-1 w-full justify-start !px-2" title="装载本地 Gin /v1/echo 八组合回归用例" onClick={() => {
|
||||
const state = useWorkspace.getState();
|
||||
if ((state.collections.some((c) => c.name === "示例集合") || state.testCases.some((c) => c.id === "tc-opsx-http8-regression")) && !confirm("装载示例会刷新已有的示例集合和回归用例。确定继续?")) return;
|
||||
setSelected(installHttp8Regression());
|
||||
}}><Icon name="play" size={13} /> 装载回归示例</button></details>
|
||||
</div>
|
||||
<div className="ops-scroll min-h-0 flex-1 p-2">
|
||||
{testCases.length === 0 && (
|
||||
<div className="ops-faint px-2 py-8 text-center text-xs leading-5">
|
||||
还没有用例
|
||||
<br />
|
||||
点「新建」手动编排,或用「AI 生成」
|
||||
</div>
|
||||
)}
|
||||
{testCases.map((c) => {
|
||||
const last = lastRunOf(c.id);
|
||||
const sched = schedules.find((s) => s.caseId === c.id && s.enabled);
|
||||
return (
|
||||
<button
|
||||
key={c.id}
|
||||
className={cx("ops-tree-row w-full text-left", selected === c.id && "ops-tree-row-active")}
|
||||
onClick={() => setSelected(c.id)}
|
||||
>
|
||||
<span className="truncate text-[12px] font-medium">{c.name}</span>
|
||||
<span className="ops-faint ml-auto shrink-0 text-[11px]">{c.steps.length} 步</span>
|
||||
{sched && <Icon name="calendar-clock" size={11} className="ops-accent" />}
|
||||
{last && (
|
||||
<Icon
|
||||
name={last.passed ? "circle-check" : "circle-x"}
|
||||
size={12}
|
||||
className={last.passed ? "ops-success" : "ops-danger"}
|
||||
/>
|
||||
)}
|
||||
</button>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
<div className="space-y-1.5 p-2.5" style={{ borderTop: "1px solid var(--border)" }}>
|
||||
<button className="ops-btn ops-btn-secondary w-full" onClick={() => setShowSchedule(true)}>
|
||||
<Icon name="calendar-clock" size={13} /> 定时任务
|
||||
{schedules.filter((s) => s.enabled).length > 0 && (
|
||||
<span className="ops-count">{schedules.filter((s) => s.enabled).length}</span>
|
||||
)}
|
||||
</button>
|
||||
<Button variant="primary" className="w-full" busy={runAllBusy} disabled={testCases.length === 0} onClick={runAll}>
|
||||
<Icon name="play" size={13} /> {runAllBusy ? "运行中…" : "运行全部"}
|
||||
</Button>
|
||||
</div>
|
||||
</WorkspaceSidebar>
|
||||
|
||||
{/* 右侧:用例详情 */}
|
||||
{tc ? (
|
||||
<CaseDetail key={tc.id} tc={tc} onOpenSchedule={() => setShowSchedule(true)} />
|
||||
) : (
|
||||
<div className="grid min-w-0 flex-1 place-items-center">
|
||||
<div className="text-center">
|
||||
<div className="ops-dim mb-2 flex justify-center"><Icon name="list-checks" size={40} /></div>
|
||||
<div className="ops-dim text-sm font-semibold">自动化测试</div>
|
||||
<div className="ops-faint mt-1 text-xs leading-5">
|
||||
手动编排 · AI 生成 · 定时调度,三种方式共用同一套场景用例
|
||||
<br />
|
||||
左侧新建用例;侧栏底部「自动化测试」随时进出本视图
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{showAi && <AiCaseDialog onClose={() => setShowAi(false)} onSaved={(id) => setSelected(id)} />}
|
||||
{showSchedule && <ScheduleDialog focusCaseId={selected} onClose={() => setShowSchedule(false)} />}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
Reference in new issue
Block a user