fix(dialogs): F03 证据弹窗同期间重复点击不再停留在加载中
- 第 0 页重复点击当前已选期间直接返回,不清空列表、不重复请求; 第二页点击当前期间回到第一页并刷新 - 请求失败显示错误并提供“重试”按钮;空结果显示空态行而不是过期数据 - E2E 覆盖:重复点击、快速本期/基期切换、第二页切期间、失败重试、空列表
This commit is contained in:
@@ -1,5 +1,12 @@
|
|||||||
import { useEffect, useRef, useState } from "react";
|
import { useEffect, useRef, useState } from "react";
|
||||||
import { ChevronLeft, ChevronRight, Download, Save, Trash2, X } from "lucide-react";
|
import {
|
||||||
|
ChevronLeft,
|
||||||
|
ChevronRight,
|
||||||
|
Download,
|
||||||
|
Save,
|
||||||
|
Trash2,
|
||||||
|
X,
|
||||||
|
} from "lucide-react";
|
||||||
import { api } from "./api";
|
import { api } from "./api";
|
||||||
import {
|
import {
|
||||||
labels,
|
labels,
|
||||||
@@ -349,7 +356,8 @@ export function EvidenceDialog({
|
|||||||
}>(),
|
}>(),
|
||||||
[side, setSide] = useState<"current" | "baseline">("current"),
|
[side, setSide] = useState<"current" | "baseline">("current"),
|
||||||
[page, setPage] = useState(0),
|
[page, setPage] = useState(0),
|
||||||
[error, setError] = useState("");
|
[error, setError] = useState(""),
|
||||||
|
[reloadKey, setReloadKey] = useState(0);
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
let active = true;
|
let active = true;
|
||||||
setResult(undefined);
|
setResult(undefined);
|
||||||
@@ -364,8 +372,14 @@ export function EvidenceDialog({
|
|||||||
return () => {
|
return () => {
|
||||||
active = false;
|
active = false;
|
||||||
};
|
};
|
||||||
}, [ds.id, ctx, side, page]);
|
}, [ds.id, ctx, side, page, reloadKey]);
|
||||||
function selectSide(value: "current" | "baseline") {
|
function selectSide(value: "current" | "baseline") {
|
||||||
|
if (value === side) {
|
||||||
|
// 重复点击当前期间:仅在不在第一页时回到第一页触发刷新,
|
||||||
|
// 同期间同页直接返回,避免清空列表后停在“加载中”。
|
||||||
|
if (page !== 0) setPage(0);
|
||||||
|
return;
|
||||||
|
}
|
||||||
setResult(undefined);
|
setResult(undefined);
|
||||||
setPage(0);
|
setPage(0);
|
||||||
setSide(value);
|
setSide(value);
|
||||||
@@ -401,7 +415,8 @@ export function EvidenceDialog({
|
|||||||
{user.role !== "admin" && <p>客户原始标识不包含在明细中。</p>}
|
{user.role !== "admin" && <p>客户原始标识不包含在明细中。</p>}
|
||||||
{error && (
|
{error && (
|
||||||
<p role="alert" className="error">
|
<p role="alert" className="error">
|
||||||
{error}
|
{error}{" "}
|
||||||
|
<button onClick={() => setReloadKey((n) => n + 1)}>重试</button>
|
||||||
</p>
|
</p>
|
||||||
)}
|
)}
|
||||||
<div className="table-scroll">
|
<div className="table-scroll">
|
||||||
@@ -435,6 +450,11 @@ export function EvidenceDialog({
|
|||||||
<td>{String(r.gross ?? "")}</td>
|
<td>{String(r.gross ?? "")}</td>
|
||||||
</tr>
|
</tr>
|
||||||
))}
|
))}
|
||||||
|
{result && result.rows.length === 0 && (
|
||||||
|
<tr>
|
||||||
|
<td colSpan={3 + ds.dimensions.length}>本期范围内没有数据</td>
|
||||||
|
</tr>
|
||||||
|
)}
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
</div>
|
</div>
|
||||||
@@ -776,7 +796,10 @@ export function FormulaExportDialog({
|
|||||||
);
|
);
|
||||||
setDefinitions((items) =>
|
setDefinitions((items) =>
|
||||||
[...items.filter((item) => item.id !== saved.id), saved].sort((a, b) =>
|
[...items.filter((item) => item.id !== saved.id), saved].sort((a, b) =>
|
||||||
String(a.definition.name).localeCompare(String(b.definition.name), "zh-CN"),
|
String(a.definition.name).localeCompare(
|
||||||
|
String(b.definition.name),
|
||||||
|
"zh-CN",
|
||||||
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
setSelectedDefinition(saved.id);
|
setSelectedDefinition(saved.id);
|
||||||
|
|||||||
+94
-23
@@ -30,7 +30,9 @@ test("桌面:真实导入、异常、下钻、证据、设置、Copilot、导
|
|||||||
});
|
});
|
||||||
await uploaded;
|
await uploaded;
|
||||||
const dialog = page.getByRole("dialog");
|
const dialog = page.getByRole("dialog");
|
||||||
await expect(dialog.getByRole("heading", { name: "确认导入字段" })).toBeVisible();
|
await expect(
|
||||||
|
dialog.getByRole("heading", { name: "确认导入字段" }),
|
||||||
|
).toBeVisible();
|
||||||
await dialog.getByRole("button", { name: "确认并导入" }).click();
|
await dialog.getByRole("button", { name: "确认并导入" }).click();
|
||||||
await expect(
|
await expect(
|
||||||
page.getByRole("heading", { name: "集团经营总览", exact: true }),
|
page.getByRole("heading", { name: "集团经营总览", exact: true }),
|
||||||
@@ -85,14 +87,27 @@ test("桌面:真实导入、异常、下钻、证据、设置、Copilot、导
|
|||||||
await expect(evidenceDialog).toContainText("1 条");
|
await expect(evidenceDialog).toContainText("1 条");
|
||||||
await expect(evidenceDialog).toContainText("2026-08");
|
await expect(evidenceDialog).toContainText("2026-08");
|
||||||
await expect(evidenceDialog).toContainText("CSV");
|
await expect(evidenceDialog).toContainText("CSV");
|
||||||
await evidenceDialog.getByRole("button", { name: "基期", exact: true }).click();
|
await evidenceDialog
|
||||||
|
.getByRole("button", { name: "基期", exact: true })
|
||||||
|
.click();
|
||||||
await expect(evidenceDialog).toContainText("2025-08");
|
await expect(evidenceDialog).toContainText("2025-08");
|
||||||
await evidenceDialog.getByLabel("关闭", { exact: true }).click();
|
await evidenceDialog.getByLabel("关闭", { exact: true }).click();
|
||||||
const evidenceOffsets: number[] = [];
|
const evidenceOffsets: number[] = [];
|
||||||
|
let evidenceFailOnce = false;
|
||||||
|
let evidenceEmpty = false;
|
||||||
await page.route("**/api/datasets/*/evidence", async (route) => {
|
await page.route("**/api/datasets/*/evidence", async (route) => {
|
||||||
const request = route.request().postDataJSON() as { offset?: number };
|
const request = route.request().postDataJSON() as { offset?: number };
|
||||||
const offset = request.offset || 0;
|
const offset = request.offset || 0;
|
||||||
|
if (evidenceFailOnce) {
|
||||||
|
evidenceFailOnce = false;
|
||||||
|
await route.fulfill({ status: 500, json: { error: "服务暂不可用" } });
|
||||||
|
return;
|
||||||
|
}
|
||||||
evidenceOffsets.push(offset);
|
evidenceOffsets.push(offset);
|
||||||
|
if (evidenceEmpty) {
|
||||||
|
await route.fulfill({ json: { total: 0, sheet: "CSV", rows: [] } });
|
||||||
|
return;
|
||||||
|
}
|
||||||
const count = offset === 0 ? 50 : 1;
|
const count = offset === 0 ? 50 : 1;
|
||||||
await route.fulfill({
|
await route.fulfill({
|
||||||
json: {
|
json: {
|
||||||
@@ -111,10 +126,51 @@ test("桌面:真实导入、异常、下钻、证据、设置、Copilot、导
|
|||||||
});
|
});
|
||||||
await page.getByLabel("查看医生A证据", { exact: true }).click();
|
await page.getByLabel("查看医生A证据", { exact: true }).click();
|
||||||
await expect(evidenceDialog).toContainText("第 1-50 条,共 51 条");
|
await expect(evidenceDialog).toContainText("第 1-50 条,共 51 条");
|
||||||
await evidenceDialog.getByRole("button", { name: "下一页", exact: true }).click();
|
await evidenceDialog
|
||||||
|
.getByRole("button", { name: "下一页", exact: true })
|
||||||
|
.click();
|
||||||
await expect(evidenceDialog).toContainText("第 51-51 条,共 51 条");
|
await expect(evidenceDialog).toContainText("第 51-51 条,共 51 条");
|
||||||
await expect(evidenceDialog).toContainText("2026-09");
|
await expect(evidenceDialog).toContainText("2026-09");
|
||||||
expect(evidenceOffsets).toEqual([0, 50]);
|
expect(evidenceOffsets).toEqual([0, 50]);
|
||||||
|
// F03 回归:第二页时重复点击当前期间应回到第一页并重新加载
|
||||||
|
await evidenceDialog
|
||||||
|
.getByRole("button", { name: "本期", exact: true })
|
||||||
|
.click();
|
||||||
|
await expect(evidenceDialog).toContainText("第 1-50 条,共 51 条");
|
||||||
|
expect(evidenceOffsets).toEqual([0, 50, 0]);
|
||||||
|
// F03 回归:第一页重复点击当前期间不清空列表、不重新请求、不停在“加载中”
|
||||||
|
await evidenceDialog
|
||||||
|
.getByRole("button", { name: "本期", exact: true })
|
||||||
|
.click();
|
||||||
|
await expect(evidenceDialog).toContainText("第 1-50 条,共 51 条");
|
||||||
|
await expect(evidenceDialog).not.toContainText("加载中");
|
||||||
|
expect(evidenceOffsets).toEqual([0, 50, 0]);
|
||||||
|
// F03 回归:快速本期/基期切换最终停留在当前期间的数据
|
||||||
|
await evidenceDialog
|
||||||
|
.getByRole("button", { name: "基期", exact: true })
|
||||||
|
.click();
|
||||||
|
await evidenceDialog
|
||||||
|
.getByRole("button", { name: "本期", exact: true })
|
||||||
|
.click();
|
||||||
|
await expect(evidenceDialog).toContainText("第 1-50 条,共 51 条");
|
||||||
|
await expect(evidenceDialog).not.toContainText("加载中");
|
||||||
|
// F03 回归:请求失败显示错误并可重试恢复
|
||||||
|
evidenceFailOnce = true;
|
||||||
|
await evidenceDialog
|
||||||
|
.getByRole("button", { name: "下一页", exact: true })
|
||||||
|
.click();
|
||||||
|
await expect(evidenceDialog).toContainText("服务暂不可用");
|
||||||
|
await expect(evidenceDialog).toContainText("加载失败");
|
||||||
|
await evidenceDialog.getByRole("button", { name: "重试" }).click();
|
||||||
|
await expect(evidenceDialog).toContainText("第 51-51 条,共 51 条");
|
||||||
|
// F03 回归:空结果显示空态而不是过期数据或加载中
|
||||||
|
evidenceEmpty = true;
|
||||||
|
await evidenceDialog
|
||||||
|
.getByRole("button", { name: "上一页", exact: true })
|
||||||
|
.click();
|
||||||
|
await expect(evidenceDialog).toContainText("本期范围内没有数据");
|
||||||
|
await expect(evidenceDialog).toContainText("共 0 条");
|
||||||
|
await expect(evidenceDialog).not.toContainText("加载中");
|
||||||
await evidenceDialog.getByLabel("关闭", { exact: true }).click();
|
await evidenceDialog.getByLabel("关闭", { exact: true }).click();
|
||||||
await page
|
await page
|
||||||
.getByRole("navigation", { name: "分析路径" })
|
.getByRole("navigation", { name: "分析路径" })
|
||||||
@@ -164,38 +220,53 @@ test("桌面:真实导入、异常、下钻、证据、设置、Copilot、导
|
|||||||
await page.route("**/formula-design", async (route) => {
|
await page.route("**/formula-design", async (route) => {
|
||||||
designCalls++;
|
designCalls++;
|
||||||
await route.fulfill({
|
await route.fulfill({
|
||||||
json: designCalls === 1
|
json:
|
||||||
? { complete: false, questions: ["缺失值和零分母如何处理?"], column: null }
|
designCalls === 1
|
||||||
: {
|
? {
|
||||||
complete: true,
|
complete: false,
|
||||||
questions: [],
|
questions: ["缺失值和零分母如何处理?"],
|
||||||
column: {
|
column: null,
|
||||||
name: "收入成本差",
|
}
|
||||||
meaning: "实际收入减成本",
|
: {
|
||||||
scope: "所选分组与期间",
|
complete: true,
|
||||||
missing: "结果不可用(#N/A);零分母不可用",
|
questions: [],
|
||||||
expression: {
|
column: {
|
||||||
operator: "subtract",
|
name: "收入成本差",
|
||||||
left: { metric: "revenue" },
|
meaning: "实际收入减成本",
|
||||||
right: { metric: "cost" },
|
scope: "所选分组与期间",
|
||||||
|
missing: "结果不可用(#N/A);零分母不可用",
|
||||||
|
expression: {
|
||||||
|
operator: "subtract",
|
||||||
|
left: { metric: "revenue" },
|
||||||
|
right: { metric: "cost" },
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
await page.getByRole("button", { name: "生成公式工作簿" }).click();
|
await page.getByRole("button", { name: "生成公式工作簿" }).click();
|
||||||
const formulaDialog = page.getByRole("dialog", { name: "生成公式工作簿" });
|
const formulaDialog = page.getByRole("dialog", { name: "生成公式工作簿" });
|
||||||
await formulaDialog.getByRole("checkbox").check();
|
await formulaDialog.getByRole("checkbox").check();
|
||||||
await formulaDialog.getByLabel("列计算描述").fill("新增收入成本差,缺失或零分母显示不可用");
|
await formulaDialog
|
||||||
|
.getByLabel("列计算描述")
|
||||||
|
.fill("新增收入成本差,缺失或零分母显示不可用");
|
||||||
await formulaDialog.getByRole("button", { name: "发送描述" }).click();
|
await formulaDialog.getByRole("button", { name: "发送描述" }).click();
|
||||||
await expect(formulaDialog).toContainText("缺失值和零分母如何处理?");
|
await expect(formulaDialog).toContainText("缺失值和零分母如何处理?");
|
||||||
await formulaDialog.getByLabel("列计算描述").fill("缺失项和零分母都按不可用处理");
|
await formulaDialog
|
||||||
|
.getByLabel("列计算描述")
|
||||||
|
.fill("缺失项和零分母都按不可用处理");
|
||||||
await formulaDialog.getByRole("button", { name: "发送描述" }).click();
|
await formulaDialog.getByRole("button", { name: "发送描述" }).click();
|
||||||
await formulaDialog.getByRole("button", { name: "确认并生成公式工作簿" }).click();
|
await formulaDialog
|
||||||
await expect(formulaDialog.getByRole("status")).toContainText("生成完成", { timeout: 30000 });
|
.getByRole("button", { name: "确认并生成公式工作簿" })
|
||||||
|
.click();
|
||||||
|
await expect(formulaDialog.getByRole("status")).toContainText("生成完成", {
|
||||||
|
timeout: 30000,
|
||||||
|
});
|
||||||
const formulaDownload = page.waitForEvent("download");
|
const formulaDownload = page.waitForEvent("download");
|
||||||
await formulaDialog.getByRole("button", { name: "下载公式工作簿" }).click();
|
await formulaDialog.getByRole("button", { name: "下载公式工作簿" }).click();
|
||||||
expect((await formulaDownload).suggestedFilename()).toBe("traceiq-formula-analysis.xlsx");
|
expect((await formulaDownload).suggestedFilename()).toBe(
|
||||||
|
"traceiq-formula-analysis.xlsx",
|
||||||
|
);
|
||||||
await formulaDialog.getByLabel("关闭").click();
|
await formulaDialog.getByLabel("关闭").click();
|
||||||
});
|
});
|
||||||
test("手机:登录、总览、设置、Copilot 布局无横向溢出", async ({ page }) => {
|
test("手机:登录、总览、设置、Copilot 布局无横向溢出", async ({ page }) => {
|
||||||
|
|||||||
Reference in New Issue
Block a user