This commit is contained in:
2026-02-07 16:11:40 +08:00
parent 7998e702a1
commit 71314c5b12
63 changed files with 33553 additions and 0 deletions

549
src/pages/vehicle/index.vue Normal file
View File

@@ -0,0 +1,549 @@
<template>
<view class="vehicle-page">
<view class="header">
<text class="title">车辆定位</text>
<text class="subtitle">获取当前车辆位置信息</text>
</view>
<view class="content">
<!-- 获取位置按钮 -->
<button class="get-location-btn" @click="handleGetLocation" :disabled="isLoading">
{{ isLoading ? '获取中...' : '获取当前位置' }}
</button>
<!-- 位置信息显示 -->
<view v-if="locationInfo" class="location-info">
<view class="info-card">
<view class="card-title">
<Location2 size="24" color="#667eea" />
<text class="title-text">位置信息</text>
</view>
<view class="info-item">
<text class="label">纬度</text>
<text class="value">{{ locationInfo.latitude }}</text>
</view>
<view class="info-item">
<text class="label">经度</text>
<text class="value">{{ locationInfo.longitude }}</text>
</view>
<view class="info-item">
<text class="label">高度</text>
<text class="value">{{ locationInfo.altitude || '未知' }}m</text>
</view>
<view class="info-item">
<text class="label">精确度</text>
<text class="value">{{ locationInfo.accuracy }}m</text>
</view>
<view class="info-item">
<text class="label">速度</text>
<text class="value">{{ locationInfo.speed || 0 }}m/s</text>
</view>
<view class="info-item">
<text class="label">方向角</text>
<text class="value">{{ locationInfo.heading || 0 }}°</text>
</view>
</view>
</view>
<!-- 地址信息显示 -->
<view v-if="addressInfo" class="address-info">
<view class="info-card">
<view class="card-title">
<Location size="24" color="#667eea" />
<text class="title-text">地址信息</text>
</view>
<view class="address-item">
<text class="address-text">{{ addressInfo.address }}</text>
</view>
<view v-if="addressInfo.formatted_addresses" class="address-item">
<text class="address-text">{{ addressInfo.formatted_addresses.recommend }}</text>
</view>
</view>
</view>
<!-- 操作按钮 -->
<view v-if="locationInfo" class="action-buttons">
<button class="action-btn primary" @click="openMap">在地图中查看</button>
<button class="action-btn secondary" @click="copyLocation">复制坐标</button>
<button class="action-btn secondary" @click="shareLocation">分享位置</button>
</view>
<!-- 历史记录 -->
<view v-if="locationHistory.length > 0" class="history-section">
<view class="section-title">
<Clock size="20" color="#666" />
<text class="title-text">历史记录</text>
</view>
<view class="history-list">
<view v-for="(item, index) in locationHistory" :key="index" class="history-item">
<view class="history-time">{{ item.time }}</view>
<view class="history-coords">{{ item.latitude }}, {{ item.longitude }}</view>
<view class="history-actions">
<button class="mini-btn" @click="viewHistoryLocation(item)">查看</button>
<button class="mini-btn" @click="deleteHistoryItem(index)">删除</button>
</view>
</view>
</view>
</view>
</view>
<!-- Toast 提示 -->
<nut-toast v-model:visible="showToast" :msg="toastMsg" />
</view>
</template>
<script setup>
import { ref, onMounted } from 'vue'
import { Toast } from '@nutui/nutui-taro'
import { Location2, Location, Clock } from '@nutui/icons-vue'
import Taro from '@tarojs/taro'
import { checkLoginAndRedirect } from '../../utils/auth.js'
// 状态管理
const isLoading = ref(false)
const locationInfo = ref(null)
const addressInfo = ref(null)
const showToast = ref(false)
const toastMsg = ref('')
const locationHistory = ref([])
// 显示提示信息
const showMessage = (msg) => {
toastMsg.value = msg
showToast.value = true
}
// 检查位置权限
const checkLocationPermission = () => {
return new Promise((resolve) => {
Taro.getSetting({
success: (res) => {
if (res.authSetting['scope.userLocation']) {
resolve(true)
} else {
resolve(false)
}
},
fail: () => {
resolve(false)
}
})
})
}
// 请求位置权限
const requestLocationPermission = () => {
return new Promise((resolve) => {
Taro.authorize({
scope: 'scope.userLocation',
success: () => {
resolve(true)
},
fail: () => {
resolve(false)
}
})
})
}
// 打开位置设置页面
const openLocationSetting = () => {
Taro.openSetting({
success: (res) => {
if (res.authSetting['scope.userLocation']) {
showMessage('位置权限已开启')
}
}
})
}
// 获取当前位置
const getCurrentLocation = (options = {}) => {
return new Promise((resolve, reject) => {
const defaultOptions = {
type: 'wgs84',
altitude: true,
isHighAccuracy: true,
highAccuracyExpireTime: 4000,
success: (res) => {
console.log('获取位置成功:', res)
resolve(res)
},
fail: (error) => {
console.error('获取位置失败:', error)
reject(error)
}
}
Taro.getLocation({ ...defaultOptions, ...options })
})
}
// 获取位置信息(包含权限检查)
const getLocationWithPermission = async (options = {}) => {
try {
// 检查权限
const hasPermission = await checkLocationPermission()
if (!hasPermission) {
// 请求权限
const granted = await requestLocationPermission()
if (!granted) {
// 权限被拒绝,引导用户手动开启
Taro.showModal({
title: '位置权限',
content: '获取位置信息需要开启位置权限,请在设置中开启位置权限',
showCancel: true,
cancelText: '取消',
confirmText: '去设置',
success: (res) => {
if (res.confirm) {
openLocationSetting()
}
}
})
throw new Error('位置权限被拒绝')
}
}
// 获取位置信息
const location = await getCurrentLocation(options)
return location
} catch (error) {
console.error('获取位置信息失败:', error)
throw error
}
}
// 处理获取位置
const handleGetLocation = async () => {
isLoading.value = true
try {
// 获取位置信息
const location = await getLocationWithPermission({
type: 'wgs84',
altitude: true,
isHighAccuracy: true
})
locationInfo.value = location
showMessage('获取位置成功')
// 保存到历史记录
const now = new Date()
const timeStr = `${now.getHours().toString().padStart(2, '0')}:${now.getMinutes().toString().padStart(2, '0')}`
const historyItem = {
...location,
time: timeStr,
timestamp: now.getTime()
}
locationHistory.value.unshift(historyItem)
// 限制历史记录数量
if (locationHistory.value.length > 10) {
locationHistory.value = locationHistory.value.slice(0, 10)
}
// 保存到本地存储
Taro.setStorageSync('locationHistory', locationHistory.value)
} catch (error) {
console.error('获取位置失败:', error)
showMessage('获取位置失败:' + error.message)
} finally {
isLoading.value = false
}
}
// 在地图中查看位置
const openMap = () => {
if (!locationInfo.value) return
Taro.openLocation({
latitude: locationInfo.value.latitude,
longitude: locationInfo.value.longitude,
name: '当前位置',
address: addressInfo.value?.address || '未知地址',
scale: 18
})
}
// 复制坐标
const copyLocation = () => {
if (!locationInfo.value) return
const coordinate = `${locationInfo.value.latitude}, ${locationInfo.value.longitude}`
Taro.setClipboardData({
data: coordinate,
success: () => {
showMessage('坐标已复制到剪贴板')
},
fail: () => {
showMessage('复制失败')
}
})
}
// 分享位置
const shareLocation = () => {
if (!locationInfo.value) return
const shareText = `我的位置:${locationInfo.value.latitude}, ${locationInfo.value.longitude}`
Taro.showShareMenu({
withShareTicket: true,
success: () => {
showMessage('位置已分享')
},
fail: () => {
showMessage('分享失败')
}
})
}
// 查看历史位置
const viewHistoryLocation = (item) => {
locationInfo.value = item
showMessage('已加载历史位置')
}
// 删除历史记录项
const deleteHistoryItem = (index) => {
Taro.showModal({
title: '确认删除',
content: '确定要删除这条历史记录吗?',
success: (res) => {
if (res.confirm) {
locationHistory.value.splice(index, 1)
Taro.setStorageSync('locationHistory', locationHistory.value)
showMessage('删除成功')
}
}
})
}
// 页面加载时读取历史记录
onMounted(() => {
// 检查登录状态
if (!checkLoginAndRedirect()) {
return
}
try {
const history = Taro.getStorageSync('locationHistory')
if (history && Array.isArray(history)) {
locationHistory.value = history
}
} catch (error) {
console.error('读取历史记录失败:', error)
}
})
</script>
<style>
.vehicle-page {
min-height: 100vh;
background: #f5f5f5;
padding: 40rpx;
}
.header {
text-align: center;
margin-bottom: 60rpx;
}
.title {
font-size: 48rpx;
font-weight: bold;
color: #333;
display: block;
margin-bottom: 20rpx;
}
.subtitle {
font-size: 28rpx;
color: #666;
}
.content {
background: white;
border-radius: 24rpx;
padding: 40rpx;
box-shadow: 0 4rpx 20rpx rgba(0, 0, 0, 0.1);
}
.get-location-btn {
width: 100%;
height: 88rpx;
background: #667eea;
color: white;
border: none;
border-radius: 16rpx;
font-size: 32rpx;
font-weight: bold;
margin-bottom: 40rpx;
}
.get-location-btn:disabled {
background: #ccc;
color: #999;
}
.location-info,
.address-info {
margin-bottom: 40rpx;
}
.info-card {
background: #f8f9fa;
border-radius: 16rpx;
padding: 30rpx;
}
.card-title {
display: flex;
align-items: center;
margin-bottom: 20rpx;
}
.title-text {
font-size: 32rpx;
font-weight: bold;
color: #333;
margin-left: 12rpx;
}
.info-item {
display: flex;
justify-content: space-between;
align-items: center;
padding: 20rpx 0;
border-bottom: 1rpx solid #e0e0e0;
}
.info-item:last-child {
border-bottom: none;
}
.label {
font-size: 28rpx;
color: #666;
font-weight: 500;
}
.value {
font-size: 28rpx;
color: #333;
font-weight: bold;
}
.address-item {
margin-bottom: 10rpx;
}
.address-text {
font-size: 28rpx;
color: #666;
line-height: 1.5;
}
.action-buttons {
display: flex;
gap: 20rpx;
margin-bottom: 40rpx;
}
.action-btn {
flex: 1;
height: 72rpx;
border: none;
border-radius: 12rpx;
font-size: 28rpx;
font-weight: 500;
}
.action-btn.primary {
background: #667eea;
color: white;
}
.action-btn.secondary {
background: #f0f0f0;
color: #333;
}
.action-btn:active {
opacity: 0.8;
}
.history-section {
margin-top: 40rpx;
}
.section-title {
display: flex;
align-items: center;
margin-bottom: 20rpx;
}
.section-title .title-text {
font-size: 28rpx;
color: #666;
margin-left: 8rpx;
}
.history-list {
background: #f8f9fa;
border-radius: 16rpx;
padding: 20rpx;
}
.history-item {
display: flex;
justify-content: space-between;
align-items: center;
padding: 20rpx 0;
border-bottom: 1rpx solid #e0e0e0;
}
.history-item:last-child {
border-bottom: none;
}
.history-time {
font-size: 24rpx;
color: #999;
min-width: 100rpx;
}
.history-coords {
font-size: 26rpx;
color: #666;
flex: 1;
margin: 0 20rpx;
}
.history-actions {
display: flex;
gap: 10rpx;
}
.mini-btn {
padding: 8rpx 16rpx;
background: #667eea;
color: white;
border: none;
border-radius: 8rpx;
font-size: 22rpx;
}
.mini-btn:active {
opacity: 0.8;
}
</style>