|
|
@@ -0,0 +1,363 @@
|
|
|
+<template>
|
|
|
+ <app-modal direction="right-top" height="100%" width="100%" v-model:show="showModal" :refresh="refresh">
|
|
|
+ <app-view class="goods-listing g-form">
|
|
|
+ <template #header>
|
|
|
+ <app-navbar :title="quote ? `${quote.goodscode}/${quote.goodsname}` : '交易下单'" @back="closed" />
|
|
|
+ </template>
|
|
|
+ <component :is="Price" v-bind="{ goodsCode }" />
|
|
|
+ <component :is="Forex" v-bind="{ goodsCode, showMore: false }" @price-click="onPriceClick" />
|
|
|
+ <!-- 下单 -->
|
|
|
+ <Form ref="formRef" class="g-form__container" @submit="onSubmit">
|
|
|
+ <CellGroup inset>
|
|
|
+ <Field label="方向">
|
|
|
+ <template #input>
|
|
|
+ <RadioGroup v-model="formData.BuyOrSell" direction="horizontal" @click="onBuyOrSellChanged">
|
|
|
+ <Radio v-for="(item, index) in getBuyOrSellList()" :key="index" :name="item.value">{{ item.label
|
|
|
+ }}</Radio>
|
|
|
+ </RadioGroup>
|
|
|
+ </template>
|
|
|
+ </Field>
|
|
|
+ <Field label="价格方式">
|
|
|
+ <template #input>
|
|
|
+ <RadioGroup v-model="formData.PriceMode" direction="horizontal" @click="onPriceModeChanged">
|
|
|
+ <Radio v-for="(item, index) in getPricemode2List()" :key="index" :name="item.value">{{
|
|
|
+ item.label
|
|
|
+ }}</Radio>
|
|
|
+ </RadioGroup>
|
|
|
+ </template>
|
|
|
+ </Field>
|
|
|
+ <Field name="OrderQty" label="数量">
|
|
|
+ <template #input>
|
|
|
+ <div class="g-qty-group">
|
|
|
+ <div class="g-qty-group__stepper">
|
|
|
+ <Stepper v-model="formData.OrderQty" theme="round" button-size="22" :min="0"
|
|
|
+ :step="qtyStep" integer />
|
|
|
+ </div>
|
|
|
+ <RadioGroup v-model="qtyStep" direction="horizontal" @change="onRadioChange">
|
|
|
+ <Radio v-for="(value, index) in qtyStepList" :key="index" :name="value">{{ value }}
|
|
|
+ </Radio>
|
|
|
+ </RadioGroup>
|
|
|
+ </div>
|
|
|
+ </template>
|
|
|
+ </Field>
|
|
|
+ <!-- 市价 -->
|
|
|
+ <Field label="价格" v-if="formData.PriceMode === PriceMode.Market">
|
|
|
+ <template #input>
|
|
|
+ <span>{{ handleNumberValue(marketPrice) }}</span>
|
|
|
+ </template>
|
|
|
+ </Field>
|
|
|
+ <!-- 允许成交范围 -->
|
|
|
+ <Field name="MarketMaxSub" label="允许成交范围" v-if="formData.PriceMode === PriceMode.Market">
|
|
|
+ <template #input>
|
|
|
+ <Stepper v-model="formData.MarketMaxSub" theme="round" button-size="22" :auto-fixed="false"
|
|
|
+ integer />
|
|
|
+ </template>
|
|
|
+ </Field>
|
|
|
+ <!-- 限价 -->
|
|
|
+ <Field name="OrderPrice" :rules="formRules.OrderPrice" label="价格"
|
|
|
+ v-if="formData.PriceMode === PriceMode.Limit">
|
|
|
+ <template #input>
|
|
|
+ <Stepper v-model="formData.OrderPrice" theme="round" button-size="22" :auto-fixed="false" />
|
|
|
+ </template>
|
|
|
+ </Field>
|
|
|
+ <Field name="SlPrice" :rules="formRules.SlPrice"
|
|
|
+ v-if="formData.PriceMode === PriceMode.Limit && formData.BuildType === BuildType.Open">
|
|
|
+ <template #label>
|
|
|
+ <Checkbox v-model="sl">止损</Checkbox>
|
|
|
+ </template>
|
|
|
+ <template #input>
|
|
|
+ <Stepper v-model="formData.SlPrice" :disabled="!sl" theme="round" button-size="22" allow-empty
|
|
|
+ :default-value="0" :min="0" integer />
|
|
|
+ </template>
|
|
|
+ </Field>
|
|
|
+ <Field name="SpPrice" :rules="formRules.SpPrice"
|
|
|
+ v-if="formData.PriceMode === PriceMode.Limit && formData.BuildType === BuildType.Open">
|
|
|
+ <template #label>
|
|
|
+ <Checkbox v-model="sp">止盈</Checkbox>
|
|
|
+ </template>
|
|
|
+ <template #input>
|
|
|
+ <Stepper v-model="formData.SpPrice" :disabled="!sp" theme="round" button-size="22" allow-empty
|
|
|
+ :default-value="0" :min="0" integer />
|
|
|
+ </template>
|
|
|
+ </Field>
|
|
|
+ <template v-if="formData.BuyOrSell === BuyOrSell.Buy || quote?.tradeproperty !== 2">
|
|
|
+ <Field label="预估可订立量">
|
|
|
+ <template #input>
|
|
|
+ <span>{{ total.enableQty }}</span>
|
|
|
+ </template>
|
|
|
+ </Field>
|
|
|
+ <Field label="预扣保证金">
|
|
|
+ <template #input>
|
|
|
+ <span>{{ total.deposit.toFixed(2) }}</span>
|
|
|
+ </template>
|
|
|
+ </Field>
|
|
|
+ </template>
|
|
|
+ <Field label="可用资金">
|
|
|
+ <template #input>
|
|
|
+ <span>{{ accountStore.currentAccount.avaiableMoney?.toFixed(2) }}</span>
|
|
|
+ </template>
|
|
|
+ </Field>
|
|
|
+ </CellGroup>
|
|
|
+ </Form>
|
|
|
+ <template #footer>
|
|
|
+ <div class="g-form__footer">
|
|
|
+ <template v-if="formData.BuyOrSell === BuyOrSell.Buy">
|
|
|
+ <Button type="danger" block square :disabled="!formData.OrderQty"
|
|
|
+ @click="onBeforeSubmit(BuildType.Open)" v-if="!quote?.iscannotbuy">订立买入</Button>
|
|
|
+ <Button color="#199e00" block square
|
|
|
+ :disabled="!formData.OrderQty || !sellQty || (formData.OrderQty > sellQty)"
|
|
|
+ @click="onBeforeSubmit(BuildType.Close)" v-if="!isTrademode16">
|
|
|
+ <span>转让买入</span>
|
|
|
+ <span v-if="sellQty">(≤{{ sellQty }})</span>
|
|
|
+ </Button>
|
|
|
+ </template>
|
|
|
+ <template v-if="formData.BuyOrSell === BuyOrSell.Sell">
|
|
|
+ <Button type="danger" block square :disabled="!formData.OrderQty"
|
|
|
+ @click="onBeforeSubmit(BuildType.Open)"
|
|
|
+ v-if="!isTrademode16 && !quote?.iscannotsell">订立卖出</Button>
|
|
|
+ <Button color="#199e00" block square
|
|
|
+ :disabled="!formData.OrderQty || !buyQty || (formData.OrderQty > buyQty)"
|
|
|
+ @click="onBeforeSubmit(BuildType.Close)">
|
|
|
+ <span>转让卖出</span>
|
|
|
+ <span v-if="buyQty">(≤{{ buyQty }})</span>
|
|
|
+ </Button>
|
|
|
+ </template>
|
|
|
+ </div>
|
|
|
+ </template>
|
|
|
+ </app-view>
|
|
|
+ </app-modal>
|
|
|
+</template>
|
|
|
+
|
|
|
+<script lang="ts" setup>
|
|
|
+import { useFuturesStore, useAccountStore, usePositionStore } from '@/stores'
|
|
|
+import { useNavigation } from '@mobile/router/navigation'
|
|
|
+import { handleNumberValue } from '@/filters'
|
|
|
+import quoteSocket from '@/services/websocket/quote'
|
|
|
+import { shallowRef, onMounted, onUnmounted, computed, defineAsyncComponent } from 'vue'
|
|
|
+import { Form, Field, Button, FieldRule, FormInstance, Radio, RadioGroup, Checkbox, CellGroup } from 'vant'
|
|
|
+import { useOrder } from '@/business/trade'
|
|
|
+import { BuyOrSell, getBuyOrSellList, BuildType, getPricemode2List, PriceMode } from '@/constants/order'
|
|
|
+import { fullloading, dialog } from '@/utils/vant'
|
|
|
+import { useRequest } from '@/hooks/request'
|
|
|
+import { queryTradePosition } from '@/services/api/order'
|
|
|
+import AppModal from '@/components/base/modal/index.vue'
|
|
|
+import Stepper from '@mobile/components/base/stepper/index.vue'
|
|
|
+
|
|
|
+const Price = defineAsyncComponent(() => import('@mobile/components/modules/quote/price/index.vue'))
|
|
|
+const Forex = defineAsyncComponent(() => import('@mobile/components/modules/quote/forex/index.vue'))
|
|
|
+
|
|
|
+const props = defineProps({
|
|
|
+ goodsCode: {
|
|
|
+ type: String,
|
|
|
+ required: true
|
|
|
+ },
|
|
|
+ buyOrSell: {
|
|
|
+ type: Number,
|
|
|
+ required: true
|
|
|
+ }
|
|
|
+})
|
|
|
+
|
|
|
+const { router } = useNavigation()
|
|
|
+const futuresStore = useFuturesStore()
|
|
|
+const accountStore = useAccountStore()
|
|
|
+const positionStore = usePositionStore()
|
|
|
+const formRef = shallowRef<FormInstance>()
|
|
|
+const { formData, formSubmit } = useOrder()
|
|
|
+
|
|
|
+const marketPrice = computed(() => {
|
|
|
+ const { ask = 0, bid = 0 } = quote.value ?? {}
|
|
|
+ return formData.BuyOrSell === BuyOrSell.Buy ? ask : bid
|
|
|
+})
|
|
|
+const quote = futuresStore.getGoodsQuote(props.goodsCode)
|
|
|
+const subscribe = quoteSocket.createSubscribe()
|
|
|
+const sl = shallowRef(false) // 止损
|
|
|
+const sp = shallowRef(false) // 止盈
|
|
|
+const position = shallowRef<Model.TradePositionRsp[]>([]) // 持仓汇总
|
|
|
+// 是否刷新父组件数据
|
|
|
+const refresh = shallowRef(true)
|
|
|
+const showModal = shallowRef(true)
|
|
|
+const qtyStepList = [1, 5, 10, 20, 30, 50] // 数量步长列表
|
|
|
+const qtyStep = shallowRef(qtyStepList[0]) // 数量步长
|
|
|
+
|
|
|
+const isTrademode16 = computed(() => quote.value?.trademode === 16)
|
|
|
+
|
|
|
+const total = computed(() => {
|
|
|
+ const { avaiableMoney = 0 } = accountStore.currentAccount
|
|
|
+ const { marketmarginalgorithm = 0, marketmarginvalue = 0, agreeunit = 0 } = quote.value ?? {}
|
|
|
+
|
|
|
+ const result = {
|
|
|
+ enableQty: 0,
|
|
|
+ deposit: 0
|
|
|
+ }
|
|
|
+
|
|
|
+ const fixed = agreeunit * marketmarginvalue
|
|
|
+ const ratio = fixed * (formData.OrderPrice ?? 0)
|
|
|
+
|
|
|
+ if (fixed && ratio) {
|
|
|
+ if (marketmarginalgorithm === 1) {
|
|
|
+ const qty = Math.trunc(avaiableMoney / ratio)
|
|
|
+ result.deposit = (formData.OrderQty ?? 0) * ratio
|
|
|
+ result.enableQty = qty > 0 ? qty : 0
|
|
|
+ }
|
|
|
+ if (marketmarginalgorithm === 2) {
|
|
|
+ const qty = Math.trunc(avaiableMoney / fixed)
|
|
|
+ result.deposit = (formData.OrderQty ?? 0) * fixed
|
|
|
+ result.enableQty = qty > 0 ? qty : 0
|
|
|
+ }
|
|
|
+ }
|
|
|
+ return result
|
|
|
+})
|
|
|
+
|
|
|
+// 买方向持仓数量
|
|
|
+const buyQty = computed(() => positionStore.getOrderQty(BuyOrSell.Buy, props.goodsCode))
|
|
|
+
|
|
|
+// 卖方向持仓数量
|
|
|
+const sellQty = computed(() => positionStore.getOrderQty(BuyOrSell.Sell, props.goodsCode))
|
|
|
+
|
|
|
+// 表单验证规则
|
|
|
+const formRules: { [key in keyof Proto.OrderReq]?: FieldRule[] } = {
|
|
|
+ OrderQty: [{
|
|
|
+ message: '请输入数量',
|
|
|
+ validator: () => {
|
|
|
+ return !!formData.OrderQty
|
|
|
+ }
|
|
|
+ }],
|
|
|
+ // 限价
|
|
|
+ OrderPrice: [{
|
|
|
+ message: '请输入价格',
|
|
|
+ validator: () => {
|
|
|
+ if (formData.PriceMode === PriceMode.Limit) {
|
|
|
+ return !!formData.OrderPrice
|
|
|
+ }
|
|
|
+ return true
|
|
|
+ }
|
|
|
+ }],
|
|
|
+ SlPrice: [{
|
|
|
+ message: '请输入止损价',
|
|
|
+ validator: () => {
|
|
|
+ if (sl.value) {
|
|
|
+ return !!formData.SlPrice
|
|
|
+ }
|
|
|
+ return true
|
|
|
+ }
|
|
|
+ }],
|
|
|
+ SpPrice: [{
|
|
|
+ message: '请输入止盈价',
|
|
|
+ validator: () => {
|
|
|
+ if (sp.value) {
|
|
|
+ return !!formData.SpPrice
|
|
|
+ }
|
|
|
+ return true
|
|
|
+ }
|
|
|
+ }],
|
|
|
+}
|
|
|
+
|
|
|
+onMounted(() => {
|
|
|
+ subscribe.start(props.goodsCode)
|
|
|
+ formData.BuyOrSell = props.buyOrSell
|
|
|
+ formData.BuildType = BuildType.Open
|
|
|
+ formData.PriceMode = PriceMode.Market
|
|
|
+ formData.MarketMaxSub = 100.0
|
|
|
+})
|
|
|
+
|
|
|
+onUnmounted(() => subscribe.stop())
|
|
|
+
|
|
|
+// 获取当前商品持仓汇总
|
|
|
+useRequest(queryTradePosition, {
|
|
|
+ params: {
|
|
|
+ tradeMode: '10'
|
|
|
+ },
|
|
|
+ onSuccess: (res) => {
|
|
|
+ position.value = res.data.filter(item => item.goodscode === props.goodsCode)
|
|
|
+ const datas = position.value.filter(item => formData.BuyOrSell === BuyOrSell.Buy ? item.buyorsell === BuyOrSell.Sell : item.buyorsell === BuyOrSell.Buy) // 反方向持仓
|
|
|
+ if (datas.length > 0) {
|
|
|
+ formData.OrderQty = datas[0].enableqty
|
|
|
+ }
|
|
|
+ },
|
|
|
+})
|
|
|
+
|
|
|
+const onBuyOrSellChanged = () => {
|
|
|
+ if (formData.PriceMode === PriceMode.Limit) {
|
|
|
+ const { ask = 0, bid = 0 } = quote.value ?? {}
|
|
|
+ formData.OrderPrice = formData.BuyOrSell === BuyOrSell.Buy ? ask : bid
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+const onPriceModeChanged = () => {
|
|
|
+ if (formData.PriceMode === PriceMode.Limit) {
|
|
|
+ const { ask = 0, bid = 0 } = quote.value ?? {}
|
|
|
+ formData.OrderPrice = formData.BuyOrSell === BuyOrSell.Buy ? ask : bid
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+const onBeforeSubmit = (buildType: BuildType) => {
|
|
|
+ formData.BuildType = buildType
|
|
|
+ if (buildType === BuildType.Close) {
|
|
|
+ const { ask = 0, bid = 0 } = quote.value ?? {}
|
|
|
+ formData.OrderPrice = formData.BuyOrSell === BuyOrSell.Buy ? ask : bid
|
|
|
+
|
|
|
+ const datas = position.value.filter(item => formData.BuyOrSell === BuyOrSell.Buy ? item.buyorsell === BuyOrSell.Sell : item.buyorsell === BuyOrSell.Buy) // 反方向持仓
|
|
|
+ if (datas.length > 0) {
|
|
|
+ formData.OrderQty = datas[0].enableqty
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ if (isTrademode16.value) {
|
|
|
+ formRef.value?.submit()
|
|
|
+ } else {
|
|
|
+ dialog({
|
|
|
+ title: '确认要提交吗?',
|
|
|
+ message: '*若存在价格匹配的反方向委托订单,系统将会自动撤销',
|
|
|
+ showCancelButton: true,
|
|
|
+ }).then(() => {
|
|
|
+ formRef.value?.submit()
|
|
|
+ })
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+// 委托下单
|
|
|
+const onSubmit = () => {
|
|
|
+ const { goodsid, marketid } = quote.value ?? {}
|
|
|
+ formData.GoodsID = goodsid
|
|
|
+ formData.MarketID = marketid
|
|
|
+ // 市价
|
|
|
+ if (formData.PriceMode === PriceMode.Market) { formData.OrderPrice = marketPrice.value }
|
|
|
+ // 限价
|
|
|
+ if (!sl.value) { formData.SlPrice = undefined }
|
|
|
+ if (!sp.value) { formData.SpPrice = undefined }
|
|
|
+
|
|
|
+ fullloading((hideLoading) => {
|
|
|
+ formSubmit().then(() => {
|
|
|
+ hideLoading()
|
|
|
+ dialog('委托成功。').then(() => {
|
|
|
+ router.back()
|
|
|
+ })
|
|
|
+ }).catch((err) => {
|
|
|
+ hideLoading(err, 'fail')
|
|
|
+ })
|
|
|
+ })
|
|
|
+}
|
|
|
+
|
|
|
+const onPriceClick = (buyorsell: BuyOrSell, value: number) => {
|
|
|
+ // 价格显示
|
|
|
+ formData.OrderPrice = value
|
|
|
+ // 方向也变化
|
|
|
+ formData.BuyOrSell = buyorsell === BuyOrSell.Buy ? 1 : 0
|
|
|
+}
|
|
|
+
|
|
|
+const onRadioChange = (value: number) => {
|
|
|
+ formData.OrderQty = value
|
|
|
+}
|
|
|
+
|
|
|
+// 关闭弹窗
|
|
|
+const closed = (isRefresh = true) => {
|
|
|
+ refresh.value = isRefresh
|
|
|
+ showModal.value = false
|
|
|
+}
|
|
|
+
|
|
|
+// 暴露组件属性给父组件调用
|
|
|
+defineExpose({
|
|
|
+ closed,
|
|
|
+})
|
|
|
+
|
|
|
+</script>
|