完善列表和编辑页面的数据验证和交互,实现服务端和客户端两重数据验证
This commit is contained in:
@@ -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',
|
||||||
|
|||||||
@@ -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表
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
@@ -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>
|
||||||
|
)}
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
|||||||
@@ -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" : ""}`}
|
||||||
|
|||||||
@@ -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">
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|
||||||
|
|||||||
@@ -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
@@ -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;
|
||||||
|
|||||||
@@ -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]);
|
||||||
|
|||||||
@@ -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(() => {
|
||||||
|
|||||||
@@ -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;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* 带边框的搜索框 */
|
/* 带边框的搜索框 */
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
Reference in New Issue
Block a user