From 9c303e0a825abfe5e2a8c01574de98a0e3a9c9f6 Mon Sep 17 00:00:00 2001 From: WrBug Date: Mon, 12 Jan 2026 14:29:12 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E6=B7=BB=E5=8A=A0=E6=8E=A8=E9=80=81?= =?UTF-8?q?=E5=B7=B2=E8=BF=87=E6=BB=A4=E8=AE=A2=E5=8D=95=E5=8A=9F=E8=83=BD?= =?UTF-8?q?=E5=B9=B6=E4=BF=AE=E5=A4=8D=E7=9B=B8=E5=85=B3=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 新增推送已过滤订单功能(pushFilteredOrders),默认关闭 - 数据库迁移:添加 pushFilteredOrders 字段到模板表和跟单配置表 - 后端:在实体、DTO、Service 中添加 pushFilteredOrders 字段支持 - 前端:在模板新增、编辑、推送页面和跟单配置新增、编辑页面添加开关 - 多语言:添加中文、繁体中文、英文翻译 - 通知逻辑:发送过滤订单通知时检查 pushFilteredOrders 字段 - 修复新建跟单配置时 pushFilteredOrders 字段未生效的问题 - 修复手动输入模式下 pushFilteredOrders 被硬编码为 false 的问题 - 修复从模板填充表单时未加载 pushFilteredOrders 的问题 - 添加 CopyTradingTemplate 接口的 pushFilteredOrders 字段定义 - 修复新建和编辑页面的截止时间输入框交互问题 - 修复删除后失去焦点自动填充1的问题 - 优化 onChange 和 onBlur 处理逻辑,支持清空操作 --- .../wrbug/polymarketbot/dto/CopyTradingDto.kt | 3 ++ .../dto/CopyTradingTemplateDto.kt | 10 +++-- .../wrbug/polymarketbot/entity/CopyTrading.kt | 3 ++ .../entity/CopyTradingTemplate.kt | 3 ++ .../copytrading/configs/CopyTradingService.kt | 14 +++++-- .../statistics/CopyOrderTrackingService.kt | 42 ++++++++++--------- .../templates/CopyTradingTemplateService.kt | 8 +++- ..._add_push_filtered_orders_to_templates.sql | 13 ++++++ frontend/src/locales/en/common.json | 10 +++++ frontend/src/locales/zh-CN/common.json | 10 +++++ frontend/src/locales/zh-TW/common.json | 10 +++++ .../src/pages/CopyTradingOrders/AddModal.tsx | 40 +++++++++++++++--- .../src/pages/CopyTradingOrders/EditModal.tsx | 13 +++++- frontend/src/pages/TemplateAdd.tsx | 15 ++++++- frontend/src/pages/TemplateEdit.tsx | 15 ++++++- frontend/src/pages/TemplateList.tsx | 13 +++++- frontend/src/types/index.ts | 1 + 17 files changed, 183 insertions(+), 40 deletions(-) create mode 100644 backend/src/main/resources/db/migration/V24__add_push_filtered_orders_to_templates.sql diff --git a/backend/src/main/kotlin/com/wrbug/polymarketbot/dto/CopyTradingDto.kt b/backend/src/main/kotlin/com/wrbug/polymarketbot/dto/CopyTradingDto.kt index a4ca8a3..d21b9a4 100644 --- a/backend/src/main/kotlin/com/wrbug/polymarketbot/dto/CopyTradingDto.kt +++ b/backend/src/main/kotlin/com/wrbug/polymarketbot/dto/CopyTradingDto.kt @@ -43,6 +43,7 @@ data class CopyTradingCreateRequest( // 新增配置字段 val configName: String? = null, // 配置名(可选) val pushFailedOrders: Boolean? = null, // 推送失败订单(可选) + val pushFilteredOrders: Boolean? = null, // 推送已过滤订单(可选) val maxMarketEndDate: Long? = null // 市场截止时间限制(毫秒时间戳),仅跟单截止时间小于此时间的订单,NULL表示不启用 ) @@ -81,6 +82,7 @@ data class CopyTradingUpdateRequest( // 新增配置字段 val configName: String? = null, // 配置名(可选,但提供时必须非空) val pushFailedOrders: Boolean? = null, // 推送失败订单(可选) + val pushFilteredOrders: Boolean? = null, // 推送已过滤订单(可选) val maxMarketEndDate: Long? = null // 市场截止时间限制(毫秒时间戳),仅跟单截止时间小于此时间的订单,NULL表示不启用 ) @@ -156,6 +158,7 @@ data class CopyTradingDto( // 新增配置字段 val configName: String? = null, // 配置名(可选) val pushFailedOrders: Boolean = false, // 推送失败订单(默认关闭) + val pushFilteredOrders: Boolean = false, // 推送已过滤订单(默认关闭) val maxMarketEndDate: Long? = null, // 市场截止时间限制(毫秒时间戳),仅跟单截止时间小于此时间的订单,NULL表示不启用 val createdAt: Long, val updatedAt: Long diff --git a/backend/src/main/kotlin/com/wrbug/polymarketbot/dto/CopyTradingTemplateDto.kt b/backend/src/main/kotlin/com/wrbug/polymarketbot/dto/CopyTradingTemplateDto.kt index 0f9a26f..651cfd0 100644 --- a/backend/src/main/kotlin/com/wrbug/polymarketbot/dto/CopyTradingTemplateDto.kt +++ b/backend/src/main/kotlin/com/wrbug/polymarketbot/dto/CopyTradingTemplateDto.kt @@ -23,7 +23,8 @@ data class TemplateCreateRequest( val minOrderDepth: String? = null, // 最小订单深度(USDC金额),NULL表示不启用 val maxSpread: String? = null, // 最大价差(绝对价格),NULL表示不启用 val minPrice: String? = null, // 最低价格(可选),NULL表示不限制最低价 - val maxPrice: String? = null // 最高价格(可选),NULL表示不限制最高价 + val maxPrice: String? = null, // 最高价格(可选),NULL表示不限制最高价 + val pushFilteredOrders: Boolean? = null // 推送已过滤订单(默认关闭) ) /** @@ -50,7 +51,8 @@ data class TemplateUpdateRequest( val minOrderDepth: String? = null, // 最小订单深度(USDC金额),NULL表示不启用 val maxSpread: String? = null, // 最大价差(绝对价格),NULL表示不启用 val minPrice: String? = null, // 最低价格(可选),NULL表示不限制最低价 - val maxPrice: String? = null // 最高价格(可选),NULL表示不限制最高价 + val maxPrice: String? = null, // 最高价格(可选),NULL表示不限制最高价 + val pushFilteredOrders: Boolean? = null // 推送已过滤订单(默认关闭) ) /** @@ -84,7 +86,8 @@ data class TemplateCopyRequest( val minOrderDepth: String? = null, // 最小订单深度(USDC金额),NULL表示不启用 val maxSpread: String? = null, // 最大价差(绝对价格),NULL表示不启用 val minPrice: String? = null, // 最低价格(可选),NULL表示不限制最低价 - val maxPrice: String? = null // 最高价格(可选),NULL表示不限制最高价 + val maxPrice: String? = null, // 最高价格(可选),NULL表示不限制最高价 + val pushFilteredOrders: Boolean? = null // 推送已过滤订单(默认关闭) ) /** @@ -119,6 +122,7 @@ data class TemplateDto( val maxSpread: String?, val minPrice: String?, // 最低价格(可选),NULL表示不限制最低价 val maxPrice: String?, // 最高价格(可选),NULL表示不限制最高价 + val pushFilteredOrders: Boolean, // 推送已过滤订单(默认关闭) val createdAt: Long, val updatedAt: Long ) diff --git a/backend/src/main/kotlin/com/wrbug/polymarketbot/entity/CopyTrading.kt b/backend/src/main/kotlin/com/wrbug/polymarketbot/entity/CopyTrading.kt index ef202ba..4c94b4e 100644 --- a/backend/src/main/kotlin/com/wrbug/polymarketbot/entity/CopyTrading.kt +++ b/backend/src/main/kotlin/com/wrbug/polymarketbot/entity/CopyTrading.kt @@ -100,6 +100,9 @@ data class CopyTrading( @Column(name = "push_failed_orders", nullable = false) val pushFailedOrders: Boolean = false, // 推送失败订单(默认关闭) + @Column(name = "push_filtered_orders", nullable = false) + val pushFilteredOrders: Boolean = false, // 推送已过滤订单(默认关闭) + @Column(name = "max_market_end_date") val maxMarketEndDate: Long? = null, // 市场截止时间限制(毫秒时间戳),仅跟单截止时间小于此时间的订单,NULL表示不启用 diff --git a/backend/src/main/kotlin/com/wrbug/polymarketbot/entity/CopyTradingTemplate.kt b/backend/src/main/kotlin/com/wrbug/polymarketbot/entity/CopyTradingTemplate.kt index 866ce86..f72f42f 100644 --- a/backend/src/main/kotlin/com/wrbug/polymarketbot/entity/CopyTradingTemplate.kt +++ b/backend/src/main/kotlin/com/wrbug/polymarketbot/entity/CopyTradingTemplate.kt @@ -72,6 +72,9 @@ data class CopyTradingTemplate( @Column(name = "max_price", precision = 20, scale = 8) val maxPrice: BigDecimal? = null, // 最高价格(可选),NULL表示不限制最高价 + @Column(name = "push_filtered_orders", nullable = false) + val pushFilteredOrders: Boolean = false, // 推送已过滤订单(默认关闭) + @Column(name = "created_at", nullable = false) val createdAt: Long = System.currentTimeMillis(), diff --git a/backend/src/main/kotlin/com/wrbug/polymarketbot/service/copytrading/configs/CopyTradingService.kt b/backend/src/main/kotlin/com/wrbug/polymarketbot/service/copytrading/configs/CopyTradingService.kt index fcf25f8..e66e434 100644 --- a/backend/src/main/kotlin/com/wrbug/polymarketbot/service/copytrading/configs/CopyTradingService.kt +++ b/backend/src/main/kotlin/com/wrbug/polymarketbot/service/copytrading/configs/CopyTradingService.kt @@ -87,7 +87,8 @@ class CopyTradingService( maxPositionCount = request.maxPositionCount, keywordFilterMode = request.keywordFilterMode ?: "DISABLED", keywords = convertKeywordsToJson(request.keywords), - maxMarketEndDate = request.maxMarketEndDate + maxMarketEndDate = request.maxMarketEndDate, + pushFilteredOrders = request.pushFilteredOrders ?: template.pushFilteredOrders ) } else { // 手动输入(所有字段必须提供) @@ -118,7 +119,8 @@ class CopyTradingService( maxPositionCount = request.maxPositionCount, keywordFilterMode = request.keywordFilterMode ?: "DISABLED", keywords = convertKeywordsToJson(request.keywords), - maxMarketEndDate = request.maxMarketEndDate + maxMarketEndDate = request.maxMarketEndDate, + pushFilteredOrders = request.pushFilteredOrders ?: false // 手动输入时使用请求中的值,默认为 false ) } @@ -151,7 +153,8 @@ class CopyTradingService( keywords = config.keywords, configName = configName, pushFailedOrders = request.pushFailedOrders ?: false, - maxMarketEndDate = config.maxMarketEndDate + maxMarketEndDate = config.maxMarketEndDate, + pushFilteredOrders = config.pushFilteredOrders ) val saved = copyTradingRepository.save(copyTrading) @@ -283,6 +286,7 @@ class CopyTradingService( }, configName = configName, pushFailedOrders = request.pushFailedOrders ?: copyTrading.pushFailedOrders, + pushFilteredOrders = request.pushFilteredOrders ?: copyTrading.pushFilteredOrders, // 处理 maxMarketEndDate:-1 表示要清空(设置为 null),null 表示不更新 maxMarketEndDate = if (request.maxMarketEndDate != null) { if (request.maxMarketEndDate == -1L) { @@ -505,6 +509,7 @@ class CopyTradingService( keywords = convertJsonToKeywords(copyTrading.keywords), configName = copyTrading.configName, pushFailedOrders = copyTrading.pushFailedOrders, + pushFilteredOrders = copyTrading.pushFilteredOrders, maxMarketEndDate = copyTrading.maxMarketEndDate, createdAt = copyTrading.createdAt, updatedAt = copyTrading.updatedAt @@ -567,6 +572,7 @@ class CopyTradingService( val maxPositionCount: Int?, val keywordFilterMode: String, val keywords: String?, // JSON 字符串 - val maxMarketEndDate: Long? // 市场截止时间限制(毫秒时间戳) + val maxMarketEndDate: Long?, // 市场截止时间限制(毫秒时间戳) + val pushFilteredOrders: Boolean // 推送已过滤订单(默认关闭) ) } diff --git a/backend/src/main/kotlin/com/wrbug/polymarketbot/service/copytrading/statistics/CopyOrderTrackingService.kt b/backend/src/main/kotlin/com/wrbug/polymarketbot/service/copytrading/statistics/CopyOrderTrackingService.kt index 0e47913..83590f1 100644 --- a/backend/src/main/kotlin/com/wrbug/polymarketbot/service/copytrading/statistics/CopyOrderTrackingService.kt +++ b/backend/src/main/kotlin/com/wrbug/polymarketbot/service/copytrading/statistics/CopyOrderTrackingService.kt @@ -347,27 +347,29 @@ open class CopyOrderTrackingService( logger.error("保存被过滤订单失败: ${e.message}", e) } - // 发送 Telegram 通知 - val locale = try { - org.springframework.context.i18n.LocaleContextHolder.getLocale() - } catch (e: Exception) { - java.util.Locale("zh", "CN") // 默认简体中文 - } + // 发送 Telegram 通知(仅在 pushFilteredOrders 为 true 时发送) + if (copyTrading.pushFilteredOrders) { + val locale = try { + org.springframework.context.i18n.LocaleContextHolder.getLocale() + } catch (e: Exception) { + java.util.Locale("zh", "CN") // 默认简体中文 + } - telegramNotificationService?.sendOrderFilteredNotification( - marketTitle = marketTitle, - marketId = trade.market, - marketSlug = marketSlug, - side = "BUY", - outcome = trade.outcome, - price = trade.price, - size = trade.size, - filterReason = filterResult.reason, - filterType = filterType, - accountName = account.accountName, - walletAddress = account.walletAddress, - locale = locale - ) + telegramNotificationService?.sendOrderFilteredNotification( + marketTitle = marketTitle, + marketId = trade.market, + marketSlug = marketSlug, + side = "BUY", + outcome = trade.outcome, + price = trade.price, + size = trade.size, + filterReason = filterResult.reason, + filterType = filterType, + accountName = account.accountName, + walletAddress = account.walletAddress, + locale = locale + ) + } } catch (e: Exception) { logger.error("处理被过滤订单通知失败: ${e.message}", e) } diff --git a/backend/src/main/kotlin/com/wrbug/polymarketbot/service/copytrading/templates/CopyTradingTemplateService.kt b/backend/src/main/kotlin/com/wrbug/polymarketbot/service/copytrading/templates/CopyTradingTemplateService.kt index d5de87e..30c3fab 100644 --- a/backend/src/main/kotlin/com/wrbug/polymarketbot/service/copytrading/templates/CopyTradingTemplateService.kt +++ b/backend/src/main/kotlin/com/wrbug/polymarketbot/service/copytrading/templates/CopyTradingTemplateService.kt @@ -62,7 +62,8 @@ class CopyTradingTemplateService( minOrderDepth = request.minOrderDepth?.toSafeBigDecimal(), maxSpread = request.maxSpread?.toSafeBigDecimal(), minPrice = request.minPrice?.toSafeBigDecimal(), - maxPrice = request.maxPrice?.toSafeBigDecimal() + maxPrice = request.maxPrice?.toSafeBigDecimal(), + pushFilteredOrders = request.pushFilteredOrders ?: false ) val saved = templateRepository.save(template) @@ -121,6 +122,7 @@ class CopyTradingTemplateService( maxSpread = request.maxSpread?.toSafeBigDecimal() ?: template.maxSpread, minPrice = request.minPrice?.toSafeBigDecimal() ?: template.minPrice, maxPrice = request.maxPrice?.toSafeBigDecimal() ?: template.maxPrice, + pushFilteredOrders = request.pushFilteredOrders ?: template.pushFilteredOrders, updatedAt = System.currentTimeMillis() ) @@ -186,7 +188,8 @@ class CopyTradingTemplateService( minOrderDepth = request.minOrderDepth?.toSafeBigDecimal() ?: sourceTemplate.minOrderDepth, maxSpread = request.maxSpread?.toSafeBigDecimal() ?: sourceTemplate.maxSpread, minPrice = request.minPrice?.toSafeBigDecimal() ?: sourceTemplate.minPrice, - maxPrice = request.maxPrice?.toSafeBigDecimal() ?: sourceTemplate.maxPrice + maxPrice = request.maxPrice?.toSafeBigDecimal() ?: sourceTemplate.maxPrice, + pushFilteredOrders = request.pushFilteredOrders ?: sourceTemplate.pushFilteredOrders ) val saved = templateRepository.save(newTemplate) @@ -260,6 +263,7 @@ class CopyTradingTemplateService( maxSpread = template.maxSpread?.toPlainString(), minPrice = template.minPrice?.toPlainString(), maxPrice = template.maxPrice?.toPlainString(), + pushFilteredOrders = template.pushFilteredOrders, createdAt = template.createdAt, updatedAt = template.updatedAt ) diff --git a/backend/src/main/resources/db/migration/V24__add_push_filtered_orders_to_templates.sql b/backend/src/main/resources/db/migration/V24__add_push_filtered_orders_to_templates.sql new file mode 100644 index 0000000..49c2e74 --- /dev/null +++ b/backend/src/main/resources/db/migration/V24__add_push_filtered_orders_to_templates.sql @@ -0,0 +1,13 @@ +-- ============================================ +-- V24: 添加推送已过滤订单字段到模板表和跟单配置表 +-- 用于配置是否推送被过滤的订单通知,默认关闭 +-- ============================================ + +-- 添加推送已过滤订单字段到模板表 +ALTER TABLE copy_trading_templates +ADD COLUMN push_filtered_orders BOOLEAN NOT NULL DEFAULT FALSE COMMENT '推送已过滤订单(默认关闭)'; + +-- 添加推送已过滤订单字段到跟单配置表 +ALTER TABLE copy_trading +ADD COLUMN push_filtered_orders BOOLEAN NOT NULL DEFAULT FALSE COMMENT '推送已过滤订单(默认关闭)'; + diff --git a/frontend/src/locales/en/common.json b/frontend/src/locales/en/common.json index e430995..7505c00 100644 --- a/frontend/src/locales/en/common.json +++ b/frontend/src/locales/en/common.json @@ -571,6 +571,8 @@ "deleteConfirmDesc": "This action cannot be undone. Please ensure no copy trading relationships are using this template", "copySuccess": "Template copied successfully", "copyFailed": "Failed to copy template", + "pushFilteredOrders": "Push Filtered Orders", + "pushFilteredOrdersTooltip": "When enabled, filtered orders will be pushed to Telegram", "minAmountError": "Minimum amount must be >= 1", "fixedAmountRequired": "Please enter fixed copy trading amount", "invalidNumber": "Please enter a valid number", @@ -623,6 +625,8 @@ "maxPricePlaceholder": "Max Price (leave empty for no limit)", "supportSell": "Support Sell", "supportSellTooltip": "Whether to copy Leader's sell orders. Enabled: copy both Leader's buy and sell orders; Disabled: only copy Leader's buy orders, ignore sell orders.", + "pushFilteredOrders": "Push Filtered Orders", + "pushFilteredOrdersTooltip": "When enabled, filtered orders will be pushed to Telegram", "create": "Create Template", "createSuccess": "Template created successfully", "createFailed": "Failed to create template", @@ -687,6 +691,8 @@ "maxPositionCountPlaceholder": "For example: 10 (optional, leave empty to disable)", "supportSell": "Support Sell", "supportSellTooltip": "Whether to copy Leader's sell orders. Enabled: copy both Leader's buy and sell orders; Disabled: only copy Leader's buy orders, ignore sell orders.", + "pushFilteredOrders": "Push Filtered Orders", + "pushFilteredOrdersTooltip": "When enabled, filtered orders will be pushed to Telegram", "invalidNumber": "Please enter a valid number" }, "copyTradingAdd": { @@ -783,6 +789,8 @@ "advancedSettings": "Advanced Settings", "pushFailedOrders": "Push Failed Orders", "pushFailedOrdersTooltip": "When enabled, failed orders will be pushed to Telegram", + "pushFilteredOrders": "Push Filtered Orders", + "pushFilteredOrdersTooltip": "When enabled, filtered orders will be pushed to Telegram", "autoRedeem": "Auto Redeem", "autoRedeemTooltip": "When enabled, the system will automatically redeem redeemable positions. Requires Builder API Key configuration to take effect", "builderApiKeyNotConfigured": "Builder API Key Not Configured", @@ -888,6 +896,8 @@ "advancedSettings": "Advanced Settings", "pushFailedOrders": "Push Failed Orders", "pushFailedOrdersTooltip": "When enabled, failed orders will be pushed to Telegram", + "pushFilteredOrders": "Push Filtered Orders", + "pushFilteredOrdersTooltip": "When enabled, filtered orders will be pushed to Telegram", "autoRedeem": "Auto Redeem", "autoRedeemTooltip": "When enabled, the system will automatically redeem redeemable positions. Requires Builder API Key configuration to take effect", "builderApiKeyNotConfigured": "Builder API Key Not Configured", diff --git a/frontend/src/locales/zh-CN/common.json b/frontend/src/locales/zh-CN/common.json index 940ed7e..18dd747 100644 --- a/frontend/src/locales/zh-CN/common.json +++ b/frontend/src/locales/zh-CN/common.json @@ -571,6 +571,8 @@ "deleteConfirmDesc": "删除后无法恢复,请确保没有跟单关系在使用该模板", "copySuccess": "复制模板成功", "copyFailed": "复制模板失败", + "pushFilteredOrders": "推送已过滤订单", + "pushFilteredOrdersTooltip": "开启后,被过滤的订单会推送到 Telegram", "minAmountError": "最小金额必须 >= 1", "fixedAmountRequired": "请输入固定跟单金额", "invalidNumber": "请输入有效的数字", @@ -623,6 +625,8 @@ "maxPricePlaceholder": "最高价(留空不限制)", "supportSell": "跟单卖出", "supportSellTooltip": "是否跟单 Leader 的卖出订单。开启:跟单 Leader 的买入和卖出订单;关闭:只跟单 Leader 的买入订单,忽略卖出订单。", + "pushFilteredOrders": "推送已过滤订单", + "pushFilteredOrdersTooltip": "开启后,被过滤的订单会推送到 Telegram", "create": "创建模板", "createSuccess": "创建模板成功", "createFailed": "创建模板失败", @@ -687,6 +691,8 @@ "maxPositionCountPlaceholder": "例如:10(可选,不填写表示不启用)", "supportSell": "跟单卖出", "supportSellTooltip": "是否跟单 Leader 的卖出订单。开启:跟单 Leader 的买入和卖出订单;关闭:只跟单 Leader 的买入订单,忽略卖出订单。", + "pushFilteredOrders": "推送已过滤订单", + "pushFilteredOrdersTooltip": "开启后,被过滤的订单会推送到 Telegram", "invalidNumber": "请输入有效的数字" }, "copyTradingAdd": { @@ -699,6 +705,8 @@ "advancedSettings": "高级设置", "pushFailedOrders": "推送失败订单", "pushFailedOrdersTooltip": "开启后,失败的订单会推送到 Telegram", + "pushFilteredOrders": "推送已过滤订单", + "pushFilteredOrdersTooltip": "开启后,被过滤的订单会推送到 Telegram", "autoRedeem": "自动赎回", "autoRedeemTooltip": "开启后,系统会自动赎回可赎回的仓位。需要配置 Builder API Key 才能生效", "builderApiKeyNotConfigured": "Builder API Key 未配置", @@ -812,6 +820,8 @@ "advancedSettings": "高级设置", "pushFailedOrders": "推送失败订单", "pushFailedOrdersTooltip": "开启后,失败的订单会推送到 Telegram", + "pushFilteredOrders": "推送已过滤订单", + "pushFilteredOrdersTooltip": "开启后,被过滤的订单会推送到 Telegram", "autoRedeem": "自动赎回", "autoRedeemTooltip": "开启后,系统会自动赎回可赎回的仓位。需要配置 Builder API Key 才能生效", "builderApiKeyNotConfigured": "Builder API Key 未配置", diff --git a/frontend/src/locales/zh-TW/common.json b/frontend/src/locales/zh-TW/common.json index 12bc738..9536b8d 100644 --- a/frontend/src/locales/zh-TW/common.json +++ b/frontend/src/locales/zh-TW/common.json @@ -571,6 +571,8 @@ "deleteConfirmDesc": "刪除後無法恢復,請確保沒有跟單關係在使用該模板", "copySuccess": "複製模板成功", "copyFailed": "複製模板失敗", + "pushFilteredOrders": "推送已過濾訂單", + "pushFilteredOrdersTooltip": "開啟後,被過濾的訂單會推送到 Telegram", "minAmountError": "最小金額必須 >= 1", "fixedAmountRequired": "請輸入固定跟單金額", "invalidNumber": "請輸入有效的數字", @@ -623,6 +625,8 @@ "maxPricePlaceholder": "最高價(留空不限制)", "supportSell": "跟單賣出", "supportSellTooltip": "是否跟單 Leader 的賣出訂單。開啟:跟單 Leader 的買入和賣出訂單;關閉:只跟單 Leader 的買入訂單,忽略賣出訂單。", + "pushFilteredOrders": "推送已過濾訂單", + "pushFilteredOrdersTooltip": "開啟後,被過濾的訂單會推送到 Telegram", "create": "創建模板", "createSuccess": "創建模板成功", "createFailed": "創建模板失敗", @@ -687,6 +691,8 @@ "maxPositionCountPlaceholder": "例如:10(可選,不填寫表示不啟用)", "supportSell": "跟單賣出", "supportSellTooltip": "是否跟單 Leader 的賣出訂單。開啟:跟單 Leader 的買入和賣出訂單;關閉:只跟單 Leader 的買入訂單,忽略賣出訂單。", + "pushFilteredOrders": "推送已過濾訂單", + "pushFilteredOrdersTooltip": "開啟後,被過濾的訂單會推送到 Telegram", "invalidNumber": "請輸入有效的數字" }, "copyTradingAdd": { @@ -783,6 +789,8 @@ "advancedSettings": "高級設置", "pushFailedOrders": "推送失敗訂單", "pushFailedOrdersTooltip": "開啟後,失敗的訂單會推送到 Telegram", + "pushFilteredOrders": "推送已過濾訂單", + "pushFilteredOrdersTooltip": "開啟後,被過濾的訂單會推送到 Telegram", "autoRedeem": "自動贖回", "autoRedeemTooltip": "開啟後,系統會自動贖回可贖回的倉位。需要配置 Builder API Key 才能生效", "builderApiKeyNotConfigured": "Builder API Key 未配置", @@ -888,6 +896,8 @@ "advancedSettings": "高級設置", "pushFailedOrders": "推送失敗訂單", "pushFailedOrdersTooltip": "開啟後,失敗的訂單會推送到 Telegram", + "pushFilteredOrders": "推送已過濾訂單", + "pushFilteredOrdersTooltip": "開啟後,被過濾的訂單會推送到 Telegram", "autoRedeem": "自動贖回", "autoRedeemTooltip": "開啟後,系統會自動贖回可贖回的倉位。需要配置 Builder API Key 才能生效", "builderApiKeyNotConfigured": "Builder API Key 未配置", diff --git a/frontend/src/pages/CopyTradingOrders/AddModal.tsx b/frontend/src/pages/CopyTradingOrders/AddModal.tsx index 5cd3dbe..ad5e6b8 100644 --- a/frontend/src/pages/CopyTradingOrders/AddModal.tsx +++ b/frontend/src/pages/CopyTradingOrders/AddModal.tsx @@ -120,7 +120,8 @@ const AddModal: React.FC = ({ minPrice: template.minPrice ? parseFloat(template.minPrice) : undefined, maxPrice: template.maxPrice ? parseFloat(template.maxPrice) : undefined, maxPositionValue: (template as any).maxPositionValue ? parseFloat((template as any).maxPositionValue) : undefined, - maxPositionCount: (template as any).maxPositionCount + maxPositionCount: (template as any).maxPositionCount, + pushFilteredOrders: template.pushFilteredOrders ?? false }) setCopyMode(template.copyMode) setTemplateModalVisible(false) @@ -255,6 +256,7 @@ const AddModal: React.FC = ({ : undefined, configName: values.configName?.trim(), pushFailedOrders: values.pushFailedOrders ?? false, + pushFilteredOrders: values.pushFilteredOrders ?? false, maxMarketEndDate } @@ -307,6 +309,7 @@ const AddModal: React.FC = ({ websocketMaxRetries: 10, supportSell: true, pushFailedOrders: false, + pushFilteredOrders: false, keywordFilterMode: 'DISABLED' }} > @@ -804,18 +807,35 @@ const AddModal: React.FC = ({ > setMaxMarketEndDateValue(value !== null && value !== undefined ? Math.floor(value) : undefined)} + onChange={(value) => { + // 允许设置为 null 或 undefined(清空) + if (value === null || value === undefined) { + setMaxMarketEndDateValue(undefined) + } else { + const num = Math.floor(value) + // 如果值为 0,也设置为 undefined(表示清空) + setMaxMarketEndDateValue(num > 0 ? num : undefined) + } + }} + onBlur={(e) => { + // 失去焦点时,如果值为 0 或空,设置为 undefined + const input = e.target as HTMLInputElement + const value = input.value + if (!value || value === '0') { + setMaxMarketEndDateValue(undefined) + } + }} style={{ width: '60%' }} placeholder={t('copyTradingAdd.maxMarketEndDatePlaceholder') || '输入时间值(可选)'} parser={(value) => { - if (!value) return 0 + if (!value) return '' const num = parseInt(value.replace(/\D/g, ''), 10) - return isNaN(num) ? 0 : num + return isNaN(num) ? '' : num.toString() }} formatter={(value) => { if (!value && value !== 0) return '' @@ -862,6 +882,16 @@ const AddModal: React.FC = ({ + {/* 推送已过滤订单 */} + + + +