refactor: 重构页面组件移除冗余Layout组件 feat: 实现WebSocket和事件总线系统 feat: 添加队列和调度系统 docs: 更新架构文档和服务映射 style: 清理重复接口定义使用数据源 chore: 更新依赖项配置 feat: 添加运行时系统和领域引导 ci: 配置ESLint边界检查规则 build: 添加Redis和WebSocket依赖 test: 添加MSW浏览器环境入口 perf: 优化数据获取逻辑使用统一数据源 fix: 修复类型定义和状态管理问题
1192 lines
37 KiB
TypeScript
1192 lines
37 KiB
TypeScript
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';
|
||
|
||
const { Title, Text, Paragraph } = Typography;
|
||
const { TabPane } = Tabs;
|
||
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',
|
||
},
|
||
];
|
||
|
||
const Settings: React.FC = () => {
|
||
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',
|
||
},
|
||
];
|
||
|
||
return (
|
||
<div className="settings-page" style={{ padding: 24 }}>
|
||
<div style={{ marginBottom: 24 }}>
|
||
<Title level={4}>系统设置</Title>
|
||
<Text type="secondary">管理您的账户设置、通知偏好和系统配置</Text>
|
||
</div>
|
||
|
||
<Tabs activeKey={activeTab} onChange={setActiveTab} type="card">
|
||
{/* 个人资料 */}
|
||
<TabPane
|
||
tab={
|
||
<span>
|
||
<UserOutlined />
|
||
个人资料
|
||
</span>
|
||
}
|
||
key="profile"
|
||
>
|
||
<Card>
|
||
<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>
|
||
</Card>
|
||
</TabPane>
|
||
|
||
{/* 通知设置 */}
|
||
<TabPane
|
||
tab={
|
||
<span>
|
||
<BellOutlined />
|
||
通知设置
|
||
</span>
|
||
}
|
||
key="notifications"
|
||
>
|
||
<Card>
|
||
<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>
|
||
</Card>
|
||
</TabPane>
|
||
|
||
{/* 安全设置 */}
|
||
<TabPane
|
||
tab={
|
||
<span>
|
||
<SafetyOutlined />
|
||
安全设置
|
||
</span>
|
||
}
|
||
key="security"
|
||
>
|
||
<Card>
|
||
<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>
|
||
</Card>
|
||
</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"
|
||
>
|
||
<Card>
|
||
<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 }}
|
||
/>
|
||
</Card>
|
||
</TabPane>
|
||
|
||
{/* 配置管理 */}
|
||
<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>
|
||
<Col span={12}>
|
||
<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>
|
||
</div>
|
||
</Card>
|
||
</Col>
|
||
<Col span={12}>
|
||
<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>
|
||
</div>
|
||
</Card>
|
||
</Col>
|
||
</Row>
|
||
</TabPane>
|
||
</Tabs>
|
||
|
||
{/* 修改密码模态框 */}
|
||
<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>
|
||
|
||
{/* 创建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>
|
||
|
||
{/* 双因素认证模态框 */}
|
||
<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>
|
||
</div>
|
||
);
|
||
};
|
||
|
||
export default Settings;
|