li.shaoyi 3 anni fa
parent
commit
ea38ae4df2
54 ha cambiato i file con 1247 aggiunte e 831 eliminazioni
  1. 1 0
      .eslintrc.js
  2. 74 118
      package-lock.json
  3. 1 1
      package.json
  4. 17 17
      src/business/common/index.ts
  5. 8 7
      src/business/customer/index.ts
  6. 12 33
      src/business/sign/index.ts
  7. 3 1
      src/constants/customer.ts
  8. 0 69
      src/constants/index.ts
  9. 3 1
      src/constants/menu.ts
  10. 6 6
      src/hooks/auth/index.ts
  11. 9 5
      src/hooks/datatable/index.ts
  12. 7 0
      src/hooks/datatable/interface.ts
  13. 3 2
      src/hooks/echarts/candlestick/index.ts
  14. 4 4
      src/hooks/echarts/candlestick/options.ts
  15. 3 2
      src/hooks/echarts/timeline/index.ts
  16. 4 4
      src/hooks/echarts/timeline/options.ts
  17. 4 4
      src/packages/mobile/router/index.ts
  18. 8 7
      src/packages/mobile/views/home/components/market/index.vue
  19. 2 2
      src/packages/pc/components/layouts/footer/index.vue
  20. 9 5
      src/packages/pc/components/modules/auth-operation/index.vue
  21. 4 2
      src/packages/pc/main.ts
  22. 5 21
      src/packages/pc/router/dynamicRouter.ts
  23. 7 9
      src/packages/pc/router/index.ts
  24. 9 8
      src/packages/pc/views/boot/index.vue
  25. 1 3
      src/packages/pc/views/error/index.less
  26. 2 2
      src/packages/pc/views/footer/futures/position/index.vue
  27. 2 2
      src/packages/pc/views/market/futures/index.vue
  28. 0 2
      src/packages/pc/views/system/menu/components/edit/index.vue
  29. 7 0
      src/services/api/common/index.ts
  30. 3 2
      src/services/http/index.ts
  31. 0 30
      src/services/language/index.ts
  32. 5 1
      src/services/socket/index.ts
  33. 3 3
      src/services/socket/quote/index.ts
  34. 8 4
      src/services/socket/trade/index.ts
  35. 3 2
      src/services/subscribe/index.ts
  36. 49 0
      src/stores/base.ts
  37. 34 18
      src/stores/index.ts
  38. 113 90
      src/stores/modules/account.ts
  39. 0 45
      src/stores/modules/common.ts
  40. 95 0
      src/stores/modules/enum.ts
  41. 55 0
      src/stores/modules/errorInfo.ts
  42. 83 69
      src/stores/modules/futures.ts
  43. 52 0
      src/stores/modules/language.ts
  44. 108 0
      src/stores/modules/login.ts
  45. 47 0
      src/stores/modules/menu.ts
  46. 0 140
      src/stores/modules/storage.ts
  47. 46 0
      src/stores/modules/tableColumn.ts
  48. 65 0
      src/stores/modules/theme.ts
  49. 65 0
      src/stores/modules/user.ts
  50. 77 0
      src/types/ermcp/common.d.ts
  51. 0 16
      src/types/store/globalStorage.d.ts
  52. 0 23
      src/types/store/quotation.d.ts
  53. 69 0
      src/utils/storage/base.ts
  54. 52 51
      src/utils/storage/index.ts

+ 1 - 0
.eslintrc.js

@@ -15,6 +15,7 @@ module.exports = {
   rules: {
     '@typescript-eslint/no-var-requires': 0,
     '@typescript-eslint/explicit-module-boundary-types': 'off',
+    '@typescript-eslint/no-non-null-assertion': 'off',
     'no-console': process.env.NODE_ENV === 'production' ? 'warn' : 'off',
     'no-debugger': process.env.NODE_ENV === 'production' ? 'warn' : 'off',
     'vue/multi-word-component-names': 0,

+ 74 - 118
package-lock.json

@@ -23,7 +23,7 @@
         "vant": "^3.4.8",
         "vue": "^3.2.13",
         "vue-class-component": "^8.0.0-0",
-        "vue-i18n": "^9.1.10",
+        "vue-i18n": "^9.2.2",
         "vue-router": "^4.0.3",
         "vuedraggable": "^4.1.0"
       },
@@ -1810,43 +1810,40 @@
       "dev": true
     },
     "node_modules/@intlify/core-base": {
-      "version": "9.1.10",
-      "resolved": "https://registry.npmjs.org/@intlify/core-base/-/core-base-9.1.10.tgz",
-      "integrity": "sha512-So9CNUavB/IsZ+zBmk2Cv6McQp6vc2wbGi1S0XQmJ8Vz+UFcNn9MFXAe9gY67PreIHrbLsLxDD0cwo1qsxM1Nw==",
+      "version": "9.2.2",
+      "resolved": "https://registry.npmjs.org/@intlify/core-base/-/core-base-9.2.2.tgz",
+      "integrity": "sha512-JjUpQtNfn+joMbrXvpR4hTF8iJQ2sEFzzK3KIESOx+f+uwIjgw20igOyaIdhfsVVBCds8ZM64MoeNSx+PHQMkA==",
       "dependencies": {
-        "@intlify/devtools-if": "9.1.10",
-        "@intlify/message-compiler": "9.1.10",
-        "@intlify/message-resolver": "9.1.10",
-        "@intlify/runtime": "9.1.10",
-        "@intlify/shared": "9.1.10",
-        "@intlify/vue-devtools": "9.1.10"
+        "@intlify/devtools-if": "9.2.2",
+        "@intlify/message-compiler": "9.2.2",
+        "@intlify/shared": "9.2.2",
+        "@intlify/vue-devtools": "9.2.2"
       },
       "engines": {
-        "node": ">= 10"
+        "node": ">= 14"
       }
     },
     "node_modules/@intlify/devtools-if": {
-      "version": "9.1.10",
-      "resolved": "https://registry.npmjs.org/@intlify/devtools-if/-/devtools-if-9.1.10.tgz",
-      "integrity": "sha512-SHaKoYu6sog3+Q8js1y3oXLywuogbH1sKuc7NSYkN3GElvXSBaMoCzW+we0ZSFqj/6c7vTNLg9nQ6rxhKqYwnQ==",
+      "version": "9.2.2",
+      "resolved": "https://registry.npmjs.org/@intlify/devtools-if/-/devtools-if-9.2.2.tgz",
+      "integrity": "sha512-4ttr/FNO29w+kBbU7HZ/U0Lzuh2cRDhP8UlWOtV9ERcjHzuyXVZmjyleESK6eVP60tGC9QtQW9yZE+JeRhDHkg==",
       "dependencies": {
-        "@intlify/shared": "9.1.10"
+        "@intlify/shared": "9.2.2"
       },
       "engines": {
-        "node": ">= 10"
+        "node": ">= 14"
       }
     },
     "node_modules/@intlify/message-compiler": {
-      "version": "9.1.10",
-      "resolved": "https://registry.npmjs.org/@intlify/message-compiler/-/message-compiler-9.1.10.tgz",
-      "integrity": "sha512-+JiJpXff/XTb0EadYwdxOyRTB0hXNd4n1HaJ/a4yuV960uRmPXaklJsedW0LNdcptd/hYUZtCkI7Lc9J5C1gxg==",
+      "version": "9.2.2",
+      "resolved": "https://registry.npmjs.org/@intlify/message-compiler/-/message-compiler-9.2.2.tgz",
+      "integrity": "sha512-IUrQW7byAKN2fMBe8z6sK6riG1pue95e5jfokn8hA5Q3Bqy4MBJ5lJAofUsawQJYHeoPJ7svMDyBaVJ4d0GTtA==",
       "dependencies": {
-        "@intlify/message-resolver": "9.1.10",
-        "@intlify/shared": "9.1.10",
+        "@intlify/shared": "9.2.2",
         "source-map": "0.6.1"
       },
       "engines": {
-        "node": ">= 10"
+        "node": ">= 14"
       }
     },
     "node_modules/@intlify/message-compiler/node_modules/source-map": {
@@ -1857,46 +1854,24 @@
         "node": ">=0.10.0"
       }
     },
-    "node_modules/@intlify/message-resolver": {
-      "version": "9.1.10",
-      "resolved": "https://registry.npmjs.org/@intlify/message-resolver/-/message-resolver-9.1.10.tgz",
-      "integrity": "sha512-5YixMG/M05m0cn9+gOzd4EZQTFRUu8RGhzxJbR1DWN21x/Z3bJ8QpDYj6hC4FwBj5uKsRfKpJQ3Xqg98KWoA+w==",
-      "engines": {
-        "node": ">= 10"
-      }
-    },
-    "node_modules/@intlify/runtime": {
-      "version": "9.1.10",
-      "resolved": "https://registry.npmjs.org/@intlify/runtime/-/runtime-9.1.10.tgz",
-      "integrity": "sha512-7QsuByNzpe3Gfmhwq6hzgXcMPpxz8Zxb/XFI6s9lQdPLPe5Lgw4U1ovRPZTOs6Y2hwitR3j/HD8BJNGWpJnOFA==",
-      "dependencies": {
-        "@intlify/message-compiler": "9.1.10",
-        "@intlify/message-resolver": "9.1.10",
-        "@intlify/shared": "9.1.10"
-      },
-      "engines": {
-        "node": ">= 10"
-      }
-    },
     "node_modules/@intlify/shared": {
-      "version": "9.1.10",
-      "resolved": "https://registry.npmjs.org/@intlify/shared/-/shared-9.1.10.tgz",
-      "integrity": "sha512-Om54xJeo1Vw+K1+wHYyXngE8cAbrxZHpWjYzMR9wCkqbhGtRV5VLhVc214Ze2YatPrWlS2WSMOWXR8JktX/IgA==",
+      "version": "9.2.2",
+      "resolved": "https://registry.npmjs.org/@intlify/shared/-/shared-9.2.2.tgz",
+      "integrity": "sha512-wRwTpsslgZS5HNyM7uDQYZtxnbI12aGiBZURX3BTR9RFIKKRWpllTsgzHWvj3HKm3Y2Sh5LPC1r0PDCKEhVn9Q==",
       "engines": {
-        "node": ">= 10"
+        "node": ">= 14"
       }
     },
     "node_modules/@intlify/vue-devtools": {
-      "version": "9.1.10",
-      "resolved": "https://registry.npmjs.org/@intlify/vue-devtools/-/vue-devtools-9.1.10.tgz",
-      "integrity": "sha512-5l3qYARVbkWAkagLu1XbDUWRJSL8br1Dj60wgMaKB0+HswVsrR6LloYZTg7ozyvM621V6+zsmwzbQxbVQyrytQ==",
+      "version": "9.2.2",
+      "resolved": "https://registry.npmjs.org/@intlify/vue-devtools/-/vue-devtools-9.2.2.tgz",
+      "integrity": "sha512-+dUyqyCHWHb/UcvY1MlIpO87munedm3Gn6E9WWYdWrMuYLcoIoOEVDWSS8xSwtlPU+kA+MEQTP6Q1iI/ocusJg==",
       "dependencies": {
-        "@intlify/message-resolver": "9.1.10",
-        "@intlify/runtime": "9.1.10",
-        "@intlify/shared": "9.1.10"
+        "@intlify/core-base": "9.2.2",
+        "@intlify/shared": "9.2.2"
       },
       "engines": {
-        "node": ">= 10"
+        "node": ">= 14"
       }
     },
     "node_modules/@jridgewell/resolve-uri": {
@@ -3315,9 +3290,9 @@
       "dev": true
     },
     "node_modules/@vue/devtools-api": {
-      "version": "6.1.4",
-      "resolved": "https://registry.npmjs.org/@vue/devtools-api/-/devtools-api-6.1.4.tgz",
-      "integrity": "sha512-IiA0SvDrJEgXvVxjNkHPFfDx6SXw0b/TUkqMcDZWNg9fnCAHbTpoo59YfJ9QLFkwa3raau5vSlRVzMSLDnfdtQ=="
+      "version": "6.4.1",
+      "resolved": "https://registry.npmjs.org/@vue/devtools-api/-/devtools-api-6.4.1.tgz",
+      "integrity": "sha512-tY5m7kwu0R+9GWHSncsE40rCX9ou4HhjhlbgdEMci8j08BE7pLlOpHRcyv6eEP0VYrW1JV0zFh6AoWsoHrVyFw=="
     },
     "node_modules/@vue/eslint-config-typescript": {
       "version": "9.1.0",
@@ -11768,17 +11743,17 @@
       "dev": true
     },
     "node_modules/vue-i18n": {
-      "version": "9.1.10",
-      "resolved": "https://registry.npmjs.org/vue-i18n/-/vue-i18n-9.1.10.tgz",
-      "integrity": "sha512-jpr7gV5KPk4n+sSPdpZT8Qx3XzTcNDWffRlHV/cT2NUyEf+sEgTTmLvnBAibjOFJ0zsUyZlVTAWH5DDnYep+1g==",
+      "version": "9.2.2",
+      "resolved": "https://registry.npmjs.org/vue-i18n/-/vue-i18n-9.2.2.tgz",
+      "integrity": "sha512-yswpwtj89rTBhegUAv9Mu37LNznyu3NpyLQmozF3i1hYOhwpG8RjcjIFIIfnu+2MDZJGSZPXaKWvnQA71Yv9TQ==",
       "dependencies": {
-        "@intlify/core-base": "9.1.10",
-        "@intlify/shared": "9.1.10",
-        "@intlify/vue-devtools": "9.1.10",
-        "@vue/devtools-api": "^6.0.0-beta.7"
+        "@intlify/core-base": "9.2.2",
+        "@intlify/shared": "9.2.2",
+        "@intlify/vue-devtools": "9.2.2",
+        "@vue/devtools-api": "^6.2.1"
       },
       "engines": {
-        "node": ">= 10"
+        "node": ">= 14"
       },
       "peerDependencies": {
         "vue": "^3.0.0"
@@ -13971,33 +13946,30 @@
       "dev": true
     },
     "@intlify/core-base": {
-      "version": "9.1.10",
-      "resolved": "https://registry.npmjs.org/@intlify/core-base/-/core-base-9.1.10.tgz",
-      "integrity": "sha512-So9CNUavB/IsZ+zBmk2Cv6McQp6vc2wbGi1S0XQmJ8Vz+UFcNn9MFXAe9gY67PreIHrbLsLxDD0cwo1qsxM1Nw==",
+      "version": "9.2.2",
+      "resolved": "https://registry.npmjs.org/@intlify/core-base/-/core-base-9.2.2.tgz",
+      "integrity": "sha512-JjUpQtNfn+joMbrXvpR4hTF8iJQ2sEFzzK3KIESOx+f+uwIjgw20igOyaIdhfsVVBCds8ZM64MoeNSx+PHQMkA==",
       "requires": {
-        "@intlify/devtools-if": "9.1.10",
-        "@intlify/message-compiler": "9.1.10",
-        "@intlify/message-resolver": "9.1.10",
-        "@intlify/runtime": "9.1.10",
-        "@intlify/shared": "9.1.10",
-        "@intlify/vue-devtools": "9.1.10"
+        "@intlify/devtools-if": "9.2.2",
+        "@intlify/message-compiler": "9.2.2",
+        "@intlify/shared": "9.2.2",
+        "@intlify/vue-devtools": "9.2.2"
       }
     },
     "@intlify/devtools-if": {
-      "version": "9.1.10",
-      "resolved": "https://registry.npmjs.org/@intlify/devtools-if/-/devtools-if-9.1.10.tgz",
-      "integrity": "sha512-SHaKoYu6sog3+Q8js1y3oXLywuogbH1sKuc7NSYkN3GElvXSBaMoCzW+we0ZSFqj/6c7vTNLg9nQ6rxhKqYwnQ==",
+      "version": "9.2.2",
+      "resolved": "https://registry.npmjs.org/@intlify/devtools-if/-/devtools-if-9.2.2.tgz",
+      "integrity": "sha512-4ttr/FNO29w+kBbU7HZ/U0Lzuh2cRDhP8UlWOtV9ERcjHzuyXVZmjyleESK6eVP60tGC9QtQW9yZE+JeRhDHkg==",
       "requires": {
-        "@intlify/shared": "9.1.10"
+        "@intlify/shared": "9.2.2"
       }
     },
     "@intlify/message-compiler": {
-      "version": "9.1.10",
-      "resolved": "https://registry.npmjs.org/@intlify/message-compiler/-/message-compiler-9.1.10.tgz",
-      "integrity": "sha512-+JiJpXff/XTb0EadYwdxOyRTB0hXNd4n1HaJ/a4yuV960uRmPXaklJsedW0LNdcptd/hYUZtCkI7Lc9J5C1gxg==",
+      "version": "9.2.2",
+      "resolved": "https://registry.npmjs.org/@intlify/message-compiler/-/message-compiler-9.2.2.tgz",
+      "integrity": "sha512-IUrQW7byAKN2fMBe8z6sK6riG1pue95e5jfokn8hA5Q3Bqy4MBJ5lJAofUsawQJYHeoPJ7svMDyBaVJ4d0GTtA==",
       "requires": {
-        "@intlify/message-resolver": "9.1.10",
-        "@intlify/shared": "9.1.10",
+        "@intlify/shared": "9.2.2",
         "source-map": "0.6.1"
       },
       "dependencies": {
@@ -14008,34 +13980,18 @@
         }
       }
     },
-    "@intlify/message-resolver": {
-      "version": "9.1.10",
-      "resolved": "https://registry.npmjs.org/@intlify/message-resolver/-/message-resolver-9.1.10.tgz",
-      "integrity": "sha512-5YixMG/M05m0cn9+gOzd4EZQTFRUu8RGhzxJbR1DWN21x/Z3bJ8QpDYj6hC4FwBj5uKsRfKpJQ3Xqg98KWoA+w=="
-    },
-    "@intlify/runtime": {
-      "version": "9.1.10",
-      "resolved": "https://registry.npmjs.org/@intlify/runtime/-/runtime-9.1.10.tgz",
-      "integrity": "sha512-7QsuByNzpe3Gfmhwq6hzgXcMPpxz8Zxb/XFI6s9lQdPLPe5Lgw4U1ovRPZTOs6Y2hwitR3j/HD8BJNGWpJnOFA==",
-      "requires": {
-        "@intlify/message-compiler": "9.1.10",
-        "@intlify/message-resolver": "9.1.10",
-        "@intlify/shared": "9.1.10"
-      }
-    },
     "@intlify/shared": {
-      "version": "9.1.10",
-      "resolved": "https://registry.npmjs.org/@intlify/shared/-/shared-9.1.10.tgz",
-      "integrity": "sha512-Om54xJeo1Vw+K1+wHYyXngE8cAbrxZHpWjYzMR9wCkqbhGtRV5VLhVc214Ze2YatPrWlS2WSMOWXR8JktX/IgA=="
+      "version": "9.2.2",
+      "resolved": "https://registry.npmjs.org/@intlify/shared/-/shared-9.2.2.tgz",
+      "integrity": "sha512-wRwTpsslgZS5HNyM7uDQYZtxnbI12aGiBZURX3BTR9RFIKKRWpllTsgzHWvj3HKm3Y2Sh5LPC1r0PDCKEhVn9Q=="
     },
     "@intlify/vue-devtools": {
-      "version": "9.1.10",
-      "resolved": "https://registry.npmjs.org/@intlify/vue-devtools/-/vue-devtools-9.1.10.tgz",
-      "integrity": "sha512-5l3qYARVbkWAkagLu1XbDUWRJSL8br1Dj60wgMaKB0+HswVsrR6LloYZTg7ozyvM621V6+zsmwzbQxbVQyrytQ==",
+      "version": "9.2.2",
+      "resolved": "https://registry.npmjs.org/@intlify/vue-devtools/-/vue-devtools-9.2.2.tgz",
+      "integrity": "sha512-+dUyqyCHWHb/UcvY1MlIpO87munedm3Gn6E9WWYdWrMuYLcoIoOEVDWSS8xSwtlPU+kA+MEQTP6Q1iI/ocusJg==",
       "requires": {
-        "@intlify/message-resolver": "9.1.10",
-        "@intlify/runtime": "9.1.10",
-        "@intlify/shared": "9.1.10"
+        "@intlify/core-base": "9.2.2",
+        "@intlify/shared": "9.2.2"
       }
     },
     "@jridgewell/resolve-uri": {
@@ -15163,9 +15119,9 @@
       }
     },
     "@vue/devtools-api": {
-      "version": "6.1.4",
-      "resolved": "https://registry.npmjs.org/@vue/devtools-api/-/devtools-api-6.1.4.tgz",
-      "integrity": "sha512-IiA0SvDrJEgXvVxjNkHPFfDx6SXw0b/TUkqMcDZWNg9fnCAHbTpoo59YfJ9QLFkwa3raau5vSlRVzMSLDnfdtQ=="
+      "version": "6.4.1",
+      "resolved": "https://registry.npmjs.org/@vue/devtools-api/-/devtools-api-6.4.1.tgz",
+      "integrity": "sha512-tY5m7kwu0R+9GWHSncsE40rCX9ou4HhjhlbgdEMci8j08BE7pLlOpHRcyv6eEP0VYrW1JV0zFh6AoWsoHrVyFw=="
     },
     "@vue/eslint-config-typescript": {
       "version": "9.1.0",
@@ -21429,14 +21385,14 @@
       "dev": true
     },
     "vue-i18n": {
-      "version": "9.1.10",
-      "resolved": "https://registry.npmjs.org/vue-i18n/-/vue-i18n-9.1.10.tgz",
-      "integrity": "sha512-jpr7gV5KPk4n+sSPdpZT8Qx3XzTcNDWffRlHV/cT2NUyEf+sEgTTmLvnBAibjOFJ0zsUyZlVTAWH5DDnYep+1g==",
-      "requires": {
-        "@intlify/core-base": "9.1.10",
-        "@intlify/shared": "9.1.10",
-        "@intlify/vue-devtools": "9.1.10",
-        "@vue/devtools-api": "^6.0.0-beta.7"
+      "version": "9.2.2",
+      "resolved": "https://registry.npmjs.org/vue-i18n/-/vue-i18n-9.2.2.tgz",
+      "integrity": "sha512-yswpwtj89rTBhegUAv9Mu37LNznyu3NpyLQmozF3i1hYOhwpG8RjcjIFIIfnu+2MDZJGSZPXaKWvnQA71Yv9TQ==",
+      "requires": {
+        "@intlify/core-base": "9.2.2",
+        "@intlify/shared": "9.2.2",
+        "@intlify/vue-devtools": "9.2.2",
+        "@vue/devtools-api": "^6.2.1"
       }
     },
     "vue-loader": {

+ 1 - 1
package.json

@@ -25,7 +25,7 @@
     "vant": "^3.4.8",
     "vue": "^3.2.13",
     "vue-class-component": "^8.0.0-0",
-    "vue-i18n": "^9.1.10",
+    "vue-i18n": "^9.2.2",
     "vue-router": "^4.0.3",
     "vuedraggable": "^4.1.0"
   },

+ 17 - 17
src/business/common/index.ts

@@ -2,7 +2,7 @@ import { shallowRef } from 'vue'
 import { timerTask } from '@/utils/timer'
 import { queryTableDefine } from '@/services/api/common'
 import { tokenCheck } from '@/services/api/account'
-import { commonStore, accountStore, futuresStore, sessionData } from '@/stores'
+import { useStore } from '@/stores'
 import eventBus from '@/services/bus'
 import socket from '@/services/socket'
 
@@ -10,9 +10,10 @@ import socket from '@/services/socket'
  * 退出登录
  */
 export function logout(callback?: () => void) {
+    const { loginStore, accountStore } = useStore()
     socket.closeAll()
     timerTask.clearAll()
-    sessionData.reset()
+    loginStore.reset()
     accountStore.reset()
     callback && callback()
 }
@@ -22,30 +23,29 @@ export function logout(callback?: () => void) {
  * @param callback 
  */
 export async function initBaseData(callback?: () => void) {
-    if (sessionData.getLoginInfo('Token')) {
-        checkTokenLoop()
+    checkTokenLoop()
 
-        const asyncTask = [
-            commonStore.getLoginData(),
-            futuresStore.getGoodsList(),
-        ]
+    const { userStore, menuStore, futuresStore, accountStore } = useStore()
+    const asyncTask = [
+        userStore.getUserData(),
+        menuStore.getUserMenuList(),
+        futuresStore.getGoodsList(),
+    ]
 
-        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('初始化失败')
+    })
 }
 
 /**
  * 令牌效验
  */
 export function checkToken() {
-    const { LoginID, Token } = sessionData.getValue('loginInfo')
+    const { loginStore } = useStore()
+    const { LoginID, Token } = loginStore.loginInfo.value
     return tokenCheck({
         data: {
             LoginID,

+ 8 - 7
src/business/customer/index.ts

@@ -2,10 +2,11 @@ import { ref } from 'vue'
 import { useDataTable } from '@/hooks/datatable'
 import { queryCustomerInfo, customerInfoOperate, userInfoOperate } from '@/services/api/customer'
 import { UserInfoType, CustomerQueryType } from '@/constants/customer'
-import { sessionData } from '@/stores'
+import { useLoginStore } from '@/stores'
 import { getTableColumns } from '../common'
 
 export function useCustomer(queryType: CustomerQueryType) {
+    const { getUserId } = useLoginStore()
     const { dataList, selectList, inputList, buttonList } = useDataTable<Ermcp.CustomerInfoRsp>()
     const { columns } = getTableColumns('table_pcweb_userinfo')
     const loading = ref(false)
@@ -29,7 +30,7 @@ export function useCustomer(queryType: CustomerQueryType) {
         loading.value = true
         return queryCustomerInfo({
             data: {
-                userid: sessionData.getLoginInfo('UserID'),
+                userid: getUserId(),
                 querytype: queryType,
             },
             success: (res) => {
@@ -53,13 +54,13 @@ export function useCustomer(queryType: CustomerQueryType) {
 }
 
 export function useCustomerForm(selectedRow?: Ermcp.CustomerInfoRsp) {
-    const loginUserId = sessionData.getLoginInfo('UserID')
+    const { getUserId } = useLoginStore()
     const loading = ref(false)
     const formData = ref<Proto.CustomerInfoOperateReq>({
         operatetype: 1,
         userinfotype: UserInfoType.Enterprise,
-        userid: loginUserId,
-        areaid: loginUserId,
+        userid: getUserId(),
+        areaid: getUserId(),
         username: '',
         cardnum: '',
         mobilephone: '',
@@ -102,7 +103,7 @@ export function useCustomerForm(selectedRow?: Ermcp.CustomerInfoRsp) {
             remark,
             customername,
             mobilephone: mobile,
-            areaid: loginUserId,
+            areaid: getUserId(),
             ipaddress: address,
             userstate: 2,
         }
@@ -116,7 +117,7 @@ export function useCustomerForm(selectedRow?: Ermcp.CustomerInfoRsp) {
                 data: {
                     ...formData.value,
                     operatesrc: 2,
-                    operateid: loginUserId,
+                    operateid: getUserId(),
                 },
                 complete: () => {
                     loading.value = false

+ 12 - 33
src/business/sign/index.ts

@@ -1,12 +1,11 @@
 import { ref, reactive } from 'vue'
 import { v4 } from 'uuid'
-import { sessionData } from '@/stores'
+import { useLoginStore } from '@/stores'
 import { initBaseData } from '@/business/common'
-import { login, queryLoginId } from '@/services/api/account'
-import cryptojs from 'crypto-js'
 import eventBus from '@/services/bus'
 
 export function useSign() {
+    const { userLogin } = useLoginStore();
     const loading = ref(false);
     const user = reactive<Proto.LoginReq>({
         LoginID: '1000',
@@ -27,36 +26,16 @@ export function useSign() {
     const signIn = () => {
         loading.value = true
         return new Promise((resolve, reject) => {
-            queryLoginId({
-                data: {
-                    username: user.LoginID
-                },
-                success: (res) => {
-                    login({
-                        data: {
-                            ...user,
-                            LoginID: res.data,
-                            LoginPWD: cryptojs.SHA256(res.data + user.LoginPWD).toString(),
-                        },
-                        success: (res) => {
-                            sessionData.setValue('loginInfo', res);
-                            initBaseData(() => {
-                                resolve(res);
-                            }).catch((err) => {
-                                loading.value = false;
-                                reject(err);
-                            })
-                        },
-                        fail: (err) => {
-                            loading.value = false;
-                            reject(err);
-                        }
-                    })
-                },
-                fail: (err) => {
-                    loading.value = false;
-                    reject(err);
-                }
+            userLogin(user).then((res) => {
+                initBaseData(() => {
+                    resolve(res)
+                }).catch((err) => {
+                    loading.value = false
+                    reject(err)
+                })
+            }).catch((err) => {
+                loading.value = false
+                reject(err)
             })
         })
     }

+ 3 - 1
src/constants/customer.ts

@@ -1,4 +1,6 @@
-import { getEnumTypeName } from './index'
+import { useEnumStore } from '@/stores'
+
+const { getEnumTypeName } = useEnumStore()
 
 /**
  * 客户资料查询类型

+ 0 - 69
src/constants/index.ts

@@ -1,69 +0,0 @@
-import { sessionData } from '@/stores'
-
-/**
- * 枚举类型
- */
-export interface EnumType {
-    label: string;
-    value: number;
-    disabled?: boolean;
-}
-
-/**
- * 获取枚举类型
- * @param codes 
- * @returns 
- */
-export function getEnumTypes<T extends string[]>(...codes: T) {
-    const enumMap = new Map<typeof codes[number], Ermcp.EnumRsp[]>()
-    sessionData.getValue('allEnums').forEach((e) => {
-        if (codes.some((code) => code.toLowerCase() === e.enumdiccode.toLowerCase())) {
-            const enums = enumMap.get(e.enumdiccode)
-            if (enums) {
-                enums.push(e)
-            } else {
-                enumMap.set(e.enumdiccode, [e])
-            }
-        }
-    })
-    return enumMap
-}
-
-/**
- * 获取枚举信息
- * @param enums 
- * @param value 
- * @returns 
- */
-export function getEnumTypeInfo(enums: Ermcp.EnumRsp[], value: number) {
-    return enums.find((e) => e.enumitemstatus === 1 && e.enumitemname === value)
-}
-
-/**
- * 获取枚举列表
- * @param enums 
- * @returns 
- */
-export function getEnumTypeList(enums?: Ermcp.EnumRsp[]): EnumType[] {
-    if (enums) {
-        return enums.map((e) => ({
-            label: e.enumdicname,
-            value: e.enumitemname,
-        }))
-    }
-    return []
-}
-
-/**
- * 根据枚举值获取枚举名称
- * @param enums 
- * @param value 
- * @returns 
- */
-export function getEnumTypeName(enums: EnumType[], value: number): string {
-    const item = enums.find((e) => e.value === value)
-    if (item) {
-        return item.label
-    }
-    return '--'
-}

+ 3 - 1
src/constants/menu.ts

@@ -1,4 +1,6 @@
-import { getEnumTypeName } from './index'
+import { useEnumStore } from '@/stores'
+
+const { getEnumTypeName } = useEnumStore()
 
 /**
  * 权限类型

+ 6 - 6
src/hooks/auth/index.ts

@@ -1,12 +1,12 @@
 import { defineAsyncComponent, Component } from 'vue'
 import { useRoute, useRouter } from 'vue-router'
-import { sessionData } from '@/stores'
+import { useMenuStore } from '@/stores'
 import { AuthType } from '@/constants/menu'
 
 export function useAuth(authCode?: string) {
+    const { userMenus } = useMenuStore()
     const route = useRoute()
     const router = useRouter()
-    const userMenus = sessionData.getValue('userMenus')
     const componentMap = new Map<string, Component>()
 
     // 过滤菜单
@@ -55,7 +55,7 @@ export function useAuth(authCode?: string) {
             }
             return data
         }
-        return filterMenu(filterLevel(userMenus, level))
+        return filterMenu(filterLevel(userMenus.value, level))
     }
 
     /**
@@ -64,7 +64,7 @@ export function useAuth(authCode?: string) {
     * @returns 
     */
     const getChildrenMenus = (code?: string) => {
-        const children = findChildren(userMenus, code)
+        const children = findChildren(userMenus.value, code)
         return filterMenu(children)
     }
 
@@ -73,7 +73,7 @@ export function useAuth(authCode?: string) {
      * @returns 
      */
     const getAuth = (authType: AuthType) => {
-        const children = findChildren(userMenus, authCode)
+        const children = findChildren(userMenus.value, authCode)
         return children.reduce((res, cur) => {
             if (cur.authType === authType) {
                 if (!componentMap.get(cur.code) && cur.component) {
@@ -84,7 +84,7 @@ export function useAuth(authCode?: string) {
                     })
                     componentMap.set(cur.code, asyncComponent)
                 }
-                res.push(cur)
+                res.push(JSON.parse(JSON.stringify(cur)))
             }
             return res
         }, [] as Ermcp.UserMenu[])

+ 9 - 5
src/hooks/datatable/index.ts

@@ -1,7 +1,7 @@
 import { reactive, ref, shallowRef, toRefs, computed, UnwrapRef } from 'vue'
-import { FilterValue, FilterOptions } from './interface'
+import { FilterValue, FilterOptions, DataTableOptions } from './interface'
 
-export function useDataTable<T>() {
+export function useDataTable<T>(options?: DataTableOptions) {
     // 数据源
     const dataSource = ref<T[]>([])
     // 总记录条数
@@ -19,7 +19,6 @@ export function useDataTable<T>() {
         buttonList: [
             {
                 lable: '重置',
-                className: 'el-button--info', // 这里 className 不应该使用框架样式,以后再解决
                 onClick: () => {
                     filterOptons.selectList.forEach((e) => {
                         if (!e.noClear) {
@@ -37,7 +36,7 @@ export function useDataTable<T>() {
             },
             {
                 lable: '查询',
-                className: 'el-button--info', // 这里 className 不应该使用框架样式,以后再解决
+                className: 'el-button--primary', // 这里 className 不应该使用框架样式,以后再解决
                 onClick: () => {
                     pageIndex.value = 1
                     filterMethod.onSearch()
@@ -59,7 +58,7 @@ export function useDataTable<T>() {
     // 数据列表
     const dataList = computed<UnwrapRef<T[]>>({
         get() {
-            return dataSource.value.filter((row) => {
+            const result = dataSource.value.filter((row) => {
                 // 过滤所有查询条件
                 return filters.value.every((e) => {
                     return e.keys.some((key) => {
@@ -73,6 +72,11 @@ export function useDataTable<T>() {
                     })
                 })
             })
+            // 本地分页
+            if (options?.pagination) {
+                return result.slice((pageIndex.value - 1) * pageSize.value, pageIndex.value * pageSize.value)
+            }
+            return result
         },
         set(val) {
             dataSource.value = val

+ 7 - 0
src/hooks/datatable/interface.ts

@@ -50,4 +50,11 @@ export interface FilterButton {
     lable: string;
     className?: string;
     onClick: () => void;
+}
+
+/** 
+ * 数据表配置项
+ */
+export interface DataTableOptions {
+    pagination: boolean; // 是否进行本地分页
 }

+ 3 - 2
src/hooks/echarts/candlestick/index.ts

@@ -2,12 +2,13 @@ import { ref, computed, watch } from 'vue'
 //import { timerInterceptor } from '@/utils/timer'
 import { ChartCycleType } from '@/constants/chart'
 import { queryHistoryDatas } from '@/services/api/quote'
-import { futuresStore } from '@/stores'
+import { useFuturesStore } from '@/stores'
 import { useDataset } from './dataset'
 import { useOptions } from './options'
 import moment from 'moment';
 
 export function useCandlestickChart(goodscode: string) {
+    const { getQuoteDayInfoByCode } = useFuturesStore()
     const { dataset, handleData, clearData, calcIndicator } = useDataset();
     const { options, initOptions, updateOptions } = useOptions(dataset);
 
@@ -15,7 +16,7 @@ export function useCandlestickChart(goodscode: string) {
     const isEmpty = ref(true);
     const dataIndex = ref(-1); // 当前数据索引值
     const cycleType = ref(ChartCycleType.Minutes);
-    const quote = futuresStore.getQuoteDayInfoByCode(goodscode); // 实时行情
+    const quote = getQuoteDayInfoByCode(goodscode); // 实时行情
 
     // 当前选中的数据项
     const selectedItem = computed(() => {

+ 4 - 4
src/hooks/echarts/candlestick/options.ts

@@ -1,11 +1,11 @@
 import { reactive, watch } from 'vue'
 import { ECOption } from '@/components/base/echarts/core'
 import { timerInterceptor } from '@/utils/timer'
-import { localData } from '@/stores'
+import { useThemeStore } from '@/stores'
 import { EchartsDataset, EchartsOptions, Colors } from './interface'
 import moment from 'moment'
 
-const theme = localData.getRef('appTheme');
+const { appTheme } = useThemeStore();
 
 function getColors() {
     // 默认主题色配置
@@ -26,7 +26,7 @@ function getColors() {
         }
     }
 
-    return colors[theme.value];
+    return colors[appTheme.value];
 }
 
 export function useOptions(dataset: EchartsDataset) {
@@ -370,7 +370,7 @@ export function useOptions(dataset: EchartsDataset) {
     }, 1000)
 
     // 监听主题变化
-    watch(theme, () => {
+    watch(appTheme, () => {
         options.colors = getColors();
         initOptions();
     })

+ 3 - 2
src/hooks/echarts/timeline/index.ts

@@ -1,19 +1,20 @@
 import { ref, computed, watch } from 'vue'
 //import { timerInterceptor } from '@/utils/timer'
 import { queryTSData } from '@/services/api/quote'
-import { futuresStore } from '@/stores'
+import { useFuturesStore } from '@/stores'
 import { useDataset } from './dataset'
 import { useOptions } from './options'
 import moment from 'moment';
 
 export function useTimelineChart(goodscode: string) {
+    const { getQuoteDayInfoByCode } = useFuturesStore()
     const { dataset, handleData, clearData, calcIndicator } = useDataset();
     const { options, initOptions, updateOptions } = useOptions(dataset);
 
     const loading = ref(false);
     const isEmpty = ref(false);
     const dataIndex = ref(-1); // 当前数据索引值
-    const quote = futuresStore.getQuoteDayInfoByCode(goodscode); // 实时行情
+    const quote = getQuoteDayInfoByCode(goodscode); // 实时行情
 
     // 当前选中的数据项
     const selectedItem = computed(() => {

+ 4 - 4
src/hooks/echarts/timeline/options.ts

@@ -1,10 +1,10 @@
 import { reactive, watch } from 'vue'
 import { timerInterceptor } from '@/utils/timer'
-import { localData } from '@/stores'
+import { useThemeStore } from '@/stores'
 import { echarts } from '@/components/base/echarts/core'
 import { EchartsDataset, EchartsOptions, Colors } from './interface'
 
-const theme = localData.getRef('appTheme');
+const { appTheme } = useThemeStore();
 
 function getColors() {
     // 默认主题色配置
@@ -45,7 +45,7 @@ function getColors() {
         }
     }
 
-    return colors[theme.value];
+    return colors[appTheme.value];
 }
 
 export function useOptions(dataset: EchartsDataset) {
@@ -245,7 +245,7 @@ export function useOptions(dataset: EchartsDataset) {
     }, 1000)
 
     // 监听主题变化
-    watch(theme, () => {
+    watch(appTheme, () => {
         options.colors = getColors();
         initOptions();
     })

+ 4 - 4
src/packages/mobile/router/index.ts

@@ -1,9 +1,11 @@
 import { createWebHashHistory, RouteRecordRaw } from 'vue-router'
-import { sessionData } from '@/stores'
+import { useLoginStore } from '@/stores'
 import service from '@/services'
 import Page from '@mobile/components/layouts/page/index.vue'
 import animateRouter from './animateRouter'
 
+const { getToken } = useLoginStore()
+
 const routes: Array<RouteRecordRaw> = [
   {
     path: '/:pathMatch(.*)*',
@@ -65,11 +67,9 @@ const router = animateRouter.create({
 
 // 路由跳转拦截
 router.beforeEach((to, from, next) => {
-  const token = sessionData.getLoginInfo('Token');
-
   // 判断服务是否加载完成
   if (service.isReady) {
-    if (to.meta.requireAuth && !token) {
+    if (to.meta.requireAuth && !getToken()) {
       next({
         name: 'login',
         query: { redirect: to.fullPath },

+ 8 - 7
src/packages/mobile/views/home/components/market/index.vue

@@ -5,7 +5,7 @@
       <Grid :column-num="3" style="margin-bottom: .2rem;">
         <GridItem icon="photo-o" text="商品" :to="{ name: 'order' }" v-for="index in 6" :key="index" />
       </Grid>
-      <app-table :data-list="quoteList" v-model:columns="columns" @row-click="rowClick">
+      <app-table :data-list="quoteDayList" v-model:columns="columns" @row-click="rowClick">
         <template #expandRow>
           扩展
         </template>
@@ -34,15 +34,16 @@
 <script lang="ts" setup>
 import { ref, reactive, onActivated, onDeactivated } from 'vue'
 import { Grid, GridItem, Button, ActionSheet, ActionSheetAction } from 'vant'
-import { futuresStore, localData } from '@/stores'
+import { useFuturesStore, useThemeStore } from '@/stores'
 import { handlePriceColor, handleNoneValue } from '@/filters'
 import AppTable from '@mobile/components/base/table/index.vue'
 import { TableColumn } from '@mobile/components/base/table/interface'
 import subscribe from '@/services/subscribe'
 
-const showAction = ref(false);
-const { quoteList } = futuresStore;
+const { setTheme } = useThemeStore();
+const { quoteDayList } = useFuturesStore();
 const quoteSubscribe = subscribe.addQuoteSubscribe(['cu2206', 'cu2207', 'cu2208', 'cu2209', 'cu2301', 'cu2303', 'cu2304']);
+const showAction = ref(false);
 
 const columns = reactive<TableColumn[]>([
   { prop: 'goodscode', label: '合约' },
@@ -62,15 +63,15 @@ const actions: ActionSheetAction[] = [
 const themeChange = (action: ActionSheetAction) => {
   switch (action.name) {
     case '默认': {
-      localData.setTheme('Default')
+      setTheme('Default');
       break;
     }
     case '浅色': {
-      localData.setTheme('Light');
+      setTheme('Light');
       break;
     }
     case '深色': {
-      localData.setTheme('Dark')
+      setTheme('Dark')
       break;
     }
   }

+ 2 - 2
src/packages/pc/components/layouts/footer/index.vue

@@ -45,10 +45,10 @@
 
 <script lang="ts" setup>
 import { handlePriceColor } from '@/filters'
-import { accountStore } from '@/stores'
+import { useAccountStore } from '@/stores'
 import AppAuthComponent from '@pc/components/modules/auth-component/index.vue'
 
-const { accountList, accountInfo, accountId, loading, getAccountPositionList } = accountStore
+const { accountList, accountInfo, accountId, loading, getAccountPositionList } = useAccountStore()
 
 // 切换资金账户
 const onAccountChange = () => {

+ 9 - 5
src/packages/pc/components/modules/auth-operation/index.vue

@@ -74,7 +74,7 @@ const props = defineProps({
         default: false,
     },
     menus: {
-        type: Array as PropType<string[]>,
+        type: Array as PropType<string[] | null>,
         default: () => ([]),
     },
 })
@@ -86,11 +86,15 @@ const auth = shallowRef<Ermcp.UserMenu[]>([]);
 
 // 数据列表
 const dataList = computed(() => {
-    if (props.menus.length) {
-        return auth.value.filter((e) => props.menus.includes(e.code) || props.menus.includes(e.buttonName));
-    } else {
-        return auth.value;
+    const menus = props.menus
+    if (menus) {
+        if (menus.length) {
+            return auth.value.filter((e) => menus.includes(e.code) || menus.includes(e.buttonName));
+        } else {
+            return auth.value;
+        }
     }
+    return []
 })
 
 // 右键菜单坐标

+ 4 - 2
src/packages/pc/main.ts

@@ -5,7 +5,7 @@ import directives from '@/directives' // 自定义指令集
 import '@/services/subscribe' // 全局订阅通知
 import '@/mock' // 模拟数据
 import client from '@/utils/client' // 适配客户端
-import language from '@/services/language' // 国际化语言
+import { useLanguageStore } from '@/stores' // 国际化语言
 import layouts from "./components/layouts" // 布局组件
 import ElementPlus from 'element-plus'
 import * as ElementIcons from '@element-plus/icons-vue'
@@ -13,12 +13,14 @@ import 'element-plus/dist/index.css'
 import './assets/themes/style.less' // 主题样式
 import { timerInterceptor } from '@/utils/timer'
 
+const languageStore = useLanguageStore()
+
 const app = createApp(App)
 app.use(router)
 app.use(directives)
 app.use(ElementPlus)
 app.use(layouts)
-app.use(language.i18n)
+app.use(languageStore.i18n)
 app.mount('#app')
 
 // 等待 html 加载完成

+ 5 - 21
src/packages/pc/router/dynamicRouter.ts

@@ -1,7 +1,6 @@
 import { RouteRecordRaw } from 'vue-router'
 import { AuthType } from '@/constants/menu'
-import { queryAccountMenu } from '@/services/api/account'
-import { sessionData } from '@/stores'
+import { useMenuStore } from '@/stores'
 import router from '../router'
 
 export default new (class {
@@ -69,24 +68,9 @@ export default new (class {
      * @returns 
      */
     registerRoutes() {
-        const menus = sessionData.getValue('userMenus');
-        return new Promise((resolve, reject) => {
-            this.addNotFound();
-            if (menus.length) {
-                this.addRoutes(menus);
-                resolve(true);
-            } else {
-                queryAccountMenu({
-                    success: (res) => {
-                        sessionData.setValue('userMenus', res.data);
-                        this.addRoutes(res.data);
-                        resolve(true);
-                    },
-                    fail: (err) => {
-                        reject(err);
-                    }
-                })
-            }
-        })
+        const { userMenus } = useMenuStore();
+        this.addNotFound();
+        this.addRoutes(userMenus.value);
+        this.isReady = true;
     }
 })

+ 7 - 9
src/packages/pc/router/index.ts

@@ -1,12 +1,14 @@
 import { createRouter, createWebHashHistory, RouteRecordRaw } from 'vue-router'
-import { sessionData } from '@/stores'
+import { useLoginStore } from '@/stores'
 import dynamicRouter from './dynamicRouter'
 import service from '@/services'
 
+const { getToken } = useLoginStore()
+
 const routes: Array<RouteRecordRaw> = [
     {
         path: '/',
-        redirect: () => sessionData.getLoginInfo('Token') ? '/market/futures' : '/login', // 重定向到默认页面
+        redirect: () => getToken() ? '/market/futures' : '/login', // 重定向到默认页面
     },
     {
         path: '/login',
@@ -39,7 +41,7 @@ router.beforeEach((to, from, next) => {
 
     // 判断服务是否加载完成
     if (service.isReady) {
-        if (sessionData.getLoginInfo('Token')) {
+        if (getToken()) {
             if (dynamicRouter.isReady) {
                 if (isLoginOrRegister) {
                     next('/');
@@ -48,12 +50,8 @@ router.beforeEach((to, from, next) => {
                 }
             } else {
                 // 注册动态路由
-                dynamicRouter.registerRoutes().then(() => {
-                    dynamicRouter.isReady = true;
-                    next({ ...to, replace: true });
-                }).catch(() => {
-                    // 404?
-                })
+                dynamicRouter.registerRoutes();
+                next({ ...to, replace: true });
             }
         } else {
             dynamicRouter.isReady = false;

+ 9 - 8
src/packages/pc/views/boot/index.vue

@@ -10,11 +10,13 @@ import { ref } from 'vue'
 import { ElMessage } from 'element-plus'
 import { useRoute, useRouter } from 'vue-router'
 import { initBaseData, checkToken } from '@/business/common'
-import { sessionData } from '@/stores'
+import { useEnumStore, useErrorInfoStore, useLoginStore } from '@/stores'
 import service from '@/services'
-import eventBus from '@/services/bus'
 import socket from '@/services/socket'
 
+const { getToken } = useLoginStore()
+const { getAllEnumList } = useEnumStore()
+const { getErrorInfoList } = useErrorInfoStore()
 const route = useRoute()
 const router = useRouter()
 const loading = ref(false)
@@ -28,18 +30,17 @@ const initService = async () => {
     loading.value = false
   })
 
-  if (sessionData.getLoginInfo('Token')) {
+  await getAllEnumList()
+  await getErrorInfoList()
+
+  if (getToken()) {
     // 等待连接交易服务
     await socket.connectTrade().catch((err) => {
       ElMessage.error(err)
       loading.value = false
     })
-
     // 等待令牌效验
-    await checkToken().catch(() => {
-      eventBus.$emit('LogoutNotify')
-    })
-
+    await checkToken()
     // 等待业务数据初始化
     await initBaseData().catch((err) => {
       ElMessage.error(err)

+ 1 - 3
src/packages/pc/views/error/index.less

@@ -1,3 +1 @@
-.error {
-    color: #fff;
-}
+.error {}

+ 2 - 2
src/packages/pc/views/footer/futures/position/index.vue

@@ -13,9 +13,9 @@
 <script lang="ts" setup>
 import { handlePriceColor } from '@/filters'
 import AppTable from '@pc/components/base/table/index.vue'
-import { accountStore } from '@/stores'
+import { useAccountStore } from '@/stores'
 
-const { loading, futuresPositionList } = accountStore
+const { loading, futuresPositionList } = useAccountStore()
 
 const columns = [
     {

+ 2 - 2
src/packages/pc/views/market/futures/index.vue

@@ -37,10 +37,10 @@
 <script lang="ts" setup>
 import { onMounted, onUnmounted, h } from 'vue'
 import { handlePriceColor } from '@/filters'
-import { futuresStore } from '@/stores'
+import { useFuturesStore } from '@/stores'
 import subscribe from '@/services/subscribe'
 
-const { quoteList } = futuresStore
+const { quoteList } = useFuturesStore()
 const quoteSubscribe = subscribe.addQuoteSubscribe(quoteList.value.map((e) => e.goodscode))
 
 const columns = [

+ 0 - 2
src/packages/pc/views/system/menu/components/edit/index.vue

@@ -67,7 +67,6 @@
 <script lang="ts" setup>
 import { ref, reactive, PropType, defineAsyncComponent } from 'vue'
 import { AuthType, UrlType, getAuthTypeList, getUrlTypeList } from '@/constants/menu'
-import { useAuth } from '@/hooks/auth'
 import AppDrawer from '@pc/components/base/drawer/index.vue'
 
 const components = {
@@ -95,7 +94,6 @@ const props = defineProps({
     }
 })
 
-const { userMenus } = useAuth()
 const show = ref(true)
 const form = reactive({ ...props.selectedRow })
 

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

@@ -20,4 +20,11 @@ export function queryTableDefine(params: HttpRequest<{ req: Ermcp.TableDefineReq
  */
 export function queryAllEnums(params: HttpRequest<{ req: Ermcp.EnumReq, rsp: Ermcp.EnumRsp[] }>) {
     return httpRequest('/Common/GetAllEnums', 'get', params);
+}
+
+/**
+ * 获取数据库错误信息
+ */
+export function queryErrorInfos(params: HttpRequest<{ req: Ermcp.ErrorInfosReq, rsp: Ermcp.ErrorInfosRsp[] }>) {
+    return httpRequest('/Common/QueryErrorInfos', 'get', params);
 }

+ 3 - 2
src/services/http/index.ts

@@ -2,7 +2,7 @@ import axios, { AxiosRequestConfig, Method } from 'axios'
 //import qs from 'qs'
 //import cryptojs from 'crypto-js'
 import { addPending, removePending } from './pending'
-import { sessionData } from '@/stores'
+import { useLoginStore } from '@/stores'
 import { HttpRequest, HttpResponse, ResultCode } from './interface'
 import service from '@/services'
 
@@ -15,12 +15,13 @@ const http = axios.create({
  */
 http.interceptors.request.use(
     (config) => {
+        const { getToken } = useLoginStore();
         removePending(config); //在请求开始前,对之前的请求做检查取消操作
         addPending(config); //将当前请求添加到列表中
 
         //请求头签名
         const sign = {
-            token: sessionData.getLoginInfo('Token'),
+            token: getToken(),
             signsecret: 'qz7qWOMXKTMT5JlDs5w4NTPwWeR3xhF1v6wqbZ9cExmP6cc3spvNAp1wJJ1SqRI5',
             timestamp: new Date().getTime(),
         };

+ 0 - 30
src/services/language/index.ts

@@ -1,30 +0,0 @@
-import axios from 'axios'
-import { createI18n } from 'vue-i18n'
-import { Language } from '@/constants/language'
-import { localData } from '@/stores'
-
-export default new (class {
-    i18n = createI18n()
-
-    constructor() {
-        const lang = localData.getValue('lang')
-        this.setLanguage(lang)
-    }
-
-    /**
-     * 设置语言
-     * @param lang 
-     */
-    async setLanguage(lang: Language) {
-        const locale = this.i18n.global.getLocaleMessage(lang)
-
-        if (!Object.keys(locale).length) {
-            await axios(`./language/${lang}.json`).then((res) => {
-                this.i18n.global.setLocaleMessage(lang, res.data)
-            })
-        }
-
-        this.i18n.global.locale = lang
-        localData.setValue('lang', lang);
-    }
-})

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

@@ -6,6 +6,7 @@ import { FunCode } from '@/constants/funcode'
 import { parseReceivePush } from './quote/build/decode'
 import protobuf from './trade/protobuf'
 import { checkToken, stopCheckToken, checkTokenLoop } from '@/business/common'
+import { useErrorInfoStore } from '@/stores/modules/errorInfo'
 import service from '@/services'
 import eventBus from '@/services/bus'
 
@@ -34,6 +35,7 @@ export default new (class {
         }
 
         this.tradeServer.onPush = (p) => {
+            const { getErrorInfoByCode } = useErrorInfoStore();
             const { funCode, content } = p;
             const delay = 1000; // 延迟推送消息,防止短时间内重复请求
 
@@ -48,7 +50,9 @@ export default new (class {
                 case FunCode.LogoutRsp: {
                     // 通知上层 用户登出
                     protobuf.responseDecode<Proto.LogoutRsp>(FunCode[funCode], content).then(({ RetCode, RetDesc }) => {
-                        eventBus.$emit('LogoutNotify', (RetDesc || RetCode)?.toString());
+                        const msg = getErrorInfoByCode(RetCode)
+                        const error = (RetDesc || RetCode).toString();
+                        eventBus.$emit('LogoutNotify', msg ?? error);
                     })
                     break;
                 }

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

@@ -4,7 +4,7 @@ import { FunCode } from '@/constants/funcode'
 import { QuoteRequest } from './interface'
 import { subscribeListToByteArrary } from './build/encode'
 import { parseSubscribeRsp } from './build/decode'
-import { sessionData } from '@/stores'
+import { useLoginStore } from '@/stores'
 import socket from '../index'
 
 /**
@@ -12,8 +12,8 @@ import socket from '../index'
  * @param params 
  */
 function quoteServerMiddleware(params: QuoteRequest): Promise<Proto.QuoteRsp[]> {
-    const { LoginID, Token } = sessionData.getValue('loginInfo');
-    const content = subscribeListToByteArrary(params.data, Token, Long.fromNumber(LoginID));
+    const { getLoginId, getToken } = useLoginStore();
+    const content = subscribeListToByteArrary(params.data, getToken(), Long.fromNumber(getLoginId()));
 
     return new Promise((resolve, reject) => {
         socket.sendQuoteServer({

+ 8 - 4
src/services/socket/trade/index.ts

@@ -1,7 +1,8 @@
 import { v4 } from 'uuid'
 import { Package50 } from '@/utils/websocket/package'
 import { FunCode } from '@/constants/funcode'
-import { sessionData } from '@/stores'
+import { useLoginStore } from '@/stores/modules/login'
+import { useErrorInfoStore } from '@/stores/modules/errorInfo'
 import { IMessageHead } from './protobuf/proto'
 import { TradeRequest, TradeResponse } from './interface'
 import Protobuf from './protobuf'
@@ -11,11 +12,12 @@ import socket from '../index'
  * 构建消息头部
  */
 function getProtoHeader(funCode: keyof typeof FunCode, header?: IMessageHead, marketId?: number) {
+    const { getUserId } = useLoginStore();
     // 组合请求头
     const protoHeader: IMessageHead = {
         FunCode: FunCode[funCode],
         UUID: v4(),
-        UserID: sessionData.getLoginInfo('UserID'),
+        UserID: getUserId(),
         ...(header ?? {})
     }
     if (marketId) {
@@ -72,6 +74,7 @@ function tradeServerMiddleware<Req, Rsp>(reqKey: keyof typeof FunCode, rspKey: k
  * @param marketId 
  */
 export async function tradeServerRequest<Req, Rsp>(reqKey: keyof typeof FunCode, rspKey: keyof typeof FunCode, params: TradeRequest<Req, Rsp & TradeResponse>, marketId?: number) {
+    const { getErrorInfoByCode } = useErrorInfoStore();
     const { success, fail, complete } = params;
 
     await tradeServerMiddleware(reqKey, rspKey, params, marketId).then((res) => {
@@ -94,8 +97,9 @@ export async function tradeServerRequest<Req, Rsp>(reqKey: keyof typeof FunCode,
                 return Promise.reject(res.RetDesc);
             }
             default: {
-                const msg = (RetDesc || RetCode).toString();
-                return Promise.reject(msg);
+                const msg = getErrorInfoByCode(RetCode)
+                const error = (RetDesc || RetCode).toString();
+                return Promise.reject(msg ?? error);
             }
         }
     }).catch((err) => {

+ 3 - 2
src/services/subscribe/index.ts

@@ -1,6 +1,6 @@
 import { v4 } from 'uuid'
 import { quoteServerRequest } from '@/services/socket/quote'
-import { sessionData } from '@/stores'
+import { useLoginStore } from '@/stores'
 import eventBus from '@/services/bus'
 import socket from '@/services/socket'
 
@@ -61,6 +61,7 @@ export default new (class {
      * @returns 
      */
     addQuoteSubscribe = (goodsCodes: string[], key?: string) => {
+        const { getToken } = useLoginStore()
         const uuid = key ?? v4()
         const value = this.quoteSubscribeMap.get(uuid) ?? []
 
@@ -78,7 +79,7 @@ export default new (class {
                 if (flag) {
                     console.log('删除订阅', uuid)
                 }
-                if (sessionData.getLoginInfo('Token')) {
+                if (getToken()) {
                     this.quoteSubscribe()
                 }
                 return flag

+ 49 - 0
src/stores/base.ts

@@ -0,0 +1,49 @@
+import { reactive, toRefs, ComputedRef, UnwrapNestedRefs, ToRefs } from 'vue'
+
+/**
+ * 状态存储
+ */
+export interface Store<T> {
+    state: UnwrapNestedRefs<T>;
+    getters?: { [propName: string]: ComputedRef };
+    actions?: { [propName: string]: (...args: never[]) => unknown };
+    methods?: {
+        $setData: (callback: (state: UnwrapNestedRefs<T>) => void) => void;
+        $storeToRefs: () => ToRefs<UnwrapNestedRefs<T>>;
+    }
+}
+
+/**
+ * 状态存储控制类
+ */
+export abstract class VueStore<T extends object> implements Store<T>{
+    constructor(state: T) {
+        this.state = reactive<T>(state)
+    }
+
+    state
+    getters = {}
+    actions = {}
+    methods = {
+        $setData: (callback: (state: UnwrapNestedRefs<T>) => void) => {
+            callback(this.state)
+        },
+        $storeToRefs: () => {
+            return toRefs(this.state)
+        },
+    }
+}
+
+/**
+ * 创建状态管理
+ * @param store
+ * @returns
+ */
+// export function createStore<T extends object>(store: VueStore<T>) {
+//     return shallowReadonly({
+//         ...toRefs(store.state),
+//         ...store.getters,
+//         ...store.actions,
+//         ...store.methods,
+//     })
+// }

+ 34 - 18
src/stores/index.ts

@@ -1,22 +1,38 @@
-import { localData, sessionData } from './modules/storage'
-import commonStore from './modules/common'
-import accountStore from './modules/account'
-import futuresStore from './modules/futures'
+import { useLoginStore } from './modules/login'
+import { useUserStore } from './modules/user'
+import { useThemeStore } from './modules/theme'
+import { useMenuStore } from './modules/menu'
+import { useAccountStore } from './modules/account'
+import { useFuturesStore } from './modules/futures'
+import { useLanguageStore } from './modules/language'
+import { useEnumStore } from './modules/enum'
+import { useErrorInfoStore } from './modules/errorInfo'
+import { useTableColumnStore } from './modules/tableColumn'
 
-/**
- * 重置数据
- */
-const resetStore = () => {
-    localData.clear()
-    sessionData.clear()
-    accountStore.reset()
+export {
+    useLoginStore,
+    useUserStore,
+    useThemeStore,
+    useMenuStore,
+    useAccountStore,
+    useFuturesStore,
+    useLanguageStore,
+    useEnumStore,
+    useErrorInfoStore,
+    useTableColumnStore,
 }
 
-export {
-    localData,
-    sessionData,
-    commonStore,
-    accountStore,
-    futuresStore,
-    resetStore,
+export function useStore() {
+    return {
+        loginStore: useLoginStore(),
+        userStore: useUserStore(),
+        themeStore: useThemeStore(),
+        menuStore: useMenuStore(),
+        accountStore: useAccountStore(),
+        futuresStore: useFuturesStore(),
+        languageStore: useLanguageStore(),
+        enumStore: useEnumStore(),
+        errorInfoStore: useErrorInfoStore(),
+        tableColumnStore: useTableColumnStore(),
+    }
 }

+ 113 - 90
src/stores/modules/account.ts

@@ -1,14 +1,24 @@
-import { ref, computed } from 'vue'
+import { computed, toRefs, shallowReadonly } from 'vue'
 import { v4 } from 'uuid'
 import { formatDecimal } from '@/filters'
 import { BuyOrSell } from '@/constants/buyorsell'
 import { queryTaAccounts } from '@/services/api/account'
 import { queryErmcpTradePosition } from '@/services/api/trade'
 import subscribe from '@/services/subscribe'
-import { sessionData } from './storage'
-import futuresStore from './futures'
+import { useLoginStore } from './login'
+import { useFuturesStore } from './futures'
+import { VueStore } from '../base'
+import eventBus from '@/services/bus'
+
+interface StoreState {
+    loading: boolean;
+    accountId: number; // 当前资金账户ID
+    accountList: Ermcp.TaAccountsRsp[]; // 资金账户列表
+    accountPositionList: Ermcp.TradePositionRsp[]; // 资金账户持仓列表
+}
 
 function useFormula(item: Ermcp.TradePositionRsp) {
+    const { getGoodsPriceByCode } = useFuturesStore()
     const { goodscode, buyorsell, curpositionqty, agreeunit, positionpl, opencost, positioncost, decimalplace } = item
 
     // 计算开仓均价
@@ -25,7 +35,7 @@ function useFormula(item: Ermcp.TradePositionRsp) {
 
     // 计算浮动盈亏 - 对应市场收益权
     const calcProfitLoss = () => {
-        const last = futuresStore.getGoodsPriceByCode(goodscode)
+        const last = getGoodsPriceByCode(goodscode)
         let result = positionpl
 
         if (last.value) {
@@ -47,19 +57,28 @@ function useFormula(item: Ermcp.TradePositionRsp) {
 /**
  * 账户存储类
  */
-export default new (class {
+const store = new (class extends VueStore<StoreState>{
+    constructor() {
+        const state: StoreState = {
+            loading: false,
+            accountId: 0,
+            accountList: [],
+            accountPositionList: [],
+        }
+        super(state)
+
+        // 接收资金变动通知
+        eventBus.$on('MoneyChangedNotify', () => {
+            this.actions.getAccountList()
+        })
+    }
+
     private uuid = v4()
-    pending = Promise.resolve() // 请求等待状态,防止重复请求
-    loading = ref(false)
-    accountId = ref(0) // 当前资金账户ID
-    accountList = ref<Ermcp.TaAccountsRsp[]>([]) // 资金账户列表
-    accountPositionList = ref<Ermcp.TradePositionRsp[]>([]) // 资金账户持仓列表
-
-    /**
-     * 当前资金账户信息
-     */
-    accountInfo = computed(() => {
-        const account = this.accountList.value.find((e) => e.accountid === this.accountId.value)
+    private pending = Promise.resolve() // 请求等待状态,防止重复请求
+
+    /** 当前资金账户信息 */
+    private accountInfo = computed(() => {
+        const account = this.state.accountList.find((e) => e.accountid === this.state.accountId)
         // 总浮动盈亏
         const totalProfitLoss = this.futuresPositionList.value.reduce((res, cur) => res + cur.positionpl, 0)
         return {
@@ -68,11 +87,9 @@ export default new (class {
         }
     })
 
-    /**
-     * 期货持仓列表
-     */
-    futuresPositionList = computed(() => {
-        const positionList = this.accountPositionList.value
+    /** 期货持仓列表 */
+    private futuresPositionList = computed(() => {
+        const positionList = this.state.accountPositionList
         return positionList.reduce((res, item) => {
             const { calcProfitLoss } = useFormula(item)
             res.push({
@@ -83,79 +100,85 @@ export default new (class {
         }, [] as Ermcp.TradePositionRsp[])
     })
 
-    /**
-     * 获取资金账户列表
-     * @returns 
-     */
-    getAccountList = async () => {
-        await this.pending
-        this.loading.value = true
-
-        this.pending = queryTaAccounts({
-            data: {
-                loginID: sessionData.getLoginInfo('LoginID')
-            },
-            success: (res) => {
-                const dataList = res.data
-                if (dataList.length) {
-                    this.accountList.value = dataList
-                    // 查找当前选中的资金账户
-                    const account = dataList.find((e) => e.accountid === this.accountId.value)
-                    if (account) {
-                        this.loading.value = false
+    getters = {
+        accountInfo: this.accountInfo,
+        futuresPositionList: this.futuresPositionList
+    }
+
+    actions = {
+        /** 获取资金账户列表 */
+        getAccountList: async () => {
+            await this.pending
+            const { getLoginId } = useLoginStore()
+            this.state.loading = true
+
+            this.pending = queryTaAccounts({
+                data: {
+                    loginID: getLoginId()
+                },
+                success: (res) => {
+                    const dataList = res.data
+                    if (dataList.length) {
+                        this.state.accountList = dataList
+                        // 查找当前选中的资金账户
+                        const account = dataList.find((e) => e.accountid === this.state.accountId)
+                        if (account) {
+                            this.state.loading = false
+                        } else {
+                            // 如果不存在,默认选中第一个账户
+                            this.state.accountId = dataList[0].accountid
+                            this.actions.getAccountPositionList()
+                        }
                     } else {
-                        // 如果不存在,默认选中第一个账户
-                        this.accountId.value = dataList[0].accountid
-                        this.getAccountPositionList()
+                        this.state.loading = false
+                        this.actions.reset()
                     }
-                } else {
-                    this.loading.value = false
-                    this.reset()
+                },
+                fail: () => {
+                    this.state.loading = false
                 }
-            },
-            fail: () => {
-                this.loading.value = false
-            }
-        })
-        return this.pending
-    }
+            })
+            return this.pending
+        },
+        /** 获取资金账户持仓列表 */
+        getAccountPositionList: async (marketID?: number) => {
+            await this.pending
+            subscribe.removeQuoteSubscribe(this.uuid)
+            this.state.loading = true
 
-    /**
-     * 获取资金账户持仓列表
-     * @param marketID 
-     * @returns 
-     */
-    getAccountPositionList = async (marketID?: number) => {
-        await this.pending
-        subscribe.removeQuoteSubscribe(this.uuid)
-        this.loading.value = true
-
-        this.pending = queryErmcpTradePosition({
-            data: {
-                accountID: this.accountId.value,
-                ...marketID ? { marketID } : {}
-            },
-            success: (res) => {
-                const codes = res.data.map((e) => e.goodscode)
-                this.accountPositionList.value = res.data
-                if (codes.length) {
-                    // 行情订阅
-                    subscribe.addQuoteSubscribe(codes, this.uuid).start()
+            this.pending = queryErmcpTradePosition({
+                data: {
+                    accountID: this.state.accountId,
+                    ...marketID ? { marketID } : {}
+                },
+                success: (res) => {
+                    const codes = res.data.map((e) => e.goodscode)
+                    this.state.accountPositionList = res.data
+                    if (codes.length) {
+                        // 行情订阅
+                        subscribe.addQuoteSubscribe(codes, this.uuid).start()
+                    }
+                },
+                complete: () => {
+                    this.state.loading = false
                 }
-            },
-            complete: () => {
-                this.loading.value = false
-            }
-        })
-        return this.pending
+            })
+            return this.pending
+        },
+        /** 重置数据 */
+        reset: () => {
+            this.state.accountId = 0
+            this.state.accountList = []
+            this.state.accountPositionList = []
+        }
     }
+})
 
-    /**
-     * 重置数据
-     */
-    reset = () => {
-        this.accountId.value = 0
-        this.accountList.value = []
-        this.accountPositionList.value = []
-    }
-})
+export function useAccountStore() {
+    return shallowReadonly({
+        ...toRefs(store.state),
+        ...store.getters,
+        ...store.actions,
+        ...store.methods,
+    })
+}

+ 0 - 45
src/stores/modules/common.ts

@@ -1,45 +0,0 @@
-import { ref } from 'vue'
-import { queryLoginData } from '@/services/api/account'
-import { sessionData } from './storage'
-
-/**
- * 公共存储类
- */
-export default new (class {
-    loading = ref(false)
-    loginData = ref<Ermcp.LoginQueryRsp>({
-        arearole: [],
-        externalExchanges: [],
-        goodsgroups: [],
-        markets: [],
-        systemParams: []
-    })
-
-    /**
-     * 获取登录数据
-     * @returns 
-     */
-    getLoginData = () => {
-        this.loading.value = true
-        return queryLoginData({
-            data: {
-                loginID: sessionData.getLoginInfo('LoginID')
-            },
-            success: (res) => {
-                this.loginData.value = res.data
-            },
-            complete: () => {
-                this.loading.value = false
-            }
-        })
-    }
-
-    /**
-     * 获取登录数据
-     * @param key 
-     * @returns 
-     */
-    getLoginDataInfo = <K extends keyof Ermcp.LoginQueryRsp>(key: K) => {
-        return this.loginData.value[key]
-    }
-})

+ 95 - 0
src/stores/modules/enum.ts

@@ -0,0 +1,95 @@
+import { toRefs, shallowReadonly, ShallowRef } from 'vue'
+import { queryAllEnums } from '@/services/api/common'
+import { VueStore } from '../base'
+import WebStorage from '@/utils/storage/base'
+
+/**
+ * 枚举类型
+ */
+interface EnumType {
+    label: string;
+    value: number;
+    disabled?: boolean;
+}
+
+interface StoreState {
+    loading: boolean;
+    allEnums: ShallowRef<Ermcp.EnumRsp[]>;
+}
+
+/**
+ * 枚举存储类
+ */
+const store = new (class extends VueStore<StoreState>{
+    constructor() {
+        const storage = new WebStorage<Ermcp.EnumRsp[]>(sessionStorage, 'allEnums', [])
+        const state: StoreState = {
+            loading: false,
+            allEnums: storage.getRef(),
+        }
+        super(state)
+    }
+
+    actions = {
+        /** 获取所有枚举列表 */
+        getAllEnumList: () => {
+            if (this.state.allEnums.length) {
+                return Promise.resolve()
+            }
+            this.state.loading = true
+            return queryAllEnums({
+                success: (res) => {
+                    this.state.allEnums = res.data
+                },
+                complete: () => {
+                    this.state.loading = false
+                }
+            })
+        },
+        /** 获取枚举类型 */
+        getEnumTypes: <T extends string[]>(...args: T) => {
+            const enumMap = new Map<typeof args[number], Ermcp.EnumRsp[]>()
+            this.state.allEnums.forEach((e) => {
+                if (args.some((code) => code.toLowerCase() === e.enumdiccode.toLowerCase())) {
+                    const enums = enumMap.get(e.enumdiccode)
+                    if (enums) {
+                        enums.push(e)
+                    } else {
+                        enumMap.set(e.enumdiccode, [e])
+                    }
+                }
+            })
+            return enumMap
+        },
+        /** 获取枚举信息 */
+        getEnumTypeInfo: (enums: Ermcp.EnumRsp[], value: number) => {
+            return enums.find((e) => e.enumitemstatus === 1 && e.enumitemname === value)
+        },
+        /** 获取枚举列表 */
+        getEnumTypeList: (enums?: Ermcp.EnumRsp[]) => {
+            if (enums) {
+                return enums.map((e) => ({
+                    label: e.enumdicname,
+                    value: e.enumitemname,
+                }))
+            }
+            return []
+        },
+        /** 根据枚举值获取枚举名称 */
+        getEnumTypeName: (enums: EnumType[], value?: number) => {
+            const item = enums.find((e) => e.value === value)
+            if (item) {
+                return item.label
+            }
+            return '--'
+        }
+    }
+})
+
+export function useEnumStore() {
+    return shallowReadonly({
+        ...toRefs(store.state),
+        ...store.actions,
+        ...store.methods,
+    })
+}

+ 55 - 0
src/stores/modules/errorInfo.ts

@@ -0,0 +1,55 @@
+import { toRefs, shallowReadonly, ShallowRef } from 'vue'
+import { queryErrorInfos } from '@/services/api/common'
+import { VueStore } from '../base'
+import WebStorage from '@/utils/storage/base'
+
+interface StoreState {
+    loading: boolean;
+    errorInfos: ShallowRef<Ermcp.ErrorInfosRsp[]>;
+}
+
+/**
+ * 错误信息存储类
+ */
+const store = new (class extends VueStore<StoreState>{
+    constructor() {
+        const storage = new WebStorage<Ermcp.ErrorInfosRsp[]>(sessionStorage, 'errorInfos', [])
+        const state: StoreState = {
+            loading: false,
+            errorInfos: storage.getRef(),
+        }
+        super(state)
+    }
+
+    actions = {
+        /** 获取系统错误信息 */
+        getErrorInfoList: () => {
+            if (this.state.errorInfos.length) {
+                return Promise.resolve()
+            }
+            this.state.loading = true
+            return queryErrorInfos({
+                success: (res) => {
+                    this.state.errorInfos = res.data
+                },
+                complete: () => {
+                    this.state.loading = false
+                }
+            })
+        },
+        /** 根据 code 获取错误信息 */
+        getErrorInfoByCode: (code: number) => {
+            const errorInfos = this.state.errorInfos
+            const error = errorInfos.find((e) => e.errorid === code)
+            return error?.description
+        }
+    }
+})
+
+export function useErrorInfoStore() {
+    return shallowReadonly({
+        ...toRefs(store.state),
+        ...store.actions,
+        ...store.methods,
+    })
+}

+ 83 - 69
src/stores/modules/futures.ts

@@ -1,28 +1,53 @@
-import { ref, computed } from 'vue'
+import { computed, toRefs, shallowReadonly } from 'vue'
 import { timerInterceptor } from '@/utils/timer'
 import { queryGoodsList } from '@/services/api/goods'
-import { sessionData } from './storage'
-import moment from 'moment'
+import { useLoginStore } from './login'
+import { VueStore } from '../base'
 import eventBus from '@/services/bus'
+import moment from 'moment'
+
+interface StoreState {
+    loading: boolean;
+    goodsList: Ermcp.GoodsRsp[]; // 商品列表
+    quoteDayList: Ermcp.QuoteDay[]; // 盘面列表
+}
 
 /**
- * 商品存储类
+ * 期货存储类
  */
-export default new (class {
-    private quotes: Proto.Quote[] = [] // 行情数据
+const store = new (class extends VueStore<StoreState>{
+    constructor() {
+        const state: StoreState = {
+            loading: false,
+            goodsList: [],
+            quoteDayList: [],
+        }
+        super(state)
+
+        // 接收行情推送通知
+        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()
+        })
+    }
 
-    loading = ref(false)
-    goodsList = ref<Ermcp.GoodsRsp[]>([]) // 商品列表
-    quoteDayList = ref<Ermcp.QuoteDay[]>([]) // 盘面列表
+    /** 行情数据 */
+    private quotes: Proto.Quote[] = []
 
-    /**
-     * 期货行情列表
-     */
-    quoteList = computed(() => {
-        return this.goodsList.value.reduce((res, cur) => {
+    /** 期货行情列表 */
+    private quoteList = computed(() => {
+        return this.state.goodsList.reduce((res, cur) => {
             const { goodscode, goodsname } = cur
-            const quote = this.getQuoteDayInfoByCode(goodscode).value
-            const item: Store.Quotation = {
+            const quote = this.actions.getQuoteDayInfoByCode(goodscode).value
+            const item: Ermcp.Quotation = {
                 goodscode,
                 goodsname,
                 last: quote?.last ?? 0,
@@ -44,30 +69,14 @@ export default new (class {
             }
             res.push(item)
             return res
-        }, [] as Store.Quotation[])
+        }, [] as Ermcp.Quotation[])
     })
 
-    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
+        const quoteList = this.state.quoteDayList
         this.quotes.forEach((item) => {
             const quote = quoteList.find((e) => e.goodscode.toUpperCase() === item.goodscode?.toUpperCase())
             const last = item.last ?? 0
@@ -225,42 +234,47 @@ export default new (class {
         })
     }, 200)
 
-    /**
-     * 获取商品列表
-     */
-    getGoodsList = () => {
-        this.loading.value = true
-        return queryGoodsList({
-            data: {
-                userid: sessionData.getLoginInfo('UserID')
-            },
-            success: (res) => {
-                this.goodsList.value = res.data
-            },
-            complete: () => {
-                this.loading.value = false
-            }
-        })
+    getters = {
+        quoteList: this.quoteList
     }
 
-    /**
-     * 通过 goodscode 获取盘面实时行情
-     * @param goodscode 
-     * @returns 
-     */
-    getQuoteDayInfoByCode = (goodscode: string) => {
-        return computed(() => this.quoteDayList.value.find((e) => e.goodscode.toUpperCase() === goodscode.toUpperCase()))
-    }
+    actions = {
+        // 获取商品列表
+        getGoodsList: () => {
+            const { getUserId } = useLoginStore()
+            this.state.loading = true
 
-    /**
-     * 通过 goodscode 获取商品实时报价
-     * @param goodscode 
-     * @returns 
-     */
-    getGoodsPriceByCode = (goodscode: string) => {
-        return computed(() => {
-            const quote = this.getQuoteDayInfoByCode(goodscode)
-            return quote.value?.last ?? 0
-        })
+            return queryGoodsList({
+                data: {
+                    userid: getUserId()
+                },
+                success: (res) => {
+                    this.state.goodsList = res.data
+                },
+                complete: () => {
+                    this.state.loading = false
+                }
+            })
+        },
+        /** 通过 goodscode 获取盘面实时行情 */
+        getQuoteDayInfoByCode: (goodscode: string) => {
+            return computed(() => this.state.quoteDayList.find((e) => e.goodscode.toUpperCase() === goodscode.toUpperCase()))
+        },
+        /** 通过 goodscode 获取商品实时报价 */
+        getGoodsPriceByCode: (goodscode: string) => {
+            return computed(() => {
+                const quote = this.actions.getQuoteDayInfoByCode(goodscode)
+                return quote.value?.last ?? 0
+            })
+        }
     }
-})
+})
+
+export function useFuturesStore() {
+    return shallowReadonly({
+        ...toRefs(store.state),
+        ...store.getters,
+        ...store.actions,
+        ...store.methods,
+    })
+}

+ 52 - 0
src/stores/modules/language.ts

@@ -0,0 +1,52 @@
+import { toRefs, shallowReadonly, ShallowRef } from 'vue'
+import axios from 'axios'
+import { createI18n } from 'vue-i18n'
+import { Language } from '@/constants/language'
+import { VueStore } from '../base'
+import WebStorage from '@/utils/storage/base'
+
+interface StoreState {
+    lang: ShallowRef<Language>;
+}
+
+const store = new (class extends VueStore<StoreState>{
+    constructor() {
+        const storage = new WebStorage<Language>(localStorage, 'language', Language.ZhCN)
+        const state: StoreState = {
+            lang: storage.getRef()
+        }
+
+        super(state)
+        const lang = this.state.lang
+        this.actions.setLanguage(lang)
+    }
+
+    i18n = createI18n({})
+
+    actions = {
+        /** 设置语言 */
+        setLanguage: async (lang: Language) => {
+            const locale = this.i18n.global.getLocaleMessage(lang)
+
+            if (!Object.keys(locale).length) {
+                await axios(`./language/${lang}.json`).then((res) => {
+                    this.i18n.global.setLocaleMessage(lang, res.data)
+                }).catch(() => {
+                    // 默认语言
+                })
+            }
+
+            this.i18n.global.locale = lang
+            this.state.lang = lang
+        }
+    }
+})
+
+export function useLanguageStore() {
+    return shallowReadonly({
+        i18n: store.i18n,
+        ...toRefs(store.state),
+        ...store.actions,
+        ...store.methods,
+    })
+}

+ 108 - 0
src/stores/modules/login.ts

@@ -0,0 +1,108 @@
+import { toRefs, shallowReadonly, ShallowRef } from 'vue'
+import { login, queryLoginId } from '@/services/api/account'
+import { VueStore } from '../base'
+import cryptojs from 'crypto-js'
+import WebStorage from '@/utils/storage/base'
+
+interface StoreState {
+    loading: boolean;
+    loginInfo: ShallowRef<Proto.LoginRsp>;
+}
+
+function getInitData(): Proto.LoginRsp {
+    return {
+        AccountIDs: [],
+        LoginCode: '',
+        LoginID: 0,
+        LoginUserType: 0,
+        AccountStatus: 0,
+        UserID: 0,
+        ClientID: 0,
+        MemberUserID: 0,
+        Token: '',
+    }
+}
+
+/**
+ * 登录存储类
+ */
+const store = new (class extends VueStore<StoreState> {
+    constructor() {
+        const storage = new WebStorage(sessionStorage, 'loginInfo', getInitData())
+        const state: StoreState = {
+            loading: false,
+            loginInfo: storage.getRef(),
+        }
+        super(state)
+    }
+
+    actions = {
+        /** 用户登录 */
+        userLogin: (param: Proto.LoginReq) => {
+            this.state.loading = true
+            return new Promise<Proto.LoginRsp>((resolve, reject) => {
+                queryLoginId({
+                    data: {
+                        username: param.LoginID
+                    },
+                    success: (res) => {
+                        login({
+                            data: {
+                                ...param,
+                                LoginID: res.data,
+                                LoginPWD: cryptojs.SHA256(res.data + param.LoginPWD).toString(),
+                            },
+                            success: (res) => {
+                                this.state.loginInfo = res
+                                resolve(res)
+                            },
+                            fail: (err) => {
+                                reject(err)
+                            },
+                            complete: () => {
+                                this.state.loading = false
+                            }
+                        })
+                    },
+                    fail: (err) => {
+                        this.state.loading = false
+                        reject(err)
+                    }
+                })
+            })
+        },
+        /** 获取用户登录信息 */
+        getLoginInfo: <K extends keyof Proto.LoginRsp>(key: K) => {
+            return this.state.loginInfo[key]
+        },
+        /** 获取登录令牌 */
+        getToken: () => {
+            return this.state.loginInfo.Token
+        },
+        /** 获取用户ID */
+        getUserId: () => {
+            return this.state.loginInfo.UserID
+        },
+        /** 获取登录ID */
+        getLoginId: () => {
+            return this.state.loginInfo.LoginID
+        },
+        /** 获取首个账户ID */
+        getFirstAccountId: () => {
+            const accounts = this.state.loginInfo.AccountIDs
+            return accounts[0] ?? 0
+        },
+        /** 重置数据 */
+        reset: () => {
+            this.state.loginInfo = getInitData()
+        }
+    }
+})
+
+export function useLoginStore() {
+    return shallowReadonly({
+        ...toRefs(store.state),
+        ...store.actions,
+        ...store.methods,
+    })
+}

+ 47 - 0
src/stores/modules/menu.ts

@@ -0,0 +1,47 @@
+import { toRefs, shallowReadonly, ShallowRef } from 'vue'
+import { queryAccountMenu } from '@/services/api/account'
+import { VueStore } from '../base'
+import WebStorage from '@/utils/storage/base'
+
+interface StoreState {
+    loading: boolean;
+    userMenus: ShallowRef<Ermcp.UserMenu[]>;
+}
+
+const store = new (class extends VueStore<StoreState>{
+    constructor() {
+        const storage = new WebStorage<Ermcp.UserMenu[]>(sessionStorage, 'userMenus', [])
+        const state: StoreState = {
+            loading: false,
+            userMenus: storage.getRef()
+        }
+        super(state)
+    }
+
+    actions = {
+        /** 获取用户菜单列表 */
+        getUserMenuList: () => {
+            this.state.loading = true
+            return queryAccountMenu({
+                success: (res) => {
+                    this.state.userMenus = res.data
+                },
+                complete: () => {
+                    this.state.loading = false
+                }
+            })
+        },
+        /** 重置数据 */
+        reset: () => {
+            this.state.userMenus = []
+        }
+    }
+})
+
+export function useMenuStore() {
+    return shallowReadonly({
+        ...toRefs(store.state),
+        ...store.actions,
+        ...store.methods,
+    })
+}

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

@@ -1,140 +0,0 @@
-import { queryAllEnums, queryTableDefine } from '@/services/api/common'
-import { AppTheme } from '@/constants/theme'
-import { Language } from '@/constants/language'
-import WebStorage from '@/utils/storage'
-import plus from '@/utils/h5plus'
-
-/**
- * 初始数据
- */
-const initData: Store.GlobalStorage = {
-    appTheme: AppTheme.Default,
-    lang: Language.ZhCN,
-    loginInfo: {
-        AccountIDs: [],
-        LoginCode: '',
-        LoginID: 0,
-        LoginUserType: 0,
-        AccountStatus: 0,
-        UserID: 0,
-        ClientID: 0,
-        MemberUserID: 0,
-        Token: '',
-    },
-    userMenus: [],
-    allEnums: [],
-    tableColumns: [],
-}
-
-/**
- * 本地存储实例
- */
-export const localData = new (class extends WebStorage<Store.GlobalStorage>{
-    constructor() {
-        super(localStorage, initData)
-        document.addEventListener('DOMContentLoaded', this.loadTheme, false)
-    }
-
-    /**
-     * 加载主题
-     */
-    private loadTheme = () => {
-        const theme = this.getValue('appTheme')
-        this.setStatusBarTheme(theme)
-        document.documentElement.setAttribute('theme', theme)
-        document.removeEventListener('DOMContentLoaded', this.loadTheme)
-    }
-
-    /**
-     * 设置状态栏主题色
-     * @param theme 
-     */
-    private setStatusBarTheme = (theme: AppTheme) => {
-        switch (theme) {
-            case AppTheme.Default:
-            case AppTheme.Dark: {
-                plus.setStatusBarStyle('light')
-                break
-            }
-            default: {
-                plus.setStatusBarStyle('dark')
-            }
-        }
-    }
-
-    /**
-     * 设置主题
-     * @param key 
-     */
-    setTheme = (key: keyof typeof AppTheme) => {
-        const theme = AppTheme[key]
-        this.setStatusBarTheme(theme)
-        document.documentElement.setAttribute('theme', theme)
-        this.setValue('appTheme', theme)
-    }
-
-    /**
-     * 重置数据
-     */
-    reset = () => {
-        this.clear('loginInfo', 'userMenus')
-    }
-})
-
-/**
- * 会话存储实例
- */
-export const sessionData = new (class extends WebStorage<Store.GlobalStorage>{
-    constructor() {
-        super(sessionStorage, initData)
-    }
-
-    /**
-     * 获取登录信息
-     * @param key 
-     * @returns 
-     */
-    getLoginInfo = <K extends keyof Proto.LoginRsp>(key: K) => {
-        return this.getValue('loginInfo')[key]
-    }
-
-    /**
-     * 获取所有枚举列表
-     * @returns 
-     */
-    getAllEnumList = () => {
-        if (this.getValue('allEnums').length) {
-            return Promise.resolve()
-        }
-        return queryAllEnums({
-            success: (res) => {
-                this.setValue('allEnums', res.data)
-            }
-        })
-    }
-
-    /**
-     * 获取表格列列表
-     * @returns 
-     */
-    getTableColumnList = () => {
-        if (this.getValue('tableColumns').length) {
-            return Promise.resolve()
-        }
-        return queryTableDefine({
-            data: {
-                tableType: 2
-            },
-            success: (res) => {
-                this.setValue('tableColumns', res.data)
-            }
-        })
-    }
-
-    /**
-     * 重置数据
-     */
-    reset = () => {
-        this.clear('loginInfo', 'userMenus')
-    }
-})

+ 46 - 0
src/stores/modules/tableColumn.ts

@@ -0,0 +1,46 @@
+import { toRefs, shallowReadonly, ShallowRef } from 'vue'
+import { queryTableDefine } from '@/services/api/common'
+import { VueStore } from '../base'
+import WebStorage from '@/utils/storage/base'
+
+interface StoreState {
+    loading: boolean;
+    tableColumns: ShallowRef<Ermcp.TableDefineRsp[]>;
+}
+
+const store = new (class extends VueStore<StoreState>{
+    constructor() {
+        const storage = new WebStorage<Ermcp.TableDefineRsp[]>(sessionStorage, 'tableColumns', [])
+        const state: StoreState = {
+            loading: false,
+            tableColumns: storage.getRef()
+        }
+        super(state)
+    }
+
+    actions = {
+        /** 获取表格列列表 */
+        getTableColumnList: () => {
+            this.state.loading = true
+            return queryTableDefine({
+                data: {
+                    tableType: 2
+                },
+                success: (res) => {
+                    this.state.tableColumns = res.data
+                },
+                complete: () => {
+                    this.state.loading = false
+                }
+            })
+        }
+    }
+})
+
+export function useTableColumnStore() {
+    return shallowReadonly({
+        ...toRefs(store.state),
+        ...store.actions,
+        ...store.methods,
+    })
+}

+ 65 - 0
src/stores/modules/theme.ts

@@ -0,0 +1,65 @@
+import { toRefs, shallowReadonly, ShallowRef } from 'vue'
+import { AppTheme } from '@/constants/theme'
+import { VueStore } from '../base'
+import WebStorage from '@/utils/storage/base'
+import plus from '@/utils/h5plus'
+
+interface StoreState {
+    appTheme: ShallowRef<AppTheme>;
+}
+
+const store = new (class extends VueStore<StoreState>{
+    constructor() {
+        const storage = new WebStorage<AppTheme>(localStorage, 'appTheme', AppTheme.Default)
+        const state: StoreState = {
+            appTheme: storage.getRef()
+        }
+        super(state)
+        document.addEventListener('DOMContentLoaded', this.loadTheme, false)
+    }
+
+    /**
+     * 加载主题
+     */
+    private loadTheme = () => {
+        const theme = this.state.appTheme
+        this.setStatusBarTheme(theme)
+        document.documentElement.setAttribute('theme', theme)
+        document.removeEventListener('DOMContentLoaded', this.loadTheme)
+    }
+
+    /**
+     * 设置状态栏主题色
+     * @param theme 
+     */
+    private setStatusBarTheme = (theme: AppTheme) => {
+        switch (theme) {
+            case AppTheme.Default:
+            case AppTheme.Dark: {
+                plus.setStatusBarStyle('light')
+                break
+            }
+            default: {
+                plus.setStatusBarStyle('dark')
+            }
+        }
+    }
+
+    actions = {
+        /** 设置主题 */
+        setTheme: (key: keyof typeof AppTheme) => {
+            const theme = AppTheme[key]
+            this.setStatusBarTheme(theme)
+            document.documentElement.setAttribute('theme', theme)
+            this.state.appTheme = theme
+        }
+    }
+})
+
+export function useThemeStore() {
+    return shallowReadonly({
+        ...toRefs(store.state),
+        ...store.actions,
+        ...store.methods,
+    })
+}

+ 65 - 0
src/stores/modules/user.ts

@@ -0,0 +1,65 @@
+import { toRefs, shallowReadonly } from 'vue'
+import { queryLoginData } from '@/services/api/account'
+import { useLoginStore } from './login'
+import { VueStore } from '../base'
+
+interface StoreState {
+    loading: boolean;
+    userData: Ermcp.LoginQueryRsp;
+}
+
+/**
+ * 用户存储类
+ */
+const store = new (class extends VueStore<StoreState>{
+    constructor() {
+        const state: StoreState = {
+            loading: false,
+            userData: {
+                arearole: [],
+                externalExchanges: [],
+                goodsgroups: [],
+                markets: [],
+                systemParams: []
+            },
+        }
+        super(state)
+    }
+
+    actions = {
+        /** 获取用户数据 */
+        getUserData: () => {
+            const { getLoginId } = useLoginStore()
+            this.state.loading = true
+
+            return queryLoginData({
+                data: {
+                    loginID: getLoginId()
+                },
+                success: (res) => {
+                    this.state.userData = res.data
+                },
+                complete: () => {
+                    this.state.loading = false
+                }
+            })
+        },
+        /** 获取用户数据 */
+        getUserDataInfo: <K extends keyof Ermcp.LoginQueryRsp>(key: K) => {
+            return this.state.userData[key]!
+        },
+        /** 获取登录机构名称 */
+        getAccountName: () => {
+            const { userAccount } = this.state.userData
+            return userAccount?.accountname
+        }
+    }
+})
+
+export function useUserStore() {
+    return shallowReadonly({
+        ...toRefs(store.state),
+        ...store.actions,
+        ...store.methods,
+    })
+}

+ 77 - 0
src/types/ermcp/common.d.ts

@@ -0,0 +1,77 @@
+/** 企业风管 */
+declare namespace Ermcp {
+    /** 行情 */
+    interface Quotation {
+        goodscode: string; // 商品代码
+        goodsname: string; // 商品名称
+        last: number; // 最新价
+        amplitude: number; // 涨跌幅
+        change: number; // 涨跌
+        bid: number; // 买价
+        ask: number; // 卖价
+        bidvolume: number; // 买量
+        askvolume: number; // 卖量
+        totalvolume: number; // 总量
+        lastvolume: number; // 现量
+        holdvolume: number; // 持仓量
+        holdincrement: number; // 日增
+        presettle: number; // 昨结价
+        totalturnover: number; // 金额
+        opened: number; // 开盘
+        highest: number; // 最高
+        lowest: number; // 最低
+    }
+
+    /** 通知公告系统消息查询 请求 */
+    interface NoticeReq {
+        page?: number; // 页码
+        pagesize?: number; // 每页条数
+        loginID: number; // 登录账号
+        msgType?: number; // 消息类型 - 1:公告通知 2:系统消息
+        onlyUnRead?: boolean; // 是否只获取未读信息
+        lastID?: number; // 自增ID,传入后会返回这个ID后面的记录
+    }
+
+    /** 通知公告系统消息查询 响应 */
+    interface NoticeRsp {
+        auditoruserid: number; // 审核人
+        auditremark: string; // 审核备注
+        audittime: string; // 审核日期
+        autoid: number; // 自增ID
+        content: string; // 内容
+        createtime: string; // 创建时间
+        creatorid: number; // 建仓人
+        endtime: string; // 结束时间
+        istop: number; // 是否置顶 - 0:不置顶 1:置顶
+        msgiconurl: string; // 消息图标Url
+        msgtype: number; // 消息类型 - 1:公告通知 2:系统消息 3:商品到期提货通知
+        publisher: string; // 消息发布者
+        readed: boolean; // 是否已读
+        scheduletime: string; // 计划发送时间
+        sendtype: number; // 推送方式 - 1:全体广播 2:按会员广播 3:个人推送 4:按会员广播(仅会员)
+        sentstatus: number; // 推送状态 - 0:未推送 1:已推送 2:审核拒绝
+        title: string; // 标题
+        userid: number; // 会员/投资者ID推送方式 为 个人时,填写投资者ID
+    }
+
+    /** 通知公告设置已读 请求 */
+    interface NoticeReadedReq {
+        loginID: number; // 登录账号
+        noticeID: number; // 通知公告ID
+    }
+
+    /** 获取数据库错误信息 请求 */
+    interface ErrorInfosReq {
+        rowNumber?: string; // 起始行号前索引
+    }
+
+    /** 获取数据库错误信息 响应 */
+    interface ErrorInfosRsp {
+        description: string; // 异常描述
+        errorcode: string; // 异常代码
+        errorid: number; // 异常ID
+        modulecode: string; // 所属模块
+        operatecode: string; // 所属操作
+        rownumber: string; // 行号
+    }
+}

+ 0 - 16
src/types/store/globalStorage.d.ts

@@ -1,16 +0,0 @@
-import { AppTheme } from '@/constants/theme'
-import { Language } from '@/constants/language'
-
-declare global {
-    namespace Store {
-        /** 本地缓存数据 */
-        interface GlobalStorage {
-            appTheme: AppTheme; // 应用主题色
-            lang: Language;
-            loginInfo: Proto.LoginRsp; // 账号登录返回信息 token等
-            userMenus: Ermcp.UserMenu[];
-            allEnums: Ermcp.EnumRsp[]; // 所有枚举
-            tableColumns: Ermcp.TableDefineRsp[];
-        }
-    }
-}

+ 0 - 23
src/types/store/quotation.d.ts

@@ -1,23 +0,0 @@
-declare namespace Store {
-    /** 行情 */
-    interface Quotation {
-        goodscode: string; // 商品代码
-        goodsname: string; // 商品名称
-        last: number; // 最新价
-        amplitude: number; // 涨跌幅
-        change: number; // 涨跌
-        bid: number; // 买价
-        ask: number; // 卖价
-        bidvolume: number; // 买量
-        askvolume: number; // 卖量
-        totalvolume: number; // 总量
-        lastvolume: number; // 现量
-        holdvolume: number; // 持仓量
-        holdincrement: number; // 日增
-        presettle: number; // 昨结价
-        totalturnover: number; // 金额
-        opened: number; // 开盘
-        highest: number; // 最高
-        lowest: number; // 最低
-    }
-}

+ 69 - 0
src/utils/storage/base.ts

@@ -0,0 +1,69 @@
+import { shallowRef, watch } from 'vue'
+
+/**
+ * 本地存储类(基础版)
+ */
+export default class <T> {
+    constructor(storage: Storage, key: string, value: T) {
+        this.storage = storage
+        this.storageKey = key
+        this.source = value
+        this.state = shallowRef(this.getValue())
+        this.observe()
+    }
+
+    private readonly source // 原始数据
+    private readonly storage
+    readonly storageKey
+    private state
+
+    /**
+     * 监听数据变化
+     */
+    private observe() {
+        watch(this.state, (value) => {
+            if (value === undefined || value === null) {
+                this.storage.removeItem(this.storageKey)
+            } else {
+                const storageValue = JSON.stringify(value) // 注意数值长度过长会被自动转换为 String 类型
+                this.storage.setItem(this.storageKey, storageValue)
+            }
+        })
+    }
+
+    /**
+     * 获取数据
+     * @returns 
+     */
+    getValue() {
+        const storageValue = this.storage.getItem(this.storageKey)
+        if (storageValue !== 'undefined' && storageValue !== null) {
+            return JSON.parse(storageValue) as T
+        } else {
+            return this.source
+        }
+    }
+
+    /**
+     * 更新数据
+     * @param value 
+     */
+    setValue(value: T) {
+        this.state.value = value
+    }
+
+    /**
+     * 获取响应数据
+     * @returns 
+     */
+    getRef() {
+        return this.state
+    }
+
+    /**
+     * 重置数据
+     */
+    reset() {
+        this.state.value = this.source
+    }
+}

+ 52 - 51
src/utils/storage/index.ts

@@ -1,100 +1,101 @@
-import { reactive, toRef, readonly, computed, WritableComputedRef, UnwrapNestedRefs } from 'vue'
+import { shallowRef, ShallowRef, watch } from 'vue'
 
 /**
  * 本地存储类
  */
 export default class <T extends object> {
-    private readonly storage: Storage
-    private readonly source // 初始数据
-    private state
-
     constructor(storage: Storage, source: T) {
         this.storage = storage
         this.source = source
-        this.state = reactive({ ...source })
 
         // 读取本地数据
-        for (const key in this.state) {
-            const strValue = this.storage.getItem(key)
-            if (strValue !== 'undefined' && strValue !== null) {
-                this.state[key] = JSON.parse(strValue)
+        for (const key in source) {
+            const storageValue = this.getValue(key)
+            const state = this.stateMap.get(key)
+
+            if (state) {
+                state.value = storageValue
+            } else {
+                this.stateMap.set(key, shallowRef(storageValue))
             }
+
+            this.observe(key)
         }
     }
 
+    private readonly storage: Storage
+    private readonly source // 初始数据
+    private stateMap = new Map<keyof T, ShallowRef<T[keyof T]>>()
+
     /**
-     * 获取状态数据 (不建议使用,可能存在对象内部值变更无法同步 Storage 的问题)
-     * @returns 
+     * 监听数据变化
+     * @param key 
      */
-    getState = () => {
-        const state = {} as { [key in keyof UnwrapNestedRefs<T>]: WritableComputedRef<UnwrapNestedRefs<T>[key]> }
-        for (const key in this.state) {
-            state[key] = computed({
-                get: () => this.getValue(key),
-                set: (value) => this.setValue(key, value)
-            })
-        }
-        return state
+    private observe<K extends keyof T>(key: K) {
+        watch(this.getRef(key), (value) => {
+            if (value === undefined || value === null) {
+                this.reset(key)
+            } else {
+                const strValue = JSON.stringify(value) // 注意数值长度过长会被自动转换为 String 类型
+                this.storage.setItem(key.toString(), strValue)
+            }
+        })
     }
 
     /**
-     * 获取属性响应值 (不建议使用,可能存在对象内部值变更无法同步 Storage 的问题)
+     * 获取数据
      * @param key 
      * @returns 
      */
-    getComputedRef<K extends keyof UnwrapNestedRefs<T>>(key: K) {
-        const state = this.getState()
-        return state[key]
+    getValue<K extends keyof T>(key: K) {
+        const storageValue = this.storage.getItem(key.toString())
+        if (storageValue !== 'undefined' && storageValue !== null) {
+            return JSON.parse(storageValue) as T[K]
+        } else {
+            return this.source[key]
+        }
     }
 
     /**
-     * 获取属性响应值 (只读)
+     * 更新数据
      * @param key 
-     * @returns 
+     * @param value 
      */
-    getRef<K extends keyof UnwrapNestedRefs<T>>(key: K) {
-        return readonly(toRef(this.state, key))
+    setValue<K extends keyof T>(key: K, value: T[K]) {
+        const state = this.getRef(key)
+        state.value = value
     }
 
     /**
-     * 获取属性值
-     * @param key 
+     * 获取响应对象
      * @returns 
      */
-    getValue<K extends keyof UnwrapNestedRefs<T>>(key: K) {
-        return this.state[key]
+    getRefs() {
+        return [...this.stateMap.entries()].reduce((obj, [key, value]) => (obj[key] = value, obj), {} as { [key in keyof T]: ShallowRef<T[key]> })
     }
 
     /**
-     * 设置属性值
+     * 获取响应数据
      * @param key 
-     * @param value 
+     * @returns 
      */
-    setValue<K extends keyof UnwrapNestedRefs<T>>(key: K, value: UnwrapNestedRefs<T>[K]) {
-        if (value === undefined || value === null) {
-            this.clear(key)
-        } else {
-            const strValue = JSON.stringify(value)
-            this.storage.setItem(key.toString(), strValue)
-            this.state[key] = value
-        }
+    getRef<K extends keyof T>(key: K) {
+        return this.stateMap.get(key)!
     }
 
     /**
-     * 清除属性值
+     * 重置数据
      * @param keys 
      */
-    clear<K extends keyof UnwrapNestedRefs<T>>(...keys: K[]) {
-        const source = reactive({ ...this.source })
+    reset<K extends keyof T>(...keys: K[]) {
         if (keys.length) {
             keys.forEach((key) => {
-                this.storage.removeItem(key.toString())
-                this.state[key] = source[key]
+                const state = this.getRef(key)
+                state.value = this.source[key]
             })
         } else {
-            for (const key in this.state) {
-                this.storage.removeItem(key)
-                this.state[key] = source[key]
+            for (const [key, state] of this.stateMap) {
+                state.value = this.source[key]
             }
         }
     }