收敛 PC 工作台视觉 05-27-pc-frontend-visual-consistency-wave1

这个提交包含在:
2026-05-27 14:10:51 +08:00
父节点 63e214ca77
当前提交 466075c4a9
共修改 16 个文件,包含 551 行新增和 148 行删除
+77 -32
查看文件
@@ -384,23 +384,6 @@ function AnnouncementsList({ announcements }: { announcements: PortalHomePayload
);
}
function RecentVisitsPanel() {
const recentVisits = readRecentVisits();
return (
<SectionPanel title="继续工作">
<div className="space-y-3">
{recentVisits.length ? recentVisits.map((item) => (
<Link key={item.path} to={item.path} className="portal-list-card">
<div className="font-medium text-zinc-900">{item.title}</div>
<div className="mt-1 text-xs text-zinc-500">{item.path}</div>
</Link>
)) : <EmptyStateCard title="暂无最近访问" description="最近页面会显示在这里。" />}
</div>
</SectionPanel>
);
}
function QuickLinksPanel({ items }: { items: PortalHomePayload["quickLinks"] }) {
const safeItems = Array.isArray(items) ? items : [];
@@ -431,7 +414,7 @@ function SecondaryCardsPanel({ title, description, cards }: { title: string; des
title={card.title}
value={card.value}
href={card.href}
note="查看"
note="查看"
tone={severityTone(card.severity)}
/>
))}
@@ -440,6 +423,75 @@ function SecondaryCardsPanel({ title, description, cards }: { title: string; des
);
}
function CompactGroup({ title, children }: { title: string; children: ReactNode }) {
return (
<section className="portal-compact-group">
<h3 className="portal-compact-group__title">{title}</h3>
<div className="portal-compact-group__body">{children}</div>
</section>
);
}
function CompactSummaryLinks({ title, items }: { title: string; items: SummaryItem[] }) {
return (
<CompactGroup title={title}>
{items.length ? items.map((item) => (
<Link key={item.key} to={item.href} className="portal-compact-row">
<span className="min-w-0 truncate">{item.label}</span>
<span className={item.tone === "warning" ? "shrink-0 font-semibold text-amber-700" : "shrink-0 font-semibold text-slate-900"}>
{item.value}
</span>
</Link>
)) : <div className="px-2 py-1.5 text-sm text-slate-500">暂无摘要。</div>}
</CompactGroup>
);
}
function CompactRecentVisits() {
const recentVisits = readRecentVisits();
return (
<CompactGroup title="继续工作">
{recentVisits.length ? recentVisits.map((item) => (
<Link key={item.path} to={item.path} className="portal-compact-row">
<span className="min-w-0 truncate">{item.title}</span>
<span className="shrink-0 text-xs text-slate-400">打开</span>
</Link>
)) : <div className="px-2 py-1.5 text-sm text-slate-500">暂无最近访问。</div>}
</CompactGroup>
);
}
function CompactQuickLinks({ items }: { items: PortalHomePayload["quickLinks"] }) {
const safeItems = Array.isArray(items) ? items : [];
return (
<CompactGroup title="常用入口">
{safeItems.length ? safeItems.slice(0, 6).map((item) => (
<Link key={item.key} to={item.href} className="portal-compact-row">
<span className="min-w-0 truncate">{item.title}</span>
<span className="shrink-0 text-xs text-slate-400">进入</span>
</Link>
)) : <div className="px-2 py-1.5 text-sm text-slate-500">暂无常用入口。</div>}
</CompactGroup>
);
}
function CompactMetricLinks({ title, cards }: { title: string; cards: PortalHomePayload["cards"] }) {
return (
<CompactGroup title={title}>
{cards.length ? cards.slice(0, 6).map((card) => (
<Link key={card.key} to={card.href} className="portal-compact-row">
<span className="min-w-0 truncate">{card.title}</span>
<span className={card.severity === "high" || card.severity === "warning" ? "shrink-0 font-semibold text-amber-700" : "shrink-0 font-semibold text-slate-900"}>
{card.value}
</span>
</Link>
)) : <div className="px-2 py-1.5 text-sm text-slate-500">暂无统计项。</div>}
</CompactGroup>
);
}
function AdoptionSummaryPanel({ adoptionSummary }: { adoptionSummary: NonNullable<PortalHomePayload["adoptionSummary"]> | null }) {
if (!adoptionSummary) {
return null;
@@ -1191,21 +1243,14 @@ export function PortalHomePage() {
</>
) : (
<SecondaryWorkspacePanel
title="我的申请与资产 / 继续工作"
description="申请、资产、最近访问"
title="更多入口"
description="申请、资产、最近访问和项目指标"
>
<div className="grid gap-6 xl:grid-cols-[minmax(0,1fr),minmax(0,1fr)]">
<SummaryPanel title="我的申请与资产" items={employeeSummaryItems} />
<RecentVisitsPanel />
</div>
<div className="grid gap-6 xl:grid-cols-[minmax(0,1fr),minmax(0,1fr)]">
<QuickLinksPanel items={quickLinkItems} />
<SecondaryCardsPanel
title="项目与协同"
description="项目、参考指标"
cards={secondaryCards}
/>
<div className="portal-secondary-compact-grid">
<CompactSummaryLinks title="我的申请与资产" items={employeeSummaryItems} />
<CompactRecentVisits />
<CompactQuickLinks items={quickLinkItems} />
<CompactMetricLinks title="项目与协同" cards={secondaryCards} />
</div>
</SecondaryWorkspacePanel>
)}
+48 -51
查看文件
@@ -366,30 +366,6 @@ export function ReimbursementsPage() {
subtitle="草稿、审批和付款状态。"
actions={
<div className="flex flex-col gap-2 sm:flex-row sm:items-center">
{showProxyOwnerSelector ? (
<select
className="select-std"
data-testid="reimbursements-owner-select"
onChange={(event) => setProxyOwnerEmployeeId(event.target.value)}
value={proxyOwnerEmployeeId}
>
<option value="">自己</option>
{(proxyDelegationsQuery.data?.received ?? [])
.filter((item) => item.status === "active")
.map((item) => (
<option key={item.id} value={item.principalEmployeeId}>
{item.principal?.name ?? item.principalEmployeeId}
</option>
))}
</select>
) : null}
{canViewAll ? (
<Button asChild variant="secondary" type="button">
<Link to="/reimbursements/dashboard">统计分析</Link>
</Button>
) : null}
{canCreateExpense ? (
<DropdownMenu>
<DropdownMenuTrigger asChild>
@@ -446,33 +422,6 @@ export function ReimbursementsPage() {
</DropdownMenuContent>
</DropdownMenu>
) : null}
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button
data-testid="reimbursements-export"
variant="secondary"
type="button"
disabled={exportMutation.isPending}
>
{exportMutation.isPending ? "导出中..." : "导出"}
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent align="end">
<DropdownMenuItem
data-testid="reimbursements-export-csv"
onSelect={() => setExportConfirmFormat("csv")}
>
导出 CSV
</DropdownMenuItem>
<DropdownMenuItem
data-testid="reimbursements-export-xlsx"
onSelect={() => setExportConfirmFormat("xlsx")}
>
导出 Excel
</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>
</div>
}
/>
@@ -547,6 +496,24 @@ export function ReimbursementsPage() {
</Tabs>
<div className="flex flex-wrap items-center gap-2">
{showProxyOwnerSelector ? (
<select
className="select-sm h-10"
data-testid="reimbursements-owner-select"
onChange={(event) => setProxyOwnerEmployeeId(event.target.value)}
value={proxyOwnerEmployeeId}
>
<option value="">自己</option>
{(proxyDelegationsQuery.data?.received ?? [])
.filter((item) => item.status === "active")
.map((item) => (
<option key={item.id} value={item.principalEmployeeId}>
{item.principal?.name ?? item.principalEmployeeId}
</option>
))}
</select>
) : null}
{canViewAll ? (
<>
<Button
@@ -565,9 +532,39 @@ export function ReimbursementsPage() {
>
全范围
</Button>
<Button asChild variant="secondary" type="button">
<Link to="/reimbursements/dashboard">统计分析</Link>
</Button>
</>
) : null}
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button
data-testid="reimbursements-export"
variant="secondary"
type="button"
disabled={exportMutation.isPending}
>
{exportMutation.isPending ? "导出中..." : "导出"}
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent align="end">
<DropdownMenuItem
data-testid="reimbursements-export-csv"
onSelect={() => setExportConfirmFormat("csv")}
>
导出 CSV
</DropdownMenuItem>
<DropdownMenuItem
data-testid="reimbursements-export-xlsx"
onSelect={() => setExportConfirmFormat("xlsx")}
>
导出 Excel
</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>
{canShowBatchEntry ? (
<Button
variant={batchMode ? "default" : "secondary"}