2026-03-19 01:39:34 +08:00
|
|
|
|
import React, { useState, useEffect } from 'react';
|
|
|
|
|
|
import {
|
|
|
|
|
|
Card,
|
|
|
|
|
|
Row,
|
|
|
|
|
|
Col,
|
|
|
|
|
|
Button,
|
|
|
|
|
|
Tabs,
|
|
|
|
|
|
Form,
|
|
|
|
|
|
Input,
|
|
|
|
|
|
Switch,
|
|
|
|
|
|
Select,
|
|
|
|
|
|
Avatar,
|
|
|
|
|
|
Upload,
|
|
|
|
|
|
message,
|
|
|
|
|
|
Divider,
|
|
|
|
|
|
Typography,
|
|
|
|
|
|
Space,
|
|
|
|
|
|
Tag,
|
|
|
|
|
|
Table,
|
|
|
|
|
|
Modal,
|
|
|
|
|
|
Descriptions,
|
|
|
|
|
|
List,
|
|
|
|
|
|
Badge,
|
|
|
|
|
|
Popconfirm,
|
|
|
|
|
|
TimePicker,
|
|
|
|
|
|
Checkbox,
|
|
|
|
|
|
Radio,
|
|
|
|
|
|
Slider,
|
|
|
|
|
|
Tooltip,
|
|
|
|
|
|
Alert,
|
|
|
|
|
|
} from 'antd';
|
|
|
|
|
|
import {
|
|
|
|
|
|
UserOutlined,
|
|
|
|
|
|
TeamOutlined,
|
|
|
|
|
|
SettingOutlined,
|
|
|
|
|
|
LockOutlined,
|
|
|
|
|
|
BellOutlined,
|
|
|
|
|
|
GlobalOutlined,
|
|
|
|
|
|
SafetyOutlined,
|
|
|
|
|
|
UploadOutlined,
|
|
|
|
|
|
EditOutlined,
|
|
|
|
|
|
DeleteOutlined,
|
|
|
|
|
|
PlusOutlined,
|
|
|
|
|
|
KeyOutlined,
|
|
|
|
|
|
MailOutlined,
|
|
|
|
|
|
MobileOutlined,
|
|
|
|
|
|
DesktopOutlined,
|
|
|
|
|
|
ApiOutlined,
|
|
|
|
|
|
DatabaseOutlined,
|
|
|
|
|
|
CloudOutlined,
|
|
|
|
|
|
FileTextOutlined,
|
|
|
|
|
|
QuestionCircleOutlined,
|
|
|
|
|
|
SaveOutlined,
|
|
|
|
|
|
ReloadOutlined,
|
|
|
|
|
|
} from '@ant-design/icons';
|
|
|
|
|
|
import type { ColumnsType } from 'antd/es/table';
|
|
|
|
|
|
import moment from 'moment';
|
2026-03-18 19:12:38 +08:00
|
|
|
|
|
2026-03-19 01:39:34 +08:00
|
|
|
|
const { Title, Text, Paragraph } = Typography;
|
2026-03-18 19:12:38 +08:00
|
|
|
|
const { TabPane } = Tabs;
|
2026-03-19 01:39:34 +08:00
|
|
|
|
const { Option } = Select;
|
|
|
|
|
|
const { TextArea } = Input;
|
|
|
|
|
|
|
|
|
|
|
|
// 用户设置接口
|
|
|
|
|
|
interface UserProfile {
|
|
|
|
|
|
id: string;
|
|
|
|
|
|
username: string;
|
|
|
|
|
|
email: string;
|
|
|
|
|
|
phone: string;
|
|
|
|
|
|
avatar: string;
|
|
|
|
|
|
role: string;
|
|
|
|
|
|
department: string;
|
|
|
|
|
|
timezone: string;
|
|
|
|
|
|
language: string;
|
|
|
|
|
|
createdAt: string;
|
|
|
|
|
|
lastLoginAt: string;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 通知设置接口
|
|
|
|
|
|
interface NotificationSettings {
|
|
|
|
|
|
emailEnabled: boolean;
|
|
|
|
|
|
smsEnabled: boolean;
|
|
|
|
|
|
pushEnabled: boolean;
|
|
|
|
|
|
orderAlerts: boolean;
|
|
|
|
|
|
inventoryAlerts: boolean;
|
|
|
|
|
|
complianceAlerts: boolean;
|
|
|
|
|
|
systemAlerts: boolean;
|
|
|
|
|
|
marketingEmails: boolean;
|
|
|
|
|
|
digestFrequency: 'realtime' | 'hourly' | 'daily' | 'weekly';
|
|
|
|
|
|
quietHoursStart: string;
|
|
|
|
|
|
quietHoursEnd: string;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 安全设置接口
|
|
|
|
|
|
interface SecuritySettings {
|
|
|
|
|
|
twoFactorEnabled: boolean;
|
|
|
|
|
|
loginNotification: boolean;
|
|
|
|
|
|
passwordExpiryDays: number;
|
|
|
|
|
|
sessionTimeout: number;
|
|
|
|
|
|
ipWhitelist: string[];
|
|
|
|
|
|
apiKey: string;
|
|
|
|
|
|
lastPasswordChange: string;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 系统设置接口
|
|
|
|
|
|
interface SystemSettings {
|
|
|
|
|
|
autoSync: boolean;
|
|
|
|
|
|
syncInterval: number;
|
|
|
|
|
|
dataRetentionDays: number;
|
|
|
|
|
|
backupEnabled: boolean;
|
|
|
|
|
|
backupFrequency: 'daily' | 'weekly' | 'monthly';
|
|
|
|
|
|
logLevel: 'debug' | 'info' | 'warn' | 'error';
|
|
|
|
|
|
theme: 'light' | 'dark' | 'auto';
|
|
|
|
|
|
dateFormat: string;
|
|
|
|
|
|
currency: string;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// API密钥接口
|
|
|
|
|
|
interface ApiKey {
|
|
|
|
|
|
id: string;
|
|
|
|
|
|
name: string;
|
|
|
|
|
|
key: string;
|
|
|
|
|
|
permissions: string[];
|
|
|
|
|
|
createdAt: string;
|
|
|
|
|
|
lastUsedAt: string;
|
|
|
|
|
|
status: 'active' | 'inactive';
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 操作日志接口
|
|
|
|
|
|
interface OperationLog {
|
|
|
|
|
|
id: string;
|
|
|
|
|
|
userId: string;
|
|
|
|
|
|
username: string;
|
|
|
|
|
|
action: string;
|
|
|
|
|
|
module: string;
|
|
|
|
|
|
details: string;
|
|
|
|
|
|
ip: string;
|
|
|
|
|
|
userAgent: string;
|
|
|
|
|
|
createdAt: string;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
const MOCK_USER_PROFILE: UserProfile = {
|
|
|
|
|
|
id: '1',
|
|
|
|
|
|
username: 'admin',
|
|
|
|
|
|
email: 'admin@crawlful.com',
|
|
|
|
|
|
phone: '+86 13800138000',
|
|
|
|
|
|
avatar: '',
|
|
|
|
|
|
role: 'ADMIN',
|
|
|
|
|
|
department: '技术部',
|
|
|
|
|
|
timezone: 'Asia/Shanghai',
|
|
|
|
|
|
language: 'zh-CN',
|
|
|
|
|
|
createdAt: '2025-01-15',
|
|
|
|
|
|
lastLoginAt: '2026-03-18 10:30:00',
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
const MOCK_NOTIFICATION_SETTINGS: NotificationSettings = {
|
|
|
|
|
|
emailEnabled: true,
|
|
|
|
|
|
smsEnabled: false,
|
|
|
|
|
|
pushEnabled: true,
|
|
|
|
|
|
orderAlerts: true,
|
|
|
|
|
|
inventoryAlerts: true,
|
|
|
|
|
|
complianceAlerts: true,
|
|
|
|
|
|
systemAlerts: true,
|
|
|
|
|
|
marketingEmails: false,
|
|
|
|
|
|
digestFrequency: 'daily',
|
|
|
|
|
|
quietHoursStart: '22:00',
|
|
|
|
|
|
quietHoursEnd: '08:00',
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
const MOCK_SECURITY_SETTINGS: SecuritySettings = {
|
|
|
|
|
|
twoFactorEnabled: false,
|
|
|
|
|
|
loginNotification: true,
|
|
|
|
|
|
passwordExpiryDays: 90,
|
|
|
|
|
|
sessionTimeout: 30,
|
|
|
|
|
|
ipWhitelist: [],
|
|
|
|
|
|
apiKey: 'sk_live_xxxxxxxxxxxxxxxx',
|
|
|
|
|
|
lastPasswordChange: '2026-01-01',
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
const MOCK_SYSTEM_SETTINGS: SystemSettings = {
|
|
|
|
|
|
autoSync: true,
|
|
|
|
|
|
syncInterval: 15,
|
|
|
|
|
|
dataRetentionDays: 365,
|
|
|
|
|
|
backupEnabled: true,
|
|
|
|
|
|
backupFrequency: 'daily',
|
|
|
|
|
|
logLevel: 'info',
|
|
|
|
|
|
theme: 'light',
|
|
|
|
|
|
dateFormat: 'YYYY-MM-DD',
|
|
|
|
|
|
currency: 'CNY',
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
const MOCK_API_KEYS: ApiKey[] = [
|
|
|
|
|
|
{
|
|
|
|
|
|
id: '1',
|
|
|
|
|
|
name: 'Production API Key',
|
|
|
|
|
|
key: 'sk_live_prod_xxxxxxxx',
|
|
|
|
|
|
permissions: ['read', 'write'],
|
|
|
|
|
|
createdAt: '2026-01-01',
|
|
|
|
|
|
lastUsedAt: '2026-03-18 09:00:00',
|
|
|
|
|
|
status: 'active',
|
|
|
|
|
|
},
|
|
|
|
|
|
{
|
|
|
|
|
|
id: '2',
|
|
|
|
|
|
name: 'Test API Key',
|
|
|
|
|
|
key: 'sk_test_xxxxxxxx',
|
|
|
|
|
|
permissions: ['read'],
|
|
|
|
|
|
createdAt: '2026-02-01',
|
|
|
|
|
|
lastUsedAt: '2026-03-17 15:30:00',
|
|
|
|
|
|
status: 'active',
|
|
|
|
|
|
},
|
|
|
|
|
|
];
|
|
|
|
|
|
|
|
|
|
|
|
const MOCK_OPERATION_LOGS: OperationLog[] = [
|
|
|
|
|
|
{
|
|
|
|
|
|
id: '1',
|
|
|
|
|
|
userId: '1',
|
|
|
|
|
|
username: 'admin',
|
|
|
|
|
|
action: 'LOGIN',
|
|
|
|
|
|
module: 'AUTH',
|
|
|
|
|
|
details: '用户登录成功',
|
|
|
|
|
|
ip: '192.168.1.100',
|
|
|
|
|
|
userAgent: 'Mozilla/5.0',
|
|
|
|
|
|
createdAt: '2026-03-18 10:30:00',
|
|
|
|
|
|
},
|
|
|
|
|
|
{
|
|
|
|
|
|
id: '2',
|
|
|
|
|
|
userId: '1',
|
|
|
|
|
|
username: 'admin',
|
|
|
|
|
|
action: 'UPDATE_PRODUCT',
|
|
|
|
|
|
module: 'PRODUCT',
|
|
|
|
|
|
details: '更新产品信息: P001',
|
|
|
|
|
|
ip: '192.168.1.100',
|
|
|
|
|
|
userAgent: 'Mozilla/5.0',
|
|
|
|
|
|
createdAt: '2026-03-18 10:35:00',
|
|
|
|
|
|
},
|
|
|
|
|
|
{
|
|
|
|
|
|
id: '3',
|
|
|
|
|
|
userId: '1',
|
|
|
|
|
|
username: 'admin',
|
|
|
|
|
|
action: 'CREATE_ORDER',
|
|
|
|
|
|
module: 'ORDER',
|
|
|
|
|
|
details: '创建订单: ORD-2026-001',
|
|
|
|
|
|
ip: '192.168.1.100',
|
|
|
|
|
|
userAgent: 'Mozilla/5.0',
|
|
|
|
|
|
createdAt: '2026-03-18 11:00:00',
|
|
|
|
|
|
},
|
|
|
|
|
|
];
|
2026-03-18 19:12:38 +08:00
|
|
|
|
|
|
|
|
|
|
const Settings: React.FC = () => {
|
2026-03-19 01:39:34 +08:00
|
|
|
|
const [activeTab, setActiveTab] = useState('profile');
|
|
|
|
|
|
const [loading, setLoading] = useState(false);
|
|
|
|
|
|
const [saving, setSaving] = useState(false);
|
|
|
|
|
|
|
|
|
|
|
|
// 表单实例
|
|
|
|
|
|
const [profileForm] = Form.useForm();
|
|
|
|
|
|
const [notificationForm] = Form.useForm();
|
|
|
|
|
|
const [securityForm] = Form.useForm();
|
|
|
|
|
|
const [systemForm] = Form.useForm();
|
|
|
|
|
|
const [passwordForm] = Form.useForm();
|
|
|
|
|
|
const [apiKeyForm] = Form.useForm();
|
|
|
|
|
|
|
|
|
|
|
|
// 数据状态
|
|
|
|
|
|
const [userProfile, setUserProfile] = useState<UserProfile>(MOCK_USER_PROFILE);
|
|
|
|
|
|
const [notificationSettings, setNotificationSettings] = useState<NotificationSettings>(MOCK_NOTIFICATION_SETTINGS);
|
|
|
|
|
|
const [securitySettings, setSecuritySettings] = useState<SecuritySettings>(MOCK_SECURITY_SETTINGS);
|
|
|
|
|
|
const [systemSettings, setSystemSettings] = useState<SystemSettings>(MOCK_SYSTEM_SETTINGS);
|
|
|
|
|
|
const [apiKeys, setApiKeys] = useState<ApiKey[]>(MOCK_API_KEYS);
|
|
|
|
|
|
const [operationLogs, setOperationLogs] = useState<OperationLog[]>(MOCK_OPERATION_LOGS);
|
|
|
|
|
|
|
|
|
|
|
|
// 模态框状态
|
|
|
|
|
|
const [passwordModalVisible, setPasswordModalVisible] = useState(false);
|
|
|
|
|
|
const [apiKeyModalVisible, setApiKeyModalVisible] = useState(false);
|
|
|
|
|
|
const [twoFactorModalVisible, setTwoFactorModalVisible] = useState(false);
|
|
|
|
|
|
|
|
|
|
|
|
useEffect(() => {
|
|
|
|
|
|
// 初始化表单值
|
|
|
|
|
|
profileForm.setFieldsValue(userProfile);
|
|
|
|
|
|
notificationForm.setFieldsValue(notificationSettings);
|
|
|
|
|
|
securityForm.setFieldsValue(securitySettings);
|
|
|
|
|
|
systemForm.setFieldsValue(systemSettings);
|
|
|
|
|
|
}, []);
|
|
|
|
|
|
|
|
|
|
|
|
// ==================== 个人资料 ====================
|
|
|
|
|
|
|
|
|
|
|
|
const handleProfileSave = async () => {
|
|
|
|
|
|
try {
|
|
|
|
|
|
setSaving(true);
|
|
|
|
|
|
const values = await profileForm.validateFields();
|
|
|
|
|
|
await new Promise(resolve => setTimeout(resolve, 500));
|
|
|
|
|
|
setUserProfile({ ...userProfile, ...values });
|
|
|
|
|
|
message.success('个人资料已保存');
|
|
|
|
|
|
} catch (error) {
|
|
|
|
|
|
console.error('Save failed:', error);
|
|
|
|
|
|
} finally {
|
|
|
|
|
|
setSaving(false);
|
|
|
|
|
|
}
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
const handleAvatarUpload = (info: any) => {
|
|
|
|
|
|
if (info.file.status === 'done') {
|
|
|
|
|
|
message.success('头像上传成功');
|
|
|
|
|
|
}
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
// ==================== 通知设置 ====================
|
|
|
|
|
|
|
|
|
|
|
|
const handleNotificationSave = async () => {
|
|
|
|
|
|
try {
|
|
|
|
|
|
setSaving(true);
|
|
|
|
|
|
const values = await notificationForm.validateFields();
|
|
|
|
|
|
await new Promise(resolve => setTimeout(resolve, 500));
|
|
|
|
|
|
setNotificationSettings(values);
|
|
|
|
|
|
message.success('通知设置已保存');
|
|
|
|
|
|
} catch (error) {
|
|
|
|
|
|
console.error('Save failed:', error);
|
|
|
|
|
|
} finally {
|
|
|
|
|
|
setSaving(false);
|
|
|
|
|
|
}
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
// ==================== 安全设置 ====================
|
|
|
|
|
|
|
|
|
|
|
|
const handlePasswordChange = async () => {
|
|
|
|
|
|
try {
|
|
|
|
|
|
const values = await passwordForm.validateFields();
|
|
|
|
|
|
if (values.newPassword !== values.confirmPassword) {
|
|
|
|
|
|
message.error('两次输入的密码不一致');
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
await new Promise(resolve => setTimeout(resolve, 500));
|
|
|
|
|
|
message.success('密码修改成功');
|
|
|
|
|
|
setPasswordModalVisible(false);
|
|
|
|
|
|
passwordForm.resetFields();
|
|
|
|
|
|
} catch (error) {
|
|
|
|
|
|
console.error('Password change failed:', error);
|
|
|
|
|
|
}
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
const handleTwoFactorToggle = async (enabled: boolean) => {
|
|
|
|
|
|
if (enabled) {
|
|
|
|
|
|
setTwoFactorModalVisible(true);
|
|
|
|
|
|
} else {
|
|
|
|
|
|
setSecuritySettings({ ...securitySettings, twoFactorEnabled: false });
|
|
|
|
|
|
message.success('双因素认证已关闭');
|
|
|
|
|
|
}
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
const handleSecuritySave = async () => {
|
|
|
|
|
|
try {
|
|
|
|
|
|
setSaving(true);
|
|
|
|
|
|
const values = await securityForm.validateFields();
|
|
|
|
|
|
await new Promise(resolve => setTimeout(resolve, 500));
|
|
|
|
|
|
setSecuritySettings({ ...securitySettings, ...values });
|
|
|
|
|
|
message.success('安全设置已保存');
|
|
|
|
|
|
} catch (error) {
|
|
|
|
|
|
console.error('Save failed:', error);
|
|
|
|
|
|
} finally {
|
|
|
|
|
|
setSaving(false);
|
|
|
|
|
|
}
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
// ==================== API密钥管理 ====================
|
|
|
|
|
|
|
|
|
|
|
|
const handleCreateApiKey = async () => {
|
|
|
|
|
|
try {
|
|
|
|
|
|
const values = await apiKeyForm.validateFields();
|
|
|
|
|
|
const newKey: ApiKey = {
|
|
|
|
|
|
id: `${Date.now()}`,
|
|
|
|
|
|
name: values.name,
|
|
|
|
|
|
key: `sk_${values.environment}_${Math.random().toString(36).substring(2, 15)}`,
|
|
|
|
|
|
permissions: values.permissions,
|
|
|
|
|
|
createdAt: moment().format('YYYY-MM-DD'),
|
|
|
|
|
|
lastUsedAt: '-',
|
|
|
|
|
|
status: 'active',
|
|
|
|
|
|
};
|
|
|
|
|
|
setApiKeys([newKey, ...apiKeys]);
|
|
|
|
|
|
setApiKeyModalVisible(false);
|
|
|
|
|
|
apiKeyForm.resetFields();
|
|
|
|
|
|
message.success('API密钥创建成功');
|
|
|
|
|
|
} catch (error) {
|
|
|
|
|
|
console.error('Create API key failed:', error);
|
|
|
|
|
|
}
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
const handleDeleteApiKey = (id: string) => {
|
|
|
|
|
|
setApiKeys(apiKeys.filter(key => key.id !== id));
|
|
|
|
|
|
message.success('API密钥已删除');
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
const handleCopyApiKey = (key: string) => {
|
|
|
|
|
|
navigator.clipboard.writeText(key);
|
|
|
|
|
|
message.success('API密钥已复制到剪贴板');
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
// ==================== 系统设置 ====================
|
|
|
|
|
|
|
|
|
|
|
|
const handleSystemSave = async () => {
|
|
|
|
|
|
try {
|
|
|
|
|
|
setSaving(true);
|
|
|
|
|
|
const values = await systemForm.validateFields();
|
|
|
|
|
|
await new Promise(resolve => setTimeout(resolve, 500));
|
|
|
|
|
|
setSystemSettings(values);
|
|
|
|
|
|
message.success('系统设置已保存');
|
|
|
|
|
|
} catch (error) {
|
|
|
|
|
|
console.error('Save failed:', error);
|
|
|
|
|
|
} finally {
|
|
|
|
|
|
setSaving(false);
|
|
|
|
|
|
}
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
// ==================== 表格列定义 ====================
|
|
|
|
|
|
|
|
|
|
|
|
const apiKeyColumns: ColumnsType<ApiKey> = [
|
|
|
|
|
|
{
|
|
|
|
|
|
title: '名称',
|
|
|
|
|
|
dataIndex: 'name',
|
|
|
|
|
|
key: 'name',
|
|
|
|
|
|
},
|
|
|
|
|
|
{
|
|
|
|
|
|
title: '密钥',
|
|
|
|
|
|
dataIndex: 'key',
|
|
|
|
|
|
key: 'key',
|
|
|
|
|
|
render: (key) => (
|
|
|
|
|
|
<Space>
|
|
|
|
|
|
<Text copyable={{ text: key }}>{key.substring(0, 20)}...</Text>
|
|
|
|
|
|
</Space>
|
|
|
|
|
|
),
|
|
|
|
|
|
},
|
|
|
|
|
|
{
|
|
|
|
|
|
title: '权限',
|
|
|
|
|
|
dataIndex: 'permissions',
|
|
|
|
|
|
key: 'permissions',
|
|
|
|
|
|
render: (permissions) => (
|
|
|
|
|
|
<Space>
|
|
|
|
|
|
{permissions.map((perm: string) => (
|
|
|
|
|
|
<Tag key={perm} color={perm === 'write' ? 'blue' : 'green'}>
|
|
|
|
|
|
{perm === 'read' ? '读取' : '写入'}
|
|
|
|
|
|
</Tag>
|
|
|
|
|
|
))}
|
|
|
|
|
|
</Space>
|
|
|
|
|
|
),
|
|
|
|
|
|
},
|
|
|
|
|
|
{
|
|
|
|
|
|
title: '状态',
|
|
|
|
|
|
dataIndex: 'status',
|
|
|
|
|
|
key: 'status',
|
|
|
|
|
|
render: (status) => (
|
|
|
|
|
|
<Badge status={status === 'active' ? 'success' : 'default'} text={status === 'active' ? '活跃' : '已停用'} />
|
|
|
|
|
|
),
|
|
|
|
|
|
},
|
|
|
|
|
|
{
|
|
|
|
|
|
title: '创建时间',
|
|
|
|
|
|
dataIndex: 'createdAt',
|
|
|
|
|
|
key: 'createdAt',
|
|
|
|
|
|
},
|
|
|
|
|
|
{
|
|
|
|
|
|
title: '最后使用',
|
|
|
|
|
|
dataIndex: 'lastUsedAt',
|
|
|
|
|
|
key: 'lastUsedAt',
|
|
|
|
|
|
},
|
|
|
|
|
|
{
|
|
|
|
|
|
title: '操作',
|
|
|
|
|
|
key: 'action',
|
|
|
|
|
|
render: (_, record) => (
|
|
|
|
|
|
<Space size="small">
|
|
|
|
|
|
<Button type="link" size="small" onClick={() => handleCopyApiKey(record.key)}>
|
|
|
|
|
|
复制
|
|
|
|
|
|
</Button>
|
|
|
|
|
|
<Popconfirm
|
|
|
|
|
|
title="确认删除"
|
|
|
|
|
|
description="确定要删除此API密钥吗?此操作不可恢复。"
|
|
|
|
|
|
onConfirm={() => handleDeleteApiKey(record.id)}
|
|
|
|
|
|
okText="删除"
|
|
|
|
|
|
cancelText="取消"
|
|
|
|
|
|
>
|
|
|
|
|
|
<Button type="link" size="small" danger>
|
|
|
|
|
|
删除
|
|
|
|
|
|
</Button>
|
|
|
|
|
|
</Popconfirm>
|
|
|
|
|
|
</Space>
|
|
|
|
|
|
),
|
|
|
|
|
|
},
|
|
|
|
|
|
];
|
|
|
|
|
|
|
|
|
|
|
|
const logColumns: ColumnsType<OperationLog> = [
|
|
|
|
|
|
{
|
|
|
|
|
|
title: '时间',
|
|
|
|
|
|
dataIndex: 'createdAt',
|
|
|
|
|
|
key: 'createdAt',
|
|
|
|
|
|
width: 160,
|
|
|
|
|
|
},
|
|
|
|
|
|
{
|
|
|
|
|
|
title: '用户',
|
|
|
|
|
|
dataIndex: 'username',
|
|
|
|
|
|
key: 'username',
|
|
|
|
|
|
},
|
|
|
|
|
|
{
|
|
|
|
|
|
title: '操作',
|
|
|
|
|
|
dataIndex: 'action',
|
|
|
|
|
|
key: 'action',
|
|
|
|
|
|
render: (action) => {
|
|
|
|
|
|
const actionMap: Record<string, { color: string; text: string }> = {
|
|
|
|
|
|
LOGIN: { color: 'green', text: '登录' },
|
|
|
|
|
|
LOGOUT: { color: 'default', text: '登出' },
|
|
|
|
|
|
CREATE: { color: 'blue', text: '创建' },
|
|
|
|
|
|
UPDATE: { color: 'orange', text: '更新' },
|
|
|
|
|
|
DELETE: { color: 'red', text: '删除' },
|
|
|
|
|
|
};
|
|
|
|
|
|
const config = actionMap[action] || { color: 'default', text: action };
|
|
|
|
|
|
return <Tag color={config.color}>{config.text}</Tag>;
|
|
|
|
|
|
},
|
|
|
|
|
|
},
|
|
|
|
|
|
{
|
|
|
|
|
|
title: '模块',
|
|
|
|
|
|
dataIndex: 'module',
|
|
|
|
|
|
key: 'module',
|
|
|
|
|
|
},
|
|
|
|
|
|
{
|
|
|
|
|
|
title: '详情',
|
|
|
|
|
|
dataIndex: 'details',
|
|
|
|
|
|
key: 'details',
|
|
|
|
|
|
},
|
|
|
|
|
|
{
|
|
|
|
|
|
title: 'IP地址',
|
|
|
|
|
|
dataIndex: 'ip',
|
|
|
|
|
|
key: 'ip',
|
|
|
|
|
|
},
|
|
|
|
|
|
];
|
2026-03-18 19:12:38 +08:00
|
|
|
|
|
|
|
|
|
|
return (
|
2026-03-19 01:39:34 +08:00
|
|
|
|
<div className="settings-page" style={{ padding: 24 }}>
|
|
|
|
|
|
<div style={{ marginBottom: 24 }}>
|
|
|
|
|
|
<Title level={4}>系统设置</Title>
|
|
|
|
|
|
<Text type="secondary">管理您的账户设置、通知偏好和系统配置</Text>
|
2026-03-18 19:12:38 +08:00
|
|
|
|
</div>
|
|
|
|
|
|
|
2026-03-19 01:39:34 +08:00
|
|
|
|
<Tabs activeKey={activeTab} onChange={setActiveTab} type="card">
|
|
|
|
|
|
{/* 个人资料 */}
|
|
|
|
|
|
<TabPane
|
|
|
|
|
|
tab={
|
|
|
|
|
|
<span>
|
|
|
|
|
|
<UserOutlined />
|
|
|
|
|
|
个人资料
|
|
|
|
|
|
</span>
|
|
|
|
|
|
}
|
|
|
|
|
|
key="profile"
|
|
|
|
|
|
>
|
2026-03-18 19:12:38 +08:00
|
|
|
|
<Card>
|
2026-03-19 01:39:34 +08:00
|
|
|
|
<Row gutter={24}>
|
|
|
|
|
|
<Col span={8} style={{ textAlign: 'center' }}>
|
|
|
|
|
|
<Avatar size={120} icon={<UserOutlined />} src={userProfile.avatar} />
|
|
|
|
|
|
<div style={{ marginTop: 16 }}>
|
|
|
|
|
|
<Upload
|
|
|
|
|
|
name="avatar"
|
|
|
|
|
|
showUploadList={false}
|
|
|
|
|
|
onChange={handleAvatarUpload}
|
|
|
|
|
|
>
|
|
|
|
|
|
<Button icon={<UploadOutlined />}>更换头像</Button>
|
|
|
|
|
|
</Upload>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<Divider />
|
|
|
|
|
|
<Descriptions column={1} size="small">
|
|
|
|
|
|
<Descriptions.Item label="用户ID">{userProfile.id}</Descriptions.Item>
|
|
|
|
|
|
<Descriptions.Item label="角色">
|
|
|
|
|
|
<Tag color="blue">{userProfile.role}</Tag>
|
|
|
|
|
|
</Descriptions.Item>
|
|
|
|
|
|
<Descriptions.Item label="部门">{userProfile.department}</Descriptions.Item>
|
|
|
|
|
|
<Descriptions.Item label="注册时间">{userProfile.createdAt}</Descriptions.Item>
|
|
|
|
|
|
<Descriptions.Item label="最后登录">{userProfile.lastLoginAt}</Descriptions.Item>
|
|
|
|
|
|
</Descriptions>
|
|
|
|
|
|
</Col>
|
|
|
|
|
|
<Col span={16}>
|
|
|
|
|
|
<Form form={profileForm} layout="vertical">
|
|
|
|
|
|
<Row gutter={16}>
|
|
|
|
|
|
<Col span={12}>
|
|
|
|
|
|
<Form.Item
|
|
|
|
|
|
name="username"
|
|
|
|
|
|
label="用户名"
|
|
|
|
|
|
rules={[{ required: true, message: '请输入用户名' }]}
|
|
|
|
|
|
>
|
|
|
|
|
|
<Input prefix={<UserOutlined />} placeholder="用户名" />
|
|
|
|
|
|
</Form.Item>
|
|
|
|
|
|
</Col>
|
|
|
|
|
|
<Col span={12}>
|
|
|
|
|
|
<Form.Item
|
|
|
|
|
|
name="email"
|
|
|
|
|
|
label="邮箱"
|
|
|
|
|
|
rules={[
|
|
|
|
|
|
{ required: true, message: '请输入邮箱' },
|
|
|
|
|
|
{ type: 'email', message: '请输入有效的邮箱地址' },
|
|
|
|
|
|
]}
|
|
|
|
|
|
>
|
|
|
|
|
|
<Input prefix={<MailOutlined />} placeholder="邮箱" />
|
|
|
|
|
|
</Form.Item>
|
|
|
|
|
|
</Col>
|
|
|
|
|
|
</Row>
|
|
|
|
|
|
<Row gutter={16}>
|
|
|
|
|
|
<Col span={12}>
|
|
|
|
|
|
<Form.Item
|
|
|
|
|
|
name="phone"
|
|
|
|
|
|
label="手机号"
|
|
|
|
|
|
rules={[{ required: true, message: '请输入手机号' }]}
|
|
|
|
|
|
>
|
|
|
|
|
|
<Input prefix={<MobileOutlined />} placeholder="手机号" />
|
|
|
|
|
|
</Form.Item>
|
|
|
|
|
|
</Col>
|
|
|
|
|
|
<Col span={12}>
|
|
|
|
|
|
<Form.Item
|
|
|
|
|
|
name="department"
|
|
|
|
|
|
label="部门"
|
|
|
|
|
|
>
|
|
|
|
|
|
<Input placeholder="部门" />
|
|
|
|
|
|
</Form.Item>
|
|
|
|
|
|
</Col>
|
|
|
|
|
|
</Row>
|
|
|
|
|
|
<Row gutter={16}>
|
|
|
|
|
|
<Col span={12}>
|
|
|
|
|
|
<Form.Item
|
|
|
|
|
|
name="timezone"
|
|
|
|
|
|
label="时区"
|
|
|
|
|
|
>
|
|
|
|
|
|
<Select placeholder="选择时区">
|
|
|
|
|
|
<Option value="Asia/Shanghai">Asia/Shanghai (GMT+8)</Option>
|
|
|
|
|
|
<Option value="Asia/Tokyo">Asia/Tokyo (GMT+9)</Option>
|
|
|
|
|
|
<Option value="America/New_York">America/New_York (GMT-5)</Option>
|
|
|
|
|
|
<Option value="Europe/London">Europe/London (GMT+0)</Option>
|
|
|
|
|
|
</Select>
|
|
|
|
|
|
</Form.Item>
|
|
|
|
|
|
</Col>
|
|
|
|
|
|
<Col span={12}>
|
|
|
|
|
|
<Form.Item
|
|
|
|
|
|
name="language"
|
|
|
|
|
|
label="语言"
|
|
|
|
|
|
>
|
|
|
|
|
|
<Select placeholder="选择语言">
|
|
|
|
|
|
<Option value="zh-CN">简体中文</Option>
|
|
|
|
|
|
<Option value="en-US">English</Option>
|
|
|
|
|
|
<Option value="ja-JP">日本語</Option>
|
|
|
|
|
|
</Select>
|
|
|
|
|
|
</Form.Item>
|
|
|
|
|
|
</Col>
|
|
|
|
|
|
</Row>
|
|
|
|
|
|
<Form.Item>
|
|
|
|
|
|
<Button type="primary" icon={<SaveOutlined />} onClick={handleProfileSave} loading={saving}>
|
|
|
|
|
|
保存更改
|
|
|
|
|
|
</Button>
|
|
|
|
|
|
</Form.Item>
|
|
|
|
|
|
</Form>
|
|
|
|
|
|
</Col>
|
|
|
|
|
|
</Row>
|
2026-03-18 19:12:38 +08:00
|
|
|
|
</Card>
|
2026-03-19 01:39:34 +08:00
|
|
|
|
</TabPane>
|
|
|
|
|
|
|
|
|
|
|
|
{/* 通知设置 */}
|
|
|
|
|
|
<TabPane
|
|
|
|
|
|
tab={
|
|
|
|
|
|
<span>
|
|
|
|
|
|
<BellOutlined />
|
|
|
|
|
|
通知设置
|
|
|
|
|
|
</span>
|
|
|
|
|
|
}
|
|
|
|
|
|
key="notifications"
|
|
|
|
|
|
>
|
2026-03-18 19:12:38 +08:00
|
|
|
|
<Card>
|
2026-03-19 01:39:34 +08:00
|
|
|
|
<Form form={notificationForm} layout="vertical">
|
|
|
|
|
|
<Title level={5}>通知渠道</Title>
|
|
|
|
|
|
<Row gutter={16}>
|
|
|
|
|
|
<Col span={8}>
|
|
|
|
|
|
<Card size="small">
|
|
|
|
|
|
<Form.Item name="emailEnabled" valuePropName="checked" noStyle>
|
|
|
|
|
|
<Switch />
|
|
|
|
|
|
</Form.Item>
|
|
|
|
|
|
<div style={{ marginTop: 8 }}>
|
|
|
|
|
|
<MailOutlined style={{ fontSize: 24, color: '#1890ff' }} />
|
|
|
|
|
|
<div style={{ marginTop: 8 }}>
|
|
|
|
|
|
<Text strong>邮件通知</Text>
|
|
|
|
|
|
<div><Text type="secondary">接收重要事件的邮件通知</Text></div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</Card>
|
|
|
|
|
|
</Col>
|
|
|
|
|
|
<Col span={8}>
|
|
|
|
|
|
<Card size="small">
|
|
|
|
|
|
<Form.Item name="smsEnabled" valuePropName="checked" noStyle>
|
|
|
|
|
|
<Switch />
|
|
|
|
|
|
</Form.Item>
|
|
|
|
|
|
<div style={{ marginTop: 8 }}>
|
|
|
|
|
|
<MobileOutlined style={{ fontSize: 24, color: '#52c41a' }} />
|
|
|
|
|
|
<div style={{ marginTop: 8 }}>
|
|
|
|
|
|
<Text strong>短信通知</Text>
|
|
|
|
|
|
<div><Text type="secondary">接收紧急事件的短信通知</Text></div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</Card>
|
|
|
|
|
|
</Col>
|
|
|
|
|
|
<Col span={8}>
|
|
|
|
|
|
<Card size="small">
|
|
|
|
|
|
<Form.Item name="pushEnabled" valuePropName="checked" noStyle>
|
|
|
|
|
|
<Switch />
|
|
|
|
|
|
</Form.Item>
|
|
|
|
|
|
<div style={{ marginTop: 8 }}>
|
|
|
|
|
|
<DesktopOutlined style={{ fontSize: 24, color: '#faad14' }} />
|
|
|
|
|
|
<div style={{ marginTop: 8 }}>
|
|
|
|
|
|
<Text strong>推送通知</Text>
|
|
|
|
|
|
<div><Text type="secondary">接收浏览器推送通知</Text></div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</Card>
|
|
|
|
|
|
</Col>
|
|
|
|
|
|
</Row>
|
|
|
|
|
|
|
|
|
|
|
|
<Divider />
|
|
|
|
|
|
|
|
|
|
|
|
<Title level={5}>通知类型</Title>
|
|
|
|
|
|
<Form.Item name="orderAlerts" valuePropName="checked">
|
|
|
|
|
|
<Checkbox>订单提醒 - 新订单、订单状态变更</Checkbox>
|
|
|
|
|
|
</Form.Item>
|
|
|
|
|
|
<Form.Item name="inventoryAlerts" valuePropName="checked">
|
|
|
|
|
|
<Checkbox>库存提醒 - 库存不足、库存预警</Checkbox>
|
|
|
|
|
|
</Form.Item>
|
|
|
|
|
|
<Form.Item name="complianceAlerts" valuePropName="checked">
|
|
|
|
|
|
<Checkbox>合规提醒 - 证书过期、合规检查失败</Checkbox>
|
|
|
|
|
|
</Form.Item>
|
|
|
|
|
|
<Form.Item name="systemAlerts" valuePropName="checked">
|
|
|
|
|
|
<Checkbox>系统提醒 - 系统维护、功能更新</Checkbox>
|
|
|
|
|
|
</Form.Item>
|
|
|
|
|
|
<Form.Item name="marketingEmails" valuePropName="checked">
|
|
|
|
|
|
<Checkbox>营销邮件 - 产品更新、优惠活动</Checkbox>
|
|
|
|
|
|
</Form.Item>
|
|
|
|
|
|
|
|
|
|
|
|
<Divider />
|
|
|
|
|
|
|
|
|
|
|
|
<Title level={5}>高级设置</Title>
|
|
|
|
|
|
<Row gutter={16}>
|
|
|
|
|
|
<Col span={12}>
|
|
|
|
|
|
<Form.Item name="digestFrequency" label="通知摘要频率">
|
|
|
|
|
|
<Select>
|
|
|
|
|
|
<Option value="realtime">实时</Option>
|
|
|
|
|
|
<Option value="hourly">每小时</Option>
|
|
|
|
|
|
<Option value="daily">每天</Option>
|
|
|
|
|
|
<Option value="weekly">每周</Option>
|
|
|
|
|
|
</Select>
|
|
|
|
|
|
</Form.Item>
|
|
|
|
|
|
</Col>
|
|
|
|
|
|
</Row>
|
|
|
|
|
|
<Row gutter={16}>
|
|
|
|
|
|
<Col span={12}>
|
|
|
|
|
|
<Form.Item name="quietHoursStart" label="免打扰开始时间">
|
|
|
|
|
|
<TimePicker format="HH:mm" style={{ width: '100%' }} />
|
|
|
|
|
|
</Form.Item>
|
|
|
|
|
|
</Col>
|
|
|
|
|
|
<Col span={12}>
|
|
|
|
|
|
<Form.Item name="quietHoursEnd" label="免打扰结束时间">
|
|
|
|
|
|
<TimePicker format="HH:mm" style={{ width: '100%' }} />
|
|
|
|
|
|
</Form.Item>
|
|
|
|
|
|
</Col>
|
|
|
|
|
|
</Row>
|
|
|
|
|
|
|
|
|
|
|
|
<Form.Item>
|
|
|
|
|
|
<Button type="primary" icon={<SaveOutlined />} onClick={handleNotificationSave} loading={saving}>
|
|
|
|
|
|
保存设置
|
|
|
|
|
|
</Button>
|
|
|
|
|
|
</Form.Item>
|
|
|
|
|
|
</Form>
|
2026-03-18 19:12:38 +08:00
|
|
|
|
</Card>
|
2026-03-19 01:39:34 +08:00
|
|
|
|
</TabPane>
|
|
|
|
|
|
|
|
|
|
|
|
{/* 安全设置 */}
|
|
|
|
|
|
<TabPane
|
|
|
|
|
|
tab={
|
|
|
|
|
|
<span>
|
|
|
|
|
|
<SafetyOutlined />
|
|
|
|
|
|
安全设置
|
|
|
|
|
|
</span>
|
|
|
|
|
|
}
|
|
|
|
|
|
key="security"
|
|
|
|
|
|
>
|
2026-03-18 19:12:38 +08:00
|
|
|
|
<Card>
|
2026-03-19 01:39:34 +08:00
|
|
|
|
<List>
|
|
|
|
|
|
<List.Item
|
|
|
|
|
|
actions={[
|
|
|
|
|
|
<Button type="primary" onClick={() => setPasswordModalVisible(true)}>
|
|
|
|
|
|
修改密码
|
|
|
|
|
|
</Button>,
|
|
|
|
|
|
]}
|
|
|
|
|
|
>
|
|
|
|
|
|
<List.Item.Meta
|
|
|
|
|
|
avatar={<LockOutlined style={{ fontSize: 24, color: '#1890ff' }} />}
|
|
|
|
|
|
title="登录密码"
|
|
|
|
|
|
description={`上次修改: ${securitySettings.lastPasswordChange}`}
|
|
|
|
|
|
/>
|
|
|
|
|
|
</List.Item>
|
|
|
|
|
|
|
|
|
|
|
|
<List.Item
|
|
|
|
|
|
actions={[
|
|
|
|
|
|
<Switch
|
|
|
|
|
|
checked={securitySettings.twoFactorEnabled}
|
|
|
|
|
|
onChange={handleTwoFactorToggle}
|
|
|
|
|
|
/>,
|
|
|
|
|
|
]}
|
|
|
|
|
|
>
|
|
|
|
|
|
<List.Item.Meta
|
|
|
|
|
|
avatar={<SafetyOutlined style={{ fontSize: 24, color: '#52c41a' }} />}
|
|
|
|
|
|
title="双因素认证 (2FA)"
|
|
|
|
|
|
description="启用后,登录时需要输入手机验证码"
|
|
|
|
|
|
/>
|
|
|
|
|
|
</List.Item>
|
|
|
|
|
|
|
|
|
|
|
|
<List.Item
|
|
|
|
|
|
actions={[
|
|
|
|
|
|
<Switch
|
|
|
|
|
|
checked={securitySettings.loginNotification}
|
|
|
|
|
|
onChange={(checked) => setSecuritySettings({ ...securitySettings, loginNotification: checked })}
|
|
|
|
|
|
/>,
|
|
|
|
|
|
]}
|
|
|
|
|
|
>
|
|
|
|
|
|
<List.Item.Meta
|
|
|
|
|
|
avatar={<BellOutlined style={{ fontSize: 24, color: '#faad14' }} />}
|
|
|
|
|
|
title="登录通知"
|
|
|
|
|
|
description="新设备登录时发送通知"
|
|
|
|
|
|
/>
|
|
|
|
|
|
</List.Item>
|
|
|
|
|
|
</List>
|
|
|
|
|
|
|
|
|
|
|
|
<Divider />
|
|
|
|
|
|
|
|
|
|
|
|
<Form form={securityForm} layout="vertical">
|
|
|
|
|
|
<Row gutter={16}>
|
|
|
|
|
|
<Col span={12}>
|
|
|
|
|
|
<Form.Item
|
|
|
|
|
|
name="passwordExpiryDays"
|
|
|
|
|
|
label="密码过期天数"
|
|
|
|
|
|
tooltip="密码将在指定天数后过期,需要重新设置"
|
|
|
|
|
|
>
|
|
|
|
|
|
<Slider min={30} max={180} marks={{ 30: '30天', 90: '90天', 180: '180天' }} />
|
|
|
|
|
|
</Form.Item>
|
|
|
|
|
|
</Col>
|
|
|
|
|
|
<Col span={12}>
|
|
|
|
|
|
<Form.Item
|
|
|
|
|
|
name="sessionTimeout"
|
|
|
|
|
|
label="会话超时时间(分钟)"
|
|
|
|
|
|
tooltip="超过指定时间无操作将自动登出"
|
|
|
|
|
|
>
|
|
|
|
|
|
<Slider min={5} max={120} marks={{ 15: '15分', 30: '30分', 60: '60分' }} />
|
|
|
|
|
|
</Form.Item>
|
|
|
|
|
|
</Col>
|
|
|
|
|
|
</Row>
|
|
|
|
|
|
|
|
|
|
|
|
<Form.Item>
|
|
|
|
|
|
<Button type="primary" icon={<SaveOutlined />} onClick={handleSecuritySave} loading={saving}>
|
|
|
|
|
|
保存设置
|
|
|
|
|
|
</Button>
|
|
|
|
|
|
</Form.Item>
|
|
|
|
|
|
</Form>
|
2026-03-18 19:12:38 +08:00
|
|
|
|
</Card>
|
2026-03-19 01:39:34 +08:00
|
|
|
|
</TabPane>
|
|
|
|
|
|
|
|
|
|
|
|
{/* API密钥 */}
|
|
|
|
|
|
<TabPane
|
|
|
|
|
|
tab={
|
|
|
|
|
|
<span>
|
|
|
|
|
|
<ApiOutlined />
|
|
|
|
|
|
API密钥
|
|
|
|
|
|
</span>
|
|
|
|
|
|
}
|
|
|
|
|
|
key="apikeys"
|
|
|
|
|
|
>
|
|
|
|
|
|
<Card
|
|
|
|
|
|
extra={
|
|
|
|
|
|
<Button type="primary" icon={<PlusOutlined />} onClick={() => setApiKeyModalVisible(true)}>
|
|
|
|
|
|
创建API密钥
|
|
|
|
|
|
</Button>
|
|
|
|
|
|
}
|
|
|
|
|
|
>
|
|
|
|
|
|
<Alert
|
|
|
|
|
|
message="API密钥安全提示"
|
|
|
|
|
|
description="请妥善保管您的API密钥,不要在客户端代码中暴露。如怀疑密钥泄露,请立即删除并重新创建。"
|
|
|
|
|
|
type="warning"
|
|
|
|
|
|
showIcon
|
|
|
|
|
|
style={{ marginBottom: 16 }}
|
|
|
|
|
|
/>
|
|
|
|
|
|
<Table
|
|
|
|
|
|
columns={apiKeyColumns}
|
|
|
|
|
|
dataSource={apiKeys}
|
|
|
|
|
|
rowKey="id"
|
|
|
|
|
|
pagination={{ pageSize: 5 }}
|
|
|
|
|
|
/>
|
|
|
|
|
|
</Card>
|
|
|
|
|
|
</TabPane>
|
|
|
|
|
|
|
|
|
|
|
|
{/* 系统设置 */}
|
|
|
|
|
|
<TabPane
|
|
|
|
|
|
tab={
|
|
|
|
|
|
<span>
|
|
|
|
|
|
<SettingOutlined />
|
|
|
|
|
|
系统设置
|
|
|
|
|
|
</span>
|
|
|
|
|
|
}
|
|
|
|
|
|
key="system"
|
|
|
|
|
|
>
|
2026-03-18 19:12:38 +08:00
|
|
|
|
<Card>
|
2026-03-19 01:39:34 +08:00
|
|
|
|
<Form form={systemForm} layout="vertical">
|
|
|
|
|
|
<Title level={5}>数据同步</Title>
|
|
|
|
|
|
<Row gutter={16}>
|
|
|
|
|
|
<Col span={12}>
|
|
|
|
|
|
<Form.Item name="autoSync" valuePropName="checked" label="自动同步">
|
|
|
|
|
|
<Switch />
|
|
|
|
|
|
</Form.Item>
|
|
|
|
|
|
</Col>
|
|
|
|
|
|
<Col span={12}>
|
|
|
|
|
|
<Form.Item name="syncInterval" label="同步间隔(分钟)">
|
|
|
|
|
|
<Select>
|
|
|
|
|
|
<Option value={5}>5分钟</Option>
|
|
|
|
|
|
<Option value={15}>15分钟</Option>
|
|
|
|
|
|
<Option value={30}>30分钟</Option>
|
|
|
|
|
|
<Option value={60}>1小时</Option>
|
|
|
|
|
|
</Select>
|
|
|
|
|
|
</Form.Item>
|
|
|
|
|
|
</Col>
|
|
|
|
|
|
</Row>
|
|
|
|
|
|
|
|
|
|
|
|
<Divider />
|
|
|
|
|
|
|
|
|
|
|
|
<Title level={5}>数据管理</Title>
|
|
|
|
|
|
<Row gutter={16}>
|
|
|
|
|
|
<Col span={12}>
|
|
|
|
|
|
<Form.Item name="dataRetentionDays" label="数据保留天数">
|
|
|
|
|
|
<Slider min={30} max={1095} marks={{ 90: '3月', 365: '1年', 730: '2年', 1095: '3年' }} />
|
|
|
|
|
|
</Form.Item>
|
|
|
|
|
|
</Col>
|
|
|
|
|
|
<Col span={12}>
|
|
|
|
|
|
<Form.Item name="backupEnabled" valuePropName="checked" label="启用自动备份">
|
|
|
|
|
|
<Switch />
|
|
|
|
|
|
</Form.Item>
|
|
|
|
|
|
</Col>
|
|
|
|
|
|
</Row>
|
|
|
|
|
|
<Row gutter={16}>
|
|
|
|
|
|
<Col span={12}>
|
|
|
|
|
|
<Form.Item name="backupFrequency" label="备份频率">
|
|
|
|
|
|
<Select>
|
|
|
|
|
|
<Option value="daily">每天</Option>
|
|
|
|
|
|
<Option value="weekly">每周</Option>
|
|
|
|
|
|
<Option value="monthly">每月</Option>
|
|
|
|
|
|
</Select>
|
|
|
|
|
|
</Form.Item>
|
|
|
|
|
|
</Col>
|
|
|
|
|
|
</Row>
|
|
|
|
|
|
|
|
|
|
|
|
<Divider />
|
|
|
|
|
|
|
|
|
|
|
|
<Title level={5}>显示设置</Title>
|
|
|
|
|
|
<Row gutter={16}>
|
|
|
|
|
|
<Col span={8}>
|
|
|
|
|
|
<Form.Item name="theme" label="主题">
|
|
|
|
|
|
<Select>
|
|
|
|
|
|
<Option value="light">浅色</Option>
|
|
|
|
|
|
<Option value="dark">深色</Option>
|
|
|
|
|
|
<Option value="auto">自动</Option>
|
|
|
|
|
|
</Select>
|
|
|
|
|
|
</Form.Item>
|
|
|
|
|
|
</Col>
|
|
|
|
|
|
<Col span={8}>
|
|
|
|
|
|
<Form.Item name="dateFormat" label="日期格式">
|
|
|
|
|
|
<Select>
|
|
|
|
|
|
<Option value="YYYY-MM-DD">YYYY-MM-DD</Option>
|
|
|
|
|
|
<Option value="DD/MM/YYYY">DD/MM/YYYY</Option>
|
|
|
|
|
|
<Option value="MM/DD/YYYY">MM/DD/YYYY</Option>
|
|
|
|
|
|
</Select>
|
|
|
|
|
|
</Form.Item>
|
|
|
|
|
|
</Col>
|
|
|
|
|
|
<Col span={8}>
|
|
|
|
|
|
<Form.Item name="currency" label="货币">
|
|
|
|
|
|
<Select>
|
|
|
|
|
|
<Option value="CNY">人民币 (CNY)</Option>
|
|
|
|
|
|
<Option value="USD">美元 (USD)</Option>
|
|
|
|
|
|
<Option value="EUR">欧元 (EUR)</Option>
|
|
|
|
|
|
<Option value="JPY">日元 (JPY)</Option>
|
|
|
|
|
|
</Select>
|
|
|
|
|
|
</Form.Item>
|
|
|
|
|
|
</Col>
|
|
|
|
|
|
</Row>
|
|
|
|
|
|
|
|
|
|
|
|
<Divider />
|
|
|
|
|
|
|
|
|
|
|
|
<Title level={5}>日志设置</Title>
|
|
|
|
|
|
<Form.Item name="logLevel" label="日志级别">
|
|
|
|
|
|
<Radio.Group>
|
|
|
|
|
|
<Radio.Button value="debug">调试</Radio.Button>
|
|
|
|
|
|
<Radio.Button value="info">信息</Radio.Button>
|
|
|
|
|
|
<Radio.Button value="warn">警告</Radio.Button>
|
|
|
|
|
|
<Radio.Button value="error">错误</Radio.Button>
|
|
|
|
|
|
</Radio.Group>
|
|
|
|
|
|
</Form.Item>
|
|
|
|
|
|
|
|
|
|
|
|
<Form.Item>
|
|
|
|
|
|
<Button type="primary" icon={<SaveOutlined />} onClick={handleSystemSave} loading={saving}>
|
|
|
|
|
|
保存设置
|
|
|
|
|
|
</Button>
|
|
|
|
|
|
</Form.Item>
|
|
|
|
|
|
</Form>
|
|
|
|
|
|
</Card>
|
|
|
|
|
|
</TabPane>
|
|
|
|
|
|
|
|
|
|
|
|
{/* 操作日志 */}
|
|
|
|
|
|
<TabPane
|
|
|
|
|
|
tab={
|
|
|
|
|
|
<span>
|
|
|
|
|
|
<FileTextOutlined />
|
|
|
|
|
|
操作日志
|
|
|
|
|
|
</span>
|
|
|
|
|
|
}
|
|
|
|
|
|
key="logs"
|
|
|
|
|
|
>
|
|
|
|
|
|
<Card>
|
|
|
|
|
|
<Table
|
|
|
|
|
|
columns={logColumns}
|
|
|
|
|
|
dataSource={operationLogs}
|
|
|
|
|
|
rowKey="id"
|
|
|
|
|
|
pagination={{ pageSize: 10 }}
|
|
|
|
|
|
/>
|
2026-03-18 19:12:38 +08:00
|
|
|
|
</Card>
|
2026-03-19 01:39:34 +08:00
|
|
|
|
</TabPane>
|
2026-03-18 19:12:38 +08:00
|
|
|
|
|
2026-03-19 01:39:34 +08:00
|
|
|
|
{/* 配置管理 */}
|
|
|
|
|
|
<TabPane
|
|
|
|
|
|
tab={
|
|
|
|
|
|
<span>
|
|
|
|
|
|
<SettingOutlined />
|
|
|
|
|
|
配置管理
|
|
|
|
|
|
</span>
|
|
|
|
|
|
}
|
|
|
|
|
|
key="configs"
|
|
|
|
|
|
>
|
|
|
|
|
|
<Row gutter={[16, 16]}>
|
|
|
|
|
|
<Col span={12}>
|
|
|
|
|
|
<Card
|
|
|
|
|
|
hoverable
|
|
|
|
|
|
onClick={() => window.location.href = '/Settings/PlatformAccountConfig'}
|
|
|
|
|
|
>
|
|
|
|
|
|
<div style={{ textAlign: 'center' }}>
|
|
|
|
|
|
<ApiOutlined style={{ fontSize: 48, color: '#1890ff' }} />
|
|
|
|
|
|
<Title level={4} style={{ marginTop: 16 }}>平台账号配置</Title>
|
|
|
|
|
|
<Text type="secondary">管理各平台的API账号和授权信息</Text>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</Card>
|
|
|
|
|
|
</Col>
|
2026-03-18 19:12:38 +08:00
|
|
|
|
<Col span={12}>
|
2026-03-19 01:39:34 +08:00
|
|
|
|
<Card
|
|
|
|
|
|
hoverable
|
|
|
|
|
|
onClick={() => window.location.href = '/Settings/ExchangeRateConfig'}
|
|
|
|
|
|
>
|
|
|
|
|
|
<div style={{ textAlign: 'center' }}>
|
|
|
|
|
|
<GlobalOutlined style={{ fontSize: 48, color: '#52c41a' }} />
|
|
|
|
|
|
<Title level={4} style={{ marginTop: 16 }}>汇率配置</Title>
|
|
|
|
|
|
<Text type="secondary">管理多币种汇率和自动同步设置</Text>
|
2026-03-18 19:12:38 +08:00
|
|
|
|
</div>
|
|
|
|
|
|
</Card>
|
|
|
|
|
|
</Col>
|
|
|
|
|
|
<Col span={12}>
|
2026-03-19 01:39:34 +08:00
|
|
|
|
<Card
|
|
|
|
|
|
hoverable
|
|
|
|
|
|
onClick={() => window.location.href = '/Settings/CostTemplateConfig'}
|
|
|
|
|
|
>
|
|
|
|
|
|
<div style={{ textAlign: 'center' }}>
|
|
|
|
|
|
<DollarOutlined style={{ fontSize: 48, color: '#faad14' }} />
|
|
|
|
|
|
<Title level={4} style={{ marginTop: 16 }}>成本模板配置</Title>
|
|
|
|
|
|
<Text type="secondary">管理各平台和品类的成本计算模板</Text>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</Card>
|
|
|
|
|
|
</Col>
|
|
|
|
|
|
<Col span={12}>
|
|
|
|
|
|
<Card
|
|
|
|
|
|
hoverable
|
|
|
|
|
|
onClick={() => window.location.href = '/Settings/WinNodeConfig'}
|
|
|
|
|
|
>
|
|
|
|
|
|
<div style={{ textAlign: 'center' }}>
|
|
|
|
|
|
<DesktopOutlined style={{ fontSize: 48, color: '#722ed1' }} />
|
|
|
|
|
|
<Title level={4} style={{ marginTop: 16 }}>WinNode配置</Title>
|
|
|
|
|
|
<Text type="secondary">管理Windows执行节点和浏览器实例</Text>
|
2026-03-18 19:12:38 +08:00
|
|
|
|
</div>
|
|
|
|
|
|
</Card>
|
|
|
|
|
|
</Col>
|
|
|
|
|
|
</Row>
|
|
|
|
|
|
</TabPane>
|
2026-03-19 01:39:34 +08:00
|
|
|
|
</Tabs>
|
2026-03-18 19:12:38 +08:00
|
|
|
|
|
2026-03-19 01:39:34 +08:00
|
|
|
|
{/* 修改密码模态框 */}
|
|
|
|
|
|
<Modal
|
|
|
|
|
|
title="修改密码"
|
|
|
|
|
|
open={passwordModalVisible}
|
|
|
|
|
|
onOk={handlePasswordChange}
|
|
|
|
|
|
onCancel={() => {
|
|
|
|
|
|
setPasswordModalVisible(false);
|
|
|
|
|
|
passwordForm.resetFields();
|
|
|
|
|
|
}}
|
|
|
|
|
|
>
|
|
|
|
|
|
<Form form={passwordForm} layout="vertical">
|
|
|
|
|
|
<Form.Item
|
|
|
|
|
|
name="currentPassword"
|
|
|
|
|
|
label="当前密码"
|
|
|
|
|
|
rules={[{ required: true, message: '请输入当前密码' }]}
|
|
|
|
|
|
>
|
|
|
|
|
|
<Input.Password placeholder="当前密码" />
|
|
|
|
|
|
</Form.Item>
|
|
|
|
|
|
<Form.Item
|
|
|
|
|
|
name="newPassword"
|
|
|
|
|
|
label="新密码"
|
|
|
|
|
|
rules={[
|
|
|
|
|
|
{ required: true, message: '请输入新密码' },
|
|
|
|
|
|
{ min: 8, message: '密码至少8位' },
|
|
|
|
|
|
]}
|
|
|
|
|
|
>
|
|
|
|
|
|
<Input.Password placeholder="新密码" />
|
|
|
|
|
|
</Form.Item>
|
|
|
|
|
|
<Form.Item
|
|
|
|
|
|
name="confirmPassword"
|
|
|
|
|
|
label="确认新密码"
|
|
|
|
|
|
rules={[{ required: true, message: '请确认新密码' }]}
|
|
|
|
|
|
>
|
|
|
|
|
|
<Input.Password placeholder="确认新密码" />
|
|
|
|
|
|
</Form.Item>
|
|
|
|
|
|
</Form>
|
|
|
|
|
|
</Modal>
|
2026-03-18 19:12:38 +08:00
|
|
|
|
|
2026-03-19 01:39:34 +08:00
|
|
|
|
{/* 创建API密钥模态框 */}
|
|
|
|
|
|
<Modal
|
|
|
|
|
|
title="创建API密钥"
|
|
|
|
|
|
open={apiKeyModalVisible}
|
|
|
|
|
|
onOk={handleCreateApiKey}
|
|
|
|
|
|
onCancel={() => {
|
|
|
|
|
|
setApiKeyModalVisible(false);
|
|
|
|
|
|
apiKeyForm.resetFields();
|
|
|
|
|
|
}}
|
|
|
|
|
|
>
|
|
|
|
|
|
<Form form={apiKeyForm} layout="vertical">
|
|
|
|
|
|
<Form.Item
|
|
|
|
|
|
name="name"
|
|
|
|
|
|
label="密钥名称"
|
|
|
|
|
|
rules={[{ required: true, message: '请输入密钥名称' }]}
|
|
|
|
|
|
>
|
|
|
|
|
|
<Input placeholder="例如:Production API Key" />
|
|
|
|
|
|
</Form.Item>
|
|
|
|
|
|
<Form.Item
|
|
|
|
|
|
name="environment"
|
|
|
|
|
|
label="环境"
|
|
|
|
|
|
rules={[{ required: true, message: '请选择环境' }]}
|
|
|
|
|
|
>
|
|
|
|
|
|
<Select placeholder="选择环境">
|
|
|
|
|
|
<Option value="live">生产环境</Option>
|
|
|
|
|
|
<Option value="test">测试环境</Option>
|
|
|
|
|
|
</Select>
|
|
|
|
|
|
</Form.Item>
|
|
|
|
|
|
<Form.Item
|
|
|
|
|
|
name="permissions"
|
|
|
|
|
|
label="权限"
|
|
|
|
|
|
rules={[{ required: true, message: '请选择权限' }]}
|
|
|
|
|
|
>
|
|
|
|
|
|
<Checkbox.Group>
|
|
|
|
|
|
<Checkbox value="read">读取</Checkbox>
|
|
|
|
|
|
<Checkbox value="write">写入</Checkbox>
|
|
|
|
|
|
</Checkbox.Group>
|
|
|
|
|
|
</Form.Item>
|
|
|
|
|
|
</Form>
|
|
|
|
|
|
</Modal>
|
2026-03-18 19:12:38 +08:00
|
|
|
|
|
2026-03-19 01:39:34 +08:00
|
|
|
|
{/* 双因素认证模态框 */}
|
|
|
|
|
|
<Modal
|
|
|
|
|
|
title="启用双因素认证"
|
|
|
|
|
|
open={twoFactorModalVisible}
|
|
|
|
|
|
onOk={() => {
|
|
|
|
|
|
setSecuritySettings({ ...securitySettings, twoFactorEnabled: true });
|
|
|
|
|
|
setTwoFactorModalVisible(false);
|
|
|
|
|
|
message.success('双因素认证已启用');
|
|
|
|
|
|
}}
|
|
|
|
|
|
onCancel={() => setTwoFactorModalVisible(false)}
|
|
|
|
|
|
>
|
|
|
|
|
|
<div style={{ textAlign: 'center' }}>
|
|
|
|
|
|
<SafetyOutlined style={{ fontSize: 48, color: '#52c41a' }} />
|
|
|
|
|
|
<p style={{ marginTop: 16 }}>
|
|
|
|
|
|
启用双因素认证后,每次登录时除了密码外,还需要输入手机验证码。
|
|
|
|
|
|
</p>
|
|
|
|
|
|
<p>
|
|
|
|
|
|
这将大大提高您账户的安全性。
|
|
|
|
|
|
</p>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</Modal>
|
2026-03-18 19:12:38 +08:00
|
|
|
|
</div>
|
|
|
|
|
|
);
|
|
|
|
|
|
};
|
|
|
|
|
|
|
2026-03-19 01:39:34 +08:00
|
|
|
|
export default Settings;
|