Files
MuMuAINovel/frontend/src/pages/SystemSettings.tsx
T

290 lines
12 KiB
TypeScript
Raw Normal View History

2026-03-20 11:06:25 +08:00
import { useEffect, useState } from 'react';
import { Alert, Button, Card, Col, Form, Input, InputNumber, Row, Select, Space, Spin, Switch, Tabs, Typography, message, theme } from 'antd';
import { CheckCircleOutlined, MailOutlined, ReloadOutlined, SaveOutlined, SendOutlined, SettingOutlined } from '@ant-design/icons';
import { authApi, settingsApi } from '../services/api';
import type { SystemSMTPSettings, SystemSMTPSettingsUpdate, User } from '../types';
const { Title, Text, Paragraph } = Typography;
const { Option } = Select;
const qqDefaults: Pick<SystemSMTPSettings, 'smtp_provider' | 'smtp_host' | 'smtp_port' | 'smtp_use_ssl' | 'smtp_use_tls'> = {
smtp_provider: 'qq',
smtp_host: 'smtp.qq.com',
smtp_port: 465,
smtp_use_ssl: true,
smtp_use_tls: false,
};
export default function SystemSettingsPage() {
const { token } = theme.useToken();
const [form] = Form.useForm<SystemSMTPSettingsUpdate>();
const [currentUser, setCurrentUser] = useState<User | null>(null);
const [initialLoading, setInitialLoading] = useState(true);
const [saving, setSaving] = useState(false);
const [testing, setTesting] = useState(false);
const [testTargetEmail, setTestTargetEmail] = useState('');
const pageBackground = `linear-gradient(180deg, ${token.colorBgLayout} 0%, ${token.colorFillSecondary} 100%)`;
const headerBackground = `linear-gradient(135deg, ${token.colorPrimary} 0%, ${token.colorPrimaryHover} 100%)`;
const footerSafeOffset = 88;
const loadData = async () => {
setInitialLoading(true);
try {
const [user, smtpSettings] = await Promise.all([
authApi.getCurrentUser(),
settingsApi.getSystemSMTPSettings(),
]);
setCurrentUser(user);
form.setFieldsValue(smtpSettings);
} catch (error) {
console.error('加载系统设置失败:', error);
message.error('加载系统设置失败');
} finally {
setInitialLoading(false);
}
};
useEffect(() => {
loadData();
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
const handleProviderChange = (value: string) => {
if (value === 'qq') {
form.setFieldsValue(qqDefaults);
}
};
const handleSave = async (values: SystemSMTPSettingsUpdate) => {
setSaving(true);
try {
const payload = values.smtp_provider === 'qq'
? {
...values,
...qqDefaults,
smtp_username: values.smtp_username,
smtp_password: values.smtp_password,
smtp_from_email: values.smtp_from_email,
smtp_from_name: values.smtp_from_name,
email_auth_enabled: values.email_auth_enabled,
email_register_enabled: values.email_register_enabled,
verification_code_ttl_minutes: values.verification_code_ttl_minutes,
verification_resend_interval_seconds: values.verification_resend_interval_seconds,
}
: values;
const result = await settingsApi.updateSystemSMTPSettings(payload);
form.setFieldsValue(result);
message.success('系统 SMTP 设置已保存');
} catch (error) {
console.error('保存系统设置失败:', error);
message.error('保存系统设置失败');
} finally {
setSaving(false);
}
};
const handleTest = async () => {
const toEmail = testTargetEmail.trim();
if (!toEmail) {
message.warning('请先填写测试目标邮箱');
return;
}
setTesting(true);
try {
const result = await settingsApi.testSystemSMTPSettings({ to_email: toEmail });
if (result.success) {
message.success(result.message);
} else {
message.error(result.message || 'SMTP 测试失败');
}
} catch (error) {
console.error('测试 SMTP 配置失败:', error);
message.error('测试 SMTP 配置失败');
} finally {
setTesting(false);
}
};
if (initialLoading) {
return (
<div style={{ minHeight: '100%', display: 'flex', alignItems: 'center', justifyContent: 'center', background: token.colorBgLayout }}>
<Spin size="large" />
</div>
);
}
if (!currentUser?.is_admin) {
return (
<div style={{ padding: 24 }}>
<Alert type="error" showIcon message="无权限访问" description="只有管理员可以访问系统设置。" />
</div>
);
}
return (
<div
style={{
minHeight: `calc(100vh - ${footerSafeOffset}px)`,
boxSizing: 'border-box',
background: pageBackground,
padding: 24,
paddingBottom: footerSafeOffset,
}}
>
<Card
bordered={false}
style={{
marginBottom: 24,
borderRadius: 20,
overflow: 'hidden',
boxShadow: `0 12px 32px ${token.colorFillSecondary}`,
}}
bodyStyle={{ padding: 0 }}
>
<div style={{ background: headerBackground, padding: '28px 32px', color: '#fff' }}>
<Space direction="vertical" size={6}>
<Space>
<SettingOutlined />
<Title level={3} style={{ color: '#fff', margin: 0 }}></Title>
</Space>
<Paragraph style={{ color: 'rgba(255,255,255,0.88)', margin: 0 }}>
SMTP
</Paragraph>
</Space>
</div>
</Card>
<Tabs
defaultActiveKey="smtp"
items={[
{
key: 'smtp',
label: (
<Space>
<MailOutlined />
SMTP
</Space>
),
children: (
<Form form={form} layout="vertical" onFinish={handleSave}>
<Row gutter={24}>
<Col xs={24} xl={16}>
<Card title="邮件服务配置" bordered={false} style={{ borderRadius: 16 }}>
<Alert
type="info"
showIcon
style={{ marginBottom: 20 }}
message="QQ 邮箱配置说明"
description="如果选择 QQ 邮箱,请使用完整 QQ 邮箱地址作为用户名,密码处填写 SMTP 授权码,而不是 QQ 登录密码。默认推荐 smtp.qq.com + SSL 465。"
/>
<Row gutter={16}>
<Col xs={24} md={12}>
<Form.Item name="smtp_provider" label="邮件服务商" rules={[{ required: true, message: '请选择邮件服务商' }]}>
<Select onChange={handleProviderChange}>
<Option value="qq">QQ </Option>
<Option value="custom"> SMTP</Option>
</Select>
</Form.Item>
</Col>
<Col xs={24} md={12}>
<Form.Item name="smtp_host" label="SMTP 主机" rules={[{ required: true, message: '请输入 SMTP 主机' }]}>
<Input placeholder="例如:smtp.qq.com" />
</Form.Item>
</Col>
<Col xs={24} md={12}>
<Form.Item name="smtp_port" label="SMTP 端口" rules={[{ required: true, message: '请输入 SMTP 端口' }]}>
<InputNumber style={{ width: '100%' }} min={1} max={65535} />
</Form.Item>
</Col>
<Col xs={24} md={12}>
<Form.Item name="smtp_username" label="SMTP 用户名" rules={[{ required: true, message: '请输入 SMTP 用户名' }]}>
<Input placeholder="完整邮箱地址" />
</Form.Item>
</Col>
<Col xs={24} md={12}>
<Form.Item name="smtp_password" label="SMTP 密码 / 授权码" rules={[{ required: true, message: '请输入 SMTP 授权码' }]}>
<Input.Password placeholder="QQ 邮箱请填写授权码" />
</Form.Item>
</Col>
<Col xs={24} md={12}>
<Form.Item name="smtp_from_email" label="发件人邮箱">
<Input placeholder="默认可与用户名一致" />
</Form.Item>
</Col>
<Col xs={24} md={12}>
<Form.Item name="smtp_from_name" label="发件人名称" rules={[{ required: true, message: '请输入发件人名称' }]}>
<Input placeholder="墨木灵思" />
2026-03-20 11:06:25 +08:00
</Form.Item>
</Col>
</Row>
<Row gutter={16}>
<Col xs={24} md={12}>
<Form.Item name="smtp_use_ssl" label="启用 SSL" valuePropName="checked">
<Switch />
</Form.Item>
</Col>
<Col xs={24} md={12}>
<Form.Item name="smtp_use_tls" label="启用 TLS" valuePropName="checked">
<Switch />
</Form.Item>
</Col>
</Row>
</Card>
</Col>
<Col xs={24} xl={8}>
<Card title="注册与验证码策略" bordered={false} style={{ borderRadius: 16, marginBottom: 24 }}>
<Form.Item name="email_auth_enabled" label="启用邮箱认证" valuePropName="checked">
<Switch />
</Form.Item>
<Form.Item name="email_register_enabled" label="启用邮箱注册" valuePropName="checked">
<Switch />
</Form.Item>
<Form.Item name="verification_code_ttl_minutes" label="验证码有效期(分钟)" rules={[{ required: true, message: '请输入验证码有效期' }]}>
<InputNumber style={{ width: '100%' }} min={1} max={120} />
</Form.Item>
<Form.Item name="verification_resend_interval_seconds" label="验证码重发间隔(秒)" rules={[{ required: true, message: '请输入验证码重发间隔' }]}>
<InputNumber style={{ width: '100%' }} min={10} max={3600} />
</Form.Item>
</Card>
<Card title="操作" bordered={false} style={{ borderRadius: 16 }}>
<Space direction="vertical" style={{ width: '100%' }} size={12}>
<Input
value={testTargetEmail}
onChange={(e) => setTestTargetEmail(e.target.value)}
placeholder="请输入测试目标邮箱,如 123456@qq.com"
/>
<Button icon={<ReloadOutlined />} onClick={loadData} block>
</Button>
<Button icon={<SendOutlined />} loading={testing} onClick={handleTest} block>
</Button>
<Button type="primary" htmlType="submit" icon={<SaveOutlined />} loading={saving} block onClick={() => form.submit()}>
</Button>
<Alert
type="success"
showIcon
icon={<CheckCircleOutlined />}
message="建议使用 QQ 默认配置"
description={<Text type="secondary"> SMTP SMTP </Text>}
/>
</Space>
</Card>
</Col>
</Row>
</Form>
),
},
]}
/>
</div>
);
}