mirror of
https://github.com/NoFxAiOS/nofx.git
synced 2026-07-16 01:06:59 +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:
@@ -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())
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|||||||
@@ -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,
|
||||||
|
|||||||
@@ -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 || [])
|
||||||
|
|||||||
@@ -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!
|
||||||
|
|||||||
@@ -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 : []
|
||||||
},
|
},
|
||||||
|
|||||||
Reference in New Issue
Block a user