diff --git a/frontend/src/components/AdminDashboard.jsx b/frontend/src/components/AdminDashboard.jsx
index 9c370e1..a970554 100644
--- a/frontend/src/components/AdminDashboard.jsx
+++ b/frontend/src/components/AdminDashboard.jsx
@@ -2,9 +2,14 @@ import React, { useEffect, useState } from 'react'
import { api } from '../services/api'
import './AdminDashboard.css'
-const UserAccountGroup = ({ user, onServiceAction }) => {
+const UserAccountGroup = ({ user, allAccounts, onServiceAction }) => {
const [expanded, setExpanded] = useState(true)
const [processing, setProcessing] = useState(false)
+
+ // 关联管理状态
+ const [linkAccountId, setLinkAccountId] = useState('')
+ const [linkRole, setLinkRole] = useState('viewer')
+ const [associating, setAssociating] = useState(false)
const handleUserAction = async (action) => {
if (!window.confirm(`确定要${action === 'start' ? '启动' : '停止'}用户 ${user.username} 下所有账号的交易服务吗?`)) return
@@ -17,11 +22,7 @@ const UserAccountGroup = ({ user, onServiceAction }) => {
.catch(e => console.error(`Failed to ${action} account ${acc.id}:`, e))
)
await Promise.all(promises)
- // 触发刷新(通过父组件回调或简单的延迟刷新,这里父组件会定时刷新,或者我们可以在父组件传递 refresh)
- // 这里简单点,父组件会定时刷新,或者我们可以手动触发父组件刷新
- // 由于 onServiceAction 只是单个操作,我们这里最好能通知父组件刷新。
- // 暂时依赖父组件的定时刷新或手动刷新。
- if (onServiceAction) onServiceAction(null, 'refresh') // Hacky way to trigger refresh if supported
+ if (onServiceAction) onServiceAction(null, 'refresh')
} catch (e) {
alert(`操作失败: ${e.message}`)
} finally {
@@ -29,10 +30,39 @@ const UserAccountGroup = ({ user, onServiceAction }) => {
}
}
+ const handleGrant = async () => {
+ if (!linkAccountId) return
+ setAssociating(true)
+ try {
+ await api.grantUserAccount(user.id, linkAccountId, linkRole)
+ setLinkAccountId('')
+ if (onServiceAction) onServiceAction(null, 'refresh')
+ } catch (e) {
+ alert(`关联失败: ${e.message}`)
+ } finally {
+ setAssociating(false)
+ }
+ }
+
+ const handleRevoke = async (accountId) => {
+ if (!window.confirm('确定要取消该账号的关联吗?')) return
+ try {
+ await api.revokeUserAccount(user.id, accountId)
+ if (onServiceAction) onServiceAction(null, 'refresh')
+ } catch (e) {
+ alert(`取消关联失败: ${e.message}`)
+ }
+ }
+
// 计算该用户下所有账号的汇总状态
const allRunning = user.accounts.every(a => a.serviceStatus?.running)
const allStopped = user.accounts.every(a => !a.serviceStatus?.running)
+ // 可供关联的账号(排除已关联的)
+ const availableAccounts = (allAccounts || []).filter(a =>
+ !user.accounts.some(ua => ua.id === a.id)
+ )
+
return (
setExpanded(!expanded)}>
@@ -70,6 +100,7 @@ const UserAccountGroup = ({ user, onServiceAction }) => {
| ID |
名称 |
+ 权限 |
账户状态 |
服务状态 |
总资产 |
@@ -83,6 +114,9 @@ const UserAccountGroup = ({ user, onServiceAction }) => {
| {acc.id} |
{acc.name} |
+
+ {acc.role || 'viewer'}
+ |
{acc.status === 'active' ? '启用' : '禁用'}
@@ -126,6 +160,14 @@ const UserAccountGroup = ({ user, onServiceAction }) => {
>
停止
+
|
@@ -133,6 +175,37 @@ const UserAccountGroup = ({ user, onServiceAction }) => {
)}
+
+ {/* 关联管理区域 */}
+
+ 新增关联:
+
+
+
+
)}
@@ -149,15 +222,17 @@ const AdminDashboard = () => {
// Don't set loading on background refresh (if data exists)
if (!data) setLoading(true)
- const [usersRes, dashboardRes, servicesRes] = await Promise.all([
+ const [usersRes, dashboardRes, servicesRes, accountsRes] = await Promise.all([
api.get('/admin/users/detailed').catch(() => ({ data: [] })),
api.getAdminDashboard(),
- api.get('/system/trading/services').catch(() => ({ data: { services: [] } }))
+ api.get('/system/trading/services').catch(() => ({ data: { services: [] } })),
+ api.get('/accounts').catch(() => ({ data: [] }))
])
const users = usersRes.data || []
const globalStats = dashboardRes // summary, accounts (list of stats)
const services = servicesRes.data.services || []
+ const allAccounts = accountsRes.data || []
// Index stats and services by account ID
const statsMap = {}
@@ -194,7 +269,8 @@ const AdminDashboard = () => {
setData({
summary: globalStats.summary,
- users: enrichedUsers
+ users: enrichedUsers,
+ allAccounts
})
setError(null)
} catch (err) {
@@ -265,6 +341,7 @@ const AdminDashboard = () => {
))}
diff --git a/frontend/src/components/GlobalConfig.jsx b/frontend/src/components/GlobalConfig.jsx
index da63533..fd1d95b 100644
--- a/frontend/src/components/GlobalConfig.jsx
+++ b/frontend/src/components/GlobalConfig.jsx
@@ -186,24 +186,6 @@ const GlobalConfig = () => {
const [loading, setLoading] = useState(true)
const [message, setMessage] = useState('')
const [busy, setBusy] = useState(false)
- 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)
const [backendStatus, setBackendStatus] = useState(null)
@@ -1175,131 +1157,7 @@ const GlobalConfig = () => {
)}
- {/* 用户-账号授权管理(仅管理员) */}
- {isAdmin && (
-
-
-
用户-账号授权管理
-
- 管理用户与交易账号的关联权限。用户登录后只能看到被授权的账号。
-
-
-
-
- {usersDetailed.length === 0 ? (
-
暂无用户或加载中...
- ) : (
- usersDetailed.map(user => (
-
-
toggleUserExpand(user.id)}
- style={{
- display: 'flex',
- justifyContent: 'space-between',
- alignItems: 'center',
- padding: '16px',
- cursor: 'pointer',
- backgroundColor: expandedUserIds.has(user.id) ? '#f8f9fa' : '#fff'
- }}
- >
-
-
- {expandedUserIds.has(user.id) ? '▼' : '▶'}
-
-
- {user.username}
-
- {user.role}
-
-
-
-
-
- 关联账号: {user.accounts ? user.accounts.length : 0}
-
-
ID: {user.id}
-
-
- {expandedUserIds.has(user.id) && (
-
- {/* 已关联账号列表 */}
-
-
已关联账号:
- {!user.accounts || user.accounts.length === 0 ? (
-
未关联任何账号
- ) : (
-
- {user.accounts.map(acc => (
-
- {acc.name || acc.id}
- ({acc.role})
-
-
- ))}
-
- )}
-
-
- {/* 添加关联操作区 */}
-
-
-
-
-
-
-
-
- )}
-
- ))
- )}
-
-
- )}