diff --git a/backend/src/main/kotlin/com/wrbug/polymarketbot/service/LeaderService.kt b/backend/src/main/kotlin/com/wrbug/polymarketbot/service/LeaderService.kt index 7035181..e87a193 100644 --- a/backend/src/main/kotlin/com/wrbug/polymarketbot/service/LeaderService.kt +++ b/backend/src/main/kotlin/com/wrbug/polymarketbot/service/LeaderService.kt @@ -186,9 +186,10 @@ class LeaderService( /** * 验证钱包地址格式 + * 必须是 0x 开头的 42 位十六进制字符串 */ private fun isValidWalletAddress(address: String): Boolean { - return address.startsWith("0x") && address.length == 42 + return address.matches(Regex("^0x[a-fA-F0-9]{40}$")) } } diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx index effee75..23e0ed4 100644 --- a/frontend/src/App.tsx +++ b/frontend/src/App.tsx @@ -9,6 +9,7 @@ import AccountDetail from './pages/AccountDetail' import AccountEdit from './pages/AccountEdit' import LeaderList from './pages/LeaderList' import LeaderAdd from './pages/LeaderAdd' +import LeaderEdit from './pages/LeaderEdit' import ConfigPage from './pages/ConfigPage' import PositionList from './pages/PositionList' import Statistics from './pages/Statistics' @@ -129,6 +130,7 @@ function App() { } /> } /> } /> + } /> } /> } /> } /> diff --git a/frontend/src/pages/LeaderAdd.tsx b/frontend/src/pages/LeaderAdd.tsx index dcca342..864fc07 100644 --- a/frontend/src/pages/LeaderAdd.tsx +++ b/frontend/src/pages/LeaderAdd.tsx @@ -1,10 +1,10 @@ -import { useState, useEffect } from 'react' +import { useState } from 'react' import { useNavigate } from 'react-router-dom' -import { Card, Form, Input, Button, Select, Switch, message, Typography, Space } from 'antd' +import { Card, Form, Input, Button, Select, message, Typography, Space } from 'antd' import { ArrowLeftOutlined } from '@ant-design/icons' import { apiService } from '../services/api' -import { useAccountStore } from '../store/accountStore' import { useMediaQuery } from 'react-responsive' +import { isValidWalletAddress } from '../utils' const { Title } = Typography const { Option } = Select @@ -12,23 +12,16 @@ const { Option } = Select const LeaderAdd: React.FC = () => { const navigate = useNavigate() const isMobile = useMediaQuery({ maxWidth: 768 }) - const { accounts, fetchAccounts } = useAccountStore() const [form] = Form.useForm() const [loading, setLoading] = useState(false) - useEffect(() => { - fetchAccounts() - }, [fetchAccounts]) - const handleSubmit = async (values: any) => { setLoading(true) try { const response = await apiService.leaders.add({ - leaderAddress: values.leaderAddress, - leaderName: values.leaderName, - accountId: values.accountId, - category: values.category, - enabled: values.enabled !== false + leaderAddress: values.leaderAddress.trim(), + leaderName: values.leaderName?.trim() || undefined, + category: values.category || undefined }) if (response.data.code === 0) { @@ -73,39 +66,34 @@ const LeaderAdd: React.FC = () => { rules={[ { required: true, message: '请输入 Leader 钱包地址' }, { - pattern: /^0x[a-fA-F0-9]{40}$/, - message: '钱包地址格式不正确' + validator: (_, value) => { + if (!value) { + return Promise.reject(new Error('请输入 Leader 钱包地址')) + } + if (!isValidWalletAddress(value.trim())) { + return Promise.reject(new Error('钱包地址格式不正确(必须是 0x 开头的 42 位地址)')) + } + return Promise.resolve() + } } ]} + tooltip="被跟单者的钱包地址,系统将监控该地址的交易并自动跟单" > - + - - - - - - - - + 编辑 Leader + + + +
+ + + + + + + + + + + + + + +
+
+ + ) +} + +export default LeaderEdit + diff --git a/frontend/src/pages/LeaderList.tsx b/frontend/src/pages/LeaderList.tsx index 25f81c9..cc8eb1c 100644 --- a/frontend/src/pages/LeaderList.tsx +++ b/frontend/src/pages/LeaderList.tsx @@ -1,9 +1,8 @@ -import { useEffect } from 'react' +import { useEffect, useState } from 'react' import { useNavigate } from 'react-router-dom' -import { Card, Table, Button, Space, Tag, Popconfirm, message } from 'antd' +import { Card, Table, Button, Space, Tag, Popconfirm, message, List, Empty, Spin, Divider } from 'antd' import { PlusOutlined, EditOutlined, DeleteOutlined } from '@ant-design/icons' import { apiService } from '../services/api' -import { useState } from 'react' import type { Leader } from '../types' import { useMediaQuery } from 'react-responsive' @@ -59,7 +58,9 @@ const LeaderList: React.FC = () => { dataIndex: 'leaderAddress', key: 'leaderAddress', render: (address: string) => ( - {address} + + {isMobile ? `${address.slice(0, 6)}...${address.slice(-4)}` : address} + ) }, { @@ -71,24 +72,30 @@ const LeaderList: React.FC = () => { ) : 全部 }, { - title: '状态', - dataIndex: 'enabled', - key: 'enabled', - render: (enabled: boolean) => ( - - {enabled ? '启用' : '禁用'} - - ) + title: '跟单关系数', + dataIndex: 'copyTradingCount', + key: 'copyTradingCount', + render: (count: number) => {count} }, { - title: '跟单比例', - dataIndex: 'copyRatio', - key: 'copyRatio', - render: (ratio: string) => `${ratio}x` + title: '创建时间', + dataIndex: 'createdAt', + key: 'createdAt', + render: (timestamp: number) => { + const date = new Date(timestamp) + return date.toLocaleString('zh-CN', { + year: 'numeric', + month: '2-digit', + day: '2-digit', + hour: '2-digit', + minute: '2-digit' + }) + } }, { title: '操作', key: 'action', + width: isMobile ? 120 : 150, render: (_: any, record: Leader) => ( 0 ? `该 Leader 还有 ${record.copyTradingCount} 个跟单关系,请先删除跟单关系` : undefined} onConfirm={() => handleDelete(record.id)} okText="确定" cancelText="取消" @@ -124,7 +132,7 @@ const LeaderList: React.FC = () => { flexWrap: 'wrap', gap: '12px' }}> -

Leader 管理

+

Leader 管理