Преглед изворни кода

新增 map 底图,界面调整

linqilong пре 1 година
родитељ
комит
b918dc8efa

Разлика између датотеке није приказан због своје велике величине
+ 831 - 3
package-lock.json


+ 4 - 0
package.json

@@ -16,6 +16,10 @@
     "format": "prettier --write src/"
     "format": "prettier --write src/"
   },
   },
   "dependencies": {
   "dependencies": {
+    "@antv/l7": "^2.22.1",
+    "@antv/l7-maps": "^2.22.1",
+    "@turf/bbox": "^7.1.0",
+    "@turf/helpers": "^7.1.0",
     "@yzfe/svgicon": "^1.2.2",
     "@yzfe/svgicon": "^1.2.2",
     "@yzfe/vue-svgicon": "^5.0.3",
     "@yzfe/vue-svgicon": "^5.0.3",
     "pinia": "^2.2.4",
     "pinia": "^2.2.4",

+ 8 - 0
src/.env.development

@@ -0,0 +1,8 @@
+# 页面标题
+VUE_APP_TITLE = 太湖流域水政执法知识平台
+
+# 开发环境配置
+NODE_ENV = 'development'
+
+# 知识库后台管理系统/开发环境
+VUE_APP_BASE_API = '/base_api'

+ 2 - 2
src/App.vue

@@ -1,7 +1,7 @@
 <script lang="ts" setup>
 <script lang="ts" setup>
-import { RouterView } from 'vue-router'
+import {RouterView} from 'vue-router'
 </script>
 </script>
 
 
 <template>
 <template>
-  <RouterView />
+  <RouterView/>
 </template>
 </template>

BIN
src/assets/images/card/header01.png


+ 4 - 1
src/assets/images/logo.svg

@@ -1 +1,4 @@
-<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 261.76 226.69"><path d="M161.096.001l-30.225 52.351L100.647.001H-.005l130.877 226.688L261.749.001z" fill="#41b883"/><path d="M161.096.001l-30.225 52.351L100.647.001H52.346l78.526 136.01L209.398.001z" fill="#34495e"/></svg>
+<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 261.76 226.69">
+    <path d="M161.096.001l-30.225 52.351L100.647.001H-.005l130.877 226.688L261.749.001z" fill="#41b883"/>
+    <path d="M161.096.001l-30.225 52.351L100.647.001H52.346l78.526 136.01L209.398.001z" fill="#34495e"/>
+</svg>

+ 29 - 30
src/assets/styles/index.scss

@@ -8,14 +8,13 @@ body {
   -moz-osx-font-smoothing: grayscale;
   -moz-osx-font-smoothing: grayscale;
   -webkit-font-smoothing: antialiased;
   -webkit-font-smoothing: antialiased;
   text-rendering: optimizeLegibility;
   text-rendering: optimizeLegibility;
-  font-family:
-    Helvetica Neue,
-    Helvetica,
-    PingFang SC,
-    Hiragino Sans GB,
-    Microsoft YaHei,
-    Arial,
-    sans-serif;
+  font-family: Helvetica Neue,
+  Helvetica,
+  PingFang SC,
+  Hiragino Sans GB,
+  Microsoft YaHei,
+  Arial,
+  sans-serif;
 }
 }
 
 
 label {
 label {
@@ -27,6 +26,16 @@ html {
   box-sizing: border-box;
   box-sizing: border-box;
 }
 }
 
 
+ul {
+  margin: 0;
+  padding: 0;
+
+  li {
+    list-style: none;
+  }
+
+}
+
 #app {
 #app {
   height: 100%;
   height: 100%;
 }
 }
@@ -47,10 +56,7 @@ html {
   --tw-skew-y: 0;
   --tw-skew-y: 0;
   --tw-scale-x: 1;
   --tw-scale-x: 1;
   --tw-scale-y: 1;
   --tw-scale-y: 1;
-  --tw-transform: translateX(var(--tw-translate-x))
-    translateY(var(--tw-translate-y)) rotate(var(--tw-rotate))
-    skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x))
-    scaleY(var(--tw-scale-y));
+  --tw-transform: translateX(var(--tw-translate-x)) translateY(var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y));
   --tw-border-opacity: 1;
   --tw-border-opacity: 1;
   border-color: rgba(229, 231, 235, var(--tw-border-opacity));
   border-color: rgba(229, 231, 235, var(--tw-border-opacity));
   --tw-ring-offset-shadow: 0 0 #0000;
   --tw-ring-offset-shadow: 0 0 #0000;
@@ -65,9 +71,7 @@ html {
   --tw-saturate: var(--tw-empty,);
   --tw-saturate: var(--tw-empty,);
   --tw-sepia: var(--tw-empty,);
   --tw-sepia: var(--tw-empty,);
   --tw-drop-shadow: var(--tw-empty,);
   --tw-drop-shadow: var(--tw-empty,);
-  --tw-filter: var(--tw-blur) var(--tw-brightness) var(--tw-contrast)
-    var(--tw-grayscale) var(--tw-hue-rotate) var(--tw-invert) var(--tw-saturate)
-    var(--tw-sepia) var(--tw-drop-shadow);
+  --tw-filter: var(--tw-blur) var(--tw-brightness) var(--tw-contrast) var(--tw-grayscale) var(--tw-hue-rotate) var(--tw-invert) var(--tw-saturate) var(--tw-sepia) var(--tw-drop-shadow);
   --tw-backdrop-blur: var(--tw-empty,);
   --tw-backdrop-blur: var(--tw-empty,);
   --tw-backdrop-brightness: var(--tw-empty,);
   --tw-backdrop-brightness: var(--tw-empty,);
   --tw-backdrop-contrast: var(--tw-empty,);
   --tw-backdrop-contrast: var(--tw-empty,);
@@ -77,11 +81,7 @@ html {
   --tw-backdrop-opacity: var(--tw-empty,);
   --tw-backdrop-opacity: var(--tw-empty,);
   --tw-backdrop-saturate: var(--tw-empty,);
   --tw-backdrop-saturate: var(--tw-empty,);
   --tw-backdrop-sepia: var(--tw-empty,);
   --tw-backdrop-sepia: var(--tw-empty,);
-  --tw-backdrop-filter: var(--tw-backdrop-blur) var(--tw-backdrop-brightness)
-    var(--tw-backdrop-contrast) var(--tw-backdrop-grayscale)
-    var(--tw-backdrop-hue-rotate) var(--tw-backdrop-invert)
-    var(--tw-backdrop-opacity) var(--tw-backdrop-saturate)
-    var(--tw-backdrop-sepia);
+  --tw-backdrop-filter: var(--tw-backdrop-blur) var(--tw-backdrop-brightness) var(--tw-backdrop-contrast) var(--tw-backdrop-grayscale) var(--tw-backdrop-hue-rotate) var(--tw-backdrop-invert) var(--tw-backdrop-opacity) var(--tw-backdrop-saturate) var(--tw-backdrop-sepia);
 }
 }
 
 
 .shadowed-box {
 .shadowed-box {
@@ -166,7 +166,7 @@ aside {
   line-height: 32px;
   line-height: 32px;
   font-size: 1rem;
   font-size: 1rem;
   font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen,
   font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen,
-    Ubuntu, Cantarell, 'Fira Sans', 'Droid Sans', 'Helvetica Neue', sans-serif;
+  Ubuntu, Cantarell, 'Fira Sans', 'Droid Sans', 'Helvetica Neue', sans-serif;
   color: #2c3e50;
   color: #2c3e50;
   -webkit-font-smoothing: antialiased;
   -webkit-font-smoothing: antialiased;
   -moz-osx-font-smoothing: grayscale;
   -moz-osx-font-smoothing: grayscale;
@@ -222,10 +222,9 @@ h5:first-child {
   position: fixed;
   position: fixed;
   inset: 0;
   inset: 0;
   z-index: 130;
   z-index: 130;
-  box-shadow:
-    rgba(52, 58, 67, 0.1) 0 0 2px,
-    rgba(52, 58, 67, 0.08) 0 1px 2px,
-    rgba(52, 58, 67, 0.08) 0 1px 4px;
+  box-shadow: rgba(52, 58, 67, 0.1) 0 0 2px,
+  rgba(52, 58, 67, 0.08) 0 1px 2px,
+  rgba(52, 58, 67, 0.08) 0 1px 4px;
   border-radius: 2px;
   border-radius: 2px;
   padding-bottom: 3px;
   padding-bottom: 3px;
 }
 }
@@ -239,11 +238,11 @@ h5:first-child {
   padding-right: 20px;
   padding-right: 20px;
   transition: 600ms ease position;
   transition: 600ms ease position;
   background: linear-gradient(
   background: linear-gradient(
-    90deg,
-    rgba(32, 182, 249, 1) 0%,
-    rgba(32, 182, 249, 1) 0%,
-    rgba(33, 120, 241, 1) 100%,
-    rgba(33, 120, 241, 1) 100%
+                  90deg,
+                  rgba(32, 182, 249, 1) 0%,
+                  rgba(32, 182, 249, 1) 0%,
+                  rgba(33, 120, 241, 1) 100%,
+                  rgba(33, 120, 241, 1) 100%
   );
   );
 
 
   .subtitle {
   .subtitle {

+ 3 - 3
src/assets/svg/convert.svg

@@ -1,5 +1,5 @@
 <svg fill="" height="32" viewBox="0 0 32 32" width="32" xmlns="http://www.w3.org/2000/svg">
 <svg fill="" height="32" viewBox="0 0 32 32" width="32" xmlns="http://www.w3.org/2000/svg">
-  <path
-    d="M0 10.353c0-1.04.843-1.882 1.882-1.882h23.96l-4.86-5.318A1.883 1.883 0 0123.76.613l7.743 8.47c1.105 1.208.247 3.152-1.389 3.152H1.882A1.882 1.882 0 010 10.353zm32 11.294c0 1.04-.843 1.882-1.882 1.882H6.158l4.86 5.318a1.883 1.883 0 01-2.778 2.541l-7.743-8.47c-1.105-1.209-.248-3.153 1.39-3.153h28.23c1.04 0 1.883.842 1.883 1.882z"
-    fill-rule="evenodd" opacity="0.9"/>
+    <path
+            d="M0 10.353c0-1.04.843-1.882 1.882-1.882h23.96l-4.86-5.318A1.883 1.883 0 0123.76.613l7.743 8.47c1.105 1.208.247 3.152-1.389 3.152H1.882A1.882 1.882 0 010 10.353zm32 11.294c0 1.04-.843 1.882-1.882 1.882H6.158l4.86 5.318a1.883 1.883 0 01-2.778 2.541l-7.743-8.47c-1.105-1.209-.248-3.153 1.39-3.153h28.23c1.04 0 1.883.842 1.883 1.882z"
+            fill-rule="evenodd" opacity="0.9"/>
 </svg>
 </svg>

+ 819 - 0
src/components/AntvMap/index.vue

@@ -0,0 +1,819 @@
+<script lang="ts" setup>
+import {points} from "@turf/helpers";
+import {bbox} from "@turf/bbox";
+import {LayerPopup, LineLayer, PointLayer, PolygonLayer, Popup, RasterLayer, Scene} from '@antv/l7';
+import {Map} from '@antv/l7-maps';
+// import {getLegendData, loadMapData, setCenter} from "@/utils/mapConfig";
+import {formatStringByTemplate} from "@/utils/string";
+import {copyObj} from "@/utils/ruoyi";
+import {onMounted, reactive} from "vue";
+import {getImageSrc} from "@/utils/image";
+
+let scene;
+let pointLayer;
+let pointLayerText;
+let lineLayer;
+let lineTextLayer;
+let polygonLayer;
+let polygonTextLayer;
+const token = 'a4eab5a1b0d94268669e192b8799f626';
+
+/**
+ * 初始化地图
+ */
+function init() {
+  const scene = new Scene({
+    id: 'mapDiv',
+    map: new Map({
+      center: [120.543139, 31.243133],
+      zoom: 9,
+    }),
+  });
+
+  const url1 = 'https://t0.tianditu.gov.cn/img_w/wmts?tk=b72aa81ac2b3cae941d1eb213499e15e&';
+  const layer1 = new RasterLayer().source(url1, {
+    parser: {
+      type: 'rasterTile',
+      tileSize: 256,
+      wmtsOptions: {
+        layer: 'img',
+        tileMatrixset: 'w',
+        format: 'tiles',
+      },
+    },
+  });
+
+  scene.on('loaded', () => {
+    scene.addLayer(layer1);
+  });
+
+  // scene = new Scene({
+  //   id: 'mapDiv',
+  //   map: new Map({}),
+  // });
+  // scene.on('mapmove', () => {
+  //   console.log('中心点:', scene.getCenter().lng, scene.getCenter().lat, scene.getZoom(), scene.getPitch())
+  // }); // 地图平移时触发事件
+  //
+  // // 天地图卫片地图
+  // scene.on('loaded', () => {
+  //   // 底图服务
+  //   const baseLayer = new RasterLayer({zIndex: 1})
+  //     .source(
+  //       `https://t1.tianditu.gov.cn/DataServer?T=vec_w&X={x}&Y={y}&L={z}&tk=${this.token}`,
+  //       {
+  //         parser: {
+  //           type: 'rasterTile',
+  //           tileSize: 256,
+  //         }
+  //       }
+  //     );
+  //   // 注记服务
+  //   const annotionLayer = new RasterLayer({zIndex: 2})
+  //     .source(
+  //       `https://t1.tianditu.gov.cn/DataServer?T=cva_w&X={x}&Y={y}&L={z}&tk=${this.token}`,
+  //       {
+  //         parser: {
+  //           type: 'rasterTile',
+  //           tileSize: 256,
+  //         }
+  //       }
+  //     );
+  //   scene.addLayer(baseLayer);
+  //   scene.addLayer(annotionLayer);
+  //
+  //   setCenter(scene, [120.543139, 31.243133], 8.03)
+  // });
+}
+
+function getWaterQualityTextByLevel(val, hasUnit = false) {
+  let waterQualityText = ''
+  switch (val) {
+    case '1':
+      waterQualityText = 'Ⅰ'
+      break
+    case '2':
+      waterQualityText = 'Ⅱ'
+      break
+    case '3':
+      waterQualityText = 'Ⅲ'
+      break
+    case '4':
+      waterQualityText = 'Ⅳ'
+      break
+    case '5':
+      waterQualityText = 'Ⅴ'
+      break
+    case '6':
+      waterQualityText = 'Ⅵ'
+      break
+  }
+
+  if (hasUnit && waterQualityText) {
+    waterQualityText = waterQualityText + '类'
+  }
+
+  return waterQualityText
+}
+
+
+// 图例
+const legendList = reactive([{}])
+const legendStyle = reactive({})
+
+/** 添加点(多个) */
+function addPoint(data, dataOptions = {}, nameField) {
+  // 1. 删除之前的图层
+  scene.removeAllLayer();
+
+  // 2. 添加点
+  // 点位标注
+  pointLayer = new PointLayer({})
+    .source(data, dataOptions)
+    .shape('circle')
+    .size(16)
+    .color('#004ef8')
+    .active(true)
+  scene.addLayer(pointLayer);
+  // 名称标注
+  pointLayerText = new PointLayer()
+    .source(data, dataOptions)
+    .shape(nameField, 'text')
+    .size(14)
+    .color('#000')
+    .style({
+      textOffset: [0, -50],
+      spacing: 2, // 字符间距
+      stroke: '#ffffff', // 描边颜色
+      strokeWidth: 2, // 描边宽度
+      strokeOpacity: 1.0,
+    });
+  scene.addLayer(pointLayerText);
+  // 3. 添加悬浮窗口
+  let keys = Object.keys(data[0]);
+  keys = keys.filter(k => !['经度', '纬度'].includes(k));
+  const fields = keys.map(k => {
+    return {
+      field: k,
+      formatField: () => k,
+    }
+  });
+  const layerPopup = new LayerPopup({
+    items: [
+      {
+        layer: pointLayer,
+        fields: fields,
+      },
+    ],
+  });
+  scene.addPopup(layerPopup);
+}
+
+/** 添加线(多个) */
+function addLine(data, nameField) {
+  // 1. 删除之前的图层
+  scene.removeAllLayer();
+
+  lineLayer = new LineLayer()
+    .source(data)
+    .size(5)
+    .shape('line')
+    .active(true)
+    .color('ICON', (ICON) => {
+      switch (ICON) {
+        case '1':
+          return '#1d8add'
+        case '2':
+          return '#4af905'
+        case '3':
+          return '#fed202'
+        case '4':
+          return '#f91005'
+        case '5':
+          return '#fb0891'
+        case '6':
+          return '#252627'
+        default:
+          return '#909399'
+      }
+    })
+    .style({
+      borderWidth: 0.4,
+      borderColor: '#fff',
+    });
+  scene.addLayer(lineLayer);
+
+  // 名称标注
+  const texts = []
+  data.features.forEach(d => {
+    let index = Math.ceil(d.geometry.coordinates[0].length / 2)
+    let array = d.geometry.coordinates[0][index]
+    texts.push({name: d.properties[nameField], x: array[0], y: array[1]})
+  })
+  lineTextLayer = new PointLayer()
+    .source(texts, {
+      parser: {
+        type: 'json',
+        x: 'x',
+        y: 'y',
+      },
+    })
+    .shape('name', 'text')
+    .color('#fff')
+    .size(14)
+    .style({
+      textOffset: [0, -50],
+    });
+  scene.addLayer(lineTextLayer);
+
+  // 悬浮窗
+  const fields = [
+    {field: 'HLNM', formatField: () => '河流名称'},
+    {field: 'ICON', formatField: () => '水质', formatValue: (val) => getWaterQualityTextByLevel(val, true)},
+  ];
+  const layerPopup = new LayerPopup({items: [{layer: lineLayer, fields: fields,},],});
+  scene.addPopup(layerPopup);
+}
+
+function addPolygon(data, nameField) {
+  // 1. 删除之前的图层
+  scene.removeAllLayer();
+
+  const color = [
+    'rgb(255,255,217)',
+    'rgb(237,248,177)',
+    'rgb(199,233,180)',
+    'rgb(127,205,187)',
+    'rgb(65,182,196)',
+    'rgb(29,145,192)',
+    'rgb(34,94,168)',
+    'rgb(12,44,132)',
+  ];
+  /**
+   *         .scale('用水量', {
+   *           type: 'quantile',
+   *         })
+   *         .color('用水量', color)
+   */
+  const layer = new PolygonLayer({})
+    .source(data)
+    .color('rgba(65,182,196,0.4)')
+    .shape('fill')
+    .active(true);
+  const layer2 = new LineLayer({zIndex: 2})
+    .source(data)
+    .color('#fff')
+    .active(true)
+    .size(1)
+    .style({
+      lineType: 'dash',
+      dashArray: [2, 2],
+    });
+  scene.addLayer(layer);
+  scene.addLayer(layer2);
+
+  const polygonTextLayer = new PointLayer({})
+    .source(data)
+    .shape(nameField, 'text')
+    .size(12)
+    .color('#000')
+    .style({
+      textAnchor: 'center', // 文本相对锚点的位置 center|left|right|top|bottom|top-left
+      textOffset: [0, 0], // 文本相对锚点的偏移量 [水平, 垂直]
+      spacing: 2, // 字符间距
+      padding: [1, 1], // 文本包围盒 padding [水平,垂直],影响碰撞检测结果,避免相邻文本靠的太近
+      stroke: '#ffffff', // 描边颜色
+      strokeWidth: 2, // 描边宽度
+      strokeOpacity: 1.0,
+    });
+
+  scene.addLayer(polygonTextLayer);
+
+  // layer.on('mousemove', (e) => {
+  //   const popup = new Popup({
+  //     offsets: [0, 0],
+  //     closeButton: false,
+  //   })
+  //     .setLnglat(e.lngLat)
+  //     .setHTML(
+  //       `
+  //       <span>${e.feature.properties['市']}: ${e.feature.properties['用水量']}</span>
+  //       <br/>
+  //       <span>许可水量: ${e.feature.properties['许可水量']}</span>
+  //       `
+  //     );
+  //   scene.addPopup(popup);
+  // });
+}
+
+function add3DPoint(list) {
+  const pointLayer = new PointLayer({})
+    .source(list, {
+      parser: {
+        type: 'json',
+        x: 'j',
+        y: 'w',
+      },
+    })
+    .shape('cylinder')
+    .size('t', function (level) {
+      return [2, 2, level * 2 + 20];
+    })
+    .animate(true)
+    .active(true)
+    .color('t', [
+      '#094D4A',
+      '#146968',
+      '#1D7F7E',
+      '#289899',
+      '#34B6B7',
+      '#4AC5AF',
+      '#5FD3A6',
+      '#7BE39E',
+      '#A1EDB8',
+      '#CEF8D6',
+    ]);
+  scene.addLayer(pointLayer);
+}
+
+function getScene() {
+  return scene;
+}
+
+function loadMapData(mapConfig) {
+  if (['left', 'center', 'right'].includes(mapConfig.legend.left)) {
+    switch (mapConfig.legend.left) {
+      case "left":
+        this.legendStyle.left = '10px';
+        break
+      case "center":
+        this.legendStyle.left = `calc(50% - (${this.$refs.mapLegend.offsetWidth} / 2))`;
+        break
+      case "right":
+        this.legendStyle.right = '10px';
+        break
+    }
+  } else if (mapConfig.legend.left.startsWith('-')) {
+    this.legendStyle.right = mapConfig.legend.left.replace('-', '')
+  } else {
+    this.legendStyle.left = mapConfig.legend.left
+  }
+
+  if (['top', 'center', 'bottom'].includes(mapConfig.legend.top)) {
+    switch (mapConfig.legend.top) {
+      case "top":
+        this.legendStyle.top = '10px';
+        break
+      case "center":
+        this.legendStyle.top = `calc(50% - (${this.$refs.mapLegend.offsetHeight} / 2))`;
+        break
+      case "bottom":
+        this.legendStyle.bottom = '10px';
+        break
+    }
+  } else if (mapConfig.legend.top.startsWith('-')) {
+    this.legendStyle.bottom = mapConfig.legend.top.replace('-', '')
+  } else {
+    this.legendStyle.top = mapConfig.legend.top
+  }
+
+  // this.legendList = getLegendData(mapConfig)
+}
+
+function fitBounds(data) {
+  const features = points(data);
+  const bboxs = bbox(features);
+
+  let lttddd = (bboxs[3] - bboxs[1]) / 8 // 计算缩放级别
+  let lgtddd = (bboxs[2] - bboxs[0]) / 8 // 计算缩放级别
+  scene.fitBounds([[bboxs[0] - lgtddd, bboxs[1] - lttddd], [bboxs[2] + lgtddd, bboxs[3] + lttddd]]);
+}
+
+/** 设置事件节点 */
+function setEventData(events) {
+  const _self = this
+  // 1. 删除之前的图层
+  scene.removeAllLayer();
+
+  const sourceOptions = {
+    parser: {
+      type: 'json',
+      x: 'x',
+      y: 'y',
+    },
+  }
+
+  const imageSet = new Set()
+  events.forEach(event => {
+    if (event.image) {
+      imageSet.add(event.image)
+    }
+  })
+  imageSet.forEach(image => {
+    scene.addImage(
+      image,
+      getImageSrc(image)
+    );
+  })
+
+  const imageEvents = events.filter(event => !!event.image)
+  const imagePointLayer = new PointLayer()
+    .source(imageEvents, sourceOptions)
+    .size(16)
+    .shape('image', Array.of(imageSet))
+    .style({
+      opacity: 0.8,
+      strokeWidth: 4
+    })
+    .active(true);
+  scene.addLayer(imagePointLayer);
+
+  const shapeEvents = events.filter(event => !event.image)
+  const shapePointLayer = new PointLayer()
+    .source(shapeEvents, sourceOptions)
+    .size(16)
+    .shape('shape')
+    .color('color')
+    .style({
+      opacity: 0.8,
+      strokeWidth: 4
+    })
+    .active(true);
+  scene.addLayer(shapePointLayer);
+
+
+  // 设置标签
+  pointLayerText = new PointLayer()
+    .source(events, sourceOptions)
+    .shape('name', 'text')
+    .size(12)
+    .color('#000')
+    .style({
+      textAnchor: 'center', // 文本相对锚点的位置 center|left|right|top|bottom|top-left
+      textOffset: [0, -60], // 文本相对锚点的偏移量 [水平, 垂直]
+      spacing: 2, // 字符间距
+      padding: [1, 1], // 文本包围盒 padding [水平,垂直],影响碰撞检测结果,避免相邻文本靠的太近
+      stroke: '#fff', // 描边颜色
+      strokeWidth: 2, // 描边宽度
+      strokeOpacity: 1.0,
+    });
+  scene.addLayer(pointLayerText);
+
+  // 显示弹窗
+  const showPopup = (e) => {
+    let data = e.feature.data
+    const template =
+      '<div class="entry-card-component"><section class="entry-base-info">' +
+      '<section class="base-info"><div class="entry-avatar"><img alt="" src="{{imgUrl}}"></div><div class="entry-text-info"><div class="entry-name">{{name}}</div><div class="entry-desc">{{summary}}</div></div></section></section>' +
+      '<section class="entry-nodes"><div class="nodes-list"><div class="node-item" data-id="{{id}}">' +
+      '<div class="title">{{source.title}}</div>' +
+      '<div class="sub-title"><div class="time">{{source.dateString}}</div>' +
+      '<div class="address">{{source.locationDesc}}</div></div>' +
+      '<div class="desc">{{source.description}}</div></div></div></section></div>'
+    const content = formatStringByTemplate(data, template)
+    const popup = new Popup({maxWidth: '36vw', autoClose: true})
+      .setLnglat(e.lngLat)
+      .setHTML(content);
+    scene.addPopup(popup);
+
+    // 确保在 DOM 完全更新后执行
+    setTimeout(() => {
+      const eventItems = document.getElementsByClassName('node-item');
+      // 遍历所有 node-item 元素
+      Array.from(eventItems).forEach(item => {
+        item.addEventListener('click', (event) => {
+          // const id = event.currentTarget?.dataset?.id;
+          // _self.$router.push({path: `/geoTemporalSearch/detail/${id}`});
+        });
+      });
+    }, 0); // 使用 setTimeout 来确保 DOM 已经更新
+  };
+
+  imagePointLayer.on('click', showPopup);
+  shapePointLayer.on('click', showPopup);
+}
+
+function setEventPoint(events) {
+  const _self = this
+  // 1. 删除之前的图层
+  scene.removeAllLayer();
+
+  const sourceOptions = {
+    parser: {
+      type: 'json',
+      x: 'x',
+      y: 'y',
+    },
+  }
+  const pointLayer = new PointLayer()
+    .source(events, sourceOptions)
+    .size(12)
+    .shape('simple')
+    .color('#fe7331')
+    .style({
+      strokeWidth: 4
+    })
+    .active(false);
+  scene.addLayer(pointLayer);
+
+  // 设置标签
+  pointLayerText = new PointLayer()
+    .source(events, sourceOptions)
+    .shape('title', 'text')
+    .size(12)
+    .color('#000')
+    .style({
+      textAnchor: 'center', // 文本相对锚点的位置 center|left|right|top|bottom|top-left
+      textOffset: [0, -60], // 文本相对锚点的偏移量 [水平, 垂直]
+      spacing: 2, // 字符间距
+      padding: [1, 1], // 文本包围盒 padding [水平,垂直],影响碰撞检测结果,避免相邻文本靠的太近
+      stroke: '#fff', // 描边颜色
+      strokeWidth: 2, // 描边宽度
+      strokeOpacity: 1.0,
+    });
+  scene.addLayer(pointLayerText);
+}
+
+function setEventPointDetail(events) {
+  const allPoints = copyObj(events)
+  // 1. 删除之前的图层
+  scene.removeAllLayer();
+
+  let mainEvent;
+  let index = events.findIndex(item => item.pointType === 'main');
+  if (index > -1) {
+    mainEvent = events.splice(index, 1); // 移除找到的第一个元素  l
+  } else {
+    return
+  }
+
+  const sourceOptions = {
+    parser: {
+      type: 'json',
+      x: 'x',
+      y: 'y',
+    },
+  }
+  const pointLayer = new PointLayer()
+    .source(events, sourceOptions)
+    .size(12)
+    .shape('simple')
+    .color('#fe7331')
+    .style({
+      strokeWidth: 4
+    })
+    .active(false);
+  scene.addLayer(pointLayer);
+
+  const mainPointLayer = new PointLayer()
+    .source(mainEvent, sourceOptions)
+    .size(20)
+    .shape('simple')
+    .color('#fe7331')
+    .style({
+      strokeWidth: 4
+    })
+    .active(false);
+  scene.addLayer(mainPointLayer);
+
+  // 设置标签
+  pointLayerText = new PointLayer()
+    .source(allPoints, sourceOptions)
+    .shape('title', 'text')
+    .size(12)
+    .color('#000')
+    .style({
+      textAnchor: 'center', // 文本相对锚点的位置 center|left|right|top|bottom|top-left
+      textOffset: [0, -60], // 文本相对锚点的偏移量 [水平, 垂直]
+      spacing: 2, // 字符间距
+      padding: [1, 1], // 文本包围盒 padding [水平,垂直],影响碰撞检测结果,避免相邻文本靠的太近
+      stroke: '#fff', // 描边颜色
+      strokeWidth: 2, // 描边宽度
+      strokeOpacity: 1.0,
+    });
+  scene.addLayer(pointLayerText);
+
+  for (let e of events) {
+    let data = []
+    if (e.pointType === 'previous') {
+      data.push({lng: e.x, lat: e.y, lng1: mainEvent[0].x, lat1: mainEvent[0].y})
+    } else {
+      data.push({lng: mainEvent[0].x, lat: mainEvent[0].y, lng1: e.x, lat1: e.y})
+    }
+
+    const lineLayer = new LineLayer()
+      .source(data, {
+        parser: {
+          type: 'json',
+          x: 'lng',
+          y: 'lat',
+          x1: 'lng1',
+          y1: 'lat1',
+        },
+      })
+      .shape('line')
+      .size(2)
+      .color('#f00')
+      .animate({
+        duration: 2,
+        interval: 1,
+        trailLength: 1,
+      })
+      .style({
+        lineType: 'dash', // 设置虚线类型
+        // dashArray: [5, 5], // 设置虚线的间隔和长度
+      });
+    scene.addLayer(lineLayer);
+  }
+
+  pointLayer.on('click', e => {
+    if (e.feature?.id) {
+      this.$emit('updateEventId', e.feature?.id)
+    }
+  });
+}
+
+onMounted(() => {
+  init()
+})
+</script>
+
+<template>
+  <div class="gw-map">
+    <!-- 2D地图 -->
+    <div id="mapDiv"></div>
+    <!--    <div v-if="legendList && legendList.length > 0" ref="mapLegend" :style="legendStyle" class="map-legend">-->
+    <!--      <ul>-->
+    <!--        <li v-for="(item, index) in legendList" :key="index">-->
+    <!--          <span :title="item.label">{{ item.label }}</span>-->
+    <!--          <template v-if="item.imgType === 'svg'">-->
+    <!--            <svg-icon :icon-class="item.src" :style="item.imgStyle" class="image"></svg-icon>-->
+    <!--          </template>-->
+    <!--          <template v-if="item.imgType === 'img'">-->
+    <!--            <el-image :src="item.src" :style="item.imgStyle" class="image"></el-image>-->
+    <!--          </template>-->
+    <!--        </li>-->
+    <!--      </ul>-->
+    <!--    </div>-->
+  </div>
+</template>
+<style lang="scss" scoped>
+.gw-map {
+  width: 100%;
+  height: 100%;
+  position: relative;
+  pointer-events: auto;
+}
+
+#mapDiv {
+  width: 100%;
+  height: 100%;
+  z-index: 1;
+  opacity: 1;
+  overflow: hidden;
+  filter: saturate(1) contrast(1) hue-rotate(0deg) brightness(1);
+  transform: rotateZ(0deg) rotateX(0deg) rotateY(0deg) skewX(0deg) skewY(0deg);
+}
+
+.map-legend {
+  position: absolute;
+  bottom: 0;
+  right: 0;
+  z-index: 100;
+  background-color: #f2f2f2;
+  border-radius: 8px;
+  padding: 5px 10px;
+
+  ul {
+    margin: 0;
+    padding: 0;
+
+    li {
+      margin: 5px 0;
+      list-style: none;
+      display: flex;
+      justify-content: flex-end;
+      align-items: center;
+
+      span {
+        text-align: right;
+        width: 7vw;
+        white-space: nowrap;
+        overflow: hidden;
+        text-overflow: ellipsis;
+      }
+
+      .image {
+        margin-left: 5px;
+        width: 1rem;
+        height: 1rem;
+      }
+
+    }
+  }
+
+}
+
+:deep(.l7-control-logo) {
+  display: none;
+}
+
+:deep(.esri-view-surface) {
+  &:after {
+    display: none;
+  }
+}
+
+</style>
+<style lang="scss">
+.entry-card-component {
+  .entry-base-info {
+
+    .base-info {
+      display: flex;
+      margin-bottom: 14px;
+
+      .entry-avatar {
+        width: 90px;
+        margin-right: 8px;
+        height: 108px;
+
+        img {
+          width: 100%;
+          height: 100%;
+          object-fit: cover;
+        }
+
+      }
+
+      .entry-text-info {
+        flex: 1;
+
+        .entry-name {
+          font-size: 16px;
+          line-height: 22px;
+          font-weight: 600;
+          margin-bottom: 10px;
+          margin-right: 24px;
+        }
+
+        .entry-desc {
+          line-height: 20px;
+          font-size: 12px;
+          word-break: break-word;
+          display: -webkit-box;
+          height: 80px;
+          overflow: hidden;
+          text-overflow: ellipsis;
+          -webkit-line-clamp: 4;
+        }
+      }
+    }
+
+
+  }
+
+  .entry-nodes {
+    .nodes-list {
+      border-top: 1px solid #ebebeb;
+      padding-top: 7px;
+
+      .node-item {
+        cursor: pointer;
+
+        .title {
+          word-break: break-word;
+          display: -webkit-box;
+          -webkit-box-orient: vertical;
+          overflow: hidden;
+          text-overflow: ellipsis;
+          -webkit-line-clamp: 2;
+        }
+
+        .sub-title {
+          display: flex;
+          margin-bottom: 8px;
+          color: #999;
+          line-height: 14px;
+
+          .time {
+            margin-right: 4px;
+          }
+
+          .address {
+            overflow: hidden;
+          }
+        }
+
+        .desc {
+          font-size: 12px;
+          color: #666;
+          line-height: 20px;
+          max-height: 40px;
+        }
+      }
+
+    }
+  }
+}
+</style>

+ 3 - 3
src/components/HelloWorld.vue

@@ -1,4 +1,4 @@
-<script setup lang="ts">
+<script lang="ts" setup>
 defineProps<{
 defineProps<{
   msg: string
   msg: string
 }>()
 }>()
@@ -9,8 +9,8 @@ defineProps<{
     <h1 class="green">{{ msg }}</h1>
     <h1 class="green">{{ msg }}</h1>
     <h3>
     <h3>
       You’ve successfully created a project with
       You’ve successfully created a project with
-      <a href="https://vite.dev/" target="_blank" rel="noopener">Vite</a> +
-      <a href="https://vuejs.org/" target="_blank" rel="noopener">Vue 3</a>.
+      <a href="https://vite.dev/" rel="noopener" target="_blank">Vite</a> +
+      <a href="https://vuejs.org/" rel="noopener" target="_blank">Vue 3</a>.
       What's next?
       What's next?
     </h3>
     </h3>
   </div>
   </div>

+ 5 - 1
src/components/RightFrame.vue

@@ -115,6 +115,10 @@ watch(
     height: calc(100%);
     height: calc(100%);
     min-width: 300px;
     min-width: 300px;
 
 
+    &:hover .gw-sidebar-btn {
+      display: flex;
+    }
+
     .gw-sidebar-container {
     .gw-sidebar-container {
       display: flex;
       display: flex;
       flex-direction: column;
       flex-direction: column;
@@ -129,7 +133,7 @@ watch(
       background-color: #2e6ca6;
       background-color: #2e6ca6;
       height: 40px;
       height: 40px;
       width: 40px;
       width: 40px;
-      display: flex;
+      display: none;
       justify-content: center;
       justify-content: center;
       align-items: center;
       align-items: center;
 
 

+ 36 - 36
src/components/TheWelcome.vue

@@ -1,4 +1,4 @@
-<script setup lang="ts">
+<script lang="ts" setup>
 import WelcomeItem from './WelcomeItem.vue'
 import WelcomeItem from './WelcomeItem.vue'
 import DocumentationIcon from './icons/IconDocumentation.vue'
 import DocumentationIcon from './icons/IconDocumentation.vue'
 import ToolingIcon from './icons/IconTooling.vue'
 import ToolingIcon from './icons/IconTooling.vue'
@@ -10,112 +10,112 @@ import SupportIcon from './icons/IconSupport.vue'
 <template>
 <template>
   <WelcomeItem>
   <WelcomeItem>
     <template #icon>
     <template #icon>
-      <DocumentationIcon />
+      <DocumentationIcon/>
     </template>
     </template>
     <template #heading>Documentation</template>
     <template #heading>Documentation</template>
 
 
     Vue’s
     Vue’s
-    <a href="https://vuejs.org/" target="_blank" rel="noopener"
-      >official documentation</a
+    <a href="https://vuejs.org/" rel="noopener" target="_blank"
+    >official documentation</a
     >
     >
     provides you with all information you need to get started.
     provides you with all information you need to get started.
   </WelcomeItem>
   </WelcomeItem>
 
 
   <WelcomeItem>
   <WelcomeItem>
     <template #icon>
     <template #icon>
-      <ToolingIcon />
+      <ToolingIcon/>
     </template>
     </template>
     <template #heading>Tooling</template>
     <template #heading>Tooling</template>
 
 
     This project is served and bundled with
     This project is served and bundled with
     <a
     <a
       href="https://vite.dev/guide/features.html"
       href="https://vite.dev/guide/features.html"
-      target="_blank"
       rel="noopener"
       rel="noopener"
-      >Vite</a
+      target="_blank"
+    >Vite</a
     >. The recommended IDE setup is
     >. The recommended IDE setup is
-    <a href="https://code.visualstudio.com/" target="_blank" rel="noopener"
-      >VSCode</a
+    <a href="https://code.visualstudio.com/" rel="noopener" target="_blank"
+    >VSCode</a
     >
     >
     +
     +
     <a
     <a
       href="https://github.com/johnsoncodehk/volar"
       href="https://github.com/johnsoncodehk/volar"
-      target="_blank"
       rel="noopener"
       rel="noopener"
-      >Volar</a
+      target="_blank"
+    >Volar</a
     >. If you need to test your components and web pages, check out
     >. If you need to test your components and web pages, check out
-    <a href="https://www.cypress.io/" target="_blank" rel="noopener">Cypress</a>
+    <a href="https://www.cypress.io/" rel="noopener" target="_blank">Cypress</a>
     and
     and
-    <a href="https://on.cypress.io/component" target="_blank" rel="noopener"
-      >Cypress Component Testing</a
+    <a href="https://on.cypress.io/component" rel="noopener" target="_blank"
+    >Cypress Component Testing</a
     >.
     >.
 
 
-    <br />
+    <br/>
 
 
     More instructions are available in <code>README.md</code>.
     More instructions are available in <code>README.md</code>.
   </WelcomeItem>
   </WelcomeItem>
 
 
   <WelcomeItem>
   <WelcomeItem>
     <template #icon>
     <template #icon>
-      <EcosystemIcon />
+      <EcosystemIcon/>
     </template>
     </template>
     <template #heading>Ecosystem</template>
     <template #heading>Ecosystem</template>
 
 
     Get official tools and libraries for your project:
     Get official tools and libraries for your project:
-    <a href="https://pinia.vuejs.org/" target="_blank" rel="noopener">Pinia</a>,
-    <a href="https://router.vuejs.org/" target="_blank" rel="noopener"
-      >Vue Router</a
+    <a href="https://pinia.vuejs.org/" rel="noopener" target="_blank">Pinia</a>,
+    <a href="https://router.vuejs.org/" rel="noopener" target="_blank"
+    >Vue Router</a
     >,
     >,
-    <a href="https://test-utils.vuejs.org/" target="_blank" rel="noopener"
-      >Vue Test Utils</a
+    <a href="https://test-utils.vuejs.org/" rel="noopener" target="_blank"
+    >Vue Test Utils</a
     >, and
     >, and
-    <a href="https://github.com/vuejs/devtools" target="_blank" rel="noopener"
-      >Vue Dev Tools</a
+    <a href="https://github.com/vuejs/devtools" rel="noopener" target="_blank"
+    >Vue Dev Tools</a
     >. If you need more resources, we suggest paying
     >. If you need more resources, we suggest paying
     <a
     <a
       href="https://github.com/vuejs/awesome-vue"
       href="https://github.com/vuejs/awesome-vue"
-      target="_blank"
       rel="noopener"
       rel="noopener"
-      >Awesome Vue</a
+      target="_blank"
+    >Awesome Vue</a
     >
     >
     a visit.
     a visit.
   </WelcomeItem>
   </WelcomeItem>
 
 
   <WelcomeItem>
   <WelcomeItem>
     <template #icon>
     <template #icon>
-      <CommunityIcon />
+      <CommunityIcon/>
     </template>
     </template>
     <template #heading>Community</template>
     <template #heading>Community</template>
 
 
     Got stuck? Ask your question on
     Got stuck? Ask your question on
-    <a href="https://chat.vuejs.org" target="_blank" rel="noopener">Vue Land</a
+    <a href="https://chat.vuejs.org" rel="noopener" target="_blank">Vue Land</a
     >, our official Discord server, or
     >, our official Discord server, or
     <a
     <a
       href="https://stackoverflow.com/questions/tagged/vue.js"
       href="https://stackoverflow.com/questions/tagged/vue.js"
-      target="_blank"
       rel="noopener"
       rel="noopener"
-      >StackOverflow</a
+      target="_blank"
+    >StackOverflow</a
     >. You should also subscribe to
     >. You should also subscribe to
-    <a href="https://news.vuejs.org" target="_blank" rel="noopener"
-      >our mailing list</a
+    <a href="https://news.vuejs.org" rel="noopener" target="_blank"
+    >our mailing list</a
     >
     >
     and follow the official
     and follow the official
-    <a href="https://twitter.com/vuejs" target="_blank" rel="noopener"
-      >@vuejs</a
+    <a href="https://twitter.com/vuejs" rel="noopener" target="_blank"
+    >@vuejs</a
     >
     >
     twitter account for latest news in the Vue world.
     twitter account for latest news in the Vue world.
   </WelcomeItem>
   </WelcomeItem>
 
 
   <WelcomeItem>
   <WelcomeItem>
     <template #icon>
     <template #icon>
-      <SupportIcon />
+      <SupportIcon/>
     </template>
     </template>
     <template #heading>Support Vue</template>
     <template #heading>Support Vue</template>
 
 
     As an independent project, Vue relies on community backing for its
     As an independent project, Vue relies on community backing for its
     sustainability. You can help us by
     sustainability. You can help us by
-    <a href="https://vuejs.org/sponsor/" target="_blank" rel="noopener"
-      >becoming a sponsor</a
+    <a href="https://vuejs.org/sponsor/" rel="noopener" target="_blank"
+    >becoming a sponsor</a
     >.
     >.
   </WelcomeItem>
   </WelcomeItem>
 </template>
 </template>

+ 3 - 3
src/components/__tests__/HelloWorld.spec.ts

@@ -1,11 +1,11 @@
-import { describe, it, expect } from 'vitest'
+import {describe, expect, it} from 'vitest'
 
 
-import { mount } from '@vue/test-utils'
+import {mount} from '@vue/test-utils'
 import HelloWorld from '../HelloWorld.vue'
 import HelloWorld from '../HelloWorld.vue'
 
 
 describe('HelloWorld', () => {
 describe('HelloWorld', () => {
   it('renders properly', () => {
   it('renders properly', () => {
-    const wrapper = mount(HelloWorld, { props: { msg: 'Hello Vitest' } })
+    const wrapper = mount(HelloWorld, {props: {msg: 'Hello Vitest'}})
     expect(wrapper.text()).toContain('Hello Vitest')
     expect(wrapper.text()).toContain('Hello Vitest')
   })
   })
 })
 })

+ 64 - 0
src/components/card/Card01.vue

@@ -0,0 +1,64 @@
+<script lang="ts" setup>
+defineProps<{
+  title: { type: string, required: false },
+  customClass: { type: string, required: false },
+}>()
+
+import header from "@/assets/images/card/header01.png";
+</script>
+
+<template>
+  <div :class="[customClass]" class="card-one">
+    <div class="card-one-header">
+      <img :src="header" alt=""/>
+      <span class="title" v-html="title"></span>
+      <slot name="headerTool"></slot>
+    </div>
+    <div class="card-one-body">
+      <slot></slot>
+    </div>
+  </div>
+</template>
+<style lang="scss" scoped>
+.card-one {
+  color: #fff;
+
+  .card-one-header {
+    position: relative;
+    height: 2.85rem;
+    line-height: 2.85rem;
+    font-size: 1.2rem;
+    font-weight: 600;
+
+    img {
+      position: absolute;
+      width: 100%;
+      height: 100%;
+      top: 0;
+      left: 0;
+    }
+
+    .title {
+      position: relative;
+      padding-left: 6%;
+    }
+  }
+
+  .card-one-body {
+    width: 100%;
+    padding: 10px;
+    position: relative;
+    background-color: rgba(3, 37, 77, 0.8);
+    border: 1px transparent solid;
+  }
+
+}
+
+.card-one + .card-one {
+  margin-top: 5px;
+}
+
+.card-one.merge + .card-one.merge {
+  margin-top: 0;
+}
+</style>

+ 132 - 0
src/components/card/Card02.vue

@@ -0,0 +1,132 @@
+<script lang="ts" setup>
+defineProps<{
+  title: { type: string, required: false },
+  customClass: { type: string, required: false },
+}>()
+</script>
+
+<template>
+  <div :class="[customClass]" class="card-one">
+    <div class="card-one-header">
+      <span class="title-box" v-html="title"></span>
+      <slot name="headerTool"></slot>
+    </div>
+    <div class="card-one-body">
+      <slot></slot>
+    </div>
+  </div>
+</template>
+<style lang="scss" scoped>
+.card-one {
+  background-image: linear-gradient(180deg,
+    rgba(0, 28, 53, 0.8) 0%,
+    rgba(11, 45, 85, 0.8) 100%);
+  border: 2px solid hsla(0, 0%, 89.8%, .31);
+  border-radius: 12px;
+  min-width: 418px;
+
+  .card-one-header {
+    border-radius: 12px 12px 0 0;
+    background-image: linear-gradient(180deg,
+      rgba(0, 28, 53, 1) 0%,
+      rgba(11, 45, 85, 1) 100%);
+    height: 2.85rem;
+    line-height: 2.85rem;
+    font-size: 1.2rem;
+    font-weight: 600;
+    color: #fff;
+    display: flex;
+    align-items: center;
+
+    .title-box {
+      flex: 1;
+    }
+
+    .title-box:before {
+      display: inline-block;
+      content: "";
+      width: .5rem;
+      height: 1.4rem;
+      background: #0cf;
+      vertical-align: top;
+      margin: .7rem;
+    }
+
+    .time {
+      position: absolute;
+      right: 1rem;
+      top: -4px;
+    }
+  }
+
+  .card-one-body {
+    width: 100%;
+    height: calc(100% - 2.85rem);
+    position: relative;
+
+    ul {
+      margin: 0;
+      padding: 0;
+
+      li {
+        list-style: none;
+        padding: 0 10px 10px 10px;
+      }
+
+    }
+
+  }
+
+  &.merge {
+    border-top: none;
+    border-bottom: none;
+    border-radius: 0;
+
+    .card-one-header {
+      border-radius: 0;
+    }
+
+    &:first-child {
+      border-radius: 12px 12px 0 0;
+      border-top: 2px solid hsla(0, 0%, 89.8%, .31);
+
+      .card-one-header {
+        border-radius: 12px 12px 0 0;
+      }
+    }
+
+  }
+
+  &:last-child {
+    &.merge {
+      border-radius: 0 0 12px 12px;
+      border-bottom: 2px solid hsla(0, 0%, 89.8%, .31);
+    }
+  }
+}
+
+.card-one + .card-one {
+  margin-top: 5px;
+}
+
+.card-one.merge + .card-one.merge {
+  margin-top: 0;
+}
+
+.card-one-body {
+  .dv-scroll-board {
+    .header {
+      color: #8ec8f8;
+      font-size: 1rem;
+    }
+
+    .rows {
+      .row-item {
+        color: #CEF0FF;
+        font-size: 1rem;
+      }
+    }
+  }
+}
+
+</style>

+ 3 - 3
src/components/icons/IconCommunity.vue

@@ -1,9 +1,9 @@
 <template>
 <template>
   <svg
   <svg
-    xmlns="http://www.w3.org/2000/svg"
-    width="20"
-    height="20"
     fill="currentColor"
     fill="currentColor"
+    height="20"
+    width="20"
+    xmlns="http://www.w3.org/2000/svg"
   >
   >
     <path
     <path
       d="M15 4a1 1 0 1 0 0 2V4zm0 11v-1a1 1 0 0 0-1 1h1zm0 4l-.707.707A1 1 0 0 0 16 19h-1zm-4-4l.707-.707A1 1 0 0 0 11 14v1zm-4.707-1.293a1 1 0 0 0-1.414 1.414l1.414-1.414zm-.707.707l-.707-.707.707.707zM9 11v-1a1 1 0 0 0-.707.293L9 11zm-4 0h1a1 1 0 0 0-1-1v1zm0 4H4a1 1 0 0 0 1.707.707L5 15zm10-9h2V4h-2v2zm2 0a1 1 0 0 1 1 1h2a3 3 0 0 0-3-3v2zm1 1v6h2V7h-2zm0 6a1 1 0 0 1-1 1v2a3 3 0 0 0 3-3h-2zm-1 1h-2v2h2v-2zm-3 1v4h2v-4h-2zm1.707 3.293l-4-4-1.414 1.414 4 4 1.414-1.414zM11 14H7v2h4v-2zm-4 0c-.276 0-.525-.111-.707-.293l-1.414 1.414C5.42 15.663 6.172 16 7 16v-2zm-.707 1.121l3.414-3.414-1.414-1.414-3.414 3.414 1.414 1.414zM9 12h4v-2H9v2zm4 0a3 3 0 0 0 3-3h-2a1 1 0 0 1-1 1v2zm3-3V3h-2v6h2zm0-6a3 3 0 0 0-3-3v2a1 1 0 0 1 1 1h2zm-3-3H3v2h10V0zM3 0a3 3 0 0 0-3 3h2a1 1 0 0 1 1-1V0zM0 3v6h2V3H0zm0 6a3 3 0 0 0 3 3v-2a1 1 0 0 1-1-1H0zm3 3h2v-2H3v2zm1-1v4h2v-4H4zm1.707 4.707l.586-.586-1.414-1.414-.586.586 1.414 1.414z"
       d="M15 4a1 1 0 1 0 0 2V4zm0 11v-1a1 1 0 0 0-1 1h1zm0 4l-.707.707A1 1 0 0 0 16 19h-1zm-4-4l.707-.707A1 1 0 0 0 11 14v1zm-4.707-1.293a1 1 0 0 0-1.414 1.414l1.414-1.414zm-.707.707l-.707-.707.707.707zM9 11v-1a1 1 0 0 0-.707.293L9 11zm-4 0h1a1 1 0 0 0-1-1v1zm0 4H4a1 1 0 0 0 1.707.707L5 15zm10-9h2V4h-2v2zm2 0a1 1 0 0 1 1 1h2a3 3 0 0 0-3-3v2zm1 1v6h2V7h-2zm0 6a1 1 0 0 1-1 1v2a3 3 0 0 0 3-3h-2zm-1 1h-2v2h2v-2zm-3 1v4h2v-4h-2zm1.707 3.293l-4-4-1.414 1.414 4 4 1.414-1.414zM11 14H7v2h4v-2zm-4 0c-.276 0-.525-.111-.707-.293l-1.414 1.414C5.42 15.663 6.172 16 7 16v-2zm-.707 1.121l3.414-3.414-1.414-1.414-3.414 3.414 1.414 1.414zM9 12h4v-2H9v2zm4 0a3 3 0 0 0 3-3h-2a1 1 0 0 1-1 1v2zm3-3V3h-2v6h2zm0-6a3 3 0 0 0-3-3v2a1 1 0 0 1 1 1h2zm-3-3H3v2h10V0zM3 0a3 3 0 0 0-3 3h2a1 1 0 0 1 1-1V0zM0 3v6h2V3H0zm0 6a3 3 0 0 0 3 3v-2a1 1 0 0 1-1-1H0zm3 3h2v-2H3v2zm1-1v4h2v-4H4zm1.707 4.707l.586-.586-1.414-1.414-.586.586 1.414 1.414z"

+ 3 - 3
src/components/icons/IconDocumentation.vue

@@ -1,9 +1,9 @@
 <template>
 <template>
   <svg
   <svg
-    xmlns="http://www.w3.org/2000/svg"
-    width="20"
-    height="17"
     fill="currentColor"
     fill="currentColor"
+    height="17"
+    width="20"
+    xmlns="http://www.w3.org/2000/svg"
   >
   >
     <path
     <path
       d="M11 2.253a1 1 0 1 0-2 0h2zm-2 13a1 1 0 1 0 2 0H9zm.447-12.167a1 1 0 1 0 1.107-1.666L9.447 3.086zM1 2.253L.447 1.42A1 1 0 0 0 0 2.253h1zm0 13H0a1 1 0 0 0 1.553.833L1 15.253zm8.447.833a1 1 0 1 0 1.107-1.666l-1.107 1.666zm0-14.666a1 1 0 1 0 1.107 1.666L9.447 1.42zM19 2.253h1a1 1 0 0 0-.447-.833L19 2.253zm0 13l-.553.833A1 1 0 0 0 20 15.253h-1zm-9.553-.833a1 1 0 1 0 1.107 1.666L9.447 14.42zM9 2.253v13h2v-13H9zm1.553-.833C9.203.523 7.42 0 5.5 0v2c1.572 0 2.961.431 3.947 1.086l1.107-1.666zM5.5 0C3.58 0 1.797.523.447 1.42l1.107 1.666C2.539 2.431 3.928 2 5.5 2V0zM0 2.253v13h2v-13H0zm1.553 13.833C2.539 15.431 3.928 15 5.5 15v-2c-1.92 0-3.703.523-5.053 1.42l1.107 1.666zM5.5 15c1.572 0 2.961.431 3.947 1.086l1.107-1.666C9.203 13.523 7.42 13 5.5 13v2zm5.053-11.914C11.539 2.431 12.928 2 14.5 2V0c-1.92 0-3.703.523-5.053 1.42l1.107 1.666zM14.5 2c1.573 0 2.961.431 3.947 1.086l1.107-1.666C18.203.523 16.421 0 14.5 0v2zm3.5.253v13h2v-13h-2zm1.553 12.167C18.203 13.523 16.421 13 14.5 13v2c1.573 0 2.961.431 3.947 1.086l1.107-1.666zM14.5 13c-1.92 0-3.703.523-5.053 1.42l1.107 1.666C11.539 15.431 12.928 15 14.5 15v-2z"
       d="M11 2.253a1 1 0 1 0-2 0h2zm-2 13a1 1 0 1 0 2 0H9zm.447-12.167a1 1 0 1 0 1.107-1.666L9.447 3.086zM1 2.253L.447 1.42A1 1 0 0 0 0 2.253h1zm0 13H0a1 1 0 0 0 1.553.833L1 15.253zm8.447.833a1 1 0 1 0 1.107-1.666l-1.107 1.666zm0-14.666a1 1 0 1 0 1.107 1.666L9.447 1.42zM19 2.253h1a1 1 0 0 0-.447-.833L19 2.253zm0 13l-.553.833A1 1 0 0 0 20 15.253h-1zm-9.553-.833a1 1 0 1 0 1.107 1.666L9.447 14.42zM9 2.253v13h2v-13H9zm1.553-.833C9.203.523 7.42 0 5.5 0v2c1.572 0 2.961.431 3.947 1.086l1.107-1.666zM5.5 0C3.58 0 1.797.523.447 1.42l1.107 1.666C2.539 2.431 3.928 2 5.5 2V0zM0 2.253v13h2v-13H0zm1.553 13.833C2.539 15.431 3.928 15 5.5 15v-2c-1.92 0-3.703.523-5.053 1.42l1.107 1.666zM5.5 15c1.572 0 2.961.431 3.947 1.086l1.107-1.666C9.203 13.523 7.42 13 5.5 13v2zm5.053-11.914C11.539 2.431 12.928 2 14.5 2V0c-1.92 0-3.703.523-5.053 1.42l1.107 1.666zM14.5 2c1.573 0 2.961.431 3.947 1.086l1.107-1.666C18.203.523 16.421 0 14.5 0v2zm3.5.253v13h2v-13h-2zm1.553 12.167C18.203 13.523 16.421 13 14.5 13v2c1.573 0 2.961.431 3.947 1.086l1.107-1.666zM14.5 13c-1.92 0-3.703.523-5.053 1.42l1.107 1.666C11.539 15.431 12.928 15 14.5 15v-2z"

+ 3 - 3
src/components/icons/IconEcosystem.vue

@@ -1,9 +1,9 @@
 <template>
 <template>
   <svg
   <svg
-    xmlns="http://www.w3.org/2000/svg"
-    width="18"
-    height="20"
     fill="currentColor"
     fill="currentColor"
+    height="20"
+    width="18"
+    xmlns="http://www.w3.org/2000/svg"
   >
   >
     <path
     <path
       d="M11.447 8.894a1 1 0 1 0-.894-1.789l.894 1.789zm-2.894-.789a1 1 0 1 0 .894 1.789l-.894-1.789zm0 1.789a1 1 0 1 0 .894-1.789l-.894 1.789zM7.447 7.106a1 1 0 1 0-.894 1.789l.894-1.789zM10 9a1 1 0 1 0-2 0h2zm-2 2.5a1 1 0 1 0 2 0H8zm9.447-5.606a1 1 0 1 0-.894-1.789l.894 1.789zm-2.894-.789a1 1 0 1 0 .894 1.789l-.894-1.789zm2 .789a1 1 0 1 0 .894-1.789l-.894 1.789zm-1.106-2.789a1 1 0 1 0-.894 1.789l.894-1.789zM18 5a1 1 0 1 0-2 0h2zm-2 2.5a1 1 0 1 0 2 0h-2zm-5.447-4.606a1 1 0 1 0 .894-1.789l-.894 1.789zM9 1l.447-.894a1 1 0 0 0-.894 0L9 1zm-2.447.106a1 1 0 1 0 .894 1.789l-.894-1.789zm-6 3a1 1 0 1 0 .894 1.789L.553 4.106zm2.894.789a1 1 0 1 0-.894-1.789l.894 1.789zm-2-.789a1 1 0 1 0-.894 1.789l.894-1.789zm1.106 2.789a1 1 0 1 0 .894-1.789l-.894 1.789zM2 5a1 1 0 1 0-2 0h2zM0 7.5a1 1 0 1 0 2 0H0zm8.553 12.394a1 1 0 1 0 .894-1.789l-.894 1.789zm-1.106-2.789a1 1 0 1 0-.894 1.789l.894-1.789zm1.106 1a1 1 0 1 0 .894 1.789l-.894-1.789zm2.894.789a1 1 0 1 0-.894-1.789l.894 1.789zM8 19a1 1 0 1 0 2 0H8zm2-2.5a1 1 0 1 0-2 0h2zm-7.447.394a1 1 0 1 0 .894-1.789l-.894 1.789zM1 15H0a1 1 0 0 0 .553.894L1 15zm1-2.5a1 1 0 1 0-2 0h2zm12.553 2.606a1 1 0 1 0 .894 1.789l-.894-1.789zM17 15l.447.894A1 1 0 0 0 18 15h-1zm1-2.5a1 1 0 1 0-2 0h2zm-7.447-5.394l-2 1 .894 1.789 2-1-.894-1.789zm-1.106 1l-2-1-.894 1.789 2 1 .894-1.789zM8 9v2.5h2V9H8zm8.553-4.894l-2 1 .894 1.789 2-1-.894-1.789zm.894 0l-2-1-.894 1.789 2 1 .894-1.789zM16 5v2.5h2V5h-2zm-4.553-3.894l-2-1-.894 1.789 2 1 .894-1.789zm-2.894-1l-2 1 .894 1.789 2-1L8.553.106zM1.447 5.894l2-1-.894-1.789-2 1 .894 1.789zm-.894 0l2 1 .894-1.789-2-1-.894 1.789zM0 5v2.5h2V5H0zm9.447 13.106l-2-1-.894 1.789 2 1 .894-1.789zm0 1.789l2-1-.894-1.789-2 1 .894 1.789zM10 19v-2.5H8V19h2zm-6.553-3.894l-2-1-.894 1.789 2 1 .894-1.789zM2 15v-2.5H0V15h2zm13.447 1.894l2-1-.894-1.789-2 1 .894 1.789zM18 15v-2.5h-2V15h2z"
       d="M11.447 8.894a1 1 0 1 0-.894-1.789l.894 1.789zm-2.894-.789a1 1 0 1 0 .894 1.789l-.894-1.789zm0 1.789a1 1 0 1 0 .894-1.789l-.894 1.789zM7.447 7.106a1 1 0 1 0-.894 1.789l.894-1.789zM10 9a1 1 0 1 0-2 0h2zm-2 2.5a1 1 0 1 0 2 0H8zm9.447-5.606a1 1 0 1 0-.894-1.789l.894 1.789zm-2.894-.789a1 1 0 1 0 .894 1.789l-.894-1.789zm2 .789a1 1 0 1 0 .894-1.789l-.894 1.789zm-1.106-2.789a1 1 0 1 0-.894 1.789l.894-1.789zM18 5a1 1 0 1 0-2 0h2zm-2 2.5a1 1 0 1 0 2 0h-2zm-5.447-4.606a1 1 0 1 0 .894-1.789l-.894 1.789zM9 1l.447-.894a1 1 0 0 0-.894 0L9 1zm-2.447.106a1 1 0 1 0 .894 1.789l-.894-1.789zm-6 3a1 1 0 1 0 .894 1.789L.553 4.106zm2.894.789a1 1 0 1 0-.894-1.789l.894 1.789zm-2-.789a1 1 0 1 0-.894 1.789l.894-1.789zm1.106 2.789a1 1 0 1 0 .894-1.789l-.894 1.789zM2 5a1 1 0 1 0-2 0h2zM0 7.5a1 1 0 1 0 2 0H0zm8.553 12.394a1 1 0 1 0 .894-1.789l-.894 1.789zm-1.106-2.789a1 1 0 1 0-.894 1.789l.894-1.789zm1.106 1a1 1 0 1 0 .894 1.789l-.894-1.789zm2.894.789a1 1 0 1 0-.894-1.789l.894 1.789zM8 19a1 1 0 1 0 2 0H8zm2-2.5a1 1 0 1 0-2 0h2zm-7.447.394a1 1 0 1 0 .894-1.789l-.894 1.789zM1 15H0a1 1 0 0 0 .553.894L1 15zm1-2.5a1 1 0 1 0-2 0h2zm12.553 2.606a1 1 0 1 0 .894 1.789l-.894-1.789zM17 15l.447.894A1 1 0 0 0 18 15h-1zm1-2.5a1 1 0 1 0-2 0h2zm-7.447-5.394l-2 1 .894 1.789 2-1-.894-1.789zm-1.106 1l-2-1-.894 1.789 2 1 .894-1.789zM8 9v2.5h2V9H8zm8.553-4.894l-2 1 .894 1.789 2-1-.894-1.789zm.894 0l-2-1-.894 1.789 2 1 .894-1.789zM16 5v2.5h2V5h-2zm-4.553-3.894l-2-1-.894 1.789 2 1 .894-1.789zm-2.894-1l-2 1 .894 1.789 2-1L8.553.106zM1.447 5.894l2-1-.894-1.789-2 1 .894 1.789zm-.894 0l2 1 .894-1.789-2-1-.894 1.789zM0 5v2.5h2V5H0zm9.447 13.106l-2-1-.894 1.789 2 1 .894-1.789zm0 1.789l2-1-.894-1.789-2 1 .894 1.789zM10 19v-2.5H8V19h2zm-6.553-3.894l-2-1-.894 1.789 2 1 .894-1.789zM2 15v-2.5H0V15h2zm13.447 1.894l2-1-.894-1.789-2 1 .894 1.789zM18 15v-2.5h-2V15h2z"

+ 3 - 3
src/components/icons/IconSupport.vue

@@ -1,9 +1,9 @@
 <template>
 <template>
   <svg
   <svg
-    xmlns="http://www.w3.org/2000/svg"
-    width="20"
-    height="20"
     fill="currentColor"
     fill="currentColor"
+    height="20"
+    width="20"
+    xmlns="http://www.w3.org/2000/svg"
   >
   >
     <path
     <path
       d="M10 3.22l-.61-.6a5.5 5.5 0 0 0-7.666.105 5.5 5.5 0 0 0-.114 7.665L10 18.78l8.39-8.4a5.5 5.5 0 0 0-.114-7.665 5.5 5.5 0 0 0-7.666-.105l-.61.61z"
       d="M10 3.22l-.61-.6a5.5 5.5 0 0 0-7.666.105 5.5 5.5 0 0 0-.114 7.665L10 18.78l8.39-8.4a5.5 5.5 0 0 0-.114-7.665 5.5 5.5 0 0 0-7.666-.105l-.61.61z"

+ 3 - 4
src/components/icons/IconTooling.vue

@@ -1,15 +1,14 @@
 <!-- This icon is from <https://github.com/Templarian/MaterialDesign>, distributed under Apache 2.0 (https://www.apache.org/licenses/LICENSE-2.0) license-->
 <!-- This icon is from <https://github.com/Templarian/MaterialDesign>, distributed under Apache 2.0 (https://www.apache.org/licenses/LICENSE-2.0) license-->
 <template>
 <template>
   <svg
   <svg
-    xmlns="http://www.w3.org/2000/svg"
-    xmlns:xlink="http://www.w3.org/1999/xlink"
     aria-hidden="true"
     aria-hidden="true"
-    role="img"
     class="iconify iconify--mdi"
     class="iconify iconify--mdi"
-    width="24"
     height="24"
     height="24"
     preserveAspectRatio="xMidYMid meet"
     preserveAspectRatio="xMidYMid meet"
+    role="img"
     viewBox="0 0 24 24"
     viewBox="0 0 24 24"
+    width="24"
+    xmlns="http://www.w3.org/2000/svg"
   >
   >
     <path
     <path
       d="M20 18v-4h-3v1h-2v-1H9v1H7v-1H4v4h16M6.33 8l-1.74 4H7v-1h2v1h6v-1h2v1h2.41l-1.74-4H6.33M9 5v1h6V5H9m12.84 7.61c.1.22.16.48.16.8V18c0 .53-.21 1-.6 1.41c-.4.4-.85.59-1.4.59H4c-.55 0-1-.19-1.4-.59C2.21 19 2 18.53 2 18v-4.59c0-.32.06-.58.16-.8L4.5 7.22C4.84 6.41 5.45 6 6.33 6H7V5c0-.55.18-1 .57-1.41C7.96 3.2 8.44 3 9 3h6c.56 0 1.04.2 1.43.59c.39.41.57.86.57 1.41v1h.67c.88 0 1.49.41 1.83 1.22l2.34 5.39z"
       d="M20 18v-4h-3v1h-2v-1H9v1H7v-1H4v4h16M6.33 8l-1.74 4H7v-1h2v1h6v-1h2v1h2.41l-1.74-4H6.33M9 5v1h6V5H9m12.84 7.61c.1.22.16.48.16.8V18c0 .53-.21 1-.6 1.41c-.4.4-.85.59-1.4.59H4c-.55 0-1-.19-1.4-.59C2.21 19 2 18.53 2 18v-4.59c0-.32.06-.58.16-.8L4.5 7.22C4.84 6.41 5.45 6 6.33 6H7V5c0-.55.18-1 .57-1.41C7.96 3.2 8.44 3 9 3h6c.56 0 1.04.2 1.43.59c.39.41.57.86.57 1.41v1h.67c.88 0 1.49.41 1.83 1.22l2.34 5.39z"

+ 2 - 9
src/layout/components/AppMain.vue

@@ -1,18 +1,11 @@
 <template>
 <template>
   <section ref="appMain" class="app-main">
   <section ref="appMain" class="app-main">
-    <router-view :key="route.path" />
+    <router-view :key="route.path"/>
   </section>
   </section>
 </template>
 </template>
 
 
 <script lang="ts" setup>
 <script lang="ts" setup>
-import { RouterView, useRoute } from 'vue-router'
+import {RouterView, useRoute} from 'vue-router'
 
 
 let route = useRoute()
 let route = useRoute()
 </script>
 </script>
-
-<style lang="scss" scoped>
-.app-main {
-  width: 100%;
-  height: 100%;
-}
-</style>

+ 11 - 14
src/layout/components/Navbar.vue

@@ -1,7 +1,7 @@
 <script lang="ts" setup>
 <script lang="ts" setup>
-import { onMounted } from 'vue'
-import { getToken } from '@/utils/auth'
-import { jumpPage } from '@/utils'
+import {onMounted} from 'vue'
+import {getToken} from '@/utils/auth'
+import {jumpPage} from '@/utils'
 
 
 let navbarBackgroundImage = new URL(
 let navbarBackgroundImage = new URL(
   '@/assets/images/layout/header-background.png',
   '@/assets/images/layout/header-background.png',
@@ -86,7 +86,7 @@ function logout() {
     class="navbar"
     class="navbar"
   >
   >
     <div class="index" @click="jumpPage('/')">
     <div class="index" @click="jumpPage('/')">
-      <img :alt="title" :src="navbarTitleImage" />
+      <img :alt="title" :src="navbarTitleImage"/>
     </div>
     </div>
   </div>
   </div>
 </template>
 </template>
@@ -96,17 +96,14 @@ function logout() {
   background-size: 100%;
   background-size: 100%;
   position: relative;
   position: relative;
   color: #fff;
   color: #fff;
+  pointer-events: auto;
 
 
-  .index {
-    width: 100%;
-    height: 70%;
-    display: flex;
-    justify-content: center;
-    align-items: center;
-
-    img {
-      width: 20%;
-    }
+  img {
+    width: 20%;
+    position: absolute;
+    top: 2vh;
+    left: 50%;
+    transform: translateX(-50%);
   }
   }
 
 
 }
 }

+ 2 - 2
src/layout/components/index.js

@@ -1,2 +1,2 @@
-export { default as AppMain } from './AppMain.vue'
-export { default as Navbar } from './Navbar.vue'
+export {default as AppMain} from './AppMain.vue'
+export {default as Navbar} from './Navbar.vue'

+ 95 - 6
src/layout/index.vue

@@ -1,20 +1,33 @@
 <script lang="ts" setup>
 <script lang="ts" setup>
-import { AppMain, Navbar } from '@/layout/components/index.js'
+import {AppMain, Navbar} from '@/layout/components/index.js'
+import AntvMap from '@/components/AntvMap/index.vue'
 
 
-let navbarHeight = '10vh'
+let navbarHeight = '12vh'
 
 
 let theme = ''
 let theme = ''
-let navbarStyle = { height: navbarHeight }
+let navbarStyle = {height: navbarHeight}
 </script>
 </script>
 
 
 <template>
 <template>
   <div :style="{ '--current-color': theme }" class="app-wrapper">
   <div :style="{ '--current-color': theme }" class="app-wrapper">
+
+    <antv-map class="floor-container"></antv-map>
+
+    <!-- 顶部阴影 -->
+    <div class="header-shade"></div>
+    <!-- 底部阴影 -->
+    <!--    <div class="bottom-shade"></div>-->
+    <!-- 左侧阴影 -->
+    <div class="left-shade"></div>
+    <!-- 右侧阴影 -->
+    <div class="right-shade"></div>
+
     <!-- 主页 -->
     <!-- 主页 -->
     <div class="main-container">
     <div class="main-container">
       <!-- 顶部 -->
       <!-- 顶部 -->
-      <navbar :style="navbarStyle" />
+      <navbar :style="navbarStyle"/>
       <!-- 展示页 -->
       <!-- 展示页 -->
-      <app-main :style="{ height: `calc(100% - ${navbarHeight})` }" />
+      <app-main/>
     </div>
     </div>
   </div>
   </div>
 </template>
 </template>
@@ -23,10 +36,13 @@ let navbarStyle = { height: navbarHeight }
 @use '@/assets/styles/mixin.scss';
 @use '@/assets/styles/mixin.scss';
 @use '@/assets/styles/variables.scss';
 @use '@/assets/styles/variables.scss';
 
 
+$base-background-color: rgba(10, 117, 195, 0.6);
+$base-background-color2: rgba(10, 117, 195, 1);
+
 .app-wrapper {
 .app-wrapper {
   height: 100%;
   height: 100%;
   width: 100%;
   width: 100%;
-  background: #000;
+  pointer-events: none;
 
 
   .main-container {
   .main-container {
     // background: linear-gradient(120deg, #6236ff 0%, #6236ff 0%, #32c5ff 99%, #32c5ff 100%);
     // background: linear-gradient(120deg, #6236ff 0%, #6236ff 0%, #32c5ff 99%, #32c5ff 100%);
@@ -38,6 +54,79 @@ let navbarStyle = { height: navbarHeight }
       width: 100%;
       width: 100%;
       background-image: linear-gradient(132deg, #4791f8 0%, #2d3bdb 100%);
       background-image: linear-gradient(132deg, #4791f8 0%, #2d3bdb 100%);
     }
     }
+
+    .app-main {
+      position: absolute;
+      top: 8vh;
+      left: 0;
+      width: 100%;
+      height: 92vh;
+    }
+  }
+
+  .floor-container {
+    position: fixed;
+    top: 0;
+    left: 0;
+    width: 100%;
+    height: 100%;
+  }
+
+  .header-shade {
+    width: 100%;
+    height: 8vh;
+    top: 0;
+    left: 0;
+    background-image: linear-gradient(180deg,
+      $base-background-color2 0%,
+      rgba(0, 0, 0, 0) 100%);
+
   }
   }
+
+  .bottom-shade {
+    width: 100%;
+    height: 8vh;
+    bottom: 0;
+    left: 0;
+    background-image: linear-gradient(0deg,
+      $base-background-color 0%,
+      rgba(0, 0, 0, 0) 100%);
+
+  }
+
+  .left-shade {
+    top: 0;
+    left: 0;
+    background-image: linear-gradient(90deg,
+      $base-background-color 0%,
+      rgba(0, 0, 0, 0) 100%);
+  }
+
+  .right-shade {
+    top: 0;
+    right: 0;
+    background-image: linear-gradient(270deg,
+      $base-background-color 0%,
+      rgba(0, 0, 0, 0) 100%);
+  }
+
+  .left-shade,
+  .right-shade {
+    width: 25vw;
+    height: 100%;
+  }
+
+  .header-shade,
+  .bottom-shade,
+  .left-shade,
+  .right-shade {
+    pointer-events: none;
+    //z-index: 1;
+    position: absolute;
+    background-size: 100% 100%;
+    background-repeat: no-repeat;
+    opacity: 1;
+  }
+
 }
 }
 </style>
 </style>

+ 4 - 4
src/layout/mixin/ResizeHandler.js

@@ -1,13 +1,13 @@
 import store from '@/store'
 import store from '@/store'
 
 
-const { body } = document
+const {body} = document
 const WIDTH = 992 // refer to Bootstrap's responsive design
 const WIDTH = 992 // refer to Bootstrap's responsive design
 
 
 export default {
 export default {
   watch: {
   watch: {
     $route(route) {
     $route(route) {
       if (this.device === 'mobile' && this.sidebar.opened) {
       if (this.device === 'mobile' && this.sidebar.opened) {
-        store.dispatch('app/closeSideBar', { withoutAnimation: false })
+        store.dispatch('app/closeSideBar', {withoutAnimation: false})
       }
       }
     },
     },
   },
   },
@@ -21,7 +21,7 @@ export default {
     const isMobile = this.$_isMobile()
     const isMobile = this.$_isMobile()
     if (isMobile) {
     if (isMobile) {
       store.dispatch('app/toggleDevice', 'mobile')
       store.dispatch('app/toggleDevice', 'mobile')
-      store.dispatch('app/closeSideBar', { withoutAnimation: true })
+      store.dispatch('app/closeSideBar', {withoutAnimation: true})
     }
     }
   },
   },
   methods: {
   methods: {
@@ -37,7 +37,7 @@ export default {
         store.dispatch('app/toggleDevice', isMobile ? 'mobile' : 'desktop')
         store.dispatch('app/toggleDevice', isMobile ? 'mobile' : 'desktop')
 
 
         if (isMobile) {
         if (isMobile) {
-          store.dispatch('app/closeSideBar', { withoutAnimation: true })
+          store.dispatch('app/closeSideBar', {withoutAnimation: true})
         }
         }
       }
       }
     },
     },

+ 4 - 4
src/main.ts

@@ -1,9 +1,9 @@
 import './assets/styles/index.scss'
 import './assets/styles/index.scss'
 
 
-import { createApp } from 'vue'
-import { createPinia } from 'pinia'
+import {createApp} from 'vue'
+import {createPinia} from 'pinia'
 import VueCookies from 'vue3-cookies'
 import VueCookies from 'vue3-cookies'
-import { VueSvgIconPlugin } from '@yzfe/vue-svgicon'
+import {VueSvgIconPlugin} from '@yzfe/vue-svgicon'
 import '@yzfe/svgicon/lib/svgicon.css'
 import '@yzfe/svgicon/lib/svgicon.css'
 
 
 // @ts-ignore
 // @ts-ignore
@@ -15,7 +15,7 @@ const app = createApp(App)
 app.use(createPinia())
 app.use(createPinia())
 app.use(router)
 app.use(router)
 app.use(VueCookies)
 app.use(VueCookies)
-app.use(VueSvgIconPlugin, { tagName: 'icon' })
+app.use(VueSvgIconPlugin, {tagName: 'icon'})
 
 
 // 添加到vue对象上,可以在全局通过 this.$cookies来调用
 // 添加到vue对象上,可以在全局通过 this.$cookies来调用
 app.config.globalProperties.$cookies = VueCookies
 app.config.globalProperties.$cookies = VueCookies

+ 2 - 2
src/router/index.ts

@@ -1,4 +1,4 @@
-import { createRouter, createWebHistory } from 'vue-router'
+import {createRouter, createWebHistory} from 'vue-router'
 import layout from '@/layout/index.vue'
 import layout from '@/layout/index.vue'
 
 
 const router = createRouter({
 const router = createRouter({
@@ -12,7 +12,7 @@ const router = createRouter({
         {
         {
           path: 'index',
           path: 'index',
           name: 'home',
           name: 'home',
-          component: () => import('@/views/HomeView.vue'),
+          component: () => import('@/views/Home.vue'),
         },
         },
       ],
       ],
     },
     },

+ 4 - 3
src/stores/counter.ts

@@ -1,12 +1,13 @@
-import { ref, computed } from 'vue'
-import { defineStore } from 'pinia'
+import {computed, ref} from 'vue'
+import {defineStore} from 'pinia'
 
 
 export const useCounterStore = defineStore('counter', () => {
 export const useCounterStore = defineStore('counter', () => {
   const count = ref(0)
   const count = ref(0)
   const doubleCount = computed(() => count.value * 2)
   const doubleCount = computed(() => count.value * 2)
+
   function increment() {
   function increment() {
     count.value++
     count.value++
   }
   }
 
 
-  return { count, doubleCount, increment }
+  return {count, doubleCount, increment}
 })
 })

+ 3 - 3
src/utils/auth.ts

@@ -3,16 +3,16 @@ import useCurrentInstance from '@/utils/useCurrentInstance'
 const TokenKey = 'Admin-Token'
 const TokenKey = 'Admin-Token'
 
 
 export function getToken() {
 export function getToken() {
-  const { proxy } = useCurrentInstance()
+  const {proxy} = useCurrentInstance()
   return proxy.$cookies.get(TokenKey)
   return proxy.$cookies.get(TokenKey)
 }
 }
 
 
 export function setToken(token: string) {
 export function setToken(token: string) {
-  const { proxy } = useCurrentInstance()
+  const {proxy} = useCurrentInstance()
   return proxy.$cookies.set(TokenKey, token)
   return proxy.$cookies.set(TokenKey, token)
 }
 }
 
 
 export function removeToken() {
 export function removeToken() {
-  const { proxy } = useCurrentInstance()
+  const {proxy} = useCurrentInstance()
   return proxy.$cookies.remove(TokenKey)
   return proxy.$cookies.remove(TokenKey)
 }
 }

+ 67 - 0
src/utils/image.ts

@@ -0,0 +1,67 @@
+import {validURL} from "@/utils/validate";
+
+/**
+ * 图片转圆形图片
+ * @param {图片地址} imageSrc
+ */
+export function circularPicture(imageSrc: string) {
+  const fun = (resolve: any) => {
+    const canvas = document.createElement("canvas");
+    const contex = canvas.getContext("2d") as CanvasRenderingContext2D;
+    const img = new Image();
+    img.crossOrigin = "";
+
+    img.onload = () => {
+      let center = {
+        x: img.width / 2,
+        y: img.height / 2.5,
+      };
+      let diameter = img.width;
+      canvas.width = diameter;
+      canvas.height = diameter;
+      contex.clearRect(0, 0, diameter, diameter);
+      contex.save();
+      contex.beginPath();
+      let radius = img.width / 2;
+      contex.arc(radius, radius, radius, 0, 2 * Math.PI); //画出圆
+      contex.clip(); //裁剪上面的圆形
+      contex.drawImage(
+        img,
+        center.x - radius,
+        center.y - radius,
+        diameter,
+        diameter,
+        0,
+        0,
+        diameter,
+        diameter
+      ); // 在刚刚裁剪的园上画图
+      contex.restore(); // 还原状态
+      resolve(canvas.toDataURL("image/png", 1));
+    };
+    img.src = imageSrc;
+  };
+  return new Promise(fun);
+}
+
+/**
+ * 查询图片地址
+ * @param imageSrc 图片地址
+ * @param baseApi 后台基础路径
+ * @returns {*|string}
+ */
+export function getImageSrc(imageSrc: string) {
+  const baseApi = process.env.VUE_APP_BASE_API as string
+
+  if (!imageSrc) {
+    return imageSrc
+  } else if (validURL(imageSrc)) {
+    return imageSrc
+  } else if (imageSrc.indexOf(baseApi) === 0) {
+    return imageSrc
+  } else if (imageSrc.startsWith('/static')) {
+    return imageSrc
+  } else {
+    return baseApi + imageSrc
+  }
+}

+ 28 - 3
src/utils/index.ts

@@ -1,4 +1,4 @@
-import { useRouter } from 'vue-router'
+import {useRouter} from 'vue-router'
 
 
 const router = useRouter()
 const router = useRouter()
 
 
@@ -15,14 +15,39 @@ export const getImgUrl = (name: string) => {
 export function jumpPage(path: string, query: any = null, isBlank = false) {
 export function jumpPage(path: string, query: any = null, isBlank = false) {
   if (path) {
   if (path) {
     if (!isBlank) {
     if (!isBlank) {
-      router.push({ path, query })
+      router.push({path, query})
     } else {
     } else {
       if (path.indexOf('http') !== -1) {
       if (path.indexOf('http') !== -1) {
         window.open(path, '_blank')
         window.open(path, '_blank')
       } else {
       } else {
-        let routeUrl = router.resolve({ path, query })
+        let routeUrl = router.resolve({path, query})
         window.open(routeUrl.href, '_blank')
         window.open(routeUrl.href, '_blank')
       }
       }
     }
     }
   }
   }
 }
 }
+
+
+/**
+ * Merges two objects, giving the last one precedence
+ * @param {Object} target
+ * @param {(Object|Array)} source
+ * @returns {Object}
+ */
+export function objectMerge(target: any, source: any) {
+  if (typeof target !== 'object') {
+    target = {}
+  }
+  if (Array.isArray(source)) {
+    return source.slice()
+  }
+  Object.keys(source).forEach(property => {
+    const sourceProperty = source[property]
+    if (typeof sourceProperty === 'object' && sourceProperty !== null) {
+      target[property] = objectMerge(target[property], sourceProperty)
+    } else {
+      target[property] = sourceProperty
+    }
+  })
+  return target
+}

+ 341 - 0
src/utils/mapConfig.ts

@@ -0,0 +1,341 @@
+// import {LineLayer, PointLayer, PolygonLayer, Popup} from "@antv/l7";
+// import {formatStringByTemplate} from "@/utils/string";
+// import {getUUID} from "@/utils/ruoyi";
+import {Scene} from "@antv/l7-scene";
+import type {ICameraOptions, Point} from "@antv/l7-core";
+
+/**
+ * 设置地图视角
+ * @param scene
+ * @param center  中心点
+ * @param zoom    层级
+ * @param pitch   倾斜角度
+ * @param padding
+ */
+export function setCenter(scene: Scene, center: Point, zoom: number, pitch = 0, padding = {}) {
+  if (center && zoom) {
+    scene.setZoomAndCenter(zoom, center);
+  } else if (center) {
+    scene.setCenter(center, padding as ICameraOptions);
+  } else if (zoom) {
+    scene.setZoom(zoom);
+  }
+  scene.setPitch(pitch);
+}
+
+//
+// let _scene: any;
+//
+// const imageMap = new Map()
+//
+// function getValueByConditions(data: any, conditions: any, field: any) {
+//   let condition;
+//   for (const item of conditions) {
+//     if (compareByConditions(data, item)) {
+//       condition = item
+//       break
+//     }
+//   }
+//
+//   if (!condition) {
+//     return null;
+//   }
+//
+//   if (field === 'color') {
+//     return condition[field]
+//   }
+//
+//   if (condition[field] !== 'image') {
+//     return condition[field]
+//   }
+//
+//   if (condition[field] === 'image') {
+//     let id;
+//     if (imageMap.has(condition.imageSrc)) {
+//       id = imageMap.get(condition.imageSrc)
+//     } else {
+//       id = getUUID()
+//       imageMap.set(condition.imageSrc, id);
+//       _scene.addImage(id, condition.imageSrc);
+//     }
+//     return id
+//   }
+//
+//   return null;
+// }
+//
+// function compareByConditions(data: any, condition: any) {
+//   switch (condition.cond) {
+//     case 'lt':
+//       if (data < condition.value) {
+//         return true
+//       }
+//       break
+//     case 'lte':
+//       if (data <= condition.value) {
+//         return true
+//       }
+//       break
+//     case 'eq':
+//       if (data == condition.value) {
+//         return true
+//       }
+//       break
+//     case 'gt':
+//       if (data > condition.value) {
+//         return true
+//       }
+//       break
+//     case 'gte':
+//       if (data >= condition.value) {
+//         return true
+//       }
+//       break
+//     case 'ne':
+//       if (data != condition.value) {
+//         return true
+//       }
+//       break
+//   }
+//   return false;
+// }
+//
+// /**
+//  * 图层数据配置
+//  * @param dataType
+//  * @returns {{parser: {x: string, y: string, type: string}}|null}
+//  */
+// function getSourceOptions(dataType: any) {
+//   switch (dataType) {
+//     case 'geojson':
+//       return null;
+//     case 'list':
+//       return {
+//         parser: {
+//           type: 'json',
+//           x: '经度',
+//           y: '纬度',
+//         },
+//       }
+//     default:
+//   }
+// }
+//
+// /**
+//  * 图层形状配置
+//  * @param layer
+//  * @param config
+//  */
+// function setLayerShape(layer: any, config: any) {
+//   if (config.isConstant) {
+//     let shape = config.value
+//     if (config.value === 'image') {
+//       let id;
+//       if (imageMap.has(config.imageSrc)) {
+//         id = imageMap.get(config.imageSrc)
+//       } else {
+//         id = getUUID()
+//         imageMap.set(config.imageSrc, id);
+//         _scene.addImage(id, config.imageSrc);
+//       }
+//       shape = id
+//     }
+//     layer.shape(shape)
+//   } else {
+//     const field = config.field
+//     const conditions = config.conditions
+//     layer.shape(field, (field: any) => {
+//       return getValueByConditions(field, conditions, 'shape');
+//     });
+//   }
+// }
+//
+// /**
+//  * 图层颜色配置
+//  * @param layer
+//  * @param config
+//  */
+// function setLayerColor(layer: any, config: any) {
+//   if (config.isConstant) {
+//     layer.color(config.value)
+//   } else {
+//     const field = config.field
+//     const conditions = config.conditions
+//     layer.color(field, (field: any) => {
+//       return getValueByConditions(field, conditions, 'color');
+//     });
+//   }
+// }
+//
+// export function setMark({dataType, mapData}, config: any, scene: any) {
+//   if (scene) {
+//     _scene = scene
+//   }
+//
+//   const sourceOptions = getSourceOptions(dataType)
+//   if (!config.enabled) {
+//     return
+//   }
+//   // 标注
+//   const markLayer = new PointLayer({})
+//     .source(mapData)
+//     .shape(config.field, 'text')
+//     .size(12)
+//     .color('#000')
+//     .style({
+//       textAnchor: 'center', // 文本相对锚点的位置 center|left|right|top|bottom|top-left
+//       textOffset: [0, 0], // 文本相对锚点的偏移量 [水平, 垂直]
+//       spacing: 2, // 字符间距
+//       padding: [1, 1], // 文本包围盒 padding [水平,垂直],影响碰撞检测结果,避免相邻文本靠的太近
+//       stroke: '#ffffff', // 描边颜色
+//       strokeWidth: 2, // 描边宽度
+//       strokeOpacity: 1.0,
+//     });
+//   _scene.addLayer(markLayer);
+// }
+//
+// function setPopup(layer: any, config: any) {
+//   if (!config.enabled) {
+//     return
+//   }
+//   layer.on(config.trigger, (e) => {
+//     let properties = e.feature.properties || e.feature
+//     const content = formatStringByTemplate(properties, config.content)
+//
+//     // offsets: [0, 0], closeButton: false,
+//     const popup = new Popup({})
+//       .setLnglat(e.lngLat)
+//       .setHTML(content);
+//     _scene.addPopup(popup);
+//   });
+// }
+//
+// function createPointByCongfig(config: any, mapData, dataType) {
+//   const sourceOptions = getSourceOptions(dataType)
+//   const layer = new PointLayer({})
+//     .source(mapData, sourceOptions)
+//     .size(16)
+//     .active(config.active);
+//   setLayerShape(layer, config.shape);
+//   // 颜色配置
+//   setLayerColor(layer, config.color);
+//   _scene.addLayer(layer);
+//   setMark({dataType, mapData}, config.mark);
+//   setPopup(layer, config.popup);
+// }
+//
+// /**
+//  * 创建线图层
+//  * @param config 配置
+//  * @param dataType 图层数据类型
+//  * @param mapData 图层数据
+//  */
+// function createLineByCongfig(config, {dataType, mapData}) {
+//   const sourceOptions = getSourceOptions(dataType)
+//   const layer = new LineLayer({})
+//     .source(mapData, sourceOptions)
+//     .size(1)
+//     .style({
+//       lineType: 'dash',
+//       dashArray: [2, 2],
+//     })
+//     .active(config.active);
+//   _scene.addLayer(layer);
+//   setMark({dataType, mapData}, config.mark);
+//   setPopup(layer, config.popup);
+// }
+//
+// /**
+//  * 创建面图层
+//  * @param config 配置
+//  * @param dataType 图层数据类型
+//  * @param mapData 图层数据
+//  */
+// function createPolygonByCongfig(config, {dataType, mapData}) {
+//   const sourceOptions = getSourceOptions(dataType)
+//   const layer = new PolygonLayer({})
+//     .source(mapData, sourceOptions)
+//     .shape('fill')
+//     .active(config.active);
+//   // 颜色配置
+//   setLayerColor(layer, config.color);
+//   _scene.addLayer(layer);
+//   setMark({dataType, mapData}, config.mark);
+//   setPopup(layer, config.popup);
+// }
+//
+// function createMarkByCongfig(config, {dataType, mapData}) {
+//   setMark({dataType, mapData}, config.mark)
+// }
+//
+// /**
+//  * 根据配置加载图层
+//  * @param scene
+//  * @param mapConfig
+//  * @returns {Promise<void>}
+//  */
+// export async function loadMapData(scene: any, mapConfig: any) {
+//   _scene = scene
+//   console.log('mapConfig', mapConfig);
+//   // 清空图层
+//   _scene.removeAllLayer();
+//   if (!mapConfig.enabled) {
+//     return
+//   }
+//
+//   const layers = mapConfig.layers
+//   for (const layer of layers) {
+//     if (!layer.data?.mapDataId) {
+//       continue;
+//     }
+//
+//     // 1. 加载数据
+//     const mapJson:any = {}
+//     console.log('mapJson', mapJson);
+//     // switch (layer.type) {
+//     //   case 'point':
+//     //     createPointByCongfig(layer, mapJson)
+//     //     break
+//     //   case 'line':
+//     //     createLineByCongfig(layer, mapJson)
+//     //     break
+//     //   case 'polygon':
+//     //     createPolygonByCongfig(layer, mapJson)
+//     //     break
+//     //   case 'mark':
+//     //     createMarkByCongfig(layer, mapJson)
+//     //     break
+//     // }
+//   }
+//
+//   // 视角
+//   setCenter(scene, mapConfig.center, mapConfig.zoom, mapConfig.pitch);
+// }
+//
+
+//
+//
+// export function getLegendData(config: any) {
+//   const layers = config.layers
+//
+//   const layerLegend = layers.reduce((acc: any, item: any) => {
+//     if (item.legend && item.legend.data) {
+//       return acc.concat(item.legend.data);
+//     } else {
+//       return acc;
+//     }
+//   }, []);
+//
+//   return layerLegend.map((item: any) => {
+//     const data = {label: item.text}
+//     if (item.shape === 'image') {
+//       data.imgType = 'img'
+//       data.src = item.imgSrc
+//     } else {
+//       data.imgType = 'svg'
+//       data.src = item.shape
+//       data.imgStyle = {fill: item.color}
+//     }
+//     return data
+//   })
+// }

+ 238 - 0
src/utils/ruoyi.ts

@@ -0,0 +1,238 @@
+/**
+ * 通用js方法封装处理
+ * Copyright (c) 2019 ruoyi
+ */
+
+// 日期格式化
+export function parseTime(time: any, pattern: string) {
+  if (arguments.length === 0 || !time) {
+    return null;
+  }
+  const format = pattern || "{y}-{m}-{d} {h}:{i}:{s}";
+  let date;
+  if (typeof time === "object") {
+    date = time;
+  } else {
+    if (typeof time === "string" && /^[0-9]+$/.test(time)) {
+      time = parseInt(time);
+    } else if (typeof time === "string") {
+      time = time
+        .replace(new RegExp(/-/gm), "/")
+        .replace("T", " ")
+        .replace(new RegExp(/\.[\d]{3}/gm), "");
+    }
+    if (typeof time === "number" && time.toString().length === 10) {
+      time = time * 1000;
+    }
+    date = new Date(time);
+  }
+  const formatObj: { [key: string]: any } = {
+    y: date.getFullYear(),
+    m: date.getMonth() + 1,
+    d: date.getDate(),
+    h: date.getHours(),
+    i: date.getMinutes(),
+    s: date.getSeconds(),
+    a: date.getDay(),
+  };
+  const time_str = format.replace(/{(y|m|d|h|i|s|a)+}/g, (result, key) => {
+    let value = formatObj[key];
+    // Note: getDay() returns 0 on Sunday
+    if (key === "a") {
+      return ["日", "一", "二", "三", "四", "五", "六"][value];
+    }
+    if (result.length > 0 && value < 10) {
+      value = "0" + value;
+    }
+    return value || 0;
+  });
+  return time_str;
+}
+
+// 字符串格式化(%s )
+export function sprintf(str: string) {
+  var args = arguments,
+    flag = true,
+    i = 1;
+  str = str.replace(/%s/g, function () {
+    var arg = args[i++];
+    if (typeof arg === "undefined") {
+      flag = false;
+      return "";
+    }
+    return arg;
+  });
+  return flag ? str : "";
+}
+
+// 转换字符串,undefined,null等转化为""
+export function praseStrEmpty(str: any) {
+  if (!str || str == "undefined" || str == "null") {
+    return "";
+  }
+  return str;
+}
+
+/**
+ * 构造树型结构数据
+ * @param {*} data 数据源
+ * @param {*} id id字段 默认 'id'
+ * @param {*} parentId 父节点字段 默认 'parentId'
+ * @param {*} children 孩子节点字段 默认 'children'
+ */
+export function handleTree(data: any[], id: string, parentId: string, children: string) {
+  let config = {
+    id: id || "id",
+    parentId: parentId || "parentId",
+    childrenList: children || "children",
+  };
+
+  var childrenListMap: { [key: string]: any } = {};
+  var nodeIds: { [key: string]: any } = {};
+  var tree = [];
+
+  for (let d of data) {
+    let parentId = d[config.parentId];
+    if (childrenListMap[parentId] == null) {
+      childrenListMap[parentId] = [];
+    }
+    nodeIds[d[config.id]] = d;
+    childrenListMap[parentId].push(d);
+  }
+
+  for (let d of data) {
+    let parentId = d[config.parentId];
+    if (nodeIds[parentId] == null) {
+      tree.push(d);
+    }
+  }
+
+  for (let t of tree) {
+    adaptToChildrenList(t);
+  }
+
+  function adaptToChildrenList(o: any) {
+    if (childrenListMap[o[config.id]] !== null) {
+      o[config.childrenList] = childrenListMap[o[config.id]];
+    }
+    if (o[config.childrenList]) {
+      for (let c of o[config.childrenList]) {
+        adaptToChildrenList(c);
+      }
+    }
+  }
+
+  return tree;
+}
+
+export function treeToList(treeList: any[]) {
+  let list: any[] = [];
+
+  function traverse(node: { [key: string]: any }) {
+    if (node) {
+      list.push(node); // 将节点的值新建到列表中
+      if (node.children) {
+        for (let child of node.children) {
+          traverse(child); // 递归遍历子节点
+        }
+      }
+    }
+  }
+
+  treeList.forEach((tree) => traverse(tree))
+  return list;
+}
+
+export function createTree(list: any[], value: any, parentId: string, id: string) {
+  let tree = [];
+  for (let num = 0; num < list.length; num++) {
+    let data = list[num];
+    if (data[parentId] === value) {
+      let children = createTree(list, data[id], parentId, id);
+      if (children && children.length > 0) {
+        data["children"] = children;
+      }
+      tree.push(data);
+    }
+  }
+  return tree;
+}
+
+export function copyObj(obj: object) {
+  let newObjStr = JSON.stringify(obj);
+  return JSON.parse(newObjStr);
+}
+
+/**
+ * * 生成一个不重复的ID
+ * @param { Number } randomLength
+ */
+export const getUUID = (randomLength = 10) => {
+  return Number(
+    Math.random().toString().substr(2, randomLength) + Date.now()
+  ).toString(36);
+};
+
+export function dateFYMDHM(val: string) {
+  if (val) {
+    return (
+      val.substr(0, 4) +
+      "-" +
+      val.substr(4, 2) +
+      "-" +
+      val.substr(6, 2) +
+      " " +
+      val.substr(8, 2) +
+      ":" +
+      val.substr(10, 2)
+    );
+  }
+  return "";
+}
+
+/**
+ * 列表筛重
+ * @param list  列表
+ * @param key   筛选字段
+ * @returns {Set<any>[]|*|*[]}
+ */
+export function listFilterDuplicateData(list: any[], key: string) {
+  if (!list || list.length === 0) {
+    return list
+  }
+  if (!key) {
+    return Array.from(new Set(list))
+  }
+
+  const fdd = new Set(list.map(i => i[key]))
+
+  let retList: any[] = []
+  list.forEach(i => {
+    let param = i[key];
+    if (fdd.has(param)) {
+      retList.push(i);
+      fdd.delete(param)
+    }
+  })
+
+  return retList
+}
+
+export function getObjectSize(obj: object) {
+  let count = 0
+  for (let key in obj)
+    count++
+  return count
+}
+
+// a 数组是否包含 b 数组的值
+export function isHasByOtherArray(a = [], b = []) {
+  for (let i in a) {
+    for (let j in b) {
+      if (a[i] == b[j]) {
+        return true
+      }
+    }
+  }
+  return false
+}

+ 86 - 0
src/utils/string.ts

@@ -0,0 +1,86 @@
+import {isArray} from "@/utils/validate";
+
+/**
+ * 根据模板生成字符串
+ * @param data      数据
+ * @param template  模板
+ * @returns 字符串
+ */
+export function formatStringByTemplate(data: any, template: string) {
+  let result = template
+  if (data) {
+    if (isArray(data)) {
+      if (data.length === 0) {
+        return result;
+      }
+      // 数组
+      return formatStringByList(data, result);
+    } else {
+      return replaceText(result, data);
+    }
+  }
+  return result;
+}
+
+/**
+ * [[{{姓名}}任{{职务}},负责{{工作职责}}]]
+ * @param data
+ * @param template
+ * @returns {*}
+ */
+function formatStringByList(list: any[], template: string) {
+  let result = template
+  const regex = /\[\[.*?\]\]/g
+  let templates = result.match(regex)
+
+  const keys = Object.keys(list[0]);
+  const templatess: any[] = []
+  templates?.forEach(t => {
+    let temp = t.substring(2, t.length - 2);
+    let temps: any[] = []
+
+    list.forEach(d => temps.push(replaceText(temp, d)))
+
+    let ts = temps.join(';');
+    if (ts.length > 0 && !ts.endsWith('。')) {
+      ts = ts + '。'
+    }
+    templatess.push(ts)
+  })
+  templates?.forEach((ts, index) => {
+    result = result.replace(ts, templatess[index])
+  })
+
+  return result;
+}
+
+
+function replaceText2(template: string, data: { [key: string]: any }) {
+  const keys = Object.keys(data);
+  keys.forEach(key => {
+    template = template.replace(new RegExp('{{' + key + '}}', 'g'), data[key] || '');
+  });
+  return template;
+}
+
+function replaceText(template: string, data: any) {
+  // 正则表达式,用于匹配{{key}},包括可能存在的空格
+  const regex = /{{\s*([^}]+?)\s*}}/g;
+
+  // 递归函数,用于处理嵌套对象
+  function replaceValue(key: string) {
+    // 分割键名,以处理嵌套对象
+    const keys = key.split('.');
+    let value = data;
+    for (const k of keys) {
+      // 如果值为undefined或null,则停止查找
+      if (value === undefined || value === null) break;
+      value = value[k];
+    }
+    // 如果找到的值是undefined或null,则替换为空字符串
+    return value === undefined || value === null ? '' : value;
+  }
+
+  // 使用正则表达式替换模板中的所有匹配项
+  return template.replace(regex, (match, key) => replaceValue(key));
+}

+ 3 - 3
src/utils/useCurrentInstance.ts

@@ -1,8 +1,8 @@
-import type { ComponentInternalInstance } from '@vue/runtime-core'
-import { getCurrentInstance } from 'vue'
+import type {ComponentInternalInstance} from '@vue/runtime-core'
+import {getCurrentInstance} from 'vue'
 
 
 export default function useCurrentInstance() {
 export default function useCurrentInstance() {
-  const { appContext } = getCurrentInstance() as ComponentInternalInstance
+  const {appContext} = getCurrentInstance() as ComponentInternalInstance
   const proxy = appContext?.config.globalProperties
   const proxy = appContext?.config.globalProperties
   return {
   return {
     proxy,
     proxy,

+ 30 - 0
src/views/Home.vue

@@ -0,0 +1,30 @@
+<script lang="ts" setup>
+import RightFrame from "@/components/RightFrame.vue";
+import Card01 from "@/components/card/Card01.vue";
+</script>
+
+<template>
+  <right-frame use-left-sidebar="">
+    <template #leftModule>
+      <card01 custom-class="" title="ceshi">
+        <div>aaaaa</div>
+        <p>aaaaa</p>
+        <p>aaaaa</p>
+        <p>aaaaa</p>
+        <div>aaaaa</div>
+      </card01>
+      <card01 custom-class="" title="ceshi">
+        <div>aaaaa</div>
+        <p>aaaaa</p>
+        <p>aaaaa</p>
+        <p>aaaaa</p>
+        <div>aaaaa</div>
+      </card01>
+    </template>
+    <template #rightModule>
+      <card01>
+        aaaaa
+      </card01>
+    </template>
+  </right-frame>
+</template>

+ 0 - 14
src/views/HomeView.vue

@@ -1,14 +0,0 @@
-<script lang="ts" setup>
-import RightFrame from "@/components/RightFrame.vue";
-</script>
-
-<template>
-  <right-frame use-left-sidebar="">
-    <template #leftModule>
-      aaaa
-    </template>
-    <template #rightModule>
-      bbbb
-    </template>
-  </right-frame>
-</template>

+ 0 - 9
src/views/index.vue

@@ -1,9 +0,0 @@
-<script setup lang="ts">
-import TheWelcome from '../components/TheWelcome.vue'
-</script>
-
-<template>
-  <main>
-    <TheWelcome />
-  </main>
-</template>

+ 1 - 0
tsconfig.json

@@ -12,6 +12,7 @@
     }
     }
   ],
   ],
   "compilerOptions": {
   "compilerOptions": {
+    "strictNullChecks": false,
     "module": "NodeNext",
     "module": "NodeNext",
     "baseUrl": "src",
     "baseUrl": "src",
     "paths": {
     "paths": {

Неке датотеке нису приказане због велике количине промена