li.shaoyi hai 2 meses
pai
achega
0bcc1a6b2b
Modificáronse 29 ficheiros con 422 adicións e 158 borrados
  1. 1 1
      app/package.json
  2. 2 2
      oem/snhl/config/appconfig.json
  3. 61 26
      public/proto/mtp.js
  4. 29 22
      public/proto/mtp.proto
  5. 1 9
      src/business/trade/index.ts
  6. 2 0
      src/constants/funcode.ts
  7. 2 2
      src/packages/digital/assets/themes/global/global.less
  8. 1 1
      src/packages/digital/views/spot/components/account/index.vue
  9. 29 4
      src/packages/digital/views/spot/components/order/cancel/index.vue
  10. 2 1
      src/packages/digital/views/spot/components/order/index.vue
  11. 10 2
      src/packages/digital/views/spot/components/statement/index.vue
  12. 42 22
      src/packages/digital/views/spot/detail/index.vue
  13. 40 0
      src/packages/digital/views/spot/goods/chart/index.less
  14. 56 5
      src/packages/digital/views/spot/goods/chart/index.vue
  15. 58 39
      src/packages/digital/views/spot/goods/detail/index.vue
  16. 7 0
      src/packages/digital/views/spot/goods/list/index.vue
  17. 15 1
      src/packages/digital/views/wallet/components/spot/composables.ts
  18. 3 3
      src/packages/digital/views/wallet/components/spot/index.vue
  19. 2 2
      src/packages/digital/views/wallet/index.vue
  20. 0 3
      src/packages/digital/views/wallet/transfer/index.vue
  21. 6 2
      src/packages/mobile/components/modules/hqchart/candlestick/index.vue
  22. 7 3
      src/packages/mobile/components/modules/hqchart/index.vue
  23. 6 2
      src/packages/mobile/components/modules/hqchart/timeline/index.vue
  24. 7 2
      src/services/api/bank/index.ts
  25. 3 0
      src/services/api/digital/index.ts
  26. 11 2
      src/services/api/trade/index.ts
  27. 2 0
      src/services/bus/types.ts
  28. 14 0
      src/services/websocket/message.ts
  29. 3 2
      src/types/proto/bank.d.ts

+ 1 - 1
app/package.json

@@ -1,6 +1,6 @@
 {
   "name": "trading",
-  "version": "1.0.4",
+  "version": "1.0.5",
   "main": "main.js",
   "dependencies": {
     "electron-updater": "^6.1.4",

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

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

+ 61 - 26
public/proto/mtp.js

@@ -51957,14 +51957,19 @@ var $root = ($protobuf.roots["default"] || ($protobuf.roots["default"] = new $pr
         type: "double",
         id: 6
       },
+      OperateSrc: {
+        rule: "required",
+        type: "uint32",
+        id: 7
+      },
       ClientTicket: {
         rule: "required",
         type: "string",
-        id: 7
+        id: 8
       },
       Remark: {
         type: "string",
-        id: 8
+        id: 9
       }
     }
   },
@@ -52023,18 +52028,23 @@ var $root = ($protobuf.roots["default"] || ($protobuf.roots["default"] = new $pr
         type: "double",
         id: 5
       },
+      OperateSrc: {
+        rule: "required",
+        type: "uint32",
+        id: 6
+      },
       ClientTicket: {
         rule: "required",
         type: "string",
-        id: 6
+        id: 7
       },
       Remark: {
         type: "string",
-        id: 7
+        id: 8
       },
       ExtendInfo: {
         type: "string",
-        id: 8
+        id: 9
       }
     }
   },
@@ -52106,18 +52116,23 @@ var $root = ($protobuf.roots["default"] || ($protobuf.roots["default"] = new $pr
         type: "double",
         id: 6
       },
+      OperateSrc: {
+        rule: "required",
+        type: "uint32",
+        id: 7
+      },
       ClientTicket: {
         rule: "required",
         type: "string",
-        id: 7
+        id: 8
       },
       Remark: {
         type: "string",
-        id: 8
+        id: 9
       },
       ExtendInfo: {
         type: "string",
-        id: 9
+        id: 10
       }
     }
   },
@@ -52166,14 +52181,19 @@ var $root = ($protobuf.roots["default"] || ($protobuf.roots["default"] = new $pr
         type: "uint32",
         id: 3
       },
+      OperateSrc: {
+        rule: "required",
+        type: "uint32",
+        id: 4
+      },
       ClientTicket: {
         rule: "required",
         type: "string",
-        id: 4
+        id: 5
       },
       Remark: {
         type: "string",
-        id: 5
+        id: 6
       }
     }
   },
@@ -52267,10 +52287,13 @@ var $root = ($protobuf.roots["default"] || ($protobuf.roots["default"] = new $pr
         type: "uint32",
         id: 2
       },
-      DigitalAccountID: {
-        rule: "required",
+      DigitalAccountIDs: {
+        rule: "repeated",
         type: "uint64",
-        id: 3
+        id: 3,
+        options: {
+          packed: false
+        }
       },
       DigitalBusinessCode: {
         rule: "required",
@@ -52286,10 +52309,19 @@ var $root = ($protobuf.roots["default"] || ($protobuf.roots["default"] = new $pr
         type: "DigitalSubCommand",
         id: 6
       },
+      OperateSrc: {
+        rule: "required",
+        type: "uint32",
+        id: 7
+      },
       SerialNumber: {
         rule: "required",
         type: "uint64",
-        id: 7
+        id: 8
+      },
+      TradeId: {
+        type: "uint64",
+        id: 9
       }
     }
   },
@@ -52312,28 +52344,27 @@ var $root = ($protobuf.roots["default"] || ($protobuf.roots["default"] = new $pr
         type: "uint32",
         id: 4
       },
-      DigitalAccountID: {
-        rule: "required",
-        type: "uint64",
-        id: 5
-      },
       DigitalBusinessCode: {
         rule: "required",
         type: "uint32",
-        id: 6
+        id: 5
       },
       RelatedOrderID: {
         type: "uint64",
-        id: 7
+        id: 6
       },
       SubCommands: {
         rule: "repeated",
         type: "DigitalSubCommand",
-        id: 8
+        id: 7
       },
       SerialNumber: {
         rule: "required",
         type: "uint64",
+        id: 8
+      },
+      TradeId: {
+        type: "uint64",
         id: 9
       }
     }
@@ -52354,13 +52385,12 @@ var $root = ($protobuf.roots["default"] || ($protobuf.roots["default"] = new $pr
   },
   DigitalFundOperationCmd: {
     fields: {
-      DigitalAccountID: {
+      DigitalOperateType: {
         rule: "required",
-        type: "uint64",
+        type: "uint32",
         id: 1
       },
-      DigitalOperateType: {
-        rule: "required",
+      DigitalBusinessCode: {
         type: "uint32",
         id: 2
       },
@@ -52380,6 +52410,11 @@ var $root = ($protobuf.roots["default"] || ($protobuf.roots["default"] = new $pr
       SubOrderIndex: {
         type: "uint64",
         id: 6
+      },
+      DigitalAccountID: {
+        rule: "required",
+        type: "uint64",
+        id: 7
       }
     }
   }

+ 29 - 22
public/proto/mtp.proto

@@ -15664,10 +15664,11 @@ message DigitalAccountTransferApplyReq {
 		required uint64 AccountID = 2; // 交易账号
 			required uint32 UserID = 3; // 用户ID
 		required uint32 CurrencyID = 4; // 币种ID
-		optional uint32 DigitalTransferType = 5; // 划转类型-枚举"digitaltransfertype"(3:转入
+		optional uint32 DigitalTransferType = 5; // 划转类型-枚举"digitaltransfertype"(3:转入,4:转出)
 		required double Amount = 6; // 转入转出金额
-		required string ClientTicket = 7; // 客户端流水号
-		optional string Remark = 8; // 备注
+			required uint32 OperateSrc = 7; // 操作来源-枚举"operatesrc"(1:管理端,2:终端)
+		required string ClientTicket = 8; // 客户端流水号
+		optional string Remark = 9; // 备注
 }
 // 数字账户转入转出申请应答
 message DigitalAccountTransferApplyRsp {
@@ -15685,9 +15686,10 @@ message DigitalAccountDepositApplyReq {
 		required uint32 CurrencyID = 3; // 币种ID
 		required uint32 WalletID = 4; // 钱包ID
 		required double Amount = 5; // 金额
-		required string ClientTicket = 6; // 客户端流水号
-		optional string Remark = 7; // 备注
-		optional string ExtendInfo = 8; // 扩展信息(JSON串,按钱包类型区分)
+			required uint32 OperateSrc = 6; // 操作来源-枚举"operatesrc"(1:管理端,2:终端)
+		required string ClientTicket = 7; // 客户端流水号
+		optional string Remark = 8; // 备注
+		optional string ExtendInfo = 9; // 扩展信息(JSON串,按钱包类型区分)
 }
 // 数字账户充值申请应答
 message DigitalAccountDepositApplyRsp {
@@ -15708,9 +15710,10 @@ message DigitalAccountWithdrawApplyReq {
 		required uint32 WalletID = 4; // 钱包ID
 		required uint64 DigitalAccountID = 5; // 数字账户ID
 		required double Amount = 6; // 金额
-		required string ClientTicket = 7; // 客户端流水号
-		optional string Remark = 8; // 备注
-		optional string ExtendInfo = 9; // 扩展信息(JSON串,按钱包类型区分)
+			required uint32 OperateSrc = 7; // 操作来源-枚举"operatesrc"(1:管理端,2:终端)
+		required string ClientTicket = 8; // 客户端流水号
+		optional string Remark = 9; // 备注
+		optional string ExtendInfo = 10; // 扩展信息(JSON串,按钱包类型区分)
 }
 // 数字账户提现申请应答
 message DigitalAccountWithdrawApplyRsp {
@@ -15719,15 +15722,16 @@ message DigitalAccountWithdrawApplyRsp {
 	optional string RetDesc = 3; // 描述信息
 		optional uint64 ApplyID = 4; // 申请ID
 		required string ClientTicket = 5; // 客户端流水号
-		optional uint32 Status = 6; // 申请状态(1:待审核2:审核中3:审核通过
+		optional uint32 Status = 6; // 申请状态(1:待审核,2:审核中,3:审核通过,4:审核拒绝,5:审核失败,6:审核超时,7:已撤销)
 }
 // 数字账户提现申请撤销请求
 message DigitalAccountWithdrawApplyCancelReq {
 	optional MessageHead Header = 1;
 		required uint64 ApplyID = 2; // 申请ID
 			required uint32 UserID = 3; // 用户ID
-		required string ClientTicket = 4; // 客户端流水号
-		optional string Remark = 5; // 备注
+			required uint32 OperateSrc = 4; // 操作来源-枚举"operatesrc"(1:管理端,2:终端)
+		required string ClientTicket = 5; // 客户端流水号
+		optional string Remark = 6; // 备注
 }
 // 数字账户提现申请撤销应答
 message DigitalAccountWithdrawApplyCancelRsp {
@@ -15741,7 +15745,7 @@ message DigitalAccountWithdrawApplyCancelRsp {
 message DigitalAccountWithdrawApplyAuditReq {
 	optional MessageHead Header = 1;
 		required uint64 ApplyID = 2; // 申请ID
-		required uint32 Status = 3; // 申请状态(3:审核通过4:审核拒绝)
+		required uint32 Status = 3; // 申请状态(3:审核通过,4:审核拒绝)
 		required string ClientTicket = 4; // 客户端流水号
 		optional string Remark = 5; // 备注
 }
@@ -15757,11 +15761,13 @@ message DigitalAccountWithdrawApplyAuditRsp {
 message DigitalAccountCommandReq {
 	optional MessageHead Header = 1;
 			required uint32 UserID = 2; // 用户ID
-		required uint64 DigitalAccountID = 3; // 数字账户ID
+		repeated uint64 DigitalAccountIDs = 3; // 数字账户ID列表
 		required uint32 DigitalBusinessCode = 4; // 业务编号(枚举"digitalbusinesscode")
 		optional uint64 RelatedOrderID = 5; // 关联单号
 		repeated DigitalSubCommand SubCommands = 6; // 子指令集合
-		required uint64 SerialNumber = 7; // 流水号
+			required uint32 OperateSrc = 7; // 操作来源-枚举"operatesrc"(1:管理端,3:交易服务)
+		required uint64 SerialNumber = 8; // 流水号
+			optional uint64 TradeId = 9; // 成交单号
 }
 // 数字账户操作指令应答
 message DigitalAccountCommandRsp {
@@ -15769,11 +15775,11 @@ message DigitalAccountCommandRsp {
 	optional int32 RetCode = 2; // 返回码
 	optional string RetDesc = 3; // 描述信息
 			required uint32 UserID = 4; // 用户ID
-		required uint64 DigitalAccountID = 5; // 数字账户ID
-		required uint32 DigitalBusinessCode = 6; // 业务编号(枚举"digitalbusinesscode")
-		optional uint64 RelatedOrderID = 7; // 关联单号
-		repeated DigitalSubCommand SubCommands = 8; // 子指令集合
-		required uint64 SerialNumber = 9; // 流水
+		required uint32 DigitalBusinessCode = 5; // 业务编号(枚举"digitalbusinesscode")
+		optional uint64 RelatedOrderID = 6; // 关联单号
+		repeated DigitalSubCommand SubCommands = 7; // 子指令集合
+		required uint64 SerialNumber = 8; // 流水号
+			optional uint64 TradeId = 9; // 成交单
 }
 // 数字账户操作子指令
 message DigitalSubCommand {
@@ -15782,10 +15788,11 @@ message DigitalSubCommand {
 }
 // 数字账户资金操作指令
 message DigitalFundOperationCmd {
-		required uint64 DigitalAccountID = 1; // 数字账户ID
-		required uint32 DigitalOperateType = 2; // 变更类型(枚举"digitaloperatetype")
+		required uint32 DigitalOperateType = 1; // 变更类型(枚举"digitaloperatetype")
+		optional uint32 DigitalBusinessCode = 2; // 业务编号(枚举"digitalbusinesscode")
 		required double ChangeAmount = 3; // 变更金额
 		optional uint32 RelatedGoodsID = 4; // 关联商品ID
 		optional uint32 RelatedMarketID = 5; // 关联市场
 		optional uint64 SubOrderIndex = 6; // 子指令序号
+		required uint64 DigitalAccountID = 7; // 数字账户ID
 }

+ 1 - 9
src/business/trade/index.ts

@@ -494,21 +494,13 @@ export function useCancelOrder() {
     const loading = shallowRef(false)
     /// 参数信息
     const formData = reactive<Partial<Proto.CancelOrderReq>>({
-        OperatorID: loginStore.loginId,
-        OrderSrc: OrderSrc.ORDERSRC_CLIENT,
-        AccountID: accountStore.currentAccountId,
-        ClientType: ClientType.Web,
         OperateType: EOperateType.OPERATETYPE_ORDERCANCEL,
     })
     const cancelSubmit = async () => {
         try {
             loading.value = true
             return await cancelOrder({
-                data: {
-                    ...formData,
-                    ClientSerialNo: v4(),
-                    ClientOrderTime: formatDate(new Date().toISOString()),
-                }
+                data: formData
             })
         } finally {
             loading.value = false

+ 2 - 0
src/constants/funcode.ts

@@ -58,6 +58,8 @@ export enum FunCode {
     RiskToWebNtf = 131139, // 风控消息管理端通知客户端
     OrderSuccessedNtf = 131152, /// 委托单委托成功通知(0, 2, 80)
     OrderCanceledNtf = 131084, /// 委托单撤单通知(0, 2, 12)
+    DigitalAccountChangedNtf = 131180, // 数字账户变化通知
+    DigitalAccountFundsChangedNtf = 131181, // 数字账户资金变化通知
     
     // 行情内容
     QuoteBeat = 0x12, // 心跳

+ 2 - 2
src/packages/digital/assets/themes/global/global.less

@@ -113,14 +113,14 @@
     line-height: 1.6;
 
     dl {
-        font-size: 13px;
         border-bottom: 1px solid #171f2d;
-        padding: 10px;
+        padding: 10px var(--van-padding-md);
 
         dd {
             display: flex;
             align-items: center;
             justify-content: space-between;
+            font-size: 13px;
         }
     }
 

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

@@ -10,7 +10,7 @@
                     </th>
                     <td>
                         <span class="text-small">可用({{ item.currencycode }})</span>
-                        <span>{{ item.currentbalance - item.freezemargin }}</span>
+                        <span>{{ spotAccountStore.getAvailableBalance(item) }}</span>
                     </td>
                 </tr>
                 <tr>

+ 29 - 4
src/packages/digital/views/spot/components/order/cancel/index.vue

@@ -5,10 +5,14 @@
 </template>
 
 <script lang="ts" setup>
-import { shallowRef, onMounted, PropType } from 'vue'
+import { shallowRef, reactive, onMounted, PropType } from 'vue'
 import { Dialog } from 'vant'
+import { fullloading } from '@/utils/vant'
+import { handleRequestBigNumber } from '@/filters'
+import { EOperateType } from '@/constants/client'
+import { cancelOrder } from '@/services/api/trade'
 
-defineProps({
+const props = defineProps({
     selectedRow: {
         type: Object as PropType<Model.DigitalTradeOrderDetailsRsp>,
         required: true
@@ -20,10 +24,31 @@ const emit = defineEmits(['closed'])
 const showDialog = shallowRef(false)
 const refresh = shallowRef(false) // 是否刷新父组件数据
 
+const formData = reactive<Partial<Proto.CancelOrderReq>>({
+    Header: {
+        AccountID: Number(props.selectedRow.quoteaccountid),
+        MarketID: props.selectedRow.marketid,
+        GoodsID: props.selectedRow.goodsid
+    },
+    AccountID: Number(props.selectedRow.quoteaccountid),
+    OperateType: EOperateType.OPERATETYPE_ORDERCANCEL,
+    OldOrderId: handleRequestBigNumber(props.selectedRow.orderid)
+})
+
 const onBeforeClose = (action: string) => {
     if (action === 'confirm') {
-        refresh.value = true
-        showDialog.value = false
+        fullloading((hideLoading) => {
+            cancelOrder({
+                data: formData
+            }).then(() => {
+                hideLoading('撤销成功', 'success')
+                refresh.value = true
+            }).catch((err) => {
+                hideLoading(err, 'fail')
+            }).finally(() => {
+                showDialog.value = false
+            })
+        })
     }
     return true
 }

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

@@ -14,7 +14,7 @@
                 <tr>
                     <th>
                         <span>{{ item.goodsname }}</span>
-                        <time class="text-small">{{ item.ordertime }}</time>
+                        <time class="text-small">{{ formatDate(item.ordertime) }}</time>
                     </th>
                     <th>
                         <span class="g-price-down">{{ getBuyOrSellName(item.buyorsell) }}</span>
@@ -65,6 +65,7 @@
 <script lang="ts" setup>
 import { shallowRef, defineAsyncComponent, PropType, computed } from 'vue'
 import { Button } from 'vant'
+import { formatDate } from '@/filters'
 import { useComponent } from '@/hooks/component'
 import { useRequest } from '@/hooks/request'
 import { getBuyOrSellName, getOrderStatusName } from '@/constants/order'

+ 10 - 2
src/packages/digital/views/spot/components/statement/index.vue

@@ -1,5 +1,10 @@
 <template>
     <div class="g-detail-list">
+        <dl>
+            <dt>
+                <app-date-picker v-model="date" />
+            </dt>
+        </dl>
         <dl v-for="(item, index) in dataList" :key="index">
             <dt>
                 <span>{{ item.tradedate }}</span>
@@ -16,7 +21,7 @@
                 <span class="text-small">金额</span>
                 <span>
                     {{ (item.changevalue > 0 ? '+' : '') + item.changevalue }} {{
-                        getDigitalCurrencyName(item.currencyid)}}
+                        getDigitalCurrencyName(item.currencyid) }}
                 </span>
             </dd>
         </dl>
@@ -24,10 +29,11 @@
 </template>
 
 <script lang="ts" setup>
-import { PropType } from 'vue'
+import { shallowRef, PropType } from 'vue'
 import { useRequest } from '@/hooks/request'
 import { getDigitalBusinessCodeName, getDigitalOperateTypeName, getDigitalCurrencyName } from '@/constants/order'
 import { queryTaaccountDigitalLogs } from '@/services/api/digital'
+import AppDatePicker from '@mobile/components/base/datepicker/index.vue'
 
 const props = defineProps({
     params: {
@@ -36,6 +42,8 @@ const props = defineProps({
     }
 })
 
+const date = shallowRef('')
+
 const { dataList } = useRequest(queryTaaccountDigitalLogs, {
     defaultParams: props.params
 })

+ 42 - 22
src/packages/digital/views/spot/detail/index.vue

@@ -3,10 +3,11 @@
         <template #header>
             <app-navbar title="现货明细" />
         </template>
-        <Grid :border="false" :column-num="3">
-            <GridItem icon="pending-payment" text="充值" :to="{ name: 'wallet-deposit', query: { id: accountId } }" />
-            <GridItem icon="paid" text="提现" :to="{ name: 'wallet-withdraw', query: { id: accountId } }" />
-            <GridItem icon="chart-trending-o" text="交易" @click="navigateToSpotDetail" />
+        <Grid :border="false" :column-num="quotes.length ? 3 : 2">
+            <GridItem icon="pending-payment" text="充值"
+                :to="{ name: 'wallet-deposit', query: { id: digitalaccountid } }" />
+            <GridItem icon="paid" text="提现" :to="{ name: 'wallet-withdraw', query: { id: digitalaccountid } }" />
+            <GridItem icon="chart-trending-o" text="交易" @click="navigateToSpotDetail()" v-if="quotes.length" />
         </Grid>
         <div class="g-detail-table" v-if="accountItem">
             <table cellspacing="0" cellpadding="0">
@@ -20,7 +21,7 @@
                     <tr>
                         <td>
                             <span class="text-small">可用({{ accountItem.currencycode }})</span>
-                            <span>{{ accountItem.currentbalance - accountItem.freezemargin }}</span>
+                            <span>{{ spotAccountStore.getAvailableBalance(accountItem) }}</span>
                         </td>
                         <td>
                             <span class="text-small">冻结({{ accountItem.currencycode }})</span>
@@ -44,46 +45,65 @@
                 <wallet-record />
             </Tab>
             <Tab title="委托">
-                <spot-order :params="{ digitalaccountid: accountId }" showDatePicker />
+                <spot-order :params="{ digitalaccountid }" showDatePicker />
             </Tab>
             <Tab title="成交">
-                <spot-trade :params="{ digitalaccountid: accountId }" />
+                <spot-trade :params="{ digitalaccountid }" />
             </Tab>
             <Tab title="资金明细">
-                <spot-statement :params="{ digitalaccountid: accountId }" />
+                <spot-statement :params="{ digitalaccountid }" />
             </Tab>
         </Tabs>
+        <ActionSheet v-model:show="showSheet" title="请选择">
+            <CellGroup>
+                <template v-for="(item, index) in quotes" :key="index">
+                    <Cell :title="item.goodsname" :value="item.goodscode" :border="false" is-link
+                        @click="navigateToSpotDetail(item.goodsid)" />
+                </template>
+            </CellGroup>
+        </ActionSheet>
     </app-view>
 </template>
 
 <script lang="ts" setup>
 import { shallowRef, computed } from 'vue'
-import { Tab, Tabs, Grid, GridItem } from 'vant'
+import { Cell, CellGroup, Tab, Tabs, Grid, GridItem, ActionSheet } from 'vant'
 import { useNavigation } from '@mobile/router/navigation'
+import { useFuturesStore } from '@/stores'
 import { useSpotAccountStore } from '../../wallet/components/spot/composables'
 import WalletRecord from '../../wallet/components/record/index.vue'
 import SpotOrder from '../components/order/index.vue'
 import SpotTrade from '../components/trade/index.vue'
 import SpotStatement from '../components/statement/index.vue'
 
-const { router, getQueryString } = useNavigation()
+const { router, getQueryStringToNumber } = useNavigation()
 
+const futuresStore = useFuturesStore()
 const spotAccountStore = useSpotAccountStore()
-const accountId = getQueryString('id') || '0'
+
+const currencyid = getQueryStringToNumber('id')
+const showSheet = shallowRef(false)
 const tabIndex = shallowRef(0)
 
-const accountItem = computed(() => spotAccountStore.getAccountItem({
-    digitalaccountid: accountId
-}))
+const accountItem = computed(() => spotAccountStore.getAccountItem({ currencyid }))
+
+const digitalaccountid = computed(() => accountItem.value?.digitalaccountid || '0')
+
+const quotes = computed(() => futuresStore.quotationList.filter((e) => e.trademode === 80 && (e.goodscurrencyid === currencyid || e.currencyid === currencyid)))
+
+const navigateToSpotDetail = (goodsid?: number) => {
+    showSheet.value = false
 
-const navigateToSpotDetail = () => {
-    const goodsId = accountId
     // 多个商品弹出列表选择,单个商品直接跳转
-    router.push({
-        name: 'spot-goods-detail',
-        query: {
-            id: goodsId
-        }
-    })
+    if (quotes.value.length > 1 && !goodsid) {
+        showSheet.value = true
+    } else {
+        router.push({
+            name: 'spot-goods-detail',
+            query: {
+                id: goodsid ?? quotes.value[0].goodsid
+            }
+        })
+    }
 }
 </script>

+ 40 - 0
src/packages/digital/views/spot/goods/chart/index.less

@@ -0,0 +1,40 @@
+.spot-goods-chart {
+    .quote-table {
+        padding: var(--van-padding-md);
+
+        .data-table {
+            width: 100%;
+
+            &__cell {
+                &--merged {
+                    width: 50%;
+                    text-align: center;
+                }
+
+                >span {
+                    display: block;
+                }
+
+                .text-small {
+                    color: #666;
+                }
+            }
+        }
+    }
+
+    .app-quote-chart {
+        background-color: #000;
+
+        &__header {
+            border-color: #171f2d;
+        }
+
+        .tabs {
+            border-color: #171f2d;
+
+            &-item.is-active {
+                color: #fff;
+            }
+        }
+    }
+}

+ 56 - 5
src/packages/digital/views/spot/goods/chart/index.vue

@@ -1,20 +1,71 @@
 <template>
-    <app-view class="spot-goods-chart g-form">
+    <app-view class="spot-goods-chart g-layout">
         <template #header>
             <app-navbar :title="quote?.goodscode" />
         </template>
-        图表
+        <template v-if="quote">
+            <div class="quote-table">
+                <table class="data-table" cellspacing="0" cellpadding="0">
+                    <tbody>
+                        <tr class="data-table__row">
+                            <td class="data-table__cell data-table__cell--merged" rowspan="2">
+                                <div :class="quote.lastColor">{{ quote.last }}</div>
+                                <div>
+                                    <span>{{ quote.rise }}</span>
+                                    <span>{{ parsePercent(quote.change) }}</span>
+                                </div>
+                            </td>
+                            <td class="data-table__cell">
+                                <span class="text-small">昨结</span>
+                                <span>{{ quote.presettle }}</span>
+                            </td>
+                            <td class="data-table__cell">
+                                <span class="text-small">最高</span>
+                                <span :class="quote.highestColor">{{ quote.highest }}</span>
+                            </td>
+                        </tr>
+                        <tr>
+                            <td class="data-table__cell">
+                                <span class="text-small">开盘</span>
+                                <span :class="quote.openedColor">{{ quote.opened }}</span>
+                            </td>
+                            <td class="data-table__cell">
+                                <span class="text-small">最低</span>
+                                <span :class="quote.lowestColor">{{ quote.lowest }}</span>
+                            </td>
+                        </tr>
+                    </tbody>
+                </table>
+            </div>
+            <goods-chart v-bind="{ theme: 'Dark', goodsCode: quote.goodscode }" />
+            <Row class="g-layout-block g-layout-block--inset" gutter="10">
+                <Col span="12">
+                <Button type="success" block @click="routerBack">买入</Button>
+                </Col>
+                <Col span="12">
+                <Button type="danger" block @click="routerBack">卖出</Button>
+                </Col>
+            </Row>
+        </template>
     </app-view>
 </template>
 
 <script lang="ts" setup>
-import { computed } from 'vue'
+import { computed,defineAsyncComponent } from 'vue'
+import { Button, Col, Row } from 'vant'
+import { parsePercent } from '@/filters'
 import { useNavigation } from '@mobile/router/navigation'
 import { useFuturesStore } from '@/stores'
 
-const { getQueryStringToNumber } = useNavigation()
+const GoodsChart = defineAsyncComponent(() => import('@mobile/components/modules/hqchart/index.vue'))
+
+const { getQueryStringToNumber, routerBack } = useNavigation()
 const goodsid = getQueryStringToNumber('id')
 const futuresStore = useFuturesStore()
 
 const quote = computed(() => futuresStore.getQuoteInfo({ goodsid }))
-</script>
+</script>
+
+<style lang="less">
+@import './index.less';
+</style>

+ 58 - 39
src/packages/digital/views/spot/goods/detail/index.vue

@@ -8,7 +8,7 @@
                             <b>{{ quote?.goodscode }}</b>
                         </template>
                         <template #label>
-                            <span>{{ quote?.presettle }}</span>
+                            <span :class="quote?.lastColor">{{ quote?.last }}</span>
                             <span>{{ parsePercent(quote?.change) }}</span>
                         </template>
                         <template #right-icon>
@@ -47,17 +47,17 @@
                         <app-stepper v-model="formData.OrderQty" />
                     </template>
                 </Field>
-                <Cell :title="formData.BuyOrSell === BuyOrSell.Buy ? '预估支付' : '预估获取'" :value="calculations.estimate" />
+                <Cell :title="formData.BuyOrSell === BuyOrSell.Buy ? '预估支付' : '预估获取'" :value="calculations.estimatedAmount" />
             </CellGroup>
             <CellGroup inset v-if="formData.BuyOrSell === BuyOrSell.Buy">
-                <Cell title="可用余额" :value="calculations.available" />
-                <Cell title="可开数量" :value="calculations.qty" />
-                <Cell title="预估手续费" :value="calculations.free" />
+                <Cell title="可用余额" :value="calculations.availableBalance" />
+                <Cell title="可开数量" :value="calculations.availableQty" />
+                <Cell title="预估手续费" :value="calculations.buyEstimatedFee" />
             </CellGroup>
             <CellGroup inset v-if="formData.BuyOrSell === BuyOrSell.Sell">
-                <Cell title="可卖数量" :value="calculations.qty" />
-                <Cell title="可获金额" :value="calculations.available" />
-                <Cell title="预估手续费" :value="calculations.free" />
+                <Cell title="可获金额" :value="calculations.availableAmount" />
+                <Cell title="可卖数量" :value="calculations.sellQty" />
+                <Cell title="预估手续费" :value="calculations.sellEstimatedFee" />
             </CellGroup>
         </Form>
         <Row class="g-layout-block g-layout-block--inset">
@@ -83,7 +83,7 @@
 import { shallowRef, reactive, computed, onMounted } from 'vue'
 import { Form, Button, CellGroup, Field, Cell, Tab, Tabs, Col, Row, FormInstance } from 'vant'
 import { fullloading } from '@/utils/vant'
-import { ClientType, EBuildType, EDelistingType, EListingSelectType, EOrderOperateType, EValidType, OrderSrc } from '@/constants/client'
+import { EBuildType, EDelistingType, EListingSelectType, EOrderOperateType, EValidType } from '@/constants/client'
 import { parsePercent, handleNumberValue } from '@/filters'
 import { BuyOrSell, PriceMode, getPricemode2List } from '@/constants/order'
 import { useNavigation } from '@mobile/router/navigation'
@@ -105,11 +105,9 @@ const tabIndex = shallowRef(0)
 const formRef = shallowRef<FormInstance>()
 
 const formData = reactive<Partial<Proto.DigitalOrderReq>>({
-    ClientType: ClientType.Web,
     BuyOrSell: BuyOrSell.Buy,
     PriceMode: PriceMode.Limit,
     OperateType: EOrderOperateType.ORDEROPERATETYPE_NORMAL,
-    OrderSrc: OrderSrc.ORDERSRC_CLIENT,
     ListingSelectType: EListingSelectType.LISTINGSELECTTYPE_DELISTING,
     DelistingType: EDelistingType.DELISTINGTYPE_SELECTED,
     BuildType: EBuildType.BUILDTYPE_OPEN,
@@ -123,14 +121,36 @@ const baseAccount = computed(() => spotAccountStore.getAccountItem({ currencyid:
 const quoteAccount = computed(() => spotAccountStore.getAccountItem({ currencyid: quote.value?.currencyid })) // 计价货币账户
 
 const calculations = computed(() => {
-    const { agreeunit = 0 } = quote.value ?? {}
+    const { last = 0, agreeunit = 0 } = quote.value ?? {}
     const { OrderPrice = 0, OrderQty = 0 } = formData
 
+    const price = formData.PriceMode === PriceMode.Market ? last : OrderPrice
+
+    // 预估金额
+    const estimatedAmount = price * OrderQty * agreeunit
+
+    // 可用余额
+    const availableBalance = spotAccountStore.getAvailableBalance(quoteAccount.value)
+    // 可用数量
+    const availableQty = availableBalance / agreeunit
+    // 预估手续费
+    const buyEstimatedFee = 0
+
+    // 可卖数量
+    const sellQty = spotAccountStore.getAvailableBalance(baseAccount.value)
+    // 可获金额
+    const availableAmount = price * sellQty * agreeunit
+    // 预估手续费
+    const sellEstimatedFee = 0
+
     return {
-        estimate: OrderPrice * OrderQty * agreeunit,
-        available: 0,
-        qty: 0,
-        free: 0
+        estimatedAmount,
+        availableBalance,
+        availableQty,
+        buyEstimatedFee,
+        availableAmount,
+        sellQty,
+        sellEstimatedFee
     }
 })
 
@@ -144,31 +164,30 @@ const navigateToGoodsChart = () => {
 }
 
 const onSubmit = () => {
-    if (quote.value) {
-        formData.GoodsID = quote.value.goodsid
-        formData.MarketID = quote.value.marketid
-    }
-
-    // 市价
-    if (formData.PriceMode === PriceMode.Market) {
-        const param112 = userStore.getSystemParamValue('112')
-        formData.OrderPrice = 0
-        formData.MarketMaxSub = Number(param112) || 100
-    }
+    fullloading((hideLoading) => {
+        if (baseAccount.value && quoteAccount.value) {
+            formData.BaseAccountID = Long.fromString(baseAccount.value.digitalaccountid)
+            formData.QuoteAccountID = Long.fromString(quoteAccount.value.digitalaccountid)
+            formData.GoodsID = quote.value?.goodsid
+            formData.MarketID = quote.value?.marketid
 
-    if (baseAccount.value && quoteAccount.value) {
-        formData.BaseAccountID = Long.fromString(baseAccount.value.digitalaccountid)
-        formData.QuoteAccountID = Long.fromString(quoteAccount.value.digitalaccountid)
-    }
+            // 市价
+            if (formData.PriceMode === PriceMode.Market) {
+                const param112 = userStore.getSystemParamValue('112')
+                formData.OrderPrice = quote.value?.last
+                formData.MarketMaxSub = Number(param112) || 100
+            }
 
-    fullloading((hideLoading) => {
-        digitalOrder({
-            data: formData
-        }).then(() => {
-            hideLoading('提交成功', 'success')
-        }).catch((err) => {
-            hideLoading(err, 'fail')
-        })
+            digitalOrder({
+                data: formData
+            }).then(() => {
+                hideLoading('提交成功', 'success')
+            }).catch((err) => {
+                hideLoading(err, 'fail')
+            })
+        } else {
+            hideLoading('请先创建数字账户', 'fail')
+        }
     })
 }
 

+ 7 - 0
src/packages/digital/views/spot/goods/list/index.vue

@@ -71,10 +71,12 @@ import { Search, Tab, Tabs, Image } from 'vant'
 import { useNavigation } from '@mobile/router/navigation'
 import { parsePercent, handleNumberValue, getFirstImage } from '@/filters'
 import { useFuturesStore, useUserStore } from '@/stores'
+import quoteSocket from '@/services/websocket/quote'
 
 const { router } = useNavigation()
 const userStore = useUserStore()
 const futuresStore = useFuturesStore()
+const subscribe = quoteSocket.createSubscribe()
 const currentGroupId = shallowRef(0)
 
 const goodsGroups = userStore.userData.goodsgroups.filter((e) => e.marketid === 80201)
@@ -90,6 +92,11 @@ const rowClick = (row: Model.GoodsQuote) => {
     })
 }
 
+futuresStore.onDataCompleted(() => {
+    const goodsCodes = goodsList.value.map((e) => e.goodscode)
+    subscribe.start(...goodsCodes)
+})
+
 onMounted(() => {
     const [firstGroup] = goodsGroups
     currentGroupId.value = firstGroup ? firstGroup.goodsgroupid : 0

+ 15 - 1
src/packages/digital/views/wallet/components/spot/composables.ts

@@ -2,6 +2,7 @@ import { defineStore } from 'pinia'
 import { isMatch } from 'lodash'
 import { useRequest } from '@/hooks/request'
 import { queryTaaccountDigitals } from '@/services/api/digital'
+import eventBus from '@/services/bus'
 
 export const useSpotAccountStore = defineStore('spotAaccount', () => {
     const { dataList, loading, run: fetchTaaccountDigitals } = useRequest(queryTaaccountDigitals)
@@ -14,11 +15,24 @@ export const useSpotAccountStore = defineStore('spotAaccount', () => {
         return dataList.value.find(item => isMatch(item, prop))
     }
 
+    // 获取可用余额
+    const getAvailableBalance = (item?: Model.TaaccountDigitalsRsp) => {
+        if (item) {
+            return item.currentbalance - item.freezeinout - item.freezemargin - item.freezetradecharge - item.freezetransfer - item.usedmargin
+        }
+        return 0
+    }
+
+    // 接收变更通知
+    const eventNotify = eventBus.$on(['DigitalAccountChangedNtf', 'DigitalAccountFundsChangedNtf'], () => fetchTaaccountDigitals())
+
     return {
         loading,
         dataList,
+        eventNotify,
         filterAccountsByCurrencyId,
         fetchTaaccountDigitals,
-        getAccountItem
+        getAccountItem,
+        getAvailableBalance
     }
 })

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

@@ -2,7 +2,7 @@
 <template>
     <div class="spot-account">
         <Search shape="round" placeholder="搜索" />
-        <div class="card" @click="onClick(item.digitalaccountid)" v-for="(item, index) in spotAccountStore.dataList"
+        <div class="card" @click="onClick(item)" v-for="(item, index) in spotAccountStore.dataList"
             :key="index">
             <div class="card-section">
                 <!-- <div class="card-section__image">
@@ -41,8 +41,8 @@ import { useSpotAccountStore } from './composables'
 
 const emit = defineEmits(['click'])
 
-const onClick = (accountId: string) => {
-    emit('click', accountId)
+const onClick = (item: Model.TaaccountDigitalsRsp) => {
+    emit('click', item.currencyid)
 }
 
 const spotAccountStore = useSpotAccountStore()

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

@@ -32,11 +32,11 @@ const { router } = useNavigation()
 
 const currentTabIndex = shallowRef(0)
 
-const navigateToSpotDetail = (accountId: number) => {
+const navigateToSpotDetail = (currencyId: number) => {
     router.push({
         name: 'spot-detail',
         query: {
-            id: accountId
+            id: currencyId
         }
     })
 }

+ 0 - 3
src/packages/digital/views/wallet/transfer/index.vue

@@ -41,7 +41,6 @@
 
 <script lang="ts" setup>
 import { shallowRef, reactive } from 'vue'
-import { v4 } from 'uuid'
 import { FormInstance, Form, Col, Row, Button, CellGroup, Field, Cell, FieldRule } from 'vant'
 import { fullloading } from '@/utils/vant'
 import { getCurrencyList, getDigitalCurrencyList } from '@/constants/order'
@@ -100,8 +99,6 @@ const formRules: { [key: string]: FieldRule[] } = {
 
 const onSubmit = () => {
     fullloading((hideLoading) => {
-        formData.ClientTicket = v4()
-
         DigitalAccountTransferApply({
             data: formData
         }).then(() => {

+ 6 - 2
src/packages/mobile/components/modules/hqchart/candlestick/index.vue

@@ -1,15 +1,16 @@
 <template>
     <div class="app-candlestick">
-        <HQChart @ready="onReady" />
+        <HQChart @ready="onReady" :default-theme="theme" />
         <Tabs class="app-tabs--indicator" :data-list="tabs" @change="changeIndex" />
     </div>
 </template>
 
 <script lang="ts" setup>
-import { shallowRef, shallowReactive, watch, computed } from 'vue'
+import { shallowRef, shallowReactive, watch, computed,PropType } from 'vue'
 import { Chart } from 'hqchart'
 import { timerInterceptor } from '@/utils/timer'
 import { handleNumberValue } from '@/filters'
+import { AppTheme } from '@/constants/theme'
 import { ChartCycleType } from '@/constants/chart'
 import { useFuturesStore, i18n } from '@/stores'
 import { useDataset } from '@/hooks/hqchart/candlestick/dataset'
@@ -33,6 +34,9 @@ const props = defineProps({
     isShowTitle: {
         type: Boolean,
         default: true
+    },
+    theme: {
+        type: String as PropType<keyof typeof AppTheme>
     }
 })
 

+ 7 - 3
src/packages/mobile/components/modules/hqchart/index.vue

@@ -16,14 +16,15 @@
             </Tabs>
             <Icon name="setting-o" style="padding: 0 16px;" v-if="false" />
         </div>
-        <MLine v-bind="{ symbol, goodsCode }" v-if="tabIndex === 0" />
-        <KLine v-bind="{ symbol, goodsCode, cycleType }" v-else />
+        <MLine v-bind="{ symbol, goodsCode }" :theme="theme" v-if="tabIndex === 0" />
+        <KLine v-bind="{ symbol, goodsCode, cycleType }" :theme="theme" v-else />
     </div>
 </template>
 
 <script lang="ts" setup>
-import { shallowRef } from 'vue'
+import { shallowRef, PropType } from 'vue'
 import { Tab, Tabs, Popover, Icon, PopoverAction } from 'vant'
+import { AppTheme } from '@/constants/theme'
 import { ChartCycleType } from '@/constants/chart'
 import MLine from './timeline/index.vue'
 import KLine from './candlestick/index.vue'
@@ -33,6 +34,9 @@ defineProps({
     goodsCode: {
         type: String,
         required: true
+    },
+    theme: {
+        type: String as PropType<keyof typeof AppTheme>
     }
 })
 

+ 6 - 2
src/packages/mobile/components/modules/hqchart/timeline/index.vue

@@ -1,14 +1,15 @@
 <template>
     <div :class="classNames">
-        <HQChart @ready="onReady" />
+        <HQChart @ready="onReady" :default-theme="theme" />
     </div>
 </template>
 
 <script lang="ts" setup>
-import { shallowRef, computed } from 'vue'
+import { shallowRef, computed, PropType } from 'vue'
 import { Chart } from 'hqchart'
 import { timerInterceptor } from '@/utils/timer'
 import { handleNumberValue, changeUnit } from '@/filters'
+import { AppTheme } from '@/constants/theme'
 import { useFuturesStore, i18n } from '@/stores'
 import { useDataset } from '@/hooks/hqchart/timeline/dataset'
 import { MinuteChartContainer, NetworkFilterData, NetworkFilterCallback } from '@/hooks/hqchart/timeline/types'
@@ -22,6 +23,9 @@ const props = defineProps({
     goodsCode: {
         type: String,
         required: true
+    },
+    theme: {
+        type: String as PropType<keyof typeof AppTheme>
     }
 })
 

+ 7 - 2
src/services/api/bank/index.ts

@@ -1,6 +1,7 @@
+import { v4 } from 'uuid'
+import { RequestConfig } from '@/services/http/types'
 import { useLoginStore, useAccountStore } from '@/stores'
 import http from '@/services/http'
-import { RequestConfig } from '@/services/http/types'
 
 const loginStore = useLoginStore()
 const accountStore = useAccountStore()
@@ -120,7 +121,11 @@ export function YJF_GetWithholdSignInSMSVCode(config: RequestConfig<Partial<Prot
  */
 export function DigitalAccountTransferApply(config: RequestConfig<Partial<Proto.DigitalAccountTransferApplyReq>>) {
     return http.mqRequest<Proto.DigitalAccountTransferApplyRsp>({
-        data: config.data,
+        data: {
+            OperateSrc: 2,
+            ClientTicket: v4(),
+            ...config.data
+        },
         requestCode: 'DigitalAccountTransferApplyReq',
         responseCode: 'DigitalAccountTransferApplyRsp',
     })

+ 3 - 0
src/services/api/digital/index.ts

@@ -1,5 +1,6 @@
 import { v4 } from 'uuid'
 import { formatDate } from '@/filters'
+import { ClientType, OrderSrc } from '@/constants/client'
 import { RequestConfig } from '@/services/http/types'
 import { useLoginStore } from '@/stores'
 import http from '@/services/http'
@@ -65,6 +66,8 @@ export function digitalOrder(config: RequestConfig<Partial<Proto.DigitalOrderReq
     return http.mqRequest<Proto.DigitalOrderRsp>({
         data: {
             ClientSerialNo: v4(),
+            ClientType: ClientType.Web,
+            OrderSrc: OrderSrc.ORDERSRC_CLIENT,
             LoginID: loginStore.loginId,
             OperatorID: loginStore.loginId,
             ClientOrderTime: formatDate(new Date().toISOString()),

+ 11 - 2
src/services/api/trade/index.ts

@@ -1,6 +1,7 @@
 import { v4 } from 'uuid'
+import { formatDate } from '@/filters'
 import { RequestConfig } from '@/services/http/types'
-import { ClientType } from '@/constants/client'
+import { ClientType,OrderSrc } from '@/constants/client'
 import { useLoginStore, useAccountStore } from '@/stores'
 import http from '@/services/http'
 
@@ -178,7 +179,15 @@ export function spotPresalePointPrice(config: RequestConfig<Proto.SpotPresalePoi
  */
 export function cancelOrder(config: RequestConfig<Proto.CancelOrderReq>) {
     return http.mqRequest<Proto.CancelOrderRsp>({
-        data: config.data,
+        data: {
+            AccountID: accountStore.currentAccountId,
+            OperatorID: loginStore.loginId,
+            OrderSrc: OrderSrc.ORDERSRC_CLIENT,
+            ClientSerialNo: v4(),
+            ClientType: ClientType.Web,
+            ClientOrderTime: formatDate(new Date().toISOString()),
+            ...config.data
+        },
         requestCode: 'CancelOrderReq',
         responseCode: 'CancelOrderRsp'
     })

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

@@ -19,6 +19,8 @@ export enum EventName {
     PosChangedNtf, // 头寸变化通知
     RiskControlNtf, // 风控通知
     RiskToWebNtf, // 风控消息管理端通知客户端
+    DigitalAccountChangedNtf, // 数字账户变化通知
+    DigitalAccountFundsChangedNtf, // 数字账户资金变化通知
 }
 
 /**

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

@@ -119,6 +119,20 @@ export async function pushMessage50(pkg: Package50, contentType: 'encrypted' | '
             }, delay, funCode.toString())
             break
         }
+        case FunCode.DigitalAccountFundsChangedNtf: {
+            timerInterceptor.debounce(() => {
+                // 数字账户资金变化通知
+                eventBus.$emit('DigitalAccountFundsChangedNtf')
+            }, delay, funCode.toString())
+            break
+        }
+        case FunCode.DigitalAccountChangedNtf: {
+            timerInterceptor.debounce(() => {
+                // 数字账户变化通知
+                eventBus.$emit('DigitalAccountChangedNtf')
+            }, delay, funCode.toString())
+            break
+        }
         default: {
             if (funCode) {
                 console.warn('接收到未定义的通知', funCode)

+ 3 - 2
src/types/proto/bank.d.ts

@@ -322,12 +322,13 @@ declare global {
             AccountID: number; // 交易账号
             UserID: number; // 用户ID
             CurrencyID: number; // 币种ID
-            DigitalTransferType: number; // 划转类型-枚举"digitaltransfertype"(3:转入
+            DigitalTransferType: number; // 划转类型-枚举"digitaltransfertype"(3:转入,4:转出)
             Amount: number; // 转入转出金额
+            OperateSrc: number; // 操作来源-枚举"operatesrc"(1:管理端,2:终端)
             ClientTicket: string; // 客户端流水号
             Remark: string; // 备注
         }
-        
+
         // 数字账户转入转出申请应答
         interface DigitalAccountTransferApplyRsp {
             Header?: IMessageHead;  // 消息头