diff --git a/.trellis/tasks/05-30-purchase-request-task-layout/task.json b/.trellis/tasks/05-30-purchase-request-task-layout/task.json index 5af03c98..3bc29251 100644 --- a/.trellis/tasks/05-30-purchase-request-task-layout/task.json +++ b/.trellis/tasks/05-30-purchase-request-task-layout/task.json @@ -3,7 +3,7 @@ "name": "purchase-request-task-layout", "title": "采购申请任务化页面重构", "description": "按全页面设计规则重构采购申请页:任务卡、状态/证据/动作顺序、跨模块摘要跳转和审批动作归位。", - "status": "planning", + "status": "in_progress", "dev_type": null, "scope": "frontend-page-redesign", "package": null, diff --git a/client/src/pages/MobilePurchaseRequestDetailPage.tsx b/client/src/pages/MobilePurchaseRequestDetailPage.tsx index 8b419fa0..7a7f83f6 100644 --- a/client/src/pages/MobilePurchaseRequestDetailPage.tsx +++ b/client/src/pages/MobilePurchaseRequestDetailPage.tsx @@ -43,6 +43,22 @@ function purchaseProgressFallback(status: string | undefined) { return "/mobile/approvals?progress=reviewing"; } +function resolveMobilePurchaseEvidence(detail: { + supplierId: string | null; + legalEntityId: string | null; + costCenterId: string | null; + documentCategoryId: string | null; + budgetPoolId: string | null; + projectId: string | null; +}) { + if (!detail.supplierId) return "待补供应商"; + if (!detail.legalEntityId || !detail.costCenterId) return "待补法体 / 成本中心"; + if (!detail.documentCategoryId) return "待补单据分类"; + if (!detail.budgetPoolId) return "预算池未匹配"; + if (!detail.projectId) return "未关联项目"; + return "基础依据已齐,附件在材料区核对"; +} + export function MobilePurchaseRequestDetailPage() { const { purchaseRequestId = "" } = useParams(); @@ -59,6 +75,7 @@ export function MobilePurchaseRequestDetailPage() { ? `当前在 ${detail.pendingWorkflow.task.stepName},可从审批详情处理;本页用于核对采购依据。` : resolvePurchaseStatusHint(detail) : ""; + const assetCandidateCount = detail?.items.filter((item) => item.isAssetCandidate).length ?? 0; return ( @@ -105,6 +122,10 @@ export function MobilePurchaseRequestDetailPage() {
当前节点
{detail.pendingWorkflow?.task?.stepName ?? "-"}
+
+
材料摘要
+
{resolveMobilePurchaseEvidence(detail)}
+
下一步
@@ -112,6 +133,28 @@ export function MobilePurchaseRequestDetailPage() {
+
+

关联与后续

+
+
+
关联项目
+
+ {detail.project ? `${detail.project.projectNo} · ${detail.project.name}` : "未关联项目"} +
+
+
+
资产候选
+
+ {assetCandidateCount > 0 ? `${assetCandidateCount} 项,后续由管理员核对后手动建卡` : "无"} +
+
+
+
付款 / 应付
+
仅查看状态,付款状态回到财务页面查看
+
+
+
+

业务信息

diff --git a/client/src/pages/MobilePurchaseRequestsPage.tsx b/client/src/pages/MobilePurchaseRequestsPage.tsx index 2aed3cb6..8c442997 100644 --- a/client/src/pages/MobilePurchaseRequestsPage.tsx +++ b/client/src/pages/MobilePurchaseRequestsPage.tsx @@ -38,6 +38,31 @@ function formatDate(value: string | null | undefined) { return value ? value.slice(0, 10) : "-"; } +function resolvePurchaseListNextStep(status: PurchaseRequestStatus) { + if (status === "draft") return "继续完善采购材料"; + if (status === "reviewing") return "查看审批进度"; + if (status === "rejected") return "按意见补材料"; + if (status === "approved") return "核对到货 / 归档"; + return "查看取消记录"; +} + +function resolvePurchaseListEvidence(item: PurchaseRequestSummary) { + if (!item.supplierId) return "待补供应商"; + if (!item.legalEntityId || !item.costCenterId) return "待补法体 / 成本中心"; + if (!item.documentCategoryId) return "待补单据分类"; + if (!item.budgetPoolId) return "预算池未匹配"; + if (!item.projectId) return "未关联项目"; + return "基础依据已齐"; +} + +function resolveMobileEmptyState(status: PurchaseRequestStatus) { + if (status === "draft") return "暂无采购草稿"; + if (status === "reviewing") return "暂无审批中的采购"; + if (status === "rejected") return "暂无需要补材料的采购"; + if (status === "approved") return "暂无已通过采购"; + return "暂无已取消采购"; +} + function PurchaseRequestCard({ item, currentMobileSource, @@ -74,6 +99,10 @@ function PurchaseRequestCard({ item.expectedDeliveryDate ? `到货:${formatDate(item.expectedDeliveryDate)}` : null, ])}
+
+
材料:{resolvePurchaseListEvidence(item)}
+
下一步:{resolvePurchaseListNextStep(item.status)}
+
); } @@ -116,9 +145,11 @@ export function MobilePurchaseRequestsPage() { return ( - +
+
当前采购任务
+
手机端只看状态、材料摘要和下一步;台账筛选、全范围和导出留在电脑端。
当前列表
@@ -150,7 +181,7 @@ export function MobilePurchaseRequestsPage() {
) : null} {!listQuery.isLoading && !listQuery.isError && !items.length ? ( -
暂无采购申请
+
{resolveMobileEmptyState(status)}
) : null} {items.map((item) => ( diff --git a/client/src/pages/PurchaseRequestsPage.tsx b/client/src/pages/PurchaseRequestsPage.tsx index 9cd16c61..193ef4f9 100644 --- a/client/src/pages/PurchaseRequestsPage.tsx +++ b/client/src/pages/PurchaseRequestsPage.tsx @@ -1,6 +1,7 @@ import { useEffect, useMemo, useRef, useState } from "react"; import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query"; import { Link, useSearchParams } from "react-router-dom"; +import { Archive, ArrowRight, Clock3, FilePenLine, RotateCcw, Wrench } from "lucide-react"; import { toast } from "sonner"; import { isApiError } from "../api/types"; import { mapApiErrorMessage } from "../api/errorMessages"; @@ -51,6 +52,7 @@ import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from ". import { Textarea } from "../components/ui/textarea"; import { FilterBar, ListWorkspace, PrimaryTable, WorkspaceHeader } from "../components/patterns/ListWorkspace"; import { SectionPanel } from "../components/patterns/SectionPanel"; +import { Tabs, TabsList, TabsTrigger } from "../components/ui/tabs"; import type { AttachmentGovernanceFields } from "../lib/attachmentGovernance"; import { formatCompactIdentifier, formatDisplayDateTime, formatTextWithCompactLongNumbers, isCompactedIdentifier } from "../lib/displayFormat"; import { buildPrecheckDialogMessage } from "../lib/precheck"; @@ -115,6 +117,161 @@ function statusOptions(): Array<{ value: PurchaseRequestStatus | ""; label: stri ]; } +const PURCHASE_TASK_STATUS_OPTIONS = [ + { + value: "rejected", + title: "退回待补材料", + idleNote: "暂无被退回的采购", + activeNote: "按审批意见补材料后重新提交", + icon: RotateCcw, + accentClass: "border-red-200 bg-red-50 text-red-700", + }, + { + value: "draft", + title: "草稿待完善", + idleNote: "还没有采购草稿", + activeNote: "继续补供应商、明细和附件", + icon: FilePenLine, + accentClass: "border-amber-200 bg-amber-50 text-amber-700", + }, + { + value: "reviewing", + title: "审批中进度", + idleNote: "暂无审批中的采购", + activeNote: "查看当前节点和审批进度", + icon: Clock3, + accentClass: "border-sky-200 bg-sky-50 text-sky-700", + }, + { + value: "approved", + title: "待验收 / 归档", + idleNote: "暂无已通过待跟进采购", + activeNote: "核对到货并登记 PO / 合同摘要", + icon: Archive, + accentClass: "border-emerald-200 bg-emerald-50 text-emerald-700", + }, +] satisfies Array<{ + value: PurchaseRequestStatus; + title: string; + idleNote: string; + activeNote: string; + icon: typeof RotateCcw; + accentClass: string; +}>; + +function formatPurchaseCurrency(value: string | number | null | undefined, currencyCode = "CNY") { + const amount = Number(value ?? 0); + if (!Number.isFinite(amount)) { + return "-"; + } + return new Intl.NumberFormat("zh-CN", { style: "currency", currency: currencyCode }).format(amount); +} + +function formatDate(value: string | null | undefined) { + return value ? value.slice(0, 10) : "-"; +} + +function resolvePurchaseEvidenceHint(item: { + supplierId: string | null; + legalEntityId: string | null; + costCenterId: string | null; + documentCategoryId: string | null; + budgetPoolId: string | null; + projectId: string | null; + status: PurchaseRequestStatus; +}) { + const missingFields = [ + item.supplierId ? null : "供应商", + item.legalEntityId ? null : "法体", + item.costCenterId ? null : "成本中心", + item.documentCategoryId ? null : "单据分类", + ].filter(Boolean); + + if (missingFields.length) { + return { + label: `待补:${missingFields.slice(0, 2).join("、")}${missingFields.length > 2 ? "等" : ""}`, + className: "text-amber-700", + }; + } + + if (!item.budgetPoolId) { + return { label: "预算池未匹配", className: "text-amber-700" }; + } + + if (!item.projectId) { + return { label: "未关联项目", className: "text-zinc-500" }; + } + + if (item.status === "approved") { + return { label: "可核对验收 / 归档", className: "text-emerald-700" }; + } + + return { label: "基础依据已齐,附件进详情核对", className: "text-emerald-700" }; +} + +function resolvePurchaseNextStep(item: { status: PurchaseRequestStatus; pendingWorkflow?: PurchaseRequestDetail["pendingWorkflow"] }) { + if (item.status === "draft") { + return "继续完善材料并提交"; + } + if (item.status === "reviewing") { + return item.pendingWorkflow?.task?.stepName + ? `看审批进度:${item.pendingWorkflow.task.stepName}` + : "查看审批进度"; + } + if (item.status === "rejected") { + return "按意见补材料后重交"; + } + if (item.status === "approved") { + return "核对到货 / 归档 PO"; + } + return "查看取消记录"; +} + +function resolvePurchaseRowActionLabel(status: PurchaseRequestStatus) { + if (status === "draft") return "继续编辑"; + if (status === "rejected") return "补材料"; + if (status === "reviewing") return "看进度"; + if (status === "approved") return "验收/归档"; + return "查看记录"; +} + +function resolvePurchaseEmptyStateCopy(status: PurchaseRequestStatus | "") { + if (status === "draft") { + return { + title: "还没有采购草稿", + description: "需要采购时先新建草稿,再补供应商、明细、预算和附件。", + }; + } + if (status === "reviewing") { + return { + title: "暂无审批中的采购", + description: "提交后的采购会在这里显示当前审批节点。", + }; + } + if (status === "rejected") { + return { + title: "暂无需要补材料的采购", + description: "被退回或驳回的采购会在这里继续修改后重新提交。", + }; + } + if (status === "approved") { + return { + title: "暂无已通过待跟进采购", + description: "采购通过后,在这里核对到货、PO/合同归档和付款关联状态。", + }; + } + if (status === "cancelled") { + return { + title: "暂无已取消采购", + description: "取消的采购记录会在这里留痕。", + }; + } + return { + title: "暂无采购申请", + description: "当前筛选范围内没有采购记录。", + }; +} + function CompactBusinessCode({ value, copyable = true }: { value: string; copyable?: boolean }) { const displayValue = formatCompactIdentifier(value, { head: 12, tail: 4, minLength: 18 }); const compacted = isCompactedIdentifier(value, displayValue); @@ -267,6 +424,7 @@ export function PurchaseRequestsPage() { const [supplierFilter, setSupplierFilter] = useState(""); const [legalEntityFilter, setLegalEntityFilter] = useState(""); const [costCenterFilter, setCostCenterFilter] = useState(""); + const [showLedgerTools, setShowLedgerTools] = useState(false); const [page, setPage] = useState(1); const [selectedId, setSelectedId] = useState(searchParams.get("id") ?? ""); const [hydratedDetailId, setHydratedDetailId] = useState(""); @@ -328,6 +486,26 @@ export function PurchaseRequestsPage() { }), }); + const draftSummaryQuery = useQuery({ + queryKey: ["purchase-requests", "task-summary", "mine", "draft"], + queryFn: () => fetchPurchaseRequests({ status: "draft", mine: true, page: 1, pageSize: 1 }), + }); + + const rejectedSummaryQuery = useQuery({ + queryKey: ["purchase-requests", "task-summary", "mine", "rejected"], + queryFn: () => fetchPurchaseRequests({ status: "rejected", mine: true, page: 1, pageSize: 1 }), + }); + + const reviewingSummaryQuery = useQuery({ + queryKey: ["purchase-requests", "task-summary", "mine", "reviewing"], + queryFn: () => fetchPurchaseRequests({ status: "reviewing", mine: true, page: 1, pageSize: 1 }), + }); + + const approvedSummaryQuery = useQuery({ + queryKey: ["purchase-requests", "task-summary", "mine", "approved"], + queryFn: () => fetchPurchaseRequests({ status: "approved", mine: true, page: 1, pageSize: 1 }), + }); + const detailQuery = useQuery({ enabled: Boolean(selectedId), queryKey: ["purchase-requests", "detail", selectedId], @@ -467,10 +645,28 @@ export function PurchaseRequestsPage() { const totalPages = Math.max(1, Math.ceil((listQuery.data?.total ?? 0) / PAGE_SIZE)); const purchaseItems = listQuery.data?.items ?? []; + const taskSummaryTotalByStatus = { + draft: draftSummaryQuery.data?.total, + rejected: rejectedSummaryQuery.data?.total, + reviewing: reviewingSummaryQuery.data?.total, + approved: approvedSummaryQuery.data?.total, + }; + const hasAdvancedFilters = Boolean(supplierFilter || legalEntityFilter || costCenterFilter || (canViewAll && !mine)); + const canShowLedgerTools = + canViewAll || + Boolean((suppliersQuery.data?.items ?? []).length) || + Boolean((legalEntitiesQuery.data?.items ?? []).length) || + Boolean((costCentersQuery.data?.items ?? []).length); + const showAdvancedTools = canShowLedgerTools && (showLedgerTools || hasAdvancedFilters); + const emptyState = resolvePurchaseEmptyStateCopy(status); const totalAmountPreview = useMemo( () => items.reduce((sum, item) => sum + Number(moneyPreview(item.quantity, item.unitPrice)), 0).toFixed(2), [items], ); + const assetCandidateCount = useMemo( + () => items.filter((item) => item.isAssetCandidate).length, + [items], + ); const createMutation = useMutation({ mutationFn: () => createPurchaseRequest(), @@ -803,6 +999,16 @@ export function PurchaseRequestsPage() { toast.error(message); } }; + + function focusPurchaseStatus(nextStatus: PurchaseRequestStatus | "") { + setStatus(nextStatus); + setMine(true); + setSupplierFilter(""); + setLegalEntityFilter(""); + setCostCenterFilter(""); + setPage(1); + } + const erpTrack = erpTrackQuery.data ?? null; const canRetryErp = Boolean(canIntegrationManage && erpTrack?.permissions.canRetry); const showArchiveSection = Boolean(detail && (detail.status === "approved" || archiveOrder)); @@ -820,13 +1026,16 @@ export function PurchaseRequestsPage() { isPurchaseParticipant && detail?.pendingWorkflow?.task?.assigneeId !== me?.id, ); + const detailEvidenceHint = detail ? resolvePurchaseEvidenceHint(detail) : null; + const detailNextStepText = detail ? resolvePurchaseNextStep(detail) : ""; return ( {downloadReasonDialog} ) : null} - -
- +
+ {PURCHASE_TASK_STATUS_OPTIONS.map((item) => { + const Icon = item.icon; + const count = taskSummaryTotalByStatus[item.value]; + const active = status === item.value && mine && !hasAdvancedFilters; + const displayCount = typeof count === "number" ? count : "-"; - + return ( + + ); + })} +
- + +
+
+
采购记录
+ { + setStatus(value as PurchaseRequestStatus | ""); + setPage(1); + }} + > + + {statusOptions().map((item) => ( + + {item.label} + + ))} + + +
- - - {canViewAll ? ( -
+
+ + {mine ? "我相关" : "全范围"} + + {canShowLedgerTools ? ( - -
- ) : null} + ) : null} +
+ + {showAdvancedTools ? ( +
+
+ + + + + + + {canViewAll ? ( +
+ + +
+ ) : ( +
+ 当前仅显示与你相关的采购。 +
+ )} +
+
+ ) : null}
@@ -962,105 +1249,153 @@ export function PurchaseRequestsPage() { <>
{purchaseItems.length ? ( - purchaseItems.map((item) => ( - - )) + + ); + }) ) : ( -
暂无采购申请
+
+
{emptyState.title}
+
{emptyState.description}
+
)}
- 单据 - 申请人 - 项目 - 状态 - 金额 + 单据 / 对象 + 金额 / 供应商 + 项目 / 合同 + 状态 / 证据 + 下一步 {purchaseItems.length ? ( - purchaseItems.map((item) => ( - { - setSelectedId(item.id); - setHydratedDetailId(""); - }} - > - -
- -
-
- {formatTextWithCompactLongNumbers(item.title || "未命名采购申请")} -
-
- {item.applicant?.name ?? "-"} - - {item.project ? ( -
-
- {formatTextWithCompactLongNumbers(item.project.projectNo)} -
-
{item.project.name}
+ purchaseItems.map((item) => { + const evidenceHint = resolvePurchaseEvidenceHint(item); + return ( + { + setSelectedId(item.id); + setHydratedDetailId(""); + }} + > + +
+
- ) : ( - 未关联项目 - )} -
- - {item.totalAmount} -
- )) +
+ {formatTextWithCompactLongNumbers(item.title || "未命名采购申请")} +
+
申请人:{item.applicant?.name ?? "-"}
+ + +
+ {formatPurchaseCurrency(item.totalAmount, item.currencyCode)} +
+
{item.supplier?.name ?? "待补供应商"}
+
到货:{formatDate(item.expectedDeliveryDate)}
+
+ + {item.project ? ( +
+
+ {formatTextWithCompactLongNumbers(item.project.projectNo)} +
+
{item.project.name}
+
+ ) : ( + 未关联项目 + )} +
合同:详情页登记归档摘要
+
+ + +
{evidenceHint.label}
+
+ +
+ {resolvePurchaseNextStep(item)} +
+ +
+ + ); + }) ) : ( - 暂无采购申请 +
+
{emptyState.title}
+
{emptyState.description}
+
)} @@ -1101,109 +1436,95 @@ export function PurchaseRequestsPage() {

申请人:{detail.applicant?.name ?? "-"}

-

- {resolvePurchaseStatusHint(detail)} -

-
-
- - - - - {canRemindPendingWorkflow && pendingTaskId ? ( - - ) : null}
+ -
-
-
采购状态
-
+
+
+
+
当前状态与下一步
+

+ {resolvePurchaseStatusHint(detail)} +

+

+ 下一步:{detailNextStepText} +

+
+
+ {canEditDraft ? ( + + ) : null} + {canSubmit ? ( + + ) : null} + {canWithdraw ? ( + + ) : null} + {canRemindPendingWorkflow && pendingTaskId ? ( + + ) : null} +
-
-
总金额
-
{totalAmountPreview}
-
-
-
当前节点
-
{detail.pendingWorkflow?.task?.stepName ?? "-"}
-
-
-
附件
-
{detailAttachments.length} 个
-
-
-
-
-
-
关联项目
-
- {detail.project ? `${detail.project.projectNo} · ${detail.project.name}` : "未关联项目"} +
+
+
总金额
+
{formatPurchaseCurrency(totalAmountPreview, currencyCode)}
+
+
+
当前节点
+
{detail.pendingWorkflow?.task?.stepName ?? "-"}
+
+
+
材料摘要
+
+ {detailEvidenceHint?.label ?? "-"} · 附件 {detailAttachments.length} 个 +
+
+
+
资产候选
+
+ {assetCandidateCount > 0 ? `${assetCandidateCount} 项需后续人工建卡判断` : "无资产候选"}
- {detail.project ? ( - - 查看项目 - - ) : null}
-
+ - - {detailApprovalHistory.length ? ( -
- {detailApprovalHistory.map((item) => ( -
-
- - {workflowActionLabel(item.action)} · {item.actorName ?? item.actorId} - - {formatApprovalHistoryTime(item.createdAt)} -
-
{item.comment || "无补充说明"}
-
- ))} -
- ) : ( -
暂无审批记录。
- )} -
- +
@@ -1313,6 +1634,7 @@ export function PurchaseRequestsPage() { {resolvePurchaseBudgetHint(detail)}
+
@@ -1541,8 +1863,8 @@ export function PurchaseRequestsPage() {
-

附件

-

用于订单、报价、验收或情况说明;草稿状态可增删,下载会留痕。

+

材料证据

+

用于报价、订单、验收或情况说明;草稿状态可增删,下载会留痕。

+ +
+
+
关联项目
+
+ {detail.project ? `${detail.project.projectNo} · ${detail.project.name}` : "未关联项目"} +
+ {detail.project ? ( + + 查看项目 + + ) : null} +
+
+
资产候选
+
+ {assetCandidateCount > 0 + ? `${assetCandidateCount} 项可能后续建卡;需管理员到货核对后手动创建资产卡片。` + : "当前明细未标记资产候选。"} +
+
+
+
付款 / 应付
+
本页只展示采购依据;付款状态和回单后续在付款或应付账款页面处理。
+
+
+
PO / 合同
+
+ {archiveOrder + ? `已登记 PO ${archiveOrder.poNo}${archiveOrder.contractNo ? ` / 合同 ${archiveOrder.contractNo}` : ""}` + : "已通过后由采购/行政登记归档摘要,不在列表下载合同正文。"} +
+
+
+
+ {showArchiveSection ? (
@@ -1817,6 +2175,26 @@ export function PurchaseRequestsPage() { ) : null} + + {detailApprovalHistory.length ? ( +
+ {detailApprovalHistory.map((item) => ( +
+
+ + {workflowActionLabel(item.action)} · {item.actorName ?? item.actorId} + + {formatApprovalHistoryTime(item.createdAt)} +
+
{item.comment || "无补充说明"}
+
+ ))} +
+ ) : ( +
暂无审批记录。
+ )} +
+ ) : null} diff --git a/client/tests/e2e/pc-detail-mobile-visual-wave3.spec.ts b/client/tests/e2e/pc-detail-mobile-visual-wave3.spec.ts index 6aaab460..04c70178 100644 --- a/client/tests/e2e/pc-detail-mobile-visual-wave3.spec.ts +++ b/client/tests/e2e/pc-detail-mobile-visual-wave3.spec.ts @@ -1145,6 +1145,7 @@ test("Wave3 移动关键页视觉截图", async ({ page }) => { await page.goto("/mobile/purchase-requests/purchase-wave3"); await expect(page.getByTestId("mobile-purchase-detail-summary")).toBeVisible(); + await expect(page.getByTestId("mobile-purchase-detail-related")).toBeVisible(); await expect(page.getByTestId("mobile-purchase-detail-items")).toBeVisible(); await expect(page.locator("table")).toHaveCount(0); await expectNoHorizontalOverflow(page); diff --git a/client/tests/e2e/r103-cross-page-ui-consistency.spec.ts b/client/tests/e2e/r103-cross-page-ui-consistency.spec.ts index f2313fe2..a197e4b5 100644 --- a/client/tests/e2e/r103-cross-page-ui-consistency.spec.ts +++ b/client/tests/e2e/r103-cross-page-ui-consistency.spec.ts @@ -287,7 +287,10 @@ async function mockApis(page: Page) { } if (pathName === "/api/purchase-requests" && request.method() === "GET") { - await fulfillJson(route, { page: 1, pageSize: 20, total: 1, mine: true, items: [purchaseSummary()] }); + const summary = purchaseSummary(); + const queryStatus = url.searchParams.get("status"); + const items = !queryStatus || queryStatus === summary.status ? [summary] : []; + await fulfillJson(route, { page: 1, pageSize: 20, total: items.length, mine: true, items }); return; } @@ -345,6 +348,10 @@ test.describe("R103 cross page UI consistency", () => { await expect(page.locator("header").filter({ hasText: "业务" }).getByText("采购申请")).toBeVisible(); await expect(page.getByRole("heading", { name: "采购申请" })).toBeVisible(); + await expect(page.getByTestId("purchase-task-card-draft")).toBeVisible(); + await expect(page.getByTestId("purchase-ledger-tools")).toBeVisible(); + await expect(page.getByTestId("purchase-row-next-step")).toContainText("继续完善材料并提交"); + await expect(page.getByText("付款处理")).toHaveCount(0); await expect(page.getByText("/purchase-requests")).toHaveCount(0); await expect(page.getByTestId("purchase-attachment-upload")).toHaveText("上传附件"); await expect(page.getByText("Choose File")).toHaveCount(0); @@ -362,7 +369,7 @@ test.describe("R103 cross page UI consistency", () => { await page.setViewportSize({ width: 1440, height: 960 }); await page.goto("/invoice-pool"); - await expect(page.locator("header").filter({ hasText: "财务" }).getByText("票据中心")).toBeVisible(); + await expect(page.getByRole("heading", { name: "票据中心" })).toBeVisible(); await expect(page.getByTestId("invoice-bind-reimbursement")).toContainText("草稿单 · 2026-05-06"); await expect(page.getByTestId("invoice-bind-reimbursement")).not.toContainText(reimbursementId); await expect(page.getByText("/invoice-pool")).toHaveCount(0);