mirror of
https://github.com/laoxong/nofx.git
synced 2026-06-04 09:58:22 +08:00
fix(dashboard): preserve trader selection in URL and silence background requests (#1459)
* refactor: streamline trader selection logic and URL handling in App component * refactor: update API request handling across components to use silent mode for improved error management --------- Co-authored-by: Dean <afei.wuhao@gmail.com>
This commit is contained in:
+34
-20
@@ -170,9 +170,7 @@ function App() {
|
||||
) {
|
||||
setCurrentPage('trader')
|
||||
// 如果 URL 中有 trader 参数(slug 格式),更新选中的 trader
|
||||
if (traderParam) {
|
||||
setSelectedTraderSlug(traderParam)
|
||||
}
|
||||
setSelectedTraderSlug(traderParam || undefined)
|
||||
} else if (
|
||||
path === '/competition' ||
|
||||
hash === 'competition' ||
|
||||
@@ -200,7 +198,7 @@ function App() {
|
||||
// 获取trader列表(仅在用户登录时)
|
||||
const { data: traders, error: tradersError } = useSWR<TraderInfo[]>(
|
||||
user && token ? 'traders' : null,
|
||||
api.getTraders,
|
||||
() => api.getTraders(currentPage === 'trader'),
|
||||
{
|
||||
refreshInterval: 10000,
|
||||
shouldRetryOnError: false, // 避免在后端未运行时无限重试
|
||||
@@ -219,19 +217,22 @@ function App() {
|
||||
|
||||
// 当获取到traders后,根据 URL 中的 trader slug 设置选中的 trader,或默认选中第一个
|
||||
useEffect(() => {
|
||||
if (traders && traders.length > 0 && !selectedTraderId) {
|
||||
if (selectedTraderSlug) {
|
||||
// 通过 slug 找到对应的 trader
|
||||
const trader = findTraderBySlug(selectedTraderSlug, traders)
|
||||
if (trader) {
|
||||
setSelectedTraderId(trader.trader_id)
|
||||
} else {
|
||||
// 如果找不到,选中第一个
|
||||
setSelectedTraderId(traders[0].trader_id)
|
||||
}
|
||||
} else {
|
||||
setSelectedTraderId(traders[0].trader_id)
|
||||
if (!traders || traders.length === 0) {
|
||||
return
|
||||
}
|
||||
|
||||
if (selectedTraderSlug) {
|
||||
// 通过 slug 找到对应的 trader
|
||||
const trader = findTraderBySlug(selectedTraderSlug, traders)
|
||||
const nextTraderId = trader?.trader_id || traders[0].trader_id
|
||||
if (nextTraderId !== selectedTraderId) {
|
||||
setSelectedTraderId(nextTraderId)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
if (!selectedTraderId) {
|
||||
setSelectedTraderId(traders[0].trader_id)
|
||||
}
|
||||
}, [traders, selectedTraderId, selectedTraderSlug])
|
||||
|
||||
@@ -240,7 +241,7 @@ function App() {
|
||||
currentPage === 'trader' && selectedTraderId
|
||||
? `status-${selectedTraderId}`
|
||||
: null,
|
||||
() => api.getStatus(selectedTraderId),
|
||||
() => api.getStatus(selectedTraderId, true),
|
||||
{
|
||||
refreshInterval: 15000, // 15秒刷新(配合后端15秒缓存)
|
||||
revalidateOnFocus: false, // 禁用聚焦时重新验证,减少请求
|
||||
@@ -303,7 +304,7 @@ function App() {
|
||||
currentPage === 'trader' && selectedTraderId
|
||||
? `statistics-${selectedTraderId}`
|
||||
: null,
|
||||
() => api.getStatistics(selectedTraderId),
|
||||
() => api.getStatistics(selectedTraderId, true),
|
||||
{
|
||||
refreshInterval: 30000, // 30秒刷新(统计数据更新频率较低)
|
||||
revalidateOnFocus: false,
|
||||
@@ -520,7 +521,18 @@ function App() {
|
||||
<AITradersPage
|
||||
onTraderSelect={(traderId) => {
|
||||
setSelectedTraderId(traderId)
|
||||
window.history.pushState({}, '', '/dashboard')
|
||||
const trader = traders?.find((item) => item.trader_id === traderId)
|
||||
const url = new URL(window.location.href)
|
||||
url.pathname = '/dashboard'
|
||||
if (trader) {
|
||||
const slug = getTraderSlug(trader)
|
||||
url.searchParams.set('trader', slug)
|
||||
setSelectedTraderSlug(slug)
|
||||
} else {
|
||||
url.searchParams.delete('trader')
|
||||
setSelectedTraderSlug(undefined)
|
||||
}
|
||||
window.history.pushState({}, '', url.toString())
|
||||
setRoute('/dashboard')
|
||||
setCurrentPage('trader')
|
||||
}}
|
||||
@@ -550,8 +562,10 @@ function App() {
|
||||
// 更新 URL 参数(使用 slug: name-id前4位)
|
||||
const trader = traders?.find(t => t.trader_id === traderId)
|
||||
if (trader) {
|
||||
const slug = getTraderSlug(trader)
|
||||
setSelectedTraderSlug(slug)
|
||||
const url = new URL(window.location.href)
|
||||
url.searchParams.set('trader', getTraderSlug(trader))
|
||||
url.searchParams.set('trader', slug)
|
||||
window.history.replaceState({}, '', url.toString())
|
||||
}
|
||||
}}
|
||||
|
||||
@@ -153,7 +153,7 @@ export function AdvancedChart({
|
||||
try {
|
||||
const limit = 1500
|
||||
const klineUrl = `/api/klines?symbol=${symbol}&interval=${interval}&limit=${limit}&exchange=${exchange}`
|
||||
const result = await httpClient.get(klineUrl)
|
||||
const result = await httpClient.request(klineUrl, { silent: true })
|
||||
|
||||
if (!result.success || !result.data) {
|
||||
throw new Error('Failed to fetch kline data')
|
||||
@@ -243,7 +243,10 @@ export function AdvancedChart({
|
||||
try {
|
||||
console.log('[AdvancedChart] Fetching orders for trader:', traderID, 'symbol:', symbol)
|
||||
// Fetch filled orders, up to 200 for more history
|
||||
const result = await httpClient.get(`/api/orders?trader_id=${traderID}&symbol=${symbol}&status=FILLED&limit=200`)
|
||||
const result = await httpClient.request(
|
||||
`/api/orders?trader_id=${traderID}&symbol=${symbol}&status=FILLED&limit=200`,
|
||||
{ silent: true }
|
||||
)
|
||||
|
||||
console.log('[AdvancedChart] Orders API response:', result)
|
||||
|
||||
@@ -326,7 +329,10 @@ export function AdvancedChart({
|
||||
const fetchOpenOrders = async (traderID: string, symbol: string): Promise<OpenOrder[]> => {
|
||||
try {
|
||||
console.log('[AdvancedChart] Fetching open orders for trader:', traderID, 'symbol:', symbol)
|
||||
const result = await httpClient.get(`/api/open-orders?trader_id=${traderID}&symbol=${symbol}`)
|
||||
const result = await httpClient.request(
|
||||
`/api/open-orders?trader_id=${traderID}&symbol=${symbol}`,
|
||||
{ silent: true }
|
||||
)
|
||||
|
||||
console.log('[AdvancedChart] Open orders API response:', result)
|
||||
|
||||
|
||||
@@ -114,7 +114,7 @@ export function ChartWithOrders({
|
||||
const limit = 2000 // Fetch recent 2000 candles (more historical data)
|
||||
const klineUrl = `/api/klines?symbol=${symbol}&interval=${interval}&limit=${limit}&exchange=${exchange}`
|
||||
|
||||
const result = await httpClient.get(klineUrl)
|
||||
const result = await httpClient.request(klineUrl, { silent: true })
|
||||
|
||||
if (!result.success || !result.data) {
|
||||
throw new Error('Failed to fetch kline data from our service')
|
||||
@@ -142,7 +142,10 @@ export function ChartWithOrders({
|
||||
const fetchOrders = async (traderID: string, symbol: string): Promise<OrderMarker[]> => {
|
||||
try {
|
||||
// Fetch filled orders for this trader from backend API
|
||||
const result = await httpClient.get(`/api/orders?trader_id=${traderID}&symbol=${symbol}&status=FILLED&limit=50`)
|
||||
const result = await httpClient.request(
|
||||
`/api/orders?trader_id=${traderID}&symbol=${symbol}&status=FILLED&limit=50`,
|
||||
{ silent: true }
|
||||
)
|
||||
|
||||
if (!result.success || !result.data) {
|
||||
console.warn('Failed to fetch orders:', result.message)
|
||||
|
||||
@@ -31,7 +31,7 @@ export function ChartWithOrdersSimple({
|
||||
const klineUrl = `/api/klines?symbol=${symbol}&interval=${interval}&limit=${limit}`
|
||||
|
||||
console.log('[ChartSimple] Fetching klines from our service:', klineUrl)
|
||||
const klineResult = await httpClient.get(klineUrl)
|
||||
const klineResult = await httpClient.request(klineUrl, { silent: true })
|
||||
|
||||
if (!klineResult.success || !klineResult.data) {
|
||||
throw new Error('Failed to fetch klines from our service')
|
||||
@@ -44,7 +44,7 @@ export function ChartWithOrdersSimple({
|
||||
if (traderID) {
|
||||
const tradesUrl = `/api/trades?trader_id=${traderID}&symbol=${symbol}&limit=100`
|
||||
console.log('[ChartSimple] Fetching trades from:', tradesUrl)
|
||||
const tradesResult = await httpClient.get(tradesUrl)
|
||||
const tradesResult = await httpClient.request(tradesUrl, { silent: true })
|
||||
|
||||
if (tradesResult.success && tradesResult.data) {
|
||||
console.log('[ChartSimple] Received trades:', tradesResult.data.length)
|
||||
|
||||
@@ -43,7 +43,7 @@ export function EquityChart({ traderId, embedded = false }: EquityChartProps) {
|
||||
|
||||
const { data: history, error, isLoading } = useSWR<EquityPoint[]>(
|
||||
user && token && traderId ? `equity-history-${traderId}` : null,
|
||||
() => api.getEquityHistory(traderId),
|
||||
() => api.getEquityHistory(traderId, true),
|
||||
{
|
||||
refreshInterval: 30000, // 30秒刷新(历史数据更新频率较低)
|
||||
revalidateOnFocus: false,
|
||||
@@ -53,7 +53,7 @@ export function EquityChart({ traderId, embedded = false }: EquityChartProps) {
|
||||
|
||||
const { data: account } = useSWR(
|
||||
user && token && traderId ? `account-${traderId}` : null,
|
||||
() => api.getAccount(traderId),
|
||||
() => api.getAccount(traderId, true),
|
||||
{
|
||||
refreshInterval: 15000, // 15秒刷新(配合后端缓存)
|
||||
revalidateOnFocus: false,
|
||||
|
||||
@@ -359,7 +359,11 @@ export function PositionHistory({ traderId }: PositionHistoryProps) {
|
||||
setLoading(true)
|
||||
setError(null)
|
||||
// Fetch more data than needed to support filtering, but respect pageSize for initial load
|
||||
const data = await api.getPositionHistory(traderId, Math.max(200, pageSize * 5))
|
||||
const data = await api.getPositionHistory(
|
||||
traderId,
|
||||
Math.max(200, pageSize * 5),
|
||||
true
|
||||
)
|
||||
setPositions(data.positions || [])
|
||||
setStats(data.stats)
|
||||
setSymbolStats(data.symbol_stats || [])
|
||||
|
||||
+14
-9
@@ -10,11 +10,11 @@ import type {
|
||||
import { API_BASE, httpClient } from './helpers'
|
||||
|
||||
export const dataApi = {
|
||||
async getStatus(traderId?: string): Promise<SystemStatus> {
|
||||
async getStatus(traderId?: string, silent?: boolean): Promise<SystemStatus> {
|
||||
const url = traderId
|
||||
? `${API_BASE}/status?trader_id=${traderId}`
|
||||
: `${API_BASE}/status`
|
||||
const result = await httpClient.get<SystemStatus>(url)
|
||||
const result = await httpClient.request<SystemStatus>(url, { silent })
|
||||
if (!result.success) throw new Error('Failed to fetch system status')
|
||||
return result.data!
|
||||
},
|
||||
@@ -65,20 +65,20 @@ export const dataApi = {
|
||||
return result.data!
|
||||
},
|
||||
|
||||
async getStatistics(traderId?: string): Promise<Statistics> {
|
||||
async getStatistics(traderId?: string, silent?: boolean): Promise<Statistics> {
|
||||
const url = traderId
|
||||
? `${API_BASE}/statistics?trader_id=${traderId}`
|
||||
: `${API_BASE}/statistics`
|
||||
const result = await httpClient.get<Statistics>(url)
|
||||
const result = await httpClient.request<Statistics>(url, { silent })
|
||||
if (!result.success) throw new Error('Failed to fetch statistics')
|
||||
return result.data!
|
||||
},
|
||||
|
||||
async getEquityHistory(traderId?: string): Promise<any[]> {
|
||||
async getEquityHistory(traderId?: string, silent?: boolean): Promise<any[]> {
|
||||
const url = traderId
|
||||
? `${API_BASE}/equity-history?trader_id=${traderId}`
|
||||
: `${API_BASE}/equity-history`
|
||||
const result = await httpClient.get<any[]>(url)
|
||||
const result = await httpClient.request<any[]>(url, { silent })
|
||||
if (!result.success) throw new Error('Failed to fetch equity history')
|
||||
return result.data!
|
||||
},
|
||||
@@ -114,9 +114,14 @@ export const dataApi = {
|
||||
return result.data!
|
||||
},
|
||||
|
||||
async getPositionHistory(traderId: string, limit: number = 100): Promise<PositionHistoryResponse> {
|
||||
const result = await httpClient.get<PositionHistoryResponse>(
|
||||
`${API_BASE}/positions/history?trader_id=${traderId}&limit=${limit}`
|
||||
async getPositionHistory(
|
||||
traderId: string,
|
||||
limit: number = 100,
|
||||
silent?: boolean
|
||||
): Promise<PositionHistoryResponse> {
|
||||
const result = await httpClient.request<PositionHistoryResponse>(
|
||||
`${API_BASE}/positions/history?trader_id=${traderId}&limit=${limit}`,
|
||||
{ silent }
|
||||
)
|
||||
if (!result.success) throw new Error('Failed to fetch position history')
|
||||
return result.data!
|
||||
|
||||
@@ -16,8 +16,11 @@ function throwApiError(
|
||||
}
|
||||
|
||||
export const traderApi = {
|
||||
async getTraders(): Promise<TraderInfo[]> {
|
||||
const result = await httpClient.get<TraderInfo[]>(`${API_BASE}/my-traders`)
|
||||
async getTraders(silent?: boolean): Promise<TraderInfo[]> {
|
||||
const result = await httpClient.request<TraderInfo[]>(
|
||||
`${API_BASE}/my-traders`,
|
||||
{ silent }
|
||||
)
|
||||
if (!result.success) throw new Error('Failed to fetch trader list')
|
||||
return Array.isArray(result.data) ? result.data : []
|
||||
},
|
||||
|
||||
Reference in New Issue
Block a user