|
@@ -0,0 +1,325 @@
|
|
|
+<template>
|
|
|
+ <div class="business">
|
|
|
+ <ShipFilterCom class="ship-filter" :map="map" :mapFunc="mapFunc" @getShipFilter="handleShipFilter"/>
|
|
|
+ <div class="track-list">
|
|
|
+ <div class="track-item track-item-head">
|
|
|
+ <div class="source">目标来源</div>
|
|
|
+ <div class="key">轨迹标识</div>
|
|
|
+ <div class="time">轨迹持续时间</div>
|
|
|
+ <div class="do">操作</div>
|
|
|
+ </div>
|
|
|
+ <div class="track-item-body">
|
|
|
+ <template v-for="([key, item], index) in trackMap">
|
|
|
+ <div class="track-item">
|
|
|
+ <div class="source" :style="`color: ${item.config?.name};`">{{item.config?.name}}</div>
|
|
|
+ <div class="key" :style="`color: ${item.color};background: #ffffff;`">
|
|
|
+ <CusEllipsis :value="key"/>
|
|
|
+ </div>
|
|
|
+ <div class="time">{{item.trackTime}}</div>
|
|
|
+ <div class="do">
|
|
|
+<!-- <el-tooltip :enterable="false" placement="top" content="隐藏" v-if="item.show">-->
|
|
|
+<!-- <img class="__hover" src="@/assets/images/ship-test/ship-track-visible.svg" @click="handleTrackHide(key)"/>-->
|
|
|
+<!-- </el-tooltip>-->
|
|
|
+<!-- <el-tooltip :enterable="false" placement="top" content="显示" v-else>-->
|
|
|
+<!-- <img class="__hover" src="@/assets/images/ship-test/ship-track-invisible.svg" @click="handleTrackShow(key)"/>-->
|
|
|
+<!-- </el-tooltip>-->
|
|
|
+<!-- <el-tooltip :enterable="false" placement="top" content="定位">-->
|
|
|
+<!-- <img class="__hover" src="@/assets/images/ship-test/tools-location.svg" @click="handleTrackLocation(key)"/>-->
|
|
|
+<!-- </el-tooltip>-->
|
|
|
+<!-- <el-tooltip :enterable="false" placement="top" content="调色盘">-->
|
|
|
+<!-- <div class="color">-->
|
|
|
+<!-- <img class="__hover" src="@/assets/images/ship-test/ship-track-speed.svg"/>-->
|
|
|
+<!-- <el-color-picker v-model="item.color" @change="color => handleTrackColor(key, color)"/>-->
|
|
|
+<!-- </div>-->
|
|
|
+<!-- </el-tooltip>-->
|
|
|
+<!-- <el-tooltip :enterable="false" placement="top" content="删除">-->
|
|
|
+<!-- <img class="__hover" src="@/assets/images/ship-test/ship-track-delete.svg" @click="handleTrackDel(key)"/>-->
|
|
|
+<!-- </el-tooltip>-->
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </template>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+</template>
|
|
|
+
|
|
|
+<script lang="ts">
|
|
|
+import {
|
|
|
+ defineComponent,
|
|
|
+ computed,
|
|
|
+ onMounted,
|
|
|
+ ref,
|
|
|
+ reactive,
|
|
|
+ watch,
|
|
|
+ getCurrentInstance,
|
|
|
+ ComponentInternalInstance,
|
|
|
+ toRefs,
|
|
|
+ nextTick, onUnmounted
|
|
|
+} from 'vue'
|
|
|
+import {useStore} from 'vuex'
|
|
|
+import {useRouter, useRoute} from 'vue-router'
|
|
|
+import {ElMessage, ElMessageBox} from "element-plus";
|
|
|
+import ShipFilterCom from "../ship-filter.vue";
|
|
|
+import AisImg from '../AIS.svg'
|
|
|
+import mapboxgl from "mapbox-gl";
|
|
|
+import {J} from "../../../../../seat-tools/assets/index-b9e83171";
|
|
|
+
|
|
|
+export default defineComponent({
|
|
|
+ name: '',
|
|
|
+ components: {
|
|
|
+ ShipFilterCom,
|
|
|
+ },
|
|
|
+ props: {
|
|
|
+ map: <any>{},
|
|
|
+ mapFunc: <any>{},
|
|
|
+ },
|
|
|
+ setup(props, {emit}) {
|
|
|
+ const store = useStore();
|
|
|
+ const router = useRouter();
|
|
|
+ const route = useRoute();
|
|
|
+ const that = (getCurrentInstance() as ComponentInternalInstance).appContext.config.globalProperties
|
|
|
+ const state = reactive({
|
|
|
+ shipFilter: {
|
|
|
+ cql: null,
|
|
|
+ config: <any>{}
|
|
|
+ },
|
|
|
+ currentWS: <any>null,
|
|
|
+ trackMap: new Map(),
|
|
|
+ popupHover: <any>null,
|
|
|
+ mapIds: {
|
|
|
+ current: {
|
|
|
+ source: {
|
|
|
+ ship: 'source-current_ship'
|
|
|
+ },
|
|
|
+ layer: {
|
|
|
+ ship: 'layer-current_ship'
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ })
|
|
|
+ const handleShipFilter = ({cql, shipParams}) => {
|
|
|
+ state.currentWS?.close()
|
|
|
+ state.currentWS = null
|
|
|
+
|
|
|
+ state.shipFilter.cql = cql
|
|
|
+ state.shipFilter.config = shipParams
|
|
|
+ console.log(state.shipFilter.config)
|
|
|
+ refreshMap()
|
|
|
+ }
|
|
|
+ const initMap = () => {
|
|
|
+ // 船舶图片
|
|
|
+ props.mapFunc.addImg('ais', AisImg)
|
|
|
+ // 船舶source
|
|
|
+ props.map.addSource(state.mapIds.current.source.ship, {
|
|
|
+ type: 'geojson',
|
|
|
+ data: {
|
|
|
+ type: 'FeatureCollection',
|
|
|
+ features: []
|
|
|
+ }
|
|
|
+ })
|
|
|
+ // 船舶layer
|
|
|
+ props.map?.addLayer({
|
|
|
+ id: state.mapIds.current.layer.ship,
|
|
|
+ type: "symbol",
|
|
|
+ source: state.mapIds.current.source.ship,
|
|
|
+ layout: {
|
|
|
+ 'icon-allow-overlap': true,
|
|
|
+ 'icon-ignore-placement': true,
|
|
|
+ // 'text-field': '{gisId}',
|
|
|
+ // "text-font": ["cus"],
|
|
|
+ 'icon-image': [
|
|
|
+ 'match',
|
|
|
+ ['get', 'type'], // 根据属性值'type'来匹配
|
|
|
+ 'A', 'icon-A', // 当属性值为'A'时应用'icon-A'图片样式
|
|
|
+ 'B', 'icon-B', // 当属性值为'B'时应用'icon-B'图片样式
|
|
|
+ 'ais' // 默认样式
|
|
|
+ ],
|
|
|
+ "symbol-spacing": 1,
|
|
|
+ "icon-rotate": ["get", "__course"]
|
|
|
+ },
|
|
|
+ paint: {
|
|
|
+ 'icon-color': '#000000',
|
|
|
+ 'icon-halo-color': '#000000',
|
|
|
+ 'icon-halo-width': 4,
|
|
|
+ }
|
|
|
+ })
|
|
|
+ props.map.on('mousemove', state.mapIds.current.layer.ship, (e) => {
|
|
|
+ const features = props.map.queryRenderedFeatures(e.point);
|
|
|
+ if (!features.length) {
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ const properties: any = features[0].properties;
|
|
|
+ if (state.popupHover) {
|
|
|
+ state.popupHover.setLngLat(e.lngLat).setHTML(properties.gisId)
|
|
|
+ } else {
|
|
|
+ state.popupHover = new mapboxgl.Popup().setLngLat(e.lngLat).setHTML(properties.title).addTo(props.map);
|
|
|
+ state.popupHover.on('close', () => {
|
|
|
+ state.popupHover = null
|
|
|
+ })
|
|
|
+ }
|
|
|
+ });
|
|
|
+ props.map.on('mouseenter', state.mapIds.current.layer.ship, () => {
|
|
|
+ props.map.getCanvas().style.cursor = 'pointer';
|
|
|
+ });
|
|
|
+ props.map.on('mouseleave', state.mapIds.current.layer.ship, () => {
|
|
|
+ props.map.getCanvas().style.cursor = '';
|
|
|
+ });
|
|
|
+ }
|
|
|
+ const refreshMap = () => {
|
|
|
+ if (state.shipFilter.config) {
|
|
|
+ initWebSocketShip()
|
|
|
+ }
|
|
|
+ }
|
|
|
+ const initWebSocketShip = () => {
|
|
|
+ const str = {
|
|
|
+ cql: `(BBOX(location, ${props.mapFunc.getBBOX().join(',')})`,
|
|
|
+ }
|
|
|
+ if (state.shipFilter.cql) {
|
|
|
+ str.cql += ` and ${state.shipFilter.cql}`
|
|
|
+ }
|
|
|
+ str.cql += ')'
|
|
|
+ let ids: any = []
|
|
|
+ state.trackMap.forEach((value, key) => {
|
|
|
+ if (value.sourceId === state.shipFilter.config.id) {
|
|
|
+ ids.push(`'${key}'`)
|
|
|
+ }
|
|
|
+ })
|
|
|
+ if (ids.length > 0) {
|
|
|
+ str.cql += ` and (${state.shipFilter.config.track.trackKey.split('DATA.')[1]} not in (${ids.join(',')}))`
|
|
|
+ }
|
|
|
+ if (state.currentWS) {
|
|
|
+ if (state.currentWS.readyState === 1) {
|
|
|
+ state.currentWS.send(JSON.stringify(str))
|
|
|
+ }
|
|
|
+ } else {
|
|
|
+ const ws = new WebSocket(state.shipFilter.config.wsUrl)
|
|
|
+ state.currentWS = ws
|
|
|
+ ws.onopen = (e) => {
|
|
|
+ ws.send(JSON.stringify(str))
|
|
|
+ }
|
|
|
+ ws.onmessage = (e) => {
|
|
|
+ try {
|
|
|
+ const json = JSON.parse(e.data)
|
|
|
+ const s = 'json' + state.shipFilter.config.wsDataFlag
|
|
|
+ const data = eval(s)
|
|
|
+ initShip(data)
|
|
|
+ } catch (e) {
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ const initShip = (data) => {
|
|
|
+ const formatData = (arr) => {
|
|
|
+ return arr.map(DATA => {
|
|
|
+ const getKeyData = (key) => {
|
|
|
+ if (state.shipFilter.config.track[key]) {
|
|
|
+ return eval(state.shipFilter.config.track[key])
|
|
|
+ } else {
|
|
|
+ return null
|
|
|
+ }
|
|
|
+ }
|
|
|
+ const obj = {
|
|
|
+ type: 'Feature',
|
|
|
+ geometry: {
|
|
|
+ type: 'Point',
|
|
|
+ coordinates: that.$easyMap.formatPosition.wptTcpt(getKeyData('trackWktKey'))
|
|
|
+ },
|
|
|
+ properties: DATA
|
|
|
+ }
|
|
|
+ obj.properties.isHistory = false
|
|
|
+ obj.properties.__course = getKeyData('trackCourseKey')
|
|
|
+ obj.properties.config = JSON.parse(JSON.stringify(state.shipFilter.config))
|
|
|
+ return obj
|
|
|
+ })
|
|
|
+ }
|
|
|
+ props.map.getSource(state.mapIds.current.source.ship).setData({
|
|
|
+ type: 'FeatureCollection',
|
|
|
+ features: formatData(data)
|
|
|
+ })
|
|
|
+ props.map.setPaintProperty(state.mapIds.current.layer.ship, 'icon-color', state.shipFilter.config.color)
|
|
|
+ }
|
|
|
+ onMounted(() => {
|
|
|
+ initMap()
|
|
|
+ })
|
|
|
+ onUnmounted(() => {
|
|
|
+ })
|
|
|
+ return {
|
|
|
+ ...toRefs(state),
|
|
|
+ handleShipFilter
|
|
|
+ }
|
|
|
+ },
|
|
|
+})
|
|
|
+</script>
|
|
|
+
|
|
|
+<style scoped lang="scss">
|
|
|
+.business {
|
|
|
+ .ship-filter {
|
|
|
+ position: fixed;
|
|
|
+ top: 0;
|
|
|
+ left: 0;
|
|
|
+ }
|
|
|
+ .track-list {
|
|
|
+ position: fixed;
|
|
|
+ top: 0;
|
|
|
+ right: 0;
|
|
|
+ max-height: 600px;
|
|
|
+ background: linear-gradient(180deg, #3874C9 0%, #0043C4 100%);
|
|
|
+ padding: 10px 0;
|
|
|
+ color: #FFFFFF;
|
|
|
+ display: flex;
|
|
|
+ flex-direction: column;
|
|
|
+ overflow: hidden;
|
|
|
+ .track-item-head {
|
|
|
+ padding: 0 20px;
|
|
|
+ text-align: left;
|
|
|
+ }
|
|
|
+ .track-item-body {
|
|
|
+ padding: 0 20px;
|
|
|
+ flex: 1;
|
|
|
+ overflow-y: auto;
|
|
|
+ display: flex;
|
|
|
+ flex-direction: column;
|
|
|
+ }
|
|
|
+ .track-item {
|
|
|
+ display: flex;
|
|
|
+ font-size: 14px;
|
|
|
+ .key {
|
|
|
+ width: 200px;
|
|
|
+ }
|
|
|
+ .source {
|
|
|
+ width: 100px;
|
|
|
+ }
|
|
|
+ .time {
|
|
|
+ width: 100px;
|
|
|
+ }
|
|
|
+ .do {
|
|
|
+ display: flex;
|
|
|
+ >* {
|
|
|
+ width: 20px;
|
|
|
+ height: 20px;
|
|
|
+ }
|
|
|
+ .color {
|
|
|
+ position: relative;
|
|
|
+ width: 16px;
|
|
|
+ height: 16px;
|
|
|
+ display: flex;
|
|
|
+ >img {
|
|
|
+ width: 100%;
|
|
|
+ height: 100%;
|
|
|
+ }
|
|
|
+ :deep(.el-color-picker) {
|
|
|
+ width: 100%;
|
|
|
+ height: 100%;
|
|
|
+ position: absolute;
|
|
|
+ opacity: 0;
|
|
|
+ .el-color-picker__trigger {
|
|
|
+ width: 100%;
|
|
|
+ height: 100%;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+}
|
|
|
+</style>
|