优化评查详情,新增信息提示框组件

This commit is contained in:
2025-04-23 20:48:32 +08:00
parent ee36ce2620
commit be99fdec79
15 changed files with 1399 additions and 757 deletions
+107 -128
View File
@@ -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, useNavigate } from "@remix-run/react";
import { type MetaFunction, type LoaderFunctionArgs } from "@remix-run/node";
import { useLoaderData, useSearchParams, Link, useNavigate, useFetcher } from "@remix-run/react";
import { Button } from '~/components/ui/Button';
import { Card } from '~/components/ui/Card';
import { Tag } from '~/components/ui/Tag';
@@ -12,10 +12,11 @@ import type { TagColor } from '~/components/ui/Tag';
import { Table } from '~/components/ui/Table';
import { FilterPanel, FilterSelect, SearchFilter } from '~/components/ui/FilterPanel';
import { Pagination } from '~/components/ui/Pagination';
import { messageService } from '~/components/ui/MessageModal';
import { toastService } from '~/components/ui/Toast';
import {
getRulesList,
deleteRule,
duplicateRule,
getRuleTypes,
getRuleGroupsByType,
type RuleType as ApiRuleType,
@@ -60,6 +61,11 @@ interface ApiRule {
updatedAt: string;
}
interface ActionResponse {
result: boolean;
message: string;
}
function mapApiRuleToModel(apiRule: ApiRule): Rule {
return {
id: apiRule.id,
@@ -109,30 +115,15 @@ export async function loader({ request }: LoaderFunctionArgs) {
throw new Error(response.error);
}
if (!response.data) {
throw new Error('API返回数据为空');
}
const apiRules = response.data.rules;
const totalCount = response.data.totalCount;
const apiRules = response.data?.rules || [];
const totalCount = response.data?.totalCount || 0;
const rules = apiRules.map((apiRule: ApiRule) => mapApiRuleToModel(apiRule));
// 计算总页数
const totalPages = Math.ceil(totalCount / params.pageSize);
// 验证页码范围
if (params.page < 1 || (totalCount > 0 && params.page > totalPages)) {
const newUrl = new URL(request.url);
newUrl.searchParams.set('page', '1');
return redirect(newUrl.pathname + newUrl.search);
}
return Response.json({
rules,
totalCount,
currentPage: params.page,
pageSize: params.pageSize,
totalPages,
ruleTypes
}, {
headers: {
@@ -142,7 +133,10 @@ export async function loader({ request }: LoaderFunctionArgs) {
} catch (error) {
console.error('加载评查点列表失败:', error);
throw new Response('加载评查点列表失败', { status: 500 });
return Response.json({
error: error || '加载评查点列表失败',
status: 500
}, { status: 500 });
}
}
@@ -152,7 +146,7 @@ export async function action({ request }: LoaderFunctionArgs) {
const ruleId = formData.get('ruleId');
if (!ruleId) {
return Response.json({ success: false, error: "缺少评查点ID" }, { status: 400 });
return Response.json({ result: false, message: "缺少评查点ID" }, { status: 400 });
}
try {
@@ -160,66 +154,20 @@ export async function action({ request }: LoaderFunctionArgs) {
// 调用API删除评查点
console.log(`删除评查点 ${ruleId}`);
try {
const deleteResponse = await deleteRule(ruleId as string);
if (deleteResponse.error) {
throw new Error(deleteResponse.error);
}
// 删除成功,获取当前URL
const url = new URL(request.url);
// 返回重定向响应,以刷新页面数据
return redirect(`${url.pathname}${url.search}`);
} catch (error) {
console.error('删除评查点失败:', error);
return Response.json({
success: false,
error: error instanceof Error ? error.message : "删除失败"
}, { status: 500 });
}
}
if (_action === 'duplicate') {
// 复制评查点
console.log(`复制评查点 ${ruleId}`);
const deleteResponse = await deleteRule(ruleId as string);
try {
const duplicateResponse = await duplicateRule(ruleId as string);
if (duplicateResponse.error) {
throw new Error(duplicateResponse.error);
}
// 复制成功,获取当前URL
const url = new URL(request.url);
// 返回重定向响应,以刷新页面数据
return redirect(`${url.pathname}${url.search}`);
} catch (error) {
console.error('复制评查点失败:', error);
return Response.json({
success: false,
error: error instanceof Error ? error.message : "复制失败"
}, { status: 500 });
if (deleteResponse.error) {
return Response.json({ result: false, message: deleteResponse.error }, { status: deleteResponse.status || 500 });
}
return Response.json({ result: true, message: "评查点删除成功" }, { status: 200 });
}
return Response.json({ success: false, error: "未知操作" }, { status: 400 });
} catch (error) {
console.error('操作评查点失败:', error);
return Response.json({ success: false, error: "操作失败" }, { status: 500 });
return Response.json({ result: false, message: error instanceof Error ? error.message : "操作失败" }, { status: 500 });
}
}
export function ErrorBoundary() {
return (
<div className="error-container p-6">
<h1 className="text-xl font-bold text-red-500 mb-4"></h1>
<p className="mb-4"></p>
<Button type="primary" to="/"></Button>
</div>
);
}
// 规则优先级的描述标签映射
const priorityLabels = {
@@ -233,24 +181,30 @@ export default function RulesIndex() {
const { rules, totalCount, currentPage, pageSize } = loaderData;
const ruleTypes = loaderData.ruleTypes || []; // 添加默认空数组避免undefined
const [searchParams, setSearchParams] = useSearchParams();
const submit = useSubmit();
const navigate = useNavigate();
const fetcher = useFetcher<ActionResponse>();
// 状态管理
const [showDeleteConfirm, setShowDeleteConfirm] = useState(false);
const [ruleToDelete, setRuleToDelete] = useState<Rule | null>(null);
const [ruleGroups, setRuleGroups] = useState<RuleGroup[]>([]);
const [loadingGroups, setLoadingGroups] = useState(false);
// 判断是否禁用规则组选择
const isRuleGroupSelectDisabled = loadingGroups || !searchParams.get('ruleType') || ruleGroups.length === 0;
// 获取当前的ruleType值
const ruleTypeParam = searchParams.get('ruleType');
// 判断是否禁用规则组选择
const isRuleGroupSelectDisabled = loadingGroups || !ruleTypeParam || ruleGroups.length === 0;
// 使用useEffect监听loaderData.error变化并显示Toast
useEffect(() => {
if(loaderData.error) {
toastService.error(loaderData.error);
}
}, [loaderData.error]);
// 当评查点类型变化时,加载对应的规则组
useEffect(() => {
const selectedType = searchParams.get('ruleType');
// 如果选择了"全部"或未选择,则清空规则组
if (!selectedType || selectedType === 'all') {
if (!ruleTypeParam || ruleTypeParam === 'all') {
setRuleGroups([]);
return;
}
@@ -259,7 +213,7 @@ export default function RulesIndex() {
const loadRuleGroups = async () => {
setLoadingGroups(true);
try {
const response = await getRuleGroupsByType(selectedType);
const response = await getRuleGroupsByType(ruleTypeParam);
if (response.data) {
setRuleGroups(response.data);
} else if (response.error) {
@@ -275,8 +229,24 @@ export default function RulesIndex() {
};
loadRuleGroups();
}, [searchParams.get('ruleType')]);
}, [ruleTypeParam]);
// 使用useEffect监听fetcher状态变化并显示Toast
useEffect(() => {
if (fetcher.data && fetcher.state === 'idle') {
if (fetcher.data.result) {
toastService.success(fetcher.data.message);
} else if (!fetcher.data.result) {
if(fetcher.data.message.includes("evaluation_results_evaluation_point_id_fkey")) {
toastService.error("对表“evaluation_points”进行更新或删除违反了表“evaluation results”上的外键约束“evaluations results_evaluation _point_id_fkey”");
} else {
toastService.error(fetcher.data.message);
}
}
}
}, [fetcher.data,fetcher.state]);
// 筛选评查点
const handleFilterChange = (e: React.ChangeEvent<HTMLSelectElement | HTMLInputElement>) => {
const { name, value } = e.target;
const newParams = new URLSearchParams(searchParams);
@@ -313,6 +283,7 @@ export default function RulesIndex() {
setSearchParams(newParams);
};
// 搜索评查点
const handleSearch = (keyword: string) => {
const newParams = new URLSearchParams(searchParams);
if (keyword) {
@@ -327,30 +298,26 @@ export default function RulesIndex() {
setSearchParams(newParams);
};
// 删除评查点
const handleDeleteClick = (rule: Rule) => {
console.log("handleDELETEclick",rule)
setRuleToDelete(rule);
setShowDeleteConfirm(true);
};
const confirmDelete = () => {
if (!ruleToDelete) return;
const formData = new FormData();
formData.append('_action', 'delete');
formData.append('ruleId', ruleToDelete.id);
submit(formData, { method: 'post' });
setShowDeleteConfirm(false);
setRuleToDelete(null);
messageService.show({
title: "确认删除",
message: `确认删除评查点【${rule.name}】吗?`,
type: "warning",
confirmText: "删除",
cancelText: "取消",
onConfirm: () => {
const form = new FormData();
form.append("_action", "delete");
form.append("ruleId", rule.id);
fetcher.submit(form, { method: "post" });
}
});
};
// 复制评查点
const handleCopy = (rule: Rule) => {
// const formData = new FormData();
// formData.append('_action', 'duplicate');
// formData.append('ruleId', rule.id);
// submit(formData, { method: 'post' });
navigate(`/rules-new?id=${rule.id}&mode=copy`);
};
@@ -360,6 +327,7 @@ export default function RulesIndex() {
setSearchParams(newParams);
};
// 处理每页条数变化
const handlePageSizeChange = (size: number) => {
const newParams = new URLSearchParams(searchParams);
newParams.set('pageSize', size.toString());
@@ -368,9 +336,13 @@ export default function RulesIndex() {
};
// 处理重置筛选
// const handleReset = () => {
// setSearchParams(new URLSearchParams());
// };
const handleReset = () => {
const input = document.querySelector('input[placeholder="输入评查点名称或编码"]');
if (input) {
(input as HTMLInputElement).value = '';
}
setSearchParams(new URLSearchParams());
};
// 定义表格列配置
const columns = [
@@ -475,7 +447,15 @@ export default function RulesIndex() {
</div>
{/* 筛选区域 */}
<FilterPanel>
<FilterPanel className="px-3 py-3" noActionDivider={true}
actions={
<>
<Button type="default" icon="ri-refresh-line" onClick={handleReset} className="mr-2 hover:!border-gray-300">
</Button>
</>
}
>
<FilterSelect
label="评查点类型"
name="ruleType"
@@ -487,7 +467,7 @@ export default function RulesIndex() {
}))
]}
onChange={handleFilterChange}
className="mr-3 w-[20%]"
className="mr-3 w-[15%]"
/>
<FilterSelect
@@ -514,15 +494,16 @@ export default function RulesIndex() {
{ value: "false", label: "禁用" }
]}
onChange={handleFilterChange}
className="mr-3 w-[20%]"
className="mr-3 w-[15%]"
/>
<SearchFilter
label="搜索"
placeholder="输入评查点名称或编码"
value={searchParams.get('keyword') || ''}
buttonText="搜索"
onSearch={handleSearch}
className="w-[30%]"
className="min-w-[200px] flex-1"
/>
</FilterPanel>
@@ -552,19 +533,17 @@ export default function RulesIndex() {
)}
</Card>
{/* 删除确认对话框 */}
{showDeleteConfirm && ruleToDelete && (
<div className="fixed inset-0 bg-black bg-opacity-30 flex items-center justify-center z-50">
<div className="bg-white rounded-lg p-6 max-w-md w-full">
<h3 className="text-lg font-medium mb-4"></h3>
<p className="mb-6">&ldquo;{ruleToDelete.name}&rdquo;</p>
<div className="flex justify-end space-x-2">
<Button type="default" onClick={() => setShowDeleteConfirm(false)}></Button>
<Button type="danger" onClick={confirmDelete}></Button>
</div>
</div>
</div>
)}
</div>
);
}
}
// 错误边界
export function ErrorBoundary() {
return (
<div className="error-container p-6">
<h1 className="text-xl font-bold text-red-500 mb-4"></h1>
<p className="mb-4"></p>
<Button type="primary" to="/"></Button>
</div>
);
}