- web/: React + Vite + TypeScript 前端 - backend/: Django + DRF + SimpleJWT 后端 - prototype/: HTML 设计原型 - docs/: PRD 和设计评审文档 - test: 单元测试 + E2E 极限测试
126 lines
4.0 KiB
TypeScript
126 lines
4.0 KiB
TypeScript
import { useEffect, useState, useCallback } 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<SystemSettings>({
|
|
default_daily_seconds_limit: 600,
|
|
default_monthly_seconds_limit: 6000,
|
|
announcement: '',
|
|
announcement_enabled: false,
|
|
});
|
|
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);
|
|
}
|
|
};
|
|
|
|
if (loading) {
|
|
return (
|
|
<div className={styles.page}>
|
|
<h1 className={styles.title}>系统设置</h1>
|
|
<div className={styles.skeletonCard} />
|
|
<div className={styles.skeletonCard} />
|
|
</div>
|
|
);
|
|
}
|
|
|
|
return (
|
|
<div className={styles.page}>
|
|
<h1 className={styles.title}>系统设置</h1>
|
|
|
|
<div className={styles.card}>
|
|
<h2 className={styles.cardTitle}>全局默认配额</h2>
|
|
<p className={styles.cardDesc}>新注册用户将自动获得以下配额</p>
|
|
<div className={styles.formRow}>
|
|
<div className={styles.formGroup}>
|
|
<label>默认每日限额 (秒)</label>
|
|
<input
|
|
type="number"
|
|
value={settings.default_daily_seconds_limit}
|
|
onChange={(e) => setSettings({ ...settings, default_daily_seconds_limit: Number(e.target.value) })}
|
|
/>
|
|
</div>
|
|
<div className={styles.formGroup}>
|
|
<label>默认每月限额 (秒)</label>
|
|
<input
|
|
type="number"
|
|
value={settings.default_monthly_seconds_limit}
|
|
onChange={(e) => setSettings({ ...settings, default_monthly_seconds_limit: Number(e.target.value) })}
|
|
/>
|
|
</div>
|
|
</div>
|
|
<button className={styles.saveBtn} onClick={handleSaveQuota} disabled={saving}>
|
|
{saving ? '保存中...' : '保存配额设置'}
|
|
</button>
|
|
</div>
|
|
|
|
<div className={styles.card}>
|
|
<div className={styles.cardHeader}>
|
|
<div>
|
|
<h2 className={styles.cardTitle}>系统公告</h2>
|
|
<p className={styles.cardDesc}>启用后公告将展示在用户端页面顶部</p>
|
|
</div>
|
|
<label className={styles.switch}>
|
|
<input
|
|
type="checkbox"
|
|
checked={settings.announcement_enabled}
|
|
onChange={(e) => setSettings({ ...settings, announcement_enabled: e.target.checked })}
|
|
/>
|
|
<span className={styles.slider}></span>
|
|
</label>
|
|
</div>
|
|
<div className={styles.formGroup}>
|
|
<label>公告内容</label>
|
|
<textarea
|
|
className={styles.textarea}
|
|
value={settings.announcement}
|
|
onChange={(e) => setSettings({ ...settings, announcement: e.target.value })}
|
|
placeholder="输入公告内容..."
|
|
rows={4}
|
|
/>
|
|
</div>
|
|
<button className={styles.saveBtn} onClick={handleSaveAnnouncement} disabled={saving}>
|
|
{saving ? '保存中...' : '保存公告'}
|
|
</button>
|
|
</div>
|
|
</div>
|
|
);
|
|
}
|