This commit is contained in:
薇薇安 2026-01-22 09:06:10 +08:00
parent dc49c2717b
commit 156acc92e0
6 changed files with 136 additions and 27 deletions

View File

@ -81,7 +81,7 @@ function App() {
<div className="nav-container">
<div className="nav-left">
<h1 className="nav-title">自动交易系统</h1>
<AccountSelector />
<AccountSelector currentUser={me} />
</div>
<div className="nav-links">
<Link to="/">仪表板</Link>

View File

@ -1,11 +1,54 @@
import React, { useEffect, useState } from 'react'
import { api, getCurrentAccountId, setCurrentAccountId } from '../services/api'
const AccountSelector = ({ onChanged }) => {
const AccountSelector = ({ onChanged, currentUser }) => {
const [accounts, setAccounts] = useState([])
const [accountId, setAccountId] = useState(getCurrentAccountId())
const [users, setUsers] = useState([])
const [selectedUserId, setSelectedUserId] = useState(null)
const isAdmin = (currentUser?.role || '') === 'admin'
//
useEffect(() => {
if (isAdmin) {
api.getUsers()
.then((list) => {
setUsers(Array.isArray(list) ? list : [])
//
const currentUserId = parseInt(String(currentUser?.id || ''), 10)
if (currentUserId && list.some((u) => parseInt(String(u?.id || ''), 10) === currentUserId)) {
setSelectedUserId(currentUserId)
} else if (list.length > 0) {
setSelectedUserId(parseInt(String(list[0]?.id || ''), 10))
}
})
.catch(() => setUsers([]))
}
}, [isAdmin, currentUser?.id])
//
useEffect(() => {
if (isAdmin && selectedUserId) {
api.getUserAccounts(selectedUserId)
.then((list) => {
const accountsList = Array.isArray(list) ? list : []
setAccounts(accountsList)
// active
const firstActive = accountsList.find((a) => String(a?.status || 'active') === 'active') || accountsList[0]
if (firstActive) {
const nextAccountId = parseInt(String(firstActive.id || ''), 10)
if (Number.isFinite(nextAccountId) && nextAccountId > 0) {
setAccountId(nextAccountId)
}
}
})
.catch(() => setAccounts([]))
}
}, [isAdmin, selectedUserId])
//
useEffect(() => {
if (!isAdmin) {
const load = () => {
api.getAccounts()
.then((list) => setAccounts(Array.isArray(list) ? list : []))
@ -17,7 +60,8 @@ const AccountSelector = ({ onChanged }) => {
const onUpdated = () => load()
window.addEventListener('ats:accounts:updated', onUpdated)
return () => window.removeEventListener('ats:accounts:updated', onUpdated)
}, [])
}
}, [isAdmin])
useEffect(() => {
setCurrentAccountId(accountId)
@ -50,22 +94,49 @@ const AccountSelector = ({ onChanged }) => {
return (
<div className="nav-account">
{isAdmin ? (
<>
<span className="nav-account-label">用户</span>
<select
className="nav-account-select"
value={selectedUserId || ''}
onChange={(e) => {
const v = parseInt(e.target.value, 10)
setSelectedUserId(Number.isFinite(v) && v > 0 ? v : null)
//
setAccountId(null)
}}
title="管理员:先选择用户,再查看该用户下的账号"
>
{users.map((u) => (
<option key={u.id} value={u.id}>
{u.username || 'user'} {u.role === 'admin' ? '(管理员)' : ''}
</option>
))}
</select>
</>
) : null}
<span className="nav-account-label">账号</span>
<select
className="nav-account-select"
value={accountId}
value={accountId || ''}
onChange={(e) => {
const v = parseInt(e.target.value, 10)
setAccountId(Number.isFinite(v) && v > 0 ? v : 1)
}}
title="切换账号后:配置/持仓/交易记录/统计会按账号隔离;推荐仍是全局"
title={isAdmin ? "切换账号后:配置/持仓/交易记录/统计会按账号隔离;推荐仍是全局" : "切换账号后:配置/持仓/交易记录/统计会按账号隔离;推荐仍是全局"}
disabled={isAdmin && !selectedUserId}
>
{options.map((a) => (
{options.length === 0 ? (
<option value="">{isAdmin && !selectedUserId ? '请先选择用户' : '暂无账号'}</option>
) : (
options.map((a) => (
<option key={a.id} value={a.id}>
#{a.id} {a.name || 'account'}
{String(a?.status || 'active') === 'disabled' ? '(已禁用)' : ''}
</option>
))}
))
)}
</select>
</div>
)

View File

@ -900,6 +900,7 @@ const ConfigPanel = ({ currentUser }) => {
</div>
{/* 我的交易进程按账号owner/admin 可启停) */}
{currentAccountMeta && String(currentAccountMeta?.status || 'active') === 'active' ? (
<div className="system-section">
<div className="system-header">
<h3>我的交易进程当前账号 #{accountId}</h3>
@ -967,8 +968,10 @@ const ConfigPanel = ({ currentUser }) => {
</details>
) : null}
</div>
) : null}
{/* 账号密钥当前账号owner/admin 可改) */}
{currentAccountMeta && String(currentAccountMeta?.status || 'active') === 'active' ? (
<div className="system-section">
<div className="system-header">
<h3>账号密钥当前账号</h3>
@ -1037,6 +1040,7 @@ const ConfigPanel = ({ currentUser }) => {
</div>
</div>
</div>
) : null}
{/* 系统控制:清缓存 / 启停 / 重启supervisor */}
{isAdmin ? (
@ -1495,7 +1499,7 @@ const ConfigPanel = ({ currentUser }) => {
</div>
{/* 配置可行性检查提示 */}
{feasibilityCheck && (
{currentAccountMeta && String(currentAccountMeta?.status || 'active') === 'active' && feasibilityCheck && (
<div className={`feasibility-check ${feasibilityCheck.feasible ? 'feasible' : 'infeasible'}`}>
<div className="feasibility-header">
<h4>

View File

@ -46,7 +46,18 @@ const StatsDashboard = () => {
loadDashboard()
loadTradingConfig() //
}, 30000) // 30
return () => clearInterval(interval)
//
const handleAccountChange = () => {
loadDashboard()
loadTradingConfig()
}
window.addEventListener('ats:account:changed', handleAccountChange)
return () => {
clearInterval(interval)
window.removeEventListener('ats:account:changed', handleAccountChange)
}
}, [])
const loadTradingConfig = async () => {

View File

@ -19,13 +19,16 @@ const TradeList = () => {
useEffect(() => {
loadData()
// 30
// const interval = setInterval(() => {
// loadData()
// }, 30000) // 30
// return () => clearInterval(interval)
//
const handleAccountChange = () => {
loadData()
}
window.addEventListener('ats:account:changed', handleAccountChange)
return () => {
window.removeEventListener('ats:account:changed', handleAccountChange)
}
}, [])
const loadData = async () => {

View File

@ -610,4 +610,24 @@ export const api = {
}
return response.json();
},
// 管理员:获取用户列表
getUsers: async () => {
const response = await fetch(buildUrl('/api/admin/users'), { headers: withAuthHeaders() });
if (!response.ok) {
const error = await response.json().catch(() => ({ detail: '获取用户列表失败' }));
throw new Error(error.detail || '获取用户列表失败');
}
return response.json();
},
// 管理员:获取用户关联的账号列表
getUserAccounts: async (userId) => {
const response = await fetch(buildUrl(`/api/admin/users/${userId}/accounts`), { headers: withAuthHeaders() });
if (!response.ok) {
const error = await response.json().catch(() => ({ detail: '获取用户账号列表失败' }));
throw new Error(error.detail || '获取用户账号列表失败');
}
return response.json();
},
};