重新构建路由和配置样式文件

This commit is contained in:
2025-03-26 10:04:27 +08:00
parent a42a9990bf
commit 97ccf5a077
141 changed files with 88034 additions and 179 deletions
+267 -125
View File
@@ -1,138 +1,280 @@
import type { MetaFunction } from "@remix-run/node";
// 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";
export const links = () => [
{ rel: "stylesheet", href: "/app/styles/index.css" }
];
export const meta: MetaFunction = () => {
return [
{ title: "New Remix App" },
{ name: "description", content: "Welcome to Remix!" },
{ 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="flex h-screen items-center justify-center">
<div className="flex flex-col items-center gap-16">
<header className="flex flex-col items-center gap-9">
<h1 className="leading text-2xl font-bold text-gray-800 dark:text-gray-100">
Welcome to <span className="sr-only">Remix</span>
</h1>
<div className="h-[144px] w-[434px]">
<img
src="/logo-light.png"
alt="Remix"
className="block w-full dark:hidden"
/>
<img
src="/logo-dark.png"
alt="Remix"
className="hidden w-full dark:block"
/>
</div>
</header>
<nav className="flex flex-col items-center justify-center gap-4 rounded-3xl border border-gray-200 p-6 dark:border-gray-700">
<p className="leading-6 text-gray-700 dark:text-gray-200">
What&apos;s next?
</p>
<ul>
{resources.map(({ href, text, icon }) => (
<li key={href}>
<a
className="group flex items-center gap-3 self-stretch p-3 leading-normal text-blue-700 hover:underline dark:text-blue-500"
href={href}
target="_blank"
rel="noreferrer"
>
{icon}
{text}
</a>
</li>
))}
</ul>
</nav>
<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">
<i className={`doc-icon ${file.name.endsWith('.pdf') ? 'ri-file-pdf-line' : 'ri-file-word-line'}`}></i>
<div>
<div className="doc-name">{file.name}</div>
<div className="doc-meta">
{file.type} · {file.updatedAt}
</div>
</div>
</div>
<div className="doc-status">
<StatusBadge status={file.reviewStatus} />
</div>
</div>
))}
</div>
</Card>
</div>
);
}
const resources = [
{
href: "https://remix.run/start/quickstart",
text: "Quick Start (5 min)",
icon: (
<svg
xmlns="http://www.w3.org/2000/svg"
width="24"
height="20"
viewBox="0 0 20 20"
fill="none"
className="stroke-gray-600 group-hover:stroke-current dark:stroke-gray-300"
>
<path
d="M8.51851 12.0741L7.92592 18L15.6296 9.7037L11.4815 7.33333L12.0741 2L4.37036 10.2963L8.51851 12.0741Z"
strokeWidth="1.5"
strokeLinecap="round"
strokeLinejoin="round"
/>
</svg>
),
},
{
href: "https://remix.run/start/tutorial",
text: "Tutorial (30 min)",
icon: (
<svg
xmlns="http://www.w3.org/2000/svg"
width="24"
height="20"
viewBox="0 0 20 20"
fill="none"
className="stroke-gray-600 group-hover:stroke-current dark:stroke-gray-300"
>
<path
d="M4.561 12.749L3.15503 14.1549M3.00811 8.99944H1.01978M3.15503 3.84489L4.561 5.2508M8.3107 1.70923L8.3107 3.69749M13.4655 3.84489L12.0595 5.2508M18.1868 17.0974L16.635 18.6491C16.4636 18.8205 16.1858 18.8205 16.0144 18.6491L13.568 16.2028C13.383 16.0178 13.0784 16.0347 12.915 16.239L11.2697 18.2956C11.047 18.5739 10.6029 18.4847 10.505 18.142L7.85215 8.85711C7.75756 8.52603 8.06365 8.21994 8.39472 8.31453L17.6796 10.9673C18.0223 11.0653 18.1115 11.5094 17.8332 11.7321L15.7766 13.3773C15.5723 13.5408 15.5554 13.8454 15.7404 14.0304L18.1868 16.4767C18.3582 16.6481 18.3582 16.926 18.1868 17.0974Z"
strokeWidth="1.5"
strokeLinecap="round"
strokeLinejoin="round"
/>
</svg>
),
},
{
href: "https://remix.run/docs",
text: "Remix Docs",
icon: (
<svg
xmlns="http://www.w3.org/2000/svg"
width="24"
height="20"
viewBox="0 0 20 20"
fill="none"
className="stroke-gray-600 group-hover:stroke-current dark:stroke-gray-300"
>
<path
d="M9.99981 10.0751V9.99992M17.4688 17.4688C15.889 19.0485 11.2645 16.9853 7.13958 12.8604C3.01467 8.73546 0.951405 4.11091 2.53116 2.53116C4.11091 0.951405 8.73546 3.01467 12.8604 7.13958C16.9853 11.2645 19.0485 15.889 17.4688 17.4688ZM2.53132 17.4688C0.951566 15.8891 3.01483 11.2645 7.13974 7.13963C11.2647 3.01471 15.8892 0.951453 17.469 2.53121C19.0487 4.11096 16.9854 8.73551 12.8605 12.8604C8.73562 16.9853 4.11107 19.0486 2.53132 17.4688Z"
strokeWidth="1.5"
strokeLinecap="round"
/>
</svg>
),
},
{
href: "https://rmx.as/discord",
text: "Join Discord",
icon: (
<svg
xmlns="http://www.w3.org/2000/svg"
width="24"
height="20"
viewBox="0 0 24 20"
fill="none"
className="stroke-gray-600 group-hover:stroke-current dark:stroke-gray-300"
>
<path
d="M15.0686 1.25995L14.5477 1.17423L14.2913 1.63578C14.1754 1.84439 14.0545 2.08275 13.9422 2.31963C12.6461 2.16488 11.3406 2.16505 10.0445 2.32014C9.92822 2.08178 9.80478 1.84975 9.67412 1.62413L9.41449 1.17584L8.90333 1.25995C7.33547 1.51794 5.80717 1.99419 4.37748 2.66939L4.19 2.75793L4.07461 2.93019C1.23864 7.16437 0.46302 11.3053 0.838165 15.3924L0.868838 15.7266L1.13844 15.9264C2.81818 17.1714 4.68053 18.1233 6.68582 18.719L7.18892 18.8684L7.50166 18.4469C7.96179 17.8268 8.36504 17.1824 8.709 16.4944L8.71099 16.4904C10.8645 17.0471 13.128 17.0485 15.2821 16.4947C15.6261 17.1826 16.0293 17.8269 16.4892 18.4469L16.805 18.8725L17.3116 18.717C19.3056 18.105 21.1876 17.1751 22.8559 15.9238L23.1224 15.724L23.1528 15.3923C23.5873 10.6524 22.3579 6.53306 19.8947 2.90714L19.7759 2.73227L19.5833 2.64518C18.1437 1.99439 16.6386 1.51826 15.0686 1.25995ZM16.6074 10.7755L16.6074 10.7756C16.5934 11.6409 16.0212 12.1444 15.4783 12.1444C14.9297 12.1444 14.3493 11.6173 14.3493 10.7877C14.3493 9.94885 14.9378 9.41192 15.4783 9.41192C16.0471 9.41192 16.6209 9.93851 16.6074 10.7755ZM8.49373 12.1444C7.94513 12.1444 7.36471 11.6173 7.36471 10.7877C7.36471 9.94885 7.95323 9.41192 8.49373 9.41192C9.06038 9.41192 9.63892 9.93712 9.6417 10.7815C9.62517 11.6239 9.05462 12.1444 8.49373 12.1444Z"
strokeWidth="1.5"
/>
</svg>
),
},
];
// 统计卡片组件
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>
);
}
// 状态标签组件
interface StatusBadgeProps {
status: string;
}
function StatusBadge({ status }: StatusBadgeProps) {
const statusMap: Record<string, { label: string, className: string, icon: string }> = {
pass: {
label: '通过',
className: 'status-badge status-success',
icon: 'ri-checkbox-circle-line'
},
warning: {
label: '警告',
className: 'status-badge status-warning',
icon: 'ri-error-warning-line'
},
fail: {
label: '不通过',
className: 'status-badge status-error',
icon: 'ri-close-circle-line'
},
pending: {
label: '待确认',
className: 'status-badge',
icon: 'ri-time-line'
}
};
const { label, className, icon } = statusMap[status] || statusMap.pending;
return (
<span className={className}>
<i className={`${icon} mr-1`}></i>
{label}
</span>
);
}