完成智慧法务前端调整20250522,还有登录和主页需要完善
This commit is contained in:
+12
-4
@@ -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,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";
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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"}
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
}
|
||||
@@ -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
@@ -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
|
||||
|
||||
@@ -485,6 +485,7 @@ export default function RulesFiles() {
|
||||
onStartDateChange={(value) => handleDateChange('dateFrom', value)}
|
||||
onEndDateChange={(value) => handleDateChange('dateTo', value)}
|
||||
simple={true}
|
||||
colorMode="light"
|
||||
/>
|
||||
|
||||
<FilterSelect
|
||||
|
||||
Reference in New Issue
Block a user