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:
deanokk
2026-04-04 17:16:47 +08:00
committed by GitHub
parent 8a0f3f5a13
commit 80272c0d5a
8 changed files with 76 additions and 41 deletions

View File

@@ -170,9 +170,7 @@ function App() {
) { ) {
setCurrentPage('trader') setCurrentPage('trader')
// 如果 URL 中有 trader 参数slug 格式),更新选中的 trader // 如果 URL 中有 trader 参数slug 格式),更新选中的 trader
if (traderParam) { setSelectedTraderSlug(traderParam || undefined)
setSelectedTraderSlug(traderParam)
}
} else if ( } else if (
path === '/competition' || path === '/competition' ||
hash === 'competition' || hash === 'competition' ||
@@ -200,7 +198,7 @@ function App() {
// 获取trader列表仅在用户登录时 // 获取trader列表仅在用户登录时
const { data: traders, error: tradersError } = useSWR<TraderInfo[]>( const { data: traders, error: tradersError } = useSWR<TraderInfo[]>(
user && token ? 'traders' : null, user && token ? 'traders' : null,
api.getTraders, () => api.getTraders(currentPage === 'trader'),
{ {
refreshInterval: 10000, refreshInterval: 10000,
shouldRetryOnError: false, // 避免在后端未运行时无限重试 shouldRetryOnError: false, // 避免在后端未运行时无限重试
@@ -219,19 +217,22 @@ function App() {
// 当获取到traders后根据 URL 中的 trader slug 设置选中的 trader或默认选中第一个 // 当获取到traders后根据 URL 中的 trader slug 设置选中的 trader或默认选中第一个
useEffect(() => { useEffect(() => {
if (traders && traders.length > 0 && !selectedTraderId) { if (!traders || traders.length === 0) {
if (selectedTraderSlug) { return
// 通过 slug 找到对应的 trader }
const trader = findTraderBySlug(selectedTraderSlug, traders)
if (trader) { if (selectedTraderSlug) {
setSelectedTraderId(trader.trader_id) // 通过 slug 找到对应的 trader
} else { const trader = findTraderBySlug(selectedTraderSlug, traders)
// 如果找不到,选中第一个 const nextTraderId = trader?.trader_id || traders[0].trader_id
setSelectedTraderId(traders[0].trader_id) if (nextTraderId !== selectedTraderId) {
} setSelectedTraderId(nextTraderId)
} else {
setSelectedTraderId(traders[0].trader_id)
} }
return
}
if (!selectedTraderId) {
setSelectedTraderId(traders[0].trader_id)
} }
}, [traders, selectedTraderId, selectedTraderSlug]) }, [traders, selectedTraderId, selectedTraderSlug])
@@ -240,7 +241,7 @@ function App() {
currentPage === 'trader' && selectedTraderId currentPage === 'trader' && selectedTraderId
? `status-${selectedTraderId}` ? `status-${selectedTraderId}`
: null, : null,
() => api.getStatus(selectedTraderId), () => api.getStatus(selectedTraderId, true),
{ {
refreshInterval: 15000, // 15秒刷新配合后端15秒缓存 refreshInterval: 15000, // 15秒刷新配合后端15秒缓存
revalidateOnFocus: false, // 禁用聚焦时重新验证,减少请求 revalidateOnFocus: false, // 禁用聚焦时重新验证,减少请求
@@ -303,7 +304,7 @@ function App() {
currentPage === 'trader' && selectedTraderId currentPage === 'trader' && selectedTraderId
? `statistics-${selectedTraderId}` ? `statistics-${selectedTraderId}`
: null, : null,
() => api.getStatistics(selectedTraderId), () => api.getStatistics(selectedTraderId, true),
{ {
refreshInterval: 30000, // 30秒刷新统计数据更新频率较低 refreshInterval: 30000, // 30秒刷新统计数据更新频率较低
revalidateOnFocus: false, revalidateOnFocus: false,
@@ -520,7 +521,18 @@ function App() {
<AITradersPage <AITradersPage
onTraderSelect={(traderId) => { onTraderSelect={(traderId) => {
setSelectedTraderId(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') setRoute('/dashboard')
setCurrentPage('trader') setCurrentPage('trader')
}} }}
@@ -550,8 +562,10 @@ function App() {
// 更新 URL 参数(使用 slug: name-id前4位 // 更新 URL 参数(使用 slug: name-id前4位
const trader = traders?.find(t => t.trader_id === traderId) const trader = traders?.find(t => t.trader_id === traderId)
if (trader) { if (trader) {
const slug = getTraderSlug(trader)
setSelectedTraderSlug(slug)
const url = new URL(window.location.href) const url = new URL(window.location.href)
url.searchParams.set('trader', getTraderSlug(trader)) url.searchParams.set('trader', slug)
window.history.replaceState({}, '', url.toString()) window.history.replaceState({}, '', url.toString())
} }
}} }}

View File

@@ -153,7 +153,7 @@ export function AdvancedChart({
try { try {
const limit = 1500 const limit = 1500
const klineUrl = `/api/klines?symbol=${symbol}&interval=${interval}&limit=${limit}&exchange=${exchange}` 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) { if (!result.success || !result.data) {
throw new Error('Failed to fetch kline data') throw new Error('Failed to fetch kline data')
@@ -243,7 +243,10 @@ export function AdvancedChart({
try { try {
console.log('[AdvancedChart] Fetching orders for trader:', traderID, 'symbol:', symbol) console.log('[AdvancedChart] Fetching orders for trader:', traderID, 'symbol:', symbol)
// Fetch filled orders, up to 200 for more history // 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) console.log('[AdvancedChart] Orders API response:', result)
@@ -326,7 +329,10 @@ export function AdvancedChart({
const fetchOpenOrders = async (traderID: string, symbol: string): Promise<OpenOrder[]> => { const fetchOpenOrders = async (traderID: string, symbol: string): Promise<OpenOrder[]> => {
try { try {
console.log('[AdvancedChart] Fetching open orders for trader:', traderID, 'symbol:', symbol) 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) console.log('[AdvancedChart] Open orders API response:', result)

View File

@@ -114,7 +114,7 @@ export function ChartWithOrders({
const limit = 2000 // Fetch recent 2000 candles (more historical data) const limit = 2000 // Fetch recent 2000 candles (more historical data)
const klineUrl = `/api/klines?symbol=${symbol}&interval=${interval}&limit=${limit}&exchange=${exchange}` 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) { if (!result.success || !result.data) {
throw new Error('Failed to fetch kline data from our service') 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[]> => { const fetchOrders = async (traderID: string, symbol: string): Promise<OrderMarker[]> => {
try { try {
// Fetch filled orders for this trader from backend API // 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) { if (!result.success || !result.data) {
console.warn('Failed to fetch orders:', result.message) console.warn('Failed to fetch orders:', result.message)

View File

@@ -31,7 +31,7 @@ export function ChartWithOrdersSimple({
const klineUrl = `/api/klines?symbol=${symbol}&interval=${interval}&limit=${limit}` const klineUrl = `/api/klines?symbol=${symbol}&interval=${interval}&limit=${limit}`
console.log('[ChartSimple] Fetching klines from our service:', klineUrl) 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) { if (!klineResult.success || !klineResult.data) {
throw new Error('Failed to fetch klines from our service') throw new Error('Failed to fetch klines from our service')
@@ -44,7 +44,7 @@ export function ChartWithOrdersSimple({
if (traderID) { if (traderID) {
const tradesUrl = `/api/trades?trader_id=${traderID}&symbol=${symbol}&limit=100` const tradesUrl = `/api/trades?trader_id=${traderID}&symbol=${symbol}&limit=100`
console.log('[ChartSimple] Fetching trades from:', tradesUrl) 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) { if (tradesResult.success && tradesResult.data) {
console.log('[ChartSimple] Received trades:', tradesResult.data.length) console.log('[ChartSimple] Received trades:', tradesResult.data.length)

View File

@@ -43,7 +43,7 @@ export function EquityChart({ traderId, embedded = false }: EquityChartProps) {
const { data: history, error, isLoading } = useSWR<EquityPoint[]>( const { data: history, error, isLoading } = useSWR<EquityPoint[]>(
user && token && traderId ? `equity-history-${traderId}` : null, user && token && traderId ? `equity-history-${traderId}` : null,
() => api.getEquityHistory(traderId), () => api.getEquityHistory(traderId, true),
{ {
refreshInterval: 30000, // 30秒刷新历史数据更新频率较低 refreshInterval: 30000, // 30秒刷新历史数据更新频率较低
revalidateOnFocus: false, revalidateOnFocus: false,
@@ -53,7 +53,7 @@ export function EquityChart({ traderId, embedded = false }: EquityChartProps) {
const { data: account } = useSWR( const { data: account } = useSWR(
user && token && traderId ? `account-${traderId}` : null, user && token && traderId ? `account-${traderId}` : null,
() => api.getAccount(traderId), () => api.getAccount(traderId, true),
{ {
refreshInterval: 15000, // 15秒刷新配合后端缓存 refreshInterval: 15000, // 15秒刷新配合后端缓存
revalidateOnFocus: false, revalidateOnFocus: false,

View File

@@ -359,7 +359,11 @@ export function PositionHistory({ traderId }: PositionHistoryProps) {
setLoading(true) setLoading(true)
setError(null) setError(null)
// Fetch more data than needed to support filtering, but respect pageSize for initial load // 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 || []) setPositions(data.positions || [])
setStats(data.stats) setStats(data.stats)
setSymbolStats(data.symbol_stats || []) setSymbolStats(data.symbol_stats || [])

View File

@@ -10,11 +10,11 @@ import type {
import { API_BASE, httpClient } from './helpers' import { API_BASE, httpClient } from './helpers'
export const dataApi = { export const dataApi = {
async getStatus(traderId?: string): Promise<SystemStatus> { async getStatus(traderId?: string, silent?: boolean): Promise<SystemStatus> {
const url = traderId const url = traderId
? `${API_BASE}/status?trader_id=${traderId}` ? `${API_BASE}/status?trader_id=${traderId}`
: `${API_BASE}/status` : `${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') if (!result.success) throw new Error('Failed to fetch system status')
return result.data! return result.data!
}, },
@@ -65,20 +65,20 @@ export const dataApi = {
return result.data! return result.data!
}, },
async getStatistics(traderId?: string): Promise<Statistics> { async getStatistics(traderId?: string, silent?: boolean): Promise<Statistics> {
const url = traderId const url = traderId
? `${API_BASE}/statistics?trader_id=${traderId}` ? `${API_BASE}/statistics?trader_id=${traderId}`
: `${API_BASE}/statistics` : `${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') if (!result.success) throw new Error('Failed to fetch statistics')
return result.data! return result.data!
}, },
async getEquityHistory(traderId?: string): Promise<any[]> { async getEquityHistory(traderId?: string, silent?: boolean): Promise<any[]> {
const url = traderId const url = traderId
? `${API_BASE}/equity-history?trader_id=${traderId}` ? `${API_BASE}/equity-history?trader_id=${traderId}`
: `${API_BASE}/equity-history` : `${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') if (!result.success) throw new Error('Failed to fetch equity history')
return result.data! return result.data!
}, },
@@ -114,9 +114,14 @@ export const dataApi = {
return result.data! return result.data!
}, },
async getPositionHistory(traderId: string, limit: number = 100): Promise<PositionHistoryResponse> { async getPositionHistory(
const result = await httpClient.get<PositionHistoryResponse>( traderId: string,
`${API_BASE}/positions/history?trader_id=${traderId}&limit=${limit}` 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') if (!result.success) throw new Error('Failed to fetch position history')
return result.data! return result.data!

View File

@@ -16,8 +16,11 @@ function throwApiError(
} }
export const traderApi = { export const traderApi = {
async getTraders(): Promise<TraderInfo[]> { async getTraders(silent?: boolean): Promise<TraderInfo[]> {
const result = await httpClient.get<TraderInfo[]>(`${API_BASE}/my-traders`) const result = await httpClient.request<TraderInfo[]>(
`${API_BASE}/my-traders`,
{ silent }
)
if (!result.success) throw new Error('Failed to fetch trader list') if (!result.success) throw new Error('Failed to fetch trader list')
return Array.isArray(result.data) ? result.data : [] return Array.isArray(result.data) ? result.data : []
}, },