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) |
整体数据流:父页面调用村委总览接口拿到各村委统计 → 传入地图组件 → 地图组件初始化腾讯地图、叠加自定义区域边界、渲染村委名称标签 → 加载资产数据 → 按缩放级别在「村委统计」与「资产点位」之间切换。
步骤一:准备村委资产统计接口
- 本步骤只需用到该文件中的两个接口:村委总览 villageOverview(区域资产统计,按类型查询各村委的资产统计数量)、资产列表 listAsset(供地图展示资产点位使用)。
- 接口返回每个村委的资产总数、未出租数、出租数、不可出租数,供父页面合并进村委数据。
- 类型参数约定:土地对应 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
})
}
步骤二:大屏父页面引入地图组件并传入村委数据
- 父页面 data 中维护 mapData 数组(15 个村委/单位),每项含村委标识、名称、资产总数、未出租/出租/不可出租数量,以及村委标注坐标(先纬度后经度)。
- 模板中渲染地图组件,传入地图中心点、村委数据 mapData 与当前 Tab 索引。
- 顶部「土地 / 物业」Tab 的索引作为地图组件的 key,切换时会重建地图组件;切换同时会重新调用村委总览接口刷新统计。
- 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;
}
})
})
});
},
步骤三:实现腾讯地图组件
该组件是整个整合的核心,一次完成以下内容:
- 初始化腾讯地图:通过腾讯地图 SDK 在容器中创建地图实例,设置中心点、初始缩放 12 与地图样式,并挂载点位图层、资产散点图层(圆点)、资产圈地图层(多边形)以及自定义信息弹窗。
- 加载资产数据:调用资产列表接口,按当前 Tab 决定资产类型(土地对应 2、物业对应 1),资产坐标字段为「经度,纬度」格式。
- 渲染村委名称与统计:用多标签图层按村委坐标渲染名称标签(名称后附带资产总数),点击标签弹出该村委的资产统计弹窗(资产总数、未出租、出租、不可出租),选中标签高亮,弹窗内「查看详情」向上抛出 village-detail 事件。
- 按缩放级别切换展示:以缩放级别 13 为界,缩小时隐藏资产散点、突出村委统计;放大时显示资产散点与圈地。缩放监听做约 300 毫秒防抖。
- 辅助控件:显示控制(点位 / 圈地开关)、图例(按使用状态过滤并定位中心点)、地图类型切换(矢量 / 卫星)。
重点:自定义区域边界
- 生效方式为图片贴地覆盖,对应组件中的 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 及圆点样式定义 |