|
|
@ -1,6 +1,6 @@ |
|
|
|
import { loadAMap } from "@screen/pages/Home/components/AMapContainer/loadAMap.js"; |
|
|
|
import { Message } from "element-ui"; |
|
|
|
|
|
|
|
// import { Message } from "element-ui";
|
|
|
|
import Vue from "vue"; |
|
|
|
/** |
|
|
|
* @typedef {Object} Point |
|
|
|
* @property {number} weight - The weight of the item. |
|
|
@ -20,163 +20,322 @@ import { Message } from "element-ui"; |
|
|
|
* @param {Function} markerFun |
|
|
|
* @returns |
|
|
|
*/ |
|
|
|
export async function setMarkerCluster(map, points, markerFun) { |
|
|
|
const AMap = await loadAMap(); |
|
|
|
|
|
|
|
let hasClick = false; |
|
|
|
/** |
|
|
|
* 聚合点 |
|
|
|
*/ |
|
|
|
export class MarkerCluster { |
|
|
|
/** |
|
|
|
* @type {} |
|
|
|
*/ |
|
|
|
map; |
|
|
|
markerCluster; |
|
|
|
infoWindow; |
|
|
|
|
|
|
|
// if (!points.length) return Message.warning("未能匹配到对应坐标点");
|
|
|
|
if (!points.length) return []; |
|
|
|
lngLatMap = {}; |
|
|
|
|
|
|
|
const markerCluster = new AMap.MarkerCluster(map, points, { |
|
|
|
// gridSize: 15,
|
|
|
|
maxZoom: 15, |
|
|
|
// 自定义聚合点样式
|
|
|
|
renderClusterMarker(context) { |
|
|
|
// 聚合中点个数
|
|
|
|
const clusterCount = context.count; |
|
|
|
const div = document.createElement("div"); |
|
|
|
let bgColor = "204,235,197"; |
|
|
|
data = []; |
|
|
|
|
|
|
|
div.style.backgroundColor = `rgba(${bgColor}, .6)`; |
|
|
|
constructor() {} |
|
|
|
|
|
|
|
const size = Math.round( |
|
|
|
25 + Math.pow(clusterCount / points.length, 1 / 5) * 36 |
|
|
|
); |
|
|
|
getMap() { |
|
|
|
if (this.map) return this.map; |
|
|
|
|
|
|
|
div.style.borderRadius = |
|
|
|
div.style.lineHeight = |
|
|
|
div.style.width = |
|
|
|
div.style.height = |
|
|
|
`${size}px`; |
|
|
|
div.style.border = `solid 1px rgba(${bgColor}, 1)`; |
|
|
|
div.innerHTML = context.count; |
|
|
|
div.style.color = "#ffffff"; |
|
|
|
div.style.fontSize = "24px"; |
|
|
|
div.style.textAlign = "center"; |
|
|
|
|
|
|
|
context.marker.setOffset(new AMap.Pixel(-size / 2, -size / 2)); |
|
|
|
context.marker.setContent(div); |
|
|
|
}, |
|
|
|
renderMarker(context) { |
|
|
|
const { content, extData } = context.data[0]; |
|
|
|
|
|
|
|
const offset = new AMap.Pixel(0, 0); |
|
|
|
context.marker.setContent(content); |
|
|
|
context.marker.setAnchor("bottom-center"); |
|
|
|
// console.log(
|
|
|
|
// "%c [ context.marker ]-62-「map.js」",
|
|
|
|
// "font-size:15px; background:#7d477f; color:#c18bc3;",
|
|
|
|
// context.marker
|
|
|
|
// );
|
|
|
|
context.marker.setOffset(offset); |
|
|
|
|
|
|
|
context.marker.setExtData(extData); |
|
|
|
|
|
|
|
context.marker.on("click", (e) => { |
|
|
|
hasClick = true; |
|
|
|
|
|
|
|
markerFun(e); |
|
|
|
}); |
|
|
|
}, |
|
|
|
}); |
|
|
|
return (this.map = Vue.prototype.mapIns); |
|
|
|
} |
|
|
|
|
|
|
|
markerCluster.on("click", (e) => { |
|
|
|
if (hasClick) return (hasClick = false); |
|
|
|
async addData(data) { |
|
|
|
this.infoWindow?.close?.(); |
|
|
|
|
|
|
|
map.setCenter(e.lnglat); |
|
|
|
map.setZoom(map.getZoom() + 3); |
|
|
|
}); |
|
|
|
if (!data) return; |
|
|
|
if (!Array.isArray(data)) data = [data]; |
|
|
|
|
|
|
|
return markerCluster; |
|
|
|
} |
|
|
|
this.data.push(...data); |
|
|
|
|
|
|
|
/** |
|
|
|
* |
|
|
|
* @param {*} item |
|
|
|
* @param {*} resolveMarker 处理数据 返回 { lnglat: [lng, lat], content: '' } |
|
|
|
* @param {*} _markerClick marker 点击 |
|
|
|
* @returns |
|
|
|
*/ |
|
|
|
export async function setMarkToMap( |
|
|
|
item, |
|
|
|
data, |
|
|
|
_markerClick, |
|
|
|
{ iconCallback, content, stateCallback } = {} |
|
|
|
) { |
|
|
|
const { mapIns } = this.getMap(); |
|
|
|
|
|
|
|
if (!mapIns) return Message.error("地图加载失败!"); |
|
|
|
|
|
|
|
const normal = |
|
|
|
normal || |
|
|
|
require(`@screen/images/layer${item.id.replace( |
|
|
|
if (!this.markerCluster) await this.setMarkerCluster(); |
|
|
|
|
|
|
|
const map = this.getMap(); |
|
|
|
|
|
|
|
console.log( |
|
|
|
"%c [ data ]-227-「map.js」", |
|
|
|
"font-size:15px; background:#641f14; color:#a86358;", |
|
|
|
data |
|
|
|
); |
|
|
|
|
|
|
|
this.markerCluster.addData(data); |
|
|
|
console.log( |
|
|
|
"%c [ this.markerCluster ]-234-「map.js」", |
|
|
|
"font-size:15px; background:#fe94d3; color:#ffd8ff;", |
|
|
|
this.markerCluster |
|
|
|
); |
|
|
|
|
|
|
|
map.setZoom(10); |
|
|
|
|
|
|
|
setTimeout(() => { |
|
|
|
map.setFitView([...this.markerCluster.U], false, [0, 0, 0, 0], 10); |
|
|
|
}, 150); |
|
|
|
} |
|
|
|
|
|
|
|
getState({ config, extData }) { |
|
|
|
return typeof config.stateCallback === "function" |
|
|
|
? config.stateCallback?.() |
|
|
|
: extData.deviceState == 1; |
|
|
|
} |
|
|
|
|
|
|
|
getIcon({ config, extData }) { |
|
|
|
const normal = require(`@screen/images/layer${config.item.id.replace( |
|
|
|
/^\.|[^/]+(?=.svg$)/g, |
|
|
|
(data) => (data === "." ? "" : `${data}_active`) |
|
|
|
(str) => (str === "." ? "" : `${str}_active`) |
|
|
|
)}`);
|
|
|
|
|
|
|
|
const fault = |
|
|
|
fault || |
|
|
|
require(`@screen/images/layer${item.id.replace( |
|
|
|
const fault = require(`@screen/images/layer${config.item.id.replace( |
|
|
|
/^\.|[^/]+(?=.svg$)/g, |
|
|
|
(data) => (data === "." ? `` : `${data}_fault`) |
|
|
|
(str) => (str === "." ? `` : `${str}_fault`) |
|
|
|
)}`);
|
|
|
|
|
|
|
|
const faultBg = require(`@screen/images/mapBg/fault.svg`); |
|
|
|
const normalBg = require(`@screen/images/mapBg/active.svg`); |
|
|
|
|
|
|
|
const markerClick = (e) => { |
|
|
|
const extData = e.target.getExtData(); |
|
|
|
|
|
|
|
_markerClick(extData, e); |
|
|
|
}; |
|
|
|
|
|
|
|
const markerCluster = await setMarkerCluster( |
|
|
|
mapIns, |
|
|
|
data.map((item) => { |
|
|
|
const currentState = |
|
|
|
typeof stateCallback === "function" |
|
|
|
? stateCallback(item) |
|
|
|
: item.deviceState == 1; |
|
|
|
|
|
|
|
const deviceIcon = |
|
|
|
typeof iconCallback === "function" && iconCallback(currentState, item); |
|
|
|
|
|
|
|
return { |
|
|
|
weight: 1, |
|
|
|
lnglat: [item.longitude, item.latitude], |
|
|
|
name: "", |
|
|
|
extData: item, |
|
|
|
content: |
|
|
|
content || |
|
|
|
`<div style="
|
|
|
|
background-image: url(${currentState ? normalBg : faultBg}); |
|
|
|
background-size: 100% 100%; |
|
|
|
background-repeat: no-repeat; |
|
|
|
width: 51px; |
|
|
|
height: 51px; |
|
|
|
display: flex; |
|
|
|
justify-content: center; |
|
|
|
"> |
|
|
|
<img style=" |
|
|
|
min-width: 24px; |
|
|
|
min-height: 24px; |
|
|
|
width: 24px; |
|
|
|
height: 24px; |
|
|
|
margin-top: 8.1px; |
|
|
|
" src='${deviceIcon ? deviceIcon : currentState ? normal : fault}' |
|
|
|
> |
|
|
|
</div>`, |
|
|
|
}; |
|
|
|
}), |
|
|
|
markerClick |
|
|
|
); |
|
|
|
|
|
|
|
mapIns.setZoom(10); |
|
|
|
|
|
|
|
setTimeout(() => { |
|
|
|
mapIns.setFitView([...markerCluster.U], false, [0, 0, 0, 0], 10); |
|
|
|
}, 150); |
|
|
|
|
|
|
|
return () => markerCluster.setMap(null); |
|
|
|
const currentState = this.getState({ config, extData }); |
|
|
|
|
|
|
|
const deviceIcon = |
|
|
|
typeof config.iconCallback === "function" && |
|
|
|
config.iconCallback(currentState, config.item); |
|
|
|
|
|
|
|
return deviceIcon ? deviceIcon : currentState ? normal : fault; |
|
|
|
} |
|
|
|
|
|
|
|
getContent(data) { |
|
|
|
const faultBg = require(`@screen/images/mapBg/fault.svg`); |
|
|
|
const normalBg = require(`@screen/images/mapBg/active.svg`); |
|
|
|
|
|
|
|
if (data.length === 1) { |
|
|
|
return ` |
|
|
|
<div style=" |
|
|
|
background-image: url(${this.getState(data[0]) ? normalBg : faultBg}); |
|
|
|
background-size: 100% 100%; |
|
|
|
background-repeat: no-repeat; |
|
|
|
width: 51px; |
|
|
|
height: 51px; |
|
|
|
display: flex; |
|
|
|
justify-content: center; |
|
|
|
"> |
|
|
|
<img style=" |
|
|
|
min-width: 24px; |
|
|
|
min-height: 24px; |
|
|
|
width: 24px; |
|
|
|
height: 24px; |
|
|
|
margin-top: 8.1px; |
|
|
|
" src='${this.getIcon(data[0])}' |
|
|
|
> |
|
|
|
</div> |
|
|
|
`;
|
|
|
|
} else { |
|
|
|
const width = `${36 + `${data.length}`.length * 15}px`; |
|
|
|
|
|
|
|
return ` |
|
|
|
<div style=" |
|
|
|
background-image: url(${normalBg}); |
|
|
|
background-size: 100% 100%; |
|
|
|
background-repeat: no-repeat; |
|
|
|
width: ${width}; |
|
|
|
height: ${width}; |
|
|
|
display: flex; |
|
|
|
justify-content: center; |
|
|
|
align-items: center; |
|
|
|
padding-bottom: 9px; |
|
|
|
"> |
|
|
|
${data.length} |
|
|
|
</div> |
|
|
|
`;
|
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
async showInfoWindow(data) { |
|
|
|
const AMap = await loadAMap(); |
|
|
|
|
|
|
|
const map = this.getMap(); |
|
|
|
|
|
|
|
if (!this.infoWindow) |
|
|
|
this.infoWindow = new AMap.InfoWindow({ |
|
|
|
content: "", |
|
|
|
isCustom: true, |
|
|
|
anchor: "bottom-center", |
|
|
|
offset: new AMap.Pixel(0, -60), |
|
|
|
}); |
|
|
|
|
|
|
|
console.log( |
|
|
|
"%c [ this.infoWindow ]-330-「map.js」", |
|
|
|
"font-size:15px; background:#6f5757; color:#b39b9b;", |
|
|
|
this.infoWindow |
|
|
|
); |
|
|
|
|
|
|
|
this.infoWindow.setContent(`<div
|
|
|
|
style=" |
|
|
|
min-width: 240px; |
|
|
|
min-height: 150px; |
|
|
|
width: 90px; |
|
|
|
height: 90px; |
|
|
|
display: flex; |
|
|
|
flex-direction: column; |
|
|
|
position: relative; |
|
|
|
background: rgba(6,66,88,0.8); |
|
|
|
border: 1px solid rgba(42,217,253,0.6); |
|
|
|
"> |
|
|
|
<div style="height: 26px; width: 100%; display: flex;align-items: center; justify-content: space-between; padding: 0 15px; background: linear-gradient(90deg, #237E9B 0%, rgba(23,145,184,0) 100%);"> |
|
|
|
<span>重复</span> |
|
|
|
<img class="info-close" style="width: 12px;cursor: pointer;" src="${require("@screen/images/dialog/icon-close.svg")}" /> |
|
|
|
</div> |
|
|
|
<div style="padding: 15px 9px;flex: 1; overflow: auto;" class="info-window-content"> |
|
|
|
${data |
|
|
|
.map( |
|
|
|
(item) => ` |
|
|
|
<div style="cursor: pointer; padding: 3px 6px;display: flex;align-items: center; gap: 6px;" class="info-window-item"> |
|
|
|
<img style="width: 18px;" src="${this.getIcon(item)}" /> |
|
|
|
<span>${ |
|
|
|
item.extData.deviceName || |
|
|
|
item.extData.warningTitle || |
|
|
|
item.config?.item.title |
|
|
|
}</span> |
|
|
|
</div> |
|
|
|
` |
|
|
|
) |
|
|
|
.join("")} |
|
|
|
</div> |
|
|
|
</div>`); |
|
|
|
|
|
|
|
this.infoWindow.open(map, data[0].lnglat); |
|
|
|
|
|
|
|
this.infoWindow.dom.querySelector(".info-close").onclick = () => |
|
|
|
this.infoWindow.close(); |
|
|
|
|
|
|
|
this.infoWindow.dom.querySelector(".info-window-content").onmousewheel = ( |
|
|
|
e |
|
|
|
) => e.stopPropagation(); |
|
|
|
|
|
|
|
this.infoWindow.dom.querySelector(".info-window-content").onwheel = (e) => |
|
|
|
e.stopPropagation(); |
|
|
|
|
|
|
|
this.infoWindow.dom |
|
|
|
.querySelectorAll(".info-window-item") |
|
|
|
.forEach((item, index) => { |
|
|
|
item.onclick = () => |
|
|
|
data[index].config.markerClick?.( |
|
|
|
data[index].extData, |
|
|
|
data[index].config?.item |
|
|
|
); |
|
|
|
}); |
|
|
|
} |
|
|
|
|
|
|
|
async setMarkerCluster() { |
|
|
|
const AMap = await loadAMap(); |
|
|
|
|
|
|
|
const map = this.getMap(); |
|
|
|
|
|
|
|
let hasClick = false; |
|
|
|
|
|
|
|
const reset = () => { |
|
|
|
this.infoWindow?.close?.(); |
|
|
|
}; |
|
|
|
|
|
|
|
map.on("zoomstart", reset); |
|
|
|
map.on("mapmove", reset); |
|
|
|
|
|
|
|
const markerCluster = new AMap.MarkerCluster(map, [], { |
|
|
|
// gridSize: 15,
|
|
|
|
maxZoom: 15, |
|
|
|
// 自定义聚合点样式
|
|
|
|
renderClusterMarker(context) { |
|
|
|
// 聚合中点个数
|
|
|
|
const clusterCount = context.count; |
|
|
|
const div = document.createElement("div"); |
|
|
|
let bgColor = "204,235,197"; |
|
|
|
|
|
|
|
div.style.backgroundColor = `rgba(${bgColor}, .6)`; |
|
|
|
|
|
|
|
const size = Math.round(24 + `${clusterCount}`.length * 15); |
|
|
|
|
|
|
|
div.style.borderRadius = |
|
|
|
div.style.lineHeight = |
|
|
|
div.style.width = |
|
|
|
div.style.height = |
|
|
|
`${size}px`; |
|
|
|
div.style.border = `solid 1px rgba(${bgColor}, 1)`; |
|
|
|
div.innerHTML = context.count; |
|
|
|
div.style.color = "#ffffff"; |
|
|
|
div.style.fontSize = "24px"; |
|
|
|
div.style.textAlign = "center"; |
|
|
|
|
|
|
|
context.marker.setOffset(new AMap.Pixel(-size / 2, -size / 2)); |
|
|
|
context.marker.setContent(div); |
|
|
|
}, |
|
|
|
renderMarker: (context) => { |
|
|
|
const { |
|
|
|
extData, |
|
|
|
lnglat: { lat, lng }, |
|
|
|
} = context.data[0]; |
|
|
|
|
|
|
|
const lngLatStr = `${lng}/${lat}`; |
|
|
|
|
|
|
|
if (this.lngLatMap[lngLatStr]) |
|
|
|
!this.lngLatMap[lngLatStr].includes(context.data[0]) && |
|
|
|
this.lngLatMap[lngLatStr].push(context.data[0]); |
|
|
|
else this.lngLatMap[lngLatStr] = [context.data[0]]; |
|
|
|
|
|
|
|
context.marker.setContent(this.getContent(this.lngLatMap[lngLatStr])); |
|
|
|
|
|
|
|
context.marker.setAnchor("bottom-center"); |
|
|
|
|
|
|
|
const offset = new AMap.Pixel(0, 0); |
|
|
|
context.marker.setOffset(offset); |
|
|
|
|
|
|
|
context.marker.setExtData(extData); |
|
|
|
|
|
|
|
context.marker.on("click", (e) => { |
|
|
|
hasClick = true; |
|
|
|
|
|
|
|
const data = this.lngLatMap[lngLatStr]; |
|
|
|
if (data.length > 1) { |
|
|
|
this.showInfoWindow(data); |
|
|
|
return; |
|
|
|
} |
|
|
|
|
|
|
|
data[0].config.markerClick?.(data[0].extData, data[0].config?.item); |
|
|
|
}); |
|
|
|
}, |
|
|
|
}); |
|
|
|
|
|
|
|
markerCluster.on("click", (e) => { |
|
|
|
if (hasClick) return (hasClick = false); |
|
|
|
|
|
|
|
map.setCenter(e.lnglat); |
|
|
|
map.setZoom(map.getZoom() + 3); |
|
|
|
}); |
|
|
|
|
|
|
|
this.markerCluster = markerCluster; |
|
|
|
} |
|
|
|
|
|
|
|
removeData(data) { |
|
|
|
data.forEach((item, index) => { |
|
|
|
const findIndex = this.data.findIndex( |
|
|
|
(removeData) => removeData === item |
|
|
|
); |
|
|
|
|
|
|
|
const lngLatStr = item.lnglat.join("/"); |
|
|
|
|
|
|
|
if (this.lngLatMap[lngLatStr]) { |
|
|
|
if (this.lngLatMap[lngLatStr].length < 2) |
|
|
|
delete this.lngLatMap[lngLatStr]; |
|
|
|
else { |
|
|
|
const findIndex = this.lngLatMap[lngLatStr].findIndex( |
|
|
|
(removeData) => removeData === item |
|
|
|
); |
|
|
|
this.lngLatMap[lngLatStr].splice(findIndex, 1); |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
if (findIndex > -1) this.data.splice(findIndex, 1); |
|
|
|
}); |
|
|
|
|
|
|
|
this.markerCluster.setData(this.data); |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
export const markerClusterIns = new MarkerCluster(); |
|
|
|