Merge branch 'main' into backtest

This commit is contained in:
WrBug
2026-01-31 01:06:54 +08:00
7 changed files with 751 additions and 604 deletions
-103
View File
@@ -1,103 +0,0 @@
name: Telegram Notification on PR Merge
on:
pull_request:
types:
- closed # 当 PR 被关闭(合并或关闭)时触发
jobs:
notify:
runs-on: ubuntu-latest
# 只在 PR 被合并到 main 分支时执行
if: github.event.pull_request.merged == true && github.event.pull_request.base.ref == 'main'
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Get PR details
id: pr_details
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
PR_NUMBER="${{ github.event.pull_request.number }}"
REPO="${{ github.repository }}"
# 获取 PR 详细信息
PR_RESPONSE=$(curl -s -H "Authorization: token ${GITHUB_TOKEN}" \
-H "Accept: application/vnd.github.v3+json" \
"https://api.github.com/repos/${REPO}/pulls/${PR_NUMBER}")
# 获取 PR 变更的文件列表
FILES_RESPONSE=$(curl -s -H "Authorization: token ${GITHUB_TOKEN}" \
-H "Accept: application/vnd.github.v3+json" \
"https://api.github.com/repos/${REPO}/pulls/${PR_NUMBER}/files")
# 提取 PR 描述(body),保留换行,限制长度
PR_BODY=$(echo "$PR_RESPONSE" | jq -r '.body // ""')
if [ ${#PR_BODY} -gt 500 ]; then
PR_BODY="${PR_BODY:0:500}..."
fi
# 保存到输出变量(使用 base64 编码避免特殊字符问题)
echo "pr_body<<EOF" >> $GITHUB_OUTPUT
echo "$PR_BODY" >> $GITHUB_OUTPUT
echo "EOF" >> $GITHUB_OUTPUT
- name: Send Telegram notification
env:
TELEGRAM_BOT_TOKEN: ${{ secrets.TELEGRAM_BOT_TOKEN }}
TELEGRAM_CHAT_ID: ${{ secrets.TELEGRAM_CHAT_ID }}
run: |
# 检查 PR 是否被合并(而不是仅关闭)
PR_MERGED="${{ github.event.pull_request.merged }}"
if [ "$PR_MERGED" != "true" ]; then
echo "ℹ️ PR 仅关闭,未合并,跳过通知"
exit 0
fi
# 检查必要的环境变量
# 注意:TELEGRAM_CHAT_ID 可以是个人聊天 ID(正数)或群组 ID(负数,如 -1001234567890
if [ -z "$TELEGRAM_BOT_TOKEN" ] || [ -z "$TELEGRAM_CHAT_ID" ]; then
echo "⚠️ Telegram Bot Token 或 Chat ID 未配置,跳过通知"
exit 0
fi
# 获取 PR 基本信息
PR_NUMBER="${{ github.event.pull_request.number }}"
PR_TITLE="${{ github.event.pull_request.title }}"
PR_URL="${{ github.event.pull_request.html_url }}"
PR_MERGE_COMMIT="${{ github.event.pull_request.merge_commit_sha }}"
# 获取 PR 详细信息
PR_BODY="${{ steps.pr_details.outputs.pr_body }}"
# 转义 PR 标题中的 HTML 特殊字符
PR_TITLE_ESCAPED=$(echo "$PR_TITLE" | sed 's/&/\&amp;/g' | sed 's/</\&lt;/g' | sed 's/>/\&gt;/g')
# 构建消息内容(仅包含关键信息)
MESSAGE="🚀 <b>main 分支代码更新</b>"$'\n'$'\n'"📝 <b>PR #${PR_NUMBER}:</b> ${PR_TITLE_ESCAPED}"$'\n'"🔗 <a href=\"${PR_URL}\">查看 PR</a>"
# 发送 Telegram 消息(使用 jq 转义 JSON
curl -s -X POST "https://api.telegram.org/bot${TELEGRAM_BOT_TOKEN}/sendMessage" \
-H "Content-Type: application/json" \
-d "$(jq -n \
--arg chat_id "$TELEGRAM_CHAT_ID" \
--arg text "$MESSAGE" \
'{chat_id: $chat_id, text: $text, parse_mode: "HTML", disable_web_page_preview: false}')" > /tmp/telegram_response.json
# 检查发送结果
if [ $? -eq 0 ]; then
RESPONSE=$(cat /tmp/telegram_response.json)
if echo "$RESPONSE" | grep -q '"ok":true'; then
echo "✅ Telegram 通知发送成功"
else
echo "❌ Telegram 通知发送失败: $RESPONSE"
exit 1
fi
else
echo "❌ 发送 Telegram 消息时发生错误"
exit 1
fi
+241
View File
@@ -0,0 +1,241 @@
# PolyHermes v2.0.3 Release Notes
## 📋 版本信息
- **版本号**: v2.0.3
- **发布日期**: 2026-01-31
- **基础版本**: v2.0.2
## 🎯 改动摘要
本次版本主要优化了用户界面显示,包括数值格式化、Leader列表优化、移除不必要的配置项,提升了用户体验。
---
## ✨ 新功能
### 1. 为所有数值显示添加千分位分隔符
**功能描述**
- ✅ 重构 `formatNumber``formatUSDC` 函数,默认添加千分位分隔符
- ✅ 所有数值(金额、数量、价格等)现在默认显示千分位
- ✅ 自动去除尾随零,提升可读性
- ✅ 示例:`1234567.89` 显示为 `1,234,567.89`
**影响范围**
- `frontend/src/utils/index.ts` - 工具函数
- `frontend/src/pages/Statistics.tsx` - 统计页面
- `frontend/src/pages/CopyTradingStatistics.tsx` - 跟单统计页面
- `frontend/src/pages/PositionList.tsx` - 持仓列表
- `frontend/src/pages/AccountList.tsx` - 账户列表
**提交**: 40081c2
---
### 2. 在创建跟单配置时显示Leader资产信息
**功能描述**
- ✅ 选择Leader后自动获取并显示资产信息
- ✅ 显示总资产、可用余额、仓位资产
- ✅ 使用Card和Statistic组件美观展示
- ✅ 支持中英文多语言
- ✅ 使用formatUSDC格式化显示金额
**影响范围**
- `frontend/src/pages/CopyTradingOrders/AddModal.tsx` - 添加跟单配置弹窗
- `frontend/src/pages/CopyTradingOrders/EditModal.tsx` - 编辑跟单配置弹窗
- `frontend/src/locales/**/common.json` - 多语言文件
**提交**: 390b3ee
---
### 3. Leader列表显示仓位资产
**功能描述**
- ✅ Leader列表新增仓位资产显示
- ✅ 显示Leader的总资产、可用余额、仓位资产
- ✅ 优化资产信息展示方式
**影响范围**
- `frontend/src/pages/LeaderList.tsx` - Leader列表页面
- `backend/src/main/kotlin/com/wrbug/polymarketbot/dto/LeaderDto.kt` - Leader DTO
- `backend/src/main/kotlin/com/wrbug/polymarketbot/service/copytrading/leaders/LeaderService.kt` - Leader服务
**提交**: 3350039
---
## 🔧 优化改进
### 1. Leader列表优化
**改进内容**
- ✅ 后端过滤价值为0的仓位
- ✅ 持仓列表显示市场名称而非ID
- ✅ 列表移除分类和创建时间列
- ✅ 文案'跟单关系数'改为'跟单数'
- ✅ 持仓DTO添加title字段
**影响范围**
- `frontend/src/pages/LeaderList.tsx` - Leader列表页面
- `backend/src/main/kotlin/com/wrbug/polymarketbot/service/copytrading/leaders/LeaderService.kt` - Leader服务
- `backend/src/main/kotlin/com/wrbug/polymarketbot/service/common/BlockchainService.kt` - 区块链服务
**提交**: 0bdc0c7
---
### 2. 列表只显示可用余额
**改进内容**
- ✅ 账户列表和Leader列表只显示可用余额
- ✅ 简化界面,减少信息冗余
**影响范围**
- `frontend/src/pages/AccountList.tsx` - 账户列表
- `backend/src/main/kotlin/com/wrbug/polymarketbot/service/accounts/AccountService.kt` - 账户服务
**提交**: 17eea01
---
### 3. 移除仓位资产列
**改进内容**
- ✅ 移除不必要的仓位资产列显示
- ✅ 简化界面布局
**影响范围**
- `frontend/src/pages/PositionList.tsx` - 持仓列表
**提交**: 6980781
---
## 🗑️ 移除功能
### 移除跟单最大仓位数量(maxPositionCount)配置
**移除原因**
- 该配置项使用频率低,且增加了系统复杂度
- 简化跟单配置,提升用户体验
**移除内容**
- ✅ 数据库:创建迁移文件 V26 删除 `max_position_count` 字段
- ✅ 后端:移除实体类、DTO、服务中的 `maxPositionCount` 相关代码
- ✅ 后端:移除 `FilterResult` 中的 `FAILED_MAX_POSITION_COUNT` 状态
- ✅ 后端:移除 `CopyTradingFilterService` 中的最大仓位数量检查逻辑
- ✅ 前端:移除类型定义、表单字段和国际化翻译
- ✅ 前端:移除过滤订单列表中的 `MAX_POSITION_COUNT` 类型
**影响范围**
- `backend/src/main/resources/db/migration/V26__remove_max_position_count.sql` - 数据库迁移
- `backend/src/main/kotlin/com/wrbug/polymarketbot/entity/CopyTrading.kt` - 实体类
- `backend/src/main/kotlin/com/wrbug/polymarketbot/dto/CopyTradingDto.kt` - DTO
- `backend/src/main/kotlin/com/wrbug/polymarketbot/service/copytrading/configs/CopyTradingFilterService.kt` - 过滤服务
- `frontend/src/pages/CopyTradingOrders/AddModal.tsx` - 添加表单
- `frontend/src/pages/CopyTradingOrders/EditModal.tsx` - 编辑表单
- `frontend/src/types/index.ts` - 类型定义
**提交**: e8fd1b5
---
## 🐛 Bug 修复
### 修复TypeScript类型错误
**修复内容**
- ✅ 修复编译时的TypeScript类型错误
- ✅ 修复Spin导入问题
- ✅ 修复Table fixed类型问题
- ✅ 修复size类型问题
**影响范围**
- `frontend/src/pages/LeaderList.tsx` - Leader列表页面
**提交**: 8097660
---
## ⚠️ 潜在问题和注意事项
### 1. 数值格式化变更
**影响**
- 所有数值现在默认显示千分位分隔符
- 如果之前有代码依赖特定的数值格式,可能需要调整
**建议**
- 检查是否有代码依赖特定的数值格式
- 确认数值显示是否符合预期
### 2. 移除maxPositionCount配置
**影响**
- 如果之前使用了最大仓位数量限制功能,升级后将不再可用
- 需要手动调整跟单策略
**建议**
- 升级前检查是否有跟单配置使用了最大仓位数量限制
- 如有需要,可以手动调整跟单策略
### 3. Leader列表显示变更
**影响**
- Leader列表现在只显示价值大于0的仓位
- 列表布局和显示内容有所调整
**建议**
- 升级后检查Leader列表显示是否符合预期
- 确认仓位信息是否正确显示
---
## 📊 文件变更统计
- **修改文件数**: 28
- **新增行数**: 1490
- **删除行数**: 897
---
## 🔄 升级建议
1. **检查数值显示**
- 升级后检查所有数值显示是否符合预期
- 确认千分位分隔符显示正确
2. **检查跟单配置**
- 如果有使用最大仓位数量限制的配置,需要手动调整
- 确认跟单功能正常工作
3. **检查Leader列表**
- 升级后检查Leader列表显示是否正确
- 确认仓位信息是否完整
4. **数据库迁移**
- 升级时会自动执行数据库迁移 V26
- 迁移会删除 `max_position_count` 字段
- 建议在升级前备份数据库
---
## 📝 完整提交列表
- 40081c2 - feat: 为所有数值显示添加千分位分隔符
- e8fd1b5 - 移除跟单最大仓位数量(maxPositionCount)配置
- 390b3ee - feat: 在创建跟单配置时显示Leader资产信息
- 8097660 - fix: 修复TypeScript类型错误
- 17eea01 - refactor: 列表只显示可用余额
- 6980781 - refactor: 移除仓位资产列
- 3350039 - feat: Leader列表显示仓位资产
- 0bdc0c7 - feat: Leader列表优化
---
## 🙏 致谢
感谢所有贡献者和用户的支持与反馈!
+113 -113
View File
@@ -26,18 +26,18 @@ const AccountList: React.FC = () => {
const [editLoading, setEditLoading] = useState(false) const [editLoading, setEditLoading] = useState(false)
const [accountImportModalVisible, setAccountImportModalVisible] = useState(false) const [accountImportModalVisible, setAccountImportModalVisible] = useState(false)
const [accountImportForm] = Form.useForm() const [accountImportForm] = Form.useForm()
useEffect(() => { useEffect(() => {
fetchAccounts() fetchAccounts()
}, [fetchAccounts]) }, [fetchAccounts])
const handleAccountImportSuccess = async () => { const handleAccountImportSuccess = async () => {
message.success(t('accountImport.importSuccess')) message.success(t('accountImport.importSuccess'))
setAccountImportModalVisible(false) setAccountImportModalVisible(false)
accountImportForm.resetFields() accountImportForm.resetFields()
fetchAccounts() fetchAccounts()
} }
// 加载所有账户的余额 // 加载所有账户的余额
useEffect(() => { useEffect(() => {
const loadBalances = async () => { const loadBalances = async () => {
@@ -46,8 +46,8 @@ const AccountList: React.FC = () => {
setBalanceLoading(prev => ({ ...prev, [account.id]: true })) setBalanceLoading(prev => ({ ...prev, [account.id]: true }))
try { try {
const balanceData = await fetchAccountBalance(account.id) const balanceData = await fetchAccountBalance(account.id)
setBalanceMap(prev => ({ setBalanceMap(prev => ({
...prev, ...prev,
[account.id]: { [account.id]: {
total: balanceData.totalBalance || '0', total: balanceData.totalBalance || '0',
available: balanceData.availableBalance || '0', available: balanceData.availableBalance || '0',
@@ -56,8 +56,8 @@ const AccountList: React.FC = () => {
})) }))
} catch (error) { } catch (error) {
console.error(`获取账户 ${account.id} 余额失败:`, error) console.error(`获取账户 ${account.id} 余额失败:`, error)
setBalanceMap(prev => ({ setBalanceMap(prev => ({
...prev, ...prev,
[account.id]: { total: '-', available: '-', position: '-' } [account.id]: { total: '-', available: '-', position: '-' }
})) }))
} finally { } finally {
@@ -66,12 +66,12 @@ const AccountList: React.FC = () => {
} }
} }
} }
if (accounts.length > 0) { if (accounts.length > 0) {
loadBalances() loadBalances()
} }
}, [accounts]) }, [accounts])
const handleDelete = async (account: Account) => { const handleDelete = async (account: Account) => {
try { try {
await deleteAccount(account.id) await deleteAccount(account.id)
@@ -80,13 +80,13 @@ const AccountList: React.FC = () => {
message.error(error.message || t('accountList.deleteFailed')) message.error(error.message || t('accountList.deleteFailed'))
} }
} }
const handleCopy = (text: string) => { const handleCopy = (text: string) => {
if (!text) { if (!text) {
message.warning(t('accountList.copyFailed') || '复制失败:地址为空') message.warning(t('accountList.copyFailed') || '复制失败:地址为空')
return return
} }
if (navigator.clipboard && navigator.clipboard.writeText) { if (navigator.clipboard && navigator.clipboard.writeText) {
navigator.clipboard.writeText(text).then(() => { navigator.clipboard.writeText(text).then(() => {
message.success({ message.success({
@@ -103,7 +103,7 @@ const AccountList: React.FC = () => {
fallbackCopyTextToClipboard(text) fallbackCopyTextToClipboard(text)
} }
} }
const fallbackCopyTextToClipboard = (text: string) => { const fallbackCopyTextToClipboard = (text: string) => {
const textArea = document.createElement('textarea') const textArea = document.createElement('textarea')
textArea.value = text textArea.value = text
@@ -113,7 +113,7 @@ const AccountList: React.FC = () => {
document.body.appendChild(textArea) document.body.appendChild(textArea)
textArea.focus() textArea.focus()
textArea.select() textArea.select()
try { try {
const successful = document.execCommand('copy') const successful = document.execCommand('copy')
if (successful) { if (successful) {
@@ -131,19 +131,19 @@ const AccountList: React.FC = () => {
document.body.removeChild(textArea) document.body.removeChild(textArea)
} }
} }
const handleShowDetail = async (account: Account) => { const handleShowDetail = async (account: Account) => {
try { try {
setDetailModalVisible(true) setDetailModalVisible(true)
setDetailAccount(account) setDetailAccount(account)
setDetailBalance(null) setDetailBalance(null)
setDetailBalanceLoading(false) setDetailBalanceLoading(false)
// 加载详情和余额 // 加载详情和余额
try { try {
const accountDetail = await fetchAccountDetail(account.id) const accountDetail = await fetchAccountDetail(account.id)
setDetailAccount(accountDetail) setDetailAccount(accountDetail)
// 加载余额 // 加载余额
setDetailBalanceLoading(true) setDetailBalanceLoading(true)
try { try {
@@ -173,10 +173,10 @@ const AccountList: React.FC = () => {
setDetailAccount(null) setDetailAccount(null)
} }
} }
const handleRefreshDetailBalance = async () => { const handleRefreshDetailBalance = async () => {
if (!detailAccount) return if (!detailAccount) return
setDetailBalanceLoading(true) setDetailBalanceLoading(true)
try { try {
const balanceData = await fetchAccountBalance(detailAccount.id) const balanceData = await fetchAccountBalance(detailAccount.id)
@@ -193,16 +193,16 @@ const AccountList: React.FC = () => {
setDetailBalanceLoading(false) setDetailBalanceLoading(false)
} }
} }
const handleShowEdit = async (account: Account) => { const handleShowEdit = async (account: Account) => {
try { try {
setEditModalVisible(true) setEditModalVisible(true)
setEditAccount(account) setEditAccount(account)
// 加载账户详情并设置表单初始值 // 加载账户详情并设置表单初始值
const accountDetail = await fetchAccountDetail(account.id) const accountDetail = await fetchAccountDetail(account.id)
setEditAccount(accountDetail) setEditAccount(accountDetail)
editForm.setFieldsValue({ editForm.setFieldsValue({
accountName: accountDetail.accountName || '', accountName: accountDetail.accountName || '',
apiKey: '', // 不显示实际值,留空表示不修改 apiKey: '', // 不显示实际值,留空表示不修改
@@ -216,10 +216,10 @@ const AccountList: React.FC = () => {
setEditAccount(null) setEditAccount(null)
} }
} }
const handleEditSubmit = async (values: any) => { const handleEditSubmit = async (values: any) => {
if (!editAccount) return if (!editAccount) return
setEditLoading(true) setEditLoading(true)
try { try {
// 构建更新请求,只支持编辑账户名称 // 构建更新请求,只支持编辑账户名称
@@ -227,17 +227,17 @@ const AccountList: React.FC = () => {
accountId: editAccount.id, accountId: editAccount.id,
accountName: values.accountName || undefined accountName: values.accountName || undefined
} }
await updateAccount(updateData) await updateAccount(updateData)
message.success(t('accountList.updateSuccess')) message.success(t('accountList.updateSuccess'))
setEditModalVisible(false) setEditModalVisible(false)
setEditAccount(null) setEditAccount(null)
editForm.resetFields() editForm.resetFields()
// 刷新账户列表 // 刷新账户列表
await fetchAccounts() await fetchAccounts()
// 如果详情 Modal 打开着,也刷新详情 // 如果详情 Modal 打开着,也刷新详情
if (detailModalVisible && detailAccount && detailAccount.id === editAccount.id) { if (detailModalVisible && detailAccount && detailAccount.id === editAccount.id) {
const accountDetail = await fetchAccountDetail(editAccount.id) const accountDetail = await fetchAccountDetail(editAccount.id)
@@ -249,7 +249,7 @@ const AccountList: React.FC = () => {
setEditLoading(false) setEditLoading(false)
} }
} }
const columns = [ const columns = [
{ {
title: t('accountList.accountName'), title: t('accountList.accountName'),
@@ -339,7 +339,7 @@ const AccountList: React.FC = () => {
<Popconfirm <Popconfirm
title={t('accountList.deleteConfirm')} title={t('accountList.deleteConfirm')}
description={ description={
record.apiKeyConfigured record.apiKeyConfigured
? t('accountList.deleteConfirmDesc') ? t('accountList.deleteConfirmDesc')
: t('accountList.deleteConfirmDescSimple') : t('accountList.deleteConfirmDescSimple')
} }
@@ -356,7 +356,7 @@ const AccountList: React.FC = () => {
) )
} }
] ]
const mobileColumns = [ const mobileColumns = [
{ {
title: t('accountList.accountName'), title: t('accountList.accountName'),
@@ -364,16 +364,16 @@ const AccountList: React.FC = () => {
render: (_: any, record: Account) => { render: (_: any, record: Account) => {
return ( return (
<div style={{ padding: '8px 0' }}> <div style={{ padding: '8px 0' }}>
<div style={{ <div style={{
fontWeight: 'bold', fontWeight: 'bold',
marginBottom: '8px', marginBottom: '8px',
fontSize: '16px' fontSize: '16px'
}}> }}>
{record.accountName || `${t('accountList.accountName')} ${record.id}`} {record.accountName || `${t('accountList.accountName')} ${record.id}`}
</div> </div>
<div style={{ <div style={{
fontSize: '11px', fontSize: '11px',
color: '#666', color: '#666',
marginBottom: '8px', marginBottom: '8px',
wordBreak: 'break-all', wordBreak: 'break-all',
fontFamily: 'monospace', fontFamily: 'monospace',
@@ -406,7 +406,7 @@ const AccountList: React.FC = () => {
/> />
</div> </div>
</div> </div>
<div style={{ <div style={{
fontSize: '14px', fontSize: '14px',
fontWeight: '500', fontWeight: '500',
color: '#1890ff' color: '#1890ff'
@@ -420,7 +420,7 @@ const AccountList: React.FC = () => {
)} )}
</div> </div>
{balanceMap[record.id] && balanceMap[record.id].available !== '-' && ( {balanceMap[record.id] && balanceMap[record.id].available !== '-' && (
<div style={{ <div style={{
fontSize: '12px', fontSize: '12px',
color: '#666', color: '#666',
marginTop: '4px' marginTop: '4px'
@@ -459,7 +459,7 @@ const AccountList: React.FC = () => {
<Popconfirm <Popconfirm
title={t('accountList.deleteConfirm')} title={t('accountList.deleteConfirm')}
description={ description={
record.apiKeyConfigured record.apiKeyConfigured
? t('accountList.deleteConfirmDesc') ? t('accountList.deleteConfirmDesc')
: t('accountList.deleteConfirmDescSimple') : t('accountList.deleteConfirmDescSimple')
} }
@@ -468,9 +468,9 @@ const AccountList: React.FC = () => {
cancelText={t('common.cancel')} cancelText={t('common.cancel')}
okButtonProps={{ danger: true }} okButtonProps={{ danger: true }}
> >
<Button <Button
size="small" size="small"
block block
danger danger
style={{ minHeight: '32px' }} style={{ minHeight: '32px' }}
> >
@@ -481,15 +481,15 @@ const AccountList: React.FC = () => {
) )
} }
] ]
return ( return (
<div style={{ <div style={{
padding: isMobile ? '0' : undefined, padding: isMobile ? '0' : undefined,
margin: isMobile ? '0 -8px' : undefined margin: isMobile ? '0 -8px' : undefined
}}> }}>
<div style={{ <div style={{
display: 'flex', display: 'flex',
justifyContent: 'space-between', justifyContent: 'space-between',
alignItems: 'center', alignItems: 'center',
marginBottom: isMobile ? '12px' : '16px', marginBottom: isMobile ? '12px' : '16px',
flexWrap: 'wrap', flexWrap: 'wrap',
@@ -510,8 +510,8 @@ const AccountList: React.FC = () => {
{t('accountList.importAccount')} {t('accountList.importAccount')}
</Button> </Button>
</div> </div>
<Card style={{ <Card style={{
margin: isMobile ? '0 -8px' : '0', margin: isMobile ? '0 -8px' : '0',
borderRadius: isMobile ? '0' : undefined borderRadius: isMobile ? '0' : undefined
}}> }}>
@@ -544,7 +544,7 @@ const AccountList: React.FC = () => {
/> />
)} )}
</Card> </Card>
{/* 账户详情 Modal */} {/* 账户详情 Modal */}
<Modal <Modal
title={detailAccount ? (detailAccount.accountName || `${t('accountList.accountName')} ${detailAccount.id}`) : t('accountList.accountDetail')} title={detailAccount ? (detailAccount.accountName || `${t('accountList.accountName')} ${detailAccount.id}`) : t('accountList.accountDetail')}
@@ -555,19 +555,19 @@ const AccountList: React.FC = () => {
setDetailBalance(null) setDetailBalance(null)
}} }}
footer={[ footer={[
<Button <Button
key="refresh" key="refresh"
icon={<ReloadOutlined />} icon={<ReloadOutlined />}
onClick={handleRefreshDetailBalance} onClick={handleRefreshDetailBalance}
loading={detailBalanceLoading} loading={detailBalanceLoading}
disabled={!detailAccount} disabled={!detailAccount}
> >
{t('accountList.refreshBalance')} {t('accountList.refreshBalance')}
</Button>, </Button>,
<Button <Button
key="edit" key="edit"
type="primary" type="primary"
icon={<EditOutlined />} icon={<EditOutlined />}
onClick={() => { onClick={() => {
if (detailAccount) { if (detailAccount) {
setDetailModalVisible(false) setDetailModalVisible(false)
@@ -578,8 +578,8 @@ const AccountList: React.FC = () => {
> >
{t('accountList.edit')} {t('accountList.edit')}
</Button>, </Button>,
<Button <Button
key="close" key="close"
onClick={() => { onClick={() => {
setDetailModalVisible(false) setDetailModalVisible(false)
setDetailAccount(null) setDetailAccount(null)
@@ -610,8 +610,8 @@ const AccountList: React.FC = () => {
</Descriptions.Item> </Descriptions.Item>
<Descriptions.Item label={t('accountList.walletAddress')} span={isMobile ? 1 : 2}> <Descriptions.Item label={t('accountList.walletAddress')} span={isMobile ? 1 : 2}>
<Space> <Space>
<span style={{ <span style={{
fontFamily: 'monospace', fontFamily: 'monospace',
fontSize: isMobile ? '11px' : '13px', fontSize: isMobile ? '11px' : '13px',
wordBreak: 'break-all', wordBreak: 'break-all',
lineHeight: '1.4', lineHeight: '1.4',
@@ -633,8 +633,8 @@ const AccountList: React.FC = () => {
</Descriptions.Item> </Descriptions.Item>
<Descriptions.Item label={t('accountList.proxyAddress')} span={isMobile ? 1 : 2}> <Descriptions.Item label={t('accountList.proxyAddress')} span={isMobile ? 1 : 2}>
<Space> <Space>
<span style={{ <span style={{
fontFamily: 'monospace', fontFamily: 'monospace',
fontSize: isMobile ? '11px' : '13px', fontSize: isMobile ? '11px' : '13px',
wordBreak: 'break-all', wordBreak: 'break-all',
lineHeight: '1.4', lineHeight: '1.4',
@@ -688,9 +688,9 @@ const AccountList: React.FC = () => {
)} )}
</Descriptions.Item> </Descriptions.Item>
</Descriptions> </Descriptions>
<Divider /> <Divider />
<Descriptions <Descriptions
column={isMobile ? 1 : 2} column={isMobile ? 1 : 2}
bordered bordered
@@ -720,51 +720,51 @@ const AccountList: React.FC = () => {
)} )}
</Descriptions.Item> </Descriptions.Item>
</Descriptions> </Descriptions>
{(detailAccount.totalOrders !== undefined || detailAccount.totalPnl !== undefined || {(detailAccount.totalOrders !== undefined || detailAccount.totalPnl !== undefined ||
detailAccount.activeOrders !== undefined || detailAccount.activeOrders !== undefined ||
detailAccount.completedOrders !== undefined || detailAccount.positionCount !== undefined) && ( detailAccount.completedOrders !== undefined || detailAccount.positionCount !== undefined) && (
<> <>
<Divider /> <Divider />
<Descriptions <Descriptions
column={isMobile ? 1 : 2} column={isMobile ? 1 : 2}
bordered bordered
size={isMobile ? 'small' : 'middle'} size={isMobile ? 'small' : 'middle'}
title={t('accountList.statistics')} title={t('accountList.statistics')}
> >
{detailAccount.totalOrders !== undefined && ( {detailAccount.totalOrders !== undefined && (
<Descriptions.Item label={t('accountList.totalOrders')}> <Descriptions.Item label={t('accountList.totalOrders')}>
{detailAccount.totalOrders} {detailAccount.totalOrders}
</Descriptions.Item> </Descriptions.Item>
)} )}
{detailAccount.activeOrders !== undefined && ( {detailAccount.activeOrders !== undefined && (
<Descriptions.Item label={t('accountList.activeOrdersCount')}> <Descriptions.Item label={t('accountList.activeOrdersCount')}>
<Tag color={detailAccount.activeOrders > 0 ? 'orange' : 'default'}>{detailAccount.activeOrders}</Tag> <Tag color={detailAccount.activeOrders > 0 ? 'orange' : 'default'}>{detailAccount.activeOrders}</Tag>
</Descriptions.Item> </Descriptions.Item>
)} )}
{detailAccount.completedOrders !== undefined && ( {detailAccount.completedOrders !== undefined && (
<Descriptions.Item label={t('accountList.completedOrders')}> <Descriptions.Item label={t('accountList.completedOrders')}>
<Tag color="success">{detailAccount.completedOrders}</Tag> <Tag color="success">{detailAccount.completedOrders}</Tag>
</Descriptions.Item> </Descriptions.Item>
)} )}
{detailAccount.positionCount !== undefined && ( {detailAccount.positionCount !== undefined && (
<Descriptions.Item label={t('accountList.positionCount')}> <Descriptions.Item label={t('accountList.positionCount')}>
<Tag color={detailAccount.positionCount > 0 ? 'blue' : 'default'}>{detailAccount.positionCount}</Tag> <Tag color={detailAccount.positionCount > 0 ? 'blue' : 'default'}>{detailAccount.positionCount}</Tag>
</Descriptions.Item> </Descriptions.Item>
)} )}
{detailAccount.totalPnl !== undefined && ( {detailAccount.totalPnl !== undefined && (
<Descriptions.Item label={t('accountList.totalPnl')}> <Descriptions.Item label={t('accountList.totalPnl')}>
<span style={{ <span style={{
fontWeight: 'bold', fontWeight: 'bold',
color: detailAccount.totalPnl && detailAccount.totalPnl.startsWith('-') ? '#ff4d4f' : '#52c41a' color: detailAccount.totalPnl && detailAccount.totalPnl.startsWith('-') ? '#ff4d4f' : '#52c41a'
}}> }}>
{formatUSDC(detailAccount.totalPnl)} USDC {formatUSDC(detailAccount.totalPnl)} USDC
</span> </span>
</Descriptions.Item> </Descriptions.Item>
)} )}
</Descriptions> </Descriptions>
</> </>
)} )}
</div> </div>
) : ( ) : (
<div style={{ textAlign: 'center', padding: '20px' }}> <div style={{ textAlign: 'center', padding: '20px' }}>
@@ -773,7 +773,7 @@ const AccountList: React.FC = () => {
</div> </div>
)} )}
</Modal> </Modal>
{/* 编辑账户 Modal */} {/* 编辑账户 Modal */}
<Modal <Modal
title={editAccount ? `${t('accountList.editAccount')} - ${editAccount.accountName || `${t('accountList.accountName')} ${editAccount.id}`}` : t('accountList.editAccount')} title={editAccount ? `${t('accountList.editAccount')} - ${editAccount.accountName || `${t('accountList.accountName')} ${editAccount.id}`}` : t('accountList.editAccount')}
@@ -804,17 +804,17 @@ const AccountList: React.FC = () => {
showIcon showIcon
style={{ marginBottom: '24px' }} style={{ marginBottom: '24px' }}
/> />
<Form.Item <Form.Item
label={t('accountList.accountName') || '账户名称'} label={t('accountList.accountName') || '账户名称'}
name="accountName" name="accountName"
> >
<Input placeholder={t('accountList.accountNamePlaceholder') || '请输入账户名称(可选)'} /> <Input placeholder={t('accountList.accountNamePlaceholder') || '请输入账户名称(可选)'} />
</Form.Item> </Form.Item>
<Form.Item> <Form.Item>
<Space style={{ width: '100%', justifyContent: 'flex-end' }}> <Space style={{ width: '100%', justifyContent: 'flex-end' }}>
<Button <Button
onClick={() => { onClick={() => {
setEditModalVisible(false) setEditModalVisible(false)
setEditAccount(null) setEditAccount(null)
@@ -844,7 +844,7 @@ const AccountList: React.FC = () => {
</div> </div>
)} )}
</Modal> </Modal>
{/* 导入账户 Modal */} {/* 导入账户 Modal */}
<Modal <Modal
title={t('accountImport.title')} title={t('accountImport.title')}
+22 -22
View File
@@ -3,7 +3,7 @@ import { useParams, useNavigate } from 'react-router-dom'
import { Card, Row, Col, Statistic, Tag, Button, message, Spin } from 'antd' import { Card, Row, Col, Statistic, Tag, Button, message, Spin } from 'antd'
import { ArrowUpOutlined, ArrowDownOutlined, LeftOutlined } from '@ant-design/icons' import { ArrowUpOutlined, ArrowDownOutlined, LeftOutlined } from '@ant-design/icons'
import { apiService } from '../services/api' import { apiService } from '../services/api'
import { formatUSDC } from '../utils' import { formatUSDC, formatNumber } from '../utils'
import { useMediaQuery } from 'react-responsive' import { useMediaQuery } from 'react-responsive'
import type { CopyTradingStatistics } from '../types' import type { CopyTradingStatistics } from '../types'
@@ -13,16 +13,16 @@ const CopyTradingStatisticsPage: React.FC = () => {
useMediaQuery({ maxWidth: 768 }) // 用于响应式布局,但当前页面未使用 useMediaQuery({ maxWidth: 768 }) // 用于响应式布局,但当前页面未使用
const [loading, setLoading] = useState(false) const [loading, setLoading] = useState(false)
const [statistics, setStatistics] = useState<CopyTradingStatistics | null>(null) const [statistics, setStatistics] = useState<CopyTradingStatistics | null>(null)
useEffect(() => { useEffect(() => {
if (copyTradingId) { if (copyTradingId) {
fetchStatistics() fetchStatistics()
} }
}, [copyTradingId]) }, [copyTradingId])
const fetchStatistics = async () => { const fetchStatistics = async () => {
if (!copyTradingId) return if (!copyTradingId) return
setLoading(true) setLoading(true)
try { try {
const response = await apiService.statistics.detail({ copyTradingId: parseInt(copyTradingId) }) const response = await apiService.statistics.detail({ copyTradingId: parseInt(copyTradingId) })
@@ -37,25 +37,25 @@ const CopyTradingStatisticsPage: React.FC = () => {
setLoading(false) setLoading(false)
} }
} }
const getPnlColor = (value: string): string => { const getPnlColor = (value: string): string => {
const num = parseFloat(value) const num = parseFloat(value)
if (isNaN(num)) return '#666' if (isNaN(num)) return '#666'
return num >= 0 ? '#3f8600' : '#cf1322' return num >= 0 ? '#3f8600' : '#cf1322'
} }
const getPnlIcon = (value: string) => { const getPnlIcon = (value: string) => {
const num = parseFloat(value) const num = parseFloat(value)
if (isNaN(num)) return null if (isNaN(num)) return null
return num >= 0 ? <ArrowUpOutlined /> : <ArrowDownOutlined /> return num >= 0 ? <ArrowUpOutlined /> : <ArrowDownOutlined />
} }
const formatPercent = (value: string): string => { const formatPercent = (value: string): string => {
const num = parseFloat(value) const num = parseFloat(value)
if (isNaN(num)) return '-' if (isNaN(num)) return '-'
return `${num >= 0 ? '+' : ''}${num.toFixed(2)}%` return `${num >= 0 ? '+' : ''}${num.toFixed(2)}%`
} }
if (loading) { if (loading) {
return ( return (
<div style={{ textAlign: 'center', padding: '50px' }}> <div style={{ textAlign: 'center', padding: '50px' }}>
@@ -63,7 +63,7 @@ const CopyTradingStatisticsPage: React.FC = () => {
</div> </div>
) )
} }
if (!statistics) { if (!statistics) {
return ( return (
<Card> <Card>
@@ -74,7 +74,7 @@ const CopyTradingStatisticsPage: React.FC = () => {
</Card> </Card>
) )
} }
return ( return (
<div> <div>
<Card style={{ marginBottom: 16 }}> <Card style={{ marginBottom: 16 }}>
@@ -98,7 +98,7 @@ const CopyTradingStatisticsPage: React.FC = () => {
</div> </div>
</div> </div>
</Card> </Card>
{/* 基本信息卡片 */} {/* 基本信息卡片 */}
<Card title="基本信息" style={{ marginBottom: 16 }}> <Card title="基本信息" style={{ marginBottom: 16 }}>
<Row gutter={[16, 16]}> <Row gutter={[16, 16]}>
@@ -130,14 +130,14 @@ const CopyTradingStatisticsPage: React.FC = () => {
</Col> </Col>
</Row> </Row>
</Card> </Card>
{/* 买入统计卡片 */} {/* 买入统计卡片 */}
<Card title="买入统计" style={{ marginBottom: 16 }}> <Card title="买入统计" style={{ marginBottom: 16 }}>
<Row gutter={[16, 16]}> <Row gutter={[16, 16]}>
<Col xs={24} sm={12} md={6}> <Col xs={24} sm={12} md={6}>
<Statistic <Statistic
title="总买入数量" title="总买入数量"
value={formatUSDC(statistics.totalBuyQuantity)} value={formatNumber(statistics.totalBuyQuantity, 4)}
suffix="" suffix=""
/> />
</Col> </Col>
@@ -151,27 +151,27 @@ const CopyTradingStatisticsPage: React.FC = () => {
<Col xs={24} sm={12} md={6}> <Col xs={24} sm={12} md={6}>
<Statistic <Statistic
title="总买入订单数" title="总买入订单数"
value={statistics.totalBuyOrders} value={formatNumber(statistics.totalBuyOrders)}
suffix="笔" suffix="笔"
/> />
</Col> </Col>
<Col xs={24} sm={12} md={6}> <Col xs={24} sm={12} md={6}>
<Statistic <Statistic
title="平均买入价格" title="平均买入价格"
value={formatUSDC(statistics.avgBuyPrice)} value={formatNumber(statistics.avgBuyPrice, 4)}
suffix="" suffix=""
/> />
</Col> </Col>
</Row> </Row>
</Card> </Card>
{/* 卖出统计卡片 */} {/* 卖出统计卡片 */}
<Card title="卖出统计" style={{ marginBottom: 16 }}> <Card title="卖出统计" style={{ marginBottom: 16 }}>
<Row gutter={[16, 16]}> <Row gutter={[16, 16]}>
<Col xs={24} sm={12} md={8}> <Col xs={24} sm={12} md={8}>
<Statistic <Statistic
title="总卖出数量" title="总卖出数量"
value={formatUSDC(statistics.totalSellQuantity)} value={formatNumber(statistics.totalSellQuantity, 4)}
suffix="" suffix=""
/> />
</Col> </Col>
@@ -185,33 +185,33 @@ const CopyTradingStatisticsPage: React.FC = () => {
<Col xs={24} sm={12} md={8}> <Col xs={24} sm={12} md={8}>
<Statistic <Statistic
title="总卖出订单数" title="总卖出订单数"
value={statistics.totalSellOrders} value={formatNumber(statistics.totalSellOrders)}
suffix="笔" suffix="笔"
/> />
</Col> </Col>
</Row> </Row>
</Card> </Card>
{/* 持仓统计卡片 */} {/* 持仓统计卡片 */}
<Card title="持仓统计" style={{ marginBottom: 16 }}> <Card title="持仓统计" style={{ marginBottom: 16 }}>
<Row gutter={[16, 16]}> <Row gutter={[16, 16]}>
<Col xs={24} sm={12} md={12}> <Col xs={24} sm={12} md={12}>
<Statistic <Statistic
title="当前持仓数量" title="当前持仓数量"
value={formatUSDC(statistics.currentPositionQuantity)} value={formatNumber(statistics.currentPositionQuantity, 4)}
suffix="" suffix=""
/> />
</Col> </Col>
<Col xs={24} sm={12} md={12}> <Col xs={24} sm={12} md={12}>
<Statistic <Statistic
title="平均买入价格" title="平均买入价格"
value={formatUSDC(statistics.avgBuyPrice)} value={formatNumber(statistics.avgBuyPrice, 4)}
suffix="" suffix=""
/> />
</Col> </Col>
</Row> </Row>
</Card> </Card>
{/* 盈亏统计卡片 */} {/* 盈亏统计卡片 */}
<Card title="盈亏统计"> <Card title="盈亏统计">
<Row gutter={[16, 16]}> <Row gutter={[16, 16]}>
File diff suppressed because it is too large Load Diff
+9 -9
View File
@@ -5,7 +5,7 @@ import { useTranslation } from 'react-i18next'
import type { Dayjs } from 'dayjs' import type { Dayjs } from 'dayjs'
import { apiService } from '../services/api' import { apiService } from '../services/api'
import type { Statistics as StatisticsType } from '../types' import type { Statistics as StatisticsType } from '../types'
import { formatUSDC } from '../utils' import { formatUSDC, formatNumber } from '../utils'
import { useMediaQuery } from 'react-responsive' import { useMediaQuery } from 'react-responsive'
const { RangePicker } = DatePicker const { RangePicker } = DatePicker
@@ -17,17 +17,17 @@ const Statistics: React.FC = () => {
const [stats, setStats] = useState<StatisticsType | null>(null) const [stats, setStats] = useState<StatisticsType | null>(null)
const [loading, setLoading] = useState(false) const [loading, setLoading] = useState(false)
const [dateRange, setDateRange] = useState<[Dayjs | null, Dayjs | null]>([null, null]) const [dateRange, setDateRange] = useState<[Dayjs | null, Dayjs | null]>([null, null])
useEffect(() => { useEffect(() => {
fetchStatistics() fetchStatistics()
}, []) }, [])
const fetchStatistics = async () => { const fetchStatistics = async () => {
setLoading(true) setLoading(true)
try { try {
const startTime = dateRange[0] ? dateRange[0].valueOf() : undefined const startTime = dateRange[0] ? dateRange[0].valueOf() : undefined
const endTime = dateRange[1] ? dateRange[1].valueOf() : undefined const endTime = dateRange[1] ? dateRange[1].valueOf() : undefined
const response = await apiService.statistics.global({ startTime, endTime }) const response = await apiService.statistics.global({ startTime, endTime })
if (response.data.code === 0 && response.data.data) { if (response.data.code === 0 && response.data.data) {
setStats(response.data.data) setStats(response.data.data)
@@ -40,11 +40,11 @@ const Statistics: React.FC = () => {
setLoading(false) setLoading(false)
} }
} }
const handleDateRangeChange = (dates: [Dayjs | null, Dayjs | null] | null) => { const handleDateRangeChange = (dates: [Dayjs | null, Dayjs | null] | null) => {
setDateRange(dates || [null, null]) setDateRange(dates || [null, null])
} }
const handleReset = () => { const handleReset = () => {
setDateRange([null, null]) setDateRange([null, null])
// 重置后自动刷新 // 重置后自动刷新
@@ -52,7 +52,7 @@ const Statistics: React.FC = () => {
fetchStatistics() fetchStatistics()
}, 100) }, 100)
} }
return ( return (
<div> <div>
<div style={{ marginBottom: '16px', display: 'flex', justifyContent: 'space-between', alignItems: 'center', flexWrap: 'wrap', gap: '12px' }}> <div style={{ marginBottom: '16px', display: 'flex', justifyContent: 'space-between', alignItems: 'center', flexWrap: 'wrap', gap: '12px' }}>
@@ -85,13 +85,13 @@ const Statistics: React.FC = () => {
)} )}
</Space> </Space>
</div> </div>
<Row gutter={[16, 16]}> <Row gutter={[16, 16]}>
<Col xs={24} sm={12} md={8}> <Col xs={24} sm={12} md={8}>
<Card> <Card>
<Statistic <Statistic
title={t('statistics.totalOrders') || '总订单数'} title={t('statistics.totalOrders') || '总订单数'}
value={stats?.totalOrders || 0} value={formatNumber(stats?.totalOrders || 0)}
loading={loading} loading={loading}
/> />
</Card> </Card>
+35 -26
View File
@@ -1,61 +1,70 @@
/** /**
* *
* @param value - * @param value -
* @param maxDecimals - * @param maxDecimals -
* @returns '' * @returns "123,456.78" ''
* @example * @example
* formatNumber(1234567.89) => "1,234,567.89"
* formatNumber(1234567.00) => "1,234,567"
* formatNumber(1234.5678, 2) => "1,234.56"
* formatNumber(100.00) => "100" * formatNumber(100.00) => "100"
* formatNumber(100.50) => "100.5" * formatNumber(100.50) => "100.5"
* formatNumber(100.55) => "100.55"
*/ */
export const formatNumber = (value: string | number | undefined | null, maxDecimals?: number): string => { export const formatNumber = (value: string | number | undefined | null, maxDecimals?: number): string => {
if (value === undefined || value === null || value === '') { if (value === undefined || value === null || value === '') {
return '' return ''
} }
const num = typeof value === 'string' ? parseFloat(value) : value const num = typeof value === 'string' ? parseFloat(value) : value
if (isNaN(num)) { if (isNaN(num)) {
return '' return ''
} }
// 如果有最大小数位数限制,先截断 // 处理小数位数
let numStr: string
if (maxDecimals !== undefined) { if (maxDecimals !== undefined) {
const multiplier = Math.pow(10, maxDecimals) const multiplier = Math.pow(10, maxDecimals)
const truncated = Math.floor(num * multiplier) / multiplier const truncated = Math.floor(num * multiplier) / multiplier
return truncated.toFixed(maxDecimals).replace(/\.?0+$/, '') numStr = truncated.toFixed(maxDecimals).replace(/\.?0+$/, '')
} else {
numStr = num.toString().replace(/\.?0+$/, '')
} }
// 直接转换为字符串,然后去除尾随零 // 分离整数和小数部分
return num.toString().replace(/\.?0+$/, '') const parts = numStr.split('.')
const integerPart = parts[0]
const decimalPart = parts[1]
// 为整数部分添加千分位分隔符
const formattedInteger = integerPart.replace(/\B(?=(\d{3})+(?!\d))/g, ',')
// 组合结果
return decimalPart ? `${formattedInteger}.${decimalPart}` : formattedInteger
} }
/** /**
* USDC * USDC
* 4 * 4
* @param value - * @param value -
* @returns '-' * @returns "1,234.56" '-'
* @example * @example
* formatUSDC(1234.56) => "1,234.56"
* formatUSDC(1234567.8901) => "1,234,567.8901"
* formatUSDC(1234.00) => "1,234"
* formatUSDC(1.23) => "1.23" * formatUSDC(1.23) => "1.23"
* formatUSDC(1.23456) => "1.2345"
* formatUSDC(1.2) => "1.2"
* formatUSDC(1) => "1" * formatUSDC(1) => "1"
*/ */
export const formatUSDC = (value: string | number | undefined | null): string => { export const formatUSDC = (value: string | number | undefined | null): string => {
if (value === undefined || value === null || value === '') { if (value === undefined || value === null || value === '') {
return '-' return '-'
} }
const num = typeof value === 'string' ? parseFloat(value) : value const num = typeof value === 'string' ? parseFloat(value) : value
if (isNaN(num)) { if (isNaN(num)) {
return '-' return '-'
} }
// 使用 Math.floor 截断到4位小数(不四舍五入) return formatNumber(num, 4)
const multiplier = Math.pow(10, 4)
const truncated = Math.floor(num * multiplier) / multiplier
// 使用 toFixed(4) 确保格式一致,然后去除尾随零和小数点
return truncated.toFixed(4).replace(/\.?0+$/, '')
} }
// 统一导出 ethers 相关工具函数 // 统一导出 ethers 相关工具函数
@@ -97,7 +106,7 @@ export const isAutoGeneratedOrderId = (orderId: string | undefined | null): bool
/** /**
* Polymarket URL * Polymarket URL
* moneyline moneyline * moneyline moneyline
* moneyline * moneyline,
* @param marketSlug - slug * @param marketSlug - slug
* @param eventSlug - slug events[0].slug 使 * @param eventSlug - slug events[0].slug 使
* @param marketCategory - sports, crypto * @param marketCategory - sports, crypto
@@ -114,7 +123,7 @@ export const getPolymarketUrl = (
): string | null => { ): string | null => {
// 优先使用 eventSlug(跳转用的 slug // 优先使用 eventSlug(跳转用的 slug
const slug = eventSlug || marketSlug const slug = eventSlug || marketSlug
if (slug) { if (slug) {
// 如果是 moneyline 市场,跳转到 moneyline 页面 // 如果是 moneyline 市场,跳转到 moneyline 页面
if (isMoneyline === true) { if (isMoneyline === true) {
@@ -123,12 +132,12 @@ export const getPolymarketUrl = (
// 其他市场跳转到普通市场页面 // 其他市场跳转到普通市场页面
return `https://polymarket.com/event/${slug}` return `https://polymarket.com/event/${slug}`
} }
// 如果没有 slug,使用 marketId(作为后备) // 如果没有 slug,使用 marketId(作为后备)
if (marketId && marketId.startsWith('0x')) { if (marketId && marketId.startsWith('0x')) {
return `https://polymarket.com/condition/${marketId}` return `https://polymarket.com/condition/${marketId}`
} }
return null return null
} }