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