266 lines
8.3 KiB
TypeScript
266 lines
8.3 KiB
TypeScript
// import React from 'react';
|
||
import { type MetaFunction } from "@remix-run/node";
|
||
import { useLoaderData } from "@remix-run/react";
|
||
import { Card } from "~/components/ui/Card";
|
||
import { Button } from "~/components/ui/Button";
|
||
import { StatusBadge, links as statusBadgeLinks } from "~/components/ui/StatusBadge";
|
||
import { FileTag, links as fileTagLinks } from "~/components/ui/FileTag";
|
||
import { FileTypeTag, links as fileTypeTagLinks } from "~/components/ui/FileTypeTag";
|
||
import homeStyles from "~/styles/pages/home.css?url";
|
||
|
||
export const links = () => [
|
||
{ rel: "stylesheet", href: homeStyles },
|
||
...statusBadgeLinks(),
|
||
...fileTagLinks(),
|
||
...fileTypeTagLinks()
|
||
];
|
||
|
||
export const meta: MetaFunction = () => {
|
||
return [
|
||
{ title: "中国烟草AI合同及卷宗审核系统 - 首页" },
|
||
{ name: "description", content: "AI审核系统首页" }
|
||
];
|
||
};
|
||
|
||
// API 响应的类型定义
|
||
interface StatsData {
|
||
totalFiles: number;
|
||
reviewedFiles: number;
|
||
pendingFiles: number;
|
||
passRate: number;
|
||
}
|
||
|
||
interface RecentFile {
|
||
id: string;
|
||
name: string;
|
||
type: string;
|
||
reviewStatus: string;
|
||
updatedAt: string;
|
||
}
|
||
|
||
// interface LoaderData {
|
||
// stats: StatsData;
|
||
// recentFiles: RecentFile[];
|
||
// }
|
||
|
||
|
||
// 模拟数据,实际项目中应该从API获取
|
||
export async function loader() {
|
||
try {
|
||
// 实际项目中这里应该是 API 调用
|
||
// const response = await fetch('/api/dashboard/stats');
|
||
// const stats: StatsData = await response.json();
|
||
|
||
// const filesResponse = await fetch('/api/files/recent');
|
||
// const recentFiles: RecentFile[] = await filesResponse.json();
|
||
|
||
// 模拟数据
|
||
const stats = {
|
||
totalFiles: 156,
|
||
reviewedFiles: 124,
|
||
pendingFiles: 32,
|
||
passRate: 92.5
|
||
} as StatsData;
|
||
|
||
const recentFiles = [
|
||
{
|
||
id: "1",
|
||
name: "2023年度烟草专卖零售许可证.pdf",
|
||
type: "专卖许可证",
|
||
reviewStatus: "pass",
|
||
updatedAt: "2023-12-24 14:30"
|
||
},
|
||
{
|
||
id: "2",
|
||
name: "烟草制品购销合同(2023-12).docx",
|
||
type: "合同文档",
|
||
reviewStatus: "warning",
|
||
updatedAt: "2023-12-23 09:15"
|
||
},
|
||
{
|
||
id: "3",
|
||
name: "专卖管理处罚决定书(2023-145).pdf",
|
||
type: "行政处罚决定书",
|
||
reviewStatus: "fail",
|
||
updatedAt: "2023-12-22 16:45"
|
||
},
|
||
{
|
||
id: "4",
|
||
name: "2023年第四季度采购合同.docx",
|
||
type: "合同文档",
|
||
reviewStatus: "pass",
|
||
updatedAt: "2023-12-20 11:20"
|
||
},
|
||
{
|
||
id: "5",
|
||
name: "广告宣传协议书.pdf",
|
||
type: "合同文档",
|
||
reviewStatus: "pass",
|
||
updatedAt: "2023-12-18 15:30"
|
||
}
|
||
] as RecentFile[];
|
||
|
||
return Response.json({ stats, recentFiles });
|
||
} catch (error) {
|
||
// 错误处理
|
||
console.error('Failed to fetch dashboard data:', error);
|
||
return Response.json(
|
||
{ error: '获取数据失败,请稍后重试' },
|
||
{ status: 500 }
|
||
);
|
||
}
|
||
}
|
||
|
||
export default function Index() {
|
||
const { stats, recentFiles } = useLoaderData<typeof loader>();
|
||
|
||
return (
|
||
<div className="dashboard-container">
|
||
{/* 页面标识 */}
|
||
<div className="mb-4 p-3 bg-yellow-100 border border-yellow-300 rounded text-yellow-800">
|
||
<h3 className="font-bold text-lg">当前页面: 首页 (_index.tsx)</h3>
|
||
<p>如果你看到这个提示,说明你正在浏览首页,而不是评查点列表页面。</p>
|
||
<div className="mt-2">
|
||
<a href="/debug" className="text-blue-600 hover:underline">查看路由诊断页面</a> |
|
||
<a href="/rules" className="ml-2 text-blue-600 hover:underline">通过原生链接访问评查点列表</a>
|
||
</div>
|
||
</div>
|
||
|
||
{/* 统计卡片区域 */}
|
||
<Card title="统计信息" icon="ri-bar-chart-line" className="mt-6 transition-all duration-200 hover:shadow-[0_4px_15px_rgba(0,0,0,0.1)]">
|
||
<div className="stat-grid ">
|
||
<StatCard
|
||
title="总文件数"
|
||
value={stats.totalFiles}
|
||
icon="ri-file-list-3-line"
|
||
/>
|
||
<StatCard
|
||
title="已审核"
|
||
value={stats.reviewedFiles}
|
||
icon="ri-check-double-line"
|
||
trend={{ value: 5.2, isUp: true }}
|
||
/>
|
||
<StatCard
|
||
title="待审核"
|
||
value={stats.pendingFiles}
|
||
icon="ri-time-line"
|
||
trend={{ value: 2.1, isUp: false }}
|
||
/>
|
||
<StatCard
|
||
title="通过率"
|
||
value={`${stats.passRate}%`}
|
||
icon="ri-pie-chart-line"
|
||
trend={{ value: 1.5, isUp: true }}
|
||
/>
|
||
</div>
|
||
</Card>
|
||
|
||
{/* 快捷访问区域 */}
|
||
<Card title="快捷访问" icon="ri-speed-line" className="mt-6 transition-all duration-200 hover:shadow-[0_4px_15px_rgba(0,0,0,0.1)]">
|
||
<div className="shortcut-grid">
|
||
<ShortcutItem icon="ri-upload-cloud-line" label="上传文件" to="/files/new" />
|
||
<ShortcutItem icon="ri-file-list-3-line" label="文件列表" to="/files" />
|
||
<ShortcutItem icon="ri-list-check-2" label="评查点管理" to="/rules" />
|
||
<ShortcutItem icon="ri-folder-open-line" label="评查点分组" to="/rule-groups" />
|
||
<ShortcutItem icon="ri-file-chart-line" label="评查详情" to="/reviews" />
|
||
<ShortcutItem icon="ri-file-list-line" label="文档类型" to="/doc-types" />
|
||
<ShortcutItem icon="ri-settings-3-line" label="系统设置" to="/settings" />
|
||
<ShortcutItem icon="ri-chat-1-line" label="提示词管理" to="/prompts" />
|
||
</div>
|
||
</Card>
|
||
|
||
{/* 最近文档区域 */}
|
||
<Card
|
||
title="最近文档"
|
||
icon="ri-file-list-3-line"
|
||
extra={<Button to="/files" size="small">查看全部</Button>}
|
||
className="mt-6"
|
||
>
|
||
<div className="doc-list">
|
||
{recentFiles.map((file: RecentFile) => (
|
||
<div key={file.id} className="doc-item">
|
||
<div className="doc-info">
|
||
<FileTag
|
||
extension={file.name.endsWith('.pdf') ? 'pdf' : 'docx'}
|
||
showIcon={true}
|
||
showText={false}
|
||
showBackground={false}
|
||
size="lg"
|
||
className="mr-2"
|
||
/>
|
||
<div>
|
||
<div className="doc-name">{file.name}</div>
|
||
<div className="doc-meta">
|
||
<FileTypeTag
|
||
type={file.type === "合同文档" ? "sales-contract" :
|
||
file.type === "专卖许可证" ? "license" :
|
||
file.type === "行政处罚决定书" ? "punishment" : "agreement"}
|
||
text={file.type}
|
||
size="sm"
|
||
showIcon={false}
|
||
className="mr-2"
|
||
/>
|
||
<span className="text-gray-500">·</span>
|
||
<span className="ml-2 text-gray-500">{file.updatedAt}</span>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
<div className="doc-status">
|
||
<StatusBadge status={file.reviewStatus} />
|
||
</div>
|
||
</div>
|
||
))}
|
||
</div>
|
||
</Card>
|
||
</div>
|
||
);
|
||
}
|
||
|
||
// 统计卡片组件
|
||
interface StatCardProps {
|
||
title: string;
|
||
value: number | string;
|
||
icon: string;
|
||
trend?: {
|
||
value: number;
|
||
isUp: boolean;
|
||
};
|
||
}
|
||
|
||
function StatCard({ title, value, icon, trend }: StatCardProps) {
|
||
return (
|
||
<div className="stat-card">
|
||
<div className="stat-title">{title}</div>
|
||
<div className="stat-value">{value}</div>
|
||
{trend && (
|
||
<div className={`stat-trend ${trend.isUp ? 'trend-up' : 'trend-down'}`}>
|
||
<i className={`mr-1 ${trend.isUp ? 'ri-arrow-up-s-line' : 'ri-arrow-down-s-line'}`}></i>
|
||
<span>{trend.value}%</span>
|
||
<span className="ml-1 text-gray-500">较上月</span>
|
||
</div>
|
||
)}
|
||
<i className={`${icon} stat-icon`}></i>
|
||
</div>
|
||
);
|
||
}
|
||
|
||
// 快捷方式组件
|
||
interface ShortcutItemProps {
|
||
icon: string;
|
||
label: string;
|
||
to: string;
|
||
}
|
||
|
||
function ShortcutItem({ icon, label, to }: ShortcutItemProps) {
|
||
return (
|
||
<Button
|
||
to={to}
|
||
type="default"
|
||
className="shortcut-item"
|
||
>
|
||
<i className={`${icon} shortcut-icon text-2xl`}></i>
|
||
<span className="shortcut-label">{label}</span>
|
||
</Button>
|
||
);
|
||
}
|