1
This commit is contained in:
parent
614b28493b
commit
50c933a8b0
|
|
@ -195,6 +195,10 @@ const GlobalConfig = () => {
|
||||||
const [showUserForm, setShowUserForm] = useState(false)
|
const [showUserForm, setShowUserForm] = useState(false)
|
||||||
const [newUser, setNewUser] = useState({ username: '', password: '', role: 'user', status: 'active' })
|
const [newUser, setNewUser] = useState({ username: '', password: '', role: 'user', status: 'active' })
|
||||||
const [editingUserId, setEditingUserId] = useState(null)
|
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)
|
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 (
|
return (
|
||||||
<div className="global-config">
|
<div className="global-config">
|
||||||
<div className="global-config-header">
|
<div className="global-config-header">
|
||||||
|
|
@ -1250,6 +1306,96 @@ const GlobalConfig = () => {
|
||||||
</section>
|
</section>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
{isAdmin && (
|
||||||
|
<section className="global-section">
|
||||||
|
<div className="section-header">
|
||||||
|
<h3>用户管理与账号授权</h3>
|
||||||
|
<p style={{ fontSize: '14px', color: '#666', marginTop: '8px' }}>
|
||||||
|
管理用户与其关联的交易账号;用户登录后仅能看到已授权的账号,并可在顶部下拉切换
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<div className="accounts-table">
|
||||||
|
{usersDetailed.length ? (
|
||||||
|
<table>
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>用户</th>
|
||||||
|
<th>角色</th>
|
||||||
|
<th>状态</th>
|
||||||
|
<th>已关联账号</th>
|
||||||
|
<th>关联新账号</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
{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 (
|
||||||
|
<tr key={u.id}>
|
||||||
|
<td>{u.username}</td>
|
||||||
|
<td><span className={`acct-badge ${u.role === 'admin' ? 'ok' : 'off'}`}>{u.role}</span></td>
|
||||||
|
<td><span className={`acct-badge ${u.status === 'active' ? 'ok' : 'off'}`}>{u.status === 'active' ? '启用' : '禁用'}</span></td>
|
||||||
|
<td>
|
||||||
|
{userAccs.length === 0 ? (
|
||||||
|
<span style={{ color: '#999' }}>暂无</span>
|
||||||
|
) : (
|
||||||
|
<div style={{ display: 'flex', gap: '8px', flexWrap: 'wrap' }}>
|
||||||
|
{userAccs.map(a => (
|
||||||
|
<div key={a.id} style={{ display: 'flex', alignItems: 'center', gap: '6px', border: '1px solid #eee', padding: '4px 8px', borderRadius: '6px' }}>
|
||||||
|
<span>#{a.id} {a.name}</span>
|
||||||
|
<span className={`acct-badge ${a.status === 'active' ? 'ok' : 'off'}`}>{a.status === 'active' ? '启用' : '禁用'}</span>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
className="system-btn danger"
|
||||||
|
onClick={() => handleRevoke(u.id, a.id)}
|
||||||
|
disabled={busy}
|
||||||
|
>
|
||||||
|
取消关联
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<div style={{ display: 'flex', gap: '8px', alignItems: 'center' }}>
|
||||||
|
<select
|
||||||
|
value={linkAccountMap[u.id] || ''}
|
||||||
|
onChange={(e) => setLinkAccountMap({ ...linkAccountMap, [u.id]: e.target.value })}
|
||||||
|
style={{ minWidth: '200px' }}
|
||||||
|
>
|
||||||
|
<option value="">选择账号</option>
|
||||||
|
{available.map(a => (
|
||||||
|
<option key={a.id} value={a.id}>#{a.id} {a.name}</option>
|
||||||
|
))}
|
||||||
|
</select>
|
||||||
|
<select value={linkRole} onChange={(e) => setLinkRole(e.target.value)}>
|
||||||
|
<option value="viewer">viewer</option>
|
||||||
|
<option value="owner">owner</option>
|
||||||
|
</select>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
className="system-btn primary"
|
||||||
|
onClick={() => handleGrant(u.id)}
|
||||||
|
disabled={busy || !linkAccountMap[u.id]}
|
||||||
|
>
|
||||||
|
关联账号
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
)
|
||||||
|
})}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
) : (
|
||||||
|
<div className="accounts-empty">暂无用户数据</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
)}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
{/* 预设方案快速切换(仅管理员 + 全局策略账号) */}
|
{/* 预设方案快速切换(仅管理员 + 全局策略账号) */}
|
||||||
|
|
|
||||||
|
|
@ -784,6 +784,14 @@ export const api = {
|
||||||
}
|
}
|
||||||
return response.json();
|
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) => {
|
getUserAccounts: async (userId) => {
|
||||||
|
|
@ -794,6 +802,29 @@ export const api = {
|
||||||
}
|
}
|
||||||
return response.json();
|
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) => {
|
createUser: async (data) => {
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue
Block a user