li.shaoyi 1 month ago
parent
commit
99af8e94c0

+ 2 - 2
oem/snhl/config/appconfig.json

@@ -1,8 +1,8 @@
 {
   "appId": "com.snhl.release",
   "appName": "三农互联",
-  "version": "1.0.6",
-  "versionCode": "100006",
+  "version": "1.0.7",
+  "versionCode": "100007",
   "apiUrl": "http://192.168.31.208:8080/cfg?key=test_208",
   "tradeChannel": "ws",
   "modules": [

+ 8 - 6
src/packages/digital/views/contract/goods/chart/index.less

@@ -1,18 +1,20 @@
 .spot-goods-chart {
     .quote-table {
-        padding: var(--van-padding-md);
+        padding: var(--van-padding-md) 0;
 
         .data-table {
             width: 100%;
 
             &__cell {
+                line-height: 1.5;
+
                 &--merged {
                     width: 50%;
                     text-align: center;
-                }
 
-                >span {
-                    display: block;
+                    span:not(:first-child) {
+                        margin-left: 10px;
+                    }
                 }
 
                 .text-small {
@@ -30,10 +32,10 @@
         }
 
         .tabs {
-            border-color: #171f2d;
+            border-color: #171f2d !important;
 
             &-item.is-active {
-                color: #fff;
+                color: var(--color-primary);
             }
         }
     }

+ 1 - 0
src/packages/digital/views/spot/components/account/index.vue

@@ -27,6 +27,7 @@
             <tfoot>
                 <tr>
                     <td colspan="2">
+                        <Button type="primary" size="small" v-if="!item.digitalaccountid">激活</Button>
                         <Button size="small" @click="navigateTo('wallet-deposit', item.digitalaccountid)">充值</Button>
                         <Button size="small" @click="navigateTo('wallet-withdraw', item.digitalaccountid)">提现</Button>
                     </td>

+ 10 - 3
src/packages/digital/views/spot/detail/index.vue

@@ -15,17 +15,23 @@
                     <tr>
                         <td colspan="2">
                             <span class="text-small">余额({{ accountItem.currencycode }})</span>
-                            <span>{{ accountItem.currentbalance }}</span>
+                            <span>
+                                {{ formatDecimal(accountItem.currentbalance, accountItem.currencydecimalplace) }}
+                            </span>
                         </td>
                     </tr>
                     <tr>
                         <td>
                             <span class="text-small">可用({{ accountItem.currencycode }})</span>
-                            <span>{{ spotAccountStore.getAvailableBalance(accountItem) }}</span>
+                            <span>
+                                {{ formatDecimal(spotAccountStore.getAvailableBalance(accountItem), accountItem.currencydecimalplace) }}
+                            </span>
                         </td>
                         <td>
                             <span class="text-small">冻结({{ accountItem.currencycode }})</span>
-                            <span>{{ accountItem.freezemargin }}</span>
+                            <span>
+                                {{ formatDecimal(accountItem.freezemargin, accountItem.currencydecimalplace) }}
+                            </span>
                         </td>
                     </tr>
                 </tbody>
@@ -68,6 +74,7 @@
 <script lang="ts" setup>
 import { shallowRef, computed } from 'vue'
 import { Cell, CellGroup, Tab, Tabs, Grid, GridItem, ActionSheet } from 'vant'
+import { formatDecimal } from '@/filters'
 import { useNavigation } from '@mobile/router/navigation'
 import { useFuturesStore } from '@/stores'
 import { useSpotAccountStore } from '../../wallet/components/spot/composables'

+ 9 - 6
src/packages/digital/views/spot/goods/chart/index.less

@@ -1,18 +1,21 @@
 .spot-goods-chart {
     .quote-table {
-        padding: var(--van-padding-md);
+        padding: var(--van-padding-md) 0;
 
         .data-table {
             width: 100%;
 
             &__cell {
+                font-size: 12px;
+                line-height: 1.5;
+
                 &--merged {
                     width: 50%;
                     text-align: center;
-                }
 
-                >span {
-                    display: block;
+                    span:not(:first-child) {
+                        margin-left: 10px;
+                    }
                 }
 
                 .text-small {
@@ -30,10 +33,10 @@
         }
 
         .tabs {
-            border-color: #171f2d;
+            border-color: #171f2d !important;
 
             &-item.is-active {
-                color: #fff;
+                color: var(--color-primary);
             }
         }
     }

+ 18 - 15
src/packages/digital/views/spot/goods/chart/index.vue

@@ -10,7 +10,7 @@
                         <tr class="data-table__row">
                             <td class="data-table__cell data-table__cell--merged" rowspan="2">
                                 <div :class="quote.lastColor">
-                                    {{ handleNumberValue(quote.last.toFixed(quote.decimalplace)) }}
+                                    <h2>{{ handleNumberValue(quote.last.toFixed(quote.decimalplace)) }}</h2>
                                 </div>
                                 <div>
                                     <span>{{ quote.rise.toFixed(quote.decimalplace) }}</span>
@@ -18,42 +18,44 @@
                                 </div>
                             </td>
                             <td class="data-table__cell">
-                                <span class="text-small">昨结</span>
-                                <span>
+                                <div class="text-small">昨结</div>
+                                <div>
                                     {{ handleNumberValue(quote.presettle.toFixed(quote.decimalplace)) }}
-                                </span>
+                                </div>
                             </td>
                             <td class="data-table__cell">
-                                <span class="text-small">最高</span>
-                                <span :class="quote.highestColor">
+                                <div class="text-small">最高</div>
+                                <div :class="quote.highestColor">
                                     {{ handleNumberValue(quote.highest.toFixed(quote.decimalplace)) }}
-                                </span>
+                                </div>
                             </td>
                         </tr>
                         <tr>
                             <td class="data-table__cell">
-                                <span class="text-small">开盘</span>
-                                <span :class="quote.openedColor">
+                                <div class="text-small">开盘</div>
+                                <div :class="quote.openedColor">
                                     {{ handleNumberValue(quote.opened.toFixed(quote.decimalplace)) }}
-                                </span>
+                                </div>
                             </td>
                             <td class="data-table__cell">
-                                <span class="text-small">最低</span>
-                                <span :class="quote.lowestColor">
+                                <div class="text-small">最低</div>
+                                <div :class="quote.lowestColor">
                                     {{ handleNumberValue(quote.lowest.toFixed(quote.decimalplace)) }}
-                                </span>
+                                </div>
                             </td>
                         </tr>
                     </tbody>
                 </table>
             </div>
             <goods-chart v-bind="{ theme: 'Dark', goodsCode: quote.goodscode }" />
+        </template>
+        <template #footer>
             <Row class="g-layout-block g-layout-block--inset" gutter="10">
                 <Col span="12">
-                <Button type="success" block @click="routerBack">买入</Button>
+                <Button type="success" block @click="routerBack({ buyOrSell: BuyOrSell.Buy })">买入</Button>
                 </Col>
                 <Col span="12">
-                <Button type="danger" block @click="routerBack">卖出</Button>
+                <Button type="danger" block @click="routerBack({ buyOrSell: BuyOrSell.Sell })">卖出</Button>
                 </Col>
             </Row>
         </template>
@@ -64,6 +66,7 @@
 import { computed, defineAsyncComponent } from 'vue'
 import { Button, Col, Row } from 'vant'
 import { handleNumberValue, parsePercent } from '@/filters'
+import { BuyOrSell } from '@/constants/order'
 import { useNavigation } from '@mobile/router/navigation'
 import { useFuturesStore } from '@/stores'
 

+ 70 - 36
src/packages/digital/views/spot/goods/detail/index.vue

@@ -8,8 +8,10 @@
                             <b>{{ quote?.goodscode }}</b>
                         </template>
                         <template #label>
-                            <span :class="quote?.lastColor">{{ quote?.last }}</span>
-                            <span>{{ parsePercent(quote?.change) }}</span>
+                            <div style="display: flex; align-items: center; gap: 10px;">
+                                <h2 :class="quote?.lastColor">{{ quote?.last }}</h2>
+                                <span>{{ parsePercent(quote?.change) }}</span>
+                            </div>
                         </template>
                         <template #right-icon>
                             <span @click="navigateToGoodsChart">图表</span>
@@ -44,20 +46,24 @@
                     v-if="formData.PriceMode === PriceMode.Market" />
                 <Field label="数量">
                     <template #input>
-                        <app-stepper v-model="formData.OrderQty" />
+                        <app-stepper v-model="formData.OrderQty" :min="0" />
                     </template>
                 </Field>
-                <Cell :title="formData.BuyOrSell === BuyOrSell.Buy ? '预估支付' : '预估获取'" :value="calculations.estimatedAmount" />
+                <Cell :title="formData.BuyOrSell === BuyOrSell.Buy ? '预估支付' : '预估获取'"
+                    :value="formatDecimal(calculations.estimatedAmount, quoteAccount?.currencydecimalplace)" />
             </CellGroup>
             <CellGroup inset v-if="formData.BuyOrSell === BuyOrSell.Buy">
-                <Cell title="可用余额" :value="calculations.availableBalance" />
-                <Cell title="可开数量" :value="calculations.availableQty" />
-                <Cell title="预估手续费" :value="calculations.buyEstimatedFee" />
+                <Cell title="预估手续费"
+                    :value="formatDecimal(calculations.buyEstimatedFee, quoteAccount?.currencydecimalplace)" />
+                <Cell title="可用余额"
+                    :value="formatDecimal(calculations.maxBalance, quoteAccount?.currencydecimalplace)" />
+                <Cell title="可买数量" :value="formatDecimal(calculations.maxBuyQty, baseAccount?.currencydecimalplace)" />
             </CellGroup>
             <CellGroup inset v-if="formData.BuyOrSell === BuyOrSell.Sell">
-                <Cell title="可获金额" :value="calculations.availableAmount" />
-                <Cell title="可卖数量" :value="calculations.sellQty" />
-                <Cell title="预估手续费" :value="calculations.sellEstimatedFee" />
+                <Cell title="预估手续费"
+                    :value="formatDecimal(calculations.sellEstimatedFee, quoteAccount?.currencydecimalplace)" />
+                <Cell title="可获金额" :value="formatDecimal(calculations.maxAmount, quoteAccount?.currencydecimalplace)" />
+                <Cell title="可卖数量" :value="formatDecimal(calculations.maxSellQty, baseAccount?.currencydecimalplace)" />
             </CellGroup>
         </Form>
         <Row class="g-layout-block g-layout-block--inset">
@@ -80,23 +86,26 @@
 </template>
 
 <script lang="ts" setup>
-import { shallowRef, reactive, computed, onMounted } from 'vue'
-import { Form, Button, CellGroup, Field, Cell, Tab, Tabs, Col, Row, FormInstance } from 'vant'
+import { shallowRef, reactive, computed, onMounted, onUnmounted, onActivated } from 'vue'
+import { Form, Button, CellGroup, Field, Cell, Tab, Tabs, Col, Row, FormInstance, showDialog } from 'vant'
 import { fullloading } from '@/utils/vant'
 import { EBuildType, EDelistingType, EListingSelectType, EOrderOperateType, EValidType } from '@/constants/client'
-import { parsePercent, handleNumberValue } from '@/filters'
+import { parsePercent, handleNumberValue, formatDecimal } from '@/filters'
 import { BuyOrSell, PriceMode, getPricemode2List } from '@/constants/order'
 import { useNavigation } from '@mobile/router/navigation'
 import { digitalOrder } from '@/services/api/digital'
 import { useFuturesStore, useUserStore } from '@/stores'
 import { useSpotAccountStore } from '../../../wallet/components/spot/composables'
+import quoteSocket from '@/services/websocket/quote'
 import Long from 'long'
 import AppSelect from '@mobile/components/base/select/index.vue'
 import AppStepper from '@mobile/components/base/stepper/index.vue'
 import SpotOrder from '../../components/order/index.vue'
 import SpotAccount from '../../components/account/index.vue'
 
-const { router, getQueryStringToNumber } = useNavigation()
+const subscribe = quoteSocket.createSubscribe()
+
+const { router, getQueryStringToNumber, getGlobalUrlParams } = useNavigation()
 const goodsId = getQueryStringToNumber('id')
 const userStore = useUserStore()
 const futuresStore = useFuturesStore()
@@ -106,7 +115,7 @@ const formRef = shallowRef<FormInstance>()
 
 const formData = reactive<Partial<Proto.DigitalOrderReq>>({
     BuyOrSell: BuyOrSell.Buy,
-    PriceMode: PriceMode.Limit,
+    PriceMode: PriceMode.Market,
     OperateType: EOrderOperateType.ORDEROPERATETYPE_NORMAL,
     ListingSelectType: EListingSelectType.LISTINGSELECTTYPE_DELISTING,
     DelistingType: EDelistingType.DELISTINGTYPE_SELECTED,
@@ -121,35 +130,39 @@ const baseAccount = computed(() => spotAccountStore.getAccountItem({ currencyid:
 const quoteAccount = computed(() => spotAccountStore.getAccountItem({ currencyid: quote.value?.currencyid })) // 计价货币账户
 
 const calculations = computed(() => {
+    const buyFeeValue = futuresStore.getFeeValue(quote.value, 101)
+    const sellFeeValue = futuresStore.getFeeValue(quote.value, 102)
+
     const { last = 0, agreeunit = 0 } = quote.value ?? {}
     const { OrderPrice = 0, OrderQty = 0 } = formData
 
     const price = formData.PriceMode === PriceMode.Market ? last : OrderPrice
+    const amount = OrderQty * agreeunit
 
     // 预估金额
-    const estimatedAmount = price * OrderQty * agreeunit
+    const estimatedAmount = price * amount
 
     // 可用余额
-    const availableBalance = spotAccountStore.getAvailableBalance(quoteAccount.value)
-    // 可数量
-    const availableQty = availableBalance / agreeunit
+    const maxBalance = spotAccountStore.getAvailableBalance(quoteAccount.value)
+    // 可数量
+    const maxBuyQty = price ? maxBalance / (price * agreeunit) : 0
     // 预估手续费
-    const buyEstimatedFee = 0
+    const buyEstimatedFee = (buyFeeValue.FeeAlgorithm === 2 ? amount : estimatedAmount) * buyFeeValue.feeValue
 
     // 可卖数量
-    const sellQty = spotAccountStore.getAvailableBalance(baseAccount.value)
+    const maxSellQty = spotAccountStore.getAvailableBalance(baseAccount.value)
     // 可获金额
-    const availableAmount = price * sellQty * agreeunit
+    const maxAmount = price * maxSellQty * agreeunit
     // 预估手续费
-    const sellEstimatedFee = 0
+    const sellEstimatedFee = (sellFeeValue.FeeAlgorithm === 2 ? amount : estimatedAmount) * sellFeeValue.feeValue
 
     return {
         estimatedAmount,
-        availableBalance,
-        availableQty,
+        maxBalance,
+        maxBuyQty,
         buyEstimatedFee,
-        availableAmount,
-        sellQty,
+        maxAmount,
+        maxSellQty,
         sellEstimatedFee
     }
 })
@@ -164,10 +177,13 @@ const navigateToGoodsChart = () => {
 }
 
 const onSubmit = () => {
-    fullloading((hideLoading) => {
-        if (baseAccount.value && quoteAccount.value) {
-            formData.BaseAccountID = Long.fromString(baseAccount.value.digitalaccountid)
-            formData.QuoteAccountID = Long.fromString(quoteAccount.value.digitalaccountid)
+    const baseAccountId = baseAccount.value?.digitalaccountid
+    const quoteAccountId = quoteAccount.value?.digitalaccountid
+
+    if (baseAccountId && quoteAccountId) {
+        fullloading((hideLoading) => {
+            formData.BaseAccountID = Long.fromString(baseAccountId)
+            formData.QuoteAccountID = Long.fromString(quoteAccountId)
             formData.GoodsID = quote.value?.goodsid
             formData.MarketID = quote.value?.marketid
 
@@ -185,14 +201,32 @@ const onSubmit = () => {
             }).catch((err) => {
                 hideLoading(err, 'fail')
             })
-        } else {
-            hideLoading('请先创建数字账户', 'fail')
-        }
-    })
+        })
+    } else {
+        showDialog({
+            message: '请先激活数字账户'
+        }).then(() => {
+            tabIndex.value = 1
+        })
+    }
 }
 
 onMounted(() => {
-    formData.OrderPrice = quote.value?.last
+    if (quote.value) {
+        subscribe.start(quote.value.goodscode)
+        formData.OrderPrice = quote.value.last
+    }
+
+    onActivated(() => {
+        const params = getGlobalUrlParams()
+        if (params.buyOrSell !== undefined) {
+            formData.BuyOrSell = params.buyOrSell
+        }
+    })
+})
+
+onUnmounted(() => {
+    subscribe.stop()
 })
 </script>
 

+ 2 - 1
src/packages/digital/views/wallet/components/spot/index.vue

@@ -23,7 +23,7 @@
                             余额({{ item.currencycode }})
                         </span>
                         <span class="text-small">
-                            {{ item.currentbalance }}
+                            {{ formatDecimal(item.currentbalance, item.currencydecimalplace) }}
                         </span>
                     </div>
                     <div class="card-section__icon">
@@ -37,6 +37,7 @@
 
 <script lang="ts" setup>
 import { Search, Icon } from 'vant'
+import { formatDecimal } from '@/filters'
 import { getDigitalCurrencyName } from '@/constants/order'
 import { useSpotAccountStore } from './composables'
 

+ 1 - 0
src/packages/mobile/components/base/pull-refresh/index.less

@@ -1,4 +1,5 @@
 .app-pull-refresh {
     height: 100%;
+    min-height: 1px;
     overflow-y: auto;
 }

+ 13 - 1
src/packages/pc/App.vue

@@ -16,7 +16,7 @@ import { computed } from 'vue'
 import { useRouter } from 'vue-router'
 import { ElMessageBox, ElNotification } from 'element-plus'
 import { useLogin } from '@/business/login'
-import { i18n } from "@/stores"
+import { i18n, useSettingStore } from "@/stores"
 import eventBus from '@/services/bus'
 import zhCn from 'element-plus/dist/locale/zh-cn.mjs'
 import en from 'element-plus/dist/locale/en.mjs'
@@ -25,6 +25,7 @@ import zhTW from 'element-plus/dist/locale/zh-tw.mjs'
 import vi from 'element-plus/dist/locale/vi.mjs'
 
 const { userLogout } = useLogin()
+const settingStore = useSettingStore()
 const router = useRouter()
 
 // 国际化语言
@@ -66,4 +67,15 @@ eventBus.$on('RiskToWebNtf', (msg, type) => {
     ElMessageBox.alert(res.content, res.title)
   }
 })
+
+// 接收委托单成交通知
+eventBus.$on('OrderDealedNtf', (res) => {
+  if (settingStore.getSettingValue('showOrderSuccessNotify')) {
+    const data = res as Proto.OrderDealedNtf
+    ElNotification({
+      title: '订单成交通知',
+      message: `订单 ${data.OrderID} 已成交,价格 ${data.TradePrice}`
+    })
+  }
+})
 </script>

+ 3 - 0
src/packages/pc/components/layouts/header/components/setting/index.vue

@@ -51,6 +51,9 @@
                 <el-form-item prop="showOrderFailMessage" :label="t('mine.setting.showOrderFailMessage')">
                     <el-switch v-model="formData.showOrderFailMessage" />
                 </el-form-item>
+                <el-form-item prop="showOrderSuccessNotify" label="显示成交通知">
+                    <el-switch v-model="formData.showOrderSuccessNotify" />
+                </el-form-item>
             </el-form>
         </fieldset>
         <fieldset class="g-fieldset" v-if="globalStore.getSystemInfo('i18nEnabled')">

+ 4 - 5
src/services/websocket/message.ts

@@ -15,8 +15,6 @@ export async function pushMessage50(pkg: Package50, contentType: 'encrypted' | '
     const { funCode, content } = pkg
     const delay = 500 // 延迟推送消息,防止短时间内重复请求
 
-    console.log('接收广播通知', funCode)
-
     switch (funCode) {
         case FunCode.LogoutRsp: {
             //console.log('接收到用户登出通知', funCode)
@@ -41,10 +39,11 @@ export async function pushMessage50(pkg: Package50, contentType: 'encrypted' | '
             break
         }
         case FunCode.OrderDealedNtf: {
-            //console.log('接收到成交通知', funCode)
+            //console.log('接收到委托单成交通知', funCode)
             timerInterceptor.debounce(() => {
-                // 成交通知
-                eventBus.$emit('OrderDealedNtf')
+                const res = decodeProto<Proto.OrderDealedNtf>('OrderDealedNtf', content)
+                // 委托单成交通知
+                eventBus.$emit('OrderDealedNtf', res)
             }, delay, funCode.toString())
             break
         }

+ 2 - 1
src/stores/modules/setting.ts

@@ -25,7 +25,8 @@ export const useSettingStore = defineStore(() => {
             orderPriceType: 1,
             orderQtyIsEmpty: false,
             orderBuyOrSell: 0,
-            luanguage: "zh-CN"
+            luanguage: "zh-CN",
+            showOrderSuccessNotify: false
         }
         return {
             ...defaultValue,

+ 1 - 0
src/types/model/common.d.ts

@@ -338,6 +338,7 @@ declare global {
             showOrderFailMessage: boolean; // 挂/摘牌失败是否显示消息提示
             showOrderEnableQty: boolean; // 挂/摘牌是否显示预估订立量
             showOrderCancelDialog: boolean; // 撤单时弹出提示确认对话框
+            showOrderSuccessNotify: boolean; // 是否显示成交通知
             orderMaxQty: number; // 单笔最大下单量
             orderFocusType: number; // 下单后默认焦点对象,1=价格,2=数量
             orderPriceType: number; // 下单默认价格类型,1=现价,2=对手价,3=实时价,4=实时对手价

+ 44 - 0
src/types/proto/notify.d.ts

@@ -47,5 +47,49 @@ declare global {
             Content: string; // string 消息内容
             RiskLevel: number; // int32 当前风险级别风控级别(RISKLEVEL):无风险0,警告级别1,追加保证金2,斩仓级别3,恢复正常4净值新仓5
         }
+
+        /** 委托单成交通知 */
+        interface OrderDealedNtf {
+            Header: IMessageHead;
+            OrderID: number; // uint64 一级生成的订单号
+            ClientSerialNo: string; // string 客户端流水号
+            GoodsID: number; // uint32 原始委托商品代码
+            TradeQty: number; // uint64 成交数量
+            Amount: number; // double 成交金额
+            ActiveQty: number; // uint64 激活数量,即剩余数量
+            OrderStatus: number; // uint32 单据状态
+            TradeID: number; // uint64 成交ID
+            AccountID: number; // uint64 资金账号代码
+            TradePrice: number; // double 成交价格
+            BuyOrSell: number; // uint32 买卖方向
+            OrderQty: number; // uint64 委托数量
+            OrderType: number; // uint32 订单类型
+            TradeTime: string; // string 成交时间
+            CloseInfos: ArrayCloseInfo; // ArrayCloseInfo 平仓信息
+            SpecialAccount: number; // uint32 特别会员账号
+            TradeCharge: number; // double 成交手续费
+            WareHouseCharge: number; // double 仓单服务费
+            UsedMargin: number; // double 占用保证金
+            OperatorID: number; // uint32 操作员账号ID
+            MemberID: number; // uint32 会员账号,会员系统平仓使用
+            MatchAccountID: number; // uint64 对手方资金账号代码
+            BuildType: number; // uint32 下单类型
+            CloseType: number; // uint32 平仓类型
+        }
+
+        /** 平仓单平仓明细 */
+        interface ArrayCloseInfo {
+            ClosedOrderID: number; // 被平持仓单ID
+            ClosedQty: number; // 被平仓数量
+            ClosePL: number; // 平仓盈亏
+            RlsUsedMargin: number; // 释放的占用保证金
+            TradeCharge: number; // 平仓手续费
+            WareHouseCharge: number; // 仓单服务费
+            TradeAmount: number; // 成交金额
+            OpenTime: string; // 开仓时间(对冲接口用)
+            OpenPrice: number; // 开仓价格(对冲接口用)
+            MatchAccountID: number; // 对手方资金账号代码
+            RealClosePL: number; // 实际盈亏
+        }
     }
 }