Files
apes-Authon-Web/src/views/brain/index.vue
T
2026-08-09 11:46:38 +08:00

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>