From 1428cc4ffab31791acc7a4f902c49bcc4fbac457 Mon Sep 17 00:00:00 2001 From: shunli Date: Sun, 31 May 2026 14:10:10 +0800 Subject: [PATCH] =?UTF-8?q?=E7=BB=9F=E4=B8=80=E7=BC=96=E8=BE=91=E6=A8=A1?= =?UTF-8?q?=E6=9D=BF=E5=B9=B6=E7=A7=BB=E9=99=A4=E9=A6=96=E6=89=B9=E5=8F=B0?= =?UTF-8?q?=E8=B4=A6=E5=B7=A5=E5=85=B7=EF=BC=8805-31-business-edit-templat?= =?UTF-8?q?e-remove-ledger-tools-wave1=EF=BC=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../spec/hyb-oa/frontend-react-vite-design.md | 59 +++-- .../check.jsonl | 5 + .../implement.jsonl | 4 + .../implement.md | 14 ++ .../prd.md | 82 +++++++ .../task.json | 31 +++ .../patterns/BusinessEditTemplate.tsx | 162 +++++++++++++ client/src/pages/AssetRegistryPage.tsx | 165 ++++++++----- client/src/pages/ReimbursementsPage.tsx | 229 +++++++++--------- client/tests/e2e/assets-flow.spec.ts | 4 +- .../e2e/manual/asset1d-public-trial.spec.ts | 1 + .../e2e/system-action-confirmation.spec.ts | 1 - client/tests/e2e/visual-shell.spec.ts | 4 +- 13 files changed, 556 insertions(+), 205 deletions(-) create mode 100644 .trellis/tasks/05-31-business-edit-template-remove-ledger-tools-wave1/check.jsonl create mode 100644 .trellis/tasks/05-31-business-edit-template-remove-ledger-tools-wave1/implement.jsonl create mode 100644 .trellis/tasks/05-31-business-edit-template-remove-ledger-tools-wave1/implement.md create mode 100644 .trellis/tasks/05-31-business-edit-template-remove-ledger-tools-wave1/prd.md create mode 100644 .trellis/tasks/05-31-business-edit-template-remove-ledger-tools-wave1/task.json create mode 100644 client/src/components/patterns/BusinessEditTemplate.tsx diff --git a/.trellis/spec/hyb-oa/frontend-react-vite-design.md b/.trellis/spec/hyb-oa/frontend-react-vite-design.md index ec31f53e..32f5cfae 100644 --- a/.trellis/spec/hyb-oa/frontend-react-vite-design.md +++ b/.trellis/spec/hyb-oa/frontend-react-vite-design.md @@ -74,11 +74,13 @@ reference, not a copy of Feishu or Ant Design branding. - List pages use a right-side operation column or card tail. Default row actions are `查看详情`, `处理`, `继续编辑` or `查看进度`; low-frequency or risky actions such as export, batch, void, download, payment and sensitive attachment - handling move to `更多`, `台账工具` or the detail page. + handling move to `更多`, clearly named permissioned actions, management + entries or the detail page. - Business ledger pages use the same shape: page title and optional creation action, visible common filters, table/card list and right-side operation - column. `台账工具` contains export, batch, statistics, templates, full-scope - views and advanced filters. + column. Do not use a generic `台账工具` entry; expose low-frequency abilities + through concrete names such as `导出`, `统计`, `筛选`, `导入`, `新建方式`, + `分类管理`, `批量处理` or `更多`. - Business ledger first screens are single-column list pages. Do not use a default left-list/right-detail workspace for procurement, contracts, projects, assets, payments, AP or invoices. Open details through a detail route, drawer @@ -90,8 +92,8 @@ reference, not a copy of Feishu or Ant Design branding. - Ledger filters fit the first PC row around `search`, `status` and `scope` when those capabilities exist. Supplier, legal entity, cost center, date ranges, contract type, sensitivity, export and test-data toggles belong in - `筛选`, `台账工具` or a tab-specific advanced area; default-visible filters - should not occupy multiple rows. + `筛选`, `更多`, clearly named role-only actions or a tab-specific advanced + area; default-visible filters should not occupy multiple rows. - Business list columns show object identity, counterpart/person, amount/date, state and operation. `下一步` is not a default list column or right-side help panel. Use short state labels such as `待补材料` or `待你处理`; longer guidance @@ -118,7 +120,7 @@ reference, not a copy of Feishu or Ant Design branding. | `工作台` | 工作台总览 | Compact counts, common entries, real reminders | No forced header action. Use `去处理`, `新建报销`, `查看全部` inside relevant sections; do not show helper sentences for ordinary items. | | `待我处理` | 任务处理列表 | `待处理` / `已处理` tabs, count, minimal filters, task list | Row action is `处理`; `已处理记录` is a tab, not a separate header button. | | `审批详情` | 详情工作区 | Summary, facts/evidence, right summary/action panel, history | One action bar: primary `通过`, secondary `退回`/`驳回`, `转审` in `更多`. | -| `报销` / `采购` / `合同` / `项目` / `资产` / `付款` / `应付账款` / `票据` | 业务台账列表 | One-row common filters plus table/card list | Header creation only when the route owns creation; risky tools in `台账工具` or detail; no default left-list/right-detail split. | +| `报销` / `采购` / `合同` / `项目` / `资产` / `付款` / `应付账款` / `票据` | 业务台账列表 | One-row common filters plus table/card list | Header creation only when the route owns creation; risky tools use `更多`, clearly named role-only actions or detail pages; no default left-list/right-detail split. | | Business details | 详情工作区 | Summary, facts, evidence, relations, history | Right-side state/action panel on PC; bottom primary action on mobile. | | `管理` | 管理配置页 | Grouped landing: `人员组织`, `审批流程`, `基础资料`, `系统设置`, `审计排障` | Management subpages use search/filter, table and right operation column. | | `个人设置` | 管理配置页 variant | `个人资料`, `账号安全`, `通知偏好`, `企业微信绑定` | No employee dossier, permission matrix or org maintenance in the default profile path. | @@ -339,11 +341,23 @@ matrices and actions explicit in each page. main facts/evidence/history on the left, status/actions/compact summary on the right. Keep the page's business fields, permission checks, state conditions and API calls explicit in the page component. +- For business edit/create pages, prefer the thin components in + `client/src/components/patterns/BusinessEditTemplate.tsx`: + `BusinessEditTemplate`, `BusinessEditWorkspace`, `BusinessEditMain`, + `BusinessEditAside`, `BusinessEditSection`, `BusinessEditFormGrid`, + `BusinessEditValidationSummary` and `BusinessEditActionBar`. They standardize + title/back/status/action, sectioned form layout, validation summary and bottom + save/submit coordinates only; fields, permissions, mutations and dirty-state + decisions stay explicit in the page. - `BusinessDetailActionPanel` is the default PC detail operation surface. Use one primary action for save/create when the detail owns editing; use a horizontal action bar for approval actions; move low-frequency actions into `更多` or a clearly named grouped section. Do not add a second page-local `操作` card or a separate `摘要` card under the same right column. +- Do not introduce or preserve a generic `台账工具` button on redesigned business + pages. Move each ability to a concrete enterprise action coordinate: `新建` / + `新建方式`, `筛选`, `导出`, `统计`, `批量处理`, row `更多`, detail action panel + or an explicitly named management entry such as `资产分类`. - Do not create a configuration-driven page generator for reimbursement, procurement, contracts, projects, assets, payments, AP or invoices. These pages have different permissions, state transitions, attachment rules and @@ -452,7 +466,7 @@ page-local action placement. - List filters live in the first command bar below the page header. Search, status, scope and other common filters stay on the left; ledger tools such as export, batch, statistics, templates and advanced filters stay on the right or - inside `台账工具` / `更多`. + inside `筛选`, `更多` or concrete role-only action groups. - Row-level actions live at the right edge of a table row or at the tail of a card. Prefer `查看详情`, `查看处理` and `继续编辑` over broad verbs that hide the result. @@ -567,8 +581,8 @@ of exposing every allowed tool at once. they must not duplicate approval action buttons. - Finance and cashier views may expose authorized reimbursement, payment, invoice, AP, receipt and exception operations. Full-scope, export, batch, - delegated creation and statistics tools belong in `台账工具`, `更多` or - role-only panels. + delegated creation and statistics tools belong in concrete actions such as + `导出`, `统计`, `批量处理`, `新建方式`, `更多` or role-only panels. - Owner and project-lead views emphasize team/project scope, blockers, risks and approvals. They should not drift into system configuration unless the route is a management route. @@ -666,8 +680,8 @@ screen. to the current user and the next action. - Place common filters on the left side of the command bar below the page header. Put export, batch, statistics, templates, full-scope, advanced - filters, delegated creation and low-frequency module links in `台账工具`, - `更多` or role-only panels. + filters, delegated creation and low-frequency module links in `筛选`, + concrete right-side actions, `更多` or role-only panels. - Batch actions appear only after selection, as a contextual command bar or bottom sticky bar. High-risk batch actions such as approve, pay, delete or publish require summary confirmation and result details. @@ -686,13 +700,13 @@ screen. boundary. The list is for discovery, filtering, status reading and navigation. High-risk or sensitive actions such as contract download, archive, payment, export, batch processing, void/cancel and sensitive attachment viewing belong - in detail workspaces, detail command panels or `台账工具`, with permission, - audit and confirmation. + in detail workspaces, detail command panels, concrete role-only actions or + `更多`, with permission, audit and confirmation. - Default ledger scope follows the current task role: ordinary employees see `我发起` / `我相关`, owners and project leads see `我负责` / `项目相关`, finance sees `待复核` / `待付款` / `异常`, and admins may enter or switch to `全范围`. - Full scope, export and batch commands stay in `台账工具` or role-only panels, - not ordinary first screens. + Full scope, export and batch commands stay in concrete role-only actions or + panels, not ordinary first screens. - Contract ledgers in V1 are registration, archive, due/renewal reminder and relation surfaces for project, procurement and payment context. Do not design `/contracts-registry` as e-signature, legal-review, clause-library, @@ -725,8 +739,8 @@ screen. creation; `应付账款` shows pending payment, exceptions, risks and differences before export; `票据` shows OCR status, failed invoices and bindable invoices before broad-scope handover tools. Batch creation, export, full-scope views - and agency-accounting handover controls live in `台账工具` or selected-context - panels, never as the first-screen primary action. + and agency-accounting handover controls live in concrete role-only actions, + `更多` or selected-context panels, never as the first-screen primary action. - Payment actions require the finance/payment context: amount, payee or target object, source records, offline payment explanation, evidence completeness, receipt requirement and correction boundary. Do not place `标记已付款`, @@ -749,7 +763,8 @@ and management workflows. - `cd client && npm run typecheck` - `cd client && npm run build` - Focused Playwright or screenshot checks when changing list filters, command - bars, `台账工具`, batch selection behavior, empty states or mobile list cards. + bars, advanced action placement, batch selection behavior, empty states or + mobile list cards. ## Convention: Asset Registry Pages @@ -920,8 +935,8 @@ state, facts and consequences before acting. - List and ledger rows show only evidence completeness summaries, missing evidence hints and a detail entry. Invoice preview, attachment download, contract file viewing, receipt download, evidence deletion and bulk packaging - belong in detail workspaces, detail command panels, `台账工具` or authorized - audit paths. + belong in detail workspaces, detail command panels, `更多`, concrete + role-only actions or authorized audit paths. - Sensitive evidence such as invoices, contracts, receipts and attachments must be permissioned; download and export belong in `更多` or audit paths when appropriate. @@ -1070,8 +1085,8 @@ summary unless a permissioned workflow requires more. - Download, batch download, export, print, attachment packaging, contract text export and finance ledger export must be controlled by server permissions and data scope. -- Export is never the primary first-screen action. Put exports in `台账工具`, - `更多` or role-only finance/admin panels. +- Export is never the primary first-screen action. Put exports in a concrete + `导出` action, `更多` or role-only finance/admin panels. - Before exporting sensitive ledgers, explain scope and sensitive contents. Finance, contract, asset and invoice exports should write audit records. - Export file names use business numbers and dates. Do not concatenate complete diff --git a/.trellis/tasks/05-31-business-edit-template-remove-ledger-tools-wave1/check.jsonl b/.trellis/tasks/05-31-business-edit-template-remove-ledger-tools-wave1/check.jsonl new file mode 100644 index 00000000..81ae621b --- /dev/null +++ b/.trellis/tasks/05-31-business-edit-template-remove-ledger-tools-wave1/check.jsonl @@ -0,0 +1,5 @@ +{"file": ".trellis/spec/hyb-oa/index.md", "reason": "hyb-oa spec entry and task read order"} +{"file": ".trellis/spec/hyb-oa/project-governance.md", "reason": "Trellis task, solo-developer scope, commit and validation rules"} +{"file": ".trellis/spec/hyb-oa/frontend-react-vite-design.md", "reason": "frontend template, navigation, list/detail/edit and visual design rules"} +{"file": ".trellis/spec/hyb-oa/docs-and-evidence.md", "reason": "evidence and task documentation hygiene"} +{"file": ".trellis/spec/hyb-oa/security-privacy.md", "reason": "export and sensitive ledger action placement constraints"} diff --git a/.trellis/tasks/05-31-business-edit-template-remove-ledger-tools-wave1/implement.jsonl b/.trellis/tasks/05-31-business-edit-template-remove-ledger-tools-wave1/implement.jsonl new file mode 100644 index 00000000..f1ee6637 --- /dev/null +++ b/.trellis/tasks/05-31-business-edit-template-remove-ledger-tools-wave1/implement.jsonl @@ -0,0 +1,4 @@ +{"file": ".trellis/spec/hyb-oa/index.md", "reason": "hyb-oa spec entry and task read order"} +{"file": ".trellis/spec/hyb-oa/project-governance.md", "reason": "Trellis task, solo-developer scope, commit and validation rules"} +{"file": ".trellis/spec/hyb-oa/frontend-react-vite-design.md", "reason": "frontend template, navigation, list/detail/edit and visual design rules"} +{"file": ".trellis/spec/hyb-oa/security-privacy.md", "reason": "export and sensitive ledger action placement constraints"} diff --git a/.trellis/tasks/05-31-business-edit-template-remove-ledger-tools-wave1/implement.md b/.trellis/tasks/05-31-business-edit-template-remove-ledger-tools-wave1/implement.md new file mode 100644 index 00000000..332e6637 --- /dev/null +++ b/.trellis/tasks/05-31-business-edit-template-remove-ledger-tools-wave1/implement.md @@ -0,0 +1,14 @@ +# Implementation Notes + +## Plan + +1. Add thin edit-template components that standardize form page coordinates without becoming a JSON form generator. +2. Remove `台账工具` from reimbursement and asset list pages and migrate its contents into explicit actions, advanced filter areas, row actions or page header actions. +3. Update frontend design specs so future pages follow the same template and action-placement rules. +4. Run frontend typecheck/build and focused text checks for the first batch. + +## Design Decisions + +- The edit template owns layout only: header/action strip, section spacing, validation summary, form grid and sticky bottom action bar. +- Business pages keep their fields, permissions, mutations, state transitions and error handling explicitly in page code. +- The generic `台账工具` name is removed from first-batch user-facing pages; every action should use a concrete verb such as `导出`, `统计`, `批量处理`, `新建`, `导入` or `更多`. diff --git a/.trellis/tasks/05-31-business-edit-template-remove-ledger-tools-wave1/prd.md b/.trellis/tasks/05-31-business-edit-template-remove-ledger-tools-wave1/prd.md new file mode 100644 index 00000000..f18b7e20 --- /dev/null +++ b/.trellis/tasks/05-31-business-edit-template-remove-ledger-tools-wave1/prd.md @@ -0,0 +1,82 @@ +# 建立业务编辑模板并移除台账工具首批 + +## Goal + +按已确认的 frontend-design 规则建立业务编辑页模板,并先在报销、资产两个高频页面取消 `台账工具` 入口,把原有能力迁移到具体、可理解的动作位置,降低国内小团队日常使用复杂度。 + +## What I Already Know + +- 面向对象是国内小于 10 人的科技初创公司,系统应优先低复杂度、高频动作和成熟后台惯例。 +- 当前已有列表模板 `BusinessListTemplate.tsx` 和详情模板 `BusinessDetailTemplate.tsx`。 +- 当前还没有统一编辑模板。 +- 用户明确不想要 `台账工具` 这个泛化入口。 +- 本轮采用成熟 Ant Design / Ant Design Pro 式后台结构:页头主动作、状态/范围切换、筛选、表格、选中后批量条、详情右侧操作区、编辑页分区表单。 + +## Scope + +- 新增 `BusinessEditTemplate` 及配套编辑页布局组件。 +- 首批只迁移报销列表和资产列表中的 `台账工具` 入口。 +- 更新 frontend 设计规范,固化三类模板和 `台账工具` 禁用规则。 +- 补充或调整必要的前端测试断言。 + +## Requirements + +- 列表、详情、编辑模板必须只统一布局和交互槽位,不做 JSON 配置式页面生成器。 +- `台账工具` 按钮名不得继续出现在报销、资产首批页面。 +- 报销列表: + - `新建报销` 保持页头主按钮。 + - 更多创建方式归入 `新建报销` 的下拉或明确命名入口。 + - 导出使用明确的 `导出` 入口。 + - 全范围/仅自己/审批中台账使用范围或筛选动作,不藏在 `台账工具`。 +- 资产列表: + - 常用列表筛选和状态切换保持在列表上方。 + - 导出、盘点、导入等能力使用具体动作名或详情/管理入口,不使用 `台账工具`。 +- 编辑模板应支持: + - 标题、返回、状态; + - 右上角操作; + - 底部固定操作条; + - 分区表单; + - 校验摘要; + - 未保存提醒接入点。 + +## Acceptance Criteria + +- [x] 新增业务编辑模板组件并可被后续页面复用。 +- [x] 报销页不再出现 `台账工具` 文案,原功能仍可通过具体按钮/下拉访问。 +- [x] 资产页不再出现 `台账工具` 文案,原功能仍可通过具体按钮/下拉访问或合理迁移。 +- [x] 前端规范记录三类模板和 `台账工具` 禁用规则。 +- [x] `cd client && npm run typecheck` 通过。 +- [x] `cd client && npm run build` 通过。 +- [x] 相关 Playwright/静态检查按需通过。 + +## Out Of Scope + +- 不一次性迁移合同、采购、付款、应付账款、票据和项目。 +- 不重做业务状态机、审批规则或后端接口。 +- 不把所有业务字段改成配置生成。 +- 不做公网部署,除非用户后续明确批准。 + +## Test Plan + +- `cd client && npm run typecheck` +- `cd client && npm run build` +- `rg -n "台账工具" client/src/pages/ReimbursementsPage.tsx client/src/pages/AssetRegistryPage.tsx` +- 运行受影响页面相关的聚焦 Playwright 测试,或新增静态/组件断言覆盖入口文案。 + +## Technical Notes + +- 设计裁决:`frontend-design` skill。 +- 现有列表模板:`client/src/components/patterns/BusinessListTemplate.tsx` +- 现有详情模板:`client/src/components/patterns/BusinessDetailTemplate.tsx` +- 首批页面:`client/src/pages/ReimbursementsPage.tsx`、`client/src/pages/AssetRegistryPage.tsx` + +## Validation Result + +- `cd client && npm run typecheck`:通过。 +- `cd client && npm run build`:通过。 +- `rg -n "台账工具|showLedgerTools|asset-ledger-tools|更多创建方式" client/src/pages/ReimbursementsPage.tsx client/src/pages/AssetRegistryPage.tsx`:无结果。 +- `git diff --check`:通过。 +- `python3 ./.trellis/scripts/task.py validate 05-31-business-edit-template-remove-ledger-tools-wave1`:通过。 +- `node scripts/qa/check-trellis-governance.mjs`:通过。 +- `cd client && npx playwright test tests/e2e/system-action-confirmation.spec.ts --workers=1 --reporter=list`:2 passed。 +- `cd client && npx playwright test tests/e2e/visual-shell.spec.ts --workers=1 --reporter=list`:5 passed。 diff --git a/.trellis/tasks/05-31-business-edit-template-remove-ledger-tools-wave1/task.json b/.trellis/tasks/05-31-business-edit-template-remove-ledger-tools-wave1/task.json new file mode 100644 index 00000000..00d89bc6 --- /dev/null +++ b/.trellis/tasks/05-31-business-edit-template-remove-ledger-tools-wave1/task.json @@ -0,0 +1,31 @@ +{ + "id": "business-edit-template-remove-ledger-tools-wave1", + "name": "business-edit-template-remove-ledger-tools-wave1", + "title": "建立业务编辑模板并移除台账工具首批", + "description": "Build the first business edit template and remove generic ledger-tools entry points from reimbursement and asset list pages.", + "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": [ + "client/src/components/patterns/BusinessEditTemplate.tsx", + "client/src/pages/ReimbursementsPage.tsx", + "client/src/pages/AssetRegistryPage.tsx", + ".trellis/spec/hyb-oa/frontend-react-vite-design.md" + ], + "notes": "", + "meta": {} +} \ No newline at end of file diff --git a/client/src/components/patterns/BusinessEditTemplate.tsx b/client/src/components/patterns/BusinessEditTemplate.tsx new file mode 100644 index 00000000..ee399268 --- /dev/null +++ b/client/src/components/patterns/BusinessEditTemplate.tsx @@ -0,0 +1,162 @@ +import type { ReactNode } from "react"; +import { Link } from "react-router-dom"; +import { cn } from "../../lib/utils"; +import { Button } from "../ui/button"; +import { WorkspaceHeader } from "./ListWorkspace"; +import { SectionPanel } from "./SectionPanel"; + +export function BusinessEditTemplate({ + eyebrow, + title, + backTo, + backLabel = "返回", + status, + actions, + meta, + validation, + bottomActions, + children, + className, + testId, +}: { + eyebrow?: string; + title: string; + backTo?: string; + backLabel?: string; + status?: ReactNode; + actions?: ReactNode; + meta?: ReactNode; + validation?: ReactNode; + bottomActions?: ReactNode; + children: ReactNode; + className?: string; + testId?: string; +}) { + const headerActions = actions || status || backTo ? ( +
+ {status ?
{status}
: null} + {actions} + {backTo ? ( + + ) : null} +
+ ) : null; + + return ( +
+ + {validation ? {validation} : null} + {children} + {bottomActions ? {bottomActions} : null} +
+ ); +} + +export function BusinessEditWorkspace({ + children, + className, + testId, +}: { + children: ReactNode; + className?: string; + testId?: string; +}) { + return ( +
+ {children} +
+ ); +} + +export function BusinessEditMain({ + children, + className, +}: { + children: ReactNode; + className?: string; +}) { + return
{children}
; +} + +export function BusinessEditAside({ + children, + className, +}: { + children: ReactNode; + className?: string; +}) { + return
{children}
; +} + +export function BusinessEditSection({ + title, + description, + actions, + children, + className, +}: { + title: string; + description?: string; + actions?: ReactNode; + children: ReactNode; + className?: string; +}) { + return ( + + {children} + + ); +} + +export function BusinessEditFormGrid({ + children, + className, + columns = 2, +}: { + children: ReactNode; + className?: string; + columns?: 1 | 2 | 3; +}) { + const columnClass = { + 1: "grid-cols-1", + 2: "md:grid-cols-2", + 3: "md:grid-cols-2 xl:grid-cols-3", + }[columns]; + + return
{children}
; +} + +export function BusinessEditValidationSummary({ + children, + className, +}: { + children: ReactNode; + className?: string; +}) { + return ( +
+ {children} +
+ ); +} + +export function BusinessEditActionBar({ + children, + className, +}: { + children: ReactNode; + className?: string; +}) { + return ( +
+
{children}
+
+ ); +} diff --git a/client/src/pages/AssetRegistryPage.tsx b/client/src/pages/AssetRegistryPage.tsx index 8a2cb724..578dd741 100644 --- a/client/src/pages/AssetRegistryPage.tsx +++ b/client/src/pages/AssetRegistryPage.tsx @@ -226,7 +226,8 @@ export function AssetRegistryPage() { const [assigneeFilter, setAssigneeFilter] = useState(""); const [assetSearch, setAssetSearch] = useState(""); const [mine, setMine] = useState(!canViewAll); - const [showLedgerTools, setShowLedgerTools] = useState(false); + const [showCategoryManager, setShowCategoryManager] = useState(false); + const [showAdvancedFilters, setShowAdvancedFilters] = useState(false); const [selectedAssetId, setSelectedAssetId] = useState(searchParams.get("id") ?? ""); const [isCreatingAsset, setIsCreatingAsset] = useState(false); @@ -377,6 +378,14 @@ export function AssetRegistryPage() { const currentAsset = assetDetailQuery.data?.asset ?? null; const canEditCurrent = canManage && (isCreatingAsset || currentAsset?.permissions.canEdit); + const hasAdvancedAssetFilters = Boolean( + sensitivityFilter || + categoryFilter || + legalEntityFilter || + costCenterFilter || + assigneeFilter, + ); + const showAssetAdvancedFilters = showAdvancedFilters || hasAdvancedAssetFilters; function resetCategoryEditor(category?: AssetCategoryItem) { if (!category) { @@ -733,9 +742,15 @@ export function AssetRegistryPage() { - + {canManage ? ( + + ) : null} )} @@ -744,8 +759,8 @@ export function AssetRegistryPage() { {!isAssetDetailMode ? (
- {showLedgerTools ? ( -
+ {showCategoryManager && canManage ? ( +

资产分类

{canManage ? ( @@ -786,56 +801,6 @@ export function AssetRegistryPage() { ) : null}
-
-
-

高级筛选

-
-
- - - - - -
-
- {categoriesQuery.isLoading ? : null} {categoriesQuery.isError ? (

{resolveError(categoriesQuery.error)}

@@ -903,15 +868,91 @@ export function AssetRegistryPage() {
- - + + + ) : null} + + )} + advanced={showAssetAdvancedFilters ? ( +
+ + + + + +
+ +
+
) : null} >
diff --git a/client/src/pages/ReimbursementsPage.tsx b/client/src/pages/ReimbursementsPage.tsx index eed695d5..7e9e01ed 100644 --- a/client/src/pages/ReimbursementsPage.tsx +++ b/client/src/pages/ReimbursementsPage.tsx @@ -1,7 +1,6 @@ import { useMemo, useState } from "react"; import { Link, useNavigate, useSearchParams } from "react-router-dom"; import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query"; -import { Wrench } from "lucide-react"; import { isApiError } from "../api/types"; import { mapApiErrorMessage } from "../api/errorMessages"; import { fetchMyDelegations } from "../api/delegations"; @@ -154,7 +153,6 @@ export function ReimbursementsPage() { const [proxyOwnerEmployeeId, setProxyOwnerEmployeeId] = useState(""); const [deleteTarget, setDeleteTarget] = useState(null); const [exportConfirmFormat, setExportConfirmFormat] = useState(null); - const [showLedgerTools, setShowLedgerTools] = useState(false); const status = (searchParams.get("status") as ReimbursementStatus | null) ?? "draft"; const page = Number(searchParams.get("page") ?? "1") || 1; @@ -170,9 +168,9 @@ export function ReimbursementsPage() { hasPermission(permissions, "invoice.manage"); const isAdministration = (me?.roleCodes ?? []).includes("administration"); const showProxyOwnerSelector = canProxySubmit && isAdministration; - const canShowLedgerEntry = canViewAll || showProxyOwnerSelector || canUseFinanceTools; - const showAdvancedTools = canShowLedgerEntry && (showLedgerTools || scope === "all" || showProxyOwnerSelector); - const shouldLoadCreateOptions = showAdvancedTools && canCreateExpense; + const canShowListActions = canViewAll || showProxyOwnerSelector || canUseFinanceTools; + const showAdvancedTools = scope === "all" || showProxyOwnerSelector; + const shouldLoadCreateOptions = canCreateExpense; const listQuery = useQuery({ queryKey: ["reimbursements", "list", status, page, scope], @@ -284,100 +282,27 @@ export function ReimbursementsPage() { title={scope === "all" ? "报销台账" : "我的报销"} actions={ canCreateExpense ? ( - - ) : null - } - /> - - {createMutation.error && isApiError(createMutation.error) ? ( -

- {createMutation.error.code}: - {mapApiErrorMessage( - createMutation.error.code, - createMutation.error.details, - createMutation.error.message, - )} -

- ) : null} - - {exportError ? ( -

- {exportError} -

- ) : null} - - setShowLedgerTools((current) => !current)} - type="button" - > - - 台账工具 - - ) : null - } - advanced={showAdvancedTools ? ( - <> -
-
-
当前列表
-
{stats.currentPageCount} 条 / 共 {listQuery.data?.total ?? 0} 条
-
-
-
当前金额
-
{formatCurrency(stats.currentPageAmount)}
-
-
-
审批中
-
0 ? "mt-1 font-semibold text-amber-700" : "mt-1 font-semibold text-zinc-900"}>{stats.reviewing} 条
-
-
-
已支付
-
{stats.paid} 条
-
-
- -
- {showProxyOwnerSelector ? ( - - ) : null} - - {canShowCreateOptionsMenu ? ( - - - - - -
- 从模板创建 -
+
+ + + + + + startCreateReimbursement()}> + 空白报销 + + {canShowCreateOptionsMenu ? ( + <> + +
+ 从模板创建 +
{templateOptions.length ? ( templateOptions.map((template) => ( )) - ) : ( - 当前无可关联申请 - )} -
-
- ) : null} + ) : ( + 当前无可关联申请 + )} + + ) : null} + + +
+ ) : null + } + /> + {createMutation.error && isApiError(createMutation.error) ? ( +

+ {createMutation.error.code}: + {mapApiErrorMessage( + createMutation.error.code, + createMutation.error.details, + createMutation.error.message, + )} +

+ ) : null} + + {exportError ? ( +

+ {exportError} +

+ ) : null} + + {canViewAll ? ( <> ) : null} - + + + ) : null + } + advanced={showAdvancedTools ? ( + <> + {scope === "all" ? ( +
+
+
当前列表
+
{stats.currentPageCount} 条 / 共 {listQuery.data?.total ?? 0} 条
+
+
+
当前金额
+
{formatCurrency(stats.currentPageAmount)}
+
+
+
审批中
+
0 ? "mt-1 font-semibold text-amber-700" : "mt-1 font-semibold text-zinc-900"}>{stats.reviewing} 条
+
+
+
已支付
+
{stats.paid} 条
+
+
+ ) : null} + + {showProxyOwnerSelector ? ( +
+ {showProxyOwnerSelector ? ( + + ) : null}
+ ) : null} ) : null} > diff --git a/client/tests/e2e/assets-flow.spec.ts b/client/tests/e2e/assets-flow.spec.ts index 82939888..3c00072a 100644 --- a/client/tests/e2e/assets-flow.spec.ts +++ b/client/tests/e2e/assets-flow.spec.ts @@ -85,8 +85,8 @@ test("E2E 资产流转主链路:建卡→领用归还→调拨→盘点", asyn await (await openNavLink(page, "资产")).click(); await expect(page.getByRole("heading", { name: "资产台账" })).toBeVisible({ timeout: 30_000 }); await expect(page.getByTestId("asset-category-code")).toHaveCount(0); - await page.getByRole("button", { name: "台账工具" }).click(); - await expect(page.getByTestId("asset-ledger-tools")).toBeVisible(); + await page.getByRole("button", { name: "资产分类" }).click(); + await expect(page.getByTestId("asset-category-manager")).toBeVisible(); await expect(page.getByTestId("asset-category-code")).toBeVisible({ timeout: 30_000 }); await page.getByTestId("asset-category-reset").click(); diff --git a/client/tests/e2e/manual/asset1d-public-trial.spec.ts b/client/tests/e2e/manual/asset1d-public-trial.spec.ts index 2bc7af05..d14c0bb7 100644 --- a/client/tests/e2e/manual/asset1d-public-trial.spec.ts +++ b/client/tests/e2e/manual/asset1d-public-trial.spec.ts @@ -162,6 +162,7 @@ test("ASSET1D 公网资产台账试录:FA/EA-SYS/EA-REPO/EA-BKP", async ({ pag await login(page, admin); await page.goto("/assets"); + await page.getByRole("button", { name: "资产分类" }).click(); await expect(page.getByTestId("asset-category-code")).toBeVisible({ timeout: 30_000 }); const category = await createTrialCategory(page, suffix); diff --git a/client/tests/e2e/system-action-confirmation.spec.ts b/client/tests/e2e/system-action-confirmation.spec.ts index afb2e8b4..9ffc9906 100644 --- a/client/tests/e2e/system-action-confirmation.spec.ts +++ b/client/tests/e2e/system-action-confirmation.spec.ts @@ -290,7 +290,6 @@ test.describe("R7E 全站高风险操作二次确认", () => { await page.getByTestId("action-confirm-submit").click(); await expect.poll(() => calls.deleteReimbursement).toBe(1); - await page.getByRole("button", { name: "台账工具" }).click(); await page.getByTestId("reimbursements-export").click(); await page.getByTestId("reimbursements-export-csv").click(); await expect(page.getByTestId("action-confirm-dialog")).toContainText("导出文件可能包含业务或个人信息"); diff --git a/client/tests/e2e/visual-shell.spec.ts b/client/tests/e2e/visual-shell.spec.ts index aaa657e1..0e0aabf2 100644 --- a/client/tests/e2e/visual-shell.spec.ts +++ b/client/tests/e2e/visual-shell.spec.ts @@ -390,8 +390,8 @@ test.describe("visual shell", () => { await page.goto("/reimbursements"); await expect(page.getByRole("heading", { name: "我的报销" })).toBeVisible(); await expect(page.getByTestId("reimbursements-create")).toBeVisible(); - await expect(page.getByRole("button", { name: "台账工具" })).toBeVisible(); - await expect(page.getByTestId("reimbursements-export")).not.toBeVisible(); + await expect(page.getByRole("button", { name: "台账工具" })).toHaveCount(0); + await expect(page.getByTestId("reimbursements-export")).toBeVisible(); await expect(page.getByTestId("reimbursements-status-tabs")).toBeVisible(); await expect(page.getByTestId("reimbursements-table")).toBeVisible(); await expect(page.getByRole("button", { name: "批量操作" })).toHaveCount(0);