yanghao 2 viikkoa sitten
vanhempi
commit
1fcdf83be8
100 muutettua tiedostoa jossa 6170 lisäystä ja 361 poistoa
  1. 1 1
      .env.dev
  2. 2 2
      .env.local
  3. 1 0
      .gitignore
  4. 2 1
      package.json
  5. 98 6
      pnpm-lock.yaml
  6. 0 0
      public/h5player/h5player.min.js
  7. 705 0
      public/h5player/playctrl1/DecodeWorker.js
  8. BIN
      public/h5player/playctrl1/Decoder.js
  9. 705 0
      public/h5player/playctrl1simd/DecodeWorker.js
  10. BIN
      public/h5player/playctrl1simd/Decoder.js
  11. 0 0
      public/h5player/playctrl2/Decoder.js
  12. BIN
      public/h5player/playctrl2/Decoder.wasm
  13. 0 0
      public/h5player/playctrl2/Decoder.worker.js
  14. 0 0
      public/h5player/playctrl3/Decoder.js
  15. BIN
      public/h5player/playctrl3/Decoder.wasm
  16. 0 0
      public/h5player/playctrl3/Decoder.worker.js
  17. 7 0
      public/h5player/talk/AudioInterCom.js
  18. BIN
      public/h5player/talk/AudioInterCom.wasm
  19. 8 0
      public/h5player/talkW/AudioInterCom.js
  20. BIN
      public/h5player/talkW/AudioInterCom.wasm
  21. 0 0
      public/h5player/talkW/AudioInterCom.worker.js
  22. 2668 0
      public/h5player/transform/libSystemTransform.js
  23. BIN
      public/h5player/transform/libSystemTransform.wasm
  24. 126 0
      public/h5player/transform/systemTransform-worker.js
  25. 48 0
      src/App.vue
  26. 49 0
      src/api/digital/index.ts
  27. 51 0
      src/api/iot/thingmodel/index.ts
  28. 25 7
      src/api/pms/failure/index.ts
  29. 26 0
      src/api/pms/fiveconstructiondailyreport/index.ts
  30. 104 0
      src/api/pms/hikvision/index.ts
  31. 32 33
      src/api/pms/iotmaintenancebom/index.ts
  32. 6 0
      src/api/pms/iotmainworkorder/index.ts
  33. 16 0
      src/api/pms/iotprojecttask/index.ts
  34. 113 0
      src/api/pms/iotprojecttaskplan/index.ts
  35. 7 0
      src/api/pms/iotprojecttaskschedule/index.ts
  36. 11 0
      src/api/pms/iotrddailyreport/index.ts
  37. 1 1
      src/api/pms/iotrhdailyreport/index.ts
  38. 11 0
      src/api/pms/iotrydailyreport/index.ts
  39. 48 0
      src/api/pms/iotryimprovedailyreport/index.ts
  40. 16 0
      src/api/pms/iotsapstock/index.ts
  41. 12 0
      src/api/pms/maotu/index.ts
  42. 366 2
      src/api/pms/qhse/index.ts
  43. 53 4
      src/api/pms/stat/index.ts
  44. 1 0
      src/assets/icons/grid-16.svg
  45. 1 0
      src/assets/icons/grid-four.svg
  46. 1 0
      src/assets/icons/grid-nine.svg
  47. 1 0
      src/assets/icons/grid-one.svg
  48. BIN
      src/assets/images/danger/地刺.png
  49. BIN
      src/assets/images/danger/当心硫化氢气体.png
  50. BIN
      src/assets/images/danger/当心触电.png
  51. BIN
      src/assets/images/danger/当心高温.png
  52. BIN
      src/assets/images/danger/注意噪音防护.png
  53. BIN
      src/assets/images/danger/防冲撞栏.png
  54. BIN
      src/assets/images/danger/高压危险.png
  55. 3 0
      src/assets/images/icons/人群.svg
  56. 3 0
      src/assets/images/icons/扳手.svg
  57. 3 0
      src/assets/images/icons/账户.svg
  58. 3 0
      src/assets/images/icons/跑.svg
  59. 3 0
      src/assets/images/icons/锅.svg
  60. 3 0
      src/assets/images/icons/门.svg
  61. BIN
      src/assets/images/materials/安保设施.png
  62. BIN
      src/assets/images/materials/气防设施.png
  63. BIN
      src/assets/images/materials/消防设施.png
  64. BIN
      src/assets/images/materials/路障.png
  65. BIN
      src/assets/images/process-demo/PSA.png
  66. BIN
      src/assets/images/process-demo/关闭阀门.png
  67. BIN
      src/assets/images/process-demo/厨房.png
  68. BIN
      src/assets/images/process-demo/变压器.png
  69. BIN
      src/assets/images/process-demo/地罐.png
  70. BIN
      src/assets/images/process-demo/增压机.png
  71. BIN
      src/assets/images/process-demo/开启阀门.png
  72. BIN
      src/assets/images/process-demo/房子.png
  73. BIN
      src/assets/images/process-demo/水罐.png
  74. BIN
      src/assets/images/process-demo/注水泵.png
  75. BIN
      src/assets/images/process-demo/流量计1.png
  76. BIN
      src/assets/images/process-demo/温度计.png
  77. BIN
      src/assets/images/process-demo/空压机.png
  78. BIN
      src/assets/images/process-demo/空压机1.png
  79. BIN
      src/assets/images/process-demo/空气处理撬.png
  80. BIN
      src/assets/images/process-demo/纯度计.png
  81. BIN
      src/assets/images/process-demo/罐.png
  82. BIN
      src/assets/images/process-demo/罐1.png
  83. BIN
      src/assets/images/process-demo/过滤器1.png
  84. BIN
      src/assets/images/process-demo/采油树.png
  85. BIN
      src/assets/images/process-demo/阀门2.png
  86. 120 0
      src/components/DeptSelectForm/qhseDept.vue
  87. 11 17
      src/components/DeptTreeSelect/index.vue
  88. 12 0
      src/components/Echart/src/Echart.vue
  89. 2 4
      src/components/Pagination/index.vue
  90. 154 189
      src/components/UploadFile/src/FileUpload.vue
  91. 4 5
      src/components/UploadFile/src/UploadFile.vue
  92. 31 7
      src/components/ZmTable/README.md
  93. 12 15
      src/components/ZmTable/ZmTableColumn.vue
  94. 214 21
      src/components/ZmTable/index.vue
  95. 57 15
      src/components/custom-components/kv-vue/index.vue
  96. 107 23
      src/components/custom-components/sys-button-vue/index.vue
  97. 6 1
      src/components/custom-components/text-vue/index.vue
  98. 49 0
      src/components/custom-components/wireframe-vue/index.vue
  99. 3 0
      src/components/mt-dzr/index.vue
  100. 48 7
      src/components/mt-edit/components/done-tree/index.vue

+ 1 - 1
.env.dev

@@ -4,7 +4,7 @@ NODE_ENV=production
 VITE_DEV=true
 VITE_DEV=true
 
 
 # 请求路径
 # 请求路径
-VITE_BASE_URL='https://iot.deepoil.cc'
+VITE_BASE_URL='https://iot.deepoil.cc:5443'
 
 
 # MQTT服务地址
 # MQTT服务地址
 VITE_MQTT_SERVER_URL = ''
 VITE_MQTT_SERVER_URL = ''

+ 2 - 2
.env.local

@@ -3,8 +3,8 @@ NODE_ENV=development
 
 
 VITE_DEV=true
 VITE_DEV=true
 
 
-# 请求路径  http://192.168.188.79:48080  https://iot.deepoil.cc  http://172.26.0.56:48080
-VITE_BASE_URL='https://iot.deepoil.cc'
+# 请求路径  http://192.168.188.86:48080  https://iot.deepoil.cc:5443  http://172.26.0.56:48080  http://192.168.188.198:48080
+VITE_BASE_URL='https://iot.deepoil.cc:5443'
 
 
 # 文件上传类型:server - 后端上传, client - 前端直连上传,仅支持 S3 服务
 # 文件上传类型:server - 后端上传, client - 前端直连上传,仅支持 S3 服务
 VITE_UPLOAD_TYPE=server
 VITE_UPLOAD_TYPE=server

+ 1 - 0
.gitignore

@@ -7,3 +7,4 @@ pnpm-debug
 auto-*.d.ts
 auto-*.d.ts
 .idea
 .idea
 .history
 .history
+pnpm-workspace.yaml

+ 2 - 1
package.json

@@ -41,6 +41,7 @@
     "@types/echarts": "^5.0.0",
     "@types/echarts": "^5.0.0",
     "@types/js-base64": "^3.3.1",
     "@types/js-base64": "^3.3.1",
     "@videojs-player/vue": "^1.0.0",
     "@videojs-player/vue": "^1.0.0",
+    "@vuepic/vue-datepicker": "^14.0.0",
     "@vueuse/core": "^10.9.0",
     "@vueuse/core": "^10.9.0",
     "@wangeditor/editor": "^5.1.23",
     "@wangeditor/editor": "^5.1.23",
     "@wangeditor/editor-for-vue": "^5.1.10",
     "@wangeditor/editor-for-vue": "^5.1.10",
@@ -118,7 +119,7 @@
   "devDependencies": {
   "devDependencies": {
     "@commitlint/cli": "^19.0.1",
     "@commitlint/cli": "^19.0.1",
     "@commitlint/config-conventional": "^19.0.0",
     "@commitlint/config-conventional": "^19.0.0",
-    "@iconify/json": "^2.2.187",
+    "@iconify/json": "^2.2.524",
     "@intlify/unplugin-vue-i18n": "^2.0.0",
     "@intlify/unplugin-vue-i18n": "^2.0.0",
     "@purge-icons/generated": "^0.9.0",
     "@purge-icons/generated": "^0.9.0",
     "@types/lodash-es": "^4.17.12",
     "@types/lodash-es": "^4.17.12",

+ 98 - 6
pnpm-lock.yaml

@@ -56,6 +56,9 @@ importers:
       '@videojs-player/vue':
       '@videojs-player/vue':
         specifier: ^1.0.0
         specifier: ^1.0.0
         version: 1.0.0(@types/video.js@7.3.58)(video.js@7.21.6)(vue@3.5.12(typescript@5.3.3))
         version: 1.0.0(@types/video.js@7.3.58)(video.js@7.21.6)(vue@3.5.12(typescript@5.3.3))
+      '@vuepic/vue-datepicker':
+        specifier: ^14.0.0
+        version: 14.0.0(vue@3.5.12(typescript@5.3.3))
       '@vueuse/core':
       '@vueuse/core':
         specifier: ^10.9.0
         specifier: ^10.9.0
         version: 10.11.1(vue@3.5.12(typescript@5.3.3))
         version: 10.11.1(vue@3.5.12(typescript@5.3.3))
@@ -283,8 +286,8 @@ importers:
         specifier: ^19.0.0
         specifier: ^19.0.0
         version: 19.6.0
         version: 19.6.0
       '@iconify/json':
       '@iconify/json':
-        specifier: ^2.2.187
-        version: 2.2.277
+        specifier: ^2.2.524
+        version: 2.2.524
       '@intlify/unplugin-vue-i18n':
       '@intlify/unplugin-vue-i18n':
         specifier: ^2.0.0
         specifier: ^2.0.0
         version: 2.0.0(rollup@4.27.4)(vue-i18n@9.10.2(vue@3.5.12(typescript@5.3.3)))
         version: 2.0.0(rollup@4.27.4)(vue-i18n@9.10.2(vue@3.5.12(typescript@5.3.3)))
@@ -1137,6 +1140,9 @@ packages:
     resolution: {integrity: sha512-SITSV6aIXsuVNV3f3O0f2n/cgyEDWoSqtZMYiAmcsYHydcKrOz3gUxB/iXd/Qf08+IZX4KpgNbvUdMBmWz+kcA==}
     resolution: {integrity: sha512-SITSV6aIXsuVNV3f3O0f2n/cgyEDWoSqtZMYiAmcsYHydcKrOz3gUxB/iXd/Qf08+IZX4KpgNbvUdMBmWz+kcA==}
     engines: {node: '>=10'}
     engines: {node: '>=10'}
 
 
+  '@date-fns/tz@1.5.0':
+    resolution: {integrity: sha512-lwYN/vDPeNRULcepoE/LO2Pgx+7/RV+S9ARfbc9lr2DtGkOD7pAiruHvbR1RX3Qyf6ja47EWJDMsNK5vK08DJg==}
+
   '@dual-bundle/import-meta-resolve@4.1.0':
   '@dual-bundle/import-meta-resolve@4.1.0':
     resolution: {integrity: sha512-+nxncfwHM5SgAtrVzgpzJOI1ol0PkumhVo469KCf9lUi21IGcY90G98VuHm9VRrUypmAzawAHO9bs6hqeADaVg==}
     resolution: {integrity: sha512-+nxncfwHM5SgAtrVzgpzJOI1ol0PkumhVo469KCf9lUi21IGcY90G98VuHm9VRrUypmAzawAHO9bs6hqeADaVg==}
 
 
@@ -1309,12 +1315,24 @@ packages:
   '@floating-ui/core@1.6.8':
   '@floating-ui/core@1.6.8':
     resolution: {integrity: sha512-7XJ9cPU+yI2QeLS+FCSlqNFZJq8arvswefkZrYI1yQBbftw6FyrZOxYSh+9S7z7TpeWlRt9zJ5IhM1WIL334jA==}
     resolution: {integrity: sha512-7XJ9cPU+yI2QeLS+FCSlqNFZJq8arvswefkZrYI1yQBbftw6FyrZOxYSh+9S7z7TpeWlRt9zJ5IhM1WIL334jA==}
 
 
+  '@floating-ui/core@1.7.5':
+    resolution: {integrity: sha512-1Ih4WTWyw0+lKyFMcBHGbb5U5FtuHJuujoyyr5zTaWS5EYMeT6Jb2AuDeftsCsEuchO+mM2ij5+q9crhydzLhQ==}
+
   '@floating-ui/dom@1.6.12':
   '@floating-ui/dom@1.6.12':
     resolution: {integrity: sha512-NP83c0HjokcGVEMeoStg317VD9W7eDlGK7457dMBANbKA6GJZdc7rjujdgqzTaz93jkGgc5P/jeWbaCHnMNc+w==}
     resolution: {integrity: sha512-NP83c0HjokcGVEMeoStg317VD9W7eDlGK7457dMBANbKA6GJZdc7rjujdgqzTaz93jkGgc5P/jeWbaCHnMNc+w==}
 
 
+  '@floating-ui/dom@1.7.6':
+    resolution: {integrity: sha512-9gZSAI5XM36880PPMm//9dfiEngYoC6Am2izES1FF406YFsjvyBMmeJ2g4SAju3xWwtuynNRFL2s9hgxpLI5SQ==}
+
+  '@floating-ui/utils@0.2.11':
+    resolution: {integrity: sha512-RiB/yIh78pcIxl6lLMG0CgBXAZ2Y0eVHqMPYugu+9U0AeT6YBeiJpf7lbdJNIugFP5SIjwNRgo4DhR1Qxi26Gg==}
+
   '@floating-ui/utils@0.2.8':
   '@floating-ui/utils@0.2.8':
     resolution: {integrity: sha512-kym7SodPp8/wloecOpcmSnWJsK7M0E5Wg8UcFA+uO4B9s5d0ywXOEro/8HM9x0rW+TljRzul/14UYz3TleT3ig==}
     resolution: {integrity: sha512-kym7SodPp8/wloecOpcmSnWJsK7M0E5Wg8UcFA+uO4B9s5d0ywXOEro/8HM9x0rW+TljRzul/14UYz3TleT3ig==}
 
 
+  '@floating-ui/vue@1.1.11':
+    resolution: {integrity: sha512-HzHKCNVxnGS35r9fCHBc3+uCnjw9IWIlCPL683cGgM9Kgj2BiAl8x1mS7vtvP6F9S/e/q4O6MApwSHj8hNLGfw==}
+
   '@form-create/component-elm-checkbox@3.2.14':
   '@form-create/component-elm-checkbox@3.2.14':
     resolution: {integrity: sha512-VtjRvNxbKpjp0bNYQ2BuLRVIQHZdPpYn3Hy0xSuzv6QjEDnffMdmawHImPSlp8wAW3b94wZdHMLMqpyMew8bBw==}
     resolution: {integrity: sha512-VtjRvNxbKpjp0bNYQ2BuLRVIQHZdPpYn3Hy0xSuzv6QjEDnffMdmawHImPSlp8wAW3b94wZdHMLMqpyMew8bBw==}
 
 
@@ -1403,8 +1421,8 @@ packages:
     resolution: {integrity: sha512-1nemfyD/OJzh9ALepH7YfuuP8BdEB24Skhd8DXWh0hzcOxImbb1ZizSZkpCzAwSZSGcJFmscIBaBQu+yLyWaxQ==}
     resolution: {integrity: sha512-1nemfyD/OJzh9ALepH7YfuuP8BdEB24Skhd8DXWh0hzcOxImbb1ZizSZkpCzAwSZSGcJFmscIBaBQu+yLyWaxQ==}
     deprecated: no longer maintained, switch to modern iconify-icon web component
     deprecated: no longer maintained, switch to modern iconify-icon web component
 
 
-  '@iconify/json@2.2.277':
-    resolution: {integrity: sha512-hNBnGD2djNgsdB4Yq5dBhP2CI0PLt+4EamozKSAD+hsbFAzVUN6sMj5FUiBFu8BKUOBIYcrX8ri7C7Qe3K10ew==}
+  '@iconify/json@2.2.524':
+    resolution: {integrity: sha512-xw2TRuiRcOzx8mTXRNIfg9FYr4sd7Z7s/DGKitJZt3njeiFmjHte8qKWEGK+IePYtQ1QyVnhfOi+5sEjVTmvDQ==}
 
 
   '@iconify/types@2.0.0':
   '@iconify/types@2.0.0':
     resolution: {integrity: sha512-+wluvCrRhXrhyOmRDJ3q8mux9JkKy5SJ/v8ol2tu4FVjyYvtEzkc/3pK15ET6RKg4b4w4BmTk1+gsCUhf21Ykg==}
     resolution: {integrity: sha512-+wluvCrRhXrhyOmRDJ3q8mux9JkKy5SJ/v8ol2tu4FVjyYvtEzkc/3pK15ET6RKg4b4w4BmTk1+gsCUhf21Ykg==}
@@ -2022,6 +2040,9 @@ packages:
   '@types/web-bluetooth@0.0.20':
   '@types/web-bluetooth@0.0.20':
     resolution: {integrity: sha512-g9gZnnXVq7gM7v3tJCWV/qw7w+KeOlSHAhgF9RytFyifW6AF61hdT2ucrYhPq9hLs5JIryeupHV3qGk95dH9ow==}
     resolution: {integrity: sha512-g9gZnnXVq7gM7v3tJCWV/qw7w+KeOlSHAhgF9RytFyifW6AF61hdT2ucrYhPq9hLs5JIryeupHV3qGk95dH9ow==}
 
 
+  '@types/web-bluetooth@0.0.21':
+    resolution: {integrity: sha512-oIQLCGWtcFZy2JW77j9k8nHzAOpqMHLQejDA48XXMWH6tjCQHz5RCFz1bzsmROyL6PUm+LLnUiI4BCn221inxA==}
+
   '@types/ws@8.18.1':
   '@types/ws@8.18.1':
     resolution: {integrity: sha512-ThVF6DCVhA8kUGy+aazFQ4kXQ7E1Ty7A3ypFOe0IcJV8O/M511G99AW24irKrW56Wt44yG9+ij8FaqoBGkuBXg==}
     resolution: {integrity: sha512-ThVF6DCVhA8kUGy+aazFQ4kXQ7E1Ty7A3ypFOe0IcJV8O/M511G99AW24irKrW56Wt44yG9+ij8FaqoBGkuBXg==}
 
 
@@ -2402,15 +2423,34 @@ packages:
   '@vue/shared@3.5.13':
   '@vue/shared@3.5.13':
     resolution: {integrity: sha512-/hnE/qP5ZoGpol0a5mDi45bOd7t3tjYJBjsgCsivow7D48cJeV5l05RD82lPqi7gRiphZM37rnhW1l6ZoCNNnQ==}
     resolution: {integrity: sha512-/hnE/qP5ZoGpol0a5mDi45bOd7t3tjYJBjsgCsivow7D48cJeV5l05RD82lPqi7gRiphZM37rnhW1l6ZoCNNnQ==}
 
 
+  '@vuepic/vue-datepicker@14.0.0':
+    resolution: {integrity: sha512-zDl1U2MRk+udu0gYA3pdY2f0O+ckqUPiKgWmp7qQV3D7CRgDKYvUXFpnt+rgMQ+uLydtlRqzy4eS6nvtVjNS4A==}
+    engines: {node: '>=18.12.0'}
+    peerDependencies:
+      vue: '>=3.5.0'
+
   '@vueuse/core@10.11.1':
   '@vueuse/core@10.11.1':
     resolution: {integrity: sha512-guoy26JQktXPcz+0n3GukWIy/JDNKti9v6VEMu6kV2sYBsWuGiTU8OWdg+ADfUbHg3/3DlqySDe7JmdHrktiww==}
     resolution: {integrity: sha512-guoy26JQktXPcz+0n3GukWIy/JDNKti9v6VEMu6kV2sYBsWuGiTU8OWdg+ADfUbHg3/3DlqySDe7JmdHrktiww==}
 
 
+  '@vueuse/core@14.3.0':
+    resolution: {integrity: sha512-aHfz47g0ZhMtTVHmIzMVpJy8ePhhOy68GY5bv110+5DVtZ+W7BsOx+m61UNQqfrWyPztIHIanWa3E2tib3NFIw==}
+    peerDependencies:
+      vue: ^3.5.0
+
   '@vueuse/metadata@10.11.1':
   '@vueuse/metadata@10.11.1':
     resolution: {integrity: sha512-IGa5FXd003Ug1qAZmyE8wF3sJ81xGLSqTqtQ6jaVfkeZ4i5kS2mwQF61yhVqojRnenVew5PldLyRgvdl4YYuSw==}
     resolution: {integrity: sha512-IGa5FXd003Ug1qAZmyE8wF3sJ81xGLSqTqtQ6jaVfkeZ4i5kS2mwQF61yhVqojRnenVew5PldLyRgvdl4YYuSw==}
 
 
+  '@vueuse/metadata@14.3.0':
+    resolution: {integrity: sha512-BwxmbAzwAVF50+MW57GXOUEV61nFBGnlBvrTqj49PqWJu3uw7hdu72ztXeZ33RdZtDY6kO+bfCAE1PCn88Tktw==}
+
   '@vueuse/shared@10.11.1':
   '@vueuse/shared@10.11.1':
     resolution: {integrity: sha512-LHpC8711VFZlDaYUXEBbFBCQ7GS3dVU9mjOhhMhXP6txTV4EhYQg/KGnQuvt/sPAtoUKq7VVUnL6mVtFoL42sA==}
     resolution: {integrity: sha512-LHpC8711VFZlDaYUXEBbFBCQ7GS3dVU9mjOhhMhXP6txTV4EhYQg/KGnQuvt/sPAtoUKq7VVUnL6mVtFoL42sA==}
 
 
+  '@vueuse/shared@14.3.0':
+    resolution: {integrity: sha512-bZpge9eSXwa4ToSiqJ7j6KRwhAsneMFoSz3LMWKQDkqimm3D/tbFlrklrs/IOqC8tEcYmXQZJ6N0UrjhBirVCg==}
+    peerDependencies:
+      vue: ^3.5.0
+
   '@wangeditor/basic-modules@1.1.7':
   '@wangeditor/basic-modules@1.1.7':
     resolution: {integrity: sha512-cY9CPkLJaqF05STqfpZKWG4LpxTMeGSIIF1fHvfm/mz+JXatCagjdkbxdikOuKYlxDdeqvOeBmsUBItufDLXZg==}
     resolution: {integrity: sha512-cY9CPkLJaqF05STqfpZKWG4LpxTMeGSIIF1fHvfm/mz+JXatCagjdkbxdikOuKYlxDdeqvOeBmsUBItufDLXZg==}
     peerDependencies:
     peerDependencies:
@@ -3247,6 +3287,9 @@ packages:
   date-fns@4.1.0:
   date-fns@4.1.0:
     resolution: {integrity: sha512-Ukq0owbQXxa/U3EGtsdVBkR1w7KOQ5gIBqdH2hkvknzZPYvBxb/aa6E8L7tmjFtkwZBu3UXBbjIgPo/Ez4xaNg==}
     resolution: {integrity: sha512-Ukq0owbQXxa/U3EGtsdVBkR1w7KOQ5gIBqdH2hkvknzZPYvBxb/aa6E8L7tmjFtkwZBu3UXBbjIgPo/Ez4xaNg==}
 
 
+  date-fns@4.4.0:
+    resolution: {integrity: sha512-+1UMbeh68lH1SegH83CGWwpb6OHHbpSgr3+s5Eww5M4CAgswBpoWS0AjTOfEJ33HiYKz1hdj/KTFprzXHmq/6w==}
+
   date.js@0.3.3:
   date.js@0.3.3:
     resolution: {integrity: sha512-HgigOS3h3k6HnW011nAb43c5xx5rBXk8P2v/WIT9Zv4koIaVXiH2BURguI78VVp+5Qc076T7OR378JViCnZtBw==}
     resolution: {integrity: sha512-HgigOS3h3k6HnW011nAb43c5xx5rBXk8P2v/WIT9Zv4koIaVXiH2BURguI78VVp+5Qc076T7OR378JViCnZtBw==}
 
 
@@ -7569,6 +7612,8 @@ snapshots:
 
 
   '@ctrl/tinycolor@3.6.1': {}
   '@ctrl/tinycolor@3.6.1': {}
 
 
+  '@date-fns/tz@1.5.0': {}
+
   '@dual-bundle/import-meta-resolve@4.1.0': {}
   '@dual-bundle/import-meta-resolve@4.1.0': {}
 
 
   '@element-plus/icons-vue@2.3.1(vue@3.5.12(typescript@5.3.3))':
   '@element-plus/icons-vue@2.3.1(vue@3.5.12(typescript@5.3.3))':
@@ -7675,13 +7720,33 @@ snapshots:
     dependencies:
     dependencies:
       '@floating-ui/utils': 0.2.8
       '@floating-ui/utils': 0.2.8
 
 
+  '@floating-ui/core@1.7.5':
+    dependencies:
+      '@floating-ui/utils': 0.2.11
+
   '@floating-ui/dom@1.6.12':
   '@floating-ui/dom@1.6.12':
     dependencies:
     dependencies:
       '@floating-ui/core': 1.6.8
       '@floating-ui/core': 1.6.8
       '@floating-ui/utils': 0.2.8
       '@floating-ui/utils': 0.2.8
 
 
+  '@floating-ui/dom@1.7.6':
+    dependencies:
+      '@floating-ui/core': 1.7.5
+      '@floating-ui/utils': 0.2.11
+
+  '@floating-ui/utils@0.2.11': {}
+
   '@floating-ui/utils@0.2.8': {}
   '@floating-ui/utils@0.2.8': {}
 
 
+  '@floating-ui/vue@1.1.11(vue@3.5.12(typescript@5.3.3))':
+    dependencies:
+      '@floating-ui/dom': 1.7.6
+      '@floating-ui/utils': 0.2.11
+      vue-demi: 0.14.10(vue@3.5.12(typescript@5.3.3))
+    transitivePeerDependencies:
+      - '@vue/composition-api'
+      - vue
+
   '@form-create/component-elm-checkbox@3.2.14':
   '@form-create/component-elm-checkbox@3.2.14':
     dependencies:
     dependencies:
       '@form-create/utils': 3.2.14
       '@form-create/utils': 3.2.14
@@ -7792,10 +7857,10 @@ snapshots:
     dependencies:
     dependencies:
       '@iconify/types': 2.0.0
       '@iconify/types': 2.0.0
 
 
-  '@iconify/json@2.2.277':
+  '@iconify/json@2.2.524':
     dependencies:
     dependencies:
       '@iconify/types': 2.0.0
       '@iconify/types': 2.0.0
-      pathe: 1.1.2
+      pathe: 2.0.3
 
 
   '@iconify/types@2.0.0': {}
   '@iconify/types@2.0.0': {}
 
 
@@ -8390,6 +8455,8 @@ snapshots:
 
 
   '@types/web-bluetooth@0.0.20': {}
   '@types/web-bluetooth@0.0.20': {}
 
 
+  '@types/web-bluetooth@0.0.21': {}
+
   '@types/ws@8.18.1':
   '@types/ws@8.18.1':
     dependencies:
     dependencies:
       '@types/node': 20.17.9
       '@types/node': 20.17.9
@@ -9006,6 +9073,16 @@ snapshots:
 
 
   '@vue/shared@3.5.13': {}
   '@vue/shared@3.5.13': {}
 
 
+  '@vuepic/vue-datepicker@14.0.0(vue@3.5.12(typescript@5.3.3))':
+    dependencies:
+      '@date-fns/tz': 1.5.0
+      '@floating-ui/vue': 1.1.11(vue@3.5.12(typescript@5.3.3))
+      '@vueuse/core': 14.3.0(vue@3.5.12(typescript@5.3.3))
+      date-fns: 4.4.0
+      vue: 3.5.12(typescript@5.3.3)
+    transitivePeerDependencies:
+      - '@vue/composition-api'
+
   '@vueuse/core@10.11.1(vue@3.5.12(typescript@5.3.3))':
   '@vueuse/core@10.11.1(vue@3.5.12(typescript@5.3.3))':
     dependencies:
     dependencies:
       '@types/web-bluetooth': 0.0.20
       '@types/web-bluetooth': 0.0.20
@@ -9016,8 +9093,17 @@ snapshots:
       - '@vue/composition-api'
       - '@vue/composition-api'
       - vue
       - vue
 
 
+  '@vueuse/core@14.3.0(vue@3.5.12(typescript@5.3.3))':
+    dependencies:
+      '@types/web-bluetooth': 0.0.21
+      '@vueuse/metadata': 14.3.0
+      '@vueuse/shared': 14.3.0(vue@3.5.12(typescript@5.3.3))
+      vue: 3.5.12(typescript@5.3.3)
+
   '@vueuse/metadata@10.11.1': {}
   '@vueuse/metadata@10.11.1': {}
 
 
+  '@vueuse/metadata@14.3.0': {}
+
   '@vueuse/shared@10.11.1(vue@3.5.12(typescript@5.3.3))':
   '@vueuse/shared@10.11.1(vue@3.5.12(typescript@5.3.3))':
     dependencies:
     dependencies:
       vue-demi: 0.14.10(vue@3.5.12(typescript@5.3.3))
       vue-demi: 0.14.10(vue@3.5.12(typescript@5.3.3))
@@ -9025,6 +9111,10 @@ snapshots:
       - '@vue/composition-api'
       - '@vue/composition-api'
       - vue
       - vue
 
 
+  '@vueuse/shared@14.3.0(vue@3.5.12(typescript@5.3.3))':
+    dependencies:
+      vue: 3.5.12(typescript@5.3.3)
+
   '@wangeditor/basic-modules@1.1.7(@wangeditor/core@1.1.19(@uppy/core@2.3.4)(@uppy/xhr-upload@2.1.3(@uppy/core@2.3.4))(dom7@3.0.0)(is-hotkey@0.2.0)(lodash.camelcase@4.3.0)(lodash.clonedeep@4.5.0)(lodash.debounce@4.0.8)(lodash.foreach@4.5.0)(lodash.isequal@4.5.0)(lodash.throttle@4.1.1)(lodash.toarray@4.4.0)(nanoid@3.3.8)(slate@0.72.8)(snabbdom@3.6.2))(dom7@3.0.0)(lodash.throttle@4.1.1)(nanoid@3.3.8)(slate@0.72.8)(snabbdom@3.6.2)':
   '@wangeditor/basic-modules@1.1.7(@wangeditor/core@1.1.19(@uppy/core@2.3.4)(@uppy/xhr-upload@2.1.3(@uppy/core@2.3.4))(dom7@3.0.0)(is-hotkey@0.2.0)(lodash.camelcase@4.3.0)(lodash.clonedeep@4.5.0)(lodash.debounce@4.0.8)(lodash.foreach@4.5.0)(lodash.isequal@4.5.0)(lodash.throttle@4.1.1)(lodash.toarray@4.4.0)(nanoid@3.3.8)(slate@0.72.8)(snabbdom@3.6.2))(dom7@3.0.0)(lodash.throttle@4.1.1)(nanoid@3.3.8)(slate@0.72.8)(snabbdom@3.6.2)':
     dependencies:
     dependencies:
       '@wangeditor/core': 1.1.19(@uppy/core@2.3.4)(@uppy/xhr-upload@2.1.3(@uppy/core@2.3.4))(dom7@3.0.0)(is-hotkey@0.2.0)(lodash.camelcase@4.3.0)(lodash.clonedeep@4.5.0)(lodash.debounce@4.0.8)(lodash.foreach@4.5.0)(lodash.isequal@4.5.0)(lodash.throttle@4.1.1)(lodash.toarray@4.4.0)(nanoid@3.3.8)(slate@0.72.8)(snabbdom@3.6.2)
       '@wangeditor/core': 1.1.19(@uppy/core@2.3.4)(@uppy/xhr-upload@2.1.3(@uppy/core@2.3.4))(dom7@3.0.0)(is-hotkey@0.2.0)(lodash.camelcase@4.3.0)(lodash.clonedeep@4.5.0)(lodash.debounce@4.0.8)(lodash.foreach@4.5.0)(lodash.isequal@4.5.0)(lodash.throttle@4.1.1)(lodash.toarray@4.4.0)(nanoid@3.3.8)(slate@0.72.8)(snabbdom@3.6.2)
@@ -9975,6 +10065,8 @@ snapshots:
 
 
   date-fns@4.1.0: {}
   date-fns@4.1.0: {}
 
 
+  date-fns@4.4.0: {}
+
   date.js@0.3.3:
   date.js@0.3.3:
     dependencies:
     dependencies:
       debug: 3.1.0
       debug: 3.1.0

Tiedoston diff-näkymää rajattu, sillä se on liian suuri
+ 0 - 0
public/h5player/h5player.min.js


+ 705 - 0
public/h5player/playctrl1/DecodeWorker.js

@@ -0,0 +1,705 @@
+/**
+ * Created by wangweijie5 on 2016/12/5.
+ */
+(function (event) {
+    const AUDIO_TYPE = 0;	// 音频
+    const VIDEO_TYPE = 1;   // 视频
+    const PRIVT_TYPE = 2;  // 私有帧
+
+    const PLAYM4_AUDIO_FRAME = 100; // 音频帧
+    const PLAYM4_VIDEO_FRAME = 101; // 视频帧
+
+    const PLAYM4_OK = 1;
+    const PLAYM4_ORDER_ERROR = 2;
+    const PLAYM4_DECODE_ERROR = 44 	// 解码失败
+    const PLAYM4_NOT_KEYFRAME = 48; 	// 非关键帧
+    const PLAYM4_NEED_MORE_DATA = 31;   // 需要更多数据才能解析
+    const PLAYM4_NEED_NEET_LOOP = 35; //丢帧需要下个循环
+    const PLAYM4_SYS_NOT_SUPPORT = 16; 	// 不支持
+
+    // importScripts('Decoder.js');
+    // Module.addOnPostRun(function () {
+    //     postMessage({ 'function': "loaded" });
+    // });
+
+    var iStreamMode = 0;  // 流模式
+
+    var bOpenMode = false;
+    var bOpenStream = false;
+
+    var funGetFrameData = null;
+    var funGetAudFrameData = null;
+
+    var bWorkerPrintLog = false;//worker层log开关
+
+    var g_nPort = -1;
+    var pInputData = null;
+    var inputBufferSize = 40960;
+
+    self.JSPlayM4_RunTimeInfoCallBack = function (nPort, pstRunTimeInfo, pUser) {
+        let port = nPort;
+        let user = pUser;
+        let nRunTimeModule = Module.HEAP32[pstRunTimeInfo >> 2];
+        let nStrVersion = Module.HEAP32[pstRunTimeInfo + 4 >> 2];
+        let nFrameTimeStamp = Module.HEAP32[pstRunTimeInfo + 8 >> 2];
+        let nFrameNum = Module.HEAP32[pstRunTimeInfo + 12 >> 2];
+        let nErrorCode = Module.HEAP32[pstRunTimeInfo + 16 >> 2];
+        // console.log("nRunTimeModule:"+nRunTimeModule+",nFrameNum:"+nFrameNum+",nErrorCode:"+nErrorCode);
+        postMessage({ 'function': "RunTimeInfoCallBack", 'nRunTimeModule': nRunTimeModule, 'nStrVersion': nStrVersion, 'nFrameTimeStamp': nFrameTimeStamp, 'nFrameNum': nFrameNum, 'nErrorCode': nErrorCode });
+    }
+	
+	self.JSPlayM4_StreamInfoCallBack = function (nPort, pstStreamInfo, pUser)
+	{
+		let port = nPort;
+        let user = pUser;
+        let nSystemformat = Module.HEAP16[pstStreamInfo >> 1]; //封装类型
+        let nVideoformat = Module.HEAP16[pstStreamInfo + 2 >> 1];//视频编码类型
+        let nAudioformat = Module.HEAP16[pstStreamInfo + 4 >> 1];//音频编码类型
+        let nAudiochannels = Module.HEAP16[pstStreamInfo + 6 >> 1]; //音频通道数
+        let nAudiobitspersample = Module.HEAP32[pstStreamInfo + 8 >> 2];//音频样位率
+        let nAudiosamplesrate = Module.HEAP32[pstStreamInfo + 12 >> 2];//音频采样率
+        let nAudiobitrate = Module.HEAP32[pstStreamInfo + 16 >> 2];//音频比特率,单位:bit
+		//console.log("nSystemformat:" + nSystemformat + ",nVideoformat:" + nVideoformat + ",nAudioformat:" + nAudioformat + ",nAudiochannels:" + nAudiochannels + ",nAudiobitspersample:" + nAudiobitspersample + ",nAudiosamplesrate:" + nAudiosamplesrate + ",nAudiobitrate:" + nAudiobitrate);
+        postMessage({ 'function': "StreamInfoCallBack", 'nSystemformat': nSystemformat, 'nVideoformat': nVideoformat, 'nAudioformat': nAudioformat, 'nAudiochannels': nAudiochannels, 'nAudiobitspersample': nAudiobitspersample, 'nAudiosamplesrate': nAudiosamplesrate, 'nAudiobitrate': nAudiobitrate});
+	}
+
+    onmessage = function (event) {
+        var eventData = event.data;
+        var res = 0;
+        switch (eventData.command) {
+            case "importScripts":
+                const decodebase = eventData.data + "Decoder.js"
+                importScripts(decodebase);
+                Module.addOnPostRun(function () {
+                    postMessage({ 'function': "loaded" });
+                });
+                break;
+            case "printLog":
+                let downloadFlag = eventData.data;
+                if (downloadFlag === true) {
+                    bWorkerPrintLog = true;
+                    res = Module._SetPrintLogFlag(g_nPort, downloadFlag);
+                }
+                else {
+                    bWorkerPrintLog = false;
+                    res = Module._SetPrintLogFlag(g_nPort, downloadFlag);
+                }
+
+                if (res !== PLAYM4_OK) {
+                    console.log("DecodeWorker.js: PlayerSDK print log failed,res" + res);
+                    postMessage({ 'function': "printLog", 'errorCode': res });
+                }
+                break;
+            case "SetPlayPosition":
+                let nFrameNumOrTime = eventData.data;
+                let enPosType = eventData.type;
+                // res = Module._SetPlayPosition(nFrameNumOrTime,enPosType);
+                // if (res !== PLAYM4_OK)
+                // {
+                //     postMessage({'function': "SetPlayPosition", 'errorCode': res});
+                //     return;
+                // }
+                // //有没有buffer需要清除
+
+                break;
+            case "SetStreamOpenMode":
+                //获取端口号
+                g_nPort = Module._GetPort();
+                //设置流打开模式
+                iStreamMode = eventData.data;
+                res = Module._SetStreamOpenMode(g_nPort, iStreamMode);
+                if (res !== PLAYM4_OK) {
+                    postMessage({ 'function': "SetStreamOpenMode", 'errorCode': res });
+                    return;
+                }
+                bOpenMode = true;
+                break;
+
+            case "OpenStream":
+                // 接收到的数据
+                var iHeadLen = eventData.dataSize;
+                var pHead = Module._malloc(iHeadLen + 4);
+                if (pHead === null) {
+                    return;
+                }
+                var aHead = Module.HEAPU8.subarray(pHead, pHead + iHeadLen);
+                aHead.set(new Uint8Array(eventData.data));
+                res = Module._OpenStream(g_nPort, pHead, iHeadLen, eventData.bufPoolSize);
+                postMessage({ 'function': "OpenStream", 'errorCode': res });
+                if (res !== PLAYM4_OK) {
+                    //释放内存
+                    Module._free(pHead);
+                    pHead = null;
+                    return;
+                }
+                bOpenStream = true;
+                break;
+            case "Play":
+                let resP = Module._Play(g_nPort);
+                if (resP !== PLAYM4_OK) {
+                    return;
+                }
+                break;
+            case "InputData":
+                // 接收到的数据
+                var iLen = eventData.dataSize;
+                if (iLen > 0) {
+                    if (pInputData == null || iLen > inputBufferSize) {
+                        if (pInputData != null) {
+                            Module._free(pInputData);
+                            pInputData = null;
+                        }
+                        if (iLen > inputBufferSize) {
+                            inputBufferSize = iLen;
+                        }
+
+                        pInputData = Module._malloc(inputBufferSize);
+                        if (pInputData === null) {
+                            return;
+                        }
+                    }
+
+                    var inputData = new Uint8Array(eventData.data);
+                    // var aInputData = Module.HEAPU8.subarray(pInputData, pInputData + iLen);
+                    // aInputData.set(inputData);
+                    Module.writeArrayToMemory(inputData, pInputData);
+                    inputData = null;
+                    res = Module._InputData(g_nPort, pInputData, iLen);
+                    if (res !== PLAYM4_OK) {
+                        let errorCode = Module._GetLastError(g_nPort);
+                        let sourceRemain = Module._GetSourceBufferRemain(g_nPort);
+                        postMessage({ 'function': "InputData", 'errorCode': errorCode, "sourceRemain": sourceRemain });
+                    }
+                    //Module._free(pInputData);
+                    //pInputData = null;
+                } else {
+                    let sourceRemain = Module._GetSourceBufferRemain(g_nPort);
+                    if (sourceRemain == 0) {
+                        postMessage({ 'function': "InputData", 'errorCode': PLAYM4_NEED_MORE_DATA });
+                        return;
+                    }
+                }
+
+                /////////////////////
+                // if (funGetFrameData === null) {
+                //     funGetFrameData = Module.cwrap('GetFrameData', 'number');
+                // }
+
+                while (bOpenMode && bOpenStream) {
+
+                    var ret = getFrameData();
+                    // 直到获取视频帧或数据不足为止
+                    if (PLAYM4_VIDEO_FRAME === ret || PLAYM4_NEED_MORE_DATA === ret || PLAYM4_ORDER_ERROR === ret)//PLAYM4_VIDEO_FRAME === ret ||  || PLAYM4_NEED_NEET_LOOP === ret
+                    {
+                        break;
+                    }
+                }
+                break;
+
+            case "SetSecretKey":
+                var keyLen = eventData.nKeyLen;
+                var pKeyData = Module._malloc(keyLen);
+                if (pKeyData === null) {
+                    return;
+                }
+                var nKeySize = eventData.data.length
+                var bufData = stringToBytes(eventData.data);
+                var aKeyData = Module.HEAPU8.subarray(pKeyData, pKeyData + keyLen);
+                let u8array = new Uint8Array(keyLen);
+                aKeyData.set(u8array, 0);
+                aKeyData.set(new Uint8Array(bufData));
+                aKeyData = null;
+                u8array = null;
+
+                res = Module._SetSecretKey(g_nPort, eventData.nKeyType, pKeyData, keyLen);//, nKeySize
+                if (res !== PLAYM4_OK) {
+                    postMessage({ 'function': "SetSecretKey", 'errorCode': res });
+                    Module._free(pKeyData);
+                    pKeyData = null;
+                    return;
+                }
+
+                Module._free(pKeyData);
+                pKeyData = null;
+                break;
+
+            case "GetBMP":
+                var nBMPWidth = eventData.width;
+                var nBMPHeight = eventData.height;
+                var pYUVData = eventData.data;
+                var nYUVSize = nBMPWidth * nBMPHeight * 3 / 2;
+                var oBMPCropRect = {
+                    left: eventData.left,
+                    top: eventData.top,
+                    right: eventData.right,
+                    bottom: eventData.bottom
+                };
+
+                var pDataYUV = Module._malloc(nYUVSize);
+                if (pDataYUV === null) {
+                    return;
+                }
+
+                Module.writeArrayToMemory(new Uint8Array(pYUVData, 0, nYUVSize), pDataYUV);
+
+                // 分配BMP空间
+                var nBmpSize = nBMPWidth * nBMPHeight * 4 + 60;
+                var pBmpData = Module._malloc(nBmpSize);
+                var pBmpSize = Module._malloc(4);
+                if (pBmpData === null || pBmpSize === null) {
+                    Module._free(pDataYUV);
+                    pDataYUV = null;
+
+                    if (pBmpData != null) {
+                        Module._free(pBmpData);
+                        pBmpData = null;
+                    }
+
+                    if (pBmpSize != null) {
+                        Module._free(pBmpSize);
+                        pBmpSize = null;
+                    }
+                    return;
+                }
+
+                //Module._memset(pBmpSize, nBmpSize, 4); // 防止bmp截图出现输入数据过大的错误码
+                Module.setValue(pBmpSize, nBmpSize, "i32");
+                res = Module._GetBMP(g_nPort, pDataYUV, nYUVSize, pBmpData, pBmpSize,
+                    oBMPCropRect.left, oBMPCropRect.top, oBMPCropRect.right, oBMPCropRect.bottom);
+                if (res !== PLAYM4_OK) {
+                    postMessage({ 'function': "GetBMP", 'errorCode': res });
+                    Module._free(pDataYUV);
+                    pDataYUV = null;
+                    Module._free(pBmpData);
+                    pBmpData = null;
+                    Module._free(pBmpSize);
+                    pBmpSize = null;
+                    return;
+                }
+
+                // 获取BMP图片大小
+                var nBmpDataSize = Module.getValue(pBmpSize, "i32");
+
+                // 获取BMP图片数据
+                var aBmpData = new Uint8Array(nBmpDataSize);
+                aBmpData.set(Module.HEAPU8.subarray(pBmpData, pBmpData + nBmpDataSize));
+
+                postMessage({ 'function': "GetBMP", 'data': aBmpData, 'errorCode': res }, [aBmpData.buffer]);
+                aBmpData = null;
+                if (pDataYUV != null) {
+                    Module._free(pDataYUV);
+                    pDataYUV = null;
+                }
+                if (pBmpData != null) {
+                    Module._free(pBmpData);
+                    pBmpData = null;
+                }
+                if (pBmpSize != null) {
+                    Module._free(pBmpSize);
+                    pBmpSize = null;
+                }
+                break;
+
+            case "GetJPEG":
+                var nJpegWidth = eventData.width;
+                var nJpegHeight = eventData.height;
+                var pYUVData1 = eventData.data;
+                var nYUVSize1 = nJpegWidth * nJpegHeight * 3 / 2;
+                var oJpegCropRect = {
+                    left: eventData.left,
+                    top: eventData.top,
+                    right: eventData.right,
+                    bottom: eventData.bottom
+                };
+
+                var pDataYUV1 = Module._malloc(nYUVSize1);
+                if (pDataYUV1 === null) {
+                    return;
+                }
+
+                Module.writeArrayToMemory(new Uint8Array(pYUVData1, 0, nYUVSize1), pDataYUV1);
+
+                // 分配JPEG空间
+                var pJpegData = Module._malloc(nYUVSize1);
+                var pJpegSize = Module._malloc(4);
+                if (pJpegData === null || pJpegSize === null) {
+                    if (pJpegData != null) {
+                        Module._free(pJpegData);
+                        pJpegData = null;
+                    }
+
+                    if (pJpegSize != null) {
+                        Module._free(pJpegSize);
+                        pJpegSize = null;
+                    }
+
+                    if (pDataYUV1 != null) {
+                        Module._free(pDataYUV1);
+                        pDataYUV1 = null;
+                    }
+                    return;
+                }
+
+                Module.setValue(pJpegSize, nJpegWidth * nJpegHeight * 2, "i32");    // JPEG抓图,输入缓冲长度不小于当前帧YUV大小
+
+                res = Module._GetJPEG(g_nPort, pDataYUV1, nYUVSize1, pJpegData, pJpegSize,
+                    oJpegCropRect.left, oJpegCropRect.top, oJpegCropRect.right, oJpegCropRect.bottom);
+                if (res !== PLAYM4_OK) {
+                    postMessage({ 'function': "GetJPEG", 'errorCode': res });
+                    if (pJpegData != null) {
+                        Module._free(pJpegData);
+                        pJpegData = null;
+                    }
+
+                    if (pJpegSize != null) {
+                        Module._free(pJpegSize);
+                        pJpegSize = null;
+                    }
+
+                    if (pDataYUV1 != null) {
+                        Module._free(pDataYUV1);
+                        pDataYUV1 = null;
+                    }
+                    return;
+                }
+
+                // 获取JPEG图片大小
+                var nJpegSize = Module.getValue(pJpegSize, "i32");
+
+                // 获取JPEG图片数据
+                var aJpegData = new Uint8Array(nJpegSize);
+                aJpegData.set(Module.HEAPU8.subarray(pJpegData, pJpegData + nJpegSize));
+
+                postMessage({ 'function': "GetJPEG", 'data': aJpegData, 'errorCode': res }, [aJpegData.buffer]);
+
+                nJpegSize = null;
+                aJpegData = null;
+
+                if (pDataYUV1 != null) {
+                    Module._free(pDataYUV1);
+                    pDataYUV1 = null;
+                }
+                if (pJpegData != null) {
+                    Module._free(pJpegData);
+                    pJpegData = null;
+                }
+                if (pJpegSize != null) {
+                    Module._free(pJpegSize);
+                    pJpegSize = null;
+                }
+                break;
+
+            case "SetDecodeFrameType":
+                var nFrameType = eventData.data;
+                res = Module._SetDecodeFrameType(g_nPort, nFrameType);
+                if (res !== PLAYM4_OK) {
+                    postMessage({ 'function': "SetDecodeFrameType", 'errorCode': res });
+                    return;
+                }
+                break;
+            case "CloseStream":
+                //stop
+                let resS = Module._Stop(g_nPort);
+                if (resS !== PLAYM4_OK) {
+                    postMessage({ 'function': "Stop", 'errorCode': res });
+                    return;
+                }
+                //closeStream
+                res = Module._CloseStream(g_nPort);
+                if (res !== PLAYM4_OK) {
+                    postMessage({ 'function': "CloseStream", 'errorCode': res });
+                    return;
+                }
+                //freePort
+                let resF = Module._FreePort(g_nPort);
+                if (resF !== PLAYM4_OK) {
+                    postMessage({ 'function': "FreePort", 'errorCode': res });
+                    return;
+                }
+                if (pInputData != null) {
+                    Module._free(pInputData);
+                    pInputData = null;
+                }
+                break;
+            case "PlaySound":
+                let resPS = Module._PlaySound(g_nPort);
+                if (resPS !== PLAYM4_OK) {
+                    console.log("PlaySound failed");
+                    return;
+                }
+                break;
+            case "StopSound":
+                let resSS = Module._StopSound();
+                if (resSS !== PLAYM4_OK) {
+                    console.log("StopSound failed");
+                    return;
+                }
+                break;
+            case "SetVolume":
+                let resSV = Module._SetVolume(g_nPort, eventData.volume);
+                if (resSV !== PLAYM4_OK) {
+                    console.log("Audio SetVolume failed");
+                    return;
+                }
+                break;
+            case "GetVolume":
+                let volume = Module._GetVolume();
+                if (volume > 0) {
+                    postMessage({ 'function': "GetVolume", 'volume': volume });
+                }
+                else {
+                    console.log("Audio GetVolume failed");
+                    return;
+                }
+                break;
+            case "OnlyPlaySound":
+                let resOPS = Module._OnlyPlaySound(g_nPort);
+                if (resOPS !== PLAYM4_OK) {
+                    console.log("OnlyPlaySound failed");
+                    return;
+                }
+                break;
+            case "Pause":
+                let resPa = Module._Pause(g_nPort, eventData.bPlay);
+                if (resPa !== PLAYM4_OK) {
+                    console.log("Pause failed");
+                    return;
+                }
+            case "PlayRate":
+                Module._SetPlayRate(g_nPort, eventData.playRate);
+                break;
+            case "SetIFrameDecInterval":
+                Module._SetIFrameDecInterval(g_nPort, eventData.data);
+                break;
+            case "SetLostFrameMode":
+                Module._SetLostFrameMode(g_nPort, eventData.data, 0);
+                break;
+            case "SetDemuxModel":
+                let resSDM = Module._SetDemuxModel(g_nPort, eventData.nIdemuxType, eventData.bTrue);
+                break;
+            case "SkipErrorData":
+                Module._SkipErrorData(g_nPort, eventData.bSkip);
+                break;
+            case "SetDecodeERC":
+                Module._SetDecodeERC(g_nPort, eventData.nLevel);
+                break;
+            case "SetANRParam":
+                Module._SetANRParam(g_nPort, eventData.nEnable, eventData.nANRLevel);
+                break;
+            case "SetResampleValue":
+                Module._SetResampleValue(g_nPort, eventData.nEnable, eventData.resampleValue);
+                break;
+            case "GetLastError":
+                let errorCode = Module._GetLastError(g_nPort);
+                postMessage({ 'function': "GetLastError", 'errorCode': errorCode });
+                break;
+            case "SetGlobalBaseTime":
+                Module._SetGlobalBaseTime(g_nPort, eventData.year, eventData.month, eventData.day, eventData.hour, eventData.min, eventData.sec, eventData.ms);
+                break;
+            case "SetRunTimeInfoCB":
+                Module._SetRunTimeInfoCallBackEx(g_nPort, eventData.nModuleType, 0);
+                break;
+			case "SetStreamInfoCB":
+                Module._SetStreamInfoCallBack(g_nPort, eventData.nType, 0);
+                break;
+			case "ResetBuffer":
+                Module._JSPlayM4_ResetBuffer(g_nPort, eventData.type);
+                break;
+            default:
+                break;
+        }
+    };
+
+    function getOSDTime(oFrameInfo) {
+        var iYear = oFrameInfo.year;
+        var iMonth = oFrameInfo.month;
+        var iDay = oFrameInfo.day;
+        var iHour = oFrameInfo.hour;
+        var iMinute = oFrameInfo.minute;
+        var iSecond = oFrameInfo.second;
+        var iMiSecond = oFrameInfo.misecond
+
+        if (iMonth < 10) {
+            iMonth = "0" + iMonth;
+        }
+        if (iDay < 10) {
+            iDay = "0" + iDay;
+        }
+        if (iHour < 10) {
+            iHour = "0" + iHour;
+        }
+        if (iMinute < 10) {
+            iMinute = "0" + iMinute;
+        }
+        if (iSecond < 10) {
+            iSecond = "0" + iSecond;
+        }
+        let osdTime = {};
+
+        osdTime.year = iYear;
+        osdTime.month = iMonth;
+        osdTime.week = 0;
+        osdTime.day = iDay;
+        osdTime.hour = iHour;
+        osdTime.minute = iMinute;
+        osdTime.second = iSecond;
+        osdTime.milliseconds = iMiSecond;
+        return osdTime;
+        //return iYear + "-" + iMonth + "-" + iDay + " " + iHour + ":" + iMinute + ":" + iSecond;
+    }
+    // 获取帧数据
+    function getFrameData() {
+        // function getFrameData() {
+        // 获取帧数据
+        var res = Module._GetFrameData();
+        //var res = fun();
+        if (res === PLAYM4_OK) {
+            var iFrameInfo = Module._GetFrameInfo();
+            let oFrameInfo = {};
+            oFrameInfo.frameType = Module.HEAP32[iFrameInfo >> 2];
+            oFrameInfo.frameSize = Module.HEAP32[iFrameInfo + 4 >> 2];
+            oFrameInfo.width = Module.HEAP32[iFrameInfo + 8 >> 2];
+            oFrameInfo.height = Module.HEAP32[iFrameInfo + 12 >> 2];
+            oFrameInfo.timeStamp = Module.HEAP32[iFrameInfo + 16 >> 2];
+            oFrameInfo.frameRate = Module.HEAP32[iFrameInfo + 20 >> 2];
+            oFrameInfo.bitsPerSample = Module.HEAP32[iFrameInfo + 24 >> 2];
+            oFrameInfo.samplesPerSec = Module.HEAP32[iFrameInfo + 28 >> 2];
+            oFrameInfo.channels = Module.HEAP32[iFrameInfo + 32 >> 2];
+            oFrameInfo.frameNum = Module.HEAP32[iFrameInfo + 36 >> 2];
+
+            oFrameInfo.cropLeft = Module.HEAP32[iFrameInfo + 40 >> 2];
+            oFrameInfo.cropRight = Module.HEAP32[iFrameInfo + 44 >> 2];
+            oFrameInfo.cropTop = Module.HEAP32[iFrameInfo + 48 >> 2];
+            oFrameInfo.cropBottom = Module.HEAP32[iFrameInfo + 52 >> 2];
+
+            oFrameInfo.year = Module.HEAP16[iFrameInfo + 64 >> 1];
+            oFrameInfo.month = Module.HEAP16[iFrameInfo + 66 >> 1];
+            oFrameInfo.day = Module.HEAP16[iFrameInfo + 68 >> 1];
+            oFrameInfo.hour = Module.HEAP16[iFrameInfo + 70 >> 1];
+            oFrameInfo.minute = Module.HEAP16[iFrameInfo + 72 >> 1];
+            oFrameInfo.second = Module.HEAP16[iFrameInfo + 74 >> 1];
+            oFrameInfo.misecond = Module.HEAP16[iFrameInfo + 76 >> 1];
+            switch (oFrameInfo.frameType) {
+                case AUDIO_TYPE:
+                    var iSize = oFrameInfo.frameSize;
+                    if (0 === iSize) {
+                        return -1;
+                    }
+                    var pPCM = Module._GetFrameBuffer();
+                    // var audioBuf = new ArrayBuffer(iSize);
+                    var aPCMData = new Uint8Array(iSize);
+                    aPCMData.set(Module.HEAPU8.subarray(pPCM, pPCM + iSize));
+                    if (bWorkerPrintLog) {
+                        console.log("<<<Worker: audio media Info: nSise:" + oFrameInfo.frameSize + ",nSampleRate:" + oFrameInfo.samplesPerSec + ',channel:' + oFrameInfo.channels + ',bitsPerSample:' + oFrameInfo.bitsPerSample);
+                    }
+                    postMessage({
+                        'function': "GetFrameData", 'type': "audioType", 'data': aPCMData.buffer,
+                        'frameInfo': oFrameInfo, 'errorCode': res
+                    }, [aPCMData.buffer]);
+
+                    oFrameInfo = null;
+                    pPCM = null;
+                    aPCMData = null;
+                    return PLAYM4_AUDIO_FRAME;
+
+                case VIDEO_TYPE:
+                    var szOSDTime = getOSDTime(oFrameInfo);
+
+                    var iWidth = oFrameInfo.width;
+                    var iHeight = oFrameInfo.height;
+
+                    var iYUVSize = iWidth * iHeight * 3 / 2;
+                    if (0 === iYUVSize) {
+                        return -1;
+                    }
+
+                    var pYUV = Module._GetFrameBuffer();
+
+                    // 图像数据渲染后压回,若从主码流切到子码流,存在数组大小与图像大小不匹配现象
+                    var aYUVData = new Uint8Array(iYUVSize);
+                    aYUVData.set(Module.HEAPU8.subarray(pYUV, pYUV + iYUVSize));
+                    if (bWorkerPrintLog) {
+                        console.log("<<<Worker: video media Info: Width:" + oFrameInfo.width + ",Height:" + oFrameInfo.height + ",timeStamp:" + oFrameInfo.timeStamp);
+                    }
+
+                    postMessage({
+                        'function': "GetFrameData", 'type': "videoType", 'data': aYUVData.buffer,
+                        'dataLen': aYUVData.length, 'osd': szOSDTime, 'frameInfo': oFrameInfo, 'errorCode': res
+                    }, [aYUVData.buffer]);
+
+                    oFrameInfo = null;
+                    pYUV = null;
+                    aYUVData = null;
+                    return PLAYM4_VIDEO_FRAME;
+
+                case PRIVT_TYPE:
+                    postMessage({
+                        'function': "GetFrameData", 'type': "", 'data': null,
+                        'dataLen': -1, 'osd': 0, 'frameInfo': null, 'errorCode': PLAYM4_SYS_NOT_SUPPORT
+                    });
+                    return PLAYM4_SYS_NOT_SUPPORT;
+
+                default:
+                    postMessage({
+                        'function': "GetFrameData", 'type': "", 'data': null,
+                        'dataLen': -1, 'osd': 0, 'frameInfo': null, 'errorCode': PLAYM4_SYS_NOT_SUPPORT
+                    });
+                    return PLAYM4_SYS_NOT_SUPPORT;
+            }
+        }
+        else {
+            let errorCode = Module._GetLastError(g_nPort);
+            //解码失败返回裸数据
+            if (PLAYM4_DECODE_ERROR === errorCode) {
+                var rawInfo = Module._GetRawDataInfo();
+                var pRawData = Module._GetRawDataBuffer();
+                var aRawData = new Uint8Array(rawInfo.isize);
+                aRawData.set(Module.HEAPU8.subarray(pRawData, pRawData + rawInfo.isize));
+                postMessage({
+                    'function': "GetRawData", 'type': "", 'data': aRawData.buffer,
+                    'rawDataLen': rawInfo.isize, 'osd': 0, 'frameInfo': null, 'errorCode': errorCode
+                });
+                rawInfo = null;
+                pRawData = null;
+                aRawData = null;
+            }
+            //需要更多数据
+            if (PLAYM4_NEED_MORE_DATA === errorCode || PLAYM4_SYS_NOT_SUPPORT === errorCode || PLAYM4_NEED_NEET_LOOP === errorCode) {
+                postMessage({
+                    'function': "GetFrameData", 'type': "", 'data': null,
+                    'dataLen': -1, 'osd': 0, 'frameInfo': null, 'errorCode': errorCode
+                });
+            }
+            return errorCode;
+        }
+    }
+
+    // 开始计算时间
+    function startTime() {
+        return new Date().getTime();
+    }
+
+    // 结束计算时间
+    function endTime() {
+        return new Date().getTime();
+    }
+
+    // 字母字符串转byte数组
+    function stringToBytes(str) {
+        var ch, st, re = [];
+        for (var i = 0; i < str.length; i++) {
+            ch = str.charCodeAt(i);  // get char
+            st = [];                 // set up "stack"
+            do {
+                st.push(ch & 0xFF);  // push byte to stack
+                ch = ch >> 8;          // shift value down by 1 byte
+            }
+            while (ch);
+            // add stack contents to result
+            // done because chars have "wrong" endianness
+            re = re.concat(st.reverse());
+        }
+        // return an array of bytes
+        return re;
+    }
+})();

BIN
public/h5player/playctrl1/Decoder.js


+ 705 - 0
public/h5player/playctrl1simd/DecodeWorker.js

@@ -0,0 +1,705 @@
+/**
+ * Created by wangweijie5 on 2016/12/5.
+ */
+(function (event) {
+    const AUDIO_TYPE = 0;	// 音频
+    const VIDEO_TYPE = 1;   // 视频
+    const PRIVT_TYPE = 2;  // 私有帧
+
+    const PLAYM4_AUDIO_FRAME = 100; // 音频帧
+    const PLAYM4_VIDEO_FRAME = 101; // 视频帧
+
+    const PLAYM4_OK = 1;
+    const PLAYM4_ORDER_ERROR = 2;
+    const PLAYM4_DECODE_ERROR = 44 	// 解码失败
+    const PLAYM4_NOT_KEYFRAME = 48; 	// 非关键帧
+    const PLAYM4_NEED_MORE_DATA = 31;   // 需要更多数据才能解析
+    const PLAYM4_NEED_NEET_LOOP = 35; //丢帧需要下个循环
+    const PLAYM4_SYS_NOT_SUPPORT = 16; 	// 不支持
+
+    // importScripts('Decoder.js');
+    // Module.addOnPostRun(function () {
+    //     postMessage({ 'function': "loaded" });
+    // });
+
+    var iStreamMode = 0;  // 流模式
+
+    var bOpenMode = false;
+    var bOpenStream = false;
+
+    var funGetFrameData = null;
+    var funGetAudFrameData = null;
+
+    var bWorkerPrintLog = false;//worker层log开关
+
+    var g_nPort = -1;
+    var pInputData = null;
+    var inputBufferSize = 40960;
+
+    self.JSPlayM4_RunTimeInfoCallBack = function (nPort, pstRunTimeInfo, pUser) {
+        let port = nPort;
+        let user = pUser;
+        let nRunTimeModule = Module.HEAP32[pstRunTimeInfo >> 2];
+        let nStrVersion = Module.HEAP32[pstRunTimeInfo + 4 >> 2];
+        let nFrameTimeStamp = Module.HEAP32[pstRunTimeInfo + 8 >> 2];
+        let nFrameNum = Module.HEAP32[pstRunTimeInfo + 12 >> 2];
+        let nErrorCode = Module.HEAP32[pstRunTimeInfo + 16 >> 2];
+        // console.log("nRunTimeModule:"+nRunTimeModule+",nFrameNum:"+nFrameNum+",nErrorCode:"+nErrorCode);
+        postMessage({ 'function': "RunTimeInfoCallBack", 'nRunTimeModule': nRunTimeModule, 'nStrVersion': nStrVersion, 'nFrameTimeStamp': nFrameTimeStamp, 'nFrameNum': nFrameNum, 'nErrorCode': nErrorCode });
+    }
+	
+	self.JSPlayM4_StreamInfoCallBack = function (nPort, pstStreamInfo, pUser)
+	{
+		let port = nPort;
+        let user = pUser;
+        let nSystemformat = Module.HEAP16[pstStreamInfo >> 1]; //封装类型
+        let nVideoformat = Module.HEAP16[pstStreamInfo + 2 >> 1];//视频编码类型
+        let nAudioformat = Module.HEAP16[pstStreamInfo + 4 >> 1];//音频编码类型
+        let nAudiochannels = Module.HEAP16[pstStreamInfo + 6 >> 1]; //音频通道数
+        let nAudiobitspersample = Module.HEAP32[pstStreamInfo + 8 >> 2];//音频样位率
+        let nAudiosamplesrate = Module.HEAP32[pstStreamInfo + 12 >> 2];//音频采样率
+        let nAudiobitrate = Module.HEAP32[pstStreamInfo + 16 >> 2];//音频比特率,单位:bit
+		//console.log("nSystemformat:" + nSystemformat + ",nVideoformat:" + nVideoformat + ",nAudioformat:" + nAudioformat + ",nAudiochannels:" + nAudiochannels + ",nAudiobitspersample:" + nAudiobitspersample + ",nAudiosamplesrate:" + nAudiosamplesrate + ",nAudiobitrate:" + nAudiobitrate);
+        postMessage({ 'function': "StreamInfoCallBack", 'nSystemformat': nSystemformat, 'nVideoformat': nVideoformat, 'nAudioformat': nAudioformat, 'nAudiochannels': nAudiochannels, 'nAudiobitspersample': nAudiobitspersample, 'nAudiosamplesrate': nAudiosamplesrate, 'nAudiobitrate': nAudiobitrate});
+	}
+
+    onmessage = function (event) {
+        var eventData = event.data;
+        var res = 0;
+        switch (eventData.command) {
+            case "importScripts":
+                const decodebase = eventData.data + "Decoder.js"
+                importScripts(decodebase);
+                Module.addOnPostRun(function () {
+                    postMessage({ 'function': "loaded" });
+                });
+                break;
+            case "printLog":
+                let downloadFlag = eventData.data;
+                if (downloadFlag === true) {
+                    bWorkerPrintLog = true;
+                    res = Module._SetPrintLogFlag(g_nPort, downloadFlag);
+                }
+                else {
+                    bWorkerPrintLog = false;
+                    res = Module._SetPrintLogFlag(g_nPort, downloadFlag);
+                }
+
+                if (res !== PLAYM4_OK) {
+                    console.log("DecodeWorker.js: PlayerSDK print log failed,res" + res);
+                    postMessage({ 'function': "printLog", 'errorCode': res });
+                }
+                break;
+            case "SetPlayPosition":
+                let nFrameNumOrTime = eventData.data;
+                let enPosType = eventData.type;
+                // res = Module._SetPlayPosition(nFrameNumOrTime,enPosType);
+                // if (res !== PLAYM4_OK)
+                // {
+                //     postMessage({'function': "SetPlayPosition", 'errorCode': res});
+                //     return;
+                // }
+                // //有没有buffer需要清除
+
+                break;
+            case "SetStreamOpenMode":
+                //获取端口号
+                g_nPort = Module._GetPort();
+                //设置流打开模式
+                iStreamMode = eventData.data;
+                res = Module._SetStreamOpenMode(g_nPort, iStreamMode);
+                if (res !== PLAYM4_OK) {
+                    postMessage({ 'function': "SetStreamOpenMode", 'errorCode': res });
+                    return;
+                }
+                bOpenMode = true;
+                break;
+
+            case "OpenStream":
+                // 接收到的数据
+                var iHeadLen = eventData.dataSize;
+                var pHead = Module._malloc(iHeadLen + 4);
+                if (pHead === null) {
+                    return;
+                }
+                var aHead = Module.HEAPU8.subarray(pHead, pHead + iHeadLen);
+                aHead.set(new Uint8Array(eventData.data));
+                res = Module._OpenStream(g_nPort, pHead, iHeadLen, eventData.bufPoolSize);
+                postMessage({ 'function': "OpenStream", 'errorCode': res });
+                if (res !== PLAYM4_OK) {
+                    //释放内存
+                    Module._free(pHead);
+                    pHead = null;
+                    return;
+                }
+                bOpenStream = true;
+                break;
+            case "Play":
+                let resP = Module._Play(g_nPort);
+                if (resP !== PLAYM4_OK) {
+                    return;
+                }
+                break;
+            case "InputData":
+                // 接收到的数据
+                var iLen = eventData.dataSize;
+                if (iLen > 0) {
+                    if (pInputData == null || iLen > inputBufferSize) {
+                        if (pInputData != null) {
+                            Module._free(pInputData);
+                            pInputData = null;
+                        }
+                        if (iLen > inputBufferSize) {
+                            inputBufferSize = iLen;
+                        }
+
+                        pInputData = Module._malloc(inputBufferSize);
+                        if (pInputData === null) {
+                            return;
+                        }
+                    }
+
+                    var inputData = new Uint8Array(eventData.data);
+                    // var aInputData = Module.HEAPU8.subarray(pInputData, pInputData + iLen);
+                    // aInputData.set(inputData);
+                    Module.writeArrayToMemory(inputData, pInputData);
+                    inputData = null;
+                    res = Module._InputData(g_nPort, pInputData, iLen);
+                    if (res !== PLAYM4_OK) {
+                        let errorCode = Module._GetLastError(g_nPort);
+                        let sourceRemain = Module._GetSourceBufferRemain(g_nPort);
+                        postMessage({ 'function': "InputData", 'errorCode': errorCode, "sourceRemain": sourceRemain });
+                    }
+                    //Module._free(pInputData);
+                    //pInputData = null;
+                } else {
+                    let sourceRemain = Module._GetSourceBufferRemain(g_nPort);
+                    if (sourceRemain == 0) {
+                        postMessage({ 'function': "InputData", 'errorCode': PLAYM4_NEED_MORE_DATA });
+                        return;
+                    }
+                }
+
+                /////////////////////
+                // if (funGetFrameData === null) {
+                //     funGetFrameData = Module.cwrap('GetFrameData', 'number');
+                // }
+
+                while (bOpenMode && bOpenStream) {
+
+                    var ret = getFrameData();
+                    // 直到获取视频帧或数据不足为止
+                    if (PLAYM4_VIDEO_FRAME === ret || PLAYM4_NEED_MORE_DATA === ret || PLAYM4_ORDER_ERROR === ret)//PLAYM4_VIDEO_FRAME === ret ||  || PLAYM4_NEED_NEET_LOOP === ret
+                    {
+                        break;
+                    }
+                }
+                break;
+
+            case "SetSecretKey":
+                var keyLen = eventData.nKeyLen;
+                var pKeyData = Module._malloc(keyLen);
+                if (pKeyData === null) {
+                    return;
+                }
+                var nKeySize = eventData.data.length
+                var bufData = stringToBytes(eventData.data);
+                var aKeyData = Module.HEAPU8.subarray(pKeyData, pKeyData + keyLen);
+                let u8array = new Uint8Array(keyLen);
+                aKeyData.set(u8array, 0);
+                aKeyData.set(new Uint8Array(bufData));
+                aKeyData = null;
+                u8array = null;
+
+                res = Module._SetSecretKey(g_nPort, eventData.nKeyType, pKeyData, keyLen);//, nKeySize
+                if (res !== PLAYM4_OK) {
+                    postMessage({ 'function': "SetSecretKey", 'errorCode': res });
+                    Module._free(pKeyData);
+                    pKeyData = null;
+                    return;
+                }
+
+                Module._free(pKeyData);
+                pKeyData = null;
+                break;
+
+            case "GetBMP":
+                var nBMPWidth = eventData.width;
+                var nBMPHeight = eventData.height;
+                var pYUVData = eventData.data;
+                var nYUVSize = nBMPWidth * nBMPHeight * 3 / 2;
+                var oBMPCropRect = {
+                    left: eventData.left,
+                    top: eventData.top,
+                    right: eventData.right,
+                    bottom: eventData.bottom
+                };
+
+                var pDataYUV = Module._malloc(nYUVSize);
+                if (pDataYUV === null) {
+                    return;
+                }
+
+                Module.writeArrayToMemory(new Uint8Array(pYUVData, 0, nYUVSize), pDataYUV);
+
+                // 分配BMP空间
+                var nBmpSize = nBMPWidth * nBMPHeight * 4 + 60;
+                var pBmpData = Module._malloc(nBmpSize);
+                var pBmpSize = Module._malloc(4);
+                if (pBmpData === null || pBmpSize === null) {
+                    Module._free(pDataYUV);
+                    pDataYUV = null;
+
+                    if (pBmpData != null) {
+                        Module._free(pBmpData);
+                        pBmpData = null;
+                    }
+
+                    if (pBmpSize != null) {
+                        Module._free(pBmpSize);
+                        pBmpSize = null;
+                    }
+                    return;
+                }
+
+                //Module._memset(pBmpSize, nBmpSize, 4); // 防止bmp截图出现输入数据过大的错误码
+                Module.setValue(pBmpSize, nBmpSize, "i32");
+                res = Module._GetBMP(g_nPort, pDataYUV, nYUVSize, pBmpData, pBmpSize,
+                    oBMPCropRect.left, oBMPCropRect.top, oBMPCropRect.right, oBMPCropRect.bottom);
+                if (res !== PLAYM4_OK) {
+                    postMessage({ 'function': "GetBMP", 'errorCode': res });
+                    Module._free(pDataYUV);
+                    pDataYUV = null;
+                    Module._free(pBmpData);
+                    pBmpData = null;
+                    Module._free(pBmpSize);
+                    pBmpSize = null;
+                    return;
+                }
+
+                // 获取BMP图片大小
+                var nBmpDataSize = Module.getValue(pBmpSize, "i32");
+
+                // 获取BMP图片数据
+                var aBmpData = new Uint8Array(nBmpDataSize);
+                aBmpData.set(Module.HEAPU8.subarray(pBmpData, pBmpData + nBmpDataSize));
+
+                postMessage({ 'function': "GetBMP", 'data': aBmpData, 'errorCode': res }, [aBmpData.buffer]);
+                aBmpData = null;
+                if (pDataYUV != null) {
+                    Module._free(pDataYUV);
+                    pDataYUV = null;
+                }
+                if (pBmpData != null) {
+                    Module._free(pBmpData);
+                    pBmpData = null;
+                }
+                if (pBmpSize != null) {
+                    Module._free(pBmpSize);
+                    pBmpSize = null;
+                }
+                break;
+
+            case "GetJPEG":
+                var nJpegWidth = eventData.width;
+                var nJpegHeight = eventData.height;
+                var pYUVData1 = eventData.data;
+                var nYUVSize1 = nJpegWidth * nJpegHeight * 3 / 2;
+                var oJpegCropRect = {
+                    left: eventData.left,
+                    top: eventData.top,
+                    right: eventData.right,
+                    bottom: eventData.bottom
+                };
+
+                var pDataYUV1 = Module._malloc(nYUVSize1);
+                if (pDataYUV1 === null) {
+                    return;
+                }
+
+                Module.writeArrayToMemory(new Uint8Array(pYUVData1, 0, nYUVSize1), pDataYUV1);
+
+                // 分配JPEG空间
+                var pJpegData = Module._malloc(nYUVSize1);
+                var pJpegSize = Module._malloc(4);
+                if (pJpegData === null || pJpegSize === null) {
+                    if (pJpegData != null) {
+                        Module._free(pJpegData);
+                        pJpegData = null;
+                    }
+
+                    if (pJpegSize != null) {
+                        Module._free(pJpegSize);
+                        pJpegSize = null;
+                    }
+
+                    if (pDataYUV1 != null) {
+                        Module._free(pDataYUV1);
+                        pDataYUV1 = null;
+                    }
+                    return;
+                }
+
+                Module.setValue(pJpegSize, nJpegWidth * nJpegHeight * 2, "i32");    // JPEG抓图,输入缓冲长度不小于当前帧YUV大小
+
+                res = Module._GetJPEG(g_nPort, pDataYUV1, nYUVSize1, pJpegData, pJpegSize,
+                    oJpegCropRect.left, oJpegCropRect.top, oJpegCropRect.right, oJpegCropRect.bottom);
+                if (res !== PLAYM4_OK) {
+                    postMessage({ 'function': "GetJPEG", 'errorCode': res });
+                    if (pJpegData != null) {
+                        Module._free(pJpegData);
+                        pJpegData = null;
+                    }
+
+                    if (pJpegSize != null) {
+                        Module._free(pJpegSize);
+                        pJpegSize = null;
+                    }
+
+                    if (pDataYUV1 != null) {
+                        Module._free(pDataYUV1);
+                        pDataYUV1 = null;
+                    }
+                    return;
+                }
+
+                // 获取JPEG图片大小
+                var nJpegSize = Module.getValue(pJpegSize, "i32");
+
+                // 获取JPEG图片数据
+                var aJpegData = new Uint8Array(nJpegSize);
+                aJpegData.set(Module.HEAPU8.subarray(pJpegData, pJpegData + nJpegSize));
+
+                postMessage({ 'function': "GetJPEG", 'data': aJpegData, 'errorCode': res }, [aJpegData.buffer]);
+
+                nJpegSize = null;
+                aJpegData = null;
+
+                if (pDataYUV1 != null) {
+                    Module._free(pDataYUV1);
+                    pDataYUV1 = null;
+                }
+                if (pJpegData != null) {
+                    Module._free(pJpegData);
+                    pJpegData = null;
+                }
+                if (pJpegSize != null) {
+                    Module._free(pJpegSize);
+                    pJpegSize = null;
+                }
+                break;
+
+            case "SetDecodeFrameType":
+                var nFrameType = eventData.data;
+                res = Module._SetDecodeFrameType(g_nPort, nFrameType);
+                if (res !== PLAYM4_OK) {
+                    postMessage({ 'function': "SetDecodeFrameType", 'errorCode': res });
+                    return;
+                }
+                break;
+            case "CloseStream":
+                //stop
+                let resS = Module._Stop(g_nPort);
+                if (resS !== PLAYM4_OK) {
+                    postMessage({ 'function': "Stop", 'errorCode': res });
+                    return;
+                }
+                //closeStream
+                res = Module._CloseStream(g_nPort);
+                if (res !== PLAYM4_OK) {
+                    postMessage({ 'function': "CloseStream", 'errorCode': res });
+                    return;
+                }
+                //freePort
+                let resF = Module._FreePort(g_nPort);
+                if (resF !== PLAYM4_OK) {
+                    postMessage({ 'function': "FreePort", 'errorCode': res });
+                    return;
+                }
+                if (pInputData != null) {
+                    Module._free(pInputData);
+                    pInputData = null;
+                }
+                break;
+            case "PlaySound":
+                let resPS = Module._PlaySound(g_nPort);
+                if (resPS !== PLAYM4_OK) {
+                    console.log("PlaySound failed");
+                    return;
+                }
+                break;
+            case "StopSound":
+                let resSS = Module._StopSound();
+                if (resSS !== PLAYM4_OK) {
+                    console.log("StopSound failed");
+                    return;
+                }
+                break;
+            case "SetVolume":
+                let resSV = Module._SetVolume(g_nPort, eventData.volume);
+                if (resSV !== PLAYM4_OK) {
+                    console.log("Audio SetVolume failed");
+                    return;
+                }
+                break;
+            case "GetVolume":
+                let volume = Module._GetVolume();
+                if (volume > 0) {
+                    postMessage({ 'function': "GetVolume", 'volume': volume });
+                }
+                else {
+                    console.log("Audio GetVolume failed");
+                    return;
+                }
+                break;
+            case "OnlyPlaySound":
+                let resOPS = Module._OnlyPlaySound(g_nPort);
+                if (resOPS !== PLAYM4_OK) {
+                    console.log("OnlyPlaySound failed");
+                    return;
+                }
+                break;
+            case "Pause":
+                let resPa = Module._Pause(g_nPort, eventData.bPlay);
+                if (resPa !== PLAYM4_OK) {
+                    console.log("Pause failed");
+                    return;
+                }
+            case "PlayRate":
+                Module._SetPlayRate(g_nPort, eventData.playRate);
+                break;
+            case "SetIFrameDecInterval":
+                Module._SetIFrameDecInterval(g_nPort, eventData.data);
+                break;
+            case "SetLostFrameMode":
+                Module._SetLostFrameMode(g_nPort, eventData.data, 0);
+                break;
+            case "SetDemuxModel":
+                let resSDM = Module._SetDemuxModel(g_nPort, eventData.nIdemuxType, eventData.bTrue);
+                break;
+            case "SkipErrorData":
+                Module._SkipErrorData(g_nPort, eventData.bSkip);
+                break;
+            case "SetDecodeERC":
+                Module._SetDecodeERC(g_nPort, eventData.nLevel);
+                break;
+            case "SetANRParam":
+                Module._SetANRParam(g_nPort, eventData.nEnable, eventData.nANRLevel);
+                break;
+            case "SetResampleValue":
+                Module._SetResampleValue(g_nPort, eventData.nEnable, eventData.resampleValue);
+                break;
+            case "GetLastError":
+                let errorCode = Module._GetLastError(g_nPort);
+                postMessage({ 'function': "GetLastError", 'errorCode': errorCode });
+                break;
+            case "SetGlobalBaseTime":
+                Module._SetGlobalBaseTime(g_nPort, eventData.year, eventData.month, eventData.day, eventData.hour, eventData.min, eventData.sec, eventData.ms);
+                break;
+            case "SetRunTimeInfoCB":
+                Module._SetRunTimeInfoCallBackEx(g_nPort, eventData.nModuleType, 0);
+                break;
+			case "SetStreamInfoCB":
+                Module._SetStreamInfoCallBack(g_nPort, eventData.nType, 0);
+                break;
+			case "ResetBuffer":
+                Module._JSPlayM4_ResetBuffer(g_nPort, eventData.type);
+                break;
+            default:
+                break;
+        }
+    };
+
+    function getOSDTime(oFrameInfo) {
+        var iYear = oFrameInfo.year;
+        var iMonth = oFrameInfo.month;
+        var iDay = oFrameInfo.day;
+        var iHour = oFrameInfo.hour;
+        var iMinute = oFrameInfo.minute;
+        var iSecond = oFrameInfo.second;
+        var iMiSecond = oFrameInfo.misecond
+
+        if (iMonth < 10) {
+            iMonth = "0" + iMonth;
+        }
+        if (iDay < 10) {
+            iDay = "0" + iDay;
+        }
+        if (iHour < 10) {
+            iHour = "0" + iHour;
+        }
+        if (iMinute < 10) {
+            iMinute = "0" + iMinute;
+        }
+        if (iSecond < 10) {
+            iSecond = "0" + iSecond;
+        }
+        let osdTime = {};
+
+        osdTime.year = iYear;
+        osdTime.month = iMonth;
+        osdTime.week = 0;
+        osdTime.day = iDay;
+        osdTime.hour = iHour;
+        osdTime.minute = iMinute;
+        osdTime.second = iSecond;
+        osdTime.milliseconds = iMiSecond;
+        return osdTime;
+        //return iYear + "-" + iMonth + "-" + iDay + " " + iHour + ":" + iMinute + ":" + iSecond;
+    }
+    // 获取帧数据
+    function getFrameData() {
+        // function getFrameData() {
+        // 获取帧数据
+        var res = Module._GetFrameData();
+        //var res = fun();
+        if (res === PLAYM4_OK) {
+            var iFrameInfo = Module._GetFrameInfo();
+            let oFrameInfo = {};
+            oFrameInfo.frameType = Module.HEAP32[iFrameInfo >> 2];
+            oFrameInfo.frameSize = Module.HEAP32[iFrameInfo + 4 >> 2];
+            oFrameInfo.width = Module.HEAP32[iFrameInfo + 8 >> 2];
+            oFrameInfo.height = Module.HEAP32[iFrameInfo + 12 >> 2];
+            oFrameInfo.timeStamp = Module.HEAP32[iFrameInfo + 16 >> 2];
+            oFrameInfo.frameRate = Module.HEAP32[iFrameInfo + 20 >> 2];
+            oFrameInfo.bitsPerSample = Module.HEAP32[iFrameInfo + 24 >> 2];
+            oFrameInfo.samplesPerSec = Module.HEAP32[iFrameInfo + 28 >> 2];
+            oFrameInfo.channels = Module.HEAP32[iFrameInfo + 32 >> 2];
+            oFrameInfo.frameNum = Module.HEAP32[iFrameInfo + 36 >> 2];
+
+            oFrameInfo.cropLeft = Module.HEAP32[iFrameInfo + 40 >> 2];
+            oFrameInfo.cropRight = Module.HEAP32[iFrameInfo + 44 >> 2];
+            oFrameInfo.cropTop = Module.HEAP32[iFrameInfo + 48 >> 2];
+            oFrameInfo.cropBottom = Module.HEAP32[iFrameInfo + 52 >> 2];
+
+            oFrameInfo.year = Module.HEAP16[iFrameInfo + 64 >> 1];
+            oFrameInfo.month = Module.HEAP16[iFrameInfo + 66 >> 1];
+            oFrameInfo.day = Module.HEAP16[iFrameInfo + 68 >> 1];
+            oFrameInfo.hour = Module.HEAP16[iFrameInfo + 70 >> 1];
+            oFrameInfo.minute = Module.HEAP16[iFrameInfo + 72 >> 1];
+            oFrameInfo.second = Module.HEAP16[iFrameInfo + 74 >> 1];
+            oFrameInfo.misecond = Module.HEAP16[iFrameInfo + 76 >> 1];
+            switch (oFrameInfo.frameType) {
+                case AUDIO_TYPE:
+                    var iSize = oFrameInfo.frameSize;
+                    if (0 === iSize) {
+                        return -1;
+                    }
+                    var pPCM = Module._GetFrameBuffer();
+                    // var audioBuf = new ArrayBuffer(iSize);
+                    var aPCMData = new Uint8Array(iSize);
+                    aPCMData.set(Module.HEAPU8.subarray(pPCM, pPCM + iSize));
+                    if (bWorkerPrintLog) {
+                        console.log("<<<Worker: audio media Info: nSise:" + oFrameInfo.frameSize + ",nSampleRate:" + oFrameInfo.samplesPerSec + ',channel:' + oFrameInfo.channels + ',bitsPerSample:' + oFrameInfo.bitsPerSample);
+                    }
+                    postMessage({
+                        'function': "GetFrameData", 'type': "audioType", 'data': aPCMData.buffer,
+                        'frameInfo': oFrameInfo, 'errorCode': res
+                    }, [aPCMData.buffer]);
+
+                    oFrameInfo = null;
+                    pPCM = null;
+                    aPCMData = null;
+                    return PLAYM4_AUDIO_FRAME;
+
+                case VIDEO_TYPE:
+                    var szOSDTime = getOSDTime(oFrameInfo);
+
+                    var iWidth = oFrameInfo.width;
+                    var iHeight = oFrameInfo.height;
+
+                    var iYUVSize = iWidth * iHeight * 3 / 2;
+                    if (0 === iYUVSize) {
+                        return -1;
+                    }
+
+                    var pYUV = Module._GetFrameBuffer();
+
+                    // 图像数据渲染后压回,若从主码流切到子码流,存在数组大小与图像大小不匹配现象
+                    var aYUVData = new Uint8Array(iYUVSize);
+                    aYUVData.set(Module.HEAPU8.subarray(pYUV, pYUV + iYUVSize));
+                    if (bWorkerPrintLog) {
+                        console.log("<<<Worker: video media Info: Width:" + oFrameInfo.width + ",Height:" + oFrameInfo.height + ",timeStamp:" + oFrameInfo.timeStamp);
+                    }
+
+                    postMessage({
+                        'function': "GetFrameData", 'type': "videoType", 'data': aYUVData.buffer,
+                        'dataLen': aYUVData.length, 'osd': szOSDTime, 'frameInfo': oFrameInfo, 'errorCode': res
+                    }, [aYUVData.buffer]);
+
+                    oFrameInfo = null;
+                    pYUV = null;
+                    aYUVData = null;
+                    return PLAYM4_VIDEO_FRAME;
+
+                case PRIVT_TYPE:
+                    postMessage({
+                        'function': "GetFrameData", 'type': "", 'data': null,
+                        'dataLen': -1, 'osd': 0, 'frameInfo': null, 'errorCode': PLAYM4_SYS_NOT_SUPPORT
+                    });
+                    return PLAYM4_SYS_NOT_SUPPORT;
+
+                default:
+                    postMessage({
+                        'function': "GetFrameData", 'type': "", 'data': null,
+                        'dataLen': -1, 'osd': 0, 'frameInfo': null, 'errorCode': PLAYM4_SYS_NOT_SUPPORT
+                    });
+                    return PLAYM4_SYS_NOT_SUPPORT;
+            }
+        }
+        else {
+            let errorCode = Module._GetLastError(g_nPort);
+            //解码失败返回裸数据
+            if (PLAYM4_DECODE_ERROR === errorCode) {
+                var rawInfo = Module._GetRawDataInfo();
+                var pRawData = Module._GetRawDataBuffer();
+                var aRawData = new Uint8Array(rawInfo.isize);
+                aRawData.set(Module.HEAPU8.subarray(pRawData, pRawData + rawInfo.isize));
+                postMessage({
+                    'function': "GetRawData", 'type': "", 'data': aRawData.buffer,
+                    'rawDataLen': rawInfo.isize, 'osd': 0, 'frameInfo': null, 'errorCode': errorCode
+                });
+                rawInfo = null;
+                pRawData = null;
+                aRawData = null;
+            }
+            //需要更多数据
+            if (PLAYM4_NEED_MORE_DATA === errorCode || PLAYM4_SYS_NOT_SUPPORT === errorCode || PLAYM4_NEED_NEET_LOOP === errorCode) {
+                postMessage({
+                    'function': "GetFrameData", 'type': "", 'data': null,
+                    'dataLen': -1, 'osd': 0, 'frameInfo': null, 'errorCode': errorCode
+                });
+            }
+            return errorCode;
+        }
+    }
+
+    // 开始计算时间
+    function startTime() {
+        return new Date().getTime();
+    }
+
+    // 结束计算时间
+    function endTime() {
+        return new Date().getTime();
+    }
+
+    // 字母字符串转byte数组
+    function stringToBytes(str) {
+        var ch, st, re = [];
+        for (var i = 0; i < str.length; i++) {
+            ch = str.charCodeAt(i);  // get char
+            st = [];                 // set up "stack"
+            do {
+                st.push(ch & 0xFF);  // push byte to stack
+                ch = ch >> 8;          // shift value down by 1 byte
+            }
+            while (ch);
+            // add stack contents to result
+            // done because chars have "wrong" endianness
+            re = re.concat(st.reverse());
+        }
+        // return an array of bytes
+        return re;
+    }
+})();

BIN
public/h5player/playctrl1simd/Decoder.js


Tiedoston diff-näkymää rajattu, sillä se on liian suuri
+ 0 - 0
public/h5player/playctrl2/Decoder.js


BIN
public/h5player/playctrl2/Decoder.wasm


Tiedoston diff-näkymää rajattu, sillä se on liian suuri
+ 0 - 0
public/h5player/playctrl2/Decoder.worker.js


Tiedoston diff-näkymää rajattu, sillä se on liian suuri
+ 0 - 0
public/h5player/playctrl3/Decoder.js


BIN
public/h5player/playctrl3/Decoder.wasm


Tiedoston diff-näkymää rajattu, sillä se on liian suuri
+ 0 - 0
public/h5player/playctrl3/Decoder.worker.js


Tiedoston diff-näkymää rajattu, sillä se on liian suuri
+ 7 - 0
public/h5player/talk/AudioInterCom.js


BIN
public/h5player/talk/AudioInterCom.wasm


Tiedoston diff-näkymää rajattu, sillä se on liian suuri
+ 8 - 0
public/h5player/talkW/AudioInterCom.js


BIN
public/h5player/talkW/AudioInterCom.wasm


Tiedoston diff-näkymää rajattu, sillä se on liian suuri
+ 0 - 0
public/h5player/talkW/AudioInterCom.worker.js


Tiedoston diff-näkymää rajattu, sillä se on liian suuri
+ 2668 - 0
public/h5player/transform/libSystemTransform.js


BIN
public/h5player/transform/libSystemTransform.wasm


+ 126 - 0
public/h5player/transform/systemTransform-worker.js

@@ -0,0 +1,126 @@
+importScripts('libSystemTransform.js');
+    const RECORDRTP = 0;  //录制一份未经过转封装的码流原始数据,用于定位问题
+    let dataType = 1;
+    
+    // 转封装库回调函数
+    self.STCallBack = function (fileIndex,indexLen, data, dataLen)
+    {
+        //stFrameInfo的类型见DETAIL_FRAME_INFO
+		let stFrameInfo = Module._GetDetialFrameInfo();
+        let nIsMp4Index = stFrameInfo.nIsMp4Index;
+		//console.log("FrameType is " , stFrameInfo);	
+		//console.log("nIsMp4Index is " + nIsMp4Index);
+        //debugger
+        var pData = null;
+        pData = new Uint8Array(dataLen);
+        pData.set(Module.HEAPU8.subarray(data, data + dataLen));
+        if (dataType === 1) {
+            if (pData[0] == 0x49 && pData[1] == 0x4d && pData[2] == 0x4b && pData[3] == 0x48) {//码流头丢掉
+                return;
+            }
+            postMessage({type: "outputData", buf: pData, dType: 1});
+            dataType = 2;
+        } else {
+            if (nIsMp4Index) {
+                postMessage({type: "outputData", buf: pData, dType: 6}); //6:索引类型
+            } else {
+                postMessage({type: "outputData", buf: pData, dType: 2}); //2:码流
+            }
+        }
+
+        //stFrameInfo的类型见DETAIL_FRAME_INFO
+		//let stFrameInfo = Module._GetDetialFrameInfo();
+		//let stFrameType = stFrameInfo.nFrameType;
+		//let nFrameNum = stFrameInfo.nFrameNum;
+		//let nTimeStamp = stFrameInfo.nTimeStamp;
+        //let nIsMp4Index = stFrameInfo.nIsMp4Index;
+		
+		//console.log("FrameType is " + stFrameType);	
+		//console.log("nIsMp4Index is " + nIsMp4Index);	
+        
+    }
+
+    // self.Module = { memoryInitializerRequest: loadMemInitFile(), TOTAL_MEMORY: 128*1024*1024 };
+    // importScripts('SystemTransform.js');
+
+    self.Module['onRuntimeInitialized'] = function (){
+        postMessage({type: "loaded"});
+    }
+    onmessage = function (e) {
+        var data = e.data;
+        if ("create" === data.type) {
+            if (RECORDRTP) {
+                postMessage({type: "created"});
+                postMessage({type: "outputData", buf: data.buf, dType: 1});
+            } else {
+                var iHeadLen = data.len;
+                var pHead = Module._malloc(iHeadLen);
+    
+                self.writeArrayToMemory(new Uint8Array(data.buf), pHead);
+                var iTransType = data.packType;//目标格式
+                var iRet = Module._CreatHandle(pHead, iTransType, 4096);
+                if (iRet != 0) {
+                    console.log("_CreatHandle failed!" + iRet);
+                } else {
+                    iRet = Module._SysTransRegisterDataCallBack();			
+                    if(iRet != 0)
+                    {
+                        console.log("_SysTransRegisterDataCallBack Failed:" + iRet);
+                    }
+                    if (iTransType === 5) {//转mp4支持私有帧打入
+                        iRet = Module._SysTransEnableCapacity(7)
+                        if(iRet != 0)
+                        {
+                            console.log("_SysTransSetGlobalTime Failed:" + iRet);
+                        }
+                    }
+                    iRet = Module._SysTransStart(null, null);
+                    if(iRet != 0)
+                    {
+                        console.log("_SysTransStart Failed:" + iRet);
+                    }
+                    postMessage({type: "created"});
+                }
+            }
+
+        } else if ("inputData" === data.type) {
+            if (RECORDRTP) {
+                var aFileData = new Uint8Array(data.buf);  // 拷贝一份
+                var iBufferLen = aFileData.length;
+                var szBufferLen = iBufferLen.toString(16);
+                if (szBufferLen.length === 1) {
+                    szBufferLen = "000" + szBufferLen;
+                } else if (szBufferLen.length === 2) {
+                    szBufferLen = "00" + szBufferLen;
+                } else if (szBufferLen.length === 3) {
+                    szBufferLen = "0" + szBufferLen;
+                }
+                var aData = [0, 0, parseInt(szBufferLen.substring(0, 2), 16), parseInt(szBufferLen.substring(2, 4), 16)];
+                for(var iIndex = 0, iDataLength = aFileData.length; iIndex < iDataLength; iIndex++) {
+                    aData[iIndex + 4] = aFileData[iIndex]
+                }
+                var dataUint8 = new Uint8Array(aData);
+                postMessage({type: "outputData", buf: dataUint8.buffer, dType: 2});
+            } else {
+                let pInputDataBuf = Module._malloc(data.len);
+                var idataLen = data.len;
+                self.writeArrayToMemory(new Uint8Array(data.buf), pInputDataBuf);
+                    // 输入数据,每次最多2m
+                let pp = Module._SysTransInputData(0, pInputDataBuf, idataLen);
+                if(pp != 0) {
+                    //console.log("InputData Failed:" + pp);
+                }
+                Module._free(pInputDataBuf);
+            }
+        } else if ("release" === data.type) {
+            var iRet = Module._SysTransStop();
+            if (iRet != 0) {
+                console.log("_SysTransStop Failed:", iRet);
+            }
+            Module._SysTransRelease();
+            if (iRet != 0) {
+                console.log("_SysTransRelease Failed:", iRet);
+            }
+            close();
+        }
+    };

+ 48 - 0
src/App.vue

@@ -28,6 +28,21 @@ const materials_files = import.meta.glob('./assets/images/materials/**', {
   query: '?url',
   query: '?url',
   import: 'default'
   import: 'default'
 })
 })
+const danger_files = import.meta.glob('./assets/images/danger/**', {
+  eager: true,
+  query: '?url',
+  import: 'default'
+})
+const icon_modules_files = {
+  ...import.meta.glob('./assets/images/icons/**.svg', {
+    eager: true,
+    as: 'raw'
+  })
+}
+
+const normalizeFillSvg = (svg: string) => {
+  return svg.replace(/\sfill="(?!none\b)[^"]*"/gi, '')
+}
 
 
 const electrical_register_config: any = []
 const electrical_register_config: any = []
 for (const key in electrical_modules_files) {
 for (const key in electrical_modules_files) {
@@ -69,6 +84,27 @@ for (const key in electrical_stroke_modules_files) {
 }
 }
 leftAsideStore.registerConfig('电气符号', electrical_register_config)
 leftAsideStore.registerConfig('电气符号', electrical_register_config)
 
 
+const icon_register_config: any = []
+for (const key in icon_modules_files) {
+  const name = key.split('/').pop()!.split('.')[0]
+  const svg = normalizeFillSvg(icon_modules_files[key])
+  icon_register_config.push({
+    id: `icon-${name}`,
+    title: name,
+    type: 'svg',
+    thumbnail: 'data:image/svg+xml;utf8,' + encodeURIComponent(svg),
+    svg,
+    props: {
+      fill: {
+        type: 'color',
+        val: '#FF0000',
+        title: '填充色'
+      }
+    }
+  })
+}
+leftAsideStore.registerConfig('图标', icon_register_config)
+
 const process_demo_register_config: any[] = Object.entries(process_demo_image_files).map(
 const process_demo_register_config: any[] = Object.entries(process_demo_image_files).map(
   ([key, url]) => {
   ([key, url]) => {
     const name = key.split('/').pop()!.split('.')[0]
     const name = key.split('/').pop()!.split('.')[0]
@@ -95,6 +131,18 @@ const materials_register_config: any[] = Object.entries(materials_files).map(([k
 })
 })
 leftAsideStore.registerConfig('素材', materials_register_config)
 leftAsideStore.registerConfig('素材', materials_register_config)
 
 
+const danger_register_config: any[] = Object.entries(danger_files).map(([key, url]) => {
+  const name = key.split('/').pop()!.split('.')[0]
+  return {
+    id: `danger-${name}`,
+    title: name,
+    type: 'img',
+    thumbnail: url,
+    props: {}
+  }
+})
+leftAsideStore.registerConfig('危险标识', danger_register_config)
+
 const route = useRoute()
 const route = useRoute()
 const { addListeners, removeListeners } = useAutoLogout()
 const { addListeners, removeListeners } = useAutoLogout()
 
 

+ 49 - 0
src/api/digital/index.ts

@@ -0,0 +1,49 @@
+import request from '@/config/axios'
+
+export interface DigitalWell {
+  id: number
+  well?: string
+  uwi?: string
+  comp?: string
+  fld?: string
+  cnty?: string
+  stat?: string
+  start?: number
+  stop?: number
+  step?: number
+  depth?: number | null
+  elev?: number
+  lat?: number
+  lon?: number
+  [key: string]: unknown
+}
+
+export interface WellListResult {
+  list: DigitalWell[]
+  total: number
+}
+
+export interface WellLogDataset {
+  wellInfo?: DigitalWell
+  depths: number[]
+  sws?: number[]
+  dts?: number[]
+  porns?: number[]
+  rhobs?: number[]
+  rxos?: number[]
+  r25s?: number[]
+  cilds?: number[]
+  calis?: number[]
+  sps?: number[]
+  grs?: number[]
+  [key: string]: unknown
+}
+
+export const DigitalApi = {
+  getWellLogDataset: (wellId: number): Promise<WellLogDataset> => {
+    return request.get({ url: '/pms/las-well-data/wellLogDataset', params: { wellId } })
+  },
+  getWellList: (): Promise<WellListResult> => {
+    return request.get({ url: '/pms/las-well/page', params: { pageNo: 1, pageSize: 100 } })
+  }
+}

+ 51 - 0
src/api/iot/thingmodel/index.ts

@@ -197,6 +197,12 @@ export const ThingModelApi = {
   // 删除产品物模型
   // 删除产品物模型
   deleteThingModel: async (id: number) => {
   deleteThingModel: async (id: number) => {
     return await request.delete({ url: `/iot/thing-model/delete?id=` + id })
     return await request.delete({ url: `/iot/thing-model/delete?id=` + id })
+  },
+  // 导入通用物模型
+  importCommonThingModel: async (data) => {
+    return await request.get({
+      url: `/iot/thing-model/import-common?commonId=${data.commonId}&productId=${data.productId}`
+    })
   }
   }
 }
 }
 
 
@@ -299,3 +305,48 @@ export const validateBoolName = (_: any, value: string, callback: any) => {
 
 
   callback()
   callback()
 }
 }
+
+// 通用物模型
+export const ThingModelCommon = {
+  // 查询产品物模型分页
+  getCommonThingModelPage: async (params: any) => {
+    return await request.get({ url: `/iot/thing-model-common/page`, params })
+  },
+
+  // 创建通用物模型
+  createCommonThingModel: async (data) => {
+    return await request.post({ url: `/iot/thing-model-common/create`, data })
+  },
+
+  // 删除通用物模型
+  deleteCommonThingModel: async (id: number) => {
+    return await request.delete({ url: `/iot/thing-model-common/delete?id=` + id })
+  },
+
+  // 编辑通用物模型
+  updateCommonThingModel: async (data) => {
+    return await request.put({ url: `/iot/thing-model-common/update`, data })
+  },
+
+  // 查询通用物模型详情
+  getCommonThingModel: async (id: number) => {
+    return await request.get({ url: `/iot/thing-model-common/get?id=` + id })
+  },
+
+  // 导出
+  exportCommonThingModel: async (params: any) => {
+    return await request.get({ url: `/iot/thing-model-common/export`, params })
+  }
+}
+
+/** 通用物模型 */
+export interface CommonThingModelData {
+  id?: number
+  identifier?: string
+  name?: string
+  description?: string
+  type?: number
+  property?: string
+  event?: string
+  service?: string
+}

+ 25 - 7
src/api/pms/failure/index.ts

@@ -4,7 +4,8 @@ import request from '@/config/axios'
 export interface IotFailureReportVO {
 export interface IotFailureReportVO {
   id: number // 主键id
   id: number // 主键id
   failureCode: string // 故障编码
   failureCode: string // 故障编码
-  failureName: string // 故障名称
+  failureName: string
+  typeDesc?: string // 其他故障描述
   deviceId: number // 设备id
   deviceId: number // 设备id
   status: string // 状态
   status: string // 状态
   ifStop: boolean // 是否停机
   ifStop: boolean // 是否停机
@@ -21,10 +22,9 @@ export interface IotFailureReportVO {
 
 
 // 故障上报 API
 // 故障上报 API
 export const IotFailureReportApi = {
 export const IotFailureReportApi = {
-
   // 提交审核
   // 提交审核
   submitForApproval: async (id: number) => {
   submitForApproval: async (id: number) => {
-    return await request.put({ url: `/rq/iot-failure-report/submitForApproval?id=${id}`})
+    return await request.put({ url: `/rq/iot-failure-report/submitForApproval?id=${id}` })
   },
   },
   // 查询故障上报分页
   // 查询故障上报分页
   getIotFailureReportPage: async (params: any) => {
   getIotFailureReportPage: async (params: any) => {
@@ -37,7 +37,7 @@ export const IotFailureReportApi = {
   },
   },
 
 
   getApproval: async () => {
   getApproval: async () => {
-    return await request.get({ url: `/rq/iot-failure-report/get/approval`})
+    return await request.get({ url: `/rq/iot-failure-report/get/approval` })
   },
   },
 
 
   // 新增故障上报
   // 新增故障上报
@@ -59,7 +59,25 @@ export const IotFailureReportApi = {
   exportIotFailureReport: async (params) => {
   exportIotFailureReport: async (params) => {
     return await request.download({ url: `/rq/iot-failure-report/export-excel`, params })
     return await request.download({ url: `/rq/iot-failure-report/export-excel`, params })
   },
   },
-  updateIotMaintainProcess: async (processId:any,id:any, type:any,assigneeUserId:any, trans:any) => {
-    return await request.put({ url: `/rq/iot-failure-report/process-info?processId=`+processId+`&id=`+id+`&type=`+type+`&assigneeUserId=`+assigneeUserId+`&trans=`+ trans})
-  },
+  updateIotMaintainProcess: async (
+    processId: any,
+    id: any,
+    type: any,
+    assigneeUserId: any,
+    trans: any
+  ) => {
+    return await request.put({
+      url:
+        `/rq/iot-failure-report/process-info?processId=` +
+        processId +
+        `&id=` +
+        id +
+        `&type=` +
+        type +
+        `&assigneeUserId=` +
+        assigneeUserId +
+        `&trans=` +
+        trans
+    })
+  }
 }
 }

+ 26 - 0
src/api/pms/fiveconstructiondailyreport/index.ts

@@ -0,0 +1,26 @@
+import request from '@/config/axios'
+
+export const FiveIotProjectDailyReportApi = {
+  // 获取项目日报分页
+  getIotProjectDailyReportPage: async (params) => {
+    return await request.get({ url: '/pms/iot-five-daily-report/page', params })
+  },
+  // 查看项目日报详情
+  getIotProjectDailyReport: async (id) => {
+    return await request.get({ url: '/pms/iot-five-daily-report/get?id=' + id })
+  },
+  // 填报项目日报
+  updateIotProjectDailyReport: async (data) => {
+    return await request.put({ url: '/pms/iot-five-daily-report/update', data })
+  },
+
+  // 日报汇总统计卡片
+  getIotProjectDailyReportSummary: async (params) => {
+    return await request.get({ url: '/pms/iot-five-daily-report/totalWorkload', params })
+  },
+
+  // 获取项目日报分页
+  getProjectDailyReportSummaryPage: async (params) => {
+    return await request.get({ url: '/pms/iot-five-daily-report/summaryStatistics', params })
+  }
+}

+ 104 - 0
src/api/pms/hikvision/index.ts

@@ -0,0 +1,104 @@
+import request from '@/config/axios'
+
+export interface HikvisionRegionNode {
+  indexCode: string
+  name: string
+  nodeType: 'region'
+  parentIndexCode?: string
+  available?: boolean
+  leaf: boolean
+  children: HikvisionTreeNode[]
+}
+
+export interface HikvisionCameraNode {
+  indexCode: string
+  name: string
+  nodeType: 'camera'
+  parentIndexCode?: string
+  available?: boolean
+  onlineStatus?: boolean
+}
+
+export type HikvisionTreeNode = HikvisionRegionNode | HikvisionCameraNode
+export type HikvisionStreamType = 0 | 1
+/**
+ * 海康云台方向命令。
+ *
+ * 接口要求 command 使用大写字符串,并通过下划线连接组合方向;这里使用联合类型
+ * 约束页面只能提交后端支持的八个方向,避免大小写或拼写错误直到运行时才被发现。
+ */
+export type HikvisionPtzCommand =
+  | 'LEFT' // 向左
+  | 'RIGHT' // 向右
+  | 'UP' // 向上
+  | 'DOWN' // 向下
+  | 'LEFT_UP' // 左上
+  | 'LEFT_DOWN' // 左下
+  | 'RIGHT_UP' // 右上
+  | 'RIGHT_DOWN' // 右下
+
+/** `/vms/ptz-control` 云台控制接口的请求体。 */
+export interface HikvisionPtzControlRequest {
+  /** 监控点唯一标识,对应当前播放窗口绑定摄像机的 indexCode。 */
+  cameraIndexCode: string
+  /** 操作类型:0 表示开始持续转动,1 表示停止当前方向转动。 */
+  action: 0 | 1
+  /** 本次需要执行或停止的云台命令。 */
+  command: HikvisionPtzCommand
+  /** 云台速度,允许范围为 1~100;不传时由海康平台使用默认值。 */
+  speed?: number
+}
+
+export const HikvisionApi = {
+  /** 按监控点名称搜索,返回匹配监控点及完整祖先目录,children 无需再懒加载。 */
+  searchTree: (keyword: string): Promise<HikvisionTreeNode[]> => {
+    return request.get({
+      url: '/vms/tree/search',
+      params: { keyword }
+    })
+  },
+
+  getTreeNodes: (regionIndexCode: string): Promise<HikvisionTreeNode[]> => {
+    return request.get({
+      url: '/vms/tree',
+      params: { regionIndexCode }
+    })
+  },
+
+  /**
+   * 获取监控点的 H5player 实时预览地址。
+   *
+   * streamType:0 为主码流,1 为子码流;CommonResult.data 返回可供 JS_Play 使用的 URL。
+   */
+  getPreviewUrl: (
+    cameraIndexCode: string,
+    streamType: HikvisionStreamType = 0
+  ): Promise<string> => {
+    return request.get({
+      url: '/vms/preview-url',
+      params: { cameraIndexCode, streamType }
+    })
+  },
+
+  /** 获取监控点的 H5player WSS 对讲地址。 */
+  getTalkUrl: (cameraIndexCode: string): Promise<string> => {
+    return request.get({
+      url: '/vms/talk-url',
+      params: { cameraIndexCode }
+    })
+  },
+
+  /**
+   * 开始或停止监控点的云台方向控制。
+   *
+   * Axios 实例的 baseURL 已包含 `/admin-api`,因此这里保留业务路径即可,浏览器最终
+   * 请求地址为 `/admin-api/vms/ptz-control`。方向控制为持续动作,调用方必须保证
+   * `action: 0` 与 `action: 1` 成对发送。
+   */
+  controlPtz: (data: HikvisionPtzControlRequest): Promise<void> => {
+    return request.post({
+      url: '/vms/ptz-control',
+      data
+    })
+  }
+}

+ 32 - 33
src/api/pms/iotmaintenancebom/index.ts

@@ -8,71 +8,71 @@ export interface IotMaintenanceBomVO {
   deviceCategoryId: number // 所属设备分类
   deviceCategoryId: number // 所属设备分类
   deviceId: number // 设备id
   deviceId: number // 设备id
   rule: string // 保养规则(里程 运行时间 自然日期) 可多选
   rule: string // 保养规则(里程 运行时间 自然日期) 可多选
-  mileageRule: number       // 保养规则-里程(0启用 1停用)
-  naturalDateRule: number   // 保养规则-自然日期(0启用 1停用)
-  runningTimeRule: number   // 保养规则-运行时间(0启用 1停用)
+  mileageRule: number // 保养规则-里程(0启用 1停用)
+  naturalDateRule: number // 保养规则-自然日期(0启用 1停用)
+  runningTimeRule: number // 保养规则-运行时间(0启用 1停用)
   lastRunningTime: number // 上次保养运行时长(小时)
   lastRunningTime: number // 上次保养运行时长(小时)
   nextRunningTime: number // 下次保养运行时长(小时) 运行时长周期
   nextRunningTime: number // 下次保养运行时长(小时) 运行时长周期
   timePeriod: number // 时间周期(小时) 距离下次保养运行时长
   timePeriod: number // 时间周期(小时) 距离下次保养运行时长
   kilometerCycle: number // 公里数周期(千米) 距离下次保养公里数
   kilometerCycle: number // 公里数周期(千米) 距离下次保养公里数
   naturalDatePeriod: number // 自然日周期(天) 下次保养自然日期
   naturalDatePeriod: number // 自然日周期(天) 下次保养自然日期
-  timePeriodLead: number  // 运行时长周期提前量 H
+  timePeriodLead: number // 运行时长周期提前量 H
   lastRunningKilometers: number // 上次保养运行公里数(千米)
   lastRunningKilometers: number // 上次保养运行公里数(千米)
   nextRunningKilometers: number // 下次保养运行公里数(千米)
   nextRunningKilometers: number // 下次保养运行公里数(千米)
   kiloCycleLead: number // 公里数周期-提前量 km
   kiloCycleLead: number // 公里数周期-提前量 km
   lastNaturalDate: Date // 上次保养自然日期(天)
   lastNaturalDate: Date // 上次保养自然日期(天)
-  tempLastNaturalDate: Date // 上次保养自然日期(天) 临时变量
-  nextNaturalDate: number // 下次保养自然日期(天)
+  tempLastNaturalDate: string | null // 上次保养自然日期(天) 临时变量
+  nextNaturalDate: number | null // 下次保养自然日期(天)
   naturalDatePeriodLead: number // 自然日周期-提前量(天)
   naturalDatePeriodLead: number // 自然日周期-提前量(天)
   bomNodeId: number // bom节点id
   bomNodeId: number // bom节点id
   name: string // BOM名称
   name: string // BOM名称
-  code: string // BOM编码
+  code: string | null // BOM编码
   parentId: number // 父BOM id 顶级为0
   parentId: number // 父BOM id 顶级为0
   childIds: string // 子节点id 逗号分隔
   childIds: string // 子节点id 逗号分隔
   level: number // 层级
   level: number // 层级
   leafFlag: number // 是否叶子节点 0是 1否
   leafFlag: number // 是否叶子节点 0是 1否
   sort: number // 显示顺序
   sort: number // 显示顺序
-  type: string // 1维修 2保养 维修+保养
+  type: string | null // 1维修 2保养 维修+保养
   status: number // 状态 0启用  1停用
   status: number // 状态 0启用  1停用
   remark: string // 备注
   remark: string // 备注
   version: number // 版本
   version: number // 版本
   // 扩展字段
   // 扩展字段
-  deviceName: string  // 设备名称
-  deviceCode: string  // 设备编码
-  deviceStatus: string  // 设备状态
+  deviceName: string // 设备名称
+  deviceCode: string // 设备编码
+  deviceStatus: string // 设备状态
   assetProperty: string //资产性质
   assetProperty: string //资产性质
-  totalMileage: number  // 累计运行公里数
-  totalRunTime: number  // 累计运行时间
-  tempTotalMileage: number  // 临时 累计运行公里数
-  tempTotalRunTime: number  // 临时 累计运行时间
-  isRuntimeFromTemp: false
-  isMileageFromTemp: false
+  totalMileage: number | null // 累计运行公里数
+  totalRunTime: number | null // 累计运行时间
+  tempTotalMileage: number | null // 临时 累计运行公里数
+  tempTotalRunTime: number | null // 临时 累计运行时间
+  isRuntimeFromTemp: boolean
+  isMileageFromTemp: boolean
   // 运行记录模板中 包含多个 累计时长 属性列表
   // 运行记录模板中 包含多个 累计时长 属性列表
   timeAccumulatedAttrs: Array<{
   timeAccumulatedAttrs: Array<{
-    pointName: string;
-    totalRunTime: number;
-    totalMileage: number;
-  }>;
+    pointName: string
+    totalRunTime: number
+    totalMileage: number
+  }>
   // 运行记录模板中 包含多个 累计公里数 属性列表
   // 运行记录模板中 包含多个 累计公里数 属性列表
   mileageAccumulatedAttrs: Array<{
   mileageAccumulatedAttrs: Array<{
-    pointName: string;
-    totalRunTime: number;
-    totalMileage: number;
-  }>;
+    pointName: string
+    totalRunTime: number
+    totalMileage: number
+  }>
   // 上次保养时间 不同于自然日保养规则下的 上次保养自然日期
   // 上次保养时间 不同于自然日保养规则下的 上次保养自然日期
   lastMaintenanceDate: Date
   lastMaintenanceDate: Date
   // 下次保养公里数
   // 下次保养公里数
-  nextMaintenanceKm: number
+  nextMaintenanceKm: number | null
   // 剩余保养公里数
   // 剩余保养公里数
-  remainKm: number
+  remainKm: number | null
   // 下次保养运行时长
   // 下次保养运行时长
-  nextMaintenanceH: number
+  nextMaintenanceH: number | null
   // 剩余保养运行时长
   // 剩余保养运行时长
-  remainH: number
+  remainH: number | null
   // 下次保养日期
   // 下次保养日期
-  nextMaintenanceDate: Date
+  nextMaintenanceDate: string | null
   // 自然日期保养 剩余天数
   // 自然日期保养 剩余天数
-  remainDay: number
+  remainDay: number | null
 }
 }
 
 
 // PMS 保养计划明细BOM API
 // PMS 保养计划明细BOM API
@@ -110,6 +110,5 @@ export const IotMaintenanceBomApi = {
   // 获得PMS 保养工单明细BOM列表
   // 获得PMS 保养工单明细BOM列表
   getMainPlanBOMs: async (params: any) => {
   getMainPlanBOMs: async (params: any) => {
     return await request.get({ url: `/pms/iot-maintenance-bom/getMainPlanBOMs`, params })
     return await request.get({ url: `/pms/iot-maintenance-bom/getMainPlanBOMs`, params })
-  },
-
+  }
 }
 }

+ 6 - 0
src/api/pms/iotmainworkorder/index.ts

@@ -24,6 +24,7 @@ export interface IotMainWorkOrderVO {
   status: number // 状态 0启用  1停用
   status: number // 状态 0启用  1停用
   processInstanceId: string // 流程实例id
   processInstanceId: string // 流程实例id
   auditStatus: number // 审批状态 未提交、审批中、审批通过、审批不通过、已取消
   auditStatus: number // 审批状态 未提交、审批中、审批通过、审批不通过、已取消
+  applicant: string // 申请人id
 }
 }
 
 
 // 保养工单 API
 // 保养工单 API
@@ -83,6 +84,11 @@ export const IotMainWorkOrderApi = {
     return await request.put({ url: `/pms/iot-main-work-order/fillWorkOrder`, data })
     return await request.put({ url: `/pms/iot-main-work-order/fillWorkOrder`, data })
   },
   },
 
 
+  // 保存保养工单附件
+  hiWorkOrderAttachment: async (data: Pick<IotMainWorkOrderVO, 'id'> & { attachments: any[] }) => {
+    return await request.put({ url: `/pms/iot-main-work-order/hiWorkOrderAttachment`, data })
+  },
+
   // 修改保养工单
   // 修改保养工单
   modifyWorkOrder: async (data: any) => {
   modifyWorkOrder: async (data: any) => {
     return await request.put({ url: `/pms/iot-main-work-order/modifyWorkOrder`, data })
     return await request.put({ url: `/pms/iot-main-work-order/modifyWorkOrder`, data })

+ 16 - 0
src/api/pms/iotprojecttask/index.ts

@@ -21,6 +21,16 @@ export interface GenerateReportParams {
   reportDate: number
   reportDate: number
 }
 }
 
 
+export interface TaskWellNameVO {
+  id: number
+  wellName: string
+}
+
+export interface TaskWellNameParams {
+  deptId: number
+  wellName: string
+}
+
 // 项目信息任务拆分 API
 // 项目信息任务拆分 API
 export const IotProjectTaskApi = {
 export const IotProjectTaskApi = {
   // 查询项目信息任务拆分分页
   // 查询项目信息任务拆分分页
@@ -30,6 +40,12 @@ export const IotProjectTaskApi = {
   getIotProjectTaskList: async (params: any) => {
   getIotProjectTaskList: async (params: any) => {
     return await request.get({ url: `/rq/iot-project-task/list`, params })
     return await request.get({ url: `/rq/iot-project-task/list`, params })
   },
   },
+  getTaskWellNames: async (params: TaskWellNameParams) => {
+    return await request.get<TaskWellNameVO[]>({
+      url: `/rq/iot-project-task/taskWellNames`,
+      params
+    })
+  },
   // 查询项目信息任务拆分详情
   // 查询项目信息任务拆分详情
   getIotProjectTask: async (id: number) => {
   getIotProjectTask: async (id: number) => {
     return await request.get({ url: `/rq/iot-project-task/get?id=` + id })
     return await request.get({ url: `/rq/iot-project-task/get?id=` + id })

+ 113 - 0
src/api/pms/iotprojecttaskplan/index.ts

@@ -0,0 +1,113 @@
+import request from '@/config/axios'
+
+export interface IotProjectTaskPlanSummaryParams {
+  pageNo: number
+  pageSize: number
+  deptId?: number
+  startTime?: string[]
+}
+
+export type IotProjectTaskPlanPageParams = IotProjectTaskPlanSummaryParams
+
+export interface IotProjectTaskPlanPageVO {
+  id: number
+  deptName: string
+  business: string
+  yearMonth: string
+  workloadUnit: string
+  planGasInjection?: number | string | null
+  planLayers?: number | string | null
+  planWellTrips?: number | string | null
+  planFootage?: number | string | null
+}
+
+export interface IotProjectTaskPlanPageResult {
+  list: IotProjectTaskPlanPageVO[]
+  total: number
+}
+
+export interface IotProjectTaskPlanTeamProjectBiz {
+  business: string
+  businessName: string
+  unit: string
+}
+
+export interface IotProjectTaskPlanCreateData {
+  deptId: number
+  startTime: number
+  business: string
+  workloadUnit: string
+  planGasInjection?: number
+  planLayers?: number
+  planWellTrips?: number
+  planFootage?: number
+}
+
+export interface IotProjectTaskPlanUpdateData extends IotProjectTaskPlanCreateData {
+  id: number
+}
+
+export interface IotProjectTaskPlanDetailVO extends IotProjectTaskPlanUpdateData {}
+
+export interface IotProjectTaskPlanSummaryItem {
+  yearMonth: string
+  cumulativeGasInjection?: number | string | null
+  cumulativeFootage?: number | string | null
+  cumulativeWellCount?: number | string | null
+  cumulativeFracLayer?: number | string | null
+}
+
+export interface IotProjectTaskPlanSummaryVO {
+  business: string
+  projectDeptName: string
+  teamName: string
+  companyName: string
+  items: IotProjectTaskPlanSummaryItem[]
+  cumulativeGasInjection?: number | string | null
+  cumulativeFootage?: number | string | null
+  cumulativeWellCount?: number | string | null
+  cumulativeFracLayer?: number | string | null
+}
+
+export interface IotProjectTaskPlanSummaryResult {
+  list: IotProjectTaskPlanSummaryVO[]
+  total: number
+}
+
+export const IotProjectTaskPlanApi = {
+  create: async (data: IotProjectTaskPlanCreateData) => {
+    return await request.post<number>({
+      url: '/pms/iot-project-task-plan/create',
+      data
+    })
+  },
+  update: async (data: IotProjectTaskPlanUpdateData) => {
+    return await request.put<boolean>({
+      url: '/pms/iot-project-task-plan/update',
+      data
+    })
+  },
+  get: async (id: number) => {
+    return await request.get<IotProjectTaskPlanDetailVO>({
+      url: `/pms/iot-project-task-plan/get?id=${id}`
+    })
+  },
+  getTeamProjectBiz: async (deptId: number) => {
+    return await request.get<IotProjectTaskPlanTeamProjectBiz[]>({
+      url: '/pms/iot-project-task-plan/teamProjectBiz',
+      params: { deptId }
+    })
+  },
+  getPage: async (params: IotProjectTaskPlanPageParams) => {
+    return await request.get<IotProjectTaskPlanPageResult>({
+      url: '/pms/iot-project-task-plan/page',
+      params
+    })
+  },
+  getSummaryStatistics: async (params: IotProjectTaskPlanSummaryParams) => {
+    return await request.get<IotProjectTaskPlanSummaryResult>({
+      url: '/pms/iot-project-task-plan/summaryStatistics',
+      params
+    })
+  }
+}

+ 7 - 0
src/api/pms/iotprojecttaskschedule/index.ts

@@ -53,4 +53,11 @@ export const IotProjectTaskScheduleApi = {
   exportIotProjectTaskSchedule: async (params) => {
   exportIotProjectTaskSchedule: async (params) => {
     return await request.download({ url: `/rq/iot-project-task-schedule/export-excel`, params })
     return await request.download({ url: `/rq/iot-project-task-schedule/export-excel`, params })
   },
   },
+
+  // 查询每个任务相关公司的 工作量字段
+  getWorkload: async (id: number | string) => {
+    return await request.get({
+      url: `/rq/iot-project-task-schedule/scheduleExtProperties?taskId=${id}`
+    })
+  }
 }
 }

+ 11 - 0
src/api/pms/iotrddailyreport/index.ts

@@ -47,6 +47,17 @@ export interface IotRdDailyReportVO {
 
 
 // 瑞都日报 API
 // 瑞都日报 API
 export const IotRdDailyReportApi = {
 export const IotRdDailyReportApi = {
+  nptStatistics: async (params: any) => {
+    return await request.get({ url: `/pms/iot-rd-daily-report/nptStatistics`, params })
+  },
+
+  exportNptStatistics: async (params: any) => {
+    return await request.download({
+      url: `/pms/iot-rd-daily-report/exportNptStatistics`,
+      params
+    })
+  },
+
   // 查询瑞都日报分页
   // 查询瑞都日报分页
   getIotRdDailyReportPage: async (params: any) => {
   getIotRdDailyReportPage: async (params: any) => {
     return await request.get({ url: `/pms/iot-rd-daily-report/page`, params })
     return await request.get({ url: `/pms/iot-rd-daily-report/page`, params })

+ 1 - 1
src/api/pms/iotrhdailyreport/index.ts

@@ -70,7 +70,7 @@ export const IotRhDailyReportApi = {
   },
   },
 
 
   nptStatistics: async (params: any) => {
   nptStatistics: async (params: any) => {
-    return await request.get({ url: `/pms/iot-rd-daily-report/nptStatistics`, params })
+    return await request.get({ url: `/pms/iot-rh-daily-report/nptStatistics`, params })
   },
   },
 
 
   // 累计工作量统计
   // 累计工作量统计

+ 11 - 0
src/api/pms/iotrydailyreport/index.ts

@@ -86,6 +86,17 @@ export const IotRyDailyReportApi = {
     return await request.get({ url: `/pms/iot-ry-daily-report/statistics`, params })
     return await request.get({ url: `/pms/iot-ry-daily-report/statistics`, params })
   },
   },
 
 
+  nptStatistics: async (params: any) => {
+    return await request.get({ url: `/pms/iot-ry-daily-report/nptStatistics`, params })
+  },
+
+  exportNptStatistics: async (params: any) => {
+    return await request.download({
+      url: `/pms/iot-ry-daily-report/exportNptStatistics`,
+      params
+    })
+  },
+
   // 累计工作量统计
   // 累计工作量统计
   totalWorkload: async (params: any) => {
   totalWorkload: async (params: any) => {
     return await request.get({ url: `/pms/iot-ry-daily-report/totalWorkload`, params })
     return await request.get({ url: `/pms/iot-ry-daily-report/totalWorkload`, params })

+ 48 - 0
src/api/pms/iotryimprovedailyreport/index.ts

@@ -0,0 +1,48 @@
+import request from '@/config/axios'
+
+export interface IotRyImproveDailyReportVO {
+  id?: number
+  title: string
+  createTime: number
+  workLocation: string
+  workPurpose: string
+  relocationDays: number
+  personnel: string
+  improveReportDetails: IotRyImproveDailyReportDetailVO[]
+  auditStatus?: 0 | 10 | 20 | 30 | 40
+  opinion?: string
+}
+
+export interface IotRyImproveDailyReportDetailVO {
+  projectName: string
+  constructionDetail: string
+  nextPlan: string
+}
+
+export interface IotRyImproveDailyReportApprovalVO {
+  id: number
+  auditStatus: 20 | 30
+  opinion?: string
+}
+
+export const IotRyImproveDailyReportApi = {
+  getIotRyImproveDailyReportPage: async (params: any) => {
+    return await request.get({ url: `/pms/iot-ry-improve-daily-report/page`, params })
+  },
+
+  getIotRyImproveDailyReport: async (id: number) => {
+    return await request.get({ url: `/pms/iot-ry-improve-daily-report/get?id=` + id })
+  },
+
+  createIotRyImproveDailyReport: async (data: IotRyImproveDailyReportVO) => {
+    return await request.post({ url: `/pms/iot-ry-improve-daily-report/create`, data })
+  },
+
+  updateIotRyImproveDailyReport: async (data: IotRyImproveDailyReportVO) => {
+    return await request.put({ url: `/pms/iot-ry-improve-daily-report/update`, data })
+  },
+
+  approvalIotRyImproveDailyReport: async (data: IotRyImproveDailyReportApprovalVO) => {
+    return await request.put({ url: `/pms/iot-ry-improve-daily-report/approval`, data })
+  }
+}

+ 16 - 0
src/api/pms/iotsapstock/index.ts

@@ -14,6 +14,7 @@ export interface IotSapStockVO {
   materialGroupId: number // 物料组id
   materialGroupId: number // 物料组id
   quantity: number // 数量
   quantity: number // 数量
   unitPrice: number // 单价
   unitPrice: number // 单价
+  currency: string // 币种
   unit: string // 单位
   unit: string // 单位
   safetyStock: number // 安全库存
   safetyStock: number // 安全库存
   storageAreaId: number // 库区id
   storageAreaId: number // 库区id
@@ -29,8 +30,23 @@ export interface IotSapStockVO {
   remark: string // 备注
   remark: string // 备注
 }
 }
 
 
+// SAP 库存汇总 VO
+export interface SapStockSummaryVO {
+  currency: string // 币种
+  totalAmount: number // 总金额
+  totalQuantity: number // 总数量
+}
+
 // PMS SAP 库存(通用库存/项目部库存) API
 // PMS SAP 库存(通用库存/项目部库存) API
 export const IotSapStockApi = {
 export const IotSapStockApi = {
+  // 查询 SAP 库存汇总
+  getSapStockSummary: async (params: PageParam) => {
+    return await request.get<SapStockSummaryVO[]>({
+      url: `/pms/iot-sap-stock/sapStockSummary`,
+      params
+    })
+  },
+
   // 查询PMS SAP 库存(通用库存/项目部库存)分页
   // 查询PMS SAP 库存(通用库存/项目部库存)分页
   getIotSapStockPage: async (params: PageParam) => {
   getIotSapStockPage: async (params: PageParam) => {
     return await request.get({ url: `/pms/iot-sap-stock/page`, params })
     return await request.get({ url: `/pms/iot-sap-stock/page`, params })

+ 12 - 0
src/api/pms/maotu/index.ts

@@ -51,6 +51,12 @@ export type WebtopoProjectUpdateReqVO = WebtopoProjectCreateReqVO & {
   dataModel?: string
   dataModel?: string
 }
 }
 
 
+export type WebtopoProjectCopyReqVO = {
+  id: number
+  projectName: string
+  remark?: string
+}
+
 type ApiResult<T> = {
 type ApiResult<T> = {
   code?: number
   code?: number
   data?: T
   data?: T
@@ -107,6 +113,12 @@ export const WebtopoProjectApi = {
       data
       data
     })
     })
   },
   },
+  copyWebtopoProject: (data: WebtopoProjectCopyReqVO) => {
+    return request.post({
+      url: '/pms/iot-webtopo-project/copyProject',
+      data
+    })
+  },
   getWebtopoProjectPage: (params: WebtopoProjectPageReqVO) => {
   getWebtopoProjectPage: (params: WebtopoProjectPageReqVO) => {
     return request.get<WebtopoProjectVO[] | { list: WebtopoProjectVO[]; total: number }>({
     return request.get<WebtopoProjectVO[] | { list: WebtopoProjectVO[]; total: number }>({
       url: '/pms/iot-webtopo-project/page',
       url: '/pms/iot-webtopo-project/page',

+ 366 - 2
src/api/pms/qhse/index.ts

@@ -53,6 +53,34 @@ export const IotMeasureCertApi = {
   }
   }
 }
 }
 
 
+// 资质证书
+export const IotCertificateApi = {
+  // 获得证书分页
+  getCertificateList: async (params) => {
+    return await request.get({ url: `/rq/qhse-org-cert/page`, params })
+  },
+  // 删除证书
+  deleteCertificate: async (id) => {
+    return await request.delete({ url: `/rq/qhse-org-cert/delete?id=` + id })
+  },
+  // 新增证书
+  createCertificate: async (data) => {
+    return await request.post({ url: `/rq/qhse-org-cert/create`, data })
+  },
+  // 修改证书
+  updateCertificate: async (data) => {
+    return await request.put({ url: `/rq/qhse-org-cert/update`, data })
+  },
+  // 导出证书 Excel
+  exportCertificate: async (params) => {
+    return await request.download({ url: `/rq/qhse-org-cert/export-excel`, params })
+  },
+  //统计
+  getCertificateStatistics: async (id) => {
+    return await request.get({ url: `/rq/qhse-org-cert/stat?deptId=${id}` })
+  }
+}
+
 // 计量器具台账 VO
 // 计量器具台账 VO
 export const IotInstrumentApi = {
 export const IotInstrumentApi = {
   // 获得计量器具台账分页
   // 获得计量器具台账分页
@@ -394,6 +422,18 @@ export const IotSocSummaryApi = {
   // 下载SOC卡
   // 下载SOC卡
   downloadIotSocSummary: async (id) => {
   downloadIotSocSummary: async (id) => {
     return await request.download({ url: `/rq/iot-soc-summary/safety-card/download/${id}` })
     return await request.download({ url: `/rq/iot-soc-summary/safety-card/download/${id}` })
+  },
+  // 统计
+  // getSocSummaryStatistics: async (id) => {
+  //   return await request.get({ url: `/rq/iot-soc-summary/stat?deptId=${id}` })
+  // }
+  getSocSummaryStatistics: async (params) => {
+    return await request.get({ url: `/rq/iot-soc-summary/stat`, params })
+  },
+
+  // 获取统计子类
+  getSocSummaryStatisticsChild: async (params) => {
+    return await request.get({ url: `/rq/iot-soc-summary/stat/child`, params })
   }
   }
 }
 }
 
 
@@ -450,11 +490,335 @@ export const QHSEPtwApi = {
   // 导出PTW管理 Excel
   // 导出PTW管理 Excel
   exportPtw: async (params) => {
   exportPtw: async (params) => {
     return await request.download({ url: `/rq/qhse-ptw/export-excel`, params })
     return await request.download({ url: `/rq/qhse-ptw/export-excel`, params })
+  },
+  // 获取PTW分类
+  getPtwCategory: async (id) => {
+    return await request.get({ url: `/rq/qhse-ptw/type?deptId=${id}` })
+  },
+  // 获取传入区间范围内的每天的数量
+  getPtwCountByDateRange: async (data) => {
+    return await request.post({ url: `/rq/qhse-ptw/time`, data })
   }
   }
 }
 }
 
 
 export const kanbanApi = {
 export const kanbanApi = {
-  getKanban: async (params) => {
-    return await request.get({ url: `/rq/qhse-kanban/get`, params })
+  getKanban: async () => {
+    return await request.get({ url: `/rq/qhse/stat/number` })
+  },
+  // 安全生产天数
+  getSafeDay: async (id) => {
+    return await request.get({ url: `rq/qhse-safe-day/get/dept/company?deptId=${id}` })
+  }
+}
+
+// 证书人员
+export const CertPersonApi = {
+  // 获得证书人员分页
+  getCertPersonList: async (params) => {
+    return await request.get({ url: `/rq/qhse-cert-person/page`, params })
+  }, // 删除证书人员
+  deleteCertPerson: async (id) => {
+    return await request.delete({ url: `/rq/qhse-cert-person/delete?id=` + id })
+  },
+  // 添加证书人员
+  createCertPerson: async (data) => {
+    return await request.post({ url: `/rq/qhse-cert-person/create`, data })
+  },
+  // 修改证书人员
+  updateCertPerson: async (data) => {
+    return await request.put({ url: `/rq/qhse-cert-person/update`, data })
+  },
+  // 导出证书人员 Excel
+  exportCertPerson: async (params) => {
+    return await request.download({ url: `/rq/qhse-cert-person/export-excel`, params })
+  },
+  // 获取详情
+  getCertPerson: async (id) => {
+    return await request.get({ url: `/rq/qhse-cert-person/get?id=` + id })
+  }
+}
+
+// QHSE月报 API
+export const QhseMonthReportApi = {
+  // 查询QHSE月报分页
+  getQhseMonthReportPage: async (params: any) => {
+    return await request.get({ url: `/rq/qhse-month-report/page`, params })
+  },
+
+  // 查询QHSE月报详情
+  getQhseMonthReport: async (id) => {
+    return await request.get({ url: `/rq/qhse-month-report/get?id=` + id })
+  },
+
+  // 新增QHSE月报
+  createQhseMonthReport: async (data) => {
+    return await request.post({ url: `/rq/qhse-month-report/create`, data })
+  },
+
+  // 修改QHSE月报
+  updateQhseMonthReport: async (data) => {
+    return await request.put({ url: `/rq/qhse-month-report/update`, data })
+  },
+
+  // 删除QHSE月报
+  deleteQhseMonthReport: async (id) => {
+    return await request.delete({ url: `/rq/qhse-month-report/delete?id=` + id })
+  },
+
+  // 导出QHSE月报 Excel
+  exportQhseMonthReport: async (params) => {
+    return await request.download({ url: `/rq/qhse-month-report/export-excel`, params })
+  },
+
+  // 提交审批
+  submitForApproval: async (id) => {
+    return await request.post({ url: `/rq/qhse-month-report/approving/${id}` })
+  }
+}
+
+// 月报汇总
+export const QhseMonthReportSummaryApi = {
+  // 获得QHSE月报汇总分页
+  getQhseMonthReportSummaryList: async (params) => {
+    return await request.get({ url: `/rq/qhse-month-report/page/year-month`, params })
+  },
+
+  // 获得QHSE月报汇总详情
+  getQhseMonthReportSummary: async (params) => {
+    return await request.get({ url: `/rq/qhse-month-report/page`, params })
+  }
+}
+
+// 月报审核
+export const QhseMonthReportAuditApi = {
+  // 获得QHSE月报审核分页
+  getQhseMonthReportAuditList: async (params) => {
+    return await request.get({ url: `/rq/qhse-month-report/page`, params })
+  },
+
+  // 获得QHSE月报审核详情
+  getQhseMonthReportAudit: async (id) => {
+    return await request.get({ url: `/rq/qhse-month-report/get?id=` + id })
+  },
+
+  // 审核通过
+  auditPass: async (id) => {
+    return await request.post({ url: `/rq/qhse-month-report/approval/${id}` })
+  },
+
+  // 审核不通过
+  auditNotPass: async (data) => {
+    return await request.post({ url: `/rq/qhse-month-report/reject`, data })
+  }
+}
+
+// 应检设备证书
+export const InspectDeviceCertApi = {
+  // 获得设备证书分页 rq/qhse-device-cert/create
+  getInspectDeviceCertList: async (params) => {
+    return await request.get({ url: `/rq/qhse-device-cert/page`, params })
+  },
+  // 删除设备证书
+  deleteInspectDeviceCert: async (id) => {
+    return await request.delete({ url: `/rq/qhse-device-cert/delete?id=` + id })
+  },
+  // 添加设备证书
+  createInspectDeviceCert: async (data) => {
+    return await request.post({ url: `/rq/qhse-device-cert/create`, data })
+  },
+  // 获取详情
+  getInspectDeviceCert: async (id) => {
+    return await request.get({ url: `/rq/qhse-device-cert/get?id=` + id })
+  },
+  // 修改设备证书
+  updateInspectDeviceCert: async (data) => {
+    return await request.put({ url: `/rq/qhse-device-cert/update`, data })
+  },
+  // 导出设备证书 Excel
+  exportInspectDeviceCert: async (params) => {
+    return await request.download({ url: `/rq/qhse-device-cert/export-excel`, params })
+  },
+  // 统计
+  getInspectDeviceCertStat: async (id) => {
+    return await request.get({ url: `/rq/qhse-device-cert/stat?deptId=${id}` })
+  }
+}
+
+// 应急演练
+export const EmergencyDrillApi = {
+  // 获得应急演练分页
+  getEmergencyDrillList: async (params) => {
+    return await request.get({ url: `/rq/qhse-emergency-book/page`, params })
+  },
+  // 删除应急演练
+  deleteEmergencyDrill: async (id) => {
+    return await request.delete({ url: `/rq/qhse-emergency-book/delete?id=` + id })
+  },
+  // 添加应急演练
+  createEmergencyDrill: async (data) => {
+    return await request.post({ url: `/rq/qhse-emergency-book/create`, data })
+  },
+  // 获取详情
+  getEmergencyDrill: async (id) => {
+    return await request.get({ url: `/rq/qhse-emergency-book/get?id=` + id })
+  },
+  // 修改应急演练
+  updateEmergencyDrill: async (data) => {
+    return await request.put({ url: `/rq/qhse-emergency-book/update`, data })
+  },
+  // 导出应急演练 Excel
+  exportEmergencyDrill: async (params) => {
+    return await request.download({ url: `/rq/qhse-emergency-book/export-excel`, params })
+  }
+}
+
+// 应急演练证书检测
+export const EmergencyDrillCertApi = {
+  // 获得应急演练证书检测分页
+  getEmergencyDrillCertList: async (params) => {
+    return await request.get({ url: `/rq/qhse-emergency-cert/page`, params })
+  },
+  // 删除应急演练证书检测
+  deleteEmergencyDrillCert: async (id) => {
+    return await request.delete({ url: `/rq/qhse-emergency-cert/delete?id=` + id })
+  },
+  // 添加应急演练证书检测
+  createEmergencyDrillCert: async (data) => {
+    return await request.post({ url: `/rq/qhse-emergency-cert/create`, data })
+  },
+  // 获取详情
+  getEmergencyDrillCert: async (id) => {
+    return await request.get({ url: `/rq/qhse-emergency-cert/get?id=` + id })
+  },
+  // 修改应急演练证书检测
+  updateEmergencyDrillCert: async (data) => {
+    return await request.put({ url: `/rq/qhse-emergency-cert/update`, data })
+  },
+  // 导出应急演练证书检测 Excel
+  exportEmergencyDrillCert: async (params) => {
+    return await request.download({ url: `/rq/qhse-emergency-cert/export-excel`, params })
+  }
+}
+
+// 资料库
+export const DataLibraryApi = {
+  // 查询资料分页
+  getIotInfoPage: async (params: any) => {
+    return await request.get({ url: `/rq/qhse-info/page`, params })
+  },
+  // 查询资料库的子目录及文件
+  getChildContentFile: async (params: any) => {
+    return await request.post({ url: `/rq/qhse-info/child/content-file`, params })
+  },
+  getAllChildContentFile: async (params: any) => {
+    return await request.post({ url: `/rq/qhse-info/all/content-file`, params })
+  },
+  getIotInfoFilePage: async (params: any) => {
+    return await request.post({ url: `/rq/qhse-info/file/page`, params })
+  },
+  // 查询资料详情
+  getIotInfo: async (id: number) => {
+    return await request.get({ url: `/rq/qhse-info/get?id=` + id })
+  },
+
+  // 新增资料
+  createIotInfo: async (data) => {
+    return await request.post({ url: `/rq/qhse-info/create`, data })
+  },
+
+  // 修改资料
+  updateIotInfo: async (data) => {
+    return await request.put({ url: `/rq/qhse-info/update`, data })
+  },
+
+  // 删除资料
+  deleteIotInfo: async (id: number) => {
+    return await request.delete({ url: `/rq/qhse-info/delete?id=` + id })
+  },
+
+  // 导出资料 Excel
+  exportIotInfo: async (params) => {
+    return await request.download({ url: `/rq/qhse-info/export-excel`, params })
+  },
+
+  // 获得资料库目录分页
+  getFileTreeList: async () => {
+    return await request.get({ url: `/rq/qhse-file-tree/simple-list` })
+  },
+
+  // 新增目录
+  createFileTree: async (data) => {
+    return await request.post({ url: `/rq/qhse-file-tree/create`, data })
+  },
+
+  // 修改目录
+  updateFileTree: async (data) => {
+    return await request.put({ url: `/rq/qhse-file-tree/update`, data })
+  },
+  // 删除目录
+  deleteFileTree: async (id: number) => {
+    return await request.delete({ url: `/rq/qhse-file-tree/delete?id=` + id })
+  },
+
+  getParentIds: async (params: any) => {
+    return await request.get({ url: `/rq/qhse-file-tree/ids/` + params })
+  }
+}
+
+// 事故案例
+export const AccidentCaseApi = {
+  // 获得事故案例分页
+  getAccidentCaseList: async (params) => {
+    return await request.get({ url: `/rq/qhse-accident-case/page`, params })
+  },
+  // 删除事故案例
+  deleteAccidentCase: async (id) => {
+    return await request.delete({ url: `/rq/qhse-accident-case/delete?id=` + id })
+  },
+  // 添加事故案例
+  createAccidentCase: async (data) => {
+    return await request.post({ url: `/rq/qhse-accident-case/create`, data })
+  },
+  // 获取详情
+  getAccidentCase: async (id) => {
+    return await request.get({ url: `/rq/qhse-accident-case/get?id=` + id })
+  },
+  // 修改事故案例
+  updateAccidentCase: async (data) => {
+    return await request.put({ url: `/rq/qhse-accident-case/update`, data })
+  },
+  // 导出事故案例 Excel
+  exportAccidentCase: async (params) => {
+    return await request.download({ url: `/rq/qhse-accident-case/export-excel`, params })
+  }
+}
+
+// 行业标准
+export const IndustryStandardApi = {
+  // 获得行业标准分页
+  getIndustryStandardList: async (params) => {
+    return await request.get({ url: `/rq/qhse-law/page`, params })
+  },
+
+  // 删除行业标准
+  deleteIndustryStandard: async (id) => {
+    return await request.delete({ url: `/rq/qhse-law/delete?id=` + id })
+  },
+  // 添加行业标准
+  createIndustryStandard: async (data) => {
+    return await request.post({ url: `/rq/qhse-law/create`, data })
+  },
+  // 获取详情
+  getIndustryStandard: async (id) => {
+    return await request.get({ url: `/rq/qhse-law/get?id=` + id })
+  },
+
+  // 修改行业标准
+  updateIndustryStandard: async (data) => {
+    return await request.put({ url: `/rq/qhse-law/update`, data })
+  },
+  // 导出行业标准 Excel
+  exportIndustryStandard: async (params) => {
+    return await request.download({ url: `/rq/qhse-law/export-excel`, params })
   }
   }
 }
 }

+ 53 - 4
src/api/pms/stat/index.ts

@@ -21,6 +21,12 @@ export const IotStatApi = {
   getRepairRigWork: async (params: any) => {
   getRepairRigWork: async (params: any) => {
     return await request.get({ url: `/rq/stat/ry/dailyReport/` + params })
     return await request.get({ url: `/rq/stat/ry/dailyReport/` + params })
   },
   },
+  getRyHiZjDailyReports: async () => {
+    return await request.get({ url: `/rq/stat/ry/hiZjDailyReports` })
+  },
+  getRyHiXjDailyReports: async () => {
+    return await request.get({ url: `/rq/stat/ry/hiXjDailyReports` })
+  },
   getOrderYwcb: async (params: any) => {
   getOrderYwcb: async (params: any) => {
     return await request.get({ url: `/rq/stat/rh/ywcb/` + params })
     return await request.get({ url: `/rq/stat/rh/ywcb/` + params })
   },
   },
@@ -57,8 +63,14 @@ export const IotStatApi = {
   getInspectStatuss: async (params: any, dept: any) => {
   getInspectStatuss: async (params: any, dept: any) => {
     return await request.get({ url: `/rq/stat/inspect/statuss/` + dept, params })
     return await request.get({ url: `/rq/stat/inspect/statuss/` + dept, params })
   },
   },
-  getInspectZjxjCount: async () => {
-    return await request.get({ url: `/rq/stat/home/ry/count/zjxj` })
+  getInspectZjxjCount: async (params?: any) => {
+    return await request.get({ url: `/rq/stat/home/ry/count/zjxj`, params })
+  },
+  getInspectLayerWellCount: async (params?: any) => {
+    return await request.get({ url: `/rq/stat/rd/device/workloadStatistics`, params })
+  },
+  getInspectRate: async (params: any) => {
+    return await request.get({ url: `/rq/stat/ry/device/totalUtilizationRate`, params })
   },
   },
   getProject: async (params: any) => {
   getProject: async (params: any) => {
     return await request.get({ url: `/rq/stat/project/` + params })
     return await request.get({ url: `/rq/stat/project/` + params })
@@ -90,6 +102,9 @@ export const IotStatApi = {
   getRhZql: async (params: any) => {
   getRhZql: async (params: any) => {
     return await request.get({ url: `/rq/stat/year/total/gas/` + params })
     return await request.get({ url: `/rq/stat/year/total/gas/` + params })
   },
   },
+  getRhZqlTotal: async (params: any) => {
+    return await request.get({ url: `/rq/stat/rh/zql`, params })
+  },
   getRhZqlGases: async (params: any) => {
   getRhZqlGases: async (params: any) => {
     return await request.get({ url: `/rq/stat/year/total/gases/` + params })
     return await request.get({ url: `/rq/stat/year/total/gases/` + params })
   },
   },
@@ -147,6 +162,24 @@ export const IotStatApi = {
   getRhRate: async (params: any) => {
   getRhRate: async (params: any) => {
     return await request.get({ url: `/rq/stat/rh/device/utilizationRate`, params })
     return await request.get({ url: `/rq/stat/rh/device/utilizationRate`, params })
   },
   },
+  exportRhDashboard: async (params: any) => {
+    return await request.download({ url: `/rq/stat/exportRhDashboard`, params })
+  },
+  getRhTotalUtilizationRate: async (params: any) => {
+    return await request.get({ url: `/rq/stat/rh/device/totalUtilizationRate`, params })
+  },
+  getRhSevenDayUtilization: async () => {
+    return await request.get({ url: `/rq/stat/rh/device/sevenDayUtilization` })
+  },
+  getRhSixMonthUtilization: async () => {
+    return await request.get({ url: `/rq/stat/rh/device/sixMonthUtilization` })
+  },
+  getRdSevenDayUtilization: async () => {
+    return await request.get({ url: `/rq/stat/rd/device/sevenDayUtilization` })
+  },
+  getRdSixMonthUtilization: async () => {
+    return await request.get({ url: `/rq/stat/rd/device/sixMonthUtilization` })
+  },
   getRyRate: async (params: any) => {
   getRyRate: async (params: any) => {
     return await request.get({ url: `/rq/stat/ry/device/utilizationRate`, params })
     return await request.get({ url: `/rq/stat/ry/device/utilizationRate`, params })
   },
   },
@@ -162,9 +195,19 @@ export const IotStatApi = {
   getRyProductionBriefs: async (params: any) => {
   getRyProductionBriefs: async (params: any) => {
     return await request.get({ url: `/pms/iot-ry-daily-report/productionBriefs`, params })
     return await request.get({ url: `/pms/iot-ry-daily-report/productionBriefs`, params })
   },
   },
+  getRyNptCount: async (params: any) => {
+    return await request.get({ url: `/rq/stat/ry/device/nptCount`, params })
+  },
+  getRhNptCount: async (params: any) => {
+    return await request.get({ url: `/rq/stat/rh/device/nptCount`, params })
+  },
+  getRdNptCount: async (params: any) => {
+    return await request.get({ url: `/rq/stat/rd/device/productionAbnormality`, params })
+  },
   getRdTeamRate: async (params: any) => {
   getRdTeamRate: async (params: any) => {
     return await request.get({ url: `rq/stat/rd/device/teamUtilizationRate`, params })
     return await request.get({ url: `rq/stat/rd/device/teamUtilizationRate`, params })
   },
   },
+
   getMaintainCount: async (params?: any) => {
   getMaintainCount: async (params?: any) => {
     return await request.get({ url: `/rq/stat/home/maintain/count/` + params })
     return await request.get({ url: `/rq/stat/home/maintain/count/` + params })
   },
   },
@@ -187,8 +230,8 @@ export const IotStatApi = {
   getSafeCount: async () => {
   getSafeCount: async () => {
     return await request.get({ url: `/rq/stat/home/safe` })
     return await request.get({ url: `/rq/stat/home/safe` })
   },
   },
-  getSafeCount1: async () => {
-    return await request.get({ url: `/rq/qhse-safe-day/get/dept/` + 158, params: { deptId: 158 } })
+  getSafeCount1: async (deptId: number) => {
+    return await request.get({ url: `/rq/qhse-safe-day/get/dept/` + deptId, params: { deptId } })
   },
   },
   getMttr: async () => {
   getMttr: async () => {
     return await request.get({ url: `/rq/stat/mttr` })
     return await request.get({ url: `/rq/stat/mttr` })
@@ -226,6 +269,12 @@ export const IotStatApi = {
   getConstructionBriefing: async () => {
   getConstructionBriefing: async () => {
     return await request.get({ url: `/pms/iot-rd-daily-report/constructionBriefing` })
     return await request.get({ url: `/pms/iot-rd-daily-report/constructionBriefing` })
   },
   },
+  getRdRecentWbcs: async () => {
+    return await request.get({ url: `/rq/stat/recent/wbcs/rd` })
+  },
+  getRdNewClassify: async () => {
+    return await request.get({ url: `/rq/stat/new/classify` })
+  },
 
 
   // 瑞都看板(新)
   // 瑞都看板(新)
   // 获取ssoToken
   // 获取ssoToken

+ 1 - 0
src/assets/icons/grid-16.svg

@@ -0,0 +1 @@
+<svg width="24.000000" height="24.000000" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"><defs><clipPath id="clip1_20937"><rect width="24.000000" height="24.000000" fill="white" fill-opacity="0"></rect></clipPath></defs><rect width="24.000000" height="24.000000" fill="currentColor" fill-opacity="0"></rect><rect width="24.000000" height="24.000000" fill-opacity="1.000000"></rect><g clip-path="url(#clip1_20937)"><path d="M1 3L22 3C22.5523 3 23 3.44771 23 4L23 20C23 20.5523 22.5523 21 22 21L1 21C0.447723 21 0 20.5523 0 20L0 4C0 3.44771 0.447723 3 1 3ZM22 4L1 4L1 20L22 20L22 4ZM2 5L6 5L6 8L2 8L2 5ZM7 5L11 5L11 8L7 8L7 5ZM12 5L16 5L16 8L12 8L12 5ZM17 5L21 5L21 8L17 8L17 5ZM2 9L6 9L6 11L2 11L2 9ZM7 9L11 9L11 11L7 11L7 9ZM12 9L16 9L16 11L12 11L12 9ZM17 9L21 9L21 11L17 11L17 9ZM2 12L6 12L6 15L2 15L2 12ZM7 12L11 12L11 15L7 15L7 12ZM12 12L16 12L16 15L12 15L12 12ZM17 12L21 12L21 15L17 15L17 12ZM2 16L6 16L6 19L2 19L2 16ZM7 16L11 16L11 19L7 19L7 16ZM12 16L16 16L16 19L12 19L12 16ZM17 16L21 16L21 19L17 19L17 16Z" clip-rule="evenodd" fill="currentColor" fill-opacity="0.701961" fill-rule="evenodd"></path></g></svg>

+ 1 - 0
src/assets/icons/grid-four.svg

@@ -0,0 +1 @@
+<svg width="24.000000" height="24.000000" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"><defs><clipPath id="clip1_20933"><rect width="24.000000" height="24.000000" fill="white" fill-opacity="0"></rect></clipPath></defs><rect width="24.000000" height="24.000000" fill="currentColor" fill-opacity="0"></rect><rect width="24.000000" height="24.000000" fill-opacity="1.000000"></rect><g clip-path="url(#clip1_20933)"><path d="M22 3L1 3C0.447723 3 0 3.44771 0 4L0 20C0 20.5523 0.447723 21 1 21L22 21C22.5523 21 23 20.5523 23 20L23 4C23 3.44771 22.5523 3 22 3ZM1 4L22 4L22 20L1 20L1 4ZM2 5L11 5L11 11L2 11L2 5ZM12 5L21 5L21 11L12 11L12 5ZM2 12L11 12L11 19L2 19L2 12ZM21 12L12 12L12 19L21 19L21 12Z" clip-rule="evenodd" fill="currentColor" fill-opacity="0.701961" fill-rule="evenodd"></path></g></svg>

+ 1 - 0
src/assets/icons/grid-nine.svg

@@ -0,0 +1 @@
+<svg width="24.000000" height="24.000000" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"><defs><clipPath id="clip1_20935"><rect width="24.000000" height="24.000000" fill="white" fill-opacity="0"></rect></clipPath></defs><rect width="24.000000" height="24.000000" fill="currentColor" fill-opacity="0"></rect><rect width="24.000000" height="24.000000" fill-opacity="1.000000"></rect><g clip-path="url(#clip1_20935)"><path d="M1 3L22 3C22.5523 3 23 3.44771 23 4L23 20C23 20.5523 22.5523 21 22 21L1 21C0.447723 21 0 20.5523 0 20L0 4C0 3.44771 0.447723 3 1 3ZM22 4L1 4L1 20L22 20L22 4ZM2 5L8 5L8 9L2 9L2 5ZM9 5L14 5L14 9L9 9L9 5ZM15 5L21 5L21 9L15 9L15 5ZM2 10L8 10L8 14L2 14L2 10ZM9 10L14 10L14 14L9 14L9 10ZM15 10L21 10L21 14L15 14L15 10ZM2 15L8 15L8 19L2 19L2 15ZM9 15L14 15L14 19L9 19L9 15ZM15 15L21 15L21 19L15 19L15 15Z" clip-rule="evenodd" fill="currentColor" fill-opacity="0.701961" fill-rule="evenodd"></path></g></svg>

+ 1 - 0
src/assets/icons/grid-one.svg

@@ -0,0 +1 @@
+<svg width="24.000000" height="24.000000" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"><defs><clipPath id="clip1_20931"><rect width="24.000000" height="24.000000" fill="white" fill-opacity="0"></rect></clipPath></defs><rect width="24.000000" height="24.000000" fill="currentColor" fill-opacity="0"></rect><rect width="24.000000" height="24.000000" fill-opacity="1.000000"></rect><g clip-path="url(#clip1_20931)"><path d="M1 3L22 3C22.5523 3 23 3.44771 23 4L23 20C23 20.5523 22.5523 21 22 21L1 21C0.447723 21 0 20.5523 0 20L0 4C0 3.44771 0.447723 3 1 3ZM22 4L1 4L1 20L22 20L22 4ZM21 5L2 5L2 19L21 19L21 5Z" clip-rule="evenodd" fill="currentColor" fill-opacity="0.701961" fill-rule="evenodd"></path></g></svg>

BIN
src/assets/images/danger/地刺.png


BIN
src/assets/images/danger/当心硫化氢气体.png


BIN
src/assets/images/danger/当心触电.png


BIN
src/assets/images/danger/当心高温.png


BIN
src/assets/images/danger/注意噪音防护.png


BIN
src/assets/images/danger/防冲撞栏.png


BIN
src/assets/images/danger/高压危险.png


+ 3 - 0
src/assets/images/icons/人群.svg

@@ -0,0 +1,3 @@
+<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24">
+	<path fill="currentColor" d="M14.754 10c.966 0 1.75.784 1.75 1.75v4.749a4.501 4.501 0 0 1-9.002 0V11.75c0-.966.783-1.75 1.75-1.75zm-7.623 0a2.7 2.7 0 0 0-.62 1.53l-.01.22v4.749c0 .847.192 1.649.534 2.365Q6.539 18.999 6 19a4 4 0 0 1-4-4.001V11.75a1.75 1.75 0 0 1 1.606-1.744L3.75 10zm9.744 0h3.375c.966 0 1.75.784 1.75 1.75V15a4 4 0 0 1-5.03 3.866c.3-.628.484-1.32.525-2.052l.009-.315V11.75c0-.665-.236-1.275-.63-1.75M12 3a3 3 0 1 1 0 6a3 3 0 0 1 0-6m6.5 1a2.5 2.5 0 1 1 0 5a2.5 2.5 0 0 1 0-5m-13 0a2.5 2.5 0 1 1 0 5a2.5 2.5 0 0 1 0-5" />
+</svg>

+ 3 - 0
src/assets/images/icons/扳手.svg

@@ -0,0 +1,3 @@
+<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24">
+	<path fill="currentColor" d="m22.7 19l-9.1-9.1c.9-2.3.4-5-1.5-6.9c-2-2-5-2.4-7.4-1.3L9 6L6 9L1.6 4.7C.4 7.1.9 10.1 2.9 12.1c1.9 1.9 4.6 2.4 6.9 1.5l9.1 9.1c.4.4 1 .4 1.4 0l2.3-2.3c.5-.4.5-1.1.1-1.4" />
+</svg>

+ 3 - 0
src/assets/images/icons/账户.svg

@@ -0,0 +1,3 @@
+<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24">
+	<path fill="currentColor" d="M12 2q1.25 0 2.125.875T15 5t-.875 2.125T12 8t-2.125-.875T9 5t.875-2.125T12 2m0 7q1.175 0 2.325.275t2.075.775q.95.475 1.525 1.125T18.5 12.6v5.8q0 .425-.2.838t-.55.762t-.812.65t-1.038.55v-2.25q0-.95-1.312-1.55T12 16.8q-1.25 0-2.412.513T8.15 18.65q.95.375 1.95.525t2.05.175H13v2.6q-.175.05-.362.05h-.388q-.9 0-2.062-.2t-2.213-.625t-1.762-1.112T5.5 18.4v-5.8q0-.775.575-1.425t1.5-1.125q.95-.5 2.1-.775T12 9m0 6q.825 0 1.413-.587T14 13t-.587-1.412T12 11t-1.412.588T10 13t.588 1.413T12 15" />
+</svg>

+ 3 - 0
src/assets/images/icons/跑.svg

@@ -0,0 +1,3 @@
+<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24">
+	<path fill="currentColor" d="M13.5 5.5c1.09 0 2-.92 2-2a2 2 0 0 0-2-2c-1.11 0-2 .88-2 2c0 1.08.89 2 2 2M9.89 19.38l1-4.38L13 17v6h2v-7.5l-2.11-2l.61-3A7.3 7.3 0 0 0 19 13v-2c-1.91 0-3.5-1-4.31-2.42l-1-1.58c-.4-.62-1-1-1.69-1c-.31 0-.5.08-.81.08L6 8.28V13h2V9.58l1.79-.7L8.19 17l-4.9-1l-.4 2z" />
+</svg>

+ 3 - 0
src/assets/images/icons/锅.svg

@@ -0,0 +1,3 @@
+<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24">
+	<path fill="currentColor" fill-rule="evenodd" d="M20 11v7a2 2 0 0 1-2 2H6a2 2 0 0 1-2-2v-7H3a1 1 0 0 1 0-2h18a1 1 0 0 1 0 2zM6 11v7h12v-7zm5-5V5a1 1 0 0 1 2 0v1h6a1 1 0 0 1 0 2H5a1 1 0 1 1 0-2z" />
+</svg>

+ 3 - 0
src/assets/images/icons/门.svg

@@ -0,0 +1,3 @@
+<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24">
+	<path fill="currentColor" d="M4.5 20v-1h2V4h8v1h3v14h2v1h-3V6h-2v14zm7.54-7.46q.23-.23.23-.54t-.23-.54t-.54-.23t-.54.23t-.23.54t.23.54t.54.23t.54-.23" />
+</svg>

BIN
src/assets/images/materials/安保设施.png


BIN
src/assets/images/materials/气防设施.png


BIN
src/assets/images/materials/消防设施.png


BIN
src/assets/images/materials/路障.png


BIN
src/assets/images/process-demo/PSA.png


BIN
src/assets/images/process-demo/关闭阀门.png


BIN
src/assets/images/process-demo/厨房.png


BIN
src/assets/images/process-demo/变压器.png


BIN
src/assets/images/process-demo/地罐.png


BIN
src/assets/images/process-demo/增压机.png


BIN
src/assets/images/process-demo/开启阀门.png


BIN
src/assets/images/process-demo/房子.png


BIN
src/assets/images/process-demo/水罐.png


BIN
src/assets/images/process-demo/注水泵.png


BIN
src/assets/images/process-demo/流量计1.png


BIN
src/assets/images/process-demo/温度计.png


BIN
src/assets/images/process-demo/空压机.png


BIN
src/assets/images/process-demo/空压机1.png


BIN
src/assets/images/process-demo/空气处理撬.png


BIN
src/assets/images/process-demo/纯度计.png


BIN
src/assets/images/process-demo/罐.png


BIN
src/assets/images/process-demo/罐1.png


BIN
src/assets/images/process-demo/过滤器1.png


BIN
src/assets/images/process-demo/采油树.png


BIN
src/assets/images/process-demo/阀门2.png


+ 120 - 0
src/components/DeptSelectForm/qhseDept.vue

@@ -0,0 +1,120 @@
+<template>
+  <Dialog v-model="dialogVisible" title="部门选择" width="600">
+    <el-row v-loading="formLoading">
+      <el-col :span="24">
+        <ContentWrap class="h-1/1">
+          <el-tree-select
+            ref="treeRef"
+            :data="deptTree"
+            :props="defaultProps"
+            show-checkbox
+            :check-strictly="true"
+            :multiple="false"
+            check-on-click-node
+            highlight-current
+            node-key="id"
+            @check="handleCheck" />
+        </ContentWrap>
+      </el-col>
+    </el-row>
+    <template #footer>
+      <el-button
+        :disabled="formLoading || !selectedDeptIds?.length"
+        type="primary"
+        @click="submitForm">
+        确 定
+      </el-button>
+      <el-button @click="dialogVisible = false">取 消</el-button>
+    </template>
+  </Dialog>
+</template>
+
+<script lang="ts" setup>
+import { defaultProps, handleTree } from '@/utils/tree'
+import * as DeptApi from '@/api/system/dept'
+
+defineOptions({ name: 'DeptSelectForm' })
+
+const emit = defineEmits<{
+  confirm: [deptList: any[]]
+}>()
+
+const { t } = useI18n() // 国际化
+const message = useMessage() // 消息弹窗
+
+const props = defineProps({
+  // 是否严格的遵循父子不互相关联
+  checkStrictly: {
+    type: Boolean,
+    default: false
+  },
+  // 是否支持多选
+  multiple: {
+    type: Boolean,
+    default: false
+  }
+})
+
+const treeRef = ref()
+const deptTree = ref<Tree[]>([]) // 部门树形结构
+const selectedDeptIds = ref<number[]>([]) // 选中的部门 ID 列表
+const dialogVisible = ref(false) // 弹窗的是否展示
+const formLoading = ref(false) // 表单的加载中
+
+/** 打开弹窗 */
+const open = async (selectedList?: DeptApi.DeptVO[]) => {
+  resetForm()
+  formLoading.value = true
+  try {
+    // 加载部门列表
+    const deptData = await DeptApi.getSimpleDeptList()
+    deptTree.value = handleTree(deptData)
+  } finally {
+    formLoading.value = false
+  }
+  dialogVisible.value = true
+  // 设置已选择的部门
+  if (selectedList?.length) {
+    await nextTick()
+    const selectedIds = selectedList
+      .map((dept) => dept.id)
+      .filter((id): id is number => id !== undefined)
+    selectedDeptIds.value = selectedIds
+    treeRef.value?.setCheckedKeys(selectedIds)
+  }
+}
+
+/** 处理选中状态变化 */
+const handleCheck = (data: any, checked: any) => {
+  selectedDeptIds.value = treeRef.value.getCheckedKeys()
+  if (!props.multiple && selectedDeptIds.value.length > 1) {
+    // 单选模式下,只保留最后选择的节点
+    const lastSelectedId = selectedDeptIds.value[selectedDeptIds.value.length - 1]
+    selectedDeptIds.value = [lastSelectedId]
+    treeRef.value.setCheckedKeys([lastSelectedId])
+  }
+}
+
+/** 提交选择 */
+const submitForm = async () => {
+  try {
+    // 获取选中的完整部门数据
+    const checkedNodes = treeRef.value.getCheckedNodes()
+    message.success(t('common.updateSuccess'))
+    dialogVisible.value = false
+    emit('confirm', checkedNodes)
+  } finally {
+  }
+}
+
+/** 重置表单 */
+const resetForm = () => {
+  deptTree.value = []
+  selectedDeptIds.value = []
+  if (treeRef.value) {
+    treeRef.value.setCheckedKeys([])
+  }
+}
+
+defineExpose({ open }) // 提供 open 方法,用于打开弹窗
+</script>

+ 11 - 17
src/components/DeptTreeSelect/index.vue

@@ -63,7 +63,7 @@ const loadDeptData = async () => {
   let id = props.deptId
   let id = props.deptId
   if (id !== props.topId) {
   if (id !== props.topId) {
     const depts = await DeptApi.specifiedSimpleDepts(props.topId)
     const depts = await DeptApi.specifiedSimpleDepts(props.topId)
-    if (depts.length && !depts.find((item) => item.id === props.deptId)) id = props.topId
+    if (!depts.some((item) => Number(item.id) === Number(props.deptId))) id = props.topId
   }
   }
   const depts = (await DeptApi.specifiedSimpleDepts(id)) as Tree[]
   const depts = (await DeptApi.specifiedSimpleDepts(id)) as Tree[]
   return {
   return {
@@ -75,22 +75,18 @@ const loadDeptData = async () => {
 const loadTree = async () => {
 const loadTree = async () => {
   try {
   try {
     const { depts, currentId } = await loadDeptData()
     const { depts, currentId } = await loadDeptData()
-    if (
-      props.initSelect &&
-      props.modelValue &&
-      currentId !== undefined &&
-      !depts.some((item) => item.id === props.modelValue)
-    ) {
+    const targetKey = props.modelValue ?? (props.initSelect ? currentId : null)
+    // Only initialize the parent value when no external selection was provided.
+    if (props.initSelect && props.modelValue === undefined && currentId !== undefined) {
       emits('update:modelValue', currentId)
       emits('update:modelValue', currentId)
     }
     }
+    if (targetKey && !expandedKeys.value.includes(targetKey)) {
+      expandedKeys.value = [...expandedKeys.value, targetKey]
+    }
     deptList.value = sortTreeBySort(handleTree(depts))
     deptList.value = sortTreeBySort(handleTree(depts))
     nextTick(() => {
     nextTick(() => {
-      const targetKey = props.modelValue ?? (props.initSelect ? currentId : null)
       if (targetKey && treeRef.value) {
       if (targetKey && treeRef.value) {
         treeRef.value.setCurrentKey(targetKey)
         treeRef.value.setCurrentKey(targetKey)
-        if (!expandedKeys.value.includes(targetKey)) expandedKeys.value.push(targetKey)
-        else if (deptList.value.length > 0)
-          expandedKeys.value = deptList.value.map((item) => item.id)
       }
       }
     })
     })
   } catch (e) {
   } catch (e) {
@@ -125,8 +121,7 @@ onMounted(loadTree)
 <template>
 <template>
   <div
   <div
     class="dept-aside-container relative bg-white dark:bg-[#1d1e1f] shadow rounded-lg transition-all duration-300 ease-in-out overflow-visible"
     class="dept-aside-container relative bg-white dark:bg-[#1d1e1f] shadow rounded-lg transition-all duration-300 ease-in-out overflow-visible"
-    :class="[isCollapsed ? 'is-collapsed' : 'p-4']"
-  >
+    :class="[isCollapsed ? 'is-collapsed' : 'p-4']">
     <div v-show="!isCollapsed" class="h-full flex flex-col gap-4 overflow-hidden w-full">
     <div v-show="!isCollapsed" class="h-full flex flex-col gap-4 overflow-hidden w-full">
       <h1 v-if="showTitle" class="text-lg font-medium truncate shrink-0">{{ props.title }}</h1>
       <h1 v-if="showTitle" class="text-lg font-medium truncate shrink-0">{{ props.title }}</h1>
 
 
@@ -136,8 +131,7 @@ onMounted(loadTree)
           placeholder="请输入部门名称"
           placeholder="请输入部门名称"
           clearable
           clearable
           size="default"
           size="default"
-          :prefix-icon="Search"
-        />
+          :prefix-icon="Search" />
       </div>
       </div>
 
 
       <div class="flex-1 relative overflow-hidden">
       <div class="flex-1 relative overflow-hidden">
@@ -152,9 +146,9 @@ onMounted(loadTree)
                 :filter-node-method="filterNode"
                 :filter-node-method="filterNode"
                 node-key="id"
                 node-key="id"
                 highlight-current
                 highlight-current
+                :current-node-key="modelValue"
                 :default-expanded-keys="expandedKeys"
                 :default-expanded-keys="expandedKeys"
-                @node-click="handleNodeClick"
-              />
+                @node-click="handleNodeClick" />
             </el-scrollbar>
             </el-scrollbar>
           </template>
           </template>
         </el-auto-resizer>
         </el-auto-resizer>

+ 12 - 0
src/components/Echart/src/Echart.vue

@@ -15,6 +15,10 @@ import 'echarts/lib/component/markArea'
 
 
 defineOptions({ name: 'EChart' })
 defineOptions({ name: 'EChart' })
 
 
+const emit = defineEmits<{
+  chartClick: [params: any]
+}>()
+
 const { getPrefixCls, variables } = useDesign()
 const { getPrefixCls, variables } = useDesign()
 
 
 const prefixCls = getPrefixCls('echart')
 const prefixCls = getPrefixCls('echart')
@@ -64,6 +68,10 @@ const initChart = () => {
   if (unref(elRef) && props.options) {
   if (unref(elRef) && props.options) {
     echartRef = echarts.init(unref(elRef) as HTMLElement)
     echartRef = echarts.init(unref(elRef) as HTMLElement)
     echartRef?.setOption(unref(options))
     echartRef?.setOption(unref(options))
+    echartRef?.off('click')
+    echartRef?.on('click', (params) => {
+      emit('chartClick', params)
+    })
   }
   }
 }
 }
 
 
@@ -85,6 +93,10 @@ const resizeHandler = debounce(() => {
   }
   }
 }, 100)
 }, 100)
 
 
+defineExpose({
+  resize: resizeHandler
+})
+
 const contentResizeHandler = async (e: TransitionEvent) => {
 const contentResizeHandler = async (e: TransitionEvent) => {
   if (e.propertyName === 'width') {
   if (e.propertyName === 'width') {
     resizeHandler()
     resizeHandler()

+ 2 - 4
src/components/Pagination/index.vue

@@ -8,17 +8,15 @@
     :page-sizes="[10, 20, 30, 50, 100]"
     :page-sizes="[10, 20, 30, 50, 100]"
     :pager-count="pagerCount"
     :pager-count="pagerCount"
     :total="total"
     :total="total"
-    :small="isSmall"
+    :size="isSmall ? 'small' : 'default'"
     class="float-right mb-15px mt-15px"
     class="float-right mb-15px mt-15px"
     layout="total, sizes, prev, pager, next, jumper"
     layout="total, sizes, prev, pager, next, jumper"
     @size-change="handleSizeChange"
     @size-change="handleSizeChange"
-    @current-change="handleCurrentChange"
-  />
+    @current-change="handleCurrentChange" />
 </template>
 </template>
 <script lang="ts" setup>
 <script lang="ts" setup>
 import { computed, watchEffect } from 'vue'
 import { computed, watchEffect } from 'vue'
 import { useAppStore } from '@/store/modules/app'
 import { useAppStore } from '@/store/modules/app'
-
 defineOptions({ name: 'Pagination' })
 defineOptions({ name: 'Pagination' })
 
 
 // 此处解决了当全局size为small的时候分页组件样式太大的问题
 // 此处解决了当全局size为small的时候分页组件样式太大的问题

+ 154 - 189
src/components/UploadFile/src/FileUpload.vue

@@ -7,18 +7,16 @@
         :loading="uploading"
         :loading="uploading"
         @click="handleFileUploadClick"
         @click="handleFileUploadClick"
         class="upload-btn"
         class="upload-btn"
-        style="margin-right: 10px"
-      >
+        style="margin-right: 10px">
         <el-icon><Document /></el-icon>
         <el-icon><Document /></el-icon>
         {{ uploading ? '文件上传中...' : '选择文件' }}
         {{ uploading ? '文件上传中...' : '选择文件' }}
       </el-button>
       </el-button>
       <el-button
       <el-button
-        v-if="showFolderButton"
+        v-if="canShowFolderButton"
         type="success"
         type="success"
         :loading="uploading"
         :loading="uploading"
         @click="handleFolderUploadClick"
         @click="handleFolderUploadClick"
-        class="upload-btn"
-      >
+        class="upload-btn">
         <el-icon><Folder /></el-icon>
         <el-icon><Folder /></el-icon>
         {{ uploading ? '文件夹上传中...' : '选择文件夹' }}
         {{ uploading ? '文件夹上传中...' : '选择文件夹' }}
       </el-button>
       </el-button>
@@ -30,24 +28,15 @@
         accept="*"
         accept="*"
         :webkitdirectory="isFolderMode"
         :webkitdirectory="isFolderMode"
         :directory="isFolderMode"
         :directory="isFolderMode"
-        @change="handleFileChange"
-      />
+        @change="handleFileChange" />
       <p class="upload-hint">{{ uploadHintText }}</p>
       <p class="upload-hint">{{ uploadHintText }}</p>
     </div>
     </div>
 
 
     <!-- 上传列表与进度 - 保持不变 -->
     <!-- 上传列表与进度 - 保持不变 -->
     <el-card v-if="uploadList.length > 0" class="upload-list-card">
     <el-card v-if="uploadList.length > 0" class="upload-list-card">
-      <el-table
-        :data="uploadList"
-        border
-        style="width: 100%; margin-bottom: 0px;"
-      >
+      <el-table :data="uploadList" border style="width: 100%; margin-bottom: 0px">
         <!-- 表格内容保持不变 -->
         <!-- 表格内容保持不变 -->
-        <el-table-column
-          prop="name"
-          label="文件名/文件夹名"
-          width="300"
-        >
+        <el-table-column prop="name" label="文件名/文件夹名" width="300">
           <template #default="scope">
           <template #default="scope">
             <div class="file-name">
             <div class="file-name">
               <el-icon v-if="scope.row.type === 'folder'"><Folder /></el-icon>
               <el-icon v-if="scope.row.type === 'folder'"><Folder /></el-icon>
@@ -56,55 +45,37 @@
             </div>
             </div>
           </template>
           </template>
         </el-table-column>
         </el-table-column>
-        <el-table-column
-          prop="size"
-          label="大小"
-          width="120"
-          :formatter="formatSize"
-        />
-        <el-table-column
-          prop="type"
-          label="类型"
-          width="120"
-        >
+        <el-table-column prop="size" label="大小" width="120" :formatter="formatSize" />
+        <el-table-column prop="type" label="类型" width="120">
           <template #default="scope">
           <template #default="scope">
             <el-tag size="small" :type="scope.row.type === 'folder' ? 'info' : 'primary'">
             <el-tag size="small" :type="scope.row.type === 'folder' ? 'info' : 'primary'">
               {{ scope.row.type || '文件夹' }}
               {{ scope.row.type || '文件夹' }}
             </el-tag>
             </el-tag>
           </template>
           </template>
         </el-table-column>
         </el-table-column>
-        <el-table-column
-          prop="progress"
-          label="进度"
-          min-width="200"
-        >
+        <el-table-column prop="progress" label="进度" min-width="200">
           <template #default="scope">
           <template #default="scope">
             <el-progress
             <el-progress
               :percentage="scope.row.progress"
               :percentage="scope.row.progress"
               :status="getProgressStatus(scope.row.status)"
               :status="getProgressStatus(scope.row.status)"
-              stroke-width="6"
-            />
+              stroke-width="6" />
           </template>
           </template>
         </el-table-column>
         </el-table-column>
-        <el-table-column
-          label="操作"
-          width="120"
-        >
+        <el-table-column label="操作" width="120">
           <template #default="scope">
           <template #default="scope">
             <el-button
             <el-button
               v-if="scope.row.status === 'uploading'"
               v-if="scope.row.status === 'uploading'"
               size="small"
               size="small"
               type="text"
               type="text"
               @click.stop="handleCancelUpload(scope.row)"
               @click.stop="handleCancelUpload(scope.row)"
-              text
-            >
+              text>
               取消
               取消
             </el-button>
             </el-button>
             <el-icon v-if="scope.row.status === 'success'" color="#10b981">
             <el-icon v-if="scope.row.status === 'success'" color="#10b981">
-<!--              <CheckCircle />-->
+              <!--              <CheckCircle />-->
             </el-icon>
             </el-icon>
             <el-icon v-if="scope.row.status === 'error'" color="#ef4444">
             <el-icon v-if="scope.row.status === 'error'" color="#ef4444">
-<!--              <CloseCircle />-->
+              <!--              <CloseCircle />-->
             </el-icon>
             </el-icon>
             <span v-if="scope.row.status === 'cancelled'">已取消</span>
             <span v-if="scope.row.status === 'cancelled'">已取消</span>
           </template>
           </template>
@@ -113,11 +84,7 @@
     </el-card>
     </el-card>
 
 
     <!-- 错误提示 - 保持不变 -->
     <!-- 错误提示 - 保持不变 -->
-    <el-dialog
-      v-model="showErrorModal"
-      title="上传错误"
-      @close="showErrorModal = false"
-    >
+    <el-dialog v-model="showErrorModal" title="上传错误" @close="showErrorModal = false">
       <p>{{ errorMessage }}</p>
       <p>{{ errorMessage }}</p>
       <template #footer>
       <template #footer>
         <el-button @click="showErrorModal = false">确定</el-button>
         <el-button @click="showErrorModal = false">确定</el-button>
@@ -127,17 +94,13 @@
 </template>
 </template>
 
 
 <script setup>
 <script setup>
-import { ref, computed, onMounted } from 'vue';
-import {
-  Upload,
-  Folder,
-  Document,
-} from '@element-plus/icons-vue';
+import { ref, computed, onMounted } from 'vue'
+import { Upload, Folder, Document } from '@element-plus/icons-vue'
 
 
 // 组件参数 - 保持不变
 // 组件参数 - 保持不变
 const props = defineProps({
 const props = defineProps({
-  deviceId:{
-    type: String,
+  deviceId: {
+    type: String
   },
   },
   allowFolderUpload: {
   allowFolderUpload: {
     type: Boolean,
     type: Boolean,
@@ -154,139 +117,142 @@ const props = defineProps({
   },
   },
   uploadUrl: {
   uploadUrl: {
     type: String,
     type: String,
-    default: import.meta.env.VITE_BASE_URL+'/admin-api/rq/file/upload'
+    default: import.meta.env.VITE_BASE_URL + '/admin-api/rq/file/upload'
   }
   }
-});
+})
 
 
 // 组件事件 - 保持不变
 // 组件事件 - 保持不变
-const emit = defineEmits(['uploadSuccess', 'uploadError', 'uploadComplete']);
+const emit = defineEmits(['uploadSuccess', 'uploadError', 'uploadComplete'])
 
 
 // 新增:用于区分当前是文件还是文件夹上传模式
 // 新增:用于区分当前是文件还是文件夹上传模式
-const isFolderMode = ref();
+const isFolderMode = ref()
 
 
 // 状态管理 - 保持不变
 // 状态管理 - 保持不变
-const fileInput = ref(null);
-const uploading = ref(false);
-const uploadList = ref([]);
-const showErrorModal = ref(false);
-const errorMessage = ref('');
-const uploadTasks = ref(new Map());
+const fileInput = ref(null)
+const uploading = ref(false)
+const uploadList = ref([])
+const showErrorModal = ref(false)
+const errorMessage = ref('')
+const uploadTasks = ref(new Map())
 
 
 // 判断父组件是否传递了 showFolderButton 属性(而非判断其值)
 // 判断父组件是否传递了 showFolderButton 属性(而非判断其值)
-const hasShowFolderButton = Object.prototype.hasOwnProperty.call(props, 'showFolderButton');
+const hasShowFolderButton = Object.prototype.hasOwnProperty.call(props, 'showFolderButton')
+
+const canShowFolderButton = computed(() => {
+  return props.allowFolderUpload && props.showFolderButton
+})
 
 
 // 根据是否传递属性决定显示的提示文本
 // 根据是否传递属性决定显示的提示文本
 const uploadHintText = computed(() => {
 const uploadHintText = computed(() => {
-  return hasShowFolderButton
-    ? '文件大小不能超过50M'
-    : '单个文件夹总大小不能超过500M';
-});
+  return hasShowFolderButton ? '文件大小不能超过50M' : '单个文件夹总大小不能超过500M'
+})
 
 
 // 获取进度条状态 - 保持不变
 // 获取进度条状态 - 保持不变
 const getProgressStatus = (status) => {
 const getProgressStatus = (status) => {
-  switch(status) {
+  switch (status) {
     case 'success':
     case 'success':
-      return 'success';
+      return 'success'
     case 'error':
     case 'error':
-      return 'exception';
+      return 'exception'
     case 'uploading':
     case 'uploading':
-      return 'processing';
+      return 'processing'
     default:
     default:
-      return '';
+      return ''
   }
   }
-};
+}
 
 
 // 格式化文件大小 - 保持不变
 // 格式化文件大小 - 保持不变
 const formatSize = (row, column, bytes) => {
 const formatSize = (row, column, bytes) => {
-  if (bytes === 0) return '0 Bytes';
-  const k = 1024;
-  const sizes = ['Bytes', 'KB', 'MB', 'GB'];
-  const i = Math.floor(Math.log(bytes) / Math.log(k));
-  return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i];
-};
+  if (bytes === 0) return '0 Bytes'
+  const k = 1024
+  const sizes = ['Bytes', 'KB', 'MB', 'GB']
+  const i = Math.floor(Math.log(bytes) / Math.log(k))
+  return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i]
+}
 
 
 // 计算文件夹总大小 - 保持不变
 // 计算文件夹总大小 - 保持不变
 const calculateFolderSize = (files) => {
 const calculateFolderSize = (files) => {
-  let totalSize = 0;
-  files.forEach(file => {
-    totalSize += file.size;
-  });
-  return totalSize;
-};
+  let totalSize = 0
+  files.forEach((file) => {
+    totalSize += file.size
+  })
+  return totalSize
+}
 
 
 // 新增:文件上传按钮点击事件
 // 新增:文件上传按钮点击事件
 const handleFileUploadClick = () => {
 const handleFileUploadClick = () => {
-  if (uploading.value) return;
+  if (uploading.value) return
   if (fileInput.value) {
   if (fileInput.value) {
-    fileInput.value.value = '';
+    fileInput.value.value = ''
   }
   }
-  isFolderMode.value = false; // 切换到文件模式
+  isFolderMode.value = false // 切换到文件模式
   // 强制DOM更新后再触发点击
   // 强制DOM更新后再触发点击
   nextTick(() => {
   nextTick(() => {
-    fileInput.value.click();
-  });
-};
+    fileInput.value.click()
+  })
+}
 
 
 // 新增:文件夹上传按钮点击事件
 // 新增:文件夹上传按钮点击事件
 const handleFolderUploadClick = () => {
 const handleFolderUploadClick = () => {
-  if (uploading.value) return;
+  if (uploading.value) return
   if (fileInput.value) {
   if (fileInput.value) {
-    fileInput.value.value = '';
+    fileInput.value.value = ''
   }
   }
-  isFolderMode.value = true; // 切换到文件夹模式
+  isFolderMode.value = true // 切换到文件夹模式
   if (!props.allowFolderUpload) {
   if (!props.allowFolderUpload) {
-    errorMessage.value = '不允许上传文件夹';
-    showErrorModal.value = true;
-    return;
+    errorMessage.value = '不允许上传文件夹'
+    showErrorModal.value = true
+    return
   }
   }
   // 强制DOM更新后再触发点击
   // 强制DOM更新后再触发点击
   nextTick(() => {
   nextTick(() => {
-    fileInput.value.click();
-  });
-};
+    fileInput.value.click()
+  })
+}
 
 
 // 处理文件选择 - 保持不变
 // 处理文件选择 - 保持不变
 const handleFileChange = async (e) => {
 const handleFileChange = async (e) => {
-  const files = Array.from(e.target.files);
-  if (files.length === 0) return;
+  const files = Array.from(e.target.files)
+  if (files.length === 0) return
 
 
   // 清空input值,允许重复选择同一文件
   // 清空input值,允许重复选择同一文件
-  e.target.value = '';
+  e.target.value = ''
 
 
   // 检查是否包含文件夹(根据当前模式判断)
   // 检查是否包含文件夹(根据当前模式判断)
-  const hasFolders = isFolderMode.value && files.some(file => file.webkitRelativePath.includes('/'));
+  const hasFolders =
+    isFolderMode.value && files.some((file) => file.webkitRelativePath.includes('/'))
 
 
   if (hasFolders) {
   if (hasFolders) {
     // 按文件夹分组
     // 按文件夹分组
-    const folderMap = new Map();
+    const folderMap = new Map()
 
 
-    files.forEach(file => {
+    files.forEach((file) => {
       // 提取文件夹路径
       // 提取文件夹路径
-      const relativePath = file.webkitRelativePath;
-      const folderPath = relativePath.substring(0, relativePath.lastIndexOf('/') + 1);
+      const relativePath = file.webkitRelativePath
+      const folderPath = relativePath.substring(0, relativePath.lastIndexOf('/') + 1)
 
 
       if (!folderMap.has(folderPath)) {
       if (!folderMap.has(folderPath)) {
-        folderMap.set(folderPath, []);
+        folderMap.set(folderPath, [])
       }
       }
-      folderMap.get(folderPath).push(file);
-    });
+      folderMap.get(folderPath).push(file)
+    })
 
 
     // 检查每个文件夹大小
     // 检查每个文件夹大小
     for (const [folderPath, folderFiles] of folderMap) {
     for (const [folderPath, folderFiles] of folderMap) {
-      const folderSize = calculateFolderSize(folderFiles);
-      const folderSizeMB = folderSize / (1024 * 1024);
+      const folderSize = calculateFolderSize(folderFiles)
+      const folderSizeMB = folderSize / (1024 * 1024)
 
 
       if (folderSizeMB > props.maxFolderSize) {
       if (folderSizeMB > props.maxFolderSize) {
-        errorMessage.value = `文件夹 "${folderPath}" 大小超过${props.maxFolderSize}M限制,无法上传`;
-        showErrorModal.value = true;
-        return;
+        errorMessage.value = `文件夹 "${folderPath}" 大小超过${props.maxFolderSize}M限制,无法上传`
+        showErrorModal.value = true
+        return
       }
       }
     }
     }
 
 
     // 添加文件夹到上传列表
     // 添加文件夹到上传列表
     folderMap.forEach((folderFiles, folderPath) => {
     folderMap.forEach((folderFiles, folderPath) => {
-      const folderSize = calculateFolderSize(folderFiles);
-      const uid = Date.now() + Math.random().toString(36).substr(2, 9);
+      const folderSize = calculateFolderSize(folderFiles)
+      const uid = Date.now() + Math.random().toString(36).substr(2, 9)
 
 
       uploadList.value.push({
       uploadList.value.push({
         uid,
         uid,
@@ -296,11 +262,11 @@ const handleFileChange = async (e) => {
         progress: 0,
         progress: 0,
         status: 'wait',
         status: 'wait',
         files: folderFiles
         files: folderFiles
-      });
-    });
+      })
+    })
   } else {
   } else {
     // 单个或多个文件
     // 单个或多个文件
-    files.forEach(file => {
+    files.forEach((file) => {
       uploadList.value.push({
       uploadList.value.push({
         uid: Date.now() + Math.random().toString(36).substr(2, 9),
         uid: Date.now() + Math.random().toString(36).substr(2, 9),
         name: file.name,
         name: file.name,
@@ -309,156 +275,155 @@ const handleFileChange = async (e) => {
         progress: 0,
         progress: 0,
         status: 'wait',
         status: 'wait',
         files: [file]
         files: [file]
-      });
-    });
+      })
+    })
   }
   }
 
 
   // 开始上传
   // 开始上传
-  await startUpload();
-};
+  await startUpload()
+}
 
 
 // 开始上传 - 保持不变
 // 开始上传 - 保持不变
 const startUpload = async () => {
 const startUpload = async () => {
-  uploading.value = true;
+  uploading.value = true
 
 
   // 过滤出等待上传的文件/文件夹
   // 过滤出等待上传的文件/文件夹
-  const pendingFiles = uploadList.value.filter(item => item.status === 'wait');
+  const pendingFiles = uploadList.value.filter((item) => item.status === 'wait')
 
 
   for (const fileItem of pendingFiles) {
   for (const fileItem of pendingFiles) {
     try {
     try {
       // 创建FormData
       // 创建FormData
-      const formData = new FormData();
+      const formData = new FormData()
 
 
       // 添加文件
       // 添加文件
-      fileItem.files.forEach(file => {
-        formData.append('files', file, file.webkitRelativePath || file.name);
-      });
+      fileItem.files.forEach((file) => {
+        formData.append('files', file, file.webkitRelativePath || file.name)
+      })
 
 
       // 添加文件夹标识和路径
       // 添加文件夹标识和路径
       if (fileItem.type === 'folder') {
       if (fileItem.type === 'folder') {
-        formData.append('isFolder', 'true');
-        formData.append('folderPath', fileItem.name);
+        formData.append('isFolder', 'true')
+        formData.append('folderPath', fileItem.name)
       }
       }
 
 
       // 创建上传任务
       // 创建上传任务
-      const xhr = new XMLHttpRequest();
-      uploadTasks.value.set(fileItem.uid, xhr);
+      const xhr = new XMLHttpRequest()
+      uploadTasks.value.set(fileItem.uid, xhr)
 
 
       // 监听进度
       // 监听进度
       xhr.upload.addEventListener('progress', (e) => {
       xhr.upload.addEventListener('progress', (e) => {
         if (e.lengthComputable) {
         if (e.lengthComputable) {
-          const percent = Math.round((e.loaded / e.total) * 100);
-          const index = uploadList.value.findIndex(item => item.uid === fileItem.uid);
+          const percent = Math.round((e.loaded / e.total) * 100)
+          const index = uploadList.value.findIndex((item) => item.uid === fileItem.uid)
           if (index !== -1) {
           if (index !== -1) {
-            uploadList.value[index].progress = percent;
-            uploadList.value[index].status = 'uploading';
+            uploadList.value[index].progress = percent
+            uploadList.value[index].status = 'uploading'
           }
           }
         }
         }
-      });
+      })
 
 
       // 监听完成
       // 监听完成
       xhr.addEventListener('load', () => {
       xhr.addEventListener('load', () => {
-        const index = uploadList.value.findIndex(item => item.uid === fileItem.uid);
+        const index = uploadList.value.findIndex((item) => item.uid === fileItem.uid)
         if (index !== -1) {
         if (index !== -1) {
           if (xhr.status >= 200 && xhr.status < 300) {
           if (xhr.status >= 200 && xhr.status < 300) {
-            uploadList.value[index].status = 'success';
-            uploadList.value[index].progress = 100;
+            uploadList.value[index].status = 'success'
+            uploadList.value[index].progress = 100
             emit('uploadSuccess', {
             emit('uploadSuccess', {
               uid: fileItem.uid,
               uid: fileItem.uid,
               name: fileItem.name,
               name: fileItem.name,
               response: JSON.parse(xhr.responseText)
               response: JSON.parse(xhr.responseText)
-            });
+            })
           } else {
           } else {
-            uploadList.value[index].status = 'error';
+            uploadList.value[index].status = 'error'
             emit('uploadError', {
             emit('uploadError', {
               uid: fileItem.uid,
               uid: fileItem.uid,
               name: fileItem.name,
               name: fileItem.name,
               error: new Error(`上传失败: ${xhr.statusText}`)
               error: new Error(`上传失败: ${xhr.statusText}`)
-            });
+            })
           }
           }
         }
         }
-        uploadTasks.value.delete(fileItem.uid);
-      });
+        uploadTasks.value.delete(fileItem.uid)
+      })
 
 
       // 监听错误
       // 监听错误
       xhr.addEventListener('error', () => {
       xhr.addEventListener('error', () => {
-        const index = uploadList.value.findIndex(item => item.uid === fileItem.uid);
+        const index = uploadList.value.findIndex((item) => item.uid === fileItem.uid)
         if (index !== -1) {
         if (index !== -1) {
-          uploadList.value[index].status = 'error';
+          uploadList.value[index].status = 'error'
           emit('uploadError', {
           emit('uploadError', {
             uid: fileItem.uid,
             uid: fileItem.uid,
             name: fileItem.name,
             name: fileItem.name,
             error: new Error('网络错误,上传失败')
             error: new Error('网络错误,上传失败')
-          });
+          })
         }
         }
-        uploadTasks.value.delete(fileItem.uid);
-      });
+        uploadTasks.value.delete(fileItem.uid)
+      })
 
 
       // 发送请求
       // 发送请求
-      xhr.open('POST', props.uploadUrl, true);
-      xhr.setRequestHeader('tenant-id', 1);
-      xhr.setRequestHeader('device-id', props.deviceId);
-      xhr.send(formData);
-
+      xhr.open('POST', props.uploadUrl, true)
+      xhr.setRequestHeader('tenant-id', 1)
+      xhr.setRequestHeader('device-id', props.deviceId)
+      xhr.send(formData)
     } catch (error) {
     } catch (error) {
-      console.error('上传失败', error);
-      const index = uploadList.value.findIndex(item => item.uid === fileItem.uid);
+      console.error('上传失败', error)
+      const index = uploadList.value.findIndex((item) => item.uid === fileItem.uid)
       if (index !== -1) {
       if (index !== -1) {
-        uploadList.value[index].status = 'error';
+        uploadList.value[index].status = 'error'
       }
       }
       emit('uploadError', {
       emit('uploadError', {
         uid: fileItem.uid,
         uid: fileItem.uid,
         name: fileItem.name,
         name: fileItem.name,
         error
         error
-      });
+      })
     }
     }
   }
   }
 
 
   // 检查是否所有上传都已完成
   // 检查是否所有上传都已完成
   const checkUploadComplete = setInterval(() => {
   const checkUploadComplete = setInterval(() => {
-    const allCompleted = uploadList.value.every(item =>
-      item.status === 'success' || item.status === 'error' || item.status === 'cancelled'
-    );
+    const allCompleted = uploadList.value.every(
+      (item) => item.status === 'success' || item.status === 'error' || item.status === 'cancelled'
+    )
 
 
     if (allCompleted) {
     if (allCompleted) {
-      clearInterval(checkUploadComplete);
-      uploading.value = false;
-      emit('uploadComplete');
+      clearInterval(checkUploadComplete)
+      uploading.value = false
+      emit('uploadComplete')
     }
     }
-  }, 1000);
-};
+  }, 1000)
+}
 
 
 // 取消上传 - 保持不变
 // 取消上传 - 保持不变
 const handleCancelUpload = (fileItem) => {
 const handleCancelUpload = (fileItem) => {
-  const xhr = uploadTasks.value.get(fileItem.uid);
+  const xhr = uploadTasks.value.get(fileItem.uid)
   if (xhr) {
   if (xhr) {
-    xhr.abort();
-    uploadTasks.value.delete(fileItem.uid);
+    xhr.abort()
+    uploadTasks.value.delete(fileItem.uid)
   }
   }
 
 
-  const index = uploadList.value.findIndex(item => item.uid === fileItem.uid);
+  const index = uploadList.value.findIndex((item) => item.uid === fileItem.uid)
   if (index !== -1) {
   if (index !== -1) {
-    uploadList.value[index].status = 'cancelled';
+    uploadList.value[index].status = 'cancelled'
   }
   }
 
 
   // 检查是否还有正在上传的任务
   // 检查是否还有正在上传的任务
-  const hasUploading = uploadList.value.some(item => item.status === 'uploading');
+  const hasUploading = uploadList.value.some((item) => item.status === 'uploading')
   if (!hasUploading) {
   if (!hasUploading) {
-    uploading.value = false;
+    uploading.value = false
   }
   }
-};
+}
 
 
 // 组件挂载时设置样式 - 保持不变
 // 组件挂载时设置样式 - 保持不变
 onMounted(() => {
 onMounted(() => {
-  const container = document.querySelector('.file-upload-container');
+  const container = document.querySelector('.file-upload-container')
   if (container) {
   if (container) {
-    container.style.height = '8vh';
-    container.style.display = 'flex';
-    container.style.flexDirection = 'column';
-    container.style.padding = '20px';
-    container.style.boxSizing = 'border-box';
+    container.style.height = '8vh'
+    container.style.display = 'flex'
+    container.style.flexDirection = 'column'
+    container.style.padding = '20px'
+    container.style.boxSizing = 'border-box'
   }
   }
-});
+})
 </script>
 </script>
 
 
 <style scoped>
 <style scoped>

+ 4 - 5
src/components/UploadFile/src/UploadFile.vue

@@ -18,8 +18,7 @@
       :on-success="handleFileSuccess"
       :on-success="handleFileSuccess"
       :show-file-list="true"
       :show-file-list="true"
       class="upload-file-uploader"
       class="upload-file-uploader"
-      name="file"
-    >
+      name="file">
       <el-button type="primary">
       <el-button type="primary">
         <Icon icon="ep:upload-filled" />
         <Icon icon="ep:upload-filled" />
         选取文件
         选取文件
@@ -41,8 +40,7 @@
               :underline="false"
               :underline="false"
               download
               download
               target="_blank"
               target="_blank"
-              type="primary"
-            >
+              type="primary">
               下载
               下载
             </el-link>
             </el-link>
           </div>
           </div>
@@ -93,7 +91,8 @@ const props = defineProps({
     'pptx',
     'pptx',
     'mp4',
     'mp4',
     'apk',
     'apk',
-    'wgt'
+    'wgt',
+    'bmp'
   ]), // 文件类型, 例如['png', 'jpg', 'jpeg']
   ]), // 文件类型, 例如['png', 'jpg', 'jpeg']
   fileSize: propTypes.number.def(50), // 大小限制(MB)
   fileSize: propTypes.number.def(50), // 大小限制(MB)
   limit: propTypes.number.def(5), // 数量限制
   limit: propTypes.number.def(5), // 数量限制

+ 31 - 7
src/components/ZmTable/README.md

@@ -73,7 +73,10 @@ const { ZmTable, ZmTableColumn } = useTableComponents<ListItem>()
 | `columnMaxWidth` | `number` | `360` | 自动计算列宽时的最大宽度。 |
 | `columnMaxWidth` | `number` | `360` | 自动计算列宽时的最大宽度。 |
 | `customClass` | `boolean` | `false` | 为 `true` 时不挂载默认 `.zm-table` class,也就不会使用组件默认样式变量。 |
 | `customClass` | `boolean` | `false` | 为 `true` 时不挂载默认 `.zm-table` class,也就不会使用组件默认样式变量。 |
 | `showBorder` | `boolean` | `false` | 控制组件自己的边框显示样式。 |
 | `showBorder` | `boolean` | `false` | 控制组件自己的边框显示样式。 |
+| `hoverHighlight` | `boolean` | `true` | 控制鼠标悬浮行时是否显示 hover 背景。 |
 | `showOverflowTooltip` | `boolean` | `true` | 继承自 Element Plus。想让文字换行时传 `false`,再配合页面 CSS 覆盖 `.cell` 的换行样式。 |
 | `showOverflowTooltip` | `boolean` | `true` | 继承自 Element Plus。想让文字换行时传 `false`,再配合页面 CSS 覆盖 `.cell` 的换行样式。 |
+| `settingsCache` | `boolean` | `true` | 是否缓存列设置。当前使用 `localStorage`,缓存列顺序、显隐和固定状态。 |
+| `settingsCacheKey` | `string` | - | 列设置缓存 key。建议业务页面传稳定唯一值,没传时组件会用当前路由和列 key 自动生成。 |
 
 
 组件内部默认还会给 Element Plus 表格设置这些值:
 组件内部默认还会给 Element Plus 表格设置这些值:
 
 
@@ -98,7 +101,7 @@ const { ZmTable, ZmTableColumn } = useTableComponents<ListItem>()
 | 属性 | 类型 | 默认值 | 说明 |
 | 属性 | 类型 | 默认值 | 说明 |
 | --- | --- | --- | --- |
 | --- | --- | --- | --- |
 | `prop` | `keyof T \| string` | - | 字段名,也会作为列设置 key 的优先来源。 |
 | `prop` | `keyof T \| string` | - | 字段名,也会作为列设置 key 的优先来源。 |
-| `action` | `boolean` | `false` | 标记为操作列,并在表头显示列设置按钮。操作列本身不会进入列设置列表。 |
+| `action` | `boolean` | `false` | 标记为操作列,并在表头显示列设置按钮。操作列会进入列设置列表。 |
 | `hideInColumnSettings` | `boolean` | `false` | 不显示在列设置面板里,适合序号列、选择列等固定功能列。 |
 | `hideInColumnSettings` | `boolean` | `false` | 不显示在列设置面板里,适合序号列、选择列等固定功能列。 |
 | `isParent` | `boolean` | `false` | 标记当前列是多级表头父级。需要控制子级列排序/显隐/固定时必须加。 |
 | `isParent` | `boolean` | `false` | 标记当前列是多级表头父级。需要控制子级列排序/显隐/固定时必须加。 |
 | `zmSortable` | `boolean` | `false` | 显示排序按钮。组件只维护/展示排序状态,具体排序逻辑由调用方处理。 |
 | `zmSortable` | `boolean` | `false` | 显示排序按钮。组件只维护/展示排序状态,具体排序逻辑由调用方处理。 |
@@ -147,8 +150,7 @@ function handleSort(prop: string, order: 'asc' | 'desc' | null) {
   label="会议日期"
   label="会议日期"
   zm-sortable
   zm-sortable
   v-model:sort-order="meetingDateOrder"
   v-model:sort-order="meetingDateOrder"
-  @sort-change="handleSortChange"
-/>
+  @sort-change="handleSortChange" />
 ```
 ```
 
 
 ```ts
 ```ts
@@ -171,8 +173,7 @@ function handleSortChange(payload: { prop: string; order: 'asc' | 'desc' | null
   label="状态"
   label="状态"
   zm-filterable
   zm-filterable
   v-model:filter-model-value="query.status"
   v-model:filter-model-value="query.status"
-  @filter-visible-change="handleFilterVisibleChange"
->
+  @filter-visible-change="handleFilterVisibleChange">
   <template #filter="{ filterModelValue, updateFilterModelValue, close }">
   <template #filter="{ filterModelValue, updateFilterModelValue, close }">
     <div class="p-2">
     <div class="p-2">
       <el-select
       <el-select
@@ -244,6 +245,30 @@ function handleSortChange(payload: { prop: string; order: 'asc' | 'desc' | null
 - 显示 / 隐藏列。
 - 显示 / 隐藏列。
 - 固定到左侧 / 固定到右侧 / 取消固定。
 - 固定到左侧 / 固定到右侧 / 取消固定。
 - 重置为初始列配置。
 - 重置为初始列配置。
+- 自动缓存列设置,下次进入同一张表时恢复。
+
+缓存默认开启,当前先写入 `localStorage`。后续接接口时,建议继续使用稳定的 `settingsCacheKey` 作为后端保存和读取配置的业务标识。
+
+```vue
+<ZmTable :data="list" :loading="loading" settings-cache-key="device-monitor-list">
+  <ZmTableColumn prop="name" label="名称" />
+  <ZmTableColumn prop="status" label="状态" />
+
+  <ZmTableColumn label="操作" width="120" fixed="right" action>
+    <template #default="{ row }">
+      <el-button link type="primary" @click="handleEdit(row)">编辑</el-button>
+    </template>
+  </ZmTableColumn>
+</ZmTable>
+```
+
+不希望某张表缓存时,可以关闭:
+
+```vue
+<ZmTable :data="list" :loading="loading" :settings-cache="false">
+  ...
+</ZmTable>
+```
 
 
 列设置 key 的优先级:
 列设置 key 的优先级:
 
 
@@ -329,8 +354,7 @@ Element Plus 原生多级表头可以直接写:
   prop="meetingDate"
   prop="meetingDate"
   label="会议日期"
   label="会议日期"
   cover-formatter
   cover-formatter
-  :real-value="(row) => dayjs(row.meetingDate).format('YYYY-MM-DD')"
-/>
+  :real-value="(row) => dayjs(row.meetingDate).format('YYYY-MM-DD')" />
 ```
 ```
 
 
 ## 插槽
 ## 插槽

+ 12 - 15
src/components/ZmTable/ZmTableColumn.vue

@@ -12,6 +12,7 @@ interface Props
   > {
   > {
   prop?: (keyof T & string) | (string & {})
   prop?: (keyof T & string) | (string & {})
   action?: boolean
   action?: boolean
+  visible?: boolean
   hideInColumnSettings?: boolean
   hideInColumnSettings?: boolean
   isParent?: boolean
   isParent?: boolean
   zmSortable?: boolean
   zmSortable?: boolean
@@ -60,12 +61,14 @@ const forwardedSlots = computed(() => {
 
 
 const defaultOptions = ref<Partial<Props>>({
 const defaultOptions = ref<Partial<Props>>({
   align: 'center',
   align: 'center',
-  resizable: true
+  resizable: true,
+  visible: true
 })
 })
 
 
 const bindProps = computed(() => {
 const bindProps = computed(() => {
   const {
   const {
     action,
     action,
+    visible,
     hideInColumnSettings,
     hideInColumnSettings,
     zmSortable,
     zmSortable,
     zmFilterable,
     zmFilterable,
@@ -221,6 +224,7 @@ const calculativeWidth = () => {
   if (hasHeaderAction.value) labelWidth += 8
   if (hasHeaderAction.value) labelWidth += 8
   if (props.zmFilterable) labelWidth += 22
   if (props.zmFilterable) labelWidth += 22
   if (props.zmSortable) labelWidth += 22
   if (props.zmSortable) labelWidth += 22
+
   const maxWidth = Math.min(
   const maxWidth = Math.min(
     Math.max(...values.map((value) => getTextWidth(String(value)) + 38), labelWidth),
     Math.max(...values.map((value) => getTextWidth(String(value)) + 38), labelWidth),
     tableContext.columnMaxWidth.value
     tableContext.columnMaxWidth.value
@@ -259,20 +263,17 @@ watch(
                     : '点击升序'
                     : '点击升序'
               "
               "
               placement="top"
               placement="top"
-              :show-after="500"
-            >
+              :show-after="500">
               <button
               <button
                 type="button"
                 type="button"
                 class="icon-btn"
                 class="icon-btn"
                 :class="{ 'is-active': isSortActive }"
                 :class="{ 'is-active': isSortActive }"
-                @click.stop="handleSortClick"
-              >
+                @click.stop="handleSortClick">
                 <div v-if="currentOrder === 'asc'" class="sort-icon i-lucide:arrow-up-narrow-wide">
                 <div v-if="currentOrder === 'asc'" class="sort-icon i-lucide:arrow-up-narrow-wide">
                 </div>
                 </div>
                 <div
                 <div
                   v-else-if="currentOrder === 'desc'"
                   v-else-if="currentOrder === 'desc'"
-                  class="sort-icon i-lucide:arrow-down-wide-narrow"
-                >
+                  class="sort-icon i-lucide:arrow-down-wide-narrow">
                 </div>
                 </div>
                 <div v-else class="sort-icon i-lucide:arrow-up-down"></div>
                 <div v-else class="sort-icon i-lucide:arrow-up-down"></div>
               </button>
               </button>
@@ -285,15 +286,13 @@ watch(
               :popper-options="{ modifiers: [{ name: 'offset', options: { offset: [16, 18] } }] }"
               :popper-options="{ modifiers: [{ name: 'offset', options: { offset: [16, 18] } }] }"
               trigger="click"
               trigger="click"
               :width="260"
               :width="260"
-              :show-arrow="false"
-            >
+              :show-arrow="false">
               <template #reference>
               <template #reference>
                 <button
                 <button
                   type="button"
                   type="button"
                   class="icon-btn"
                   class="icon-btn"
                   :class="{ 'is-active': isFilterActive }"
                   :class="{ 'is-active': isFilterActive }"
-                  @click.stop="handleFilterReferenceClick"
-                >
+                  @click.stop="handleFilterReferenceClick">
                   <div class="filter-icon i-lucide:list-filter"></div>
                   <div class="filter-icon i-lucide:list-filter"></div>
                 </button>
                 </button>
               </template>
               </template>
@@ -307,8 +306,7 @@ watch(
               trigger="click"
               trigger="click"
               :width="360"
               :width="360"
               :show-arrow="false"
               :show-arrow="false"
-              popper-class="zm-table-column-setting-popper"
-            >
+              popper-class="zm-table-column-setting-popper">
               <template #reference>
               <template #reference>
                 <button type="button" class="icon-btn" title="列设置" @click.stop>
                 <button type="button" class="icon-btn" title="列设置" @click.stop>
                   <div class="setting-icon i-lucide:settings"></div>
                   <div class="setting-icon i-lucide:settings"></div>
@@ -323,8 +321,7 @@ watch(
                     link
                     link
                     type="primary"
                     type="primary"
                     size="small"
                     size="small"
-                    @click="tableContext.resetColumnSettings"
-                  >
+                    @click="tableContext.resetColumnSettings">
                     重置
                     重置
                   </el-button>
                   </el-button>
                 </div>
                 </div>

+ 214 - 21
src/components/ZmTable/index.vue

@@ -10,8 +10,10 @@ import {
   provide,
   provide,
   ref,
   ref,
   useAttrs,
   useAttrs,
-  useSlots
+  useSlots,
+  watch
 } from 'vue'
 } from 'vue'
+import { useRoute } from 'vue-router'
 import { TableContextKey } from './token'
 import { TableContextKey } from './token'
 import type { ColumnAlign, ColumnFixed, ColumnSettingItem } from './token'
 import type { ColumnAlign, ColumnFixed, ColumnSettingItem } from './token'
 import type { DefaultRow } from 'element-plus/es/components/table/src/table/defaults'
 import type { DefaultRow } from 'element-plus/es/components/table/src/table/defaults'
@@ -20,6 +22,7 @@ import type { VNode } from 'vue'
 interface ColumnMeta {
 interface ColumnMeta {
   key: string
   key: string
   label: string
   label: string
+  visible?: boolean
   action: boolean
   action: boolean
   configurable: boolean
   configurable: boolean
   fixed: ColumnFixed
   fixed: ColumnFixed
@@ -34,17 +37,24 @@ interface Props
   loading: boolean
   loading: boolean
   customClass?: boolean
   customClass?: boolean
   showBorder?: boolean
   showBorder?: boolean
+  hoverHighlight?: boolean
   align?: ColumnAlign
   align?: ColumnAlign
   columnMaxWidth?: number
   columnMaxWidth?: number
+  settingsCache?: boolean
+  settingsCacheKey?: string
 }
 }
 
 
 const props = defineProps<Props>()
 const props = defineProps<Props>()
 const attrs = useAttrs()
 const attrs = useAttrs()
 const slots = useSlots()
 const slots = useSlots()
+const route = useRoute()
 const tableRef = ref<TableInstance>()
 const tableRef = ref<TableInstance>()
 const columnSettings = ref<ColumnSettingItem[]>([])
 const columnSettings = ref<ColumnSettingItem[]>([])
 const columnDefaultSettings = ref<ColumnSettingItem[]>([])
 const columnDefaultSettings = ref<ColumnSettingItem[]>([])
 const slotColumnSignature = ref('')
 const slotColumnSignature = ref('')
+const columnSettingsStorageKey = ref('')
+const isSyncingColumnSettings = ref(false)
+const columnSettingsStoragePrefix = 'zm-table:column-settings:'
 
 
 const defaultOptions: Partial<Props> = {
 const defaultOptions: Partial<Props> = {
   size: 'default',
   size: 'default',
@@ -54,6 +64,7 @@ const defaultOptions: Partial<Props> = {
   showOverflowTooltip: true,
   showOverflowTooltip: true,
   scrollbarAlwaysOn: true,
   scrollbarAlwaysOn: true,
   showBorder: false,
   showBorder: false,
+  hoverHighlight: true,
   customClass: false,
   customClass: false,
   tooltipOptions: {
   tooltipOptions: {
     popperClass: 'max-w-120'
     popperClass: 'max-w-120'
@@ -65,8 +76,11 @@ const bindProps = computed(() => {
     data,
     data,
     customClass: _customClass,
     customClass: _customClass,
     showBorder: _showBorder,
     showBorder: _showBorder,
+    hoverHighlight: _hoverHighlight,
     align: _align,
     align: _align,
     columnMaxWidth,
     columnMaxWidth,
+    settingsCache: _settingsCache,
+    settingsCacheKey: _settingsCacheKey,
     ...otherProps
     ...otherProps
   } = props
   } = props
 
 
@@ -89,6 +103,7 @@ const safeColumnMaxWidth = computed(() => {
   const maxWidth = Number(props.columnMaxWidth)
   const maxWidth = Number(props.columnMaxWidth)
   return Number.isFinite(maxWidth) && maxWidth > 0 ? maxWidth : 360
   return Number.isFinite(maxWidth) && maxWidth > 0 ? maxWidth : 360
 })
 })
+const isColumnSettingsCacheEnabled = computed(() => props.settingsCache !== false)
 
 
 const forwardedSlots = computed(() => {
 const forwardedSlots = computed(() => {
   const { default: _default, ...restSlots } = slots
   const { default: _default, ...restSlots } = slots
@@ -163,8 +178,9 @@ const getColumnMeta = (node: VNode, index: number, parentKey?: string): ColumnMe
   return {
   return {
     key,
     key,
     label: getColumnLabel(node, index, parentKey),
     label: getColumnLabel(node, index, parentKey),
+    visible: getPropValue(node.props, 'visible'),
     action,
     action,
-    configurable: !action && !hideInColumnSettings,
+    configurable: !hideInColumnSettings,
     fixed: normalizeFixed(getPropValue(node.props, 'fixed')),
     fixed: normalizeFixed(getPropValue(node.props, 'fixed')),
     children: getColumnChildren(node)
     children: getColumnChildren(node)
       .map((child, childIndex) => getColumnMeta(child, childIndex, key))
       .map((child, childIndex) => getColumnMeta(child, childIndex, key))
@@ -192,8 +208,11 @@ const syncColumnSettings = (nodes: VNode[]) => {
   slotColumnSignature.value = signature
   slotColumnSignature.value = signature
   nextTick(() => {
   nextTick(() => {
     const defaults = metas.map(createDefaultColumnSetting)
     const defaults = metas.map(createDefaultColumnSetting)
+    const storageKey = resolveColumnSettingsStorageKey(defaults)
+    const cachedSettings = loadColumnSettings(storageKey)
     columnDefaultSettings.value = defaults
     columnDefaultSettings.value = defaults
-    columnSettings.value = mergeColumnSettings(defaults, columnSettings.value)
+    columnSettingsStorageKey.value = storageKey
+    setColumnSettings(mergeColumnSettings(defaults, cachedSettings || columnSettings.value))
   })
   })
 }
 }
 
 
@@ -286,9 +305,111 @@ const cloneColumnSettings = (items: ColumnSettingItem[]): ColumnSettingItem[] =>
 }
 }
 
 
 const resetColumnSettings = () => {
 const resetColumnSettings = () => {
-  columnSettings.value = cloneColumnSettings(columnDefaultSettings.value)
+  setColumnSettings(cloneColumnSettings(columnDefaultSettings.value))
 }
 }
 
 
+const flattenColumnSettingKeys = (items: ColumnSettingItem[]): string[] => {
+  return items.flatMap((item) => [
+    item.key,
+    ...(item.children ? flattenColumnSettingKeys(item.children) : [])
+  ])
+}
+
+const encodeStorageKeyPart = (value: string) => {
+  try {
+    return encodeURIComponent(value)
+  } catch {
+    return value
+  }
+}
+
+const resolveColumnSettingsStorageKey = (defaults: ColumnSettingItem[]) => {
+  if (!isColumnSettingsCacheEnabled.value) return ''
+
+  const userKey = props.settingsCacheKey?.trim()
+  if (userKey) return `${columnSettingsStoragePrefix}${encodeStorageKeyPart(userKey)}`
+
+  const routeKey = String(route.name || route.path || 'global')
+  const columnKey = flattenColumnSettingKeys(defaults).sort().join('|')
+  return `${columnSettingsStoragePrefix}${encodeStorageKeyPart(`${routeKey}:${columnKey}`)}`
+}
+
+const isColumnSettingItem = (item: unknown): item is ColumnSettingItem => {
+  if (!item || typeof item !== 'object') return false
+  const setting = item as ColumnSettingItem
+  const validFixed =
+    setting.fixed === false || setting.fixed === 'left' || setting.fixed === 'right'
+  return typeof setting.key === 'string' && typeof setting.visible === 'boolean' && validFixed
+}
+
+const normalizeCachedColumnSettings = (items: unknown): ColumnSettingItem[] => {
+  if (!Array.isArray(items)) return []
+
+  return items.filter(isColumnSettingItem).map((item) => ({
+    key: item.key,
+    label: typeof item.label === 'string' ? item.label : item.key,
+    visible: item.visible,
+    fixed: item.fixed,
+    children: item.children ? normalizeCachedColumnSettings(item.children) : undefined
+  }))
+}
+
+const loadColumnSettings = (storageKey: string) => {
+  if (!storageKey || typeof window === 'undefined') return null
+
+  try {
+    const rawValue = window.localStorage.getItem(storageKey)
+    if (!rawValue) return null
+
+    const cachedValue = JSON.parse(rawValue)
+    const settings = normalizeCachedColumnSettings(cachedValue?.settings)
+    return settings.length ? settings : null
+  } catch {
+    return null
+  }
+}
+
+const saveColumnSettings = (storageKey: string, settings: ColumnSettingItem[]) => {
+  if (!storageKey || typeof window === 'undefined') return
+
+  try {
+    window.localStorage.setItem(
+      storageKey,
+      JSON.stringify({
+        version: 1,
+        settings
+      })
+    )
+  } catch {
+    // localStorage 可能被浏览器策略禁用,失败时不影响表格正常使用。
+  }
+}
+
+const setColumnSettings = (settings: ColumnSettingItem[]) => {
+  isSyncingColumnSettings.value = true
+  columnSettings.value = settings
+  nextTick(() => {
+    isSyncingColumnSettings.value = false
+    saveColumnSettings(columnSettingsStorageKey.value, columnSettings.value)
+  })
+}
+
+watch(
+  columnSettings,
+  (settings) => {
+    if (isSyncingColumnSettings.value) return
+    saveColumnSettings(columnSettingsStorageKey.value, settings)
+  },
+  { deep: true }
+)
+
+watch(
+  [() => props.settingsCache, () => props.settingsCacheKey, () => route.name, () => route.path],
+  () => {
+    slotColumnSignature.value = ''
+  }
+)
+
 const applyColumnSetting = (node: VNode, setting: ColumnSettingItem) => {
 const applyColumnSetting = (node: VNode, setting: ColumnSettingItem) => {
   const clonedNode = cloneVNode(
   const clonedNode = cloneVNode(
     node,
     node,
@@ -315,10 +436,17 @@ const applyColumnSetting = (node: VNode, setting: ColumnSettingItem) => {
   } as VNode
   } as VNode
 }
 }
 
 
-const isColumnVisible = (setting: ColumnSettingItem) => {
-  if (setting.visible === false) return false
-  if (!setting.children?.length) return true
-  return setting.children.some(isColumnVisible)
+const getSettingVisible = (meta: ColumnMeta, setting?: ColumnSettingItem) => {
+  return meta.visible ?? setting?.visible ?? true
+}
+
+const isColumnVisible = (meta: ColumnMeta, setting?: ColumnSettingItem) => {
+  if (!getSettingVisible(meta, setting)) return false
+  if (!meta.children.length) return true
+
+  const childrenSettings = setting?.children || []
+  const childSettingMap = new Map(childrenSettings.map((item) => [item.key, item]))
+  return meta.children.some((child) => isColumnVisible(child, childSettingMap.get(child.key)))
 }
 }
 
 
 const renderColumnNodes = (nodes: VNode[], settings: ColumnSettingItem[], parentKey?: string) => {
 const renderColumnNodes = (nodes: VNode[], settings: ColumnSettingItem[], parentKey?: string) => {
@@ -328,7 +456,7 @@ const renderColumnNodes = (nodes: VNode[], settings: ColumnSettingItem[], parent
     .map((node, index) => ({ node, meta: getColumnMeta(node, index, parentKey) }))
     .map((node, index) => ({ node, meta: getColumnMeta(node, index, parentKey) }))
     .filter(({ meta }) => {
     .filter(({ meta }) => {
       const setting = settingMap.get(meta.key)
       const setting = settingMap.get(meta.key)
-      return meta.configurable && setting && isColumnVisible(setting)
+      return meta.configurable && setting && isColumnVisible(meta, setting)
     })
     })
     .sort((a, b) => {
     .sort((a, b) => {
       const orderA = orderMap.get(a.meta.key) ?? Number.MAX_SAFE_INTEGER
       const orderA = orderMap.get(a.meta.key) ?? Number.MAX_SAFE_INTEGER
@@ -339,13 +467,19 @@ const renderColumnNodes = (nodes: VNode[], settings: ColumnSettingItem[], parent
   return nodes.flatMap((node, index) => {
   return nodes.flatMap((node, index) => {
     const meta = getColumnMeta(node, index, parentKey)
     const meta = getColumnMeta(node, index, parentKey)
 
 
+    const setting = settingMap.get(meta.key)
+
+    if (!isColumnVisible(meta, setting)) return []
+
     if (!meta.configurable) return [node]
     if (!meta.configurable) return [node]
 
 
     const nextColumn = sortedConfigurableNodes.shift()
     const nextColumn = sortedConfigurableNodes.shift()
     if (!nextColumn) return []
     if (!nextColumn) return []
 
 
-    const setting = settingMap.get(nextColumn.meta.key)
-    return setting ? [applyColumnSetting(nextColumn.node, setting)] : [nextColumn.node]
+    const nextColumnSetting = settingMap.get(nextColumn.meta.key)
+    return nextColumnSetting
+      ? [applyColumnSetting(nextColumn.node, nextColumnSetting)]
+      : [nextColumn.node]
   })
   })
 }
 }
 
 
@@ -378,10 +512,13 @@ defineExpose({
   <el-table
   <el-table
     ref="tableRef"
     ref="tableRef"
     v-loading="loading"
     v-loading="loading"
-    :class="{ 'zm-table': !customClass, 'show-border': showBorder }"
+    :class="{
+      'zm-table': !customClass,
+      'show-border': showBorder,
+      'is-hover-highlight-disabled': hoverHighlight === false
+    }"
     v-bind="bindProps"
     v-bind="bindProps"
-    :data="data"
-  >
+    :data="data">
     <template v-for="(_, name) in forwardedSlots" #[name]="slotData">
     <template v-for="(_, name) in forwardedSlots" #[name]="slotData">
       <slot :name="name" v-bind="slotData || {}"></slot>
       <slot :name="name" v-bind="slotData || {}"></slot>
     </template>
     </template>
@@ -419,6 +556,10 @@ defineExpose({
   --zm-table-stripe-bg: #fcfdff;
   --zm-table-stripe-bg: #fcfdff;
   --zm-table-hover-bg: #f5f9ff;
   --zm-table-hover-bg: #f5f9ff;
   --zm-table-current-bg: #eef6ff;
   --zm-table-current-bg: #eef6ff;
+  --zm-table-summary-bg: #f7f9fc;
+  --zm-table-summary-text-color: var(--zm-table-strong-text-color);
+  --zm-table-summary-font-weight: 600;
+  --zm-table-summary-border-color: var(--zm-table-border-color);
   --zm-table-cell-height: 38px;
   --zm-table-cell-height: 38px;
   --zm-table-cell-padding-x: 8px;
   --zm-table-cell-padding-x: 8px;
   --zm-table-cell-first-padding-left: 0px;
   --zm-table-cell-first-padding-left: 0px;
@@ -545,13 +686,6 @@ defineExpose({
       }
       }
     }
     }
 
 
-    tr:hover,
-    tr.hover-row {
-      .el-table__cell {
-        background: var(--zm-table-hover-bg) !important;
-      }
-    }
-
     tr.current-row {
     tr.current-row {
       .el-table__cell {
       .el-table__cell {
         // color: var(--el-color-primary);
         // color: var(--el-color-primary);
@@ -560,6 +694,17 @@ defineExpose({
     }
     }
   }
   }
 
 
+  &:not(.is-hover-highlight-disabled) {
+    .el-table__body {
+      tr:hover,
+      tr.hover-row {
+        .el-table__cell {
+          background: var(--zm-table-hover-bg) !important;
+        }
+      }
+    }
+  }
+
   .el-table__row {
   .el-table__row {
     .el-table__cell {
     .el-table__cell {
       font-weight: var(--zm-table-row-font-weight);
       font-weight: var(--zm-table-row-font-weight);
@@ -580,6 +725,8 @@ defineExpose({
   }
   }
 
 
   .el-table__empty-block {
   .el-table__empty-block {
+    width: 100% !important;
+    min-width: 100%;
     min-height: var(--zm-table-empty-min-height);
     min-height: var(--zm-table-empty-min-height);
     background: var(--zm-table-empty-bg);
     background: var(--zm-table-empty-bg);
   }
   }
@@ -589,6 +736,52 @@ defineExpose({
     color: var(--zm-table-empty-text-color);
     color: var(--zm-table-empty-text-color);
   }
   }
 
 
+  .el-table__footer-wrapper {
+    background: var(--zm-table-summary-bg);
+    border-top: 1px solid var(--zm-table-summary-border-color);
+  }
+
+  .el-table__footer {
+    color: var(--zm-table-summary-text-color);
+
+    .el-table__cell {
+      height: var(--zm-table-cell-height);
+      font-weight: var(--zm-table-summary-font-weight);
+      color: var(--zm-table-summary-text-color);
+      background: var(--zm-table-summary-bg) !important;
+      border-right: 1px solid var(--zm-table-row-border-color) !important;
+      border-bottom: none !important;
+
+      .cell {
+        display: flex;
+        min-height: 100%;
+        align-items: center;
+        justify-content: center;
+      }
+
+      &:last-child {
+        border-right: none !important;
+      }
+    }
+
+    tr:last-child {
+      .el-table__cell {
+        &:first-child {
+          border-bottom-left-radius: var(--zm-table-radius);
+        }
+
+        &:last-child {
+          border-bottom-right-radius: var(--zm-table-radius);
+        }
+      }
+    }
+
+    .el-table__cell.el-table-fixed-column--left.is-last-column,
+    .el-table__cell.el-table-fixed-column--right.is-first-column {
+      box-shadow: none;
+    }
+  }
+
   .el-table__cell.el-table-fixed-column--left,
   .el-table__cell.el-table-fixed-column--left,
   .el-table__cell.el-table-fixed-column--right {
   .el-table__cell.el-table-fixed-column--right {
     background: inherit;
     background: inherit;

+ 57 - 15
src/components/custom-components/kv-vue/index.vue

@@ -1,14 +1,22 @@
 <template>
 <template>
-  <table class="w-1/1 h-1/1 kvTable">
-    <tbody>
-      <tr>
-        <td class="kvKey kvKeyValue" colspan="1">{{ props.label }}</td>
-        <td class="kvValue kvKeyValue" colspan="1">{{ props.value }}</td>
-      </tr>
-    </tbody>
-  </table>
+  <div class="kvBox" :style="kvBoxStyle">
+    <table class="w-1/1 h-1/1 kvTable">
+      <colgroup>
+        <col class="kvKeyCol" />
+        <col class="kvValueCol" />
+      </colgroup>
+      <tbody>
+        <tr>
+          <td class="kvKey kvKeyValue" colspan="1">{{ props.label }}</td>
+          <td class="kvValue kvKeyValue" colspan="1">{{ props.value }}</td>
+        </tr>
+      </tbody>
+    </table>
+  </div>
 </template>
 </template>
 <script setup lang="ts">
 <script setup lang="ts">
+import { computed } from 'vue'
+
 const props = defineProps({
 const props = defineProps({
   fontFamily: {
   fontFamily: {
     type: String,
     type: String,
@@ -18,6 +26,10 @@ const props = defineProps({
     type: Number,
     type: Number,
     default: 15
     default: 15
   },
   },
+  textAlign: {
+    type: String,
+    default: 'left'
+  },
   label: {
   label: {
     type: String,
     type: String,
     default: ''
     default: ''
@@ -42,29 +54,59 @@ const props = defineProps({
     type: Boolean,
     type: Boolean,
     default: true
     default: true
   },
   },
+  backgroundColor: {
+    type: String,
+    default: 'rgba(255, 255, 255, 0)'
+  },
   borderColor: {
   borderColor: {
     type: String,
     type: String,
     default: ''
     default: ''
+  },
+  borderRadius: {
+    type: Number,
+    default: 0
   }
   }
 })
 })
+
+const kvBoxStyle = computed(() => ({
+  backgroundColor: props.backgroundColor,
+  border: `${props.border ? 1 : 0}px solid ${props.borderColor}`,
+  borderRadius: `${props.borderRadius}px`
+}))
 </script>
 </script>
-<style scroped>
+<style scoped>
+.kvBox {
+  width: 100%;
+  height: 100%;
+  overflow: hidden;
+  box-sizing: border-box;
+}
+
 .kvTable {
 .kvTable {
-  border: v-bind('`${props.border?1:0}px solid ${props.borderColor}`');
+  border-collapse: separate;
+  border-spacing: 0;
+  table-layout: fixed;
+}
+
+.kvKeyCol {
+  width: v-bind('`${props.labelWidth}px`');
+}
+
+.kvValueCol {
+  width: v-bind('`${props.valueWidth}px`');
 }
 }
 
 
 .kvKeyValue {
 .kvKeyValue {
+  overflow: hidden;
   font-family: v-bind('`${props.fontFamily}`');
   font-family: v-bind('`${props.fontFamily}`');
   font-size: v-bind('`${props.fontSize}px`');
   font-size: v-bind('`${props.fontSize}px`');
   color: v-bind('`${props.color}`');
   color: v-bind('`${props.color}`');
+  text-align: v-bind('`${props.textAlign}`');
+  text-overflow: ellipsis;
+  white-space: nowrap;
 }
 }
 
 
 .kvKey {
 .kvKey {
-  width: v-bind('`${props.labelWidth}px`');
   border-right: v-bind('`${props.border?1:0}px`') solid v-bind('`${props.borderColor}`') !important;
   border-right: v-bind('`${props.border?1:0}px`') solid v-bind('`${props.borderColor}`') !important;
 }
 }
-
-.kvValue {
-  width: v-bind('`${props.valueWidth}px`');
-}
 </style>
 </style>

+ 107 - 23
src/components/custom-components/sys-button-vue/index.vue

@@ -1,12 +1,12 @@
 <template>
 <template>
-  <div style="width: 100%; height: 100%">
-    <button class="w-1/1 h-1/1" :style="getStyle(props.type, props.round)">
-      <el-text>{{ props.text }}</el-text>
+  <div class="sys-button-wrapper">
+    <button class="sys-button" :style="buttonStyle">
+      <span class="sys-button-text">{{ props.text }}</span>
     </button>
     </button>
   </div>
   </div>
 </template>
 </template>
 <script setup lang="ts">
 <script setup lang="ts">
-import { ElText } from 'element-plus'
+import { computed } from 'vue'
 import type { PropType } from 'vue'
 import type { PropType } from 'vue'
 type ButtonType = '' | 'default' | 'success' | 'warning' | 'info' | 'primary' | 'danger'
 type ButtonType = '' | 'default' | 'success' | 'warning' | 'info' | 'primary' | 'danger'
 const props = defineProps({
 const props = defineProps({
@@ -21,28 +21,112 @@ const props = defineProps({
   round: {
   round: {
     type: Boolean,
     type: Boolean,
     default: false
     default: false
+  },
+  backgroundColor: {
+    type: String,
+    default: ''
+  },
+  borderColor: {
+    type: String,
+    default: ''
+  },
+  fontColor: {
+    type: String,
+    default: ''
+  },
+  fontFamily: {
+    type: String,
+    default: ''
+  },
+  fontSize: {
+    type: Number,
+    default: 14
+  },
+  borderRadius: {
+    type: Number,
+    default: 4
   }
   }
 })
 })
-const getStyle = (type: ButtonType, round: boolean) => {
-  let bg_color = ''
-  let border_radius = '4px'
-  if (type == 'primary') {
-    bg_color = '#409eff'
-  } else if (type == 'success') {
-    bg_color = '#67c23a'
-  } else if (type == 'warning') {
-    bg_color = '#e6a23c'
-  } else if (type == 'danger') {
-    bg_color = '#f56c6c'
-  } else if (type == 'info') {
-    bg_color = '#909399'
-  }
-  if (round) {
-    border_radius = '20px'
+
+const buttonTypeStyleMap: Record<
+  ButtonType,
+  { background: string; border: string; color: string }
+> = {
+  '': {
+    background: '#ffffff',
+    border: '#dcdfe6',
+    color: '#606266'
+  },
+  default: {
+    background: '#ffffff',
+    border: '#dcdfe6',
+    color: '#606266'
+  },
+  primary: {
+    background: '#409eff',
+    border: '#409eff',
+    color: '#ffffff'
+  },
+  success: {
+    background: '#67c23a',
+    border: '#67c23a',
+    color: '#ffffff'
+  },
+  warning: {
+    background: '#e6a23c',
+    border: '#e6a23c',
+    color: '#ffffff'
+  },
+  danger: {
+    background: '#f56c6c',
+    border: '#f56c6c',
+    color: '#ffffff'
+  },
+  info: {
+    background: '#909399',
+    border: '#909399',
+    color: '#ffffff'
   }
   }
+}
+
+const buttonStyle = computed(() => {
+  const typeStyle = buttonTypeStyleMap[props.type] || buttonTypeStyleMap.default
+  const borderRadius = props.round ? 20 : props.borderRadius
   return {
   return {
-    backgroundColor: bg_color,
-    borderRadius: border_radius
+    backgroundColor: props.backgroundColor || typeStyle.background,
+    borderColor: props.borderColor || typeStyle.border,
+    color: props.fontColor || typeStyle.color,
+    fontFamily: props.fontFamily || undefined,
+    fontSize: `${props.fontSize}px`,
+    borderRadius: `${borderRadius}px`
   }
   }
-}
+})
 </script>
 </script>
+<style scoped>
+.sys-button-wrapper {
+  width: 100%;
+  height: 100%;
+}
+
+.sys-button {
+  width: 100%;
+  height: 100%;
+  padding: 0 12px;
+  overflow: hidden;
+  line-height: 1;
+  cursor: pointer;
+  border: 1px solid;
+  transition:
+    background-color 0.2s,
+    border-color 0.2s,
+    color 0.2s;
+}
+
+.sys-button-text {
+  display: block;
+  overflow: hidden;
+  color: inherit;
+  text-overflow: ellipsis;
+  white-space: nowrap;
+}
+</style>

+ 6 - 1
src/components/custom-components/text-vue/index.vue

@@ -6,7 +6,8 @@
       fontFamily: props.fontFamily,
       fontFamily: props.fontFamily,
       fontSize: props.fontSize + 'px',
       fontSize: props.fontSize + 'px',
       color: props.fill,
       color: props.fill,
-      fontWeight: props.bold ? 'bold' : 'normal'
+      fontWeight: props.bold ? 'bold' : 'normal',
+      textAlign: props.textAlign
     }">
     }">
     {{ props.text }}
     {{ props.text }}
   </p>
   </p>
@@ -36,6 +37,10 @@ const props = defineProps({
   bold: {
   bold: {
     type: Boolean,
     type: Boolean,
     default: false
     default: false
+  },
+  textAlign: {
+    type: String,
+    default: 'left'
   }
   }
 })
 })
 </script>
 </script>

+ 49 - 0
src/components/custom-components/wireframe-vue/index.vue

@@ -0,0 +1,49 @@
+<template>
+  <div class="wireframe" :style="wireframeStyle"></div>
+</template>
+
+<script setup lang="ts">
+import { computed } from 'vue'
+import type { PropType } from 'vue'
+
+type BorderStyle = 'solid' | 'dashed' | 'dotted' | 'double' | 'none'
+
+const props = defineProps({
+  fill: {
+    type: String,
+    default: 'rgba(255, 255, 255, 0)'
+  },
+  borderColor: {
+    type: String,
+    default: '#409eff'
+  },
+  borderStyle: {
+    type: String as PropType<BorderStyle>,
+    default: 'solid'
+  },
+  borderWidth: {
+    type: Number,
+    default: 2
+  },
+  borderRadius: {
+    type: Number,
+    default: 0
+  }
+})
+
+const wireframeStyle = computed(() => ({
+  backgroundColor: props.fill,
+  borderColor: props.borderColor,
+  borderStyle: props.borderStyle,
+  borderWidth: `${props.borderWidth}px`,
+  borderRadius: `${props.borderRadius}px`
+}))
+</script>
+
+<style scoped>
+.wireframe {
+  width: 100%;
+  height: 100%;
+  box-sizing: border-box;
+}
+</style>

+ 3 - 0
src/components/mt-dzr/index.vue

@@ -142,6 +142,9 @@ const mt_dzr_vmodel = computed({
 })
 })
 const onMouseDown = (de: MouseTouchEvent) => {
 const onMouseDown = (de: MouseTouchEvent) => {
   MtDzrEmits('mousedown', de)
   MtDzrEmits('mousedown', de)
+  if (de.defaultPrevented) {
+    return
+  }
   if (MtDzrProps.lock || MtDzrProps.disabled) {
   if (MtDzrProps.lock || MtDzrProps.disabled) {
     return
     return
   }
   }

+ 48 - 7
src/components/mt-edit/components/done-tree/index.vue

@@ -9,13 +9,37 @@
     node-key="id"
     node-key="id"
     :current-node-key="current_node_key">
     :current-node-key="current_node_key">
     <template #default="{ node, data }">
     <template #default="{ node, data }">
-      <div class="flex justify-between w-8/10">
+      <div class="flex justify-between w-9/10">
         <div>{{ node.label }}</div>
         <div>{{ node.label }}</div>
-        <el-button text circle size="small" class="mr-10px">
-          <el-icon :title="data.hide ? '隐藏' : '显示'" :size="20" @click.stop="changeHide(data)">
-            <svg-analysis :name="data.hide ? 'view-hide' : 'view-show'" />
-          </el-icon>
-        </el-button>
+        <div class="flex items-center">
+          <el-button
+            text
+            circle
+            size="small"
+            :disabled="!canMoveUp(data)"
+            title="上移一层"
+            @click.stop="moveLayer(data, 'up')">
+            <el-icon :size="16">
+              <ArrowUp />
+            </el-icon>
+          </el-button>
+          <el-button
+            text
+            circle
+            size="small"
+            :disabled="!canMoveDown(data)"
+            title="下移一层"
+            @click.stop="moveLayer(data, 'down')">
+            <el-icon :size="16">
+              <ArrowDown />
+            </el-icon>
+          </el-button>
+          <el-button text circle size="small" class="mr-10px">
+            <el-icon :title="data.hide ? '隐藏' : '显示'" :size="20" @click.stop="changeHide(data)">
+              <svg-analysis :name="data.hide ? 'view-hide' : 'view-show'" />
+            </el-icon>
+          </el-button>
+        </div>
       </div>
       </div>
     </template>
     </template>
   </el-tree>
   </el-tree>
@@ -23,6 +47,7 @@
 
 
 <script lang="ts" setup>
 <script lang="ts" setup>
 import { ElTree, ElButton, ElIcon } from 'element-plus'
 import { ElTree, ElButton, ElIcon } from 'element-plus'
+import { ArrowDown, ArrowUp } from '@element-plus/icons-vue'
 import { computed } from 'vue'
 import { computed } from 'vue'
 import type { IDoneJson } from '@/components/mt-edit/store/types'
 import type { IDoneJson } from '@/components/mt-edit/store/types'
 import SvgAnalysis from '@/components/mt-edit/components/svg-analysis/index.vue'
 import SvgAnalysis from '@/components/mt-edit/components/svg-analysis/index.vue'
@@ -32,7 +57,7 @@ type DoneTree = {
 }
 }
 const doneTreeProps = withDefaults(defineProps<DoneTree>(), {})
 const doneTreeProps = withDefaults(defineProps<DoneTree>(), {})
 
 
-const emits = defineEmits(['updateSelectedItemsId', 'updateSelectedIdHide'])
+const emits = defineEmits(['updateSelectedItemsId', 'updateSelectedIdHide', 'moveLayer'])
 
 
 const current_node_key = computed(() => {
 const current_node_key = computed(() => {
   return doneTreeProps.selectedItemsId.length == 1 ? doneTreeProps.selectedItemsId[0] : ''
   return doneTreeProps.selectedItemsId.length == 1 ? doneTreeProps.selectedItemsId[0] : ''
@@ -43,6 +68,22 @@ const handleNodeClick = (data: IDoneJson) => {
 const changeHide = (data: IDoneJson) => {
 const changeHide = (data: IDoneJson) => {
   emits('updateSelectedIdHide', data.id)
   emits('updateSelectedIdHide', data.id)
 }
 }
+const getLayerIndex = (data: IDoneJson) => {
+  return doneTreeProps.doneJson.findIndex((item) => item.id === data.id)
+}
+const canMoveUp = (data: IDoneJson) => {
+  const index = getLayerIndex(data)
+  return index >= 0 && index < doneTreeProps.doneJson.length - 1
+}
+const canMoveDown = (data: IDoneJson) => {
+  return getLayerIndex(data) > 0
+}
+const moveLayer = (data: IDoneJson, direction: 'up' | 'down') => {
+  emits('moveLayer', {
+    id: data.id,
+    direction
+  })
+}
 const defaultProps = {
 const defaultProps = {
   children: 'nochildren',
   children: 'nochildren',
   label: 'title'
   label: 'title'

Kaikkia tiedostoja ei voida näyttää, sillä liian monta tiedostoa muuttui tässä diffissä