fix: 修复跟单配置更新时清空可选字段无法保存的问题

- 修复价格区间(minPrice/maxPrice)清空后无法保存的问题
- 修复最大仓位数量(maxPositionCount)清空后无法保存的问题
- 修复市场截止时间(maxMarketEndDate)清空后无法保存的问题
- 修复其他可选字段(minOrderDepth/maxSpread/maxPositionValue)清空后无法保存的问题
- 修复截止时间输入框删除后失去焦点自动填充1的问题

前端:清空字段时传空字符串或-1标记,让后端识别为清空操作
后端:处理空字符串和-1标记,正确设置为null以清空字段
This commit is contained in:
WrBug
2026-01-12 13:31:27 +08:00
parent 279806da2b
commit cb8e46919f
2 changed files with 107 additions and 18 deletions
@@ -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()
)
@@ -168,12 +168,17 @@ const EditModal: React.FC<EditModalProps> = ({
}
// 计算市场截止时间(毫秒)
// 如果用户清空了,传 -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<EditModalProps> = ({
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<EditModalProps> = ({
>
<Input.Group compact style={{ display: 'flex' }}>
<InputNumber
min={1}
min={0}
max={9999}
step={1}
precision={0}
value={maxMarketEndDateValue}
onChange={(value) => 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 ''