实现报销材料合并预览与差旅概要(07-27-yang-jun-reimbursement-readiness)
ci-e2e / e2e (push) Canceled after 0s
ci-minimal / check-minimal (push) Canceled after 0s

这个提交包含在:
2026-08-01 09:25:44 +08:00
父节点 3d9ec4c5a7
当前提交 da35591bd5
共修改 29 个文件,包含 907 行新增和 527 行删除
@@ -180,6 +180,13 @@ function buildReimbursementDetail(state: ReturnType<typeof buildReimbursementSta
reimbursementDate: "2026-05-08",
totalAmount: "1683.00",
description: "客户招待报销",
travelSummary: {
isTravel: true,
purpose: "合成差旅概要",
location: "测试地点",
startDate: "2026-05-07",
endDate: "2026-05-08",
},
projectId: null,
linkedTravelRequestId: null,
linkedTravelRequest: null,
@@ -358,26 +365,19 @@ async function mockApis(page: Page, state: ReturnType<typeof buildReimbursementS
}
if (pathName === `/api/reimbursements/${reimbursementId}/materials/preview-bundle` && request.method() === "POST") {
const pages = [
...state.invoices.map((_, index) => ({
url: `https://oa.hyinda.com/api/reimbursements/${reimbursementId}/materials/preview/77777777-7777-4777-8777-777777777777/${index}?previewToken=invoice-${index}`,
materialType: "invoice",
materialIndex: index + 1,
pageNumber: 1,
})),
...state.attachments.map((_, index) => ({
url: `https://oa.hyinda.com/api/reimbursements/${reimbursementId}/materials/preview/77777777-7777-4777-8777-777777777777/${state.invoices.length + index}?previewToken=attachment-${index}`,
materialType: "attachment",
materialIndex: index + 1,
pageNumber: 1,
})),
];
await fulfillJson(route, { expiresAt: "2026-07-31T10:02:00.000Z", pages });
await fulfillJson(route, {
expiresAt: "2026-07-31T10:02:00.000Z",
signedUrl: `https://oa.hyinda.com/api/reimbursements/${reimbursementId}/materials/preview/77777777-7777-4777-8777-777777777777?previewToken=merged-pdf`,
originalName: "reimbursement-materials-preview.pdf",
mimeType: "application/pdf",
fileSize: 8192,
pageCount: state.invoices.length + state.attachments.length,
});
return;
}
if (pathName.includes(`/api/reimbursements/${reimbursementId}/materials/preview/`) && request.method() === "GET") {
await route.fulfill({ status: 200, contentType: "image/png", body: Buffer.from("synthetic-preview-image") });
await route.fulfill({ status: 200, contentType: "application/pdf", body: Buffer.from("%PDF synthetic-preview") });
return;
}
@@ -658,14 +658,12 @@ test.describe("R111 国内报销发票与附件双端体验", () => {
});
await page.getByRole("button", { name: "上传附件" }).click();
await expect(page.getByText("付款截图.png")).toBeVisible();
await expect(page.getByTestId("reimbursement-travel-summary")).toContainText("合成差旅概要");
await expect(page.getByTestId("reimbursement-travel-summary")).toContainText("测试地点");
await expect(page.getByTestId("reimbursement-materials")).toContainText("发票 2 · 补充附件 1");
await page.getByTestId("reimbursement-materials-open-all").click();
await page.waitForURL(new RegExp(`/reimbursements/${reimbursementId}/materials$`));
await expect(page.getByTestId("reimbursement-material-viewer")).toContainText("共 3 页");
await expect(page.getByRole("img", { name: /预览/ })).toHaveCount(3);
await page.getByRole("button", { name: "返回报销单" }).click();
await page.waitForURL(new RegExp(`/reimbursements/${reimbursementId}$`));
expect(await page.evaluate(() => (window as typeof window & { __r111OpenCalls: unknown[][] }).__r111OpenCalls.length)).toBe(0);
await expect.poll(() => page.evaluate(() => (window as typeof window & { __r111OpenCalls: unknown[][] }).__r111OpenCalls.length)).toBe(1);
expect(await page.evaluate(() => (window as typeof window & { __r111OpenCalls: unknown[][] }).__r111OpenCalls[0]?.[0])).toContain("previewToken=merged-pdf");
const bodyText = await page.locator("body").innerText();
expect(bodyText).not.toContain("绑定整单");
@@ -783,6 +781,8 @@ test.describe("R111 国内报销发票与附件双端体验", () => {
});
await mobile.page.getByTestId("mobile-reimbursement-materials").getByRole("button", { name: "上传附件" }).click();
await expect(mobile.page.getByText("付款截图.png")).toBeVisible();
await expect(mobile.page.getByTestId("mobile-reimbursement-travel-summary")).toContainText("合成差旅概要");
await expect(mobile.page.getByTestId("mobile-reimbursement-travel-summary")).toContainText("测试地点");
await expect(mobile.page.getByTestId("mobile-reimbursement-materials")).toContainText("发票 2 · 补充附件 1");
const openAllButton = mobile.page.getByTestId("mobile-reimbursement-materials-open-all");
const openAllTarget = await openAllButton.boundingBox();
@@ -790,19 +790,16 @@ test.describe("R111 国内报销发票与附件双端体验", () => {
await openAllButton.click();
await expect.poll(() => mobile.page.evaluate(() => (
window as typeof window & { __r111BridgeCalls: Array<{ name: string }> }
).__r111BridgeCalls.filter((call) => call.name === "imagePreview").length)).toBe(1);
).__r111BridgeCalls.filter((call) => call.name === "previewFile").length)).toBe(1);
const nativePreviewCalls = await mobile.page.evaluate(() => (
window as typeof window & { __r111BridgeCalls: Array<{ name: string; params: Record<string, unknown> }> }
).__r111BridgeCalls.filter((call) => call.name === "imagePreview"));
).__r111BridgeCalls.filter((call) => call.name === "previewFile"));
expect(nativePreviewCalls).toEqual([
expect.objectContaining({
params: expect.objectContaining({
current: expect.stringContaining(`/materials/preview/`),
urls: expect.arrayContaining([
expect.stringContaining("previewToken=invoice-0"),
expect.stringContaining("previewToken=invoice-1"),
expect.stringContaining("previewToken=attachment-0"),
]),
url: expect.stringContaining("previewToken=merged-pdf"),
name: "reimbursement-materials-preview.pdf",
size: 8192,
}),
}),
]);