Handy_Cao 1 rok pred
rodič
commit
5cf4e4a73f

+ 3 - 1
src/constants/funcode.ts

@@ -56,7 +56,9 @@ export enum FunCode {
     PosChangedNtf = 131075, // 头寸变化通知
     RiskControlNtf = 131086, // 风控通知
     RiskToWebNtf = 131139, // 风控消息管理端通知客户端
-
+    OrderSuccessedNtf = 131152, /// 委托单委托成功通知(0, 2, 80)
+    OrderCanceledNtf = 131084, /// 委托单撤单通知(0, 2, 12)
+    
     // 行情内容
     QuoteBeat = 0x12, // 心跳
     QuoteSubscribeReq = 0x20, // 实时行情订阅请求

+ 3 - 1
src/packages/mobile/views/order/position/Index.vue

@@ -16,7 +16,7 @@
                 </Tab>
             </template>
         </Tabs>
-        <component ref="componentRef" :is="selectedComponent.detail" @closed="closeComponent"
+        <component ref="componentRef" :is="selectedComponent.detail" v-bind="{ fromTrade }" @closed="closeComponent"
             v-if="componentId && selectedComponent.detail" />
     </app-view>
 </template>
@@ -28,6 +28,7 @@ import { useComponent } from '@/hooks/component'
 import { i18n } from '@/stores'
 
 const { global: { t } } = i18n
+const fromTrade = false
 
 const components = [
     {
@@ -60,6 +61,7 @@ const components = [
         name: 'pricing',
         title: t('position.pricing.title'),
         component: defineAsyncComponent(() => import('./components/pricing/list/Index.vue')),
+        detail: defineAsyncComponent(() => import('./components/pricing/detail/Index.vue')),
     },
 ]
 

+ 104 - 0
src/packages/mobile/views/order/position/components/pricing/detail/Index.vue

@@ -0,0 +1,104 @@
+<!-- 我的持仓-订单明细 -->
+<template>
+    <app-modal direction="right-top" height="100%" width="100%" v-model:show="showModal">
+        <app-view class="g-form">
+            <template #header>
+                <app-navbar :title="$t('position.goods.holddetail.title')" @back="closed" />
+            </template>
+            <app-pull-refresh ref="pullRefreshRef" v-model:loading="loading" v-model:error="error" @refresh="run">
+                <div class="g-order-list">
+                    <div class="g-order-list__box" v-for="(item, index) in dataList" :key="index">
+                        <div class="g-order-list__titlebar">
+                            <div class="left">
+                                <h4>{{ item.goodscode }}/{{ item.goodsname }}</h4>
+                            </div>
+                        </div>
+                        <div class="g-order-list__content">
+                            <ul>
+                                <li>
+                                    <span>{{ $t('position.goods.holddetail.tradetime') }}</span>
+                                    <span>{{ formatDate(item.tradetime, 'HH:mm:ss') }}</span>
+                                </li>
+                                <li>
+                                    <span>{{ $t('position.goods.holddetail.buyorsell') }}</span>
+                                    <span :class="!item.buyorsell ? 'g-price-up' : 'g-price-down'">
+                                        {{ getBuyOrSellName(item.buyorsell) }}
+                                    </span>
+                                </li>
+                                <li>
+                                    <span>{{ $t('position.goods.holddetail.holderqty') }}</span>
+                                    <span>{{ item.holderqty }}</span>
+                                </li>
+                                <li>
+                                    <span>{{ $t('position.goods.holddetail.freezeqty') }}</span>
+                                    <span>{{ item.freezeqty }}</span>
+                                </li>
+                                <li>
+                                    <span>{{ $t('position.goods.holddetail.holderprice') }}</span>
+                                    <span>{{ item.holderprice }}</span>
+                                </li>
+                                <li>
+                                    <span>{{ $t('position.goods.holddetail.holderamount') }}</span>
+                                    <span>{{ formatDecimal(item.holderamount) }}</span>
+                                </li>
+                            </ul>
+                        </div>
+                        <div class="g-order-list__btnbar" v-if="item.trademode === 50 && item.holderqty">
+                            <Button size="small" @click="showComponent('close', item)" round>{{ $t('operation.transfer') }}</Button>
+                        </div>
+                    </div>
+                </div>
+            </app-pull-refresh>
+            <component ref="componentRef" v-bind="{ selectedRow }" :is="componentMap.get(componentId)"
+                @closed="closeComponent" v-if="componentId" />
+        </app-view>
+    </app-modal>
+</template>
+
+<script lang="ts" setup>
+import { shallowRef, defineAsyncComponent } from 'vue'
+import { Button } from 'vant'
+import { useRequest } from '@/hooks/request'
+import { useComponent } from '@/hooks/component'
+import { getBuyOrSellName } from '@/constants/order'
+import { formatDecimal, formatDate } from '@/filters'
+import { queryTradeHolderDetail } from '@/services/api/order'
+import AppModal from '@/components/base/modal/index.vue'
+import AppPullRefresh from '@mobile/components/base/pull-refresh/index.vue'
+
+const error = shallowRef(false)
+const showModal = shallowRef(true)
+const refresh = shallowRef(false) // 是否刷新父组件数据
+
+const componentMap = new Map<string, unknown>([
+    ['close', defineAsyncComponent(() => import('./components/transfer/Index.vue'))],
+])
+
+const { dataList, loading, run } = useRequest(queryTradeHolderDetail, {
+    params: {
+        trademodes: '10'
+    }
+})
+const selectedRow = shallowRef<Model.TradeHolderDetailRsp>()
+const pullRefreshRef = shallowRef()
+
+const { componentRef, componentId, openComponent, closeComponent } = useComponent(() => {
+    pullRefreshRef.value?.refresh()
+})
+
+const showComponent = (componentName: string, row: Model.TradeHolderDetailRsp) => {
+    selectedRow.value = row
+    openComponent(componentName)
+}
+
+// 关闭弹窗
+const closed = (isRefresh = false) => {
+    refresh.value = isRefresh
+    showModal.value = false
+}
+
+// 暴露组件属性给父组件调用
+defineExpose({
+    closed,
+})
+</script>

+ 168 - 0
src/packages/mobile/views/order/position/components/pricing/detail/components/transfer/Index.vue

@@ -0,0 +1,168 @@
+<!-- 我的持仓- 明细 - 转让 -->
+<template>
+    <app-modal direction="right-top" height="100%" width="100%" v-model:show="showModal" :refresh="refresh">
+        <app-view class="g-form">
+            <template #header>
+                <app-navbar :title="$t('operation.transfer')" @back="closed" />
+            </template>
+            <Form ref="formRef" class="g-form__container" @submit="onCloseSumit">
+                <CellGroup :title="$t('position.goods.subtitle')" inset>
+                    <Cell :title="$t('position.goods.goodsname')" :value="`${selectedRow.goodscode}/${selectedRow.goodsname}`" />
+                    <Cell :title="$t('position.goods.buyorsell')" :value="getBuyOrSellName(selectedRow.buyorsell)" />
+                    <Cell :title="$t('position.goods.holderprice')" :value="selectedRow.holderprice" />
+                    <Cell :title="$t('position.goods.curholderamount')" :value="formatDecimal(selectedRow.holderamount)" />
+                    <Cell :title="$t('position.goods.curpositionqty')" :value="selectedRow.holderqty" />
+                    <Cell :title="$t('position.goods.freezeqty')" :value="selectedRow.freezeqty" />
+                    <Cell :title="$t('position.goods.enableqty')" :value="maxQty" />
+                    <Cell :title="$t('position.goods.closepl')">
+                        <template #value>
+                            <span :class="handlePriceColor(closepl)">
+                                {{ formatDecimal(closepl, selectedRow.decimalplace) }}
+                            </span>
+                        </template>
+                    </Cell>
+                    <Cell :title="$t('position.goods.tradetime')" :value="selectedRow.tradetime" />
+                </CellGroup>
+                <CellGroup :title="$t('position.goods.subtitle3')" inset>
+                    <Cell :title="$t('position.goods.last')" :value="handleNumberValue(quote?.last)" />
+                    <Field name="OrderPrice" :rules="formRules.OrderPrice" :label="$t('position.goods.transferprice')">
+                        <template #input>
+                            <Stepper v-model="formData.OrderPrice" theme="round" button-size="22" :min="0"
+                                :decimal-length="quote?.decimalplace" :step="quote?.decimalvalue" :auto-fixed="false" />
+                        </template>
+                    </Field>
+                    <Field name="OrderQty" :rules="formRules.OrderQty" :label="$t('position.goods.orderqty')">
+                        <template #input>
+                            <Stepper v-model="formData.OrderQty" theme="round" button-size="22" :min="0" :max="maxQty"
+                                :auto-fixed="false" integer />
+                        </template>
+                    </Field>
+                </CellGroup>
+            </Form>
+            <template #footer>
+                <Button block square type="danger" @click="formRef?.submit">{{ $t('operation.transfer') }}</Button>
+            </template>
+        </app-view>
+    </app-modal>
+</template>
+
+<script lang="ts" setup>
+import { shallowRef, PropType, onMounted, computed } from 'vue'
+import AppModal from '@/components/base/modal/index.vue'
+import { CellGroup, Cell, Button, FieldRule, Form, Field, Stepper, FormInstance } from 'vant'
+import { getBuyOrSellName, BuyOrSell } from '@/constants/order'
+import { formatDecimal, handleNumberValue, handleRequestBigNumber, handlePriceColor } from '@/filters'
+import { useOrder } from '@/business/trade'
+import { dialog, fullloading } from '@/utils/vant'
+import { useFuturesStore, usePositionStore, i18n } from '@/stores'
+import { EBuildType, EDelistingType, EListingSelectType, EPriceMode, EValidType } from '@/constants/client'
+
+const props = defineProps({
+    selectedRow: {
+        type: Object as PropType<Model.TradeHolderDetailRsp>,
+        required: true,
+    }
+})
+
+const futuresStore = useFuturesStore()
+const positionStore = usePositionStore()
+const quote = futuresStore.getGoodsQuote(props.selectedRow.goodscode)
+const { global: { t } } = i18n
+// 可用数量
+const maxQty = computed(() => {
+    const record = positionStore.positionList.find((e) => e.goodsid === props.selectedRow.goodsid && e.buyorsell === props.selectedRow.buyorsell)
+    const qty = props.selectedRow.holderqty - props.selectedRow.freezeqty
+    return Math.min(record?.enableqty ?? 0, qty)
+})
+
+// 损益
+const closepl = computed(() => {
+    const { presettle = 0, last = 0 } = quote.value ?? {}
+    const { holderqty, holderamount, agreeunit, buyorsell } = props.selectedRow
+    const price = last || presettle // 没有最新价取昨结价
+    // 计算市值 = 现价 * 数量 * 合约单位
+    const marketValue = price ? price * holderqty * agreeunit : 0
+
+    return price ? (marketValue - holderamount) * (buyorsell === BuyOrSell.Buy ? 1 : -1) : 0
+})
+
+const formRef = shallowRef<FormInstance>()
+const showModal = shallowRef(true)
+// 是否刷新父组件数据
+const refresh = shallowRef(false)
+const { formSubmit, formData } = useOrder()
+
+// 表单验证规则
+const formRules: { [key: string]: FieldRule[] } = {
+    OrderPrice: [{
+        message: t('position.goods.tips1'),
+        validator: () => {
+            return !!formData.OrderPrice
+        }
+    }],
+    OrderQty: [{
+        message: t('position.goods.tips2'),
+        validator: () => {
+            return !!formData.OrderQty
+        }
+    }],
+}
+
+const onCloseSumit = () => {
+    dialog({
+        message: t('position.goods.tips3'),
+        showCancelButton: true,
+    }).then(() => {
+
+        const { marketid, goodsid, buyorsell, tradeid } = props.selectedRow
+        /// 市场ID
+        formData.Header = { GoodsID: goodsid }
+        formData.MarketID = marketid
+        formData.PriceMode = EPriceMode.PRICEMODE_LIMIT
+        formData.BuyOrSell = buyorsell === BuyOrSell.Buy ? BuyOrSell.Sell : BuyOrSell.Buy
+        formData.GoodsID = goodsid
+        formData.ListingSelectType = EListingSelectType.LISTINGSELECTTYPE_DELISTINGTHENLISTING
+        formData.DelistingType = EDelistingType.DELISTINGTYPE_PRICE
+        formData.BuildType = EBuildType.BUILDTYPE_CLOSE
+        formData.TimevalidType = EValidType.VALIDTYPE_DR
+        formData.OperateType = 24
+        formData.RelatedID = handleRequestBigNumber(tradeid)
+
+        /// loding....
+        fullloading((hideLoading) => {
+            formSubmit().then(() => {
+                hideLoading(t('position.goods.tips4'), 'success')
+                closed(true)
+            }).catch((err) => {
+                hideLoading(err, 'fail')
+            })
+        })
+    })
+}
+
+// 关闭弹窗
+const closed = (isRefresh = false) => {
+    refresh.value = isRefresh
+    showModal.value = false
+}
+
+onMounted(() => {
+    const { bid, ask, presettle = 0 } = quote.value ?? {}
+    switch (props.selectedRow.buyorsell) {
+        case BuyOrSell.Buy:
+            formData.OrderPrice = ask || presettle
+            break
+        case BuyOrSell.Sell:
+            formData.OrderPrice = bid || presettle
+            break
+        default:
+            formData.OrderPrice = presettle
+    }
+    formData.OrderQty = maxQty.value
+})
+
+// 暴露组件属性给父组件调用
+defineExpose({
+    closed,
+})
+</script>

+ 13 - 15
src/packages/mobile/views/order/position/components/pricing/list/Index.vue

@@ -44,21 +44,17 @@
                     </ul>
                 </div>
                 <div class="g-order-list__btnbar">
-                    <!-- <Button size="small" @click="showComponent('close', item)" round>平仓</Button> -->
-                    <!-- <Button size="small" v-if="userStore.userType === 5" @click="showComponent('delivery', item)" round>交收</Button> -->
-                    <Button size="small" @click="onClosed(item)" round>平仓</Button>
+                    <Button size="small" @click="onClosed(item)" v-if="!fromTrade" round>平仓</Button>
+                    <Button size="small" @click="callBack(item)" v-if="fromTrade" round>平仓</Button>
                 </div>
             </div>
         </div>
-        <component ref="componentRef" v-bind="{ selectedRow }" :is="componentMap.get(componentId)" @closed="closeComponent"
-            v-if="componentId" />
     </app-pull-refresh>
 </template>
 
 <script lang="ts" setup>
 import { shallowRef, computed } from 'vue'
 import { Button } from 'vant'
-import { useComponent } from '@/hooks/component'
 import AppPullRefresh from '@mobile/components/base/pull-refresh/index.vue'
 import { getBuyOrSellName } from '@/constants/order'
 import { formatDecimal } from '@/filters'
@@ -66,26 +62,28 @@ import { useNavigation } from '@mobile/router/navigation'
 import { BuyOrSell, BuildType } from '@/constants/order'
 import { usePositionStore } from '@/stores'
 
-const componentMap = new Map<string, unknown>([
-    // ['close', defineAsyncComponent(() => import('../close/Index.vue'))],
-    // ['delivery', defineAsyncComponent(() => import('../delivery/Index.vue'))]
-])
-
 const { router } = useNavigation()
 const positionStore = usePositionStore()
-const selectedRow = shallowRef<Model.TradePositionRsp>()
 const pullRefreshRef = shallowRef()
 
 const positionList = computed(() => positionStore.getPositionListByTradeMode(10))
 
-const { componentRef, componentId, closeComponent } = useComponent(() => {
-    pullRefreshRef.value?.refresh()
+const emit = defineEmits(['callBack'])
+const callBack = (item: Model.TradePositionRsp) => {
+    emit('callBack', 1, item.buyorsell)
+}
+
+defineProps({
+    fromTrade: {
+        type: Boolean,
+        required: true
+    }
 })
 
 // 平仓
 const onClosed = (item: Model.TradePositionRsp) => {
     const buyOrSell = item.buyorsell === BuyOrSell.Buy ? BuyOrSell.Sell : BuyOrSell.Buy
-    router.push({ name: 'pricing-detail', query: { goodscode: item.goodscode, buyOrSell: buyOrSell, buildType: BuildType.Close } })
+    router.push({ name: 'pricing-trade', query: { goodscode: item.goodscode, buyOrSell: buyOrSell, buildType: BuildType.Close } })
 }
 
 // 持仓列表刷新

+ 36 - 3
src/packages/mobile/views/pricing/trade/Index.vue

@@ -137,8 +137,16 @@
                 </Button>
             </template>
         </div>
+        <Tabs class="van-tabs--list" v-model:active="active">
+            <template v-for="(item, index) in components" :key="index">
+                <Tab :title="item.title" :name="item.name">
+                    <component :is="item.component" v-bind="{ goodsCode, goodsid, fromTrade }" 
+                        @callBack="itemBack"/>
+                </Tab>
+            </template>
+        </Tabs>
         <template #footer>
-            <component ref="componentRef" :is="componentMap.get(componentId)" v-bind="{ goodsCode, goodsid}"
+            <component ref="componentRef" :is="componentMap.get(componentId)" v-bind="{ goodsCode, goodsid }"
             @closed="closeComponent" v-if="componentId" />
         </template> 
     </app-view>
@@ -148,7 +156,7 @@
 import { useFuturesStore, usePositionStore, i18n } from '@/stores'
 import { useNavigation } from '@mobile/router/navigation'
 import { shallowRef, onMounted, computed, defineAsyncComponent } from 'vue'
-import { Form, Field, Button, FieldRule, FormInstance, Radio, RadioGroup, CellGroup, Icon } from 'vant'
+import { Form, Field, Button, FieldRule, FormInstance, Radio, RadioGroup, CellGroup, Icon, Tab, Tabs } from 'vant'
 import { useOrder } from '@/business/trade'
 import { BuyOrSell, getBuyOrSellList, BuildType, getPricemode2List, PriceMode } from '@/constants/order'
 import { useComponent } from '@/hooks/component'
@@ -173,13 +181,38 @@ const { formData, formSubmit } = useOrder()
 const formRef = shallowRef<FormInstance>()
 const qtyStepList = [1, 5, 10, 20, 30, 50] // 数量步长列表
 const qtyStep = shallowRef(qtyStepList[0]) // 数量步长
+const fromTrade = true
 
 // const accountStore = useAccountStore()
 const positionStore = usePositionStore()
 // 持仓汇总
 const position = shallowRef<Model.TradePositionRsp[]>([]) 
 
+const itemBack = (isPosition: number, buyorsell: BuyOrSell, tradeId: string) => {
+    console.log(isPosition, buyorsell, tradeId)
+    formData.BuyOrSell = buyorsell
+}
+
 const { componentRef, componentId, openComponent, closeComponent } = useComponent()
+const active = shallowRef('cancel')
+const components = [
+    {
+        name: 'cancel',
+        title: '订单可撤',
+        component: defineAsyncComponent(() => import('../trade/components/cancel/Index.vue')),
+    },
+    {
+        name: 'position',
+        title: '持仓汇总',
+        component: defineAsyncComponent(() => import('@mobile/views/order/position/components/pricing/list/Index.vue')),
+    },
+    {
+        name: 'holdlb',
+        title: '持仓明细',
+        component: defineAsyncComponent(() => import('../trade/holdlb/Index.vue')),
+    }
+]
+
 const componentMap = new Map<string, unknown>([
     ['detail', defineAsyncComponent(() => import('./components/detail/Index.vue'))],
 ])
@@ -316,7 +349,7 @@ const onSubmit = () => {
         formSubmit().then(() => {
             hideLoading()
             dialog(t('common.submitsuccess')).then(() => {
-                router.back()
+                // router.back()
             })
         }).catch((err) => {
             hideLoading(err, 'fail')

+ 104 - 0
src/packages/mobile/views/pricing/trade/components/cancel/Index.vue

@@ -0,0 +1,104 @@
+<!-- 我的订单-订单委托 -->
+<template>
+    <app-pull-refresh ref="pullRefreshRef" v-model:loading="loading" v-model:error="error" v-model:pageIndex="pageIndex"
+        :page-count="pageCount" @refresh="run">
+        <div class="g-order-list">
+            <div class="g-order-list__box" v-for="(item, index) in dataList" :key="index">
+                <div class="g-order-list__titlebar">
+                    <div class="left">
+                        <h4>{{ item.goodscode }}/{{ item.goodsname }}</h4>
+                    </div>
+                    <div class="right">
+                        <span>{{ getWRTradeOrderStatusName(item.orderstatus) }}</span>
+                    </div>
+                </div>
+                <div class="g-order-list__content">
+                    <ul>
+                        <li>
+                            <span>{{ $t('order.pricingorder.orderdate') }}</span>
+                            <span>{{ formatDate(item.ordertime, 'YYYY-MM-DD') }}</span>
+                        </li>
+                        <li>
+                            <span>{{ $t('order.pricingorder.buyorsell') }}</span>
+                            <span>{{ getBuyOrSellName(item.buyorsell) }}</span>
+                        </li>
+                        <li>
+                            <span>{{ $t('order.pricingorder.orderqty') }}</span>
+                            <span>{{ formatDecimal(item.orderqty) }}</span>
+                        </li>
+                        <li>
+                            <span>{{ $t('order.pricingorder.orderprice') }}</span>
+                            <span>{{ formatDecimal(item.orderprice) }}</span>
+                        </li>
+                        <li>
+                            <span>{{ $t('order.pricingorder.tradeqty') }}</span>
+                            <span>{{ formatDecimal(item.tradeqty) }}</span>
+                        </li>
+                    </ul>
+                </div>
+                <div class="g-order-list__btnbar">
+                    <Button size="small" v-if="(item.orderstatus === 3 || item.orderstatus === 7)"
+                        @click="onCancelSumit(item)" round>{{ $t('operation.cancel2') }}</Button>
+                </div>
+            </div>
+        </div>
+    </app-pull-refresh>
+</template>
+
+<script lang="ts" setup>
+import { shallowRef } from 'vue'
+import { Button } from 'vant'
+import { useRequest } from '@/hooks/request'
+import { queryTradeOrderDetail } from '@/services/api/order'
+import { getBuyOrSellName, getWRTradeOrderStatusName } from '@/constants/order'
+import { formatDate, formatDecimal, handleRequestBigNumber } from '@/filters'
+import { useCancelOrder } from '@/business/trade'
+import { dialog } from '@/utils/vant'
+import { fullloading } from '@/utils/vant'
+import { i18n } from '@/stores'
+import AppPullRefresh from '@mobile/components/base/pull-refresh/index.vue'
+
+const { global: { t } } = i18n
+const { cancelSubmit, formData } = useCancelOrder()
+const dataList = shallowRef<Model.TradeOrderDetailRsp[]>([])
+const error = shallowRef(false)
+const pullRefreshRef = shallowRef()
+
+const { loading, pageIndex, pageCount, run } = useRequest(queryTradeOrderDetail, {
+    params: {
+        pagesize: 20,
+        tradeMode: '10',
+        orderStatus: '3,7'
+    },
+    onSuccess: (res) => {
+        if (pageIndex.value === 1) {
+            dataList.value = []
+        }
+        dataList.value.push(...res.data)
+    },
+    onError: () => {
+        error.value = true
+    }
+})
+
+const onCancelSumit = (item: Model.TradeOrderDetailRsp) => {
+    dialog({
+        message: t('order.pricingorder.tips1'),
+        showCancelButton: true,
+    }).then(() => {
+        formData.Header = { MarketID: item.marketid, GoodsID: item.goodsid }
+        formData.OldOrderId = handleRequestBigNumber(item.orderid)
+
+        /// loding....
+        fullloading((hideLoading) => {
+            cancelSubmit().then(() => {
+                hideLoading(t('order.pricingorder.tips2'))
+                pullRefreshRef.value?.refresh()
+            }).catch((err) => {
+                hideLoading(err, 'fail')
+            })
+        })
+    })
+}
+
+</script>

+ 84 - 0
src/packages/mobile/views/pricing/trade/holdlb/Index.vue

@@ -0,0 +1,84 @@
+<!-- 我的持仓-订单明细 -->
+<template>
+    <app-pull-refresh ref="pullRefreshRef" v-model:loading="loading" v-model:error="error" @refresh="run">
+        <div class="g-order-list">
+            <div class="g-order-list__box" v-for="(item, index) in dataList" :key="index">
+                <div class="g-order-list__titlebar">
+                    <div class="left">
+                        <h4>{{ item.goodscode }}/{{ item.goodsname }}</h4>
+                    </div>
+                </div>
+                <div class="g-order-list__content">
+                    <ul>
+                        <li>
+                            <span>{{ $t('position.goods.holddetail.tradetime') }}</span>
+                            <span>{{ formatDate(item.tradetime, 'HH:mm:ss') }}</span>
+                        </li>
+                        <li>
+                            <span>{{ $t('position.goods.holddetail.buyorsell') }}</span>
+                            <span :class="!item.buyorsell ? 'g-price-up' : 'g-price-down'">
+                                {{ getBuyOrSellName(item.buyorsell) }}
+                            </span>
+                        </li>
+                        <li>
+                            <span>{{ $t('position.goods.holddetail.holderqty') }}</span>
+                            <span>{{ item.holderqty }}</span>
+                        </li>
+                        <li>
+                            <span>{{ $t('position.goods.holddetail.freezeqty') }}</span>
+                            <span>{{ item.freezeqty }}</span>
+                        </li>
+                        <li>
+                            <span>{{ $t('position.goods.holddetail.holderprice') }}</span>
+                            <span>{{ item.holderprice }}</span>
+                        </li>
+                        <li>
+                            <span>{{ $t('position.goods.holddetail.holderamount') }}</span>
+                            <span>{{ formatDecimal(item.holderamount) }}</span>
+                        </li>
+                    </ul>
+                </div>
+                <div class="g-order-list__btnbar" v-if="item.trademode === 10 && item.holderqty">
+                    <Button size="small" @click="callBack(item)" round>平仓</Button>
+                </div>
+            </div>
+        </div>
+    </app-pull-refresh>
+</template>
+
+<script lang="ts" setup>
+import { shallowRef } from 'vue'
+import { Button } from 'vant'
+import { useRequest } from '@/hooks/request'
+import { getBuyOrSellName } from '@/constants/order'
+import { formatDecimal, formatDate } from '@/filters'
+import { queryTradeHolderDetail } from '@/services/api/order'
+import AppPullRefresh from '@mobile/components/base/pull-refresh/index.vue'
+
+const error = shallowRef(false)
+const showModal = shallowRef(true)
+const refresh = shallowRef(false) // 是否刷新父组件数据
+
+const { dataList, loading, run } = useRequest(queryTradeHolderDetail, {
+    params: {
+        trademodes: '10'
+    }
+})
+const pullRefreshRef = shallowRef()
+
+const emit = defineEmits(['callBack'])
+const callBack = (item: Model.TradeHolderDetailRsp) => {
+    emit('callBack', 2, item.buyorsell, item.tradeid)
+}
+
+// 关闭弹窗
+const closed = (isRefresh = false) => {
+    refresh.value = isRefresh
+    showModal.value = false
+}
+
+// 暴露组件属性给父组件调用
+defineExpose({
+    closed,
+})
+</script>

+ 168 - 0
src/packages/mobile/views/pricing/trade/holdlb/components/transfer/Index.vue

@@ -0,0 +1,168 @@
+<!-- 我的持仓- 明细 - 转让 -->
+<template>
+    <app-modal direction="right-top" height="100%" width="100%" v-model:show="showModal" :refresh="refresh">
+        <app-view class="g-form">
+            <template #header>
+                <app-navbar :title="$t('operation.transfer')" @back="closed" />
+            </template>
+            <Form ref="formRef" class="g-form__container" @submit="onCloseSumit">
+                <CellGroup :title="$t('position.goods.subtitle')" inset>
+                    <Cell :title="$t('position.goods.goodsname')" :value="`${selectedRow.goodscode}/${selectedRow.goodsname}`" />
+                    <Cell :title="$t('position.goods.buyorsell')" :value="getBuyOrSellName(selectedRow.buyorsell)" />
+                    <Cell :title="$t('position.goods.holderprice')" :value="selectedRow.holderprice" />
+                    <Cell :title="$t('position.goods.curholderamount')" :value="formatDecimal(selectedRow.holderamount)" />
+                    <Cell :title="$t('position.goods.curpositionqty')" :value="selectedRow.holderqty" />
+                    <Cell :title="$t('position.goods.freezeqty')" :value="selectedRow.freezeqty" />
+                    <Cell :title="$t('position.goods.enableqty')" :value="maxQty" />
+                    <Cell :title="$t('position.goods.closepl')">
+                        <template #value>
+                            <span :class="handlePriceColor(closepl)">
+                                {{ formatDecimal(closepl, selectedRow.decimalplace) }}
+                            </span>
+                        </template>
+                    </Cell>
+                    <Cell :title="$t('position.goods.tradetime')" :value="selectedRow.tradetime" />
+                </CellGroup>
+                <CellGroup :title="$t('position.goods.subtitle3')" inset>
+                    <Cell :title="$t('position.goods.last')" :value="handleNumberValue(quote?.last)" />
+                    <Field name="OrderPrice" :rules="formRules.OrderPrice" :label="$t('position.goods.transferprice')">
+                        <template #input>
+                            <Stepper v-model="formData.OrderPrice" theme="round" button-size="22" :min="0"
+                                :decimal-length="quote?.decimalplace" :step="quote?.decimalvalue" :auto-fixed="false" />
+                        </template>
+                    </Field>
+                    <Field name="OrderQty" :rules="formRules.OrderQty" :label="$t('position.goods.orderqty')">
+                        <template #input>
+                            <Stepper v-model="formData.OrderQty" theme="round" button-size="22" :min="0" :max="maxQty"
+                                :auto-fixed="false" integer />
+                        </template>
+                    </Field>
+                </CellGroup>
+            </Form>
+            <template #footer>
+                <Button block square type="danger" @click="formRef?.submit">{{ $t('operation.transfer') }}</Button>
+            </template>
+        </app-view>
+    </app-modal>
+</template>
+
+<script lang="ts" setup>
+import { shallowRef, PropType, onMounted, computed } from 'vue'
+import AppModal from '@/components/base/modal/index.vue'
+import { CellGroup, Cell, Button, FieldRule, Form, Field, Stepper, FormInstance } from 'vant'
+import { getBuyOrSellName, BuyOrSell } from '@/constants/order'
+import { formatDecimal, handleNumberValue, handleRequestBigNumber, handlePriceColor } from '@/filters'
+import { useOrder } from '@/business/trade'
+import { dialog, fullloading } from '@/utils/vant'
+import { useFuturesStore, usePositionStore, i18n } from '@/stores'
+import { EBuildType, EDelistingType, EListingSelectType, EPriceMode, EValidType } from '@/constants/client'
+
+const props = defineProps({
+    selectedRow: {
+        type: Object as PropType<Model.TradeHolderDetailRsp>,
+        required: true,
+    }
+})
+
+const futuresStore = useFuturesStore()
+const positionStore = usePositionStore()
+const quote = futuresStore.getGoodsQuote(props.selectedRow.goodscode)
+const { global: { t } } = i18n
+// 可用数量
+const maxQty = computed(() => {
+    const record = positionStore.positionList.find((e) => e.goodsid === props.selectedRow.goodsid && e.buyorsell === props.selectedRow.buyorsell)
+    const qty = props.selectedRow.holderqty - props.selectedRow.freezeqty
+    return Math.min(record?.enableqty ?? 0, qty)
+})
+
+// 损益
+const closepl = computed(() => {
+    const { presettle = 0, last = 0 } = quote.value ?? {}
+    const { holderqty, holderamount, agreeunit, buyorsell } = props.selectedRow
+    const price = last || presettle // 没有最新价取昨结价
+    // 计算市值 = 现价 * 数量 * 合约单位
+    const marketValue = price ? price * holderqty * agreeunit : 0
+
+    return price ? (marketValue - holderamount) * (buyorsell === BuyOrSell.Buy ? 1 : -1) : 0
+})
+
+const formRef = shallowRef<FormInstance>()
+const showModal = shallowRef(true)
+// 是否刷新父组件数据
+const refresh = shallowRef(false)
+const { formSubmit, formData } = useOrder()
+
+// 表单验证规则
+const formRules: { [key: string]: FieldRule[] } = {
+    OrderPrice: [{
+        message: t('position.goods.tips1'),
+        validator: () => {
+            return !!formData.OrderPrice
+        }
+    }],
+    OrderQty: [{
+        message: t('position.goods.tips2'),
+        validator: () => {
+            return !!formData.OrderQty
+        }
+    }],
+}
+
+const onCloseSumit = () => {
+    dialog({
+        message: t('position.goods.tips3'),
+        showCancelButton: true,
+    }).then(() => {
+
+        const { marketid, goodsid, buyorsell, tradeid } = props.selectedRow
+        /// 市场ID
+        formData.Header = { GoodsID: goodsid }
+        formData.MarketID = marketid
+        formData.PriceMode = EPriceMode.PRICEMODE_LIMIT
+        formData.BuyOrSell = buyorsell === BuyOrSell.Buy ? BuyOrSell.Sell : BuyOrSell.Buy
+        formData.GoodsID = goodsid
+        formData.ListingSelectType = EListingSelectType.LISTINGSELECTTYPE_DELISTINGTHENLISTING
+        formData.DelistingType = EDelistingType.DELISTINGTYPE_PRICE
+        formData.BuildType = EBuildType.BUILDTYPE_CLOSE
+        formData.TimevalidType = EValidType.VALIDTYPE_DR
+        formData.OperateType = 24
+        formData.RelatedID = handleRequestBigNumber(tradeid)
+
+        /// loding....
+        fullloading((hideLoading) => {
+            formSubmit().then(() => {
+                hideLoading(t('position.goods.tips4'), 'success')
+                closed(true)
+            }).catch((err) => {
+                hideLoading(err, 'fail')
+            })
+        })
+    })
+}
+
+// 关闭弹窗
+const closed = (isRefresh = false) => {
+    refresh.value = isRefresh
+    showModal.value = false
+}
+
+onMounted(() => {
+    const { bid, ask, presettle = 0 } = quote.value ?? {}
+    switch (props.selectedRow.buyorsell) {
+        case BuyOrSell.Buy:
+            formData.OrderPrice = ask || presettle
+            break
+        case BuyOrSell.Sell:
+            formData.OrderPrice = bid || presettle
+            break
+        default:
+            formData.OrderPrice = presettle
+    }
+    formData.OrderQty = maxQty.value
+})
+
+// 暴露组件属性给父组件调用
+defineExpose({
+    closed,
+})
+</script>

+ 4 - 63
src/packages/mobile/views/pricing/trade/index.less

@@ -2,7 +2,7 @@
     &__form {
         background-color: #fff;
         border-radius: 8px;
-        margin: 5px 15px;
+        margin: 15px 15px 0px 15px;
         margin-bottom: 0;
 
         .form-price {
@@ -23,68 +23,9 @@
                 }
             }
         }
-
-        // .form-submit {
-        //     display: flex;
-        //     gap: 16px;
-        //     padding: 16px;
-        // }
-
-        // .g-qty-group {
-        //     &__stepper {
-        //         width: 70%;
-
-        //         .van-stepper {
-        //             &__input {
-        //                 flex: 1;
-        //                 font-size: 16px;
-        //             }
-
-        //             &__minus,
-        //             &__plus,
-        //             &__input {
-        //                 height: 36px;
-        //             }
-
-        //             &__minus,
-        //             &__plus {
-        //                 width: 36px;
-        //             }
-        //         }
-        //     }
-        // }
     }
 
-    // &__list {
-    //     padding: 10px;
-
-    //     table {
-    //         width: 100%;
-    //         text-align: center;
-    //         border-radius: 10px;
-    //         overflow: hidden;
-
-    //         tr {
-    //             &.is-active td {
-    //                 background-color: #f0f1f5;
-    //             }
-
-    //             td {
-    //                 background-color: #fff;
-    //                 border-bottom: 1px solid #f0f1f5;
-
-    //                 &:first-child {
-    //                     >span {
-    //                         font-weight: bold;
-    //                     }
-    //                 }
-
-    //                 >span {
-    //                     display: block;
-    //                     padding: 14px 0;
-    //                 }
-    //             }
-    //         }
-    //     }
-    // }
+    .g-form__footer {
+        margin-bottom: 10px;
+    }
 }

+ 1 - 0
src/services/bus/types.ts

@@ -14,6 +14,7 @@ export enum EventName {
     WRTradeDealedNtf, // 仓单贸易成交通知
     ListingOrderChangeNtf, // 挂牌委托变更广播通知
     OrderDealedNtf, // 成交通知
+    OrderSuccessedNtf, // 委托成功通知
     OrderRsp, // 委托回应通知
     PosChangedNtf, // 头寸变化通知
     RiskControlNtf, // 风控通知

+ 8 - 0
src/services/websocket/message.ts

@@ -46,6 +46,14 @@ export async function pushMessage50(pkg: Package50, contentType: 'encrypted' | '
             }, delay, funCode.toString())
             break
         }
+
+        case FunCode.OrderSuccessedNtf: {
+            timerInterceptor.debounce(() => {
+                // 成交通知
+                eventBus.$emit('OrderSuccessedNtf')
+            }, delay, funCode.toString())
+            break
+        }
         // case FunCode.MarketStatusChangeNtf: {
         //     // 运行状态 - 0:初始化 1:待开市 2:开市 3:休市 4:手工休市 5:闭市 6:确认行权开始 7:确认行权结束 10:日终处理开始 11:日终处理成功 12:日终处理失败 13基础服务结算开始 14基础服务结算成功 23.资金结算开始 24.资金结算成功 25.资金结算失败 26.系统结算成功 27.系统结算失败 28.盘中处理开始 29.盘中处理成功 30.盘中处理失败 31.资金结算开始(内) 32.资金结算成功(内) 33.资金结算失败(内) 40.签到开始 41.签到成功 42.签到部份成功 43.签到失败 44.签退开始 45.签退成功 46.签退部份成功 47.签退失败 48.对账开始 49.对账成功 50.对账失败 51.清算开始 52.清算成功 53.清算失败 54.清算部分成功 55. 系统结算开始 62.今日免清算
         //     try {