rwadurian/frontend/admin-web/src/app/(dashboard)/settings/page.tsx

610 lines
30 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

'use client';
import { useState, useEffect, useCallback } from 'react';
import { PageContainer } from '@/components/layout';
import { cn } from '@/utils/helpers';
import styles from './settings.module.scss';
import { systemConfigService, type DisplaySettings } from '@/services/systemConfigService';
/**
* 后台账号数据接口
*/
interface AdminAccount {
id: string;
name: string;
role: string;
status: 'active' | 'disabled';
lastLogin: string;
}
/**
* 操作日志数据接口
*/
interface OperationLog {
id: string;
time: string;
account: string;
type: string;
description: string;
result: 'pass' | 'reject' | 'pending';
}
/**
* 考核目标数据接口
*/
interface AssessmentTarget {
month: number;
provinceMonthly: number;
provinceCumulative: number;
cityMonthly: number;
cityCumulative: number;
}
// 模拟后台账号数据
const mockAccounts: AdminAccount[] = [
{ id: '1', name: 'admin', role: '超级管理员', status: 'active', lastLogin: '2023-10-26 10:30' },
{ id: '2', name: 'operator01', role: '运营人员', status: 'active', lastLogin: '2023-10-26 09:15' },
];
// 模拟操作日志数据
const mockLogs: OperationLog[] = [
{ id: '1', time: '10-26 10:35', account: 'admin', type: '修改配置', description: '修改结算参数', result: 'pass' },
];
// 模拟考核目标数据
const mockTargets: AssessmentTarget[] = [
{ month: 1, provinceMonthly: 100, provinceCumulative: 100, cityMonthly: 20, cityCumulative: 20 },
{ month: 2, provinceMonthly: 120, provinceCumulative: 220, cityMonthly: 25, cityCumulative: 45 },
];
export default function SettingsPage() {
// 结算参数设置
const [settlementCurrencies, setSettlementCurrencies] = useState<string[]>(['BNB', '积分']);
const [defaultCurrency, setDefaultCurrency] = useState('');
// 龙虎榜设置
const [enableVirtualAccount, setEnableVirtualAccount] = useState(false);
const [virtualAccountCount, setVirtualAccountCount] = useState('');
const [dailyRankEnabled, setDailyRankEnabled] = useState(true);
const [weeklyRankEnabled, setWeeklyRankEnabled] = useState(true);
const [monthlyRankEnabled, setMonthlyRankEnabled] = useState(true);
const [displayCount, setDisplayCount] = useState('');
// 认种限额设置
const [singleAccountLimitEnabled, setSingleAccountLimitEnabled] = useState(false);
const [singleAccountDays, setSingleAccountDays] = useState('');
const [singleAccountTrees, setSingleAccountTrees] = useState('');
const [networkLimitEnabled, setNetworkLimitEnabled] = useState(false);
const [networkDays, setNetworkDays] = useState('');
const [networkTrees, setNetworkTrees] = useState('');
// 考核规则设置
const [localUserThreshold, setLocalUserThreshold] = useState('5');
const [exemptionEnabled, setExemptionEnabled] = useState(false);
// 前端展示设置
const [allowNonAdopterView, setAllowNonAdopterView] = useState(false);
const [heatDisplayMode, setHeatDisplayMode] = useState<'count' | 'level'>('count');
const [displaySettingsLoading, setDisplaySettingsLoading] = useState(false);
const [displaySettingsSaving, setDisplaySettingsSaving] = useState(false);
const [displaySettingsError, setDisplaySettingsError] = useState<string | null>(null);
// 后台账号与安全
const [approvalCount, setApprovalCount] = useState('3');
const [sensitiveOperations, setSensitiveOperations] = useState(['修改结算参数', '删除用户']);
const [logDate, setLogDate] = useState('');
const [logSearch, setLogSearch] = useState('');
// 加载前端展示设置
const loadDisplaySettings = useCallback(async () => {
setDisplaySettingsLoading(true);
setDisplaySettingsError(null);
try {
const settings = await systemConfigService.getDisplaySettings();
setAllowNonAdopterView(settings.allowNonAdopterViewHeat);
setHeatDisplayMode(settings.heatDisplayMode);
} catch (error) {
console.error('Failed to load display settings:', error);
setDisplaySettingsError('加载展示设置失败');
} finally {
setDisplaySettingsLoading(false);
}
}, []);
// 保存前端展示设置
const saveDisplaySettings = useCallback(async () => {
setDisplaySettingsSaving(true);
setDisplaySettingsError(null);
try {
await systemConfigService.updateDisplaySettings({
allowNonAdopterViewHeat: allowNonAdopterView,
heatDisplayMode: heatDisplayMode,
});
alert('展示设置保存成功');
} catch (error) {
console.error('Failed to save display settings:', error);
setDisplaySettingsError('保存展示设置失败');
} finally {
setDisplaySettingsSaving(false);
}
}, [allowNonAdopterView, heatDisplayMode]);
// 组件挂载时加载展示设置
useEffect(() => {
loadDisplaySettings();
}, [loadDisplaySettings]);
// 切换货币选择
const toggleCurrency = (currency: string) => {
if (settlementCurrencies.includes(currency)) {
setSettlementCurrencies(settlementCurrencies.filter(c => c !== currency));
} else {
setSettlementCurrencies([...settlementCurrencies, currency]);
}
};
// Toggle 组件
const Toggle = ({ checked, onChange, size = 'normal' }: { checked: boolean; onChange: (v: boolean) => void; size?: 'normal' | 'small' }) => {
if (size === 'small') {
return (
<div
className={cn(styles.settings__toggleSmall, checked ? styles['settings__toggleSmall--on'] : styles['settings__toggleSmall--off'])}
onClick={() => onChange(!checked)}
>
<div className={cn(styles.settings__toggleSmallHandle, checked ? styles['settings__toggleSmallHandle--on'] : styles['settings__toggleSmallHandle--off'])} />
</div>
);
}
return (
<div
className={cn(styles.settings__toggle, checked ? styles['settings__toggle--on'] : styles['settings__toggle--off'])}
onClick={() => onChange(!checked)}
>
<div className={cn(styles.settings__toggleHandle, checked ? styles['settings__toggleHandle--on'] : styles['settings__toggleHandle--off'])} />
</div>
);
};
return (
<PageContainer title="系统设置">
<div className={styles.settings}>
{/* 页面头部 */}
<header className={styles.settings__header}>
<h1 className={styles.settings__title}></h1>
<p className={styles.settings__desc}></p>
<div className={styles.settings__notice}>
"保存设置"
</div>
</header>
{/* 结算参数设置 */}
<section className={styles.settings__section}>
<div className={styles.settings__sectionHeader}>
<h2 className={styles.settings__sectionTitle}></h2>
<button className={styles.settings__resetBtn}></button>
</div>
<div className={styles.settings__content}>
<div className={styles.settings__fieldGroup}>
<label className={styles.settings__label}></label>
<div className={styles.settings__checkboxGroup}>
{['BNB', 'OG', '积分', 'DST'].map(currency => (
<label key={currency} className={styles.settings__checkboxItem}>
<input
type="checkbox"
className={styles.settings__checkbox}
checked={settlementCurrencies.includes(currency)}
onChange={() => toggleCurrency(currency)}
/>
<span className={styles.settings__checkboxLabel}>{currency}</span>
</label>
))}
</div>
</div>
<div className={styles.settings__selectWrapper}>
<label className={styles.settings__label}></label>
<select
className={styles.settings__select}
value={defaultCurrency}
onChange={(e) => setDefaultCurrency(e.target.value)}
>
<option value=""></option>
{settlementCurrencies.map(currency => (
<option key={currency} value={currency}>{currency}</option>
))}
</select>
<span className={styles.settings__hint}>"一键结算"</span>
</div>
</div>
</section>
{/* 龙虎榜设置 */}
<section className={styles.settings__section}>
<div className={styles.settings__sectionHeader}>
<h2 className={styles.settings__sectionTitle}></h2>
<button className={styles.settings__resetBtn}></button>
</div>
<div className={styles.settings__content}>
<div className={styles.settings__toggleRow}>
<span className={styles.settings__toggleLabel}></span>
<Toggle checked={enableVirtualAccount} onChange={setEnableVirtualAccount} />
</div>
<div className={styles.settings__inputRow}>
<span className={styles.settings__toggleLabel}></span>
<input
type="text"
className={cn(styles.settings__input, styles['settings__input--medium'])}
placeholder="请输入数量"
value={virtualAccountCount}
onChange={(e) => setVirtualAccountCount(e.target.value)}
/>
<span className={styles.settings__inputUnit}></span>
</div>
<div className={styles.settings__fieldGroup}>
<label className={styles.settings__label}></label>
<div className={styles.settings__switchGroup}>
<div className={styles.settings__switchItem}>
<span className={styles.settings__switchLabel}></span>
<Toggle checked={dailyRankEnabled} onChange={setDailyRankEnabled} size="small" />
</div>
<div className={styles.settings__switchItem}>
<span className={styles.settings__switchLabel}></span>
<Toggle checked={weeklyRankEnabled} onChange={setWeeklyRankEnabled} size="small" />
</div>
<div className={styles.settings__switchItem}>
<span className={styles.settings__switchLabel}></span>
<Toggle checked={monthlyRankEnabled} onChange={setMonthlyRankEnabled} size="small" />
</div>
</div>
</div>
<div className={styles.settings__fieldGroup}>
<label className={styles.settings__label}></label>
<input
type="text"
className={cn(styles.settings__input, styles['settings__input--medium'])}
placeholder="例如10、20、31"
value={displayCount}
onChange={(e) => setDisplayCount(e.target.value)}
/>
<span className={styles.settings__hint}> N </span>
</div>
<div className={styles.settings__hint}>
</div>
</div>
</section>
{/* 认种限额设置 */}
<section className={styles.settings__section}>
<div className={styles.settings__sectionHeader}>
<h2 className={styles.settings__sectionTitle}></h2>
<button className={styles.settings__resetBtn}></button>
</div>
<div className={styles.settings__content}>
{/* 单账户认种限额 */}
<div className={styles.settings__quotaCard}>
<div className={styles.settings__quotaHeader}>
<span className={styles.settings__quotaTitle}></span>
<Toggle checked={singleAccountLimitEnabled} onChange={setSingleAccountLimitEnabled} />
</div>
<div className={styles.settings__quotaInputRow}>
<span className={styles.settings__quotaText}></span>
<input
type="text"
className={cn(styles.settings__input, styles['settings__input--small'])}
placeholder="天数"
value={singleAccountDays}
onChange={(e) => setSingleAccountDays(e.target.value)}
/>
<span className={styles.settings__quotaText}></span>
<input
type="text"
className={cn(styles.settings__input, styles['settings__input--small'])}
placeholder="棵数"
value={singleAccountTrees}
onChange={(e) => setSingleAccountTrees(e.target.value)}
/>
<span className={styles.settings__quotaText}></span>
</div>
<span className={styles.settings__hint}>5 1 </span>
</div>
{/* 全网总量限额 */}
<div className={styles.settings__quotaCard}>
<div className={styles.settings__quotaHeader}>
<span className={styles.settings__quotaTitle}></span>
<Toggle checked={networkLimitEnabled} onChange={setNetworkLimitEnabled} />
</div>
<div className={styles.settings__quotaInputRow}>
<span className={styles.settings__quotaText}></span>
<input
type="text"
className={cn(styles.settings__input, styles['settings__input--small'])}
placeholder="天数"
value={networkDays}
onChange={(e) => setNetworkDays(e.target.value)}
/>
<span className={styles.settings__quotaText}></span>
<input
type="text"
className={cn(styles.settings__input, styles['settings__input--small'])}
placeholder="棵数"
value={networkTrees}
onChange={(e) => setNetworkTrees(e.target.value)}
/>
<span className={styles.settings__quotaText}></span>
</div>
<span className={styles.settings__hint}>2 100 </span>
</div>
</div>
</section>
{/* 考核规则设置 */}
<section className={styles.settings__section}>
<div className={styles.settings__sectionHeader}>
<h2 className={styles.settings__sectionTitle}></h2>
<button className={styles.settings__resetBtn}></button>
</div>
<div className={styles.settings__content}>
{/* 本地用户占比阈值 */}
<div className={styles.settings__assessmentRow}>
<label className={styles.settings__assessmentLabel}>/</label>
<div className={styles.settings__assessmentInputRow}>
<input
type="text"
className={cn(styles.settings__input, styles['settings__input--small'])}
value={localUserThreshold}
onChange={(e) => setLocalUserThreshold(e.target.value)}
/>
<span className={styles.settings__assessmentUnit}>%</span>
</div>
<span className={styles.settings__hint}>/</span>
</div>
{/* 豁免设置 */}
<div className={styles.settings__exemptionRow}>
<div className={styles.settings__exemptionInfo}>
<span className={styles.settings__exemptionLabel}>"不考核自有团队本省/市占比"</span>
<span className={styles.settings__exemptionHint}></span>
</div>
<Toggle checked={exemptionEnabled} onChange={setExemptionEnabled} />
</div>
{/* 阶梯性考核目标表 */}
<div className={styles.settings__tableSection}>
<div className={styles.settings__tableHeader}>
<h3 className={styles.settings__tableTitle}> / </h3>
<div className={styles.settings__tableBtns}>
<button className={styles.settings__tableBtn}></button>
<button className={styles.settings__tableBtn}></button>
</div>
</div>
<div className={styles.settings__table}>
<div className={styles.settings__tableHead}>
<div className={cn(styles.settings__tableHeadCell, styles['settings__tableHeadCell--month'])}></div>
<div className={cn(styles.settings__tableHeadCell, styles['settings__tableHeadCell--target'])}></div>
<div className={cn(styles.settings__tableHeadCell, styles['settings__tableHeadCell--target'])}></div>
<div className={cn(styles.settings__tableHeadCell, styles['settings__tableHeadCell--target'])}></div>
<div className={cn(styles.settings__tableHeadCell, styles['settings__tableHeadCell--target'])}></div>
</div>
<div className={styles.settings__tableBody}>
{mockTargets.map((target) => (
<div key={target.month} className={styles.settings__tableRow}>
<div className={cn(styles.settings__tableCell, styles['settings__tableCell--month'])}>{target.month}</div>
<div className={cn(styles.settings__tableCell, styles['settings__tableCell--target'])}>{target.provinceMonthly}</div>
<div className={cn(styles.settings__tableCell, styles['settings__tableCell--target'])}>{target.provinceCumulative}</div>
<div className={cn(styles.settings__tableCell, styles['settings__tableCell--target'])}>{target.cityMonthly}</div>
<div className={cn(styles.settings__tableCell, styles['settings__tableCell--target'])}>{target.cityCumulative}</div>
</div>
))}
</div>
</div>
<span className={styles.settings__hint}></span>
</div>
</div>
</section>
{/* 前端展示设置 */}
<section className={styles.settings__section}>
<div className={styles.settings__sectionHeader}>
<h2 className={styles.settings__sectionTitle}></h2>
<div className={styles.settings__sectionActions}>
<button
className={styles.settings__resetBtn}
onClick={loadDisplaySettings}
disabled={displaySettingsLoading}
>
{displaySettingsLoading ? '加载中...' : '刷新'}
</button>
<button
className={styles.settings__saveBtn}
onClick={saveDisplaySettings}
disabled={displaySettingsSaving || displaySettingsLoading}
>
{displaySettingsSaving ? '保存中...' : '保存展示设置'}
</button>
</div>
</div>
{displaySettingsError && (
<div className={styles.settings__error}>{displaySettingsError}</div>
)}
<div className={styles.settings__content}>
<div className={styles.settings__toggleRow}>
<span className={styles.settings__toggleLabel}></span>
<Toggle checked={allowNonAdopterView} onChange={setAllowNonAdopterView} />
</div>
<div className={styles.settings__fieldGroup}>
<label className={styles.settings__label}></label>
<div className={styles.settings__radioGroup}>
<label className={styles.settings__radioItem}>
<input
type="radio"
className={styles.settings__radio}
name="heatDisplayMode"
checked={heatDisplayMode === 'count'}
onChange={() => setHeatDisplayMode('count')}
/>
<span className={styles.settings__radioLabel}></span>
</label>
<label className={styles.settings__radioItem}>
<input
type="radio"
className={styles.settings__radio}
name="heatDisplayMode"
checked={heatDisplayMode === 'level'}
onChange={() => setHeatDisplayMode('level')}
/>
<span className={styles.settings__radioLabel}> / / </span>
</label>
</div>
</div>
<span className={styles.settings__hint}></span>
</div>
</section>
{/* 后台账号与安全 */}
<section className={styles.settings__section}>
<div className={styles.settings__sectionHeader}>
<h2 className={styles.settings__sectionTitle}></h2>
<button className={styles.settings__resetBtn}></button>
</div>
<div className={styles.settings__securityContent}>
{/* 左侧:账号管理 */}
<div className={styles.settings__securityLeft}>
<div className={styles.settings__accountHeader}>
<h3 className={styles.settings__accountTitle}></h3>
<button className={styles.settings__addBtn}></button>
</div>
<div className={styles.settings__accountTable}>
<div className={styles.settings__accountTableInner}>
<div className={styles.settings__accountHead}>
<div className={cn(styles.settings__accountHeadCell, styles['settings__accountHeadCell--name'])}></div>
<div className={cn(styles.settings__accountHeadCell, styles['settings__accountHeadCell--role'])}></div>
<div className={cn(styles.settings__accountHeadCell, styles['settings__accountHeadCell--status'])}></div>
<div className={cn(styles.settings__accountHeadCell, styles['settings__accountHeadCell--login'])}></div>
<div className={cn(styles.settings__accountHeadCell, styles['settings__accountHeadCell--actions'])}></div>
</div>
<div className={styles.settings__accountBody}>
{mockAccounts.map((account) => (
<div key={account.id} className={styles.settings__accountRow}>
<div className={cn(styles.settings__accountCell, styles['settings__accountCell--name'])}>{account.name}</div>
<div className={cn(styles.settings__accountCell, styles['settings__accountCell--role'])}>{account.role}</div>
<div className={cn(styles.settings__accountCell, styles['settings__accountCell--status'])}>
<span className={cn(styles.settings__statusBadge, account.status === 'disabled' && styles['settings__statusBadge--disabled'])}>
{account.status === 'active' ? '正常' : '禁用'}
</span>
</div>
<div className={cn(styles.settings__accountCell, styles['settings__accountCell--login'])}>{account.lastLogin}</div>
<div className={cn(styles.settings__accountCell, styles['settings__accountCell--actions'])}>
<button className={styles.settings__actionLink}></button>
<button className={cn(styles.settings__actionLink, styles['settings__actionLink--danger'])}></button>
</div>
</div>
))}
</div>
</div>
</div>
<span className={styles.settings__hint}></span>
</div>
{/* 右侧:敏感操作和日志 */}
<div className={styles.settings__securityRight}>
{/* 敏感操作审批 */}
<div className={styles.settings__sensitiveCard}>
<div className={styles.settings__sensitiveRow}>
<span className={styles.settings__sensitiveLabel}></span>
<input
type="text"
className={cn(styles.settings__input, styles['settings__input--small'])}
value={approvalCount}
onChange={(e) => setApprovalCount(e.target.value)}
/>
<span className={styles.settings__inputUnit}></span>
</div>
<div className={styles.settings__sensitiveFieldGroup}>
<label className={styles.settings__label}></label>
<input
type="text"
className={styles.settings__tagInput}
placeholder="选择或输入操作名称"
/>
<div className={styles.settings__tagList}>
{sensitiveOperations.map((op, index) => (
<span key={index} className={styles.settings__tag}>
{op}
<button
className={styles.settings__tagRemove}
onClick={() => setSensitiveOperations(sensitiveOperations.filter((_, i) => i !== index))}
>
×
</button>
</span>
))}
</div>
</div>
</div>
{/* 操作日志 */}
<div className={styles.settings__logSection}>
<h3 className={styles.settings__logTitle}></h3>
<div className={styles.settings__logFilters}>
<input
type="date"
className={styles.settings__dateInput}
value={logDate}
onChange={(e) => setLogDate(e.target.value)}
/>
<input
type="text"
className={styles.settings__searchInput}
placeholder="操作账号/关键字搜索"
value={logSearch}
onChange={(e) => setLogSearch(e.target.value)}
/>
<button className={styles.settings__exportBtn}></button>
</div>
<div className={styles.settings__logTable}>
<div className={styles.settings__logTableInner}>
<div className={styles.settings__logHead}>
<div className={cn(styles.settings__logHeadCell, styles['settings__logHeadCell--time'])}></div>
<div className={cn(styles.settings__logHeadCell, styles['settings__logHeadCell--account'])}></div>
<div className={cn(styles.settings__logHeadCell, styles['settings__logHeadCell--type'])}></div>
<div className={cn(styles.settings__logHeadCell, styles['settings__logHeadCell--desc'])}></div>
<div className={cn(styles.settings__logHeadCell, styles['settings__logHeadCell--result'])}></div>
</div>
<div className={styles.settings__logBody}>
{mockLogs.map((log) => (
<div key={log.id} className={styles.settings__logRow}>
<div className={cn(styles.settings__logCell, styles['settings__logCell--time'])}>{log.time}</div>
<div className={cn(styles.settings__logCell, styles['settings__logCell--account'])}>{log.account}</div>
<div className={cn(styles.settings__logCell, styles['settings__logCell--type'])}>{log.type}</div>
<div className={cn(styles.settings__logCell, styles['settings__logCell--desc'])}>{log.description}</div>
<div className={cn(styles.settings__logCell, styles['settings__logCell--result'])}>
<span className={cn(
styles.settings__logResult,
log.result === 'pass' && styles['settings__logResult--pass'],
log.result === 'reject' && styles['settings__logResult--reject'],
log.result === 'pending' && styles['settings__logResult--pending']
)}>
{log.result === 'pass' ? '通过' : log.result === 'reject' ? '拒绝' : '待审'}
</span>
</div>
</div>
))}
</div>
</div>
</div>
</div>
</div>
</div>
</section>
{/* 页面底部操作按钮 */}
<footer className={styles.settings__footer}>
<button className={styles.settings__cancelBtn}></button>
<button className={styles.settings__saveBtn}></button>
</footer>
</div>
</PageContainer>
);
}