浏览代码

静态物距离

CzRger 1 年之前
父节点
当前提交
768fb93a2e

+ 33 - 0
src/api/modules/ship-test/static.ts

@@ -0,0 +1,33 @@
+import { handle } from '../../index'
+
+const suffix = 'ax-node-api'
+
+export const shipTestStaticList = (params: any) => handle({
+  url: `/${suffix}/ship-test/static/list`,
+  method: 'post',
+  params
+})
+export const shipTestStaticPage = (params: any) => handle({
+  url: `/${suffix}/ship-test/static/page`,
+  method: 'post',
+  params
+})
+export const shipTestStaticInfo = (id: any) => handle({
+  url: `/${suffix}/ship-test/static/info/${id}`,
+  method: 'get',
+})
+export const shipTestStaticAdd = (params: any) => handle({
+  url: `/${suffix}/ship-test/static/add`,
+  method: 'post',
+  params
+})
+export const shipTestStaticEdit = (params: any) => handle({
+  url: `/${suffix}/ship-test/static/edit`,
+  method: 'put',
+  params
+})
+export const shipTestStaticDel = (params: any) => handle({
+  url: `/${suffix}/ship-test/static/del/`,
+  method: 'delete',
+  params
+})

+ 4 - 3
src/components/easyMap/func/base-draw.ts

@@ -26,6 +26,7 @@ const modifyFlag = ['interactionName', 'modifyInteraction']
 const baseDrawConfig = {
     //  样式字段
     text: null, // 要素上显示的文字,默认无文字
+    pointColor: '#e810dd', // Point的颜色,默认#e810dd
     pointIcon: null, // Point的图标,默认圆形
     pointScale: 1, // Point的缩放,默认1
     pointOffset: [0, 0], // Point的偏移量,默认[0, 0]
@@ -114,7 +115,7 @@ export const drawViews = (map, arr, isAuto = true) => {
         }
         const {
             textOffsetY = (type === 'Point' ? -30 : 0),
-            pointIcon, pointScale, pointOffset,
+            pointIcon, pointColor, pointScale, pointOffset,
             lineColor, lineWidth, lineType, lineDash,
             polyColor, polyBorderColor, polyBorderWidth, polyBorderType, polyBorderDash
         } = Object.assign(getBaseDrawConfig(), v)
@@ -136,7 +137,7 @@ export const drawViews = (map, arr, isAuto = true) => {
                         image: new style.Circle({
                             radius: 10,
                             fill: new style.Fill({
-                                color: '#e810dd',
+                                color: pointColor,
                             }),
                             scale: pointScale,
                             displacement: pointOffset
@@ -207,7 +208,7 @@ export const drawEdits = (map, obj, emitWkt, isAuto = true) => {
                     }) : new style.Circle({
                         radius: 10,
                         fill: new style.Fill({
-                            color: '#e810dd',
+                            color: obj.pointColor,
                         }),
                         scale: obj.pointScale,
                         displacement: obj.pointOffset

+ 123 - 0
src/components/easyMap/index.vue

@@ -8,6 +8,129 @@
       @olMapLoad="(map) => handleOlMapLoad(map)"
       :layout="layout"
     />
+    <template v-if="drawEditsConfig.show">
+      <div class="draw-edits-config">
+        <div class="draw-edits-config_feature-type">
+          <el-select v-model="drawEditsConfig.featureType" @change="drawEditsSwitchFeatureType">
+            <el-option label="点" value="Point" :disabled="!drawEditsConfig.isPoint"/>
+            <el-option label="线" value="LineString" :disabled="!drawEditsConfig.isLineString"/>
+            <el-option label="多边形" value="Polygon" :disabled="!drawEditsConfig.isPolygon"/>
+          </el-select>
+        </div>
+        <div class="draw-edits-config_position __hover">
+          <el-popover
+            width="500px"
+            placement="top"
+            title="经纬度"
+            trigger="click"
+            v-model:visible="drawEditsConfig.showPosition"
+          >
+            <div class="popover">
+              <CusForm ref="ref_drawEditsForm" labelWidth="0">
+                <CusFormColumn
+                  ref="ref_drawEditsWkt"
+                  required
+                  :span="24"
+                  type="textarea"
+                  :rows="4"
+                  v-model:param="drawEditsConfig.wkt"
+                  :placeholder="drawEditsWktPlaceholderCpt"
+                  :rules="[
+                      {
+                        handle: (val) => drawEditsConfig.featureType !== 'Point' || (drawEditsConfig.featureType === 'Point' && $easyMap.validWkt.Point(val)),
+                        message: '点位WKT坐标格式错误'
+                      },
+                      {
+                        handle: (val) => drawEditsConfig.featureType !== 'LineString' || (drawEditsConfig.featureType === 'LineString' && $easyMap.validWkt.LineString(val)),
+                        message: '线段WKT坐标格式错误'
+                      },
+                      {
+                        handle: (val) => drawEditsConfig.featureType !== 'Polygon' || (drawEditsConfig.featureType === 'Polygon' && $easyMap.validWkt.Polygon(val)),
+                        message: '区域WKT坐标格式错误'
+                      },
+                      {
+                        handle: (val) => drawEditsConfig.featureType !== 'Polygon' || (drawEditsConfig.featureType === 'Polygon' && $easyMap.validWkt.PolygonKinks(val)),
+                        message: '区域坐标不可自相交'
+                      },
+                    ]"
+                />
+              </CusForm>
+
+              <!--              <el-input-->
+              <!--                  v-model="drawEditsConfig.wkt"-->
+              <!--                  type="textarea"-->
+              <!--                  :rows="4"-->
+              <!--                  placeholder="请输入经纬度"-->
+              <!--              ></el-input>-->
+              <div class="footer">
+                <el-button size="small" type="primary" @click="changeWkt">保存</el-button>
+                <el-button size="small" @click="drawEditsConfig.showPosition = false">取消</el-button>
+              </div>
+            </div>
+            <template #reference>
+              <div class="reference">
+                <img src="./images/draw-edits-position.png" alt />
+                <span>经纬度</span>
+              </div>
+            </template>
+          </el-popover>
+        </div>
+        <template v-if="drawEditsConfig.featureType === 'Point'">
+          <div class="draw-edits-config_icon">
+            <img v-if="drawEditsConfig.pointIcon" class="icon" :src="drawEditsConfig.pointIcon"/>
+            <div class="text"><CusEllipsis :value="drawEditsConfig.text || '点位'"/></div>
+            <el-color-picker v-model="drawEditsConfig.pointColor" show-alpha @change="drawEditsConfig.refreshStyleFunc()"/>
+          </div>
+        </template>
+        <template v-if="drawEditsConfig.featureType === 'LineString'">
+          <div class="draw-edits-config_split"/>
+          <div class="draw-edits-config_line">
+            <div class="text">线段</div>
+            <el-input-number
+              class="width"
+              v-model="drawEditsConfig.lineWidth"
+              :min="1"
+              :max="100"
+              controls-position="right"
+              step-strictly
+              @change="drawEditsConfig.refreshStyleFunc()"
+            />
+            <el-color-picker v-model="drawEditsConfig.lineColor" show-alpha @change="drawEditsConfig.refreshStyleFunc()"/>
+            <el-select class="type" v-model="drawEditsConfig.lineType" @change="drawEditsConfig.refreshStyleFunc()">
+              <el-option label="—————" :value="0"/>
+              <el-option label="— — — —" :value="1"/>
+              <el-option label="- - - - - -  - -" :value="2"/>
+            </el-select>
+          </div>
+        </template>
+        <template v-if="drawEditsConfig.featureType === 'Polygon'">
+          <div class="draw-edits-config_split"/>
+          <div class="draw-edits-config_line">
+            <div class="text">边框</div>
+            <el-input-number
+              class="width"
+              v-model="drawEditsConfig.polyBorderWidth"
+              :min="1"
+              :max="100"
+              controls-position="right"
+              step-strictly
+              @change="drawEditsConfig.refreshStyleFunc()"
+            />
+            <el-color-picker v-model="drawEditsConfig.polyBorderColor" show-alpha @change="drawEditsConfig.refreshStyleFunc()"/>
+            <el-select class="type" v-model="drawEditsConfig.polyBorderType" @change="drawEditsConfig.refreshStyleFunc()">
+              <el-option label="—————" :value="0"/>
+              <el-option label="— — — —" :value="1"/>
+              <el-option label="- - - - - -  - -" :value="2"/>
+            </el-select>
+          </div>
+          <div class="draw-edits-config_split"/>
+          <div class="draw-edits-config_poly">
+            <div class="text">填充</div>
+            <el-color-picker v-model="drawEditsConfig.polyColor" show-alpha @change="drawEditsConfig.refreshStyleFunc()"/>
+          </div>
+        </template>
+      </div>
+    </template>
     <template v-if="layout === 'info'">
       <div class="easy-map_layout-info">
         <div class="location">

+ 2 - 0
src/components/easyMapGL/index.vue

@@ -72,6 +72,7 @@ export default defineComponent({
       const img = await loadImage(url);
       state.map.addImage(name, img, {sdf: true});
     }
+    const addCss = () => {}
     onMounted(() => {
     });
     return {
@@ -82,6 +83,7 @@ export default defineComponent({
 });
 </script>
 <style scoped lang="scss">
+@import "./popup-style-global.scss";
 .easy-map {
   width: 100%;
   height: 100%;

+ 1 - 0
src/components/easyMapGL/popup-style-global.scss

@@ -0,0 +1 @@
+@import '@/views/ship-test/business/v2/popup-style.scss';

+ 8 - 0
src/router/ship-test.ts

@@ -41,6 +41,14 @@ const aisTestRouter = [
           title: '详情框'
         }
       },
+      {
+        name: '14a6190e-4552-495a-880c-5aad5455c41e',
+        path: 'static',
+        component: () => import('@/views/ship-test/manage/static/index.vue'),
+        meta: {
+          title: '静态物'
+        }
+      },
     ]
   },
 ]

+ 1 - 1
src/styles/index.scss

@@ -1,6 +1,6 @@
 @import './webkit-scrollbar';
 @import './cus';
-@import './mapboxgl';
+@import 'mapboxgl';
 @import '@/components/easyMap/easy-map-style.scss';
 
 * {

+ 10 - 0
src/styles/mapboxgl.scss

@@ -329,6 +329,16 @@ a.mapboxgl-ctrl-logo.mapboxgl-compact {
   display: flex;
   will-change: transform;
   pointer-events: none;
+  &.cus {
+    .mapboxgl-popup-tip {
+      display: none;
+    }
+    .mapboxgl-popup-content {
+      background-color: transparent;
+      padding: 0;
+      box-shadow: unset;
+    }
+  }
 }
 .mapboxgl-popup-anchor-top,
 .mapboxgl-popup-anchor-top-left,

+ 34 - 1
src/views/ship-test/business/index.vue

@@ -81,6 +81,9 @@ import * as layer from "ol/layer";
 import ShipStyle from "@/views/ship-test/business/shipStyle";
 import * as format from "ol/format";
 import * as ol from "ol";
+import {shipTestStaticList} from "@/api/modules/ship-test/static";
+import TrackStyle from "@/views/init-speed-track/track-style";
+import * as turf from "@turf/turf";
 
 export default defineComponent({
   name: '',
@@ -107,7 +110,8 @@ export default defineComponent({
       trackMap: new Map(),
       shipHover: <any>null,
       shipHoverInfo: <any>{},
-      currentTime: 1
+      currentTime: 1,
+      staticList: []
     })
     const ref_shipHover = ref()
     const initMap = () => {
@@ -513,6 +517,12 @@ export default defineComponent({
       refreshMap()
     })
     const shipHoverInfoCpt = computed(() => {
+      const STATIC = (id) => {
+        return state.staticList.filter(v => id == v.id)?.[0].wkt
+      }
+      const DISTANCE = (p1, p2) => {
+        return turf.distance(that.$easyMap.formatPosition.wptTcpt(p1), that.$easyMap.formatPosition.wptTcpt(p2), {units: 'kilometers'}).toFixed(2) + 'km'
+      }
       const arr: any = []
       const DATA = state.shipHoverInfo.data
       if (DATA.isHistory) {
@@ -595,8 +605,31 @@ export default defineComponent({
         ElMessage.warning('没有轨迹点可定位')
       }
     }
+    const initStatic = () => {
+      state.staticList = []
+      that.$api.shipTestStaticList().then(res => {
+        if (res.code === 200) {
+          state.staticList = res.data
+          that.$easyMap.initShape({
+            map: props.map,
+            layerName: 'static-point',
+            layerZIndex: 10,
+            list: state.staticList.map((v, i) => {
+              return {
+                easyMapParams: {
+                  id: `static-point-${i}`,
+                  position: v.wkt,
+                  normalStyle: ShipStyle.staticPointStyle(v.color, v.name)
+                }
+              }
+            })
+          })
+        }
+      })
+    }
     onMounted(() => {
       initMap()
+      initStatic()
       setInterval(() => state.currentTime++, 5000)
     })
     onUnmounted(() => {

+ 28 - 1
src/views/ship-test/business/shipStyle.ts

@@ -231,10 +231,37 @@ const trackPointHoverStyle = (color) => {
   }))
   return _style
 }
+const staticPointStyle = (color, text) => {
+  const _style: any = []
+  _style.push(new style.Style({
+    image: new style.Circle({
+      radius: 10,
+      fill: new style.Fill({
+        color: color,
+      }),
+    })
+  }))
+  _style.push(new style.Style({
+    text: new style.Text({
+      font: "16px bold 微软雅黑",
+      text: text,
+      fill: new style.Fill({
+        color: '#ffffff'
+      }),
+      stroke: new style.Stroke({
+        color: '#D26CDB',
+        width: 2
+      }),
+      offsetY: -30,
+    }),
+  }))
+  return _style
+}
 export default {
   ShipNormalStyle,
   shipActiveStyle,
   trackStyle,
   trackPointNormalStyle,
-  trackPointHoverStyle
+  trackPointHoverStyle,
+  staticPointStyle
 }

+ 74 - 4
src/views/ship-test/business/v2/index.vue

@@ -40,6 +40,22 @@
         </template>
       </div>
     </div>
+    <div class="hover-info" id="ID_shipHover" ref="ref_shipHover">
+      <div class="hover-info-head">
+        <span>{{shipHoverInfo?.config?.name}}_{{ shipHoverInfo?.isHistory ? '历史' : '实时'}}</span>
+      </div>
+      <div class="hover-info-close __hover" @click="null">
+        <img src="@/components/easyMap/images/close.png" alt=""/>
+      </div>
+      <div class="hover-main" v-if="shipHoverInfo?.data">
+        <template v-for="item in shipHoverInfoCpt">
+          <div class="hover-item">
+            <div class="hover-item-label">{{item.label}}:</div>
+            <div class="hover-item-value">{{item.value}}</div>
+          </div>
+        </template>
+      </div>
+    </div>
   </div>
 </template>
 
@@ -62,7 +78,6 @@ 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: '',
@@ -95,8 +110,14 @@ export default defineComponent({
             ship: 'layer-current_ship'
           }
         }
+      },
+      shipHoverInfo: {
+        data: {},
+        config: {},
+        isHistory: false
       }
     })
+    const ref_shipHover = ref()
     const handleShipFilter = ({cql, shipParams}) => {
       state.currentWS?.close()
       state.currentWS = null
@@ -149,10 +170,23 @@ export default defineComponent({
           return;
         }
         const properties: any = features[0].properties;
+        state.shipHoverInfo = {
+          data: properties,
+          config: state.shipFilter.config,
+          isHistory: true
+        }
         if (state.popupHover) {
-          state.popupHover.setLngLat(e.lngLat).setHTML(properties.gisId)
+          state.popupHover.setLngLat(e.lngLat)
         } else {
-          state.popupHover = new mapboxgl.Popup().setLngLat(e.lngLat).setHTML(properties.title).addTo(props.map);
+          state.popupHover = new mapboxgl.Popup({
+            className: 'cus',
+            maxWidth: 'none',
+            closeButton: false,
+            closeOnClick: false,
+            offset: 60
+          }).setLngLat(e.lngLat)
+          .setDOMContent(document.getElementById('ID_shipHover'))
+          .addTo(props.map);
           state.popupHover.on('close', () => {
             state.popupHover = null
           })
@@ -238,6 +272,40 @@ export default defineComponent({
       })
       props.map.setPaintProperty(state.mapIds.current.layer.ship, 'icon-color', state.shipFilter.config.color)
     }
+    const shipHoverInfoCpt = computed(() => {
+      const arr: any = []
+      const DATA = state.shipHoverInfo.data
+      if (DATA.isHistory) {
+        state.shipHoverInfo?.config?.hover?.forEach(h => {
+          if (h.historyValue) {
+            let value = eval(h.historyValue)
+            h.options?.forEach(v => {
+              if (String(value) == v.value) {
+                value = v.label
+              }
+            })
+            arr.push({
+              label: h.name,
+              value: value
+            })
+          }
+        })
+      } else {
+        state.shipHoverInfo?.config?.hover?.forEach(h => {
+          let value = eval(h.value)
+          h.options?.forEach(v => {
+            if (String(value) == v.value) {
+              value = v.label
+            }
+          })
+          arr.push({
+            label: h.name,
+            value: value
+          })
+        })
+      }
+      return arr
+    })
     onMounted(() => {
       initMap()
     })
@@ -245,7 +313,9 @@ export default defineComponent({
     })
     return {
       ...toRefs(state),
-      handleShipFilter
+      handleShipFilter,
+      ref_shipHover,
+      shipHoverInfoCpt
     }
   },
 })

+ 0 - 164
src/views/ship-test/business/v2/ship-hover.vue

@@ -1,164 +0,0 @@
-<template>
-  <div class="hover-info" ref="ref_shipHover">
-    ASDASDASDADADS
-<!--    <div class="hover-info-head">-->
-<!--      <span>{{shipHoverInfo?.config?.name}}_{{ shipHoverInfo?.isHistory ? '历史' : '实时'}}</span>-->
-<!--    </div>-->
-<!--    <div class="hover-info-close __hover" @click="shipHover.setPosition(undefined)">-->
-<!--      <img src="@/components/easyMap/images/close.png" alt=""/>-->
-<!--    </div>-->
-<!--    <div class="hover-main" v-if="shipHoverInfo?.data">-->
-<!--      <template v-for="item in shipHoverInfoCpt">-->
-<!--        <div class="hover-item">-->
-<!--          <div class="hover-item-label">{{item.label}}:</div>-->
-<!--          <div class="hover-item-value">{{item.value}}</div>-->
-<!--        </div>-->
-<!--      </template>-->
-<!--    </div>-->
-  </div>
-</template>
-
-<script lang="ts">
-import {
-  defineComponent,
-  computed,
-  onMounted,
-  ref,
-  reactive,
-  watch,
-  getCurrentInstance,
-  ComponentInternalInstance,
-  toRefs,
-  nextTick
-} from 'vue'
-import {useStore} from 'vuex'
-import {useRouter, useRoute} from 'vue-router'
-import {ElMessage, ElMessageBox} from "element-plus";
-
-export default defineComponent({
-  name: '',
-  components: {},
-  props: {
-    form: {
-      required: true,
-      default: <any>{}
-    }
-  },
-  setup(props, {emit}) {
-    const store = useStore();
-    const router = useRouter();
-    const route = useRoute();
-    const that = (getCurrentInstance() as ComponentInternalInstance).appContext.config.globalProperties
-    const state = reactive({})
-    const shipHoverInfoCpt = computed(() => {
-      const arr: any = []
-      // const DATA = props.form
-      // if (DATA.isHistory) {
-      //   state.shipHoverInfo.config.hover.forEach(h => {
-      //     if (h.historyValue) {
-      //       let value = eval(h.historyValue)
-      //       h.options?.forEach(v => {
-      //         if (String(value) == v.value) {
-      //           value = v.label
-      //         }
-      //       })
-      //       arr.push({
-      //         label: h.name,
-      //         value: value
-      //       })
-      //     }
-      //   })
-      // } else {
-      //   state.shipHoverInfo.config.hover.forEach(h => {
-      //     let value = eval(h.value)
-      //     h.options?.forEach(v => {
-      //       if (String(value) == v.value) {
-      //         value = v.label
-      //       }
-      //     })
-      //     arr.push({
-      //       label: h.name,
-      //       value: value
-      //     })
-      //   })
-      // }
-
-      return arr
-    })
-    onMounted(() => {
-      shipHoverInfoCpt
-    })
-    return {
-      ...toRefs(state),
-    }
-  },
-})
-</script>
-
-<style scoped lang="scss">
-.hover-info {
-  $footH: 10px;
-  width: 300px;
-  background: linear-gradient(180deg, #3874C9 0%, #0043C4 100%);
-  border-radius: 0px 4px 4px 4px;
-  position: relative;
-  display: flex;
-  justify-content: center;
-  &:after {
-    content: '';
-    position: absolute;
-    bottom: -$footH;
-    border-top: $footH solid #0043C4;
-    border-left: $footH solid transparent;
-    border-right: $footH solid transparent;
-  }
-  .hover-info-head {
-    padding: 0 4px;
-    height: 18px;
-    position: absolute;
-    top: -18px;
-    left: 0;
-    font-size: 12px;
-    font-family: PingFang SC;
-    font-weight: 500;
-    color: #FFFFFF;
-    display: flex;
-    align-items: center;
-    line-height: 8px;
-    background: linear-gradient(180deg, #3874C9 0%, #0043C4 100%);
-    border-radius: 2px 2px 0 0;/* 设置圆角 */
-    >img {
-      margin: 0 4px 0 6px;
-    }
-  }
-  .hover-info-close {
-    position: absolute;
-    right: 0;
-    top: -16px;
-  }
-  .hover-main {
-    width: 100%;
-    height: auto;
-    padding: 10px;
-    .hover-item {
-      display: flex;
-      .hover-item-label {
-        min-width: 42px;
-        font-size: 14px;
-        font-family: PingFang SC;
-        font-weight: 600;
-        color: #08FFFF;
-        line-height: 20px;
-      }
-      .hover-item-value {
-        flex: 1;
-        font-size: 14px;
-        font-family: PingFang SC;
-        font-weight: 400;
-        color: #FFFFFF;
-        line-height: 20px;
-      }
-    }
-  }
-}
-</style>

+ 1 - 1
src/views/ship-test/index.vue

@@ -55,7 +55,7 @@ export default defineComponent({
     const state = reactive({
       map: <any>null,
       mapFunc: <any>null,
-      version: 'v2'
+      version: 'v1'
     })
     const mapLoad = (map, func) => {
       state.map = map

+ 2 - 2
src/views/ship-test/manage/hover/detail.vue

@@ -24,7 +24,7 @@
             v-model:param="cusDetail.value"
             :rows="2">
           <template #label>
-            <el-tooltip content="数据属性取值,采用DATA.xxx格式">
+            <el-tooltip content="数据属性取值,采用DATA.xxx格式,DISTANCE(wkt1, wkt2)方法可以判断坐标,传入两个坐标值,STATIC(ID)可以获取静态物的坐标">
               <div>
                 取值<SvgIcon name="tips" color="#333333"/>
               </div>
@@ -37,7 +37,7 @@
             v-model:param="cusDetail.historyValue"
             :rows="2">
           <template #label>
-            <el-tooltip content="数据属性取值,采用DATA.xxx格式">
+            <el-tooltip content="数据属性取值,采用DATA.xxx格式,DISTANCE(wkt1, wkt2)方法可以判断坐标,传入两个坐标值,STATIC(ID)可以获取静态物的坐标">
               <div>
                 取值(历史)<SvgIcon name="tips" color="#333333"/>
               </div>

二进制
src/views/ship-test/manage/static/biaohui.png


+ 255 - 0
src/views/ship-test/manage/static/detail.vue

@@ -0,0 +1,255 @@
+<template>
+  <CusDialog
+      :title="transfer.method === 'add' ? '新增静态物' : '静态物详情'"
+      :show="show"
+      @onClose="$emit('update:show', false)"
+      @onSubmit="onSubmit"
+      height="auto"
+      :closeConfirm="!isViewCpt"
+      :loading="loading"
+      :showSubmit="!isViewCpt"
+  >
+    <div class="__normal-form">
+      <CusForm labelWidth="70px" ref="ref_form" :formView="isViewCpt">
+        <div class="__cus-title_2">基本信息</div>
+        <CusFormColumn
+            v-if="cusDetail.id"
+            :span="24"
+            :disabled="true"
+            label="ID"
+            v-model:param="cusDetail.name"/>
+        <CusFormColumn
+            :span="12"
+            required
+            label="名称"
+            v-model:param="cusDetail.name"
+            :maxLength="20"/>
+        <CusFormColumn
+            :span="12"
+            label="排序"
+            link="input-number"
+            :min="0"
+            :max="999"
+            v-model:param="cusDetail.sort"/>
+        <CusFormColumn
+          :span="24"
+          required
+          label="WKT"
+          v-model:param="cusDetail.wkt">
+          <template #cus="{handleValidate}">
+            <div class="map-draw">
+              <el-input
+                type="textarea"
+                v-model="cusDetail.wkt"
+                rows="4"
+                resize="none"
+                autocomplete="off"
+                disabled
+              ></el-input>
+              <div class="btn-wrap">
+                <template v-if="isViewCpt && cusDetail.wkt">
+                  <div class="btn __hover" @click="onMapDraw">
+                    <span> <i class="bh-icon"></i>查看 </span>
+                  </div>
+                </template>
+                <template v-if="!isViewCpt">
+                  <div class="btn __hover" @click="onMapDraw">
+                    <span> <i class="bh-icon"></i>标绘 </span>
+                  </div>
+                  <div class="btn __hover" @click="onMapClear">
+                    <span> <i class="qk-icon"></i>清空 </span>
+                  </div>
+                </template>
+              </div>
+            </div>
+          </template>
+        </CusFormColumn>
+        <CusFormColumn
+            :span="24"
+            label="备注"
+            type="textarea"
+            v-model:param="cusDetail.remark"
+            :rows="4"/>
+      </CusForm>
+      <MapDrawCom v-model:show="showMapDraw" :transfer="transferMapDraw" @getMapDrawData="getMapDrawData"/>
+    </div>
+  </CusDialog>
+</template>
+
+<script lang="ts">
+import {
+  defineComponent,
+  computed,
+  onMounted,
+  ref,
+  reactive,
+  watch,
+  getCurrentInstance,
+  ComponentInternalInstance,
+  toRefs,
+  nextTick
+} from 'vue'
+import {useStore} from 'vuex'
+import {useRouter, useRoute} from 'vue-router'
+import {ElMessage, ElMessageBox } from 'element-plus';
+import MapDrawCom from './map-draw.vue'
+
+export default defineComponent({
+  name: '',
+  components: {MapDrawCom},
+  props: {
+    show: {},
+    transfer: <any>{}
+  },
+  setup(props, {emit}) {
+    const store = useStore();
+    const router = useRouter();
+    const route = useRoute();
+    const that = (getCurrentInstance() as ComponentInternalInstance).appContext.config.globalProperties
+    const state = reactive({
+      cusDetail: {},
+      loading: false,
+      showMapDraw: false,
+      transferMapDraw: {}
+    })
+    watch(() => props.show, (n) => {
+      if (n) {
+        state.cusDetail = {}
+        if (props.transfer.method !== 'add') {
+          state.loading = true
+          that.$api.shipTestStaticInfo(props.transfer.id).then(res => {
+            if (res.code === 200) {
+              state.cusDetail = res.data
+              state.loading = false
+            } else {
+              ElMessage.error(res.msg)
+            }
+          })
+        }
+        nextTick(() => {
+          ref_form.value.reset()
+        })
+      }
+    })
+    const ref_form = ref()
+    const isViewCpt = computed(() => {
+      return props.transfer.method === 'view'
+    })
+    const onSubmit = () => {
+      ref_form.value.submit().then(() => {
+        ElMessageBox.confirm("是否提交?", "提示", {
+          confirmButtonText: "确定",
+          cancelButtonText: "取消",
+          type: "warning",
+        }).then(() => {
+          state.loading = true
+          const apiHandle = props.transfer.method === 'edit' ? that.$api.shipTestStaticEdit(state.cusDetail) : that.$api.shipTestStaticAdd(state.cusDetail)
+          apiHandle.then(res => {
+            if (res.code === 200) {
+              ElMessage.success(props.transfer.method === 'edit' ? '编辑成功' : '新增成功')
+              emit('update:show', false)
+              emit('refresh')
+            } else {
+              ElMessage.error(res.msg)
+            }
+            state.loading = false
+          }).catch(() => {
+            state.loading = false
+          })
+        }).catch(() => {})
+      }).catch((e) => {
+        ElMessage({
+          message: e[0].message,
+          grouping: true,
+          type: 'warning',
+        })
+      })
+    }
+    const onMapDraw = () => {
+      state.transferMapDraw = {
+        isView: isViewCpt.value,
+        name: state.cusDetail.name || '',
+        mapDrawParams: {
+          wkt: state.cusDetail.wkt,
+          pointColor: state.cusDetail.color,
+          featureType: 'Point',
+          text: state.cusDetail.name || ''
+        }
+      }
+      state.showMapDraw = true
+    }
+    const onMapClear = () => {
+      state.cusDetail.wkt = ''
+      state.cusDetail.color = ''
+    }
+    const getMapDrawData = (data) => {
+      state.cusDetail.wkt = data.wkt
+      state.cusDetail.color = data.pointColor
+    }
+    return {
+      ...toRefs(state),
+      onSubmit,
+      isViewCpt,
+      ref_form,
+      onMapDraw,
+      onMapClear,
+      getMapDrawData
+    }
+  },
+})
+</script>
+
+<style scoped lang="scss">
+.__cus-title_2 {
+  margin-bottom: 10px;
+}
+.map-draw {
+  display: flex;
+  flex-direction: column;
+  :deep(.el-textarea__inner) {
+    border-bottom-left-radius: 0 !important;
+    border-bottom-right-radius: 0 !important;
+  }
+  .btn-wrap {
+    border: 1px solid #dcdfe6;
+    border-top: 0;
+    padding: 8px 0;
+    line-height: 20px;
+    width: 100%;
+    display: flex;
+    color: #2956f1;
+    border-bottom-left-radius: 4px;
+    border-bottom-right-radius: 4px;
+    .btn {
+      cursor: pointer;
+      span {
+        padding: 0 15px;
+        position: relative;
+        display: flex;
+        align-items: center;
+        i {
+          display: inline-block;
+          width: 22px;
+          height: 22px;
+          background-size: contain;
+        }
+        .bh-icon {
+          background: url("./biaohui.png") no-repeat;
+        }
+        .qk-icon {
+          background: url("./qingkong.png") no-repeat;
+        }
+      }
+      &:nth-child(2) span::after {
+        position: absolute;
+        left: 0;
+        top: 0;
+        height: 100%;
+        width: 1px;
+        background-color: #dcdfe6;
+        content: "";
+      }
+    }
+  }
+}
+</style>

+ 293 - 0
src/views/ship-test/manage/static/index.vue

@@ -0,0 +1,293 @@
+<template>
+  <div class="__normal-page">
+    <div class="__normal-content">
+      <CusContent
+          v-model:tableHead="tableHead"
+          @handleReset="handleReset"
+          @handleSearch="onSearch"
+          v-model:full="isFull"
+      >
+        <template #fieldOut>
+          <CusForm labelWidth="100px" @handleEnter="onSearch">
+            <CusFormColumn
+                filterSpan="1"
+                label="名称"
+                v-model:param="queryForm.name"/>
+            <CusSearchButtons
+                @handleReset="handleReset"
+                @handleSearch="onSearch"
+            />
+          </CusForm>
+        </template>
+        <template #buttons>
+          <btn-add @click="onAdd"/>
+          <btn-delete @click="onDel()"/>
+        </template>
+        <template #table>
+          <CusTable
+              v-loading="loading"
+              ref="ref_cusTable"
+              :tableData="queryResult.tableData"
+              :tableHead="tableHead"
+              :total="queryResult.total"
+              :page="queryPage.pageNum"
+              :pageSize="queryPage.pageSize"
+              @handlePage="handlePage"
+              @handleSort="handleSort"
+              v-model:selected="selected"
+              v-model:full="isFull"
+          >
+            <template #wkt-column-value="{ scope }">
+              <el-link
+                class="__text-ellipsis"
+                href="javascript:;"
+                type="primary"
+                @click="onMapView(scope.row)"
+              >{{scope.row.wkt}}</el-link>
+            </template>
+            <template #color-column-value="{ scope }">
+              <div :style="`width: 100%;height: 10px;background-color: ${scope.row.color};`"></div>
+            </template>
+            <template #name-column-value="{ scope }">
+              <el-link
+                  class="__text-ellipsis"
+                  href="javascript:;"
+                  type="primary"
+                  @click="onView(scope.row)"
+              >{{scope.row.name}}</el-link>
+            </template>
+            <template #caozuo-column-value="{ scope }">
+              <el-link
+                  href="javascript:;"
+                  type="primary"
+                  @click="onEdit(scope.row)"
+              >编辑</el-link>
+              <el-link
+                  href="javascript:;"
+                  type="danger"
+                  @click="onDel(scope.row)"
+              >删除</el-link>
+            </template>
+          </CusTable>
+        </template>
+      </CusContent>
+    </div>
+    <DetailCom v-model:show="showDetail" :transfer="transfer" @refresh="refreshSearch"/>
+    <MapDrawCom v-model:show="showMapDraw" :transfer="transferMapDraw"/>
+  </div>
+</template>
+
+<script lang="ts">
+import {
+  defineComponent,
+  onMounted,
+  ref,
+  toRefs,
+  reactive,
+  watch,
+  getCurrentInstance,
+  ComponentInternalInstance,
+  computed,
+} from "vue";
+import { useStore } from "vuex";
+import { useRouter, useRoute } from "vue-router";
+import { ElMessage, ElMessageBox } from "element-plus";
+import DetailCom from './detail.vue'
+import MapDrawCom from "./map-draw.vue";
+
+export default defineComponent({
+  name: "",
+  components: {
+    MapDrawCom,
+    DetailCom,
+  },
+  setup(props, {emit}) {
+    const store = useStore();
+    const that = (getCurrentInstance() as ComponentInternalInstance).appContext.config.globalProperties;
+    const state = reactive({
+      //  加载中
+      loading: false,
+      //  查询分页参数
+      queryPage: {
+        pageNum: 1,
+        pageSize: 10
+      },
+      //  查询结果
+      queryResult: {
+        total: 0,
+        tableData: []
+      },
+      //  查询表单参数
+      queryForm: <any>{},
+      //  查询表单参数备份
+      back_queryForm: {},
+      //  查询表格排序
+      querySort: {},
+      //  表格可选的情况下,绑定的数组
+      selected: [],
+      //  表格表头
+      tableHead: [
+        {value: 'id', label: 'ID', show:true,},
+        {value: 'name', label: '名称', show:true,},
+        {value: 'wkt', label: 'WKT', show:true,},
+        {value: 'color', label: 'color', show:true,},
+        {value: 'sort', label: '排序', show:true, sort: true},
+        {value: 'updateTime', label: '更新时间', show:true, sort: true},
+        {value: 'caozuo', label: '操作', show:true}
+      ],
+      isFull: false,
+      transfer: {},
+      showDetail: false,
+      showMapDraw: false,
+      transferMapDraw: {}
+    });
+    const ref_cusTable = ref()
+    //  获取字典
+    const initDictionary = () => {
+    }
+    //  查询分页参数改变处理方法
+    const handlePage = ({page, pageSize}: any) => {
+      state.queryPage.pageNum = page
+      state.queryPage.pageSize = pageSize
+      handleSearch(page, pageSize)
+    }
+    //  查询排序参数改变处理方法
+    const handleSort = ({key, value}: any) => {
+      state.querySort = key ? {[key]: value} : {}
+      refreshSearch()
+    }
+    //  列表刷新方法
+    const refreshSearch = () => {
+      state.queryPage.pageNum = 1
+      handleSearch(state.queryPage.pageNum, state.queryPage.pageSize)
+    }
+    //  重置查询表单方法
+    const handleReset = () => {
+      ref_cusTable.value?.reset()
+      state.querySort = {}
+      state.queryForm = {}
+      state.back_queryForm = JSON.parse(JSON.stringify(state.queryForm))
+      refreshSearch()
+    }
+    //  查询方法
+    const handleSearch = (page = 1, pageSize = 10) => {
+      //  添加分页参数
+      const queryParams: any = {
+        pageNum: page,
+        pageSize: pageSize,
+      }
+      //  添加排序参数
+      for (const [k, v] of Object.entries(state.querySort)) {
+        if (that.$util.isValue(v)) {
+          queryParams['orderByColumn'] = k
+          queryParams['isAsc'] = v === "ascending"
+        }
+      }
+      //  添加表单参数
+      for (const [k, v] of Object.entries(state.back_queryForm)) {
+        if (that.$util.isValue(v)) {
+          queryParams[k] = v
+        }
+      }
+      state.loading = true
+      that.$api.shipTestStaticPage(queryParams).then((res) => {
+        if (res.code === 200) {
+          state.queryResult.tableData = res.data.data
+          state.queryResult.total = res.data.total
+        } else {
+          ElMessage.error(res.msg)
+        }
+        state.loading = false
+      }).catch(() => {
+        state.loading = false
+      })
+    }
+    //  点击查询按钮后
+    const onSearch = () => {
+      state.back_queryForm = JSON.parse(JSON.stringify(state.queryForm))
+      refreshSearch()
+    }
+    const onAdd = () => {
+      state.transfer = {
+        method: 'add',
+      }
+      state.showDetail = true
+    }
+    const onEdit = (row) => {
+      state.transfer = {
+        method: 'edit',
+        id: row.id
+      }
+      state.showDetail = true
+    }
+    const onView = (row) => {
+      state.transfer = {
+        method: 'view',
+        id: row.id
+      }
+      state.showDetail = true
+    }
+    const onDel = (row: any = null) => {
+      const arr = row ? [row] : state.selected
+      if (arr.length > 0) {
+        ElMessageBox.confirm(`是否删除${arr.length === 1 ? arr[0].name : `${arr.length}条记录`}?`, "提示", {
+          confirmButtonText: "确定",
+          cancelButtonText: "取消",
+          type: "warning",
+        }).then(() => {
+          state.loading = true
+          that.$api.shipTestStaticDel(arr.map(v => v.id).join(',')).then(res => {
+            if (res.code === 200) {
+              ElMessage.success('删除成功')
+              refreshSearch()
+            } else {
+              ElMessage.error(res.msg)
+              state.loading = false
+            }
+          }).catch(() => {
+            state.loading = false
+          })
+        }).catch(() => {
+        })
+      } else {
+        ElMessage.warning('请至少选择一条记录!')
+      }
+    }
+    const onMapView = (row) => {
+      state.transferMapDraw = {
+        isView: true,
+        name: row.name,
+        mapDrawParams: {
+          wkt: row.wkt,
+          pointColor: row.color,
+          featureType: 'Point',
+          text: row.name
+        }
+      }
+      state.showMapDraw = true
+    }
+    onMounted(() => {
+      state.back_queryForm = JSON.parse(JSON.stringify(state.queryForm))
+      initDictionary()
+      refreshSearch()
+    })
+    return {
+      ref_cusTable,
+      ...toRefs(state),
+      handleSearch,
+      refreshSearch,
+      handlePage,
+      handleSort,
+      handleReset,
+      onSearch,
+      onAdd,
+      onEdit,
+      onView,
+      onDel,
+      onMapView
+    }
+  },
+});
+</script>
+<style scoped lang="scss">
+</style>

+ 84 - 0
src/views/ship-test/manage/static/map-draw.vue

@@ -0,0 +1,84 @@
+<template>
+  <CusDialog
+      :title="transfer.name + '位置'"
+      :show="show"
+      @onClose="$emit('update:show', false)"
+      @onSubmit="$emit('getMapDrawData', drawData), $emit('update:show', false)"
+  >
+      <EasyMapComponent
+          class="map"
+          @easyMapLoad="mapLoad"
+          layout="info"
+      />
+  </CusDialog>
+</template>
+
+<script lang="ts">
+import {
+  defineComponent,
+  computed,
+  onMounted,
+  ref,
+  reactive,
+  watch,
+  getCurrentInstance,
+  ComponentInternalInstance,
+  toRefs,
+  nextTick
+} from 'vue'
+import {useStore} from 'vuex'
+import {useRouter, useRoute} from 'vue-router'
+
+export default defineComponent({
+  name: '',
+  components: {},
+  props: {
+    show: {},
+    transfer: <any>{}
+  },
+  setup(props, {emit}) {
+    const store = useStore();
+    const router = useRouter();
+    const route = useRoute();
+    const that = (getCurrentInstance() as ComponentInternalInstance).appContext.config.globalProperties
+    const state = reactive({
+      map: <any>null,
+      mapFunc: null,
+      drawData: null
+    })
+    watch(() => props.show, (n) => {
+      if (n) {
+        initDraw()
+      } else {
+        if (state.mapFunc) {
+          state.mapFunc?.drawExit()
+        }
+      }
+    })
+    const mapLoad = (map, func) => {
+      state.map = map
+      state.mapFunc = func
+      if (props.show) {
+        initDraw()
+      }
+    }
+    const initDraw = () => {
+      if (props.transfer.isView) {
+        state.mapFunc?.drawViews([props.transfer.mapDrawParams])
+      } else {
+        state.drawData = null
+        state.mapFunc?.drawEdits(props.transfer.mapDrawParams, (param) => {
+          state.drawData = param
+        })
+      }
+    }
+    return {
+      ...toRefs(state),
+      mapLoad,
+    }
+  },
+})
+</script>
+
+<style scoped lang="scss">
+</style>

二进制
src/views/ship-test/manage/static/qingkong.png