Browse Source

mky 设备管理、监测、demo

hrx 2 years ago
parent
commit
d96ee1c948
100 changed files with 7721 additions and 206 deletions
  1. 10 4
      .env.development
  2. 7 2
      .env.production
  3. 0 4
      .yarnclean
  4. 5 0
      index.html
  5. 127 7
      package.json
  6. 9 0
      src/App.vue
  7. 3 0
      src/api/sys/model/ventModal.ts
  8. 10 0
      src/api/sys/vent.ts
  9. BIN
      src/assets/images/vent/bg.png
  10. BIN
      src/assets/images/vent/light.png
  11. BIN
      src/assets/images/vent/short-light.png
  12. BIN
      src/assets/images/vent/tj.png
  13. 53 0
      src/assets/less/modal.less
  14. 1 0
      src/components/Form/index.ts
  15. 1 1
      src/components/Form/src/BasicForm.vue
  16. 2 0
      src/components/Form/src/componentMap.ts
  17. 268 0
      src/components/Form/src/jeecg/components/MTreeSelect.vue
  18. 1 0
      src/components/Form/src/types/index.ts
  19. 8 1
      src/components/Table/src/BasicTable.vue
  20. 5 1
      src/components/Table/src/hooks/useTableForm.ts
  21. 1 1
      src/components/Table/src/types/table.ts
  22. 27 5
      src/components/chart/Bar.vue
  23. 22 9
      src/components/chart/BarMulti.vue
  24. 17 8
      src/components/chart/LineMulti.vue
  25. 8 1
      src/components/jeecg/OnLine/SearchFormItem.vue
  26. 5 0
      src/design/ant/table.less
  27. 418 0
      src/hooks/core/useThree copy.ts
  28. 441 0
      src/hooks/core/useThree.ts
  29. 2 0
      src/hooks/setting/index.ts
  30. 6 0
      src/hooks/setting/useRootSetting.ts
  31. 92 0
      src/hooks/web/useVentWebSocket.ts
  32. 35 0
      src/hooks/web/useWebColumns.ts
  33. 1 1
      src/layouts/default/header/components/ErrorAction.vue
  34. 1 1
      src/layouts/default/header/components/FullScreen.vue
  35. 1 1
      src/layouts/default/header/components/LockScreen.vue
  36. 1 1
      src/layouts/default/setting/components/TypePicker.vue
  37. 2 2
      src/layouts/default/tabs/index.vue
  38. 2 0
      src/logics/initAppConfig.ts
  39. 4 0
      src/router/constant.ts
  40. 25 1
      src/router/routes/basic.ts
  41. 2 2
      src/settings/projectSetting.ts
  42. 28 0
      src/store/modules/vent.ts
  43. 2 0
      src/utils/env.ts
  44. 2 1
      src/utils/lib/echarts.ts
  45. 64 0
      src/utils/threejs/ResourceTracker.js
  46. 44 0
      src/utils/threejs/loadGltf.worker.js
  47. 38 0
      src/utils/threejs/main.worker.ts
  48. 93 0
      src/utils/threejs/util.ts
  49. 1 1
      src/views/dashboard/Analysis/components/BdcTabCard.vue
  50. 1 1
      src/views/dashboard/Analysis/components/SaleTabCard.vue
  51. 1 1
      src/views/dashboard/Analysis/homePage/IndexBdc.vue
  52. 11 2
      src/views/demo/charts/Line.vue
  53. 189 47
      src/views/demo/charts/Map.vue
  54. 75 0
      src/views/demo/charts/Map1 copy.vue
  55. 138 0
      src/views/demo/charts/Num.vue
  56. 429 99
      src/views/demo/charts/Pie.vue
  57. 135 0
      src/views/demo/charts/Pie1.vue
  58. 432 0
      src/views/demo/charts/PieAnimation.ts
  59. 325 0
      src/views/demo/threejs/damper.vue
  60. 1 1
      src/views/sys/login/LoginForm.vue
  61. 6 0
      src/views/sys/micro/index.vue
  62. 156 0
      src/views/vent/comment/EditRowTable.vue
  63. 58 0
      src/views/vent/deviceManager/comment/DeviceModal.vue
  64. 58 0
      src/views/vent/deviceManager/comment/FormModal.vue
  65. 264 0
      src/views/vent/deviceManager/comment/NormalTable.vue
  66. 43 0
      src/views/vent/deviceManager/comment/cameraTabel/camera.api.ts
  67. 49 0
      src/views/vent/deviceManager/comment/cameraTabel/camera.data.ts
  68. 60 0
      src/views/vent/deviceManager/comment/pointTabel/point.api.ts
  69. 50 0
      src/views/vent/deviceManager/comment/pointTabel/point.data.ts
  70. 60 0
      src/views/vent/deviceManager/damperTabel/damper.api.ts
  71. 292 0
      src/views/vent/deviceManager/damperTabel/damper.data.ts
  72. 26 0
      src/views/vent/deviceManager/damperTabel/index.vue
  73. 60 0
      src/views/vent/deviceManager/deviceColumns/columns.api.ts
  74. 283 0
      src/views/vent/deviceManager/deviceColumns/columns.data.ts
  75. 25 0
      src/views/vent/deviceManager/deviceColumns/index.vue
  76. 60 0
      src/views/vent/deviceManager/fanTabel/fan.api.ts
  77. 123 0
      src/views/vent/deviceManager/fanTabel/fan.data.ts
  78. 25 0
      src/views/vent/deviceManager/fanTabel/index.vue
  79. 25 0
      src/views/vent/deviceManager/ledTabel/index.vue
  80. 60 0
      src/views/vent/deviceManager/ledTabel/led.api.ts
  81. 126 0
      src/views/vent/deviceManager/ledTabel/led.data.ts
  82. 25 0
      src/views/vent/deviceManager/pointTabel/index.vue
  83. 60 0
      src/views/vent/deviceManager/pointTabel/point.api.ts
  84. 587 0
      src/views/vent/deviceManager/pointTabel/point.data.ts
  85. 25 0
      src/views/vent/deviceManager/sensorTabel/index.vue
  86. 60 0
      src/views/vent/deviceManager/sensorTabel/sensor.api.ts
  87. 137 0
      src/views/vent/deviceManager/sensorTabel/sensor.data.ts
  88. 28 0
      src/views/vent/deviceManager/substationTabel/index.vue
  89. 60 0
      src/views/vent/deviceManager/substationTabel/substation.api.ts
  90. 145 0
      src/views/vent/deviceManager/substationTabel/substation.data.ts
  91. 26 0
      src/views/vent/deviceManager/windWindowTabel/index.vue
  92. 60 0
      src/views/vent/deviceManager/windWindowTabel/ventanalyWindow.api.ts
  93. 155 0
      src/views/vent/deviceManager/windWindowTabel/ventanalyWindow.data.ts
  94. 25 0
      src/views/vent/deviceManager/windfindingTabel/index.vue
  95. 60 0
      src/views/vent/deviceManager/windfindingTabel/windfinding.api.ts
  96. 133 0
      src/views/vent/deviceManager/windfindingTabel/windfinding.data.ts
  97. 159 0
      src/views/vent/monitorManager/comment/MonitorTable.vue
  98. 98 0
      src/views/vent/monitorManager/comment/MonitorTable1.vue
  99. 60 0
      src/views/vent/monitorManager/fanLocalMonitor/fanLocal.api.ts
  100. 277 0
      src/views/vent/monitorManager/fanLocalMonitor/fanLocal.data.ts

+ 10 - 4
.env.development

@@ -5,19 +5,25 @@ VITE_USE_MOCK = true
 VITE_PUBLIC_PATH = /
 
 # 跨域代理,您可以配置多个 ,请注意,没有换行符
-VITE_PROXY = [["/jeecgboot","http://localhost:8080/jeecg-boot"],["/upload","http://localhost:3300/upload"]]
+#VITE_PROXY = [["/jeecgboot","http://localhost:8080/jeecg-boot"],["/upload","http://localhost:3300/upload"]]
+VITE_PROXY = [["/jeecgsystem","http://47.94.222.6:9999"],["/upload","http://localhost:3300/upload"]]
+#VITE_PROXY = [["/jeecgsystem","http://192.168.1.88:9999"],["/upload","http://192.168.1.88/upload"]]
 
 # 控制台不输出
 VITE_DROP_CONSOLE = false
 
 #后台接口父地址(必填)
-VITE_GLOB_API_URL=/jeecgboot
+#VITE_GLOB_API_URL=/jeecgboot
+VITE_GLOB_API_URL=/jeecgsystem
 
 #后台接口全路径地址(必填)
-VITE_GLOB_DOMAIN_URL=http://localhost:8080/jeecg-boot
+#VITE_GLOB_DOMAIN_URL=http://localhost:8080/jeecg-boot
+VITE_GLOB_DOMAIN_URL=http://47.94.222.6:9999
 
 # 接口前缀
 VITE_GLOB_API_URL_PREFIX=
 
 #微前端qiankun应用,命名必须以VITE_APP_SUB_开头,jeecg-app-1为子应用的项目名称,也是子应用的路由父路径
-VITE_APP_SUB_jeecg-app-1 = '//localhost:8092'
+VITE_APP_SUB_micro-need-air = '//localhost:8099/'
+
+VITE_3D_MODAL_ARR = ['fm/fm-n-processed.glb']

+ 7 - 2
.env.production

@@ -15,11 +15,14 @@ VITE_BUILD_COMPRESS = 'gzip'
 # 使用压缩时是否删除原始文件,默认为false
 VITE_BUILD_COMPRESS_DELETE_ORIGIN_FILE = false
 
+VITE_PROXY = [["/jeecgsystem","http://47.94.222.6:9999"],["/upload","http://localhost:3300/upload"]]
+
 #后台接口父地址(必填)
-VITE_GLOB_API_URL=/jeecgboot
+VITE_GLOB_API_URL=/jeecgsystem
 
 #后台接口全路径地址(必填)
-VITE_GLOB_DOMAIN_URL=http://jeecg-boot-system:8080/jeecg-boot
+#VITE_GLOB_DOMAIN_URL=http://jeecg-boot-system:8080/jeecg-boot
+VITE_GLOB_DOMAIN_URL=http://47.94.222.6:9999
 
 # 接口父路径前缀
 VITE_GLOB_API_URL_PREFIX=
@@ -32,3 +35,5 @@ VITE_USE_PWA = false
 
 # 是否兼容旧浏览器
 VITE_LEGACY = false
+
+VITE_3D_MODAL_ARR = ['fm/fm-n-processed.glb']

+ 0 - 4
.yarnclean

@@ -11,10 +11,6 @@ website
 images
 assets
 
-# examples
-example
-examples
-
 # code coverage directories
 coverage
 .nyc_output

+ 5 - 0
index.html

@@ -26,6 +26,8 @@
           theme = htmlRoot = null;
         }
       })();
+
+      
     </script>
     <div id="app">
       <style>
@@ -154,6 +156,8 @@
             opacity: 1;
           }
         }
+
+        
       </style>
       <div class="app-loading">
         <div class="app-loading-wrap">
@@ -166,5 +170,6 @@
       </div>
     </div>
     <script type="module" src="/src/main.ts"></script>
+    <!-- <script type="module" src="/src/utils/threejs/main.worker.ts"></script> -->
   </body>
 </html>

+ 127 - 7
package.json

@@ -1,6 +1,6 @@
 {
-  "name": "jeecgboot-vue3",
-  "version": "3.4.0",
+  "name": "mky-vent-base",
+  "version": "2.0.0",
   "author": {
     "name": "jeecg",
     "email": "jeecgos@163.com",
@@ -34,11 +34,11 @@
     "prepare": "husky install"
   },
   "dependencies": {
-    "@jeecg/online": "1.0.1",
     "@iconify/iconify": "^2.0.4",
+    "@jeecg/online": "1.0.1",
     "@vueuse/core": "^6.6.2",
     "@zxcvbn-ts/core": "^1.0.0-beta.0",
-    "ant-design-vue": "2.2.8",
+    "ant-design-vue": "^2.2.8",
     "axios": "^0.23.0",
     "china-area-data": "^5.0.1",
     "clipboard": "^2.0.8",
@@ -47,13 +47,16 @@
     "cropperjs": "^1.5.12",
     "crypto-js": "^4.1.1",
     "dayjs": "^1.10.6",
+    "dexie": "^3.2.2",
     "dom-align": "^1.12.2",
     "echarts": "^5.2.1",
     "enquire.js": "^2.1.6",
+    "gsap": "^3.11.0",
     "intro.js": "^4.2.2",
     "lodash-es": "^4.17.21",
     "md5": "^2.3.0",
     "mockjs": "^1.1.0",
+    "moment": "^2.29.4",
     "nprogress": "^0.2.0",
     "path-to-regexp": "^6.2.0",
     "pinia": "2.0.0-rc.14",
@@ -63,6 +66,7 @@
     "resize-observer-polyfill": "^1.5.1",
     "showdown": "^1.9.1",
     "sortablejs": "^1.14.0",
+    "three": "0.121.1",
     "tinymce": "^5.10.3",
     "vditor": "^3.8.13",
     "vue": "^3.2.20",
@@ -70,6 +74,7 @@
     "vue-cropperjs": "^5.0.0",
     "vue-i18n": "^9.1.9",
     "vue-infinite-scroll": "^2.0.2",
+    "vue-json-pretty": "^2.0.4",
     "vue-print-nb-jeecg": "^1.0.11",
     "vue-router": "^4.0.12",
     "vue-types": "^4.1.1",
@@ -77,7 +82,6 @@
     "vxe-table": "4.1.0",
     "vxe-table-plugin-antd": "^3.0.3",
     "xe-utils": "^3.3.1",
-    "vue-json-pretty": "^2.0.4",
     "xss": "^1.0.13"
   },
   "devDependencies": {
@@ -99,6 +103,7 @@
     "@types/qs": "^6.9.7",
     "@types/showdown": "^1.9.4",
     "@types/sortablejs": "^1.10.7",
+    "@types/three": "^0.143.1",
     "@typescript-eslint/eslint-plugin": "^5.1.0",
     "@typescript-eslint/parser": "^5.1.0",
     "@vitejs/plugin-legacy": "^1.6.2",
@@ -110,6 +115,7 @@
     "commitizen": "^4.2.4",
     "conventional-changelog-cli": "^2.1.1",
     "cross-env": "^7.0.3",
+    "dat.gui": "^0.7.9",
     "dotenv": "^10.0.0",
     "eslint": "^8.0.1",
     "eslint-config-prettier": "^8.3.0",
@@ -139,7 +145,7 @@
     "ts-jest": "^27.0.7",
     "ts-node": "^10.3.0",
     "typescript": "^4.4.4",
-    "vite": "^2.6.10",
+    "vite": "2.6.10",
     "vite-plugin-compression": "^0.3.5",
     "vite-plugin-html": "^2.1.0",
     "vite-plugin-imagemin": "^0.4.6",
@@ -176,7 +182,121 @@
   "vite": {
     "optimizeDeps": {
       "include": [
-
+        "@ant-design/colors",
+        "@ant-design/icons-vue",
+        "@jeecg/online",
+        "@vue/reactivity",
+        "@vueuse/core",
+        "@vueuse/shared",
+        "@zxcvbn-ts/core",
+        "ant-design-vue",
+        "axios",
+        "china-area-data",
+        "clipboard",
+        "codemirror",
+        "codemirror/addon/fold/brace-fold.js",
+        "codemirror/addon/fold/comment-fold.js",
+        "codemirror/addon/fold/foldcode.js",
+        "codemirror/addon/fold/foldgutter.js",
+        "codemirror/addon/fold/indent-fold.js",
+        "codemirror/addon/hint/anyword-hint.js",
+        "codemirror/addon/hint/show-hint.js",
+        "codemirror/addon/selection/active-line.js",
+        "codemirror/mode/clike/clike.js",
+        "codemirror/mode/css/css.js",
+        "codemirror/mode/javascript/javascript.js",
+        "codemirror/mode/markdown/markdown.js",
+        "codemirror/mode/python/python.js",
+        "codemirror/mode/r/r.js",
+        "codemirror/mode/shell/shell.js",
+        "codemirror/mode/sql/sql.js",
+        "codemirror/mode/swift/swift.js",
+        "codemirror/mode/vue/vue.js",
+        "codemirror/mode/xml/xml.js",
+        "cron-parser",
+        "cropperjs",
+        "crypto-js/aes",
+        "crypto-js/enc-base64",
+        "crypto-js/enc-utf8",
+        "crypto-js/md5",
+        "crypto-js/mode-ecb",
+        "crypto-js/pad-pkcs7",
+        "dat.gui",
+        "dexie",
+        "dom-align",
+        "echarts",
+        "echarts/charts",
+        "echarts/components",
+        "echarts/core",
+        "echarts/renderers",
+        "gsap",
+        "intro.js",
+        "lodash-es",
+        "md5",
+        "moment",
+        "nprogress",
+        "path-to-regexp",
+        "pinia",
+        "print-js",
+        "qrcode",
+        "qs",
+        "resize-observer-polyfill",
+        "showdown",
+        "sortablejs",
+        "three",
+        "three/examples/jsm/controls/OrbitControls",
+        "three/examples/jsm/controls/TransformControls",
+        "three/examples/jsm/loaders/DRACOLoader",
+        "three/examples/jsm/loaders/GLTFLoader",
+        "three/examples/jsm/loaders/RGBELoader.js",
+        "three/examples/jsm/renderers/CSS3DRenderer",
+        "tinymce/icons/default/icons",
+        "tinymce/plugins/advlist",
+        "tinymce/plugins/anchor",
+        "tinymce/plugins/autolink",
+        "tinymce/plugins/autosave",
+        "tinymce/plugins/code",
+        "tinymce/plugins/codesample",
+        "tinymce/plugins/contextmenu",
+        "tinymce/plugins/directionality",
+        "tinymce/plugins/fullscreen",
+        "tinymce/plugins/hr",
+        "tinymce/plugins/image",
+        "tinymce/plugins/insertdatetime",
+        "tinymce/plugins/link",
+        "tinymce/plugins/lists",
+        "tinymce/plugins/media",
+        "tinymce/plugins/nonbreaking",
+        "tinymce/plugins/noneditable",
+        "tinymce/plugins/pagebreak",
+        "tinymce/plugins/paste",
+        "tinymce/plugins/preview",
+        "tinymce/plugins/print",
+        "tinymce/plugins/save",
+        "tinymce/plugins/searchreplace",
+        "tinymce/plugins/spellchecker",
+        "tinymce/plugins/tabfocus",
+        "tinymce/plugins/table",
+        "tinymce/plugins/template",
+        "tinymce/plugins/textcolor",
+        "tinymce/plugins/textpattern",
+        "tinymce/plugins/visualblocks",
+        "tinymce/plugins/visualchars",
+        "tinymce/plugins/wordcount",
+        "tinymce/themes/silver",
+        "tinymce/tinymce",
+        "vditor",
+        "vite-plugin-theme/es/client",
+        "vite-plugin-theme/es/colorUtils",
+        "vue",
+        "vue-i18n",
+        "vue-print-nb-jeecg/src/printarea",
+        "vue-router",
+        "vue-types",
+        "vxe-table",
+        "vxe-table-plugin-antd",
+        "xe-utils",
+        "xss"
       ]
     }
   }

+ 9 - 0
src/App.vue

@@ -11,9 +11,18 @@
   import { AppProvider } from '/@/components/Application';
   import { useTitle } from '/@/hooks/web/useTitle';
   import { useLocale } from '/@/locales/useLocale';
+  // import { initModalWorker } from '/@/utils/threejs/main.worker';
 
   // support Multi-language
   const { getAntdLocale } = useLocale();
 
+  // initModalWorker()
+
   useTitle();
 </script>
+<style lang="less" scoped>
+  #app{
+    overflow: hidden;
+  }
+</style>
+

+ 3 - 0
src/api/sys/model/ventModal.ts

@@ -0,0 +1,3 @@
+export interface TableColumnsModal {
+  result: Array<any>;
+}

+ 10 - 0
src/api/sys/vent.ts

@@ -0,0 +1,10 @@
+import { defHttp } from '/@/utils/http/axios';
+enum Api {
+  allTableHeaderColumns = '/ventanaly-device/safety/ventanalyShowColum/show_colum',
+}
+
+export const getAllTableHeaderColumnsApi = () => {
+  return defHttp.get({
+    url: Api.allTableHeaderColumns,
+  });
+};

BIN
src/assets/images/vent/bg.png


BIN
src/assets/images/vent/light.png


BIN
src/assets/images/vent/short-light.png


BIN
src/assets/images/vent/tj.png


+ 53 - 0
src/assets/less/modal.less

@@ -0,0 +1,53 @@
+.bg{
+  width: 100%;
+  height: 100%;
+  background: url('/@/assets/images/vent/bg.png');
+  // z-index: 1;
+  #damper3DCSS{
+    .elementTag{
+      position: relative;
+      left: -18px;
+      top:  -2px;
+      // padding: 5px 20px;
+      // color: #fff;
+      // box-shadow: 0 0 18px #dbecff22;
+      // border: 1px solid #dbecff22;
+      // border-radius: 5px;
+      // background-color: #dbecff77;
+      cursor: pointer;
+      // &::before {
+      //   content: "";
+      //   display: block;
+      //   position: absolute;
+      //   width: 80px;
+      //   height: 1px;
+      //   background: rgb(127 177 255 / 75%);
+      //   bottom: 0;
+      //   right: -80px;
+      //   transform-origin: 0 0;
+      //   transform: rotate(30deg);
+      // }
+      // &::after {
+      //   content: "";
+      //   display: block;
+      //   position: absolute;
+      //   width: 20px;
+      //   height: 20px;
+      //   background: rgb(127 177 255 / 75%);
+      //   bottom: -55px;
+      //   right: -85px;
+      //   border-radius: 50%;
+      // }
+      .elementContent{
+        background-color: rgb(20 143 221 / 68%);
+        box-shadow: 0px 0px 12px rgb(0 128 255 / 75%);
+        border: 1px solid rgb(127 177 255 / 75%);
+        padding: 10px 20px 0px 20px;
+        color: #efefef;
+        p{
+          line-height: 1rem;
+        }
+      }
+    }
+  }
+}

+ 1 - 0
src/components/Form/index.ts

@@ -25,6 +25,7 @@ export { default as JInput } from './src/jeecg/components/JInput.vue';
 export { default as JEllipsis } from './src/jeecg/components/JEllipsis.vue';
 export { default as JDictSelectTag } from './src/jeecg/components/JDictSelectTag.vue';
 export { default as JTreeSelect } from './src/jeecg/components/JTreeSelect.vue';
+export { default as MTreeSelect } from './src/jeecg/components/MTreeSelect.vue';
 export { default as JSearchSelect } from './src/jeecg/components/JSearchSelect.vue';
 export { default as JSelectUserByDept } from './src/jeecg/components/JSelectUserByDept.vue';
 export { default as JEditor } from './src/jeecg/components/JEditor.vue';

+ 1 - 1
src/components/Form/src/BasicForm.vue

@@ -68,7 +68,7 @@
         hideAdvanceBtn: true,
         isLoad: false,
         actionSpan: 6,
-      });
+      });      
 
       const defaultValueRef = ref<Recordable>({});
       const isInitedDefaultRef = ref(false);

+ 2 - 0
src/components/Form/src/componentMap.ts

@@ -36,6 +36,7 @@ import { JEasyCron } from './jeecg/components/JEasyCron';
 import JCheckbox from './jeecg/components/JCheckbox.vue';
 import JInput from './jeecg/components/JInput.vue';
 import JTreeSelect from './jeecg/components/JTreeSelect.vue';
+import MTreeSelect from './jeecg/components/MTreeSelect.vue';
 import JEllipsis from './jeecg/components/JEllipsis.vue';
 import JSelectUserByDept from './jeecg/components/JSelectUserByDept.vue';
 import JUpload from './jeecg/components/JUpload/JUpload.vue';
@@ -104,6 +105,7 @@ componentMap.set('JEasyCron', JEasyCron);
 componentMap.set('JCheckbox', JCheckbox);
 componentMap.set('JInput', JInput);
 componentMap.set('JTreeSelect', JTreeSelect);
+componentMap.set('MTreeSelect', MTreeSelect);
 componentMap.set('JEllipsis', JEllipsis);
 componentMap.set('JSelectUserByDept', JSelectUserByDept);
 componentMap.set('JUpload', JUpload);

+ 268 - 0
src/components/Form/src/jeecg/components/MTreeSelect.vue

@@ -0,0 +1,268 @@
+<template>
+  <a-tree-select
+    allowClear
+    style="width: 100%"
+    :getPopupContainer="(node) => node.parentNode"
+    :dropdownStyle="{ maxHeight: '400px', overflow: 'auto' }"
+    :placeholder="placeholder"
+    :value="treeValue"
+    :treeData="treeData"
+    :field-names="fieldNames"
+    v-bind="attrs"
+    @change="onChange"
+    @search="onSearch"
+  >
+  </a-tree-select>
+</template>
+<script lang="ts" setup>
+  /*
+   * 异步树加载组件 通过传入表名 显示字段 存储字段 加载一个树控件
+   * <j-tree-select dict="aa_tree_test,aad,id" pid-field="pid" ></j-tree-select>
+   * */
+  import { ref, watch, unref } from 'vue';
+  import { defHttp } from '/@/utils/http/axios';
+  import { propTypes } from '/@/utils/propTypes';
+  import { useAttrs } from '/@/hooks/core/useAttrs';
+  import { useMessage } from '/@/hooks/web/useMessage';
+
+  enum Api {
+    url = '/sys/dict/DeviceKind/query',
+    // view = '/sys/dict/loadDictItem/',
+  }
+
+  const props = defineProps({
+    value: propTypes.string.def(''),
+    placeholder: propTypes.string.def('请选择'),
+    fieldNames: propTypes.object.def({}),
+    // dict: propTypes.string.def('id'),
+    // parentCode: propTypes.string.def(''),
+    // pidField: propTypes.string.def('pid'),
+    // //update-begin---author:wangshuai ---date:20220620  for:JTreeSelect组件pidValue还原成空,否则会影响自定义组件树示例------------
+    // pidValue: propTypes.string.def(''),
+    // //update-end---author:wangshuai ---date:20220620  for:JTreeSelect组件pidValue还原成空,否则会影响自定义组件树示例--------------
+    // hasChildField: propTypes.string.def(''),
+    // condition: propTypes.string.def(''),
+    multiple: propTypes.bool.def(false),
+    loadTriggleChange: propTypes.bool.def(false),
+    reload: propTypes.number.def(1),
+  });
+  const attrs = useAttrs();
+  const emit = defineEmits(['change', 'update:value']);
+  const { createMessage } = useMessage();
+  //树形下拉数据
+  const treeData = ref<any[]>([]);
+  //选择数据
+  const treeValue = ref<any>(null);
+  const tableName = ref<any>('');
+  const text = ref<any>('');
+  const code = ref<any>('');
+  // /**
+  //  * 监听value数据并初始化
+  //  */
+  // watch(
+  //   () => props.value,
+  //   () => loadItemByCode(),
+  //   { deep: true, immediate: true }
+  // );
+  // /**
+  //  * 监听dict变化
+  //  */
+  // watch(
+  //   () => props.dict,
+  //   () => {
+  //     initDictInfo();
+  //     loadRoot();
+  //   },
+  //   { deep: true, immediate: true }
+  // );
+
+  // //update-begin-author:taoyan date:2022-5-25 for: VUEN-1056 15、严重——online树表单,添加的时候,父亲节点是空的
+  // watch(
+  //   () => props.reload,
+  //   async () => {
+  //     treeData.value = [];
+  //     await loadRoot();
+  //   },
+  //   {
+  //     immediate: false,
+  //   }
+  // );
+  //update-end-author:taoyan date:2022-5-25 for: VUEN-1056 15、严重——online树表单,添加的时候,父亲节点是空的
+
+  /**
+   * 根据code获取下拉数据并回显
+   */
+  async function loadItemByCode() {
+    if (!props.value || props.value == '0') {
+      treeValue.value = null;
+    } else {
+      let params = { key: props.value };
+      let result = await defHttp.get({ url: `${Api.view}${props.dict}`, params }, { isTransformResponse: false });
+      if (result.success) {
+        let values = props.value.split(',');
+        treeValue.value = result.result.map((item, index) => ({
+          key: values[index],
+          value: values[index],
+          label: item,
+        }));
+        onLoadTriggleChange(result.result[0]);
+      }
+    }
+  }
+
+  function onLoadTriggleChange(text) {
+    //只有单选才会触发
+    if (!props.multiple && props.loadTriggleChange) {
+      emit('change', props.value, text);
+    }
+  }
+
+  /**
+   * 初始化数据
+   */
+  function initDictInfo() {
+    let arr = props.dict?.split(',');
+    tableName.value = arr[0];
+    text.value = arr[1];
+    code.value = arr[2];
+  }
+
+  /**
+   * 加载下拉树形数据
+   */
+  async function loadRoot() {
+    let params = {
+      // pid: props.pidValue,
+      // pidField: props.pidField,
+      // hasChildField: props.hasChildField,
+      // condition: props.condition,
+      // tableName: unref(tableName),
+      // text: unref(text),
+      // code: unref(code),
+    };
+    let res = await defHttp.get({ url: Api.url, params }, { isTransformResponse: false });
+    // debugger
+    if (res.success && res.result) {
+      for (let i of res.result) {
+        i.value = i.key;
+        i.isLeaf = !!i.leaf;
+      }
+      treeData.value = [...res.result];
+    } else {
+      console.log('数根节点查询结果异常', res);
+    }
+  }
+
+  /**
+   * 异步加载数据
+   */
+  async function asyncLoadTreeData(treeNode) {
+    if (treeNode.dataRef.children) {
+      return Promise.resolve();
+    }
+    let pid = treeNode.dataRef.key;
+    let params = {
+      pid: pid,
+      pidField: props.pidField,
+      hasChildField: props.hasChildField,
+      condition: props.condition,
+      tableName: unref(tableName),
+      text: unref(text),
+      code: unref(code),
+    };
+    let res = await defHttp.get({ url: Api.url, params }, { isTransformResponse: false });
+    if (res.success) {
+      for (let i of res.result) {
+        i.value = i.key;
+        i.isLeaf = !!i.leaf;
+      }
+      //添加子节点
+      addChildren(pid, res.result, treeData.value);
+      treeData.value = [...treeData.value];
+    }
+    return Promise.resolve();
+  }
+
+  /**
+   * 加载子节点
+   */
+  function addChildren(pid, children, treeArray) {
+    if (treeArray && treeArray.length > 0) {
+      for (let item of treeArray) {
+        if (item.key == pid) {
+          if (!children || children.length == 0) {
+            item.isLeaf = true;
+          } else {
+            item.children = children;
+          }
+          break;
+        } else {
+          addChildren(pid, children, item.children);
+        }
+      }
+    }
+  }
+
+  /**
+   * 选中树节点事件
+   */
+  function onChange(value) {
+    if (!value) {
+      emitValue('');
+    } else if (value instanceof Array) {
+      emitValue(value.map((item) => item.value).join(','));
+    } else {
+      emitValue(value.value);
+    }
+    treeValue.value = value;
+    console.log(treeValue.value);
+    
+  }
+
+  function emitValue(value) {
+    emit('change', value);
+    emit('update:value', value);
+  }
+
+  /**
+   * 文本框值变化
+   */
+  function onSearch(value) {
+    console.log(value);
+  }
+
+  /**
+   * 校验条件配置是否有误
+   */
+  function validateProp() {
+    let mycondition = props.condition;
+    return new Promise((resolve, reject) => {
+      if (!mycondition) {
+        resolve();
+      } else {
+        try {
+          let test = JSON.parse(mycondition);
+          if (typeof test == 'object' && test) {
+            resolve();
+          } else {
+            createMessage.error('组件JTreeSelect-condition传值有误,需要一个json字符串!');
+            reject();
+          }
+        } catch (e) {
+          createMessage.error('组件JTreeSelect-condition传值有误,需要一个json字符串!');
+          reject();
+        }
+      }
+    });
+  }
+
+  loadRoot();
+  // // onCreated
+  // validateProp().then(() => {
+  //   initDictInfo();
+  //   loadRoot();
+  //   loadItemByCode();
+  // });
+</script>
+
+<style lang="less"></style>

+ 1 - 0
src/components/Form/src/types/index.ts

@@ -135,6 +135,7 @@ export type ComponentType =
   | 'JCheckbox'
   | 'JInput'
   | 'JTreeSelect'
+  | 'MTreeSelect'
   | 'JEllipsis'
   | 'JSelectUserByDept'
   | 'JUpload'

+ 8 - 1
src/components/Table/src/BasicTable.vue

@@ -12,6 +12,8 @@
       <template #[replaceFormSlotKey(item)]="data" v-for="item in getFormSlotKeys">
         <slot :name="item" v-bind="data || {}"></slot>
       </template>
+      
+
     </BasicForm>
 
     <Table ref="tableElRef" v-bind="getBindValues" :rowClassName="getRowClassName" v-show="getEmptyDataIsShowTable" @change="handleTableChange">
@@ -20,6 +22,7 @@
       </template>
 
       <template #[`header-${column.dataIndex}`] v-for="column in columns" :key="column.dataIndex">
+
         <HeaderCell :column="column" />
       </template>
     </Table>
@@ -95,6 +98,7 @@
         return { ...props, ...unref(innerPropsRef) } as BasicTableProps;
       });
 
+      
       const isFixedHeightPage = inject(PageWrapperFixedHeightKey, false);
       watchEffect(() => {
         unref(isFixedHeightPage) && props.canResize && warn("'canResize' of BasicTable may not work in PageWrapper with 'fixedHeight' (especially in hot updates)");
@@ -170,7 +174,7 @@
       const { getFooterProps } = useTableFooter(getProps, getScrollRef, tableElRef, getDataSourceRef);
 
       const { getFormProps, replaceFormSlotKey, getFormSlotKeys, handleSearchInfoChange } = useTableForm(getProps, slots, fetch, getLoading);
-
+      
       const getBindValues = computed(() => {
         const dataSource = unref(getDataSourceRef);
         let propsData: Recordable = {
@@ -200,6 +204,9 @@
         return propsData;
       });
 
+      console.log(777,getBindValues);
+      
+
       const getWrapperClass = computed(() => {
         const values = unref(getBindValues);
         return [

+ 5 - 1
src/components/Table/src/hooks/useTableForm.ts

@@ -8,14 +8,18 @@ export function useTableForm(propsRef: ComputedRef<BasicTableProps>, slots: Slot
   const getFormProps = computed((): Partial<FormProps> => {
     const { formConfig } = unref(propsRef);
     const { submitButtonOptions } = formConfig || {};
+
     return {
       showAdvancedButton: true,
       ...formConfig,
       submitButtonOptions: { loading: unref(getLoading), ...submitButtonOptions },
       compact: true,
     };
+    
+    
   });
-
+  
+  
   const getFormSlotKeys: ComputedRef<string[]> = computed(() => {
     const keys = Object.keys(slots);
     return keys.map((item) => (item.startsWith('form-') ? item : null)).filter((item) => !!item) as string[];

+ 1 - 1
src/components/Table/src/types/table.ts

@@ -1,7 +1,7 @@
 import type { VNodeChild } from 'vue';
 import type { PaginationProps } from './pagination';
 import type { FormProps } from '/@/components/Form';
-import type { ColumnProps, TableRowSelection as ITableRowSelection } from 'ant-design-vue/lib/table/interface';
+import type {  ColumnType as ColumnProps, TableRowSelection as ITableRowSelection } from 'ant-design-vue/lib/table/interface';
 
 import { ComponentType } from './componentType';
 import { VueNode } from '/@/utils/propTypes';

+ 27 - 5
src/components/chart/Bar.vue

@@ -16,6 +16,14 @@
         type: Object,
         default: () => ({}),
       },
+	  xAxisPropType: {
+        type: String,
+        required: true,
+      },
+      seriesPropType: {
+        type: String,
+        required: true,
+      },
       width: {
         type: String as PropType<string>,
         default: '100%',
@@ -39,17 +47,29 @@
             },
           },
         },
+		    grid: {
+          left: 60,
+          right: 50,
+          bottom: 50,
+        },
         xAxis: {
           type: 'category',
           data: [],
         },
         yAxis: {
           type: 'value',
+		      nameTextStyle: {
+            fontSize: 14,
+          },
         },
         series: [
           {
             name: 'bar',
             type: 'bar',
+			      showBackground: true,
+            backgroundStyle: {
+              color: 'rgba(220, 220, 220, 0.8)',
+            },
             data: [],
           },
         ],
@@ -63,15 +83,17 @@
         if (props.option) {
           Object.assign(option, props.option);
         }
-        let seriesData = props.chartData.map((item) => {
-          return item.value;
+        let seriesData = props.chartData.map((item: any) => {
+          // return item.value;
+          return item[props.seriesPropType];
         });
-        let xAxisData = props.chartData.map((item) => {
-          return item.name;
+        let xAxisData = props.chartData.map((item: any) => {
+          // return item.name;
+          return item[props.xAxisPropType];
         });
         option.series[0].data = seriesData;
         option.xAxis.data = xAxisData;
-        setOptions(option);
+        setOptions(option, false);
       }
       return { chartRef };
     },

+ 22 - 9
src/components/chart/BarMulti.vue

@@ -21,6 +21,15 @@
         type: String as PropType<string>,
         default: 'bar',
       },
+	  xAxisPropType: {
+        type: String,
+        required: true,
+      },
+      propTypeArr: {
+        type: Map,
+        default: () => new Map(),
+        required: true,
+      },
       width: {
         type: String as PropType<string>,
         default: '100%',
@@ -46,10 +55,12 @@
           },
         },
         legend: {
-          top: 30,
+          top: 10,
         },
         grid: {
-          top: 60,
+          left: 60,
+          right: 50,
+          bottom: 50,
         },
         xAxis: {
           type: 'category',
@@ -57,6 +68,9 @@
         },
         yAxis: {
           type: 'value',
+		  nameTextStyle: {
+            fontSize: 14,
+          },
         },
         series: [],
       });
@@ -70,20 +84,19 @@
           Object.assign(option, props.option);
         }
         //图例类型
-        let typeArr = Array.from(new Set(props.chartData.map((item) => item.type)));
+        // let typeArr = Array.from(new Set(props.chartData.map((item) => item.type)));
         //轴数据
-        let xAxisData = Array.from(new Set(props.chartData.map((item) => item.name)));
+        let xAxisData = Array.from(new Set(props.chartData.map((item) => item[props.xAxisPropType])));
         let seriesData = [];
-        typeArr.forEach((type) => {
-          let obj = { name: type, type: props.type };
-          let chartArr = props.chartData.filter((item) => type === item.type);
+        [...props.propTypeArr.keys()].forEach((type) => {
+          let obj = { name: props.propTypeArr.get(type), type: props.type };
           //data数据
-          obj['data'] = chartArr.map((item) => item.value);
+          obj['data'] = props.chartData.map((item) => item[type]);
           seriesData.push(obj);
         });
         option.series = seriesData;
         option.xAxis.data = xAxisData;
-        setOptions(option);
+        setOptions(option, false);
         getInstance()?.off('click', onClick);
         getInstance()?.on('click', onClick);
       }

+ 17 - 8
src/components/chart/LineMulti.vue

@@ -17,9 +17,18 @@
         type: Object,
         default: () => ({}),
       },
+	  xAxisPropType: {
+        type: String,
+        required: true,
+      },
+      propTypeArr: {
+        type: Map,
+        default: () => new Map(),
+        required: true,
+      },
       type: {
         type: String as PropType<string>,
-        default: 'bar',
+        default: 'line',
       },
       width: {
         type: String as PropType<string>,
@@ -46,7 +55,7 @@
           },
         },
         legend: {
-          top: 30,
+          top: 10,
         },
         grid: {
           top: 60,
@@ -70,20 +79,20 @@
           Object.assign(option, props.option);
         }
         //图例类型
-        let typeArr = Array.from(new Set(props.chartData.map((item) => item.type)));
+        // let typeArr = Array.from(new Set(props.chartData.map((item) => item.type)));
         //轴数据
-        let xAxisData = Array.from(new Set(props.chartData.map((item) => item.name)));
+        let xAxisData = Array.from(new Set(props.chartData.map((item) => item[props.xAxisPropType])));
         let seriesData = [];
-        typeArr.forEach((type) => {
-          let obj = { name: type, type: props.type };
+        [...props.propTypeArr.keys()].forEach((type) => {
+          let obj = { name: props.propTypeArr.get(type), type: props.type };
           let chartArr = props.chartData.filter((item) => type === item.type);
           //data数据
-          obj['data'] = chartArr.map((item) => item.value);
+          obj['data'] = props.chartData.map((item) => item[type]);
           seriesData.push(obj);
         });
         option.series = seriesData;
         option.xAxis.data = xAxisData;
-        setOptions(option);
+        setOptions(option, false);
         getInstance()?.off('click', onClick);
         getInstance()?.on('click', onClick);
       }

+ 8 - 1
src/components/jeecg/OnLine/SearchFormItem.vue

@@ -79,6 +79,12 @@
       load-triggle-change
     >
     </JTreeSelect>
+    <MTreeSelect
+      :placeholder="'请选择' + item.label"
+      v-model:value="queryParam[item.field]"
+      load-triggle-change
+    >
+    </MTreeSelect>
   </a-form-item>
 
   <a-form-item v-else-if="item.view === CompTypeEnum.CatTree" :labelCol="labelCol" :class="'jeecg-online-search'">
@@ -166,13 +172,14 @@
   import { defineComponent, ref } from 'vue';
   import { DateTypeEnum } from '/@/enums/DateTypeEnum.ts';
   import { CompTypeEnum } from '/@/enums/CompTypeEnum.ts';
-  import { JDictSelectTag, JTreeSelect, JCategorySelect, JSelectUserByDept, JSelectDept, JPopup, JAreaLinkage } from '/@/components/Form';
+  import { JDictSelectTag, JTreeSelect, MTreeSelect, JCategorySelect, JSelectUserByDept, JSelectDept, JPopup, JAreaLinkage } from '/@/components/Form';
   export default defineComponent({
     name: 'JPopupOnlReport',
     components: {
       //JOnlineSearchSelect
       JDictSelectTag,
       JTreeSelect,
+      MTreeSelect,
       JCategorySelect,
       JSelectUserByDept,
       JSelectDept,

+ 5 - 0
src/design/ant/table.less

@@ -10,10 +10,13 @@
             .ant-table-scroll {
               .ant-table-hide-scrollbar {
                 overflow-y: auto !important;
+                // overflow-x: hidden !important;
+                // width: 0px !important;
               }
 
               .ant-table-body {
                 overflow-y: auto !important;
+                // overflow-x: hidden !important;
               }
             }
 
@@ -21,6 +24,7 @@
               .ant-table-body-outer {
                 .ant-table-body-inner {
                   overflow-y: auto !important;
+                  // overflow-x: hidden !important;
                 }
               }
             }
@@ -29,6 +33,7 @@
               .ant-table-body-outer {
                 .ant-table-body-inner {
                   overflow-y: auto !important;
+                  // overflow-x: hidden !important;
                 }
               }
             }

+ 418 - 0
src/hooks/core/useThree copy.ts

@@ -0,0 +1,418 @@
+import * as THREE from 'three';
+import { RGBELoader } from 'three/examples/jsm/loaders/RGBELoader.js';
+// 导入轨道控制器
+import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls';
+
+import gsap from 'gsap';
+
+import ResourceTracker from '/@/utils/threejs/ResourceTracker';
+const resourceTracker = new ResourceTracker()
+const track = resourceTracker.track.bind(resourceTracker)
+
+
+class UseThree {
+
+  container: HTMLCanvasElement;
+  camera: THREE.PerspectiveCamera | null = null;
+  scene: THREE.Scene | null = null;
+  renderer: THREE.WebGLRenderer | null = null;
+  orbitControls: OrbitControls | null = null;
+  giftLoader: THREE.Object3D | null = null;
+  animationMixer: THREE.AnimationMixer | null = null;
+  animationAction: THREE.AnimationAction | null = null;
+  clock: THREE.Clock = new THREE.Clock(); // 计时器
+  timeoutId: NodeJS.Timeout | null = null;
+  animationId: number = 0
+
+  constructor(selector) {
+    this.animationId = 0
+    this.container = document.querySelector(selector);
+    //初始化
+    this.init();
+    this.animate();
+    window.addEventListener('resize', this.resizeRenderer.bind(this));
+    // 添加滚动事件,鼠标滚动模型执行动画
+    // window.addEventListener('wheel', this.wheelRenderer.bind(this));
+  }
+  init() {
+
+    // 初始化场景
+    this.initScene();
+    // 初始化环境光
+    this.initLight();
+    // 初始化相机
+    this.initCamera();
+    //初始化渲染器
+    this.initRenderer();
+    // 初始化控制器
+    this.initControles();
+  }
+
+  initScene() {
+    this.scene = new THREE.Scene();
+    const axesHelper = new THREE.AxesHelper(5);
+    // this.scene.add(axesHelper);
+  }
+  initLight() {
+    const light = new THREE.AmbientLight(0xfafafa, 1);
+    light.position.set(0, 200, 200);
+    (this.scene as THREE.Scene).add(light);
+  }
+  initCamera() {
+    this.camera = new THREE.PerspectiveCamera(45, window.innerWidth / window.innerHeight, 0.1, 100);
+    this.camera.position.set(0, 0.2, 0.3);
+  }
+  initRenderer() {
+
+    this.renderer = new THREE.WebGLRenderer({ antialias: true });
+    // 设置屏幕像素比
+    this.renderer.setPixelRatio(window.devicePixelRatio);
+    // 设置渲染的尺寸
+    this.renderer.setSize(window.innerWidth, window.innerHeight);
+    // 色调映射
+    this.renderer.toneMapping = THREE.ACESFilmicToneMapping;
+    // 曝光程度
+    this.renderer.toneMappingExposure = 3;
+    this.container.appendChild(this.renderer.domElement);
+  }
+
+  initControles() {
+    this.orbitControls = new OrbitControls(this.camera as THREE.Camera, this.renderer?.domElement);
+  }
+
+  test() {
+    //
+    const planeGeometry = new THREE.PlaneGeometry(1, 1);
+    const meshStandardMaterial = new THREE.MeshStandardMaterial();
+    const mesh = new THREE.Mesh(planeGeometry, meshStandardMaterial);
+    mesh.position.set(0, -0.2, 0);
+    mesh.rotation.z = Math.PI / 4;
+    this.scene?.add(mesh);
+  }
+
+  setModel(modalName) {
+    const a = new Date().getTime() / 1000
+    return new Promise(async (resolve, reject) => {
+      try {
+        const db =  window['CustomDB']
+        const modalArr =  await db.modal.where('modalName').equals(modalName).toArray()
+        if(modalArr.length > 0) {
+          const modalValue = modalArr[0].modalVal
+          new THREE.ObjectLoader().parse(modalValue, (e) => {
+            const group = e.children[0]
+
+            const cityMaterial = new THREE.MeshBasicMaterial({
+              color: new THREE.Color(0x0c016f),
+            });
+            group?.traverse((item: THREE.Mesh) => {
+              if (item.type === 'Mesh') {
+                item.material = cityMaterial;
+                this.setMaterial(item);
+              }
+            });
+            this.scene?.add(e.children[0]);
+            console.log((new Date().getTime() / 1000) - a + " s")
+            resolve(this.scene);
+          })
+        } 
+      } catch (error) {
+        reject('加载模型出错')
+      }
+      
+    })
+  }
+
+  setMaterial(obj) {
+
+    obj.geometry.computeBoundingBox();
+    const { max, min } = obj.geometry.boundingBox;
+    const distance = max.y - min.y + 2;
+
+    obj.material.onBeforeCompile = (shader) => {
+      shader.uniforms.uDistance = {
+        value: distance,
+      };
+      shader.uniforms.uTopColor = {
+        value: new THREE.Color(0xaaaeff),
+      };
+      this.addGrad(shader);
+      this.addRadiusSpread(shader);
+      this.addLineSpread(shader);
+      this.addToSpread(shader);
+    };
+  }
+  addGrad(shader) {
+    shader.vertexShader = shader.vertexShader.replace(
+      '#include <common>',
+      `
+
+      #include <common>
+      varying vec3 vPosition;
+    `
+    );
+    shader.vertexShader = shader.vertexShader.replace(
+      '#include <fog_vertex>',
+      `
+
+      #include <fog_vertex>
+      vPosition = position;
+     `
+    );
+    shader.fragmentShader = shader.fragmentShader.replace(
+      '#include <common>',
+      `
+      #include <common>
+      uniform vec3 uTopColor;
+      uniform float uDistance;
+      varying vec3 vPosition;
+  
+      `
+    );
+
+    shader.fragmentShader = shader.fragmentShader.replace(
+      '#include <dithering_fragment>',
+      `
+      #include <dithering_fragment>
+
+      vec4 distGradColor = gl_FragColor;
+      // 设置混合的百分比
+      float gradMix = (vPosition.y + (uDistance / 2.0)) / uDistance / 5.0;
+      // 设置混合颜色
+      vec3 colorMix = mix(distGradColor.xyz, uTopColor, gradMix);
+      gl_FragColor = vec4(colorMix, 1.0);
+      //#end#
+      `
+    );
+  }
+
+  addRadiusSpread(shader) {
+    // 设置扩散中心店
+    shader.uniforms.uRadiusSpreadCenter = {
+      value: new THREE.Vector2(0, 0),
+    };
+    // 扩散时间
+    shader.uniforms.uRadiusSpreadTime = {
+      value: 0,
+    };
+
+    shader.uniforms.uRadiusSpreadWidth = {
+      value: 20,
+    };
+    shader.fragmentShader = shader.fragmentShader.replace(
+      '#include <common>',
+      `
+      #include <common>
+      uniform vec2 uRadiusSpreadCenter;
+      uniform float uRadiusSpreadTime;
+      uniform float uRadiusSpreadWidth;
+
+      `
+    );
+
+    shader.fragmentShader = shader.fragmentShader.replace(
+      '//#end#',
+      `
+      float spreadRadius = distance(vPosition.xz + vec2(-720, -550), uRadiusSpreadCenter);
+      // 扩散范围
+      float spreadIndex = -(spreadRadius - uRadiusSpreadTime) * (spreadRadius - uRadiusSpreadTime ) + uRadiusSpreadWidth;
+      if(spreadIndex > 0.0){
+        gl_FragColor = mix(gl_FragColor, vec4(1.0, 1.0, 1.0, 1.0), spreadIndex / uRadiusSpreadWidth );
+      }
+      //#end#
+      `
+    );
+    gsap.to(shader.uniforms.uRadiusSpreadTime, {
+      value: 250,
+      duration: 1,
+      ease: 'none',
+      repeat: -1,
+    });
+  }
+
+  addLineSpread(shader) {
+    // 设置扩散中心店
+    shader.uniforms.uLineSpreadCenter = {
+      value: new THREE.Vector2(0, 0),
+    };
+    // 扩散时间
+    shader.uniforms.uLineSpreadTime = {
+      value: 0,
+    };
+
+    shader.uniforms.uLineSpreadWidth = {
+      value: 20,
+    };
+    shader.fragmentShader = shader.fragmentShader.replace(
+      '#include <common>',
+      `
+      #include <common>
+      uniform vec2 uLineSpreadCenter;
+      uniform float uLineSpreadTime;
+      uniform float uLineSpreadWidth;
+
+      `
+    );
+
+    shader.fragmentShader = shader.fragmentShader.replace(
+      '//#end#',
+      `
+      float spreadLine = vPosition.x - 400.0;
+      // 扩散范围
+      float spreadLineIndex = -(spreadLine - uLineSpreadTime * 5.0) * (spreadLine - uLineSpreadTime * 5.0) + uLineSpreadWidth;
+      if(spreadLineIndex > 0.0){
+        gl_FragColor = mix(gl_FragColor, vec4(1.0, 1.0, 1.0, 1.0), spreadLineIndex / uLineSpreadWidth );
+      }
+      //#end#
+      `
+    );
+    gsap.to(shader.uniforms.uLineSpreadTime, {
+      value: 150,
+      duration: 2,
+      ease: 'none',
+      repeat: -1,
+    });
+  }
+
+  addToSpread(shader) {
+    // 设置扩散中心店
+    shader.uniforms.uTopSpreadCenter = {
+      value: new THREE.Vector2(0, 0),
+    };
+    // 扩散时间
+    shader.uniforms.uTopSpreadTime = {
+      value: 0,
+    };
+
+    shader.uniforms.uTopSpreadWidth = {
+      value: 20,
+    };
+    shader.fragmentShader = shader.fragmentShader.replace(
+      '#include <common>',
+      `
+      #include <common>
+      uniform vec2 uTopSpreadCenter;
+      uniform float uTopSpreadTime;
+      uniform float uTopSpreadWidth;
+
+      `
+    );
+
+    shader.fragmentShader = shader.fragmentShader.replace(
+      '//#end#',
+      `
+      float spreadTopLine = vPosition.y * 2.0;
+      // 扩散范围uTopSpreadWidth
+      float spreadTopIndex = -(spreadTopLine - uTopSpreadTime) * (spreadTopLine - uTopSpreadTime) + uTopSpreadWidth;
+      if(spreadTopIndex > 0.0){
+        gl_FragColor = mix(gl_FragColor, vec4(0.0, 1.0, 1.0, 1.0), spreadTopIndex / uTopSpreadWidth / uTopSpreadTime * 2.0);
+      }
+      //#end#
+      `
+    );
+    gsap.to(shader.uniforms.uTopSpreadTime, {
+      value: 200,
+      duration: 5,
+      ease: 'none',
+      repeat: -1,
+    });
+  }
+
+  addLine(geometry) {
+    const edges = new THREE.EdgesGeometry(geometry);
+    const line = new THREE.LineSegments(edges, new THREE.LineBasicMaterial({ color: 0xff0000 }));
+    (this.scene as THREE.Scene).add(line);
+    // console.log(line);
+  }
+
+  /* 场景环境背景 */
+  setEnvMap(hdr) {
+    new RGBELoader().setPath('/public/3D/hdr/').load(hdr + '.hdr', (texture) => {
+      texture.mapping = THREE.EquirectangularReflectionMapping;
+      (this.scene as THREE.Scene).background = texture;
+      (this.scene as THREE.Scene).environment = texture;
+    });
+  }
+
+  render() {
+    const delta = this.clock.getDelta();
+    this.renderer?.render(this.scene as THREE.Object3D, this.camera as THREE.Camera);
+    this.animationMixer?.update(delta);
+  }
+
+  animate() {
+    // this.renderer?.setAnimationLoop(this.render.bind(this));
+    if(this.animationId != -1) {
+      this.render()
+      this.animationId = requestAnimationFrame(this.animate.bind(this))
+    }
+  }
+  resizeRenderer() {
+    // 更新相机比例
+    (this.camera as THREE.PerspectiveCamera).aspect = window.innerWidth / window.innerHeight;
+    // 刷新相机矩阵
+    this.camera?.updateProjectionMatrix();
+    // 设置场景尺寸
+    this.renderer?.setSize(window.innerWidth, window.innerHeight);
+  }
+  wheelRenderer(e: WheelEvent) {
+    const timeScale = e.deltaY > 0 ? 1 : -1;
+    this.animationAction?.setEffectiveTimeScale(timeScale);
+    (this.animationAction as THREE.AnimationAction).paused = false;
+    this.animationAction?.play();
+    if (this.timeoutId) {
+      clearTimeout(this.timeoutId);
+    }
+    this.timeoutId = setTimeout(() => {
+      this.animationAction?.halt(0.5);
+    }, 500);
+  }
+  deleteModal() {
+    this.scene?.children.forEach((obj:any) => {
+      if(obj.type === 'Group'){
+        obj.traverse(function(item:any) {
+          if (item.type === 'Mesh') {
+            item.geometry.dispose();
+            item.material.dispose();
+            !!item.clear&&item.clear();
+          }
+        })
+      }else if (obj.material) {
+        obj.material.dispose();
+      }else if (obj.geometry) {
+        obj.geometry.dispose();
+      }
+      this.scene?.remove(obj)
+      !!obj.clear??obj.clear()
+      obj = null;
+    })
+    let gl = this.renderer?.domElement.getContext("webgl");
+    gl && gl?.getExtension("WEBGL_lose_context")?.loseContext();
+
+    this.renderer?.forceContextLoss();
+    this.renderer?.dispose();
+    this.camera = null;
+    this.orbitControls = null;
+    this.renderer.domElement = null;
+    this.renderer = null;
+
+    // css3dRender.domElement = null;
+    // css3dRender = null;
+    // model3D.innerHTML = '';
+    // css3D.innerHTML = '';
+    // model3D = null
+    // css3D = null
+
+    !!this.scene?.clear??this.scene?.clear();
+    cancelAnimationFrame(this.animationId);
+    this.animationId = -1
+    // this.stats = null
+    // scene = null
+
+    THREE.Cache.clear();  
+    console.log('3D环境已清理干净');
+  }
+
+
+}
+
+export default UseThree;

+ 441 - 0
src/hooks/core/useThree.ts

@@ -0,0 +1,441 @@
+import * as THREE from 'three';
+import { RGBELoader } from 'three/examples/jsm/loaders/RGBELoader.js';
+// 导入轨道控制器
+import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls';
+import { CSS3DRenderer, CSS3DObject } from 'three/examples/jsm/renderers/CSS3DRenderer'
+// import gsap from 'gsap';
+import ResourceTracker from '/@/utils/threejs/ResourceTracker';
+import { GLTFLoader } from 'three/examples/jsm/loaders/GLTFLoader'
+import { DRACOLoader } from 'three/examples/jsm/loaders/DRACOLoader'
+
+
+
+class UseThree {
+  canvasContainer: HTMLCanvasElement;
+  CSSCanvasContainer: HTMLCanvasElement | null = null;
+  camera: THREE.PerspectiveCamera | null = null;
+  scene: THREE.Scene | null = null;
+  renderer: THREE.WebGLRenderer | null = null;
+  css3dRender: CSS3DRenderer | null = null;
+  orbitControls: OrbitControls | null = null;
+  giftLoader: THREE.Object3D | null = null;
+  animationMixer: THREE.AnimationMixer | null = null;
+  animationAction: THREE.AnimationAction | null = null;
+  clock: THREE.Clock = new THREE.Clock(); // 计时器
+  timeoutId: NodeJS.Timeout | null = null;
+  animationId: number = 0
+  resourceTracker:ResourceTracker | null = null
+  track: any = null
+  spriteText: THREE.Sprite | null = null
+
+  constructor(canvasSelector, cssCanvas?) {
+ 
+
+    this.resourceTracker = new ResourceTracker()
+    this.track = this.resourceTracker.track.bind(this.resourceTracker)
+    this.animationId = 0
+    this.canvasContainer = document.querySelector(canvasSelector);
+
+    //初始化
+    this.init(cssCanvas);
+    this.animate();
+    window.addEventListener('resize', this.resizeRenderer.bind(this));
+    // 添加滚动事件,鼠标滚动模型执行动画
+    // window.addEventListener('wheel', this.wheelRenderer.bind(this));
+  }
+  init(cssCanvas?) {
+    // 初始化场景
+    this.initScene();
+    // 初始化环境光
+    this.initLight();
+    // 初始化相机
+    this.initCamera();
+    //初始化渲染器
+    this.initRenderer();
+
+    // 初始化控制器
+    this.initControles();
+
+    if(cssCanvas){
+      this.initCSSRenderer(cssCanvas)
+      // this.addTextSprite()
+    }
+    // this.setTestPlane()
+  }
+
+  initScene() {
+    this.scene = this.track(new THREE.Scene());
+    // const axesHelper = new THREE.AxesHelper(100);
+    // this.scene?.add(axesHelper);
+
+    // const size = 1000;
+    // const divisions = 10;
+    // const gridHelper = new THREE.GridHelper( size, divisions );
+    // this.scene?.add( gridHelper )
+  }
+
+  initLight() {
+    const light = this.track(new THREE.AmbientLight(0xffffff, 1));
+    light.position.set(0, 1000, 1000);
+    (this.scene as THREE.Scene).add(light);
+  }
+
+  initCamera() {
+    this.camera = this.track(new THREE.PerspectiveCamera(50, window.innerWidth / window.innerHeight, 0.01, 1000));
+    this.camera?.position.set(0, 0.2, 0.3);
+    // const helper = new THREE.CameraHelper( this.camera);
+    // this.scene?.add( helper );
+    
+  }
+
+  initRenderer() {
+
+    this.renderer = this.track(new THREE.WebGLRenderer({ antialias: true, alpha:true })) as THREE.WebGLRenderer;
+    // 设置屏幕像素比
+    this.renderer?.setPixelRatio(window.devicePixelRatio);
+    // 设置渲染的尺寸
+    this.renderer?.setSize(window.innerWidth, window.innerHeight);
+    // 色调映射
+    this.renderer.toneMapping = THREE.ACESFilmicToneMapping;
+
+    this.renderer.outputEncoding = THREE.sRGBEncoding;
+
+    this.renderer.shadowMap.enabled = true
+
+    this.renderer.shadowMap.type = THREE.PCFSoftShadowMap
+
+    // 曝光程度
+    this.renderer.toneMappingExposure = 1;
+
+    this.canvasContainer.appendChild(this.renderer.domElement);
+    
+  }
+
+  initCSSRenderer(cssCanvas) {
+    this.CSSCanvasContainer = document.querySelector(cssCanvas);
+    this.css3dRender = this.track(new CSS3DRenderer()) as CSS3DRenderer
+    this.css3dRender.setSize((window.innerWidth), (window.innerHeight))
+    this.CSSCanvasContainer?.appendChild(this.css3dRender.domElement)
+    this.css3dRender.render(this.scene as THREE.Scene, this.camera as THREE.PerspectiveCamera)
+    this.orbitControls = this.track(new OrbitControls(this.camera as THREE.Camera, this.css3dRender?.domElement)) as OrbitControls;
+    this.orbitControls.update()
+    // this.orbitControls.enableZoom = false;
+    // // to disable rotation
+    // this.orbitControls.enableRotate = false;
+    // // to disable pan
+    // this.orbitControls.enablePan = false;
+  }
+
+  initControles() {
+    this.orbitControls = this.track(new OrbitControls(this.camera as THREE.Camera, this.renderer?.domElement)) as OrbitControls;
+    this.orbitControls.update()
+    // this.orbitControls.enableZoom = false;
+    // // to disable rotation
+    // this.orbitControls.enableRotate = false;
+    // // to disable pan
+    // this.orbitControls.enablePan = false;
+  }
+
+  setModalCenter(group) {
+    var box3 = new THREE.Box3()
+  
+    // 计算层级模型group的包围盒
+    // 模型group是加载一个三维模型返回的对象,包含多个网格模型
+    box3.expandByObject(group)
+    // 计算一个层级模型对应包围盒的几何体中心在世界坐标中的位置
+    var center = new THREE.Vector3()
+    box3.getCenter(center)
+    // console.log('查看几何体中心坐标', center);
+    // 重新设置模型的位置,使之居中。
+    group.position.x = group.position.x - center.x
+    group.position.y = group.position.y - center.y
+    group.position.z = group.position.z - center.z
+  };
+
+  setModel(modalName) {
+    const a = new Date().getTime() / 1000
+    return new Promise(async (resolve, reject) => {
+      try {
+        const db =  window['CustomDB']
+        const modalArr =  await db.modal.where('modalName').equals(modalName).toArray()
+        // debugger
+        if(modalArr.length > 0) {
+          const modalValue = modalArr[0].modalVal
+          try {
+            const gltfLoader = new GLTFLoader()
+            const dracoLoader = new DRACOLoader();
+            dracoLoader.setDecoderPath( '/3D/draco/gltf/' );
+            dracoLoader.setDecoderConfig({ type: "js" }); //使用兼容性强的draco_decoder.js解码器
+            dracoLoader.preload();
+            gltfLoader.setDRACOLoader(dracoLoader);
+
+            gltfLoader.setPath('/3d/glft/')
+            gltfLoader.parse(modalValue, '/3d/glft/', (gltf) => {
+              const object = this.track(gltf.scene.children[0])
+              this.setModalCenter(object)
+              object.traverse((obj) => {
+                if (obj instanceof THREE.Mesh) {
+                  obj.material.emissiveIntensity = 1
+                  obj.material.emissiveMap = obj.material.map
+                  if(obj.name !== "buxiugangse") {
+                    obj.receiveShadow = true
+                  }
+                  obj.castShadow = true
+                  
+                  
+                } else if (obj.type === 'Object3D') {
+                  // const text3D = this.addYFText(obj)
+                  // const textCQ3D = this.addCQText(obj)
+                  // gltf.scene.add(text3D)
+                  // gltf.scene.add(textCQ3D)
+                }
+              })
+              this.scene?.add(object);
+              console.log(object);
+              resolve(object)
+            }, (err)=> {
+              console.log(err);
+              
+            })
+  
+          } catch (error) {
+            console.log(error);
+          }
+        } 
+      } catch (error) {
+        reject('加载模型出错')
+      }
+    })
+  }
+
+  setTestPlane() {
+			const plane = new THREE.Mesh(
+				new THREE.PlaneGeometry( 100, 100 ),
+				new THREE.MeshPhongMaterial( { color: 0x999999, specular: 0x101010 } )
+			);
+			plane.rotation.x = - Math.PI / 2;
+			plane.position.y = 0.03;
+			plane.receiveShadow = true;
+			// this.scene?.add( plane );
+  }
+  
+  /* 自定义材质 */
+  setCustomMaterial(group){}
+
+  /* 场景环境背景 */
+  setEnvMap(hdr) {
+    // new RGBELoader().setPath('/public/3D/hdr/').load(hdr + '.jpeg', (texture) => {
+    //   debugger
+    //   texture.mapping = THREE.EquirectangularReflectionMapping;
+    //   (this.scene as THREE.Scene).background = texture;
+    //   (this.scene as THREE.Scene).environment = texture;
+    // });
+    new THREE.TextureLoader().setPath('/3D/hdr/').load(hdr + '.jpeg', (texture) => {
+      texture.mapping = THREE.EquirectangularReflectionMapping;
+      // (this.scene as THREE.Scene).background = new THREE.Color('#00000000');
+      (this.scene as THREE.Scene).environment = texture;
+    });
+  }
+
+  render() {
+    const delta = this.clock.getElapsedTime();
+    this.resetLookAt()
+    this.startMY()
+    this.renderer?.render(this.scene as THREE.Object3D, this.camera as THREE.Camera);
+    this.css3dRender?.render(this.scene as THREE.Scene, this.camera as THREE.PerspectiveCamera)
+    this.animationMixer?.update(delta);
+  }
+
+  /* 实时物体刷新朝向 */
+  resetLookAt() {
+  }
+  /* 漫游 */
+  startMY() {}
+
+  animate() {
+    // this.renderer?.setAnimationLoop(this.render.bind(this));
+    if(this.animationId != -1) {
+      this.render()
+      this.animationId = requestAnimationFrame(this.animate.bind(this))
+    }
+  }
+  resizeRenderer() {
+    // 更新相机比例
+    (this.camera as THREE.PerspectiveCamera).aspect = window.innerWidth / window.innerHeight;
+    // 刷新相机矩阵
+    this.camera?.updateProjectionMatrix();
+    // 设置场景尺寸
+    this.renderer?.setSize(window.innerWidth, window.innerHeight);
+    this.css3dRender?.setSize(window.innerWidth, window.innerHeight);
+  }
+  wheelRenderer(e: WheelEvent) {
+    const timeScale = e.deltaY > 0 ? 1 : -1;
+    this.animationAction?.setEffectiveTimeScale(timeScale);
+    (this.animationAction as THREE.AnimationAction).paused = false;
+    this.animationAction?.play();
+    if (this.timeoutId) {
+      clearTimeout(this.timeoutId);
+    }
+    this.timeoutId = setTimeout(() => {
+      this.animationAction?.halt(0.5);
+    }, 500);
+  }
+  deleteModal() {
+    try {
+      
+      this.resourceTracker && this.resourceTracker.dispose()
+      this.renderer?.dispose()
+      this.renderer?.forceContextLoss()
+      // this.renderer?.domElement = null
+      this.renderer = null
+
+      // this.css3dRender?.domElement = null
+      this.css3dRender = null;
+
+      cancelAnimationFrame(this.animationId);
+
+      const gl = this.renderer?.domElement.getContext("webgl");
+      gl && gl?.getExtension("WEBGL_lose_context")?.loseContext();
+
+      const gl1 = this.css3dRender?.domElement.getContext("webgl");
+      gl1 && gl1?.getExtension("WEBGL_lose_context")?.loseContext();
+
+      this.animationId = -1
+
+      this.resourceTracker?.untrack(this.resourceTracker.resources)
+      // this.scene = null
+      console.log('销毁场景',this.scene);
+      
+    } catch (error) {
+      console.log(error);
+    }
+    // this.scene?.children.forEach((obj:any) => {
+    //   if(obj.type === 'Group'){
+    //     obj.traverse(function(item:any) {
+    //       if (item.type === 'Mesh') {
+    //         item.geometry.dispose();
+    //         item.material.dispose();
+    //         !!item.clear&&item.clear();
+    //       }
+    //     })
+    //   }else if (obj.material) {
+    //     obj.material.dispose();
+    //   }else if (obj.geometry) {
+    //     obj.geometry.dispose();
+    //   }
+    //   this.scene?.remove(obj)
+    //   !!obj.clear??obj.clear()
+    //   obj = null;
+    // })
+    // let gl = this.renderer?.domElement.getContext("webgl");
+    // gl && gl?.getExtension("WEBGL_lose_context")?.loseContext();
+
+    // this.renderer?.forceContextLoss();
+    // this.renderer?.dispose();
+    // this.camera = null;
+    // this.orbitControls = null;
+    // this.renderer.domElement = null;
+    // this.renderer = null;
+
+    // css3dRender.domElement = null;
+    // css3dRender = null;
+    // model3D.innerHTML = '';
+    // css3D.innerHTML = '';
+    // model3D = null
+    // css3D = null
+
+    // this.stats = null
+    // scene = null
+
+    THREE.Cache.clear();  
+    console.log('3D环境已清理干净');
+  }
+
+
+  /* 创建字体精灵 */
+  addTextSprite(message, parameters) {
+    if (parameters === undefined) parameters = {};
+
+    var fontface = parameters.hasOwnProperty("fontface") ?
+        parameters["fontface"] : "Arial";
+
+    /* 字体大小 */
+    var fontsize = parameters.hasOwnProperty("fontsize") ?
+        parameters["fontsize"] : 18;
+
+    /* 边框厚度 */
+    var borderThickness = parameters.hasOwnProperty("borderThickness") ?
+        parameters["borderThickness"] : 4;
+
+    /* 边框颜色 */
+    var borderColor = parameters.hasOwnProperty("borderColor") ?
+        parameters["borderColor"] : { r: 0, g: 0, b: 0, a: 1.0 };
+
+    /* 背景颜色 */
+    var backgroundColor = parameters.hasOwnProperty("backgroundColor") ?
+        parameters["backgroundColor"] : { r: 255, g: 255, b: 255, a: 1.0 };
+
+    /* 创建画布 */
+    var canvas = document.createElement('canvas');
+    var context = canvas.getContext('2d') as CanvasRenderingContext2D;
+    // canvas.width = 0
+    // canvas.height = 200
+
+    /* 字体加粗 */
+    context.font = "Bold " + fontsize + "px " + fontface;
+
+    /* 获取文字的大小数据,高度取决于文字的大小 */
+    var metrics = context.measureText(message);
+    var textWidth = metrics.width;
+
+    /* 背景颜色 */
+    context.fillStyle = "rgba(" + backgroundColor.r + "," + backgroundColor.g + ","
+        + backgroundColor.b + "," + backgroundColor.a + ")";
+
+    /* 边框的颜色 */
+    context.strokeStyle = "rgba(" + borderColor.r + "," + borderColor.g + ","
+        + borderColor.b + "," + borderColor.a + ")";
+    context.lineWidth = borderThickness;
+
+    /* 绘制圆角矩形 */
+    this.roundRect(context, borderThickness / 2, borderThickness / 2, textWidth + borderThickness, fontsize * 1.4 + borderThickness, 6);
+    // this.roundRect(context, borderThickness / 2, borderThickness / 2, 10000, 10000, 6);
+    /* 字体颜色 */
+    context.fillStyle = "rgba(0, 0, 0, 1.0)";
+    context.fillText(message, borderThickness, fontsize + borderThickness);
+    // context.fillRect(borderThickness / 2, borderThickness / 2, 10000, 10000)
+    /* 画布内容用于纹理贴图 */
+    var texture = new THREE.Texture(canvas);
+    texture.needsUpdate = true;
+
+    var spriteMaterial = new THREE.SpriteMaterial({ map: texture, transparent:false });
+    var sprite = new THREE.Sprite(spriteMaterial);
+
+    /* 缩放比例 */
+    sprite.scale.set(10, 5, 1);
+
+    return sprite;
+  }
+
+  /* 绘制圆角矩形 */
+  roundRect(ctx, x, y, w, h, r) {
+
+    ctx.beginPath();
+    ctx.moveTo(x + r, y);
+    ctx.lineTo(x + w - r, y);
+    ctx.quadraticCurveTo(x + w, y, x + w, y + r);
+    ctx.lineTo(x + w, y + h - r);
+    ctx.quadraticCurveTo(x + w, y + h, x + w - r, y + h);
+    ctx.lineTo(x + r, y + h);
+    ctx.quadraticCurveTo(x, y + h, x, y + h - r);
+    ctx.lineTo(x, y + r);
+    ctx.quadraticCurveTo(x, y, x + r, y);
+    ctx.closePath();
+    ctx.fill();
+    ctx.stroke();
+
+  }
+
+}
+
+export default UseThree;

+ 2 - 0
src/hooks/setting/index.ts

@@ -13,6 +13,7 @@ export const useGlobSetting = (): Readonly<GlobConfig> => {
     VITE_GLOB_APP_OPEN_QIANKUN,
     VITE_GLOB_DOMAIN_URL,
     VITE_GLOB_ONLINE_VIEW_URL,
+    VITE_3D_MODAL_ARR
   } = getAppEnvConfig();
 
   if (!/[a-zA-Z\_]*/.test(VITE_GLOB_APP_SHORT_NAME)) {
@@ -33,6 +34,7 @@ export const useGlobSetting = (): Readonly<GlobConfig> => {
     urlPrefix: VITE_GLOB_API_URL_PREFIX,
     uploadUrl: VITE_GLOB_DOMAIN_URL,
     viewUrl: VITE_GLOB_ONLINE_VIEW_URL,
+    modalUrlArr: VITE_3D_MODAL_ARR
   };
   window._CONFIG['domianURL'] = VITE_GLOB_DOMAIN_URL;
   return glob as Readonly<GlobConfig>;

+ 6 - 0
src/hooks/setting/useRootSetting.ts

@@ -3,12 +3,15 @@ import type { ProjectConfig } from '/#/config';
 import { computed } from 'vue';
 
 import { useAppStore } from '/@/store/modules/app';
+import { useVentStore } from '/@/store/modules/vent';
 import { ContentEnum, ThemeEnum } from '/@/enums/appEnum';
 
 type RootSetting = Omit<ProjectConfig, 'locale' | 'headerSetting' | 'menuSetting' | 'multiTabsSetting'>;
 
 export function useRootSetting() {
   const appStore = useAppStore();
+  
+  const ventStore = useVentStore(); //通风项目初始设置
 
   const getPageLoading = computed(() => appStore.getPageLoading);
 
@@ -51,6 +54,9 @@ export function useRootSetting() {
   const getDarkMode = computed(() => appStore.getDarkMode);
 
   const getLayoutContentMode = computed(() => (appStore.getProjectConfig.contentMode === ContentEnum.FULL ? ContentEnum.FULL : ContentEnum.FIXED));
+  
+  //通风项目
+  ventStore.setAllTableHeaderColumns();
 
   function setRootSetting(setting: Partial<RootSetting>) {
     appStore.setProjectConfig(setting);

+ 92 - 0
src/hooks/web/useVentWebSocket.ts

@@ -0,0 +1,92 @@
+import { reactive, watch, unref, computed, ref, Ref, watchEffect, WatchStopHandle } from 'vue';
+import { useWebSocket as $useWebSocket } from '@vueuse/core';
+import { useGlobSetting } from '/@/hooks/setting';
+import { useUserStore } from '/@/store/modules/user';
+import { useVentStore } from '/@/store/modules/vent';
+import { getToken } from '/@/utils/auth';
+
+const glob = useGlobSetting();
+const userStore = useUserStore();
+const state = reactive({
+  server: glob.domainUrl?.replace('https://', 'wss://').replace('http://', 'ws://') + '/deviceSocket/' + unref(userStore.getUserInfo).username + '?token=' + getToken(),
+  sendValue: '',
+  recordList: [] as { id: number; time: number; res: string }[],
+});
+
+const ventStore = useVentStore();
+
+const resultWebSocket = computed(() => {
+  return ventStore.getResultWebSocket;
+});
+
+const data = computed(() => (resultWebSocket.value ? resultWebSocket.value.data : ''));
+const status = computed(() => (resultWebSocket.value ? resultWebSocket.value.status : ''));
+const stop = <Ref<WatchStopHandle>>ref();
+
+const startWatchEffect = () => {
+  stop.value = watchEffect(() => {
+    if (status.value === 'OPEN' && !data.value) {
+      try {
+        resultWebSocket.value.send(state.sendValue);
+        console.log(222, 'sendValue', state.sendValue, status.value);
+      } catch (e) {
+        console.error(e);
+      }
+    }
+    if (data.value && status.value === 'OPEN') {
+      try {
+        const res = JSON.parse(data.value);
+        if (res.cmd === 'monitordata') {
+          state.recordList = [];
+          const list: [] = res.msgTxt[0].datalist;
+          list.forEach((item: any) => {
+            const resultItem = { ...item };
+            const readData = item.readData;
+            for (const key in readData) {
+              resultItem[key] = readData[key];
+            }
+            state.recordList.push(resultItem);
+          });
+        }
+      } catch (error) {
+        state.recordList.push({
+          res: data.value,
+          id: Math.ceil(Math.random() * 1000),
+          time: new Date().getTime(),
+        });
+      }
+    }
+  });
+};
+
+function connectWebSocket() {
+  if (!resultWebSocket.value) {
+    console.log('测试链接。。。。');
+    const result = $useWebSocket(state.server, {
+      // 自动重连
+      autoReconnect: false,
+      // 心跳检测
+      heartbeat: true,
+      //protocols: [token],
+    });
+    ventStore.setResultWebSocket(result);
+    startWatchEffect();
+  }
+}
+
+export function initWebSocket(sendValue) {
+  if (resultWebSocket.value) {
+    closeWebSocket();
+  }
+  state.sendValue = sendValue;
+  connectWebSocket();
+}
+
+export function getRecordList() {
+  return state.recordList;
+}
+
+export function closeWebSocket() {
+  state.recordList = [];
+  ventStore.setResultWebSocket(null);
+}

+ 35 - 0
src/hooks/web/useWebColumns.ts

@@ -0,0 +1,35 @@
+import { computed, ref } from 'vue';
+import { useVentStore } from '/@/store/modules/vent';
+import { BasicColumn } from '/@/components/Table';
+
+const ventStore = useVentStore();
+const webColumnsKey = ref('');
+
+const arrToColumns = (tableHeaderColumns = []) => {
+  // debugger
+  const columns: BasicColumn[] = [];
+  tableHeaderColumns.forEach((item: any) => {
+    const columnsItem = {
+      title: item.unit ? `${item.des}(${item.unit})` : item.des, //_dictText
+      dataIndex: item.dict ? `${item.monitorcode}_dictText` : item.monitorcode,
+      width: item.width || 100,
+      defaultHidden: !item.showflag,
+      sorter: item.sort ? true : false,
+      customRender: ({ text }) => {
+        return text ? text : '/';
+      },
+    };
+    columns.push(columnsItem);
+  });
+  return columns;
+};
+
+export const setWebColumnsKey = (key: string) => {
+  webColumnsKey.value = key;
+};
+
+export const getTableHeaderColumns = computed(() => {
+  const allTableHeaderColumnArr = ventStore.getAllTableHeaderColumnsApi;
+  const tabelHeaderColumns = allTableHeaderColumnArr[webColumnsKey.value];
+  return arrToColumns(tabelHeaderColumns);
+});

+ 1 - 1
src/layouts/default/header/components/ErrorAction.vue

@@ -1,5 +1,5 @@
 <template>
-  <Tooltip :title="t('layout.header.tooltipErrorLog')" placement="bottom" :mouseEnterDelay="0.5" @click="handleToErrorList">
+  <Tooltip :title="t('layout.header.tooltipErrorLog')" placement="bottomCenter" :mouseEnterDelay="0.5" @click="handleToErrorList">
     <Badge :count="getCount" :offset="[0, 10]" :overflowCount="99">
       <Icon icon="ion:bug-outline" />
     </Badge>

+ 1 - 1
src/layouts/default/header/components/FullScreen.vue

@@ -1,5 +1,5 @@
 <template>
-  <Tooltip :title="getTitle" placement="bottom" :mouseEnterDelay="0.5">
+  <Tooltip :title="getTitle" placement="bottomCenter" :mouseEnterDelay="0.5">
     <span @click="toggle">
       <FullscreenOutlined v-if="!isFullscreen" />
       <FullscreenExitOutlined v-else />

+ 1 - 1
src/layouts/default/header/components/LockScreen.vue

@@ -1,5 +1,5 @@
 <template>
-  <Tooltip :title="t('layout.header.tooltipLock')" placement="bottom" :mouseEnterDelay="0.5" @click="handleLock">
+  <Tooltip :title="t('layout.header.tooltipLock')" placement="bottomCenter" :mouseEnterDelay="0.5" @click="handleLock">
     <LockOutlined />
   </Tooltip>
   <LockModal @register="register" />

+ 1 - 1
src/layouts/default/setting/components/TypePicker.vue

@@ -1,7 +1,7 @@
 <template>
   <div :class="prefixCls">
     <template v-for="item in menuTypeList || []" :key="item.title">
-      <Tooltip :title="item.title" placement="bottom">
+      <Tooltip :title="item.title" placement="bottomCenter">
         <div
           @click="handler(item)"
           :class="[

+ 2 - 2
src/layouts/default/tabs/index.vue

@@ -9,11 +9,11 @@
         </TabPane>
       </template>
 
-      <template #tabBarExtraContent v-if="getShowRedo || getShowQuick">
+      <!-- <template v-if="getShowRedo || getShowQuick">
         <TabRedo v-if="getShowRedo" />
         <TabContent isExtra :tabItem="$route" v-if="getShowQuick" />
         <FoldButton v-if="getShowFold" />
-      </template>
+      </template> -->
     </Tabs>
   </div>
 </template>

+ 2 - 0
src/logics/initAppConfig.ts

@@ -26,6 +26,7 @@ import { ThemeEnum } from '/@/enums/appEnum';
 export function initAppConfigStore() {
   const localeStore = useLocaleStore();
   const appStore = useAppStore();
+
   let projCfg: ProjectConfig = Persistent.getLocal(PROJ_CFG_KEY) as ProjectConfig;
   projCfg = deepMerge(projectSetting, projCfg || {});
   const darkMode = appStore.getDarkMode;
@@ -61,6 +62,7 @@ export function initAppConfigStore() {
   // init store
   localeStore.initLocale();
 
+
   setTimeout(() => {
     clearObsoleteStorage();
   }, 16);

+ 4 - 0
src/router/constant.ts

@@ -4,8 +4,12 @@ export const PARENT_LAYOUT_NAME = 'ParentLayout';
 
 export const PAGE_NOT_FOUND_NAME = 'PageNotFound';
 
+export const QIANKUN_ROUTE_NAME = 'MicroApp';
+
 export const EXCEPTION_COMPONENT = () => import('/@/views/sys/exception/Exception.vue');
 
+export const QIANKUN_COMPONENT = () => import('/@/views/sys/micro/index.vue');
+
 /**
  * @description: default layout
  */

+ 25 - 1
src/router/routes/basic.ts

@@ -1,6 +1,6 @@
 import type { AppRouteRecordRaw } from '/@/router/types';
 import { t } from '/@/hooks/web/useI18n';
-import { REDIRECT_NAME, LAYOUT, EXCEPTION_COMPONENT, PAGE_NOT_FOUND_NAME } from '/@/router/constant';
+import { REDIRECT_NAME, LAYOUT, EXCEPTION_COMPONENT, PAGE_NOT_FOUND_NAME, QIANKUN_ROUTE_NAME, QIANKUN_COMPONENT } from '/@/router/constant';
 
 // 404 on a page
 export const PAGE_NOT_FOUND_ROUTE: AppRouteRecordRaw = {
@@ -26,6 +26,30 @@ export const PAGE_NOT_FOUND_ROUTE: AppRouteRecordRaw = {
   ],
 };
 
+export const QIANKUN_ROUTE: AppRouteRecordRaw = {
+  path: '/micro-:path(.*)*',
+  name: QIANKUN_ROUTE_NAME,
+  component: LAYOUT,
+  meta: {
+    title: '子应用',
+    hideBreadcrumb: true,
+    hideMenu: true,
+  },
+  children: [
+    {
+      path: '/micro-:path(.*)*',
+      name: QIANKUN_ROUTE_NAME,
+      component: QIANKUN_COMPONENT,
+      meta: {
+        title: '子应用',
+        hideBreadcrumb: true,
+        hideMenu: true,
+      },
+    },
+  ],
+};
+
+
 export const REDIRECT_ROUTE: AppRouteRecordRaw = {
   path: '/redirect',
   component: LAYOUT,

+ 2 - 2
src/settings/projectSetting.ts

@@ -126,9 +126,9 @@ const setting: ProjectConfig = {
     // 是否可以拖拽
     canDrag: true,
     // 开启快速操作
-    showQuick: true,
+    showQuick: false,
     // 是否显示刷新按钮
-    showRedo: true,
+    showRedo: false,
     // 是否显示折叠按钮
     showFold: true,
     // 标签页样式

+ 28 - 0
src/store/modules/vent.ts

@@ -0,0 +1,28 @@
+import { defineStore } from 'pinia';
+import { getAllTableHeaderColumnsApi } from '/@/api/sys/vent';
+
+export const useVentStore = defineStore({
+  id: 'app-vent',
+  state: () => {
+    return {
+      allTableHeaderColumns: {},
+      resultWebSocket: null,
+    };
+  },
+  getters: {
+    getAllTableHeaderColumnsApi(): any {
+      return this.allTableHeaderColumns;
+    },
+    getResultWebSocket(): any {
+      return this.resultWebSocket;
+    },
+  },
+  actions: {
+    async setAllTableHeaderColumns() {
+      this.allTableHeaderColumns = await getAllTableHeaderColumnsApi();
+    },
+    setResultWebSocket(resultWebSocket) {
+      this.resultWebSocket = resultWebSocket;
+    },
+  },
+});

+ 2 - 0
src/utils/env.ts

@@ -33,6 +33,7 @@ export function getAppEnvConfig() {
     VITE_GLOB_APP_CAS_BASE_URL,
     VITE_GLOB_DOMAIN_URL,
     VITE_GLOB_ONLINE_VIEW_URL,
+    VITE_3D_MODAL_ARR
   } = ENV;
 
   if (!/^[a-zA-Z\_]*$/.test(VITE_GLOB_APP_SHORT_NAME)) {
@@ -52,6 +53,7 @@ export function getAppEnvConfig() {
     VITE_GLOB_APP_CAS_BASE_URL,
     VITE_GLOB_DOMAIN_URL,
     VITE_GLOB_ONLINE_VIEW_URL,
+    VITE_3D_MODAL_ARR
   };
 }
 

+ 2 - 1
src/utils/lib/echarts.ts

@@ -1,6 +1,6 @@
 import * as echarts from 'echarts/core';
 
-import { BarChart, LineChart, PieChart, MapChart, PictorialBarChart, RadarChart } from 'echarts/charts';
+import { BarChart, LineChart, PieChart, MapChart, PictorialBarChart, RadarChart, GaugeChart } from 'echarts/charts';
 
 import {
   TitleComponent,
@@ -36,6 +36,7 @@ echarts.use([
   PieChart,
   MapChart,
   RadarChart,
+  GaugeChart,
   // TODO 因为要兼容Online图表自适应打印,所以改成 CanvasRenderer,可能会模糊
   CanvasRenderer,
   PictorialBarChart,

+ 64 - 0
src/utils/threejs/ResourceTracker.js

@@ -0,0 +1,64 @@
+import * as THREE from 'three';
+
+export default class ResourceTracker {
+   constructor() {
+   	this.resources = new Set();
+   }
+   track(resource) {
+   	if (!resource) {
+   		return resource;
+   	}
+
+   	// handle children and when material is an array of materials or
+   	// uniform is array of textures
+   	if (Array.isArray(resource)) {
+   		resource.forEach(resource => this.track(resource));
+   		return resource;
+   	}
+
+   	if (resource.dispose || resource instanceof THREE.Object3D) {
+   		this.resources.add(resource);
+   	}
+   	if (resource instanceof THREE.Object3D) {
+   		this.track(resource.geometry);
+   		this.track(resource.material);
+   		this.track(resource.children);
+   	} else if (resource instanceof THREE.Material) {
+   		// We have to check if there are any textures on the material
+   		for (const value of Object.values(resource)) {
+   			if (value instanceof THREE.Texture) {
+   				this.track(value);
+   			}
+   		}
+   		// We also have to check if any uniforms reference textures or arrays of textures
+   		if (resource.uniforms) {
+   			for (const value of Object.values(resource.uniforms)) {
+   				if (value) {
+   					const uniformValue = value.value;
+   					if (uniformValue instanceof THREE.Texture ||
+   						Array.isArray(uniformValue)) {
+   						this.track(uniformValue);
+   					}
+   				}
+   			}
+   		}
+   	}
+   	return resource;
+   }
+   untrack(resource) {
+   	this.resources.delete(resource);
+   }
+   dispose() {
+   	for (const resource of this.resources) {
+   		if (resource instanceof THREE.Object3D) {
+   			if (resource.parent) {
+   				resource.parent.remove(resource);
+   			}
+   		}
+   		if (resource.dispose) {
+   			resource.dispose();
+   		}
+   	}
+   	this.resources.clear();
+   }
+}

+ 44 - 0
src/utils/threejs/loadGltf.worker.js

@@ -0,0 +1,44 @@
+import * as THREE from 'three'
+import Dexie from 'dexie';
+
+const db = new Dexie('DB')
+db.version(1).stores({
+    modal: "++id, modalName, modalVal",
+})
+
+self.addEventListener('message', function(e) {
+    const { data, message } = e.data;
+    if(message == 'end') {
+        // self.postMessage({message: 'end', data: null})
+        // self.close()
+    }else {
+        if(data) {
+            loadGltf(data)
+        }
+    }
+    
+}, false);
+
+function loadGltf(url){
+    const loader = new THREE.FileLoader(); 
+
+    loader.setPath( '/3d/glft/' );
+    loader.setResponseType( 'arraybuffer' );
+    loader.setRequestHeader( {} );
+    loader.setWithCredentials( false );
+    try {
+        loader.load(url, (data)=> {
+            const modalName = url.replace(/(.*\/)*([^.]+).*/ig,"$2")
+            
+            db.modal.add({
+                modalName: modalName,
+                modalVal: data
+            })
+            self.postMessage({message: 'end', data: null})
+            // self.close()
+        })
+    } catch (error) {
+        console.log(error);
+    }
+}
+

+ 38 - 0
src/utils/threejs/main.worker.ts

@@ -0,0 +1,38 @@
+import modalWorker from "./loadGltf.worker?worker";
+import Dexie from 'dexie';
+import { useGlobSetting } from '/@/hooks/setting';
+const glob = useGlobSetting();
+
+
+const db:any = new Dexie('DB')
+window['CustomDB'] = db
+
+
+export function initModalWorker () {
+
+  db.version(1).stores({
+    modal: "++id, modalName, modalVal",
+  })
+
+  const worker = new modalWorker()
+
+  worker.onmessage = async function(e) {
+    const { data, message } = e.data;
+    if(message === 'end'){
+      setTimeout(() => {
+        worker.terminate()
+      }, 10000)
+    }
+  }
+
+  glob.modalUrlArr.forEach( async (url) => {    
+    const modalName = url.replace(/(.*\/)*([^.]+).*/ig,"$2")
+    const modalArr =  await db.modal.where('modalName').equals(modalName).toArray()
+    if(modalArr.length < 1) {
+      worker.postMessage({message: 'load', data: url})
+    }
+  });
+  
+}
+
+initModalWorker()

+ 93 - 0
src/utils/threejs/util.ts

@@ -0,0 +1,93 @@
+import * as THREE from 'three';
+import { TransformControls } from 'three/examples/jsm/controls/TransformControls'; // 引入模块
+import * as dat from "dat.gui";
+
+let modal, cubeList:THREE.Mesh[] = [], curve, line
+
+//创建gui对象
+const gui = new dat.GUI();
+gui.domElement.style.top = '300'
+gui.domElement.style.clientTop = 300
+gui.domElement.style.zIndex = 9999
+
+// 画点
+const addCube = (initialPoints, scene) => {
+  return initialPoints.map(pos => {
+    const geometry = new THREE.BoxBufferGeometry(1, 1, 1);
+      const material = new THREE.MeshBasicMaterial({
+        color: 0xffffff,
+        side: THREE.DoubleSide
+      });
+      const cube = new THREE.Mesh(geometry, material);
+      cube.position.copy(pos);
+      scene.add(cube);
+      cubeList.push(cube)
+      // gui.add(cube.position, 'x', -1000, 1000)
+      // gui.add(cube.position, 'y', -1000, 1000)
+      // gui.add(cube.position, 'z', -1000, 1000)
+  });
+}
+// 画线
+export const drawLine = (initialPoints, obj) => {
+  modal = obj
+  addCube(initialPoints, modal.scene)
+  curve = new THREE.CatmullRomCurve3(
+    cubeList.map((cube) => cube.position) // 直接绑定方块的position以便后续用方块调整曲线
+  );
+  curve.curveType = 'chordal'; // 曲线类型
+  curve.closed = false; // 曲线是否闭合
+
+  const points = curve.getPoints(50); // 50等分获取曲线点数组
+  line = new THREE.LineLoop(
+      new THREE.BufferGeometry().setFromPoints(points),
+      new THREE.LineBasicMaterial({ color: 0xffffff,
+        linewidth: 10 })
+  ); // 绘制实体线条,仅用于示意曲线,后面的向量线条同理,相关代码就省略了
+
+  modal.scene.add(line);
+
+  addChangeEvent()
+}
+
+// 获取点击位置
+const addChangeEvent = () => {
+  debugger
+  const control = new TransformControls(modal.camera, modal.renderer.domElement);
+  // 获取点击位置
+  const mouse = new THREE.Vector2();
+  window.addEventListener(
+      'click',
+      (event) => {
+        debugger
+          mouse.x = (event.clientX / window.innerWidth) * 2 - 1;
+          mouse.y = -(event.clientY / window.innerHeight) * 2 + 1;
+          // mouse.x = ((event.clientX - dom.offsetLeft) / dom.clientWidth) * 2 - 1; // dom.offsetLeft -- dom元素距离浏览器左侧的距离   dom.clientWidth -- dom元素宽度
+          // mouse.y = -((event.clientY - dom.offsetTop) / dom.clientHeight) * 2 + 1; // dom.offsetTop -- dom元素距离浏览器顶部的距离    dom.clientHeight -- dom元素高度
+          //根据照相机,把这个向量转换到视点坐标系
+          const vector = new THREE.Vector3(mouse.x, mouse.y, 0.5).unproject(modal.camera);
+          
+          // 方块点击检测
+          // const rayCaster = new THREE.Raycaster();
+          //在视点坐标系中形成射线,射线的起点向量是照相机, 射线的方向向量是照相机到点击的点,这个向量应该归一标准化。
+          const rayCaster = new THREE.Raycaster(modal.camera.position, vector.sub(modal.camera.position).normalize());
+          // rayCaster.setFromCamera(mouse, modal.camera);
+          const intersects = rayCaster.intersectObjects(cubeList);
+          if (intersects.length) {
+              const target = intersects[0].object;
+              control.attach(target); // 绑定controls和方块
+              modal.scene.add(control);
+          }
+      },
+      false
+  );
+
+  // 修改曲线后同步修改实体线条
+  control.addEventListener('dragging-changed', (event) => {
+      if (!event.value) {
+          console.log(event.value);
+        
+          const points = curve.getPoints(50);
+          line.geometry.setFromPoints(points);
+      }
+  });
+}

+ 1 - 1
src/views/dashboard/Analysis/components/BdcTabCard.vue

@@ -2,7 +2,7 @@
   <a-card :loading="loading" :bordered="false" :body-style="{ padding: '0' }">
     <div class="salesCard">
       <a-tabs default-active-key="1" size="large" :tab-bar-style="{ marginBottom: '24px', paddingLeft: '16px' }">
-        <template #tabBarExtraContent>
+        <template >
           <div class="extra-wrapper">
             <div class="extra-item">
               <a>今日</a>

+ 1 - 1
src/views/dashboard/Analysis/components/SaleTabCard.vue

@@ -2,7 +2,7 @@
   <a-card :loading="loading" :bordered="false" :body-style="{ padding: '0' }">
     <div class="salesCard">
       <a-tabs default-active-key="1" size="large" :tab-bar-style="{ marginBottom: '24px', paddingLeft: '16px' }">
-        <template #tabBarExtraContent>
+        <template >
           <div class="extra-wrapper">
             <div class="extra-item">
               <a>今日</a>

+ 1 - 1
src/views/dashboard/Analysis/homePage/IndexBdc.vue

@@ -6,7 +6,7 @@
       <a-col :span="24">
         <a-card :loading="loading" :class="{ 'anty-list-cust': true }" :bordered="false">
           <a-tabs v-model:activeKey="indexBottomTab" size="large" :tab-bar-style="{ marginBottom: '24px', paddingLeft: '16px' }">
-            <template #tabBarExtraContent>
+            <template >
               <div class="extra-wrapper">
                 <a-radio-group v-model:value="indexRegisterType" @change="changeRegisterType">
                   <a-radio-button value="转移登记">转移登记</a-radio-button>

+ 11 - 2
src/views/demo/charts/Line.vue

@@ -34,6 +34,15 @@
                 backgroundColor: '#333',
               },
             },
+            formatter: function (datas) {
+              var res = datas[0].name + '<br/>';
+              for (var i = 0, length = datas.length; i < length; i++) {
+                if (!datas[i].seriesName.startsWith('series')) {
+                  res += datas[i].seriesName + ':' + Number(datas[i].data).toFixed(2) + '<br/>';
+                }
+              }
+              return res;
+            },
           },
           legend: {
             data: ['line', 'bar'],
@@ -81,7 +90,7 @@
               data: barData,
             },
             {
-              name: 'line',
+              // name: 'line',
               type: 'bar',
               barGap: '-100%',
               barWidth: 10,
@@ -96,7 +105,7 @@
               data: lineData,
             },
             {
-              name: 'dotted',
+              // name: 'dotted',
               type: 'pictorialBar',
               symbol: 'rect',
               itemStyle: {

+ 189 - 47
src/views/demo/charts/Map.vue

@@ -1,75 +1,217 @@
 <template>
-  <div ref="chartRef" :style="{ height, width }"></div>
+  <div class="box">
+    <div class="" ref="chartRef" :style="{ height, width }"></div>
+  </div>
 </template>
 <script lang="ts">
-  import { defineComponent, PropType, ref, Ref, onMounted } from 'vue';
-
+  import { defineComponent, PropType, reactive, ref, Ref, onMounted } from 'vue';
   import { useECharts } from '/@/hooks/web/useECharts';
-  import { mapData } from './data';
-  import { registerMap } from 'echarts';
-
+  import type { EChartsOption } from 'echarts';
   export default defineComponent({
     props: {
       width: {
         type: String as PropType<string>,
-        default: '100%',
+        default: '300px',
       },
       height: {
         type: String as PropType<string>,
-        default: 'calc(100vh - 78px)',
+        default: '300px',
       },
     },
     setup() {
       const chartRef = ref<HTMLDivElement | null>(null);
       const { setOptions } = useECharts(chartRef as Ref<HTMLDivElement>);
+      const data = reactive({
+        name: '需风量',
+        num: 500,
+      });
+      const max = ref(1000);
+      const timeTickId = ref(0);
 
-      onMounted(async () => {
-        const json = (await (await import('./china.json')).default) as any;
-        registerMap('china', json);
-        setOptions({
-          visualMap: [
-            {
-              min: 0,
-              max: 1000,
-              left: 'left',
-              top: 'bottom',
-              text: ['高', '低'],
-              calculable: false,
-              orient: 'horizontal',
-              inRange: {
-                color: ['#e0ffff', '#006edd'],
-                symbolSize: [30, 100],
+      const option = <EChartsOption>reactive({
+        angleAxis: {
+          show: false,
+          max: (max.value * 3) / 2, //这里将极坐标最大值转换成仪表盘的最大值,(360度除以240度)
+          type: 'value',
+          startAngle: 210, //极坐标初始角度,从第一象限算起,大约在7-8点钟角度之间
+          splitLine: {
+            show: false, //隐藏坐标
+          },
+        },
+        barMaxWidth: 18, //圆环宽度
+        radiusAxis: {
+          //隐藏坐标
+          show: false,
+          type: 'category',
+        },
+        polar: {
+          //设置圆环位置和大小
+          // center: ['50%', '50%'],
+          radius: '120%',
+        },
+        series: [
+          {
+            type: 'bar',
+            data: [
+              {
+                //上层圆环,用于显示真实数据
+                value: data.num,
+                itemStyle: {
+                  color: {
+                    //图形渐变颜色方法,四个数字分别代表,右,下,左,上,offset表示0%到100%
+                    type: 'linear',
+                    x: 0,
+                    y: 0,
+                    x2: 1, //从左到右 0-1
+                    y2: 0,
+                    colorStops: [
+                      {
+                        offset: 0,
+                        color: '#CD48AE', // 0% 处的颜色
+                      },
+                      {
+                        offset: 1,
+                        color: '#2CABFC', // 100% 处的颜色
+                      },
+                    ],
+                    global: false, // 缺省为 false
+                  },
+                  shadowColor: 'rgba(255, 255, 255, 0.2)', //加白色阴影产生高亮效果
+                  shadowBlur: 10,
+                },
+              },
+            ],
+            barGap: '-100%', //柱间距离,用来将上下两层圆环重合
+            coordinateSystem: 'polar', //类型,极坐标
+            roundCap: true, //顶端圆角
+            z: 2, //圆环层级,和zindex相似
+          },
+          {
+            //下层圆环,用于显示最大值
+            type: 'bar',
+            data: [
+              {
+                value: max.value,
+                itemStyle: {
+                  color: '#265195',
+                  shadowColor: 'rgba(0, 0, 0, 0.2)', //加白色阴影产生高亮效果
+                  shadowBlur: 5,
+                  shadowOffsetY: 2,
+                },
+              },
+            ],
+            barGap: '-100%', //柱间距离,用来将上下两层圆环重合
+            coordinateSystem: 'polar', //类型,极坐标
+            roundCap: true, //顶端圆角
+            z: 1, //圆环层级,和zindex相似
+          },
+          {
+            //仪表盘
+            type: 'gauge',
+            radius: '100%',
+            startAngle: 210, //起始角度,同极坐标
+            endAngle: -30, //终止角度,同极坐标
+            max: max.value,
+            splitNumber: 5, //分割线个数(除原点外)
+            axisLine: {
+              // 坐标轴线
+              show: false,
+            },
+            pointer: {
+              show: false,
+            },
+            axisLabel: {
+              // 坐标轴数字
+              fontSize: 8,
+              color: '#13B5FC',
+            },
+            axisTick: {
+              // 坐标轴标记
+              length: 10,
+              lineStyle: {
+                color: '#13B5FC',
+              },
+            },
+            splitLine: {
+              // 分隔线
+              length: 5,
+              lineStyle: {
+                width: 1,
               },
             },
-          ],
-          tooltip: {
-            trigger: 'item',
-            backgroundColor: 'rgba(0, 0, 0, .6)',
-            textStyle: {
+            title: {
+              //标题,显示'馆藏量'
               color: '#fff',
-              fontSize: 12,
+              shadowColor: '#fff',
+              fontSize: 24,
+              offsetCenter: ['0', '-25%'], //位置偏移
             },
-          },
-          series: [
-            {
-              name: 'iphone4',
-              type: 'map',
-              map: 'china',
-              label: {
-                show: true,
-                color: 'rgb(249, 249, 249)',
-                fontSize: 10,
+            detail: {
+              //仪表盘数值
+              formatter: function (params) {
+                var name = data.num.toString();
+                var list = '';
+                for (var i = 0; i < name.length; i++) {
+                  list += '{value|' + name[i] + '}'; //每个数字用border隔开
+                  if (i !== name.length - 1) {
+                    list += '{margin|}'; //添加margin值
+                  }
+                }
+                return [list];
               },
-              itemStyle: {
-                areaColor: '#2f82ce',
-                borderColor: '#0DAAC1',
+              offsetCenter: ['0', '5%'],
+              rich: {
+                //编辑富文本样式
+                value: {
+                  width: 28,
+                  height: 32,
+                  borderColor: '#02A0F0',
+                  borderWidth: 2,
+                  borderRadius: 5,
+                  // lineHeight: 1000,
+                  fontSize: 30,
+                  padding: [6, 0, 0, 0],
+                  color: '#fff',
+                  shadowColor: 'rgb(2,157,239)',
+                  shadowBlur: 5,
+                },
+
+                margin: {
+                  width: 8,
+                  height: 32,
+                },
               },
-              data: mapData,
             },
-          ],
-        });
+            data: [
+              {
+                value: data.num,
+                name: data.name,
+              },
+            ],
+          },
+        ],
+      });
+
+      function timeTick() {
+        if (timeTickId.value) {
+          clearTimeout(timeTickId.value);
+          timeTickId.value = 0;
+        }
+        setOptions(option);
+        // timeTickId.value = setTimeout(timeTick, 1000 * timer.value || 5000) as unknown as number;
+      }
+
+      onMounted(async () => {
+        timeTick();
       });
-      return { chartRef };
+      return { chartRef, timeTick };
     },
   });
 </script>
+<style scoped lang="less">
+  .box {
+    width: 100%;
+    height: 100%;
+    background-color: #0f375f;
+  }
+</style>

+ 75 - 0
src/views/demo/charts/Map1 copy.vue

@@ -0,0 +1,75 @@
+<template>
+  <div ref="chartRef" :style="{ height, width }"></div>
+</template>
+<script lang="ts">
+  import { defineComponent, PropType, ref, Ref, onMounted } from 'vue';
+
+  import { useECharts } from '/@/hooks/web/useECharts';
+  import { mapData } from './data';
+  import { registerMap } from 'echarts';
+
+  export default defineComponent({
+    props: {
+      width: {
+        type: String as PropType<string>,
+        default: '100%',
+      },
+      height: {
+        type: String as PropType<string>,
+        default: 'calc(100vh - 78px)',
+      },
+    },
+    setup() {
+      const chartRef = ref<HTMLDivElement | null>(null);
+      const { setOptions } = useECharts(chartRef as Ref<HTMLDivElement>);
+
+      onMounted(async () => {
+        const json = (await (await import('./china.json')).default) as any;
+        registerMap('china', json);
+        setOptions({
+          visualMap: [
+            {
+              min: 0,
+              max: 1000,
+              left: 'left',
+              top: 'bottom',
+              text: ['高', '低'],
+              calculable: false,
+              orient: 'horizontal',
+              inRange: {
+                color: ['#e0ffff', '#006edd'],
+                symbolSize: [30, 100],
+              },
+            },
+          ],
+          tooltip: {
+            trigger: 'item',
+            backgroundColor: 'rgba(0, 0, 0, .6)',
+            textStyle: {
+              color: '#fff',
+              fontSize: 12,
+            },
+          },
+          series: [
+            {
+              name: 'iphone4',
+              type: 'map',
+              map: 'china',
+              label: {
+                show: true,
+                color: 'rgb(249, 249, 249)',
+                fontSize: 10,
+              },
+              itemStyle: {
+                areaColor: '#2f82ce',
+                borderColor: '#0DAAC1',
+              },
+              data: mapData,
+            },
+          ],
+        });
+      });
+      return { chartRef };
+    },
+  });
+</script>

+ 138 - 0
src/views/demo/charts/Num.vue

@@ -0,0 +1,138 @@
+<template>
+  <div class="chartNum">
+    <div class="box-item">
+      <li :class="{ 'number-item': !isNaN(item), 'mark-item': isNaN(item) }" v-for="(item, index) in orderNum" :key="index">
+        <span v-if="!isNaN(item)">
+          <i class="numberItem">0123456789</i>
+        </span>
+        <span class="comma" v-else>{{ item }}</span>
+      </li>
+    </div>
+  </div>
+</template>
+<script lang="ts">
+  import { defineComponent, PropType, ref, onMounted, nextTick } from 'vue';
+
+  export default defineComponent({
+    props: {
+      width: {
+        type: String as PropType<string>,
+        default: '100%',
+      },
+      height: {
+        type: String as PropType<string>,
+        default: 'calc(100vh - 78px)',
+      },
+    },
+    setup() {
+      const orderNum = ref(['0', '0', ',', '0', '0', '0', ',', '0', '0', '0']); // 默认订单总数
+      const numberItem = ref(null);
+      const setNumberTransform = () => {
+        const numberItems = document.getElementsByClassName('numberItem') as HTMLCollectionOf<HTMLElement>; // 拿到数字的ref,计算元素数量
+        const numberArr: any = orderNum.value.filter((item) => !isNaN(Number(item)));
+        // 结合CSS 对数字字符进行滚动,显示订单数量
+        for (let index = 0; index < numberArr.length; index++) {
+          const elem = numberItems[index];
+          elem.style.transform = `translate(-50%, -${numberArr[index] * 10}%)`;
+        }
+      };
+
+      const toOrderNum = (num) => {
+        num = num.toString();
+        // 把订单数变成字符串
+        if (num.length < 8) {
+          num = '0' + num; // 如未满八位数,添加"0"补位
+          toOrderNum(num); // 递归添加"0"补位
+        } else if (num.length === 8) {
+          // 订单数中加入逗号
+          num = num.slice(0, 2) + ',' + num.slice(2, 5) + ',' + num.slice(5, 8);
+          orderNum.value = num.split(''); // 将其便变成数据,渲染至滚动数组
+        } else {
+          // 订单总量数字超过八位显示异常
+          // $message.warning('订单总量数字过大,显示异常,请联系客服')
+        }
+      };
+      onMounted(() => {
+        nextTick(() => {
+          toOrderNum(1213123);
+          setNumberTransform();
+        });
+      });
+      return { orderNum, numberItem, setNumberTransform, toOrderNum };
+    },
+  });
+</script>
+<style scoped lang="less">
+  .chartNum {
+    width: 100%;
+    height: 100%;
+    background-color: #0f375f;
+  }
+  .box-item {
+    position: relative;
+    height: 100px;
+    font-size: 54px;
+    line-height: 41px;
+    text-align: center;
+    list-style: none;
+    color: #2d7cff;
+    writing-mode: vertical-lr;
+    text-orientation: upright;
+    /*文字禁止编辑*/
+    -moz-user-select: none; /*火狐*/
+    -webkit-user-select: none; /*webkit浏览器*/
+    -ms-user-select: none; /*IE10*/
+    -khtml-user-select: none; /*早期浏览器*/
+    user-select: none;
+    /* overflow: hidden; */
+  }
+  /* 默认逗号设置 */
+  .mark-item {
+    width: 10px;
+    height: 100px;
+    margin-right: 5px;
+    line-height: 10px;
+    font-size: 48px;
+    position: relative;
+    & > span {
+      position: absolute;
+      width: 100%;
+      top: 10px;
+      writing-mode: vertical-rl;
+      text-orientation: upright;
+    }
+  }
+  /*滚动数字设置*/
+  .number-item {
+    width: 41px;
+    height: 65px;
+    background: #ccc;
+    list-style: none;
+    margin-right: 5px;
+    background: rgba(250, 250, 250, 1);
+    border-radius: 4px;
+    border: 1px solid rgba(221, 221, 221, 1);
+    & > span {
+      position: relative;
+      display: inline-block;
+      margin-right: 10px;
+      width: 100%;
+      height: 100%;
+      writing-mode: vertical-rl;
+      text-orientation: upright;
+      overflow: hidden;
+      & > i {
+        font-style: normal;
+        position: absolute;
+        top: 0px;
+        left: 50%;
+        transform: translate(-50%, 0);
+        transition: transform 1s ease-in-out;
+        letter-spacing: 10px;
+      }
+    }
+  }
+  .number-item:last-child {
+    margin-right: 0;
+  }
+</style>

+ 429 - 99
src/views/demo/charts/Pie.vue

@@ -2,8 +2,8 @@
   <div ref="chartRef" :style="{ height, width }"></div>
 </template>
 <script lang="ts">
-  import { defineComponent, PropType, ref, Ref, onMounted } from 'vue';
-
+  import { defineComponent, PropType, ref, Ref, onMounted, reactive, nextTick, onUnmounted } from 'vue';
+  import type { EChartsOption } from 'echarts';
   import { useECharts } from '/@/hooks/web/useECharts';
 
   export default defineComponent({
@@ -14,120 +14,450 @@
       },
       height: {
         type: String as PropType<string>,
-        default: 'calc(100vh - 78px)',
+        default: 'calc(100vh - 178px)',
+      },
+      count: {
+        type: Number,
+        default: 10,
       },
     },
-    setup() {
+    setup(props) {
       const chartRef = ref<HTMLDivElement | null>(null);
-      const { setOptions } = useECharts(chartRef as Ref<HTMLDivElement>);
-      const dataAll = [389, 259, 262, 324, 232, 176, 196, 214, 133, 370];
-      const yAxisData = ['原因1', '原因2', '原因3', '原因4', '原因5', '原因6', '原因7', '原因8', '原因9', '原因10'];
-      onMounted(() => {
-        setOptions({
-          backgroundColor: '#0f375f',
-          title: [
-            {
-              text: '各渠道投诉占比',
-              left: '2%',
-              top: '1%',
-              textStyle: {
-                color: '#fff',
-                fontSize: 14,
+      const { setOptions, echarts, getInstance } = useECharts(chartRef as Ref<HTMLDivElement>);
+      const routeTimer = ref(null);
+      // const echartInstance = ref();
+      function _pie1() {
+        let dataArr: any = [];
+        for (var i = 0; i < 8; i++) {
+          dataArr.push({
+            name: (i + 1).toString(),
+            value: 20,
+            itemStyle: {
+              normal: {
+                color: 'rgba(88,142,197,0.4)',
+                borderWidth: 0,
+                borderColor: 'rgba(0,0,0,0)',
+              },
+            },
+          });
+        }
+        return dataArr;
+      }
+
+      function _pie2() {
+        let dataArr: any = [];
+        for (var i = 0; i < 8; i++) {
+          if (i % 2 === 0) {
+            dataArr.push({
+              name: (i + 1).toString(),
+              value: 25,
+              itemStyle: {
+                normal: {
+                  color: 'rgba(88,142,197,0.5)',
+                  borderWidth: 0,
+                  borderColor: 'rgba(0,0,0,0)',
+                },
+              },
+            });
+          } else {
+            dataArr.push({
+              name: (i + 1).toString(),
+              value: 20,
+              itemStyle: {
+                normal: {
+                  color: 'rgba(0,0,0,0)',
+                  borderWidth: 0,
+                  borderColor: 'rgba(0,0,0,0)',
+                },
+              },
+            });
+          }
+        }
+        return dataArr;
+      }
+
+      function _pie3() {
+        let dataArr: any = [];
+        for (var i = 0; i < 100; i++) {
+          if (i % 2 === 0) {
+            dataArr.push({
+              name: (i + 1).toString(),
+              value: 25,
+              itemStyle: {
+                normal: {
+                  color: 'rgb(126,190,255)',
+                  borderWidth: 0,
+                  borderColor: 'rgba(0,0,0,0)',
+                },
+              },
+            });
+          } else {
+            dataArr.push({
+              name: (i + 1).toString(),
+              value: 20,
+              itemStyle: {
+                normal: {
+                  color: 'rgba(0,0,0,0)',
+                  borderWidth: 0,
+                  borderColor: 'rgba(0,0,0,0)',
+                },
+              },
+            });
+          }
+        }
+        return dataArr;
+      }
+
+      function _pieData(data) {
+        let _data = data;
+        let dataArr: any = [];
+        for (var i = 0; i < 5; i++) {
+          if (i === 2) {
+            let dt = data[0].unit ? 25 : Number(data[0].value);
+            dataArr.push({
+              name: (i + 1).toString(),
+              value: dt,
+              itemStyle: {
+                normal: {
+                  color: new echarts.graphic.LinearGradient(0, 1, 1, 0, [
+                    {
+                      offset: 0,
+                      color: 'rgb(147,187,216)',
+                    },
+                    {
+                      offset: 1,
+                      color: '#588ec5',
+                    },
+                  ]),
+                  borderWidth: 0,
+                  borderColor: 'rgba(0,0,0,0.4)',
+                },
+              },
+            });
+          } else {
+            let dta = data[0].unit ? 25 : (1 - Number(data[0].value)) / 4;
+            dataArr.push({
+              name: (i + 1).toString(),
+              value: dta,
+              itemStyle: {
+                normal: {
+                  color: 'rgba(0,0,0,0)',
+                  borderWidth: 0,
+                  borderColor: 'rgba(0,0,0,0)',
+                },
+              },
+            });
+          }
+        }
+        //console.log(dataArr)
+        return dataArr;
+      }
+
+      const option = <EChartsOption>reactive({
+        backgroundColor: '#0f375f',
+        title: [
+          {
+            text: '各渠道投诉占比',
+            left: '2%',
+            top: '1%',
+            textStyle: {
+              color: '#fff',
+              fontSize: 14,
+            },
+          },
+        ],
+        // tooltip: {
+        //   // formatter: '{b} ({c})',
+        // },
+        series: [
+          {
+            type: 'pie',
+            zlevel: 1,
+            silent: true,
+            /*
+                radius
+                饼图的半径。可以为如下类型:
+                number:直接指定外半径值。
+                string:例如,'20%',表示外半径为可视区尺寸(容器高宽中较小一项)的 20% 长度。
+                Array.<number|string>:数组的第一项是内半径,第二项是外半径。每一项遵从上述 number string 的描述。
+                */
+            radius: ['98%', '97%'],
+            animation: false,
+            color: 'rgba(88,142,197,0.5)',
+
+            label: {
+              show: false,
+            },
+            labelLine: {
+              show: false,
+            },
+            // data: _pie1(),
+            data: [1],
+          },
+          {
+            type: 'pie',
+            zlevel: 2,
+            silent: true,
+            radius: ['90%', '91%'],
+            startAngle: 50,
+            animation: false,
+            label: {
+              show: false,
+            },
+            labelLine: {
+              show: false,
+            },
+            data: _pie2(),
+          },
+          {
+            type: 'pie',
+            zlevel: 3,
+            silent: true,
+            radius: ['88%', '87%'],
+            label: {
+              show: false,
+            },
+            labelLine: {
+              show: false,
+            },
+            data: _pie2(),
+          },
+          {
+            type: 'pie',
+            zlevel: 4,
+            silent: true,
+            radius: ['84%', '83%'],
+            label: {
+              show: false,
+            },
+            labelLine: {
+              show: false,
+            },
+            data: _pie3(),
+          },
+          {
+            type: 'pie',
+            zlevel: 5,
+            silent: true,
+            radius: ['80%', '78%'],
+            color: ['#fc8d89', '#46d3f3', 'rgba(203,203,203,.2)'],
+            startAngle: 50,
+            animation: false,
+            label: {
+              show: false,
+            },
+            data: [50, 20, 40],
+          },
+          {
+            name: '',
+            type: 'gauge',
+            splitNumber: 30, //刻度数量
+            min: 0,
+            max: 100,
+            radius: '73%', //图表尺寸
+            center: ['50%', '50%'],
+            startAngle: 90,
+            endAngle: -269.9999,
+            axisLine: {
+              show: false,
+              lineStyle: {
+                width: 0,
+                shadowBlur: 0,
+                color: [[1, '#0dc2fe']],
               },
             },
-            {
-              text: '投诉原因TOP10',
-              left: '40%',
-              top: '1%',
-              textStyle: {
-                color: '#fff',
-                fontSize: 14,
+            axisTick: {
+              show: false,
+              lineStyle: {
+                color: 'auto',
+                width: 2,
               },
+              length: 20,
+              splitNumber: 5,
             },
-            {
-              text: '各级别投诉占比',
-              left: '2%',
-              top: '50%',
-              textStyle: {
-                color: '#fff',
-                fontSize: 14,
+            splitLine: {
+              show: true,
+              length: 32,
+              lineStyle: {
+                color: 'auto',
               },
             },
-          ],
-          grid: [{ left: '50%', top: '7%', width: '45%', height: '90%' }],
-          tooltip: {
-            formatter: '{b} ({c})',
+            axisLabel: {
+              show: false,
+            },
+            pointer: {
+              //仪表盘指针
+              show: false,
+            },
+            detail: {
+              show: false,
+            },
           },
-          xAxis: [
-            {
-              gridIndex: 0,
-              axisTick: { show: false },
-              axisLabel: { show: false },
-              splitLine: { show: false },
-              axisLine: { show: false },
-            },
-          ],
-          yAxis: [
-            {
-              gridIndex: 0,
-              interval: 0,
-              data: yAxisData.reverse(),
-              axisTick: { show: false },
-              axisLabel: { show: true },
-              splitLine: { show: false },
-              axisLine: { show: true, lineStyle: { color: '#6173a3' } },
-            },
-          ],
-          series: [
-            {
-              name: '各渠道投诉占比',
-              type: 'pie',
-              radius: '30%',
-              center: ['22%', '25%'],
-              data: [
-                { value: 335, name: '客服电话' },
-                { value: 310, name: '奥迪官网' },
-                { value: 234, name: '媒体曝光' },
-                { value: 135, name: '质检总局' },
-                { value: 105, name: '其他' },
-              ],
-              labelLine: { show: false },
-              label: {
-                show: true,
-                formatter: '{b} \n ({d}%)',
-                color: '#B1B9D3',
+          {
+            name: '统计',
+            type: 'gauge',
+            splitNumber: 30, //刻度数量
+            min: 0,
+            max: 100,
+            radius: '68%', //图表尺寸
+            center: ['50%', '50%'],
+            startAngle: 90,
+            endAngle: -269.9999,
+            axisLine: {
+              show: true,
+              lineStyle: {
+                width: 0,
+                shadowBlur: 0,
+                color: [
+                  [0, '#0dc2fe'],
+                  [1, '#0dc2fe'],
+                ],
+              },
+            },
+            axisTick: {
+              show: true,
+              lineStyle: {
+                color: '#0dc2fe',
+                width: 2,
               },
+              length: 20,
+              splitNumber: 5,
+            },
+            splitLine: {
+              show: true,
+              length: 20,
+              lineStyle: {
+                color: '#0dc2fe',
+              },
+            },
+            axisLabel: {
+              show: false,
+            },
+            pointer: {
+              //仪表盘指针
+              show: false,
+            },
+            detail: {
+              borderColor: '#fff',
+              shadowColor: '#fff', //默认透明
+              shadowBlur: 2,
+              offsetCenter: [0, '0%'], // x, y,单位px
+              textBorderColor: '#fff',
+              fontSize: 50,
+              formatter: '{value}',
+            },
+            data: [
+              {
+                name: '',
+                value: 1,
+                detail: {
+                  color: '#ffffff99',
+                },
+              },
+            ],
+          },
+          {
+            type: 'pie',
+            zlevel: 20,
+            silent: true,
+            radius: ['60%', '59%'],
+            animation: false,
+            color: '#2dc0c9',
+            // color: '#fff',
+            // animation:false,
+            data: [1],
+            labelLine: {
+              show: false,
             },
-            {
-              name: '各级别投诉占比',
-              type: 'pie',
-              radius: '30%',
-              center: ['22%', '75%'],
-              labelLine: { show: false },
-              data: [
-                { value: 335, name: 'A级' },
-                { value: 310, name: 'B级' },
-                { value: 234, name: 'C级' },
-                { value: 135, name: 'D级' },
-              ],
+          },
+          {
+            name: '中间环形图',
+            type: 'pie',
+            radius: ['35%', '55%'],
+            avoidLabelOverlap: false,
+            animation: false,
+            itemStyle: {
+              color: '#80ADD2',
+              borderColor: '#3D4268',
+            },
+            label: {
+              show: false,
+              position: 'center',
+            },
+            emphasis: {
               label: {
                 show: true,
-                formatter: '{b} \n ({d}%)',
-                color: '#B1B9D3',
+                fontSize: '30',
+                fontWeight: 'bold',
               },
             },
-            {
-              name: '投诉原因TOP10',
-              type: 'bar',
-              xAxisIndex: 0,
-              yAxisIndex: 0,
-              barWidth: '45%',
-              itemStyle: { color: '#86c9f4' },
-              label: { show: true, position: 'right', color: '#9EA7C4' },
-              data: dataAll.sort(),
-            },
-          ],
+            labelLine: {
+              show: false,
+            },
+            data: [25, 25, 25, 25, 25, 25],
+          },
+        ],
+      });
+
+      function doing() {
+        // option.series[4].startAngle = option.series[4].startAngle - 1;
+        // option.series[6].data[0].value = option.series[6].data[0].value + 1;
+        const start = props.count - option.series[6].data[0].value;
+        let step = 1;
+        if (start / 1 < 10) {
+          step = 1;
+        } else if (start / 1 < 100) {
+          step = 10;
+        } else if (start / 1 < 1000) {
+          step = 100;
+        } else if (start / 1 < 10000) {
+          step = 1000;
+        }
+
+        let timer = setInterval(() => {
+          if (props.count > option.series[6].data[0].value) {
+            option.series[6].data[0].value += step;
+            setOptions(option);
+          } else {
+            clearInterval(timer);
+            timer = null;
+            routeTimer.value = setInterval(() => {
+              rotate();
+            }, 200);
+          }
+        }, 8);
+      }
+
+      function rotate() {
+        // option.series[4].startAngle = option.series[4].startAngle - 5;
+        // option.series[6].data[0].value = option.series[6].data[0].value + 5;
+        // setOptions(option);
+      }
+
+      // const timer = <Ref<NodeJS.Timer>>ref();
+      // const startTimer = () => {
+      //   timer.value = setInterval(doing, 1000);
+      // };
+
+      // const stopTimer = () => {
+      //   clearInterval(timer.value);
+      //   timer.value = null as unknown as NodeJS.Timer;
+      // };
+
+      onMounted(() => {
+        setOptions(option);
+        chartRef.value?.addEventListener('mouseover', () => {
+          // startTimer();
+          // doing();
+        });
+        chartRef.value?.addEventListener('mouseout', () => {
+          // stopTimer();
         });
+        doing();
+      });
+      onUnmounted(() => {
+        clearInterval(routeTimer.value);
+        routeTimer.value = null;
       });
       return { chartRef };
     },

+ 135 - 0
src/views/demo/charts/Pie1.vue

@@ -0,0 +1,135 @@
+<template>
+  <div ref="chartRef" :style="{ height, width }"></div>
+</template>
+<script lang="ts">
+  import { defineComponent, PropType, ref, Ref, onMounted } from 'vue';
+
+  import { useECharts } from '/@/hooks/web/useECharts';
+
+  export default defineComponent({
+    props: {
+      width: {
+        type: String as PropType<string>,
+        default: '100%',
+      },
+      height: {
+        type: String as PropType<string>,
+        default: 'calc(100vh - 78px)',
+      },
+    },
+    setup() {
+      const chartRef = ref<HTMLDivElement | null>(null);
+      const { setOptions } = useECharts(chartRef as Ref<HTMLDivElement>);
+      const dataAll = [389, 259, 262, 324, 232, 176, 196, 214, 133, 370];
+      const yAxisData = ['原因1', '原因2', '原因3', '原因4', '原因5', '原因6', '原因7', '原因8', '原因9', '原因10'];
+      onMounted(() => {
+        setOptions({
+          backgroundColor: '#0f375f',
+          title: [
+            {
+              text: '各渠道投诉占比',
+              left: '2%',
+              top: '1%',
+              textStyle: {
+                color: '#fff',
+                fontSize: 14,
+              },
+            },
+            {
+              text: '投诉原因TOP10',
+              left: '40%',
+              top: '1%',
+              textStyle: {
+                color: '#fff',
+                fontSize: 14,
+              },
+            },
+            {
+              text: '各级别投诉占比',
+              left: '2%',
+              top: '50%',
+              textStyle: {
+                color: '#fff',
+                fontSize: 14,
+              },
+            },
+          ],
+          grid: [{ left: '50%', top: '7%', width: '45%', height: '90%' }],
+          tooltip: {
+            formatter: '{b} ({c})',
+          },
+          xAxis: [
+            {
+              gridIndex: 0,
+              axisTick: { show: false },
+              axisLabel: { show: false },
+              splitLine: { show: false },
+              axisLine: { show: false },
+            },
+          ],
+          yAxis: [
+            {
+              gridIndex: 0,
+              interval: 0,
+              data: yAxisData.reverse(),
+              axisTick: { show: false },
+              axisLabel: { show: true },
+              splitLine: { show: false },
+              axisLine: { show: true, lineStyle: { color: '#6173a3' } },
+            },
+          ],
+          series: [
+            {
+              name: '各渠道投诉占比',
+              type: 'pie',
+              radius: '30%',
+              center: ['22%', '25%'],
+              data: [
+                { value: 335, name: '客服电话' },
+                { value: 310, name: '奥迪官网' },
+                { value: 234, name: '媒体曝光' },
+                { value: 135, name: '质检总局' },
+                { value: 105, name: '其他' },
+              ],
+              labelLine: { show: false },
+              label: {
+                show: true,
+                formatter: '{b} \n ({d}%)',
+                color: '#B1B9D3',
+              },
+            },
+            {
+              name: '各级别投诉占比',
+              type: 'pie',
+              radius: '30%',
+              center: ['22%', '75%'],
+              labelLine: { show: false },
+              data: [
+                { value: 335, name: 'A级' },
+                { value: 310, name: 'B级' },
+                { value: 234, name: 'C级' },
+                { value: 135, name: 'D级' },
+              ],
+              label: {
+                show: true,
+                formatter: '{b} \n ({d}%)',
+                color: '#B1B9D3',
+              },
+            },
+            {
+              name: '投诉原因TOP10',
+              type: 'bar',
+              xAxisIndex: 0,
+              yAxisIndex: 0,
+              barWidth: '45%',
+              itemStyle: { color: '#86c9f4' },
+              label: { show: true, position: 'right', color: '#9EA7C4' },
+              data: dataAll.sort(),
+            },
+          ],
+        });
+      });
+      return { chartRef };
+    },
+  });
+</script>

+ 432 - 0
src/views/demo/charts/PieAnimation.ts

@@ -0,0 +1,432 @@
+option = {
+    backgroundColor: '#142468',
+    title:{
+          //text: '实时旋转饼图' 
+    },
+    series: [
+        {
+            type: 'pie',
+            zlevel: 1,
+            silent: true,
+            /*
+            radius
+            饼图的半径。可以为如下类型:
+            number:直接指定外半径值。
+            string:例如,'20%',表示外半径为可视区尺寸(容器高宽中较小一项)的 20% 长度。
+            Array.<number|string>:数组的第一项是内半径,第二项是外半径。每一项遵从上述 number string 的描述。
+            */
+            radius: ['98%', '97%'],
+            hoverAnimation: false,
+            color: "rgba(88,142,197,0.5)",
+            // animation:false,    //charts3 no
+            label: {
+                normal: {
+                    show: false
+                },
+            },
+            labelLine: {
+                normal: {
+                    show: false
+                }
+            },
+            data: [1]
+        },
+        {
+            type: 'pie',
+            zlevel: 2,
+            silent: true,
+            radius: ['90%', '91%'],
+            startAngle: 50,
+            hoverAnimation: false,
+            // animation:false,    //charts3 no
+            label: {
+                normal: {
+                    show: false
+                },
+            },
+            labelLine: {
+                normal: {
+                    show: false
+                }
+            },
+            data: _pie2()
+        },
+        {
+            type: 'pie',
+            zlevel: 3,
+            silent: true,
+            radius: ['88%', '87%'],
+            label: {
+                normal: {
+                    show: false
+                },
+            },
+            labelLine: {
+                normal: {
+                    show: false
+                }
+            },
+            data: _pie2()
+        },
+        {
+            type: 'pie',
+            zlevel: 4,
+            silent: true,
+            radius: ['84%', '83%'],
+            label: {
+                normal: {
+                    show: false
+                },
+            },
+            labelLine: {
+                normal: {
+                    show: false
+                }
+            },
+            data: _pie3()
+        },
+        {
+            type: 'pie',
+            zlevel: 5,
+            silent: true,
+            radius: ['80%', '78%'],
+            color: ["#fc8d89", "#46d3f3", "rgba(203,203,203,.2)"],
+            startAngle: 50,
+            hoverAnimation: false,
+            // animation:false,    //charts3 no
+            label: {
+                normal: {
+                    show: false
+                },
+            },
+            data: [50, 20, 40]
+        },
+        {
+            name: "",
+            type: 'gauge',
+            splitNumber: 30, //刻度数量
+            min: 0,
+            max: 100,
+            radius: '73%', //图表尺寸
+            center: ['50%', '50%'],
+            startAngle: 90,
+            endAngle: -269.9999,
+            axisLine: {
+                show: false,
+                lineStyle: {
+                    width: 0,
+                    shadowBlur: 0,
+                    color: [
+                        [1, '#0dc2fe']
+                    ]
+                }
+            },
+            axisTick: {
+                show: false,
+                lineStyle: {
+                    color: 'auto',
+                    width: 2
+                },
+                length: 20,
+                splitNumber: 5
+            },
+            splitLine: {
+                show: true,
+                length: 32,
+                lineStyle: {
+                    color: 'auto',
+                }
+            },
+            axisLabel: {
+                show: false
+            },
+            pointer: { //仪表盘指针
+                show: 0,
+            },
+            detail: {
+                show: 0,
+            },
+        },
+        {
+            name: '统计',
+            type: 'gauge',
+            splitNumber: 30, //刻度数量
+            min: 0,
+            max: 100,
+            radius: '68%', //图表尺寸
+            center: ['50%', '50%'],
+            startAngle: 90,
+            endAngle: -269.9999,
+            axisLine: {
+                show: true,
+                lineStyle: {
+                    width: 0,
+                    shadowBlur: 0,
+                    color: [
+                        [0, '#0dc2fe'],
+                        [1, '#0dc2fe']
+                    ]
+                }
+            },
+            axisTick: {
+                show: true,
+                lineStyle: {
+                    color: '#0dc2fe',
+                    width: 2
+                },
+                length: 20,
+                splitNumber: 5
+            },
+            splitLine: {
+                show: true,
+                length: 20,
+                lineStyle: {
+                    color: '#0dc2fe',
+                }
+            },
+            axisLabel: {
+                show: false
+            },
+            pointer: { //仪表盘指针
+                show: 0,
+            },
+            detail: {
+                borderColor: '#fff',
+                shadowColor: '#fff', //默认透明
+                shadowBlur: 2,
+                offsetCenter: [0, '0%'], // x, y,单位px
+                textStyle: { // 其余属性默认使用全局文本样式,详见TEXTSTYLE
+                    color: '#fff',
+                    fontSize: 50,
+                },
+                formatter: '{value}'
+            },
+            data: [{
+                name: "",
+                value: 2020
+            }]
+        },
+        {
+            type: 'pie',
+            zlevel: 20,
+            silent: true,
+            radius: ['60%', '59%'],
+            hoverAnimation: false,
+            color: '#2dc0c9',
+            // animation:false,
+            data: [1],
+            labelLine: {
+                normal: {
+                    show: false
+                }
+            }
+        },
+        {
+            name: '中间环形图',
+            type: 'pie',
+            radius: ['35%', '55%'],
+            avoidLabelOverlap: false,
+            hoverAnimation: false,
+            itemStyle: {
+                normal: {
+                    color: '#80ADD2',
+                    borderColor: '#3D4268',
+                }
+            },
+            label: {
+                normal: {
+                    show: false,
+                    position: 'center',
+ 
+                },
+                emphasis: {
+                    show: true,
+                    textStyle: {
+                        fontSize: '30',
+                        fontWeight: 'bold'
+                    }
+                }
+            },
+            labelLine: {
+                normal: {
+                    show: false
+                }
+            },
+            data: [
+                25, 25, 25, 25, 25, 25
+            ]
+        },
+    ]
+};
+ 
+function _pie1() {
+    let dataArr = [];
+    for (var i = 0; i < 8; i++) {
+ 
+        dataArr.push({
+            name: (i + 1).toString(),
+            value: 20,
+            itemStyle: {
+                normal: {
+                    color: "rgba(88,142,197,0.4)",
+                    borderWidth: 0,
+                    borderColor: "rgba(0,0,0,0)"
+                }
+            }
+        })
+ 
+    }
+    return dataArr
+ 
+}
+ 
+function _pie2() {
+    let dataArr = [];
+    for (var i = 0; i < 8; i++) {
+        if (i % 2 === 0) {
+            dataArr.push({
+                name: (i + 1).toString(),
+                value: 25,
+                itemStyle: {
+                    normal: {
+                        color: "rgba(88,142,197,0.5)",
+                        borderWidth: 0,
+                        borderColor: "rgba(0,0,0,0)"
+                    }
+                }
+            })
+        } else {
+            dataArr.push({
+                name: (i + 1).toString(),
+                value: 20,
+                itemStyle: {
+                    normal: {
+                        color: "rgba(0,0,0,0)",
+                        borderWidth: 0,
+                        borderColor: "rgba(0,0,0,0)"
+                    }
+                }
+            })
+        }
+ 
+    }
+    return dataArr
+ 
+}
+ 
+function _pie3() {
+    let dataArr = [];
+    for (var i = 0; i < 100; i++) {
+        if (i % 2 === 0) {
+            dataArr.push({
+                name: (i + 1).toString(),
+                value: 25,
+                itemStyle: {
+                    normal: {
+                        color: "rgb(126,190,255)",
+                        borderWidth: 0,
+                        borderColor: "rgba(0,0,0,0)"
+                    }
+                }
+            })
+        } else {
+            dataArr.push({
+                name: (i + 1).toString(),
+                value: 20,
+                itemStyle: {
+                    normal: {
+                        color: "rgba(0,0,0,0)",
+                        borderWidth: 0,
+                        borderColor: "rgba(0,0,0,0)"
+                    }
+                }
+            })
+        }
+ 
+    }
+    return dataArr
+ 
+}
+ 
+function _pieData(data) {
+    let _data = data;
+    let dataArr = [];
+    for (var i = 0; i < 5; i++) {
+        if (i === 2) {
+            let dt = (data[0].unit) ? 25 : (Number(data[0].value));
+            dataArr.push({
+                name: (i + 1).toString(),
+                value: dt,
+                itemStyle: {
+                    normal: {
+ 
+                        color: new echarts.graphic.LinearGradient(0, 1, 1, 0, [{
+                            offset: 0,
+                            color: 'rgb(147,187,216)'
+                        }, {
+                            offset: 1,
+                            color: '#588ec5'
+                        }]),
+                        borderWidth: 0,
+                        borderColor: "rgba(0,0,0,0.4)"
+ 
+                    }
+                }
+            })
+        } else {
+            let dta = (data[0].unit) ? 25 : (1 - Number(data[0].value)) / 4;
+            dataArr.push({
+                name: (i + 1).toString(),
+                value: dta,
+                itemStyle: {
+                    normal: {
+                        color: "rgba(0,0,0,0)",
+                        borderWidth: 0,
+                        borderColor: "rgba(0,0,0,0)"
+                    }
+                }
+            })
+        }
+ 
+    }
+    //console.log(dataArr)
+    return dataArr
+}
+ 
+ 
+//鼠标事件:'click','dblclick','mousedown','mouseup','mouseover','mouseout','globalout'。
+ 
+myChart.on('mouseover', function(params) {
+    stopTimer();
+});
+ 
+myChart.on('mouseout', function(params) {
+    startTimer();
+});
+ 
+ 
+var timer;
+ 
+function doing() {
+    let option = myChart.getOption();
+    option.series[3].startAngle = option.series[3].startAngle - 1;
+    option.series[6].data[0].value = option.series[6].data[0].value + 1;
+    myChart.setOption(option);
+ 
+}
+ 
+function startTimer() {
+ 
+    timer = setInterval(doing, 100);
+ 
+}
+ 
+function stopTimer() {
+ 
+    clearInterval(timer);
+ 
+    xzTimer = null;
+ 
+}
+ 
+setTimeout(startTimer, 500);

+ 325 - 0
src/views/demo/threejs/damper.vue

@@ -0,0 +1,325 @@
+<template>
+  <div style="width: 100%; height: calc(100vh - 200px); display: flex; justify-content: center; align-items: center">
+    <a-spin :spinning="loading" />
+    <div id="damper3D" v-show="!loading"> </div>
+  </div>
+</template>
+
+<script setup lang="ts">
+  import { ref, onMounted, onUnmounted } from 'vue';
+  import UseThree from '/@/hooks/core/useThree';
+  import * as THREE from 'three';
+  import gsap from 'gsap';
+
+  const loading = ref(false);
+  let model;
+
+
+  const setCustomMaterial = (group)=>{
+    const cityMaterial = model.track(new THREE.MeshBasicMaterial({
+      color: new THREE.Color(0x0c016f),
+    }));
+    group?.traverse((item: THREE.Mesh) => {
+      if (item.type === 'Mesh') {
+        item.material = cityMaterial;
+        setMaterial(item);
+      }
+    });
+  }
+
+  /* 自定义材质 */
+  const setMaterial = (obj) => {
+    obj.geometry.computeBoundingBox();
+    const { max, min } = obj.geometry.boundingBox;
+    const distance = max.y - min.y + 2;
+
+    obj.material.onBeforeCompile = (shader) => {
+      shader.uniforms.uDistance = {
+        value: distance,
+      };
+      shader.uniforms.uTopColor = {
+        value: new THREE.Color(0xaaaeff),
+      };
+      addGrad(shader);
+      addRadiusSpread(shader);
+      addLineSpread(shader);
+      addToSpread(shader);
+    };
+  }
+  /* 自定义着色器 */
+  const addGrad = (shader) => {
+    shader.vertexShader = shader.vertexShader.replace(
+      '#include <common>',
+      `
+
+      #include <common>
+      varying vec3 vPosition;
+    `
+    );
+    shader.vertexShader = shader.vertexShader.replace(
+      '#include <fog_vertex>',
+      `
+
+      #include <fog_vertex>
+      vPosition = position;
+     `
+    );
+    shader.fragmentShader = shader.fragmentShader.replace(
+      '#include <common>',
+      `
+      #include <common>
+      uniform vec3 uTopColor;
+      uniform float uDistance;
+      varying vec3 vPosition;
+  
+      `
+    );
+
+    shader.fragmentShader = shader.fragmentShader.replace(
+      '#include <dithering_fragment>',
+      `
+      #include <dithering_fragment>
+
+      vec4 distGradColor = gl_FragColor;
+      // 设置混合的百分比
+      float gradMix = (vPosition.y + (uDistance / 2.0)) / uDistance / 5.0;
+      // 设置混合颜色
+      vec3 colorMix = mix(distGradColor.xyz, uTopColor, gradMix);
+      gl_FragColor = vec4(colorMix, 1.0);
+      //#end#
+      `
+    );
+  }
+  /* 自定义着色器 */
+  const addRadiusSpread = (shader) => {
+    // 设置扩散中心店
+    shader.uniforms.uRadiusSpreadCenter = {
+      value: new THREE.Vector2(0, 0),
+    };
+    // 扩散时间
+    shader.uniforms.uRadiusSpreadTime = {
+      value: 0,
+    };
+
+    shader.uniforms.uRadiusSpreadWidth = {
+      value: 20,
+    };
+    shader.fragmentShader = shader.fragmentShader.replace(
+      '#include <common>',
+      `
+      #include <common>
+      uniform vec2 uRadiusSpreadCenter;
+      uniform float uRadiusSpreadTime;
+      uniform float uRadiusSpreadWidth;
+
+      `
+    );
+
+    shader.fragmentShader = shader.fragmentShader.replace(
+      '//#end#',
+      `
+      float spreadRadius = distance(vPosition.xz + vec2(-720, -550), uRadiusSpreadCenter);
+      // 扩散范围
+      float spreadIndex = -(spreadRadius - uRadiusSpreadTime) * (spreadRadius - uRadiusSpreadTime ) + uRadiusSpreadWidth;
+      if(spreadIndex > 0.0){
+        gl_FragColor = mix(gl_FragColor, vec4(1.0, 1.0, 1.0, 1.0), spreadIndex / uRadiusSpreadWidth );
+      }
+      //#end#
+      `
+    );
+    gsap.to(shader.uniforms.uRadiusSpreadTime, {
+      value: 250,
+      duration: 1,
+      ease: 'none',
+      repeat: -1,
+    });
+  }
+  /* 自定义着色器 */
+  const addLineSpread = (shader)=>{
+    // 设置扩散中心店
+    shader.uniforms.uLineSpreadCenter = {
+      value: new THREE.Vector2(0, 0),
+    };
+    // 扩散时间
+    shader.uniforms.uLineSpreadTime = {
+      value: 0,
+    };
+
+    shader.uniforms.uLineSpreadWidth = {
+      value: 20,
+    };
+    shader.fragmentShader = shader.fragmentShader.replace(
+      '#include <common>',
+      `
+      #include <common>
+      uniform vec2 uLineSpreadCenter;
+      uniform float uLineSpreadTime;
+      uniform float uLineSpreadWidth;
+
+      `
+    );
+
+    shader.fragmentShader = shader.fragmentShader.replace(
+      '//#end#',
+      `
+      float spreadLine = vPosition.x - 400.0;
+      // 扩散范围
+      float spreadLineIndex = -(spreadLine - uLineSpreadTime * 5.0) * (spreadLine - uLineSpreadTime * 5.0) + uLineSpreadWidth;
+      if(spreadLineIndex > 0.0){
+        gl_FragColor = mix(gl_FragColor, vec4(1.0, 1.0, 1.0, 1.0), spreadLineIndex / uLineSpreadWidth );
+      }
+      //#end#
+      `
+    );
+    gsap.to(shader.uniforms.uLineSpreadTime, {
+      value: 150,
+      duration: 2,
+      ease: 'none',
+      repeat: -1,
+    });
+  }
+  /* 自定义着色器 */
+  const addToSpread = (shader) => {
+    // 设置扩散中心店
+    shader.uniforms.uTopSpreadCenter = {
+      value: new THREE.Vector2(0, 0),
+    };
+    // 扩散时间
+    shader.uniforms.uTopSpreadTime = {
+      value: 0,
+    };
+
+    shader.uniforms.uTopSpreadWidth = {
+      value: 20,
+    };
+    shader.fragmentShader = shader.fragmentShader.replace(
+      '#include <common>',
+      `
+      #include <common>
+      uniform vec2 uTopSpreadCenter;
+      uniform float uTopSpreadTime;
+      uniform float uTopSpreadWidth;
+
+      `
+    );
+
+    shader.fragmentShader = shader.fragmentShader.replace(
+      '//#end#',
+      `
+      float spreadTopLine = vPosition.y * 2.0;
+      // 扩散范围uTopSpreadWidth
+      float spreadTopIndex = -(spreadTopLine - uTopSpreadTime) * (spreadTopLine - uTopSpreadTime) + uTopSpreadWidth;
+      if(spreadTopIndex > 0.0){
+        gl_FragColor = mix(gl_FragColor, vec4(0.0, 1.0, 1.0, 1.0), spreadTopIndex / uTopSpreadWidth / uTopSpreadTime * 2.0);
+      }
+      //#end#
+      `
+    );
+    gsap.to(shader.uniforms.uTopSpreadTime, {
+      value: 200,
+      duration: 5,
+      ease: 'none',
+      repeat: -1,
+    });
+  }
+
+  /* 模型线框 */
+  const addLine = (geometry)=>{
+    const edges = new THREE.EdgesGeometry(geometry);
+    const line = new THREE.LineSegments(edges, new THREE.LineBasicMaterial({ color: 0xff0000 }));
+    (model.scene as THREE.Scene).add(line);
+  }
+
+  const addLight = (scene) => {
+
+    const light = new THREE.PointLight( 0xffffff, 1, 1000 );
+    light.position.set( 50, 10, 50 );
+    // scene.add( light );
+
+    const pointLight = new THREE.PointLight( 0xffffff, 1, 200 );
+    pointLight.position.set(0, 0, 10)
+    // scene.add( pointLight );
+    
+    const pointLight2 = new THREE.PointLight( 0xffffff, 1, 100 );
+    pointLight2.position.set(0, 3, 10)
+    // light2.castShadow = true
+    pointLight2.shadow.bias = 0.05
+    scene.add(pointLight2)
+    // const pointLightHelper2 = new THREE.PointLightHelper( pointLight2, 1 );
+    // scene.add( pointLightHelper2 );
+
+    const pointLight3 = new THREE.PointLight( 0xffffff, 1, 100 );
+    pointLight3.position.set(-80, 3, 10)
+    // light2.castShadow = true
+    pointLight3.shadow.bias = 0.05
+    scene.add(pointLight3)
+    // const pointLightHelper = new THREE.PointLightHelper( pointLight3, 1 );
+    // scene.add( pointLightHelper );
+
+    const pointLight4 = new THREE.PointLight( 0xffffff, 1, 100 );
+    pointLight4.position.set(37, 3, 10)
+    // light2.castShadow = true
+    pointLight4.shadow.bias = 0.05
+    scene.add(pointLight4)
+    // const pointLightHelper4 = new THREE.PointLightHelper( pointLight4, 1 );
+    // scene.add( pointLightHelper4 );
+
+    const pointLight5 = new THREE.PointLight( 0xffffff, 1, 100 );
+    pointLight5.position.set(100, 3, 10)
+    // light2.castShadow = true
+    pointLight5.shadow.bias = 0.05
+    scene.add(pointLight5)
+    // const pointLightHelper5 = new THREE.PointLightHelper( pointLight5, 1 );
+    // scene.add( pointLightHelper5 );
+
+ 
+    pointLight2.shadow.mapSize.width = 10; // default
+    pointLight2.shadow.mapSize.height = 10; // default
+    pointLight2.shadow.camera.near = -0.0000001; // default
+    pointLight2.shadow.camera.far = 20; // default
+  
+    const spotLight = new THREE.SpotLight();
+    spotLight.angle = Math.PI / 16;
+    spotLight.penumbra = 0;
+    spotLight.castShadow = true;
+    spotLight.intensity = 2
+    spotLight.position.set( -400, 400, 400 );
+    scene.add( spotLight );
+
+    spotLight.shadow.mapSize.width = 2000;  // default
+    spotLight.shadow.mapSize.height = 1000; // default
+    spotLight.shadow.camera.near = 0.5;    // default
+    spotLight.shadow.camera.far = 800      // default
+    spotLight.shadow.focus = 1; 
+    spotLight.shadow.bias = -0.000001
+
+  }
+  const resetCamera = () => {
+    model.camera.position.setZ(200)
+    model.camera.position.setY(120)
+    model.camera?.lookAt( -100, 0.1, 50 );
+    model.camera.updateProjectionMatrix();
+    model.orbitControls.update()
+  }
+
+  onMounted(() => {
+    model = new UseThree('#damper3D');
+    model.setEnvMap('test1');
+    model.setCustomMaterial = setCustomMaterial
+    loading.value = true;
+    model.setModel('fm-n-processed').then(() => {
+      addLight(model.scene)
+      resetCamera()
+      // 模型加载成功
+      loading.value = false;
+    });
+  });
+
+  onUnmounted(() => {
+    if(model){
+      model.deleteModal()
+    }
+  })
+
+</script>
+<style scoped lang="scss"></style>

+ 1 - 1
src/views/sys/login/LoginForm.vue

@@ -84,7 +84,7 @@
   import { reactive, ref, toRaw, unref, computed, onMounted } from 'vue';
 
   import { Checkbox, Form, Input, Row, Col, Button, Divider } from 'ant-design-vue';
-  import { GithubFilled, WechatFilled, DingtalkCircleFilled, QuestionCircleFilled, createFromIconfontCN } from '@ant-design/icons-vue';
+  import { GithubFilled, WechatFilled, DingtalkCircleFilled, createFromIconfontCN } from '@ant-design/icons-vue';
   import LoginFormTitle from './LoginFormTitle.vue';
   import ThirdModal from './ThirdModal.vue';
   import { useI18n } from '/@/hooks/web/useI18n';

+ 6 - 0
src/views/sys/micro/index.vue

@@ -0,0 +1,6 @@
+<template>
+  <div>222</div>
+</template>
+<script lang="ts" setup>
+  name: '';
+</script>

+ 156 - 0
src/views/vent/comment/EditRowTable.vue

@@ -0,0 +1,156 @@
+<template>
+  <div class="p-4">
+    <a-button v-if="isAdd" type="primary" @click="addRow"> 新增 </a-button>
+    <BasicTable @register="registerTable" @edit-change="onEditChange">
+      <template #action="{ record, column }">
+        <TableAction :actions="createActions(record, column)" />
+      </template>
+    </BasicTable>
+  </div>
+</template>
+<script lang="ts">
+  import { defineComponent, ref, nextTick, inject } from 'vue';
+  import { BasicTable, useTable, TableAction, BasicColumn, ActionItem, EditRecordRow } from '/@/components/Table';
+  import { useMessage } from '/@/hooks/web/useMessage';
+  // import { nextTick } from 'process';
+
+  export default defineComponent({
+    components: { BasicTable, TableAction },
+    props: {
+      columns: {
+        type: Array,
+        requried: true,
+      },
+      list: {
+        type: Function,
+        requried: true,
+      },
+      isAdd: {
+        type: Boolean,
+      },
+    },
+    emits: ['saveOrUpdate', 'deleteById'],
+    setup(props, { emit }) {
+      const deviceType = inject('deviceType');
+      const { createMessage: msg } = useMessage();
+      const currentEditKeyRef = ref('');
+      const [registerTable, { insertTableDataRecord, reload }] = useTable({
+        title: '',
+        api: props.list?.bind(null, { devicetype: deviceType }),
+        columns: props.columns as BasicColumn[],
+        showIndexColumn: false,
+        showTableSetting: false,
+        tableSetting: { fullScreen: true },
+        actionColumn: {
+          width: 160,
+          title: '操作',
+          dataIndex: 'action',
+          slots: { customRender: 'action' },
+        },
+      });
+      function addRow() {
+        const record = {} as EditRecordRow;
+        insertTableDataRecord(record);
+        nextTick(() => {
+          handleEdit(record);
+        });
+      }
+      function handleEdit(record: EditRecordRow) {
+        currentEditKeyRef.value = record.key;
+        record.onEdit?.(true);
+      }
+
+      function handleCancel(record: EditRecordRow) {
+        currentEditKeyRef.value = '';
+        record.onEdit?.(false, false);
+      }
+
+      function handleDelete(record: EditRecordRow) {
+        emit('deleteById', record.id, reload);
+      }
+
+      async function handleSave(record: EditRecordRow) {
+        // 校验
+        msg.loading({ content: '正在保存...', duration: 0, key: 'saving' });
+        const valid = await record.onValid?.();
+        if (valid) {
+          try {
+            //TODO 此处将数据提交给服务器保存
+            emit('saveOrUpdate', record);
+            // 保存之后提交编辑状态
+            const pass = await record.onEdit?.(false, true);
+            if (pass) {
+              currentEditKeyRef.value = '';
+            }
+            msg.success({ content: '数据已保存', key: 'saving' });
+          } catch (error) {
+            msg.error({ content: '保存失败', key: 'saving' });
+          }
+        } else {
+          msg.error({ content: '请填写正确的数据', key: 'saving' });
+        }
+      }
+
+      function createActions(record: EditRecordRow, column: BasicColumn): ActionItem[] {
+        if (!record.editable) {
+          if (props.isAdd) {
+            return [
+              {
+                label: '编辑',
+                disabled: currentEditKeyRef.value ? currentEditKeyRef.value !== record.key : false,
+                onClick: handleEdit.bind(null, record),
+              },
+              {
+                label: '删除',
+                disabled: currentEditKeyRef.value ? currentEditKeyRef.value !== record.key : false,
+                onClick: handleDelete.bind(null, record),
+              },
+            ];
+          } else {
+            return [
+              {
+                label: '编辑',
+                disabled: currentEditKeyRef.value ? currentEditKeyRef.value !== record.key : false,
+                onClick: handleEdit.bind(null, record),
+              },
+            ];
+          }
+        }
+        return [
+          {
+            label: '保存',
+            onClick: handleSave.bind(null, record, column),
+          },
+          {
+            label: '取消',
+            popConfirm: {
+              title: '是否取消编辑',
+              confirm: handleCancel.bind(null, record, column),
+            },
+          },
+        ];
+      }
+
+      function onEditChange({ column, value, record }) {
+        // 本例
+        if (column.dataIndex === 'id') {
+          record.editValueRefs.name4.value = `${value}`;
+        }
+        console.log(column, value, record);
+      }
+
+      return {
+        registerTable,
+        handleEdit,
+        createActions,
+        onEditChange,
+        addRow,
+      };
+    },
+  });
+</script>
+<style scoped lang="less">
+  :deep(.ant-table-body) {
+    height: auto !important;
+  }
+</style>

+ 58 - 0
src/views/vent/deviceManager/comment/DeviceModal.vue

@@ -0,0 +1,58 @@
+<template>
+  <BasicModal v-bind="$attrs" @register="registerModal" :title="title" width="900px" :showCancelBtn="false" :showOkBtn="false" :footer="null" destroyOnClose>
+    <a-tabs v-if="props.showtTab">
+      <a-tab-pane key="1" tab="基本信息" force-render> <FormModal :record="record" @saveOrUpdate="(values) => emit('saveOrUpdate', values)" /> </a-tab-pane>
+      <a-tab-pane key="2" tab="点表配置"><EditRowTable :columns="pointColumns" :list="pointList" @saveOrUpdate="savePointData" @deleteById="deletePointById" /></a-tab-pane>
+      <a-tab-pane key="3" tab="摄像头配置"
+        ><EditRowTable :columns="cameraColumns" :list="cameraList.bind(null, { deviceId: deviceData.id })" @saveOrUpdate="saveCameraData" @deleteById="deleteCameraById" :isAdd="true"
+      /></a-tab-pane>
+    </a-tabs>
+    <FormModal v-else :record="record" @saveOrUpdate="(values) => emit('saveOrUpdate', values)" />
+  </BasicModal>
+</template>
+<script lang="ts" setup>
+  import { computed, unref, inject, reactive } from 'vue';
+  import { BasicModal, useModalInner } from '/@/components/Modal';
+  import EditRowTable from '../../comment/EditRowTable.vue';
+  import FormModal from './FormModal.vue';
+  import { cloneDeep } from 'lodash-es';
+  import { columns as pointColumns } from './pointTabel/point.data';
+  import { list as pointList, saveOrUpdate as pointSaveOrUpdate, deleteById as pointDeleteById } from './pointTabel/point.api';
+  import { columns as cameraColumns } from './cameraTabel/camera.data';
+  import { list as cameraList, saveOrUpdate as cameraSaveOrUpdate, deleteById as cameraDeleteById } from './cameraTabel/camera.api';
+
+  const props = defineProps({
+    showtTab: { type: Boolean, required: true },
+  });
+  // 声明Emits
+  const emit = defineEmits(['saveOrUpdate', 'register']);
+  const isUpdate = inject('isUpdate');
+  const deviceData = inject('formData') as any;
+  const record = reactive({});
+
+  //表单赋值
+  const [registerModal, { setModalProps }] = useModalInner(async (data) => {
+    //重置表单
+    setModalProps({ confirmLoading: false });
+    Object.assign(record, data.record);
+  });
+
+  //设置标题
+  const title = computed(() => (!unref(isUpdate) ? '新增' : '编辑'));
+
+  const savePointData = (data) => {
+    const record = cloneDeep(data.editValueRefs);
+    pointSaveOrUpdate(Object.assign(record, { id: data.id, deviceId: deviceData.id }), data.id);
+  };
+  const saveCameraData = (data: any) => {
+    const record = cloneDeep(data.editValueRefs);
+    cameraSaveOrUpdate(Object.assign(record, { id: data.id, deviceId: deviceData.id }), data.id);
+  };
+  const deletePointById = (id, reload) => {
+    pointDeleteById({ id: id }, reload);
+  };
+  const deleteCameraById = (id, reload) => {
+    cameraDeleteById({ id: id }, reload);
+  };
+</script>
+<style scoped lang="less"></style>

+ 58 - 0
src/views/vent/deviceManager/comment/FormModal.vue

@@ -0,0 +1,58 @@
+<template>
+  <div>
+    <BasicForm @register="registerForm" />
+    <div class="j-box-bottom-button offset-20" style="margin-top: 30px">
+      <div class="j-box-bottom-button-float">
+        <a-button preIcon="ant-design:sync-outlined" @click="onReset">重置</a-button>
+        <a-button type="primary" preIcon="ant-design:save-filled" @click="handleSubmit">保存</a-button>
+      </div>
+    </div>
+  </div>
+</template>
+<script lang="ts" setup>
+  import { reject } from 'lodash';
+  import { inject, nextTick, watch } from 'vue';
+  import { BasicForm, useForm } from '/@/components/Form/index';
+  // 声明Emits
+  const emit = defineEmits(['saveOrUpdate']);
+  const testData = inject('formData') as any;
+  //表单配置
+  const [registerForm, { resetFields, setFieldsValue, validate }] = useForm({
+    schemas: inject('formSchema'),
+    showActionButtonGroup: false,
+  });
+
+  watch(
+    testData,
+    (newV) => {
+      nextTick(() => {
+        setFieldsValue({ ...newV });
+      });
+    },
+    { immediate: true }
+  );
+
+  // 重置表单
+  async function onReset() {
+    await resetFields();
+    await setFieldsValue({ ...testData });
+  }
+  //表单提交事件
+  async function handleSubmit(v) {
+    try {
+      let values = await validate();
+      emit('saveOrUpdate', values);
+    } finally {
+      // setModalProps({ confirmLoading: false });
+    }
+  }
+</script>
+<style lang="less" scoped>
+  .j-box-bottom-button-float {
+    border: none !important;
+    padding-bottom: 30px;
+    left: 0px !important;
+    right: 0px !important;
+    bottom: 0px !important;
+  }
+</style>

+ 264 - 0
src/views/vent/deviceManager/comment/NormalTable.vue

@@ -0,0 +1,264 @@
+<template>
+  <div>
+    <BasicTable @register="registerTable" :rowSelection="rowSelection">
+      <template #tableTitle>
+        <a-button preIcon="ant-design:plus-outlined" type="primary" @click="handleAdd">新增</a-button>
+        <a-button type="primary" preIcon="ant-design:export-outlined" @click="onExportXls"> 导出</a-button>
+        <j-upload-button type="primary" preIcon="ant-design:import-outlined" @click="onImportXls">导入</j-upload-button>
+        <a-dropdown v-if="selectedRowKeys.length > 0">
+          <template #overlay>
+            <a-menu>
+              <a-menu-item key="1" @click="batchHandleDelete">
+                <Icon icon="ant-design:delete-outlined" />
+                删除
+              </a-menu-item>
+            </a-menu>
+          </template>
+          <a-button
+            >批量操作
+            <Icon style="fontsize: 12px" icon="ant-design:down-outlined" />
+          </a-button>
+        </a-dropdown>
+      </template>
+      <template #action="{ record }">
+        <TableAction :actions="getActions(record)" :dropDownActions="getDropDownAction(record)" />
+      </template>
+    </BasicTable>
+    <DeviceModal @register="registerModal" @saveOrUpdate="saveOrUpdateHandler" :showtTab="showtTab" />
+  </div>
+</template>
+
+<script lang="ts" name="system-user" setup>
+  //ts语法
+  import { ref, provide, reactive, toRaw, defineExpose } from 'vue';
+  import { BasicTable, TableAction } from '/@/components/Table';
+  import { useModal } from '/@/components/Modal';
+  import DeviceModal from './DeviceModal.vue';
+  // import { getToken } from '/@/utils/auth';
+  // import { useGlobSetting } from '/@/hooks/setting';
+  import { useListPage } from '/@/hooks/system/useListPage';
+  import { useMessage } from '/@/hooks/web/useMessage';
+  const $message = useMessage();
+  const props = defineProps({
+    columns: {
+      type: Array,
+      required: true,
+      default: () => [],
+    },
+    searchFormSchema: {
+      type: Array,
+      required: true,
+      default: () => [],
+    },
+    formSchema: {
+      type: Array,
+      required: true,
+    },
+    list: {
+      type: Function,
+      required: true,
+    },
+    getImportUrl: {
+      type: String,
+      required: true,
+    },
+    getExportUrl: {
+      type: String,
+      required: true,
+    },
+    deleteById: {
+      type: Function,
+      required: true,
+    },
+    batchDelete: {
+      type: Function,
+      // required: true,
+    },
+    saveOrUpdate: {
+      type: Function,
+      required: true,
+    },
+    showtTab: {
+      type: Boolean,
+      default: false,
+    },
+    designScope: {
+      type: String,
+    },
+    title: {
+      type: String,
+    },
+    deviceType: {
+      type: String,
+    },
+  });
+  // debugger  
+  const isUpdate = ref(false);
+  const record = reactive({});
+
+  provide('formSchema', props.formSchema);
+  provide('isUpdate', isUpdate);
+  provide('formData', record);
+  provide('deviceType', props.deviceType);
+  // const glob = useGlobSetting();
+  const [registerModal, { openModal, closeModal }] = useModal();
+
+  // 列表页面公共参数、方法
+  const { prefixCls, tableContext, onExportXls, onImportXls, doRequest } = useListPage({
+    designScope: props.designScope,
+    tableProps: {
+      title: props.title,
+      api: props.list,
+      columns: props.columns as any[],
+      size: 'small',
+      formConfig: {
+        labelWidth: 200,
+        schemas: props.searchFormSchema as any[],
+      },
+      actionColumn: {
+        width: 180,
+      },
+      beforeFetch: (params) => {
+        return Object.assign({ column: 'createTime', order: 'desc' }, params);
+      },
+    },
+    exportConfig: {
+      name: props.title,
+      url: props.getExportUrl,
+    },
+    importConfig: {
+      url: props.getImportUrl,
+    },
+  });
+
+  //注册table数据
+  const [registerTable, { reload }, { rowSelection, selectedRowKeys }] = tableContext;
+
+  const saveOrUpdateHandler = async (params) => {
+    try {
+      await props.saveOrUpdate(params, isUpdate.value);
+      !props.showtTab ? closeModal() : '';
+      await doRequest(props.list, { confirm: false });
+    } catch (error) {
+      $message.createMessage.error('保存失败,请联系管理员');
+    }
+  };
+
+  /**
+   * 新增事件
+   */
+  function handleAdd() {
+    for (let key in record) {
+      delete record[key];
+    }
+    isUpdate.value = false;
+    openModal(true);
+  }
+
+  /**
+   * 编辑事件
+   */
+  function handleEdit(data) {
+    isUpdate.value = true;
+    Object.assign(record, toRaw(data));
+    openModal(true, {
+      record,
+    });
+  }
+
+  /**
+   * 删除事件
+   */
+  async function handleDelete(record) {
+    await props.deleteById({ id: record }, reload);
+  }
+
+  /**
+   * 批量删除事件
+   */
+  async function batchHandleDelete() {
+    doRequest(() => props.batchDelete({ ids: selectedRowKeys.value }));
+  }
+  /**
+   * 查看
+   */
+  // function handleDetail(record) {
+  //   iframeUrl.value = `${glob.uploadUrl}/sys/annountCement/show/${record.id}?token=${getToken()}`;
+  //   openDetail(true);
+  // }
+  /**
+   * 操作列定义
+   * @param record
+   */
+  function getActions(record) {
+    return [
+      {
+        label: '编辑',
+        onClick: handleEdit.bind(null, record),
+      },
+      {
+        label: '删除',
+        popConfirm: {
+          title: '是否确认删除',
+          confirm: handleDelete.bind(null, record),
+        },
+      },
+      // {
+      //   label: '查看',
+      //   onClick: handleDetail.bind(null, record),
+      // },
+    ];
+  }
+  /**
+   * 下拉操作栏
+   */
+  function getDropDownAction(record) {
+    return [
+      // {
+      //   label: '删除',
+      //   popConfirm: {
+      //     title: '是否确认删除',
+      //     confirm: handleDelete.bind(null, record),
+      //   },
+      // },
+      // {
+      //   label: '查看',
+      //   onClick: handleDetail.bind(null, record),
+      // },
+    ];
+  }
+  defineExpose({
+    doRequest,
+  });
+</script>
+
+<style scoped lang="less">
+  // :deep(.ant-table-header){
+  //   background-color:transparent;
+  //   height: 0;
+  // }
+  // :deep(.jeecg-basic-table .ant-table-wrapper){
+  //   background-color: #ffffff00;
+  // }
+  // :deep(.ant-table-body) {
+  //   height: auto !important;
+  // }
+  // :deep(.ant-table){
+  //   background-color: #ffffff00 !important;
+  // }
+  // :deep(.ant-table-thead > tr > th){
+  //   background-color:transparent
+  // }
+  // :deep(.ant-table-body > tr > th){
+  //   background-color:transparent
+  // }
+  // :deep(.ant-table-body > tr > td){
+  //   border: none;
+  // }
+  // :deep(.ant-table-fixed-header > .ant-table-content > .ant-table-scroll > .ant-table-body){
+  //   background-color:transparent
+  // }
+  // :deep(.jeecg-basic-table-row__striped td){
+  //   background-color: transparent;
+  // }
+</style>

+ 43 - 0
src/views/vent/deviceManager/comment/cameraTabel/camera.api.ts

@@ -0,0 +1,43 @@
+import { defHttp } from '/@/utils/http/axios';
+import { Modal } from 'ant-design-vue';
+
+enum Api {
+  list = '/ventanaly-device/safety/ventanalyCamera/list',
+  save = '/ventanaly-device/safety/ventanalyCamera/add',
+  edit = '/ventanaly-device/safety/ventanalyCamera/edit',
+  deleteById = '/ventanaly-device/safety/ventanalyCamera/delete',
+  importExcel = '/sys/user/importExcel',
+  exportXls = '/sys/user/exportXls',
+}
+/**
+ * 导出api
+ * @param params
+ */
+export const getExportUrl = Api.exportXls;
+/**
+ * 导入api
+ */
+export const getImportUrl = Api.importExcel;
+/**
+ * 列表接口
+ * @param params
+ */
+export const list = (params) => defHttp.get({ url: Api.list, params });
+
+/**
+ * 删除用户
+ */
+export const deleteById = (params, handleSuccess) => {
+  return defHttp.delete({ url: Api.deleteById, params }, { joinParamsToUrl: true }).then(() => {
+    handleSuccess();
+  });
+};
+
+/**
+ * 保存或者更新用户
+ * @param params
+ */
+export const saveOrUpdate = (params, isUpdate) => {
+  const url = isUpdate ? Api.edit : Api.save;
+  return defHttp.put({ url: url, params });
+};

+ 49 - 0
src/views/vent/deviceManager/comment/cameraTabel/camera.data.ts

@@ -0,0 +1,49 @@
+import { BasicColumn } from '/@/components/Table';
+export const columns: BasicColumn[] = [
+  {
+    title: 'id',
+    dataIndex: 'id',
+    ifShow: false,
+    editRow: true,
+  },
+  {
+    title: '设备id',
+    dataIndex: 'deviceid',
+    ifShow: false,
+    editRow: true,
+  },
+  {
+    title: 'IP地址',
+    dataIndex: 'ip',
+    width: 100,
+    editRow: true,
+    editRule: true,
+  },
+  {
+    title: '端口号',
+    width: 100,
+    dataIndex: 'port',
+    editRow: true,
+    editRule: true,
+    editComponent: 'InputNumber',
+  },
+  {
+    title: '摄像头名称',
+    dataIndex: 'name',
+    width: 100,
+    editRow: true,
+  },
+  {
+    title: '控制密码',
+    dataIndex: 'password',
+    width: 120,
+    editRow: true,
+    editRule: true,
+  },
+  {
+    title: '摄像头用户',
+    dataIndex: 'username',
+    width: 120,
+    editRow: true,
+  },
+];

+ 60 - 0
src/views/vent/deviceManager/comment/pointTabel/point.api.ts

@@ -0,0 +1,60 @@
+import { defHttp } from '/@/utils/http/axios';
+import { Modal } from 'ant-design-vue';
+
+enum Api {
+  list = '/ventanaly-device/safety/ventanalyMonitorParams/list',
+  save = '/ventanaly-device/safety/ventanalyMonitorParams/add',
+  edit = '/ventanaly-device/safety/ventanalyMonitorParams/edit',
+  deleteById = '/ventanaly-device/safety/ventanalyMonitorParams/delete',
+  deleteBatch = '/sys/user/deleteBatch',
+  importExcel = '/sys/user/importExcel',
+  exportXls = '/sys/user/exportXls',
+}
+/**
+ * 导出api
+ * @param params
+ */
+export const getExportUrl = Api.exportXls;
+/**
+ * 导入api
+ */
+export const getImportUrl = Api.importExcel;
+/**
+ * 列表接口
+ * @param params
+ */
+export const list = (params) => defHttp.get({ url: Api.list, params });
+
+/**
+ * 删除用户
+ */
+export const deleteById = (params, handleSuccess) => {
+  return defHttp.delete({ url: Api.deleteById, params }, { joinParamsToUrl: true }).then(() => {
+    handleSuccess();
+  });
+};
+/**
+ * 批量删除用户
+ * @param params
+ */
+export const batchDeleteById = (params, handleSuccess) => {
+  Modal.confirm({
+    title: '确认删除',
+    content: '是否删除选中数据',
+    okText: '确认',
+    cancelText: '取消',
+    onOk: () => {
+      return defHttp.delete({ url: Api.deleteBatch, data: params }, { joinParamsToUrl: true }).then(() => {
+        handleSuccess();
+      });
+    },
+  });
+};
+/**
+ * 保存或者更新用户
+ * @param params
+ */
+export const saveOrUpdate = (params, isUpdate) => {
+  const url = isUpdate ? Api.edit : Api.save;
+  return defHttp.put({ url: url, params });
+};

+ 50 - 0
src/views/vent/deviceManager/comment/pointTabel/point.data.ts

@@ -0,0 +1,50 @@
+import { BasicColumn } from '/@/components/Table';
+export const columns: BasicColumn[] = [
+  {
+    title: 'plc地址',
+    dataIndex: 'plcaddr',
+    width: 100,
+  },
+  {
+    title: '值名称',
+    dataIndex: 'valuename',
+    width: 100,
+  },
+  {
+    title: '值类型',
+    width: 100,
+    dataIndex: 'valuetype',
+  },
+  {
+    title: '最小值',
+    dataIndex: 'fmin',
+    width: 120,
+    editRow: true,
+    editRule: true,
+    editComponent: 'InputNumber',
+  },
+  {
+    title: '最大值',
+    dataIndex: 'fmax',
+    width: 120,
+    editRow: true,
+    editRule: true,
+    editComponent: 'InputNumber',
+  },
+  {
+    title: '模拟最小值',
+    dataIndex: 'testlow',
+    width: 120,
+    editRow: true,
+    editRule: true,
+    editComponent: 'InputNumber',
+  },
+  {
+    title: '模拟最大值',
+    dataIndex: 'testup',
+    width: 120,
+    editRow: true,
+    editRule: true,
+    editComponent: 'InputNumber',
+  },
+];

+ 60 - 0
src/views/vent/deviceManager/damperTabel/damper.api.ts

@@ -0,0 +1,60 @@
+import { defHttp } from '/@/utils/http/axios';
+import { Modal } from 'ant-design-vue';
+
+enum Api {
+  list = '/ventanaly-device/safety/ventanalyGate/list',
+  save = '/ventanaly-device/safety/ventanalyGate/add',
+  edit = '/ventanaly-device/safety/ventanalyGate/edit',
+  deleteById = '/ventanaly-device/safety/ventanalyGate/delete',
+  deleteBatch = '/sys/user/deleteBatch',
+  importExcel = '/sys/user/importExcel',
+  exportXls = '/sys/user/exportXls',
+}
+/**
+ * 导出api
+ * @param params
+ */
+export const getExportUrl = Api.exportXls;
+/**
+ * 导入api
+ */
+export const getImportUrl = Api.importExcel;
+/**
+ * 列表接口
+ * @param params
+ */
+export const list = (params) => defHttp.get({ url: Api.list, params });
+
+/**
+ * 删除用户
+ */
+export const deleteById = (params, handleSuccess) => {
+  return defHttp.delete({ url: Api.deleteById, params: params.id }, { joinParamsToUrl: true }).then(() => {
+    handleSuccess();
+  });
+};
+/**
+ * 批量删除用户
+ * @param params
+ */
+export const batchDeleteById = (params, handleSuccess) => {
+  Modal.confirm({
+    title: '确认删除',
+    content: '是否删除选中数据',
+    okText: '确认',
+    cancelText: '取消',
+    onOk: () => {
+      return defHttp.delete({ url: Api.deleteBatch, data: params.id }, { joinParamsToUrl: true }).then(() => {
+        handleSuccess();
+      });
+    },
+  });
+};
+/**
+ * 保存或者更新用户
+ * @param params
+ */
+export const saveOrUpdate = (params, isUpdate) => {
+  const url = isUpdate ? Api.edit : Api.save;
+  return isUpdate ? defHttp.put({ url: url, params }) : defHttp.post({ url: url, params });
+};

+ 292 - 0
src/views/vent/deviceManager/damperTabel/damper.data.ts

@@ -0,0 +1,292 @@
+import { BasicColumn } from '/@/components/Table';
+import { FormSchema } from '/@/components/Table';
+import { rules } from '/@/utils/helper/validator';
+import { list } from '../substationTabel/substation.api';
+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,
+    sorter: true,
+  },
+  {
+    title: '净高',
+    dataIndex: 'fclearheight',
+    width: 100,
+    sorter: true,
+  },
+  {
+    title: '风门道数',
+    dataIndex: 'ndoorcount',
+    width: 100,
+  },
+  {
+    title: '所属分站',
+    width: 150,
+    dataIndex: 'stationname',
+  },
+  {
+    title: '点表',
+    width: 100,
+    dataIndex: 'strtype_dictText',
+  },
+  {
+    title: '是否开启监测',
+    dataIndex: 'monitorflag_dictText',
+    width: 100,
+  },
+  {
+    title: '是否模拟数据',
+    dataIndex: 'testflag_dictText',
+    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: 'nsubstationid',
+    component: 'ApiSelect',
+    componentProps: {
+      api: list,
+      labelField: 'strname',
+      valueField: 'nsubstationid',
+    },
+  },
+  {
+    label: '点表',
+    field: 'strtype',
+    component: 'JDictSelectTag',
+    componentProps: {
+      dictCode: 'strtype',
+      placeholder: '请选择状态',
+    },
+  },
+  {
+    label: '监测类型',
+    field: 'monitorflag',
+    component: 'JDictSelectTag',
+    componentProps: {
+      dictCode: 'devicekind',
+      placeholder: '请选择状态',
+    },
+  },
+  {
+    label: '是否开启监测',
+    field: 'monitorflag',
+    component: 'RadioGroup',
+    defaultValue: 1,
+    componentProps: () => {
+      return {
+        options: [
+          { label: '是', value: 1, key: '1' },
+          { label: '否', value: 0, key: '2' },
+        ],
+      };
+    },
+  },
+  {
+    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',
+    },
+  },
+];

+ 26 - 0
src/views/vent/deviceManager/damperTabel/index.vue

@@ -0,0 +1,26 @@
+<template>
+  <NormalTable
+    :columns="columns"
+    :searchFormSchema="searchFormSchema"
+    :list="list"
+    :getImportUrl="getImportUrl"
+    :getExportUrl="getExportUrl"
+    :formSchema="formSchema"
+    :deleteById="deleteById"
+    :batchDelete="batchDeleteById"
+    :saveOrUpdate="saveOrUpdate"
+    designScope="damper-tabel"
+    title="风门列表"
+    :showtTab="true"
+    deviceType="gate_normal"
+  />
+</template>
+
+<script lang="ts" name="system-user" setup>
+  //ts语法
+  import NormalTable from '../comment/NormalTable.vue';
+  import { columns, searchFormSchema, formSchema } from './damper.data';
+  import { list, getImportUrl, getExportUrl, deleteById, batchDeleteById, saveOrUpdate } from './damper.api';
+</script>
+
+<style scoped></style>

+ 60 - 0
src/views/vent/deviceManager/deviceColumns/columns.api.ts

@@ -0,0 +1,60 @@
+import { defHttp } from '/@/utils/http/axios';
+import { Modal } from 'ant-design-vue';
+
+enum Api {
+  list = '/ventanaly-device/safety/ventanalyGate/list',
+  save = '/ventanaly-device/safety/ventanalyGate/add',
+  edit = '/ventanaly-device/safety/ventanalyGate/edit',
+  deleteById = '/ventanaly-device/safety/ventanalyGate/delete',
+  deleteBatch = '/sys/user/deleteBatch',
+  importExcel = '/sys/user/importExcel',
+  exportXls = '/sys/user/exportXls',
+}
+/**
+ * 导出api
+ * @param params
+ */
+export const getExportUrl = Api.exportXls;
+/**
+ * 导入api
+ */
+export const getImportUrl = Api.importExcel;
+/**
+ * 列表接口
+ * @param params
+ */
+export const list = (params) => defHttp.get({ url: Api.list, params });
+
+/**
+ * 删除用户
+ */
+export const deleteById = (params, handleSuccess) => {
+  return defHttp.delete({ url: Api.deleteById, params }, { joinParamsToUrl: true }).then(() => {
+    handleSuccess();
+  });
+};
+/**
+ * 批量删除用户
+ * @param params
+ */
+export const batchDeleteById = (params, handleSuccess) => {
+  Modal.confirm({
+    title: '确认删除',
+    content: '是否删除选中数据',
+    okText: '确认',
+    cancelText: '取消',
+    onOk: () => {
+      return defHttp.delete({ url: Api.deleteBatch, data: params }, { joinParamsToUrl: true }).then(() => {
+        handleSuccess();
+      });
+    },
+  });
+};
+/**
+ * 保存或者更新用户
+ * @param params
+ */
+export const saveOrUpdate = (params, isUpdate) => {
+  const url = isUpdate ? Api.edit : Api.save;
+  return defHttp.put({ url: url, params });
+};

+ 283 - 0
src/views/vent/deviceManager/deviceColumns/columns.data.ts

@@ -0,0 +1,283 @@
+import { BasicColumn } from '/@/components/Table';
+import { FormSchema } from '/@/components/Table';
+import { getAllRolesList, getAllTenantList } from './columns.api';
+import { rules } from '/@/utils/helper/validator';
+import { render } from '/@/utils/common/renderUtils';
+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,
+    // sorter: true,
+    // customRender: ({ text }) => {
+    //   return render.renderDict(text, 'sex');
+    // },
+  },
+  {
+    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',
+    },
+  },
+];

+ 25 - 0
src/views/vent/deviceManager/deviceColumns/index.vue

@@ -0,0 +1,25 @@
+<template>
+  <NormalTable
+    :columns="columns"
+    :searchFormSchema="searchFormSchema"
+    :list="list"
+    :getImportUrl="getImportUrl"
+    :getExportUrl="getExportUrl"
+    :formSchema="formSchema"
+    :deleteById="deleteById"
+    :batchDelete="batchDeleteById"
+    :saveOrUpdate="saveOrUpdate"
+    designScope="damper-tabel"
+    title="风门列表"
+    :showtTab="true"
+  />
+</template>
+
+<script lang="ts" name="system-user" setup>
+  //ts语法
+  import NormalTable from '../comment/NormalTable.vue';
+  import { columns, searchFormSchema, formSchema } from './columns.data';
+  import { list, getImportUrl, getExportUrl, deleteById, batchDeleteById, saveOrUpdate } from './columns.api';
+</script>
+
+<style scoped></style>

+ 60 - 0
src/views/vent/deviceManager/fanTabel/fan.api.ts

@@ -0,0 +1,60 @@
+import { defHttp } from '/@/utils/http/axios';
+import { Modal } from 'ant-design-vue';
+
+enum Api {
+  list = '/ventanaly-device/safety/ventanalyFan/list',
+  save = '/ventanaly-device/safety/ventanalyFan/add',
+  edit = '/ventanaly-device/safety/ventanalyFan/edit',
+  deleteById = '/ventanaly-device/safety/ventanalyFan/delete',
+  deleteBatch = '/sys/user/deleteBatch',
+  importExcel = '/sys/user/importExcel',
+  exportXls = '/sys/user/exportXls',
+}
+/**
+ * 导出api
+ * @param params
+ */
+export const getExportUrl = Api.exportXls;
+/**
+ * 导入api
+ */
+export const getImportUrl = Api.importExcel;
+/**
+ * 列表接口
+ * @param params
+ */
+export const list = (params) => defHttp.get({ url: Api.list, params });
+
+/**
+ * 删除用户
+ */
+export const deleteById = (params, handleSuccess) => {
+  return defHttp.delete({ url: Api.deleteById, params: params.id }, { joinParamsToUrl: true }).then(() => {
+    handleSuccess();
+  });
+};
+/**
+ * 批量删除用户
+ * @param params
+ */
+export const batchDeleteById = (params, handleSuccess) => {
+  Modal.confirm({
+    title: '确认删除',
+    content: '是否删除选中数据',
+    okText: '确认',
+    cancelText: '取消',
+    onOk: () => {
+      return defHttp.delete({ url: Api.deleteBatch, data: params }, { joinParamsToUrl: true }).then(() => {
+        handleSuccess();
+      });
+    },
+  });
+};
+/**
+ * 保存或者更新用户
+ * @param params
+ */
+export const saveOrUpdate = (params, isUpdate) => {
+  const url = isUpdate ? Api.edit : Api.save;
+  return isUpdate ? defHttp.put({ url: url, params }) : defHttp.post({ url: url, params });
+};

+ 123 - 0
src/views/vent/deviceManager/fanTabel/fan.data.ts

@@ -0,0 +1,123 @@
+import { BasicColumn } from '/@/components/Table';
+import { FormSchema } from '/@/components/Table';
+import { list } from '../substationTabel/substation.api';
+
+export const columns: BasicColumn[] = [
+  {
+    title: '名称',
+    dataIndex: 'strname',
+    width: 120,
+  },
+  {
+    title: '安装位置',
+    dataIndex: 'strinstallpos',
+    width: 100,
+  },
+  {
+    title: '风筒长度(m)',
+    dataIndex: 'flength',
+    width: 100,
+  },
+  {
+    title: '风筒直径(m)',
+    dataIndex: 'fclearwidth',
+    width: 80,
+  },
+  {
+    title: '风机类型',
+    dataIndex: 'ntype_dictText',
+    width: 100,
+  },
+  {
+    title: '所属分站',
+    width: 150,
+    dataIndex: 'stationname',
+  },
+];
+
+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: 'nsubstationid',
+    component: 'ApiSelect',
+    componentProps: {
+      api: list,
+      labelField: 'strname',
+      valueField: 'nsubstationid',
+    },
+  },
+];
+
+export const formSchema: FormSchema[] = [
+  {
+    label: '',
+    field: 'id',
+    component: 'Input',
+    show: false,
+  },
+  {
+    label: '名称',
+    field: 'strname',
+    component: 'Input',
+  },
+  {
+    label: '安装位置',
+    field: 'strinstallpos',
+    component: 'Input',
+  },
+  {
+    label: '风筒长度(m)',
+    field: 'flength',
+    component: 'InputNumber',
+  },
+  {
+    label: '风筒直径(m)',
+    field: 'fclearwidth',
+    component: 'InputNumber',
+  },
+  {
+    label: '风机类型',
+    field: 'ntype',
+    component: 'JDictSelectTag',
+    componentProps: {
+      dictCode: 'fantype',
+      placeholder: '请选择状态',
+      stringToNumber: true,
+    },
+  },
+  {
+    label: '所属分站',
+    field: 'nsubstationid',
+    component: 'ApiSelect',
+    componentProps: {
+      api: list,
+      labelField: 'strname',
+      valueField: 'nsubstationid',
+    },
+  },
+];

+ 25 - 0
src/views/vent/deviceManager/fanTabel/index.vue

@@ -0,0 +1,25 @@
+<template>
+  <NormalTable
+    :columns="columns"
+    :searchFormSchema="searchFormSchema"
+    :list="list"
+    :getImportUrl="getImportUrl"
+    :getExportUrl="getExportUrl"
+    :formSchema="formSchema"
+    :deleteById="deleteById"
+    :batchDelete="batchDeleteById"
+    :saveOrUpdate="saveOrUpdate"
+    designScope="fan-tabel"
+    title="风机列表"
+    :showtTab="true"
+  />
+</template>
+
+<script lang="ts" name="system-user" setup>
+  //ts语法
+  import NormalTable from '../comment/NormalTable.vue';
+  import { columns, searchFormSchema, formSchema } from './fan.data';
+  import { list, getImportUrl, getExportUrl, deleteById, batchDeleteById, saveOrUpdate } from './fan.api';
+</script>
+
+<style scoped></style>

+ 25 - 0
src/views/vent/deviceManager/ledTabel/index.vue

@@ -0,0 +1,25 @@
+<template>
+  <NormalTable
+    :columns="columns"
+    :searchFormSchema="searchFormSchema"
+    :list="list"
+    :getImportUrl="getImportUrl"
+    :getExportUrl="getExportUrl"
+    :formSchema="formSchema"
+    :deleteById="deleteById"
+    :batchDelete="batchDeleteById"
+    :saveOrUpdate="saveOrUpdate"
+    designScope="led-tabel"
+    title="LED屏列表"
+    :showtTab="true"
+  />
+</template>
+
+<script lang="ts" name="system-user" setup>
+  //ts语法
+  import NormalTable from '../comment/NormalTable.vue';
+  import { columns, searchFormSchema, formSchema } from './led.data';
+  import { list, getImportUrl, getExportUrl, deleteById, batchDeleteById, saveOrUpdate } from './led.api';
+</script>
+
+<style scoped></style>

+ 60 - 0
src/views/vent/deviceManager/ledTabel/led.api.ts

@@ -0,0 +1,60 @@
+import { defHttp } from '/@/utils/http/axios';
+import { Modal } from 'ant-design-vue';
+
+enum Api {
+  list = '/ventanaly-device/safety/ventanalyModelSensor/list',
+  save = '/ventanaly-device/safety/ventanalyModelSensor/add',
+  edit = '/ventanaly-device/safety/ventanalyModelSensor/edit',
+  deleteById = '/ventanaly-device/safety/ventanalyModelSensor/delete',
+  deleteBatch = '/sys/user/deleteBatch',
+  importExcel = '/sys/user/importExcel',
+  exportXls = '/sys/user/exportXls',
+}
+/**
+ * 导出api
+ * @param params
+ */
+export const getExportUrl = Api.exportXls;
+/**
+ * 导入api
+ */
+export const getImportUrl = Api.importExcel;
+/**
+ * 列表接口
+ * @param params
+ */
+export const list = (params) => defHttp.get({ url: Api.list, params });
+
+/**
+ * 删除用户
+ */
+export const deleteById = (params, handleSuccess) => {
+  return defHttp.delete({ url: Api.deleteById, params: params.id }, { joinParamsToUrl: true }).then(() => {
+    handleSuccess();
+  });
+};
+/**
+ * 批量删除用户
+ * @param params
+ */
+export const batchDeleteById = (params, handleSuccess) => {
+  Modal.confirm({
+    title: '确认删除',
+    content: '是否删除选中数据',
+    okText: '确认',
+    cancelText: '取消',
+    onOk: () => {
+      return defHttp.delete({ url: Api.deleteBatch, data: params }, { joinParamsToUrl: true }).then(() => {
+        handleSuccess();
+      });
+    },
+  });
+};
+/**
+ * 保存或者更新用户
+ * @param params
+ */
+export const saveOrUpdate = (params, isUpdate) => {
+  const url = isUpdate ? Api.edit : Api.save;
+  return isUpdate ? defHttp.put({ url: url, params }) : defHttp.post({ url: url, params });
+};

+ 126 - 0
src/views/vent/deviceManager/ledTabel/led.data.ts

@@ -0,0 +1,126 @@
+import { BasicColumn } from '/@/components/Table';
+import { FormSchema } from '/@/components/Table';
+import { list } from '../substationTabel/substation.api';
+
+export const columns: BasicColumn[] = [
+  {
+    title: '名称',
+    dataIndex: 'strname',
+    width: 120,
+  },
+  {
+    title: '安装位置',
+    dataIndex: 'strinstallpos',
+    width: 100,
+  },
+  {
+    title: '类型',
+    dataIndex: 'strtype_dictText',
+    width: 100,
+  },
+  {
+    title: '所属分站',
+    width: 150,
+    dataIndex: 'stationname',
+  },
+];
+
+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: 'nsubstationid',
+    component: 'ApiSelect',
+    componentProps: {
+      api: list,
+      labelField: 'strname',
+      valueField: 'nsubstationid',
+    },
+  },
+];
+
+export const formSchema: FormSchema[] = [
+  {
+    label: '',
+    field: 'id',
+    component: 'Input',
+    show: false,
+  },
+  {
+    label: '名称',
+    field: 'strname',
+    component: 'Input',
+  },
+  {
+    label: '安装位置',
+    field: 'strinstallpos',
+    component: 'Input',
+  },
+  {
+    label: '类型',
+    field: 'ntype',
+    component: 'JDictSelectTag',
+    componentProps: {
+      dictCode: 'ledtype',
+      placeholder: '请选择状态',
+      stringToNumber: true,
+    },
+  },
+  {
+    label: '所属分站',
+    field: 'nsubstationid',
+    component: 'ApiSelect',
+    componentProps: {
+      api: list,
+      labelField: 'strname',
+      valueField: 'nsubstationid',
+    },
+  },
+  {
+    label: '点表',
+    field: 'strtype',
+    component: 'JDictSelectTag',
+    componentProps: {
+      dictCode: 'ledkind',
+      placeholder: '请选择状态',
+    },
+  },
+  {
+    label: '是否模拟数据',
+    field: 'testflag',
+    component: 'RadioGroup',
+    defaultValue: 1,
+    componentProps: () => {
+      return {
+        options: [
+          { label: '是', value: 1, key: '1' },
+          { label: '否', value: 0, key: '2' },
+        ],
+      };
+    },
+  },
+];

+ 25 - 0
src/views/vent/deviceManager/pointTabel/index.vue

@@ -0,0 +1,25 @@
+<template>
+  <NormalTable
+    :columns="columns"
+    :searchFormSchema="searchFormSchema"
+    :list="list"
+    :getImportUrl="getImportUrl"
+    :getExportUrl="getExportUrl"
+    :formSchema="formSchema"
+    :deleteById="deleteById"
+    :batchDelete="batchDeleteById"
+    :saveOrUpdate="saveOrUpdate"
+    designScope="point-tabel"
+    title="点表列表"
+    :showtTab="false"
+  />
+</template>
+
+<script lang="ts" name="system-user" setup>
+  //ts语法
+  import NormalTable from '../comment/NormalTable.vue';
+  import { columns, searchFormSchema, formSchema } from './point.data';
+  import { list, getImportUrl, getExportUrl, deleteById, batchDeleteById, saveOrUpdate } from './point.api';
+</script>
+
+<style scoped></style>

+ 60 - 0
src/views/vent/deviceManager/pointTabel/point.api.ts

@@ -0,0 +1,60 @@
+import { defHttp } from '/@/utils/http/axios';
+import { Modal } from 'ant-design-vue';
+
+enum Api {
+  list = '/ventanaly-device/safety/ventanalyMonitorParams/list',
+  save = '/ventanaly-device/safety/ventanalyMonitorParams/add',
+  edit = '/ventanaly-device/safety/ventanalyMonitorParams/edit',
+  deleteById = '/ventanaly-device/safety/ventanalyMonitorParams/delete',
+  deleteBatch = '/sys/user/deleteBatch',
+  importExcel = '/sys/user/importExcel',
+  exportXls = '/sys/user/exportXls',
+}
+/**
+ * 导出api
+ * @param params
+ */
+export const getExportUrl = Api.exportXls;
+/**
+ * 导入api
+ */
+export const getImportUrl = Api.importExcel;
+/**
+ * 列表接口
+ * @param params
+ */
+export const list = (params) => defHttp.get({ url: Api.list, params });
+
+/**
+ * 删除用户
+ */
+export const deleteById = (params, handleSuccess) => {
+  return defHttp.delete({ url: Api.deleteById, params: params.id }, { joinParamsToUrl: true }).then(() => {
+    handleSuccess();
+  });
+};
+/**
+ * 批量删除用户
+ * @param params
+ */
+export const batchDeleteById = (params, handleSuccess) => {
+  Modal.confirm({
+    title: '确认删除',
+    content: '是否删除选中数据',
+    okText: '确认',
+    cancelText: '取消',
+    onOk: () => {
+      return defHttp.delete({ url: Api.deleteBatch, data: params }, { joinParamsToUrl: true }).then(() => {
+        handleSuccess();
+      });
+    },
+  });
+};
+/**
+ * 保存或者更新用户
+ * @param params
+ */
+export const saveOrUpdate = (params, isUpdate) => {
+  const url = isUpdate ? Api.edit : Api.save;
+  return isUpdate ? defHttp.put({ url: url, params }) : defHttp.post({ url: url, params });
+};

+ 587 - 0
src/views/vent/deviceManager/pointTabel/point.data.ts

@@ -0,0 +1,587 @@
+import { BasicColumn } from '/@/components/Table';
+import { FormSchema } from '/@/components/Table';
+import { getAllRolesList, getAllTenantList } from './point.api';
+import { rules } from '/@/utils/helper/validator';
+import { render } from '/@/utils/common/renderUtils';
+export const columns: BasicColumn[] = [
+  {
+    title: '设备类型',
+    dataIndex: 'devicekind_dictText',
+    width: 120,
+  },
+  {
+    title: 'plc地址',
+    dataIndex: 'plcaddr', 
+    width: 100,
+  },
+  {
+    title: 'plc读写位数',
+    dataIndex: 'floatnum',
+    width: 100,
+    // customRender: render.renderAvatar,
+  },
+  {
+    title: '读写类型',
+    dataIndex: 'datakind_dictText',
+    width: 80,
+    // sorter: true,
+    // customRender: ({ text }) => {
+    //   return render.renderDict(text, 'sex');
+    // },
+  },
+  {
+    title: '值名称',
+    dataIndex: 'valuename',
+    width: 100,
+  },
+  {
+    title: '值code',
+    dataIndex: 'valuecode',
+    width: 100,
+  },
+  {
+    title: '值类型',
+    width: 150,
+    dataIndex: 'valuetype_dictText',
+  },
+  {
+    title: '最小值',
+    width: 100,
+    dataIndex: 'fmin',
+  },
+  {
+    title: '最大值',
+    dataIndex: 'fmax',
+    width: 100,
+  },
+  {
+    title: '模拟最小值',
+    dataIndex: 'testlow',
+    width: 100,
+  },
+  {
+    title: '模拟最大值',
+    width: 100,
+    dataIndex: 'testup',
+  },
+  {
+    title: '小数位数',
+    width: 100,
+    dataIndex: 'floatnum',
+  },
+  {
+    title: '是否保存',
+    dataIndex: 'saveflag_dictText',
+    width: 80,
+  },
+];
+
+export const recycleColumns: BasicColumn[] = [
+  {
+    title: '用户账号',
+    dataIndex: 'username',
+    width: 100,
+  },
+  {
+    title: '用户姓名',
+    dataIndex: 'realname',
+    width: 100,
+  },
+  {
+    title: '头像',
+    dataIndex: 'avatar',
+    width: 80,
+    customRender: render.renderAvatar,
+  },
+  {
+    title: '性别',
+    dataIndex: 'sex',
+    width: 80,
+    sorter: true,
+    customRender: ({ text }) => {
+      return render.renderDict(text, 'sex');
+    },
+  },
+];
+
+export const searchFormSchema: FormSchema[] = [
+  // {
+  //   field: 'devicekind',
+  //   component: 'JTreeSelect',
+  //   label: '下拉树选择',
+  //   helpMessage: ['component模式'],
+  //   componentProps: {
+  //     dict: 'sys_permission,name,id',
+  //     pidField: 'parent_id',
+  //   },
+  //   colProps: {
+  //     span: 12,
+  //   },
+  // },
+  {
+    label: '设备类型',
+    field: 'devicekind',
+    component: 'MTreeSelect',
+    componentProps: {
+      fieldNames: {
+        children: 'children',
+        label: 'itemText',
+        value: 'id',
+      },
+    },
+    colProps: { span: 6 },
+  },
+  {
+    label: 'plc地址',
+    field: 'plcaddr',
+    component: 'Input',
+    colProps: { span: 6 },
+  },
+  {
+    label: 'plc读写位数',
+    field: 'floatnum',
+    component: 'Input',
+    colProps: { span: 6 },
+  },
+  {
+    label: '读写类型',
+    field: 'datakind',
+    component: 'JDictSelectTag',
+    componentProps: {
+      dictCode: 'datakind',
+      placeholder: '请选择读写类型',
+      stringToNumber: true,
+    },
+    colProps: { span: 6 },
+  },
+  {
+    label: '值名称',
+    field: 'valuename',
+    component: 'Input',
+    colProps: { span: 6 },
+  },
+  {
+    label: '值code',
+    field: 'valuecode',
+    component: 'Input',
+    colProps: { span: 6 },
+  },
+  {
+    label: '值类型',
+    field: 'valuetype',
+    component: 'Input',
+    colProps: { span: 6 },
+  },
+  {
+    label: '最小值',
+    field: 'fmin',
+    component: 'Input',
+    colProps: { span: 6 },
+  },
+  {
+    label: '最大值',
+    field: 'fmax',
+    component: 'Input',
+    colProps: { span: 6 },
+  },
+  {
+    label: '模拟最大值',
+    field: 'testup',
+    component: 'Input',
+    colProps: { span: 6 },
+  },
+  {
+    label: '模拟最小值',
+    field: 'testlow',
+    component: 'Input',
+    colProps: { span: 6 },
+  },
+  {
+    label: '小数位数',
+    field: 'floatnum',
+    component: 'Input',
+    colProps: { span: 6 },
+  },
+  {
+    label: '是否保存',
+    field: 'saveflag',
+    component: 'RadioGroup',
+    defaultValue: 1,
+    componentProps: () => {
+      return {
+        options: [
+          { label: '是', value: 1, key: '1' },
+          { label: '否', value: 0, key: '2' },
+        ],
+      };
+    },
+    colProps: { span: 6 },
+  },
+];
+
+export const formSchema: FormSchema[] = [
+  {
+    label: '',
+    field: 'id',
+    component: 'Input',
+    show: false,
+  },
+  // {
+  //   label: '用户账号',
+  //   field: 'username',
+  //   component: 'Input',
+  //   dynamicDisabled: ({ values }) => {
+  //     return !!values.id;
+  //   },
+  //   dynamicRules: ({ model, schema }) => rules.duplicateCheckRule('sys_user', 'username', model, schema, true),
+  // },
+  // {
+  //   label: '登录密码',
+  //   field: 'password',
+  //   component: 'StrengthMeter',
+  //   rules: [
+  //     {
+  //       required: true,
+  //       message: '请输入登录密码',
+  //     },
+  //   ],
+  // },
+  // {
+  //   label: '确认密码',
+  //   field: 'confirmPassword',
+  //   component: 'InputPassword',
+  //   dynamicRules: ({ values }) => rules.confirmPassword(values, true),
+  // },
+  // {
+  //   label: '用户姓名',
+  //   field: 'realname',
+  //   required: true,
+  //   component: 'Input',
+  // },
+  // {
+  //   label: '工号',
+  //   field: 'workNo',
+  //   required: true,
+  //   component: 'Input',
+  //   dynamicRules: ({ model, schema }) => rules.duplicateCheckRule('sys_user', 'work_no', model, schema, true),
+  // },
+  // {
+  //   label: '职务',
+  //   field: 'post',
+  //   required: false,
+  //   component: 'JSelectPosition',
+  //   componentProps: {
+  //     rowKey: 'code',
+  //     labelKey: 'name',
+  //   },
+  // },
+  // {
+  //   label: '角色',
+  //   field: 'selectedroles',
+  //   component: 'ApiSelect',
+  //   componentProps: {
+  //     mode: 'multiple',
+  //     api: getAllRolesList,
+  //     labelField: 'roleName',
+  //     valueField: 'id',
+  //   },
+  // },
+  // {
+  //   label: '所属部门',
+  //   field: 'selecteddeparts',
+  //   component: 'JSelectDept',
+  //   componentProps: ({ formActionType, formModel }) => {
+  //     return {
+  //       sync: false,
+  //       checkStrictly: true,
+  //       defaultExpandLevel: 2,
+
+  //       onSelect: (options, values) => {
+  //         const { updateSchema } = formActionType;
+  //         //所属部门修改后更新负责部门下拉框数据
+  //         updateSchema([
+  //           {
+  //             field: 'departIds',
+  //             componentProps: { options },
+  //           },
+  //         ]);
+  //         //所属部门修改后更新负责部门数据
+  //         formModel.departIds && (formModel.departIds = formModel.departIds.filter((item) => values.value.indexOf(item) > -1));
+  //       },
+  //     };
+  //   },
+  // },
+  // {
+  //   label: '租户',
+  //   field: 'relTenantIds',
+  //   component: 'ApiSelect',
+  //   componentProps: {
+  //     mode: 'multiple',
+  //     api: getAllTenantList,
+  //     numberToString: true,
+  //     labelField: 'name',
+  //     valueField: 'id',
+  //   },
+  // },
+  // {
+  //   label: '身份',
+  //   field: 'userIdentity',
+  //   component: 'RadioGroup',
+  //   defaultValue: 1,
+  //   componentProps: ({ formModel }) => {
+  //     return {
+  //       options: [
+  //         { label: '普通用户', value: 1, key: '1' },
+  //         { label: '上级', value: 2, key: '2' },
+  //       ],
+  //       onChange: () => {
+  //         formModel.userIdentity == 1 && (formModel.departIds = []);
+  //       },
+  //     };
+  //   },
+  // },
+  // {
+  //   label: '负责部门',
+  //   field: 'departIds',
+  //   component: 'Select',
+  //   componentProps: {
+  //     mode: 'multiple',
+  //   },
+  //   ifShow: ({ values }) => values.userIdentity == 2,
+  // },
+  // {
+  //   label: '头像',
+  //   field: 'avatar',
+  //   component: 'JImageUpload',
+  //   componentProps: {
+  //     fileMax: 1,
+  //   },
+  // },
+  // {
+  //   label: '生日',
+  //   field: 'birthday',
+  //   component: 'DatePicker',
+  // },
+  // {
+  //   label: '性别',
+  //   field: 'sex',
+  //   component: 'JDictSelectTag',
+  //   componentProps: {
+  //     dictCode: 'sex',
+  //     placeholder: '请选择性别',
+  //     stringToNumber: true,
+  //   },
+  // },
+  // {
+  //   label: '邮箱',
+  //   field: 'email',
+  //   component: 'Input',
+  //   rules: rules.rule('email', false),
+  // },
+  // {
+  //   label: '手机号码',
+  //   field: 'phone',
+  //   component: 'Input',
+  //   dynamicRules: ({ model, schema }) => {
+  //     return [{ ...rules.duplicateCheckRule('sys_user', 'phone', model, schema, true)[0] }, { pattern: /^1[3|4|5|7|8|9][0-9]\d{8}$/, message: '手机号码格式有误' }];
+  //   },
+  // },
+  // {
+  //   label: '座机',
+  //   field: 'telephone',
+  //   component: 'Input',
+  //   rules: [{ pattern: /^0\d{2,3}-[1-9]\d{6,7}$/, message: '请输入正确的座机号码' }],
+  // },
+  // {
+  //   label: '工作流引擎',
+  //   field: 'activitiSync',
+  //   defaultValue: 1,
+  //   component: 'JDictSelectTag',
+  //   componentProps: {
+  //     dictCode: 'activiti_sync',
+  //     type: 'radio',
+  //     stringToNumber: true,
+  //   },
+  // },
+  {
+    label: '设备类型',
+    field: 'devicekind',
+    component: 'MTreeSelect',
+    componentProps: {
+      fieldNames: {
+        children: 'children',
+        label: 'itemText',
+        value: 'id',
+      },
+    },
+  },
+  {
+    label: 'plc地址',
+    field: 'plcaddr',
+    component: 'Input',
+  },
+  {
+    label: 'plc读写位数',
+    field: 'floatnum',
+    component: 'Input',
+  },
+  {
+    label: '读写类型',
+    field: 'datakind',
+    component: 'JDictSelectTag',
+    componentProps: {
+      dictCode: 'datakind',
+      placeholder: '请选择读写类型',
+      stringToNumber: true,
+    },
+  },
+  {
+    label: '值名称',
+    field: 'valuename',
+    component: 'Input',
+  },
+  {
+    label: '值code',
+    field: 'valuecode',
+    component: 'Input',
+  },
+  {
+    label: '值类型',
+    field: 'valuetype',
+    component: 'JDictSelectTag',
+    componentProps: {
+      dictCode: 'valuetype',
+      placeholder: '请选择读写类型',
+      stringToNumber: true,
+    },
+  },
+  {
+    label: '最小值',
+    field: 'fmin',
+    component: 'Input',
+  },
+  {
+    label: '最大值',
+    field: 'fmax',
+    component: 'Input',
+  },
+  {
+    label: '模拟最大值',
+    field: 'testup',
+    component: 'Input',
+  },
+  {
+    label: '模拟最小值',
+    field: 'testlow',
+    component: 'Input',
+  },
+  {
+    label: '小数位数',
+    field: 'floatnum',
+    component: 'Input',
+  },
+  {
+    label: '是否保存',
+    field: 'saveflag',
+    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',
+    },
+  },
+];

+ 25 - 0
src/views/vent/deviceManager/sensorTabel/index.vue

@@ -0,0 +1,25 @@
+<template>
+  <NormalTable
+    :columns="columns"
+    :searchFormSchema="searchFormSchema"
+    :list="list"
+    :getImportUrl="getImportUrl"
+    :getExportUrl="getExportUrl"
+    :formSchema="formSchema"
+    :deleteById="deleteById"
+    :batchDelete="batchDeleteById"
+    :saveOrUpdate="saveOrUpdate"
+    designScope="sensor-tabel"
+    title="传感器列表"
+    :showtTab="true"
+  />
+</template>
+
+<script lang="ts" name="system-user" setup>
+  //ts语法
+  import NormalTable from '../comment/NormalTable.vue';
+  import { columns, searchFormSchema, formSchema } from './sensor.data';
+  import { list, getImportUrl, getExportUrl, deleteById, batchDeleteById, saveOrUpdate } from './sensor.api';
+</script>
+
+<style scoped></style>

+ 60 - 0
src/views/vent/deviceManager/sensorTabel/sensor.api.ts

@@ -0,0 +1,60 @@
+import { defHttp } from '/@/utils/http/axios';
+import { Modal } from 'ant-design-vue';
+
+enum Api {
+  list = '/ventanaly-device/safety/ventanalyModelSensor/list',
+  save = '/ventanaly-device/safety/ventanalyModelSensor/add',
+  edit = '/ventanaly-device/safety/ventanalyModelSensor/edit',
+  deleteById = '/ventanaly-device/safety/ventanalyModelSensor/delete',
+  deleteBatch = '/sys/user/deleteBatch',
+  importExcel = '/sys/user/importExcel',
+  exportXls = '/sys/user/exportXls',
+}
+/**
+ * 导出api
+ * @param params
+ */
+export const getExportUrl = Api.exportXls;
+/**
+ * 导入api
+ */
+export const getImportUrl = Api.importExcel;
+/**
+ * 列表接口
+ * @param params
+ */
+export const list = (params) => defHttp.get({ url: Api.list, params });
+
+/**
+ * 删除用户
+ */
+export const deleteById = (params, handleSuccess) => {
+  return defHttp.delete({ url: Api.deleteById, params: params.id }, { joinParamsToUrl: true }).then(() => {
+    handleSuccess();
+  });
+};
+/**
+ * 批量删除用户
+ * @param params
+ */
+export const batchDeleteById = (params, handleSuccess) => {
+  Modal.confirm({
+    title: '确认删除',
+    content: '是否删除选中数据',
+    okText: '确认',
+    cancelText: '取消',
+    onOk: () => {
+      return defHttp.delete({ url: Api.deleteBatch, data: params }, { joinParamsToUrl: true }).then(() => {
+        handleSuccess();
+      });
+    },
+  });
+};
+/**
+ * 保存或者更新用户
+ * @param params
+ */
+export const saveOrUpdate = (params, isUpdate) => {
+  const url = isUpdate ? Api.edit : Api.save;
+  return isUpdate ? defHttp.put({ url: url, params }) : defHttp.post({ url: url, params });
+};

+ 137 - 0
src/views/vent/deviceManager/sensorTabel/sensor.data.ts

@@ -0,0 +1,137 @@
+import { BasicColumn } from '/@/components/Table';
+import { FormSchema } from '/@/components/Table';
+import { list } from '../substationTabel/substation.api';
+
+export const columns: BasicColumn[] = [
+  {
+    title: '名称',
+    dataIndex: 'strname',
+    width: 120,
+  },
+  {
+    title: '安装位置',
+    dataIndex: 'strinstallpos',
+    width: 100,
+  },
+  {
+    title: '距巷道起点长度',
+    dataIndex: 'flength',
+    width: 100,
+  },
+  {
+    title: '传感器类型',
+    dataIndex: 'nsensortype_dictText',
+    width: 80,
+  },
+  {
+    title: '所属分站',
+    width: 150,
+    dataIndex: 'stationname',
+  },
+];
+
+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: 'nsubstationid',
+    component: 'ApiSelect',
+    componentProps: {
+      api: list,
+      labelField: 'strname',
+      valueField: 'nsubstationid',
+    },
+    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: 'flength',
+    component: 'InputNumber',
+  },
+  {
+    label: '传感器类型',
+    field: 'nsensortype',
+    component: 'JDictSelectTag',
+    componentProps: {
+      dictCode: 'nsensortype',
+      placeholder: '请选择状态',
+    },
+  },
+  {
+    label: '报警上限',
+    field: 'fdownlimit',
+    component: 'InputNumber',
+  },
+  {
+    label: '报警下限',
+    field: 'fuplimit',
+    component: 'InputNumber',
+  },
+  {
+    label: '所属分站',
+    field: 'nsubstationid',
+    component: 'ApiSelect',
+    componentProps: {
+      api: list,
+      labelField: 'strname',
+      valueField: 'nsubstationid',
+    },
+  },
+  {
+    label: '是否模拟数据',
+    field: 'testflag',
+    component: 'RadioGroup',
+    defaultValue: 1,
+    componentProps: () => {
+      return {
+        options: [
+          { label: '是', value: 1, key: '1' },
+          { label: '否', value: 0, key: '2' },
+        ],
+      };
+    },
+  },
+];

+ 28 - 0
src/views/vent/deviceManager/substationTabel/index.vue

@@ -0,0 +1,28 @@
+<template>
+  <NormalTable
+    ref="normalTabel"
+    :columns="columns"
+    :searchFormSchema="searchFormSchema"
+    :list="list"
+    :getImportUrl="getImportUrl"
+    :getExportUrl="getExportUrl"
+    :formSchema="formSchema"
+    :deleteById="deleteById"
+    :batchDelete="batchDeleteById"
+    :saveOrUpdate="saveOrUpdate"
+    designScope="substation-tabel"
+    title="分站列表"
+    :showtTab="false"
+  />
+</template>
+
+<script lang="ts" name="system-user" setup>
+  //ts语法
+  import { ref } from 'vue';
+  import NormalTable from '../comment/NormalTable.vue';
+  import { columns, searchFormSchema, formSchema } from './substation.data';
+  import { list, getImportUrl, getExportUrl, deleteById, batchDeleteById, saveOrUpdate } from './substation.api';
+  const normalTabel = ref();
+</script>
+
+<style scoped></style>

+ 60 - 0
src/views/vent/deviceManager/substationTabel/substation.api.ts

@@ -0,0 +1,60 @@
+import { defHttp } from '/@/utils/http/axios';
+import { Modal } from 'ant-design-vue';
+
+enum Api {
+  list = '/ventanaly-device/safety/ventanalySubStation/alllist',
+  save = '/ventanaly-device/safety/ventanalySubStation/add',
+  edit = '/ventanaly-device/safety/ventanalySubStation/edit',
+  deleteById = '/ventanaly-device/safety/ventanalySubStation/delete',
+  deleteBatch = '/sys/user/deleteBatch',
+  importExcel = '/sys/user/importExcel',
+  exportXls = '/sys/user/exportXls',
+}
+/**
+ * 导出api
+ * @param params
+ */
+export const getExportUrl = Api.exportXls;
+/**
+ * 导入api
+ */
+export const getImportUrl = Api.importExcel;
+/**
+ * 列表接口
+ * @param params
+ */
+export const list = (params) => defHttp.get({ url: Api.list, params });
+
+/**
+ * 删除用户
+ */
+export const deleteById = (params, handleSuccess) => {
+  return defHttp.delete({ url: Api.deleteById, params: params.nsubstationid }, { joinParamsToUrl: true }).then(() => {
+    handleSuccess();
+  });
+};
+/**
+ * 批量删除用户
+ * @param params
+ */
+export const batchDeleteById = (params, handleSuccess) => {
+  Modal.confirm({
+    title: '确认删除',
+    content: '是否删除选中数据',
+    okText: '确认',
+    cancelText: '取消',
+    onOk: () => {
+      return defHttp.delete({ url: Api.deleteBatch, data: params }, { joinParamsToUrl: true }).then(() => {
+        handleSuccess();
+      });
+    },
+  });
+};
+/**
+ * 保存或者更新用户
+ * @param params
+ */
+export const saveOrUpdate = (params, isUpdate) => {
+  const url = isUpdate ? Api.edit : Api.save;
+  return isUpdate ? defHttp.put({ url: url, params }) : defHttp.post({ url: url, params });
+};

+ 145 - 0
src/views/vent/deviceManager/substationTabel/substation.data.ts

@@ -0,0 +1,145 @@
+import { BasicColumn } from '/@/components/Table';
+import { FormSchema } from '/@/components/Table';
+import { rules } from '/@/utils/helper/validator';
+import { render } from '/@/utils/common/renderUtils';
+export const columns: BasicColumn[] = [
+  {
+    title: '名称',
+    dataIndex: 'strname',
+    width: 120,
+  },
+  {
+    title: '安装位置',
+    dataIndex: 'strinstallpos',
+    width: 100,
+  },
+  {
+    title: '读取数据方式',
+    dataIndex: 'strtype',
+    width: 100,
+    // customRender: render.renderAvatar,
+  },
+  {
+    title: '分站用途',
+    dataIndex: 'nkj980use_dictText',
+    width: 80,
+  },
+  {
+    title: '分站IP地址',
+    dataIndex: 'strip',
+    width: 100,
+  },
+  {
+    title: '链接状态',
+    dataIndex: 'linkstatus_dictText',
+    width: 100,
+  },
+  {
+    title: '备注',
+    width: 150,
+    dataIndex: 'strremark',
+  },
+];
+
+export const recycleColumns: BasicColumn[] = [
+  {
+    title: '名称',
+    dataIndex: 'strname',
+    width: 100,
+  },
+  {
+    title: '安装位置',
+    dataIndex: 'strinstallpos',
+    width: 100,
+  },
+  {
+    title: '链接状态',
+    dataIndex: 'linkstatus',
+    width: 80,
+    customRender: render.renderAvatar,
+  },
+];
+
+export const searchFormSchema: FormSchema[] = [
+  {
+    label: '名称',
+    field: 'strname',
+    component: 'Input',
+    colProps: { span: 6 },
+  },
+  {
+    label: '安装位置',
+    field: 'strinstallpos',
+    component: 'Input',
+    colProps: { span: 6 },
+  },
+  {
+    label: '链接状态',
+    field: 'linkstatus',
+    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: 'strtype',
+    component: 'JDictSelectTag',
+    componentProps: {
+      dictCode: 'getdatatype',
+      placeholder: '请选择分站用途',
+    },
+  },
+  {
+    label: '分站用途',
+    field: 'nkj980use',
+    component: 'JDictSelectTag',
+    componentProps: {
+      dictCode: 'nkj980use',
+      placeholder: '请选择分站用途',
+      stringToNumber: true,
+    },
+  },
+  {
+    label: '分站IP地址',
+    field: 'strip',
+    component: 'Input',
+  },
+  {
+    label: '链接状态',
+    field: 'linkstatus_dictText',
+    component: 'JDictSelectTag',
+    componentProps: {
+      dictCode: 'linkstatus',
+      placeholder: '请选择链接状态',
+      stringToNumber: true,
+    },
+  },
+  {
+    label: '备注',
+    field: 'strremark',
+    component: 'InputTextArea',
+  },
+];

+ 26 - 0
src/views/vent/deviceManager/windWindowTabel/index.vue

@@ -0,0 +1,26 @@
+<template>
+  <NormalTable
+    :columns="columns"
+    :searchFormSchema="searchFormSchema"
+    :list="list"
+    :getImportUrl="getImportUrl"
+    :getExportUrl="getExportUrl"
+    :formSchema="formSchema"
+    :deleteById="deleteById"
+    :batchDelete="batchDeleteById"
+    :saveOrUpdate="saveOrUpdate"
+    designScope="ventanalyWindow-tabel"
+    title="风窗列表"
+    :showtTab="true"
+    deviceType="window_normal"
+  />
+</template>
+
+<script lang="ts" name="system-user" setup>
+  //ts语法
+  import NormalTable from '../comment/NormalTable.vue';
+  import { columns, searchFormSchema, formSchema } from './ventanalyWindow.data';
+  import { list, getImportUrl, getExportUrl, deleteById, batchDeleteById, saveOrUpdate } from './ventanalyWindow.api';
+</script>
+
+<style scoped></style>

+ 60 - 0
src/views/vent/deviceManager/windWindowTabel/ventanalyWindow.api.ts

@@ -0,0 +1,60 @@
+import { defHttp } from '/@/utils/http/axios';
+import { Modal } from 'ant-design-vue';
+
+enum Api {
+  list = '/ventanaly-device/safety/ventanalyWindow/list',
+  save = '/ventanaly-device/safety/ventanalyWindow/add',
+  edit = '/ventanaly-device/safety/ventanalyWindow/edit',
+  deleteById = '/ventanaly-device/safety/ventanalyWindow/delete',
+  deleteBatch = '/sys/user/deleteBatch',
+  importExcel = '/sys/user/importExcel',
+  exportXls = '/sys/user/exportXls',
+}
+/**
+ * 导出api
+ * @param params
+ */
+export const getExportUrl = Api.exportXls;
+/**
+ * 导入api
+ */
+export const getImportUrl = Api.importExcel;
+/**
+ * 列表接口
+ * @param params
+ */
+export const list = (params) => defHttp.get({ url: Api.list, params });
+
+/**
+ * 删除用户
+ */
+export const deleteById = (params, handleSuccess) => {
+  return defHttp.delete({ url: Api.deleteById, params: params.id }, { joinParamsToUrl: true }).then(() => {
+    handleSuccess();
+  });
+};
+/**
+ * 批量删除用户
+ * @param params
+ */
+export const batchDeleteById = (params, handleSuccess) => {
+  Modal.confirm({
+    title: '确认删除',
+    content: '是否删除选中数据',
+    okText: '确认',
+    cancelText: '取消',
+    onOk: () => {
+      return defHttp.delete({ url: Api.deleteBatch, data: params }, { joinParamsToUrl: true }).then(() => {
+        handleSuccess();
+      });
+    },
+  });
+};
+/**
+ * 保存或者更新用户
+ * @param params
+ */
+export const saveOrUpdate = (params, isUpdate) => {
+  const url = isUpdate ? Api.edit : Api.save;
+  return isUpdate ? defHttp.put({ url: url, params }) : defHttp.post({ url: url, params });
+};

+ 155 - 0
src/views/vent/deviceManager/windWindowTabel/ventanalyWindow.data.ts

@@ -0,0 +1,155 @@
+import { BasicColumn } from '/@/components/Table';
+import { FormSchema } from '/@/components/Table';
+import { list } from '../substationTabel/substation.api';
+
+export const columns: BasicColumn[] = [
+  {
+    title: '名称',
+    dataIndex: 'strname',
+    width: 120,
+  },
+  {
+    title: '安装位置',
+    dataIndex: 'strinstallpos',
+    width: 100,
+  },
+  {
+    title: '风窗类型',
+    dataIndex: 'nwindowtype_dictText',
+    width: 100,
+  },
+  {
+    title: '风窗净宽(m)',
+    dataIndex: 'fclearwidth',
+    width: 80,
+  },
+  {
+    title: '活动扇叶高度(m)',
+    dataIndex: 'fperheight',
+    width: 100,
+  },
+  {
+    title: '活动扇叶数量',
+    dataIndex: 'nwindow',
+    width: 100,
+  },
+  {
+    title: '所属分站',
+    width: 150,
+    dataIndex: 'stationname',
+  },
+  {
+    title: '安装位置巷道面积',
+    width: 100,
+    dataIndex: 'finstalltunarea',
+  },
+];
+
+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: 'nwindowtype',
+    component: 'JDictSelectTag',
+    componentProps: {
+      dictCode: 'user_status',
+      placeholder: '请选择读写类型',
+      stringToNumber: true,
+    },
+    colProps: { span: 6 },
+  },
+  {
+    label: '所属分站',
+    field: 'nsubstationid',
+    component: 'ApiSelect',
+    componentProps: {
+      api: list,
+      labelField: 'strname',
+      valueField: 'nsubstationid',
+    },
+    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: 'nwindowtype',
+    component: 'JDictSelectTag',
+    componentProps: {
+      dictCode: 'nwindowtype',
+      placeholder: '请选择',
+      stringToNumber: true,
+    },
+  },
+  {
+    label: '风窗净宽(m)',
+    field: 'fclearwidth',
+    component: 'InputNumber',
+  },
+  {
+    label: '活动扇叶高度(m)',
+    field: 'fperheight',
+    component: 'InputNumber',
+  },
+  {
+    label: '风门道数',
+    field: 'ndoorcount',
+    component: 'InputNumber',
+  },
+  {
+    label: '所属分站',
+    field: 'nsubstationid',
+    component: 'ApiSelect',
+    componentProps: {
+      api: list,
+      labelField: 'strname',
+      valueField: 'nsubstationid',
+    },
+  },
+  {
+    label: '安装位置巷道面积',
+    field: 'finstalltunarea',
+    component: 'InputNumber',
+  },
+];

+ 25 - 0
src/views/vent/deviceManager/windfindingTabel/index.vue

@@ -0,0 +1,25 @@
+<template>
+  <NormalTable
+    :columns="columns"
+    :searchFormSchema="searchFormSchema"
+    :list="list"
+    :getImportUrl="getImportUrl"
+    :getExportUrl="getExportUrl"
+    :formSchema="formSchema"
+    :deleteById="deleteById"
+    :batchDelete="batchDeleteById"
+    :saveOrUpdate="saveOrUpdate"
+    designScope="windfinding-tabel"
+    title="测风装置列表"
+    :showtTab="true"
+  />
+</template>
+
+<script lang="ts" name="system-user" setup>
+  //ts语法
+  import NormalTable from '../comment/NormalTable.vue';
+  import { columns, searchFormSchema, formSchema } from './windfinding.data';
+  import { list, getImportUrl, getExportUrl, deleteById, batchDeleteById, saveOrUpdate } from './windfinding.api';
+</script>
+
+<style scoped></style>

+ 60 - 0
src/views/vent/deviceManager/windfindingTabel/windfinding.api.ts

@@ -0,0 +1,60 @@
+import { defHttp } from '/@/utils/http/axios';
+import { Modal } from 'ant-design-vue';
+
+enum Api {
+  list = '/ventanaly-device/safety/ventanalyWind/list',
+  save = '/ventanaly-device/safety/ventanalyWind/add',
+  edit = '/ventanaly-device/safety/ventanalyWind/edit',
+  deleteById = '/ventanaly-device/safety/ventanalyWind/delete',
+  deleteBatch = '/sys/user/deleteBatch',
+  importExcel = '/sys/user/importExcel',
+  exportXls = '/sys/user/exportXls',
+}
+/**
+ * 导出api
+ * @param params
+ */
+export const getExportUrl = Api.exportXls;
+/**
+ * 导入api
+ */
+export const getImportUrl = Api.importExcel;
+/**
+ * 列表接口
+ * @param params
+ */
+export const list = (params) => defHttp.get({ url: Api.list, params });
+
+/**
+ * 删除用户
+ */
+export const deleteById = (params, handleSuccess) => {
+  return defHttp.delete({ url: Api.deleteById, params: params.id }, { joinParamsToUrl: true }).then(() => {
+    handleSuccess();
+  });
+};
+/**
+ * 批量删除用户
+ * @param params
+ */
+export const batchDeleteById = (params, handleSuccess) => {
+  Modal.confirm({
+    title: '确认删除',
+    content: '是否删除选中数据',
+    okText: '确认',
+    cancelText: '取消',
+    onOk: () => {
+      return defHttp.delete({ url: Api.deleteBatch, data: params }, { joinParamsToUrl: true }).then(() => {
+        handleSuccess();
+      });
+    },
+  });
+};
+/**
+ * 保存或者更新用户
+ * @param params
+ */
+export const saveOrUpdate = (params, isUpdate) => {
+  const url = isUpdate ? Api.edit : Api.save;
+  return isUpdate ? defHttp.put({ url: url, params }) : defHttp.post({ url: url, params });
+};

+ 133 - 0
src/views/vent/deviceManager/windfindingTabel/windfinding.data.ts

@@ -0,0 +1,133 @@
+import { BasicColumn } from '/@/components/Table';
+import { FormSchema } from '/@/components/Table';
+import { list } from '../substationTabel/substation.api';
+
+export const columns: BasicColumn[] = [
+  {
+    title: '名称',
+    dataIndex: 'strname',
+    width: 120,
+  },
+  {
+    title: '安装位置',
+    dataIndex: 'strinstallpos',
+    width: 100,
+  },
+  {
+    title: '安装位置巷道断面',
+    dataIndex: 'ftunarea',
+    width: 100,
+  },
+  {
+    title: '风速传感器1修正系数',
+    dataIndex: 'famendv1',
+    width: 100,
+  },
+  {
+    title: '风速传感器2修正系数',
+    dataIndex: 'famendv2',
+    width: 100,
+  },
+  {
+    title: '风速传感器3修正系数',
+    dataIndex: 'famendv3',
+    width: 100,
+  },
+  {
+    title: '所属分站',
+    width: 150,
+    dataIndex: 'stationname',
+  },
+];
+
+export const recycleColumns: BasicColumn[] = [
+  {
+    title: '名称',
+    dataIndex: 'strname',
+    width: 100,
+  },
+  {
+    title: '是否为常闭型',
+    dataIndex: 'bnormalclose',
+    width: 100,
+  },
+  {
+    title: '所属分站',
+    width: 150,
+    dataIndex: 'stationname',
+  },
+];
+
+export const searchFormSchema: FormSchema[] = [
+  {
+    label: '名称',
+    field: 'strname',
+    component: 'Input',
+    colProps: { span: 6 },
+  },
+  {
+    label: '安装位置',
+    field: 'strinstallpos',
+    component: 'Input',
+    colProps: { span: 6 },
+  },
+  {
+    label: '所属分站',
+    field: 'nsubstationid',
+    component: 'ApiSelect',
+    componentProps: {
+      api: list,
+      labelField: 'strname',
+      valueField: 'nsubstationid',
+    },
+  },
+];
+
+export const formSchema: FormSchema[] = [
+  {
+    label: '',
+    field: 'id',
+    component: 'Input',
+    show: false,
+  },
+  {
+    label: '名称',
+    field: 'strname',
+    component: 'Input',
+  },
+  {
+    label: '安装位置',
+    field: 'strinstallpos',
+    component: 'Input',
+  },
+  {
+    label: '安装位置巷道断面',
+    field: 'ftunarea',
+    component: 'InputNumber',
+  },
+  {
+    label: '风速传感器1修正系数',
+    field: 'famendv1',
+    component: 'InputNumber',
+  },
+  {
+    label: '风速传感器2修正系数',
+    field: 'famendv2',
+    component: 'InputNumber',
+  },
+  {
+    label: '风速传感器3修正系数',
+    field: 'famendv3',
+    component: 'InputNumber',
+  },
+  {
+    label: '所属分站',
+    field: 'nsubstationid',
+    component: 'ApiSelect',
+    componentProps: {
+      api: list,
+      labelField: 'strname',
+      valueField: 'nsubstationid',
+    },
+  },
+];

+ 159 - 0
src/views/vent/monitorManager/comment/MonitorTable.vue

@@ -0,0 +1,159 @@
+<template>
+  <div>
+    <BasicTable @register="registerTable" :rowSelection="rowSelection" :pagination="false" :loading="loading">
+      <template #tableTitle>
+        <div></div>
+      </template>
+      <template #tableTop>
+        <div></div>
+      </template>
+      <template #action="{ record }">
+        <TableAction :actions="getActions(record)" :dropDownActions="getDropDownAction(record)" />
+      </template>
+    </BasicTable>
+  </div>
+</template>
+
+<script lang="ts" name="system-user" setup>
+  //ts语法
+  import { defineExpose, toRaw, watch, ref } from 'vue';
+  import { BasicTable, TableAction } from '/@/components/Table';
+  import { useListPage } from '/@/hooks/system/useListPage';
+  import { useMessage } from '/@/hooks/web/useMessage';
+  import { getTableHeaderColumns, setWebColumnsKey } from '/@/hooks/web/useWebColumns';
+
+  const $message = useMessage();
+  const props = defineProps({
+    columnsType: {
+      type: String,
+      required: true,
+    },
+    dataSource: {
+      type: Array,
+      required: true,
+    },
+    searchFormSchema: {
+      type: Array,
+      default: () => [],
+    },
+    list: {
+      type: Function,
+      // required: true,
+    },
+    designScope: {
+      type: String,
+    },
+    title: {
+      type: String,
+    },
+
+  });
+  const dataTableSource = ref([]);
+  const loading = ref(true)
+
+
+  watch(
+    () => {
+      // loading.value = true
+      return props.dataSource
+    },
+    (newVal) => {
+      const list = [];
+      newVal.forEach((item) => {
+        list.push(toRaw(item));
+      });
+      if (newVal) {
+        // setLoading(false);
+      }
+
+      dataTableSource.value = list;
+      loading.value = false
+    }
+  );
+  setWebColumnsKey(props.columnsType);
+  const columns = getTableHeaderColumns;
+  // 列表页面公共参数、方法
+  const { prefixCls, tableContext, doRequest } = useListPage({
+    designScope: props.designScope,
+    tableProps: {
+      title: props.title,
+      // api: props.list,
+      dataSource: dataTableSource,
+      columns: columns.value,
+      size: 'small',
+      useSearchForm: false,
+      showTableSetting: false,
+      maxHeight: 150,
+      bordered: false,
+      actionColumn: {
+        width: 180,
+      },
+      beforeFetch: (params) => {
+        return Object.assign({ column: 'createTime', order: 'desc' }, params);
+      },
+    },
+  });
+
+  //注册table数据
+  const [registerTable, { reload, setLoading }, { rowSelection, selectedRowKeys }] = tableContext;
+
+  rowSelection.type = 'radio'
+
+  function openDetail(record) {
+    record;
+  }
+
+  /**
+   * 查看
+   */
+  // function handleDetail(record) {
+  //   iframeUrl.value = `${glob.uploadUrl}/sys/annountCement/show/${record.id}?token=${getToken()}`;
+  //   openDetail(true);
+  // }
+  /**
+   * 操作列定义
+   * @param record
+   */
+  function getActions(record) {
+    return [
+    {
+        label: '开启',
+        onClick: openDetail.bind(null, record),
+      },
+      {
+        label: '关闭',
+        onClick: openDetail.bind(null, record),
+      },
+    ];
+  }
+  /**
+   * 下拉操作栏
+   */
+  function getDropDownAction(record) {
+    return [
+      // {
+      //   label: '删除',
+      //   popConfirm: {
+      //     title: '是否确认删除',
+      //     confirm: handleDelete.bind(null, record),
+      //   },
+      // },
+      // {
+      //   label: '查看',
+      //   onClick: handleDetail.bind(null, record),
+      // },
+    ];
+  }
+  defineExpose({
+    doRequest,
+  });
+</script>
+
+<style scoped lang="less">
+  :deep(.ant-table-body) {
+    height: auto !important;
+  }
+  :deep(.jeecg-basic-table .ant-table-wrapper .ant-table-title){
+    min-height: 0;
+  }
+</style>

+ 98 - 0
src/views/vent/monitorManager/comment/MonitorTable1.vue

@@ -0,0 +1,98 @@
+<template>
+  <div class="monitor-table">
+    <!-- <BasicTable @register="registerTable" :rowSelection="rowSelection">
+      <template #action="{ record }">
+        <TableAction :actions="getActions(record)" :dropDownActions="getDropDownAction(record)" />
+      </template>
+    </BasicTable> -->
+    <a-table :row-selection="rowSelection" :columns="columns.value" :data-source="dataTableSource" row-key="dataIndex">
+      <template #name="{ text }">
+        <a>{{ text }}</a>
+      </template>
+    </a-table>
+  </div>
+</template>
+
+<script lang="ts" name="system-user" setup>
+  //ts语法
+  import { computed } from '@vue/reactivity';
+import { debug } from 'console';
+import { defineExpose, toRaw, watch, ref } from 'vue';
+  import { BasicColumn } from '/@/components/Table';
+  import { getTableHeaderColumns, setWebColumnsKey } from '/@/hooks/web/useWebColumns';
+
+  const props = defineProps({
+    columnsType: {
+      type: String,
+      required: true,
+    },
+    dataSource: {
+      type: Array,
+      required: true,
+    },
+    searchFormSchema: {
+      type: Array,
+      default: () => [],
+    },
+    list: {
+      type: Function,
+      // required: true,
+    },
+    designScope: {
+      type: String,
+    },
+    title: {
+      type: String,
+    },
+  });
+  const dataTableSource = ref([]);
+  watch(
+    () => props.dataSource,
+    (newVal) => {
+      const list = [];
+      newVal.forEach((item) => {
+        list.push(toRaw(item));
+      });
+      dataTableSource.value = list;
+    }
+  );
+  setWebColumnsKey(props.columnsType);
+  const columns = computed(() => getTableHeaderColumns);
+
+  /**
+   * 操作列定义
+   * @param record
+   */
+  function getActions(record) {
+    return [
+      {
+        label: '详情',
+        onClick: openDetail.bind(null, record),
+      },
+    ];
+  }
+
+  const rowSelection = {
+    onChange: (selectedRowKeys: [], selectedRows: BasicColumn[]) => {
+      console.log(`selectedRowKeys: ${selectedRowKeys}`, 'selectedRows: ', selectedRows);
+    },
+    // getCheckboxProps: (record: BasicColumn) => ({
+    //   disabled: record.name === 'Disabled User', // Column configuration not to be checked
+    //   name: record.name,
+    // }),
+  };
+
+
+  function openDetail(record) {
+    record;
+  }
+</script>
+
+<style scoped lang="less">
+  :deep(.ant-table-body) {
+    height: auto !important;
+  }
+  .monitor-table{
+    width: 100%;
+  }
+</style>

+ 60 - 0
src/views/vent/monitorManager/fanLocalMonitor/fanLocal.api.ts

@@ -0,0 +1,60 @@
+import { defHttp } from '/@/utils/http/axios';
+import { Modal } from 'ant-design-vue';
+
+enum Api {
+  list = '/ventanaly-device/monitor/device',
+  save = '/ventanaly-device/safety/ventanalyGate/add',
+  edit = '/ventanaly-device/safety/ventanalyGate/edit',
+  deleteById = '/ventanaly-device/safety/ventanalyGate/delete',
+  deleteBatch = '/sys/user/deleteBatch',
+  importExcel = '/sys/user/importExcel',
+  exportXls = '/sys/user/exportXls',
+}
+/**
+ * 导出api
+ * @param params
+ */
+export const getExportUrl = Api.exportXls;
+/**
+ * 导入api
+ */
+export const getImportUrl = Api.importExcel;
+/**
+ * 列表接口
+ * @param params
+ */
+export const list = (params) => defHttp.get({ url: Api.list, params });
+
+/**
+ * 删除用户
+ */
+export const deleteById = (params, handleSuccess) => {
+  return defHttp.delete({ url: Api.deleteById, params }, { joinParamsToUrl: true }).then(() => {
+    handleSuccess();
+  });
+};
+/**
+ * 批量删除用户
+ * @param params
+ */
+export const batchDeleteById = (params, handleSuccess) => {
+  Modal.confirm({
+    title: '确认删除',
+    content: '是否删除选中数据',
+    okText: '确认',
+    cancelText: '取消',
+    onOk: () => {
+      return defHttp.delete({ url: Api.deleteBatch, data: params }, { joinParamsToUrl: true }).then(() => {
+        handleSuccess();
+      });
+    },
+  });
+};
+/**
+ * 保存或者更新用户
+ * @param params
+ */
+export const saveOrUpdate = (params, isUpdate) => {
+  const url = isUpdate ? Api.edit : Api.save;
+  return defHttp.put({ url: url, params });
+};

+ 277 - 0
src/views/vent/monitorManager/fanLocalMonitor/fanLocal.data.ts

@@ -0,0 +1,277 @@
+import { BasicColumn } from '/@/components/Table';
+import { FormSchema } from '/@/components/Table';
+import { rules } from '/@/utils/helper/validator';
+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',
+    },
+  },
+];

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