Browse Source

制氮、制浆、硐室

hongrunxia 1 year ago
parent
commit
8fdb7f6a8f
68 changed files with 5583 additions and 2 deletions
  1. BIN
      public/model/glft/cf/dsgd.glb
  2. BIN
      public/model/glft/cf/dsmove.glb
  3. BIN
      public/model/glft/fire/grout.glb
  4. BIN
      public/model/glft/fire/nitrogen.glb
  5. BIN
      public/model/glft/yafeng/compressor.glb
  6. 1 0
      src/assets/icons/alarm-CO.svg
  7. 3 0
      src/assets/icons/alarm-fire.svg
  8. 1 0
      src/assets/icons/alarm-smoke.svg
  9. 3 0
      src/assets/icons/alarm-temperature.svg
  10. 96 0
      src/assets/icons/device-group-paramer.svg
  11. 14 0
      src/assets/icons/device-paramer.svg
  12. 12 0
      src/assets/icons/dust-nd.svg
  13. 6 0
      src/assets/icons/expansion.svg
  14. 5 0
      src/assets/icons/hd-wd.svg
  15. 6 0
      src/assets/icons/kg.svg
  16. 6 0
      src/assets/icons/max-temperature.svg
  17. 6 0
      src/assets/icons/put-away.svg
  18. 3 0
      src/assets/icons/pw-sy.svg
  19. 2 0
      src/assets/icons/pw-zz.svg
  20. 3 0
      src/assets/icons/smoke-temperature.svg
  21. 3 0
      src/assets/icons/warning-CO.svg
  22. 3 0
      src/assets/icons/warning-fire.svg
  23. 3 0
      src/assets/icons/warning-smoke.svg
  24. 3 0
      src/assets/icons/warning-temperature.svg
  25. BIN
      src/assets/images/vent/border/box1-bottom.png
  26. BIN
      src/assets/images/vent/border/box1-top.png
  27. BIN
      src/assets/images/vent/control-switch-bg.png
  28. BIN
      src/assets/images/vent/home/tosmall.png
  29. BIN
      src/assets/images/vent/home/tree-bg.png
  30. BIN
      src/assets/images/vent/home/tree-icon-bg.png
  31. BIN
      src/assets/images/vent/home/tree-icon-hover-bg.png
  32. BIN
      src/assets/images/vent/model_image/dust-monitor-bg.png
  33. 11 0
      src/design/vent/index.less
  34. 91 0
      src/views/vent/comment/components/bottomMenu.vue
  35. 139 0
      src/views/vent/comment/components/customHeader.vue
  36. 25 0
      src/views/vent/monitorManager/chamberMonitor/components/chamberAlarmHistory.vue
  37. 25 0
      src/views/vent/monitorManager/chamberMonitor/components/chamberHandleHistory.vue
  38. 28 0
      src/views/vent/monitorManager/chamberMonitor/components/chamberHistory.vue
  39. 416 0
      src/views/vent/monitorManager/chamberMonitor/components/chamberHome.vue
  40. 17 0
      src/views/vent/monitorManager/compressor/components/nitrogenAlarmHistory.vue
  41. 246 0
      src/views/vent/monitorManager/compressor/components/nitrogenEcharts.vue
  42. 16 0
      src/views/vent/monitorManager/compressor/components/nitrogenHandleHistory.vue
  43. 16 0
      src/views/vent/monitorManager/compressor/components/nitrogenHistory.vue
  44. 642 0
      src/views/vent/monitorManager/compressor/components/nitrogenHome.vue
  45. 109 0
      src/views/vent/monitorManager/compressor/index.vue
  46. 17 0
      src/views/vent/monitorManager/compressor/nitrogen.api.ts
  47. 72 0
      src/views/vent/monitorManager/compressor/nitrogen.data.ts
  48. 239 0
      src/views/vent/monitorManager/compressor/nitrogen.dishang.threejs.ts
  49. 110 0
      src/views/vent/monitorManager/compressor/nitrogen.threejs.ts
  50. 25 0
      src/views/vent/monitorManager/groutMonitor/components/groutAlarmHistory.vue
  51. 25 0
      src/views/vent/monitorManager/groutMonitor/components/groutHandleHistory.vue
  52. 28 0
      src/views/vent/monitorManager/groutMonitor/components/groutHistory.vue
  53. 447 0
      src/views/vent/monitorManager/groutMonitor/components/groutHome.vue
  54. 40 0
      src/views/vent/monitorManager/groutMonitor/components/runParameter.modal.vue
  55. 40 0
      src/views/vent/monitorManager/groutMonitor/components/warningParameter.modal.vue
  56. 18 0
      src/views/vent/monitorManager/groutMonitor/grout.api.ts
  57. 63 0
      src/views/vent/monitorManager/groutMonitor/grout.data.ts
  58. 67 0
      src/views/vent/monitorManager/groutMonitor/grout.threejs.base.ts
  59. 116 0
      src/views/vent/monitorManager/groutMonitor/grout.threejs.ts
  60. 233 0
      src/views/vent/monitorManager/groutMonitor/index.vue
  61. 18 0
      src/views/vent/monitorManager/wokerFaceMonitor/fiber.api.ts
  62. 373 0
      src/views/vent/monitorManager/wokerFaceMonitor/fiber.belt.threejs.ts
  63. 449 0
      src/views/vent/monitorManager/wokerFaceMonitor/fiber.data.ts
  64. 371 0
      src/views/vent/monitorManager/wokerFaceMonitor/fiber.ds.threejs.ts
  65. 168 0
      src/views/vent/monitorManager/wokerFaceMonitor/fiber.threejs.ts
  66. 415 0
      src/views/vent/monitorManager/wokerFaceMonitor/fiber.workFace.threejs.ts
  67. 289 0
      src/views/vent/monitorManager/wokerFaceMonitor/index.vue
  68. 0 2
      src/views/vent/performance/comment/DeviceModal.vue

BIN
public/model/glft/cf/dsgd.glb


BIN
public/model/glft/cf/dsmove.glb


BIN
public/model/glft/fire/grout.glb


BIN
public/model/glft/fire/nitrogen.glb


BIN
public/model/glft/yafeng/compressor.glb


File diff suppressed because it is too large
+ 1 - 0
src/assets/icons/alarm-CO.svg


+ 3 - 0
src/assets/icons/alarm-fire.svg

@@ -0,0 +1,3 @@
+<svg xmlns="http://www.w3.org/2000/svg" width="43.789" height="43.832" viewBox="0 0 43.789 43.832">
+  <path id="路径_55193" data-name="路径 55193" d="M625.272,347.156a1.364,1.364,0,0,0-1.677-.966l-5.954,1.6a1.37,1.37,0,0,0,.708,2.647h0l5.954-1.6A1.37,1.37,0,0,0,625.272,347.156Zm-36.064.633-5.954-1.6a1.368,1.368,0,1,0-.708,2.643l5.954,1.6a1.368,1.368,0,1,0,.708-2.643Zm28.6-12.4a1.368,1.368,0,0,0-1.869.5l-3.081,5.336a1.368,1.368,0,1,0,2.369,1.368l3.081-5.336A1.369,1.369,0,0,0,617.805,335.385ZM594,341.226l-3.084-5.336a1.368,1.368,0,1,0-2.369,1.368l3.081,5.336A1.37,1.37,0,0,0,594,341.226Zm9.43-10.806a1.37,1.37,0,0,0-1.368,1.368v6.165a1.365,1.365,0,0,0,1.368,1.368h0a1.364,1.364,0,0,0,1.364-1.368v-6.165A1.367,1.367,0,0,0,603.427,330.42Zm14.265,41.893a2.717,2.717,0,0,0-1.388-.747.676.676,0,0,1-.551-.665V355.171A12.327,12.327,0,1,0,591.1,354.9c0,.055,0,.113,0,.172v15.829a.685.685,0,0,1-.555.665,2.738,2.738,0,0,0-2.185,2.682h30.134A2.753,2.753,0,0,0,617.692,372.313Zm-12.733-5.055s2.17-3.041,0-4.781-1.74-3.472-1.74-3.472c-5.544,3.409-1.462,8.073-1.306,8.249-8.687-3.906-4.343-9.121-2.17-10.856s2.608-6.517,2.608-6.517c4.343,1.74,4.343,6.951,4.343,6.951l1.3-1.3C615.386,364.655,604.959,367.258,604.959,367.258Z" transform="translate(-581.53 -330.42)" fill="#ff3823"/>
+</svg>

File diff suppressed because it is too large
+ 1 - 0
src/assets/icons/alarm-smoke.svg


+ 3 - 0
src/assets/icons/alarm-temperature.svg

@@ -0,0 +1,3 @@
+<svg xmlns="http://www.w3.org/2000/svg" width="43.789" height="43.832" viewBox="0 0 43.789 43.832">
+  <path id="路径_55193" data-name="路径 55193" d="M449.692,372.313a2.717,2.717,0,0,0-1.388-.747.675.675,0,0,1-.551-.665V355.171A12.327,12.327,0,1,0,423.1,354.9c0,.055,0,.113,0,.172v15.829a.685.685,0,0,1-.555.665,2.738,2.738,0,0,0-2.185,2.682h30.134A2.753,2.753,0,0,0,449.692,372.313ZM435.7,369.076a5.9,5.9,0,0,1-6.036-5.77,5.691,5.691,0,0,1,2.033-4.347.849.849,0,0,0,.289-.653v-5.958a3.58,3.58,0,0,1,7.158,0v6.134a.542.542,0,0,0,.211.414,5.686,5.686,0,0,1,2.115,4.41A5.907,5.907,0,0,1,435.7,369.076Zm2.025-9.1-.289-.176a.314.314,0,0,1-.149-.266v-7.189a1.721,1.721,0,0,0-3.44,0v7.189a.316.316,0,0,1-.149.27l-.285.176a3.9,3.9,0,0,0-1.892,3.323,4.046,4.046,0,0,0,8.089,0A3.9,3.9,0,0,0,437.725,359.979Zm-2.224,5.547a2.158,2.158,0,0,1-2.1-2.221,2.108,2.108,0,0,1,1.548-2.009v-5.442a.577.577,0,0,1,.579-.579h.047a.6.6,0,0,1,.6.6V361.3a2.16,2.16,0,0,1-.676,4.23Zm-.074-35.107a1.37,1.37,0,0,0-1.368,1.368v6.165a1.365,1.365,0,0,0,1.368,1.368h0a1.364,1.364,0,0,0,1.364-1.368v-6.165A1.367,1.367,0,0,0,435.427,330.42ZM426,341.226l-3.085-5.336a1.368,1.368,0,1,0-2.369,1.368l3.081,5.336A1.37,1.37,0,0,0,426,341.226Zm23.808-5.841a1.368,1.368,0,0,0-1.869.5l-3.081,5.336a1.368,1.368,0,1,0,2.369,1.368l3.081-5.336A1.369,1.369,0,0,0,449.806,335.385Zm-28.6,12.4-5.954-1.6a1.368,1.368,0,1,0-.708,2.643l5.954,1.6a1.368,1.368,0,1,0,.708-2.643Zm36.064-.633a1.364,1.364,0,0,0-1.677-.966l-5.954,1.6a1.37,1.37,0,0,0,.708,2.647h0l5.954-1.6A1.37,1.37,0,0,0,457.273,347.156Z" transform="translate(-413.53 -330.42)" fill="#ff3823"/>
+</svg>

+ 96 - 0
src/assets/icons/device-group-paramer.svg

@@ -0,0 +1,96 @@
+<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="36.056" height="37.502" viewBox="0 0 36.056 37.502">
+  <defs>
+    <filter id="路径_55417" x="0.964" y="0" width="25.447" height="32.35" filterUnits="userSpaceOnUse">
+      <feOffset input="SourceAlpha"/>
+      <feGaussianBlur stdDeviation="4" result="blur"/>
+      <feFlood flood-color="#006eff"/>
+      <feComposite operator="in" in2="blur"/>
+      <feComposite in="SourceGraphic"/>
+    </filter>
+    <filter id="路径_55418" x="0.964" y="10.235" width="25.447" height="27.267" filterUnits="userSpaceOnUse">
+      <feOffset input="SourceAlpha"/>
+      <feGaussianBlur stdDeviation="4" result="blur-2"/>
+      <feFlood flood-color="#006eff"/>
+      <feComposite operator="in" in2="blur-2"/>
+      <feComposite in="SourceGraphic"/>
+    </filter>
+    <filter id="路径_55419" x="12.645" y="3" width="19.447" height="26.292" filterUnits="userSpaceOnUse">
+      <feOffset input="SourceAlpha"/>
+      <feGaussianBlur stdDeviation="3" result="blur-3"/>
+      <feFlood flood-color="#006eff"/>
+      <feComposite operator="in" in2="blur-3"/>
+      <feComposite in="SourceGraphic"/>
+    </filter>
+    <filter id="路径_55420" x="9.645" y="10.274" width="25.447" height="27.229" filterUnits="userSpaceOnUse">
+      <feOffset input="SourceAlpha"/>
+      <feGaussianBlur stdDeviation="4" result="blur-4"/>
+      <feFlood flood-color="#006eff"/>
+      <feComposite operator="in" in2="blur-4"/>
+      <feComposite in="SourceGraphic"/>
+    </filter>
+    <filter id="路径_55421" x="5.305" y="5.155" width="25.447" height="32.347" filterUnits="userSpaceOnUse">
+      <feOffset input="SourceAlpha"/>
+      <feGaussianBlur stdDeviation="4" result="blur-5"/>
+      <feFlood flood-color="#006eff"/>
+      <feComposite operator="in" in2="blur-5"/>
+      <feComposite in="SourceGraphic"/>
+    </filter>
+    <filter id="路径_55422" x="0" y="7.716" width="27.376" height="27.376" filterUnits="userSpaceOnUse">
+      <feOffset input="SourceAlpha"/>
+      <feGaussianBlur stdDeviation="4" result="blur-6"/>
+      <feFlood flood-color="#006eff"/>
+      <feComposite operator="in" in2="blur-6"/>
+      <feComposite in="SourceGraphic"/>
+    </filter>
+    <filter id="路径_55423" x="4.34" y="2.351" width="27.376" height="27.376" filterUnits="userSpaceOnUse">
+      <feOffset input="SourceAlpha"/>
+      <feGaussianBlur stdDeviation="4" result="blur-7"/>
+      <feFlood flood-color="#006eff"/>
+      <feComposite operator="in" in2="blur-7"/>
+      <feComposite in="SourceGraphic"/>
+    </filter>
+    <filter id="路径_55424" x="8.68" y="7.716" width="27.376" height="27.376" filterUnits="userSpaceOnUse">
+      <feOffset input="SourceAlpha"/>
+      <feGaussianBlur stdDeviation="4" result="blur-8"/>
+      <feFlood flood-color="#006eff"/>
+      <feComposite operator="in" in2="blur-8"/>
+      <feComposite in="SourceGraphic"/>
+    </filter>
+    <filter id="路径_55425" x="5.305" y="0" width="25.447" height="26.968" filterUnits="userSpaceOnUse">
+      <feOffset input="SourceAlpha"/>
+      <feGaussianBlur stdDeviation="4" result="blur-9"/>
+      <feFlood flood-color="#006eff"/>
+      <feComposite operator="in" in2="blur-9"/>
+      <feComposite in="SourceGraphic"/>
+    </filter>
+  </defs>
+  <g id="组_13324" data-name="组 13324" transform="translate(-42.857 12)">
+    <g transform="matrix(1, 0, 0, 1, 42.86, -12)" filter="url(#路径_55417)">
+      <path id="路径_55417-2" data-name="路径 55417" d="M128,0m.294,0h.858a.26.26,0,0,1,.294.294V8.056a.26.26,0,0,1-.294.294h-.858A.26.26,0,0,1,128,8.056V.294A.26.26,0,0,1,128.294,0Z" transform="translate(-115.04 12)" fill="#b0d7ff"/>
+    </g>
+    <g transform="matrix(1, 0, 0, 1, 42.86, -12)" filter="url(#路径_55418)">
+      <path id="路径_55418-2" data-name="路径 55418" d="M128,776.229m.294,0h.858a.26.26,0,0,1,.294.294V779.2a.26.26,0,0,1-.294.294h-.858A.26.26,0,0,1,128,779.2v-2.679A.26.26,0,0,1,128.294,776.229Z" transform="translate(-115.04 -753.99)" fill="#b0d7ff"/>
+    </g>
+    <g transform="matrix(1, 0, 0, 1, 42.86, -12)" filter="url(#路径_55419)">
+      <path id="路径_55419-2" data-name="路径 55419" d="M786.286,0m.294,0h.858a.26.26,0,0,1,.294.294V8a.26.26,0,0,1-.294.294h-.858A.26.26,0,0,1,786.286,8V.294A.26.26,0,0,1,786.58,0Z" transform="translate(-764.64 12)" fill="#b0d7ff"/>
+    </g>
+    <g transform="matrix(1, 0, 0, 1, 42.86, -12)" filter="url(#路径_55420)">
+      <path id="路径_55420-2" data-name="路径 55420" d="M786.286,779.154m.294,0h.858a.26.26,0,0,1,.294.294v2.64a.26.26,0,0,1-.294.294h-.858a.26.26,0,0,1-.294-.294v-2.64A.26.26,0,0,1,786.58,779.154Z" transform="translate(-764.64 -756.88)" fill="#b0d7ff"/>
+    </g>
+    <g transform="matrix(1, 0, 0, 1, 42.86, -12)" filter="url(#路径_55421)">
+      <path id="路径_55421-2" data-name="路径 55421" d="M457.143,390.949m.294,0h.858a.26.26,0,0,1,.294.294V399a.26.26,0,0,1-.294.294h-.858a.26.26,0,0,1-.294-.294v-7.759A.26.26,0,0,1,457.437,390.949Z" transform="translate(-439.84 -373.79)" fill="#b0d7ff"/>
+    </g>
+    <g transform="matrix(1, 0, 0, 1, 42.86, -12)" filter="url(#路径_55422)">
+      <path id="路径_55422-2" data-name="路径 55422" d="M56.545,586.831m-1.688,0a1.688,1.688,0,1,0,1.688-1.688A1.688,1.688,0,0,0,54.857,586.831Z" transform="translate(-42.86 -565.43)" fill="#b0d7ff"/>
+    </g>
+    <g transform="matrix(1, 0, 0, 1, 42.86, -12)" filter="url(#路径_55423)">
+      <path id="路径_55423-2" data-name="路径 55423" d="M385.688,179.974m-1.688,0a1.688,1.688,0,1,0,1.688-1.688A1.688,1.688,0,0,0,384,179.974Z" transform="translate(-367.66 -163.94)" fill="#b0d7ff"/>
+    </g>
+    <g transform="matrix(1, 0, 0, 1, 42.86, -12)" filter="url(#路径_55424)">
+      <path id="路径_55424-2" data-name="路径 55424" d="M714.831,586.831m-1.688,0a1.688,1.688,0,1,0,1.688-1.688A1.688,1.688,0,0,0,713.143,586.831Z" transform="translate(-692.46 -565.43)" fill="#b0d7ff"/>
+    </g>
+    <g transform="matrix(1, 0, 0, 1, 42.86, -12)" filter="url(#路径_55425)">
+      <path id="路径_55425-2" data-name="路径 55425" d="M457.143,0m.294,0h.858a.26.26,0,0,1,.294.294v2.38a.26.26,0,0,1-.294.294h-.858a.26.26,0,0,1-.294-.294V.294A.26.26,0,0,1,457.437,0Z" transform="translate(-439.84 12)" fill="#b0d7ff"/>
+    </g>
+  </g>
+</svg>

+ 14 - 0
src/assets/icons/device-paramer.svg

@@ -0,0 +1,14 @@
+<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="31.31" height="31.309" viewBox="0 0 31.31 31.309">
+  <defs>
+    <filter id="路径_55426" x="0" y="0" width="31.31" height="31.309" filterUnits="userSpaceOnUse">
+      <feOffset input="SourceAlpha"/>
+      <feGaussianBlur stdDeviation="3" result="blur"/>
+      <feFlood flood-color="#3df6ff"/>
+      <feComposite operator="in" in2="blur"/>
+      <feComposite in="SourceGraphic"/>
+    </filter>
+  </defs>
+  <g transform="matrix(1, 0, 0, 1, 0, 0)" filter="url(#路径_55426)">
+    <path id="路径_55426-2" data-name="路径 55426" d="M12.478,1.608A9.518,9.518,0,0,0,6.655,0,9.518,9.518,0,0,0,.832,1.608,2.022,2.022,0,0,0,0,3.12V7.876A.166.166,0,0,0,0,7.9H0v2.288c0,1.722,2.979,3.12,6.655,3.12s6.655-1.4,6.655-3.12V7.9h0c0-.023,0-.045,0-.069V3.12A2.022,2.022,0,0,0,12.478,1.608ZM.832,4.631A9.518,9.518,0,0,0,6.655,6.239a9.518,9.518,0,0,0,5.823-1.608V5.661a6.607,6.607,0,0,1-.764.419,12.23,12.23,0,0,1-5.059.992A12.209,12.209,0,0,1,1.6,6.078a6.607,6.607,0,0,1-.764-.419V4.631Zm0,2.357A9.518,9.518,0,0,0,6.655,8.6a9.518,9.518,0,0,0,5.823-1.608V8.017a6.808,6.808,0,0,1-.764.419,12.235,12.235,0,0,1-5.059.992A12.235,12.235,0,0,1,1.6,8.436a6.607,6.607,0,0,1-.764-.419Zm10.882,3.8a12.235,12.235,0,0,1-5.059.992A12.235,12.235,0,0,1,1.6,10.792a6.935,6.935,0,0,1-.733-.4.7.7,0,0,1-.031-.2V9.346a9.518,9.518,0,0,0,5.823,1.608,9.526,9.526,0,0,0,5.823-1.608v.845a.708.708,0,0,1-.031.2A6.706,6.706,0,0,1,11.714,10.792Z" transform="translate(9 9)" fill="#81f9ff"/>
+  </g>
+</svg>

+ 12 - 0
src/assets/icons/dust-nd.svg

@@ -0,0 +1,12 @@
+<svg xmlns="http://www.w3.org/2000/svg" width="35.94" height="35.285" viewBox="0 0 35.94 35.285">
+  <g id="组_13152" data-name="组 13152" transform="translate(-12.295 -87.45)">
+    <path id="路径_55299" data-name="路径 55299" d="M396.112,91.37m-3.92,0a3.921,3.921,0,1,0,3.92-3.92A3.92,3.92,0,0,0,392.192,91.37Z" transform="translate(-365.776)" fill="#15a1ff"/>
+    <path id="路径_55300" data-name="路径 55300" d="M637.881,587.5m-3.922,0a3.922,3.922,0,1,0,3.922-3.922A3.922,3.922,0,0,0,633.959,587.5Z" transform="translate(-599.702 -480.037)" fill="#15a1ff"/>
+    <path id="路径_55301" data-name="路径 55301" d="M19.846,579.462m-3.922,0a3.922,3.922,0,1,0,3.922-3.922A3.922,3.922,0,0,0,15.924,579.462Z" transform="translate(-1.711 -472.26)" fill="#15a1ff"/>
+    <path id="路径_55302" data-name="路径 55302" d="M394.7,795.751m-2.561,0a2.56,2.56,0,1,0,2.561-2.56A2.56,2.56,0,0,0,392.141,795.751Z" transform="translate(-365.726 -682.852)" fill="#15a1ff"/>
+    <path id="路径_55303" data-name="路径 55303" d="M837.172,340.48m-2.56,0a2.561,2.561,0,1,0,2.56-2.561A2.56,2.56,0,0,0,834.612,340.48Z" transform="translate(-793.847 -242.346)" fill="#15a1ff"/>
+    <path id="路径_55304" data-name="路径 55304" d="M290.509,386.765m-2.56,0a2.561,2.561,0,1,0,2.56-2.56A2.56,2.56,0,0,0,287.949,386.765Z" transform="translate(-264.913 -287.13)" fill="#15a1ff"/>
+    <path id="路径_55305" data-name="路径 55305" d="M30.823,219.853m-2.561,0a2.561,2.561,0,1,0,2.561-2.561A2.56,2.56,0,0,0,28.263,219.853Z" transform="translate(-13.649 -125.631)" fill="#15a1ff"/>
+    <rect id="矩形_4127" data-name="矩形 4127" width="35.94" height="3.7" transform="translate(12.295 119.036)" fill="#15a1ff"/>
+  </g>
+</svg>

+ 6 - 0
src/assets/icons/expansion.svg

@@ -0,0 +1,6 @@
+<svg xmlns="http://www.w3.org/2000/svg" width="20.134" height="20.444" viewBox="0 0 20.134 20.444">
+  <g id="组_13239" data-name="组 13239" transform="translate(0.5 0.5)">
+    <path id="路径_55411" data-name="路径 55411" d="M162.69,177.785a.806.806,0,0,1-.8.8h-1.971a.806.806,0,0,1-.8-.8V159.948a.806.806,0,0,1,.8-.8h1.971a.806.806,0,0,1,.8.8Z" transform="translate(-159.111 -159.144)" fill="#fff" stroke="rgba(0,0,0,0)" stroke-width="1"/>
+    <path id="路径_55412" data-name="路径 55412" d="M391.806,172.172c0-.442.29-.588.644-.324l11.778,8.771a.566.566,0,0,1,0,.96L392.45,190.35c-.354.264-.644.118-.644-.324Z" transform="translate(-385.36 -171.369)" fill="#fff" stroke="rgba(0,0,0,0)" stroke-width="1"/>
+  </g>
+</svg>

+ 5 - 0
src/assets/icons/hd-wd.svg

@@ -0,0 +1,5 @@
+<svg xmlns="http://www.w3.org/2000/svg" width="20.978" height="35.926" viewBox="0 0 20.978 35.926">
+  <g id="组_13139" data-name="组 13139" transform="translate(-506.549 -358.691)">
+    <path id="路径_55369" data-name="路径 55369" d="M224.706,22.115V12.478a1.064,1.064,0,0,0-1.063-1.063h-.084a1.031,1.031,0,0,0-1.031,1.031v9.668a3.743,3.743,0,0,0-2.747,3.568,3.838,3.838,0,1,0,4.925-3.568Zm5.645-4.269a.97.97,0,0,1-.379-.737V6.213a6.358,6.358,0,0,0-12.713,0V16.8a1.515,1.515,0,0,1-.516,1.161,10.1,10.1,0,0,0-3.613,7.725,10.492,10.492,0,0,0,20.978,0A10.1,10.1,0,0,0,230.351,17.846Zm-6.736,14.829a7.1,7.1,0,0,1-7.185-6.992,6.926,6.926,0,0,1,3.357-5.908l.509-.312a.564.564,0,0,0,.267-.477V6.213a3.057,3.057,0,0,1,6.111,0V18.986a.564.564,0,0,0,.267.477l.509.312a6.918,6.918,0,0,1,3.354,5.908,7.1,7.1,0,0,1-7.188,6.992Z" transform="translate(293.42 358.691)" fill="#15a1ff"/>
+  </g>
+</svg>

+ 6 - 0
src/assets/icons/kg.svg

@@ -0,0 +1,6 @@
+<svg xmlns="http://www.w3.org/2000/svg" width="31.482" height="17.99" viewBox="0 0 31.482 17.99">
+  <g id="组_13154" data-name="组 13154" transform="translate(-64 -256)">
+    <path id="路径_55376" data-name="路径 55376" d="M86.487,273.99H72.995a8.995,8.995,0,1,1,0-17.99H86.487a8.995,8.995,0,0,1,0,17.99Zm0-15.741H72.995a6.746,6.746,0,0,0,0,13.492H86.487a6.746,6.746,0,0,0,0-13.492Z" transform="translate(0 0)" fill="#15a1ff"/>
+    <path id="路径_55377" data-name="路径 55377" d="M160,357.622a5.622,5.622,0,1,1,5.622,5.622A5.622,5.622,0,0,1,160,357.622Z" transform="translate(-92.627 -92.627)" fill="#15a1ff"/>
+  </g>
+</svg>

+ 6 - 0
src/assets/icons/max-temperature.svg

@@ -0,0 +1,6 @@
+<svg xmlns="http://www.w3.org/2000/svg" width="50.313" height="73.149" viewBox="0 0 50.313 73.149">
+  <g id="组_13139" data-name="组 13139" transform="translate(-506.549 -355.112)">
+    <path id="路径_55369" data-name="路径 55369" d="M234.565,40.948V23.1a1.971,1.971,0,0,0-1.968-1.968h-.156a1.909,1.909,0,0,0-1.91,1.91v17.9a6.93,6.93,0,0,0-5.086,6.606,7.106,7.106,0,1,0,9.12-6.606Zm10.451-7.905a1.8,1.8,0,0,1-.7-1.364V11.5a11.773,11.773,0,0,0-23.54,0v19.6a2.8,2.8,0,0,1-.955,2.15,18.709,18.709,0,0,0-6.69,14.3,19.427,19.427,0,0,0,38.844,0A18.71,18.71,0,0,0,245.016,33.043ZM232.545,60.5a13.14,13.14,0,0,1-13.3-12.946,12.824,12.824,0,0,1,6.216-10.939l.942-.578a1.044,1.044,0,0,0,.494-.883V11.5a5.66,5.66,0,0,1,11.315,0v23.65a1.044,1.044,0,0,0,.494.883l.942.578a12.81,12.81,0,0,1,6.21,10.939A13.154,13.154,0,0,1,232.545,60.5Z" transform="translate(293.42 361.739)" fill="#fff"/>
+    <path id="路径_55370" data-name="路径 55370" d="M284.718,159.821v14.623h2.954V159.821l3.1,3.1,2.068-2.216L286.2,154.06l-6.647,6.647,2.068,2.068Zm-7.385-7.533h17.725v-2.954H277.333Z" transform="translate(261.805 205.778)" fill="#3df6ff"/>
+  </g>
+</svg>

+ 6 - 0
src/assets/icons/put-away.svg

@@ -0,0 +1,6 @@
+<svg xmlns="http://www.w3.org/2000/svg" width="20.134" height="20.444" viewBox="0 0 20.134 20.444">
+  <g id="组_13239" data-name="组 13239" transform="translate(0.5 0.5)">
+    <path id="路径_55411" data-name="路径 55411" d="M159.112,177.785a.806.806,0,0,0,.8.8h1.971a.806.806,0,0,0,.8-.8V159.948a.806.806,0,0,0-.8-.8h-1.971a.806.806,0,0,0-.8.8Z" transform="translate(-143.556 -159.144)" fill="#fff" stroke="rgba(0,0,0,0)" stroke-width="1"/>
+    <path id="路径_55412" data-name="路径 55412" d="M404.494,172.172c0-.442-.29-.588-.644-.324l-11.778,8.771a.566.566,0,0,0,0,.96l11.778,8.771c.354.264.644.118.644-.324Z" transform="translate(-391.806 -171.369)" fill="#fff" stroke="rgba(0,0,0,0)" stroke-width="1"/>
+  </g>
+</svg>

+ 3 - 0
src/assets/icons/pw-sy.svg

@@ -0,0 +1,3 @@
+<svg xmlns="http://www.w3.org/2000/svg" width="34.212" height="30.791" viewBox="0 0 34.212 30.791">
+  <path id="路径_55375" data-name="路径 55375" d="M102.439,141.685q6.415,7.149,6.415,10.691a6.415,6.415,0,1,1-12.83,0Q96.024,148.835,102.439,141.685Zm0-13.685a17.221,17.221,0,0,1,17.106,17.334,17.415,17.415,0,0,1-3.158,10.036h-4.059a14.232,14.232,0,0,0,4.105-10.036q0-.624-.051-1.235H113.9v-3.218h1.832a14.05,14.05,0,0,0-11.654-9.631v3.192H100.8v-3.2a14.057,14.057,0,0,0-11.654,9.632h1.829V144.1H88.5q-.052.616-.051,1.235a14.232,14.232,0,0,0,4.105,10.035l-4.061,0a17.411,17.411,0,0,1-3.158-10.036A17.221,17.221,0,0,1,102.439,128Zm1.924,20.527q-1.924,2.145-1.924,3.207a1.924,1.924,0,1,0,3.849,0Q106.287,150.673,104.363,148.527Z" transform="translate(-85.332 -128)" fill="#15a1ff"/>
+</svg>

File diff suppressed because it is too large
+ 2 - 0
src/assets/icons/pw-zz.svg


+ 3 - 0
src/assets/icons/smoke-temperature.svg

@@ -0,0 +1,3 @@
+<svg xmlns="http://www.w3.org/2000/svg" width="40.011" height="37.151" viewBox="0 0 40.011 37.151">
+  <path id="路径_55409" data-name="路径 55409" d="M458.332,493.569l-18.179-33.425c-.817-1.521-2.17-1.583-2.991-.063L418.984,493.6c-.821,1.517-.063,2.518,1.685,2.518h35.982C458.395,496.122,459.153,495.09,458.332,493.569Zm-19.512-3.585a5.9,5.9,0,0,1-6.036-5.77,5.691,5.691,0,0,1,2.033-4.347.849.849,0,0,0,.289-.653v-5.958a3.58,3.58,0,0,1,7.158,0v6.134a.542.542,0,0,0,.211.414,5.685,5.685,0,0,1,2.115,4.41A5.907,5.907,0,0,1,438.82,489.984Zm2.025-9.1-.289-.176a.314.314,0,0,1-.149-.266v-7.189a1.721,1.721,0,0,0-3.44,0v7.189a.316.316,0,0,1-.149.27l-.285.176a3.9,3.9,0,0,0-1.892,3.323,4.046,4.046,0,0,0,8.089,0A3.9,3.9,0,0,0,440.845,480.887Zm-2.224,5.547a2.158,2.158,0,0,1-2.1-2.221,2.108,2.108,0,0,1,1.548-2.009v-5.442a.577.577,0,0,1,.579-.579h.047a.6.6,0,0,1,.6.6V482.2a2.16,2.16,0,0,1-.676,4.23Z" transform="translate(-418.653 -458.971)" fill="#fff"/>
+</svg>

+ 3 - 0
src/assets/icons/warning-CO.svg

@@ -0,0 +1,3 @@
+<svg xmlns="http://www.w3.org/2000/svg" width="40.011" height="37.151" viewBox="0 0 40.011 37.151">
+  <path id="路径_55408" data-name="路径 55408" d="M950.857,481.723a.752.752,0,0,0-.739.743v.86a.741.741,0,0,0,1.482,0v-.86A.758.758,0,0,0,950.857,481.723Zm0,0a.752.752,0,0,0-.739.743v.86a.741.741,0,0,0,1.482,0v-.86A.758.758,0,0,0,950.857,481.723Zm17.475,11.846-18.179-33.425c-.817-1.521-2.17-1.583-2.991-.063L928.984,493.6c-.821,1.517-.063,2.518,1.685,2.518h35.982C968.395,496.122,969.153,495.09,968.332,493.569Zm-10.4-9.063a5.205,5.205,0,0,1-5.039,3.866h-8.609a5.2,5.2,0,0,1-1.345-10.231,5.646,5.646,0,0,1,.8-1.63,5.848,5.848,0,0,1,1.3-1.314,5.909,5.909,0,0,1,1.654-.844,6.1,6.1,0,0,1,3.792,0,5.811,5.811,0,0,1,2.956,2.158,5.753,5.753,0,0,1,.8,1.63A5.2,5.2,0,0,1,957.933,484.506Zm-4.508-4.66h-.016a.967.967,0,0,1-.712-.715,4.215,4.215,0,0,0-8.218,0,.961.961,0,0,1-.712.715l-.008,0h-.008a3.38,3.38,0,0,0,.532,6.72h8.609a3.4,3.4,0,0,0,3.4-3.4A3.348,3.348,0,0,0,953.426,479.846Zm-4.871,1.208a.7.7,0,0,1-.641.747.477.477,0,0,1-.106,0h-1.095a1.132,1.132,0,0,0,0,2.264h1.1a.741.741,0,0,1,.747.731v.016a.614.614,0,0,1-.555.668.541.541,0,0,1-.113,0h-1.173a2.555,2.555,0,0,1-2.545-2.545,2.673,2.673,0,0,1,.747-1.834,2.472,2.472,0,0,1,1.8-.79h1.095a.741.741,0,0,1,.747.731Zm4.457,2.271a2.154,2.154,0,0,1-4.308,0v-.86a2.154,2.154,0,0,1,4.308,0Zm-2.154-1.6a.752.752,0,0,0-.739.743v.86a.741.741,0,0,0,1.482,0v-.86A.758.758,0,0,0,950.857,481.723Z" transform="translate(-928.653 -458.971)" fill="#ff9b17"/>
+</svg>

+ 3 - 0
src/assets/icons/warning-fire.svg

@@ -0,0 +1,3 @@
+<svg xmlns="http://www.w3.org/2000/svg" width="40.011" height="37.151" viewBox="0 0 40.011 37.151">
+  <path id="路径_55406" data-name="路径 55406" d="M626.332,493.569l-18.179-33.425c-.817-1.521-2.17-1.583-2.991-.063L586.984,493.6c-.821,1.517-.063,2.518,1.685,2.518h35.982C626.395,496.122,627.153,495.09,626.332,493.569Zm-17.253-3.012s2.17-3.042,0-4.781a8.332,8.332,0,0,1-2.74-3.472c-5.544,3.409-1.462,8.073-1.306,8.249-8.687-3.905-5.343-11.121-3.17-12.856s3.608-6.517,3.608-6.517c4.343,1.74,5.343,6.951,5.343,6.951l1.3-1.3C619.506,485.953,609.079,490.557,609.079,490.557Z" transform="translate(-586.653 -458.971)" fill="#ff9b17"/>
+</svg>

+ 3 - 0
src/assets/icons/warning-smoke.svg

@@ -0,0 +1,3 @@
+<svg xmlns="http://www.w3.org/2000/svg" width="40.011" height="37.151" viewBox="0 0 40.011 37.151">
+  <path id="路径_55407" data-name="路径 55407" d="M799.332,493.569l-18.179-33.425c-.817-1.521-2.17-1.583-2.991-.063L759.984,493.6c-.821,1.517-.062,2.518,1.685,2.518h35.982C799.395,496.122,800.153,495.09,799.332,493.569Zm-28.523-11.58a1.116,1.116,0,0,1,.192-.864l-.117.008-.109-1.986,9.191-.516h.5l.121,1.912-7.51.457c1.583.184,3.163.375,4.816.575l.016,0c.731.086,1.482.18,2.232.27l.512.059-.231,1.947-9.637-1.141.031-.485A1.6,1.6,0,0,0,770.809,481.989Zm16.486-.805a5.612,5.612,0,0,1,.426,2.545,4.692,4.692,0,0,1-.942,2.424,5,5,0,0,1-1.747,1.521,4.8,4.8,0,0,1-2.193.5,6.541,6.541,0,0,1-.958-.074,2.173,2.173,0,0,0-.321-.031,1.111,1.111,0,0,0-.778.328,3.7,3.7,0,0,1-1.165.688,3.85,3.85,0,0,1-3.448-.461,3.152,3.152,0,0,1-1.306-1.857l-.1-.422.4-.172.086-.039a1.7,1.7,0,0,1,.7-.192,1.188,1.188,0,0,1,.981.649,1.5,1.5,0,0,0,1.294.755,1.911,1.911,0,0,0,1.869-1.556l.082-.391.4-.016c.059,0,.125-.012.2-.023a2.492,2.492,0,0,1,.34-.031.9.9,0,0,1,.583.188.941.941,0,0,1,.332.586h0a1.808,1.808,0,0,0,.661.113,3.351,3.351,0,0,0,2.361-1.095,2.988,2.988,0,0,0,.606-1.486,2.8,2.8,0,0,0-.082-1.345l-1.458.512-.618-1.716.676-.336.5-.25a2,2,0,0,0-.938-3.765.5.5,0,0,0-.074,0c-.3.016-.367.082-.367.082s-.035.043-.02.211a2.214,2.214,0,0,1-.074.661c-.016.082-.035.164-.047.246l-.074.465-.473-.035a1.519,1.519,0,0,1-.926-.3,1.2,1.2,0,0,1-.371-1.04,1.738,1.738,0,0,0-1.263-1.88,1.431,1.431,0,0,0-.481-.082,1.824,1.824,0,0,0-1.411.766,1.29,1.29,0,0,1-.954.563,1.41,1.41,0,0,1-.958-.493l-.242-.242.129-.317a3.4,3.4,0,0,1,1.071-1.45,3.709,3.709,0,0,1,1.626-.7,4.515,4.515,0,0,1,.86-.082v0a3.463,3.463,0,0,1,1.47.3,3.231,3.231,0,0,1,1.232.977.857.857,0,0,0,.762.426c.09,0,.176-.008.262-.02a4.371,4.371,0,0,1,.508-.031,3.62,3.62,0,0,1,1.755.438,3.936,3.936,0,0,1,1.357,1.267,3.856,3.856,0,0,1,.692,1.978,4.654,4.654,0,0,1-.383,2.033A1.105,1.105,0,0,0,787.3,481.183Z" transform="translate(-759.653 -458.971)" fill="#ff9b17"/>
+</svg>

+ 3 - 0
src/assets/icons/warning-temperature.svg

@@ -0,0 +1,3 @@
+<svg xmlns="http://www.w3.org/2000/svg" width="40.011" height="37.151" viewBox="0 0 40.011 37.151">
+  <path id="路径_55409" data-name="路径 55409" d="M458.332,493.569l-18.179-33.425c-.817-1.521-2.17-1.583-2.991-.063L418.984,493.6c-.821,1.517-.063,2.518,1.685,2.518h35.982C458.395,496.122,459.153,495.09,458.332,493.569Zm-19.512-3.585a5.9,5.9,0,0,1-6.036-5.77,5.691,5.691,0,0,1,2.033-4.347.849.849,0,0,0,.289-.653v-5.958a3.58,3.58,0,0,1,7.158,0v6.134a.542.542,0,0,0,.211.414,5.685,5.685,0,0,1,2.115,4.41A5.907,5.907,0,0,1,438.82,489.984Zm2.025-9.1-.289-.176a.314.314,0,0,1-.149-.266v-7.189a1.721,1.721,0,0,0-3.44,0v7.189a.316.316,0,0,1-.149.27l-.285.176a3.9,3.9,0,0,0-1.892,3.323,4.046,4.046,0,0,0,8.089,0A3.9,3.9,0,0,0,440.845,480.887Zm-2.224,5.547a2.158,2.158,0,0,1-2.1-2.221,2.108,2.108,0,0,1,1.548-2.009v-5.442a.577.577,0,0,1,.579-.579h.047a.6.6,0,0,1,.6.6V482.2a2.16,2.16,0,0,1-.676,4.23Z" transform="translate(-418.653 -458.971)" fill="#ff9b17"/>
+</svg>

BIN
src/assets/images/vent/border/box1-bottom.png


BIN
src/assets/images/vent/border/box1-top.png


BIN
src/assets/images/vent/control-switch-bg.png


BIN
src/assets/images/vent/home/tosmall.png


BIN
src/assets/images/vent/home/tree-bg.png


BIN
src/assets/images/vent/home/tree-icon-bg.png


BIN
src/assets/images/vent/home/tree-icon-hover-bg.png


BIN
src/assets/images/vent/model_image/dust-monitor-bg.png


+ 11 - 0
src/design/vent/index.less

@@ -18,6 +18,17 @@
     border-color: #91e9fe99 !important;
   }
 }
+.@{ventSpace}-btn-dangerous{
+  color: #fff !important;
+  border-color: #ed4c4c !important;
+  background: #ed4c4c !important;
+  text-shadow: 0 -1px 0 rgba(0, 0, 0, 0.12) !important;
+  box-shadow: 0 2px 0 rgba(0, 0, 0, 0.045) !important;
+  &:hover, &:focus {
+    background: #ee3636 !important;
+    border-color: #ee3636 !important;
+  }
+}
 
 .@{ventSpace}-btn-link {
   color: #00e7ff !important;

+ 91 - 0
src/views/vent/comment/components/bottomMenu.vue

@@ -0,0 +1,91 @@
+<template>
+  <div class="bottom-btn-group">
+    <div v-for="item in navList" :key="item.pathName" class="vent-row-center btn-item" @click="setBtn('click', item)"
+      @mouseenter="setBtn('over', item)" @mouseleave="setBtn('leave', item)">
+      <dv-decoration11
+        :color="isBtnActive === item.pathName ? activeColors : item.isHover ? activeColors : noActiveColors"
+        style="width:200px;height:60px;">
+        {{ item.title }}
+      </dv-decoration11>
+    </div>
+  </div>
+</template>
+<script lang="ts">
+import { ref, defineComponent } from 'vue'
+import { Decoration11 as DvDecoration11} from '@kjgl77/datav-vue3'
+type navListType = { title: string; pathName: string; isHover: Boolean };
+
+export default defineComponent({
+  name: 'BottomMenu',
+  components: { DvDecoration11 },
+  props: {
+    navList: {
+      type: Array<navListType>,
+      default: () => [
+        {
+          title: '监控界面',
+          pathName: 'monitor',
+          isHover: false
+        },
+        {
+          title: '历史监测记录',
+          pathName: 'monitor_history',
+          isHover: false
+        },
+        {
+          title: '操作历史记录',
+          pathName: 'handler_history',
+          isHover: false
+        },
+        {
+          title: '故障诊断历史记录',
+          pathName: 'faultRecord',
+          isHover: false
+        }
+      ]
+    },
+  },
+  emits: ['change'],
+  setup(props, {emit}) {
+    const isBtnActive = ref(props.navList[0].pathName)
+    const activeColors = ['#009BFF', '#28DBE4']
+    const noActiveColors = ['#aaa', '#aaa']
+
+    function setBtn(type, activeObj) {
+      if (type === 'over') {
+        activeObj.isHover = true
+      } else if (type === 'leave') {
+        activeObj.isHover = false
+      } else if (type === 'click') {
+        isBtnActive.value = activeObj.pathName
+      }
+      emit('change', isBtnActive.value)
+    }
+    return {
+      activeColors, noActiveColors, setBtn, isBtnActive
+    }
+  },
+})
+
+
+</script>
+<style lang="less" scoped>
+.bottom-btn-group {
+    display: flex;
+    position: fixed;
+    width: 100%;
+    height: 100px;
+    bottom: 20px;
+    align-items: center;
+    justify-content: center;
+
+    .btn-item {
+      width: 200px;
+      height: 60px;
+      margin: 10px;
+      color: #fff;
+      cursor: pointer;
+      pointer-events: auto;
+    }
+  }
+</style>

+ 139 - 0
src/views/vent/comment/components/customHeader.vue

@@ -0,0 +1,139 @@
+<template>
+  <div class="vent-custom-header">
+    <div class="vent-home-header">
+      <Decoration5 class="header-icon" :dur="2" :color="['#21437F', '#2CF7FE']" style="width:500px;height:40px;" />
+      <div class="header-text"><slot></slot></div>
+    </div>
+    <div class = "container-title">
+      <a-select
+        class="title-select"
+        ref="select"
+        v-model:value="currentTitleValue"
+        @change="handleTitleChange"
+        popupClassName="drop"
+        :field-names="fieldNames"
+        :options="options"
+        :dropdownStyle="{ background : 'transparent',  borderBottom: '1px solid #ececec66', backdropFilter: 'blur(50px)'}"
+      >
+        <!-- <a-select-option value="2">15212工作面</a-select-option>
+        <a-select-option value="1598491318007898113">采煤工作面</a-select-option>
+        <a-select-option value="3">掘进工作面</a-select-option> -->
+      </a-select>
+    </div>
+  </div>
+</template>
+<script lang="ts">
+import { defineComponent, computed } from 'vue'
+import { Decoration5 } from '@kjgl77/datav-vue3'
+export default defineComponent({
+  name: 'customHeader',
+  components: { Decoration5 },
+  props: {
+    optionValue: {
+      type: String
+    },
+    fieldNames: {
+      type: Object,
+      default: () => {}
+    },
+    options: {
+      type: Array,
+      default: () => []
+    }
+  },
+  emits: ['change'],
+  setup(props, { emit }) {
+    const currentTitleValue = computed(() => props.optionValue)
+    // 标题选择
+    function handleTitleChange(value) {
+      emit('change', value)
+    }   
+    return {
+      currentTitleValue,
+      handleTitleChange,
+    }
+  },
+})
+</script>
+<style lang="less">
+@import '/@/design/vent/modal.less';
+
+.@{ventSpace}-select-dropdown {
+  .@{ventSpace}-select-item {
+    color: #fff !important;
+  }
+
+  .@{ventSpace}-select-item-option-selected,
+  .@{ventSpace}-select-item-option-active {
+    background-color: #00678b66 !important;
+  }
+
+  .@{ventSpace}-select-item:hover {
+    background-color: #008fc366 !important;
+  }
+}
+</style>
+<style lang="less" scoped>
+  @ventSpace: zxm;
+  .vent-custom-header{
+    width: 100%;
+    .vent-home-header {
+      width: 100%;
+      height: 100px;
+      position: fixed;
+      top: 0;
+      background: url('/@/assets/images/vent/new-home/header-bg.png') no-repeat;
+      background-size: contain;
+      display: flex;
+      justify-content: center;
+      .header-icon{
+        margin-top: 45px;
+      }
+      .header-text{
+        position: fixed;
+        top: 18px;
+        color: #fff;
+        font-size: 26px;
+      }
+    }
+    .container-title {
+      width: 398px;
+      height: 34px;
+      top: 60px;
+      background: url('/@/assets/images/vent/new-home/container-title-bg.png') no-repeat;
+      background-size: contain;
+      padding: 0 0 0 180px;
+      font-size: 20px;
+      pointer-events: auto;
+      position: relative;
+
+      .title-select {
+        width: 198px;
+        position: absolute;
+        top: 0;
+        left: 180px;
+      }
+    }
+  }
+  .custom-drop{
+      border-bottom: 1px solid #ececec66 !important;
+      background: transparent !important;
+      backdrop-filter: blur(50px) !important;
+  }
+  :deep(.zxm-select){
+    width: 300px;
+  
+    .@{ventSpace}-select-selector{
+      background: transparent !important;
+      border: none !important;
+      box-shadow: none !important;
+      .zxm-select-selection-item{
+        color: #fff !important;
+        font-size: 20px;
+      }
+    }
+    .@{ventSpace}-select-arrow{
+      color: #fff !important;
+    }
+  }
+</style>

+ 25 - 0
src/views/vent/monitorManager/chamberMonitor/components/chamberAlarmHistory.vue

@@ -0,0 +1,25 @@
+<template>
+  <div class="alarm-history">
+    <AlarmHistoryTable columns-type="alarm" device-type="pressurefan"
+      :device-list-api="getTableList.bind(null, { strtype: 'pressurefan' })" designScope="alarm-history" />
+  </div>
+</template>
+<script setup lang="ts">
+import AlarmHistoryTable from '../../comment/AlarmHistoryTable.vue';
+import { getTableList } from '../chamber.api'
+const props = defineProps({
+  deviceType: {
+    type: String,
+    required: true,
+  },
+  deviceId: {
+    type: String,
+    required: true,
+  }
+})
+</script>
+<style lang="less" scoped>
+.alarm-history {
+  pointer-events: auto;
+}
+</style>

+ 25 - 0
src/views/vent/monitorManager/chamberMonitor/components/chamberHandleHistory.vue

@@ -0,0 +1,25 @@
+<template>
+  <div class="handle-history">
+    <HandlerHistoryTable columns-type="operatorhistory" device-type="pressurefan"
+      :device-list-api="getTableList.bind(null, { strtype: 'pressurefan' })" designScope="pressurefan_history" />
+  </div>
+</template>
+<script setup lang="ts">
+import HandlerHistoryTable from '../../comment/HandlerHistoryTable.vue';
+import { getTableList } from '../chamber.api'
+  const props = defineProps({
+    deviceType: {
+      type: String,
+      required: true,
+    },
+    deviceId: {
+      type: String,
+      required: true,
+    }
+  })
+</script>
+<style lang="less" scoped>
+.handle-history {
+  pointer-events: auto;
+}
+</style>

+ 28 - 0
src/views/vent/monitorManager/chamberMonitor/components/chamberHistory.vue

@@ -0,0 +1,28 @@
+<template>
+  <div class="history-box">
+    <HistoryTable :columns-type="`${deviceType}`" :device-type="deviceType" :sysId="deviceId"
+      :device-list-api="getTableList.bind(null, { strtype: deviceType, sysId: deviceId })" designScope="pressurefan_history" />
+  </div>
+</template>
+<script setup lang="ts">
+import { ref, defineProps } from 'vue'
+import HistoryTable from '../../comment/HistoryTable.vue';
+import { getTableList } from '../chamber.api'
+
+const props = defineProps({
+  deviceType: {
+    type: String,
+    required: true,
+  },
+  deviceId: {
+    type: String,
+    required: true,
+  }
+})
+
+</script>
+<style lang="less" scoped>
+.history-box {
+  pointer-events: auto;
+}
+</style>

+ 416 - 0
src/views/vent/monitorManager/chamberMonitor/components/chamberHome.vue

@@ -0,0 +1,416 @@
+<template>
+  <div class="monitor-container">
+    <div class="lr left-box">
+      <div class="monitor-info item-box">
+        <FourBorderBg>
+          <div>
+            <div class="box-title" style="margin-bottom: 10px;">光纤测温实时监测</div>
+            <div class="temperature-group">
+              <div class="temperature-item">
+                <SvgIcon class="icon" size="40" name="aveg-temperature" />
+                <div class="temperature">
+                  <span>平均温度</span>
+                  <span class="value">56.99</span>
+                </div>
+              </div>
+              <div class="temperature-item">
+                <SvgIcon class="icon" size="40" name="max-temperature" />
+                <div class="temperature">
+                  <span>最高温度</span>
+                  <span class="value">56.99</span>
+                </div>
+              </div>
+              <div class="temperature-item">
+                <SvgIcon class="icon" size="40" name="min-temperature" />
+                <div class="temperature">
+                  <span>最低温度</span>
+                  <span class="value">56.99</span>
+                </div>
+              </div>
+            </div>
+          </div>
+          <div class="warning-group">
+            <div class="box-title" style="margin-bottom: 10px;">近一月报警情况</div>
+            <div class="warning-item">
+              <div>
+                <SvgIcon class="icon" size="28" name="alarm-fire" />
+                <span class="title">火焰传感器报警</span>
+                <span class="value">无</span>
+              </div>
+              <div>
+                <SvgIcon class="icon" size="28" name="warning-fire" />
+                <span class="title">火焰传感器预警</span>
+                <span class="value">无</span>
+              </div>
+            </div>
+            <div class="warning-item">
+              <div>
+                <SvgIcon class="icon" size="28" name="alarm-smoke" />
+                <span class="title">烟雾传感器报警</span>
+                <span class="value" style="color: #FF3823; font-weight: 600;">15</span>
+              </div>
+              <div>
+                <SvgIcon class="icon" size="28" name="warning-smoke" />
+                <span class="title">烟雾传感器预警</span>
+                <span class="value">无</span>
+              </div>
+            </div>
+            <div class="warning-item">
+              <div>
+                <SvgIcon class="icon" size="28" name="alarm-CO" />
+                <span class="title">CO传感器报警</span>
+                <span class="value">无</span>
+              </div>
+              <div>
+                <SvgIcon class="icon" size="28" name="warning-CO" />
+                <span class="title">CO传感器预警</span>
+                <span class="value">无</span>
+              </div>
+            </div>
+            <div class="warning-item">
+              <div>
+                <SvgIcon class="icon" size="28" name="alarm-temperature" />
+                <span class="title">温度传感器报警</span>
+                <span class="value">无</span>
+              </div>
+              <div>
+                <SvgIcon class="icon" size="28" name="warning-temperature" />
+                <span class="title">温度传感器预警</span>
+                <span class="value" style="color: #FF9B17; font-weight: 600;">10</span>
+              </div>
+            </div>
+            <div class="alarm-box">
+              
+              <dv-scroll-board ref="scrollBoard" :config="warningConfig"
+                style="width: 100%; height: 240px; overflow-y: auto; " />
+            </div>
+            
+          </div>
+          <!-- <div class="base-info">
+            <div class="box-title" style="margin-bottom: 10px;">基本情况</div>
+            <div>
+              <div>
+                <div>火焰传感器</div>
+              </div>
+            </div>
+          </div> -->
+        </FourBorderBg>
+      </div>
+    </div>
+    <div class="lr right-box">
+      <div class="item-box sensor-container">
+        <div class="box-title">烟雾传感器实时监测</div>
+        <a-table :columns="sensorColumns" :data-source="smokeDataSource" :pagination="false" size="small" maxWidth="340"
+          :scroll="{ x: 'max-content', y: 240 }">
+          <template #bodyCell="{ column, record }">
+            <template v-if="column.dataIndex === 'warnFlag'">
+              <span v-if="record['warnFlag'] == 0" style="color: #00ff00;">正常</span>
+              <span v-else style="color: #ff0000;"> {{ record.warnDes }}</span>
+            </template>
+          </template>
+        </a-table>
+      </div>
+      <div class="item-box">
+        <div class="box-title">喷粉监控</div>
+        <a-table :columns="dustColumns" :data-source="dustDataSource" :pagination="false" size="small" maxWidth="340"
+          :scroll="{ x: 'max-content', y: 240 }">
+          <template #bodyCell="{ column, record }">
+            <template v-if="column.dataIndex === 'warnFlag'">
+              <span v-if="record['warnFlag'] == 0" style="color: #00ff00;">链接</span>
+              <span v-else style="color: #ff0000;"> {{ record.warnDes }}</span>
+            </template>
+            <template v-if="column.dataIndex === 'action'">
+              <a-switch v-model:checked="openDust" @change="handleDust" />
+            </template>
+          </template>
+        </a-table>
+      </div>
+      <div class="item-box" >
+        <LivePlayer id="fm-player1" style="height: 250px;" ref="player1" :videoUrl="flvURL1()" muted live loading controls />
+      </div>
+      
+    </div>
+  </div>
+</template>
+
+<script setup lang="ts">
+
+import { onBeforeMount, ref, onMounted, onUnmounted, reactive, toRaw, shallowReactive, defineProps, watch } from 'vue';
+// import MonitorTable from '../comment/MonitorTable.vue';
+import { ScrollBoard as DvScrollBoard } from '@kjgl77/datav-vue3';
+import { mountedThree, destroy, addChamberText, setModelType } from '../chamber.threejs';
+import { SvgIcon } from '/@/components/Icon';
+import FourBorderBg from '/@/components/vent/fourBorderBg.vue';
+import { warningConfig, sensorColumns, dustColumns } from '../chamber.data'
+import { list } from '../chamber.api';
+import LivePlayer from '@liveqing/liveplayer-v3';
+
+
+const props = defineProps({
+  deviceId: {
+    type: String,
+    require: true
+  }
+})
+
+
+const loading = ref(false);
+
+// 默认初始是第一行
+const openDust = ref(false)
+const fiberDataSource = ref([]); //dusting
+const dustDataSource = ref([]);
+const smokeDataSource = ref([]);
+const temperatureDataSource = ref([]);
+const fireDataSource = ref([]);
+
+// 监测数据
+const selectData = reactive({});
+
+const flvURL1 = () => {
+  return `https://sf1-hscdn-tos.pstatp.com/obj/media-fe/xgplayer_doc_video/flv/xgplayer-demo-360p.flv`;
+  // return ''
+};
+
+// https获取监测数据
+let timer: null | NodeJS.Timeout = null;
+function getMonitor() {
+  if (Object.prototype.toString.call(timer) === '[object Null]') {
+    timer = setTimeout(async () => {
+      if (props.deviceId) {
+        const data = await getDataSource(props.deviceId)
+        Object.assign(selectData, data); 
+      }
+      if (timer) {
+        timer = null;
+      }
+      await getMonitor();
+    }, 1000);
+  }
+};
+
+async function getDataSource(systemID) {
+  const res = await list({ devicetype: 'sys', systemID });
+  const result = res.msgTxt;
+  result.forEach(item => {
+
+    if (item.type === 'dusting_auto') {
+      // 喷粉
+      dustDataSource.value = item['datalist'].filter((data: any) => {
+        const readData = data.readData;
+        return Object.assign(data, readData);
+      })
+
+    }
+    if (item.type === 'fiber_normal') {
+      // 光纤
+      fiberDataSource.value = item['datalist'].filter((data: any) => {
+        const readData = data.readData;
+        return Object.assign(data, readData);
+      })
+    }
+    if (item.type === 'modelsensor_smoke') {
+      // 烟雾
+      smokeDataSource.value = item['datalist'].filter((data: any) => {
+        const readData = data.readData;
+        return Object.assign(data, readData);
+      })
+    }
+    if (item.type === 'modelsensor_temperature') {
+      // 温度
+      temperatureDataSource.value = item['datalist'].filter((data: any) => {
+        const readData = data.readData;
+        return Object.assign(data, readData);
+      })
+    }
+    if (item.type === 'modelsensor_fire') {
+      // 温度
+      fireDataSource.value = item['datalist'].filter((data: any) => {
+        const readData = data.readData;
+        return Object.assign(data, readData);
+      })
+    }
+    if (item.type === 'sys') {
+      // 硐室基本
+      // fiberDataSource.value = item.filter((data: any) => {
+      //   const readData = data.readData;
+      //   return Object.assign(data, readData);
+      // })
+    }
+  })
+}
+
+
+// 喷粉操作
+function handleDust() {
+  //
+}
+
+onBeforeMount(() => {
+
+});
+
+onMounted(() => {
+  loading.value = true;
+  mountedThree().then(async () => {
+    await setModelType('chamberBase');
+    loading.value = false;
+    timer = null
+    await getMonitor()
+  });
+});
+onUnmounted(() => {
+  destroy();
+  if (timer) {
+    clearTimeout(timer);
+    timer = undefined;
+  }
+});
+</script>
+<style lang="less" scoped>
+@import '/@/design/vent/modal.less';
+@ventSpace: zxm;
+
+.monitor-container {
+  width: 100%;
+  height: 100%;
+  // height: 550px;
+  // border: 1px solid #fff;
+  margin-top: 40px;
+  display: flex;
+  justify-content: space-between;
+
+  .lr {
+    width: 380px;
+    height: 100%;
+    display: flex;
+    flex-direction: column;
+    margin-top: 10px;
+    pointer-events: auto;
+
+  }
+
+  .right-box {
+    width: 320px;
+  }
+
+  .left-box {
+    margin-top: 30px;
+
+    .monitor-info {
+      padding: 0 10px;
+      .temperature-group {
+        width: 100%;
+        display: flex;
+        flex-direction: row;
+        justify-content: space-between;
+        margin-top: 10px;
+        
+
+        .temperature-item {
+          display: flex;
+          justify-content: center;
+          align-items: center;
+
+          .icon {
+            display: flex;
+            justify-content: center;
+            align-items: center;
+            border-radius: 25px;
+            background: #00d4f944;
+            padding: 8px;
+            margin-right: 5px;
+          }
+
+          .temperature {
+            color: #fff;
+
+            span {
+              display: block;
+
+            }
+          }
+
+          .value {
+            font-family: 'douyuFont';
+            color: #20dbfd;
+            text-shadow: 0 0 25px #00d8ff;
+            font-size: 13px;
+          }
+        }
+      }
+
+      .warning-group {
+        padding: 10px 0;
+        margin-top: 10px;
+
+        .warning-item {
+          display: flex;
+          justify-content: space-between;
+          height: 40px;
+
+          span {
+            display: inline-block;
+          }
+
+          .value {
+            width: 20px;
+          }
+        }
+        .alarm-box{
+          margin-top: 10px;
+          
+        }
+        .title {
+          width: 110px;
+          margin-left: 8px;
+        }
+      }
+
+    }
+  }
+
+  .item-box{
+    margin-bottom: 15px;
+  }
+  .box-title{
+    color: #fff;
+    margin-bottom: 8px;
+    padding-left: 10px;
+    position: relative;
+    font-size: 16px;
+    &::after{
+      content: '';
+      position: absolute;
+      display: block;
+      width: 4px;
+      height: 12px;
+      top: 7px;
+      left: 0px;
+      background: #45d3fd;
+      border-radius: 4px;
+    }
+  }
+
+}
+
+:deep(.@{ventSpace}-tabs-tabpane-active) {
+  overflow: auto;
+}
+
+.input-box {
+  display: flex;
+  align-items: center;
+  padding-left: 10px;
+
+  .input-title {
+    color: #73e8fe;
+    width: auto;
+  }
+
+  .@{ventSpace}-input-number {
+    border-color: #ffffff88 !important;
+  }
+
+  margin-right: 10px;
+}</style>

+ 17 - 0
src/views/vent/monitorManager/compressor/components/nitrogenAlarmHistory.vue

@@ -0,0 +1,17 @@
+<template>
+  <div class="alarm-history">
+     <AlarmHistoryTable columns-type="alarm" device-type="nitrogen" :device-list-api="getTableList.bind(null, { devicekind: 'nitrogen' })" designScope="alarm-history" />
+  </div>
+</template>
+<script setup lang="ts">
+  import AlarmHistoryTable from '../../comment/AlarmHistoryTable.vue';
+  import { getTableList } from '../nitrogen.api'
+
+</script>
+<style lang="less" scoped>
+  .alarm-history{
+    width: 100%;
+    position: fixed;
+    top: 80px;
+  }
+</style>

+ 246 - 0
src/views/vent/monitorManager/compressor/components/nitrogenEcharts.vue

@@ -0,0 +1,246 @@
+<template>
+  <div class="nitrogen-echatrs-box">  
+    <div class="button-box">
+      <div style="color: #fff; font-weight: 600;">{{ currentTime }}</div>
+    </div>
+    <div class="echarts-container">
+      <div v-for="(item, index) in dataSource" :key="index" class="echarts-item">
+        <div class="echarts-nitrogen-item">
+          <BarAndLine
+          xAxisPropType="readTime"
+          :dataSource="item"
+          height="100%"
+          :chartsColumns="nitrogenChartsColumns"
+          :option="nitrogenOption"
+          chartsType="detail" />
+        </div>
+        <div class="echarts-cqg-item">
+          <BarAndLine
+            xAxisPropType="readTime"
+            :dataSource="item"
+            height="100%"
+            :chartsColumns="cqgChartsColumns"
+            :option="cqgOption"
+            chartsType="detail" />
+        </div>
+      </div>    
+    </div>
+    </div>
+  </template>
+  <script setup lang="ts">
+    import{ref, onMounted, onUnmounted, reactive } from 'vue'
+    import dayjs from 'dayjs'
+    import BarAndLine from '/@/components/chart/BarAndLine.vue';
+    import { list } from '../nitrogen.api'
+
+    const nitrogenChartsColumns = [
+      {
+        legend: '空压机排气压力',
+        seriesName: '(Mpa)',
+        ymax: 100,
+        yname: 'Pa',
+        linetype: 'line',
+        yaxispos: 'left',
+        color: '#9BCB75',
+        sort: 1,
+        xRotate: 0,
+        dataIndex: 'compressExhaustPressF1',
+      },
+      {
+        legend: '空压机分离压力',
+        seriesName: '(Mpa)',
+        ymax: 100,
+        yname: 'Mpa',
+        linetype: 'line',
+        yaxispos: 'left',
+        color: '#1EB0FC',
+        sort: 1,
+        xRotate: 0,
+        dataIndex: 'compressSeparatePressF1',
+      },
+      {
+        legend: '空压机主机温度',
+        seriesName: '(℃)',
+        ymax: 200,
+        yname: '℃',
+        linetype: 'line',
+        yaxispos: 'right',
+        color: '#FDB146',
+        sort: 2,
+        xRotate: 0,
+        dataIndex: 'compressHostTempF1',
+      },
+      {
+        legend: '空压机机组温度',
+        seriesName: '(℃)',
+        ymax: 200,
+        yname: '℃',
+        linetype: 'line',
+        yaxispos: 'right',
+        color: '#EE6666',
+        sort: 2,
+        xRotate: 0,
+        dataIndex: 'compressCrewTempF1',
+      },
+    ];
+    const cqgChartsColumns = [
+    {
+      legend: '储气罐压力',
+      seriesName: '(Mpa)',
+      ymax: 100,
+      yname: 'Pa',
+      linetype: 'line',
+      yaxispos: 'left',
+      color: '#9BCB75',
+      sort: 1,
+      xRotate: 0,
+      dataIndex: 'airReceiverPress',
+    },
+    {
+      legend: '储气罐温度',
+      seriesName: '(℃)',
+      ymax: 100,
+      yname: '℃',
+      linetype: 'line',
+      yaxispos: 'right',
+      color: '#DD7FCD',
+      sort: 2,
+      xRotate: 0,
+      dataIndex: 'airReceiverTemp',
+    },
+    {
+      legend: '储气罐流量',
+      seriesName: '(m³/k)',
+      ymax: 200,
+      yname: 'm³/k',
+      linetype: 'line',
+      yaxispos: 'right',
+      color: '#56B1FD',
+      sort: 3,
+      xRotate: 0,
+      dataIndex: 'airReceiverFlow',
+    },
+  ];
+
+    const nitrogenOption = {
+      grid: {
+        top: '20%',
+        left: '2px',
+        right: '0px',
+        bottom: '3%',
+        containLabel: true
+      },
+      toolbox: {
+        feature:{
+          
+        }
+      }
+    }
+
+    const cqgOption = {
+      grid: {
+        top: '20%',
+        left: '0px',
+        right: '40px',
+        bottom: '3%',
+        containLabel: true
+      },
+      toolbox: {
+        feature: {
+
+        }
+      }
+    }
+
+    const currentTime = ref(dayjs().format('YYYY-MM-DD HH:mm:ss'))
+    // const monitorData = [] as any[]
+    const dataSource = reactive<any[]>([])
+
+    // https获取监测数据
+    let timer: null | NodeJS.Timeout = null;
+    let count = 0
+    async function getMonitor() {
+      if (Object.prototype.toString.call(timer) === '[object Null]') {
+        timer = await setTimeout(async () => {
+          await getDataSource();
+          if (timer) {
+            timer = null;
+          }
+          await getMonitor();
+        }, 1000);
+      }
+    };
+
+    async function getDataSource() {
+      
+      const res = await list({ devicetype: 'pressurefan', pagetype: 'normal' });
+      const dataList = res.msgTxt[0].datalist || [];
+      dataList.forEach((data, index) => {
+        const item = data.readData;
+        Object.assign(item, data);
+        item['readTime'] = item['readTime'].substring(11)
+        if(count == 0){
+          dataSource.push([item])
+        }else {
+          if(dataSource[index].length < 10){
+            dataSource[index].push(item)
+          }else {
+            dataSource[index].shift()
+            dataSource[index].push(item)
+          }
+        }
+      });
+      count++ // 实时数据累计
+      console.log('实时监测数据----->', dataSource)
+    };
+
+    onMounted(() => {
+      getMonitor()
+    })
+
+    onUnmounted(() => {
+      if (timer) {
+        clearTimeout(timer);
+        timer = undefined;
+      }
+    });
+
+</script>
+<style lang="less" scoped>
+  .button-box{
+    text-align: end;
+    margin-top: 10px;
+    margin-right: 10px;
+  }
+  .nitrogen-echatrs-box{
+    width: 100%;
+    height: 100%;
+  }
+  .echarts-container{
+    position: fixed;
+    width: 100%;
+    height: 730px;
+    color: #fff;
+    overflow: scroll;
+    top: 100px;
+    display: flex;
+    flex-direction: column;
+    z-index: 99999;
+    
+    .echarts-item{
+      display: flex;
+      flex-direction: row;
+      justify-content: space-between;
+      .echarts-nitrogen-item{
+        width: calc(60% - 30px);
+        height: 240px;
+      }
+      .echarts-cqg-item{
+        width: calc(40% - 10px);
+        height: 240px;
+      }
+    }
+    
+  }
+
+</style>

+ 16 - 0
src/views/vent/monitorManager/compressor/components/nitrogenHandleHistory.vue

@@ -0,0 +1,16 @@
+<template>
+  <div class="handle-history">
+     <HandlerHistoryTable columns-type="operatorhistory" device-type="nitrogen" :device-list-api="getTableList.bind(null, {devicekind :'nitrogen'})" designScope="nitrogen_history" />
+  </div>
+</template>
+<script setup lang="ts">
+  import HandlerHistoryTable from '../../comment/HandlerHistoryTable.vue';
+  import { getTableList } from '../nitrogen.api'
+
+</script>
+<style lang="less" scoped>
+  .handle-history{
+    position: fixed;
+    top: 80px;
+  }
+</style>

+ 16 - 0
src/views/vent/monitorManager/compressor/components/nitrogenHistory.vue

@@ -0,0 +1,16 @@
+<template>
+  <div class="nitrogen-history">
+     <HistoryTable columns-type="nitrogen" device-type="nitrogen" :device-list-api="getTableList.bind(null, {devicekind :'nitrogen'})" designScope="nitrogen_auto_history" />
+  </div>
+</template>
+<script setup lang="ts">
+  import HistoryTable from '../../comment/HistoryTable.vue';
+  import { getTableList } from '../nitrogen.api'
+
+</script>
+<style lang="less" scoped>
+  .nitrogen-history{
+    position: fixed;
+    top: 80px;
+  }
+</style>

+ 642 - 0
src/views/vent/monitorManager/compressor/components/nitrogenHome.vue

@@ -0,0 +1,642 @@
+<template>
+    <div id="nitrogenCss3D" class="threejs-Object-CSS"
+      style="width: 100%; height: 100%; position: absolute; pointer-events: none; overflow: hidden; z-index: 2; top: 0px; left: 0px">
+      <a-spin :spinning="loading" />
+      <div v-for="groupNum in monitorDataGroupNum" :key="groupNum" class="modal-monitor">
+        <fourBorderBg :class="`kyj${groupNum}`" :id="`nitrogenMonitor${groupNum}`">
+          <div class="title">{{ groupNum }}号制氮机 </div>
+          <div class="monitor-item">
+            <span class="monitor-title">注氮压力:</span>
+            <span class="monitor-val"><span class="val">{{ monitorData[groupNum - 1]['nitrogenPressure'] ?
+              monitorData[groupNum - 1]['nitrogenPressure'] : '-' }}</span><span class="unit">Mpa</span></span>
+          </div>
+          <div class="monitor-item">
+            <span class="monitor-title">氮气实时流量:</span>
+            <span class="monitor-val"><span class="val">{{ monitorData[groupNum - 1]['instantaneousFlow'] ?
+              monitorData[groupNum - 1]['instantaneousFlow'] : '-' }}</span><span class="unit">m³/h</span></span>
+          </div>
+          <div class="monitor-item">
+            <span class="monitor-title">氮气浓度:</span>
+            <span class="monitor-val"><span class="val">{{ monitorData[groupNum - 1]['nitrogenContent'] ?
+              monitorData[groupNum - 1]['nitrogenContent'] : '-' }}</span><span class="unit">%</span></span>
+          </div>
+          <div class="signal-item">
+            <div class="signal"><span class="monitor-title">运行信号</span><span
+                :class="{ 'signal-round': true, 'signal-round-run': monitorData[groupNum - 1]['compressRunSigF1'], 'signal-round-gry': !monitorData[groupNum - 1]['compressRunSigF1'] }"></span>
+            </div>
+            <div class="signal"><span class="monitor-title">加载信号</span><span
+                :class="{ 'signal-round': true, 'signal-round-run': monitorData[groupNum - 1]['compressLoadSigF1'], 'signal-round-gry': !monitorData[groupNum - 1]['compressLoadSigF1'] }"></span>
+            </div>
+          </div>
+        </fourBorderBg>
+      </div>
+    </div>
+    <div id="nitrogen3D" style="width: 100%; height: 100%; position: absolute; overflow: hidden"></div>
+    <div class="nitrogen-home">
+      <div class="nitrogen-container">
+        <div class="top-box">
+          <!-- 左边监测数据 -->
+          <div class="lr-box left-box">
+            <div class="item item-l" v-for="groupNum in monitorDataGroupNum" :key="groupNum">
+              <ventBox1>
+                <template #title>
+                  <div>{{ groupNum }}号制氮机组</div>
+                </template>
+                <template #container>
+                  <div class="monitor-box">
+                    <div class="parameter-title group-parameter-title"><SvgIcon class="icon" size="38" name="device-group-paramer"/><span>机组参数</span></div>
+                    <div class="state-item" v-for="(data, index) in groupParameterData" :key="index">
+                      <div class="item-col">
+                        <span class="state-title">{{ Object.values(data)[0] }} :</span>
+                        <span class="state-val">{{ (monitorData.length > 0 && monitorData[groupNum - 1][Object.keys(data)[0]])
+                          >= 0 ? monitorData[groupNum - 1][Object.keys(data)[0]] : '-' }}</span>
+                      </div>
+                      <div class="item-col">
+                        <span class="state-title">{{ Object.values(data)[1] }} :</span>
+                        <span class="state-val">{{ (monitorData.length > 0 && monitorData[groupNum - 1][Object.keys(data)[1]])
+                          >= 0 ? monitorData[groupNum - 1][Object.keys(data)[1]] : '-' }}</span>
+                      </div>
+                    </div>
+                  </div>
+                  <div class="monitor-box">
+                    <div class="parameter-title device-parameter-title"><SvgIcon class="icon" size="32" name="device-paramer"/><span>电机数据</span></div>
+                    <div class="state-item" v-for="(data, index) in deviceParameterData" :key="index">
+                      <div class="item-col">
+                        <span class="state-title">{{ Object.values(data)[0] }} :</span>
+                        <span class="state-val">{{ (monitorData.length > 0 && monitorData[groupNum - 1][Object.keys(data)[0]])
+                          >= 0 ? monitorData[groupNum - 1][Object.keys(data)[0]] : '-' }}</span>
+                      </div>
+                      <div class="item-col">
+                        <span class="state-title">{{ Object.values(data)[1] }} :</span>
+                        <span class="state-val">{{ (monitorData.length > 0 && monitorData[groupNum - 1][Object.keys(data)[1]])
+                          >= 0 ? monitorData[groupNum - 1][Object.keys(data)[1]] : '-' }}</span>
+                      </div>
+                    </div>
+                  </div>
+                </template>
+              </ventBox1>
+            </div>
+          </div>
+          <!-- 右边控制状态 -->
+          <div class="lr-box right-box">
+            <ventBox1>
+              <template #title>
+                <div>远程控制</div>
+              </template>
+              <template #container>
+                <div class="control-group">
+                  <div class="control-item" v-for="groupNum in monitorDataGroupNum" :key="groupNum">
+                    <div class="control-item-title">{{ zdjs[groupNum - 1] }}</div>
+                    <div class="control-item-state">
+                      <a-switch v-model="airCompressorState[groupNum - 1][`compressRunSigF1`]" size="small"  checked-children="开启"
+                        un-checked-children="关闭" :disabled="airCompressorState[groupNum - 1][`controlModel`]"
+                        @change="handlerDevice(airCompressorState[groupNum - 1])">
+                      </a-switch>
+                    </div>
+                  </div>
+                  <div class="control-item" v-for="groupNum in monitorDataGroupNum" :key="groupNum">
+                    <div class="control-item-title">{{ kyjs[groupNum - 1] }}</div>
+                    <div class="control-item-state">
+                      <a-switch v-model="airCompressorState[groupNum - 1][`compressRunSigF1`]" size="small"  checked-children="开启"
+                        un-checked-children="关闭" :disabled="airCompressorState[groupNum - 1][`controlModel`]"
+                        @change="handlerDevice(airCompressorState[groupNum - 1])">
+                      </a-switch>
+                    </div>
+                  </div>
+                  <div class="control-item">
+                    <div class="control-item-title">是否开启联动</div>
+                    <div class="control-item-state">
+                      <a-radio v-model:checked="isLink">开启</a-radio>
+                    </div>
+                  </div>
+                </div>
+              </template>
+            </ventBox1>
+          </div>
+        </div>
+      </div>
+    </div>
+</template>
+<script lang="ts" setup name="nitrogenHome">
+import { onMounted, onUnmounted, ref, watch } from 'vue'
+import ventBox1 from '/@/components/vent/ventBox1.vue'
+import fourBorderBg from '../../../comment/components/fourBorderBg.vue'
+import { mountedThree, destroy } from '../nitrogen.threejs'
+import { list } from '../nitrogen.api'
+import { SvgIcon } from '/@/components/Icon'
+
+const loading = ref(true)
+const isLink = ref(true)
+
+const zdjs = ['1号制氮机', '2号制氮机', '3号制氮机', '4号制氮机'];
+const kyjs = ['1号空压机', '1号空压机', '1号空压机', '1号空压机'];
+
+const monitorDataGroupNum = ref(0);
+
+const airCompressorState = ref([
+  {
+    id: '',
+    compressRunSigF1: false,
+    controlModel: false
+  },
+  {
+    id: '',
+    compressRunSigF1: false,
+    controlModel: false
+  },
+  {
+    id: '',
+    compressRunSigF1: false,
+    controlModel: false
+  },
+  {
+    id: '',
+    compressRunSigF1: false,
+    controlModel: false
+  }
+]);
+
+const groupParameterData = [
+  {
+    cumulativeFlow: '累计流量(m³)',
+    centerTemperature: '加热器中心温度',
+  },
+  {
+    outletTemperature: '加热器出口温度',
+  },
+];
+const deviceParameterData = [
+  {
+    Ia: 'A项电流(A)',
+    Ib: 'B项电流(A)',
+  },
+  {
+    Ic: 'c项电流(A)',
+    Vab: 'AB项间电压(V)',
+  },
+  {
+    Vac: 'AC项间电压(V)',
+    Vbc: 'BC项间电压(V)',
+  },
+];
+
+const monitorData = ref(new Array(3).fill({
+  strName: '空压机',
+  compressGroupName: '',
+  compressExhaustPressF1: '-',
+  compressSeparatePressF1: '-',
+  compressHostTempF1: '-',
+  compressCrewTempF1: '-',
+  compressRunTimeF1: '-',
+  controlModel: 'LOC'
+}));
+
+// https获取监测数据
+let timer: null | NodeJS.Timeout = null;
+async function getMonitor() {
+  if (Object.prototype.toString.call(timer) === '[object Null]') {
+    timer = await setTimeout(async () => {
+      await getDataSource();
+      if (timer) {
+        timer = null;
+      }
+      await getMonitor();
+    }, 1000);
+  }
+};
+
+async function getDataSource() {
+  const res = await list({ devicetype: 'nitrogen_auto', pagetype: 'normal' });
+  const dataSource = res.msgTxt[0].datalist || [];
+  monitorData.value = dataSource.filter((data) => {
+    const item = data.readData;
+    Object.assign(data, item);
+    return item
+  });
+  monitorDataGroupNum.value = monitorData.value.length
+  loading.value = false
+};
+
+function handlerDevice(data) {
+  // if (data.length < 1) return
+  // handleAirCompressor({ id: data.id, compressRunF1: data.compressRunSigF1 }).then(res => {
+  //   if (res.success) {
+  //     message.success('操作成功')
+  //   } else {
+  //     message.warning(data.msg)
+  //   }
+  // })
+};
+function resetDevice(data) {
+
+}
+
+function handlerControlModel(data) {
+
+}
+
+watch(monitorDataGroupNum, (newVal) => {
+  if (newVal) {
+    destroy()
+    mountedThree(newVal)
+  }
+})
+onMounted(async () => {
+  await getMonitor()
+})
+
+onUnmounted(() => {
+  destroy();
+  if (timer) {
+    clearTimeout(timer);
+    timer = undefined;
+  }
+});
+
+</script>
+
+<style lang="less" scoped>
+@ventSpace: zxm;
+
+.nitrogen-box {
+  width: 100%;
+  height: 100%;
+  display: flex;
+  justify-content: center;
+}
+
+#nitrogenCss3D {
+  .modal-monitor {
+    width: 200px;
+    position: absolute;
+    left: 0px;
+    top: 0px;
+  }
+  &:deep(.win) {
+    margin: 0 !important;
+  }
+
+}
+
+.nitrogen-home {
+  width: 100%;
+  height: 100%;
+  position: fixed;
+  z-index: 99;
+  display: flex;
+  flex-direction: column;
+  justify-content: center;
+  align-items: center;
+  pointer-events: none;
+  top: 60px;
+  .nitrogen-container {
+    width: 100%;
+    height: calc(100%);
+    display: flex;
+    justify-content: space-between;
+    margin-bottom: 100px;
+
+    .top-box {
+      width: 100%;
+      padding: 10px;
+      overflow: hidden;
+      display: flex;
+      justify-content: space-between;
+
+      .lr-box {
+        height: fit-content;
+        display: flex;
+        flex-direction: column;
+        position: relative;
+        overflow: hidden;
+        z-index: 9999;
+        pointer-events: auto;
+      }
+
+      .item {
+        width: 335px;
+        height: auto;
+        position: relative;
+        border-radius: 5px;
+        margin-top: 10px;
+        margin-bottom: 0px;
+        pointer-events: auto;
+        color: #fff;
+        overflow: hidden;
+        
+        
+        .base-title {
+          color: #fff;
+          margin-bottom: 8px;
+          padding-left: 10px;
+          position: relative;
+          font-size: 16px;
+          &::after{
+            content: '';
+            position: absolute;
+            display: block;
+            width: 4px;
+            height: 12px;
+            top: 7px;
+            left: 0px;
+            background: #45d3fd;
+            border-radius: 4px;
+          }
+        }
+
+        .state-item {
+          display: flex;
+          flex-direction: row;
+          padding: 5px;
+
+          .item-col {
+            width: calc(50% - 5px);
+            display: flex;
+            justify-content: center;
+            align-items: center;
+            padding-right: 4px;
+            background-image: linear-gradient(to right, #39A3FF00, #39A3FF10);
+            &:first-child{
+              margin-right: 10px;
+            }
+
+            .state-title {
+              color: #ffffffcc;
+              flex: 9;
+              font-size: 14px;
+            }
+
+            .state-val {
+              flex: 1;
+              color: #00eefffe;
+              margin-right: 5px;
+              text-align: right;
+              font-size: 14px;
+            }
+          }
+        }
+
+        .signal-box {
+          margin: 5px 0;
+          display: flex;
+          align-items: center;
+
+          .signal-title {
+            color: #7AF5FF;
+            margin: 0 5px;
+          }
+
+          &:last-child {
+            margin-right: 0px;
+          }
+        }
+
+        .list-item {
+          padding: 0 10px;
+          display: flex;
+          justify-content: space-between;
+          align-items: center;
+
+          .item-data-key {
+            color: #ffffff99;
+          }
+        }
+
+        .item-data-box {
+          color: #fff;
+
+          .state-icon {
+            display: inline-block;
+            width: 12px;
+            height: 12px;
+            border-radius: 12px;
+          }
+
+          .open {
+            border: 5px solid #133a56;
+            background: #4ecb73;
+          }
+
+          .close {
+            border: 5px solid #192961;
+            background: #6d7898;
+          }
+        }
+      }
+
+      .item-l {
+        width: 100%;
+        .monitor-box {
+          width: 100%;
+          .parameter-title{
+            position: relative;
+            width: 100%;
+            height: 14px;
+            margin-top: 10px;
+            .icon, span{
+              position: absolute;
+              top: -10px;
+            }
+            
+          }
+          .group-parameter-title{
+            background-image: linear-gradient(to right, #39a3ff50, #39a3ff00);
+            
+            .icon{
+              left: -12px;
+              top: -17px;
+            }
+            span{
+              left: 18px;
+            }
+            .item-col{
+              background-image: linear-gradient(to right, #39A3FF00, #39A3FF10);
+            }
+          }
+          .device-parameter-title{
+            background-image: linear-gradient(to right, #3df6ff40, #3df6ff00);
+            .icon{
+              left: -10px;
+              top: -14px;
+            }
+            span{
+              left: 18px;
+            }
+            .item-col{
+              background-image: linear-gradient(to right, #3df6ff10, #3df6ff00);
+            }
+          }
+          
+        }
+      }
+
+      .right-box {
+        .control-group{
+          display: flex;
+          // justify-content: space-around;
+          flex-wrap: wrap;
+          .control-item {
+            
+            display: flex;
+            flex-direction: column;
+            justify-content: center;
+            align-items: center;
+            padding: 0 13px;
+            .control-item-title{
+              color: #A6DCE9;
+              position: relative;
+              top: 5px;
+            }
+            .control-item-state{
+              width: 94px;
+              height: 47px;
+              background: url('/@/assets/images/vent/control-switch-bg.png');
+              display: flex;
+              justify-content: center;
+              align-items: center;
+              color: #fff;
+            }
+          
+            .button-box {
+              position: relative;
+              padding: 5px;
+              border: 1px transparent solid;
+              background-clip: border-box;
+              border-radius: 5px;
+              margin-left: 8px;
+            }
+
+            .a-button {
+              pointer-events: auto;
+            }
+
+            &::v-deep .a-button--mini {
+              padding: 6px 10px;
+            }
+
+            &::v-deep .a-button--mini.is-round {
+              padding: 6px 10px;
+            }
+          }
+
+        }
+      }
+
+      .left-box {
+        width: 385px;
+        
+      }
+    }
+    &:deep(.win) {
+      width: 100%;
+      margin: 0 !important;
+    }
+  }
+  
+}
+  
+
+  &:deep(.main) {
+    .title {
+      height: 34px;
+      text-align: center;
+      font-weight: 600;
+      color: #7AF5FF;
+      // background-image: url('../../../assets/img/yfj/light.png');
+      background-repeat: no-repeat;
+      background-position-x: center;
+      background-position-y: 100%;
+      background-size: 80%;
+      font-size: 16px;
+    }
+
+    .monitor-item {
+      width: 200px;
+      display: flex;
+      flex-direction: row;
+      width: auto;
+      margin-bottom: 3px;
+
+      .monitor-val {
+        color: #ffb700;
+        display: flex;
+        width: auto;
+
+        .val {
+          width: 80px;
+          font-size: 14px;
+        }
+
+        .unit {
+          color: #ffffffbb;
+          font-size: 14px;
+
+        }
+      }
+    }
+
+    .monitor-title {
+      width: 100px;
+      color: #7AF5FF;
+      font-weight: 400;
+      font-size: 14px;
+    }
+
+    .signal-item {
+      display: flex;
+      justify-content: space-between;
+
+      // margin-bottom: 5px;
+      .signal-round {
+        display: inline-block;
+        width: 8px;
+        height: 8px;
+        border-radius: 50%;
+        margin: 0 10px;
+        position: relative;
+
+        &::after {
+          display: block;
+          content: '';
+          position: absolute;
+          width: 12px;
+          height: 12px;
+          top: -2px;
+          left: -2px;
+          border-radius: 50%;
+        }
+      }
+
+      .signal-round-gry {
+        background-color: #858585;
+
+        &::after {
+          background-color: #85858544;
+          box-shadow: 0 0 1px 1px #85858599;
+        }
+      }
+
+      .signal-round-run {
+        background-color: #67FC00;
+
+        &::after {
+          background-color: #67FC0044;
+          box-shadow: 0 0 1px 1px #c6ff77;
+        }
+      }
+
+      .signal-round-warning {
+        background-color: #E9170B;
+
+        &::after {
+          background-color: #E9170B44;
+          box-shadow: 0 0 1px 1px #E9170B;
+        }
+      }
+    }
+  }
+  :deep(.zxm-radio-wrapper){
+    color: #fff !important;
+  }
+
+</style>

+ 109 - 0
src/views/vent/monitorManager/compressor/index.vue

@@ -0,0 +1,109 @@
+<template>
+  <div class="nitrogen-box">
+    <div class="nitrogen-home-header">
+      <Decoration5 class="header-icon" :dur="2" :color="['#21437F', '#2CF7FE']" style="width:500px;height:40px;" />
+      <div class="header-text">智能注氮管控系统</div>
+    </div>
+    <nitrogenHome v-if="btnActive == 'nitrogen_page'" />
+    <nitrogenEcharts v-if="btnActive == 'yfj_monitor_echarts'"/>
+    <nitrogenHistory v-if="btnActive == 'yfj_history'"/>
+    <nitrogenHandleHistory v-if="btnActive == 'yfj_handler_history'"/>
+    <nitrogenAlarmHistory v-if="btnActive == 'yfj_faultRecord'"/>
+    <BottomMenu :nav-list="navList" @change="changeActive"/>
+  </div>
+  
+</template>
+<script lang="ts" setup>
+import { ref } from 'vue'
+import nitrogenHome from './components/nitrogenHome.vue'
+import nitrogenEcharts from './components/nitrogenEcharts.vue'
+import nitrogenHistory from './components/nitrogenHistory.vue'
+import nitrogenHandleHistory from './components/nitrogenHandleHistory.vue'
+import nitrogenAlarmHistory from './components/nitrogenAlarmHistory.vue'
+import { Decoration5 } from '@kjgl77/datav-vue3'
+import BottomMenu from '/@/views/vent/comment/components/bottomMenu.vue';
+
+const btnActive = ref('nitrogen_page')
+const navList = ref([
+  {
+    title: '监控界面',
+    pathName: 'nitrogen_page',
+    isHover: true
+  },
+  {
+    title: '历史监测记录',
+    pathName: 'yfj_history',
+    isHover: false
+  },
+  {
+    title: '操作历史记录',
+    pathName: 'yfj_handler_history',
+    isHover: false
+  },
+  {
+    title: '故障诊断历史记录',
+    pathName: 'yfj_faultRecord',
+    isHover: false
+  }
+])
+
+
+function changeActive(activeValue) {
+  btnActive.value = activeValue
+}
+
+</script>
+
+<style lang="less" scoped>
+@ventSpace: zxm;
+.nitrogen-home-header {
+    width: 100%;
+    height: 100px;
+    position: fixed;
+    top: 0;
+    background: url('/@/assets/images/vent/new-home/header-bg.png') no-repeat;
+    background-size: contain;
+    display: flex;
+    justify-content: center;
+
+    .header-icon {
+      margin-top: 45px;
+    }
+
+    .header-text {
+      position: fixed;
+      top: 18px;
+      color: #fff;
+      font-size: 24px;
+    }
+  }
+.nitrogen-box{
+  width: 100%;
+  height: 100%;
+  display: flex;
+  justify-content: center;
+  .bottom-btn-group {
+    display: flex;
+    position: fixed;
+    width: calc(100% - 400px);
+    height: 100px;
+    bottom: 10px;
+    align-items: center;
+    justify-content: center;
+    z-index: 2;
+    .btn-item {
+      width: 200px;
+      height: 60px;
+      margin: 10px;
+      color: #fff;
+      cursor: pointer;
+      pointer-events: auto;
+    }
+  }
+  &:deep(.win) {
+    margin: 0 !important;
+  }
+}
+
+
+</style>

+ 17 - 0
src/views/vent/monitorManager/compressor/nitrogen.api.ts

@@ -0,0 +1,17 @@
+import { defHttp } from '/@/utils/http/axios';
+
+enum Api {
+  list = '/monitor/device',
+  baseList = '/safety/ventanalyDeviceInfo/list',
+}
+/**
+ * 列表接口
+ * @param params
+ */
+export const list = (params) => defHttp.post({ url: Api.list, params });
+
+/**
+ * 保存或者更新用户
+ * @param params
+ */
+export const getTableList = (params) => defHttp.get({ url: Api.baseList, params });

+ 72 - 0
src/views/vent/monitorManager/compressor/nitrogen.data.ts

@@ -0,0 +1,72 @@
+import { ref } from 'vue';
+export const bottomBtnList = ref([
+  {
+    text: '监控界面',
+    value: 'nitrogenMonitor',
+    isHover: false,
+  },
+  {
+    text: '关键节点监测',
+    value: 'nitrogenNode',
+    isHover: false,
+  },
+  {
+    text: '实时曲线',
+    value: 'nitrogenEcharts',
+    isHover: false,
+  },
+  {
+    text: '压风机历史记录',
+    value: 'nitrogenHistory',
+    isHover: false,
+  },
+  {
+    text: '操作历史记录',
+    value: 'nitrogenHandleHistory',
+    isHover: false,
+  },
+  {
+    text: '故障诊断历史记录',
+    value: 'nitrogenWarningHistory',
+    isHover: false,
+  },
+]);
+
+
+      // dataInfo: {
+      //   controlModel: false
+      // };
+      // videoUrl: '',
+      // isDestroyVideo: false,
+      // navList: [
+      //   {
+      //     title: '监控界面',
+      //     pathName: 'nitrogen_page_lh'
+      //   },
+      //   // {
+      //   //   title: '关键节点监测',
+      //   //   pathName: 'critical_node'
+      //   // },
+      //   {
+      //     title: '实时曲线',
+      //     pathName: 'yfj_monitor_echarts_cy'
+      //   },
+      //   {
+      //     title: '压风机历史记录',
+      //     pathName: 'yfj_history'
+      //   },
+      //   {
+      //     title: '操作历史记录',
+      //     pathName: 'yfj_handler_history'
+      //   },
+      //   {
+      //     title: '故障诊断历史记录',
+      //     pathName: 'yfj_faultRecord'
+      //   }
+      // ],
+      // kyjMonitorDataKey: new Map(
+      //   [
+      //     ['压力', 'pressure'],
+      //     ['温度', 'temp'],
+      //   ]
+      // ),

+ 239 - 0
src/views/vent/monitorManager/compressor/nitrogen.dishang.threejs.ts

@@ -0,0 +1,239 @@
+import * as THREE from 'three';
+import { getTextCanvas, addEnvMap, setModalCenter } from '/@/utils/threejs/util';
+import { CSS3DSprite } from 'three/examples/jsm/renderers/CSS3DRenderer.js';
+// import * as dat from 'dat.gui';
+// const gui = new dat.GUI();
+// gui.domElement.style = 'position:absolute;top:100px;left:10px;z-index:99999999999999';
+
+class Nitrogen {
+  model;
+  modelName = 'nitrogen';
+  group: THREE.Object3D = new THREE.Object3D();
+  animationTimer;
+  isLRAnimation = true;
+  direction = 1;
+  playerStartClickTime1 = new Date().getTime();
+  playerStartClickTime2 = new Date().getTime();
+  deviceRunState = '';
+  nitrogenNum = 0;
+
+  constructor(model) {
+    this.model = model;
+    this.group.name = this.modelName;
+  }
+
+  addLight() {
+    const directionalLight = new THREE.DirectionalLight(0xffffff, 0.8);
+    directionalLight.position.set(75, 96, 177);
+    this.group.add(directionalLight);
+    directionalLight.target = this.group;
+
+    // gui.add(directionalLight.position, 'x', -500, 500).onChange(function (value) {
+    //   directionalLight.position.x = Number(value);
+    // });
+    // gui.add(directionalLight.position, 'y', -500, 500).onChange(function (value) {
+    //   directionalLight.position.y = Number(value);
+    // });
+    // gui.add(directionalLight.position, 'z', -200, 200).onChange(function (value) {
+    //   directionalLight.position.z = Number(value);
+    // });
+
+    const spotLight = new THREE.SpotLight();
+    spotLight.angle = Math.PI / 4;
+    spotLight.penumbra = 0;
+    spotLight.castShadow = true;
+    spotLight.distance = 0;
+    spotLight.position.set(-88, 85, -88);
+    spotLight.target = this.group;
+    this.group.add(spotLight);
+
+    spotLight.shadow.camera.near = 0.5; // default
+    spotLight.shadow.camera.far = 1000; // default
+    spotLight.shadow.focus = 1;
+    spotLight.shadow.bias = -0.000002;
+
+    // gui.add(spotLight.position, 'x', -800, 800).onChange(function (value) {
+    //   spotLight.position.x = Number(value);
+    // });
+    // gui.add(spotLight.position, 'y', -800, 800).onChange(function (value) {
+    //   spotLight.position.y = Number(value);
+    // });
+    // gui.add(spotLight.position, 'z', -800, 800).onChange(function (value) {
+    //   spotLight.position.z = Number(value);
+    // });
+  }
+
+  // 设置模型位置
+  setModalPosition() {
+    if (this.nitrogenNum == 4) {
+      this.group.position.set(0, -17, 3);
+      this.group?.scale.set(24.0, 24.0, 24.0);
+    }
+    if (this.nitrogenNum == 3) {
+      this.group.position.set(0, -1, 3);
+      this.group?.scale.set(24.0, 24.0, 24.0);
+    }
+    if (this.nitrogenNum == 2) {
+      this.group.position.set(0, 0.42, 1.21);
+      this.group?.scale.set(24.0, 24.0, 24.0);
+    }
+    if (this.nitrogenNum == 1) {
+      this.group.position.set(0, 8, 3.0);
+      this.group?.scale.set(24.0, 24.0, 24.0);
+    }
+  }
+
+  /* 提取风门序列帧,初始化前后门动画 */
+  initAnimation() {}
+
+  /* 点击 */
+  mousedownModel(intersects: THREE.Intersection<THREE.Object3D<THREE.Event>>[]) {
+    this.isLRAnimation = false;
+    if (this.animationTimer) {
+      clearTimeout(this.animationTimer);
+      this.animationTimer = null;
+    }
+
+    intersects.find((intersect) => {
+      const mesh = intersect.object;
+
+      return false;
+    });
+  }
+
+  mouseUpModel() {}
+
+  // 播放动画
+  play() {}
+  /**
+   * 生序排列模型的子元素
+   */
+  sortMeshChildren = (children: THREE.Mesh[]) => {
+    //生序排列
+    children.sort((x, y) => {
+      return x.geometry.attributes.position.count - y.geometry.attributes.position.count;
+    });
+    return children;
+  };
+
+  /**
+   * 设置模型透明
+   */
+  transparentModel = (model: THREE.Mesh) => {
+    const transparentMaterial = new THREE.MeshBasicMaterial({
+      transparent: true,
+      opacity: 0,
+    });
+    model.material = transparentMaterial;
+  };
+
+  addCssText = () => {
+    if (this.nitrogenNum > 0) {
+      for (let i = 0; i < this.nitrogenNum; i++) {
+        const nitrogenModal = this.group.getObjectByName('nitrogenModal' + i) as THREE.Object3D;
+        if (!nitrogenModal.getObjectByName('monitorNitrogenText')) {
+          const element = document.getElementById('nitrogenMonitor' + (i + 1)) as HTMLElement;
+          element.style.top = '0px';
+          element.style.left = '0px';
+          const nitrogenMonitorCSS3D = new CSS3DSprite(element);
+          nitrogenMonitorCSS3D.name = 'monitorNitrogenText';
+          nitrogenMonitorCSS3D.scale.set(0.003, 0.003, 0.003);
+          if (i == 0) nitrogenMonitorCSS3D.position.set(-0.89, 0.31, 0);
+          if (i == 1) nitrogenMonitorCSS3D.position.set(-0.89, 0.31, 0.04);
+          if (i == 2) nitrogenMonitorCSS3D.position.set(-0.89, 0.31, 0.08);
+          if (i == 3) nitrogenMonitorCSS3D.position.set(-0.89, 0.31, 0.12);
+          nitrogenModal.add(nitrogenMonitorCSS3D);
+        }
+        // if (!nitrogenModal.getObjectByName('cqgMonitorText')) {
+        //   const element = document.getElementById('cqgMonitor' + (i + 1)) as HTMLElement;
+        //   element.style.top = '0px';
+        //   element.style.left = '0px';
+        //   const cqgMonitorCSS3D = new CSS3DSprite(element);
+        //   cqgMonitorCSS3D.name = 'cqgMonitorText';
+        //   cqgMonitorCSS3D.scale.set(0.003, 0.003, 0.003);
+        //   if (i == 0) cqgMonitorCSS3D.position.set(1.24, 0.49, 0.0);
+        //   if (i == 1) cqgMonitorCSS3D.position.set(1.24, 0.49, 0.04);
+        //   if (i == 2) cqgMonitorCSS3D.position.set(1.24, 0.49, 0.08);
+        //   if (i == 3) cqgMonitorCSS3D.position.set(1.24, 0.49, 0.12);
+        //   nitrogenModal.add(cqgMonitorCSS3D);
+        // }
+      }
+    }
+  };
+
+  /**
+   * 处理杯子的纹理和杯子外层透明壳子
+   */
+  handleGlassAndWrap = (
+    objects: THREE.Object3D,
+    withVolume: THREE.Object3D[],
+    glassModel: THREE.Mesh,
+    params: THREE.MeshPhysicalMaterialParameters,
+    scale: number,
+    position: THREE.Vector3,
+    rotation?: THREE.Vector3
+  ) => {
+    //辨别杯子和壳 大的是杯子 小的是壳 壳的点比杯子少
+    const children = glassModel.children as THREE.Mesh[];
+    this.sortMeshChildren(children);
+
+    children.forEach((mesh) => {
+      mesh.position.copy(position);
+      mesh.scale.set(scale, scale, scale);
+      rotation && mesh.rotation.setFromVector3(rotation, 'XYZ');
+    });
+
+    const [transparentWrap, glass] = children;
+
+    this.transparentModel(transparentWrap);
+
+    glass.material = new THREE.MeshPhysicalMaterial({
+      side: THREE.DoubleSide,
+      // specularColor: new Color("#ffffff"),
+      // color: new Color(0xffa000),
+      ...params,
+    });
+
+    objects.add(...children);
+    //只检测壳子 减小开销
+    withVolume.push(transparentWrap);
+  };
+
+  mountedThree(nitrogenNum) {
+    this.nitrogenNum = nitrogenNum;
+    return new Promise((resolve) => {
+      if (nitrogenNum < 1) {
+        resolve(null);
+        return;
+      }
+      this.model.setModel([this.modelName]).then(async (gltf) => {
+        const nitrogenGroup = new THREE.Object3D();
+        for (let i = 0; i < nitrogenNum; i++) {
+          const nitrogenModal = gltf[0].clone();
+          nitrogenModal.name = 'nitrogenModal' + i;
+          nitrogenModal.position.set(0, 0, -1.29 * i);
+          nitrogenGroup.add(nitrogenModal);
+        }
+
+        this.group = nitrogenGroup;
+
+        this.group.name = this.modelName;
+        setModalCenter(this.group);
+        this.addCssText();
+        this.setModalPosition();
+        this.addLight();
+        resolve(null);
+      });
+    });
+  }
+
+  destroy() {
+    if (this.group) {
+      this.model.clearGroup(this.group);
+    }
+    this.model = null;
+    this.group = null;
+  }
+}
+
+export default Nitrogen;

+ 110 - 0
src/views/vent/monitorManager/compressor/nitrogen.threejs.ts

@@ -0,0 +1,110 @@
+import * as THREE from 'three';
+import { animateCamera, setModalCenter, updateAxisCenter } from '/@/utils/threejs/util';
+import UseThree from '../../../../utils/threejs/useThree';
+import Nitrogen from './nitrogen.dishang.threejs';
+import { useAppStore } from '/@/store/modules/app';
+// import * as dat from 'dat.gui';
+// const gui = new dat.GUI();
+// gui.domElement.style = 'position:absolute;top:100px;left:10px;z-index:99999999999999';
+
+// 模型对象、 文字对象
+let model, //
+  group,
+  nitrogenObj,
+  modalType = 'lmWindRect';
+
+const appStore = useAppStore();
+
+// 鼠标点击、松开事件
+const mouseEvent = (event) => {
+  event.stopPropagation();
+  const widthScale = appStore.getWidthScale;
+  const heightScale = appStore.getHeightScale;
+  // 将鼠标位置归一化为设备坐标。x 和 y 方向的取值范围是 (-1 to +1)
+  model.mouse.x =
+    ((-model.canvasContainer.getBoundingClientRect().left * widthScale + event.clientX) / (model.canvasContainer.clientWidth * widthScale)) * 2 - 1;
+  model.mouse.y =
+    -((-model.canvasContainer.getBoundingClientRect().top + event.clientY) / (model.canvasContainer.clientHeight * heightScale)) * 2 + 1;
+  (model.rayCaster as THREE.Raycaster).setFromCamera(model.mouse, model.camera as THREE.Camera);
+
+  // console.log('相机与控制器信息--->', model.camera, model.orbitControls);
+  // updateAxisCenter(model, event);
+
+  if (group) {
+    const intersects = model.rayCaster?.intersectObjects(group.children, false) as THREE.Intersection[];
+    if (intersects.length > 0) {
+      // 单道、 双道
+      if (modalType === 'nitrogen') {
+        nitrogenObj.mousedownModel.call(nitrogenObj, intersects);
+      }
+    }
+    console.log('99999------>', model.camera, model.orbitControls);
+  }
+};
+
+/* 添加监控数据 */
+export const addText = (selectData) => {
+  if (modalType === 'nitrogen') {
+    return nitrogenObj.addText.call(nitrogenObj, selectData);
+  }
+};
+
+export const play = () => {
+  if (modalType === 'nitrogen') {
+    return nitrogenObj.play.call(nitrogenObj);
+  }
+};
+
+// 切换风窗类型
+export const setModelType = (type) => {
+  modalType = type;
+  model.camera.position.set(-1000, 100, 500);
+
+  return new Promise((resolve) => {
+    // 显示双道风窗
+    if (modalType === 'nitrogen') {
+      group = nitrogenObj.group;
+      // setModalCenter(group);
+      const oldCameraPosition = { x: -1000, y: 100, z: 500 };
+      const oldControlsPosition = { x: -10, y: 10, z: 10 };
+      model.scene.add(nitrogenObj.group);
+      setTimeout(async () => {
+        resolve(null);
+        await animateCamera(
+          oldCameraPosition,
+          oldControlsPosition,
+          // { x: -0.24658823766780538, y: 35.50352092191473, z: 83.90511756512278 },
+          // { x: -0.24658823766780538, y: 35.50352092191473, z: 83.90511756512278 },
+          // { x: 1.5582599568763913, y: -3.2828007511721147, z: 2.2606374587827234 },
+          { x: 0.2528210526315788, y: 38.731704155124646, z: 66.23189916897506 },
+          { x: 0, y: 0, z: 0 },
+          model,
+          0.8
+        );
+      }, 300);
+    }
+  });
+};
+
+export const mountedThree = (nitrogenNum) => {
+  return new Promise(async (resolve) => {
+    model = new UseThree('#nitrogen3D', '#nitrogenCss3D');
+    model.setEnvMap('test1');
+    model.renderer.toneMappingExposure = 0.8;
+    model.canvasContainer?.addEventListener('mousedown', mouseEvent.bind(null));
+    nitrogenObj = new Nitrogen(model);
+    await nitrogenObj.mountedThree(nitrogenNum);
+    setModelType('nitrogen');
+    resolve(null);
+    model.animate();
+  });
+};
+
+export const destroy = () => {
+  if (model) {
+    nitrogenObj?.destroy();
+    model.deleteModal();
+    model = null;
+    group = null;
+  }
+};

+ 25 - 0
src/views/vent/monitorManager/groutMonitor/components/groutAlarmHistory.vue

@@ -0,0 +1,25 @@
+<template>
+  <div class="alarm-history">
+    <AlarmHistoryTable columns-type="alarm" device-type="pressurefan"
+      :device-list-api="getTableList.bind(null, { strtype: 'pressurefan' })" designScope="alarm-history" />
+  </div>
+</template>
+<script setup lang="ts">
+import AlarmHistoryTable from '../../comment/AlarmHistoryTable.vue';
+import { getTableList } from '../grout.api'
+const props = defineProps({
+  deviceType: {
+    type: String,
+    required: true,
+  },
+  deviceId: {
+    type: String,
+    required: true,
+  }
+})
+</script>
+<style lang="less" scoped>
+.alarm-history {
+  pointer-events: auto;
+}
+</style>

+ 25 - 0
src/views/vent/monitorManager/groutMonitor/components/groutHandleHistory.vue

@@ -0,0 +1,25 @@
+<template>
+  <div class="handle-history">
+    <HandlerHistoryTable columns-type="operatorhistory" device-type="pressurefan"
+      :device-list-api="getTableList.bind(null, { strtype: 'pressurefan' })" designScope="pressurefan_history" />
+  </div>
+</template>
+<script setup lang="ts">
+import HandlerHistoryTable from '../../comment/HandlerHistoryTable.vue';
+import { getTableList } from '../grout.api'
+  const props = defineProps({
+    deviceType: {
+      type: String,
+      required: true,
+    },
+    deviceId: {
+      type: String,
+      required: true,
+    }
+  })
+</script>
+<style lang="less" scoped>
+.handle-history {
+  pointer-events: auto;
+}
+</style>

+ 28 - 0
src/views/vent/monitorManager/groutMonitor/components/groutHistory.vue

@@ -0,0 +1,28 @@
+<template>
+  <div class="history-box">
+    <HistoryTable :columns-type="`${deviceType}`" :device-type="deviceType" :sysId="deviceId"
+      :device-list-api="getTableList.bind(null, { strtype: deviceType, sysId: deviceId })" designScope="pressurefan_history" />
+  </div>
+</template>
+<script setup lang="ts">
+import { ref, defineProps } from 'vue'
+import HistoryTable from '../../comment/HistoryTable.vue';
+import { getTableList } from '../grout.api'
+
+const props = defineProps({
+  deviceType: {
+    type: String,
+    required: true,
+  },
+  deviceId: {
+    type: String,
+    required: true,
+  }
+})
+
+</script>
+<style lang="less" scoped>
+.history-box {
+  pointer-events: auto;
+}
+</style>

+ 447 - 0
src/views/vent/monitorManager/groutMonitor/components/groutHome.vue

@@ -0,0 +1,447 @@
+<template>
+  <div class="monitor-container">
+    <div class="lr left-box">
+      <div class="monitor-info item-box">
+        <ventBox1>
+          <template #title>
+            <div>设备信息监测</div>
+          </template>
+          <template #container v-if="dataSource.length > 0 ">
+            <div v-for="(monitor, key) in deviceMonitorData" :key="key" class="monitor-item">
+              <div class="item-title">{{ monitor.text }}</div>
+              <div class="item-val">{{ key.startsWith('1#') ? dataSource[0][key.split('_')[1]]||'-' : key.startsWith('2#') ? dataSource[1][key.split('_')[1]]||'-' : (dataSource[0][key] || '-') }}</div>
+              <div class="item-unit">{{ monitor.unit }}</div>
+            </div>
+          </template>
+        </ventBox1>
+      </div>
+      <div class="warning-group">
+        <ventBox1>
+          <template #title>
+            <div>近一月报警情况</div>
+          </template>
+          <template #container>
+           <dv-scroll-board ref="scrollBoard" :config="warningConfig"
+              style="width: 100%; height: 240px; overflow-y: auto; " />
+          </template>
+        </ventBox1>
+      </div>
+    </div>
+    <div class="lr right-box">
+      <div class="control-container item-box">
+        <ventBox1>
+          <template #title>
+            <div>设备设施集中控制</div>
+          </template>
+          <template #container >
+            <div class="control-group">
+              <div class="control-item" v-for="(item, key) in deviceControlData" :key="key">
+                <div class="control-item-title">{{ item }}</div>
+                <div class="control-item-state" v-if="!key.startsWith('2#')">
+                  <a-switch v-model="dataSource[0][key.split('_')[1]]" size="small"  checked-children="开启"
+                    un-checked-children="关闭"
+                    @change="handlerDevice(dataSource[0][key.split('_')[1]])">
+                  </a-switch>
+                </div>
+                <div class="control-item-state" v-else>
+                  <a-switch v-model="dataSource[1][key.split('_')[1]]" size="small"  checked-children="开启"
+                    un-checked-children="关闭"
+                    @change="handlerDevice(dataSource[1][key.split('_')[1]])">
+                  </a-switch>
+                </div>
+              </div>
+            </div>
+            <a-divider style="height: 1px; background-color: #d7d7d755" />
+            <div class="control-btn-group">
+              <div class="control-left-box">
+                <div class="btn-box">
+                  <span @click="handlerDevice({ remote : true})">远程</span>
+                  <span @click="handlerDevice({ remote: false })">就地</span>
+                </div>
+                <div class="icon-box" :class="{'remote-icon-box': true}">
+                  <div class="icon"></div>
+                </div>
+              </div>
+              <div class="control-right-box">
+                <a-button class="btn" type="primary" @click="handlerDevice({run: true})">一键启动</a-button>
+                <a-button type="primary" danger @click="handlerDevice({ run: false })">紧急停止</a-button>
+              </div>
+            </div>
+            <a-divider style="height: 1px; background-color: #d7d7d755" />
+            <div class="parameter-btn-group"> 
+              <a-button type="primary"  @click="openModal('RunParameterModal')">参数设置</a-button>
+              <a-button type="primary"  @click="openModal('WarningParameterModal')">报警设置</a-button>
+            </div>
+          </template>
+        </ventBox1>
+      </div>
+      <div class="item-box" >
+        <LivePlayer id="fm-player1" style="height: 250px;" ref="player1" :videoUrl="flvURL1()" muted live loading controls />
+      </div>
+    </div>
+  </div>
+  <component v-if="modalVisible" :is="currentModal" v-model:visible="modalVisible" />
+</template>
+
+<script setup lang="ts">
+
+import { onBeforeMount, ref, onMounted, onUnmounted, shallowRef, defineProps, ComponentOptions } from 'vue';
+import { mountedThree, destroy, setModelType } from '../grout.threejs';
+import { ScrollBoard as DvScrollBoard } from '@kjgl77/datav-vue3';
+import { SvgIcon } from '/@/components/Icon';
+import ventBox1 from '/@/components/vent/ventBox1.vue';
+import RunParameterModal from './runParameter.modal.vue'
+import WarningParameterModal from './warningParameter.modal.vue'
+import { warningConfig} from '../grout.data'
+import { list } from '../grout.api';
+import LivePlayer from '@liveqing/liveplayer-v3';
+
+
+const props = defineProps({
+  deviceId: {
+    type: String,
+    require: true
+  }
+})
+
+const currentModal = shallowRef<Nullable<ComponentOptions>>(null); //模态框
+const modalVisible = ref<Boolean>(false); // 模态框是否可见
+const loading = ref(false);
+
+// 默认初始是第一行
+const dataSource = ref([
+  { 
+    waterSupply : '-',
+    beltVla: '-',
+    density: '-',
+    pressure: '-',
+    liquidLevel: '-',
+    flowRate: '-'
+  },
+  {
+    waterSupply: '-',
+    beltVla: '-',
+    density: '-',
+    pressure: '-',
+    liquidLevel: '-',
+    flowRate: '-'
+  }
+]); //dusting
+const deviceMonitorData = {
+  '1#_waterSupply': { text: '1#制浆机供水流量', unit: 'm³/h' },
+  '1#_beltVla': { text: '1#皮带秤数值', unit: 'T' },
+  '2#_waterSupply': { text: '2#制浆机供水流量', unit: 'm³/h' },
+  '2#_beltVla': { text: '2#皮带秤数值', unit: 'T' },
+  density: {text: '密度' , unit: 'g/cm'},
+  pressure: { text: '压力', unit: 'MPa' },
+  liquidLevel: { text: '缓冲池液位', unit: 'm' },
+  flowRate: { text: '注浆流量', unit: 'm³/h' },
+}
+const deviceControlData = {
+  '1#_waterPump': '1#清水泵',
+  '1#_groutingPump': '1#注浆泵',
+  '1#_pulpingMachine': '1#制浆机',
+  '2#_waterPump': '2#清水泵',
+  '2#_groutingPump': '2#注浆泵',
+  '2#_pulpingMachine': '2#制浆机',
+  'liquidLevelProtect ':'液位保护'
+}
+
+
+const flvURL1 = () => {
+  return `https://sf1-hscdn-tos.pstatp.com/obj/media-fe/xgplayer_doc_video/flv/xgplayer-demo-360p.flv`;
+  // return ''
+};
+const openModal = (modalName) => {
+  modalVisible.value = true
+  if(modalName == 'RunParameterModal'){
+    currentModal.value = RunParameterModal
+  }else{
+    currentModal.value = WarningParameterModal
+  }
+}
+
+// https获取监测数据
+let timer: null | NodeJS.Timeout = null;
+function getMonitor() {
+  if (Object.prototype.toString.call(timer) === '[object Null]') {
+    timer = setTimeout(async () => {
+      await getDataSource()
+      if (timer) {
+        timer = null;
+      }
+      await getMonitor();
+    }, 1000);
+  }
+};
+
+async function getDataSource() {
+  const res = await list({ devicetype: 'pulping_auto', pagetype: 'normal' });
+  const dataList = res.msgTxt[0].datalist || [];
+  dataSource.value = dataList.filter((data) => {
+    const item = data.readData;
+    Object.assign(data, item);
+    return item
+  });
+}
+
+function handlerDevice(param: string | Object) {
+
+}
+function controlDevice(flag){
+
+}
+
+onBeforeMount(() => {
+
+});
+
+onMounted(() => {
+  loading.value = true;
+  mountedThree().then(async () => {
+    await setModelType('groutBase');
+    loading.value = false;
+    timer = null
+    await getMonitor()
+  });
+});
+onUnmounted(() => {
+  destroy();
+  if (timer) {
+    clearTimeout(timer);
+    timer = undefined;
+  }
+});
+</script>
+<style lang="less" scoped>
+@import '/@/design/vent/modal.less';
+@ventSpace: zxm;
+
+.monitor-container {
+  width: 100%;
+  height: 100%;
+  // height: 550px;
+  // border: 1px solid #fff;
+  margin-top: 40px;
+  display: flex;
+  justify-content: space-between;
+  padding: 0 5px;
+  
+  .lr {
+    width: 350px;
+    height: 100%;
+    display: flex;
+    flex-direction: column;
+    margin-top: 10px;
+    pointer-events: auto;
+  }
+
+  .right-box {
+    width: 320px;
+    .control-group{
+      display: flex;
+      // justify-content: space-around;
+      flex-wrap: wrap;
+      .control-item {
+        
+        display: flex;
+        flex-direction: column;
+        justify-content: center;
+        align-items: center;
+        padding: 0 2px;
+        .control-item-title{
+          color: #A6DCE9;
+          position: relative;
+          top: 5px;
+        }
+        .control-item-state{
+          width: 94px;
+          height: 47px;
+          background: url('/@/assets/images/vent/control-switch-bg.png');
+          display: flex;
+          justify-content: center;
+          align-items: center;
+          color: #fff;
+        }
+      
+        .button-box {
+          position: relative;
+          padding: 5px;
+          border: 1px transparent solid;
+          background-clip: border-box;
+          border-radius: 5px;
+          margin-left: 8px;
+        }
+
+        .a-button {
+          pointer-events: auto;
+        }
+
+        &::v-deep .a-button--mini {
+          padding: 6px 10px;
+        }
+
+        &::v-deep .a-button--mini.is-round {
+          padding: 6px 10px;
+        }
+      }
+
+    }
+    .control-btn-group{
+      display: flex;
+      flex-direction: row;
+      justify-content: space-between;
+      .control-left-box{
+        display: flex;
+        flex-direction: column;
+        justify-content: center;
+        align-items: center;
+        padding: 0 20px;
+        .btn-box{
+          width: 100px;
+          color: #fff;
+          display: flex;
+          justify-content: space-between;
+          span{
+            display: inline-block;
+            padding: 2px 8px;
+            background: #007099;
+            border-radius: 4px;
+            border: 1px solid rgb(125, 230, 249);
+            cursor: pointer;
+            &:hover{
+              background: #005574;
+            }
+          }
+        }
+        .icon-box{
+          width: 60px;
+          height: 60px;
+          border-radius: 30px;
+          border: 2px solid #00bcdd;
+          box-shadow: 0 0 20px #ffffff88;
+          display: flex;
+          justify-content: center;
+          align-items: center;
+          margin-top: 20px;
+             
+          .icon{
+            width: 18px;
+            height: 18px;
+            border-radius: 9px;
+            border: 3px solid #d7f9ff;
+            position: relative;
+            background: #00bcdd;
+            &::before{
+              position: absolute;
+              content: '';
+              width: 2px;
+              height: 12px;
+              background-color: #00bcdd;
+              left: 6px;
+              top: -16px;
+            }
+            &::after{
+              position: absolute;
+              content: '';
+              width: 2px;
+              height: 12px;
+              left: 6px;
+              top: 17px;
+              background-color: #00d9ff;
+            }
+          }
+        }
+        .remote-icon-box{
+          transform: rotate(30deg);
+          animation: iconRotate 1s linear infinite;
+        }
+        .remote-icon-box1{
+          transform: rotate(-30deg);
+          animation: iconRotate1 1s linear infinite;
+        }
+
+        @keyframes iconRotate{
+          from{
+            transform: rotate(-30deg);
+          }
+          to {
+            transform: rotate(30deg);
+          }
+        }
+        @keyframes iconRotate1{
+          from {
+            transform: rotate(30deg);
+          }
+          to {
+            transform: rotate(-30deg);
+          }
+        }
+        
+      }
+      .control-right-box{
+        width: 100px;
+        .btn{
+          margin-bottom: 30px;
+        }
+      }
+    }
+    .parameter-btn-group{
+      display: flex;
+      justify-content: space-between;
+      padding: 0 20px;
+      margin-bottom: 10px;
+    }
+  }
+
+  .left-box {
+    margin-top: 30px;
+    .monitor-item{
+      display: flex;
+      color: #fff;
+      justify-content: space-between;
+      background-image: linear-gradient(to left, #3df6ff10, #3df6ff00);
+      margin: 10px 0;
+    
+      .item-title{
+        width: 200px;
+        margin-left: 10px;
+      }
+      .item-val{
+        width: 80px;
+        color: #00eefffe;
+      }
+      .item-unit{
+        width: 80px;
+      }
+    }
+    
+  }
+
+  .item-box{
+    margin-bottom: 15px;
+  }
+
+}
+
+:deep(.@{ventSpace}-tabs-tabpane-active) {
+  overflow: auto;
+}
+
+.input-box {
+  display: flex;
+  align-items: center;
+  padding-left: 10px;
+
+  .input-title {
+    color: #73e8fe;
+    width: auto;
+  }
+
+  .@{ventSpace}-input-number {
+    border-color: #ffffff88 !important;
+  }
+
+  margin-right: 10px;
+}
+</style>

+ 40 - 0
src/views/vent/monitorManager/groutMonitor/components/runParameter.modal.vue

@@ -0,0 +1,40 @@
+<template>
+  <BasicModal v-bind="$attrs" @register="register" title="黄泥灌浆系统运行参数设置" width="1200px" @ok="handleOk" @cancel="handleCancel" >
+    <div>123</div>
+  </BasicModal>
+</template>
+<script lang="ts">
+import { defineComponent} from 'vue';
+import { BasicModal, useModalInner } from '/@/components/Modal';
+import BarAndLine from '/@/components/chart/BarAndLine.vue';
+import { SvgIcon } from '/@/components/Icon';
+import { Decoration7 as DvDecoration7, ScrollBoard as DvScrollBoard } from '@kjgl77/datav-vue3';
+
+export default defineComponent({
+  name: 'RunParameterModal',
+  components: { BasicModal, BarAndLine, SvgIcon, DvScrollBoard, DvDecoration7 },
+
+  setup(props) {
+    
+    const [register, { closeModal }] = useModalInner();
+
+    function handleOk(e) {
+      e.preventDefault()
+      closeModal()
+    }
+
+    function handleCancel(e) {
+      e.preventDefault()
+      closeModal()
+    }
+
+    
+
+    return { register, handleOk, handleCancel, };
+  },
+  
+});
+</script>
+<style lang="less" scoped>
+  
+</style>

+ 40 - 0
src/views/vent/monitorManager/groutMonitor/components/warningParameter.modal.vue

@@ -0,0 +1,40 @@
+<template>
+  <BasicModal v-bind="$attrs" @register="register" title="黄泥灌浆系统报警参数设置" width="1200px" @ok="handleOk" @cancel="handleCancel" >
+    <div>123</div>
+  </BasicModal>
+</template>
+<script lang="ts">
+import { defineComponent} from 'vue';
+import { BasicModal, useModalInner } from '/@/components/Modal';
+import BarAndLine from '/@/components/chart/BarAndLine.vue';
+import { SvgIcon } from '/@/components/Icon';
+import { Decoration7 as DvDecoration7, ScrollBoard as DvScrollBoard } from '@kjgl77/datav-vue3';
+
+export default defineComponent({
+  name: 'WarningParameterModal',
+  components: { BasicModal, BarAndLine, SvgIcon, DvScrollBoard, DvDecoration7 },
+
+  setup(props) {
+    
+    const [register, { closeModal }] = useModalInner();
+
+    function handleOk(e) {
+      e.preventDefault()
+      closeModal()
+    }
+
+    function handleCancel(e) {
+      e.preventDefault()
+      closeModal()
+    }
+
+    
+
+    return { register, handleOk, handleCancel, };
+  },
+  
+});
+</script>
+<style lang="less" scoped>
+  
+</style>

+ 18 - 0
src/views/vent/monitorManager/groutMonitor/grout.api.ts

@@ -0,0 +1,18 @@
+import { defHttp } from '/@/utils/http/axios';
+import { Modal } from 'ant-design-vue';
+
+enum Api {
+  list = '/monitor/device',
+  baseList = '/safety/ventanalyDeviceInfo/list',
+}
+/**
+ * 列表接口
+ * @param params
+ */
+export const list = (params) => defHttp.post({ url: Api.list, params });
+
+/**
+ * 保存或者更新用户
+ * @param params
+ */
+export const getTableList = (params) => defHttp.get({ url: Api.baseList, params });

+ 63 - 0
src/views/vent/monitorManager/groutMonitor/grout.data.ts

@@ -0,0 +1,63 @@
+import { reactive } from 'vue';
+import { BasicColumn } from '/@/components/Table';
+
+export const warningConfig = reactive({
+  header: ['设备名称', '预警信息', '时间'],
+  data: [
+    ['火焰6', '严重报警', '03-05'],
+    ['测点43', '一般预警', '03-05'],
+    ['CO23', '一版预警', '03-05'],
+    ['测点6', '超高预警', '03-05'],
+    ['测点65', '超高预警', '03-05'],
+    ['温度4', '一般预警', '03-05'],
+    ['测点61', '一般预警', '03-05'],
+    ['测点87', '一般信息', '03-05'],
+  ],
+  index: false,
+  // columnWidth: [150, 80, 150, 150],
+  headerBGC: '#3d9dd45d',
+  oddRowBGC: '#009acd10',
+  evenRowBGC: '#009acd05',
+  align: ['center', 'center', 'center'],
+});
+
+export const sensorColumns: BasicColumn[] = [
+  {
+    title: '名称',
+    dataIndex: 'strname',
+    width: 100,
+    align: 'center',
+  },
+  {
+    title: '实时值',
+    dataIndex: 'smokeval',
+    width: 80,
+    align: 'center',
+  },
+  {
+    title: '报警',
+    dataIndex: 'warnFlag',
+    width: 100,
+    align: 'center',
+  },
+];
+export const dustColumns: BasicColumn[] = [
+  {
+    title: '名称',
+    dataIndex: 'strname',
+    width: 100,
+    align: 'center',
+  },
+  {
+    title: '链接状态',
+    dataIndex: 'netStatus',
+    width: 80,
+    align: 'center',
+  },
+  {
+    title: '操作',
+    dataIndex: 'action',
+    width: 100,
+    align: 'center',
+  },
+];

+ 67 - 0
src/views/vent/monitorManager/groutMonitor/grout.threejs.base.ts

@@ -0,0 +1,67 @@
+import * as THREE from 'three';
+// import * as dat from 'dat.gui';
+// const gui = new dat.GUI();
+// gui.domElement.style = 'position:absolute;top:100px;left:10px;z-index:99999999999999';
+
+class ChamberBase {
+  model;
+  modelName = 'grout';
+  group: THREE.Object3D | null = null;
+
+  constructor(model) {
+    this.model = model;
+  }
+
+  addLight() {
+    const directionalLight = new THREE.DirectionalLight(0xffffff, 1.2);
+    directionalLight.position.set(-0.88, 10, 42.4);
+    this.group?.add(directionalLight);
+    directionalLight.target = this.group as THREE.Object3D;
+
+    // gui.add(directionalLight.position, 'x', -500, 500);
+    // gui.add(directionalLight.position, 'y', -500, 500);
+    // gui.add(directionalLight.position, 'z', -500, 500);
+
+    const spotLight = new THREE.SpotLight();
+    spotLight.angle = Math.PI / 2;
+    spotLight.penumbra = 0;
+    spotLight.castShadow = true;
+    spotLight.intensity = 1;
+
+    spotLight.shadow.camera.near = 0.5; // default
+    spotLight.shadow.focus = 1.2;
+    spotLight.shadow.bias = -0.000002;
+
+    spotLight.position.set(89.8, 144, 314);
+    this.group?.add(spotLight);
+
+    // gui.add(spotLight.position, 'x', -600, 600);
+    // gui.add(spotLight.position, 'y', -600, 800);
+    // gui.add(spotLight.position, 'z', -500, 1000);
+  }
+
+  addChamberText(selectData) {
+    //
+  }
+
+  mountedThree() {
+    return new Promise((resolve) => {
+      this.model.setModel([this.modelName]).then((gltf) => {
+        this.group = gltf[0];
+        if (this.group) {
+          this.group?.scale.set(12.5, 12.5, 12.5);
+          this.group.position.y = 4;
+          resolve(null);
+          this.addLight();
+        }
+      });
+    });
+  }
+
+  destroy() {
+    this.model.clearGroup(this.group);
+    this.model = null;
+    this.group = null;
+  }
+}
+export default ChamberBase;

+ 116 - 0
src/views/vent/monitorManager/groutMonitor/grout.threejs.ts

@@ -0,0 +1,116 @@
+import * as THREE from 'three';
+import UseThree from '../../../../utils/threejs/useThree';
+import ChamberBase from './grout.threejs.base';
+import { animateCamera } from '/@/utils/threejs/util';
+import { useAppStore } from '/@/store/modules/app';
+
+// 模型对象、 文字对象
+let model,
+  groutBaseObj: ChamberBase,
+  group,
+  groutType = 'groutBase'; // workerFaceFiber
+const appStore = useAppStore();
+
+// 鼠标点击事件
+const mouseEvent = (event) => {
+  event.stopPropagation();
+  if (!model) return;
+  const widthScale = appStore.getWidthScale;
+  const heightScale = appStore.getHeightScale;
+  // 将鼠标位置归一化为设备坐标。x 和 y 方向的取值范围是 (-1 to +1)
+  model.mouse.x =
+    ((-model.canvasContainer.getBoundingClientRect().left * widthScale + event.clientX) / (model.canvasContainer.clientWidth * widthScale)) * 2 - 1;
+  model.mouse.y =
+    -((-model.canvasContainer.getBoundingClientRect().top + event.clientY) / (model.canvasContainer.clientHeight * heightScale)) * 2 + 1;
+  (model.rayCaster as THREE.Raycaster).setFromCamera(model.mouse, model.camera as THREE.Camera);
+  if (group) {
+    if (groutType === 'groutBase') {
+      // groutBaseObj.mousedownModel.call(groutBaseObj, model.rayCaster);
+    }
+
+    // const intersects = model.rayCaster?.intersectObjects([...group.children]) as THREE.Intersection[];
+    // if (intersects.length > 0) {
+    //   if (fiberType === 'beltFiber') {
+    //     beltFiberObj.mousedownModel.call(beltFiberObj, intersects);
+    //   } else if (fiberType === 'workFace') {
+    //     workerFaceFiberObj.mousedownModel.call(workerFaceFiberObj, intersects);
+    //   }
+    // }
+
+    console.log('摄像头,控制器------>', model.camera, model.orbitControls);
+  }
+};
+
+const addMouseEvent = () => {
+  // 定义鼠标点击事件
+  model.canvasContainer?.addEventListener('mousedown', mouseEvent.bind(null));
+};
+
+const render = () => {
+  if (model.animationId != -1) {
+    model.animationId = requestAnimationFrame(render);
+    model.css3dRender?.render(model.scene as THREE.Scene, model.camera as THREE.PerspectiveCamera);
+    model.stats?.update();
+  }
+};
+
+export const addChamberText = (selectData) => {
+  if (groutType === 'groutBase') {
+    return groutBaseObj.addChamberText.call(groutBaseObj, selectData);
+  }
+};
+
+// 切换模型类型
+export const setModelType = (type) => {
+  groutType = type;
+  return new Promise((resolve) => {
+    if (groutType === 'groutBase') {
+      group = groutBaseObj.group;
+      const oldCameraPosition = { x: 24.736, y: 63.486, z: 103.337 };
+      model.scene.add(groutBaseObj.group);
+      model.camera.position.set(0, 0, 300);
+      setTimeout(async () => {
+        // const position = { x: 0, y: 3.8, z: 10.5 };
+        await animateCamera(
+          oldCameraPosition,
+          oldCameraPosition,
+          { x: -36.224455845316, y: 21.749986776845592, z: -0.10614789855468158 },
+          { x: 0, y: 0, z: 0 },
+          model,
+          0.8
+        );
+      }, 300);
+
+      resolve(null);
+    }
+  });
+};
+
+export const mountedThree = () => {
+  return new Promise(async (resolve) => {
+    model = new UseThree('#grout3D');
+    model.setEnvMap('test1');
+    model.renderer.toneMappingExposure = 1;
+    // model.camera.position.set(100, 0, 1000);
+
+    groutBaseObj = new ChamberBase(model);
+    await groutBaseObj.mountedThree();
+
+    // model.scene.add(groutBaseObj.group);
+
+    addMouseEvent();
+    // render();
+    model.animate();
+    resolve(null);
+  });
+};
+
+export const destroy = () => {
+  if (model) {
+    groutBaseObj.destroy();
+    model.deleteModal();
+    model = null;
+    group = null;
+    groutBaseObj = null;
+  }
+};

+ 233 - 0
src/views/vent/monitorManager/groutMonitor/index.vue

@@ -0,0 +1,233 @@
+<template>
+  <div class="bg"
+    style="width: 100%; height: 100%; display: flex; justify-content: center; align-items: center; overflow: hidden">
+    <a-spin :spinning="loading" />
+    <div id="grout3D" v-show="!loading" style="width: 100%; height: 100%; position: absolute; overflow: hidden"> </div>
+    <!-- <div id="damper3DCSS" v-show="!loading" style="width: 100%; height: 100%; top:0; left: 0; position: absolute; overflow: hidden;">
+      <div>
+        <div ref="elementContent" class="elementContent">
+          <p><span class="data-title">压力(Pa):</span>{{selectData.frontRearDP}}</p>
+          <p><span class="data-title">动力源压力(MPa):</span>{{selectData.sourcePressure}}</p>
+          <p><span class="data-title">故障诊断:</span>
+            <i
+              :class="{'state-icon': true, 'open': selectData.messageBoxStatus, 'close': !selectData.messageBoxStatus}"
+            ></i>{{selectData.fault}}</p>
+        </div>
+      </div>
+    </div> -->
+  </div>
+  <div class="scene-box">
+    <customHeader :fieldNames="{ label: 'strinstallpos', value: 'deviceID', options: 'children' }" :options = 'options' @change="getSelectRow" :optionValue="optionValue">智能注浆系统</customHeader>
+    <div class="center-container">
+      <groutHome v-if="activeKey == 'monitor'" :deviceId = 'optionValue' />
+      <div v-else class="history-group">
+        <div class="device-button-group" v-if="deviceList.length > 0">
+          <div class="device-button" :class="{ 'device-active': deviceActive == device.deviceType }" v-for="(device, index) in deviceList" :key="index" @click="deviceChange(index)">{{ device.deviceName }}</div>
+        </div>
+        <div class="history-container">
+          <groutHistory v-if="activeKey == 'monitor_history'" ref="historyTable" class="vent-margin-t-20" :deviceId = 'optionValue' :device-type="deviceType"/>
+          <groutHandleHistoryVue v-if="activeKey == 'handler_history'" ref="alarmHistoryTable" class="vent-margin-t-20" :deviceId = 'optionValue' :device-type="deviceType" />
+          <groutAlarmHistory v-if="activeKey == 'faultRecord'" ref="handlerHistoryTable" class="vent-margin-t-20" :deviceId = 'optionValue' :device-type="deviceType"/>
+        </div> 
+      </div>      
+    </div>
+    <BottomMenu @change="changeActive"/>
+  </div>
+  
+</template>
+
+<script setup lang="ts">
+import customHeader from '/@/views/vent/comment/components/customHeader.vue';
+import { onBeforeMount, ref, onMounted, onUnmounted, reactive, toRaw } from 'vue';
+import { list } from './grout.api';
+import BottomMenu from '/@/views/vent/comment/components/bottomMenu.vue';
+import groutHome from './components/groutHome.vue';
+import groutHistory from './components/groutHistory.vue';
+import groutHandleHistoryVue from './components/groutHandleHistory.vue';
+import groutAlarmHistory from './components/groutAlarmHistory.vue';
+
+type DeviceType = { deviceType: string, deviceName: string, datalist: any[] };
+const activeKey = ref('monitor');
+const loading = ref(false);
+
+const monitorTable = ref()
+const historyTable = ref()
+const alarmHistoryTable = ref()
+const handlerHistoryTable = ref()
+
+
+//关联设备
+const deviceList = ref<DeviceType[]>([])
+const deviceActive = ref('')
+const deviceType = ref('')
+
+const options = ref()
+// 默认初始是第一行
+const selectRowIndex = ref(0);
+const dataSource = ref([])
+
+const optionValue = ref('')
+
+// 监测数据
+const selectData = reactive({});
+
+function changeActive(activeValue) {
+  activeKey.value = activeValue
+}
+
+function deviceChange(index) {
+  deviceActive.value = deviceType.value = deviceList.value[index].deviceType
+}
+
+// 查询关联设备列表
+async function getDeviceList() {
+  const res = await list({ devicetype: 'sys', systemID: optionValue.value });
+  const result = res.msgTxt;
+  const deviceArr = <DeviceType[]>[]
+  result.forEach(item => {
+    const data = item['datalist'].filter((data: any) => {
+      const readData = data.readData;
+      return Object.assign(data, readData);
+    })
+    if (item.type != 'sys') {
+      deviceArr.unshift({ deviceType: item.type, deviceName: item['typeName'] ? item['typeName'] : item['datalist'][0]['typeName'], datalist: data })
+    }
+  })
+  deviceList.value = deviceArr
+  deviceActive.value = deviceArr[0].deviceType
+  deviceChange(0)
+};
+
+async function getSysDataSource () {
+  const res = await list({ devicetype: 'sys_dongshi', pagetype: 'normal' });
+  dataSource.value = res.msgTxt[0].datalist || [];
+  dataSource.value.forEach((data: any) => {
+    const readData = data.readData;
+    data = Object.assign(data, readData);
+  });
+  
+  if(!options.value) {
+    // 初始时选择第一条数据
+    options.value = dataSource.value
+    optionValue.value = dataSource.value[0]['deviceID']
+    Object.assign(selectData,  dataSource.value[0])
+  }
+  const data: any = toRaw(dataSource.value[selectRowIndex.value]); //maxarea
+  return data;
+};
+
+// 切换检测数据
+function getSelectRow(deviceID){
+  const currentData = dataSource.value.find((item: any) => {
+    return item.deviceID == deviceID
+  })
+  if(currentData){
+    optionValue.value = currentData['deviceID']
+    Object.assign(selectData, currentData)
+  }
+}
+
+onBeforeMount(() => {
+
+});
+
+onMounted(async() => {
+  await getSysDataSource()
+  await getDeviceList()
+});
+onUnmounted(() => {
+ 
+});
+</script>
+<style lang="less" scoped>
+@import '/@/design/vent/modal.less';
+@ventSpace: zxm;
+.scene-box{
+  pointer-events: none;
+  .history-group{
+    padding: 0 20px;
+    .history-container{
+      position: relative;
+      background: #6176AF11;
+      width: calc(100% + 10px);
+      top: 0px;
+      left: -10px;
+      border: 1px solid #ffffff22;
+      padding: 10px 0;
+    }
+  }
+  .device-button-group{
+    // margin: 0 20px;
+    display: flex;
+    pointer-events: auto;
+    position: relative;
+    margin-top: 90px;
+    &::after{
+      position:absolute;
+      content: '';
+      width: calc(100% + 10px);
+      height: 2px;
+      top: 30px;
+      left: -10px;
+      border-bottom: 1px solid #0efcff;
+    }
+    .device-button{
+      padding: 4px 15px;
+      position: relative;
+      display: flex;
+      justify-content: center;
+      align-items: center;
+      font-size: 14px;
+      
+      color: #fff;
+      cursor: pointer;
+      margin: 0 3px;
+
+      &::before{
+        content: '';
+        position: absolute;
+        top: 0;
+        right: 0;
+        bottom: 0;
+        left: 0;
+        border: 1px solid #6176AF;
+        transform: skewX(-38deg);
+        background-color: rgba(0, 77, 103,85%);
+        z-index: -1;
+      }
+    }
+    .device-active{
+      // color: #0efcff;
+      &::before{
+        border-color: #0efcff;
+        box-shadow: 1px 1px 3px 1px #0efcff inset;
+      }
+    }
+  }
+}
+.center-container{
+  width: 100%;
+  height: calc(100% - 200px);
+}
+
+:deep(.@{ventSpace}-tabs-tabpane-active) {
+  overflow: auto;
+}
+
+.input-box {
+  display: flex;
+  align-items: center;
+  padding-left: 10px;
+
+  .input-title {
+    color: #73e8fe;
+    width: auto;
+  }
+
+  .@{ventSpace}-input-number {
+    border-color: #ffffff88 !important;
+  }
+
+  margin-right: 10px;
+}
+</style>

+ 18 - 0
src/views/vent/monitorManager/wokerFaceMonitor/fiber.api.ts

@@ -0,0 +1,18 @@
+import { defHttp } from '/@/utils/http/axios';
+import { Modal } from 'ant-design-vue';
+
+enum Api {
+  list = '/monitor/device',
+  baseList = '/safety/ventanalyWindow/list',
+}
+/**
+ * 列表接口
+ * @param params
+ */
+export const list = (params) => defHttp.post({ url: Api.list, params });
+
+/**
+ * 保存或者更新用户
+ * @param params
+ */
+export const getTableList = (params) => defHttp.get({ url: Api.baseList, params });

+ 373 - 0
src/views/vent/monitorManager/wokerFaceMonitor/fiber.belt.threejs.ts

@@ -0,0 +1,373 @@
+import * as THREE from 'three';
+import { EffectComposer } from 'three/examples/jsm/postprocessing/EffectComposer.js';
+import { RenderPass } from 'three/examples/jsm/postprocessing/RenderPass.js';
+import { ShaderPass } from 'three/examples/jsm/postprocessing/ShaderPass.js';
+import { UnrealBloomPass } from 'three/examples/jsm/postprocessing/UnrealBloomPass.js';
+import { GammaCorrectionShader } from 'three/examples/jsm/shaders/GammaCorrectionShader.js';
+import { CSS3DObject } from 'three/examples/jsm/renderers/CSS3DRenderer.js';
+// import * as dat from 'dat.gui';
+// const gui = new dat.GUI();
+// gui.domElement.style = 'position:absolute;top:100px;left:10px;z-index:99999999999999';
+
+class beltFiber {
+  model;
+  modelName = 'belt';
+  group: THREE.Object3D = new THREE.Object3D();
+  bloomComposer: EffectComposer | null = null;
+  finalComposer: EffectComposer | null = null;
+  positions: THREE.Vector3[] = [];
+  msgPositions: THREE.Vector3[] = [];
+  bloomLayer = new THREE.Layers();
+  darkMaterial = new THREE.MeshBasicMaterial({ color: 'black', transparent: true, side: THREE.DoubleSide });
+  materials = {};
+  glob = {
+    ENTIRE_SCENE: 0,
+    BLOOM_SCENE: 1,
+    N: 11,
+  };
+  locationMaterial: THREE.Material | null = null;
+  warningMaterial: THREE.Material | null = null;
+  errorMaterial: THREE.Material | null = null;
+  playerStartClickTime1 = new Date().getTime();
+  playerStartClickTime2 = new Date().getTime();
+
+  constructor(model) {
+    this.model = model;
+    this.group.name = this.modelName;
+    this.initMaterial();
+  }
+  addLight() {
+    const directionalLight = new THREE.DirectionalLight(0xffffff, 1.2);
+    directionalLight.position.set(-0.8, 23, 3.9);
+    this.group.add(directionalLight);
+    directionalLight.target = this.group;
+
+    // gui.add(directionalLight.position, 'x', -10, 20).onChange(function (value) {
+    //   directionalLight.position.x = Number(value);
+    //   _this.render();
+    // });
+    // gui.add(directionalLight.position, 'y', -50, 50).onChange(function (value) {
+    //   directionalLight.position.y = Number(value);
+    //   _this.render();
+    // });
+    // gui.add(directionalLight.position, 'z', -20, 20).onChange(function (value) {
+    //   directionalLight.position.z = Number(value);
+    //   _this.render();
+    // });
+    // const pointLight5 = new THREE.PointLight(0xffffff, 0.8, 120);
+    // pointLight5.position.set(-54, 30, 23.8);
+    // pointLight5.shadow.bias = 0.05;
+    // this.group.add(pointLight5);
+
+    // const pointLight7 = new THREE.PointLight(0xffffff, 1, 1000);
+    // pointLight7.position.set(45, 51, -4.1);
+    // pointLight7.shadow.bias = 0.05;
+    // this.model.scene.add(pointLight7);
+
+    const spotLight = new THREE.SpotLight();
+    spotLight.angle = Math.PI / 2;
+    spotLight.penumbra = 0;
+    spotLight.castShadow = true;
+    spotLight.intensity = 1;
+
+    spotLight.shadow.camera.near = 0.5; // default
+    spotLight.shadow.focus = 1.2;
+    spotLight.shadow.bias = -0.000002;
+
+    spotLight.position.set(-7.19, 199, -68.1);
+    // this.group.add(spotLight);
+
+    // gui.add(directionalLight.position, 'x', -10, 20).onChange(function (value) {
+    //   directionalLight.position.x = Number(value);
+    //   _this.render();
+    // });
+    // gui.add(directionalLight.position, 'y', -50, 50).onChange(function (value) {
+    //   directionalLight.position.y = Number(value);
+    //   _this.render();
+    // });
+    // gui.add(directionalLight.position, 'z', -20, 20).onChange(function (value) {
+    //   directionalLight.position.z = Number(value);
+    //   _this.render();
+    // });
+
+    // gui.add(spotLight.position, 'x', -600, 600).onChange(function (value) {
+    //   spotLight.position.x = Number(value);
+    //   _this.render();
+    // });
+    // gui.add(spotLight.position, 'y', -600, 800).onChange(function (value) {
+    //   spotLight.position.y = Number(value);
+    //   _this.render();
+    // });
+    // gui.add(spotLight.position, 'z', -500, 1000).onChange(function (value) {
+    //   spotLight.position.z = Number(value);
+    //   _this.render();
+    // });
+  }
+  // 设置模型位置
+  setModalPosition() {
+    this.group?.scale.set(22, 22, 22);
+    this.group?.position.set(-15, 25, 15);
+  }
+  initMaterial() {
+    const loader = new THREE.TextureLoader();
+    const locationTexture = loader.load('/model/img/location.png');
+    const warningLocationTexture = loader.load('/model/img/warning-location.png');
+    const errorLocationTexture = loader.load('/model/img/error-location.png');
+    locationTexture.encoding = warningLocationTexture.encoding = warningLocationTexture.encoding = THREE.sRGBEncoding;
+
+    this.locationMaterial = new THREE.MeshPhongMaterial({
+      emissive: 0x16ecf4,
+      map: locationTexture,
+      emissiveMap: locationTexture,
+      transparent: true,
+      side: THREE.DoubleSide,
+    });
+    this.warningMaterial = new THREE.MeshPhongMaterial({
+      emissive: 0xf99b21,
+      map: warningLocationTexture,
+      emissiveMap: warningLocationTexture,
+      transparent: true,
+      side: THREE.DoubleSide,
+    });
+    this.errorMaterial = new THREE.MeshPhongMaterial({
+      emissive: 0xf83b24,
+      map: errorLocationTexture,
+      emissiveMap: errorLocationTexture,
+      transparent: true,
+      side: THREE.DoubleSide,
+    });
+  }
+
+  monitorData(warningNum = [3, 6], errorNum = [9]) {
+    if (this.group) {
+      if (warningNum.length > 0)
+        warningNum.forEach((i) => {
+          const warningMesh = this.group.getObjectByName('location' + i);
+          if (warningMesh) {
+            warningMesh.material = this.warningMaterial;
+          }
+        });
+      if (errorNum.length > 0)
+        errorNum.forEach((i) => {
+          const errorMesh = this.group.getObjectByName('location' + i);
+          if (errorMesh) {
+            errorMesh.material = this.errorMaterial;
+          }
+        });
+    }
+  }
+
+  addBeltText(selectData) {
+    if (!this.group) {
+      return;
+    }
+
+    const locationX = [-8.406, -6.597, -4.787, -2.967, -1.118, 0.692, 2.501, 4.411, 6.211, 8.0];
+    for (let i = 1; i <= this.glob.N; i++) {
+      const position = this.msgPositions[i];
+      if (!this.group.getObjectByName('fiberMeg1' + i)) {
+        const element = document.getElementById('fiberMeg' + i) as HTMLElement;
+        if (element) {
+          const mainCSS3D = new CSS3DObject(element);
+          mainCSS3D.name = 'fiberMeg' + i;
+          mainCSS3D.scale.set(0.008, 0.008, 0.008);
+          mainCSS3D.position.set(position.x, -97.8, position.z);
+          const lookAtPosition = this.model.camera.position;
+          mainCSS3D.lookAt(lookAtPosition.x, lookAtPosition.y, lookAtPosition.z);
+          this.group.add(mainCSS3D);
+        }
+      }
+      // if (!this.group.getObjectByName('location' + i)) {
+      //   const geometry = new THREE.PlaneGeometry(1, 0.8);
+      //   const location = new THREE.Mesh(geometry, this.locationMaterial as THREE.Material);
+      //   location.scale.set(0.3, 0.3, 0.3);
+      //   location.name = 'location' + i;
+      //   location.position.set(locationX[i - 1], 1.384, position.z);
+      //   location.layers.enable(this.glob.ENTIRE_SCENE);
+      //   this.group.add(location);
+      // }
+    }
+  }
+
+  render() {
+    const _this = this;
+    if (this.model && this.model.scene.getObjectByName(this.modelName)) {
+      this.group?.traverse((obj) => {
+        _this.darkenNonBloomed(obj);
+      });
+      this.bloomComposer?.render();
+      this.group?.traverse((obj) => {
+        _this.restoreMaterial(obj);
+      });
+      this.finalComposer?.render();
+      this.model.css3dRender?.render(this.model.scene as THREE.Scene, this.model.camera as THREE.PerspectiveCamera);
+    }
+  }
+
+  setRenderPass = () => {
+    this.bloomLayer.set(this.glob.BLOOM_SCENE);
+
+    const params = {
+      bloomStrength: 2,
+      bloomThreshold: 0,
+      bloomRadius: 0,
+    };
+    const renderScene = new RenderPass(this.model.scene, this.model.camera);
+
+    const bloomPass = new UnrealBloomPass(new THREE.Vector2(window.innerWidth, window.innerHeight), 1.5, 0.4, 0.85);
+
+    bloomPass.strength = params.bloomStrength;
+    bloomPass.radius = params.bloomRadius;
+    bloomPass.threshold = params.bloomThreshold;
+
+    this.bloomComposer = new EffectComposer(this.model.renderer);
+    this.bloomComposer.renderToScreen = false;
+    this.bloomComposer.addPass(renderScene);
+    this.bloomComposer.addPass(bloomPass);
+
+    const finalPass = new ShaderPass(
+      new THREE.ShaderMaterial({
+        uniforms: {
+          baseTexture: { value: null },
+          bloomTexture: { value: this.bloomComposer.renderTarget2.texture },
+        },
+        vertexShader: `
+      varying vec2 vUv;
+
+      void main() {
+        vUv = uv;
+        gl_Position = projectionMatrix * modelViewMatrix * vec4( position, 1.0 );
+      }`,
+        fragmentShader: `uniform sampler2D baseTexture;
+      uniform sampler2D bloomTexture;
+
+      varying vec2 vUv;
+
+      void main() {
+        gl_FragColor = ( texture2D( baseTexture, vUv ) + vec4( 1.0, 1.0, 1.0, 0.0 ) * texture2D( bloomTexture, vUv ) );
+      }`,
+        defines: {},
+      }),
+      'baseTexture'
+    );
+    const gammaCorrection = new ShaderPass(GammaCorrectionShader);
+    finalPass.needsSwap = true;
+
+    this.finalComposer = new EffectComposer(this.model.renderer);
+    this.finalComposer.addPass(renderScene);
+
+    this.finalComposer.addPass(gammaCorrection);
+    this.finalComposer.addPass(finalPass);
+  };
+
+  getPositions(num = 40) {
+    const curve1 = new THREE.LineCurve3(new THREE.Vector3(-9.676, 0.814, 0.019), new THREE.Vector3(9.68, 0.814, 0.019));
+    // const curve2 = new THREE.LineCurve3(new THREE.Vector3(-7.758, -0.462, -0.275), new THREE.Vector3(20.758, 2.037, -0.275));
+
+    // const len1 = curve1.getLength();
+    // const len2 = curve2.getLength();
+
+    // const unit = len1 + len2 / num;
+    // const num1 = Math.floor(unit * len1);
+    // const num2 = Math.floor(unit * len2);
+
+    const points1 = curve1.getPoints(num);
+    this.msgPositions = curve1.getPoints(this.glob.N);
+    // const points2 = curve2.getPoints(num2);
+
+    this.positions = [...points1];
+  }
+
+  drawSpheres = () => {
+    const _this = this;
+    return new Promise((resolve) => {
+      new THREE.TextureLoader().load('/model/img/texture-smoke.png', (texture) => {
+        texture.encoding = THREE.sRGBEncoding;
+        const geometry = new THREE.BufferGeometry();
+        geometry.setFromPoints(_this.positions);
+        const material = new THREE.PointsMaterial({
+          color: '#FFFFAF',
+          size: 0.13,
+          map: texture,
+          transparent: true, // 开启透明度
+        });
+        const points = new THREE.Points(geometry, material);
+        points.renderOrder = 0;
+        _this.group.add(points);
+        points.layers.enable(_this.glob.BLOOM_SCENE);
+        resolve(null);
+      });
+    });
+  };
+
+  darkenNonBloomed(obj) {
+    if (obj.isMesh && this.bloomLayer.test(obj.layers) === false) {
+      const opacity = obj.material.opacity;
+      this.materials[obj.uuid] = obj.material;
+      obj.material = this.darkMaterial.clone();
+      obj.material.opacity = opacity;
+    }
+  }
+
+  restoreMaterial(obj) {
+    if (this.materials[obj.uuid]) {
+      obj.material = this.materials[obj.uuid];
+      delete this.materials[obj.uuid];
+    }
+  }
+
+  /* 点击 */
+  mousedownModel(intersects: THREE.Intersection<THREE.Object3D<THREE.Event>>[]) {
+    if (intersects.length > 0) {
+      // 判断是否点击到视频
+      intersects.find((intersect) => {
+        const mesh = intersect.object;
+        if (mesh.name === 'player1') {
+          return true;
+        }
+        return false;
+      });
+    }
+  }
+  mouseUpModel() {}
+  mountedThree() {
+    return new Promise(async (resolve) => {
+      this.model.renderer.sortObjects = true;
+      this.model.camera.position.set(0, 3.1, 500);
+      this.setRenderPass();
+      this.model.orbitControls.update();
+      // this.model.orbitControls.addEventListener('change', _this.render.bind(_this));
+      this.model.setModel(['laneway']).then(async (gltf) => {
+        this.group = gltf[0];
+        this.group.position.set(0, 0.7, 0);
+        (this.group as THREE.Group).remove(this.group.getObjectByName('mesh001'));
+        const optical = this.group.getObjectByName('optical_fiber_');
+        if (optical) {
+          optical.renderOrder = 9;
+          optical.material = new THREE.MeshBasicMaterial({
+            color: 0x555555,
+            side: THREE.DoubleSide,
+            transparent: true,
+            opacity: 0.15,
+          });
+        }
+
+        this.getPositions();
+        this.addLight();
+        this.drawSpheres().then(() => {
+          // this.render();
+        });
+        // this.addBeltText();
+        resolve(null);
+      });
+    });
+  }
+
+  destroy() {
+    this.model.clearGroup(this.group);
+    this.model = null;
+    this.group = null;
+  }
+}
+
+export default beltFiber;

+ 449 - 0
src/views/vent/monitorManager/wokerFaceMonitor/fiber.data.ts

@@ -0,0 +1,449 @@
+import { BasicColumn } from '/@/components/Table';
+import { FormSchema } from '/@/components/Table';
+import { rules } from '/@/utils/helper/validator';
+export const resultColumns: BasicColumn[] = [
+  // {
+  //   title: '测风描述',
+  //   dataIndex: 'strremark',
+  //   width: 120,
+  //   align: 'center',
+  // },
+  {
+    title: '测风设备数量',
+    dataIndex: 'totalcount',
+    width: 100,
+    align: 'center',
+  },
+  {
+    title: '同时运行数量',
+    dataIndex: 'count',
+    width: 100,
+    align: 'center',
+  },
+  {
+    title: '总时长',
+    dataIndex: 'runtime',
+    width: 100,
+    align: 'center',
+  },
+  {
+    title: '开始测风时间',
+    dataIndex: 'begintime',
+    width: 80,
+    align: 'center',
+  },
+  {
+    title: '结束测风时间',
+    dataIndex: 'endtime',
+    width: 100,
+    align: 'center',
+    sorter: (a: any, b: any) => new Date(a.endtime) - new Date(b.endtime),
+  },
+  {
+    title: '用时(s)',
+    dataIndex: 'runtime',
+    width: 100,
+    align: 'center',
+  },
+  {
+    title: '操作',
+    dataIndex: 'action',
+    width: 100,
+    align: 'center',
+  },
+];
+
+export const innerResultColumns: BasicColumn[] = [
+  {
+    title: '设备名称',
+    dataIndex: 'strname',
+    align: 'center',
+    width: 120,
+  },
+  {
+    title: 'v1a',
+    dataIndex: 'v1a',
+    align: 'center',
+    width: 100,
+  },
+  {
+    title: 'v1b',
+    dataIndex: 'v1b',
+    align: 'center',
+    width: 100,
+  },
+  {
+    title: 'v1c',
+    dataIndex: 'v1c',
+    align: 'center',
+    width: 100,
+  },
+  {
+    title: 'v2a',
+    dataIndex: 'v2a',
+    align: 'center',
+    width: 80,
+  },
+  {
+    title: 'v2b',
+    dataIndex: 'v2b',
+    align: 'center',
+    width: 100,
+  },
+  {
+    title: 'v2c',
+    dataIndex: 'v2c',
+    align: 'center',
+    width: 100,
+  },
+  {
+    title: 'v3a',
+    dataIndex: 'v3a',
+    align: 'center',
+    width: 100,
+  },
+  {
+    title: 'v3b',
+    dataIndex: 'v3b',
+    align: 'center',
+    width: 80,
+  },
+  {
+    title: 'v3c',
+    dataIndex: 'v3c',
+    align: 'center',
+    width: 100,
+  },
+  {
+    title: '平均风速',
+    dataIndex: 'va',
+    align: 'center',
+    width: 100,
+  },
+  {
+    title: '风量',
+    dataIndex: 'm3',
+    align: 'center',
+    width: 100,
+  },
+  {
+    title: '用时(s)',
+    dataIndex: 'runtime',
+    align: 'center',
+    width: 80,
+  },
+];
+
+export const columns: BasicColumn[] = [
+  {
+    title: '名称',
+    dataIndex: 'strname',
+    width: 120,
+  },
+  {
+    title: '安装位置',
+    dataIndex: 'strinstallpos',
+    width: 100,
+  },
+  {
+    title: '是否为常闭型',
+    dataIndex: 'bnormalclose',
+    width: 100,
+    // customRender: render.renderAvatar,
+  },
+  {
+    title: '净宽',
+    dataIndex: 'fclearwidth',
+    width: 80,
+  },
+  {
+    title: '净高',
+    dataIndex: 'fclearheight',
+    width: 100,
+  },
+  {
+    title: '风门道数',
+    dataIndex: 'ndoorcount',
+    width: 100,
+  },
+  {
+    title: '所属分站',
+    width: 150,
+    dataIndex: 'stationname',
+  },
+  {
+    title: '点表',
+    width: 100,
+    dataIndex: 'strtype',
+  },
+  {
+    title: '监测类型',
+    dataIndex: 'monitorflag',
+    width: 100,
+  },
+  {
+    title: '是否模拟数据',
+    dataIndex: 'testflag',
+    width: 100,
+  },
+];
+
+export const recycleColumns: BasicColumn[] = [
+  {
+    title: '名称',
+    dataIndex: 'strname',
+    width: 100,
+  },
+  {
+    title: '是否为常闭型',
+    dataIndex: 'bnormalclose',
+    width: 100,
+  },
+];
+
+export const searchFormSchema: FormSchema[] = [
+  {
+    label: '名称',
+    field: 'strname',
+    component: 'Input',
+    colProps: { span: 6 },
+  },
+  {
+    label: '安装位置',
+    field: 'strinstallpos',
+    component: 'Input',
+    colProps: { span: 6 },
+  },
+  {
+    label: '是否为常闭型',
+    field: 'bnormalclose',
+    component: 'JDictSelectTag',
+    componentProps: {
+      dictCode: 'user_status',
+      placeholder: '请选择读写类型',
+      stringToNumber: true,
+    },
+    colProps: { span: 6 },
+  },
+];
+
+export const formSchema: FormSchema[] = [
+  {
+    label: '',
+    field: 'id',
+    component: 'Input',
+    show: false,
+  },
+  {
+    label: '名称',
+    field: 'strname',
+    component: 'Input',
+  },
+  {
+    label: '安装位置',
+    field: 'strinstallpos',
+    component: 'Input',
+  },
+  {
+    label: '是否为常闭型',
+    field: 'bnormalclose',
+    component: 'RadioGroup',
+    defaultValue: 1,
+    componentProps: () => {
+      return {
+        options: [
+          { label: '是', value: 1, key: '1' },
+          { label: '否', value: 0, key: '2' },
+        ],
+      };
+    },
+  },
+  {
+    label: '净宽',
+    field: 'fclearwidth',
+    component: 'Input',
+  },
+  {
+    label: '净高',
+    field: 'fclearheight',
+    component: 'Input',
+  },
+  {
+    label: '风门道数',
+    field: 'ndoorcount',
+    component: 'Input',
+  },
+  {
+    label: '所属分站',
+    field: 'stationname',
+    component: 'JDictSelectTag',
+    componentProps: {
+      dictCode: 'user_status',
+      placeholder: '请选择状态',
+      stringToNumber: true,
+    },
+  },
+  {
+    label: '点表',
+    field: 'strtype',
+    component: 'JDictSelectTag',
+    componentProps: {
+      dictCode: 'user_status',
+      placeholder: '请选择状态',
+      stringToNumber: true,
+    },
+  },
+  {
+    label: '监测类型',
+    field: 'monitorflag',
+    component: 'JDictSelectTag',
+    componentProps: {
+      dictCode: 'user_status',
+      placeholder: '请选择状态',
+      stringToNumber: true,
+    },
+  },
+  {
+    label: '是否模拟数据',
+    field: 'testflag',
+    component: 'RadioGroup',
+    defaultValue: 1,
+    componentProps: () => {
+      return {
+        options: [
+          { label: '是', value: 1, key: '1' },
+          { label: '否', value: 0, key: '2' },
+        ],
+      };
+    },
+  },
+];
+
+export const formPasswordSchema: FormSchema[] = [
+  {
+    label: '用户账号',
+    field: 'username',
+    component: 'Input',
+    componentProps: { readOnly: true },
+  },
+  {
+    label: '登录密码',
+    field: 'password',
+    component: 'StrengthMeter',
+    componentProps: {
+      placeholder: '请输入登录密码',
+    },
+    rules: [
+      {
+        required: true,
+        message: '请输入登录密码',
+      },
+    ],
+  },
+  {
+    label: '确认密码',
+    field: 'confirmPassword',
+    component: 'InputPassword',
+    dynamicRules: ({ values }) => rules.confirmPassword(values, true),
+  },
+];
+
+export const formAgentSchema: FormSchema[] = [
+  {
+    label: '',
+    field: 'id',
+    component: 'Input',
+    show: false,
+  },
+  {
+    field: 'userName',
+    label: '用户名',
+    component: 'Input',
+    componentProps: {
+      readOnly: true,
+      allowClear: false,
+    },
+  },
+  {
+    field: 'agentUserName',
+    label: '代理人用户名',
+    required: true,
+    component: 'JSelectUser',
+    componentProps: {
+      rowKey: 'username',
+      labelKey: 'realname',
+      maxSelectCount: 10,
+    },
+  },
+  {
+    field: 'startTime',
+    label: '代理开始时间',
+    component: 'DatePicker',
+    required: true,
+    componentProps: {
+      showTime: true,
+      valueFormat: 'YYYY-MM-DD HH:mm:ss',
+      placeholder: '请选择代理开始时间',
+    },
+  },
+  {
+    field: 'endTime',
+    label: '代理结束时间',
+    component: 'DatePicker',
+    required: true,
+    componentProps: {
+      showTime: true,
+      valueFormat: 'YYYY-MM-DD HH:mm:ss',
+      placeholder: '请选择代理结束时间',
+    },
+  },
+  {
+    field: 'status',
+    label: '状态',
+    component: 'JDictSelectTag',
+    defaultValue: '1',
+    componentProps: {
+      dictCode: 'valid_status',
+      type: 'radioButton',
+    },
+  },
+];
+
+export const chartsColumns = [
+  {
+    legend: '平均温度',
+    seriesName: '℃',
+    ymax: '100',
+    yname: '',
+    linetype: 'bar',
+    yaxispos: 'left',
+    color: '#cd5fff',
+    sort: 1,
+    xRotate: 0,
+    dataIndex: 'favg',
+  },
+  {
+    legend: '最高温度',
+    seriesName: '',
+    ymax: '100',
+    yname: '',
+    linetype: 'line',
+    yaxispos: 'left',
+    color: '#3DF6FF',
+    sort: 1,
+    xRotate: 0,
+    dataIndex: 'fmax',
+  },
+  {
+    legend: '最低温度',
+    seriesName: '',
+    ymax: '100',
+    yname: '',
+    linetype: 'line',
+    yaxispos: 'left',
+    color: '#fac858',
+    sort: 1,
+    xRotate: 0,
+    dataIndex: 'fmin',
+  },
+];

+ 371 - 0
src/views/vent/monitorManager/wokerFaceMonitor/fiber.ds.threejs.ts

@@ -0,0 +1,371 @@
+import * as THREE from 'three';
+import { EffectComposer } from 'three/examples/jsm/postprocessing/EffectComposer.js';
+import { RenderPass } from 'three/examples/jsm/postprocessing/RenderPass.js';
+import { ShaderPass } from 'three/examples/jsm/postprocessing/ShaderPass.js';
+import { UnrealBloomPass } from 'three/examples/jsm/postprocessing/UnrealBloomPass.js';
+import { GammaCorrectionShader } from 'three/examples/jsm/shaders/GammaCorrectionShader.js';
+import { CSS3DObject } from 'three/examples/jsm/renderers/CSS3DRenderer.js';
+// import * as dat from 'dat.gui';
+// const gui = new dat.GUI();
+// gui.domElement.style = 'position:absolute;top:100px;left:10px;z-index:99999999999999';
+
+class GrottoFiber {
+  model;
+  modelName = 'grotto';
+  group: THREE.Object3D = new THREE.Object3D();
+  bloomComposer: EffectComposer | null = null;
+  finalComposer: EffectComposer | null = null;
+  positions: THREE.Vector3[] = [];
+  msgPositions: THREE.Vector3[] = [];
+  bloomLayer = new THREE.Layers();
+  darkMaterial = new THREE.MeshBasicMaterial({ color: 'black', transparent: true, side: THREE.DoubleSide });
+  materials = {};
+  glob = {
+    ENTIRE_SCENE: 0,
+    BLOOM_SCENE: 1,
+    N: 11,
+  };
+  locationTexture: THREE.Texture | null = null;
+  warningLocationTexture: THREE.Texture | null = null;
+  errorLocationTexture: THREE.Texture | null = null;
+  playerStartClickTime1 = new Date().getTime();
+  playerStartClickTime2 = new Date().getTime();
+
+  constructor(model) {
+    this.model = model;
+    this.group.name = this.modelName;
+  }
+  addLight() {
+    const _this = this;
+    const directionalLight = new THREE.DirectionalLight(0xffffff, 1.2);
+    directionalLight.position.set(-0.8, 23, 3.9);
+    this.group.add(directionalLight);
+    directionalLight.target = this.group;
+
+    // gui.add(directionalLight.position, 'x', -10, 20).onChange(function (value) {
+    //   directionalLight.position.x = Number(value);
+    //   _this.render();
+    // });
+    // gui.add(directionalLight.position, 'y', -50, 50).onChange(function (value) {
+    //   directionalLight.position.y = Number(value);
+    //   _this.render();
+    // });
+    // gui.add(directionalLight.position, 'z', -20, 20).onChange(function (value) {
+    //   directionalLight.position.z = Number(value);
+    //   _this.render();
+    // });
+    // const pointLight5 = new THREE.PointLight(0xffffff, 0.8, 120);
+    // pointLight5.position.set(-54, 30, 23.8);
+    // pointLight5.shadow.bias = 0.05;
+    // this.group.add(pointLight5);
+
+    // const pointLight7 = new THREE.PointLight(0xffffff, 1, 1000);
+    // pointLight7.position.set(45, 51, -4.1);
+    // pointLight7.shadow.bias = 0.05;
+    // this.model.scene.add(pointLight7);
+
+    const spotLight = new THREE.SpotLight();
+    spotLight.angle = Math.PI / 2;
+    spotLight.penumbra = 0;
+    spotLight.castShadow = true;
+    spotLight.intensity = 1;
+
+    spotLight.shadow.camera.near = 0.5; // default
+    spotLight.shadow.focus = 1.2;
+    spotLight.shadow.bias = -0.000002;
+
+    spotLight.position.set(-7.19, 199, -68.1);
+    // this.group.add(spotLight);
+
+    // gui.add(directionalLight.position, 'x', -10, 20).onChange(function (value) {
+    //   directionalLight.position.x = Number(value);
+    //   _this.render();
+    // });
+    // gui.add(directionalLight.position, 'y', -50, 50).onChange(function (value) {
+    //   directionalLight.position.y = Number(value);
+    //   _this.render();
+    // });
+    // gui.add(directionalLight.position, 'z', -20, 20).onChange(function (value) {
+    //   directionalLight.position.z = Number(value);
+    //   _this.render();
+    // });
+
+    // gui.add(spotLight.position, 'x', -600, 600).onChange(function (value) {
+    //   spotLight.position.x = Number(value);
+    //   _this.render();
+    // });
+    // gui.add(spotLight.position, 'y', -600, 800).onChange(function (value) {
+    //   spotLight.position.y = Number(value);
+    //   _this.render();
+    // });
+    // gui.add(spotLight.position, 'z', -500, 1000).onChange(function (value) {
+    //   spotLight.position.z = Number(value);
+    //   _this.render();
+    // });
+  }
+  // 设置模型位置
+  setModalPosition() {
+    this.group?.scale.set(22, 22, 22);
+    this.group?.position.set(-15, 25, 15);
+  }
+
+  monitorData(warningNum = [3, 6], errorNum = [9]) {
+    if (this.group) {
+      if (warningNum.length > 0)
+        warningNum.forEach((i) => {
+          const warningMesh = this.group.getObjectByName('location' + i);
+          if (warningMesh) {
+            const material = (warningMesh as THREE.Mesh).material as THREE.MeshStandardMaterial;
+            material.map = this.warningLocationTexture;
+            material.emissiveMap = this.warningLocationTexture;
+          }
+        });
+      if (errorNum.length > 0)
+        errorNum.forEach((i) => {
+          const warningMesh = this.group.getObjectByName('location' + i);
+          if (warningMesh) {
+            const material = (warningMesh as THREE.Mesh).material as THREE.MeshStandardMaterial;
+            material.map = this.errorLocationTexture;
+            material.emissiveMap = this.errorLocationTexture;
+          }
+        });
+    }
+  }
+
+  addBeltText() {
+    if (!this.group) {
+      return;
+    }
+    const locationMaterial = new THREE.MeshStandardMaterial({
+      map: this.locationTexture,
+      emissiveMap: this.locationTexture,
+      transparent: true,
+      side: THREE.DoubleSide,
+    });
+
+    const locationX = [-8.406, -6.597, -4.787, -2.967, -1.118, 0.692, 2.501, 4.411, 6.211, 8.0];
+    for (let i = 1; i <= this.glob.N; i++) {
+      if (!this.group.getObjectByName('fiberMeg' + i)) {
+        const position = this.msgPositions[i];
+        const element = document.getElementById('fiberMeg' + i) as HTMLElement;
+        if (element) {
+          const mainCSS3D = new CSS3DObject(element);
+          mainCSS3D.name = 'fiberMeg' + i;
+          mainCSS3D.scale.set(0.008, 0.008, 0.008);
+          mainCSS3D.position.set(position.x, -97.8, position.z);
+          const lookAtPosition = this.model.camera.position;
+          mainCSS3D.lookAt(lookAtPosition.x, lookAtPosition.y, lookAtPosition.z);
+          this.group.add(mainCSS3D);
+        }
+        const geometry = new THREE.PlaneGeometry(1, 0.9);
+        const location = new THREE.Mesh(geometry, locationMaterial);
+        location.scale.set(0.3, 0.3, 0.3);
+        location.name = 'location' + i;
+        location.position.set(locationX[i - 1], 1.384, position.z);
+        location.layers.enable(this.glob.ENTIRE_SCENE);
+
+        this.group.add(location);
+      }
+    }
+  }
+
+  render() {
+    const _this = this;
+    if (this.model && this.model.scene.getObjectByName(this.modelName)) {
+      this.group?.traverse((obj) => {
+        _this.darkenNonBloomed(obj);
+      });
+      this.bloomComposer?.render();
+      this.group?.traverse((obj) => {
+        _this.restoreMaterial(obj);
+      });
+      this.finalComposer?.render();
+      this.model.css3dRender?.render(this.model.scene as THREE.Scene, this.model.camera as THREE.PerspectiveCamera);
+    }
+  }
+
+  setRenderPass = () => {
+    this.bloomLayer.set(this.glob.BLOOM_SCENE);
+
+    const params = {
+      bloomStrength: 2,
+      bloomThreshold: 0,
+      bloomRadius: 0,
+    };
+    const renderScene = new RenderPass(this.model.scene, this.model.camera);
+
+    const bloomPass = new UnrealBloomPass(new THREE.Vector2(window.innerWidth, window.innerHeight), 1.5, 0.4, 0.85);
+
+    bloomPass.strength = params.bloomStrength;
+    bloomPass.radius = params.bloomRadius;
+    bloomPass.threshold = params.bloomThreshold;
+
+    this.bloomComposer = new EffectComposer(this.model.renderer);
+    this.bloomComposer.renderToScreen = false;
+    this.bloomComposer.addPass(renderScene);
+    this.bloomComposer.addPass(bloomPass);
+
+    const finalPass = new ShaderPass(
+      new THREE.ShaderMaterial({
+        uniforms: {
+          baseTexture: { value: null },
+          bloomTexture: { value: this.bloomComposer.renderTarget2.texture },
+        },
+        vertexShader: `
+      varying vec2 vUv;
+
+      void main() {
+        vUv = uv;
+        gl_Position = projectionMatrix * modelViewMatrix * vec4( position, 1.0 );
+      }`,
+        fragmentShader: `uniform sampler2D baseTexture;
+      uniform sampler2D bloomTexture;
+
+      varying vec2 vUv;
+
+      void main() {
+        gl_FragColor = ( texture2D( baseTexture, vUv ) + vec4( 1.0, 1.0, 1.0, 0.0 ) * texture2D( bloomTexture, vUv ) );
+      }`,
+        defines: {},
+      }),
+      'baseTexture'
+    );
+    const gammaCorrection = new ShaderPass(GammaCorrectionShader);
+    finalPass.needsSwap = true;
+
+    this.finalComposer = new EffectComposer(this.model.renderer);
+    this.finalComposer.addPass(renderScene);
+
+    this.finalComposer.addPass(gammaCorrection);
+    this.finalComposer.addPass(finalPass);
+  };
+
+  getPositions(num = 40) {
+    const curve1 = new THREE.LineCurve3(new THREE.Vector3(-9.676, 0.814, 0.019), new THREE.Vector3(9.68, 0.814, 0.019));
+    // const curve2 = new THREE.LineCurve3(new THREE.Vector3(-7.758, -0.462, -0.275), new THREE.Vector3(20.758, 2.037, -0.275));
+
+    // const len1 = curve1.getLength();
+    // const len2 = curve2.getLength();
+
+    // const unit = len1 + len2 / num;
+    // const num1 = Math.floor(unit * len1);
+    // const num2 = Math.floor(unit * len2);
+
+    const points1 = curve1.getPoints(num);
+    this.msgPositions = curve1.getPoints(this.glob.N);
+    // const points2 = curve2.getPoints(num2);
+
+    this.positions = [...points1];
+  }
+
+  drawSpheres = () => {
+    const _this = this;
+    return new Promise((resolve) => {
+      new THREE.TextureLoader().load('/model/img/texture-smoke.png', (texture) => {
+        texture.encoding = THREE.sRGBEncoding;
+        const geometry = new THREE.BufferGeometry();
+        geometry.setFromPoints(_this.positions);
+        const material = new THREE.PointsMaterial({
+          color: '#FFFFAF',
+          size: 0.13,
+          map: texture,
+          transparent: true, // 开启透明度
+        });
+        const points = new THREE.Points(geometry, material);
+        points.renderOrder = 0;
+        _this.group.add(points);
+        points.layers.enable(_this.glob.BLOOM_SCENE);
+        resolve(null);
+      });
+    });
+  };
+
+  darkenNonBloomed(obj) {
+    if (obj.isMesh && this.bloomLayer.test(obj.layers) === false) {
+      const opacity = obj.material.opacity;
+      this.materials[obj.uuid] = obj.material;
+      obj.material = this.darkMaterial.clone();
+      obj.material.opacity = opacity;
+    }
+  }
+
+  restoreMaterial(obj) {
+    if (this.materials[obj.uuid]) {
+      obj.material = this.materials[obj.uuid];
+      delete this.materials[obj.uuid];
+    }
+  }
+
+  /* 点击 */
+  mousedownModel(intersects: THREE.Intersection<THREE.Object3D<THREE.Event>>[]) {
+    // 判断是否点击到视频
+    intersects.find((intersect) => {
+      const mesh = intersect.object;
+      if (mesh.name === 'player1') {
+        return true;
+      }
+      return false;
+    });
+  }
+
+  mountedThree() {
+    this.locationTexture = new THREE.TextureLoader().load('/model/img/location.png');
+    this.warningLocationTexture = new THREE.TextureLoader().load('/model/img/location.png');
+    this.errorLocationTexture = new THREE.TextureLoader().load('/model/img/location.png');
+    this.locationTexture.encoding = this.warningLocationTexture.encoding = this.warningLocationTexture.encoding = THREE.sRGBEncoding;
+
+    return new Promise(async (resolve) => {
+      this.model.renderer.sortObjects = true;
+      this.model.camera.position.set(0, 3.1, 500);
+      this.setRenderPass();
+      this.model.orbitControls.update();
+      // this.model.orbitControls.addEventListener('change', _this.render.bind(_this));
+      this.model.setModel(['laneway']).then(async (gltf) => {
+        this.group = gltf[0];
+        this.group.position.set(0, 0.7, 0);
+        (this.group as THREE.Group).remove(this.group.getObjectByName('mesh001'));
+        const optical = this.group.getObjectByName('optical_fiber_');
+        if (optical) {
+          optical.renderOrder = 9;
+          optical.material = new THREE.MeshBasicMaterial({
+            color: 0x555555,
+            side: THREE.DoubleSide,
+            transparent: true,
+            opacity: 0.15,
+          });
+        }
+
+        this.getPositions();
+        this.addLight();
+        this.drawSpheres().then(() => {
+          // this.render();
+        });
+        this.addBeltText();
+        resolve(null);
+      });
+
+      // window.onresize = function () {
+      //   const width = window.innerWidth;
+      //   const height = window.innerHeight;
+
+      //   model.camera.aspect = width / height;
+      //   model.camera.updateProjectionMatrix();
+
+      //   model.renderer.setSize(width, height);
+
+      //   bloomComposer.setSize(width, height);
+      //   finalComposer.setSize(width, height);
+
+      //   render();
+      // };
+    });
+  }
+
+  destroy() {
+    this.model.clearGroup(this.group);
+    this.model = null;
+    this.group = null;
+  }
+}
+
+export default GrottoFiber;

+ 168 - 0
src/views/vent/monitorManager/wokerFaceMonitor/fiber.threejs.ts

@@ -0,0 +1,168 @@
+import * as THREE from 'three';
+import UseThree from '../../../../utils/threejs/useThree';
+import BeltFiber from './fiber.belt.threejs';
+import WorkFace from './fiber.workFace.threejs';
+import { animateCamera } from '/@/utils/threejs/util';
+import { useAppStore } from '/@/store/modules/app';
+
+// 模型对象、 文字对象
+let model,
+  beltFiberObj: BeltFiber,
+  workerFaceFiberObj: WorkFace,
+  group,
+  fiberType = 'beltFiber'; // workerFaceFiber
+const appStore = useAppStore();
+
+// 鼠标点击、松开事件
+const mouseEvent = (event) => {
+  event.stopPropagation();
+  const widthScale = appStore.getWidthScale;
+  const heightScale = appStore.getHeightScale;
+  // 将鼠标位置归一化为设备坐标。x 和 y 方向的取值范围是 (-1 to +1)
+  model.mouse.x =
+    ((-model.canvasContainer.getBoundingClientRect().left * widthScale + event.clientX) / (model.canvasContainer.clientWidth * widthScale)) * 2 - 1;
+  model.mouse.y =
+    -((-model.canvasContainer.getBoundingClientRect().top + event.clientY) / (model.canvasContainer.clientHeight * heightScale)) * 2 + 1;
+  (model.rayCaster as THREE.Raycaster).setFromCamera(model.mouse, model.camera as THREE.Camera);
+  if (group) {
+    if (fiberType === 'beltFiber') {
+      beltFiberObj.mousedownModel.call(beltFiberObj, model.rayCaster);
+    } else if (fiberType === 'workFace') {
+      workerFaceFiberObj.mousedownModel.call(workerFaceFiberObj, model.rayCaster);
+    }
+
+    // const intersects = model.rayCaster?.intersectObjects([...group.children]) as THREE.Intersection[];
+    // if (intersects.length > 0) {
+    //   if (fiberType === 'beltFiber') {
+    //     beltFiberObj.mousedownModel.call(beltFiberObj, intersects);
+    //   } else if (fiberType === 'workFace') {
+    //     workerFaceFiberObj.mousedownModel.call(workerFaceFiberObj, intersects);
+    //   }
+    // }
+  }
+};
+
+const addMouseEvent = () => {
+  // 定义鼠标点击事件
+  model.canvasContainer?.addEventListener('mousedown', mouseEvent.bind(null));
+  model.canvasContainer?.addEventListener('pointerup', (event) => {
+    event.stopPropagation();
+    // 单道、 双道
+    if (fiberType === 'beltFiber') {
+      beltFiberObj.mouseUpModel.call(beltFiberObj);
+    } else if (fiberType === 'workFace') {
+      workerFaceFiberObj.mouseUpModel.call(workerFaceFiberObj);
+    }
+  });
+};
+
+const render = () => {
+  if (model.animationId != -1) {
+    model.animationId = requestAnimationFrame(render);
+    model.css3dRender?.render(model.scene as THREE.Scene, model.camera as THREE.PerspectiveCamera);
+    model.stats?.update();
+  }
+};
+
+export const refreshModal = (warningNum, errorNum) => {
+  if (fiberType === 'beltFiber') {
+    beltFiberObj.monitorData(warningNum, errorNum);
+    beltFiberObj.render();
+  } else if (fiberType === 'workFace') {
+    workerFaceFiberObj.render();
+  }
+};
+
+export const addFiberText = (selectData) => {
+  if (fiberType === 'beltFiber') {
+    return beltFiberObj.addBeltText.call(beltFiberObj, selectData);
+  }
+  if (fiberType === 'workFace') {
+    return workerFaceFiberObj.addMonitorText.call(workerFaceFiberObj, selectData);
+  }
+};
+
+// 切换风窗类型
+export const setModelType = (type) => {
+  fiberType = type;
+  return new Promise((resolve) => {
+    if (fiberType === 'beltFiber') {
+      group = beltFiberObj.group;
+      if (model.scene.getObjectByName('workFace')) {
+        model.scene.remove(workerFaceFiberObj.group);
+      }
+      const oldCameraPosition = { x: 124.736, y: 63.486, z: 103.337 };
+      model.scene.add(beltFiberObj.group);
+      model.orbitControls.addEventListener('change', beltFiberObj.render.bind(beltFiberObj));
+
+      setTimeout(async () => {
+        resolve(null);
+
+        const position = { x: 0, y: 3.8, z: 10.5 };
+        await animateCamera(
+          oldCameraPosition,
+          oldCameraPosition,
+          { x: position.x, y: position.y, z: position.z },
+          { x: 10, y: 3.1, z: -8.8 },
+          model,
+          0.8,
+          beltFiberObj.render.bind(beltFiberObj)
+        );
+      }, 300);
+    }
+    if (fiberType === 'workFace') {
+      group = workerFaceFiberObj.group;
+
+      if (model.scene.getObjectByName('beltFiber')) {
+        model.scene.remove(beltFiberObj.group);
+      }
+      const oldCameraPosition = { x: 124.736, y: 63.486, z: 103.337 };
+      model.scene.add(workerFaceFiberObj.group);
+      model.orbitControls.addEventListener('change', workerFaceFiberObj.render.bind(workerFaceFiberObj));
+
+      setTimeout(async () => {
+        resolve(null);
+
+        const position = { x: 0, y: 3.8, z: 1.5 };
+        await animateCamera(
+          oldCameraPosition,
+          oldCameraPosition,
+          { x: position.x, y: position.y, z: position.z },
+          { x: 10, y: 3.1, z: -8.8 },
+          model,
+          0.8,
+          workerFaceFiberObj.render.bind(workerFaceFiberObj)
+        );
+      }, 300);
+    }
+  });
+};
+
+export const mountedThree = () => {
+  return new Promise(async (resolve) => {
+    model = new UseThree('#fiberBox', '#fiber3DCSS');
+    model.setEnvMap('test1');
+    model.renderer.toneMappingExposure = 1;
+    model.camera.position.set(100, 0, 1000);
+
+    beltFiberObj = new BeltFiber(model);
+    await beltFiberObj.mountedThree();
+
+    workerFaceFiberObj = new WorkFace(model);
+    await workerFaceFiberObj.mountedThree();
+    
+    addMouseEvent()
+    render();
+    resolve(null);
+  });
+};
+
+export const destroy = () => {
+  if (model) {
+    beltFiberObj.destroy();
+    model.deleteModal();
+    model = null;
+    group = null;
+    beltFiberObj = null;
+  }
+};

+ 415 - 0
src/views/vent/monitorManager/wokerFaceMonitor/fiber.workFace.threejs.ts

@@ -0,0 +1,415 @@
+import * as THREE from 'three';
+import { EffectComposer } from 'three/examples/jsm/postprocessing/EffectComposer.js';
+import { RenderPass } from 'three/examples/jsm/postprocessing/RenderPass.js';
+import { ShaderPass } from 'three/examples/jsm/postprocessing/ShaderPass.js';
+import { OutlinePass } from 'three/examples/jsm/postprocessing/OutlinePass.js';
+import { FXAAShader } from 'three/examples/jsm/shaders/FXAAShader.js';
+import { UnrealBloomPass } from 'three/examples/jsm/postprocessing/UnrealBloomPass.js';
+import { GammaCorrectionShader } from 'three/examples/jsm/shaders/GammaCorrectionShader.js';
+import { CSS3DObject } from 'three/examples/jsm/renderers/CSS3DRenderer.js';
+// import * as dat from 'dat.gui';
+// const gui = new dat.GUI();
+// gui.domElement.style = 'position:absolute;top:100px;left:10px;z-index:99999999999999';
+
+class WorkFace {
+  model;
+  modelName = 'workFace';
+  group: THREE.Object3D = new THREE.Object3D();
+  bloomComposer: EffectComposer | null = null;
+  finalComposer: EffectComposer | null = null;
+  outlinePass: OutlinePass | null = null;
+  positions: THREE.Vector3[][] = [];
+  msgPositions = [];
+  bloomLayer = new THREE.Layers();
+  darkMaterial = new THREE.MeshBasicMaterial({ color: 'black', transparent: true, side: THREE.DoubleSide });
+  materials = {};
+  glob = {
+    ENTIRE_SCENE: 0,
+    BLOOM_SCENE: 1,
+    N: 100,
+  };
+  locationTexture: THREE.Texture | null = null;
+  warningLocationTexture: THREE.Texture | null = null;
+  errorLocationTexture: THREE.Texture | null = null;
+  playerStartClickTime1 = new Date().getTime();
+  playerStartClickTime2 = new Date().getTime();
+
+  constructor(model) {
+    this.model = model;
+    this.group.name = this.modelName;
+  }
+  addLight() {
+    const directionalLight = new THREE.DirectionalLight(0xffffff, 1.2);
+    directionalLight.position.set(-0.8, 23, 3.9);
+    this.group.add(directionalLight);
+    directionalLight.target = this.group;
+
+    // gui.add(directionalLight.position, 'x', -10, 20).onChange(function (value) {
+    //   directionalLight.position.x = Number(value);
+    //   _this.render();
+    // });
+    // gui.add(directionalLight.position, 'y', -50, 50).onChange(function (value) {
+    //   directionalLight.position.y = Number(value);
+    //   _this.render();
+    // });
+    // gui.add(directionalLight.position, 'z', -20, 20).onChange(function (value) {
+    //   directionalLight.position.z = Number(value);
+    //   _this.render();
+    // });
+    // const pointLight5 = new THREE.PointLight(0xffffff, 0.8, 120);
+    // pointLight5.position.set(-54, 30, 23.8);
+    // pointLight5.shadow.bias = 0.05;
+    // this.group.add(pointLight5);
+
+    // const pointLight7 = new THREE.PointLight(0xffffff, 1, 1000);
+    // pointLight7.position.set(45, 51, -4.1);
+    // pointLight7.shadow.bias = 0.05;
+    // this.model.scene.add(pointLight7);
+
+    const spotLight = new THREE.SpotLight();
+    spotLight.angle = Math.PI / 2;
+    spotLight.penumbra = 0;
+    spotLight.castShadow = true;
+    spotLight.intensity = 1;
+
+    spotLight.shadow.camera.near = 0.5; // default
+    spotLight.shadow.focus = 1.2;
+    spotLight.shadow.bias = -0.000002;
+
+    spotLight.position.set(-7.19, 199, -68.1);
+    // this.group.add(spotLight);
+
+    // gui.add(directionalLight.position, 'x', -10, 20).onChange(function (value) {
+    //   directionalLight.position.x = Number(value);
+    //   _this.render();
+    // });
+    // gui.add(directionalLight.position, 'y', -50, 50).onChange(function (value) {
+    //   directionalLight.position.y = Number(value);
+    //   _this.render();
+    // });
+    // gui.add(directionalLight.position, 'z', -20, 20).onChange(function (value) {
+    //   directionalLight.position.z = Number(value);
+    //   _this.render();
+    // });
+
+    // gui.add(spotLight.position, 'x', -600, 600).onChange(function (value) {
+    //   spotLight.position.x = Number(value);
+    //   _this.render();
+    // });
+    // gui.add(spotLight.position, 'y', -600, 800).onChange(function (value) {
+    //   spotLight.position.y = Number(value);
+    //   _this.render();
+    // });
+    // gui.add(spotLight.position, 'z', -500, 1000).onChange(function (value) {
+    //   spotLight.position.z = Number(value);
+    //   _this.render();
+    // });
+  }
+  // 设置模型位置
+  setModalPosition() {
+    this.group?.scale.set(22, 22, 22);
+    this.group?.position.set(-15, 25, 15);
+  }
+
+  monitorData(warningNum = [3, 6], errorNum = [9]) {
+    if (this.group) {
+      if (warningNum.length > 0)
+        warningNum.forEach((i) => {
+          const warningMesh = this.group.getObjectByName('location' + i);
+          if (warningMesh) {
+            const material = (warningMesh as THREE.Mesh).material as THREE.MeshStandardMaterial;
+            material.map = this.warningLocationTexture;
+            material.emissiveMap = this.warningLocationTexture;
+          }
+        });
+      if (errorNum.length > 0)
+        errorNum.forEach((i) => {
+          const warningMesh = this.group.getObjectByName('location' + i);
+          if (warningMesh) {
+            const material = (warningMesh as THREE.Mesh).material as THREE.MeshStandardMaterial;
+            material.map = this.errorLocationTexture;
+            material.emissiveMap = this.errorLocationTexture;
+          }
+        });
+    }
+  }
+
+  addMonitorText(selectData) {
+    if (!this.group) {
+      return;
+    }
+    const locationMaterial = new THREE.MeshStandardMaterial({
+      map: this.locationTexture,
+      emissiveMap: this.locationTexture,
+      transparent: true,
+      side: THREE.DoubleSide,
+    });
+
+    // const locationX = [-8.406, -6.597, -4.787, -2.967, -1.118, 0.692, 2.501, 4.411, 6.211, 8.0];
+    for (let i = 1; i <= this.glob.N; i++) {
+      if (!this.group.getObjectByName('fiberMeg' + i)) {
+        const position = this.msgPositions[i];
+        const element = document.getElementById('fiberMeg' + i) as HTMLElement;
+        if (element) {
+          const mainCSS3D = new CSS3DObject(element);
+          mainCSS3D.name = 'fiberMeg' + i;
+          mainCSS3D.scale.set(0.008, 0.008, 0.008);
+          mainCSS3D.position.set(position.x, -97.8, position.z);
+          const lookAtPosition = this.model.camera.position;
+          mainCSS3D.lookAt(lookAtPosition.x, lookAtPosition.y, lookAtPosition.z);
+          this.group.add(mainCSS3D);
+        }
+        // const geometry = new THREE.PlaneGeometry(1, 0.9);
+        // const location = new THREE.Mesh(geometry, locationMaterial);
+        // location.scale.set(0.3, 0.3, 0.3);
+        // location.name = 'location' + i;
+        // location.position.set(locationX[i - 1], 1.384, position.z);
+        // location.layers.enable(this.glob.ENTIRE_SCENE);
+
+        // this.group.add(location);
+      }
+    }
+  }
+
+  render() {
+    const _this = this;
+    if (this.model && this.model.scene.getObjectByName(this.modelName)) {
+      this.group?.traverse((obj) => {
+        _this.darkenNonBloomed(obj);
+      });
+      this.bloomComposer?.render();
+      this.group?.traverse((obj) => {
+        _this.restoreMaterial(obj);
+      });
+      this.finalComposer?.render();
+      this.model.css3dRender?.render(this.model.scene as THREE.Scene, this.model.camera as THREE.PerspectiveCamera);
+    }
+  }
+
+  setRenderPass = () => {
+    this.bloomLayer.set(this.glob.BLOOM_SCENE);
+
+    const params = {
+      bloomStrength: 2.5,
+      bloomThreshold: 0,
+      bloomRadius: 0,
+    };
+    const renderScene = new RenderPass(this.model.scene, this.model.camera);
+
+    const bloomPass = new UnrealBloomPass(new THREE.Vector2(window.innerWidth, window.innerHeight), 1.5, 0.4, 0.85);
+
+    bloomPass.strength = params.bloomStrength;
+    bloomPass.radius = params.bloomRadius;
+    bloomPass.threshold = params.bloomThreshold;
+
+    this.bloomComposer = new EffectComposer(this.model.renderer);
+    this.bloomComposer.renderToScreen = false;
+    this.bloomComposer.addPass(renderScene);
+    this.bloomComposer.addPass(bloomPass);
+
+    const finalPass = new ShaderPass(
+      new THREE.ShaderMaterial({
+        uniforms: {
+          baseTexture: { value: null },
+          bloomTexture: { value: this.bloomComposer.renderTarget2.texture },
+        },
+        vertexShader: `
+      varying vec2 vUv;
+
+      void main() {
+        vUv = uv;
+        gl_Position = projectionMatrix * modelViewMatrix * vec4( position, 1.0 );
+      }`,
+        fragmentShader: `uniform sampler2D baseTexture;
+      uniform sampler2D bloomTexture;
+
+      varying vec2 vUv;
+
+      void main() {
+        gl_FragColor = ( texture2D( baseTexture, vUv ) + vec4( 1.0, 1.0, 1.0, 0.0 ) * texture2D( bloomTexture, vUv ) );
+      }`,
+        defines: {},
+      }),
+      'baseTexture'
+    );
+    const gammaCorrection = new ShaderPass(GammaCorrectionShader);
+    finalPass.needsSwap = true;
+
+    this.finalComposer = new EffectComposer(this.model.renderer);
+    this.finalComposer.addPass(renderScene);
+
+    this.finalComposer.addPass(gammaCorrection);
+    this.finalComposer.addPass(finalPass);
+
+    const effectFXAA = new ShaderPass(FXAAShader);
+    effectFXAA.uniforms['resolution'].value.set(1 / window.innerWidth, 1 / window.innerHeight);
+    this.finalComposer.addPass(effectFXAA);
+
+    // this.outlinePass = new OutlinePass(new THREE.Vector2(window.innerWidth, window.innerHeight), this.model.scene, this.model.camera);
+    // this.finalComposer.addPass(this.outlinePass);
+  };
+
+  getPositions(num = 40) {
+    const curve1 = new THREE.LineCurve3(new THREE.Vector3(-0.818, 0.002, 0.363), new THREE.Vector3(0.722, 0.002, 0.363)); // 前
+    const curve2 = new THREE.LineCurve3(new THREE.Vector3(-0.818, 0.002, -0.459), new THREE.Vector3(0.718, -0.002, -0.459)); // 中
+    const curve3 = new THREE.LineCurve3(new THREE.Vector3(0.345, 0.008, 0.329), new THREE.Vector3(0.345, 0.008, -0.43)); // 后
+
+    const len1 = curve1.getLength();
+    const len2 = curve2.getLength();
+    const len3 = curve3.getLength();
+
+    const unit = (len1 + len2 + len3) / num;
+    const num1 = Math.floor(len1 / unit);
+    const num2 = Math.floor(len2 / unit);
+    const num3 = Math.floor(len3 / unit);
+
+    const points1 = curve1.getPoints(num1);
+    const points2 = curve2.getPoints(num2);
+    const points3 = curve3.getPoints(num3);
+
+    this.positions = [points1, points2, points3];
+
+    this.msgPositions = [curve1.getSpacedPoints(10), curve2.getSpacedPoints(10), curve3.getSpacedPoints(10)];
+  }
+
+  drawSpheres = () => {
+    const _this = this;
+    const pointLines = new THREE.Object3D();
+    pointLines.name = 'pointLines';
+    return new Promise((resolve) => {
+      new THREE.TextureLoader().load('/model/img/texture-smoke.png', (texture) => {
+        texture.encoding = THREE.sRGBEncoding;
+        const material = new THREE.PointsMaterial({
+          color: '#FFFFAF',
+          size: 0.03,
+          map: texture,
+          transparent: true, // 开启透明度
+        });
+
+        _this.positions.forEach((position, index) => {
+          const geometry = new THREE.BufferGeometry();
+          geometry.setFromPoints(position);
+          const points = new THREE.Points(geometry, material);
+          points.renderOrder = 0;
+          index == 0 ? (points.name = 'line_q') : index == 1 ? (points.name = 'line_h') : (points.name = 'line_z');
+          pointLines.add(points);
+          points.layers.enable(_this.glob.BLOOM_SCENE);
+        });
+
+        this.group.add(pointLines);
+
+        resolve(null);
+      });
+    });
+  };
+
+  darkenNonBloomed(obj) {
+    if (obj.isMesh && this.bloomLayer.test(obj.layers) === false) {
+      const opacity = obj.material.opacity;
+      this.materials[obj.uuid] = obj.material;
+      obj.material = this.darkMaterial.clone();
+      obj.material.opacity = opacity;
+    }
+  }
+
+  restoreMaterial(obj) {
+    if (this.materials[obj.uuid]) {
+      obj.material = this.materials[obj.uuid];
+      delete this.materials[obj.uuid];
+    }
+  }
+
+  resetMesh() {
+    const opticalFiber = this.group.getObjectByName('opticalfiber');
+    const optical = opticalFiber?.getObjectByName('optical_fiber_02');
+    const optical1 = opticalFiber?.getObjectByName('optical_fiber_01');
+    const optical2 = opticalFiber?.getObjectByName('optical_fiber_03');
+    if (optical && optical1 && optical2) {
+      optical.renderOrder = 9;
+      optical1.renderOrder = 9;
+      optical2.renderOrder = 9;
+      optical.material =
+        optical1.material =
+        optical2.material =
+          new THREE.MeshStandardMaterial({
+            color: 0x555555,
+            side: THREE.DoubleSide,
+            transparent: true,
+            opacity: 0.15,
+          });
+    }
+  }
+
+  /* 点击 */
+  mousedownModel(rayCaster: THREE.Raycaster) {
+    // const outlinePass = this.outlinePass;
+    // const selectedObjects = [];
+    // outlinePass.selectedObjects = selectedObjects;
+    const opticalFiber = this.group.getObjectByName('opticalfiber');
+    if (opticalFiber) {
+      const intersects = rayCaster?.intersectObjects([...opticalFiber.children]) as THREE.Intersection[];
+
+      // 判断是否点击到视频
+      intersects.find((intersect) => {
+        const mesh = intersect.object;
+        if (mesh.name.startsWith('optical_fiber_')) {
+          // outlinePass?.selectedObjects.push(mesh);
+          return true;
+        }
+        return false;
+      });
+    }
+    this.render();
+  }
+
+  mouseUpModel() {
+    //
+  }
+
+  mountedThree() {
+    this.locationTexture = new THREE.TextureLoader().load('/model/img/location.png');
+    this.warningLocationTexture = new THREE.TextureLoader().load('/model/img/location.png');
+    this.errorLocationTexture = new THREE.TextureLoader().load('/model/img/location.png');
+    this.locationTexture.encoding = this.warningLocationTexture.encoding = this.warningLocationTexture.encoding = THREE.sRGBEncoding;
+
+    return new Promise(async (resolve) => {
+      this.model.renderer.sortObjects = true;
+      this.model.camera.position.set(0, 3.1, 500);
+      this.setRenderPass();
+      this.model.orbitControls.update();
+      this.model.setModel(['workFace']).then(async (gltf) => {
+        this.group = gltf[0];
+        this.group.position.set(0, 1, 0);
+        this.group.scale.set(2, 2, 2);
+        this.resetMesh();
+        this.getPositions(this.glob.N);
+        this.addLight();
+        this.drawSpheres();
+        resolve(null);
+      });
+
+      // window.onresize = function () {
+      //   const width = window.innerWidth;
+      //   const height = window.innerHeight;
+
+      //   model.camera.aspect = width / height;
+      //   model.camera.updateProjectionMatrix();
+
+      //   model.renderer.setSize(width, height);
+
+      //   bloomComposer.setSize(width, height);
+      //   finalComposer.setSize(width, height);
+
+      //   render();
+      // };
+    });
+  }
+
+  destroy() {
+    this.model.clearGroup(this.group);
+    this.model = null;
+    this.group = null;
+  }
+}
+
+export default WorkFace;

+ 289 - 0
src/views/vent/monitorManager/wokerFaceMonitor/index.vue

@@ -0,0 +1,289 @@
+<template>
+  <div class="bg"
+    style="width: 100%; height: 100%; display: flex; justify-content: center; align-items: center; overflow: hidden">
+    <a-spin :spinning="loading" />
+    <!-- <div
+        id="fiber3DCSS"
+        class="threejs-Object-CSS"
+        v-show="!loading"
+        style="width: 100%; height: 100%; position: absolute; pointer-events: none; overflow: hidden; z-index: 1; top: 0"
+      >
+      <div v-for="i in fiberListNum">
+        <div v-for="item in 10" :id="'fiberMeg' + i + item" :key="item" class="monitor-msg-box"> 
+             
+            <div class="monitor-msg-container"  :class="{ errorColor: errorNum.includes(item), warningColor: warningNum.includes(item) }">
+              <div class="monitor-item">
+                <p class="item-title">{{ (item - 1) * 100 }}~{{ item * 100 }}m区域:</p>
+                <p><span class="data-title">平均温度(℃):</span><span class="num">30</span></p>
+                <p><span class="data-title">最高温度(℃):</span><span class="num">40</span></p>
+                <p><span class="data-title">最低温度(℃):</span><span class="num">10</span></p>
+              </div>
+            </div>
+          </div>
+        </div>
+    </div> -->
+    <div id="fiberBox"  style="width: 100%; height: 100%; position: absolute; overflow: hidden"> </div>
+  </div>
+  <div class="scene-box">
+    <div class="top-box">
+      <div class="top-center">
+        <div class="input-box">
+          <span class="input-title">模型展示范围:</span>
+          <a-select
+            class="title-select"
+            ref="select"
+            v-model:value="currentLen"
+            @change="handleLenChange"
+          >
+            <a-select-option value="1">0m~1000m</a-select-option>
+            <a-select-option value="2">1000m~2000m</a-select-option>
+            <a-select-option value="3">2000m~3000m</a-select-option>
+          </a-select>
+        </div>          
+      </div>
+    </div>
+    <div class="title-text">
+      {{ selectData.strname }}
+    </div>
+    <div class="bottom-tabs-box">
+      <a-tabs class="tabs-box" v-model:activeKey="activeKey" @change="tabChange">
+        <a-tab-pane key="1" tab="实时监测">
+          <MonitorTable columnsType="fiber_monitor"  :dataSource="dataSource" @selectRow="getSelectRow"
+            design-scope="fiber-monitor" title="皮带机监测">
+            <template #filterCell="{ column, record }">
+              <a-tag v-if="column.dataIndex === 'warnFlag'" :color="record.warnFlag == 0 ? 'green' : 'red'">{{
+                record.warnFlag == 0 ? '正常' : '报警'
+              }}</a-tag>
+              <a-tag v-if="column.dataIndex === 'netStatus'" :color="record.netStatus == 0 ? 'default' : 'green'">{{
+                record.netStatus == 0 ? '断开' : '连接'
+              }}</a-tag>
+              <div v-if="record.nwindownum == 1 && column.dataIndex === 'rearArea'">/</div>
+            </template>
+          </MonitorTable>
+        </a-tab-pane>
+        <a-tab-pane key="2" tab="实时曲线图" force-render>
+          <div class="tab-item" v-if="activeKey === '2'">
+            <DeviceEcharts chartsColumnsType="fiber_chart" xAxisPropType="strname" :dataSource="dataSource" height="100%"
+              :chartsColumns="chartsColumns" :device-list-api="baseList" device-type="fiber" />
+          </div>
+        </a-tab-pane>
+        <a-tab-pane key="3" tab="历史数据">
+          <div class="tab-item">
+            <HistoryTable columns-type="fibre" device-type="fiber" :device-list-api="baseList"
+              designScope="fiber-history" />
+          </div>
+        </a-tab-pane>
+        <a-tab-pane key="4" tab="报警历史">
+          <div class="tab-item">
+            <AlarmHistoryTable columns-type="alarm" device-type="fiber" :device-list-api="baseList"
+              designScope="alarm-history" />
+          </div>
+        </a-tab-pane>
+        <a-tab-pane key="5" tab="操作历史">
+          <div class="tab-item">
+            <HandlerHistoryTable columns-type="operatorhistory" device-type="fiber" :device-list-api="baseList"
+              designScope="alarm-history" />
+          </div>
+        </a-tab-pane>
+      </a-tabs>
+    </div>
+  </div>
+</template>
+
+<script setup lang="ts">
+import DeviceEcharts from '../comment/DeviceEcharts.vue';
+import { onBeforeMount, ref, onMounted, onUnmounted, reactive, toRaw, watch } from 'vue';
+import MonitorTable from '../comment/MonitorTable.vue';
+import HistoryTable from '../comment/HistoryTable.vue';
+import AlarmHistoryTable from '../comment/AlarmHistoryTable.vue';
+import HandlerHistoryTable from '../comment/HandlerHistoryTable.vue';
+import { mountedThree, destroy, setModelType, refreshModal, addFiberText } from './fiber.threejs';
+import { list, getTableList } from './fiber.api';
+import { list as baseList } from '../../deviceManager/windWindowTabel/ventanalyWindow.api';
+import { chartsColumns } from './fiber.data';
+import lodash from 'lodash';
+
+const fiberListNum = ref(3)
+
+const deviceBaseList = ref([]);
+const activeKey = ref('1');
+const loading = ref(false);
+
+const warningNum = ref<number[]>([])
+const errorNum = ref<number[]>([])
+
+
+// 默认初始是第一行
+const selectRowIndex = ref(0);
+const dataSource = ref([]);
+
+const currentLen = ref('1') // 选择光纤距离
+
+
+// 设备数据
+const controlType = ref(1);
+
+const tabChange = (activeKeyVal) => {
+  activeKey.value = activeKeyVal;
+};
+
+const initData = {
+  deviceID: '',
+  deviceType: '',
+  strname: '',
+  dataDh: '-', //压差
+  dataDtestq: '-', //测试风量
+  sourcePressure: '-', //气源压力
+  dataDequivalarea: '-',
+  netStatus: '0', //通信状态
+  fault: '气源压力超限',
+  forntArea: '0',
+  rearArea: '0',
+  frontRearDifference: '-',
+  rearPresentValue: '-',
+  maxarea: '',
+  nwindownum: 0
+};
+
+// 监测数据
+const selectData = reactive(lodash.cloneDeep(initData));
+
+watch([warningNum, errorNum], ([newWarningNum, newErrorNum]) => {
+  // 刷新
+  refreshModal(newWarningNum, newErrorNum)
+})
+
+function handleLenChange() {
+  //
+}
+
+// https获取监测数据
+let timer: null | NodeJS.Timeout = null;
+function getMonitor(){
+  if (Object.prototype.toString.call(timer) === '[object Null]') {
+    timer = setTimeout(async () => {
+      const data = await getDataSource();
+      Object.assign(selectData, data);
+      // addFiberText(selectData)
+      if (timer) {
+        timer = null;
+      }
+      getMonitor();
+    }, 1000);
+  }
+};
+
+async function getDataSource(){
+  console.log(11111)
+  const res = await list({ devicetype: 'fiber', pagetype: 'normal' });
+  console.log(2222, res)
+
+  dataSource.value = res.msgTxt[0].datalist || [];
+  dataSource.value.forEach((data: any) => {
+    const readData = data.readData;
+    data = Object.assign(data, readData);
+  });
+  const data: any = toRaw(dataSource.value[selectRowIndex.value]); //maxarea
+  return data;
+};
+
+// 获取设备基本信息列表
+function getDeviceBaseList(){
+  getTableList({ pageSize: 1000 }).then((res) => {
+    deviceBaseList.value = res.records;
+  });
+};
+
+// 切换检测数据
+function getSelectRow(selectRow, index){
+  
+  if (!selectRow) return;
+  selectRowIndex.value = index;
+  const baseData: any = deviceBaseList.value.find((baseData: any) => baseData.id === selectRow.deviceID);
+  Object.assign(selectData, initData, selectRow, baseData);
+  
+  setModelType('workFace')
+};
+
+onBeforeMount(() => {
+  getDeviceBaseList();
+});
+
+onMounted(() => {
+  loading.value = true;
+  
+  mountedThree().then(async () => {
+    loading.value = false;
+    // getMonitor();
+    setTimeout(() => {
+      warningNum.value = [3, 6]
+      errorNum.value = [9]
+    }, 3000)
+  });
+  getMonitor();
+});
+
+onUnmounted(() => {
+  destroy();
+  if (timer) {
+    clearTimeout(timer);
+    timer = undefined;
+  }
+});
+
+</script>
+<style lang="less" scoped>
+@import '/@/design/vent/modal.less';
+@ventSpace: zxm;
+
+:deep(.@{ventSpace}-tabs-tabpane-active) {
+  overflow: auto;
+}
+
+.input-box {
+  display: flex;
+  align-items: center;
+  padding-left: 10px;
+
+  .input-title {
+    color: #73e8fe;
+    width: auto;
+  }
+
+  .@{ventSpace}-input-number {
+    border-color: #ffffff88 !important;
+  }
+
+  margin-right: 10px;
+}
+.monitor-msg-box{
+  width: 170px;
+  margin-top: 100px;
+  .monitor-msg-container{
+    width: 170px;
+    height: 150px;
+    box-shadow: rgba(128, 128, 128, 0.3) 0px 0px 40px inset;
+    border: 1px solid rgba(128, 128, 128, 0.3);
+    background-color: transparent;
+  }
+  .errorColor{
+    box-shadow: #F73B2440 0px 0px 40px inset;
+    border: 1px solid #F73B2440;
+  }
+  .warningColor{
+    box-shadow: #FF9B1740 0px 0px 40px inset;
+    border: 1px solid #FF9B1740;
+  }
+  .monitor-item{
+    padding: 10px 10px 0px 10px;
+    color: #fff;
+    letter-spacing: 2px;
+    .item-title{
+      color: #73e8fe;
+    }
+    .num{
+      color: #FFA500;
+    }
+  }
+}
+</style>

+ 0 - 2
src/views/vent/performance/comment/DeviceModal.vue

@@ -30,8 +30,6 @@ import { onMounted } from 'vue';
     setModalProps({ confirmLoading: false });
     Object.assign(record, data.record);
     
-
-
     new DocsAPI.DocEditor("fileEdit", // 元素id
     {
       type: "desktop",

Some files were not shown because too many files changed in this diff