Prechádzať zdrojové kódy

挂牌点价界面优化

Handy_Cao 1 rok pred
rodič
commit
e8c8994a25

+ 3 - 2
public/locales/en-US.json

@@ -103,7 +103,7 @@
         "goodsname": "Name/Code",
         "refgoodsname": "RefGoodsName",
         "averageprice": "Average",
-        "last": "last",
+        "last": "Last",
         "rise": "Up Down",
         "change": "Change",
         "opened": "Opened",
@@ -258,7 +258,8 @@
             "deposit": "Deposit",
             "avaiableMoney": "Avaiable Funds",
             "tips1": "Please enter the order qty",
-            "tips2": "Please enter the order price"
+            "tips2": "Please enter the order price",
+            "tips3": "Please enter the market maxsub"
         },
         "spot": {
             "title": "Listing Detail",

+ 2 - 1
public/locales/zh-CN.json

@@ -259,7 +259,8 @@
             "deposit": "预扣保证金",
             "avaiableMoney": "可用资金",
             "tips1": "请输入数量",
-            "tips2": "请输入价格"
+            "tips2": "请输入价格",
+            "tips3": "请输入点差"
         },
         "spot": {
             "title": "挂牌详情",

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

@@ -58,9 +58,9 @@ const pageRoutes: RouteRecordRaw[] = [
                 }
             },
             {
-                path: 'detail',
-                name: 'pricing-detail',
-                component: () => import('@mobile/views/pricing/detail/Index.vue'),
+                path: 'trade',
+                name: 'pricing-trade',
+                component: () => import('@mobile/views/pricing/trade/Index.vue'),
             }
         ]
     },

+ 0 - 74
src/packages/mobile/views/pricing/detail/Index.vue

@@ -1,74 +0,0 @@
-<template>
-    <app-view class="goods-detail g-form">
-        <template #header>
-            <app-navbar :title="quote ? quote.goodscode + '/' + quote.goodsname : $t('quote.listinghall')" />
-        </template>
-        <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>
-            <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>
-
-<script lang="ts" setup>
-import { useFuturesStore } from '@/stores'
-import { useNavigation } from '@mobile/router/navigation'
-import { shallowRef, defineAsyncComponent } from 'vue'
-import { Button } from 'vant'
-import { BuyOrSell } from '@/constants/order'
-import { useComponent } from '@/hooks/component'
-
-defineProps({
-    showMore: {
-        type: Boolean,
-        default: true
-    }
-})
-
-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'))
-
-const componentMap = new Map<string, unknown>([
-    ['trade', defineAsyncComponent(() => import('./components/trade/Index.vue'))],
-])
-
-const futuresStore = useFuturesStore()
-const { router, getQueryString, getQueryStringToNumber } = useNavigation()
-
-const goodsCode = getQueryString('goodscode') ?? ''
-const goodsid = getQueryStringToNumber('goodsid')
-const quote = futuresStore.getGoodsQuote(goodsCode)
-const buyOrSell = shallowRef<BuyOrSell>()
-
-const { componentRef, componentId, openComponent, closeComponent } = useComponent()
-
-const onListing = (type: BuyOrSell) => {
-    buyOrSell.value = type
-    openComponent('trade')
-}
-
-const onMoreClick = (buyorsell: BuyOrSell) => {
-    router.push({
-        name: 'goods-trade',
-        query: {
-            goodsid,
-            buyorsell,
-        }
-    })
-}
-
-</script>
-
-<style lang="less">
-@import './index.less';
-</style>

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

@@ -1,340 +0,0 @@
-<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}` : $t('quote.pricing.title')" @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="$t('quote.pricing.buyorsell')">
-                        <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="$t('quote.pricing.pricemode')">
-                        <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="$t('quote.pricing.orderqty')">
-                        <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="$t('quote.pricing.marketmaxsub')" 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="$t('quote.pricing.price')" v-if="formData.PriceMode === PriceMode.Limit">
-                        <template #input>
-                            <Stepper v-model="formData.OrderPrice" theme="round" button-size="22" :min="0"
-                             :auto-fixed="false" :decimal-length="decimalplace" :step="decimalvalue" />
-                        </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" :decimal-length="decimalplace" :step="decimalvalue" />
-                        </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" :decimal-length="decimalplace" :step="decimalvalue" />
-                        </template>
-                    </Field> -->
-                    <template v-if="formData.BuyOrSell === BuyOrSell.Buy || quote?.tradeproperty !== 2">
-                        <Field :label="$t('quote.pricing.enableQty')">
-                            <template #input>
-                                <span>{{ total.enableQty }}</span>
-                            </template>
-                        </Field>
-                        <Field :label="$t('quote.pricing.deposit')">
-                            <template #input>
-                                <span>{{ total.deposit.toFixed(2) }}</span>
-                            </template>
-                        </Field>
-                    </template>
-                    <Field :label="$t('quote.pricing.avaiableMoney')">
-                        <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">{{ $t('quote.orderbuy') }}</Button>
-                        <Button color="#199e00" block square
-                            :disabled="!formData.OrderQty || !sellQty || (formData.OrderQty > sellQty)"
-                            @click="onBeforeSubmit(BuildType.Close)" v-if="!isTrademode16">
-                            <span>{{ $t('quote.transferbuy') }}</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">{{ $t('quote.ordersell') }}</Button>
-                        <Button color="#199e00" block square
-                            :disabled="!formData.OrderQty || !buyQty || (formData.OrderQty > buyQty)"
-                            @click="onBeforeSubmit(BuildType.Close)">
-                            <span>{{ $t('quote.transfersell') }}</span>
-                            <span v-if="buyQty">(≤{{ buyQty }})</span>
-                        </Button>
-                    </template>
-                </div>
-            </template>
-        </app-view>
-    </app-modal>
-</template>
-
-<script lang="ts" setup>
-import { useFuturesStore, useAccountStore, 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 } from 'vant'
-import { useOrder } from '@/business/trade'
-import { BuyOrSell, getBuyOrSellList, BuildType, getPricemode2List, PriceMode } from '@/constants/order'
-import { fullloading, dialog } from '@/utils/vant'
-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 { global: { t } } = i18n
-const { router } = useNavigation()
-const futuresStore = useFuturesStore()
-const accountStore = useAccountStore()
-const positionStore = usePositionStore()
-const formRef = shallowRef<FormInstance>()
-const { formData, formSubmit } = useOrder()
-
-const quote = futuresStore.getGoodsQuote(props.goodsCode)
-// 小数位以及步进值
-const { decimalplace = 0.0 } = quote.value ?? {}
-const quoteminunit = quote.value?.quoteminunit ?? 1.0
-const decimalvalue = Math.pow(10.0, -decimalplace)*(quoteminunit == 0 ? 1 : quoteminunit)
-
-// 计算市价
-const marketPrice = computed(() => {
-    const { ask = 0, bid = 0 } = quote.value ?? {}
-    return formData.BuyOrSell === BuyOrSell.Buy ? ask : bid
-})
-// 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: t('quote.pricing.tips1'),
-        validator: () => {
-            return !!formData.OrderQty
-        }
-    }],
-    // 限价
-    OrderPrice: [{
-        message: t('quote.pricing.tips2'),
-        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
-    //     }
-    // }],
-}
-
-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
-        }
-    }
-    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(t('common.submitsuccess')).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
-}
-
-onMounted(() => {
-    formData.BuyOrSell = props.buyOrSell
-    formData.BuildType = BuildType.Open
-    formData.PriceMode = PriceMode.Market
-    formData.MarketMaxSub = 100.0
-    /// 默认价格和数量
-    const { last = 0, presettle = 0 } = quote.value ?? {}
-    formData.OrderPrice = last || presettle
-    formData.OrderQty = qtyStep.value
-})
-
-// 暴露组件属性给父组件调用
-defineExpose({
-    closed,
-})
-
-</script>

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

@@ -124,7 +124,7 @@ const columns: Model.TableColumn[] = [
 
 const rowClick = (row: Model.GoodsQuote) => {
     router.push({
-        name: 'pricing-detail',
+        name: 'pricing-trade',
         query: {
             goodsid: row.goodsid,
             goodscode: row.goodscode,

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

@@ -0,0 +1,346 @@
+<template>
+    <app-view class="pricing-trade">
+        <template #header>
+            <app-navbar :title="quote ? `${quote.goodscode}/${quote.goodsname}` : $t('quote.pricing.title')" >
+                <template #right>
+                    <Icon name="bars" size="20px" @click="openComponent('detail')" />
+                </template>
+            </app-navbar>
+        </template>
+        <div class="pricing-trade__form" v-if="quote">
+            <div class="form-price">
+                <dl>
+                    <dt>买价</dt>
+                    <dd :class="quote.bidColor">{{ handleNumberValue(formatDecimal(quote.ask, quote.decimalplace)) }}
+                    </dd>
+                </dl>
+                <dl>
+                    <dt>卖价</dt>
+                    <dd :class="quote.askColor">{{ handleNumberValue(formatDecimal(quote.bid, quote.decimalplace)) }}
+                    </dd>
+                </dl>
+            </div>
+        </div>
+        <!-- 下单 -->
+        <Form ref="formRef" class="g-form__container" @submit="onSubmit">
+            <CellGroup inset>
+                <Field :label="$t('quote.pricing.buyorsell')">
+                    <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="$t('quote.pricing.pricemode')">
+                    <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="$t('quote.pricing.orderqty')">
+                    <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" :rules="formRules.MarketMaxSub" :label="$t('quote.pricing.marketmaxsub')" 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="$t('quote.pricing.price')" v-if="formData.PriceMode === PriceMode.Limit">
+                    <template #input>
+                        <Stepper v-model="formData.OrderPrice" theme="round" button-size="22" :min="0"
+                            :auto-fixed="false" :decimal-length="decimalplace" :step="decimalvalue" />
+                    </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" :decimal-length="decimalplace" :step="decimalvalue" />
+                    </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" :decimal-length="decimalplace" :step="decimalvalue" />
+                    </template>
+                </Field> -->
+                <!-- <template v-if="formData.BuyOrSell === BuyOrSell.Buy || quote?.tradeproperty !== 2">
+                    <Field :label="$t('quote.pricing.enableQty')">
+                        <template #input>
+                            <span>{{ total.enableQty }}</span>
+                        </template>
+                    </Field>
+                    <Field :label="$t('quote.pricing.deposit')">
+                        <template #input>
+                            <span>{{ total.deposit.toFixed(2) }}</span>
+                        </template>
+                    </Field>
+                </template>
+                <Field :label="$t('quote.pricing.avaiableMoney')">
+                    <template #input>
+                        <span>{{ accountStore.currentAccount.avaiableMoney?.toFixed(2) }}</span>
+                    </template>
+                </Field> -->
+            </CellGroup>
+        </Form> 
+        <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">{{ $t('quote.orderbuy') }}</Button>
+                <Button color="#199e00" block square
+                    :disabled="!formData.OrderQty || !sellQty || (formData.OrderQty > sellQty)"
+                    @click="onBeforeSubmit(BuildType.Close)" v-if="!isTrademode16">
+                    <span>{{ $t('quote.transferbuy') }}</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">{{ $t('quote.ordersell') }}</Button>
+                <Button color="#199e00" block square
+                    :disabled="!formData.OrderQty || !buyQty || (formData.OrderQty > buyQty)"
+                    @click="onBeforeSubmit(BuildType.Close)">
+                    <span>{{ $t('quote.transfersell') }}</span>
+                    <span v-if="buyQty">(≤{{ buyQty }})</span>
+                </Button>
+            </template>
+        </div>
+        <template #footer>
+            <component ref="componentRef" :is="componentMap.get(componentId)" v-bind="{ goodsCode, goodsid}"
+            @closed="closeComponent" v-if="componentId" />
+        </template> 
+    </app-view>
+</template>
+
+<script lang="ts" setup>
+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 { useOrder } from '@/business/trade'
+import { BuyOrSell, getBuyOrSellList, BuildType, getPricemode2List, PriceMode } from '@/constants/order'
+import { useComponent } from '@/hooks/component'
+import { fullloading, dialog } from '@/utils/vant'
+import Stepper from '@mobile/components/base/stepper/index.vue'
+import { formatDecimal, handleNumberValue } from '@/filters'
+
+const { router, getQueryString, getQueryStringToNumber } = useNavigation()
+const { global: { t } } = i18n
+
+const goodsCode = getQueryString('goodscode') ?? ''
+const goodsid = getQueryStringToNumber('goodsid')
+const futuresStore = useFuturesStore()
+const quote = futuresStore.getGoodsQuote(goodsCode)
+// 小数位以及步进值
+const { decimalplace = 0.0 } = quote.value ?? {}
+const quoteminunit = quote.value?.quoteminunit ?? 1.0
+const decimalvalue = Math.pow(10.0, -decimalplace)*(quoteminunit == 0 ? 1 : quoteminunit)
+const isTrademode16 = computed(() => quote.value?.trademode === 16)
+
+const { formData, formSubmit } = useOrder()
+const formRef = shallowRef<FormInstance>()
+const qtyStepList = [1, 5, 10, 20, 30, 50] // 数量步长列表
+const qtyStep = shallowRef(qtyStepList[0]) // 数量步长
+
+// const accountStore = useAccountStore()
+const positionStore = usePositionStore()
+// 持仓汇总
+const position = shallowRef<Model.TradePositionRsp[]>([]) 
+
+const { componentRef, componentId, openComponent, closeComponent } = useComponent()
+const componentMap = new Map<string, unknown>([
+    ['detail', defineAsyncComponent(() => import('./components/detail/Index.vue'))],
+])
+
+// // const sl = shallowRef(false) // 止损
+// // const sp = shallowRef(false) // 止盈
+
+// 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 marketPrice = computed(() => {
+    const { ask = 0, bid = 0 } = quote.value ?? {}
+    return formData.BuyOrSell === BuyOrSell.Buy ? ask : bid
+})
+
+// 买方向持仓数量
+const buyQty = computed(() => positionStore.getOrderQty(BuyOrSell.Buy, goodsCode))
+// 卖方向持仓数量
+const sellQty = computed(() => positionStore.getOrderQty(BuyOrSell.Sell, goodsCode))
+
+// 表单验证规则
+const formRules: { [key in keyof Proto.OrderReq]?: FieldRule[] } = {
+    OrderQty: [{
+        message: t('quote.pricing.tips1'),
+        validator: () => {
+            return !!formData.OrderQty
+        }
+    }],
+    // 限价
+    MarketMaxSub: [{
+        message: t('quote.pricing.tips3'),
+        validator: () => {
+            if (formData.PriceMode === PriceMode.Market) {
+                return !!formData.MarketMaxSub
+            }
+            return true
+        }
+    }],
+    // 限价
+    OrderPrice: [{
+        message: t('quote.pricing.tips2'),
+        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
+//     //     }
+//     // }],
+}
+
+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
+        }
+    }
+    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(t('common.submitsuccess')).then(() => {
+                router.back()
+            })
+        }).catch((err) => {
+            hideLoading(err, 'fail')
+        })
+    })
+}
+
+const onRadioChange = (value: number) => {
+    formData.OrderQty = value
+}
+
+ onMounted(() => {
+    formData.BuyOrSell = BuyOrSell.Buy
+    formData.BuildType = BuildType.Open
+    formData.PriceMode = PriceMode.Market
+    formData.MarketMaxSub = 100.0
+    /// 默认价格和数量
+    const { last = 0, presettle = 0 } = quote.value ?? {}
+    formData.OrderPrice = last || presettle
+    formData.OrderQty = qtyStep.value
+ })
+
+</script>
+
+<style lang="less">
+@import './index.less';
+</style>

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

@@ -0,0 +1,38 @@
+<template>
+    <app-modal direction="right-top" height="100%" width="100%" v-model:show="showModal" :refresh="refresh">
+        <app-view class="pricing-detail g-form">
+            <template #header>
+                <app-navbar :title="quote ? quote.goodscode + '/' + quote.goodsname : $t('quote.listinghall')" />
+            </template>
+            <component :is="Price" v-bind="{ goodsCode }" />
+            <component :is="Chart" v-bind="{ goodsCode }" />
+        </app-view>
+    </app-modal>
+</template>
+
+<script lang="ts" setup>
+import { useFuturesStore } from '@/stores'
+import { shallowRef, defineAsyncComponent } from 'vue'
+import AppModal from '@/components/base/modal/index.vue'
+
+const props = defineProps({
+    goodsCode: {
+        type: String,
+        required: true
+    }
+})
+
+// 是否刷新父组件数据
+const refresh = shallowRef(true) 
+const showModal = shallowRef(true)
+const futuresStore = useFuturesStore()
+const quote = futuresStore.getGoodsQuote(props.goodsCode)
+
+const Price = defineAsyncComponent(() => import('@mobile/components/modules/quote/price/index.vue'))
+const Chart = defineAsyncComponent(() => import('@mobile/components/modules/hqchart/index.vue'))
+
+</script>
+
+<style lang="less">
+@import './index.less';
+</style>

+ 0 - 0
src/packages/mobile/views/pricing/detail/index.less → src/packages/mobile/views/pricing/trade/components/detail/index.less


+ 90 - 0
src/packages/mobile/views/pricing/trade/index.less

@@ -0,0 +1,90 @@
+.pricing-trade {
+    &__form {
+        background-color: #fff;
+        border-radius: 8px;
+        margin: 5px 15px;
+        margin-bottom: 0;
+
+        .form-price {
+            display: flex;
+            padding: 16px 0;
+
+            dl {
+                flex: 1;
+                text-align: center;
+
+                dt {
+                    margin-bottom: 5px;
+                }
+
+                dd {
+                    font-size: 28px;
+                    font-weight: bold;
+                }
+            }
+        }
+
+        // .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;
+    //                 }
+    //             }
+    //         }
+    //     }
+    // }
+}