Browse Source

Merge branch 'master' of http://47.101.159.18:3000/Muchinfo/MTP20_WEB_GLOBAL

li.shaoyi 2 năm trước cách đây
mục cha
commit
0ba0344774

+ 29 - 2
src/business/trade/index.ts

@@ -18,7 +18,8 @@ import {
     spotPresalePointPrice,
     cancelOrder,
     holderClose,
-    order
+    order,
+    offlineDelivery
 } from '@/services/api/trade'
 import { formatDate } from "@/filters";
 import Long from 'long'
@@ -585,4 +586,30 @@ export function useOrder() {
         formData,
         formSubmit
     }
-}
+}
+
+// 线下交收
+export function useOfflineDelivery() {
+    const loading = shallowRef(false)
+    const formData = reactive<Partial<Proto.OfflineDeliveryReq>>({})
+    /// 提交
+    const formSubmit = async () => {
+        try {
+            loading.value = true
+
+            return await offlineDelivery({
+                data: {
+                    ...formData
+                }
+            })
+        } finally {
+            loading.value = false
+        }
+    }
+
+    return {
+        loading,
+        formData,
+        formSubmit
+    }
+}

+ 2 - 0
src/constants/funcode.ts

@@ -102,4 +102,6 @@ export enum FunCode {
     MakeUpDepositRsp = 196716, // 补足定金应答
     HoldAppendDepositReq = 196719, // 持仓追加定金接口请求
     HoldAppendDepositRsp = 196720, // 持仓追加定金接口应答
+    OfflineDeliveryReq = 196723,   // 线下交收申请请求
+    OfflineDeliveryRsp = 196724,   // 线下交收申请请求
 } 

+ 6 - 5
src/packages/mobile/views/goods/detail/components/delisting/Index.vue

@@ -97,14 +97,15 @@ const formRules: { [key in keyof Proto.OrderReq]?: FieldRule[] } = {
     }],
 }
 
-// 提交
+// 提交
 const onSubmit = () => {
-    const { goodsid, orderid } = props.selectedRow
+    const { goodsid, orderid, orderprice, buyorsell} = props.selectedRow
     /// 获取对应的市场ID
     formData.MarketID = useFuturesStore().getGoods(goodsid).value?.marketid ?? 0
-    formData.PriceMode = EPriceMode.PRICEMODE_MARKET
+    formData.PriceMode = EPriceMode.PRICEMODE_LIMIT
     formData.RelatedID = orderid
-    formData.BuyOrSell = props.selectedRow.buyorsell,
+    formData.OrderPrice = orderprice
+    formData.BuyOrSell = buyorsell === 0 ? 1 : 0,
     formData.GoodsID = goodsid
     formData.ListingSelectType = EListingSelectType.LISTINGSELECTTYPE_DELISTING
     formData.DelistingType = EDelistingType.DELISTINGTYPE_SELECTED
@@ -119,7 +120,7 @@ const onSubmit = () => {
     fullloading((hideLoading) => {
         formSubmit().then(() => {
             hideLoading()
-            dialog('牌提交成功。').then(() => closed(true))
+            dialog('牌提交成功。').then(() => closed(true))
         }).catch((err) => {
             hideLoading(err, 'fail')
         })

+ 7 - 5
src/packages/mobile/views/goods/detail/components/listing/Index.vue

@@ -25,12 +25,14 @@
             </Field>
             <Field name="OrderPrice" :rules="formRules.OrderPrice" label="价格">
                 <template #input>
-                    <Stepper v-model="formData.OrderPrice" input-width="100" theme="round" button-size="22" :auto-fixed="false" :step="0.01" />
+                    <Stepper v-model="formData.OrderPrice" input-width="100" theme="round" button-size="22"
+                        :auto-fixed="false" :step="0.01" />
                 </template>
             </Field>
             <Field name="OrderQty" :rules="formRules.OrderQty" label="数量">
                 <template #input>
-                    <Stepper v-model="formData.OrderQty" input-width="100" theme="round" button-size="22" :auto-fixed="false" integer />
+                    <Stepper v-model="formData.OrderQty" input-width="100" theme="round" button-size="22"
+                        :auto-fixed="false" integer />
                 </template>
             </Field>
         </Form>
@@ -81,10 +83,10 @@ const onSubmit = () => {
     /// 获取对应的市场ID
     formData.MarketID = useFuturesStore().getGoods(goodsid).value?.marketid ?? 0
     formData.BuyOrSell = buyOrSell.value,
-    formData.PriceMode = EPriceMode.PRICEMODE_LIMIT
-    formData.MarketMaxSub = 0.0 
+        formData.PriceMode = EPriceMode.PRICEMODE_LIMIT
+    formData.MarketMaxSub = 0.0
     formData.GoodsID = goodsid
-    formData.ListingSelectType = EListingSelectType.LISTINGSELECTTYPE_LISTING
+    formData.ListingSelectType = EListingSelectType.LISTINGSELECTTYPE_DELISTINGTHENLISTING
     formData.DelistingType = EDelistingType.DELISTINGTYPE_SELECTED
     formData.BuildType = EBuildType.BUILDTYPE_OPEN
     formData.ValidType = EValidType.VALIDTYPE_DR

+ 8 - 0
src/packages/mobile/views/order/list/Index.vue

@@ -35,6 +35,12 @@
             </Tab>
             <Tab title="挂牌成交">
             </Tab>
+            <Tab title="点价委托">
+                <component :is="componentMap.get('pricingorder')" />
+            </Tab>
+            <Tab title="点价成交">
+                <component :is="componentMap.get('pricingtrade')" />
+            </Tab>
         </Tabs>
         <component ref="componentRef" :is="componentMap.get(componentId)" @closed="closeComponent" v-if="componentId" />
     </app-view>
@@ -60,6 +66,8 @@ const componentMap = new Map<string, unknown>([
     ['hisswaptrade', defineAsyncComponent(() => import('./components/hisswaptrade/list/Index.vue'))], // 历史掉期成交
     ['hisgoodsorder', defineAsyncComponent(() => import('./components/hisgoodsorder/list/Index.vue'))], // 历史订单委托
     ['hisgoodstrade', defineAsyncComponent(() => import('./components/hisgoodstrade/list/Index.vue'))], // 历史订单成交
+    ['pricingorder', defineAsyncComponent(() => import('./components/pricingorder/list/Index.vue'))], // 挂牌点价委托
+    ['pricingtrade', defineAsyncComponent(() => import('./components/pricingtrade/list/Index.vue'))], // 挂牌点价成交
 ])
 
 const onMoreClick = () => {

+ 2 - 1
src/packages/mobile/views/order/list/components/goodsorder/list/Index.vue

@@ -101,7 +101,8 @@ const onCancelSumit = (item: Model.TradeOrderDetailRsp) => {
         message: '确认要撤销吗?',
         showCancelButton: true,
     }).then(() => {
-        formData.Header = { MarketID: item.marketid }
+
+        formData.Header = { MarketID: item.marketid, GoodsID: item.goodsid }
         formData.OldOrderId = item.orderid
 
         /// loding....

+ 79 - 0
src/packages/mobile/views/order/list/components/pricingorder/detail/Index.vue

@@ -0,0 +1,79 @@
+<!-- 我的订单- 订单委托 - 详情 -->
+<template>
+    <app-modal direction="right" height="100%" v-model:show="showModal" :refresh="refresh">
+        <app-view class="g-form">
+            <template #header>
+                <app-navbar title="详细" @back="closed" />
+            </template>
+            <div v-if="props" class="order-detail__container g-form__container">
+                <CellGroup title="挂牌点价委托信息">
+                    <Cell title="商品代码/名称" :value="selectedRow.goodscode + '/' + selectedRow.goodsname" />
+                    <Cell title="方向" :value="getBuyOrSellName(selectedRow.buyorsell)" />
+                    <Cell title="委托数量" :value="formatDecimal(selectedRow.orderqty)" />
+                    <Cell title="委托价格" :value="formatDecimal(selectedRow.orderprice)" />
+                    <Cell title="成交数量" :value="formatDecimal(selectedRow.tradeqty)" />
+                    <Cell title="委托状态" :value="getWRTradeOrderStatusName(selectedRow.orderstatus)" />
+                    <Cell title="委托时间" :value="formatDate(selectedRow.ordertime)" />
+                    <Cell title="委托单号" :value="selectedRow.orderid" />
+                </CellGroup>
+            </div>
+            <template #footer v-if="selectedRow.orderstatus === 3 || selectedRow.orderstatus === 7">
+                <Button type="primary" block round @click="onCancelSumit">撤销</Button>
+            </template>
+        </app-view>
+    </app-modal>
+</template>
+
+<script lang="ts" setup>
+import { shallowRef, PropType } from 'vue'
+import AppModal from '@/components/base/modal/index.vue'
+import { CellGroup, Cell, Button } from 'vant'
+import { getBuyOrSellName, getWRTradeOrderStatusName } from '@/constants/order'
+import { formatDate, formatDecimal } from '@/filters'
+import { useCancelOrder } from '@/business/trade'
+import { dialog, fullloading } from '@/utils/vant'
+
+const showModal = shallowRef(true)
+// 是否刷新父组件数据
+const refresh = shallowRef(false)
+const { cancelSubmit, formData } = useCancelOrder()
+
+const props = defineProps({
+    selectedRow: {
+        type: Object as PropType<Model.TradeOrderDetailRsp>,
+        required: true,
+    }
+})
+
+const onCancelSumit = () => {
+    dialog({
+        message: '确认要撤销吗?',
+        showCancelButton: true,
+    }).then(() => {
+        /// 市场ID
+        formData.Header = { MarketID: props.selectedRow.marketid }
+        formData.OldOrderId = props.selectedRow.orderid
+
+        /// loding....
+        fullloading((hideLoading) => {
+            cancelSubmit().then(() => {
+                hideLoading('撤销成功')
+                closed(true)
+            }).catch((err) => {
+                hideLoading(err, 'fail')
+            })
+        })
+    })
+}
+
+// 关闭弹窗
+const closed = (isRefresh = false) => {
+    refresh.value = isRefresh
+    showModal.value = false
+}
+
+// 暴露组件属性给父组件调用
+defineExpose({
+    closed,
+})
+</script>

+ 122 - 0
src/packages/mobile/views/order/list/components/pricingorder/list/Index.vue

@@ -0,0 +1,122 @@
+<!-- 我的订单-订单委托 -->
+<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">
+                        <h5>{{ item.goodscode }}/{{ item.goodsname }}</h5>
+                    </div>
+                    <div class="right">
+                        <span>{{ item.orderid }}</span>
+                    </div>
+                </div>
+                <div class="g-order-list__content">
+                    <ul>
+                        <li>
+                            <span>时间:</span>
+                            <span>{{ formatDate(item.ordertime, 'HH:mm:ss') }}</span>
+                        </li>
+                        <li>
+                            <span>方向:</span>
+                            <span>{{ getBuyOrSellName(item.buyorsell) }}</span>
+                        </li>
+                        <li>
+                            <span>委托数量:</span>
+                            <span>{{ formatDecimal(item.orderqty) }}</span>
+                        </li>
+                        <li>
+                            <span>委托价格:</span>
+                            <span>{{ formatDecimal(item.orderprice) }}</span>
+                        </li>
+                        <li>
+                            <span>成交数量:</span>
+                            <span>{{ formatDecimal(item.tradeqty) }}</span>
+                        </li>
+                        <li>
+                            <span>委托状态:</span>
+                            <span>{{ getWRTradeOrderStatusName(item.orderstatus) }}</span>
+                        </li>
+                    </ul>
+                </div>
+                <div class="g-order-list__btnbar">
+                    <Button size="small" @click="showComponent('detail', item)" round>详情</Button>
+                    <Button size="small" v-if="(item.orderstatus === 3 || item.orderstatus === 7)"
+                        @click="onCancelSumit(item)" 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, defineAsyncComponent } from 'vue'
+import { Button } from 'vant'
+import { useComponent } from '@/hooks/component'
+import { useRequest } from '@/hooks/request'
+import { queryTradeOrderDetail } from '@/services/api/order'
+import { getBuyOrSellName, getWRTradeOrderStatusName } from '@/constants/order'
+import { formatDate, formatDecimal } from '@/filters'
+import { useCancelOrder } from '@/business/trade'
+import { dialog } from '@/utils/vant'
+import { fullloading } from '@/utils/vant'
+
+import AppPullRefresh from '@mobile/components/base/pull-refresh/index.vue'
+
+const componentMap = new Map<string, unknown>([
+    ['detail', defineAsyncComponent(() => import('../detail/Index.vue'))]
+])
+
+const { cancelSubmit, formData } = useCancelOrder()
+const dataList = shallowRef<Model.TradeOrderDetailRsp[]>([])
+const selectedRow = shallowRef<Model.TradeOrderDetailRsp>()
+const error = shallowRef(false)
+const pullRefreshRef = shallowRef()
+
+const { componentRef, componentId, openComponent, closeComponent } = useComponent(() => {
+    pullRefreshRef.value?.refresh()
+})
+
+const { loading, pageIndex, pageCount, run } = useRequest(queryTradeOrderDetail, {
+    params: {
+        pagesize: 20,
+        tradeMode: '10'
+    },
+    onSuccess: (res) => {
+        if (pageIndex.value === 1) {
+            dataList.value = []
+        }
+        dataList.value.push(...res.data)
+    },
+    onError: () => {
+        error.value = true
+    }
+})
+
+const onCancelSumit = (item: Model.TradeOrderDetailRsp) => {
+    dialog({
+        message: '确认要撤销吗?',
+        showCancelButton: true,
+    }).then(() => {
+        formData.Header = { MarketID: item.marketid }
+        formData.OldOrderId = item.orderid
+
+        /// loding....
+        fullloading((hideLoading) => {
+            cancelSubmit().then(() => {
+                hideLoading('撤销成功')
+            }).catch((err) => {
+                hideLoading(err, 'fail')
+            })
+        })
+    })
+}
+
+const showComponent = (componentName: string, row: Model.TradeOrderDetailRsp) => {
+    selectedRow.value = row
+    openComponent(componentName)
+}
+</script>

+ 56 - 0
src/packages/mobile/views/order/list/components/pricingtrade/detail/Index.vue

@@ -0,0 +1,56 @@
+<!-- 我的订单- 点价成交 - 详情 -->
+<template>
+    <app-modal direction="right" height="100%" v-model:show="showModal" :refresh="refresh">
+        <app-view class="g-form">
+            <template #header>
+                <app-navbar title="详细" @back="closed" />
+            </template>
+            <div v-if="props" class="order-detail__container g-form__container">
+                <CellGroup title="挂牌点价成交信息">
+                    <Cell title="商品代码/名称" :value="selectedRow.goodscode+'/'+selectedRow.goodsname"/>
+                    <Cell title="方向" :value="getBuyOrSellName(selectedRow.buyorsell)" />
+                    <Cell title="类型" :value="getBuildTypeName(selectedRow.buildtype)" />
+                    <Cell title="成交数量" :value="formatDecimal(selectedRow.tradeqty)" />
+                    <Cell title="成交价格" :value="formatDecimal(selectedRow.tradeprice)" />
+                    <Cell title="手续费" :value="formatDecimal(selectedRow.charge)" />
+                    <Cell title="平仓损益" :value="formatDecimal(selectedRow.closepl)" />
+                    <!-- <Cell title="对手方" :value="selectedRow.matchaccountid" /> -->
+                    <Cell title="成交时间" :value="formatDate(selectedRow.tradetime)" />
+                    <Cell title="成交单号" :value="selectedRow.tradeid" />
+                </CellGroup>
+            </div>
+            <div v-else>
+                <Empty />
+            </div>
+        </app-view>
+    </app-modal>
+</template>
+
+<script lang="ts" setup>
+import { shallowRef, PropType } from 'vue'
+import { CellGroup, Cell } from 'vant'
+import { formatDate, formatDecimal } from '@/filters'
+import AppModal from '@/components/base/modal/index.vue'
+import { getBuyOrSellName, getBuildTypeName } from '@/constants/order'
+
+const showModal = shallowRef(true)
+const refresh = shallowRef(false) // 是否刷新父组件数据
+
+const props = defineProps({
+    selectedRow: {
+        type: Object as PropType<Model.TradeDetailRsp>,
+        required: true,
+    }
+})
+
+// 关闭弹窗
+const closed = (isRefresh = false) => {
+    refresh.value = isRefresh
+    showModal.value = false
+}
+
+// 暴露组件属性给父组件调用
+defineExpose({
+    closed,
+})
+</script>

+ 96 - 0
src/packages/mobile/views/order/list/components/pricingtrade/list/Index.vue

@@ -0,0 +1,96 @@
+<!-- 我的订单-订单成交 -->
+<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">
+                        <h5>{{ item.goodscode }}/{{ item.goodsname }}</h5>
+                    </div>
+                    <div class="right">
+                        <span>{{ item.tradeid }}</span>
+                    </div>
+                </div>
+                <div class="g-order-list__content">
+                    <ul>
+                        <li>
+                            <span>时间:</span>
+                            <span>{{ formatDate(item.tradetime, 'HH:mm:ss') }}</span>
+                        </li>
+                        <li>
+                            <span>类型:</span>
+                            <span>{{ getBuildTypeName(item.buildtype) }}{{ getBuyOrSellName(item.buyorsell) }}</span>
+                        </li>
+                        <li>
+                            <span>成交价格:</span>
+                            <span>{{ formatDecimal(item.tradeprice) }}</span>
+                        </li>
+                        <li>
+                            <span>手续费:</span>
+                            <span>{{ formatDecimal(item.charge)}}</span>
+                        </li>
+                        <li>
+                            <span>成交数量:</span>
+                            <span>{{ formatDecimal(item.tradeqty) }}</span>
+                        </li>
+                        <li>
+                            <span>平仓盈亏:</span>
+                            <span>{{ formatDecimal(item.closepl) }}</span>
+                        </li>
+                    </ul>
+                </div>
+                <div class="g-order-list__btnbar">
+                    <Button size="small" @click="showComponent('detail', item)" 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, defineAsyncComponent } from 'vue'
+import { Button } from 'vant'
+import { useComponent } from '@/hooks/component'
+import { useRequest } from '@/hooks/request'
+import { queryTradeDetail } from '@/services/api/order'
+import AppPullRefresh from '@mobile/components/base/pull-refresh/index.vue'
+import { getBuyOrSellName, getBuildTypeName } from '@/constants/order'
+import { formatDate, formatDecimal } from '@/filters'
+
+const componentMap = new Map<string, unknown>([
+    ['detail', defineAsyncComponent(() => import('../detail/Index.vue'))],
+])
+
+const dataList = shallowRef<Model.TradeDetailRsp[]>([])
+const selectedRow = shallowRef<Model.TradeDetailRsp>()
+const error = shallowRef(false)
+const pullRefreshRef = shallowRef()
+
+const { componentRef, componentId, openComponent, closeComponent } = useComponent(() => {
+    pullRefreshRef.value?.refresh()
+})
+
+const { loading, pageIndex, pageCount, run } = useRequest(queryTradeDetail, {
+    params: {
+        pagesize: 20,
+        tradeMode: '10'
+    },
+    onSuccess: (res) => {
+        if (pageIndex.value === 1) {
+            dataList.value = []
+        }
+        dataList.value.push(...res.data)
+    },
+    onError: () => {
+        error.value = true
+    }
+})
+
+const showComponent = (componentName: string, row: Model.TradeDetailRsp) => {
+    selectedRow.value = row
+    openComponent(componentName)
+}
+</script>

+ 4 - 0
src/packages/mobile/views/order/position/Index.vue

@@ -19,6 +19,9 @@
             <Tab title="现货持仓">
                 <component :is="componentMap.get('spot')" />
             </Tab>
+            <Tab title="点价持仓">
+                <component :is="componentMap.get('pricing')" />
+            </Tab>
         </Tabs>
     </app-view>
 </template>
@@ -33,6 +36,7 @@ const componentMap = new Map<string, unknown>([
     ['spot', defineAsyncComponent(() => import('./components/spot/list/Index.vue'))], // 现货持仓
     ['swap', defineAsyncComponent(() => import('./components/swap/list/Index.vue'))], // 掉期持仓
     ['goods', defineAsyncComponent(() => import('./components/goods/list/Index.vue'))], // 订单持仓
+    ['pricing', defineAsyncComponent(() => import('./components/pricing/list/Index.vue'))], // 挂牌点价持仓
 ])
 
 const active = shallowRef(0)

+ 114 - 0
src/packages/mobile/views/order/position/components/goods/close/Index.vue

@@ -0,0 +1,114 @@
+<!-- 我的订单- 订单持仓 - 平仓 -->
+<template>
+    <app-modal direction="right" height="100%" v-model:show="showModal" :refresh="refresh">
+        <app-view class="g-form">
+            <template #header>
+                <app-navbar title="订单持仓 - 平仓" @back="closed" />
+            </template>
+            <div v-if="props" class="order-detail__container g-form__container">
+                <CellGroup title="持仓信息">
+                    <Cell title="商品代码/名称" :value="selectedRow.goodscode + '/' + selectedRow.goodsname" />
+                    <Cell title="持仓方向" :value="getBuyOrSellName(selectedRow.buyorsell)" />
+                    <Cell title="持仓金额" :value="formatDecimal(selectedRow.holderamount)" />
+                    <Cell title="持仓数量" :value="formatDecimal(selectedRow.curpositionqty)" />
+                    <Cell title="冻结数量" :value="formatDecimal(selectedRow.frozenqty)" />
+                    <Cell title="可用数量" :value="formatDecimal(selectedRow.enableqty)" />
+                    <Cell title="持仓均价" :value="formatDecimal(selectedRow.averageprice)" />
+                    <Cell title="参考损益" :value="'--'" />
+                </CellGroup>
+                <CellGroup title="平仓信息">
+                    <Cell title="当前价" :value="'--'" />
+                    <Form class="goods-close__form" ref="formRef" @submit="onCloseSumit" v-if="props">
+                        <Field name="OrderQty" :rules="formRules.OrderQty" label="平仓数量">
+                            <template #input>
+                                <Stepper v-model="formData.OrderQty" input-width="100" theme="round" button-size="22" :min="0" :step="0.01" :max="selectedRow.enableqty" :auto-fixed="false" />
+                            </template>
+                        </Field>
+                    </Form>
+                </CellGroup>
+            </div>
+            <template #footer>
+                <Button type="primary" block round @click="onCloseSumit">平仓</Button>
+            </template>
+        </app-view>
+    </app-modal>
+</template>
+
+<script lang="ts" setup>
+import { shallowRef, PropType } from 'vue'
+import AppModal from '@/components/base/modal/index.vue'
+import { CellGroup, Cell, Button, FieldRule, Form, Field, Stepper } from 'vant'
+import { getBuyOrSellName } from '@/constants/order'
+import { formatDecimal } from '@/filters'
+import { useOrder } from '@/business/trade'
+import { dialog, fullloading } from '@/utils/vant'
+import { EBuildType, EDelistingType, EListingSelectType, EOrderOperateType, EPriceMode, EValidType } from '@/constants/client'
+
+const showModal = shallowRef(true)
+// 是否刷新父组件数据
+const refresh = shallowRef(false)
+const { formSubmit, formData } = useOrder()
+
+const props = defineProps({
+    selectedRow: {
+        type: Object as PropType<Model.TradePositionRsp>,
+        required: true,
+    }
+})
+
+// 表单验证规则
+const formRules: { [key in keyof Proto.OrderReq]?: FieldRule[] } = {
+    OrderQty: [{
+        message: '请输入平仓数量',
+        validator: () => {
+            return !!formData.OrderQty
+        }
+    }],
+}
+
+const onCloseSumit = () => {
+    dialog({
+        message: '确认要撤销吗?',
+        showCancelButton: true,
+    }).then(() => {
+
+        const { marketid, goodsid, buyorsell, averageprice} = props.selectedRow
+        /// 市场ID
+        formData.Header = { MarketID: marketid, GoodsID: goodsid }
+        formData.MarketID = marketid
+        formData.PriceMode = EPriceMode.PRICEMODE_LIMIT
+        formData.OrderPrice = averageprice
+        formData.BuyOrSell = buyorsell === 0 ? 1 : 0,
+        formData.GoodsID = goodsid
+        formData.ListingSelectType = EListingSelectType.LISTINGSELECTTYPE_DELISTING
+        formData.DelistingType = EDelistingType.DELISTINGTYPE_PRICE
+        formData.BuildType = EBuildType.BUILDTYPE_CLOSE
+        formData.ValidType = EValidType.VALIDTYPE_DR
+        formData.OperateType = EOrderOperateType.ORDEROPERATETYPE_NORMAL
+        formData.TriggerType = 0
+        formData.ServiceTime = ""
+        formData.ValidTime = ""
+
+        /// loding....
+        fullloading((hideLoading) => {
+            formSubmit().then(() => {
+                hideLoading('平仓成功')
+                closed(true)
+            }).catch((err) => {
+                hideLoading(err, 'fail')
+            })
+        })
+    })
+}
+
+// 关闭弹窗
+const closed = (isRefresh = false) => {
+    refresh.value = isRefresh
+    showModal.value = false
+}
+
+// 暴露组件属性给父组件调用
+defineExpose({
+    closed,
+})
+</script>

+ 110 - 0
src/packages/mobile/views/order/position/components/goods/delivery/Index.vue

@@ -0,0 +1,110 @@
+<!-- 我的订单- 订单持仓 - 交收 -->
+<template>
+    <app-modal direction="right" height="100%" v-model:show="showModal" :refresh="refresh">
+        <app-view class="g-form">
+            <template #header>
+                <app-navbar title="订单持仓 - 交收" @back="closed" />
+            </template>
+            <div v-if="props" class="order-detail__container g-form__container">
+                <CellGroup title="持仓信息">
+                    <Cell title="商品代码/名称" :value="selectedRow.goodscode + '/' + selectedRow.goodsname" />
+                    <Cell title="持仓方向" :value="getBuyOrSellName(selectedRow.buyorsell)" />
+                    <Cell title="持仓金额" :value="formatDecimal(selectedRow.holderamount)" />
+                    <Cell title="持仓数量" :value="formatDecimal(selectedRow.curpositionqty)" />
+                    <Cell title="冻结数量" :value="formatDecimal(selectedRow.frozenqty)" />
+                    <Cell title="可用数量" :value="formatDecimal(selectedRow.enableqty)" />
+                    <Cell title="持仓均价" :value="formatDecimal(selectedRow.averageprice)" />
+                    <Cell title="参考损益" :value="'--'" />
+                </CellGroup>
+                <CellGroup title="交收信息">
+                    <Cell title="交收对手方" :value="'--'" />
+                    <Form class="goods-close__form" ref="formRef" @submit="onDeliverySumit" v-if="props">
+                        <Field name="DeliveryLot" :rules="formRules.DeliveryLot" label="交收数量">
+                            <template #input>
+                                <Stepper v-model="formData.DeliveryLot" input-width="100" theme="round" button-size="22" :min="0" :step="0.01" :max="selectedRow.enableqty" :auto-fixed="false" />
+                            </template>
+                        </Field>
+                        <Field name="DeliveryInfo" :rules="formRules.DeliveryLot" label="交收信息" placeholder="请输入交收信息">
+                        </Field>
+                    </Form>
+                </CellGroup>
+            </div>
+            <template #footer>
+                <Button type="primary" block round @click="onDeliverySumit">交收</Button>
+            </template>
+        </app-view>
+    </app-modal>
+</template>
+
+<script lang="ts" setup>
+import { shallowRef, PropType } from 'vue'
+import AppModal from '@/components/base/modal/index.vue'
+import { CellGroup, Cell, Button, FieldRule, Form, Field, Stepper} from 'vant'
+import { getBuyOrSellName } from '@/constants/order'
+import { formatDecimal } from '@/filters'
+import { useOfflineDelivery } from '@/business/trade'
+import { dialog, fullloading } from '@/utils/vant'
+
+const showModal = shallowRef(true)
+// 是否刷新父组件数据
+const refresh = shallowRef(false)
+const { formSubmit, formData } = useOfflineDelivery()
+
+const props = defineProps({
+    selectedRow: {
+        type: Object as PropType<Model.TradePositionRsp>,
+        required: true,
+    }
+})
+
+// 表单验证规则
+const formRules: { [key in keyof Proto.OfflineDeliveryReq]?: FieldRule[] } = {
+    DeliveryLot: [{
+        message: '请输入交收数量',
+        validator: () => {
+            return !!formData.DeliveryLot
+        }
+    }],
+    DeliveryInfo: [{
+        message: '请输入交收信息',
+        validator: () => {
+            return !!formData.DeliveryInfo
+        }
+    }],
+}
+
+const onDeliverySumit = () => {
+    dialog({
+        message: '确认要交收吗?',
+        showCancelButton: true,
+    }).then(() => {
+        const { marketid, goodsid, goodscode, buyorsell }  = props.selectedRow
+        /// 市场ID
+        formData.Header = { MarketID: marketid, GoodsID: goodsid }
+        formData.GoodsCode = goodscode
+        formData.GoodsID = goodsid
+        formData.BuyOrSell = buyorsell
+	
+        /// loding....
+        fullloading((hideLoading) => {
+            formSubmit().then(() => {
+                hideLoading('交收成功')
+                closed(true)
+            }).catch((err) => {
+                hideLoading(err, 'fail')
+            })
+        })
+    })
+}
+
+// 关闭弹窗
+const closed = (isRefresh = false) => {
+    refresh.value = isRefresh
+    showModal.value = false
+}
+
+// 暴露组件属性给父组件调用
+defineExpose({
+    closed,
+})
+</script>

+ 108 - 0
src/packages/mobile/views/order/position/components/pricing/list/Index.vue

@@ -0,0 +1,108 @@
+<!-- 我的持仓-挂牌点价持仓 -->
+<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>
+                        <span>--</span>
+                    </div>
+                    <div class="right">
+                        <span>{{ getBuyOrSellName(item.buyorsell) }}</span>
+                    </div>
+                </div>
+                <div class="g-order-list__content">
+                    <ul>
+                        <li>
+                            <span>持有数量:</span>
+                            <span>{{ formatDecimal(item.curpositionqty) }}</span>
+                        </li>
+                        <li>
+                            <span>持仓均价:</span>
+                            <span>{{ item.averageprice === 0.0 ? '--' : formatDecimal(item.averageprice) }}</span>
+                        </li>
+                        <li>
+                            <span>冻结数量:</span>
+                            <span>{{ formatDecimal(item.frozenqty) }}</span>
+                        </li>
+                        <li>
+                            <span>持仓金额:</span>
+                            <span>{{ formatDecimal(item.curholderamount) }}</span>
+                        </li>
+                        <li>
+                            <span>可用数量:</span>
+                            <span>{{ formatDecimal(item.enableqty) }}</span>
+                        </li>
+                        <li>
+                            <span>参考损益:</span>
+                            <span>--</span>
+                        </li>
+                    </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>
+                </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, defineAsyncComponent } from 'vue'
+import { Button } from 'vant'
+import { useComponent } from '@/hooks/component'
+import { useRequest } from '@/hooks/request'
+import { queryTradePosition } from '@/services/api/order'
+import AppPullRefresh from '@mobile/components/base/pull-refresh/index.vue'
+import { getBuyOrSellName } from '@/constants/order'
+import { formatDecimal } from '@/filters'
+import { useUserStore } from '@/stores'
+import { useNavigation } from '@/hooks/navigation'
+import { BuyOrSell, BuildType, PriceMode } from '@/constants/order'
+
+const componentMap = new Map<string, unknown>([
+    // ['close', defineAsyncComponent(() => import('../close/Index.vue'))],
+    // ['delivery', defineAsyncComponent(() => import('../delivery/Index.vue'))]
+])
+
+const {router} = useNavigation()
+const userStore = useUserStore()
+const dataList = shallowRef<Model.TradePositionRsp[]>([])
+const selectedRow = shallowRef<Model.TradePositionRsp>()
+const error = shallowRef(false)
+const pullRefreshRef = shallowRef()
+
+const { componentRef, componentId, openComponent, closeComponent } = useComponent(() => {
+    pullRefreshRef.value?.refresh()
+})
+
+const { loading, pageIndex, pageCount, run } = useRequest(queryTradePosition, {
+    params: {
+        pagesize: 20,
+        tradeMode: '10'
+    },
+    onSuccess: (res) => {
+        if (pageIndex.value === 1) {
+            dataList.value = []
+        }
+        dataList.value.push(...res.data)
+    },
+    onError: () => {
+        error.value = 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 } })
+}
+
+
+</script>

+ 134 - 20
src/packages/mobile/views/pricing/detail/Index.vue

@@ -1,5 +1,5 @@
 <template>
-    <app-view>
+    <app-view class="pricing-detail">
         <template #header>
             <app-navbar title="点价详情" />
         </template>
@@ -16,12 +16,12 @@
                     <tr>
                         <td style="width: calc(`100% / 3`);">
                             <span>幅度</span>
-                            <span :class="quote.lastColor" style="margin-left: .2rem;">{{ handleNumberValue(quote.amplitude)
+                            <span :class="quote.lastColor" style="margin-left: .2rem;">{{ parsePercent(quote.amplitude)
                             }}</span>
                         </td>
                         <td style="width: calc(`100% / 3`);">
                             <span>涨跌</span>
-                            <span :class="quote.lastColor" style="margin-left: .2rem;">{{ handleNumberValue(quote.change)
+                            <span :class="quote.lastColor" style="margin-left: .2rem;">{{ parsePercent(quote.change)
                             }}</span>
                         </td>
                         <td style="width: calc(`100% / 3`);" rowspan="3">
@@ -63,10 +63,10 @@
                 </table>
             </div>
             <!-- 下单 -->
-            <Form ref="formRef" style="padding: 0.2rem; background-color: white;" @submit="onSubmit">
+            <Form class="pricing-detail__form" ref="formRef" style="padding: 0.2rem; background-color: white;" @submit="onSubmit">
                 <Field label="方向">
                     <template #input>
-                        <RadioGroup v-model="formData.BuyOrSell" direction="horizontal">
+                        <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>
@@ -74,7 +74,7 @@
                 </Field>
                 <Field label="类型">
                     <template #input>
-                        <RadioGroup v-model="formData.BuildType" direction="horizontal">
+                        <RadioGroup v-model="formData.BuildType" direction="horizontal" @click="onBuildTypeChanged">
                             <Radio :name="BuildType.Open">{{ getBuildTypeName(BuildType.Open) }}</Radio>
                             <Radio :name="BuildType.Close">{{ getBuildTypeName(BuildType.Close) }}</Radio>
                         </RadioGroup>
@@ -82,16 +82,16 @@
                 </Field>
                 <Field label="价格方式">
                     <template #input>
-                        <RadioGroup v-model="formData.PriceMode" direction="horizontal">
+                        <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="挂牌数量">
+                <Field name="OrderQty" label="数量">
                     <template #input>
-                        <Stepper v-model="formData.OrderQty" theme="round" button-size="22" :auto-fixed="false" integer />
+                        <Stepper v-model="formData.OrderQty" :rules="formRules.OrderQty"  theme="round" button-size="22" :auto-fixed="false" integer />
                     </template>
                 </Field>
                 <!-- 市价 -->
@@ -106,7 +106,27 @@
                     </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" shape="square">止损</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" shape="square">止盈</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>
             </Form>
         </div>
         <template #footer>
@@ -118,28 +138,72 @@
 <script lang="ts" setup>
 import { useFuturesStore } from '@/stores'
 import { useNavigation } from '@/hooks/navigation'
-import { handleNumberValue } from '@/filters'
+import { parsePercent, handleNumberValue } from '@/filters'
 import quoteSocket from '@/services/websocket/quote'
 import { shallowRef, onMounted, onUnmounted, computed } from 'vue'
-import { Form, Field, Stepper, Button, FieldRule, FormInstance, Radio, RadioGroup } from 'vant'
+import { Form, Field, Stepper, Button, FieldRule, FormInstance, Radio, RadioGroup, Checkbox } from 'vant'
 import { useOrder } from '@/business/trade'
 import { BuyOrSell, getBuyOrSellList, BuildType, getBuildTypeName, getPricemode2List, PriceMode } from '@/constants/order'
 import { fullloading, dialog } from '@/utils/vant'
+import { useRequest } from '@/hooks/request'
+import { queryTradePosition } from '@/services/api/order'
 
+const { router } = useNavigation()
 const futuresStore = useFuturesStore()
 const { getQueryString, getQueryStringToNumber } = useNavigation()
-
-const goodscode = getQueryString('goodscode')
-const buyOrSell = getQueryStringToNumber('buyOrSell')
-const buildType = getQueryStringToNumber('buildType')
-const quote = futuresStore.getQuoteInfo(goodscode)
-const subscribe = quoteSocket.addSubscribe([goodscode])
 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 goodscode = getQueryString('goodscode')
+const buyOrSell = getQueryStringToNumber('buyOrSell')
+const buildType = getQueryStringToNumber('buildType')
+const quote = futuresStore.getQuoteInfo(goodscode)
+const subscribe = quoteSocket.addSubscribe([goodscode])
+const sl = shallowRef(false) // 止损
+const sp = shallowRef(false) // 止盈
+const position = shallowRef<Model.TradePositionRsp[]>([]) // 持仓汇总
+
+// 表单验证规则
+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()
@@ -154,20 +218,70 @@ onUnmounted(() => {
     subscribe.stop()
 })
 
+// 获取当前商品持仓汇总
+useRequest(queryTradePosition, {
+    params: {
+        tradeMode: '10'
+    },
+    onSuccess: (res) => {
+        position.value = res.data.filter(item => item.goodscode === 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 onBuildTypeChanged = () => {
+    if (formData.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
+        }
+    }
+}
+
 // 委托下单
 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('委托成功。')
+            dialog('委托成功。').then(() => {
+                router.back()
+            })
         }).catch((err) => {
             hideLoading(err, 'fail')
         })
     })
 }
 
-</script>
+</script>
+
+<style lang="less">
+@import './index.less';
+</style>

+ 13 - 0
src/packages/mobile/views/pricing/detail/index.less

@@ -0,0 +1,13 @@
+.pricing-detail {
+    &__form {
+        .van-stepper {
+            display: flex;
+            align-items: center;
+            width: 100%;
+
+            &__input {
+                flex: 1;
+            }
+        }
+    }
+}

+ 18 - 1
src/services/api/trade/index.ts

@@ -2,7 +2,7 @@ import { v4 } from 'uuid'
 import http from '@/services/http'
 import { RequestConfig } from '@/services/http/types'
 import { ClientType } from '@/constants/client'
-import { useLoginStore, useAccountStore } from '@/stores'
+import { useLoginStore, useAccountStore,useUserStore } from '@/stores'
 
 const loginStore = useLoginStore()
 const accountStore = useAccountStore()
@@ -251,4 +251,21 @@ export function holdAppendDeposit(config: RequestConfig<Partial<Proto.HoldAppend
         responseCode: 'HoldAppendDepositRsp',
         marketId: 49201
     })
+}
+
+/**
+ * 线下交收申请请求
+ */
+export function offlineDelivery(config: RequestConfig<Partial<Proto.OfflineDeliveryReq>>) {
+    return http.mqRequest<Proto.OfflineDeliveryRsp>({
+        data: {
+            AccountID: accountStore.accountId,
+            ClientTicket: v4(),
+            UserID: loginStore.userId,
+            ...config.data
+        },
+        requestCode: 'OfflineDeliveryReq',
+        responseCode: 'OfflineDeliveryRsp',
+        marketId: 49201
+    })
 }

+ 23 - 0
src/types/proto/trade.d.ts

@@ -577,5 +577,28 @@ declare global {
             AccountID: number; // 交易账号,必填
             ClientSerialNo: string; // 客户端流水号
         }
+
+        // 线下交收申请请求
+        interface OfflineDeliveryReq {
+            Header?: IMessageHead; // 消息头
+			AccountID?: number; // 账号ID
+			UserID?: number; // 用户ID
+			GoodsCode?: string; // 商品代码
+            GoodsID?: number; // 商品ID
+			BuyOrSell?: number; // 方向
+			DeliveryLot?: number; // 交收手数
+			DeliveryInfo?: string; // 交收信息
+			ClientTicket?: string; // 客户端流水号
+        }
+        // 线下交收申请请求
+        interface OfflineDeliveryRsp {
+            Header?: IMessageHead; // 消息头
+            RetCode: number; // 返回码
+            RetDesc: string; // 描述信息
+            AccountID: number; // 交易账号,必填
+            UserID: number; // 用户ID
+            ApplyID: number; // 申请ID
+            ClientTicket: string; // 客户端流水号
+        }
     }
 }