优化评查详情,新增信息提示框组件
This commit is contained in:
+132
-109
@@ -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>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user