fix(ui): [UI7-2] 统一全局 Design Tokens,移除冗余字体,修复异常色值引用
这个提交包含在:
+15
-12
@@ -1,14 +1,17 @@
|
||||
<!doctype html>
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<link href="https://fonts.googleapis.com/css2?family=Public+Sans:wght@400;500;700;900&display=swap" rel="stylesheet" />
|
||||
<link href="https://fonts.googleapis.com/css2?family=Material+Symbols+Outlined:wght,FILL@300,0&display=swap" rel="stylesheet" />
|
||||
<title>海音达工作台</title>
|
||||
</head>
|
||||
<body>
|
||||
<div id="root"></div>
|
||||
<script type="module" src="/src/main.tsx"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<link href="https://fonts.googleapis.com/css2?family=Material+Symbols+Outlined:wght,FILL@300,0&display=swap"
|
||||
rel="stylesheet" />
|
||||
<title>海音达工作台</title>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div id="root"></div>
|
||||
<script type="module" src="/src/main.tsx"></script>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
@@ -33,8 +33,8 @@ export class ErrorBoundary extends Component<Props, State> {
|
||||
|
||||
return (
|
||||
<Card className="mx-auto mt-12 max-w-lg space-y-4 p-6 text-center">
|
||||
<h2 className="text-lg font-semibold text-slate-900">页面渲染出错</h2>
|
||||
<p className="text-sm text-slate-600">
|
||||
<h2 className="text-lg font-semibold text-zinc-900">页面渲染出错</h2>
|
||||
<p className="text-sm text-zinc-600">
|
||||
{this.state.error?.message ?? "发生了未知错误"}
|
||||
</p>
|
||||
<div className="flex justify-center gap-3">
|
||||
@@ -46,7 +46,7 @@ export class ErrorBoundary extends Component<Props, State> {
|
||||
重试
|
||||
</button>
|
||||
<button
|
||||
className="inline-flex h-11 items-center rounded-lg border border-slate-200 bg-white px-4 text-sm font-medium text-slate-700 hover:bg-slate-50"
|
||||
className="inline-flex h-11 items-center rounded-lg border border-zinc-200 bg-white px-4 text-sm font-medium text-zinc-700 hover:bg-zinc-50"
|
||||
onClick={() => {
|
||||
window.location.href = "/dashboard";
|
||||
}}
|
||||
|
||||
@@ -92,7 +92,7 @@ export function Topbar() {
|
||||
</Button>
|
||||
</DialogTrigger>
|
||||
|
||||
<DialogContent className="left-0 top-0 h-full w-72 max-w-[80vw] translate-x-0 translate-y-0 rounded-none rounded-r-xl p-0">
|
||||
<DialogContent className="left-0 top-0 h-full w-72 max-w-[80vw] tranzinc-x-0 tranzinc-y-0 rounded-none rounded-r-xl p-0">
|
||||
<div className="flex h-full flex-col">
|
||||
<div className="flex items-center justify-between border-b border-zinc-200 px-4 py-4">
|
||||
<div>
|
||||
|
||||
@@ -4,15 +4,15 @@ import { cva, type VariantProps } from "class-variance-authority";
|
||||
import { cn } from "../../lib/utils";
|
||||
|
||||
const buttonVariants = cva(
|
||||
"inline-flex items-center justify-center whitespace-nowrap rounded-lg text-sm font-medium transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-primary-200 disabled:pointer-events-none disabled:opacity-50",
|
||||
"inline-flex items-center justify-center whitespace-nowrap rounded-lg text-sm font-medium transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-zinc-300 disabled:pointer-events-none disabled:opacity-50",
|
||||
{
|
||||
variants: {
|
||||
variant: {
|
||||
default: "bg-primary text-white hover:bg-primary-600",
|
||||
secondary: "border border-slate-200 bg-white text-slate-700 hover:bg-slate-50",
|
||||
outline: "border border-slate-200 bg-transparent text-slate-900 hover:bg-slate-50",
|
||||
default: "bg-primary text-white hover:bg-primary-light",
|
||||
secondary: "border border-zinc-200 bg-white text-zinc-700 hover:bg-zinc-50",
|
||||
outline: "border border-zinc-200 bg-transparent text-zinc-900 hover:bg-zinc-50",
|
||||
destructive: "bg-red-600 text-white hover:bg-red-700",
|
||||
ghost: "bg-transparent text-slate-700 hover:bg-slate-100",
|
||||
ghost: "bg-transparent text-zinc-700 hover:bg-zinc-100",
|
||||
link: "bg-transparent text-primary underline-offset-4 hover:underline",
|
||||
},
|
||||
size: {
|
||||
|
||||
@@ -6,7 +6,7 @@ export const Card = React.forwardRef<HTMLDivElement, React.HTMLAttributes<HTMLDi
|
||||
return (
|
||||
<div
|
||||
ref={ref}
|
||||
className={cn("rounded-xl border border-slate-200 bg-white shadow-sm", className)}
|
||||
className={cn("rounded-xl border border-zinc-200 bg-white shadow-sm", className)}
|
||||
{...props}
|
||||
/>
|
||||
);
|
||||
@@ -38,7 +38,7 @@ export const CardTitle = React.forwardRef<
|
||||
React.HTMLAttributes<HTMLHeadingElement>
|
||||
>(({ className, ...props }, ref) => {
|
||||
return (
|
||||
<h3 ref={ref} className={cn("text-base font-semibold text-slate-900", className)} {...props} />
|
||||
<h3 ref={ref} className={cn("text-base font-semibold text-zinc-900", className)} {...props} />
|
||||
);
|
||||
});
|
||||
|
||||
@@ -49,7 +49,7 @@ export const CardDescription = React.forwardRef<
|
||||
React.HTMLAttributes<HTMLParagraphElement>
|
||||
>(({ className, ...props }, ref) => {
|
||||
return (
|
||||
<p ref={ref} className={cn("text-sm text-slate-600", className)} {...props} />
|
||||
<p ref={ref} className={cn("text-sm text-zinc-600", className)} {...props} />
|
||||
);
|
||||
});
|
||||
|
||||
|
||||
@@ -30,7 +30,7 @@ export const DialogContent = React.forwardRef<
|
||||
<DialogPrimitive.Content
|
||||
ref={ref}
|
||||
className={cn(
|
||||
"fixed left-1/2 top-1/2 z-50 w-[92vw] max-w-lg -translate-x-1/2 -translate-y-1/2 rounded-xl border border-slate-200 bg-white p-6 shadow-lg focus-visible:outline-none",
|
||||
"fixed left-1/2 top-1/2 z-50 w-[92vw] max-w-lg -tranzinc-x-1/2 -tranzinc-y-1/2 rounded-xl border border-zinc-200 bg-white p-6 shadow-lg focus-visible:outline-none",
|
||||
className,
|
||||
)}
|
||||
{...props}
|
||||
@@ -54,7 +54,7 @@ export const DialogTitle = React.forwardRef<
|
||||
>(({ className, ...props }, ref) => (
|
||||
<DialogPrimitive.Title
|
||||
ref={ref}
|
||||
className={cn("text-lg font-semibold text-slate-900", className)}
|
||||
className={cn("text-lg font-semibold text-zinc-900", className)}
|
||||
{...props}
|
||||
/>
|
||||
));
|
||||
@@ -67,7 +67,7 @@ export const DialogDescription = React.forwardRef<
|
||||
>(({ className, ...props }, ref) => (
|
||||
<DialogPrimitive.Description
|
||||
ref={ref}
|
||||
className={cn("text-sm text-slate-600", className)}
|
||||
className={cn("text-sm text-zinc-600", className)}
|
||||
{...props}
|
||||
/>
|
||||
));
|
||||
|
||||
@@ -13,7 +13,7 @@ export const DropdownMenuContent = React.forwardRef<
|
||||
ref={ref}
|
||||
sideOffset={sideOffset}
|
||||
className={cn(
|
||||
"z-50 min-w-[12rem] overflow-hidden rounded-xl border border-slate-200 bg-white p-1 text-slate-900 shadow-md",
|
||||
"z-50 min-w-[12rem] overflow-hidden rounded-xl border border-zinc-200 bg-white p-1 text-zinc-900 shadow-md",
|
||||
className,
|
||||
)}
|
||||
{...props}
|
||||
@@ -30,7 +30,7 @@ export const DropdownMenuItem = React.forwardRef<
|
||||
<DropdownMenuPrimitive.Item
|
||||
ref={ref}
|
||||
className={cn(
|
||||
"flex cursor-default select-none items-center rounded-lg px-3 py-2 text-sm outline-none focus:bg-slate-100 data-[disabled]:pointer-events-none data-[disabled]:opacity-50",
|
||||
"flex cursor-default select-none items-center rounded-lg px-3 py-2 text-sm outline-none focus:bg-zinc-100 data-[disabled]:pointer-events-none data-[disabled]:opacity-50",
|
||||
className,
|
||||
)}
|
||||
{...props}
|
||||
@@ -45,7 +45,7 @@ export const DropdownMenuSeparator = React.forwardRef<
|
||||
>(({ className, ...props }, ref) => (
|
||||
<DropdownMenuPrimitive.Separator
|
||||
ref={ref}
|
||||
className={cn("-mx-1 my-1 h-px bg-slate-200", className)}
|
||||
className={cn("-mx-1 my-1 h-px bg-zinc-200", className)}
|
||||
{...props}
|
||||
/>
|
||||
));
|
||||
|
||||
@@ -9,7 +9,7 @@ export const Input = React.forwardRef<HTMLInputElement, InputProps>(
|
||||
<input
|
||||
type={type}
|
||||
className={cn(
|
||||
"flex h-11 w-full rounded-lg border border-slate-300 bg-white px-3 py-2 text-sm text-slate-900 placeholder:text-slate-400 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-primary-200 disabled:cursor-not-allowed disabled:opacity-50",
|
||||
"flex h-11 w-full rounded-lg border border-zinc-300 bg-white px-3 py-2 text-sm text-zinc-900 placeholder:text-zinc-400 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-primary-200 disabled:cursor-not-allowed disabled:opacity-50",
|
||||
className,
|
||||
)}
|
||||
ref={ref}
|
||||
|
||||
@@ -6,7 +6,7 @@ export type LabelProps = React.LabelHTMLAttributes<HTMLLabelElement>;
|
||||
export function Label({ className, ...props }: LabelProps) {
|
||||
return (
|
||||
<label
|
||||
className={cn("text-sm font-medium leading-none text-slate-800", className)}
|
||||
className={cn("text-sm font-medium leading-none text-zinc-800", className)}
|
||||
{...props}
|
||||
/>
|
||||
);
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { cn } from "../../lib/utils";
|
||||
|
||||
export function Skeleton({ className, ...props }: React.HTMLAttributes<HTMLDivElement>) {
|
||||
return <div className={cn("animate-pulse rounded-md bg-slate-200", className)} {...props} />;
|
||||
return <div className={cn("animate-pulse rounded-md bg-zinc-200", className)} {...props} />;
|
||||
}
|
||||
|
||||
@@ -7,7 +7,7 @@ export function Toaster() {
|
||||
duration={3000}
|
||||
toastOptions={{
|
||||
className:
|
||||
"border border-slate-200 bg-white text-slate-900 shadow-lg rounded-lg text-sm",
|
||||
"border border-zinc-200 bg-white text-zinc-900 shadow-lg rounded-lg text-sm",
|
||||
}}
|
||||
/>
|
||||
);
|
||||
|
||||
@@ -14,7 +14,7 @@ export function Spinner({
|
||||
}) {
|
||||
return (
|
||||
<svg
|
||||
className={cn("animate-spin text-slate-400", sizeMap[size], className)}
|
||||
className={cn("animate-spin text-zinc-400", sizeMap[size], className)}
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
>
|
||||
|
||||
@@ -15,7 +15,7 @@ export const TableHeader = React.forwardRef<
|
||||
HTMLTableSectionElement,
|
||||
React.HTMLAttributes<HTMLTableSectionElement>
|
||||
>(({ className, ...props }, ref) => (
|
||||
<thead ref={ref} className={cn("bg-slate-50 text-slate-600", className)} {...props} />
|
||||
<thead ref={ref} className={cn("bg-zinc-50 text-zinc-600", className)} {...props} />
|
||||
));
|
||||
|
||||
TableHeader.displayName = "TableHeader";
|
||||
@@ -31,7 +31,7 @@ TableBody.displayName = "TableBody";
|
||||
|
||||
export const TableRow = React.forwardRef<HTMLTableRowElement, React.HTMLAttributes<HTMLTableRowElement>>(
|
||||
({ className, ...props }, ref) => (
|
||||
<tr ref={ref} className={cn("border-t border-slate-100", className)} {...props} />
|
||||
<tr ref={ref} className={cn("border-t border-zinc-100", className)} {...props} />
|
||||
),
|
||||
);
|
||||
|
||||
@@ -41,7 +41,7 @@ export const TableHead = React.forwardRef<HTMLTableCellElement, React.ThHTMLAttr
|
||||
({ className, ...props }, ref) => (
|
||||
<th
|
||||
ref={ref}
|
||||
className={cn("h-10 px-3 text-left align-middle font-medium text-slate-600", className)}
|
||||
className={cn("h-10 px-3 text-left align-middle font-medium text-zinc-600", className)}
|
||||
{...props}
|
||||
/>
|
||||
),
|
||||
|
||||
@@ -11,7 +11,7 @@ export const TabsList = React.forwardRef<
|
||||
<TabsPrimitive.List
|
||||
ref={ref}
|
||||
className={cn(
|
||||
"inline-flex h-11 items-center justify-center rounded-lg border border-slate-200 bg-white p-1 text-slate-700",
|
||||
"inline-flex h-11 items-center justify-center rounded-lg border border-zinc-200 bg-white p-1 text-zinc-700",
|
||||
className,
|
||||
)}
|
||||
{...props}
|
||||
|
||||
@@ -8,7 +8,7 @@ export const Textarea = React.forwardRef<HTMLTextAreaElement, TextareaProps>(
|
||||
return (
|
||||
<textarea
|
||||
className={cn(
|
||||
"flex min-h-[120px] w-full rounded-lg border border-slate-300 bg-white px-3 py-2 text-sm text-slate-900 placeholder:text-slate-400 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-primary-200 disabled:cursor-not-allowed disabled:opacity-50",
|
||||
"flex min-h-[120px] w-full rounded-lg border border-zinc-300 bg-white px-3 py-2 text-sm text-zinc-900 placeholder:text-zinc-400 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-primary-200 disabled:cursor-not-allowed disabled:opacity-50",
|
||||
className,
|
||||
)}
|
||||
ref={ref}
|
||||
|
||||
@@ -2,7 +2,7 @@ import { Outlet } from "react-router-dom";
|
||||
|
||||
export function AuthLayout() {
|
||||
return (
|
||||
<div className="flex min-h-screen items-center justify-center bg-gradient-to-br from-primary-50 via-background-light to-slate-100 p-6">
|
||||
<div className="flex min-h-screen items-center justify-center bg-gradient-to-br from-primary-50 via-background-light to-zinc-100 p-6">
|
||||
<div className="w-full max-w-md">
|
||||
<Outlet />
|
||||
</div>
|
||||
|
||||
@@ -157,7 +157,7 @@ export function ApprovalRulesPage() {
|
||||
) : null}
|
||||
|
||||
<Card className="space-y-4 p-6">
|
||||
<h3 className="text-base font-semibold text-slate-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="space-y-1">
|
||||
@@ -209,7 +209,7 @@ export function ApprovalRulesPage() {
|
||||
<select
|
||||
id="level1Role"
|
||||
data-testid="approval-rules-create-level1"
|
||||
className="h-11 w-full rounded-lg border border-slate-300 bg-white px-3 py-2 text-sm"
|
||||
className="h-11 w-full rounded-lg border border-zinc-300 bg-white px-3 py-2 text-sm"
|
||||
value={level1Role}
|
||||
onChange={(event) => setLevel1Role(event.target.value as ApprovalRoleCode)}
|
||||
>
|
||||
@@ -226,7 +226,7 @@ export function ApprovalRulesPage() {
|
||||
<select
|
||||
id="level2Role"
|
||||
data-testid="approval-rules-create-level2"
|
||||
className="h-11 w-full rounded-lg border border-slate-300 bg-white px-3 py-2 text-sm"
|
||||
className="h-11 w-full rounded-lg border border-zinc-300 bg-white px-3 py-2 text-sm"
|
||||
value={level2Role}
|
||||
onChange={(event) => setLevel2Role(event.target.value as ApprovalRoleCode)}
|
||||
>
|
||||
@@ -244,7 +244,7 @@ export function ApprovalRulesPage() {
|
||||
<select
|
||||
id="level3Role"
|
||||
data-testid="approval-rules-create-level3"
|
||||
className="h-11 w-full rounded-lg border border-slate-300 bg-white px-3 py-2 text-sm"
|
||||
className="h-11 w-full rounded-lg border border-zinc-300 bg-white px-3 py-2 text-sm"
|
||||
value={level3Role}
|
||||
onChange={(event) => setLevel3Role(event.target.value as ApprovalRoleCode)}
|
||||
>
|
||||
@@ -282,7 +282,7 @@ export function ApprovalRulesPage() {
|
||||
</Card>
|
||||
|
||||
<Card className="space-y-4 p-6">
|
||||
<h3 className="text-base font-semibold text-slate-900">规则列表</h3>
|
||||
<h3 className="text-base font-semibold text-zinc-900">规则列表</h3>
|
||||
|
||||
{listQuery.isLoading ? <TableSkeleton columns={6} rows={5} /> : null}
|
||||
|
||||
@@ -299,7 +299,7 @@ export function ApprovalRulesPage() {
|
||||
) : null}
|
||||
|
||||
{!listQuery.isLoading && !listQuery.isError ? (
|
||||
<div className="rounded-lg border border-slate-200">
|
||||
<div className="rounded-lg border border-zinc-200">
|
||||
<Table data-testid="approval-rules-table">
|
||||
<TableHeader>
|
||||
<tr>
|
||||
@@ -316,18 +316,18 @@ export function ApprovalRulesPage() {
|
||||
rules.map((rule) => (
|
||||
<TableRow key={rule.id} data-testid="approval-rule-row">
|
||||
<TableCell>
|
||||
<div className="font-medium text-slate-900">{rule.name}</div>
|
||||
<div className="font-mono text-xs text-slate-500">{rule.id}</div>
|
||||
<div className="font-medium text-zinc-900">{rule.name}</div>
|
||||
<div className="font-mono text-xs text-zinc-500">{rule.id}</div>
|
||||
</TableCell>
|
||||
<TableCell className="text-sm text-slate-700">
|
||||
<TableCell className="text-sm text-zinc-700">
|
||||
{formatAmount(rule.minAmount)} ~ {rule.maxAmount ? formatAmount(rule.maxAmount) : "∞"}
|
||||
</TableCell>
|
||||
<TableCell className="text-sm text-slate-700">
|
||||
<TableCell className="text-sm text-zinc-700">
|
||||
{[rule.level1Role, rule.level2Role, rule.level3Role].filter(Boolean).join(" -> ") || "-"}
|
||||
</TableCell>
|
||||
<TableCell>{rule.priority}</TableCell>
|
||||
<TableCell className="text-sm">
|
||||
{rule.enabled ? <span className="text-emerald-600">启用</span> : <span className="text-slate-500">停用</span>}
|
||||
{rule.enabled ? <span className="text-emerald-600">启用</span> : <span className="text-zinc-500">停用</span>}
|
||||
</TableCell>
|
||||
<TableCell>
|
||||
<div className="flex flex-wrap gap-2">
|
||||
@@ -359,7 +359,7 @@ export function ApprovalRulesPage() {
|
||||
))
|
||||
) : (
|
||||
<TableRow>
|
||||
<TableCell className="py-3 text-slate-500" colSpan={6}>
|
||||
<TableCell className="py-3 text-zinc-500" colSpan={6}>
|
||||
暂无审批规则
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
@@ -430,7 +430,7 @@ export function ApprovalRulesPage() {
|
||||
<select
|
||||
id="editRuleLevel1"
|
||||
data-testid="approval-rule-edit-level1"
|
||||
className="h-11 w-full rounded-lg border border-slate-300 bg-white px-3 py-2 text-sm"
|
||||
className="h-11 w-full rounded-lg border border-zinc-300 bg-white px-3 py-2 text-sm"
|
||||
value={level1Role}
|
||||
onChange={(event) => setLevel1Role(event.target.value as ApprovalRoleCode)}
|
||||
>
|
||||
@@ -447,7 +447,7 @@ export function ApprovalRulesPage() {
|
||||
<select
|
||||
id="editRuleLevel2"
|
||||
data-testid="approval-rule-edit-level2"
|
||||
className="h-11 w-full rounded-lg border border-slate-300 bg-white px-3 py-2 text-sm"
|
||||
className="h-11 w-full rounded-lg border border-zinc-300 bg-white px-3 py-2 text-sm"
|
||||
value={level2Role}
|
||||
onChange={(event) => setLevel2Role(event.target.value as ApprovalRoleCode)}
|
||||
>
|
||||
@@ -465,7 +465,7 @@ export function ApprovalRulesPage() {
|
||||
<select
|
||||
id="editRuleLevel3"
|
||||
data-testid="approval-rule-edit-level3"
|
||||
className="h-11 w-full rounded-lg border border-slate-300 bg-white px-3 py-2 text-sm"
|
||||
className="h-11 w-full rounded-lg border border-zinc-300 bg-white px-3 py-2 text-sm"
|
||||
value={level3Role}
|
||||
onChange={(event) => setLevel3Role(event.target.value as ApprovalRoleCode)}
|
||||
>
|
||||
|
||||
@@ -137,10 +137,10 @@ export function AuditLogsPage() {
|
||||
|
||||
<section className="card-std space-y-4">
|
||||
<div className="grid gap-3 md:grid-cols-3">
|
||||
<label className="text-sm text-slate-700">
|
||||
<label className="text-sm text-zinc-700">
|
||||
Action
|
||||
<input
|
||||
className="mt-1 w-full rounded-lg border border-slate-300 px-3 py-2"
|
||||
className="mt-1 w-full rounded-lg border border-zinc-300 px-3 py-2"
|
||||
onChange={(event) => {
|
||||
setPage(1);
|
||||
setActionFilter(event.target.value.trim());
|
||||
@@ -150,10 +150,10 @@ export function AuditLogsPage() {
|
||||
/>
|
||||
</label>
|
||||
|
||||
<label className="text-sm text-slate-700">
|
||||
<label className="text-sm text-zinc-700">
|
||||
Target Type
|
||||
<input
|
||||
className="mt-1 w-full rounded-lg border border-slate-300 px-3 py-2"
|
||||
className="mt-1 w-full rounded-lg border border-zinc-300 px-3 py-2"
|
||||
onChange={(event) => {
|
||||
setPage(1);
|
||||
setTargetTypeFilter(event.target.value.trim());
|
||||
@@ -163,9 +163,9 @@ export function AuditLogsPage() {
|
||||
/>
|
||||
</label>
|
||||
|
||||
<div className="text-sm text-slate-700">
|
||||
<div className="text-sm text-zinc-700">
|
||||
结果统计
|
||||
<div className="mt-1 rounded-lg border border-slate-200 bg-slate-50 px-3 py-2 text-slate-900">
|
||||
<div className="mt-1 rounded-lg border border-zinc-200 bg-zinc-50 px-3 py-2 text-zinc-900">
|
||||
共 {data?.total ?? 0} 条
|
||||
</div>
|
||||
</div>
|
||||
@@ -177,9 +177,9 @@ export function AuditLogsPage() {
|
||||
</p>
|
||||
) : null}
|
||||
|
||||
<div className="overflow-x-auto rounded-lg border border-slate-200">
|
||||
<div className="overflow-x-auto rounded-lg border border-zinc-200">
|
||||
<table className="min-w-full text-left text-sm">
|
||||
<thead className="bg-slate-50 text-slate-600">
|
||||
<thead className="bg-zinc-50 text-zinc-600">
|
||||
<tr>
|
||||
<th className="px-3 py-2">时间</th>
|
||||
<th className="px-3 py-2">Action</th>
|
||||
@@ -199,12 +199,12 @@ export function AuditLogsPage() {
|
||||
|
||||
{!loading && data?.items?.length ? (
|
||||
data.items.map((item) => (
|
||||
<tr className="border-t border-slate-100" key={item.id}>
|
||||
<tr className="border-t border-zinc-100" key={item.id}>
|
||||
<td className="px-3 py-2">{new Date(item.createdAt).toLocaleString()}</td>
|
||||
<td className="px-3 py-2">{item.action}</td>
|
||||
<td className="px-3 py-2">
|
||||
<div>{item.actorName || item.actorId}</div>
|
||||
<div className="text-xs text-slate-500">{item.actorEmail}</div>
|
||||
<div className="text-xs text-zinc-500">{item.actorEmail}</div>
|
||||
</td>
|
||||
<td className="px-3 py-2">
|
||||
{item.targetType}
|
||||
@@ -217,7 +217,7 @@ export function AuditLogsPage() {
|
||||
|
||||
{!loading && !(data?.items?.length ?? 0) ? (
|
||||
<tr>
|
||||
<td className="px-3 py-3 text-slate-500" colSpan={5}>
|
||||
<td className="px-3 py-3 text-zinc-500" colSpan={5}>
|
||||
暂无数据
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
+18
-18
@@ -118,7 +118,7 @@ export function BudgetsPage() {
|
||||
return (
|
||||
<div className="space-y-4">
|
||||
<PageHeader title="预算管理" subtitle="你当前没有预算权限,请联系管理员开通。" />
|
||||
<Card className="p-6 text-sm text-slate-600">所需权限:budget.view 或 budget.manage</Card>
|
||||
<Card className="p-6 text-sm text-zinc-600">所需权限:budget.view 或 budget.manage</Card>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -139,10 +139,10 @@ export function BudgetsPage() {
|
||||
) : null}
|
||||
|
||||
<Card className="space-y-3 p-6">
|
||||
<h3 className="text-base font-semibold text-slate-900">筛选条件</h3>
|
||||
<h3 className="text-base font-semibold text-zinc-900">筛选条件</h3>
|
||||
<div className="grid gap-3 md:grid-cols-3">
|
||||
<select
|
||||
className="h-11 rounded-lg border border-slate-300 bg-white px-3 py-2 text-sm"
|
||||
className="h-11 rounded-lg border border-zinc-300 bg-white px-3 py-2 text-sm"
|
||||
value={status}
|
||||
onChange={(event) => {
|
||||
setStatus(event.target.value as "active" | "archived" | "");
|
||||
@@ -155,7 +155,7 @@ export function BudgetsPage() {
|
||||
</select>
|
||||
|
||||
<select
|
||||
className="h-11 rounded-lg border border-slate-300 bg-white px-3 py-2 text-sm"
|
||||
className="h-11 rounded-lg border border-zinc-300 bg-white px-3 py-2 text-sm"
|
||||
value={departmentId}
|
||||
onChange={(event) => {
|
||||
setDepartmentId(event.target.value);
|
||||
@@ -182,22 +182,22 @@ export function BudgetsPage() {
|
||||
|
||||
{canManageBudget ? (
|
||||
<Card className="space-y-3 p-6">
|
||||
<h3 className="text-base font-semibold text-slate-900">新建预算池</h3>
|
||||
<h3 className="text-base font-semibold text-zinc-900">新建预算池</h3>
|
||||
<div className="grid gap-3 md:grid-cols-3">
|
||||
<input
|
||||
className="h-11 rounded-lg border border-slate-300 bg-white px-3 py-2 text-sm"
|
||||
className="h-11 rounded-lg border border-zinc-300 bg-white px-3 py-2 text-sm"
|
||||
placeholder="编码(例如 BGT-2026-RD)"
|
||||
value={code}
|
||||
onChange={(event) => setCode(event.target.value)}
|
||||
/>
|
||||
<input
|
||||
className="h-11 rounded-lg border border-slate-300 bg-white px-3 py-2 text-sm"
|
||||
className="h-11 rounded-lg border border-zinc-300 bg-white px-3 py-2 text-sm"
|
||||
placeholder="名称"
|
||||
value={name}
|
||||
onChange={(event) => setName(event.target.value)}
|
||||
/>
|
||||
<select
|
||||
className="h-11 rounded-lg border border-slate-300 bg-white px-3 py-2 text-sm"
|
||||
className="h-11 rounded-lg border border-zinc-300 bg-white px-3 py-2 text-sm"
|
||||
value={formDepartmentId}
|
||||
onChange={(event) => setFormDepartmentId(event.target.value)}
|
||||
>
|
||||
@@ -209,25 +209,25 @@ export function BudgetsPage() {
|
||||
))}
|
||||
</select>
|
||||
<input
|
||||
className="h-11 rounded-lg border border-slate-300 bg-white px-3 py-2 text-sm"
|
||||
className="h-11 rounded-lg border border-zinc-300 bg-white px-3 py-2 text-sm"
|
||||
type="date"
|
||||
value={periodStart}
|
||||
onChange={(event) => setPeriodStart(event.target.value)}
|
||||
/>
|
||||
<input
|
||||
className="h-11 rounded-lg border border-slate-300 bg-white px-3 py-2 text-sm"
|
||||
className="h-11 rounded-lg border border-zinc-300 bg-white px-3 py-2 text-sm"
|
||||
type="date"
|
||||
value={periodEnd}
|
||||
onChange={(event) => setPeriodEnd(event.target.value)}
|
||||
/>
|
||||
<input
|
||||
className="h-11 rounded-lg border border-slate-300 bg-white px-3 py-2 text-sm"
|
||||
className="h-11 rounded-lg border border-zinc-300 bg-white px-3 py-2 text-sm"
|
||||
placeholder="预算总额(例如 100000.00)"
|
||||
value={allocatedAmount}
|
||||
onChange={(event) => setAllocatedAmount(event.target.value)}
|
||||
/>
|
||||
<input
|
||||
className="h-11 rounded-lg border border-slate-300 bg-white px-3 py-2 text-sm"
|
||||
className="h-11 rounded-lg border border-zinc-300 bg-white px-3 py-2 text-sm"
|
||||
type="number"
|
||||
min={1}
|
||||
max={100}
|
||||
@@ -254,13 +254,13 @@ export function BudgetsPage() {
|
||||
) : null}
|
||||
|
||||
<Card className="space-y-3 p-6">
|
||||
<h3 className="text-base font-semibold text-slate-900">预算池列表</h3>
|
||||
<h3 className="text-base font-semibold text-zinc-900">预算池列表</h3>
|
||||
|
||||
{budgetsQuery.isLoading ? <TableSkeleton columns={7} rows={4} /> : null}
|
||||
|
||||
{!budgetsQuery.isLoading ? (
|
||||
<>
|
||||
<div className="rounded-lg border border-slate-200">
|
||||
<div className="rounded-lg border border-zinc-200">
|
||||
<Table>
|
||||
<TableHeader>
|
||||
<TableRow>
|
||||
@@ -277,7 +277,7 @@ export function BudgetsPage() {
|
||||
{(budgetsQuery.data?.items ?? []).length ? (
|
||||
(budgetsQuery.data?.items ?? []).map((item) => (
|
||||
<TableRow key={item.id}>
|
||||
<TableCell className="font-medium text-slate-900">{item.code}</TableCell>
|
||||
<TableCell className="font-medium text-zinc-900">{item.code}</TableCell>
|
||||
<TableCell>{item.name}</TableCell>
|
||||
<TableCell><StatusBadge status={item.status} /></TableCell>
|
||||
<TableCell>
|
||||
@@ -285,7 +285,7 @@ export function BudgetsPage() {
|
||||
</TableCell>
|
||||
<TableCell>
|
||||
{item.usedAmount} / {item.allocatedAmount}
|
||||
<div className="text-xs text-slate-500">剩余 {item.remainingAmount}</div>
|
||||
<div className="text-xs text-zinc-500">剩余 {item.remainingAmount}</div>
|
||||
</TableCell>
|
||||
<TableCell>{item.warningThresholdPercent}%</TableCell>
|
||||
<TableCell>
|
||||
@@ -300,14 +300,14 @@ export function BudgetsPage() {
|
||||
归档
|
||||
</Button>
|
||||
) : (
|
||||
<span className="text-xs text-slate-400">-</span>
|
||||
<span className="text-xs text-zinc-400">-</span>
|
||||
)}
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
))
|
||||
) : (
|
||||
<TableRow>
|
||||
<TableCell className="py-3 text-slate-500" colSpan={7}>
|
||||
<TableCell className="py-3 text-zinc-500" colSpan={7}>
|
||||
暂无预算池
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
|
||||
@@ -46,7 +46,7 @@ export function ChangePasswordPage() {
|
||||
<Card className="space-y-6 border-primary-100 p-6">
|
||||
<div className="mb-6 space-y-2">
|
||||
<p className="text-xs font-semibold uppercase tracking-wide text-primary">hyb-oa</p>
|
||||
<h1 className="text-2xl font-bold text-slate-900">修改密码</h1>
|
||||
<h1 className="text-2xl font-bold text-zinc-900">修改密码</h1>
|
||||
<p className="text-sm text-secondary">
|
||||
为了账号安全,请先修改初始密码后再进入系统。
|
||||
</p>
|
||||
|
||||
@@ -117,8 +117,8 @@ export function DashboardPage() {
|
||||
<div className="space-y-4">
|
||||
<PageHeader title="仪表盘" subtitle="你当前没有看板权限,请联系管理员开通。" />
|
||||
<Card className="space-y-2 p-6 text-sm">
|
||||
<p className="text-slate-700">当前用户:{me?.name ?? "-"}</p>
|
||||
<p className="text-slate-500">所需权限:report.dashboard.view</p>
|
||||
<p className="text-zinc-700">当前用户:{me?.name ?? "-"}</p>
|
||||
<p className="text-zinc-500">所需权限:report.dashboard.view</p>
|
||||
</Card>
|
||||
</div>
|
||||
);
|
||||
@@ -166,36 +166,36 @@ export function DashboardPage() {
|
||||
<Card className="space-y-4 p-6">
|
||||
<div className="grid gap-3 md:grid-cols-4">
|
||||
<div className="space-y-1">
|
||||
<label className="text-xs text-slate-600" htmlFor="dashboard-date-from">
|
||||
<label className="text-xs text-zinc-600" htmlFor="dashboard-date-from">
|
||||
开始日期
|
||||
</label>
|
||||
<input
|
||||
id="dashboard-date-from"
|
||||
className="h-11 w-full rounded-lg border border-slate-300 bg-white px-3 py-2 text-sm"
|
||||
className="h-11 w-full rounded-lg border border-zinc-300 bg-white px-3 py-2 text-sm"
|
||||
type="date"
|
||||
value={dateFrom}
|
||||
onChange={(event) => setDateFrom(event.target.value)}
|
||||
/>
|
||||
</div>
|
||||
<div className="space-y-1">
|
||||
<label className="text-xs text-slate-600" htmlFor="dashboard-date-to">
|
||||
<label className="text-xs text-zinc-600" htmlFor="dashboard-date-to">
|
||||
结束日期
|
||||
</label>
|
||||
<input
|
||||
id="dashboard-date-to"
|
||||
className="h-11 w-full rounded-lg border border-slate-300 bg-white px-3 py-2 text-sm"
|
||||
className="h-11 w-full rounded-lg border border-zinc-300 bg-white px-3 py-2 text-sm"
|
||||
type="date"
|
||||
value={dateTo}
|
||||
onChange={(event) => setDateTo(event.target.value)}
|
||||
/>
|
||||
</div>
|
||||
<div className="space-y-1">
|
||||
<label className="text-xs text-slate-600" htmlFor="dashboard-department-id">
|
||||
<label className="text-xs text-zinc-600" htmlFor="dashboard-department-id">
|
||||
部门
|
||||
</label>
|
||||
<select
|
||||
id="dashboard-department-id"
|
||||
className="h-11 w-full rounded-lg border border-slate-300 bg-white px-3 py-2 text-sm"
|
||||
className="h-11 w-full rounded-lg border border-zinc-300 bg-white px-3 py-2 text-sm"
|
||||
value={departmentId}
|
||||
onChange={(event) => setDepartmentId(event.target.value)}
|
||||
>
|
||||
@@ -230,26 +230,26 @@ export function DashboardPage() {
|
||||
|
||||
<div className="grid gap-4 md:grid-cols-4">
|
||||
<Card className="p-4">
|
||||
<p className="text-xs text-slate-500">报销单总数</p>
|
||||
<p className="mt-1 text-2xl font-semibold text-slate-900">
|
||||
<p className="text-xs text-zinc-500">报销单总数</p>
|
||||
<p className="mt-1 text-2xl font-semibold text-zinc-900">
|
||||
{loading ? "..." : overviewQuery.data?.totalCount ?? 0}
|
||||
</p>
|
||||
</Card>
|
||||
<Card className="p-4">
|
||||
<p className="text-xs text-slate-500">报销总金额</p>
|
||||
<p className="mt-1 text-2xl font-semibold text-slate-900">
|
||||
<p className="text-xs text-zinc-500">报销总金额</p>
|
||||
<p className="mt-1 text-2xl font-semibold text-zinc-900">
|
||||
{loading ? "..." : toCurrency(overviewQuery.data?.totalAmount)}
|
||||
</p>
|
||||
</Card>
|
||||
<Card className="p-4">
|
||||
<p className="text-xs text-slate-500">已付款金额</p>
|
||||
<p className="mt-1 text-2xl font-semibold text-slate-900">
|
||||
<p className="text-xs text-zinc-500">已付款金额</p>
|
||||
<p className="mt-1 text-2xl font-semibold text-zinc-900">
|
||||
{loading ? "..." : toCurrency(overviewQuery.data?.paidAmount)}
|
||||
</p>
|
||||
</Card>
|
||||
<Card className="p-4">
|
||||
<p className="text-xs text-slate-500">审批中数量</p>
|
||||
<p className="mt-1 text-2xl font-semibold text-slate-900">
|
||||
<p className="text-xs text-zinc-500">审批中数量</p>
|
||||
<p className="mt-1 text-2xl font-semibold text-zinc-900">
|
||||
{loading ? "..." : overviewQuery.data?.reviewingCount ?? 0}
|
||||
</p>
|
||||
</Card>
|
||||
@@ -257,7 +257,7 @@ export function DashboardPage() {
|
||||
|
||||
<div className="grid gap-4 lg:grid-cols-2">
|
||||
<Card className="space-y-3 p-6">
|
||||
<h3 className="text-base font-semibold text-slate-900">费用趋势</h3>
|
||||
<h3 className="text-base font-semibold text-zinc-900">费用趋势</h3>
|
||||
{trendPoints.length ? (
|
||||
<div className="space-y-2">
|
||||
{trendPoints.map((item) => {
|
||||
@@ -265,13 +265,13 @@ export function DashboardPage() {
|
||||
const width = `${Math.max(5, Math.round((amount / maxTrendAmount) * 100))}%`;
|
||||
return (
|
||||
<div key={item.date} className="space-y-1">
|
||||
<div className="flex items-center justify-between text-xs text-slate-600">
|
||||
<div className="flex items-center justify-between text-xs text-zinc-600">
|
||||
<span>{item.date}</span>
|
||||
<span>
|
||||
{toCurrency(item.amount)} / {item.count} 笔
|
||||
</span>
|
||||
</div>
|
||||
<div className="h-2 rounded-full bg-slate-100">
|
||||
<div className="h-2 rounded-full bg-zinc-100">
|
||||
<div className="h-2 rounded-full bg-primary" style={{ width }} />
|
||||
</div>
|
||||
</div>
|
||||
@@ -279,12 +279,12 @@ export function DashboardPage() {
|
||||
})}
|
||||
</div>
|
||||
) : (
|
||||
<p className="text-sm text-slate-500">暂无趋势数据</p>
|
||||
<p className="text-sm text-zinc-500">暂无趋势数据</p>
|
||||
)}
|
||||
</Card>
|
||||
|
||||
<Card className="space-y-3 p-6">
|
||||
<h3 className="text-base font-semibold text-slate-900">费用分类 TopN</h3>
|
||||
<h3 className="text-base font-semibold text-zinc-900">费用分类 TopN</h3>
|
||||
{topCategories.length ? (
|
||||
<div className="space-y-2">
|
||||
{topCategories.map((item) => {
|
||||
@@ -292,7 +292,7 @@ export function DashboardPage() {
|
||||
const width = `${Math.max(5, Math.round((amount / maxCategoryAmount) * 100))}%`;
|
||||
return (
|
||||
<div key={item.code} className="space-y-1">
|
||||
<div className="flex items-center justify-between text-xs text-slate-600">
|
||||
<div className="flex items-center justify-between text-xs text-zinc-600">
|
||||
<span>
|
||||
{item.name} ({item.code})
|
||||
</span>
|
||||
@@ -300,7 +300,7 @@ export function DashboardPage() {
|
||||
{toCurrency(item.amount)} / {item.count} 笔
|
||||
</span>
|
||||
</div>
|
||||
<div className="h-2 rounded-full bg-slate-100">
|
||||
<div className="h-2 rounded-full bg-zinc-100">
|
||||
<div className="h-2 rounded-full bg-emerald-500" style={{ width }} />
|
||||
</div>
|
||||
</div>
|
||||
@@ -308,22 +308,22 @@ export function DashboardPage() {
|
||||
})}
|
||||
</div>
|
||||
) : (
|
||||
<p className="text-sm text-slate-500">暂无分类统计</p>
|
||||
<p className="text-sm text-zinc-500">暂无分类统计</p>
|
||||
)}
|
||||
</Card>
|
||||
</div>
|
||||
|
||||
<Card className="space-y-2 p-6 text-sm">
|
||||
<h3 className="text-base font-semibold text-slate-900">审批效率</h3>
|
||||
<p className="text-slate-700">
|
||||
<h3 className="text-base font-semibold text-zinc-900">审批效率</h3>
|
||||
<p className="text-zinc-700">
|
||||
已统计通过单据:
|
||||
<span className="ml-1 font-medium text-slate-900">
|
||||
<span className="ml-1 font-medium text-zinc-900">
|
||||
{approvalEfficiencyQuery.data?.approvedCount ?? 0}
|
||||
</span>
|
||||
</p>
|
||||
<p className="text-slate-700">
|
||||
<p className="text-zinc-700">
|
||||
平均审批耗时(小时):
|
||||
<span className="ml-1 font-medium text-slate-900">
|
||||
<span className="ml-1 font-medium text-zinc-900">
|
||||
{approvalEfficiencyQuery.data?.averageHours?.toFixed(2) ?? "0.00"}
|
||||
</span>
|
||||
</p>
|
||||
|
||||
@@ -81,13 +81,13 @@ export function DelegationsPage() {
|
||||
) : null}
|
||||
|
||||
<Card className="space-y-4 p-6">
|
||||
<h3 className="text-base font-semibold text-slate-900">创建委派</h3>
|
||||
<h3 className="text-base font-semibold text-zinc-900">创建委派</h3>
|
||||
|
||||
<div className="grid gap-3 md:grid-cols-2">
|
||||
<label className="space-y-1 text-sm">
|
||||
<span className="font-medium text-slate-700">委派给(审批人候选)</span>
|
||||
<span className="font-medium text-zinc-700">委派给(审批人候选)</span>
|
||||
<select
|
||||
className="h-11 w-full rounded-lg border border-slate-300 bg-white px-3 py-2 text-sm"
|
||||
className="h-11 w-full rounded-lg border border-zinc-300 bg-white px-3 py-2 text-sm"
|
||||
data-testid="delegations-create-delegate"
|
||||
onChange={(event) => setDelegateEmployeeId(event.target.value)}
|
||||
value={delegateEmployeeId}
|
||||
@@ -99,15 +99,15 @@ export function DelegationsPage() {
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
<p className="text-xs text-slate-500">
|
||||
<p className="text-xs text-zinc-500">
|
||||
说明:当前 MVP 未接入员工搜索,这里先复用“转审候选”列表作为可选目标。
|
||||
</p>
|
||||
</label>
|
||||
|
||||
<label className="space-y-1 text-sm">
|
||||
<span className="font-medium text-slate-700">范围</span>
|
||||
<span className="font-medium text-zinc-700">范围</span>
|
||||
<select
|
||||
className="h-11 w-full rounded-lg border border-slate-300 bg-white px-3 py-2 text-sm"
|
||||
className="h-11 w-full rounded-lg border border-zinc-300 bg-white px-3 py-2 text-sm"
|
||||
data-testid="delegations-create-scope"
|
||||
onChange={(event) => setScope(event.target.value as "all" | "reimbursement_submit")}
|
||||
value={scope}
|
||||
@@ -118,7 +118,7 @@ export function DelegationsPage() {
|
||||
</label>
|
||||
|
||||
<label className="space-y-1 text-sm md:col-span-2">
|
||||
<span className="font-medium text-slate-700">有效期结束(可选)</span>
|
||||
<span className="font-medium text-zinc-700">有效期结束(可选)</span>
|
||||
<Input
|
||||
data-testid="delegations-create-valid-to"
|
||||
onChange={(event) => setValidTo(event.target.value)}
|
||||
@@ -142,9 +142,9 @@ export function DelegationsPage() {
|
||||
|
||||
<Card className="space-y-4 p-6">
|
||||
<div className="flex flex-wrap items-center justify-between gap-2">
|
||||
<h3 className="text-base font-semibold text-slate-900">我的委派</h3>
|
||||
<h3 className="text-base font-semibold text-zinc-900">我的委派</h3>
|
||||
<select
|
||||
className="h-11 rounded-lg border border-slate-300 bg-white px-3 py-2 text-sm"
|
||||
className="h-11 rounded-lg border border-zinc-300 bg-white px-3 py-2 text-sm"
|
||||
data-testid="delegations-status-filter"
|
||||
onChange={(event) =>
|
||||
setStatus(event.target.value as "active" | "scheduled" | "expired" | "revoked" | "all")
|
||||
@@ -175,8 +175,8 @@ export function DelegationsPage() {
|
||||
{listQuery.data ? (
|
||||
<div className="space-y-6">
|
||||
<div>
|
||||
<p className="mb-2 text-sm font-medium text-slate-800">我发起的委派</p>
|
||||
<div className="rounded-lg border border-slate-200">
|
||||
<p className="mb-2 text-sm font-medium text-zinc-800">我发起的委派</p>
|
||||
<div className="rounded-lg border border-zinc-200">
|
||||
<Table data-testid="delegations-initiated-table">
|
||||
<TableHeader>
|
||||
<tr>
|
||||
@@ -211,7 +211,7 @@ export function DelegationsPage() {
|
||||
))
|
||||
) : (
|
||||
<TableRow>
|
||||
<TableCell className="py-3 text-slate-500" colSpan={5}>
|
||||
<TableCell className="py-3 text-zinc-500" colSpan={5}>
|
||||
暂无数据
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
@@ -222,8 +222,8 @@ export function DelegationsPage() {
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<p className="mb-2 text-sm font-medium text-slate-800">我接收的委派</p>
|
||||
<div className="rounded-lg border border-slate-200">
|
||||
<p className="mb-2 text-sm font-medium text-zinc-800">我接收的委派</p>
|
||||
<div className="rounded-lg border border-zinc-200">
|
||||
<Table data-testid="delegations-received-table">
|
||||
<TableHeader>
|
||||
<tr>
|
||||
@@ -245,7 +245,7 @@ export function DelegationsPage() {
|
||||
))
|
||||
) : (
|
||||
<TableRow>
|
||||
<TableCell className="py-3 text-slate-500" colSpan={4}>
|
||||
<TableCell className="py-3 text-zinc-500" colSpan={4}>
|
||||
暂无数据
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
|
||||
@@ -104,7 +104,7 @@ export function DepartmentsPage() {
|
||||
|
||||
<Card className="space-y-4 p-6">
|
||||
<div className="flex items-center justify-between gap-3">
|
||||
<h3 className="text-base font-semibold text-slate-900">新建部门</h3>
|
||||
<h3 className="text-base font-semibold text-zinc-900">新建部门</h3>
|
||||
<Button
|
||||
data-testid="departments-toggle-archived"
|
||||
variant="secondary"
|
||||
@@ -136,7 +136,7 @@ export function DepartmentsPage() {
|
||||
<select
|
||||
id="deptParent"
|
||||
data-testid="departments-create-parent"
|
||||
className="h-11 w-full rounded-lg border border-slate-300 bg-white px-3 py-2 text-sm"
|
||||
className="h-11 w-full rounded-lg border border-zinc-300 bg-white px-3 py-2 text-sm"
|
||||
value={parentId}
|
||||
onChange={(event) => setParentId(event.target.value)}
|
||||
>
|
||||
@@ -165,7 +165,7 @@ export function DepartmentsPage() {
|
||||
</Card>
|
||||
|
||||
<Card className="space-y-4 p-6">
|
||||
<h3 className="text-base font-semibold text-slate-900">部门列表</h3>
|
||||
<h3 className="text-base font-semibold text-zinc-900">部门列表</h3>
|
||||
|
||||
{listQuery.isLoading ? <TableSkeleton columns={4} rows={4} /> : null}
|
||||
|
||||
@@ -182,7 +182,7 @@ export function DepartmentsPage() {
|
||||
) : null}
|
||||
|
||||
{!listQuery.isLoading && !listQuery.isError ? (
|
||||
<div className="rounded-lg border border-slate-200">
|
||||
<div className="rounded-lg border border-zinc-200">
|
||||
<Table data-testid="departments-table">
|
||||
<TableHeader>
|
||||
<tr>
|
||||
@@ -199,11 +199,11 @@ export function DepartmentsPage() {
|
||||
return (
|
||||
<TableRow data-testid="department-row" key={item.id}>
|
||||
<TableCell>
|
||||
<div className="font-medium text-slate-900">{item.name}</div>
|
||||
<div className="text-xs text-slate-500 font-mono">{item.id}</div>
|
||||
<div className="font-medium text-zinc-900">{item.name}</div>
|
||||
<div className="text-xs text-zinc-500 font-mono">{item.id}</div>
|
||||
</TableCell>
|
||||
<TableCell>{parentName}</TableCell>
|
||||
<TableCell className="text-xs text-slate-600">
|
||||
<TableCell className="text-xs text-zinc-600">
|
||||
{item.archivedAt ? item.archivedAt.slice(0, 19).replace("T", " ") : "-"}
|
||||
</TableCell>
|
||||
<TableCell>
|
||||
@@ -219,7 +219,7 @@ export function DepartmentsPage() {
|
||||
归档
|
||||
</Button>
|
||||
) : (
|
||||
<span className="text-xs text-slate-500">已归档</span>
|
||||
<span className="text-xs text-zinc-500">已归档</span>
|
||||
)}
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
@@ -227,7 +227,7 @@ export function DepartmentsPage() {
|
||||
})
|
||||
) : (
|
||||
<TableRow>
|
||||
<TableCell className="py-3 text-slate-500" colSpan={4}>
|
||||
<TableCell className="py-3 text-zinc-500" colSpan={4}>
|
||||
暂无部门
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
|
||||
@@ -140,7 +140,7 @@ export function FeatureFlagsPage() {
|
||||
) : null}
|
||||
|
||||
<Card className="space-y-4 p-6">
|
||||
<h3 className="text-base font-semibold text-slate-900">开关列表</h3>
|
||||
<h3 className="text-base font-semibold text-zinc-900">开关列表</h3>
|
||||
|
||||
{flagsQuery.isLoading ? <TableSkeleton columns={6} rows={2} /> : null}
|
||||
|
||||
@@ -157,7 +157,7 @@ export function FeatureFlagsPage() {
|
||||
) : null}
|
||||
|
||||
{!flagsQuery.isLoading && !flagsQuery.isError ? (
|
||||
<div className="rounded-lg border border-slate-200">
|
||||
<div className="rounded-lg border border-zinc-200">
|
||||
<Table data-testid="feature-flags-table">
|
||||
<TableHeader>
|
||||
<tr>
|
||||
@@ -177,8 +177,8 @@ export function FeatureFlagsPage() {
|
||||
return (
|
||||
<TableRow key={item.flagKey} data-testid={`feature-flag-row-${item.flagKey}`}>
|
||||
<TableCell>
|
||||
<div className="font-medium text-slate-900">{toDisplayName(item.flagKey)}</div>
|
||||
<div className="font-mono text-xs text-slate-500">{item.flagKey}</div>
|
||||
<div className="font-medium text-zinc-900">{toDisplayName(item.flagKey)}</div>
|
||||
<div className="font-mono text-xs text-zinc-500">{item.flagKey}</div>
|
||||
</TableCell>
|
||||
|
||||
<TableCell className="text-sm">
|
||||
@@ -194,14 +194,14 @@ export function FeatureFlagsPage() {
|
||||
{item.effectiveEnabled ? "ON" : "OFF"}
|
||||
</span>
|
||||
</div>
|
||||
<div className="text-xs text-slate-500">reason: {item.effectiveReason}</div>
|
||||
<div className="text-xs text-zinc-500">reason: {item.effectiveReason}</div>
|
||||
</TableCell>
|
||||
|
||||
<TableCell className="space-y-2 text-sm">
|
||||
<div>
|
||||
<Label className="mb-1 block text-xs text-slate-500">override</Label>
|
||||
<Label className="mb-1 block text-xs text-zinc-500">override</Label>
|
||||
<select
|
||||
className="h-10 w-full rounded-lg border border-slate-300 bg-white px-3 py-2 text-sm"
|
||||
className="h-10 w-full rounded-lg border border-zinc-300 bg-white px-3 py-2 text-sm"
|
||||
value={draft.overrideMode}
|
||||
onChange={(event) =>
|
||||
patchDraft(item.flagKey, (prev) => ({
|
||||
@@ -219,9 +219,9 @@ export function FeatureFlagsPage() {
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<Label className="mb-1 block text-xs text-slate-500">rollout</Label>
|
||||
<Label className="mb-1 block text-xs text-zinc-500">rollout</Label>
|
||||
<select
|
||||
className="h-10 w-full rounded-lg border border-slate-300 bg-white px-3 py-2 text-sm"
|
||||
className="h-10 w-full rounded-lg border border-zinc-300 bg-white px-3 py-2 text-sm"
|
||||
value={draft.rolloutType}
|
||||
onChange={(event) =>
|
||||
patchDraft(item.flagKey, (prev) => ({
|
||||
@@ -247,7 +247,7 @@ export function FeatureFlagsPage() {
|
||||
{draft.rolloutType === "department_allowlist" ? (
|
||||
<div className="space-y-2">
|
||||
<select
|
||||
className="h-10 w-full rounded-lg border border-slate-300 bg-white px-3 py-2 text-sm"
|
||||
className="h-10 w-full rounded-lg border border-zinc-300 bg-white px-3 py-2 text-sm"
|
||||
value=""
|
||||
onChange={(event) => {
|
||||
const selected = event.target.value;
|
||||
@@ -266,12 +266,12 @@ export function FeatureFlagsPage() {
|
||||
))}
|
||||
</select>
|
||||
|
||||
<div className="max-h-24 overflow-auto rounded border border-slate-200 p-2 text-xs">
|
||||
<div className="max-h-24 overflow-auto rounded border border-zinc-200 p-2 text-xs">
|
||||
{draft.departmentIds.length ? (
|
||||
draft.departmentIds.map((departmentId) => (
|
||||
<div
|
||||
key={departmentId}
|
||||
className="mb-1 flex items-center justify-between rounded bg-slate-50 px-2 py-1"
|
||||
className="mb-1 flex items-center justify-between rounded bg-zinc-50 px-2 py-1"
|
||||
>
|
||||
<span>{departmentsById.get(departmentId) ?? departmentId}</span>
|
||||
<button
|
||||
@@ -289,18 +289,18 @@ export function FeatureFlagsPage() {
|
||||
</div>
|
||||
))
|
||||
) : (
|
||||
<p className="text-slate-500">暂无白名单部门</p>
|
||||
<p className="text-zinc-500">暂无白名单部门</p>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
) : (
|
||||
<span className="text-slate-500">全量</span>
|
||||
<span className="text-zinc-500">全量</span>
|
||||
)}
|
||||
</TableCell>
|
||||
|
||||
<TableCell>
|
||||
<textarea
|
||||
className="min-h-24 w-full rounded-lg border border-slate-300 bg-white px-3 py-2 text-sm"
|
||||
className="min-h-24 w-full rounded-lg border border-zinc-300 bg-white px-3 py-2 text-sm"
|
||||
placeholder="填写本次变更说明"
|
||||
value={draft.note}
|
||||
onChange={(event) =>
|
||||
@@ -310,7 +310,7 @@ export function FeatureFlagsPage() {
|
||||
}))
|
||||
}
|
||||
/>
|
||||
<p className="mt-1 text-xs text-slate-500">version: {draft.version}</p>
|
||||
<p className="mt-1 text-xs text-zinc-500">version: {draft.version}</p>
|
||||
</TableCell>
|
||||
|
||||
<TableCell>
|
||||
|
||||
@@ -5,7 +5,7 @@ export function ForbiddenPage() {
|
||||
<div className="flex min-h-screen items-center justify-center bg-background-light p-6">
|
||||
<div className="card-std max-w-lg text-center">
|
||||
<p className="text-sm font-semibold uppercase tracking-wide text-primary">403</p>
|
||||
<h1 className="mt-2 text-2xl font-bold text-slate-900">无权限访问</h1>
|
||||
<h1 className="mt-2 text-2xl font-bold text-zinc-900">无权限访问</h1>
|
||||
<p className="mt-2 text-sm text-secondary">
|
||||
当前账号没有访问该页面的权限,请联系管理员或返回可访问页面。
|
||||
</p>
|
||||
|
||||
@@ -62,9 +62,9 @@ export function LoginPage() {
|
||||
<Card className="space-y-6 border-primary-100 p-6">
|
||||
<div className="mb-6">
|
||||
<p className="text-xs font-semibold uppercase tracking-wide text-primary">hyb-oa</p>
|
||||
<h1 className="mt-2 text-2xl font-bold text-slate-900">登录工作台</h1>
|
||||
<h1 className="mt-2 text-2xl font-bold text-zinc-900">登录工作台</h1>
|
||||
<p className="mt-2 text-sm text-secondary">使用邮箱/手机号与密码登录。</p>
|
||||
<p className="mt-1 text-xs text-slate-500">忘记密码:请联系管理员重置。</p>
|
||||
<p className="mt-1 text-xs text-zinc-500">忘记密码:请联系管理员重置。</p>
|
||||
</div>
|
||||
|
||||
<form className="space-y-4" onSubmit={handleSubmit}>
|
||||
|
||||
@@ -108,12 +108,12 @@ export function MobileApprovalsPage() {
|
||||
) : null}
|
||||
|
||||
<Card className="space-y-3 p-4" data-testid="mobile-approvals-list">
|
||||
<h3 className="text-base font-semibold text-slate-900">待办任务</h3>
|
||||
<h3 className="text-base font-semibold text-zinc-900">待办任务</h3>
|
||||
|
||||
{tasksQuery.isLoading ? <div className="flex items-center gap-2 text-sm text-slate-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}
|
||||
|
||||
{!tasksQuery.isLoading && pendingTasks.length === 0 ? (
|
||||
<p className="text-sm text-slate-500">暂无待办任务。</p>
|
||||
<p className="text-sm text-zinc-500">暂无待办任务。</p>
|
||||
) : null}
|
||||
|
||||
<div className="space-y-2">
|
||||
@@ -123,32 +123,32 @@ export function MobileApprovalsPage() {
|
||||
className={`w-full rounded-lg border px-3 py-2 text-left text-sm ${
|
||||
selectedTaskId === task.id
|
||||
? "border-primary bg-primary-50 text-primary"
|
||||
: "border-slate-200 bg-white text-slate-800"
|
||||
: "border-zinc-200 bg-white text-zinc-800"
|
||||
}`}
|
||||
data-testid={`mobile-approvals-task-${task.id}`}
|
||||
onClick={() => setSelectedTaskId(task.id)}
|
||||
type="button"
|
||||
>
|
||||
<div className="font-medium">{task.stepName}</div>
|
||||
<div className="text-xs text-slate-500">任务ID: {task.id}</div>
|
||||
<div className="text-xs text-slate-500">状态: {task.status}</div>
|
||||
<div className="text-xs text-zinc-500">任务ID: {task.id}</div>
|
||||
<div className="text-xs text-zinc-500">状态: {task.status}</div>
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
</Card>
|
||||
|
||||
<Card className="space-y-3 p-4" data-testid="mobile-approvals-detail">
|
||||
<h3 className="text-base font-semibold text-slate-900">任务详情与动作</h3>
|
||||
<h3 className="text-base font-semibold text-zinc-900">任务详情与动作</h3>
|
||||
|
||||
{!selectedTaskId ? <p className="text-sm text-slate-500">请先选择任务。</p> : null}
|
||||
{!selectedTaskId ? <p className="text-sm text-zinc-500">请先选择任务。</p> : null}
|
||||
|
||||
{selectedTaskId && detailQuery.isLoading ? (
|
||||
<p className="text-sm text-slate-500">加载任务详情中...</p>
|
||||
<p className="text-sm text-zinc-500">加载任务详情中...</p>
|
||||
) : null}
|
||||
|
||||
{selectedTask && (
|
||||
<>
|
||||
<div className="rounded-lg border border-slate-200 p-3 text-sm">
|
||||
<div className="rounded-lg border border-zinc-200 p-3 text-sm">
|
||||
<div>任务状态:{selectedTask.task.status}</div>
|
||||
<div>步骤:{selectedTask.task.stepName}</div>
|
||||
<div>报销单号:{selectedTask.reimbursement?.formNumber ?? "-"}</div>
|
||||
@@ -193,10 +193,10 @@ export function MobileApprovalsPage() {
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
<div className="space-y-2 rounded-lg border border-slate-200 p-3 text-sm">
|
||||
<p className="font-medium text-slate-800">转审</p>
|
||||
<div className="space-y-2 rounded-lg border border-zinc-200 p-3 text-sm">
|
||||
<p className="font-medium text-zinc-800">转审</p>
|
||||
<select
|
||||
className="h-10 w-full rounded-lg border border-slate-300 bg-white px-3 py-2 text-sm"
|
||||
className="h-10 w-full rounded-lg border border-zinc-300 bg-white px-3 py-2 text-sm"
|
||||
data-testid="mobile-approvals-transfer-to"
|
||||
onChange={(event) => setTransferToApproverId(event.target.value)}
|
||||
value={transferToApproverId}
|
||||
|
||||
@@ -142,7 +142,7 @@ export function MobileCapturePage() {
|
||||
|
||||
<Card className="space-y-3 p-4">
|
||||
<div className="flex items-center justify-between">
|
||||
<h3 className="text-base font-semibold text-slate-900">拍照/选择文件</h3>
|
||||
<h3 className="text-base font-semibold text-zinc-900">拍照/选择文件</h3>
|
||||
<span className={isOnline ? "text-emerald-600" : "text-orange-600"}>
|
||||
{isOnline ? "在线" : "离线"}
|
||||
</span>
|
||||
@@ -160,7 +160,7 @@ export function MobileCapturePage() {
|
||||
type="file"
|
||||
/>
|
||||
|
||||
<div className="text-sm text-slate-600">
|
||||
<div className="text-sm text-zinc-600">
|
||||
当前文件:{selectedFile ? `${selectedFile.name} (${Math.ceil(selectedFile.size / 1024)} KB)` : "未选择"}
|
||||
</div>
|
||||
|
||||
@@ -216,20 +216,20 @@ export function MobileCapturePage() {
|
||||
|
||||
<Card className="space-y-3 p-4" data-testid="mobile-capture-queue">
|
||||
<div className="flex items-center justify-between">
|
||||
<h3 className="text-base font-semibold text-slate-900">离线上传队列</h3>
|
||||
<span className="text-sm text-slate-500">{queuedCountLabel}</span>
|
||||
<h3 className="text-base font-semibold text-zinc-900">离线上传队列</h3>
|
||||
<span className="text-sm text-zinc-500">{queuedCountLabel}</span>
|
||||
</div>
|
||||
|
||||
{queueQuery.isLoading ? <div className="flex items-center gap-2 text-sm text-slate-500"><Spinner size="sm" />加载中...</div> : null}
|
||||
{queueQuery.isLoading ? <div className="flex items-center gap-2 text-sm text-zinc-500"><Spinner size="sm" />加载中...</div> : null}
|
||||
|
||||
{!queueQuery.isLoading && queuedItems.length === 0 ? (
|
||||
<p className="text-sm text-slate-500">暂无待上传文件。</p>
|
||||
<p className="text-sm text-zinc-500">暂无待上传文件。</p>
|
||||
) : null}
|
||||
|
||||
{queuedItems.map((item) => (
|
||||
<div key={item.id} className="rounded-lg border border-slate-200 px-3 py-2 text-sm">
|
||||
<div className="font-medium text-slate-800">{item.fileName}</div>
|
||||
<div className="text-slate-500">创建时间:{formatTime(item.createdAt)}</div>
|
||||
<div key={item.id} className="rounded-lg border border-zinc-200 px-3 py-2 text-sm">
|
||||
<div className="font-medium text-zinc-800">{item.fileName}</div>
|
||||
<div className="text-zinc-500">创建时间:{formatTime(item.createdAt)}</div>
|
||||
<div className="mt-2">
|
||||
<Button
|
||||
data-testid={`mobile-capture-remove-${item.id}`}
|
||||
|
||||
@@ -5,7 +5,7 @@ export function NotFoundPage() {
|
||||
<div className="flex min-h-screen items-center justify-center bg-background-light p-6">
|
||||
<div className="card-std max-w-lg text-center">
|
||||
<p className="text-sm font-semibold uppercase tracking-wide text-primary">404</p>
|
||||
<h1 className="mt-2 text-2xl font-bold text-slate-900">页面不存在</h1>
|
||||
<h1 className="mt-2 text-2xl font-bold text-zinc-900">页面不存在</h1>
|
||||
<p className="mt-2 text-sm text-secondary">请检查地址,或返回工作台首页继续操作。</p>
|
||||
<div className="mt-5 flex justify-center">
|
||||
<Link className="btn-primary" to="/dashboard">
|
||||
|
||||
@@ -102,7 +102,7 @@ export function NotificationsPage() {
|
||||
</TabsList>
|
||||
</Tabs>
|
||||
|
||||
<div className="text-sm text-slate-600">
|
||||
<div className="text-sm text-zinc-600">
|
||||
未读 {listQuery.data?.unreadCount ?? 0} 条
|
||||
</div>
|
||||
</div>
|
||||
@@ -122,7 +122,7 @@ export function NotificationsPage() {
|
||||
) : null}
|
||||
|
||||
{!listQuery.isLoading && !listQuery.isError ? (
|
||||
<div className="rounded-lg border border-slate-200">
|
||||
<div className="rounded-lg border border-zinc-200">
|
||||
<Table data-testid="notifications-table">
|
||||
<TableHeader>
|
||||
<tr>
|
||||
@@ -139,15 +139,15 @@ export function NotificationsPage() {
|
||||
return (
|
||||
<TableRow data-testid="notification-row" key={item.id}>
|
||||
<TableCell>
|
||||
<div className="font-medium text-slate-900">{item.title}</div>
|
||||
<div className="font-medium text-zinc-900">{item.title}</div>
|
||||
{item.body ? (
|
||||
<div className="mt-1 text-xs text-slate-500">{item.body}</div>
|
||||
<div className="mt-1 text-xs text-zinc-500">{item.body}</div>
|
||||
) : null}
|
||||
</TableCell>
|
||||
<TableCell className="text-xs text-slate-600">
|
||||
<TableCell className="text-xs text-zinc-600">
|
||||
{item.createdAt.slice(0, 19).replace("T", " ")}
|
||||
</TableCell>
|
||||
<TableCell className="text-xs text-slate-600">
|
||||
<TableCell className="text-xs text-zinc-600">
|
||||
{item.readAt ? "已读" : "未读"}
|
||||
</TableCell>
|
||||
<TableCell>
|
||||
@@ -177,7 +177,7 @@ export function NotificationsPage() {
|
||||
})
|
||||
) : (
|
||||
<TableRow>
|
||||
<TableCell className="py-3 text-slate-500" colSpan={4}>
|
||||
<TableCell className="py-3 text-zinc-500" colSpan={4}>
|
||||
暂无通知
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
|
||||
@@ -10,30 +10,30 @@ export function ProfilePage() {
|
||||
|
||||
<section className="card-std">
|
||||
<h3 className="card-title">个人信息</h3>
|
||||
<dl className="grid gap-3 text-sm text-slate-700 md:grid-cols-2">
|
||||
<dl className="grid gap-3 text-sm text-zinc-700 md:grid-cols-2">
|
||||
<div>
|
||||
<dt className="text-secondary">姓名</dt>
|
||||
<dd className="font-medium text-slate-900">{me?.name ?? "-"}</dd>
|
||||
<dd className="font-medium text-zinc-900">{me?.name ?? "-"}</dd>
|
||||
</div>
|
||||
<div>
|
||||
<dt className="text-secondary">员工号</dt>
|
||||
<dd className="font-medium text-slate-900">{me?.employeeNo ?? "-"}</dd>
|
||||
<dd className="font-medium text-zinc-900">{me?.employeeNo ?? "-"}</dd>
|
||||
</div>
|
||||
<div>
|
||||
<dt className="text-secondary">邮箱</dt>
|
||||
<dd className="font-medium text-slate-900">{me?.email ?? "-"}</dd>
|
||||
<dd className="font-medium text-zinc-900">{me?.email ?? "-"}</dd>
|
||||
</div>
|
||||
<div>
|
||||
<dt className="text-secondary">手机号</dt>
|
||||
<dd className="font-medium text-slate-900">{me?.phone ?? "-"}</dd>
|
||||
<dd className="font-medium text-zinc-900">{me?.phone ?? "-"}</dd>
|
||||
</div>
|
||||
<div>
|
||||
<dt className="text-secondary">部门ID</dt>
|
||||
<dd className="font-medium text-slate-900">{me?.departmentId ?? "-"}</dd>
|
||||
<dd className="font-medium text-zinc-900">{me?.departmentId ?? "-"}</dd>
|
||||
</div>
|
||||
<div>
|
||||
<dt className="text-secondary">直属上级ID</dt>
|
||||
<dd className="font-medium text-slate-900">{me?.directManagerId ?? "-"}</dd>
|
||||
<dd className="font-medium text-zinc-900">{me?.directManagerId ?? "-"}</dd>
|
||||
</div>
|
||||
</dl>
|
||||
</section>
|
||||
|
||||
@@ -292,7 +292,7 @@ export function ReimbursementDetailPage() {
|
||||
|
||||
{budgetHint ? (
|
||||
<Card className="space-y-2 p-6 text-sm" data-testid="reimbursement-budget-hint">
|
||||
<h3 className="text-base font-semibold text-slate-900">预算提示</h3>
|
||||
<h3 className="text-base font-semibold text-zinc-900">预算提示</h3>
|
||||
<div>预算池:{budgetHint.budgetCode} / {budgetHint.budgetName}</div>
|
||||
<div>
|
||||
本次提交后预计占用:{budgetHint.nextUsedAmount} / {budgetHint.allocatedAmount}
|
||||
@@ -308,7 +308,7 @@ export function ReimbursementDetailPage() {
|
||||
当前金额已达到预算预警阈值,提交后会记录预算预警审计。
|
||||
</p>
|
||||
) : (
|
||||
<p className="text-slate-500">当前预算充足,可继续提交。</p>
|
||||
<p className="text-zinc-500">当前预算充足,可继续提交。</p>
|
||||
)}
|
||||
</Card>
|
||||
) : null}
|
||||
@@ -316,8 +316,8 @@ export function ReimbursementDetailPage() {
|
||||
<Card className="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="space-y-1">
|
||||
<h3 className="text-base font-semibold text-slate-900">报销明细</h3>
|
||||
<p className="text-xs text-slate-500">明细变更会自动重算总金额。</p>
|
||||
<h3 className="text-base font-semibold text-zinc-900">报销明细</h3>
|
||||
<p className="text-xs text-zinc-500">明细变更会自动重算总金额。</p>
|
||||
</div>
|
||||
|
||||
{canEditItems ? (
|
||||
@@ -331,7 +331,7 @@ export function ReimbursementDetailPage() {
|
||||
新增明细
|
||||
</Button>
|
||||
) : (
|
||||
<p className="text-xs text-slate-500">当前状态不可编辑明细</p>
|
||||
<p className="text-xs text-zinc-500">当前状态不可编辑明细</p>
|
||||
)}
|
||||
</div>
|
||||
|
||||
@@ -344,7 +344,7 @@ export function ReimbursementDetailPage() {
|
||||
</p>
|
||||
) : null}
|
||||
|
||||
<div className="rounded-lg border border-slate-200">
|
||||
<div className="rounded-lg border border-zinc-200">
|
||||
<Table data-testid="reimbursement-items-table">
|
||||
<TableHeader>
|
||||
<tr>
|
||||
@@ -360,10 +360,10 @@ export function ReimbursementDetailPage() {
|
||||
detail.items.map((item) => (
|
||||
<TableRow data-testid="reimbursement-item-row" key={item.id}>
|
||||
<TableCell>
|
||||
<div className="font-medium text-slate-900">
|
||||
<div className="font-medium text-zinc-900">
|
||||
{item.expenseType?.name ?? item.expenseTypeId}
|
||||
</div>
|
||||
<div className="text-xs text-slate-500">{item.expenseType?.code ?? "-"}</div>
|
||||
<div className="text-xs text-zinc-500">{item.expenseType?.code ?? "-"}</div>
|
||||
</TableCell>
|
||||
<TableCell>{item.expenseDate ?? "-"}</TableCell>
|
||||
<TableCell>{item.amount}</TableCell>
|
||||
@@ -397,7 +397,7 @@ export function ReimbursementDetailPage() {
|
||||
))
|
||||
) : (
|
||||
<TableRow>
|
||||
<TableCell className="py-3 text-slate-500" colSpan={5}>
|
||||
<TableCell className="py-3 text-zinc-500" colSpan={5}>
|
||||
暂无明细
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
@@ -431,20 +431,20 @@ export function ReimbursementDetailPage() {
|
||||
/>
|
||||
|
||||
<Card className="space-y-2 p-6" data-testid="reimbursement-invoices">
|
||||
<h3 className="text-base font-semibold text-slate-900">关联票据</h3>
|
||||
<h3 className="text-base font-semibold text-zinc-900">关联票据</h3>
|
||||
{(detail.invoices ?? []).length ? (
|
||||
<div className="space-y-2 text-sm">
|
||||
{detail.invoices.map((item) => (
|
||||
<div className="rounded-lg border border-slate-200 px-3 py-2" key={item.id}>
|
||||
<div className="font-medium text-slate-900">{item.id}</div>
|
||||
<div className="text-slate-600">
|
||||
<div className="rounded-lg border border-zinc-200 px-3 py-2" key={item.id}>
|
||||
<div className="font-medium text-zinc-900">{item.id}</div>
|
||||
<div className="text-zinc-600">
|
||||
状态 {item.status} / 发票 {item.invoiceCode || "-"}-{item.invoiceNumber || "-"}
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
) : (
|
||||
<p className="text-sm text-slate-500">暂无关联票据</p>
|
||||
<p className="text-sm text-zinc-500">暂无关联票据</p>
|
||||
)}
|
||||
</Card>
|
||||
|
||||
|
||||
@@ -97,7 +97,7 @@ export function ReimbursementsPage() {
|
||||
<div className="flex flex-col gap-2 sm:flex-row sm:items-center">
|
||||
{showProxyOwnerSelector ? (
|
||||
<select
|
||||
className="h-11 rounded-lg border border-slate-300 bg-white px-3 py-2 text-sm"
|
||||
className="h-11 rounded-lg border border-zinc-300 bg-white px-3 py-2 text-sm"
|
||||
data-testid="reimbursements-owner-select"
|
||||
onChange={(event) => setProxyOwnerEmployeeId(event.target.value)}
|
||||
value={proxyOwnerEmployeeId}
|
||||
|
||||
@@ -189,7 +189,7 @@ export function UserManagementPage() {
|
||||
placeholder="搜索工号/姓名/手机号/邮箱"
|
||||
/>
|
||||
<select
|
||||
className="h-11 rounded-lg border border-slate-300 bg-white px-3 py-2 text-sm"
|
||||
className="h-11 rounded-lg border border-zinc-300 bg-white px-3 py-2 text-sm"
|
||||
data-testid="users-status-filter"
|
||||
value={status}
|
||||
onChange={(e) => {
|
||||
@@ -216,7 +216,7 @@ export function UserManagementPage() {
|
||||
</p>
|
||||
) : null}
|
||||
|
||||
<div className="rounded-lg border border-slate-200">
|
||||
<div className="rounded-lg border border-zinc-200">
|
||||
<Table data-testid="users-table">
|
||||
<TableHeader>
|
||||
<tr>
|
||||
@@ -232,14 +232,14 @@ export function UserManagementPage() {
|
||||
listQuery.data?.items.map((item) => (
|
||||
<TableRow data-testid="users-row" key={item.id}>
|
||||
<TableCell>
|
||||
<div className="font-medium text-slate-900">
|
||||
<div className="font-medium text-zinc-900">
|
||||
{item.employeeNo} / {item.name}
|
||||
</div>
|
||||
<div className="text-xs text-slate-500 font-mono">{item.id}</div>
|
||||
<div className="text-xs text-zinc-500 font-mono">{item.id}</div>
|
||||
</TableCell>
|
||||
<TableCell className="text-sm">
|
||||
<div>{item.email}</div>
|
||||
<div className="text-slate-500">{item.phone}</div>
|
||||
<div className="text-zinc-500">{item.phone}</div>
|
||||
</TableCell>
|
||||
<TableCell><StatusBadge status={item.status} /></TableCell>
|
||||
<TableCell className="text-xs">{formatRoleCodes(item.roleCodes)}</TableCell>
|
||||
@@ -283,7 +283,7 @@ export function UserManagementPage() {
|
||||
))
|
||||
) : (
|
||||
<TableRow>
|
||||
<TableCell colSpan={5} className="py-3 text-slate-500">
|
||||
<TableCell colSpan={5} className="py-3 text-zinc-500">
|
||||
暂无数据
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
|
||||
@@ -157,7 +157,7 @@ export function WorkflowTaskDetailPage() {
|
||||
</Card>
|
||||
|
||||
<Card className="space-y-4 p-6">
|
||||
<h3 className="text-base font-semibold text-slate-900">审批动作</h3>
|
||||
<h3 className="text-base font-semibold text-zinc-900">审批动作</h3>
|
||||
|
||||
{actionError ? (
|
||||
<p
|
||||
@@ -179,8 +179,8 @@ export function WorkflowTaskDetailPage() {
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
<div className="space-y-2 rounded-lg border border-slate-200 p-4">
|
||||
<p className="text-sm font-medium text-slate-800">驳回</p>
|
||||
<div className="space-y-2 rounded-lg border border-zinc-200 p-4">
|
||||
<p className="text-sm font-medium text-zinc-800">驳回</p>
|
||||
<Textarea
|
||||
data-testid="workflow-task-reject-comment"
|
||||
onChange={(event) => setRejectComment(event.target.value)}
|
||||
@@ -198,8 +198,8 @@ export function WorkflowTaskDetailPage() {
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
<div className="space-y-2 rounded-lg border border-slate-200 p-4">
|
||||
<p className="text-sm font-medium text-slate-800">退回</p>
|
||||
<div className="space-y-2 rounded-lg border border-zinc-200 p-4">
|
||||
<p className="text-sm font-medium text-zinc-800">退回</p>
|
||||
<Textarea
|
||||
data-testid="workflow-task-return-comment"
|
||||
onChange={(event) => setReturnComment(event.target.value)}
|
||||
@@ -217,10 +217,10 @@ export function WorkflowTaskDetailPage() {
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
<div className="space-y-2 rounded-lg border border-slate-200 p-4">
|
||||
<p className="text-sm font-medium text-slate-800">转审</p>
|
||||
<div className="space-y-2 rounded-lg border border-zinc-200 p-4">
|
||||
<p className="text-sm font-medium text-zinc-800">转审</p>
|
||||
<select
|
||||
className="h-11 w-full rounded-lg border border-slate-300 bg-white px-3 py-2 text-sm"
|
||||
className="h-11 w-full rounded-lg border border-zinc-300 bg-white px-3 py-2 text-sm"
|
||||
data-testid="workflow-task-transfer-to-approver"
|
||||
onChange={(event) => setSelectedApprover(event.target.value)}
|
||||
value={selectedApprover}
|
||||
@@ -256,20 +256,20 @@ export function WorkflowTaskDetailPage() {
|
||||
</Card>
|
||||
|
||||
<Card className="space-y-3 p-6">
|
||||
<h3 className="text-base font-semibold text-slate-900">处理历史</h3>
|
||||
<h3 className="text-base font-semibold text-zinc-900">处理历史</h3>
|
||||
<div className="space-y-2">
|
||||
{detail.history.length ? (
|
||||
detail.history.map((item) => (
|
||||
<div className="rounded-lg border border-slate-200 px-3 py-2 text-sm" key={item.id}>
|
||||
<div className="font-medium text-slate-900">
|
||||
<div className="rounded-lg border border-zinc-200 px-3 py-2 text-sm" key={item.id}>
|
||||
<div className="font-medium text-zinc-900">
|
||||
{item.action} · {item.actorName || item.actorId}
|
||||
</div>
|
||||
<div className="text-slate-600">{item.comment || "-"}</div>
|
||||
<div className="text-xs text-slate-500">{item.createdAt || "-"}</div>
|
||||
<div className="text-zinc-600">{item.comment || "-"}</div>
|
||||
<div className="text-xs text-zinc-500">{item.createdAt || "-"}</div>
|
||||
</div>
|
||||
))
|
||||
) : (
|
||||
<p className="text-sm text-slate-500">暂无历史记录</p>
|
||||
<p className="text-sm text-zinc-500">暂无历史记录</p>
|
||||
)}
|
||||
</div>
|
||||
</Card>
|
||||
|
||||
@@ -78,7 +78,7 @@ export function WorkflowTasksPage() {
|
||||
) : null}
|
||||
|
||||
{!query.isLoading && !query.isError ? (
|
||||
<div className="rounded-lg border border-slate-200">
|
||||
<div className="rounded-lg border border-zinc-200">
|
||||
<Table data-testid="workflow-tasks-table">
|
||||
<TableHeader>
|
||||
<tr>
|
||||
@@ -94,8 +94,8 @@ export function WorkflowTasksPage() {
|
||||
query.data?.items.map((task) => (
|
||||
<TableRow data-testid="workflow-task-row" key={task.id}>
|
||||
<TableCell>
|
||||
<div className="font-medium text-slate-900">{task.stepName}</div>
|
||||
<div className="text-xs text-slate-500">{task.id}</div>
|
||||
<div className="font-medium text-zinc-900">{task.stepName}</div>
|
||||
<div className="text-xs text-zinc-500">{task.id}</div>
|
||||
</TableCell>
|
||||
<TableCell className="font-mono text-xs">
|
||||
{task.workflowInstance?.entityId}
|
||||
@@ -116,7 +116,7 @@ export function WorkflowTasksPage() {
|
||||
))
|
||||
) : (
|
||||
<TableRow>
|
||||
<TableCell className="py-3 text-slate-500" colSpan={5}>
|
||||
<TableCell className="py-3 text-zinc-500" colSpan={5}>
|
||||
暂无数据
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
|
||||
@@ -39,7 +39,7 @@ export function ReimbursementActions({
|
||||
|
||||
return (
|
||||
<Card className="space-y-4 p-6">
|
||||
<h3 className="text-base font-semibold text-slate-900">可执行动作</h3>
|
||||
<h3 className="text-base font-semibold text-zinc-900">可执行动作</h3>
|
||||
|
||||
{actionError ? (
|
||||
<p
|
||||
@@ -61,8 +61,8 @@ export function ReimbursementActions({
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
<div className="space-y-2 rounded-lg border border-slate-200 p-4">
|
||||
<p className="text-sm font-medium text-slate-800">撤回报销</p>
|
||||
<div className="space-y-2 rounded-lg border border-zinc-200 p-4">
|
||||
<p className="text-sm font-medium text-zinc-800">撤回报销</p>
|
||||
<Textarea
|
||||
data-testid="reimbursement-withdraw-comment"
|
||||
onChange={(event) => setWithdrawComment(event.target.value)}
|
||||
@@ -81,8 +81,8 @@ export function ReimbursementActions({
|
||||
</div>
|
||||
|
||||
{canPay ? (
|
||||
<div className="space-y-2 rounded-lg border border-slate-200 p-4">
|
||||
<p className="text-sm font-medium text-slate-800">财务支付</p>
|
||||
<div className="space-y-2 rounded-lg border border-zinc-200 p-4">
|
||||
<p className="text-sm font-medium text-zinc-800">财务支付</p>
|
||||
<Input
|
||||
data-testid="reimbursement-pay-voucher-no"
|
||||
onChange={(event) => setPaymentVoucherNo(event.target.value)}
|
||||
@@ -96,7 +96,7 @@ export function ReimbursementActions({
|
||||
value={paymentDate}
|
||||
/>
|
||||
<select
|
||||
className="h-11 w-full rounded-lg border border-slate-300 bg-white px-3 py-2 text-sm"
|
||||
className="h-11 w-full rounded-lg border border-zinc-300 bg-white px-3 py-2 text-sm"
|
||||
data-testid="reimbursement-pay-channel"
|
||||
onChange={(event) =>
|
||||
setPaymentChannel(event.target.value as PaymentChannel)
|
||||
|
||||
@@ -59,13 +59,13 @@ export function ReimbursementItemEditor({
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="space-y-3 rounded-lg border border-slate-200 p-4" data-testid="reimbursement-item-editor">
|
||||
<p className="text-sm font-medium text-slate-800">
|
||||
<div className="space-y-3 rounded-lg border border-zinc-200 p-4" data-testid="reimbursement-item-editor">
|
||||
<p className="text-sm font-medium text-zinc-800">
|
||||
{mode === "create" ? "新增明细" : "编辑明细"}
|
||||
</p>
|
||||
|
||||
{expenseTypesLoading ? (
|
||||
<p className="text-sm text-slate-500">加载费用类型中...</p>
|
||||
<p className="text-sm text-zinc-500">加载费用类型中...</p>
|
||||
) : null}
|
||||
|
||||
{expenseTypesError ? (
|
||||
@@ -74,11 +74,11 @@ export function ReimbursementItemEditor({
|
||||
|
||||
<div className="grid gap-3 md:grid-cols-4">
|
||||
<div className="space-y-1">
|
||||
<label className="text-xs text-slate-600" htmlFor="expenseTypeId">
|
||||
<label className="text-xs text-zinc-600" htmlFor="expenseTypeId">
|
||||
费用类型
|
||||
</label>
|
||||
<select
|
||||
className="h-11 w-full rounded-lg border border-slate-300 bg-white px-3 py-2 text-sm"
|
||||
className="h-11 w-full rounded-lg border border-zinc-300 bg-white px-3 py-2 text-sm"
|
||||
data-testid="reimbursement-item-expense-type"
|
||||
disabled={expenseTypes.length === 0 || isPending}
|
||||
id="expenseTypeId"
|
||||
@@ -95,7 +95,7 @@ export function ReimbursementItemEditor({
|
||||
</div>
|
||||
|
||||
<div className="space-y-1">
|
||||
<label className="text-xs text-slate-600" htmlFor="expenseDate">
|
||||
<label className="text-xs text-zinc-600" htmlFor="expenseDate">
|
||||
发生日期
|
||||
</label>
|
||||
<Input
|
||||
@@ -109,7 +109,7 @@ export function ReimbursementItemEditor({
|
||||
</div>
|
||||
|
||||
<div className="space-y-1">
|
||||
<label className="text-xs text-slate-600" htmlFor="amount">
|
||||
<label className="text-xs text-zinc-600" htmlFor="amount">
|
||||
金额
|
||||
</label>
|
||||
<Input
|
||||
@@ -123,7 +123,7 @@ export function ReimbursementItemEditor({
|
||||
</div>
|
||||
|
||||
<div className="space-y-1 md:col-span-4">
|
||||
<label className="text-xs text-slate-600" htmlFor="itemDescription">
|
||||
<label className="text-xs text-zinc-600" htmlFor="itemDescription">
|
||||
说明
|
||||
</label>
|
||||
<Textarea
|
||||
|
||||
@@ -167,7 +167,7 @@ export function CreateUserDialog({
|
||||
<select
|
||||
id="departmentId"
|
||||
data-testid="users-create-department"
|
||||
className="h-11 w-full rounded-lg border border-slate-300 bg-white px-3 py-2 text-sm"
|
||||
className="h-11 w-full rounded-lg border border-zinc-300 bg-white px-3 py-2 text-sm"
|
||||
value={departmentId}
|
||||
onChange={(event) => setDepartmentId(event.target.value)}
|
||||
>
|
||||
@@ -187,7 +187,7 @@ export function CreateUserDialog({
|
||||
<select
|
||||
id="directManagerId"
|
||||
data-testid="users-create-direct-manager"
|
||||
className="h-11 w-full rounded-lg border border-slate-300 bg-white px-3 py-2 text-sm"
|
||||
className="h-11 w-full rounded-lg border border-zinc-300 bg-white px-3 py-2 text-sm"
|
||||
value={directManagerId}
|
||||
onChange={(event) => setDirectManagerId(event.target.value)}
|
||||
>
|
||||
|
||||
在新工单中引用
屏蔽一个用户