统一业务台账首屏设计(05-30-unified-page-design-review)
这个提交包含在:
@@ -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() {
|
||||
<ListWorkspace>
|
||||
<WorkspaceHeader
|
||||
title="项目"
|
||||
subtitle="轻项目主档、成员、里程碑、任务与风险统一在 OA 内跟进。"
|
||||
actions={
|
||||
<div className="flex gap-2">
|
||||
<Button asChild variant="secondary" type="button">
|
||||
@@ -201,15 +185,8 @@ export function ProjectsPage() {
|
||||
}
|
||||
/>
|
||||
|
||||
<StatStrip columns={4}>
|
||||
<MetricCard title="我相关项目" value={projectStats.total} note={mine ? "默认只看我负责/我参与" : "已切换到全范围"} />
|
||||
<MetricCard title="进行中" value={projectStats.active} note="优先跟进活跃项目" />
|
||||
<MetricCard title="逾期里程碑" value={projectStats.overdueMilestones} note="延期事项待处理" tone={projectStats.overdueMilestones > 0 ? "warning" : "neutral"} />
|
||||
<MetricCard title="未关闭风险" value={projectStats.openRisks} note="问题留痕建议绑定责任人" tone={projectStats.openRisks > 0 ? "danger" : "neutral"} />
|
||||
</StatStrip>
|
||||
|
||||
{showLedgerTools ? (
|
||||
<SectionPanel title="台账工具" description="新建项目、显示测试数据和全范围查看放在这里,避免普通首屏变成配置台。">
|
||||
<SectionPanel title="台账工具">
|
||||
<div className="grid gap-5 xl:grid-cols-[minmax(0,1fr),260px]">
|
||||
<div>
|
||||
{!canManageProjects ? (
|
||||
@@ -274,14 +251,12 @@ export function ProjectsPage() {
|
||||
<Button type="button" variant={showTrialData ? "default" : "secondary"} onClick={() => setShowTrialData((value) => !value)}>
|
||||
{showTrialData ? "隐藏测试数据" : "显示测试数据"}
|
||||
</Button>
|
||||
<p>试运行默认隐藏 TEST/SMOKE 项目。全范围查看请使用筛选区的范围按钮,避免普通项目负责人默认进入公司全量项目。</p>
|
||||
</div>
|
||||
</div>
|
||||
</SectionPanel>
|
||||
) : null}
|
||||
|
||||
<div className="page-shell-grid">
|
||||
<div className="space-y-6">
|
||||
<div className="space-y-6">
|
||||
<FilterBar className="grid gap-3 md:grid-cols-[minmax(0,1fr),180px,160px]">
|
||||
<Input placeholder="搜索项目编号、项目名、客户名" value={q} onChange={(event) => updateSearch({ q: event.target.value })} />
|
||||
<select className="select-std" value={status} onChange={(event) => updateSearch({ status: event.target.value })}>
|
||||
@@ -302,10 +277,7 @@ export function ProjectsPage() {
|
||||
</WorkspaceNotice>
|
||||
) : null}
|
||||
|
||||
<SectionPanel
|
||||
title="项目列表"
|
||||
description={visibleProjectItems.length ? `当前显示 ${visibleProjectItems.length} 个项目` : "按项目名、客户或状态快速缩小范围"}
|
||||
>
|
||||
<SectionPanel title="项目列表">
|
||||
<div className="space-y-4">
|
||||
{listQuery.isLoading ? <div className="text-sm text-zinc-500">项目加载中...</div> : null}
|
||||
{listQuery.isError ? <div className="text-sm text-red-600">{resolveError(listQuery.error, "加载项目失败")}</div> : null}
|
||||
@@ -341,14 +313,6 @@ export function ProjectsPage() {
|
||||
</div>
|
||||
</div>
|
||||
</SectionPanel>
|
||||
</div>
|
||||
|
||||
<SectionPanel title="下一步提示" description="项目页只保留轻台账和详情入口。">
|
||||
<div className="space-y-3 text-sm text-zinc-600">
|
||||
<p>项目列表用于看状态、风险、负责人和进入详情;成员、里程碑、风险、交付记录和关联业务在详情页处理。</p>
|
||||
<p>合同、采购、报销和付款只展示摘要与跳转,不在项目列表直接处理对方模块动作。</p>
|
||||
</div>
|
||||
</SectionPanel>
|
||||
</div>
|
||||
</ListWorkspace>
|
||||
);
|
||||
|
||||
在新工单中引用
屏蔽一个用户