chore: initialize frontend miniapp repository

This commit is contained in:
lyf
2026-06-09 21:08:45 +08:00
commit a90f63cef0
107 changed files with 60454 additions and 0 deletions

View File

@@ -0,0 +1,401 @@
<template>
<view class="map-component">
<map
id="museum-map"
class="map"
:latitude="mapCenter.latitude"
:longitude="mapCenter.longitude"
:scale="mapScale"
:markers="markers"
:polygons="polygons"
:show-location="!isH5"
:enable-3D="false"
:enable-overlooking="false"
:enable-zoom="true"
:enable-scroll="true"
:enable-rotate="false"
@markertap="handleMarkerTap"
@regionchange="handleRegionChange"
@tap="handleMapTap"
>
<!-- 自定义控件 -->
<cover-view class="map-controls">
<!-- 楼层选择器 -->
<cover-view class="floor-selector-wrapper">
<cover-view class="floor-selector">
<cover-view
v-for="floor in floors"
:key="floor.id"
class="floor-item"
:class="{ active: currentFloor === floor.id }"
@tap="handleFloorClick(floor.id)"
>
<cover-view class="floor-label">{{ floor.label }}</cover-view>
</cover-view>
</cover-view>
</cover-view>
<!-- 功能按钮 -->
<cover-view class="action-buttons">
<!-- 进入 3D 室内模式按钮 -->
<cover-view class="action-btn indoor-mode-btn" @tap="handleEnter3D">
<cover-view class="btn-text">🏛</cover-view>
<cover-view class="btn-sub-text">室内</cover-view>
</cover-view>
<cover-view class="action-btn" @tap="handleLocation">
<cover-view class="btn-text">📍</cover-view>
</cover-view>
<cover-view class="action-btn" @tap="handleZoomIn">
<cover-view class="btn-text">+</cover-view>
</cover-view>
<cover-view class="action-btn" @tap="handleZoomOut">
<cover-view class="btn-text">-</cover-view>
</cover-view>
</cover-view>
</cover-view>
</map>
</view>
</template>
<script setup lang="ts">
import { ref, onMounted } from 'vue'
const isH5 = typeof window !== 'undefined'
interface MapCenter {
latitude: number
longitude: number
}
interface Marker {
id: number
latitude: number
longitude: number
iconPath: string
width: number
height: number
title?: string
callout?: {
content: string
display: 'ALWAYS' | 'BYCLICK'
padding: number
borderRadius: number
bgColor: string
color: string
fontSize: number
}
}
interface Polygon {
points: Array<{ latitude: number; longitude: number }>
strokeWidth: number
strokeColor: string
fillColor: string
zIndex: number
}
const emit = defineEmits<{
markerClick: [markerId: number]
floorChange: [floor: string]
enter3DMode: []
}>()
// 深圳自然博物馆实际坐标
// POI ID: 7043042949572197975
const mapCenter = ref<MapCenter>({
latitude: 22.692763, // 深圳自然博物馆纬度
longitude: 114.363987 // 深圳自然博物馆经度(向右偏移,让建筑显示在左侧)
})
// 地图缩放级别(调整为适中比例,既能看到建筑全貌又能看清细节)
const mapScale = ref(17)
// 当前楼层
const currentFloor = ref('1F')
// 楼层列表
const floors = [
{ id: '3F', label: '3F' },
{ id: '2F', label: '2F' },
{ id: '1F', label: '1F' },
{ id: 'B1', label: 'B1' }
]
// 博物馆建筑围栏(多边形)
// 深圳自然博物馆实际建筑轮廓坐标
const polygons = ref<Polygon[]>([
{
points: [
{ latitude: 22.693738, longitude: 114.362331 },
{ latitude: 22.69358, longitude: 114.362239 },
{ latitude: 22.693513, longitude: 114.362239 },
{ latitude: 22.691953, longitude: 114.362252 },
{ latitude: 22.692106, longitude: 114.365058 },
{ latitude: 22.692167, longitude: 114.365051 },
{ latitude: 22.693129, longitude: 114.365018 },
{ latitude: 22.693135, longitude: 114.364655 },
{ latitude: 22.693196, longitude: 114.363982 },
{ latitude: 22.693232, longitude: 114.363764 },
{ latitude: 22.693348, longitude: 114.363473 },
{ latitude: 22.693476, longitude: 114.36319 },
{ latitude: 22.693653, longitude: 114.362714 },
{ latitude: 22.693701, longitude: 114.362523 },
{ latitude: 22.693738, longitude: 114.362351 },
],
strokeWidth: 3,
strokeColor: '#E0E100', // 黄色边框
fillColor: '#E0E10020', // 半透明黄色填充
zIndex: 1
}
])
// 地图标记点(博物馆入口和主要位置)
const markers = ref<Marker[]>([
{
id: 0,
latitude: 22.692763,
longitude: 114.363487,
iconPath: '/static/icons/marker-museum.svg',
width: 40,
height: 40,
title: '深圳自然博物馆',
callout: {
content: '深圳自然博物馆',
display: 'ALWAYS',
padding: 12,
borderRadius: 6,
bgColor: '#E0E100',
color: '#000000',
fontSize: 14
}
},
{
id: 1,
latitude: 22.692363,
longitude: 114.363487,
iconPath: '/static/icons/marker-entrance.svg',
width: 30,
height: 30,
title: '主入口',
callout: {
content: '主入口',
display: 'BYCLICK',
padding: 10,
borderRadius: 5,
bgColor: '#000000',
color: '#FFFFFF',
fontSize: 12
}
},
])
// 处理标记点点击
const handleMarkerTap = (e: any) => {
const markerId = e.detail.markerId || e.markerId
console.log('点击标记点:', markerId)
emit('markerClick', markerId)
}
// 处理地图区域变化
const handleRegionChange = (e: any) => {
if (e.type === 'end') {
console.log('地图区域变化:', e.detail)
}
}
// 处理地图点击
const handleMapTap = (e: any) => {
console.log('点击地图:', e.detail)
}
// 处理楼层点击
const handleFloorClick = (floorId: string) => {
currentFloor.value = floorId
emit('floorChange', floorId)
console.log('切换楼层:', floorId)
}
// 定位到当前位置
const handleLocation = () => {
uni.getLocation({
type: 'gcj02',
success: (res) => {
mapCenter.value = {
latitude: res.latitude,
longitude: res.longitude
}
// 添加或更新用户位置标记
const userMarkerIndex = markers.value.findIndex(m => m.id === 999)
const userMarker: Marker = {
id: 999,
latitude: res.latitude,
longitude: res.longitude,
iconPath: '/static/icons/marker-user-location.svg',
width: 40,
height: 40,
title: '我的位置',
callout: {
content: '您在这里',
display: 'BYCLICK',
padding: 10,
borderRadius: 5,
bgColor: '#000000',
color: '#FFFFFF',
fontSize: 12
}
}
if (userMarkerIndex >= 0) {
markers.value[userMarkerIndex] = userMarker
} else {
markers.value.push(userMarker)
}
console.log('定位成功:', res)
},
fail: (err) => {
console.error('定位失败:', err)
uni.showToast({
title: '定位失败',
icon: 'none'
})
}
})
}
// 放大地图
const handleZoomIn = () => {
if (mapScale.value < 20) {
mapScale.value += 1
console.log('放大地图,当前缩放级别:', mapScale.value)
}
}
// 缩小地图
const handleZoomOut = () => {
if (mapScale.value > 3) {
mapScale.value -= 1
console.log('缩小地图,当前缩放级别:', mapScale.value)
}
}
// 进入 3D 室内模式
const handleEnter3D = () => {
console.log('进入 3D 室内模式')
emit('enter3DMode')
}
onMounted(() => {
console.log('地图组件已挂载')
})
</script>
<style scoped lang="scss">
.map-component {
width: 100%;
height: 100%;
position: relative;
background: var(--museum-bg-map);
}
.map {
width: 100%;
height: 100%;
background: var(--museum-bg-map);
}
.map-controls {
display: none;
}
.floor-selector-wrapper {
position: absolute;
right: 12px;
top: 50%;
transform: translateY(-50%);
pointer-events: auto;
}
.floor-selector {
display: flex;
flex-direction: column;
gap: 8px;
background-color: rgba(255, 255, 255, 0.8);
border-radius: 8px;
padding: 8px;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.15);
}
.floor-item {
width: 46px;
height: 44px;
display: flex;
align-items: center;
justify-content: center;
background-color: transparent;
border-radius: 8px;
transition: all 0.3s;
}
.floor-item.active {
background-color: #000000;
}
.floor-label {
font-size: 14px;
font-weight: 500;
color: #424754;
}
.floor-item.active .floor-label {
color: #E0E100;
}
.action-buttons {
position: absolute;
right: 12px;
bottom: 20px;
display: flex;
flex-direction: column;
align-items: flex-end;
gap: 12px;
pointer-events: auto;
}
.action-btn {
width: 46px;
height: 46px;
background-color: rgba(255, 255, 255, 0.9);
border-radius: 8px;
display: flex;
align-items: center;
justify-content: center;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.15);
}
.btn-text {
font-size: 24px;
font-weight: 500;
color: #333333;
}
// 室内模式按钮
.indoor-mode-btn {
width: 46px;
height: 64px !important;
background-color: #E0E100;
flex-direction: column;
gap: 2px;
.btn-text {
font-size: 20px;
}
.btn-sub-text {
font-size: 10px;
font-weight: 600;
color: #000000;
}
}
</style>