Browse Source

初始化

CzRger 2 years ago
parent
commit
a64d98c9f7

+ 24 - 0
.gitignore

@@ -0,0 +1,24 @@
+# Logs
+logs
+*.log
+npm-debug.log*
+yarn-debug.log*
+yarn-error.log*
+pnpm-debug.log*
+lerna-debug.log*
+
+node_modules
+dist
+dist-ssr
+*.local
+
+# Editor directories and files
+.vscode/*
+!.vscode/extensions.json
+.idea
+.DS_Store
+*.suo
+*.ntvs*
+*.njsproj
+*.sln
+*.sw?

+ 3 - 0
.vscode/extensions.json

@@ -0,0 +1,3 @@
+{
+  "recommendations": ["Vue.volar", "Vue.vscode-typescript-vue-plugin"]
+}

+ 13 - 0
index.html

@@ -0,0 +1,13 @@
+<!DOCTYPE html>
+<html lang="en">
+  <head>
+    <meta charset="UTF-8" />
+    <link rel="icon" type="image/svg+xml" href="/vite.svg" />
+    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
+    <title>Vite + Vue + TS</title>
+  </head>
+  <body>
+    <div id="app"></div>
+    <script type="module" src="/src/main.ts"></script>
+  </body>
+</html>

+ 26 - 0
package.json

@@ -0,0 +1,26 @@
+{
+  "name": "seat-tools",
+  "private": true,
+  "version": "0.0.0",
+  "type": "module",
+  "scripts": {
+    "dev": "vite",
+    "build": "vue-tsc && vite build",
+    "preview": "vite preview"
+  },
+  "dependencies": {
+    "vue": "^3.2.47",
+    "sass": "^1.60.0",
+    "axios": "^1.3.4",
+    "ol": "^6.5.0",
+    "vuex": "^4.1.0",
+    "vue-router": "^4.1.6",
+    "element-plus": "^2.3.1"
+  },
+  "devDependencies": {
+    "@vitejs/plugin-vue": "^4.1.0",
+    "typescript": "^4.9.3",
+    "vite": "^4.2.0",
+    "vue-tsc": "^1.2.0"
+  }
+}

File diff suppressed because it is too large
+ 1 - 0
public/vite.svg


+ 43 - 0
src/App.vue

@@ -0,0 +1,43 @@
+<template>
+  <div style="overflow: hidden">
+    <router-view/>
+  </div>
+</template>
+<script lang="ts">
+import {
+  defineComponent,
+  ref,
+  nextTick,
+  onMounted,
+  watch,
+  computed,
+  ComponentInternalInstance,
+  getCurrentInstance
+} from 'vue'
+import {useStore} from 'vuex'
+import {ElConfigProvider} from 'element-plus'
+import zhLocale from 'element-plus/lib/locale/lang/zh-cn'
+export default defineComponent({
+  name: 'App',
+  components: {
+    [ElConfigProvider.name]: ElConfigProvider //添加组件
+  },
+  setup() {
+    const store = useStore()
+    const locale = ref(zhLocale)
+    const that = (getCurrentInstance() as ComponentInternalInstance).appContext.config.globalProperties
+    return {
+      locale
+    }
+  }
+})
+</script>
+<style scope lang="scss">
+html, body {
+  margin: 0;
+  padding: 0;
+  border: 0;
+  width: 100%;
+  height: 100%;
+}
+</style>

+ 1 - 0
src/assets/vue.svg

@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" class="iconify iconify--logos" width="37.07" height="36" preserveAspectRatio="xMidYMid meet" viewBox="0 0 256 198"><path fill="#41B883" d="M204.8 0H256L128 220.8L0 0h97.92L128 51.2L157.44 0h47.36Z"></path><path fill="#41B883" d="m0 0l128 220.8L256 0h-51.2L128 132.48L50.56 0H0Z"></path><path fill="#35495E" d="M50.56 0L128 133.12L204.8 0h-47.36L128 51.2L97.92 0H50.56Z"></path></svg>

+ 55 - 0
src/components/easyMap/func/location.ts

@@ -0,0 +1,55 @@
+import * as ol from 'ol'
+import * as style from 'ol/style'
+import * as layer from 'ol/layer'
+import * as source from 'ol/source'
+import * as geom from 'ol/geom'
+import * as proj from 'ol/proj'
+import * as interaction from 'ol/interaction'
+import * as coordinate from 'ol/coordinate'
+import * as format from "ol/format";
+
+const layerFlag = ['layerName', 'positionLayer']
+export default function Location ({map, position = null, wkt = null, zoom = null, color = '#039ff3'}) {
+    let _source = null
+    const realLayer = map.getLayers().getArray().filter(v => v.get(layerFlag[0]) === layerFlag[1])
+    if (realLayer[0]) {
+        _source = realLayer[0].getSource()
+        _source.clear()
+    } else {
+        _source = new source.Vector(); //图层数据源
+        const _vector = new layer.Vector({
+            zIndex: 9999,
+            source: _source,
+        });
+        _vector.set(layerFlag[0], layerFlag[1])
+        map.addLayer(_vector);
+    }
+    const feat = new format.WKT().readFeature(position ? `POINT(${position[0]} ${position[1]})` : wkt)
+    const radius = 25
+    const longRadius = radius * Math.SQRT2
+    feat.setStyle([new style.Style({ //图层样式
+        image: new style.RegularShape({
+            stroke: new style.Stroke({
+                color: color,
+                width: 2,
+                lineDash: [
+                    (longRadius * 3) / 10,
+                    (longRadius * 4) / 10,
+                    (longRadius * 3) / 10,
+                    0
+                ]
+            }),
+            radius1: radius,
+            rotation: Math.PI / (180 / 45),
+            points: 4
+        })
+    })])
+    _source.addFeature(feat)
+    setTimeout(() => {
+        feat?.getGeometry().setCoordinates([0, 0])
+    }, 3000)
+    map.getView().setCenter(feat?.getGeometry().getCoordinates())
+    if (zoom) {
+        map.getView().setZoom(zoom)
+    }
+}

+ 45 - 0
src/components/easyMap/func/measure.scss

@@ -0,0 +1,45 @@
+.tooltip {
+  position: relative;
+  background: rgba(0, 0, 0, 0.5);
+  border-radius: 4px;
+  color: white;
+  padding: 4px 8px;
+  white-space: nowrap;
+}
+
+.tooltip-measure {
+  opacity: 1;
+  font-weight: bold;
+}
+
+.tooltip-static {
+  background-color: #ffcc33;
+  color: black;
+  border: 1px solid white;
+}
+
+.tooltip-measure:before,
+.tooltip-static:before {
+  border-top: 6px solid rgba(0, 0, 0, 0.5);
+  border-right: 6px solid transparent;
+  border-left: 6px solid transparent;
+  content: "";
+  position: absolute;
+  bottom: -6px;
+  margin-left: -7px;
+  left: 50%;
+}
+
+.tooltip-static:before {
+  border-top-color: #ffcc33;
+}
+.lineDel {
+  width: 16px;
+  height: 16px;
+  display: inline-block;
+  vertical-align: middle;
+  margin-left: 10px;
+  cursor: pointer;
+  background: url('@/assets/images/map/lineDel.png') no-repeat;
+  background-size: 100% 100%;
+}

+ 262 - 0
src/components/easyMap/func/measure.ts

@@ -0,0 +1,262 @@
+import * as ol from 'ol'
+import * as style from 'ol/style'
+import * as layer from 'ol/layer'
+import * as source from 'ol/source'
+import * as geom from 'ol/geom'
+import * as proj from 'ol/proj'
+import * as interaction from 'ol/interaction'
+import * as coordinate from 'ol/coordinate'
+import * as control from 'ol/control'
+import * as sphere from 'ol/sphere'
+import { unByKey } from 'ol/Observable'
+import './measure.scss'
+import {createBox} from "ol/interaction/Draw";
+import {Circle, LineString, Polygon} from "ol/geom";
+
+const layerFlag = ['layerName', 'measureLayer']
+let measureTooltipElement;
+let helpTooltipElement;
+const typeMapper = new Map([
+    ['line', 'LineString'],
+    ['rectangle', 'LineString'],
+    ['polygon', 'Polygon'],
+    ['circle', 'Circle'],
+])
+/**
+ *
+ * @param map
+ * @param typeSelect    line线,rectangle矩形,polygon多边形,circle圆形
+ */
+export default function Measure (map, typeSelect) {
+    let _source = null
+    const realLayer = map.getLayers().getArray().filter(v => v.get(layerFlag[0]) === layerFlag[1])
+    if (realLayer[0]) {
+        _source = realLayer[0].getSource()
+    } else {
+        _source = new source.Vector(); //图层数据源
+        const _vector = new layer.Vector({
+            zIndex: 9999,
+            source: _source,
+            style: new style.Style({ //图层样式
+                fill: new style.Fill({
+                    color: 'rgba(255, 255, 255, 0.2)' //填充颜色
+                }),
+                stroke: new style.Stroke({
+                    color: '#f31a4a',  //边框颜色
+                    width: 2   // 边框宽度
+                }),
+                image: new style.Circle({
+                    radius: 7,
+                    fill: new style.Fill({
+                        color: '#ffcc33'
+                    })
+                })
+            })
+        });
+        _vector.set(layerFlag[0], layerFlag[1])
+        map.addLayer(_vector);
+    }
+    let sketch;
+    let helpTooltip;
+    let measureTooltip;
+    let continueMsg = '双击结束测量';
+    const geodesicCheckbox = true;//测地学方式对象
+    const createMeasureTooltip = () => {
+        const id = 'measureTooltipElementId'
+        if (measureTooltipElement) {
+            map.removeOverlay(map.getOverlayById(id))
+            measureTooltipElement.parentNode.removeChild(measureTooltipElement);
+        }
+        measureTooltipElement = document.createElement('div');
+        measureTooltipElement.className = 'tooltip tooltip-measure';
+        measureTooltip = new ol.Overlay({
+            id,
+            element: measureTooltipElement,
+            offset: [0, -15],
+            positioning: 'bottom-center'
+        });
+        map.addOverlay(measureTooltip);
+    }
+    const createHelpTooltip = () => {
+        const id = 'helpTooltipElementId'
+        if (helpTooltipElement) {
+            map.removeOverlay(map.getOverlayById(id))
+            helpTooltipElement.parentNode.removeChild(helpTooltipElement);
+        }
+        helpTooltipElement = document.createElement('div');
+        helpTooltipElement.className = 'tooltip hidden';
+        helpTooltip = new ol.Overlay({
+            id,
+            element: helpTooltipElement,
+            offset: [15, 0],
+            positioning: 'center-left'
+        });
+        map.addOverlay(helpTooltip);
+    }
+    const formatLength = (line) => {
+        // 获取投影坐标系
+        const sourceProj = map.getView().getProjection();
+        // ol/sphere里有getLength()和getArea()用来测量距离和区域面积,默认的投影坐标系是EPSG:3857, 其中有个options的参数,可以设置投影坐标系
+        const length = sphere.getLength(line, {projection: sourceProj});
+        // const length = getLength(line);
+        let output;
+        if (length > 100) {
+            const km = Math.round((length / 1000) * 100) / 100;
+            output = `${km} 千米 <br>${parseFloat(String(km * 0.53995)).toFixed(2)} 海里`;
+        } else {
+            output = `${Math.round(length * 100) / 100} m`;
+        }
+        return output;
+    };
+    //获取圆的面积
+    const getCircleArea = (circle, projection) => {
+        const P = 3.14
+        const radius = getCircleRadius(circle, projection)
+        return P * radius * radius
+    }
+//获取圆的半径
+    const getCircleRadius = (circle, projection) => {
+        return circle.getRadius() * projection.getMetersPerUnit()
+    }
+    const formatArea = (polygon, type= 'polygon') => {
+        let area
+        const sourceProj = map.getView().getProjection();
+        // 获取投影坐标系
+        if (type === 'polygon') {
+            area = sphere.getArea(polygon, {
+                projection: sourceProj,
+            });
+        } else if (type === 'circle') {
+            area = getCircleArea(polygon, sourceProj)
+        }
+        let output;
+        if (area > 10000) {
+            const km = Math.round((area / 1000000) * 100) / 100;
+            output = `${km} 平方公里<br>${parseFloat(String(km * 0.38610)).toFixed(
+                2
+            )} 平方英里`;
+        } else {
+            output = `${Math.round(area * 100) / 100} ` + " m<sup>2</sup>";
+        }
+        return output;
+    };
+    const addInteraction = () => {
+        const id = 'drawName'
+        const draw = new interaction.Draw({
+            source: _source,//测量绘制层数据源
+            type: typeMapper.get(typeSelect),  //几何图形类型
+            geometryFunction: typeSelect === 'rectangle' ? createBox() : null,
+            style: new style.Style({
+                fill: new style.Fill({
+                    color: "rgba(255, 255, 255, 0.2)",
+                }),
+                stroke: new style.Stroke({
+                    color: "#f3584a",
+                    width: 2,
+                }),
+                image: new style.Circle({
+                    radius: 5,
+                    stroke: new style.Stroke({
+                        color: "rgba(0, 0, 0, 0.7)",
+                    }),
+                    fill: new style.Fill({
+                        color: "rgba(255, 255, 255, 0.2)",
+                    }),
+                }),
+            }),
+        });
+        draw.set(id, id)
+        createMeasureTooltip(); //创建测量工具提示框
+        createHelpTooltip(); //创建帮助提示框
+        map.addInteraction(draw);
+        let listener;
+        //绑定交互绘制工具开始绘制的事件
+        const drawstartHandle = (evt) => {
+            sketch = evt.feature; //绘制的要素
+            let tooltipCoord = evt.coordinate;// 绘制的坐标
+            //绑定change事件,根据绘制几何类型得到测量长度值或面积值,并将其设置到测量工具提示框中显示
+            listener = sketch.getGeometry().on('change', function (evt) {
+                const geom = evt.target
+                let output;
+                if (geom.getType() === 'LineString') {
+                    output = formatLength(geom);//长度值
+                    tooltipCoord = geom.getLastCoordinate();//坐标
+                } else if (geom.getType() === 'Polygon') {
+                    output = formatArea(geom);//面积值
+                    tooltipCoord = geom.getInteriorPoint().getCoordinates();//坐标
+                } else if (geom.getType() === 'Circle') {
+                    output = formatArea(geom, 'circle');//面积值
+                    tooltipCoord = geom.getCenter()
+                }
+                measureTooltipElement.innerHTML = output;//将测量值设置到测量工具提示框中显示
+                measureTooltip.setPosition(tooltipCoord);//设置测量工具提示框的显示位置
+            });
+        }
+        draw.on('drawstart', drawstartHandle);
+        //绑定交互绘制工具结束绘制的事件
+        const copy = (value) => {
+            const str = document.createElement('input')
+            str.setAttribute('value', value)
+            document.body.appendChild(str)
+            str.select()
+            document.execCommand('copy')
+            document.body.removeChild(str)
+        }
+        const drawendHandle = (evt) => {
+            map.removeInteraction(map.getInteractions().getArray().filter(v => v.get(id) === id)[0]);
+            const del = document.createElement("div");
+            del.className = "lineDel";
+            measureTooltipElement.append(del);
+            del.onclick = () => {
+                _source.removeFeature(evt.feature)
+                const b = del.parentElement.parentElement
+                b.parentElement.removeChild(b);
+                const g = evt.feature.getGeometry()
+                if (g.getType() === 'LineString') {
+                    const w = `LINESTRING(${g.getCoordinates().map(v => v[0] + ' ' + v[1]).join(',')})`
+                    copy(w)
+                } else if (g.getType() === 'Polygon') {
+                    const w = `POLYGON(${g.getCoordinates().map(v => '(' + v.map(c => c[0] + ' ' + c[1]) + ')').join(',')})`
+                    copy(w)
+                }
+            };
+            measureTooltipElement.className = 'tooltip tooltip-static'; //设置测量提示框的样式
+            measureTooltip.setOffset([0, -7]);
+            sketch = null; //置空当前绘制的要素对象
+            measureTooltipElement = null; //置空测量工具提示框对象
+            helpTooltipElement.parentNode.removeChild(helpTooltipElement);
+            helpTooltipElement = null; //置空测量工具提示框对象
+            unByKey(listener);
+            draw.un('drawstart', drawstartHandle);
+            draw.un('drawend', drawendHandle);
+            map.removeInteraction(map.getInteractions().getArray().filter(v => v.get(id) === id)[0]);
+            map.un('pointermove', pointerMoveHandler)
+        }
+        draw.on('drawend', drawendHandle);
+    }
+    addInteraction(); //调用加载绘制交互控件方法,添加绘图进行测量
+    const pointerMoveHandler = (evt) => {
+        if (evt.dragging) {
+            return;
+        }
+        let helpMsg = '单击开始测量';//当前默认提示信息
+        //判断绘制几何类型设置相应的帮助提示信息
+        if (sketch) {
+            const geom = sketch.getGeometry()
+            helpMsg = continueMsg;
+            // if (geom.getType() === 'Polygon') {
+            //     helpMsg = continueMsg; //绘制多边形时提示相应内容
+            // } else if (geom.getType() === 'LineString') {
+            //     helpMsg = continueMsg; //绘制线时提示相应内容
+            // }
+        }
+        helpTooltipElement.innerHTML = helpMsg; //将提示信息设置到对话框中显示
+        helpTooltip.setPosition(evt.coordinate);//设置帮助提示框的位置
+        helpTooltipElement.classList.remove('hidden');//移除帮助提示框的隐藏样式进行显示
+    };
+    map.on('pointermove', pointerMoveHandler); //地图容器绑定鼠标移动事件,动态显示帮助提示框内容
+    //地图绑定鼠标移出事件,鼠标移出时为帮助提示框设置隐藏样式
+    map.getViewport().on('mouseout', () => {
+        helpTooltipElement.addClass('hidden');
+    });
+}

BIN
src/components/easyMap/images/bg-land.png


BIN
src/components/easyMap/images/bg-ocean.png


BIN
src/components/easyMap/images/bg-sky.png


File diff suppressed because it is too large
+ 12 - 0
src/components/easyMap/images/bg-switch.svg


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

@@ -0,0 +1,165 @@
+<template>
+  <div class="easy-map">
+    <OlMap
+      ref="ref_olMap"
+      :baseMapLayers="_baseMapLayers"
+      :baseMapView="_baseMapView"
+      @olZoomChange="(zoom) => $emit('zoomChange', zoom)"
+      @olMapLoad="(map) => handleOlMapLoad(map)"
+    />
+    <template v-if="showBaseSwitch">
+      <div class="base-switch">
+        <el-popover
+          placement="left"
+          trigger="hover"
+          popper-class="easy-map-base-switch"
+        >
+          <template #reference>
+            <img class="__hover" src="./images/bg-switch.svg" />
+          </template>
+          <div class="base-switch-item">
+            <template v-for="item in _baseMapLayers">
+              <div
+                class="base-item __hover"
+                :class="{
+                  active: judgeBaseLayerActive(
+                    item.get('_easyMapOl_layerName')
+                  ),
+                }"
+                @click="
+                  ref_olMap.switchBaseLayer(item.get('_easyMapOl_layerName'))
+                "
+              >
+                <div class="label">{{ item.get("_label") }}</div>
+                <img :src="item.get('_img')" />
+              </div>
+            </template>
+          </div>
+        </el-popover>
+      </div>
+    </template>
+  </div>
+</template>
+
+<script lang="ts">
+import {
+  defineComponent,
+  onMounted,
+  ref,
+  toRefs,
+  reactive,
+  watch,
+  getCurrentInstance,
+  ComponentInternalInstance,
+  computed,
+  nextTick,
+} from "vue";
+import { useStore } from "vuex";
+import OlMap from "./ol-map.vue";
+import InitMapInfoClass from "./initMapInfo";
+import MeasureFunc from "./func/measure";
+import LocationFunc from "./func/location";
+
+export default defineComponent({
+  name: "EasyMap",
+  components: {
+    OlMap,
+  },
+  props: {
+    baseMapLayers: {},
+    baseMapView: {},
+    showBaseSwitch: {
+      default: false,
+    },
+  },
+  setup(props, { emit }) {
+    const store = useStore();
+    const that = (getCurrentInstance() as ComponentInternalInstance).appContext
+      .config.globalProperties;
+    const state = reactive({});
+    const ref_olMap = ref();
+    const _baseMapLayers = computed(
+      () => props.baseMapLayers || InitMapInfoClass.baseMapLayers
+    );
+    const _baseMapView = computed(
+      () => props.baseMapView || InitMapInfoClass.baseMapView
+    );
+    const easyMap = computed(() => ref_olMap.value?.easyMapOl);
+    const handleOlMapLoad = (map) => {
+      emit("easyMapLoad", map, {
+        getBBOX,
+        measure,
+        toLocation,
+        resetCenter,
+        baseLayer: {
+          switchMapper: _baseMapLayers.value,
+          switchLayer: ref_olMap.value.switchBaseLayer,
+          judgeActive: judgeBaseLayerActive,
+        },
+      });
+    };
+    const getBBOX = () => {
+      return easyMap.value.getView().calculateExtent(easyMap.value.getSize());
+    };
+    const judgeBaseLayerActive = (layerName) => {
+      return easyMap.value
+        ?.getLayers()
+        .getArray()
+        .filter((v) => v.get("_easyMapOl_layerGroupType") === "base")[0]
+        .getLayers()
+        .getArray()
+        .filter((v) => v.get("_easyMapOl_layerName") === layerName)[0]
+        ?.getVisible();
+    };
+    const measure = (type) => {
+      MeasureFunc(easyMap.value, type);
+    };
+    const toLocation = ({ position = null, zoom = null, wkt = null }) => {
+      LocationFunc({
+        map: easyMap.value,
+        position,
+        wkt,
+        zoom,
+      });
+    };
+    const resetCenter = () => {
+      easyMap.value.getView().setCenter(_baseMapView.value.center);
+      easyMap.value.getView().setZoom(_baseMapView.value.zoom);
+    };
+    onMounted(() => {
+      nextTick(() => {});
+    });
+    return {
+      ...toRefs(state),
+      ref_olMap,
+      _baseMapLayers,
+      _baseMapView,
+      easyMap,
+      getBBOX,
+      measure,
+      toLocation,
+      handleOlMapLoad,
+      judgeBaseLayerActive,
+    };
+  },
+});
+</script>
+<style scoped lang="scss">
+.easy-map {
+  width: 100%;
+  height: 100%;
+  position: relative;
+  .base-switch {
+    position: absolute;
+    z-index: 2;
+    right: 40px;
+    bottom: 70px;
+    width: 26px;
+    height: 26px;
+    background-color: #f2f8fd;
+    border-radius: 4px;
+    display: grid;
+    place-items: center;
+  }
+}
+</style>

+ 87 - 0
src/components/easyMap/initMapInfo.ts

@@ -0,0 +1,87 @@
+import * as layer from 'ol/layer'
+import * as source from 'ol/source'
+import HaituImg from './images/bg-ocean.png'
+import LutuImg from './images/bg-land.png'
+import WeixingImg from './images/bg-sky.png'
+import store from '@/store/index'
+
+const baseMapView = {
+  center: [109.6915958479584, 19.111636735969318],
+  projection: "EPSG:4326",
+  zoom: 9
+  // extent: [120.8953306326286,31.3667480047968,121.37735577911297,31.692561298253832]
+}
+const initBaseLayer = (obj) => {
+  const _layer = new layer.Tile({
+    source: new source.XYZ({
+      projection: "EPSG:4326",
+      url: `/${store.state.app.apiProxy.EzServer6Api}/EzServer6/Maps/${obj.key}/EzMap?Service=getImage&Type=RGB&ZoomOffset=0&Col={x}&Row={y}&Zoom={z}&V=0.3`,
+    }),
+    visible: obj.visible,
+  })
+  _layer.set('_maxZoom', obj.maxZoom)
+  _layer.set('_minZoom', obj.minZoom)
+  _layer.set('_easyMapOl_layerName', obj.name)
+  _layer.set('_label', obj.label)
+  _layer.set('_img', obj.img)
+  return _layer
+}
+const initLocalhost = (obj) => {
+  const _layer = new layer.Tile({
+    source: new source.XYZ({
+      url: 'http://wprd0{1-4}.is.autonavi.com/appmaptile?lang=zh_cn&size=1&style=7&x={x}&y={y}&z={z}'
+    }),
+    visible: obj.visible,
+  })
+  _layer.set('_maxZoom', obj.maxZoom)
+  _layer.set('_minZoom', obj.minZoom)
+  _layer.set('_easyMapOl_layerName', obj.name)
+  _layer.set('_label', obj.label)
+  _layer.set('_img', obj.img)
+  return _layer
+}
+
+const baseMapLayers = [
+  initBaseLayer({
+    key: 'sea',
+    name: 'base_haitu',
+    label: '海图',
+    maxZoom: 14,
+    minZoom: 5,
+    visible: false,
+    img: HaituImg
+  }),
+  !window.location.origin.includes('localhost')
+    ? initBaseLayer({
+      key: 'tdtsl',
+      name: 'base_tianditu',
+      label: '陆图',
+      maxZoom: 20,
+      minZoom: 8,
+      visible: true,
+      img: LutuImg
+    })
+  : initLocalhost({
+      key: 'tdtsl',
+      name: 'base_tianditu',
+      label: '陆图',
+      maxZoom: 20,
+      minZoom: 8,
+      visible: true,
+      img: LutuImg
+    }),
+  initBaseLayer({
+    key: 'hnimg',
+    name: 'base_weixingtu',
+    label: '卫星遥感图',
+    maxZoom: 19,
+    minZoom: 8,
+    visible: false,
+    img: WeixingImg
+  }),
+]
+
+export default {
+  baseMapView,
+  baseMapLayers,
+}

+ 296 - 0
src/components/easyMap/ol-map.vue

@@ -0,0 +1,296 @@
+<template>
+  <div class="easy-map-ol">
+    <div class="map" ref="ref_easyMapOl"/>
+    <div class="easy-map_ol-mouse-position" ref="ref_easyMap_olMousePosition" @click="controlMousePosition.format = !controlMousePosition.format">
+      <template v-if="controlMousePosition.format">
+        {{controlMousePosition.formatLongitude}}<br/>
+        {{controlMousePosition.formatLatitude}}
+      </template>
+      <template v-else>
+        {{controlMousePosition.longitude}}<br/>
+        {{controlMousePosition.latitude}}
+      </template>
+    </div>
+    <div class="easy-map_ol-zoom" ref="ref_easyMap_olZoom">
+      <div class="easy-map_ol-zoom-button" @click="zoomChange(true)">+</div>
+      <div class="easy-map_ol-zoom-num">{{Math.floor(interactionZoom)}}</div>
+      <div class="easy-map_ol-zoom-button" @click="zoomChange(false)">-</div>
+    </div>
+    <div class="easy-map_ol-scaleLine" ref="ref_easyMap_scaleLine"></div>
+  </div>
+</template>
+
+<script lang="ts">
+  import {
+    defineComponent,
+    onMounted,
+    ref,
+    toRefs,
+    reactive,
+    watch,
+    getCurrentInstance,
+    ComponentInternalInstance,
+    computed,
+    nextTick,
+  } from "vue";
+  import { useStore } from "vuex";
+  import * as ol from 'ol'
+  import * as style from 'ol/style'
+  import * as layer from 'ol/layer'
+  import * as source from 'ol/source'
+  import * as geom from 'ol/geom'
+  import * as proj from 'ol/proj'
+  import * as interaction from 'ol/interaction'
+  import * as coordinate from 'ol/coordinate'
+  import * as control from 'ol/control'
+
+  export default defineComponent({
+    name: "",
+    components: {
+    },
+    props: {
+      baseMapLayers: { default: () => [] },
+      baseMapView: {},
+    },
+    setup(props, { emit }) {
+      const store = useStore();
+      const that = (getCurrentInstance() as ComponentInternalInstance).appContext.config.globalProperties;
+      const state = reactive({
+        controlMousePosition: {
+          longitude: <any>null,
+          latitude: <any>null,
+          format: false,
+          formatLongitude: <any>null,
+          formatLatitude: <any>null,
+        },
+        interactionZoom: props.baseMapView.zoom,
+      });
+      const easyMapOl = ref()
+      const ref_easyMapOl = ref()
+      const ref_easyMap_olMousePosition = ref()
+      const ref_easyMap_olZoom = ref()
+      const ref_easyMap_scaleLine = ref()
+      const initMap = () => {
+        easyMapOl.value = new ol.Map({
+          target: ref_easyMapOl.value,
+          layers: [
+            new layer.Group({
+              _easyMapOl_layerGroupType: 'base',
+              layers: props.baseMapLayers,
+              zIndex: 1
+            }),
+          ],
+          view: new ol.View(props.baseMapView),
+          controls: control.defaults({
+            attribution: false,
+            rotate: false,
+            zoom: false,
+          }).extend([
+            new control.MousePosition({
+              target: ref_easyMap_olMousePosition.value,
+              coordinateFormat: (e) => {
+                state.controlMousePosition.longitude = e[0]
+                state.controlMousePosition.latitude = e[1]
+                const f = coordinate.toStringHDMS(e, 0).split(' ')
+                state.controlMousePosition.formatLatitude = `${f[0]} ${f[1]} ${f[2]} ${f[3]}`
+                state.controlMousePosition.formatLongitude = `${f[4]} ${f[5]} ${f[6]} ${f[7]}`
+                return null
+              },
+              placeholder: ''
+            }),
+            new control.Zoom({
+              target: ref_easyMap_olZoom.value,
+            }),
+            new control.ScaleLine({
+              target: ref_easyMap_scaleLine.value,
+              bar: true
+            })
+          ]),
+          interactions: interaction.defaults({
+            doubleClickZoom: false
+          })
+        })
+        easyMapOl.value.getView().on('change:resolution', e => {
+          state.interactionZoom = e.target.getZoom()
+          emit('olZoomChange', state.interactionZoom)
+        })
+        const defaultBaseLayer = props.baseMapLayers.filter(v => v.getVisible())[0]
+        setLayerView(defaultBaseLayer)
+        emit('olMapLoad', easyMapOl.value)
+        easyMapOl.value.on('contextmenu', e => {
+          window.event.returnValue = false
+          if (window?.event?.shiftKey) {
+            const str = document.createElement('input')
+            str.setAttribute('value', `POINT(${e.coordinate[0]} ${e.coordinate[1]})`)
+            document.body.appendChild(str)
+            str.select()
+            document.execCommand('copy')
+            document.body.removeChild(str)
+          }
+        })
+      }
+      const zoomChange = (flag) => {
+        state.interactionZoom = flag ? state.interactionZoom + 1 : state.interactionZoom - 1
+        if (state.interactionZoom > easyMapOl.value.getView().getMaxZoom()) {
+          state.interactionZoom = easyMapOl.value.getView().getMaxZoom()
+        } else if (state.interactionZoom < easyMapOl.value.getView().getMinZoom()) {
+          state.interactionZoom = easyMapOl.value.getView().getMinZoom()
+        }
+        easyMapOl.value.getView().setZoom(state.interactionZoom)
+      }
+      const baseLayersMap = computed(() => {
+        const map = new Map()
+        easyMapOl.value?.getLayers().getArray().filter(v => v.get('_easyMapOl_layerGroupType') === 'base')[0].getLayers().getArray().forEach(v => {
+          map.set(v.get('_easyMapOl_layerName'), v)
+        })
+        return map
+      })
+      const switchBaseLayer = (layerName) => {
+        baseLayersMap.value.forEach(v => {
+          if (layerName === v.get('_easyMapOl_layerName')) {
+            setLayerView(v)
+            v.setVisible(true)
+          } else {
+            v.setVisible(false)
+          }
+        })
+      }
+      const setLayerView = (_layer) => {
+        easyMapOl.value.getView().setMaxZoom(_layer.get('_maxZoom'))
+        easyMapOl.value.getView().setMinZoom(_layer.get('_minZoom'))
+      }
+      onMounted(() => {
+        nextTick(() => {
+          initMap()
+          setTimeout(() => {
+            easyMapOl.value.updateSize();
+            easyMapOl.value.render()
+          })
+        })
+      })
+      return {
+        ...toRefs(state),
+        ref_easyMapOl,
+        ref_easyMap_olMousePosition,
+        ref_easyMap_olZoom,
+        ref_easyMap_scaleLine,
+        easyMapOl,
+        zoomChange,
+        switchBaseLayer,
+        baseLayersMap,
+      }
+    },
+  });
+</script>
+<style scoped lang="scss">
+.easy-map-ol {
+  width: 100%;
+  height: 100%;
+  position: relative;
+  .map {
+    width: 100%;
+    height: 100%;
+    background-color: #bfdbf3;
+  }
+  .easy-map_ol-mouse-position {
+    cursor: pointer;
+    position: absolute;
+    z-index: 1;
+    color: #000;
+    top: unset;
+    font-size: 14px;
+    right: 10px;
+    bottom: 10px;
+    height: 50px;
+    padding: 5px 10px;
+    background-color: #fff;
+    border-radius: 2px;
+    line-height: 22px;
+    opacity: .8;
+  }
+  .easy-map_ol-zoom {
+    position: absolute;
+    bottom: 70px;
+    right: 10px;
+    >div {
+      display: flex;
+      align-items: center;
+      justify-content: center;
+      margin: 0;
+      padding: 0;
+      color: #000;
+      font-size: 12px;
+      font-weight: 700;
+      height: 20px;
+      width: 20px;
+      background-color: #fff;
+    }
+    .easy-map_ol-zoom-num {
+      border-top: 1px solid rgba(153, 153, 153, 0.32);
+      border-bottom: 1px solid rgba(153, 153, 153, 0.32);
+    }
+    .easy-map_ol-zoom-button {
+      cursor: pointer;
+      &:hover {
+        opacity: 0.7;
+      }
+    }
+  }
+  .easy-map_ol-scaleLine {
+    position: absolute;
+    bottom: 20px;
+    left: 20px;
+  }
+  .easy-map_switchBaseLayer {
+    position: absolute;
+    left: 20px;
+    bottom: 40px;
+  }
+  ::v-deep(.ol-zoom) {
+    display: none;
+  }
+  ::v-deep(.ol-scale-bar-inner) {
+    position: absolute;
+    bottom: 1%;
+    left: 1%;
+
+    &>div>div:nth-child(2) {
+      .ol-scale-singlebar {
+        border-left: 2px solid #807A7A;
+      }
+    }
+
+    &>div>div:nth-child(5) {
+      .ol-scale-singlebar {
+        border-right: 2px solid #807A7A;
+      }
+    }
+
+    .ol-scale-step-text {
+      padding-bottom: 20px;
+      font-size: 12px;
+      transform: scale(0.8);
+      position: absolute;
+      bottom: -5px;
+      font-size: 12px;
+      z-index: 11;
+      color: #000000;
+      text-shadow: -2px 0 #ffffff,
+      0 2px #ffffff,
+      2px 0 #ffffff,
+      0 -2px #ffffff;
+    }
+
+    .ol-scale-singlebar {
+      border: 0;
+      background-color: transparent !important;
+      border-bottom: 2px solid #807A7A;
+      height: 10px;
+    }
+
+    .ol-scale-step-marker {
+      display: none;
+    }
+  }
+}
+</style>

+ 19 - 0
src/main.ts

@@ -0,0 +1,19 @@
+import { createApp } from 'vue'
+import App from './App.vue'
+import router from './router'
+import store from "./store"
+import ElementPlus from 'element-plus'
+import 'element-plus/dist/index.css'
+// @ts-ignore
+import EasyMapComponent from '@/components/easyMap/index.vue'
+import './style/cus-element.scss'
+import * as easyMap from '@/utils/easyMap.ts'
+import * as util from '@/utils/util.ts'
+const app = createApp(App)
+app.use(router)
+app.use(store)
+app.use(ElementPlus)
+app.config.globalProperties.$util = util
+app.config.globalProperties.$easyMap = easyMap
+app.component('EasyMapComponent', EasyMapComponent)
+app.mount('#app')

+ 14 - 0
src/router/index.ts

@@ -0,0 +1,14 @@
+import { RouteRecordRaw, createRouter, createWebHistory } from 'vue-router'
+const routes = [
+    {
+        path: '/',
+        component: () => import('@/views/init-speed-track/index.vue'),
+    }
+]
+
+const router = createRouter({
+  history: createWebHistory(),
+  routes,
+});
+
+export default router;

+ 11 - 0
src/store/index.ts

@@ -0,0 +1,11 @@
+import { createStore } from "vuex";
+import app from "./modules/app";
+
+export default createStore({
+  state: {},
+  mutations: {},
+  actions: {},
+  modules: {
+    app,
+  },
+});

+ 18 - 0
src/store/modules/app.ts

@@ -0,0 +1,18 @@
+
+const state = {
+	apiProxy: {
+		EzServer6Api: 'EzServer6-api',	// 地图底图代理
+	},
+}
+const mutations = {
+}
+
+const actions = {
+}
+
+export default {
+	namespaced: true,
+	state,
+	mutations,
+	actions
+}

+ 49 - 0
src/style/cus-element.scss

@@ -0,0 +1,49 @@
+.easy-map-base-switch {
+  width: auto !important;
+  min-width: auto !important;
+  padding: 0 !important;
+  border: none !important;
+
+  .base-switch-item {
+    background: #f2f8fd;
+    border-radius: 6px;
+    opacity: .9;
+    display: flex;
+
+    .base-item {
+      flex: 0 0 auto;
+      width: 115px;
+      height: 70px;
+      position: relative;
+      margin: 10px 0 10px 10px;
+
+      &:last-child {
+        margin-right: 10px;
+      }
+
+      .label {
+        position: absolute;
+        right: 0;
+        bottom: 0;
+        padding: 0 3px;
+        font-size: 14px;
+        background: rgba(0, 0, 0, .3);
+        color: #fff;
+      }
+
+      >img {
+        width: 100%;
+        height: 100%;
+      }
+
+      &:hover,
+      &.active {
+        border: 2px solid #255fef;
+
+        .label {
+          background-color: #255fef;
+        }
+      }
+    }
+  }
+}

+ 391 - 0
src/utils/easyMap.ts

@@ -0,0 +1,391 @@
+import * as ol from 'ol'
+import * as style from 'ol/style'
+import * as layer from 'ol/layer'
+import * as source from 'ol/source'
+import * as geom from 'ol/geom'
+import * as proj from 'ol/proj'
+import * as format from 'ol/format'
+import * as extent from 'ol/extent'
+import store from '@/store/index'
+
+let hasPointClick = false
+let zIndex = 999
+let ctrlHideListGlobal = []
+const activeFeatureMap = new Map()
+const hoverFeatureMap = new Map()
+let moveFlag = false
+
+export const initShape = ({map, layerName, list, clickEl, hoverEl, hoverClick = false, clickHandle = (feature: any) => {}, layerZIndex}) => {
+  const _layers = map.getLayers().getArray().filter(v => v.get('easyMapLayerName') === layerName)
+  let realLayer = _layers.length > 0 ? _layers[0] : null
+  const features = []
+  const featuresMap = new Map()
+  list.forEach((v, i) => {
+    try {
+      const feat = new format.WKT().readFeature(v.easyMapParams.position)
+      feat.set('layerName', layerName)
+      feat.set('easyMap', v)
+      feat.set('_geom', feat.getGeometry())
+      feat.setStyle(v.easyMapParams.normalStyle)
+      feat.set('normalStyle', v.easyMapParams.normalStyle)
+      feat.set('activeStyle', v.easyMapParams.activeStyle ? v.easyMapParams.activeStyle : v.easyMapParams.normalStyle)
+      feat.set('hoverStyle', v.easyMapParams.hoverStyle ? v.easyMapParams.hoverStyle : (v.easyMapParams.activeStyle ? v.easyMapParams.activeStyle : v.easyMapParams.normalStyle))
+      feat.set('backStyle', v.easyMapParams.normalStyle)
+      feat.set('layerZ', realLayer ? realLayer.getZIndex() : layerZIndex ? layerZIndex : zIndex)
+      feat.set('featureZ', i)
+      feat.set('value', v.value)
+      feat.setId(v.easyMapParams.id)
+      v.easyMapParams.featureSetHandle?.(feat)
+      features.push(feat)
+      featuresMap.set(v.easyMapParams.id, feat)
+    } catch (e) {
+      console.log('v:\n%o  e:\n%o', v, e)
+    }
+  })
+  const vectorSource = new source.Vector({
+    features: features,
+    wrapX: false
+  });
+  let clickDialog = clickEl ? map.getOverlayById(clickEl.id) : null
+  let hoverDialog = hoverEl ? map.getOverlayById(hoverEl.id) : null
+  const clickClose = () => {
+    clickDialog?.setPosition(undefined)
+  }
+  const hoverClose = () => {
+    hoverDialog?.setPosition(undefined)
+  }
+  const setActive = (feature, isClick = false, e = null) => {
+    if (feature) {
+      feature.set('backStyle', feature.getStyle())
+      feature.setStyle(hoverFeatureMap.get(layerName)?.getId() === feature.getId() ? feature.get('hoverStyle') : feature.get('activeStyle'))
+      activeFeatureMap.set(layerName, feature)
+      store.dispatch('easyMap/LOAD_INFO', {
+        type: 'click',
+        data: {
+          layerName: layerName,
+          value: feature.get('easyMap'),
+          feature: feature,
+          isClick,
+          e
+        }
+      })
+    }
+  }
+  const setHover = (feature) => {
+    if (feature) {
+      if (hoverDialog && !(hoverClick || activeFeatureMap.get(layerName)?.getId() !== hoverFeatureMap.get(layerName)?.getId())) {
+        hoverClose()
+      }
+      feature.set('backStyle', feature.getStyle())
+      feature.setStyle(feature.get('hoverStyle'))
+      hoverFeatureMap.set(layerName, feature)
+      store.dispatch('easyMap/LOAD_INFO', {
+        type: 'hover',
+        data: {
+          layerName: layerName,
+          value: feature.get('easyMap'),
+          feature: feature
+        }
+      })
+    }
+  }
+  if (hoverFeatureMap.get(layerName)) {
+    hoverFeatureMap.set(layerName, vectorSource.getFeatureById(hoverFeatureMap.get(layerName).getId()))
+    setHover(hoverFeatureMap.get(layerName))
+  } else {
+    hoverFeatureMap.set(layerName, null)
+  }
+  if (activeFeatureMap.get(layerName)) {
+    activeFeatureMap.set(layerName, vectorSource.getFeatureById(activeFeatureMap.get(layerName).getId()))
+    setActive(activeFeatureMap.get(layerName))
+  } else {
+    activeFeatureMap.set(layerName, null)
+  }
+    if (realLayer) {
+    realLayer.setSource(vectorSource)
+  } else {
+    realLayer = new layer.VectorImage({
+      source: vectorSource,
+      easyMapLayerName: layerName,
+      layerType: 'easyMap',
+      zIndex: layerZIndex ? layerZIndex : zIndex--
+    })
+    map.addLayer(realLayer)
+    map.on('movestart', e => {
+      moveFlag = true
+      map.un('pointermove', mapPointerMove)
+      map.un('singleclick', mapSingleClick)
+      map.un('contextmenu', mapContextmenu)
+    })
+    map.on('moveend', e => {
+      map.on('singleclick', mapSingleClick)
+      map.on('pointermove', mapPointerMove)
+      map.on('contextmenu', mapContextmenu)
+      moveFlag = false
+    })
+    if (clickEl) {
+      clickDialog = setOverlay(clickEl)
+      map.addOverlay(clickDialog)
+    }
+    const mapSingleClick = e => {
+      let pointFlag = true
+      clickDialog?.setPosition(undefined)
+      // activeFeatureMap.get(layerName)?.setStyle(activeFeatureMap.get(layerName).get('normalStyle'))
+      let activeLayer = {
+        zIndex: 0,
+        layerName: null,
+      }
+      map.forEachFeatureAtPixel(e.pixel, (feature) => {
+        if (feature.get('layerZ') >= activeLayer.zIndex) {
+          activeLayer = {
+            zIndex: feature.get('layerZ'),
+            layerName: feature.get('layerName'),
+          }
+        }
+      }, {
+        hitTolerance: 0,
+      });
+      map.forEachFeatureAtPixel(e.pixel, (feature) => {
+        if (activeLayer.layerName === layerName && feature.get('layerName') === layerName && !hasPointClick) {
+          if (pointFlag) {
+            hasPointClick = true
+            if (activeFeatureMap.get(layerName)) {
+              activeFeatureMap.get(layerName).set('backStyle', activeFeatureMap.get(layerName).getStyle())
+              activeFeatureMap.get(layerName).setStyle(feature.get('normalStyle'))
+            }
+            pointFlag = false
+            if (!hoverClick) {
+              hoverClose()
+              hoverFeatureMap.delete(layerName)
+            }
+            setActive(feature, true, e)
+            clickHandle(feature)
+            if (clickDialog) {
+              if (feature.getGeometry().getType() !== 'Point') {
+                clickDialog.setPosition(e.coordinate)
+              } else {
+                clickDialog.setPosition(feature.getGeometry().getCoordinates())
+              }
+            }
+            setTimeout(() => {
+              hasPointClick = false
+            }, 100)
+          }
+        }
+      }, {
+        hitTolerance: 0,
+      });
+    }
+    map.on('singleclick', mapSingleClick)
+    if (hoverEl) {
+      hoverDialog = setOverlay(hoverEl)
+      map.addOverlay(hoverDialog)
+    }
+    let pointerMoveTime = null
+    const mapPointerMove = e => {
+      clearTimeout(pointerMoveTime)
+      pointerMoveTime = setTimeout(() => {
+        if (!moveFlag) {
+          let isFeature = false
+          let hoverLayer = {
+            zIndex: 0,
+            layerName: null
+          }
+          map.forEachFeatureAtPixel(e.pixel, (feature) => {
+            if (feature.get('layerZ') > hoverLayer.zIndex) {
+              hoverLayer = {
+                zIndex: feature.get('layerZ'),
+                layerName: feature.get('layerName')
+              }
+            }
+            isFeature = true
+          }, {
+            hitTolerance: 0,
+          });
+          const reset = () => {
+            hoverDialog?.setPosition(undefined)
+            hoverFeatureMap.get(layerName)?.setStyle(hoverFeatureMap.get(layerName).get('backStyle'))
+            hoverFeatureMap.delete(layerName)
+          }
+          if (layerName !== hoverLayer.layerName) {
+            reset()
+          }
+          if (!isFeature) {
+            reset()
+          }
+          let pointFlag = true
+          map.forEachFeatureAtPixel(e.pixel, (feature) => {
+            if (feature.get('layerName') === layerName) {
+              if (pointFlag) {
+                if (layerName !== hoverLayer.layerName) {
+                  hoverDialog?.setPosition(undefined)
+                } else {
+                  pointFlag = false
+                  if (hoverFeatureMap.get(layerName)?.getId() !== feature.getId()) {
+                    if (feature.getId() !== hoverFeatureMap.get(layerName)?.getId()) {
+                      reset()
+                    }
+                    setHover(feature)
+                    if (hoverDialog && (hoverClick || activeFeatureMap.get(layerName)?.getId() !== hoverFeatureMap.get(layerName)?.getId())) {
+                      if (feature.getGeometry().getType() !== 'Point') {
+                        hoverDialog.setPosition(e.coordinate)
+                      } else {
+                        hoverDialog.setPosition(feature.getGeometry().getCoordinates())
+                      }
+                    }
+                  }
+                }
+              }
+            }
+          }, {
+            hitTolerance: 0,
+          });
+        }
+      }, 10)
+    }
+    map.on('pointermove', mapPointerMove)
+    let ctrlHideList = []
+    const mapContextmenu = e => {
+      let isFeature = false
+      let hoverLayer = {
+        zIndex: 0,
+        layerName: null
+      }
+      map.forEachFeatureAtPixel(e.pixel, (feature) => {
+        if (feature.get('layerZ') > hoverLayer.zIndex) {
+          hoverLayer = {
+            zIndex: feature.get('layerZ'),
+            layerName: feature.get('layerName')
+          }
+        }
+        isFeature = true
+      }, {
+        hitTolerance: 0,
+      });
+      if (ctrlHideList.length > 0 && !window?.event?.ctrlKey && !window?.event.altKey) {
+        if (ctrlHideListGlobal[ctrlHideListGlobal.length - 1] === ctrlHideList[ctrlHideList.length - 1]) {
+          setTimeout(() => {
+            switchVisible([ctrlHideList.pop()], true)
+            ctrlHideListGlobal.pop()
+          }, 100)
+        }
+      } else if (ctrlHideList.length > 0 && window?.event.altKey) {
+        switchVisible(ctrlHideList, true)
+        ctrlHideList = []
+        ctrlHideListGlobal = []
+      }
+      let pointFlag = true
+      map.forEachFeatureAtPixel(e.pixel, (feature) => {
+        if (feature.get('layerName') === layerName) {
+          if (pointFlag) {
+            if (feature.get('layerName') === hoverLayer.layerName) {
+              pointFlag = false
+              if (window?.event?.ctrlKey) {
+                switchVisible([feature.get('easyMap').easyMapParams.id], false)
+                ctrlHideList.push(feature.get('easyMap').easyMapParams.id)
+                ctrlHideListGlobal.push(feature.get('easyMap').easyMapParams.id)
+              }
+            }
+          }
+        }
+      }, {
+        hitTolerance: 0,
+      });
+    }
+    map.on('contextmenu', mapContextmenu)
+  }
+  const switchVisible = (arr, show) => {
+    arr.forEach(v => {
+      const feat = featuresMap.get(v)
+      if (show) {
+        feat.setGeometry(feat.get('_geom'))
+      } else {
+        if (feat.getGeometry().getCoordinates().toString() !== [0, 0].toString()) {
+          feat.setGeometry(new geom.Point([0, 0]))
+        }
+      }
+    })
+  }
+  const removeRealLayer = () => {
+    map.removeOverlay(clickDialog)
+    map.removeOverlay(hoverDialog)
+    map.removeLayer(realLayer)
+  }
+  return {
+    clickDialog, hoverDialog, clickClose, hoverClose, switchVisible, features, realLayer, removeRealLayer, setActive
+  }
+}
+
+const setOverlay = (el) => {
+  const over = new ol.Overlay({
+    id: el.id,
+    element: el.element,
+    autoPan: false,
+    offset: el.offset,
+    positioning: el.positioning,
+    stopEvent: true,
+    autoPanAnimation: {
+      duration: 250
+    }
+  })
+  return over
+}
+
+/**
+ *
+ * @param map         地图实例,传了直接定位到中心点最大可视范围,不传返回中心点和最大分辨率
+ * @param position    坐标数组
+ * @param L           缩放比例系数,数值越小,要素边界越靠近中心
+ */
+export const getShapeView = (map, position, L = 600) => {
+  const center = extent.getCenter(extent.boundingExtent(position))
+  let x = 0
+  let y = 0
+  position.forEach(v => {
+    if (Math.abs(v[0] - center[0]) > x) {
+      x = Math.abs(v[0] - center[0])
+    }
+    if (Math.abs(v[1] - center[1]) > y) {
+      y = Math.abs(v[1] - center[1])
+    }
+  })
+  const resolution = Math.sqrt(Math.pow(x, 2) + Math.pow(y, 2)) / (L / document.body.clientWidth * document.body.clientHeight)
+  if (map) {
+    map.getView().animate({
+      center, resolution
+    })
+  }
+  return {
+    center, resolution
+  }
+}
+
+export const formatPosition = {
+  wptTwl: (arr) => {  // WKT POINT ARR TO WKT LINESTRING
+    const temp = arr.map(v => new format.WKT().readFeature(v).getGeometry().getCoordinates())
+    return new format.WKT().writeGeometry(new geom.LineString(proj.fromLonLat(temp, 'EPSG:4326')), {
+      dataProjection: 'EPSG:4326'
+    })
+  },
+  cptTwpt: (cpt) => {  // coordinates POINT TO WKT POINT
+    return `POINT(${cpt[0]} ${cpt[1]})`
+  },
+  wptTcpt: (wpt) => {  // WKT POINT TO coordinates POINT
+    return new format.WKT().readFeature(wpt).getGeometry().getCoordinates()
+  },
+  wpnTcpn: (wpn) => {  // WKT POLYGON TO coordinates POLYGON
+    return new format.WKT().readFeature(wpn).getGeometry().getCoordinates()
+  },
+  cpnTwpn: (cpn) => {  // coordinates POLYGON TO WKT POLYGON
+    return `POLYGON(${cpn.map(v => '(' + v.map(c => `${c[0]} ${c[1]}`).join(',') + ')')})`
+  },
+  cmpnTwmpn: (cmpt) => {  // coordinates MULTIPOLYGON TO WKT MULTIPOLYGON
+    return `MULTIPOLYGON(${cmpt.map(v1 => `(${v1.map(v => '(' + v.map(c => `${c[0]} ${c[1]}`).join(',') + ')')})`)})`
+  },
+  wmpnTcmpn: (wmpn) => {  // WKT MULTIPOLYGON TO coordinates MULTIPOLYGON
+    return new format.WKT().readFeature(wmpn).getGeometry().getCoordinates()
+  },
+  wlTcl: (wl) => {  // WKT LINESTRING TO coordinates LINESTRING
+    return new format.WKT().readFeature(wl).getGeometry().getCoordinates()
+  }
+}

+ 281 - 0
src/utils/util.ts

@@ -0,0 +1,281 @@
+export const isValue = (val: any) => {
+  if (val === null || val === undefined || val === '') {
+    return false
+  }
+  return true
+}
+
+export const structureParams = (array: Array<any>, attribute: string) => {
+  const endArray: any[] = []
+  array.forEach(v => {
+    endArray.push(v[attribute])
+  })
+  return endArray
+}
+
+export const replaceParams = (array: Array<any>, reArray: Array<any>, attribute: string, reAttribute: string) => {
+  const endArray: any[] = []
+  const endAllArray: any[] = []
+  array.forEach(v => {
+    reArray.forEach(rv => {
+      if (v === rv[attribute]) {
+        endArray.push(rv[reAttribute])
+        endAllArray.push(rv)
+      }
+    })
+  })
+  return {
+    replace: endArray,
+    all: endAllArray
+  }
+}
+
+export const copyObject = (ob: Object) => {
+  return JSON.parse(JSON.stringify(ob))
+}
+
+export const arrayToMap = (array: Array<any>, key: any) => {
+  const map = new Map()
+  array.forEach((v: any) => {
+    map.set(v[key], v)
+  })
+  return map
+}
+
+/**
+ * 通过某个字段在一个多级数组中查找数据
+ * @param data 目标数组,不能包含函数
+ * @param current 目标数据
+ * @param key 查找的字段
+ * @param children 子集集合字段
+ */
+export const findInListData = (data: Array<any>, current:any, key = "id", children = 'children') => {
+
+  for(let item of data){
+
+    if(item[key] && JSON.parse(JSON.stringify(item[key])) == JSON.parse(JSON.stringify(current))) return item
+
+    if(!!item[children] && Array.isArray(item[children]) && item[children].length > 0){
+
+      const findChildData = findInListData(item[children], current, key, children)
+
+      if(findChildData) return findChildData
+
+    }
+
+  }
+
+  return null
+}
+
+export const formatGetParam = (params: Object) => {
+  let paramUrl = ''
+  Object.keys(params).forEach((v, i) => {
+    paramUrl += i === 0 ? `${v}=${encodeURIComponent(params[v])}` : `&${v}=${encodeURIComponent(params[v])}`
+  })
+  return paramUrl
+}
+
+export const formatTableHeadFilters = (arr, text = 'dictLabel', value = 'dictValue') => {
+  return arr.map(v => {
+    v.value = v[value]
+    v.text = v[text]
+    return v
+  })
+}
+
+export const YMDHms = (date) => {
+  const _date = new Date(date)
+  const Y = `${_date.getFullYear()}`;
+  const M = `${_date.getMonth() + 1 < 10 ? `0${_date.getMonth() + 1}` : _date.getMonth() + 1}`;
+  const D = `${_date.getDate() + 1 < 10 ? `0${_date.getDate()}` : _date.getDate()}`;
+  const H = `${_date.getHours() < 10 ? `0${_date.getHours()}` : _date.getHours()}`;
+  const m = `${_date.getMinutes() < 10 ? `0${_date.getMinutes()}` : _date.getMinutes()}`;
+  const s = _date.getSeconds() < 10 ? `0${_date.getSeconds()}` : _date.getSeconds();
+  return `${Y}-${M}-${D} ${H}:${m}:${s}`;
+}
+
+export const YMD = (date) => {
+  const _date = new Date(date)
+  const Y = `${_date.getFullYear()}`;
+  const M = `${_date.getMonth() + 1 < 10 ? `0${_date.getMonth() + 1}` : _date.getMonth() + 1}`;
+  const D = `${_date.getDate() + 1 < 10 ? `0${_date.getDate()}` : _date.getDate()}`;
+  return `${Y}-${M}-${D}`;
+}
+
+//防抖
+export const debounce = function (cb, ms = 0) {
+  let timer = null
+  return function () {
+    if (timer) clearTimeout(timer)
+    timer = setTimeout(() => {
+      cb.apply(this, arguments)
+      timer = null
+    }, ms)
+  }
+}
+
+export const comTime = (time) => {
+  const sAll = time
+  const d = Math.floor(sAll / (1000 * 60 * 60 * 24))
+  const h = Math.floor((sAll - d * (1000 * 60 * 60 * 24)) / (1000 * 60 * 60))
+  const m = Math.floor((sAll - d * (1000 * 60 * 60 * 24) - h * (1000 * 60 * 60)) / (1000 * 60))
+  const s = Math.floor((sAll - d * (1000 * 60 * 60 * 24) - h * (1000 * 60 * 60) - m * (1000 * 60)) / 1000)
+  return{
+    d, h, m ,s
+  }
+}
+
+export const comTimeByArea = (start, end) => {
+  const sAll = new Date(end).getTime() - new Date(start).getTime()
+  const d = Math.floor(sAll / (1000 * 60 * 60 * 24))
+  const h = Math.floor((sAll - d * (1000 * 60 * 60 * 24)) / (1000 * 60 * 60))
+  const m = Math.floor((sAll - d * (1000 * 60 * 60 * 24) - h * (1000 * 60 * 60)) / (1000 * 60))
+  const s = Math.floor((sAll - d * (1000 * 60 * 60 * 24) - h * (1000 * 60 * 60) - m * (1000 * 60)) / 1000)
+  return{
+    d, h, m ,s
+  }
+}
+
+export const deepAssign = (...obj) => {
+  const result = Object.assign({}, ...obj)
+  for (let item of obj) {
+    for (let [idx, val] of Object.entries(item)) {
+      if (val instanceof Array) {
+        result[idx] = val
+      } else if (val instanceof Object) {
+        result[idx] = deepAssign(result[idx], val)
+      }
+    }
+  }
+  return result
+}
+
+export const copy = (value) => {
+  const str = document.createElement('input')
+  str.setAttribute('value', value)
+  document.body.appendChild(str)
+  str.select()
+  document.execCommand('copy')
+  document.body.removeChild(str)
+  console.log(value)
+}
+
+/**
+ *
+ * @param precision 精度  1、0.1 、0.01……
+ * @param colorArr
+ * [
+ *    [20.1, '#111111'],
+ *    [20.3, '#dddddd'],
+ *    [20.7, '#eeeeee'],
+ * ]
+ * @return colorMap
+ * new Map([
+ *    [20.1, '#111111']
+ *    ……
+ *    [20.3, '#dddddd']
+ *    ……
+ *    [20.7, '#eeeeee']
+ * ])
+ */
+export const getGradientColorArray = (precision, colorArr) => {
+  // 将hex表示方式转换为rgb表示方式(这里返回rgb数组模式)
+  const colorRgb = (sColor) => {
+    const reg = /^#([0-9a-fA-f]{3}|[0-9a-fA-f]{6})$/;
+    let _sColor = sColor.toLowerCase();
+    if (_sColor && reg.test(_sColor)) {
+      if (_sColor.length === 4) {
+        let sColorNew = "#";
+        for (let i = 1; i < 4; i += 1) {
+          sColorNew += _sColor.slice(i, i + 1).concat(_sColor.slice(i, i + 1));
+        }
+        _sColor = sColorNew;
+      }
+      //处理六位的颜色值
+      const sColorChange = [];
+      for (let i = 1; i < 7; i += 2) {
+        sColorChange.push(parseInt("0x" + _sColor.slice(i, i + 2)));
+      }
+      return sColorChange;
+    } else {
+      return _sColor;
+    }
+  };
+  // 将rgb表示方式转换为hex表示方式
+  const colorHex = (rgb) => {
+    const _this = rgb;
+    const reg = /^#([0-9a-fA-f]{3}|[0-9a-fA-f]{6})$/;
+    if (/^(rgb|RGB)/.test(_this)) {
+      const aColor = _this.replace(/(?:(|)|rgb|RGB)*/g, "").split(",");
+      let strHex = "#";
+      for (let i = 0; i < aColor.length; i++) {
+        let hex = Number(aColor[i]).toString(16);
+        hex = hex < 10 ? 0 + '' + hex : hex;// 保证每个rgb的值为2位
+        if (hex === "0") {
+          hex += hex;
+        }
+        strHex += hex;
+      }
+      if (strHex.length !== 7) {
+        strHex = _this;
+      }
+      return strHex;
+    } else if (reg.test(_this)) {
+      const aNum = _this.replace(/#/, "").split("");
+      if (aNum.length === 6) {
+        return _this;
+      } else if (aNum.length === 3) {
+        let numHex = "#";
+        for (let i = 0; i < aNum.length; i += 1) {
+          numHex += (aNum[i] + aNum[i]);
+        }
+        return numHex;
+      }
+    } else {
+      return _this;
+    }
+  }
+  const rgb2hex = (sRGB) => {
+    const reg = /^(RGB|rgb)\((\d+),\s*(\d+),\s*(\d+)\)$/
+    if (!reg.test(sRGB)) return sRGB
+    const rgbArr = sRGB.match(/\d+/g)
+    const resultRgbArr = rgbArr.map(v => {
+      if (+v > 16) return (+v).toString(16)
+      return '0' + (+v).toString(16)
+    })
+    return '#' + resultRgbArr.join('')
+  }
+  const gradientColor = (startColor, endColor, step) => {
+    const startRGB = colorRgb(startColor);//转换为rgb数组模式
+    const startR = startRGB[0];
+    const startG = startRGB[1];
+    const startB = startRGB[2];
+    const endRGB = colorRgb(endColor);
+    const endR = endRGB[0];
+    const endG = endRGB[1];
+    const endB = endRGB[2];
+    const sR = (endR - startR) / step;//总差值
+    const sG = (endG - startG) / step;
+    const sB = (endB - startB) / step;
+    const colorArr = [];
+    for (let i = 0; i <= step; i++) {
+      //计算每一步的hex值
+      const hex = colorHex('rgb(' + parseInt((sR * i + startR)) + ',' + parseInt((sG * i + startG)) + ',' + parseInt((sB * i + startB)) + ')');
+      colorArr.push(rgb2hex(hex));
+    }
+    return colorArr;
+  }
+  const colorMap = new Map()
+  colorArr.forEach((v, i) => {
+    if (i < colorArr.length - 1) {
+      const _arr = gradientColor(v[1], colorArr[i + 1][1], (Number(colorArr[i + 1][0]) - Number(v[0])) / precision)
+      _arr.forEach((cV, cI) => {
+        colorMap.set((Number(v[0]) + cI * precision).toFixed(String(precision).split('').filter(p => p === '0').length), cV)
+      })
+    } else {
+      colorMap.set(Number(v[0]).toFixed(String(precision).split('').filter(p => p === '0').length), v[1])
+    }
+  })
+  return colorMap
+}

+ 256 - 0
src/views/init-speed-track/drawTrack.ts

@@ -0,0 +1,256 @@
+import * as ol from 'ol'
+import * as style from 'ol/style'
+import * as layer from 'ol/layer'
+import * as source from 'ol/source'
+import * as geom from 'ol/geom'
+import * as proj from 'ol/proj'
+import * as interaction from 'ol/interaction'
+import * as coordinate from 'ol/coordinate'
+import * as control from 'ol/control'
+import * as sphere from 'ol/sphere'
+import { unByKey } from 'ol/Observable'
+import './measure.scss'
+import {createBox} from "ol/interaction/Draw";
+import {Circle, LineString, Polygon} from "ol/geom";
+
+const layerFlag = ['layerName', 'measureLayer']
+let measureTooltipElement;
+let helpTooltipElement;
+/**
+ *
+ * @param map
+ * @param typeSelect    line线,rectangle矩形,polygon多边形,circle圆形
+ */
+export default function Measure (map, typeSelect) {
+    let _source = null
+    const realLayer = map.getLayers().getArray().filter(v => v.get(layerFlag[0]) === layerFlag[1])
+    if (realLayer[0]) {
+        _source = realLayer[0].getSource()
+    } else {
+        _source = new source.Vector(); //图层数据源
+        const _vector = new layer.Vector({
+            zIndex: 9999,
+            source: _source,
+            style: new style.Style({ //图层样式
+                fill: new style.Fill({
+                    color: 'rgba(255, 255, 255, 0.2)' //填充颜色
+                }),
+                stroke: new style.Stroke({
+                    color: '#f31a4a',  //边框颜色
+                    width: 2   // 边框宽度
+                }),
+                image: new style.Circle({
+                    radius: 7,
+                    fill: new style.Fill({
+                        color: '#ffcc33'
+                    })
+                })
+            })
+        });
+        _vector.set(layerFlag[0], layerFlag[1])
+        map.addLayer(_vector);
+    }
+    let sketch;
+    let helpTooltip;
+    let measureTooltip;
+    let continueMsg = '双击结束测量';
+    const geodesicCheckbox = true;//测地学方式对象
+    const createMeasureTooltip = () => {
+        const id = 'measureTooltipElementId'
+        if (measureTooltipElement) {
+            map.removeOverlay(map.getOverlayById(id))
+            measureTooltipElement.parentNode.removeChild(measureTooltipElement);
+        }
+        measureTooltipElement = document.createElement('div');
+        measureTooltipElement.className = 'tooltip tooltip-measure';
+        measureTooltip = new ol.Overlay({
+            id,
+            element: measureTooltipElement,
+            offset: [0, -15],
+            positioning: 'bottom-center'
+        });
+        map.addOverlay(measureTooltip);
+    }
+    const createHelpTooltip = () => {
+        const id = 'helpTooltipElementId'
+        if (helpTooltipElement) {
+            map.removeOverlay(map.getOverlayById(id))
+            helpTooltipElement.parentNode.removeChild(helpTooltipElement);
+        }
+        helpTooltipElement = document.createElement('div');
+        helpTooltipElement.className = 'tooltip hidden';
+        helpTooltip = new ol.Overlay({
+            id,
+            element: helpTooltipElement,
+            offset: [15, 0],
+            positioning: 'center-left'
+        });
+        map.addOverlay(helpTooltip);
+    }
+    const formatLength = (line) => {
+        // 获取投影坐标系
+        const sourceProj = map.getView().getProjection();
+        // ol/sphere里有getLength()和getArea()用来测量距离和区域面积,默认的投影坐标系是EPSG:3857, 其中有个options的参数,可以设置投影坐标系
+        const length = sphere.getLength(line, {projection: sourceProj});
+        // const length = getLength(line);
+        let output;
+        if (length > 100) {
+            const km = Math.round((length / 1000) * 100) / 100;
+            output = `${km} 千米 <br>${parseFloat(String(km * 0.53995)).toFixed(2)} 海里`;
+        } else {
+            output = `${Math.round(length * 100) / 100} m`;
+        }
+        return output;
+    };
+    //获取圆的面积
+    const getCircleArea = (circle, projection) => {
+        const P = 3.14
+        const radius = getCircleRadius(circle, projection)
+        return P * radius * radius
+    }
+//获取圆的半径
+    const getCircleRadius = (circle, projection) => {
+        return circle.getRadius() * projection.getMetersPerUnit()
+    }
+    const formatArea = (polygon, type= 'polygon') => {
+        let area
+        const sourceProj = map.getView().getProjection();
+        // 获取投影坐标系
+        if (type === 'polygon') {
+            area = sphere.getArea(polygon, {
+                projection: sourceProj,
+            });
+        } else if (type === 'circle') {
+            area = getCircleArea(polygon, sourceProj)
+        }
+        let output;
+        if (area > 10000) {
+            const km = Math.round((area / 1000000) * 100) / 100;
+            output = `${km} 平方公里<br>${parseFloat(String(km * 0.38610)).toFixed(
+                2
+            )} 平方英里`;
+        } else {
+            output = `${Math.round(area * 100) / 100} ` + " m<sup>2</sup>";
+        }
+        return output;
+    };
+    const addInteraction = () => {
+        const id = 'drawName'
+        const draw = new interaction.Draw({
+            source: _source,//测量绘制层数据源
+            type: 'LineString',  //几何图形类型
+            geometryFunction: typeSelect === 'rectangle' ? createBox() : null,
+            style: new style.Style({
+                fill: new style.Fill({
+                    color: "rgba(255, 255, 255, 0.2)",
+                }),
+                stroke: new style.Stroke({
+                    color: "#f3584a",
+                    width: 2,
+                }),
+                image: new style.Circle({
+                    radius: 5,
+                    stroke: new style.Stroke({
+                        color: "rgba(0, 0, 0, 0.7)",
+                    }),
+                    fill: new style.Fill({
+                        color: "rgba(255, 255, 255, 0.2)",
+                    }),
+                }),
+            }),
+        });
+        draw.set(id, id)
+        createMeasureTooltip(); //创建测量工具提示框
+        createHelpTooltip(); //创建帮助提示框
+        map.addInteraction(draw);
+        let listener;
+        //绑定交互绘制工具开始绘制的事件
+        const drawstartHandle = (evt) => {
+            sketch = evt.feature; //绘制的要素
+            let tooltipCoord = evt.coordinate;// 绘制的坐标
+            //绑定change事件,根据绘制几何类型得到测量长度值或面积值,并将其设置到测量工具提示框中显示
+            listener = sketch.getGeometry().on('change', function (evt) {
+                const geom = evt.target
+                let output;
+                if (geom.getType() === 'LineString') {
+                    output = formatLength(geom);//长度值
+                    tooltipCoord = geom.getLastCoordinate();//坐标
+                } else if (geom.getType() === 'Polygon') {
+                    output = formatArea(geom);//面积值
+                    tooltipCoord = geom.getInteriorPoint().getCoordinates();//坐标
+                } else if (geom.getType() === 'Circle') {
+                    output = formatArea(geom, 'circle');//面积值
+                    tooltipCoord = geom.getCenter()
+                }
+                measureTooltipElement.innerHTML = output;//将测量值设置到测量工具提示框中显示
+                measureTooltip.setPosition(tooltipCoord);//设置测量工具提示框的显示位置
+            });
+        }
+        draw.on('drawstart', drawstartHandle);
+        //绑定交互绘制工具结束绘制的事件
+        const copy = (value) => {
+            const str = document.createElement('input')
+            str.setAttribute('value', value)
+            document.body.appendChild(str)
+            str.select()
+            document.execCommand('copy')
+            document.body.removeChild(str)
+        }
+        const drawendHandle = (evt) => {
+            map.removeInteraction(map.getInteractions().getArray().filter(v => v.get(id) === id)[0]);
+            const del = document.createElement("div");
+            del.className = "lineDel";
+            measureTooltipElement.append(del);
+            del.onclick = () => {
+                _source.removeFeature(evt.feature)
+                const b = del.parentElement.parentElement
+                b.parentElement.removeChild(b);
+                const g = evt.feature.getGeometry()
+                if (g.getType() === 'LineString') {
+                    const w = `LINESTRING(${g.getCoordinates().map(v => v[0] + ' ' + v[1]).join(',')})`
+                    copy(w)
+                } else if (g.getType() === 'Polygon') {
+                    const w = `POLYGON(${g.getCoordinates().map(v => '(' + v.map(c => c[0] + ' ' + c[1]) + ')').join(',')})`
+                    copy(w)
+                }
+            };
+            measureTooltipElement.className = 'tooltip tooltip-static'; //设置测量提示框的样式
+            measureTooltip.setOffset([0, -7]);
+            sketch = null; //置空当前绘制的要素对象
+            measureTooltipElement = null; //置空测量工具提示框对象
+            helpTooltipElement.parentNode.removeChild(helpTooltipElement);
+            helpTooltipElement = null; //置空测量工具提示框对象
+            unByKey(listener);
+            draw.un('drawstart', drawstartHandle);
+            draw.un('drawend', drawendHandle);
+            map.removeInteraction(map.getInteractions().getArray().filter(v => v.get(id) === id)[0]);
+            map.un('pointermove', pointerMoveHandler)
+        }
+        draw.on('drawend', drawendHandle);
+    }
+    addInteraction(); //调用加载绘制交互控件方法,添加绘图进行测量
+    const pointerMoveHandler = (evt) => {
+        if (evt.dragging) {
+            return;
+        }
+        let helpMsg = '单击开始测量';//当前默认提示信息
+        //判断绘制几何类型设置相应的帮助提示信息
+        if (sketch) {
+            const geom = sketch.getGeometry()
+            helpMsg = continueMsg;
+            // if (geom.getType() === 'Polygon') {
+            //     helpMsg = continueMsg; //绘制多边形时提示相应内容
+            // } else if (geom.getType() === 'LineString') {
+            //     helpMsg = continueMsg; //绘制线时提示相应内容
+            // }
+        }
+        helpTooltipElement.innerHTML = helpMsg; //将提示信息设置到对话框中显示
+        helpTooltip.setPosition(evt.coordinate);//设置帮助提示框的位置
+        helpTooltipElement.classList.remove('hidden');//移除帮助提示框的隐藏样式进行显示
+    };
+    map.on('pointermove', pointerMoveHandler); //地图容器绑定鼠标移动事件,动态显示帮助提示框内容
+    //地图绑定鼠标移出事件,鼠标移出时为帮助提示框设置隐藏样式
+    map.getViewport().on('mouseout', () => {
+        helpTooltipElement.addClass('hidden');
+    });
+}

+ 287 - 0
src/views/init-speed-track/index.vue

@@ -0,0 +1,287 @@
+<template>
+  <div class="init-speed-track">
+    <EasyMapComponent
+        class="map"
+        :showBaseSwitch="true"
+        @easyMapLoad="mapLoad"
+    />
+    <div class="track">
+      <el-card shadow="always">
+        <template #header>
+          <div class="card-header">
+            <span>轨迹列表</span>
+            <el-button-group>
+              <el-button color="#f0a461" size="small" @click="drawTrack('TIANAO_RADAR')" style="color: white">小目标雷达</el-button>
+              <el-button color="#f755f3" size="small" @click="drawTrack('BEIDOU')" style="color: white">北斗</el-button>
+              <el-button type="primary" size="small" @click="drawTrack('GLOBAL_AIS')" style="color: white">全球AIS</el-button>
+            </el-button-group>
+          </div>
+        </template>
+        <div v-for="o in 4" :key="o" class="text item">{{ 'List item ' + o }}</div>
+      </el-card>
+      <el-card shadow="always">
+        ff
+      </el-card>
+    </div>
+  </div>
+</template>
+
+<script lang="ts">
+import {
+  defineComponent,
+  ref,
+  nextTick,
+  onMounted,
+  watch,
+  computed,
+  ComponentInternalInstance,
+  reactive,
+  toRefs,
+  getCurrentInstance
+} from 'vue'
+import {useStore} from 'vuex'
+import * as source from "ol/source";
+import * as layer from "ol/layer";
+import * as style from "ol/style";
+import * as ol from "ol";
+import * as sphere from "ol/sphere";
+import * as interaction from "ol/interaction";
+import {createBox} from "ol/interaction/Draw";
+import {unByKey} from "ol/Observable";
+import { Geometry } from 'ol/geom';
+import { EventsKey } from 'ol/events';
+import { Coordinate } from 'ol/coordinate';
+
+export default defineComponent({
+  name: 'App',
+  components: {},
+  setup() {
+    const store = useStore()
+    const that = (getCurrentInstance() as ComponentInternalInstance).appContext.config.globalProperties
+    const state = reactive({
+      map: <any>null,
+      mapFunc: null,
+    });
+    const mapLoad = (map: null, func: null) => {
+      state.map = map
+      state.mapFunc = func
+    }
+    const drawTrack = (trackSource: string) => {
+      let measureTooltipElement: HTMLDivElement | null;
+      let helpTooltipElement: HTMLDivElement | null;
+      const realLayer = state.map.getLayers().getArray().filter((v: { get: (arg0: string) => string; }) => v.get('layerName') === 'measureLayer')
+      let _source: { removeFeature: (arg0: any) => void; } | null = null
+      if (realLayer[0]) {
+        _source = realLayer[0].getSource()
+      } else {
+        _source = new source.Vector(); //图层数据源
+        const _vector = new layer.Vector({
+          zIndex: 9999,
+          source: _source,
+          style: new style.Style({ //图层样式
+            fill: new style.Fill({
+              color: 'rgba(255, 255, 255, 0.2)' //填充颜色
+            }),
+            stroke: new style.Stroke({
+              color: '#f31a4a',  //边框颜色
+              width: 2   // 边框宽度
+            }),
+            image: new style.Circle({
+              radius: 7,
+              fill: new style.Fill({
+                color: '#ffcc33'
+              })
+            })
+          })
+        });
+        _vector.set('layerName', 'measureLayer')
+        state.map.addLayer(_vector);
+      }
+      let sketch: { getGeometry: () => { (): any; new(): any; on: { (arg0: string, arg1: (evt: any) => void): any; new(): any; }; }; } | null;
+      let helpTooltip: ol.Overlay;
+      let measureTooltip: ol.Overlay;
+      let continueMsg = '双击结束标绘';
+      const createMeasureTooltip = () => {
+        const id = 'measureTooltipElementId'
+        if (measureTooltipElement) {
+          state.map.removeOverlay(state.map.getOverlayById(id))
+          measureTooltipElement.parentNode?.removeChild(measureTooltipElement);
+        }
+        measureTooltipElement = document.createElement('div');
+        measureTooltipElement.className = 'tooltip tooltip-measure';
+        measureTooltip = new ol.Overlay({
+          id,
+          element: measureTooltipElement,
+          offset: [0, -15],
+          positioning: 'bottom-center'
+        });
+        state.map.addOverlay(measureTooltip);
+      }
+      const createHelpTooltip = () => {
+        const id = 'helpTooltipElementId'
+        if (helpTooltipElement) {
+          state.map.removeOverlay(state.map.getOverlayById(id))
+          helpTooltipElement.parentNode?.removeChild(helpTooltipElement);
+        }
+        helpTooltipElement = document.createElement('div');
+        helpTooltipElement.className = 'tooltip hidden';
+        helpTooltip = new ol.Overlay({
+          id,
+          element: helpTooltipElement,
+          offset: [15, 0],
+          positioning: 'center-left'
+        });
+        state.map.addOverlay(helpTooltip);
+      }
+      const formatLength = (line: Geometry) => {
+        // 获取投影坐标系
+        const sourceProj = state.map.getView().getProjection();
+        // ol/sphere里有getLength()和getArea()用来测量距离和区域面积,默认的投影坐标系是EPSG:3857, 其中有个options的参数,可以设置投影坐标系
+        const length = sphere.getLength(line, {projection: sourceProj});
+        // const length = getLength(line);
+        let output;
+        if (length > 100) {
+          const km = Math.round((length / 1000) * 100) / 100;
+          output = `${km} 千米 <br>${parseFloat(String(km * 0.53995)).toFixed(2)} 海里`;
+        } else {
+          output = `${Math.round(length * 100) / 100} m`;
+        }
+        return output;
+      };
+      const addInteraction = () => {
+        const id = 'drawName'
+        const draw = new interaction.Draw({
+          source: _source,//测量绘制层数据源
+          type: 'LineString',  //几何图形类型
+          style: new style.Style({
+            fill: new style.Fill({
+              color: "rgba(255, 255, 255, 0.2)",
+            }),
+            stroke: new style.Stroke({
+              color: "#f3584a",
+              width: 2,
+            }),
+            image: new style.Circle({
+              radius: 5,
+              stroke: new style.Stroke({
+                color: "rgba(0, 0, 0, 0.7)",
+              }),
+              fill: new style.Fill({
+                color: "rgba(255, 255, 255, 0.2)",
+              }),
+            }),
+          }),
+        });
+        draw.set(id, id)
+        createMeasureTooltip(); //创建测量工具提示框
+        createHelpTooltip(); //创建帮助提示框
+        state.map.addInteraction(draw);
+        let listener: EventsKey | EventsKey[];
+        //绑定交互绘制工具开始绘制的事件
+        const drawstartHandle = (evt: { feature: { getGeometry: () => { (): any; new(): any; on: { (arg0: string, arg1: (evt: any) => void): any; new(): any; }; }; } | null; coordinate: any; }) => {
+          sketch = evt.feature; //绘制的要素
+          let tooltipCoord = evt.coordinate;// 绘制的坐标
+          //绑定change事件,根据绘制几何类型得到测量长度值或面积值,并将其设置到测量工具提示框中显示
+          listener = sketch?.getGeometry().on('change', function (evt) {
+            const geom = evt.target
+            let output;
+            output = formatLength(geom);//长度值
+            tooltipCoord = geom.getLastCoordinate();//坐标
+            if (measureTooltipElement) measureTooltipElement.innerHTML = output;//将测量值设置到测量工具提示框中显示
+            measureTooltip.setPosition(tooltipCoord);//设置测量工具提示框的显示位置
+          });
+        }
+        draw.on('drawstart', drawstartHandle);
+        //绑定交互绘制工具结束绘制的事件
+        const copy = (value: string) => {
+          const str = document.createElement('input')
+          str.setAttribute('value', value)
+          document.body.appendChild(str)
+          str.select()
+          document.execCommand('copy')
+          document.body.removeChild(str)
+        }
+        const drawendHandle = (evt: { feature: { getGeometry: () => any; }; }) => {
+          state.map.removeInteraction(state.map.getInteractions().getArray().filter((v: { get: (arg0: string) => string; }) => v.get(id) === id)[0]);
+          const del = document.createElement("div");
+          del.className = "lineDel";
+          measureTooltipElement?.append(del);
+          del.onclick = () => {
+            _source?.removeFeature(evt.feature)
+            const b = del.parentElement?.parentElement
+            b?.parentElement?.removeChild(b);
+            const g = evt.feature.getGeometry()
+            if (g.getType() === 'LineString') {
+              const w = `LINESTRING(${g.getCoordinates().map((v: string[]) => v[0] + ' ' + v[1]).join(',')})`
+              copy(w)
+            } else if (g.getType() === 'Polygon') {
+              const w = `POLYGON(${g.getCoordinates().map(v => '(' + v.map(c => c[0] + ' ' + c[1]) + ')').join(',')})`
+              copy(w)
+            }
+          };
+          if (measureTooltipElement) measureTooltipElement.className = 'tooltip tooltip-static'; //设置测量提示框的样式
+          measureTooltip.setOffset([0, -7]);
+          sketch = null; //置空当前绘制的要素对象
+          measureTooltipElement = null; //置空测量工具提示框对象
+          helpTooltipElement?.parentNode?.removeChild(helpTooltipElement);
+          helpTooltipElement = null; //置空测量工具提示框对象
+          unByKey(listener);
+          draw.un('drawstart', drawstartHandle);
+          draw.un('drawend', drawendHandle);
+          state.map.removeInteraction(state.map.getInteractions().getArray().filter((v: { get: (arg0: string) => string; }) => v.get(id) === id)[0]);
+          state.map.un('pointermove', pointerMoveHandler)
+        }
+        draw.on('drawend', drawendHandle);
+      }
+      addInteraction(); //调用加载绘制交互控件方法,添加绘图进行测量
+      const pointerMoveHandler = (evt: { dragging: any; coordinate: Coordinate | undefined; }) => {
+        if (evt.dragging) {
+          return;
+        }
+        let helpMsg = '单击开始标绘';//当前默认提示信息
+        //判断绘制几何类型设置相应的帮助提示信息
+        if (sketch) {
+          const geom = sketch.getGeometry()
+          helpMsg = continueMsg;
+          // if (geom.getType() === 'Polygon') {
+          //     helpMsg = continueMsg; //绘制多边形时提示相应内容
+          // } else if (geom.getType() === 'LineString') {
+          //     helpMsg = continueMsg; //绘制线时提示相应内容
+          // }
+        }
+        if (helpTooltipElement)helpTooltipElement.innerHTML = helpMsg; //将提示信息设置到对话框中显示
+        helpTooltip.setPosition(evt.coordinate);//设置帮助提示框的位置
+        helpTooltipElement?.classList.remove('hidden');//移除帮助提示框的隐藏样式进行显示
+      };
+      state.map.on('pointermove', pointerMoveHandler); //地图容器绑定鼠标移动事件,动态显示帮助提示框内容
+      //地图绑定鼠标移出事件,鼠标移出时为帮助提示框设置隐藏样式
+      // state.map.getViewport().on('mouseout', () => {
+      //   helpTooltipElement?.addClass('hidden');
+      // });
+    }
+    return {
+      ...toRefs(state),
+      mapLoad,
+      drawTrack
+    }
+  }
+})
+</script>
+
+<style lang="scss" scoped>
+.init-speed-track {
+  width: 100%;
+  height: 100%;
+  position: relative;
+  .map {
+    width: 100%;
+    height: 100vh;
+  }
+  .track {
+    position: absolute;
+    z-index: 2;
+    top: 0;
+    left: 0;
+  }
+}
+</style>

+ 1 - 0
src/vite-env.d.ts

@@ -0,0 +1 @@
+/// <reference types="vite/client" />

+ 18 - 0
tsconfig.json

@@ -0,0 +1,18 @@
+{
+  "compilerOptions": {
+    "target": "ESNext",
+    "useDefineForClassFields": true,
+    "module": "ESNext",
+    "moduleResolution": "Node",
+    "strict": true,
+    "jsx": "preserve",
+    "resolveJsonModule": true,
+    "isolatedModules": true,
+    "esModuleInterop": true,
+    "lib": ["ESNext", "DOM"],
+    "skipLibCheck": true,
+    "noEmit": true
+  },
+  "include": ["src/**/*.ts", "src/**/*.d.ts", "src/**/*.tsx", "src/**/*.vue"],
+  "references": [{ "path": "./tsconfig.node.json" }]
+}

+ 9 - 0
tsconfig.node.json

@@ -0,0 +1,9 @@
+{
+  "compilerOptions": {
+    "composite": true,
+    "module": "ESNext",
+    "moduleResolution": "Node",
+    "allowSyntheticDefaultImports": true
+  },
+  "include": ["vite.config.ts"]
+}

+ 40 - 0
vite.config.ts

@@ -0,0 +1,40 @@
+import { defineConfig } from 'vite'
+import vue from '@vitejs/plugin-vue'
+import {resolve} from "path";
+
+// https://vitejs.dev/config/
+export default defineConfig({
+  plugins: [vue()],
+  base: '/',
+  resolve: {
+    alias: {
+      '@': resolve(__dirname, 'src'),
+    },
+  },
+  server: {
+    port: 1006,
+    // open: true,
+    https: false,
+    base: '/',
+    host: '0.0.0.0',
+    strictPort: false,
+    proxy: {
+      '/api': {
+        // target: 'http://localhost:8080/',
+        target: 'http://120.25.74.229:8000/',
+        // target: 'http://192.168.1.110:8080/',
+        changeOrigin: true,
+        rewrite: path => {
+          return path.replace(/^\/api/, '')
+        }
+      },
+    }
+  },
+  build: {
+    outDir: "seat-tools",
+  },
+  publicDir: 'src/out',
+  optimizeDeps: {
+    include: []
+  }
+})

+ 965 - 0
yarn.lock

@@ -0,0 +1,965 @@
+# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.
+# yarn lockfile v1
+
+
+"@babel/parser@^7.16.4":
+  version "7.21.3"
+  resolved "https://registry.npmmirror.com/@babel/parser/-/parser-7.21.3.tgz#1d285d67a19162ff9daa358d4cb41d50c06220b3"
+  integrity sha512-lobG0d7aOfQRXh8AyklEAgZGvA4FShxo6xQbUrrT/cNBPUdIDojlokwJsQyCC/eKia7ifqM0yP+2DRZ4WKw2RQ==
+
+"@ctrl/tinycolor@^3.4.1":
+  version "3.6.0"
+  resolved "https://registry.npmmirror.com/@ctrl/tinycolor/-/tinycolor-3.6.0.tgz#53fa5fe9c34faee89469e48f91d51a3766108bc8"
+  integrity sha512-/Z3l6pXthq0JvMYdUFyX9j0MaCltlIn6mfh9jLyQwg5aPKxkyNa0PTHtU1AlFXLNk55ZuAeJRcpvq+tmLfKmaQ==
+
+"@element-plus/icons-vue@^2.0.6":
+  version "2.1.0"
+  resolved "https://registry.npmmirror.com/@element-plus/icons-vue/-/icons-vue-2.1.0.tgz#7ad90d08a8c0d5fd3af31c4f73264ca89614397a"
+  integrity sha512-PSBn3elNoanENc1vnCfh+3WA9fimRC7n+fWkf3rE5jvv+aBohNHABC/KAR5KWPecxWxDTVT1ERpRbOMRcOV/vA==
+
+"@esbuild/android-arm64@0.17.14":
+  version "0.17.14"
+  resolved "https://registry.npmmirror.com/@esbuild/android-arm64/-/android-arm64-0.17.14.tgz#4624cea3c8941c91f9e9c1228f550d23f1cef037"
+  integrity sha512-eLOpPO1RvtsP71afiFTvS7tVFShJBCT0txiv/xjFBo5a7R7Gjw7X0IgIaFoLKhqXYAXhahoXm7qAmRXhY4guJg==
+
+"@esbuild/android-arm@0.17.14":
+  version "0.17.14"
+  resolved "https://registry.npmmirror.com/@esbuild/android-arm/-/android-arm-0.17.14.tgz#74fae60fcab34c3f0e15cb56473a6091ba2b53a6"
+  integrity sha512-0CnlwnjDU8cks0yJLXfkaU/uoLyRf9VZJs4p1PskBr2AlAHeEsFEwJEo0of/Z3g+ilw5mpyDwThlxzNEIxOE4g==
+
+"@esbuild/android-x64@0.17.14":
+  version "0.17.14"
+  resolved "https://registry.npmmirror.com/@esbuild/android-x64/-/android-x64-0.17.14.tgz#f002fbc08d5e939d8314bd23bcfb1e95d029491f"
+  integrity sha512-nrfQYWBfLGfSGLvRVlt6xi63B5IbfHm3tZCdu/82zuFPQ7zez4XjmRtF/wIRYbJQ/DsZrxJdEvYFE67avYXyng==
+
+"@esbuild/darwin-arm64@0.17.14":
+  version "0.17.14"
+  resolved "https://registry.npmmirror.com/@esbuild/darwin-arm64/-/darwin-arm64-0.17.14.tgz#b8dcd79a1dd19564950b4ca51d62999011e2e168"
+  integrity sha512-eoSjEuDsU1ROwgBH/c+fZzuSyJUVXQTOIN9xuLs9dE/9HbV/A5IqdXHU1p2OfIMwBwOYJ9SFVGGldxeRCUJFyw==
+
+"@esbuild/darwin-x64@0.17.14":
+  version "0.17.14"
+  resolved "https://registry.npmmirror.com/@esbuild/darwin-x64/-/darwin-x64-0.17.14.tgz#4b49f195d9473625efc3c773fc757018f2c0d979"
+  integrity sha512-zN0U8RWfrDttdFNkHqFYZtOH8hdi22z0pFm0aIJPsNC4QQZv7je8DWCX5iA4Zx6tRhS0CCc0XC2m7wKsbWEo5g==
+
+"@esbuild/freebsd-arm64@0.17.14":
+  version "0.17.14"
+  resolved "https://registry.npmmirror.com/@esbuild/freebsd-arm64/-/freebsd-arm64-0.17.14.tgz#480923fd38f644c6342c55e916cc7c231a85eeb7"
+  integrity sha512-z0VcD4ibeZWVQCW1O7szaLxGsx54gcCnajEJMdYoYjLiq4g1jrP2lMq6pk71dbS5+7op/L2Aod+erw+EUr28/A==
+
+"@esbuild/freebsd-x64@0.17.14":
+  version "0.17.14"
+  resolved "https://registry.npmmirror.com/@esbuild/freebsd-x64/-/freebsd-x64-0.17.14.tgz#a6b6b01954ad8562461cb8a5e40e8a860af69cbe"
+  integrity sha512-hd9mPcxfTgJlolrPlcXkQk9BMwNBvNBsVaUe5eNUqXut6weDQH8whcNaKNF2RO8NbpT6GY8rHOK2A9y++s+ehw==
+
+"@esbuild/linux-arm64@0.17.14":
+  version "0.17.14"
+  resolved "https://registry.npmmirror.com/@esbuild/linux-arm64/-/linux-arm64-0.17.14.tgz#1fe2f39f78183b59f75a4ad9c48d079916d92418"
+  integrity sha512-FhAMNYOq3Iblcj9i+K0l1Fp/MHt+zBeRu/Qkf0LtrcFu3T45jcwB6A1iMsemQ42vR3GBhjNZJZTaCe3VFPbn9g==
+
+"@esbuild/linux-arm@0.17.14":
+  version "0.17.14"
+  resolved "https://registry.npmmirror.com/@esbuild/linux-arm/-/linux-arm-0.17.14.tgz#18d594a49b64e4a3a05022c005cb384a58056a2a"
+  integrity sha512-BNTl+wSJ1omsH8s3TkQmIIIQHwvwJrU9u1ggb9XU2KTVM4TmthRIVyxSp2qxROJHhZuW/r8fht46/QE8hU8Qvg==
+
+"@esbuild/linux-ia32@0.17.14":
+  version "0.17.14"
+  resolved "https://registry.npmmirror.com/@esbuild/linux-ia32/-/linux-ia32-0.17.14.tgz#f7f0182a9cfc0159e0922ed66c805c9c6ef1b654"
+  integrity sha512-91OK/lQ5y2v7AsmnFT+0EyxdPTNhov3y2CWMdizyMfxSxRqHazXdzgBKtlmkU2KYIc+9ZK3Vwp2KyXogEATYxQ==
+
+"@esbuild/linux-loong64@0.17.14":
+  version "0.17.14"
+  resolved "https://registry.npmmirror.com/@esbuild/linux-loong64/-/linux-loong64-0.17.14.tgz#5f5305fdffe2d71dd9a97aa77d0c99c99409066f"
+  integrity sha512-vp15H+5NR6hubNgMluqqKza85HcGJgq7t6rMH7O3Y6ApiOWPkvW2AJfNojUQimfTp6OUrACUXfR4hmpcENXoMQ==
+
+"@esbuild/linux-mips64el@0.17.14":
+  version "0.17.14"
+  resolved "https://registry.npmmirror.com/@esbuild/linux-mips64el/-/linux-mips64el-0.17.14.tgz#a602e85c51b2f71d2aedfe7f4143b2f92f97f3f5"
+  integrity sha512-90TOdFV7N+fgi6c2+GO9ochEkmm9kBAKnuD5e08GQMgMINOdOFHuYLPQ91RYVrnWwQ5683sJKuLi9l4SsbJ7Hg==
+
+"@esbuild/linux-ppc64@0.17.14":
+  version "0.17.14"
+  resolved "https://registry.npmmirror.com/@esbuild/linux-ppc64/-/linux-ppc64-0.17.14.tgz#32d918d782105cbd9345dbfba14ee018b9c7afdf"
+  integrity sha512-NnBGeoqKkTugpBOBZZoktQQ1Yqb7aHKmHxsw43NddPB2YWLAlpb7THZIzsRsTr0Xw3nqiPxbA1H31ZMOG+VVPQ==
+
+"@esbuild/linux-riscv64@0.17.14":
+  version "0.17.14"
+  resolved "https://registry.npmmirror.com/@esbuild/linux-riscv64/-/linux-riscv64-0.17.14.tgz#38612e7b6c037dff7022c33f49ca17f85c5dec58"
+  integrity sha512-0qdlKScLXA8MGVy21JUKvMzCYWovctuP8KKqhtE5A6IVPq4onxXhSuhwDd2g5sRCzNDlDjitc5sX31BzDoL5Fw==
+
+"@esbuild/linux-s390x@0.17.14":
+  version "0.17.14"
+  resolved "https://registry.npmmirror.com/@esbuild/linux-s390x/-/linux-s390x-0.17.14.tgz#4397dff354f899e72fd035d72af59a700c465ccb"
+  integrity sha512-Hdm2Jo1yaaOro4v3+6/zJk6ygCqIZuSDJHdHaf8nVH/tfOuoEX5Riv03Ka15LmQBYJObUTNS1UdyoMk0WUn9Ww==
+
+"@esbuild/linux-x64@0.17.14":
+  version "0.17.14"
+  resolved "https://registry.npmmirror.com/@esbuild/linux-x64/-/linux-x64-0.17.14.tgz#6c5cb99891b6c3e0c08369da3ef465e8038ad9c2"
+  integrity sha512-8KHF17OstlK4DuzeF/KmSgzrTWQrkWj5boluiiq7kvJCiQVzUrmSkaBvcLB2UgHpKENO2i6BthPkmUhNDaJsVw==
+
+"@esbuild/netbsd-x64@0.17.14":
+  version "0.17.14"
+  resolved "https://registry.npmmirror.com/@esbuild/netbsd-x64/-/netbsd-x64-0.17.14.tgz#5fa5255a64e9bf3947c1b3bef5e458b50b211994"
+  integrity sha512-nVwpqvb3yyXztxIT2+VsxJhB5GCgzPdk1n0HHSnchRAcxqKO6ghXwHhJnr0j/B+5FSyEqSxF4q03rbA2fKXtUQ==
+
+"@esbuild/openbsd-x64@0.17.14":
+  version "0.17.14"
+  resolved "https://registry.npmmirror.com/@esbuild/openbsd-x64/-/openbsd-x64-0.17.14.tgz#74d14c79dcb6faf446878cc64284aa4e02f5ca6f"
+  integrity sha512-1RZ7uQQ9zcy/GSAJL1xPdN7NDdOOtNEGiJalg/MOzeakZeTrgH/DoCkbq7TaPDiPhWqnDF+4bnydxRqQD7il6g==
+
+"@esbuild/sunos-x64@0.17.14":
+  version "0.17.14"
+  resolved "https://registry.npmmirror.com/@esbuild/sunos-x64/-/sunos-x64-0.17.14.tgz#5c7d1c7203781d86c2a9b2ff77bd2f8036d24cfa"
+  integrity sha512-nqMjDsFwv7vp7msrwWRysnM38Sd44PKmW8EzV01YzDBTcTWUpczQg6mGao9VLicXSgW/iookNK6AxeogNVNDZA==
+
+"@esbuild/win32-arm64@0.17.14":
+  version "0.17.14"
+  resolved "https://registry.npmmirror.com/@esbuild/win32-arm64/-/win32-arm64-0.17.14.tgz#dc36ed84f1390e73b6019ccf0566c80045e5ca3d"
+  integrity sha512-xrD0mccTKRBBIotrITV7WVQAwNJ5+1va6L0H9zN92v2yEdjfAN7864cUaZwJS7JPEs53bDTzKFbfqVlG2HhyKQ==
+
+"@esbuild/win32-ia32@0.17.14":
+  version "0.17.14"
+  resolved "https://registry.npmmirror.com/@esbuild/win32-ia32/-/win32-ia32-0.17.14.tgz#0802a107afa9193c13e35de15a94fe347c588767"
+  integrity sha512-nXpkz9bbJrLLyUTYtRotSS3t5b+FOuljg8LgLdINWFs3FfqZMtbnBCZFUmBzQPyxqU87F8Av+3Nco/M3hEcu1w==
+
+"@esbuild/win32-x64@0.17.14":
+  version "0.17.14"
+  resolved "https://registry.npmmirror.com/@esbuild/win32-x64/-/win32-x64-0.17.14.tgz#e81fb49de05fed91bf74251c9ca0343f4fc77d31"
+  integrity sha512-gPQmsi2DKTaEgG14hc3CHXHp62k8g6qr0Pas+I4lUxRMugGSATh/Bi8Dgusoz9IQ0IfdrvLpco6kujEIBoaogA==
+
+"@floating-ui/core@^1.2.4":
+  version "1.2.5"
+  resolved "https://registry.npmmirror.com/@floating-ui/core/-/core-1.2.5.tgz#612f0d203e6f647490d572c7b798eebac9e3cf54"
+  integrity sha512-qrcbyfnRVziRlB6IYwjCopYhO7Vud750JlJyuljruIXcPxr22y8zdckcJGsuOdnQ639uVD1tTXddrcH3t3QYIQ==
+
+"@floating-ui/dom@^1.0.1":
+  version "1.2.5"
+  resolved "https://registry.npmmirror.com/@floating-ui/dom/-/dom-1.2.5.tgz#c9ec259a24ce0958b1ea29674df4eee4455361a9"
+  integrity sha512-+sAUfpQ3Frz+VCbPCqj+cZzvEESy3fjSeT/pDWkYCWOBXYNNKZfuVsHuv8/JO2zze8+Eb/Q7a6hZVgzS81fLbQ==
+  dependencies:
+    "@floating-ui/core" "^1.2.4"
+
+"@mapbox/jsonlint-lines-primitives@~2.0.2":
+  version "2.0.2"
+  resolved "https://registry.npmmirror.com/@mapbox/jsonlint-lines-primitives/-/jsonlint-lines-primitives-2.0.2.tgz#ce56e539f83552b58d10d672ea4d6fc9adc7b234"
+  integrity sha512-rY0o9A5ECsTQRVhv7tL/OyDpGAoUB4tTvLiW1DSzQGq4bvTPhNw1VpSNjDJc5GFZ2XuyOtSWSVN05qOtcD71qQ==
+
+"@mapbox/mapbox-gl-style-spec@^13.23.1":
+  version "13.28.0"
+  resolved "https://registry.npmmirror.com/@mapbox/mapbox-gl-style-spec/-/mapbox-gl-style-spec-13.28.0.tgz#2ec226320a0f77856046e000df9b419303a56458"
+  integrity sha512-B8xM7Fp1nh5kejfIl4SWeY0gtIeewbuRencqO3cJDrCHZpaPg7uY+V8abuR+esMeuOjRl5cLhVTP40v+1ywxbg==
+  dependencies:
+    "@mapbox/jsonlint-lines-primitives" "~2.0.2"
+    "@mapbox/point-geometry" "^0.1.0"
+    "@mapbox/unitbezier" "^0.0.0"
+    csscolorparser "~1.0.2"
+    json-stringify-pretty-compact "^2.0.0"
+    minimist "^1.2.6"
+    rw "^1.3.3"
+    sort-object "^0.3.2"
+
+"@mapbox/point-geometry@^0.1.0":
+  version "0.1.0"
+  resolved "https://registry.npmmirror.com/@mapbox/point-geometry/-/point-geometry-0.1.0.tgz#8a83f9335c7860effa2eeeca254332aa0aeed8f2"
+  integrity sha512-6j56HdLTwWGO0fJPlrZtdU/B13q8Uwmo18Ck2GnGgN9PCFyKTZ3UbXeEdRFh18i9XQ92eH2VdtpJHpBD3aripQ==
+
+"@mapbox/unitbezier@^0.0.0":
+  version "0.0.0"
+  resolved "https://registry.npmmirror.com/@mapbox/unitbezier/-/unitbezier-0.0.0.tgz#15651bd553a67b8581fb398810c98ad86a34524e"
+  integrity sha512-HPnRdYO0WjFjRTSwO3frz1wKaU649OBFPX3Zo/2WZvuRi6zMiRGui8SnPQiQABgqCf8YikDe5t3HViTVw1WUzA==
+
+"@petamoriken/float16@^3.4.7":
+  version "3.8.0"
+  resolved "https://registry.npmmirror.com/@petamoriken/float16/-/float16-3.8.0.tgz#3a48b7938e1a62188a61ec02d5b12630f671401f"
+  integrity sha512-AhVAm6SQ+zgxIiOzwVdUcDmKlu/qU39FiYD2UD6kQQaVenrn0dGZewIghWAENGQsvC+1avLCuT+T2/3Gsp/W3w==
+
+"@popperjs/core@npm:@sxzz/popperjs-es@^2.11.7":
+  version "2.11.7"
+  resolved "https://registry.npmmirror.com/@sxzz/popperjs-es/-/popperjs-es-2.11.7.tgz#a7f69e3665d3da9b115f9e71671dae1b97e13671"
+  integrity sha512-Ccy0NlLkzr0Ex2FKvh2X+OyERHXJ88XJ1MXtsI9y9fGexlaXaVTPzBCRBwIxFkORuOb+uBqeu+RqnpgYTEZRUQ==
+
+"@types/lodash-es@^4.17.6":
+  version "4.17.7"
+  resolved "https://registry.npmmirror.com/@types/lodash-es/-/lodash-es-4.17.7.tgz#22edcae9f44aff08546e71db8925f05b33c7cc40"
+  integrity sha512-z0ptr6UI10VlU6l5MYhGwS4mC8DZyYer2mCoyysZtSF7p26zOX8UpbrV0YpNYLGS8K4PUFIyEr62IMFFjveSiQ==
+  dependencies:
+    "@types/lodash" "*"
+
+"@types/lodash@*", "@types/lodash@^4.14.182":
+  version "4.14.192"
+  resolved "https://registry.npmmirror.com/@types/lodash/-/lodash-4.14.192.tgz#5790406361a2852d332d41635d927f1600811285"
+  integrity sha512-km+Vyn3BYm5ytMO13k9KTp27O75rbQ0NFw+U//g+PX7VZyjCioXaRFisqSIJRECljcTv73G3i6BpglNGHgUQ5A==
+
+"@types/web-bluetooth@^0.0.16":
+  version "0.0.16"
+  resolved "https://registry.npmmirror.com/@types/web-bluetooth/-/web-bluetooth-0.0.16.tgz#1d12873a8e49567371f2a75fe3e7f7edca6662d8"
+  integrity sha512-oh8q2Zc32S6gd/j50GowEjKLoOVOwHP/bWVjKJInBwQqdOYMdPrf1oVlelTlyfFK3CKxL1uahMDAr+vy8T7yMQ==
+
+"@vitejs/plugin-vue@^4.1.0":
+  version "4.1.0"
+  resolved "https://registry.npmmirror.com/@vitejs/plugin-vue/-/plugin-vue-4.1.0.tgz#b6a9d83cd91575f7ee15593f6444397f68751073"
+  integrity sha512-++9JOAFdcXI3lyer9UKUV4rfoQ3T1RN8yDqoCLar86s0xQct5yblxAE+yWgRnU5/0FOlVCpTZpYSBV/bGWrSrQ==
+
+"@volar/language-core@1.3.0-alpha.0":
+  version "1.3.0-alpha.0"
+  resolved "https://registry.npmmirror.com/@volar/language-core/-/language-core-1.3.0-alpha.0.tgz#4924b4cbc37dbce5f3845c1d2b2811938223a980"
+  integrity sha512-W3uMzecHPcbwddPu4SJpUcPakRBK/y/BP+U0U6NiPpUX1tONLC4yCawt+QBJqtgJ+sfD6ztf5PyvPL3hQRqfOA==
+  dependencies:
+    "@volar/source-map" "1.3.0-alpha.0"
+
+"@volar/source-map@1.3.0-alpha.0":
+  version "1.3.0-alpha.0"
+  resolved "https://registry.npmmirror.com/@volar/source-map/-/source-map-1.3.0-alpha.0.tgz#c45d51ecb9759604d29fb80211d2fc9765e5559c"
+  integrity sha512-jSdizxWFvDTvkPYZnO6ew3sBZUnS0abKCbuopkc0JrIlFbznWC/fPH3iPFIMS8/IIkRxq1Jh9VVG60SmtsdaMQ==
+  dependencies:
+    muggle-string "^0.2.2"
+
+"@volar/typescript@1.3.0-alpha.0":
+  version "1.3.0-alpha.0"
+  resolved "https://registry.npmmirror.com/@volar/typescript/-/typescript-1.3.0-alpha.0.tgz#f79bbc9939016700812b18191c47eb035913c6c3"
+  integrity sha512-5UItyW2cdH2mBLu4RrECRNJRgtvvzKrSCn2y3v/D61QwIDkGx4aeil6x8RFuUL5TFtV6QvVHXnsOHxNgd+sCow==
+  dependencies:
+    "@volar/language-core" "1.3.0-alpha.0"
+
+"@volar/vue-language-core@1.2.0":
+  version "1.2.0"
+  resolved "https://registry.npmmirror.com/@volar/vue-language-core/-/vue-language-core-1.2.0.tgz#a600aa93c6a4e89bf2b525b7e876b39e3afdfb9b"
+  integrity sha512-w7yEiaITh2WzKe6u8ZdeLKCUz43wdmY/OqAmsB/PGDvvhTcVhCJ6f0W/RprZL1IhqH8wALoWiwEh/Wer7ZviMQ==
+  dependencies:
+    "@volar/language-core" "1.3.0-alpha.0"
+    "@volar/source-map" "1.3.0-alpha.0"
+    "@vue/compiler-dom" "^3.2.47"
+    "@vue/compiler-sfc" "^3.2.47"
+    "@vue/reactivity" "^3.2.47"
+    "@vue/shared" "^3.2.47"
+    minimatch "^6.1.6"
+    muggle-string "^0.2.2"
+    vue-template-compiler "^2.7.14"
+
+"@volar/vue-typescript@1.2.0":
+  version "1.2.0"
+  resolved "https://registry.npmmirror.com/@volar/vue-typescript/-/vue-typescript-1.2.0.tgz#825dab4624a116d8be21efbf0c4a7bd6dec51d37"
+  integrity sha512-zjmRi9y3J1EkG+pfuHp8IbHmibihrKK485cfzsHjiuvJMGrpkWvlO5WVEk8oslMxxeGC5XwBFE9AOlvh378EPA==
+  dependencies:
+    "@volar/typescript" "1.3.0-alpha.0"
+    "@volar/vue-language-core" "1.2.0"
+
+"@vue/compiler-core@3.2.47":
+  version "3.2.47"
+  resolved "https://registry.npmmirror.com/@vue/compiler-core/-/compiler-core-3.2.47.tgz#3e07c684d74897ac9aa5922c520741f3029267f8"
+  integrity sha512-p4D7FDnQb7+YJmO2iPEv0SQNeNzcbHdGByJDsT4lynf63AFkOTFN07HsiRSvjGo0QrxR/o3d0hUyNCUnBU2Tig==
+  dependencies:
+    "@babel/parser" "^7.16.4"
+    "@vue/shared" "3.2.47"
+    estree-walker "^2.0.2"
+    source-map "^0.6.1"
+
+"@vue/compiler-dom@3.2.47", "@vue/compiler-dom@^3.2.47":
+  version "3.2.47"
+  resolved "https://registry.npmmirror.com/@vue/compiler-dom/-/compiler-dom-3.2.47.tgz#a0b06caf7ef7056939e563dcaa9cbde30794f305"
+  integrity sha512-dBBnEHEPoftUiS03a4ggEig74J2YBZ2UIeyfpcRM2tavgMWo4bsEfgCGsu+uJIL/vax9S+JztH8NmQerUo7shQ==
+  dependencies:
+    "@vue/compiler-core" "3.2.47"
+    "@vue/shared" "3.2.47"
+
+"@vue/compiler-sfc@3.2.47", "@vue/compiler-sfc@^3.2.47":
+  version "3.2.47"
+  resolved "https://registry.npmmirror.com/@vue/compiler-sfc/-/compiler-sfc-3.2.47.tgz#1bdc36f6cdc1643f72e2c397eb1a398f5004ad3d"
+  integrity sha512-rog05W+2IFfxjMcFw10tM9+f7i/+FFpZJJ5XHX72NP9eC2uRD+42M3pYcQqDXVYoj74kHMSEdQ/WmCjt8JFksQ==
+  dependencies:
+    "@babel/parser" "^7.16.4"
+    "@vue/compiler-core" "3.2.47"
+    "@vue/compiler-dom" "3.2.47"
+    "@vue/compiler-ssr" "3.2.47"
+    "@vue/reactivity-transform" "3.2.47"
+    "@vue/shared" "3.2.47"
+    estree-walker "^2.0.2"
+    magic-string "^0.25.7"
+    postcss "^8.1.10"
+    source-map "^0.6.1"
+
+"@vue/compiler-ssr@3.2.47":
+  version "3.2.47"
+  resolved "https://registry.npmmirror.com/@vue/compiler-ssr/-/compiler-ssr-3.2.47.tgz#35872c01a273aac4d6070ab9d8da918ab13057ee"
+  integrity sha512-wVXC+gszhulcMD8wpxMsqSOpvDZ6xKXSVWkf50Guf/S+28hTAXPDYRTbLQ3EDkOP5Xz/+SY37YiwDquKbJOgZw==
+  dependencies:
+    "@vue/compiler-dom" "3.2.47"
+    "@vue/shared" "3.2.47"
+
+"@vue/devtools-api@^6.0.0-beta.11", "@vue/devtools-api@^6.4.5":
+  version "6.5.0"
+  resolved "https://registry.npmmirror.com/@vue/devtools-api/-/devtools-api-6.5.0.tgz#98b99425edee70b4c992692628fa1ea2c1e57d07"
+  integrity sha512-o9KfBeaBmCKl10usN4crU53fYtC1r7jJwdGKjPT24t348rHxgfpZ0xL3Xm/gLUYnc0oTp8LAmrxOeLyu6tbk2Q==
+
+"@vue/reactivity-transform@3.2.47":
+  version "3.2.47"
+  resolved "https://registry.npmmirror.com/@vue/reactivity-transform/-/reactivity-transform-3.2.47.tgz#e45df4d06370f8abf29081a16afd25cffba6d84e"
+  integrity sha512-m8lGXw8rdnPVVIdIFhf0LeQ/ixyHkH5plYuS83yop5n7ggVJU+z5v0zecwEnX7fa7HNLBhh2qngJJkxpwEEmYA==
+  dependencies:
+    "@babel/parser" "^7.16.4"
+    "@vue/compiler-core" "3.2.47"
+    "@vue/shared" "3.2.47"
+    estree-walker "^2.0.2"
+    magic-string "^0.25.7"
+
+"@vue/reactivity@3.2.47", "@vue/reactivity@^3.2.47":
+  version "3.2.47"
+  resolved "https://registry.npmmirror.com/@vue/reactivity/-/reactivity-3.2.47.tgz#1d6399074eadfc3ed35c727e2fd707d6881140b6"
+  integrity sha512-7khqQ/75oyyg+N/e+iwV6lpy1f5wq759NdlS1fpAhFXa8VeAIKGgk2E/C4VF59lx5b+Ezs5fpp/5WsRYXQiKxQ==
+  dependencies:
+    "@vue/shared" "3.2.47"
+
+"@vue/runtime-core@3.2.47":
+  version "3.2.47"
+  resolved "https://registry.npmmirror.com/@vue/runtime-core/-/runtime-core-3.2.47.tgz#406ebade3d5551c00fc6409bbc1eeb10f32e121d"
+  integrity sha512-RZxbLQIRB/K0ev0K9FXhNbBzT32H9iRtYbaXb0ZIz2usLms/D55dJR2t6cIEUn6vyhS3ALNvNthI+Q95C+NOpA==
+  dependencies:
+    "@vue/reactivity" "3.2.47"
+    "@vue/shared" "3.2.47"
+
+"@vue/runtime-dom@3.2.47":
+  version "3.2.47"
+  resolved "https://registry.npmmirror.com/@vue/runtime-dom/-/runtime-dom-3.2.47.tgz#93e760eeaeab84dedfb7c3eaf3ed58d776299382"
+  integrity sha512-ArXrFTjS6TsDei4qwNvgrdmHtD930KgSKGhS5M+j8QxXrDJYLqYw4RRcDy1bz1m1wMmb6j+zGLifdVHtkXA7gA==
+  dependencies:
+    "@vue/runtime-core" "3.2.47"
+    "@vue/shared" "3.2.47"
+    csstype "^2.6.8"
+
+"@vue/server-renderer@3.2.47":
+  version "3.2.47"
+  resolved "https://registry.npmmirror.com/@vue/server-renderer/-/server-renderer-3.2.47.tgz#8aa1d1871fc4eb5a7851aa7f741f8f700e6de3c0"
+  integrity sha512-dN9gc1i8EvmP9RCzvneONXsKfBRgqFeFZLurmHOveL7oH6HiFXJw5OGu294n1nHc/HMgTy6LulU/tv5/A7f/LA==
+  dependencies:
+    "@vue/compiler-ssr" "3.2.47"
+    "@vue/shared" "3.2.47"
+
+"@vue/shared@3.2.47", "@vue/shared@^3.2.47":
+  version "3.2.47"
+  resolved "https://registry.npmmirror.com/@vue/shared/-/shared-3.2.47.tgz#e597ef75086c6e896ff5478a6bfc0a7aa4bbd14c"
+  integrity sha512-BHGyyGN3Q97EZx0taMQ+OLNuZcW3d37ZEVmEAyeoA9ERdGvm9Irc/0Fua8SNyOtV1w6BS4q25wbMzJujO9HIfQ==
+
+"@vueuse/core@^9.1.0":
+  version "9.13.0"
+  resolved "https://registry.npmmirror.com/@vueuse/core/-/core-9.13.0.tgz#2f69e66d1905c1e4eebc249a01759cf88ea00cf4"
+  integrity sha512-pujnclbeHWxxPRqXWmdkKV5OX4Wk4YeK7wusHqRwU0Q7EFusHoqNA/aPhB6KCh9hEqJkLAJo7bb0Lh9b+OIVzw==
+  dependencies:
+    "@types/web-bluetooth" "^0.0.16"
+    "@vueuse/metadata" "9.13.0"
+    "@vueuse/shared" "9.13.0"
+    vue-demi "*"
+
+"@vueuse/metadata@9.13.0":
+  version "9.13.0"
+  resolved "https://registry.npmmirror.com/@vueuse/metadata/-/metadata-9.13.0.tgz#bc25a6cdad1b1a93c36ce30191124da6520539ff"
+  integrity sha512-gdU7TKNAUVlXXLbaF+ZCfte8BjRJQWPCa2J55+7/h+yDtzw3vOoGQDRXzI6pyKyo6bXFT5/QoPE4hAknExjRLQ==
+
+"@vueuse/shared@9.13.0":
+  version "9.13.0"
+  resolved "https://registry.npmmirror.com/@vueuse/shared/-/shared-9.13.0.tgz#089ff4cc4e2e7a4015e57a8f32e4b39d096353b9"
+  integrity sha512-UrnhU+Cnufu4S6JLCPZnkWh0WwZGUp72ktOF2DFptMlOs3TOdVv8xJN53zhHGARmVOsz5KqOls09+J1NR6sBKw==
+  dependencies:
+    vue-demi "*"
+
+anymatch@~3.1.2:
+  version "3.1.3"
+  resolved "https://registry.npmmirror.com/anymatch/-/anymatch-3.1.3.tgz#790c58b19ba1720a84205b57c618d5ad8524973e"
+  integrity sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==
+  dependencies:
+    normalize-path "^3.0.0"
+    picomatch "^2.0.4"
+
+async-validator@^4.2.5:
+  version "4.2.5"
+  resolved "https://registry.npmmirror.com/async-validator/-/async-validator-4.2.5.tgz#c96ea3332a521699d0afaaceed510a54656c6339"
+  integrity sha512-7HhHjtERjqlNbZtqNqy2rckN/SpOOlmDliet+lP7k+eKZEjPk3DgyeU9lIXLdeLz0uBbbVp+9Qdow9wJWgwwfg==
+
+asynckit@^0.4.0:
+  version "0.4.0"
+  resolved "https://registry.npmmirror.com/asynckit/-/asynckit-0.4.0.tgz#c79ed97f7f34cb8f2ba1bc9790bcc366474b4b79"
+  integrity sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==
+
+axios@^1.3.4:
+  version "1.3.4"
+  resolved "https://registry.npmmirror.com/axios/-/axios-1.3.4.tgz#f5760cefd9cfb51fd2481acf88c05f67c4523024"
+  integrity sha512-toYm+Bsyl6VC5wSkfkbbNB6ROv7KY93PEBBL6xyDczaIHasAiv4wPqQ/c4RjoQzipxRD2W5g21cOqQulZ7rHwQ==
+  dependencies:
+    follow-redirects "^1.15.0"
+    form-data "^4.0.0"
+    proxy-from-env "^1.1.0"
+
+balanced-match@^1.0.0:
+  version "1.0.2"
+  resolved "https://registry.npmmirror.com/balanced-match/-/balanced-match-1.0.2.tgz#e83e3a7e3f300b34cb9d87f615fa0cbf357690ee"
+  integrity sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==
+
+binary-extensions@^2.0.0:
+  version "2.2.0"
+  resolved "https://registry.npmmirror.com/binary-extensions/-/binary-extensions-2.2.0.tgz#75f502eeaf9ffde42fc98829645be4ea76bd9e2d"
+  integrity sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==
+
+brace-expansion@^2.0.1:
+  version "2.0.1"
+  resolved "https://registry.npmmirror.com/brace-expansion/-/brace-expansion-2.0.1.tgz#1edc459e0f0c548486ecf9fc99f2221364b9a0ae"
+  integrity sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==
+  dependencies:
+    balanced-match "^1.0.0"
+
+braces@~3.0.2:
+  version "3.0.2"
+  resolved "https://registry.npmmirror.com/braces/-/braces-3.0.2.tgz#3454e1a462ee8d599e236df336cd9ea4f8afe107"
+  integrity sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==
+  dependencies:
+    fill-range "^7.0.1"
+
+"chokidar@>=3.0.0 <4.0.0":
+  version "3.5.3"
+  resolved "https://registry.npmmirror.com/chokidar/-/chokidar-3.5.3.tgz#1cf37c8707b932bd1af1ae22c0432e2acd1903bd"
+  integrity sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw==
+  dependencies:
+    anymatch "~3.1.2"
+    braces "~3.0.2"
+    glob-parent "~5.1.2"
+    is-binary-path "~2.1.0"
+    is-glob "~4.0.1"
+    normalize-path "~3.0.0"
+    readdirp "~3.6.0"
+  optionalDependencies:
+    fsevents "~2.3.2"
+
+combined-stream@^1.0.8:
+  version "1.0.8"
+  resolved "https://registry.npmmirror.com/combined-stream/-/combined-stream-1.0.8.tgz#c3d45a8b34fd730631a110a8a2520682b31d5a7f"
+  integrity sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==
+  dependencies:
+    delayed-stream "~1.0.0"
+
+csscolorparser@~1.0.2:
+  version "1.0.3"
+  resolved "https://registry.npmmirror.com/csscolorparser/-/csscolorparser-1.0.3.tgz#b34f391eea4da8f3e98231e2ccd8df9c041f171b"
+  integrity sha512-umPSgYwZkdFoUrH5hIq5kf0wPSXiro51nPw0j2K/c83KflkPSTBGMz6NJvMB+07VlL0y7VPo6QJcDjcgKTTm3w==
+
+csstype@^2.6.8:
+  version "2.6.21"
+  resolved "https://registry.npmmirror.com/csstype/-/csstype-2.6.21.tgz#2efb85b7cc55c80017c66a5ad7cbd931fda3a90e"
+  integrity sha512-Z1PhmomIfypOpoMjRQB70jfvy/wxT50qW08YXO5lMIJkrdq4yOTR+AW7FqutScmB9NkLwxo+jU+kZLbofZZq/w==
+
+dayjs@^1.11.3:
+  version "1.11.7"
+  resolved "https://registry.npmmirror.com/dayjs/-/dayjs-1.11.7.tgz#4b296922642f70999544d1144a2c25730fce63e2"
+  integrity sha512-+Yw9U6YO5TQohxLcIkrXBeY73WP3ejHWVvx8XCk3gxvQDCTEmS48ZrSZCKciI7Bhl/uCMyxYtE9UqRILmFphkQ==
+
+de-indent@^1.0.2:
+  version "1.0.2"
+  resolved "https://registry.npmmirror.com/de-indent/-/de-indent-1.0.2.tgz#b2038e846dc33baa5796128d0804b455b8c1e21d"
+  integrity sha512-e/1zu3xH5MQryN2zdVaF0OrdNLUbvWxzMbi+iNA6Bky7l1RoP8a2fIbRocyHclXt/arDrrR6lL3TqFD9pMQTsg==
+
+delayed-stream@~1.0.0:
+  version "1.0.0"
+  resolved "https://registry.npmmirror.com/delayed-stream/-/delayed-stream-1.0.0.tgz#df3ae199acadfb7d440aaae0b29e2272b24ec619"
+  integrity sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==
+
+element-plus@^2.3.1:
+  version "2.3.1"
+  resolved "https://registry.npmmirror.com/element-plus/-/element-plus-2.3.1.tgz#e4e13c28702709583ab978041145bc1367844842"
+  integrity sha512-IBS7ic1mRyDXpOreRkredV4ByZSuax5HPb0zNOHm4qwKC4wm927yQv+Is0JbzxPzCW5zWaV4PLy9/Gl3E3v59w==
+  dependencies:
+    "@ctrl/tinycolor" "^3.4.1"
+    "@element-plus/icons-vue" "^2.0.6"
+    "@floating-ui/dom" "^1.0.1"
+    "@popperjs/core" "npm:@sxzz/popperjs-es@^2.11.7"
+    "@types/lodash" "^4.14.182"
+    "@types/lodash-es" "^4.17.6"
+    "@vueuse/core" "^9.1.0"
+    async-validator "^4.2.5"
+    dayjs "^1.11.3"
+    escape-html "^1.0.3"
+    lodash "^4.17.21"
+    lodash-es "^4.17.21"
+    lodash-unified "^1.0.2"
+    memoize-one "^6.0.0"
+    normalize-wheel-es "^1.2.0"
+
+esbuild@^0.17.5:
+  version "0.17.14"
+  resolved "https://registry.npmmirror.com/esbuild/-/esbuild-0.17.14.tgz#d61a22de751a3133f3c6c7f9c1c3e231e91a3245"
+  integrity sha512-vOO5XhmVj/1XQR9NQ1UPq6qvMYL7QFJU57J5fKBKBKxp17uDt5PgxFDb4A2nEiXhr1qQs4x0F5+66hVVw4ruNw==
+  optionalDependencies:
+    "@esbuild/android-arm" "0.17.14"
+    "@esbuild/android-arm64" "0.17.14"
+    "@esbuild/android-x64" "0.17.14"
+    "@esbuild/darwin-arm64" "0.17.14"
+    "@esbuild/darwin-x64" "0.17.14"
+    "@esbuild/freebsd-arm64" "0.17.14"
+    "@esbuild/freebsd-x64" "0.17.14"
+    "@esbuild/linux-arm" "0.17.14"
+    "@esbuild/linux-arm64" "0.17.14"
+    "@esbuild/linux-ia32" "0.17.14"
+    "@esbuild/linux-loong64" "0.17.14"
+    "@esbuild/linux-mips64el" "0.17.14"
+    "@esbuild/linux-ppc64" "0.17.14"
+    "@esbuild/linux-riscv64" "0.17.14"
+    "@esbuild/linux-s390x" "0.17.14"
+    "@esbuild/linux-x64" "0.17.14"
+    "@esbuild/netbsd-x64" "0.17.14"
+    "@esbuild/openbsd-x64" "0.17.14"
+    "@esbuild/sunos-x64" "0.17.14"
+    "@esbuild/win32-arm64" "0.17.14"
+    "@esbuild/win32-ia32" "0.17.14"
+    "@esbuild/win32-x64" "0.17.14"
+
+escape-html@^1.0.3:
+  version "1.0.3"
+  resolved "https://registry.npmmirror.com/escape-html/-/escape-html-1.0.3.tgz#0258eae4d3d0c0974de1c169188ef0051d1d1988"
+  integrity sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==
+
+estree-walker@^2.0.2:
+  version "2.0.2"
+  resolved "https://registry.npmmirror.com/estree-walker/-/estree-walker-2.0.2.tgz#52f010178c2a4c117a7757cfe942adb7d2da4cac"
+  integrity sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==
+
+fill-range@^7.0.1:
+  version "7.0.1"
+  resolved "https://registry.npmmirror.com/fill-range/-/fill-range-7.0.1.tgz#1919a6a7c75fe38b2c7c77e5198535da9acdda40"
+  integrity sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==
+  dependencies:
+    to-regex-range "^5.0.1"
+
+follow-redirects@^1.15.0:
+  version "1.15.2"
+  resolved "https://registry.npmmirror.com/follow-redirects/-/follow-redirects-1.15.2.tgz#b460864144ba63f2681096f274c4e57026da2c13"
+  integrity sha512-VQLG33o04KaQ8uYi2tVNbdrWp1QWxNNea+nmIB4EVM28v0hmP17z7aG1+wAkNzVq4KeXTq3221ye5qTJP91JwA==
+
+form-data@^4.0.0:
+  version "4.0.0"
+  resolved "https://registry.npmmirror.com/form-data/-/form-data-4.0.0.tgz#93919daeaf361ee529584b9b31664dc12c9fa452"
+  integrity sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==
+  dependencies:
+    asynckit "^0.4.0"
+    combined-stream "^1.0.8"
+    mime-types "^2.1.12"
+
+fsevents@~2.3.2:
+  version "2.3.2"
+  resolved "https://registry.npmmirror.com/fsevents/-/fsevents-2.3.2.tgz#8a526f78b8fdf4623b709e0b975c52c24c02fd1a"
+  integrity sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==
+
+function-bind@^1.1.1:
+  version "1.1.1"
+  resolved "https://registry.npmmirror.com/function-bind/-/function-bind-1.1.1.tgz#a56899d3ea3c9bab874bb9773b7c5ede92f4895d"
+  integrity sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==
+
+geotiff@2.0.4:
+  version "2.0.4"
+  resolved "https://registry.npmmirror.com/geotiff/-/geotiff-2.0.4.tgz#d6f231fdd76186aba21c61823ed759fcbf5d4f86"
+  integrity sha512-aG8h9bJccGusioPsEWsEqx8qdXpZN71A20WCvRKGxcnHSOWLKmC5ZmsAmodfxb9TRQvs+89KikGuPzxchhA+Uw==
+  dependencies:
+    "@petamoriken/float16" "^3.4.7"
+    lerc "^3.0.0"
+    lru-cache "^6.0.0"
+    pako "^2.0.4"
+    parse-headers "^2.0.2"
+    web-worker "^1.2.0"
+    xml-utils "^1.0.2"
+
+glob-parent@~5.1.2:
+  version "5.1.2"
+  resolved "https://registry.npmmirror.com/glob-parent/-/glob-parent-5.1.2.tgz#869832c58034fe68a4093c17dc15e8340d8401c4"
+  integrity sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==
+  dependencies:
+    is-glob "^4.0.1"
+
+has@^1.0.3:
+  version "1.0.3"
+  resolved "https://registry.npmmirror.com/has/-/has-1.0.3.tgz#722d7cbfc1f6aa8241f16dd814e011e1f41e8796"
+  integrity sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==
+  dependencies:
+    function-bind "^1.1.1"
+
+he@^1.2.0:
+  version "1.2.0"
+  resolved "https://registry.npmmirror.com/he/-/he-1.2.0.tgz#84ae65fa7eafb165fddb61566ae14baf05664f0f"
+  integrity sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==
+
+ieee754@^1.1.12:
+  version "1.2.1"
+  resolved "https://registry.npmmirror.com/ieee754/-/ieee754-1.2.1.tgz#8eb7a10a63fff25d15a57b001586d177d1b0d352"
+  integrity sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==
+
+immutable@^4.0.0:
+  version "4.3.0"
+  resolved "https://registry.npmmirror.com/immutable/-/immutable-4.3.0.tgz#eb1738f14ffb39fd068b1dbe1296117484dd34be"
+  integrity sha512-0AOCmOip+xgJwEVTQj1EfiDDOkPmuyllDuTuEX+DDXUgapLAsBIfkg3sxCYyCEA8mQqZrrxPUGjcOQ2JS3WLkg==
+
+is-binary-path@~2.1.0:
+  version "2.1.0"
+  resolved "https://registry.npmmirror.com/is-binary-path/-/is-binary-path-2.1.0.tgz#ea1f7f3b80f064236e83470f86c09c254fb45b09"
+  integrity sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==
+  dependencies:
+    binary-extensions "^2.0.0"
+
+is-core-module@^2.9.0:
+  version "2.11.0"
+  resolved "https://registry.npmmirror.com/is-core-module/-/is-core-module-2.11.0.tgz#ad4cb3e3863e814523c96f3f58d26cc570ff0144"
+  integrity sha512-RRjxlvLDkD1YJwDbroBHMb+cukurkDWNyHx7D3oNB5x9rb5ogcksMC5wHCadcXoo67gVr/+3GFySh3134zi6rw==
+  dependencies:
+    has "^1.0.3"
+
+is-extglob@^2.1.1:
+  version "2.1.1"
+  resolved "https://registry.npmmirror.com/is-extglob/-/is-extglob-2.1.1.tgz#a88c02535791f02ed37c76a1b9ea9773c833f8c2"
+  integrity sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==
+
+is-glob@^4.0.1, is-glob@~4.0.1:
+  version "4.0.3"
+  resolved "https://registry.npmmirror.com/is-glob/-/is-glob-4.0.3.tgz#64f61e42cbbb2eec2071a9dac0b28ba1e65d5084"
+  integrity sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==
+  dependencies:
+    is-extglob "^2.1.1"
+
+is-number@^7.0.0:
+  version "7.0.0"
+  resolved "https://registry.npmmirror.com/is-number/-/is-number-7.0.0.tgz#7535345b896734d5f80c4d06c50955527a14f12b"
+  integrity sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==
+
+json-stringify-pretty-compact@^2.0.0:
+  version "2.0.0"
+  resolved "https://registry.npmmirror.com/json-stringify-pretty-compact/-/json-stringify-pretty-compact-2.0.0.tgz#e77c419f52ff00c45a31f07f4c820c2433143885"
+  integrity sha512-WRitRfs6BGq4q8gTgOy4ek7iPFXjbra0H3PmDLKm2xnZ+Gh1HUhiKGgCZkSPNULlP7mvfu6FV/mOLhCarspADQ==
+
+lerc@^3.0.0:
+  version "3.0.0"
+  resolved "https://registry.npmmirror.com/lerc/-/lerc-3.0.0.tgz#36f36fbd4ba46f0abf4833799fff2e7d6865f5cb"
+  integrity sha512-Rm4J/WaHhRa93nCN2mwWDZFoRVF18G1f47C+kvQWyHGEZxFpTUi73p7lMVSAndyxGt6lJ2/CFbOcf9ra5p8aww==
+
+lodash-es@^4.17.21:
+  version "4.17.21"
+  resolved "https://registry.npmmirror.com/lodash-es/-/lodash-es-4.17.21.tgz#43e626c46e6591b7750beb2b50117390c609e3ee"
+  integrity sha512-mKnC+QJ9pWVzv+C4/U3rRsHapFfHvQFoFB92e52xeyGMcX6/OlIl78je1u8vePzYZSkkogMPJ2yjxxsb89cxyw==
+
+lodash-unified@^1.0.2:
+  version "1.0.3"
+  resolved "https://registry.npmmirror.com/lodash-unified/-/lodash-unified-1.0.3.tgz#80b1eac10ed2eb02ed189f08614a29c27d07c894"
+  integrity sha512-WK9qSozxXOD7ZJQlpSqOT+om2ZfcT4yO+03FuzAHD0wF6S0l0090LRPDx3vhTTLZ8cFKpBn+IOcVXK6qOcIlfQ==
+
+lodash@^4.17.21:
+  version "4.17.21"
+  resolved "https://registry.npmmirror.com/lodash/-/lodash-4.17.21.tgz#679591c564c3bffaae8454cf0b3df370c3d6911c"
+  integrity sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==
+
+lru-cache@^6.0.0:
+  version "6.0.0"
+  resolved "https://registry.npmmirror.com/lru-cache/-/lru-cache-6.0.0.tgz#6d6fe6570ebd96aaf90fcad1dafa3b2566db3a94"
+  integrity sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==
+  dependencies:
+    yallist "^4.0.0"
+
+magic-string@^0.25.7:
+  version "0.25.9"
+  resolved "https://registry.npmmirror.com/magic-string/-/magic-string-0.25.9.tgz#de7f9faf91ef8a1c91d02c2e5314c8277dbcdd1c"
+  integrity sha512-RmF0AsMzgt25qzqqLc1+MbHmhdx0ojF2Fvs4XnOqz2ZOBXzzkEwc/dJQZCYHAn7v1jbVOjAZfK8msRn4BxO4VQ==
+  dependencies:
+    sourcemap-codec "^1.4.8"
+
+mapbox-to-css-font@^2.4.1:
+  version "2.4.2"
+  resolved "https://registry.npmmirror.com/mapbox-to-css-font/-/mapbox-to-css-font-2.4.2.tgz#a9e31b363ad8ca881cd339ca99f2d2a6b02ea5dd"
+  integrity sha512-f+NBjJJY4T3dHtlEz1wCG7YFlkODEjFIYlxDdLIDMNpkSksqTt+l/d4rjuwItxuzkuMFvPyrjzV2lxRM4ePcIA==
+
+memoize-one@^6.0.0:
+  version "6.0.0"
+  resolved "https://registry.npmmirror.com/memoize-one/-/memoize-one-6.0.0.tgz#b2591b871ed82948aee4727dc6abceeeac8c1045"
+  integrity sha512-rkpe71W0N0c0Xz6QD0eJETuWAJGnJ9afsl1srmwPrI+yBCkge5EycXXbYRyvL29zZVUWQCY7InPRCv3GDXuZNw==
+
+mime-db@1.52.0:
+  version "1.52.0"
+  resolved "https://registry.npmmirror.com/mime-db/-/mime-db-1.52.0.tgz#bbabcdc02859f4987301c856e3387ce5ec43bf70"
+  integrity sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==
+
+mime-types@^2.1.12:
+  version "2.1.35"
+  resolved "https://registry.npmmirror.com/mime-types/-/mime-types-2.1.35.tgz#381a871b62a734450660ae3deee44813f70d959a"
+  integrity sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==
+  dependencies:
+    mime-db "1.52.0"
+
+minimatch@^6.1.6:
+  version "6.2.0"
+  resolved "https://registry.npmmirror.com/minimatch/-/minimatch-6.2.0.tgz#2b70fd13294178c69c04dfc05aebdb97a4e79e42"
+  integrity sha512-sauLxniAmvnhhRjFwPNnJKaPFYyddAgbYdeUpHULtCT/GhzdCx/MDNy+Y40lBxTQUrMzDE8e0S43Z5uqfO0REg==
+  dependencies:
+    brace-expansion "^2.0.1"
+
+minimist@^1.2.6:
+  version "1.2.8"
+  resolved "https://registry.npmmirror.com/minimist/-/minimist-1.2.8.tgz#c1a464e7693302e082a075cee0c057741ac4772c"
+  integrity sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==
+
+muggle-string@^0.2.2:
+  version "0.2.2"
+  resolved "https://registry.npmmirror.com/muggle-string/-/muggle-string-0.2.2.tgz#786aa53fea1652c61c6a59e1f839292b262bc72a"
+  integrity sha512-YVE1mIJ4VpUMqZObFndk9CJu6DBJR/GB13p3tXuNbwD4XExaI5EOuRl6BHeIDxIqXZVxSfAC+y6U1Z/IxCfKUg==
+
+nanoid@^3.3.4:
+  version "3.3.6"
+  resolved "https://registry.npmmirror.com/nanoid/-/nanoid-3.3.6.tgz#443380c856d6e9f9824267d960b4236ad583ea4c"
+  integrity sha512-BGcqMMJuToF7i1rt+2PWSNVnWIkGCU78jBG3RxO/bZlnZPK2Cmi2QaffxGO/2RvWi9sL+FAiRiXMgsyxQ1DIDA==
+
+normalize-path@^3.0.0, normalize-path@~3.0.0:
+  version "3.0.0"
+  resolved "https://registry.npmmirror.com/normalize-path/-/normalize-path-3.0.0.tgz#0dcd69ff23a1c9b11fd0978316644a0388216a65"
+  integrity sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==
+
+normalize-wheel-es@^1.2.0:
+  version "1.2.0"
+  resolved "https://registry.npmmirror.com/normalize-wheel-es/-/normalize-wheel-es-1.2.0.tgz#0fa2593d619f7245a541652619105ab076acf09e"
+  integrity sha512-Wj7+EJQ8mSuXr2iWfnujrimU35R2W4FAErEyTmJoJ7ucwTn2hOUSsRehMb5RSYkxXGTM7Y9QpvPmp++w5ftoJw==
+
+ol-mapbox-style@^8.0.5:
+  version "8.2.1"
+  resolved "https://registry.npmmirror.com/ol-mapbox-style/-/ol-mapbox-style-8.2.1.tgz#0f0c252b6495853a137d7e4dd3f915fab664b356"
+  integrity sha512-3kBBuZC627vDL8vnUdfVbCbfkhkcZj2kXPHQcuLhC4JJEA+XkEVEtEde8x8+AZctRbHwBkSiubTPaRukgLxIRw==
+  dependencies:
+    "@mapbox/mapbox-gl-style-spec" "^13.23.1"
+    mapbox-to-css-font "^2.4.1"
+
+ol@^6.5.0:
+  version "6.15.1"
+  resolved "https://registry.npmmirror.com/ol/-/ol-6.15.1.tgz#364f459939ef71f970b2376a821a896529f65e3a"
+  integrity sha512-ZG2CKTpJ8Q+tPywYysVwPk+yevwJzlbwjRKhoCvd7kLVWMbfBl1O/+Kg/yrZZrhG9FNXbFH4GeOZ5yVRqo3P4w==
+  dependencies:
+    geotiff "2.0.4"
+    ol-mapbox-style "^8.0.5"
+    pbf "3.2.1"
+    rbush "^3.0.1"
+
+pako@^2.0.4:
+  version "2.1.0"
+  resolved "https://registry.npmmirror.com/pako/-/pako-2.1.0.tgz#266cc37f98c7d883545d11335c00fbd4062c9a86"
+  integrity sha512-w+eufiZ1WuJYgPXbV/PO3NCMEc3xqylkKHzp8bxp1uW4qaSNQUkwmLLEc3kKsfz8lpV1F8Ht3U1Cm+9Srog2ug==
+
+parse-headers@^2.0.2:
+  version "2.0.5"
+  resolved "https://registry.npmmirror.com/parse-headers/-/parse-headers-2.0.5.tgz#069793f9356a54008571eb7f9761153e6c770da9"
+  integrity sha512-ft3iAoLOB/MlwbNXgzy43SWGP6sQki2jQvAyBg/zDFAgr9bfNWZIUj42Kw2eJIl8kEi4PbgE6U1Zau/HwI75HA==
+
+path-parse@^1.0.7:
+  version "1.0.7"
+  resolved "https://registry.npmmirror.com/path-parse/-/path-parse-1.0.7.tgz#fbc114b60ca42b30d9daf5858e4bd68bbedb6735"
+  integrity sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==
+
+pbf@3.2.1:
+  version "3.2.1"
+  resolved "https://registry.npmmirror.com/pbf/-/pbf-3.2.1.tgz#b4c1b9e72af966cd82c6531691115cc0409ffe2a"
+  integrity sha512-ClrV7pNOn7rtmoQVF4TS1vyU0WhYRnP92fzbfF75jAIwpnzdJXf8iTd4CMEqO4yUenH6NDqLiwjqlh6QgZzgLQ==
+  dependencies:
+    ieee754 "^1.1.12"
+    resolve-protobuf-schema "^2.1.0"
+
+picocolors@^1.0.0:
+  version "1.0.0"
+  resolved "https://registry.npmmirror.com/picocolors/-/picocolors-1.0.0.tgz#cb5bdc74ff3f51892236eaf79d68bc44564ab81c"
+  integrity sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==
+
+picomatch@^2.0.4, picomatch@^2.2.1:
+  version "2.3.1"
+  resolved "https://registry.npmmirror.com/picomatch/-/picomatch-2.3.1.tgz#3ba3833733646d9d3e4995946c1365a67fb07a42"
+  integrity sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==
+
+postcss@^8.1.10, postcss@^8.4.21:
+  version "8.4.21"
+  resolved "https://registry.npmmirror.com/postcss/-/postcss-8.4.21.tgz#c639b719a57efc3187b13a1d765675485f4134f4"
+  integrity sha512-tP7u/Sn/dVxK2NnruI4H9BG+x+Wxz6oeZ1cJ8P6G/PZY0IKk4k/63TDsQf2kQq3+qoJeLm2kIBUNlZe3zgb4Zg==
+  dependencies:
+    nanoid "^3.3.4"
+    picocolors "^1.0.0"
+    source-map-js "^1.0.2"
+
+protocol-buffers-schema@^3.3.1:
+  version "3.6.0"
+  resolved "https://registry.npmmirror.com/protocol-buffers-schema/-/protocol-buffers-schema-3.6.0.tgz#77bc75a48b2ff142c1ad5b5b90c94cd0fa2efd03"
+  integrity sha512-TdDRD+/QNdrCGCE7v8340QyuXd4kIWIgapsE2+n/SaGiSSbomYl4TjHlvIoCWRpE7wFt02EpB35VVA2ImcBVqw==
+
+proxy-from-env@^1.1.0:
+  version "1.1.0"
+  resolved "https://registry.npmmirror.com/proxy-from-env/-/proxy-from-env-1.1.0.tgz#e102f16ca355424865755d2c9e8ea4f24d58c3e2"
+  integrity sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==
+
+quickselect@^2.0.0:
+  version "2.0.0"
+  resolved "https://registry.npmmirror.com/quickselect/-/quickselect-2.0.0.tgz#f19680a486a5eefb581303e023e98faaf25dd018"
+  integrity sha512-RKJ22hX8mHe3Y6wH/N3wCM6BWtjaxIyyUIkpHOvfFnxdI4yD4tBXEBKSbriGujF6jnSVkJrffuo6vxACiSSxIw==
+
+rbush@^3.0.1:
+  version "3.0.1"
+  resolved "https://registry.npmmirror.com/rbush/-/rbush-3.0.1.tgz#5fafa8a79b3b9afdfe5008403a720cc1de882ecf"
+  integrity sha512-XRaVO0YecOpEuIvbhbpTrZgoiI6xBlz6hnlr6EHhd+0x9ase6EmeN+hdwwUaJvLcsFFQ8iWVF1GAK1yB0BWi0w==
+  dependencies:
+    quickselect "^2.0.0"
+
+readdirp@~3.6.0:
+  version "3.6.0"
+  resolved "https://registry.npmmirror.com/readdirp/-/readdirp-3.6.0.tgz#74a370bd857116e245b29cc97340cd431a02a6c7"
+  integrity sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==
+  dependencies:
+    picomatch "^2.2.1"
+
+resolve-protobuf-schema@^2.1.0:
+  version "2.1.0"
+  resolved "https://registry.npmmirror.com/resolve-protobuf-schema/-/resolve-protobuf-schema-2.1.0.tgz#9ca9a9e69cf192bbdaf1006ec1973948aa4a3758"
+  integrity sha512-kI5ffTiZWmJaS/huM8wZfEMer1eRd7oJQhDuxeCLe3t7N7mX3z94CN0xPxBQxFYQTSNz9T0i+v6inKqSdK8xrQ==
+  dependencies:
+    protocol-buffers-schema "^3.3.1"
+
+resolve@^1.22.1:
+  version "1.22.1"
+  resolved "https://registry.npmmirror.com/resolve/-/resolve-1.22.1.tgz#27cb2ebb53f91abb49470a928bba7558066ac177"
+  integrity sha512-nBpuuYuY5jFsli/JIs1oldw6fOQCBioohqWZg/2hiaOybXOft4lonv85uDOKXdf8rhyK159cxU5cDcK/NKk8zw==
+  dependencies:
+    is-core-module "^2.9.0"
+    path-parse "^1.0.7"
+    supports-preserve-symlinks-flag "^1.0.0"
+
+rollup@^3.18.0:
+  version "3.20.2"
+  resolved "https://registry.npmmirror.com/rollup/-/rollup-3.20.2.tgz#f798c600317f216de2e4ad9f4d9ab30a89b690ff"
+  integrity sha512-3zwkBQl7Ai7MFYQE0y1MeQ15+9jsi7XxfrqwTb/9EK8D9C9+//EBR4M+CuA1KODRaNbFez/lWxA5vhEGZp4MUg==
+  optionalDependencies:
+    fsevents "~2.3.2"
+
+rw@^1.3.3:
+  version "1.3.3"
+  resolved "https://registry.npmmirror.com/rw/-/rw-1.3.3.tgz#3f862dfa91ab766b14885ef4d01124bfda074fb4"
+  integrity sha512-PdhdWy89SiZogBLaw42zdeqtRJ//zFd2PgQavcICDUgJT5oW10QCRKbJ6bg4r0/UY2M6BWd5tkxuGFRvCkgfHQ==
+
+sass@^1.60.0:
+  version "1.60.0"
+  resolved "https://registry.npmmirror.com/sass/-/sass-1.60.0.tgz#657f0c23a302ac494b09a5ba8497b739fb5b5a81"
+  integrity sha512-updbwW6fNb5gGm8qMXzVO7V4sWf7LMXnMly/JEyfbfERbVH46Fn6q02BX7/eHTdKpE7d+oTkMMQpFWNUMfFbgQ==
+  dependencies:
+    chokidar ">=3.0.0 <4.0.0"
+    immutable "^4.0.0"
+    source-map-js ">=0.6.2 <2.0.0"
+
+sort-asc@^0.1.0:
+  version "0.1.0"
+  resolved "https://registry.npmmirror.com/sort-asc/-/sort-asc-0.1.0.tgz#ab799df61fc73ea0956c79c4b531ed1e9e7727e9"
+  integrity sha512-jBgdDd+rQ+HkZF2/OHCmace5dvpos/aWQpcxuyRs9QUbPRnkEJmYVo81PIGpjIdpOcsnJ4rGjStfDHsbn+UVyw==
+
+sort-desc@^0.1.1:
+  version "0.1.1"
+  resolved "https://registry.npmmirror.com/sort-desc/-/sort-desc-0.1.1.tgz#198b8c0cdeb095c463341861e3925d4ee359a9ee"
+  integrity sha512-jfZacW5SKOP97BF5rX5kQfJmRVZP5/adDUTY8fCSPvNcXDVpUEe2pr/iKGlcyZzchRJZrswnp68fgk3qBXgkJw==
+
+sort-object@^0.3.2:
+  version "0.3.2"
+  resolved "https://registry.npmmirror.com/sort-object/-/sort-object-0.3.2.tgz#98e0d199ede40e07c61a84403c61d6c3b290f9e2"
+  integrity sha512-aAQiEdqFTTdsvUFxXm3umdo04J7MRljoVGbBlkH7BgNsMvVNAJyGj7C/wV1A8wHWAJj/YikeZbfuCKqhggNWGA==
+  dependencies:
+    sort-asc "^0.1.0"
+    sort-desc "^0.1.1"
+
+"source-map-js@>=0.6.2 <2.0.0", source-map-js@^1.0.2:
+  version "1.0.2"
+  resolved "https://registry.npmmirror.com/source-map-js/-/source-map-js-1.0.2.tgz#adbc361d9c62df380125e7f161f71c826f1e490c"
+  integrity sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw==
+
+source-map@^0.6.1:
+  version "0.6.1"
+  resolved "https://registry.npmmirror.com/source-map/-/source-map-0.6.1.tgz#74722af32e9614e9c287a8d0bbde48b5e2f1a263"
+  integrity sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==
+
+sourcemap-codec@^1.4.8:
+  version "1.4.8"
+  resolved "https://registry.npmmirror.com/sourcemap-codec/-/sourcemap-codec-1.4.8.tgz#ea804bd94857402e6992d05a38ef1ae35a9ab4c4"
+  integrity sha512-9NykojV5Uih4lgo5So5dtw+f0JgJX30KCNI8gwhz2J9A15wD0Ml6tjHKwf6fTSa6fAdVBdZeNOs9eJ71qCk8vA==
+
+supports-preserve-symlinks-flag@^1.0.0:
+  version "1.0.0"
+  resolved "https://registry.npmmirror.com/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz#6eda4bd344a3c94aea376d4cc31bc77311039e09"
+  integrity sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==
+
+to-regex-range@^5.0.1:
+  version "5.0.1"
+  resolved "https://registry.npmmirror.com/to-regex-range/-/to-regex-range-5.0.1.tgz#1648c44aae7c8d988a326018ed72f5b4dd0392e4"
+  integrity sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==
+  dependencies:
+    is-number "^7.0.0"
+
+typescript@^4.9.3:
+  version "4.9.5"
+  resolved "https://registry.npmmirror.com/typescript/-/typescript-4.9.5.tgz#095979f9bcc0d09da324d58d03ce8f8374cbe65a"
+  integrity sha512-1FXk9E2Hm+QzZQ7z+McJiHL4NW1F2EzMu9Nq9i3zAaGqibafqYwCVU6WyWAuyQRRzOlxou8xZSyXLEN8oKj24g==
+
+vite@^4.2.0:
+  version "4.2.1"
+  resolved "https://registry.npmmirror.com/vite/-/vite-4.2.1.tgz#6c2eb337b0dfd80a9ded5922163b94949d7fc254"
+  integrity sha512-7MKhqdy0ISo4wnvwtqZkjke6XN4taqQ2TBaTccLIpOKv7Vp2h4Y+NpmWCnGDeSvvn45KxvWgGyb0MkHvY1vgbg==
+  dependencies:
+    esbuild "^0.17.5"
+    postcss "^8.4.21"
+    resolve "^1.22.1"
+    rollup "^3.18.0"
+  optionalDependencies:
+    fsevents "~2.3.2"
+
+vue-demi@*:
+  version "0.13.11"
+  resolved "https://registry.npmmirror.com/vue-demi/-/vue-demi-0.13.11.tgz#7d90369bdae8974d87b1973564ad390182410d99"
+  integrity sha512-IR8HoEEGM65YY3ZJYAjMlKygDQn25D5ajNFNoKh9RSDMQtlzCxtfQjdQgv9jjK+m3377SsJXY8ysq8kLCZL25A==
+
+vue-router@^4.1.6:
+  version "4.1.6"
+  resolved "https://registry.npmmirror.com/vue-router/-/vue-router-4.1.6.tgz#b70303737e12b4814578d21d68d21618469375a1"
+  integrity sha512-DYWYwsG6xNPmLq/FmZn8Ip+qrhFEzA14EI12MsMgVxvHFDYvlr4NXpVF5hrRH1wVcDP8fGi5F4rxuJSl8/r+EQ==
+  dependencies:
+    "@vue/devtools-api" "^6.4.5"
+
+vue-template-compiler@^2.7.14:
+  version "2.7.14"
+  resolved "https://registry.npmmirror.com/vue-template-compiler/-/vue-template-compiler-2.7.14.tgz#4545b7dfb88090744c1577ae5ac3f964e61634b1"
+  integrity sha512-zyA5Y3ArvVG0NacJDkkzJuPQDF8RFeRlzV2vLeSnhSpieO6LK2OVbdLPi5MPPs09Ii+gMO8nY4S3iKQxBxDmWQ==
+  dependencies:
+    de-indent "^1.0.2"
+    he "^1.2.0"
+
+vue-tsc@^1.2.0:
+  version "1.2.0"
+  resolved "https://registry.npmmirror.com/vue-tsc/-/vue-tsc-1.2.0.tgz#2b64b960cc96208492541394423ace589a461be6"
+  integrity sha512-rIlzqdrhyPYyLG9zxsVRa+JEseeS9s8F2BbVVVWRRsTZvJO2BbhLEb2HW3MY+DFma0378tnIqs+vfTzbcQtRFw==
+  dependencies:
+    "@volar/vue-language-core" "1.2.0"
+    "@volar/vue-typescript" "1.2.0"
+
+vue@^3.2.47:
+  version "3.2.47"
+  resolved "https://registry.npmmirror.com/vue/-/vue-3.2.47.tgz#3eb736cbc606fc87038dbba6a154707c8a34cff0"
+  integrity sha512-60188y/9Dc9WVrAZeUVSDxRQOZ+z+y5nO2ts9jWXSTkMvayiWxCWOWtBQoYjLeccfXkiiPZWAHcV+WTPhkqJHQ==
+  dependencies:
+    "@vue/compiler-dom" "3.2.47"
+    "@vue/compiler-sfc" "3.2.47"
+    "@vue/runtime-dom" "3.2.47"
+    "@vue/server-renderer" "3.2.47"
+    "@vue/shared" "3.2.47"
+
+vuex@^4.1.0:
+  version "4.1.0"
+  resolved "https://registry.npmmirror.com/vuex/-/vuex-4.1.0.tgz#aa1b3ea5c7385812b074c86faeeec2217872e36c"
+  integrity sha512-hmV6UerDrPcgbSy9ORAtNXDr9M4wlNP4pEFKye4ujJF8oqgFFuxDCdOLS3eNoRTtq5O3hoBDh9Doj1bQMYHRbQ==
+  dependencies:
+    "@vue/devtools-api" "^6.0.0-beta.11"
+
+web-worker@^1.2.0:
+  version "1.2.0"
+  resolved "https://registry.npmmirror.com/web-worker/-/web-worker-1.2.0.tgz#5d85a04a7fbc1e7db58f66595d7a3ac7c9c180da"
+  integrity sha512-PgF341avzqyx60neE9DD+XS26MMNMoUQRz9NOZwW32nPQrF6p77f1htcnjBSEV8BGMKZ16choqUG4hyI0Hx7mA==
+
+xml-utils@^1.0.2:
+  version "1.3.0"
+  resolved "https://registry.npmmirror.com/xml-utils/-/xml-utils-1.3.0.tgz#f1043534e3ac3deda12ddab39f8442e16da98ebb"
+  integrity sha512-i4PIrX33Wd66dvwo4syicwlwmnr6wuvvn4f2ku9hA67C2Uk62Xubczuhct+Evnd12/DV71qKNeDdJwES8HX1RA==
+
+yallist@^4.0.0:
+  version "4.0.0"
+  resolved "https://registry.npmmirror.com/yallist/-/yallist-4.0.0.tgz#9bb92790d9c0effec63be73519e11a35019a3a72"
+  integrity sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==