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 c334fbe..fcf25f8 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 @@ -10,6 +10,7 @@ import com.wrbug.polymarketbot.repository.CopyTradingTemplateRepository import com.wrbug.polymarketbot.repository.LeaderRepository import com.wrbug.polymarketbot.service.copytrading.monitor.CopyTradingMonitorService import com.google.gson.Gson +import com.wrbug.polymarketbot.util.IllegalBigDecimal import com.wrbug.polymarketbot.util.JsonUtils import com.wrbug.polymarketbot.util.toSafeBigDecimal import org.slf4j.LoggerFactory @@ -211,12 +212,67 @@ class CopyTradingService( websocketReconnectInterval = request.websocketReconnectInterval ?: copyTrading.websocketReconnectInterval, websocketMaxRetries = request.websocketMaxRetries ?: copyTrading.websocketMaxRetries, supportSell = request.supportSell ?: copyTrading.supportSell, - minOrderDepth = request.minOrderDepth?.toSafeBigDecimal() ?: copyTrading.minOrderDepth, - maxSpread = request.maxSpread?.toSafeBigDecimal() ?: copyTrading.maxSpread, - minPrice = request.minPrice?.toSafeBigDecimal() ?: copyTrading.minPrice, - maxPrice = request.maxPrice?.toSafeBigDecimal() ?: copyTrading.maxPrice, - maxPositionValue = request.maxPositionValue?.toSafeBigDecimal() ?: copyTrading.maxPositionValue, - maxPositionCount = request.maxPositionCount ?: copyTrading.maxPositionCount, + // 处理可选字段:空字符串表示要清空(设置为 null),null 表示不更新,转换失败保留旧值 + minOrderDepth = if (request.minOrderDepth != null) { + if (request.minOrderDepth.isBlank()) { + null + } else { + val converted = request.minOrderDepth.toSafeBigDecimal() + if (converted == IllegalBigDecimal) copyTrading.minOrderDepth else converted + } + } else { + copyTrading.minOrderDepth + }, + maxSpread = if (request.maxSpread != null) { + if (request.maxSpread.isBlank()) { + null + } else { + val converted = request.maxSpread.toSafeBigDecimal() + if (converted == IllegalBigDecimal) copyTrading.maxSpread else converted + } + } else { + copyTrading.maxSpread + }, + minPrice = if (request.minPrice != null) { + if (request.minPrice.isBlank()) { + null + } else { + val converted = request.minPrice.toSafeBigDecimal() + if (converted == IllegalBigDecimal) copyTrading.minPrice else converted + } + } else { + copyTrading.minPrice + }, + maxPrice = if (request.maxPrice != null) { + if (request.maxPrice.isBlank()) { + null + } else { + val converted = request.maxPrice.toSafeBigDecimal() + if (converted == IllegalBigDecimal) copyTrading.maxPrice else converted + } + } else { + copyTrading.maxPrice + }, + maxPositionValue = if (request.maxPositionValue != null) { + if (request.maxPositionValue.isBlank()) { + null + } else { + val converted = request.maxPositionValue.toSafeBigDecimal() + if (converted == IllegalBigDecimal) copyTrading.maxPositionValue else converted + } + } else { + copyTrading.maxPositionValue + }, + // 处理 maxPositionCount:-1 表示要清空(设置为 null),null 表示不更新 + maxPositionCount = if (request.maxPositionCount != null) { + if (request.maxPositionCount == -1) { + null + } else { + request.maxPositionCount + } + } else { + copyTrading.maxPositionCount + }, keywordFilterMode = request.keywordFilterMode ?: copyTrading.keywordFilterMode, keywords = if (request.keywords != null) { convertKeywordsToJson(request.keywords) @@ -227,7 +283,16 @@ class CopyTradingService( }, configName = configName, pushFailedOrders = request.pushFailedOrders ?: copyTrading.pushFailedOrders, - maxMarketEndDate = request.maxMarketEndDate ?: copyTrading.maxMarketEndDate, + // 处理 maxMarketEndDate:-1 表示要清空(设置为 null),null 表示不更新 + maxMarketEndDate = if (request.maxMarketEndDate != null) { + if (request.maxMarketEndDate == -1L) { + null + } else { + request.maxMarketEndDate + } + } else { + copyTrading.maxMarketEndDate + }, updatedAt = System.currentTimeMillis() ) diff --git a/frontend/src/pages/CopyTradingOrders/EditModal.tsx b/frontend/src/pages/CopyTradingOrders/EditModal.tsx index a4a67bb..61ba5ff 100644 --- a/frontend/src/pages/CopyTradingOrders/EditModal.tsx +++ b/frontend/src/pages/CopyTradingOrders/EditModal.tsx @@ -168,12 +168,17 @@ const EditModal: React.FC = ({ } // 计算市场截止时间(毫秒) + // 如果用户清空了,传 -1 表示要清空(后端会识别并设置为 null) let maxMarketEndDate: number | undefined - if (maxMarketEndDateValue !== undefined && maxMarketEndDateValue > 0) { + if (maxMarketEndDateValue !== undefined && maxMarketEndDateValue !== null && maxMarketEndDateValue > 0) { const multiplier = maxMarketEndDateUnit === 'HOUR' ? 60 * 60 * 1000 // 小时转毫秒 : 24 * 60 * 60 * 1000 // 天转毫秒 maxMarketEndDate = maxMarketEndDateValue * multiplier + } else { + // 如果值为 null/undefined/0/负数,传 -1 表示要清空 + // 这样无论之前是否有值,清空后都会设置为 null + maxMarketEndDate = -1 } setLoading(true) @@ -195,12 +200,14 @@ const EditModal: React.FC = ({ websocketReconnectInterval: values.websocketReconnectInterval, websocketMaxRetries: values.websocketMaxRetries, supportSell: values.supportSell, - minOrderDepth: values.minOrderDepth?.toString(), - maxSpread: values.maxSpread?.toString(), - minPrice: values.minPrice?.toString(), - maxPrice: values.maxPrice?.toString(), - maxPositionValue: values.maxPositionValue?.toString(), - maxPositionCount: values.maxPositionCount, + // 对于可选字段,始终发送(即使为空也发送空字符串,让后端知道要清空) + minOrderDepth: values.minOrderDepth != null ? values.minOrderDepth.toString() : '', + maxSpread: values.maxSpread != null ? values.maxSpread.toString() : '', + minPrice: values.minPrice != null ? values.minPrice.toString() : '', + maxPrice: values.maxPrice != null ? values.maxPrice.toString() : '', + maxPositionValue: values.maxPositionValue != null ? values.maxPositionValue.toString() : '', + // 对于 maxPositionCount,如果值为 null/undefined,传 -1 表示要清空(后端会识别并设置为 null) + maxPositionCount: values.maxPositionCount != null ? values.maxPositionCount : -1, keywordFilterMode: values.keywordFilterMode || 'DISABLED', keywords: (values.keywordFilterMode === 'WHITELIST' || values.keywordFilterMode === 'BLACKLIST') ? keywords @@ -722,18 +729,35 @@ const EditModal: 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('copyTradingEdit.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 ''