|
|
|
@ -100,10 +100,14 @@ |
|
|
|
<div slot="footer"> |
|
|
|
<Row align="middle" justify="space-between"> |
|
|
|
<div style="flex: 1; margin-right: 12px"> |
|
|
|
<Alert show-icon style="margin-bottom: 0" |
|
|
|
>当前选点坐标:{{ currentValue || "未选择" }}</Alert |
|
|
|
<Alert show-icon style="margin-bottom: 0" |
|
|
|
>{{ valueSummaryLabel }}:{{ valueSummaryText }}</Alert |
|
|
|
> |
|
|
|
<div class="locate-tip">坐标为高德 GCJ-02;可搜索、定位或点击地图选点</div> |
|
|
|
<div class="locate-tip">{{ locateTip }}</div> |
|
|
|
<div v-if="isPolygonMode" class="polygon-actions"> |
|
|
|
<Button size="small" @click="undoPolygonPoint">撤销上一个点</Button> |
|
|
|
<Button size="small" @click="clearPolygonValue">清空范围</Button> |
|
|
|
</div> |
|
|
|
</div> |
|
|
|
<div> |
|
|
|
<Button type="text" @click="showModal = false">取消</Button> |
|
|
|
@ -193,12 +197,21 @@ export default { |
|
|
|
type: Boolean, |
|
|
|
default: true, |
|
|
|
}, |
|
|
|
mode: { |
|
|
|
type: String, |
|
|
|
default: "point", |
|
|
|
}, |
|
|
|
}, |
|
|
|
data() { |
|
|
|
return { |
|
|
|
data: this.value, |
|
|
|
map: null, |
|
|
|
marker: null, |
|
|
|
referenceMarker: null, |
|
|
|
polygon: null, |
|
|
|
polygonLine: null, |
|
|
|
polygonEditor: null, |
|
|
|
vertexMarkers: [], |
|
|
|
placeSearch: null, |
|
|
|
currentValue: "", |
|
|
|
showModal: false, |
|
|
|
@ -210,6 +223,26 @@ export default { |
|
|
|
mapReady: false, |
|
|
|
}; |
|
|
|
}, |
|
|
|
computed: { |
|
|
|
isPolygonMode() { |
|
|
|
return this.mode === "polygon"; |
|
|
|
}, |
|
|
|
valueSummaryLabel() { |
|
|
|
return this.isPolygonMode ? "当前匹配范围" : "当前选点坐标"; |
|
|
|
}, |
|
|
|
valueSummaryText() { |
|
|
|
if (!this.isPolygonMode) { |
|
|
|
return this.currentValue || "未选择"; |
|
|
|
} |
|
|
|
const points = this.parsePolygonValue(this.currentValue); |
|
|
|
return points.length ? `${points.length} 个点` : "未绘制"; |
|
|
|
}, |
|
|
|
locateTip() { |
|
|
|
return this.isPolygonMode |
|
|
|
? "坐标为高德 GCJ-02;点击地图依次绘制多边形顶点,至少 3 个点;未绘制则不参与自动匹配" |
|
|
|
: "坐标为高德 GCJ-02;可搜索、定位或点击地图选点"; |
|
|
|
}, |
|
|
|
}, |
|
|
|
methods: { |
|
|
|
getAmapKey() { |
|
|
|
return (typeof config !== "undefined" && config.amapKey) || ""; |
|
|
|
@ -223,6 +256,12 @@ export default { |
|
|
|
if (decimal > 14) decimal = 14; |
|
|
|
return Number(lng).toFixed(decimal) + ", " + Number(lat).toFixed(decimal); |
|
|
|
}, |
|
|
|
formatPoint(lng, lat) { |
|
|
|
let decimal = this.decimal; |
|
|
|
if (decimal < 0) decimal = 0; |
|
|
|
if (decimal > 14) decimal = 14; |
|
|
|
return [Number(Number(lng).toFixed(decimal)), Number(Number(lat).toFixed(decimal))]; |
|
|
|
}, |
|
|
|
parsePosition(value) { |
|
|
|
if (!value || !String(value).trim()) return null; |
|
|
|
const parts = String(value).split(","); |
|
|
|
@ -253,11 +292,15 @@ export default { |
|
|
|
}); |
|
|
|
this.map.on("click", (e) => { |
|
|
|
if (!e || !e.lnglat) return; |
|
|
|
this.placeMarker(e.lnglat.getLng(), e.lnglat.getLat()); |
|
|
|
if (this.isPolygonMode) { |
|
|
|
this.addPolygonPoint(e.lnglat.getLng(), e.lnglat.getLat()); |
|
|
|
} else { |
|
|
|
this.placeMarker(e.lnglat.getLng(), e.lnglat.getLat()); |
|
|
|
} |
|
|
|
}); |
|
|
|
return new Promise((resolve) => { |
|
|
|
AMap.plugin( |
|
|
|
["AMap.ToolBar", "AMap.Scale", "AMap.PlaceSearch", "AMap.Geolocation"], |
|
|
|
["AMap.ToolBar", "AMap.Scale", "AMap.PlaceSearch", "AMap.Geolocation", "AMap.PolygonEditor"], |
|
|
|
() => { |
|
|
|
this.map.addControl( |
|
|
|
new AMap.ToolBar({ |
|
|
|
@ -307,10 +350,158 @@ export default { |
|
|
|
lat, |
|
|
|
]); |
|
|
|
}, |
|
|
|
placeReferenceMarker(lng, lat, zoom, title) { |
|
|
|
if (!this.map || typeof window.AMap === "undefined") return; |
|
|
|
if (this.referenceMarker) { |
|
|
|
this.referenceMarker.setMap(null); |
|
|
|
this.referenceMarker = null; |
|
|
|
} |
|
|
|
this.referenceMarker = new window.AMap.Marker({ |
|
|
|
position: [lng, lat], |
|
|
|
title: title || "当前位置", |
|
|
|
icon: "https://webapi.amap.com/theme/v1.3/markers/n/mark_b.png", |
|
|
|
}); |
|
|
|
this.referenceMarker.setMap(this.map); |
|
|
|
this.map.setZoomAndCenter(zoom || Math.max(this.map.getZoom(), 17), [ |
|
|
|
lng, |
|
|
|
lat, |
|
|
|
]); |
|
|
|
}, |
|
|
|
parsePolygonValue(value) { |
|
|
|
if (!value || !String(value).trim()) return []; |
|
|
|
try { |
|
|
|
const parsed = JSON.parse(value); |
|
|
|
if (!Array.isArray(parsed)) return []; |
|
|
|
return parsed |
|
|
|
.map((item) => { |
|
|
|
if (Array.isArray(item) && item.length >= 2) { |
|
|
|
return this.formatPoint(item[0], item[1]); |
|
|
|
} |
|
|
|
if (item && typeof item === "object") { |
|
|
|
return this.formatPoint(item.lng, item.lat); |
|
|
|
} |
|
|
|
return null; |
|
|
|
}) |
|
|
|
.filter((item) => { |
|
|
|
if (!item) return false; |
|
|
|
const lng = item[0]; |
|
|
|
const lat = item[1]; |
|
|
|
return Number.isFinite(lng) && Number.isFinite(lat) && lng >= -180 && lng <= 180 && lat >= -90 && lat <= 90; |
|
|
|
}); |
|
|
|
} catch (e) { |
|
|
|
return []; |
|
|
|
} |
|
|
|
}, |
|
|
|
formatPolygonValue(points) { |
|
|
|
return JSON.stringify((points || []).map((item) => this.formatPoint(item[0], item[1]))); |
|
|
|
}, |
|
|
|
getPolygonPathPoints() { |
|
|
|
if (!this.polygon || typeof this.polygon.getPath !== "function") { |
|
|
|
return this.parsePolygonValue(this.currentValue); |
|
|
|
} |
|
|
|
return this.polygon.getPath().map((item) => { |
|
|
|
const lng = typeof item.getLng === "function" ? item.getLng() : item.lng; |
|
|
|
const lat = typeof item.getLat === "function" ? item.getLat() : item.lat; |
|
|
|
return this.formatPoint(lng, lat); |
|
|
|
}); |
|
|
|
}, |
|
|
|
syncPolygonValue(points) { |
|
|
|
this.currentValue = points && points.length ? this.formatPolygonValue(points) : ""; |
|
|
|
this.$emit("on-click", this.currentValue); |
|
|
|
}, |
|
|
|
addPolygonPoint(lng, lat) { |
|
|
|
const points = this.parsePolygonValue(this.currentValue); |
|
|
|
points.push(this.formatPoint(lng, lat)); |
|
|
|
this.syncPolygonValue(points); |
|
|
|
this.renderPolygon(points, false); |
|
|
|
}, |
|
|
|
clearPolygonOverlays() { |
|
|
|
if (this.polygonEditor) { |
|
|
|
this.polygonEditor.close(); |
|
|
|
this.polygonEditor = null; |
|
|
|
} |
|
|
|
if (this.polygon) { |
|
|
|
this.polygon.setMap(null); |
|
|
|
this.polygon = null; |
|
|
|
} |
|
|
|
if (this.polygonLine) { |
|
|
|
this.polygonLine.setMap(null); |
|
|
|
this.polygonLine = null; |
|
|
|
} |
|
|
|
this.vertexMarkers.forEach((item) => item.setMap(null)); |
|
|
|
this.vertexMarkers = []; |
|
|
|
}, |
|
|
|
renderPolygon(points, fitView) { |
|
|
|
if (!this.map || typeof window.AMap === "undefined") return; |
|
|
|
this.clearPolygonOverlays(); |
|
|
|
const path = (points || []).map((item) => item.slice(0, 2)); |
|
|
|
path.forEach((item, index) => { |
|
|
|
const marker = new window.AMap.Marker({ |
|
|
|
position: item, |
|
|
|
content: `<div class="amap-vertex-marker">${index + 1}</div>`, |
|
|
|
offset: new window.AMap.Pixel(-8, -8), |
|
|
|
}); |
|
|
|
marker.setMap(this.map); |
|
|
|
this.vertexMarkers.push(marker); |
|
|
|
}); |
|
|
|
if (path.length >= 3) { |
|
|
|
this.polygon = new window.AMap.Polygon({ |
|
|
|
path, |
|
|
|
strokeColor: "#2d8cf0", |
|
|
|
strokeWeight: 2, |
|
|
|
fillColor: "#2d8cf0", |
|
|
|
fillOpacity: 0.18, |
|
|
|
}); |
|
|
|
this.polygon.setMap(this.map); |
|
|
|
this.polygonEditor = new window.AMap.PolygonEditor(this.map, this.polygon); |
|
|
|
["adjust", "addnode", "removenode", "end"].forEach((eventName) => { |
|
|
|
this.polygonEditor.on(eventName, () => { |
|
|
|
this.syncPolygonValue(this.getPolygonPathPoints()); |
|
|
|
}); |
|
|
|
}); |
|
|
|
this.polygonEditor.open(); |
|
|
|
if (fitView) { |
|
|
|
this.map.setFitView([this.polygon]); |
|
|
|
} |
|
|
|
} else if (path.length > 0) { |
|
|
|
this.polygonLine = new window.AMap.Polyline({ |
|
|
|
path, |
|
|
|
strokeColor: "#2d8cf0", |
|
|
|
strokeWeight: 2, |
|
|
|
strokeStyle: "dashed", |
|
|
|
}); |
|
|
|
this.polygonLine.setMap(this.map); |
|
|
|
if (fitView) { |
|
|
|
this.map.setFitView([...this.vertexMarkers, this.polygonLine]); |
|
|
|
} |
|
|
|
} |
|
|
|
}, |
|
|
|
syncPolygonFromValue() { |
|
|
|
const points = this.parsePolygonValue(this.data); |
|
|
|
this.currentValue = points.length ? this.formatPolygonValue(points) : ""; |
|
|
|
this.renderPolygon(points, true); |
|
|
|
if (!points.length && this.map) { |
|
|
|
this.map.setZoomAndCenter(this.zoom, this.center); |
|
|
|
} |
|
|
|
}, |
|
|
|
undoPolygonPoint() { |
|
|
|
const points = this.getPolygonPathPoints(); |
|
|
|
points.pop(); |
|
|
|
this.syncPolygonValue(points); |
|
|
|
this.renderPolygon(points, false); |
|
|
|
}, |
|
|
|
clearPolygonValue() { |
|
|
|
this.syncPolygonValue([]); |
|
|
|
this.clearPolygonOverlays(); |
|
|
|
}, |
|
|
|
syncMarkerFromValue() { |
|
|
|
if (this.isPolygonMode) { |
|
|
|
this.syncPolygonFromValue(); |
|
|
|
return; |
|
|
|
} |
|
|
|
const pos = this.parsePosition(this.data); |
|
|
|
if (pos) { |
|
|
|
this.currentValue = this.data; |
|
|
|
this.currentValue = this.data; |
|
|
|
this.placeMarker(pos.lng, pos.lat, 16); |
|
|
|
} else { |
|
|
|
this.currentValue = ""; |
|
|
|
@ -355,7 +546,11 @@ export default { |
|
|
|
if (!item || !item.location) return; |
|
|
|
const lng = item.location.lng; |
|
|
|
const lat = item.location.lat; |
|
|
|
this.placeMarker(lng, lat, 17); |
|
|
|
if (this.isPolygonMode) { |
|
|
|
this.placeReferenceMarker(lng, lat, 17, item.name || "搜索位置"); |
|
|
|
} else { |
|
|
|
this.placeMarker(lng, lat, 17); |
|
|
|
} |
|
|
|
this.searchResults = []; |
|
|
|
this.keyword = item.name || this.keyword; |
|
|
|
}, |
|
|
|
@ -374,7 +569,11 @@ export default { |
|
|
|
.then((pos) => { |
|
|
|
loading(); |
|
|
|
this.locating = false; |
|
|
|
this.placeMarker(pos.lng, pos.lat, 17); |
|
|
|
if (this.isPolygonMode) { |
|
|
|
this.placeReferenceMarker(pos.lng, pos.lat, 17, "当前位置"); |
|
|
|
} else { |
|
|
|
this.placeMarker(pos.lng, pos.lat, 17); |
|
|
|
} |
|
|
|
if (pos.accuracy && pos.accuracy > 1000) { |
|
|
|
this.$Message.warning( |
|
|
|
`定位成功,精度约 ${Math.round(pos.accuracy)} 米,请核对后确认` |
|
|
|
@ -440,6 +639,14 @@ export default { |
|
|
|
} |
|
|
|
}, |
|
|
|
handelSubmit() { |
|
|
|
if (this.isPolygonMode) { |
|
|
|
const points = this.getPolygonPathPoints(); |
|
|
|
if (points.length > 0 && points.length < 3) { |
|
|
|
this.$Message.warning("匹配范围至少需要 3 个点"); |
|
|
|
return; |
|
|
|
} |
|
|
|
this.currentValue = points.length ? this.formatPolygonValue(points) : ""; |
|
|
|
} |
|
|
|
this.data = this.currentValue; |
|
|
|
this.$emit("input", this.data); |
|
|
|
this.$emit("on-change", this.data); |
|
|
|
@ -451,7 +658,12 @@ export default { |
|
|
|
this.marker.setMap(null); |
|
|
|
this.marker = null; |
|
|
|
} |
|
|
|
if (this.referenceMarker) { |
|
|
|
this.referenceMarker.setMap(null); |
|
|
|
this.referenceMarker = null; |
|
|
|
} |
|
|
|
if (this.map) { |
|
|
|
this.clearPolygonOverlays(); |
|
|
|
this.map.destroy(); |
|
|
|
this.map = null; |
|
|
|
} |
|
|
|
@ -526,6 +738,22 @@ export default { |
|
|
|
font-size: 12px; |
|
|
|
line-height: 1.4; |
|
|
|
} |
|
|
|
.polygon-actions { |
|
|
|
margin-top: 8px; |
|
|
|
button + button { |
|
|
|
margin-left: 8px; |
|
|
|
} |
|
|
|
} |
|
|
|
.amap-vertex-marker { |
|
|
|
width: 16px; |
|
|
|
height: 16px; |
|
|
|
line-height: 16px; |
|
|
|
text-align: center; |
|
|
|
color: #fff; |
|
|
|
background: #2d8cf0; |
|
|
|
border-radius: 50%; |
|
|
|
font-size: 12px; |
|
|
|
} |
|
|
|
.modal-fullscreen { |
|
|
|
position: absolute; |
|
|
|
right: 42px; |
|
|
|
|