Explorar o código

feat:新增播放器插件、新增水文站弹窗组件

yangqishu hai 4 meses
pai
achega
af7618fe94

+ 5 - 2
package.json

@@ -9,11 +9,13 @@
   },
   "dependencies": {
     "@ct/iframe-connect-sdk": "^1.0.17",
+    "axios": "^1.11.0",
+    "js-cookie": "3.0.5",
     "@turf/turf": "^7.2.0",
     "core-js": "^3.8.3",
     "echarts": "^5.5.1",
-    "lodash-es": "^4.17.21",
     "element-ui": "^2.15.14",
+    "lodash-es": "^4.17.21",
     "mars3d": "^3.9.3",
     "mars3d-cesium": "^1.127.0",
     "process": "^0.11.10",
@@ -26,12 +28,13 @@
   "devDependencies": {
     "@babel/core": "^7.12.16",
     "@babel/eslint-parser": "^7.12.16",
+    "@ct/remote-page-loader": "^1.0.15",
     "@vue/cli-plugin-babel": "~5.0.0",
     "@vue/cli-plugin-eslint": "~5.0.0",
     "@vue/cli-service": "~5.0.0",
-    "node-polyfill-webpack-plugin": "^2.0.1",
     "eslint": "^7.32.0",
     "eslint-plugin-vue": "^8.0.3",
+    "node-polyfill-webpack-plugin": "^2.0.1",
     "vue-template-compiler": "^2.6.14"
   },
   "eslintConfig": {

A diferenza do arquivo foi suprimida porque é demasiado grande
+ 71357 - 0
public/EasyPlayer-lib.min.js


+ 6 - 0
public/geojson/sw1.geojson

@@ -0,0 +1,6 @@
+{
+  "type": "FeatureCollection",
+  "name": "sw1",
+  "crs": { "type": "name", "properties": { "name": "urn:ogc:def:crs:OGC:1.3:CRS84" } },
+  "features": [{ "type": "Feature", "properties": {}, "geometry": { "type": "Point", "coordinates": [108.291252, 34.21246] } }]
+}

+ 6 - 0
public/geojson/sw2.geojson

@@ -0,0 +1,6 @@
+{
+  "type": "FeatureCollection",
+  "name": "sw1",
+  "crs": { "type": "name", "properties": { "name": "urn:ogc:def:crs:OGC:1.3:CRS84" } },
+  "features": [{ "type": "Feature", "properties": {}, "geometry": { "type": "Point", "coordinates": [108.350786, 34.198267] } }]
+}

+ 1 - 0
public/index.html

@@ -9,6 +9,7 @@
     <script>
       window.basePathUrl = '<%= BASE_URL %>' //标识config、widgets所在的目录
     </script>
+    <script defer="defer" src="<%= BASE_URL %>EasyPlayer-lib.min.js"></script>
   </head>
   <body>
     <noscript>

+ 37 - 0
src/api/cameraApi.js

@@ -0,0 +1,37 @@
+import request from '@/utils/request'
+
+// 通过区域获取摄像机列表
+export function getTreeDeviceList(data) {
+  return request({
+    url: '/camera/getTreeDeviceList',
+    method: 'post',
+    data: data
+  })
+}
+
+//查询摄像头列表
+export function getCameraDeviceList(data) {
+  return request({
+    url: '/camera/getCameraDeviceList',
+    method: 'post',
+    data: data
+  })
+}
+
+//摄像机数量
+export function getCameraInfo(params) {
+  return request({
+    url: '/index/getCameraInfo',
+    method: 'get',
+    params: params
+  })
+}
+
+//获取摄像头直播地址
+export function queryCameraVideo(data) {
+  return request({
+    url: '/camera/cameraPreview',
+    method: 'post',
+    data: data
+  })
+}

BIN=BIN
src/assets/image/common/close.png


BIN=BIN
src/assets/image/common/popup_title_bg.png


BIN=BIN
src/assets/image/common/摄像头.png


+ 3 - 2
src/components/base-panel/base-panel-left.vue

@@ -20,6 +20,7 @@ export default {
     toggleLeftPanel() {
       console.log('切换左侧面板')
       this.isHide = !this.isHide
+      this.$globalEventBus.$emit('toggleLeftPanel', this.isHide)
     }
   }
 }
@@ -35,7 +36,7 @@ export default {
     width: 4.62rem;
     padding-top: 0.82rem;
     padding-left: 0.12rem;
-    transition: all 0.3s;
+    transition: all 0.3s ease-in-out;
     pointer-events: auto;
     z-index: 40;
 
@@ -64,7 +65,7 @@ export default {
     }
     .left-background {
       position: absolute;
-      transition: all 0.3s;
+      transition: all 0.3s ease-in-out;
       pointer-events: none;
       width: 6.5rem;
       height: 100%;

+ 3 - 2
src/components/base-panel/base-panel-right.vue

@@ -20,6 +20,7 @@ export default {
     toggleRightPanel() {
       console.log('切换右侧面板')
       this.isHide = !this.isHide
+      this.$globalEventBus.$emit('toggleRightPanel', this.isHide)
     }
   }
 }
@@ -35,7 +36,7 @@ export default {
     width: 3.94rem;
     padding-top: 0.82rem;
     padding-right: 0.18rem;
-    transition: all 0.3s;
+    transition: all 0.3s ease-in-out;
     pointer-events: auto;
     z-index: 40;
     .rightbtn {
@@ -63,7 +64,7 @@ export default {
     }
     .right-background {
       position: absolute;
-      transition: all 0.3s;
+      transition: all 0.3s ease-in-out;
       pointer-events: none;
       width: 6.5rem;
       height: 100%;

+ 2 - 0
src/main.js

@@ -9,6 +9,7 @@ import sdk from '@ct/iframe-connect-sdk'
 import router from './router'
 import store from './store'
 
+import EasyPlayer from '@/utils/EasyPlayer-component.min.js'
 import { pxToRemMixin } from './pxToRem.js'
 
 Vue.mixin(pxToRemMixin)
@@ -20,6 +21,7 @@ Vue.prototype.$requestSDK = requestSDK
 Vue.prototype.$postMsgUtil = postMsgUtil
 
 Vue.use(Element, { size: 'small' })
+Vue.component('EasyPlayer', EasyPlayer)
 Vue.prototype.isCross = true
 
 localStorage.setItem('isCross', true)

A diferenza do arquivo foi suprimida porque é demasiado grande
+ 34372 - 0
src/utils/EasyPlayer-component.min.js


+ 1 - 0
src/utils/index.js

@@ -0,0 +1 @@
+export * from './message'

+ 22 - 0
src/utils/message.js

@@ -0,0 +1,22 @@
+import { Notification, MessageBox } from 'element-ui'
+import { throttle } from 'lodash-es'
+
+const baseOption = {
+  duration: 2000,
+  showClose: true
+}
+
+export const success = (message = '操作成功!', title = '信息') => Notification.success({ ...baseOption, title, message })
+
+export const error = throttle((message = '操作失败!', title = '信息') => Notification.error({ ...baseOption, title, message }), 1000)
+
+export const warning = (message = '操作失败!', title = '提示') => Notification.warning({ ...baseOption, title, message })
+
+export const info = (message = '信息', title = '信息') => Notification.info({ ...baseOption, title, message })
+
+export const confirm = (message = '确定删除吗?', info = '提示') =>
+  MessageBox.confirm(message, info, {
+    confirmButtonText: '确定',
+    cancelButtonText: '取消',
+    type: 'warning'
+  })

+ 66 - 0
src/utils/request.js

@@ -0,0 +1,66 @@
+import axios from 'axios'
+// import Cookies from 'js-cookie'
+axios.defaults.headers['Content-Type'] = 'application/json;charset=utf-8'
+// 创建axios实例
+const service = axios.create({
+  baseURL: '/hntt-uav',
+  // 超时
+  timeout: 10000
+})
+
+// request拦截器
+service.interceptors.request.use(
+  (config) => {
+    const token =
+      'eyJhbGciOiJIUzUxMiJ9.eyJsb2dpbl91c2VyX2tleSI6ImExNDZhYTM0LTBkNTYtNDU5My1iOWVkLTdiMDkxZDYwYWM5ZSJ9.SY61O-6bGj8NsPEgKDhIeUO4ywEZFHoUKbbwL-NK68PckviJhIXuKKdD5laNPhCRvDJT5h2aC614SL7GEqOfWw'
+    // config.headers['Authorization'] = 'Bearer ' + Cookies.get('Admin-Token')
+    config.headers['Authorization'] = 'Bearer ' + token
+    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
+    // 二进制数据则直接返回
+    if (res.request.responseType === 'blob' || res.request.responseType === 'arraybuffer') {
+      return res.data
+    }
+    if (code === 401) {
+      return Promise.reject('无效的会话,或者会话已过期,请重新登录。')
+    } else if (code === 500) {
+      // ElMessage({ message: msg, type: 'error' })
+      return Promise.reject(new Error(msg))
+    } else if (code === 601) {
+      // ElMessage({ message: msg, type: 'warning' })
+      return Promise.reject(new Error(msg))
+    } else if (code !== 200) {
+      // ElNotification.error({ title: msg })
+      return Promise.reject('error')
+    } else {
+      return Promise.resolve(res.data)
+    }
+  },
+  (error) => {
+    console.log('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

BIN=BIN
src/views/components/layerList/image/水文站.png


+ 70 - 3
src/views/components/layerList/index.vue

@@ -1,5 +1,5 @@
 <template>
-  <div class="layer-list-panel">
+  <div class="layer-list-panel" :class="{ 'fix-layer-list-panel': leftPanelHide }">
     <div class="layer-list-panel-title">
       <div class="title">
         <img src="@/assets/image/common/layerIcon.png" alt="" />
@@ -12,7 +12,7 @@
     </div>
 
     <div class="layer-list-panel-content" v-show="isExpanded">
-      <el-tree ref="tree" :data="treeData" show-checkbox node-key="id" :props="defaultProps" @check-change="onCheckChange" :default-expanded-keys="['1']">
+      <el-tree ref="treeRef" :data="treeData" show-checkbox node-key="id" :props="defaultProps" @check-change="onCheckChange" :default-expanded-keys="['1']">
         <template #default="{ node, data }">
           <span class="custom-tree-node">
             <span>{{ node.label }}</span>
@@ -27,11 +27,18 @@
 
 <script>
 import * as mars3d from 'mars3d'
+import { mapState } from 'vuex'
 let layerCache = {}
 export default {
   name: 'LayerListView',
+  mounted() {
+    this.$globalEventBus.$on('toggleLeftPanel', (val) => {
+      this.leftPanelHide = val
+    })
+  },
   data() {
     return {
+      leftPanelHide: false,
       isExpanded: true,
       defaultProps: { children: 'children', label: 'label' },
       treeData: [
@@ -79,6 +86,22 @@ export default {
       ]
     }
   },
+  computed: {
+    ...mapState({
+      mainMenu: (state) => state.home.mainMenu
+    })
+  },
+  watch: {
+    mainMenu: {
+      handler(val) {
+        if (val === '水文信息') {
+          this.handleData(true)
+        } else {
+          this.handleData(false)
+        }
+      }
+    }
+  },
   methods: {
     onExpandChange() {
       this.isExpanded = !this.isExpanded
@@ -100,6 +123,7 @@ export default {
         this.$set(node, 'error', '')
         try {
           const layer = new mars3d.layer.GeoJsonLayer({
+            id: node.id,
             name: node.label,
             url: node.meta.url,
             crs: 'EPSG:4326',
@@ -107,9 +131,10 @@ export default {
             symbol: this.getStyleByName(node.label)
           })
           window.map.addLayer(layer)
+          this.bindEvent(layer)
           layerCache[id] = layer
           // 父子关联:勾选子节点时,父节点作为 groupId
-          const parent = this.$refs.tree.getNode(id).parent
+          const parent = this.$refs.treeRef.getNode(id).parent
           if (parent && parent.data.id) {
             layer.options.parentId = parent.data.id
           }
@@ -136,6 +161,40 @@ export default {
         }
       } else if (name === '一二级管控区界限') {
         return { type: 'polyline', styleOptions: { color: '#db7f06', width: 4 } }
+      } else if (name.indexOf('水文') > -1) {
+        return {
+          type: 'billboard',
+          styleOptions: {
+            image: require('./image/水文站.png'),
+            scale: 1,
+            clampToGround: true,
+            horizontalOrigin: this.Cesium.HorizontalOrigin.CENTER,
+            verticalOrigin: this.Cesium.VerticalOrigin.BOTTOM,
+            pixelOffset: new this.Cesium.Cartesian2(0, -6), // 偏移量
+            distanceDisplayCondition: new this.Cesium.DistanceDisplayCondition(0.0, 500000) // 按视距显示
+          }
+        }
+      }
+    },
+    // 处理水文站数据
+    handleData(isShow) {
+      const nodeIds = ['2-1', '2-2']
+      this.$nextTick(() => {
+        nodeIds.forEach((id) => {
+          this.$refs.treeRef.setChecked(id, isShow)
+          const node = this.$refs.treeRef.getNode(id)
+          this.onCheckChange(node.data, isShow)
+        })
+      })
+    },
+
+    // 绑定点击事件
+    bindEvent(layer) {
+      const _that = this
+      if (layer.id === '2-1' || layer.id === '2-2') {
+        layer.on(mars3d.EventType.click, function (event) {
+          _that.$globalEventBus.$emit('clickWaterStation', event)
+        })
       }
     }
   }
@@ -153,6 +212,7 @@ export default {
   border-radius: 6px;
   opacity: 0.85;
   z-index: 1000;
+  transition: left 0.3s ease-in-out;
   .layer-list-panel-title {
     margin-bottom: px-to-rem(10);
     height: px-to-rem(30);
@@ -189,6 +249,13 @@ export default {
         white-space: wrap;
       }
     }
+    .custom-tree-node {
+      white-space: wrap;
+      font-size: px-to-rem(16);
+    }
   }
 }
+.fix-layer-list-panel {
+  left: px-to-rem(20);
+}
 </style>

+ 26 - 25
src/views/components/map/index.vue

@@ -3,58 +3,59 @@
 </template>
 
 <script>
-import Vue from "vue";
-import "mars3d/mars3d.css";
-import * as mars3d from "mars3d";
+import Vue from 'vue'
+import 'mars3d/mars3d.css'
+import * as mars3d from 'mars3d'
+import { warning } from '@/utils'
 // 为了方便使用,绑定到原型链,在其他vue文件,直接this.mars3d 来使用
-Vue.prototype.mars3d = mars3d;
-Vue.prototype.Cesium = mars3d.Cesium;
+Vue.prototype.mars3d = mars3d
+Vue.prototype.Cesium = mars3d.Cesium
 
 export default {
-  name: "MapViewer",
+  name: 'MapViewer',
   props: {
     // 地图唯一性标识
-    mapKey: { type: String, default: "" },
+    mapKey: { type: String, default: '' },
     // 初始化配置config.json的地址
     url: String,
     // 自定义参数
-    options: Object,
+    options: Object
   },
 
   mounted() {
     mars3d.Util.fetchJson({ url: this.url }).then((data) => {
-      this.initMars3d({ ...data.map3d, ...this.options }); // 构建地图
-    });
+      this.initMars3d({ ...data.map3d, ...this.options }) // 构建地图
+    })
   },
 
   beforeDestroy() {
-    const map = this[`_map${this.mapKey}`];
+    const map = this[`_map${this.mapKey}`]
     if (map) {
-      map.destroy();
-      delete this[`_map${this.mapKey}`];
+      map.destroy()
+      delete this[`_map${this.mapKey}`]
     }
-    console.log(">>>>> 地图卸载完成 >>>>");
+    console.log('>>>>> 地图卸载完成 >>>>')
   },
 
   methods: {
     initMars3d(mapOptions) {
       if (this[`_map${this.mapKey}`]) {
-        this[`_map${this.mapKey}`].destroy();
+        this[`_map${this.mapKey}`].destroy()
       }
       // 创建三维地球场景
-      const map = new mars3d.Map(`mapView_${this.mapKey}`, mapOptions);
-      window.map = map;
-      console.log(">>>>> 地图创建成功 >>>>", map);
+      const map = new mars3d.Map(`mapView_${this.mapKey}`, mapOptions)
+      window.map = map
+      console.log('>>>>> 地图创建成功 >>>>', map)
       // webgl渲染失败后,刷新页面
       map.on(mars3d.EventType.renderError, async () => {
-        warning("程序内存消耗过大,请重启浏览器");
-        window.location.reload();
-      });
+        warning('程序内存消耗过大,请重启浏览器')
+        window.location.reload()
+      })
       // 抛出事件
-      this.$emit("onload", map);
-    },
-  },
-};
+      this.$emit('onload', map)
+    }
+  }
+}
 </script>
 
 <style lang="scss">

+ 2 - 1
src/views/components/menu/index.vue

@@ -31,7 +31,8 @@ export default {
       timeVal: '',
       leftMenuList: [
         { id: 'left_1', name: '综合概览', active: true },
-        { id: 'left_2', name: '水文信息', active: false }
+        { id: 'left_2', name: '水文信息', active: false },
+        // { id: 'left_3', name: '视频管理', active: false },
       ],
       rightMenuList: [
         { id: 'right_1', name: '智能预警', active: false },

+ 15 - 1
src/views/index.vue

@@ -14,6 +14,8 @@
           <SafetyInspectionRight v-if="mainMenu === '安全巡查'"></SafetyInspectionRight>
           <ComprehensiveOverview v-if="mainMenu === '综合概览'"></ComprehensiveOverview>
           <HydrologicInfo v-if="mainMenu === '水文信息'"></HydrologicInfo>
+
+          <WaterStationPopup />
         </div>
       </el-main>
     </el-container>
@@ -31,10 +33,22 @@ import SafetyInspectionLeft from './safety-inspection/left.vue'
 import SafetyInspectionRight from './safety-inspection/right.vue'
 import ComprehensiveOverview from '@/views/comprehensive-overview/index'
 import HydrologicInfo from '@/views/hydrologic-info/index'
+import WaterStationPopup from '@/views/water-station-popup'
 const basePathUrl = window.basePathUrl || ''
 export default {
   name: 'MainView',
-  components: { MainMap, LayerListPanel, menuPanel, SandMonitorLeft, SandMonitorRight,SafetyInspectionLeft, ComprehensiveOverview,SafetyInspectionRight,HydrologicInfo },
+  components: {
+    MainMap,
+    LayerListPanel,
+    menuPanel,
+    SandMonitorLeft,
+    SandMonitorRight,
+    SafetyInspectionLeft,
+    ComprehensiveOverview,
+    SafetyInspectionRight,
+    HydrologicInfo,
+    WaterStationPopup
+  },
   computed: {
     ...mapState({
       mainMenu: (state) => state.home.mainMenu

+ 121 - 0
src/views/video-manage/index.vue

@@ -0,0 +1,121 @@
+<template>
+  <div class=""></div>
+</template>
+
+<script>
+export default {
+  name: 'ViewManage'
+}
+</script>
+
+<style scoped lang="scss"></style>
+
+<!-- <template>
+  <div style="width: 100%; height: 100%" ref="remoteLayout">
+    <RemoteComponentsLoader v-if="components['common-comp-video']" :config="components['common-comp-video']" />
+  </div>
+</template>
+<script>
+import RemoteComponentsLoader from '@ct/remote-page-loader/remote-page-loader.umd.js'
+import { loadSpaceShardAsync } from '@ct/remote-page-loader/utils/remote-loader'
+// 远程组件配置
+const remoteComp = { 'common-comp-video': { uniqueId: '2024060188028', version: '1.7.10' } }
+
+export default {
+  name: 'ViewManage',
+  components: { RemoteComponentsLoader },
+  data() {
+    return {
+      loaded: false,
+      components: {}
+    }
+  },
+  async mounted() {
+    const { data } = await this.getFisUrl()
+    const { configValue } = data || {}
+    // console.log('当前配置远程域名为:', configValue)
+    await this.getRemoteComps(configValue)
+  },
+  beforeDestroy() {
+    const htmlElement = document.documentElement
+    htmlElement.style.fontSize = `calc((100vh / 1080) * 100 * (1080 / 1028))`
+  },
+  methods: {
+    // 远程组件加载完成回调
+    remoteFulfilled(payload) {
+      this.$nextTick(() => {
+        this.registerMiddleWare.push(payload.payload.name)
+      })
+    },
+    getFisUrl() {
+      return window.requestSDK('/admin/system/config/base/detail/businessComponentFilsUrl', {}, {}, 'get')
+    },
+    async getRemoteComps(configValue) {
+      const param = {
+        components: Object.values(remoteComp)
+      }
+      console.log('Minami', remoteComp, param)
+      window
+        .requestSDK(configValue || '/component-gallery/api/business/files', param, {}, 'post')
+        .then(async (resp) => {
+          const { code, data } = resp
+          if (code && code === 200) {
+            const baseCompObj = {
+              name: 'RemoteComponentsLoader',
+              config: {
+                name: '',
+                description: '',
+                js: '',
+                css: ''
+              }
+            }
+            const keys = Object.keys(data)
+            const remotes = keys.map((key) => {
+              const { js, css } = data[key]
+              return {
+                ...baseCompObj,
+                config: {
+                  name: key,
+                  css,
+                  js
+                }
+              }
+            })
+            const resp = await loadSpaceShardAsync({ data: remotes })
+            this.$nextTick(() => {
+              const obj = {}
+              resp.forEach((o) => {
+                obj[o.config.name] = o.config
+              })
+              this.components = obj
+              this.loaded = true
+            })
+            return
+          }
+
+          this.$notify.error({
+            title: `获取远程组件失败`,
+            message: `获取远程组件失败`
+          })
+        })
+        .catch((err) => {
+          const { title, message } = err
+          this.$notify.error({
+            title: `${title}`,
+            message: `${message}`
+          })
+        })
+        .finally(() => {
+          false
+        })
+    }
+  }
+}
+</script>
+<style lang="scss" scoped>
+@import '@/assets/scss/px-to-rem';
+
+.container {
+  height: 100%;
+}
+</style> -->

+ 245 - 0
src/views/water-station-popup/index.vue

@@ -0,0 +1,245 @@
+<template>
+  <div class="water-station-popup-container" v-if="visible">
+    <div class="water-station-popup-title">
+      <span class="title-text">水文监测点</span>
+      <img src="@/assets/image/common/close.png" style="cursor: pointer" alt="" @click="visible = false" />
+    </div>
+    <div class="water-station-popup-content">
+      <el-radio-group v-model="typeVal" size="mini">
+        <el-radio-button label="1">实时监测信息</el-radio-button>
+        <el-radio-button label="2">周边监控查询</el-radio-button>
+      </el-radio-group>
+      <div class="monitoring-info-container" v-if="typeVal === '1'">
+        <div class="info-panel">
+          <el-row :gutter="10">
+            <el-col :span="8" class="label-col">
+              <div class="label">实时高程:</div>
+              <div class="value"><span>300</span><span>m</span></div>
+            </el-col>
+            <el-col :span="8" class="label-col">
+              <div class="label">1小时雨量:</div>
+              <div class="value"><span>300</span><span>mm</span></div>
+            </el-col>
+            <el-col :span="8" class="label-col">
+              <div class="label">实时流量:</div>
+              <div class="value"><span>300</span><span>m³/s</span></div>
+            </el-col>
+          </el-row>
+          <el-row :gutter="10">
+            <el-col :span="8" class="label-col">
+              <div class="label">昨日最高:</div>
+              <div class="value"><span>0</span><span>m</span></div>
+            </el-col>
+            <el-col :span="8" class="label-col">
+              <div class="label">当天雨量:</div>
+              <div class="value"><span>300</span><span>mm</span></div>
+            </el-col>
+            <el-col :span="8" class="label-col">
+              <div class="label">实时流速:</div>
+              <div class="value"><span>0</span><span>m³/s</span></div>
+            </el-col>
+          </el-row>
+        </div>
+        <div class="info-chart">
+          <LineChart style="height: 2.05rem" />
+        </div>
+      </div>
+      <div class="monitor-container" v-else>
+        <div class="monitor-search">
+          <span>查询半径</span>
+          <div class="input-bg">
+            <el-input v-model="radius" size="mini" placeholder="输入半径"></el-input>
+          </div>
+          <span>&nbsp;km&nbsp;</span>
+          <el-button type="primary" size="mini" @click="search">查询</el-button>
+        </div>
+        <div class="monitor-list">
+          <ul>
+            <li v-for="(item, index) in monitorList" :key="index">
+              <span>新建18米-点位32332432{{ item }}</span>
+            </li>
+          </ul>
+        </div>
+        <div class="monitor-video">
+          <!-- <EasyPlayer
+            :ref="item.videoId"
+            :playerName="item.sources[0].camera.name"
+            :videoUrl="item.sources[0].src"
+            class="video-player screenshot"
+            live
+            speed
+            :easyStretch="easyStretch"
+            :has-audio="!item.sources[0].src.includes('.flv')"
+            :reconnection="true"
+            @click.native.stop
+          /> -->
+        </div>
+      </div>
+    </div>
+  </div>
+</template>
+
+<script>
+import LineChart from './line-chart'
+// import { getCameraDeviceList } from '@/api/cameraApi'
+import { warning } from '@/utils'
+let graphicsLayer = null
+export default {
+  name: 'WaterStationPopup',
+  components: { LineChart },
+  data() {
+    return {
+      visible: false,
+      typeVal: '1',
+      radius: '',
+      monitorList: [1, 2, 3, 4, 5, 6, 7, 9, 66, 67, 67, 76],
+      stationGraphic: {}
+    }
+  },
+  mounted() {
+    this.$globalEventBus.$on('clickWaterStation', (data) => {
+      this.visible = true
+      this.stationGraphic = data.graphic
+    })
+  },
+  destroyed() {
+    this.$globalEventBus.$off('clickWaterStation')
+  },
+  methods: {
+    search() {
+      if (this.radius === '') {
+        warning('请输入查询半径')
+        return
+      }
+      this.addCameraToMap()
+      // const params = { longitude: 113.07634141539151, latitude: 28.18518024316094, visualRadius: this.radius * 1000 }
+      // getCameraDeviceList(params).then((res) => {
+      //   console.log(res)
+      // })
+    },
+    addCameraToMap() {
+      if (graphicsLayer) {
+        window.map.removeLayer(graphicsLayer)
+      }
+      graphicsLayer = new this.mars3d.layer.GraphicLayer()
+      window.map.addLayer(graphicsLayer)
+      const graphic = new this.mars3d.graphic.CircleEntity({
+        position: this.stationGraphic.coordinate,
+        style: {
+          radius: this.radius * 1000,
+          color: '#498EE3',
+          opacity: 0.2,
+          outline: true,
+          outlineWidth: 1,
+          outlineColor: '#4D7BFF',
+          clampToGround: true
+        }
+      })
+      graphicsLayer.addGraphic(graphic)
+    }
+  }
+}
+</script>
+
+<style scoped lang="scss">
+.water-station-popup-container {
+  position: absolute;
+  top: 50%;
+  left: 50%;
+  transform: translate(-50%, -80%);
+  width: px-to-rem(600);
+  z-index: 9999;
+  .water-station-popup-title {
+    background: url('@/assets/image/common/popup_title_bg.png') no-repeat;
+    background-size: 100% 100%;
+    height: px-to-rem(39);
+    display: flex;
+    justify-content: space-between;
+    align-items: center;
+    padding: 0 px-to-rem(20);
+    font-size: px-to-rem(16);
+    color: #fff;
+    .title-text {
+      font-weight: bold;
+      margin-left: px-to-rem(20);
+    }
+  }
+  .water-station-popup-content {
+    height: px-to-rem(354);
+    background: rgba(#233d6c, 0.8);
+    padding: px-to-rem(10);
+    :deep(.el-radio-button__inner) {
+      background: transparent;
+      color: #4f9fff;
+      border-color: #4f9fff;
+      font-size: px-to-rem(14);
+    }
+    :deep(.el-radio-button__orig-radio:checked + .el-radio-button__inner) {
+      background-color: #4f9fff;
+      color: #fff;
+    }
+    .monitoring-info-container {
+      .info-panel {
+        color: #fff;
+        margin: px-to-rem(10);
+        .label-col {
+          display: flex;
+          margin-bottom: px-to-rem(10);
+          font-size: px-to-rem(16);
+          .value {
+            & > span:nth-child(1) {
+              color: #488de1;
+              margin-right: px-to-rem(6);
+            }
+          }
+        }
+      }
+    }
+    .monitor-container {
+      .monitor-search {
+        display: flex;
+        align-items: center;
+        margin: px-to-rem(10);
+        border-bottom: px-to-rem(1) solid #4d628b;
+        .input-bg {
+          margin: px-to-rem(10) 0;
+          background: url('@/assets/image/safety-inspection/search-bg.png') no-repeat 0 0 / 100% 100%;
+          :deep(.el-input__inner) {
+            background: transparent;
+            color: #fff;
+            border: none;
+          }
+        }
+        span {
+          color: #fff;
+          margin-right: px-to-rem(10);
+        }
+      }
+      .monitor-list {
+        display: flex;
+        justify-content: space-between;
+        ul {
+          width: px-to-rem(180);
+          height: px-to-rem(230);
+          overflow-y: auto;
+          border-right: px-to-rem(1) solid #4d628b;
+          li {
+            color: #fff;
+            padding: px-to-rem(10);
+            height: px-to-rem(36);
+            line-height: px-to-rem(16);
+            font-size: px-to-rem(16);
+            white-space: nowrap;
+            overflow: hidden;
+            text-overflow: ellipsis;
+            cursor: pointer;
+            &:hover {
+              background-color: #498ee3;
+            }
+          }
+        }
+      }
+    }
+  }
+}
+</style>

+ 68 - 0
src/views/water-station-popup/line-chart/index.vue

@@ -0,0 +1,68 @@
+<template>
+  <div ref="chart" class="line-chart-container"></div>
+</template>
+
+<script>
+import { echartMixin } from '@/mixins'
+import * as echarts from 'echarts'
+export default {
+  name: 'LineChart',
+  mixins: [echartMixin],
+  data() {
+    return {
+      seriesData: []
+    }
+  },
+  methods: {
+    initChart() {
+      const option = {
+        backgroundColor: 'transparent',
+        color: ['#66DC95'],
+        grid: {
+          left: 16,
+          right: 16,
+          top: 30,
+          bottom: 0,
+          containLabel: true
+        },
+        textStyle: { color: '#fff' },
+        tooltip: {
+          trigger: 'axis',
+          axisPointer: { type: 'none' },
+          textStyle: { color: '#fff' },
+          extraCssText: 'background:#2F5481; border-radius: 8px;border:none ',
+          formatter: (params) => {
+            return `${params[0].axisValue} 高程${params[0].data}m`
+          }
+        },
+        xAxis: [{ type: 'category', boundaryGap: false, data: ['08/20', '08/21', '08/22', '08/23', '08/24', '08/25'] }],
+        yAxis: [{ type: 'value', name: '高程(m)', splitLine: { show: false }, min: 380, max: 420 }],
+        series: [
+          {
+            name: '高程(m)',
+            type: 'line',
+            smooth: true,
+            symbol: 'emptyCircle',
+            symbolSize: 6,
+            lineStyle: { width: 2 },
+            emphasis: { scale: 1.5 },
+            areaStyle: {
+              opacity: 0.8,
+              color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
+                { offset: 1, color: 'rgba(102,220,149,0)' },
+                { offset: 0, color: ' rgba(102,220,149,0.25)' }
+              ])
+            },
+            data: [401, 398, 391, 410, 395, 400, 396]
+          }
+        ]
+      }
+      this.chartObj.setOption(option)
+    }
+  }
+}
+</script>
+<style lang="scss">
+.line-chart-container {
+}
+</style>

+ 42 - 29
vue.config.js

@@ -1,52 +1,65 @@
-const { defineConfig } = require("@vue/cli-service");
-const path = require("path");
-const webpack = require("webpack");
-const CopyWebpackPlugin = require("copy-webpack-plugin");
-const NodePolyfillPlugin = require("node-polyfill-webpack-plugin");
+const { defineConfig } = require('@vue/cli-service')
+const path = require('path')
+const webpack = require('webpack')
+const CopyWebpackPlugin = require('copy-webpack-plugin')
+const NodePolyfillPlugin = require('node-polyfill-webpack-plugin')
 
 module.exports = defineConfig({
   transpileDependencies: true,
-  publicPath: "/",
-  assetsDir: "static",
-  outputDir: "dist",
+  publicPath: '/',
+  assetsDir: 'static',
+  outputDir: 'dist',
   lintOnSave: false, // 是否开启eslint
   productionSourceMap: false, // 生产环境是否生成sourceMap
   filenameHashing: true, // 文件名哈希
   configureWebpack: (config) => {
-    if (process.env.VUE_APP_MARS3D_SOURCE === "module") {
-      const cesiumSourcePath = "node_modules/mars3d-cesium/Build/Cesium/"; // cesium库安装目录
-      const cesiumRunPath = "./mars3d-cesium/"; // cesium运行时路径
+    if (process.env.VUE_APP_MARS3D_SOURCE === 'module') {
+      const cesiumSourcePath = 'node_modules/mars3d-cesium/Build/Cesium/' // cesium库安装目录
+      const cesiumRunPath = './mars3d-cesium/' // cesium运行时路径
       let plugins = [
         // 标识cesium资源所在的主目录,cesium内部资源加载、多线程等处理时需要用到
         new webpack.DefinePlugin({
-          CESIUM_BASE_URL: JSON.stringify(path.join(config.output.publicPath, cesiumRunPath)),
+          CESIUM_BASE_URL: JSON.stringify(path.join(config.output.publicPath, cesiumRunPath))
         }),
         // Cesium相关资源目录需要拷贝到系统目录下面(部分CopyWebpackPlugin版本的语法可能没有patterns)
         new CopyWebpackPlugin({
           patterns: [
-            { from: path.join(cesiumSourcePath, "Workers"), to: path.join(config.output.path, cesiumRunPath, "Workers") },
-            { from: path.join(cesiumSourcePath, "Assets"), to: path.join(config.output.path, cesiumRunPath, "Assets") },
-            { from: path.join(cesiumSourcePath, "ThirdParty"), to: path.join(config.output.path, cesiumRunPath, "ThirdParty") },
-            { from: path.join(cesiumSourcePath, "Widgets"), to: path.join(config.output.path, cesiumRunPath, "Widgets") },
-          ],
+            { from: path.join(cesiumSourcePath, 'Workers'), to: path.join(config.output.path, cesiumRunPath, 'Workers') },
+            { from: path.join(cesiumSourcePath, 'Assets'), to: path.join(config.output.path, cesiumRunPath, 'Assets') },
+            { from: path.join(cesiumSourcePath, 'ThirdParty'), to: path.join(config.output.path, cesiumRunPath, 'ThirdParty') },
+            { from: path.join(cesiumSourcePath, 'Widgets'), to: path.join(config.output.path, cesiumRunPath, 'Widgets') }
+          ]
         }),
-        new NodePolyfillPlugin(),
-      ];
+        new NodePolyfillPlugin()
+      ]
       return {
         module: { unknownContextCritical: false }, // 配置加载的模块类型,cesium时必须配置
-        plugins: plugins,
-      };
+        plugins: plugins
+      }
     } else {
       return {
-        externals: { "mars3d-cesium": "Cesium" }, //排除使用 mars3d-cesium
-      };
+        externals: { 'mars3d-cesium': 'Cesium' } //排除使用 mars3d-cesium
+      }
     }
   },
   devServer: {
     client: { overlay: false },
-    host: "0.0.0.0", // 也可以直接写IP地址这样方便真机测试
+    host: '0.0.0.0', // 也可以直接写IP地址这样方便真机测试
     port: 3000, // 端口号
     open: false, // 配置自动启动浏览器
+    proxy: {
+      '/system-biz': {
+        target: 'http://10.157.225.55:28085',
+        logLevel: 'debug',
+        changeOrigin: true,
+        rewrite: (p) => p.replace(/^\/system-biz/, '')
+      },
+      '/hntt-uav': {
+        //测试环境
+        target: 'http://10.157.225.55:28083',
+        changOrigin: true
+      }
+    }
   },
   css: {
     // 启用 CSS modules
@@ -58,8 +71,8 @@ module.exports = defineConfig({
     // css预设器配置项
     loaderOptions: {
       sass: {
-        additionalData: '@import "mars3d-cesium/Build/Cesium/Widgets/widgets.css"; @import "@/assets/scss/index.scss";',
-      },
-    },
-  },
-});
+        additionalData: '@import "mars3d-cesium/Build/Cesium/Widgets/widgets.css"; @import "@/assets/scss/index.scss";'
+      }
+    }
+  }
+})