新增提示Toast组件
This commit is contained in:
+19
-31
@@ -9,6 +9,7 @@ import { Tag } from "~/components/ui/Tag";
|
||||
import homeStyles from "~/styles/pages/home.css?url";
|
||||
import { getDocuments, type DocumentUI } from "~/api/files/documents";
|
||||
import { useState, useEffect } from "react";
|
||||
import { getHomeData } from "~/api/home/home";
|
||||
import dayjs from 'dayjs';
|
||||
|
||||
// 文件处理状态选项
|
||||
@@ -40,13 +41,6 @@ interface StatsData {
|
||||
passRate: number;
|
||||
}
|
||||
|
||||
|
||||
// interface LoaderData {
|
||||
// stats: StatsData;
|
||||
// recentFiles: RecentFile[];
|
||||
// }
|
||||
|
||||
|
||||
// 模拟数据,实际项目中应该从API获取
|
||||
export async function loader() {
|
||||
try {
|
||||
@@ -66,17 +60,11 @@ export async function loader() {
|
||||
console.log("recentFiles-------",recentFiles);
|
||||
|
||||
|
||||
// 模拟数据
|
||||
const stats = {
|
||||
totalFiles: 156,
|
||||
reviewedFiles: 124,
|
||||
pendingFiles: 32,
|
||||
passRate: 92.5
|
||||
} as StatsData;
|
||||
|
||||
const homeData = await getHomeData();
|
||||
console.log("homeData-------",homeData);
|
||||
|
||||
|
||||
return Response.json({ stats, recentFiles });
|
||||
return Response.json({ homeData, recentFiles });
|
||||
} catch (error) {
|
||||
// 错误处理
|
||||
console.error('Failed to fetch dashboard data:', error);
|
||||
@@ -88,7 +76,7 @@ export async function loader() {
|
||||
}
|
||||
|
||||
export default function Index() {
|
||||
const { stats, recentFiles } = useLoaderData<typeof loader>();
|
||||
const { homeData, recentFiles } = useLoaderData<typeof loader>();
|
||||
const [currentDateTime, setCurrentDateTime] = useState<{
|
||||
date: string;
|
||||
time: string;
|
||||
@@ -135,26 +123,26 @@ export default function Index() {
|
||||
<div className="stat-grid ">
|
||||
<StatCard
|
||||
title="今日待审文件"
|
||||
value={stats.totalFiles}
|
||||
icon="ri-file-list-3-line"
|
||||
/>
|
||||
value={homeData.todayPendingFiles}
|
||||
icon="ri-inbox-line"
|
||||
/>
|
||||
<StatCard
|
||||
title="本月已审核文件"
|
||||
value={stats.reviewedFiles}
|
||||
icon="ri-check-double-line"
|
||||
trend={{ value: 5.2, isUp: true }}
|
||||
value={homeData.monthlyReviewedFiles}
|
||||
icon="ri-file-search-line"
|
||||
trend={{ value: homeData.monthlyReviewGrowth.value, isUp: homeData.monthlyReviewGrowth.isUp }}
|
||||
/>
|
||||
<StatCard
|
||||
title="审核通过率"
|
||||
value={stats.pendingFiles}
|
||||
icon="ri-time-line"
|
||||
trend={{ value: 2.1, isUp: false }}
|
||||
title="本月审核通过率"
|
||||
value={homeData.monthlyPassRate}
|
||||
icon="ri-percent-line"
|
||||
trend={{ value: homeData.passRateGrowth.value, isUp: homeData.passRateGrowth.isUp }}
|
||||
/>
|
||||
<StatCard
|
||||
title="问题检出数"
|
||||
value={`${stats.passRate}%`}
|
||||
icon="ri-pie-chart-line"
|
||||
trend={{ value: 1.5, isUp: true }}
|
||||
title="本月问题检出数"
|
||||
value={homeData.issuesDetected}
|
||||
icon="ri-error-warning-line"
|
||||
trend={{ value: homeData.issuesGrowth.value, isUp: homeData.issuesGrowth.isUp }}
|
||||
/>
|
||||
</div>
|
||||
</Card>
|
||||
|
||||
@@ -373,6 +373,36 @@ export default function DocumentTypesList() {
|
||||
onChange={handleGroupChange}
|
||||
className="flex-1 min-w-[200px]"
|
||||
/>
|
||||
|
||||
{/* <FilterSelect
|
||||
label="评查点类型"
|
||||
name="ruleType"
|
||||
value={searchParams.get('ruleType') || ''}
|
||||
options={[
|
||||
...ruleTypes.map((type: ApiRuleType) => ({
|
||||
value: type.id,
|
||||
label: type.name
|
||||
}))
|
||||
]}
|
||||
onChange={handleFilterChange}
|
||||
className="mr-3 w-[20%]"
|
||||
/>
|
||||
|
||||
<FilterSelect
|
||||
label="所属规则组"
|
||||
name="groupId"
|
||||
value={searchParams.get('groupId') || ''}
|
||||
options={[
|
||||
...(isRuleGroupSelectDisabled ? [{ value: "", label: "请先选择评查点类型" }] : []),
|
||||
...ruleGroups.map(group => ({
|
||||
value: group.id,
|
||||
label: group.name
|
||||
}))
|
||||
]}
|
||||
onChange={handleFilterChange}
|
||||
className={`mr-3 w-[20%] ${isRuleGroupSelectDisabled ? 'opacity-50' : ''}`}
|
||||
/> */}
|
||||
|
||||
</FilterPanel>
|
||||
|
||||
{/* 数据表格 */}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { useState } from "react";
|
||||
import { useState, useEffect } from "react";
|
||||
import { useSearchParams, useLoaderData, useFetcher, useNavigate,Link } from "@remix-run/react";
|
||||
import { type MetaFunction, type ActionFunctionArgs, type LoaderFunctionArgs } from "@remix-run/node";
|
||||
import { Card } from "~/components/ui/Card";
|
||||
@@ -12,6 +12,7 @@ import documentsIndexStyles from "~/styles/pages/documents_index.css?url";
|
||||
import { getDocuments, deleteDocument, type DocumentUI } from "~/api/files/documents";
|
||||
import { getDocumentTypes } from "~/api/document-types/document-types";
|
||||
import { updateDocumentAuditStatus } from "~/api/evaluation_points/rules-files";
|
||||
import { toastService } from "~/components/ui/Toast";
|
||||
|
||||
// 导入样式
|
||||
export function links() {
|
||||
@@ -80,6 +81,12 @@ export const loader = async ({ request }: LoaderFunctionArgs) => {
|
||||
});
|
||||
};
|
||||
|
||||
// 定义action返回的数据类型
|
||||
interface ActionResponse {
|
||||
success: boolean;
|
||||
message: string;
|
||||
}
|
||||
|
||||
// 处理表单提交和删除等操作
|
||||
export const action = async ({ request }: ActionFunctionArgs) => {
|
||||
const formData = await request.formData();
|
||||
@@ -92,7 +99,6 @@ export const action = async ({ request }: ActionFunctionArgs) => {
|
||||
if (response.error) {
|
||||
return Response.json({ success: false, message: response.error }, { status: response.status || 500 });
|
||||
}
|
||||
|
||||
return Response.json({ success: true, message: "文档已成功删除" });
|
||||
}
|
||||
|
||||
@@ -109,7 +115,7 @@ export const action = async ({ request }: ActionFunctionArgs) => {
|
||||
message: `删除失败: ${failures.map(f => f.error).join(', ')}`
|
||||
}, { status: 400 });
|
||||
}
|
||||
|
||||
|
||||
return Response.json({ success: true, message: `已成功删除${ids.length}个文档` });
|
||||
}
|
||||
|
||||
@@ -133,6 +139,7 @@ const fileProcessingStatusOptions = [
|
||||
{ value: "Cutting", label: "切分中", icon: "ri-loader-line", color: "purple" },
|
||||
{ value: "Extractioning", label: "抽取中", icon: "ri-loader-line", color: "cyan" },
|
||||
{ value: "Evaluationing", label: "评查中", icon: "ri-loader-line", color: "teal" },
|
||||
{ value: "Failed", label: "抽取异常", icon: "ri-close-circle-line", color: "red" },
|
||||
{ value: "Processed", label: "已完成", icon: "ri-check-line", color: "green" },
|
||||
];
|
||||
|
||||
@@ -143,6 +150,7 @@ const fileStatusOptions = [
|
||||
{ value: "Cutting", label: "切分中" },
|
||||
{ value: "Extractioning", label: "抽取中" },
|
||||
{ value: "Evaluationing", label: "评查中" },
|
||||
{ value: "Failed", label: "抽取异常" },
|
||||
{ value: "Processed", label: "已完成" },
|
||||
];
|
||||
|
||||
@@ -182,7 +190,7 @@ export default function DocumentsIndex() {
|
||||
const [searchParams, setSearchParams] = useSearchParams();
|
||||
const [selectedRowKeys, setSelectedRowKeys] = useState<string[]>([]);
|
||||
const loaderData = useLoaderData<typeof loader>();
|
||||
const fetcher = useFetcher();
|
||||
const fetcher = useFetcher<ActionResponse>();
|
||||
const navigate = useNavigate();
|
||||
|
||||
// 从URL获取当前筛选条件
|
||||
@@ -363,27 +371,26 @@ export default function DocumentsIndex() {
|
||||
|
||||
// 删除文档
|
||||
const handleDelete = (id: string, name: string, fileStatus: string) => {
|
||||
if (fileStatus !== 'Processed') {
|
||||
alert('文档正在处理中,不能删除');
|
||||
// 禁止删除处理中的文件
|
||||
if (fileStatus !== "Processed" && fileStatus !== "Failed") {
|
||||
toastService.warning("文件正在处理中,无法删除");
|
||||
return;
|
||||
}
|
||||
if (window.confirm(`确认删除文档 "${name}"?`)) {
|
||||
// 使用fetcher提交表单
|
||||
const formData = new FormData();
|
||||
formData.append('_action', 'delete');
|
||||
formData.append('id', id);
|
||||
|
||||
if (confirm(`确定要删除文档"${name}"吗?`)) {
|
||||
const form = new FormData();
|
||||
form.append("_action", "delete");
|
||||
form.append("id", id);
|
||||
|
||||
fetcher.submit(formData, { method: 'post' });
|
||||
|
||||
// 更新选中行
|
||||
setSelectedRowKeys(selectedRowKeys.filter(key => key !== id));
|
||||
fetcher.submit(form, { method: "post" });
|
||||
}
|
||||
};
|
||||
|
||||
// 批量删除
|
||||
const handleBatchDelete = () => {
|
||||
if (selectedRowKeys.length === 0) {
|
||||
alert('请至少选择一个文档');
|
||||
// alert('请至少选择一个文档');
|
||||
toastService.error('请至少选择一个文档');
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -393,7 +400,8 @@ export default function DocumentsIndex() {
|
||||
);
|
||||
|
||||
if (hasProcessingFiles) {
|
||||
alert('存在服务器未处理完成的文件,请重新选择需要删除的文件');
|
||||
// alert('存在服务器未处理完成的文件,请重新选择需要删除的文件');
|
||||
toastService.error('存在服务器未处理完成的文件,请重新选择需要删除的文件');
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -430,7 +438,8 @@ export default function DocumentsIndex() {
|
||||
const handleExport = async () => {
|
||||
// 如果没有文档,显示提示信息
|
||||
if (documents.length === 0) {
|
||||
alert('当前页面没有文档可供导出');
|
||||
// alert('当前页面没有文档可供导出');
|
||||
toastService.error('当前页面没有文档可供导出');
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -480,7 +489,8 @@ export default function DocumentsIndex() {
|
||||
const failed = results.filter(r => r && !r.success).length;
|
||||
|
||||
if (succeeded === 0) {
|
||||
alert('所有文件下载失败');
|
||||
// alert('所有文件下载失败');
|
||||
toastService.error('所有文件下载失败');
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -503,36 +513,51 @@ export default function DocumentsIndex() {
|
||||
|
||||
// 显示结果消息
|
||||
if (failed > 0) {
|
||||
alert(`成功导出 ${succeeded} 个文件,${failed} 个文件失败`);
|
||||
// alert(`成功导出 ${succeeded} 个文件,${failed} 个文件失败`);
|
||||
toastService.warning(`成功导出 ${succeeded} 个文件,${failed} 个文件失败`);
|
||||
} else {
|
||||
alert(`成功导出 ${succeeded} 个文件`);
|
||||
// alert(`成功导出 ${succeeded} 个文件`);
|
||||
toastService.success(`成功导出 ${succeeded} 个文件`);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('导出文件失败:', error);
|
||||
alert(`导出文件失败: ${error instanceof Error ? error.message : '未知错误'}`);
|
||||
// alert(`导出文件失败: ${error instanceof Error ? error.message : '未知错误'}`);
|
||||
toastService.error(`导出文件失败: ${error instanceof Error ? error.message : '未知错误'}`);
|
||||
}
|
||||
};
|
||||
|
||||
// 开始审核
|
||||
const handleReviewFileClick = async (fileId: number, auditStatus: number | null) => {
|
||||
// 检查audit_status是否为0,如果是则更新为2
|
||||
if (auditStatus === 0) {
|
||||
if (auditStatus === 0 || auditStatus === null) {
|
||||
try {
|
||||
const response = await updateDocumentAuditStatus(fileId.toString(), 2);
|
||||
if (response.error) {
|
||||
console.error('更新文件审核状态失败:', response.error);
|
||||
alert('更新文件审核状态失败:' + (response.error || '未知错误'));
|
||||
// alert('更新文件审核状态失败:' + (response.error || '未知错误'));
|
||||
toastService.error('更新文件审核状态失败:' + (response.error || '未知错误'));
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('更新文件审核状态时出错:', error);
|
||||
alert('更新文件审核状态时出错:' + (error instanceof Error ? error.message : '未知错误'));
|
||||
// alert('更新文件审核状态时出错:' + (error instanceof Error ? error.message : '未知错误'));
|
||||
toastService.error('更新文件审核状态时出错:' + (error instanceof Error ? error.message : '未知错误'));
|
||||
}
|
||||
}
|
||||
|
||||
// 导航到评查详情页
|
||||
navigate(`/reviews?id=${fileId}`);
|
||||
navigate(`/reviews?id=${fileId}&previousRoute=documents`);
|
||||
};
|
||||
|
||||
// 使用useEffect监听fetcher状态变化并显示Toast
|
||||
useEffect(() => {
|
||||
if (fetcher.data && fetcher.state === 'idle') {
|
||||
if (fetcher.data.success) {
|
||||
toastService.success(fetcher.data.message);
|
||||
} else if (fetcher.data.message) {
|
||||
toastService.error(fetcher.data.message);
|
||||
}
|
||||
}
|
||||
}, [fetcher.data, fetcher.state]);
|
||||
|
||||
// 表格列定义
|
||||
const columns = [
|
||||
@@ -610,7 +635,7 @@ export default function DocumentsIndex() {
|
||||
const fileStatus = record.fileStatus || "-";
|
||||
const status = fileProcessingStatusOptions.find(s => s.value === fileStatus) ||
|
||||
fileProcessingStatusOptions[0];
|
||||
const isSpinning = fileStatus !== "Processed";
|
||||
const isSpinning = fileStatus !== "Processed" && fileStatus !== "Failed";
|
||||
return (
|
||||
<div className={`inline-flex items-center px-2 py-1 rounded-full text-xs bg-${status.color}-100 text-${status.color}-800`}>
|
||||
<i className={`${status.icon} ${isSpinning ? "animate-spin" : ""} mr-1`}></i>
|
||||
@@ -680,7 +705,7 @@ export default function DocumentsIndex() {
|
||||
</Link>
|
||||
) : (
|
||||
<Link
|
||||
to={`/reviews?id=${record.id}`}
|
||||
to={`/reviews?id=${record.id}&previousRoute=documents`}
|
||||
className="mr-1 hover:underline"
|
||||
>
|
||||
<i className="ri-eye-line"></i>
|
||||
|
||||
@@ -19,13 +19,14 @@ export const meta: MetaFunction = () => {
|
||||
];
|
||||
};
|
||||
|
||||
|
||||
|
||||
// 文档审核状态定义
|
||||
enum DocumentAuditStatus {
|
||||
FAIL = -1,
|
||||
WAITING = 0,
|
||||
PASS = 1,
|
||||
WARNING = 2,
|
||||
PROCESSING = 3
|
||||
WAITING = 0,
|
||||
PROCESSING = 2
|
||||
}
|
||||
|
||||
// 文档状态对应的中文标签
|
||||
@@ -33,7 +34,6 @@ const STATUS_LABELS: Record<DocumentAuditStatus, string> = {
|
||||
[DocumentAuditStatus.FAIL]: "不通过",
|
||||
[DocumentAuditStatus.WAITING]: "待审核",
|
||||
[DocumentAuditStatus.PASS]: "通过",
|
||||
[DocumentAuditStatus.WARNING]: "警告",
|
||||
[DocumentAuditStatus.PROCESSING]: "审核中"
|
||||
};
|
||||
|
||||
@@ -314,7 +314,6 @@ export default function DocumentEdit() {
|
||||
<option value={DocumentAuditStatus.WAITING}>{STATUS_LABELS[DocumentAuditStatus.WAITING]}</option>
|
||||
<option value={DocumentAuditStatus.PROCESSING}>{STATUS_LABELS[DocumentAuditStatus.PROCESSING]}</option>
|
||||
<option value={DocumentAuditStatus.PASS}>{STATUS_LABELS[DocumentAuditStatus.PASS]}</option>
|
||||
<option value={DocumentAuditStatus.WARNING}>{STATUS_LABELS[DocumentAuditStatus.WARNING]}</option>
|
||||
<option value={DocumentAuditStatus.FAIL}>{STATUS_LABELS[DocumentAuditStatus.FAIL]}</option>
|
||||
</select>
|
||||
<div className="text-sm text-secondary mt-1">更改状态可能会影响此文档在列表中的显示和排序</div>
|
||||
|
||||
@@ -0,0 +1,204 @@
|
||||
import { useState } from 'react';
|
||||
import { MessageModal, messageService, MessageModalProvider } from '~/components/ui/MessageModal';
|
||||
import type { MessageType } from '~/components/ui/MessageModal';
|
||||
import { LinksFunction } from '@remix-run/node';
|
||||
import messageModalStyles from '~/styles/components/message-modal.css?url';
|
||||
|
||||
export const links: LinksFunction = () => [
|
||||
{ rel: "stylesheet", href: messageModalStyles },
|
||||
];
|
||||
|
||||
export default function MessageModalExample() {
|
||||
const [isModalOpen, setIsModalOpen] = useState(false);
|
||||
const [modalType, setModalType] = useState<MessageType>('info');
|
||||
const [modalTitle, setModalTitle] = useState('');
|
||||
const [modalMessage, setModalMessage] = useState('');
|
||||
const [withConfirm, setWithConfirm] = useState(false);
|
||||
const [autoClose, setAutoClose] = useState(false);
|
||||
|
||||
// 打开普通模态框
|
||||
const openModal = (type: MessageType, title: string, message: string) => {
|
||||
setModalType(type);
|
||||
setModalTitle(title);
|
||||
setModalMessage(message);
|
||||
setIsModalOpen(true);
|
||||
};
|
||||
|
||||
// 打开各类型的服务提示框
|
||||
const showSuccessMessage = () => {
|
||||
messageService.success('操作成功完成!', {
|
||||
title: '成功提示',
|
||||
autoClose: true
|
||||
});
|
||||
};
|
||||
|
||||
const showErrorMessage = () => {
|
||||
messageService.error('操作过程中发生错误,请重试。', {
|
||||
title: '错误提示'
|
||||
});
|
||||
};
|
||||
|
||||
const showWarningMessage = () => {
|
||||
messageService.warning('此操作可能产生不可逆转的结果。', {
|
||||
title: '警告提示',
|
||||
onConfirm: () => {
|
||||
messageService.success('您已确认继续操作')
|
||||
},
|
||||
confirmText: '继续操作',
|
||||
cancelText: '取消'
|
||||
});
|
||||
};
|
||||
|
||||
const showInfoMessage = () => {
|
||||
messageService.info('系统将于今晚10点进行升级维护。', {
|
||||
title: '通知',
|
||||
autoClose: true,
|
||||
autoCloseDelay: 5000
|
||||
});
|
||||
};
|
||||
|
||||
const showCustomMessage = () => {
|
||||
messageService.show({
|
||||
title: '自定义消息',
|
||||
message: '这是一个带有自定义内容的消息',
|
||||
type: 'info',
|
||||
confirmText: '了解',
|
||||
children: (
|
||||
<div style={{
|
||||
padding: '10px',
|
||||
backgroundColor: '#f0f0f0',
|
||||
borderRadius: '6px',
|
||||
marginTop: '10px'
|
||||
}}>
|
||||
<p>这里可以放置更复杂的自定义内容。</p>
|
||||
<div style={{
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
marginTop: '10px'
|
||||
}}>
|
||||
<i className="ri-information-line" style={{ marginRight: '8px', color: '#1890ff' }}></i>
|
||||
<span>比如详细的操作说明或其他信息。</span>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
});
|
||||
};
|
||||
|
||||
// 处理确认
|
||||
const handleConfirm = () => {
|
||||
messageService.success('您点击了确认按钮!', { autoClose: true });
|
||||
setIsModalOpen(false);
|
||||
};
|
||||
|
||||
return (
|
||||
<MessageModalProvider>
|
||||
<div className="container mx-auto p-6">
|
||||
<h1 className="text-2xl font-bold mb-6">消息模态框示例</h1>
|
||||
|
||||
<div className="bg-white rounded-lg p-6 shadow-md mb-8">
|
||||
<h2 className="text-xl font-semibold mb-4">基本用法</h2>
|
||||
<p className="mb-4">您可以直接使用MessageModal组件来控制模态框的显示和隐藏。</p>
|
||||
|
||||
<div className="flex flex-wrap gap-3 mb-6">
|
||||
<button
|
||||
className="px-4 py-2 bg-blue-500 text-white rounded hover:bg-blue-600"
|
||||
onClick={() => openModal('info', '信息提示', '这是一个普通的信息提示框')}
|
||||
>
|
||||
信息提示框
|
||||
</button>
|
||||
<button
|
||||
className="px-4 py-2 bg-green-500 text-white rounded hover:bg-green-600"
|
||||
onClick={() => openModal('success', '成功提示', '操作已成功完成')}
|
||||
>
|
||||
成功提示框
|
||||
</button>
|
||||
<button
|
||||
className="px-4 py-2 bg-red-500 text-white rounded hover:bg-red-600"
|
||||
onClick={() => openModal('error', '错误提示', '操作失败,请检查输入')}
|
||||
>
|
||||
错误提示框
|
||||
</button>
|
||||
<button
|
||||
className="px-4 py-2 bg-yellow-500 text-white rounded hover:bg-yellow-600"
|
||||
onClick={() => openModal('warning', '警告提示', '此操作将删除数据,是否继续?')}
|
||||
>
|
||||
警告提示框
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div className="flex flex-wrap gap-4 mb-4">
|
||||
<label className="flex items-center">
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={withConfirm}
|
||||
onChange={(e) => setWithConfirm(e.target.checked)}
|
||||
className="mr-2"
|
||||
/>
|
||||
添加确认按钮
|
||||
</label>
|
||||
|
||||
<label className="flex items-center">
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={autoClose}
|
||||
onChange={(e) => setAutoClose(e.target.checked)}
|
||||
className="mr-2"
|
||||
/>
|
||||
自动关闭 (3秒)
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<MessageModal
|
||||
isOpen={isModalOpen}
|
||||
onClose={() => setIsModalOpen(false)}
|
||||
title={modalTitle}
|
||||
message={modalMessage}
|
||||
type={modalType}
|
||||
autoClose={autoClose}
|
||||
onConfirm={withConfirm ? handleConfirm : undefined}
|
||||
confirmText={withConfirm ? "确认" : "我知道了"}
|
||||
cancelText="取消"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="bg-white rounded-lg p-6 shadow-md mb-8">
|
||||
<h2 className="text-xl font-semibold mb-4">全局消息服务</h2>
|
||||
<p className="mb-4">使用messageService可以在任何组件中方便地显示消息提示,而无需手动管理状态。</p>
|
||||
|
||||
<div className="flex flex-wrap gap-3">
|
||||
<button
|
||||
className="px-4 py-2 bg-green-500 text-white rounded hover:bg-green-600"
|
||||
onClick={showSuccessMessage}
|
||||
>
|
||||
成功提示 (自动关闭)
|
||||
</button>
|
||||
<button
|
||||
className="px-4 py-2 bg-red-500 text-white rounded hover:bg-red-600"
|
||||
onClick={showErrorMessage}
|
||||
>
|
||||
错误提示
|
||||
</button>
|
||||
<button
|
||||
className="px-4 py-2 bg-yellow-500 text-white rounded hover:bg-yellow-600"
|
||||
onClick={showWarningMessage}
|
||||
>
|
||||
警告提示 (带确认)
|
||||
</button>
|
||||
<button
|
||||
className="px-4 py-2 bg-blue-500 text-white rounded hover:bg-blue-600"
|
||||
onClick={showInfoMessage}
|
||||
>
|
||||
信息提示 (5秒后关闭)
|
||||
</button>
|
||||
<button
|
||||
className="px-4 py-2 bg-purple-500 text-white rounded hover:bg-purple-600"
|
||||
onClick={showCustomMessage}
|
||||
>
|
||||
自定义内容提示
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</MessageModalProvider>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,164 @@
|
||||
import { useState } from 'react';
|
||||
import { Toast, toastService } from '~/components/ui/Toast';
|
||||
import type { ToastType } from '~/components/ui/Toast';
|
||||
import { LinksFunction } from '@remix-run/node';
|
||||
import toastStyles from '~/styles/components/toast.css?url';
|
||||
|
||||
export const links: LinksFunction = () => [
|
||||
{ rel: "stylesheet", href: toastStyles },
|
||||
];
|
||||
|
||||
export default function ToastExample() {
|
||||
const [isToastOpen, setIsToastOpen] = useState(false);
|
||||
const [toastType, setToastType] = useState<ToastType>('info');
|
||||
const [toastMessage, setToastMessage] = useState('');
|
||||
const [autoClose, setAutoClose] = useState(true);
|
||||
|
||||
// 打开普通通知
|
||||
const openToast = (type: ToastType, message: string) => {
|
||||
setToastType(type);
|
||||
setToastMessage(message);
|
||||
setIsToastOpen(true);
|
||||
};
|
||||
|
||||
// 使用服务显示不同类型通知
|
||||
const showSuccessToast = () => {
|
||||
toastService.success('操作成功完成!');
|
||||
};
|
||||
|
||||
const showErrorToast = () => {
|
||||
toastService.error('操作过程中发生错误,请重试。');
|
||||
};
|
||||
|
||||
const showWarningToast = () => {
|
||||
toastService.warning('此操作可能产生不可逆转的结果。');
|
||||
};
|
||||
|
||||
const showInfoToast = () => {
|
||||
toastService.info('系统将于今晚10点进行升级维护。');
|
||||
};
|
||||
|
||||
// 显示多行文本的长通知
|
||||
const showLongToast = () => {
|
||||
toastService.info('这是一个具有很长内容的通知,将自动换行以适应容器宽度,并且最多显示三行,超出部分会被截断。系统会自动处理长文本的换行和截断,确保显示效果一致。');
|
||||
};
|
||||
|
||||
// 短时间内显示多个通知
|
||||
const showMultipleToasts = () => {
|
||||
toastService.success('第一条通知');
|
||||
setTimeout(() => {
|
||||
toastService.info('第二条通知');
|
||||
}, 300);
|
||||
setTimeout(() => {
|
||||
toastService.warning('第三条通知');
|
||||
}, 600);
|
||||
setTimeout(() => {
|
||||
toastService.error('第四条通知');
|
||||
}, 900);
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="container mx-auto p-6">
|
||||
<h1 className="text-2xl font-bold mb-6">轻量级顶部通知示例</h1>
|
||||
|
||||
<div className="bg-white rounded-lg p-6 shadow-md mb-8">
|
||||
<h2 className="text-xl font-semibold mb-4">基本用法</h2>
|
||||
<p className="mb-4">您可以直接使用Toast组件来控制通知的显示和隐藏。</p>
|
||||
|
||||
<div className="flex flex-wrap gap-3 mb-6">
|
||||
<button
|
||||
className="px-4 py-2 bg-blue-500 text-white rounded hover:bg-blue-600"
|
||||
onClick={() => openToast('info', '这是一个信息通知')}
|
||||
>
|
||||
信息通知
|
||||
</button>
|
||||
<button
|
||||
className="px-4 py-2 bg-green-500 text-white rounded hover:bg-green-600"
|
||||
onClick={() => openToast('success', '操作已成功完成')}
|
||||
>
|
||||
成功通知
|
||||
</button>
|
||||
<button
|
||||
className="px-4 py-2 bg-red-500 text-white rounded hover:bg-red-600"
|
||||
onClick={() => openToast('error', '操作失败,请检查输入')}
|
||||
>
|
||||
错误通知
|
||||
</button>
|
||||
<button
|
||||
className="px-4 py-2 bg-yellow-500 text-white rounded hover:bg-yellow-600"
|
||||
onClick={() => openToast('warning', '请注意,这是一个警告通知')}
|
||||
>
|
||||
警告通知
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div className="mb-4">
|
||||
<label className="flex items-center">
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={autoClose}
|
||||
onChange={(e) => setAutoClose(e.target.checked)}
|
||||
className="mr-2"
|
||||
/>
|
||||
自动关闭 (3秒)
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<Toast
|
||||
isOpen={isToastOpen}
|
||||
onClose={() => setIsToastOpen(false)}
|
||||
message={toastMessage}
|
||||
type={toastType}
|
||||
autoClose={autoClose}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="bg-white rounded-lg p-6 shadow-md mb-8">
|
||||
<h2 className="text-xl font-semibold mb-4">全局通知服务</h2>
|
||||
<p className="mb-4">使用toastService可以在任何组件中方便地显示通知,而无需手动管理状态。</p>
|
||||
|
||||
<div className="flex flex-wrap gap-3 mb-6">
|
||||
<button
|
||||
className="px-4 py-2 bg-green-500 text-white rounded hover:bg-green-600"
|
||||
onClick={showSuccessToast}
|
||||
>
|
||||
成功通知
|
||||
</button>
|
||||
<button
|
||||
className="px-4 py-2 bg-red-500 text-white rounded hover:bg-red-600"
|
||||
onClick={showErrorToast}
|
||||
>
|
||||
错误通知
|
||||
</button>
|
||||
<button
|
||||
className="px-4 py-2 bg-yellow-500 text-white rounded hover:bg-yellow-600"
|
||||
onClick={showWarningToast}
|
||||
>
|
||||
警告通知
|
||||
</button>
|
||||
<button
|
||||
className="px-4 py-2 bg-blue-500 text-white rounded hover:bg-blue-600"
|
||||
onClick={showInfoToast}
|
||||
>
|
||||
信息通知
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div className="flex flex-wrap gap-3">
|
||||
<button
|
||||
className="px-4 py-2 bg-purple-500 text-white rounded hover:bg-purple-600"
|
||||
onClick={showLongToast}
|
||||
>
|
||||
长文本通知
|
||||
</button>
|
||||
<button
|
||||
className="px-4 py-2 bg-pink-500 text-white rounded hover:bg-pink-600"
|
||||
onClick={showMultipleToasts}
|
||||
>
|
||||
多个通知
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -19,6 +19,7 @@ import {
|
||||
type FileUploadResponse,
|
||||
DocumentStatus
|
||||
} from "~/api/files/files-upload";
|
||||
import { updateDocumentAuditStatus } from "~/api/evaluation_points/rules-files";
|
||||
|
||||
export function links() {
|
||||
return [
|
||||
@@ -781,6 +782,24 @@ export default function FilesUpload() {
|
||||
const type = documentTypesState.find(t => t.id === codeId);
|
||||
return type ? type.name : '未知类型';
|
||||
};
|
||||
|
||||
// 处理查看文件
|
||||
const handleViewFile = async (record: Document) => {
|
||||
// 检查audit_status是否为0,如果是则更新为2
|
||||
if (record.audit_status === 0 || record.audit_status === null) {
|
||||
try {
|
||||
const response = await updateDocumentAuditStatus(record.id.toString(), 2);
|
||||
if (response.error) {
|
||||
console.error('更新文件审核状态失败:', response.error);
|
||||
alert('更新文件审核状态失败:' + (response.error || '未知错误'));
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('更新文件审核状态时出错:', error);
|
||||
alert('更新文件审核状态时出错:' + (error instanceof Error ? error.message : '未知错误'));
|
||||
}
|
||||
}
|
||||
navigate(`/reviews?id=${record.id}&previousRoute=filesUpload`);
|
||||
};
|
||||
|
||||
// 表格列定义
|
||||
const columns = [
|
||||
@@ -880,7 +899,7 @@ export default function FilesUpload() {
|
||||
size="small"
|
||||
disabled={record.status !== DocumentStatus.PROCESSED}
|
||||
icon="ri-eye-line"
|
||||
onClick={() => navigate(`/reviews?id=${record.id}`)}
|
||||
onClick={() => handleViewFile(record)}
|
||||
>
|
||||
查看
|
||||
</Button>
|
||||
|
||||
+81
-7
@@ -29,7 +29,7 @@ import { type MetaFunction, type LoaderFunctionArgs } from "@remix-run/node";
|
||||
import { useState, useEffect } from "react";
|
||||
import { useNavigate, useLoaderData } from "@remix-run/react";
|
||||
import reviewsStyles from "~/styles/reviews.css?url";
|
||||
import { getReviewPoints, updateReviewResult } from "~/api/evaluation_points/reviews";
|
||||
import { getReviewPoints, updateReviewResult, confirmReviewResults } from "~/api/evaluation_points/reviews";
|
||||
|
||||
// 导入评查详情页面组件
|
||||
import {
|
||||
@@ -44,6 +44,7 @@ import {
|
||||
// 从ReviewPointsList组件中导入ReviewPoint类型
|
||||
import { type ReviewPoint } from '~/components/reviews';
|
||||
|
||||
|
||||
/**
|
||||
* 文件信息组件
|
||||
* 显示文件名称、状态信息以及操作按钮(下载原文件、导出评查报告、确认评查结果)
|
||||
@@ -148,6 +149,9 @@ interface ReviewData {
|
||||
aiAnalysis: AnalysisData;
|
||||
}
|
||||
|
||||
interface LoaderData {
|
||||
previousRoute: string;
|
||||
}
|
||||
export const meta: MetaFunction = () => {
|
||||
return [
|
||||
{ title: "评查详情 - 中国烟草AI合同及卷宗审核系统" },
|
||||
@@ -163,13 +167,38 @@ export function links() {
|
||||
}
|
||||
|
||||
export const handle = {
|
||||
breadcrumb: "评查详情"
|
||||
breadcrumb: "评查详情",
|
||||
// 添加一个previousRoute属性用于支持自定义的面包屑导航
|
||||
previousRoute: (data:LoaderData)=>{
|
||||
if(data.previousRoute){
|
||||
if(data.previousRoute === 'filesUpload'){
|
||||
return {
|
||||
title: "文件上传",
|
||||
to: "/files/upload"
|
||||
}
|
||||
}
|
||||
if(data.previousRoute === 'documents'){
|
||||
return {
|
||||
title: "文档列表",
|
||||
to: "/documents"
|
||||
}
|
||||
}
|
||||
if(data.previousRoute === 'rulesFiles'){
|
||||
return {
|
||||
title: "评查文件列表",
|
||||
to: "/rules-files"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
export async function loader({ request }: LoaderFunctionArgs) {
|
||||
try {
|
||||
const url = new URL(request.url);
|
||||
const id = url.searchParams.get('id') || undefined;
|
||||
const previousRoute = url.searchParams.get('previousRoute') || '';
|
||||
// console.log("id-------",id);
|
||||
if (!id) {
|
||||
return Response.json({ error: '评查ID不能为空' }, { status: 400 });
|
||||
@@ -188,6 +217,7 @@ export async function loader({ request }: LoaderFunctionArgs) {
|
||||
// 确保reviewData有效且具有预期的属性
|
||||
if ('document' in reviewData && 'data' in reviewData && 'reviewInfo' in reviewData && 'stats' in reviewData) {
|
||||
return Response.json({
|
||||
previousRoute: previousRoute,
|
||||
document: reviewData.document,
|
||||
reviewPoints: reviewData.data,
|
||||
reviewInfo: reviewData.reviewInfo,
|
||||
@@ -219,6 +249,7 @@ export default function ReviewDetails() {
|
||||
// 构建文件信息对象
|
||||
const fileInfo = {
|
||||
fileName: document.name || "未知文件名",
|
||||
path: document.path || "未知路径",
|
||||
contractNumber: document.documentNumber || "未知编号",
|
||||
fileSize: document.size ? formatFileSize(document.size) : "未知大小",
|
||||
fileFormat: document.fileType ? document.fileType.toUpperCase() : "未知格式",
|
||||
@@ -252,8 +283,19 @@ export default function ReviewDetails() {
|
||||
};
|
||||
|
||||
const handleReviewPointSelect = (reviewPointId: string, page?: number) => {
|
||||
setActiveReviewPointId(reviewPointId);
|
||||
setTargetPage(page);
|
||||
// 如果点击的是相同的评查点,但有page参数,先重置targetPage以确保useEffect能够触发
|
||||
if (reviewPointId === activeReviewPointId && page) {
|
||||
setTargetPage(undefined);
|
||||
// 使用setTimeout确保状态更新后再设置新的targetPage
|
||||
setTimeout(() => {
|
||||
setActiveReviewPointId(reviewPointId);
|
||||
setTargetPage(page);
|
||||
}, 0);
|
||||
} else {
|
||||
// 正常设置activeReviewPointId和targetPage
|
||||
setActiveReviewPointId(reviewPointId);
|
||||
setTargetPage(page);
|
||||
}
|
||||
};
|
||||
|
||||
// 刷新评审数据
|
||||
@@ -378,9 +420,36 @@ export default function ReviewDetails() {
|
||||
}
|
||||
};
|
||||
|
||||
const handleConfirmResults = () => {
|
||||
alert('评查结果已确认');
|
||||
navigate('/reviews'); // 假设评查列表页面路径为 /reviews
|
||||
const handleConfirmResults = async () => {
|
||||
if (!document || !document.id) {
|
||||
alert('文档数据不完整,无法确认评查结果');
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
// 显示加载状态
|
||||
setIsLoading(true);
|
||||
|
||||
// 调用API确认评查结果
|
||||
const response = await confirmReviewResults(document.id.toString());
|
||||
|
||||
if (response.error) {
|
||||
console.error('确认评查结果失败:', response.error);
|
||||
alert(`确认评查结果失败: ${response.error}`);
|
||||
return;
|
||||
}
|
||||
|
||||
// 显示成功消息
|
||||
alert('评查结果已确认,文档审核状态已更新');
|
||||
|
||||
// 导航到文档列表页
|
||||
navigate('/documents');
|
||||
} catch (error) {
|
||||
console.error('确认评查结果出错:', error);
|
||||
alert(`确认评查结果失败: ${error instanceof Error ? error.message : '未知错误'}`);
|
||||
} finally {
|
||||
setIsLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
@@ -529,6 +598,7 @@ function getMockReviewData(): ReviewData {
|
||||
reviewPoints: [
|
||||
{
|
||||
id: "1",
|
||||
pointName: "付款条款",
|
||||
title: "付款条件描述不明确",
|
||||
groupName: "付款条款清晰性",
|
||||
// location: "交货与付款条款",
|
||||
@@ -540,6 +610,7 @@ function getMockReviewData(): ReviewData {
|
||||
},
|
||||
{
|
||||
id: "2",
|
||||
pointName: "违约责任",
|
||||
title: "违约责任条款缺失",
|
||||
groupName: "合同权利义务对等性",
|
||||
status: "warning",
|
||||
@@ -550,6 +621,7 @@ function getMockReviewData(): ReviewData {
|
||||
},
|
||||
{
|
||||
id: "3",
|
||||
pointName: "签章审核",
|
||||
title: "签章不完整",
|
||||
groupName: "合同签署规范性",
|
||||
status: "warning",
|
||||
@@ -562,6 +634,7 @@ function getMockReviewData(): ReviewData {
|
||||
},
|
||||
{
|
||||
id: "9",
|
||||
pointName: "交货方式",
|
||||
title: "交货方式描述模糊",
|
||||
groupName: "履行条款明确性",
|
||||
status: "success",
|
||||
@@ -576,6 +649,7 @@ function getMockReviewData(): ReviewData {
|
||||
},
|
||||
{
|
||||
id: "10",
|
||||
pointName: "法律适用",
|
||||
title: "法律适用条款缺失",
|
||||
groupName: "争议解决条款完整性",
|
||||
status: "error",
|
||||
|
||||
@@ -542,7 +542,12 @@ export default function RuleGroupsIndex() {
|
||||
<div className="content-container rule-groups-page">
|
||||
{/* 页面头部 */}
|
||||
<div className="flex justify-between items-center mb-4">
|
||||
<h2 className="text-xl font-medium">评查点分组管理</h2>
|
||||
<h2 className="text-xl font-medium flex">评查点分组管理
|
||||
<div className="flex items-center bg-white px-3 py-1 rounded-md">
|
||||
<span className="text-sm text-secondary">分组数:</span>
|
||||
<span className="text-base font-normal text-primary ml-1">{processedData.length}</span>
|
||||
</div>
|
||||
</h2>
|
||||
<div className="flex">
|
||||
<Button
|
||||
type="default"
|
||||
@@ -634,13 +639,13 @@ export default function RuleGroupsIndex() {
|
||||
/>
|
||||
|
||||
{/* 分页 - 使用Pagination组件 */}
|
||||
<Pagination
|
||||
{/* <Pagination
|
||||
currentPage={1}
|
||||
total={processedData.length}
|
||||
pageSize={10}
|
||||
onChange={() => {}}
|
||||
showTotal={true}
|
||||
/>
|
||||
/> */}
|
||||
</>
|
||||
)}
|
||||
</Card>
|
||||
|
||||
@@ -167,7 +167,7 @@ export default function RulesFiles() {
|
||||
// 查看评查文件
|
||||
const handleReviewFileClick = async (fileId: string, auditStatus: number | null) => {
|
||||
// 检查audit_status是否为0,如果是则更新为2
|
||||
if (auditStatus === 0) {
|
||||
if (auditStatus === 0 || auditStatus === null) {
|
||||
try {
|
||||
const response = await updateDocumentAuditStatus(fileId, 2);
|
||||
if (response.error) {
|
||||
@@ -181,7 +181,7 @@ export default function RulesFiles() {
|
||||
}
|
||||
|
||||
// 导航到评查详情页
|
||||
navigate(`/reviews?id=${fileId}`);
|
||||
navigate(`/reviews?id=${fileId}&previousRoute=rulesFiles`);
|
||||
};
|
||||
|
||||
// 渲染问题摘要
|
||||
|
||||
+90
-17
@@ -49,6 +49,7 @@ import type { EvaluationPointGroup } from "~/models/evaluation_point_groups";
|
||||
// 导入RuleContext上下文
|
||||
import { RuleContext } from "~/contexts/RuleContext";
|
||||
import { postgrestGet, postgrestPost, postgrestPut } from "~/api/postgrest-client";
|
||||
import { toastService } from '~/components/ui/Toast';
|
||||
|
||||
export const meta: MetaFunction = () => {
|
||||
return [
|
||||
@@ -65,7 +66,21 @@ export function links() {
|
||||
}
|
||||
|
||||
export const handle = {
|
||||
breadcrumb: "评查点管理"
|
||||
breadcrumb: "评查点管理",
|
||||
previousRoute: () => {
|
||||
if (typeof window !== 'undefined') {
|
||||
const searchParams = new URLSearchParams(window.location.search);
|
||||
const mode = searchParams.get('mode');
|
||||
const id = searchParams.get('id');
|
||||
if (mode || id) {
|
||||
return {
|
||||
title: "评查点列表",
|
||||
to: `/rules`
|
||||
};
|
||||
}
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
};
|
||||
|
||||
// 添加规则配置接口
|
||||
@@ -266,13 +281,13 @@ export default function RuleNew() {
|
||||
setInstanceKey(`edit_${id}_${Date.now()}`);
|
||||
} catch (jsonError) {
|
||||
console.error('JSON处理错误:', jsonError);
|
||||
alert(`数据处理错误: ${jsonError instanceof Error ? jsonError.message : '未知错误'}`);
|
||||
toastService.error(`数据处理错误: ${jsonError instanceof Error ? jsonError.message : '未知错误'}`);
|
||||
resetFormData();
|
||||
navigate('/rules');
|
||||
}
|
||||
} else {
|
||||
console.error('获取数据失败: 返回数据为空');
|
||||
alert('获取数据失败: 返回数据为空');
|
||||
toastService.error('获取数据失败: 返回数据为空');
|
||||
resetFormData();
|
||||
navigate('/rules');
|
||||
}
|
||||
@@ -281,7 +296,7 @@ export default function RuleNew() {
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('获取评查点数据失败:', error);
|
||||
alert(`获取评查点数据失败: ${error instanceof Error ? error.message : '未知错误'}`);
|
||||
toastService.error(`获取评查点数据失败: ${error instanceof Error ? error.message : '未知错误'}`);
|
||||
// 获取数据失败时返回上一页
|
||||
resetFormData();
|
||||
navigate('/rules');
|
||||
@@ -305,7 +320,7 @@ export default function RuleNew() {
|
||||
} catch (error) {
|
||||
console.error('获取评查点组数据失败:', error);
|
||||
// 显示错误提示但不影响应用继续使用
|
||||
alert(`获取评查点组数据失败: ${error instanceof Error ? error.message : '未知错误'}\n将使用默认数据`);
|
||||
toastService.error(`获取评查点组数据失败: ${error instanceof Error ? error.message : '未知错误'}\n将使用默认数据`);
|
||||
}
|
||||
}, []);
|
||||
|
||||
@@ -314,12 +329,12 @@ export default function RuleNew() {
|
||||
|
||||
// 验证必填字段
|
||||
if (!formData.name?.trim()) {
|
||||
alert("评查点名称不能为空");
|
||||
toastService.warning("评查点名称不能为空");
|
||||
return;
|
||||
}
|
||||
|
||||
if (!formData.code?.trim()) {
|
||||
alert("评查点编码不能为空");
|
||||
toastService.warning("评查点编码不能为空");
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -379,6 +394,23 @@ export default function RuleNew() {
|
||||
score: formData.score !== undefined ? Number(formData.score) : 0
|
||||
};
|
||||
|
||||
// 获取当前所有有效的抽取字段
|
||||
const currentExtractionFields = extractionFields.map(field => {
|
||||
// 处理字段名,去掉类型后缀(如果有)
|
||||
if (field.includes('_')) {
|
||||
return field.split('_')[0];
|
||||
}
|
||||
return field;
|
||||
});
|
||||
|
||||
// 去重,确保不会有重复字段
|
||||
const validFields = [...new Set(currentExtractionFields)];
|
||||
console.log("当前有效的抽取字段:", validFields);
|
||||
|
||||
// 重要:这段代码解决了字段删除后,评查配置中仍保留已删除字段的问题
|
||||
// 在保存前,我们会确保所有规则中引用的字段都是当前有效的抽取字段
|
||||
// 这样即使用户在界面操作中删除了某个抽取字段,相关的评查规则也会被自动清理
|
||||
|
||||
// 确保rules中的每个配置对象都被正确处理
|
||||
if (cleanedData.evaluation_config && Array.isArray(cleanedData.evaluation_config.rules)) {
|
||||
cleanedData.evaluation_config.rules = cleanedData.evaluation_config.rules
|
||||
@@ -390,6 +422,12 @@ export default function RuleNew() {
|
||||
switch (rule.type) {
|
||||
case 'exists':
|
||||
if (!Array.isArray((config as ExistsRuleConfig).fields)) (config as ExistsRuleConfig).fields = [];
|
||||
// 过滤掉不存在于当前抽取字段中的字段
|
||||
if (Array.isArray((config as ExistsRuleConfig).fields)) {
|
||||
(config as ExistsRuleConfig).fields = (config as ExistsRuleConfig).fields.filter(
|
||||
field => validFields.includes(field)
|
||||
);
|
||||
}
|
||||
if (!(config as ExistsRuleConfig).logic) (config as ExistsRuleConfig).logic = 'and';
|
||||
// 删除不必要的字段
|
||||
delete (config as ExistsRuleConfig & {availableFields?: string}).availableFields;
|
||||
@@ -399,6 +437,12 @@ export default function RuleNew() {
|
||||
|
||||
case 'consistency':
|
||||
if (!Array.isArray((config as ConsistencyRuleConfig).pairs)) (config as ConsistencyRuleConfig).pairs = [];
|
||||
// 过滤掉包含不存在于当前抽取字段中的字段的配对
|
||||
if (Array.isArray((config as ConsistencyRuleConfig).pairs)) {
|
||||
(config as ConsistencyRuleConfig).pairs = (config as ConsistencyRuleConfig).pairs.filter(
|
||||
pair => validFields.includes(pair.sourceField) && validFields.includes(pair.targetField)
|
||||
);
|
||||
}
|
||||
if (!(config as ConsistencyRuleConfig).logic) (config as ConsistencyRuleConfig).logic = 'and';
|
||||
delete (config as ConsistencyRuleConfig & {availableFields?: string}).availableFields;
|
||||
delete (config as ConsistencyRuleConfig).logicRelation;
|
||||
@@ -408,6 +452,10 @@ export default function RuleNew() {
|
||||
break;
|
||||
|
||||
case 'format':
|
||||
// 检查字段是否存在于当前抽取字段中
|
||||
if ((config as FormatRuleConfig).field && !validFields.includes((config as FormatRuleConfig).field)) {
|
||||
(config as FormatRuleConfig).field = '';
|
||||
}
|
||||
if (!(config as FormatRuleConfig).field) (config as FormatRuleConfig).field = '';
|
||||
if (!(config as FormatRuleConfig).formatType) (config as FormatRuleConfig).formatType = 'date';
|
||||
if (!(config as FormatRuleConfig).parameters) (config as FormatRuleConfig).parameters = '';
|
||||
@@ -418,6 +466,12 @@ export default function RuleNew() {
|
||||
|
||||
case 'logic':
|
||||
if (!Array.isArray((config as LogicRuleConfig).conditions)) (config as LogicRuleConfig).conditions = [];
|
||||
// 过滤掉包含不存在于当前抽取字段中的字段的条件
|
||||
if (Array.isArray((config as LogicRuleConfig).conditions)) {
|
||||
(config as LogicRuleConfig).conditions = (config as LogicRuleConfig).conditions.filter(
|
||||
condition => validFields.includes(condition.field)
|
||||
);
|
||||
}
|
||||
if (!(config as LogicRuleConfig).logic) (config as LogicRuleConfig).logic = 'and';
|
||||
delete (config as LogicRuleConfig & {availableFields?: string}).availableFields;
|
||||
delete (config as LogicRuleConfig).logicRelation;
|
||||
@@ -427,6 +481,10 @@ export default function RuleNew() {
|
||||
break;
|
||||
|
||||
case 'regex':
|
||||
// 检查字段是否存在于当前抽取字段中
|
||||
if ((config as RegexRuleConfig).field && !validFields.includes((config as RegexRuleConfig).field)) {
|
||||
(config as RegexRuleConfig).field = '';
|
||||
}
|
||||
if (!(config as RegexRuleConfig).field) (config as RegexRuleConfig).field = '';
|
||||
if (!(config as RegexRuleConfig).pattern) (config as RegexRuleConfig).pattern = '';
|
||||
if (!(config as RegexRuleConfig).matchType) (config as RegexRuleConfig).matchType = 'match';
|
||||
@@ -456,6 +514,8 @@ export default function RuleNew() {
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
// console.log("当前评查配置-----------------:", formData.evaluation_config);
|
||||
|
||||
// 如果是新建模式,则删除id字段
|
||||
if (!isEditMode) {
|
||||
@@ -499,14 +559,19 @@ export default function RuleNew() {
|
||||
}
|
||||
|
||||
if (response.error) {
|
||||
alert(`系统繁忙: ${response.error}`);
|
||||
if (response.error.includes('evaluation_points_code_key')) {
|
||||
toastService.error('在基本信息中:评查点编码已存在,请修改后保存。');
|
||||
} else {
|
||||
toastService.error(`系统繁忙: ${response.error}`);
|
||||
}
|
||||
setIsLoading(false);
|
||||
} else if (response.data && Array.isArray(response.data) && response.data.length > 0) {
|
||||
// 获取新创建或更新的评查点ID
|
||||
const savedPointId = response.data[0]?.id;
|
||||
|
||||
if (savedPointId) {
|
||||
// 显示成功消息
|
||||
alert(`评查点${isEditMode ? '更新' : '创建'}成功!`);
|
||||
toastService.success(`评查点${isEditMode ? '更新' : '创建'}成功!`);
|
||||
|
||||
// 保存成功后跳转到编辑页面并重新加载数据
|
||||
navigate(`/rules-new?id=${savedPointId}`, { replace: true });
|
||||
@@ -514,22 +579,22 @@ export default function RuleNew() {
|
||||
await fetchEvaluationPoint(savedPointId);
|
||||
} else {
|
||||
// 无法获取ID的情况
|
||||
alert(`评查点${isEditMode ? '更新' : '创建'}成功,但无法获取ID。正在返回列表页面。`);
|
||||
toastService.warning(`评查点${isEditMode ? '更新' : '创建'}成功,但无法获取ID。正在返回列表页面。`);
|
||||
navigate('/rules');
|
||||
}
|
||||
} else {
|
||||
alert(`系统繁忙`);
|
||||
toastService.error('系统繁忙');
|
||||
}
|
||||
} catch (jsonError) {
|
||||
console.error("JSON处理错误:", jsonError);
|
||||
alert(`数据处理错误: ${jsonError instanceof Error ? jsonError.message : '未知错误'}`);
|
||||
toastService.error(`数据处理错误: ${jsonError instanceof Error ? jsonError.message : '未知错误'}`);
|
||||
setIsLoading(false);
|
||||
return;
|
||||
}
|
||||
|
||||
} catch (error) {
|
||||
console.error("数据处理错误:", error);
|
||||
alert(`数据处理错误: ${error instanceof Error ? error.message : '未知错误'}`);
|
||||
toastService.error(`数据处理错误: ${error instanceof Error ? error.message : '未知错误'}`);
|
||||
setIsLoading(false);
|
||||
}
|
||||
}
|
||||
@@ -586,17 +651,21 @@ export default function RuleNew() {
|
||||
customLogic: '',
|
||||
rules: []
|
||||
};
|
||||
|
||||
// console.log("当前评查配置:", currentConfig);
|
||||
// console.log("变更评查配置:", data.evaluation_config);
|
||||
|
||||
// 合并评查配置数据
|
||||
const mergedConfig = {
|
||||
...currentConfig,
|
||||
...(data.evaluation_config as object)
|
||||
};
|
||||
|
||||
// console.log("合并评查配置:", data.evaluation_config);
|
||||
// 更新表单数据
|
||||
setFormData(prev => ({
|
||||
...prev,
|
||||
evaluation_config: mergedConfig
|
||||
// evaluation_config: data.evaluation_config as typeof prev.evaluation_config
|
||||
}));
|
||||
} else {
|
||||
// 处理其他普通字段
|
||||
@@ -634,11 +703,15 @@ export default function RuleNew() {
|
||||
*/
|
||||
useEffect(() => {
|
||||
const searchParams = new URLSearchParams(location.search);
|
||||
const id = searchParams.get('id');
|
||||
const id = searchParams.get('id');
|
||||
const mode = searchParams.get('mode');
|
||||
|
||||
// 设置编辑模式
|
||||
const newIsEditMode = !!id;
|
||||
setIsEditMode(newIsEditMode);
|
||||
if (mode && mode === 'copy') {
|
||||
setIsEditMode(false);
|
||||
} else {
|
||||
setIsEditMode(!!id);
|
||||
}
|
||||
|
||||
if (id) {
|
||||
// 编辑模式:获取数据
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import { type MetaFunction, type LoaderFunctionArgs, redirect } from "@remix-run/node";
|
||||
import { useLoaderData, useSearchParams, useSubmit, Link } from "@remix-run/react";
|
||||
import { useLoaderData, useSearchParams, useSubmit, Link, useNavigate } from "@remix-run/react";
|
||||
import { Button } from '~/components/ui/Button';
|
||||
import { Card } from '~/components/ui/Card';
|
||||
import { Tag } from '~/components/ui/Tag';
|
||||
@@ -234,6 +234,7 @@ export default function RulesIndex() {
|
||||
const ruleTypes = loaderData.ruleTypes || []; // 添加默认空数组避免undefined
|
||||
const [searchParams, setSearchParams] = useSearchParams();
|
||||
const submit = useSubmit();
|
||||
const navigate = useNavigate();
|
||||
|
||||
// 状态管理
|
||||
const [showDeleteConfirm, setShowDeleteConfirm] = useState(false);
|
||||
@@ -345,11 +346,12 @@ export default function RulesIndex() {
|
||||
};
|
||||
|
||||
const handleCopy = (rule: Rule) => {
|
||||
const formData = new FormData();
|
||||
formData.append('_action', 'duplicate');
|
||||
formData.append('ruleId', rule.id);
|
||||
// const formData = new FormData();
|
||||
// formData.append('_action', 'duplicate');
|
||||
// formData.append('ruleId', rule.id);
|
||||
|
||||
submit(formData, { method: 'post' });
|
||||
// submit(formData, { method: 'post' });
|
||||
navigate(`/rules-new?id=${rule.id}&mode=copy`);
|
||||
};
|
||||
|
||||
const handlePageChange = (page: number) => {
|
||||
|
||||
Reference in New Issue
Block a user