a
This commit is contained in:
parent
dc49c2717b
commit
156acc92e0
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
)
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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 () => {
|
||||
|
|
|
|||
|
|
@ -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 () => {
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
},
|
||||
};
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user