5 changed files with 324 additions and 69 deletions
@ -0,0 +1,168 @@ |
|||||||
|
<template> |
||||||
|
<div id="map" style="height: 100%; width: 100%; overflow: hidden"></div> |
||||||
|
</template> |
||||||
|
|
||||||
|
<script> |
||||||
|
import AMapLoader from '@amap/amap-jsapi-loader'; |
||||||
|
|
||||||
|
/** |
||||||
|
* props.rectangles: Array<{ id, name, status, geomWkt }> |
||||||
|
* - geomWkt: "POLYGON((lng lat,lng lat,...))" |
||||||
|
* - status: 施工中 | 已施工 | 未施工 | 故障 | 运行中 | 停机 |
||||||
|
*/ |
||||||
|
const STATUS_STYLE = { |
||||||
|
施工中: { stroke: 'rgba(41,169,253,1)', fill: 'rgba(41,169,253,0.45)' }, |
||||||
|
已施工: { stroke: 'rgba(82,196,26,1)', fill: 'rgba(82,196,26,0.45)' }, |
||||||
|
未施工: { stroke: 'rgba(250,173,20,1)', fill: 'rgba(250,173,20,0.45)' }, |
||||||
|
故障: { stroke: 'rgba(219,77,77,1)', fill: 'rgba(219,77,77,0.6)' }, |
||||||
|
运行中: { stroke: 'rgba(19,194,194,1)', fill: 'rgba(19,194,194,0.45)' }, |
||||||
|
停机: { stroke: 'rgba(99,99,99,1)', fill: 'rgba(0,0,0,0.45)' } |
||||||
|
}; |
||||||
|
|
||||||
|
export default { |
||||||
|
name: 'TaskMapPolygon', |
||||||
|
props: { |
||||||
|
rectangles: { |
||||||
|
type: Array, |
||||||
|
default: () => [] |
||||||
|
} |
||||||
|
}, |
||||||
|
data() { |
||||||
|
return { |
||||||
|
map: null, |
||||||
|
AMapIns: null, |
||||||
|
polygons: [], |
||||||
|
texts: [], |
||||||
|
pointAll: { lng: [], lat: [] } |
||||||
|
}; |
||||||
|
}, |
||||||
|
watch: { |
||||||
|
rectangles: { |
||||||
|
immediate: true, |
||||||
|
deep: true, |
||||||
|
handler(val) { |
||||||
|
if (this.map && val?.length) { |
||||||
|
this.clearLayers(); |
||||||
|
this.formatData(val); |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
}, |
||||||
|
mounted() { |
||||||
|
this.initAMap(); |
||||||
|
}, |
||||||
|
beforeUnmount() { |
||||||
|
this.map?.destroy(); |
||||||
|
}, |
||||||
|
methods: { |
||||||
|
initAMap() { |
||||||
|
const map_center = JSON.parse(this.$storage.get('map_center') || 'null'); |
||||||
|
let defaultCenterPoint = [121.5563, 32.107]; |
||||||
|
if (map_center) { |
||||||
|
defaultCenterPoint = map_center.find((el) => el.key == 'default')?.value || defaultCenterPoint; |
||||||
|
} |
||||||
|
|
||||||
|
window._AMapSecurityConfig = { securityJsCode: '5869fe06ab3640b49006f4ec5e595e9f' }; |
||||||
|
AMapLoader.load({ |
||||||
|
key: '3758e4530242ba6282c77f96ea69262d', |
||||||
|
version: '2.0', |
||||||
|
plugins: ['AMap.MouseTool', 'AMap.Scale', 'AMap.Text'] |
||||||
|
}) |
||||||
|
.then((AMap) => { |
||||||
|
this.AMapIns = AMap; |
||||||
|
this.map = new AMap.Map('map', { |
||||||
|
viewMode: '2D', |
||||||
|
zoom: 12, |
||||||
|
center: defaultCenterPoint |
||||||
|
}); |
||||||
|
if (this.rectangles?.length) { |
||||||
|
this.formatData(this.rectangles); |
||||||
|
} |
||||||
|
}) |
||||||
|
.catch(console.error); |
||||||
|
}, |
||||||
|
clearLayers() { |
||||||
|
if (!this.map) return; |
||||||
|
this.map.remove(this.polygons); |
||||||
|
this.map.remove(this.texts); |
||||||
|
this.polygons = []; |
||||||
|
this.texts = []; |
||||||
|
this.pointAll = { lng: [], lat: [] }; |
||||||
|
}, |
||||||
|
formatData(data = []) { |
||||||
|
// 解析 WKT -> AMap path,并着色绘制(使用原始坐标,不做偏移转换) |
||||||
|
const list = (data || []).map((item, index) => { |
||||||
|
const raw = (item.geomWkt || '') |
||||||
|
.replace(/POLYGON\(\(/i, '') |
||||||
|
.replace(/\)\)$/i, '') |
||||||
|
.trim(); |
||||||
|
const points = raw.split(','); |
||||||
|
const path = []; |
||||||
|
for (let i = 0; i < points.length; i++) { |
||||||
|
const parts = points[i].trim().split(/\s+/); |
||||||
|
const lng = Number(parts[0]); |
||||||
|
const lat = Number(parts[1]); |
||||||
|
if (isNaN(lng) || isNaN(lat)) continue; |
||||||
|
this.pointAll.lng.push(lng); |
||||||
|
this.pointAll.lat.push(lat); |
||||||
|
path.push([lng, lat]); |
||||||
|
} |
||||||
|
return { ...item, path, index }; |
||||||
|
}); |
||||||
|
|
||||||
|
list.forEach((it) => this.createPolygon(it)); |
||||||
|
|
||||||
|
// 视野自适配:基于多边形覆盖物 |
||||||
|
if (this.polygons.length) { |
||||||
|
// padding: [top, right, bottom, left] |
||||||
|
this.map.setFitView(this.polygons, false, [60, 60, 60, 60]); |
||||||
|
} |
||||||
|
}, |
||||||
|
createPolygon(item) { |
||||||
|
const { path, status, name } = item; |
||||||
|
const style = STATUS_STYLE[status] || STATUS_STYLE['未施工']; |
||||||
|
const polygon = new this.AMapIns.Polygon({ |
||||||
|
path, |
||||||
|
strokeColor: style.stroke, |
||||||
|
strokeWeight: 2, |
||||||
|
strokeOpacity: 1, |
||||||
|
strokeStyle: 'solid', |
||||||
|
fillColor: style.fill, |
||||||
|
fillOpacity: 0.45, |
||||||
|
cursor: 'pointer', |
||||||
|
zIndex: 50 |
||||||
|
}); |
||||||
|
console.log(polygon, 'polygon'); |
||||||
|
this.map.add(polygon); |
||||||
|
this.polygons.push(polygon); |
||||||
|
|
||||||
|
// 居中名称标签 |
||||||
|
const text = new this.AMapIns.Text({ |
||||||
|
text: name || '', |
||||||
|
anchor: 'center', |
||||||
|
style: { 'background-color': 'transparent', border: 'none', color: '#ffffff', 'font-size': '12px' } |
||||||
|
}); |
||||||
|
// 计算简单中心点 |
||||||
|
const mid = path[Math.floor(path.length / 2)] || path[0]; |
||||||
|
if (mid) text.setPosition(mid); |
||||||
|
text.setMap(this.map); |
||||||
|
this.texts.push(text); |
||||||
|
|
||||||
|
// hover 高亮 |
||||||
|
polygon.on('mouseover', () => { |
||||||
|
polygon.setOptions({ fillOpacity: 0.55 }); |
||||||
|
}); |
||||||
|
polygon.on('mouseout', () => { |
||||||
|
polygon.setOptions({ fillOpacity: 0.35 }); |
||||||
|
}); |
||||||
|
} |
||||||
|
} |
||||||
|
}; |
||||||
|
</script> |
||||||
|
|
||||||
|
<style scoped lang="scss"> |
||||||
|
#map { |
||||||
|
width: 100%; |
||||||
|
height: 100%; |
||||||
|
} |
||||||
|
</style> |
||||||
@ -0,0 +1,52 @@ |
|||||||
|
<template> |
||||||
|
<div class="block-title"> |
||||||
|
<div class="title-left"> |
||||||
|
<img src="../images/block-title.png" alt="logo" /> |
||||||
|
<div class="title-text">{{ title }}</div> |
||||||
|
</div> |
||||||
|
<div class="title-more"> |
||||||
|
<slot name="more"> </slot> |
||||||
|
</div> |
||||||
|
</div> |
||||||
|
</template> |
||||||
|
|
||||||
|
<script> |
||||||
|
export default { |
||||||
|
name: 'BlockTitle', |
||||||
|
props: { |
||||||
|
title: { |
||||||
|
type: String, |
||||||
|
default: '' |
||||||
|
} |
||||||
|
} |
||||||
|
}; |
||||||
|
</script> |
||||||
|
<style lang="scss" scoped> |
||||||
|
.block-title { |
||||||
|
display: flex; |
||||||
|
justify-content: space-between; |
||||||
|
align-items: center; |
||||||
|
height: 28px; |
||||||
|
img { |
||||||
|
height: 26px; |
||||||
|
width: auto; |
||||||
|
margin-right: 8px; |
||||||
|
} |
||||||
|
.title-left { |
||||||
|
display: flex; |
||||||
|
align-items: center; |
||||||
|
} |
||||||
|
.title-text { |
||||||
|
font-family: PingFang SC, PingFang SC; |
||||||
|
font-weight: 600; |
||||||
|
font-size: 20px; |
||||||
|
color: #333333; |
||||||
|
} |
||||||
|
.title-more { |
||||||
|
font-family: PingFang SC, PingFang SC; |
||||||
|
font-weight: 400; |
||||||
|
font-size: 14px; |
||||||
|
color: #4886e0; |
||||||
|
} |
||||||
|
} |
||||||
|
</style> |
||||||
|
After Width: | Height: | Size: 4.0 KiB |
Loading…
Reference in new issue