|
@@ -10,38 +10,85 @@
|
|
|
|
|
|
<!-- 信息面板 -->
|
|
<!-- 信息面板 -->
|
|
<div class="overlay">
|
|
<div class="overlay">
|
|
- <h3>雷达点云图例</h3>
|
|
|
|
- <div class="legend-item">
|
|
|
|
- <div class="legend-color" style="background-color: #ff0000"></div>
|
|
|
|
- <span>强度(1800-2000)</span>
|
|
|
|
- </div>
|
|
|
|
- <div class="legend-item">
|
|
|
|
- <div class="legend-color" style="background-color: #ffff00"></div>
|
|
|
|
- <span>强度(1600-1800)</span>
|
|
|
|
- </div>
|
|
|
|
- <div class="legend-item">
|
|
|
|
- <div class="legend-color" style="background-color: #00ff00"></div>
|
|
|
|
- <span>强度(1400-1600)</span>
|
|
|
|
|
|
+ <!-- 强度颜色图例 -->
|
|
|
|
+ <div class="legend-title">强度图例</div>
|
|
|
|
+ <div class="legend-content">
|
|
|
|
+ <!-- 渐变条 -->
|
|
|
|
+ <div class="legend-gradient"> </div>
|
|
|
|
+ <!-- 数值标注 -->
|
|
|
|
+ <div class="legend-numbers">
|
|
|
|
+ <span>0.0</span>
|
|
|
|
+ <span>0.25</span>
|
|
|
|
+ <span>0.5</span>
|
|
|
|
+ <span>0.75</span>
|
|
|
|
+ <span>1.0</span>
|
|
|
|
+ </div>
|
|
</div>
|
|
</div>
|
|
|
|
+
|
|
|
|
+ <!-- 点云数量 -->
|
|
<div class="legend-item">
|
|
<div class="legend-item">
|
|
- <div class="legend-color" style="background-color: #00ffff"></div>
|
|
|
|
- <span>强度(1200-1400)</span>
|
|
|
|
|
|
+ <span class="legend-item-title">点云数量: </span>
|
|
|
|
+ <span class="legend-item-value" id="pointCount">{{ pointCount }}</span>
|
|
</div>
|
|
</div>
|
|
|
|
+
|
|
|
|
+ <!-- 更新时间 -->
|
|
<div class="legend-item">
|
|
<div class="legend-item">
|
|
- <div class="legend-color" style="background-color: #0000ff"></div>
|
|
|
|
- <span>强度(1000-1200)</span>
|
|
|
|
|
|
+ <span class="legend-item-title">更新时间: </span>
|
|
|
|
+ <span class="legend-item-value" id="updateTime">{{ lastUpdate }}</span>
|
|
</div>
|
|
</div>
|
|
- <p
|
|
|
|
- >当前点数量: <span>{{ pointCount }}</span></p
|
|
|
|
- >
|
|
|
|
- <p
|
|
|
|
- >最后更新: <span>{{ lastUpdate }}</span></p
|
|
|
|
- >
|
|
|
|
|
|
+
|
|
|
|
+ <!-- 按钮区域 -->
|
|
<a-space class="control-space" :size="12">
|
|
<a-space class="control-space" :size="12">
|
|
<ZoomInOutlined @click="zoomIn" :disabled="isLoading" title="放大" />
|
|
<ZoomInOutlined @click="zoomIn" :disabled="isLoading" title="放大" />
|
|
<RedoOutlined @click="resetView" :disabled="isLoading" title="重置" />
|
|
<RedoOutlined @click="resetView" :disabled="isLoading" title="重置" />
|
|
<ZoomOutOutlined @click="zoomOut" :disabled="isLoading" title="缩小" />
|
|
<ZoomOutOutlined @click="zoomOut" :disabled="isLoading" title="缩小" />
|
|
|
|
+ <CodeSandboxOutlined
|
|
|
|
+ @click="configPanelVisible = !configPanelVisible"
|
|
|
|
+ :disabled="isLoading"
|
|
|
|
+ :style="{ color: configPanelVisible ? '#1677ff' : '#fff' }"
|
|
|
|
+ title="调整房间尺寸"
|
|
|
|
+ />
|
|
</a-space>
|
|
</a-space>
|
|
|
|
+
|
|
|
|
+ <!-- 配置面板 -->
|
|
|
|
+ <div v-if="configPanelVisible" class="config-panel">
|
|
|
|
+ <div class="config-item">
|
|
|
|
+ <div class="config-item-label">房间宽度cm:</div>
|
|
|
|
+ <a-input-number
|
|
|
|
+ class="config-item-input"
|
|
|
|
+ v-model:value="roomDimensions.width"
|
|
|
|
+ size="small"
|
|
|
|
+ min="1"
|
|
|
|
+ :controls="false"
|
|
|
|
+ @keyup.enter="applyRoomDimensions"
|
|
|
|
+ @blur="applyRoomDimensions"
|
|
|
|
+ />
|
|
|
|
+ </div>
|
|
|
|
+ <div class="config-item">
|
|
|
|
+ <div class="config-item-label">房间长度cm:</div>
|
|
|
|
+ <a-input-number
|
|
|
|
+ class="config-item-input"
|
|
|
|
+ v-model:value="roomDimensions.length"
|
|
|
|
+ size="small"
|
|
|
|
+ min="1"
|
|
|
|
+ :controls="false"
|
|
|
|
+ @keyup.enter="applyRoomDimensions"
|
|
|
|
+ @blur="applyRoomDimensions"
|
|
|
|
+ />
|
|
|
|
+ </div>
|
|
|
|
+ <div class="config-item">
|
|
|
|
+ <div class="config-item-label">房间高度cm:</div>
|
|
|
|
+ <a-input-number
|
|
|
|
+ class="config-item-input"
|
|
|
|
+ v-model:value="roomDimensions.height"
|
|
|
|
+ size="small"
|
|
|
|
+ min="1"
|
|
|
|
+ :controls="false"
|
|
|
|
+ @keyup.enter="applyRoomDimensions"
|
|
|
|
+ @blur="applyRoomDimensions"
|
|
|
|
+ />
|
|
|
|
+ </div>
|
|
|
|
+ </div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
@@ -58,7 +105,12 @@ import { RenderPass } from 'three/examples/jsm/postprocessing/RenderPass.js'
|
|
import { ShaderPass } from 'three/examples/jsm/postprocessing/ShaderPass.js'
|
|
import { ShaderPass } from 'three/examples/jsm/postprocessing/ShaderPass.js'
|
|
import { BloomPass } from 'three/examples/jsm/postprocessing/BloomPass.js'
|
|
import { BloomPass } from 'three/examples/jsm/postprocessing/BloomPass.js'
|
|
import { CopyShader } from 'three/examples/jsm/shaders/CopyShader.js'
|
|
import { CopyShader } from 'three/examples/jsm/shaders/CopyShader.js'
|
|
-import { ZoomInOutlined, ZoomOutOutlined, RedoOutlined } from '@ant-design/icons-vue'
|
|
|
|
|
|
+import {
|
|
|
|
+ ZoomInOutlined,
|
|
|
|
+ ZoomOutOutlined,
|
|
|
|
+ RedoOutlined,
|
|
|
|
+ CodeSandboxOutlined,
|
|
|
|
+} from '@ant-design/icons-vue'
|
|
|
|
|
|
// 定义组件名称
|
|
// 定义组件名称
|
|
defineOptions({
|
|
defineOptions({
|
|
@@ -100,7 +152,7 @@ const state = reactive({
|
|
isLoading: true, // 加载状态
|
|
isLoading: true, // 加载状态
|
|
loadingText: '正在加载雷达点云数据...', // 加载提示文本
|
|
loadingText: '正在加载雷达点云数据...', // 加载提示文本
|
|
pointCount: 0, // 点数量
|
|
pointCount: 0, // 点数量
|
|
- lastUpdate: '-', // 最后更新时间
|
|
|
|
|
|
+ lastUpdate: '--:--:--', // 最后更新时间
|
|
})
|
|
})
|
|
|
|
|
|
// 将响应式状态转换为ref以便在模板中使用
|
|
// 将响应式状态转换为ref以便在模板中使用
|
|
@@ -124,11 +176,11 @@ let initializationAttempts = 0 // 初始化尝试次数
|
|
const MAX_INIT_ATTEMPTS = 5 // 最大初始化尝试次数
|
|
const MAX_INIT_ATTEMPTS = 5 // 最大初始化尝试次数
|
|
|
|
|
|
// 房间尺寸配置(明确边界)
|
|
// 房间尺寸配置(明确边界)
|
|
-const roomDimensions: RoomDimensions = {
|
|
|
|
- width: 400, // X轴范围:-200 ~ 200
|
|
|
|
- length: 400, // Z轴范围:-200 ~ 200
|
|
|
|
- height: 280, // Y轴范围:0 ~ 280
|
|
|
|
-}
|
|
|
|
|
|
+const roomDimensions = ref<RoomDimensions>({
|
|
|
|
+ length: 400, // 长 Y轴范围:-200 ~ 200
|
|
|
|
+ width: 400, // 宽 X轴范围:-200 ~ 200
|
|
|
|
+ height: 280, // 高 Z轴范围:0 ~ 280
|
|
|
|
+})
|
|
|
|
|
|
/**
|
|
/**
|
|
* 安全地释放材质资源
|
|
* 安全地释放材质资源
|
|
@@ -150,8 +202,12 @@ function disposeMaterial(material: THREE.Material | THREE.Material[] | null) {
|
|
*/
|
|
*/
|
|
const getRoomBoundingBox = (): THREE.Box3 => {
|
|
const getRoomBoundingBox = (): THREE.Box3 => {
|
|
return new THREE.Box3(
|
|
return new THREE.Box3(
|
|
- new THREE.Vector3(-roomDimensions.width / 2, 0, -roomDimensions.length / 2), // 最小点
|
|
|
|
- new THREE.Vector3(roomDimensions.width / 2, roomDimensions.height, roomDimensions.length / 2) // 最大点
|
|
|
|
|
|
+ new THREE.Vector3(-roomDimensions.value.width / 2, 0, -roomDimensions.value.length / 2), // 最小点
|
|
|
|
+ new THREE.Vector3(
|
|
|
|
+ roomDimensions.value.width / 2,
|
|
|
|
+ roomDimensions.value.height,
|
|
|
|
+ roomDimensions.value.length / 2
|
|
|
|
+ ) // 最大点
|
|
)
|
|
)
|
|
}
|
|
}
|
|
|
|
|
|
@@ -292,7 +348,7 @@ function setupCameraAndControls() {
|
|
roomBbox.getCenter(roomCenter) // 房间中心点(目标点)
|
|
roomBbox.getCenter(roomCenter) // 房间中心点(目标点)
|
|
|
|
|
|
// 调整目标点高度,更符合正视视角
|
|
// 调整目标点高度,更符合正视视角
|
|
- roomCenter.y = roomDimensions.height / 3
|
|
|
|
|
|
+ roomCenter.y = roomDimensions.value.height / 3
|
|
|
|
|
|
// 计算刚好能完整显示房间的距离
|
|
// 计算刚好能完整显示房间的距离
|
|
const initialDistance = calculateOptimalDistance()
|
|
const initialDistance = calculateOptimalDistance()
|
|
@@ -418,29 +474,29 @@ function createRoom() {
|
|
|
|
|
|
// 后墙
|
|
// 后墙
|
|
const backWall = new THREE.Mesh(
|
|
const backWall = new THREE.Mesh(
|
|
- new THREE.PlaneGeometry(roomDimensions.width, roomDimensions.height),
|
|
|
|
|
|
+ new THREE.PlaneGeometry(roomDimensions.value.width, roomDimensions.value.height),
|
|
glassMat
|
|
glassMat
|
|
)
|
|
)
|
|
- backWall.position.set(0, roomDimensions.height / 2, -roomDimensions.length / 2)
|
|
|
|
|
|
+ backWall.position.set(0, roomDimensions.value.height / 2, -roomDimensions.value.length / 2)
|
|
room.add(backWall)
|
|
room.add(backWall)
|
|
glassWalls.push(backWall)
|
|
glassWalls.push(backWall)
|
|
|
|
|
|
// 左墙
|
|
// 左墙
|
|
const leftWall = new THREE.Mesh(
|
|
const leftWall = new THREE.Mesh(
|
|
- new THREE.PlaneGeometry(roomDimensions.length, roomDimensions.height),
|
|
|
|
|
|
+ new THREE.PlaneGeometry(roomDimensions.value.length, roomDimensions.value.height),
|
|
glassMat
|
|
glassMat
|
|
)
|
|
)
|
|
- leftWall.position.set(-roomDimensions.width / 2, roomDimensions.height / 2, 0)
|
|
|
|
|
|
+ leftWall.position.set(-roomDimensions.value.width / 2, roomDimensions.value.height / 2, 0)
|
|
leftWall.rotation.y = Math.PI / 2
|
|
leftWall.rotation.y = Math.PI / 2
|
|
room.add(leftWall)
|
|
room.add(leftWall)
|
|
glassWalls.push(leftWall)
|
|
glassWalls.push(leftWall)
|
|
|
|
|
|
// 右墙
|
|
// 右墙
|
|
const rightWall = new THREE.Mesh(
|
|
const rightWall = new THREE.Mesh(
|
|
- new THREE.PlaneGeometry(roomDimensions.length, roomDimensions.height),
|
|
|
|
|
|
+ new THREE.PlaneGeometry(roomDimensions.value.length, roomDimensions.value.height),
|
|
glassMat
|
|
glassMat
|
|
)
|
|
)
|
|
- rightWall.position.set(roomDimensions.width / 2, roomDimensions.height / 2, 0)
|
|
|
|
|
|
+ rightWall.position.set(roomDimensions.value.width / 2, roomDimensions.value.height / 2, 0)
|
|
rightWall.rotation.y = -Math.PI / 2
|
|
rightWall.rotation.y = -Math.PI / 2
|
|
room.add(rightWall)
|
|
room.add(rightWall)
|
|
glassWalls.push(rightWall)
|
|
glassWalls.push(rightWall)
|
|
@@ -474,7 +530,12 @@ function createFloorPoints() {
|
|
if (!scene) return
|
|
if (!scene) return
|
|
|
|
|
|
// 创建平面几何体作为地板
|
|
// 创建平面几何体作为地板
|
|
- const geometry = new THREE.PlaneGeometry(roomDimensions.width, roomDimensions.length, 64, 64)
|
|
|
|
|
|
+ const geometry = new THREE.PlaneGeometry(
|
|
|
|
+ roomDimensions.value.width,
|
|
|
|
+ roomDimensions.value.length,
|
|
|
|
+ 64,
|
|
|
|
+ 64
|
|
|
|
+ )
|
|
|
|
|
|
// 创建点材质的地板
|
|
// 创建点材质的地板
|
|
floorPoints = new THREE.Points(
|
|
floorPoints = new THREE.Points(
|
|
@@ -561,17 +622,28 @@ function updatePointCloud(rawPoints: [number, number, number, number][]) {
|
|
const colors = new Float32Array(pointCountValue * 3)
|
|
const colors = new Float32Array(pointCountValue * 3)
|
|
|
|
|
|
// 填充位置和颜色数据
|
|
// 填充位置和颜色数据
|
|
|
|
+ // 先找到该帧的最小强度和最大强度
|
|
|
|
+ const intensities = rawPoints.map((p) => p[3])
|
|
|
|
+ const minIntensity = Math.min(...intensities)
|
|
|
|
+ const maxIntensity = Math.max(...intensities)
|
|
|
|
+
|
|
|
|
+ // 防止除以 0
|
|
|
|
+ const range = maxIntensity - minIntensity || 1
|
|
|
|
+
|
|
rawPoints.forEach((point, index) => {
|
|
rawPoints.forEach((point, index) => {
|
|
const [x, y, z, intensity] = point
|
|
const [x, y, z, intensity] = point
|
|
const idx = index * 3
|
|
const idx = index * 3
|
|
|
|
|
|
- // 设置位置
|
|
|
|
- positions[idx] = x
|
|
|
|
- positions[idx + 1] = y
|
|
|
|
- positions[idx + 2] = z
|
|
|
|
|
|
+ // 设置位置(原始单位m 需要转为cm * 100)
|
|
|
|
+ // positions[idx] = x
|
|
|
|
+ // positions[idx + 1] = y
|
|
|
|
+ // positions[idx + 2] = z
|
|
|
|
+ positions[idx] = x * 100
|
|
|
|
+ positions[idx + 1] = y * 100
|
|
|
|
+ positions[idx + 2] = z * 100
|
|
|
|
|
|
- // 归一化强度:假设范围是 [1000, 2000]
|
|
|
|
- const normalizedIntensity = Math.min(Math.max((intensity - 1000) / 1000, 0), 1)
|
|
|
|
|
|
+ // 动态归一化强度
|
|
|
|
+ const normalizedIntensity = (intensity - minIntensity) / range
|
|
|
|
|
|
// 获取插值颜色
|
|
// 获取插值颜色
|
|
const { r, g, b } = getInterpolatedColor(normalizedIntensity)
|
|
const { r, g, b } = getInterpolatedColor(normalizedIntensity)
|
|
@@ -605,53 +677,59 @@ function updatePointCloud(rawPoints: [number, number, number, number][]) {
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
-/**
|
|
|
|
- * 根据强度值获取插值颜色
|
|
|
|
- * @param value 归一化的强度值(0-1)
|
|
|
|
- * @returns RGB颜色对象
|
|
|
|
- */
|
|
|
|
-function getInterpolatedColor(value: number): RgbColor {
|
|
|
|
- // 强度到颜色的渐变映射
|
|
|
|
- const intensityGradientSmooth = [
|
|
|
|
- { value: 0.0, color: '#0000FF' }, // 蓝
|
|
|
|
- { value: 0.25, color: '#00FFFF' }, // 青
|
|
|
|
- { value: 0.5, color: '#00FF00' }, // 绿
|
|
|
|
- { value: 0.75, color: '#FFFF00' }, // 黄
|
|
|
|
- { value: 1.0, color: '#FF0000' }, // 红
|
|
|
|
- ]
|
|
|
|
-
|
|
|
|
- // 找到对应的颜色区间并插值
|
|
|
|
- for (let i = 0; i < intensityGradientSmooth.length - 1; i++) {
|
|
|
|
- const start = intensityGradientSmooth[i]
|
|
|
|
- const end = intensityGradientSmooth[i + 1]
|
|
|
|
- if (value >= start.value && value <= end.value) {
|
|
|
|
- const t = (value - start.value) / (end.value - start.value)
|
|
|
|
- const c1 = hexToRgb(start.color)
|
|
|
|
- const c2 = hexToRgb(end.color)
|
|
|
|
- return {
|
|
|
|
- r: c1.r + (c2.r - c1.r) * t,
|
|
|
|
- g: c1.g + (c2.g - c1.g) * t,
|
|
|
|
- b: c1.b + (c2.b - c1.b) * t,
|
|
|
|
- }
|
|
|
|
- }
|
|
|
|
|
|
+/** 五段渐变:蓝→青→绿→黄→红(与 Jet 主锚点一致) */
|
|
|
|
+const intensityGradientSmooth = [
|
|
|
|
+ { value: 0.0, color: '#0000FF' }, // 蓝
|
|
|
|
+ { value: 0.25, color: '#00FFFF' }, // 青
|
|
|
|
+ { value: 0.5, color: '#00FF00' }, // 绿
|
|
|
|
+ { value: 0.75, color: '#FFFF00' }, // 黄
|
|
|
|
+ { value: 1.0, color: '#FF0000' }, // 红
|
|
|
|
+]
|
|
|
|
+
|
|
|
|
+// 预计算成 RGB,减少运行时开销
|
|
|
|
+const gradientStops = intensityGradientSmooth.map((s) => ({
|
|
|
|
+ value: s.value,
|
|
|
|
+ rgb: hexToRgb(s.color),
|
|
|
|
+}))
|
|
|
|
+
|
|
|
|
+/** 将十六进制颜色转换为 0–1 的 RGB */
|
|
|
|
+function hexToRgb(hex: string): RgbColor {
|
|
|
|
+ const n = parseInt(hex.slice(1), 16)
|
|
|
|
+ return {
|
|
|
|
+ r: ((n >> 16) & 255) / 255,
|
|
|
|
+ g: ((n >> 8) & 255) / 255,
|
|
|
|
+ b: (n & 255) / 255,
|
|
}
|
|
}
|
|
|
|
+}
|
|
|
|
|
|
- // 默认返回最后一个颜色
|
|
|
|
- return hexToRgb(intensityGradientSmooth[intensityGradientSmooth.length - 1].color)
|
|
|
|
|
|
+/** 线性插值 */
|
|
|
|
+function lerp(a: number, b: number, t: number) {
|
|
|
|
+ return a + (b - a) * t
|
|
}
|
|
}
|
|
|
|
|
|
/**
|
|
/**
|
|
- * 将十六进制颜色转换为RGB颜色
|
|
|
|
- * @param hex 十六进制颜色字符串
|
|
|
|
- * @returns RGB颜色对象(通道值0-1)
|
|
|
|
|
|
+ * 已归一化强度(0–1) → 颜色
|
|
|
|
+ * @param value01 已在外部归一化到 0–1 的强度
|
|
|
|
+ * @returns RGB (0–1)
|
|
*/
|
|
*/
|
|
-function hexToRgb(hex: string): RgbColor {
|
|
|
|
- const bigint = parseInt(hex.slice(1), 16)
|
|
|
|
- return {
|
|
|
|
- r: ((bigint >> 16) & 255) / 255,
|
|
|
|
- g: ((bigint >> 8) & 255) / 255,
|
|
|
|
- b: (bigint & 255) / 255,
|
|
|
|
|
|
+function getInterpolatedColor(value01: number): RgbColor {
|
|
|
|
+ const v = Math.min(Math.max(value01, 0), 1) // clamp 0–1
|
|
|
|
+
|
|
|
|
+ // 落在哪个区间
|
|
|
|
+ for (let i = 0; i < gradientStops.length - 1; i++) {
|
|
|
|
+ const a = gradientStops[i]
|
|
|
|
+ const b = gradientStops[i + 1]
|
|
|
|
+ if (v >= a.value && v <= b.value) {
|
|
|
|
+ const t = (v - a.value) / (b.value - a.value)
|
|
|
|
+ return {
|
|
|
|
+ r: lerp(a.rgb.r, b.rgb.r, t),
|
|
|
|
+ g: lerp(a.rgb.g, b.rgb.g, t),
|
|
|
|
+ b: lerp(a.rgb.b, b.rgb.b, t),
|
|
|
|
+ }
|
|
|
|
+ }
|
|
}
|
|
}
|
|
|
|
+ // v==1 的兜底
|
|
|
|
+ return gradientStops[gradientStops.length - 1].rgb
|
|
}
|
|
}
|
|
|
|
|
|
/**
|
|
/**
|
|
@@ -802,6 +880,41 @@ function cleanupThreeJs() {
|
|
initialCameraDirection = new THREE.Vector3()
|
|
initialCameraDirection = new THREE.Vector3()
|
|
}
|
|
}
|
|
|
|
|
|
|
|
+// 在script部分添加applyRoomDimensions函数
|
|
|
|
+const configPanelVisible = ref(false)
|
|
|
|
+/**
|
|
|
|
+ * 应用新的房间尺寸设置
|
|
|
|
+ * 重新创建房间墙壁和地板,并重置视图
|
|
|
|
+ */
|
|
|
|
+function applyRoomDimensions() {
|
|
|
|
+ if (!scene) return
|
|
|
|
+
|
|
|
|
+ // 移除现有的房间墙壁
|
|
|
|
+ glassWalls.forEach((wall) => {
|
|
|
|
+ if (wall.parent) {
|
|
|
|
+ wall.parent.remove(wall)
|
|
|
|
+ }
|
|
|
|
+ disposeMaterial(wall.material)
|
|
|
|
+ wall.geometry.dispose()
|
|
|
|
+ })
|
|
|
|
+ glassWalls = []
|
|
|
|
+
|
|
|
|
+ // 移除现有的地板点
|
|
|
|
+ if (floorPoints && floorPoints.parent) {
|
|
|
|
+ floorPoints.parent.remove(floorPoints)
|
|
|
|
+ disposeMaterial(floorPoints.material)
|
|
|
|
+ floorPoints.geometry.dispose()
|
|
|
|
+ floorPoints = null
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ // 创建新的房间墙壁和地板
|
|
|
|
+ createRoom()
|
|
|
|
+ createFloorPoints()
|
|
|
|
+
|
|
|
|
+ // 重置视图以适应新的房间尺寸
|
|
|
|
+ resetView()
|
|
|
|
+}
|
|
|
|
+
|
|
// 组件挂载时初始化
|
|
// 组件挂载时初始化
|
|
onMounted(() => {
|
|
onMounted(() => {
|
|
nextTick().then(() => {
|
|
nextTick().then(() => {
|
|
@@ -882,16 +995,54 @@ onUnmounted(() => {
|
|
position: absolute;
|
|
position: absolute;
|
|
top: 10px;
|
|
top: 10px;
|
|
right: 10px;
|
|
right: 10px;
|
|
- color: white;
|
|
|
|
- padding: 10px;
|
|
|
|
- border-radius: 5px;
|
|
|
|
|
|
+ color: #fff;
|
|
|
|
+ padding: 12px;
|
|
|
|
+ width: 200px;
|
|
|
|
+ font-size: 16px;
|
|
z-index: 10;
|
|
z-index: 10;
|
|
}
|
|
}
|
|
|
|
|
|
|
|
+.legend-title {
|
|
|
|
+ margin-bottom: 12px;
|
|
|
|
+ font-weight: bold;
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+.legend-content {
|
|
|
|
+ display: flex;
|
|
|
|
+ flex-direction: column;
|
|
|
|
+ align-items: center;
|
|
|
|
+ margin-bottom: 12px;
|
|
|
|
+
|
|
|
|
+ .legend-gradient {
|
|
|
|
+ width: 100%;
|
|
|
|
+ height: 20px;
|
|
|
|
+ background: linear-gradient(
|
|
|
|
+ to right,
|
|
|
|
+ #0000ff 0%,
|
|
|
|
+ #00ffff 25%,
|
|
|
|
+ #00ff00 50%,
|
|
|
|
+ #ffff00 75%,
|
|
|
|
+ #ff0000 100%
|
|
|
|
+ );
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ .legend-numbers {
|
|
|
|
+ display: flex;
|
|
|
|
+ justify-content: space-between;
|
|
|
|
+ width: 100%;
|
|
|
|
+ font-size: 12px;
|
|
|
|
+ margin-top: 4px;
|
|
|
|
+ }
|
|
|
|
+}
|
|
|
|
+
|
|
.legend-item {
|
|
.legend-item {
|
|
display: flex;
|
|
display: flex;
|
|
align-items: center;
|
|
align-items: center;
|
|
margin: 5px 0;
|
|
margin: 5px 0;
|
|
|
|
+
|
|
|
|
+ &-title {
|
|
|
|
+ margin-right: 8px;
|
|
|
|
+ }
|
|
}
|
|
}
|
|
|
|
|
|
.legend-color {
|
|
.legend-color {
|
|
@@ -903,9 +1054,15 @@ onUnmounted(() => {
|
|
|
|
|
|
.control-space {
|
|
.control-space {
|
|
color: #fff;
|
|
color: #fff;
|
|
- font-size: 24px;
|
|
|
|
- cursor: pointer;
|
|
|
|
|
|
+ font-size: 20px;
|
|
font-weight: 600;
|
|
font-weight: 600;
|
|
|
|
+
|
|
|
|
+ :deep(.ant-space-item) {
|
|
|
|
+ cursor: pointer;
|
|
|
|
+ &:hover {
|
|
|
|
+ color: #1677ff;
|
|
|
|
+ }
|
|
|
|
+ }
|
|
}
|
|
}
|
|
|
|
|
|
.reset-button:disabled {
|
|
.reset-button:disabled {
|
|
@@ -913,6 +1070,32 @@ onUnmounted(() => {
|
|
cursor: not-allowed;
|
|
cursor: not-allowed;
|
|
}
|
|
}
|
|
|
|
|
|
|
|
+.config-panel {
|
|
|
|
+ margin-top: 12px;
|
|
|
|
+ transition: all 0.3s ease;
|
|
|
|
+}
|
|
|
|
+.config-title {
|
|
|
|
+ margin: 0 0 10px 0;
|
|
|
|
+ font-size: 16px;
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+.config-item {
|
|
|
|
+ display: flex;
|
|
|
|
+ align-items: center;
|
|
|
|
+ margin-bottom: 8px;
|
|
|
|
+ &-label {
|
|
|
|
+ width: 100px;
|
|
|
|
+ line-height: 24px;
|
|
|
|
+ }
|
|
|
|
+ &-input {
|
|
|
|
+ flex: 1;
|
|
|
|
+ background-color: transparent;
|
|
|
|
+ :deep(.ant-input-number-input-wrap) .ant-input-number-input {
|
|
|
|
+ color: #fff;
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+}
|
|
|
|
+
|
|
.info {
|
|
.info {
|
|
position: absolute;
|
|
position: absolute;
|
|
bottom: 10px;
|
|
bottom: 10px;
|