diff --git a/.trellis/spec/hyb-oa/frontend-react-vite-design.md b/.trellis/spec/hyb-oa/frontend-react-vite-design.md index daa8f203..71992d21 100644 --- a/.trellis/spec/hyb-oa/frontend-react-vite-design.md +++ b/.trellis/spec/hyb-oa/frontend-react-vite-design.md @@ -74,6 +74,23 @@ reference, not a copy of Feishu or Ant Design branding. action, visible common filters, table/card list and right-side operation column. `台账工具` contains export, batch, statistics, templates, full-scope views and advanced filters. +- 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 + or explicit detail/edit area after the user chooses a row; the initial list + page must not auto-select the first item just to fill a second column. +- Ledger status summaries use tabs, segmented controls or compact chips with + counts. Do not place four or five large status cards above ordinary business + lists, and do not put explanatory sentences inside status cards. +- 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. +- 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 + belongs in detail pages, exception blocks or confirmations. - PC detail pages use an Ant Design Pro style detail workspace: top summary, main facts/details/evidence, right-side state and action panel, and bottom history. Do not build PC details as a free stack of equal-weight large cards. @@ -93,10 +110,10 @@ reference, not a copy of Feishu or Ant Design branding. | Surface | Template | First-screen content | Primary action rule | | --- | --- | --- | --- | -| `工作台` | 工作台总览 | 待办摘要、常用业务入口、必要提醒 | No forced header action. Use `去处理`, `新建报销`, `查看全部` inside relevant sections. | +| `工作台` | 工作台总览 | 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 `更多`. | -| `报销` / `采购` / `合同` / `项目` / `资产` / `付款` / `应付账款` / `票据` | 业务台账列表 | Common filters plus table/card list | Header creation only when the route owns creation; risky tools in `台账工具` or detail. | +| `报销` / `采购` / `合同` / `项目` / `资产` / `付款` / `应付账款` / `票据` | 业务台账列表 | 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. | | 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. | diff --git a/.trellis/tasks/05-30-unified-page-design-review/design.md b/.trellis/tasks/05-30-unified-page-design-review/design.md index ad922dee..fa2e53fe 100644 --- a/.trellis/tasks/05-30-unified-page-design-review/design.md +++ b/.trellis/tasks/05-30-unified-page-design-review/design.md @@ -14,11 +14,15 @@ 3. 页头主按钮只用于创建类或真实页面级主动作,例如 `新建报销`、`新建采购`、`新增合同`、`新增资产`。 4. 按钮使用成熟系统常规文案:`处理`、`去处理`、`查看详情`、`查看全部`、`查看进度`、`继续编辑`、`新建`、`新增`、`提交`、`保存`、`更多`。禁止 `处理最急的一件` 这类口号型按钮。 5. 列表页右侧固定操作列;低频或高风险动作进入 `更多`、`台账工具` 或详情页。 -6. PC 详情页采用顶部摘要、中部事实/明细/证据、右侧状态与动作栏、底部历史;移动端采用单列分区和底部安全区主动作。 -7. 审批详情不使用竖向动作卡片。PC 使用一个动作条:`通过` 主按钮,`退回`、`驳回` 次按钮,`转审` 默认进 `更多`;需要理由的动作点击后再展开输入和确认。 -8. 移动端底部只保留一个主动作,次动作进入 `更多` 或确认面板。 -9. 通知中心只做事件收件箱,不复制待办,不做快速上手或流程解释。 -10. 请假、补卡、外出、加班和打卡由企业微信主办;OA 只做记录、镜像、异常、审计和应急入口。 +6. 业务台账首屏统一为单列列表页,不默认使用左列表/右详情,不自动选中第一条记录填充右侧详情。 +7. 状态统计统一降级为 Tab、分段控件或 Chip + 数量;普通业务列表上方不放多张大状态卡片和说明句。 +8. 筛选区默认一行,只放搜索、状态、范围;供应商、法体、成本中心、日期区间、合同类型、敏感级别、导出和测试数据开关进入 `筛选`、`台账工具` 或 Tab 内高级区域。 +9. 业务列表默认不放 `下一步` 列或右侧提示区;异常用短状态标签表达,长说明进入详情、异常块或确认弹窗。 +10. PC 详情页采用顶部摘要、中部事实/明细/证据、右侧状态与动作栏、底部历史;移动端采用单列分区和底部安全区主动作。 +11. 审批详情不使用竖向动作卡片。PC 使用一个动作条:`通过` 主按钮,`退回`、`驳回` 次按钮,`转审` 默认进 `更多`;需要理由的动作点击后再展开输入和确认。 +12. 移动端底部只保留一个主动作,次动作进入 `更多` 或确认面板。 +13. 通知中心只做事件收件箱,不复制待办,不做快速上手或流程解释。 +14. 请假、补卡、外出、加班和打卡由企业微信主办;OA 只做记录、镜像、异常、审计和应急入口。 ## 页面归类 @@ -27,7 +31,7 @@ | 工作台 | 工作台总览 | 待办摘要、常用业务入口、必要提醒;不强行放页头主按钮。 | | 待我处理 | 任务处理列表 | `待处理` / `已处理` 两个 Tab;行操作为 `处理`。 | | 审批详情 | 详情工作区 | 摘要、证据、右侧动作栏、历史;禁止竖向动作卡片。 | -| 报销、采购、合同、项目、资产、付款、应付账款、票据 | 业务台账列表 | 标题、创建按钮、常用筛选、表格/卡片列表、右侧操作列;低频工具进 `台账工具`。 | +| 报销、采购、合同、项目、资产、付款、应付账款、票据 | 业务台账列表 | 标题、创建按钮、一行常用筛选、表格/卡片列表、右侧操作列;低频工具进 `台账工具`;首屏不做左列表/右详情。 | | 业务详情 | 详情工作区 | 摘要、事实、证据、关联、右侧状态/动作、底部历史。 | | 管理 | 管理配置页 | 管理首页分 `人员组织`、`审批流程`、`基础资料`、`系统设置`、`审计排障`;子页表格配置。 | | 个人设置 | 账号设置页 | `个人资料`、`账号安全`、`通知偏好`、`企业微信绑定`;组织角色只读折叠,维护入口进管理。 | diff --git a/.trellis/tasks/05-30-unified-page-design-review/prd.md b/.trellis/tasks/05-30-unified-page-design-review/prd.md index 1fdf62d4..809fb543 100644 --- a/.trellis/tasks/05-30-unified-page-design-review/prd.md +++ b/.trellis/tasks/05-30-unified-page-design-review/prd.md @@ -54,6 +54,10 @@ - 侧边栏入口已确认:常用业务直接展示,管理/低频/冻结/阶段性页面进入 `更多`。默认直接入口包括 `工作台`、`待我处理`、`报销`、`采购`、`合同`、`项目`、`资产`、`付款`、`应付账款`、`票据`、`团队`、`请假记录`、`管理`,但仍需按角色和权限收敛,不能按权限把所有可见模块全铺出来。 - `待我处理` 页面已确认:采用飞书审批式任务列表,主 Tab 为 `待处理` / `已处理`。顶部只显示数量和必要筛选;列表行展示事项、申请人/发起人、等待时间、状态、业务编号和右侧 `处理` 操作。`已处理记录` 不作为孤立右上角按钮,页面不显示解释性副标题或大段说明。 - 报销、采购、合同、项目、资产、付款、应付账款、票据等业务页已确认统一采用 `业务台账列表`:页头标题与创建按钮、常用筛选区、表格/卡片列表、右侧操作列。低频或高风险工具统一进入 `台账工具`、`更多` 或详情页,不在列表首屏铺开。 +- 二次线上反馈后确认业务台账页继续收紧:首屏统一为单列列表,不默认使用左列表/右详情,不自动选中第一条记录;状态摘要改为 Tab/Chip + 数量;筛选区默认只放搜索、状态、范围,超过一行的筛选进入 `筛选` 或 `台账工具`;列表默认删除 `下一步` 列和右侧提示区。 +- 工作台已确认进一步极简:只显示紧凑数量、入口、真实提醒和动作,不再显示解释普通流程的副文案。 +- 详情页动作区已确认:PC 详情页可保留右侧固定动作区,移动端底部固定主动作;列表页不承担详情和流程说明。 +- 本轮追加实现范围已确认:修审批详情 `更多` 遮挡、工作台去解释文案、采购改单列列表、合同筛选压一行、项目删除右侧提示、资产/应付账款去首屏双列和大卡片。 - 管理配置页已确认:侧边栏只保留 `管理`,管理首页按 `人员组织`、`审批流程`、`基础资料`、`系统设置`、`审计排障` 分组进入;子页采用搜索/筛选、表格和右侧操作列。普通业务页面、工作台和个人设置不得混入管理配置矩阵。 - 个人设置页已确认:采用常见网站账号设置结构,只包含 `个人资料`、`账号安全`、`通知偏好`、`企业微信绑定`。组织、角色、直属汇报只做折叠只读摘要;人员档案、权限、组织维护和角色管理全部进入 `管理`。 - 通知中心已确认:只做事件收件箱,显示状态变化、提醒、失败、同步结果和流转消息;点击通知必须跳到业务详情、审批任务或失败说明页。通知中心不复制 `待我处理` 列表,不承担快速上手或流程解释职责。 diff --git a/client/src/index.css b/client/src/index.css index 4b44c9c1..173e3603 100644 --- a/client/src/index.css +++ b/client/src/index.css @@ -650,7 +650,7 @@ } .detail-action-stack { - @apply grid gap-3; + @apply grid gap-3 overflow-visible; } .detail-action-card { @@ -661,7 +661,7 @@ } .workflow-action-panel { - @apply xl:sticky xl:top-20; + @apply relative z-30 xl:sticky xl:top-20; } .workflow-action-bar { @@ -669,7 +669,7 @@ } .workflow-action-more { - @apply relative; + @apply relative z-40; } .workflow-action-more > summary { @@ -694,7 +694,7 @@ background: #fff; border-radius: var(--radius-sm); box-shadow: var(--shadow-md); - @apply absolute right-0 top-10 z-20 min-w-28 px-3 py-2 text-left text-sm font-medium text-zinc-800 hover:bg-slate-50; + @apply absolute right-0 top-10 z-50 min-w-28 px-3 py-2 text-left text-sm font-medium text-zinc-800 hover:bg-slate-50; } .notification-inbox { @@ -889,7 +889,7 @@ .portal-task-row { background: rgba(255, 255, 255, 0.88); color: var(--color-text-primary); - @apply grid min-h-[68px] grid-cols-[auto,minmax(0,1fr)] items-center gap-3 px-3.5 py-3 transition-colors hover:bg-white md:grid-cols-[auto,minmax(0,1fr),auto,auto]; + @apply grid min-h-[56px] grid-cols-[auto,minmax(0,1fr)] items-center gap-3 px-3.5 py-2.5 transition-colors hover:bg-white md:grid-cols-[auto,minmax(0,1fr),auto,auto]; } .portal-task-row--warning { diff --git a/client/src/pages/ApOperationsPage.tsx b/client/src/pages/ApOperationsPage.tsx index 567c4e03..0bdb1fd9 100644 --- a/client/src/pages/ApOperationsPage.tsx +++ b/client/src/pages/ApOperationsPage.tsx @@ -229,32 +229,6 @@ function prettyJson(value: unknown) { } } -function SummaryCard({ - title, - value, - tone = "zinc", -}: { - title: string; - value: number | string; - tone?: "zinc" | "amber" | "red" | "emerald"; -}) { - const toneClass = - tone === "amber" - ? "text-amber-700" - : tone === "red" - ? "text-red-700" - : tone === "emerald" - ? "text-emerald-700" - : "text-zinc-900"; - - return ( -
-
{title}
-
{value}
-
- ); -} - function SectionError({ message }: { message: string }) { return (
@@ -619,7 +593,6 @@ export function ApOperationsPage() {
-
- 当前工具不会执行真实付款,也不会生成正式总账凭证;付款仍由线下银行或既有财务流程完成。 -
) : null} diff --git a/client/src/pages/AssetRegistryPage.tsx b/client/src/pages/AssetRegistryPage.tsx index 567315d5..a631227a 100644 --- a/client/src/pages/AssetRegistryPage.tsx +++ b/client/src/pages/AssetRegistryPage.tsx @@ -364,24 +364,6 @@ export function AssetRegistryPage() { } }, [searchParams, selectedAssetId]); - useEffect(() => { - if (isCreatingAsset) { - return; - } - if (selectedAssetId) { - if (assetDetailQuery.isLoading) { - return; - } - if (!assetDetailQuery.isError) { - return; - } - } - const nextId = assetsQuery.data?.items?.[0]?.id ?? ""; - if (nextId !== selectedAssetId) { - setSelectedAssetId(nextId); - } - }, [assetDetailQuery.isError, assetDetailQuery.isLoading, assetsQuery.data?.items, isCreatingAsset, selectedAssetId]); - useEffect(() => { const currentId = searchParams.get("id") ?? ""; if (isCreatingAsset) { @@ -812,20 +794,6 @@ export function AssetRegistryPage() { return (b.updatedAt ?? "").localeCompare(a.updatedAt ?? ""); }); }, [assetItems]); - const assetStats = useMemo(() => { - return assetItems.reduce( - (acc, item) => { - acc.total += 1; - if (item.assetType === "fixed_asset") acc.fixedAsset += 1; - else acc.electronicAsset += 1; - if (item.assetType === "fixed_asset" && !item.assignee) acc.unassigned += 1; - if (!item.lastVerifiedAt) acc.needsVerification += 1; - if ((item.assetType === "fixed_asset" && !item.assignee) || !item.lastVerifiedAt) acc.attention += 1; - return acc; - }, - { total: 0, fixedAsset: 0, electronicAsset: 0, unassigned: 0, needsVerification: 0, attention: 0 }, - ); - }, [assetItems]); const selectAsset = (assetId: string) => { setIsCreatingAsset(false); setSelectedAssetId(assetId); @@ -835,7 +803,6 @@ export function AssetRegistryPage() { )) @@ -1144,7 +1082,6 @@ export function AssetRegistryPage() { 责任链 位置 状态 - 下一步 @@ -1171,12 +1108,11 @@ export function AssetRegistryPage() {
盘点 {item.lastVerifiedAt ? formatDisplayDateTime(item.lastVerifiedAt) : "未登记"}
- {assetNextStep(item)} )) ) : ( - + 暂无资产卡片 @@ -1188,13 +1124,8 @@ export function AssetRegistryPage() { ) : null} + {selectedAssetId || isCreatingAsset ? (
- {!selectedAssetId && !isCreatingAsset ? ( -
- 请先从列表选择资产卡片,或点击“新建资产卡片”。 -
- ) : null} - {selectedAssetId && !isCreatingAsset && assetDetailQuery.isLoading ? : null} {selectedAssetId && !isCreatingAsset && assetDetailQuery.isError ? (

{resolveError(assetDetailQuery.error)}

@@ -1207,9 +1138,11 @@ export function AssetRegistryPage() {

{isCreatingAsset ? "新建资产卡片" : currentAsset ? : "资产卡片"}

-

- {isCreatingAsset ? "创建新的资产台账卡片。" : formatTextWithCompactLongNumbers(currentAsset?.name)} -

+ {!isCreatingAsset ? ( +

+ {formatTextWithCompactLongNumbers(currentAsset?.name)} +

+ ) : null}
{!isCreatingAsset && currentAsset ? : null} @@ -1626,6 +1559,7 @@ export function AssetRegistryPage() { ) : null} + ) : null}
diff --git a/client/src/pages/ContractsRegistryPage.tsx b/client/src/pages/ContractsRegistryPage.tsx index 638db83f..ca5b2104 100644 --- a/client/src/pages/ContractsRegistryPage.tsx +++ b/client/src/pages/ContractsRegistryPage.tsx @@ -26,7 +26,6 @@ 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 { MetricCard } from "../components/patterns/MetricCard"; import { PageScaffold } from "../components/patterns/PageScaffold"; import { Button } from "../components/ui/button"; import { Input } from "../components/ui/input"; @@ -250,9 +249,6 @@ export function ContractsRegistryPage() { ? "保留现有责任人" : "默认当前用户"; const amountInvalid = Boolean(editor.amount.trim()) && !AMOUNT_PATTERN.test(editor.amount.trim()); - const activeContractCount = visibleContracts.filter((item) => item.status === "active").length; - const draftContractCount = visibleContracts.filter((item) => item.status === "draft").length; - const archivedContractCount = visibleContracts.filter((item) => item.status === "archived").length; const expiringContractCount = expiringQuery.data?.total ?? expiringItems.length; const refreshAll = async () => { @@ -323,21 +319,14 @@ export function ContractsRegistryPage() { return ( - 可见 {visibleContracts.length} 份 - 30 天到期 {expiringContractCount} 份 - 私密正文不入库 - - } actions={
+ {initialProjectId ? ( + ) : null} @@ -345,82 +334,21 @@ export function ContractsRegistryPage() { } /> -
- - - 0 ? "warning" : "neutral"} /> - -
- -
-
-
-
生命周期焦点
-
{expiringQuery.data?.total ?? 0}
-
- 份合同将在 {EXPIRING_DAYS} 天内到期;续约会生成草稿合同,导出仅用于授权复核。 -
-
-
- -
-
- - {expiringQuery.isError ? ( -
{resolveError(expiringQuery.error, "加载到期预警失败")}
- ) : null} - - {showExpiring ? ( -
- {expiringItems.map((item) => ( -
-
-
- {item.contractNo} · {item.counterpartyName} -
-
- {contractTypeLabel(item.contractType)} · 到期日 {formatDisplayDate(item.expiryDate)} · 责任人 {item.owner?.name ?? "-"} -
-
-
- -
-
- ))} - {!expiringQuery.isLoading && !expiringItems.length ? ( -
未来 {EXPIRING_DAYS} 天内暂无即将到期合同。
- ) : null} -
- ) : null} -
- {showLedgerTools ? (

台账工具

-

- 新建/编辑、导出和高级到期范围放在这里。导出文件可能包含合同金额、相对方、责任人和归档摘要。 -

+ @@ -446,10 +374,31 @@ export function ContractsRegistryPage() { />
+ {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} +
+ ) : null}
) : null} -
+
{showLedgerTools || editorId ? (
@@ -735,11 +684,11 @@ export function ContractsRegistryPage() {
-
-
- +
+
-
- +
-
- +
-
- +
setFilters((current) => ({ ...current, counterpartyName: event.target.value }))} - placeholder="输入相对方名称关键字" + placeholder="搜索相对方" />
diff --git a/client/src/pages/PortalHomePage.tsx b/client/src/pages/PortalHomePage.tsx index 559309b1..040f82ed 100644 --- a/client/src/pages/PortalHomePage.tsx +++ b/client/src/pages/PortalHomePage.tsx @@ -515,11 +515,10 @@ function SummaryStrip({ reminderCount: number; }) { const attentionCount = blockerCount + reminderCount; - const summaryText = actionCount > 0 - ? `今天有 ${actionCount} 件待处理${progressCount > 0 ? `,${progressCount} 个进度更新` : ""}${attentionCount > 0 ? `,${attentionCount} 个团队或管理提醒` : ""}。` - : progressCount > 0 || attentionCount > 0 - ? `今天没有必须处理的事项${progressCount > 0 ? `,有 ${progressCount} 个进度更新` : ""}${attentionCount > 0 ? `,${attentionCount} 个团队或管理提醒` : ""}。` - : "今天没有需要你处理的事项。"; + const summaryParts = [`待处理 ${actionCount}`]; + if (progressCount > 0) summaryParts.push(`进度 ${progressCount}`); + if (attentionCount > 0) summaryParts.push(`提醒 ${attentionCount}`); + const summaryText = summaryParts.join(" · "); return (
@@ -545,7 +544,6 @@ function WorkItemRow({ item }: { item: WorkItem }) { {item.title} - {item.reason} {item.meta} {item.actionLabel} @@ -578,7 +576,6 @@ function QuickEntryPanel({ entries }: { entries: QuickEntry[] }) { {entry.label} - {entry.actionLabel} ))} @@ -601,8 +598,7 @@ function TodayActionPanel({ ) : (
-
今天没有需要你处理的事项。
-
需要发起新申请或回看进度时,从下面的常用动作进入。
+
暂无待处理
{emptyActions.length ? (
diff --git a/client/src/pages/ProjectsPage.tsx b/client/src/pages/ProjectsPage.tsx index 0c72da34..a52d6a8c 100644 --- a/client/src/pages/ProjectsPage.tsx +++ b/client/src/pages/ProjectsPage.tsx @@ -14,8 +14,7 @@ import { useAuth } from "../auth/AuthContext"; import { hasAnyPermission } from "../auth/permissions"; import { DateTextInput } from "../components/common/DateTextInput"; import { StatusBadge } from "../components/common/StatusBadge"; -import { FilterBar, ListWorkspace, StatStrip, WorkspaceHeader, WorkspaceNotice } from "../components/patterns/ListWorkspace"; -import { MetricCard } from "../components/patterns/MetricCard"; +import { FilterBar, ListWorkspace, WorkspaceHeader, WorkspaceNotice } from "../components/patterns/ListWorkspace"; import { SectionPanel } from "../components/patterns/SectionPanel"; import { Button } from "../components/ui/button"; import { Input } from "../components/ui/input"; @@ -123,20 +122,6 @@ export function ProjectsPage() { }); }, [projectItems, showTrialData]); const hiddenTrialProjectCount = projectItems.length - visibleProjectItems.length; - const projectStats = useMemo(() => { - return visibleProjectItems.reduce( - (acc, item) => { - acc.total += 1; - if (item.status === "active") { - acc.active += 1; - } - acc.overdueMilestones += item.counts.overdueMilestones; - acc.openRisks += item.counts.openRisks; - return acc; - }, - { total: 0, active: 0, overdueMilestones: 0, openRisks: 0 }, - ); - }, [visibleProjectItems]); const createMutation = useMutation({ mutationFn: () => @@ -188,7 +173,6 @@ export function ProjectsPage() { -

试运行默认隐藏 TEST/SMOKE 项目。全范围查看请使用筛选区的范围按钮,避免普通项目负责人默认进入公司全量项目。

) : null} -
-
+
updateSearch({ q: event.target.value })} />