优化评查详情,新增信息提示框组件
This commit is contained in:
+107
-128
@@ -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">确定要删除评查点“{ruleToDelete.name}”吗?</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>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user