feat: 赎回批量执行与仓位页体验优化
- 赎回:同一账户多市场合并为一笔交易,减少 Relayer 调用次数 - 仓位页:可赎回统计静默刷新,赎回按钮不再常驻 loading - 账户导入表单、API、多语言等相关改动 Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
+44
@@ -23,6 +23,50 @@ class AccountController(
|
|||||||
|
|
||||||
private val logger = LoggerFactory.getLogger(AccountController::class.java)
|
private val logger = LoggerFactory.getLogger(AccountController::class.java)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 检查代理地址选项(用于导入前选择代理类型)
|
||||||
|
*/
|
||||||
|
@PostMapping("/check-proxy-options")
|
||||||
|
fun checkProxyOptions(@RequestBody request: CheckProxyOptionsRequest): ResponseEntity<ApiResponse<CheckProxyOptionsResponse>> {
|
||||||
|
return try {
|
||||||
|
if (request.walletAddress.isBlank()) {
|
||||||
|
return ResponseEntity.ok(ApiResponse.error(ErrorCode.PARAM_WALLET_ADDRESS_EMPTY, messageSource = messageSource))
|
||||||
|
}
|
||||||
|
if (request.privateKey.isNullOrBlank() && request.mnemonic.isNullOrBlank()) {
|
||||||
|
return ResponseEntity.ok(ApiResponse.error(ErrorCode.PARAM_ERROR, "必须提供私钥或助记词", messageSource))
|
||||||
|
}
|
||||||
|
|
||||||
|
val result = runBlocking { accountService.checkProxyOptions(request) }
|
||||||
|
result.fold(
|
||||||
|
onSuccess = { response ->
|
||||||
|
ResponseEntity.ok(ApiResponse.success(response))
|
||||||
|
},
|
||||||
|
onFailure = { e ->
|
||||||
|
logger.error("检查代理地址选项失败: ${e.message}", e)
|
||||||
|
when (e) {
|
||||||
|
is IllegalArgumentException -> ResponseEntity.ok(
|
||||||
|
ApiResponse.error(
|
||||||
|
ErrorCode.PARAM_ERROR,
|
||||||
|
e.message,
|
||||||
|
messageSource
|
||||||
|
)
|
||||||
|
)
|
||||||
|
else -> ResponseEntity.ok(
|
||||||
|
ApiResponse.error(
|
||||||
|
ErrorCode.SERVER_ERROR,
|
||||||
|
e.message,
|
||||||
|
messageSource
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
} catch (e: Exception) {
|
||||||
|
logger.error("检查代理地址选项异常: ${e.message}", e)
|
||||||
|
ResponseEntity.ok(ApiResponse.error(ErrorCode.SERVER_ERROR, e.message, messageSource))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 通过私钥导入账户
|
* 通过私钥导入账户
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -11,6 +11,37 @@ data class AccountImportRequest(
|
|||||||
val walletType: String = "magic" // 钱包类型:magic(邮箱/OAuth登录)或 safe(MetaMask浏览器钱包)
|
val walletType: String = "magic" // 钱包类型:magic(邮箱/OAuth登录)或 safe(MetaMask浏览器钱包)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 检查代理地址选项请求
|
||||||
|
*/
|
||||||
|
data class CheckProxyOptionsRequest(
|
||||||
|
val walletAddress: String, // EOA 地址(必需)
|
||||||
|
val privateKey: String? = null, // 私钥(加密,私钥导入时提供)
|
||||||
|
val mnemonic: String? = null // 助记词(加密,助记词导入时提供)
|
||||||
|
)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 代理地址选项信息
|
||||||
|
*/
|
||||||
|
data class ProxyOptionDto(
|
||||||
|
val walletType: String, // "magic" 或 "safe"
|
||||||
|
val proxyAddress: String, // 代理地址
|
||||||
|
val descriptionKey: String, // 说明文案的多语言 key(如 "accountImport.proxyOption.magic.description")
|
||||||
|
val availableBalance: String, // 可用余额
|
||||||
|
val positionBalance: String, // 仓位余额
|
||||||
|
val totalBalance: String, // 总余额
|
||||||
|
val positionCount: Int, // 持仓数量
|
||||||
|
val hasAssets: Boolean, // 是否有资产(余额>0 或持仓>0)
|
||||||
|
val error: String? = null // 获取失败时的错误信息(可选)
|
||||||
|
)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 检查代理地址选项响应
|
||||||
|
*/
|
||||||
|
data class CheckProxyOptionsResponse(
|
||||||
|
val options: List<ProxyOptionDto> // 代理地址选项列表(私钥导入返回2个,助记词返回1个)
|
||||||
|
)
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 账户更新请求
|
* 账户更新请求
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import com.wrbug.polymarketbot.repository.AccountRepository
|
|||||||
import com.wrbug.polymarketbot.util.RetrofitFactory
|
import com.wrbug.polymarketbot.util.RetrofitFactory
|
||||||
import com.wrbug.polymarketbot.util.toSafeBigDecimal
|
import com.wrbug.polymarketbot.util.toSafeBigDecimal
|
||||||
import com.wrbug.polymarketbot.util.eq
|
import com.wrbug.polymarketbot.util.eq
|
||||||
|
import com.wrbug.polymarketbot.util.gt
|
||||||
import com.wrbug.polymarketbot.util.JsonUtils
|
import com.wrbug.polymarketbot.util.JsonUtils
|
||||||
import com.wrbug.polymarketbot.util.getEventSlug
|
import com.wrbug.polymarketbot.util.getEventSlug
|
||||||
import com.wrbug.polymarketbot.service.common.PolymarketClobService
|
import com.wrbug.polymarketbot.service.common.PolymarketClobService
|
||||||
@@ -171,6 +172,192 @@ class AccountService(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 检查代理地址选项(用于账户导入前选择代理类型)
|
||||||
|
* 私钥导入:返回 Magic 和 Safe 两个选项
|
||||||
|
* 助记词导入:仅返回 Safe 选项
|
||||||
|
*/
|
||||||
|
suspend fun checkProxyOptions(request: CheckProxyOptionsRequest): Result<CheckProxyOptionsResponse> {
|
||||||
|
return try {
|
||||||
|
// 1. 验证钱包地址格式
|
||||||
|
if (!isValidWalletAddress(request.walletAddress)) {
|
||||||
|
return Result.failure(IllegalArgumentException("无效的钱包地址格式"))
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. 验证至少提供了私钥或助记词之一
|
||||||
|
if (request.privateKey.isNullOrBlank() && request.mnemonic.isNullOrBlank()) {
|
||||||
|
return Result.failure(IllegalArgumentException("必须提供私钥或助记词"))
|
||||||
|
}
|
||||||
|
|
||||||
|
val options = mutableListOf<ProxyOptionDto>()
|
||||||
|
|
||||||
|
// 3. 判断导入类型
|
||||||
|
val isPrivateKeyImport = !request.privateKey.isNullOrBlank()
|
||||||
|
|
||||||
|
if (isPrivateKeyImport) {
|
||||||
|
// 私钥导入:并行获取 Magic 和 Safe 代理地址及资产
|
||||||
|
coroutineScope {
|
||||||
|
val magicDeferred = async {
|
||||||
|
try {
|
||||||
|
val proxyAddress = blockchainService.getProxyAddress(request.walletAddress, "magic").getOrNull()
|
||||||
|
if (proxyAddress != null) {
|
||||||
|
val balance = blockchainService.getWalletBalance(proxyAddress).getOrNull()
|
||||||
|
ProxyOptionDto(
|
||||||
|
walletType = "magic",
|
||||||
|
proxyAddress = proxyAddress,
|
||||||
|
descriptionKey = "accountImport.proxyOption.magic.description",
|
||||||
|
availableBalance = balance?.availableBalance ?: "0",
|
||||||
|
positionBalance = balance?.positionBalance ?: "0",
|
||||||
|
totalBalance = balance?.totalBalance ?: "0",
|
||||||
|
positionCount = balance?.positions?.size ?: 0,
|
||||||
|
hasAssets = (balance?.availableBalance?.toSafeBigDecimal()?.gt(BigDecimal.ZERO) == true) ||
|
||||||
|
(balance?.positionBalance?.toSafeBigDecimal()?.gt(BigDecimal.ZERO) == true) ||
|
||||||
|
(balance?.positions?.isNotEmpty() == true),
|
||||||
|
error = null
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
ProxyOptionDto(
|
||||||
|
walletType = "magic",
|
||||||
|
proxyAddress = "",
|
||||||
|
descriptionKey = "accountImport.proxyOption.magic.description",
|
||||||
|
availableBalance = "0",
|
||||||
|
positionBalance = "0",
|
||||||
|
totalBalance = "0",
|
||||||
|
positionCount = 0,
|
||||||
|
hasAssets = false,
|
||||||
|
error = "获取 Magic 代理地址失败"
|
||||||
|
)
|
||||||
|
}
|
||||||
|
} catch (e: Exception) {
|
||||||
|
logger.warn("获取 Magic 代理地址或资产失败: ${e.message}", e)
|
||||||
|
ProxyOptionDto(
|
||||||
|
walletType = "magic",
|
||||||
|
proxyAddress = blockchainService.calculateMagicProxyAddress(request.walletAddress),
|
||||||
|
descriptionKey = "accountImport.proxyOption.magic.description",
|
||||||
|
availableBalance = "0",
|
||||||
|
positionBalance = "0",
|
||||||
|
totalBalance = "0",
|
||||||
|
positionCount = 0,
|
||||||
|
hasAssets = false,
|
||||||
|
error = "获取资产信息失败: ${e.message}"
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
val safeDeferred = async {
|
||||||
|
try {
|
||||||
|
val proxyAddress = blockchainService.getProxyAddress(request.walletAddress, "safe").getOrNull()
|
||||||
|
if (proxyAddress != null) {
|
||||||
|
val balance = blockchainService.getWalletBalance(proxyAddress).getOrNull()
|
||||||
|
ProxyOptionDto(
|
||||||
|
walletType = "safe",
|
||||||
|
proxyAddress = proxyAddress,
|
||||||
|
descriptionKey = "accountImport.proxyOption.safe.description",
|
||||||
|
availableBalance = balance?.availableBalance ?: "0",
|
||||||
|
positionBalance = balance?.positionBalance ?: "0",
|
||||||
|
totalBalance = balance?.totalBalance ?: "0",
|
||||||
|
positionCount = balance?.positions?.size ?: 0,
|
||||||
|
hasAssets = (balance?.availableBalance?.toSafeBigDecimal()?.gt(BigDecimal.ZERO) == true) ||
|
||||||
|
(balance?.positionBalance?.toSafeBigDecimal()?.gt(BigDecimal.ZERO) == true) ||
|
||||||
|
(balance?.positions?.isNotEmpty() == true),
|
||||||
|
error = null
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
ProxyOptionDto(
|
||||||
|
walletType = "safe",
|
||||||
|
proxyAddress = "",
|
||||||
|
descriptionKey = "accountImport.proxyOption.safe.description",
|
||||||
|
availableBalance = "0",
|
||||||
|
positionBalance = "0",
|
||||||
|
totalBalance = "0",
|
||||||
|
positionCount = 0,
|
||||||
|
hasAssets = false,
|
||||||
|
error = "获取 Safe 代理地址失败"
|
||||||
|
)
|
||||||
|
}
|
||||||
|
} catch (e: Exception) {
|
||||||
|
logger.warn("获取 Safe 代理地址或资产失败: ${e.message}", e)
|
||||||
|
ProxyOptionDto(
|
||||||
|
walletType = "safe",
|
||||||
|
proxyAddress = "",
|
||||||
|
descriptionKey = "accountImport.proxyOption.safe.description",
|
||||||
|
availableBalance = "0",
|
||||||
|
positionBalance = "0",
|
||||||
|
totalBalance = "0",
|
||||||
|
positionCount = 0,
|
||||||
|
hasAssets = false,
|
||||||
|
error = "获取资产信息失败: ${e.message}"
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
val magicOption = magicDeferred.await()
|
||||||
|
val safeOption = safeDeferred.await()
|
||||||
|
// Safe 在前,Magic 在后
|
||||||
|
options.add(safeOption)
|
||||||
|
options.add(magicOption)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// 助记词导入:仅获取 Safe 代理地址及资产
|
||||||
|
try {
|
||||||
|
val proxyAddress = blockchainService.getProxyAddress(request.walletAddress, "safe").getOrNull()
|
||||||
|
if (proxyAddress != null) {
|
||||||
|
val balance = blockchainService.getWalletBalance(proxyAddress).getOrNull()
|
||||||
|
options.add(
|
||||||
|
ProxyOptionDto(
|
||||||
|
walletType = "safe",
|
||||||
|
proxyAddress = proxyAddress,
|
||||||
|
descriptionKey = "accountImport.proxyOption.safe.description",
|
||||||
|
availableBalance = balance?.availableBalance ?: "0",
|
||||||
|
positionBalance = balance?.positionBalance ?: "0",
|
||||||
|
totalBalance = balance?.totalBalance ?: "0",
|
||||||
|
positionCount = balance?.positions?.size ?: 0,
|
||||||
|
hasAssets = (balance?.availableBalance?.toSafeBigDecimal()?.gt(BigDecimal.ZERO) == true) ||
|
||||||
|
(balance?.positionBalance?.toSafeBigDecimal()?.gt(BigDecimal.ZERO) == true) ||
|
||||||
|
(balance?.positions?.isNotEmpty() == true),
|
||||||
|
error = null
|
||||||
|
)
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
options.add(
|
||||||
|
ProxyOptionDto(
|
||||||
|
walletType = "safe",
|
||||||
|
proxyAddress = "",
|
||||||
|
descriptionKey = "accountImport.proxyOption.safe.description",
|
||||||
|
availableBalance = "0",
|
||||||
|
positionBalance = "0",
|
||||||
|
totalBalance = "0",
|
||||||
|
positionCount = 0,
|
||||||
|
hasAssets = false,
|
||||||
|
error = "获取 Safe 代理地址失败"
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
} catch (e: Exception) {
|
||||||
|
logger.warn("获取 Safe 代理地址或资产失败: ${e.message}", e)
|
||||||
|
options.add(
|
||||||
|
ProxyOptionDto(
|
||||||
|
walletType = "safe",
|
||||||
|
proxyAddress = "",
|
||||||
|
descriptionKey = "accountImport.proxyOption.safe.description",
|
||||||
|
availableBalance = "0",
|
||||||
|
positionBalance = "0",
|
||||||
|
totalBalance = "0",
|
||||||
|
positionCount = 0,
|
||||||
|
hasAssets = false,
|
||||||
|
error = "获取资产信息失败: ${e.message}"
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Result.success(CheckProxyOptionsResponse(options = options))
|
||||||
|
} catch (e: Exception) {
|
||||||
|
logger.error("检查代理地址选项失败: ${e.message}", e)
|
||||||
|
Result.failure(e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 更新账户信息
|
* 更新账户信息
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { useState } from 'react'
|
import { useState, useEffect } from 'react'
|
||||||
import { Form, Input, Button, Radio, Space, Alert, Tooltip } from 'antd'
|
import { Form, Input, Button, Radio, Space, Alert, Card, Spin, message } from 'antd'
|
||||||
import { QuestionCircleOutlined } from '@ant-design/icons'
|
import { CheckCircleOutlined, ExclamationCircleOutlined } from '@ant-design/icons'
|
||||||
import { useTranslation } from 'react-i18next'
|
import { useTranslation } from 'react-i18next'
|
||||||
import { useAccountStore } from '../store/accountStore'
|
import { useAccountStore } from '../store/accountStore'
|
||||||
import {
|
import {
|
||||||
@@ -9,12 +9,14 @@ import {
|
|||||||
getPrivateKeyFromMnemonic,
|
getPrivateKeyFromMnemonic,
|
||||||
isValidWalletAddress,
|
isValidWalletAddress,
|
||||||
isValidPrivateKey,
|
isValidPrivateKey,
|
||||||
isValidMnemonic
|
isValidMnemonic,
|
||||||
|
formatUSDC
|
||||||
} from '../utils'
|
} from '../utils'
|
||||||
import { useMediaQuery } from 'react-responsive'
|
import { useMediaQuery } from 'react-responsive'
|
||||||
|
import { apiService } from '../services/api'
|
||||||
|
import type { ProxyOption } from '../types'
|
||||||
|
|
||||||
type ImportType = 'privateKey' | 'mnemonic'
|
type ImportType = 'privateKey' | 'mnemonic'
|
||||||
type WalletType = 'magic' | 'safe'
|
|
||||||
|
|
||||||
interface AccountImportFormProps {
|
interface AccountImportFormProps {
|
||||||
form: any
|
form: any
|
||||||
@@ -35,9 +37,12 @@ const AccountImportForm: React.FC<AccountImportFormProps> = ({
|
|||||||
const isMobile = useMediaQuery({ maxWidth: 768 })
|
const isMobile = useMediaQuery({ maxWidth: 768 })
|
||||||
const { importAccount, loading } = useAccountStore()
|
const { importAccount, loading } = useAccountStore()
|
||||||
const [importType, setImportType] = useState<ImportType>('privateKey')
|
const [importType, setImportType] = useState<ImportType>('privateKey')
|
||||||
const [walletType, setWalletType] = useState<WalletType>('safe')
|
|
||||||
const [derivedAddress, setDerivedAddress] = useState<string>('')
|
const [derivedAddress, setDerivedAddress] = useState<string>('')
|
||||||
const [addressError, setAddressError] = useState<string>('')
|
const [addressError, setAddressError] = useState<string>('')
|
||||||
|
const [proxyOptions, setProxyOptions] = useState<ProxyOption[]>([])
|
||||||
|
const [selectedProxyType, setSelectedProxyType] = useState<string>('')
|
||||||
|
const [loadingProxyOptions, setLoadingProxyOptions] = useState<boolean>(false)
|
||||||
|
const [step, setStep] = useState<'input' | 'select'>('input') // 步骤:输入 -> 选择代理地址
|
||||||
|
|
||||||
// 当私钥输入时,自动推导地址
|
// 当私钥输入时,自动推导地址
|
||||||
const handlePrivateKeyChange = (e: React.ChangeEvent<HTMLTextAreaElement>) => {
|
const handlePrivateKeyChange = (e: React.ChangeEvent<HTMLTextAreaElement>) => {
|
||||||
@@ -45,6 +50,9 @@ const AccountImportForm: React.FC<AccountImportFormProps> = ({
|
|||||||
if (!privateKey) {
|
if (!privateKey) {
|
||||||
setDerivedAddress('')
|
setDerivedAddress('')
|
||||||
setAddressError('')
|
setAddressError('')
|
||||||
|
setProxyOptions([])
|
||||||
|
setSelectedProxyType('')
|
||||||
|
setStep('input')
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -52,6 +60,9 @@ const AccountImportForm: React.FC<AccountImportFormProps> = ({
|
|||||||
if (!isValidPrivateKey(privateKey)) {
|
if (!isValidPrivateKey(privateKey)) {
|
||||||
setAddressError(t('accountImport.privateKeyInvalid'))
|
setAddressError(t('accountImport.privateKeyInvalid'))
|
||||||
setDerivedAddress('')
|
setDerivedAddress('')
|
||||||
|
setProxyOptions([])
|
||||||
|
setSelectedProxyType('')
|
||||||
|
setStep('input')
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -62,9 +73,17 @@ const AccountImportForm: React.FC<AccountImportFormProps> = ({
|
|||||||
|
|
||||||
// 自动填充钱包地址字段
|
// 自动填充钱包地址字段
|
||||||
form.setFieldsValue({ walletAddress: address })
|
form.setFieldsValue({ walletAddress: address })
|
||||||
|
|
||||||
|
// 延迟获取代理选项(避免频繁请求)
|
||||||
|
setTimeout(() => {
|
||||||
|
fetchProxyOptions(address, privateKey, null)
|
||||||
|
}, 500)
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
setAddressError(error.message || t('accountImport.addressError'))
|
setAddressError(error.message || t('accountImport.addressError'))
|
||||||
setDerivedAddress('')
|
setDerivedAddress('')
|
||||||
|
setProxyOptions([])
|
||||||
|
setSelectedProxyType('')
|
||||||
|
setStep('input')
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -74,6 +93,9 @@ const AccountImportForm: React.FC<AccountImportFormProps> = ({
|
|||||||
if (!mnemonic) {
|
if (!mnemonic) {
|
||||||
setDerivedAddress('')
|
setDerivedAddress('')
|
||||||
setAddressError('')
|
setAddressError('')
|
||||||
|
setProxyOptions([])
|
||||||
|
setSelectedProxyType('')
|
||||||
|
setStep('input')
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -81,6 +103,9 @@ const AccountImportForm: React.FC<AccountImportFormProps> = ({
|
|||||||
if (!isValidMnemonic(mnemonic)) {
|
if (!isValidMnemonic(mnemonic)) {
|
||||||
setAddressError(t('accountImport.mnemonicInvalid'))
|
setAddressError(t('accountImport.mnemonicInvalid'))
|
||||||
setDerivedAddress('')
|
setDerivedAddress('')
|
||||||
|
setProxyOptions([])
|
||||||
|
setSelectedProxyType('')
|
||||||
|
setStep('input')
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -91,14 +116,85 @@ const AccountImportForm: React.FC<AccountImportFormProps> = ({
|
|||||||
|
|
||||||
// 自动填充钱包地址字段
|
// 自动填充钱包地址字段
|
||||||
form.setFieldsValue({ walletAddress: address })
|
form.setFieldsValue({ walletAddress: address })
|
||||||
|
|
||||||
|
// 延迟获取代理选项(避免频繁请求)
|
||||||
|
setTimeout(() => {
|
||||||
|
fetchProxyOptions(address, null, mnemonic)
|
||||||
|
}, 500)
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
setAddressError(error.message || t('accountImport.addressErrorMnemonic'))
|
setAddressError(error.message || t('accountImport.addressErrorMnemonic'))
|
||||||
setDerivedAddress('')
|
setDerivedAddress('')
|
||||||
|
setProxyOptions([])
|
||||||
|
setSelectedProxyType('')
|
||||||
|
setStep('input')
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 获取代理地址选项
|
||||||
|
const fetchProxyOptions = async (walletAddress: string, privateKey: string | null, mnemonic: string | null) => {
|
||||||
|
if (!walletAddress || (!privateKey && !mnemonic)) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
setLoadingProxyOptions(true)
|
||||||
|
try {
|
||||||
|
const response = await apiService.accounts.checkProxyOptions({
|
||||||
|
walletAddress,
|
||||||
|
privateKey: privateKey || undefined,
|
||||||
|
mnemonic: mnemonic || undefined
|
||||||
|
})
|
||||||
|
|
||||||
|
if (response.data.code === 0 && response.data.data) {
|
||||||
|
const options = response.data.data.options || []
|
||||||
|
setProxyOptions(options)
|
||||||
|
|
||||||
|
// 如果有选项,进入选择步骤
|
||||||
|
if (options.length > 0) {
|
||||||
|
setStep('select')
|
||||||
|
// 如果有资产,默认选择第一个有资产的选项
|
||||||
|
const hasAssetsOption = options.find(opt => opt.hasAssets)
|
||||||
|
if (hasAssetsOption) {
|
||||||
|
setSelectedProxyType(hasAssetsOption.walletType)
|
||||||
|
} else {
|
||||||
|
// 否则选择第一个选项
|
||||||
|
setSelectedProxyType(options[0].walletType)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
setStep('input')
|
||||||
|
message.warning(t('accountImport.proxyOption.error') || '未获取到代理地址选项')
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
setProxyOptions([])
|
||||||
|
setStep('input')
|
||||||
|
message.error(response.data.msg || '获取代理地址选项失败')
|
||||||
|
}
|
||||||
|
} catch (error: any) {
|
||||||
|
setProxyOptions([])
|
||||||
|
setStep('input')
|
||||||
|
message.error(error.message || '获取代理地址选项失败')
|
||||||
|
} finally {
|
||||||
|
setLoadingProxyOptions(false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 切换导入方式时重置状态
|
||||||
|
useEffect(() => {
|
||||||
|
setDerivedAddress('')
|
||||||
|
setAddressError('')
|
||||||
|
setProxyOptions([])
|
||||||
|
setSelectedProxyType('')
|
||||||
|
setStep('input')
|
||||||
|
form.setFieldsValue({ walletAddress: '', privateKey: '', mnemonic: '' })
|
||||||
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
|
}, [importType])
|
||||||
|
|
||||||
const handleSubmit = async (values: any) => {
|
const handleSubmit = async (values: any) => {
|
||||||
try {
|
try {
|
||||||
|
// 如果还在输入步骤,需要先选择代理地址
|
||||||
|
if (step === 'input' || !selectedProxyType) {
|
||||||
|
return Promise.reject(new Error(t('accountImport.proxyOptionRequired')))
|
||||||
|
}
|
||||||
|
|
||||||
let privateKey: string
|
let privateKey: string
|
||||||
let walletAddress: string
|
let walletAddress: string
|
||||||
|
|
||||||
@@ -124,14 +220,11 @@ const AccountImportForm: React.FC<AccountImportFormProps> = ({
|
|||||||
// 如果用户手动输入了地址,验证是否与推导的地址一致
|
// 如果用户手动输入了地址,验证是否与推导的地址一致
|
||||||
if (values.walletAddress) {
|
if (values.walletAddress) {
|
||||||
if (values.walletAddress !== derivedAddressFromMnemonic) {
|
if (values.walletAddress !== derivedAddressFromMnemonic) {
|
||||||
// 地址不匹配,使用推导的地址(因为私钥是从助记词导出的,必须使用对应的地址)
|
|
||||||
walletAddress = derivedAddressFromMnemonic
|
walletAddress = derivedAddressFromMnemonic
|
||||||
} else {
|
} else {
|
||||||
// 地址匹配,使用用户输入的地址
|
|
||||||
walletAddress = values.walletAddress
|
walletAddress = values.walletAddress
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// 如果用户没有输入地址,使用推导的地址
|
|
||||||
walletAddress = derivedAddressFromMnemonic
|
walletAddress = derivedAddressFromMnemonic
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -145,14 +238,13 @@ const AccountImportForm: React.FC<AccountImportFormProps> = ({
|
|||||||
privateKey: privateKey,
|
privateKey: privateKey,
|
||||||
walletAddress: walletAddress,
|
walletAddress: walletAddress,
|
||||||
accountName: values.accountName,
|
accountName: values.accountName,
|
||||||
walletType: walletType
|
walletType: selectedProxyType
|
||||||
})
|
})
|
||||||
|
|
||||||
// 等待store更新
|
// 等待store更新
|
||||||
await new Promise(resolve => setTimeout(resolve, 100))
|
await new Promise(resolve => setTimeout(resolve, 100))
|
||||||
|
|
||||||
// 获取新添加的账户ID(通过API获取,因为store可能还没更新)
|
// 获取新添加的账户ID(通过API获取,因为store可能还没更新)
|
||||||
const { apiService } = await import('../services/api')
|
|
||||||
const accountsResponse = await apiService.accounts.list()
|
const accountsResponse = await apiService.accounts.list()
|
||||||
if (accountsResponse.data.code === 0 && accountsResponse.data.data) {
|
if (accountsResponse.data.code === 0 && accountsResponse.data.data) {
|
||||||
const newAccounts = accountsResponse.data.data.list || []
|
const newAccounts = accountsResponse.data.data.list || []
|
||||||
@@ -160,11 +252,9 @@ const AccountImportForm: React.FC<AccountImportFormProps> = ({
|
|||||||
if (newAccount && onSuccess) {
|
if (newAccount && onSuccess) {
|
||||||
onSuccess(newAccount.id)
|
onSuccess(newAccount.id)
|
||||||
} else if (onSuccess) {
|
} else if (onSuccess) {
|
||||||
// 如果找不到账户,仍然调用onSuccess(可能在其他地方处理)
|
|
||||||
onSuccess(0)
|
onSuccess(0)
|
||||||
}
|
}
|
||||||
} else if (onSuccess) {
|
} else if (onSuccess) {
|
||||||
// API调用失败,仍然调用onSuccess
|
|
||||||
onSuccess(0)
|
onSuccess(0)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -197,9 +287,6 @@ const AccountImportForm: React.FC<AccountImportFormProps> = ({
|
|||||||
value={importType}
|
value={importType}
|
||||||
onChange={(e) => {
|
onChange={(e) => {
|
||||||
setImportType(e.target.value)
|
setImportType(e.target.value)
|
||||||
setDerivedAddress('')
|
|
||||||
setAddressError('')
|
|
||||||
form.setFieldsValue({ walletAddress: '' })
|
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Radio value="privateKey">{t('accountImport.privateKey')}</Radio>
|
<Radio value="privateKey">{t('accountImport.privateKey')}</Radio>
|
||||||
@@ -207,32 +294,6 @@ const AccountImportForm: React.FC<AccountImportFormProps> = ({
|
|||||||
</Radio.Group>
|
</Radio.Group>
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
|
|
||||||
<Form.Item
|
|
||||||
label={
|
|
||||||
<span>
|
|
||||||
{t('accountImport.walletType')}{' '}
|
|
||||||
<Tooltip
|
|
||||||
title={t('accountImport.walletTypeHelp')}
|
|
||||||
overlayInnerStyle={{ whiteSpace: 'pre-line', maxWidth: '300px' }}
|
|
||||||
>
|
|
||||||
<QuestionCircleOutlined style={{ color: '#999' }} />
|
|
||||||
</Tooltip>
|
|
||||||
</span>
|
|
||||||
}
|
|
||||||
>
|
|
||||||
<Radio.Group
|
|
||||||
value={walletType}
|
|
||||||
onChange={(e) => setWalletType(e.target.value)}
|
|
||||||
>
|
|
||||||
<Radio value="safe">
|
|
||||||
{t('accountImport.walletTypeSafe')}
|
|
||||||
</Radio>
|
|
||||||
<Radio value="magic">
|
|
||||||
{t('accountImport.walletTypeMagic')}
|
|
||||||
</Radio>
|
|
||||||
</Radio.Group>
|
|
||||||
</Form.Item>
|
|
||||||
|
|
||||||
{importType === 'privateKey' ? (
|
{importType === 'privateKey' ? (
|
||||||
<>
|
<>
|
||||||
<Form.Item
|
<Form.Item
|
||||||
@@ -250,13 +311,14 @@ const AccountImportForm: React.FC<AccountImportFormProps> = ({
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
]}
|
]}
|
||||||
help={addressError || (derivedAddress ? `${t('accountImport.derivedAddress')}: ${derivedAddress}` : '')}
|
help={addressError || ''}
|
||||||
validateStatus={addressError ? 'error' : derivedAddress ? 'success' : ''}
|
validateStatus={addressError ? 'error' : derivedAddress ? 'success' : ''}
|
||||||
>
|
>
|
||||||
<Input.TextArea
|
<Input.TextArea
|
||||||
rows={3}
|
rows={3}
|
||||||
placeholder={t('accountImport.privateKeyPlaceholder')}
|
placeholder={t('accountImport.privateKeyPlaceholder')}
|
||||||
onChange={handlePrivateKeyChange}
|
onChange={handlePrivateKeyChange}
|
||||||
|
disabled={loadingProxyOptions}
|
||||||
/>
|
/>
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
|
|
||||||
@@ -282,6 +344,7 @@ const AccountImportForm: React.FC<AccountImportFormProps> = ({
|
|||||||
<Input
|
<Input
|
||||||
placeholder={t('accountImport.walletAddressPlaceholder')}
|
placeholder={t('accountImport.walletAddressPlaceholder')}
|
||||||
readOnly={!!derivedAddress}
|
readOnly={!!derivedAddress}
|
||||||
|
disabled={loadingProxyOptions}
|
||||||
/>
|
/>
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
</>
|
</>
|
||||||
@@ -302,13 +365,14 @@ const AccountImportForm: React.FC<AccountImportFormProps> = ({
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
]}
|
]}
|
||||||
help={addressError || (derivedAddress ? `${t('accountImport.derivedAddress')}: ${derivedAddress}` : '')}
|
help={addressError || ''}
|
||||||
validateStatus={addressError ? 'error' : derivedAddress ? 'success' : ''}
|
validateStatus={addressError ? 'error' : derivedAddress ? 'success' : ''}
|
||||||
>
|
>
|
||||||
<Input.TextArea
|
<Input.TextArea
|
||||||
rows={4}
|
rows={4}
|
||||||
placeholder={t('accountImport.mnemonicPlaceholder')}
|
placeholder={t('accountImport.mnemonicPlaceholder')}
|
||||||
onChange={handleMnemonicChange}
|
onChange={handleMnemonicChange}
|
||||||
|
disabled={loadingProxyOptions}
|
||||||
/>
|
/>
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
|
|
||||||
@@ -334,11 +398,113 @@ const AccountImportForm: React.FC<AccountImportFormProps> = ({
|
|||||||
<Input
|
<Input
|
||||||
placeholder={t('accountImport.walletAddressPlaceholder')}
|
placeholder={t('accountImport.walletAddressPlaceholder')}
|
||||||
readOnly={!!derivedAddress}
|
readOnly={!!derivedAddress}
|
||||||
|
disabled={loadingProxyOptions}
|
||||||
/>
|
/>
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
{/* 请求代理地址时的 loading 提示 */}
|
||||||
|
{loadingProxyOptions && step === 'input' && (
|
||||||
|
<Form.Item>
|
||||||
|
<Alert
|
||||||
|
message={
|
||||||
|
<div style={{ display: 'flex', alignItems: 'center', gap: '8px' }}>
|
||||||
|
<Spin size="small" />
|
||||||
|
<span>{t('accountImport.loadingProxyOptions')}</span>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
type="info"
|
||||||
|
showIcon={false}
|
||||||
|
style={{ marginBottom: '16px' }}
|
||||||
|
/>
|
||||||
|
</Form.Item>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{/* 代理地址选项选择 */}
|
||||||
|
{step === 'select' && (
|
||||||
|
<Form.Item
|
||||||
|
label={t('accountImport.selectProxyOption')}
|
||||||
|
required
|
||||||
|
rules={[
|
||||||
|
{
|
||||||
|
validator: () => {
|
||||||
|
if (!selectedProxyType) {
|
||||||
|
return Promise.reject(new Error(t('accountImport.proxyOptionRequired')))
|
||||||
|
}
|
||||||
|
return Promise.resolve()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]}
|
||||||
|
>
|
||||||
|
{loadingProxyOptions ? (
|
||||||
|
<Spin tip={t('accountImport.loadingProxyOptions')} />
|
||||||
|
) : (
|
||||||
|
<Space direction="vertical" style={{ width: '100%' }} size="middle">
|
||||||
|
{proxyOptions.map((option) => (
|
||||||
|
<Card
|
||||||
|
key={option.walletType}
|
||||||
|
hoverable
|
||||||
|
onClick={() => setSelectedProxyType(option.walletType)}
|
||||||
|
style={{
|
||||||
|
cursor: 'pointer',
|
||||||
|
border: selectedProxyType === option.walletType ? '2px solid #1890ff' : '1px solid #d9d9d9',
|
||||||
|
backgroundColor: selectedProxyType === option.walletType ? '#e6f7ff' : '#fff'
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'flex-start' }}>
|
||||||
|
<div style={{ flex: 1 }}>
|
||||||
|
<div style={{ display: 'flex', alignItems: 'center', marginBottom: '8px' }}>
|
||||||
|
<Radio checked={selectedProxyType === option.walletType} />
|
||||||
|
<strong style={{ marginLeft: '8px' }}>
|
||||||
|
{t(`accountImport.proxyOption.${option.walletType}.title`)}
|
||||||
|
</strong>
|
||||||
|
{option.hasAssets && (
|
||||||
|
<span style={{ marginLeft: '8px', color: '#52c41a' }}>
|
||||||
|
<CheckCircleOutlined /> {t('accountImport.proxyOption.hasAssets')}
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
|
{option.error && (
|
||||||
|
<span style={{ marginLeft: '8px', color: '#ff4d4f' }}>
|
||||||
|
<ExclamationCircleOutlined /> {t('accountImport.proxyOption.error')}
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
<div style={{ marginLeft: '24px', color: '#666', fontSize: '14px', marginBottom: '8px' }}>
|
||||||
|
{t(`accountImport.proxyOption.${option.walletType}.description`)}
|
||||||
|
</div>
|
||||||
|
<div style={{ marginLeft: '24px', fontSize: '12px', color: '#999', marginBottom: '4px' }}>
|
||||||
|
{t('accountImport.proxyOption.proxyAddress')}: {option.proxyAddress || '-'}
|
||||||
|
</div>
|
||||||
|
{option.error ? (
|
||||||
|
<div style={{ marginLeft: '24px', color: '#ff4d4f', fontSize: '12px' }}>
|
||||||
|
{option.error}
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
<div style={{ marginLeft: '24px', display: 'flex', gap: '16px', fontSize: '12px', color: '#666' }}>
|
||||||
|
<span>
|
||||||
|
{t('accountImport.proxyOption.availableBalance')}: {formatUSDC(option.availableBalance)} USDC
|
||||||
|
</span>
|
||||||
|
<span>
|
||||||
|
{t('accountImport.proxyOption.positionBalance')}: {formatUSDC(option.positionBalance)} USDC
|
||||||
|
</span>
|
||||||
|
<span>
|
||||||
|
{t('accountImport.proxyOption.totalBalance')}: {formatUSDC(option.totalBalance)} USDC
|
||||||
|
</span>
|
||||||
|
<span>
|
||||||
|
{t('accountImport.proxyOption.positionCount')}: {option.positionCount}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</Card>
|
||||||
|
))}
|
||||||
|
</Space>
|
||||||
|
)}
|
||||||
|
</Form.Item>
|
||||||
|
)}
|
||||||
|
|
||||||
<Form.Item
|
<Form.Item
|
||||||
label={t('accountImport.accountName')}
|
label={t('accountImport.accountName')}
|
||||||
name="accountName"
|
name="accountName"
|
||||||
@@ -352,6 +518,7 @@ const AccountImportForm: React.FC<AccountImportFormProps> = ({
|
|||||||
type="primary"
|
type="primary"
|
||||||
htmlType="submit"
|
htmlType="submit"
|
||||||
loading={loading}
|
loading={loading}
|
||||||
|
disabled={step !== 'select' || !selectedProxyType || loadingProxyOptions}
|
||||||
size={isMobile ? 'middle' : 'large'}
|
size={isMobile ? 'middle' : 'large'}
|
||||||
>
|
>
|
||||||
{t('accountImport.importAccount')}
|
{t('accountImport.importAccount')}
|
||||||
@@ -369,4 +536,3 @@ const AccountImportForm: React.FC<AccountImportFormProps> = ({
|
|||||||
}
|
}
|
||||||
|
|
||||||
export default AccountImportForm
|
export default AccountImportForm
|
||||||
|
|
||||||
|
|||||||
@@ -215,7 +215,29 @@
|
|||||||
"walletTypeHelp": "Web3 Wallet: Polymarket accounts connected via browser wallets like MetaMask\nMagic: Polymarket accounts logged in via email or social accounts (Google, Twitter, etc.)",
|
"walletTypeHelp": "Web3 Wallet: Polymarket accounts connected via browser wallets like MetaMask\nMagic: Polymarket accounts logged in via email or social accounts (Google, Twitter, etc.)",
|
||||||
"walletTypeMagic": "Magic (Email/Social Login)",
|
"walletTypeMagic": "Magic (Email/Social Login)",
|
||||||
"walletTypeSafe": "Web3 Wallet",
|
"walletTypeSafe": "Web3 Wallet",
|
||||||
"magicNotSupported": ""
|
"magicNotSupported": "",
|
||||||
|
"loadingProxyOptions": "Loading proxy addresses and asset information...",
|
||||||
|
"selectProxyOption": "Please select a proxy address",
|
||||||
|
"proxyOptionRequired": "Please select a proxy address",
|
||||||
|
"proxyOption": {
|
||||||
|
"magic": {
|
||||||
|
"description": "Email/Social Login Account (Magic)",
|
||||||
|
"title": "Magic Proxy Address"
|
||||||
|
},
|
||||||
|
"safe": {
|
||||||
|
"description": "MetaMask Browser Wallet Account (Safe)",
|
||||||
|
"title": "Safe Proxy Address"
|
||||||
|
},
|
||||||
|
"proxyAddress": "Proxy Address",
|
||||||
|
"availableBalance": "Available Balance",
|
||||||
|
"positionBalance": "Position Balance",
|
||||||
|
"totalBalance": "Total Balance",
|
||||||
|
"positionCount": "Position Count",
|
||||||
|
"noAssets": "No Assets",
|
||||||
|
"hasAssets": "Has Assets",
|
||||||
|
"error": "Failed to fetch",
|
||||||
|
"select": "Select this proxy address"
|
||||||
|
}
|
||||||
},
|
},
|
||||||
"leader": {
|
"leader": {
|
||||||
"title": "Leader Management",
|
"title": "Leader Management",
|
||||||
|
|||||||
@@ -215,7 +215,29 @@
|
|||||||
"walletTypeHelp": "Web3钱包:使用 MetaMask 等浏览器钱包连接的 Polymarket 账户\nMagic:通过邮箱或社交账号(如 Google、Twitter)登录的 Polymarket 账户",
|
"walletTypeHelp": "Web3钱包:使用 MetaMask 等浏览器钱包连接的 Polymarket 账户\nMagic:通过邮箱或社交账号(如 Google、Twitter)登录的 Polymarket 账户",
|
||||||
"walletTypeMagic": "Magic(邮箱/社交账号登录)",
|
"walletTypeMagic": "Magic(邮箱/社交账号登录)",
|
||||||
"walletTypeSafe": "Web3钱包",
|
"walletTypeSafe": "Web3钱包",
|
||||||
"magicNotSupported": ""
|
"magicNotSupported": "",
|
||||||
|
"loadingProxyOptions": "正在获取代理地址和资产信息...",
|
||||||
|
"selectProxyOption": "请选择代理地址",
|
||||||
|
"proxyOptionRequired": "请选择一个代理地址",
|
||||||
|
"proxyOption": {
|
||||||
|
"magic": {
|
||||||
|
"description": "邮箱/社交账号登录账户(Magic)",
|
||||||
|
"title": "Magic 代理地址"
|
||||||
|
},
|
||||||
|
"safe": {
|
||||||
|
"description": "MetaMask 等浏览器钱包账户(Safe)",
|
||||||
|
"title": "Safe 代理地址"
|
||||||
|
},
|
||||||
|
"proxyAddress": "代理地址",
|
||||||
|
"availableBalance": "可用余额",
|
||||||
|
"positionBalance": "仓位余额",
|
||||||
|
"totalBalance": "总余额",
|
||||||
|
"positionCount": "持仓数量",
|
||||||
|
"noAssets": "无资产",
|
||||||
|
"hasAssets": "有资产",
|
||||||
|
"error": "获取失败",
|
||||||
|
"select": "选择此代理地址"
|
||||||
|
}
|
||||||
},
|
},
|
||||||
"leader": {
|
"leader": {
|
||||||
"title": "Leader 管理",
|
"title": "Leader 管理",
|
||||||
|
|||||||
@@ -215,7 +215,29 @@
|
|||||||
"walletTypeHelp": "Web3錢包:使用 MetaMask 等瀏覽器錢包連接的 Polymarket 帳戶\nMagic:透過郵箱或社群帳號(如 Google、Twitter)登入的 Polymarket 帳戶",
|
"walletTypeHelp": "Web3錢包:使用 MetaMask 等瀏覽器錢包連接的 Polymarket 帳戶\nMagic:透過郵箱或社群帳號(如 Google、Twitter)登入的 Polymarket 帳戶",
|
||||||
"walletTypeMagic": "Magic(郵箱/社群帳號登入)",
|
"walletTypeMagic": "Magic(郵箱/社群帳號登入)",
|
||||||
"walletTypeSafe": "Web3錢包",
|
"walletTypeSafe": "Web3錢包",
|
||||||
"magicNotSupported": ""
|
"magicNotSupported": "",
|
||||||
|
"loadingProxyOptions": "正在獲取代理地址和資產信息...",
|
||||||
|
"selectProxyOption": "請選擇代理地址",
|
||||||
|
"proxyOptionRequired": "請選擇一個代理地址",
|
||||||
|
"proxyOption": {
|
||||||
|
"magic": {
|
||||||
|
"description": "郵箱/社群帳號登入帳戶(Magic)",
|
||||||
|
"title": "Magic 代理地址"
|
||||||
|
},
|
||||||
|
"safe": {
|
||||||
|
"description": "MetaMask 等瀏覽器錢包帳戶(Safe)",
|
||||||
|
"title": "Safe 代理地址"
|
||||||
|
},
|
||||||
|
"proxyAddress": "代理地址",
|
||||||
|
"availableBalance": "可用餘額",
|
||||||
|
"positionBalance": "倉位餘額",
|
||||||
|
"totalBalance": "總餘額",
|
||||||
|
"positionCount": "持倉數量",
|
||||||
|
"noAssets": "無資產",
|
||||||
|
"hasAssets": "有資產",
|
||||||
|
"error": "獲取失敗",
|
||||||
|
"select": "選擇此代理地址"
|
||||||
|
}
|
||||||
},
|
},
|
||||||
"leader": {
|
"leader": {
|
||||||
"title": "Leader 管理",
|
"title": "Leader 管理",
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { useEffect, useState, useMemo } from 'react'
|
import { useEffect, useState, useMemo, useRef } from 'react'
|
||||||
import { Card, Table, Tag, message, Space, Input, Radio, Select, Button, Row, Col, Empty, Modal, Form, Descriptions } from 'antd'
|
import { Card, Table, Tag, message, Space, Input, Radio, Select, Button, Row, Col, Empty, Modal, Form, Descriptions } from 'antd'
|
||||||
import { SearchOutlined, AppstoreOutlined, UnorderedListOutlined, UpOutlined, DownOutlined } from '@ant-design/icons'
|
import { SearchOutlined, AppstoreOutlined, UnorderedListOutlined, UpOutlined, DownOutlined } from '@ant-design/icons'
|
||||||
import { useNavigate } from 'react-router-dom'
|
import { useNavigate } from 'react-router-dom'
|
||||||
@@ -62,10 +62,10 @@ const PositionList: React.FC = () => {
|
|||||||
}
|
}
|
||||||
}, [])
|
}, [])
|
||||||
|
|
||||||
// 当仓位数据变化时,更新可赎回统计
|
// 当仓位数据变化时,静默更新可赎回统计(不显示loading状态)
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (currentPositions.length > 0) {
|
if (currentPositions.length > 0) {
|
||||||
fetchRedeemableSummary()
|
fetchRedeemableSummarySilently()
|
||||||
}
|
}
|
||||||
}, [currentPositions, selectedAccountId])
|
}, [currentPositions, selectedAccountId])
|
||||||
|
|
||||||
@@ -74,7 +74,19 @@ const PositionList: React.FC = () => {
|
|||||||
setCurrentPage(1)
|
setCurrentPage(1)
|
||||||
}, [positionFilter, selectedAccountId, searchKeyword])
|
}, [positionFilter, selectedAccountId, searchKeyword])
|
||||||
|
|
||||||
// 获取可赎回仓位统计
|
// 静默获取可赎回仓位统计(不显示loading状态)
|
||||||
|
const fetchRedeemableSummarySilently = async () => {
|
||||||
|
try {
|
||||||
|
const response = await apiService.accounts.getRedeemableSummary({ accountId: selectedAccountId })
|
||||||
|
if (response.data.code === 0 && response.data.data) {
|
||||||
|
setRedeemableSummary(response.data.data)
|
||||||
|
}
|
||||||
|
} catch (error: any) {
|
||||||
|
console.error('获取可赎回统计失败:', error)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取可赎回仓位统计(带loading状态,用于用户主动操作)
|
||||||
const fetchRedeemableSummary = async () => {
|
const fetchRedeemableSummary = async () => {
|
||||||
setLoadingRedeemableSummary(true)
|
setLoadingRedeemableSummary(true)
|
||||||
try {
|
try {
|
||||||
@@ -91,8 +103,9 @@ const PositionList: React.FC = () => {
|
|||||||
|
|
||||||
// 处理赎回按钮点击
|
// 处理赎回按钮点击
|
||||||
const handleRedeemClick = async () => {
|
const handleRedeemClick = async () => {
|
||||||
await fetchRedeemableSummary()
|
|
||||||
setRedeemModalVisible(true)
|
setRedeemModalVisible(true)
|
||||||
|
// 打开模态框时重新获取最新数据
|
||||||
|
fetchRedeemableSummary()
|
||||||
}
|
}
|
||||||
|
|
||||||
// 提交赎回
|
// 提交赎回
|
||||||
|
|||||||
@@ -206,6 +206,12 @@ export const apiService = {
|
|||||||
* 账户管理 API
|
* 账户管理 API
|
||||||
*/
|
*/
|
||||||
accounts: {
|
accounts: {
|
||||||
|
/**
|
||||||
|
* 检查代理地址选项(导入前选择代理类型)
|
||||||
|
*/
|
||||||
|
checkProxyOptions: (data: any) =>
|
||||||
|
apiClient.post<ApiResponse<any>>('/accounts/check-proxy-options', data),
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 导入账户
|
* 导入账户
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -46,6 +46,37 @@ export interface AccountImportRequest {
|
|||||||
walletType?: string // 钱包类型:magic(邮箱/OAuth登录)或 safe(MetaMask浏览器钱包)
|
walletType?: string // 钱包类型:magic(邮箱/OAuth登录)或 safe(MetaMask浏览器钱包)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 检查代理地址选项请求
|
||||||
|
*/
|
||||||
|
export interface CheckProxyOptionsRequest {
|
||||||
|
walletAddress: string // EOA 地址
|
||||||
|
privateKey?: string // 私钥(加密,私钥导入时提供)
|
||||||
|
mnemonic?: string // 助记词(加密,助记词导入时提供)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 代理地址选项信息
|
||||||
|
*/
|
||||||
|
export interface ProxyOption {
|
||||||
|
walletType: string // "magic" 或 "safe"
|
||||||
|
proxyAddress: string // 代理地址
|
||||||
|
descriptionKey: string // 说明文案的多语言 key
|
||||||
|
availableBalance: string // 可用余额
|
||||||
|
positionBalance: string // 仓位余额
|
||||||
|
totalBalance: string // 总余额
|
||||||
|
positionCount: number // 持仓数量
|
||||||
|
hasAssets: boolean // 是否有资产
|
||||||
|
error?: string // 获取失败时的错误信息
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 检查代理地址选项响应
|
||||||
|
*/
|
||||||
|
export interface CheckProxyOptionsResponse {
|
||||||
|
options: ProxyOption[] // 代理地址选项列表
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 账户更新请求
|
* 账户更新请求
|
||||||
*/
|
*/
|
||||||
|
|||||||
Reference in New Issue
Block a user