@@ -3,6 +3,7 @@ import { showToast } from 'vant'
33import { resolveServerUrl } from '@/config'
44import router from '@/router'
55import { useUserStore } from '@/stores'
6+ import { getLocale , t } from '@/locales'
67
78const http = axios . create ( {
89 timeout : 30000 ,
@@ -12,7 +13,7 @@ const http = axios.create({
1213} )
1314
1415export const getBaseUrl = ( ) => {
15- return resolveServerUrl ( localStorage . getItem ( 'serverUrl' ) )
16+ return resolveServerUrl ( )
1617}
1718
1819const ensureArray = ( value ) => Array . isArray ( value ) ? value : [ ]
@@ -41,8 +42,7 @@ const parseNumericMetric = (val) => {
4142 return { text, num : Number . isFinite ( num ) ? num : null }
4243}
4344
44- // 事件 → 黄金影响极性:{ bullish: '实际高于预期->利多', bearish: ... }
45- // 命中的关键词按从上到下优先级
45+ // Gold-impact rules are evaluated from top to bottom.
4646const GOLD_IMPACT_RULES = [
4747 { kw : / u n e m p l o y | j o b l e s s | i n i t i a l \s * c l a i m s | 失 业 | 申 请 失 业 / , higherIs : 'bullish' } ,
4848 { kw : / c p i | i n f l a t i o n | p p i | p c e | 消 费 者 物 价 | 生 产 者 物 价 | 通 胀 | 通 膨 / , higherIs : 'bullish' } ,
@@ -207,18 +207,10 @@ const unwrapItems = (data, key = 'items') => {
207207
208208const normalizeStrategy = ( raw = { } ) => {
209209 const tradingConfig = raw ?. trading_config && typeof raw . trading_config === 'object' ? raw . trading_config : { }
210- const indicatorConfig = raw ?. indicator_config && typeof raw . indicator_config === 'object' ? raw . indicator_config : { }
211210 const exchangeConfig = raw ?. exchange_config && typeof raw . exchange_config === 'object' ? raw . exchange_config : { }
212211 const notificationConfig = raw ?. notification_config && typeof raw . notification_config === 'object' ? raw . notification_config : { }
213212
214- const name = raw . name || raw . strategy_name || raw . group_base_name || ( raw . id ? `策略 #${ raw . id } ` : '未命名策略' )
215- const indicatorName = raw . indicator_name ||
216- indicatorConfig . indicator_name ||
217- indicatorConfig . name ||
218- indicatorConfig . display_name ||
219- indicatorConfig . indicator ||
220- tradingConfig . bot_name ||
221- ''
213+ const name = raw . strategy_name || ( raw . id ? t ( 'trading.strategy_fallback' , { id : raw . id } ) : '' )
222214
223215 const performance = raw . performance && typeof raw . performance === 'object'
224216 ? raw . performance
@@ -232,30 +224,47 @@ const normalizeStrategy = (raw = {}) => {
232224 return {
233225 ...raw ,
234226 name,
235- strategy_name : raw . strategy_name || name ,
236- type : raw . type || raw . strategy_type || '' ,
237- symbol : raw . symbol || tradingConfig . symbol || '' ,
238- timeframe : raw . timeframe || tradingConfig . timeframe || '' ,
239- indicator_name : indicatorName ,
240- indicator : {
241- ...( raw . indicator || { } ) ,
242- name : raw ?. indicator ?. name || indicatorName
243- } ,
244- trading_config : {
245- ...tradingConfig ,
246- symbol : tradingConfig . symbol || raw . symbol || '' ,
247- timeframe : tradingConfig . timeframe || raw . timeframe || '' ,
248- initial_capital : tradingConfig . initial_capital || raw . initial_capital || 0 ,
249- leverage : tradingConfig . leverage || raw . leverage || 1 ,
250- market_type : tradingConfig . market_type || raw . market_type || ''
251- } ,
227+ symbol : raw . symbol || '' ,
228+ timeframe : raw . timeframe || '' ,
229+ trading_config : tradingConfig ,
252230 exchange_config : exchangeConfig ,
253231 notification_config : notificationConfig ,
254232 performance
255233 }
256234}
257235
258- /** 登录/注册等「主动提交凭证」接口的 401 不应整页踢回登录(例如密码错误) */
236+ const normalizePosition = ( raw = { } ) => ( {
237+ ...raw ,
238+ quantity : Number ( raw . quantity ?? raw . qty ?? raw . size ?? raw . amount ?? 0 ) ,
239+ entry_price : Number ( raw . entry_price ?? raw . avg_price ?? 0 ) ,
240+ current_price : Number ( raw . current_price ?? raw . mark_price ?? raw . price ?? 0 ) ,
241+ unrealized_pnl : Number ( raw . unrealized_pnl ?? raw . pnl ?? 0 )
242+ } )
243+
244+ const normalizeTrade = ( raw = { } ) => ( {
245+ ...raw ,
246+ side : raw . side || raw . type || '' ,
247+ quantity : Number ( raw . quantity ?? raw . qty ?? raw . amount ?? 0 ) ,
248+ trade_price : Number ( raw . trade_price ?? raw . price ?? raw . entry_price ?? 0 ) ,
249+ pnl : Number ( raw . net_pnl ?? raw . profit ?? raw . pnl ?? 0 ) ,
250+ value : Number ( raw . value ?? raw . notional_value ?? 0 ) ,
251+ commission : Number ( raw . total_commission ?? raw . commission ?? 0 )
252+ } )
253+
254+ const localizedApiMessage = ( message , fallbackKey = 'api_errors.request_failed' ) => {
255+ const value = String ( message || '' ) . trim ( )
256+ if ( value ) {
257+ if ( / ^ n o d a t a f o u n d [ . ! ] ? $ / i. test ( value ) ) return t ( 'api_errors.no_data' )
258+ if ( value . includes ( '.' ) ) {
259+ const translated = t ( value )
260+ if ( translated && translated !== value ) return translated
261+ }
262+ return value
263+ }
264+ return t ( fallbackKey )
265+ }
266+
267+ /** Credential submission failures must not redirect the whole app. */
259268const isAuthCredentialRequest = ( url ) =>
260269 / \/ a p i \/ a u t h \/ ( l o g i n | l o g i n - c o d e | r e g i s t e r | s e n d - c o d e | r e s e t - p a s s w o r d | m f a \/ v e r i f y - l o g i n ) (?: \? | $ ) / i. test ( String ( url || '' ) )
261270
@@ -268,7 +277,7 @@ function clearAuthSession() {
268277 }
269278}
270279
271- /** 会话失效:清状态并回登录(已在登录页则只清状态) */
280+ /** Clear an expired session and return to login when needed. */
272281function redirectToLoginIfNeeded ( requestUrl ) {
273282 if ( isAuthCredentialRequest ( requestUrl ) ) {
274283 clearAuthSession ( )
@@ -280,11 +289,11 @@ function redirectToLoginIfNeeded(requestUrl) {
280289 const full = router . currentRoute . value . fullPath || '/home'
281290 router . replace ( { path : '/login' , query : { redirect : full } } )
282291 } catch ( _ ) {
283- /* router 未就绪时忽略 */
292+ // The router may not be ready during app bootstrap.
284293 }
285294}
286295
287- /** HTTP 200 但业务体表示需重新登录 */
296+ /** Detect session-expiry envelopes returned with HTTP 200. */
288297function isSessionExpiredBusinessResponse ( res ) {
289298 if ( ! res || typeof res !== 'object' ) return false
290299 const code = res . code
@@ -306,6 +315,9 @@ http.interceptors.request.use(
306315 if ( token ) {
307316 config . headers . Authorization = `Bearer ${ token } `
308317 }
318+ const locale = getLocale ( )
319+ config . headers [ 'Accept-Language' ] = locale
320+ config . headers [ 'X-App-Lang' ] = locale
309321 return config
310322 } ,
311323 ( error ) => Promise . reject ( error )
@@ -327,39 +339,47 @@ http.interceptors.response.use(
327339 if ( isSessionExpiredBusinessResponse ( res ) && ! isAuthCredentialRequest ( reqUrl ) ) {
328340 redirectToLoginIfNeeded ( reqUrl )
329341 }
330- showToast ( {
331- message : res ?. msg || res ?. message || '请求失败' ,
332- type : 'fail'
333- } )
334- return Promise . reject ( new Error ( res ?. msg || res ?. message || '请求失败' ) )
342+ const message = localizedApiMessage ( res ?. msg || res ?. message )
343+ showToast ( { message, type : 'fail' } )
344+ const error = new Error ( message )
345+ error . backendMessage = res ?. msg || res ?. message || ''
346+ return Promise . reject ( error )
335347 } ,
336348 ( error ) => {
337- let message = '网络错误'
349+ let message = t ( 'api_errors.network_error' )
338350 if ( error . response ) {
339351 switch ( error . response . status ) {
340352 case 401 :
341- message = '未授权,请重新登录'
342- redirectToLoginIfNeeded ( error . config ?. url )
353+ if ( isAuthCredentialRequest ( error . config ?. url ) ) {
354+ message = localizedApiMessage ( error . response . data ?. message || error . response . data ?. msg )
355+ } else {
356+ message = t ( 'api_errors.unauthorized' )
357+ redirectToLoginIfNeeded ( error . config ?. url )
358+ }
343359 break
344360 case 403 :
345- message = '拒绝访问'
361+ message = t ( 'api_errors.forbidden' )
346362 break
347363 case 404 :
348- message = '请求地址不存在'
364+ message = t ( 'api_errors.not_found' )
349365 break
350366 case 500 :
351- message = '服务器错误'
367+ message = localizedApiMessage (
368+ error . response . data ?. message || error . response . data ?. msg ,
369+ 'api_errors.server_error'
370+ )
352371 break
353372 default :
354- message = error . response . data ?. message || error . response . data ?. msg || '请求失败'
373+ message = localizedApiMessage ( error . response . data ?. message || error . response . data ?. msg )
355374 }
356375 } else if ( error . message ?. includes ( 'timeout' ) ) {
357- message = '请求超时'
376+ message = t ( 'api_errors.timeout' )
358377 } else if ( error . message ?. includes ( 'Network Error' ) ) {
359- message = '网络连接失败,请检查服务器地址'
378+ message = t ( 'api_errors.connection_failed' )
360379 }
361380
362381 showToast ( { message, type : 'fail' } )
382+ error . localizedMessage = message
363383 return Promise . reject ( error )
364384 }
365385)
@@ -371,6 +391,7 @@ export const authApi = {
371391 register : ( data ) => http . post ( '/api/auth/register' , data ) ,
372392 sendCode : ( data ) => http . post ( '/api/auth/send-code' , data ) ,
373393 resetPassword : ( data ) => http . post ( '/api/auth/reset-password' , data ) ,
394+ issueTurnstileClearance : ( data ) => http . post ( '/api/auth/turnstile-clearance' , data ) ,
374395 getSecurityConfig : ( ) => http . get ( '/api/auth/security-config' ) ,
375396 getInfo : ( ) => http . get ( '/api/auth/info' ) ,
376397 logout : ( ) => http . post ( '/api/auth/logout' ) ,
@@ -439,40 +460,33 @@ export const strategyApi = {
439460 data : res . data || null
440461 }
441462 } ,
442- create : ( payload ) => http . post ( '/api/strategies/create' , payload ) ,
443- batchCreate : ( payload ) => http . post ( '/api/strategies/batch-create' , payload ) ,
444- update : ( id , payload ) => http . put ( '/api/strategies/update' , { id, ...payload } ) ,
445- delete : ( id ) => http . delete ( '/api/strategies/delete' , { params : { id } } ) ,
446- aiGenerate : ( payload ) => http . post ( '/api/strategies/ai-generate' , payload , { raw : true , timeout : 180000 } ) ,
463+ create : ( payload ) => http . post ( '/api/strategies' , payload ) ,
464+ update : ( id , payload ) => http . put ( `/api/strategies/${ id } ` , payload ) ,
465+ delete : ( id ) => http . delete ( `/api/strategies/${ id } ` ) ,
466+ generate : ( payload ) => http . post ( '/api/strategies/generate' , payload , { timeout : 180000 } ) ,
447467 getList : async ( ) => {
448468 const res = await http . get ( '/api/strategies' )
449469 return {
450470 ...res ,
451- data : ensureArray ( res . data ?. strategies ) . map ( normalizeStrategy )
471+ data : ensureArray ( res . data ) . map ( normalizeStrategy )
452472 }
453473 } ,
454474 getDetail : async ( id ) => {
455- const res = await http . get ( '/api/strategies/detail' , {
456- params : { id }
457- } )
475+ const res = await http . get ( `/api/strategies/${ id } ` )
458476 return {
459477 ...res ,
460478 data : res . data ? normalizeStrategy ( res . data ) : null
461479 }
462480 } ,
463- start : ( id ) => http . post ( '/api/strategies/start' , null , {
464- params : { id }
465- } ) ,
466- stop : ( id ) => http . post ( '/api/strategies/stop' , null , {
467- params : { id }
468- } ) ,
481+ start : ( id ) => http . post ( `/api/strategies/${ id } /start` ) ,
482+ stop : ( id ) => http . post ( `/api/strategies/${ id } /stop` ) ,
469483 getTrades : async ( id , limit = 50 ) => {
470484 const res = await http . get ( '/api/strategies/trades' , {
471485 params : { id, limit }
472486 } )
473487 return {
474488 ...res ,
475- data : unwrapItems ( res . data , 'trades' )
489+ data : unwrapItems ( res . data , 'trades' ) . map ( normalizeTrade )
476490 }
477491 } ,
478492 getPositions : async ( id ) => {
@@ -481,7 +495,7 @@ export const strategyApi = {
481495 } )
482496 return {
483497 ...res ,
484- data : unwrapItems ( res . data , 'positions' )
498+ data : unwrapItems ( res . data , 'positions' ) . map ( normalizePosition )
485499 }
486500 } ,
487501 getEquityCurve : async ( id ) => {
@@ -511,7 +525,7 @@ export const strategyApi = {
511525 data : unwrapItems ( res . data , 'logs' )
512526 }
513527 } ,
514- testConnection : ( data ) => http . post ( '/api/strategies/test-connection ' , data ) ,
528+ testConnection : ( data ) => http . post ( '/api/strategies/exchange/ test' , data ) ,
515529 getNotifications : async ( params = { } ) => {
516530 const res = await http . get ( '/api/strategies/notifications' , { params } )
517531 return {
@@ -623,6 +637,14 @@ export const scriptSourceApi = {
623637 ...res ,
624638 data : res . data || null
625639 }
640+ } ,
641+ create : ( payload ) => http . post ( '/api/strategies/script-sources/create' , payload ) ,
642+ compile : async ( sourceId ) => {
643+ const res = await http . post ( '/api/strategies/script-sources/compile' , { sourceId } )
644+ return {
645+ ...res ,
646+ data : res . data ?. manifest || null
647+ }
626648 }
627649}
628650
@@ -683,15 +705,13 @@ export const watchlistApi = {
683705 return {
684706 ...res ,
685707 data : ensureArray ( res . data ) . map ( ( item ) => ( {
686- id : item . id ,
687- market : item . market ,
688- symbol : item . symbol ,
708+ ...item ,
689709 name : item . name || item . symbol
690710 } ) )
691711 }
692712 } ,
693713 add : ( payload ) => http . post ( '/api/market/watchlist/add' , payload ) ,
694- remove : ( symbol ) => http . post ( '/api/market/watchlist/remove' , { symbol } ) ,
714+ remove : ( item ) => http . post ( '/api/market/watchlist/remove' , typeof item === 'string' ? { symbol : item } : item ) ,
695715 search : async ( params ) => {
696716 const res = await http . get ( '/api/market/symbols/search' , { params } )
697717 return {
@@ -718,22 +738,39 @@ export const watchlistApi = {
718738}
719739
720740export const klineApi = {
721- getKline : async ( { market = 'Crypto' , symbol, timeframe = '1h' , limit = 200 , beforeTime } = { } ) => {
741+ getKline : async ( {
742+ market = 'Crypto' ,
743+ symbol,
744+ timeframe = '1h' ,
745+ limit = 200 ,
746+ beforeTime,
747+ exchangeId,
748+ marketType,
749+ instrumentId
750+ } = { } ) => {
722751 const params = { market, symbol, timeframe, limit }
723752 if ( beforeTime ) params . before_time = beforeTime
753+ if ( exchangeId ) params . exchange_id = exchangeId
754+ if ( marketType ) params . market_type = marketType
755+ if ( instrumentId ) params . instrument_id = instrumentId
724756 const res = await http . get ( '/api/indicator/kline' , { params } )
725757 return {
726758 ...res ,
727759 data : ensureArray ( res . data )
728760 }
729761 } ,
730- getPrice : async ( { market = 'Crypto' , symbol } = { } ) => {
731- const res = await http . get ( '/api/market/price' , { params : { market, symbol } } )
762+ getPrice : async ( { market = 'Crypto' , symbol, exchangeId, marketType, instrumentId } = { } ) => {
763+ const params = { market, symbol }
764+ if ( exchangeId ) params . exchange_id = exchangeId
765+ if ( marketType ) params . market_type = marketType
766+ if ( instrumentId ) params . instrument_id = instrumentId
767+ const res = await http . get ( '/api/market/price' , { params } )
732768 return {
733769 ...res ,
734770 data : res . data || null
735771 }
736- }
772+ } ,
773+ getTicker : ( symbol , market = 'Crypto' ) => klineApi . getPrice ( { market, symbol } )
737774}
738775
739776export const aiChatApi = {
@@ -830,13 +867,6 @@ export const indicatorApi = {
830867 ...res ,
831868 data : Array . isArray ( res . data ) ? res . data : ( res . data ?. params || [ ] )
832869 }
833- } ,
834- parseStrategyConfig : async ( code ) => {
835- const res = await http . post ( '/api/indicator/parseStrategyConfig' , { code : code || '' } )
836- return {
837- ...res ,
838- data : res . data || { strategyConfig : { } , indicatorParams : [ ] }
839- }
840870 }
841871}
842872
0 commit comments