diff --git a/.trellis/spec/hyb-oa/frontend-react-vite-design.md b/.trellis/spec/hyb-oa/frontend-react-vite-design.md index 9dfec67c..7c3bcead 100644 --- a/.trellis/spec/hyb-oa/frontend-react-vite-design.md +++ b/.trellis/spec/hyb-oa/frontend-react-vite-design.md @@ -62,6 +62,12 @@ reference, not a copy of Feishu or Ant Design branding. action: `新建报销`, `新建采购`, `新增合同`, `新增资产`, `新建项目`. Workbench, pending-task, notification and read-only record pages must not force a header button for visual symmetry. +- PC page header actions follow a strict mature enterprise rule: the title + right side may have zero buttons, one page-level primary action, or one + primary action plus one `更多`. Do not place `筛选`, `导出`, `刷新`, + `显示测试数据`, category managers, export tools or other list-level tools as + persistent header-right buttons. Put those in the list toolbar, filter panel, + explicit management panel, row action or detail action area. - Button labels use common enterprise verbs: `处理`, `去处理`, `查看详情`, `查看全部`, `查看进度`, `继续编辑`, `新建`, `新增`, `提交`, `保存`, `取消`, `撤回`, `作废`, `归档`, `更多`. Do not use slogan-like or invented labels @@ -94,6 +100,10 @@ reference, not a copy of Feishu or Ant Design branding. ranges, contract type, sensitivity, export and test-data toggles belong in `筛选`, `更多`, clearly named role-only actions or a tab-specific advanced area; default-visible filters should not occupy multiple rows. +- List toolbars may show one or two high-frequency controls beside common + filters. Low-frequency navigation, export, expiry lists, trial-data toggles + and troubleshooting actions should collapse into `更多` or advanced filters + instead of forming a second row of persistent buttons. - 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 diff --git a/.trellis/tasks/05-31-unified-page-header-actions/check.jsonl b/.trellis/tasks/05-31-unified-page-header-actions/check.jsonl new file mode 100644 index 00000000..98a275ea --- /dev/null +++ b/.trellis/tasks/05-31-unified-page-header-actions/check.jsonl @@ -0,0 +1,3 @@ +{"file": ".trellis/spec/hyb-oa/project-governance.md", "reason": "检查 Trellis validate、governance、提交和任务收口"} +{"file": ".trellis/spec/hyb-oa/frontend-react-vite-design.md", "reason": "检查右上角操作区是否符合统一规则"} +{"file": ".trellis/spec/hyb-oa/docs-and-evidence.md", "reason": "检查截图、报告和任务记录是否安全"} diff --git a/.trellis/tasks/05-31-unified-page-header-actions/design.md b/.trellis/tasks/05-31-unified-page-header-actions/design.md new file mode 100644 index 00000000..668228b5 --- /dev/null +++ b/.trellis/tasks/05-31-unified-page-header-actions/design.md @@ -0,0 +1,79 @@ +# 设计方案:页面右上角操作区 + +## 统一模型 + +页面分三层放动作: + +1. **页面标题右上角** + - 只放页面级主操作。 + - 最多一个主按钮 + 一个 `更多`。 + - 主按钮必须是用户来到该页面最自然的创建/发起动作。 + +2. **列表工具栏 / 筛选区右侧** + - 放筛选、范围、导出、刷新、统计、批量、测试数据开关。 + - 这些动作影响列表视图或当前查询范围,不是页面主操作。 + - 默认只露出一到两个高频动作;导出、到期列表、测试数据、关联跳转等低频动作收进 `更多` 或高级筛选,避免工具栏形成第二排常驻按钮。 + +3. **行操作 / 详情操作区** + - 放 `查看详情`、`处理`、`继续编辑`、`查看进度`、`作废`、`归档` 等对象级动作。 + - 风险动作必须在详情页或确认弹窗内完成。 + +## 右上角按钮规则 + +- 0 个按钮:工作台、待我处理、通知、只读记录页默认允许没有标题右侧按钮。 +- 1 个按钮:有明确创建/发起动作的页面,只保留主按钮。 +- 2 个按钮:主按钮 + `更多`。仅当存在必要页面级低频动作。 +- 禁止 3 个及以上标题右侧按钮。 +- 禁止把 `筛选`、`刷新`、`导出`、`显示测试数据` 直接放在标题右侧。 +- 禁止为了视觉对齐给无主操作页面硬塞按钮。 + +## 页面应用 + +- `报销`:标题右上角 `新建报销`;统计/导出/范围切换在列表工具栏。 +- `采购`:标题右上角 `新建采购`;筛选在列表工具栏。 +- `合同`:标题右上角 `新增合同`;到期、导出、筛选进入列表工具栏/更多。 +- `项目`:标题右上角 `新建项目`;合同入口和测试数据开关不在标题右上角。 +- `资产`:标题右上角 `新增资产` + `更多`;更多内放扫码查找、资产分类。 +- `付款`:标题右上角 `新建批次`;刷新在列表工具栏。 +- `应付账款`:标题右上角无主按钮;刷新、导出在列表工具栏。 +- `票据`:标题右上角 `上传票据` + `更多`;更多内放绑定和导出。 + +## 其他必须统一的页面规则 + +1. **命名规则** + 菜单、页面标题、面包屑/返回入口使用同一个短名。业务对象内部可以叫 `采购申请`,但一级菜单和列表页标题统一叫 `采购`。 + +2. **标题区文案** + 标题区只放标题、必要标签和主操作。默认不放副标题,不解释页面用途。 + +3. **筛选区** + 状态切换优先用 tabs/chips;高频筛选一行展示;高级筛选统一由 `筛选` 打开。筛选入口属于列表工具栏,不属于页面标题区。 + +4. **列表工具区** + 列表工具栏右侧只保留高频范围切换、`筛选` 或一个 `更多`。低频导出、到期、测试数据、关联跳转、排障动作不常驻外露。 + +5. **列表区** + 所有业务列表保持单列首屏,不自动展开详情。表格列顺序统一为身份、业务关系、金额/日期、状态、操作。 + +6. **空状态** + 空状态只说明当前筛选没有数据,并给一个真实下一步。不要写教学式流程说明。 + +7. **更多菜单** + `更多` 用于低频、导出、管理、排障或非主路径动作。浮层必须右对齐,不能被表格、卡片或滚动容器遮挡。 + +8. **详情与编辑** + 详情页操作在右侧状态/操作面板;编辑页操作在底部或表单结尾固定区域。不要把详情操作塞回列表页标题右上角。 + +9. **权限** + 无权限动作默认隐藏。配置缺失时才展示边界说明和配置入口。 + +10. **移动端** + 移动端使用底部主操作和 `更多`,不复刻 PC 顶部多按钮。 + +## 实施策略 + +优先小步改造,不引入大 UI 框架: + +1. 在现有 `WorkspaceHeader` / `PageHeader` 上继续使用 `actions`,先完成页面迁移。 +2. 增加一个轻量共享 `HeaderActionGroup` 或等价模式,约束一主一更多。 +3. 更新设计规范和 E2E 断言,防止新页面继续把多个工具塞到标题右侧。 diff --git a/.trellis/tasks/05-31-unified-page-header-actions/implement.jsonl b/.trellis/tasks/05-31-unified-page-header-actions/implement.jsonl new file mode 100644 index 00000000..32224e43 --- /dev/null +++ b/.trellis/tasks/05-31-unified-page-header-actions/implement.jsonl @@ -0,0 +1,3 @@ +{"file": ".trellis/spec/hyb-oa/project-governance.md", "reason": "Trellis task、编码前方案和提交规则"} +{"file": ".trellis/spec/hyb-oa/frontend-react-vite-design.md", "reason": "页面标题、业务列表、操作区和成熟企业后台设计规则"} +{"file": ".trellis/spec/hyb-oa/docs-and-evidence.md", "reason": "记录视觉复检证据和避免敏感信息入库"} diff --git a/.trellis/tasks/05-31-unified-page-header-actions/implement.md b/.trellis/tasks/05-31-unified-page-header-actions/implement.md new file mode 100644 index 00000000..25f60431 --- /dev/null +++ b/.trellis/tasks/05-31-unified-page-header-actions/implement.md @@ -0,0 +1,16 @@ +# 实施记录 + +## 本轮改造 + +- 新增 `HeaderActionGroup`,统一标题右侧按钮排列。 +- 采购、合同、项目、资产、付款、应付账款、票据完成首轮标题右侧操作迁移。 +- 创建类标题按钮保持主按钮样式;详情态只保留 `返回列表`。 +- 合同和项目的低频列表工具收进 `更多`,避免工具栏持续堆叠。 +- 应付账款 tabs 与 `刷新` / `导出` 调整为横向命令栏。 +- 票据列表支持把绑定和导出放入列表工具栏,不再占用标题右侧。 + +## 视觉结论 + +- 标题区符合“0 个 / 1 个主操作 / 主操作 + 更多”的企业后台常规模式。 +- 列表级 `筛选`、`导出`、`刷新`、测试数据和关联跳转不再作为标题右上角常驻按钮。 +- 合同、付款、应付账款、票据截图已覆盖;后续页面改造继续按同一模板推进。 diff --git a/.trellis/tasks/05-31-unified-page-header-actions/prd.md b/.trellis/tasks/05-31-unified-page-header-actions/prd.md new file mode 100644 index 00000000..4b8dbcb8 --- /dev/null +++ b/.trellis/tasks/05-31-unified-page-header-actions/prd.md @@ -0,0 +1,99 @@ +# 统一页面右上角操作区规划 + +## Goal + +统一 PC 页面标题右上角按钮规划,解决当前业务页右上角按钮数量、优先级和位置不一致的问题。页面应采用成熟企业后台/Ant Design Pro 常见模式:页面标题区只承载页面级主操作,列表筛选、导出、刷新、测试数据、分类管理等低频或列表级工具进入列表工具栏、筛选区或 `更多`。 + +## Scope + +- 前端 PC 页面标题右侧操作区、业务列表工具栏和相关 E2E/视觉断言。 +- 首轮覆盖报销、采购、合同、项目、资产、付款、应付账款和票据等业务页。 +- 只调整操作入口位置、按钮优先级、列表工具收纳和设计规范,不改变业务权限、接口、状态机或数据结构。 + +## What I Already Know + +- 用户反馈:现在页面右上角按钮规划不统一。 +- 产品背景:面向国内小于 10 人科技初创公司,系统应低认知负担、成熟常规、不创新。 +- 既有规范要求:页面标题默认为 route 名称;Header primary action 只保留真实页面级主操作;低频工具不要堆在右上角。 +- 当前代码审计发现: + - `报销`:标题右侧主要是 `新建报销`,列表工具在下方,相对接近目标。 + - `采购`:标题右侧同时放 `新建采购` 和 `筛选`,筛选应进入列表工具区。 + - `合同`:标题右侧同时放 `新增合同`、`到期列表`、`导出`、`筛选`,过载最明显。 + - `项目`:标题右侧放 `合同`、`显示测试数据`、`新建项目`,页面级动作与排障开关混用。 + - `资产`:标题右侧放 `新增资产`、`移动扫码查找`、`资产分类`,管理工具和主操作混用。 + - `付款`:标题右侧放 `刷新`、`新建批次`,刷新不应占据标题主操作区。 + - `应付账款`:标题右侧放 `刷新`、`导出`,只读列表页不应强制占用标题主按钮区。 + - `票据`:标题右侧放 `上传票据`、`绑定`、`导出`,主操作与后续处理工具混用。 + +## Requirements + +- 标题右上角只允许: + - 一个页面级主操作按钮,例如 `新建报销`、`新建采购`、`新增合同`、`新建项目`、`新增资产`、`新建批次`、`上传票据`。 + - 最多一个 `更多`,用于页面级低频操作;当低频操作只有列表级含义时,放到列表工具栏,不放标题区。 + - 详情/编辑模式可用一个 `返回列表`,但不与创建按钮并列。 +- `筛选` 不放标题右上角;统一放在列表工具栏/筛选区右侧。 +- `导出` 不作为标题区常驻按钮;统一进入列表工具栏右侧或 `更多`。 +- `刷新` 不作为标题区常驻按钮;需要保留时进入列表工具栏,或作为低频工具。 +- `显示测试数据` 属于排障/试运行开关,不放标题右上角,进入筛选/更多/排障区。 +- `分类管理`、`扫码查找`、`到期列表`、`绑定` 等非创建动作,不与主创建按钮并列;按场景进入 `更多`、筛选区、管理入口或下方操作面板。 +- 尽量通过共享组件或模板约束新页面,避免继续逐页漂移。 + +## Additional Consistency Rules To Review + +- 页面标题:菜单名、页面主标题和浏览路径用词要一致;例如菜单 `合同`,页面标题也用 `合同`,不要在不同位置混用 `合同台账`、`合同管理`、`合同列表`。 +- 说明文案:默认页面不展示解释性副标题;只在空状态、错误、权限、企业微信边界、不可逆操作和试运行边界中解释。 +- 筛选布局:业务列表统一为“状态/范围 tabs 或 chips + 一行高频筛选 + 高级筛选入口”;不允许有的页面两行筛选、有的页面无筛选、有的页面把筛选放标题右上角。 +- 列表列结构:业务列表统一按“对象身份、相对方/人员、金额/日期、状态、操作”组织;右侧固定操作列默认 `查看详情` / `处理` / `继续编辑`。 +- 空状态:统一使用短标题 + 一句当前筛选结果 + 一个真实下一步;不要写流程说明。 +- 更多菜单:所有 `更多` 都右对齐、浮层不被遮挡、只放低频动作;危险动作必须二次确认。 +- 详情页:PC 统一为“顶部摘要 + 主事实/证据 + 右侧状态/操作区 + 历史”;列表页不默认在下方展开详情。 +- 编辑页/面板:创建和编辑统一使用同一编辑模板;主按钮固定为 `保存` / `提交`,取消/返回在左或次级位置。 +- 权限显隐:无权限动作直接隐藏;不要显示不可用按钮再解释一堆原因,除非是权限申请或配置缺失边界。 +- 移动端对应:移动端不照搬 PC 右上角;统一为顶部标题 + 卡片列表/详情 + 底部主操作,次级动作进入 `更多`。 + +## Proposed Page-Level Changes + +| 页面 | 标题右上角目标 | 迁移动作 | +| --- | --- | --- | +| `报销` | `新建报销` | 保持;检查导出/统计是否仍在列表工具栏。 | +| `采购` | `新建采购` | `筛选` 移到列表工具栏。 | +| `合同` | `新增合同` + `更多`(如需要) | `筛选` 移到筛选区;`导出` 移到列表工具栏/更多;`到期列表` 改为列表状态/筛选入口。 | +| `项目` | `新建项目` | `合同` 移为列表/详情关联入口;`显示测试数据` 移到筛选/排障入口。 | +| `资产` | `新增资产` + `更多` | `移动扫码查找`、`资产分类` 放入 `更多` 或管理面板,不与新增并列。 | +| `付款` | `新建批次` | `刷新` 移到列表工具栏。 | +| `应付账款` | 无主按钮,必要时 `更多` | `刷新`、`导出` 移到列表工具栏/更多。 | +| `票据` | `上传票据` + `更多` | `绑定`、`导出` 放入 `更多` 或列表工具栏。 | + +## Out of Scope + +- 不改变业务权限、审批规则、数据结构或接口。 +- 不新增真实业务写入。 +- 不重新设计移动端底部主操作;本任务先收敛 PC 页面标题右上角。 +- 不部署公网;实现完成后先本地验证,部署另行获得批准。 + +## Acceptance Criteria + +- [ ] 业务列表页标题右上角符合“一主操作 + 最多一个更多”的规则。 +- [ ] `筛选`、`导出`、`刷新`、`显示测试数据` 不再作为标题右上角常驻按钮。 +- [ ] 采购、合同、项目、资产、付款、应付账款、票据至少完成首轮统一。 +- [ ] 规则写入前端设计规范,后续页面可复用。 +- [ ] 标题、筛选、列表列、空状态、详情/编辑操作区至少完成一致性审计,形成后续改造清单。 +- [ ] 前端 typecheck/build 通过。 +- [ ] 聚焦 Playwright/视觉复检覆盖主要业务页。 + +## Test Plan + +```bash +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 --workers=1 --reporter=list +git diff --check +python3 ./.trellis/scripts/task.py validate 05-31-unified-page-header-actions +node scripts/qa/check-trellis-governance.mjs +``` + +## Technical Notes + +- 设计裁决使用 `frontend-design` skill,并按成熟企业后台/Ant Design Pro 的页面区分:标题区负责页面级主操作,表格工具栏负责列表级工具。 +- 相关规范:`.trellis/spec/hyb-oa/frontend-react-vite-design.md` +- 相关组件:`client/src/components/patterns/ListWorkspace.tsx`、`client/src/components/common/PageHeader.tsx`、`client/src/components/patterns/BusinessListTemplate.tsx` diff --git a/.trellis/tasks/05-31-unified-page-header-actions/task.json b/.trellis/tasks/05-31-unified-page-header-actions/task.json new file mode 100644 index 00000000..8f014da4 --- /dev/null +++ b/.trellis/tasks/05-31-unified-page-header-actions/task.json @@ -0,0 +1,26 @@ +{ + "id": "unified-page-header-actions", + "name": "unified-page-header-actions", + "title": "统一页面右上角操作区规划", + "description": "统一 PC 页面标题右上角操作区规划,收敛主操作、更多、筛选、导出、刷新等按钮位置。", + "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": {} +} \ No newline at end of file diff --git a/client/src/components/patterns/ListWorkspace.tsx b/client/src/components/patterns/ListWorkspace.tsx index d21e91b6..8266ef9a 100644 --- a/client/src/components/patterns/ListWorkspace.tsx +++ b/client/src/components/patterns/ListWorkspace.tsx @@ -33,6 +33,10 @@ export function WorkspaceHeader({ eyebrow, title, subtitle, meta, actions, class ); } +export function HeaderActionGroup({ className, children }: WorkspaceChildrenProps) { + return
{children}
; +} + export function FilterBar({ className, children }: WorkspaceChildrenProps) { return
{children}
; } diff --git a/client/src/pages/ApOperationsPage.tsx b/client/src/pages/ApOperationsPage.tsx index d28aa183..c4d40051 100644 --- a/client/src/pages/ApOperationsPage.tsx +++ b/client/src/pages/ApOperationsPage.tsx @@ -593,41 +593,7 @@ export function ApOperationsPage() { return ( - - - - - - - - exportMutation.mutate("csv")}> - 导出 CSV - - exportMutation.mutate("xlsx")}> - 导出 Excel - - - - - } - /> + {summaryQuery.isError ? ( @@ -642,15 +608,49 @@ export function ApOperationsPage() { ) : null} -
+
setActiveTab(value as OperationsTab)}> - - 待付款 {pendingBatches.length} - 异常付款 {failedItems.length} - 风险标记 {filteredRiskFlags.length} - 对账异常 {partialMatchItems.length} - 财务台账 {financeLedgerQuery.data?.total ?? 0} - +
+ + 待付款 {pendingBatches.length} + 异常付款 {failedItems.length} + 风险标记 {filteredRiskFlags.length} + 对账异常 {partialMatchItems.length} + 财务台账 {financeLedgerQuery.data?.total ?? 0} + +
+ + + + + + + exportMutation.mutate("csv")}> + 导出 CSV + + exportMutation.mutate("xlsx")}> + 导出 Excel + + + +
+
diff --git a/client/src/pages/AssetRegistryPage.tsx b/client/src/pages/AssetRegistryPage.tsx index c4b2ba6f..7d3931a9 100644 --- a/client/src/pages/AssetRegistryPage.tsx +++ b/client/src/pages/AssetRegistryPage.tsx @@ -37,13 +37,14 @@ import { BusinessDetailMain, BusinessDetailWorkspace, } from "../components/patterns/BusinessDetailTemplate"; -import { ListWorkspace, PrimaryTable, WorkspaceHeader } from "../components/patterns/ListWorkspace"; +import { HeaderActionGroup, ListWorkspace, PrimaryTable, WorkspaceHeader } from "../components/patterns/ListWorkspace"; import { BusinessListToolbar, BusinessMobileList, BusinessTable, } from "../components/patterns/BusinessListTemplate"; import { Button } from "../components/ui/button"; +import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuTrigger } from "../components/ui/dropdown-menu"; import { Input } from "../components/ui/input"; import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "../components/ui/table"; import { formatCompactIdentifier, formatDisplayDateTime, formatTextWithCompactLongNumbers, isCompactedIdentifier } from "../lib/displayFormat"; @@ -727,7 +728,7 @@ export function AssetRegistryPage() { + {isAssetDetailMode ? ( ) : null} - - {canManage ? ( - - ) : null} + + + + + + navigate("/mobile/assets/scan")}> + 移动扫码查找 + + {canManage ? ( + setShowCategoryManager((value) => !value)}> + {showCategoryManager ? "收起资产分类" : "资产分类"} + + ) : null} + + )} -
+ } /> diff --git a/client/src/pages/ContractsRegistryPage.tsx b/client/src/pages/ContractsRegistryPage.tsx index 90431175..03176626 100644 --- a/client/src/pages/ContractsRegistryPage.tsx +++ b/client/src/pages/ContractsRegistryPage.tsx @@ -25,7 +25,7 @@ import { ActionConfirmDialog } from "../components/common/ActionConfirmDialog"; import { PageHeader } from "../components/common/PageHeader"; import { StatusBadge } from "../components/common/StatusBadge"; import { DateTextInput } from "../components/common/DateTextInput"; -import { WorkspaceNotice } from "../components/patterns/ListWorkspace"; +import { HeaderActionGroup, WorkspaceNotice } from "../components/patterns/ListWorkspace"; import { BusinessListToolbar, BusinessMobileList, @@ -334,12 +334,7 @@ export function ContractsRegistryPage() { - {initialProjectId ? ( - - ) : null} + - - - - - - - setConfirmExportFormat("csv")}> - 导出 CSV - - setConfirmExportFormat("xlsx")}> - 导出 Excel - - - - -
+ } /> @@ -672,27 +637,48 @@ export function ContractsRegistryPage() { <> + + + + + + {initialProjectId ? ( + + 返回项目 + + ) : null} + setShowExpiring((value) => !value)} + > + {showExpiring ? "退出到期列表" : `到期列表 ${expiringContractCount}`} + + setConfirmExportFormat("csv")} + > + 导出 CSV + + setConfirmExportFormat("xlsx")} + > + 导出 Excel + + + } advanced={showAdvancedFilters || filters.expiryDateFrom || filters.expiryDateTo || showTrialData ? ( -
+
+
+ +
) : null} > diff --git a/client/src/pages/InvoicePoolPage.tsx b/client/src/pages/InvoicePoolPage.tsx index 4ecae22f..1dfad3da 100644 --- a/client/src/pages/InvoicePoolPage.tsx +++ b/client/src/pages/InvoicePoolPage.tsx @@ -29,7 +29,7 @@ import { hasPermission } from "../auth/permissions"; import { toast } from "sonner"; import { downloadBlob } from "../lib/download"; import { formatInvoiceOcrErrorMessage } from "../lib/invoiceOcr"; -import { ListWorkspace, WorkspaceHeader } from "../components/patterns/ListWorkspace"; +import { HeaderActionGroup, ListWorkspace, WorkspaceHeader } from "../components/patterns/ListWorkspace"; import { Button } from "../components/ui/button"; import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuTrigger } from "../components/ui/dropdown-menu"; import { InvoiceUploadCard } from "./invoice-pool/InvoiceUploadCard"; @@ -475,7 +475,7 @@ export function InvoicePoolPage() { + {isInvoiceDetailMode ? ( - - - - - - - exportMutation.mutate("csv")}> - 导出 CSV - - exportMutation.mutate("xlsx")}> - 导出 Excel - - - )} -
+ } /> @@ -573,6 +545,39 @@ export function InvoicePoolPage() { {!isInvoiceDetailMode ? ( + + + + + + + exportMutation.mutate("csv")}> + 导出 CSV + + exportMutation.mutate("xlsx")}> + 导出 Excel + + + + + } status={status} setStatus={setStatus} ownerScope={ownerScope} diff --git a/client/src/pages/PaymentBatchesPage.tsx b/client/src/pages/PaymentBatchesPage.tsx index 398a2e38..f47d261f 100644 --- a/client/src/pages/PaymentBatchesPage.tsx +++ b/client/src/pages/PaymentBatchesPage.tsx @@ -21,8 +21,9 @@ import { isApiError } from "../api/types"; import { mapApiErrorMessage } from "../api/errorMessages"; import { useAuth } from "../auth/AuthContext"; import { hasPermission } from "../auth/permissions"; -import { ListWorkspace, WorkspaceHeader } from "../components/patterns/ListWorkspace"; +import { HeaderActionGroup, ListWorkspace, WorkspaceHeader } from "../components/patterns/ListWorkspace"; import { + BusinessListToolbar, BusinessMobileList, BusinessTable, } from "../components/patterns/BusinessListTemplate"; @@ -346,21 +347,18 @@ export function PaymentBatchesPage() { + {selectedBatchId ? ( ) : null} - {!selectedBatchId ? ( - ) : null} -
+ } /> @@ -663,6 +661,17 @@ export function PaymentBatchesPage() { ) ) : ( <> + void refresh()}> + 刷新 + + } + > + + 全部批次 {batches.length} + + {batches.map((item) => ( - + {canManageProjects ? ( - ) : null} - + } /> @@ -260,9 +260,26 @@ export function ProjectsPage() {
updateSearch({ mine: !mine })}> - {mine ? "我相关" : "全范围"} - + <> + + + + + + + + 查看合同 + + setShowTrialData((value) => !value)}> + {showTrialData ? "隐藏测试数据" : "显示测试数据"} + + + + } >
diff --git a/client/src/pages/PurchaseRequestsPage.tsx b/client/src/pages/PurchaseRequestsPage.tsx index 2c5563ad..170e4b26 100644 --- a/client/src/pages/PurchaseRequestsPage.tsx +++ b/client/src/pages/PurchaseRequestsPage.tsx @@ -44,7 +44,7 @@ import { Button } from "../components/ui/button"; import { Input } from "../components/ui/input"; import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "../components/ui/table"; import { Textarea } from "../components/ui/textarea"; -import { ListWorkspace, WorkspaceHeader } from "../components/patterns/ListWorkspace"; +import { HeaderActionGroup, ListWorkspace, WorkspaceHeader } from "../components/patterns/ListWorkspace"; import { BusinessListToolbar, BusinessMobileList, @@ -804,7 +804,7 @@ export function PurchaseRequestsPage() { + {isPurchaseDetailMode ? ( ) : null} - {canShowAdvancedFilters ? ( - - ) : null} )} -
+ } /> @@ -847,9 +837,22 @@ export function PurchaseRequestsPage() { <> - {mine ? "我相关" : "全范围"} - + <> + + {mine ? "我相关" : "全范围"} + + {canShowAdvancedFilters ? ( + + ) : null} + } advanced={showAdvancedFilterPanel ? (
diff --git a/client/src/pages/invoice-pool/InvoiceListCard.tsx b/client/src/pages/invoice-pool/InvoiceListCard.tsx index bed61554..49ddc69c 100644 --- a/client/src/pages/invoice-pool/InvoiceListCard.tsx +++ b/client/src/pages/invoice-pool/InvoiceListCard.tsx @@ -1,3 +1,4 @@ +import type { ReactNode } from "react"; import type { UseMutationResult, UseQueryResult } from "@tanstack/react-query"; import type { InvoicePoolListResponse, InvoicePoolStatus } from "../../api/invoicePool"; import { StatusBadge } from "../../components/common/StatusBadge"; @@ -21,6 +22,7 @@ import { toast } from "sonner"; export type InvoiceListCardProps = { listQuery: UseQueryResult; + actions?: ReactNode; status: InvoicePoolStatus | ""; setStatus: (status: InvoicePoolStatus | "") => void; ownerScope: "own" | "all"; @@ -84,6 +86,7 @@ function CompactInvoiceNumber({ value }: { value: string }) { export function InvoiceListCard({ listQuery, + actions, status, setStatus, ownerScope, @@ -96,7 +99,7 @@ export function InvoiceListCard({ }: InvoiceListCardProps) { return (
- +