a
This commit is contained in:
parent
dc49c2717b
commit
156acc92e0
|
|
@ -81,7 +81,7 @@ function App() {
|
||||||
<div className="nav-container">
|
<div className="nav-container">
|
||||||
<div className="nav-left">
|
<div className="nav-left">
|
||||||
<h1 className="nav-title">自动交易系统</h1>
|
<h1 className="nav-title">自动交易系统</h1>
|
||||||
<AccountSelector />
|
<AccountSelector currentUser={me} />
|
||||||
</div>
|
</div>
|
||||||
<div className="nav-links">
|
<div className="nav-links">
|
||||||
<Link to="/">仪表板</Link>
|
<Link to="/">仪表板</Link>
|
||||||
|
|
|
||||||
|
|
@ -1,23 +1,67 @@
|
||||||
import React, { useEffect, useState } from 'react'
|
import React, { useEffect, useState } from 'react'
|
||||||
import { api, getCurrentAccountId, setCurrentAccountId } from '../services/api'
|
import { api, getCurrentAccountId, setCurrentAccountId } from '../services/api'
|
||||||
|
|
||||||
const AccountSelector = ({ onChanged }) => {
|
const AccountSelector = ({ onChanged, currentUser }) => {
|
||||||
const [accounts, setAccounts] = useState([])
|
const [accounts, setAccounts] = useState([])
|
||||||
const [accountId, setAccountId] = useState(getCurrentAccountId())
|
const [accountId, setAccountId] = useState(getCurrentAccountId())
|
||||||
|
const [users, setUsers] = useState([])
|
||||||
|
const [selectedUserId, setSelectedUserId] = useState(null)
|
||||||
|
const isAdmin = (currentUser?.role || '') === 'admin'
|
||||||
|
|
||||||
|
// 管理员:加载用户列表
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const load = () => {
|
if (isAdmin) {
|
||||||
api.getAccounts()
|
api.getUsers()
|
||||||
.then((list) => setAccounts(Array.isArray(list) ? list : []))
|
.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([]))
|
.catch(() => setAccounts([]))
|
||||||
}
|
}
|
||||||
load()
|
}, [isAdmin, selectedUserId])
|
||||||
|
|
||||||
// 配置页创建/更新账号后会触发该事件,用于即时刷新下拉列表
|
// 普通用户:直接加载自己的账号列表
|
||||||
const onUpdated = () => load()
|
useEffect(() => {
|
||||||
window.addEventListener('ats:accounts:updated', onUpdated)
|
if (!isAdmin) {
|
||||||
return () => window.removeEventListener('ats:accounts:updated', onUpdated)
|
const load = () => {
|
||||||
}, [])
|
api.getAccounts()
|
||||||
|
.then((list) => setAccounts(Array.isArray(list) ? list : []))
|
||||||
|
.catch(() => setAccounts([]))
|
||||||
|
}
|
||||||
|
load()
|
||||||
|
|
||||||
|
// 配置页创建/更新账号后会触发该事件,用于即时刷新下拉列表
|
||||||
|
const onUpdated = () => load()
|
||||||
|
window.addEventListener('ats:accounts:updated', onUpdated)
|
||||||
|
return () => window.removeEventListener('ats:accounts:updated', onUpdated)
|
||||||
|
}
|
||||||
|
}, [isAdmin])
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setCurrentAccountId(accountId)
|
setCurrentAccountId(accountId)
|
||||||
|
|
@ -50,22 +94,49 @@ const AccountSelector = ({ onChanged }) => {
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="nav-account">
|
<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>
|
<span className="nav-account-label">账号</span>
|
||||||
<select
|
<select
|
||||||
className="nav-account-select"
|
className="nav-account-select"
|
||||||
value={accountId}
|
value={accountId || ''}
|
||||||
onChange={(e) => {
|
onChange={(e) => {
|
||||||
const v = parseInt(e.target.value, 10)
|
const v = parseInt(e.target.value, 10)
|
||||||
setAccountId(Number.isFinite(v) && v > 0 ? v : 1)
|
setAccountId(Number.isFinite(v) && v > 0 ? v : 1)
|
||||||
}}
|
}}
|
||||||
title="切换账号后:配置/持仓/交易记录/统计会按账号隔离;推荐仍是全局"
|
title={isAdmin ? "切换账号后:配置/持仓/交易记录/统计会按账号隔离;推荐仍是全局" : "切换账号后:配置/持仓/交易记录/统计会按账号隔离;推荐仍是全局"}
|
||||||
|
disabled={isAdmin && !selectedUserId}
|
||||||
>
|
>
|
||||||
{options.map((a) => (
|
{options.length === 0 ? (
|
||||||
<option key={a.id} value={a.id}>
|
<option value="">{isAdmin && !selectedUserId ? '请先选择用户' : '暂无账号'}</option>
|
||||||
#{a.id} {a.name || 'account'}
|
) : (
|
||||||
{String(a?.status || 'active') === 'disabled' ? '(已禁用)' : ''}
|
options.map((a) => (
|
||||||
</option>
|
<option key={a.id} value={a.id}>
|
||||||
))}
|
#{a.id} {a.name || 'account'}
|
||||||
|
{String(a?.status || 'active') === 'disabled' ? '(已禁用)' : ''}
|
||||||
|
</option>
|
||||||
|
))
|
||||||
|
)}
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -900,6 +900,7 @@ const ConfigPanel = ({ currentUser }) => {
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* 我的交易进程(按账号;owner/admin 可启停) */}
|
{/* 我的交易进程(按账号;owner/admin 可启停) */}
|
||||||
|
{currentAccountMeta && String(currentAccountMeta?.status || 'active') === 'active' ? (
|
||||||
<div className="system-section">
|
<div className="system-section">
|
||||||
<div className="system-header">
|
<div className="system-header">
|
||||||
<h3>我的交易进程(当前账号 #{accountId})</h3>
|
<h3>我的交易进程(当前账号 #{accountId})</h3>
|
||||||
|
|
@ -967,8 +968,10 @@ const ConfigPanel = ({ currentUser }) => {
|
||||||
</details>
|
</details>
|
||||||
) : null}
|
) : null}
|
||||||
</div>
|
</div>
|
||||||
|
) : null}
|
||||||
|
|
||||||
{/* 账号密钥(当前账号;owner/admin 可改) */}
|
{/* 账号密钥(当前账号;owner/admin 可改) */}
|
||||||
|
{currentAccountMeta && String(currentAccountMeta?.status || 'active') === 'active' ? (
|
||||||
<div className="system-section">
|
<div className="system-section">
|
||||||
<div className="system-header">
|
<div className="system-header">
|
||||||
<h3>账号密钥(当前账号)</h3>
|
<h3>账号密钥(当前账号)</h3>
|
||||||
|
|
@ -1037,6 +1040,7 @@ const ConfigPanel = ({ currentUser }) => {
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
) : null}
|
||||||
|
|
||||||
{/* 系统控制:清缓存 / 启停 / 重启(supervisor) */}
|
{/* 系统控制:清缓存 / 启停 / 重启(supervisor) */}
|
||||||
{isAdmin ? (
|
{isAdmin ? (
|
||||||
|
|
@ -1495,7 +1499,7 @@ const ConfigPanel = ({ currentUser }) => {
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* 配置可行性检查提示 */}
|
{/* 配置可行性检查提示 */}
|
||||||
{feasibilityCheck && (
|
{currentAccountMeta && String(currentAccountMeta?.status || 'active') === 'active' && feasibilityCheck && (
|
||||||
<div className={`feasibility-check ${feasibilityCheck.feasible ? 'feasible' : 'infeasible'}`}>
|
<div className={`feasibility-check ${feasibilityCheck.feasible ? 'feasible' : 'infeasible'}`}>
|
||||||
<div className="feasibility-header">
|
<div className="feasibility-header">
|
||||||
<h4>
|
<h4>
|
||||||
|
|
|
||||||
|
|
@ -46,7 +46,18 @@ const StatsDashboard = () => {
|
||||||
loadDashboard()
|
loadDashboard()
|
||||||
loadTradingConfig() // 同时刷新配置
|
loadTradingConfig() // 同时刷新配置
|
||||||
}, 30000) // 每30秒刷新
|
}, 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 () => {
|
const loadTradingConfig = async () => {
|
||||||
|
|
|
||||||
|
|
@ -19,13 +19,16 @@ const TradeList = () => {
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
loadData()
|
loadData()
|
||||||
// 每30秒自动刷新一次,确保订单状态及时更新
|
|
||||||
// const interval = setInterval(() => {
|
|
||||||
// loadData()
|
|
||||||
// }, 30000) // 30秒刷新一次
|
|
||||||
|
|
||||||
// return () => clearInterval(interval)
|
// 监听账号切换事件,自动重新加载数据
|
||||||
loadData()
|
const handleAccountChange = () => {
|
||||||
|
loadData()
|
||||||
|
}
|
||||||
|
window.addEventListener('ats:account:changed', handleAccountChange)
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
window.removeEventListener('ats:account:changed', handleAccountChange)
|
||||||
|
}
|
||||||
}, [])
|
}, [])
|
||||||
|
|
||||||
const loadData = async () => {
|
const loadData = async () => {
|
||||||
|
|
|
||||||
|
|
@ -610,4 +610,24 @@ export const api = {
|
||||||
}
|
}
|
||||||
return response.json();
|
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