refactor(ui): [UI8] 彻底移除遗留Card并补充下拉框中文化

这个提交包含在:
2026-03-06 10:09:18 +08:00
父节点 5a6a6f67c9
当前提交 3883f14a48
共修改 25 个文件,包含 87 行新增和 95 行删除
@@ -1,13 +1,12 @@
import { Skeleton } from "../ui/skeleton"; import { Skeleton } from "../ui/skeleton";
import { Card } from "../ui/card";
export function CardSkeleton({ lines = 6 }: { lines?: number }) { export function CardSkeleton({ lines = 6 }: { lines?: number }) {
return ( return (
<Card className="space-y-3 p-6"> <div className="card-std space-y-3 p-6">
<Skeleton className="h-5 w-48" /> <Skeleton className="h-5 w-48" />
{Array.from({ length: lines }, (_, i) => ( {Array.from({ length: lines }, (_, i) => (
<Skeleton key={i} className="h-4 w-full" /> <Skeleton key={i} className="h-4 w-full" />
))} ))}
</Card> </div>
); );
} }
@@ -1,5 +1,4 @@
import { Component, type ErrorInfo, type ReactNode } from "react"; import { Component, type ErrorInfo, type ReactNode } from "react";
import { Card } from "../ui/card";
type Props = { type Props = {
children: ReactNode; children: ReactNode;
@@ -32,7 +31,7 @@ export class ErrorBoundary extends Component<Props, State> {
} }
return ( return (
<Card className="mx-auto mt-12 max-w-lg space-y-4 p-6 text-center"> <div className="card-std mx-auto mt-12 max-w-lg space-y-4 p-6 text-center">
<h2 className="text-lg font-semibold text-zinc-900">页面渲染出错</h2> <h2 className="text-lg font-semibold text-zinc-900">页面渲染出错</h2>
<p className="text-sm text-zinc-600"> <p className="text-sm text-zinc-600">
{this.state.error?.message ?? "发生了未知错误"} {this.state.error?.message ?? "发生了未知错误"}
@@ -55,7 +54,7 @@ export class ErrorBoundary extends Component<Props, State> {
返回首页 返回首页
</button> </button>
</div> </div>
</Card> </div>
); );
} }
+1 -1
查看文件
@@ -42,7 +42,7 @@
/* Cards */ /* Cards */
.card-std { .card-std {
@apply bg-white p-6 rounded-xl shadow-sm ring-1 ring-zinc-900/5 transition-all; @apply bg-white rounded-xl shadow-sm ring-1 ring-zinc-900/5 transition-all;
} }
.card-title { .card-title {
+2 -2
查看文件
@@ -155,7 +155,7 @@ export function ApprovalRulesPage() {
</p> </p>
) : null} ) : null}
<div className="card-std space-y-4"> <div className="card-std p-6 space-y-4">
<h3 className="text-base font-semibold text-zinc-900">新建审批规则</h3> <h3 className="text-base font-semibold text-zinc-900">新建审批规则</h3>
<div className="grid gap-3 md:grid-cols-2 lg:grid-cols-3"> <div className="grid gap-3 md:grid-cols-2 lg:grid-cols-3">
@@ -280,7 +280,7 @@ export function ApprovalRulesPage() {
</div> </div>
</div> </div>
<div className="card-std space-y-4"> <div className="card-std p-6 space-y-4">
<h3 className="text-base font-semibold text-zinc-900">规则列表</h3> <h3 className="text-base font-semibold text-zinc-900">规则列表</h3>
{listQuery.isLoading ? <TableSkeleton columns={6} rows={5} /> : null} {listQuery.isLoading ? <TableSkeleton columns={6} rows={5} /> : null}
+1 -1
查看文件
@@ -135,7 +135,7 @@ export function AuditLogsPage() {
} }
/> />
<section className="card-std space-y-4"> <section className="card-std p-6 space-y-4">
<div className="grid gap-3 md:grid-cols-3"> <div className="grid gap-3 md:grid-cols-3">
<label className="text-sm text-zinc-700"> <label className="text-sm text-zinc-700">
Action Action
+6 -7
查看文件
@@ -8,7 +8,6 @@ import { useAuth } from "../auth/AuthContext";
import { hasPermission } from "../auth/permissions"; import { hasPermission } from "../auth/permissions";
import { PageHeader } from "../components/common/PageHeader"; import { PageHeader } from "../components/common/PageHeader";
import { Button } from "../components/ui/button"; import { Button } from "../components/ui/button";
import { Card } from "../components/ui/card";
import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "../components/ui/table"; import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "../components/ui/table";
import { toast } from "sonner"; import { toast } from "sonner";
import { TableSkeleton } from "../components/common/TableSkeleton"; import { TableSkeleton } from "../components/common/TableSkeleton";
@@ -118,7 +117,7 @@ export function BudgetsPage() {
return ( return (
<div className="space-y-4"> <div className="space-y-4">
<PageHeader title="预算管理" subtitle="你当前没有预算权限,请联系管理员开通。" /> <PageHeader title="预算管理" subtitle="你当前没有预算权限,请联系管理员开通。" />
<Card className="p-6 text-sm text-zinc-600">所需权限:budget.view 或 budget.manage</Card> <div className="card-std p-6 text-sm text-zinc-600">所需权限:budget.view 或 budget.manage</div>
</div> </div>
); );
} }
@@ -138,7 +137,7 @@ export function BudgetsPage() {
</p> </p>
) : null} ) : null}
<div className="card-std space-y-3"> <div className="card-std p-6 space-y-3">
<h3 className="text-base font-semibold text-zinc-900">筛选条件</h3> <h3 className="text-base font-semibold text-zinc-900">筛选条件</h3>
<div className="grid gap-3 md:grid-cols-3"> <div className="grid gap-3 md:grid-cols-3">
<select <select
@@ -150,8 +149,8 @@ export function BudgetsPage() {
}} }}
> >
<option value="">全部状态</option> <option value="">全部状态</option>
<option value="active">active</option> <option value="active">生效中</option>
<option value="archived">archived</option> <option value="archived">已归档</option>
</select> </select>
<select <select
@@ -181,7 +180,7 @@ export function BudgetsPage() {
</div> </div>
{canManageBudget ? ( {canManageBudget ? (
<div className="card-std space-y-3"> <div className="card-std p-6 space-y-3">
<h3 className="text-base font-semibold text-zinc-900">新建预算池</h3> <h3 className="text-base font-semibold text-zinc-900">新建预算池</h3>
<div className="grid gap-3 md:grid-cols-3"> <div className="grid gap-3 md:grid-cols-3">
<input <input
@@ -253,7 +252,7 @@ export function BudgetsPage() {
</div> </div>
) : null} ) : null}
<div className="card-std space-y-3"> <div className="card-std p-6 space-y-3">
<h3 className="text-base font-semibold text-zinc-900">预算池列表</h3> <h3 className="text-base font-semibold text-zinc-900">预算池列表</h3>
{budgetsQuery.isLoading ? <TableSkeleton columns={7} rows={4} /> : null} {budgetsQuery.isLoading ? <TableSkeleton columns={7} rows={4} /> : null}
+2 -3
查看文件
@@ -4,7 +4,6 @@ import { isApiError } from "../api/types";
import { mapApiErrorMessage } from "../api/errorMessages"; import { mapApiErrorMessage } from "../api/errorMessages";
import { useAuth } from "../auth/AuthContext"; import { useAuth } from "../auth/AuthContext";
import { Button } from "../components/ui/button"; import { Button } from "../components/ui/button";
import { Card } from "../components/ui/card";
import { Input } from "../components/ui/input"; import { Input } from "../components/ui/input";
import { Label } from "../components/ui/label"; import { Label } from "../components/ui/label";
@@ -43,7 +42,7 @@ export function ChangePasswordPage() {
} }
return ( return (
<Card className="space-y-6 border-primary-100 p-6"> <div className="card-std space-y-6 border-primary-100 p-6">
<div className="mb-6 space-y-2"> <div className="mb-6 space-y-2">
<p className="text-xs font-semibold uppercase tracking-wide text-primary">hyb-oa</p> <p className="text-xs font-semibold uppercase tracking-wide text-primary">hyb-oa</p>
<h1 className="text-2xl font-bold text-zinc-900">修改密码</h1> <h1 className="text-2xl font-bold text-zinc-900">修改密码</h1>
@@ -109,7 +108,7 @@ export function ChangePasswordPage() {
{submitting ? "提交中..." : "确认修改"} {submitting ? "提交中..." : "确认修改"}
</Button> </Button>
</form> </form>
</Card> </div>
); );
} }
+11 -12
查看文件
@@ -16,7 +16,6 @@ import { useAuth } from "../auth/AuthContext";
import { hasPermission } from "../auth/permissions"; import { hasPermission } from "../auth/permissions";
import { PageHeader } from "../components/common/PageHeader"; import { PageHeader } from "../components/common/PageHeader";
import { Button } from "../components/ui/button"; import { Button } from "../components/ui/button";
import { Card } from "../components/ui/card";
import { downloadBlob } from "../lib/download"; import { downloadBlob } from "../lib/download";
function currentDateString() { function currentDateString() {
@@ -116,10 +115,10 @@ export function DashboardPage() {
return ( return (
<div className="space-y-4"> <div className="space-y-4">
<PageHeader title="仪表盘" subtitle="你当前没有看板权限,请联系管理员开通。" /> <PageHeader title="仪表盘" subtitle="你当前没有看板权限,请联系管理员开通。" />
<Card className="space-y-2 p-6 text-sm"> <div className="card-std space-y-2 p-6 text-sm">
<p className="text-zinc-700">当前用户:{me?.name ?? "-"}</p> <p className="text-zinc-700">当前用户:{me?.name ?? "-"}</p>
<p className="text-zinc-500">所需权限:report.dashboard.view</p> <p className="text-zinc-500">所需权限:report.dashboard.view</p>
</Card> </div>
</div> </div>
); );
} }
@@ -163,7 +162,7 @@ export function DashboardPage() {
</p> </p>
) : null} ) : null}
<Card className="space-y-4 p-6"> <div className="card-std space-y-4 p-6">
<div className="grid gap-3 md:grid-cols-4"> <div className="grid gap-3 md:grid-cols-4">
<div className="space-y-1"> <div className="space-y-1">
<label className="text-xs text-zinc-600" htmlFor="dashboard-date-from"> <label className="text-xs text-zinc-600" htmlFor="dashboard-date-from">
@@ -226,28 +225,28 @@ export function DashboardPage() {
</Button> </Button>
</div> </div>
</div> </div>
</Card> </div>
<div className="grid gap-4 md:grid-cols-4"> <div className="grid gap-4 md:grid-cols-4">
<div className="card-std space-y-1"> <div className="card-std p-4 space-y-1">
<p className="text-xs text-zinc-500">报销单总数</p> <p className="text-xs text-zinc-500">报销单总数</p>
<p className="mt-1 text-2xl font-semibold text-zinc-900"> <p className="mt-1 text-2xl font-semibold text-zinc-900">
{loading ? "..." : overviewQuery.data?.totalCount ?? 0} {loading ? "..." : overviewQuery.data?.totalCount ?? 0}
</p> </p>
</div> </div>
<div className="card-std space-y-1"> <div className="card-std p-4 space-y-1">
<p className="text-xs text-zinc-500">报销总金额</p> <p className="text-xs text-zinc-500">报销总金额</p>
<p className="mt-1 text-2xl font-semibold text-zinc-900"> <p className="mt-1 text-2xl font-semibold text-zinc-900">
{loading ? "..." : toCurrency(overviewQuery.data?.totalAmount)} {loading ? "..." : toCurrency(overviewQuery.data?.totalAmount)}
</p> </p>
</div> </div>
<div className="card-std space-y-1"> <div className="card-std p-4 space-y-1">
<p className="text-xs text-zinc-500">已付款金额</p> <p className="text-xs text-zinc-500">已付款金额</p>
<p className="mt-1 text-2xl font-semibold text-zinc-900"> <p className="mt-1 text-2xl font-semibold text-zinc-900">
{loading ? "..." : toCurrency(overviewQuery.data?.paidAmount)} {loading ? "..." : toCurrency(overviewQuery.data?.paidAmount)}
</p> </p>
</div> </div>
<div className="card-std space-y-1"> <div className="card-std p-4 space-y-1">
<p className="text-xs text-zinc-500">审批中数量</p> <p className="text-xs text-zinc-500">审批中数量</p>
<p className="mt-1 text-2xl font-semibold text-zinc-900"> <p className="mt-1 text-2xl font-semibold text-zinc-900">
{loading ? "..." : overviewQuery.data?.reviewingCount ?? 0} {loading ? "..." : overviewQuery.data?.reviewingCount ?? 0}
@@ -256,7 +255,7 @@ export function DashboardPage() {
</div> </div>
<div className="grid gap-4 lg:grid-cols-2"> <div className="grid gap-4 lg:grid-cols-2">
<div className="card-std space-y-4"> <div className="card-std p-6 space-y-4">
<h3 className="text-base font-semibold text-zinc-900">费用趋势</h3> <h3 className="text-base font-semibold text-zinc-900">费用趋势</h3>
{trendPoints.length ? ( {trendPoints.length ? (
<div className="space-y-2"> <div className="space-y-2">
@@ -283,7 +282,7 @@ export function DashboardPage() {
)} )}
</div> </div>
<div className="card-std space-y-4"> <div className="card-std p-6 space-y-4">
<h3 className="text-base font-semibold text-zinc-900">费用分类 TopN</h3> <h3 className="text-base font-semibold text-zinc-900">费用分类 TopN</h3>
{topCategories.length ? ( {topCategories.length ? (
<div className="space-y-2"> <div className="space-y-2">
@@ -313,7 +312,7 @@ export function DashboardPage() {
</div> </div>
</div> </div>
<div className="card-std space-y-2 text-sm"> <div className="card-std p-6 space-y-2 text-sm">
<h3 className="text-base font-semibold text-zinc-900">审批效率</h3> <h3 className="text-base font-semibold text-zinc-900">审批效率</h3>
<p className="text-zinc-700"> <p className="text-zinc-700">
已统计通过单据: 已统计通过单据:
+11 -11
查看文件
@@ -9,7 +9,6 @@ import { PageHeader } from "../components/common/PageHeader";
import { TableSkeleton } from "../components/common/TableSkeleton"; import { TableSkeleton } from "../components/common/TableSkeleton";
import { StatusBadge } from "../components/common/StatusBadge"; import { StatusBadge } from "../components/common/StatusBadge";
import { Button } from "../components/ui/button"; import { Button } from "../components/ui/button";
import { Input } from "../components/ui/input";
import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "../components/ui/table"; import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "../components/ui/table";
export function DelegationsPage() { export function DelegationsPage() {
@@ -79,7 +78,7 @@ export function DelegationsPage() {
<p className="rounded-lg border border-red-200 bg-red-50 px-3 py-2 text-sm text-red-600">{error}</p> <p className="rounded-lg border border-red-200 bg-red-50 px-3 py-2 text-sm text-red-600">{error}</p>
) : null} ) : null}
<div className="card-std space-y-4"> <div className="card-std p-6 space-y-4">
<h3 className="text-base font-semibold text-zinc-900">创建委派</h3> <h3 className="text-base font-semibold text-zinc-900">创建委派</h3>
<div className="grid gap-3 md:grid-cols-2"> <div className="grid gap-3 md:grid-cols-2">
@@ -111,14 +110,15 @@ export function DelegationsPage() {
onChange={(event) => setScope(event.target.value as "all" | "reimbursement_submit")} onChange={(event) => setScope(event.target.value as "all" | "reimbursement_submit")}
value={scope} value={scope}
> >
<option value="all">all</option> <option value="all">任意单据(全权代理)</option>
<option value="reimbursement_submit">reimbursement_submit</option> <option value="reimbursement_submit">仅限提交报销单</option>
</select> </select>
</label> </label>
<label className="space-y-1 text-sm md:col-span-2"> <label className="space-y-1 text-sm md:col-span-2">
<span className="font-medium text-zinc-700">有效期结束(可选)</span> <span className="font-medium text-zinc-700">有效期结束(可选)</span>
<Input <input
className="input-std"
data-testid="delegations-create-valid-to" data-testid="delegations-create-valid-to"
onChange={(event) => setValidTo(event.target.value)} onChange={(event) => setValidTo(event.target.value)}
type="datetime-local" type="datetime-local"
@@ -139,7 +139,7 @@ export function DelegationsPage() {
</div> </div>
</div> </div>
<div className="card-std space-y-4"> <div className="card-std p-6 space-y-4">
<div className="flex flex-wrap items-center justify-between gap-2"> <div className="flex flex-wrap items-center justify-between gap-2">
<h3 className="text-base font-semibold text-zinc-900">我的委派</h3> <h3 className="text-base font-semibold text-zinc-900">我的委派</h3>
<select <select
@@ -150,11 +150,11 @@ export function DelegationsPage() {
} }
value={status} value={status}
> >
<option value="active">active</option> <option value="active">生效中</option>
<option value="scheduled">scheduled</option> <option value="scheduled">未开始</option>
<option value="expired">expired</option> <option value="expired">已失效</option>
<option value="revoked">revoked</option> <option value="revoked">已撤销</option>
<option value="all">all</option> <option value="all">全状态</option>
</select> </select>
</div> </div>
+2 -2
查看文件
@@ -101,7 +101,7 @@ export function DepartmentsPage() {
</p> </p>
) : null} ) : null}
<div className="card-std space-y-4"> <div className="card-std p-6 space-y-4">
<div className="flex items-center justify-between gap-3"> <div className="flex items-center justify-between gap-3">
<h3 className="text-base font-semibold text-zinc-900">新建部门</h3> <h3 className="text-base font-semibold text-zinc-900">新建部门</h3>
<Button <Button
@@ -163,7 +163,7 @@ export function DepartmentsPage() {
</div> </div>
</div> </div>
<div className="card-std space-y-4"> <div className="card-std p-6 space-y-4">
<h3 className="text-base font-semibold text-zinc-900">部门列表</h3> <h3 className="text-base font-semibold text-zinc-900">部门列表</h3>
{listQuery.isLoading ? <TableSkeleton columns={4} rows={4} /> : null} {listQuery.isLoading ? <TableSkeleton columns={4} rows={4} /> : null}
+1 -1
查看文件
@@ -138,7 +138,7 @@ export function FeatureFlagsPage() {
<p className="rounded-lg border border-red-200 bg-red-50 px-3 py-2 text-sm text-red-600">{error}</p> <p className="rounded-lg border border-red-200 bg-red-50 px-3 py-2 text-sm text-red-600">{error}</p>
) : null} ) : null}
<div className="card-std space-y-4"> <div className="card-std p-6 space-y-4">
<h3 className="text-base font-semibold text-zinc-900">开关列表</h3> <h3 className="text-base font-semibold text-zinc-900">开关列表</h3>
{flagsQuery.isLoading ? <TableSkeleton columns={6} rows={2} /> : null} {flagsQuery.isLoading ? <TableSkeleton columns={6} rows={2} /> : null}
+2 -3
查看文件
@@ -4,7 +4,6 @@ import { mapApiErrorMessage } from "../api/errorMessages";
import { isApiError } from "../api/types"; import { isApiError } from "../api/types";
import { useAuth } from "../auth/AuthContext"; import { useAuth } from "../auth/AuthContext";
import { Button } from "../components/ui/button"; import { Button } from "../components/ui/button";
import { Card } from "../components/ui/card";
import { Input } from "../components/ui/input"; import { Input } from "../components/ui/input";
import { Label } from "../components/ui/label"; import { Label } from "../components/ui/label";
@@ -59,7 +58,7 @@ export function LoginPage() {
} }
return ( return (
<Card className="space-y-6 border-primary-100 p-6"> <div className="card-std space-y-6 border-primary-100 p-6">
<div className="mb-6"> <div className="mb-6">
<p className="text-xs font-semibold uppercase tracking-wide text-primary">hyb-oa</p> <p className="text-xs font-semibold uppercase tracking-wide text-primary">hyb-oa</p>
<h1 className="mt-2 text-2xl font-bold text-zinc-900">登录工作台</h1> <h1 className="mt-2 text-2xl font-bold text-zinc-900">登录工作台</h1>
@@ -106,6 +105,6 @@ export function LoginPage() {
{submitting ? "登录中..." : "登录"} {submitting ? "登录中..." : "登录"}
</Button> </Button>
</form> </form>
</Card> </div>
); );
} }
+4 -5
查看文件
@@ -15,7 +15,6 @@ import {
import { PageHeader } from "../components/common/PageHeader"; import { PageHeader } from "../components/common/PageHeader";
import { Button } from "../components/ui/button"; import { Button } from "../components/ui/button";
import { Spinner } from "../components/ui/spinner"; import { Spinner } from "../components/ui/spinner";
import { Card } from "../components/ui/card";
import { Textarea } from "../components/ui/textarea"; import { Textarea } from "../components/ui/textarea";
type MobileAction = "approve" | "reject" | "return" | "transfer"; type MobileAction = "approve" | "reject" | "return" | "transfer";
@@ -107,7 +106,7 @@ export function MobileApprovalsPage() {
<p className="rounded-lg border border-red-200 bg-red-50 px-3 py-2 text-sm text-red-600">{error}</p> <p className="rounded-lg border border-red-200 bg-red-50 px-3 py-2 text-sm text-red-600">{error}</p>
) : null} ) : null}
<Card className="space-y-3 p-4" data-testid="mobile-approvals-list"> <div className="card-std space-y-3 p-4" data-testid="mobile-approvals-list">
<h3 className="text-base font-semibold text-zinc-900">待办任务</h3> <h3 className="text-base font-semibold text-zinc-900">待办任务</h3>
{tasksQuery.isLoading ? <div className="flex items-center gap-2 text-sm text-zinc-500"><Spinner size="sm" />加载中...</div> : null} {tasksQuery.isLoading ? <div className="flex items-center gap-2 text-sm text-zinc-500"><Spinner size="sm" />加载中...</div> : null}
@@ -135,9 +134,9 @@ export function MobileApprovalsPage() {
</button> </button>
))} ))}
</div> </div>
</Card> </div>
<Card className="space-y-3 p-4" data-testid="mobile-approvals-detail"> <div className="card-std space-y-3 p-4" data-testid="mobile-approvals-detail">
<h3 className="text-base font-semibold text-zinc-900">任务详情与动作</h3> <h3 className="text-base font-semibold text-zinc-900">任务详情与动作</h3>
{!selectedTaskId ? <p className="text-sm text-zinc-500">请先选择任务。</p> : null} {!selectedTaskId ? <p className="text-sm text-zinc-500">请先选择任务。</p> : null}
@@ -229,7 +228,7 @@ export function MobileApprovalsPage() {
</div> </div>
</> </>
)} )}
</Card> </div>
</div> </div>
); );
} }
+4 -5
查看文件
@@ -7,7 +7,6 @@ import { isApiError } from "../api/types";
import { PageHeader } from "../components/common/PageHeader"; import { PageHeader } from "../components/common/PageHeader";
import { Button } from "../components/ui/button"; import { Button } from "../components/ui/button";
import { Spinner } from "../components/ui/spinner"; import { Spinner } from "../components/ui/spinner";
import { Card } from "../components/ui/card";
import { import {
enqueueInvoiceUpload, enqueueInvoiceUpload,
listQueuedInvoiceUploads, listQueuedInvoiceUploads,
@@ -140,7 +139,7 @@ export function MobileCapturePage() {
<p className="rounded-lg border border-emerald-200 bg-emerald-50 px-3 py-2 text-sm text-emerald-700">{message}</p> <p className="rounded-lg border border-emerald-200 bg-emerald-50 px-3 py-2 text-sm text-emerald-700">{message}</p>
) : null} ) : null}
<Card className="space-y-3 p-4"> <div className="card-std space-y-3 p-4">
<div className="flex items-center justify-between"> <div className="flex items-center justify-between">
<h3 className="text-base font-semibold text-zinc-900">拍照/选择文件</h3> <h3 className="text-base font-semibold text-zinc-900">拍照/选择文件</h3>
<span className={isOnline ? "text-emerald-600" : "text-orange-600"}> <span className={isOnline ? "text-emerald-600" : "text-orange-600"}>
@@ -212,9 +211,9 @@ export function MobileCapturePage() {
{syncMutation.isPending ? "同步中..." : "同步队列"} {syncMutation.isPending ? "同步中..." : "同步队列"}
</Button> </Button>
</div> </div>
</Card> </div>
<Card className="space-y-3 p-4" data-testid="mobile-capture-queue"> <div className="card-std space-y-3 p-4" data-testid="mobile-capture-queue">
<div className="flex items-center justify-between"> <div className="flex items-center justify-between">
<h3 className="text-base font-semibold text-zinc-900">离线上传队列</h3> <h3 className="text-base font-semibold text-zinc-900">离线上传队列</h3>
<span className="text-sm text-zinc-500">{queuedCountLabel}</span> <span className="text-sm text-zinc-500">{queuedCountLabel}</span>
@@ -246,7 +245,7 @@ export function MobileCapturePage() {
</div> </div>
</div> </div>
))} ))}
</Card> </div>
</div> </div>
); );
} }
+1 -1
查看文件
@@ -79,7 +79,7 @@ export function NotificationsPage() {
} }
/> />
<div className="card-std space-y-4"> <div className="card-std p-6 space-y-4">
<div className="flex flex-col gap-3 md:flex-row md:items-center md:justify-between"> <div className="flex flex-col gap-3 md:flex-row md:items-center md:justify-between">
<Tabs <Tabs
data-testid="notifications-filter-tabs" data-testid="notifications-filter-tabs"
+12 -13
查看文件
@@ -21,7 +21,6 @@ import { CardSkeleton } from "../components/common/CardSkeleton";
import { ConfirmDialog } from "../components/common/ConfirmDialog"; import { ConfirmDialog } from "../components/common/ConfirmDialog";
import { PageHeader } from "../components/common/PageHeader"; import { PageHeader } from "../components/common/PageHeader";
import { Button } from "../components/ui/button"; import { Button } from "../components/ui/button";
import { Card } from "../components/ui/card";
import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "../components/ui/table"; import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "../components/ui/table";
import { ReimbursementActions, type PayPayload } from "./reimbursement-detail/ReimbursementActions"; import { ReimbursementActions, type PayPayload } from "./reimbursement-detail/ReimbursementActions";
import { import {
@@ -195,9 +194,9 @@ export function ReimbursementDetailPage() {
if (!reimbursementId) { if (!reimbursementId) {
return ( return (
<Card className="p-6"> <div className="card-std p-6">
<p className="text-sm text-red-600">缺少 reimbursementId 参数。</p> <p className="text-sm text-red-600">缺少 reimbursementId 参数。</p>
</Card> </div>
); );
} }
@@ -215,12 +214,12 @@ export function ReimbursementDetailPage() {
: "加载报销详情失败"; : "加载报销详情失败";
return ( return (
<Card className="space-y-3 p-6"> <div className="card-std space-y-3 p-6">
<p className="text-sm text-red-600">{message}</p> <p className="text-sm text-red-600">{message}</p>
<Button asChild variant="secondary"> <Button asChild variant="secondary">
<Link to="/reimbursements">返回报销列表</Link> <Link to="/reimbursements">返回报销列表</Link>
</Button> </Button>
</Card> </div>
); );
} }
@@ -281,17 +280,17 @@ export function ReimbursementDetailPage() {
} }
/> />
<Card className="space-y-2 p-6 text-sm" data-testid="reimbursement-summary"> <div className="card-std space-y-2 p-6 text-sm" data-testid="reimbursement-summary">
<div>报销ID:{detail.id}</div> <div>报销ID:{detail.id}</div>
<div>报销日期:{detail.reimbursementDate ?? "-"}</div> <div>报销日期:{detail.reimbursementDate ?? "-"}</div>
<div>金额:{detail.totalAmount ?? "-"}</div> <div>金额:{detail.totalAmount ?? "-"}</div>
<div>提交人:{detail.submitter?.name ?? "-"}</div> <div>提交人:{detail.submitter?.name ?? "-"}</div>
<div>报销人:{detail.owner?.name ?? "-"}</div> <div>报销人:{detail.owner?.name ?? "-"}</div>
<div>说明:{detail.description || "-"}</div> <div>说明:{detail.description || "-"}</div>
</Card> </div>
{budgetHint ? ( {budgetHint ? (
<Card className="space-y-2 p-6 text-sm" data-testid="reimbursement-budget-hint"> <div className="card-std space-y-2 p-6 text-sm" data-testid="reimbursement-budget-hint">
<h3 className="text-base font-semibold text-zinc-900">预算提示</h3> <h3 className="text-base font-semibold text-zinc-900">预算提示</h3>
<div>预算池:{budgetHint.budgetCode} / {budgetHint.budgetName}</div> <div>预算池:{budgetHint.budgetCode} / {budgetHint.budgetName}</div>
<div> <div>
@@ -310,10 +309,10 @@ export function ReimbursementDetailPage() {
) : ( ) : (
<p className="text-zinc-500">当前预算充足,可继续提交。</p> <p className="text-zinc-500">当前预算充足,可继续提交。</p>
)} )}
</Card> </div>
) : null} ) : null}
<Card className="space-y-4 p-6" data-testid="reimbursement-items"> <div className="card-std space-y-4 p-6" data-testid="reimbursement-items">
<div className="flex flex-col gap-3 md:flex-row md:items-center md:justify-between"> <div className="flex flex-col gap-3 md:flex-row md:items-center md:justify-between">
<div className="space-y-1"> <div className="space-y-1">
<h3 className="text-base font-semibold text-zinc-900">报销明细</h3> <h3 className="text-base font-semibold text-zinc-900">报销明细</h3>
@@ -418,7 +417,7 @@ export function ReimbursementDetailPage() {
isPending={itemSaveMutation.isPending} isPending={itemSaveMutation.isPending}
/> />
) : null} ) : null}
</Card> </div>
<ReimbursementActions <ReimbursementActions
status={detail.status} status={detail.status}
@@ -430,7 +429,7 @@ export function ReimbursementDetailPage() {
onPay={(payload) => mutation.mutate({ action: "pay", payload })} onPay={(payload) => mutation.mutate({ action: "pay", payload })}
/> />
<Card className="space-y-2 p-6" data-testid="reimbursement-invoices"> <div className="card-std space-y-2 p-6" data-testid="reimbursement-invoices">
<h3 className="text-base font-semibold text-zinc-900">关联票据</h3> <h3 className="text-base font-semibold text-zinc-900">关联票据</h3>
{(detail.invoices ?? []).length ? ( {(detail.invoices ?? []).length ? (
<div className="space-y-2 text-sm"> <div className="space-y-2 text-sm">
@@ -446,7 +445,7 @@ export function ReimbursementDetailPage() {
) : ( ) : (
<p className="text-sm text-zinc-500">暂无关联票据</p> <p className="text-sm text-zinc-500">暂无关联票据</p>
)} )}
</Card> </div>
<ConfirmDialog <ConfirmDialog
open={Boolean(confirmDeleteItemId)} open={Boolean(confirmDeleteItemId)}
+2 -3
查看文件
@@ -16,7 +16,6 @@ import {
} from "../api/users"; } from "../api/users";
import { PageHeader } from "../components/common/PageHeader"; import { PageHeader } from "../components/common/PageHeader";
import { Button } from "../components/ui/button"; import { Button } from "../components/ui/button";
import { Card } from "../components/ui/card";
import { Input } from "../components/ui/input"; import { Input } from "../components/ui/input";
import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "../components/ui/table"; import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "../components/ui/table";
import { toast } from "sonner"; import { toast } from "sonner";
@@ -177,7 +176,7 @@ export function UserManagementPage() {
</p> </p>
) : null} ) : null}
<Card className="space-y-4 p-6"> <div className="card-std space-y-4 p-6">
<div className="flex flex-wrap items-center gap-2"> <div className="flex flex-wrap items-center gap-2">
<Input <Input
data-testid="users-search" data-testid="users-search"
@@ -298,7 +297,7 @@ export function UserManagementPage() {
onPageChange={setPage} onPageChange={setPage}
testIdPrefix="users" testIdPrefix="users"
/> />
</Card> </div>
<UserRoleDialog <UserRoleDialog
open={Boolean(roleDialogUser)} open={Boolean(roleDialogUser)}
+10 -11
查看文件
@@ -18,7 +18,6 @@ import { CardSkeleton } from "../components/common/CardSkeleton";
import { PageHeader } from "../components/common/PageHeader"; import { PageHeader } from "../components/common/PageHeader";
import { StatusBadge } from "../components/common/StatusBadge"; import { StatusBadge } from "../components/common/StatusBadge";
import { Button } from "../components/ui/button"; import { Button } from "../components/ui/button";
import { Card } from "../components/ui/card";
import { Textarea } from "../components/ui/textarea"; import { Textarea } from "../components/ui/textarea";
export function WorkflowTaskDetailPage() { export function WorkflowTaskDetailPage() {
@@ -98,9 +97,9 @@ export function WorkflowTaskDetailPage() {
if (!taskId) { if (!taskId) {
return ( return (
<Card className="p-6"> <div className="card-std p-6">
<p className="text-sm text-red-600">缺少 taskId 参数。</p> <p className="text-sm text-red-600">缺少 taskId 参数。</p>
</Card> </div>
); );
} }
@@ -120,7 +119,7 @@ export function WorkflowTaskDetailPage() {
: "加载失败,请返回列表重试"; : "加载失败,请返回列表重试";
return ( return (
<Card className="space-y-3 p-6"> <div className="card-std space-y-3 p-6">
<p className="text-sm text-red-600">{message}</p> <p className="text-sm text-red-600">{message}</p>
<Button <Button
variant="secondary" variant="secondary"
@@ -129,7 +128,7 @@ export function WorkflowTaskDetailPage() {
> >
返回待办列表 返回待办列表
</Button> </Button>
</Card> </div>
); );
} }
@@ -147,16 +146,16 @@ export function WorkflowTaskDetailPage() {
} }
/> />
<Card className="space-y-2 p-6 text-sm" data-testid="workflow-task-summary"> <div className="card-std space-y-2 p-6 text-sm" data-testid="workflow-task-summary">
<div>状态:<StatusBadge status={detail.task.status} /></div> <div>状态:<StatusBadge status={detail.task.status} /></div>
<div>实例:{detail.workflowInstance?.id ?? "-"}</div> <div>实例:{detail.workflowInstance?.id ?? "-"}</div>
<div>报销单号:{detail.reimbursement?.formNumber ?? "-"}</div> <div>报销单号:{detail.reimbursement?.formNumber ?? "-"}</div>
<div>报销金额:{detail.reimbursement?.totalAmount ?? "-"}</div> <div>报销金额:{detail.reimbursement?.totalAmount ?? "-"}</div>
<div>提交人:{detail.reimbursement?.submitter?.name ?? "-"}</div> <div>提交人:{detail.reimbursement?.submitter?.name ?? "-"}</div>
<div>报销人:{detail.reimbursement?.owner?.name ?? "-"}</div> <div>报销人:{detail.reimbursement?.owner?.name ?? "-"}</div>
</Card> </div>
<Card className="space-y-4 p-6"> <div className="card-std space-y-4 p-6">
<h3 className="text-base font-semibold text-zinc-900">审批动作</h3> <h3 className="text-base font-semibold text-zinc-900">审批动作</h3>
{actionError ? ( {actionError ? (
@@ -253,9 +252,9 @@ export function WorkflowTaskDetailPage() {
提交转审 提交转审
</Button> </Button>
</div> </div>
</Card> </div>
<Card className="space-y-3 p-6"> <div className="card-std space-y-3 p-6">
<h3 className="text-base font-semibold text-zinc-900">处理历史</h3> <h3 className="text-base font-semibold text-zinc-900">处理历史</h3>
<div className="space-y-2"> <div className="space-y-2">
{detail.history.length ? ( {detail.history.length ? (
@@ -272,7 +271,7 @@ export function WorkflowTaskDetailPage() {
<p className="text-sm text-zinc-500">暂无历史记录</p> <p className="text-sm text-zinc-500">暂无历史记录</p>
)} )}
</div> </div>
</Card> </div>
</div> </div>
); );
} }
+1 -1
查看文件
@@ -37,7 +37,7 @@ export function WorkflowTasksPage() {
<div className="space-y-6"> <div className="space-y-6">
<PageHeader title="待我处理" subtitle="审批待办列表(支持筛选与详情动作联动)。" /> <PageHeader title="待我处理" subtitle="审批待办列表(支持筛选与详情动作联动)。" />
<div className="card-std space-y-4"> <div className="card-std p-6 space-y-4">
<Tabs <Tabs
data-testid="workflow-tasks-status-tabs" data-testid="workflow-tasks-status-tabs"
value={status} value={status}
@@ -23,7 +23,7 @@ export function InvoiceBindCard({
setSelectedReimbursementId, setSelectedReimbursementId,
}: InvoiceBindCardProps) { }: InvoiceBindCardProps) {
return ( return (
<div className="card-std space-y-4"> <div className="card-std p-6 space-y-4">
<h3 className="card-title">绑定报销</h3> <h3 className="card-title">绑定报销</h3>
<select <select
className="select-std" className="select-std"
@@ -69,7 +69,7 @@ export function InvoiceDetailPanel({
}: InvoiceDetailPanelProps) { }: InvoiceDetailPanelProps) {
return ( return (
<> <>
<div className="card-std space-y-4" data-testid="invoice-detail-panel"> <div className="card-std p-6 space-y-4" data-testid="invoice-detail-panel">
<h3 className="card-title">详情轮询</h3> <h3 className="card-title">详情轮询</h3>
{detail ? ( {detail ? (
<> <>
@@ -32,7 +32,7 @@ export function InvoiceListCard({
signedUrlMutation, signedUrlMutation,
}: InvoiceListCardProps) { }: InvoiceListCardProps) {
return ( return (
<div className="card-std space-y-4"> <div className="card-std p-6 space-y-4">
<div className="flex items-center justify-between"> <div className="flex items-center justify-between">
<h3 className="card-title">筛选与列表</h3> <h3 className="card-title">筛选与列表</h3>
<div className="flex items-center gap-2"> <div className="flex items-center gap-2">
@@ -22,7 +22,7 @@ export function InvoiceUploadCard({
setUploadOwnerEmployeeId, setUploadOwnerEmployeeId,
}: InvoiceUploadCardProps) { }: InvoiceUploadCardProps) {
return ( return (
<div className="card-std space-y-4"> <div className="card-std p-6 space-y-4">
<h3 className="card-title">上传票据(VAT OCR)</h3> <h3 className="card-title">上传票据(VAT OCR)</h3>
{showProxyOwnerSelector ? ( {showProxyOwnerSelector ? (
<select <select
@@ -1,7 +1,6 @@
import { useState } from "react"; import { useState } from "react";
import type { ReimbursementStatus } from "../../api/reimbursements"; import type { ReimbursementStatus } from "../../api/reimbursements";
import { Button } from "../../components/ui/button"; import { Button } from "../../components/ui/button";
import { Card } from "../../components/ui/card";
import { Input } from "../../components/ui/input"; import { Input } from "../../components/ui/input";
import { Textarea } from "../../components/ui/textarea"; import { Textarea } from "../../components/ui/textarea";
@@ -38,7 +37,7 @@ export function ReimbursementActions({
const [paymentChannel, setPaymentChannel] = useState<PaymentChannel>("bank"); const [paymentChannel, setPaymentChannel] = useState<PaymentChannel>("bank");
return ( return (
<Card className="space-y-4 p-6"> <div className="card-std space-y-4 p-6">
<h3 className="text-base font-semibold text-zinc-900">可执行动作</h3> <h3 className="text-base font-semibold text-zinc-900">可执行动作</h3>
{actionError ? ( {actionError ? (
@@ -126,6 +125,6 @@ export function ReimbursementActions({
</Button> </Button>
</div> </div>
) : null} ) : null}
</Card> </div>
); );
} }
@@ -0,0 +1,4 @@
ID,Title,Description,Acceptance,Test_Method,Tools,Dev_Status,Review1_Status,Regression_Status,Files,Dependencies,Notes
UI8-0,UI翻新阶段8验收建档,创建并校验本轮遗留Card组件与边缘文案中文化Issue CSV,CSV校验通过,python3 /home/shunli/.codex/skills/plan/scripts/validate_issues_csv.py issues/2026-03-06_10-09-00-ui-refresh-stage8.csv,manual,TODO,TODO,TODO,issues/2026-03-06_10-09-00-ui-refresh-stage8.csv,[],none
UI8-1,清理残余的Card与混用UI,修复Dashboard及15个遗留页面中的shadcn Card引入混用问题及样式调整,全站所有遗留被替换为 card-std 且Typecheck通过,cd client && npm run typecheck && npm run build,manual,DONE,DONE,DONE,client/src/pages/**,"[""UI8-0""]",确保剥离 p-6 后的样式无异常
UI8-2,下拉框中文化,修复Budgets和Delegations等的选项文案中文化,文案完成对应本地化不影响value传入后端,手动校验下拉框显示,manual,DONE,DONE,DONE,client/src/pages/BudgetsPage.tsx|client/src/pages/DelegationsPage.tsx,"[""UI8-0""]",无
1 ID Title Description Acceptance Test_Method Tools Dev_Status Review1_Status Regression_Status Files Dependencies Notes
2 UI8-0 UI翻新阶段8验收建档 创建并校验本轮遗留Card组件与边缘文案中文化Issue CSV CSV校验通过 python3 /home/shunli/.codex/skills/plan/scripts/validate_issues_csv.py issues/2026-03-06_10-09-00-ui-refresh-stage8.csv manual TODO TODO TODO issues/2026-03-06_10-09-00-ui-refresh-stage8.csv [] none
3 UI8-1 清理残余的Card与混用UI 修复Dashboard及15个遗留页面中的shadcn Card引入混用问题及样式调整 全站所有遗留被替换为 card-std 且Typecheck通过 cd client && npm run typecheck && npm run build manual DONE DONE DONE client/src/pages/** ["UI8-0"] 确保剥离 p-6 后的样式无异常
4 UI8-2 下拉框中文化 修复Budgets和Delegations等的选项文案中文化 文案完成对应本地化不影响value传入后端 手动校验下拉框显示 manual DONE DONE DONE client/src/pages/BudgetsPage.tsx|client/src/pages/DelegationsPage.tsx ["UI8-0"] 无