imetting/frontend/src/pages/admin/ParameterManagement.jsx

258 lines
8.7 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

import React, { useCallback, useEffect, useMemo, useState } from 'react';
import { Alert, App, Button, Drawer, Form, Input, Popconfirm, Select, Space, Switch, Table, Tag, Tooltip, Typography } from 'antd';
import { DeleteOutlined, EditOutlined, PlusOutlined, ReloadOutlined, SaveOutlined, SettingOutlined } from '@ant-design/icons';
import httpService from '../../services/httpService';
import { API_ENDPOINTS, buildApiUrl } from '../../config/api';
import AdminModuleShell from '../../components/AdminModuleShell';
import ActionButton from '../../components/ActionButton';
import StatusTag from '../../components/StatusTag';
import configService from '../../utils/configService';
import useSystemPageSize from '../../hooks/useSystemPageSize';
const { Text } = Typography;
const CATEGORY_OPTIONS = [
{ label: 'public', value: 'public' },
{ label: 'system', value: 'system' },
];
const VALUE_TYPE_OPTIONS = [
{ label: 'string', value: 'string' },
{ label: 'number', value: 'number' },
{ label: 'json', value: 'json' },
];
const PUBLIC_PARAM_KEYS = new Set([
'app_name',
'page_size',
'max_audio_size',
'preview_title',
'login_welcome',
'footer_text',
'console_subtitle',
]);
const ParameterManagement = () => {
const { message } = App.useApp();
const [items, setItems] = useState([]);
const [loading, setLoading] = useState(false);
const [drawerOpen, setDrawerOpen] = useState(false);
const [editing, setEditing] = useState(null);
const [submitting, setSubmitting] = useState(false);
const [form] = Form.useForm();
const pageSize = useSystemPageSize(10);
const currentParamKey = Form.useWatch('param_key', form);
const categorySuggestion = useMemo(() => {
const normalizedKey = String(currentParamKey || '').trim();
if (!normalizedKey) {
return '前端需要读取并缓存的参数请选择 public仅后端内部使用的参数请选择 system。';
}
if (PUBLIC_PARAM_KEYS.has(normalizedKey)) {
return `检测到参数键 ${normalizedKey} 更适合作为 public 参数,前端会统一初始化并缓存本地。`;
}
return `参数键 ${normalizedKey} 暂未命中公开参数白名单,如仅后端使用,建议归类为 system。`;
}, [currentParamKey]);
const fetchItems = useCallback(async () => {
setLoading(true);
try {
const res = await httpService.get(buildApiUrl(API_ENDPOINTS.ADMIN.PARAMETERS));
setItems(res.data.items || []);
} catch {
message.error('获取参数列表失败');
} finally {
setLoading(false);
}
}, [message]);
useEffect(() => {
fetchItems();
}, [fetchItems]);
const openCreate = () => {
setEditing(null);
form.setFieldsValue({
param_key: '',
param_name: '',
param_value: '',
value_type: 'string',
category: 'system',
description: '',
is_active: true,
});
setDrawerOpen(true);
};
const openEdit = (row) => {
setEditing(row);
form.setFieldsValue({
...row,
is_active: Boolean(row.is_active),
});
setDrawerOpen(true);
};
const submit = async () => {
const values = await form.validateFields();
setSubmitting(true);
try {
if (editing) {
await httpService.put(buildApiUrl(API_ENDPOINTS.ADMIN.PARAMETER_DETAIL(editing.param_key)), values);
message.success('参数更新成功');
} else {
await httpService.post(buildApiUrl(API_ENDPOINTS.ADMIN.PARAMETERS), values);
message.success('参数创建成功');
}
configService.clearCache();
setDrawerOpen(false);
fetchItems();
} catch (error) {
message.error(error?.response?.data?.message || '参数保存失败');
} finally {
setSubmitting(false);
}
};
const columns = [
{ title: '参数键', dataIndex: 'param_key', key: 'param_key', width: 220 },
{ title: '参数名', dataIndex: 'param_name', key: 'param_name', width: 180 },
{ title: '值', dataIndex: 'param_value', key: 'param_value' },
{ title: '类型', dataIndex: 'value_type', key: 'value_type', width: 100, render: (v) => <Tag>{v}</Tag> },
{
title: '分类',
dataIndex: 'category',
key: 'category',
width: 120,
render: (value) => (
<Tag color={value === 'public' ? 'blue' : 'default'}>
{value || 'system'}
</Tag>
),
},
{
title: '状态',
dataIndex: 'is_active',
key: 'is_active',
width: 90,
render: (v) => <StatusTag active={v} />,
},
{
title: '操作',
key: 'action',
width: 90,
render: (_, row) => (
<Space size={6}>
<ActionButton tone="edit" variant="iconSm" tooltip="编辑" icon={<EditOutlined />} onClick={() => openEdit(row)} />
<Popconfirm
title="确认删除参数?"
description={`参数键:${row.param_key}`}
okText="删除"
cancelText="取消"
okButtonProps={{ danger: true }}
onConfirm={async () => {
try {
await httpService.delete(buildApiUrl(API_ENDPOINTS.ADMIN.PARAMETER_DELETE(row.param_key)));
configService.clearCache();
message.success('参数删除成功');
fetchItems();
} catch (error) {
message.error(error?.response?.data?.message || '参数删除失败');
}
}}
>
<ActionButton tone="delete" variant="iconSm" tooltip="删除" icon={<DeleteOutlined />} />
</Popconfirm>
</Space>
),
},
];
return (
<div>
<AdminModuleShell
icon={<SettingOutlined style={{ fontSize: 18, color: '#1d4ed8' }} />}
title="参数管理"
subtitle="系统参数已从字典 system_config 迁移到专用参数表。"
rightActions={(
<Space>
<Button icon={<ReloadOutlined />} onClick={fetchItems} loading={loading}>刷新</Button>
<Button type="primary" icon={<PlusOutlined />} onClick={openCreate}>新增参数</Button>
</Space>
)}
stats={[
{ label: '参数总数', value: items.length },
{ label: '启用参数', value: items.filter((item) => item.is_active).length },
]}
>
<div className="console-table">
<Table
rowKey="param_key"
columns={columns}
dataSource={items}
loading={loading}
pagination={{ pageSize, showTotal: (count) => `${count}` }}
scroll={{ x: 1100 }}
/>
</div>
</AdminModuleShell>
<Drawer
title={editing ? '编辑参数' : '新增参数'}
placement="right"
width={560}
open={drawerOpen}
onClose={() => setDrawerOpen(false)}
extra={(
<Space>
<Button type="primary" icon={<SaveOutlined />} loading={submitting} onClick={submit}>保存</Button>
</Space>
)}
>
<Form form={form} layout="vertical">
<Alert
type="info"
showIcon
style={{ marginBottom: 16 }}
message="参数分类说明"
description={(
<Space direction="vertical" size={4}>
<Text>`public`前端可读取适合页面交互参数</Text>
<Text>`system`仅后端内部使用不对前端公开</Text>
</Space>
)}
/>
<Form.Item name="param_key" label="参数键" rules={[{ required: true, message: '请输入参数键' }]}>
<Input placeholder="page_size" disabled={Boolean(editing)} />
</Form.Item>
<Form.Item name="param_name" label="参数名" rules={[{ required: true, message: '请输入参数名' }]}>
<Input placeholder="通用分页数量" />
</Form.Item>
<Form.Item name="param_value" label="参数值" rules={[{ required: true, message: '请输入参数值' }]}>
<Input.TextArea rows={3} />
</Form.Item>
<Form.Item name="value_type" label="值类型" rules={[{ required: true, message: '请选择值类型' }]}>
<Select options={VALUE_TYPE_OPTIONS} />
</Form.Item>
<Form.Item
name="category"
label="分类"
rules={[{ required: true, message: '请选择分类' }]}
extra={categorySuggestion}
>
<Select options={CATEGORY_OPTIONS} />
</Form.Item>
<Form.Item name="description" label="描述">
<Input.TextArea rows={2} />
</Form.Item>
<Form.Item name="is_active" label="启用状态" valuePropName="checked">
<Switch />
</Form.Item>
</Form>
</Drawer>
</div>
);
};
export default ParameterManagement;