统一业务台账首屏设计(05-30-unified-page-design-review)
这个提交包含在:
@@ -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() {
|
||||
<ListWorkspace>
|
||||
<WorkspaceHeader
|
||||
title="资产台账"
|
||||
subtitle="管理固定资产责任链、位置、盘点和电子资产索引;分类维护和高级筛选收在台账工具。"
|
||||
actions={
|
||||
<div className="flex flex-wrap gap-2">
|
||||
<Button type="button" variant="secondary" onClick={() => navigate("/mobile/assets/scan") }>
|
||||
@@ -848,35 +815,11 @@ export function AssetRegistryPage() {
|
||||
}
|
||||
/>
|
||||
|
||||
<section className="ops-status-strip grid gap-4 md:grid-cols-2 xl:grid-cols-4" data-testid="asset-registry-summary">
|
||||
<div className="finance-metric-card">
|
||||
<div className="text-sm text-zinc-500">当前可见</div>
|
||||
<div className="mt-1 text-2xl font-semibold text-zinc-900">{assetStats.total}</div>
|
||||
<div className="mt-1 text-xs text-zinc-500">{mine ? "默认我相关资产" : "全范围查看"}</div>
|
||||
</div>
|
||||
<div className="finance-metric-card">
|
||||
<div className="text-sm text-zinc-500">固定资产 FA</div>
|
||||
<div className="mt-1 text-2xl font-semibold text-zinc-900">{assetStats.fixedAsset}</div>
|
||||
<div className="mt-1 text-xs text-zinc-500">责任、位置、盘点和交接</div>
|
||||
</div>
|
||||
<div className="finance-metric-card">
|
||||
<div className="text-sm text-zinc-500">电子资产索引</div>
|
||||
<div className="mt-1 text-2xl font-semibold text-zinc-900">{assetStats.electronicAsset}</div>
|
||||
<div className="mt-1 text-xs text-zinc-500">只登记责任和验证摘要</div>
|
||||
</div>
|
||||
<div className="finance-metric-card">
|
||||
<div className="text-sm text-zinc-500">待补责任/验证</div>
|
||||
<div className="mt-1 text-2xl font-semibold text-zinc-900">{assetStats.attention}</div>
|
||||
<div className="mt-1 text-xs text-zinc-500">优先补齐保管和最近盘点</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<div className={showLedgerTools ? "grid gap-6 xl:grid-cols-[380px_minmax(0,1fr)]" : "grid gap-6"}>
|
||||
{showLedgerTools ? (
|
||||
<div className="card-std space-y-4 p-4 md:p-5" data-testid="asset-ledger-tools">
|
||||
<div className="rounded-lg border border-slate-200 bg-slate-50 p-3 text-sm text-slate-600">
|
||||
<div className="font-medium text-slate-900">台账工具</div>
|
||||
<p className="mt-1">新建资产卡片、分类维护、全范围与高级筛选放在这里,普通首屏只保留查找和当前资产处理。</p>
|
||||
{canManage ? (
|
||||
<Button type="button" className="mt-3" data-testid="asset-create" onClick={resetAssetEditor}>
|
||||
新建资产卡片
|
||||
@@ -927,7 +870,6 @@ export function AssetRegistryPage() {
|
||||
<div className="space-y-3 rounded-lg border border-zinc-200 bg-white p-3">
|
||||
<div>
|
||||
<h4 className="text-sm font-semibold text-zinc-900">高级筛选</h4>
|
||||
<p className="text-xs text-zinc-500">低频分类、组织、责任人和敏感级别筛选放在工具区。</p>
|
||||
</div>
|
||||
<div className="grid gap-3">
|
||||
<select className="select-std" value={sensitivityFilter} onChange={(event) => setSensitivityFilter(event.target.value as AssetSensitivityLevel | "") }>
|
||||
@@ -1040,7 +982,7 @@ export function AssetRegistryPage() {
|
||||
</div>
|
||||
) : null}
|
||||
|
||||
<div className={showLedgerTools ? "grid gap-6 2xl:grid-cols-[minmax(0,0.95fr)_minmax(420px,1.05fr)]" : "grid gap-6 xl:grid-cols-[minmax(0,0.95fr)_minmax(420px,1.05fr)]"}>
|
||||
<div className="grid gap-6">
|
||||
<FilterBar className="space-y-4">
|
||||
<div className="grid gap-3 md:grid-cols-[minmax(0,1fr)_180px_190px_auto]">
|
||||
<Input
|
||||
@@ -1124,10 +1066,6 @@ export function AssetRegistryPage() {
|
||||
<div className="text-zinc-400">位置</div>
|
||||
<div className="mt-0.5 truncate text-zinc-700">{item.location ?? "-"}</div>
|
||||
</div>
|
||||
<div className="col-span-2">
|
||||
<div className="text-zinc-400">下一步</div>
|
||||
<div className="mt-0.5 truncate text-zinc-700">{assetNextStep(item)}</div>
|
||||
</div>
|
||||
</div>
|
||||
</button>
|
||||
))
|
||||
@@ -1144,7 +1082,6 @@ export function AssetRegistryPage() {
|
||||
<TableHead>责任链</TableHead>
|
||||
<TableHead>位置</TableHead>
|
||||
<TableHead>状态</TableHead>
|
||||
<TableHead>下一步</TableHead>
|
||||
</tr>
|
||||
</TableHeader>
|
||||
<TableBody>
|
||||
@@ -1171,12 +1108,11 @@ export function AssetRegistryPage() {
|
||||
<div className="text-xs text-zinc-500">盘点 {item.lastVerifiedAt ? formatDisplayDateTime(item.lastVerifiedAt) : "未登记"}</div>
|
||||
</TableCell>
|
||||
<TableCell><StatusBadge status={item.status} /></TableCell>
|
||||
<TableCell>{assetNextStep(item)}</TableCell>
|
||||
</TableRow>
|
||||
))
|
||||
) : (
|
||||
<TableRow>
|
||||
<TableCell colSpan={6} className="py-4 text-zinc-500">
|
||||
<TableCell colSpan={5} className="py-4 text-zinc-500">
|
||||
暂无资产卡片
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
@@ -1188,13 +1124,8 @@ export function AssetRegistryPage() {
|
||||
) : null}
|
||||
</FilterBar>
|
||||
|
||||
{selectedAssetId || isCreatingAsset ? (
|
||||
<div className="card-std space-y-4 p-4 md:p-5">
|
||||
{!selectedAssetId && !isCreatingAsset ? (
|
||||
<div className="rounded-lg border border-dashed border-zinc-300 p-8 text-center text-sm text-zinc-500">
|
||||
请先从列表选择资产卡片,或点击“新建资产卡片”。
|
||||
</div>
|
||||
) : null}
|
||||
|
||||
{selectedAssetId && !isCreatingAsset && assetDetailQuery.isLoading ? <TableSkeleton columns={2} rows={8} /> : null}
|
||||
{selectedAssetId && !isCreatingAsset && assetDetailQuery.isError ? (
|
||||
<p className="rounded-lg border border-red-200 bg-red-50 px-3 py-2 text-sm text-red-600">{resolveError(assetDetailQuery.error)}</p>
|
||||
@@ -1207,9 +1138,11 @@ export function AssetRegistryPage() {
|
||||
<h3 className="text-lg font-semibold text-zinc-900">
|
||||
{isCreatingAsset ? "新建资产卡片" : currentAsset ? <CompactAssetNo value={currentAsset.assetNo} /> : "资产卡片"}
|
||||
</h3>
|
||||
<p className="text-sm text-zinc-500" title={currentAsset?.name ?? undefined}>
|
||||
{isCreatingAsset ? "创建新的资产台账卡片。" : formatTextWithCompactLongNumbers(currentAsset?.name)}
|
||||
</p>
|
||||
{!isCreatingAsset ? (
|
||||
<p className="text-sm text-zinc-500" title={currentAsset?.name ?? undefined}>
|
||||
{formatTextWithCompactLongNumbers(currentAsset?.name)}
|
||||
</p>
|
||||
) : null}
|
||||
</div>
|
||||
{!isCreatingAsset && currentAsset ? <StatusBadge status={currentAsset.status} /> : null}
|
||||
</div>
|
||||
@@ -1626,6 +1559,7 @@ export function AssetRegistryPage() {
|
||||
</>
|
||||
) : null}
|
||||
</div>
|
||||
) : null}
|
||||
</div>
|
||||
</div>
|
||||
</ListWorkspace>
|
||||
|
||||
在新工单中引用
屏蔽一个用户