点云
This commit is contained in:
4804
src/config/point1.js
Normal file
4804
src/config/point1.js
Normal file
File diff suppressed because it is too large
Load Diff
@@ -49,7 +49,7 @@
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import GlMap from './gl-map.vue'
|
||||
import GlMap from './gl-map-1.vue'
|
||||
import { driver } from 'driver.js'
|
||||
import 'driver.js/dist/driver.css'
|
||||
import { startMapping, stopMapping, getMappingStatus, setStation, oneClickDeployment, abandonMapping } from '../../config/getData.js'
|
||||
@@ -147,7 +147,7 @@ export default {
|
||||
/* eslint-disable */
|
||||
initGuide() {
|
||||
const config = {
|
||||
allowClose: false,
|
||||
// allowClose: false,
|
||||
overlayOpacity: 0.7,
|
||||
popoverClass: 'driverjs-theme',
|
||||
stagePadding: 0,
|
||||
|
||||
565
src/pages/modules/gl-map-1.vue
Normal file
565
src/pages/modules/gl-map-1.vue
Normal file
@@ -0,0 +1,565 @@
|
||||
<template>
|
||||
<div class="point-cloud-map">
|
||||
<div ref="canvasContainer" class="canvas-container"></div>
|
||||
<!-- 加载状态指示器 -->
|
||||
<div v-if="isLoading" class="loading-indicator">
|
||||
<i class="fa fa-circle-o-notch fa-spin"></i> 加载中...
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import * as THREE from 'three'
|
||||
import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls'
|
||||
import { mapGetters } from 'vuex'
|
||||
// import { points } from '../../config/point.js'
|
||||
// import { points1 } from '../../config/point1.js'
|
||||
export default {
|
||||
/* eslint-disable */
|
||||
data () {
|
||||
return {
|
||||
// Three.js核心对象
|
||||
scene: null,
|
||||
camera: null,
|
||||
renderer: null,
|
||||
controls: null,
|
||||
// 点云相关
|
||||
pointCloudGeometry: null,
|
||||
pointCloudMaterial: null,
|
||||
pointCloudMesh: null,
|
||||
allPoints: [], // 存储所有累加的点云数据
|
||||
pointCount: 0,
|
||||
pointScale: 1.0,
|
||||
// 小车相关
|
||||
vehicleImage: require('../../images/new/agv.png'),
|
||||
carMesh: null,
|
||||
carTexture: null,
|
||||
// WebSocket相关
|
||||
socket: null,
|
||||
isLoading: true,
|
||||
reconnectTimer: null, // 重连计时器
|
||||
// 动画与性能
|
||||
viewSize: 20,
|
||||
animationId: null,
|
||||
lastUpdateTime: 0,
|
||||
updateInterval: 800, // 限制更新频率,毫秒
|
||||
// 点云边界:记录所有点的最大/最小x、y
|
||||
pointBounds: {
|
||||
minX: Infinity, // 初始设为无穷大,方便后续比较
|
||||
maxX: -Infinity,
|
||||
minY: Infinity,
|
||||
maxY: -Infinity
|
||||
},
|
||||
boundMargin: 0.1, // 边界边距(避免点贴屏幕边缘,值为10%的范围)
|
||||
defaultViewSize: 20, // 无点云时的默认可视范围
|
||||
// 标记组件是否已销毁
|
||||
isDestroyed: false
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
...mapGetters(['serverUrl', , 'userRole', 'carPosition']),
|
||||
},
|
||||
watch: {
|
||||
carPosition: {
|
||||
handler(newVal) {
|
||||
this.updateCarPosition(newVal)
|
||||
},
|
||||
deep: true
|
||||
}
|
||||
},
|
||||
mounted () {
|
||||
// 初始化Three.js场景
|
||||
this.initThreeJs();
|
||||
// 初始化控制器(拖动和缩放)
|
||||
this.initControls();
|
||||
// 初始化点云
|
||||
this.initPointCloud();
|
||||
// 初始化小车模型
|
||||
this.initCar();
|
||||
// 启动动画循环
|
||||
this.startAnimationLoop();
|
||||
// 监听窗口大小变化
|
||||
window.addEventListener('resize', this.handleResize);
|
||||
// 初始加载完成
|
||||
setTimeout(() => {
|
||||
this.isLoading = false;
|
||||
}, 1000);
|
||||
},
|
||||
beforeDestroy () {
|
||||
// 先标记组件已销毁,阻止新消息处理
|
||||
this.isDestroyed = true;
|
||||
// 1. 清理WebSocket(先移除事件监听,再关闭连接)
|
||||
this.closeWebSocket()
|
||||
// 2. 停止动画循环并清理
|
||||
if (this.animationId) {
|
||||
cancelAnimationFrame(this.animationId);
|
||||
this.animationId = null;
|
||||
}
|
||||
// 3. 清理OrbitControls(关键!控制器有内部事件监听)
|
||||
if (this.controls) {
|
||||
this.controls.dispose(); // 控制器自带的清理方法,移除事件监听
|
||||
this.controls = null;
|
||||
}
|
||||
// 4. 清理Three.js场景对象(先从场景移除,再释放资源)
|
||||
if (this.scene) {
|
||||
// 移除点云网格
|
||||
if (this.pointCloudMesh) {
|
||||
this.scene.remove(this.pointCloudMesh);
|
||||
}
|
||||
// 移除小车模型
|
||||
if (this.carMesh) {
|
||||
this.scene.remove(this.carMesh);
|
||||
}
|
||||
// 清空场景(彻底释放所有子对象引用)
|
||||
this.scene.clear();
|
||||
this.scene = null;
|
||||
}
|
||||
// 5. 释放Three.js渲染器及DOM
|
||||
if (this.renderer) {
|
||||
// 从DOM中移除canvas元素(避免残留DOM节点)
|
||||
const container = this.$refs.canvasContainer;
|
||||
if (container && this.renderer.domElement) {
|
||||
container.removeChild(this.renderer.domElement);
|
||||
}
|
||||
// 释放渲染器资源
|
||||
this.renderer.dispose();
|
||||
this.renderer.forceContextLoss(); // 强制释放WebGL上下文(关键!)
|
||||
this.renderer = null;
|
||||
}
|
||||
// 6. 释放几何体和材质(确保先移除引用)
|
||||
if (this.pointCloudGeometry) {
|
||||
this.pointCloudGeometry.dispose();
|
||||
this.pointCloudGeometry = null;
|
||||
}
|
||||
if (this.pointCloudMaterial) {
|
||||
// 先清除材质的纹理引用(避免纹理无法释放)
|
||||
this.pointCloudMaterial.map = null;
|
||||
this.pointCloudMaterial.dispose();
|
||||
this.pointCloudMaterial = null;
|
||||
}
|
||||
// 7. 释放小车纹理(确保材质不再引用)
|
||||
if (this.carTexture) {
|
||||
this.carTexture.dispose();
|
||||
this.carTexture = null;
|
||||
}
|
||||
// 8. 移除窗口大小变化监听(避免重复监听)
|
||||
window.removeEventListener('resize', this.handleResize);
|
||||
// 9. 清空其他引用(帮助GC回收)
|
||||
this.camera = null;
|
||||
this.carMesh = null;
|
||||
this.pointCloudMesh = null;
|
||||
},
|
||||
methods: {
|
||||
/**
|
||||
* 初始化Three.js场景、相机和渲染器
|
||||
*/
|
||||
initThreeJs() {
|
||||
const container = this.$refs.canvasContainer;
|
||||
const aspect = container.clientWidth / container.clientHeight
|
||||
|
||||
// 创建场景
|
||||
this.scene = new THREE.Scene();
|
||||
|
||||
// 创建正交相机(适合2D场景)
|
||||
this.camera = new THREE.OrthographicCamera(
|
||||
-this.viewSize * aspect / 2, // left
|
||||
this.viewSize * aspect / 2, // right
|
||||
this.viewSize / 2, // top
|
||||
-this.viewSize / 2, // bottom
|
||||
0.1, // near
|
||||
1000 // far
|
||||
);
|
||||
this.camera.position.z = 100; // 保持在Z轴上方,不影响2D视图
|
||||
|
||||
// 创建渲染器 - 启用alpha通道实现透明背景
|
||||
this.renderer = new THREE.WebGLRenderer({
|
||||
antialias: true,
|
||||
alpha: true, // 关键:启用alpha通道
|
||||
transparent: true // 关键:允许透明
|
||||
});
|
||||
this.renderer.setSize(container.clientWidth, container.clientHeight);
|
||||
this.renderer.setPixelRatio(window.devicePixelRatio);
|
||||
// 可选:设置clearAlpha确保完全透明
|
||||
this.renderer.setClearAlpha(0);
|
||||
container.appendChild(this.renderer.domElement);
|
||||
},
|
||||
|
||||
/**
|
||||
* 初始化控制器,支持拖动和缩放
|
||||
*/
|
||||
initControls() {
|
||||
this.controls = new OrbitControls(this.camera, this.renderer.domElement);
|
||||
this.controls.enableRotate = false; // 禁用旋转,保持2D视图
|
||||
this.controls.enableZoom = true; // 启用缩放
|
||||
this.controls.enablePan = true; // 启用平移(拖动)
|
||||
this.controls.screenSpacePanning = true; // 2D平移模式
|
||||
this.controls.touchZoomSpeed = 0.5;
|
||||
this.controls.panSpeed = 0.5;
|
||||
|
||||
this.controls.touches = {
|
||||
ONE: THREE.TOUCH.PAN, // 单指拖动为平移
|
||||
TWO: THREE.TOUCH.DOLLY_PAN // 双指拖动为缩放和平移
|
||||
};
|
||||
|
||||
// 添加错误处理逻辑
|
||||
this.controls.addEventListener('error', (event) => {
|
||||
console.error('OrbitControls error:', event);
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* 初始化点云
|
||||
*/
|
||||
initPointCloud() {
|
||||
// 使用BufferGeometry提高大量点的渲染性能
|
||||
this.pointCloudGeometry = new THREE.BufferGeometry();
|
||||
|
||||
// 点材质设置
|
||||
this.pointCloudMaterial = new THREE.PointsMaterial({
|
||||
color: 0xFFFFFF,
|
||||
size: 1, // 初始值会被动态覆盖
|
||||
sizeAttenuation: true, // 关键!
|
||||
transparent: true,
|
||||
opacity: 1
|
||||
});
|
||||
|
||||
// 创建点云网格
|
||||
this.pointCloudMesh = new THREE.Points(
|
||||
this.pointCloudGeometry,
|
||||
this.pointCloudMaterial
|
||||
);
|
||||
this.scene.add(this.pointCloudMesh);
|
||||
// 初始化历史数据为空
|
||||
this.allPoints = [];
|
||||
},
|
||||
|
||||
/**
|
||||
* 初始化小车模型
|
||||
*/
|
||||
initCar() {
|
||||
// 加载小车图片作为精灵
|
||||
const loader = new THREE.TextureLoader();
|
||||
this.carTexture = loader.load(this.vehicleImage, (texture) => {
|
||||
const material = new THREE.SpriteMaterial({
|
||||
map: texture,
|
||||
transparent: true, // 保留图片透明区域
|
||||
opacity: 1,
|
||||
depthWrite: false // 避免被点云遮挡(可选,根据层级需求调整)
|
||||
});
|
||||
|
||||
this.carMesh = new THREE.Sprite(material);
|
||||
this.scene.add(this.carMesh);
|
||||
|
||||
// 初始计算小车尺寸
|
||||
this.calculateCarSize();
|
||||
|
||||
// 初始位置角度更新
|
||||
if (this.carPosition) {
|
||||
this.updateCarPosition(this.carPosition);
|
||||
}
|
||||
}, undefined, (error) => {
|
||||
console.error('小车图片加载失败:', error);
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* 单独的小车尺寸计算方法,确保窗口缩放时可复用
|
||||
*/
|
||||
calculateCarSize() {
|
||||
if (!this.carMesh) return;
|
||||
|
||||
const container = this.$refs.canvasContainer;
|
||||
const canvasWidth = container.clientWidth;
|
||||
const canvasHeight = container.clientHeight;
|
||||
const aspect = canvasWidth / canvasHeight;
|
||||
|
||||
// 核心计算:将50px转换为Three.js世界坐标
|
||||
// 公式:(目标像素尺寸 / 画布尺寸) * 相机可视范围 * 校正系数
|
||||
const spriteSizeX = (50 / canvasWidth) * this.viewSize * aspect;
|
||||
const spriteSizeY = (50 / canvasHeight) * this.viewSize;
|
||||
|
||||
this.carMesh.scale.set(spriteSizeX, spriteSizeY, 1);
|
||||
},
|
||||
|
||||
/**
|
||||
* 更新小车位置和角度
|
||||
*/
|
||||
updateCarPosition(position) {
|
||||
if (this.carMesh && position) {
|
||||
this.carMesh.position.x = position.x * this.pointScale;
|
||||
this.carMesh.position.y = position.y * this.pointScale;
|
||||
this.carMesh.position.z = 1;
|
||||
// 转换角度为弧度并调整方向(Three.js使用弧度)
|
||||
this.carMesh.rotation.z = position.angle;
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* 初始化WebSocket连接
|
||||
*/
|
||||
initWebSocket() {
|
||||
if (this.reconnectTimer) clearTimeout(this.reconnectTimer)
|
||||
const wsHost = this.serverUrl.replace(/^https?:\/\//, '')
|
||||
this.socket = new WebSocket(`ws://${wsHost}/webSocket/PointCloudData/${this.userRole}`);
|
||||
|
||||
this.socket.onopen = () => {
|
||||
console.log('WebSocket连接已建立');
|
||||
this.isLoading = false;
|
||||
};
|
||||
|
||||
this.socket.onmessage = (event) => {
|
||||
// 组件已销毁则直接返回,不处理消息
|
||||
if (this.isDestroyed) return
|
||||
try {
|
||||
// 限制更新频率,优化性能
|
||||
const now = Date.now();
|
||||
if (now - this.lastUpdateTime < this.updateInterval) {
|
||||
return;
|
||||
}
|
||||
this.lastUpdateTime = now;
|
||||
if (this.reconnectTimer) clearTimeout(this.reconnectTimer)
|
||||
const pointData = JSON.parse(event.data);
|
||||
this.updatePointCloud(pointData.data);
|
||||
} catch (error) {
|
||||
console.error('解析点云数据失败:', error);
|
||||
}
|
||||
};
|
||||
|
||||
this.socket.onclose = (event) => {
|
||||
console.log('WebSocket连接已关闭,代码:', event.code);
|
||||
this.isLoading = true;
|
||||
// 自动重连
|
||||
this.reconnectTimer = setTimeout(() => this.initWebSocket(), 3000);
|
||||
};
|
||||
|
||||
this.socket.onerror = (error) => {
|
||||
if (this.reconnectTimer) clearTimeout(this.reconnectTimer)
|
||||
console.error('WebSocket错误:', error);
|
||||
this.isLoading = true;
|
||||
};
|
||||
},
|
||||
|
||||
closeWebSocket () {
|
||||
if (this.reconnectTimer) clearTimeout(this.reconnectTimer)
|
||||
if (this.socket) {
|
||||
this.socket.onopen = null;
|
||||
this.socket.onmessage = null;
|
||||
this.socket.onclose = null;
|
||||
this.socket.onerror = null;
|
||||
this.socket.close(1000, '组件销毁');
|
||||
this.socket = null;
|
||||
}
|
||||
this.allPoints = []
|
||||
},
|
||||
|
||||
init () {
|
||||
// 初始化WebSocket连接
|
||||
this.initWebSocket();
|
||||
// const pointData = points.data
|
||||
// this.updatePointCloud(pointData);
|
||||
// setTimeout(() => {
|
||||
// const arr = points1.data
|
||||
// this.updatePointCloud(arr);
|
||||
// }, 5000)
|
||||
},
|
||||
|
||||
/**
|
||||
* 更新点云数据,优化大量点的渲染性能
|
||||
*/
|
||||
updatePointCloud(points) {
|
||||
if (!Array.isArray(points) || points.length === 0) return;
|
||||
|
||||
// 用于跟踪已存在的点,使用"x,y"作为唯一标识
|
||||
const existingPoints = {};
|
||||
// 先将已有点添加到跟踪对象
|
||||
this.allPoints.forEach(point => {
|
||||
const key = `${point.x},${point.y}`;
|
||||
existingPoints[key] = true;
|
||||
});
|
||||
|
||||
// 过滤新点中的重复点
|
||||
const newUniquePoints = points.filter(point => {
|
||||
const key = `${point.x},${point.y}`;
|
||||
if (!existingPoints[key]) {
|
||||
existingPoints[key] = true;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
});
|
||||
|
||||
this.allPoints = [...this.allPoints, ...newUniquePoints];
|
||||
this.pointCount = this.allPoints.length; // 更新总点数
|
||||
|
||||
// 重新计算所有点云的边界
|
||||
this.pointBounds = { minX: Infinity, maxX: -Infinity, minY: Infinity, maxY: -Infinity };
|
||||
this.allPoints.forEach(point => {
|
||||
const scaledX = (point.x || 0) * this.pointScale; // 缩放后的实际坐标
|
||||
const scaledY = (point.y || 0) * this.pointScale;
|
||||
// 更新最大/最小值
|
||||
this.pointBounds.minX = Math.min(this.pointBounds.minX, scaledX);
|
||||
this.pointBounds.maxX = Math.max(this.pointBounds.maxX, scaledX);
|
||||
this.pointBounds.minY = Math.min(this.pointBounds.minY, scaledY);
|
||||
this.pointBounds.maxY = Math.max(this.pointBounds.maxY, scaledY);
|
||||
});
|
||||
this.adjustCameraByBounds();
|
||||
|
||||
// 动态扩展缓冲区
|
||||
const positionAttribute = this.pointCloudGeometry.getAttribute('position');
|
||||
let positions;
|
||||
|
||||
if (!positionAttribute) {
|
||||
// 首次初始化:创建缓冲区
|
||||
positions = new Float32Array(this.pointCount * 3);
|
||||
this.pointCloudGeometry.setAttribute(
|
||||
'position',
|
||||
new THREE.BufferAttribute(positions, 3)
|
||||
);
|
||||
} else {
|
||||
// 后续更新:扩展现有缓冲区(避免重建)
|
||||
positions = positionAttribute.array;
|
||||
const newLength = this.pointCount * 3;
|
||||
if (newLength > positions.length) {
|
||||
// 扩展时多预留20%空间,减少频繁扩展
|
||||
const newPositions = new Float32Array(Math.ceil(newLength * 1.2));
|
||||
newPositions.set(positions); // 复制原有数据
|
||||
positions = newPositions;
|
||||
this.pointCloudGeometry.setAttribute(
|
||||
'position',
|
||||
new THREE.BufferAttribute(positions, 3)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// 填充新数据(仅更新新增部分,减少重复计算)
|
||||
const startIndex = (this.pointCount - newUniquePoints.length) * 3; // 从新增点开始
|
||||
for (let i = 0; i < newUniquePoints.length; i++) {
|
||||
const point = newUniquePoints[i];
|
||||
const index = startIndex + i * 3;
|
||||
positions[index] = (point.x || 0) * this.pointScale;
|
||||
positions[index + 1] = (point.y || 0) * this.pointScale;
|
||||
positions[index + 2] = 0;
|
||||
}
|
||||
|
||||
// 标记更新
|
||||
this.pointCloudGeometry.getAttribute('position').needsUpdate = true;
|
||||
},
|
||||
|
||||
/**
|
||||
* 根据边界动态调整相机可视范围
|
||||
*/
|
||||
adjustCameraByBounds() {
|
||||
const container = this.$refs.canvasContainer;
|
||||
const aspect = container.clientWidth / container.clientHeight; // 画布宽高比
|
||||
|
||||
// 1. 计算点云的实际范围(宽和高)
|
||||
const pointRangeX = this.pointBounds.maxX - this.pointBounds.minX;
|
||||
const pointRangeY = this.pointBounds.maxY - this.pointBounds.minY;
|
||||
|
||||
// 2. 处理特殊情况:无点云或单点时,用默认viewSize
|
||||
if (this.allPoints.length === 0 || (pointRangeX === 0 && pointRangeY === 0)) {
|
||||
this.viewSize = this.defaultViewSize;
|
||||
} else {
|
||||
// 计算需要的可视范围:覆盖点云范围 + 10%边距
|
||||
const requiredViewSizeX = pointRangeX * (1 + this.boundMargin);
|
||||
const requiredViewSizeY = pointRangeY * (1 + this.boundMargin);
|
||||
// 取X/Y方向中较大的作为viewSize,确保宽高比适配后能覆盖所有点
|
||||
this.viewSize = Math.max(requiredViewSizeX / aspect, requiredViewSizeY);
|
||||
}
|
||||
|
||||
// 3. 更新正交相机的可视范围(left/right/top/bottom)
|
||||
this.camera.left = -this.viewSize * aspect / 2;
|
||||
this.camera.right = this.viewSize * aspect / 2;
|
||||
this.camera.top = this.viewSize / 2;
|
||||
this.camera.bottom = -this.viewSize / 2;
|
||||
this.camera.updateProjectionMatrix(); // 必须更新投影矩阵,否则不生效
|
||||
|
||||
// 4. 让相机焦点对准点云中心(可选,确保点云在屏幕中间)
|
||||
const centerX = (this.pointBounds.minX + this.pointBounds.maxX) / 2;
|
||||
const centerY = (this.pointBounds.minY + this.pointBounds.maxY) / 2;
|
||||
this.controls.target.set(centerX, centerY, 0); // 控制器目标对准点云中心
|
||||
this.camera.position.set(centerX, centerY, 100); // 相机位置也对准中心(Z轴不变)
|
||||
|
||||
// 5. 同步更新小车尺寸(确保小车仍为50px,因viewSize变化了)
|
||||
if (this.carMesh) {
|
||||
const width = container.clientWidth;
|
||||
const height = container.clientHeight;
|
||||
const spriteSizeX = (50 / width) * this.viewSize * aspect;
|
||||
const spriteSizeY = (50 / height) * this.viewSize;
|
||||
this.carMesh.scale.set(spriteSizeX, spriteSizeY, 1);
|
||||
}
|
||||
|
||||
// 相机参数更新后,同步更新小车尺寸
|
||||
this.calculateCarSize();
|
||||
},
|
||||
|
||||
/**
|
||||
* 处理窗口大小变化
|
||||
*/
|
||||
handleResize() {
|
||||
const container = this.$refs.canvasContainer;
|
||||
const width = container.clientWidth;
|
||||
const height = container.clientHeight;
|
||||
|
||||
// 原有渲染器尺寸更新
|
||||
this.renderer.setSize(width, height);
|
||||
|
||||
// 基于当前点云边界重新适配相机(关键)
|
||||
this.adjustCameraByBounds();
|
||||
|
||||
// 窗口缩放时重新计算小车尺寸
|
||||
this.calculateCarSize();
|
||||
},
|
||||
|
||||
/**
|
||||
* 启动动画循环
|
||||
*/
|
||||
startAnimationLoop() {
|
||||
const animate = () => {
|
||||
this.animationId = requestAnimationFrame(animate);
|
||||
|
||||
// 更新控制器(用于阻尼效果)
|
||||
if (this.controls) {
|
||||
this.controls.update();
|
||||
}
|
||||
|
||||
// 渲染场景
|
||||
this.renderer.render(this.scene, this.camera);
|
||||
};
|
||||
|
||||
animate();
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="stylus" scoped>
|
||||
.point-cloud-map
|
||||
position relative
|
||||
width 100%
|
||||
height 100%
|
||||
background-color rgba(4, 33, 58, 70%)
|
||||
box-shadow inset 1px 1px 7px 2px #4d9bcd
|
||||
overflow hidden
|
||||
.canvas-container {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
position: relative;
|
||||
z-index: 0;
|
||||
}
|
||||
.loading-indicator {
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
background-color: rgba(255, 255, 255, 0.8);
|
||||
padding: .01rem .02rem;
|
||||
border-radius: 4px;
|
||||
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1);
|
||||
font-size: .12rem;
|
||||
color: #333;
|
||||
z-index: 100;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
</style>
|
||||
../../config/point copy.js
|
||||
@@ -268,7 +268,7 @@
|
||||
}
|
||||
|
||||
.driver-popover.driverjs-theme .driver-popover-close-btn:hover {
|
||||
color: #000;
|
||||
opacity: .5;
|
||||
}
|
||||
.driver-popover-footer button:hover, .driver-popover-footer button:focus {
|
||||
background-color: #000 !important;
|
||||
|
||||
Reference in New Issue
Block a user