From 43efc9ea082eae1e22a45eb22400f23fb6b8ecb2 Mon Sep 17 00:00:00 2001 From: shunli Date: Sun, 31 May 2026 15:06:26 +0800 Subject: [PATCH] =?UTF-8?q?=E7=BB=9F=E4=B8=80=E8=8F=9C=E5=8D=95=E5=91=BD?= =?UTF-8?q?=E5=90=8D=E5=B9=B6=E7=A7=BB=E9=99=A4=E5=89=A9=E4=BD=99=E5=8F=B0?= =?UTF-8?q?=E8=B4=A6=E5=B7=A5=E5=85=B7=EF=BC=8805-31-unified-menu-remove-l?= =?UTF-8?q?edger-tools-wave2=EF=BC=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .trellis/spec/hyb-oa/deployment-trialrun.md | 2 +- .../spec/hyb-oa/frontend-react-vite-design.md | 4 +- .../check.jsonl | 5 + .../implement.jsonl | 5 + .../implement.md | 22 +++ .../prd.md | 89 ++++++++++ .../task.json | 26 +++ client/src/components/GlobalSearchDialog.tsx | 2 +- client/src/components/navigation/NavLinks.tsx | 6 +- client/src/features/navigation/schema.ts | 4 +- client/src/pages/ApOperationsPage.tsx | 57 +++---- client/src/pages/AssetRegistryPage.tsx | 2 +- client/src/pages/ContractsRegistryPage.tsx | 157 +++++++++--------- client/src/pages/InvoicePoolPage.tsx | 142 ++++++++-------- client/src/pages/PaymentBatchesPage.tsx | 8 +- client/src/pages/ProjectDetailPage.tsx | 6 +- client/src/pages/ProjectsPage.tsx | 129 +++++++------- client/src/pages/PurchaseRequestsPage.tsx | 24 ++- client/src/pages/WorkspaceSetupPage.tsx | 4 +- client/tests/e2e/assets-flow.spec.ts | 2 +- client/tests/e2e/helpers/liveInvoiceOcr.ts | 2 +- client/tests/e2e/invoice-ocr-flow.spec.ts | 4 +- client/tests/e2e/login-portal-ux.spec.ts | 6 +- client/tests/e2e/main-flow.spec.ts | 6 +- .../e2e/manual/asset1d-public-trial.spec.ts | 2 +- .../r7e-browser-business-trialrun.spec.ts | 4 +- .../e2e/oafront3-pc-readonly-visual.spec.ts | 14 +- .../e2e/pc-detail-mobile-visual-wave3.spec.ts | 15 +- .../e2e/pc-finance-admin-visual-wave4.spec.ts | 21 ++- .../e2e/permission-direct-access.spec.ts | 2 +- client/tests/e2e/procurement-flow.spec.ts | 4 +- .../e2e/r102-reimbursement-main-flow.spec.ts | 1 - .../r103-cross-page-ui-consistency.spec.ts | 18 +- .../e2e/r104-public-visual-acceptance.spec.ts | 2 +- client/tests/e2e/r105-pretrial-visual.spec.ts | 4 +- client/tests/e2e/r106-visual-audit.spec.ts | 6 +- .../e2e/r92-mobile-invoice-usability.spec.ts | 4 +- .../reimbursement-payment-batch-flow.spec.ts | 2 +- client/tests/e2e/travel-payment-flow.spec.ts | 4 +- client/tests/e2e/visual-shell.spec.ts | 9 +- docs/产品设计规则.md | 16 +- docs/报销发票附件规则.md | 2 +- docs/管理员手册.md | 2 +- docs/需求-合同附件归档规约.md | 2 +- docs/需求-资产管理规约.md | 2 +- docs/需求-采购费用审批规约.md | 2 +- server/src/services/workspaceSettings.js | 4 +- 47 files changed, 498 insertions(+), 358 deletions(-) create mode 100644 .trellis/tasks/05-31-unified-menu-remove-ledger-tools-wave2/check.jsonl create mode 100644 .trellis/tasks/05-31-unified-menu-remove-ledger-tools-wave2/implement.jsonl create mode 100644 .trellis/tasks/05-31-unified-menu-remove-ledger-tools-wave2/implement.md create mode 100644 .trellis/tasks/05-31-unified-menu-remove-ledger-tools-wave2/prd.md create mode 100644 .trellis/tasks/05-31-unified-menu-remove-ledger-tools-wave2/task.json diff --git a/.trellis/spec/hyb-oa/deployment-trialrun.md b/.trellis/spec/hyb-oa/deployment-trialrun.md index cb2013a0..57f30495 100644 --- a/.trellis/spec/hyb-oa/deployment-trialrun.md +++ b/.trellis/spec/hyb-oa/deployment-trialrun.md @@ -194,7 +194,7 @@ await expect( - Runtime smoke must distinguish primary sidebar entries from collapsed `更多` entries. -- Core day-to-day ledgers such as `报销`, `采购申请`, `合同台账`, `项目` and +- Core day-to-day ledgers such as `报销`, `采购`, `合同`, `项目` and `资产` are expected in `main` when the active role has access. - Low-frequency entries such as `周报` and `公告` may be enabled but should be checked in `backup` / `更多`, not forced into `main`. diff --git a/.trellis/spec/hyb-oa/frontend-react-vite-design.md b/.trellis/spec/hyb-oa/frontend-react-vite-design.md index 32f5cfae..9dfec67c 100644 --- a/.trellis/spec/hyb-oa/frontend-react-vite-design.md +++ b/.trellis/spec/hyb-oa/frontend-react-vite-design.md @@ -52,7 +52,7 @@ reference, not a copy of Feishu or Ant Design branding. should simplify or hide low-frequency work for small teams, not invent new controls or unusual action flows. - Page titles default to the route name only, such as `工作台`, `待我处理`, `报销`, - `合同台账`, `个人设置` and `通知中心`. Page subtitles are off by default and + `合同`, `采购`, `资产`, `个人设置` and `通知中心`. Page subtitles are off by default and appear only for exception, empty, permission-denied, Enterprise WeChat/OA boundary, external-system boundary or irreversible-action explanations. - Normal workflows should not be explained in default-visible copy. Do not add @@ -674,7 +674,7 @@ screen. status, scope and date/time; add business-specific filters such as project, person or type only when they are genuinely frequent. - The default view must match the route's main job: `我的报销`, `待我处理`, - `合同台账`, `资产台账` and similar task nouns. Do not default ordinary users + `合同`, `资产` and similar task nouns. Do not default ordinary users into company-wide management views. - Rows and cards must answer what the object is, current state, why it relates to the current user and the next action. diff --git a/.trellis/tasks/05-31-unified-menu-remove-ledger-tools-wave2/check.jsonl b/.trellis/tasks/05-31-unified-menu-remove-ledger-tools-wave2/check.jsonl new file mode 100644 index 00000000..fa85f482 --- /dev/null +++ b/.trellis/tasks/05-31-unified-menu-remove-ledger-tools-wave2/check.jsonl @@ -0,0 +1,5 @@ +{"file": ".trellis/spec/hyb-oa/index.md", "reason": "hyb-oa spec entry and read order"} +{"file": ".trellis/spec/hyb-oa/project-governance.md", "reason": "Trellis validation and commit rules"} +{"file": ".trellis/spec/hyb-oa/frontend-react-vite-design.md", "reason": "frontend action placement and menu naming verification"} +{"file": ".trellis/spec/hyb-oa/docs-and-evidence.md", "reason": "documentation evidence hygiene"} +{"file": ".trellis/spec/hyb-oa/security-privacy.md", "reason": "sensitive export and permissioned action checks"} diff --git a/.trellis/tasks/05-31-unified-menu-remove-ledger-tools-wave2/implement.jsonl b/.trellis/tasks/05-31-unified-menu-remove-ledger-tools-wave2/implement.jsonl new file mode 100644 index 00000000..38afca72 --- /dev/null +++ b/.trellis/tasks/05-31-unified-menu-remove-ledger-tools-wave2/implement.jsonl @@ -0,0 +1,5 @@ +{"file": ".trellis/spec/hyb-oa/index.md", "reason": "hyb-oa spec entry and read order"} +{"file": ".trellis/spec/hyb-oa/project-governance.md", "reason": "Trellis task, solo-developer scope and commit rules"} +{"file": ".trellis/spec/hyb-oa/frontend-react-vite-design.md", "reason": "frontend visual, template, menu and action-placement rules"} +{"file": ".trellis/spec/hyb-oa/security-privacy.md", "reason": "export and sensitive business action placement constraints"} +{"file": "docs/产品设计规则.md", "reason": "business experience facts and menu/action naming rules"} diff --git a/.trellis/tasks/05-31-unified-menu-remove-ledger-tools-wave2/implement.md b/.trellis/tasks/05-31-unified-menu-remove-ledger-tools-wave2/implement.md new file mode 100644 index 00000000..59d75f8f --- /dev/null +++ b/.trellis/tasks/05-31-unified-menu-remove-ledger-tools-wave2/implement.md @@ -0,0 +1,22 @@ +# 实现说明 + +## 设计口径 + +- 参考 Ant Design / Ant Design Pro 的成熟后台惯例:业务列表首屏保留标题、常用筛选、列表和明确动作,不再使用泛化 `台账工具`。 +- 菜单与页面标题统一短模块名:`采购`、`合同`、`资产`、`付款`、`应付账款`、`票据`。 +- 页面内的具体业务对象仍可使用 `采购申请` 等单据名,避免把对象语义和菜单命名混为一类。 + +## 改造边界 + +- 只改前端入口、文案、测试断言和稳定设计/业务文档。 +- 不改后端权限、审批、付款、OCR、合同、采购或资产状态机。 +- 不做公网部署,除非用户后续单独批准。 + +## 页面迁移 + +- `采购`:原高级入口改为 `筛选`,保留供应商、法体、成本中心和范围筛选。 +- `合同`:页头保留 `新增合同`、`到期列表`、`导出`、`筛选`;到期日与测试数据切换进入高级筛选。 +- `项目`:页头使用 `合同`、`显示测试数据`、`新建项目`;新建表单改为明确的 `新建项目` 面板。 +- `付款`:原入口改为 `新建批次`,批次创建能力不变。 +- `应付账款`:导出直接作为页头 `导出` 下拉,不再包在工具面板。 +- `票据`:页头使用 `上传票据`、`绑定`、`导出`,上传和绑定各自打开明确面板。 diff --git a/.trellis/tasks/05-31-unified-menu-remove-ledger-tools-wave2/prd.md b/.trellis/tasks/05-31-unified-menu-remove-ledger-tools-wave2/prd.md new file mode 100644 index 00000000..e7cb6275 --- /dev/null +++ b/.trellis/tasks/05-31-unified-menu-remove-ledger-tools-wave2/prd.md @@ -0,0 +1,89 @@ +# 统一菜单命名并移除剩余台账工具 + +## Goal + +把 wave1 未覆盖的业务页面继续收敛到成熟后台惯例:全系统不再使用泛化的 `台账工具` 按钮,菜单栏统一使用短模块名,避免同一层级混用 `报销`、`采购申请`、`合同台账` 这类不同粒度名称。 + +## What I Already Know + +- 产品面向国内小于 10 人的科技初创公司,首屏要低复杂度、少解释文案、动作位置稳定。 +- wave1 已完成 `报销` 和 `资产` 页面去 `台账工具`,并新增列表/详情/编辑薄模板规则。 +- 当前仍有 `台账工具` 残留在 `采购`、`合同`、`项目`、`付款`、`应付账款`、`票据`。 +- 当前菜单命名残留不一致:`报销`、`采购申请`、`合同台账`、`资产台账` 等混用。 +- 用户已确认 wave2 规则:菜单统一为短模块名,页面内按具体上下文显示详情或单据名。 + +## Scope + +- 移除剩余业务页面中的用户可见 `台账工具` 文案和按钮。 +- 将原有能力迁移到明确动作:`筛选`、`导出`、`统计`、`新增`、`上传`、`绑定`、`创建批次`、`更多` 或详情页操作区。 +- 统一 PC 导航、服务端导航配置和页面路由标题里的模块命名。 +- 更新 `docs/产品设计规则.md` 和前端 spec,禁止后续再新增泛化 `台账工具`。 +- 更新相关 Playwright 断言,新增或调整防回归检查。 + +## Requirements + +- 菜单栏统一短模块名: + - `报销` + - `采购` + - `合同` + - `项目` + - `资产` + - `付款` + - `应付账款` + - `票据` +- 页面标题也优先使用短模块名;详情页、审批对象、单据正文可以继续使用具体名称如 `采购申请`、`合同详情`、`采购详情`。 +- 不删除原功能,只把入口迁移到具体、可理解的位置。 +- 不改变后端业务逻辑、权限模型、审批规则、付款状态机、OCR 规则或数据结构。 +- 不做公网部署,除非用户后续另行批准。 + +## Acceptance Criteria + +- [x] `client/src/pages/**` 中普通用户页面不再出现可点击的 `台账工具`。 +- [x] PC 菜单和服务端导航返回的业务模块名统一为短模块名。 +- [x] 采购、合同、项目、付款、应付账款、票据的原工具能力仍可通过明确动作访问。 +- [x] `docs/产品设计规则.md` 与 frontend spec 均记录禁用 `台账工具` 和菜单命名规则。 +- [x] `cd client && npm run typecheck` 通过。 +- [x] `cd client && npm run build` 通过。 +- [x] 相关聚焦 Playwright 或静态检查通过。 + +## Out Of Scope + +- 不部署公网。 +- 不重做页面视觉大布局和详情页信息架构。 +- 不改审批、付款、OCR、资产流转、合同归档等业务状态机。 +- 不一次性处理非业务菜单,如后台管理子页面、审计、配置页面的深层命名。 + +## Test Plan + +- `rg -n "台账工具" client/src/pages client/src/components server/src/services/workspaceSettings.js docs/产品设计规则.md .trellis/spec/hyb-oa/frontend-react-vite-design.md` +- `cd client && npm run typecheck` +- `cd client && npm run build` +- 聚焦运行受影响的菜单/视觉/确认类 Playwright 测试。 +- `git diff --check` +- `python3 ./.trellis/scripts/task.py validate 05-31-unified-menu-remove-ledger-tools-wave2` +- `node scripts/qa/check-trellis-governance.mjs` + +## Validation Results + +- `rg -n "台账工具|showLedgerTools|showBatchTools|ledger-tools|合同台账|资产台账|票据中心|AP 运营|ERP 集成" client/src server/src/services/workspaceSettings.js client/src/features/navigation/schema.ts`:无匹配。 +- `cd client && npm run typecheck`:通过。 +- `cd client && npm run build`:通过。 +- `E2E_BASE_URL=http://127.0.0.1:5173 npx playwright test tests/e2e/r103-cross-page-ui-consistency.spec.ts tests/e2e/pc-finance-admin-visual-wave4.spec.ts tests/e2e/visual-shell.spec.ts --workers=1 --reporter=list`:12 passed。 +- `git diff --check`:通过。 +- `python3 ./.trellis/scripts/task.py validate 05-31-unified-menu-remove-ledger-tools-wave2`:通过。 +- `node scripts/qa/check-trellis-governance.mjs`:通过,有 `implement_non_spec_context` 提醒,原因为 `implement.jsonl` 引用了业务事实源 `docs/产品设计规则.md`。 + +## Technical Notes + +- 设计裁决:`frontend-design` skill;成熟后台参考 Ant Design / Ant Design Pro 的命名和动作位置。 +- 重点页面: + - `client/src/pages/PurchaseRequestsPage.tsx` + - `client/src/pages/ContractsRegistryPage.tsx` + - `client/src/pages/ProjectsPage.tsx` + - `client/src/pages/PaymentBatchesPage.tsx` + - `client/src/pages/ApOperationsPage.tsx` + - `client/src/pages/InvoicePoolPage.tsx` +- 菜单配置: + - `client/src/components/navigation/NavLinks.tsx` + - `client/src/features/navigation/schema.ts` + - `server/src/services/workspaceSettings.js` diff --git a/.trellis/tasks/05-31-unified-menu-remove-ledger-tools-wave2/task.json b/.trellis/tasks/05-31-unified-menu-remove-ledger-tools-wave2/task.json new file mode 100644 index 00000000..ece2cc93 --- /dev/null +++ b/.trellis/tasks/05-31-unified-menu-remove-ledger-tools-wave2/task.json @@ -0,0 +1,26 @@ +{ + "id": "unified-menu-remove-ledger-tools-wave2", + "name": "unified-menu-remove-ledger-tools-wave2", + "title": "统一菜单命名并移除剩余台账工具", + "description": "移除采购、合同、项目、付款、应付账款和票据页面剩余的泛化台账工具入口,并统一菜单与页面标题为短模块名。", + "status": "in_progress", + "dev_type": null, + "scope": "frontend", + "package": null, + "priority": "P2", + "creator": "shunli", + "assignee": "shunli", + "createdAt": "2026-05-31", + "completedAt": null, + "branch": null, + "base_branch": "main", + "worktree_path": null, + "commit": null, + "pr_url": null, + "subtasks": [], + "children": [], + "parent": null, + "relatedFiles": [], + "notes": "", + "meta": {} +} diff --git a/client/src/components/GlobalSearchDialog.tsx b/client/src/components/GlobalSearchDialog.tsx index 783fea04..109551de 100644 --- a/client/src/components/GlobalSearchDialog.tsx +++ b/client/src/components/GlobalSearchDialog.tsx @@ -28,7 +28,7 @@ function entityLabel(entityType: string) { if (entityType === "travel_request") return "差旅"; if (entityType === "asset") return "资产"; if (entityType === "project") return "项目"; - if (entityType === "contract_registry") return "合同台账"; + if (entityType === "contract_registry") return "合同"; if (entityType === "confidential_doc_ref") return "资料引用"; return entityType; } diff --git a/client/src/components/navigation/NavLinks.tsx b/client/src/components/navigation/NavLinks.tsx index 2333e33c..e784e27d 100644 --- a/client/src/components/navigation/NavLinks.tsx +++ b/client/src/components/navigation/NavLinks.tsx @@ -29,8 +29,8 @@ export const NAV_ITEMS: NavItem[] = [ { to: "/projects", label: "项目", icon: "workspaces", anyOf: ["project.view_own", "project.view_all", "project.manage"] }, { to: "/system/settings", label: "管理", icon: "settings", anyOf: ["system.settings", "system.users", "masterdata.manage", "notification.channel.manage", "integration.manage", "system.audit.view"] }, { to: "/workflow/tasks", label: "待我处理", icon: "fact_check", anyOf: ["expense.approve", "leave.approve", "attendance.approve", "procurement.approve", "travel.approve"] }, - { to: "/contracts-registry", label: "合同台账", icon: "description", anyOf: ["project.view_all", "project.manage", "system.settings"] }, - { to: "/purchase-requests", label: "采购申请", icon: "shopping_cart", anyOf: ["procurement.create", "procurement.view_own", "procurement.view_all", "procurement.manage"] }, + { to: "/contracts-registry", label: "合同", icon: "description", anyOf: ["project.view_all", "project.manage", "system.settings"] }, + { to: "/purchase-requests", label: "采购", icon: "shopping_cart", anyOf: ["procurement.create", "procurement.view_own", "procurement.view_all", "procurement.manage"] }, { to: "/travel-requests", label: "差旅申请", icon: "flight", anyOf: ["travel.view_all", "travel.manage", "travel.payment.manage"] }, { to: "/budgets", label: "预算管理", icon: "account_balance", anyOf: ["budget.view", "budget.manage"] }, { to: "/payment-batches", label: "付款", icon: "payments", anyOf: ["payment.batch.manage", "risk.review.manage"] }, @@ -43,7 +43,7 @@ export const NAV_ITEMS: NavItem[] = [ const ACCESSIBLE_LABELS: Record = { "/reimbursements": "我的报销", "/workflow/tasks": "待我处理", - "/assets": "资产台账", + "/assets": "资产", }; const MANAGEMENT_ACTIVE_PATHS = [ diff --git a/client/src/features/navigation/schema.ts b/client/src/features/navigation/schema.ts index cfabf7e7..a6c9a5c6 100644 --- a/client/src/features/navigation/schema.ts +++ b/client/src/features/navigation/schema.ts @@ -9,8 +9,8 @@ const PAGE_TITLES: Array<{ path: string; title: string; group: string }> = [ { path: "/attendance", title: "考勤", group: "业务" }, { path: "/wecom-approval-archive", title: "企微审批历史", group: "业务" }, { path: "/projects", title: "项目", group: "业务" }, - { path: "/contracts-registry", title: "合同台账", group: "业务" }, - { path: "/purchase-requests", title: "采购申请", group: "业务" }, + { path: "/contracts-registry", title: "合同", group: "业务" }, + { path: "/purchase-requests", title: "采购", group: "业务" }, { path: "/travel-requests", title: "差旅申请", group: "更多" }, { path: "/budgets", title: "预算管理", group: "财务" }, { path: "/payment-batches", title: "付款", group: "财务" }, diff --git a/client/src/pages/ApOperationsPage.tsx b/client/src/pages/ApOperationsPage.tsx index 1e1fa105..d28aa183 100644 --- a/client/src/pages/ApOperationsPage.tsx +++ b/client/src/pages/ApOperationsPage.tsx @@ -44,6 +44,7 @@ import { ConfirmDialog } from "../components/common/ConfirmDialog"; import { DateTextInput } from "../components/common/DateTextInput"; import { ListWorkspace, WorkspaceHeader } from "../components/patterns/ListWorkspace"; import { Button } from "../components/ui/button"; +import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuTrigger } from "../components/ui/dropdown-menu"; import { Dialog, DialogContent, @@ -258,7 +259,6 @@ export function ApOperationsPage() { const [ledgerDateTo, setLedgerDateTo] = useState(""); const [ledgerSearch, setLedgerSearch] = useState(""); const [ledgerPage, setLedgerPage] = useState(1); - const [showLedgerTools, setShowLedgerTools] = useState(false); const [showLedgerAdvancedFilters, setShowLedgerAdvancedFilters] = useState(false); const [reviewTarget, setReviewTarget] = useState(null); @@ -605,13 +605,26 @@ export function ApOperationsPage() { > 刷新 - + + + + + + exportMutation.mutate("csv")}> + 导出 CSV + + exportMutation.mutate("xlsx")}> + 导出 Excel + + + } /> @@ -629,34 +642,6 @@ export function ApOperationsPage() { ) : null} - {showLedgerTools ? ( -
-
-
-

台账工具

-
-
- - -
-
-
- ) : null} -
setActiveTab(value as OperationsTab)}> diff --git a/client/src/pages/AssetRegistryPage.tsx b/client/src/pages/AssetRegistryPage.tsx index 578dd741..c4b2ba6f 100644 --- a/client/src/pages/AssetRegistryPage.tsx +++ b/client/src/pages/AssetRegistryPage.tsx @@ -725,7 +725,7 @@ export function AssetRegistryPage() { return ( {isAssetDetailMode ? ( diff --git a/client/src/pages/ContractsRegistryPage.tsx b/client/src/pages/ContractsRegistryPage.tsx index 8459fb1b..90431175 100644 --- a/client/src/pages/ContractsRegistryPage.tsx +++ b/client/src/pages/ContractsRegistryPage.tsx @@ -216,7 +216,7 @@ export function ContractsRegistryPage() { const [editor, setEditor] = useState(() => buildEmptyEditor(initialProjectId)); const [showExpiring, setShowExpiring] = useState(false); const [showTrialData, setShowTrialData] = useState(false); - const [showLedgerTools, setShowLedgerTools] = useState(false); + const [showAdvancedFilters, setShowAdvancedFilters] = useState(false); const [showEditor, setShowEditor] = useState(false); const [confirmExportFormat, setConfirmExportFormat] = useState(null); const [confirmStatusTarget, setConfirmStatusTarget] = useState(null); @@ -293,9 +293,9 @@ export function ContractsRegistryPage() { await refreshAll(); setShowEditor(false); resetEditor(); - toast.success(editorId ? "合同台账已更新" : "合同台账已创建"); + toast.success(editorId ? "合同已更新" : "合同已创建"); }, - onError: (error) => toast.error(resolveError(error, editorId ? "更新合同台账失败" : "创建合同台账失败")), + onError: (error) => toast.error(resolveError(error, editorId ? "更新合同失败" : "创建合同失败")), }); const toggleStatusMutation = useMutation({ @@ -324,15 +324,15 @@ export function ContractsRegistryPage() { }), onSuccess: (response, format) => { downloadBlob(response.blob, response.filename ?? `contracts-registry.${format}`); - toast.success(`合同台账已导出为 ${format.toUpperCase()}`); + toast.success(`合同已导出为 ${format.toUpperCase()}`); }, - onError: (error) => toast.error(resolveError(error, "导出合同台账失败")), + onError: (error) => toast.error(resolveError(error, "导出合同失败")), }); return ( {initialProjectId ? ( @@ -349,73 +349,58 @@ export function ContractsRegistryPage() { > 新增合同 - + + + + + + setConfirmExportFormat("csv")}> + 导出 CSV + + setConfirmExportFormat("xlsx")}> + 导出 Excel + + + +
} /> - {showLedgerTools ? ( -
-
-
-

台账工具

-
-
- - -
-
-
-
- - setFilters((current) => ({ ...current, expiryDateFrom: event.target.value }))} - /> -
- -
- - setFilters((current) => ({ ...current, expiryDateTo: event.target.value }))} - /> -
-
- {expiringQuery.isError ? ( -
{resolveError(expiringQuery.error, "加载到期预警失败")}
- ) : null} - {showExpiring ? ( -
- {expiringItems.map((item) => ( -
-
-
{item.contractNo} · {item.counterpartyName}
-
到期日 {formatDisplayDate(item.expiryDate)} · 责任人 {item.owner?.name ?? "-"}
-
- -
- ))} - {!expiringQuery.isLoading && !expiringItems.length ? ( -
暂无到期合同
- ) : null} -
+ ))} + {!expiringQuery.isLoading && !expiringItems.length ? ( +
暂无到期合同
) : null}
) : null} @@ -425,7 +410,7 @@ export function ContractsRegistryPage() {
-

{editorId ? "编辑合同台账" : "新建合同台账"}

+

{editorId ? "编辑合同" : "新建合同"}

补齐合同类型、金额、签署/生效/到期日期,续约将自动创建新的草稿合同。

@@ -676,7 +661,7 @@ export function ContractsRegistryPage() { } onClick={() => saveMutation.mutate()} > - {saveMutation.isPending ? (editorId ? "保存中..." : "创建中...") : editorId ? "保存修改" : "创建合同台账"} + {saveMutation.isPending ? (editorId ? "保存中..." : "创建中...") : editorId ? "保存修改" : "创建合同"}
) : null} @@ -704,13 +689,33 @@ export function ContractsRegistryPage() { > 重置筛选 - {showLedgerTools ? ( + + } + advanced={showAdvancedFilters || filters.expiryDateFrom || filters.expiryDateTo || showTrialData ? ( +
+
+ + setFilters((current) => ({ ...current, expiryDateFrom: event.target.value }))} + /> +
+ +
+ + setFilters((current) => ({ ...current, expiryDateTo: event.target.value }))} + /> +
+ +
- ) : null} - - } +
+
+ ) : null} >
@@ -774,7 +779,7 @@ export function ContractsRegistryPage() { {contractsQuery.isLoading ?
加载中...
: null} {contractsQuery.isError ? ( -
{resolveError(contractsQuery.error, "加载合同台账失败")}
+
{resolveError(contractsQuery.error, "加载合同失败")}
) : null} {!showTrialData && hiddenTrialContractCount > 0 ? ( @@ -804,7 +809,7 @@ export function ContractsRegistryPage() { ))} {!contractsQuery.isLoading && !visibleContracts.length ? ( -
暂无合同台账
+
暂无合同
) : null} @@ -884,7 +889,7 @@ export function ContractsRegistryPage() { ) : ( - 暂无合同台账 + 暂无合同 )} @@ -898,7 +903,7 @@ export function ContractsRegistryPage() { if (!open) setConfirmExportFormat(null); }} kind="sensitiveExport" - targetName="合同台账" + targetName="合同" description="导出文件可能包含合同金额、相对方、项目归属、责任人和归档位置摘要。请确认当前筛选范围和用途合规。" onConfirm={() => { if (!confirmExportFormat) return; diff --git a/client/src/pages/InvoicePoolPage.tsx b/client/src/pages/InvoicePoolPage.tsx index f7525f3e..4ecae22f 100644 --- a/client/src/pages/InvoicePoolPage.tsx +++ b/client/src/pages/InvoicePoolPage.tsx @@ -87,7 +87,7 @@ export function InvoicePoolPage() { const [editTotalAmount, setEditTotalAmount] = useState(""); const [error, setError] = useState(""); const [confirmAction, setConfirmAction] = useState<{ type: string; invoiceId: string } | null>(null); - const [showLedgerTools, setShowLedgerTools] = useState(false); + const [invoiceActionPanel, setInvoiceActionPanel] = useState<"upload" | "bind" | null>(null); const previousDetailStatusRef = useRef(null); // --- Queries --- @@ -473,7 +473,7 @@ export function InvoicePoolPage() { return ( {isInvoiceDetailMode ? ( @@ -481,9 +481,42 @@ export function InvoicePoolPage() { 返回列表 ) : ( - + <> + + + + + + + + exportMutation.mutate("csv")}> + 导出 CSV + + exportMutation.mutate("xlsx")}> + 导出 Excel + + + + )}
} @@ -495,66 +528,45 @@ export function InvoicePoolPage() {

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

台账工具

-
- - - - - - exportMutation.mutate("csv")}> - 导出 CSV - - exportMutation.mutate("xlsx")}> - 导出 Excel - - - -
- uploadMutation.mutate()} - showProxyOwnerSelector={showProxyOwnerSelector} - proxyDelegationsQuery={proxyDelegationsQuery} - uploadOwnerEmployeeId={uploadOwnerEmployeeId} - setUploadOwnerEmployeeId={setUploadOwnerEmployeeId} - /> - + {!isInvoiceDetailMode && invoiceActionPanel ? ( +
+ {invoiceActionPanel === "upload" ? ( + uploadMutation.mutate()} + showProxyOwnerSelector={showProxyOwnerSelector} + proxyDelegationsQuery={proxyDelegationsQuery} + uploadOwnerEmployeeId={uploadOwnerEmployeeId} + setUploadOwnerEmployeeId={setUploadOwnerEmployeeId} + /> + ) : null} + {invoiceActionPanel === "bind" ? ( + + ) : null}
) : null} diff --git a/client/src/pages/PaymentBatchesPage.tsx b/client/src/pages/PaymentBatchesPage.tsx index 7a222f61..398a2e38 100644 --- a/client/src/pages/PaymentBatchesPage.tsx +++ b/client/src/pages/PaymentBatchesPage.tsx @@ -117,7 +117,7 @@ export function PaymentBatchesPage() { const receiptInputRef = useRef(null); const [selectedReceiptFiles, setSelectedReceiptFiles] = useState([]); const [error, setError] = useState(""); - const [showBatchTools, setShowBatchTools] = useState(false); + const [showCreatePanel, setShowCreatePanel] = useState(false); const [confirmPaymentAction, setConfirmPaymentAction] = useState<{ kind: "financeReview" | "financePayment" | "cancel"; batchId: string; @@ -356,8 +356,8 @@ export function PaymentBatchesPage() { 刷新 {!selectedBatchId ? ( - ) : null}
@@ -366,7 +366,7 @@ export function PaymentBatchesPage() { {error ?
{error}
: null} - {!selectedBatchId && showBatchTools ? ( + {!selectedBatchId && showCreatePanel ? (

新建批次

diff --git a/client/src/pages/ProjectDetailPage.tsx b/client/src/pages/ProjectDetailPage.tsx index 7e7a82f5..81f38638 100644 --- a/client/src/pages/ProjectDetailPage.tsx +++ b/client/src/pages/ProjectDetailPage.tsx @@ -596,7 +596,7 @@ export function ProjectDetailPage() { 返回项目
-
合同台账
+
合同
{contractsQuery.data?.items.length ?? 0}
{(contractsQuery.data?.items ?? []).slice(0, 5).map((item) => ( diff --git a/client/src/pages/ProjectsPage.tsx b/client/src/pages/ProjectsPage.tsx index 15772b3f..82681982 100644 --- a/client/src/pages/ProjectsPage.tsx +++ b/client/src/pages/ProjectsPage.tsx @@ -82,7 +82,7 @@ export function ProjectsPage() { const [targetEndAt, setTargetEndAt] = useState(""); const [isConfidential, setIsConfidential] = useState(false); const [showTrialData, setShowTrialData] = useState(false); - const [showLedgerTools, setShowLedgerTools] = useState(false); + const [showCreatePanel, setShowCreatePanel] = useState(false); const listQuery = useQuery({ queryKey: ["projects", "list", status, mine, q], @@ -181,79 +181,76 @@ export function ProjectsPage() { actions={
- + {canManageProjects ? ( + + ) : null}
} /> - {showLedgerTools ? ( - + {showCreatePanel ? ( +
- {!canManageProjects ? ( -
你当前只有查看权限,项目创建入口仅对老板 / 管理员 / 项目管理角色开放。
- ) : ( - <> -
-
- - setName(event.target.value)} placeholder="例如:深圳某客户现场实施" /> -
-
- - setClientName(event.target.value)} placeholder="客户全称" /> -
-
- - setClientAlias(event.target.value)} placeholder="可选" /> -
-
- - -
-
-
-
- - setStartsAt(event.target.value)} /> -
-
- - setTargetEndAt(event.target.value)} /> -
-
-
- -