li.shaoyi il y a 3 ans
Parent
commit
a6658f5e0c
44 fichiers modifiés avec 971 ajouts et 453 suppressions
  1. 39 39
      public/proto/mtp.proto
  2. 47 46
      src/business/common/index.ts
  3. 3 3
      src/business/goods/index.ts
  4. 27 0
      src/business/search/index.ts
  5. 0 1
      src/business/sign/index.ts
  6. 8 1
      src/business/table/index.ts
  7. 97 0
      src/business/warehouse/index.ts
  8. 12 4
      src/constants/enum/funcode.ts
  9. 27 1
      src/mock/router.ts
  10. 1 1
      src/packages/mobile/views/boot/index.vue
  11. 16 1
      src/packages/pc/App.vue
  12. 13 11
      src/packages/pc/assets/themes/default/default.less
  13. 48 20
      src/packages/pc/views/boot/index.vue
  14. 12 0
      src/packages/pc/views/listing/buy/components/add/index.vue
  15. 31 0
      src/packages/pc/views/listing/buy/components/details/index.vue
  16. 13 1
      src/packages/pc/views/listing/buy/index.vue
  17. 66 12
      src/packages/pc/views/search/diamonds/index.vue
  18. 8 0
      src/packages/pc/views/search/index.vue
  19. 3 8
      src/packages/pc/views/sign/login/index.vue
  20. 4 3
      src/packages/pc/views/warehousing/stock/components/edit/diamonds.vue
  21. 1 1
      src/packages/pc/views/warehousing/stock/components/edit/index.vue
  22. 1 0
      src/packages/pc/views/warehousing/stock/index.vue
  23. 76 0
      src/packages/pc/views/warehousing/warehouse/components/edit/index.vue
  24. 22 1
      src/packages/pc/views/warehousing/warehouse/index.vue
  25. 0 7
      src/services/api/common/index.ts
  26. 18 0
      src/services/api/warehouse/index.ts
  27. 4 5
      src/services/index.ts
  28. 43 19
      src/services/socket/index.ts
  29. 5 5
      src/services/socket/quote/build/decode.ts
  30. 2 2
      src/services/socket/quote/build/encode.ts
  31. 1 1
      src/services/socket/quote/index.ts
  32. 2 2
      src/services/socket/quote/interface.ts
  33. 3 3
      src/services/socket/trade/index.ts
  34. 23 203
      src/services/subscribe/index.ts
  35. 0 10
      src/stores/index.ts
  36. 8 0
      src/stores/modules/account.ts
  37. 185 2
      src/stores/modules/futures.ts
  38. 14 0
      src/stores/modules/storage.ts
  39. 3 0
      src/types/ermcp/warehouse.d.ts
  40. 7 0
      src/types/proto/account.d.ts
  41. 1 1
      src/types/proto/goods.d.ts
  42. 3 3
      src/types/proto/quote.d.ts
  43. 30 0
      src/types/proto/warehouse.d.ts
  44. 44 36
      src/utils/websocket/index.ts

+ 39 - 39
public/proto/mtp.proto

@@ -2153,45 +2153,45 @@ message ChannelOrderRsp {
 // 钻石属性
 message GZWRStandardExInfo {
 	optional uint32 ZSCategory = 1; // 钻石分类,必填
-	optional uint32 ZSCurrencyType = 2; // 货币类型,必填
-	optional string GoodsNo = 3; // 商品编号,必填
-	optional uint64 WarehouseID = 4; // 仓库ID,必填
-	optional double MarketPrice = 5; // 市场价,两位小数
-	optional string ImagePath = 6; // 商品照片
-	optional string WRPath = 7; // 仓单扫描件
-	optional string Remark = 8; // 备注
-	optional double Price = 9; // 总价(价格),两位小数
-	optional double Weight = 10; // 总重量(克拉重量),两位小数
-	optional double WeightAvg = 11; // 平均单颗重量
-	repeated uint32 ZSSharpType = 12; // 形状,1个或多个形状
-	optional uint32 ZSColorType1 = 13; // 颜色1
-	optional uint32 ZSColorType2 = 14; // 颜色2
-	optional uint32 ZSClarityType1 = 15; // 净度1
-	optional uint32 ZSClarityType2 = 16; // 净度2
-	optional uint32 ZSCutType1 = 17; // 切工1
-	optional uint32 ZSCutType2 = 18; // 切工2
-	optional uint32 ZSSymmetryType1 = 19; // 对称度1
-	optional uint32 ZSSymmetryType2 = 20; // 对称度2
-	optional uint32 ZSPolishType1 = 21; // 抛光度1
-	optional uint32 ZSPolishType2 = 22; // 抛光度2
-	optional uint32 ZSFluorescenceType1 = 23; // 荧光1
-	optional uint32 ZSFluorescenceType2 = 24; // 荧光2
-	optional double Size1 = 25; // 尺寸1
-	optional double Size2 = 26; // 尺寸2
-	optional double Size3 = 27; // 尺寸3
-	optional uint32 ZSCertType = 28; // 证书类型
-	optional string CerNo = 29; // 证书编号
-	repeated uint32 ZSCrystalType = 30; // 晶型范围
-	optional string CPCertNo = 31; // 金伯利证书编号
-	optional string Origin = 32; // 原产地
-	optional string KPWeight = 33; // 金伯利证书重量
-	optional string KPPath = 34; // 金伯利证书图片
-	optional uint32 ZSStyleType = 35; // 款式
-	optional string StoneDesc = 36; // 配石描述
-	optional string SettingMaterial = 37; // 镶嵌材料
-	optional uint32 ZSCZColor1Type = 38; // 彩钻颜色1
-	optional uint32 ZSCZColor2Type = 39; // 彩钻颜色2
-	optional uint32 ZSCZColor3Type = 40; // 彩钻颜色3
+		optional uint32 ZSCurrencyType = 2; // 货币类型,必填
+		optional string GoodsNo = 3; // 商品编号,必填
+		optional uint64 WarehouseID = 4; // 仓库ID,必填
+		optional double MarketPrice = 5; // 市场价,两位小数
+		optional string ImagePath = 6; // 商品照片
+		optional string WRPath = 7; // 仓单扫描件
+		optional string Remark = 8; // 备注
+		optional double Price = 9; // 总价(价格),两位小数
+		optional double Weight = 10; // 总重量(克拉重量),两位小数
+		optional double WeightAvg = 11; // 平均单颗重量
+		repeated uint32 ZSShapeType = 12; // 形状,1个或多个形状
+		optional uint32 ZSColorType1 = 13; // 颜色1
+		optional uint32 ZSColorType2 = 14; // 颜色2
+		optional uint32 ZSClarityType1 = 15; // 净度1
+		optional uint32 ZSClarityType2 = 16; // 净度2
+		optional uint32 ZSCutType1 = 17; // 切工1
+		optional uint32 ZSCutType2 = 18; // 切工2
+		optional uint32 ZSSymmetryType1 = 19; // 对称度1
+		optional uint32 ZSSymmetryType2 = 20; // 对称度2
+		optional uint32 ZSPolishType1 = 21; // 抛光度1
+		optional uint32 ZSPolishType2 = 22; // 抛光度2
+		optional uint32 ZSFluorescenceType1 = 23; // 荧光1
+		optional uint32 ZSFluorescenceType2 = 24; // 荧光2
+		optional double Size1 = 25; // 尺寸1
+		optional double Size2 = 26; // 尺寸2
+		optional double Size3 = 27; // 尺寸3
+		optional uint32 ZSCertType = 28; // 证书类型
+		optional string CerNo = 29; // 证书编号
+		repeated uint32 ZSCrystalType = 30; // 晶型范围
+		optional string CPCertNo = 31; // 金伯利证书编号
+		optional string Origin = 32; // 原产地
+		optional string KPWeight = 33; // 金伯利证书重量
+		optional string KPPath = 34; // 金伯利证书图片
+		optional uint32 ZSStyleType = 35; // 款式
+		optional string StoneDesc = 36; // 配石描述
+		optional string SettingMaterial = 37; // 镶嵌材料
+		optional uint32 ZSCZColor1Type = 38; // 彩钻颜色1
+		optional uint32 ZSCZColor2Type = 39; // 彩钻颜色2
+		optional uint32 ZSCZColor3Type = 40; // 彩钻颜色3
 }
 
 // 新增钻石商品接口请求

+ 47 - 46
src/business/common/index.ts

@@ -1,63 +1,49 @@
 import { timerTask } from '@/utils/timer'
-import { commonStore, sessionData, futuresStore, resetStore, accountStore } from '@/stores'
+import { commonStore, accountStore, futuresStore, sessionData } from '@/stores'
 import { tokenCheck } from '@/services/api/account'
-import service from '@/services'
 import eventBus from '@/services/bus'
 import socket from '@/services/socket'
 
-export const business = new (class {
-    logoutNotify
-    moneyChangedNotify
-
-    constructor() {
-        // 接收用户登出通知
-        this.logoutNotify = eventBus.$on('LogoutNotify', () => {
-            socket.closeQuoteServer()
-            socket.closeTradeServer()
-            timerTask.clearAll()
-            resetStore()
-        })
-
-        // 接收资金变动通知
-        this.moneyChangedNotify = eventBus.$on('MoneyChangedNotify', () => {
-            accountStore.getAccountList()
-        })
-    }
-})
+/**
+ * 退出登录
+ */
+export function logout(callback?: () => void) {
+    socket.closeAll()
+    timerTask.clearAll()
+    sessionData.reset()
+    accountStore.reset()
+    callback && callback()
+}
 
 /**
  * 初始化业务数据
  * @param callback 
  */
 export async function initBaseData(callback?: () => void) {
-    await service.onReady(async () => {
-        if (sessionData.getLoginInfo('Token')) {
-            // 连接交易服务
-            socket.connectTrade()
-            await checkToken()
+    if (sessionData.getLoginInfo('Token')) {
+        checkTokenLoop()
 
-            const asyncTask = [
-                commonStore.getLoginData(),
-                futuresStore.getGoodsList(),
-                sessionData.getUserMenuList(),
-                sessionData.getAllEnumList(),
-                sessionData.getTableColumnList(),
-            ]
+        const asyncTask = [
+            commonStore.getLoginData(),
+            futuresStore.getGoodsList(),
+            sessionData.getUserMenuList(),
+            sessionData.getAllEnumList(),
+            sessionData.getTableColumnList(),
+        ]
 
-            await Promise.all(asyncTask).then(() => {
-                accountStore.getAccountList()
-                callback && callback()
-            }).catch(() => {
-                return Promise.reject('初始化失败')
-            })
-        } else {
+        await Promise.all(asyncTask).then(() => {
+            accountStore.getAccountList()
             callback && callback()
-        }
-    })
+        }).catch(() => {
+            return Promise.reject('初始化失败')
+        })
+    } else {
+        callback && callback()
+    }
 }
 
 /**
- * Token 效验
+ * 令牌效验
  */
 export function checkToken() {
     const { LoginID, Token } = sessionData.getValue('loginInfo')
@@ -66,8 +52,23 @@ export function checkToken() {
             LoginID,
             Token,
         },
-        fail: () => {
-            // 失败应该退到登录页面
-        }
+        fail: () => eventBus.$emit('LogoutNotify')
     })
+}
+
+/**
+ * 轮询效验令牌
+ */
+export function checkTokenLoop() {
+    const delay = 1 * 60 * 1000 // 每1分钟效验一次令牌
+    timerTask.setTimeout(() => {
+        checkToken().then(() => checkTokenLoop())
+    }, delay, 'checkToken')
+}
+
+/**
+ * 停止令牌效验
+ */
+export function stopCheckToken() {
+    timerTask.clearTimeout('checkToken')
 }

+ 3 - 3
src/business/goods/index.ts

@@ -1,7 +1,7 @@
 import { shallowRef, reactive, computed } from 'vue'
 import { v4 } from 'uuid'
 import { formatDecimal } from '@/filters'
-import { queryWarehouseInfo } from '@/services/api/common'
+import { queryWarehouseInfo } from '@/services/api/warehouse'
 import { addZSGoods } from '@/services/api/goods'
 import {
     Category,
@@ -76,10 +76,10 @@ export function useGoodsForm(category: Category) {
         data: {
             userid: loginUserId,
             status: 1,
+            isincludeexchange: true,
         },
         success: (res) => {
-            // 交易中心的仓库(areauserid is null OR  areauserid = 1 ) + 自己的仓库 ( areauserid = 【登录用户ID】 )
-            warehouseList.value = res.data.filter((e) => !e.areauserid || e.areauserid === 1 || e.areauserid === loginUserId)
+            warehouseList.value = res.data
         }
     })
 

+ 27 - 0
src/business/search/index.ts

@@ -0,0 +1,27 @@
+import {
+    getCurrencyTypeList,
+    getShapeTypeList,
+    getColorTypeList,
+    getCutTypeList,
+    getClarityTypeList,
+    getPolishTypeList,
+    getSymmetryTypeList,
+    getFluorescenceTypeList,
+} from '@/constants/enum/diamond'
+
+export function useSearchForm() {
+    const enums = {
+        currencyTypeList: getCurrencyTypeList(),
+        shapeTypeList: getShapeTypeList(),
+        colorTypeList: getColorTypeList(),
+        cutTypeList: getCutTypeList(),
+        clarityTypeList: getClarityTypeList(),
+        polishTypeList: getPolishTypeList(),
+        symmetryTypeList: getSymmetryTypeList(),
+        fluorescenceTypeList: getFluorescenceTypeList(),
+    }
+
+    return {
+        enums
+    }
+}

+ 0 - 1
src/business/sign/index.ts

@@ -41,7 +41,6 @@ export function useSign() {
                         success: (res) => {
                             sessionData.setValue('loginInfo', res);
                             initBaseData(() => {
-                                loading.value = false;
                                 resolve(res);
                             }).catch((err) => {
                                 loading.value = false;

+ 8 - 1
src/business/table/index.ts

@@ -1,7 +1,7 @@
 import { shallowRef } from 'vue'
 import { sessionData } from '@/stores'
 
-export function useTableColumns(tableKey: string) {
+export function useTableColumns(tableKey: string, operateColumn = true) {
     const tableColumns = sessionData.getValue('tableColumns')
 
     const columns = shallowRef(tableColumns.reduce((res, cur) => {
@@ -15,6 +15,13 @@ export function useTableColumns(tableKey: string) {
                     fixed: e.fixed,
                 })
             })
+            if (operateColumn) {
+                res.push({
+                    prop: 'operate',
+                    label: '操作',
+                    show: true,
+                })
+            }
         }
         return res
     }, [] as Ermcp.TableColumn[]))

+ 97 - 0
src/business/warehouse/index.ts

@@ -0,0 +1,97 @@
+import { shallowRef, reactive } from 'vue'
+import { useDataTable } from '@/hooks/datatable'
+import { queryWarehouseInfo, warehouseApply } from '@/services/api/warehouse'
+import { sessionData } from '@/stores'
+//import { useTableColumns } from '../table'
+
+export function useWarehouse() {
+    const { dataList } = useDataTable<Ermcp.WarehouseInfoRsp>()
+    const loading = shallowRef(false)
+
+    const columns = shallowRef<Ermcp.TableColumn[]>([
+        {
+            prop: 'warehousecode',
+            label: '仓库代码',
+            show: true,
+        },
+        {
+            prop: 'warehousename',
+            label: '仓库名称',
+            show: true,
+        },
+        {
+            prop: 'address',
+            label: '仓库地址',
+            show: true,
+        },
+        {
+            prop: 'contactname',
+            label: '联系人',
+            show: true,
+        },
+        {
+            prop: 'contactnum',
+            label: '联系电话',
+            show: true,
+        },
+        {
+            prop: 'createtime',
+            label: '创建时间',
+            show: true,
+        },
+        {
+            prop: 'operate',
+            label: '操作',
+            show: true,
+        }
+    ])
+
+    const getWarehouseList = () => {
+        loading.value = true
+        return queryWarehouseInfo({
+            data: {
+                userid: sessionData.getLoginInfo('UserID'),
+            },
+            success: (res) => {
+                dataList.value = res.data
+            },
+            complete: () => {
+                loading.value = false
+            }
+        })
+    }
+
+    return {
+        dataList,
+        columns,
+        getWarehouseList,
+    }
+}
+
+export function useWarehouseForm(selectedRow?: Ermcp.WarehouseInfoRsp) {
+    const loading = shallowRef(false)
+    const formItem = reactive<Proto.WarehouseApplyReq>({
+        type: selectedRow ? 2 : 1,
+        userid: sessionData.getLoginInfo('UserID'),
+        warehousecode: '',
+        warehousename: '',
+        warehousetype: 0,
+        contactname: '',
+        contactnum: '',
+    })
+
+    const warehouseOperate = () => {
+        return warehouseApply({
+            data: formItem,
+            complete: () => {
+                loading.value = false
+            }
+        })
+    }
+
+    return {
+        loading,
+        formItem,
+        warehouseOperate,
+    }
+}

+ 12 - 4
src/constants/enum/funcode.ts

@@ -6,10 +6,6 @@ export enum FunCode {
     QueryCommonRsp = 17104901, // 通用查询应答(17104901)
     QueryCommonSenReq = 17104952, // 加密信息通用查询请求
     QueryCommonSenRsp = 17104953, // 加密信息通用查询应答
-    LoginReq = 65537, // 用户登录请求
-    LoginRsp = 65538, // 用户登录应答
-    TokenCheckReq = 65541, // 用户令牌校验请求
-    TokenCheckRsp = 65542, // 用户令牌校验应答
 
     // 交易通知
     MoneyChangedNotify = 131076, // 资金变化通知
@@ -21,6 +17,18 @@ export enum FunCode {
     QueryQuoteReq = 0x22, // 盘面查询请求
     QueryQuoteRsp = 0x23, // 盘面查询应答
 
+    // --------  账户操作相关接口 -----------
+    LoginReq = 65537, // 用户登录请求
+    LoginRsp = 65538, // 用户登录应答
+    LogoutReq = 65539, // 用户登出请求
+    LogoutRsp = 65540, // 用户登出应答
+    TokenCheckReq = 65541, // 用户令牌校验请求
+    TokenCheckRsp = 65542, // 用户令牌校验应答
+
     AddZSGoodsReq = 1441827, // 新增钻石商品接口请求
     AddZSGoodsRsp = 1441828, // 新增钻石商品接口响应
+
+    // 仓库信息
+    WarehouseApplyReq = 1900685,     /// 仓库申请请求
+    WarehouseApplyRsp = 1900686,    /// 仓库申请响应
 }

+ 27 - 1
src/mock/router.ts

@@ -64,6 +64,24 @@ const appmenu = {
                         url: '',
                         urlType: 1,
                         component: 'views/listing/buy/index.vue',
+                        children: [
+                            {
+                                authType: 3,
+                                title: '我要求购',
+                                code: 'listing_buy_add',
+                                component: 'views/listing/buy/components/add/index.vue',
+                                buttonName: 'add',
+                                buttonType: 'primary',
+                            },
+                            {
+                                authType: 3,
+                                title: '求购信息',
+                                code: 'listing_buy_details',
+                                component: 'views/listing/buy/components/details/index.vue',
+                                buttonName: 'details',
+                                buttonType: 'primary',
+                            },
+                        ]
                     },
                     {
                         authType: 1,
@@ -137,9 +155,17 @@ const appmenu = {
                                 authType: 3,
                                 title: '新增仓库',
                                 code: 'warehousing_warehouse_add',
-                                component: 'views/warehouse/warehouse/components/edit/index.vue',
+                                component: 'views/warehousing/warehouse/components/edit/index.vue',
                                 buttonName: 'add',
                                 buttonType: 'primary',
+                            },
+                            {
+                                authType: 3,
+                                title: '修改仓库',
+                                code: 'warehousing_warehouse_edit',
+                                component: 'views/warehousing/warehouse/components/edit/index.vue',
+                                buttonName: 'edit',
+                                buttonType: 'primary',
                             }
                         ]
                     },

+ 1 - 1
src/packages/mobile/views/boot/index.vue

@@ -45,7 +45,7 @@ const initService = () => {
   state.isError = false;
   // 模拟等待初始化
   window.setTimeout(() => {
-    service.onReady(() => state.loading = false).catch(() => state.isError = true)
+    service.onReady().then(() => state.loading = false).catch(() => state.isError = true)
   }, 1000);
 }
 

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

@@ -13,13 +13,28 @@ export default {
 
 <script lang="ts" setup>
 import { ref, watch } from 'vue'
-import { useRoute } from 'vue-router'
+import { useRouter, useRoute } from 'vue-router'
+import { ElMessageBox } from 'element-plus'
+import { logout } from '@/business/common'
 import { sessionData } from '@/stores'
 import zhCn from 'element-plus/lib/locale/lang/zh-cn'
+import eventBus from '@/services/bus'
 
 const route = useRoute()
+const router = useRouter()
 const isReady = ref(false)
 
+// 接收用户登出通知
+eventBus.$on('LogoutNotify', (msg) => {
+  logout(() => {
+    if (msg) {
+      ElMessageBox.alert(msg as string)
+    }
+    isReady.value = false
+    router.replace({ name: 'login' })
+  })
+})
+
 watch(() => route.name, (routeName) => {
   if (!isReady.value) {
     const token = sessionData.getLoginInfo('Token')

+ 13 - 11
src/packages/pc/assets/themes/default/default.less

@@ -30,6 +30,19 @@
     --sidebar-menu-item-active: #278eff;
 
     .el-form {
+        &-item {
+            &--col {
+                display: flex;
+                width  : 100%;
+
+                >* {
+                    &:not(:first-child) {
+                        margin-left: 10px;
+                    }
+                }
+            }
+        }
+
         &--horizontal {
             display  : flex;
             flex-wrap: wrap;
@@ -49,17 +62,6 @@
                 &--row {
                     width: 100%;
                 }
-
-                &--col {
-                    display: flex;
-                    width  : 100%;
-
-                    >* {
-                        &:not(:first-child) {
-                            margin-left: 10px;
-                        }
-                    }
-                }
             }
 
             .el-select {

+ 48 - 20
src/packages/pc/views/boot/index.vue

@@ -1,34 +1,62 @@
 <template>
   <div class="boot">
-    <el-button :loading="loading" v-if="loading">正在烧烤...</el-button>
-    <div v-else>烧烤失败</div>
+    <el-button :loading="loading" v-if="loading">正在初始化...</el-button>
+    <el-button @click="initService" v-else>重试</el-button>
   </div>
 </template>
 
 <script lang="ts" setup>
 import { ref } from 'vue'
+import { ElMessage } from 'element-plus'
 import { useRoute, useRouter } from 'vue-router'
-import { initBaseData } from '@/business/common'
+import { initBaseData, checkToken } from '@/business/common'
+import { sessionData } from '@/stores'
 import service from '@/services'
+import eventBus from '@/services/bus'
+import socket from '@/services/socket'
 
-const route = useRoute(),
-  router = useRouter(),
-  loading = ref(true);
-
-service.onReady(() => {
-  initBaseData(() => {
-    const redirect = route.query.redirect;
-    if (redirect) {
-      router.replace(redirect.toString());
-    } else {
-      router.replace('/');
-    }
-  }).catch(() => {
-    loading.value = false;
+const route = useRoute()
+const router = useRouter()
+const loading = ref(false)
+
+const initService = async () => {
+  loading.value = true
+
+  // 等待服务初始化
+  await service.onReady().catch((err) => {
+    ElMessage.error(err)
+    loading.value = false
   })
-}).catch(() => {
-  loading.value = false;
-})
+
+  if (sessionData.getLoginInfo('Token')) {
+    // 等待连接交易服务
+    await socket.connectTrade().catch((err) => {
+      ElMessage.error(err)
+      loading.value = false
+    })
+
+    // 等待令牌效验
+    await checkToken().catch(() => {
+      eventBus.$emit('LogoutNotify')
+    })
+
+    // 等待业务数据初始化
+    await initBaseData().catch((err) => {
+      ElMessage.error(err)
+      loading.value = false
+    })
+  }
+
+  // 路由跳转
+  const redirect = route.query.redirect
+  if (redirect) {
+    router.replace(redirect.toString())
+  } else {
+    router.replace('/')
+  }
+}
+
+initService()
 </script>
 
 <style lang="less" scoped>

+ 12 - 0
src/packages/pc/views/listing/buy/components/add/index.vue

@@ -0,0 +1,12 @@
+<template>
+    <app-drawer class="listing-buy-details" title="我要求购" width="680" v-model:show="show">
+        求购
+    </app-drawer>
+</template>
+
+<script lang="ts" setup>
+import { ref } from 'vue'
+import AppDrawer from '@pc/components/base/drawer/index.vue'
+
+const show = ref(true)
+</script>

+ 31 - 0
src/packages/pc/views/listing/buy/components/details/index.vue

@@ -0,0 +1,31 @@
+<template>
+    <app-drawer class="listing-buy-details" title="求购详情" width="680" v-model:show="show">
+        <el-descriptions :column="2" style="padding:20px">
+            <el-descriptions-item label="委托单号"></el-descriptions-item>
+            <el-descriptions-item label="买方"></el-descriptions-item>
+            <el-descriptions-item label="钻石分类"></el-descriptions-item>
+            <el-descriptions-item label="货币类型"></el-descriptions-item>
+            <el-descriptions-item label="尺寸"></el-descriptions-item>
+            <el-descriptions-item label="形状"></el-descriptions-item>
+            <el-descriptions-item label="净度"></el-descriptions-item>
+            <el-descriptions-item label="对称度"></el-descriptions-item>
+            <el-descriptions-item label="颜色"></el-descriptions-item>
+            <el-descriptions-item label="切工"></el-descriptions-item>
+            <el-descriptions-item label="荧光"></el-descriptions-item>
+        </el-descriptions>
+    </app-drawer>
+</template>
+
+<script lang="ts" setup>
+import { ref, PropType } from 'vue'
+import AppDrawer from '@pc/components/base/drawer/index.vue'
+
+defineProps({
+    selectedRow: {
+        type: Object as PropType<Ermcp.GoodsRsp>,
+        default: () => ({})
+    }
+})
+
+const show = ref(true)
+</script>

+ 13 - 1
src/packages/pc/views/listing/buy/index.vue

@@ -1,10 +1,16 @@
 <template>
-    <app-view class="market-quote">
+    <app-view>
         <template #header>
             <app-filter v-bind="{ selectList, inputList, buttonList }" />
         </template>
         <!-- 表格数据 -->
         <app-table :data="dataList" v-model:columns="columns" :row-key="rowKey" border>
+            <template #header>
+                <app-auth-operation :menus="['add']" />
+            </template>
+            <template #operate="{ row }">
+                <app-auth-operation :menus="['details']" :options="{ selectedRow: row }" linkButton />
+            </template>
             <template #footer>
                 <app-pagination :total="400" />
             </template>
@@ -17,6 +23,7 @@ import { ref } from 'vue'
 import { useDataTable } from '@/hooks/datatable'
 import { queryGoodsList } from '@/services/api/goods'
 import { useTable } from '@pc/components/base/table'
+import AppAuthOperation from '@pc/components/modules/auth-operation/index.vue'
 import AppTable from '@pc/components/base/table/index.vue'
 import AppPagination from '@pc/components/base/pagination/index.vue'
 import AppFilter from '@pc/components/base/table-filter/index.vue'
@@ -62,6 +69,11 @@ const columns = ref([
         prop: 'lastPrice',
         label: '最新价',
         show: true,
+    },
+    {
+        prop: 'operate',
+        label: '操作',
+        show: true,
     }
 ])
 

+ 66 - 12
src/packages/pc/views/search/diamonds/index.vue

@@ -1,18 +1,54 @@
 <template>
     <app-view>
-        <el-form label-width="100px" label-position="left" style="max-width: 600px">
+        <el-form label-width="80px">
             <el-form-item label="形状">
-                <el-radio-group>
-                    <el-radio label="椭圆形" />
-                    <el-radio label="心形" />
-                </el-radio-group>
+                <el-checkbox-group>
+                    <el-checkbox :label="item.value" v-for="(item, index) in enums.shapeTypeList" :key="index">
+                        {{ item.label }}
+                    </el-checkbox>
+                </el-checkbox-group>
             </el-form-item>
             <el-form-item label="颜色">
-                <el-radio-group>
-                    <el-radio label="D" />
-                    <el-radio label="E" />
-                    <el-radio label="F" />
-                </el-radio-group>
+                <el-checkbox-group>
+                    <el-checkbox :label="item.value" v-for="(item, index) in enums.colorTypeList" :key="index">
+                        {{ item.label }}
+                    </el-checkbox>
+                </el-checkbox-group>
+            </el-form-item>
+            <el-form-item label="净度">
+                <el-checkbox-group>
+                    <el-checkbox :label="item.value" v-for="(item, index) in enums.clarityTypeList" :key="index">
+                        {{ item.label }}
+                    </el-checkbox>
+                </el-checkbox-group>
+            </el-form-item>
+            <el-form-item label="切工">
+                <el-checkbox-group>
+                    <el-checkbox :label="item.value" v-for="(item, index) in enums.cutTypeList" :key="index">
+                        {{ item.label }}
+                    </el-checkbox>
+                </el-checkbox-group>
+            </el-form-item>
+            <el-form-item label="对称">
+                <el-checkbox-group>
+                    <el-checkbox :label="item.value" v-for="(item, index) in enums.symmetryTypeList" :key="index">
+                        {{ item.label }}
+                    </el-checkbox>
+                </el-checkbox-group>
+            </el-form-item>
+            <el-form-item label="抛光">
+                <el-checkbox-group>
+                    <el-checkbox :label="item.value" v-for="(item, index) in enums.polishTypeList" :key="index">
+                        {{ item.label }}
+                    </el-checkbox>
+                </el-checkbox-group>
+            </el-form-item>
+            <el-form-item label="荧光">
+                <el-checkbox-group>
+                    <el-checkbox :label="item.value" v-for="(item, index) in enums.fluorescenceTypeList" :key="index">
+                        {{ item.label }}
+                    </el-checkbox>
+                </el-checkbox-group>
             </el-form-item>
             <el-form-item label="仓库">
                 <el-select placeholder="请选择">
@@ -20,11 +56,26 @@
                     <el-option label="Zone two" value="beijing" />
                 </el-select>
             </el-form-item>
+            <el-form-item label="货币">
+                <el-checkbox-group>
+                    <el-checkbox :label="item.value" v-for="(item, index) in enums.currencyTypeList" :key="index">
+                        {{ item.label }}
+                    </el-checkbox>
+                </el-checkbox-group>
+            </el-form-item>
             <el-form-item label="总重量">
-                <el-input />
+                <div class="el-form-item--col">
+                    <el-input placeholder="从" style="width:160px" />
+                    <span>-</span>
+                    <el-input placeholder="至" style="width:160px" />
+                </div>
             </el-form-item>
             <el-form-item label="单颗重量">
-                <el-input />
+                <div class="el-form-item--col">
+                    <el-input placeholder="从" style="width:160px" />
+                    <span>-</span>
+                    <el-input placeholder="至" style="width:160px" />
+                </div>
             </el-form-item>
             <el-form-item>
                 <el-button type="primary">搜索</el-button>
@@ -35,4 +86,7 @@
 </template>
 
 <script lang="ts" setup>
+import { useSearchForm } from '@/business/search'
+
+const { enums } = useSearchForm()
 </script>

+ 8 - 0
src/packages/pc/views/search/index.vue

@@ -0,0 +1,8 @@
+<!-- 钻石搜索 -->
+<template>
+    <div></div>
+</template>
+
+<script lang="ts" setup>
+import { getCategoryList, Category } from '@/constants/enum/diamond'
+</script>

+ 3 - 8
src/packages/pc/views/sign/login/index.vue

@@ -12,14 +12,9 @@
         <el-checkbox label="记住账号"></el-checkbox>
       </el-form-item>
       <el-form-item>
-        <el-button class="submit" type="primary" :disabled="loading" @click="userLogin">
-          <template v-if="loading">
-            <i class="el-icon-loading"></i>
-            <span>正在登录</span>
-          </template>
-          <template v-else>
-            <span>登录</span>
-          </template>
+        <el-button class="submit" type="primary" :loading="loading" @click="userLogin">
+          <span v-if="loading">正在登录</span>
+          <span v-else>登录</span>
         </el-button>
         <!-- <el-button>注册</el-button> -->
       </el-form-item>

+ 4 - 3
src/packages/pc/views/warehousing/stock/components/edit/diamonds.vue

@@ -1,3 +1,4 @@
+<!-- 我的库存-成批裸钻 -->
 <template>
     <el-form ref="formRef" class="el-form--horizontal" label-width="100px" label-position="left" :model="formItem"
         :rules="formRules">
@@ -29,8 +30,8 @@
         <el-form-item label="克拉单位">
             <span>{{ currencyInfo?.param2 }}{{ caratUnit }} (总价/总重量)</span>
         </el-form-item>
-        <el-form-item class="el-form-item--row" label="形状" prop="ZSSharpType">
-            <el-checkbox-group v-model="formItem.ZSSharpType">
+        <el-form-item class="el-form-item--row" label="形状" prop="ZSShapeType">
+            <el-checkbox-group v-model="formItem.ZSShapeType">
                 <el-checkbox :label="item.value" v-for="(item, index) in enums.shapeTypeList" :key="index">
                     {{ item.label }}
                 </el-checkbox>
@@ -165,7 +166,7 @@ const formRules: FormRules = {
     Price: [{ required: true, type: 'number', message: '请输入总价' }],
     Weight: [{ required: true, type: 'number', message: '请输入总重量' }],
     WeightAvg: [{ required: true, type: 'number', message: '请输入平均重量' }],
-    ZSSharpType: [{ required: true, message: '请选择形状' }],
+    ZSShapeType: [{ required: true, message: '请选择形状' }],
     WarehouseID: [{ required: true, message: '请选择仓库' }],
     ZSColorType: [
         {

+ 1 - 1
src/packages/pc/views/warehousing/stock/components/edit/index.vue

@@ -18,7 +18,7 @@ import AppDrawer from '@pc/components/base/drawer/index.vue'
 
 defineProps({
     selectedRow: {
-        type: Object as PropType<Ermcp.AccountRole>,
+        type: Object as PropType<Ermcp.GoodsRsp>,
         default: () => ({})
     }
 })

+ 1 - 0
src/packages/pc/views/warehousing/stock/index.vue

@@ -1,3 +1,4 @@
+<!-- 我的库存 -->
 <template>
     <app-view class="market-quote">
         <template #header>

+ 76 - 0
src/packages/pc/views/warehousing/warehouse/components/edit/index.vue

@@ -0,0 +1,76 @@
+<!-- 我的仓库-编辑仓库 -->
+<template>
+    <el-form ref="formRef" class="el-form--horizontal" label-width="100px" label-position="left" :model="formItem"
+        :rules="formRules">
+        <el-form-item label="仓库代码" prop="warehousecode">
+            <el-input placeholder="请输入" v-model="formItem.warehousecode" />
+        </el-form-item>
+        <el-form-item label="所属机构">
+            <span>机构001</span>
+        </el-form-item>
+        <el-form-item label="仓库名称" prop="warehousename">
+            <el-input placeholder="请输入" v-model="formItem.warehousename" />
+        </el-form-item>
+        <el-form-item label="仓库类型" prop="warehousetype">
+            <span>自有库</span>
+        </el-form-item>
+        <el-form-item label="联系人" prop="contactname">
+            <el-input placeholder="请输入" v-model="formItem.contactname" />
+        </el-form-item>
+        <el-form-item label="联系电话" prop="contactnum">
+            <el-input placeholder="请输入" v-model="formItem.contactnum" />
+        </el-form-item>
+        <el-form-item class="el-form-item--row" label="仓库地址" prop="area">
+
+        </el-form-item>
+        <el-form-item prop="address">
+            <el-input placeholder="请输入" v-model="formItem.address" />
+        </el-form-item>
+        <el-form-item class="el-form-item--row">
+            <el-button :loading="loading" type="primary" @click="onSubmit">提交</el-button>
+            <el-button :loading="loading" @click="onCancel(false)" plain>取消</el-button>
+        </el-form-item>
+    </el-form>
+</template>
+
+<script lang="ts" setup>
+import { ref, PropType } from 'vue'
+import { ElMessage } from 'element-plus'
+import type { FormInstance, FormRules } from 'element-plus'
+import { useWarehouseForm } from '@/business/warehouse'
+
+const props = defineProps({
+    selectedRow: {
+        type: Object as PropType<Ermcp.WarehouseInfoRsp>,
+        default: () => ({})
+    }
+})
+
+const emit = defineEmits(['cancel'])
+const { loading, formItem, warehouseOperate } = useWarehouseForm(props.selectedRow)
+const formRef = ref<FormInstance>()
+
+const formRules: FormRules = {
+    warehousecode: [{ required: true, message: '请输入仓库代码', trigger: 'blur' }],
+    warehousename: [{ required: true, message: '请输入仓库名称', trigger: 'blur' }],
+    contactname: [{ required: true, message: '请输入联系人', trigger: 'blur' }],
+    contactnum: [{ required: true, message: '请输入联系电话', trigger: 'blur' }],
+}
+
+const onCancel = (isRefresh = false) => {
+    emit('cancel', isRefresh)
+}
+
+const onSubmit = () => {
+    formRef.value?.validate((valid) => {
+        if (valid) {
+            warehouseOperate().then(() => {
+                ElMessage.success('提交成功')
+                onCancel(true)
+            }).catch((err) => {
+                ElMessage.error('提交失败:' + err)
+            })
+        }
+    })
+}
+</script>

+ 22 - 1
src/packages/pc/views/warehousing/warehouse/index.vue

@@ -1,6 +1,27 @@
 <template>
-    <app-view>仓库</app-view>
+    <app-view>
+        <app-table :data="dataList" v-model:columns="columns" border>
+            <template #header>
+                <app-auth-operation :menus="['add']" />
+            </template>
+            <template #operate="{ row }">
+                <app-auth-operation :menus="['edit']" :options="{ selectedRow: row }" linkButton />
+            </template>
+            <template #footer>
+                <app-pagination :total="400" />
+            </template>
+        </app-table>
+    </app-view>
 </template>
 
 <script lang="ts" setup>
+import { ElMessage } from 'element-plus'
+import { useWarehouse } from '@/business/warehouse'
+import AppAuthOperation from '@pc/components/modules/auth-operation/index.vue'
+import AppTable from '@pc/components/base/table/index.vue'
+import AppPagination from '@pc/components/base/pagination/index.vue'
+
+const { dataList, columns, getWarehouseList } = useWarehouse()
+
+getWarehouseList().catch((err) => ElMessage.error(err))
 </script>

+ 0 - 7
src/services/api/common/index.ts

@@ -23,13 +23,6 @@ export function queryAllEnums(params: HttpRequest<{ req: Ermcp.EnumReq, rsp: Erm
 }
 
 /**
- * 查询仓库信息
- */
-export function queryWarehouseInfo(params: HttpRequest<{ req: Ermcp.WarehouseInfoReq, rsp: Ermcp.WarehouseInfoRsp[] }>) {
-    return httpRequest('/Ermcp/QueryWarehouseInfo', 'get', params);
-}
-
-/**
  * 获取菜单表数据
  */
 export function queryNewFuncmenu(params: HttpRequest<{ req: Ermcp.NewFuncmenuReq, rsp: Ermcp.NewFuncmenuRsp[] }>) {

+ 18 - 0
src/services/api/warehouse/index.ts

@@ -0,0 +1,18 @@
+import { httpRequest } from '@/services/http'
+import { HttpRequest } from '@/services/http/interface'
+import { tradeServerRequest } from '@/services/socket/trade'
+import { TradeRequest } from '@/services/socket/trade/interface'
+
+/**
+ * 查询仓库信息
+ */
+export function queryWarehouseInfo(params: HttpRequest<{ req: Ermcp.WarehouseInfoReq, rsp: Ermcp.WarehouseInfoRsp[] }>) {
+    return httpRequest('/Guangzuan/QueryWarehouseInfo', 'get', params);
+}
+
+/**
+ * 仓库新增修改
+ */
+export function warehouseApply(params: TradeRequest<Proto.WarehouseApplyReq, Proto.WarehouseApplyRsp>) {
+    return tradeServerRequest('WarehouseApplyReq', 'WarehouseApplyRsp', params);
+}

+ 4 - 5
src/services/index.ts

@@ -45,7 +45,7 @@ export default new (class {
     /**
      * 初始化服务配置
      */
-    private init(): Promise<void> {
+    private init(): Promise<typeof this.config> {
         this.isPending = true;
         return new Promise((resolve, reject) => {
             this.appConfig.then((res) => {
@@ -56,7 +56,7 @@ export default new (class {
                         console.log('服务配置信息', res.data);
                         this.config = res.data as typeof this.config;
                         this.isReady = true;
-                        resolve();
+                        resolve(this.config);
                     },
                     fail: () => {
                         reject('获取服务配置地址失败');
@@ -74,14 +74,13 @@ export default new (class {
 
     /**
      * 服务初始化完成时触发
-     * @param callback 
      */
-    async onReady(callback?: () => void) {
+    onReady() {
         // 初始化失败时重新初始化
         if (!this.isReady && !this.isPending) {
             this.tryInit = this.init();
         }
         // 确保当前只有一个初始化实例
-        await this.tryInit.then(() => callback && callback());
+        return this.tryInit;
     }
 })

+ 43 - 19
src/services/socket/index.ts

@@ -4,6 +4,8 @@ import { Package40, Package50 } from '@/utils/websocket/package'
 import { timerInterceptor } from '@/utils/timer'
 import { FunCode } from '@/constants/enum/funcode'
 import { parseReceivePush } from './quote/build/decode'
+import protobuf from './trade/protobuf'
+import { checkToken, stopCheckToken, checkTokenLoop } from '@/business/common'
 import service from '@/services'
 import eventBus from '@/services/bus'
 
@@ -31,7 +33,8 @@ export default new (class {
             eventBus.$emit('QuoteServerReconnectNotify');
         }
 
-        this.tradeServer.onPush = ({ funCode }) => {
+        this.tradeServer.onPush = (p) => {
+            const { funCode, content } = p;
             const delay = 1000; // 延迟推送消息,防止短时间内重复请求
 
             switch (funCode) {
@@ -42,6 +45,13 @@ export default new (class {
                     }, delay, funCode.toString())
                     break;
                 }
+                case FunCode.LogoutRsp: {
+                    // 通知上层 用户登出
+                    protobuf.responseDecode<Proto.LogoutRsp>(FunCode[funCode], content).then(({ RetCode, RetDesc }) => {
+                        eventBus.$emit('LogoutNotify', (RetDesc || RetCode)?.toString());
+                    })
+                    break;
+                }
                 default: {
                     if (funCode) {
                         console.warn('接收到未定义的通知', funCode);
@@ -49,36 +59,44 @@ export default new (class {
                 }
             }
         }
+
+        this.tradeServer.onBeforeReconnect = () => {
+            // 停止令牌效验
+            stopCheckToken();
+        }
+
+        this.tradeServer.onReconnect = () => {
+            // 重新进行令牌效验
+            checkToken().then(() => checkTokenLoop());
+        }
     }
 
     /** 主动连接行情服务 */
-    connectQuote(callback?: () => void) {
-        service.onReady(() => {
-            const { quoteUrl } = service.config;
-            this.quoteServer.connect(quoteUrl).then(() => {
-                callback && callback();
-            })
-        })
+    async connectQuote() {
+        const res = await service.onReady();
+        await this.quoteServer.connect(res.quoteUrl);
     }
 
     /** 主动连接交易服务 */
-    connectTrade(callback?: () => void) {
-        service.onReady(() => {
-            const { tradeUrl } = service.config;
-            this.tradeServer.connect(tradeUrl).then(() => {
-                callback && callback();
-            })
-        })
+    async connectTrade() {
+        const res = await service.onReady();
+        await this.tradeServer.connect(res.tradeUrl);
     }
 
     /** 向行情服务器发送请求 */
-    sendQuoteServer(msg: SendMessage<Package40>) {
-        this.connectQuote(() => this.quoteServer.send(msg));
+    async sendQuoteServer(msg: SendMessage<Package40>) {
+        await this.connectQuote().catch((err) => {
+            msg.fail && msg.fail(err);
+        })
+        return this.quoteServer.send(msg);
     }
 
     /** 向交易服务器发送请求 */
-    sendTradeServer(msg: SendMessage<Package50>) {
-        this.connectTrade(() => this.tradeServer.send(msg));
+    async sendTradeServer(msg: SendMessage<Package50>) {
+        await this.connectTrade().catch((err) => {
+            msg.fail && msg.fail(err);
+        })
+        return this.tradeServer.send(msg);
     }
 
     /** 主动关闭行情服务 */
@@ -90,4 +108,10 @@ export default new (class {
     closeTradeServer() {
         this.tradeServer.close();
     }
+
+    /** 关闭所有服务 */
+    closeAll() {
+        this.closeQuoteServer();
+        this.closeTradeServer();
+    }
 })

+ 5 - 5
src/services/socket/quote/build/decode.ts

@@ -22,8 +22,8 @@ function byteArrayToUInt(array: Uint8Array, isLittleEndian: boolean): number {
  * 解析行情订阅请求包
  * @param rspPackage
  */
-export function parseSubscribeRsp(content: Uint8Array): Promise<Proto.GoodsQuoteRsp[]> {
-    const result: Proto.GoodsQuoteRsp[] = [];
+export function parseSubscribeRsp(content: Uint8Array): Promise<Proto.QuoteRsp[]> {
+    const result: Proto.QuoteRsp[] = [];
     return new Promise((resolve, reject) => {
         if (content.length === 0) {
             // 有可能空订阅,也有可能订阅返回数据为空,但其实已经订阅了商品
@@ -74,7 +74,7 @@ export function parseSubscribeRsp(content: Uint8Array): Promise<Proto.GoodsQuote
  * @param {Array} quotationData	行情信息
  */
 export function parseReceivePush(quotationData: Uint8Array) {
-    const result: Proto.GoodsQuote[] = [];
+    const result: Proto.Quote[] = [];
 
     // 目前发现可能会传入空数组
     if (quotationData.length) {
@@ -99,7 +99,7 @@ export function parseReceivePush(quotationData: Uint8Array) {
                 const values = low.match(regValue);
 
                 if (keys && values) {
-                    const quote: Proto.GoodsQuote = {};
+                    const quote: Proto.Quote = {};
 
                     for (let i = 0; i < keys.length; i++) {
                         const key = parseInt(keys[i].substring(3, 5), 16);
@@ -127,7 +127,7 @@ export function parseReceivePush(quotationData: Uint8Array) {
  * @param key 
  * @param value 
  */
-function setQuoteTikFieldByByte(quote: Proto.GoodsQuote, key: number, value: Uint8Array) {
+function setQuoteTikFieldByByte(quote: Proto.Quote, key: number, value: Uint8Array) {
     const strValue = String.fromCharCode(...value);
     switch (key) {
         case 0x56:

+ 2 - 2
src/services/socket/quote/build/encode.ts

@@ -5,7 +5,7 @@ import Long from 'long'
  * @param param SubscribeInfoType
  * @returns {Uint8Array}
  */
-function subscribeInfoToByteArrary(param: Proto.GoodsQuoteReq): Uint8Array {
+function subscribeInfoToByteArrary(param: Proto.QuoteReq): Uint8Array {
     const { subState, exchangeCode, goodsCode } = param;
     const dataArray = new Uint8Array(66);
 
@@ -82,7 +82,7 @@ function uIntToByteArray(value: number, isLittleEndian: boolean, length: number)
  * @param token
  * @param loginId 登录id
  */
-export function subscribeListToByteArrary(arr: Proto.GoodsQuoteReq[], token: string, loginId: Long): Uint8Array {
+export function subscribeListToByteArrary(arr: Proto.QuoteReq[], token: string, loginId: Long): Uint8Array {
     const count = arr.length;
     const dataArray = new Uint8Array(76 + 66 * count);
     // 处理登录账号id

+ 1 - 1
src/services/socket/quote/index.ts

@@ -11,7 +11,7 @@ import socket from '../index'
  * 向行情服务器发送请求
  * @param params 
  */
-function quoteServerMiddleware(params: QuoteRequest): Promise<Proto.GoodsQuoteRsp[]> {
+function quoteServerMiddleware(params: QuoteRequest): Promise<Proto.QuoteRsp[]> {
     const { LoginID, Token } = sessionData.getValue('loginInfo');
     const content = subscribeListToByteArrary(params.data, Token, Long.fromNumber(LoginID));
 

+ 2 - 2
src/services/socket/quote/interface.ts

@@ -2,8 +2,8 @@
  * 行情服务请求参数
  */
 export interface QuoteRequest {
-    data: Proto.GoodsQuoteReq[];
-    success?: (res: Proto.GoodsQuoteRsp[]) => void;
+    data: Proto.QuoteReq[];
+    success?: (res: Proto.QuoteRsp[]) => void;
     fail?: (err: { msg: string }) => void;
     complete?: () => void;
 }

+ 3 - 3
src/services/socket/trade/index.ts

@@ -4,7 +4,7 @@ import { FunCode } from '@/constants/enum/funcode'
 import { sessionData } from '@/stores'
 import { IMessageHead } from './protobuf/proto'
 import { TradeRequest, TradeResponse } from './interface'
-import Protobuf from './protobuf'
+import protobuf from './protobuf'
 import socket from '../index'
 
 /**
@@ -37,14 +37,14 @@ function tradeServerMiddleware<Req, Rsp>(reqCode: keyof typeof FunCode, rspCode:
     console.log(reqCode, FunCode[reqCode], params.data);
 
     return new Promise((resolve, reject) => {
-        Protobuf.requestEncode(reqCode, params.data).then((res) => {
+        protobuf.requestEncode(reqCode, params.data).then((res) => {
             // 发送消息
             socket.sendTradeServer({
                 data: {
                     payload: new Package50(FunCode[reqCode], res)
                 },
                 success: (raw) => {
-                    Protobuf.responseDecode<Rsp>(rspCode, raw.content).then((res) => {
+                    protobuf.responseDecode<Rsp>(rspCode, raw.content).then((res) => {
                         console.log(rspCode, FunCode[rspCode], res);
                         resolve(res);
                     }).catch((msg) => {

+ 23 - 203
src/services/subscribe/index.ts

@@ -1,210 +1,28 @@
 import { v4 } from 'uuid'
 import { quoteServerRequest } from '@/services/socket/quote'
-import { futuresStore } from '@/stores'
-import { timerInterceptor } from '@/utils/timer'
-import moment from 'moment'
+import { sessionData } from '@/stores'
 import eventBus from '@/services/bus'
 import socket from '@/services/socket'
 
 /**
- * 订阅通知
+ * 订阅行情
  */
 export default new (class {
     /** 行情订阅列表 */
-    private quoteSubscribeMap = new Map<string, string[]>();
-    private quotes: Proto.GoodsQuote[] = [];
-
-    quotePushNotify;
-    quoteServerReconnectNotify;
+    private quoteSubscribeMap = new Map<string, string[]>()
 
     constructor() {
-        // 接收行情推送通知
-        this.quotePushNotify = eventBus.$on('QuotePushNotify', (res) => {
-            const data = res as Proto.GoodsQuote[]
-            data.forEach((item) => {
-                const index = this.quotes.findIndex((e) => e.goodscode === item.goodscode)
-                if (index > -1) {
-                    this.quotes[index] = item
-                } else {
-                    this.quotes.push(item)
-                }
-            })
-            this.handleQuote()
-        })
-
         // 接收行情服务断线重连成功通知
-        this.quoteServerReconnectNotify = eventBus.$on('QuoteServerReconnectNotify', () => {
+        eventBus.$on('QuoteServerReconnectNotify', () => {
             this.quoteSubscribe();
         })
     }
 
     /**
-     * 处理行情数据
-     */
-    private handleQuote = timerInterceptor.setThrottle(() => {
-        const quoteList = futuresStore.quoteDayList.value
-        this.quotes.forEach((item) => {
-            const quote = quoteList.find((e) => e.goodscode.toUpperCase() === item.goodscode?.toUpperCase())
-            const last = item.last ?? 0
-            const lasttime = (item.date && item.time) ? moment(item.date + item.time, 'YYYYMMDDHHmmss').format('YYYY-MM-DD HH:mm:ss') : ''
-
-            if (quote) {
-                Object.entries(item).forEach(([key, value]) => {
-                    // 只更新存在的属性
-                    if (Reflect.has(quote, key)) {
-                        const prop = key as keyof Ermcp.QuoteDay;
-                        (<K extends typeof prop>(key: K) => quote[key] = value)(prop);
-                    }
-                })
-                // 处理最高最低价
-                if (last) {
-                    if (last > quote.highest) {
-                        quote.highest = last;
-                    }
-                    if (last < quote.lowest) {
-                        quote.lowest = last;
-                    }
-                }
-                // 处理最新时间
-                if (lasttime) {
-                    quote.lasttime = lasttime;
-                }
-            } else {
-                console.warn('行情推送的商品 ' + item.goodscode + ' 缺少盘面信息')
-                quoteList.push({
-                    Lastturnover: 0,
-                    ask: item.ask ?? 0,
-                    ask2: item.ask2 ?? 0,
-                    ask3: item.ask3 ?? 0,
-                    ask4: item.ask4 ?? 0,
-                    ask5: item.ask5 ?? 0,
-                    ask6: 0,
-                    ask7: 0,
-                    ask8: 0,
-                    ask9: 0,
-                    ask10: 0,
-                    askorderid: 0,
-                    askorderid2: 0,
-                    askorderid3: 0,
-                    askorderid4: 0,
-                    askorderid5: 0,
-                    askordervolume: 0,
-                    askordervolume2: 0,
-                    askordervolume3: 0,
-                    askordervolume4: 0,
-                    askordervolume5: 0,
-                    askordervolume6: 0,
-                    askordervolume7: 0,
-                    askordervolume8: 0,
-                    askordervolume9: 0,
-                    askordervolume10: 0,
-                    askqueueinfo: "",
-                    askvolume: item.askvolume ?? 0,
-                    askvolume2: item.askvolume2 ?? 0,
-                    askvolume3: item.askvolume3 ?? 0,
-                    askvolume4: item.askvolume4 ?? 0,
-                    askvolume5: item.askvolume5 ?? 0,
-                    askvolume6: 0,
-                    askvolume7: 0,
-                    askvolume8: 0,
-                    askvolume9: 0,
-                    askvolume10: 0,
-                    averageprice: 0,
-                    bid: item.bid ?? 0,
-                    bid2: item.bid2 ?? 0,
-                    bid3: item.bid3 ?? 0,
-                    bid4: item.bid4 ?? 0,
-                    bid5: item.bid5 ?? 0,
-                    bid6: 0,
-                    bid7: 0,
-                    bid8: 0,
-                    bid9: 0,
-                    bid10: 0,
-                    bidorderid: 0,
-                    bidorderid2: 0,
-                    bidorderid3: 0,
-                    bidorderid4: 0,
-                    bidorderid5: 0,
-                    bidordervolume: 0,
-                    bidordervolume2: 0,
-                    bidordervolume3: 0,
-                    bidordervolume4: 0,
-                    bidordervolume5: 0,
-                    bidordervolume6: 0,
-                    bidordervolume7: 0,
-                    bidordervolume8: 0,
-                    bidordervolume9: 0,
-                    bidordervolume10: 0,
-                    bidqueueinfo: "",
-                    bidvolume: item.bidvolume ?? 0,
-                    bidvolume2: item.bidvolume2 ?? 0,
-                    bidvolume3: item.bidvolume3 ?? 0,
-                    bidvolume4: item.bidvolume4 ?? 0,
-                    bidvolume5: item.bidvolume5 ?? 0,
-                    bidvolume6: 0,
-                    bidvolume7: 0,
-                    bidvolume8: 0,
-                    bidvolume9: 0,
-                    bidvolume10: 0,
-                    calloptionpremiums: item.calloptionpremiums ?? 0,
-                    calloptionpremiums2: item.calloptionpremiums2 ?? 0,
-                    calloptionpremiums3: item.calloptionpremiums3 ?? 0,
-                    calloptionpremiums4: item.calloptionpremiums4 ?? 0,
-                    calloptionpremiums5: item.calloptionpremiums5 ?? 0,
-                    cleartime: 0,
-                    exchangecode: item.exchangecode ?? 0,
-                    exchangedate: item.exchangedate ?? 0,
-                    goodscode: item.goodscode ?? '',
-                    grepmarketprice: 0,
-                    highest: item.highest ?? 0,
-                    holdincrement: 0,
-                    holdvolume: item.holdvolume ?? 0,
-                    iep: 0,
-                    iev: 0,
-                    inventory: item.inventory ?? 0,
-                    iscleared: 0,
-                    issettled: 0,
-                    last,
-                    lastlot: 0,
-                    lasttime,
-                    lastvolume: item.lastvolume ?? 0,
-                    limitdown: item.limitlow ?? 0,
-                    limitup: item.limithigh ?? 0,
-                    lowest: item.lowest ?? 0,
-                    nontotalholdervolume: 0,
-                    nontotallot: 0,
-                    nontotalturnover: 0,
-                    nontotalvolume: 0,
-                    opened: item.opened ?? 0,
-                    opentime: '',
-                    orderid: 0,
-                    preclose: item.preclose ?? 0,
-                    preholdvolume: item.preholdvolume ?? 0,
-                    presettle: item.presettle ?? 0,
-                    publictradetype: '',
-                    putoptionpremiums: item.putoptionpremiums ?? 0,
-                    putoptionpremiums2: item.putoptionpremiums2 ?? 0,
-                    putoptionpremiums3: item.putoptionpremiums3 ?? 0,
-                    putoptionpremiums4: item.putoptionpremiums4 ?? 0,
-                    putoptionpremiums5: item.putoptionpremiums5 ?? 0,
-                    settle: item.settle ?? 0,
-                    strikeprice: 0,
-                    totalaskvolume: 0,
-                    totalbidvolume: 0,
-                    totallot: 0,
-                    totalturnover: item.totalturnover ?? 0,
-                    totalvolume: item.totalvolume ?? 0,
-                    utclasttime: ''
-                })
-            }
-        })
-    }, 200)
-
-    /**
      * 开始行情订阅
      */
-    private quoteSubscribe() {
-        const subscribeData: Proto.GoodsQuoteReq[] = [];
+    private quoteSubscribe = () => {
+        const subscribeData: Proto.QuoteReq[] = []
 
         this.quoteSubscribeMap.forEach((value) => {
             const item = value.map((code) => ({
@@ -212,7 +30,7 @@ export default new (class {
                 exchangeCode: 250,
                 subState: 0
             }))
-            subscribeData.push(...item);
+            subscribeData.push(...item)
         })
 
         if (subscribeData.length) {
@@ -232,7 +50,7 @@ export default new (class {
             })
         } else {
             // 没有订阅商品的时候,断开连接
-            socket.closeQuoteServer();
+            socket.closeQuoteServer()
         }
     }
 
@@ -242,14 +60,14 @@ export default new (class {
      * @param key 
      * @returns 
      */
-    addQuoteSubscribe(goodsCodes: string[], key?: string) {
-        const uuid = key ?? v4();
-        const value = this.quoteSubscribeMap.get(uuid) ?? [];
+    addQuoteSubscribe = (goodsCodes: string[], key?: string) => {
+        const uuid = key ?? v4()
+        const value = this.quoteSubscribeMap.get(uuid) ?? []
 
         const start = () => {
             // 对相同 key 订阅的商品进行合并处理
-            this.quoteSubscribeMap.set(uuid, [...value, ...goodsCodes]);
-            this.quoteSubscribe();
+            this.quoteSubscribeMap.set(uuid, [...value, ...goodsCodes])
+            this.quoteSubscribe()
         }
 
         return {
@@ -258,10 +76,12 @@ export default new (class {
             stop: () => {
                 const flag = this.quoteSubscribeMap.delete(uuid)
                 if (flag) {
-                    console.log('删除订阅', uuid);
+                    console.log('删除订阅', uuid)
+                }
+                if (sessionData.getLoginInfo('Token')) {
+                    this.quoteSubscribe()
                 }
-                this.quoteSubscribe();
-                return flag;
+                return flag
             },
         }
     }
@@ -270,17 +90,17 @@ export default new (class {
      * 删除行情订阅
      * @param keys 
      */
-    removeQuoteSubscribe(...keys: string[]) {
+    removeQuoteSubscribe = (...keys: string[]) => {
         if (keys.length) {
             keys.forEach((key) => {
                 if (this.quoteSubscribeMap.delete(key)) {
-                    console.log('删除订阅', key);
+                    console.log('删除订阅', key)
                 }
             })
         } else {
-            console.log('清空订阅', keys);
-            this.quoteSubscribeMap.clear();
+            console.log('取消订阅')
+            this.quoteSubscribeMap.clear()
         }
-        this.quoteSubscribe();
+        this.quoteSubscribe()
     }
 })

+ 0 - 10
src/stores/index.ts

@@ -3,20 +3,10 @@ import commonStore from './modules/common'
 import accountStore from './modules/account'
 import futuresStore from './modules/futures'
 
-/**
- * 重置数据
- */
-const resetStore = () => {
-    localData.clear()
-    sessionData.clear()
-    accountStore.reset()
-}
-
 export {
     localData,
     sessionData,
     commonStore,
     accountStore,
     futuresStore,
-    resetStore,
 }

+ 8 - 0
src/stores/modules/account.ts

@@ -1,6 +1,7 @@
 import { ref, computed } from 'vue'
 import { queryTaAccounts } from '@/services/api/account'
 import { sessionData } from './storage'
+import eventBus from '@/services/bus'
 
 /**
  * 账号存储类
@@ -10,6 +11,13 @@ export default new (class {
     accountId = ref(0) // 当前资金账户ID
     accountList = ref<Ermcp.TaAccountsRsp[]>([]) // 资金账户列表
 
+    constructor() {
+        // 接收资金变动通知
+        eventBus.$on('MoneyChangedNotify', () => {
+            this.getAccountList()
+        })
+    }
+
     /**
      * 当前资金账户信息
      */

+ 185 - 2
src/stores/modules/futures.ts

@@ -1,14 +1,197 @@
 import { ref, computed } from 'vue'
+import { timerInterceptor } from '@/utils/timer'
 import { queryGoodsList } from '@/services/api/goods'
 import { sessionData } from './storage'
+import moment from 'moment'
+import eventBus from '@/services/bus'
 
 /**
  * 期货存储类
  */
 export default new (class {
+    private quotes: Proto.Quote[] = [] // 行情数据
+
     loading = ref(false)
-    goodsList = ref<Ermcp.GoodsRsp[]>([])
-    quoteDayList = ref<Ermcp.QuoteDay[]>([])
+    goodsList = ref<Ermcp.GoodsRsp[]>([]) // 商品列表
+    quoteDayList = ref<Ermcp.QuoteDay[]>([]) // 盘面列表
+
+    constructor() {
+        // 接收行情推送通知
+        eventBus.$on('QuotePushNotify', (res) => {
+            const data = res as Proto.Quote[]
+            data.forEach((item) => {
+                const index = this.quotes.findIndex((e) => e.goodscode === item.goodscode)
+                if (index > -1) {
+                    this.quotes[index] = item
+                } else {
+                    this.quotes.push(item)
+                }
+            })
+            this.handleQuote()
+        })
+    }
+
+    /**
+     * 处理行情数据
+     */
+    private handleQuote = timerInterceptor.setThrottle(() => {
+        const quoteList = this.quoteDayList.value
+        this.quotes.forEach((item) => {
+            const quote = quoteList.find((e) => e.goodscode.toUpperCase() === item.goodscode?.toUpperCase())
+            const last = item.last ?? 0
+            const lasttime = (item.date && item.time) ? moment(item.date + item.time, 'YYYYMMDDHHmmss').format('YYYY-MM-DD HH:mm:ss') : ''
+
+            if (quote) {
+                Object.entries(item).forEach(([key, value]) => {
+                    // 只更新存在的属性
+                    if (Reflect.has(quote, key)) {
+                        const prop = key as keyof Ermcp.QuoteDay;
+                        (<K extends typeof prop>(key: K) => quote[key] = value)(prop);
+                    }
+                })
+                // 处理最高最低价
+                if (last) {
+                    if (last > quote.highest) {
+                        quote.highest = last;
+                    }
+                    if (last < quote.lowest) {
+                        quote.lowest = last;
+                    }
+                }
+                // 处理最新时间
+                if (lasttime) {
+                    quote.lasttime = lasttime;
+                }
+            } else {
+                console.warn('行情推送的商品 ' + item.goodscode + ' 缺少盘面信息')
+                quoteList.push({
+                    Lastturnover: 0,
+                    ask: item.ask ?? 0,
+                    ask2: item.ask2 ?? 0,
+                    ask3: item.ask3 ?? 0,
+                    ask4: item.ask4 ?? 0,
+                    ask5: item.ask5 ?? 0,
+                    ask6: 0,
+                    ask7: 0,
+                    ask8: 0,
+                    ask9: 0,
+                    ask10: 0,
+                    askorderid: 0,
+                    askorderid2: 0,
+                    askorderid3: 0,
+                    askorderid4: 0,
+                    askorderid5: 0,
+                    askordervolume: 0,
+                    askordervolume2: 0,
+                    askordervolume3: 0,
+                    askordervolume4: 0,
+                    askordervolume5: 0,
+                    askordervolume6: 0,
+                    askordervolume7: 0,
+                    askordervolume8: 0,
+                    askordervolume9: 0,
+                    askordervolume10: 0,
+                    askqueueinfo: "",
+                    askvolume: item.askvolume ?? 0,
+                    askvolume2: item.askvolume2 ?? 0,
+                    askvolume3: item.askvolume3 ?? 0,
+                    askvolume4: item.askvolume4 ?? 0,
+                    askvolume5: item.askvolume5 ?? 0,
+                    askvolume6: 0,
+                    askvolume7: 0,
+                    askvolume8: 0,
+                    askvolume9: 0,
+                    askvolume10: 0,
+                    averageprice: 0,
+                    bid: item.bid ?? 0,
+                    bid2: item.bid2 ?? 0,
+                    bid3: item.bid3 ?? 0,
+                    bid4: item.bid4 ?? 0,
+                    bid5: item.bid5 ?? 0,
+                    bid6: 0,
+                    bid7: 0,
+                    bid8: 0,
+                    bid9: 0,
+                    bid10: 0,
+                    bidorderid: 0,
+                    bidorderid2: 0,
+                    bidorderid3: 0,
+                    bidorderid4: 0,
+                    bidorderid5: 0,
+                    bidordervolume: 0,
+                    bidordervolume2: 0,
+                    bidordervolume3: 0,
+                    bidordervolume4: 0,
+                    bidordervolume5: 0,
+                    bidordervolume6: 0,
+                    bidordervolume7: 0,
+                    bidordervolume8: 0,
+                    bidordervolume9: 0,
+                    bidordervolume10: 0,
+                    bidqueueinfo: "",
+                    bidvolume: item.bidvolume ?? 0,
+                    bidvolume2: item.bidvolume2 ?? 0,
+                    bidvolume3: item.bidvolume3 ?? 0,
+                    bidvolume4: item.bidvolume4 ?? 0,
+                    bidvolume5: item.bidvolume5 ?? 0,
+                    bidvolume6: 0,
+                    bidvolume7: 0,
+                    bidvolume8: 0,
+                    bidvolume9: 0,
+                    bidvolume10: 0,
+                    calloptionpremiums: item.calloptionpremiums ?? 0,
+                    calloptionpremiums2: item.calloptionpremiums2 ?? 0,
+                    calloptionpremiums3: item.calloptionpremiums3 ?? 0,
+                    calloptionpremiums4: item.calloptionpremiums4 ?? 0,
+                    calloptionpremiums5: item.calloptionpremiums5 ?? 0,
+                    cleartime: 0,
+                    exchangecode: item.exchangecode ?? 0,
+                    exchangedate: item.exchangedate ?? 0,
+                    goodscode: item.goodscode ?? '',
+                    grepmarketprice: 0,
+                    highest: item.highest ?? 0,
+                    holdincrement: 0,
+                    holdvolume: item.holdvolume ?? 0,
+                    iep: 0,
+                    iev: 0,
+                    inventory: item.inventory ?? 0,
+                    iscleared: 0,
+                    issettled: 0,
+                    last,
+                    lastlot: 0,
+                    lasttime,
+                    lastvolume: item.lastvolume ?? 0,
+                    limitdown: item.limitlow ?? 0,
+                    limitup: item.limithigh ?? 0,
+                    lowest: item.lowest ?? 0,
+                    nontotalholdervolume: 0,
+                    nontotallot: 0,
+                    nontotalturnover: 0,
+                    nontotalvolume: 0,
+                    opened: item.opened ?? 0,
+                    opentime: '',
+                    orderid: 0,
+                    preclose: item.preclose ?? 0,
+                    preholdvolume: item.preholdvolume ?? 0,
+                    presettle: item.presettle ?? 0,
+                    publictradetype: '',
+                    putoptionpremiums: item.putoptionpremiums ?? 0,
+                    putoptionpremiums2: item.putoptionpremiums2 ?? 0,
+                    putoptionpremiums3: item.putoptionpremiums3 ?? 0,
+                    putoptionpremiums4: item.putoptionpremiums4 ?? 0,
+                    putoptionpremiums5: item.putoptionpremiums5 ?? 0,
+                    settle: item.settle ?? 0,
+                    strikeprice: 0,
+                    totalaskvolume: 0,
+                    totalbidvolume: 0,
+                    totallot: 0,
+                    totalturnover: item.totalturnover ?? 0,
+                    totalvolume: item.totalvolume ?? 0,
+                    utclasttime: ''
+                })
+            }
+        })
+    }, 200)
 
     /**
      * 获取商品列表

+ 14 - 0
src/stores/modules/storage.ts

@@ -73,6 +73,13 @@ export const localData = new (class extends WebStorage<Store.GlobalStorage>{
         document.documentElement.setAttribute('theme', theme)
         this.setValue('appTheme', theme)
     }
+
+    /**
+     * 重置数据
+     */
+    reset = () => {
+        this.clear('loginInfo', 'userMenus')
+    }
 })
 
 /**
@@ -172,4 +179,11 @@ export const sessionData = new (class extends WebStorage<Store.GlobalStorage>{
             }
         })
     }
+
+    /**
+     * 重置数据
+     */
+    reset = () => {
+        this.clear('loginInfo', 'userMenus')
+    }
 })

+ 3 - 0
src/types/ermcp/warehouse.d.ts

@@ -4,6 +4,9 @@ declare namespace Ermcp {
     interface WarehouseInfoReq {
         userid: number; // 用户ID
         status?: number; // 仓库状态(可多项,逗号隔开) 1:正常 2:注销 3:待审核 4:审核拒绝
+        isincludeexchange?: boolean; // 是返回交易所仓库
+        page?: number; // 页码
+        pagesize?: number; // 每页条数
     }
 
     /** 查询仓库信息 响应 */

+ 7 - 0
src/types/proto/account.d.ts

@@ -65,5 +65,12 @@ declare global {
             AccountIDs: number; // 账户ID列表(有权限的)
             SystemTime: number; // 返回服务器最新时间
         }
+
+        /** 用户登出应答 */
+        interface LogoutRsp {
+            Header?: IMessageHead; // 消息头
+            RetCode: number; // 返回码
+            RetDesc?: string; // 描述信息
+        }
     }
 }

+ 1 - 1
src/types/proto/goods.d.ts

@@ -41,7 +41,7 @@ declare global {
             Price?: number; // 总价(价格),两位小数
             Weight?: number; // 总重量(克拉重量),两位小数
             WeightAvg?: number; // 平均单颗重量
-            ZSSharpType?: number[]; // 形状,1个或多个形状
+            ZSShapeType?: number[]; // 形状,1个或多个形状
             ZSColorType1?: number; // 颜色1
             ZSColorType2?: number; // 颜色2
             ZSClarityType1?: number; // 净度1

+ 3 - 3
src/types/proto/quote.d.ts

@@ -1,6 +1,6 @@
 declare namespace Proto {
     /** 行情推送 */
-    interface GoodsQuote {
+    interface Quote {
         ask?: number;
         ask2?: number;
         ask3?: number;
@@ -65,14 +65,14 @@ declare namespace Proto {
     }
 
     /** 行情订阅请求 */
-    interface GoodsQuoteReq {
+    interface QuoteReq {
         exchangeCode: number; // 交易所代码, 1 Byte MTP 2.0 由于交易所代码服务端未下发,暂时写死250
         goodsCode: string; // 商品代码,64 Byte,不足位使用'\0'补齐
         subState: number; // 订阅成功标志;1为成功,0为失败,订阅的时候填0, 1 Byte
     }
 
     /** 行情订阅响应 */
-    interface GoodsQuoteRsp {
+    interface QuoteRsp {
         exchangeCode: number;
         goodsCode: string;
         subState: number;

+ 30 - 0
src/types/proto/warehouse.d.ts

@@ -0,0 +1,30 @@
+import { IMessageHead } from '@/services/socket/trade/protobuf/proto'
+
+declare global {
+    namespace Proto {
+        /** 仓库申请请求 */
+        interface WarehouseApplyReq {
+            Header?: IMessageHead,
+            type: number // int32 类型 1 新增 2 修改
+            userid: number// uint64 用户ID
+            warehouseid?: number // uint64 仓库ID
+            warehousecode: string // string 仓库代码
+            warehousename: string // string 仓库名称
+            warehousetype: number  // int32 仓库类型 - 1 厂库  2 自有库  3 合作库
+            provinceid?: number  // uint64 省
+            cityid?: number  // uint64 市
+            districtid?: number  // int32 区
+            address?: string // string 详细地址
+            contactname: string // string 联系人
+            contactnum: string // string 联系电话
+        }
+
+        /** 仓库申请响应 */
+        interface WarehouseApplyRsp {
+            Header: IMessageHead,
+            RetCode: number; // int32 返回码
+            RetDesc: string; // string 描述信息
+            warehouseid: number; // uint64 仓库ID
+        }
+    }
+}

+ 44 - 36
src/utils/websocket/index.ts

@@ -40,8 +40,10 @@ export class MTP2WebSocket<T extends Package40 | Package50>{
     onError?: (e: Event) => void;
     /** 接收到推送类报文的回调 */
     onPush?: (data: T) => void;
-    /** 重连状态发生改变时的回调 */
-    onReconnect?: (socket: MTP2WebSocket<T>) => void;
+    /** 在重连之前回调 */
+    onBeforeReconnect?: (count: number) => void;
+    /** 在重连成功之后回调 */
+    onReconnect?: () => void;
 
     /** 当前连接状态 */
     connState: keyof typeof ConnectionState = 'Unconnected';
@@ -62,7 +64,7 @@ export class MTP2WebSocket<T extends Package40 | Package50>{
     /**
      * 连接服务器
      */
-    connect(host?: string): Promise<MTP2WebSocket<T>> {
+    async connect(host?: string): Promise<MTP2WebSocket<T>> {
         if (!this.onReady) {
             clearTimeout(this.reconnectTimer);
             this.stopHeartBeat();
@@ -71,44 +73,48 @@ export class MTP2WebSocket<T extends Package40 | Package50>{
             console.log(this.Package.name, this.host, '正在连接');
 
             this.onReady = new Promise((resolve, reject) => {
+                const errMsg = this.host + ' 连接发生错误';
                 const uuid = v4();
                 this.uuid = uuid;
-                this.ws = new WebSocket(this.host);
 
-                // 连接成功
-                this.ws.onopen = () => {
-                    console.log(this.Package.name, this.host, '连接成功');
-                    this.connState = 'Connected';
-                    this.startHeartBeat();
-                    this.onOpen && this.onOpen(this);
-                    resolve(this);
-                }
-                // 连接断开(CLOSING有可能延迟)
-                this.ws.onclose = () => {
-                    // 判断是否当前实例
-                    // 如果连接断开后不等待 onclose 响应,由于 onclose 延迟的原因可能会在创建新的 ws 实例后触发,导致刚创建的实例被断开进入重连机制
-                    if (this.uuid === uuid) {
-                        console.warn(this.Package.name, this.host, '连接已断开');
-                        this.reset();
-                        this.reconnect();
+                try {
+                    this.ws = new WebSocket(this.host);
+                    // 连接成功
+                    this.ws.onopen = () => {
+                        console.log(this.Package.name, this.host, '连接成功');
+                        this.connState = 'Connected';
+                        this.startHeartBeat();
+                        this.onOpen && this.onOpen(this);
+                        resolve(this);
+                    }
+                    // 连接断开(CLOSING有可能延迟)
+                    this.ws.onclose = () => {
+                        // 判断是否当前实例
+                        // 如果连接断开后不等待 onclose 响应,由于 onclose 延迟的原因可能会在创建新的 ws 实例后触发,导致刚创建的实例被断开进入重连机制
+                        if (this.uuid === uuid) {
+                            console.warn(this.Package.name, this.host, '连接已断开');
+                            this.reset();
+                            this.reconnect(); // 重连失败会不断尝试,直到成功为止
+                        }
+                    }
+                    // 连接发生错误
+                    this.ws.onerror = (e) => {
+                        this.onError && this.onError(e);
+                        reject(errMsg);
                     }
-                }
-                // 连接发生错误
-                this.ws.onerror = (e) => {
-                    const message = this.host + '连接发生错误';
-                    this.onError && this.onError(e);
-                    reject(message);
-                }
-                // 接收数据
-                this.ws.onmessage = (e) => {
                     // 接收数据
-                    new Response(e.data).arrayBuffer().then((res) => {
-                        this.disposeReceiveDatas(new Uint8Array(res));
-                    })
+                    this.ws.onmessage = (e) => {
+                        // 接收数据
+                        new Response(e.data).arrayBuffer().then((res) => {
+                            this.disposeReceiveDatas(new Uint8Array(res));
+                        })
+                    }
+                } catch {
+                    reject(errMsg);
                 }
             })
         }
-        return this.onReady;
+        return this.onReady
     }
 
     /**
@@ -117,7 +123,7 @@ export class MTP2WebSocket<T extends Package40 | Package50>{
     close() {
         clearTimeout(this.reconnectTimer);
         this.stopHeartBeat();
-        this.uuid = v4();
+        this.uuid = v4(); // 改变实例标识,取消重连状态
         this.reconnectCount = 0;
         this.reconnectInterval = this.defaultReconnectInterval;
 
@@ -181,12 +187,13 @@ export class MTP2WebSocket<T extends Package40 | Package50>{
     }
 
     /**
-     * 断网重连方法,在重连尝试失败时会递归调用自己
+     * 断网重连方法,在重连尝试失败时会再次重试
      */
     private reconnect() {
         this.stopHeartBeat();
         if (this.connState !== 'Connecting') {
             this.reconnectCount++;
+            this.onBeforeReconnect && this.onBeforeReconnect(this.reconnectCount);
             console.log(this.Package.name, this.host, `${this.reconnectInterval / 1000}秒后将进行第${this.reconnectCount}次重连`);
 
             this.reconnectTimer = window.setTimeout(() => {
@@ -195,8 +202,9 @@ export class MTP2WebSocket<T extends Package40 | Package50>{
                     console.log(this.Package.name, this.host, '重连成功,可开始进行业务操作');
                     this.reconnectCount = 0;
                     this.reconnectInterval = this.defaultReconnectInterval;
-                    this.onReconnect && this.onReconnect(this);
+                    this.onReconnect && this.onReconnect();
                 }).catch(() => {
+                    // 重连失败会进入 ws.onclose 再次发起重连
                     if (this.reconnectCount) {
                         this.reconnectInterval += this.defaultReconnectInterval; // 重连间隔时间每次递增5秒
                         console.warn(this.Package.name, this.host, `第${this.reconnectCount}次重连失败`);