CzRger před 4 dny
rodič
revize
d5c3ceec27

+ 51 - 11
src/stores/ship-map/ship-map.ts

@@ -1,5 +1,5 @@
 import {defineStore} from "pinia";
-import {ElMessage} from "element-plus";
+import {ElMessage, ElLoading} from "element-plus";
 import {computed, reactive, toRefs, watch} from "vue";
 import * as format from "ol/format";
 import * as layer from "ol/layer";
@@ -10,6 +10,7 @@ import {formatGetParam, randomColor, YMDHms} from "@/utils/util";
 import {formatPosition, getShapeView} from "@/utils/easyMap";
 import { v4 } from "uuid";
 import {shipArchiveDetail} from "@/api/modules/web/archive";
+import axios from "axios";
 
 export const useShipMapStore = defineStore('shipMap', () => {
   const state: any = reactive({
@@ -113,6 +114,9 @@ export const useShipMapStore = defineStore('shipMap', () => {
     let feature = state.map.forEachFeatureAtPixel(pixel, function (feature) {
       return feature
     })
+    clickShip(feature)
+  }
+  const clickShip = (feature) => {
     if (feature && feature.get('_featureType') == 'ship') {
       if (!state.trackMap.has(feature.get('_id'))) {
         const d = feature.get('_data')
@@ -209,7 +213,7 @@ export const useShipMapStore = defineStore('shipMap', () => {
         })
         if (d[state.trackKeys.shipId]) {
           shipArchiveDetail(formatGetParam({id: d[state.trackKeys.shipId]})).then(res => {
-          // shipArchiveDetail(formatGetParam({id: '01d64e98bd904d2880ecd0e74d35383c'})).then(res => {
+            // shipArchiveDetail(formatGetParam({id: '01d64e98bd904d2880ecd0e74d35383c'})).then(res => {
             if (res.code == 0) {
               state.trackMap.get(feature.get('_id')).archiveParams.data = res.data
             } else {
@@ -372,15 +376,7 @@ export const useShipMapStore = defineStore('shipMap', () => {
     //  动态拼接数据的唯一标识DATA,不可修改
     const features = data.map(v => {
       try {
-        const feat: any = new format.WKT().readFeature(`POINT(${v[state.trackKeys.lon]} ${v[state.trackKeys.lat]})`)
-        feat.set('_course', v[state.trackKeys.course] || 0)
-        feat.set('_speed', v[state.trackKeys.speed] || 0)
-        // feat.set('_head', v.targetHeading)
-        feat.set('_mergeType', v[state.trackKeys.mergeType])
-        feat.set('_id', v[state.trackKeys.mergeTarget])
-        feat.set('_trackId', v[state.trackKeys.mergeId])
-        feat.set('_data', v)
-        feat.set('_featureType', 'ship')
+        const feat: any = initShipFeature(v)
         // 实时轨迹
         const t = state.trackMap.get(feat.get('_id'))
         if (t && t.trackId !== feat.get('_trackId')) {
@@ -400,6 +396,18 @@ export const useShipMapStore = defineStore('shipMap', () => {
     });
     state.ws.layerShip.setSource(vectorSource)
   }
+  const initShipFeature = (data) => {
+    const feat: any = new format.WKT().readFeature(`POINT(${data[state.trackKeys.lon]} ${data[state.trackKeys.lat]})`)
+    feat.set('_course', data[state.trackKeys.course] || 0)
+    feat.set('_speed', data[state.trackKeys.speed] || 0)
+    // feat.set('_head', v.targetHeading)
+    feat.set('_mergeType', data[state.trackKeys.mergeType])
+    feat.set('_id', data[state.trackKeys.mergeTarget])
+    feat.set('_trackId', data[state.trackKeys.mergeId])
+    feat.set('_data', data)
+    feat.set('_featureType', 'ship')
+    return feat
+  }
   const initWarningWS = () => {
     // const js = {
     //   "createBy" : "admin",
@@ -452,11 +460,43 @@ export const useShipMapStore = defineStore('shipMap', () => {
       }
     }
   }
+
+  const toShip = (mergeTarget) => {
+    const loading = ElLoading.service({
+      lock: true,
+      text: '船舶位置信息查询中……',
+      background: 'rgba(0, 0, 0, 0.7)',
+    })
+    axios.get('/geoserver-api/geoserver/kafka/wms', {
+      params: {
+        service: 'WFS',
+        version: '2.0.0',
+        request: 'GetFeature',
+        typeNames: 'kafka:fusion',
+        outputFormat: 'application/json',
+        CQL_FILTER: `${state.trackKeys.mergeTarget} = '${mergeTarget}'`
+      }
+    }).then(res => {
+      if (res.status === 200) {
+        if (res.data.features.length > 0) {
+          const s = res.data.features[0]
+          clickShip(initShipFeature({...s.properties, targetSource: JSON.parse(s.properties.targetSource)}))
+          getShapeView(state.map, [s.geometry.coordinates])
+        } else {
+          ElMessage.warning('未查询到船舶位置信息,请稍后重试!')
+        }
+      }
+      loading.close()
+    }).catch(e => {
+      loading.close()
+    })
+  }
   watch(() => state.warningOpen, (n) => {
     localStorage.setItem('warningOpen', n)
   }, {immediate: true})
   return {
     ...toRefs(state),
     initMap,
+    toShip
   }
 })

+ 7 - 1
src/views/web/config/index.vue

@@ -77,7 +77,7 @@
           :filter-node-method="areaFilterNode"
         >
           <template #default="{ node, data }">
-            <span class="custom-tree-node">
+            <span class="custom-tree-node" @click="toLocation(data)">
               <span>{{ data.name }}</span>
               <span class="tree-buttons">
                 <div class="tree-button __hover" @click.stop="onEditArea(data)">
@@ -112,6 +112,7 @@ import * as style from "ol/style";
 import {warnModelRuleTree} from "@/api/modules/web/model";
 import modelDetail from './model-detail.vue'
 import {warnRuleDelete, warnRuleStatus} from "@/api/modules/web/rule";
+import {formatPosition, getShapeView} from "@/utils/easyMap";
 
 const {proxy} = getCurrentInstance()
 const emit = defineEmits(['update:show'])
@@ -370,6 +371,11 @@ const ruleTotalCpt = computed(() => {
   })
   return total
 })
+const toLocation = (data) => {
+  try {
+    getShapeView(props.map, formatPosition.wpnTcpn(data.location)[0])
+  } catch (e) {}
+}
 watch(() => state.textReal, (n) => {
   if (state.tab == 1) {
     ref_rulesTree.value?.filter(n)

+ 1 - 1
src/views/web/index.vue

@@ -102,7 +102,7 @@ const state: any = reactive({
     showArchive: false,
     showWarning: false,
     showExample: false,
-    showSearch: true,
+    showSearch: false,
   },
 })
 const ref_web = ref()

+ 261 - 0
src/views/web/search/index.vue

@@ -0,0 +1,261 @@
+<template>
+  <DragWindow
+    v-if="show"
+    @onClose="onClose"
+    title="搜索"
+    v-model:layout="state.layout"
+    close
+    expend
+  >
+    <div class="search-com" v-if="show" v-loading="state.loading">
+      <CusForm
+        labelWidth="60px"
+        class="__cus-form_map"
+        @handleEnter="onSearch()"
+      >
+        <CusFormColumn
+          :span="24"
+          labelWidth="0px"
+          v-model:param="state.text"
+          class="search"
+        >
+          <template #append>
+            <div class="search-btn __hover" @click="onSearch()">搜索</div>
+          </template>
+        </CusFormColumn>
+      </CusForm>
+      <div class="list">
+        <div class="head">
+          <div class="name">船名号</div>
+          <div class="rh">融合批次号</div>
+          <div class="beidou">北斗终端号</div>
+          <div class="mmsi">MMSI</div>
+          <div class="radar">雷达批次号</div>
+          <div class="operation">操作</div>
+        </div>
+        <div class="body">
+          <template v-for="item in tableDataCpt">
+            <div class="row">
+              <div class="name" v-html="strBr(item.properties.targetName || item.properties.targetNameEn)"></div>
+              <div class="rh" v-html="strBr(item.properties.mergeTarget)"></div>
+              <div class="beidou" v-html="strBr(item.properties.beidouId)"></div>
+              <div class="mmsi" v-html="strBr(item.properties.mmsi)"></div>
+              <div class="radar" v-html="strBr(item.properties.radarTargetId)"></div>
+              <div class="operation">
+                <SvgIcon name="focus" color="#ffffff" class="__hover" @click="ShipMapStore.toShip(item.properties[ShipMapStore.trackKeys.mergeTarget])"/>
+              </div>
+            </div>
+          </template>
+        </div>
+        <div class="list-page __cus-page_map">
+          <CusPage
+            :page-num="state.page.pageNum"
+            :page-size="state.page.pageSize"
+            :page-sizes="[10, 20, 30]"
+            :total="state.list.length"
+            @page="onPage"
+          />
+        </div>
+      </div>
+    </div>
+  </DragWindow>
+</template>
+
+<script setup lang="ts">
+import {computed, onMounted, reactive, watch} from "vue";
+import DragWindow from '../components/drag-window.vue'
+import axios from "axios";
+import {ElMessage} from "element-plus";
+import {useShipMapStore} from "@/stores";
+
+const emit = defineEmits(['update:show'])
+const ShipMapStore = useShipMapStore()
+
+const props = defineProps({
+  show: {},
+})
+const state: any = reactive({
+  layout: {
+    width: 800,
+    left: 85,
+    top: 110
+  },
+  loading: false,
+  text: '',
+  textReal: '',
+  list: [],
+  page: {
+    pageNum: 1,
+    pageSize: 30
+  },
+})
+const onSearch = () => {
+  if (!state.text) {
+    ElMessage.warning('请输入船名号/融合批次号/北斗终端号/MMSI号/雷达批次号!')
+    return
+  }
+  state.list = []
+  state.page.pageNum = 1
+  state.textReal = state.text + ''
+  state.loading = true
+  let cql = ''
+  cql += `(mergeTarget like '%${state.textReal}%')`
+  cql += `or (targetName like '%${state.textReal}%')`
+  cql += `or (targetNameEn like '%${state.textReal}%')`
+  cql += `or (beidouId like '%${state.textReal}%')`
+  cql += `or (mmsi like '%${state.textReal}%')`
+  cql += `or (radarTargetId like '%${state.textReal}%')`
+  axios.get('/geoserver-api/geoserver/kafka/wms', {
+    params: {
+      service: 'WFS',
+      version: '2.0.0',
+      request: 'GetFeature',
+      typeNames: 'kafka:fusion',
+      outputFormat: 'application/json',
+      CQL_FILTER: cql
+    }
+  }).then(res => {
+    if (res.status === 200) {
+      state.list = res.data.features
+    }
+    state.loading = false
+  }).catch(e => {
+    state.loading = false
+  })
+}
+const onClose = () => {
+  emit('update:show', false)
+}
+const onPage = (pageNum, pageSize) => {
+  state.page = {
+    pageNum: pageNum,
+    pageSize: pageSize
+  }
+}
+const tableDataCpt = computed(() => {
+  return state.list.slice((state.page.pageNum - 1) * state.page.pageSize, state.page.pageNum * state.page.pageSize)
+})
+const strBr = (str) => {
+  if (str) {
+    return str.split(',').map(v => v.replace(new RegExp(state.textReal, 'g'), `<span>${state.textReal}</span>`)).join('<br/>')
+  }
+  return ''
+}
+watch(() => props.show, (n) => {
+  if (n) {
+    state.list = []
+  }
+})
+onMounted(() => {
+})
+</script>
+
+<style lang="scss" scoped>
+$mapHeight: var(--easy-map-height);
+.search-com {
+  max-height: calc($mapHeight - 250px);
+  padding: 20px;
+  display: flex;
+  flex-direction: column;
+  overflow: hidden;
+  .__cus-form_map {
+    :deep(.search) {
+      .el-input-group__append {
+        padding: 0;
+        background-color: transparent;
+        box-shadow: none;
+        .search-btn {
+          width: 80px;
+          height: 100%;
+          background: linear-gradient(150deg, #2FBCCD, #2467EB);
+          font-weight: 400;
+          font-size: 16px;
+          color: #FFFFFF;
+          display: flex;
+          align-items: center;
+          justify-content: center;
+          border-radius: 0px 4px 4px 0px;
+        }
+      }
+    }
+  }
+  :deep(.list) {
+    flex: 1;
+    margin-top: 10px;
+    display: flex;
+    flex-direction: column;
+    overflow: hidden;
+    .head, .row {
+      display: flex;
+      align-items: center;
+      >div {
+        height: 100%;
+        display: flex;
+        align-items: center;
+        justify-content: center;
+        border-left: 1px solid rgba(104,195,255,0.1);
+        border-bottom: 1px solid rgba(104,195,255,0.1);
+        &:last-child {
+          border-right: 1px solid rgba(104,195,255,0.1);
+        }
+      }
+      .name {
+        flex: 1;
+      }
+      .rh {
+        width: 180px;
+      }
+      .beidou {
+        width: 100px;
+      }
+      .mmsi {
+        width: 100px;
+      }
+      .radar {
+        width: 200px;
+      }
+      .operation {
+        width: 80px;
+      }
+    }
+    .head {
+      background: #152584;
+      height: 32px;
+      min-height: 32px;
+      >div {
+        font-family: PingFang SC, PingFang SC;
+        font-weight: 400;
+        font-size: 14px;
+        color: #68C3FF;
+      }
+    }
+    .body {
+      flex: 1;
+      overflow-y: auto;
+      display: flex;
+      flex-direction: column;
+      .row {
+        height: 40px;
+        min-height: 40px;
+        >div {
+          font-family: PingFang SC, PingFang SC;
+          font-weight: 400;
+          font-size: 14px;
+          color: #ffffff;
+          >span {
+            color: red;
+          }
+        }
+        .operation {
+          gap: 4px;
+        }
+      }
+    }
+    .list-page {
+      margin-top: 10px;
+      display: flex;
+      justify-content: flex-end;
+    }
+  }
+}
+</style>