2025-09-02 22:55:31 +08:00
|
|
|
|
<template>
|
|
|
|
|
|
<div class="app-container">
|
|
|
|
|
|
<el-card class="mediaCard">
|
|
|
|
|
|
<el-row :gutter="10" class="my_row" style="padding: 0 20px;">
|
|
|
|
|
|
<el-col :span="12">
|
|
|
|
|
|
<el-form :model="queryParams" ref="queryRef" :inline="true" class="searchInputForm">
|
|
|
|
|
|
<el-form-item label="" prop="templateName">
|
2025-09-12 15:20:28 +08:00
|
|
|
|
<!-- <el-input v-model="queryParams.keyword" placeholder="请输入媒体名称/媒体编号/关键字" :prefix-icon="Search"
|
|
|
|
|
|
style="width: 400px;" /> -->
|
|
|
|
|
|
<el-select class="filterSelect" v-model="queryParams.keyword" filterable remote
|
|
|
|
|
|
reserve-keyword :remote-method="getLocaleListList" :loading="selectLoading"
|
|
|
|
|
|
@change="currentSelect" placeholder="请输入关键字" remote-show-suffix clearable
|
|
|
|
|
|
style="width: 400px">
|
|
|
|
|
|
<el-option v-for="item in localeList" :key="item.id" :label="item.name" :value="item"
|
|
|
|
|
|
class="one-text">
|
|
|
|
|
|
<div style="height: 24px; line-height: 24px;font-size: 16px;">{{ item.name }}</div>
|
|
|
|
|
|
<div style="color: #8492a6; font-size: 12px;height: 18px; line-height: 18px;">{{
|
|
|
|
|
|
item.address
|
2025-09-12 17:20:26 +08:00
|
|
|
|
}}</div>
|
2025-09-12 15:20:28 +08:00
|
|
|
|
</el-option>
|
|
|
|
|
|
</el-select>
|
2025-09-02 22:55:31 +08:00
|
|
|
|
</el-form-item>
|
|
|
|
|
|
</el-form>
|
|
|
|
|
|
</el-col>
|
|
|
|
|
|
<el-col :span="12" style="text-align: right;">
|
|
|
|
|
|
<el-dropdown placement="bottom-start">
|
|
|
|
|
|
<el-button type="primary" class="mediaMapBtn">距离:{{ distanceLable }}</el-button>
|
|
|
|
|
|
<template #dropdown>
|
|
|
|
|
|
<el-dropdown-menu style="min-width: 122px;">
|
|
|
|
|
|
<template v-for="item in distanceLableArray" :key="item.value">
|
|
|
|
|
|
<el-dropdown-item
|
|
|
|
|
|
:class="activeLableIndex === item.value ? 'distanceItemActive' : 'distanceItem'"
|
|
|
|
|
|
@click="handleChangeDistance(item)">{{ item.label }}</el-dropdown-item>
|
|
|
|
|
|
</template>
|
|
|
|
|
|
</el-dropdown-menu>
|
|
|
|
|
|
</template>
|
|
|
|
|
|
</el-dropdown>
|
2025-09-11 22:54:53 +08:00
|
|
|
|
<div class="clearBtn" @click="resetQuery">清除条件</div>
|
2025-09-02 22:55:31 +08:00
|
|
|
|
</el-col>
|
|
|
|
|
|
</el-row>
|
|
|
|
|
|
|
|
|
|
|
|
<div id="mapContainer" class="mediaMapContainer"></div>
|
|
|
|
|
|
</el-card>
|
|
|
|
|
|
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</template>
|
|
|
|
|
|
|
|
|
|
|
|
<script setup name="Post">
|
2025-09-03 20:21:01 +08:00
|
|
|
|
import { onMounted, onUnmounted, ref } from 'vue';
|
2025-09-02 22:55:31 +08:00
|
|
|
|
import { Search } from '@element-plus/icons-vue'
|
|
|
|
|
|
import AMapLoader from "@amap/amap-jsapi-loader"; // 引入地图服务
|
|
|
|
|
|
import { useBackgroundStore } from '@/store/modules/background'
|
|
|
|
|
|
import otherbg from '@/assets/images/otherbg.png'
|
|
|
|
|
|
|
2025-09-11 22:54:53 +08:00
|
|
|
|
import { mediaByMap } from "@/api/mediaLibrary"
|
|
|
|
|
|
|
2025-09-02 22:55:31 +08:00
|
|
|
|
const bgStore = useBackgroundStore()
|
|
|
|
|
|
const { proxy } = getCurrentInstance()
|
|
|
|
|
|
|
2025-09-09 14:52:18 +08:00
|
|
|
|
const { apiKey, secretKey } = window._CONFIG
|
|
|
|
|
|
|
2025-09-12 15:20:28 +08:00
|
|
|
|
// map实例
|
|
|
|
|
|
const mapInstance = ref(null)
|
|
|
|
|
|
const massMarks = ref(null)
|
|
|
|
|
|
const centerMarker = ref(null)
|
|
|
|
|
|
// 当前地图模式(2D/3D)
|
|
|
|
|
|
const mapMode = ref('2D')
|
|
|
|
|
|
// 是否全屏状态
|
|
|
|
|
|
const isFullscreen = ref(false)
|
|
|
|
|
|
const placeSearch = ref(null)
|
|
|
|
|
|
|
|
|
|
|
|
// 热区圆实例
|
|
|
|
|
|
const circle = ref(null);
|
|
|
|
|
|
const circleRadius = ref(2000); // 实际半径(米)
|
|
|
|
|
|
const circleHandle = ref(null); // 拖拽手柄
|
|
|
|
|
|
|
|
|
|
|
|
// 选择地点展示框
|
|
|
|
|
|
const selectLoading = ref(false)
|
|
|
|
|
|
const localeList = ref([])
|
2025-09-12 17:20:26 +08:00
|
|
|
|
const selectConfig = ref(null)
|
2025-09-12 15:20:28 +08:00
|
|
|
|
|
2025-09-02 22:55:31 +08:00
|
|
|
|
// 距离显示文本
|
|
|
|
|
|
const distanceLable = ref('请选择')
|
2025-09-11 22:54:53 +08:00
|
|
|
|
// 选择的距离值
|
|
|
|
|
|
const activeLableIndex = ref(null)
|
2025-09-02 22:55:31 +08:00
|
|
|
|
const distanceLableArray = ref([
|
2025-09-11 22:54:53 +08:00
|
|
|
|
{ label: '1公里内', value: 1000 },
|
|
|
|
|
|
{ label: '2公里内', value: 2000 },
|
|
|
|
|
|
{ label: '3公里内', value: 3000 },
|
|
|
|
|
|
{ label: '5公里内', value: 5000 }
|
2025-09-02 22:55:31 +08:00
|
|
|
|
])
|
|
|
|
|
|
|
2025-09-11 22:54:53 +08:00
|
|
|
|
const points = ref([])
|
2025-09-02 22:55:31 +08:00
|
|
|
|
|
|
|
|
|
|
const data = reactive({
|
|
|
|
|
|
queryParams: {
|
|
|
|
|
|
keyword: undefined,
|
2025-09-11 22:54:53 +08:00
|
|
|
|
x: undefined, //中心点经度
|
|
|
|
|
|
y: undefined, //中心点纬度
|
2025-09-02 22:55:31 +08:00
|
|
|
|
distance: undefined,
|
|
|
|
|
|
}
|
|
|
|
|
|
})
|
|
|
|
|
|
const { queryParams } = toRefs(data)
|
|
|
|
|
|
|
|
|
|
|
|
/** 清除条件操作 */
|
|
|
|
|
|
const resetQuery = () => {
|
2025-09-11 22:54:53 +08:00
|
|
|
|
queryParams.value = {
|
|
|
|
|
|
keyword: undefined,
|
|
|
|
|
|
x: undefined, //中心点经度
|
|
|
|
|
|
y: undefined, //中心点纬度
|
|
|
|
|
|
distance: undefined,
|
|
|
|
|
|
}
|
2025-09-12 15:20:28 +08:00
|
|
|
|
// 距离显示文本
|
|
|
|
|
|
distanceLable.value = '请选择'
|
|
|
|
|
|
// 选择的距离值
|
|
|
|
|
|
activeLableIndex.value = null
|
|
|
|
|
|
circleRadius.value = 2000; // 实际半径(米)
|
2025-09-12 17:20:26 +08:00
|
|
|
|
selectConfig.value = null
|
|
|
|
|
|
|
2025-09-12 15:20:28 +08:00
|
|
|
|
// 清除现有的圆形和标记
|
|
|
|
|
|
if (centerMarker.value) {
|
|
|
|
|
|
mapInstance.value.remove(centerMarker.value);
|
|
|
|
|
|
centerMarker.value = null;
|
|
|
|
|
|
}
|
|
|
|
|
|
if (circle.value) {
|
|
|
|
|
|
mapInstance.value.remove(circle.value);
|
|
|
|
|
|
circle.value = null;
|
|
|
|
|
|
}
|
|
|
|
|
|
if (circleHandle.value) {
|
|
|
|
|
|
mapInstance.value.remove(circleHandle.value);
|
|
|
|
|
|
circleHandle.value = null;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 重新渲染数据点
|
|
|
|
|
|
renderMassMarks()
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// 调用API获取地点
|
|
|
|
|
|
const getLocaleListList = (searchValue) => {
|
|
|
|
|
|
selectLoading.value = true
|
|
|
|
|
|
if (searchValue !== "") {
|
|
|
|
|
|
placeSearch.value.search(searchValue, function (status, result) {
|
|
|
|
|
|
// 查询成功时,result即对应匹配的POI信息
|
|
|
|
|
|
console.log(result.poiList, result.poiList.pois, result.poiList.pois?.length)
|
|
|
|
|
|
if (result.poiList.pois?.length) {
|
|
|
|
|
|
localeList.value = result.poiList?.pois
|
|
|
|
|
|
}
|
|
|
|
|
|
selectLoading.value = false
|
|
|
|
|
|
});
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
// 中心点选择
|
|
|
|
|
|
const currentSelect = (val) => {
|
|
|
|
|
|
console.log('val', val)
|
2025-09-12 17:20:26 +08:00
|
|
|
|
selectConfig.value = val
|
2025-09-12 15:20:28 +08:00
|
|
|
|
queryParams.value.keyword = val.name
|
|
|
|
|
|
queryParams.value.x = val.location.lng
|
|
|
|
|
|
queryParams.value.y = val.location.lat
|
|
|
|
|
|
queryParams.value.distance = circleRadius.value
|
|
|
|
|
|
|
|
|
|
|
|
// 距离显示文本
|
2025-09-12 15:27:20 +08:00
|
|
|
|
distanceLable.value = circleRadius.value == 2000 ? '2000米' : circleRadius.value + '米'
|
2025-09-12 15:20:28 +08:00
|
|
|
|
// 选择的距离值
|
2025-09-12 15:27:20 +08:00
|
|
|
|
activeLableIndex.value = circleRadius.value == 2000 ? 2000 : circleRadius.value
|
2025-09-12 15:20:28 +08:00
|
|
|
|
|
|
|
|
|
|
// 清除现有的圆形和标记
|
|
|
|
|
|
if (circle.value) {
|
|
|
|
|
|
mapInstance.value.remove(circle.value);
|
|
|
|
|
|
circle.value = null;
|
|
|
|
|
|
}
|
|
|
|
|
|
if (circleHandle.value) {
|
|
|
|
|
|
mapInstance.value.remove(circleHandle.value);
|
|
|
|
|
|
circleHandle.value = null;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
createCircle(val)
|
|
|
|
|
|
addCenterMark(val)
|
|
|
|
|
|
}
|
|
|
|
|
|
// 创建中心点标记
|
|
|
|
|
|
const addCenterMark = (val) => {
|
|
|
|
|
|
if (centerMarker.value) mapInstance.value.remove(centerMarker.value);
|
|
|
|
|
|
centerMarker.value = new AMap.Marker({
|
|
|
|
|
|
position: [val.location.lng, val.location.lat],
|
|
|
|
|
|
title: val.name,
|
|
|
|
|
|
zIndex: 100,
|
|
|
|
|
|
draggable: false, // 是否可以拖拽
|
|
|
|
|
|
cursor: 'move'
|
|
|
|
|
|
});
|
|
|
|
|
|
// 将点添加到地图
|
|
|
|
|
|
mapInstance.value.add(centerMarker.value);
|
|
|
|
|
|
mapInstance.value.setFitView();
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 创建圆形热区
|
|
|
|
|
|
const createCircle = (val) => {
|
|
|
|
|
|
// 创建圆形覆盖物
|
|
|
|
|
|
circle.value = new AMap.Circle({
|
|
|
|
|
|
center: [val.location.lng, val.location.lat],
|
|
|
|
|
|
radius: circleRadius.value,
|
|
|
|
|
|
strokeColor: '#4e54c8',
|
|
|
|
|
|
strokeOpacity: 0.8,
|
|
|
|
|
|
strokeWeight: 2,
|
|
|
|
|
|
fillColor: '#4e54c8',
|
|
|
|
|
|
fillOpacity: 0.15,
|
|
|
|
|
|
zIndex: 100,
|
|
|
|
|
|
bubble: true,
|
|
|
|
|
|
cursor: 'move',
|
|
|
|
|
|
draggable: false // 是否可以拖拽
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
// 将圆形添加到地图
|
|
|
|
|
|
mapInstance.value.add(circle.value);
|
|
|
|
|
|
|
|
|
|
|
|
// 创建圆形边缘拖拽手柄
|
|
|
|
|
|
createCircleHandle();
|
|
|
|
|
|
|
|
|
|
|
|
// 渲染数据点
|
|
|
|
|
|
renderMassMarks()
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// 创建圆形边缘拖拽手柄
|
|
|
|
|
|
const createCircleHandle = () => {
|
|
|
|
|
|
// 计算手柄的初始位置(圆形右侧边缘)
|
|
|
|
|
|
const center = circle.value.getCenter();
|
|
|
|
|
|
const radius = circle.value.getRadius();
|
|
|
|
|
|
// 更精确地计算手柄位置
|
|
|
|
|
|
const handlePosition = new AMap.LngLat(
|
|
|
|
|
|
center.lng + radius / (111320 * Math.cos(center.lat * Math.PI / 180)),
|
|
|
|
|
|
center.lat
|
|
|
|
|
|
);
|
|
|
|
|
|
console.log('手柄', handlePosition)
|
|
|
|
|
|
// 创建手柄标记
|
|
|
|
|
|
circleHandle.value = new AMap.Marker({
|
|
|
|
|
|
position: [handlePosition.lng, handlePosition.lat],
|
|
|
|
|
|
icon: new AMap.Icon({
|
|
|
|
|
|
size: new AMap.Size(20, 20),
|
|
|
|
|
|
image: '//a.amap.com/jsapi_demos/static/demo-center/icons/poi-marker-default.png',
|
|
|
|
|
|
imageSize: new AMap.Size(20, 20)
|
|
|
|
|
|
}),
|
|
|
|
|
|
offset: new AMap.Pixel(-10, -10),
|
|
|
|
|
|
zIndex: 101,
|
|
|
|
|
|
draggable: true,
|
|
|
|
|
|
cursor: 'pointer'
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
mapInstance.value.add(circleHandle.value);
|
|
|
|
|
|
|
|
|
|
|
|
// 监听手柄拖拽事件
|
|
|
|
|
|
circleHandle.value.on('dragging', (e) => {
|
|
|
|
|
|
const handlePos = e.lnglat;
|
|
|
|
|
|
const center = circle.value.getCenter();
|
|
|
|
|
|
const newRadius = Math.round(calculateDistance(center, handlePos));
|
|
|
|
|
|
circleRadius.value = newRadius;
|
|
|
|
|
|
distanceLable.value = `${Math.round(circleRadius.value)}米`;
|
|
|
|
|
|
queryParams.value.distance = circleRadius.value // 更改查询条件距离
|
|
|
|
|
|
if (circleRadius.value !== 1000 && circleRadius.value !== 2000 && circleRadius.value !== 3000 && circleRadius.value !== 5000) activeLableIndex.value = null
|
|
|
|
|
|
else activeLableIndex.value = circleRadius.value
|
|
|
|
|
|
circle.value.setRadius(newRadius);
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
circleHandle.value.on('dragend', () => {
|
|
|
|
|
|
// 拖拽结束后更新手柄位置
|
|
|
|
|
|
updateCircleHandlePosition();
|
|
|
|
|
|
// 重新渲染数据点
|
|
|
|
|
|
renderMassMarks()
|
|
|
|
|
|
});
|
2025-09-02 22:55:31 +08:00
|
|
|
|
}
|
2025-09-12 15:20:28 +08:00
|
|
|
|
// 更新圆形拖拽手柄位置
|
|
|
|
|
|
const updateCircleHandlePosition = () => {
|
|
|
|
|
|
if (!circle.value || !circleHandle.value) return;
|
|
|
|
|
|
|
|
|
|
|
|
const center = circle.value.getCenter();
|
|
|
|
|
|
const radius = circle.value.getRadius();
|
|
|
|
|
|
|
|
|
|
|
|
const handlePosition = new AMap.LngLat(
|
|
|
|
|
|
center.lng + radius / (111320 * Math.cos(center.lat * Math.PI / 180)),
|
|
|
|
|
|
center.lat
|
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
|
|
circleHandle.value.setPosition(handlePosition);
|
|
|
|
|
|
};
|
|
|
|
|
|
// 计算两点之间的距离(米)
|
|
|
|
|
|
const calculateDistance = (point1, point2) => {
|
|
|
|
|
|
const lng1 = point1.lng;
|
|
|
|
|
|
const lat1 = point1.lat;
|
|
|
|
|
|
const lng2 = point2.lng;
|
|
|
|
|
|
const lat2 = point2.lat;
|
|
|
|
|
|
|
|
|
|
|
|
const radLat1 = lat1 * Math.PI / 180.0;
|
|
|
|
|
|
const radLat2 = lat2 * Math.PI / 180.0;
|
|
|
|
|
|
const a = radLat1 - radLat2;
|
|
|
|
|
|
const b = lng1 * Math.PI / 180.0 - lng2 * Math.PI / 180.0;
|
|
|
|
|
|
|
|
|
|
|
|
let distance = 2 * Math.asin(Math.sqrt(Math.pow(Math.sin(a / 2), 2) +
|
|
|
|
|
|
Math.cos(radLat1) * Math.cos(radLat2) * Math.pow(Math.sin(b / 2), 2)));
|
|
|
|
|
|
distance = distance * 6378.137;
|
|
|
|
|
|
distance = Math.round(distance * 10000) / 10;
|
|
|
|
|
|
|
|
|
|
|
|
return distance;
|
|
|
|
|
|
};
|
2025-09-02 22:55:31 +08:00
|
|
|
|
|
|
|
|
|
|
// 选择距离
|
|
|
|
|
|
const handleChangeDistance = (itemDistance) => {
|
|
|
|
|
|
queryParams.value.distance = itemDistance.value
|
|
|
|
|
|
activeLableIndex.value = itemDistance.value
|
2025-09-12 15:20:28 +08:00
|
|
|
|
distanceLable.value = itemDistance.value + '米'
|
|
|
|
|
|
|
|
|
|
|
|
// 更新热区
|
|
|
|
|
|
circleRadius.value = itemDistance.value;
|
|
|
|
|
|
circle.value.setRadius(itemDistance.value);
|
|
|
|
|
|
updateCircleHandlePosition();
|
|
|
|
|
|
|
|
|
|
|
|
// 重新渲染数据点
|
|
|
|
|
|
renderMassMarks()
|
2025-09-02 22:55:31 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2025-09-12 15:20:28 +08:00
|
|
|
|
|
2025-09-02 22:55:31 +08:00
|
|
|
|
// 初始化地图
|
|
|
|
|
|
const loadMap = () => {
|
|
|
|
|
|
return new Promise((resolve, reject) => {
|
|
|
|
|
|
// 设置安全密钥
|
|
|
|
|
|
window._AMapSecurityConfig = {
|
2025-09-09 14:52:18 +08:00
|
|
|
|
securityJsCode: secretKey
|
2025-09-02 22:55:31 +08:00
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
AMapLoader.load({
|
2025-09-09 14:52:18 +08:00
|
|
|
|
key: apiKey,
|
2025-09-12 15:20:28 +08:00
|
|
|
|
plugins: ["AMap.PlaceSearch"],
|
2025-09-09 14:52:18 +08:00
|
|
|
|
AMapUI: {
|
2025-09-02 22:55:31 +08:00
|
|
|
|
plugins: []
|
|
|
|
|
|
}
|
|
|
|
|
|
}).then(AMap => {
|
|
|
|
|
|
// 根据当前模式设置初始参数
|
|
|
|
|
|
const initialPitch = mapMode.value === '3D' ? 65 : 0;
|
|
|
|
|
|
const initialRotation = mapMode.value === '3D' ? -15 : 0;
|
|
|
|
|
|
|
|
|
|
|
|
mapInstance.value = new AMap.Map("mapContainer", {
|
|
|
|
|
|
resizeEnable: true,
|
2025-09-12 15:20:28 +08:00
|
|
|
|
// center: [116.397428, 39.90923], // 默认中心点
|
2025-09-02 22:55:31 +08:00
|
|
|
|
pitch: initialPitch, // 倾斜角度决定2D/3D模式
|
|
|
|
|
|
rotation: initialRotation, // 旋转角度
|
2025-09-12 15:20:28 +08:00
|
|
|
|
// buildingAnimation: true,
|
|
|
|
|
|
// expandZoomRange: true,
|
|
|
|
|
|
zoom: 16,
|
|
|
|
|
|
zooms: [3, 16],
|
2025-09-02 22:55:31 +08:00
|
|
|
|
viewMode: mapMode.value // 启用3D视图
|
|
|
|
|
|
});
|
|
|
|
|
|
|
2025-09-12 15:20:28 +08:00
|
|
|
|
// // 先添加基本控件
|
|
|
|
|
|
mapInstance.value.setZoom(16);
|
|
|
|
|
|
|
|
|
|
|
|
placeSearch.value = new AMap.PlaceSearch({
|
|
|
|
|
|
// city 指定搜索所在城市,支持传入格式有:城市名、citycode和adcode
|
|
|
|
|
|
city: '全国'
|
|
|
|
|
|
})
|
2025-09-02 22:55:31 +08:00
|
|
|
|
|
|
|
|
|
|
// 添加自定义控件容器
|
|
|
|
|
|
addCustomControls(AMap);
|
|
|
|
|
|
|
|
|
|
|
|
// 地图加载完成后隐藏Logo
|
|
|
|
|
|
setTimeout(() => {
|
|
|
|
|
|
hideAmapLogo();
|
|
|
|
|
|
}, 1000);
|
|
|
|
|
|
|
|
|
|
|
|
// 监听地图渲染完成事件
|
|
|
|
|
|
mapInstance.value.on('render', hideAmapLogo);
|
|
|
|
|
|
|
2025-09-12 15:20:28 +08:00
|
|
|
|
// 添加缩放变化监听
|
|
|
|
|
|
mapInstance.value.on('zoomchange', handleZoomChange);
|
2025-09-12 17:20:26 +08:00
|
|
|
|
if (selectConfig.value !== null) {
|
|
|
|
|
|
currentSelect(selectConfig.value)
|
|
|
|
|
|
} else {
|
|
|
|
|
|
// 在地图完全加载后执行点数据处理
|
|
|
|
|
|
renderMassMarks();
|
|
|
|
|
|
}
|
2025-09-02 22:55:31 +08:00
|
|
|
|
|
|
|
|
|
|
resolve();
|
|
|
|
|
|
|
|
|
|
|
|
}).catch(e => {
|
|
|
|
|
|
console.log(e, "高德地图加载失败");
|
|
|
|
|
|
reject(e);
|
|
|
|
|
|
});
|
|
|
|
|
|
});
|
|
|
|
|
|
}
|
2025-09-12 15:20:28 +08:00
|
|
|
|
// 处理地图缩放事件
|
|
|
|
|
|
const handleZoomChange = () => {
|
|
|
|
|
|
const currentZoomLevel = mapInstance.value.getZoom();
|
|
|
|
|
|
// 如果缩放级别超过16,自动调整回16
|
|
|
|
|
|
if (currentZoomLevel > 16) {
|
|
|
|
|
|
mapInstance.value.setZoom(16);
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
2025-09-02 22:55:31 +08:00
|
|
|
|
// 添加自定义控件
|
|
|
|
|
|
const addCustomControls = (AMap) => {
|
|
|
|
|
|
// 创建控件容器
|
|
|
|
|
|
const controlContainer = document.createElement('div');
|
|
|
|
|
|
controlContainer.className = 'custom-map-controls';
|
|
|
|
|
|
|
|
|
|
|
|
// 2D/3D切换按钮
|
|
|
|
|
|
const toggle2D3DBtn = document.createElement('div');
|
|
|
|
|
|
toggle2D3DBtn.className = mapMode.value === '2D' ? 'map-control-btn map-control-3dbtn' : 'map-control-btn map-control-2dbtn';
|
|
|
|
|
|
toggle2D3DBtn.onclick = toggle2D3DMode;
|
|
|
|
|
|
|
|
|
|
|
|
// 全屏按钮
|
|
|
|
|
|
const fullscreenBtn = document.createElement('div');
|
|
|
|
|
|
fullscreenBtn.className = isFullscreen.value === true ? 'map-control-btn map-full-screen' : 'map-control-btn map-nofull-screen';
|
|
|
|
|
|
fullscreenBtn.onclick = toggleFullscreen;
|
|
|
|
|
|
|
|
|
|
|
|
controlContainer.appendChild(toggle2D3DBtn);
|
|
|
|
|
|
controlContainer.appendChild(fullscreenBtn);
|
|
|
|
|
|
|
|
|
|
|
|
// 添加到地图容器
|
|
|
|
|
|
document.getElementById('mapContainer').appendChild(controlContainer);
|
|
|
|
|
|
}
|
|
|
|
|
|
// 切换2D/3D模式
|
|
|
|
|
|
const toggle2D3DMode = () => {
|
|
|
|
|
|
if (!mapInstance.value) return;
|
|
|
|
|
|
|
|
|
|
|
|
const currentPitch = mapInstance.value.getPitch();
|
|
|
|
|
|
|
|
|
|
|
|
if (currentPitch === 0) {
|
|
|
|
|
|
// 切换到3D模式
|
|
|
|
|
|
mapInstance.value.setPitch(65);
|
|
|
|
|
|
mapInstance.value.setRotation(-15);
|
|
|
|
|
|
mapMode.value = '3D';
|
|
|
|
|
|
loadMap()
|
|
|
|
|
|
} else {
|
|
|
|
|
|
// 切换到2D模式
|
|
|
|
|
|
mapInstance.value.setPitch(0);
|
|
|
|
|
|
mapInstance.value.setRotation(0);
|
|
|
|
|
|
mapMode.value = '2D';
|
|
|
|
|
|
loadMap()
|
|
|
|
|
|
}
|
2025-09-12 17:20:26 +08:00
|
|
|
|
|
2025-09-02 22:55:31 +08:00
|
|
|
|
}
|
|
|
|
|
|
// 切换全屏模式
|
|
|
|
|
|
const toggleFullscreen = () => {
|
|
|
|
|
|
const mapContainer = document.getElementById('mapContainer');
|
|
|
|
|
|
|
|
|
|
|
|
if (!document.fullscreenElement) {
|
|
|
|
|
|
// 进入全屏
|
|
|
|
|
|
if (mapContainer.requestFullscreen) {
|
|
|
|
|
|
mapContainer.requestFullscreen().then(() => {
|
|
|
|
|
|
isFullscreen.value = true;
|
|
|
|
|
|
const buttons = document.querySelectorAll('.map-control-btn');
|
|
|
|
|
|
buttons[1].className = 'map-control-btn map-full-screen';
|
|
|
|
|
|
}).catch(err => {
|
|
|
|
|
|
console.error('全屏模式错误:', err);
|
|
|
|
|
|
});
|
|
|
|
|
|
}
|
|
|
|
|
|
} else {
|
|
|
|
|
|
// 退出全屏
|
|
|
|
|
|
if (document.exitFullscreen) {
|
|
|
|
|
|
document.exitFullscreen().then(() => {
|
|
|
|
|
|
isFullscreen.value = false;
|
|
|
|
|
|
const buttons = document.querySelectorAll('.map-control-btn');
|
|
|
|
|
|
buttons[1].className = 'map-control-btn map-nofull-screen';
|
|
|
|
|
|
});
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
// 监听全屏变化事件
|
|
|
|
|
|
document.addEventListener('fullscreenchange', () => {
|
|
|
|
|
|
if (!document.fullscreenElement) {
|
|
|
|
|
|
isFullscreen.value = false;
|
|
|
|
|
|
}
|
|
|
|
|
|
});
|
|
|
|
|
|
// 创建自定义点样式
|
|
|
|
|
|
const createPointStyle = (color, size, styleType) => {
|
|
|
|
|
|
const canvas = document.createElement('canvas');
|
|
|
|
|
|
canvas.width = size * 2;
|
|
|
|
|
|
canvas.height = size * 2;
|
|
|
|
|
|
const ctx = canvas.getContext('2d');
|
|
|
|
|
|
|
|
|
|
|
|
// 根据样式类型绘制不同的图形
|
|
|
|
|
|
switch (styleType) {
|
|
|
|
|
|
case 'circle':
|
|
|
|
|
|
ctx.beginPath();
|
|
|
|
|
|
ctx.arc(size, size, size, 0, Math.PI * 2);
|
|
|
|
|
|
ctx.fillStyle = color;
|
|
|
|
|
|
ctx.fill();
|
|
|
|
|
|
break;
|
|
|
|
|
|
case 'ring':
|
|
|
|
|
|
ctx.beginPath();
|
|
|
|
|
|
ctx.arc(size, size, size, 0, Math.PI * 2);
|
|
|
|
|
|
ctx.fillStyle = 'rgba(255, 255, 255, 0.8)';
|
|
|
|
|
|
ctx.fill();
|
|
|
|
|
|
|
|
|
|
|
|
ctx.beginPath();
|
|
|
|
|
|
ctx.arc(size, size, size * 0.7, 0, Math.PI * 2);
|
|
|
|
|
|
ctx.fillStyle = color;
|
|
|
|
|
|
ctx.fill();
|
|
|
|
|
|
break;
|
|
|
|
|
|
case 'square':
|
|
|
|
|
|
ctx.fillStyle = color;
|
|
|
|
|
|
ctx.fillRect(0, 0, size * 2, size * 2);
|
|
|
|
|
|
break;
|
|
|
|
|
|
case 'star':
|
|
|
|
|
|
ctx.fillStyle = color;
|
|
|
|
|
|
ctx.beginPath();
|
|
|
|
|
|
for (let i = 0; i < 5; i++) {
|
|
|
|
|
|
const angle = (i * 2 * Math.PI / 5) - Math.PI / 2;
|
|
|
|
|
|
const x = size + size * Math.cos(angle);
|
|
|
|
|
|
const y = size + size * Math.sin(angle);
|
|
|
|
|
|
if (i === 0) {
|
|
|
|
|
|
ctx.moveTo(x, y);
|
|
|
|
|
|
} else {
|
|
|
|
|
|
ctx.lineTo(x, y);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
const innerAngle = angle + Math.PI / 5;
|
|
|
|
|
|
const innerX = size + size * 0.4 * Math.cos(innerAngle);
|
|
|
|
|
|
const innerY = size + size * 0.4 * Math.sin(innerAngle);
|
|
|
|
|
|
ctx.lineTo(innerX, innerY);
|
|
|
|
|
|
}
|
|
|
|
|
|
ctx.closePath();
|
|
|
|
|
|
ctx.fill();
|
|
|
|
|
|
break;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return canvas;
|
|
|
|
|
|
}
|
|
|
|
|
|
// 渲染海量点
|
|
|
|
|
|
const renderMassMarks = () => {
|
|
|
|
|
|
// 清除现有的海量点
|
|
|
|
|
|
if (massMarks.value) {
|
|
|
|
|
|
massMarks.value.clear();
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 创建三种不同大小的样式
|
|
|
|
|
|
const styles = [
|
|
|
|
|
|
// 优势媒体颜色-红色
|
|
|
|
|
|
{
|
|
|
|
|
|
url: createPointStyle('#EC1B60', 18, 'circle').toDataURL(),
|
|
|
|
|
|
anchor: new AMap.Pixel(18, 18),
|
|
|
|
|
|
size: new AMap.Size(18, 18)
|
|
|
|
|
|
},
|
|
|
|
|
|
// 网络媒体颜色-蓝色
|
|
|
|
|
|
{
|
|
|
|
|
|
url: createPointStyle('#058DED', 18, 'circle').toDataURL(),
|
|
|
|
|
|
anchor: new AMap.Pixel(18, 18),
|
|
|
|
|
|
size: new AMap.Size(18, 18)
|
|
|
|
|
|
},
|
|
|
|
|
|
];
|
2025-09-11 22:54:53 +08:00
|
|
|
|
// 调用接口,获取数据点
|
|
|
|
|
|
points.value = []
|
|
|
|
|
|
mediaByMap(queryParams.value).then(res => {
|
|
|
|
|
|
if (res.code == 200) {
|
|
|
|
|
|
res.data.forEach(itemPoint => {
|
|
|
|
|
|
if (itemPoint.businessType == 1) points.value.push({ "lnglat": [itemPoint.x, itemPoint.y], "name": itemPoint.mediaId, "style": 0 })
|
|
|
|
|
|
if (itemPoint.businessType == 2) points.value.push({ "lnglat": [itemPoint.x, itemPoint.y], "name": itemPoint.mediaId, "style": 1 })
|
|
|
|
|
|
});
|
|
|
|
|
|
}
|
2025-09-12 15:20:28 +08:00
|
|
|
|
}).then(res => {
|
2025-09-11 22:54:53 +08:00
|
|
|
|
console.log('points', points.value)
|
|
|
|
|
|
// 创建MassMarks对象
|
|
|
|
|
|
massMarks.value = new AMap.MassMarks(points.value, {
|
|
|
|
|
|
opacity: 1,
|
|
|
|
|
|
zIndex: 111,
|
|
|
|
|
|
cursor: 'pointer',
|
|
|
|
|
|
style: styles
|
|
|
|
|
|
});
|
2025-09-02 22:55:31 +08:00
|
|
|
|
|
2025-09-11 22:54:53 +08:00
|
|
|
|
// 将海量点添加到地图
|
|
|
|
|
|
massMarks.value.setMap(mapInstance.value);
|
2025-09-02 22:55:31 +08:00
|
|
|
|
|
2025-09-11 22:54:53 +08:00
|
|
|
|
// 添加点击事件
|
|
|
|
|
|
massMarks.value.on('click', function (e) {
|
|
|
|
|
|
console.log('点击了节点', e.data)
|
|
|
|
|
|
});
|
|
|
|
|
|
})
|
2025-09-02 22:55:31 +08:00
|
|
|
|
}
|
|
|
|
|
|
// 隐藏Logo的函数
|
|
|
|
|
|
const hideAmapLogo = () => {
|
|
|
|
|
|
const logos = document.querySelectorAll('.amap-logo, .amap-copyright');
|
|
|
|
|
|
logos.forEach(logo => {
|
|
|
|
|
|
logo.style.display = 'none';
|
|
|
|
|
|
logo.style.visibility = 'hidden';
|
|
|
|
|
|
logo.style.opacity = '0';
|
|
|
|
|
|
});
|
|
|
|
|
|
}
|
2025-09-03 20:21:01 +08:00
|
|
|
|
|
|
|
|
|
|
onUnmounted(() => {
|
2025-09-12 15:20:28 +08:00
|
|
|
|
if (mapInstance.value) {
|
|
|
|
|
|
mapInstance.value.destroy();
|
|
|
|
|
|
}
|
2025-09-03 20:21:01 +08:00
|
|
|
|
})
|
2025-09-02 22:55:31 +08:00
|
|
|
|
// 初始化
|
|
|
|
|
|
onMounted(() => {
|
|
|
|
|
|
bgStore.setBgImage(otherbg)
|
|
|
|
|
|
loadMap()
|
|
|
|
|
|
});
|
|
|
|
|
|
</script>
|
|
|
|
|
|
|
|
|
|
|
|
<style lang="scss">
|
|
|
|
|
|
.mediaCard .el-card__body {
|
|
|
|
|
|
padding: 0px !important;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.mediaMapBtn {
|
|
|
|
|
|
min-width: 122px;
|
|
|
|
|
|
height: 34px;
|
|
|
|
|
|
border-radius: 4px;
|
|
|
|
|
|
background: #1a75e6;
|
|
|
|
|
|
font-family: Microsoft YaHei;
|
|
|
|
|
|
font-weight: 400;
|
|
|
|
|
|
font-size: 16px;
|
|
|
|
|
|
text-align: left;
|
|
|
|
|
|
color: #FFFFFF;
|
|
|
|
|
|
padding: 10px;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.mediaMapBtn>span {
|
|
|
|
|
|
align-items: center;
|
|
|
|
|
|
display: inline-flex;
|
|
|
|
|
|
width: 100%;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.mediaMapBtn:hover {
|
|
|
|
|
|
background: #1a75e6;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.distanceItem {
|
|
|
|
|
|
position: relative;
|
|
|
|
|
|
padding-left: 40px;
|
|
|
|
|
|
padding-right: 30px;
|
|
|
|
|
|
font-family: Microsoft YaHei;
|
|
|
|
|
|
font-weight: 400;
|
|
|
|
|
|
font-size: 16px;
|
|
|
|
|
|
color: #1E1E1E;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.distanceItem:hover {
|
|
|
|
|
|
font-weight: 600;
|
|
|
|
|
|
color: #1A75E6;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.distanceItemActive {
|
|
|
|
|
|
position: relative;
|
|
|
|
|
|
padding-left: 40px;
|
|
|
|
|
|
padding-right: 30px;
|
|
|
|
|
|
font-family: Microsoft YaHei;
|
|
|
|
|
|
font-weight: 600;
|
|
|
|
|
|
font-size: 16px;
|
|
|
|
|
|
color: #1A75E6;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.distanceItem:hover::before,
|
|
|
|
|
|
.distanceItemActive::before {
|
|
|
|
|
|
content: "√";
|
|
|
|
|
|
position: absolute;
|
|
|
|
|
|
left: 17px;
|
|
|
|
|
|
top: 6px;
|
|
|
|
|
|
width: 13px;
|
|
|
|
|
|
height: 10px;
|
|
|
|
|
|
font-family: Microsoft YaHei;
|
|
|
|
|
|
font-weight: 600;
|
|
|
|
|
|
color: #1A75E6;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.clearBtn {
|
|
|
|
|
|
display: inline-flex;
|
|
|
|
|
|
margin-left: 20px;
|
|
|
|
|
|
padding-left: 18px;
|
|
|
|
|
|
position: relative;
|
|
|
|
|
|
min-width: 82px;
|
|
|
|
|
|
height: 34px;
|
|
|
|
|
|
line-height: 34px;
|
|
|
|
|
|
font-family: Microsoft YaHei;
|
|
|
|
|
|
font-weight: 400;
|
|
|
|
|
|
font-size: 16px;
|
|
|
|
|
|
color: #87898E;
|
|
|
|
|
|
cursor: pointer;
|
|
|
|
|
|
}
|
2025-09-09 14:52:18 +08:00
|
|
|
|
|
2025-09-02 22:55:31 +08:00
|
|
|
|
.clearBtn::before {
|
|
|
|
|
|
content: "";
|
|
|
|
|
|
position: absolute;
|
|
|
|
|
|
left: 0px;
|
|
|
|
|
|
top: 10px;
|
|
|
|
|
|
width: 14px;
|
2025-09-09 14:52:18 +08:00
|
|
|
|
height: 14px;
|
2025-09-02 22:55:31 +08:00
|
|
|
|
background-image: url('../../assets/images/iconClearWhere.png');
|
|
|
|
|
|
background-repeat: no-repeat;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.mediaMapContainer {
|
|
|
|
|
|
width: 100%;
|
|
|
|
|
|
height: calc(100vh - 170px);
|
|
|
|
|
|
background: #3f8bff;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.custom-map-controls {
|
|
|
|
|
|
position: absolute;
|
|
|
|
|
|
top: 5px;
|
|
|
|
|
|
right: 5px;
|
|
|
|
|
|
z-index: 1000;
|
|
|
|
|
|
display: flex;
|
|
|
|
|
|
flex-direction: column;
|
|
|
|
|
|
gap: 0px;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.map-control-btn {
|
|
|
|
|
|
cursor: pointer;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.map-control-2dbtn {
|
|
|
|
|
|
width: 40px;
|
|
|
|
|
|
height: 40px;
|
|
|
|
|
|
background: url('../../assets/images/icon-2D-btn.png');
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.map-control-2dbtn:hover {
|
|
|
|
|
|
background: url('../../assets/images/icon-2D-active-btn.png');
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.map-control-3dbtn {
|
|
|
|
|
|
width: 40px;
|
|
|
|
|
|
height: 40px;
|
|
|
|
|
|
background: url('../../assets/images/icon-3D-btn.png');
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.map-control-3dbtn:hover {
|
|
|
|
|
|
background: url('../../assets/images/icon-3D-active-btn.png');
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.map-nofull-screen {
|
|
|
|
|
|
width: 40px;
|
|
|
|
|
|
height: 40px;
|
|
|
|
|
|
background: url('../../assets/images/icon-full-screen.png');
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.map-nofull-screen:hover {
|
|
|
|
|
|
background: url('../../assets/images/icon-full-screen-active.png');
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
.map-full-screen {
|
|
|
|
|
|
width: 40px;
|
|
|
|
|
|
height: 40px;
|
|
|
|
|
|
background: url('../../assets/images/icon-map-nofull-screen.png');
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.map-full-screen:hover {
|
|
|
|
|
|
background: url('../../assets/images/icon-map-nofull-screen-active.png');
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/* 全屏样式 */
|
|
|
|
|
|
.mapContainer:-webkit-full-screen {
|
|
|
|
|
|
width: 100% !important;
|
|
|
|
|
|
height: 100% !important;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.mapContainer:-moz-full-screen {
|
|
|
|
|
|
width: 100% !important;
|
|
|
|
|
|
height: 100% !important;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.mapContainer:-ms-fullscreen {
|
|
|
|
|
|
width: 100% !important;
|
|
|
|
|
|
height: 100% !important;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.mapContainer:fullscreen {
|
|
|
|
|
|
width: 100% !important;
|
|
|
|
|
|
height: 100% !important;
|
|
|
|
|
|
}
|
2025-09-12 15:20:28 +08:00
|
|
|
|
|
|
|
|
|
|
.one-text {
|
|
|
|
|
|
height: 50px;
|
|
|
|
|
|
padding-top: 4px;
|
|
|
|
|
|
}
|
2025-09-02 22:55:31 +08:00
|
|
|
|
</style>
|