Files
PolyHermes/frontend/src/services/api.ts
T

603 lines
16 KiB
TypeScript
Raw Normal View History

import axios, { AxiosInstance, AxiosError } from 'axios'
2025-12-05 02:20:46 +08:00
import type { ApiResponse, NotificationConfig, NotificationConfigRequest, NotificationConfigUpdateRequest } from '../types'
import { getToken, setToken, removeToken } from '../utils'
import { wsManager } from './websocket'
import i18n from '../i18n/config'
2025-11-21 04:32:08 +08:00
/**
* API 基础配置
* 默认使用相对路径 /api(通过反向代理转发)
* 如果设置了 VITE_API_URL 环境变量,则使用完整 URL(用于跨域场景)
2025-11-21 04:32:08 +08:00
*/
2025-12-03 02:48:02 +08:00
const getBaseURL = (): string => {
const envApiUrl = import.meta.env.VITE_API_URL
if (envApiUrl) {
// 如果设置了环境变量,使用完整 URL(支持跨域)
2025-12-03 02:48:02 +08:00
return `${envApiUrl}/api`
}
// 否则使用相对路径(通过反向代理转发)
2025-12-03 02:48:02 +08:00
return '/api'
}
2025-11-21 04:32:08 +08:00
const apiClient: AxiosInstance = axios.create({
2025-12-03 02:48:02 +08:00
baseURL: getBaseURL(),
2025-11-21 04:32:08 +08:00
timeout: 30000,
headers: {
'Content-Type': 'application/json'
}
})
/**
* 获取当前语言设置(优先从 localStorage 读取,确保获取最新值)
*/
const getCurrentLanguage = (): string => {
// 优先从 localStorage 读取用户设置的语言(统一使用 i18n_language
let savedLanguage = localStorage.getItem('i18n_language')
// 如果 i18n_language 不存在,尝试从 i18nextLng 读取(i18next 默认使用的 key
if (!savedLanguage) {
savedLanguage = localStorage.getItem('i18nextLng')
}
// 如果设置了具体语言,使用设置的语言
if (savedLanguage && savedLanguage !== 'auto' && ['zh-CN', 'zh-TW', 'en'].includes(savedLanguage)) {
return savedLanguage
}
// 如果设置为 auto 或未设置,使用 i18n 的当前语言
// 如果 i18n.language 也没有,使用默认值 'en'
const currentLang = i18n.language || 'en'
// 确保返回的语言格式正确(移除可能的区域代码,如 'en-US' -> 'en'
if (currentLang.startsWith('zh-CN')) return 'zh-CN'
if (currentLang.startsWith('zh-TW') || currentLang.startsWith('zh-HK')) return 'zh-TW'
if (currentLang.startsWith('en')) return 'en'
return 'en'
}
2025-11-21 04:32:08 +08:00
/**
* 请求拦截器
*/
apiClient.interceptors.request.use(
(config) => {
// 从 localStorage 读取 token 并添加到请求头
const token = getToken()
if (token) {
config.headers.Authorization = `Bearer ${token}`
}
// 添加语言 Header(每次请求都获取最新值)
const language = getCurrentLanguage()
config.headers['X-Language'] = language
2025-11-21 04:32:08 +08:00
return config
},
(error) => {
return Promise.reject(error)
}
)
/**
* 处理认证错误(自动登出)
*/
const handleAuthError = (code: number) => {
// 检查是否是认证错误(2001-2999)
if (code >= 2001 && code < 3000) {
// 清除 token
removeToken()
// 断开 WebSocket 连接
wsManager.disconnect()
// 跳转到登录页(避免循环跳转)
if (window.location.pathname !== '/login' && window.location.pathname !== '/reset-password') {
window.location.href = '/login'
}
}
}
2025-11-21 04:32:08 +08:00
/**
* 响应拦截器
*/
apiClient.interceptors.response.use(
(response) => {
// 检查响应头中是否有新的 token(自动刷新)
const newToken = response.headers['x-new-token']
if (newToken) {
setToken(newToken)
}
// 检查响应体中的 code,如果是认证错误,自动登出
// 后端可能返回 200 状态码,但 code 是 2001(认证失败)
const data = response.data as ApiResponse<any>
if (data && data.code !== undefined) {
handleAuthError(data.code)
}
2025-11-21 04:32:08 +08:00
return response
},
(error: AxiosError<ApiResponse<any>>) => {
2025-11-21 04:32:08 +08:00
if (error.response) {
const response = error.response
const data = response.data
// 检查是否是认证错误(2001-2999)
if (data && data.code !== undefined) {
handleAuthError(data.code)
}
console.error('API 错误:', data)
2025-11-21 04:32:08 +08:00
} else if (error.request) {
console.error('网络错误:', error.request)
} else {
console.error('请求错误:', error.message)
}
return Promise.reject(error)
}
)
/**
* API 服务
*/
export const apiService = {
/**
* 用户管理 API
*/
users: {
/**
* 获取用户列表
*/
list: () =>
apiClient.post<ApiResponse<any[]>>('/users/list', {}),
/**
* 创建用户
*/
create: (data: { username: string; password: string }) =>
apiClient.post<ApiResponse<any>>('/users/create', data),
/**
* 更新用户密码
*/
updatePassword: (data: { userId: number; newPassword: string }) =>
apiClient.post<ApiResponse<void>>('/users/update-password', data),
/**
* 删除用户
*/
delete: (data: { userId: number }) =>
apiClient.post<ApiResponse<void>>('/users/delete', data),
/**
* 用户修改自己的密码
*/
updateOwnPassword: (data: { newPassword: string }) =>
apiClient.post<ApiResponse<void>>('/users/update-own-password', data)
},
/**
* 认证 API
*/
auth: {
/**
* 登录
*/
login: (data: { username: string; password: string }) =>
apiClient.post<ApiResponse<{ token: string }>>('/auth/login', data),
/**
* 重置密码
*/
resetPassword: (data: { resetKey: string; username: string; newPassword: string }) =>
apiClient.post<ApiResponse<void>>('/auth/reset-password', data),
/**
* 检查是否首次使用
*/
checkFirstUse: () =>
apiClient.post<ApiResponse<{ isFirstUse: boolean }>>('/auth/check-first-use', {})
},
2025-11-21 04:32:08 +08:00
/**
* 账户管理 API
*/
accounts: {
/**
* 导入账户
*/
import: (data: any) =>
apiClient.post<ApiResponse<any>>('/copy-trading/accounts/import', data),
/**
* 更新账户
*/
update: (data: any) =>
apiClient.post<ApiResponse<any>>('/copy-trading/accounts/update', data),
/**
* 删除账户
*/
delete: (data: { accountId: number }) =>
apiClient.post<ApiResponse<void>>('/copy-trading/accounts/delete', data),
/**
* 查询账户列表
*/
list: () =>
apiClient.post<ApiResponse<any>>('/copy-trading/accounts/list', {}),
/**
* 查询账户详情
*/
detail: (data: { accountId?: number }) =>
apiClient.post<ApiResponse<any>>('/copy-trading/accounts/detail', data),
/**
* 查询账户余额
*/
balance: (data: { accountId?: number }) =>
apiClient.post<ApiResponse<any>>('/copy-trading/accounts/balance', data),
/**
* 查询所有账户的仓位列表
*/
positionsList: () =>
apiClient.post<ApiResponse<any>>('/copy-trading/accounts/positions/list', {}),
/**
* 卖出仓位
*/
sellPosition: (data: any) =>
apiClient.post<ApiResponse<any>>('/copy-trading/accounts/positions/sell', data),
/**
* 获取可赎回仓位统计
*/
getRedeemableSummary: (data: { accountId?: number }) =>
apiClient.post<ApiResponse<any>>('/copy-trading/accounts/positions/redeemable-summary', data),
/**
* 赎回仓位
*/
redeemPositions: (data: any) =>
apiClient.post<ApiResponse<any>>('/copy-trading/accounts/positions/redeem', data),
},
/**
* 市场数据 API
*/
markets: {
/**
* 获取市场价格(通过 Gamma API)
*/
getMarketPrice: (data: { marketId: string; outcomeIndex?: number }) =>
apiClient.post<ApiResponse<any>>('/copy-trading/markets/price', data),
/**
* 获取最新价(从订单表获取,供前端下单时显示)
*/
getLatestPrice: (data: { tokenId: string }) =>
apiClient.post<ApiResponse<any>>('/copy-trading/markets/latest-price', data)
2025-11-21 04:32:08 +08:00
},
/**
* Leader 管理 API
*/
leaders: {
/**
* 添加 Leader
*/
2025-12-01 23:03:17 +08:00
add: (data: { leaderAddress: string; leaderName?: string; category?: string }) =>
2025-11-21 04:32:08 +08:00
apiClient.post<ApiResponse<any>>('/copy-trading/leaders/add', data),
/**
* 更新 Leader
*/
2025-12-01 23:03:17 +08:00
update: (data: { leaderId: number; leaderName?: string; category?: string }) =>
2025-11-21 04:32:08 +08:00
apiClient.post<ApiResponse<any>>('/copy-trading/leaders/update', data),
/**
* 删除 Leader
*/
delete: (data: { leaderId: number }) =>
apiClient.post<ApiResponse<void>>('/copy-trading/leaders/delete', data),
/**
* 查询 Leader 列表
*/
2025-12-01 23:03:17 +08:00
list: (data: { category?: string } = {}) =>
apiClient.post<ApiResponse<any>>('/copy-trading/leaders/list', data),
/**
* 查询 Leader 详情
*/
detail: (data: { leaderId: number }) =>
apiClient.post<ApiResponse<any>>('/copy-trading/leaders/detail', data)
2025-11-21 04:32:08 +08:00
},
/**
2025-12-01 23:03:17 +08:00
* 跟单模板管理 API(子菜单:跟单模板)
2025-11-21 04:32:08 +08:00
*/
2025-12-01 23:03:17 +08:00
templates: {
2025-11-21 04:32:08 +08:00
/**
2025-12-01 23:03:17 +08:00
* 创建模板
2025-11-21 04:32:08 +08:00
*/
2025-12-01 23:03:17 +08:00
create: (data: any) =>
apiClient.post<ApiResponse<any>>('/copy-trading/templates/create', data),
2025-11-21 04:32:08 +08:00
/**
2025-12-01 23:03:17 +08:00
* 更新模板
2025-11-21 04:32:08 +08:00
*/
update: (data: any) =>
2025-12-01 23:03:17 +08:00
apiClient.post<ApiResponse<any>>('/copy-trading/templates/update', data),
/**
* 删除模板
*/
delete: (data: { templateId: number }) =>
apiClient.post<ApiResponse<void>>('/copy-trading/templates/delete', data),
/**
* 复制模板
*/
copy: (data: any) =>
apiClient.post<ApiResponse<any>>('/copy-trading/templates/copy', data),
/**
* 查询模板列表
*/
list: () =>
apiClient.post<ApiResponse<any>>('/copy-trading/templates/list', {}),
/**
* 查询模板详情
*/
detail: (data: { templateId: number }) =>
apiClient.post<ApiResponse<any>>('/copy-trading/templates/detail', data)
},
/**
* 跟单配置管理 API(子菜单:跟单配置)
*/
copyTrading: {
/**
* 创建跟单配置
* 支持两种方式:
* 1. 提供 templateId:从模板填充配置,可以覆盖部分字段
* 2. 不提供 templateId:手动输入所有配置参数
2025-12-01 23:03:17 +08:00
*/
create: (data: any) =>
2025-12-01 23:03:17 +08:00
apiClient.post<ApiResponse<any>>('/copy-trading/create', data),
/**
* 更新跟单配置
*/
update: (data: any) =>
apiClient.post<ApiResponse<any>>('/copy-trading/update', data),
2025-12-01 23:03:17 +08:00
/**
* 查询跟单列表
*/
list: (data: { accountId?: number; leaderId?: number; enabled?: boolean } = {}) =>
2025-12-01 23:03:17 +08:00
apiClient.post<ApiResponse<any>>('/copy-trading/list', data),
/**
* 更新跟单状态(兼容旧接口)
2025-12-01 23:03:17 +08:00
*/
updateStatus: (data: { copyTradingId: number; enabled: boolean }) =>
apiClient.post<ApiResponse<any>>('/copy-trading/update-status', data),
/**
* 删除跟单
*/
delete: (data: { copyTradingId: number }) =>
apiClient.post<ApiResponse<void>>('/copy-trading/delete', data),
/**
* 查询钱包绑定的跟单配置(兼容旧接口)
2025-12-01 23:03:17 +08:00
*/
getAccountTemplates: (data: { accountId: number }) =>
apiClient.post<ApiResponse<any>>('/copy-trading/account-templates', data),
/**
* 查询被过滤订单列表
*/
getFilteredOrders: (data: {
copyTradingId: number
filterType?: string
page?: number
limit?: number
startTime?: number
endTime?: number
}) =>
apiClient.post<ApiResponse<any>>('/copy-trading/filtered-orders', data)
2025-11-21 04:32:08 +08:00
},
/**
* 订单管理 API
*/
orders: {
/**
* 查询跟单订单列表
*/
list: (data: any) =>
apiClient.post<ApiResponse<any>>('/copy-trading/orders/list', data),
/**
* 取消跟单订单
*/
cancel: (data: { copyOrderId: number }) =>
apiClient.post<ApiResponse<void>>('/copy-trading/orders/cancel', data)
},
/**
* 统计 API
*/
statistics: {
/**
* 获取全局统计
*/
global: (data: { startTime?: number; endTime?: number } = {}) =>
apiClient.post<ApiResponse<any>>('/copy-trading/statistics/global', data),
/**
* 获取 Leader 统计
*/
leader: (data: { leaderId: number; startTime?: number; endTime?: number }) =>
apiClient.post<ApiResponse<any>>('/copy-trading/statistics/leader', data),
/**
* 获取分类统计
*/
category: (data: { category: string; startTime?: number; endTime?: number }) =>
2025-12-02 22:22:39 +08:00
apiClient.post<ApiResponse<any>>('/copy-trading/statistics/category', data),
/**
* 获取跟单关系统计详情
*/
detail: (data: { copyTradingId: number }) =>
apiClient.post<ApiResponse<any>>('/copy-trading/statistics/detail', data)
},
/**
* 订单跟踪 API
*/
orderTracking: {
/**
* 查询订单列表(买入/卖出/匹配)
*/
list: (data: any) =>
apiClient.post<ApiResponse<any>>('/copy-trading/orders/tracking', data)
},
/**
* 代理配置 API
*/
proxyConfig: {
/**
* 获取当前代理配置
*/
get: () =>
apiClient.post<ApiResponse<any>>('/proxy-config/get', {}),
/**
* 获取所有代理配置
*/
list: () =>
apiClient.post<ApiResponse<any[]>>('/proxy-config/list', {}),
/**
* 保存 HTTP 代理配置
*/
saveHttp: (data: {
enabled: boolean
host: string
port: number
username?: string
password?: string
}) =>
apiClient.post<ApiResponse<any>>('/proxy-config/http/save', data),
/**
* 检查代理是否可用
*/
check: () =>
apiClient.post<ApiResponse<{
success: boolean
message: string
responseTime?: number
}>>('/proxy-config/check', {}),
/**
* 删除代理配置
*/
delete: (data: { id: number }) =>
apiClient.post<ApiResponse<void>>('/proxy-config/delete', data),
/**
* 检查所有 API 的健康状态
*/
checkApiHealth: () =>
apiClient.post<ApiResponse<{
apis: Array<{
name: string
url: string
status: string
message: string
responseTime?: number
}>
}>>('/proxy-config/api-health-check', {})
2025-12-05 02:20:46 +08:00
},
/**
* 消息推送配置 API
*/
notifications: {
/**
* 获取配置列表
*/
list: (data?: { type?: string }) =>
apiClient.post<ApiResponse<NotificationConfig[]>>('/notifications/configs/list', data || {}),
/**
* 获取配置详情
*/
detail: (data: { id: number }) =>
apiClient.post<ApiResponse<NotificationConfig>>('/notifications/configs/detail', data),
/**
* 创建配置
*/
create: (data: NotificationConfigRequest) =>
apiClient.post<ApiResponse<NotificationConfig>>('/notifications/configs/create', data),
/**
* 更新配置
*/
update: (data: NotificationConfigUpdateRequest) =>
apiClient.post<ApiResponse<NotificationConfig>>('/notifications/configs/update', data),
/**
* 更新启用状态
*/
updateEnabled: (data: { id: number; enabled: boolean }) =>
apiClient.post<ApiResponse<NotificationConfig>>('/notifications/configs/update-enabled', data),
/**
* 删除配置
*/
delete: (data: { id: number }) =>
apiClient.post<ApiResponse<void>>('/notifications/configs/delete', data),
/**
* 测试通知
*/
test: (data?: { message?: string }) =>
apiClient.post<ApiResponse<boolean>>('/notifications/test', data || {}),
/**
* 获取 Telegram Chat IDs
*/
getTelegramChatIds: (data: { botToken: string }) =>
apiClient.post<ApiResponse<string[]>>('/notifications/telegram/get-chat-ids', data)
},
/**
* 系统配置 API
*/
systemConfig: {
/**
* 获取系统配置
*/
get: () =>
apiClient.post<ApiResponse<import('../types').SystemConfig>>('/system/config/get', {}),
/**
* 更新 Builder API Key 配置
*/
updateBuilderApiKey: (data: import('../types').BuilderApiKeyUpdateRequest) =>
apiClient.post<ApiResponse<import('../types').SystemConfig>>('/system/config/builder-api-key/update', data)
2025-11-21 04:32:08 +08:00
}
}
export default apiService
2025-11-21 04:32:08 +08:00