From 50c933a8b0c05b81c47763ace5a6a4d41f37a0e8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=96=87=E8=96=87=E5=AE=89?= Date: Tue, 3 Feb 2026 15:00:17 +0800 Subject: [PATCH] 1 --- frontend/src/components/GlobalConfig.jsx | 146 +++++++++++++++++++++++ frontend/src/services/api.js | 31 +++++ 2 files changed, 177 insertions(+) diff --git a/frontend/src/components/GlobalConfig.jsx b/frontend/src/components/GlobalConfig.jsx index 21267a3..d825362 100644 --- a/frontend/src/components/GlobalConfig.jsx +++ b/frontend/src/components/GlobalConfig.jsx @@ -195,6 +195,10 @@ const GlobalConfig = () => { 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 [systemStatus, setSystemStatus] = useState(null) @@ -1125,6 +1129,58 @@ const GlobalConfig = () => { }, ] + const loadUsersAndAccounts = async () => { + if (!isAdmin) return + try { + setBusy(true) + const [users, accounts] = await Promise.all([ + api.getUsersDetailed ? api.getUsersDetailed() : api.get('/admin/users/detailed').then(r => r.data), + api.getAccounts(), + ]) + setUsersDetailed(Array.isArray(users) ? users : []) + setAccountsAdmin(Array.isArray(accounts) ? accounts : []) + const initMap = {} + ;(Array.isArray(users) ? users : []).forEach(u => { + initMap[u.id] = '' + }) + setLinkAccountMap(initMap) + } catch (e) { + setMessage(e?.message || '加载失败') + } finally { + setBusy(false) + } + } + useEffect(() => { + if (isAdmin) loadUsersAndAccounts() + }, [isAdmin]) + + const handleGrant = async (userId) => { + const aid = parseInt(String(linkAccountMap[userId] || ''), 10) + if (!Number.isFinite(aid) || aid <= 0) return + try { + setBusy(true) + await api.grantUserAccount(userId, aid, linkRole) + setMessage('已关联账号') + await loadUsersAndAccounts() + } catch (e) { + setMessage(e?.message || '关联失败') + } finally { + setBusy(false) + } + } + const handleRevoke = async (userId, accountId) => { + try { + setBusy(true) + await api.revokeUserAccount(userId, accountId) + setMessage('已取消关联') + await loadUsersAndAccounts() + } catch (e) { + setMessage(e?.message || '取消失败') + } finally { + setBusy(false) + } + } + return (
@@ -1250,6 +1306,96 @@ const GlobalConfig = () => { )} + {isAdmin && ( +
+
+

用户管理与账号授权

+

+ 管理用户与其关联的交易账号;用户登录后仅能看到已授权的账号,并可在顶部下拉切换 +

+
+
+ {usersDetailed.length ? ( + + + + + + + + + + + + {usersDetailed.map(u => { + const userAccs = Array.isArray(u.accounts) ? u.accounts : [] + const linkedIds = new Set(userAccs.map(a => a.id)) + const available = accountsAdmin.filter(a => !linkedIds.has(a.id)) + return ( + + + + + + + + ) + })} + +
用户角色状态已关联账号关联新账号
{u.username}{u.role}{u.status === 'active' ? '启用' : '禁用'} + {userAccs.length === 0 ? ( + 暂无 + ) : ( +
+ {userAccs.map(a => ( +
+ #{a.id} {a.name} + {a.status === 'active' ? '启用' : '禁用'} + +
+ ))} +
+ )} +
+
+ + + +
+
+ ) : ( +
暂无用户数据
+ )} +
+
+ )} + {/* 预设方案快速切换(仅管理员 + 全局策略账号) */} diff --git a/frontend/src/services/api.js b/frontend/src/services/api.js index 4ba29e2..9e9c787 100644 --- a/frontend/src/services/api.js +++ b/frontend/src/services/api.js @@ -784,6 +784,14 @@ export const api = { } return response.json(); }, + getUsersDetailed: async () => { + const response = await fetch(buildUrl('/api/admin/users/detailed'), { headers: withAuthHeaders() }); + if (!response.ok) { + const error = await response.json().catch(() => ({ detail: '获取用户详情失败' })); + throw new Error(error.detail || '获取用户详情失败'); + } + return response.json(); + }, // 管理员:获取用户关联的账号列表 getUserAccounts: async (userId) => { @@ -794,6 +802,29 @@ export const api = { } return response.json(); }, + grantUserAccount: async (userId, accountId, role = 'viewer') => { + const response = await fetch(buildUrl(`/api/admin/users/${userId}/accounts/${accountId}`), { + method: 'PUT', + headers: withAuthHeaders({ 'Content-Type': 'application/json' }), + body: JSON.stringify({ role }), + }); + if (!response.ok) { + const error = await response.json().catch(() => ({ detail: '授权失败' })); + throw new Error(error.detail || '授权失败'); + } + return response.json(); + }, + revokeUserAccount: async (userId, accountId) => { + const response = await fetch(buildUrl(`/api/admin/users/${userId}/accounts/${accountId}`), { + method: 'DELETE', + headers: withAuthHeaders(), + }); + if (!response.ok) { + const error = await response.json().catch(() => ({ detail: '取消授权失败' })); + throw new Error(error.detail || '取消授权失败'); + } + return response.json(); + }, // 管理员:创建用户 createUser: async (data) => {