hejianhao
2026-02-04 ce17f4640cf2ca71528bc5e6b2943b82d2e23cd9
feat: 优化地图展示及界面布局

- 为地图容器和头部添加固定定位,防止滚动时内容重叠
- 在自适应缩放配置中忽略地图容器,避免缩放冲突
- 添加地图区域边界渲染功能,根据项目位置显示省、市、区级边界
- 优化代码格式,统一空格和换行风格
- 添加地图插件加载和窗口大小变化时的地图重绘处理
3个文件已修改
419 ■■■■■ 已修改文件
screen/src/App.vue 1 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
screen/src/view/header/index.vue 2 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
screen/src/view/home/index.vue 416 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
screen/src/App.vue
@@ -19,6 +19,7 @@
      dw: 1920,
      renderDom: "#app",
      resize: true,
      ignore: ["#container"],
    });
  },
  created() {
screen/src/view/header/index.vue
@@ -1,5 +1,5 @@
<template>
  <div class="txt-center h--85 w100 relative imgBg">
  <div class="txt-center h--85 w100 relative imgBg fixed z-10">
    <div
      @click="toHome"
      class="flex a-center c-fff absolute adress1 cursor-pointer"
screen/src/view/home/index.vue
@@ -3,7 +3,15 @@
    <HeaderBox />
    <div
      id="container"
      style="width: 1920px; height: 995px; background-color: #001635"
      style="
        position: fixed;
        top: 0;
        left: 0;
        z-index: 0;
        width: 100vw;
        height: 100vh;
        background-color: #001635;
      "
    ></div>
    <!-- 类型切换tab -->
    <div
@@ -604,7 +612,10 @@
              >
                <el-carousel-item v-for="item in workListData" :key="item.id">
                  <div @click="toWorkDetail(item)" class="cursor-pointer">
                    <img :src="VUE_APP_IMAGE_BASE_URL + item.img.split(',')[0]" class="w100 h--290" />
                    <img
                      :src="VUE_APP_IMAGE_BASE_URL + item.img.split(',')[0]"
                      class="w100 h--290"
                    />
                    <div
                      class="fs--18 lh--24 SHSBold py--10 pl--20 pr--14 c-fff"
                    >
@@ -800,13 +811,15 @@
      searchTabs: ["本周", "本月", "本季度", "本年"],
      activeTabs: "本周",
      workListData: [],
      VUE_APP_IMAGE_BASE_URL: process.env.VUE_APP_IMAGE_BASE_URL + "/project/download/",
      VUE_APP_IMAGE_BASE_URL:
        process.env.VUE_APP_IMAGE_BASE_URL + "/project/download/",
      projectList: [],
      tooltipVisible: false,
      tooltipPos: { left: 0, top: 0 },
      tooltipData: {},
      tooltipLoading: false,
      tooltipError: false,
      boundaryPolygons: [],
    };
  },
  watch: {
@@ -822,7 +835,7 @@
        }
        this.$nextTick(()=>{
          this.getScreenData();
        })
        });
      },
      immediate: true,
    },
@@ -834,6 +847,11 @@
    this.mapPromise = AMapLoader.load({
      key: "232929db1cb31f26d42b3645a7456154",
      version: "2.0",
      plugins: ["AMap.DistrictSearch", "AMap.Geocoder"],
      AMapUI: {
        version: "1.1",
        plugins: ["geo/DistrictExplorer"],
      },
    });
    this.getStoryData();
    getWorkPresentationList({
@@ -853,6 +871,10 @@
  beforeDestroy() {
    localStorage.setItem("activeTab", this.activeTab);
    this.stopAutoScroll();
    if (this._mapResizeHandler) {
      window.removeEventListener("resize", this._mapResizeHandler);
      this._mapResizeHandler = null;
    }
  },
  methods: {
    handleTabClick(tab) {
@@ -935,18 +957,20 @@
    },
    toPorjectList() {
      const query = {
        projectCategory: ["ongoing", "fixed"][this.activeTab - 1]
        projectCategory: ["ongoing", "fixed"][this.activeTab - 1],
      };
      // 如果有选择日期范围,则传递开始和结束时间
      if (this.dateRange && this.dateRange.length === 2) {
        query.startTime = moment(this.dateRange[0]).format("YYYY-MM-DD HH:mm:ss");
        query.startTime = moment(this.dateRange[0]).format(
          "YYYY-MM-DD HH:mm:ss"
        );
        query.endTime = moment(this.dateRange[1]).format("YYYY-MM-DD HH:mm:ss");
      }
      this.$router.push({
        path: '/projectList',
        query: query
        path: "/projectList",
        query: query,
      });
    },
    toEquipmentList() {
@@ -954,29 +978,33 @@
      // 如果有选择日期范围,则传递开始和结束时间
      if (this.dateRange && this.dateRange.length === 2) {
        query.startTime = moment(this.dateRange[0]).format("YYYY-MM-DD HH:mm:ss");
        query.startTime = moment(this.dateRange[0]).format(
          "YYYY-MM-DD HH:mm:ss"
        );
        query.endTime = moment(this.dateRange[1]).format("YYYY-MM-DD HH:mm:ss");
      }
      this.$router.push({
        path: '/equipmentList',
        query: query
        path: "/equipmentList",
        query: query,
      });
    },
    toHiddenDanger() {
      const query = {
        projectCategory: ["ongoing", "fixed"][this.activeTab - 1]
        projectCategory: ["ongoing", "fixed"][this.activeTab - 1],
      };
      // 如果有选择日期范围,则传递开始和结束时间
      if (this.dateRange && this.dateRange.length === 2) {
        query.startTime = moment(this.dateRange[0]).format("YYYY-MM-DD HH:mm:ss");
        query.startTime = moment(this.dateRange[0]).format(
          "YYYY-MM-DD HH:mm:ss"
        );
        query.endTime = moment(this.dateRange[1]).format("YYYY-MM-DD HH:mm:ss");
      }
      this.$router.push({
        path: '/hiddenDanger',
        query: query
        path: "/hiddenDanger",
        query: query,
      });
    },
    // 切换活动索引
@@ -1121,6 +1149,11 @@
        this.mapPromise = AMapLoader.load({
          key: "232929db1cb31f26d42b3645a7456154",
          version: "2.0",
          plugins: ["AMap.DistrictSearch", "AMap.Geocoder"],
          AMapUI: {
            version: "1.1",
            plugins: ["geo/DistrictExplorer"],
          },
        });
      }
      this.mapPromise
@@ -1130,11 +1163,19 @@
            center: [104.065617, 30.656952], // 初始化地图中心点
            mapStyle: "amap://styles/darkblue",
          });
          this.mapView.resize();
          this._mapResizeHandler = () => {
            if (this.mapView) {
              this.mapView.resize();
            }
          };
          window.addEventListener("resize", this._mapResizeHandler);
          // this.infoWindow = new AMap.InfoWindow({
          //   isCustom: true,
          //   offset: new AMap.Pixel(0, -30),
          // });
          this.initCategoryMarkers(AMap);
          this.renderAllBoundaries();
        })
        .catch((e) => {
          console.error(e); //加载错误提示
@@ -1322,6 +1363,351 @@
    hideTooltip() {
      this.tooltipVisible = false;
    },
    clearBoundary() {
      if (this.boundaryPolygons.length) {
        this.mapView.remove(this.boundaryPolygons);
        this.boundaryPolygons = [];
      }
    },
    async renderAllBoundaries() {
      this.clearBoundary();
      if (!this.mapView || !this.screenData.projectMapPoints) return;
      // 确保 AMapUI 已加载
      if (!window.AMapUI) {
        console.warn("AMapUI not loaded");
        return;
      }
      const provinceSet = new Set();
      const districtSet = new Set();
      const citySet = new Set(); // 显式收集有数据的城市
      const pointsToGeocode = [];
      // 1. 收集数据
      this.screenData.projectMapPoints.forEach((point) => {
        let hasData = false;
        if (point.addressInfo) {
          try {
            const info = JSON.parse(point.addressInfo);
            if (info.province) {
              provinceSet.add(info.province);
              hasData = true;
            }
            if (info.district) {
              districtSet.add(info.district);
              hasData = true;
            }
            // 如果只有市没有区,或者需要单独渲染市
            if (info.city) {
              citySet.add(info.city);
              hasData = true;
            }
          } catch (e) {
            // ignore
          }
        }
        if (!hasData) {
          pointsToGeocode.push([point.longitude, point.latitude]);
        }
      });
      // 2. 逆地理编码补充缺失数据
      if (pointsToGeocode.length > 0) {
        const geocoder = new AMap.Geocoder();
        const batchSize = 20;
        const batches = [];
        for (let i = 0; i < pointsToGeocode.length; i += batchSize) {
          batches.push(pointsToGeocode.slice(i, i + batchSize));
        }
        await Promise.all(
          batches.map((batch) => {
            return new Promise((resolve) => {
              geocoder.getAddress(batch, (status, result) => {
                if (status === "complete" && result.regeocodes) {
                  result.regeocodes.forEach((geo) => {
                    if (geo.addressComponent) {
                      const { district, city, province } = geo.addressComponent;
                      if (province) provinceSet.add(province);
                      if (city) citySet.add(city);
                      if (district) districtSet.add(district);
                    }
                  });
                }
                resolve();
              });
            });
          })
        );
      }
      // 初始化 DistrictExplorer
      let districtExplorer = null;
      try {
        districtExplorer = new AMapUI.DistrictExplorer({
          map: this.mapView,
          eventSupport: false, // 我们自己绘制,不需要它的事件
        });
      } catch (e) {
        console.error("Init DistrictExplorer failed", e);
        return;
      }
      // 辅助函数:根据名称查找 adcode
      const resolveAdcode = (name, level) => {
        return new Promise((resolve) => {
          const search = new AMap.DistrictSearch({
            subdistrict: 1, // 获取下一级以便后续逻辑使用(如省查市)
            extensions: "base", // 这里只需要 adcode,不需要详细边界
            level: level,
          });
          search.search(name, (status, result) => {
            if (status === "complete" && result.districtList.length > 0) {
              resolve(result.districtList[0]);
            } else {
              resolve(null);
            }
          });
        });
      };
      // 辅助函数:使用 DistrictExplorer 加载并绘制区域
      const renderAreaNode = (
        adcode,
        style,
        level,
        isActive = false,
        isHoverable = false
      ) => {
        return new Promise((resolve) => {
          districtExplorer.loadAreaNode(adcode, (error, areaNode) => {
            if (error || !areaNode) {
              resolve();
              return;
            }
            // 获取父级区域的 GeoJSON (ParentFeature)
            // 这包含了正确的孔洞信息
            const feature = areaNode.getParentFeature();
            if (!feature || !feature.geometry) {
              resolve();
              return;
            }
            const geometry = feature.geometry;
            const types =
              geometry.type === "MultiPolygon"
                ? geometry.coordinates
                : [geometry.coordinates];
            // 遍历所有多边形(处理 MultiPolygon)
            types.forEach((coords) => {
              // GeoJSON 的 coordinates 是 [lng, lat] 数组
              // AMap.Polygon 接受 path,格式一致
              // 注意:GeoJSON Polygon 是 [OuterRing, Hole1, Hole2...]
              // AMap.Polygon 同样支持这种格式
              const polygon = new AMap.Polygon({
                path: coords, // 直接使用 GeoJSON 的坐标数组
                strokeColor: style.strokeColor,
                strokeWeight: 2,
                fillColor: style.fillColor || "transparent",
                fillOpacity:
                  style.fillOpacity !== undefined ? style.fillOpacity : 0.2,
                zIndex: style.zIndex,
                bubble: true,
                cursor: "default",
                extData: { level, isActive },
              });
              // 添加 hover 事件
              if (isHoverable) {
                polygon.on("mouseover", () => {
                  polygon.setOptions({
                    fillColor: "#00ffd2",
                    fillOpacity: 0.2,
                  });
                });
                polygon.on("mouseout", () => {
                  polygon.setOptions({
                    fillColor: "transparent",
                    fillOpacity: 0,
                  });
                });
              }
              this.mapView.add(polygon);
              this.boundaryPolygons.push(polygon);
            });
            resolve();
          });
        });
      };
      // 3. 处理省份:渲染省界,并收集其下属的所有城市 adcode
      const cityCodesFromProvinces = new Set();
      const cityAdcodeToNameMap = new Map(); // 辅助Map,用于判断是否在citySet中
      const provincePromises = Array.from(provinceSet).map(
        async (provinceName) => {
          const result = await resolveAdcode(provinceName, "province");
          if (result) {
            // 渲染省 - 无填充
            renderAreaNode(
              result.adcode,
              {
                strokeColor: "#5470C6",
                fillColor: "",
                fillOpacity: 0,
                zIndex: 8,
              },
              "province",
              false
            );
            // 收集该省下的所有市(用于渲染市界)
            if (result.districtList && result.districtList.length) {
              result.districtList.forEach((city) => {
                cityCodesFromProvinces.add(city.adcode);
                cityAdcodeToNameMap.set(city.adcode, city.name);
              });
            }
          }
        }
      );
      await Promise.all(provincePromises);
      // 4. 渲染市
      // 合并 "省份下属的市" 和 "直接出现在数据中的市"
      const allCityAdcodes = new Set([...cityCodesFromProvinces]);
      // 处理直接出现的市名称
      const cityResolutionPromises = Array.from(citySet).map(
        async (cityName) => {
          const result = await resolveAdcode(cityName, "city");
          if (result) {
            allCityAdcodes.add(result.adcode);
            cityAdcodeToNameMap.set(result.adcode, cityName);
          }
        }
      );
      await Promise.all(cityResolutionPromises);
      const cityRenderPromises = Array.from(allCityAdcodes).map((adcode) => {
        // 渲染所有市 - 无填充 (仅轮廓)
        // 注意:isActive设为false,因为我们只希望聚焦在具体的区或标点上
        return renderAreaNode(
          adcode,
          { strokeColor: "#FAC858", fillColor: "", fillOpacity: 0, zIndex: 9 },
          "city",
          false
        );
      });
      await Promise.all(cityRenderPromises);
      // 5. 渲染区
      // 策略:
      // 1. 遍历 citySet (有数据的市),渲染其下属的所有区。
      //    - 如果区在 districtSet 中 (有数据):高亮填充 (isActive=true)
      //    - 如果区不在 districtSet 中 (无数据):透明 + Hover效果 (isActive=false)
      // 2. 遍历 districtSet,处理可能漏掉的区 (比如没有对应的city信息的孤立区数据)
      const renderedDistrictAdcodes = new Set();
      const cityDistrictPromises = Array.from(citySet).map(async (cityName) => {
        const result = await resolveAdcode(cityName, "city");
        if (result && result.districtList) {
          const subDistricts = result.districtList;
          const subPromises = subDistricts.map((sub) => {
            renderedDistrictAdcodes.add(sub.adcode);
            // 检查该区是否有数据
            // 模糊匹配:districtSet中的名字可能不带"区"字
            let hasData = false;
            if (sub.name) {
              for (let d of districtSet) {
                if (sub.name.includes(d) || d.includes(sub.name)) {
                  hasData = true;
                  break;
                }
              }
            }
            if (hasData) {
              // 有数据:默认高亮,无Hover特效
              return renderAreaNode(
                sub.adcode,
                { strokeColor: "#00ffd2", fillColor: "#00ffd2", zIndex: 10 },
                "district",
                true,
                false
              );
            } else {
              // 无数据:默认透明,有Hover特效
              return renderAreaNode(
                sub.adcode,
                {
                  strokeColor: "#00ffd2",
                  fillColor: "",
                  fillOpacity: 0,
                  zIndex: 10,
                },
                "district",
                false,
                true
              );
            }
          });
          return Promise.all(subPromises);
        }
      });
      await Promise.all(cityDistrictPromises);
      // 处理可能遗漏的区 (districtSet 中有,但没在上面的循环中渲染过)
      const orphanDistrictPromises = Array.from(districtSet).map(
        async (districtName) => {
          // 先检查是否已经渲染过 (需要先resolve adcode才能精确匹配,但这里先用名字简单过滤一下,减少请求)
          // 由于上面是根据adcode记录的,这里还是得resolve一下比较稳妥
          const result = await resolveAdcode(districtName, "district");
          if (result && !renderedDistrictAdcodes.has(result.adcode)) {
            // 既然在districtSet里,肯定是有数据的
            return renderAreaNode(
              result.adcode,
              { strokeColor: "#00ffd2", fillColor: "#00ffd2", zIndex: 10 },
              "district",
              true,
              false
            );
          }
        }
      );
      await Promise.all(orphanDistrictPromises);
      // 6. 自动调整视野
      const targetOverlays = [];
      if (this.markers && this.markers.length > 0) {
        targetOverlays.push(...this.markers);
      }
      if (this.boundaryPolygons.length > 0) {
        // 只保留 isActive 为 true 的区域用于缩放 (即有数据的区)
        const activePolygons = this.boundaryPolygons.filter((p) => {
          const data = p.getExtData();
          return data && data.isActive;
        });
        targetOverlays.push(...activePolygons);
      }
      if (targetOverlays.length > 0) {
        this.mapView.setFitView(targetOverlays, false, [60, 60, 60, 60]);
      }
    },
  },
};
</script>