【VUE】整合腾讯地图、自定义区域边界、村委名称及资产统计(放大显示资产点位)

pc端整合腾讯地图、自定义区域边界、村委名称及资产统计(放大显示资产点位)

整合步骤总览

步骤 内容 涉及文件
步骤一 准备村委资产统计接口 api/fsgdzc/asset/index.js(file:///e:/project/pc-ui/src/api/fsgdzc/asset/index.js)
步骤二 大屏父页面引入地图组件并传入村委数据 bigScreen/index.vue(file:///e:/project/pc-ui/src/views/fsgdzc/bigScreen/index.vue)(仅 tmap 相关部分)
步骤三 实现腾讯地图组件:初始化、自定义区域边界、村委标签、资产点位、缩放联动 tmap/index.vue(file:///e:/project/pc-ui/src/views/fsgdzc/bigScreen/tmap/index.vue)
备选方案 GeoJSON 矢量边界(当前未接入,仅保留说明与配套文件) tmap/geoJson.js(file:///e:/project/pc-ui/src/views/fsgdzc/bigScreen/tmap/geoJson.js)

整体数据流:父页面调用村委总览接口拿到各村委统计 → 传入地图组件 → 地图组件初始化腾讯地图、叠加自定义区域边界、渲染村委名称标签 → 加载资产数据 → 按缩放级别在「村委统计」与「资产点位」之间切换。

步骤一:准备村委资产统计接口

  1. 本步骤只需用到该文件中的两个接口:村委总览 villageOverview(区域资产统计,按类型查询各村委的资产统计数量)、资产列表 listAsset(供地图展示资产点位使用)。
  2. 接口返回每个村委的资产总数、未出租数、出租数、不可出租数,供父页面合并进村委数据。
  3. 类型参数约定:土地对应 2、物业对应 1。

相关接口代码:api/fsgdzc/asset/index.js(仅保留用到的接口)

javascript 复制代码
import request from '@/utils/request'

// 村委总览(区域资产统计):按类型查询各村委的资产统计数量
export function villageOverview(type){
  return request({
    url: '/asset/info/count/villageOverview?type='+type,
    method: 'get'
  })
}

// 资产列表(地图资产点位展示使用)
export function listAsset(data){
  return request({
    url: '/mpwx/asset/list',
    method: 'post',
    data: data
  })
}

步骤二:大屏父页面引入地图组件并传入村委数据

  1. 父页面 data 中维护 mapData 数组(15 个村委/单位),每项含村委标识、名称、资产总数、未出租/出租/不可出租数量,以及村委标注坐标(先纬度后经度)。
  2. 模板中渲染地图组件,传入地图中心点、村委数据 mapData 与当前 Tab 索引。
  3. 顶部「土地 / 物业」Tab 的索引作为地图组件的 key,切换时会重建地图组件;切换同时会重新调用村委总览接口刷新统计。
  4. selectVillageOverview 用村委名称前两个字与接口返回的村委名匹配,把资产总数拼接到名称后(形如「村委名(总数)」),并回填各项数量;每次匹配前先用正则去掉上次拼接的括号数量,避免括号越拼越多。

代码:bigScreen/index.vue(仅 tmap 相关部分,其余与地图无关的代码未收录)

vue 复制代码
<!-- 模板中的地图展示区域(原文件第 111-115 行) -->
        <div class="vis-area-tabs">
          <div class="vis-area-tabs-item" :class="tabsArrActive2 == index ? 'active':''" v-for="(item, index) in tabsArr2" @click="tabsArrActive2 = index;selectVillageOverview()">{{ item }}</div>
        </div>
        <!--        <div class="vis-map">-->
        <tmap style="position: relative;z-index: 10;" :lnglat="centerLngLat" :mapData="mapData" :tabsArrActive2="tabsArrActive2" :key="tabsArrActive2"></tmap>

// ──────────── 引入地图组件与村委总览接口(原文件第 368-382 行)────────────
import {contractStatis} from "@/api/fsgdzc/contract/contract";
import {countAsset, countAreaByAssetType, villageIncome, propertyUsageAnalysis, villageOverview} from "@/api/fsgdzc/asset/index";
import {countByCustomerType, customerContractCount, overdueCount, overdueDayCount, lateFeeCount} from "@/api/fsgdzc/customers/customers";
import {payAnalysis, rentAnalysis, yearlyRentAnalysis} from "@/api/fsgdzc/ledgers/index";
import tmap from "./tmap/index.vue";

export default {
  components: {
    tmap,
    mixinChart,
    Echart3DPie,
    EchartPie,
    EchartPieBig,
    DateRangeDropdown
  },

// ──────────── Tab 定义与当前索引(原文件第 479-481 行)────────────
      tabsArr2: ['土地', '物业'],
      tabsArrActive2: 0,
      tabsArrActive22: 1,

// ──────────── 村委坐标与统计容器 mapData(原文件第 514-636 行)────────────
      mapData: [
        {
          key: 'sjcw',
          name: '三江村委',
          totalCount: 0,
          xzCount: 0,
          czCount: 0,
          bkczCount: 0,
          center: [23.231835, 113.026861]
        }, {
          key: 'sxcw',
          name: '三溪村委',
          totalCount: 0,
          xzCount: 0,
          czCount: 0,
          bkczCount: 0,
          center: [23.231187, 112.987291]
        }, {
          key: 'hbcw',
          name: '华㘵村委',
          totalCount: 0,
          xzCount: 0,
          czCount: 0,
          bkczCount: 0,
          center: [23.267152, 113.040755]
        }, {
          key: 'lpcw',
          name: '乐平村委',
          totalCount: 0,
          xzCount: 0,
          czCount: 0,
          bkczCount: 0,
          center: [23.260015, 113.018526]
        }, {
          key: 'ytcw',
          name: '源潭村委',
          totalCount: 0,
          xzCount: 0,
          czCount: 0,
          bkczCount: 0,
          center: [23.26953, 112.979652]
        }, {
          key: 'xqcw',
          name: '新旗村委',
          totalCount: 0,
          xzCount: 0,
          czCount: 0,
          bkczCount: 0,
          center: [23.281252, 113.019258]
        }, {
          key: 'fhcw',
          name: '范湖村委',
          totalCount: 0,
          xzCount: 0,
          czCount: 0,
          bkczCount: 0,
          center: [23.299372, 112.998074]
        }, {
          key: 'nlcw',
          name: '南联村委',
          totalCount: 0,
          xzCount: 0,
          czCount: 0,
          bkczCount: 0,
          center: [23.330682, 113.000471]
        }, {
          key: 'dgcw',
          name: '大岗村委',
          totalCount: 0,
          xzCount: 0,
          czCount: 0,
          bkczCount: 0,
          center: [23.304003, 112.938363]
        }, {
          key: 'zscw',
          name: '竹山村委',
          totalCount: 0,
          xzCount: 0,
          czCount: 0,
          bkczCount: 0,
          center: [23.283047, 112.95597]
        }, {
          key: 'ndcw',
          name: '念德村委',
          totalCount: 0,
          xzCount: 0,
          czCount: 0,
          bkczCount: 0,
          center: [23.272531, 112.932292]
        }, {
          key: 'nfljs',
          name: '南丰劳教所',
          totalCount: 0,
          xzCount: 0,
          czCount: 0,
          bkczCount: 0,
          center: [23.291387, 112.906008]
        }, {
          key: 'bacw',
          name: '保安村委',
          totalCount: 0,
          xzCount: 0,
          czCount: 0,
          bkczCount: 0,
          center: [23.276728, 112.893139]
        }, {
          key: 'hgcw',
          name: '湖岗村委',
          totalCount: 0,
          xzCount: 0,
          czCount: 0,
          bkczCount: 0,
          center: [23.258563, 112.869062]
        }, {
          key: 'htcw',
          name: '黄塘村委',
          totalCount: 0,
          xzCount: 0,
          czCount: 0,
          bkczCount: 0,
          center: [23.309776, 112.883705]
        }
      ],

// ──────────── 地图中心点(原文件第 677 行)────────────
      centerLngLat: '113.02,23.26',

// ──────────── 初始化时请求村委总览(原文件第 762-763 行)────────────
      // 村委会资产总览
      this.selectVillageOverview();

// ──────────── 村委资产总览 selectVillageOverview(原文件第 989-1009 行)────────────
    //村委会资产总览(地图汇总)
    selectVillageOverview() {
      let type = this.tabsArrActive2 === 0 ? '2' : '1';
      villageOverview(type).then(response => {
        if (response.code !== 200) return
        let data = response.data || [];
        this.mapData.forEach(item => {
          // 恢复 item.name 的初始值(去掉括号和计数部分)
          const originalName = item.name.replace(/\(\d+\)$/, '');
          data.forEach(item1 => {
            if (item1.villageName && item1.villageName.indexOf(originalName.substring(0, 2)) > -1) {
              item.name = originalName + '(' + item1.totalCount + ')';
              item.totalCount = item1.totalCount;
              item.xzCount = item1.xzCount;
              item.czCount = item1.yczCount;
              item.bkczCount = item1.bkczCount;
            }
          })
        })
      });
    },

步骤三:实现腾讯地图组件

该组件是整个整合的核心,一次完成以下内容:

  1. 初始化腾讯地图:通过腾讯地图 SDK 在容器中创建地图实例,设置中心点、初始缩放 12 与地图样式,并挂载点位图层、资产散点图层(圆点)、资产圈地图层(多边形)以及自定义信息弹窗。
  2. 加载资产数据:调用资产列表接口,按当前 Tab 决定资产类型(土地对应 2、物业对应 1),资产坐标字段为「经度,纬度」格式。
  3. 渲染村委名称与统计:用多标签图层按村委坐标渲染名称标签(名称后附带资产总数),点击标签弹出该村委的资产统计弹窗(资产总数、未出租、出租、不可出租),选中标签高亮,弹窗内「查看详情」向上抛出 village-detail 事件。
  4. 按缩放级别切换展示:以缩放级别 13 为界,缩小时隐藏资产散点、突出村委统计;放大时显示资产散点与圈地。缩放监听做约 300 毫秒防抖。
  5. 辅助控件:显示控制(点位 / 圈地开关)、图例(按使用状态过滤并定位中心点)、地图类型切换(矢量 / 卫星)。

重点:自定义区域边界

  • 生效方式为图片贴地覆盖,对应组件中的 drawPicture 方法:用腾讯地图的贴地图层(ImageGroundLayer)把一张乐平镇边界透明图(lepingzhen.png,位于静态资源 fsgdzc 目录下)按一个地理矩形铺到地图上,形成自定义边界线。
  • 矩形的两个角点是核心:左下(西南)角为纬度 23.1918、经度 112.799999,右上(东北)角为纬度 23.3766、经度 113.0879。注意腾讯地图经纬度构造顺序是先纬度、后经度。
  • 铺图后把缩放设为 12;每次调用会先移除旧图层再重建,避免叠加。
  • 调参:边界偏移或大小不符时,只需调整上述两个角的经纬度(整体偏大就缩小跨度,偏小就放大跨度,位置偏移就同步平移);换图改图片路径;深浅改透明度(当前 0.7)。
  • 组件中另保留了基于 GeoJSON 的矢量绘制方法 drawLine(配合 geoJson.js 封装),但已在初始化处注释掉,当前未接入,详见文末「备选方案」。

完整代码文件:tmap/index.vue

vue 复制代码
<template>
  <div class="app-container home">
    <!-- 腾讯地图容器 -->
    <div id="container">
      <!-- 地图类型切换控件 -->
      <div id="map-type-control"></div>
      <!-- 图例控件 -->
      <div id="map-legend"></div>
      <!-- 全屏按钮 -->
      <div class="fullscreen-btn" @click="toggleFullscreen">
        <i class="el-icon-full-screen"></i>
      </div>
    </div>
  </div>
</template>

<script>
import maps from "@/views/components/tmap/index";
import mapConfig from "@/views/components/tmap/config";
import {listAsset} from "@/api/fsgdzc/asset/index";
import { TMapGeoJSONLayer } from './geoJson.js'  // 根据实际路径调整
import lpGeoJson from "./leping.json";
import screenfull from 'screenfull';

export default {
  name: "tmap",
  props: {
    lnglat: {
      type: String,
      default: null
    },
    mapData: {
      type: Array,
      default: () => {
        return []
      }
    },
    tabsArrActive2: {
      type: Number,
      default: 0
    }
  },
  data() {
    return {
      version: "1.0.0",
      marker: null,
      searchKeyword: null,
      lnglatVal: null,
      centerVal: mapConfig.center.join(','),
      map: null,
      search: null,
      geocoder: null,
      imageGroundLayer: null,
      villageLabelLayer: null,
      infoWindow: null,
      infoWindowDom: null,
      villageData: [],
      currentVillage: null,
      labelClicked: false,
      assetCircleLayer: null,
      zoomTimer: null,
      currentZoom: 12,
      assetCircles: [],
      isLoadingAssets: false,
      currentBounds: null,
      assetCircleClicked: false,
      mapKey: mapConfig.htMap,
      lastClickTime: 0,
      clickDebounceTimer: null,
      hoverTimer: null,
      // 添加标志位,记录资产数据是否已加载
      hasLoadedAssets: false,
      // 存储资产数据
      assetsData: [],
      // 存储事件处理函数引用,以便正确移除
      eventHandlers: {
        outsideClick: null,
        mapClick: null,
        zoomChanged: null,
        boundsChanged: null,
        assetCircleClick: null,
        assetCircleMouseOver: null,
        assetCircleMouseOut: null,
        villageLabelClick: null,
        infoWindowClick: null
      },
      // 显示控制:点位和圈地
      displayOptions: {
        point: true,
        polygon: true
      }
    };
  },
  mounted() {
    //初始化地图
    this.initAMap();
  },
  watch: {
    mapData: {
      deep: true,
      immediate: true,
      handler(val) {
        if (val && val.length > 0) {
          setTimeout(() => {
            this.addAreaMarker();
          }, 1000);
        }
      }
    }
  },
  methods: {
    // 切换全屏
    toggleFullscreen() {
      const container = document.getElementById('container');
      if (screenfull.isEnabled) {
        if (screenfull.isFullscreen) {
          screenfull.exit();
          window.location.reload();
        } else {
          screenfull.request(container);
        }
      }
    },
    initAMap() {
      var that = this;
      var container = document.getElementById("container");
      maps.init(this.mapKey, () => {
        var myLatlng = new TMap.LatLng(this.centerVal.split(',')[0], this.centerVal.split(',')[1]);
        var myOptions = {
          zoom: 12,
          center: myLatlng,
          mapStyleId: 'style1'
        };
        that.map = new TMap.Map(
          container,
          myOptions
        );
        that.map.setCursorStyle("pointer");
        that.search = new TMap.service.Search({pageSize: 5});
        that.geocoder = new TMap.service.Geocoder();

        // 存储当前地图类型
        that.currentMapType = 'vector';

        // 监听地图缩放事件
        that.map.on('zoomend', function() {
          // 重新显示多边形
          that.showAssetPolygons();
          //输出当前中心点
          console.log('当前中心点:', that.map.getCenter());
        });

        // 添加地图类型切换控件
        that.addMapTypeControl();

        // 添加图例控件
        that.initLegend();

        // 添加显示控制控件
        that.addDisplayControl();

        // 初始化marker图层
        that.marker = new TMap.MultiMarker({
          id: 'marker-layer',
          map: that.map,
          styles: {
            "marker": new TMap.MarkerStyle({
              "width": 25,
              "height": 35,
              "anchor": {x: 12, y: 35},
              "color": "#ff0000"
            })
          },
        });

        // 创建资产散点图层
        that.assetCircleLayer = new TMap.MultiCircle({
          id: 'asset-circle-layer',
          map: that.map,
          zIndex: 100,
          styles: {
            "asset-circle-default": new TMap.CircleStyle({
              "color": "rgba(24, 144, 255, 0.6)",
              "width": 2,
              "borderColor": "#1890ff",
              "fillColor": "rgba(24, 144, 255, 0.3)"
            }),
            "asset-circle-idle": new TMap.CircleStyle({
              "color": "rgba(82, 196, 26, 0.6)",
              "width": 2,
              "borderColor": "#52c41a",
              "fillColor": "rgba(82, 196, 26, 0.3)"
            }),
            "asset-circle-rent": new TMap.CircleStyle({
              "color": "rgba(250, 140, 22, 0.6)",
              "width": 2,
              "borderColor": "#fa8c16",
              "fillColor": "rgba(250, 140, 22, 0.3)"
            }),
            "asset-circle-unrentable": new TMap.CircleStyle({
              "color": "rgba(245, 34, 45, 0.6)",
              "width": 2,
              "borderColor": "#f5222d",
              "fillColor": "rgba(245, 34, 45, 0.3)"
            }),
            "asset-circle-hover": new TMap.CircleStyle({
              "color": "rgba(114, 46, 209, 0.8)",
              "width": 3,
              "borderColor": "#722ed1",
              "fillColor": "rgba(114, 46, 209, 0.4)"
            }),
            "asset-circle-selected": new TMap.CircleStyle({
              "color": "rgba(245, 34, 45, 0.8)",
              "width": 3,
              "borderColor": "#f5222d",
              "fillColor": "rgba(245, 34, 45, 0.4)"
            })
          },
        });

        // 创建多边形图层
        that.polygonLayer = new TMap.MultiPolygon({
          id: 'polygon-layer',
          map: that.map,
          zIndex: 50,
          styles: {
            "polygon-default": new TMap.PolygonStyle({
              "borderColor": "rgba(51, 102, 255, 0.3)",
              "fillColor": "transparent",
              "borderWidth": 4
            }),
            "polygon-idle": new TMap.PolygonStyle({
              "borderColor": "rgba(82, 196, 26, 0.3)",
              "fillColor": "transparent",
              "borderWidth": 4
            }),
            "polygon-rent": new TMap.PolygonStyle({
              "borderColor": "rgba(250, 140, 22, 0.3)",
              "fillColor": "transparent",
              "borderWidth": 4
            }),
            "polygon-unrentable": new TMap.PolygonStyle({
              "borderColor": "rgba(245, 34, 45, 0.3)",
              "fillColor": "transparent",
              "borderWidth": 4
            })
          }
        });

        // 创建信息窗口DOM
        that.createInfoWindow();

        // 监听地图缩放事件
        that.eventHandlers.zoomChanged = () => {
          this.handleZoomChange();
        };
        that.map.on("zoom_changed", that.eventHandlers.zoomChanged);

        // 监听地图移动事件
        that.eventHandlers.boundsChanged = () => {
          this.currentBounds = this.map.getBounds();
        };
        that.map.on("bounds_changed", that.eventHandlers.boundsChanged);

        // 地图点击事件
        that.eventHandlers.mapClick = (e) => {
          const now = Date.now();
          if (now - this.lastClickTime < 300) {
            return;
          }
          this.lastClickTime = now;

          if (this.clickDebounceTimer) {
            clearTimeout(this.clickDebounceTimer);
          }

          // this.clickDebounceTimer = setTimeout(() => {
          //   this.labelClicked = false;
          //   this.assetCircleClicked = false;
          //   //点击地图关闭弹窗和重置点位样式
          //   this.closeInfoWindow();
          //   this.resetAssetCircleStyle();
          // }, 50);
        };
        that.map.on("click", that.eventHandlers.mapClick);

        // 初始化乐平镇政府所在点位
        // this.initMarker();
        // 真实区域划线
        // this.drawLine();
        // 把区域划线图层覆盖到地图上面
        this.drawPicture();
        // 添加村委的名称显示(mapData里面有各个村委的坐标),并且绑定村委点击时的弹窗事件
        if (this.mapData && this.mapData.length > 0) {
          this.addAreaMarker();
        }

        // 初始获取地图边界
        this.currentBounds = this.map.getBounds();
        this.currentZoom = this.map.getZoom();

        // 初始化时加载一次资产数据
        this.loadAssetData();
      });
    },

    // 处理地图缩放事件
    handleZoomChange() {
      if (this.zoomTimer) {
        clearTimeout(this.zoomTimer);
      }
      this.zoomTimer = setTimeout(() => {
        const newZoom = this.map.getZoom();
        this.currentZoom = newZoom;

        // 更新散点大小
        // this.updateCircleSizeByZoom(newZoom);

        // 根据缩放级别显示或隐藏资产散点
        if (newZoom >= 13) {
          // 缩放级别>=13,显示资产散点
          this.showAssetCircles();
        } else {
          // 缩放级别<13,隐藏资产散点
          this.hideAssetCircles();
        }
      }, 300);
    },

    // 根据缩放级别更新资产散点大小
    updateCircleSizeByZoom(zoom) {
      if (!this.assetCircleLayer || !this.assetsData || this.assetsData.length === 0) {
        return;
      }

      let radius = 15;
      if (zoom >= 18) {
        radius = 25;
      } else if (zoom >= 17) {
        radius = 22;
      } else if (zoom >= 16) {
        radius = 20;
      } else if (zoom >= 15) {
        radius = 18;
      } else if (zoom >= 14) {
        radius = 18;
      } else if (zoom >= 13) {
        radius = 18;
      } else {
        radius = 15;
      }

      // 只更新当前显示的散点大小
      if (this.currentZoom >= 13) {
        const geometries = this.assetCircleLayer.geometries;
        const updatedGeometries = geometries.map(geo => {
          return {
            ...geo,
            radius: radius
          };
        });
        this.assetCircleLayer.setGeometries(updatedGeometries);
      }
    },

    // 修改:查询资产数据,支持按使用状态筛选
    async loadAssetData(usageStatus) {
      // 如果已经加载过资产数据且没有指定使用状态,直接返回
      if (this.hasLoadedAssets && usageStatus === undefined) return;

      if (this.isLoadingAssets) return;
      const bounds = this.map.getBounds();
      if (!bounds) return;

      try {
        this.isLoadingAssets = true;

        const zoom = this.map.getZoom();

        let assetData = {}
        let paramData = {
          "pageNum": 1,
          "pageSize": 10000,
          "assetType": [this.tabsArrActive2===0?'2':'1'],
        };

        // 如果指定了使用状态且不为空字符串,添加到参数中
        if (usageStatus !== undefined && usageStatus !== '') {
          paramData.usageStatus = usageStatus;
        }

        await listAsset(paramData).then(response => {
          if (response.code !== 200) return
          assetData = response;
        });

        if (assetData.code === 200 && assetData.rows && assetData.rows.length > 0) {
          // 保存资产数据
          this.assetsData = assetData.rows;

          // 只有在未指定使用状态时才标记为已加载
          if (usageStatus === undefined) {
            this.hasLoadedAssets = true;
          }

          // 处理多边形回显
          this.showAssetPolygons();

          // 如果当前缩放级别>=13,则显示资产散点
          if (this.currentZoom >= 13) {
            this.showAssetCircles();
          }
        }
      } catch (error) {
        console.error('加载资产数据失败:', error);
      } finally {
        this.isLoadingAssets = false;
      }
    },

    // 显示资产多边形
    showAssetPolygons() {
      // 如果未勾选圈地,清空图层并返回
      if (!this.displayOptions.polygon) {
        this.polygonLayer.setGeometries([]);
        return;
      }

      // 获取当前缩放级别
      var zoom = this.map.getZoom();

      // 调试:查看当前缩放级别和地图类型
      console.log('当前缩放级别:', zoom);
      console.log('当前地图类型:', this.currentMapType);

      // 只在缩放级别大于13
      if (zoom < 13) {
        // 调试:不满足显示条件
        console.log('不满足显示条件,清空多边形图层');
        // 清空多边形图层
        //this.polygonLayer.setGeometries([]);
        return;
      }

      // 调试:满足显示条件
      console.log('满足显示条件,准备显示多边形');

      if (!this.assetsData || this.assetsData.length === 0) {
        console.log('资产数据为空,返回');
        return;
      }

      // 调试:资产数据长度
      console.log('资产数据长度:', this.assetsData.length);

      // 准备所有多边形的几何数据
      var geometries = [];

      // 遍历资产数据
      for (let i = 0; i < this.assetsData.length; i++) {
        let asset = this.assetsData[i];
        if (asset.polygonCoords) {
          try {
            // 解析 JSON 字符串为对象
            let polygonCoordsArray = typeof asset.polygonCoords === 'string' ? JSON.parse(asset.polygonCoords) : asset.polygonCoords;

            if (polygonCoordsArray && polygonCoordsArray.length > 0) {
              // 调试:找到多边形数据
              console.log('找到多边形数据:', polygonCoordsArray);

              // 遍历多边形数组
              for (let j = 0; j < polygonCoordsArray.length; j++) {
                let polygonItem = polygonCoordsArray[j];
                if (polygonItem && polygonItem.length > 0) {
                  // 转换坐标格式为 TMap.LatLng
                  var latLngs = polygonItem.map(coord => {
                    return new TMap.LatLng(coord.lat, coord.lng);
                  });

                  // 根据资产使用状态设置多边形样式
                  let styleId = 'polygon-default';

                  if (asset.usageStatus === '1') {
                    // 空闲 - 绿色
                    styleId = 'polygon-idle';
                    console.log('设置为绿色');
                  } else if (asset.usageStatus === '2') {
                    // 出租 - 黄色
                    styleId = 'polygon-rent';
                    console.log('设置为黄色');
                  } else if (asset.usageStatus === '3') {
                    // 不可出租 - 红色
                    styleId = 'polygon-unrentable';
                    console.log('设置为红色');
                  } else {
                    console.log('使用默认蓝色');
                  }

                  // 添加多边形几何数据
                  geometries.push({
                    id: 'asset-polygon-' + asset.id + '-' + j,
                    styleId: styleId,
                    paths: latLngs
                  });
                }
              }
            }
          } catch (error) {
            console.error('解析 polygonCoords 失败:', error);
          }
        }
      }

      // 调试:多边形几何数据长度
      // console.log('多边形几何数据长度:', geometries.length);

      // 一次性设置所有多边形
      if (geometries.length > 0) {
        console.log('设置多边形几何数据');
        this.polygonLayer.setGeometries(geometries);
      } else {
        // 清空多边形图层
        console.log('清空多边形图层');
        this.polygonLayer.setGeometries([]);
      }
    },

    // 显示资产散点
    showAssetCircles() {
      // 如果未勾选点位,清空图层并返回
      if (!this.displayOptions.point) {
        this.clearAssetCircles();
        return;
      }

      if (!this.assetsData || this.assetsData.length === 0) return;

      // 清除现有的资产散点
      this.clearAssetCircles();

      // 获取当前缩放级别以确定散点大小
      const zoom = this.map.getZoom();
      let radius = 30;
      if (zoom >= 18) radius = 10;
      else if (zoom >= 17) radius = 15
      else if (zoom >= 16) radius = 20;
      else if (zoom >= 15) radius = 30;
      else if (zoom >= 14) radius = 30;

      this.assetCircles = [];

      // 为每个资产添加散点
      this.assetsData.forEach(asset => {
        if (asset.locationCoordinates) {
          const coords = asset.locationCoordinates.split(',');
          if (coords.length === 2) {
            const lng = parseFloat(coords[0]);
            const lat = parseFloat(coords[1]);

            let styleId = 'asset-circle-default';
            if (asset.usageStatus === '1') {
              styleId = 'asset-circle-idle';
            } else if (asset.usageStatus === '2') {
              styleId = 'asset-circle-rent';
            } else if (asset.usageStatus === '3') {
              styleId = 'asset-circle-unrentable';
            }

            const circle = {
              id: `asset-${asset.id}`,
              styleId: styleId,
              center: new TMap.LatLng(lat, lng),
              radius: radius,
              properties: {
                assetId: asset.id,
                assetData: asset,
                originalStyleId: styleId,
                isSelected: false
              }
            };
            this.assetCircles.push(circle);
          }
        }
      });

      if (this.assetCircles.length > 0) {
        this.assetCircleLayer.add(this.assetCircles);
        this.bindAssetCircleEvents();
        console.log(`已显示 ${this.assetCircles.length} 个资产散点`);
      }
    },

    // 隐藏资产散点
    hideAssetCircles() {
      this.clearAssetCircles();
    },

    // 修改:绑定资产散点事件 - 点击显示弹窗,保留鼠标悬停样式变化
    bindAssetCircleEvents() {
      if (!this.assetCircleLayer) return;

      // 先移除旧的事件监听器
      if (this.eventHandlers.assetCircleClick) {
        this.assetCircleLayer.off('click', this.eventHandlers.assetCircleClick);
      }
      if (this.eventHandlers.assetCircleMouseOver) {
        this.assetCircleLayer.off('mouseover', this.eventHandlers.assetCircleMouseOver);
      }
      if (this.eventHandlers.assetCircleMouseOut) {
        this.assetCircleLayer.off('mouseout', this.eventHandlers.assetCircleMouseOut);
      }

      // 创建新的事件处理函数
      // 修改:点击资产散点显示弹窗
      this.eventHandlers.assetCircleClick = (evt) => {
        const now = Date.now();
        if (now - this.lastClickTime < 300) {
          return;
        }
        this.lastClickTime = now;

        this.assetCircleClicked = true;

        if (evt.originalEvent) {
          evt.originalEvent.stopPropagation();
        }

        const geometry = evt.geometry;
        if (geometry && geometry.properties) {
          const assetData = geometry.properties.assetData;
          const position = geometry.center;

          // 点击时显示信息窗口
          this.showAssetInfo(assetData, position);
          this.updateAssetCircleStyle(geometry.id);
        }

        setTimeout(() => {
          this.assetCircleClicked = false;
        }, 100);
      };

      // 保留鼠标悬停样式变化,但悬停时不显示弹窗
      this.eventHandlers.assetCircleMouseOver = (evt) => {
        const geometry = evt.geometry;
        if (geometry && !geometry.properties.isSelected) {
          // 悬停时只改变样式,不显示弹窗
          this.setCircleStyle(geometry.id, 'asset-circle-hover');
        }
      };

      this.eventHandlers.assetCircleMouseOut = (evt) => {
        const geometry = evt.geometry;
        if (geometry && !geometry.properties.isSelected) {
          if (this.hoverTimer) clearTimeout(this.hoverTimer);
          const originalStyleId = geometry.properties.originalStyleId || 'asset-circle-default';
          this.setCircleStyle(geometry.id, originalStyleId);
        }
      };

      // 绑定事件
      this.assetCircleLayer.on('click', this.eventHandlers.assetCircleClick);
      this.assetCircleLayer.on('mouseover', this.eventHandlers.assetCircleMouseOver);
      this.assetCircleLayer.on('mouseout', this.eventHandlers.assetCircleMouseOut);
    },

    // 更新资产散点样式
    updateAssetCircleStyle(circleId) {
      this.resetAssetCircleStyle();
      this.setCircleStyle(circleId, 'asset-circle-selected', true);
    },

    // 设置资产散点样式
    setCircleStyle(circleId, styleId, isSelected = false) {
      try {
        const geometries = this.assetCircleLayer.geometries;
        const updatedGeometries = geometries.map(geo => {
          if (geo.id === circleId) {
            return {
              ...geo,
              styleId: styleId,
              properties: {
                ...geo.properties,
                isSelected: isSelected
              }
            };
          }
          return geo;
        });
        this.assetCircleLayer.setGeometries(updatedGeometries);
      } catch (error) {
        console.error('更新散点样式失败:', error);
      }
    },

    // 重置资产散点样式
    resetAssetCircleStyle() {
      try {
        const geometries = this.assetCircleLayer.geometries;
        const updatedGeometries = geometries.map(geo => {
          const originalStyleId = geo.properties?.originalStyleId || 'asset-circle-default';
          return {
            ...geo,
            styleId: originalStyleId,
            properties: {
              ...geo.properties,
              isSelected: false
            }
          };
        });
        this.assetCircleLayer.setGeometries(updatedGeometries);
      } catch (error) {
        console.error('重置散点样式失败:', error);
      }
    },

    // 显示资产信息窗口
    showAssetInfo(assetData, position) {
      this.closeInfoWindow();
      this.infoWindowDom.innerHTML = this.buildAssetInfoContent(assetData);
      this.infoWindowDom.style.display = 'block';
      this.positionInfoWindow(position);
      this.bindAssetInfoWindowEvents(assetData);
      this.infoWindowDom.style.zIndex = '10000';
    },

    // 构建资产信息窗口内容
    buildAssetInfoContent(assetData) {
      let statusText = '';
      let statusColor = '';
      let statusClass = '';
      switch (assetData.usageStatus) {
        case '1':
          statusText = '未出租';
          statusColor = '#52c41a';
          statusClass = 'idle';
          break;
        case '2':
          statusText = '出租';
          statusColor = '#fa8c16';
          statusClass = 'rent';
          break;
        case '3':
          statusText = '不可出租';
          statusColor = '#f5222d';
          statusClass = 'unrentable';
          break;
        default:
          statusText = '未知';
          statusColor = '#666';
          statusClass = 'default';
      }

      const assetTypeMap = {
        '1': '房屋',
        '2': '土地',
        '3': '停车场',
        '4': '商铺',
        '5': '厂房'
      };

      const assetTypeText = assetTypeMap[assetData.assetType] || assetData.assetType || '未知类型';

      return `
        <div class="asset-info-window">
          <div class="asset-info-header">
            <h3 class="asset-name" data-asset-id="${assetData.id}">
              ${assetData.assetName || '未命名资产'}
            </h3>
            <button class="close-btn" title="关闭">×</button>
          </div>

          <div class="asset-info-body">
            <div class="info-row">
              <span class="info-label">资产类型:</span>
              <span class="info-value asset-type">${assetTypeText}</span>
            </div>

            <div class="info-row">
              <span class="info-label">资产地址:</span>
              <div class="info-value asset-address">${assetData.assetAddress || '暂无地址信息'}</div>
            </div>

            <div class="info-row">
              <span class="info-label">资产面积:</span>
              <span class="info-value asset-area">${assetData.area || 0} ${assetData.unit || ''}</span>
            </div>

            <div class="info-row">
              <span class="info-label">使用状态:</span>
              <span class="info-value asset-status ${statusClass}" style="color: ${statusColor}">${statusText}</span>
            </div>

            ${assetData.unitPrice ? `
            <div class="info-row">
              <span class="info-label">单价:</span>
              <span class="info-value asset-price">${assetData.unitPrice} 元/${assetData.unit || '单位'}</span>
            </div>
            ` : ''}

            ${assetData.description ? `
            <div class="info-row">
              <span class="info-label">说明:</span>
              <div class="info-value asset-desc">${assetData.description}</div>
            </div>
            ` : ''}
          </div>
        </div>
      `;
    },

    // 修复:正确绑定信息窗口事件
    bindAssetInfoWindowEvents(assetData) {
      // 先移除旧的事件监听
      if (this.eventHandlers.infoWindowClick) {
        this.infoWindowDom.removeEventListener('click', this.eventHandlers.infoWindowClick);
      }

      // 创建新的事件处理函数
      this.eventHandlers.infoWindowClick = (e) => {
        e.stopPropagation();
        const target = e.target;

        if (target.classList.contains('close-btn')) {
          this.closeInfoWindow();
          this.resetAssetCircleStyle();
          return;
        }

        // 只保留点击资产名称跳转到详情页
        if (target.classList.contains('asset-name') ||
          target.closest('.asset-name')) {
          this.openAssetDetailPage(assetData);
        }
      };

      // 绑定事件
      this.infoWindowDom.addEventListener('click', this.eventHandlers.infoWindowClick);

      // 绑定全局点击事件
      this.eventHandlers.outsideClick = this.handleOutsideClick.bind(this);
      document.addEventListener('click', this.eventHandlers.outsideClick, true);
    },

    // 修复:确保这是绑定到实例的方法
    handleOutsideClick(e) {
      if (this.infoWindowDom && this.infoWindowDom.style.display === 'block') {
        const isClickInside = this.infoWindowDom.contains(e.target);
        if (!isClickInside) {
          this.closeInfoWindow();
          this.resetAssetCircleStyle();
        }
      }
    },

    openAssetDetailPage(assetData) {
      console.log('打开资产详情:', assetData);
      window.open(`/fsgdzc/asset/details?pkId=${assetData.id}`, '_blank');
      this.closeInfoWindow();
      this.resetAssetCircleStyle();
    },

    clearAssetCircles() {
      if (this.assetCircleLayer) {
        try {
          this.assetCircleLayer.setGeometries([]);
          this.assetCircles = [];
          console.log('已清除资产散点');
        } catch (error) {
          console.error('清除资产散点失败:', error);
        }
      }
    },

    createInfoWindow() {
      if (this.infoWindowDom && this.infoWindowDom.parentNode) {
        this.infoWindowDom.parentNode.removeChild(this.infoWindowDom);
      }

      this.infoWindowDom = document.createElement('div');
      this.infoWindowDom.className = 'village-info-window';
      this.infoWindowDom.style.cssText = `
        position: absolute;
        background: white;
        border: 1px solid #e8e8e8;
        border-radius: 8px;
        padding: 0;
        min-width: 280px;
        max-width: 350px;
        box-shadow: 0 6px 16px 0 rgba(0,0,0,.2);
        z-index: 10000;
        display: none;
        font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'PingFang SC', 'Hiragino Sans GB', 'Microsoft YaHei', 'Helvetica Neue', Helvetica, Arial, sans-serif;
        pointer-events: auto;
        overflow: hidden;
      `;

      const container = document.getElementById('container');
      if (container) {
        container.appendChild(this.infoWindowDom);
      }
    },

    positionInfoWindow(position) {
      if (!this.map || !this.infoWindowDom) return;

      try {
        const pixel = this.map.projectToContainer(position);
        const container = document.getElementById('container');

        if (pixel && container) {
          const x = pixel.getX();
          const y = pixel.getY();

          this.infoWindowDom.style.visibility = 'hidden';
          this.infoWindowDom.style.display = 'block';

          const infoWidth = this.infoWindowDom.offsetWidth;
          const infoHeight = this.infoWindowDom.offsetHeight;
          const containerRect = container.getBoundingClientRect();

          let left = x - infoWidth / 2;
          let top = y - infoHeight - 20;

          if (left < 10) left = 10;
          if (left + infoWidth > containerRect.width - 10) {
            left = containerRect.width - infoWidth - 10;
          }
          if (top < 10) {
            top = y + 30;
          }

          this.infoWindowDom.style.left = left + 'px';
          this.infoWindowDom.style.top = top + 'px';
          this.infoWindowDom.style.visibility = 'visible';
        }
      } catch (error) {
        console.error('定位信息窗口失败:', error);
        this.infoWindowDom.style.left = '50%';
        this.infoWindowDom.style.top = '50%';
        this.infoWindowDom.style.transform = 'translate(-50%, -50%)';
        this.infoWindowDom.style.visibility = 'visible';
      }
    },

    // 修复:村委标签事件绑定
    addAreaMarker() {
      if (this.villageLabelLayer) {
        this.villageLabelLayer.setMap(null);
        this.villageLabelLayer = null;
      }

      if (!this.mapData || this.mapData.length === 0) {
        return;
      }

      this.villageData = [...this.mapData];

      this.villageLabelLayer = new TMap.MultiLabel({
        id: 'village-label-layer',
        map: this.map,
        zIndex: 50,
        styles: {
          "village-label": new TMap.LabelStyle({
            "color": "#1890ff",
            "size": 14,
            "offset": {x: 0, y: 0},
            "angle": 0,
            "alignment": "center",
            "verticalAlignment": "middle",
            "backgroundColor": "rgba(255, 255, 255, 0.9)",
            "borderColor": "#1890ff",
            "borderWidth": 1,
            "borderRadius": 4,
            "padding": 6,
            "fontWeight": "bold"
          }),
          "village-label-active": new TMap.LabelStyle({
            "color": "#ffffff",
            "size": 14,
            "offset": {x: 0, y: 0},
            "angle": 0,
            "alignment": "center",
            "verticalAlignment": "middle",
            "backgroundColor": "#1890ff",
            "borderColor": "#1890ff",
            "borderWidth": 1,
            "borderRadius": 4,
            "padding": 6,
            "fontWeight": "bold"
          })
        },
        geometries: []
      });

      this.mapData.forEach((village, index) => {
        if (village.center && village.center.length === 2) {
          const position = new TMap.LatLng(village.center[0], village.center[1]);

          this.villageLabelLayer.add({
            id: `village-label-${village.key}`,
            styleId: 'village-label',
            position: position,
            content: village.name,
            properties: {
              villageKey: village.key,
              villageName: village.name,
              villageData: village
            }
          });
        }
      });
      // 绑定村委点击弹窗事件
      this.bindVillageLabelEvents();
    },
    // 绑定村委点击弹窗事件
    bindVillageLabelEvents() {
      if (!this.villageLabelLayer) return;

      // 移除旧的事件监听
      if (this.eventHandlers.villageLabelClick) {
        this.villageLabelLayer.off('click', this.eventHandlers.villageLabelClick);
      }

      // 创建新的事件处理函数
      this.eventHandlers.villageLabelClick = (evt) => {
        const now = Date.now();
        if (now - this.lastClickTime < 300) {
          return;
        }
        this.lastClickTime = now;

        if (evt.originalEvent) {
          evt.originalEvent.stopPropagation();
        }

        this.labelClicked = true;

        const geometry = evt.geometry;
        if (geometry && geometry.properties) {
          const villageData = geometry.properties.villageData;
          const position = geometry.position;
          //展示村委统计数据
          this.showVillageInfo(villageData, position);
        }

        setTimeout(() => {
          this.labelClicked = false;
        }, 100);
      };

      // 绑定事件
      this.villageLabelLayer.on('click', this.eventHandlers.villageLabelClick);
    },

    //展示村委统计数据
    showVillageInfo(villageData, position) {
      this.closeInfoWindow();
      this.currentVillage = villageData;
      this.updateLabelStyle(villageData.key);
      // 构建信息窗口内容
      this.infoWindowDom.innerHTML = this.buildInfoWindowContent(villageData);
      this.infoWindowDom.style.display = 'block';
      // 定位信息窗口
      this.positionInfoWindow(position);
      // 绑定村委点击事件
      this.bindVillageInfoWindowEvents(villageData);
    },

    // 修复:村委信息窗口事件绑定
    bindVillageInfoWindowEvents(villageData) {
      // 先移除旧的事件监听
      if (this.eventHandlers.infoWindowClick) {
        this.infoWindowDom.removeEventListener('click', this.eventHandlers.infoWindowClick);
      }

      // 创建新的事件处理函数
      this.eventHandlers.infoWindowClick = (e) => {
        e.stopPropagation();
        const target = e.target;

        if (target.classList.contains('close-btn') ||
          target.closest('.close-btn')) {
          this.closeInfoWindow();
        }

        // 预留查看村委详情事件
        if (target.classList.contains('detail-btn') ||
          target.closest('.detail-btn')) {
          this.$emit('village-detail', villageData);
          this.closeInfoWindow();
        }
      };

      // 绑定事件
      this.infoWindowDom.addEventListener('click', this.eventHandlers.infoWindowClick);

      // 绑定全局点击事件
      this.eventHandlers.outsideClick = this.handleOutsideClick.bind(this);
      document.addEventListener('click', this.eventHandlers.outsideClick, true);
    },

    closeInfoWindow() {
      if (this.infoWindowDom) {
        this.infoWindowDom.style.display = 'none';
        this.infoWindowDom.innerHTML = '';
      }
      this.resetLabelStyle();

      // 移除事件监听
      if (this.eventHandlers.infoWindowClick) {
        this.infoWindowDom.removeEventListener('click', this.eventHandlers.infoWindowClick);
      }
      if (this.eventHandlers.outsideClick) {
        document.removeEventListener('click', this.eventHandlers.outsideClick, true);
      }
    },

    // 构建信息窗口内容
    buildInfoWindowContent(villageData) {
      return `
        <div class="village-info-content">
          <div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 12px; padding: 16px 16px 12px; border-bottom: 1px solid #f0f0f0;">
            <h3 style="margin: 0; color: #1890ff; font-size: 16px; font-weight: 600;">${villageData.name}</h3>
            <button class="close-btn" style="
              background: none;
              border: none;
              font-size: 20px;
              cursor: pointer;
              color: #999;
              line-height: 1;
              padding: 0;
              width: 24px;
              height: 24px;
              display: flex;
              align-items: center;
              justify-content: center;
              transition: color 0.3s;
            " title="关闭">×</button>
          </div>
          <div class="statistics" style="font-size: 14px; padding: 0 16px 16px;">
            <div style="display: flex; justify-content: space-between; margin-bottom: 10px; padding: 8px; background: #f9f9f9; border-radius: 4px;">
              <span style="color: #666;">资产总数:</span>
              <span style="color: #1890ff; font-weight: bold; font-size: 16px;">${villageData.totalCount || 0}</span>
            </div>
            <div style="display: flex; justify-content: space-between; margin-bottom: 10px; padding: 8px; background: #f0f9ff; border-radius: 4px;">
              <span style="color: #666;">未出租数量:</span>
              <span style="color: #52c41a; font-weight: bold;">${villageData.xzCount || 0}</span>
            </div>
            <div style="display: flex; justify-content: space-between; margin-bottom: 10px; padding: 8px; background: #fff7e6; border-radius: 4px;">
              <span style="color: #666;">出租数量:</span>
              <span style="color: #fa8c16; font-weight: bold;">${villageData.czCount || 0}</span>
            </div>
            <div style="display: flex; justify-content: space-between; margin-bottom: 15px; padding: 8px; background: #fff1f0; border-radius: 4px;">
              <span style="color: #666;">不可出租数量:</span>
              <span style="color: #f5222d; font-weight: bold;">${villageData.bkczCount || 0}</span>
            </div>
          </div>
        </div>
      `;
    },

    //点击村委时改变村委文字颜色
    updateLabelStyle(villageKey) {
      this.resetLabelStyle();

      const geometries = this.villageLabelLayer.geometries;
      const updatedGeometries = geometries.map(geo => {
        if (geo.id === `village-label-${villageKey}`) {
          return {
            ...geo,
            styleId: 'village-label-active'
          };
        }
        return geo;
      });

      this.villageLabelLayer.setGeometries(updatedGeometries);
    },

    resetLabelStyle() {
      const geometries = this.villageLabelLayer.geometries;
      const updatedGeometries = geometries.map(geo => {
        return {
          ...geo,
          styleId: 'village-label'
        };
      });

      this.villageLabelLayer.setGeometries(updatedGeometries);
    },

    // 初始化乐平镇政府点位
    initMarker() {
      this.lnglatVal = this.lnglat;
      if (this.lnglatVal) {
        this.addMarker(this.lnglatVal.split(',')[1], this.lnglatVal.split(',')[0])
      }
    },

    // 添加默认点位
    addMarker(lat, lng) {
      this.clearMarker();
      this.lnglatVal = lng + ',' + lat;
      this.marker.add({
        "styleId": 'marker',
        position: new TMap.LatLng(lat, lng)
      });
    },

    clearMarker() {
      var marker = this.marker;
      if (marker) {
        marker.setGeometries([])
      }
    },

    // 真实区域划线
    drawLine() {
      // 使用示例
      const tmapRegionLayer = new TMapGeoJSONLayer({
        map: this.map, // 你的腾讯地图实例
        reference: 'WGS84',
        data: lpGeoJson,
        polygonStyle: function (properties) {
          return {
            color: '#2c7fff30', // 填充颜色带透明度
            borderColor: '#2c7fff',
            borderWidth: 4
          };
        },
        polylineStyle: function (properties) {
          return {
            color: 'blue',
            width: 2
          };
        }
      });
      // 渲染图层
      tmapRegionLayer.render();
    },

    // 区域划线图层覆盖(调整下面的坐标可以使划线更加准确,目前是大体上与真实的边界线重合)
    drawPicture() {
      if (this.imageGroundLayer) {
        this.imageGroundLayer.setMap(null);
        this.imageGroundLayer = null;
      }

      var imageSW = new TMap.LatLng(23.1918, 112.799999);
      var imageNE = new TMap.LatLng(23.3766, 113.0879);
      var imageLatLngBounds = new TMap.LatLngBounds(imageSW, imageNE);

      this.imageGroundLayer = new TMap.ImageGroundLayer({
        map: this.map,
        bounds: imageLatLngBounds,
        src: '/fsgdzc/lepingzhen.png',
        opacity: 0.7,
        visible: true
      });

      this.map.setZoom(12);
    },

    // 添加地图类型切换控件
    addMapTypeControl() {
      var that = this;
      var controlDiv = document.getElementById('map-type-control');

      if (!controlDiv) {
        console.error('地图类型切换控件容器不存在');
        return;
      }

      // 清空容器
      controlDiv.innerHTML = '';

      // 创建矢量地图按钮
      var vectorBtn = document.createElement('button');
      vectorBtn.innerHTML = '矢量地图';
      vectorBtn.className = 'map-type-btn active';
      vectorBtn.style.position = 'relative';
      vectorBtn.style.zIndex = '10001';
      vectorBtn.onclick = function() {
        // 切换到矢量地图
        that.map.setBaseMap({
          type: 'vector',
          features: ['base', 'building', 'label']
        });
        vectorBtn.className = 'map-type-btn active';
        satelliteBtn.className = 'map-type-btn';

        // 更新当前地图类型
        that.currentMapType = 'vector';

        // 重新显示多边形
        that.showAssetPolygons();
      };
      controlDiv.appendChild(vectorBtn);

      // 创建卫星地图按钮
      var satelliteBtn = document.createElement('button');
      satelliteBtn.innerHTML = '卫星地图';
      satelliteBtn.className = 'map-type-btn';
      satelliteBtn.style.position = 'relative';
      satelliteBtn.style.zIndex = '10001';
      satelliteBtn.onclick = function() {
        // 切换到卫星地图
        that.map.setBaseMap({
          type: 'satellite'
        });
        satelliteBtn.className = 'map-type-btn active';
        vectorBtn.className = 'map-type-btn';

        // 更新当前地图类型
        that.currentMapType = 'satellite';

        // 重新显示多边形
        that.showAssetPolygons();
      };
      controlDiv.appendChild(satelliteBtn);
    },

    // 添加显示控制控件
    addDisplayControl() {
      var that = this;

      // 创建控件容器
      var controlDiv = document.createElement('div');
      controlDiv.id = 'display-control';
      controlDiv.className = 'display-control';
      document.getElementById('container').appendChild(controlDiv);

      // 创建标题
      var title = document.createElement('div');
      title.className = 'display-control-title';
      title.innerHTML = '显示控制';
      controlDiv.appendChild(title);

      // 创建点位复选框
      var pointItem = document.createElement('div');
      pointItem.className = 'display-control-item';

      var pointCheckbox = document.createElement('input');
      pointCheckbox.type = 'checkbox';
      pointCheckbox.id = 'point-checkbox';
      pointCheckbox.checked = that.displayOptions.point;
      pointCheckbox.onchange = function() {
        that.displayOptions.point = this.checked;
        if (that.currentZoom >= 13) {
          that.showAssetCircles();
        } else {
          that.clearAssetCircles();
        }
      };

      var pointLabel = document.createElement('label');
      pointLabel.htmlFor = 'point-checkbox';
      pointLabel.innerHTML = '点位';

      pointItem.appendChild(pointCheckbox);
      pointItem.appendChild(pointLabel);
      controlDiv.appendChild(pointItem);

      // 创建圈地复选框
      var polygonItem = document.createElement('div');
      polygonItem.className = 'display-control-item';

      var polygonCheckbox = document.createElement('input');
      polygonCheckbox.type = 'checkbox';
      polygonCheckbox.id = 'polygon-checkbox';
      polygonCheckbox.checked = that.displayOptions.polygon;
      polygonCheckbox.onchange = function() {
        that.displayOptions.polygon = this.checked;
        that.showAssetPolygons();
      };

      var polygonLabel = document.createElement('label');
      polygonLabel.htmlFor = 'polygon-checkbox';
      polygonLabel.innerHTML = '圈地';

      polygonItem.appendChild(polygonCheckbox);
      polygonItem.appendChild(polygonLabel);
      controlDiv.appendChild(polygonItem);
    },

    // 初始化图例
    initLegend() {
      var that = this;
      var legendDiv = document.getElementById('map-legend');

      if (!legendDiv) {
        console.error('图例控件容器不存在');
        return;
      }

      // 清空容器
      legendDiv.innerHTML = '';

      // 图例数据
      var legendData = [
        { usageStatus: '', name: '所有类型', color: '#ffffff' },
        { usageStatus: '1', name: '未出租', color: '#52c41a' },
        { usageStatus: '2', name: '出租', color: '#fa8c16' },
        { usageStatus: '3', name: '不可出租', color: '#f5222d' }
      ];

      // 创建图例标题
      var legendTitle = document.createElement('div');
      legendTitle.className = 'legend-title';
      // legendTitle.innerHTML = '资产类型';
      legendDiv.appendChild(legendTitle);

      // 创建图例项
      var legendItems = [];
      var firstLegendItem = null;
      legendData.forEach(function(item, index) {
        var legendItem = document.createElement('div');
        legendItem.className = 'legend-item';
        legendItem.dataset.usageStatus = item.usageStatus;

        // 创建颜色块
        var colorBlock = document.createElement('div');
        colorBlock.className = 'legend-color';
        colorBlock.style.backgroundColor = item.color;
        legendItem.appendChild(colorBlock);

        // 创建文字描述
        var legendText = document.createElement('span');
        legendText.className = 'legend-text';
        legendText.innerHTML = item.name;
        legendItem.appendChild(legendText);

        // 添加点击事件
        legendItem.onclick = function() {
          // 移除所有图例项的选中状态
          legendItems.forEach(function(li) {
            li.classList.remove('active');
          });

          // 为当前点击的图例项添加选中状态
          this.classList.add('active');

          // 调用 loadAssetData 方法,传递 usageStatus 参数
          that.loadAssetData(item.usageStatus);
          // 切换到地图中心点,层级为13
          that.map.setZoom(13);
          var centerLatlng = new TMap.LatLng(23.279922527036053, 112.98045154810086);
          that.map.setCenter(centerLatlng);
        };

        legendDiv.appendChild(legendItem);
        legendItems.push(legendItem);

        // 保存第一个图例项(所有类型)
        if (index === 0) {
          firstLegendItem = legendItem;
        }
      });

      // 默认选中所有类型
      if (firstLegendItem) {
        firstLegendItem.classList.add('active');
        // 加载所有类型的资产数据
        (async function() {
          await that.loadAssetData('');
          // 切换到地图中心点,层级为13
          that.map.setZoom(13);
          var centerLatlng = new TMap.LatLng(23.279922527036053, 112.98045154810086);
          that.map.setCenter(centerLatlng);
        })();
      }
    }
  },

  beforeDestroy() {
    // 清理所有定时器
    if (this.zoomTimer) clearTimeout(this.zoomTimer);
    if (this.clickDebounceTimer) clearTimeout(this.clickDebounceTimer);
    if (this.hoverTimer) clearTimeout(this.hoverTimer);

    // 清理DOM元素
    if (this.infoWindowDom && this.infoWindowDom.parentNode) {
      this.infoWindowDom.parentNode.removeChild(this.infoWindowDom);
    }

    // 移除地图事件监听
    if (this.map) {
      if (this.eventHandlers.mapClick) {
        this.map.off("click", this.eventHandlers.mapClick);
      }
      if (this.eventHandlers.zoomChanged) {
        this.map.off("zoom_changed", this.eventHandlers.zoomChanged);
      }
      if (this.eventHandlers.boundsChanged) {
        this.map.off("bounds_changed", this.eventHandlers.boundsChanged);
      }
    }

    // 移除资产散点事件监听
    if (this.assetCircleLayer) {
      if (this.eventHandlers.assetCircleClick) {
        this.assetCircleLayer.off('click', this.eventHandlers.assetCircleClick);
      }
      if (this.eventHandlers.assetCircleMouseOver) {
        this.assetCircleLayer.off('mouseover', this.eventHandlers.assetCircleMouseOver);
      }
      if (this.eventHandlers.assetCircleMouseOut) {
        this.assetCircleLayer.off('mouseout', this.eventHandlers.assetCircleMouseOut);
      }
    }

    // 移除村委标签事件监听
    if (this.villageLabelLayer && this.eventHandlers.villageLabelClick) {
      this.villageLabelLayer.off('click', this.eventHandlers.villageLabelClick);
    }

    // 移除全局事件监听
    if (this.eventHandlers.outsideClick && typeof this.eventHandlers.outsideClick === 'function') {
      document.removeEventListener('click', this.eventHandlers.outsideClick, true);
    }
  }
};
</script>

<style scoped lang="scss">
/* 样式保持不变,与之前相同 */
.home {
  blockquote {
    padding: 10px 20px;
    margin: 0 0 20px;
    font-size: 17.5px;
    border-left: 5px solid #eee;
  }

  hr {
    margin-top: 20px;
    margin-bottom: 20px;
    border: 0;
    border-top: 1px solid #eee;
  }

  .col-item {
    margin-bottom: 20px;
  }

  ul {
    padding: 0;
    margin: 0;
  }

  font-family: "open sans", "Helvetica Neue", Helvetica, Arial, sans-serif;
  font-size: 13px;
  color: #676a6c;
  overflow-x: hidden;

  ul {
    list-style-type: none;
  }

  h4 {
    margin-top: 0px;
  }

  h2 {
    margin-top: 10px;
    font-size: 26px;
    font-weight: 100;
  }

  p {
    margin-top: 10px;

    b {
      font-weight: 700;
    }
  }

  .update-log {
    ol {
      display: block;
      list-style-type: decimal;
      margin-block-start: 1em;
      margin-block-end: 1em;
      margin-inline-start: 0;
      margin-inline-end: 0;
      padding-inline-start: 40px;
    }
  }
}

#container {
  position: relative;
  width: 100%;
  height: 100%;
  background: #ffffff;
}

.search-box {
  z-index: 10000;
  width: 70%;
  left: 13%;
  top: 10px;
  height: 30px;
}

.search-box input {
  float: left;
  width: 59%;
  height: 100%;
  border: 1px solid #cedeee;
  border-radius: 5px;
  padding: 0 8px;
  outline: none;
}

.search-box button {
  float: left;
  width: 20%;
  height: 100%;
  border-radius: 5px;
  background: linear-gradient(#7ea4ff, #5c8cff);
  border: 1px solid #5b8cff;
  color: #fff;
  outline: none;
}

/* 信息窗口样式 */
::v-deep .village-info-window {
  top: 10px !important;
  position: absolute !important;
  background: white !important;
  border: 1px solid #e8e8e8 !important;
  border-radius: 8px !important;
  box-shadow: 0 6px 16px 0 rgba(0, 0, 0, .2) !important;
  z-index: 10000 !important;
  font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'PingFang SC', 'Hiragino Sans GB', 'Microsoft YaHei', 'Helvetica Neue', Helvetica, Arial, sans-serif !important;
  pointer-events: auto !important;
  overflow: hidden !important;
}

::v-deep .asset-info-window {
  width: 100%;
  top: 10px !important;
}

::v-deep .asset-info-header {
  display: flex !important;
  justify-content: space-between !important;
  align-items: center !important;
  margin-bottom: 12px !important;
  padding: 16px 16px 12px !important;
  border-bottom: 1px solid #f0f0f0 !important;
}

::v-deep .asset-info-header h3 {
  margin: 0 !important;
  color: #1890ff !important;
  font-size: 16px !important;
  font-weight: 600 !important;
  cursor: pointer !important;
  transition: color 0.3s !important;
  flex: 1 !important;
  overflow: hidden !important;
  text-overflow: ellipsis !important;
  white-space: nowrap !important;
}

::v-deep .asset-info-header h3:hover {
  color: #40a9ff !important;
}

::v-deep .asset-info-body {
  padding: 0 16px !important;
  font-size: 14px !important;
}

::v-deep .info-row {
  display: flex !important;
  margin-bottom: 10px !important;
  align-items: flex-start !important;
}

::v-deep .info-label {
  color: #666 !important;
  min-width: 70px !important;
  flex-shrink: 0 !important;
  line-height: 1.5 !important;
}

::v-deep .info-value {
  color: #333 !important;
  flex: 1 !important;
  line-height: 1.5 !important;
}

::v-deep .asset-type {
  color: #722ed1 !important;
  background: #f9f0ff !important;
  padding: 2px 8px !important;
  border-radius: 4px !important;
  font-size: 12px !important;
  font-weight: 500 !important;
  display: inline-block !important;
}

::v-deep .asset-address {
  background: #f9f9f9 !important;
  padding: 6px 8px !important;
  border-radius: 4px !important;
  margin-top: 2px !important;
}

::v-deep .asset-area {
  color: #fa8c16 !important;
  font-weight: bold !important;
}

::v-deep .asset-status {
  font-weight: bold !important;
}

::v-deep .asset-status.idle {
  color: #52c41a !important;
}

::v-deep .asset-status.rent {
  color: #fa8c16 !important;
}

::v-deep .asset-status.unrentable {
  color: #f5222d !important;
}

::v-deep .asset-price {
  color: #f5222d !important;
  font-weight: bold !important;
}

::v-deep .asset-desc {
  background: #f6ffed !important;
  border: 1px solid #b7eb8f !important;
  border-radius: 4px !important;
  padding: 8px !important;
  margin-top: 4px !important;
  font-size: 13px !important;
  line-height: 1.5 !important;
}

::v-deep .asset-info-footer {
  padding: 16px !important;
  text-align: center !important;
  border-top: 1px solid #f0f0f0 !important;
}

::v-deep .close-btn {
  background: none !important;
  border: none !important;
  font-size: 20px !important;
  cursor: pointer !important;
  color: #999 !important;
  line-height: 1 !important;
  padding: 0 !important;
  width: 24px !important;
  height: 24px !important;
  display: flex !important;
  align-items: center !important;
  justify-content: center !important;
  transition: color 0.3s !important;
  flex-shrink: 0 !important;
}

::v-deep .close-btn:hover {
  color: #333 !important;
}

::v-deep .asset-detail-btn {
  background: #1890ff !important;
  color: white !important;
  border: none !important;
  padding: 8px 24px !important;
  border-radius: 4px !important;
  cursor: pointer !important;
  font-size: 14px !important;
  font-weight: 500 !important;
  transition: all 0.3s !important;
  width: 100% !important;
}

::v-deep .asset-detail-btn:hover {
  background: #40a9ff !important;
  transform: translateY(-1px) !important;
  box-shadow: 0 4px 12px rgba(24, 144, 255, 0.3) !important;
}

::v-deep .village-info-content .statistics {
  font-size: 14px !important;
  margin-bottom: 12px !important;
}

/* 散点鼠标悬停样式 */
::v-deep .tmap-circle {
  cursor: pointer !important;
  transition: all 0.2s ease !important;
}

::v-deep .tmap-circle:hover {
  transform: scale(1.1) !important;
}

/* 全屏按钮样式 */
.fullscreen-btn {
  position: absolute;
  top: 190px;
  right: 30px;
  width: 40px;
  height: 40px;
  background: rgba(255, 255, 255, 0.9);
  border: 1px solid #ddd;
  border-radius: 4px;
  cursor: pointer;
  z-index: 10000;
  display: flex;
  align-items: center;
  justify-content: center;
  transition: all 0.3s;
  box-shadow: 0 2px 6px rgba(0, 0, 0, 0.1);

  &:hover {
    background: #40a9ff;
    border-color: #40a9ff;
    color: #fff;
    box-shadow: 0 4px 12px rgba(64, 169, 255, 0.3);
  }

  i {
    font-size: 18px;
    color: #333;
  }

  &:hover i {
    color: #fff;
  }
}
</style>

<style lang="scss">
/* 地图类型切换控件样式 */
#map-type-control {
  position: absolute;
  bottom: 10px;
  right: 10px;
  z-index: 10000;
  background: rgba(255, 255, 255, 0.9);
  border: 1px solid #ddd;
  border-radius: 4px;
  padding: 5px;
  box-shadow: 0 2px 6px rgba(0, 0, 0, 0.1);
}

#map-type-control .map-type-btn {
  display: block;
  width: 80px;
  padding: 6px 10px;
  margin: 2px 0;
  border: 1px solid #eee;
  background: white;
  border-radius: 3px;
  font-size: 12px;
  cursor: pointer;
  transition: all 0.3s;
  position: relative;
  z-index: 10001;
}

#map-type-control .map-type-btn:hover {
  background: #f5f5f5;
  border-color: #1890ff;
}

#map-type-control .map-type-btn.active {
  background: #1890ff !important;
  color: white !important;
  border-color: #1890ff !important;
}

/* 图例样式 */
#map-legend {
  top: 10px;
  left: 10px;
  position: absolute;
  z-index: 10000;
  background: #f5f5f5;
  border: none;
  border-radius: 4px;
  padding: 5px;
  font-size: 12px;
}

/* 显示控制控件样式 */
.display-control {
  position: absolute;
  bottom: 10px;
  right: 110px;
  z-index: 10001;
  background-color: rgba(255, 255, 255, 0.9);
  border-radius: 4px;
  padding: 3px 15px;
  box-shadow: 0 2px 6px rgba(0, 0, 0, 0.1);
}

.display-control-title {
  font-size: 14px;
  font-weight: bold;
  margin-bottom: 8px;
  color: #333;
}

.display-control-item {
  display: flex;
  align-items: center;
  margin-bottom: 6px;
  cursor: pointer;
  user-select: none;
}

.display-control-item:last-child {
  margin-bottom: 0;
}

.display-control-item input[type="checkbox"] {
  margin-right: 8px;
  width: 16px;
  height: 16px;
  cursor: pointer;
}

.display-control-item label {
  font-size: 14px;
  cursor: pointer;
  color: #333;
  margin: 0;
}

#map-legend .legend-title {
  font-weight: bold;
  margin-bottom: 8px;
  color: #333;
}

#map-legend .legend-item {
  display: flex;
  align-items: center;
  margin-bottom: 6px;
  cursor: pointer;
  padding: 4px 8px;
  border-radius: 3px;
  transition: all 0.3s;
}

#map-legend .legend-item:hover {
  background: #f5f5f5;
}

#map-legend .legend-item.active {
  background: #1890ff;
  color: white;
}

#map-legend .legend-item.active .legend-text {
  color: white;
}

#map-legend .legend-color {
  width: 12px;
  height: 12px;
  border-radius: 50%;
  margin-right: 8px;
  border: 1px solid #ddd;
}

#map-legend .legend-text {
  color: #666;
}
</style>

备选方案(当前未接入):GeoJSON 矢量边界

  • 组件中保留了 drawLine 方法:基于 GeoJSON 数据(MultiPolygon),通过 geoJson.js 封装的 TMapGeoJSONLayer 转成腾讯地图的多边形/折线图层来绘制乐平镇边界。
  • 由于 GeoJSON 采用 WGS84 坐标系、腾讯地图为 GCJ02,需要逐点做坐标转换,转换逻辑封装在 geoJson.js 中。
  • 该方法在初始化处(drawLine 调用)已被注释,当前未生效,故其数据文件 leping.json 不再附上;如后续启用该方案,恢复 drawLine 调用并引入 leping.json 即可。
  • 与当前生效方案对比:矢量方案边界更精确,但需维护 GeoJSON 与坐标转换、逐点生成多边形开销更高;图片贴地方案开销低、呈现快,边界为「大体重合」,靠人工调两个角点坐标。

配套代码文件:tmap/geoJson.js

javascript 复制代码
import coordtransform from 'coordtransform';

/**
 * 腾讯地图GeoJSON图层类
 * 模拟百度地图的GeoJSONLayer功能
 */
class TMapGeoJSONLayer {
  constructor(options = {}) {
    this.map = options.map;
    this.data = options.data || null;
    this.reference = options.reference || 'GCJ02'; // 坐标系
    this.level = options.level || 0;

    // 默认样式
    this.styles = {
      polygon: {
        color: '#2c7fff50',
        borderColor: '#2c7fff',
        borderWidth: 2
      },
      polyline: {
        color: 'blue',
        width: 2
      },
      marker: {
        // 点标记样式
      }
    };

    // 自定义样式函数
    this.polygonStyle = options.polygonStyle || null;
    this.polylineStyle = options.polylineStyle || null;
    this.markerStyle = options.markerStyle || null;

    // 存储创建的图形对象
    this.geometries = [];
  }

  /**
   * 设置GeoJSON数据并渲染
   */
  setData(geoJsonData) {
    this.clear(); // 清除现有图形
    this.data = geoJsonData;
    debugger
    this.render();
  }

  /**
   * 渲染GeoJSON
   */
  render() {
    if (!this.data || !this.map) {
      console.error('缺少地图实例或GeoJSON数据');
      return;
    }

    const features = this.data.type === 'FeatureCollection'
      ? this.data.features
      : [this.data];

    features.forEach((feature, index) => {
      this._renderFeature(feature, index);
    });
  }

  /**
   * 渲染单个要素
   */
  _renderFeature(feature, index) {
    const geometry = feature.geometry;
    const properties = feature.properties || {};

    // 获取自定义样式
    const polygonStyle = this.polygonStyle
      ? this.polygonStyle(properties)
      : this.styles.polygon;

    const polylineStyle = this.polylineStyle
      ? this.polylineStyle(properties)
      : this.styles.polyline;

    switch (geometry.type) {
      case 'Polygon':
        this._createPolygon(geometry.coordinates, polygonStyle, properties);
        break;
      case 'MultiPolygon':
        geometry.coordinates.forEach(polygonCoords => {
          this._createPolygon(polygonCoords, polygonStyle, properties);
        });
        break;
      case 'LineString':
        this._createPolyline(geometry.coordinates, polylineStyle, properties);
        break;
      case 'MultiLineString':
        geometry.coordinates.forEach(lineCoords => {
          this._createPolyline(lineCoords, polylineStyle, properties);
        });
        break;
      case 'Point':
        // 点标记处理
        break;
    }
  }

  /**
   * 创建多边形
   */
  _createPolygon(coordinates, style, properties) {
    // 转换坐标(GeoJSON格式为[经度, 纬度])
    const paths = coordinates[0].map(coord => {
      let lng = coord[0];
      let lat = coord[1];

      [lng, lat] = wgs84ToGcj02(lng, lat);

      return new TMap.LatLng(lat, lng);
    });

    const polygon = new TMap.MultiPolygon({
      map: this.map,
      geometries: [{
        id: `polygon_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`,
        styleId: 'customPolygonStyle',
        paths: [paths]
      }],
      styles: {
        customPolygonStyle: new TMap.PolygonStyle({
          fillColor: style.color || this.styles.polygon.color,
          strokeColor: style.borderColor || style.strokeColor || this.styles.polygon.borderColor,
          strokeWidth: style.borderWidth || this.styles.polygon.borderWidth,
          fillOpacity: 0.5,
          strokeOpacity: 1,
          showBorder: true
        })
      }
    });

    this.geometries.push(polygon);
    return polygon;
  }

  /**
   * 创建折线
   */
  _createPolyline(coordinates, style, properties) {
    const path = coordinates.map(coord => {
      let lng = coord[0];
      let lat = coord[1];

      // 如需要从WGS84转GCJ02,可使用相应转换函数
      [lng, lat] = wgs84ToGcj02(lng, lat);

      return new TMap.LatLng(lat, lng);
    });

    const polyline = new TMap.MultiPolyline({
      map: this.map,
      geometries: [{
        id: `polyline_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`,
        styleId: 'customPolylineStyle',
        paths: [path]
      }],
      styles: {
        customPolylineStyle: new TMap.PolylineStyle({
          color: style.color || style.strokeColor || this.styles.polyline.color,
          width: style.width || this.styles.polyline.width
        })
      }
    });

    this.geometries.push(polyline);
    return polyline;
  }


  /**
   * 清除所有图形
   */
  clear() {
    this.geometries.forEach(geometry => {
      if (geometry.setMap) {
        geometry.setMap(null);
      }
    });
    this.geometries = [];
  }

  /**
   * 设置显示层级
   */
  setLevel(level) {
    this.level = level;
    // 腾讯地图中可以通过zIndex控制显示层级
    this.geometries.forEach(geometry => {
      // 根据具体API调整zIndex
    });
  }
}

// 转换函数:WGS84转GCJ02(腾讯地图坐标系)
function wgs84ToGcj02(lng, lat) {
  return coordtransform.wgs84togcj02(lng, lat);
}

// 添加导出语句
export { TMapGeoJSONLayer };

调整位置速查

需求 调整位置
边界线偏移或大小不符 tmap/index.vue 的 drawPicture 中 imageSW / imageNE 两个角点(约第 1250-1252 行)
更换边界图片 tmap/index.vue 的 drawPicture 中图片路径(约第 1254-1260 行)
边界透明度 tmap/index.vue 的 drawPicture 中 opacity(约第 1258 行)
放大/缩小切换阈值 tmap/index.vue 的 handleZoomChange 中级别判断(约第 320-326 行)
初始中心点与缩放 bigScreen/index.vue 的 centerLngLat 与地图配置
村委坐标 bigScreen/index.vue 的 mapData 中 center
村委统计字段映射 bigScreen/index.vue 的 selectVillageOverview
散点颜色与半径 tmap/index.vue 的 showAssetCircles 及圆点样式定义
相关推荐
郑州光合科技余经理1 小时前
同城电商系统:库存变更怎么同步到订单
java·开发语言·前端·后端·uni-app·php·ai编程
前端snow3 小时前
ai agent --- DeepAgents 中间件
前端
data analyse 4564 小时前
报表能自定义的国内统计工具有哪些
前端·数据分析
IT_陈寒4 小时前
JavaScript闭包的这个坑,我居然今天才爬出来
前端·人工智能·后端
可乐鸡翅yeah_4 小时前
hls.js 切换多个视频源,新手开发常见踩坑
开发语言·前端·javascript·ios·ffmpeg·音视频·safari
葡萄城技术团队4 小时前
复制走的是数字,留下的是身份:Web 表格里被忽视的数据断层
前端
kyriewen4 小时前
我实测了前端日期的 5 个坑:差 8 小时只是开始
前端·javascript·程序员
anew___4 小时前
《从零手写操作系统 (11):文件系统初探——initrd与VFS抽象层》
java·javascript·算法