
根据后端返回的数据绘制
javascript
// 返回数据格式,经纬度
[
{
"longitude": 106.8300012,
"latitude": -6.1824352
},
{
"longitude": 106.8335945,
"latitude": -6.1824352
},
{
"longitude": 106.8335945,
"latitude": -6.1833283
},
{
"longitude": 106.8308996,
"latitude": -6.1833283
},
{
"longitude": 106.8308996,
"latitude": -6.1842214
},
{
"longitude": 106.8317979,
"latitude": -6.1842214
},
{
"longitude": 106.8317979,
"latitude": -6.1851145
},
{
"longitude": 106.8326962,
"latitude": -6.1851145
},
{
"longitude": 106.8326962,
"latitude": -6.1842214
},
{
"longitude": 106.8335945,
"latitude": -6.1842214
},
{
"longitude": 106.8335945,
"latitude": -6.1860076
},
{
"longitude": 106.8326962,
"latitude": -6.1860076
},
{
"longitude": 106.8326962,
"latitude": -6.1869007
},
{
"longitude": 106.8353911,
"latitude": -6.1842214
},
{
"longitude": 106.8362894,
"latitude": -6.1842214
},
{
"longitude": 106.8362894,
"latitude": -6.1851145
},
{
"longitude": 106.8344928,
"latitude": -6.1851145
},
{
"longitude": 106.8344928,
"latitude": -6.1824352
},
{
"longitude": 106.8353911,
"latitude": -6.1824352
},
{
"longitude": 106.8353911,
"latitude": -6.1842214
},
{
"longitude": 106.8326962,
"latitude": -6.1869007
},
{
"longitude": 106.8335945,
"latitude": -6.1869007
},
{
"longitude": 106.8335945,
"latitude": -6.1877938
},
{
"longitude": 106.8308996,
"latitude": -6.1877938
},
{
"longitude": 106.8308996,
"latitude": -6.1869007
},
{
"longitude": 106.8317979,
"latitude": -6.1869007
},
{
"longitude": 106.8317979,
"latitude": -6.1860076
},
{
"longitude": 106.8308996,
"latitude": -6.1860076
},
{
"longitude": 106.8308996,
"latitude": -6.1851145
},
{
"longitude": 106.8300012,
"latitude": -6.1851145
},
{
"longitude": 106.8300012,
"latitude": -6.1824352
}
]
javascript
<template>
<div class="grid-map">
<el-upload
action="#"
accept=".xlsx,.xls,.csv"
:show-file-list="false"
:http-request="handleUpload"
>
<el-button
type="primary"
:disabled="uploadLoading"
class="mb20"
>
{{ $t('tools.importGridFile') }}
</el-button>
</el-upload>
<div
ref="mapRef"
class="map"
v-loading="uploadLoading"
element-loading-background="rgba(0, 0, 0, 0.7)"
></div>
</div>
</template>
<script setup>
import { ref, onMounted, watch } from 'vue';
import html2canvas from 'html2canvas';
import { uploadImgFile, griddingUpload } from '@/api/common';
import { ElMessage } from 'element-plus';
import { useI18n } from 'vue-i18n';
const { t } = useI18n();
const emit = defineEmits(['updateGridImageUrl', 'update:pointList']);
const mapRef = ref(null);
const map = ref(null); // 地图绘制参数
const GOOGLE_MAP_API_KEY = 'AIzaSyBcoIe_T6s2uH0wDi2dRocGCtkyP2cJhd4'; // 谷歌地图唯一key
const polygons = ref([]); // 区域网格参数
const uploadLoading = ref(false);
const props = defineProps({
pointList: {
type: Array,
default: () => [],
},
});
onMounted(() => {
const script = document.createElement('script');
script.src = `https://maps.googleapis.com/maps/api/js?key=${GOOGLE_MAP_API_KEY}&callback=initMap`;
script.async = true; // 异步加载
script.defer = true;
window.initMap = () => {
map.value = new google.maps.Map(mapRef.value, {
center: { lat: -6.2088, lng: 106.8456 }, // 地图初始中心点坐标
zoom: 17, // 地图初始缩放级别
minZoom: 5, // 最小缩放级别
maxZoom: 20, // 最大缩放级别
clickableIcons: false, // 禁用地图上POI图标(如商家、地标)的点击交互,点击后不会触发默认弹窗
disableDoubleClickZoom: true, // 禁用"双击地图自动放大"功能,双击后地图不会缩放
disableDefaultUI: true, // 禁用谷歌地图所有默认UI控件(包括缩放、地图类型、街景等)
// fullscreenControl: false, // 隐藏全屏按钮
// streetViewControl: false, // 隐藏街景小人控件
// mapTypeControl: false, // 隐藏"地图/卫星图"切换控件
// zoomControl: false, // 隐藏"+/-"缩放按钮控件
// draggable: true, // 禁用地图拖动(四向箭头交互)
});
};
document.head.appendChild(script);
});
watch(
() => props.pointList,
(newVal) => {
if (map.value && newVal?.length) {
uploadLoading.value = true;
drawGridOnMap(map.value, newVal);
}
},
{
deep: true,
},
);
// 处理文件上传
const handleUpload = (params) => {
if (uploadLoading.value) return;
// 文件类型验证
const file = params.file;
const isExcelOrCsv =
// 通过MIME类型判断
file.type === 'application/vnd.ms-excel' ||
file.type === 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet' ||
file.type === 'text/csv' ||
file.type === 'application/csv' ||
file.type === 'text/comma-separated-values';
// 文件格式验证(.xlsx,.xls,.csv)
if (!isExcelOrCsv) {
ElMessage.error(t('tools.gridFileFormatError'));
return;
}
// 文件大小验证
const isLt5M = file.size / 1024 / 1024 < 30;
if (!isLt5M) {
ElMessage.error(t('tools.fileSizeExceed30MB'));
return;
}
const data = new FormData();
data.append('file', file);
uploadLoading.value = true;
griddingUpload(data).then((res) => {
if (res.success) {
emit('update:pointList', res.data || []);
// 处理后端返回的数据并绘制
drawGridOnMap(map.value, res.data);
}
});
};
/**
* 根据后端返回的经纬度数据绘制多边形网格
* @param {google.maps.Map} map - 地图实例
* @param {Array} coords - 后端返回的坐标数组:[{longitude, latitude}, ...]
*/
const drawGridOnMap = (map, coords) => {
// 清除已有多边形
polygons.value.forEach((poly) => poly.setMap(null));
polygons.value = [];
if (!coords || coords.length < 3) {
console.warn('坐标数据不足,无法绘制多边形');
return;
}
// 转换后端数据格式为Google Maps需要的LatLng对象数组
const path = coords.map((item) => ({
lat: item.latitude,
lng: item.longitude,
}));
// 计算地图显示范围
const bounds = new window.google.maps.LatLngBounds();
path.forEach((point) => bounds.extend(point));
// 创建并绘制多边形
const polygon = new google.maps.Polygon({
paths: path, // 多边形顶点路径
strokeColor: 'transparent',
strokeOpacity: 0,
strokeWeight: 0,
fillColor: '#00FF00',
fillOpacity: 0.3,
map: map,
});
polygons.value.push(polygon);
// 调整地图视野以显示整个多边形
map.fitBounds(bounds, {
padding: { top: 50, bottom: 50, left: 50, right: 50 },
});
uploadLoading.value = false;
// 延迟截图,确保地图渲染完成
// setTimeout(() => {
captureMapScreenshot();
// }, 800);
};
const captureMapScreenshot = async () => {
const mapDom = mapRef.value;
if (!mapDom) return;
try {
const canvas = await html2canvas(mapDom, {
useCORS: true,
scale: 2,
logging: false,
});
// 关键修改:将Blob转换为带元数据的File对象
canvas.toBlob(
async (blob) => {
if (blob) {
// 1. 生成唯一文件名(避免重复)
const timestamp = new Date().getTime();
const fileName = `map-screenshot-${timestamp}.jpg`; // 明确使用.jpg扩展名
// 2. 将Blob转换为File对象,指定MIME类型为image/jpeg
const file = new File([blob], fileName, {
type: 'image/jpeg', // 匹配你提供的示例中的image/jpeg类型
lastModified: Date.now(), // 添加最后修改时间
});
// 3. 上传转换后的File对象
await uploadScreenshotToBackend(file);
}
},
'image/jpeg',
0.9,
); // 明确指定格式为jpeg,0.9为压缩质量(0-1)
} catch (error) {
console.error('截图失败:', error);
}
};
// 上传截图到后端
const uploadScreenshotToBackend = async (file) => {
// 参数已改为File对象
const formData = new FormData();
// 直接 append File对象,而非Blob
formData.append('file', file);
formData.append('business', 'product');
const res = await uploadImgFile(formData);
if (res.success) {
emit('updateGridImageUrl', res.data.visitUrl || '');
}
};
</script>
<style lang="scss" scoped>
.grid-map {
.map {
position: relative;
width: 900px;
height: 600px;
}
}
</style>
根据本地上传的excel文件绘制

javascript
<template>
<div class="grid-map">
<input
type="file"
accept=".xlsx,.xls,.csv"
@change="handleFileUpload"
class="file-upload"
:disabled="uploadLoading"
/>
<div
ref="mapRef"
class="map"
v-loading="uploadLoading"
element-loading-background="rgba(0, 0, 0, 0.7)"
></div>
</div>
</template>
<script setup>
import { ref, onMounted } from 'vue';
import * as XLSX from 'xlsx';
import html2canvas from 'html2canvas';
import { uploadImgFile } from '@/api/common';
const emit = defineEmits(['updateGridImageUrl']);
const mapRef = ref(null);
const map = ref(null);
const gridData = ref([]);
const GOOGLE_MAP_API_KEY = 'AIzaSyBcoIe_T6s2uH0wDi2dRocGCtkyP2cJhd4';
const polygons = ref([]);
const uploadLoading = ref(false); // 添加上传loading状态
onMounted(() => {
const script = document.createElement('script');
script.src = `https://maps.googleapis.com/maps/api/js?key=${GOOGLE_MAP_API_KEY}&callback=initMap`;
script.async = true; // 异步加载
script.defer = true;
window.initMap = () => {
map.value = new google.maps.Map(mapRef.value, {
center: { lat: -6.2088, lng: 106.8456 }, // 地图初始中心点坐标
zoom: 17, // 地图初始缩放级别
minZoom: 5, // 最小缩放级别
maxZoom: 20, // 最大缩放级别
clickableIcons: false, // 禁用地图上POI图标(如商家、地标)的点击交互,点击后不会触发默认弹窗
disableDoubleClickZoom: true, // 禁用"双击地图自动放大"功能,双击后地图不会缩放
disableDefaultUI: true, // 禁用谷歌地图所有默认UI控件(包括缩放、地图类型、街景等)
// fullscreenControl: false, // 隐藏全屏按钮
// streetViewControl: false, // 隐藏街景小人控件
// mapTypeControl: false, // 隐藏"地图/卫星图"切换控件
// zoomControl: false, // 隐藏"+/-"缩放按钮控件
// draggable: true, // 禁用地图拖动(四向箭头交互)
});
if (gridData.value.length) drawGridOnMap(map.value, gridData.value);
};
document.head.appendChild(script);
});
/**
* 处理Excel文件上传事件:读取文件内容 → 解析Excel数据 → 提取网格顶点经纬度 → 触发地图绘制
* @param {Event} event - 文件上传控件的change事件对象
*/
const handleFileUpload = (event) => {
const file = event.target.files[0];
if (!file) return;
uploadLoading.value = true;
event.target.value = '';
// 2. 创建FileReader实例:用于读取文件的二进制数据
const reader = new FileReader();
// 3. 定义文件读取成功后的回调函数
reader.onload = (e) => {
try {
const isCSV = file.name.toLowerCase().endsWith('.csv');
let workbook;
// 区分解析CSV和Excel文件
if (isCSV) {
// CSV文件:先转为字符串再解析
const text = new TextDecoder().decode(e.target.result);
workbook = XLSX.read(text, { type: 'string' });
} else {
// Excel文件:用ArrayBuffer解析
const data = new Uint8Array(e.target.result);
workbook = XLSX.read(data, { type: 'array' });
}
// 提取第一个工作表数据
const sheet = workbook.Sheets[workbook.SheetNames[0]];
const jsonData = XLSX.utils.sheet_to_json(sheet, { header: 1 });
// 校验数据有效性(至少包含表头和一行数据)
if (jsonData.length <= 1) {
uploadLoading.value = false;
return;
}
// 解析表头和数据行(重点处理C、D、E、F列)
const headers = jsonData[0]; // 表头数组(C列索引为2,D为3,E为4,F为5)
gridData.value = jsonData.slice(1).map((row) => {
const grid = {};
// 映射所有字段到grid对象(保留原始数据)
headers.forEach((header, index) => {
grid[header] = row[index];
});
/**
* 解析坐标字符串(适配CSV中C-F列的格式:"经度,纬度",兼容空格)
* 例如:"106.7936694, -6.1663594" → 拆分为经度106.7936694,纬度-6.1663594
* @param {string} str - 坐标字符串
* @returns {Object} 包含lat(纬度)和lng(经度)的坐标对象
*/
const splitCoord = (str) => {
if (!str || typeof str !== 'string') return { lat: NaN, lng: NaN };
// 按逗号分割,处理可能的空格(如"106.79, -6.16")
const [lngStr, latStr] = str.split(/\s*,\s*/).map((s) => s.trim());
return {
lat: parseFloat(latStr) || NaN, // 纬度(第二部分)
lng: parseFloat(lngStr) || NaN, // 经度(第一部分)
};
};
// 关键修改:强制绑定C、D、E、F列(索引2、3、4、5),兼容表头字段名变化
// C列 → 顶点1,D列 → 顶点2,E列 → 顶点3,F列 → 顶点4
grid.vertex1 = splitCoord(row[2]); // C列(索引2)
grid.vertex2 = splitCoord(row[3]); // D列(索引3)
grid.vertex3 = splitCoord(row[4]); // E列(索引4)
grid.vertex4 = splitCoord(row[5]); // F列(索引5)
return grid;
});
// 若地图已初始化,绘制网格
if (map.value) {
drawGridOnMap(map.value, gridData.value);
}
} catch (error) {
console.error('文件解析失败:', error);
} finally {
uploadLoading.value = false;
}
};
// 文件读取错误处理
reader.onerror = () => {
console.error('文件读取失败');
uploadLoading.value = false;
};
// 读取文件(统一用ArrayBuffer,后续按需处理)
reader.readAsArrayBuffer(file);
};
/**
* 在地图上绘制网格多边形
* @param {google.maps.Map} map - 地图实例
* @param {Array} data - 网格数据数组
*/
const drawGridOnMap = (map, data) => {
// 清除已有多边形
polygons.value.forEach((poly) => poly.setMap(null));
polygons.value = [];
// 计算地图显示范围
const bounds = new window.google.maps.LatLngBounds();
// 遍历网格数据,绘制多边形
data.forEach((grid) => {
// 提取4个顶点坐标(C-F列解析结果)
const vertices = [grid.vertex1, grid.vertex2, grid.vertex3, grid.vertex4];
// 扩展地图范围以包含所有有效顶点
vertices.forEach((vertex) => {
if (!isNaN(vertex.lat) && !isNaN(vertex.lng)) {
bounds.extend(vertex);
}
});
// 过滤无效坐标,确保多边形至少有3个有效顶点
const validVertices = vertices.filter((v) => !isNaN(v.lat) && !isNaN(v.lng));
if (validVertices.length >= 3) {
// 创建多边形(绿色半透明填充)
const polygon = new google.maps.Polygon({
paths: validVertices, // 多边形的有效顶点路径(paths支持单个多边形,兼容多区域场景)
strokeColor: 'transparent', // 边框颜色:透明
strokeOpacity: 0, // 边框透明度
strokeWeight: 0, // 边框粗细
fillColor: '#00FF00', // 多边形填充色
fillOpacity: 0.3, // 填充透明度
map, // 将多边形绑定到当前地图实例
});
polygons.value.push(polygon);
}
});
// 调整地图视野以显示所有网格
if (bounds.getNorthEast() && bounds.getSouthWest()) {
map.fitBounds(bounds, {
padding: { top: 50, bottom: 50, left: 50, right: 50 },
});
// 延迟截图,确保地图渲染完成
setTimeout(() => {
captureMapScreenshot();
}, 800); // 延长延迟,确保复杂网格完全渲染
}
};
/**
* 捕获地图截图并上传
*/
const captureMapScreenshot = async () => {
const mapDom = mapRef.value;
if (!mapDom) return;
try {
// 生成截图(scale=2提升清晰度)
const canvas = await html2canvas(mapDom, {
useCORS: true, // 允许跨域图片(谷歌地图瓦片)
scale: 2,
logging: false,
});
// 转为Blob并上传
canvas.toBlob(async (blob) => {
if (blob) {
await uploadScreenshotToBackend(blob);
}
}, 'image/png');
} catch (error) {
console.error('截图失败:', error);
}
};
/**
* 上传截图到后端
* @param {Blob} blob - 截图的Blob对象
*/
const uploadScreenshotToBackend = async (blob) => {
const formData = new FormData();
formData.append('file', blob);
formData.append('business', 'product');
const res = await uploadImgFile(formData);
if (res.success) {
emit('updateGridImageUrl', res.data.visitUrl || '');
}
};
</script>
<style lang="scss" scoped>
.grid-map {
display: flex;
flex-direction: column;
gap: 16px;
.file-upload {
cursor: pointer;
&:disabled {
cursor: not-allowed;
opacity: 0.6;
}
}
.map {
position: relative;
width: 900px;
height: 600px;
}
}
</style>