570 lines
16 KiB
Vue
570 lines
16 KiB
Vue
<template>
|
|
<div class="page-container">
|
|
<div class="left-section">
|
|
<div class="logistics-map">
|
|
<div class="map-title">武鸣沃柑大脑</div>
|
|
<div class="count-panel">
|
|
<div class="count-box">
|
|
<div class="count-value">{{ exportOrchardCount }}</div>
|
|
<div class="count-title">出口果园</div>
|
|
</div>
|
|
<div class="count-box">
|
|
<div class="count-value">{{ exportBatchCount }}</div>
|
|
<div class="count-title">出口批次</div>
|
|
</div>
|
|
<div class="count-box">
|
|
<div class="count-value">{{ totalExportWeight }}</div>
|
|
<div class="count-title">累计出口</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div id="map-container" ref="mapContainer"></div>
|
|
<div v-if="!isMapReady" class="map-loading">地图资源加载中...</div>
|
|
</div>
|
|
</div>
|
|
<div class="right-section">
|
|
<RightChartPanel />
|
|
</div>
|
|
|
|
<!-- 果园详情弹框 -->
|
|
<OrchardDetailModal
|
|
:orchard-name="selectedOrchardName"
|
|
:visible.sync="showModal"
|
|
/>
|
|
</div>
|
|
</template>
|
|
|
|
<script>
|
|
import AMapLoader from "@amap/amap-jsapi-loader";
|
|
import RightChartPanel from "@/components/brain/RightChartPanel.vue";
|
|
import OrchardDetailModal from '@/components/brain/OrchardDetailModal.vue';
|
|
export default {
|
|
name: "OrchardMap",
|
|
components: { RightChartPanel,OrchardDetailModal },
|
|
data() {
|
|
return {
|
|
map: null,
|
|
AMap: null,
|
|
isMapReady: false,
|
|
infoWindow: null,
|
|
exportOrchardCount: "56家",
|
|
exportBatchCount: "101批",
|
|
totalExportWeight: "4031万斤",
|
|
orchards: [], // 空数组,后续初始化10条数据
|
|
woganIcon: require("@/assets/images/brain/出口icon.png"),
|
|
markers: [],
|
|
resizeHandler: null,
|
|
currentIndex: 0, // 当前展示的信息框索引
|
|
autoPlay: true, // 是否自动播放
|
|
playTimer: null, // 自动播放定时器
|
|
playDuration: { min: 5000, max: 8000 }, // 播放时长范围:5-8秒
|
|
showModal: false,
|
|
selectedOrchardName: '正欣农业-1号园区' // 从你的果园数据中传递
|
|
};
|
|
},
|
|
created() {
|
|
window._AMapSecurityConfig = {
|
|
securityJsCode: "8f1711e57fb2704974d568056a762ee3",
|
|
};
|
|
this.resizeHandler = this.debounce(() => this.adjustMapZoomByScreen(), 300);
|
|
// 初始化10条果园数据
|
|
this.initOrchardData();
|
|
},
|
|
mounted() {
|
|
this.initMap();
|
|
window.addEventListener("resize", this.resizeHandler);
|
|
},
|
|
beforeDestroy() {
|
|
// 清除定时器
|
|
if (this.playTimer) clearInterval(this.playTimer);
|
|
if (this.map) this.map.destroy();
|
|
window.removeEventListener("resize", this.resizeHandler);
|
|
},
|
|
methods: {
|
|
// 防抖函数
|
|
debounce(fn, delay) {
|
|
let timer = null;
|
|
return function () {
|
|
clearTimeout(timer);
|
|
timer = setTimeout(() => fn.apply(this), delay);
|
|
};
|
|
},
|
|
|
|
// 生成10条果园数据(4个在108.01,23.19附近3000米内,其余6个补充)
|
|
initOrchardData() {
|
|
// 中心点:108.01,23.19(正欣农业),3000米约对应经纬度偏移±0.03
|
|
const centerLng = 108.01;
|
|
const centerLat = 23.19;
|
|
|
|
// 生成正欣农业附近4个园区(3000米内)
|
|
const nearOrchards = Array.from({ length: 4 }, (_, i) => {
|
|
// 随机偏移经纬度(±0.03内)
|
|
const lngOffset = (Math.random() - 0.5) * 2 * 0.03;
|
|
const latOffset = (Math.random() - 0.5) * 2 * 0.03;
|
|
const lng = Number((centerLng + lngOffset).toFixed(4));
|
|
const lat = Number((centerLat + latOffset).toFixed(4));
|
|
|
|
return {
|
|
id: i + 1,
|
|
name: `正欣农业-${i + 1}号园区`,
|
|
lat,
|
|
lng,
|
|
registerNo: `7201GXXXX${i + 1}`,
|
|
address: `广西南宁市武鸣区锣圩镇板新村长平屯剑麻地地-${i + 1}区`,
|
|
registerType: "出境新鲜水果果园注册登记",
|
|
productType: "沃柑、青柚",
|
|
yield: `${1000 - i * 50}万斤`,
|
|
validPeriod: "自2025年10月21日至2028年10月21日",
|
|
};
|
|
});
|
|
|
|
// 补充6个其他园区数据
|
|
const otherOrchards = [
|
|
{
|
|
id: 5,
|
|
name: "武鸣沃柑示范基地",
|
|
lat: 23.1681,
|
|
lng: 108.2778,
|
|
registerNo: "7202GXXXX",
|
|
address: "广西南宁市武鸣区双桥镇杨李村",
|
|
registerType: "出境新鲜水果果园注册登记",
|
|
productType: "沃柑",
|
|
yield: "800万斤",
|
|
validPeriod: "自2024年05月10日至2027年05月10日",
|
|
},
|
|
{
|
|
id: 6,
|
|
name: "宁武镇沃柑种植园",
|
|
lat: 23.1223,
|
|
lng: 108.3123,
|
|
registerNo: "7203GXXXX",
|
|
address: "广西南宁市武鸣区宁武镇雄孟村",
|
|
registerType: "出境新鲜水果果园注册登记",
|
|
productType: "沃柑",
|
|
yield: "600万斤",
|
|
validPeriod: "自2024年08月15日至2027年08月15日",
|
|
},
|
|
{
|
|
id: 7,
|
|
name: "陆斡镇沃柑基地",
|
|
lat: 23.2512,
|
|
lng: 108.1823,
|
|
registerNo: "7204GXXXX",
|
|
address: "广西南宁市武鸣区陆斡镇桥东村",
|
|
registerType: "出境新鲜水果果园注册登记",
|
|
productType: "沃柑",
|
|
yield: "750万斤",
|
|
validPeriod: "自2024年11月01日至2027年11月01日",
|
|
},
|
|
{
|
|
id: 8,
|
|
name: "双桥镇柑桔产业园",
|
|
lat: 23.1721,
|
|
lng: 108.2818,
|
|
registerNo: "7205GXXXX",
|
|
address: "广西南宁市武鸣区双桥镇镇南社区",
|
|
registerType: "出境新鲜水果果园注册登记",
|
|
productType: "沃柑、茂谷柑",
|
|
yield: "900万斤",
|
|
validPeriod: "自2024年06月01日至2027年06月01日",
|
|
},
|
|
{
|
|
id: 9,
|
|
name: "太平镇沃柑种植基地",
|
|
lat: 23.2015,
|
|
lng: 108.2235,
|
|
registerNo: "7206GXXXX",
|
|
address: "广西南宁市武鸣区太平镇庆乐村",
|
|
registerType: "出境新鲜水果果园注册登记",
|
|
productType: "沃柑",
|
|
yield: "850万斤",
|
|
validPeriod: "自2024年07月10日至2027年07月10日",
|
|
},
|
|
{
|
|
id: 10,
|
|
name: "府城镇柑桔示范园",
|
|
lat: 23.2342,
|
|
lng: 108.1567,
|
|
registerNo: "7207GXXXX",
|
|
address: "广西南宁市武鸣区府城镇四明村",
|
|
registerType: "出境新鲜水果果园注册登记",
|
|
productType: "沃柑、脐橙",
|
|
yield: "780万斤",
|
|
validPeriod: "自2024年08月01日至2027年08月01日",
|
|
},
|
|
];
|
|
|
|
// 合并为10条数据
|
|
this.orchards = [...nearOrchards, ...otherOrchards];
|
|
},
|
|
|
|
// 计算适配的缩放级别
|
|
getAdaptedZoom() {
|
|
const el = document.getElementById("map-container");
|
|
const mapContainerWidth = el ? el.clientWidth : 0;
|
|
if (mapContainerWidth >= 1920) return 11;
|
|
if (mapContainerWidth >= 1440) return 11;
|
|
if (mapContainerWidth >= 1024) return 10;
|
|
if (mapContainerWidth >= 768) return 9;
|
|
return 8;
|
|
},
|
|
|
|
// 调整地图缩放(首次进入与 resize 共用)
|
|
adjustMapZoomByScreen() {
|
|
if (!this.map) return;
|
|
const targetZoom = this.getAdaptedZoom();
|
|
this.map.setZoom(targetZoom);
|
|
},
|
|
|
|
// 初始化地图
|
|
async initMap() {
|
|
try {
|
|
const AMap = await AMapLoader.load({
|
|
key: "8c615942a3955277d8b4855ce50a43a6",
|
|
version: "2.0",
|
|
plugins: ["AMap.DistrictSearch"],
|
|
});
|
|
|
|
this.AMap = AMap;
|
|
const initZoom = this.getAdaptedZoom();
|
|
this.map = new AMap.Map("map-container", {
|
|
zoom: initZoom,
|
|
center: [108.01, 23.19], // 初始中心点定位到正欣农业
|
|
viewMode: "2D",
|
|
pitch: 0,
|
|
mapStyle: "amap://styles/darkblue",
|
|
showLabel: true,
|
|
features: ["bg", "road", "building", "point"],
|
|
showIndoorMap: false,
|
|
zoomEnable: true,
|
|
dragEnable: true,
|
|
rotateEnable: false,
|
|
});
|
|
|
|
this.map.on("complete", () => {
|
|
this.adjustMapZoomByScreen();
|
|
});
|
|
|
|
this.isMapReady = true;
|
|
this.drawBoundaries("武鸣区");
|
|
this.drawOrchardMarkers();
|
|
// 启动自动播放
|
|
this.startAutoPlay();
|
|
} catch (error) {
|
|
console.error("地图初始化失败:", error);
|
|
}
|
|
},
|
|
|
|
// 绘制区域边界
|
|
drawBoundaries(districtName) {
|
|
if (!this.AMap) return;
|
|
const district = new this.AMap.DistrictSearch({
|
|
level: "district",
|
|
extensions: "all",
|
|
subdistrict: 0,
|
|
});
|
|
|
|
district.search(districtName, (status, result) => {
|
|
if (status === "complete" && result.districtList.length > 0) {
|
|
const bounds = result.districtList[0].boundaries;
|
|
if (bounds) {
|
|
bounds.forEach((boundary) => {
|
|
new this.AMap.Polygon({
|
|
path: boundary,
|
|
strokeColor: "#00eaff",
|
|
strokeWeight: 3,
|
|
strokeOpacity: 1,
|
|
fillColor: "#1cadff",
|
|
fillOpacity: 0.1,
|
|
map: this.map,
|
|
zIndex: 10,
|
|
});
|
|
});
|
|
}
|
|
}
|
|
});
|
|
},
|
|
|
|
// 绘制果园标记
|
|
drawOrchardMarkers() {
|
|
if (!this.map) return;
|
|
this.markers.forEach((m) => m.setMap(null));
|
|
this.markers = [];
|
|
|
|
this.orchards.forEach((item) => {
|
|
const marker = new this.AMap.Marker({
|
|
position: [item.lng, item.lat],
|
|
icon: new this.AMap.Icon({
|
|
image: this.woganIcon,
|
|
size: new this.AMap.Size(40, 40),
|
|
imageSize: new this.AMap.Size(40, 40),
|
|
}),
|
|
offset: new this.AMap.Pixel(-20, -20),
|
|
map: this.map,
|
|
zIndex: 100,
|
|
cursor: "pointer",
|
|
extData: item,
|
|
});
|
|
|
|
// 点击marker:停止自动播放,手动切换到当前信息框
|
|
marker.on("click", () => {
|
|
// this.stopAutoPlay();
|
|
this.currentIndex = this.orchards.findIndex(orchard => orchard.id === item.id);
|
|
this.selectedOrchardName = this.orchards[this.currentIndex].name;
|
|
this.showModal = true
|
|
console.log('弹框',this.showModal);
|
|
|
|
this.openInfoWindow(marker, item);
|
|
// this.map.panTo(marker.getPosition());
|
|
});
|
|
|
|
this.markers.push(marker);
|
|
});
|
|
|
|
// 初始显示第一个信息框,并按屏幕宽度初始化缩放(不再用 setFitView 覆盖)
|
|
setTimeout(() => {
|
|
const firstMarker = this.markers[0];
|
|
this.openInfoWindow(firstMarker, this.orchards[0]);
|
|
this.adjustMapZoomByScreen();
|
|
}, 800);
|
|
},
|
|
|
|
// 打开信息窗口(半透明 + 右侧显示)
|
|
openInfoWindow(marker, data) {
|
|
const infoContent = `
|
|
<div class="custom-info-window">
|
|
<div class="info-header">${data.name}</div>
|
|
<div class="info-body">
|
|
<div class="info-row"><span>果园注册号:</span><span>${data.registerNo}</span></div>
|
|
<div class="info-row"><span>地址:</span><span>${data.address}</span></div>
|
|
<div class="info-row"><span>登记类型:</span><span>${data.registerType}</span></div>
|
|
<div class="info-row"><span>产品类型:</span><span>${data.productType}</span></div>
|
|
<div class="info-row"><span>年产量:</span><span>${data.yield}</span></div>
|
|
<div class="info-row"><span>有效期限:</span><span>${data.validPeriod}</span></div>
|
|
</div>
|
|
</div>
|
|
`;
|
|
|
|
if (!this.infoWindow) {
|
|
this.infoWindow = new this.AMap.InfoWindow({
|
|
isCustom: true,
|
|
anchor: "top-left", // 信息框显示在图标右侧
|
|
offset: new this.AMap.Pixel(10, -10), // 微调位置,避免重叠
|
|
closeWhenClickMap: false, // 点击地图不关闭信息框
|
|
});
|
|
}
|
|
|
|
this.infoWindow.setContent(infoContent);
|
|
this.infoWindow.open(this.map, marker.getPosition());
|
|
},
|
|
|
|
// 开始自动播放
|
|
startAutoPlay() {
|
|
if (this.playTimer) clearInterval(this.playTimer);
|
|
this.autoPlay = true;
|
|
// 随机5-8秒切换
|
|
this.playTimer = setInterval(() => {
|
|
this.nextInfoWindow();
|
|
}, Math.floor(Math.random() * (this.playDuration.max - this.playDuration.min)) + this.playDuration.min);
|
|
},
|
|
// 下一个信息框
|
|
nextInfoWindow() {
|
|
this.currentIndex = (this.currentIndex + 1) % this.orchards.length;
|
|
const currentOrchard = this.orchards[this.currentIndex];
|
|
const currentMarker = this.markers.find(marker => marker.getExtData().id === currentOrchard.id);
|
|
if (currentMarker) {
|
|
this.openInfoWindow(currentMarker, currentOrchard);
|
|
// this.map.panTo(currentMarker.getPosition()); // 地图跟随定位
|
|
}
|
|
},
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
},
|
|
};
|
|
</script>
|
|
|
|
<style>
|
|
.page-container {
|
|
width: 100%;
|
|
height: 100vh;
|
|
display: flex;
|
|
background-color: #020f26;
|
|
font-family: "Microsoft YaHei", sans-serif;
|
|
overflow: hidden;
|
|
}
|
|
|
|
.left-section {
|
|
width: 80%;
|
|
height: 100%;
|
|
position: relative;
|
|
border-right: 1px solid #00eaff;
|
|
}
|
|
|
|
.logistics-map {
|
|
width: 100%;
|
|
height: 100%;
|
|
position: relative;
|
|
}
|
|
|
|
/* 手动控制按钮面板 */
|
|
.info-control-panel {
|
|
position: absolute;
|
|
bottom: 30px;
|
|
left: 50%;
|
|
transform: translateX(-50%);
|
|
z-index: 999;
|
|
display: flex;
|
|
gap: 10px;
|
|
}
|
|
|
|
.control-btn {
|
|
padding: 8px 16px;
|
|
background: rgba(12, 28, 56, 0.8);
|
|
border: 1px solid #00eaff;
|
|
color: #00eaff;
|
|
border-radius: 4px;
|
|
cursor: pointer;
|
|
transition: all 0.3s;
|
|
}
|
|
|
|
.control-btn:hover {
|
|
background: rgba(0, 234, 255, 0.2);
|
|
border-color: #01ffff;
|
|
}
|
|
|
|
#map-container {
|
|
width: 100%;
|
|
height: 100%;
|
|
padding: 0 !important;
|
|
margin: 0 !important;
|
|
border: none !important;
|
|
}
|
|
|
|
/* 标记图标圆形样式 */
|
|
.amap-marker img {
|
|
border-radius: 50% !important;
|
|
overflow: hidden !important;
|
|
border: 2px solid #00eaff !important;
|
|
}
|
|
|
|
/* 隐藏原始工具栏 */
|
|
.amap-toolbar {
|
|
display: none !important;
|
|
}
|
|
|
|
.map-title {
|
|
position: absolute;
|
|
top: 20px;
|
|
left: 20px;
|
|
z-index: 10;
|
|
font-size: 28px;
|
|
font-weight: bold;
|
|
color: #00eaff;
|
|
text-shadow: 0 0 10px rgba(0, 234, 255, 0.8);
|
|
}
|
|
|
|
.count-panel {
|
|
position: absolute;
|
|
top: 80px;
|
|
left: 20px;
|
|
z-index: 10;
|
|
display: flex;
|
|
flex-direction: column;
|
|
gap: 15px;
|
|
}
|
|
|
|
.count-box {
|
|
width: 120px;
|
|
height: 70px;
|
|
background: rgba(12, 28, 56, 0.8);
|
|
border: 1px solid #1f76ab;
|
|
box-shadow: 0 0 10px rgba(31, 118, 171, 0.5);
|
|
display: flex;
|
|
flex-direction: column;
|
|
justify-content: center;
|
|
align-items: center;
|
|
color: #fff;
|
|
border-radius: 4px;
|
|
}
|
|
|
|
.count-value {
|
|
font-size: 22px;
|
|
color: #00eaff;
|
|
font-weight: bold;
|
|
text-shadow: 0 0 8px rgba(0, 234, 255, 0.6);
|
|
margin-bottom: 5px;
|
|
}
|
|
|
|
.count-title {
|
|
font-size: 14px;
|
|
color: #cceaff;
|
|
}
|
|
|
|
.map-loading {
|
|
position: absolute;
|
|
top: 50%;
|
|
left: 50%;
|
|
transform: translate(-50%, -50%);
|
|
background: rgba(0, 0, 0, 0.8);
|
|
color: #00eaff;
|
|
padding: 10px 20px;
|
|
border-radius: 4px;
|
|
z-index: 999;
|
|
}
|
|
|
|
.right-section {
|
|
width: 20%;
|
|
height: 100%;
|
|
background: rgba(2, 15, 38, 0.95);
|
|
padding: 10px;
|
|
box-sizing: border-box;
|
|
border-top: 2px solid #01ffff;
|
|
border-right: 2px solid #01ffff;
|
|
border-bottom: 2px solid #01ffff;
|
|
border-left: none;
|
|
}
|
|
|
|
/* 半透明信息框样式 */
|
|
.custom-info-window {
|
|
padding: 12px 15px;
|
|
min-width: 300px;
|
|
font-size: 13px;
|
|
line-height: 1.6;
|
|
border: 1px solid #01ffff;
|
|
background: rgba(12, 28, 56, 0.96) !important; /* 80%透明度 */
|
|
border-radius: 4px;
|
|
color: #fff;
|
|
}
|
|
|
|
.custom-info-window .info-header {
|
|
font-size: 15px;
|
|
font-weight: bold;
|
|
color: #00eaff;
|
|
margin-bottom: 8px;
|
|
padding-bottom: 5px;
|
|
border-bottom: 1px solid rgba(0, 234, 255, 0.3);
|
|
}
|
|
|
|
.custom-info-window .info-body .info-row {
|
|
display: flex;
|
|
margin-bottom: 4px;
|
|
word-break: break-all;
|
|
}
|
|
|
|
.custom-info-window .info-body .info-row span:first-child {
|
|
color: #cceaff;
|
|
width: 120px;
|
|
display: inline-block;
|
|
}
|
|
|
|
.custom-info-window .info-body .info-row span:last-child {
|
|
color: #fff;
|
|
flex: 1;
|
|
}
|
|
</style> |