refactor: mock清理+安全配置+通用组件+菜单优化

P0安全: SECRET_KEY/ADMIN_PASSWORD/CORS环境变量化, seed仅admin

P1Mock: mock.ts 301->88行, 删8数据mock+5generateMock, net-1300行

P2组件: useToast/useFetchList+ConfirmDialog/FormDialog

P3菜单: menuSections/menuItems->constants/menu.ts

P4API: NotificationsPage/WorkflowPage重写+RolesPage演示

测试: 1050passed, tsc clean
This commit is contained in:
logan committed 2026-05-24 02:42:29 +08:00
1 parent ec3a03cf38
commit 8b921c6c83
33 files changed
+1734 -2006

No files matched your search

+59
View File
@@ -0,0 +1,59 @@
import {
Dialog,
DialogContent,
DialogTitle,
DialogDescription,
} from '@/components/ui/dialog';
import { Button } from '@/components/ui/button';
interface Props {
open: boolean;
onConfirm: () => void;
onCancel: () => void;
title?: string;
description?: string;
confirmText?: string;
cancelText?: string;
variant?: 'danger' | 'default';
}
export function ConfirmDialog({
open,
onConfirm,
onCancel,
title = '确认操作',
description,
confirmText = '确认',
cancelText = '取消',
variant = 'default',
}: Props) {
return (
<Dialog open={open} onOpenChange={(v) => { if (!v) onCancel(); }}>
<DialogContent
showCloseButton={false}
className="border-0 sm:max-w-[400px]"
onPointerDownOutside={(e) => e.preventDefault()}
onInteractOutside={(e) => e.preventDefault()}
>
<DialogTitle className="text-base">{title}</DialogTitle>
{description && (
<DialogDescription className="text-sm text-[var(--dg-text-secondary)]">
{description}
</DialogDescription>
)}
<div className="flex justify-end gap-2 pt-2">
<Button variant="ghost" size="sm" onClick={onCancel}>
{cancelText}
</Button>
<Button
size="sm"
variant={variant === 'danger' ? 'destructive' : 'default'}
onClick={onConfirm}
>
{confirmText}
</Button>
</div>
</DialogContent>
</Dialog>
);
}
+53
View File
@@ -0,0 +1,53 @@
import {
Dialog,
DialogContent,
DialogTitle,
} from '@/components/ui/dialog';
import { XIcon } from 'lucide-react';
import type { ReactNode } from 'react';
interface Props {
open: boolean;
onClose: () => void;
title: string;
children: ReactNode;
width?: string;
footer?: ReactNode;
}
export function FormDialog({
open,
onClose,
title,
children,
width = 'max-w-lg',
footer,
}: Props) {
return (
<Dialog open={open} onOpenChange={(v) => { if (!v) onClose(); }}>
<DialogContent
showCloseButton={false}
className={`border-0 ${width} flex max-h-[85vh] flex-col`}
onPointerDownOutside={(e) => e.preventDefault()}
onInteractOutside={(e) => e.preventDefault()}
>
{/* Header */}
<div className="flex items-center justify-between">
<DialogTitle className="text-base font-semibold">{title}</DialogTitle>
<button
onClick={onClose}
className="rounded-xs p-1 text-[var(--dg-text-muted)] transition-opacity hover:opacity-70"
>
<XIcon className="h-4 w-4" />
</button>
</div>
{/* Scrollable content */}
<div className="flex-1 overflow-y-auto pr-1">{children}</div>
{/* Optional footer */}
{footer && <div className="flex justify-end gap-2 pt-2">{footer}</div>}
</DialogContent>
</Dialog>
);
}
+1 -1
View File
@@ -4,7 +4,7 @@ import {
ClipboardList, Search, UserCog, Users, Lock, Shield, GitBranch, Bell,
LogOut, Settings, Clock, Megaphone, Sun, Moon, BookOpen
} from 'lucide-react';
import { menuSections } from '@/lib/mock';
import { menuSections } from '@/constants/menu';
import { useAuth } from '@/hooks/useAuth';
import { useTheme } from '@/hooks/useTheme';