feat:新增地图模块,用于查找附近设备场地
This commit is contained in:
+458
-351
@@ -1,420 +1,527 @@
|
||||
<template>
|
||||
<view class="map-container">
|
||||
<map
|
||||
id="mainMap"
|
||||
class="map"
|
||||
:key="mapKey"
|
||||
:longitude="mapCenter.longitude"
|
||||
:latitude="mapCenter.latitude"
|
||||
:scale="mapZoom"
|
||||
:markers="mapMarkers"
|
||||
:show-location="false"
|
||||
:enable-scroll="true"
|
||||
:enable-zoom="true"
|
||||
:enable-rotate="false"
|
||||
:show-compass="false"
|
||||
@markertap="handleMarkerTap"
|
||||
@regionchange="handleRegionChange"
|
||||
></map>
|
||||
|
||||
<!-- 地图加载状态 -->
|
||||
<view class="map-loading" v-if="!mapCenter.longitude">
|
||||
<view class="loading-content">
|
||||
<view class="loading-spinner"></view>
|
||||
<text>地图加载中...</text>
|
||||
<!-- 地图容器 -->
|
||||
<view class="map-wrapper">
|
||||
<!-- 使用小程序原生地图组件 -->
|
||||
<map id="map" class="native-map" :longitude="mapCenter.longitude" :latitude="mapCenter.latitude"
|
||||
:markers="mapMarkers" :scale="mapZoom" :show-location="true" @regionchange="onMapRegionChange"
|
||||
@markertap="onMapMarkerTap" @callouttap="onCalloutTap" @updated="onMapUpdated"
|
||||
@error="onMapError"></map>
|
||||
|
||||
|
||||
|
||||
<!-- 地图加载状态 -->
|
||||
<view class="map-loading" v-if="isLoading">
|
||||
<view class="loading-content">
|
||||
<view class="loading-spinner"></view>
|
||||
<text>地图加载中...</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 地图上的浮动按钮 -->
|
||||
|
||||
<!-- 地图控制按钮 -->
|
||||
<view class="map-controls">
|
||||
<view class="control-btn location-control" @click="handleRelocate">
|
||||
<image class="control-icon" src="@/static/scan-icon.png" mode="aspectFit" />
|
||||
<text>我的位置</text>
|
||||
<view class="control-btn location-control" @tap="handleRelocate">
|
||||
<!-- <image class="control-icon" src="/static/scan-icon.png" mode="aspectFit" /> -->
|
||||
<uv-icon name="map-fill" size="18"></uv-icon>
|
||||
<text style="margin-left: 8rpx;">我的位置</text>
|
||||
</view>
|
||||
<view class="control-btn scan-control main-btn" @click="handleScan">
|
||||
<image class="control-icon" src="@/static/scan-icon.png" mode="aspectFit" />
|
||||
<view class="control-btn scan-control main-btn" @tap="handleScan">
|
||||
<image class="control-icon" src="/static/scan-icon.png" mode="aspectFit" />
|
||||
<text>扫码使用</text>
|
||||
</view>
|
||||
<view class="control-btn list-control" @click="handleShowList">
|
||||
<image class="control-icon" src="@/static/scan-icon.png" mode="aspectFit" />
|
||||
<text>附近场地</text>
|
||||
<view class="control-btn list-control" @tap="handleShowList">
|
||||
<image class="control-icon" src="/static/map.png" mode="aspectFit" />
|
||||
<text>附近设备</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, computed, watch, nextTick, onUnmounted, onMounted, getCurrentInstance } from 'vue'
|
||||
import AmapUtil from '@/utils/amap.js'
|
||||
import {
|
||||
ref,
|
||||
computed,
|
||||
watch,
|
||||
onMounted,
|
||||
onUnmounted,
|
||||
nextTick
|
||||
} from 'vue'
|
||||
// 导入地图工具函数
|
||||
import {
|
||||
calculateDistanceSync
|
||||
} from '../utils/mapUtils.js'
|
||||
|
||||
// Props
|
||||
const props = defineProps({
|
||||
userLocation: {
|
||||
type: Object,
|
||||
default: null
|
||||
},
|
||||
positionList: {
|
||||
type: Array,
|
||||
default: () => []
|
||||
},
|
||||
filteredPositions: {
|
||||
type: Array,
|
||||
default: () => []
|
||||
},
|
||||
searchKeyword: {
|
||||
type: String,
|
||||
default: ''
|
||||
}
|
||||
})
|
||||
// 引用折叠面板组件的ref
|
||||
const collapseRef = ref(null)
|
||||
|
||||
// Emits
|
||||
const emit = defineEmits([
|
||||
'relocate',
|
||||
'scan',
|
||||
'showList',
|
||||
'markerTap',
|
||||
'mapCenterChange'
|
||||
])
|
||||
// 使用指南步骤
|
||||
const guideSteps = ref([{
|
||||
title: '扫码使用',
|
||||
desc: '找到附近设备,扫描设备上的二维码'
|
||||
},
|
||||
{
|
||||
title: '免押金支付',
|
||||
desc: '无需支付押金,使用支付分免押即可完成租借'
|
||||
},
|
||||
{
|
||||
title: '开始使用',
|
||||
desc: '设备自动解锁,风扇弹出后取出即可开始使用'
|
||||
},
|
||||
{
|
||||
title: '归还设备',
|
||||
desc: '使用完毕后,按照设备规格要求将风扇还入即可结束订单'
|
||||
}
|
||||
])
|
||||
|
||||
// 响应式数据
|
||||
const mapKey = ref(0)
|
||||
const mapZoom = ref(16) // 地图缩放级别 (16级约等于1:500m比例)
|
||||
const mapCenter = ref({
|
||||
longitude: 116.397128,
|
||||
latitude: 39.916527
|
||||
})
|
||||
const loadPositionsTimer = ref(null)
|
||||
const isMapInitialized = ref(false) // 标记地图是否已初始化
|
||||
|
||||
// 获取组件实例
|
||||
const instance = getCurrentInstance()
|
||||
|
||||
// 方法定义(需要在watch之前定义)
|
||||
const updateMapCenter = (longitude, latitude) => {
|
||||
// 检查是否真的需要更新
|
||||
if (mapCenter.value.longitude === longitude && mapCenter.value.latitude === latitude) {
|
||||
return
|
||||
}
|
||||
|
||||
mapCenter.value = { longitude, latitude }
|
||||
mapZoom.value = 16 // 确保缩放级别正确
|
||||
|
||||
// 延迟调用地图API,确保DOM已更新
|
||||
nextTick(() => {
|
||||
setTimeout(() => {
|
||||
const mapContext = uni.createMapContext('mainMap')
|
||||
if (mapContext) {
|
||||
mapContext.setCenterOffset({
|
||||
longitude,
|
||||
latitude,
|
||||
success: () => {},
|
||||
fail: () => {
|
||||
// 备用方案
|
||||
mapContext.includePoints({
|
||||
points: [{ longitude, latitude }],
|
||||
padding: [0, 0, 0, 0]
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
}, 200)
|
||||
// Props
|
||||
const props = defineProps({
|
||||
userLocation: {
|
||||
type: Object,
|
||||
default: null
|
||||
},
|
||||
positionList: {
|
||||
type: Array,
|
||||
default: () => []
|
||||
},
|
||||
filteredPositions: {
|
||||
type: Array,
|
||||
default: () => []
|
||||
},
|
||||
searchKeyword: {
|
||||
type: String,
|
||||
default: ''
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
const handleRelocate = () => {
|
||||
emit('relocate')
|
||||
}
|
||||
// Emits
|
||||
const emit = defineEmits([
|
||||
'relocate',
|
||||
'scan',
|
||||
'showList',
|
||||
'markerTap',
|
||||
'mapCenterChange'
|
||||
])
|
||||
|
||||
const handleScan = () => {
|
||||
emit('scan')
|
||||
}
|
||||
// 响应式数据
|
||||
const isLoading = ref(true)
|
||||
const mapCenter = ref({
|
||||
longitude: 116.397128,
|
||||
latitude: 39.916527
|
||||
})
|
||||
const mapZoom = ref(17)
|
||||
const mapMarkers = ref([]) // 用于地图组件的markers
|
||||
const mapContext = ref(null) // 地图上下文
|
||||
|
||||
const handleShowList = () => {
|
||||
emit('showList')
|
||||
}
|
||||
// 方法
|
||||
const updateMapMarkers = () => {
|
||||
mapMarkers.value = []
|
||||
|
||||
const handleMarkerTap = (e) => {
|
||||
if (!e.detail || typeof e.detail.markerId === 'undefined') {
|
||||
return
|
||||
}
|
||||
|
||||
const markerId = e.detail.markerId
|
||||
if (markerId === 9999) {
|
||||
uni.showToast({
|
||||
title: '这是您的位置',
|
||||
icon: 'none'
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
const position = props.filteredPositions[markerId]
|
||||
if (position) {
|
||||
emit('markerTap', position)
|
||||
}
|
||||
}
|
||||
|
||||
const handleRegionChange = (e) => {
|
||||
if (e.detail.type === 'end') {
|
||||
const { center } = e.detail
|
||||
|
||||
if (!center || typeof center.longitude === 'undefined' || typeof center.latitude === 'undefined') {
|
||||
return
|
||||
}
|
||||
|
||||
mapCenter.value = {
|
||||
longitude: center.longitude,
|
||||
latitude: center.latitude
|
||||
}
|
||||
|
||||
mapZoom.value = 16 // 确保缩放级别保持正确
|
||||
|
||||
// 清除之前的定时器
|
||||
if (loadPositionsTimer.value) {
|
||||
clearTimeout(loadPositionsTimer.value)
|
||||
}
|
||||
|
||||
// 设置防抖定时器,500ms后执行
|
||||
loadPositionsTimer.value = setTimeout(() => {
|
||||
emit('mapCenterChange', mapCenter.value)
|
||||
}, 500)
|
||||
}
|
||||
}
|
||||
|
||||
// 计算属性 - 地图标记
|
||||
const mapMarkers = computed(() => {
|
||||
const markers = []
|
||||
|
||||
// 添加场地标记
|
||||
props.filteredPositions.forEach((item, index) => {
|
||||
if (item.longitude && item.latitude) {
|
||||
markers.push({
|
||||
id: index,
|
||||
longitude: parseFloat(item.longitude),
|
||||
latitude: parseFloat(item.latitude),
|
||||
title: item.name,
|
||||
iconPath: '/static/scan-icon.png',
|
||||
width: 30,
|
||||
height: 30,
|
||||
// 添加用户位置标记
|
||||
if (props.userLocation) {
|
||||
mapMarkers.value.push({
|
||||
id: 0, // ID必须是数字
|
||||
// iconPath: '/static/scan-icon.png',
|
||||
width: 32,
|
||||
height: 32,
|
||||
latitude: props.userLocation.latitude,
|
||||
longitude: props.userLocation.longitude,
|
||||
title: '我的位置',
|
||||
callout: {
|
||||
content: item.name,
|
||||
fontSize: 14,
|
||||
borderRadius: 8,
|
||||
bgColor: '#ffffff',
|
||||
padding: 10,
|
||||
display: 'BYCLICK'
|
||||
content: '我的位置',
|
||||
color: '#ffffff',
|
||||
fontSize: 12,
|
||||
borderRadius: 4,
|
||||
bgColor: '#2196F3',
|
||||
padding: 6,
|
||||
display: 'BYCLICK' // 点击时显示
|
||||
},
|
||||
customCallout: {
|
||||
anchorX: 0,
|
||||
anchorY: 0
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// 添加位置点标记
|
||||
if (props.filteredPositions && props.filteredPositions.length > 0) {
|
||||
props.filteredPositions.forEach((pos, index) => {
|
||||
if (pos.longitude && pos.latitude) {
|
||||
// 验证纬度值是否在有效范围内
|
||||
const lat = parseFloat(pos.latitude);
|
||||
const lng = parseFloat(pos.longitude);
|
||||
|
||||
// 检查纬度是否在-90到90之间,经度是否在-180到180之间
|
||||
if (lat >= -90 && lat <= 90 && lng >= -180 && lng <= 180) {
|
||||
mapMarkers.value.push({
|
||||
id: index + 1, // ID必须是数字,避免和用户位置的ID冲突
|
||||
// iconPath: '/static/scan-icon.png',
|
||||
width: 30,
|
||||
height: 30,
|
||||
latitude: lat,
|
||||
longitude: lng,
|
||||
title: pos.name,
|
||||
position: pos, // 存储原始位置数据,用于事件处理
|
||||
callout: {
|
||||
content: pos.name,
|
||||
color: '#333333',
|
||||
fontSize: 12,
|
||||
borderRadius: 4,
|
||||
bgColor: '#ffffff',
|
||||
padding: 6,
|
||||
display: 'BYCLICK' // 点击时显示
|
||||
}
|
||||
})
|
||||
} else {
|
||||
console.warn(`忽略无效坐标: ${pos.name}, 纬度=${lat}, 经度=${lng}`);
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
isLoading.value = false
|
||||
}
|
||||
|
||||
// 移动地图到指定位置
|
||||
const moveToLocation = (location) => {
|
||||
if (!location || !location.longitude || !location.latitude) return
|
||||
|
||||
if (mapContext.value) {
|
||||
mapContext.value.moveToLocation({
|
||||
longitude: location.longitude,
|
||||
latitude: location.latitude,
|
||||
success: () => {
|
||||
console.log('地图已移动到指定位置')
|
||||
},
|
||||
fail: (error) => {
|
||||
console.error('移动地图失败:', error)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// 监听用户位置变化
|
||||
watch(() => props.userLocation, (newLocation) => {
|
||||
if (newLocation && newLocation.longitude && newLocation.latitude) {
|
||||
mapCenter.value = {
|
||||
longitude: newLocation.longitude,
|
||||
latitude: newLocation.latitude
|
||||
}
|
||||
updateMapMarkers()
|
||||
moveToLocation(newLocation)
|
||||
}
|
||||
}, {
|
||||
immediate: true,
|
||||
deep: true
|
||||
})
|
||||
|
||||
// 添加用户位置标记
|
||||
if (props.userLocation) {
|
||||
markers.push({
|
||||
id: 9999, // 特殊ID标识用户位置
|
||||
longitude: props.userLocation.longitude,
|
||||
latitude: props.userLocation.latitude,
|
||||
title: '我的位置',
|
||||
iconPath: '/static/scan-icon.png',
|
||||
width: 32,
|
||||
height: 32,
|
||||
callout: {
|
||||
content: '我的位置',
|
||||
fontSize: 14,
|
||||
borderRadius: 8,
|
||||
bgColor: '#2196F3',
|
||||
color: '#ffffff',
|
||||
padding: 10,
|
||||
display: 'BYCLICK'
|
||||
// 监听位置列表变化
|
||||
watch(() => props.filteredPositions, (newPositions) => {
|
||||
updateMapMarkers()
|
||||
}, {
|
||||
deep: true
|
||||
})
|
||||
|
||||
// 地图加载完成事件
|
||||
const onMapUpdated = () => {
|
||||
isLoading.value = false
|
||||
}
|
||||
|
||||
// 地图区域变化事件
|
||||
const onMapRegionChange = (e) => {
|
||||
// 当地图区域变化结束时,更新mapCenter
|
||||
if (e.type === 'end' && e.causedBy === 'drag') {
|
||||
// 获取地图中心点
|
||||
if (mapContext.value) {
|
||||
mapContext.value.getCenterLocation({
|
||||
success: (res) => {
|
||||
if (res.longitude && res.latitude) {
|
||||
mapCenter.value = {
|
||||
longitude: res.longitude,
|
||||
latitude: res.latitude
|
||||
}
|
||||
emit('mapCenterChange', mapCenter.value)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 标记点点击事件
|
||||
const onMapMarkerTap = (e) => {
|
||||
const markerId = e.markerId
|
||||
const marker = mapMarkers.value.find(item => item.id === markerId)
|
||||
|
||||
if (marker) {
|
||||
if (markerId === 0) { // 用户位置标记
|
||||
uni.showToast({
|
||||
title: '这是您的位置',
|
||||
icon: 'none'
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
if (marker.position) {
|
||||
emit('markerTap', marker.position)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 标记点气泡点击事件
|
||||
const onCalloutTap = (e) => {
|
||||
const markerId = e.markerId
|
||||
const marker = mapMarkers.value.find(item => item.id === markerId)
|
||||
|
||||
if (marker && marker.position) {
|
||||
emit('markerTap', marker.position)
|
||||
}
|
||||
}
|
||||
|
||||
// 地图错误事件
|
||||
const onMapError = (error) => {
|
||||
console.error('地图加载失败:', error)
|
||||
isLoading.value = false
|
||||
}
|
||||
|
||||
const handleRelocate = () => {
|
||||
emit('relocate')
|
||||
}
|
||||
|
||||
const handleScan = () => {
|
||||
emit('scan')
|
||||
}
|
||||
|
||||
const handleShowList = () => {
|
||||
emit('showList')
|
||||
}
|
||||
|
||||
// 生命周期钩子
|
||||
onMounted(() => {
|
||||
// 初始化地图上下文
|
||||
nextTick(() => {
|
||||
// 需要使用nextTick确保地图组件已经渲染
|
||||
mapContext.value = uni.createMapContext('map')
|
||||
updateMapMarkers()
|
||||
|
||||
// 初始化折叠面板
|
||||
if (collapseRef.value) {
|
||||
collapseRef.value.init()
|
||||
}
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
return markers
|
||||
})
|
||||
onUnmounted(() => {
|
||||
// 清理工作
|
||||
mapContext.value = null
|
||||
})
|
||||
|
||||
// 监听用户位置变化,更新地图中心
|
||||
watch(() => props.userLocation, (newLocation) => {
|
||||
if (newLocation && newLocation.longitude && newLocation.latitude && !isMapInitialized.value) {
|
||||
updateMapCenter(newLocation.longitude, newLocation.latitude)
|
||||
isMapInitialized.value = true
|
||||
}
|
||||
}, { immediate: true, deep: true })
|
||||
// 折叠面板事件处理
|
||||
const onCollapseChange = (names) => {}
|
||||
|
||||
// 组件挂载时的初始化
|
||||
onMounted(() => {
|
||||
// 现在组件只在有用户位置时才会渲染,所以不需要默认位置
|
||||
})
|
||||
const onCollapseOpen = (names) => {}
|
||||
|
||||
// 组件卸载时清理
|
||||
onUnmounted(() => {
|
||||
if (loadPositionsTimer.value) {
|
||||
clearTimeout(loadPositionsTimer.value)
|
||||
}
|
||||
})
|
||||
const onCollapseClose = (names) => {}
|
||||
|
||||
// 暴露给父组件的方法
|
||||
defineExpose({
|
||||
mapCenter: computed(() => mapCenter.value)
|
||||
})
|
||||
// 暴露给父组件的方法
|
||||
defineExpose({
|
||||
mapCenter: computed(() => mapCenter.value),
|
||||
moveToLocation,
|
||||
updateMapMarkers,
|
||||
initCollapse: () => {
|
||||
if (collapseRef.value) {
|
||||
collapseRef.value.init()
|
||||
}
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
/* 地图容器 */
|
||||
.map-container {
|
||||
flex: 1;
|
||||
position: relative;
|
||||
height: 100vh;
|
||||
width: 100%;
|
||||
/* 地图容器 */
|
||||
.map-container {
|
||||
flex: 1;
|
||||
position: relative;
|
||||
height: 60vh;
|
||||
/* 增加高度 */
|
||||
width: 92%;
|
||||
/* 略微增加宽度 */
|
||||
margin: 10rpx auto 30rpx;
|
||||
/* 调整上下间距,左右自动居中 */
|
||||
border-radius: 24rpx;
|
||||
/* 添加圆角 */
|
||||
overflow: hidden;
|
||||
/* 确保圆角生效 */
|
||||
box-shadow: 0 4rpx 16rpx rgba(0, 0, 0, 0.1);
|
||||
/* 添加阴影效果 */
|
||||
|
||||
// min-height: 400rpx; /* 确保有最小高度 */
|
||||
.map-wrapper {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
border-radius: 24rpx;
|
||||
/* 内层也添加圆角 */
|
||||
|
||||
.map {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
// min-height: 400rpx; /* 确保地图有最小高度 */
|
||||
}
|
||||
|
||||
.map-loading {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background-color: rgba(255, 255, 255, 0.8);
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
z-index: 10;
|
||||
|
||||
.loading-content {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
|
||||
.loading-spinner {
|
||||
width: 60rpx;
|
||||
height: 60rpx;
|
||||
border: 8rpx solid #f3f3f3;
|
||||
border-top: 8rpx solid #3498db;
|
||||
border-radius: 50%;
|
||||
animation: spin 1s linear infinite;
|
||||
margin-bottom: 20rpx;
|
||||
}
|
||||
|
||||
text {
|
||||
font-size: 32rpx;
|
||||
color: #333;
|
||||
font-weight: 500;
|
||||
.native-map {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
display: block;
|
||||
border-radius: 24rpx;
|
||||
/* 地图也添加圆角 */
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.map-controls {
|
||||
position: absolute;
|
||||
right: 30rpx;
|
||||
bottom: 20rpx;
|
||||
// margin-bottom: 30rpx;
|
||||
left: 30rpx;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
gap: 30rpx;
|
||||
|
||||
.control-btn {
|
||||
min-width: 140rpx;
|
||||
height: 80rpx;
|
||||
background: #ffffff;
|
||||
border-radius: 40rpx;
|
||||
.map-loading {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background-color: rgba(255, 255, 255, 0.8);
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
box-shadow: 0 4rpx 12rpx rgba(0, 0, 0, 0.1);
|
||||
transition: all 0.2s ease;
|
||||
padding: 0 20rpx;
|
||||
align-items: center;
|
||||
z-index: 10;
|
||||
|
||||
&:active {
|
||||
transform: scale(0.95);
|
||||
}
|
||||
.loading-content {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
|
||||
.control-icon {
|
||||
width: 32rpx;
|
||||
height: 32rpx;
|
||||
margin-right: 12rpx;
|
||||
}
|
||||
|
||||
text {
|
||||
font-size: 26rpx;
|
||||
color: #333;
|
||||
white-space: nowrap;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
&.main-btn {
|
||||
min-width: 160rpx;
|
||||
height: 90rpx;
|
||||
box-shadow: 0 6rpx 20rpx rgba(33, 150, 243, 0.4);
|
||||
transform: translateY(-5rpx);
|
||||
|
||||
.control-icon {
|
||||
width: 36rpx;
|
||||
height: 36rpx;
|
||||
margin-right: 16rpx;
|
||||
.loading-spinner {
|
||||
width: 60rpx;
|
||||
height: 60rpx;
|
||||
border: 8rpx solid #f3f3f3;
|
||||
border-top: 8rpx solid #3498db;
|
||||
border-radius: 50%;
|
||||
animation: spin 1s linear infinite;
|
||||
margin-bottom: 20rpx;
|
||||
}
|
||||
|
||||
text {
|
||||
font-size: 28rpx;
|
||||
font-weight: 600;
|
||||
font-size: 32rpx;
|
||||
color: #333;
|
||||
font-weight: 500;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.map-controls {
|
||||
position: absolute;
|
||||
right: 20rpx;
|
||||
bottom: 20rpx;
|
||||
left: 20rpx;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
gap: 20rpx;
|
||||
|
||||
.control-btn {
|
||||
min-width: 120rpx;
|
||||
/* 减小按钮宽度 */
|
||||
height: 70rpx;
|
||||
/* 减小按钮高度 */
|
||||
background: #ffffff;
|
||||
border-radius: 35rpx;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
box-shadow: 0 4rpx 12rpx rgba(0, 0, 0, 0.1);
|
||||
transition: all 0.2s ease;
|
||||
padding: 0 16rpx;
|
||||
|
||||
&:active {
|
||||
transform: translateY(-5rpx) scale(0.95);
|
||||
transform: scale(0.95);
|
||||
}
|
||||
|
||||
.control-icon {
|
||||
width: 32rpx;
|
||||
height: 32rpx;
|
||||
margin-right: 12rpx;
|
||||
}
|
||||
|
||||
text {
|
||||
font-size: 26rpx;
|
||||
color: #333;
|
||||
white-space: nowrap;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
&.main-btn {
|
||||
min-width: 140rpx;
|
||||
/* 减小主按钮宽度 */
|
||||
height: 80rpx;
|
||||
/* 减小主按钮高度 */
|
||||
box-shadow: 0 6rpx 20rpx rgba(33, 150, 243, 0.4);
|
||||
transform: translateY(-5rpx);
|
||||
|
||||
.control-icon {
|
||||
width: 36rpx;
|
||||
height: 36rpx;
|
||||
margin-right: 16rpx;
|
||||
}
|
||||
|
||||
text {
|
||||
font-size: 28rpx;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
&:active {
|
||||
transform: translateY(-5rpx) scale(0.95);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.scan-control {
|
||||
background: #2196F3;
|
||||
.scan-control {
|
||||
background: #2196F3;
|
||||
|
||||
.control-icon {
|
||||
filter: brightness(0) invert(1);
|
||||
.control-icon {
|
||||
filter: brightness(0) invert(1);
|
||||
}
|
||||
|
||||
text {
|
||||
color: #ffffff;
|
||||
}
|
||||
}
|
||||
|
||||
text {
|
||||
color: #ffffff;
|
||||
}
|
||||
}
|
||||
.list-control {
|
||||
background: #4CAF50;
|
||||
|
||||
.list-control {
|
||||
background: #4CAF50;
|
||||
.control-icon {
|
||||
filter: brightness(0) invert(1);
|
||||
}
|
||||
|
||||
.control-icon {
|
||||
filter: brightness(0) invert(1);
|
||||
text {
|
||||
color: #ffffff;
|
||||
}
|
||||
}
|
||||
|
||||
text {
|
||||
color: #ffffff;
|
||||
}
|
||||
}
|
||||
.location-control {
|
||||
background: #ffffff;
|
||||
border: 2rpx solid #e0e0e0;
|
||||
|
||||
.location-control {
|
||||
background: #ffffff;
|
||||
border: 2rpx solid #e0e0e0;
|
||||
.control-icon {
|
||||
filter: none;
|
||||
}
|
||||
|
||||
.control-icon {
|
||||
filter: none;
|
||||
}
|
||||
|
||||
text {
|
||||
color: #333;
|
||||
text {
|
||||
color: #333;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes spin {
|
||||
0% { transform: rotate(0deg); }
|
||||
100% { transform: rotate(360deg); }
|
||||
}
|
||||
</style>
|
||||
@keyframes spin {
|
||||
0% {
|
||||
transform: rotate(0deg);
|
||||
}
|
||||
|
||||
100% {
|
||||
transform: rotate(360deg);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
</style>
|
||||
Reference in New Issue
Block a user