Files
Driver-program/src/pages/vehicle/index.vue
2026-02-07 16:11:40 +08:00

550 lines
12 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<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>