feat: 优化账户管理和订单功能
- 重置密码后自动跳转到登录页,登录页添加重置密码入口 - 移除默认账户相关逻辑和UI - 移除前端API Key自动获取提示文案(后端保留自动获取逻辑) - 修复SELL订单takerAmount计算精度问题,使用精确计算避免精度丢失 - 修复导入账户页面白屏问题(缺少Alert组件导入) - 持仓出售支持百分比模式,使用原始精度数据计算,支持小数百分比 - 优化订单金额计算逻辑,确保与SDK一致
This commit is contained in:
@@ -74,7 +74,6 @@ const AccountDetail: React.FC = () => {
|
||||
const updateData: any = {
|
||||
accountId: account.id,
|
||||
accountName: values.accountName || undefined,
|
||||
isDefault: values.isDefault || false
|
||||
}
|
||||
|
||||
// 只有非空字符串才更新 API 凭证
|
||||
@@ -163,8 +162,7 @@ const AccountDetail: React.FC = () => {
|
||||
accountName: account.accountName || '',
|
||||
apiKey: '', // 不显示实际值,留空表示不修改
|
||||
apiSecret: '', // 不显示实际值,留空表示不修改
|
||||
apiPassphrase: '', // 不显示实际值,留空表示不修改
|
||||
isDefault: account.isDefault || false
|
||||
apiPassphrase: '' // 不显示实际值,留空表示不修改
|
||||
})
|
||||
}}
|
||||
size={isMobile ? 'middle' : 'large'}
|
||||
@@ -203,11 +201,6 @@ const AccountDetail: React.FC = () => {
|
||||
{account.walletAddress}
|
||||
</span>
|
||||
</Descriptions.Item>
|
||||
<Descriptions.Item label="默认账户">
|
||||
<Tag color={account.isDefault ? 'gold' : 'default'}>
|
||||
{account.isDefault ? '是' : '否'}
|
||||
</Tag>
|
||||
</Descriptions.Item>
|
||||
<Descriptions.Item label="账户余额">
|
||||
{balanceLoading ? (
|
||||
<Spin size="small" />
|
||||
@@ -378,13 +371,6 @@ const AccountDetail: React.FC = () => {
|
||||
<Input.Password placeholder="留空表示不修改" />
|
||||
</Form.Item>
|
||||
|
||||
<Form.Item
|
||||
name="isDefault"
|
||||
valuePropName="checked"
|
||||
>
|
||||
<Checkbox>设为默认账户</Checkbox>
|
||||
</Form.Item>
|
||||
|
||||
<Form.Item>
|
||||
<Space style={{ width: '100%', justifyContent: 'flex-end' }}>
|
||||
<Button
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { useEffect, useState } from 'react'
|
||||
import { useNavigate, useSearchParams } from 'react-router-dom'
|
||||
import { Card, Form, Input, Button, message, Typography, Space, Alert, Checkbox } from 'antd'
|
||||
import { Card, Form, Input, Button, message, Typography, Space } from 'antd'
|
||||
import { ArrowLeftOutlined } from '@ant-design/icons'
|
||||
import { useAccountStore } from '../store/accountStore'
|
||||
import { useMediaQuery } from 'react-responsive'
|
||||
@@ -37,8 +37,7 @@ const AccountEdit: React.FC = () => {
|
||||
|
||||
// 设置表单初始值
|
||||
form.setFieldsValue({
|
||||
accountName: accountData.accountName || '',
|
||||
isDefault: accountData.isDefault || false
|
||||
accountName: accountData.accountName || ''
|
||||
})
|
||||
} catch (error: any) {
|
||||
message.error(error.message || '获取账户详情失败')
|
||||
@@ -55,8 +54,7 @@ const AccountEdit: React.FC = () => {
|
||||
// 构建更新请求
|
||||
const updateData: any = {
|
||||
accountId: Number(accountId),
|
||||
accountName: values.accountName || undefined,
|
||||
isDefault: values.isDefault || false
|
||||
accountName: values.accountName || undefined
|
||||
}
|
||||
|
||||
await updateAccount(updateData)
|
||||
@@ -119,20 +117,6 @@ const AccountEdit: React.FC = () => {
|
||||
<Input placeholder="账户名称(可选)" />
|
||||
</Form.Item>
|
||||
|
||||
<Alert
|
||||
message="API Key 管理"
|
||||
description="API Key 由系统自动管理,无需手动更新。如需重新获取 API Key,请删除并重新导入账户。"
|
||||
type="info"
|
||||
showIcon
|
||||
style={{ marginBottom: '24px' }}
|
||||
/>
|
||||
|
||||
<Form.Item
|
||||
name="isDefault"
|
||||
valuePropName="checked"
|
||||
>
|
||||
<Checkbox>设为默认账户</Checkbox>
|
||||
</Form.Item>
|
||||
|
||||
<Form.Item>
|
||||
<Space>
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { useState } from 'react'
|
||||
import { useNavigate } from 'react-router-dom'
|
||||
import { Card, Form, Input, Button, message, Typography, Radio, Space, Alert, Checkbox } from 'antd'
|
||||
import { Card, Form, Input, Button, message, Typography, Radio, Space, Alert } from 'antd'
|
||||
import { ArrowLeftOutlined } from '@ant-design/icons'
|
||||
import { useAccountStore } from '../store/accountStore'
|
||||
import {
|
||||
@@ -135,8 +135,7 @@ const AccountImport: React.FC = () => {
|
||||
await importAccount({
|
||||
privateKey: privateKey,
|
||||
walletAddress: walletAddress,
|
||||
accountName: values.accountName,
|
||||
isDefault: values.isDefault || false
|
||||
accountName: values.accountName
|
||||
})
|
||||
|
||||
message.success('导入账户成功')
|
||||
@@ -302,20 +301,6 @@ const AccountImport: React.FC = () => {
|
||||
<Input placeholder="可选,用于标识账户" />
|
||||
</Form.Item>
|
||||
|
||||
<Alert
|
||||
message="API Key 自动获取"
|
||||
description="系统将自动从 Polymarket 获取或创建 API Key,无需手动输入。"
|
||||
type="info"
|
||||
showIcon
|
||||
style={{ marginBottom: '24px' }}
|
||||
/>
|
||||
|
||||
<Form.Item
|
||||
name="isDefault"
|
||||
valuePropName="checked"
|
||||
>
|
||||
<Checkbox>设为默认账户</Checkbox>
|
||||
</Form.Item>
|
||||
|
||||
<Form.Item>
|
||||
<Space>
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { useEffect, useState } from 'react'
|
||||
import { useNavigate } from 'react-router-dom'
|
||||
import { Card, Table, Button, Space, Tag, Popconfirm, message, Typography, Spin, Modal, Descriptions, Divider, Form, Input, Checkbox, Alert } from 'antd'
|
||||
import { Card, Table, Button, Space, Tag, Popconfirm, message, Typography, Spin, Modal, Descriptions, Divider, Form, Input, Alert } from 'antd'
|
||||
import { PlusOutlined, ReloadOutlined, EditOutlined, CopyOutlined } from '@ant-design/icons'
|
||||
import { useAccountStore } from '../store/accountStore'
|
||||
import type { Account } from '../types'
|
||||
@@ -154,8 +154,7 @@ const AccountList: React.FC = () => {
|
||||
accountName: accountDetail.accountName || '',
|
||||
apiKey: '', // 不显示实际值,留空表示不修改
|
||||
apiSecret: '', // 不显示实际值,留空表示不修改
|
||||
apiPassphrase: '', // 不显示实际值,留空表示不修改
|
||||
isDefault: accountDetail.isDefault || false
|
||||
apiPassphrase: '' // 不显示实际值,留空表示不修改
|
||||
})
|
||||
} catch (error: any) {
|
||||
console.error('打开编辑失败:', error)
|
||||
@@ -173,8 +172,7 @@ const AccountList: React.FC = () => {
|
||||
// 构建更新请求,空字符串转换为 undefined(不修改)
|
||||
const updateData: any = {
|
||||
accountId: editAccount.id,
|
||||
accountName: values.accountName || undefined,
|
||||
isDefault: values.isDefault || false
|
||||
accountName: values.accountName || undefined
|
||||
}
|
||||
|
||||
// 只有非空字符串才更新 API 凭证
|
||||
@@ -816,13 +814,6 @@ const AccountList: React.FC = () => {
|
||||
<Input.Password placeholder="留空表示不修改" />
|
||||
</Form.Item>
|
||||
|
||||
<Form.Item
|
||||
name="isDefault"
|
||||
valuePropName="checked"
|
||||
>
|
||||
<Checkbox>设为默认账户</Checkbox>
|
||||
</Form.Item>
|
||||
|
||||
<Form.Item>
|
||||
<Space style={{ width: '100%', justifyContent: 'flex-end' }}>
|
||||
<Button
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { useState } from 'react'
|
||||
import { useNavigate } from 'react-router-dom'
|
||||
import { useNavigate, Link } from 'react-router-dom'
|
||||
import { Card, Form, Input, Button, message, Typography } from 'antd'
|
||||
import { UserOutlined, LockOutlined } from '@ant-design/icons'
|
||||
import { apiService } from '../services/api'
|
||||
@@ -95,6 +95,11 @@ const Login: React.FC = () => {
|
||||
登录
|
||||
</Button>
|
||||
</Form.Item>
|
||||
<Form.Item style={{ marginBottom: 0, textAlign: 'right' }}>
|
||||
<Link to="/reset-password" style={{ fontSize: isMobile ? '14px' : '13px' }}>
|
||||
忘记密码?重置密码
|
||||
</Link>
|
||||
</Form.Item>
|
||||
</Form>
|
||||
</Card>
|
||||
</div>
|
||||
|
||||
@@ -30,6 +30,7 @@ const PositionList: React.FC = () => {
|
||||
const [orderType, setOrderType] = useState<'MARKET' | 'LIMIT'>('LIMIT')
|
||||
const [sellQuantity, setSellQuantity] = useState<string>('')
|
||||
const [limitPrice, setLimitPrice] = useState<string>('')
|
||||
const [selectedPercent, setSelectedPercent] = useState<string | null>(null) // 记录选择的百分比(字符串格式)
|
||||
const [form] = Form.useForm()
|
||||
const [submitting, setSubmitting] = useState(false)
|
||||
const [wsConnected, setWsConnected] = useState(false)
|
||||
@@ -361,6 +362,7 @@ const PositionList: React.FC = () => {
|
||||
setOrderType('LIMIT')
|
||||
setSellQuantity('')
|
||||
setLimitPrice('')
|
||||
setSelectedPercent(null) // 重置百分比选择
|
||||
form.resetFields()
|
||||
|
||||
// 加载市场价格
|
||||
@@ -382,6 +384,9 @@ const PositionList: React.FC = () => {
|
||||
// 处理数量快捷按钮
|
||||
const handleQuantityQuickSelect = (percent: number) => {
|
||||
if (!selectedPosition) return
|
||||
// 记录选择的百分比(转为字符串,避免精度问题)
|
||||
setSelectedPercent(percent.toString())
|
||||
// 计算显示用的数量(用于预览,使用显示数量即可)
|
||||
const quantity = parseFloat(selectedPosition.quantity)
|
||||
const sellQty = (quantity * percent / 100).toFixed(4)
|
||||
setSellQuantity(sellQty)
|
||||
@@ -440,7 +445,12 @@ const PositionList: React.FC = () => {
|
||||
side: selectedPosition.side,
|
||||
outcomeIndex: selectedPosition.outcomeIndex, // 传递 outcomeIndex
|
||||
orderType: orderType,
|
||||
quantity: sellQuantity,
|
||||
// 如果选择了百分比,只传递百分比,不传 quantity
|
||||
// 如果手动输入,只传递 quantity,不传 percent
|
||||
...(selectedPercent != null
|
||||
? { percent: selectedPercent }
|
||||
: { quantity: sellQuantity }
|
||||
),
|
||||
price: orderType === 'LIMIT' ? limitPrice : undefined
|
||||
}
|
||||
|
||||
@@ -452,6 +462,7 @@ const PositionList: React.FC = () => {
|
||||
// 重置表单
|
||||
setSellQuantity('')
|
||||
setLimitPrice('')
|
||||
setSelectedPercent(null) // 重置百分比选择
|
||||
form.resetFields()
|
||||
// 仓位列表会通过WebSocket自动更新
|
||||
} else {
|
||||
@@ -1371,6 +1382,8 @@ const PositionList: React.FC = () => {
|
||||
onChange={(e) => {
|
||||
const newQuantity = e.target.value
|
||||
setSellQuantity(newQuantity)
|
||||
// 用户手动输入时,清除百分比选择
|
||||
setSelectedPercent(null)
|
||||
if (newQuantity) {
|
||||
const price = getCurrentSellPrice()
|
||||
calculatePnl(newQuantity, price)
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import { useState } from 'react'
|
||||
import { useNavigate } from 'react-router-dom'
|
||||
import { Card, Form, Input, Button, message, Typography, Alert, Progress } from 'antd'
|
||||
import { LockOutlined, KeyOutlined, UserOutlined } from '@ant-design/icons'
|
||||
import { apiService } from '../services/api'
|
||||
@@ -52,7 +51,6 @@ const getPasswordStrengthInfo = (strength: number): { text: string; color: strin
|
||||
}
|
||||
|
||||
const ResetPassword: React.FC = () => {
|
||||
const navigate = useNavigate()
|
||||
const isMobile = useMediaQuery({ maxWidth: 768 })
|
||||
const [loading, setLoading] = useState(false)
|
||||
const [passwordStrength, setPasswordStrength] = useState(0)
|
||||
@@ -77,11 +75,11 @@ const ResetPassword: React.FC = () => {
|
||||
newPassword: values.newPassword
|
||||
})
|
||||
if (response.data.code === 0) {
|
||||
message.success('密码重置成功,请登录')
|
||||
// 延迟跳转到登录页,让用户看到成功提示
|
||||
message.success('密码重置成功', 1)
|
||||
// 使用 window.location.href 强制跳转到登录页,确保跳转成功
|
||||
setTimeout(() => {
|
||||
navigate('/login', { replace: true })
|
||||
}, 1000)
|
||||
window.location.href = '/login'
|
||||
}, 500)
|
||||
} else {
|
||||
message.error(response.data.msg || '密码重置失败')
|
||||
}
|
||||
|
||||
@@ -184,12 +184,6 @@ export const apiService = {
|
||||
balance: (data: { accountId?: number }) =>
|
||||
apiClient.post<ApiResponse<any>>('/copy-trading/accounts/balance', data),
|
||||
|
||||
/**
|
||||
* 设置默认账户
|
||||
*/
|
||||
setDefault: (data: { accountId: number }) =>
|
||||
apiClient.post<ApiResponse<void>>('/copy-trading/accounts/set-default', data),
|
||||
|
||||
/**
|
||||
* 查询所有账户的仓位列表
|
||||
*/
|
||||
|
||||
@@ -15,7 +15,6 @@ export interface Account {
|
||||
walletAddress: string
|
||||
proxyAddress: string // Polymarket 代理钱包地址
|
||||
accountName?: string
|
||||
isDefault: boolean
|
||||
isEnabled?: boolean // 是否启用
|
||||
apiKeyConfigured: boolean
|
||||
apiSecretConfigured: boolean
|
||||
@@ -43,7 +42,6 @@ export interface AccountImportRequest {
|
||||
privateKey: string
|
||||
walletAddress: string
|
||||
accountName?: string
|
||||
isDefault?: boolean
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -52,7 +50,6 @@ export interface AccountImportRequest {
|
||||
export interface AccountUpdateRequest {
|
||||
accountId: number
|
||||
accountName?: string
|
||||
isDefault?: boolean
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -287,7 +284,8 @@ export interface AccountPosition {
|
||||
marketIcon?: string // 市场图标 URL
|
||||
side: string // 结果名称(如 "YES", "NO", "Pakistan" 等)
|
||||
outcomeIndex?: number // 结果索引(0, 1, 2...),用于计算 tokenId
|
||||
quantity: string
|
||||
quantity: string // 显示用的数量(可能被截位)
|
||||
originalQuantity?: string // 原始数量(保留完整精度,用于100%出售)
|
||||
avgPrice: string
|
||||
currentPrice: string
|
||||
currentValue: string
|
||||
@@ -319,7 +317,8 @@ export interface PositionSellRequest {
|
||||
side: string // 结果名称(如 "YES", "NO", "Pakistan" 等)
|
||||
outcomeIndex?: number // 结果索引(0, 1, 2...),用于计算 tokenId(推荐提供)
|
||||
orderType: 'MARKET' | 'LIMIT'
|
||||
quantity: string
|
||||
quantity?: string // 卖出数量(可选,手动输入时使用)
|
||||
percent?: string // 卖出百分比(可选,BigDecimal字符串,支持小数,0-100之间,选择百分比按钮时使用)
|
||||
price?: string // 限价订单必需
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user