完善文件上传的接口对接

This commit is contained in:
2025-04-08 22:07:14 +08:00
parent 5cf05eca40
commit fda6515891
9 changed files with 1309 additions and 892 deletions
+86 -210
View File
@@ -7,13 +7,13 @@ import { FilterPanel, FilterSelect, SearchFilter } from "~/components/ui/FilterP
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";
export const links = () => [
{ rel: "stylesheet", href: configListsStyles }
];
export const meta: MetaFunction = () => {
return [
{ title: "系统配置管理 - 中国烟草AI合同及卷宗审核系统" },
@@ -54,165 +54,55 @@ export const MODULE_LABELS: Record<ConfigModule, string> = {
[ConfigModule.NOTIFICATION]: '通知'
};
// 配置数据类型
interface ConfigDataType {
[key: string]: string | number | boolean | string[] | ConfigDataType | ConfigDataType[];
}
// 配置项模型
interface ConfigItem {
id: string;
configName: string;
module: ConfigModule;
environment: ConfigEnvironment;
isActive: boolean;
configData: ConfigDataType;
createdAt: string;
updatedAt: string;
}
interface LoaderData {
configs: ConfigItem[];
totalCount: number;
currentPage: number;
pageSize: number;
totalPages: number;
types: string[];
environments: string[];
}
export async function loader({ request }: LoaderFunctionArgs) {
const url = new URL(request.url);
const configName = url.searchParams.get("configName") || "";
const module = url.searchParams.get("module") || "";
const name = url.searchParams.get("name") || "";
const type = url.searchParams.get("type") || "";
const environment = url.searchParams.get("environment") || "";
const isActive = url.searchParams.get("isActive") || "";
const is_active = url.searchParams.get("is_active") ? url.searchParams.get("is_active") === "true" : undefined;
const currentPage = parseInt(url.searchParams.get("page") || "1", 10);
const pageSize = parseInt(url.searchParams.get("pageSize") || "10", 10);
try {
// 模拟数据,实际项目中应从API获取
const mockConfigs: ConfigItem[] = [
{
id: "1",
configName: "database_connection",
module: ConfigModule.SYSTEM,
environment: ConfigEnvironment.PROD,
isActive: true,
configData: {
database: {
host: "db.cluster.com",
port: 5432,
pool_size: 20,
ssl: true
},
cache: {
ttl: 3600,
max_entries: 1000
},
feature_flags: ["new_ui", "analytics_v2"]
},
createdAt: "2023-07-10 10:15:23",
updatedAt: "2023-07-15 14:30:26"
},
{
id: "2",
configName: "text_extraction_ai",
module: ConfigModule.AI,
environment: ConfigEnvironment.TEST,
isActive: true,
configData: {
model: "gpt-4",
parameters: {
temperature: 0.7,
max_tokens: 2000
},
api_key: "sk-**********",
timeout: 30
},
createdAt: "2023-07-12 08:45:12",
updatedAt: "2023-07-14 09:15:33"
},
{
id: "3",
configName: "notification_service",
module: ConfigModule.NOTIFICATION,
environment: ConfigEnvironment.DEV,
isActive: false,
configData: {
email: {
smtp_server: "smtp.example.com",
port: 587,
use_tls: true,
sender: "noreply@example.com"
},
sms: {
provider: "aliyun",
region: "cn-hangzhou",
sign_name: "AI审核系统"
}
},
createdAt: "2023-07-05 13:20:45",
updatedAt: "2023-07-10 16:45:19"
},
{
id: "4",
configName: "file_storage",
module: ConfigModule.FILE,
environment: ConfigEnvironment.PROD,
isActive: true,
configData: {
type: "oss",
region: "cn-shanghai",
bucket: "contracts-ai-review",
access_control: "private",
lifecycle_rules: [
{
prefix: "temp/",
ttl_days: 7
}
]
},
createdAt: "2023-06-28 09:30:18",
updatedAt: "2023-07-08 11:22:07"
}
];
// 过滤数据
let filteredConfigs = [...mockConfigs];
if (configName) {
filteredConfigs = filteredConfigs.filter(config =>
config.configName.toLowerCase().includes(configName.toLowerCase())
);
// 获取配置列表
const configsResponse = await getConfigLists({
name,
type,
environment,
is_active,
page: currentPage,
pageSize
});
if (configsResponse.error || !configsResponse.data) {
throw new Error(configsResponse.error || "获取配置列表失败");
}
if (module) {
filteredConfigs = filteredConfigs.filter(config => config.module === module);
// 获取配置选项
const optionsResponse = await getConfigOptions();
if (optionsResponse.error || !optionsResponse.data) {
throw new Error(optionsResponse.error || "获取配置选项失败");
}
if (environment) {
filteredConfigs = filteredConfigs.filter(config => config.environment === environment);
}
if (isActive) {
const activeValue = isActive === 'true';
filteredConfigs = filteredConfigs.filter(config => config.isActive === activeValue);
}
// 计算分页信息
const totalCount = filteredConfigs.length;
const totalPages = Math.ceil(totalCount / pageSize);
// 分页截取
const startIndex = (currentPage - 1) * pageSize;
const endIndex = startIndex + pageSize;
const paginatedConfigs = filteredConfigs.slice(startIndex, endIndex);
return json<LoaderData>({
configs: paginatedConfigs,
totalCount,
configs: configsResponse.data,
totalCount: configsResponse.total,
currentPage,
pageSize,
totalPages
totalPages: Math.ceil(configsResponse.total / pageSize),
types: optionsResponse.data.types,
environments: optionsResponse.data.environments
}, {
headers: {
"Cache-Control": "max-age=60, s-maxage=180"
@@ -233,28 +123,18 @@ export async function action({ request }: ActionFunctionArgs) {
return json({ success: false, error: "缺少配置ID" }, { status: 400 });
}
// 进行更新启用和禁用的状态
try {
if (_action === 'toggleStatus') {
const isActive = formData.get('isActive') === 'true';
const newStatus = !isActive;
const is_active = formData.get('is_active') === 'true';
// 实际项目中应调用API更新状态
console.log(`切换配置 ${configId} 状态为: ${newStatus}`);
const response = await updateConfigStatus(parseInt(configId as string), is_active);
// 模拟API调用
// const response = await fetch(`/api/configs/${configId}/status`, {
// method: 'PATCH',
// headers: {
// 'Content-Type': 'application/json',
// },
// body: JSON.stringify({ isActive: newStatus }),
// });
if (!response.success) {
return json({ success: false, error: response.error }, { status: 500 });
}
// if (!response.ok) {
// throw new Error(`状态切换失败: ${response.status}`);
// }
return json({ success: true, newStatus });
return json({ success: true });
}
return json({ success: false, error: "未知操作" }, { status: 400 });
@@ -275,7 +155,7 @@ export function ErrorBoundary() {
}
export default function ConfigListsIndex() {
const { configs, totalCount, currentPage, pageSize } = useLoaderData<typeof loader>();
const { configs, totalCount, currentPage, pageSize, types, environments } = useLoaderData<typeof loader>();
const [searchParams, setSearchParams] = useSearchParams();
const submit = useSubmit();
const [showDetailModal, setShowDetailModal] = useState(false);
@@ -300,9 +180,9 @@ export default function ConfigListsIndex() {
const handleConfigNameSearch = (value: string) => {
const newParams = new URLSearchParams(searchParams);
if (value) {
newParams.set('configName', value);
newParams.set('name', value);
} else {
newParams.delete('configName');
newParams.delete('name');
}
// 搜索时,重置到第一页
@@ -312,11 +192,11 @@ export default function ConfigListsIndex() {
};
const handleToggleStatus = (config: ConfigItem) => {
if (window.confirm(`确定要${config.isActive ? '禁用' : '启用'}该配置吗?`)) {
if (window.confirm(`确定要${config.is_active ? '禁用' : '启用'}该配置吗?`)) {
const formData = new FormData();
formData.append('_action', 'toggleStatus');
formData.append('configId', config.id);
formData.append('isActive', String(config.isActive));
formData.append('configId', config.id.toString());
formData.append('is_active', String(!config.is_active));
submit(formData, { method: 'post' });
}
@@ -342,10 +222,20 @@ 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;
setSearchParams(new URLSearchParams());
if(nameInput) nameInput.value = ''
if(typeSelect) typeSelect.value = ''
if(environmentSelect) environmentSelect.value = ''
if(statusSelect) statusSelect.value = ''
};
// 关闭详情模态框
const closeDetailModal = () => {
setShowDetailModal(false);
@@ -356,43 +246,42 @@ export default function ConfigListsIndex() {
const columns = [
{
title: "配置名称",
dataIndex: "configName" as keyof ConfigItem,
key: "configName",
dataIndex: "name" as keyof ConfigItem,
key: "name",
width: "20%"
},
{
title: "所属模块",
key: "module",
key: "type",
width: "10%",
render: (_: unknown, record: ConfigItem) => MODULE_LABELS[record.module]
render: (_: unknown, record: ConfigItem) => record.type
},
{
title: "环境",
key: "environment",
width: "15%",
render: (_: unknown, record: ConfigItem) => {
const envClass = `env-tag env-tag-${record.environment}`;
return (
<span className={envClass}>
{ENVIRONMENT_LABELS[record.environment]}
<span className="env-tag">
{record.environment}
</span>
);
}
},
{
title: "状态",
key: "isActive",
key: "is_active",
width: "15%",
render: (_: unknown, record: ConfigItem) => (
<Tag color={record.isActive ? 'green' : 'red'}>
{record.isActive ? '已启用' : '已禁用'}
<Tag color={record.is_active ? 'green' : 'red'}>
{record.is_active ? '已启用' : '已禁用'}
</Tag>
)
},
{
title: "最后更新时间",
dataIndex: "updatedAt" as keyof ConfigItem,
key: "updatedAt",
dataIndex: "updated_at" as keyof ConfigItem,
key: "updated_at",
width: "15%"
},
{
@@ -417,29 +306,17 @@ export default function ConfigListsIndex() {
</Link>
<button
type="button"
className={`operation-btn ${record.isActive ? '!text-[--color-warning]' : '!text-[--color-success]'}`}
className={`operation-btn ${record.is_active ? '!text-[--color-warning]' : '!text-[--color-success]'}`}
onClick={() => handleToggleStatus(record)}
>
<i className={record.isActive ? `ri-stop-circle-line` : `ri-play-circle-line`}></i>
{record.isActive ? '禁用' : '启用'}
<i className={record.is_active ? `ri-stop-circle-line` : `ri-play-circle-line`}></i>
{record.is_active ? '禁用' : '启用'}
</button>
</div>
)
}
];
// 生成环境选项
const environmentOptions = Object.entries(ENVIRONMENT_LABELS).map(([value, label]) => ({
value,
label
}));
// 生成模块选项
const moduleOptions = Object.entries(MODULE_LABELS).map(([value, label]) => ({
value,
label
}));
return (
<div className="config-lists">
{/* 页面头部 */}
@@ -458,9 +335,9 @@ export default function ConfigListsIndex() {
<Button type="default" icon="ri-refresh-line" onClick={handleReset} className="mr-2">
</Button>
<Button type="primary" icon="ri-search-line">
{/* <Button type="primary" icon="ri-search-line">
查询
</Button>
</Button> */}
</>
}
noActionDivider={true}
@@ -468,7 +345,7 @@ export default function ConfigListsIndex() {
<SearchFilter
label="配置名称"
placeholder="请输入配置名称"
value={searchParams.get('configName') || ''}
value={searchParams.get('name') || ''}
onSearch={handleConfigNameSearch}
className="flex-1 min-w-[200px]"
instantSearch={true}
@@ -476,9 +353,9 @@ export default function ConfigListsIndex() {
<FilterSelect
label="所属模块"
name="module"
value={searchParams.get('module') || ''}
options={[{ value: '', label: '全部' }, ...moduleOptions]}
name="type"
value={searchParams.get('type') || ''}
options={[ ...types.map(type => ({ value: type, label: type }))]}
onChange={handleFilterChange}
className="flex-1 min-w-[200px]"
/>
@@ -487,17 +364,16 @@ export default function ConfigListsIndex() {
label="环境"
name="environment"
value={searchParams.get('environment') || ''}
options={[{ value: '', label: '全部' }, ...environmentOptions]}
options={[ ...environments.map(env => ({ value: env, label: env }))]}
onChange={handleFilterChange}
className="flex-1 min-w-[200px]"
/>
<FilterSelect
label="状态"
name="isActive"
value={searchParams.get('isActive') || ''}
name="is_active"
value={searchParams.get('is_active') || ''}
options={[
{ value: '', label: '全部' },
{ value: 'true', label: '已启用' },
{ value: 'false', label: '已禁用' }
]}
@@ -545,19 +421,19 @@ export default function ConfigListsIndex() {
<div className="config-detail-content">
<div className="config-detail-item">
<div className="config-detail-label"></div>
<div className="config-detail-value">{selectedConfig.configName}</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">{MODULE_LABELS[selectedConfig.module]}</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 env-tag-${selectedConfig.environment}`}>
{ENVIRONMENT_LABELS[selectedConfig.environment]}
<span className="env-tag">
{selectedConfig.environment}
</span>
</div>
</div>
@@ -565,8 +441,8 @@ export default function ConfigListsIndex() {
<div className="config-detail-item">
<div className="config-detail-label"></div>
<div className="config-detail-value">
<Tag color={selectedConfig.isActive ? 'green' : 'red'}>
{selectedConfig.isActive ? '已启用' : '已禁用'}
<Tag color={selectedConfig.is_active ? 'green' : 'red'}>
{selectedConfig.is_active ? '已启用' : '已禁用'}
</Tag>
</div>
</div>
@@ -574,19 +450,19 @@ export default function ConfigListsIndex() {
<div className="config-detail-item">
<div className="config-detail-label"></div>
<pre className="config-detail-code">
{JSON.stringify(selectedConfig.configData, null, 2)}
{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.createdAt}</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.updatedAt}</div>
<div className="config-detail-value">{selectedConfig.updated_at}</div>
</div>
</div>
</div>