You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
583 lines
17 KiB
583 lines
17 KiB
<template>
|
|
<div>
|
|
<div style="display: flex">
|
|
<Input
|
|
v-model="data"
|
|
@on-change="handleChange"
|
|
:placeholder="placeholder"
|
|
:size="size"
|
|
:disabled="disabled"
|
|
:readonly="readonly"
|
|
:maxlength="maxlength"
|
|
:clearable="clearable"
|
|
v-if="showInput"
|
|
style="margin-right: 10px"
|
|
/>
|
|
<Button
|
|
:size="size"
|
|
:type="type"
|
|
:shape="shape"
|
|
:ghost="ghost"
|
|
:disabled="disabled"
|
|
:icon="icon"
|
|
@click="handleShow"
|
|
>{{ text }}</Button
|
|
>
|
|
</div>
|
|
<Modal
|
|
v-model="showModal"
|
|
:mask-closable="false"
|
|
:width="width"
|
|
:fullscreen="full"
|
|
@on-visible-change="changeModal"
|
|
class="map-modal"
|
|
>
|
|
<div slot="header">
|
|
<div class="ivu-modal-header-inner">{{ text }}</div>
|
|
<a @click="handleFull" class="modal-fullscreen">
|
|
<Icon
|
|
v-show="!full"
|
|
type="ios-expand"
|
|
class="model-fullscreen-icon"
|
|
/>
|
|
<Icon
|
|
v-show="full"
|
|
type="ios-contract"
|
|
class="model-fullscreen-icon"
|
|
/>
|
|
</a>
|
|
<a @click="showModal = false" class="ivu-modal-close">
|
|
<Icon type="ios-close" class="ivu-icon-ios-close" />
|
|
</a>
|
|
</div>
|
|
<div :id="id" :style="{ width: '100%', height: modalHeight }"></div>
|
|
<div slot="footer">
|
|
<Row align="middle" justify="space-between">
|
|
<Alert show-icon style="margin-bottom: 0" v-show="currentValue"
|
|
>当前坐标:{{ currentValue }}</Alert
|
|
>
|
|
<div v-show="!currentValue"></div>
|
|
<div>
|
|
<Button type="text" @click="showModal = false" v-if="!preview"
|
|
>取消</Button
|
|
>
|
|
<Button @click="showModal = false" v-if="preview">关闭</Button>
|
|
<Button type="primary" @click="handelSubmit" v-if="!preview"
|
|
>确认提交</Button
|
|
>
|
|
</div>
|
|
</Row>
|
|
</div>
|
|
</Modal>
|
|
</div>
|
|
</template>
|
|
<script>
|
|
import mapboxgl from "mapbox-gl";
|
|
import MapboxGeocoder from "@mapbox/mapbox-gl-geocoder";
|
|
import '@mapbox/mapbox-gl-geocoder/dist/mapbox-gl-geocoder.css';
|
|
export default {
|
|
name: "mapLocate",
|
|
props: {
|
|
id: {
|
|
type: String,
|
|
default: "map",
|
|
},
|
|
value: {
|
|
type: String,
|
|
default: "",
|
|
},
|
|
showInput: {
|
|
type: Boolean,
|
|
default: true,
|
|
},
|
|
preview: {
|
|
type: Boolean,
|
|
default: false,
|
|
},
|
|
text: {
|
|
type: String,
|
|
default: "地图选点",
|
|
},
|
|
width: {
|
|
type: Number,
|
|
default: 900,
|
|
},
|
|
pitch: {
|
|
type: Number,
|
|
default: 0,
|
|
},
|
|
decimal: {
|
|
type: Number,
|
|
default: 6,
|
|
},
|
|
draggable: {
|
|
type: Boolean,
|
|
default: true,
|
|
},
|
|
size: String,
|
|
type: String,
|
|
shape: String,
|
|
ghost: {
|
|
type: Boolean,
|
|
default: false,
|
|
},
|
|
disabled: {
|
|
type: Boolean,
|
|
default: false,
|
|
},
|
|
clearable: {
|
|
type: Boolean,
|
|
default: true,
|
|
},
|
|
placeholder: {
|
|
type: String,
|
|
default: "输入坐标或选择地点",
|
|
},
|
|
maxlength: Number,
|
|
readonly: {
|
|
type: Boolean,
|
|
default: false,
|
|
},
|
|
icon: {
|
|
type: String,
|
|
default: "md-locate",
|
|
},
|
|
styles: {
|
|
type: String,
|
|
default: "mapbox://styles/mapbox/streets-v11",
|
|
},
|
|
center: {
|
|
type: Array,
|
|
default: function () {
|
|
return [116.35, 39.85];
|
|
},
|
|
},
|
|
zoom: {
|
|
type: Number,
|
|
default: 9,
|
|
},
|
|
compact: {
|
|
type: Boolean,
|
|
default: true,
|
|
},
|
|
customAttribution: {
|
|
type: String,
|
|
default: "",
|
|
},
|
|
searchable: {
|
|
type: Boolean,
|
|
default: true,
|
|
},
|
|
changeStyle: {
|
|
type: Boolean,
|
|
default: true,
|
|
},
|
|
navigation: {
|
|
type: Boolean,
|
|
default: true,
|
|
},
|
|
locate: {
|
|
type: Boolean,
|
|
default: true,
|
|
},
|
|
fullscreen: {
|
|
type: Boolean,
|
|
default: false,
|
|
},
|
|
building3D: {
|
|
type: Boolean,
|
|
default: true,
|
|
},
|
|
},
|
|
data() {
|
|
return {
|
|
data: this.value,
|
|
mapbox: null,
|
|
currentValue: "",
|
|
showModal: false,
|
|
full: false,
|
|
modalHeight: "500px",
|
|
marker: null,
|
|
};
|
|
},
|
|
methods: {
|
|
init() {
|
|
if (!config.mapboxToken) {
|
|
return;
|
|
}
|
|
mapboxgl.accessToken = config.mapboxToken;
|
|
this.mapbox = new mapboxgl.Map({
|
|
container: this.id,
|
|
style: this.styles,
|
|
center: this.center,
|
|
zoom: this.zoom,
|
|
pitch: this.pitch,
|
|
attributionControl: false,
|
|
});
|
|
// 属性
|
|
this.mapbox.addControl(
|
|
new mapboxgl.AttributionControl({
|
|
compact: this.compact,
|
|
customAttribution: this.customAttribution,
|
|
})
|
|
);
|
|
// 搜索
|
|
if (this.searchable) {
|
|
this.mapbox.addControl(
|
|
new MapboxGeocoder({
|
|
accessToken: mapboxgl.accessToken,
|
|
mapboxgl: mapboxgl,
|
|
})
|
|
);
|
|
}
|
|
// 地图样式切换
|
|
class ChangeStyleControl {
|
|
getDefaultPosition() {
|
|
const defaultPosition = "top-right";
|
|
return defaultPosition;
|
|
}
|
|
onAdd(map) {
|
|
this.map = map;
|
|
this.controlContainer = document.createElement("div");
|
|
this.controlContainer.classList.add("mapboxgl-ctrl");
|
|
this.controlContainer.classList.add("mapboxgl-ctrl-group");
|
|
this.mapStyleContainer = document.createElement("div");
|
|
this.styleButton = document.createElement("button");
|
|
this.styleButton.type = "button";
|
|
this.mapStyleContainer.classList.add("mapboxgl-style-list");
|
|
const defaultStyle = "街道地图";
|
|
const styles = [
|
|
{ title: "街道地图", uri: "mapbox://styles/mapbox/streets-v11" },
|
|
{
|
|
title: "卫星街道",
|
|
uri: "mapbox://styles/mapbox/satellite-streets-v11",
|
|
},
|
|
{ title: "暗黑风格", uri: "mapbox://styles/mapbox/dark-v10" },
|
|
{ title: "明亮风格", uri: "mapbox://styles/mapbox/light-v10" },
|
|
{ title: "户外地图", uri: "mapbox://styles/mapbox/outdoors-v11" },
|
|
{ title: "卫星地图", uri: "mapbox://styles/mapbox/satellite-v9" },
|
|
];
|
|
for (const style of styles) {
|
|
const styleElement = document.createElement("button");
|
|
styleElement.type = "button";
|
|
styleElement.innerText = style.title;
|
|
styleElement.classList.add(
|
|
style.title.replace(/[^a-z0-9-]/gi, "_")
|
|
);
|
|
styleElement.dataset.uri = JSON.stringify(style.uri);
|
|
styleElement.addEventListener("click", (event) => {
|
|
const srcElement = event.srcElement;
|
|
if (srcElement.classList.contains("active")) {
|
|
return;
|
|
}
|
|
this.map.setStyle(JSON.parse(srcElement.dataset.uri));
|
|
this.map.on("data", (e) => {
|
|
if (e.isSourceLoaded) {
|
|
const labelList = this.map
|
|
.getStyle()
|
|
.layers.filter((layer) => {
|
|
return /-label/.test(layer.id);
|
|
});
|
|
for (let labelLayer of labelList) {
|
|
this.map.setLayoutProperty(labelLayer.id, "text-field", [
|
|
"coalesce",
|
|
["get", "name_zh-Hans"],
|
|
["get", "name"],
|
|
]);
|
|
}
|
|
}
|
|
});
|
|
this.mapStyleContainer.style.display = "none";
|
|
this.styleButton.style.display = "block";
|
|
const elms =
|
|
this.mapStyleContainer.getElementsByClassName("active");
|
|
while (elms[0]) {
|
|
elms[0].classList.remove("active");
|
|
}
|
|
srcElement.classList.add("active");
|
|
});
|
|
if (style.title === defaultStyle) {
|
|
styleElement.classList.add("active");
|
|
}
|
|
this.mapStyleContainer.appendChild(styleElement);
|
|
}
|
|
this.styleButton.classList.add("mapboxgl-ctrl-icon");
|
|
this.styleButton.classList.add("mapboxgl-style-switcher");
|
|
this.styleButton.addEventListener("click", () => {
|
|
this.styleButton.style.display = "none";
|
|
this.mapStyleContainer.style.display = "block";
|
|
});
|
|
document.addEventListener("click", this.onDocumentClick);
|
|
this.controlContainer.appendChild(this.styleButton);
|
|
this.controlContainer.appendChild(this.mapStyleContainer);
|
|
return this.controlContainer;
|
|
}
|
|
onRemove() {
|
|
if (
|
|
!this.controlContainer ||
|
|
!this.controlContainer.parentNode ||
|
|
!this.map ||
|
|
!this.styleButton
|
|
) {
|
|
return;
|
|
}
|
|
this.styleButton.removeEventListener("click", this.onDocumentClick);
|
|
this.controlContainer.parentNode.removeChild(this.controlContainer);
|
|
document.removeEventListener("click", this.onDocumentClick);
|
|
this.map = undefined;
|
|
}
|
|
onDocumentClick(event) {
|
|
if (
|
|
this.controlContainer &&
|
|
!this.controlContainer.contains(event.target) &&
|
|
this.mapStyleContainer &&
|
|
this.styleButton
|
|
) {
|
|
this.mapStyleContainer.style.display = "none";
|
|
this.styleButton.style.display = "block";
|
|
}
|
|
}
|
|
}
|
|
if (this.changeStyle) {
|
|
this.mapbox.addControl(new ChangeStyleControl());
|
|
}
|
|
// 导航控件
|
|
if (this.navigation) {
|
|
this.mapbox.addControl(new mapboxgl.NavigationControl());
|
|
}
|
|
// 定位
|
|
if (this.locate) {
|
|
this.mapbox.addControl(
|
|
new mapboxgl.GeolocateControl({
|
|
positionOptions: {
|
|
enableHighAccuracy: true,
|
|
},
|
|
trackUserLocation: true,
|
|
})
|
|
);
|
|
}
|
|
// 全屏
|
|
if (this.fullscreen) {
|
|
this.mapbox.addControl(
|
|
new mapboxgl.FullscreenControl({
|
|
container: document.querySelector("body"),
|
|
})
|
|
);
|
|
}
|
|
this.mapbox.on("load", () => {
|
|
// 中文
|
|
const labelList = this.mapbox.getStyle().layers.filter((layer) => {
|
|
return /-label/.test(layer.id);
|
|
});
|
|
for (let labelLayer of labelList) {
|
|
this.mapbox.setLayoutProperty(labelLayer.id, "text-field", [
|
|
"coalesce",
|
|
["get", "name_zh-Hans"],
|
|
["get", "name"],
|
|
]);
|
|
}
|
|
// 3D建筑
|
|
if (this.building3D) {
|
|
this.mapbox.addLayer({
|
|
id: "3d-buildings",
|
|
source: "composite",
|
|
"source-layer": "building",
|
|
filter: ["==", "extrude", "true"],
|
|
type: "fill-extrusion",
|
|
minzoom: 15,
|
|
paint: {
|
|
"fill-extrusion-color": "#aaa",
|
|
"fill-extrusion-height": [
|
|
"interpolate",
|
|
["linear"],
|
|
["zoom"],
|
|
15,
|
|
0,
|
|
15.05,
|
|
["get", "height"],
|
|
],
|
|
"fill-extrusion-base": [
|
|
"interpolate",
|
|
["linear"],
|
|
["zoom"],
|
|
15,
|
|
0,
|
|
15.05,
|
|
["get", "min_height"],
|
|
],
|
|
"fill-extrusion-opacity": 0.6,
|
|
},
|
|
});
|
|
// 显示点
|
|
this.flyTo();
|
|
}
|
|
});
|
|
// 单点标记
|
|
this.mapbox.on("click", (e) => {
|
|
if (this.preview) {
|
|
return;
|
|
}
|
|
if (this.marker) {
|
|
this.marker.remove();
|
|
}
|
|
this.marker = new mapboxgl.Marker({
|
|
offset: [0, -25],
|
|
draggable: this.draggable,
|
|
})
|
|
.setLngLat([e.lngLat.lng, e.lngLat.lat])
|
|
.addTo(this.mapbox);
|
|
if (this.decimal < 0) {
|
|
this.decimal = 0;
|
|
}
|
|
if (this.decimal > 14) {
|
|
this.decimal = 14;
|
|
}
|
|
let lng = e.lngLat.lng.toFixed(this.decimal);
|
|
let lat = e.lngLat.lat.toFixed(this.decimal);
|
|
this.currentValue = lng + ", " + lat;
|
|
this.$emit("on-click", this.currentValue);
|
|
});
|
|
},
|
|
flyTo() {
|
|
if (this.data) {
|
|
try {
|
|
let lngLat = this.data.split(",");
|
|
let lng = lngLat[0].trim();
|
|
let lat = lngLat[1].trim();
|
|
this.currentValue = this.data;
|
|
if (this.marker) {
|
|
this.marker.remove();
|
|
}
|
|
this.marker = new mapboxgl.Marker({
|
|
offset: [0, -25],
|
|
draggable: true,
|
|
})
|
|
.setLngLat([lng, lat])
|
|
.addTo(this.mapbox);
|
|
this.mapbox.flyTo({
|
|
center: [lng, lat],
|
|
essential: true,
|
|
});
|
|
} catch (error) {
|
|
this.$Message.error("您输入的坐标不合法");
|
|
}
|
|
} else {
|
|
this.currentValue = "";
|
|
if (this.marker) {
|
|
this.marker.remove();
|
|
}
|
|
this.mapbox.flyTo({
|
|
center: this.center,
|
|
zoom: 9,
|
|
essential: true,
|
|
});
|
|
}
|
|
},
|
|
handleChange(v) {
|
|
this.$emit("input", this.data);
|
|
this.$emit("on-change", this.data);
|
|
},
|
|
setData(value) {
|
|
if (!this.mapbox) {
|
|
this.init();
|
|
}
|
|
if (value != this.data) {
|
|
this.data = value;
|
|
this.$emit("input", this.data);
|
|
this.$emit("on-change", this.data);
|
|
}
|
|
},
|
|
handleFull() {
|
|
this.full = !this.full;
|
|
if (this.full) {
|
|
this.modalHeight = "100%";
|
|
} else {
|
|
this.modalHeight = "500px";
|
|
}
|
|
setTimeout(() => {
|
|
this.mapbox.resize();
|
|
}, 10);
|
|
},
|
|
handleShow() {
|
|
if (!config.mapboxToken) {
|
|
this.$Message.warning("请先配置Mapbox地图的accessToken");
|
|
return;
|
|
}
|
|
this.showModal = true;
|
|
},
|
|
changeModal(v) {
|
|
if (v) {
|
|
setTimeout(() => {
|
|
this.mapbox.resize();
|
|
this.flyTo();
|
|
}, 10);
|
|
}
|
|
},
|
|
handelSubmit() {
|
|
if (!this.currentValue) {
|
|
this.$Message.warning("请在地图中点击鼠标选择一个地点");
|
|
return;
|
|
}
|
|
this.data = this.currentValue;
|
|
this.$emit("input", this.data);
|
|
this.$emit("on-change", this.data);
|
|
this.showModal = false;
|
|
},
|
|
},
|
|
beforeDestroy() {
|
|
// 调用销毁 API 对当前编辑器实例进行销毁
|
|
if (this.mapbox != null) {
|
|
this.mapbox.remove();
|
|
}
|
|
},
|
|
watch: {
|
|
value(val) {
|
|
this.setData(val);
|
|
},
|
|
},
|
|
mounted() {
|
|
this.init();
|
|
},
|
|
};
|
|
</script>
|
|
<style lang="less">
|
|
.mapboxgl-style-list {
|
|
display: none;
|
|
}
|
|
|
|
.mapboxgl-ctrl-group .mapboxgl-style-list button {
|
|
background: none;
|
|
border: none;
|
|
cursor: pointer;
|
|
display: block;
|
|
font-size: 14px;
|
|
padding: 8px 8px 6px;
|
|
text-align: right;
|
|
width: 100%;
|
|
height: auto;
|
|
}
|
|
|
|
.mapboxgl-style-list button.active {
|
|
font-weight: bold;
|
|
}
|
|
|
|
.mapboxgl-style-list button:hover {
|
|
background-color: rgba(0, 0, 0, 0.05);
|
|
}
|
|
|
|
.mapboxgl-style-list button + button {
|
|
border-top: 1px solid #ddd;
|
|
}
|
|
|
|
.mapboxgl-style-switcher {
|
|
background-image: url(data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iaXNvLTg4NTktMSI/PjwhRE9DVFlQRSBzdmcgUFVCTElDICItLy9XM0MvL0RURCBTVkcgMS4xLy9FTiIgImh0dHA6Ly93d3cudzMub3JnL0dyYXBoaWNzL1NWRy8xLjEvRFREL3N2ZzExLmR0ZCI+PHN2ZyB2ZXJzaW9uPSIxLjEiIGlkPSJDYXBhXzEiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgeG1sbnM6eGxpbms9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGxpbmsiIHg9IjBweCIgeT0iMHB4IiB3aWR0aD0iNTQuODQ5cHgiIGhlaWdodD0iNTQuODQ5cHgiIHZpZXdCb3g9IjAgMCA1NC44NDkgNTQuODQ5IiBzdHlsZT0iZW5hYmxlLWJhY2tncm91bmQ6bmV3IDAgMCA1NC44NDkgNTQuODQ5OyIgeG1sOnNwYWNlPSJwcmVzZXJ2ZSI+PGc+PGc+PGc+PHBhdGggZD0iTTU0LjQ5NywzOS42MTRsLTEwLjM2My00LjQ5bC0xNC45MTcsNS45NjhjLTAuNTM3LDAuMjE0LTEuMTY1LDAuMzE5LTEuNzkzLDAuMzE5Yy0wLjYyNywwLTEuMjU0LTAuMTA0LTEuNzktMC4zMThsLTE0LjkyMS01Ljk2OEwwLjM1MSwzOS42MTRjLTAuNDcyLDAuMjAzLTAuNDY3LDAuNTI0LDAuMDEsMC43MTZMMjYuNTYsNTAuODFjMC40NzcsMC4xOTEsMS4yNTEsMC4xOTEsMS43MjksMEw1NC40ODgsNDAuMzNDNTQuOTY0LDQwLjEzOSw1NC45NjksMzkuODE3LDU0LjQ5NywzOS42MTR6Ii8+PHBhdGggZD0iTTU0LjQ5NywyNy41MTJsLTEwLjM2NC00LjQ5MWwtMTQuOTE2LDUuOTY2Yy0wLjUzNiwwLjIxNS0xLjE2NSwwLjMyMS0xLjc5MiwwLjMyMWMtMC42MjgsMC0xLjI1Ni0wLjEwNi0xLjc5My0wLjMyMWwtMTQuOTE4LTUuOTY2TDAuMzUxLDI3LjUxMmMtMC40NzIsMC4yMDMtMC40NjcsMC41MjMsMC4wMSwwLjcxNkwyNi41NiwzOC43MDZjMC40NzcsMC4xOSwxLjI1MSwwLjE5LDEuNzI5LDBsMjYuMTk5LTEwLjQ3OUM1NC45NjQsMjguMDM2LDU0Ljk2OSwyNy43MTYsNTQuNDk3LDI3LjUxMnoiLz48cGF0aCBkPSJNMC4zNjEsMTYuMTI1bDEzLjY2Miw1LjQ2NWwxMi41MzcsNS4wMTVjMC40NzcsMC4xOTEsMS4yNTEsMC4xOTEsMS43MjksMGwxMi41NDEtNS4wMTZsMTMuNjU4LTUuNDYzYzAuNDc3LTAuMTkxLDAuNDgtMC41MTEsMC4wMS0wLjcxNkwyOC4yNzcsNC4wNDhjLTAuNDcxLTAuMjA0LTEuMjM2LTAuMjA0LTEuNzA4LDBMMC4zNTEsMTUuNDFDLTAuMTIxLDE1LjYxNC0wLjExNiwxNS45MzUsMC4zNjEsMTYuMTI1eiIvPjwvZz48L2c+PC9nPjxnPjwvZz48Zz48L2c+PGc+PC9nPjxnPjwvZz48Zz48L2c+PGc+PC9nPjxnPjwvZz48Zz48L2c+PGc+PC9nPjxnPjwvZz48Zz48L2c+PGc+PC9nPjxnPjwvZz48Zz48L2c+PGc+PC9nPjwvc3ZnPg==);
|
|
background-position: center;
|
|
background-repeat: no-repeat;
|
|
background-size: 70%;
|
|
}
|
|
.map-modal {
|
|
.ivu-modal-body {
|
|
padding: 0px;
|
|
}
|
|
}
|
|
</style>
|