diff --git a/.trellis/spec/hyb-oa/frontend-react-vite-design.md b/.trellis/spec/hyb-oa/frontend-react-vite-design.md index 82fa7b97..cd938f09 100644 --- a/.trellis/spec/hyb-oa/frontend-react-vite-design.md +++ b/.trellis/spec/hyb-oa/frontend-react-vite-design.md @@ -280,8 +280,9 @@ pages must pick one primary template before implementation: - `业务台账列表`: reimbursement, contracts, projects, payments, AP and invoices. Page-header primary action, command bar, list/table body and folded ledger tools. -- `主从工作台`: procurement, assets and inventory checks. Left/index list plus - right/current-object detail and actions. +- `主从工作台`: only for true task execution after an explicit user selection, + such as an inventory-check task that has been opened. It is not the default + first screen for procurement, assets, payments, AP or invoices. - `详情工作区`: reimbursement detail, workflow task detail, project detail and future contract detail. Overview, facts/evidence, right-side summary/command panel and history. @@ -297,9 +298,11 @@ pages must pick one primary template before implementation: - Mobile is the responsive expression of these templates: top status/focus, card list or sectioned detail, bottom primary action. Do not create a PC shell inside mobile routes. -- B-class business pages should usually choose `业务台账列表` or `主从工作台`. - Use a ledger when records open into details; use master-detail when users - select one object and edit/process it in the same workspace. +- B-class business pages should usually choose `业务台账列表`. Procurement, + assets, payments, AP and invoices must start as a list and open details only + through an explicit row action, `?id=` detail mode, drawer or detail route. + Use `主从工作台` only for explicit execution tasks, and never auto-select the + first item merely to fill a detail column. - A-class task pages must not drift into configuration surfaces. C-class configuration pages must not appear as ordinary employee task pages. diff --git a/.trellis/tasks/05-30-fix-payment-ledger-layout-consistency/check.jsonl b/.trellis/tasks/05-30-fix-payment-ledger-layout-consistency/check.jsonl new file mode 100644 index 00000000..317351f6 --- /dev/null +++ b/.trellis/tasks/05-30-fix-payment-ledger-layout-consistency/check.jsonl @@ -0,0 +1,3 @@ +{"file":".trellis/spec/hyb-oa/frontend-react-vite-design.md","reason":"检查业务台账是否保持单列列表、显式详情和紧凑筛选。"} +{"file":".trellis/spec/hyb-oa/deployment-trialrun.md","reason":"部署、公网复检和可测试告知边界。"} +{"file":".trellis/spec/hyb-oa/project-governance.md","reason":"检查 Trellis task、提交、验证和交付流程是否闭环。"} diff --git a/.trellis/tasks/05-30-fix-payment-ledger-layout-consistency/implement.jsonl b/.trellis/tasks/05-30-fix-payment-ledger-layout-consistency/implement.jsonl new file mode 100644 index 00000000..a2a4f1ef --- /dev/null +++ b/.trellis/tasks/05-30-fix-payment-ledger-layout-consistency/implement.jsonl @@ -0,0 +1,3 @@ +{"file":".trellis/spec/hyb-oa/frontend-react-vite-design.md","reason":"业务台账首屏、成熟企业页面基线、按钮命名和详情页布局约束。"} +{"file":".trellis/spec/hyb-oa/project-governance.md","reason":"Trellis task、提交、验证和 solo developer 工作流约束。"} +{"file":".trellis/spec/hyb-oa/deployment-trialrun.md","reason":"公网部署、公网检查和可测试告知边界。"} diff --git a/.trellis/tasks/05-30-fix-payment-ledger-layout-consistency/implement.md b/.trellis/tasks/05-30-fix-payment-ledger-layout-consistency/implement.md new file mode 100644 index 00000000..267f9a4f --- /dev/null +++ b/.trellis/tasks/05-30-fix-payment-ledger-layout-consistency/implement.md @@ -0,0 +1,14 @@ +# 实施说明 + +## 页面策略 + +- 业务台账首屏统一为单列列表:付款、资产、采购、票据、应付账款、报销和项目不得默认渲染详情区。 +- 详情必须由用户显式进入:使用 `?id=`、详情路由或行级 `查看详情`,不自动选择第一条记录。 +- `台账工具` 固定为页头右侧入口,展开后占据列表上方全宽区域,不作为左侧栏或右侧提示栏。 +- 状态摘要使用紧凑 chips/tabs/counts,去掉普通工作流解释句和大型状态卡。 + +## 风险控制 + +- 不改后端 API、权限校验、审批状态机和付款执行语义。 +- 旧 E2E 按新交互同步:票据上传/绑定先打开 `台账工具`,付款执行进入批次详情。 +- 资产盘点保留执行页例外,但首屏不自动选择盘点任务。 diff --git a/.trellis/tasks/05-30-fix-payment-ledger-layout-consistency/prd.md b/.trellis/tasks/05-30-fix-payment-ledger-layout-consistency/prd.md new file mode 100644 index 00000000..5c06d08b --- /dev/null +++ b/.trellis/tasks/05-30-fix-payment-ledger-layout-consistency/prd.md @@ -0,0 +1,185 @@ +# 修正业务台账首屏布局一致性 + +## Goal + +修正用户指出的台账统一规则漏改问题,并扩展为所有业务台账页一致性审查。统一采用成熟企业系统常见的列表边界:首屏只做查找、筛选、状态判断和进入详情;详情通过显式入口进入独立详情/编辑视图;台账工具在页头右侧触发并展开为列表上方的工具区,不挤压列表,不在首屏默认塞入详情工作台。 + +## What I already know + +- 用户明确反馈: + - `付款` 还没按统一规则改。 + - 台账虽然是列表,但详情在列表下面,仍然不一致。 + - `台账工具` 还在左侧,和其他页面操作区域不一致。 + - 需要继续检查其他页面,避免只修局部后同类问题继续存在。 +- 已确认的统一规则: + - 业务台账首屏统一为单列列表。 + - 不默认左列表/右详情,不自动选中第一条记录。 + - 不在列表下方自动展开详情。 + - 状态摘要用 Tab/Chip/counts,不使用大状态卡片。 + - `台账工具` 属于页头右侧操作,低频工具展开在列表上方或独立面板,不作为左侧栏。 + - 详情页采用独立详情工作区:PC 顶部摘要、中部事实/证据/明细、右侧或顶部动作区;移动端单列。 +- 已定位代码: + - `client/src/pages/PaymentBatchesPage.tsx` + - 仍有 `WorkspaceHeader` 副标题解释文案。 + - 仍有 `finance-status-strip` 五张大指标卡。 + - `selectedBatch` 默认回退到 `batches[0]`,会自动选中首条。 + - `useEffect` 自动写入 `?id=`。 + - 主体为 `xl:grid-cols-[340px,minmax(0,1fr)]` 的左列表/右详情。 + - `client/src/pages/AssetRegistryPage.tsx` + - `showLedgerTools` 时使用 `xl:grid-cols-[380px,minmax(0,1fr)]`,台账工具成为左侧列。 + - 点击资产行后保持列表并在下方直接渲染详情/编辑区。 + - 详情仍依赖 `selectedAssetId || isCreatingAsset` 在列表页面下方展开。 + - `client/src/pages/PurchaseRequestsPage.tsx` + - 仍有 `selectedId`、`?id`、行高亮和列表下方详情区。 + - `id` 模式仍像内嵌详情,不像独立详情工作区。 + - `client/src/pages/InvoicePoolPage.tsx` + - 会在列表数据返回后自动选中第一张票据。 + - 仍有页头解释性副标题和五张大状态卡。 + - 列表后默认渲染 `InvoiceDetailPanel`。 + - `client/src/pages/InventoryChecksPage.tsx` + - 资产盘点是任务执行页而非普通台账,但当前使用大状态卡、左侧新建/列表、右侧详情执行工作台,并自动选中第一条盘点任务。 + - 本轮需要判断是否按“资产执行页例外”处理,至少不能混入普通资产台账首屏。 + - 已初步检查但不作为主要改造目标: + - `client/src/pages/ReimbursementsPage.tsx`:当前更接近列表优先,详情走独立路由。 + - `client/src/pages/ContractsRegistryPage.tsx`:台账工具位于上方,但新建/编辑仍在列表前方,需做防回退和局部降噪检查。 + - `client/src/pages/ProjectsPage.tsx`:项目详情走独立路由,台账工具位于上方;工具区内有低频查看侧块,需做一致性检查。 + - `client/src/pages/ApOperationsPage.tsx`:应付账款是财务操作页,采用 tabs/list 和上方工具;`财务台账` tab 默认筛选较多,需检查是否压缩到常用筛选/台账工具。 + +## Assumptions + +- `付款`、`资产`、`采购`、`票据` 都是业务台账列表,不是双列工作台。 +- `资产`、`采购`、`票据` 的详情/编辑表单可以继续复用现有组件代码,但显示方式改为独立视图,不再压在列表下方。 +- `盘点任务` 可以保留执行页属性,但不能影响普通资产台账;如果保留主从工作台,也必须在 PRD/规范中明确它是任务执行页例外,而不是业务台账默认形态。 +- 本轮只修 PC 台账一致性;移动资产扫码/移动页面不改业务流程。 +- 付款批次主链路测试需要同步更新:创建批次后进入详情视图,详情里的复核、回单、执行操作继续可用。 + +## Proposed Plan + +1. 全量页面归类: + - 业务台账页:`报销`、`采购`、`合同台账`、`项目`、`资产`、`付款`、`应付账款`、`票据`。 + - 必改页面:`付款`、`资产`、`采购`、`票据`。 + - 防回退检查页面:`报销`、`合同台账`、`项目`、`应付账款`。 + - 例外/单独判断页面:`资产盘点` 是任务执行页;`请假记录`、`考勤` 是企业微信边界页;管理配置页不套普通台账首屏规则。 +2. `付款` 页面: + - 去掉页头解释性副标题。 + - 去掉五张大状态卡,改为列表上方紧凑 Tab/Chip:`全部`、`待复核`、`待付款`、`待补回单`、`异常`。 + - 首屏只显示批次列表表格/卡片,列包含批次、实体/金额、状态/复核、回单、操作。 + - 不自动选中首条,不自动写入 `?id=`。 + - 行操作使用 `查看详情`;可保留低风险快捷动作,但优先详情页处理。 + - `?id=` 时显示独立付款批次详情视图,不再同时显示左列表。 + - `台账工具` 仍为页头右侧按钮;展开时显示在列表上方全宽工具区。 +3. `资产` 页面: + - `台账工具` 展开为页头下方全宽工具区,不再作为左侧列。 + - 首屏只显示筛选 + 资产列表;点击行进入 `?id=` 的独立资产详情/编辑视图,不在列表下方展开。 + - `新建资产卡片` 进入独立创建视图,创建完成后进入该资产详情。 + - 列表页增加右侧操作列 `查看详情`,保持行点击可用但不高亮当前行。 +4. `采购` 页面: + - `/purchase-requests` 首屏只显示筛选 + 采购申请列表。 + - `?id=` 改为独立详情/编辑视图,不在列表下方继续展示详情。 + - 行操作统一为 `查看详情`、`继续编辑`、`查看进度` 等常规动作,审批动作仍进入统一待办/审批详情。 + - 台账工具仅承接创建、导出、归档/ERP 等低频能力,不作为第二个主体区域。 +5. `票据` 页面: + - `/invoice-pool` 不自动选中第一张票据。 + - 去掉页头解释性副标题和五张大状态卡,改为紧凑状态 tabs/chips。 + - 列表首屏只显示票据列表、识别/绑定状态和操作;详情通过显式 `查看详情` 打开独立详情视图或抽屉。 +6. `资产盘点` 页面: + - 先审查并明确页面模板:若作为资产执行页例外保留主从结构,需要在规范中写清“任务执行页例外”;否则改为盘点任务列表 + 独立盘点详情。 + - 至少去掉默认自动选中和首屏大段解释文案,避免被误认为普通业务台账模板。 +7. 防回退检查: + - `报销`、`合同台账`、`项目`、`应付账款` 保持列表优先、上方工具、详情显式进入。 + - 若发现同类内嵌详情、默认选中、左侧工具挤压列表或多行默认筛选,纳入同一轮修正。 +8. 测试同步: + - 更新付款批次、资产、采购、票据、视觉一致性相关断言,明确首屏不显示详情面板。 + - 保持付款批次主链路仍能创建、复核、上传回单、执行。 +9. 验证与发布: + - `cd client && npm run typecheck && npm run build` + - 聚焦 Playwright:付款、资产、采购、票据、财务视觉、跨页面一致性。 + - 通过后提交、部署、公网复检。 + +## Scope + +- 前端页面:付款、资产、采购、票据、资产盘点、应付账款、项目、报销。 +- 测试:同步受布局变更影响的票据、付款、跨页面一致性、移动票据和高风险动作确认 E2E。 +- 规范:沉淀业务台账首屏为单列列表、详情显式进入、台账工具页头右侧展开的统一规则。 +- 不触碰后端 API、权限模型、审批状态机、付款执行语义和企业微信边界。 + +## Test Plan + +- `cd client && npm run typecheck` +- `cd client && npm run build` +- `npx playwright test tests/e2e/pc-finance-admin-visual-wave4.spec.ts tests/e2e/r103-cross-page-ui-consistency.spec.ts tests/e2e/r92-mobile-invoice-usability.spec.ts --workers=1 --reporter=list` +- `npx playwright test tests/e2e/r102-reimbursement-main-flow.spec.ts --workers=1 --reporter=list` +- `npx playwright test tests/e2e/system-action-confirmation.spec.ts --workers=1 --reporter=list` +- `git diff --check` +- `python3 ./.trellis/scripts/task.py validate 05-30-fix-payment-ledger-layout-consistency` +- `node scripts/qa/check-trellis-governance.mjs` + +## Requirements + +- 付款列表首屏不得出现 `payment-batch-detail-panel`、`payment-batch-receipt-panel` 或批次项详情。 +- 付款首屏不得出现五张 `finance-metric-card` 大状态卡。 +- 付款首屏不得自动选择第一条批次。 +- 资产列表首屏不得出现资产详情/编辑表单。 +- 资产 `台账工具` 打开后不得作为左侧栏挤压列表;应位于列表上方或作为弹层/抽屉。 +- 采购列表首屏不得在列表下方渲染采购详情/编辑区。 +- 票据列表首屏不得自动选择第一张票据,不得默认渲染票据详情面板。 +- 业务台账首屏不得使用左列表/右详情、左工具/右列表或列表下方详情作为默认形态。 +- 业务台账首屏不得使用解释普通工作流的大段副标题、提示卡或说明句。 +- 业务台账默认筛选应控制在一行常用项;高级筛选、导出、批量、统计、测试数据进入 `台账工具` 或高级筛选区。 +- 所有台账工具按钮位置保持页头右侧。 +- 不改变付款、资产、采购、票据的后端 API、权限模型和业务状态机。 + +## Acceptance Criteria + +- [ ] `/payment-batches` 首屏为单列列表,未带 `id` 时不渲染付款详情。 +- [ ] `/payment-batches?id=...` 为独立付款详情/处理视图,操作仍可复核、上传回单、执行。 +- [ ] `/assets` 首屏为单列资产列表,未带 `id` 时不渲染资产详情。 +- [ ] `/assets?id=...` 为独立资产详情/编辑视图。 +- [ ] `/purchase-requests` 首屏为单列采购列表,未带 `id` 时不渲染采购详情/编辑区。 +- [ ] `/purchase-requests?id=...` 为独立采购详情/编辑视图。 +- [ ] `/invoice-pool` 首屏为单列票据列表,未带 `id` 时不渲染票据详情。 +- [ ] `/invoice-pool?id=...` 或显式详情入口为独立票据详情视图/抽屉,不自动选择第一条。 +- [ ] `台账工具` 在付款、资产页面均为页头右侧入口,展开后不变成左侧栏。 +- [ ] `报销`、`合同台账`、`项目`、`应付账款` 经检查无默认内嵌详情、默认选中第一条、左侧工具挤压列表或多行默认筛选的回退。 +- [ ] `资产盘点` 的页面模板和例外口径明确,不误导为普通业务台账默认样式。 +- [ ] 相关 E2E 和构建通过。 +- [ ] 部署并公网复检通过后再告知可线上测试。 + +## Out of Scope + +- 不改付款、资产、采购、票据后端模型。 +- 不新增付款审批/支付能力。 +- 不改企业微信、考勤、请假边界。 +- 不重做所有详情页视觉,只处理台账首屏与详情入口一致性。 +- 不把管理配置页、审计排障页强行改成普通业务台账页。 + +## Technical Notes + +- 相关规范: + - `.trellis/spec/hyb-oa/frontend-react-vite-design.md` + - `.trellis/spec/hyb-oa/project-governance.md` +- 相关页面: + - `client/src/pages/PaymentBatchesPage.tsx` + - `client/src/pages/AssetRegistryPage.tsx` + - `client/src/pages/PurchaseRequestsPage.tsx` + - `client/src/pages/InvoicePoolPage.tsx` + - `client/src/pages/InventoryChecksPage.tsx` + - `client/src/pages/ReimbursementsPage.tsx` + - `client/src/pages/ContractsRegistryPage.tsx` + - `client/src/pages/ProjectsPage.tsx` + - `client/src/pages/ApOperationsPage.tsx` +- 相关测试候选: + - `client/tests/e2e/main-flow.spec.ts` + - `client/tests/e2e/reimbursement-payment-batch-flow.spec.ts` + - `client/tests/e2e/assets-flow.spec.ts` + - `client/tests/e2e/purchase-requests*.spec.ts` + - `client/tests/e2e/*invoice*.spec.ts` + - `client/tests/e2e/pc-finance-admin-visual-wave4.spec.ts` + - `client/tests/e2e/r103-cross-page-ui-consistency.spec.ts` + +## Definition of Done + +- 代码、测试、规范/任务文档保持一致。 +- 本地构建和聚焦 E2E 通过。 +- 提交中文 commit,包含 Trellis task 名称。 +- 公网部署和复检通过后,才告知用户可以看线上效果。 diff --git a/.trellis/tasks/05-30-fix-payment-ledger-layout-consistency/task.json b/.trellis/tasks/05-30-fix-payment-ledger-layout-consistency/task.json new file mode 100644 index 00000000..ad7397fe --- /dev/null +++ b/.trellis/tasks/05-30-fix-payment-ledger-layout-consistency/task.json @@ -0,0 +1,34 @@ +{ + "id": "fix-payment-ledger-layout-consistency", + "name": "fix-payment-ledger-layout-consistency", + "title": "修正付款与台账详情布局一致性", + "description": "统一业务台账首屏为单列列表,修正付款、资产、采购、票据、应付账款等页面默认详情和工具区不一致问题。", + "status": "in_progress", + "dev_type": "frontend", + "scope": "client,docs,trellis-spec", + "package": "hyb-oa", + "priority": "P2", + "creator": "shunli", + "assignee": "shunli", + "createdAt": "2026-05-30", + "completedAt": null, + "branch": null, + "base_branch": "main", + "worktree_path": null, + "commit": null, + "pr_url": null, + "subtasks": [], + "children": [], + "parent": null, + "relatedFiles": [ + "client/src/pages/PaymentBatchesPage.tsx", + "client/src/pages/AssetRegistryPage.tsx", + "client/src/pages/PurchaseRequestsPage.tsx", + "client/src/pages/InvoicePoolPage.tsx", + "client/src/pages/ApOperationsPage.tsx", + ".trellis/spec/hyb-oa/frontend-react-vite-design.md", + "docs/产品设计规则.md" + ], + "notes": "", + "meta": {} +} diff --git a/client/src/pages/ApOperationsPage.tsx b/client/src/pages/ApOperationsPage.tsx index 0bdb1fd9..1e1fa105 100644 --- a/client/src/pages/ApOperationsPage.tsx +++ b/client/src/pages/ApOperationsPage.tsx @@ -259,6 +259,7 @@ export function ApOperationsPage() { const [ledgerSearch, setLedgerSearch] = useState(""); const [ledgerPage, setLedgerPage] = useState(1); const [showLedgerTools, setShowLedgerTools] = useState(false); + const [showLedgerAdvancedFilters, setShowLedgerAdvancedFilters] = useState(false); const [reviewTarget, setReviewTarget] = useState(null); const [reviewComment, setReviewComment] = useState(""); @@ -384,6 +385,7 @@ export function ApOperationsPage() { }), [openRiskFlags, riskBucket], ); + const hasLedgerAdvancedFilters = Boolean(ledgerInvoiceStatus !== "all" || ledgerDateFrom || ledgerDateTo); const failedDetailItem = useMemo(() => { if (!failedDetailTarget) { @@ -666,108 +668,123 @@ export function ApOperationsPage() { -
-
-
审批状态
- -
-
-
付款状态
- -
-
-
发票状态
- -
-
-
报销日期从
- { - setLedgerDateFrom(event.target.value); - setLedgerPage(1); - }} - /> -
-
-
报销日期至
- { - setLedgerDateTo(event.target.value); - setLedgerPage(1); - }} - /> -
-
-
搜索
- { - setLedgerSearch(event.target.value); - setLedgerPage(1); - }} - /> +
+
+
+
搜索
+ { + setLedgerSearch(event.target.value); + setLedgerPage(1); + }} + /> +
+
+
审批状态
+ +
+
+
付款状态
+ +
+
+ +
+
+ +
-
- -
+ {showLedgerAdvancedFilters || hasLedgerAdvancedFilters ? ( +
+
+
发票状态
+ +
+
+
报销日期从
+ { + setLedgerDateFrom(event.target.value); + setLedgerPage(1); + }} + /> +
+
+
报销日期至
+ { + setLedgerDateTo(event.target.value); + setLedgerPage(1); + }} + /> +
+
+ ) : null}
diff --git a/client/src/pages/AssetRegistryPage.tsx b/client/src/pages/AssetRegistryPage.tsx index a631227a..8e2de483 100644 --- a/client/src/pages/AssetRegistryPage.tsx +++ b/client/src/pages/AssetRegistryPage.tsx @@ -798,6 +798,14 @@ export function AssetRegistryPage() { setIsCreatingAsset(false); setSelectedAssetId(assetId); }; + const returnToAssetList = () => { + setIsCreatingAsset(false); + setSelectedAssetId(""); + const next = new URLSearchParams(searchParams); + next.delete("id"); + setSearchParams(next, { replace: true }); + }; + const isAssetDetailMode = isCreatingAsset || Boolean(selectedAssetId); return ( @@ -805,17 +813,26 @@ export function AssetRegistryPage() { title="资产台账" actions={
- - + {isAssetDetailMode ? ( + + ) : ( + <> + + + + )}
} /> -
+ {!isAssetDetailMode ? ( +
{showLedgerTools ? (
@@ -1025,17 +1042,13 @@ export function AssetRegistryPage() {
{orderedAssetItems.length ? ( orderedAssetItems.map((item) => ( - +
+
+
查看详情
+ )) ) : (
暂无资产卡片
@@ -1079,17 +1093,18 @@ export function AssetRegistryPage() {
资产类型 - 责任链 - 位置 - 状态 - + 责任链 + 位置 + 状态 + 操作 + {orderedAssetItems.length ? ( orderedAssetItems.map((item) => ( - { - selectAsset(item.id); - }} className={item.id === selectedAssetId && !isCreatingAsset ? "bg-zinc-50" : "cursor-pointer"}> + { + selectAsset(item.id); + }} className="cursor-pointer">
@@ -1107,12 +1122,17 @@ export function AssetRegistryPage() {
{formatTextWithCompactLongNumbers(item.location ?? "-")}
盘点 {item.lastVerifiedAt ? formatDisplayDateTime(item.lastVerifiedAt) : "未登记"}
- - + + + + + )) ) : ( - + 暂无资产卡片 @@ -1121,11 +1141,12 @@ export function AssetRegistryPage() {
- ) : null} - - - {selectedAssetId || isCreatingAsset ? ( -
+ ) : null} + +
+
+ ) : ( +
{selectedAssetId && !isCreatingAsset && assetDetailQuery.isLoading ? : null} {selectedAssetId && !isCreatingAsset && assetDetailQuery.isError ? (

{resolveError(assetDetailQuery.error)}

@@ -1559,9 +1580,7 @@ export function AssetRegistryPage() { ) : null}
- ) : null} -
-
+ )} ); } diff --git a/client/src/pages/InventoryChecksPage.tsx b/client/src/pages/InventoryChecksPage.tsx index 24d1b818..09884dce 100644 --- a/client/src/pages/InventoryChecksPage.tsx +++ b/client/src/pages/InventoryChecksPage.tsx @@ -109,15 +109,11 @@ export function InventoryChecksPage() { }); useEffect(() => { - const nextId = inventoryChecksQuery.data?.items?.[0]?.id ?? ""; - if (!selectedCheckId && nextId) { - setSelectedCheckId(nextId); - } - if (selectedCheckId && inventoryChecksQuery.data?.items?.some((item) => item.id === selectedCheckId)) { + if (!selectedCheckId) { return; } - if (nextId !== selectedCheckId) { - setSelectedCheckId(nextId); + if (!inventoryChecksQuery.data?.items?.some((item) => item.id === selectedCheckId)) { + setSelectedCheckId(""); } }, [inventoryChecksQuery.data?.items, selectedCheckId]); @@ -247,7 +243,6 @@ export function InventoryChecksPage() { -
-
-
盘点任务
-
{inventoryStats.total}
-
按季度或事件触发
-
-
-
草稿
-
{inventoryStats.draft}
-
待启动后录入
-
-
-
盘点中
-
{inventoryStats.counting}
-
可扫码或手工录入
-
-
-
已完成
-
{inventoryStats.completed}
-
保留差异和备注
-
+
+ {[ + ["全部", inventoryStats.total], + ["草稿", inventoryStats.draft], + ["盘点中", inventoryStats.counting], + ["已完成", inventoryStats.completed], + ].map(([label, value]) => ( + + {label} {value} + + ))}
-
+

新建盘点任务

@@ -338,7 +323,7 @@ export function InventoryChecksPage() { setSelectedCheckId(item.id)} > {item.checkNo} @@ -358,17 +343,12 @@ export function InventoryChecksPage() {
+ {selectedCheck ? (
{inventoryChecksQuery.isLoading ? : null} {inventoryChecksQuery.isError ? (

{resolveError(inventoryChecksQuery.error)}

) : null} - {!inventoryChecksQuery.isLoading && !selectedCheck ? ( -
- 请先创建或选择盘点任务。 -
- ) : null} - {selectedCheck ? ( <>
@@ -464,6 +444,7 @@ export function InventoryChecksPage() { ) : null}
+ ) : null}
{ - if (!detailInvoiceId && listQuery.data?.items?.length) { - setDetailInvoiceId(listQuery.data.items[0].id); + const searchInvoiceId = searchParams.get("id") ?? ""; + if (searchInvoiceId !== detailInvoiceId) { + setDetailInvoiceId(searchInvoiceId); } - }, [detailInvoiceId, listQuery.data?.items]); + }, [detailInvoiceId, searchParams]); + + useEffect(() => { + const currentId = searchParams.get("id") ?? ""; + if (detailInvoiceId === currentId) { + return; + } + const next = new URLSearchParams(searchParams); + if (detailInvoiceId) { + next.set("id", detailInvoiceId); + } else { + next.delete("id"); + } + setSearchParams(next, { replace: true }); + }, [detailInvoiceId, searchParams, setSearchParams]); // --- Helper for error handling --- @@ -453,6 +470,13 @@ export function InvoicePoolPage() { detail.ownerEmployeeId === me?.id || detail.uploadedByEmployeeId === me?.id), ); + const isInvoiceDetailMode = Boolean(detailInvoiceId); + const returnToInvoiceList = () => { + setDetailInvoiceId(""); + const next = new URLSearchParams(searchParams); + next.delete("id"); + setSearchParams(next, { replace: true }); + }; // --- Render --- @@ -460,11 +484,18 @@ export function InvoicePoolPage() { setShowLedgerTools((current) => !current)}> - 台账工具 - +
+ {isInvoiceDetailMode ? ( + + ) : ( + + )} +
} /> @@ -474,14 +505,11 @@ export function InvoicePoolPage() {

) : null} - {showLedgerTools ? ( + {!isInvoiceDetailMode && showLedgerTools ? (

台账工具

-

- 导出按当前状态和范围生成,只用于财务复核、OCR 排障和代账交接;普通员工日常补票据仍从报销详情或移动上传进入。 -

@@ -504,124 +532,114 @@ export function InvoicePoolPage() {
-
- 票据原文、预览和下载仍按详情页权限与审计控制处理,列表导出不作为敏感材料分发入口。 -
+ uploadMutation.mutate()} + showProxyOwnerSelector={showProxyOwnerSelector} + proxyDelegationsQuery={proxyDelegationsQuery} + uploadOwnerEmployeeId={uploadOwnerEmployeeId} + setUploadOwnerEmployeeId={setUploadOwnerEmployeeId} + /> +
) : null} -
-
-
票据总数
-
{invoiceStats.total}
+ {!isInvoiceDetailMode ? ( +
+ {[ + ["全部", invoiceStats.total], + ["识别中", invoiceStats.pending], + ["识别失败", invoiceStats.failed], + ["可绑定", invoiceStats.available], + ["已绑定", invoiceStats.bound], + ].map(([label, value]) => ( + + {label} {value} + + ))}
-
-
识别中
-
{invoiceStats.pending}
-
-
-
识别失败
-
{invoiceStats.failed}
-
-
-
可绑定
-
{invoiceStats.available}
-
-
-
已绑定
-
{invoiceStats.bound}
-
-
+ ) : null} - uploadMutation.mutate()} - showProxyOwnerSelector={showProxyOwnerSelector} - proxyDelegationsQuery={proxyDelegationsQuery} - uploadOwnerEmployeeId={uploadOwnerEmployeeId} - setUploadOwnerEmployeeId={setUploadOwnerEmployeeId} - /> - - - - - - + {!isInvoiceDetailMode ? ( + + ) : ( + + )} ); } diff --git a/client/src/pages/PaymentBatchesPage.tsx b/client/src/pages/PaymentBatchesPage.tsx index d09fdd50..9f4e1198 100644 --- a/client/src/pages/PaymentBatchesPage.tsx +++ b/client/src/pages/PaymentBatchesPage.tsx @@ -1,4 +1,4 @@ -import { useEffect, useMemo, useRef, useState } from "react"; +import { useMemo, useRef, useState } from "react"; import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query"; import { useSearchParams } from "react-router-dom"; import { @@ -128,7 +128,7 @@ export function PaymentBatchesPage() { const selectedBatchId = searchParams.get("id") ?? ""; const batches = useMemo(() => listQuery.data?.items ?? [], [listQuery.data?.items]); const selectedBatch = useMemo(() => { - return batches.find((item) => item.id === selectedBatchId) ?? batches[0] ?? null; + return batches.find((item) => item.id === selectedBatchId) ?? null; }, [batches, selectedBatchId]); const batchStats = useMemo(() => { return { @@ -152,17 +152,6 @@ export function PaymentBatchesPage() { enabled: Boolean(selectedBatch?.id), queryFn: () => fetchPaymentBatchReceiptAttachments(selectedBatch!.id), }); - useEffect(() => { - if (!selectedBatch) { - return; - } - if (selectedBatch.id === selectedBatchId) { - return; - } - const next = new URLSearchParams(searchParams); - next.set("id", selectedBatch.id); - setSearchParams(next, { replace: true }); - }, [searchParams, selectedBatch, selectedBatchId, setSearchParams]); const canEditSelectedBatch = canManageBatch && @@ -195,6 +184,12 @@ export function PaymentBatchesPage() { await queryClient.invalidateQueries({ queryKey: ["ap-operations"] }); }; + const returnToList = () => { + const next = new URLSearchParams(searchParams); + next.delete("id"); + setSearchParams(next, { replace: true }); + }; + const createMutation = useMutation({ mutationFn: async () => createPaymentBatch({ @@ -363,51 +358,47 @@ export function PaymentBatchesPage() { {downloadReasonDialog} +
+ {selectedBatchId ? ( + + ) : null} - - + {!selectedBatchId ? ( + + ) : null} +
} /> {error ?
{error}
: null} -
-
-
待复核
-
{batchStats.needsReview}
+ {!selectedBatchId ? ( +
+ {[ + ["全部", batchStats.total], + ["待复核", batchStats.needsReview], + ["待付款", batchStats.readyToPay], + ["待补回单", batchStats.needsReceipt], + ["异常", batchStats.exceptions], + ].map(([label, value]) => ( + + {label} {value} + + ))}
-
-
待付款标记
-
{batchStats.readyToPay}
-
-
-
待补回单
-
{batchStats.needsReceipt}
-
-
-
异常批次
-
{batchStats.exceptions}
-
-
-
批次数
-
{batchStats.total}
-
-
+ ) : null} - {showBatchTools ? ( + {!selectedBatchId && showBatchTools ? (
-

台账工具 · 新建批次

-

- 仅把已确认待付款的单据归入批次。真实付款仍在银行或既有财务流程完成,OA 记录复核、回单和状态。 -

+

新建批次

{!canManageBatch ? (
@@ -490,39 +481,8 @@ export function PaymentBatchesPage() {
) : null} -
-
-
-

批次列表

-
- {batches.map((item) => ( - - ))} - {!listQuery.data?.items?.length ?
暂无付款批次
: null} -
-
- - {selectedBatch ? ( + {selectedBatchId ? ( + selectedBatch ? (
@@ -727,9 +687,44 @@ export function PaymentBatchesPage() {
- ) : null} + ) : ( +
未找到付款批次
+ ) + ) : ( +
+
+

批次列表

+ 共 {batches.length} 条 +
+
+ {batches.map((item) => ( + + ))} + {!listQuery.data?.items?.length ?
暂无付款批次
: null} +
-
+ )} { diff --git a/client/src/pages/ProjectsPage.tsx b/client/src/pages/ProjectsPage.tsx index a52d6a8c..eef942f0 100644 --- a/client/src/pages/ProjectsPage.tsx +++ b/client/src/pages/ProjectsPage.tsx @@ -187,67 +187,66 @@ export function ProjectsPage() { {showLedgerTools ? ( -
-
- {!canManageProjects ? ( -
你当前只有查看权限,项目创建入口仅对老板 / 管理员 / 项目管理角色开放。
- ) : ( -
-
-
- - setName(event.target.value)} placeholder="例如:深圳某客户现场实施" /> -
-
- - setClientName(event.target.value)} placeholder="客户全称" /> -
-
- - setClientAlias(event.target.value)} placeholder="可选" /> -
-
- - -
-
-
-
- - setStartsAt(event.target.value)} /> -
-
- - setTargetEndAt(event.target.value)} /> -
+
+ {!canManageProjects ? ( +
你当前只有查看权限,项目创建入口仅对老板 / 管理员 / 项目管理角色开放。
+ ) : ( + <> +
+
+ + setName(event.target.value)} placeholder="例如:深圳某客户现场实施" />
- -