From 6e1e00f17603093905986a6a0192c47655eba1d0 Mon Sep 17 00:00:00 2001 From: shunli Date: Wed, 27 May 2026 20:41:16 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BC=98=E5=8C=96=E8=AF=A6=E6=83=85=E4=B8=8E?= =?UTF-8?q?=E7=A7=BB=E5=8A=A8=E5=85=B3=E9=94=AE=E9=A1=B5=E8=A7=86=E8=A7=89?= =?UTF-8?q?=20Wave3=EF=BC=8805-27-pc-detail-mobile-visual-wave3=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 | 39 + .../check.jsonl | 4 + .../implement.jsonl | 4 + .../implement.md | 48 ++ .../prd.md | 91 +++ .../task.json | 26 + client/src/index.css | 98 +++ client/src/pages/MobileApprovalsPage.tsx | 20 +- client/src/pages/MobileMePage.tsx | 6 +- client/src/pages/MobileNotificationsPage.tsx | 11 +- client/src/pages/MobilePortalPage.tsx | 11 +- client/src/pages/MobileReimbursementsPage.tsx | 22 +- client/src/pages/NotificationsPage.tsx | 138 ++-- client/src/pages/ReimbursementDetailPage.tsx | 122 ++-- client/src/pages/WorkflowTaskDetailPage.tsx | 32 +- client/src/styles/mobile-touch.css | 29 + .../e2e/pc-detail-mobile-visual-wave3.spec.ts | 666 ++++++++++++++++++ 17 files changed, 1194 insertions(+), 173 deletions(-) create mode 100644 .trellis/tasks/05-27-pc-detail-mobile-visual-wave3/check.jsonl create mode 100644 .trellis/tasks/05-27-pc-detail-mobile-visual-wave3/implement.jsonl create mode 100644 .trellis/tasks/05-27-pc-detail-mobile-visual-wave3/implement.md create mode 100644 .trellis/tasks/05-27-pc-detail-mobile-visual-wave3/prd.md create mode 100644 .trellis/tasks/05-27-pc-detail-mobile-visual-wave3/task.json create mode 100644 client/tests/e2e/pc-detail-mobile-visual-wave3.spec.ts diff --git a/.trellis/spec/hyb-oa/frontend-react-vite-design.md b/.trellis/spec/hyb-oa/frontend-react-vite-design.md index 45e9d88b..03e7fc3d 100644 --- a/.trellis/spec/hyb-oa/frontend-react-vite-design.md +++ b/.trellis/spec/hyb-oa/frontend-react-vite-design.md @@ -77,6 +77,45 @@ change the visual treatment once while preserving component contracts. - Focused Playwright screenshot coverage when changing login, Portal, primary PC list pages or command-bar/table styling. +## Convention: Detail And Mobile Command Surfaces + +**What**: PC detail pages and notification-like flows should reuse the Wave3 +surface classes in `client/src/index.css` before adding page-local Tailwind +card systems: + +- `detail-workspace-grid` for the main PC detail two-column workspace. +- `detail-command-panel`, `detail-fact-grid` and `detail-fact-card` for compact + status, summary, payment, export and evidence facts. +- `detail-evidence-card` for attachments, invoices and other proof material. +- `detail-timeline` and `detail-timeline-item` for approval progress and + history. +- `detail-action-stack` and `detail-action-card` for approve/reject/return, + payment and other high-risk command groups. +- `notification-inbox`, `notification-row-card` and + `notification-row-card--unread` for PC notification streams; avoid reverting + notifications to a desktop table unless a task explicitly requires tabular + comparison. + +Mobile key pages should reuse the mobile command classes in +`client/src/styles/mobile-touch.css`: + +- `mobile-focus-card` for the current task, current list or account summary. +- `mobile-command-surface` for tabs, search and filters. +- `mobile-list-card--focus` and `mobile-list-card--unread` for state emphasis. + +**Why**: Detail pages should feel like quiet command surfaces: status, summary, +primary action, evidence and history have predictable places. Mobile pages +should stay card/list based, with no PC tables, no horizontal overflow and no +button text squeeze. + +**Tests Required**: + +- `cd client && npm run typecheck` +- `cd client && npm run build` +- Focused Playwright screenshot coverage for changed PC detail, notification + and mobile pages. +- UI design rules check when changing shared visual classes. + ## Scenario: Auth Pages And PWA Updates ### 1. Scope / Trigger diff --git a/.trellis/tasks/05-27-pc-detail-mobile-visual-wave3/check.jsonl b/.trellis/tasks/05-27-pc-detail-mobile-visual-wave3/check.jsonl new file mode 100644 index 00000000..5f4372d0 --- /dev/null +++ b/.trellis/tasks/05-27-pc-detail-mobile-visual-wave3/check.jsonl @@ -0,0 +1,4 @@ +{"file": ".trellis/spec/hyb-oa/frontend-react-vite-design.md", "reason": "核对 Wave3 页面是否符合前端设计与验收边界"} +{"file": ".trellis/spec/frontend/quality-guidelines.md", "reason": "前端类型检查、构建和测试质量门禁"} +{"file": ".trellis/spec/hyb-oa/docs-and-evidence.md", "reason": "Trellis 任务证据、截图和报告路径脱敏要求"} +{"file": ".trellis/spec/hyb-oa/security-privacy.md", "reason": "治理检查和敏感信息排除"} diff --git a/.trellis/tasks/05-27-pc-detail-mobile-visual-wave3/implement.jsonl b/.trellis/tasks/05-27-pc-detail-mobile-visual-wave3/implement.jsonl new file mode 100644 index 00000000..5224b6bd --- /dev/null +++ b/.trellis/tasks/05-27-pc-detail-mobile-visual-wave3/implement.jsonl @@ -0,0 +1,4 @@ +{"file": ".trellis/spec/hyb-oa/frontend-react-vite-design.md", "reason": "Wave3 前端视觉与交互裁决、截图和移动约束"} +{"file": ".trellis/spec/frontend/component-guidelines.md", "reason": "React 组件拆分、复用和可维护性约束"} +{"file": ".trellis/spec/frontend/type-safety.md", "reason": "TypeScript 类型和 props 约束"} +{"file": ".trellis/spec/hyb-oa/security-privacy.md", "reason": "详情、通知和测试证据不得泄露敏感资料"} diff --git a/.trellis/tasks/05-27-pc-detail-mobile-visual-wave3/implement.md b/.trellis/tasks/05-27-pc-detail-mobile-visual-wave3/implement.md new file mode 100644 index 00000000..41d7b685 --- /dev/null +++ b/.trellis/tasks/05-27-pc-detail-mobile-visual-wave3/implement.md @@ -0,0 +1,48 @@ +# 实施记录 + +## 执行策略 + +1. 先审视目标页面的组件结构和现有样式,复用 Wave2 已建立的工作台语言。 +2. 只增加必要的共享样式或小组件,不重写业务逻辑。 +3. PC 详情页按“状态/摘要/动作/证据/历史”重新压缩层级。 +4. 移动关键页按单手办理和企业微信 WebView 约束处理密度、按钮和列表。 +5. 增加 focused Playwright mock 视觉用例,覆盖 PC 和移动截图。 + +## 风险控制 + +- 不修改后端 API、schema、权限、审批状态机、通知投递、企业微信和部署环境变量。 +- 不写公网业务数据。 +- 动作按钮、确认弹窗、错误提示和权限判断必须保持可达。 +- 移动页不得回退到 PC 表格体验。 + +## 待记录 + +## 实施改动 + +- PC 报销详情 `/reimbursements/:id`:按“办理摘要、付款状态、关联项目、附件材料、明细与票据、审批进度/记录、可执行动作”重排层级,复用新的 detail surface 样式。 +- PC 审批详情 `/workflow/tasks/:id`:审批对象摘要、审批动作和处理历史改为紧凑控制台结构,动作区使用统一 action card。 +- PC 通知中心 `/notifications`:桌面端从表格改为通知流卡片,保留未读筛选、类型筛选、直达业务和标记已读。 +- 移动关键页 `/mobile`、`/mobile/reimbursements`、`/mobile/approvals`、`/mobile/approvals/:taskId`、`/mobile/notifications`、`/mobile/me`:增加当前任务/列表高亮、移动命令面板和未读/重点卡片状态,不引入 PC 表格。 +- 新增 focused Playwright 用例 `client/tests/e2e/pc-detail-mobile-visual-wave3.spec.ts`,mock API 覆盖 PC 与移动截图。 +- 将 Wave3 可复用 detail/mobile surface 规则沉淀到 `.trellis/spec/hyb-oa/frontend-react-vite-design.md`。 + +## 验证命令 + +- `cd client && npm run typecheck`:通过。 +- `cd client && npm run build`:通过。 +- `cd client && npx playwright test tests/e2e/pc-detail-mobile-visual-wave3.spec.ts --workers=1 --reporter=list`:2 passed。 +- `cd client && npx playwright test tests/e2e/pc-core-workflow-visual-wave2.spec.ts --workers=1 --reporter=list`:1 passed,用于确认 Wave2 核心页面无明显回退。 +- `node scripts/qa/check-ui-design-rules.mjs`:0 findings。 +- `python3 ./.trellis/scripts/task.py validate 05-27-pc-detail-mobile-visual-wave3`:通过。 +- `node scripts/qa/check-trellis-governance.mjs`:通过。 +- `git diff --check`:通过。 + +## 截图证据 + +- Focused 截图输出:`output/playwright/pc-detail-mobile-visual-wave3/` +- 覆盖文件:`01-pc-reimbursement-detail.png`、`02-pc-workflow-detail.png`、`03-pc-notifications.png`、`04-mobile-home.png`、`05-mobile-reimbursements.png`、`06-mobile-approvals.png`、`07-mobile-approval-detail.png`、`08-mobile-notifications.png`、`09-mobile-me.png`。 + +## 收尾判断 + +- 本轮未修改后端 API、数据库 schema、权限模型、审批状态机、企业微信配置或 SMTP 配置。 +- 本轮未执行公网部署和公网真实环境 smoke;部署与公网复核应作为合并后的下一步。 diff --git a/.trellis/tasks/05-27-pc-detail-mobile-visual-wave3/prd.md b/.trellis/tasks/05-27-pc-detail-mobile-visual-wave3/prd.md new file mode 100644 index 00000000..463c1f90 --- /dev/null +++ b/.trellis/tasks/05-27-pc-detail-mobile-visual-wave3/prd.md @@ -0,0 +1,91 @@ +# PC详情与移动关键页视觉重塑 Wave3 + +## Goal + +在 Wave2 已完成登录页、工作台、报销列表和审批中心后,继续用 `frontend-design` 收敛高频“点进去之后”的体验:报销详情、审批详情、通知中心和移动关键页。目标是让真实试运行用户从列表进入详情、从通知进入待办、从手机进入报销/审批时,仍然看到统一、简洁、低噪音的 Hyinda 工作台体验。 + +## Start Confirmation + +- 用户在 2026-05-27 确认“开始”。 +- 本任务是前端视觉与交互重塑,不改变后端 API、权限模型、审批规则、状态机、数据库 schema、企业微信配置或公网真实数据。 +- 设计裁决来源:`frontend-design` skill;项目事实源仍按 Trellis task、`.trellis/spec/hyb-oa/` 和 `docs/` 执行。 + +## Scope + +- 报销详情 `/reimbursements/:id`: + - 降低说明性文案和卡片堆叠。 + - 强化单据状态、审批进度、票据/附件、付款/财务状态和主要动作。 + - 保留现有提交、撤回、审批、付款、打印、导出、附件和审计相关能力。 +- 审批详情 `/workflow/tasks/:id`: + - 统一为紧凑审批控制台。 + - 强化当前节点、申请摘要、风险提示、审批动作和历史轨迹。 + - 保留通过、驳回、退回、转审等现有入口。 +- 通知中心 `/notifications`: + - 将通知列表从“消息堆叠”收敛为可扫描的任务/提醒流。 + - 强化未读、类型、关联业务和批量处理入口。 +- 移动关键页: + - `/mobile`、`/mobile/reimbursements`、`/mobile/approvals`、`/mobile/me`、`/mobile/notifications`。 + - 不引入 PC 表格,不让按钮出屏,不堆叠说明文字。 + - 保留企业微信 WebView 入口语义、底部导航、PDF 上传和审批操作可达性。 + +## Out of Scope + +- 不改财务/付款/AP/票据池和 C 类管理页;这些进入 Wave4。 +- 不改后端接口、数据模型、权限、审批状态机、通知投递逻辑、企业微信 OAuth 或 SMTP 配置。 +- 不执行公网真实业务写入、真实付款、ERP 写入、数据库重置或企业微信写入。 +- 不把 `docs/产品设计规则.md` 重新扩展为视觉规则。 + +## Design Direction + +- Tone:quiet command surface,克制、紧凑、面向办理。 +- Composition:详情页用“状态带 + 关键摘要 + 主动作 + 证据/历史”的层级;移动端用任务卡和固定动作区,不复制 PC。 +- Copy:删除“解释系统如何工作”的文本,保留业务下一步、风险和不可逆动作提示。 +- Density:PC 详情页允许更高信息密度;移动端优先单手可扫、按钮稳定、不横向溢出。 +- Safety:涉及付款、驳回、退回、删除、附件和导出等动作,保持原确认与错误提示。 + +## Requirements + +- Wave3 页面视觉必须与 Wave2 的登录页、Portal、报销列表和审批待办保持同一套工作台语言。 +- 报销详情和审批详情必须突出当前可做动作,不让次级信息抢占首屏。 +- 通知中心必须能快速区分未读、业务类型和后续入口。 +- 移动关键页必须在 390px 和常见企业微信 WebView 宽度下无横向滚动、无桌面表格、无按钮文字溢出。 +- 现有业务能力不能丢失;若入口降级为次级工具,必须仍可达。 +- 页面和测试不得保存密码、token、Secret、完整账号、真实票据、合同正文、银行资料、完整本地路径或 raw payload。 + +## Acceptance Criteria + +- [x] 报销详情、审批详情、通知中心和移动关键页完成统一视觉与交互层级收敛。 +- [x] Wave2 页面不发生明显回退。 +- [x] PC 详情页首屏核心状态、主要动作和风险/证据入口更清晰。 +- [x] 移动关键页无 PC 表格、无横向溢出、无按钮文字挤压。 +- [x] `cd client && npm run typecheck` 通过。 +- [x] `cd client && npm run build` 通过。 +- [x] Focused Playwright 覆盖报销详情、审批详情、通知中心和移动关键页,并生成截图证据。 +- [x] `node scripts/qa/check-trellis-governance.mjs` 与 `git diff --check` 通过。 + +## Test Plan + +```bash +cd client && npm run typecheck +cd client && npm run build +cd client && npx playwright test tests/e2e/pc-detail-mobile-visual-wave3.spec.ts --workers=1 --reporter=list +node scripts/qa/check-trellis-governance.mjs +git diff --check +``` + +发布前后按需补充: + +```bash +SYNC_ENV=0 LIVE_SMOKE_BASE_URL=https://oa.hyinda.com bash deploy/trialrun/deploy.sh +LIVE_SMOKE_BASE_URL=https://oa.hyinda.com bash scripts/qa/run-live-smoke-readiness.sh +E2E_BASE_URL=https://oa.hyinda.com npx playwright test tests/e2e/pc-detail-mobile-visual-wave3.spec.ts --workers=1 --reporter=list +``` + +## Technical Notes + +- 前端设计 skill:`frontend-design` +- 前端 spec:`.trellis/spec/hyb-oa/frontend-react-vite-design.md` +- 前端基础 spec:`.trellis/spec/frontend/` +- 产品体验事实源:`docs/产品设计规则.md` +- Wave2 参考:`.trellis/tasks/archive/2026-05/05-27-05-27-pc-core-workflow-visual-wave2/` +- 现有视觉巡检:`client/tests/e2e/oafront3-pc-readonly-visual.spec.ts` diff --git a/.trellis/tasks/05-27-pc-detail-mobile-visual-wave3/task.json b/.trellis/tasks/05-27-pc-detail-mobile-visual-wave3/task.json new file mode 100644 index 00000000..662eca58 --- /dev/null +++ b/.trellis/tasks/05-27-pc-detail-mobile-visual-wave3/task.json @@ -0,0 +1,26 @@ +{ + "id": "pc-detail-mobile-visual-wave3", + "name": "pc-detail-mobile-visual-wave3", + "title": "PC详情与移动关键页视觉重塑 Wave3", + "description": "用 frontend-design 统一 PC 报销详情、审批详情、通知中心和移动关键页的视觉层级与交互密度。", + "status": "in_progress", + "dev_type": null, + "scope": "client", + "package": null, + "priority": "P2", + "creator": "shunli", + "assignee": "shunli", + "createdAt": "2026-05-27", + "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/index.css b/client/src/index.css index a9e6832e..ffaca956 100644 --- a/client/src/index.css +++ b/client/src/index.css @@ -429,6 +429,104 @@ @apply grid gap-6 xl:grid-cols-[minmax(0,1.4fr),minmax(320px,0.8fr)]; } + .detail-workspace-grid { + @apply grid gap-5 xl:grid-cols-[minmax(0,1.45fr),minmax(300px,0.72fr)]; + } + + .detail-command-panel { + border-color: rgba(203, 213, 225, 0.92); + background: linear-gradient(180deg, rgba(255, 255, 255, 0.98), rgba(248, 250, 252, 0.9)); + border-radius: var(--radius-lg); + box-shadow: var(--shadow-xs); + @apply border p-3.5 md:p-4; + } + + .detail-fact-grid { + @apply grid gap-2.5 md:grid-cols-2; + } + + .detail-fact-card { + border-color: rgba(226, 232, 240, 0.94); + background: rgba(255, 255, 255, 0.92); + border-radius: var(--radius-sm); + @apply border px-3 py-2.5 text-sm; + } + + .detail-fact-card__label { + color: var(--color-text-tertiary); + @apply text-xs font-semibold; + } + + .detail-fact-card__value { + color: var(--color-text-primary); + @apply mt-1 break-words font-medium; + } + + .detail-evidence-card { + border-color: rgba(226, 232, 240, 0.94); + background: rgba(248, 250, 252, 0.72); + border-radius: var(--radius-sm); + @apply border px-3.5 py-3 text-sm; + } + + .detail-evidence-card--accent { + border-color: rgba(0, 57, 138, 0.2); + background: rgba(0, 57, 138, 0.045); + } + + .detail-timeline { + @apply space-y-2.5; + } + + .detail-timeline-item { + border-color: rgba(226, 232, 240, 0.94); + background: rgba(255, 255, 255, 0.94); + border-radius: var(--radius-sm); + @apply border px-3 py-2.5 text-sm; + } + + .detail-timeline-item--current { + border-color: rgba(0, 57, 138, 0.28); + background: rgba(0, 57, 138, 0.055); + } + + .detail-timeline-item--done { + border-color: rgba(16, 185, 129, 0.28); + background: rgba(236, 253, 245, 0.88); + } + + .detail-action-stack { + @apply grid gap-3; + } + + .detail-action-card { + border-color: rgba(226, 232, 240, 0.94); + background: rgba(255, 255, 255, 0.96); + border-radius: var(--radius-sm); + @apply border p-3.5; + } + + .notification-inbox { + @apply space-y-3; + } + + .notification-row-card { + border-color: rgba(226, 232, 240, 0.94); + background: rgba(255, 255, 255, 0.96); + border-radius: var(--radius-sm); + @apply border px-4 py-3 transition-colors hover:border-slate-300 hover:bg-slate-50/50; + } + + .notification-row-card--unread { + border-color: rgba(0, 57, 138, 0.22); + background: rgba(0, 57, 138, 0.035); + } + + .notification-row-card__meta { + color: var(--color-text-tertiary); + @apply text-xs font-medium; + } + .badge-success { @apply inline-flex items-center rounded-full bg-emerald-50 px-2.5 py-0.5 text-xs font-semibold text-emerald-700 ring-1 ring-inset ring-emerald-600/15; } diff --git a/client/src/pages/MobileApprovalsPage.tsx b/client/src/pages/MobileApprovalsPage.tsx index 83e5535a..1dea4634 100644 --- a/client/src/pages/MobileApprovalsPage.tsx +++ b/client/src/pages/MobileApprovalsPage.tsx @@ -1022,7 +1022,7 @@ export function MobileApprovalsPage() { ) : ( )} @@ -1104,14 +1104,14 @@ export function MobileApprovalsPage() { {isOnline ? "在线" : "离线"} - - - 待我审批 - 已处理 - - +
+ + + 待我审批 + 已处理 + + -
-
+
筛选与批量 @@ -1318,7 +1318,7 @@ export function MobileApprovalsPage() { ) : null} {isPushDetail ? ( -
+

处理

{!selectedTaskId ?

请先选择任务。

: null} diff --git a/client/src/pages/MobileMePage.tsx b/client/src/pages/MobileMePage.tsx index 237ffb59..58ff203b 100644 --- a/client/src/pages/MobileMePage.tsx +++ b/client/src/pages/MobileMePage.tsx @@ -89,9 +89,9 @@ export function MobileMePage() { return ( - + 账号与入口} /> - +
@@ -122,7 +122,7 @@ export function MobileMePage() {
- +
diff --git a/client/src/pages/MobileNotificationsPage.tsx b/client/src/pages/MobileNotificationsPage.tsx index 6d91d4b6..e7663354 100644 --- a/client/src/pages/MobileNotificationsPage.tsx +++ b/client/src/pages/MobileNotificationsPage.tsx @@ -137,7 +137,14 @@ export function MobileNotificationsPage() { />
-
+
+
+

通知流

+

未读优先,点击直达业务详情

+
+
+ +
void openNotificationTarget(item, targetLink)} > diff --git a/client/src/pages/MobilePortalPage.tsx b/client/src/pages/MobilePortalPage.tsx index a82c041d..4abcf569 100644 --- a/client/src/pages/MobilePortalPage.tsx +++ b/client/src/pages/MobilePortalPage.tsx @@ -251,11 +251,14 @@ export function MobilePortalPage() { return ( - + 移动工作台} + /> {canAccessApprovals && pendingApprovalCount > 0 ? ( -
-
+
+
待处理
待我审批
@@ -343,7 +346,7 @@ export function MobilePortalPage() {
-
常用
+
常用动作
{actions.map((action) => ( diff --git a/client/src/pages/MobileReimbursementsPage.tsx b/client/src/pages/MobileReimbursementsPage.tsx index 9aa28464..45036962 100644 --- a/client/src/pages/MobileReimbursementsPage.tsx +++ b/client/src/pages/MobileReimbursementsPage.tsx @@ -70,7 +70,10 @@ function ReimbursementCard({ const displayCode = resolveReimbursementDisplayCode(item); return ( -
+
@@ -204,7 +207,8 @@ export function MobileReimbursementsPage() { return ( {STATUS_LABEL[status]}} actions={ canCreateExpense ? ( + ) : null} + + {!item.readAt ? ( + + ) : null} +
+
+
+ ); + }) + ) : ( +
暂无通知
+ )}
) : null} @@ -294,6 +276,6 @@ export function NotificationsPage() { testIdPrefix="notifications" />
-
+ ); } diff --git a/client/src/pages/ReimbursementDetailPage.tsx b/client/src/pages/ReimbursementDetailPage.tsx index 52e07593..1afbd4e0 100644 --- a/client/src/pages/ReimbursementDetailPage.tsx +++ b/client/src/pages/ReimbursementDetailPage.tsx @@ -961,7 +961,7 @@ export function ReimbursementDetailPage() { @@ -1005,11 +1005,11 @@ export function ReimbursementDetailPage() { )} /> -
-
- -
- +
+
+ +
+ -
-
报销编号:{displayCode}
-
报销日期:{detail.reimbursementDate ?? "-"}
-
金额:{detail.totalAmount ?? "-"}
-
提交人:{detail.submitter?.name ?? "-"}
-
报销人:{detail.owner?.name ?? "-"}
-
说明:{detail.description || "-"}
+
+
+
+
报销编号
+
{displayCode}
+
+
+
报销日期
+
{detail.reimbursementDate ?? "-"}
+
+
+
提交人 / 报销人
+
{detail.submitter?.name ?? "-"} / {detail.owner?.name ?? "-"}
+
+
+
说明
+
{detail.description || "-"}
+
+
-
+

付款状态

-

付款备注写入审计日志,凭证、日期和操作人用于财务台账核对。

+

凭证、日期和操作人用于财务台账核对。

-
-
-
付款日期
-
{payment.paymentDate ?? "-"}
+
+
+
付款日期
+
{payment.paymentDate ?? "-"}
-
-
付款凭证号
-
{payment.paymentVoucherNo ?? "-"}
+
+
付款凭证号
+
{payment.paymentVoucherNo ?? "-"}
-
-
付款渠道
-
{payment.paymentChannel ?? "-"}
+
+
付款渠道
+
{payment.paymentChannel ?? "-"}
-
-
操作人 / 时间
-
+
+
操作人 / 时间
+
{payment.paidBy?.name ?? "-"} · {formatDateTime(payment.paidAt)}
@@ -1110,11 +1122,11 @@ export function ReimbursementDetailPage() {
) : null} -
+

关联项目

-

报销可选挂到项目,用于项目执行面板汇总。

+

用于项目执行面板汇总。

{detail.project ? ( @@ -1179,7 +1191,7 @@ export function ReimbursementDetailPage() { {(detail.attachments ?? []).length ? (
{(detail.attachments ?? []).map((attachment) => ( -
+
{attachment.originalName}
@@ -1254,7 +1266,7 @@ export function ReimbursementDetailPage() {

正在刷新费用标准校验结果...

) : null} -
+
@@ -1401,7 +1413,7 @@ export function ReimbursementDetailPage() { const invoiceHint = resolveInvoiceNextStepText(item); return (
@@ -1421,7 +1433,7 @@ export function ReimbursementDetailPage() {
- + {resolveInvoiceStatusLabel(item.status)} {canUnbindInvoices ? ( @@ -1455,7 +1467,7 @@ export function ReimbursementDetailPage() {
{budgetHint ? ( -
+

预算提示

预算池:{budgetHint.budgetCode} / {budgetHint.budgetName}
@@ -1519,22 +1531,22 @@ export function ReimbursementDetailPage() {

) : null} -
-
-
导出状态
-
{erpSyncStatus}
+
+
+
导出状态
+
{erpSyncStatus}
-
-
最近导出
-
{formatDateTime(erpPreviewQuery.data?.exportState.exportedAt ?? null)}
+
+
最近导出
+
{formatDateTime(erpPreviewQuery.data?.exportState.exportedAt ?? null)}
-
-
导出任务
-
{erpPreviewQuery.data?.exportState.lastOutboxId ?? "-"}
+
+
导出任务
+
{erpPreviewQuery.data?.exportState.lastOutboxId ?? "-"}
-
-
外部凭证号
-
{erpPreviewQuery.data?.exportState.externalId ?? "-"}
+
+
外部凭证号
+
{erpPreviewQuery.data?.exportState.externalId ?? "-"}
@@ -1549,7 +1561,7 @@ export function ReimbursementDetailPage() {
-
+
报销编号:{displayCode}
报销人:{detail.owner?.name ?? "-"}
@@ -1565,16 +1577,16 @@ export function ReimbursementDetailPage() { title="审批进度" description={`当前位于第 ${detail.workflowProgress.currentStepIndex + 1} / ${detail.workflowProgress.totalSteps} 步。`} > -
+
{detail.workflowProgress.steps.map((step, index) => (
@@ -1593,9 +1605,9 @@ export function ReimbursementDetailPage() { {detail.approvalHistory.length ? ( -
+
{detail.approvalHistory.map((item) => ( -
+
{workflowActionLabel(item.action)} {formatDateTime(item.createdAt)} diff --git a/client/src/pages/WorkflowTaskDetailPage.tsx b/client/src/pages/WorkflowTaskDetailPage.tsx index 9ab8dd81..88af01af 100644 --- a/client/src/pages/WorkflowTaskDetailPage.tsx +++ b/client/src/pages/WorkflowTaskDetailPage.tsx @@ -104,9 +104,9 @@ function workflowActionLabel(action: string | undefined | null) { function DetailField({ label, value }: { label: string; value: ReactNode }) { return ( -
-
{label}
-
{value || "-"}
+
+
{label}
+
{value || "-"}
); } @@ -263,7 +263,7 @@ export function WorkflowTaskDetailPage() { @@ -277,18 +277,18 @@ export function WorkflowTaskDetailPage() { )} /> -
-
+
+
- + {detail.task.stepName}} /> {entityTitle(detail.workflowInstance?.entityType)}} /> {allowedActionLabels || "-"}} /> {formatDateTime(detail.task.createdAt)}} /> -
+
{detail.reimbursement ? ( <> @@ -401,7 +401,7 @@ export function WorkflowTaskDetailPage() { ) : null} -
+
{actionError ? (

) : null} -

+
-
+

驳回