重构 Wave3 详情与移动体验(05-28-frontend-ux-wave3-business-mobile)

这个提交包含在:
2026-05-29 07:57:17 +08:00
父节点 85c298e25a
当前提交 2b5fd6b1b7
共修改 17 个文件,包含 1087 行新增和 32 行删除
@@ -264,6 +264,235 @@ const notifications = [
},
];
const projectDetail = {
id: "project-wave3",
projectNo: "HYB-W3",
name: "OA 试运行",
clientName: "海音达内部",
clientAlias: "海音达",
ownerEmployeeId: visualMe.id,
managerEmployeeId: visualMe.id,
status: "active",
startsAt: "2026-05-20",
targetEndAt: "2026-06-03",
actualEndAt: null,
summary: "真实试运行阶段,重点验证审批、报销、合同台账和移动入口。",
isConfidential: true,
createdAt: "2026-05-20T09:00:00.000Z",
updatedAt: "2026-05-27T09:30:00.000Z",
owner: { id: visualMe.id, employeeNo: visualMe.employeeNo, name: visualMe.name },
manager: { id: visualMe.id, employeeNo: visualMe.employeeNo, name: visualMe.name },
counts: {
members: 2,
milestones: 2,
tasks: 3,
risks: 1,
reimbursements: 1,
purchaseRequests: 1,
travelRequests: 0,
todoItems: 2,
weeklyReports: 1,
communicationLogs: 1,
closureChecklistItems: 3,
contracts: 1,
deliveries: 1,
documentRefs: 1,
},
members: [
{
id: "member-wave3",
employeeId: visualMe.id,
role: "manager",
joinedAt: "2026-05-20",
leftAt: null,
employee: { id: visualMe.id, employeeNo: visualMe.employeeNo, name: visualMe.name },
},
],
milestones: [
{
id: "milestone-wave3",
title: "公网试运行准备",
description: null,
status: "in_progress",
dueDate: "2026-05-29",
completedAt: null,
ownerEmployeeId: visualMe.id,
sortOrder: 1,
},
],
tasks: [
{
id: "project-task-wave3",
title: "整理试运行反馈",
description: "跟进移动端和合同台账反馈",
status: "doing",
priority: "high",
assigneeEmployeeId: visualMe.id,
dueDate: "2026-05-30",
blockedReason: null,
createdBy: visualMe.id,
assignee: { id: visualMe.id, employeeNo: visualMe.employeeNo, name: visualMe.name },
creator: { id: visualMe.id, employeeNo: visualMe.employeeNo, name: visualMe.name },
createdAt: "2026-05-27T09:00:00.000Z",
updatedAt: "2026-05-27T09:10:00.000Z",
},
],
risks: [
{
id: "risk-wave3",
title: "移动端首屏信息过多",
detail: "需要保持办理摘要和主要动作优先。",
level: "medium",
status: "monitoring",
ownerEmployeeId: visualMe.id,
owner: { id: visualMe.id, employeeNo: visualMe.employeeNo, name: visualMe.name },
createdAt: "2026-05-27T09:00:00.000Z",
updatedAt: "2026-05-27T09:00:00.000Z",
},
],
communicationSummary: {
totalCount: 1,
latestHappenedAt: "2026-05-27T10:00:00.000Z",
followUpDueTodayCount: 1,
overdueFollowUpCount: 0,
},
closureSummary: {
totalCount: 3,
pendingCount: 1,
waivedCount: 0,
warned: true,
facts: {
openRiskCount: 1,
openReimbursementCount: 1,
openTravelCount: 0,
deliveryRecordCount: 1,
contractCount: 1,
confidentialDocRefCount: 1,
},
},
capabilities: {
communicationLogEnabled: true,
closureChecklistEnabled: true,
},
};
const contractRegistryItem = {
id: "contract-wave3",
contractNo: "HT-2026-W3",
projectId: projectDetail.id,
contractType: "service",
counterpartyName: "北京示例服务有限公司",
summary: "OA 试运行服务协议台账摘要",
amount: "30000.00",
currency: "CNY",
signedDate: "2026-05-20",
effectiveDate: "2026-05-21",
expiryDate: "2026-06-20",
confidentialityLevel: "internal",
archiveLocation: "离线档案柜 A-01",
offlineArchiveNo: "OFF-2026-W3",
ownerEmployeeId: visualMe.id,
status: "active",
persistedStatus: "active",
createdAt: "2026-05-20T09:00:00.000Z",
updatedAt: "2026-05-27T09:00:00.000Z",
owner: { id: visualMe.id, employeeNo: visualMe.employeeNo, name: visualMe.name },
project: { id: projectDetail.id, projectNo: projectDetail.projectNo, name: projectDetail.name },
};
const purchaseDetail = {
id: "purchase-wave3",
requestNo: "CG20260527001",
applicantId: visualMe.id,
submitterId: visualMe.id,
departmentId: "dept-wave3",
legalEntityId: "legal-wave3",
costCenterId: "cost-wave3",
supplierId: "supplier-wave3",
documentCategoryId: "category-wave3",
budgetPoolId: "budget-wave3",
projectId: projectDetail.id,
title: "试运行办公物料采购",
reason: "补齐试运行期间需要的标签纸和档案用品。",
currencyCode: "CNY",
totalAmount: "1280.00",
expectedDeliveryDate: "2026-05-30",
status: "reviewing",
createdAt: "2026-05-27T08:30:00.000Z",
updatedAt: "2026-05-27T09:30:00.000Z",
applicant: { id: visualMe.id, employeeNo: visualMe.employeeNo, name: visualMe.name },
submitter: { id: visualMe.id, employeeNo: visualMe.employeeNo, name: visualMe.name },
department: { id: "dept-wave3", name: "研发部" },
legalEntity: { id: "legal-wave3", code: "HYD", name: "海音达" },
costCenter: { id: "cost-wave3", code: "RD", name: "研发中心", legalEntityId: "legal-wave3" },
supplier: { id: "supplier-wave3", code: "SUP-W3", name: "北京示例服务有限公司" },
documentCategory: { id: "category-wave3", code: "OFFICE", name: "办公用品", entityType: "purchase_request" },
budgetPool: { id: "budget-wave3", code: "OPS", name: "运营预算" },
project: { id: projectDetail.id, projectNo: projectDetail.projectNo, name: projectDetail.name, status: projectDetail.status },
items: [
{
id: "purchase-item-wave3",
description: "档案标签纸",
specification: "A4 / 100 张",
quantity: "2",
unitPrice: "640.00",
lineAmount: "1280.00",
isAssetCandidate: false,
createdAt: "2026-05-27T08:30:00.000Z",
updatedAt: "2026-05-27T08:30:00.000Z",
},
],
attachments: [
{
id: "purchase-att-wave3",
originalName: "采购说明.pdf",
mimeType: "application/pdf",
fileSize: 64000,
fileSha256: "purchase-hash-wave3",
uploadedBy: visualMe.id,
createdAt: "2026-05-27T08:40:00.000Z",
uploader: { id: visualMe.id, employeeNo: visualMe.employeeNo, name: visualMe.name },
governanceStatus: "approved",
sensitivityLevel: "internal",
attachmentType: "supporting_material",
downloadPolicy: "reason_required",
reviewedBy: visualMe.id,
reviewedAt: "2026-05-27T08:50:00.000Z",
reviewNote: "已复核",
archivedBy: null,
archivedAt: null,
archiveNote: null,
},
],
pendingWorkflow: {
instanceId: "workflow-purchase-wave3",
currentStatus: "reviewing",
currentStepId: "manager-review",
task: {
id: "task-purchase-wave3",
stepId: "manager-review",
stepName: "主管审批",
assigneeId: visualMe.id,
assigneeRole: "manager",
},
},
approvalHistory: [
{
id: "purchase-history-wave3",
taskId: "task-purchase-wave3",
actorId: visualMe.id,
actorName: visualMe.name,
actorEmployeeNo: visualMe.employeeNo,
action: "submit",
comment: "请审批",
createdAt: "2026-05-27T09:00:00.000Z",
},
],
archiveOrder: null,
permissions: { canEditDraft: false, canSubmit: false, canWithdraw: false, canArchiveOrder: false },
riskFlags: [],
};
function envelope<T>(data: T) {
return { requestId: "mock-wave3", data };
}
@@ -449,8 +678,235 @@ async function mockWave3Apis(page: Page) {
return;
}
if (pathName === "/api/projects/project-wave3") {
await fulfillJson(route, { item: projectDetail });
return;
}
if (pathName === "/api/projects/project-wave3/members") {
await fulfillJson(route, { items: projectDetail.members });
return;
}
if (pathName === "/api/projects/project-wave3/milestones") {
await fulfillJson(route, { items: projectDetail.milestones });
return;
}
if (pathName === "/api/projects/project-wave3/tasks") {
await fulfillJson(route, { items: projectDetail.tasks });
return;
}
if (pathName === "/api/projects/project-wave3/risks") {
await fulfillJson(route, { items: projectDetail.risks });
return;
}
if (pathName === "/api/projects/project-wave3/expenses/summary") {
await fulfillJson(route, {
totalAmount: reimbursementDetail.totalAmount,
items: [
{
id: reimbursementDetail.id,
formNumber: reimbursementDetail.formNumber,
status: reimbursementDetail.status,
totalAmount: reimbursementDetail.totalAmount,
reimbursementDate: reimbursementDetail.reimbursementDate,
updatedAt: "2026-05-27T09:20:00.000Z",
},
],
});
return;
}
if (pathName === "/api/projects/project-wave3/travel/summary") {
await fulfillJson(route, { totalAmount: "0.00", items: [] });
return;
}
if (pathName === "/api/projects/project-wave3/purchase-requests/summary") {
await fulfillJson(route, {
totalAmount: purchaseDetail.totalAmount,
items: [
{
id: purchaseDetail.id,
requestNo: purchaseDetail.requestNo,
title: purchaseDetail.title,
status: purchaseDetail.status,
totalAmount: purchaseDetail.totalAmount,
updatedAt: purchaseDetail.updatedAt,
},
],
});
return;
}
if (pathName === "/api/projects/project-wave3/weekly-reports") {
await fulfillJson(route, {
items: [
{
id: "weekly-link-wave3",
weeklyReportId: "weekly-wave3",
weekStart: "2026-05-25",
status: "submitted",
employee: { id: visualMe.id, employeeNo: visualMe.employeeNo, name: visualMe.name },
progressText: "完成 Wave3 视觉复核",
riskText: null,
nextStepText: "继续公网验证",
updatedAt: "2026-05-27T09:20:00.000Z",
},
],
});
return;
}
if (pathName === "/api/projects/project-wave3/deliveries") {
await fulfillJson(route, {
items: [
{
id: "delivery-wave3",
title: "试运行准备记录",
recordType: "stage_acceptance",
summary: "完成页面体验基线复核。",
occurredAt: "2026-05-27",
owner: { id: visualMe.id, employeeNo: visualMe.employeeNo, name: visualMe.name },
createdAt: "2026-05-27T09:20:00.000Z",
},
],
});
return;
}
if (pathName === "/api/projects/project-wave3/document-refs") {
await fulfillJson(route, {
items: [
{
id: "doc-ref-wave3",
title: "试运行资料索引",
documentNo: "DOC-W3",
confidentialityLevel: "internal",
storageType: "offline_archive",
storagePath: "离线档案柜 A-01",
offlineArchiveNo: "OFF-2026-W3",
updatedAt: "2026-05-27T09:00:00.000Z",
},
],
});
return;
}
if (pathName === "/api/projects/project-wave3/communication-logs") {
await fulfillJson(route, {
items: [
{
id: "comm-wave3",
happenedAt: "2026-05-27T10:00:00.000Z",
channel: "meeting",
externalContactName: "试运行负责人",
externalOrgName: "海音达",
summary: "确认先优化详情页和移动入口。",
nextAction: "继续收集反馈",
nextFollowUpDate: "2026-05-29",
createdBy: visualMe.id,
createdAt: "2026-05-27T10:10:00.000Z",
updatedAt: "2026-05-27T10:10:00.000Z",
canEdit: true,
creator: { id: visualMe.id, employeeNo: visualMe.employeeNo, name: visualMe.name },
},
],
});
return;
}
if (pathName === "/api/projects/project-wave3/closure-checklist") {
await fulfillJson(route, {
items: [
{
id: "closure-wave3",
itemKey: "contract",
title: "合同归档位置已登记",
status: "pending",
note: null,
sortOrder: 1,
completedAt: null,
completedBy: null,
canEdit: true,
completer: null,
},
],
});
return;
}
if (pathName === "/api/projects") {
await fulfillJson(route, { items: [reimbursementDetail.project] });
await fulfillJson(route, {
items: [
{
id: projectDetail.id,
projectNo: projectDetail.projectNo,
name: projectDetail.name,
clientName: projectDetail.clientName,
clientAlias: projectDetail.clientAlias,
status: projectDetail.status,
startsAt: projectDetail.startsAt,
targetEndAt: projectDetail.targetEndAt,
actualEndAt: projectDetail.actualEndAt,
summary: projectDetail.summary,
isConfidential: projectDetail.isConfidential,
owner: projectDetail.owner,
manager: projectDetail.manager,
counts: {
members: projectDetail.counts.members,
milestones: projectDetail.counts.milestones,
tasks: projectDetail.counts.tasks,
risks: projectDetail.counts.risks,
overdueMilestones: 0,
highPriorityTasks: 1,
openRisks: projectDetail.closureSummary.facts.openRiskCount,
},
updatedAt: projectDetail.updatedAt,
},
],
});
return;
}
if (pathName === "/api/employees") {
await fulfillJson(route, {
page: 1,
pageSize: 100,
total: 1,
items: [
{
id: visualMe.id,
employeeNo: visualMe.employeeNo,
name: visualMe.name,
email: visualMe.email,
phone: visualMe.phone,
status: "active",
},
],
});
return;
}
if (pathName === "/api/contracts-registry/expiring") {
await fulfillJson(route, {
days: Number(url.searchParams.get("days") ?? 30),
total: 1,
items: [contractRegistryItem],
});
return;
}
if (pathName === "/api/contracts-registry") {
await fulfillJson(route, { items: [contractRegistryItem] });
return;
}
if (pathName === "/api/purchase-requests/purchase-wave3") {
await fulfillJson(route, purchaseDetail);
return;
}
@@ -603,21 +1059,34 @@ test("Wave3 PC 详情和通知中心视觉截图", async ({ page }) => {
await page.goto("/reimbursements/reim-wave3");
await expect(page.getByRole("heading", { name: /报销详情/ })).toBeVisible();
await expect(page.getByText("办理摘要")).toBeVisible();
await expect(page.getByTestId("reimbursement-summary")).toBeVisible();
await expect(page.getByTestId("reimbursement-payment-status")).toBeVisible();
await page.screenshot({ path: screenshotPath("01-pc-reimbursement-detail.png"), fullPage: true });
await page.goto("/workflow/tasks/task-wave3");
await expect(page.getByRole("heading", { name: /任务详情/ })).toBeVisible();
await expect(page.getByTestId("workflow-task-next-action")).toBeVisible();
await expect(page.getByText("审批动作")).toBeVisible();
await expect(page.getByTestId("workflow-task-overview")).toBeVisible();
await page.screenshot({ path: screenshotPath("02-pc-workflow-detail.png"), fullPage: true });
await page.goto("/projects/project-wave3");
await expect(page.getByRole("heading", { name: /HYB-W3/ })).toBeVisible();
await expect(page.getByTestId("project-current-command")).toBeVisible();
await expect(page.locator("table").first()).toBeVisible();
await page.screenshot({ path: screenshotPath("03-pc-project-detail.png"), fullPage: true });
await page.goto("/contracts-registry?projectId=project-wave3");
await expect(page.getByRole("heading", { name: "合同台账", exact: true })).toBeVisible();
await expect(page.getByTestId("contracts-registry-summary")).toBeVisible();
await expect(page.getByTestId("contracts-registry-row")).toBeVisible();
await page.screenshot({ path: screenshotPath("04-pc-contracts-registry.png"), fullPage: true });
await page.goto("/notifications");
await expect(page.getByRole("heading", { name: "通知" })).toBeVisible();
await expect(page.getByTestId("notification-row").first()).toBeVisible();
await expect(page.locator("table")).toHaveCount(0);
await page.screenshot({ path: screenshotPath("03-pc-notifications.png"), fullPage: true });
await page.screenshot({ path: screenshotPath("05-pc-notifications.png"), fullPage: true });
});
test("Wave3 移动关键页视觉截图", async ({ page }) => {
@@ -632,35 +1101,53 @@ test("Wave3 移动关键页视觉截图", async ({ page }) => {
await page.goto("/mobile/reimbursements");
await expect(page.getByRole("heading", { name: "移动报销" })).toBeVisible();
await expect(page.getByTestId("mobile-reimbursements-focus")).toBeVisible();
await expect(page.getByTestId("mobile-reimbursements-list")).toBeVisible();
await expect(page.locator("table")).toHaveCount(0);
await expectNoHorizontalOverflow(page);
await page.screenshot({ path: screenshotPath("05-mobile-reimbursements.png"), fullPage: true });
await page.goto("/mobile/reimbursements/reim-wave3");
await expect(page.getByTestId("mobile-reimbursement-detail-focus")).toBeVisible();
await expect(page.getByTestId("mobile-reimbursement-items")).toBeVisible();
await expect(page.locator("table")).toHaveCount(0);
await expectNoHorizontalOverflow(page);
await page.screenshot({ path: screenshotPath("06-mobile-reimbursement-detail.png"), fullPage: true });
await page.goto("/mobile/approvals");
await expect(page.getByRole("heading", { name: "审批", exact: true })).toBeVisible();
await expect(page.getByTestId("mobile-approvals-focus")).toBeVisible();
await expect(page.getByTestId("mobile-approvals-list")).toBeVisible();
await expect(page.locator("table")).toHaveCount(0);
await expectNoHorizontalOverflow(page);
await page.screenshot({ path: screenshotPath("06-mobile-approvals.png"), fullPage: true });
await page.screenshot({ path: screenshotPath("07-mobile-approvals.png"), fullPage: true });
await page.goto("/mobile/approvals/task-wave3");
await expect(page.getByTestId("mobile-approvals-detail")).toBeVisible();
await expect(page.getByTestId("mobile-approvals-detail-focus")).toBeVisible();
await expect(page.locator("table")).toHaveCount(0);
await expectNoHorizontalOverflow(page);
await page.screenshot({ path: screenshotPath("07-mobile-approval-detail.png"), fullPage: true });
await page.screenshot({ path: screenshotPath("08-mobile-approval-detail.png"), fullPage: true });
await page.goto("/mobile/notifications");
await expect(page.getByTestId("mobile-notifications-focus")).toBeVisible();
await expect(page.getByTestId("mobile-notifications-list")).toBeVisible();
await expect(page.getByTestId("mobile-notification-card").first()).toBeVisible();
await expect(page.locator("table")).toHaveCount(0);
await expectNoHorizontalOverflow(page);
await page.screenshot({ path: screenshotPath("08-mobile-notifications.png"), fullPage: true });
await page.screenshot({ path: screenshotPath("09-mobile-notifications.png"), fullPage: true });
await page.goto("/mobile/purchase-requests/purchase-wave3");
await expect(page.getByTestId("mobile-purchase-detail-summary")).toBeVisible();
await expect(page.getByTestId("mobile-purchase-detail-items")).toBeVisible();
await expect(page.locator("table")).toHaveCount(0);
await expectNoHorizontalOverflow(page);
await page.screenshot({ path: screenshotPath("10-mobile-purchase-detail.png"), fullPage: true });
await page.goto("/mobile/me");
await expect(page.getByRole("heading", { name: "我的" })).toBeVisible();
await expect(page.getByTestId("mobile-more-links")).toBeVisible();
await expect(page.locator("table")).toHaveCount(0);
await expectNoHorizontalOverflow(page);
await page.screenshot({ path: screenshotPath("09-mobile-me.png"), fullPage: true });
await page.screenshot({ path: screenshotPath("11-mobile-me.png"), fullPage: true });
});