439 lines
14 KiB
Vue
439 lines
14 KiB
Vue
<template>
|
||
<view class="container">
|
||
<!-- {{ polyline[0]?.points.length }} - {{ pointArr.length }} -->
|
||
<!-- <button @tap="startWatch()">{{ polyline[0].points.length }}开始监听</button>
|
||
<button @click="fileWriter">存储</button> -->
|
||
<!-- 地图容器 -->
|
||
<view class="map-container">
|
||
<map id="amap" ref="mapRef" :latitude="latitude" :longitude="longitude" :scale="scale" :polyline="polyline"
|
||
:markers="markers" style="width: 100%; height: 100%;" />
|
||
<!-- <cover-image class="center-img" :src="center" v-show="reportParams.type == 'location'"></cover-image> -->
|
||
</view>
|
||
|
||
</view>
|
||
</template>
|
||
|
||
<script setup>
|
||
import { ref, onUnmounted, getCurrentInstance } from 'vue';
|
||
import { onLoad, onUnload, onReady } from '@dcloudio/uni-app';
|
||
import center from "../../static/mark.png";
|
||
import { locationReport, historyLocation } from '@/api/index'
|
||
|
||
// 地图相关变量
|
||
const mapRef = ref(null);
|
||
const latitude = ref(null); // 默认北京纬度
|
||
const longitude = ref(null); // 默认北京经度
|
||
const scale = ref(16)
|
||
// const scale = ref(20)
|
||
let reportParams = ref({})
|
||
const polyline = ref([
|
||
{
|
||
points: [],
|
||
color: '#007AFF', // 高德蓝,更醒目
|
||
width: 15, // 适当加粗
|
||
}
|
||
]);
|
||
|
||
|
||
const markers = ref([])
|
||
let mapContext = null;
|
||
|
||
onLoad((options) => {
|
||
reportParams.value = options
|
||
uni.setKeepScreenOn({
|
||
keepScreenOn: true
|
||
})
|
||
|
||
})
|
||
|
||
onReady(() => {
|
||
if (reportParams.value.type == 'location') {
|
||
startWatch()
|
||
} else if (reportParams.value.type == 'history') {
|
||
historyLocation(reportParams.value).then(async (res) => {
|
||
mapContext = uni.createMapContext('amap');
|
||
let arr = []
|
||
res.data.forEach(item => {
|
||
arr.push({
|
||
latitude: item.lat,
|
||
longitude: item.lng
|
||
})
|
||
})
|
||
polyline.value[0].points = JSON.parse(JSON.stringify(arr))
|
||
console.log("!!!!!",polyline)
|
||
// const centerPoint = await getCenterPoint(JSON.parse(JSON.stringify(arr)))
|
||
// console.log('centerPoint', centerPoint)
|
||
|
||
// const autoScale = await calculateScaleByPoints(JSON.parse(JSON.stringify(arr))); // 调用计算方法
|
||
// scale.value = autoScale.scale; // 替换固定的scale:20
|
||
mapContext.moveToLocation({
|
||
latitude: polyline.value[0].points[0].latitude,
|
||
longitude: polyline.value[0].points[0].longitude
|
||
});
|
||
})
|
||
}
|
||
})
|
||
|
||
|
||
|
||
|
||
const fileWriter = () => {
|
||
// 请求本地系统文件对象 plus.io.PRIVATE_WWW:应用运行资源目录常量
|
||
plus.io.requestFileSystem(plus.io.PRIVATE_DOC, (fobject) => {
|
||
// fs.root是根目录操作对象DirectoryEntry
|
||
fobject.root.getFile(formatTime(new Date()) + '.txt', {
|
||
create: true
|
||
}, (fileEntry) => {
|
||
fileEntry.file((file) => {
|
||
console.log('file :>> ', file)
|
||
// create a FileWriter to write to the file
|
||
fileEntry.createWriter((writer) => {
|
||
console.log('writer :>> ', writer)
|
||
// Write data to file.
|
||
// 返回页面的数据
|
||
writer.seek(file.size)
|
||
// 写入可以加入需要写入的数据
|
||
let content = "{points:" + JSON.stringify(polyline.value[0].points) + ",pointArr:" + JSON.stringify(pointArr.value) + "}"
|
||
writer.write(content)
|
||
const fileReader = new plus.io.FileReader()
|
||
fileReader.readAsText(file, 'utf-8')
|
||
fileReader.onloadend = (evt) => {
|
||
console.log('evt :>> ', evt)
|
||
}
|
||
console.log('file info :>> ', file)
|
||
|
||
// 获取对象
|
||
plus.io.resolveLocalFileSystemURL(file.fullPath, (res) => {
|
||
res.file((localFile) => {
|
||
const localFilePath = localFile.fullPath; // 本地文件路径
|
||
console.log("localFilePath :>> ", localFilePath)
|
||
// 直接保存并打开本地文件
|
||
uni.openDocument({
|
||
filePath: localFilePath,
|
||
success: () => console.log('打开成功')
|
||
});
|
||
});
|
||
})
|
||
}, (e) => {
|
||
console.log('e :>> ', e)
|
||
})
|
||
})
|
||
})
|
||
})
|
||
}
|
||
|
||
// 1. 计算中心点(经纬度平均值)
|
||
// const getCenterPoint = (points) => {
|
||
// const sumLng = points.reduce((sum, p) => sum + p.longitude, 0);
|
||
// const sumLat = points.reduce((sum, p) => sum + p.latitude, 0);
|
||
// return {
|
||
// longitude: sumLng / points.length,
|
||
// latitude: sumLat / points.length
|
||
// };
|
||
// };
|
||
|
||
// const calculateScaleByPoints = async (points) => {
|
||
// if (points.length <= 1) return 20; // 单点默认缩放18
|
||
|
||
// // 1. 获取经纬度极值
|
||
// const extremes = getLatLngExtremes(points);
|
||
// const centerLat = (extremes.minLat + extremes.maxLat) / 2; // 轨迹中心纬度
|
||
// // 2. 计算范围宽度(米)
|
||
// const rangeWidth = getRangeWidth(extremes);
|
||
// // 3. 获取地图容器宽度(像素)
|
||
// const containerWidth = await getMapContainerWidth();
|
||
|
||
// // 4. 目标:让范围宽度占容器80%(留边距,避免轨迹贴边)
|
||
// const targetMeterPerPixel = rangeWidth / (containerWidth * 0.9);
|
||
|
||
// const scale = calculatePreciseScale(targetMeterPerPixel, centerLat);
|
||
|
||
// return { centerLat, scale };
|
||
// };
|
||
|
||
|
||
// const getLatLngExtremes = (points) => {
|
||
// if (!points.length) return null;
|
||
|
||
// const lats = points.map(p => p.latitude);
|
||
// const lngs = points.map(p => p.longitude);
|
||
|
||
// return {
|
||
// minLat: Math.min(...lats), // 最小纬度
|
||
// maxLat: Math.max(...lats), // 最大纬度
|
||
// minLng: Math.min(...lngs), // 最小经度
|
||
// maxLng: Math.max(...lngs), // 最大经度
|
||
// };
|
||
// };
|
||
|
||
|
||
// // 计算范围的东西向实际宽度(米)
|
||
// const getRangeWidth = (extremes) => {
|
||
// // 取纬度中点(减少纬度对经度距离计算的影响)
|
||
// const centerLat = (extremes.minLat + extremes.maxLat) / 2;
|
||
// // 计算同一纬度下,最小和最大经度的距离(米)
|
||
// return haversineDistance(centerLat, extremes.minLng, centerLat, extremes.maxLng);
|
||
// };
|
||
// // 2. Haversine公式:计算两点经纬度的球面距离(单位:米)
|
||
// const haversineDistance = (lat1, lon1, lat2, lon2) => {
|
||
// const R = 6371000; // 地球半径(米)
|
||
// const φ1 = (lat1 * Math.PI) / 180;
|
||
// const φ2 = (lat2 * Math.PI) / 180;
|
||
// const Δφ = ((lat2 - lat1) * Math.PI) / 180;
|
||
// const Δλ = ((lon2 - lon1) * Math.PI) / 180;
|
||
|
||
// const a = Math.sin(Δφ / 2) ** 2 + Math.cos(φ1) * Math.cos(φ2) * Math.sin(Δλ / 2) ** 2;
|
||
// const c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a));
|
||
// return R * c;
|
||
// };
|
||
|
||
|
||
// // 获取地图容器宽度(像素)- 适配uni-app
|
||
// const getMapContainerWidth = () => {
|
||
// // 获取容器节点信息
|
||
// return new Promise((resolve) => {
|
||
// uni.createSelectorQuery().in(getCurrentInstance())
|
||
// .select('.map-container')
|
||
// .boundingClientRect(rect => {
|
||
// resolve(rect.width); // 返回容器宽度(像素)
|
||
// })
|
||
// .exec();
|
||
// });
|
||
// };
|
||
|
||
// const calculatePreciseScale = (targetMeterPerPixel, centerLat) => {
|
||
// const earthRadius = 6378137; // 地球半径(高德用的WGS84坐标系,半径6378137米)
|
||
// const centerLatRad = (centerLat * Math.PI) / 180; // 中心纬度转弧度
|
||
|
||
// // 高德官方公式:米/像素 = (2 * π * R * cos(lat)) / (2^(scale + 8))
|
||
// // 反向推导:scale = log2( (2 * π * R * cos(lat)) / 米/像素 ) - 8
|
||
// const numerator = 2 * Math.PI * earthRadius * Math.cos(centerLatRad);
|
||
// const scaleLog = Math.log2(numerator / targetMeterPerPixel);
|
||
// let scale = scaleLog - 8;
|
||
|
||
// // scale必须是整数(地图scale只支持整数),同时限制范围(10-20,避免过大/过小)
|
||
// scale = Math.round(scale);
|
||
// console.log(scale, "scale")
|
||
// return Math.max(10, Math.min(20, scale));
|
||
// };
|
||
|
||
|
||
function formatTime(date) {
|
||
const year = date.getFullYear();
|
||
const month = String(date.getMonth() + 1).padStart(2, '0'); // 补 0 为两位数(如 09)
|
||
const day = String(date.getDate()).padStart(2, '0');
|
||
const hour = String(date.getHours()).padStart(2, '0');
|
||
const minute = String(date.getMinutes()).padStart(2, '0');
|
||
const second = String(date.getSeconds()).padStart(2, '0');
|
||
return `${year}-${month}-${day} ${hour}:${minute}:${second}`;
|
||
}
|
||
|
||
|
||
|
||
|
||
let pointArr = ref([
|
||
// { longitude: 116.478935, latitude: 39.997761 },
|
||
// { longitude: 116.478939, latitude: 39.997825 },
|
||
// // {longitude:116.478912,latitude: 39.998549},
|
||
// { longitude: 116.478912, latitude: 39.998549 },
|
||
// // {longitude:116.478998,latitude: 39.998555},
|
||
// { longitude: 116.478998, latitude: 39.998555 },
|
||
// { longitude: 116.479282, latitude: 39.99856 },
|
||
// { longitude: 116.479658, latitude: 39.998528 },
|
||
// { longitude: 116.480151, latitude: 39.998453 },
|
||
// // {longitude:116.480784,latitude: 39.998302},
|
||
// { longitude: 116.480784, latitude: 39.998302 },
|
||
// { longitude: 116.481149, latitude: 39.998184 },
|
||
// { longitude: 116.481573, latitude: 39.997997 },
|
||
// { longitude: 116.481863, latitude: 39.997846 },
|
||
// { longitude:116.482072,latitude: 39.997718 },
|
||
// { longitude: 116.482362, latitude: 39.997718 },
|
||
// { longitude: 116.483633, latitude: 39.998935 },
|
||
// { longitude: 116.48367, latitude: 39.998968 },
|
||
// { longitude: 116.484648, latitude: 39.999861 }
|
||
])
|
||
|
||
|
||
// 检查并请求定位权限
|
||
const requestLocationPermission = async () => {
|
||
if (plus.os.name === "Android") {
|
||
// 检查后台定位权限(Android 10+)
|
||
const hasBgPermission = await plus.android.requestPermissions(
|
||
["android.permission.ACCESS_BACKGROUND_LOCATION"]
|
||
);
|
||
// if (!hasBgPermission.granted) {
|
||
// uni.showToast({ title: "请授予后台定位权限", icon: "none" });
|
||
// return false;
|
||
// }
|
||
} else if (plus.os.name === "iOS") {
|
||
// iOS 需引导用户开启"始终允许"定位
|
||
const authStatus = await uni.getLocationAuthStatus();
|
||
if (authStatus.authStatus !== 3) { // 3 表示"始终允许"
|
||
uni.showModal({
|
||
title: "提示",
|
||
content: "请在设置中开启始终允许定位,否则锁屏后无法跟踪",
|
||
success: () => {
|
||
plus.runtime.openURL("app-settings:"); // 打开应用设置页
|
||
}
|
||
});
|
||
return false;
|
||
}
|
||
}
|
||
return true;
|
||
};
|
||
|
||
let locationWatchId = null
|
||
// 初始化地图
|
||
const startWatch = (async () => {
|
||
// 先请求权限
|
||
const hasPermission = await requestLocationPermission();
|
||
if (!hasPermission) return;
|
||
|
||
|
||
// 获取地图上下文
|
||
mapContext = uni.createMapContext('amap');
|
||
|
||
try {
|
||
// 直接在组件中实现定位功能,不再依赖外部工具类
|
||
|
||
uni.startLocationUpdate({
|
||
type: 'gcj02',
|
||
// needFullAccuracy: true,
|
||
success: res => {
|
||
console.log('开启应用接收位置消息成功')
|
||
locationWatchId = uni.onLocationChange(function (res) {
|
||
|
||
let time = formatTime(new Date())
|
||
// 检查是否已有最后一个点,且与当前点经纬度相同
|
||
const points = polyline.value[0].points;
|
||
|
||
res.time = time
|
||
// console.log('points', formatTime(new Date()));
|
||
let hasSameLastPoint = false
|
||
if (points.length) {
|
||
hasSameLastPoint = points[points.length - 1].latitude === res.latitude && points[points.length - 1].longitude === res.longitude;
|
||
}
|
||
if (markers.value.length == 0) {
|
||
markers.value = [{
|
||
id: 1,
|
||
latitude: res.latitude,
|
||
longitude: res.longitude,
|
||
iconPath: center,
|
||
width: 10, // 小车图标宽度(rpx/px,根据需求调整)
|
||
height: 10, // 小车图标高度
|
||
}]
|
||
} else {
|
||
markers.value[0].latitude = res.latitude
|
||
markers.value[0].longitude = res.longitude
|
||
}
|
||
// console.log('hasSameLastPoint', hasSameLastPoint);
|
||
if (!hasSameLastPoint) {
|
||
polyline.value[0].points.push({
|
||
latitude: res.latitude,
|
||
longitude: res.longitude,
|
||
accuracy: res.accuracy,
|
||
speed: res.speed,
|
||
altitude: res.altitude,
|
||
time: time
|
||
});
|
||
console.log(polyline.value[0].points)
|
||
latitude.value = res.latitude;
|
||
longitude.value = res.longitude;
|
||
mapContext.moveToLocation({
|
||
latitude: latitude.value,
|
||
longitude: longitude.value
|
||
});
|
||
reportParams.value.lat = res.latitude
|
||
reportParams.value.lng = res.longitude
|
||
// console.log('reportParams', reportParams.value);
|
||
locationReport(reportParams.value).then((res) => {
|
||
console.log("页面", res)
|
||
if (res.code == 401) {
|
||
console.log("停止")
|
||
stopLocationUpdate();
|
||
}
|
||
})
|
||
// console.log('纬度:' + res.latitude);
|
||
// console.log('经度:' + res.longitude);
|
||
}
|
||
// pointArr.value.push(res);
|
||
// console.log(pointArr.value)
|
||
|
||
|
||
});
|
||
|
||
},
|
||
fail: err => {
|
||
console.error('开启应用接收位置消息失败:', err)
|
||
},
|
||
complete: msg => console.log('调用开启应用接收位置消息 API 完成')
|
||
});
|
||
|
||
|
||
|
||
|
||
} catch (error) {
|
||
console.error('获取位置失败:', error);
|
||
uni.showToast({
|
||
title: '获取位置失败,使用默认位置',
|
||
icon: 'none'
|
||
});
|
||
}
|
||
});
|
||
|
||
|
||
// 页面卸载时清理资源
|
||
onUnmounted(() => {
|
||
stopLocationUpdate();
|
||
});
|
||
onUnload(() => {
|
||
stopLocationUpdate();
|
||
});
|
||
const stopLocationUpdate = () => {
|
||
if (locationWatchId) {
|
||
uni.offLocationChange();
|
||
locationWatchId = null;
|
||
}
|
||
uni.stopLocationUpdate({
|
||
success: () => console.log('已停止位置更新'),
|
||
fail: (err) => console.error('停止位置更新失败:', err)
|
||
});
|
||
};
|
||
|
||
</script>
|
||
|
||
<style scoped>
|
||
.container {
|
||
display: flex;
|
||
flex-direction: column;
|
||
height: calc(100vh - env(safe-area-inset-bottom));
|
||
}
|
||
|
||
.map-container {
|
||
flex: 1;
|
||
position: relative;
|
||
}
|
||
|
||
.center-img {
|
||
height: 80rpx;
|
||
left: 50%;
|
||
margin-left: -40rpx;
|
||
margin-top: -88rpx;
|
||
top: 50%;
|
||
width: 80rpx;
|
||
z-index: 9999;
|
||
position: absolute;
|
||
}
|
||
|
||
.position-img {
|
||
bottom: 32rpx;
|
||
height: 66rpx;
|
||
right: 16rpx;
|
||
width: 66rpx;
|
||
border-radius: 50%;
|
||
background-color: #fff;
|
||
overflow: hidden;
|
||
z-index: 9999;
|
||
position: absolute;
|
||
}
|
||
</style> |