修改前端

This commit is contained in:
2026-03-27 01:54:55 +08:00
parent 4e178477fe
commit 091c9db0da
3 changed files with 783 additions and 215 deletions

View File

@@ -1,72 +1,95 @@
import React, { useEffect, useState } from 'react';
import { Card, CardContent, CardHeader, CardTitle, CardDescription } from '@/components/ui/card';
import { useNavigate, Link } from 'react-router-dom';
import { Link } from 'react-router-dom';
import { Button } from '@/components/ui/button';
import {
FileText,
TableProperties,
MessageSquareCode,
TrendingUp,
Clock,
CheckCircle2,
import {
FileText,
TableProperties,
MessageSquareCode,
TrendingUp,
Clock,
CheckCircle2,
ArrowRight,
UploadCloud,
Layers,
Sparkles
Sparkles,
Database,
FileSpreadsheet,
RefreshCcw
} from 'lucide-react';
import { useAuth } from '@/context/AuthContext';
import { documentApi, taskApi } from '@/db/api';
import { backendApi } from '@/db/backend-api';
import { formatDistanceToNow } from 'date-fns';
import { zhCN } from 'date-fns/locale';
import { cn } from '@/lib/utils';
type Document = any;
type FillTask = any;
type DocumentItem = {
doc_id: string;
filename: string;
original_filename: string;
doc_type: string;
file_size: number;
created_at: string;
metadata?: {
row_count?: number;
column_count?: number;
columns?: string[];
};
};
type TaskItem = {
task_id: string;
status: string;
created_at: string;
message?: string;
};
const Dashboard: React.FC = () => {
const navigate = useNavigate();
const { profile } = useAuth();
const [stats, setStats] = useState({ docs: 0, entities: 0, tasks: 0 });
const [recentDocs, setRecentDocs] = useState<Document[]>([]);
const [recentTasks, setRecentTasks] = useState<any[]>([]);
const [stats, setStats] = useState({ docs: 0, excelFiles: 0, tasks: 0 });
const [recentDocs, setRecentDocs] = useState<DocumentItem[]>([]);
const [recentTasks, setRecentTasks] = useState<TaskItem[]>([]);
const [loading, setLoading] = useState(true);
useEffect(() => {
if (!profile) return;
const loadData = async () => {
try {
const docs = await documentApi.listDocuments((profile as any).id);
const tasks = await taskApi.listTasks((profile as any).id);
setRecentDocs(docs.slice(0, 5));
setRecentTasks(tasks.slice(0, 5));
let entityCount = 0;
docs.forEach(d => {
if (d.extracted_entities) entityCount += (d.extracted_entities as any[]).length;
});
const loadData = async () => {
setLoading(true);
try {
// 获取文档列表
const docsResult = await backendApi.getDocuments(undefined, 50);
if (docsResult.success && docsResult.documents) {
setRecentDocs(docsResult.documents.slice(0, 5));
// 分类统计
const docxMdTxt = docsResult.documents.filter((d: DocumentItem) =>
['docx', 'md', 'txt'].includes(d.doc_type)
).length;
const xlsx = docsResult.documents.filter((d: DocumentItem) =>
d.doc_type === 'xlsx'
).length;
setStats({
docs: docs.length,
entities: entityCount,
tasks: tasks.length
docs: docxMdTxt,
excelFiles: xlsx,
tasks: 0 // TODO: 后端任务接口
});
} catch (err) {
console.error(err);
} finally {
setLoading(false);
}
};
} catch (err) {
console.error('加载数据失败:', err);
} finally {
setLoading(false);
}
};
useEffect(() => {
loadData();
}, [profile]);
}, []);
return (
<div className="space-y-8 animate-fade-in">
<section className="flex flex-col md:flex-row md:items-center justify-between gap-4">
<div className="space-y-1">
<h1 className="text-3xl font-extrabold tracking-tight">
, <span className="text-primary">{((profile as any)?.email)?.split('@')[0] || '用户'}</span> 👋
使 <span className="text-primary"></span> 👋
</h1>
<p className="text-muted-foreground">使</p>
<p className="text-muted-foreground"></p>
</div>
<div className="flex items-center gap-3">
<Button variant="outline" className="rounded-xl" asChild>
@@ -84,9 +107,9 @@ const Dashboard: React.FC = () => {
{/* Stats Grid */}
<div className="grid grid-cols-1 md:grid-cols-3 gap-6">
{[
{ label: '已上传文档', value: stats.docs, icon: FileText, color: 'bg-blue-500', trend: '+12% 较上周' },
{ label: '提取实体', value: stats.entities, icon: Layers, color: 'bg-indigo-500', trend: '+25% 较上周' },
{ label: '生成表格', value: stats.tasks, icon: TableProperties, color: 'bg-emerald-500', trend: '+8% 较上周' }
{ label: '已上传文档', value: stats.docs, icon: FileText, color: 'bg-blue-500', trend: '非结构化文档', link: '/documents' },
{ label: 'Excel 文件', value: stats.excelFiles, icon: FileSpreadsheet, color: 'bg-emerald-500', trend: '结构化数据', link: '/excel-parse' },
{ label: '填表任务', value: stats.tasks, icon: TableProperties, color: 'bg-indigo-500', trend: '待实现', link: '/form-fill' }
].map((stat, i) => (
<Card key={i} className="border-none shadow-md overflow-hidden group hover:shadow-xl transition-all duration-300">
<CardContent className="p-0">
@@ -94,8 +117,7 @@ const Dashboard: React.FC = () => {
<div className="space-y-2">
<p className="text-sm font-medium text-muted-foreground">{stat.label}</p>
<p className="text-3xl font-bold tracking-tight">{stat.value}</p>
<div className="flex items-center gap-1 text-xs text-emerald-500 font-medium bg-emerald-500/10 px-2 py-1 rounded-full w-fit">
<TrendingUp size={12} />
<div className="flex items-center gap-1 text-xs text-muted-foreground bg-muted px-2 py-1 rounded-full w-fit">
<span>{stat.trend}</span>
</div>
</div>
@@ -118,7 +140,7 @@ const Dashboard: React.FC = () => {
<Clock className="text-primary" size={20} />
</CardTitle>
<CardDescription></CardDescription>
<CardDescription></CardDescription>
</div>
<Button variant="ghost" size="sm" asChild className="text-primary hover:text-primary/80 hover:bg-primary/5">
<Link to="/documents"> <ArrowRight size={14} className="ml-1" /></Link>
@@ -132,21 +154,18 @@ const Dashboard: React.FC = () => {
) : recentDocs.length > 0 ? (
<div className="space-y-3">
{recentDocs.map(doc => (
<div key={doc.id} className="flex items-center gap-4 p-3 rounded-xl border border-transparent hover:border-border hover:bg-muted/30 transition-all group">
<div key={doc.doc_id} className="flex items-center gap-4 p-3 rounded-xl border border-transparent hover:border-border hover:bg-muted/30 transition-all group">
<div className="w-10 h-10 rounded-lg bg-blue-500/10 text-blue-500 flex items-center justify-center">
<FileText size={20} />
</div>
<div className="flex-1 min-w-0">
<p className="font-semibold text-sm truncate">{doc.name}</p>
<p className="font-semibold text-sm truncate">{doc.original_filename}</p>
<p className="text-xs text-muted-foreground">
{formatDistanceToNow(new Date(doc.created_at!), { addSuffix: true, locale: zhCN })}
{doc.doc_type.toUpperCase()} {formatDistanceToNow(new Date(doc.created_at), { addSuffix: true, locale: zhCN })}
</p>
</div>
<div className={cn(
"px-2 py-1 rounded-full text-[10px] font-bold uppercase tracking-wider",
doc.status === 'completed' ? "bg-emerald-500/10 text-emerald-500" : "bg-amber-500/10 text-amber-500"
)}>
{doc.status === 'completed' ? '已解析' : '处理中'}
<div className="px-2 py-1 rounded-full text-[10px] font-bold uppercase tracking-wider bg-muted">
{doc.doc_type}
</div>
</div>
))}
@@ -163,58 +182,81 @@ const Dashboard: React.FC = () => {
</CardContent>
</Card>
{/* Recent Tasks */}
{/* Quick Actions */}
<Card className="border-none shadow-md">
<CardHeader className="flex flex-row items-center justify-between pb-2">
<div className="space-y-1">
<CardTitle className="text-xl flex items-center gap-2">
<CheckCircle2 className="text-primary" size={20} />
<Sparkles className="text-primary" size={20} />
</CardTitle>
<CardDescription></CardDescription>
<CardDescription>使</CardDescription>
</div>
<Button variant="ghost" size="sm" asChild className="text-primary hover:text-primary/80 hover:bg-primary/5">
<Link to="/form-fill"> <ArrowRight size={14} className="ml-1" /></Link>
</Button>
</CardHeader>
<CardContent>
{loading ? (
<div className="space-y-4 py-4">
{[1, 2, 3].map(i => <div key={i} className="h-12 bg-muted rounded-xl animate-pulse" />)}
</div>
) : recentTasks.length > 0 ? (
<div className="space-y-3">
{recentTasks.map(task => (
<div key={task.id} className="flex items-center gap-4 p-3 rounded-xl border border-transparent hover:border-border hover:bg-muted/30 transition-all">
<div className="w-10 h-10 rounded-lg bg-emerald-500/10 text-emerald-500 flex items-center justify-center">
<TableProperties size={20} />
</div>
<div className="flex-1 min-w-0">
<p className="font-semibold text-sm truncate">{task.templates?.name || '未知模板'}</p>
<p className="text-xs text-muted-foreground">
{task.document_ids?.length || 0} {formatDistanceToNow(new Date(task.created_at!), { addSuffix: true, locale: zhCN })}
</p>
</div>
<Button variant="ghost" size="icon" className="text-primary h-8 w-8" onClick={() => navigate('/form-fill')}>
<ArrowRight size={16} />
</Button>
<div className="grid grid-cols-1 sm:grid-cols-2 gap-4">
{[
{ title: '上传文档', desc: '支持 docx/md/txt', icon: FileText, link: '/documents', color: 'bg-blue-500' },
{ title: '解析 Excel', desc: '上传并分析数据', icon: FileSpreadsheet, link: '/excel-parse', color: 'bg-emerald-500' },
{ title: '智能填表', desc: '自动填写表格模板', icon: TableProperties, link: '/form-fill', color: 'bg-indigo-500' },
{ title: 'AI 助手', desc: '自然语言交互', icon: MessageSquareCode, link: '/assistant', color: 'bg-amber-500' }
].map((item, i) => (
<Link
key={i}
to={item.link}
className="flex items-center gap-4 p-4 rounded-2xl border border-transparent hover:border-border hover:bg-muted/30 transition-all group"
>
<div className={cn("w-12 h-12 rounded-xl flex items-center justify-center text-white shadow-lg", item.color)}>
<item.icon size={24} />
</div>
))}
</div>
) : (
<div className="flex flex-col items-center justify-center py-10 text-center space-y-3">
<MessageSquareCode size={48} className="text-muted-foreground/30" />
<p className="text-muted-foreground italic"></p>
<Button variant="outline" size="sm" asChild className="rounded-xl">
<Link to="/form-fill"></Link>
</Button>
</div>
)}
<div>
<p className="font-semibold group-hover:text-primary transition-colors">{item.title}</p>
<p className="text-xs text-muted-foreground">{item.desc}</p>
</div>
<ArrowRight size={16} className="ml-auto opacity-0 group-hover:opacity-100 transition-opacity" />
</Link>
))}
</div>
</CardContent>
</Card>
</div>
{/* System Status */}
<Card className="border-none shadow-md">
<CardHeader className="pb-2">
<CardTitle className="text-xl flex items-center gap-2">
<Database className="text-primary" size={20} />
</CardTitle>
</CardHeader>
<CardContent>
<div className="grid grid-cols-1 md:grid-cols-3 gap-4">
<div className="flex items-center gap-3 p-4 rounded-xl bg-muted/30">
<div className="w-3 h-3 rounded-full bg-emerald-500" />
<div>
<p className="font-semibold text-sm">MySQL</p>
<p className="text-xs text-muted-foreground"></p>
</div>
</div>
<div className="flex items-center gap-3 p-4 rounded-xl bg-muted/30">
<div className="w-3 h-3 rounded-full bg-emerald-500" />
<div>
<p className="font-semibold text-sm">MongoDB</p>
<p className="text-xs text-muted-foreground"></p>
</div>
</div>
<div className="flex items-center gap-3 p-4 rounded-xl bg-muted/30">
<div className="w-3 h-3 rounded-full bg-emerald-500" />
<div>
<p className="font-semibold text-sm">Faiss + RAG</p>
<p className="text-xs text-muted-foreground"></p>
</div>
</div>
</div>
</CardContent>
</Card>
</div>
);
};
export default Dashboard;
export default Dashboard;