重构采购申请任务化页面(purchase-request-task-layout)
这个提交包含在:
@@ -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,
|
||||
|
||||
@@ -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 (
|
||||
<PageScaffold className="space-y-3 pb-4">
|
||||
@@ -105,6 +122,10 @@ export function MobilePurchaseRequestDetailPage() {
|
||||
<div className="text-xs text-zinc-500">当前节点</div>
|
||||
<div className="mt-1 truncate font-medium text-zinc-900">{detail.pendingWorkflow?.task?.stepName ?? "-"}</div>
|
||||
</div>
|
||||
<div>
|
||||
<div className="text-xs text-zinc-500">材料摘要</div>
|
||||
<div className="mt-1 truncate font-medium text-zinc-900">{resolveMobilePurchaseEvidence(detail)}</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="mobile-subcard bg-white text-sm">
|
||||
<div className="text-xs font-semibold text-zinc-500">下一步</div>
|
||||
@@ -112,6 +133,28 @@ export function MobilePurchaseRequestDetailPage() {
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section className="mobile-card" data-testid="mobile-purchase-detail-related">
|
||||
<h3 className="text-base font-semibold text-zinc-900">关联与后续</h3>
|
||||
<div className="mobile-field-list mt-3">
|
||||
<div className="mobile-field-row">
|
||||
<div className="mobile-field-label">关联项目</div>
|
||||
<div className="mobile-field-value">
|
||||
{detail.project ? `${detail.project.projectNo} · ${detail.project.name}` : "未关联项目"}
|
||||
</div>
|
||||
</div>
|
||||
<div className="mobile-field-row">
|
||||
<div className="mobile-field-label">资产候选</div>
|
||||
<div className="mobile-field-value">
|
||||
{assetCandidateCount > 0 ? `${assetCandidateCount} 项,后续由管理员核对后手动建卡` : "无"}
|
||||
</div>
|
||||
</div>
|
||||
<div className="mobile-field-row">
|
||||
<div className="mobile-field-label">付款 / 应付</div>
|
||||
<div className="mobile-field-value">仅查看状态,付款状态回到财务页面查看</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section className="mobile-card" data-testid="mobile-purchase-detail-fields">
|
||||
<h3 className="text-base font-semibold text-zinc-900">业务信息</h3>
|
||||
<div className="mobile-field-list mt-3">
|
||||
|
||||
@@ -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,
|
||||
])}
|
||||
</div>
|
||||
<div className="mt-3 rounded-lg bg-slate-50 px-3 py-2 text-xs leading-5 text-zinc-600">
|
||||
<div>材料:{resolvePurchaseListEvidence(item)}</div>
|
||||
<div>下一步:{resolvePurchaseListNextStep(item.status)}</div>
|
||||
</div>
|
||||
</Link>
|
||||
);
|
||||
}
|
||||
@@ -116,9 +145,11 @@ export function MobilePurchaseRequestsPage() {
|
||||
|
||||
return (
|
||||
<PageScaffold className="space-y-4">
|
||||
<PageHeader title="采购" />
|
||||
<PageHeader title="我的采购" />
|
||||
|
||||
<section className="mobile-card">
|
||||
<div className="text-sm font-semibold text-zinc-900">当前采购任务</div>
|
||||
<div className="mt-1 text-xs leading-5 text-zinc-500">手机端只看状态、材料摘要和下一步;台账筛选、全范围和导出留在电脑端。</div>
|
||||
<div className="mobile-info-grid">
|
||||
<div>
|
||||
<div className="text-xs text-zinc-500">当前列表</div>
|
||||
@@ -150,7 +181,7 @@ export function MobilePurchaseRequestsPage() {
|
||||
</div>
|
||||
) : null}
|
||||
{!listQuery.isLoading && !listQuery.isError && !items.length ? (
|
||||
<div className="mobile-empty-state">暂无采购申请</div>
|
||||
<div className="mobile-empty-state">{resolveMobileEmptyState(status)}</div>
|
||||
) : null}
|
||||
{items.map((item) => (
|
||||
<PurchaseRequestCard currentMobileSource={currentMobileSource} item={item} key={item.id} />
|
||||
|
||||
文件差异内容过多而无法显示
加载差异
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
|
||||
在新工单中引用
屏蔽一个用户