feat: initialize mini program map engine
This commit is contained in:
4
miniprogram/pages/index/index.json
Normal file
4
miniprogram/pages/index/index.json
Normal file
@@ -0,0 +1,4 @@
|
||||
{
|
||||
"usingComponents": {
|
||||
}
|
||||
}
|
||||
54
miniprogram/pages/index/index.ts
Normal file
54
miniprogram/pages/index/index.ts
Normal file
@@ -0,0 +1,54 @@
|
||||
// index.ts
|
||||
// 获取应用实例
|
||||
const app = getApp<IAppOption>()
|
||||
const defaultAvatarUrl = 'https://mmbiz.qpic.cn/mmbiz/icTdbqWNOwNRna42FI242Lcia07jQodd2FJGIYQfG0LAJGFxM4FbnQP6yfMxBgJ0F3YRqJCJ1aPAK2dQagdusBZg/0'
|
||||
|
||||
Component({
|
||||
data: {
|
||||
motto: 'Hello World',
|
||||
userInfo: {
|
||||
avatarUrl: defaultAvatarUrl,
|
||||
nickName: '',
|
||||
},
|
||||
hasUserInfo: false,
|
||||
canIUseGetUserProfile: wx.canIUse('getUserProfile'),
|
||||
canIUseNicknameComp: wx.canIUse('input.type.nickname'),
|
||||
},
|
||||
methods: {
|
||||
// 事件处理函数
|
||||
bindViewTap() {
|
||||
wx.navigateTo({
|
||||
url: '../logs/logs',
|
||||
})
|
||||
},
|
||||
onChooseAvatar(e: any) {
|
||||
const { avatarUrl } = e.detail
|
||||
const { nickName } = this.data.userInfo
|
||||
this.setData({
|
||||
"userInfo.avatarUrl": avatarUrl,
|
||||
hasUserInfo: nickName && avatarUrl && avatarUrl !== defaultAvatarUrl,
|
||||
})
|
||||
},
|
||||
onInputChange(e: any) {
|
||||
const nickName = e.detail.value
|
||||
const { avatarUrl } = this.data.userInfo
|
||||
this.setData({
|
||||
"userInfo.nickName": nickName,
|
||||
hasUserInfo: nickName && avatarUrl && avatarUrl !== defaultAvatarUrl,
|
||||
})
|
||||
},
|
||||
getUserProfile() {
|
||||
// 推荐使用wx.getUserProfile获取用户信息,开发者每次通过该接口获取用户个人信息均需用户确认,开发者妥善保管用户快速填写的头像昵称,避免重复弹窗
|
||||
wx.getUserProfile({
|
||||
desc: '展示用户信息', // 声明获取用户个人信息后的用途,后续会展示在弹窗中,请谨慎填写
|
||||
success: (res) => {
|
||||
console.log(res)
|
||||
this.setData({
|
||||
userInfo: res.userInfo,
|
||||
hasUserInfo: true
|
||||
})
|
||||
}
|
||||
})
|
||||
},
|
||||
},
|
||||
})
|
||||
27
miniprogram/pages/index/index.wxml
Normal file
27
miniprogram/pages/index/index.wxml
Normal file
@@ -0,0 +1,27 @@
|
||||
<!--index.wxml-->
|
||||
<scroll-view class="scrollarea" scroll-y type="list">
|
||||
<view class="container">
|
||||
<view class="userinfo">
|
||||
<block wx:if="{{canIUseNicknameComp && !hasUserInfo}}">
|
||||
<button class="avatar-wrapper" open-type="chooseAvatar" bind:chooseavatar="onChooseAvatar">
|
||||
<image class="avatar" src="{{userInfo.avatarUrl}}"></image>
|
||||
</button>
|
||||
<view class="nickname-wrapper">
|
||||
<text class="nickname-label">昵称</text>
|
||||
<input type="nickname" class="nickname-input" placeholder="请输入昵称" bind:change="onInputChange" />
|
||||
</view>
|
||||
</block>
|
||||
<block wx:elif="{{!hasUserInfo}}">
|
||||
<button wx:if="{{canIUseGetUserProfile}}" bindtap="getUserProfile"> 获取头像昵称 </button>
|
||||
<view wx:else> 请使用2.10.4及以上版本基础库 </view>
|
||||
</block>
|
||||
<block wx:else>
|
||||
<image bindtap="bindViewTap" class="userinfo-avatar" src="{{userInfo.avatarUrl}}" mode="cover"></image>
|
||||
<text class="userinfo-nickname">{{userInfo.nickName}}</text>
|
||||
</block>
|
||||
</view>
|
||||
<view class="usermotto">
|
||||
<text class="user-motto">{{motto}}</text>
|
||||
</view>
|
||||
</view>
|
||||
</scroll-view>
|
||||
62
miniprogram/pages/index/index.wxss
Normal file
62
miniprogram/pages/index/index.wxss
Normal file
@@ -0,0 +1,62 @@
|
||||
/**index.wxss**/
|
||||
page {
|
||||
height: 100vh;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
.scrollarea {
|
||||
flex: 1;
|
||||
overflow-y: hidden;
|
||||
}
|
||||
|
||||
.userinfo {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
color: #aaa;
|
||||
width: 80%;
|
||||
}
|
||||
|
||||
.userinfo-avatar {
|
||||
overflow: hidden;
|
||||
width: 128rpx;
|
||||
height: 128rpx;
|
||||
margin: 20rpx;
|
||||
border-radius: 50%;
|
||||
}
|
||||
|
||||
.usermotto {
|
||||
margin-top: 200px;
|
||||
}
|
||||
|
||||
.avatar-wrapper {
|
||||
padding: 0;
|
||||
width: 56px !important;
|
||||
border-radius: 8px;
|
||||
margin-top: 40px;
|
||||
margin-bottom: 40px;
|
||||
}
|
||||
|
||||
.avatar {
|
||||
display: block;
|
||||
width: 56px;
|
||||
height: 56px;
|
||||
}
|
||||
|
||||
.nickname-wrapper {
|
||||
display: flex;
|
||||
width: 100%;
|
||||
padding: 16px;
|
||||
box-sizing: border-box;
|
||||
border-top: .5px solid rgba(0, 0, 0, 0.1);
|
||||
border-bottom: .5px solid rgba(0, 0, 0, 0.1);
|
||||
color: black;
|
||||
}
|
||||
|
||||
.nickname-label {
|
||||
width: 105px;
|
||||
}
|
||||
|
||||
.nickname-input {
|
||||
flex: 1;
|
||||
}
|
||||
4
miniprogram/pages/logs/logs.json
Normal file
4
miniprogram/pages/logs/logs.json
Normal file
@@ -0,0 +1,4 @@
|
||||
{
|
||||
"usingComponents": {
|
||||
}
|
||||
}
|
||||
21
miniprogram/pages/logs/logs.ts
Normal file
21
miniprogram/pages/logs/logs.ts
Normal file
@@ -0,0 +1,21 @@
|
||||
// logs.ts
|
||||
// const util = require('../../utils/util.js')
|
||||
import { formatTime } from '../../utils/util'
|
||||
|
||||
Component({
|
||||
data: {
|
||||
logs: [],
|
||||
},
|
||||
lifetimes: {
|
||||
attached() {
|
||||
this.setData({
|
||||
logs: (wx.getStorageSync('logs') || []).map((log: string) => {
|
||||
return {
|
||||
date: formatTime(new Date(log)),
|
||||
timeStamp: log
|
||||
}
|
||||
}),
|
||||
})
|
||||
}
|
||||
},
|
||||
})
|
||||
6
miniprogram/pages/logs/logs.wxml
Normal file
6
miniprogram/pages/logs/logs.wxml
Normal file
@@ -0,0 +1,6 @@
|
||||
<!--logs.wxml-->
|
||||
<scroll-view class="scrollarea" scroll-y type="list">
|
||||
<block wx:for="{{logs}}" wx:key="timeStamp" wx:for-item="log">
|
||||
<view class="log-item">{{index + 1}}. {{log.date}}</view>
|
||||
</block>
|
||||
</scroll-view>
|
||||
16
miniprogram/pages/logs/logs.wxss
Normal file
16
miniprogram/pages/logs/logs.wxss
Normal file
@@ -0,0 +1,16 @@
|
||||
page {
|
||||
height: 100vh;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
.scrollarea {
|
||||
flex: 1;
|
||||
overflow-y: hidden;
|
||||
}
|
||||
.log-item {
|
||||
margin-top: 20rpx;
|
||||
text-align: center;
|
||||
}
|
||||
.log-item:last-child {
|
||||
padding-bottom: env(safe-area-inset-bottom);
|
||||
}
|
||||
4
miniprogram/pages/map/map.json
Normal file
4
miniprogram/pages/map/map.json
Normal file
@@ -0,0 +1,4 @@
|
||||
{
|
||||
"navigationBarTitleText": "地图",
|
||||
"disableScroll": true
|
||||
}
|
||||
173
miniprogram/pages/map/map.ts
Normal file
173
miniprogram/pages/map/map.ts
Normal file
@@ -0,0 +1,173 @@
|
||||
import { MapEngine, type MapEngineStageRect, type MapEngineViewState } from '../../engine/map/mapEngine'
|
||||
|
||||
const INTERNAL_BUILD_VERSION = 'map-build-58'
|
||||
|
||||
let mapEngine: MapEngine | null = null
|
||||
|
||||
function getFallbackStageRect(): MapEngineStageRect {
|
||||
const systemInfo = wx.getSystemInfoSync()
|
||||
const width = Math.max(320, systemInfo.windowWidth - 20)
|
||||
const height = Math.max(280, Math.floor(systemInfo.windowHeight * 0.66))
|
||||
|
||||
return {
|
||||
width,
|
||||
height,
|
||||
left: 10,
|
||||
top: 0,
|
||||
}
|
||||
}
|
||||
|
||||
Page({
|
||||
data: {} as MapEngineViewState,
|
||||
|
||||
onLoad() {
|
||||
mapEngine = new MapEngine(INTERNAL_BUILD_VERSION, {
|
||||
onData: (patch) => {
|
||||
this.setData(patch)
|
||||
},
|
||||
})
|
||||
|
||||
this.setData(mapEngine.getInitialData())
|
||||
},
|
||||
|
||||
onReady() {
|
||||
this.measureStageAndCanvas()
|
||||
},
|
||||
|
||||
onUnload() {
|
||||
if (mapEngine) {
|
||||
mapEngine.destroy()
|
||||
mapEngine = null
|
||||
}
|
||||
},
|
||||
|
||||
measureStageAndCanvas() {
|
||||
const page = this
|
||||
const applyStage = (rawRect?: Partial<WechatMiniprogram.BoundingClientRectCallbackResult>) => {
|
||||
const fallbackRect = getFallbackStageRect()
|
||||
const rect: MapEngineStageRect = {
|
||||
width: rawRect && typeof rawRect.width === 'number' ? rawRect.width : fallbackRect.width,
|
||||
height: rawRect && typeof rawRect.height === 'number' ? rawRect.height : fallbackRect.height,
|
||||
left: rawRect && typeof rawRect.left === 'number' ? rawRect.left : fallbackRect.left,
|
||||
top: rawRect && typeof rawRect.top === 'number' ? rawRect.top : fallbackRect.top,
|
||||
}
|
||||
|
||||
const currentEngine = mapEngine
|
||||
if (!currentEngine) {
|
||||
return
|
||||
}
|
||||
|
||||
currentEngine.setStage(rect)
|
||||
|
||||
const canvasQuery = wx.createSelectorQuery().in(page)
|
||||
canvasQuery.select('#mapCanvas').fields({ node: true, size: true })
|
||||
canvasQuery.exec((canvasRes) => {
|
||||
const canvasRef = canvasRes[0] as any
|
||||
if (!canvasRef || !canvasRef.node) {
|
||||
page.setData({
|
||||
statusText: `WebGL 引擎初始化失败 (${INTERNAL_BUILD_VERSION})`,
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
const dpr = wx.getSystemInfoSync().pixelRatio || 1
|
||||
try {
|
||||
currentEngine.attachCanvas(canvasRef.node, rect.width, rect.height, dpr)
|
||||
} catch (error) {
|
||||
page.setData({
|
||||
statusText: `WebGL 初始化失败 (${INTERNAL_BUILD_VERSION})`,
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
const query = wx.createSelectorQuery().in(page)
|
||||
query.select('.map-stage').boundingClientRect()
|
||||
query.exec((res) => {
|
||||
const rect = res[0] as WechatMiniprogram.BoundingClientRectCallbackResult | undefined
|
||||
applyStage(rect)
|
||||
})
|
||||
},
|
||||
|
||||
handleTouchStart(event: WechatMiniprogram.TouchEvent) {
|
||||
if (mapEngine) {
|
||||
mapEngine.handleTouchStart(event)
|
||||
}
|
||||
},
|
||||
|
||||
handleTouchMove(event: WechatMiniprogram.TouchEvent) {
|
||||
if (mapEngine) {
|
||||
mapEngine.handleTouchMove(event)
|
||||
}
|
||||
},
|
||||
|
||||
handleTouchEnd(event: WechatMiniprogram.TouchEvent) {
|
||||
if (mapEngine) {
|
||||
mapEngine.handleTouchEnd(event)
|
||||
}
|
||||
},
|
||||
|
||||
handleTouchCancel() {
|
||||
if (mapEngine) {
|
||||
mapEngine.handleTouchCancel()
|
||||
}
|
||||
},
|
||||
|
||||
handleRecenter() {
|
||||
if (mapEngine) {
|
||||
mapEngine.handleRecenter()
|
||||
}
|
||||
},
|
||||
|
||||
handleRotateStep() {
|
||||
if (mapEngine) {
|
||||
mapEngine.handleRotateStep()
|
||||
}
|
||||
},
|
||||
|
||||
handleRotationReset() {
|
||||
if (mapEngine) {
|
||||
mapEngine.handleRotationReset()
|
||||
}
|
||||
},
|
||||
|
||||
handleSetManualMode() {
|
||||
if (mapEngine) {
|
||||
mapEngine.handleSetManualMode()
|
||||
}
|
||||
},
|
||||
|
||||
handleSetNorthUpMode() {
|
||||
if (mapEngine) {
|
||||
mapEngine.handleSetNorthUpMode()
|
||||
}
|
||||
},
|
||||
|
||||
handleSetHeadingUpMode() {
|
||||
if (mapEngine) {
|
||||
mapEngine.handleSetHeadingUpMode()
|
||||
}
|
||||
},
|
||||
|
||||
handleAutoRotateCalibrate() {
|
||||
if (mapEngine) {
|
||||
mapEngine.handleAutoRotateCalibrate()
|
||||
}
|
||||
},
|
||||
})
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
152
miniprogram/pages/map/map.wxml
Normal file
152
miniprogram/pages/map/map.wxml
Normal file
@@ -0,0 +1,152 @@
|
||||
<view class="page">
|
||||
<view class="page__header">
|
||||
<view>
|
||||
<view class="page__eyebrow">CMR MINI PROGRAM</view>
|
||||
<view class="page__title">{{mapName}}</view>
|
||||
</view>
|
||||
<view class="page__badge">{{mapReadyText}}</view>
|
||||
</view>
|
||||
|
||||
<view class="map-stage-wrap">
|
||||
<view
|
||||
class="map-stage"
|
||||
catchtouchstart="handleTouchStart"
|
||||
catchtouchmove="handleTouchMove"
|
||||
catchtouchend="handleTouchEnd"
|
||||
catchtouchcancel="handleTouchCancel"
|
||||
>
|
||||
<view class="map-content">
|
||||
<canvas
|
||||
id="mapCanvas"
|
||||
type="webgl"
|
||||
canvas-id="mapCanvas"
|
||||
class="map-canvas map-canvas--base"
|
||||
></canvas>
|
||||
</view>
|
||||
|
||||
<view class="map-stage__crosshair"></view>
|
||||
|
||||
<view class="map-stage__overlay">
|
||||
<view class="overlay-card">
|
||||
<view class="overlay-card__label">WEBGL MAP ENGINE</view>
|
||||
<view class="overlay-card__title">North Up / Heading Up / Manual</view>
|
||||
<view class="overlay-card__desc">
|
||||
地图北已经固定为正上方。现在支持手动旋转、北朝上、朝向朝上三种模式,并提供指北针用于校验朝向。
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="compass-widget">
|
||||
<view class="compass-widget__ring">
|
||||
<view class="compass-widget__north">N</view>
|
||||
<view class="compass-widget__needle" style="transform: translateX(-50%) rotate({{compassNeedleDeg}}deg);"></view>
|
||||
<view class="compass-widget__center"></view>
|
||||
</view>
|
||||
<view class="compass-widget__label">{{sensorHeadingText}}</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<scroll-view class="info-panel" scroll-y enhanced show-scrollbar="true">
|
||||
<view class="info-panel__row">
|
||||
<text class="info-panel__label">Build</text>
|
||||
<text class="info-panel__value">{{buildVersion}}</text>
|
||||
</view>
|
||||
<view class="info-panel__row">
|
||||
<text class="info-panel__label">Renderer</text>
|
||||
<text class="info-panel__value">{{renderMode}}</text>
|
||||
</view>
|
||||
<view class="info-panel__row info-panel__row--stack">
|
||||
<text class="info-panel__label">Projection</text>
|
||||
<text class="info-panel__value">{{projectionMode}}</text>
|
||||
</view>
|
||||
<view class="info-panel__row">
|
||||
<text class="info-panel__label">Heading Mode</text>
|
||||
<text class="info-panel__value">{{orientationModeText}}</text>
|
||||
</view>
|
||||
<view class="info-panel__row">
|
||||
<text class="info-panel__label">Sensor Heading</text>
|
||||
<text class="info-panel__value">{{sensorHeadingText}}</text>
|
||||
</view>
|
||||
<view class="info-panel__row">
|
||||
<text class="info-panel__label">North Ref</text>
|
||||
<text class="info-panel__value">{{northReferenceText}}</text>
|
||||
</view>
|
||||
<view class="info-panel__row">
|
||||
<text class="info-panel__label">Auto Source</text>
|
||||
<text class="info-panel__value">{{autoRotateSourceText}}</text>
|
||||
</view>
|
||||
<view class="info-panel__row">
|
||||
<text class="info-panel__label">Calibration</text>
|
||||
<text class="info-panel__value">{{autoRotateCalibrationText}}</text>
|
||||
</view>
|
||||
<view class="info-panel__row info-panel__row--stack">
|
||||
<text class="info-panel__label">Tile URL</text>
|
||||
<text class="info-panel__value">{{tileSource}}</text>
|
||||
</view>
|
||||
<view class="info-panel__row">
|
||||
<text class="info-panel__label">Zoom</text>
|
||||
<text class="info-panel__value">{{zoom}}</text>
|
||||
</view>
|
||||
<view class="info-panel__row">
|
||||
<text class="info-panel__label">Rotation</text>
|
||||
<text class="info-panel__value">{{rotationText}}</text>
|
||||
</view>
|
||||
<view class="info-panel__row">
|
||||
<text class="info-panel__label">Center Tile</text>
|
||||
<text class="info-panel__value">{{centerText}}</text>
|
||||
</view>
|
||||
<view class="info-panel__row">
|
||||
<text class="info-panel__label">Tile Size</text>
|
||||
<text class="info-panel__value">{{tileSizePx}}px</text>
|
||||
</view>
|
||||
<view class="info-panel__row">
|
||||
<text class="info-panel__label">Visible Tiles</text>
|
||||
<text class="info-panel__value">{{visibleTileCount}}</text>
|
||||
</view>
|
||||
<view class="info-panel__row">
|
||||
<text class="info-panel__label">Ready Tiles</text>
|
||||
<text class="info-panel__value">{{readyTileCount}}</text>
|
||||
</view>
|
||||
<view class="info-panel__row">
|
||||
<text class="info-panel__label">Memory Tiles</text>
|
||||
<text class="info-panel__value">{{memoryTileCount}}</text>
|
||||
</view>
|
||||
<view class="info-panel__row">
|
||||
<text class="info-panel__label">Disk Tiles</text>
|
||||
<text class="info-panel__value">{{diskTileCount}}</text>
|
||||
</view>
|
||||
<view class="info-panel__row">
|
||||
<text class="info-panel__label">Cache Hit</text>
|
||||
<text class="info-panel__value">{{cacheHitRateText}}</text>
|
||||
</view>
|
||||
<view class="info-panel__row">
|
||||
<text class="info-panel__label">Disk Hits</text>
|
||||
<text class="info-panel__value">{{diskHitCount}}</text>
|
||||
</view>
|
||||
<view class="info-panel__row">
|
||||
<text class="info-panel__label">Net Fetches</text>
|
||||
<text class="info-panel__value">{{networkFetchCount}}</text>
|
||||
</view>
|
||||
<view class="info-panel__row info-panel__row--stack">
|
||||
<text class="info-panel__label">Status</text>
|
||||
<text class="info-panel__value">{{statusText}}</text>
|
||||
</view>
|
||||
|
||||
<view class="control-row">
|
||||
<view class="control-chip control-chip--primary" bindtap="handleRecenter">回到首屏</view>
|
||||
<view class="control-chip control-chip--secondary" bindtap="handleRotationReset">旋转归零</view>
|
||||
</view>
|
||||
<view class="control-row control-row--triple">
|
||||
<view class="control-chip {{orientationMode === 'manual' ? 'control-chip--active' : ''}}" bindtap="handleSetManualMode">手动</view>
|
||||
<view class="control-chip {{orientationMode === 'north-up' ? 'control-chip--active' : ''}}" bindtap="handleSetNorthUpMode">北朝上</view>
|
||||
<view class="control-chip {{orientationMode === 'heading-up' ? 'control-chip--active' : ''}}" bindtap="handleSetHeadingUpMode">朝向朝上</view>
|
||||
</view>
|
||||
<view class="control-row" wx:if="{{orientationMode === 'heading-up'}}">
|
||||
<view class="control-chip" bindtap="handleAutoRotateCalibrate">按当前方向校准</view>
|
||||
</view>
|
||||
<view class="control-row" wx:if="{{orientationMode === 'manual'}}">
|
||||
<view class="control-chip" bindtap="handleRotateStep">旋转 +15°</view>
|
||||
</view>
|
||||
</scroll-view>
|
||||
</view>
|
||||
337
miniprogram/pages/map/map.wxss
Normal file
337
miniprogram/pages/map/map.wxss
Normal file
@@ -0,0 +1,337 @@
|
||||
.page {
|
||||
height: 100vh;
|
||||
padding: 20rpx 20rpx 24rpx;
|
||||
box-sizing: border-box;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
overflow: hidden;
|
||||
background:
|
||||
radial-gradient(circle at top left, #d8f3dc 0%, rgba(216, 243, 220, 0) 32%),
|
||||
linear-gradient(180deg, #f7fbf2 0%, #eef6ea 100%);
|
||||
color: #163020;
|
||||
}
|
||||
|
||||
.page__header {
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
justify-content: space-between;
|
||||
margin-bottom: 16rpx;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.page__eyebrow {
|
||||
font-size: 20rpx;
|
||||
letter-spacing: 4rpx;
|
||||
color: #5f7a65;
|
||||
}
|
||||
|
||||
.page__title {
|
||||
margin-top: 8rpx;
|
||||
font-size: 44rpx;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.page__badge {
|
||||
padding: 10rpx 18rpx;
|
||||
border-radius: 999rpx;
|
||||
background: #163020;
|
||||
color: #f7fbf2;
|
||||
font-size: 22rpx;
|
||||
}
|
||||
|
||||
.map-stage-wrap {
|
||||
position: relative;
|
||||
width: 100%;
|
||||
height: 66vh;
|
||||
min-height: 520rpx;
|
||||
max-height: 72vh;
|
||||
flex-shrink: 0;
|
||||
margin-bottom: 16rpx;
|
||||
}
|
||||
|
||||
.map-stage {
|
||||
position: relative;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
overflow: hidden;
|
||||
border: 2rpx solid rgba(22, 48, 32, 0.08);
|
||||
border-radius: 32rpx;
|
||||
background: #dbeed4;
|
||||
box-shadow: 0 18rpx 40rpx rgba(22, 48, 32, 0.08);
|
||||
}
|
||||
|
||||
.map-content {
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
}
|
||||
|
||||
.map-canvas {
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
display: block;
|
||||
}
|
||||
|
||||
.map-canvas--base {
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
.map-stage__crosshair {
|
||||
position: absolute;
|
||||
left: 50%;
|
||||
top: 50%;
|
||||
width: 44rpx;
|
||||
height: 44rpx;
|
||||
transform: translate(-50%, -50%);
|
||||
border: 3rpx solid rgba(255, 255, 255, 0.95);
|
||||
border-radius: 50%;
|
||||
box-shadow: 0 0 0 4rpx rgba(22, 48, 32, 0.2);
|
||||
pointer-events: none;
|
||||
z-index: 3;
|
||||
}
|
||||
|
||||
.map-stage__crosshair::before,
|
||||
.map-stage__crosshair::after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
background: rgba(255, 255, 255, 0.95);
|
||||
}
|
||||
|
||||
.map-stage__crosshair::before {
|
||||
left: 50%;
|
||||
top: -18rpx;
|
||||
width: 2rpx;
|
||||
height: 76rpx;
|
||||
transform: translateX(-50%);
|
||||
}
|
||||
|
||||
.map-stage__crosshair::after {
|
||||
left: -18rpx;
|
||||
top: 50%;
|
||||
width: 76rpx;
|
||||
height: 2rpx;
|
||||
transform: translateY(-50%);
|
||||
}
|
||||
|
||||
.map-stage__overlay {
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
justify-content: space-between;
|
||||
padding: 24rpx;
|
||||
box-sizing: border-box;
|
||||
pointer-events: none;
|
||||
z-index: 4;
|
||||
}
|
||||
|
||||
.overlay-card {
|
||||
width: 68%;
|
||||
padding: 22rpx;
|
||||
border-radius: 24rpx;
|
||||
background: rgba(247, 251, 242, 0.92);
|
||||
box-shadow: 0 12rpx 30rpx rgba(22, 48, 32, 0.08);
|
||||
}
|
||||
|
||||
.overlay-card__label {
|
||||
font-size: 20rpx;
|
||||
letter-spacing: 3rpx;
|
||||
color: #5f7a65;
|
||||
}
|
||||
|
||||
.overlay-card__title {
|
||||
margin-top: 10rpx;
|
||||
font-size: 34rpx;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.overlay-card__desc {
|
||||
margin-top: 12rpx;
|
||||
font-size: 24rpx;
|
||||
line-height: 1.6;
|
||||
color: #45624b;
|
||||
}
|
||||
|
||||
.compass-widget {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
gap: 10rpx;
|
||||
}
|
||||
|
||||
.compass-widget__ring {
|
||||
position: relative;
|
||||
width: 108rpx;
|
||||
height: 108rpx;
|
||||
border-radius: 50%;
|
||||
background: rgba(247, 251, 242, 0.94);
|
||||
border: 2rpx solid rgba(22, 48, 32, 0.12);
|
||||
box-shadow: 0 10rpx 24rpx rgba(22, 48, 32, 0.1);
|
||||
}
|
||||
|
||||
.compass-widget__north {
|
||||
position: absolute;
|
||||
left: 50%;
|
||||
top: 10rpx;
|
||||
transform: translateX(-50%);
|
||||
font-size: 20rpx;
|
||||
font-weight: 700;
|
||||
color: #d62828;
|
||||
}
|
||||
|
||||
.compass-widget__needle {
|
||||
position: absolute;
|
||||
left: 50%;
|
||||
top: 18rpx;
|
||||
width: 4rpx;
|
||||
height: 72rpx;
|
||||
transform-origin: 50% 36rpx;
|
||||
background: linear-gradient(180deg, #d62828 0%, #163020 100%);
|
||||
border-radius: 999rpx;
|
||||
}
|
||||
|
||||
.compass-widget__center {
|
||||
position: absolute;
|
||||
left: 50%;
|
||||
top: 50%;
|
||||
width: 14rpx;
|
||||
height: 14rpx;
|
||||
transform: translate(-50%, -50%);
|
||||
border-radius: 50%;
|
||||
background: #163020;
|
||||
}
|
||||
|
||||
.compass-widget__label {
|
||||
min-width: 92rpx;
|
||||
padding: 6rpx 10rpx;
|
||||
border-radius: 999rpx;
|
||||
background: rgba(247, 251, 242, 0.94);
|
||||
font-size: 20rpx;
|
||||
text-align: center;
|
||||
color: #163020;
|
||||
box-shadow: 0 8rpx 18rpx rgba(22, 48, 32, 0.08);
|
||||
}
|
||||
|
||||
.info-panel {
|
||||
flex: 1;
|
||||
min-height: 0;
|
||||
padding: 22rpx 20rpx 28rpx;
|
||||
box-sizing: border-box;
|
||||
border-radius: 28rpx;
|
||||
background: rgba(255, 255, 255, 0.88);
|
||||
box-shadow: 0 12rpx 32rpx rgba(22, 48, 32, 0.08);
|
||||
}
|
||||
|
||||
.info-panel__row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: 16rpx;
|
||||
padding: 10rpx 0;
|
||||
border-bottom: 1rpx solid rgba(22, 48, 32, 0.08);
|
||||
}
|
||||
|
||||
.info-panel__row--stack {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.info-panel__row:last-of-type {
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
.info-panel__label {
|
||||
flex-shrink: 0;
|
||||
font-size: 22rpx;
|
||||
letter-spacing: 2rpx;
|
||||
color: #5f7a65;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
|
||||
.info-panel__value {
|
||||
font-size: 25rpx;
|
||||
color: #163020;
|
||||
text-align: right;
|
||||
word-break: break-all;
|
||||
}
|
||||
|
||||
.info-panel__row--stack .info-panel__value {
|
||||
display: block;
|
||||
margin-top: 10rpx;
|
||||
text-align: left;
|
||||
color: #45624b;
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
.info-panel__actions {
|
||||
display: flex;
|
||||
gap: 14rpx;
|
||||
margin-top: 18rpx;
|
||||
}
|
||||
|
||||
.info-panel__actions--triple .info-panel__action {
|
||||
font-size: 23rpx;
|
||||
}
|
||||
|
||||
.info-panel__action {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
border-radius: 999rpx;
|
||||
background: #d7e8da;
|
||||
color: #163020;
|
||||
font-size: 26rpx;
|
||||
}
|
||||
|
||||
.info-panel__action--primary {
|
||||
background: #2d6a4f;
|
||||
color: #f7fbf2;
|
||||
}
|
||||
|
||||
.info-panel__action--secondary {
|
||||
background: #eef6ea;
|
||||
color: #45624b;
|
||||
}
|
||||
|
||||
.info-panel__action--active {
|
||||
background: #2d6a4f;
|
||||
color: #f7fbf2;
|
||||
}
|
||||
|
||||
.control-row {
|
||||
display: flex;
|
||||
gap: 14rpx;
|
||||
margin-top: 18rpx;
|
||||
}
|
||||
|
||||
.control-row--triple .control-chip {
|
||||
font-size: 23rpx;
|
||||
}
|
||||
|
||||
.control-chip {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
padding: 20rpx 16rpx;
|
||||
border-radius: 999rpx;
|
||||
background: #d7e8da;
|
||||
color: #163020;
|
||||
font-size: 26rpx;
|
||||
text-align: center;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.control-chip--primary {
|
||||
background: #2d6a4f;
|
||||
color: #f7fbf2;
|
||||
}
|
||||
|
||||
.control-chip--secondary {
|
||||
background: #eef6ea;
|
||||
color: #45624b;
|
||||
}
|
||||
|
||||
.control-chip--active {
|
||||
background: #2d6a4f;
|
||||
color: #f7fbf2;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user