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

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
+132 -109
View File
@@ -1,14 +1,17 @@
import { json, type MetaFunction, type LoaderFunctionArgs, type ActionFunctionArgs } from "@remix-run/node";
import { useLoaderData, useSearchParams, useSubmit, Link } from "@remix-run/react";
import { useState } from "react";
import { useLoaderData, useSearchParams, useFetcher, Link } from "@remix-run/react";
import { useState, useEffect } from "react";
import { Button } from "~/components/ui/Button";
import { Card } from "~/components/ui/Card";
import { FilterPanel, FilterSelect, SearchFilter } from "~/components/ui/FilterPanel";
import { Modal } from "~/components/ui/Modal";
import { Pagination } from "~/components/ui/Pagination";
import { Table } from "~/components/ui/Table";
import { Tag } from "~/components/ui/Tag";
import { getConfigLists, getConfigOptions, updateConfigStatus, type ConfigItem } from "~/api/system_setting/config-lists";
import configListsStyles from "~/styles/pages/config-lists_index.css?url";
import { toastService } from "~/components/ui/Toast";
import { messageService } from "~/components/ui/MessageModal";
export const links = () => [
{ rel: "stylesheet", href: configListsStyles }
@@ -54,16 +57,13 @@ export const MODULE_LABELS: Record<ConfigModule, string> = {
[ConfigModule.NOTIFICATION]: '通知'
};
interface LoaderData {
configs: ConfigItem[];
totalCount: number;
currentPage: number;
pageSize: number;
totalPages: number;
types: string[];
environments: string[];
// 操作响应
interface ActionResponse {
result: boolean;
message: string;
}
export async function loader({ request }: LoaderFunctionArgs) {
const url = new URL(request.url);
const name = url.searchParams.get("name") || "";
@@ -95,7 +95,7 @@ export async function loader({ request }: LoaderFunctionArgs) {
throw new Error(optionsResponse.error || "获取配置选项失败");
}
return json<LoaderData>({
return Response.json({
configs: configsResponse.data,
totalCount: configsResponse.total,
currentPage,
@@ -110,8 +110,11 @@ 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 });
}
}
export async function action({ request }: ActionFunctionArgs) {
@@ -120,7 +123,7 @@ export async function action({ request }: ActionFunctionArgs) {
const configId = formData.get('configId');
if (!configId) {
return json({ success: false, error: "缺少配置ID" }, { status: 400 });
return Response.json({ result: false, message: "缺少配置ID" }, { status: 400 });
}
// 进行更新启用和禁用的状态
@@ -130,37 +133,48 @@ export async function action({ request }: ActionFunctionArgs) {
const response = await updateConfigStatus(parseInt(configId as string), is_active);
if (!response.success) {
return json({ success: false, error: response.error }, { status: 500 });
if (response.error) {
return Response.json({ result: false, message: response.error }, { status: 500 });
}
return json({ success: true });
return Response.json({ result: true, message: is_active ? '启用成功' : '禁用成功' });
}
return json({ success: false, error: "未知操作" }, { status: 400 });
return Response.json({ result: false, message: "未知操作" }, { status: 400 });
} catch (error) {
console.error('操作配置失败:', error);
return json({ success: false, error: "操作失败" }, { status: 500 });
return Response.json({ result: false, message: error || "操作失败" }, { 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>
);
}
export default function ConfigListsIndex() {
const { configs, totalCount, currentPage, pageSize, types, environments } = useLoaderData<typeof loader>();
const { configs, totalCount, currentPage, pageSize, types, environments, error } = useLoaderData<typeof loader>();
const [searchParams, setSearchParams] = useSearchParams();
const submit = useSubmit();
const fetcher = useFetcher<ActionResponse>();
const [showDetailModal, setShowDetailModal] = useState(false);
const [selectedConfig, setSelectedConfig] = useState<ConfigItem | null>(null);
// 处理loader错误
useEffect(() => {
if(error) {
toastService.error(error);
}
}, [error]);
// 使用useEffect监听fetcher状态变化并显示Toast
useEffect(() => {
if(fetcher.state === 'idle' && fetcher.data) {
if(fetcher.data.result) {
toastService.success(fetcher.data.message);
} else if (fetcher.data.message) {
toastService.error(fetcher.data.message);
}
}
}, [fetcher.state,fetcher.data]);
// 处理筛选条件变化
const handleFilterChange = (e: React.ChangeEvent<HTMLSelectElement | HTMLInputElement>) => {
const { name, value } = e.target;
const newParams = new URLSearchParams(searchParams);
@@ -177,13 +191,11 @@ export default function ConfigListsIndex() {
setSearchParams(newParams);
};
// 搜索配置名称
const handleConfigNameSearch = (value: string) => {
const newParams = new URLSearchParams(searchParams);
if (value) {
newParams.set('name', value);
} else {
newParams.delete('name');
}
value ? newParams.set('name', value) : newParams.delete('name');
// 搜索时,重置到第一页
newParams.set('page', '1');
@@ -191,28 +203,40 @@ export default function ConfigListsIndex() {
setSearchParams(newParams);
};
// 处理启用和禁用状态
const handleToggleStatus = (config: ConfigItem) => {
if (window.confirm(`确定要${config.is_active ? '禁用' : '启用'}该配置吗?`)) {
const formData = new FormData();
formData.append('_action', 'toggleStatus');
formData.append('configId', config.id.toString());
formData.append('is_active', String(!config.is_active));
submit(formData, { method: 'post' });
}
messageService.show({
title: '提示',
message: `确定要${config.is_active ? '禁用' : '启用'}该配置吗?`,
type: config.is_active ? 'warning' : 'success',
confirmText: '确定',
cancelText: '取消',
onConfirm: () => {
const formData = new FormData();
formData.append('_action', 'toggleStatus');
formData.append('configId', config.id.toString());
formData.append('is_active', String(!config.is_active));
fetcher.submit(formData, { method: 'post' });
}
});
};
// 处理查看详情
const handleViewDetail = (config: ConfigItem) => {
setSelectedConfig(config);
setShowDetailModal(true);
};
// 处理分页
const handlePageChange = (page: number) => {
const newParams = new URLSearchParams(searchParams);
newParams.set('page', page.toString());
setSearchParams(newParams);
};
// 处理每页条数变更
const handlePageSizeChange = (size: number) => {
const newParams = new URLSearchParams(searchParams);
newParams.set('pageSize', size.toString());
@@ -223,17 +247,10 @@ export default function ConfigListsIndex() {
// 处理重置筛选
const handleReset = () => {
const nameInput = document.querySelector('input[placeholder="请输入配置名称"]') as HTMLInputElement;
const typeSelect = document.querySelector('select[name="type"]') as HTMLInputElement;
const environmentSelect = document.querySelector('select[name="environment"]') as HTMLInputElement;
const statusSelect = document.querySelector('select[name="is_active"]') as HTMLInputElement;
if(nameInput) nameInput.value = ''
setSearchParams(new URLSearchParams());
if(nameInput) nameInput.value = ''
if(typeSelect) typeSelect.value = ''
if(environmentSelect) environmentSelect.value = ''
if(statusSelect) statusSelect.value = ''
};
// 关闭详情模态框
@@ -355,7 +372,7 @@ export default function ConfigListsIndex() {
label="所属模块"
name="type"
value={searchParams.get('type') || ''}
options={[ ...types.map(type => ({ value: type, label: type }))]}
options={[ ...types.map((type: string) => ({ value: type, label: type }))]}
onChange={handleFilterChange}
className="flex-1 min-w-[200px]"
/>
@@ -364,7 +381,7 @@ export default function ConfigListsIndex() {
label="环境"
name="environment"
value={searchParams.get('environment') || ''}
options={[ ...environments.map(env => ({ value: env, label: env }))]}
options={[ ...environments.map((env: string) => ({ value: env, label: env }))]}
onChange={handleFilterChange}
className="flex-1 min-w-[200px]"
/>
@@ -409,70 +426,76 @@ export default function ConfigListsIndex() {
{/* 配置详情模态框 */}
{showDetailModal && selectedConfig && (
<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-3xl w-full">
<div className="flex justify-between items-center mb-4">
<h3 className="text-lg font-medium"></h3>
<button className="text-gray-500 hover:text-gray-700" onClick={closeDetailModal}>
<i className="ri-close-line text-xl"></i>
</button>
<Modal
isOpen={showDetailModal}
onClose={closeDetailModal}
title="查看配置详情"
width="800px"
footer={
<Button type="default" onClick={closeDetailModal}></Button>
}
>
<div className="config-detail-content">
<div className="config-detail-item">
<div className="config-detail-label"></div>
<div className="config-detail-value">{selectedConfig.name}</div>
</div>
<div className="config-detail-content">
<div className="config-detail-item">
<div className="config-detail-label"></div>
<div className="config-detail-value">{selectedConfig.name}</div>
</div>
<div className="config-detail-item">
<div className="config-detail-label"></div>
<div className="config-detail-value">{selectedConfig.type}</div>
</div>
<div className="config-detail-item">
<div className="config-detail-label"></div>
<div className="config-detail-value">
<span className="env-tag">
{selectedConfig.environment}
</span>
</div>
</div>
<div className="config-detail-item">
<div className="config-detail-label"></div>
<div className="config-detail-value">
<Tag color={selectedConfig.is_active ? 'green' : 'red'}>
{selectedConfig.is_active ? '已启用' : '已禁用'}
</Tag>
</div>
</div>
<div className="config-detail-item">
<div className="config-detail-label"></div>
<pre className="config-detail-code">
{JSON.stringify(selectedConfig.config, null, 2)}
</pre>
</div>
<div className="grid grid-cols-2 gap-4">
<div className="config-detail-item">
<div className="config-detail-label"></div>
<div className="config-detail-value">{selectedConfig.created_at}</div>
</div>
<div className="config-detail-item">
<div className="config-detail-label"></div>
<div className="config-detail-value">{selectedConfig.updated_at}</div>
</div>
<div className="config-detail-item">
<div className="config-detail-label"></div>
<div className="config-detail-value">{selectedConfig.type}</div>
</div>
<div className="config-detail-item">
<div className="config-detail-label"></div>
<div className="config-detail-value">
<span className="env-tag">
{selectedConfig.environment}
</span>
</div>
</div>
<div className="flex justify-end mt-6">
<Button type="default" onClick={closeDetailModal}></Button>
<div className="config-detail-item">
<div className="config-detail-label"></div>
<div className="config-detail-value">
<Tag color={selectedConfig.is_active ? 'green' : 'red'}>
{selectedConfig.is_active ? '已启用' : '已禁用'}
</Tag>
</div>
</div>
<div className="config-detail-item">
<div className="config-detail-label"></div>
<pre className="config-detail-code">
{JSON.stringify(selectedConfig.config, null, 2)}
</pre>
</div>
<div className="grid grid-cols-2 gap-4">
<div className="config-detail-item">
<div className="config-detail-label"></div>
<div className="config-detail-value">{selectedConfig.created_at}</div>
</div>
<div className="config-detail-item">
<div className="config-detail-label"></div>
<div className="config-detail-value">{selectedConfig.updated_at}</div>
</div>
</div>
</div>
</div>
</Modal>
)}
</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>
);
}