import { useEffect, useState, useCallback, useRef } from 'react'; import { adminApi } from '../lib/api'; import type { SystemSettings } from '../types'; import { showToast } from '../components/Toast'; import styles from './SettingsPage.module.css'; export function SettingsPage() { const [settings, setSettings] = useState({ default_daily_seconds_limit: 600, default_monthly_seconds_limit: 6000, default_daily_generation_limit: 50, default_monthly_generation_limit: 500, base_token_price: 0, base_token_price_video: 0, base_token_price_fast: 0, base_token_price_fast_video: 0, base_token_price_1080p: 0, base_token_price_1080p_video: 0, announcement: '', announcement_enabled: false, max_desktop_sessions: 1, max_mobile_sessions: 0, anomaly_detection_enabled: false, r1_enabled_default: true, r2_enabled_default: true, r2_window_seconds: 3600, r3_enabled_default: true, r3_window_seconds: 3600, r3_max_count: 10, r4_enabled_default: true, r4_window_seconds: 3600, r4_city_count: 5, r5_enabled_default: true, r5_days: 7, r5_country_count: 10, feishu_alert_mobiles: '', sms_alert_mobiles: '', alert_cooldown_seconds: 1800, }); const [testingFeishu, setTestingFeishu] = useState(false); const [testingSms, setTestingSms] = useState(false); const [previewAnnouncement, setPreviewAnnouncement] = useState(false); const announcementRef = useRef(null); const [loading, setLoading] = useState(true); const [saving, setSaving] = useState(false); const fetchSettings = useCallback(async () => { try { const { data } = await adminApi.getSettings(); setSettings(data); } catch { showToast('加载设置失败'); } finally { setLoading(false); } }, []); useEffect(() => { fetchSettings(); }, [fetchSettings]); const handleSaveQuota = async () => { setSaving(true); try { await adminApi.updateSettings(settings); showToast('设置已保存'); } catch { showToast('保存失败'); } finally { setSaving(false); } }; const handleSaveAnnouncement = async () => { setSaving(true); try { await adminApi.updateSettings(settings); showToast('公告已保存'); } catch { showToast('保存失败'); } finally { setSaving(false); } }; const handleTestFeishu = async () => { const mobiles = settings.feishu_alert_mobiles.split(',').map(s => s.trim()).filter(Boolean); if (mobiles.length === 0) { showToast('请先填写飞书告警手机号'); return; } setTestingFeishu(true); try { await adminApi.testFeishu(mobiles[0]); showToast('测试消息已发送'); } catch (err: any) { showToast(err.response?.data?.error || '发送失败'); } finally { setTestingFeishu(false); } }; const handleTestSms = async () => { const mobiles = settings.sms_alert_mobiles.split(',').map(s => s.trim()).filter(Boolean); if (mobiles.length === 0) { showToast('请先填写短信告警手机号'); return; } setTestingSms(true); try { await adminApi.testSms(mobiles[0]); showToast('测试短信已发送'); } catch (err: any) { showToast(err.response?.data?.error || '发送失败'); } finally { setTestingSms(false); } }; if (loading) { return (

系统设置

); } return (

系统设置

全局默认配额

新注册用户将自动获得以下配额

setSettings({ ...settings, default_daily_generation_limit: Number(e.target.value) })} />
setSettings({ ...settings, default_monthly_generation_limit: Number(e.target.value) })} />

Seedance 2.0(480P / 720P)

setSettings({ ...settings, base_token_price: Number(e.target.value) })} />
setSettings({ ...settings, base_token_price_video: Number(e.target.value) })} />

Seedance 2.0(1080P)

setSettings({ ...settings, base_token_price_1080p: Number(e.target.value) })} />
setSettings({ ...settings, base_token_price_1080p_video: Number(e.target.value) })} />

Seedance 2.0 Fast(不支持 1080P)

setSettings({ ...settings, base_token_price_fast: Number(e.target.value) })} />
setSettings({ ...settings, base_token_price_fast_video: Number(e.target.value) })} />

登录设备限制

限制每个用户在不同设备类型上的同时登录数量

setSettings({ ...settings, max_desktop_sessions: Number(e.target.value) })} />
setSettings({ ...settings, max_mobile_sessions: Number(e.target.value) })} />

桌面端至少为 1,移动端设为 0 表示暂不启用移动端登录

系统公告

启用后公告将展示在用户端页面顶部

{[ { label: 'B', tag: 'b', title: '加粗' }, { label: '红字', wrap: ['', ''], title: '红色文字' }, { label: '蓝字', wrap: ['', ''], title: '蓝色文字' }, { label: 'H3', wrap: ['

', '

'], title: '标题' }, { label: '分割线', insert: '
', title: '分割线' }, { label: '列表项', insert: '
  • ', title: '列表项' }, ].map((btn) => ( ))}
  • {previewAnnouncement ? (
    ) : (