Skip to content

Commit 721d82e

Browse files
committed
v5.0.1
Signed-off-by: TIANHE <TIANHE@GMAIL.COM>
1 parent d68245b commit 721d82e

38 files changed

Lines changed: 2013 additions & 7484 deletions

src/api/index.js

Lines changed: 110 additions & 80 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import { showToast } from 'vant'
33
import { resolveServerUrl } from '@/config'
44
import router from '@/router'
55
import { useUserStore } from '@/stores'
6+
import { getLocale, t } from '@/locales'
67

78
const http = axios.create({
89
timeout: 30000,
@@ -12,7 +13,7 @@ const http = axios.create({
1213
})
1314

1415
export const getBaseUrl = () => {
15-
return resolveServerUrl(localStorage.getItem('serverUrl'))
16+
return resolveServerUrl()
1617
}
1718

1819
const 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.
4646
const GOLD_IMPACT_RULES = [
4747
{ kw: /unemploy|jobless|initial\s*claims||/, higherIs: 'bullish' },
4848
{ kw: /cpi|inflation|ppi|pce||||/, higherIs: 'bullish' },
@@ -207,18 +207,10 @@ const unwrapItems = (data, key = 'items') => {
207207

208208
const 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 (/^no data found[.!]?$/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. */
259268
const isAuthCredentialRequest = (url) =>
260269
/\/api\/auth\/(login|login-code|register|send-code|reset-password|mfa\/verify-login)(?:\?|$)/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. */
272281
function 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. */
288297
function 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

720740
export 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

739776
export 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

Comments
 (0)