Handy_Cao 1 년 전
부모
커밋
56b5d09557

+ 3 - 0
src/packages/mobile/router/section.ts

@@ -53,6 +53,9 @@ const pageRoutes: RouteRecordRaw[] = [
                 path: 'list',
                 name: 'pricing-list',
                 component: () => import('@mobile/views/pricing/list/Index.vue'),
+                props: {
+                    showBackButton: true
+                }
             },
             {
                 path: 'detail',

+ 6 - 6
src/packages/mobile/views/account/authresult/Index.vue

@@ -11,10 +11,10 @@
                 <Cell title="证件正面照片">
                     <Image fit="contain" :src="getFileUrl(userInfo.cardfrontphotourl)" width="100" height="100" />
                 </Cell>
-                <Cell title="证件反面照片" v-if="showCardBackPhoto === 1">
+                <Cell title="证件反面照片" v-if="showCardBackPhoto === '1'">
                     <Image fit="contain" :src="getFileUrl(userInfo.cardbackphotourl)" width="100" height="100" />
                 </Cell>
-                <Cell title="手持证件照" v-if="showHalfBodyPhoto === 1">
+                <Cell title="手持证件照" v-if="showHalfBodyPhoto === '1'">
                     <Image fit="contain" :src="getFileUrl(userInfo.halfbodyphotourl)" width="100" height="100" />
                 </Cell>
                 <Cell title="实名状态" :value="getAuthStatusName(2)" />
@@ -35,8 +35,8 @@ import { getFileUrl } from '@/filters'
 import { getWskhOpenAccountConfigs } from '@/services/api/account'
 
 const userInfo = shallowRef<Model.UserInfo>()
-const showHalfBodyPhoto = shallowRef(0)
-const showCardBackPhoto = shallowRef(0)
+const showHalfBodyPhoto = shallowRef('0')
+const showCardBackPhoto = shallowRef('0')
 
 /// 查询托管银行信息
 useRequest(queryWrDraftUserInfo, {
@@ -53,8 +53,8 @@ useRequest(getWskhOpenAccountConfigs, {
     },
     onSuccess: (res) => {
         /// 是否显示半身照和 证件背面照
-        showHalfBodyPhoto.value = res.data.filter(e => { e.configid === 54})[0].configid ?? 0
-        showCardBackPhoto.value = res.data.filter(e => { e.configid === 53})[0].configid ?? 0
+        showHalfBodyPhoto.value = res.data.filter(e => { e.configid === 54})[0].configvalue ?? '0'
+        showCardBackPhoto.value = res.data.filter(e => { e.configid === 53})[0].configvalue ?? '0'
     }
 })
 

+ 6 - 6
src/packages/mobile/views/account/certification/Index.vue

@@ -20,12 +20,12 @@
                         <app-uploader @success="b_afterRead" />
                     </template>
                 </Field>
-                <Field name="cardbackphotourl" v-if="showCardBackPhoto === 1" label="证件反面照片" :rules="formRules.cardbackphotourl">
+                <Field name="cardbackphotourl" v-if="showCardBackPhoto === '1'" label="证件反面照片" :rules="formRules.cardbackphotourl">
                     <template #input>
                         <app-uploader @success="f_afterRead" />
                     </template>
                 </Field>
-                <Field name="halfbodyphotourl" v-if="showHalfBodyPhoto === 1" label="手持证件照" :rules="formRules.halfbodyphotourl">
+                <Field name="halfbodyphotourl" v-if="showHalfBodyPhoto === '1'" label="手持证件照" :rules="formRules.halfbodyphotourl">
                     <template #input>
                         <app-uploader @success="h_afterRead" />
                     </template>
@@ -60,8 +60,8 @@ const formRef = shallowRef<FormInstance>()
 const { formData, formSubmit, modifyremark } = addAuthReq()
 const { router } = useNavigation()
 
-const showHalfBodyPhoto = shallowRef(0)
-const showCardBackPhoto = shallowRef(0)
+const showHalfBodyPhoto = shallowRef('0')
+const showCardBackPhoto = shallowRef('0')
 
 // 获取网上开户配置
 useRequest(getWskhOpenAccountConfigs, {
@@ -70,8 +70,8 @@ useRequest(getWskhOpenAccountConfigs, {
     },
     onSuccess: (res) => {
         /// 是否显示半身照和 证件背面照
-        showHalfBodyPhoto.value = res.data.filter(e => { e.configid === 54})[0].configid ?? 0
-        showCardBackPhoto.value = res.data.filter(e => { e.configid === 53})[0].configid ?? 0
+        showHalfBodyPhoto.value = res.data.filter(e => { e.configid === 54})[0].configvalue ?? '0'
+        showCardBackPhoto.value = res.data.filter(e => { e.configid === 53})[0].configvalue ?? '0'
     }
 })
 

+ 45 - 263
src/packages/mobile/views/pricing/detail/Index.vue

@@ -1,144 +1,19 @@
 <template>
-    <app-view class="pricing-detail">
+    <app-view class="goods-detail g-form">
         <template #header>
-            <app-navbar title="点价详情" />
+            <app-navbar :title="quote ? quote.goodscode + '/' + quote.goodsname : '挂牌大厅'" />
         </template>
-        <div v-if="quote">
-            <!-- 商品代码、现价 -->
-            <div style="padding: 15px;">
-                <span>商品代码/名称</span>
-                <span style="margin-left: 15px; color: blue;">{{ quote.goodscode }}/{{ quote.goodsname }}</span>
-                <span :class="quote.lastColor" style="margin-left: 25px;">{{ handleNumberValue(quote.last) }}</span>
-            </div>
-            <!-- 盘面 -->
-            <div style="padding: 15px; background-color: white;">
-                <table cellspacing="10" cellpadding="0" style="width: 100%;">
-                    <tr>
-                        <td style="width: calc(`100% / 3`);">
-                            <span>幅度</span>
-                            <span :class="quote.lastColor" style="margin-left: 10px;">{{ parsePercent(quote.amplitude)
-                            }}</span>
-                        </td>
-                        <td style="width: calc(`100% / 3`);">
-                            <span>涨跌</span>
-                            <span :class="quote.lastColor" style="margin-left: 10px;">{{ parsePercent(quote.change)
-                            }}</span>
-                        </td>
-                        <td style="width: calc(`100% / 3`);" rowspan="3">
-                            <div>
-                                <span style="background-color: green; color: white;">卖</span>
-                                <span :class="quote.bidColor" style="margin-left: 10px;">{{ handleNumberValue(quote.bid)
-                                }}</span>
-                            </div>
-                            <div style="margin-top: 10px;">
-                                <span style="background-color: red; color: white;">买</span>
-                                <span :class="quote.askColor" style="margin-left: 10px;">{{ handleNumberValue(quote.ask)
-                                }}</span>
-                            </div>
-                        </td>
-                    </tr>
-                    <tr>
-                        <td style="width: calc(`100% / 3`);">
-                            <span>今开</span>
-                            <span :class="quote.openedColor" style="margin-left: 10px;">{{ handleNumberValue(quote.opened)
-                            }}</span>
-                        </td>
-                        <td style="width: calc(`100% / 3`);">
-                            <span>昨结</span>
-                            <span style="margin-left: 10px;">{{ handleNumberValue(quote.presettle) }}</span>
-                        </td>
-                    </tr>
-                    <tr>
-                        <td style="width: calc(`100% / 3`);">
-                            <span>最高</span>
-                            <span :class="quote.highestColor" style="margin-left: 10px;">{{
-                                handleNumberValue(quote.highest) }}</span>
-                        </td>
-                        <td style="width: calc(`100% / 3`);">
-                            <span>最低</span>
-                            <span :class="quote.lowestColor" style="margin-left: 10px;">{{ handleNumberValue(quote.lowest)
-                            }}</span>
-                        </td>
-                    </tr>
-                </table>
-            </div>
-            <!-- 下单 -->
-            <Form class="pricing-detail__form" ref="formRef" style="padding: 010px; background-color: white;"
-                @submit="onSubmit">
-                <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.BuildType" direction="horizontal" @click="onBuildTypeChanged">
-                            <Radio :name="BuildType.Open">{{ getBuildTypeName(BuildType.Open) }}</Radio>
-                            <Radio :name="BuildType.Close">{{ getBuildTypeName(BuildType.Close) }}</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>
-                        <Stepper v-model="formData.OrderQty" :rules="formRules.OrderQty" theme="round" button-size="22"
-                            :auto-fixed="false" integer />
-                    </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" 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>
+        <component :is="Price" v-bind="{ goodsCode }" />
+        <component :is="Chart" v-bind="{ goodsCode }" />
+        <component :is="Forex" v-bind="{ goodsCode }" :show-more="showMore" @more-click="onMoreClick" />
+        <component :is="Tik" v-bind="{ goodsCode }" />
         <template #footer>
-            <Button type="danger" block round @click="formRef?.submit">确定</Button>
+            <div class="g-form__footer">
+                <Button block type="danger" square @click="onListing(BuyOrSell.Buy)">{{ $t('quote.buy') }}</Button>
+                <Button block color="#199e00" square @click="onListing(BuyOrSell.Sell)">{{ $t('quote.selll') }}</Button>
+            </div>
+            <component ref="componentRef" :is="componentMap.get(componentId)" v-bind="{ goodsCode, buyOrSell }"
+                @closed="closeComponent" v-if="componentId" />
         </template>
     </app-view>
 </template>
@@ -146,142 +21,49 @@
 <script lang="ts" setup>
 import { useFuturesStore } from '@/stores'
 import { useNavigation } from '@mobile/router/navigation'
-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, 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'
+import { shallowRef, defineAsyncComponent } from 'vue'
+import { Button } from 'vant'
+import { BuyOrSell } from '@/constants/order'
+import { useComponent } from '@/hooks/component'
 
-const { router } = useNavigation()
-const futuresStore = useFuturesStore()
-const { getQueryString, getQueryStringToNumber } = useNavigation()
-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
+defineProps({
+    showMore: {
+        type: Boolean,
+        default: true
+    }
 })
-const goodscode = getQueryString('goodscode') ?? ''
-const buyOrSell = getQueryStringToNumber('buyOrSell')
-const buildType = getQueryStringToNumber('buildType')
-const quote = futuresStore.getGoodsQuote(goodscode)
-const subscribe = quoteSocket.createSubscribe()
-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(goodscode)
-    formData.BuyOrSell = buyOrSell
-    formData.BuildType = buildType
-    formData.PriceMode = PriceMode.Market
-    formData.MarketMaxSub = 100.0
-})
+const Price = defineAsyncComponent(() => import('@mobile/components/modules/quote/price/index.vue'))
+const Chart = defineAsyncComponent(() => import('@mobile/components/modules/hqchart/index.vue'))
+const Forex = defineAsyncComponent(() => import('@mobile/components/modules/quote/forex/index.vue'))
+const Tik = defineAsyncComponent(() => import('@mobile/components/modules/quote/tik/index.vue'))
 
-onUnmounted(() => subscribe.stop())
+const componentMap = new Map<string, unknown>([
+    ['trade', defineAsyncComponent(() => import('./components/trade/Index.vue'))],
+])
 
-// 获取当前商品持仓汇总
-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 futuresStore = useFuturesStore()
+const { router, getQueryString, getQueryStringToNumber } = useNavigation()
 
-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 goodsCode = getQueryString('goodscode') ?? ''
+const goodsid = getQueryStringToNumber('goodsid')
+const quote = futuresStore.getGoodsQuote(goodsCode)
+const buyOrSell = shallowRef<BuyOrSell>()
 
-const onBuildTypeChanged = () => {
-    if (formData.BuildType === BuildType.Close) {
-        const { ask = 0, bid = 0 } = quote.value ?? {}
-        formData.OrderPrice = formData.BuyOrSell === BuyOrSell.Buy ? ask : bid
+const { componentRef, componentId, openComponent, closeComponent } = useComponent()
 
-        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 onListing = (type: BuyOrSell) => {
+    buyOrSell.value = type
+    openComponent('trade')
 }
 
-// 委托下单
-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 onMoreClick = (buyorsell: BuyOrSell) => {
+    router.push({
+        name: 'goods-trade',
+        query: {
+            goodsid,
+            buyorsell,
+        }
     })
 }
 

+ 363 - 0
src/packages/mobile/views/pricing/detail/components/trade/Index.vue

@@ -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>

+ 1 - 0
src/packages/mobile/views/pricing/list/Index.vue

@@ -125,6 +125,7 @@ const rowClick = (row: Model.GoodsQuote) => {
     router.push({
         name: 'pricing-detail',
         query: {
+            goodsid: row.goodsid,
             goodscode: row.goodscode,
             buyOrSell: BuyOrSell.Buy,
             buildType: BuildType.Open

+ 1 - 1
src/stores/modules/language.ts

@@ -6,7 +6,7 @@ import enUS from '@public/locales/en-US.json'
 const appLanguage = localData.getRef('appLanguage')
 
 export const i18n = createI18n({
-    locale: 'en-US',//appLanguage.value,
+    locale: appLanguage.value,
     messages: {
         'zh-CN': zhCN,
         'en-US': enUS