1
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import {
|
||||
Button, Card, Form, Input, message, Space, Typography, Upload,
|
||||
Button, Card, Form, Input, message, Space, Switch, Typography, Upload,
|
||||
} from 'antd';
|
||||
import {
|
||||
SettingOutlined, SaveOutlined, UploadOutlined, FilePdfOutlined, EyeOutlined,
|
||||
@@ -69,6 +69,7 @@ const AdminSettings: React.FC = () => {
|
||||
'站点信息': configs.filter(c => c.key.startsWith('site_')),
|
||||
'协议配置': configs.filter(c => c.key === 'user_agreement_url' || c.key === 'privacy_policy_url'),
|
||||
'SEO 设置': configs.filter(c => c.key.startsWith('seo_')),
|
||||
'用户积分配置': configs.filter(c => c.key.startsWith('user_') && c.key.includes('credits')),
|
||||
};
|
||||
|
||||
const getFieldDescription = (config: SystemConfig): string => {
|
||||
@@ -80,6 +81,9 @@ const AdminSettings: React.FC = () => {
|
||||
seo_title: '搜索引擎结果中显示的标题',
|
||||
seo_description: '搜索引擎结果中显示的描述文字,建议150字以内',
|
||||
seo_keywords: '用逗号分隔的关键词列表',
|
||||
user_register_credits: '用户注册时赠送的初始积分,默认100',
|
||||
user_login_credits: '用户每日登录赠送的积分数量',
|
||||
user_login_credits_enabled: '是否启用每日登录赠送积分功能',
|
||||
};
|
||||
return descMap[config.key] || config.description || '';
|
||||
};
|
||||
@@ -91,6 +95,19 @@ const AdminSettings: React.FC = () => {
|
||||
if (config.key === 'seo_keywords') {
|
||||
return <Input placeholder="关键词1, 关键词2, 关键词3" size="large" />;
|
||||
}
|
||||
if (config.key === 'user_login_credits_enabled') {
|
||||
return (
|
||||
<div style={{ display: 'flex', alignItems: 'center', gap: 12 }}>
|
||||
<Switch defaultChecked={config.value === 'true'} />
|
||||
<Typography.Text type="secondary" style={{ fontSize: 13 }}>
|
||||
{config.value === 'true' ? '已启用' : '已禁用'}
|
||||
</Typography.Text>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
if (config.key === 'user_register_credits' || config.key === 'user_login_credits') {
|
||||
return <Input type="number" min={0} placeholder={config.description} size="large" />;
|
||||
}
|
||||
return <Input placeholder={config.description} size="large" />;
|
||||
};
|
||||
|
||||
|
||||
@@ -3,10 +3,10 @@ import {
|
||||
Button, Card, Checkbox, Form, Input, InputNumber, message, Modal, Popconfirm, Select, Space, Switch, Table, Tabs, Tag, Typography,
|
||||
} from 'antd';
|
||||
import {
|
||||
UserOutlined, WalletOutlined, SearchOutlined, StopOutlined, CheckCircleOutlined, PlusOutlined, MenuOutlined, LockOutlined,
|
||||
UserOutlined, WalletOutlined, SearchOutlined, StopOutlined, CheckCircleOutlined, PlusOutlined, MenuOutlined, LockOutlined, SettingOutlined, SaveOutlined,
|
||||
} from '@ant-design/icons';
|
||||
import { getAdminUsers, adjustCredits, toggleUserStatus, createUser, updateUserMenus, getMenuConfigs, resetUserPassword } from '../api';
|
||||
import type { AdminUser } from '../types';
|
||||
import { getAdminUsers, adjustCredits, toggleUserStatus, createUser, updateUserMenus, getMenuConfigs, resetUserPassword, getSystemConfigs, updateSystemConfig } from '../api';
|
||||
import type { AdminUser, SystemConfig } from '../types';
|
||||
import { formatDate } from '../utils/formatDate';
|
||||
|
||||
const AdminUsers: React.FC = () => {
|
||||
@@ -29,6 +29,10 @@ const AdminUsers: React.FC = () => {
|
||||
const [pageSize, setPageSize] = useState(20);
|
||||
const [total, setTotal] = useState(0);
|
||||
|
||||
const [creditConfigs, setCreditConfigs] = useState<SystemConfig[]>([]);
|
||||
const [configForm] = Form.useForm();
|
||||
const [configSaving, setConfigSaving] = useState(false);
|
||||
|
||||
const load = async () => {
|
||||
setLoading(true);
|
||||
try {
|
||||
@@ -41,6 +45,40 @@ const AdminUsers: React.FC = () => {
|
||||
|
||||
useEffect(() => { load(); }, [page, pageSize, search]);
|
||||
|
||||
useEffect(() => {
|
||||
const loadCreditConfigs = async () => {
|
||||
try {
|
||||
const configs = await getSystemConfigs();
|
||||
const credit = configs.filter(c => c.key.startsWith('user_') && c.key.includes('credits'));
|
||||
setCreditConfigs(credit);
|
||||
const formValues: Record<string, string> = {};
|
||||
credit.forEach(c => { formValues[c.key] = c.value; });
|
||||
configForm.setFieldsValue(formValues);
|
||||
} catch { /* auth error handled by client */ }
|
||||
};
|
||||
loadCreditConfigs();
|
||||
}, []);
|
||||
|
||||
const handleSaveCreditConfigs = async () => {
|
||||
try {
|
||||
const values = await configForm.validateFields();
|
||||
setConfigSaving(true);
|
||||
for (const config of creditConfigs) {
|
||||
const newVal = values[config.key];
|
||||
if (newVal !== undefined && newVal !== config.value) {
|
||||
await updateSystemConfig(config.id, newVal ?? '');
|
||||
}
|
||||
}
|
||||
message.success('积分配置已保存');
|
||||
const configs = await getSystemConfigs();
|
||||
setCreditConfigs(configs.filter(c => c.key.startsWith('user_') && c.key.includes('credits')));
|
||||
setConfigSaving(false);
|
||||
} catch (e: any) {
|
||||
setConfigSaving(false);
|
||||
message.error(e?.message || '保存失败');
|
||||
}
|
||||
};
|
||||
|
||||
const handlePageChange = (p: number, ps: number) => {
|
||||
setPage(p);
|
||||
setPageSize(ps);
|
||||
@@ -224,6 +262,60 @@ const AdminUsers: React.FC = () => {
|
||||
|
||||
return (
|
||||
<div>
|
||||
<Card bordered={false} style={{ borderRadius: 12, border: '1px solid #f0f0f5', marginBottom: 16 }}>
|
||||
<div style={{ display: 'flex', alignItems: 'center', gap: 12, marginBottom: 20 }}>
|
||||
<div style={{
|
||||
width: 44, height: 44, borderRadius: 10,
|
||||
background: 'rgba(99,102,241,0.08)',
|
||||
display: 'flex', alignItems: 'center', justifyContent: 'center',
|
||||
fontSize: 20, color: '#6366f1',
|
||||
}}>
|
||||
<SettingOutlined />
|
||||
</div>
|
||||
<div>
|
||||
<Typography.Title level={4} style={{ margin: 0 }}>积分配置</Typography.Title>
|
||||
<Typography.Text type="secondary">设置用户注册和登录赠送的积分</Typography.Text>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<Form form={configForm} layout="vertical">
|
||||
<div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr 1fr', gap: 20 }}>
|
||||
<Form.Item
|
||||
name="user_register_credits"
|
||||
label={<span style={{ fontWeight: 500 }}>注册赠送积分</span>}
|
||||
extra="用户注册时赠送的初始积分"
|
||||
>
|
||||
<InputNumber min={0} style={{ width: '100%' }} size="large" />
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
name="user_login_credits"
|
||||
label={<span style={{ fontWeight: 500 }}>每日登录赠送积分</span>}
|
||||
extra="用户每日首次登录赠送的积分"
|
||||
>
|
||||
<InputNumber min={0} style={{ width: '100%' }} size="large" />
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
name="user_login_credits_enabled"
|
||||
label={<span style={{ fontWeight: 500 }}>启用每日登录积分</span>}
|
||||
extra="是否开启每日登录赠送积分功能"
|
||||
>
|
||||
<div style={{ display: 'flex', alignItems: 'center', gap: 12 }}>
|
||||
<Switch defaultChecked={creditConfigs.find(c => c.key === 'user_login_credits_enabled')?.value === 'true'} />
|
||||
<Typography.Text type="secondary" style={{ fontSize: 13 }}>
|
||||
{creditConfigs.find(c => c.key === 'user_login_credits_enabled')?.value === 'true' ? '已启用' : '已禁用'}
|
||||
</Typography.Text>
|
||||
</div>
|
||||
</Form.Item>
|
||||
</div>
|
||||
|
||||
<div style={{ display: 'flex', justifyContent: 'flex-end', marginTop: 16 }}>
|
||||
<Button type="primary" icon={<SaveOutlined />} onClick={handleSaveCreditConfigs} loading={configSaving} size="large" style={{ borderRadius: 8 }}>
|
||||
保存配置
|
||||
</Button>
|
||||
</div>
|
||||
</Form>
|
||||
</Card>
|
||||
|
||||
<Card bordered={false} style={{ borderRadius: 12, border: '1px solid #f0f0f5' }}>
|
||||
{/* Search bar */}
|
||||
<div style={{ display: 'flex', justifyContent: 'space-between', marginBottom: 16 }}>
|
||||
|
||||
Reference in New Issue
Block a user