浏览代码

更新,完成站点详情界面样式展示

linqilong 8 月之前
父节点
当前提交
b669352b86
共有 46 个文件被更改,包括 1784 次插入1210 次删除
  1. 1 1
      index.html
  2. 385 7
      package-lock.json
  3. 2 0
      package.json
  4. 二进制
      public/favicon.ico
  5. 11 0
      src/api/home.ts
  6. 二进制
      src/assets/images/dike.png
  7. 二进制
      src/assets/images/layout/header-title-tsq.png
  8. 二进制
      src/assets/images/layout/header-title.png
  9. 二进制
      src/assets/images/layout/header-title2.png
  10. 二进制
      src/assets/images/layout/menu-left.png
  11. 二进制
      src/assets/images/layout/menu-right.png
  12. 二进制
      src/assets/images/logo.png
  13. 二进制
      src/assets/images/sz-ad.png
  14. 二进制
      src/assets/images/sz-ddl.png
  15. 二进制
      src/assets/images/sz-rjy.png
  16. 二进制
      src/assets/images/sz-tn.png
  17. 二进制
      src/assets/images/sz-wd.png
  18. 二进制
      src/assets/images/sz-zd.png
  19. 二进制
      src/assets/images/sz-zl.png
  20. 二进制
      src/assets/images/tag-background.png
  21. 61 0
      src/assets/styles/element/index.scss
  22. 1 7
      src/assets/styles/index.scss
  23. 19 0
      src/assets/styles/introduce.scss
  24. 8 632
      src/components/AntvMap/index.vue
  25. 3 5
      src/components/Chart.vue
  26. 7 6
      src/components/RightFrame.vue
  27. 26 0
      src/components/StripeTable.vue
  28. 57 0
      src/components/tag/ColorTag.vue
  29. 63 0
      src/components/tag/ImageTag.vue
  30. 134 66
      src/layout/components/Navbar.vue
  31. 4 1
      src/main.ts
  32. 5 5
      src/router/index.ts
  33. 31 0
      src/stores/map.ts
  34. 5 8
      src/utils/auth.ts
  35. 21 0
      src/utils/bus.ts
  36. 70 0
      src/utils/color.ts
  37. 20 5
      src/utils/errorCode.ts
  38. 1 1
      src/utils/image.ts
  39. 1 8
      src/utils/index.ts
  40. 373 323
      src/utils/mapConfig.ts
  41. 112 119
      src/utils/request.ts
  42. 27 0
      src/utils/ruoyi.ts
  43. 53 0
      src/utils/station.ts
  44. 127 14
      src/views/Home.vue
  45. 131 0
      src/views/Station.vue
  46. 25 2
      vite.config.ts

+ 1 - 1
index.html

@@ -4,7 +4,7 @@
     <meta charset="UTF-8">
     <link rel="icon" href="/favicon.ico">
     <meta name="viewport" content="width=device-width, initial-scale=1.0">
-    <title>Vite App</title>
+    <title>太湖流域国家基本水文站</title>
   </head>
   <body>
     <div id="app"></div>

+ 385 - 7
package-lock.json

@@ -15,6 +15,8 @@
         "@yzfe/svgicon": "^1.2.2",
         "@yzfe/vue-svgicon": "^5.0.3",
         "echarts": "^5.5.1",
+        "element-plus": "^2.8.6",
+        "mitt": "^3.0.1",
         "pinia": "^2.2.4",
         "vue": "^3.5.12",
         "vue-router": "^4.4.5",
@@ -839,6 +841,15 @@
         "node": ">=0.1.90"
       }
     },
+    "node_modules/@ctrl/tinycolor": {
+      "version": "3.6.1",
+      "resolved": "https://registry.npmmirror.com/@ctrl/tinycolor/-/tinycolor-3.6.1.tgz",
+      "integrity": "sha512-SITSV6aIXsuVNV3f3O0f2n/cgyEDWoSqtZMYiAmcsYHydcKrOz3gUxB/iXd/Qf08+IZX4KpgNbvUdMBmWz+kcA==",
+      "license": "MIT",
+      "engines": {
+        "node": ">=10"
+      }
+    },
     "node_modules/@cypress/request": {
       "version": "3.0.5",
       "resolved": "https://registry.npmmirror.com/@cypress/request/-/request-3.0.5.tgz",
@@ -887,6 +898,15 @@
         "ms": "^2.1.1"
       }
     },
+    "node_modules/@element-plus/icons-vue": {
+      "version": "2.3.1",
+      "resolved": "https://registry.npmmirror.com/@element-plus/icons-vue/-/icons-vue-2.3.1.tgz",
+      "integrity": "sha512-XxVUZv48RZAd87ucGS48jPf6pKu0yV5UCg9f4FFwtrYxXOwWuVJo6wOvSLKEoMQKjv8GsX/mhP6UsC1lRwbUWg==",
+      "license": "MIT",
+      "peerDependencies": {
+        "vue": "^3.2.0"
+      }
+    },
     "node_modules/@esbuild/aix-ppc64": {
       "version": "0.21.5",
       "resolved": "https://registry.npmmirror.com/@esbuild/aix-ppc64/-/aix-ppc64-0.21.5.tgz",
@@ -1411,6 +1431,31 @@
         "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
       }
     },
+    "node_modules/@floating-ui/core": {
+      "version": "1.6.8",
+      "resolved": "https://registry.npmmirror.com/@floating-ui/core/-/core-1.6.8.tgz",
+      "integrity": "sha512-7XJ9cPU+yI2QeLS+FCSlqNFZJq8arvswefkZrYI1yQBbftw6FyrZOxYSh+9S7z7TpeWlRt9zJ5IhM1WIL334jA==",
+      "license": "MIT",
+      "dependencies": {
+        "@floating-ui/utils": "^0.2.8"
+      }
+    },
+    "node_modules/@floating-ui/dom": {
+      "version": "1.6.11",
+      "resolved": "https://registry.npmmirror.com/@floating-ui/dom/-/dom-1.6.11.tgz",
+      "integrity": "sha512-qkMCxSR24v2vGkhYDo/UzxfJN3D4syqSjyuTFz6C7XcpU1pASPRieNI0Kj5VP3/503mOfYiGY891ugBX1GlABQ==",
+      "license": "MIT",
+      "dependencies": {
+        "@floating-ui/core": "^1.6.0",
+        "@floating-ui/utils": "^0.2.8"
+      }
+    },
+    "node_modules/@floating-ui/utils": {
+      "version": "0.2.8",
+      "resolved": "https://registry.npmmirror.com/@floating-ui/utils/-/utils-0.2.8.tgz",
+      "integrity": "sha512-kym7SodPp8/wloecOpcmSnWJsK7M0E5Wg8UcFA+uO4B9s5d0ywXOEro/8HM9x0rW+TljRzul/14UYz3TleT3ig==",
+      "license": "MIT"
+    },
     "node_modules/@hapi/hoek": {
       "version": "9.3.0",
       "resolved": "https://registry.npmmirror.com/@hapi/hoek/-/hoek-9.3.0.tgz",
@@ -2052,6 +2097,17 @@
       "integrity": "sha512-8LduaNlMZGwdZ6qWrKlfa+2M4gahzFkprZiAt2TF8uS0qQgBizKXpXURqvTJ4WtmupWxaLqjRb2UCTe72mu+Aw==",
       "dev": true
     },
+    "node_modules/@popperjs/core": {
+      "name": "@sxzz/popperjs-es",
+      "version": "2.11.7",
+      "resolved": "https://registry.npmmirror.com/@sxzz/popperjs-es/-/popperjs-es-2.11.7.tgz",
+      "integrity": "sha512-Ccy0NlLkzr0Ex2FKvh2X+OyERHXJ88XJ1MXtsI9y9fGexlaXaVTPzBCRBwIxFkORuOb+uBqeu+RqnpgYTEZRUQ==",
+      "license": "MIT",
+      "funding": {
+        "type": "opencollective",
+        "url": "https://opencollective.com/popperjs"
+      }
+    },
     "node_modules/@rollup/pluginutils": {
       "version": "5.1.2",
       "resolved": "https://registry.npmmirror.com/@rollup/pluginutils/-/pluginutils-5.1.2.tgz",
@@ -2518,6 +2574,21 @@
       "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==",
       "dev": true
     },
+    "node_modules/@types/lodash": {
+      "version": "4.17.12",
+      "resolved": "https://registry.npmmirror.com/@types/lodash/-/lodash-4.17.12.tgz",
+      "integrity": "sha512-sviUmCE8AYdaF/KIHLDJBQgeYzPBI0vf/17NaYehBJfYD1j6/L95Slh07NlyK2iNyBNaEkb3En2jRt+a8y3xZQ==",
+      "license": "MIT"
+    },
+    "node_modules/@types/lodash-es": {
+      "version": "4.17.12",
+      "resolved": "https://registry.npmmirror.com/@types/lodash-es/-/lodash-es-4.17.12.tgz",
+      "integrity": "sha512-0NgftHUcV4v34VhXm8QBSftKVXtbkBG3ViCjs6+eJ5a6y6Mi/jiFGPc1sC7QK+9BFhWrURE3EOggmWaSxL9OzQ==",
+      "license": "MIT",
+      "dependencies": {
+        "@types/lodash": "*"
+      }
+    },
     "node_modules/@types/mapbox__point-geometry": {
       "version": "0.1.4",
       "resolved": "https://registry.npmmirror.com/@types/mapbox__point-geometry/-/mapbox__point-geometry-0.1.4.tgz",
@@ -2587,6 +2658,12 @@
       "integrity": "sha512-/Ad8+nIOV7Rl++6f1BdKxFSMgmoqEoYbHRpPcx3JEfv8VRsQe9Z4mCXeJBzxs7mbHY/XOZZuXlRNfhpVPbs6ZA==",
       "dev": true
     },
+    "node_modules/@types/web-bluetooth": {
+      "version": "0.0.16",
+      "resolved": "https://registry.npmmirror.com/@types/web-bluetooth/-/web-bluetooth-0.0.16.tgz",
+      "integrity": "sha512-oh8q2Zc32S6gd/j50GowEjKLoOVOwHP/bWVjKJInBwQqdOYMdPrf1oVlelTlyfFK3CKxL1uahMDAr+vy8T7yMQ==",
+      "license": "MIT"
+    },
     "node_modules/@types/yauzl": {
       "version": "2.10.3",
       "resolved": "https://registry.npmmirror.com/@types/yauzl/-/yauzl-2.10.3.tgz",
@@ -3246,6 +3323,94 @@
       "integrity": "sha512-VcZK7MvpjuTPx2w6blwnwZAu5/LgBUtejFOi3pPGQFXQN5Ela03FUtd2Qtg4yWGGissVL0dr6Ro1LfOFh+PCuQ==",
       "dev": true
     },
+    "node_modules/@vueuse/core": {
+      "version": "9.13.0",
+      "resolved": "https://registry.npmmirror.com/@vueuse/core/-/core-9.13.0.tgz",
+      "integrity": "sha512-pujnclbeHWxxPRqXWmdkKV5OX4Wk4YeK7wusHqRwU0Q7EFusHoqNA/aPhB6KCh9hEqJkLAJo7bb0Lh9b+OIVzw==",
+      "license": "MIT",
+      "dependencies": {
+        "@types/web-bluetooth": "^0.0.16",
+        "@vueuse/metadata": "9.13.0",
+        "@vueuse/shared": "9.13.0",
+        "vue-demi": "*"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/antfu"
+      }
+    },
+    "node_modules/@vueuse/core/node_modules/vue-demi": {
+      "version": "0.14.10",
+      "resolved": "https://registry.npmmirror.com/vue-demi/-/vue-demi-0.14.10.tgz",
+      "integrity": "sha512-nMZBOwuzabUO0nLgIcc6rycZEebF6eeUfaiQx9+WSk8e29IbLvPU9feI6tqW4kTo3hvoYAJkMh8n8D0fuISphg==",
+      "hasInstallScript": true,
+      "license": "MIT",
+      "bin": {
+        "vue-demi-fix": "bin/vue-demi-fix.js",
+        "vue-demi-switch": "bin/vue-demi-switch.js"
+      },
+      "engines": {
+        "node": ">=12"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/antfu"
+      },
+      "peerDependencies": {
+        "@vue/composition-api": "^1.0.0-rc.1",
+        "vue": "^3.0.0-0 || ^2.6.0"
+      },
+      "peerDependenciesMeta": {
+        "@vue/composition-api": {
+          "optional": true
+        }
+      }
+    },
+    "node_modules/@vueuse/metadata": {
+      "version": "9.13.0",
+      "resolved": "https://registry.npmmirror.com/@vueuse/metadata/-/metadata-9.13.0.tgz",
+      "integrity": "sha512-gdU7TKNAUVlXXLbaF+ZCfte8BjRJQWPCa2J55+7/h+yDtzw3vOoGQDRXzI6pyKyo6bXFT5/QoPE4hAknExjRLQ==",
+      "license": "MIT",
+      "funding": {
+        "url": "https://github.com/sponsors/antfu"
+      }
+    },
+    "node_modules/@vueuse/shared": {
+      "version": "9.13.0",
+      "resolved": "https://registry.npmmirror.com/@vueuse/shared/-/shared-9.13.0.tgz",
+      "integrity": "sha512-UrnhU+Cnufu4S6JLCPZnkWh0WwZGUp72ktOF2DFptMlOs3TOdVv8xJN53zhHGARmVOsz5KqOls09+J1NR6sBKw==",
+      "license": "MIT",
+      "dependencies": {
+        "vue-demi": "*"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/antfu"
+      }
+    },
+    "node_modules/@vueuse/shared/node_modules/vue-demi": {
+      "version": "0.14.10",
+      "resolved": "https://registry.npmmirror.com/vue-demi/-/vue-demi-0.14.10.tgz",
+      "integrity": "sha512-nMZBOwuzabUO0nLgIcc6rycZEebF6eeUfaiQx9+WSk8e29IbLvPU9feI6tqW4kTo3hvoYAJkMh8n8D0fuISphg==",
+      "hasInstallScript": true,
+      "license": "MIT",
+      "bin": {
+        "vue-demi-fix": "bin/vue-demi-fix.js",
+        "vue-demi-switch": "bin/vue-demi-switch.js"
+      },
+      "engines": {
+        "node": ">=12"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/antfu"
+      },
+      "peerDependencies": {
+        "@vue/composition-api": "^1.0.0-rc.1",
+        "vue": "^3.0.0-0 || ^2.6.0"
+      },
+      "peerDependenciesMeta": {
+        "@vue/composition-api": {
+          "optional": true
+        }
+      }
+    },
     "node_modules/@webgpu/types": {
       "version": "0.1.49",
       "resolved": "https://registry.npmmirror.com/@webgpu/types/-/types-0.1.49.tgz",
@@ -3527,6 +3692,12 @@
       "resolved": "https://registry.npmmirror.com/async/-/async-3.2.6.tgz",
       "integrity": "sha512-htCUDlxyyCLMgaM3xXg0C0LW2xqfuQ6p05pCEIsXuyQ+a1koYKTuBMzRNwmybfLgvJDMd0r1LTn4+E0Ti6C2AA=="
     },
+    "node_modules/async-validator": {
+      "version": "4.2.5",
+      "resolved": "https://registry.npmmirror.com/async-validator/-/async-validator-4.2.5.tgz",
+      "integrity": "sha512-7HhHjtERjqlNbZtqNqy2rckN/SpOOlmDliet+lP7k+eKZEjPk3DgyeU9lIXLdeLz0uBbbVp+9Qdow9wJWgwwfg==",
+      "license": "MIT"
+    },
     "node_modules/asynckit": {
       "version": "0.4.0",
       "resolved": "https://registry.npmmirror.com/asynckit/-/asynckit-0.4.0.tgz",
@@ -4464,8 +4635,7 @@
     "node_modules/dayjs": {
       "version": "1.11.13",
       "resolved": "https://registry.npmmirror.com/dayjs/-/dayjs-1.11.13.tgz",
-      "integrity": "sha512-oaMBel6gjolK862uaPQOVTA7q3TZhuSvuMQAAglQDOWYO9A91IrAOUJEyKVlqJlHE0vq5p5UXxzdPfMH/x6xNg==",
-      "dev": true
+      "integrity": "sha512-oaMBel6gjolK862uaPQOVTA7q3TZhuSvuMQAAglQDOWYO9A91IrAOUJEyKVlqJlHE0vq5p5UXxzdPfMH/x6xNg=="
     },
     "node_modules/de-indent": {
       "version": "1.0.2",
@@ -4751,6 +4921,32 @@
       "integrity": "sha512-dfdv/2xNjX0P8Vzme4cfzHqnPm5xsZXwsolTYr0eyW18IUmNyG08vL+fttvinTfhKfIKdRoqkDIC9e9iWQCNYQ==",
       "dev": true
     },
+    "node_modules/element-plus": {
+      "version": "2.8.6",
+      "resolved": "https://registry.npmmirror.com/element-plus/-/element-plus-2.8.6.tgz",
+      "integrity": "sha512-fk5jB8V3efM02/4roZ5SWOLArgaYXbxEydZLlXSr+KPAwjNyHBlk2+HO5em8YKo5+RLBoHnn6BaThj6IE4nXoQ==",
+      "license": "MIT",
+      "dependencies": {
+        "@ctrl/tinycolor": "^3.4.1",
+        "@element-plus/icons-vue": "^2.3.1",
+        "@floating-ui/dom": "^1.0.1",
+        "@popperjs/core": "npm:@sxzz/popperjs-es@^2.11.7",
+        "@types/lodash": "^4.14.182",
+        "@types/lodash-es": "^4.17.6",
+        "@vueuse/core": "^9.1.0",
+        "async-validator": "^4.2.5",
+        "dayjs": "^1.11.3",
+        "escape-html": "^1.0.3",
+        "lodash": "^4.17.21",
+        "lodash-es": "^4.17.21",
+        "lodash-unified": "^1.0.2",
+        "memoize-one": "^6.0.0",
+        "normalize-wheel-es": "^1.2.0"
+      },
+      "peerDependencies": {
+        "vue": "^3.2.0"
+      }
+    },
     "node_modules/element-resize-detector": {
       "version": "1.2.4",
       "resolved": "https://registry.npmmirror.com/element-resize-detector/-/element-resize-detector-1.2.4.tgz",
@@ -4876,6 +5072,12 @@
         "node": ">=6"
       }
     },
+    "node_modules/escape-html": {
+      "version": "1.0.3",
+      "resolved": "https://registry.npmmirror.com/escape-html/-/escape-html-1.0.3.tgz",
+      "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==",
+      "license": "MIT"
+    },
     "node_modules/escape-string-regexp": {
       "version": "1.0.5",
       "resolved": "https://registry.npmmirror.com/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz",
@@ -6713,6 +6915,23 @@
       "resolved": "https://registry.npmmirror.com/lodash/-/lodash-4.17.21.tgz",
       "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg=="
     },
+    "node_modules/lodash-es": {
+      "version": "4.17.21",
+      "resolved": "https://registry.npmmirror.com/lodash-es/-/lodash-es-4.17.21.tgz",
+      "integrity": "sha512-mKnC+QJ9pWVzv+C4/U3rRsHapFfHvQFoFB92e52xeyGMcX6/OlIl78je1u8vePzYZSkkogMPJ2yjxxsb89cxyw==",
+      "license": "MIT"
+    },
+    "node_modules/lodash-unified": {
+      "version": "1.0.3",
+      "resolved": "https://registry.npmmirror.com/lodash-unified/-/lodash-unified-1.0.3.tgz",
+      "integrity": "sha512-WK9qSozxXOD7ZJQlpSqOT+om2ZfcT4yO+03FuzAHD0wF6S0l0090LRPDx3vhTTLZ8cFKpBn+IOcVXK6qOcIlfQ==",
+      "license": "MIT",
+      "peerDependencies": {
+        "@types/lodash-es": "*",
+        "lodash": "*",
+        "lodash-es": "*"
+      }
+    },
     "node_modules/lodash.merge": {
       "version": "4.6.2",
       "resolved": "https://registry.npmmirror.com/lodash.merge/-/lodash.merge-4.6.2.tgz",
@@ -7029,6 +7248,12 @@
       "dev": true,
       "license": "CC0-1.0"
     },
+    "node_modules/memoize-one": {
+      "version": "6.0.0",
+      "resolved": "https://registry.npmmirror.com/memoize-one/-/memoize-one-6.0.0.tgz",
+      "integrity": "sha512-rkpe71W0N0c0Xz6QD0eJETuWAJGnJ9afsl1srmwPrI+yBCkge5EycXXbYRyvL29zZVUWQCY7InPRCv3GDXuZNw==",
+      "license": "MIT"
+    },
     "node_modules/memorystream": {
       "version": "0.3.1",
       "resolved": "https://registry.npmmirror.com/memorystream/-/memorystream-0.3.1.tgz",
@@ -7132,7 +7357,7 @@
       "version": "3.0.1",
       "resolved": "https://registry.npmmirror.com/mitt/-/mitt-3.0.1.tgz",
       "integrity": "sha512-vKivATfr97l2/QBCYAkXYDbrIWPM2IIKEl7YPhjCvKlG3kE2gm+uBo6nEXK3M5/Ffh/FLpKExzOQ3JJoJGFKBw==",
-      "dev": true
+      "license": "MIT"
     },
     "node_modules/mrmime": {
       "version": "2.0.0",
@@ -7219,6 +7444,12 @@
         "node": "^14.17.0 || ^16.13.0 || >=18.0.0"
       }
     },
+    "node_modules/normalize-wheel-es": {
+      "version": "1.2.0",
+      "resolved": "https://registry.npmmirror.com/normalize-wheel-es/-/normalize-wheel-es-1.2.0.tgz",
+      "integrity": "sha512-Wj7+EJQ8mSuXr2iWfnujrimU35R2W4FAErEyTmJoJ7ucwTn2hOUSsRehMb5RSYkxXGTM7Y9QpvPmp++w5ftoJw==",
+      "license": "BSD-3-Clause"
+    },
     "node_modules/npm-normalize-package-bin": {
       "version": "3.0.1",
       "resolved": "https://registry.npmmirror.com/npm-normalize-package-bin/-/npm-normalize-package-bin-3.0.1.tgz",
@@ -10645,6 +10876,11 @@
       "dev": true,
       "optional": true
     },
+    "@ctrl/tinycolor": {
+      "version": "3.6.1",
+      "resolved": "https://registry.npmmirror.com/@ctrl/tinycolor/-/tinycolor-3.6.1.tgz",
+      "integrity": "sha512-SITSV6aIXsuVNV3f3O0f2n/cgyEDWoSqtZMYiAmcsYHydcKrOz3gUxB/iXd/Qf08+IZX4KpgNbvUdMBmWz+kcA=="
+    },
     "@cypress/request": {
       "version": "3.0.5",
       "resolved": "https://registry.npmmirror.com/@cypress/request/-/request-3.0.5.tgz",
@@ -10692,6 +10928,12 @@
         }
       }
     },
+    "@element-plus/icons-vue": {
+      "version": "2.3.1",
+      "resolved": "https://registry.npmmirror.com/@element-plus/icons-vue/-/icons-vue-2.3.1.tgz",
+      "integrity": "sha512-XxVUZv48RZAd87ucGS48jPf6pKu0yV5UCg9f4FFwtrYxXOwWuVJo6wOvSLKEoMQKjv8GsX/mhP6UsC1lRwbUWg==",
+      "requires": {}
+    },
     "@esbuild/aix-ppc64": {
       "version": "0.21.5",
       "resolved": "https://registry.npmmirror.com/@esbuild/aix-ppc64/-/aix-ppc64-0.21.5.tgz",
@@ -10971,6 +11213,28 @@
         "levn": "^0.4.1"
       }
     },
+    "@floating-ui/core": {
+      "version": "1.6.8",
+      "resolved": "https://registry.npmmirror.com/@floating-ui/core/-/core-1.6.8.tgz",
+      "integrity": "sha512-7XJ9cPU+yI2QeLS+FCSlqNFZJq8arvswefkZrYI1yQBbftw6FyrZOxYSh+9S7z7TpeWlRt9zJ5IhM1WIL334jA==",
+      "requires": {
+        "@floating-ui/utils": "^0.2.8"
+      }
+    },
+    "@floating-ui/dom": {
+      "version": "1.6.11",
+      "resolved": "https://registry.npmmirror.com/@floating-ui/dom/-/dom-1.6.11.tgz",
+      "integrity": "sha512-qkMCxSR24v2vGkhYDo/UzxfJN3D4syqSjyuTFz6C7XcpU1pASPRieNI0Kj5VP3/503mOfYiGY891ugBX1GlABQ==",
+      "requires": {
+        "@floating-ui/core": "^1.6.0",
+        "@floating-ui/utils": "^0.2.8"
+      }
+    },
+    "@floating-ui/utils": {
+      "version": "0.2.8",
+      "resolved": "https://registry.npmmirror.com/@floating-ui/utils/-/utils-0.2.8.tgz",
+      "integrity": "sha512-kym7SodPp8/wloecOpcmSnWJsK7M0E5Wg8UcFA+uO4B9s5d0ywXOEro/8HM9x0rW+TljRzul/14UYz3TleT3ig=="
+    },
     "@hapi/hoek": {
       "version": "9.3.0",
       "resolved": "https://registry.npmmirror.com/@hapi/hoek/-/hoek-9.3.0.tgz",
@@ -11337,6 +11601,11 @@
       "integrity": "sha512-8LduaNlMZGwdZ6qWrKlfa+2M4gahzFkprZiAt2TF8uS0qQgBizKXpXURqvTJ4WtmupWxaLqjRb2UCTe72mu+Aw==",
       "dev": true
     },
+    "@popperjs/core": {
+      "version": "npm:@sxzz/popperjs-es@2.11.7",
+      "resolved": "https://registry.npmmirror.com/@sxzz/popperjs-es/-/popperjs-es-2.11.7.tgz",
+      "integrity": "sha512-Ccy0NlLkzr0Ex2FKvh2X+OyERHXJ88XJ1MXtsI9y9fGexlaXaVTPzBCRBwIxFkORuOb+uBqeu+RqnpgYTEZRUQ=="
+    },
     "@rollup/pluginutils": {
       "version": "5.1.2",
       "resolved": "https://registry.npmmirror.com/@rollup/pluginutils/-/pluginutils-5.1.2.tgz",
@@ -11645,6 +11914,19 @@
       "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==",
       "dev": true
     },
+    "@types/lodash": {
+      "version": "4.17.12",
+      "resolved": "https://registry.npmmirror.com/@types/lodash/-/lodash-4.17.12.tgz",
+      "integrity": "sha512-sviUmCE8AYdaF/KIHLDJBQgeYzPBI0vf/17NaYehBJfYD1j6/L95Slh07NlyK2iNyBNaEkb3En2jRt+a8y3xZQ=="
+    },
+    "@types/lodash-es": {
+      "version": "4.17.12",
+      "resolved": "https://registry.npmmirror.com/@types/lodash-es/-/lodash-es-4.17.12.tgz",
+      "integrity": "sha512-0NgftHUcV4v34VhXm8QBSftKVXtbkBG3ViCjs6+eJ5a6y6Mi/jiFGPc1sC7QK+9BFhWrURE3EOggmWaSxL9OzQ==",
+      "requires": {
+        "@types/lodash": "*"
+      }
+    },
     "@types/mapbox__point-geometry": {
       "version": "0.1.4",
       "resolved": "https://registry.npmmirror.com/@types/mapbox__point-geometry/-/mapbox__point-geometry-0.1.4.tgz",
@@ -11709,6 +11991,11 @@
       "integrity": "sha512-/Ad8+nIOV7Rl++6f1BdKxFSMgmoqEoYbHRpPcx3JEfv8VRsQe9Z4mCXeJBzxs7mbHY/XOZZuXlRNfhpVPbs6ZA==",
       "dev": true
     },
+    "@types/web-bluetooth": {
+      "version": "0.0.16",
+      "resolved": "https://registry.npmmirror.com/@types/web-bluetooth/-/web-bluetooth-0.0.16.tgz",
+      "integrity": "sha512-oh8q2Zc32S6gd/j50GowEjKLoOVOwHP/bWVjKJInBwQqdOYMdPrf1oVlelTlyfFK3CKxL1uahMDAr+vy8T7yMQ=="
+    },
     "@types/yauzl": {
       "version": "2.10.3",
       "resolved": "https://registry.npmmirror.com/@types/yauzl/-/yauzl-2.10.3.tgz",
@@ -12182,6 +12469,46 @@
       "integrity": "sha512-VcZK7MvpjuTPx2w6blwnwZAu5/LgBUtejFOi3pPGQFXQN5Ela03FUtd2Qtg4yWGGissVL0dr6Ro1LfOFh+PCuQ==",
       "dev": true
     },
+    "@vueuse/core": {
+      "version": "9.13.0",
+      "resolved": "https://registry.npmmirror.com/@vueuse/core/-/core-9.13.0.tgz",
+      "integrity": "sha512-pujnclbeHWxxPRqXWmdkKV5OX4Wk4YeK7wusHqRwU0Q7EFusHoqNA/aPhB6KCh9hEqJkLAJo7bb0Lh9b+OIVzw==",
+      "requires": {
+        "@types/web-bluetooth": "^0.0.16",
+        "@vueuse/metadata": "9.13.0",
+        "@vueuse/shared": "9.13.0",
+        "vue-demi": "*"
+      },
+      "dependencies": {
+        "vue-demi": {
+          "version": "0.14.10",
+          "resolved": "https://registry.npmmirror.com/vue-demi/-/vue-demi-0.14.10.tgz",
+          "integrity": "sha512-nMZBOwuzabUO0nLgIcc6rycZEebF6eeUfaiQx9+WSk8e29IbLvPU9feI6tqW4kTo3hvoYAJkMh8n8D0fuISphg==",
+          "requires": {}
+        }
+      }
+    },
+    "@vueuse/metadata": {
+      "version": "9.13.0",
+      "resolved": "https://registry.npmmirror.com/@vueuse/metadata/-/metadata-9.13.0.tgz",
+      "integrity": "sha512-gdU7TKNAUVlXXLbaF+ZCfte8BjRJQWPCa2J55+7/h+yDtzw3vOoGQDRXzI6pyKyo6bXFT5/QoPE4hAknExjRLQ=="
+    },
+    "@vueuse/shared": {
+      "version": "9.13.0",
+      "resolved": "https://registry.npmmirror.com/@vueuse/shared/-/shared-9.13.0.tgz",
+      "integrity": "sha512-UrnhU+Cnufu4S6JLCPZnkWh0WwZGUp72ktOF2DFptMlOs3TOdVv8xJN53zhHGARmVOsz5KqOls09+J1NR6sBKw==",
+      "requires": {
+        "vue-demi": "*"
+      },
+      "dependencies": {
+        "vue-demi": {
+          "version": "0.14.10",
+          "resolved": "https://registry.npmmirror.com/vue-demi/-/vue-demi-0.14.10.tgz",
+          "integrity": "sha512-nMZBOwuzabUO0nLgIcc6rycZEebF6eeUfaiQx9+WSk8e29IbLvPU9feI6tqW4kTo3hvoYAJkMh8n8D0fuISphg==",
+          "requires": {}
+        }
+      }
+    },
     "@webgpu/types": {
       "version": "0.1.49",
       "resolved": "https://registry.npmmirror.com/@webgpu/types/-/types-0.1.49.tgz",
@@ -12363,6 +12690,11 @@
       "resolved": "https://registry.npmmirror.com/async/-/async-3.2.6.tgz",
       "integrity": "sha512-htCUDlxyyCLMgaM3xXg0C0LW2xqfuQ6p05pCEIsXuyQ+a1koYKTuBMzRNwmybfLgvJDMd0r1LTn4+E0Ti6C2AA=="
     },
+    "async-validator": {
+      "version": "4.2.5",
+      "resolved": "https://registry.npmmirror.com/async-validator/-/async-validator-4.2.5.tgz",
+      "integrity": "sha512-7HhHjtERjqlNbZtqNqy2rckN/SpOOlmDliet+lP7k+eKZEjPk3DgyeU9lIXLdeLz0uBbbVp+9Qdow9wJWgwwfg=="
+    },
     "asynckit": {
       "version": "0.4.0",
       "resolved": "https://registry.npmmirror.com/asynckit/-/asynckit-0.4.0.tgz",
@@ -13052,8 +13384,7 @@
     "dayjs": {
       "version": "1.11.13",
       "resolved": "https://registry.npmmirror.com/dayjs/-/dayjs-1.11.13.tgz",
-      "integrity": "sha512-oaMBel6gjolK862uaPQOVTA7q3TZhuSvuMQAAglQDOWYO9A91IrAOUJEyKVlqJlHE0vq5p5UXxzdPfMH/x6xNg==",
-      "dev": true
+      "integrity": "sha512-oaMBel6gjolK862uaPQOVTA7q3TZhuSvuMQAAglQDOWYO9A91IrAOUJEyKVlqJlHE0vq5p5UXxzdPfMH/x6xNg=="
     },
     "de-indent": {
       "version": "1.0.2",
@@ -13256,6 +13587,28 @@
       "integrity": "sha512-dfdv/2xNjX0P8Vzme4cfzHqnPm5xsZXwsolTYr0eyW18IUmNyG08vL+fttvinTfhKfIKdRoqkDIC9e9iWQCNYQ==",
       "dev": true
     },
+    "element-plus": {
+      "version": "2.8.6",
+      "resolved": "https://registry.npmmirror.com/element-plus/-/element-plus-2.8.6.tgz",
+      "integrity": "sha512-fk5jB8V3efM02/4roZ5SWOLArgaYXbxEydZLlXSr+KPAwjNyHBlk2+HO5em8YKo5+RLBoHnn6BaThj6IE4nXoQ==",
+      "requires": {
+        "@ctrl/tinycolor": "^3.4.1",
+        "@element-plus/icons-vue": "^2.3.1",
+        "@floating-ui/dom": "^1.0.1",
+        "@popperjs/core": "npm:@sxzz/popperjs-es@^2.11.7",
+        "@types/lodash": "^4.14.182",
+        "@types/lodash-es": "^4.17.6",
+        "@vueuse/core": "^9.1.0",
+        "async-validator": "^4.2.5",
+        "dayjs": "^1.11.3",
+        "escape-html": "^1.0.3",
+        "lodash": "^4.17.21",
+        "lodash-es": "^4.17.21",
+        "lodash-unified": "^1.0.2",
+        "memoize-one": "^6.0.0",
+        "normalize-wheel-es": "^1.2.0"
+      }
+    },
     "element-resize-detector": {
       "version": "1.2.4",
       "resolved": "https://registry.npmmirror.com/element-resize-detector/-/element-resize-detector-1.2.4.tgz",
@@ -13352,6 +13705,11 @@
       "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==",
       "dev": true
     },
+    "escape-html": {
+      "version": "1.0.3",
+      "resolved": "https://registry.npmmirror.com/escape-html/-/escape-html-1.0.3.tgz",
+      "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow=="
+    },
     "escape-string-regexp": {
       "version": "1.0.5",
       "resolved": "https://registry.npmmirror.com/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz",
@@ -14648,6 +15006,17 @@
       "resolved": "https://registry.npmmirror.com/lodash/-/lodash-4.17.21.tgz",
       "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg=="
     },
+    "lodash-es": {
+      "version": "4.17.21",
+      "resolved": "https://registry.npmmirror.com/lodash-es/-/lodash-es-4.17.21.tgz",
+      "integrity": "sha512-mKnC+QJ9pWVzv+C4/U3rRsHapFfHvQFoFB92e52xeyGMcX6/OlIl78je1u8vePzYZSkkogMPJ2yjxxsb89cxyw=="
+    },
+    "lodash-unified": {
+      "version": "1.0.3",
+      "resolved": "https://registry.npmmirror.com/lodash-unified/-/lodash-unified-1.0.3.tgz",
+      "integrity": "sha512-WK9qSozxXOD7ZJQlpSqOT+om2ZfcT4yO+03FuzAHD0wF6S0l0090LRPDx3vhTTLZ8cFKpBn+IOcVXK6qOcIlfQ==",
+      "requires": {}
+    },
     "lodash.merge": {
       "version": "4.6.2",
       "resolved": "https://registry.npmmirror.com/lodash.merge/-/lodash.merge-4.6.2.tgz",
@@ -14904,6 +15273,11 @@
       "integrity": "sha512-dn6wd0uw5GsdswPFfsgMp5NSB0/aDe6fK94YJV/AJDYXL6HVLWBsxeq7js7Ad+mU2K9LAlwpk6kN2D5mwCPVow==",
       "dev": true
     },
+    "memoize-one": {
+      "version": "6.0.0",
+      "resolved": "https://registry.npmmirror.com/memoize-one/-/memoize-one-6.0.0.tgz",
+      "integrity": "sha512-rkpe71W0N0c0Xz6QD0eJETuWAJGnJ9afsl1srmwPrI+yBCkge5EycXXbYRyvL29zZVUWQCY7InPRCv3GDXuZNw=="
+    },
     "memorystream": {
       "version": "0.3.1",
       "resolved": "https://registry.npmmirror.com/memorystream/-/memorystream-0.3.1.tgz",
@@ -14976,8 +15350,7 @@
     "mitt": {
       "version": "3.0.1",
       "resolved": "https://registry.npmmirror.com/mitt/-/mitt-3.0.1.tgz",
-      "integrity": "sha512-vKivATfr97l2/QBCYAkXYDbrIWPM2IIKEl7YPhjCvKlG3kE2gm+uBo6nEXK3M5/Ffh/FLpKExzOQ3JJoJGFKBw==",
-      "dev": true
+      "integrity": "sha512-vKivATfr97l2/QBCYAkXYDbrIWPM2IIKEl7YPhjCvKlG3kE2gm+uBo6nEXK3M5/Ffh/FLpKExzOQ3JJoJGFKBw=="
     },
     "mrmime": {
       "version": "2.0.0",
@@ -15040,6 +15413,11 @@
         "abbrev": "^2.0.0"
       }
     },
+    "normalize-wheel-es": {
+      "version": "1.2.0",
+      "resolved": "https://registry.npmmirror.com/normalize-wheel-es/-/normalize-wheel-es-1.2.0.tgz",
+      "integrity": "sha512-Wj7+EJQ8mSuXr2iWfnujrimU35R2W4FAErEyTmJoJ7ucwTn2hOUSsRehMb5RSYkxXGTM7Y9QpvPmp++w5ftoJw=="
+    },
     "npm-normalize-package-bin": {
       "version": "3.0.1",
       "resolved": "https://registry.npmmirror.com/npm-normalize-package-bin/-/npm-normalize-package-bin-3.0.1.tgz",

+ 2 - 0
package.json

@@ -23,6 +23,8 @@
     "@yzfe/svgicon": "^1.2.2",
     "@yzfe/vue-svgicon": "^5.0.3",
     "echarts": "^5.5.1",
+    "element-plus": "^2.8.6",
+    "mitt": "^3.0.1",
     "pinia": "^2.2.4",
     "vue": "^3.5.12",
     "vue-router": "^4.4.5",

二进制
public/favicon.ico


+ 11 - 0
src/api/home.ts

@@ -0,0 +1,11 @@
+import request from '@/utils/request'
+
+/**
+ * 水文测站实时信息
+ */
+export function getRStLLMaxDate() {
+  return request({
+    url: '/tbazmw_api/tbazmw/thdata/getRVandDrpMaxDate',
+    method: 'get'
+  })
+}

二进制
src/assets/images/dike.png


二进制
src/assets/images/layout/header-title-tsq.png


二进制
src/assets/images/layout/header-title.png


二进制
src/assets/images/layout/header-title2.png


二进制
src/assets/images/layout/menu-left.png


二进制
src/assets/images/layout/menu-right.png


二进制
src/assets/images/logo.png


二进制
src/assets/images/sz-ad.png


二进制
src/assets/images/sz-ddl.png


二进制
src/assets/images/sz-rjy.png


二进制
src/assets/images/sz-tn.png


二进制
src/assets/images/sz-wd.png


二进制
src/assets/images/sz-zd.png


二进制
src/assets/images/sz-zl.png


二进制
src/assets/images/tag-background.png


+ 61 - 0
src/assets/styles/element/index.scss

@@ -0,0 +1,61 @@
+$--bg-color: (
+        '': rgba(0, 0, 0, 0),
+);
+
+$--fill-color: (
+        '': #f0f2f5,
+        'light': #031f46,
+        'lighter': rgba(0, 249, 255, 0.3),
+        'extra-light': #fafcff,
+        'dark': #ebedf0,
+        'darker': #e6e8eb,
+        'blank': rgba(0, 0, 0, 0),
+);
+
+$--text-color: (
+        'primary': #fff,
+        'regular': #00c7e3,
+        'secondary': #9ee6f8,
+        'placeholder': #a8abb2,
+        'disabled': #c0c4cc,
+);
+
+$--border-color: (
+        'lighter': #00cbe6,
+);
+
+
+// You should use them in scss, because we calculate it by sass.
+// comment next lines to use default color
+@forward "element-plus/theme-chalk/src/common/var.scss" with (
+        $bg-color: $--bg-color,
+        $fill-color: $--fill-color,
+        $text-color: $--text-color,
+        $border-color: $--border-color,
+        $button-padding-horizontal: ("default": 50px)
+);
+
+// if you want to import all
+@use "element-plus/theme-chalk/src/index.scss" as *;
+
+
+.el-table--border::after,
+.el-table--border::before,
+.el-table--border .el-table__inner-wrapper::after,
+.el-table__inner-wrapper::before {
+  display: none;
+}
+
+.el-table {
+  td.el-table__cell {
+    border-bottom: none;
+  }
+
+  .cell {
+    padding: 0 6px;
+  }
+}
+
+.el-table--enable-row-hover .el-table__body tr:hover > td.el-table__cell {
+  color: var(--el-text-color-primary);
+}

+ 1 - 7
src/assets/styles/index.scss

@@ -8,13 +8,7 @@ body {
   -moz-osx-font-smoothing: grayscale;
   -webkit-font-smoothing: antialiased;
   text-rendering: optimizeLegibility;
-  font-family: Helvetica Neue,
-  Helvetica,
-  PingFang SC,
-  Hiragino Sans GB,
-  Microsoft YaHei,
-  Arial,
-  sans-serif;
+  font-family: Helvetica Neue, Helvetica, PingFang SC, Hiragino Sans GB, Microsoft YaHei, Arial, sans-serif;
 }
 
 label {

+ 19 - 0
src/assets/styles/introduce.scss

@@ -0,0 +1,19 @@
+.introduce-title {
+  margin-bottom: 10px;
+  text-align: center;
+}
+
+.introduce-text {
+  text-indent: 2em;
+  line-height: 1.6rem;
+  margin: 0 0 .3em 0;
+  font-size: 1rem;
+  color: #fff;
+  letter-spacing: 1px;
+}
+
+.introduce-img {
+  width: 100%;
+  height: 200px;
+  border-radius: 6px;
+}

+ 8 - 632
src/components/AntvMap/index.vue

@@ -1,641 +1,17 @@
 <script lang="ts" setup>
-import {points} from "@turf/helpers";
-import {bbox} from "@turf/bbox";
-import {LayerPopup, LineLayer, PointLayer, PolygonLayer, Popup, RasterLayer, Scene} from '@antv/l7';
-import {Map} from '@antv/l7-maps';
-// import {getLegendData, loadMapData, setCenter} from "@/utils/mapConfig";
-import {formatStringByTemplate} from "@/utils/string";
-import {copyObj} from "@/utils/ruoyi";
-import {onMounted, reactive} from "vue";
-import {getImageSrc} from "@/utils/image";
-
-let scene;
-let pointLayer;
-let pointLayerText;
-let lineLayer;
-let lineTextLayer;
-let polygonLayer;
-let polygonTextLayer;
-const token = 'a4eab5a1b0d94268669e192b8799f626';
-
-/**
- * 初始化地图
- */
-function init() {
-  const scene = new Scene({
-    id: 'mapDiv',
-    map: new Map({
-      center: [120.543139, 31.243133],
-      zoom: 9,
-    }),
-  });
-
-  const url1 = 'https://t0.tianditu.gov.cn/img_w/wmts?tk=b72aa81ac2b3cae941d1eb213499e15e&';
-  const layer1 = new RasterLayer().source(url1, {
-    parser: {
-      type: 'rasterTile',
-      tileSize: 256,
-      wmtsOptions: {
-        layer: 'img',
-        tileMatrixset: 'w',
-        format: 'tiles',
-      },
-    },
-  });
-
-  scene.on('loaded', () => {
-    scene.addLayer(layer1);
-  });
-
-  // scene = new Scene({
-  //   id: 'mapDiv',
-  //   map: new Map({}),
-  // });
-  // scene.on('mapmove', () => {
-  //   console.log('中心点:', scene.getCenter().lng, scene.getCenter().lat, scene.getZoom(), scene.getPitch())
-  // }); // 地图平移时触发事件
-  //
-  // // 天地图卫片地图
-  // scene.on('loaded', () => {
-  //   // 底图服务
-  //   const baseLayer = new RasterLayer({zIndex: 1})
-  //     .source(
-  //       `https://t1.tianditu.gov.cn/DataServer?T=vec_w&X={x}&Y={y}&L={z}&tk=${this.token}`,
-  //       {
-  //         parser: {
-  //           type: 'rasterTile',
-  //           tileSize: 256,
-  //         }
-  //       }
-  //     );
-  //   // 注记服务
-  //   const annotionLayer = new RasterLayer({zIndex: 2})
-  //     .source(
-  //       `https://t1.tianditu.gov.cn/DataServer?T=cva_w&X={x}&Y={y}&L={z}&tk=${this.token}`,
-  //       {
-  //         parser: {
-  //           type: 'rasterTile',
-  //           tileSize: 256,
-  //         }
-  //       }
-  //     );
-  //   scene.addLayer(baseLayer);
-  //   scene.addLayer(annotionLayer);
-  //
-  //   setCenter(scene, [120.543139, 31.243133], 8.03)
-  // });
-}
-
-function getWaterQualityTextByLevel(val, hasUnit = false) {
-  let waterQualityText = ''
-  switch (val) {
-    case '1':
-      waterQualityText = 'Ⅰ'
-      break
-    case '2':
-      waterQualityText = 'Ⅱ'
-      break
-    case '3':
-      waterQualityText = 'Ⅲ'
-      break
-    case '4':
-      waterQualityText = 'Ⅳ'
-      break
-    case '5':
-      waterQualityText = 'Ⅴ'
-      break
-    case '6':
-      waterQualityText = 'Ⅵ'
-      break
-  }
-
-  if (hasUnit && waterQualityText) {
-    waterQualityText = waterQualityText + '类'
-  }
-
-  return waterQualityText
-}
-
-
-// 图例
-const legendList = reactive([{}])
-const legendStyle = reactive({})
-
-/** 添加点(多个) */
-function addPoint(data, dataOptions = {}, nameField) {
-  // 1. 删除之前的图层
-  scene.removeAllLayer();
-
-  // 2. 添加点
-  // 点位标注
-  pointLayer = new PointLayer({})
-    .source(data, dataOptions)
-    .shape('circle')
-    .size(16)
-    .color('#004ef8')
-    .active(true)
-  scene.addLayer(pointLayer);
-  // 名称标注
-  pointLayerText = new PointLayer()
-    .source(data, dataOptions)
-    .shape(nameField, 'text')
-    .size(14)
-    .color('#000')
-    .style({
-      textOffset: [0, -50],
-      spacing: 2, // 字符间距
-      stroke: '#ffffff', // 描边颜色
-      strokeWidth: 2, // 描边宽度
-      strokeOpacity: 1.0,
-    });
-  scene.addLayer(pointLayerText);
-  // 3. 添加悬浮窗口
-  let keys = Object.keys(data[0]);
-  keys = keys.filter(k => !['经度', '纬度'].includes(k));
-  const fields = keys.map(k => {
-    return {
-      field: k,
-      formatField: () => k,
-    }
-  });
-  const layerPopup = new LayerPopup({
-    items: [
-      {
-        layer: pointLayer,
-        fields: fields,
-      },
-    ],
-  });
-  scene.addPopup(layerPopup);
-}
-
-/** 添加线(多个) */
-function addLine(data, nameField) {
-  // 1. 删除之前的图层
-  scene.removeAllLayer();
-
-  lineLayer = new LineLayer()
-    .source(data)
-    .size(5)
-    .shape('line')
-    .active(true)
-    .color('ICON', (ICON) => {
-      switch (ICON) {
-        case '1':
-          return '#1d8add'
-        case '2':
-          return '#4af905'
-        case '3':
-          return '#fed202'
-        case '4':
-          return '#f91005'
-        case '5':
-          return '#fb0891'
-        case '6':
-          return '#252627'
-        default:
-          return '#909399'
-      }
-    })
-    .style({
-      borderWidth: 0.4,
-      borderColor: '#fff',
-    });
-  scene.addLayer(lineLayer);
-
-  // 名称标注
-  const texts = []
-  data.features.forEach(d => {
-    let index = Math.ceil(d.geometry.coordinates[0].length / 2)
-    let array = d.geometry.coordinates[0][index]
-    texts.push({name: d.properties[nameField], x: array[0], y: array[1]})
-  })
-  lineTextLayer = new PointLayer()
-    .source(texts, {
-      parser: {
-        type: 'json',
-        x: 'x',
-        y: 'y',
-      },
-    })
-    .shape('name', 'text')
-    .color('#fff')
-    .size(14)
-    .style({
-      textOffset: [0, -50],
-    });
-  scene.addLayer(lineTextLayer);
-
-  // 悬浮窗
-  const fields = [
-    {field: 'HLNM', formatField: () => '河流名称'},
-    {field: 'ICON', formatField: () => '水质', formatValue: (val) => getWaterQualityTextByLevel(val, true)},
-  ];
-  const layerPopup = new LayerPopup({items: [{layer: lineLayer, fields: fields,},],});
-  scene.addPopup(layerPopup);
-}
-
-function addPolygon(data, nameField) {
-  // 1. 删除之前的图层
-  scene.removeAllLayer();
-
-  const color = [
-    'rgb(255,255,217)',
-    'rgb(237,248,177)',
-    'rgb(199,233,180)',
-    'rgb(127,205,187)',
-    'rgb(65,182,196)',
-    'rgb(29,145,192)',
-    'rgb(34,94,168)',
-    'rgb(12,44,132)',
-  ];
-  /**
-   *         .scale('用水量', {
-   *           type: 'quantile',
-   *         })
-   *         .color('用水量', color)
-   */
-  const layer = new PolygonLayer({})
-    .source(data)
-    .color('rgba(65,182,196,0.4)')
-    .shape('fill')
-    .active(true);
-  const layer2 = new LineLayer({zIndex: 2})
-    .source(data)
-    .color('#fff')
-    .active(true)
-    .size(1)
-    .style({
-      lineType: 'dash',
-      dashArray: [2, 2],
-    });
-  scene.addLayer(layer);
-  scene.addLayer(layer2);
-
-  const polygonTextLayer = new PointLayer({})
-    .source(data)
-    .shape(nameField, 'text')
-    .size(12)
-    .color('#000')
-    .style({
-      textAnchor: 'center', // 文本相对锚点的位置 center|left|right|top|bottom|top-left
-      textOffset: [0, 0], // 文本相对锚点的偏移量 [水平, 垂直]
-      spacing: 2, // 字符间距
-      padding: [1, 1], // 文本包围盒 padding [水平,垂直],影响碰撞检测结果,避免相邻文本靠的太近
-      stroke: '#ffffff', // 描边颜色
-      strokeWidth: 2, // 描边宽度
-      strokeOpacity: 1.0,
-    });
-
-  scene.addLayer(polygonTextLayer);
-
-  // layer.on('mousemove', (e) => {
-  //   const popup = new Popup({
-  //     offsets: [0, 0],
-  //     closeButton: false,
-  //   })
-  //     .setLnglat(e.lngLat)
-  //     .setHTML(
-  //       `
-  //       <span>${e.feature.properties['市']}: ${e.feature.properties['用水量']}</span>
-  //       <br/>
-  //       <span>许可水量: ${e.feature.properties['许可水量']}</span>
-  //       `
-  //     );
-  //   scene.addPopup(popup);
-  // });
-}
-
-function add3DPoint(list) {
-  const pointLayer = new PointLayer({})
-    .source(list, {
-      parser: {
-        type: 'json',
-        x: 'j',
-        y: 'w',
-      },
-    })
-    .shape('cylinder')
-    .size('t', function (level) {
-      return [2, 2, level * 2 + 20];
-    })
-    .animate(true)
-    .active(true)
-    .color('t', [
-      '#094D4A',
-      '#146968',
-      '#1D7F7E',
-      '#289899',
-      '#34B6B7',
-      '#4AC5AF',
-      '#5FD3A6',
-      '#7BE39E',
-      '#A1EDB8',
-      '#CEF8D6',
-    ]);
-  scene.addLayer(pointLayer);
-}
-
-function getScene() {
-  return scene;
-}
-
-function loadMapData(mapConfig) {
-  if (['left', 'center', 'right'].includes(mapConfig.legend.left)) {
-    switch (mapConfig.legend.left) {
-      case "left":
-        this.legendStyle.left = '10px';
-        break
-      case "center":
-        this.legendStyle.left = `calc(50% - (${this.$refs.mapLegend.offsetWidth} / 2))`;
-        break
-      case "right":
-        this.legendStyle.right = '10px';
-        break
-    }
-  } else if (mapConfig.legend.left.startsWith('-')) {
-    this.legendStyle.right = mapConfig.legend.left.replace('-', '')
-  } else {
-    this.legendStyle.left = mapConfig.legend.left
-  }
-
-  if (['top', 'center', 'bottom'].includes(mapConfig.legend.top)) {
-    switch (mapConfig.legend.top) {
-      case "top":
-        this.legendStyle.top = '10px';
-        break
-      case "center":
-        this.legendStyle.top = `calc(50% - (${this.$refs.mapLegend.offsetHeight} / 2))`;
-        break
-      case "bottom":
-        this.legendStyle.bottom = '10px';
-        break
-    }
-  } else if (mapConfig.legend.top.startsWith('-')) {
-    this.legendStyle.bottom = mapConfig.legend.top.replace('-', '')
-  } else {
-    this.legendStyle.top = mapConfig.legend.top
-  }
-
-  // this.legendList = getLegendData(mapConfig)
-}
-
-function fitBounds(data) {
-  const features = points(data);
-  const bboxs = bbox(features);
+import {init, loadMap} from "@/utils/mapConfig";
+import {onMounted, watch} from "vue";
+import {useMapStore} from "@/stores/map";
 
-  let lttddd = (bboxs[3] - bboxs[1]) / 8 // 计算缩放级别
-  let lgtddd = (bboxs[2] - bboxs[0]) / 8 // 计算缩放级别
-  scene.fitBounds([[bboxs[0] - lgtddd, bboxs[1] - lttddd], [bboxs[2] + lgtddd, bboxs[3] + lttddd]]);
-}
-
-/** 设置事件节点 */
-function setEventData(events) {
-  const _self = this
-  // 1. 删除之前的图层
-  scene.removeAllLayer();
-
-  const sourceOptions = {
-    parser: {
-      type: 'json',
-      x: 'x',
-      y: 'y',
-    },
-  }
-
-  const imageSet = new Set()
-  events.forEach(event => {
-    if (event.image) {
-      imageSet.add(event.image)
-    }
-  })
-  imageSet.forEach(image => {
-    scene.addImage(
-      image,
-      getImageSrc(image)
-    );
-  })
-
-  const imageEvents = events.filter(event => !!event.image)
-  const imagePointLayer = new PointLayer()
-    .source(imageEvents, sourceOptions)
-    .size(16)
-    .shape('image', Array.of(imageSet))
-    .style({
-      opacity: 0.8,
-      strokeWidth: 4
-    })
-    .active(true);
-  scene.addLayer(imagePointLayer);
-
-  const shapeEvents = events.filter(event => !event.image)
-  const shapePointLayer = new PointLayer()
-    .source(shapeEvents, sourceOptions)
-    .size(16)
-    .shape('shape')
-    .color('color')
-    .style({
-      opacity: 0.8,
-      strokeWidth: 4
-    })
-    .active(true);
-  scene.addLayer(shapePointLayer);
-
-
-  // 设置标签
-  pointLayerText = new PointLayer()
-    .source(events, sourceOptions)
-    .shape('name', 'text')
-    .size(12)
-    .color('#000')
-    .style({
-      textAnchor: 'center', // 文本相对锚点的位置 center|left|right|top|bottom|top-left
-      textOffset: [0, -60], // 文本相对锚点的偏移量 [水平, 垂直]
-      spacing: 2, // 字符间距
-      padding: [1, 1], // 文本包围盒 padding [水平,垂直],影响碰撞检测结果,避免相邻文本靠的太近
-      stroke: '#fff', // 描边颜色
-      strokeWidth: 2, // 描边宽度
-      strokeOpacity: 1.0,
-    });
-  scene.addLayer(pointLayerText);
-
-  // 显示弹窗
-  const showPopup = (e) => {
-    let data = e.feature.data
-    const template =
-      '<div class="entry-card-component"><section class="entry-base-info">' +
-      '<section class="base-info"><div class="entry-avatar"><img alt="" src="{{imgUrl}}"></div><div class="entry-text-info"><div class="entry-name">{{name}}</div><div class="entry-desc">{{summary}}</div></div></section></section>' +
-      '<section class="entry-nodes"><div class="nodes-list"><div class="node-item" data-id="{{id}}">' +
-      '<div class="title">{{source.title}}</div>' +
-      '<div class="sub-title"><div class="time">{{source.dateString}}</div>' +
-      '<div class="address">{{source.locationDesc}}</div></div>' +
-      '<div class="desc">{{source.description}}</div></div></div></section></div>'
-    const content = formatStringByTemplate(data, template)
-    const popup = new Popup({maxWidth: '36vw', autoClose: true})
-      .setLnglat(e.lngLat)
-      .setHTML(content);
-    scene.addPopup(popup);
-
-    // 确保在 DOM 完全更新后执行
-    setTimeout(() => {
-      const eventItems = document.getElementsByClassName('node-item');
-      // 遍历所有 node-item 元素
-      Array.from(eventItems).forEach(item => {
-        item.addEventListener('click', (event) => {
-          // const id = event.currentTarget?.dataset?.id;
-          // _self.$router.push({path: `/geoTemporalSearch/detail/${id}`});
-        });
-      });
-    }, 0); // 使用 setTimeout 来确保 DOM 已经更新
-  };
-
-  imagePointLayer.on('click', showPopup);
-  shapePointLayer.on('click', showPopup);
-}
-
-function setEventPoint(events) {
-  const _self = this
-  // 1. 删除之前的图层
-  scene.removeAllLayer();
-
-  const sourceOptions = {
-    parser: {
-      type: 'json',
-      x: 'x',
-      y: 'y',
-    },
-  }
-  const pointLayer = new PointLayer()
-    .source(events, sourceOptions)
-    .size(12)
-    .shape('simple')
-    .color('#fe7331')
-    .style({
-      strokeWidth: 4
-    })
-    .active(false);
-  scene.addLayer(pointLayer);
-
-  // 设置标签
-  pointLayerText = new PointLayer()
-    .source(events, sourceOptions)
-    .shape('title', 'text')
-    .size(12)
-    .color('#000')
-    .style({
-      textAnchor: 'center', // 文本相对锚点的位置 center|left|right|top|bottom|top-left
-      textOffset: [0, -60], // 文本相对锚点的偏移量 [水平, 垂直]
-      spacing: 2, // 字符间距
-      padding: [1, 1], // 文本包围盒 padding [水平,垂直],影响碰撞检测结果,避免相邻文本靠的太近
-      stroke: '#fff', // 描边颜色
-      strokeWidth: 2, // 描边宽度
-      strokeOpacity: 1.0,
-    });
-  scene.addLayer(pointLayerText);
-}
-
-function setEventPointDetail(events) {
-  const allPoints = copyObj(events)
-  // 1. 删除之前的图层
-  scene.removeAllLayer();
-
-  let mainEvent;
-  let index = events.findIndex(item => item.pointType === 'main');
-  if (index > -1) {
-    mainEvent = events.splice(index, 1); // 移除找到的第一个元素  l
-  } else {
-    return
-  }
-
-  const sourceOptions = {
-    parser: {
-      type: 'json',
-      x: 'x',
-      y: 'y',
-    },
-  }
-  const pointLayer = new PointLayer()
-    .source(events, sourceOptions)
-    .size(12)
-    .shape('simple')
-    .color('#fe7331')
-    .style({
-      strokeWidth: 4
-    })
-    .active(false);
-  scene.addLayer(pointLayer);
-
-  const mainPointLayer = new PointLayer()
-    .source(mainEvent, sourceOptions)
-    .size(20)
-    .shape('simple')
-    .color('#fe7331')
-    .style({
-      strokeWidth: 4
-    })
-    .active(false);
-  scene.addLayer(mainPointLayer);
-
-  // 设置标签
-  pointLayerText = new PointLayer()
-    .source(allPoints, sourceOptions)
-    .shape('title', 'text')
-    .size(12)
-    .color('#000')
-    .style({
-      textAnchor: 'center', // 文本相对锚点的位置 center|left|right|top|bottom|top-left
-      textOffset: [0, -60], // 文本相对锚点的偏移量 [水平, 垂直]
-      spacing: 2, // 字符间距
-      padding: [1, 1], // 文本包围盒 padding [水平,垂直],影响碰撞检测结果,避免相邻文本靠的太近
-      stroke: '#fff', // 描边颜色
-      strokeWidth: 2, // 描边宽度
-      strokeOpacity: 1.0,
-    });
-  scene.addLayer(pointLayerText);
-
-  for (let e of events) {
-    let data = []
-    if (e.pointType === 'previous') {
-      data.push({lng: e.x, lat: e.y, lng1: mainEvent[0].x, lat1: mainEvent[0].y})
-    } else {
-      data.push({lng: mainEvent[0].x, lat: mainEvent[0].y, lng1: e.x, lat1: e.y})
-    }
-
-    const lineLayer = new LineLayer()
-      .source(data, {
-        parser: {
-          type: 'json',
-          x: 'lng',
-          y: 'lat',
-          x1: 'lng1',
-          y1: 'lat1',
-        },
-      })
-      .shape('line')
-      .size(2)
-      .color('#f00')
-      .animate({
-        duration: 2,
-        interval: 1,
-        trailLength: 1,
-      })
-      .style({
-        lineType: 'dash', // 设置虚线类型
-        // dashArray: [5, 5], // 设置虚线的间隔和长度
-      });
-    scene.addLayer(lineLayer);
-  }
-
-  pointLayer.on('click', e => {
-    if (e.feature?.id) {
-      this.$emit('updateEventId', e.feature?.id)
-    }
-  });
-}
+const store = useMapStore()
 
 onMounted(() => {
   init()
 })
+
+watch(() => store.mapOption, (option): void => {
+  loadMap(option)
+})
 </script>
 
 <template>

+ 3 - 5
src/components/chart.vue → src/components/Chart.vue

@@ -62,11 +62,9 @@ function carousel(timeout = 5000, yAxisChange = false) {
 
 onMounted(() => chartRef.value?.addEventListener("resize", reloadChart))
 onUnmounted(() => {
-  if (chart) {
-    chart.removeEventListener("resize", reloadChart)
-    if (chart.dispose) {
-      chart.dispose();
-    }
+  chartRef.value?.removeEventListener("resize", reloadChart)
+  if (chart && chart.dispose) {
+    chart.dispose();
   }
 })
 </script>

+ 7 - 6
src/components/RightFrame.vue

@@ -1,7 +1,7 @@
 <script lang="ts" setup>
 import {computed, ref, useSlots, watch} from 'vue'
 import convert from '@/assets/svg/convert.svg'
-// const emit = defineEmits(['update:useLeftSidebar'])
+
 const slots = useSlots()
 
 let sidebarWidth = ref(window.innerWidth * 0.22)
@@ -22,7 +22,6 @@ const rightSidebarStyle = computed(() => {
 
 function convertSidebar(modelName) {
   if (modelName === 'left') {
-    debugger
     leftSidebarShow.value = !leftSidebarShow.value
   } else {
     rightSidebarShow.value = !rightSidebarShow.value
@@ -77,6 +76,7 @@ watch(
   position: relative;
   height: 100%;
   padding: 0 0.7rem 0.7rem 0.7rem;
+  overflow: hidden;
 
   .right-top-btn {
     pointer-events: auto;
@@ -107,13 +107,12 @@ watch(
   .gw-sidebar-right {
     pointer-events: auto;
     position: relative;
-    transition: 0.3s;
-    //     transition: all 1s;
+    transition: all 0.3s;
     height: calc(100%);
     min-width: 300px;
 
     &:hover .gw-sidebar-btn {
-      display: flex;
+      opacity: 1;
     }
 
     .gw-sidebar-container {
@@ -127,10 +126,12 @@ watch(
       top: 10px;
       position: absolute;
       cursor: pointer;
+      transition: opacity 0.5s ease-in-out;
       background-color: #2e6ca6;
       height: 40px;
       width: 40px;
-      display: none;
+      display: flex;
+      opacity: 0;
       justify-content: center;
       align-items: center;
 

+ 26 - 0
src/components/StripeTable.vue

@@ -0,0 +1,26 @@
+<script lang="ts" setup>
+defineProps({
+  data: {type: Array, default: []},
+  columns: {type: Array, default: []},
+})
+
+const emit = defineEmits(['click'])
+
+function handleClick(row) {
+  emit('click', row)
+}
+</script>
+
+<template>
+  <el-table :data="data" height="100%" stripe @row-click="handleClick">
+    <el-table-column v-for="column in columns" :key="column.prop" :label="column.label" :prop="column.prop"
+                     :width="column.width" align="center">
+      <template v-if="!column.convertFn" #default="scope">
+        {{ scope.row[column.prop] }}
+      </template>
+      <template v-else #default="scope">
+        <span v-html="column.convertFn(scope.row[column.prop], scope.$index)"></span>
+      </template>
+    </el-table-column>
+  </el-table>
+</template>

+ 57 - 0
src/components/tag/ColorTag.vue

@@ -0,0 +1,57 @@
+<script lang="ts" setup>
+import {computed, getCurrentInstance} from "vue";
+import {HexToRgb} from "@/utils/color";
+
+defineProps({
+  label: {type: String, default: null},
+  value: {type: String, default: null},
+  unit: {type: String, default: null},
+  backgroundColor: {type: String, default: '#1088d7'},
+})
+
+const {props} = getCurrentInstance()
+
+const colorTagStyle = computed(() => {
+  const rgbc = HexToRgb(props.backgroundColor)
+  const color = `rgba(${rgbc[0]}, ${rgbc[1]}, ${rgbc[2]}, 0.3)`
+  return {
+    'background-image': `linear-gradient(100deg, ${color} 0%, ${props.backgroundColor} 70%)`
+  }
+})
+</script>
+
+<template>
+  <div :style="colorTagStyle" class="color-tag">
+    <div class="label" v-html="props.label"></div>
+    <div class="value">{{ props.value }} <span v-if="props.unit" class="unit">{{ props.unit }}</span></div>
+  </div>
+</template>
+<style lang="scss" scoped>
+.color-tag {
+  position: relative;
+  width: 100%;
+  height: 100%;
+  padding: 5px 10px;
+  border-radius: 8px;
+  color: #fff;
+
+  .label {
+    font-size: 0.9rem;
+    text-align: right;
+  }
+
+  .value {
+    position: absolute;
+    bottom: 0;
+    font-size: 1.4rem;
+    font-weight: bold;
+    margin-bottom: 10px;
+
+    .unit {
+      font-size: 1rem;
+    }
+
+  }
+
+}
+</style>

+ 63 - 0
src/components/tag/ImageTag.vue

@@ -0,0 +1,63 @@
+<script lang="ts" setup>
+import {computed, getCurrentInstance} from "vue";
+
+defineProps({
+  label: {type: String, default: null},
+  value: {type: String, default: null},
+  unit: {type: String, default: null},
+  imgSrc: {type: String, default: null},
+})
+
+const {props} = getCurrentInstance()
+const backgroundImage = new URL('@/assets/images/tag-background.png', import.meta.url).href
+const tagBackgroundStyle = computed(() => {
+  return {'background-image': `url(${backgroundImage})`}
+})
+</script>
+
+<template>
+  <!-- :style="tagBackgroundStyle" -->
+  <div class="image-tag">
+    <div class="label" v-html="props.label"></div>
+    <div class="value">{{ props.value }}</div>
+    <div v-if="props.unit" class="unit">{{ props.unit }}</div>
+    <img v-if="imgSrc" :src="imgSrc" alt="" class="image"/>
+  </div>
+</template>
+<style lang="scss" scoped>
+.image-tag {
+  position: relative;
+  width: 100%;
+  height: 100%;
+  padding: 10px 0;
+  border-radius: 8px;
+  color: #fff;
+  background-image: linear-gradient(135deg, rgba(0, 0, 0, 0) 0%, #064786 100%);
+  background-size: 100% 100%;
+
+  .label {
+    color: #f3f5f6;
+    font-size: .9rem;
+  }
+
+  .value {
+    font-size: 1.4rem;
+    font-weight: bold;
+    margin: 10px 0;
+  }
+
+  .unit {
+    font-size: 0.8rem;
+    color: #d9dde1;
+  }
+
+  .image {
+    position: absolute;
+    top: 0;
+    right: 0;
+    width: 50%;
+    height: 50%;
+  }
+
+}
+</style>

+ 134 - 66
src/layout/components/Navbar.vue

@@ -1,92 +1,88 @@
 <script lang="ts" setup>
-import {computed, onMounted} from 'vue'
-import {getToken} from '@/utils/auth'
+import {computed, onMounted, ref, watch} from 'vue'
 import {jumpPage} from '@/utils'
 import {useRoute, useRouter} from "vue-router";
-import {useCounterStore} from "@/stores/counter";
+import {formatd} from "@/utils/ruoyi";
 
 const route = useRoute()
 const router = useRouter()
-const store = useCounterStore()
 
 let navbarBackgroundImage = new URL('@/assets/images/layout/header-background.png', import.meta.url).href
-let navbarTitleImage = new URL('@/assets/images/layout/header-title.png', import.meta.url).href
-let title = ''
-let nickname = ''
-let isLogin = false
-let activeIndex = '/index'
-let sidebarRouters = []
-let menu = [
-  {
-    index: '1',
-    name: '首&nbsp;&nbsp;页',
-    path: '/',
-    children: null,
-    svg: 'home',
-  },
-  {
-    index: '2',
-    name: '知&nbsp;识&nbsp;库',
-    path: '/knowledge',
-    children: null,
-    svg: 'knowledge',
-  },
-  {
-    index: '3',
-    name: '知识引擎',
-    path: '/directory/knowledgeEngine',
-    children: null,
-    svg: 'engine',
-  },
-  {
-    index: '4',
-    name: '知识应用',
-    path: '/application',
-    children: null,
-    svg: 'application',
-  },
-]
-
+let navbarTitleImage = ref(new URL('@/assets/images/layout/header-title.png', import.meta.url).href)
+let menuLeftImage = ref(new URL('@/assets/images/layout/menu-left.png', import.meta.url).href)
+let menuRightImage = ref(new URL('@/assets/images/layout/menu-right.png', import.meta.url).href)
 const navbarBackgroundStyle = computed(() => {
   return {'background-image': `url(${navbarBackgroundImage})`}
 })
+const showMenu = computed(() => route.path !== '/index')
+let leftMenu = [
+  {name: '总&nbsp;&nbsp;览', path: '/index', type: 'left', style: {}},
+]
+let rightMenu = [
+  {name: '智慧运维', path: '/application', type: 'right', style: {}},
+]
+const date = ref('')
+const time = ref('')
+const weather = ref('')
+const location = ref('')
 
-function changeMenu(item) {
-  const path = item.path === '/' ? '/' + item.children[0].path : item.path
-  activeIndex = path
-  if (item.redirect === 'noRedirect') {
-    jumpPage(`/directory${path}`)
-  } else {
-    jumpPage(path)
+function titleChange(path) {
+  switch (path) {
+    case '/station/63304700':
+      navbarTitleImage.value = new URL('@/assets/images/layout/header-title-tsq.png', import.meta.url).href
+      break
+    default:
+      navbarTitleImage.value = new URL('@/assets/images/layout/header-title.png', import.meta.url).href
   }
 }
 
-function changeActiveIndex() {
-  let nowPath = route.path
-  menu.forEach(element => {
-    if (nowPath == element.path || nowPath.startsWith(element.path)) {
-      activeIndex = element.index
-    }
-  })
+function getDateTime() {
+  const datetime = formatd(new Date(), 'yyyy-MM-dd hh:mm:ss')
+  date.value = datetime.split(' ')[0]
+  time.value = datetime.split(' ')[1]
 }
 
-function logout() {
-  // store.increment('LogOut').then(() => {
-  //   router.go(0)
-  // })
+function getWeather() {
+  location.value = '嘉兴市'
+  weather.value = '晴 23℃'
 }
 
+setInterval(getDateTime, 1000);
+watch(() => route.path, path => titleChange(path))
+
 onMounted(() => {
-  if (getToken()) {
-    isLogin = true
-  }
-  changeActiveIndex()
+  titleChange(route.path)
+  getWeather()
 })
 </script>
 
 <template>
   <div :style="navbarBackgroundStyle" class="navbar">
-    <img :alt="title" :src="navbarTitleImage" @click="jumpPage('/')"/>
+    <img :src="navbarTitleImage" alt="" @click="jumpPage('/')"/>
+    <div v-if="showMenu" class="menu-list">
+      <div class="left-menu-list">
+        <div v-for="item in  leftMenu" :key="item.name"
+             :style="{ 'background-image': `url(${item.type === 'left' ? menuLeftImage: menuRightImage})` }"
+             class="menu-item"
+             @click="jumpPage(item.path)" v-html="item.name"></div>
+      </div>
+      <div class="right-menu-list">
+        <div v-for="item in  rightMenu" :key="item.name"
+             :style="{ 'background-image': `url(${item.type === 'left' ? menuLeftImage: menuRightImage})` }"
+             class="menu-item"
+             @click="jumpPage(item.path)" v-html="item.name"></div>
+      </div>
+    </div>
+    <div class="weather-and-time">
+      <div class="weather-and-loc">
+        <div class="weather">{{ weather }}</div>
+        <div class="loc">{{ location }}</div>
+      </div>
+      <div class="datetime">
+        <div class="time">{{ time }}</div>
+        <div class="date">{{ date }}</div>
+      </div>
+    </div>
   </div>
 </template>
 
@@ -98,12 +94,84 @@ onMounted(() => {
   pointer-events: auto;
 
   img {
-    width: 20%;
+    //width: 18%;
+    height: 40px;
     position: absolute;
-    top: 2vh;
+    top: 1.5vh;
     left: 50%;
     transform: translateX(-50%);
   }
 
+  .menu-list {
+    height: 10vh;
+    display: flex;
+    justify-content: center;
+    align-items: center;
+    color: #fff;
+    font-size: 1.2rem;
+    font-weight: bold;
+
+    .left-menu-list,
+    .right-menu-list {
+
+    }
+
+    .left-menu-list {
+      margin-right: 14%;
+    }
+
+    .right-menu-list {
+      margin-left: 14%;
+    }
+
+    .menu-item {
+      width: 12rem;
+      height: 3rem;
+      background-size: 100% 100%;
+      display: flex;
+      justify-content: center;
+      align-items: center;
+      cursor: pointer;
+
+      &:hover {
+        color: #4791f8;
+      }
+
+    }
+
+  }
+
+  .weather-and-time {
+    position: fixed;
+    top: 2vh;
+    right: 0;
+    display: flex;
+    align-items: center;
+    font-weight: bold;
+    font-size: 1.2rem;
+    line-height: 1.2rem;
+
+    .weather-and-loc {
+      padding: 0 10px;
+      border-right: 2px solid #fff;
+
+      .loc {
+        text-align: right;
+        font-size: 0.8rem;
+      }
+
+    }
+
+    .datetime {
+      padding: 0 10px;
+
+      .date {
+        font-size: 0.8rem;
+      }
+
+    }
+
+  }
+
 }
 </style>

+ 4 - 1
src/main.ts

@@ -1,10 +1,12 @@
-import './assets/styles/index.scss'
+import '@/assets/styles/index.scss'
 
 import {createApp} from 'vue'
 import {createPinia} from 'pinia'
 import VueCookies from 'vue3-cookies'
 import {VueSvgIconPlugin} from '@yzfe/vue-svgicon'
 import '@yzfe/svgicon/lib/svgicon.css'
+import ElementPlus from 'element-plus'
+import '@/assets/styles/element/index.scss'
 
 // @ts-ignore
 import App from './App.vue'
@@ -16,6 +18,7 @@ app.use(createPinia())
 app.use(router)
 app.use(VueCookies)
 app.use(VueSvgIconPlugin, {tagName: 'icon'})
+app.use(ElementPlus)
 
 // 添加到vue对象上,可以在全局通过 this.$cookies来调用
 app.config.globalProperties.$cookies = VueCookies

+ 5 - 5
src/router/index.ts

@@ -14,13 +14,13 @@ const router = createRouter({
           name: 'home',
           component: () => import('@/views/Home.vue'),
         },
+        {
+          path: 'station/:stcd',
+          name: 'station',
+          component: () => import('@/views/Station.vue'),
+        },
       ],
     },
-    {
-      path: '/about',
-      name: 'about',
-      component: () => import('@/views/AboutView.vue'),
-    },
   ],
 })
 

+ 31 - 0
src/stores/map.ts

@@ -0,0 +1,31 @@
+import {computed, ref} from 'vue'
+import {defineStore} from 'pinia'
+import bus from "@/utils/bus";
+
+export const useMapStore = defineStore('map', () => {
+  let mapOption: any = ref(null)
+
+  const count = ref(0)
+  const doubleCount = computed(() => count.value * 2)
+
+  const setOption = (option: any) => {
+    mapOption.value = option
+  }
+
+  function increment() {
+    count.value++
+  }
+
+  /**
+   * 点击事件处理
+   * @param data 数据
+   * @param e     元数据
+   * @param type 类型(point,line,polygon)
+   */
+  function handleClick(data: any, e: any, type: string) {
+    // @ts-ignore
+
+  }
+
+  return {mapOption, setOption, handleClick, count, doubleCount, increment}
+})

+ 5 - 8
src/utils/auth.ts

@@ -1,19 +1,16 @@
-import useCurrentInstance from '@/utils/useCurrentInstance'
+import {useCookies} from "vue3-cookies";
 
+const {cookies} = useCookies();
 const TokenKey = 'Admin-Token'
 
 export function getToken() {
-  const {proxy} = useCurrentInstance()
-  // return proxy.$cookies.get(TokenKey)
-  return false
+  return cookies.get(TokenKey)
 }
 
 export function setToken(token: string) {
-  const {proxy} = useCurrentInstance()
-  return proxy.$cookies.set(TokenKey, token)
+  cookies.set(TokenKey, token)
 }
 
 export function removeToken() {
-  const {proxy} = useCurrentInstance()
-  return proxy.$cookies.remove(TokenKey)
+  return cookies.remove(TokenKey)
 }

+ 21 - 0
src/utils/bus.ts

@@ -0,0 +1,21 @@
+import mitt from 'mitt'
+
+const bus = mitt()
+export default bus
+
+/*
+// 发布一个事件
+bus.emit('foo', { a: 'b' })
+
+// 订阅一个具体的事件
+bus.on('foo', (e) => console.log('foo', e))
+
+// 订阅所有事件
+bus.on('*', (type, e) => console.log(type, e))
+
+// 取消订阅同名事件
+bus.off('foo') // unlisten
+
+// 取消所有事件
+bus.all.clear()
+* */

+ 70 - 0
src/utils/color.ts

@@ -0,0 +1,70 @@
+const r = /^\#?[0-9a-fA-F]{6}$/;
+
+//hex颜色转rgb颜色
+export function HexToRgb(str: string) {
+  //test方法检查在字符串中是否存在一个模式,如果存在则返回true,否则返回false
+  if (!r.test(str)) {
+    console.log("颜色", str);
+    throw new Error("输入错误的hex");
+  }
+  // replace替换查找的到的字符串
+  str = str.replace("#", "");
+  // match得到查询数组
+  const hxs = str.match(/../g);
+  return hxs?.map(h => parseInt(h, 16));
+}
+
+//GRB颜色转Hex颜色
+export function RgbToHex(a: any, b: any, c: any) {
+  var r = /^\d{1,3}$/;
+  if (!r.test(a) || !r.test(b) || !r.test(c))
+    throw new Error("输入错误的rgb颜色值");
+  var hexs = [a.toString(16), b.toString(16), c.toString(16)];
+  for (var i = 0; i < 3; i++) if (hexs[i].length == 1) hexs[i] = "0" + hexs[i];
+  return "#" + hexs.join("");
+}
+
+//得到hex颜色值为color的加深颜色值,level为加深的程度,限0-1之间
+export function getDarkColor(color: string, level: number) {
+  if (!r.test(color)) throw new Error("输入错误的hex颜色值");
+  const rgbc = HexToRgb(color);
+  // floor 向下取整
+  if (rgbc && rgbc.length === 3) {
+    for (let i = 0; i < 3; i++) rgbc[i] = Math.floor(rgbc[i] * (1 - level));
+    return RgbToHex(rgbc[0], rgbc[1], rgbc[2]);
+  }
+  throw new Error("输入错误的rgb颜色值");
+}
+
+/**
+ * 得到颜色的减淡颜色值
+ * @param color hex颜色值
+ * @param level 加深的程度,限0-1之间
+ */
+export function getLightColor(color: string, level: number) {
+  if (!r.test(color)) throw new Error("输入错误的hex颜色值");
+  const rgbc = HexToRgb(color);
+  if (rgbc && rgbc.length === 3) {
+    for (let i = 0; i < 3; i++)
+      rgbc[i] = Math.floor((255 - rgbc[i]) * level + rgbc[i]);
+    return RgbToHex(rgbc[0], rgbc[1], rgbc[2]);
+  }
+  throw new Error("输入错误的hex颜色值");
+}
+
+// 随机颜色
+export function randomColorOfHex() {
+  //十六进制颜色随机
+  var r = Math.random();
+  var g = Math.random();
+  var b = Math.random();
+
+  g = 0.7 + g * 0.2; // [0.7 - 0.9] 排除过灰颜色
+  b = 0.4 + b * 0.4; // [0.4 - 0.8] 排除过亮过暗色
+
+  r = Math.floor(r * 256);
+  g = Math.floor(g * 256);
+  b = Math.floor(b * 256);
+  let color = "#" + r.toString(16) + g.toString(16) + b.toString(16);
+  return color;
+}

+ 20 - 5
src/utils/errorCode.ts

@@ -1,6 +1,21 @@
-export default {
-  '401': '认证失败,无法访问系统资源',
-  '403': '当前操作没有权限',
-  '404': '访问资源不存在',
-  default: '系统未知错误,请反馈给管理员',
+enum ErrorCode {
+  AuthenticationFailed = 401,
+  NoPermission = 403,
+  ResourceNotExist = 404,
 }
+
+function getErrorMessage(code: ErrorCode): string {
+  switch (code) {
+    case ErrorCode.AuthenticationFailed:
+      return '认证失败,无法访问系统资源';
+    case ErrorCode.NoPermission:
+      return '当前操作没有权限';
+    case ErrorCode.ResourceNotExist:
+      return '访问资源不存在';
+    default:
+      return '系统未知错误,请反馈给管理员';
+  }
+}
+
+export {ErrorCode, getErrorMessage}
+

+ 1 - 1
src/utils/image.ts

@@ -51,7 +51,7 @@ export function circularPicture(imageSrc: string) {
  * @returns {*|string}
  */
 export function getImageSrc(imageSrc: string) {
-  const baseApi = process.env.VUE_APP_BASE_API as string
+  const baseApi = process.env.BASE_API as string
 
   if (!imageSrc) {
     return imageSrc

+ 1 - 8
src/utils/index.ts

@@ -1,9 +1,4 @@
-import {useRouter} from 'vue-router'
-
-
-export const getImgUrl = (name: string) => {
-  return new URL(name, import.meta.url).href
-}
+import router from '@/router/index';
 
 /**
  * 跳转页面
@@ -12,8 +7,6 @@ export const getImgUrl = (name: string) => {
  * @param isBlank 是否新页面打开
  */
 export function jumpPage(path: string, query: any = null, isBlank = false) {
-  const router = useRouter()
-
   if (path) {
     if (!isBlank) {
       router.push({path, query})

+ 373 - 323
src/utils/mapConfig.ts

@@ -1,18 +1,90 @@
-// import {LineLayer, PointLayer, PolygonLayer, Popup} from "@antv/l7";
-// import {formatStringByTemplate} from "@/utils/string";
-// import {getUUID} from "@/utils/ruoyi";
-import {Scene} from "@antv/l7-scene";
-import type {ICameraOptions, Point} from "@antv/l7-core";
+import {LineLayer, PointLayer, PolygonLayer, Popup, RasterLayer, Scene} from "@antv/l7";
+import type {ICameraOptions, ISourceCFG, Point} from "@antv/l7-core";
+import {Map as AntvMap} from "@antv/l7-maps";
+import {formatStringByTemplate} from "@/utils/string";
+import {getUUID} from "@/utils/ruoyi";
+import bus from "@/utils/bus";
+
+export let scene: Scene;
+
+/**
+ * 初始化地图
+ */
+export function init() {
+  scene = new Scene({
+    id: 'mapDiv',
+    map: new AntvMap({
+      center: [119.6620473872265, 29.415337822470235],
+      zoom: 7.1,
+    }),
+  });
+
+  scene.on('loaded', () => {
+    loadBaseLayer()
+  });
+
+  // 地图平移时触发事件
+  scene.on('mapmove', () => {
+    console.log('中心点:', scene.getCenter().lng, scene.getCenter().lat, scene.getZoom(), scene.getPitch())
+  });
+
+}
+
+/**
+ * 加载底图
+ */
+function loadBaseLayer() {
+  const url1 = 'https://t0.tianditu.gov.cn/img_w/wmts?tk=b72aa81ac2b3cae941d1eb213499e15e&';
+  const layer1 = new RasterLayer().source(url1, {
+    parser: {
+      type: 'rasterTile',
+      tileSize: 256,
+      wmtsOptions: {
+        layer: 'img',
+        tileMatrixset: 'w',
+        format: 'tiles',
+      },
+    },
+  });
+  scene.addLayer(layer1);
+
+  // // 天地图卫片地图
+  // scene.on('loaded', () => {
+  //   // 底图服务
+  //   const baseLayer = new RasterLayer({zIndex: 1})
+  //     .source(
+  //       `https://t1.tianditu.gov.cn/DataServer?T=vec_w&X={x}&Y={y}&L={z}&tk=${this.token}`,
+  //       {
+  //         parser: {
+  //           type: 'rasterTile',
+  //           tileSize: 256,
+  //         }
+  //       }
+  //     );
+  //   // 注记服务
+  //   const annotionLayer = new RasterLayer({zIndex: 2})
+  //     .source(
+  //       `https://t1.tianditu.gov.cn/DataServer?T=cva_w&X={x}&Y={y}&L={z}&tk=${this.token}`,
+  //       {
+  //         parser: {
+  //           type: 'rasterTile',
+  //           tileSize: 256,
+  //         }
+  //       }
+  //     );
+  //   scene.addLayer(baseLayer);
+  //   scene.addLayer(annotionLayer);
+  // });
+}
 
 /**
  * 设置地图视角
- * @param scene
  * @param center  中心点
  * @param zoom    层级
  * @param pitch   倾斜角度
  * @param padding
  */
-export function setCenter(scene: Scene, center: Point, zoom: number, pitch = 0, padding = {}) {
+export function setCenter(center: Point, zoom: number, pitch = 0, padding = {}) {
   if (center && zoom) {
     scene.setZoomAndCenter(zoom, center);
   } else if (center) {
@@ -23,319 +95,297 @@ export function setCenter(scene: Scene, center: Point, zoom: number, pitch = 0,
   scene.setPitch(pitch);
 }
 
-//
-// let _scene: any;
-//
-// const imageMap = new Map()
-//
-// function getValueByConditions(data: any, conditions: any, field: any) {
-//   let condition;
-//   for (const item of conditions) {
-//     if (compareByConditions(data, item)) {
-//       condition = item
-//       break
-//     }
-//   }
-//
-//   if (!condition) {
-//     return null;
-//   }
-//
-//   if (field === 'color') {
-//     return condition[field]
-//   }
-//
-//   if (condition[field] !== 'image') {
-//     return condition[field]
-//   }
-//
-//   if (condition[field] === 'image') {
-//     let id;
-//     if (imageMap.has(condition.imageSrc)) {
-//       id = imageMap.get(condition.imageSrc)
-//     } else {
-//       id = getUUID()
-//       imageMap.set(condition.imageSrc, id);
-//       _scene.addImage(id, condition.imageSrc);
-//     }
-//     return id
-//   }
-//
-//   return null;
-// }
-//
-// function compareByConditions(data: any, condition: any) {
-//   switch (condition.cond) {
-//     case 'lt':
-//       if (data < condition.value) {
-//         return true
-//       }
-//       break
-//     case 'lte':
-//       if (data <= condition.value) {
-//         return true
-//       }
-//       break
-//     case 'eq':
-//       if (data == condition.value) {
-//         return true
-//       }
-//       break
-//     case 'gt':
-//       if (data > condition.value) {
-//         return true
-//       }
-//       break
-//     case 'gte':
-//       if (data >= condition.value) {
-//         return true
-//       }
-//       break
-//     case 'ne':
-//       if (data != condition.value) {
-//         return true
-//       }
-//       break
-//   }
-//   return false;
-// }
-//
-// /**
-//  * 图层数据配置
-//  * @param dataType
-//  * @returns {{parser: {x: string, y: string, type: string}}|null}
-//  */
-// function getSourceOptions(dataType: any) {
-//   switch (dataType) {
-//     case 'geojson':
-//       return null;
-//     case 'list':
-//       return {
-//         parser: {
-//           type: 'json',
-//           x: '经度',
-//           y: '纬度',
-//         },
-//       }
-//     default:
-//   }
-// }
-//
-// /**
-//  * 图层形状配置
-//  * @param layer
-//  * @param config
-//  */
-// function setLayerShape(layer: any, config: any) {
-//   if (config.isConstant) {
-//     let shape = config.value
-//     if (config.value === 'image') {
-//       let id;
-//       if (imageMap.has(config.imageSrc)) {
-//         id = imageMap.get(config.imageSrc)
-//       } else {
-//         id = getUUID()
-//         imageMap.set(config.imageSrc, id);
-//         _scene.addImage(id, config.imageSrc);
-//       }
-//       shape = id
-//     }
-//     layer.shape(shape)
-//   } else {
-//     const field = config.field
-//     const conditions = config.conditions
-//     layer.shape(field, (field: any) => {
-//       return getValueByConditions(field, conditions, 'shape');
-//     });
-//   }
-// }
-//
-// /**
-//  * 图层颜色配置
-//  * @param layer
-//  * @param config
-//  */
-// function setLayerColor(layer: any, config: any) {
-//   if (config.isConstant) {
-//     layer.color(config.value)
-//   } else {
-//     const field = config.field
-//     const conditions = config.conditions
-//     layer.color(field, (field: any) => {
-//       return getValueByConditions(field, conditions, 'color');
-//     });
-//   }
-// }
-//
-// export function setMark({dataType, mapData}, config: any, scene: any) {
-//   if (scene) {
-//     _scene = scene
-//   }
-//
-//   const sourceOptions = getSourceOptions(dataType)
-//   if (!config.enabled) {
-//     return
-//   }
-//   // 标注
-//   const markLayer = new PointLayer({})
-//     .source(mapData)
-//     .shape(config.field, 'text')
-//     .size(12)
-//     .color('#000')
-//     .style({
-//       textAnchor: 'center', // 文本相对锚点的位置 center|left|right|top|bottom|top-left
-//       textOffset: [0, 0], // 文本相对锚点的偏移量 [水平, 垂直]
-//       spacing: 2, // 字符间距
-//       padding: [1, 1], // 文本包围盒 padding [水平,垂直],影响碰撞检测结果,避免相邻文本靠的太近
-//       stroke: '#ffffff', // 描边颜色
-//       strokeWidth: 2, // 描边宽度
-//       strokeOpacity: 1.0,
-//     });
-//   _scene.addLayer(markLayer);
-// }
-//
-// function setPopup(layer: any, config: any) {
-//   if (!config.enabled) {
-//     return
-//   }
-//   layer.on(config.trigger, (e) => {
-//     let properties = e.feature.properties || e.feature
-//     const content = formatStringByTemplate(properties, config.content)
-//
-//     // offsets: [0, 0], closeButton: false,
-//     const popup = new Popup({})
-//       .setLnglat(e.lngLat)
-//       .setHTML(content);
-//     _scene.addPopup(popup);
-//   });
-// }
-//
-// function createPointByCongfig(config: any, mapData, dataType) {
-//   const sourceOptions = getSourceOptions(dataType)
-//   const layer = new PointLayer({})
-//     .source(mapData, sourceOptions)
-//     .size(16)
-//     .active(config.active);
-//   setLayerShape(layer, config.shape);
-//   // 颜色配置
-//   setLayerColor(layer, config.color);
-//   _scene.addLayer(layer);
-//   setMark({dataType, mapData}, config.mark);
-//   setPopup(layer, config.popup);
-// }
-//
-// /**
-//  * 创建线图层
-//  * @param config 配置
-//  * @param dataType 图层数据类型
-//  * @param mapData 图层数据
-//  */
-// function createLineByCongfig(config, {dataType, mapData}) {
-//   const sourceOptions = getSourceOptions(dataType)
-//   const layer = new LineLayer({})
-//     .source(mapData, sourceOptions)
-//     .size(1)
-//     .style({
-//       lineType: 'dash',
-//       dashArray: [2, 2],
-//     })
-//     .active(config.active);
-//   _scene.addLayer(layer);
-//   setMark({dataType, mapData}, config.mark);
-//   setPopup(layer, config.popup);
-// }
-//
-// /**
-//  * 创建面图层
-//  * @param config 配置
-//  * @param dataType 图层数据类型
-//  * @param mapData 图层数据
-//  */
-// function createPolygonByCongfig(config, {dataType, mapData}) {
-//   const sourceOptions = getSourceOptions(dataType)
-//   const layer = new PolygonLayer({})
-//     .source(mapData, sourceOptions)
-//     .shape('fill')
-//     .active(config.active);
-//   // 颜色配置
-//   setLayerColor(layer, config.color);
-//   _scene.addLayer(layer);
-//   setMark({dataType, mapData}, config.mark);
-//   setPopup(layer, config.popup);
-// }
-//
-// function createMarkByCongfig(config, {dataType, mapData}) {
-//   setMark({dataType, mapData}, config.mark)
-// }
-//
-// /**
-//  * 根据配置加载图层
-//  * @param scene
-//  * @param mapConfig
-//  * @returns {Promise<void>}
-//  */
-// export async function loadMapData(scene: any, mapConfig: any) {
-//   _scene = scene
-//   console.log('mapConfig', mapConfig);
-//   // 清空图层
-//   _scene.removeAllLayer();
-//   if (!mapConfig.enabled) {
-//     return
-//   }
-//
-//   const layers = mapConfig.layers
-//   for (const layer of layers) {
-//     if (!layer.data?.mapDataId) {
-//       continue;
-//     }
-//
-//     // 1. 加载数据
-//     const mapJson:any = {}
-//     console.log('mapJson', mapJson);
-//     // switch (layer.type) {
-//     //   case 'point':
-//     //     createPointByCongfig(layer, mapJson)
-//     //     break
-//     //   case 'line':
-//     //     createLineByCongfig(layer, mapJson)
-//     //     break
-//     //   case 'polygon':
-//     //     createPolygonByCongfig(layer, mapJson)
-//     //     break
-//     //   case 'mark':
-//     //     createMarkByCongfig(layer, mapJson)
-//     //     break
-//     // }
-//   }
-//
-//   // 视角
-//   setCenter(scene, mapConfig.center, mapConfig.zoom, mapConfig.pitch);
-// }
-//
-
-//
-//
-// export function getLegendData(config: any) {
-//   const layers = config.layers
-//
-//   const layerLegend = layers.reduce((acc: any, item: any) => {
-//     if (item.legend && item.legend.data) {
-//       return acc.concat(item.legend.data);
-//     } else {
-//       return acc;
-//     }
-//   }, []);
-//
-//   return layerLegend.map((item: any) => {
-//     const data = {label: item.text}
-//     if (item.shape === 'image') {
-//       data.imgType = 'img'
-//       data.src = item.imgSrc
-//     } else {
-//       data.imgType = 'svg'
-//       data.src = item.shape
-//       data.imgStyle = {fill: item.color}
-//     }
-//     return data
-//   })
-// }
+export function loadMap(option: any) {
+  if (option.view) {
+    setCenter(option.view.center, option.view.zoom)
+  }
+  if (option.layers) {
+    // 清空图层
+    scene.removeAllLayer();
+    // 加载地图
+    loadBaseLayer()
+
+    option.layers.forEach((layer: any) => {
+      switch (layer.type) {
+        case 'point':
+          createPointByCongfig(layer)
+          break
+        // case 'line':
+        //   createLineByCongfig(layer, mapJson)
+        //   break
+        // case 'polygon':
+        //   createPolygonByCongfig(layer, mapJson)
+        //   break
+        // case 'mark':
+        //     setMark(config)
+        //   break
+      }
+    })
+  }
+}
+
+function createPointByCongfig(config: any) {
+  const layer = new PointLayer({})
+    .source(config.data, config.dataOptions)
+    .size(14)
+    .active(config.emphasis.show);
+  setLayerShape(layer, config.shape);
+  // 颜色配置
+  setLayerColor(layer, config.color);
+  scene.addLayer(layer);
+  setMark(config);
+  setPopup(layer, config);
+
+  // 点击事件
+  layer.on('click', (e: any) => {
+    let row = e.feature.properties || e.feature
+    bus.emit('point_click', {data: row, e})
+  });
+}
+
+const imageMap = new Map()
+
+function getValueByConditions(data: any, conditions: any, field: any) {
+  let condition;
+  for (const item of conditions) {
+    if (compareByConditions(data, item)) {
+      condition = item
+      break
+    }
+  }
+
+  if (!condition) {
+    return null;
+  }
+
+  if (field === 'color') {
+    return condition[field]
+  }
+
+  if (condition[field] !== 'image') {
+    return condition[field]
+  }
+
+  if (condition[field] === 'image') {
+    let id;
+    if (imageMap.has(condition.imageSrc)) {
+      id = imageMap.get(condition.imageSrc)
+    } else {
+      id = getUUID()
+      imageMap.set(condition.imageSrc, id);
+      scene.addImage(id, condition.imageSrc);
+    }
+    return id
+  }
+
+  return null;
+}
+
+function compareByConditions(data: any, condition: any) {
+  switch (condition.cond) {
+    case 'lt':
+      if (data < condition.value) {
+        return true
+      }
+      break
+    case 'lte':
+      if (data <= condition.value) {
+        return true
+      }
+      break
+    case 'eq':
+      if (data == condition.value) {
+        return true
+      }
+      break
+    case 'gt':
+      if (data > condition.value) {
+        return true
+      }
+      break
+    case 'gte':
+      if (data >= condition.value) {
+        return true
+      }
+      break
+    case 'ne':
+      if (data != condition.value) {
+        return true
+      }
+      break
+  }
+  return false;
+}
+
+/**
+ * 图层数据配置
+ * @param dataType
+ * @returns {{parser: {x: string, y: string, type: string}}|null}
+ */
+function getSourceOptions(dataType: any) {
+  switch (dataType) {
+    case 'geojson':
+      return null;
+    case 'list':
+      return {
+        parser: {
+          type: 'json',
+          x: '经度',
+          y: '纬度',
+        },
+      }
+    default:
+  }
+}
+
+
+/**
+ * 图层形状配置
+ * @param layer
+ * @param config
+ */
+function setLayerShape(layer: any, config: any) {
+  if (config.isConstant) {
+    let shape = config.value
+    if (config.value === 'image') {
+      let id;
+      if (imageMap.has(config.imageSrc)) {
+        id = imageMap.get(config.imageSrc)
+      } else {
+        id = getUUID()
+        imageMap.set(config.imageSrc, id);
+        scene.addImage(id, config.imageSrc);
+      }
+      shape = id
+    }
+    layer.shape(shape)
+  } else {
+    const field = config.field
+    const conditions = config.conditions
+    layer.shape(field, (field: any) => {
+      return getValueByConditions(field, conditions, 'shape');
+    });
+  }
+}
+
+/**
+ * 图层颜色配置
+ * @param layer
+ * @param config
+ */
+function setLayerColor(layer: any, config: any) {
+  if (config.isConstant) {
+    layer.color(config.value)
+  } else {
+    const field = config.field
+    const conditions = config.conditions
+    layer.color(field, (field: any) => {
+      return getValueByConditions(field, conditions, 'color');
+    });
+  }
+}
+
+export function setMark(config: any) {
+  if (!config.label.show) {
+    return
+  }
+  // 标注
+  const markLayer = new PointLayer({})
+    .source(config.data, config.dataOptions)
+    .shape(config.label.field, 'text')
+    .size(12)
+    .color('#000')
+    .style({
+      textAnchor: config.label.position || 'center', // 文本相对锚点的位置 center|left|right|top|bottom|top-left
+      textOffset: [0, -60], // 文本相对锚点的偏移量 [水平, 垂直]
+      spacing: 2, // 字符间距
+      padding: [1, 1], // 文本包围盒 padding [水平,垂直],影响碰撞检测结果,避免相邻文本靠的太近
+      stroke: '#ffffff', // 描边颜色
+      strokeWidth: 2, // 描边宽度
+      strokeOpacity: 1.0,
+    });
+  scene.addLayer(markLayer);
+}
+
+function setPopup(layer: any, config: any) {
+  if (!config.enabled) {
+    return
+  }
+  layer.on(config.trigger, (e: any) => {
+    let properties = e.feature.properties || e.feature
+    const content = formatStringByTemplate(properties, config.content)
+
+    // offsets: [0, 0], closeButton: false,
+    const popup = new Popup({})
+      .setLnglat(e.lngLat)
+      .setHTML(content);
+    scene.addPopup(popup);
+  });
+}
+
+
+/**
+ * 创建线图层
+ * @param config 配置
+ * @param dataType 图层数据类型
+ * @param mapData 图层数据
+ */
+function createLineByCongfig(config: any, dataType: any, mapData: any) {
+  const sourceOptions = getSourceOptions(dataType) as ISourceCFG
+  const layer = new LineLayer({})
+    .source(mapData, sourceOptions)
+    .size(1)
+    .style({
+      lineType: 'dash',
+      dashArray: [2, 2],
+    })
+    .active(config.active);
+  scene.addLayer(layer);
+  setMark(config);
+  setPopup(layer, config.popup);
+}
+
+/**
+ * 创建面图层
+ * @param config 配置
+ * @param dataType 图层数据类型
+ * @param mapData 图层数据
+ */
+function createPolygonByCongfig(config: any, dataType: any, mapData: any) {
+  const sourceOptions = getSourceOptions(dataType) as ISourceCFG
+  const layer = new PolygonLayer({})
+    .source(mapData, sourceOptions)
+    .shape('fill')
+    .active(config.active);
+  // 颜色配置
+  setLayerColor(layer, config.color);
+  scene.addLayer(layer);
+  setMark(config);
+  setPopup(layer, config.popup);
+}
+
+export function getLegendData(config: any) {
+  const layers = config.layers
+
+  const layerLegend = layers.reduce((acc: any, item: any) => {
+    if (item.legend && item.legend.data) {
+      return acc.concat(item.legend.data);
+    } else {
+      return acc;
+    }
+  }, []);
+
+  // return layerLegend.map((item: any) => {
+  //   const data = {label: item.text}
+  //   if (item.shape === 'image') {
+  //     data.imgType = 'img'
+  //     data.src = item.imgSrc
+  //   } else {
+  //     data.imgType = 'svg'
+  //     data.src = item.shape
+  //     data.imgStyle = {fill: item.color}
+  //   }
+  //   return data
+  // })
+  return null
+}

+ 112 - 119
src/utils/request.ts

@@ -1,119 +1,112 @@
-// import axios from "axios";
-// import {getToken} from "@/utils/auth";
-// import errorCode from "@/utils/errorCode";
-// import {validURL} from "@/utils/validate";
-//
-// axios.defaults.headers["Content-Type"] = "application/json;charset=utf-8";
-// // 创建axios实例
-// const service = axios.create({
-//   // axios中请求配置有baseURL选项,表示请求URL公共部分
-//   // baseURL: process.env.VUE_APP_BASE_API,
-//   // 超时
-//   timeout: 10000,
-// });
-// // request拦截器
-// service.interceptors.request.use(
-//   (config) => {
-//     if (
-//       !config.url.startsWith("/gxpt") &&
-//       !config.url.startsWith("/knowDocApi") &&
-//       !config.url.startsWith("/flood_api") &&
-//       !config.url.startsWith("/thgx_api") &&
-//       !config.url.startsWith("/twin_api") &&
-//       !config.url.startsWith("/yuyan_api") &&
-//       !config.url.startsWith("/py_api") &&
-//       !config.url.startsWith("/langchain_api") &&
-//       !config.url.startsWith("/tbaszy_api") &&
-//       !validURL(config.url)
-//     ) {
-//       config.url = process.env.VUE_APP_BASE_API + config.url;
-//     }
-//     // 是否需要设置 token
-//     const isToken = (config.headers || {}).isToken === false;
-//     if (getToken() && !isToken) {
-//       config.headers["Authorization"] = "Bearer " + getToken(); // 让每个请求携带自定义token 请根据实际情况自行修改
-//     }
-//     // get请求映射params参数
-//     if (config.method === "get" && config.params) {
-//       let url = config.url + "?";
-//       for (const propName of Object.keys(config.params)) {
-//         const value = config.params[propName];
-//         var part = encodeURIComponent(propName) + "=";
-//         if (value !== null && typeof value !== "undefined") {
-//           if (typeof value === "object") {
-//             for (const key of Object.keys(value)) {
-//               if (value[key] !== null && typeof value[key] !== "undefined") {
-//                 let params = propName + "[" + key + "]";
-//                 let subPart = encodeURIComponent(params) + "=";
-//                 url += subPart + encodeURIComponent(value[key]) + "&";
-//               }
-//             }
-//           } else {
-//             url += part + encodeURIComponent(value) + "&";
-//           }
-//         }
-//       }
-//       url = url.slice(0, -1);
-//       config.params = {};
-//       config.url = url;
-//     }
-//     return config;
-//   },
-//   (error) => {
-//     console.log(error);
-//     Promise.reject(error);
-//   }
-// );
-//
-// // 响应拦截器
-// service.interceptors.response.use(
-//   (res) => {
-//     // 未设置状态码则默认成功状态
-//     const code = res.data.code || 200;
-//     // 查询错误信息
-//     const msg = errorCode[code] || res.data.msg || errorCode["default"];
-//     if (code === 401) {
-//       MessageBox.confirm(
-//         "登录状态已过期,您可以继续留在该页面,或者重新登录",
-//         "系统提示",
-//         {
-//           confirmButtonText: "重新登录",
-//           cancelButtonText: "取消",
-//           type: "warning",
-//         }
-//       )
-//         .then(() => {
-//           store.dispatch("LogOut").then(() => {
-//             location.href = "/login";
-//           });
-//         })
-//         .catch(() => {
-//         });
-//       return Promise.reject("无效的会话,或者会话已过期,请重新登录。");
-//     } else if (code === 500) {
-//       Message({message: msg, type: 'error'})
-//       return Promise.reject(msg)
-//     } else if (code === 601) {
-//       Message({message: msg, type: 'error'})
-//       return Promise.reject('error')
-//     } else if (code !== 200) {
-//       return Promise.reject(msg);
-//     } else {
-//       return Promise.resolve(res.data);
-//     }
-//   },
-//   (error) => {
-//     console.error("err", error);
-//     let {message} = error;
-//     if (message == "Network Error") {
-//       message = "后端接口连接异常";
-//     } else if (message.includes("timeout")) {
-//       message = "系统接口请求超时";
-//     } else if (message.includes("Request failed with status code")) {
-//       message = "系统接口" + message.substr(message.length - 3) + "异常";
-//     }
-//     return Promise.reject(error);
-//   }
-// );
-//
-// export default service;
+import axios from "axios";
+import {getToken} from "@/utils/auth";
+import {getErrorMessage} from "@/utils/errorCode";
+import {validURL} from "@/utils/validate";
+import {ElMessage, ElMessageBox} from 'element-plus'
+
+axios.defaults.headers["Content-Type"] = "application/json;charset=utf-8";
+// 创建axios实例
+const service = axios.create({
+  // axios中请求配置有baseURL选项,表示请求URL公共部分
+  // baseURL: process.env.BASE_API,
+  // 超时
+  timeout: 10000,
+});
+// request拦截器
+service.interceptors.request.use(
+  (config: any) => {
+    if (
+      !config.url.startsWith("/tbazmw_api") &&
+      !validURL(config.url)
+    ) {
+      config.url = process.env.BASE_API + config.url;
+    }
+    // 是否需要设置 token
+    const isToken = (config.headers || {}).isToken === false;
+    if (getToken() && !isToken) {
+      config.headers["Authorization"] = "Bearer " + getToken(); // 让每个请求携带自定义token 请根据实际情况自行修改
+    }
+    // get请求映射params参数
+    if (config.method === "get" && config.params) {
+      let url = config.url + "?";
+      for (const propName of Object.keys(config.params)) {
+        const value = config.params[propName];
+        var part = encodeURIComponent(propName) + "=";
+        if (value !== null && typeof value !== "undefined") {
+          if (typeof value === "object") {
+            for (const key of Object.keys(value)) {
+              if (value[key] !== null && typeof value[key] !== "undefined") {
+                let params = propName + "[" + key + "]";
+                let subPart = encodeURIComponent(params) + "=";
+                url += subPart + encodeURIComponent(value[key]) + "&";
+              }
+            }
+          } else {
+            url += part + encodeURIComponent(value) + "&";
+          }
+        }
+      }
+      url = url.slice(0, -1);
+      config.params = {};
+      config.url = url;
+    }
+    return config;
+  },
+  (error) => {
+    console.log(error);
+    Promise.reject(error);
+  }
+);
+
+// 响应拦截器
+service.interceptors.response.use(
+  (res) => {
+    // 未设置状态码则默认成功状态
+    const code = res.data.code || 200;
+    // 查询错误信息
+    const msg = res.data.msg || getErrorMessage(code);
+    if (code === 401) {
+      ElMessageBox.confirm(
+        "登录状态已过期,您可以继续留在该页面,或者重新登录",
+        "系统提示",
+        {
+          confirmButtonText: "重新登录",
+          cancelButtonText: "取消",
+          type: "warning",
+        }
+      )
+        .then(() => {
+          // store.dispatch("LogOut").then(() => {
+          //   location.href = "/login";
+          // });
+        })
+        .catch(() => {
+        });
+      return Promise.reject("无效的会话,或者会话已过期,请重新登录。");
+    } else if (code === 500) {
+      ElMessage({message: msg, type: 'error'})
+      return Promise.reject(msg)
+    } else if (code === 601) {
+      ElMessage({message: msg, type: 'error'})
+      return Promise.reject('error')
+    } else if (code !== 200) {
+      return Promise.reject(msg);
+    } else {
+      return Promise.resolve(res.data);
+    }
+  },
+  (error) => {
+    console.error("err", error);
+    let {message} = error;
+    if (message == "Network Error") {
+      message = "后端接口连接异常";
+    } else if (message.includes("timeout")) {
+      message = "系统接口请求超时";
+    } else if (message.includes("Request failed with status code")) {
+      message = "系统接口" + message.substr(message.length - 3) + "异常";
+    }
+    return Promise.reject(error);
+  }
+);
+
+export default service;

+ 27 - 0
src/utils/ruoyi.ts

@@ -49,6 +49,33 @@ export function parseTime(time: any, pattern: string) {
   return time_str;
 }
 
+export function formatd(date: Date, fmt: string): string {
+  const o: { [key: string]: number } = {
+    "M+": date.getMonth() + 1, // 月份
+    "d+": date.getDate(), // 日
+    "h+": date.getHours(), // 小时
+    "m+": date.getMinutes(), // 分
+    "s+": date.getSeconds(), // 秒
+    "q+": Math.floor((date.getMonth() + 3) / 3), // 季度
+    "S": date.getMilliseconds() // 毫秒
+  };
+
+  if (/(y+)/.test(fmt)) {
+    fmt = fmt.replace(RegExp.$1, (date.getFullYear() + "").substr(4 - RegExp.$1.length));
+  }
+
+  for (let k in o) {
+    if (new RegExp("(" + k + ")").test(fmt)) {
+      fmt = fmt.replace(
+        RegExp.$1,
+        (RegExp.$1.length === 1) ? o[k].toString() : ("00" + o[k]).substr(("" + o[k]).length)
+      );
+    }
+  }
+
+  return fmt;
+}
+
 // 字符串格式化(%s )
 export function sprintf(str: string) {
   var args = arguments,

+ 53 - 0
src/utils/station.ts

@@ -0,0 +1,53 @@
+export default [
+  {
+    stcd: '63304700',
+    stnm: '太师桥',
+    lgtd: '120.4941',
+    lttd: '30.762836',
+    isExternalAddress: false,
+    path: '',
+    sttp: '水文站',
+    detail: '太师桥水文站是国家基本水文站,位于江苏省苏州市吴江区桃源镇前窑村,在江南运河(澜溪塘)干流江苏~浙江省际边界上,属太湖流域杭嘉湖区水系。\n2015年7月设站至今,主要监测项目有水位、流量、降水、水质(水温、pH、电导率、溶解氧、浊度、氧化还原电位、氨氮、高锰酸盐指数等8项指标)。',
+    img: ''
+  },
+  {
+    stcd: '63304650',
+    stnm: '思源',
+    lgtd: '120.522088',
+    lttd: '30.841321',
+    isExternalAddress: false,
+    path: '',
+    sttp: '',
+    detail: '', img: ''
+  },
+  {
+    stcd: '70111400',
+    stnm: '街口',
+    lgtd: '118.71691',
+    lttd: '29.72391',
+    isExternalAddress: true,
+    path: '',
+    sttp: '',
+    detail: '', img: ''
+  },
+  {
+    stcd: '70112100',
+    stnm: '罗桐埠',
+    lgtd: '119.254444',
+    lttd: '29.476389',
+    isExternalAddress: true,
+    path: '',
+    sttp: '',
+    detail: '', img: ''
+  },
+  {
+    stcd: '71501010',
+    stnm: '托溪',
+    lgtd: '119.354764',
+    lttd: '27.491128',
+    isExternalAddress: true,
+    path: '',
+    sttp: '',
+    detail: '', img: ''
+  },
+]

+ 127 - 14
src/views/Home.vue

@@ -1,8 +1,16 @@
 <script lang="ts" setup>
-import {onMounted, ref} from "vue";
+import {onMounted, reactive, ref} from "vue";
+import {useMapStore} from "@/stores/map";
 import RightFrame from "@/components/RightFrame.vue";
 import Card01 from "@/components/card/Card01.vue";
 import Chart from "@/components/chart.vue";
+import StripeTable from "@/components/StripeTable.vue";
+import {getRStLLMaxDate} from "@/api/home";
+import bus from "@/utils/bus";
+import stations from "@/utils/station";
+import {jumpPage} from "@/utils";
+
+const zmwjianjie = new URL('@/assets/images/zmw_jieshao.jpg', import.meta.url).href
 
 const left2Ref = ref(null)
 
@@ -125,10 +133,45 @@ function reloadRight1() {
   right1Ref.value.loadChart(option)
 }
 
+const tableColumns = [
+  {
+    label: '站名', prop: 'stnm', width: '110', convertFn: (data) => {
+      return data ? data.trim() : ''
+    }
+  },
+  {
+    label: '时间', prop: 'tm', width: '90', convertFn: (data) => {
+      return data ? data.substring(5, 16) : ''
+    }
+  },
+  {
+    label: '水位(m)', prop: 'z', width: '80', convertFn: (data) => {
+      return Number(data).toFixed(2)
+    }
+  },
+  {
+    label: '流量', prop: 'q', width: '70', convertFn: (data) => {
+      return Number(data).toFixed(2)
+    }
+  },
+  {
+    label: '雨量', prop: 'drp', width: '65', convertFn: (data) => {
+      return data | 0
+    }
+  },
+]
+const tableData = reactive([])
+
+function getStationList() {
+  getRStLLMaxDate().then(res => {
+    tableData.push(...res)
+    initPoints()
+  })
+}
+
 const right3Ref = ref(null)
 
 function reloadRight3() {
-  debugger
   const option = {
     backgroundColor: "#0B2D55",
     tooltip: {
@@ -195,31 +238,98 @@ function reloadRight3() {
   right3Ref.value.loadChart(option)
 }
 
+const mapStore = useMapStore()
+
+/**
+ * 初始化测站点位
+ */
+function initPoints() {
+  const option = {
+    view: {
+      center: [104.114129, 37.550339],
+      zoom: 5,
+    },
+    layers: [
+      {
+        type: 'point',
+        data: tableData,
+        dataOptions: {
+          parser: {
+            type: 'json',
+            x: 'lgtd',
+            y: 'lttd',
+          },
+        },
+        label: {
+          show: true,
+          field: 'stnm',
+        },
+        emphasis: {
+          show: true,
+          label: {
+            show: true
+          }
+        },
+        size: 16,
+        color: {
+          isConstant: true,
+          value: '#004ef8',
+          field: null,
+          conditions: []
+        },
+        shape: {
+          isConstant: true,
+          value: 'circle',
+          imageSrc: null,
+          field: null,
+          conditions: []
+        },
+        popup: {
+          enabled: true,
+          trigger: 'click',
+          content: '',
+        },
+      },
+    ]
+  }
+  mapStore.setOption(option)
+}
 
 onMounted(() => {
   reloadLeft2()
 
   reloadRight1()
+  getStationList()
   reloadRight3()
 })
+
+// 订阅一个具体的事件
+bus.on('point_click', (data: any) => {
+  console.log('data', data.data, data.e)
+  const station = stations.find(item => item.stcd === data.data.stcd)
+  if (station) {
+    if (station.isExternalAddress) {
+      jumpPage(station.path, null, true)
+    } else {
+      jumpPage(`/station/${station.stcd}`)
+    }
+  }
+})
 </script>
 
 <template>
   <right-frame>
     <template #leftModule>
-      <card01 style="height: 60%" title="简介">
-        <h3>浙闽皖水文水资源监测中心</h3>
-        <p class="indented-text">
-          浙闽皖水文水资源监测中心位于浙江省嘉兴市经济技术开发区龙舟路753号,土地面积4.98亩,建筑面积1699.87平方米。
+      <card01 title="简介">
+        <h3 class="introduce-title">浙闽皖水文水资源监测中心</h3>
+        <p class="introduce-text">
+          位于浙江省嘉兴市经济技术开发区龙舟路753号,土地面积4.98亩,建筑面积1699.87平方米。
         </p>
-        <p class="indented-text">
-          根据《水利部太湖流域管理局关于水文局(信息中心)增设浙闽皖水文水资源监测中心等内部业务机构的批复》太管人事[2017]188号批复,浙闽皖水文水资源监测中心为二级非独立法人业务机构,正处级。
-          主要负责太湖流域片浙闽、浙皖边界河流、湖泊(千岛湖)、新安江水文实验区水文测站运行管理、水文水资源水环境监测评价分析工作;负责太湖流域地下水监测管理、资料汇交,提供地下水信息服务;负责杭州湾、钱塘江河口区域风暴潮监测与研究,负责太湖流域片沿海潮位站的风暴潮信息收集、分析等工作。
-          浙闽皖水文水资源监测中心内设综合科、测验与技术科、黄山水文水资源监测分中心(三级非独立法人业务机构,正科级)、宁德水文水资源监测分中心(三级非独立法人业务机构,正科级)。
-          截止2021年底,浙闽皖水文水资源监测中心有职工23名,其中1名正高职称,1名副高职称。
-          浙闽皖水文水资源监测中心管理运行14个水文测站,其中国家基本水文站5个,共建共管2个。监督性管理地下水监测井387个。与河海大学联合管理新安江水文实验站1处。浙闽、浙闽省界边界水质站13个。
+        <p class="introduce-text">
+          浙闽皖水文水资源监测中心主要负责太湖流域片浙闽、浙皖边界河流、湖泊(千岛湖)、新安江水文实验区水文测站运行管理、水文水资源水环境监测评价分析工作;负责太湖流域地下水监测管理、资料汇交,提供地下水信息服务;负责杭州湾、钱塘江河口区域风暴潮监测与研究,负责太湖流域片沿海潮位站的风暴潮信息收集、分析等工作。
+          浙闽皖水文水资源监测中心内设综合科、测验与技术科、黄山水文水资源监测分中心、宁德水文水资源监测分中心。
         </p>
-        <img class="float-image float-image-right" src=""/>
+        <img :src="zmwjianjie" alt="" class="introduce-img"/>
       </card01>
       <card01 style="height: 30%" title="站点统计">
         <chart ref="left2Ref"></chart>
@@ -230,7 +340,7 @@ onMounted(() => {
         <chart ref="right1Ref"></chart>
       </card01>
       <card01 style="height: 33%" title="站点清单">
-        aaaaa
+        <stripe-table :columns="tableColumns" :data="tableData"></stripe-table>
       </card01>
       <card01 style="height: 33%" title="水质分析">
         <chart ref="right3Ref"></chart>
@@ -238,3 +348,6 @@ onMounted(() => {
     </template>
   </right-frame>
 </template>
+<style lang="scss" scoped>
+@use "@/assets/styles/introduce.scss";
+</style>

+ 131 - 0
src/views/Station.vue

@@ -0,0 +1,131 @@
+<script lang="ts" setup>
+import {onMounted, reactive, ref} from "vue";
+import {useRoute} from 'vue-router';
+import RightFrame from "@/components/RightFrame.vue";
+import Card01 from "@/components/card/Card01.vue";
+import stations from "@/utils/station";
+import StripeTable from "@/components/StripeTable.vue";
+import ColorTag from "@/components/tag/ColorTag.vue";
+import ImageTag from "@/components/tag/ImageTag.vue";
+
+const route = useRoute()
+let dibaImage = ref(new URL('@/assets/images/dike.png', import.meta.url).href)
+
+function getStation(stcd) {
+  return stations.find(item => item.stcd === stcd)
+}
+
+const station = ref(getStation(route.params.stcd))
+const introduces = ref(station.value.detail?.split('\n'))
+const introduceImg = new URL(station.value.img, import.meta.url)
+
+const deviceInfoColumns = [
+  {label: '设备名称', prop: 'name'},
+  {label: '设备位置', prop: 'loc', width: '110'},
+  {label: '设备状态', prop: 'status', width: '110'},
+]
+
+const deviceInfoData = [
+  {name: '总磷分析仪', loc: '100米', status: '正常'},
+  {name: '总氮分析仪', loc: '100米', status: '正常'},
+  {name: '水位计', loc: '100米', status: '正常'},
+  {name: '氢氮分析仪', loc: '100米', status: '正常'},
+]
+
+const wqData = ref([])
+const wlData = reactive({ss: '3.80', zg: '4.65', zd: '4.65', bz: '4.65', jj: '4.65'})
+
+onMounted(() => {
+  wqData.value = [
+    {label: '水温', value: '22.3', unit: '℃', imgSrc: new URL('@/assets/images/sz-wd.png', import.meta.url).href},
+    {label: '浊度', value: '235', unit: 'NTU', imgSrc: new URL('@/assets/images/sz-zd.png', import.meta.url).href},
+    {label: '溶解氧', value: '8.22', unit: 'mg/L', imgSrc: new URL('@/assets/images/sz-rjy.png', import.meta.url).href},
+    {
+      label: '电导率',
+      value: '8.22',
+      unit: 'US/cm',
+      imgSrc: new URL('@/assets/images/sz-ddl.png', import.meta.url).href
+    },
+    {label: '总磷', value: '22.3', unit: 'mg/L', imgSrc: new URL('@/assets/images/sz-zl.png', import.meta.url).href},
+    {label: '总氮', value: '22.3', unit: 'mg/L', imgSrc: new URL('@/assets/images/sz-tn.png', import.meta.url).href},
+    {label: '氨氮', value: '22.3', unit: 'mg/L', imgSrc: new URL('@/assets/images/sz-ad.png', import.meta.url).href},
+  ]
+
+})
+</script>
+
+<template>
+  <right-frame>
+    <template #leftModule>
+      <card01 title="测站简介">
+        <h3 class="introduce-title">{{ station.stnm + station.sttp }}</h3>
+        <p v-for="text in introduces" class="introduce-text" v-html="text"></p>
+        <img v-if="station.img" :src="introduceImg" alt="" class="introduce-img"/>
+      </card01>
+      <card01 style="height: 30%" title="现场监控">
+      </card01>
+    </template>
+    <template #rightModule>
+      <card01 style="height: 33%;" title="特征水位">
+        <el-row :gutter="10" justify="space-around" style="height: 100%;">
+          <el-col :span="16" style="height: 66%;margin-bottom: 1%;position: relative;">
+            <div class="difang-water-level">
+              <span class="difang-water-level-value">3.8m</span>
+            </div>
+            <img :src="dibaImage" alt="" class="difang-image"/>
+          </el-col>
+          <el-col :span="8"
+                  style="height: 66%;margin-bottom: 1%;display: flex;flex-direction: column;justify-content: space-between;">
+            <color-tag :value="wlData.jj" label="实时水位" style="height: 48%;" unit="m"></color-tag>
+            <color-tag :value="wlData.zg" label="历史最高水位" style="height: 48%;" unit="m"></color-tag>
+          </el-col>
+          <el-col :span="8" style="height: 33%;">
+            <color-tag :value="wlData.bz" backgroundColor="#bb232f" label="保证水位" unit="m"></color-tag>
+          </el-col>
+          <el-col :span="8" style="height: 33%;">
+            <color-tag :value="wlData.jj" backgroundColor="#b38b30" label="警戒水位" unit="m"></color-tag>
+          </el-col>
+          <el-col :span="8" style="height: 33%;">
+            <color-tag :value="wlData.zd" backgroundColor="#adcbe0" label="历史最低水位" unit="m"></color-tag>
+          </el-col>
+        </el-row>
+      </card01>
+      <card01 style="height: 33%" title="水质分析">
+        <el-row :gutter="10" style="height: 100%;">
+          <el-col v-for="(item, index) in wqData" :key="index" :span="6" style="height: 49%;margin-bottom: 1%;">
+            <image-tag :imgSrc="item.imgSrc" :label="item.label" :unit="item.unit" :value="item.value"></image-tag>
+          </el-col>
+        </el-row>
+      </card01>
+      <card01 style="height: 33%" title="设备总览">
+        <stripe-table :columns="deviceInfoColumns" :data="deviceInfoData"></stripe-table>
+      </card01>
+    </template>
+  </right-frame>
+</template>
+<style lang="scss" scoped>
+@use "@/assets/styles/introduce.scss";
+
+.difang-water-level {
+  position: absolute;
+  bottom: 10px;
+  width: 80%;
+  height: 60%;
+  background-color: rgba(0, 204, 255, 0.6);
+  border-top: 3px solid rgba(0, 204, 255);
+
+  .difang-water-level-value {
+    position: absolute;
+    top: -2rem;
+    font-size: 1.2rem;
+    color: #fff;
+  }
+
+}
+
+.difang-image {
+  position: relative;
+  width: 100%;
+  height: 100%;
+}
+</style>

+ 25 - 2
vite.config.ts

@@ -5,10 +5,29 @@ import vue from '@vitejs/plugin-vue'
 import vueJsx from '@vitejs/plugin-vue-jsx'
 import vueDevTools from 'vite-plugin-vue-devtools'
 import svgicon from 'vite-plugin-svgicon'
-import * as path from "path";
+import * as path from 'path'
 
 // https://vite.dev/config/
 export default defineConfig({
+  define: {
+    'process.env': {
+      'BASE_API': '/base_api'
+    },
+  },
+  // 配置前端服务地址和端口
+  server: {
+    host: '0.0.0.0',
+    port: 80,
+    // 设置反向代理,跨域
+    proxy: {
+      '/tbazmw_api': {
+        // 后台地址
+        target: 'http://10.8.48.234:8081/',
+        changeOrigin: true,
+        rewrite: path => path.replace(/^\/tbazmw_api/, '')
+      },
+    }
+  },
   plugins: [
     vue(),
     vueJsx(),
@@ -26,7 +45,11 @@ export default defineConfig({
   css: {
     preprocessorOptions: {
       scss: {
-        api: "modern-compiler" // or 'modern'
+        api: "modern-compiler", // or 'modern'
+        // 自动导入定制化样式文件进行样式覆盖
+        // additionalData: `
+        //   @use "@/assets/styles/element/index.scss";
+        // `,
       }
     }
   },