完成智慧法务前端调整20250522,还有登录和主页需要完善

This commit is contained in:
2025-05-27 23:48:28 +08:00
parent 742a789244
commit 690d369f57
30 changed files with 1557 additions and 292 deletions
+12 -4
View File
@@ -1,16 +1,17 @@
// import React from 'react';
import { type MetaFunction } from "@remix-run/node";
import { type MetaFunction, type LoaderFunctionArgs, redirect } from "@remix-run/node";
import { useLoaderData } from "@remix-run/react";
import { Card } from "~/components/ui/Card";
import { Button } from "~/components/ui/Button";
import { FileTag, links as fileTagLinks } from "~/components/ui/FileTag";
// import { FileTypeTag, links as fileTypeTagLinks } from "~/components/ui/FileTypeTag";
import { Tag } from "~/components/ui/Tag";
import homeStyles from "~/styles/pages/home.css?url";
import homeStyles from "~/styles/pages/sys_overview.css?url";
import { getDocuments, type DocumentUI } from "~/api/files/documents";
import { useState, useEffect } from "react";
import { getHomeData } from "~/api/home/home";
import dayjs from 'dayjs';
import { getUserSession } from "~/root";
// 文件处理状态选项
const fileProcessingStatusOptions = [
@@ -41,8 +42,15 @@ export const meta: MetaFunction = () => {
// passRate: number;
// }
// 模拟数据,实际项目中应该从API获取
export async function loader() {
// 添加认证检查
export async function loader({ request }: LoaderFunctionArgs) {
// 检查用户登录状态
// const { isAuthenticated } = await getUserSession(request);
// if (!isAuthenticated) {
// return redirect("/login");
// }
try {
const documentSearchParams = {
page: 1,
+1 -1
View File
@@ -1,4 +1,4 @@
import { json, type MetaFunction, type LoaderFunctionArgs, type ActionFunctionArgs } from "@remix-run/node";
import { type MetaFunction, type LoaderFunctionArgs, type ActionFunctionArgs } from "@remix-run/node";
import { useLoaderData, useSearchParams, useFetcher, Link } from "@remix-run/react";
import { useState, useEffect } from "react";
import { Button } from "~/components/ui/Button";
+5 -1
View File
@@ -75,7 +75,8 @@ export const loader = async ({ request }: LoaderFunctionArgs) => {
value: type.id,
label: type.name
}));
// console.log('typesResponse-----',JSON.stringify(documentsResponse.data?.documents[1],null,2));
return Response.json({
documents: documentsResponse.data?.documents || [],
total: documentsResponse.data?.total || 0,
@@ -643,10 +644,12 @@ export default function DocumentsIndex() {
<div className="mt-2 flex inline-block">
<FileTypeTag
type={record.type}
typeName={record.typeName}
text={record.typeName}
size="sm"
showIcon={false}
fileType={record.fileType}
colorMode="light"
/>
{record.isTest && (
<span className="ml-2 text-xs bg-gray-100 text-gray-500 px-1 rounded"></span>
@@ -878,6 +881,7 @@ export default function DocumentsIndex() {
onStartDateChange={(value) => handleDateChange('dateFrom', value)}
onEndDateChange={(value) => handleDateChange('dateTo', value)}
simple={true}
colorMode="light"
/>
</div>
</FilterPanel>
+1 -1
View File
@@ -1527,7 +1527,7 @@ export default function FilesUpload() {
id="docNumber"
name="docNumber"
className="form-input w-full"
placeholder="请输入合同编号、许可证号等"
placeholder="请输入卷宗编号、合同编号等"
value={documentNumber}
onChange={(e) => setDocumentNumber(e.target.value)}
disabled={uploadStage !== "idle"}
+180
View File
@@ -0,0 +1,180 @@
import { useState, useEffect } from 'react';
import { useNavigate, Form } from '@remix-run/react';
import { type MetaFunction, type ActionFunctionArgs, LoaderFunctionArgs, redirect, json } from "@remix-run/node";
import styles from "~/styles/pages/home.css?url";
import { getUserSession, logout } from "~/root";
export const links = () => [
{ rel: "stylesheet", href: styles }
];
export const meta: MetaFunction = () => {
return [
{ title: "中国烟草AI合同及卷宗审核系统 - 首页" },
{ name: "description", content: "中国烟草AI合同及卷宗审核系统首页" },
];
};
// 处理登出请求
export async function action({ request }: ActionFunctionArgs) {
const formData = await request.formData();
const intent = formData.get("intent");
if (intent === "logout") {
return logout(request);
}
return null;
}
// 验证用户登录状态
export async function loader({ request }: LoaderFunctionArgs) {
const { isAuthenticated } = await getUserSession(request);
if (!isAuthenticated) {
return redirect("/login");
}
return json({ isAuthenticated });
}
export default function Home() {
const navigate = useNavigate();
const [currentTime, setCurrentTime] = useState('');
const [currentDate, setCurrentDate] = useState('');
// 更新日期时间
useEffect(() => {
const updateDateTime = () => {
const now = new Date();
// 格式化日期: YYYY/MM/DD
const date = now.toLocaleDateString('zh-CN', {
year: 'numeric',
month: '2-digit',
day: '2-digit'
}).replace(/\//g, '/');
// 格式化时间: HH:MM
const time = now.toLocaleTimeString('zh-CN', {
hour: '2-digit',
minute: '2-digit',
hour12: false
});
setCurrentDate(date);
setCurrentTime(time);
};
// 初始化时间
updateDateTime();
// 每分钟更新一次
const interval = setInterval(updateDateTime, 60000);
return () => clearInterval(interval);
}, []);
// 处理模块点击
const handleModuleClick = (path: string) => {
navigate(path);
};
// 处理键盘事件
const handleKeyDown = (path: string, e: React.KeyboardEvent<HTMLDivElement>) => {
if (e.key === 'Enter' || e.key === ' ') {
handleModuleClick(path);
}
};
// 处理登出
const handleLogout = () => {
// 使用Form组件提交登出请求
const form = document.getElementById('logout-form') as HTMLFormElement;
if (form) {
form.submit();
}
};
return (
<div className="home-page">
{/* 登出表单 - 隐藏 */}
<Form method="post" id="logout-form" className="hidden">
<input type="hidden" name="intent" value="logout" />
</Form>
{/* 头部 */}
<header className="header">
<div className="logo-container">
<img src="/logo.png" alt="中国烟草" className="logo" />
<span className="logo-text"></span>
<span className="logo-text-en">CHINA TOBACCO</span>
</div>
<div className="user-info">
<span className="datetime">{currentDate} {currentTime}</span>
<div className="user">
<img src="/avatar.png" alt="用户头像" className="avatar" />
<span className="username"></span>
<button
onClick={handleLogout}
className="logout-button"
aria-label="登出"
>
<i className="ri-logout-box-line"></i>
</button>
</div>
</div>
</header>
{/* 主要内容 */}
<main className="main-content">
<h1 className="welcome-text">- -</h1>
<div className="modules-container">
{/* 合同管理模块 */}
<div
className="module-card"
onClick={() => handleModuleClick('/documents')}
onKeyDown={(e) => handleKeyDown('/documents', e)}
role="button"
tabIndex={0}
aria-label="合同管理"
>
<div className="module-icon contract-icon"></div>
<span className="module-name"></span>
</div>
{/* 案卷智能评查模块 */}
<div
className="module-card"
onClick={() => handleModuleClick('/')}
onKeyDown={(e) => handleKeyDown('/', e)}
role="button"
tabIndex={0}
aria-label="案卷智能评查"
>
<div className="module-icon review-icon"></div>
<span className="module-name"></span>
</div>
{/* 智慧法务大模型模块 */}
<div
className="module-card"
onClick={() => handleModuleClick('/prompts')}
onKeyDown={(e) => handleKeyDown('/prompts', e)}
role="button"
tabIndex={0}
aria-label="智慧法务大模型"
>
<div className="module-icon ai-icon"></div>
<span className="module-name"></span>
</div>
</div>
</main>
{/* 底部山水背景 */}
<footer className="footer">
<div className="mountains-bg"></div>
</footer>
</div>
);
}
+120
View File
@@ -0,0 +1,120 @@
import { useState } from "react";
import { Form, useActionData, useNavigation } from "@remix-run/react";
import { type MetaFunction, type ActionFunctionArgs, redirect, json, type LoaderFunctionArgs } from "@remix-run/node";
import styles from "~/styles/pages/login.css?url";
import { createUserSession, getUserSession, getSession } from "~/root";
export const links = () => [
{ rel: "stylesheet", href: styles }
];
export const meta: MetaFunction = () => {
return [
{ title: "中国烟草AI合同及卷宗审核系统 - 登录" },
{ name: "description", content: "中国烟草AI合同及卷宗审核系统登录页面" },
];
};
// 处理表单提交的action
export async function action({ request }: ActionFunctionArgs) {
const formData = await request.formData();
const username = formData.get("username") as string;
const password = formData.get("password") as string;
// 简单的登录验证,实际应用中应该进行真正的身份验证
if (!username || !password) {
return json({ error: "用户名和密码不能为空" });
}
// 在实际应用中,这里应该是对用户名和密码的验证逻辑
// 简化起见,我们直接视为登录成功
// 获取session中存储的重定向URL,如果没有则默认到/home
const session = await getSession(request);
const redirectTo = session.get("redirectTo") || "/home";
// 创建登录会话并重定向
return createUserSession(true, redirectTo);
}
// 加载器,获取当前会话状态
export async function loader({ request }: LoaderFunctionArgs) {
const { isAuthenticated } = await getUserSession(request);
// 如果已登录,重定向到首页
if (isAuthenticated) {
return redirect("/home");
}
return Response.json({ isAuthenticated });
}
export default function Login() {
const [username, setUsername] = useState("");
const [password, setPassword] = useState("");
const actionData = useActionData<typeof action>();
const navigation = useNavigation();
// 判断是否正在提交表单
const isSubmitting = navigation.state === "submitting";
return (
<div className="login-page">
<div className="login-container">
<div className="login-header">
<img src="/logo.png" alt="中国烟草" className="login-logo" />
<h1 className="login-title">AI合同及卷宗审核系统</h1>
</div>
<div className="login-form-container">
<h2 className="login-subtitle"></h2>
<Form method="post" className="login-form">
{actionData?.error && (
<div className="error-message">{actionData.error}</div>
)}
<div className="form-group">
<label htmlFor="username"></label>
<input
type="text"
id="username"
name="username"
value={username}
onChange={(e) => setUsername(e.target.value)}
className="form-input"
placeholder="请输入用户名"
required
/>
</div>
<div className="form-group">
<label htmlFor="password"></label>
<input
type="password"
id="password"
name="password"
value={password}
onChange={(e) => setPassword(e.target.value)}
className="form-input"
placeholder="请输入密码"
required
/>
</div>
<button
type="submit"
className="login-button"
disabled={isSubmitting}
>
{isSubmitting ? "登录中..." : "登录"}
</button>
</Form>
</div>
<div className="login-footer">
<p>© 2024 </p>
</div>
</div>
</div>
);
}
+41 -3
View File
@@ -194,7 +194,7 @@ export async function loader({ request }: LoaderFunctionArgs) {
// 确保reviewData有效且具有预期的属性
if ('document' in reviewData && 'data' in reviewData && 'reviewInfo' in reviewData && 'stats' in reviewData) {
console.log("reviewData-------",JSON.stringify(reviewData.document?.type,null,2));
// console.log("reviewData-------",JSON.stringify(reviewData.document?.type,null,2));
return Response.json({
previousRoute: previousRoute,
document: reviewData.document,
@@ -517,7 +517,7 @@ export default function ReviewDetails() {
};
return (
<div className="container">
<div className="review-container">
{isLoading ? (
<div className="flex justify-center items-center p-12">
<div className="loading-spinner"></div>
@@ -584,7 +584,8 @@ export default function ReviewDetails() {
fileInfo={{
previousRoute: loaderData.previousRoute,
path: document?.path,
auditStatus: document?.auditStatus
auditStatus: document?.auditStatus,
type: document?.type
}}
onConfirmResults={handleConfirmResults}
>
@@ -614,6 +615,43 @@ export default function ReviewDetails() {
</div>
)}
{/* 结构比对选项卡内容 */}
{activeTab === 'filecompare' && (
<div className="flex flex-col lg:flex-row space-y-4 lg:space-y-0 lg:space-x-4">
{/* 左侧:原文件预览 */}
<div className="w-full lg:w-[38%]">
<FilePreview
fileContent={document}
reviewPoints={reviewData.reviewPoints}
activeReviewPointResultId={activeReviewPointResultId}
targetPage={targetPage}
/>
</div>
{/* 中间:附件文件预览 */}
<div className="w-full lg:w-[38%]">
<FilePreview
fileContent={document}
reviewPoints={[]}
activeReviewPointResultId={activeReviewPointResultId}
targetPage={targetPage}
isStructuredView={true}
/>
</div>
{/* 右侧:评查结果 */}
<div className="w-full lg:w-[24%]">
<ReviewPointsList
reviewPoints={reviewData.reviewPoints}
statistics={reviewData.statistics}
activeReviewPointResultId={activeReviewPointResultId}
onReviewPointSelect={handleReviewPointSelect}
onStatusChange={handleReviewPointStatusChange}
/>
</div>
</div>
)}
{/* AI智能分析选项卡内容 */}
{activeTab === 'analysis' && (
<AIAnalysis
+1
View File
@@ -485,6 +485,7 @@ export default function RulesFiles() {
onStartDateChange={(value) => handleDateChange('dateFrom', value)}
onEndDateChange={(value) => handleDateChange('dateTo', value)}
simple={true}
colorMode="light"
/>
<FilterSelect