完善列表和编辑页面的数据验证和交互,实现服务端和客户端两重数据验证

This commit is contained in:
2025-04-24 18:33:09 +08:00
parent be99fdec79
commit 65b7d0739a
13 changed files with 444 additions and 229 deletions
+16 -10
View File
@@ -60,7 +60,8 @@ export interface DocumentTypeGroup {
// 搜索参数 // 搜索参数
export interface DocumentTypeSearchParams { export interface DocumentTypeSearchParams {
name?: string; name?: string;
group_id?: string; ruleType?: string;
groupId?: string;
page?: number; page?: number;
pageSize?: number; pageSize?: number;
} }
@@ -239,13 +240,18 @@ export async function getDocumentTypes(searchParams: DocumentTypeSearchParams =
} }
// 如果有分组ID筛选条件 // 如果有分组ID筛选条件
if (searchParams.group_id) { if (searchParams.ruleType) {
filter['evaluation_point_groups_ids'] = `cs.{${searchParams.group_id}}`; filter['evaluation_point_groups_ids'] = `cs.[${searchParams.ruleType}]`;
}
if (searchParams.groupId) {
// 如果groupId存在,则将groupId作为子级评查点分组ID
filter['evaluation_point_groups_ids'] = `cs.[${searchParams.groupId}]`;
} }
params.filter = filter; params.filter = filter;
// console.log('获取文档类型列表,参数:', params); console.log('获取文档类型列表,参数:', params);
const response = await postgrestGet<DocumentType[]>('document_types', params); const response = await postgrestGet<DocumentType[]>('document_types', params);
if (response.error) { if (response.error) {
@@ -529,8 +535,8 @@ export async function createDocumentType(documentType: DocumentTypeCreateDTO): P
if (!groupId || isNaN(parseInt(groupId, 10))) { if (!groupId || isNaN(parseInt(groupId, 10))) {
return { error: '无效的评查点分组ID', status: 400 }; return { error: '无效的评查点分组ID', status: 400 };
} }
const groupIds = parseInt(groupId, 10); // 修改为数组形式 // const groupIds = parseInt(groupId, 10); // 修改为数组形式
// const groupIds = [parseInt(groupId, 10)]; // 修改为数组形式 const groupIds = [parseInt(groupId, 10)]; // 修改为数组形式
// 构建提示词配置 - 确保所有字段都有明确的设置 // 构建提示词配置 - 确保所有字段都有明确的设置
const promptConfig: Record<string, number | null> = { const promptConfig: Record<string, number | null> = {
@@ -580,13 +586,13 @@ export async function createDocumentType(documentType: DocumentTypeCreateDTO): P
description: documentType.description || '', description: documentType.description || '',
evaluation_point_groups_ids: groupIds, evaluation_point_groups_ids: groupIds,
prompt_config: promptConfig, prompt_config: promptConfig,
code: documentType.code || null // code: documentType.code || null
}; };
// console.log('创建文档类型请求数据:', JSON.stringify(apiDocumentType, null, 2)); // console.log('创建文档类型请求数据:', JSON.stringify(apiDocumentType, null, 2));
// console.log('创建文档类型请求数据:', apiDocumentType); // console.log('创建文档类型请求数据:', apiDocumentType);
// if(apiDocumentType){ // if(apiDocumentType){
// throw new Error('测试错误'); // throw new Error('测试错误');
// } // }
// 发送创建请求 // 发送创建请求
@@ -660,7 +666,7 @@ export async function updateDocumentType(id: string, documentType: DocumentTypeU
const promptConfig: Record<string, number | null> = { const promptConfig: Record<string, number | null> = {
llm_extract_template: null, llm_extract_template: null,
vlm_extract_template: null, vlm_extract_template: null,
evaluation_template: null, // evaluation_template: null,
execution_template: null, execution_template: null,
summary_template: null summary_template: null
}; };
@@ -707,7 +713,7 @@ export async function updateDocumentType(id: string, documentType: DocumentTypeU
}; };
console.log('更新文档类型请求数据:', JSON.stringify(apiDocumentType, null, 2)); console.log('更新文档类型请求数据:', JSON.stringify(apiDocumentType, null, 2));
// throw new Error('测试错误');
// 发送更新请求 // 发送更新请求
const response = await postgrestPut<DocumentType, typeof apiDocumentType>( const response = await postgrestPut<DocumentType, typeof apiDocumentType>(
'document_types', 'document_types',
+4 -4
View File
@@ -121,17 +121,17 @@ export async function getReviewPoints(fileId: string) {
return { error: evaluationResultsResponse.error, status: evaluationResultsResponse.status }; return { error: evaluationResultsResponse.error, status: evaluationResultsResponse.status };
} }
const evaluationResultsData = extractApiData<EvaluationResult[]>(evaluationResultsResponse.data); const evaluationResultsData = extractApiData<EvaluationResult[]>(evaluationResultsResponse.data) || [];
if (!evaluationResultsData || !Array.isArray(evaluationResultsData)) { if (Array.isArray(evaluationResultsData) && evaluationResultsData.length <= 0) {
return { data: [], stats: { total: 0, success: 0, warning: 0, error: 0, score: 0 } }; return { data: [], stats: { total: 0, success: 0, warning: 0, error: 0, score: 0 },error: '获取评查结果数据失败' };
} }
// 收集所有评查点ID,用于查询评查点详情 // 收集所有评查点ID,用于查询评查点详情
const evaluationPointIds = evaluationResultsData.map(item => item.evaluation_point_id).filter(Boolean); const evaluationPointIds = evaluationResultsData.map(item => item.evaluation_point_id).filter(Boolean);
if (evaluationPointIds.length === 0) { if (evaluationPointIds.length === 0) {
return { data: [], stats: { total: 0, success: 0, warning: 0, error: 0, score: 0 } }; return { data: [], stats: { total: 0, success: 0, warning: 0, error: 0, score: 0 },error: '获取评查点ID失败' };
} }
// 步骤2:根据evaluation_point_id查询evaluation_points表 // 步骤2:根据evaluation_point_id查询evaluation_points表
+6 -1
View File
@@ -1,4 +1,5 @@
import { useNavigate } from "@remix-run/react"; import { useNavigate } from "@remix-run/react";
import { toastService } from "~/components/ui/Toast";
interface FileInfoProps { interface FileInfoProps {
fileInfo: { fileInfo: {
fileName: string; fileName: string;
@@ -54,6 +55,10 @@ export function FileInfo({ fileInfo, onConfirmResults }: FileInfoProps) {
} }
}; };
const handleBack = () => {
navigate(-1);
};
const handleExportReport = () => { const handleExportReport = () => {
alert('导出评查报告功能'); alert('导出评查报告功能');
}; };
@@ -83,7 +88,7 @@ export function FileInfo({ fileInfo, onConfirmResults }: FileInfoProps) {
{/* 返回上一级 */} {/* 返回上一级 */}
<button <button
className="ant-btn ant-btn-default flex items-center" className="ant-btn ant-btn-default flex items-center"
onClick={() => navigate(-1)} onClick={() => handleBack()}
> >
<i className="ri-arrow-left-line mr-1"></i> <i className="ri-arrow-left-line mr-1"></i>
</button> </button>
+13 -9
View File
@@ -146,16 +146,18 @@ export function MessageModal({
onClick={handleClose} onClick={handleClose}
onKeyDown={handleKeyDown} onKeyDown={handleKeyDown}
tabIndex={0} tabIndex={0}
role="button" role="presentation"
aria-label="关闭对话框" aria-label="关闭对话框"
> >
<div <div
className={`message-modal message-modal-${type} ${isClosing ? 'closing' : ''}`} className={`message-modal message-modal-${type} ${isClosing ? 'closing' : ''}`}
onClick={(e) => e.stopPropagation()}
role="dialog" role="dialog"
aria-modal="true" aria-modal="true"
aria-labelledby="message-modal-title" aria-labelledby="message-modal-title"
aria-describedby="message-modal-content" aria-describedby="message-modal-content"
onClick={(e) => e.stopPropagation()}
onKeyDown={(e) => e.stopPropagation()}
tabIndex={-1}
> >
{showCloseButton && ( {showCloseButton && (
<button <button
@@ -198,12 +200,14 @@ export function MessageModal({
> >
{confirmText} {confirmText}
</button> </button>
<button {cancelText && (
className="message-modal-button" <button
onClick={handleClose} className="message-modal-button"
> onClick={handleClose}
{cancelText} >
</button> {cancelText}
</button>
)}
</> </>
)} )}
+56 -12
View File
@@ -1,4 +1,4 @@
import React from 'react'; import React, { useState, useRef } from 'react';
interface SearchBoxProps { interface SearchBoxProps {
placeholder?: string; placeholder?: string;
@@ -17,6 +17,11 @@ export function SearchBox({
className = '', className = '',
name = 'keyword' name = 'keyword'
}: SearchBoxProps) { }: SearchBoxProps) {
const [inputValue, setInputValue] = useState(defaultValue);
const [isHovering, setIsHovering] = useState(false);
const inputRef = useRef<HTMLInputElement>(null);
const containerRef = useRef<HTMLDivElement>(null);
const handleSubmit = (e: React.FormEvent<HTMLFormElement>) => { const handleSubmit = (e: React.FormEvent<HTMLFormElement>) => {
e.preventDefault(); e.preventDefault();
const formData = new FormData(e.currentTarget); const formData = new FormData(e.currentTarget);
@@ -25,28 +30,67 @@ export function SearchBox({
}; };
const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => { const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {
const value = e.target.value;
setInputValue(value);
// 对于没有按钮的输入框,我们希望在输入时就触发搜索 // 对于没有按钮的输入框,我们希望在输入时就触发搜索
if (className.includes('form-input-only')) { if (className.includes('form-input-only')) {
onSearch(e.target.value); onSearch(value);
} }
}; };
const handleClear = () => {
setInputValue('');
if (inputRef.current) {
inputRef.current.value = '';
inputRef.current.focus();
}
onSearch('');
};
const handleMouseEnter = () => {
setIsHovering(true);
};
const handleMouseLeave = () => {
setIsHovering(false);
};
const isIconOnly = buttonText === ''; const isIconOnly = buttonText === '';
const isFilterControl = className.includes('filter-control'); const isFilterControl = className.includes('filter-control');
const hasButton = !className.includes('form-input-only');
const searchBoxClass = `search-box ${className} ${isFilterControl ? 'search-box-row' : ''}`; const searchBoxClass = `search-box ${className} ${isFilterControl ? 'search-box-row' : ''}`;
const showClearButton = inputValue && isHovering;
return ( return (
<form onSubmit={handleSubmit} className={searchBoxClass}> <form onSubmit={handleSubmit} className={searchBoxClass}>
<input <div
type="text" className={`relative ${hasButton ? 'flex-1' : 'w-full'}`}
id={name} ref={containerRef}
name={name} onMouseEnter={handleMouseEnter}
className={`form-input ${isFilterControl ? 'flex-1' : ''}`} onMouseLeave={handleMouseLeave}
placeholder={placeholder} >
defaultValue={defaultValue} <input
onChange={handleChange} type="text"
/> id={name}
{!className.includes('form-input-only') && ( name={name}
className={`form-input w-full ${hasButton ? 'rounded-r-none' : ''}`}
placeholder={placeholder}
defaultValue={defaultValue}
onChange={handleChange}
ref={inputRef}
/>
{showClearButton && (
<button
type="button"
className="search-box-clear"
onClick={handleClear}
>
<i className="ri-close-circle-line"></i>
</button>
)}
</div>
{hasButton && (
<button <button
type="submit" type="submit"
className={`search-button ${isIconOnly ? "icon-only-btn" : ""}`} className={`search-button ${isIconOnly ? "icon-only-btn" : ""}`}
+7 -9
View File
@@ -338,7 +338,7 @@ export default function ConfigNew() {
case 'name': case 'name':
if(value.trim() === ""){ if(value.trim() === ""){
return "配置名称不能为空"; return "配置名称不能为空";
}else if(/^[a-zA-Z_]+$/.test(value)){ }else if(!/^[a-zA-Z_]+$/.test(value)){
return "配置名称只能包含英文字母和下划线"; return "配置名称只能包含英文字母和下划线";
}else if(value.length > 100){ }else if(value.length > 100){
return "配置名称不能超过100个字符"; return "配置名称不能超过100个字符";
@@ -574,7 +574,7 @@ export default function ConfigNew() {
{/* 配置名称和状态 */} {/* 配置名称和状态 */}
<div className="form-row"> <div className="form-row">
<div className="form-group"> <div className="form-group">
<label htmlFor="name" className="form-label required"></label> <label htmlFor="name" className="form-label "> <span className="text-red-500">*</span></label>
<input <input
type="text" type="text"
id="name" id="name"
@@ -583,7 +583,6 @@ export default function ConfigNew() {
value={formValues.name} value={formValues.name}
onChange={handleInputChange} onChange={handleInputChange}
placeholder="请输入配置名称,如database_connection" placeholder="请输入配置名称,如database_connection"
required
/> />
{touchedFields.name && formErrors.name && ( {touchedFields.name && formErrors.name && (
<div className="error-message">{formErrors.name}</div> <div className="error-message">{formErrors.name}</div>
@@ -619,7 +618,7 @@ export default function ConfigNew() {
{/* 所属模块 */} {/* 所属模块 */}
<div className="form-group"> <div className="form-group">
<label htmlFor="type" className="form-label required"></label> <label htmlFor="type" className="form-label"> <span className="text-red-500">*</span></label>
<input <input
type="hidden" type="hidden"
name="type" name="type"
@@ -633,7 +632,7 @@ export default function ConfigNew() {
onChange={handleInputChange} onChange={handleInputChange}
name="type" name="type"
placeholder="请输入或选择所属模块" placeholder="请输入或选择所属模块"
required
/> />
{touchedFields.type && formErrors.type && ( {touchedFields.type && formErrors.type && (
<div className="error-message">{formErrors.type}</div> <div className="error-message">{formErrors.type}</div>
@@ -657,7 +656,7 @@ export default function ConfigNew() {
{/* 环境 */} {/* 环境 */}
<div className="form-group"> <div className="form-group">
<label htmlFor="environment" className="form-label required"></label> <label htmlFor="environment" className="form-label"> <span className="text-red-500">*</span></label>
<input <input
type="hidden" type="hidden"
name="environment" name="environment"
@@ -671,7 +670,6 @@ export default function ConfigNew() {
onChange={handleInputChange} onChange={handleInputChange}
name="environment" name="environment"
placeholder="请输入或选择环境" placeholder="请输入或选择环境"
required
/> />
{touchedFields.environment && formErrors.environment && ( {touchedFields.environment && formErrors.environment && (
<div className="error-message">{formErrors.environment}</div> <div className="error-message">{formErrors.environment}</div>
@@ -695,7 +693,7 @@ export default function ConfigNew() {
{/* 配置数据 */} {/* 配置数据 */}
<div className="form-group"> <div className="form-group">
<label htmlFor="config" className="form-label required"> (JSON)</label> <label htmlFor="config" className="form-label "> (JSON) <span className="text-red-500">*</span></label>
<div className="grid grid-cols-1 md:grid-cols-2 gap-4" style={{ minHeight: '390px' }}> <div className="grid grid-cols-1 md:grid-cols-2 gap-4" style={{ minHeight: '390px' }}>
{/* 左侧JSON编辑区 */} {/* 左侧JSON编辑区 */}
<div className="h-full"> <div className="h-full">
@@ -705,7 +703,7 @@ export default function ConfigNew() {
className={`json-editor ${touchedFields.config && formErrors.config ? 'input-error' : ''}`} className={`json-editor ${touchedFields.config && formErrors.config ? 'input-error' : ''}`}
value={formValues.config} value={formValues.config}
onChange={handleConfigDataChange} onChange={handleConfigDataChange}
required
placeholder='请输入JSON格式的配置数据' placeholder='请输入JSON格式的配置数据'
/> />
<div className="editor-actions"> <div className="editor-actions">
+130 -81
View File
@@ -1,21 +1,23 @@
import { useState } from "react"; import { useState, useEffect } from "react";
import { useSearchParams, useNavigate, useLoaderData } from "@remix-run/react"; import { useSearchParams, useNavigate, useLoaderData } from "@remix-run/react";
import { ActionFunctionArgs, LoaderFunctionArgs, MetaFunction, json } from "@remix-run/node"; import { ActionFunctionArgs, LoaderFunctionArgs, MetaFunction } from "@remix-run/node";
import { Table } from "~/components/ui/Table"; import { Table } from "~/components/ui/Table";
import { Card } from "~/components/ui/Card"; import { Card } from "~/components/ui/Card";
import { Button } from "~/components/ui/Button"; import { Button } from "~/components/ui/Button";
import { Pagination } from "~/components/ui/Pagination"; import { Pagination } from "~/components/ui/Pagination";
import { FilterPanel, FilterSelect, SearchFilter } from "~/components/ui/FilterPanel"; import { FilterPanel, FilterSelect, SearchFilter } from "~/components/ui/FilterPanel";
import { getRuleTypes, getRuleGroupsByType, type RuleType, type RuleGroup } from "~/api/evaluation_points/rules";
import { toastService } from "~/components/ui/Toast";
import { import {
getDocumentTypes, getDocumentTypes,
deleteDocumentType, deleteDocumentType,
getAllEvaluationPointGroups,
type DocumentTypeUI, type DocumentTypeUI,
type DocumentTypeSearchParams, type DocumentTypeSearchParams,
type DocumentTypeGroup type DocumentTypeGroup
} from "~/api/document-types/document-types"; } from "~/api/document-types/document-types";
import documentTypesStyles from "~/styles/pages/document-types_index.css?url"; import documentTypesStyles from "~/styles/pages/document-types_index.css?url";
// 引入CSS样式 // 引入CSS样式
export function links() { export function links() {
return [ return [
@@ -39,6 +41,7 @@ interface LoaderData {
currentPage: number; currentPage: number;
error?: string; error?: string;
groups: DocumentTypeGroup[]; groups: DocumentTypeGroup[];
ruleTypes: RuleType[];
} }
// 加载函数 - 获取文档类型列表 // 加载函数 - 获取文档类型列表
@@ -46,60 +49,51 @@ export async function loader({ request }: LoaderFunctionArgs) {
try { try {
const url = new URL(request.url); const url = new URL(request.url);
const name = url.searchParams.get('name') || undefined; const name = url.searchParams.get('name') || undefined;
const group_id = url.searchParams.get('group_id') || undefined; const ruleType = url.searchParams.get('ruleType') || undefined;
const groupId = url.searchParams.get('groupId') || undefined;
const page = parseInt(url.searchParams.get('page') || '1', 10); const page = parseInt(url.searchParams.get('page') || '1', 10);
const pageSize = parseInt(url.searchParams.get('pageSize') || '10', 10); const pageSize = parseInt(url.searchParams.get('pageSize') || '10', 10);
// 构建搜索参数 // 构建搜索参数
const searchParams: DocumentTypeSearchParams = { const searchParams: DocumentTypeSearchParams = {
name, name,
group_id, ruleType,
groupId,
page, page,
pageSize pageSize
}; };
// 并行获取文档类型数据和所有评查点分组 // 并行获取文档类型数据和父级评查点分组
const [typesResult, groupsResult] = await Promise.all([ const ruleTypesResponse = await getRuleTypes();
getDocumentTypes(searchParams), if(ruleTypesResponse.error){
getAllEvaluationPointGroups() console.error("获取父级评查点分组失败:", ruleTypesResponse.error);
]); }
const ruleTypes = ruleTypesResponse.error ? [] : ruleTypesResponse.data;
const typesResponse = await getDocumentTypes(searchParams);
if(typesResponse.error){
console.error("获取文档类型失败:", typesResponse.error);
throw new Error(typesResponse.error);
}
const typesResult = typesResponse.data?.types || [];
// console.log('文档类型数据:', typesResult.data?.types); // console.log('文档类型数据:', typesResult.data?.types);
// console.log('评查点分组数据:', groupsResult.data); // console.log('父级评查点分组:', groupsResult.data);
if (typesResult.error) { return Response.json({
return json<LoaderData>( types: typesResult,
{ total: typesResponse.data?.total || typesResult.length,
types: [],
total: 0,
pageSize,
currentPage: page,
error: typesResult.error,
groups: groupsResult.data || []
},
{ status: typesResult.status || 500 }
);
}
return json<LoaderData>({
types: typesResult.data?.types || [],
total: typesResult.data?.total || 0,
pageSize, pageSize,
currentPage: page, currentPage: page,
groups: groupsResult.data || [] ruleTypes
}); });
} catch (error) { } catch (error) {
console.error("加载文档类型列表失败:", error); console.error("加载文档类型列表失败:", error);
return json<LoaderData>( return Response.json(
{ {
types: [], error: error || "加载文档类型列表失败",
total: 0, status: 500
pageSize: 10, }
currentPage: 1,
error: "加载文档类型列表失败",
groups: []
},
{ status: 500 }
); );
} }
} }
@@ -138,13 +132,60 @@ export default function DocumentTypesList() {
const [isDeleting, setIsDeleting] = useState(false); const [isDeleting, setIsDeleting] = useState(false);
// 获取加载器数据 // 获取加载器数据
const { types, total, pageSize: initialPageSize, currentPage: initialPage, error, groups } = useLoaderData<LoaderData>(); const { types, total, error, ruleTypes } = useLoaderData<LoaderData>();
// 状态管理
const [ruleGroups, setRuleGroups] = useState<RuleGroup[]>([]);
const [loadingGroups, setLoadingGroups] = useState(false);
// 获取当前的ruleType值
const ruleTypeParam = searchParams.get('ruleType');
// 获取搜索参数 // 获取搜索参数
const name = searchParams.get('name') || ''; const name = searchParams.get('name') || '';
const group_id = searchParams.get('group_id') || ''; const currentPage = parseInt(searchParams.get('page') || String(1), 10);
const currentPage = parseInt(searchParams.get('page') || String(initialPage), 10); const pageSize = parseInt(searchParams.get('pageSize') || String(10), 10);
const pageSize = parseInt(searchParams.get('pageSize') || String(initialPageSize), 10);
// 判断是否禁用子级评查分组选择,true表示禁用,false表示不禁用
const isRuleGroupSelectDisabled = loadingGroups || !ruleTypeParam || ruleGroups.length === 0;
// 当评查点类型变化时,加载对应的子级评查分组
useEffect(() => {
// 如果选择了"全部"或未选择,则清空子级评查分组
if (!ruleTypeParam || ruleTypeParam === 'all') {
setRuleGroups([]);
return;
}
// 加载当前类型的子级评查分组
const loadRuleGroups = async () => {
setLoadingGroups(true);
try {
const response = await getRuleGroupsByType(ruleTypeParam);
if (response.data) {
setRuleGroups(response.data);
} else if (response.error) {
console.error('加载子级规则组失败:', response.error);
setRuleGroups([]);
}
} catch (error) {
console.error('加载子级规则组出错:', error);
toastService.error('加载子级规则组出错:' + error);
setRuleGroups([]);
} finally {
setLoadingGroups(false);
}
};
loadRuleGroups();
}, [ruleTypeParam]);
// 处理loader加载数据的时候的错误
useEffect(() => {
if(error){
toastService.error(error);
}
}, [error]);
// 处理名称搜索 // 处理名称搜索
const handleNameSearch = (value: string) => { const handleNameSearch = (value: string) => {
@@ -158,29 +199,51 @@ export default function DocumentTypesList() {
setSearchParams(newParams); setSearchParams(newParams);
}; };
// 处理分组筛选 // 处理筛选变更
const handleGroupChange = (e: React.ChangeEvent<HTMLSelectElement>) => { const handleFilterChange = (e: React.ChangeEvent<HTMLSelectElement>) => {
const { value } = e.target; const { name, value } = e.target;
const newParams = new URLSearchParams(searchParams); const newParams = new URLSearchParams(searchParams);
if (value) {
newParams.set('group_id', value); // 如果是子级评查分组选择,但是当前应该被禁用,则不处理
} else { if (name === 'groupId' && isRuleGroupSelectDisabled) {
newParams.delete('group_id'); return;
} }
if (value) {
newParams.set(name, value);
// 如果是评查点类型变更,清空子级评查分组选择
if (name === 'ruleType') {
newParams.delete('groupId');
// 如果选择了"全部"或空值,也清空子级评查分组选择
if (value === '' || value === 'all') {
setRuleGroups([]);
}
}
} else {
newParams.delete(name);
// 如果清除评查点类型,也清除规则组
if (name === 'ruleType') {
newParams.delete('groupId');
setRuleGroups([]);
}
}
// 切换筛选条件时,重置到第一页
newParams.set('page', '1'); newParams.set('page', '1');
setSearchParams(newParams); setSearchParams(newParams);
}; };
// 处理重置筛选 // 处理重置筛选
const handleReset = () => { const handleReset = () => {
const nameInput = document.querySelector('input[name="name"]'); const nameInput = document.querySelector('input[placeholder="请输入文档类型名称"]');
if (nameInput) { if (nameInput) {
(nameInput as HTMLInputElement).value = ''; (nameInput as HTMLInputElement).value = '';
} }
const groupIdInput = document.querySelector('select[name="group_id"]');
if (groupIdInput) { // 重置所有筛选条件
(groupIdInput as HTMLSelectElement).value = '';
}
setSearchParams(new URLSearchParams()); setSearchParams(new URLSearchParams());
}; };
@@ -351,35 +414,12 @@ export default function DocumentTypesList() {
} }
noActionDivider={true} noActionDivider={true}
> >
<SearchFilter
label="类型名称"
placeholder="请输入文档类型名称"
value={name}
onSearch={handleNameSearch}
className="flex-1 min-w-[200px]"
instantSearch={true}
/>
<FilterSelect <FilterSelect
label="关联分组" label="父级评查分组"
name="group_id"
value={group_id}
options={[
...(groups || []).map(group => ({
value: group.id,
label: group.name
}))
]}
onChange={handleGroupChange}
className="flex-1 min-w-[200px]"
/>
{/* <FilterSelect
label="评查点类型"
name="ruleType" name="ruleType"
value={searchParams.get('ruleType') || ''} value={searchParams.get('ruleType') || ''}
options={[ options={[
...ruleTypes.map((type: ApiRuleType) => ({ ...(ruleTypes || []).map(type => ({
value: type.id, value: type.id,
label: type.name label: type.name
})) }))
@@ -389,7 +429,7 @@ export default function DocumentTypesList() {
/> />
<FilterSelect <FilterSelect
label="所属规则组" label="所属子级评查分组"
name="groupId" name="groupId"
value={searchParams.get('groupId') || ''} value={searchParams.get('groupId') || ''}
options={[ options={[
@@ -401,7 +441,16 @@ export default function DocumentTypesList() {
]} ]}
onChange={handleFilterChange} onChange={handleFilterChange}
className={`mr-3 w-[20%] ${isRuleGroupSelectDisabled ? 'opacity-50' : ''}`} className={`mr-3 w-[20%] ${isRuleGroupSelectDisabled ? 'opacity-50' : ''}`}
/> */} />
<SearchFilter
label="类型名称"
placeholder="请输入文档类型名称"
value={name}
onSearch={handleNameSearch}
className="flex-1 min-w-[200px]"
instantSearch={true}
/>
</FilterPanel> </FilterPanel>
+160 -83
View File
@@ -1,4 +1,4 @@
import React, { useState, useEffect } from "react"; import React, { useState, useEffect, useRef } from "react";
import { Form, useActionData, useLoaderData, useNavigate, useSearchParams } from "@remix-run/react"; import { Form, useActionData, useLoaderData, useNavigate, useSearchParams } from "@remix-run/react";
import { redirect, type MetaFunction, type ActionFunctionArgs, type LoaderFunctionArgs } from "@remix-run/node"; import { redirect, type MetaFunction, type ActionFunctionArgs, type LoaderFunctionArgs } from "@remix-run/node";
import { Card } from "~/components/ui/Card"; import { Card } from "~/components/ui/Card";
@@ -7,6 +7,7 @@ import documentTypesNewStyles from "~/styles/pages/document-types_new.css?url";
import { getAllRuleGroups, type RuleGroup } from "~/api/evaluation_points/rule-groups"; import { getAllRuleGroups, type RuleGroup } from "~/api/evaluation_points/rule-groups";
import { getDocumentType, createDocumentType, updateDocumentType } from "~/api/document-types/document-types"; import { getDocumentType, createDocumentType, updateDocumentType } from "~/api/document-types/document-types";
import { getPromptTemplates, type PromptTemplateUI } from "~/api/prompts/prompts"; import { getPromptTemplates, type PromptTemplateUI } from "~/api/prompts/prompts";
import { toastService } from "~/components/ui/Toast";
export function links() { export function links() {
return [{ rel: "stylesheet", href: documentTypesNewStyles }]; return [{ rel: "stylesheet", href: documentTypesNewStyles }];
@@ -42,8 +43,23 @@ const TEMPLATE_TYPES = {
SUMMARY: "Summary" SUMMARY: "Summary"
}; };
// 定义动作返回的数据类型
interface ActionData {
result?: boolean;
errors?: {
name?: string;
groups?: string;
general?: string;
llmExtractionTemplate?: string;
vlmExtractionTemplate?: string;
evaluationTemplate?: string;
summaryTemplate?: string;
};
values?: Record<string, string | string[]>;
}
// 加载函数 - 获取数据
// 获取数据
export async function loader({ request }: LoaderFunctionArgs) { export async function loader({ request }: LoaderFunctionArgs) {
try { try {
const url = new URL(request.url); const url = new URL(request.url);
@@ -54,6 +70,7 @@ export async function loader({ request }: LoaderFunctionArgs) {
const ruleGroupsResponse = await getAllRuleGroups(); const ruleGroupsResponse = await getAllRuleGroups();
if (ruleGroupsResponse.error) { if (ruleGroupsResponse.error) {
console.error("获取评查点分组失败:", ruleGroupsResponse.error); console.error("获取评查点分组失败:", ruleGroupsResponse.error);
// throw new Error(ruleGroupsResponse.error);
} }
// ruleGroupsResponse.data已经是树形结构数据,getAllRuleGroups内部已处理好parent-children关系 // ruleGroupsResponse.data已经是树形结构数据,getAllRuleGroups内部已处理好parent-children关系
@@ -96,26 +113,13 @@ export async function loader({ request }: LoaderFunctionArgs) {
llmExtractionTemplates: [], llmExtractionTemplates: [],
vlmExtractionTemplates: [], vlmExtractionTemplates: [],
evaluationTemplates: [], evaluationTemplates: [],
summaryTemplates: [] summaryTemplates: [],
error: error || "加载数据失败"
}); });
} }
} }
// 定义动作返回的数据类型 // 处理表单提交
interface ActionData {
success?: boolean;
errors?: {
name?: string;
groups?: string;
general?: string;
llmExtractionTemplate?: string;
vlmExtractionTemplate?: string;
evaluationTemplate?: string;
summaryTemplate?: string;
};
}
// 动作函数 - 处理表单提交
export async function action({ request }: ActionFunctionArgs) { export async function action({ request }: ActionFunctionArgs) {
const formData = await request.formData(); const formData = await request.formData();
const id = formData.get("id") as string | null; const id = formData.get("id") as string | null;
@@ -159,7 +163,7 @@ export async function action({ request }: ActionFunctionArgs) {
// 如果有错误,返回错误信息 // 如果有错误,返回错误信息
if (Object.keys(errors).length > 0) { if (Object.keys(errors).length > 0) {
return Response.json({ errors }); return Response.json({ errors, result: false });
} }
try { try {
@@ -198,7 +202,7 @@ export async function action({ request }: ActionFunctionArgs) {
} catch (error) { } catch (error) {
console.error("保存文档类型失败:", error); console.error("保存文档类型失败:", error);
return Response.json({ return Response.json({
success: false, result: false,
errors: { errors: {
general: error instanceof Error ? error.message : "保存文档类型失败" general: error instanceof Error ? error.message : "保存文档类型失败"
} }
@@ -235,12 +239,28 @@ export default function DocumentTypeNew() {
}); });
// 添加本地验证错误状态 // 添加本地验证错误状态
const [localErrors, setLocalErrors] = useState<ActionData["errors"]>({} as ActionData["errors"]); const [formErrors, setFormErrors] = useState<ActionData["errors"]>({} as ActionData["errors"]);
// 表单引用
const formRef = useRef<HTMLFormElement>(null);
// 字段是否被触摸过(用于确定何时显示错误)
const [touchedFields, setTouchedFields] = useState({
name: false,
llmExtractionTemplate: false,
vlmExtractionTemplate: false,
evaluationTemplate: false,
summaryTemplate: false,
groups: false
});
// 从actionData初始化本地错误 // 从actionData初始化本地错误
useEffect(() => { useEffect(() => {
if (actionData?.errors) { if (!actionData?.result) {
setLocalErrors(actionData.errors); setFormErrors(actionData?.errors);
if (actionData?.errors?.general) {
toastService.error(actionData?.errors?.general || "保存文档类型失败");
}
} }
}, [actionData]); }, [actionData]);
@@ -281,6 +301,26 @@ export default function DocumentTypeNew() {
} }
}, [documentType, ruleGroups]); }, [documentType, ruleGroups]);
// 验证表单字段
const validateField = (field: string, value: string | string[]): string => {
switch (field) {
case 'name':
return !value || (typeof value === 'string' && value.trim() === "") ? "文档类型名称不能为空" : "";
case 'llmExtractionTemplate':
return !value || (typeof value === 'string' && value.trim() === "") ? "请选择llm抽取提示词模板" : "";
case 'vlmExtractionTemplate':
return !value || (typeof value === 'string' && value.trim() === "") ? "请选择vlm抽取提示词模板" : "";
case 'evaluationTemplate':
return !value || (typeof value === 'string' && value.trim() === "") ? "请选择评查提示词模板" : "";
case 'summaryTemplate':
return !value || (typeof value === 'string' && value.trim() === "") ? "请选择总结提示词模板" : "";
case 'groups':
return Array.isArray(value) && value.length === 0 ? "请至少选择一个关联的评查点分组" : "";
default:
return "";
}
};
// 处理分组勾选 // 处理分组勾选
const handleGroupCheckChange = ( const handleGroupCheckChange = (
groupId: string, groupId: string,
@@ -292,18 +332,16 @@ export default function DocumentTypeNew() {
if (isChecked) { if (isChecked) {
// 只添加当前选中的分组 // 只添加当前选中的分组
newSelectedGroups = [groupId]; newSelectedGroups = [groupId];
// 如果选择的是父分组,不自动选择子分组
// 如果选择的是子分组,不影响父分组状态
} }
// 如果取消选中,则清空选择(在单选模式下可能不需要,但保留逻辑以防万一)
setFormData(prev => ({ ...prev, selectedGroups: newSelectedGroups })); setFormData(prev => ({ ...prev, selectedGroups: newSelectedGroups }));
// 清除groups相关的错误 // 标记字段为已触摸
if (localErrors?.groups) { setTouchedFields(prev => ({...prev, groups: true}));
setLocalErrors(prev => ({...prev, groups: undefined}));
} // 实时验证
const error = validateField('groups', newSelectedGroups);
setFormErrors(prev => ({...prev, groups: error}));
}; };
// 修复展开/折叠功能 // 修复展开/折叠功能
@@ -322,39 +360,78 @@ export default function DocumentTypeNew() {
const { name, value } = e.target; const { name, value } = e.target;
// 根据name属性映射到对应的formData字段 // 根据name属性映射到对应的formData字段
let fieldName = name;
if (name === 'llm_extraction_template') { if (name === 'llm_extraction_template') {
setFormData(prev => ({ ...prev, llmExtractionTemplateId: value })); fieldName = 'llmExtractionTemplateId';
// 清除相关错误 // 标记字段为已触摸
if (localErrors?.llmExtractionTemplate) { setTouchedFields(prev => ({...prev, llmExtractionTemplate: true}));
setLocalErrors(prev => ({...prev, llmExtractionTemplate: undefined}));
}
} else if (name === 'vlm_extraction_template') { } else if (name === 'vlm_extraction_template') {
setFormData(prev => ({ ...prev, vlmExtractionTemplateId: value })); fieldName = 'vlmExtractionTemplateId';
// 清除相关错误 // 标记字段为已触摸
if (localErrors?.vlmExtractionTemplate) { setTouchedFields(prev => ({...prev, vlmExtractionTemplate: true}));
setLocalErrors(prev => ({...prev, vlmExtractionTemplate: undefined}));
}
} else if (name === 'evaluation_template') { } else if (name === 'evaluation_template') {
setFormData(prev => ({ ...prev, evaluationTemplateId: value })); fieldName = 'evaluationTemplateId';
// 清除相关错误 // 标记字段为已触摸
if (localErrors?.evaluationTemplate) { setTouchedFields(prev => ({...prev, evaluationTemplate: true}));
setLocalErrors(prev => ({...prev, evaluationTemplate: undefined}));
}
} else if (name === 'summary_template') { } else if (name === 'summary_template') {
setFormData(prev => ({ ...prev, summaryTemplateId: value })); fieldName = 'summaryTemplateId';
// 清除相关错误 // 标记字段为已触摸
if (localErrors?.summaryTemplate) { setTouchedFields(prev => ({...prev, summaryTemplate: true}));
setLocalErrors(prev => ({...prev, summaryTemplate: undefined}));
}
} else if (name === 'name') { } else if (name === 'name') {
setFormData(prev => ({ ...prev, [name]: value })); // 标记字段为已触摸
// 清除相关错误 setTouchedFields(prev => ({...prev, name: true}));
if (localErrors?.name) { }
setLocalErrors(prev => ({...prev, name: undefined}));
} setFormData(prev => ({ ...prev, [fieldName]: value }));
} else {
// 其他表单字段(description等) // 实时验证
setFormData(prev => ({ ...prev, [name]: value })); if (name === 'name') {
const error = validateField(name, value);
setFormErrors(prev => ({...prev, name: error}));
} else if (name === 'llm_extraction_template') {
const error = validateField('llmExtractionTemplate', value);
setFormErrors(prev => ({...prev, llmExtractionTemplate: error}));
} else if (name === 'vlm_extraction_template') {
const error = validateField('vlmExtractionTemplate', value);
setFormErrors(prev => ({...prev, vlmExtractionTemplate: error}));
} else if (name === 'evaluation_template') {
const error = validateField('evaluationTemplate', value);
setFormErrors(prev => ({...prev, evaluationTemplate: error}));
} else if (name === 'summary_template') {
const error = validateField('summaryTemplate', value);
setFormErrors(prev => ({...prev, summaryTemplate: error}));
}
};
// 处理表单提交前验证
const handleBeforeSubmit = (e: React.FormEvent) => {
// 标记所有字段为已触摸
setTouchedFields({
name: true,
llmExtractionTemplate: true,
vlmExtractionTemplate: true,
evaluationTemplate: true,
summaryTemplate: true,
groups: true
});
// 验证所有字段
const errors = {
name: validateField('name', formData.name),
llmExtractionTemplate: validateField('llmExtractionTemplate', formData.llmExtractionTemplateId),
vlmExtractionTemplate: validateField('vlmExtractionTemplate', formData.vlmExtractionTemplateId),
evaluationTemplate: validateField('evaluationTemplate', formData.evaluationTemplateId),
summaryTemplate: validateField('summaryTemplate', formData.summaryTemplateId),
groups: validateField('groups', formData.selectedGroups)
};
setFormErrors(errors);
// 如果有错误,阻止提交
if (errors.name || errors.llmExtractionTemplate || errors.vlmExtractionTemplate ||
errors.evaluationTemplate || errors.summaryTemplate || errors.groups) {
e.preventDefault();
} }
}; };
@@ -384,16 +461,16 @@ export default function DocumentTypeNew() {
{/* 表单内容 */} {/* 表单内容 */}
<Card> <Card>
<Form id="type-form" method="post" noValidate> <Form id="type-form" method="post" noValidate ref={formRef} onSubmit={handleBeforeSubmit}>
{/* 如果是编辑模式,添加隐藏的ID字段 */} {/* 如果是编辑模式,添加隐藏的ID字段 */}
{formData.id && <input type="hidden" name="id" value={formData.id} />} {formData.id && <input type="hidden" name="id" value={formData.id} />}
<div className="grid grid-cols-1 gap-6"> <div className="grid grid-cols-1 gap-6">
{/* 错误提示 */} {/* 错误提示 */}
{localErrors?.general && ( {formErrors?.general && (
<div className="error-message general-error error-show"> <div className="error-message general-error">
<i className="ri-error-warning-line"></i> <i className="ri-error-warning-line"></i>
{localErrors.general} {formErrors.general}
</div> </div>
)} )}
@@ -406,16 +483,16 @@ export default function DocumentTypeNew() {
type="text" type="text"
id="type-name" id="type-name"
name="name" name="name"
className={`form-input ${localErrors?.name ? 'input-error' : ''}`} className={`form-input ${touchedFields.name && formErrors?.name ? 'input-error' : ''}`}
placeholder="请输入文档类型名称" placeholder="请输入文档类型名称"
value={formData.name} value={formData.name}
onChange={handleInputChange} onChange={handleInputChange}
required required
/> />
<div className="form-tip"></div> {touchedFields.name && formErrors?.name && (
{localErrors?.name && ( <div className="error-message">{formErrors.name}</div>
<div className="error-message error-show">{localErrors.name}</div>
)} )}
<div className="form-tip"></div>
</div> </div>
{/* 类型描述 */} {/* 类型描述 */}
@@ -440,7 +517,7 @@ export default function DocumentTypeNew() {
<select <select
id="llm-extraction-template" id="llm-extraction-template"
name="llm_extraction_template" name="llm_extraction_template"
className={`form-select ${localErrors?.llmExtractionTemplate ? 'input-error' : ''}`} className={`form-select ${touchedFields.llmExtractionTemplate && formErrors?.llmExtractionTemplate ? 'input-error' : ''}`}
value={formData.llmExtractionTemplateId} value={formData.llmExtractionTemplateId}
onChange={handleInputChange} onChange={handleInputChange}
> >
@@ -451,8 +528,8 @@ export default function DocumentTypeNew() {
</option> </option>
))} ))}
</select> </select>
{localErrors?.llmExtractionTemplate && ( {touchedFields.llmExtractionTemplate && formErrors?.llmExtractionTemplate && (
<div className="error-message error-show">{localErrors.llmExtractionTemplate}</div> <div className="error-message">{formErrors.llmExtractionTemplate}</div>
)} )}
<div className="form-tip">llm提示词模板</div> <div className="form-tip">llm提示词模板</div>
</div> </div>
@@ -463,7 +540,7 @@ export default function DocumentTypeNew() {
<select <select
id="vlm-extraction-template" id="vlm-extraction-template"
name="vlm_extraction_template" name="vlm_extraction_template"
className={`form-select ${localErrors?.vlmExtractionTemplate ? 'input-error' : ''}`} className={`form-select ${touchedFields.vlmExtractionTemplate && formErrors?.vlmExtractionTemplate ? 'input-error' : ''}`}
value={formData.vlmExtractionTemplateId} value={formData.vlmExtractionTemplateId}
onChange={handleInputChange} onChange={handleInputChange}
> >
@@ -474,8 +551,8 @@ export default function DocumentTypeNew() {
</option> </option>
))} ))}
</select> </select>
{localErrors?.vlmExtractionTemplate && ( {touchedFields.vlmExtractionTemplate && formErrors?.vlmExtractionTemplate && (
<div className="error-message error-show">{localErrors.vlmExtractionTemplate}</div> <div className="error-message">{formErrors.vlmExtractionTemplate}</div>
)} )}
<div className="form-tip">vlm提示词模板</div> <div className="form-tip">vlm提示词模板</div>
</div> </div>
@@ -486,7 +563,7 @@ export default function DocumentTypeNew() {
<select <select
id="evaluation-template" id="evaluation-template"
name="evaluation_template" name="evaluation_template"
className={`form-select ${localErrors?.evaluationTemplate ? 'input-error' : ''}`} className={`form-select ${touchedFields.evaluationTemplate && formErrors?.evaluationTemplate ? 'input-error' : ''}`}
value={formData.evaluationTemplateId} value={formData.evaluationTemplateId}
onChange={handleInputChange} onChange={handleInputChange}
> >
@@ -497,8 +574,8 @@ export default function DocumentTypeNew() {
</option> </option>
))} ))}
</select> </select>
{localErrors?.evaluationTemplate && ( {touchedFields.evaluationTemplate && formErrors?.evaluationTemplate && (
<div className="error-message error-show">{localErrors.evaluationTemplate}</div> <div className="error-message">{formErrors.evaluationTemplate}</div>
)} )}
<div className="form-tip"></div> <div className="form-tip"></div>
</div> </div>
@@ -509,7 +586,7 @@ export default function DocumentTypeNew() {
<select <select
id="summary-template" id="summary-template"
name="summary_template" name="summary_template"
className={`form-select ${localErrors?.summaryTemplate ? 'input-error' : ''}`} className={`form-select ${touchedFields.summaryTemplate && formErrors?.summaryTemplate ? 'input-error' : ''}`}
value={formData.summaryTemplateId} value={formData.summaryTemplateId}
onChange={handleInputChange} onChange={handleInputChange}
> >
@@ -520,8 +597,8 @@ export default function DocumentTypeNew() {
</option> </option>
))} ))}
</select> </select>
{localErrors?.summaryTemplate && ( {touchedFields.summaryTemplate && formErrors?.summaryTemplate && (
<div className="error-message error-show">{localErrors.summaryTemplate}</div> <div className="error-message">{formErrors.summaryTemplate}</div>
)} )}
<div className="form-tip"></div> <div className="form-tip"></div>
</div> </div>
@@ -534,7 +611,7 @@ export default function DocumentTypeNew() {
<span className="text-red-500">*</span> <span className="text-red-500">*</span>
</legend> </legend>
<div <div
className={`checkbox-group ${localErrors?.groups ? 'group-error' : ''}`} className={`checkbox-group ${touchedFields.groups && formErrors?.groups ? 'group-error' : ''}`}
aria-labelledby="checkpoint-groups-label" aria-labelledby="checkpoint-groups-label"
role="group" role="group"
> >
@@ -599,10 +676,10 @@ export default function DocumentTypeNew() {
</React.Fragment> </React.Fragment>
))} ))}
</div> </div>
<div className="form-tip"></div> {touchedFields.groups && formErrors?.groups && (
{localErrors?.groups && ( <div className="error-message">{formErrors.groups}</div>
<div className="error-message error-show">{localErrors.groups}</div>
)} )}
<div className="form-tip"></div>
</fieldset> </fieldset>
</div> </div>
</div> </div>
+26 -7
View File
@@ -44,6 +44,7 @@ import {
// 从ReviewPointsList组件中导入ReviewPoint类型 // 从ReviewPointsList组件中导入ReviewPoint类型
import { type ReviewPoint } from '~/components/reviews'; import { type ReviewPoint } from '~/components/reviews';
import { messageService } from "~/components/ui/MessageModal";
/** /**
@@ -202,17 +203,17 @@ export async function loader({ request }: LoaderFunctionArgs) {
const previousRoute = url.searchParams.get('previousRoute') || ''; const previousRoute = url.searchParams.get('previousRoute') || '';
// console.log("id-------",id); // console.log("id-------",id);
if (!id) { if (!id) {
return Response.json({ error: '评查ID不能为空' }, { status: 400 }); return Response.json({ result: false, message: '文件ID不能为空' });
} }
// 获取评查点数据
const reviewData = await getReviewPoints(id); const reviewData = await getReviewPoints(id);
// console.log("documentData-------",JSON.stringify(documentData.data,null,2)); // console.log("documentData-------",JSON.stringify(documentData.data,null,2));
// console.log("reviewData-------",JSON.stringify('data' in reviewData ? reviewData.data : '',null,2)); // console.log("reviewData-------",JSON.stringify('data' in reviewData ? reviewData.data : '',null,2));
if ('error' in reviewData && reviewData.error) { if ('error' in reviewData && reviewData.error) {
console.error("获取评查点数据错误:", reviewData.error); console.error("获取评查点数据错误:", reviewData.error);
return Response.json({ error: reviewData.error }, { status: reviewData.status || 500 }); return Response.json({ result: false, message: reviewData.error });
} }
// 确保reviewData有效且具有预期的属性 // 确保reviewData有效且具有预期的属性
@@ -225,24 +226,42 @@ export async function loader({ request }: LoaderFunctionArgs) {
statistics: reviewData.stats statistics: reviewData.stats
}); });
} else { } else {
console.error("返回的评查数据格式不正确"); console.error("返回的评查数据格式不正确",JSON.stringify(reviewData,null,2));
return Response.json({ error: '返回的评查数据格式不正确' }, { status: 500 }); return Response.json({ result: false, message: '返回的评查数据格式不正确' });
} }
} catch (error) { } catch (error) {
console.error('获取评查数据失败:', error); console.error('获取评查数据失败:', error);
return Response.json({ error: '获取评查数据失败' }, { status: 500 }); return Response.json({ result: false, message: '获取评查数据失败' });
} }
} }
export default function ReviewDetails() { export default function ReviewDetails() {
const navigate = useNavigate(); const navigate = useNavigate();
const { document, reviewPoints, statistics, reviewInfo } = useLoaderData<typeof loader>(); const loaderData = useLoaderData<typeof loader>();
const { document, reviewPoints, statistics, reviewInfo } = loaderData;
const [isLoading, setIsLoading] = useState(false); // 已经通过loader加载了数据,不需要再显示加载状态 const [isLoading, setIsLoading] = useState(false); // 已经通过loader加载了数据,不需要再显示加载状态
const [activeTab, setActiveTab] = useState<string>('preview'); // 'preview', 'analysis', 'fileinfo' const [activeTab, setActiveTab] = useState<string>('preview'); // 'preview', 'analysis', 'fileinfo'
const [reviewData, setReviewData] = useState<ReviewData | null>(null); const [reviewData, setReviewData] = useState<ReviewData | null>(null);
const [activeReviewPointResultId, setActiveReviewPointResultId] = useState<string | null>(null); const [activeReviewPointResultId, setActiveReviewPointResultId] = useState<string | null>(null);
const [targetPage, setTargetPage] = useState<number | undefined>(undefined); const [targetPage, setTargetPage] = useState<number | undefined>(undefined);
// loader 数据加载出错
useEffect(()=>{
if(Object.keys(loaderData).find(key => key === 'result') && !loaderData.result){
messageService.show({
title: '错误',
message: loaderData.message,
type: 'error',
confirmText: '确定',
cancelText: '',
onConfirm: () => {
navigate(-1);
}
})
}
},[loaderData, navigate]);
// 模拟获取评查数据 // 模拟获取评查数据
useEffect(() => { useEffect(() => {
if (!document) return; if (!document) return;
+1 -1
View File
@@ -115,7 +115,7 @@ export default function RulesFiles() {
// 处理初始加载数据loader的错误 // 处理初始加载数据loader的错误
useEffect(() => { useEffect(() => {
if(!result) { if(result === false && message) {
toastService.error(message); toastService.error(message);
} }
}, [result, message]); }, [result, message]);
+3 -1
View File
@@ -198,8 +198,10 @@ export default function RulesIndex() {
useEffect(() => { useEffect(() => {
if(loaderData.error) { if(loaderData.error) {
toastService.error(loaderData.error); toastService.error(loaderData.error);
}else if(loaderData.ruleTypes.length === 0){
toastService.error("评查点类型数据为空");
} }
}, [loaderData.error]); }, [loaderData.error,loaderData.ruleTypes]);
// 当评查点类型变化时,加载对应的规则组 // 当评查点类型变化时,加载对应的规则组
useEffect(() => { useEffect(() => {
+19 -2
View File
@@ -65,8 +65,25 @@
/* 清除按钮 */ /* 清除按钮 */
.search-box-clear { .search-box-clear {
@apply absolute right-3 top-1/2 transform -translate-y-1/2 @apply absolute right-3 top-1/2 transform
text-gray-400 hover:text-gray-600 cursor-pointer transition-colors duration-200; text-gray-400 hover:text-gray-600 cursor-pointer transition-colors duration-200
border-none bg-transparent p-0 outline-none;
}
/* 当搜索框内有按钮时,调整清除按钮位置和输入框右侧内边距 */
.search-box:not(.form-input-only) .form-input {
@apply pr-8;
}
/* 修正当搜索框右侧有搜索按钮时,清除图标的位置,确保它显示在输入框内部 */
.search-box:not(.form-input-only) .search-box-clear {
@apply right-20;
z-index: 10;
}
/* 清除按钮的图标样式 */
.search-box-clear i {
@apply text-lg;
} }
/* 带边框的搜索框 */ /* 带边框的搜索框 */
+3 -9
View File
@@ -38,22 +38,16 @@
} }
.document-type-new-page .error-message { .document-type-new-page .error-message {
@apply text-sm text-red-600 mt-1 font-medium; @apply text-sm text-red-600 mt-1;
display: flex; display: flex;
align-items: center; align-items: center;
} }
.document-type-new-page .error-message::before { /* .document-type-new-page .error-message::before {
content: "⚠️"; content: "⚠️";
margin-right: 0.25rem; margin-right: 0.25rem;
} } */
.document-type-new-page .error-show {
display: flex !important;
color: #ff4d4f;
font-weight: 500;
visibility: visible;
}
.document-type-new-page .general-error { .document-type-new-page .general-error {
@apply flex items-center p-3 mb-4 bg-red-50 rounded-md text-red-600 text-sm; @apply flex items-center p-3 mb-4 bg-red-50 rounded-md text-red-600 text-sm;