refactor(ui): [UI7-1] 为其余配置与边缘页面应用新版空壳布局
这个提交包含在:
+83
-81
@@ -10,6 +10,11 @@ import { PageHeader } from "../components/common/PageHeader";
|
||||
import { Button } from "../components/ui/button";
|
||||
import { Card } from "../components/ui/card";
|
||||
import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "../components/ui/table";
|
||||
import { toast } from "sonner";
|
||||
import { TableSkeleton } from "../components/common/TableSkeleton";
|
||||
import { StatusBadge } from "../components/common/StatusBadge";
|
||||
import { ConfirmDialog } from "../components/common/ConfirmDialog";
|
||||
import { Pagination } from "../components/common/Pagination";
|
||||
|
||||
const PAGE_SIZE = 20;
|
||||
|
||||
@@ -42,6 +47,7 @@ export function BudgetsPage() {
|
||||
const [warningThresholdPercent, setWarningThresholdPercent] = useState("80");
|
||||
const [formDepartmentId, setFormDepartmentId] = useState("");
|
||||
const [error, setError] = useState("");
|
||||
const [confirmArchiveId, setConfirmArchiveId] = useState<{ id: string; code: string } | null>(null);
|
||||
|
||||
const queryInput = useMemo<BudgetListQuery>(
|
||||
() => ({
|
||||
@@ -87,9 +93,11 @@ export function BudgetsPage() {
|
||||
setWarningThresholdPercent("80");
|
||||
setFormDepartmentId("");
|
||||
setError("");
|
||||
toast.success("预算池已创建");
|
||||
},
|
||||
onError: (mutationError) => {
|
||||
setError(resolveError(mutationError));
|
||||
toast.error(resolveError(mutationError));
|
||||
},
|
||||
});
|
||||
|
||||
@@ -98,9 +106,11 @@ export function BudgetsPage() {
|
||||
onSuccess: async () => {
|
||||
await queryClient.invalidateQueries({ queryKey: ["budgets"] });
|
||||
setError("");
|
||||
toast.success("已归档");
|
||||
},
|
||||
onError: (mutationError) => {
|
||||
setError(resolveError(mutationError));
|
||||
toast.error(resolveError(mutationError));
|
||||
},
|
||||
});
|
||||
|
||||
@@ -246,89 +256,81 @@ export function BudgetsPage() {
|
||||
<Card className="space-y-3 p-6">
|
||||
<h3 className="text-base font-semibold text-slate-900">预算池列表</h3>
|
||||
|
||||
<div className="rounded-lg border border-slate-200">
|
||||
<Table>
|
||||
<TableHeader>
|
||||
<TableRow>
|
||||
<TableHead>编码</TableHead>
|
||||
<TableHead>名称</TableHead>
|
||||
<TableHead>状态</TableHead>
|
||||
<TableHead>期间</TableHead>
|
||||
<TableHead>额度</TableHead>
|
||||
<TableHead>预警阈值</TableHead>
|
||||
<TableHead className="w-28">操作</TableHead>
|
||||
</TableRow>
|
||||
</TableHeader>
|
||||
<TableBody>
|
||||
{(budgetsQuery.data?.items ?? []).length ? (
|
||||
(budgetsQuery.data?.items ?? []).map((item) => (
|
||||
<TableRow key={item.id}>
|
||||
<TableCell className="font-medium text-slate-900">{item.code}</TableCell>
|
||||
<TableCell>{item.name}</TableCell>
|
||||
<TableCell>{item.status}</TableCell>
|
||||
<TableCell>
|
||||
{(item.periodStart ?? "-")} ~ {(item.periodEnd ?? "-")}
|
||||
</TableCell>
|
||||
<TableCell>
|
||||
{item.usedAmount} / {item.allocatedAmount}
|
||||
<div className="text-xs text-slate-500">剩余 {item.remainingAmount}</div>
|
||||
</TableCell>
|
||||
<TableCell>{item.warningThresholdPercent}%</TableCell>
|
||||
<TableCell>
|
||||
{canManageBudget && item.status !== "archived" ? (
|
||||
<Button
|
||||
size="sm"
|
||||
type="button"
|
||||
variant="secondary"
|
||||
disabled={archiveMutation.isPending}
|
||||
onClick={() => {
|
||||
if (!window.confirm(`确认归档预算池 ${item.code} ?`)) return;
|
||||
archiveMutation.mutate(item.id);
|
||||
}}
|
||||
>
|
||||
归档
|
||||
</Button>
|
||||
) : (
|
||||
<span className="text-xs text-slate-400">-</span>
|
||||
)}
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
))
|
||||
) : (
|
||||
<TableRow>
|
||||
<TableCell className="py-3 text-slate-500" colSpan={7}>
|
||||
暂无预算池
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
)}
|
||||
</TableBody>
|
||||
</Table>
|
||||
</div>
|
||||
{budgetsQuery.isLoading ? <TableSkeleton columns={7} rows={4} /> : null}
|
||||
|
||||
<div className="flex items-center justify-between text-sm">
|
||||
<span>
|
||||
第 {page} / {totalPages} 页
|
||||
</span>
|
||||
<div className="flex gap-2">
|
||||
<Button
|
||||
variant="secondary"
|
||||
type="button"
|
||||
disabled={page <= 1}
|
||||
onClick={() => setPage((current) => Math.max(1, current - 1))}
|
||||
>
|
||||
上一页
|
||||
</Button>
|
||||
<Button
|
||||
variant="secondary"
|
||||
type="button"
|
||||
disabled={page >= totalPages}
|
||||
onClick={() => setPage((current) => Math.min(totalPages, current + 1))}
|
||||
>
|
||||
下一页
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
{!budgetsQuery.isLoading ? (
|
||||
<>
|
||||
<div className="rounded-lg border border-slate-200">
|
||||
<Table>
|
||||
<TableHeader>
|
||||
<TableRow>
|
||||
<TableHead>编码</TableHead>
|
||||
<TableHead>名称</TableHead>
|
||||
<TableHead>状态</TableHead>
|
||||
<TableHead>期间</TableHead>
|
||||
<TableHead>额度</TableHead>
|
||||
<TableHead>预警阈值</TableHead>
|
||||
<TableHead className="w-28">操作</TableHead>
|
||||
</TableRow>
|
||||
</TableHeader>
|
||||
<TableBody>
|
||||
{(budgetsQuery.data?.items ?? []).length ? (
|
||||
(budgetsQuery.data?.items ?? []).map((item) => (
|
||||
<TableRow key={item.id}>
|
||||
<TableCell className="font-medium text-slate-900">{item.code}</TableCell>
|
||||
<TableCell>{item.name}</TableCell>
|
||||
<TableCell><StatusBadge status={item.status} /></TableCell>
|
||||
<TableCell>
|
||||
{(item.periodStart ?? "-")} ~ {(item.periodEnd ?? "-")}
|
||||
</TableCell>
|
||||
<TableCell>
|
||||
{item.usedAmount} / {item.allocatedAmount}
|
||||
<div className="text-xs text-slate-500">剩余 {item.remainingAmount}</div>
|
||||
</TableCell>
|
||||
<TableCell>{item.warningThresholdPercent}%</TableCell>
|
||||
<TableCell>
|
||||
{canManageBudget && item.status !== "archived" ? (
|
||||
<Button
|
||||
size="sm"
|
||||
type="button"
|
||||
variant="secondary"
|
||||
disabled={archiveMutation.isPending}
|
||||
onClick={() => setConfirmArchiveId({ id: item.id, code: item.code })}
|
||||
>
|
||||
归档
|
||||
</Button>
|
||||
) : (
|
||||
<span className="text-xs text-slate-400">-</span>
|
||||
)}
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
))
|
||||
) : (
|
||||
<TableRow>
|
||||
<TableCell className="py-3 text-slate-500" colSpan={7}>
|
||||
暂无预算池
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
)}
|
||||
</TableBody>
|
||||
</Table>
|
||||
</div>
|
||||
|
||||
<Pagination page={page} totalPages={totalPages} onPageChange={setPage} />
|
||||
</>
|
||||
) : null}
|
||||
</Card>
|
||||
|
||||
<ConfirmDialog
|
||||
open={Boolean(confirmArchiveId)}
|
||||
title="确认归档"
|
||||
description={`确认归档预算池 ${confirmArchiveId?.code ?? ""} ?`}
|
||||
variant="destructive"
|
||||
onConfirm={async () => {
|
||||
if (confirmArchiveId) archiveMutation.mutate(confirmArchiveId.id);
|
||||
}}
|
||||
onOpenChange={(open) => { if (!open) setConfirmArchiveId(null); }}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
在新工单中引用
屏蔽一个用户