feat: ✨ 新增系统配置页面
This commit is contained in:
414
src/components/EnvSyncModal.tsx
Normal file
414
src/components/EnvSyncModal.tsx
Normal file
@@ -0,0 +1,414 @@
|
||||
/**
|
||||
* .env配置同步功能组件
|
||||
* @author MHXY Development Team
|
||||
* @version 1.0.0
|
||||
*/
|
||||
|
||||
import React, { useState } from 'react';
|
||||
import { Modal, Form, Button, Space, Alert, Typography, Divider, Checkbox, Progress } from 'antd';
|
||||
import {
|
||||
SyncOutlined,
|
||||
FileTextOutlined,
|
||||
CheckCircleOutlined,
|
||||
ExclamationCircleOutlined,
|
||||
DownloadOutlined
|
||||
} from '@ant-design/icons';
|
||||
import { SystemConfig } from '../types/systemConfig';
|
||||
import systemConfigService from '../services/systemConfigService';
|
||||
|
||||
const { Text, Paragraph } = Typography;
|
||||
const { confirm } = Modal;
|
||||
|
||||
interface EnvSyncModalProps {
|
||||
visible: boolean;
|
||||
configs: SystemConfig[];
|
||||
onClose: () => void;
|
||||
onSync?: () => void;
|
||||
}
|
||||
|
||||
const EnvSyncModal: React.FC<EnvSyncModalProps> = ({
|
||||
visible,
|
||||
configs,
|
||||
onClose,
|
||||
onSync
|
||||
}) => {
|
||||
const [form] = Form.useForm();
|
||||
const [loading, setLoading] = useState<boolean>(false);
|
||||
const [progress, setProgress] = useState<number>(0);
|
||||
const [syncResult, setSyncResult] = useState<{
|
||||
success: boolean;
|
||||
message: string;
|
||||
details?: string[];
|
||||
} | null>(null);
|
||||
|
||||
// 准备.env文件内容
|
||||
const generateEnvContent = (selectedConfigs: string[]): string => {
|
||||
const envVars: Record<string, string> = {};
|
||||
|
||||
// 根据选择的配置生成环境变量
|
||||
configs.forEach(config => {
|
||||
if (selectedConfigs.includes(config.config_key)) {
|
||||
switch (config.config_key) {
|
||||
case 'jwt_secret':
|
||||
envVars.JWT_SECRET = config.config_value;
|
||||
break;
|
||||
case 'jwt_expires_in':
|
||||
envVars.JWT_EXPIRES_IN = config.config_value;
|
||||
break;
|
||||
case 'game_server_api':
|
||||
envVars.GAME_SERVER_API = config.config_value;
|
||||
break;
|
||||
case 'game_server_psk':
|
||||
envVars.GAME_SERVER_PSK = config.config_value;
|
||||
break;
|
||||
case 'game_server_timeout':
|
||||
envVars.GAME_SERVER_TIMEOUT = config.config_value;
|
||||
break;
|
||||
case 'game_server_retry_count':
|
||||
envVars.GAME_SERVER_RETRY_COUNT = config.config_value;
|
||||
break;
|
||||
case 'site_name':
|
||||
envVars.SITE_NAME = config.config_value;
|
||||
break;
|
||||
case 'maintenance_mode':
|
||||
envVars.MAINTENANCE_MODE = config.config_value;
|
||||
break;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// 生成.env文件内容
|
||||
const envContent = [
|
||||
'# .env文件 - 由系统配置自动生成',
|
||||
`# 生成时间: ${new Date().toLocaleString('zh-CN')}`,
|
||||
'# 警告: 请不要手动修改此文件,系统配置变更时此文件将被覆盖',
|
||||
'',
|
||||
'# === 系统基本信息 ===',
|
||||
`SITE_NAME="${envVars.SITE_NAME || '梦幻西游运营管理系统'}"`,
|
||||
`MAINTENANCE_MODE=${envVars.MAINTENANCE_MODE || '0'}`,
|
||||
'',
|
||||
'# === JWT安全配置 ===',
|
||||
`JWT_SECRET="${envVars.JWT_SECRET || ''}"`,
|
||||
`JWT_EXPIRES_IN=${envVars.JWT_EXPIRES_IN || '24'}`,
|
||||
'',
|
||||
'# === 游戏服务端配置 ===',
|
||||
`GAME_SERVER_API="${envVars.GAME_SERVER_API || ''}"`,
|
||||
`GAME_SERVER_PSK="${envVars.GAME_SERVER_PSK || ''}"`,
|
||||
`GAME_SERVER_TIMEOUT=${envVars.GAME_SERVER_TIMEOUT || '30'}`,
|
||||
`GAME_SERVER_RETRY_COUNT=${envVars.GAME_SERVER_RETRY_COUNT || '3'}`,
|
||||
'',
|
||||
'# === 其他配置 ===',
|
||||
'# 请在此处添加其他环境变量',
|
||||
''
|
||||
].join('\n');
|
||||
|
||||
return envContent;
|
||||
};
|
||||
|
||||
// 执行同步操作
|
||||
const handleSync = async () => {
|
||||
try {
|
||||
const values = await form.validateFields();
|
||||
const selectedConfigs = Object.keys(values).filter(key => values[key]);
|
||||
|
||||
if (selectedConfigs.length === 0) {
|
||||
throw new Error('请至少选择一个配置项进行同步');
|
||||
}
|
||||
|
||||
setLoading(true);
|
||||
setProgress(0);
|
||||
setSyncResult(null);
|
||||
|
||||
// 模拟同步进度
|
||||
const progressSteps = [
|
||||
{ percent: 20, message: '准备配置数据...' },
|
||||
{ percent: 40, message: '生成.env文件内容...' },
|
||||
{ percent: 60, message: '验证配置格式...' },
|
||||
{ percent: 80, message: '执行同步操作...' },
|
||||
{ percent: 100, message: '同步完成' }
|
||||
];
|
||||
|
||||
for (const step of progressSteps) {
|
||||
await new Promise(resolve => setTimeout(resolve, 500));
|
||||
setProgress(step.percent);
|
||||
}
|
||||
|
||||
// 准备环境配置数据
|
||||
const envConfigs = selectedConfigs.reduce((acc, configKey) => {
|
||||
const config = configs.find(c => c.config_key === configKey);
|
||||
if (config) {
|
||||
switch (configKey) {
|
||||
case 'jwt_secret':
|
||||
acc.JWT_SECRET = config.config_value;
|
||||
break;
|
||||
case 'jwt_expires_in':
|
||||
acc.JWT_EXPIRES_IN = config.config_value;
|
||||
break;
|
||||
case 'game_server_api':
|
||||
acc.GAME_SERVER_API = config.config_value;
|
||||
break;
|
||||
case 'game_server_psk':
|
||||
acc.GAME_SERVER_PSK = config.config_value;
|
||||
break;
|
||||
}
|
||||
}
|
||||
return acc;
|
||||
}, {} as any);
|
||||
|
||||
// 调用服务同步
|
||||
const result = await systemConfigService.syncToEnvFile(envConfigs);
|
||||
|
||||
setSyncResult({
|
||||
success: result.success,
|
||||
message: result.message,
|
||||
details: selectedConfigs
|
||||
});
|
||||
|
||||
if (result.success) {
|
||||
onSync?.();
|
||||
}
|
||||
|
||||
} catch (error) {
|
||||
setSyncResult({
|
||||
success: false,
|
||||
message: error instanceof Error ? error.message : '同步失败',
|
||||
details: []
|
||||
});
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
// 下载.env文件
|
||||
const handleDownload = () => {
|
||||
const selectedConfigs = ['jwt_secret', 'jwt_expires_in', 'game_server_api', 'game_server_psk'];
|
||||
const envContent = generateEnvContent(selectedConfigs);
|
||||
|
||||
const blob = new Blob([envContent], { type: 'text/plain;charset=utf-8' });
|
||||
const url = URL.createObjectURL(blob);
|
||||
const link = document.createElement('a');
|
||||
link.href = url;
|
||||
link.download = '.env';
|
||||
document.body.appendChild(link);
|
||||
link.click();
|
||||
document.body.removeChild(link);
|
||||
URL.revokeObjectURL(url);
|
||||
|
||||
// 重置状态
|
||||
setSyncResult(null);
|
||||
setProgress(0);
|
||||
};
|
||||
|
||||
// 关闭模态框
|
||||
const handleClose = () => {
|
||||
setSyncResult(null);
|
||||
setProgress(0);
|
||||
form.resetFields();
|
||||
onClose();
|
||||
};
|
||||
|
||||
// 可同步的配置项
|
||||
const configOptions = [
|
||||
{
|
||||
key: 'jwt_secret',
|
||||
label: 'JWT密钥',
|
||||
description: 'JWT签名密钥,用于令牌验证',
|
||||
sensitive: true,
|
||||
envVar: 'JWT_SECRET'
|
||||
},
|
||||
{
|
||||
key: 'jwt_expires_in',
|
||||
label: 'JWT过期时间',
|
||||
description: 'JWT访问令牌有效期(小时)',
|
||||
sensitive: false,
|
||||
envVar: 'JWT_EXPIRES_IN'
|
||||
},
|
||||
{
|
||||
key: 'game_server_api',
|
||||
label: '游戏服务端API',
|
||||
description: '游戏服务端HTTP接口地址',
|
||||
sensitive: false,
|
||||
envVar: 'GAME_SERVER_API'
|
||||
},
|
||||
{
|
||||
key: 'game_server_psk',
|
||||
label: '游戏服务端PSK',
|
||||
description: '游戏服务端预共享密钥',
|
||||
sensitive: true,
|
||||
envVar: 'GAME_SERVER_PSK'
|
||||
},
|
||||
{
|
||||
key: 'game_server_timeout',
|
||||
label: '请求超时时间',
|
||||
description: '与游戏服务端通信超时时间(秒)',
|
||||
sensitive: false,
|
||||
envVar: 'GAME_SERVER_TIMEOUT'
|
||||
},
|
||||
{
|
||||
key: 'game_server_retry_count',
|
||||
label: '重试次数',
|
||||
description: 'API请求失败时的重试次数',
|
||||
sensitive: false,
|
||||
envVar: 'GAME_SERVER_RETRY_COUNT'
|
||||
}
|
||||
];
|
||||
|
||||
// 默认选中的配置
|
||||
const defaultChecked = ['jwt_secret', 'jwt_expires_in', 'game_server_api', 'game_server_psk'];
|
||||
|
||||
return (
|
||||
<Modal
|
||||
title={
|
||||
<div style={{ display: 'flex', alignItems: 'center', gap: '8px' }}>
|
||||
<SyncOutlined />
|
||||
同步配置到.env文件
|
||||
</div>
|
||||
}
|
||||
open={visible}
|
||||
onCancel={handleClose}
|
||||
width={600}
|
||||
footer={[
|
||||
<Button key="cancel" onClick={handleClose}>
|
||||
取消
|
||||
</Button>,
|
||||
<Button
|
||||
key="download"
|
||||
icon={<DownloadOutlined />}
|
||||
onClick={handleDownload}
|
||||
disabled={loading}
|
||||
>
|
||||
下载.env文件
|
||||
</Button>,
|
||||
<Button
|
||||
key="sync"
|
||||
type="primary"
|
||||
icon={<SyncOutlined />}
|
||||
loading={loading}
|
||||
onClick={handleSync}
|
||||
disabled={syncResult?.success}
|
||||
>
|
||||
开始同步
|
||||
</Button>
|
||||
]}
|
||||
>
|
||||
<div style={{ marginBottom: '16px' }}>
|
||||
<Alert
|
||||
message="配置同步说明"
|
||||
description="选择要同步到.env文件的系统配置项,然后执行同步操作。敏感配置(如密钥)将被加密处理。"
|
||||
type="info"
|
||||
showIcon
|
||||
style={{ marginBottom: '16px' }}
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* 同步进度 */}
|
||||
{loading && (
|
||||
<div style={{ marginBottom: '16px' }}>
|
||||
<Progress
|
||||
percent={progress}
|
||||
status="active"
|
||||
strokeColor={{
|
||||
'0%': '#108ee9',
|
||||
'100%': '#87d068',
|
||||
}}
|
||||
/>
|
||||
<Text type="secondary" style={{ fontSize: '12px' }}>
|
||||
正在执行同步操作...
|
||||
</Text>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* 同步结果 */}
|
||||
{syncResult && (
|
||||
<div style={{ marginBottom: '16px' }}>
|
||||
<Alert
|
||||
message={syncResult.success ? '同步成功' : '同步失败'}
|
||||
description={syncResult.message}
|
||||
type={syncResult.success ? 'success' : 'error'}
|
||||
showIcon
|
||||
action={
|
||||
syncResult.success && (
|
||||
<Button size="small" onClick={handleDownload}>
|
||||
下载.env文件
|
||||
</Button>
|
||||
)
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* 配置选择 */}
|
||||
{!syncResult?.success && (
|
||||
<Form
|
||||
form={form}
|
||||
layout="vertical"
|
||||
initialValues={{
|
||||
configOptions: defaultChecked
|
||||
}}
|
||||
>
|
||||
<Form.Item
|
||||
name="configOptions"
|
||||
label="选择要同步的配置项"
|
||||
rules={[
|
||||
{ required: true, message: '请至少选择一个配置项' }
|
||||
]}
|
||||
>
|
||||
<Checkbox.Group style={{ width: '100%' }}>
|
||||
<Space direction="vertical" size="middle" style={{ width: '100%' }}>
|
||||
{configOptions.map(option => (
|
||||
<div
|
||||
key={option.key}
|
||||
style={{
|
||||
padding: '12px',
|
||||
border: '1px solid #d9d9d9',
|
||||
borderRadius: '6px',
|
||||
background: '#fafafa'
|
||||
}}
|
||||
>
|
||||
<div style={{ display: 'flex', alignItems: 'center', gap: '8px' }}>
|
||||
<Checkbox value={option.key}>
|
||||
<div style={{ flex: 1 }}>
|
||||
<div style={{ display: 'flex', alignItems: 'center', gap: '8px' }}>
|
||||
<Text strong>{option.label}</Text>
|
||||
{option.sensitive && (
|
||||
<Text type="danger" style={{ fontSize: '12px' }}>
|
||||
敏感
|
||||
</Text>
|
||||
)}
|
||||
</div>
|
||||
<Text type="secondary" style={{ fontSize: '12px', display: 'block', marginTop: '4px' }}>
|
||||
{option.description}
|
||||
</Text>
|
||||
<Text code style={{ fontSize: '11px', display: 'block', marginTop: '4px' }}>
|
||||
{option.envVar}
|
||||
</Text>
|
||||
</div>
|
||||
</Checkbox>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</Space>
|
||||
</Checkbox.Group>
|
||||
</Form.Item>
|
||||
</Form>
|
||||
)}
|
||||
|
||||
{/* 操作说明 */}
|
||||
<Divider />
|
||||
<div style={{ fontSize: '12px', color: '#666' }}>
|
||||
<div style={{ display: 'flex', alignItems: 'center', gap: '8px', marginBottom: '8px' }}>
|
||||
<FileTextOutlined />
|
||||
<strong>操作说明</strong>
|
||||
</div>
|
||||
<ul style={{ margin: 0, paddingLeft: '20px' }}>
|
||||
<li>同步操作将把选中的配置写入项目的.env文件</li>
|
||||
<li>敏感配置(如密钥、密码)将被加密或掩码处理</li>
|
||||
<li>建议在同步前备份现有的.env文件</li>
|
||||
<li>同步完成后需要重启应用程序以使配置生效</li>
|
||||
</ul>
|
||||
</div>
|
||||
</Modal>
|
||||
);
|
||||
};
|
||||
|
||||
export default EnvSyncModal;
|
||||
Reference in New Issue
Block a user