Merge branch 'shiy' into awen

This commit is contained in:
2025-04-16 18:50:30 +08:00
18 changed files with 382 additions and 306 deletions
-131
View File
@@ -1,131 +0,0 @@
/**
* API基础服务
*/
// 默认API基础URL
const API_BASE_URL = process.env.API_BASE_URL || 'http://localhost:8000';
/**
* 通用API响应格式
*/
export interface ApiResponse<T> {
status: 'success' | 'error';
data?: T;
message?: string;
error?: {
code: string;
details?: any;
};
}
/**
* 分页请求参数
*/
export interface PaginationParams {
page: number;
pageSize: number;
}
/**
* 分页响应数据
*/
export interface PaginatedResponse<T> {
items: T[];
totalItems: number;
totalPages: number;
currentPage: number;
pageSize: number;
}
/**
* 统一错误处理
*/
export class ApiError extends Error {
statusCode: number;
errorCode: string;
details?: any;
constructor(message: string, statusCode: number, errorCode: string, details?: any) {
super(message);
this.name = 'ApiError';
this.statusCode = statusCode;
this.errorCode = errorCode;
this.details = details;
}
}
/**
* 构建请求URL
*/
export function buildUrl(path: string, params?: Record<string, any>): string {
// 确保API_BASE_URL末尾没有斜杠,而path开头有斜杠
const baseUrl = API_BASE_URL.endsWith('/') ? API_BASE_URL.slice(0, -1) : API_BASE_URL;
const normalizedPath = path.startsWith('/') ? path : `/${path}`;
try {
const url = new URL(`${baseUrl}${normalizedPath}`);
if (params) {
Object.entries(params).forEach(([key, value]) => {
if (value !== undefined && value !== null) {
url.searchParams.append(key, String(value));
}
});
}
return url.toString();
} catch (error) {
console.error('URL构建错误:', error);
throw new Error(`无法构建URL: ${baseUrl}${normalizedPath}, 错误: ${error}`);
}
}
/**
* 通用API请求函数
*/
export async function apiRequest<T>(
url: string,
method: 'GET' | 'POST' | 'PUT' | 'DELETE' = 'GET',
body?: any,
headers?: Record<string, string>
): Promise<T> {
const requestInit: RequestInit = {
method,
headers: {
'Content-Type': 'application/json',
'Accept': 'application/json',
...headers
}
};
if (body) {
requestInit.body = JSON.stringify(body);
}
const response = await fetch(url, requestInit);
// 获取响应数据
const data = await response.json() as ApiResponse<T>;
// 检查状态码
if (!response.ok) {
throw new ApiError(
data.message || '请求失败',
response.status,
data.error?.code || 'UNKNOWN_ERROR',
data.error?.details
);
}
// 检查业务状态
if (data.status === 'error') {
throw new ApiError(
data.message || '请求失败',
response.status,
data.error?.code || 'BUSINESS_ERROR',
data.error?.details
);
}
return data.data as T;
}
+41 -3
View File
@@ -57,6 +57,7 @@ interface EvaluationPoint {
suggestion_message_type?: string;
suggestion_message?: string;
score?: number;
updated_at?: string;
[key: string]: unknown;
}
@@ -92,7 +93,7 @@ interface StatsData {
* 获取当前评查文件的所有评查点结果
* @param fileId 评查文件ID
* @returns 评查点结果列表和统计数据
*/
*/
export async function getReviewPoints(fileId: string) {
// 步骤1:根据fileId查询evaluation_results表
const evaluationResultsParams: PostgrestParams = {
@@ -197,12 +198,24 @@ export async function getReviewPoints(fileId: string) {
return {
id: result.id,
title: message,
pointName: point.name || '',
groupName: group.name || '',
status: point.suggestion_message_type || '',
content: data,
suggestion: point.suggestion_message || '',
result: result.evaluated_results?.result, // 记录评查结果,用于统计
score: point.score || 0
score: point.score || 0,
legalBasis: point.references_laws || {}
// legalBasis: {
// name: '中华人民共和国食品安全法',
// content: '中华人民共和国食品安全法',
// article: [
// {
// name: '中华人民共和国食品安全法',
// content: '中华人民共和国食品安全法'
// }
// ]
// }
};
});
@@ -233,5 +246,30 @@ export async function getReviewPoints(fileId: string) {
stats.score += item.score || 0;
});
return { data: resultData, stats };
// 构建文件信息-评查信息的数据
// 找出最新的评查时间
let latestUpdatedAt = '';
evaluationResultsData.forEach(result => {
if (result.updated_at && (!latestUpdatedAt || result.updated_at > latestUpdatedAt)) {
latestUpdatedAt = result.updated_at.toString();
}
});
// 提取不重复的规则组名称
const uniqueGroups = Array.from(new Set(resultData.map(item => item.groupName))).filter(Boolean);
// 计算问题数量
const issueCount = stats.warning + stats.error;
// 构建评查信息对象
const reviewInfo = {
reviewTime: formatDate(latestUpdatedAt),
reviewModel: 'DeepSeek',
ruleGroup: uniqueGroups.join('、'),
result: issueCount > 0 ? 'warning' : 'success',
issueCount: issueCount
};
// console.log("reviewInfo-------",JSON.stringify(reviewInfo,null,2));
return { data: resultData, stats, reviewInfo };
}
+7 -2
View File
@@ -85,7 +85,7 @@ export interface DocumentSearchParams {
function formatDate(dateString: string): string {
if (!dateString) return '';
try {
return dayjs(dateString).format('YYYY-MM-DD HH:mm:ss');
return dayjs(dateString).format('YYYY-MM-DD');
} catch (error) {
console.error('日期格式化失败:', error);
return dateString;
@@ -215,7 +215,12 @@ export async function getReviewFiles(searchParams: DocumentSearchParams = {}): P
case 'upload_time_asc':
params.order = 'created_at.asc';
break;
// 其他排序方式可以在这里添加
// case 'issue_count_desc':
// params.order = 'issue_count.desc';
// break;
// case 'issue_count_asc':
// params.order = 'issue_count.asc';
// break;
}
}
+38 -1
View File
@@ -114,6 +114,25 @@ function getFileExtension(filename: string): string {
return parts.length > 1 ? parts.pop()?.toLowerCase() || '' : '';
}
/**
* 获取评查结果
* @param id 评查结果ID
* @returns 评查结果
*/
async function getEvaluationResults(id: number) {
const response = await postgrestGet<[]>('evaluation_results', {
filter: {
'document_id': `eq.${id}`
}
});
if (response.error) {
return { error: response.error, status: response.status };
}
const evaluationResult = extractApiData<[]>(response.data);
return evaluationResult;
}
/**
* 将API文档转换为UI文档
*/
@@ -122,6 +141,24 @@ async function convertToUIDocument(doc: Document): Promise<DocumentUI> {
const typeResponse = await getDocumentTypes();
const documentTypes = typeResponse.data?.types || [];
const docType = documentTypes.find(type => type.id.toString() === doc.type_id.toString());
const evaluationResult = await getEvaluationResults(doc.id);
let issues = 0;
interface EvaluationResultItem {
evaluated_results?: {
result?: string;
[key: string]: unknown;
};
[key: string]: unknown;
}
if (evaluationResult && Array.isArray(evaluationResult)) {
evaluationResult.forEach((result: EvaluationResultItem) => {
if(result && result.evaluated_results && !result.evaluated_results.result){
issues++;
}
});
}
return {
id: doc.id,
@@ -132,7 +169,7 @@ async function convertToUIDocument(doc: Document): Promise<DocumentUI> {
size: doc.file_size,
auditStatus: doc.audit_status || 0,
fileStatus: doc.status || '', // 默认为''
issues: 0, // 固定为0
issues: issues, // 使用计算得到的issues
uploadTime: formatDate(doc.updated_at),
fileType: getFileExtension(doc.name),
path: doc.path,
+3 -1
View File
@@ -40,6 +40,7 @@ function extractApiData<T>(responseData: unknown): T | null {
// 文档状态枚举
export enum DocumentStatus {
waiting = 'waiting',
WAITING = "Waiting",
CUTTING = "Cutting",
EXTRACTIONING = "Extractioning",
@@ -175,7 +176,8 @@ export async function uploadDocumentToServer(
// 发送请求
// const response = await fetch(`${API_BASE_URL}/admin/documents/upload`, {
const response = await fetch('http://172.16.0.119:8000/admin/documents/upload', {
const response = await fetch('http://172.16.0.55:8000/admin/documents/upload', {
// const response = await fetch('http://172.16.0.119:8000/admin/documents/upload', {
method: 'POST',
headers: {
'X-File-Name': encodeURIComponent(fileName)
+49 -18
View File
@@ -44,6 +44,7 @@ interface FileDetailsProps {
export function FileDetails({ fileInfo, contractInfo, reviewInfo }: FileDetailsProps) {
// 情况状态对应的标签
const controlContractShow = false
const renderResultBadge = (result: string) => {
switch (result) {
case 'success':
@@ -75,11 +76,39 @@ export function FileDetails({ fileInfo, contractInfo, reviewInfo }: FileDetailsP
// 渲染信息区块
const renderInfoSection = (title: string, icon: string, color: string, children: ReactNode) => {
// 根据color参数返回对应的具体类名
const getBgClass = (colorName: string) => {
switch(colorName) {
case 'blue': return 'bg-blue-50';
case 'green': return 'bg-green-50';
case 'purple': return 'bg-purple-50';
default: return 'bg-gray-50';
}
};
const getTextClass = (colorName: string) => {
switch(colorName) {
case 'blue': return 'text-blue-500';
case 'green': return 'text-green-500';
case 'purple': return 'text-purple-500';
default: return 'text-gray-500';
}
};
const getTextTitleClass = (colorName: string) => {
switch(colorName) {
case 'blue': return 'text-blue-700';
case 'green': return 'text-green-700';
case 'purple': return 'text-purple-700';
default: return 'text-gray-700';
}
};
return (
<div className="info-section">
<div className={`info-header bg-${color}-50`}>
<i className={`${icon} text-${color}-500 text-lg mr-2`}></i>
<h3 className={`font-medium text-${color}-700`}>{title}</h3>
<div className={`info-header ${getBgClass(color)}`}>
<i className={`${icon} ${getTextClass(color)} text-lg mr-2`}></i>
<h3 className={`font-medium ${getTextTitleClass(color)}`}>{title}</h3>
</div>
<div className="info-content">
{children}
@@ -114,20 +143,22 @@ export function FileDetails({ fileInfo, contractInfo, reviewInfo }: FileDetailsP
))}
{/* 合同信息 */}
{renderInfoSection('合同信息', 'ri-file-paper-2-line', 'green', (
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
{renderInfoRow('合同类型', contractInfo.contractType)}
{renderInfoRow('签约日期', contractInfo.signDate)}
{renderInfoRow('合同当事人', (
<div>
<div>{contractInfo.parties.partyA}</div>
<div>{contractInfo.parties.partyB}</div>
</div>
))}
{renderInfoRow('合同金额', contractInfo.amount)}
{renderInfoRow('履行期限', contractInfo.period)}
</div>
))}
{ controlContractShow && (
renderInfoSection('合同信息', 'ri-file-paper-2-line', 'green', (
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
{renderInfoRow('合同类型', contractInfo.contractType)}
{renderInfoRow('签约日期', contractInfo.signDate)}
{renderInfoRow('合同当事人', (
<div>
<div>{contractInfo.parties.partyA}</div>
<div>{contractInfo.parties.partyB}</div>
</div>
))}
{renderInfoRow('合同金额', contractInfo.amount)}
{renderInfoRow('履行期限', contractInfo.period)}
</div>
))
)}
{/* 评查信息 */}
{renderInfoSection('评查信息', 'ri-search-eye-line', 'purple', (
@@ -139,7 +170,7 @@ export function FileDetails({ fileInfo, contractInfo, reviewInfo }: FileDetailsP
{renderInfoRow('评查结果', (
<div className="flex items-center">
{renderResultBadge(reviewInfo.result)}
<span className="text-sm ml-2">{reviewInfo.issueCount}</span>
{reviewInfo.issueCount > 0 && <span className="text-sm ml-2">{reviewInfo.issueCount}</span>}
</div>
))}
</div>
+9 -9
View File
@@ -26,17 +26,17 @@ export function FileInfo({ fileInfo, onConfirmResults }: FileInfoProps) {
<div className="mb-4 file-info-header">
<div className="flex justify-between items-center">
<div>
<h2 className="text-xl font-medium">
<h2 className="text-xl font-medium max-w-xl">
{fileInfo.fileName}
<span className="text-sm text-secondary font-normal ml-2">
{fileInfo.contractNumber}
</span>
{fileInfo.fileSize && (
<span className="text-xs text-gray-500 ml-2">
{fileInfo.fileSize} | {fileInfo.fileFormat} | {fileInfo.pageCount}
</span>
)}
</h2>
<span className="text-xs text-gray-500">
{fileInfo.contractNumber}
</span>
{fileInfo.fileSize && (
<span className="text-xs text-gray-500 ml-2">
{fileInfo.fileSize} | {fileInfo.fileFormat} | {fileInfo.pageCount}
</span>
)}
{fileInfo.uploadTime && (
<div className="text-xs text-gray-500 mt-1">
{fileInfo.uploadTime} | {fileInfo.uploadUser}
+2 -12
View File
@@ -4,18 +4,8 @@
*/
import { useState, useEffect, useRef } from 'react';
// 定义评查点类型
interface ReviewPoint {
id: string;
title: string;
status: string;
content: string;
suggestion: string;
position?: {
section: string;
index: number;
};
}
// 导入统一的ReviewPoint类型
import { type ReviewPoint } from './';
// 定义文档内容类型
interface FileContent {
+124 -63
View File
@@ -39,6 +39,12 @@ export interface ReviewPoint {
index: number;
};
result?: boolean;
legalBasis?: {
name?: string;
content?: string;
articles?: Array<string | { name?: string; content?: string; [key: string]: unknown }>;
[key: string]: unknown;
};
}
// 统计数据类型
@@ -70,6 +76,7 @@ export function ReviewPointsList({
const [userInputText, setUserInputText] = useState(''); // 用户输入的审核意见文本
const [searchText, setSearchText] = useState(''); // 搜索文本
const [statusFilter, setStatusFilter] = useState<string | null>(null); // 状态过滤
// eslint-disable-next-line no-unused-vars
const [suggestionTexts, setSuggestionTexts] = useState<Record<string, string>>({}); // 存储每个评查点的建议文本
// 初始化建议文本
@@ -83,6 +90,7 @@ export function ReviewPointsList({
}, [reviewPoints]);
// 处理建议文本变更
// eslint-disable-next-line no-unused-vars
const handleSuggestionChange = (reviewPointId: string, text: string) => {
setSuggestionTexts(prev => ({
...prev,
@@ -135,7 +143,7 @@ export function ReviewPointsList({
alert(`将为评查点 ${reviewPointId} 执行一键替换操作`);
// 更新评查点状态为成功
onStatusChange(reviewPointId, 'success');
// onStatusChange(reviewPointId, 'success');
};
/**
@@ -206,9 +214,17 @@ export function ReviewPointsList({
<div className="flex justify-between items-center">
{/* 总计数量 */}
<div className="flex items-center">
<div className="w-7 h-7 bg-gray-100 rounded-md flex items-center justify-center">
<button
className={`w-7 h-7 bg-gray-100 rounded-md flex items-center justify-center cursor-pointer ${statusFilter === null && searchText === '' ? 'ring-2 ring-gray-400' : ''}`}
onClick={() => {
setStatusFilter(null);
setSearchText('');
}}
aria-label="显示所有评查点"
type="button"
>
<span className="text-sm font-semibold text-gray-600">{totalToShow}</span>
</div>
</button>
<span className="text-xs text-gray-500 ml-1"></span>
</div>
<div className="h-8 border-r border-gray-200"></div>
@@ -422,75 +438,120 @@ export function ReviewPointsList({
return (
<div className="mt-2">
{/* 内容显示区域 */}
<div className="p-2 bg-white rounded border border-gray-200 text-xs mb-3">
{/* 移除顶部的"当前值"标题,在每个内容项中显示 */}
{typeof reviewPoint.content === 'object' && reviewPoint.content !== null ? (
// 当 content 是对象时的渲染方式
<div>
{Object.entries(reviewPoint.content).map(([key, value], index) => (
<div key={index} className="mb-2 pb-2 border-b border-gray-100 last:border-b-0 last:mb-0 last:pb-0">
{/* 使用flex布局使key和状态标签左右对齐 */}
{reviewPoint.content !== null && (
(typeof reviewPoint.content === 'string' && reviewPoint.content !== '') ||
(typeof reviewPoint.content === 'object' && Object.keys(reviewPoint.content).length > 0)
) && (
<>
{/* 内容显示区域 */}
<div className="p-2 bg-white rounded border border-gray-200 text-xs mb-3">
{/* 移除顶部的"当前值"标题,在每个内容项中显示 */}
{typeof reviewPoint.content === 'object' && reviewPoint.content !== null ? (
// 当 content 是对象时的渲染方式
<div>
{Object.entries(reviewPoint.content).map(([key, value], index) => (
<div key={index} className="mb-2 pb-2 border-b border-gray-100 last:border-b-0 last:mb-0 last:pb-0">
{/* 使用flex布局使key和状态标签左右对齐 */}
<div className="flex justify-between items-center mb-1">
<span className="text-xs">{key}</span>
{/* <span className={`text-xs ${isErrorStatus ? 'text-error' : 'text-warning'}`}>
{isErrorStatus ? '不符合规范' : '需优化'}
</span> */}
<span className={`text-xs ${isErrorStatus ? 'text-error' : 'text-warning'}`}>
{value ? '' : '缺失'}
</span>
</div>
<p className="text-xs text-left">{value || ' '}</p>
</div>
))}
</div>
) : (
// 当 content 是字符串时的渲染方式
<>
{/* 为字符串内容也添加标题和状态 */}
<div className="flex justify-between items-center mb-1">
<span className="text-xs">{key}</span>
<span className="text-xs"></span>
<span className={`text-xs ${isErrorStatus ? 'text-error' : 'text-warning'}`}>
{isErrorStatus ? '不符合规范' : '需优化'}
</span>
</div>
<p className="text-xs text-left">{value || '(内容为空)'}</p>
</div>
))}
<p className="text-xs text-left">{reviewPoint.content || '(内容为空)'}</p>
</>
)}
</div>
) : (
// 当 content 是字符串时的渲染方式
<>
{/* 为字符串内容也添加标题和状态 */}
<div className="flex justify-between items-center mb-1">
<span className="text-xs"></span>
<span className={`text-xs ${isErrorStatus ? 'text-error' : 'text-warning'}`}>
{isErrorStatus ? '不符合规范' : '需优化'}
</span>
{/* 法律依据内容 */}
{reviewPoint.legalBasis && (typeof reviewPoint.legalBasis === 'object') && (
(reviewPoint.legalBasis.name || reviewPoint.legalBasis.content ||
(reviewPoint.legalBasis.articles && Array.isArray(reviewPoint.legalBasis.articles) && reviewPoint.legalBasis.articles.length > 0)) && (
<div className="p-2 bg-white rounded border border-gray-200 text-xs mb-3">
<div className="flex justify-between items-center mb-1">
<span className="text-xs font-medium"></span>
</div>
{reviewPoint.legalBasis.name && (
<p className="text-xs text-left mb-1">{reviewPoint.legalBasis.name}</p>
)}
{reviewPoint.legalBasis.content && (
<p className="text-xs text-left mb-1"><span className="font-medium"></span>{reviewPoint.legalBasis.content}</p>
)}
{reviewPoint.legalBasis.articles && Array.isArray(reviewPoint.legalBasis.articles) && reviewPoint.legalBasis.articles.length > 0 && (
<div>
<p className="text-xs text-left font-medium mb-1"></p>
<ul className="list-disc pl-4">
{reviewPoint.legalBasis.articles.map((item, index) => (
<li key={index} className="text-xs text-left">
{typeof item === 'string' ? item :
typeof item === 'object' && item !== null ?
(item.name ? `${item.name}: ${item.content || ''}` :
item.content || JSON.stringify(item)) :
String(item)}
</li>
))}
</ul>
</div>
)}
</div>
)
)}
{/* 建议修改区域 */}
<div className="mb-2">
<div className="flex justify-between items-center mb-2">
<span className="text-gray-700"></span>
{/* <span className="text-green-500">符合规范</span> */}
</div>
<p className="text-xs text-left">{reviewPoint.content || '(内容为空)'}</p>
</>
)}
</div>
<textarea
value={suggestionTexts[reviewPoint.id] || ''}
onChange={(e) => handleSuggestionChange(reviewPoint.id, e.target.value)}
className="w-full p-2 border rounded bg-white min-h-[100px] focus:outline-none focus:border-[#00684a] focus:shadow-[0_0_0_2px_rgba(0,104,74,0.2)]"
/>
</div>
{/* 建议修改区域 */}
<div className="mb-2">
<div className="flex justify-between items-center mb-2">
<span className="text-gray-700"></span>
<span className="text-green-500"></span>
</div>
<textarea
value={suggestionTexts[reviewPoint.id] || ''}
onChange={(e) => handleSuggestionChange(reviewPoint.id, e.target.value)}
className="w-full p-2 border rounded bg-gray-50 min-h-[100px] focus:outline-none focus:border-[#00684a] focus:shadow-[0_0_0_2px_rgba(0,104,74,0.2)]"
/>
</div>
{/* 操作按钮区域 */}
<div className="flex space-x-2 mt-2">
{/* 一键替换按钮 - 只有非人工审核的点或未通过的人工审核点才显示 */}
{(!reviewPoint.needsHumanReview || (!reviewPoint.result && reviewPoint.status !== 'success')) && (
<button
className="replace-action flex-1 justify-center"
onClick={() => handleReplace(reviewPoint.id)}
>
<i className="ri-replace-line"></i>
</button>
)}
{/* 人工审核按钮 */}
{reviewPoint.needsHumanReview && !reviewPoint.result && reviewPoint.status !== 'success' && (
<button
className="replace-action flex-1 justify-center human-review-request"
onClick={() => handleEditReviewPoint(reviewPoint.id)}
>
<i className="ri-edit-line"></i>
</button>
)}
</div>
</>
)}
{/* 操作按钮区域 */}
<div className="flex space-x-2 mt-2">
{/* 一键替换按钮 - 只有非人工审核的点或未通过的人工审核点才显示 */}
{(!reviewPoint.needsHumanReview || (!reviewPoint.result && reviewPoint.status !== 'success')) && (
<button
className="replace-action flex-1 justify-center"
onClick={() => handleReplace(reviewPoint.id)}
>
<i className="ri-replace-line"></i>
</button>
)}
{/* 人工审核按钮 */}
{reviewPoint.needsHumanReview && !reviewPoint.result && reviewPoint.status !== 'success' && (
<button
className="replace-action flex-1 justify-center human-review-request"
onClick={() => handleEditReviewPoint(reviewPoint.id)}
>
<i className="ri-edit-line"></i>
</button>
)}
</div>
</div>
);
}
+1 -1
View File
@@ -105,4 +105,4 @@ export function Table<T extends Record<string, any>>({
)}
</div>
);
}
}
+22 -3
View File
@@ -3,15 +3,23 @@ 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 { Tag } from "~/components/ui/Tag";
import homeStyles from "~/styles/pages/home.css?url";
import { getDocuments, type DocumentUI } from "~/api/files/documents";
// 文件处理状态选项
const fileProcessingStatusOptions = [
{ value: "Waiting", label: "上传中", icon: "ri-loader-line", color: "blue" },
{ value: "Cutting", label: "切分中", icon: "ri-loader-line", color: "purple" },
{ value: "Extractioning", label: "抽取中", icon: "ri-loader-line", color: "cyan" },
{ value: "Evaluationing", label: "评查中", icon: "ri-loader-line", color: "teal" },
{ value: "Processed", label: "已完成", icon: "ri-check-line", color: "green" },
];
export const links = () => [
{ rel: "stylesheet", href: homeStyles },
...statusBadgeLinks(),
...fileTagLinks()
];
@@ -158,7 +166,18 @@ export default function Index() {
</div>
</div>
<div className="doc-status">
<StatusBadge status={file.fileStatus} />
{(() => {
const fileStatus = file.fileStatus || "-";
const status = fileProcessingStatusOptions.find(s => s.value === fileStatus) ||
fileProcessingStatusOptions[0];
const isSpinning = fileStatus !== "Processed";
return (
<div className={`inline-flex items-center px-2 py-1 rounded-full text-xs bg-${status.color}-100 text-${status.color}-800`}>
<i className={`${status.icon} ${isSpinning ? "animate-spin" : ""} mr-1`}></i>
<span>{status.label}</span>
</div>
);
})()}
</div>
</div>
))}
+5 -4
View File
@@ -56,6 +56,7 @@ export const loader = async ({ request }: LoaderFunctionArgs) => {
// 获取文档列表
const documentsResponse = await getDocuments(searchParams);
// console.log('documentsResponse---1--',JSON.stringify(documentsResponse,null,2));
if (documentsResponse.error) {
throw new Error(documentsResponse.error);
}
@@ -436,17 +437,17 @@ export default function DocumentsIndex() {
key: "name",
width:'25%',
render: (_: unknown, record: DocumentUI) => (
<div className="flex items-center m-1">
<div className="flex m-1">
<FileTag
extension={record.fileType}
showIcon={true}
showText={false}
showBackground={false}
size="lg"
className="mr-2 flex-shrink-0"
className="mr-2 flex-shrink-0 self-center"
/>
<div className="overflow-hidden">
<span className="file-name break-words block" title={record.name}>{record.name}</span>
<span className="file-name break-words block whitespace-normal leading-normal" title={record.name}>{record.name}</span>
<div className="mt-2 flex inline-block">
<FileTypeTag
type={record.type}
@@ -484,7 +485,7 @@ export default function DocumentsIndex() {
render: (_: unknown, record: DocumentUI) => {
// 处理fileStatus为null或undefined的情况
// console.log('fileStatus',record.fileStatus)
const fileStatus = record.fileStatus || "Processed";
const fileStatus = record.fileStatus || "-";
const status = fileProcessingStatusOptions.find(s => s.value === fileStatus) ||
fileProcessingStatusOptions[0];
const isSpinning = fileStatus !== "Processed";
+12 -4
View File
@@ -1,6 +1,6 @@
import { useState, useEffect, useRef } from "react";
import { MetaFunction, ActionFunctionArgs, LoaderFunctionArgs } from "@remix-run/node";
import { Form, useActionData, useLoaderData } from "@remix-run/react";
import { Form, useActionData, useLoaderData, useNavigate } from "@remix-run/react";
import { Card } from "~/components/ui/Card";
import { Button } from "~/components/ui/Button";
import { Table } from "~/components/ui/Table";
@@ -288,6 +288,7 @@ export default function FilesUpload() {
{ title: "评查点审核", description: "DeepSeek 评查中", status: "waiting" },
{ title: "审核准备", description: "文档已准备就绪,等待审核", status: "waiting" }
]);
const navigate = useNavigate();
// 队列文件状态
const [queueFiles, setQueueFiles] = useState<Document[]>(documents);
@@ -823,6 +824,11 @@ export default function FilesUpload() {
let statusText = "";
switch(record.status) {
case 'waiting':
statusClass = "status-processing";
statusIcon = "ri-loader-4-line";
statusText = "等待中";
break;
case DocumentStatus.WAITING:
statusClass = "status-processing";
statusIcon = "ri-loader-4-line";
@@ -854,6 +860,7 @@ export default function FilesUpload() {
statusText = "已完成";
break;
}
return (
<span className={`status-badge ${statusClass}`}>
@@ -873,7 +880,7 @@ export default function FilesUpload() {
size="small"
disabled={record.status !== DocumentStatus.PROCESSED}
icon="ri-eye-line"
onClick={() => alert(`查看文件详情: ${record.name}`)}
onClick={() => navigate(`/reviews?id=${record.id}`)}
>
</Button>
@@ -1144,14 +1151,15 @@ export default function FilesUpload() {
<p className="text-sm text-green-700"></p>
</div>
<div className="flex justify-end">
{/* <div className="flex justify-end">
<Button
type="primary"
icon="ri-file-search-line"
>
查看详情并审核
</Button>
</div>
</div> */}
</div>
)}
+7 -4
View File
@@ -183,7 +183,7 @@ export async function loader({ request }: LoaderFunctionArgs) {
}
const reviewData = await getReviewPoints(id);
console.log("reviewData-------",reviewData);
console.log("reviewData-------",JSON.stringify(reviewData.data,null,2));
if (reviewData.error) {
console.error("获取评查点数据错误:", reviewData.error);
return Response.json({ error: reviewData.error }, { status: reviewData.status || 500 });
@@ -192,6 +192,7 @@ export async function loader({ request }: LoaderFunctionArgs) {
return Response.json({
document: documentData.data,
reviewPoints: reviewData.data,
reviewInfo: reviewData.reviewInfo,
statistics: reviewData.stats
});
} catch (error) {
@@ -202,7 +203,7 @@ export async function loader({ request }: LoaderFunctionArgs) {
export default function ReviewDetails() {
const navigate = useNavigate();
const { document, reviewPoints, statistics } = useLoaderData<typeof loader>();
const { document, reviewPoints, statistics, reviewInfo } = useLoaderData<typeof loader>();
const [isLoading, setIsLoading] = useState(false); // 已经通过loader加载了数据,不需要再显示加载状态
const [activeTab, setActiveTab] = useState<string>('preview'); // 'preview', 'analysis', 'fileinfo'
const [reviewData, setReviewData] = useState<ReviewData | null>(null);
@@ -222,6 +223,7 @@ export default function ReviewDetails() {
uploadTime: document.uploadTime || "未知时间",
uploadUser: document.uploadUser || "未知用户",
auditStatus: document.auditStatus || 0,
legalBasis: document.legalBasis || {}
};
// 创建包含真实文档数据的评查数据对象
@@ -230,16 +232,17 @@ export default function ReviewDetails() {
fileInfo: fileInfo,
// 其他字段暂时使用默认值
contractInfo: getMockReviewData().contractInfo,
reviewInfo: getMockReviewData().reviewInfo,
reviewInfo: reviewInfo,
statistics: statistics,
fileContent: getMockReviewData().fileContent,
reviewPoints: reviewPoints,
aiAnalysis: getMockReviewData().aiAnalysis,
};
console.log("reviewDataObj-------",reviewDataObj);
setReviewData(reviewDataObj);
setIsLoading(false);
}, [document, reviewPoints, statistics]);
}, [document, reviewPoints, statistics, reviewInfo]);
const handleTabChange = (tabKey: string) => {
setActiveTab(tabKey);
+26 -50
View File
@@ -163,19 +163,6 @@ export default function RulesFiles() {
setSearchParams(newParams);
};
// 处理确认评查状态
const handleConfirmStatus = async (id: string, status: string) => {
try {
await updateReviewStatus(id, status);
// 刷新页面获取最新数据
const newParams = new URLSearchParams(searchParams);
setSearchParams(newParams);
} catch (error) {
console.error('更新评查状态失败:', error);
// 可以在这里添加错误提示
}
};
// 渲染问题摘要
const renderIssues = (file: ReviewFileUI) => {
// 如果评查状态为通过,显示"所有评查点均通过"
@@ -220,11 +207,13 @@ export default function RulesFiles() {
key: "fileName",
width: "30%",
render: (_: unknown, file: ReviewFileUI) => (
<div className="flex items-center">
<FileIcon fileName={file.fileName} className="mr-2 text-lg flex-shrink-0 w-10 h-10" />
<div>
<div className="font-normal text-base break-words" title={file.fileName}>{file.fileName}</div>
<div className="text-xs text-secondary mt-1">
<div className="flex">
<div className="flex-shrink-0 flex items-center self-center">
<FileIcon fileName={file.fileName} className="text-lg w-10 h-10" />
</div>
<div className="min-w-0 flex-1 flex flex-col py-2 ml-2">
<div className="font-normal text-base break-words whitespace-normal leading-normal" title={file.fileName}>{file.fileName}</div>
<div className="text-xs text-secondary mt-2">
{file.fileCode}
</div>
</div>
@@ -283,27 +272,16 @@ export default function RulesFiles() {
width: "14%",
render: (_: unknown, file: ReviewFileUI) => (
<>
{file.reviewStatus === 'pending' ? (
<Button
type="primary"
size="small"
icon="ri-check-double-line"
className="mr-2"
onClick={() => handleConfirmStatus(file.id, 'pass')}
>
</Button>
) : (
<Button
type="default"
size="small"
icon="ri-eye-line"
to={`/files/${file.id}`}
className="mr-2"
>
</Button>
)}
<Button
type="default"
size="small"
icon="ri-eye-line"
to={`/reviews?id=${file.id}`}
className="mr-2"
>
</Button>
<Button type="default" size="small" icon="ri-download-2-line">
</Button>
@@ -357,16 +335,6 @@ export default function RulesFiles() {
onChange={handleFilterChange}
className="mr-2 w-40"
/>
<SearchFilter
label="搜索"
placeholder="搜索文件名、合同编号或关键词"
value={searchParams.get('keyword') || ''}
onSearch={handleSearch}
buttonText=""
className="mr-2 w-64"
/>
<FilterSelect
label="排序方式"
name="sortOrder"
@@ -380,6 +348,14 @@ export default function RulesFiles() {
{ value: "issue_count_asc", label: "问题数量 ↑" }
]}
/>
<SearchFilter
label="搜索"
placeholder="搜索文件名、合同编号或关键词"
value={searchParams.get('keyword') || ''}
onSearch={handleSearch}
buttonText=""
className="mr-2 flex-1"
/>
</FilterPanel>
{/* 文件列表 */}
@@ -389,7 +365,7 @@ export default function RulesFiles() {
dataSource={files}
rowKey="id"
emptyText="暂无文件数据"
className="files-table"
className="files-table table-auto-height"
/>
{/* 分页组件 */}
+11
View File
@@ -27,6 +27,17 @@
@apply bg-white hover:bg-gray-50 transition-colors duration-150;
}
/* 自动高度表格样式 - 全局可用 */
.table-auto-height tr {
height: auto !important;
min-height: 60px;
}
.table-auto-height td {
height: auto !important;
word-break: break-word;
}
/* 空状态 */
.ant-table-empty {
@apply py-12 text-center text-gray-500;
+3
View File
@@ -25,6 +25,9 @@
max-width: 100%;
word-wrap: break-word;
word-break: break-word;
line-height: 1.4;
white-space: normal;
display: block;
}
.document-number {
+22
View File
@@ -477,3 +477,25 @@
text-align: center !important;
vertical-align: middle !important;
} */
/* 新增:确保表格行能根据内容自动调整高度 */
/* .review-files-page .files-table tr {
height: auto;
min-height: 60px;
}
.review-files-page .files-table td {
height: auto;
vertical-align: top;
word-break: break-word;
padding: 12px 16px;
}
.review-files-page .files-table .break-all {
word-break: break-all;
}
.review-files-page .files-table .whitespace-pre-wrap {
white-space: pre-wrap;
} */