/** * 环境配置页面。 * * - Ant Design Table 展示键值对,支持 inline 编辑 * - 敏感值显示为 ****,编辑时可输入新值 * - 顶部按钮栏:刷新、保存、导出 */ import React, { useEffect, useState, useCallback, useRef } from 'react'; import { Table, Button, Input, Tag, Space, message, Card, Typography, Badge } from 'antd'; import type { InputRef } from 'antd'; import { ReloadOutlined, SaveOutlined, DownloadOutlined, ToolOutlined, } from '@ant-design/icons'; import type { ColumnsType } from 'antd/es/table'; import type { EnvConfigItem } from '../types'; import { fetchEnvConfig, updateEnvConfig, exportEnvConfig } from '../api/envConfig'; const { Title, Text } = Typography; const MASK = '****'; const EnvConfig: React.FC = () => { const [items, setItems] = useState([]); const [loading, setLoading] = useState(false); const [saving, setSaving] = useState(false); const [exporting, setExporting] = useState(false); const [editingKey, setEditingKey] = useState(null); const [editValue, setEditValue] = useState(''); const [dirtyMap, setDirtyMap] = useState>({}); const inputRef = useRef(null); const load = useCallback(async () => { setLoading(true); try { const data = await fetchEnvConfig(); setItems(data); setDirtyMap({}); setEditingKey(null); } catch { message.error('加载环境配置失败'); } finally { setLoading(false); } }, []); useEffect(() => { load(); }, [load]); const startEdit = (key: string, currentValue: string, isSensitive: boolean) => { setEditingKey(key); setEditValue(isSensitive ? '' : (dirtyMap[key] ?? currentValue)); setTimeout(() => { inputRef.current?.focus(); }, 0); }; const confirmEdit = (key: string, originalValue: string, isSensitive: boolean) => { const trimmed = editValue.trim(); if (isSensitive && trimmed === '') { setDirtyMap((prev) => { const next = { ...prev }; delete next[key]; return next; }); } else if (!isSensitive && trimmed === originalValue) { setDirtyMap((prev) => { const next = { ...prev }; delete next[key]; return next; }); } else { setDirtyMap((prev) => ({ ...prev, [key]: trimmed })); } setEditingKey(null); }; const cancelEdit = () => { setEditingKey(null); }; const handleSave = async () => { if (Object.keys(dirtyMap).length === 0) { message.info('没有需要保存的修改'); return; } setSaving(true); try { const payload = items.map((item) => ({ key: item.key, value: dirtyMap[item.key] ?? item.value, is_sensitive: item.is_sensitive, })); await updateEnvConfig(payload); message.success('保存成功'); await load(); } catch { message.error('保存失败'); } finally { setSaving(false); } }; const handleExport = async () => { setExporting(true); try { await exportEnvConfig(); message.success('导出成功'); } catch { message.error('导出失败'); } finally { setExporting(false); } }; const columns: ColumnsType = [ { title: '键名', dataIndex: 'key', key: 'key', width: '35%', render: (text: string) => {text}, }, { title: '值', dataIndex: 'value', key: 'value', width: '50%', render: (_: string, record: EnvConfigItem) => { if (editingKey === record.key) { return ( setEditValue(e.target.value)} onPressEnter={() => confirmEdit(record.key, record.value, record.is_sensitive)} onBlur={() => confirmEdit(record.key, record.value, record.is_sensitive)} onKeyDown={(e) => { if (e.key === 'Escape') cancelEdit(); }} style={{ fontFamily: 'monospace' }} /> ); } const isDirty = record.key in dirtyMap; const displayValue = record.is_sensitive ? (isDirty ? MASK + ' (已修改)' : MASK) : (isDirty ? dirtyMap[record.key] : record.value); return ( startEdit(record.key, record.value, record.is_sensitive)} title="点击编辑" > {displayValue || (空)} ); }, }, { title: '类型', dataIndex: 'is_sensitive', key: 'is_sensitive', width: '15%', align: 'center', render: (v: boolean) => v ? 敏感 : 普通, }, ]; const hasDirty = Object.keys(dirtyMap).length > 0; return (
<ToolOutlined style={{ marginRight: 8 }} /> 环境配置
rowKey="key" columns={columns} dataSource={items} loading={loading} pagination={false} size="small" />
); }; export default EnvConfig;