diff --git a/frontend/src/components/GlobalConfig.jsx b/frontend/src/components/GlobalConfig.jsx index d825362..2dff426 100644 --- a/frontend/src/components/GlobalConfig.jsx +++ b/frontend/src/components/GlobalConfig.jsx @@ -1,8 +1,8 @@ -import React, { useState, useEffect, useRef } from 'react' +import React, { useState, useEffect } from 'react' import { Link } from 'react-router-dom' import { useSelector } from 'react-redux' import { api } from '../services/api' -import { selectCurrentUser, selectIsAdmin } from '../store/appSlice' +import { selectIsAdmin } from '../store/appSlice' import './GlobalConfig.css' import './ConfigPanel.css' // 复用 ConfigPanel 的样式 @@ -68,12 +68,10 @@ const ConfigItem = ({ label, config, onUpdate, disabled }) => { return val === null || val === undefined ? '' : val } - const [value, setValue] = useState(config.value) const [localValue, setLocalValue] = useState(getInitialDisplayValue(config.value)) const [isEditing, setIsEditing] = useState(false) useEffect(() => { - setValue(config.value) setIsEditing(false) setLocalValue(getInitialDisplayValue(config.value)) }, [config.value]) @@ -164,11 +162,11 @@ const ConfigItem = ({ label, config, onUpdate, disabled }) => { handleChange(newValue) }} onBlur={handleBlur} - onKeyPress={(e) => { - if (e.key === 'Enter') { - handleBlur() - } - }} + onKeyDown={(e) => { + if (e.key === 'Enter') { + handleBlur() + } + }} disabled={disabled} style={{ width: '100%', padding: '8px', border: '1px solid #ddd', borderRadius: '4px' }} /> @@ -183,22 +181,28 @@ const ConfigItem = ({ label, config, onUpdate, disabled }) => { } const GlobalConfig = () => { - const currentUser = useSelector(selectCurrentUser) const isAdmin = useSelector(selectIsAdmin) - const [users, setUsers] = useState([]) - const [accounts, setAccounts] = useState([]) const [loading, setLoading] = useState(true) const [message, setMessage] = useState('') const [busy, setBusy] = useState(false) - const [selectedUserId, setSelectedUserId] = useState(null) - const [showUserForm, setShowUserForm] = useState(false) - const [newUser, setNewUser] = useState({ username: '', password: '', role: 'user', status: 'active' }) - const [editingUserId, setEditingUserId] = useState(null) const [usersDetailed, setUsersDetailed] = useState([]) const [accountsAdmin, setAccountsAdmin] = useState([]) const [linkRole, setLinkRole] = useState('viewer') const [linkAccountMap, setLinkAccountMap] = useState({}) + const [expandedUserIds, setExpandedUserIds] = useState(new Set()) + + const toggleUserExpand = (userId) => { + setExpandedUserIds(prev => { + const next = new Set(prev) + if (next.has(userId)) { + next.delete(userId) + } else { + next.add(userId) + } + return next + }) + } // 系统控制相关 const [systemStatus, setSystemStatus] = useState(null) @@ -209,7 +213,6 @@ const GlobalConfig = () => { // 预设方案相关 const [configs, setConfigs] = useState({}) const [saving, setSaving] = useState(false) - const [configMeta, setConfigMeta] = useState(null) // 配置快照相关 const [showSnapshot, setShowSnapshot] = useState(false) @@ -447,38 +450,6 @@ const GlobalConfig = () => { } } - // 所有函数定义(必须在 useEffect 之前) - const loadUsers = async () => { - try { - const list = await api.getUsers() - setUsers(Array.isArray(list) ? list : []) - } catch (error) { - setMessage('加载用户列表失败: ' + (error.message || '未知错误')) - } finally { - setLoading(false) - } - } - - const loadAccounts = async () => { - try { - const list = await api.getAccounts() - setAccounts(Array.isArray(list) ? list : []) - } catch (error) { - console.error('加载账号列表失败:', error) - } - } - - const loadConfigMeta = async () => { - try { - const m = await api.getConfigMeta() - setConfigMeta(m || null) - } catch (e) { - // 静默失败,可能是权限问题 - console.error('loadConfigMeta failed:', e) - setConfigMeta(null) - } - } - // 已知全局配置项默认值(兜底:后端未返回时前端仍能显示,避免看不到新配置项) const KNOWN_GLOBAL_CONFIG_DEFAULTS = { MAX_RSI_FOR_LONG: { value: 70, type: 'number', category: 'strategy', description: '做多时 RSI 超过此值则不开多(避免超买区追多)。2026-01-31新增。' }, @@ -545,58 +516,22 @@ const GlobalConfig = () => { } } - // 检测当前配置匹配哪个预设方案 - const detectCurrentPreset = () => { - if (!configs || Object.keys(configs).length === 0) return null - - for (const [presetKey, preset] of Object.entries(presets)) { - let match = true - for (const [key, expectedValue] of Object.entries(preset.configs)) { - const currentConfig = configs[key] - if (!currentConfig) { - match = false - break - } - - let currentValue = currentConfig.value - if (key.includes('PERCENT') || key.includes('PCT')) { - if (PCT_LIKE_KEYS.has(key)) { - currentValue = currentValue <= 0.05 ? currentValue * 100 : currentValue - } else { - currentValue = currentValue * 100 - } - } - - if (typeof expectedValue === 'number' && typeof currentValue === 'number') { - if (Math.abs(currentValue - expectedValue) > 0.01) { - match = false - break - } - } else if (currentValue !== expectedValue) { - match = false - break - } - } - - if (match) { - return presetKey - } - } - - return null - } - useEffect(() => { - loadUsers() - loadAccounts() - // 只有管理员才加载配置和系统状态 + const init = async () => { + if (isAdmin) { + // 加载全局配置(独立于账户) + await Promise.allSettled([ + loadConfigs(), + loadSystemStatus(), + loadBackendStatus() + ]) + } + setLoading(false) + } + init() + + // 只有管理员才轮询系统状态 if (isAdmin) { - // 加载全局配置(独立于账户) - loadConfigs().catch(() => {}) - loadConfigMeta().catch(() => {}) // 静默失败 - loadSystemStatus().catch(() => {}) // 静默失败 - loadBackendStatus().catch(() => {}) // 静默失败 - const timer = setInterval(() => { loadSystemStatus().catch(() => {}) loadBackendStatus().catch(() => {}) @@ -784,7 +719,7 @@ const GlobalConfig = () => { displayNew = `${value} (${(value * 100).toFixed(2)}%)` } - const confirmMsg = `确定修改配置项【${key}】吗?\n\n原值: ${config.value}\n新值: ${value}\n\n修改将立即生效。` + const confirmMsg = `确定修改配置项【${key}】吗?\n\n原值: ${displayOld}\n新值: ${displayNew}\n\n修改将立即生效。` if (!window.confirm(confirmMsg)) { // 如果用户取消,理论上 UI 应该回滚。 // 但 ConfigItem 内部状态已经变了(onBlur 触发)。 @@ -973,75 +908,6 @@ const GlobalConfig = () => { } } - const handleCreateUser = async () => { - if (!newUser.username || !newUser.password) { - setMessage('用户名和密码不能为空') - return - } - setBusy(true) - setMessage('') - try { - await api.createUser(newUser) - setMessage('用户创建成功') - setShowUserForm(false) - setNewUser({ username: '', password: '', role: 'user', status: 'active' }) - await loadUsers() - } catch (error) { - setMessage('创建用户失败: ' + (error.message || '未知错误')) - } finally { - setBusy(false) - } - } - - const handleUpdateUserPassword = async (userId) => { - const passwordInput = document.querySelector(`input[data-user-id="${userId}"]`) - const password = passwordInput?.value - if (!password) { - setMessage('密码不能为空') - return - } - setBusy(true) - setMessage('') - try { - await api.updateUserPassword(userId, password) - setMessage('密码更新成功') - setEditingUserId(null) - if (passwordInput) passwordInput.value = '' - await loadUsers() - } catch (error) { - setMessage('更新密码失败: ' + (error.message || '未知错误')) - } finally { - setBusy(false) - } - } - - const handleUpdateUserRole = async (userId, role) => { - setBusy(true) - setMessage('') - try { - await api.updateUserRole(userId, role) - setMessage('角色更新成功') - await loadUsers() - } catch (error) { - setMessage('更新角色失败: ' + (error.message || '未知错误')) - } finally { - setBusy(false) - } - } - - const handleUpdateUserStatus = async (userId, status) => { - setBusy(true) - setMessage('') - try { - await api.updateUserStatus(userId, status) - setMessage('状态更新成功') - await loadUsers() - } catch (error) { - setMessage('更新状态失败: ' + (error.message || '未知错误')) - } finally { - setBusy(false) - } - } if (loading) { return
- 管理用户与其关联的交易账号;用户登录后仅能看到已授权的账号,并可在顶部下拉切换 + 管理用户与交易账号的关联权限。用户登录后只能看到被授权的账号。
| 用户 | -角色 | -状态 | -已关联账号 | -关联新账号 | -
|---|---|---|---|---|
| {u.username} | -{u.role} | -{u.status === 'active' ? '启用' : '禁用'} | -
- {userAccs.length === 0 ? (
- 暂无
- ) : (
-
- {userAccs.map(a => (
-
- )}
-
- #{a.id} {a.name}
- {a.status === 'active' ? '启用' : '禁用'}
-
-
- ))}
- |
-
-
-
-
-
-
- |
-
| ID | -用户名 | -角色 | -状态 | -操作 | -
|---|---|---|---|---|
| {user.id} | -{user.username} | -- - | -- - | -
- {editingUserId === user.id ? (
-
- {
- if (e.key === 'Enter') {
- handleUpdateUserPassword(user.id)
- }
- }}
- />
-
-
-
- ) : (
-
- )}
- |
-
| ID | -名称 | -状态 | -测试网 | -API Key | -
|---|---|---|---|---|
| {account.id} | -{account.name || '未命名'} | -- - {account.status === 'active' ? '启用' : '禁用'} - - | -{account.use_testnet ? '是' : '否'} | -{account.has_api_key ? '已配置' : '未配置'} | -