feat RTK热力图绘制

This commit is contained in:
Ironsp
2026-09-17 16:20:57 +08:00
parent 1ae0371c18
commit c1d2958095
10 changed files with 843 additions and 65 deletions

4
src/components.d.ts vendored
View File

@@ -12,6 +12,10 @@ declare module 'vue' {
DeviceCard: typeof import('./components/deviceCard.vue')['default'] DeviceCard: typeof import('./components/deviceCard.vue')['default']
DeviceInfo: typeof import('./components/deviceInfo.vue')['default'] DeviceInfo: typeof import('./components/deviceInfo.vue')['default']
FullscreenNav: typeof import('./components/FullscreenNav.vue')['default'] FullscreenNav: typeof import('./components/FullscreenNav.vue')['default']
HeatmapCanvas: typeof import('./components/heatmap/canvas.vue')['default']
HeatmapLayerPanel: typeof import('./components/heatmap/layerPanel.vue')['default']
HeatmapLegend: typeof import('./components/heatmap/legend.vue')['default']
HeatmapPointDetail: typeof import('./components/heatmap/pointDetail.vue')['default']
HomeConstruction: typeof import('./components/home/construction.vue')['default'] HomeConstruction: typeof import('./components/home/construction.vue')['default']
HomeHomeNav: typeof import('./components/home/homeNav.vue')['default'] HomeHomeNav: typeof import('./components/home/homeNav.vue')['default']
HomeList: typeof import('./components/home/list.vue')['default'] HomeList: typeof import('./components/home/list.vue')['default']

View File

@@ -0,0 +1,207 @@
<script setup lang="ts">
import type { HeatmapLayerKey } from './heatUtils'
/**
* 压实热力图网格:
* - 36 列固定;行数 = ceil(数据量 / 36),随数据渐进增加
* - 每个数据占一格;格子间距固定一致
* - 色阶:0–90 绿,90–95 黄,95–100 红
*/
import type { MockRtkPoint } from '@/mock/rtkTrack'
import { getCurrentInstance, nextTick, onMounted, onUnmounted, ref, watch } from 'vue'
import { createMockRtkStream } from '@/mock/rtkTrack'
import { colorByScore, layerScore } from './heatUtils'
const props = defineProps<{
layer: HeatmapLayerKey
}>()
const emit = defineEmits<{
selectPoint: [point: MockRtkPoint]
}>()
const instance = getCurrentInstance()?.proxy
const CANVAS_ID = 'heatmapCanvas'
const COLS = 36
const GAP = 3
const PADDING = 12
const MIN_ROWS = 8
const MIN_H = 200
const canvasHeight = ref(MIN_H)
interface HeatCell {
point: MockRtkPoint
score: number
col: number
row: number
}
const cells: HeatCell[] = []
let cssWidth = 0
let cssHeight = MIN_H
let ready = false
let stream = createMockRtkStream({ intervalMs: 50, loop: false, rows: 40, pointsPerRow: 36 })
function rowCount() {
if (!cells.length)
return MIN_ROWS
return Math.max(MIN_ROWS, Math.ceil(cells.length / COLS))
}
function layoutMetrics() {
const rows = rowCount()
const innerW = Math.max(cssWidth - PADDING * 2, 1)
const cellW = (innerW - GAP * (COLS - 1)) / COLS
const cellH = cellW
const contentH = PADDING * 2 + rows * cellH + GAP * Math.max(0, rows - 1)
return { rows, cellW, cellH, contentH }
}
function syncHeight() {
if (cssWidth <= 0)
return
const { contentH } = layoutMetrics()
const next = Math.max(MIN_H, Math.round(contentH))
if (next !== cssHeight) {
cssHeight = next
canvasHeight.value = next
}
}
function cellRect(col: number, row: number, cellW: number, cellH: number) {
return {
x: PADDING + col * (cellW + GAP),
y: PADDING + row * (cellH + GAP),
w: cellW,
h: cellH,
}
}
function draw() {
if (!ready || cssWidth <= 0)
return
const ctx = uni.createCanvasContext(CANVAS_ID, instance as any)
const { cellW, cellH } = layoutMetrics()
ctx.setFillStyle('#f8fafc')
ctx.fillRect(0, 0, cssWidth, cssHeight)
for (const cell of cells) {
const { x, y, w, h } = cellRect(cell.col, cell.row, cellW, cellH)
ctx.setFillStyle(colorByScore(cell.score))
ctx.fillRect(x, y, w, h)
}
ctx.draw()
}
function appendPoint(point: MockRtkPoint) {
const index = cells.length
cells.push({
point,
score: layerScore(props.layer, point),
col: index % COLS,
row: Math.floor(index / COLS),
})
syncHeight()
nextTick(() => draw())
}
function refreshScores() {
for (const c of cells)
c.score = layerScore(props.layer, c.point)
draw()
}
function startStream() {
stream.stop()
stream = createMockRtkStream({ intervalMs: 50, loop: false, rows: 40, pointsPerRow: 36 })
cells.length = 0
syncHeight()
draw()
stream.start((point) => {
appendPoint(point)
})
}
function measureAndStart() {
uni.createSelectorQuery()
.in(instance as any)
.select('.heatmap-canvas')
.boundingClientRect((rect) => {
const box = Array.isArray(rect) ? rect[0] : rect
const w = Number(box?.width) || 0
if (w <= 0) {
setTimeout(() => measureAndStart(), 50)
return
}
cssWidth = w
ready = true
startStream()
})
.exec()
}
function onTap(e: any) {
if (!cells.length || cssWidth <= 0)
return
const x = Number(e?.detail?.x ?? e?.touches?.[0]?.x ?? 0)
const y = Number(e?.detail?.y ?? e?.touches?.[0]?.y ?? 0)
const { cellW, cellH } = layoutMetrics()
const col = Math.floor((x - PADDING) / (cellW + GAP))
const row = Math.floor((y - PADDING) / (cellH + GAP))
if (col < 0 || col >= COLS || row < 0)
return
// 点落在 gap 上则忽略
const localX = (x - PADDING) - col * (cellW + GAP)
const localY = (y - PADDING) - row * (cellH + GAP)
if (localX > cellW || localY > cellH)
return
const found = cells.find(c => c.col === col && c.row === row)
if (found)
emit('selectPoint', found.point)
}
watch(() => props.layer, () => refreshScores())
onMounted(() => {
nextTick(() => measureAndStart())
})
onUnmounted(() => {
stream.stop()
ready = false
})
</script>
<template>
<view class="heatmap-canvas" :style="{ height: `${canvasHeight}px` }" @tap="onTap">
<canvas
:id="CANVAS_ID"
:canvas-id="CANVAS_ID"
class="canvas"
:style="{ width: '100%', height: `${canvasHeight}px` }"
/>
</view>
</template>
<style scoped>
.heatmap-canvas {
width: 100%;
/* background: #f8fafc; */
border-radius: 8px;
overflow: hidden;
flex-shrink: 0;
}
.canvas {
width: 100%;
display: block;
}
</style>

View File

@@ -0,0 +1,74 @@
/** 热力图图层与色阶工具 */
export type HeatmapLayerKey = 'compaction' | 'speed' | 'temperature' | 'passCount'
export const HEATMAP_LAYERS: { key: HeatmapLayerKey, label: string }[] = [
{ key: 'compaction', label: '压实度' },
{ key: 'speed', label: '压实速度' },
{ key: 'temperature', label: '温度' },
{ key: 'passCount', label: '碾压遍数' },
]
/** 色阶阈值:0–90 绿,90–95 黄,95–100 红 */
export const HEAT_THRESHOLD_YELLOW = 90
export const HEAT_THRESHOLD_RED = 95
export function colorByScore(score: number): string {
if (score >= HEAT_THRESHOLD_RED)
return '#ef4444'
if (score >= HEAT_THRESHOLD_YELLOW)
return '#eab308'
return '#22c55e'
}
/** 各图层原始值映射到 0–100 分,统一用 90/95 分色 */
export function layerScore(
layer: HeatmapLayerKey,
point: { compaction: number, speed: number, temperature: number, passCount: number },
): number {
switch (layer) {
case 'compaction':
return point.compaction
case 'speed': {
// 目标约 4~5 km/h 偏优,偏快偏慢降分
const ideal = 4.2
const diff = Math.abs(point.speed - ideal)
return Math.max(0, Math.min(100, 100 - diff * 18))
}
case 'temperature': {
// 目标约 150℃
const ideal = 150
const diff = Math.abs(point.temperature - ideal)
return Math.max(0, Math.min(100, 100 - diff * 2.5))
}
case 'passCount':
// 目标遍数 6
return Math.max(0, Math.min(100, (point.passCount / 6) * 100))
default:
return 0
}
}
export function layerRawText(
layer: HeatmapLayerKey,
point: { compaction: number, speed: number, temperature: number, passCount: number },
): string {
switch (layer) {
case 'compaction':
return `${point.compaction}%`
case 'speed':
return `${point.speed}km/h`
case 'temperature':
return `${point.temperature}℃`
case 'passCount':
return `${point.passCount}遍`
default:
return '-'
}
}
export function headingText(heading: number): string {
const dirs = ['N', 'NE', 'E', 'SE', 'S', 'SW', 'W', 'NW']
const idx = Math.round(heading / 45) % 8
return `${dirs[idx]} ${heading.toFixed(1)}°`
}

View File

@@ -0,0 +1,106 @@
<script setup lang="ts">
import type { HeatmapLayerKey } from './heatUtils'
import { HEATMAP_LAYERS } from './heatUtils'
const props = defineProps<{
modelValue: HeatmapLayerKey
visible: boolean
}>()
const emit = defineEmits<{
'update:modelValue': [key: HeatmapLayerKey]
'update:visible': [v: boolean]
}>()
function select(key: HeatmapLayerKey) {
emit('update:modelValue', key)
emit('update:visible', false)
}
function toggle() {
emit('update:visible', !props.visible)
}
</script>
<template>
<view class="layer-wrap">
<view class="layer-btn" @tap.stop="toggle">
<text class="layer-btn-text">
图层切换
</text>
</view>
<view v-if="visible" class="layer-panel" @tap.stop>
<text class="panel-title">
图层切换
</text>
<view
v-for="item in HEATMAP_LAYERS"
:key="item.key"
class="layer-item"
:class="{ active: modelValue === item.key }"
@tap="select(item.key)"
>
<text class="layer-item-text">
{{ item.label }}
</text>
</view>
</view>
</view>
</template>
<style scoped>
.layer-wrap {
position: relative;
/* z-index: 20; */
}
.layer-btn {
background: #fff;
border-radius: 8px;
padding: 10px 14px;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.08);
}
.layer-btn-text {
font-size: 14px;
color: #333;
font-weight: 600;
}
.layer-panel {
position: absolute;
top: calc(100% + 8px);
left: 0;
min-width: 140px;
background: #fff;
border-radius: 10px;
padding: 10px 0;
box-shadow: 0 4px 16px rgba(0, 0, 0, 0.12);
}
.panel-title {
display: block;
padding: 4px 16px 10px;
font-size: 13px;
/* color: #999; */
}
.layer-item {
padding: 10px 16px;
}
.layer-item.active {
background: #eff6ff;
}
.layer-item-text {
font-size: 15px;
color: #333;
}
.layer-item.active .layer-item-text {
color: #2563eb;
font-weight: 600;
}
</style>

View File

@@ -0,0 +1,118 @@
<script setup lang="ts">
import type { HeatmapLayerKey } from './heatUtils'
import { computed } from 'vue'
import { HEATMAP_LAYERS } from './heatUtils'
const props = defineProps<{
layer: HeatmapLayerKey
}>()
const title = computed(() => {
const found = HEATMAP_LAYERS.find(i => i.key === props.layer)
return `${found?.label || '压实度'}色阶`
})
</script>
<template>
<view class="legend">
<text class="legend-title">
{{ title }}
</text>
<view class="bar-row">
<view class="bar">
<view class="seg seg-green" />
<view class="seg seg-yellow" />
<view class="seg seg-red" />
</view>
</view>
<view class="marks">
<text class="mark">
0
</text>
<text class="mark mark-90">
90
</text>
<text class="mark mark-95">
95
</text>
<text class="mark">
100
</text>
</view>
</view>
</template>
<style scoped>
.legend {
padding: 12px 4px 4px;
}
.legend-title {
font-size: 13px;
color: #666;
margin-bottom: 8px;
display: block;
}
.bar-row {
width: 100%;
}
.bar {
display: flex;
height: 12px;
border-radius: 6px;
overflow: hidden;
}
.seg {
height: 100%;
}
.seg-green {
flex: 90;
background: #22c55e;
}
.seg-yellow {
flex: 5;
background: #eab308;
}
.seg-red {
flex: 5;
background: #ef4444;
}
.marks {
position: relative;
height: 18px;
margin-top: 4px;
}
.mark {
position: absolute;
font-size: 11px;
color: #999;
transform: translateX(-50%);
}
.mark:first-child {
left: 0;
transform: none;
}
.mark:last-child {
right: 0;
left: auto;
transform: none;
}
.mark-90 {
left: 90%;
}
.mark-95 {
left: 95%;
}
</style>

View File

@@ -0,0 +1,169 @@
<script setup lang="ts">
import type { MockRtkPoint } from '@/mock/rtkTrack'
import { headingText } from './heatUtils'
defineProps<{
visible: boolean
point: MockRtkPoint | null
}>()
const emit = defineEmits<{
close: []
}>()
</script>
<template>
<view v-if="visible && point" class="mask" @tap="emit('close')">
<view class="card" @tap.stop>
<view class="card-head">
<view class="pin" />
<view class="head-text">
<text class="title">
点位详情
</text>
<text class="sub">
点击地图任意位置查看
</text>
</view>
</view>
<view class="row">
<text class="label">
点位坐标
</text>
<text class="value">
{{ point.longitude.toFixed(4) }}°E, {{ point.latitude.toFixed(4) }}°N
</text>
</view>
<view class="row">
<text class="label">
航向角
</text>
<text class="value">
{{ headingText(point.heading) }}
</text>
</view>
<view class="row">
<text class="label">
压实速度
</text>
<text class="value">
{{ point.speed }}km/h
</text>
</view>
<view class="row">
<text class="label">
压实度
</text>
<text class="value">
{{ point.compaction }}%
</text>
</view>
<view class="row">
<text class="label">
温度
</text>
<text class="value">
{{ point.temperature }}℃
</text>
</view>
<view class="row">
<text class="label">
碾压遍数
</text>
<text class="value">
{{ point.passCount }}遍
</text>
</view>
<text class="hint">
点击空白处关闭
</text>
</view>
</view>
</template>
<style scoped>
.mask {
position: fixed;
left: 0;
right: 0;
top: 0;
bottom: 0;
background: rgba(0, 0, 0, 0.25);
z-index: 100;
display: flex;
align-items: center;
justify-content: center;
padding: 24px;
box-sizing: border-box;
}
.card {
width: 100%;
max-width: 420px;
background: #fff;
border-radius: 12px;
padding: 16px;
}
.card-head {
display: flex;
align-items: flex-start;
gap: 10px;
margin-bottom: 14px;
}
.pin {
width: 18px;
height: 18px;
margin-top: 2px;
border-radius: 50% 50% 50% 0;
background: #2563eb;
transform: rotate(-45deg);
}
.head-text {
display: flex;
flex-direction: column;
gap: 4px;
}
.title {
font-size: 18px;
font-weight: 700;
color: #111;
}
.sub {
font-size: 12px;
color: #999;
}
.row {
display: flex;
justify-content: space-between;
align-items: center;
padding: 10px 0;
border-bottom: 1px solid #f3f4f6;
}
.label {
font-size: 14px;
color: #666;
}
.value {
font-size: 14px;
color: #111;
font-weight: 600;
}
.hint {
display: block;
text-align: right;
margin-top: 12px;
font-size: 12px;
color: #999;
}
</style>

View File

@@ -7,10 +7,20 @@ import { getCurrentInstance, nextTick, onMounted, onUnmounted, ref, watch } from
import { useCurrentProject } from '@/composables/useCurrentProject' import { useCurrentProject } from '@/composables/useCurrentProject'
import { createMockRtkStream } from '@/mock/rtkTrack' import { createMockRtkStream } from '@/mock/rtkTrack'
const props = withDefaults(defineProps<{
/** canvas-id,同页多实例时需唯一 */
canvasId?: string
/** 是否点击跳转全屏 RTK 页(全屏页内应关闭) */
navigateOnClick?: boolean
}>(), {
canvasId: 'rtkTrackCanvas',
navigateOnClick: true,
})
const { hasCurrentProject } = useCurrentProject() const { hasCurrentProject } = useCurrentProject()
const instance = getCurrentInstance()?.proxy const instance = getCurrentInstance()?.proxy
const CANVAS_ID = 'rtkTrackCanvas' const CANVAS_ID = props.canvasId
const PADDING = 28 const PADDING = 28
const ROLLER_WIDTH = 14 const ROLLER_WIDTH = 14
const MIN_H = 160 const MIN_H = 160
@@ -31,6 +41,8 @@ let fixedMaxLat = 0 // 北端固定(起点一侧)
let pxPerMeter = 1 let pxPerMeter = 1
function goToRtk() { function goToRtk() {
if (!props.navigateOnClick)
return
uni.navigateTo({ url: '/pages/rtk/index' }) uni.navigateTo({ url: '/pages/rtk/index' })
} }

View File

@@ -1,13 +1,16 @@
/** /**
* RTK 轨迹假数据:蛇形碾压路径 + 持续推送 * RTK 轨迹假数据:蛇形碾压路径 + 持续推送
* 字段:经纬度、航向角(度,0=北)、压实度 * 字段:经纬度、航向、压实度、速度、温度、碾压遍数
*/ */
export interface MockRtkPoint { export interface MockRtkPoint {
latitude: number latitude: number // 纬度
longitude: number longitude: number // 经度
heading: number heading: number // 航向角(度,0=北,顺时针)
compaction: number compaction: number // 压实度 %
speed: number // 压实速度 km/h
temperature: number // 温度 ℃
passCount: number // 碾压遍数
} }
export interface MockRtkStreamOptions { export interface MockRtkStreamOptions {
@@ -40,6 +43,35 @@ function makeCompaction(seed: number) {
return Number(Math.min(98, Math.max(78, 88 + wave + (Math.random() - 0.5) * 3)).toFixed(1)) return Number(Math.min(98, Math.max(78, 88 + wave + (Math.random() - 0.5) * 3)).toFixed(1))
} }
function makeSpeed(seed: number) {
return Number((3.2 + Math.sin(seed / 5) * 1.2 + Math.random() * 0.8).toFixed(1))
}
function makeTemperature(seed: number) {
return Number((148 + Math.sin(seed / 7) * 8 + (Math.random() - 0.5) * 6).toFixed(0))
}
function makePassCount(seed: number) {
return Math.max(1, Math.min(8, Math.round(2 + (seed % 9) / 2 + Math.random())))
}
function buildPoint(
lat: number,
lon: number,
heading: number,
seed: number,
): MockRtkPoint {
return {
latitude: Number(lat.toFixed(7)),
longitude: Number(lon.toFixed(7)),
heading,
compaction: makeCompaction(seed),
speed: makeSpeed(seed),
temperature: makeTemperature(seed),
passCount: makePassCount(seed),
}
}
/** /**
* 行末三角掉头:行末 → 外侧顶点 → 下行起点 * 行末三角掉头:行末 → 外侧顶点 → 下行起点
* 航向先转 60°(到顶点),再转 120°(合计 180° 衔接下行) * 航向先转 60°(到顶点),再转 120°(合计 180° 衔接下行)
@@ -55,30 +87,25 @@ function pushUTurnTriangle(
) { ) {
const nextLat = startLat + latStep const nextLat = startLat + latStep
const midLat = startLat + latStep / 2 const midLat = startLat + latStep / 2
// 外侧突出量 ≈ 半个行距(换算到经度),形成尖角
const tipLatOffset = Math.abs(latStep) / 2 const tipLatOffset = Math.abs(latStep) / 2
const metersPerDegLat = 111320 const metersPerDegLat = 111320
const metersPerDegLon = 111320 * Math.cos((startLat * Math.PI) / 180) const metersPerDegLon = 111320 * Math.cos((startLat * Math.PI) / 180)
const tipLonOffset = (tipLatOffset * metersPerDegLat) / metersPerDegLon const tipLonOffset = (tipLatOffset * metersPerDegLat) / metersPerDegLon
const tipLon = startLon + (eastward ? tipLonOffset : -tipLonOffset) const tipLon = startLon + (eastward ? tipLonOffset : -tipLonOffset)
const turnSign = eastward ? 1 : -1 const turnSign = eastward ? 1 : -1
// 1) 外侧顶点:航向 +60° / -60° points.push(buildPoint(
points.push({ midLat,
latitude: Number(midLat.toFixed(7)), tipLon,
longitude: Number(tipLon.toFixed(7)), normalizeHeading(fromHeading + 60 * turnSign),
heading: normalizeHeading(fromHeading + 60 * turnSign), seedRef.value++,
compaction: makeCompaction(seedRef.value++), ))
}) points.push(buildPoint(
nextLat,
// 2) 下行起点:航向转满 180° startLon,
points.push({ normalizeHeading(fromHeading + 180 * turnSign),
latitude: Number(nextLat.toFixed(7)), seedRef.value++,
longitude: Number(startLon.toFixed(7)), ))
heading: normalizeHeading(fromHeading + 180 * turnSign),
compaction: makeCompaction(seedRef.value++),
})
} }
/** 生成类似示意图的左右蛇形碾压路径 */ /** 生成类似示意图的左右蛇形碾压路径 */
@@ -86,11 +113,10 @@ export function generateSerpentineTrack(options: MockRtkStreamOptions = {}): Moc
const rows = options.rows ?? 80 const rows = options.rows ?? 80
const pointsPerRow = options.pointsPerRow ?? 40 const pointsPerRow = options.pointsPerRow ?? 40
// 以雄安附近坐标为基准,量级约几十米行程
const originLat = 39.0425 const originLat = 39.0425
const originLon = 115.8620 const originLon = 115.8620
const lonStep = 0.00012 // ~10m 东向 const lonStep = 0.00012
const latStep = -0.000045 // ~5m 南向(行间距) const latStep = -0.000045
const points: MockRtkPoint[] = [] const points: MockRtkPoint[] = []
const seedRef = { value: 0 } const seedRef = { value: 0 }
@@ -103,15 +129,9 @@ export function generateSerpentineTrack(options: MockRtkStreamOptions = {}): Moc
for (let col = 0; col < pointsPerRow; col++) { for (let col = 0; col < pointsPerRow; col++) {
const t = eastward ? col : (pointsPerRow - 1 - col) const t = eastward ? col : (pointsPerRow - 1 - col)
const lon = originLon + t * lonStep const lon = originLon + t * lonStep
points.push({ points.push(buildPoint(lat, lon, heading, seedRef.value++))
latitude: Number(lat.toFixed(7)),
longitude: Number(lon.toFixed(7)),
heading,
compaction: makeCompaction(seedRef.value++),
})
} }
// 行末三角掉头(外凸尖角)
if (row < rows - 1) { if (row < rows - 1) {
const endT = eastward ? (pointsPerRow - 1) : 0 const endT = eastward ? (pointsPerRow - 1) : 0
const endLon = originLon + endT * lonStep const endLon = originLon + endT * lonStep

View File

@@ -1,23 +1,66 @@
<script lang="ts" setup> <script lang="ts" setup>
import type { HeatmapLayerKey } from '@/components/heatmap/heatUtils'
import type { MockRtkPoint } from '@/mock/rtkTrack'
import { ref } from 'vue'
definePage({ definePage({
layout: false, layout: false,
style: { style: {
navigationStyle: 'custom', navigationStyle: 'custom',
}, },
}) })
const layer = ref<HeatmapLayerKey>('compaction')
const layerVisible = ref(false)
const detailVisible = ref(false)
const selectedPoint = ref<MockRtkPoint | null>(null)
function onSelectPoint(point: MockRtkPoint) {
selectedPoint.value = point
detailVisible.value = true
layerVisible.value = false
}
function closeDetail() {
detailVisible.value = false
}
function onBlankTap() {
if (layerVisible.value)
layerVisible.value = false
}
</script> </script>
<template> <template>
<view class="page"> <view class="page" @tap="onBlankTap">
<!-- 顶部导航 -->
<FullscreenNav /> <FullscreenNav />
<!-- 占位内容 --> <scroll-view class="content" scroll-y>
<view class="content"> <view class="card" @tap.stop>
<text class="placeholder-text"> <view class="toolbar">
压实热力图 <text class="tip">
点击地图查看点位详情
</text> </text>
<heatmap-layerPanel
v-model="layer"
v-model:visible="layerVisible"
/>
</view> </view>
<heatmap-canvas
:layer="layer"
@select-point="onSelectPoint"
/>
<heatmap-legend :layer="layer" />
</view>
</scroll-view>
<heatmap-pointDetail
:visible="detailVisible"
:point="selectedPoint"
@close="closeDetail"
/>
</view> </view>
</template> </template>
@@ -25,19 +68,38 @@ definePage({
.page { .page {
min-height: 100vh; min-height: 100vh;
background-color: #EAEFF9; background-color: #EAEFF9;
display: flex;
flex-direction: column;
} }
.content { .content {
padding: 16px; flex: 1;
height: 0;
padding: 12px 16px;
padding-top: 140px; padding-top: 140px;
display: flex; box-sizing: border-box;
align-items: center;
justify-content: center;
min-height: calc(100vh - 120px);
} }
.placeholder-text { .card {
font-size: 18px; background: #fff;
color: #999; border-radius: 12px;
padding: 12px;
}
.toolbar {
display: flex;
align-items: flex-start;
justify-content: space-between;
gap: 12px;
margin-bottom: 12px;
}
.tip {
flex: 1;
font-size: 13px;
color: #666;
/* background: #f8fafc; */
border-radius: 8px;
padding: 10px 12px;
} }
</style> </style>

View File

@@ -2,7 +2,6 @@
definePage({ definePage({
layout: false, layout: false,
style: { style: {
// navigationBarTitleText: 'RTK轨迹',
navigationStyle: 'custom', navigationStyle: 'custom',
}, },
}) })
@@ -10,15 +9,17 @@ definePage({
<template> <template>
<view class="page"> <view class="page">
<!-- 顶部导航 -->
<FullscreenNav /> <FullscreenNav />
<!-- 占位内容 --> <!-- 放大展示与作业页相同的 RTK 轨迹绘制 -->
<view class="content"> <scroll-view class="content" scroll-y>
<text class="placeholder-text"> <view class="canvas-card">
RTK轨迹 <work-rtkCanvas
</text> canvas-id="rtkTrackCanvasFull"
:navigate-on-click="false"
/>
</view> </view>
</scroll-view>
</view> </view>
</template> </template>
@@ -26,19 +27,24 @@ definePage({
.page { .page {
min-height: 100vh; min-height: 100vh;
background-color: #EAEFF9; background-color: #EAEFF9;
display: flex;
flex-direction: column;
} }
.content { .content {
padding: 16px; flex: 1;
height: 0;
padding: 12px 16px;
padding-top: 140px; padding-top: 140px;
display: flex; box-sizing: border-box;
align-items: center;
justify-content: center;
min-height: calc(100vh - 120px);
} }
.placeholder-text { .canvas-card {
font-size: 18px; background: #fff;
color: #999; border-radius: 12px;
padding: 12px;
/* 全屏宽度绘制 → 比例尺更大,视觉上放大 */
width: 100%;
box-sizing: border-box;
} }
</style> </style>