This commit is contained in:
2025-03-12 16:35:54 +08:00
parent 1069707953
commit 443778cf36
2 changed files with 779 additions and 310 deletions

View File

@ -150,7 +150,7 @@
/> />
<!-- 添加或修改对话框 --> <!-- 添加或修改对话框 -->
<el-dialog :title="title" :visible.sync="open" width="1200px" :close-on-click-modal="false"> <el-dialog :title="title" :visible.sync="open" width="1300px" :close-on-click-modal="false">
<el-form ref="form" :model="form" :rules="rules" label-width="80px"> <el-form ref="form" :model="form" :rules="rules" label-width="80px">
<el-form-item label="模板名称" prop="name"> <el-form-item label="模板名称" prop="name">
<el-input v-model="form.name" placeholder="模板名称" /> <el-input v-model="form.name" placeholder="模板名称" />
@ -428,109 +428,151 @@
@click="onCalculate" @click="onCalculate"
>计算</el-button></el-col> >计算</el-button></el-col>
</el-row> </el-row>
<div v-for="(item, index) in inForm.ext" :key="index" class="exts"> <el-form ref="extForm" :model="inForm" label-position="top" :rules="inRules" class="ext-form">
<el-row v-if="item.addType === 1" :gutter="20" style="margin-bottom: 20px;"> <div v-for="(item, index) in inForm.ext" :key="index" class="exts">
<el-col :span="5"> <el-row v-if="item.addType === 1" :gutter="20" style="margin-bottom: 20px;">
<div style="margin-bottom: 8px;">{{ item.addType === 1 ? '加仓类型' : '减仓类型' }}</div> <el-col :span="5">
<el-radio-group v-model="item.orderType" size="mini"> <div style="margin-bottom: 8px;">{{ item.addType === 1 ? '加仓类型' : '减仓类型' }}</div>
<el-radio label="LIMIT">限价</el-radio> <el-radio-group v-model="item.orderType" size="mini">
<el-radio label="MARKET"></el-radio> <el-radio label="LIMIT"></el-radio>
</el-radio-group> <el-radio label="MARKET">市价</el-radio>
</el-col> </el-radio-group>
<el-col :span="5"> </el-col>
<div style="margin-bottom: 8px;">{{ item.addType === 1 ? '加仓方式' : '减仓方式' }}</div> <el-col :span="5">
<el-radio-group v-model="item.addPositionType" size="mini"> <div style="margin-bottom: 8px;">{{ item.addType === 1 ? '加仓方式' : '减仓方式' }}</div>
<el-radio :label="1">百分比</el-radio> <el-radio-group v-model="item.addPositionType" size="mini">
<el-radio :label="2" :disabled="item.addType === 2">实际金额</el-radio> <el-radio :label="1">百分比</el-radio>
</el-radio-group> <el-radio :label="2" :disabled="item.addType === 2">实际金额</el-radio>
</el-col> </el-radio-group>
</el-col>
</el-row> </el-row>
<el-row :gutter="20"> <el-row :gutter="20">
<el-col :span="3">{{ item.addType === 1 ? '加仓下跌百分比' : '减仓下跌百分比' }}
<el-input
v-model.number="item.priceRatio"
min="0"
size="mini"
type="number"
:placeholder="item.addType === 1 ? '加仓下跌百分比' : '减仓下跌百分比'"
/></el-col>
<el-col :span="3">{{ item.addType === 1 ? '加仓数值' : '减仓百分比' }}
<el-input
v-model.number="item.addPositionVal"
min="0"
size="mini"
type="number"
:placeholder="item.addType === 1 ? '加仓数值' : '减仓百分比'"
/>
</el-col>
<el-col :span="6">
<span>止盈/止损百分比</span>
<div>
<el-input
v-model="item.takeProfitRatio"
min="0"
style="width:50%;"
size="mini"
type="number"
placeholder="止盈百分比"
>
<template v-if="form.type === 1" #append>{{ ext[index] ? ext[index].reTakeProfitRatio : 0
}}</template>
</el-input>
<el-input
v-model.number="item.stopLossRatio"
style="width:42%;"
size="mini"
type="number"
placeholder="止损百分比"
/>
</div>
</el-col>
<!-- <el-col :span="3">止损百分比<el-input v-model.number="item.stopLossRatio" size="mini" type="number" placeholder="止损百分比" /></el-col> -->
<template v-if="item.addType === 1">
<el-col :span="3"> <el-col :span="3">
止盈数量百分比<el-input <!-- {{ item.addType === 1 ? '加仓下跌百分比' : '减仓下跌百分比' }} -->
v-model.number="item.takeProfitNumRatio" <el-form-item
size="mini" :prop="'ext.' + index + '.priceRatio'"
type="number" :rules="inRules.priceRatio"
placeholder="止盈数量百分比" class="ext-form-item"
/> :label="item.addType === 1 ? '加仓下跌百分比' : '减仓下跌百分比'"
>
<el-input
v-model.number="item.priceRatio"
min="0"
size="mini"
type="number"
:placeholder="item.addType === 1 ? '加仓下跌百分比' : '减仓下跌百分比'"
/>
</el-form-item>
</el-col>
<el-col :span="3">
<!-- {{ item.addType === 1 ? '加仓数值' : '减仓百分比' }} -->
<el-form-item
:prop="'ext.' + index + '.addPositionVal'"
:label="item.addType === 1 ? '加仓数值' : '减仓百分比'"
:rules="inRules.addPositionVal"
class="ext-form-item"
>
<el-input
v-model.number="item.addPositionVal"
min="0"
size="mini"
type="number"
:placeholder="item.addType === 1 ? '加仓数值' : '减仓百分比'"
/>
</el-form-item>
</el-col> </el-col>
<el-col :span="6"> <el-col :span="6">
<span>第二止盈/止损百分比</span> <!-- <span>止盈/止损百分比</span> -->
<div> <el-form-item
<el-input label="止盈/止损百分比"
v-model.number="item.tpTpPriceRatio" :prop="'ext.' + index + '.takeProfitRatio'"
min="0" :rules="[{ validator: validateTakeProfitAndStopLoss, trigger: 'blur' }]"
style="width:42%" class="ext-form-item"
size="mini" >
type="number" <div>
placeholder="第二止盈价百分比" <el-input
/> v-model="item.takeProfitRatio"
min="0"
<el-input style="width:50%;"
v-model.number="item.tpSlPriceRatio" size="mini"
min="0" type="number"
style="width:42%" placeholder="止盈百分比"
size="mini" >
type="number" <template v-if="form.type === 1" #append>{{ ext[index] ? ext[index].reTakeProfitRatio : 0
placeholder="第二止损价百分比" }}</template>
/> </el-input>
</div>
<el-input
v-model.number="item.stopLossRatio"
style="width:42%;"
size="mini"
type="number"
placeholder="止损百分比"
/>
</div>
</el-form-item>
</el-col> </el-col>
</template> <!-- <el-col :span="3">止损百分比<el-input v-model.number="item.stopLossRatio" size="mini" type="number" placeholder="止损百分比" /></el-col> -->
<el-col :span="3">
<div style="margin-bottom: 14px;" /><el-button <template v-if="item.addType === 1">
size="mini" <el-col :span="3">
type="danger" <!-- 止盈数量百分比 -->
@click="onAddExt(1, index)" <el-form-item
>删除</el-button> label="止盈数量百分比"
</el-col> :prop="'ext.' + index + '.takeProfitNumRatio'"
</el-row> :rules="inRules.takeProfitNumRatio"
</div> class="ext-form-item"
>
<el-input
v-model.number="item.takeProfitNumRatio"
size="mini"
type="number"
placeholder="止盈数量百分比"
/>
</el-form-item>
</el-col>
<el-col :span="6">
<!-- <span>第二止盈/止损百分比</span> -->
<el-form-item
label="第二止盈/止损百分比"
:prop="'ext.' + index + '.tpTpPriceRatio'"
:rules="[{ validator: validateTp, trigger: 'blur' }]"
class="ext-form-item"
>
<div>
<el-input
v-model.number="item.tpTpPriceRatio"
min="0"
style="width:42%"
size="mini"
type="number"
placeholder="第二止盈价百分比"
/>
<el-input
v-model.number="item.tpSlPriceRatio"
min="0"
style="width:42%"
size="mini"
type="number"
placeholder="第二止损价百分比"
/>
</div>
</el-form-item>
</el-col>
</template>
<el-col :span="3">
<div style="margin-bottom: 14px;" /><el-button
size="mini"
type="danger"
@click="onAddExt(1, index)"
>删除</el-button>
</el-col>
</el-row>
</div>
</el-form>
<!-- <el-form-item label="是否保存模板" prop="save_template"> <!-- <el-form-item label="是否保存模板" prop="save_template">
<el-radio-group v-model="inForm.save_template"> <el-radio-group v-model="inForm.save_template">
<el-radio label="0"></el-radio> <el-radio label="0"></el-radio>
@ -636,17 +678,53 @@ export default {
percenter: undefined, percenter: undefined,
symbolGroups: [], symbolGroups: [],
inRules: { inRules: {
reduce_price: [{ required: true, message: '主单亏损减仓百分比不能为空', trigger: 'blur' }], profit: [{ required: true, message: '请输入止盈百分比', trigger: 'blur' },
reduce_num: [{ required: true, message: '主单减仓数量百分比不能为空', trigger: 'blur' }], { validator: this.validateProfit, trigger: 'blur' }],
reduce_take_profit: [{ required: true, message: '主单减仓后止盈价百分比不能为空', trigger: 'blur' }], reduce_price: [
// reduce_stop_price: [{ required: true, message: '主单减仓后止损价不能为空', trigger: 'blur' }], { required: true, message: '主单亏损减仓百分比不能为空', trigger: 'blur' },
buy_price: [{ required: true, message: '购买金额不能为空', trigger: 'blur' }], { validator: this.validateReducePrice, trigger: 'blur' }],
reduce_num: [
{ required: true, message: '主单减仓数量百分比不能为空', trigger: 'blur' },
{ validator: this.validateReduceNum, trigger: 'blur' }],
reduce_take_profit: [
{ required: true, message: '主单减仓后止盈价百分比不能为空', trigger: 'blur' },
{ validator: this.validateReduceTakeProfit, trigger: 'blur' }],
buy_price: [
{ required: true, message: '购买金额不能为空', trigger: 'blur' },
{ validator: this.validateBuyPrice, trigger: 'blur' }
],
exchange_type: [{ required: true, message: '交易所不能为空', trigger: 'blur' }], exchange_type: [{ required: true, message: '交易所不能为空', trigger: 'blur' }],
api_id: [{ required: true, message: 'api用户不能为空', trigger: 'blur' }], api_id: [{ required: true, message: 'api用户不能为空', trigger: 'blur' }],
symbol: [{ required: true, message: '交易对不能为空', trigger: 'blur' }], symbol: [{ required: true, message: '交易对不能为空', trigger: 'blur' }],
symbol_group_id: [{ required: true, message: '交易对组不能为空', trigger: 'blur' }], symbol_group_id: [{ required: true, message: '交易对组不能为空', trigger: 'blur' }],
quote_symbol: [{ required: true, message: '计货币不能为空', trigger: 'blur' }], quote_symbol: [{ required: true, message: '计货币不能为空', trigger: 'blur' }],
order_sn: [{ required: true, message: '订单号不能为空', trigger: 'blur' }] order_sn: [{ required: true, message: '订单号不能为空', trigger: 'blur' }],
profit_num_ratio: [
{ required: true, message: '止盈数量百分比不能为空', trigger: 'blur' },
{ validator: this.validateProfitNumRatio, trigger: 'blur' }
],
profit_tp_tp_price_ratio: [{ validator: this.validateTptpPriceRatio, trigger: 'blur' }],
priceRatio: [{ required: true, message: '下跌百分比不能为空', trigger: 'blur' }, { validator: this.validateExtPriceRatio, trigger: 'blur' }],
addPositionVal: [
{ required: true, message: '追加仓位不能为空', trigger: 'blur' },
{ validator: this.validateAddPosition, trigger: 'blur' }],
takeProfitRatio: [
{ required: true, message: '请输入止盈百分比', trigger: 'blur' },
{ type: 'number', message: '止盈百分比必须为数字', trigger: 'blur' },
{ min: 0, message: '止盈百分比不能小于 0', trigger: 'blur' }
],
stopLossRatio: [
{ required: true, message: '请输入止损百分比', trigger: 'blur' },
{ type: 'number', message: '止损百分比必须为数字', trigger: 'blur' },
{ min: 0, message: '止损百分比不能小于 0', trigger: 'blur' }
],
takeProfitNumRatio: [
{ required: true, message: '止盈数量不能为空', trigger: 'blur' },
{ validator: this.validateExtTakeProfitNumRatio, trigger: 'blur' }
],
tpTpPriceRatio: [{ required: true, message: '第二止盈价格百分比不能为空', trigger: 'blur' }],
slSlPriceRatio: [{ required: true, message: '第二止损价格百分比不能为空', trigger: 'blur' }]
}, },
inForm: {}, inForm: {},
searchLoding: false, searchLoding: false,
@ -688,6 +766,145 @@ export default {
}) })
}, },
methods: { methods: {
validateExtTakeProfitNumRatio(rule, value, callback) {
if (value === 0 || value === undefined || value === null || value === '') {
callback(new Error('止盈数量百分比必须大于0'))
} else {
callback()
}
},
validateReducePrice(rule, value, callback) {
if (value === 0 || value === undefined || value === null || value === '') {
callback(new Error('亏损百分比必须大于0'))
} else {
callback()
}
},
validateBuyPrice(rule, value, callback) {
if (value === 0) {
callback(new Error('购买金额必须大于0'))
} else {
callback()
}
},
validateReduceTakeProfit(rule, value, callback) {
if (this.inForm.reduce_num > 0 && this.inForm.reduce_num < 100 && this.inForm.reduce_take_profit === 0) {
callback(new Error('主单减仓后止盈必须大于0'))
} else if (this.inForm.reduce_num === 100 && this.inForm.reduce_take_profit > 0) {
callback(new Error('主单减仓全部后无需止盈'))
} else {
callback()
}
},
/** 主单减仓数量 */
validateReduceNum(rule, value, callback) {
if (value <= 0) {
callback(new Error('主单减仓数量必须大于0'))
} else {
callback()
}
},
/** 主单止盈数量 */
validateProfitNumRatio(rule, value, callback) {
if (value <= 0) {
callback(new Error('止盈数量比例必须大于0'))
} else {
callback()
}
},
/** 验证第二止盈止损 */
validateTptpPriceRatio(rule, value, callback) {
if (!this.inForm) {
callback(new Error('表单未初始化'))
return
}
if (this.inForm.profit_num_ratio > 0 && this.inForm.profit_num_ratio < 100 && (!value || value <= 0)) {
callback(new Error('第二止盈百分比不能为空且必须大于 0'))
} else if (this.inForm.profit_num_ratio === 100 && value > 0) {
callback(new Error('第一止盈已经全部止盈了,不需要第二止盈百分比'))
} else {
callback()
}
},
validateProfit(rule, value, callback) {
if (this.inForm.profit === '' || this.inForm.profit === undefined || this.inForm.profit === null) {
callback(new Error('止盈百分比不能为空'))
} else if (this.inForm.profit <= 0) {
callback(new Error('止盈百分比不能小于0'))
} else {
callback()
}
},
validateExtPriceRatio(rule, value, callback) {
const index = rule.fullField.split('.')[1] // 获取索引
const item = this.inForm.ext[index]
const message = item.addType === 1 ? '加仓下跌百分比' : '减仓下跌百分比'
if (item.priceRatio === '') {
callback(new Error(message + '不能为空'))
} else if (isNaN(item.priceRatio)) {
callback(new Error('必须输入数字'))
} else if (item.priceRatio < 0) {
callback(new Error('百分比不能小于 0'))
}
if (index === 0 && item.priceRatio <= this.inForm.reduce_price) {
callback(new Error('下跌百分比不能小于上一节点'))
} else if (index > 0 && item.priceRatio <= this.inForm.ext[index - 1].priceRatio) {
callback(new Error('下跌百分比不能小于上一节点'))
} else {
callback()
}
},
validateAddPosition(rule, value, callback) {
const index = rule.fullField.split('.')[1] // 获取索引
const item = this.inForm.ext[index]
const message = item.addType === 1 ? '加仓数值' : '减仓百分比'
if (item.addPositionVal === '') {
callback(new Error(message + '不能为空'))
} else if (isNaN(item.addPositionVal)) {
callback(new Error('必须输入数字'))
} else if (item.addPositionVal <= 0) {
callback(new Error(message + '必须大于 0'))
} else {
callback() // 校验通过
}
},
// 自定义校验逻辑
validateTakeProfitAndStopLoss(rule, value, callback) {
const index = rule.fullField.split('.')[1] // 获取索引
const item = this.inForm.ext[index]
if (item.takeProfitRatio === '' || item.stopLossRatio === '') {
callback(new Error('止盈和止损百分比都不能为空'))
} else if (isNaN(item.takeProfitRatio) || isNaN(item.stopLossRatio)) {
callback(new Error('必须输入数字'))
} else if (item.takeProfitRatio < 0 || item.stopLossRatio < 0) {
callback(new Error('百分比不能小于 0'))
} else if (item.takeProfitRatio === 0) {
callback(new Error('止盈百分比不能为0'))
} else {
callback() // 校验通过
}
},
// 自定义 第二止盈止损校验
validateTp(rule, value, callback) {
const index = rule.fullField.split('.')[1] // 获取索引
const item = this.inForm.ext[index]
if (item.tpTpPriceRatio === '' || item.tpSlPriceRatio === '') {
callback(new Error('第二止盈、止损百分比不能为空'))
} else if (isNaN(item.tpTpPriceRatio) || isNaN(item.tpSlPriceRatio)) {
callback(new Error('必须输入数字'))
} else if (item.tpTpPriceRatio < 0 || item.tpSlPriceRatio < 0) {
callback(new Error('百分比不能小于 0'))
} else if (item.takeProfitNumRatio < 100 && item.takeProfitNumRatio > 0 && item.tpTpPriceRatio <= 0) {
callback(new Error('第二止盈百分比必须大于0'))
} else {
callback() // 校验通过
}
},
onChangeSymbolType() { onChangeSymbolType() {
if (this.inForm.symbol_type === 1) { if (this.inForm.symbol_type === 1) {
this.inForm.cover_type = 0 this.inForm.cover_type = 0
@ -747,7 +964,7 @@ export default {
takeProfitRatio: 0, takeProfitRatio: 0,
stopLossRatio: 0, stopLossRatio: 0,
// 止盈数量百分比 止盈后止盈价格百分比 止盈后止损价格百分比 // 止盈数量百分比 止盈后止盈价格百分比 止盈后止损价格百分比
takeProfitNumRatio: 0, takeProfitNumRatio: 100,
tpTpPriceRatio: 0, tpTpPriceRatio: 0,
tpSlPriceRatio: 0 tpSlPriceRatio: 0
@ -812,7 +1029,7 @@ export default {
res = (1 + (this.percenter / 100)) * Number(this.inForm.price) res = (1 + (this.percenter / 100)) * Number(this.inForm.price)
} }
const [num, decimal] = String(res).split('.') const [num, decimal] = String(res).split('.')
console.log(num, decimal, '?') // console.log(num, decimal, '?')
if (decimal) { if (decimal) {
this.inForm.price = decimal.length > 4 ? `${num}.${decimal.substring(0, 4)}` : res this.inForm.price = decimal.length > 4 ? `${num}.${decimal.substring(0, 4)}` : res
} else { } else {
@ -854,7 +1071,8 @@ export default {
userId: undefined, userId: undefined,
params: undefined, params: undefined,
type: 1, type: 1,
switch: '0' switch: '0',
ext: []
} }
this.resetForm('form') this.resetForm('form')
}, },
@ -922,37 +1140,53 @@ export default {
}, },
/** 提交按钮 */ /** 提交按钮 */
submitForm: function() { submitForm: function() {
this.$refs['form'].validate(valid => { console.log('this.inForm', this.inForm)
if (valid) { Promise.all([
this.inForm.price = this.inForm.price ? String(this.inForm.price) : '' this.$refs['inForm'].validate(), // 验证第一个表单
if (this.form.id !== undefined) { this.$refs['extForm'].validate() // 验证第二个表单
this.$refs['inForm'].validate(valided => { ])
if (!valided) return false .then((formValid) => {
const params = JSON.stringify({ if (formValid.every(valid => valid)) {
...this.inForm, this.inForm.price = this.inForm.price ? String(this.inForm.price) : ''
reduce_price: this.inForm.reduce_price || 0, if (this.form.id !== undefined) {
reduce_num: this.inForm.reduce_num || 0, this.$refs['inForm'].validate(valided => {
reduce_take_profit: this.inForm.reduce_take_profit || 0, if (!valided) return false
reduce_stop_price: this.inForm.reduce_stop_price || 0, const params = JSON.stringify({
symbol_group_id: String(this.inForm.symbol_group_id), ...this.inForm,
profit_num_ratio: this.inForm.profit_num_ratio || 100, reduce_price: this.inForm.reduce_price || 0,
profit_tp_tp_price_ratio: this.inForm.profit_tp_tp_price_ratio || 0, reduce_num: this.inForm.reduce_num || 0,
profit_tp_sl_price_ratio: this.inForm.profit_tp_sl_price_ratio || 0, reduce_take_profit: this.inForm.reduce_take_profit || 0,
api_id: this.inForm.api_id.toString(), reduce_stop_price: this.inForm.reduce_stop_price || 0,
hedge_trigger_percent: Number(this.inForm.hedge_trigger_percent), symbol_group_id: String(this.inForm.symbol_group_id),
hedge_trigger_percent_max: Number(this.inForm.hedge_trigger_percent_max), profit_num_ratio: this.inForm.profit_num_ratio || 100,
ext: Array.isArray(this.inForm.ext) profit_tp_tp_price_ratio: this.inForm.profit_tp_tp_price_ratio || 0,
? this.inForm.ext.map(item => ({ profit_tp_sl_price_ratio: this.inForm.profit_tp_sl_price_ratio || 0,
...item, api_id: this.inForm.api_id.toString(),
stopLossRatio: item.stopLossRatio || 0, hedge_trigger_percent: Number(this.inForm.hedge_trigger_percent),
takeProfitNumRatio: item.takeProfitNumRatio || 0, hedge_trigger_percent_max: Number(this.inForm.hedge_trigger_percent_max),
tpTpPriceRatio: item.tpTpPriceRatio || 0, ext: Array.isArray(this.inForm.ext)
tpSlPriceRatio: item.tpSlPriceRatio || 0 ? this.inForm.ext.map(item => ({
})) ...item,
: [] stopLossRatio: item.stopLossRatio || 0,
}) takeProfitNumRatio: item.takeProfitNumRatio || 0,
tpTpPriceRatio: item.tpTpPriceRatio || 0,
tpSlPriceRatio: item.tpSlPriceRatio || 0
}))
: []
})
updateLineOrderTemplateLogs({ ...this.form, params }).then(response => { updateLineOrderTemplateLogs({ ...this.form, params }).then(response => {
if (response.code === 200) {
this.msgSuccess(response.msg)
this.open = false
this.getList()
} else {
this.msgError(response.msg)
}
})
})
} else {
addLineOrderTemplateLogs({ ...this.inForm, symbol_group_id: String(this.inForm.symbol_group_id), api_id: this.inForm.api_id.toString() }).then(response => {
if (response.code === 200) { if (response.code === 200) {
this.msgSuccess(response.msg) this.msgSuccess(response.msg)
this.open = false this.open = false
@ -961,20 +1195,9 @@ export default {
this.msgError(response.msg) this.msgError(response.msg)
} }
}) })
}) }
} else {
addLineOrderTemplateLogs({ ...this.inForm, symbol_group_id: String(this.inForm.symbol_group_id), api_id: this.inForm.api_id.toString() }).then(response => {
if (response.code === 200) {
this.msgSuccess(response.msg)
this.open = false
this.getList()
} else {
this.msgError(response.msg)
}
})
} }
} })
})
}, },
/** 删除按钮操作 */ /** 删除按钮操作 */
handleDelete(row) { handleDelete(row) {
@ -1033,6 +1256,20 @@ export default {
} }
} }
::v-deep.ext-form-item {
.el-form-item__label {
padding: 0px !important;
}
}
.el-form--label-top .el-form-item__label {
padding: 0px !important;
}
.ext-form .el-form-item__content .el-input-group {
vertical-align: middle;
}
.page { .page {
.pagination-container { .pagination-container {
padding: 0 !important; padding: 0 !important;

View File

@ -711,109 +711,146 @@
@click="onCalculate" @click="onCalculate"
>计算</el-button></el-col> >计算</el-button></el-col>
</el-row> </el-row>
<div v-for="(item, index) in form.ext" :key="index" class="exts"> <el-form ref="extForm" :model="form" label-position="top" :rules="rules" class="ext-form">
<el-row v-if="item.addType === 1" :gutter="20" style="margin-bottom: 20px;"> <div v-for="(item, index) in form.ext" :key="index" class="exts">
<el-col :span="4">
<div style="margin-bottom: 8px;">{{ item.addType === 1 ? '加仓类型' : '减仓类型' }}</div>
<el-radio-group v-model="item.orderType" size="mini">
<el-radio label="LIMIT">限价</el-radio>
<el-radio label="MARKET">市价</el-radio>
</el-radio-group>
</el-col>
<el-col :span="4">
<div style="margin-bottom: 8px;">{{ item.addType === 1 ? '加仓方式' : '减仓方式' }}</div>
<el-radio-group v-model="item.addPositionType" size="mini">
<el-radio :label="1">百分比</el-radio>
<el-radio :label="2" :disabled="item.addType === 2">实际金额</el-radio>
</el-radio-group>
</el-col>
</el-row> <el-row v-if="item.addType === 1" :gutter="20" style="margin-bottom: 20px;">
<el-row :gutter="20"> <el-col :span="4">
<el-col :span="3">{{ item.addType === 1 ? '加仓下跌百分比' : '减仓下跌百分比' }} <div style="margin-bottom: 8px;">{{ item.addType === 1 ? '加仓类型' : '减仓类型' }}</div>
<el-input <el-radio-group v-model="item.orderType" size="mini">
v-model.number="item.priceRatio" <el-radio label="LIMIT">限价</el-radio>
min="0" <el-radio label="MARKET">市价</el-radio>
size="mini" </el-radio-group>
type="number" </el-col>
:placeholder="item.addType === 1 ? '加仓下跌百分比' : '减仓下跌百分比'" <el-col :span="4">
/></el-col> <div style="margin-bottom: 8px;">{{ item.addType === 1 ? '加仓方式' : '减仓方式' }}</div>
<el-col :span="3">{{ item.addType === 1 ? '加仓数值' : '减仓百分比' }} <el-radio-group v-model="item.addPositionType" size="mini">
<el-input <el-radio :label="1">百分比</el-radio>
v-model.number="item.addPositionVal" <el-radio :label="2" :disabled="item.addType === 2">实际金额</el-radio>
min="0" </el-radio-group>
size="mini" </el-col>
type="number"
:placeholder="item.addType === 1 ? '加仓数值' : '减仓百分比'"
/>
</el-col>
<el-col :span="6">
<span>止盈/止损百分比</span>
<div>
<el-input
v-model.number="item.takeProfitRatio"
min="0"
style="width:50%;"
size="mini"
type="number"
placeholder="止盈百分比"
>
<template v-if="title !== '批量添加'" #append>{{ ext[index] ? ext[index].reTakeProfitRatio : 0
}}</template>
</el-input>
<el-input
v-model.number="item.stopLossRatio"
style="width:42%;"
size="mini"
type="number"
placeholder="止损百分比"
/>
</div>
</el-col>
<!-- <el-col :span="3">止损百分比<el-input v-model.number="item.stopLossRatio" size="mini" type="number" placeholder="止损百分比" /></el-col> -->
<template v-if="item.addType === 1"> </el-row>
<el-row :gutter="20">
<el-col :span="3"> <el-col :span="3">
止盈数量百分比<el-input <el-form-item
v-model.number="item.takeProfitNumRatio" :prop="'ext.' + index + '.priceRatio'"
size="mini" :rules="rules.priceRatio"
type="number" class="ext-form-item"
placeholder="止盈数量百分比" :label="item.addType === 1 ? '加仓下跌百分比' : '减仓下跌百分比'"
/> >
<el-input
v-model.number="item.priceRatio"
min="0"
size="mini"
type="number"
:placeholder="item.addType === 1 ? '加仓下跌百分比' : '减仓下跌百分比'"
/>
</el-form-item>
</el-col>
<el-col :span="3">
<el-form-item
:prop="'ext.' + index + '.addPositionVal'"
:label="item.addType === 1 ? '加仓数值' : '减仓百分比'"
:rules="rules.addPositionVal"
class="ext-form-item"
>
<el-input
v-model.number="item.addPositionVal"
min="0"
size="mini"
type="number"
:placeholder="item.addType === 1 ? '加仓数值' : '减仓百分比'"
/>
</el-form-item>
</el-col> </el-col>
<el-col :span="6"> <el-col :span="6">
<span>第二止盈/止损百分比</span> <el-form-item
<div> label="止盈/止损百分比"
<el-input :prop="'ext.' + index + '.takeProfitRatio'"
v-model.number="item.tpTpPriceRatio" :rules="[{ validator: validateTakeProfitAndStopLoss, trigger: 'blur' }]"
min="0" class="ext-form-item"
style="width:42%" >
size="mini" <div>
type="number" <el-input
placeholder="止盈后止盈价百分比" v-model.number="item.takeProfitRatio"
/> min="0"
style="width:50%;"
<el-input size="mini"
v-model.number="item.tpSlPriceRatio" type="number"
min="0" placeholder="止盈百分比"
style="width:42%" :rules="rules.takeProfitRatio"
size="mini" >
type="number" <template v-if="title !== '批量添加'" #append>{{ ext[index] ? ext[index].reTakeProfitRatio : 0
placeholder="止盈后止损价百分比" }}</template>
/> </el-input>
</div>
<el-input
v-model.number="item.stopLossRatio"
style="width:42%;"
size="mini"
type="number"
placeholder="止损百分比"
:rules="rules.stopLossRatio"
/>
</div>
</el-form-item>
</el-col> </el-col>
</template> <template v-if="item.addType === 1">
<el-col :span="3"> <el-col :span="3">
<div style="margin-bottom: 14px;" /><el-button <el-form-item
size="mini" label="止盈数量百分比"
type="danger" :prop="'ext.' + index + '.takeProfitNumRatio'"
@click="onAddExt(1, index)" :rules="rules.takeProfitNumRatio"
>删除</el-button> class="ext-form-item"
</el-col> >
</el-row> <el-input
</div> v-model.number="item.takeProfitNumRatio"
size="mini"
type="number"
placeholder="止盈数量百分比"
/>
</el-form-item>
</el-col>
<el-col :span="6">
<el-form-item
label="第二止盈/止损百分比"
:prop="'ext.' + index + '.tpTpPriceRatio'"
:rules="[{ validator: validateTp, trigger: 'blur' }]"
class="ext-form-item"
>
<div>
<el-input
v-model.number="item.tpTpPriceRatio"
min="0"
style="width:42%"
size="mini"
type="number"
placeholder="止盈后止盈价百分比"
/>
<el-input
v-model.number="item.tpSlPriceRatio"
min="0"
style="width:42%"
size="mini"
type="number"
placeholder="止盈后止损价百分比"
/>
</div>
</el-form-item>
</el-col>
</template>
<el-col :span="3">
<div style="margin-bottom: 14px;" /><el-button
size="mini"
type="danger"
@click="onAddExt(1, index)"
>删除</el-button>
</el-col>
</el-row>
</div>
</el-form>
<el-form-item label="是否保存模板" prop="save_template"> <el-form-item label="是否保存模板" prop="save_template">
<el-radio-group v-model="form.save_template"> <el-radio-group v-model="form.save_template">
<el-radio label="0"></el-radio> <el-radio label="0"></el-radio>
@ -1118,7 +1155,7 @@
v-for="(item, index) in sideList" v-for="(item, index) in sideList"
:key="'position_side' + index" :key="'position_side' + index"
:label="item.value" :label="item.value"
:disabled="item.value=='SELL'&&positionForm.close_type==1" :disabled="item.value === 'SELL' && positionForm.close_type === 1"
>{{ item.label }}</el-radio> >{{ item.label }}</el-radio>
</el-radio-group> </el-radio-group>
</el-form-item> </el-form-item>
@ -1331,18 +1368,51 @@ export default {
}, },
// 表单校验 // 表单校验
rules: { rules: {
reduce_price: [{ required: true, message: '主单亏损减仓百分比不能为空', trigger: 'blur' }], profit: [{ required: true, message: '请输入止盈百分比', trigger: 'blur' }, { validator: this.validateProfit, trigger: 'blur' }],
reduce_num: [{ required: true, message: '主单减仓数量百分比不能为空', trigger: 'blur' }], reduce_price: [
reduce_take_profit: [{ required: true, message: '主单减仓后止盈价百分比不能为空', trigger: 'blur' }], { required: true, message: '主单亏损减仓百分比不能为空', trigger: 'blur' },
// reduce_stop_price: [{ required: true, message: '主单减仓后止损价不能为空', trigger: 'blur' }], { validator: this.validateReducePrice, trigger: 'blur' }],
buy_price: [{ required: true, message: '购买金额不能为空', trigger: 'blur' }], reduce_num: [
{ required: true, message: '主单减仓数量百分比不能为空', trigger: 'blur' },
{ validator: this.validateReduceNum, trigger: 'blur' }],
reduce_take_profit: [
{ required: true, message: '主单减仓后止盈价百分比不能为空', trigger: 'blur' },
{ validator: this.validateReduceTakeProfit, trigger: 'blur' }],
buy_price: [
{ required: true, message: '购买金额不能为空', trigger: 'blur' },
{ validator: this.validateBuyPrice, trigger: 'blur' }
],
exchange_type: [{ required: true, message: '交易所不能为空', trigger: 'blur' }], exchange_type: [{ required: true, message: '交易所不能为空', trigger: 'blur' }],
api_id: [{ required: true, message: 'api用户不能为空', trigger: 'blur' }], api_id: [{ required: true, message: 'api用户不能为空', trigger: 'blur' }],
symbol: [{ required: true, message: '交易对不能为空', trigger: 'blur' }], symbol: [{ required: true, message: '交易对不能为空', trigger: 'blur' }],
symbol_group_id: [{ required: true, message: '交易对组不能为空', trigger: 'blur' }], symbol_group_id: [{ required: true, message: '交易对组不能为空', trigger: 'blur' }],
quote_symbol: [{ required: true, message: '计价货币不能为空', trigger: 'blur' }], quote_symbol: [{ required: true, message: '计价货币不能为空', trigger: 'blur' }],
order_sn: [{ required: true, message: '订单号不能为空', trigger: 'blur' }], order_sn: [{ required: true, message: '订单号不能为空', trigger: 'blur' }],
profit_num_ratio: [{ required: true, message: '止盈数量百分比不能为空', trigger: 'blur' }] profit_num_ratio: [
{ required: true, message: '止盈数量百分比不能为空', trigger: 'blur' },
{ validator: this.validateProfitNumRatio, trigger: 'blur' }
],
profit_tp_tp_price_ratio: [{ validator: this.validateTptpPriceRatio, trigger: 'blur' }],
priceRatio: [{ validator: this.validateExtPriceRatio, trigger: 'blur' }],
addPositionVal: [
{ required: true, message: '追加仓位不能为空', trigger: 'blur' },
{ validator: this.validateAddPosition, trigger: 'blur' }],
takeProfitRatio: [
{ required: true, message: '请输入止盈百分比', trigger: 'blur' },
{ type: 'number', message: '止盈百分比必须为数字', trigger: 'blur' },
{ min: 0, message: '止盈百分比不能小于 0', trigger: 'blur' }
],
stopLossRatio: [
{ required: true, message: '请输入止损百分比', trigger: 'blur' },
{ type: 'number', message: '止损百分比必须为数字', trigger: 'blur' },
{ min: 0, message: '止损百分比不能小于 0', trigger: 'blur' }
],
takeProfitNumRatio: [{ required: true, message: '止盈数量不能为空', trigger: 'blur' },
{ validator: this.validateExtTakeProfitNumRatio, trigger: 'blur' }
],
tpTpPriceRatio: [{ required: true, message: '第二止盈价格百分比不能为空', trigger: 'blur' }],
slSlPriceRatio: [{ required: true, message: '第二止损价格百分比不能为空', trigger: 'blur' }]
}, },
currentExpandId: undefined, currentExpandId: undefined,
cacheRefresh: {}, cacheRefresh: {},
@ -1382,7 +1452,6 @@ export default {
positionRules: { positionRules: {
exchangeType: [{ required: true, message: '交易所不能为空', trigger: 'blur' }], exchangeType: [{ required: true, message: '交易所不能为空', trigger: 'blur' }],
api_id: [{ required: true, message: 'api用户不能为空', trigger: 'blur' }] api_id: [{ required: true, message: 'api用户不能为空', trigger: 'blur' }]
// symbol: [{ required: true, message: '交易对不能为空', trigger: 'blur' }]
}, },
searchLoding: false, searchLoding: false,
// 取消委托 // 取消委托
@ -1523,7 +1592,7 @@ export default {
takeProfitRatio: 0, takeProfitRatio: 0,
stopLossRatio: 0, stopLossRatio: 0,
// 止盈数量百分比 止盈后止盈价格百分比 止盈后止损价格百分比 // 止盈数量百分比 止盈后止盈价格百分比 止盈后止损价格百分比
takeProfitNumRatio: 0, takeProfitNumRatio: 100,
tpTpPriceRatio: 0, tpTpPriceRatio: 0,
tpSlPriceRatio: 0 tpSlPriceRatio: 0
@ -1914,6 +1983,146 @@ export default {
} }
this.resetForm('form') this.resetForm('form')
}, },
validateExtTakeProfitNumRatio(rule, value, callback) {
if (value === 0 || value === undefined || value === null || value === '') {
callback(new Error('止盈数量百分比必须大于0'))
} else {
callback()
}
},
validateReducePrice(rule, value, callback) {
if (value === 0 || value === undefined || value === null || value === '') {
callback(new Error('亏损百分比必须大于0'))
} else {
callback()
}
},
validateBuyPrice(rule, value, callback) {
if (value === 0) {
callback(new Error('购买金额必须大于0'))
} else {
callback()
}
},
validateReduceTakeProfit(rule, value, callback) {
if (this.form.reduce_num > 0 && this.form.reduce_num < 100 && this.form.reduce_take_profit === 0) {
callback(new Error('主单减仓后止盈必须大于0'))
} else if (this.form.reduce_num === 100 && this.form.reduce_take_profit > 0) {
callback(new Error('主单减仓全部后无需止盈'))
} else {
callback()
}
},
/** 主单减仓数量 */
validateReduceNum(rule, value, callback) {
if (value <= 0) {
callback(new Error('主单减仓数量必须大于0'))
} else {
callback()
}
},
/** 主单止盈数量 */
validateProfitNumRatio(rule, value, callback) {
if (value <= 0) {
callback(new Error('止盈数量比例必须大于0'))
} else {
callback()
}
},
/** 验证第二止盈止损 */
validateTptpPriceRatio(rule, value, callback) {
if (!this.form) {
callback(new Error('表单未初始化'))
return
}
if (this.form.profit_num_ratio > 0 && this.form.profit_num_ratio < 100 && (!value || value <= 0)) {
callback(new Error('第二止盈百分比不能为空且必须大于 0'))
} else if (this.form.profit_num_ratio === 100 && value > 0) {
callback(new Error('第一止盈已经全部止盈了,不需要第二止盈百分比'))
} else {
callback()
}
},
validateProfit(rule, value, callback) {
console.log(this.form)
if (this.form.profit === '' || this.form.profit === undefined || this.form.profit === null) {
callback(new Error('止盈百分比不能为空'))
} else if (this.form.profit <= 0) {
callback(new Error('止盈百分比不能小于0'))
} else {
callback()
}
},
validateExtPriceRatio(rule, value, callback) {
const index = rule.fullField.split('.')[1] // 获取索引
const item = this.form.ext[index]
const message = item.addType === 1 ? '加仓下跌百分比' : '减仓下跌百分比'
if (item.priceRatio === '') {
callback(new Error(message + '不能为空'))
} else if (isNaN(item.priceRatio)) {
callback(new Error('必须输入数字'))
} else if (item.priceRatio < 0) {
callback(new Error('百分比不能小于 0'))
}
if (index === 0 && item.priceRatio <= this.form.reduce_price) {
callback(new Error('下跌百分比不能小于上一节点'))
} else if (index > 0 && item.priceRatio <= this.form.ext[index - 1].priceRatio) {
callback(new Error('下跌百分比不能小于上一节点'))
} else {
callback()
}
},
validateAddPosition(rule, value, callback) {
const index = rule.fullField.split('.')[1] // 获取索引
const item = this.form.ext[index]
const message = item.addType === 1 ? '加仓数值' : '减仓百分比'
if (item.addPositionVal === '') {
callback(new Error(message + '不能为空'))
} else if (isNaN(item.addPositionVal)) {
callback(new Error('必须输入数字'))
} else if (item.addPositionVal <= 0) {
callback(new Error(message + '必须大于 0'))
} else {
callback() // 校验通过
}
},
// 自定义校验逻辑
validateTakeProfitAndStopLoss(rule, value, callback) {
const index = rule.fullField.split('.')[1] // 获取索引
const item = this.form.ext[index]
if (item.takeProfitRatio === '' || item.stopLossRatio === '') {
callback(new Error('止盈和止损百分比都不能为空'))
} else if (isNaN(item.takeProfitRatio) || isNaN(item.stopLossRatio)) {
callback(new Error('必须输入数字'))
} else if (item.takeProfitRatio < 0 || item.stopLossRatio < 0) {
callback(new Error('百分比不能小于 0'))
} else if (item.takeProfitRatio === 0) {
callback(new Error('止盈百分比不能为0'))
} else {
callback() // 校验通过
}
},
// 自定义 第二止盈止损校验
validateTp(rule, value, callback) {
const index = rule.fullField.split('.')[1] // 获取索引
const item = this.form.ext[index]
if (item.tpTpPriceRatio === '' || item.tpSlPriceRatio === '') {
callback(new Error('第二止盈、止损百分比不能为空'))
} else if (isNaN(item.tpTpPriceRatio) || isNaN(item.tpSlPriceRatio)) {
callback(new Error('必须输入数字'))
} else if (item.tpTpPriceRatio < 0 || item.tpSlPriceRatio < 0) {
callback(new Error('百分比不能小于 0'))
} else if (item.takeProfitNumRatio < 100 && item.takeProfitNumRatio > 0 && item.tpTpPriceRatio <= 0) {
callback(new Error('第二止盈百分比必须大于0'))
} else {
callback() // 校验通过
}
},
getImgList: function() { getImgList: function() {
this.form[this.fileIndex] = this.$refs['fileChoose'].resultList[0].fullUrl this.form[this.fileIndex] = this.$refs['fileChoose'].resultList[0].fullUrl
}, },
@ -1971,75 +2180,84 @@ export default {
}) })
}, },
/** 提交按钮 */ /** 提交按钮 */
submitForm: function() { submitForm: async function() {
this.$refs['form'].validate(valid => { const formValid = await Promise.all([
if (valid) { this.$refs['form'].validate(), // 验证第一个表单
this.formLoading = true this.$refs['extForm'].validate() // 验证第二个表单
if (this.form.price) { ])
this.form.price = String(this.form.price)
}
if (this.form.id !== undefined) { console.log(formValid)
updateLinePreOrder(this.form).then(response => { if (formValid.every(valid => valid)) {
if (response.code === 200) { this.formLoading = true
this.msgSuccess(response.msg) if (this.form.price) {
this.open = false this.form.price = String(this.form.price)
this.getList() }
} else {
this.msgError(response.msg) if (this.form.id !== undefined) {
} updateLinePreOrder(this.form).then(response => {
}).finally(() => { if (response.code === 200) {
this.formLoading = false this.msgSuccess(response.msg)
}) this.open = false
} else { this.getList()
if (this.title === '批量添加') { } else {
const params = JSON.parse(JSON.stringify({ this.msgError(response.msg)
...this.form,
symbol_group_id: String(this.form.symbol_group_id),
api_id: this.form.api_id.toString(),
profit_num_ratio: this.form.profit_num_ratio || 100,
profit_tp_tp_price_ratio: this.form.profit_tp_tp_price_ratio || 0,
profit_tp_sl_price_ratio: this.form.profit_tp_sl_price_ratio || 0
}))
delete params.symbol
batchAddOrder(params).then(response => {
if (response.code === 200) {
this.msgSuccess(response.msg)
this.open = false
this.getList()
} else {
this.msgError(response.msg)
}
}).finally(() => {
this.formLoading = false
})
return false
} }
addOrder({ }).finally(() => {
this.formLoading = false
})
} else {
if (this.title === '批量添加') {
const params = JSON.parse(JSON.stringify({
...this.form, ...this.form,
reduce_price: this.form.reduce_price || 0, symbol_group_id: String(this.form.symbol_group_id),
reduce_num: this.form.reduce_num || 0, api_id: this.form.api_id.toString(),
reduce_take_profit: this.form.reduce_take_profit || 0,
reduce_stop_price: this.form.reduce_stop_price || 0,
profit_num_ratio: this.form.profit_num_ratio || 100, profit_num_ratio: this.form.profit_num_ratio || 100,
profit_tp_tp_price_ratio: this.form.profit_tp_tp_price_ratio || 0, profit_tp_tp_price_ratio: this.form.profit_tp_tp_price_ratio || 0,
profit_tp_sl_price_ratio: this.form.profit_tp_sl_price_ratio || 0, profit_tp_sl_price_ratio: this.form.profit_tp_sl_price_ratio || 0
api_id: this.form.api_id.toString() }))
}).then(response => { delete params.symbol
batchAddOrder(params).then(response => {
if (response.code === 200) { if (response.code === 200) {
this.msgSuccess(response.msg) this.msgSuccess(response.msg)
this.open = false this.open = false
this.getList() this.getList()
} else { } else {
this.formLoadng = false
this.msgError(response.msg) this.msgError(response.msg)
} }
}).finally(() => { }).finally(() => {
this.formLoading = false this.formLoading = false
}) })
return false
} }
addOrder({
...this.form,
reduce_price: this.form.reduce_price || 0,
reduce_num: this.form.reduce_num || 0,
reduce_take_profit: this.form.reduce_take_profit || 0,
reduce_stop_price: this.form.reduce_stop_price || 0,
profit_num_ratio: this.form.profit_num_ratio || 100,
profit_tp_tp_price_ratio: this.form.profit_tp_tp_price_ratio || 0,
profit_tp_sl_price_ratio: this.form.profit_tp_sl_price_ratio || 0,
api_id: this.form.api_id.toString()
}).then(response => {
if (response.code === 200) {
this.msgSuccess(response.msg)
this.open = false
this.getList()
} else {
this.formLoadng = false
this.msgError(response.msg)
}
}).finally(() => {
this.formLoading = false
})
} }
}) }
// this.$refs['form'].validate(valid => {
// if (valid) {
// }
// })
}, },
onCancel() { onCancel() {
this.cancelOpen = true this.cancelOpen = true
@ -2162,6 +2380,20 @@ export default {
} }
} }
::v-deep.ext-form-item {
.el-form-item__label {
padding: 0px !important;
}
}
.el-form--label-top .el-form-item__label {
padding: 0px !important;
}
.ext-form .el-form-item__content .el-input-group {
vertical-align: middle;
}
.page { .page {
.pagination-container { .pagination-container {
padding: 0 !important; padding: 0 !important;