feat: 添加推送已过滤订单功能并修复相关问题
- 新增推送已过滤订单功能(pushFilteredOrders),默认关闭 - 数据库迁移:添加 pushFilteredOrders 字段到模板表和跟单配置表 - 后端:在实体、DTO、Service 中添加 pushFilteredOrders 字段支持 - 前端:在模板新增、编辑、推送页面和跟单配置新增、编辑页面添加开关 - 多语言:添加中文、繁体中文、英文翻译 - 通知逻辑:发送过滤订单通知时检查 pushFilteredOrders 字段 - 修复新建跟单配置时 pushFilteredOrders 字段未生效的问题 - 修复手动输入模式下 pushFilteredOrders 被硬编码为 false 的问题 - 修复从模板填充表单时未加载 pushFilteredOrders 的问题 - 添加 CopyTradingTemplate 接口的 pushFilteredOrders 字段定义 - 修复新建和编辑页面的截止时间输入框交互问题 - 修复删除后失去焦点自动填充1的问题 - 优化 onChange 和 onBlur 处理逻辑,支持清空操作
This commit is contained in:
@@ -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",
|
||||
|
||||
@@ -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 未配置",
|
||||
|
||||
@@ -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 未配置",
|
||||
|
||||
@@ -120,7 +120,8 @@ const AddModal: React.FC<AddModalProps> = ({
|
||||
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<AddModalProps> = ({
|
||||
: undefined,
|
||||
configName: values.configName?.trim(),
|
||||
pushFailedOrders: values.pushFailedOrders ?? false,
|
||||
pushFilteredOrders: values.pushFilteredOrders ?? false,
|
||||
maxMarketEndDate
|
||||
}
|
||||
|
||||
@@ -307,6 +309,7 @@ const AddModal: React.FC<AddModalProps> = ({
|
||||
websocketMaxRetries: 10,
|
||||
supportSell: true,
|
||||
pushFailedOrders: false,
|
||||
pushFilteredOrders: false,
|
||||
keywordFilterMode: 'DISABLED'
|
||||
}}
|
||||
>
|
||||
@@ -804,18 +807,35 @@ const AddModal: React.FC<AddModalProps> = ({
|
||||
>
|
||||
<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('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<AddModalProps> = ({
|
||||
<Switch />
|
||||
</Form.Item>
|
||||
|
||||
{/* 推送已过滤订单 */}
|
||||
<Form.Item
|
||||
label={t('copyTradingAdd.pushFilteredOrders') || '推送已过滤订单'}
|
||||
name="pushFilteredOrders"
|
||||
tooltip={t('copyTradingAdd.pushFilteredOrdersTooltip') || '开启后,被过滤的订单会推送到 Telegram'}
|
||||
valuePropName="checked"
|
||||
>
|
||||
<Switch />
|
||||
</Form.Item>
|
||||
|
||||
<Form.Item>
|
||||
<Space>
|
||||
<Button
|
||||
|
||||
@@ -91,7 +91,8 @@ const EditModal: React.FC<EditModalProps> = ({
|
||||
maxPositionCount: found.maxPositionCount,
|
||||
keywordFilterMode: found.keywordFilterMode || 'DISABLED',
|
||||
configName: found.configName || '',
|
||||
pushFailedOrders: found.pushFailedOrders ?? false
|
||||
pushFailedOrders: found.pushFailedOrders ?? false,
|
||||
pushFilteredOrders: found.pushFilteredOrders ?? false
|
||||
})
|
||||
// 设置关键字列表
|
||||
setKeywords(found.keywords || [])
|
||||
@@ -214,6 +215,7 @@ const EditModal: React.FC<EditModalProps> = ({
|
||||
: undefined,
|
||||
configName: values.configName?.trim() || undefined,
|
||||
pushFailedOrders: values.pushFailedOrders,
|
||||
pushFilteredOrders: values.pushFilteredOrders,
|
||||
maxMarketEndDate
|
||||
}
|
||||
|
||||
@@ -802,6 +804,15 @@ const EditModal: React.FC<EditModalProps> = ({
|
||||
<Switch />
|
||||
</Form.Item>
|
||||
|
||||
<Form.Item
|
||||
label={t('copyTradingEdit.pushFilteredOrders') || '推送已过滤订单'}
|
||||
name="pushFilteredOrders"
|
||||
tooltip={t('copyTradingEdit.pushFilteredOrdersTooltip') || '开启后,被过滤的订单会推送到 Telegram'}
|
||||
valuePropName="checked"
|
||||
>
|
||||
<Switch />
|
||||
</Form.Item>
|
||||
|
||||
<Form.Item>
|
||||
<Space>
|
||||
<Button
|
||||
|
||||
@@ -55,7 +55,8 @@ const TemplateAdd: React.FC = () => {
|
||||
minOrderDepth: values.minOrderDepth?.toString(),
|
||||
maxSpread: values.maxSpread?.toString(),
|
||||
minPrice: values.minPrice?.toString(),
|
||||
maxPrice: values.maxPrice?.toString()
|
||||
maxPrice: values.maxPrice?.toString(),
|
||||
pushFilteredOrders: values.pushFilteredOrders ?? false
|
||||
})
|
||||
|
||||
if (response.data.code === 0) {
|
||||
@@ -96,7 +97,8 @@ const TemplateAdd: React.FC = () => {
|
||||
minOrderSize: 1,
|
||||
maxDailyOrders: 100,
|
||||
priceTolerance: 5,
|
||||
supportSell: true
|
||||
supportSell: true,
|
||||
pushFilteredOrders: false
|
||||
}}
|
||||
>
|
||||
<Form.Item
|
||||
@@ -373,6 +375,15 @@ const TemplateAdd: React.FC = () => {
|
||||
<Switch />
|
||||
</Form.Item>
|
||||
|
||||
<Form.Item
|
||||
label={t('templateAdd.pushFilteredOrders') || '推送已过滤订单'}
|
||||
name="pushFilteredOrders"
|
||||
tooltip={t('templateAdd.pushFilteredOrdersTooltip') || '开启后,被过滤的订单会推送到 Telegram'}
|
||||
valuePropName="checked"
|
||||
>
|
||||
<Switch />
|
||||
</Form.Item>
|
||||
|
||||
<Form.Item shouldUpdate>
|
||||
{({ getFieldsError }) => {
|
||||
const errors = getFieldsError()
|
||||
|
||||
@@ -41,7 +41,8 @@ const TemplateEdit: React.FC = () => {
|
||||
minOrderDepth: template.minOrderDepth ? parseFloat(template.minOrderDepth) : undefined,
|
||||
maxSpread: template.maxSpread ? parseFloat(template.maxSpread) : undefined,
|
||||
minPrice: template.minPrice ? parseFloat(template.minPrice) : undefined,
|
||||
maxPrice: template.maxPrice ? parseFloat(template.maxPrice) : undefined
|
||||
maxPrice: template.maxPrice ? parseFloat(template.maxPrice) : undefined,
|
||||
pushFilteredOrders: template.pushFilteredOrders ?? false
|
||||
})
|
||||
} else {
|
||||
message.error(response.data.msg || t('templateEdit.fetchFailed') || '获取模板详情失败')
|
||||
@@ -99,7 +100,8 @@ const TemplateEdit: React.FC = () => {
|
||||
minOrderDepth: values.minOrderDepth?.toString(),
|
||||
maxSpread: values.maxSpread?.toString(),
|
||||
minPrice: values.minPrice?.toString(),
|
||||
maxPrice: values.maxPrice?.toString()
|
||||
maxPrice: values.maxPrice?.toString(),
|
||||
pushFilteredOrders: values.pushFilteredOrders
|
||||
})
|
||||
|
||||
if (response.data.code === 0) {
|
||||
@@ -409,6 +411,15 @@ const TemplateEdit: React.FC = () => {
|
||||
<Switch />
|
||||
</Form.Item>
|
||||
|
||||
<Form.Item
|
||||
label={t('templateEdit.pushFilteredOrders') || '推送已过滤订单'}
|
||||
name="pushFilteredOrders"
|
||||
tooltip={t('templateEdit.pushFilteredOrdersTooltip') || '开启后,被过滤的订单会推送到 Telegram'}
|
||||
valuePropName="checked"
|
||||
>
|
||||
<Switch />
|
||||
</Form.Item>
|
||||
|
||||
<Form.Item shouldUpdate>
|
||||
{({ getFieldsError }) => {
|
||||
const errors = getFieldsError()
|
||||
|
||||
@@ -72,6 +72,7 @@ const TemplateList: React.FC = () => {
|
||||
maxDailyOrders: template.maxDailyOrders,
|
||||
priceTolerance: parseFloat(template.priceTolerance),
|
||||
supportSell: template.supportSell,
|
||||
pushFilteredOrders: template.pushFilteredOrders ?? false,
|
||||
minOrderDepth: template.minOrderDepth ? parseFloat(template.minOrderDepth) : undefined,
|
||||
maxSpread: template.maxSpread ? parseFloat(template.maxSpread) : undefined,
|
||||
minPrice: template.minPrice ? parseFloat(template.minPrice) : undefined,
|
||||
@@ -122,7 +123,8 @@ const TemplateList: React.FC = () => {
|
||||
minOrderDepth: values.minOrderDepth?.toString(),
|
||||
maxSpread: values.maxSpread?.toString(),
|
||||
minPrice: values.minPrice?.toString(),
|
||||
maxPrice: values.maxPrice?.toString()
|
||||
maxPrice: values.maxPrice?.toString(),
|
||||
pushFilteredOrders: values.pushFilteredOrders ?? false
|
||||
})
|
||||
|
||||
if (response.data.code === 0) {
|
||||
@@ -631,6 +633,15 @@ const TemplateList: React.FC = () => {
|
||||
<Switch />
|
||||
</Form.Item>
|
||||
|
||||
<Form.Item
|
||||
label={t('templateList.pushFilteredOrders') || '推送已过滤订单'}
|
||||
name="pushFilteredOrders"
|
||||
tooltip={t('templateList.pushFilteredOrdersTooltip') || '开启后,被过滤的订单会推送到 Telegram'}
|
||||
valuePropName="checked"
|
||||
>
|
||||
<Switch />
|
||||
</Form.Item>
|
||||
|
||||
<Divider>过滤条件(可选)</Divider>
|
||||
|
||||
<Form.Item
|
||||
|
||||
@@ -116,6 +116,7 @@ export interface CopyTradingTemplate {
|
||||
maxSpread?: string
|
||||
minPrice?: string // 最低价格(可选),NULL表示不限制最低价
|
||||
maxPrice?: string // 最高价格(可选),NULL表示不限制最高价
|
||||
pushFilteredOrders?: boolean // 推送已过滤订单(默认关闭)
|
||||
createdAt: number
|
||||
updatedAt: number
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user