33b4c734aa
后端: - 新增认证(auth)、任务(tasks)、映射(mappings)、对账(reconciliation)、异常(exceptions)、导出(exports) API - 新增核心模块: database, security, permissions, tenant, exceptions, error_handlers - 新增数据模型: user, company, reconciliation_task, field_mapping, uploaded_file 等 - 新增服务层: ai_recognizer, file_parser, file_storage, mapping, reconciliation 等 - 添加数据库迁移脚本 前端: - 新增登录页面和仪表盘页面 - 新增任务列表、任务详情、字段映射页面 - 新增异常处理页面和规则设置页面 - 新增 API 代理路由 /api/[...path] - 新增 UI 组件库 (button, card, dialog, input, table 等) - 新增 auth 组件 (ProtectedRoute, PermissionGate) - 新增 layout 组件 (Header, Sidebar) - 新增 mapping 组件 (FieldMappingTable, AISuggestionPanel) - 新增 API 客户端和 hooks (useAsync, useToast, usePermission 等) - 新增状态管理 (auth-store, company-store, ui-store) - 集成 Tailwind CSS 和 shadcn/ui 组件库 其他: - 添加 Alembic 数据库迁移配置 - 添加初始化示例数据脚本 - 更新项目文档
213 lines
7.6 KiB
TypeScript
213 lines
7.6 KiB
TypeScript
"use client";
|
|
|
|
import { motion } from "motion/react";
|
|
import { AlertTriangle, CheckCircle, Info, Sparkles } from "lucide-react";
|
|
import { Card, CardContent } from "@/components/ui/card";
|
|
import { Button } from "@/components/ui/button";
|
|
import { cn } from "@/lib/utils";
|
|
|
|
interface AISuggestionPanelProps {
|
|
totalFields: number;
|
|
highConfidence: number;
|
|
mediumConfidence: number;
|
|
lowConfidence: number;
|
|
confirmedCount: number;
|
|
onAutoConfirm?: () => void;
|
|
className?: string;
|
|
}
|
|
|
|
const config = {
|
|
success: {
|
|
bg: "bg-emerald-50 dark:bg-emerald-950/30",
|
|
border: "border-emerald-200 dark:border-emerald-800",
|
|
iconBg: "bg-emerald-100 dark:bg-emerald-900/50",
|
|
iconColor: "text-emerald-600 dark:text-emerald-400",
|
|
titleColor: "text-emerald-800 dark:text-emerald-300",
|
|
descColor: "text-emerald-700 dark:text-emerald-400",
|
|
buttonBg: "bg-emerald-600 hover:bg-emerald-700",
|
|
progressBg: "bg-emerald-500",
|
|
},
|
|
warning: {
|
|
bg: "bg-amber-50 dark:bg-amber-950/30",
|
|
border: "border-amber-200 dark:border-amber-800",
|
|
iconBg: "bg-amber-100 dark:bg-amber-900/50",
|
|
iconColor: "text-amber-600 dark:text-amber-400",
|
|
titleColor: "text-amber-800 dark:text-amber-300",
|
|
descColor: "text-amber-700 dark:text-amber-400",
|
|
buttonBg: "bg-amber-600 hover:bg-amber-700",
|
|
progressBg: "bg-amber-500",
|
|
},
|
|
info: {
|
|
bg: "bg-blue-50 dark:bg-blue-950/30",
|
|
border: "border-blue-200 dark:border-blue-800",
|
|
iconBg: "bg-blue-100 dark:bg-blue-900/50",
|
|
iconColor: "text-blue-600 dark:text-blue-400",
|
|
titleColor: "text-blue-800 dark:text-blue-300",
|
|
descColor: "text-blue-700 dark:text-blue-400",
|
|
buttonBg: "bg-blue-600 hover:bg-blue-700",
|
|
progressBg: "bg-blue-500",
|
|
},
|
|
};
|
|
|
|
export function AISuggestionPanel({
|
|
totalFields,
|
|
highConfidence,
|
|
mediumConfidence,
|
|
lowConfidence,
|
|
confirmedCount,
|
|
onAutoConfirm,
|
|
className,
|
|
}: AISuggestionPanelProps) {
|
|
const pendingCount = totalFields - confirmedCount;
|
|
const allConfirmed = pendingCount === 0;
|
|
const progressPercent = totalFields > 0 ? (confirmedCount / totalFields) * 100 : 0;
|
|
|
|
const getSuggestion = () => {
|
|
if (allConfirmed) {
|
|
return {
|
|
type: "success" as const,
|
|
icon: CheckCircle,
|
|
title: "所有字段已确认",
|
|
description: "字段映射已完成,可以进入下一步",
|
|
action: null,
|
|
};
|
|
}
|
|
|
|
if (lowConfidence > 0) {
|
|
return {
|
|
type: "warning" as const,
|
|
icon: AlertTriangle,
|
|
title: "建议检查低置信度字段",
|
|
description: `有 ${lowConfidence} 个字段置信度较低(<70%),建议人工确认`,
|
|
action: "检查低置信度字段",
|
|
};
|
|
}
|
|
|
|
if (mediumConfidence > 0) {
|
|
return {
|
|
type: "info" as const,
|
|
icon: Info,
|
|
title: "存在中等置信度字段",
|
|
description: `有 ${mediumConfidence} 个字段置信度为中等(70%-90%),建议确认`,
|
|
action: "批量确认",
|
|
};
|
|
}
|
|
|
|
return {
|
|
type: "success" as const,
|
|
icon: Sparkles,
|
|
title: "可直接确认",
|
|
description: "所有字段置信度较高,可以批量确认",
|
|
action: "批量确认",
|
|
};
|
|
};
|
|
|
|
const suggestion = getSuggestion();
|
|
const Icon = suggestion.icon;
|
|
const style = config[suggestion.type];
|
|
|
|
return (
|
|
<motion.div
|
|
initial={{ opacity: 0, scale: 0.98 }}
|
|
animate={{ opacity: 1, scale: 1 }}
|
|
transition={{ duration: 0.3, ease: [0.16, 1, 0.3, 1] as const }}
|
|
>
|
|
<Card className={cn(style.bg, style.border, className)}>
|
|
<CardContent className="p-5">
|
|
<div className="flex items-start gap-4">
|
|
{/* Icon */}
|
|
<motion.div
|
|
initial={{ scale: 0 }}
|
|
animate={{ scale: 1 }}
|
|
transition={{ delay: 0.1, type: "spring", stiffness: 200 }}
|
|
className={cn("p-2.5 rounded-xl", style.iconBg)}
|
|
>
|
|
<Icon className={cn("w-5 h-5", style.iconColor)} />
|
|
</motion.div>
|
|
|
|
{/* Content */}
|
|
<div className="flex-1 min-w-0">
|
|
<div className="flex items-start justify-between gap-4">
|
|
<div>
|
|
<h3 className={cn("font-semibold text-base", style.titleColor)}>
|
|
{suggestion.title}
|
|
</h3>
|
|
<p className={cn("text-sm mt-1", style.descColor)}>
|
|
{suggestion.description}
|
|
</p>
|
|
|
|
{suggestion.action && !allConfirmed && onAutoConfirm && (
|
|
<Button
|
|
onClick={onAutoConfirm}
|
|
size="sm"
|
|
className={cn("mt-3 text-white btn-press", style.buttonBg)}
|
|
>
|
|
{suggestion.action}
|
|
</Button>
|
|
)}
|
|
</div>
|
|
|
|
{/* Progress Stats */}
|
|
<div className="text-right shrink-0">
|
|
<motion.div
|
|
initial={{ opacity: 0, y: -4 }}
|
|
animate={{ opacity: 1, y: 0 }}
|
|
transition={{ delay: 0.2 }}
|
|
className="text-3xl font-bold text-foreground"
|
|
>
|
|
{confirmedCount}
|
|
<span className="text-lg text-muted-foreground">/{totalFields}</span>
|
|
</motion.div>
|
|
<p className="text-caption text-muted-foreground">已确认</p>
|
|
</div>
|
|
</div>
|
|
|
|
{/* Progress Bar */}
|
|
<div className="mt-5">
|
|
<div className="flex justify-between text-caption text-muted-foreground mb-1.5">
|
|
<span>确认进度</span>
|
|
<span>{Math.round(progressPercent)}%</span>
|
|
</div>
|
|
<div className="h-2 bg-muted rounded-full overflow-hidden">
|
|
<motion.div
|
|
initial={{ width: 0 }}
|
|
animate={{ width: `${progressPercent}%` }}
|
|
transition={{ duration: 0.6, delay: 0.3, ease: [0.16, 1, 0.3, 1] as const }}
|
|
className={cn("h-full rounded-full", style.progressBg)}
|
|
/>
|
|
</div>
|
|
</div>
|
|
|
|
{/* Confidence Distribution */}
|
|
<div className="mt-5 pt-4 border-t border-border/50">
|
|
<p className="text-caption text-muted-foreground mb-3">置信度分布</p>
|
|
<div className="flex flex-wrap gap-4">
|
|
<div className="flex items-center gap-2">
|
|
<span className="w-2.5 h-2.5 rounded-full bg-emerald-500" />
|
|
<span className="text-sm text-muted-foreground">
|
|
高置信度: <span className="font-semibold text-foreground">{highConfidence}</span>
|
|
</span>
|
|
</div>
|
|
<div className="flex items-center gap-2">
|
|
<span className="w-2.5 h-2.5 rounded-full bg-amber-500" />
|
|
<span className="text-sm text-muted-foreground">
|
|
中置信度: <span className="font-semibold text-foreground">{mediumConfidence}</span>
|
|
</span>
|
|
</div>
|
|
<div className="flex items-center gap-2">
|
|
<span className="w-2.5 h-2.5 rounded-full bg-red-500" />
|
|
<span className="text-sm text-muted-foreground">
|
|
低置信度: <span className="font-semibold text-foreground">{lowConfidence}</span>
|
|
</span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</CardContent>
|
|
</Card>
|
|
</motion.div>
|
|
);
|
|
}
|
|
|
|
export default AISuggestionPanel; |