import React, { useEffect, useState } from 'react'; import { Button, Card, Form, Input, InputNumber, message, Modal, Select, Space, Switch, Tabs, Typography, Upload, Table, Tag, Popconfirm, } from 'antd'; import { SettingOutlined, SaveOutlined, UploadOutlined, FilePdfOutlined, EyeOutlined, DatabaseOutlined, VideoCameraOutlined, RobotOutlined, BankOutlined, PlusOutlined, EditOutlined, DeleteOutlined, } from '@ant-design/icons'; import { createSystemConfig, getGlobalResourceCapacity, getSystemConfigs, saveGlobalResourceCapacity, updateSystemConfig, uploadLogo, uploadPdf, uploadLoginVideo, listBankAccounts, createBankAccount, updateBankAccount, deleteBankAccount, } from '../api'; import type { ResourceCapacityUnit, SystemConfig, BankAccount } from '../types'; const capacityUnitOptions: { value: ResourceCapacityUnit; label: string }[] = [ { value: 'MB', label: 'MB(1024 × 1024 字节)' }, { value: 'GB', label: 'GB(1024 × 1024 × 1024 字节)' }, { value: 'TB', label: 'TB(1024 × 1024 × 1024 × 1024 字节)' }, ]; const AdminSettings: React.FC = () => { const [configs, setConfigs] = useState([]); const [loading, setLoading] = useState(true); const [saving, setSaving] = useState(false); const [uploading, setUploading] = useState(''); const [form] = Form.useForm(); // 银行账户管理 const [bankAccounts, setBankAccounts] = useState([]); const [loadingAccounts, setLoadingAccounts] = useState(false); const [accountModal, setAccountModal] = useState(false); const [accountEditing, setAccountEditing] = useState(null); const [accountForm] = Form.useForm(); const [accountSaving, setAccountSaving] = useState(false); useEffect(() => { load(); loadBankAccounts(); }, []); const loadBankAccounts = async () => { setLoadingAccounts(true); try { const res = await listBankAccounts(); setBankAccounts(res.items || []); } catch (e: any) { message.error(e?.message || '加载银行账户失败'); } finally { setLoadingAccounts(false); } }; const openAccountModal = (account?: BankAccount) => { if (account) { setAccountEditing(account); accountForm.setFieldsValue({ account_name: account.accountName, bank_name: account.bankName, account_no: account.accountNo, is_active: account.isActive, is_default: account.isDefault, description: account.description, }); } else { setAccountEditing(null); accountForm.resetFields(); accountForm.setFieldsValue({ is_active: true, is_default: false }); } setAccountModal(true); }; const handleAccountSave = async () => { try { const values = await accountForm.validateFields(); setAccountSaving(true); if (accountEditing) { await updateBankAccount(accountEditing.id, values); message.success('更新成功'); } else { await createBankAccount(values); message.success('创建成功'); } setAccountModal(false); await loadBankAccounts(); } catch (e: any) { message.error(e?.message || '保存失败'); } finally { setAccountSaving(false); } }; const handleAccountDelete = async (accountId: string) => { try { await deleteBankAccount(accountId); message.success('删除成功'); await loadBankAccounts(); } catch (e: any) { message.error(e?.message || '删除失败'); } }; const accountColumns = [ { title: '账户名称', dataIndex: 'accountName', width: 150 }, { title: '开户银行', dataIndex: 'bankName', width: 150 }, { title: '银行账号', dataIndex: 'accountNo', width: 180, render: (v: string) => {v}, }, { title: '状态', dataIndex: 'isActive', width: 80, render: (v: boolean) => {v ? '启用' : '禁用'}, }, { title: '默认', dataIndex: 'isDefault', width: 80, render: (v: boolean) => v && 默认, }, { title: '备注', dataIndex: 'description', ellipsis: true, render: (v: string) => v || '-' }, { title: '操作', width: 140, render: (_: any, record: BankAccount) => ( handleAccountDelete(record.id)} okText="确定" cancelText="取消"> ), }, ]; const load = async () => { setLoading(true); try { const [data, capacity] = await Promise.all([ getSystemConfigs(), getGlobalResourceCapacity(), ]); // 确保 llm_media_as_base64 配置存在 if (!data.some(c => c.key === 'llm_media_as_base64')) { data.push({ id: 'cfg_llm_media_as_base64', key: 'llm_media_as_base64', value: 'true', description: '文字模型请求时图片/视频使用 base64 编码' }); } // 确保 single_device_login_enabled 配置存在 if (!data.some(c => c.key === 'single_device_login_enabled')) { data.push({ id: 'cfg_single_device_login_enabled', key: 'single_device_login_enabled', value: 'false', description: '启用单设备登录(同端互斥):同一设备类型只允许一个登录会话' }); } setConfigs(data); const formValues: Record = {}; data.forEach(c => { formValues[c.key] = c.value; }); formValues.resource_capacity_enabled = capacity.enabled; formValues.resource_capacity_limit_value = capacity.limitValue || '1.000'; formValues.resource_capacity_limit_unit = capacity.limitUnit || 'GB'; form.setFieldsValue(formValues); } catch (e: any) { message.error(e?.message || '加载配置失败'); } finally { setLoading(false); } }; const handleSave = async () => { try { const values = await form.validateFields(); setSaving(true); for (const config of configs) { const newVal = values[config.key]; if (newVal !== undefined && String(newVal) !== config.value) { await updateSystemConfig(config.id, String(newVal ?? '')); } } await saveGlobalResourceCapacity({ enabled: !!values.resource_capacity_enabled, limitValue: String(values.resource_capacity_limit_value ?? '1.000'), limitUnit: values.resource_capacity_limit_unit || 'GB', }); message.success('系统配置已保存'); await load(); } catch (e: any) { message.error(e?.message || '保存失败'); } finally { setSaving(false); } }; const handleUpload = async (file: File, configKey: string) => { setUploading(configKey); try { const res = await uploadPdf(file, configKey); setConfigs(prev => prev.map(c => c.key === configKey ? { ...c, value: res.url } : c)); form.setFieldsValue({ [configKey]: res.url }); // Find the config and update to database const config = configs.find(c => c.key === configKey); if (config) { await updateSystemConfig(config.id, res.url); } message.success('PDF上传成功并已保存'); } catch { message.error('上传失败'); } finally { setUploading(''); } return false; }; const handleLogoUpload = async (file: File) => { setUploading('site_logo'); try { const res = await uploadLogo(file); setConfigs(prev => prev.map(c => c.key === 'site_logo' ? { ...c, value: res.url } : c)); form.setFieldsValue({ site_logo: res.url }); const config = configs.find(c => c.key === 'site_logo'); if (config) { await updateSystemConfig(config.id, res.url); } message.success('Logo上传成功并已保存'); } catch { message.error('上传失败'); } finally { setUploading(''); } return false; }; const handleLoginVideoUpload = async (file: File) => { setUploading('login_bg_video'); try { const res = await uploadLoginVideo(file); setConfigs(prev => prev.map(c => c.key === 'login_bg_video' ? { ...c, value: res.url } : c)); form.setFieldsValue({ login_bg_video: res.url }); const config = configs.find(c => c.key === 'login_bg_video'); if (config) { await updateSystemConfig(config.id, res.url); } message.success('登录背景视频上传成功并已保存'); } catch (e: any) { message.error(e?.message || '上传失败'); } finally { setUploading(''); } return false; }; const handleRemoveLoginVideo = async () => { setConfigs(prev => prev.map(c => c.key === 'login_bg_video' ? { ...c, value: '' } : c)); form.setFieldsValue({ login_bg_video: '' }); const config = configs.find(c => c.key === 'login_bg_video'); if (config) { await updateSystemConfig(config.id, ''); } message.success('已移除登录背景视频'); }; const handleToggleSingleDevice = async (checked: boolean) => { try { let config = configs.find(c => c.key === 'single_device_login_enabled'); if (config && config.id && !config.id.startsWith('cfg_')) { await updateSystemConfig(config.id, checked ? 'true' : 'false'); } else { const res = await createSystemConfig('single_device_login_enabled', checked ? 'true' : 'false', '启用单设备登录(同端互斥):同一设备类型只允许一个登录会话'); config = res; } setConfigs(prev => { const exists = prev.some(c => c.key === 'single_device_login_enabled'); if (exists) return prev.map(c => c.key === 'single_device_login_enabled' ? { ...c, value: checked ? 'true' : 'false', id: config!.id } : c); return [...prev, config!]; }); message.success(`已${checked ? '开启' : '关闭'}单设备登录限制`); } catch (e: any) { message.error(e?.message || '操作失败'); } }; const handleToggleBase64 = async (checked: boolean) => { try { let config = configs.find(c => c.key === 'llm_media_as_base64'); if (config && config.id && !config.id.startsWith('cfg_')) { await updateSystemConfig(config.id, checked ? 'true' : 'false'); } else { const res = await createSystemConfig('llm_media_as_base64', checked ? 'true' : 'false', '文字模型请求时图片/视频使用 base64 编码'); config = res; } setConfigs(prev => { const exists = prev.some(c => c.key === 'llm_media_as_base64'); if (exists) return prev.map(c => c.key === 'llm_media_as_base64' ? { ...c, value: checked ? 'true' : 'false', id: config!.id } : c); return [...prev, config!]; }); message.success(`已${checked ? '开启' : '关闭'}文字模型媒体 base64 编码`); } catch (e: any) { message.error(e?.message || '操作失败'); } }; const groupedConfigs: Record = { '站点信息': configs.filter(c => c.key.startsWith('site_') && c.key !== 'site_banner'), '协议配置': configs.filter(c => c.key === 'user_agreement_privacy_url'), 'SEO 设置': configs.filter(c => c.key.startsWith('seo_')), '用户积分配置': configs.filter(c => c.key.startsWith('user_') && c.key.includes('credits')), '收款银行': [], '其他配置': configs.filter(c => c.key === 'operation_manual'), }; const getFieldDescription = (config: SystemConfig): string => { const descMap: Record = { site_name: '平台显示名称,将展示在页面标题和导航栏', site_logo: '平台Logo图片URL,建议尺寸 200x40px', site_copyright: '显示在前台登录页底部的版权信息,例如:© 2024 民众智创 版权所有', user_agreement_privacy_url: '用户登录时需同意的用户协议及隐私政策PDF文件', seo_title: '搜索引擎结果中显示的标题', seo_description: '搜索引擎结果中显示的描述文字,建议150字以内', seo_keywords: '用逗号分隔的关键词列表', user_register_credits: '用户注册时赠送的初始积分,默认100', user_login_credits: '用户每日登录赠送的积分数量', user_login_credits_enabled: '是否启用每日登录赠送积分功能', operation_manual: '操作手册链接,前台用户菜单将展示该入口,点击跳转此链接', }; return descMap[config.key] || config.description || ''; }; const LogoUploadField: React.FC<{ config: SystemConfig }> = ({ config }) => { const hasLogo = config.value && config.value.startsWith('/uploads/'); const baseUrl = import.meta.env.VITE_API_BASE || 'http://localhost:8000'; const logoUrl = hasLogo ? `${baseUrl}${config.value}` : ''; const handleRemove = () => { setConfigs(prev => prev.map(c => c.key === 'site_logo' ? { ...c, value: '' } : c)); form.setFieldsValue({ site_logo: '' }); message.success('Logo已移除'); }; return (
网站Logo {hasLogo && ( )}
{hasLogo ? (
Logo预览 建议尺寸:200x40px,支持 PNG、JPG 格式
) : ( 尚未上传Logo,将使用系统默认Logo )}
); }; const PdfUploadField: React.FC<{ config: SystemConfig }> = ({ config }) => { const label = config.key === 'user_agreement_privacy_url' ? '用户协议及隐私政策' : '协议文件'; const hasFile = config.value && config.value.startsWith('/uploads/'); const baseUrl = import.meta.env.VITE_API_BASE || 'http://localhost:8000'; return (
{label} {hasFile && ( )} handleUpload(file, config.key)} >
{hasFile ? `已上传: ${config.value}` : '尚未上传,前台将不显示对应链接'}
); }; const getFieldComponent = (config: SystemConfig) => { if (config.key === 'site_logo') { return ; } if (config.key === 'seo_description') { return ; } if (config.key === 'seo_keywords') { return ; } if (config.key === 'user_login_credits_enabled') { return (
{config.value === 'true' ? '已启用' : '已禁用'}
); } if (config.key === 'user_register_credits' || config.key === 'user_login_credits') { return ; } return ; }; if (loading) { return ; } const tabItems = [ { key: 'basic', label: '网站基础设置', children: (
{['站点信息', '协议配置', 'SEO 设置'].map(group => (
{group} {group === '协议配置' ? ( groupedConfigs[group]?.map(config => ( )) ) : ( groupedConfigs[group]?.map(config => ( {config.description}} extra={getFieldDescription(config)} > {getFieldComponent(config)} )) )}
))}
), }, { key: 'credits', label: '用户积分配置', children: (
用户积分配置 {groupedConfigs['用户积分配置']?.map(config => ( {config.description}} extra={getFieldDescription(config)} > {getFieldComponent(config)} ))}
), }, { key: 'integration', label: '收款银行', children: (
{/* 银行账户管理 */}
收款银行管理
), }, { key: 'other', label: '其他配置', children: (
其他配置 {groupedConfigs['其他配置']?.map(config => ( {config.description}} extra={getFieldDescription(config)} > {getFieldComponent(config)} ))}
{/* 登录背景视频 */}
登录背景视频
背景视频 {form.getFieldValue('login_bg_video') && ( )}
{(() => { const url = form.getFieldValue('login_bg_video'); if (!url) { return ( 未设置,前台将默认使用 backimage.png ); } const fullUrl = url.startsWith('http') ? url : `${import.meta.env.VITE_API_BASE || 'http://localhost:8000'}${url}`; const isGif = url.toLowerCase().endsWith('.gif'); return isGif ? ( 预览 ) : (
{/* 文字模型媒体编码 */}
文字模型媒体编码
图片/视频 base64 编码
开启后文字模型请求时将媒体转 base64 发送,而非 URL 链接
c.key === 'llm_media_as_base64') || {}).value === 'true'} onChange={handleToggleBase64} checkedChildren="base64" unCheckedChildren="链接" />
{/* 单设备登录限制 */}
登录安全
单设备登录限制
开启后同一账号仅允许在一台同类型设备登录(手机和电脑可同时登录)
c.key === 'single_device_login_enabled') || {}).value === 'true'} onChange={handleToggleSingleDevice} checkedChildren="已启用" unCheckedChildren="已禁用" />
), }, ]; return (
系统设置 管理站点基础信息、用户积分和系统配置
{/* 银行账户新增/编辑弹窗 */} setAccountModal(false)} confirmLoading={accountSaving} okText="保存" cancelText="取消" destroyOnClose >
); }; export default AdminSettings;