wangfukang 1 week ago
parent
commit
7b0c7f5ed4
  1. 4
      public/config.js
  2. 124
      src/libs/amapLoader.js
  3. 88
      src/libs/coordTransform.js
  4. 1
      src/views/app/business/logistics/logistics.vue
  5. 1
      src/views/app/shop/addEdit.vue
  6. 180
      src/views/app/shopArea/shopArea.vue
  7. 676
      src/views/my-components/hiver/map.vue
  8. 581
      src/views/my-components/hiver/mapLocate.vue

4
public/config.js

@ -1,5 +1,7 @@
// 打包后仍可修改的配置 // 打包后仍可修改的配置
const config = { const config = {
baseApi: "/hiver", // 请求路径统一前缀 baseApi: "/hiver", // 请求路径统一前缀
mapboxToken: "pk.eyJ1IjoiZGVsaWNhY3lsZWUiLCJhIjoiY2o4b3U2eWx0MDh2ODJxcnNra2ZrM3l1dyJ9.z6CNmgaMEbC0Ks8AMM72Tw" // mapbox地图accessToken mapboxToken: "pk.eyJ1IjoiZGVsaWNhY3lsZWUiLCJhIjoiY2o4b3U2eWx0MDh2ODJxcnNra2ZrM3l1dyJ9.z6CNmgaMEbC0Ks8AMM72Tw", // mapbox地图accessToken
amapKey: "4732ddeaa9ce280fa01864ff256426a8", // 高德地图 Web端(JS API) Key
amapSecurityJsCode: "05d7751de12e72a8e764a21e66d2e16f" // 高德安全密钥
} }

124
src/libs/amapLoader.js

@ -0,0 +1,124 @@
let amapLoading = null;
function ensureSecurityConfig(securityJsCode) {
const code =
securityJsCode ||
(typeof config !== "undefined" && config.amapSecurityJsCode) ||
"";
if (code) {
window._AMapSecurityConfig = {
securityJsCode: code,
};
}
}
/**
* 项目里有 monaco AMD loader加载高德时需临时屏蔽 define
* 且不要用高德 loader.js会触发 Can only have one anonymous define
*/
function loadAmapScript(key) {
return new Promise((resolve, reject) => {
if (window.AMap) {
resolve(window.AMap);
return;
}
const previousDefine = window.define;
const hadAmd =
typeof previousDefine === "function" && previousDefine.amd;
if (hadAmd) {
window.define = undefined;
}
const restoreDefine = () => {
if (hadAmd) {
window.define = previousDefine;
}
};
const script = document.createElement("script");
script.type = "text/javascript";
script.charset = "utf-8";
// 不用 async/callback/plugin,避免 _cssload_ 与 AMD 冲突
script.src =
"https://webapi.amap.com/maps?v=2.0&key=" + encodeURIComponent(key);
script.onload = () => {
restoreDefine();
if (window.AMap) {
resolve(window.AMap);
} else {
reject(new Error("高德地图加载失败"));
}
};
script.onerror = () => {
restoreDefine();
reject(new Error("高德地图脚本加载失败"));
};
document.head.appendChild(script);
});
}
/**
* 按需加载高德 JS API
*/
export function loadAmap(key, securityJsCode) {
if (typeof window === "undefined") {
return Promise.reject(new Error("非浏览器环境"));
}
const finalKey = key || (typeof config !== "undefined" && config.amapKey) || "";
if (!finalKey) {
return Promise.reject(new Error("未配置高德地图 Key"));
}
if (window.AMap) {
return Promise.resolve(window.AMap);
}
if (amapLoading) {
return amapLoading;
}
ensureSecurityConfig(securityJsCode);
amapLoading = loadAmapScript(finalKey).catch((err) => {
amapLoading = null;
throw err;
});
return amapLoading;
}
/**
* 使用高德定位返回 GCJ-02 { lng, lat, accuracy, address }
*/
export function getAmapLocation(key, securityJsCode) {
return loadAmap(key, securityJsCode).then((AMap) => {
return new Promise((resolve, reject) => {
const runLocate = () => {
const geolocation = new AMap.Geolocation({
enableHighAccuracy: true,
timeout: 15000,
convert: true,
getCityWhenFail: false,
});
geolocation.getCurrentPosition((status, result) => {
if (status === "complete" && result && result.position) {
const position = result.position;
resolve({
lng:
typeof position.getLng === "function"
? position.getLng()
: position.lng,
lat:
typeof position.getLat === "function"
? position.getLat()
: position.lat,
accuracy: result.accuracy,
address: result.formattedAddress || "",
});
} else {
const message =
(result && (result.message || result.info)) || "定位失败";
reject(new Error(message));
}
});
};
AMap.plugin("AMap.Geolocation", runLocate);
});
});
}

88
src/libs/coordTransform.js

@ -0,0 +1,88 @@
/**
* 国内坐标系转换高德 GCJ-02 GPS WGS84
* Mapbox 使用 WGS84高德定位返回 GCJ-02
*/
const PI = Math.PI;
const A = 6378245.0;
const EE = 0.00669342162296594323;
function outOfChina(lng, lat) {
return lng < 72.004 || lng > 137.8347 || lat < 0.8293 || lat > 55.8271;
}
function transformLat(lng, lat) {
let ret =
-100.0 +
2.0 * lng +
3.0 * lat +
0.2 * lat * lat +
0.1 * lng * lat +
0.2 * Math.sqrt(Math.abs(lng));
ret +=
((20.0 * Math.sin(6.0 * lng * PI) + 20.0 * Math.sin(2.0 * lng * PI)) *
2.0) /
3.0;
ret +=
((20.0 * Math.sin(lat * PI) + 40.0 * Math.sin((lat / 3.0) * PI)) * 2.0) /
3.0;
ret +=
((160.0 * Math.sin((lat / 12.0) * PI) +
320 * Math.sin((lat * PI) / 30.0)) *
2.0) /
3.0;
return ret;
}
function transformLng(lng, lat) {
let ret =
300.0 +
lng +
2.0 * lat +
0.1 * lng * lng +
0.1 * lng * lat +
0.1 * Math.sqrt(Math.abs(lng));
ret +=
((20.0 * Math.sin(6.0 * lng * PI) + 20.0 * Math.sin(2.0 * lng * PI)) *
2.0) /
3.0;
ret +=
((20.0 * Math.sin(lng * PI) + 40.0 * Math.sin((lng / 3.0) * PI)) * 2.0) /
3.0;
ret +=
((150.0 * Math.sin((lng / 12.0) * PI) +
300.0 * Math.sin((lng / 30.0) * PI)) *
2.0) /
3.0;
return ret;
}
function delta(lng, lat) {
let dLat = transformLat(lng - 105.0, lat - 35.0);
let dLng = transformLng(lng - 105.0, lat - 35.0);
const radLat = (lat / 180.0) * PI;
let magic = Math.sin(radLat);
magic = 1 - EE * magic * magic;
const sqrtMagic = Math.sqrt(magic);
dLat = (dLat * 180.0) / (((A * (1 - EE)) / (magic * sqrtMagic)) * PI);
dLng = (dLng * 180.0) / ((A / sqrtMagic) * Math.cos(radLat) * PI);
return { lat: dLat, lng: dLng };
}
/** GCJ-02 -> WGS84 */
export function gcj02ToWgs84(lng, lat) {
if (outOfChina(lng, lat)) {
return [lng, lat];
}
const d = delta(lng, lat);
return [lng - d.lng, lat - d.lat];
}
/** WGS84 -> GCJ-02 */
export function wgs84ToGcj02(lng, lat) {
if (outOfChina(lng, lat)) {
return [lng, lat];
}
const d = delta(lng, lat);
return [lng + d.lng, lat + d.lat];
}

1
src/views/app/business/logistics/logistics.vue

@ -323,6 +323,7 @@
xiangtong: false, xiangtong: false,
sortField: "avgTime", sortField: "avgTime",
regionId: regionId, regionId: regionId,
fromAdmin: true,
pageNum: 1, pageNum: 1,
pageSize: 10 pageSize: 10
}, },

1
src/views/app/shop/addEdit.vue

@ -218,6 +218,7 @@
<FormItem label="中转地点" prop="transferAddressId" class="form-noheight"> <FormItem label="中转地点" prop="transferAddressId" class="form-noheight">
<shopArea-tree-choose @on-change="handleSelectTransferAddress" <shopArea-tree-choose @on-change="handleSelectTransferAddress"
@on-clear="handleClearTransferAddress" ref="transferAddressTree" @on-clear="handleClearTransferAddress" ref="transferAddressTree"
:root-id="shopAreaRootId"
text="选择中转地点" placeholder="点击选择中转地点" text="选择中转地点" placeholder="点击选择中转地点"
placement="top" tree-max-height="260px"></shopArea-tree-choose> placement="top" tree-max-height="260px"></shopArea-tree-choose>
</FormItem> </FormItem>

180
src/views/app/shopArea/shopArea.vue

@ -7,6 +7,13 @@
white-space: normal; white-space: normal;
line-height: 1.5; line-height: 1.5;
} }
.geo-tip {
margin-top: 6px;
color: #808695;
font-size: 12px;
line-height: 1.4;
}
</style> </style>
<template> <template>
<div class="search"> <div class="search">
@ -158,6 +165,30 @@
></InputNumber> ></InputNumber>
<span style="margin-left: 8px">%</span> <span style="margin-left: 8px">%</span>
</FormItem> </FormItem>
<FormItem v-if="showEditParentAgent" label="区域类型" prop="areaKind">
<RadioGroup v-model="form.areaKind">
<Radio :label="1">校园区域</Radio>
<Radio :label="2">社会区域</Radio>
</RadioGroup>
<div class="geo-tip">社会区域首页展示待成团并隐藏配送入口</div>
</FormItem>
<FormItem v-if="showEditParentAgent" label="区域中心">
<Map
id="shopAreaEditMap"
v-model="form.geoPosition"
text="地图选点"
placeholder="经度, 纬度"
/>
<div class="geo-tip">用于小程序定位自动绑定校区请用高德地图选点GCJ-02未设置则不参与自动匹配</div>
</FormItem>
<FormItem v-if="showEditParentAgent" label="匹配半径">
<Input
v-model="form.radiusMeters"
placeholder="请输入匹配半径"
style="width: 200px"
/>
<span style="margin-left: 8px"></span>
</FormItem>
<FormItem label="排序值" prop="sortOrder"> <FormItem label="排序值" prop="sortOrder">
<Tooltip <Tooltip
@ -235,7 +266,7 @@
:title="modalTitle" :title="modalTitle"
v-model="modalVisible" v-model="modalVisible"
:mask-closable="false" :mask-closable="false"
:width="500" :width="showAddParentAgent ? 620 : 500"
> >
<Form <Form
ref="formAdd" ref="formAdd"
@ -281,6 +312,30 @@
></InputNumber> ></InputNumber>
<span style="margin-left: 8px">%</span> <span style="margin-left: 8px">%</span>
</FormItem> </FormItem>
<FormItem v-if="showAddParentAgent" label="区域类型" prop="areaKind">
<RadioGroup v-model="formAdd.areaKind">
<Radio :label="1">校园区域</Radio>
<Radio :label="2">社会区域</Radio>
</RadioGroup>
<div class="geo-tip">社会区域首页展示待成团并隐藏配送入口</div>
</FormItem>
<FormItem v-if="showAddParentAgent" label="区域中心">
<Map
id="shopAreaAddMap"
v-model="formAdd.geoPosition"
text="地图选点"
placeholder="经度, 纬度"
/>
<div class="geo-tip">用于小程序定位自动绑定校区请用高德地图选点GCJ-02未设置则不参与自动匹配</div>
</FormItem>
<FormItem v-if="showAddParentAgent" label="匹配半径">
<Input
v-model="formAdd.radiusMeters"
placeholder="请输入匹配半径"
style="width: 200px"
/>
<span style="margin-left: 8px"></span>
</FormItem>
<FormItem label="排序值" prop="sortOrder"> <FormItem label="排序值" prop="sortOrder">
<Tooltip <Tooltip
trigger="hover" trigger="hover"
@ -397,8 +452,12 @@ import {
searchShopArea, searchShopArea,
} from "@/api/app"; } from "@/api/app";
import { getUserListData } from "@/api/index"; import { getUserListData } from "@/api/index";
import Map from "@/views/my-components/hiver/map";
export default { export default {
name: "shopArea", name: "shopArea",
components: {
Map,
},
data() { data() {
const validateCommissionRatio = (rule, value, callback) => { const validateCommissionRatio = (rule, value, callback) => {
if (value === "" || value == null || value === "null") { if (value === "" || value == null || value === "null") {
@ -436,6 +495,11 @@ export default {
parentAgentId: "", parentAgentId: "",
parentAgentName: "", parentAgentName: "",
commissionRatio: 0, commissionRatio: 0,
areaKind: 1,
centerLng: null,
centerLat: null,
radiusMeters: "",
geoPosition: "",
}, },
formAdd: {}, formAdd: {},
agentModalVisible: false, agentModalVisible: false,
@ -643,18 +707,87 @@ export default {
this.$set(target, "parentAgentId", ""); this.$set(target, "parentAgentId", "");
this.$set(target, "parentAgentName", ""); this.$set(target, "parentAgentName", "");
}, },
clearGeoValue(target) {
this.$set(target, "centerLng", null);
this.$set(target, "centerLat", null);
this.$set(target, "radiusMeters", "");
this.$set(target, "geoPosition", "");
},
buildGeoPosition(lng, lat) {
if (lng === "" || lng == null || lat === "" || lat == null) {
return "";
}
return `${lng}, ${lat}`;
},
applyGeoPosition(target) {
if (!this.isRootArea(target.parentId)) {
this.clearGeoValue(target);
return true;
}
const position = (target.geoPosition || "").trim();
if (!position) {
this.clearGeoValue(target);
return true;
}
const parts = position.split(",");
if (parts.length < 2) {
this.$Message.warning("区域中心坐标格式应为:经度, 纬度");
return false;
}
const lng = Number(parts[0].trim());
const lat = Number(parts[1].trim());
if (!Number.isFinite(lng) || !Number.isFinite(lat)) {
this.$Message.warning("区域中心坐标不合法");
return false;
}
if (lng < -180 || lng > 180 || lat < -90 || lat > 90) {
this.$Message.warning("区域中心坐标超出范围");
return false;
}
const radius = Number(target.radiusMeters);
if (!Number.isInteger(radius) || radius <= 0) {
this.$Message.warning("请设置有效的匹配半径(米)");
return false;
}
this.$set(target, "centerLng", lng);
this.$set(target, "centerLat", lat);
this.$set(target, "radiusMeters", radius);
return true;
},
normalizeParentAgent(target) { normalizeParentAgent(target) {
if (!this.isRootArea(target.parentId)) { if (!this.isRootArea(target.parentId)) {
this.clearParentAgentValue(target); this.clearParentAgentValue(target);
this.$delete(target, "commissionRatio"); this.$delete(target, "commissionRatio");
} else if ( this.clearGeoValue(target);
target.commissionRatio === "" || this.$set(target, "areaKind", 1);
target.commissionRatio == null || } else {
target.commissionRatio === "null" if (
target.commissionRatio === "" ||
target.commissionRatio == null ||
target.commissionRatio === "null"
) {
this.$set(target, "commissionRatio", 0);
} else {
this.$set(target, "commissionRatio", Number(target.commissionRatio));
}
const areaKind = Number(target.areaKind);
this.$set(target, "areaKind", areaKind === 2 ? 2 : 1);
}
},
syncGeoFromEntity(target) {
this.$set(
target,
"geoPosition",
this.buildGeoPosition(target.centerLng, target.centerLat)
);
if (
target.radiusMeters === "" ||
target.radiusMeters === "null" ||
target.radiusMeters == null
) { ) {
this.$set(target, "commissionRatio", 0); this.$set(target, "radiusMeters", "");
} else { } else {
this.$set(target, "commissionRatio", Number(target.commissionRatio)); this.$set(target, "radiusMeters", String(target.radiusMeters));
} }
}, },
init() { init() {
@ -783,6 +916,9 @@ export default {
) { ) {
this.$set(this.form, "commissionRatio", 0); this.$set(this.form, "commissionRatio", 0);
} }
const areaKind = Number(this.form.areaKind);
this.$set(this.form, "areaKind", areaKind === 2 ? 2 : 1);
this.syncGeoFromEntity(this.form);
} else { } else {
this.cancelEdit(); this.cancelEdit();
} }
@ -804,6 +940,11 @@ export default {
parentAgentId: "", parentAgentId: "",
parentAgentName: "", parentAgentName: "",
commissionRatio: 0, commissionRatio: 0,
areaKind: 1,
centerLng: null,
centerLat: null,
radiusMeters: "",
geoPosition: "",
}; };
this.editTitle = ""; this.editTitle = "";
}, },
@ -843,11 +984,19 @@ export default {
this.$Message.warning("请先点击选择要修改的区域"); this.$Message.warning("请先点击选择要修改的区域");
return; return;
} }
if (!this.applyGeoPosition(this.form)) {
return;
}
this.submitLoading = true; this.submitLoading = true;
delete this.form.mainHeaders; delete this.form.mainHeaders;
delete this.form.viceHeaders; delete this.form.viceHeaders;
this.normalizeParentAgent(this.form); this.normalizeParentAgent(this.form);
editShopArea(this.form).then((res) => { const payload = { ...this.form };
delete payload.geoPosition;
if (payload.centerLng == null) payload.centerLng = "";
if (payload.centerLat == null) payload.centerLat = "";
if (payload.radiusMeters == null) payload.radiusMeters = "";
editShopArea(payload).then((res) => {
this.submitLoading = false; this.submitLoading = false;
if (res.success) { if (res.success) {
this.editTitle = this.form.title; this.editTitle = this.form.title;
@ -863,9 +1012,17 @@ export default {
this.normalizeParentAgent(this.formAdd); this.normalizeParentAgent(this.formAdd);
this.$refs.formAdd.validate((valid) => { this.$refs.formAdd.validate((valid) => {
if (valid) { if (valid) {
if (!this.applyGeoPosition(this.formAdd)) {
return;
}
this.submitLoading = true; this.submitLoading = true;
this.normalizeParentAgent(this.formAdd); this.normalizeParentAgent(this.formAdd);
addShopArea(this.formAdd).then((res) => { const payload = { ...this.formAdd };
delete payload.geoPosition;
if (payload.centerLng == null) payload.centerLng = "";
if (payload.centerLat == null) payload.centerLat = "";
if (payload.radiusMeters == null) payload.radiusMeters = "";
addShopArea(payload).then((res) => {
this.submitLoading = false; this.submitLoading = false;
if (res.success) { if (res.success) {
this.$Message.success("添加成功"); this.$Message.success("添加成功");
@ -927,6 +1084,11 @@ export default {
parentAgentId: "", parentAgentId: "",
parentAgentName: "", parentAgentName: "",
commissionRatio: 0, commissionRatio: 0,
areaKind: 1,
centerLng: null,
centerLat: null,
radiusMeters: "",
geoPosition: "",
}; };
this.modalVisible = true; this.modalVisible = true;
}, },

676
src/views/my-components/hiver/map.vue

@ -50,12 +50,61 @@
<Icon type="ios-close" class="ivu-icon-ios-close" /> <Icon type="ios-close" class="ivu-icon-ios-close" />
</a> </a>
</div> </div>
<div :id="id" :style="{ width: '100%', height: modalHeight }"></div> <div class="amap-panel">
<div v-if="searchable" class="amap-search">
<Input
v-model="keyword"
search
enter-button="搜索"
placeholder="搜索校区/地点名称"
@on-search="searchPlace"
/>
<Button
v-if="locate"
class="amap-locate-btn"
size="small"
icon="md-locate"
:loading="locating"
@click="locateByAmap"
>定位到当前位置</Button
>
<ul v-if="searchResults.length" class="amap-search-list">
<li
v-for="(item, index) in searchResults"
:key="index"
@click="selectSearchResult(item)"
>
<div class="name">{{ item.name }}</div>
<div class="addr">{{ item.address || item.district || "" }}</div>
</li>
</ul>
</div>
<div
v-else-if="locate"
class="amap-locate-only"
>
<Button
size="small"
icon="md-locate"
:loading="locating"
@click="locateByAmap"
>定位到当前位置</Button
>
</div>
<div
:id="id"
class="amap-container"
:style="{ width: '100%', height: modalHeight }"
></div>
</div>
<div slot="footer"> <div slot="footer">
<Row align="middle" justify="space-between"> <Row align="middle" justify="space-between">
<Alert show-icon style="margin-bottom: 0" <div style="flex: 1; margin-right: 12px">
>当前选点坐标{{ currentValue }}</Alert <Alert show-icon style="margin-bottom: 0"
> >当前选点坐标{{ currentValue || "未选择" }}</Alert
>
<div class="locate-tip">坐标为高德 GCJ-02可搜索定位或点击地图选点</div>
</div>
<div> <div>
<Button type="text" @click="showModal = false">取消</Button> <Button type="text" @click="showModal = false">取消</Button>
<Button type="primary" @click="handelSubmit">确认提交</Button> <Button type="primary" @click="handelSubmit">确认提交</Button>
@ -65,9 +114,10 @@
</Modal> </Modal>
</div> </div>
</template> </template>
<script> <script>
import mapboxgl from "mapbox-gl"; import { loadAmap, getAmapLocation } from "@/libs/amapLoader";
import MapboxGeocoder from "@mapbox/mapbox-gl-geocoder";
export default { export default {
name: "map", name: "map",
props: { props: {
@ -91,10 +141,6 @@ export default {
type: Number, type: Number,
default: 900, default: 900,
}, },
pitch: {
type: Number,
default: 0
},
decimal: { decimal: {
type: Number, type: Number,
default: 6, default: 6,
@ -131,367 +177,266 @@ export default {
type: String, type: String,
default: "md-locate", default: "md-locate",
}, },
style: {
type: String,
default: "mapbox://styles/mapbox/streets-v11",
},
center: { center: {
type: Array, type: Array,
default: [116.35, 39.85], default: () => [116.397428, 39.90923],
}, },
zoom: { zoom: {
type: Number, type: Number,
default: 9, default: 12,
},
compact: {
type: Boolean,
default: true,
},
customAttribution: {
type: String,
default: "",
}, },
searchable: { searchable: {
type: Boolean, type: Boolean,
default: true, default: true,
}, },
changeStyle: {
type: Boolean,
default: true,
},
navigation: {
type: Boolean,
default: true,
},
locate: { locate: {
type: Boolean, type: Boolean,
default: true, default: true,
}, },
fullscreen: {
type: Boolean,
default: false,
},
building3D: {
type: Boolean,
default: true,
},
}, },
data() { data() {
return { return {
data: this.value, data: this.value,
mapbox: null, map: null,
marker: null,
placeSearch: null,
currentValue: "", currentValue: "",
showModal: false, showModal: false,
full: false, full: false,
modalHeight: "500px", modalHeight: "500px",
marker: null, keyword: "",
searchResults: [],
locating: false,
mapReady: false,
}; };
}, },
methods: { methods: {
init() { getAmapKey() {
if (!config.mapboxToken) { return (typeof config !== "undefined" && config.amapKey) || "";
return; },
getSecurityCode() {
return (typeof config !== "undefined" && config.amapSecurityJsCode) || "";
},
formatLngLat(lng, lat) {
let decimal = this.decimal;
if (decimal < 0) decimal = 0;
if (decimal > 14) decimal = 14;
return Number(lng).toFixed(decimal) + ", " + Number(lat).toFixed(decimal);
},
parsePosition(value) {
if (!value || !String(value).trim()) return null;
const parts = String(value).split(",");
if (parts.length < 2) return null;
const lng = Number(parts[0].trim());
const lat = Number(parts[1].trim());
if (!Number.isFinite(lng) || !Number.isFinite(lat)) return null;
return { lng, lat };
},
ensureMap() {
if (this.map) {
return Promise.resolve(this.map);
} }
mapboxgl.accessToken = config.mapboxToken; const key = this.getAmapKey();
this.mapbox = new mapboxgl.Map({ if (!key) {
container: this.id, return Promise.reject(new Error("未配置高德地图 Key"));
style: this.style,
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,
})
);
} }
// return loadAmap(key, this.getSecurityCode()).then((AMap) => {
class ChangeStyleControl { const centerPos = this.parsePosition(this.data) || {
getDefaultPosition() { lng: this.center[0],
const defaultPosition = "top-right"; lat: this.center[1],
return defaultPosition; };
} this.map = new AMap.Map(this.id, {
onAdd(map) { zoom: this.parsePosition(this.data) ? 16 : this.zoom,
this.map = map; center: [centerPos.lng, centerPos.lat],
this.controlContainer = document.createElement("div"); resizeEnable: true,
this.controlContainer.classList.add("mapboxgl-ctrl"); mapStyle: "amap://styles/normal",
this.controlContainer.classList.add("mapboxgl-ctrl-group"); });
this.mapStyleContainer = document.createElement("div"); this.map.on("click", (e) => {
this.styleButton = document.createElement("button"); if (!e || !e.lnglat) return;
this.styleButton.type = "button"; this.placeMarker(e.lnglat.getLng(), e.lnglat.getLat());
this.mapStyleContainer.classList.add("mapboxgl-style-list"); });
const defaultStyle = "街道地图"; return new Promise((resolve) => {
const styles = [ AMap.plugin(
{ title: "街道地图", uri: "mapbox://styles/mapbox/streets-v11" }, ["AMap.ToolBar", "AMap.Scale", "AMap.PlaceSearch", "AMap.Geolocation"],
{ () => {
title: "卫星街道", this.map.addControl(
uri: "mapbox://styles/mapbox/satellite-streets-v11", new AMap.ToolBar({
}, position: "RB",
{ title: "暗黑风格", uri: "mapbox://styles/mapbox/dark-v10" }, locate: false,
{ 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));
setTimeout(() => {
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"],
]);
}
}, 500);
this.mapStyleContainer.style.display = "none";
this.styleButton.style.display = "block";
const elms = this.mapStyleContainer.getElementsByClassName(
"active"
); );
while (elms[0]) { this.map.addControl(new AMap.Scale({ position: "LB" }));
elms[0].classList.remove("active"); this.placeSearch = new AMap.PlaceSearch({
} pageSize: 8,
srcElement.classList.add("active"); pageIndex: 1,
}); citylimit: false,
if (style.title === defaultStyle) { });
styleElement.classList.add("active"); this.mapReady = true;
resolve(this.map);
} }
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) => { placeMarker(lng, lat, zoom) {
if (!this.map || typeof window.AMap === "undefined") return;
if (this.marker) {
this.marker.setMap(null);
this.marker = null;
}
this.marker = new window.AMap.Marker({
position: [lng, lat],
draggable: this.draggable,
cursor: "move",
});
this.marker.setMap(this.map);
if (this.draggable) {
this.marker.on("dragend", (e) => {
const pos = e.lnglat || (e.target && e.target.getPosition());
if (!pos) return;
const nextLng = typeof pos.getLng === "function" ? pos.getLng() : pos.lng;
const nextLat = typeof pos.getLat === "function" ? pos.getLat() : pos.lat;
this.currentValue = this.formatLngLat(nextLng, nextLat);
this.$emit("on-click", this.currentValue);
});
}
this.currentValue = this.formatLngLat(lng, lat);
this.$emit("on-click", this.currentValue);
this.map.setZoomAndCenter(zoom || Math.max(this.map.getZoom(), 16), [
lng,
lat,
]);
},
syncMarkerFromValue() {
const pos = this.parsePosition(this.data);
if (pos) {
this.currentValue = this.data;
this.placeMarker(pos.lng, pos.lat, 16);
} else {
this.currentValue = "";
if (this.marker) { if (this.marker) {
this.marker.remove(); this.marker.setMap(null);
} this.marker = null;
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) { if (this.map) {
this.decimal = 14; this.map.setZoomAndCenter(this.zoom, this.center);
} }
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() { searchPlace() {
if (this.data) { const keyword = (this.keyword || "").trim();
try { if (!keyword) {
let lngLat = this.data.split(","); this.searchResults = [];
let lng = lngLat[0].trim(); return;
let lat = lngLat[1].trim(); }
this.currentValue = this.data; this.ensureMap()
if (this.marker) { .then(() => {
this.marker.remove(); if (!this.placeSearch) {
this.$Message.warning("搜索组件未就绪,请稍后重试");
return;
} }
this.marker = new mapboxgl.Marker({ this.placeSearch.search(keyword, (status, result) => {
offset: [0, -25], if (status === "complete" && result && result.poiList) {
draggable: true, this.searchResults = result.poiList.pois || [];
}) if (!this.searchResults.length) {
.setLngLat([lng, lat]) this.$Message.info("未找到相关地点");
.addTo(this.mapbox); }
this.mapbox.flyTo({ } else {
center: [lng, lat], this.searchResults = [];
essential: true, this.$Message.warning("搜索失败,请换个关键词试试");
}
}); });
} catch (error) { })
this.$Message.error("您输入的坐标不合法"); .catch((err) => {
} this.$Message.error((err && err.message) || "地图初始化失败");
} else {
this.currentValue = "";
if (this.marker) {
this.marker.remove();
}
this.mapbox.flyTo({
center: this.center,
zoom: 9,
essential: true,
}); });
},
selectSearchResult(item) {
if (!item || !item.location) return;
const lng = item.location.lng;
const lat = item.location.lat;
this.placeMarker(lng, lat, 17);
this.searchResults = [];
this.keyword = item.name || this.keyword;
},
locateByAmap() {
if (!this.getAmapKey()) {
this.$Message.warning("请先在 config.js 配置高德地图 Key");
return;
} }
this.locating = true;
const loading = this.$Message.loading({
content: "正在定位...",
duration: 0,
});
this.ensureMap()
.then(() => getAmapLocation(this.getAmapKey(), this.getSecurityCode()))
.then((pos) => {
loading();
this.locating = false;
this.placeMarker(pos.lng, pos.lat, 17);
if (pos.accuracy && pos.accuracy > 1000) {
this.$Message.warning(
`定位成功,精度约 ${Math.round(pos.accuracy)} 米,请核对后确认`
);
} else {
this.$Message.success(
pos.address ? `定位成功:${pos.address}` : "定位成功"
);
}
})
.catch((err) => {
loading();
this.locating = false;
this.$Message.error(
(err && err.message) || "定位失败,请搜索或手动点击地图选点"
);
});
}, },
handleChange(v) { handleChange() {
this.$emit("input", this.data); this.$emit("input", this.data);
this.$emit("on-change", this.data); this.$emit("on-change", this.data);
}, },
setData(value) { setData(value) {
if (!this.mapbox) {
this.init();
}
if (value != this.data) { if (value != this.data) {
this.data = value; this.data = value;
this.$emit("input", this.data); this.$emit("input", this.data);
this.$emit("on-change", this.data); this.$emit("on-change", this.data);
} }
if (this.showModal && this.map) {
this.syncMarkerFromValue();
}
}, },
handleFull() { handleFull() {
this.full = !this.full; this.full = !this.full;
if (this.full) { this.modalHeight = this.full ? "100%" : "500px";
this.modalHeight = "100%"; this.$nextTick(() => {
} else { if (this.map) {
this.modalHeight = "500px"; this.map.resize();
} }
setTimeout(() => { });
this.mapbox.resize();
}, 10);
}, },
handleShow() { handleShow() {
if (!config.mapboxToken) { if (!this.getAmapKey()) {
this.$Message.warning("请先配置Mapbox地图的accessToken"); this.$Message.warning("请先在 config.js 配置高德地图 Key");
return; return;
} }
this.showModal = true; this.showModal = true;
}, },
changeModal(v) { changeModal(v) {
if (v) { if (v) {
setTimeout(() => { this.$nextTick(() => {
this.mapbox.resize(); this.ensureMap()
this.flyTo(); .then(() => {
}, 10); this.map.resize();
this.syncMarkerFromValue();
})
.catch((err) => {
this.$Message.error((err && err.message) || "地图初始化失败");
});
});
} else {
this.searchResults = [];
} }
}, },
handelSubmit() { handelSubmit() {
@ -502,9 +447,13 @@ export default {
}, },
}, },
beforeDestroy() { beforeDestroy() {
// API if (this.marker) {
if (this.mapbox != null) { this.marker.setMap(null);
this.mapbox.remove(); this.marker = null;
}
if (this.map) {
this.map.destroy();
this.map = null;
} }
}, },
watch: { watch: {
@ -512,49 +461,76 @@ export default {
this.setData(val); this.setData(val);
}, },
}, },
mounted() {
this.init();
},
}; };
</script> </script>
<style lang="less">
.mapboxgl-style-list {
display: none;
}
.mapboxgl-ctrl-group .mapboxgl-style-list button { <style lang="less">
background: none; .map-modal {
border: none; .ivu-modal-body {
cursor: pointer; padding: 0;
display: block; }
font-size: 14px;
padding: 8px 8px 6px;
text-align: right;
width: 100%;
height: auto;
} }
.amap-panel {
.mapboxgl-style-list button.active { position: relative;
font-weight: bold;
} }
.amap-search {
.mapboxgl-style-list button:hover { position: absolute;
background-color: rgba(0, 0, 0, 0.05); top: 12px;
left: 12px;
z-index: 120;
width: 320px;
} }
.amap-locate-btn {
.mapboxgl-style-list button + button { margin-top: 8px;
border-top: 1px solid #ddd; background: #fff;
} }
.amap-locate-only {
.mapboxgl-style-switcher { position: absolute;
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==); top: 12px;
background-position: center; left: 12px;
background-repeat: no-repeat; z-index: 120;
background-size: 70%;
} }
.map-modal { .amap-search-list {
.ivu-modal-body { margin: 8px 0 0;
padding: 0px; padding: 0;
list-style: none;
max-height: 240px;
overflow: auto;
background: #fff;
border-radius: 4px;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.15);
li {
padding: 8px 12px;
cursor: pointer;
border-bottom: 1px solid #f0f0f0;
&:hover {
background: #f5f7fa;
}
.name {
color: #17233d;
font-size: 13px;
}
.addr {
margin-top: 2px;
color: #808695;
font-size: 12px;
}
} }
} }
</style> .amap-container {
min-height: 360px;
}
.locate-tip {
margin-top: 6px;
color: #808695;
font-size: 12px;
line-height: 1.4;
}
.modal-fullscreen {
position: absolute;
right: 42px;
top: 10px;
font-size: 18px;
color: #999;
}
</style>

581
src/views/my-components/hiver/mapLocate.vue

@ -1,583 +1,12 @@
<template> <template>
<div> <Map v-bind="$attrs" v-on="$listeners" />
<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> </template>
<script> <script>
import mapboxgl from "mapbox-gl"; import Map from "@/views/my-components/hiver/map";
import MapboxGeocoder from "@mapbox/mapbox-gl-geocoder";
import '@mapbox/mapbox-gl-geocoder/dist/mapbox-gl-geocoder.css';
export default { export default {
name: "mapLocate", name: "mapLocate",
props: { components: { Map },
id: { inheritAttrs: false,
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> </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>
Loading…
Cancel
Save