|
@@ -66,6 +66,8 @@
|
|
|
<script setup lang="ts">
|
|
<script setup lang="ts">
|
|
|
import { ref, onMounted, onUnmounted, reactive } from 'vue';
|
|
import { ref, onMounted, onUnmounted, reactive } from 'vue';
|
|
|
import { Viewer2d, ViewerEvent, CoordinateUtils, THREE } from '@x-viewer/core';
|
|
import { Viewer2d, ViewerEvent, CoordinateUtils, THREE } from '@x-viewer/core';
|
|
|
|
|
+ import { CSS3DRenderer, CSS3DObject } from 'three/examples/jsm/renderers/CSS3DRenderer.js';
|
|
|
|
|
+ import { CSS2DObject } from 'three/examples/jsm/renderers/CSS2DRenderer.js';
|
|
|
import { useAppStoreWithOut } from '/@/store/modules/app';
|
|
import { useAppStoreWithOut } from '/@/store/modules/app';
|
|
|
import dayjs from 'dayjs';
|
|
import dayjs from 'dayjs';
|
|
|
|
|
|
|
@@ -83,6 +85,13 @@
|
|
|
|
|
|
|
|
const containerId = 'dwg-viewer-canvas';
|
|
const containerId = 'dwg-viewer-canvas';
|
|
|
const dwgSrc = '/dwg/2026年2月总平面布置图_无高程.dwg';
|
|
const dwgSrc = '/dwg/2026年2月总平面布置图_无高程.dwg';
|
|
|
|
|
+ const fontFiles = [
|
|
|
|
|
+ '/dwg/simplex.shx',
|
|
|
|
|
+ '/dwg/hztxt.shx',
|
|
|
|
|
+ '/dwg/arial.ttf',
|
|
|
|
|
+ '/dwg/Microsoft_YaHei.ttf',
|
|
|
|
|
+ '/dwg/Microsoft_YaHei_Regular.typeface.json',
|
|
|
|
|
+ ];
|
|
|
let markerIdCounter = 0;
|
|
let markerIdCounter = 0;
|
|
|
|
|
|
|
|
// ==================== 表格 ====================
|
|
// ==================== 表格 ====================
|
|
@@ -141,8 +150,20 @@
|
|
|
const currentCoord = reactive({ x: 0, y: 0, z: 0 });
|
|
const currentCoord = reactive({ x: 0, y: 0, z: 0 });
|
|
|
|
|
|
|
|
// 存储 marker:markerId → { deviceId, worldPos, domEl }
|
|
// 存储 marker:markerId → { deviceId, worldPos, domEl }
|
|
|
- const markersMap = new Map<string, { deviceId: string; worldPos: { x: number; y: number }; mesh: THREE.Mesh }>();
|
|
|
|
|
|
|
+ const markersMap = new Map<
|
|
|
|
|
+ string,
|
|
|
|
|
+ {
|
|
|
|
|
+ deviceId: string;
|
|
|
|
|
+ worldPos: { x: number; y: number };
|
|
|
|
|
+ mesh: THREE.Mesh;
|
|
|
|
|
+ iconSprite: THREE.Sprite;
|
|
|
|
|
+ cssLabel: CSS2DObject;
|
|
|
|
|
+ }
|
|
|
|
|
+ >();
|
|
|
const allMeshes: THREE.Mesh[] = [];
|
|
const allMeshes: THREE.Mesh[] = [];
|
|
|
|
|
+ const allIconSprites: THREE.Sprite[] = [];
|
|
|
|
|
+ const allCssLabels: CSS2DObject[] = [];
|
|
|
|
|
+ const textureLoader = new THREE.TextureLoader();
|
|
|
|
|
|
|
|
const monitoringData = ref<ReturnType<typeof getMockMonitoring> | null>(null);
|
|
const monitoringData = ref<ReturnType<typeof getMockMonitoring> | null>(null);
|
|
|
const modalVisible = ref(false);
|
|
const modalVisible = ref(false);
|
|
@@ -230,7 +251,38 @@
|
|
|
viewer.scene.add(mesh);
|
|
viewer.scene.add(mesh);
|
|
|
allMeshes.push(mesh);
|
|
allMeshes.push(mesh);
|
|
|
|
|
|
|
|
- markersMap.set(markerId, { deviceId: device.id, worldPos: { x: location.x, y: location.y }, mesh });
|
|
|
|
|
|
|
+ // 精灵图标(safetymonitor3D.png)
|
|
|
|
|
+ const tex = textureLoader.load('/texture/safetymonitor3D.png');
|
|
|
|
|
+ const spriteMat = new THREE.SpriteMaterial({ map: tex, depthTest: false, depthWrite: false, transparent: true });
|
|
|
|
|
+ const icon = new THREE.Sprite(spriteMat);
|
|
|
|
|
+ icon.position.set(location.x, location.y, 0);
|
|
|
|
|
+ const iconSize = frustumH * 0.015;
|
|
|
|
|
+ icon.scale.set(iconSize * 200, iconSize * 200, 1);
|
|
|
|
|
+ icon.renderOrder = 1000;
|
|
|
|
|
+ icon.matrixAutoUpdate = true;
|
|
|
|
|
+ icon.updateMatrixWorld();
|
|
|
|
|
+ viewer.scene.add(icon);
|
|
|
|
|
+ allIconSprites.push(icon);
|
|
|
|
|
+
|
|
|
|
|
+ // CSS2DObject 风速标签(图标上方)
|
|
|
|
|
+ const labelDiv = document.createElement('div');
|
|
|
|
|
+ labelDiv.textContent = `${+(Math.random() * 3 + 1.5).toFixed(1)} m/s`;
|
|
|
|
|
+ labelDiv.style.cssText =
|
|
|
|
|
+ 'color:#00ffcc;font-size:22px;font-weight:bold;background:rgba(0,0,0,0.7);padding:2px 6px;border-radius:3px;white-space:nowrap;';
|
|
|
|
|
+ const cssLabel = new CSS2DObject(labelDiv);
|
|
|
|
|
+ cssLabel.position.set(location.x, location.y + 150, 0.1);
|
|
|
|
|
+ cssLabel.matrixAutoUpdate = true;
|
|
|
|
|
+ cssLabel.updateMatrixWorld();
|
|
|
|
|
+ viewer.scene.add(cssLabel);
|
|
|
|
|
+ allCssLabels.push(cssLabel);
|
|
|
|
|
+
|
|
|
|
|
+ markersMap.set(markerId, {
|
|
|
|
|
+ deviceId: device.id,
|
|
|
|
|
+ worldPos: { x: location.x, y: location.y },
|
|
|
|
|
+ mesh,
|
|
|
|
|
+ iconSprite: icon,
|
|
|
|
|
+ cssLabel,
|
|
|
|
|
+ });
|
|
|
device.markerId = markerId;
|
|
device.markerId = markerId;
|
|
|
console.log(`[布点] 设备=${device.name} X=${location.x.toFixed(2)} Y=${location.y.toFixed(2)} r=${radius.toFixed(2)}`);
|
|
console.log(`[布点] 设备=${device.name} X=${location.x.toFixed(2)} Y=${location.y.toFixed(2)} r=${radius.toFixed(2)}`);
|
|
|
exitPlacingMode();
|
|
exitPlacingMode();
|
|
@@ -245,8 +297,16 @@
|
|
|
viewer.scene.remove(m.mesh);
|
|
viewer.scene.remove(m.mesh);
|
|
|
m.mesh.geometry.dispose();
|
|
m.mesh.geometry.dispose();
|
|
|
(m.mesh.material as THREE.Material).dispose();
|
|
(m.mesh.material as THREE.Material).dispose();
|
|
|
|
|
+ viewer.scene.remove(m.iconSprite);
|
|
|
|
|
+ (m.iconSprite.material as THREE.SpriteMaterial).map?.dispose();
|
|
|
|
|
+ m.iconSprite.material.dispose();
|
|
|
|
|
+ const ci = allCssLabels.indexOf(m.cssLabel);
|
|
|
|
|
+ if (ci > -1) allCssLabels.splice(ci, 1);
|
|
|
|
|
+
|
|
|
const idx = allMeshes.indexOf(m.mesh);
|
|
const idx = allMeshes.indexOf(m.mesh);
|
|
|
if (idx > -1) allMeshes.splice(idx, 1);
|
|
if (idx > -1) allMeshes.splice(idx, 1);
|
|
|
|
|
+ const si = allIconSprites.indexOf(m.iconSprite);
|
|
|
|
|
+ if (si > -1) allIconSprites.splice(si, 1);
|
|
|
markersMap.delete(markerId);
|
|
markersMap.delete(markerId);
|
|
|
}
|
|
}
|
|
|
|
|
|
|
@@ -312,13 +372,15 @@
|
|
|
enableProgressBar: true,
|
|
enableProgressBar: true,
|
|
|
enableLayoutBar: true,
|
|
enableLayoutBar: true,
|
|
|
});
|
|
});
|
|
|
-
|
|
|
|
|
|
|
+ await viewer.setFont(fontFiles);
|
|
|
await viewer.loadModel({ modelId: 'main-plan', name: '总平面布置图', src: dwgSrc }, (event: ProgressEvent) => {
|
|
await viewer.loadModel({ modelId: 'main-plan', name: '总平面布置图', src: dwgSrc }, (event: ProgressEvent) => {
|
|
|
if (event.total > 0) console.log(`加载: ${Math.round((event.loaded / event.total) * 100)}%`);
|
|
if (event.total > 0) console.log(`加载: ${Math.round((event.loaded / event.total) * 100)}%`);
|
|
|
});
|
|
});
|
|
|
|
|
|
|
|
viewer.goToHomeView();
|
|
viewer.goToHomeView();
|
|
|
viewerReady.value = true;
|
|
viewerReady.value = true;
|
|
|
|
|
+ // initCss();
|
|
|
|
|
+
|
|
|
console.log('[DWG] Viewer2d 就绪');
|
|
console.log('[DWG] Viewer2d 就绪');
|
|
|
|
|
|
|
|
// === 参照官方示例: ViewerEvent.MouseClick ===
|
|
// === 参照官方示例: ViewerEvent.MouseClick ===
|
|
@@ -354,6 +416,10 @@
|
|
|
if (location) placeMarker(placingDevice.value, location);
|
|
if (location) placeMarker(placingDevice.value, location);
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
|
|
+
|
|
|
|
|
+ viewer.renderer.render(viewer.scene, viewer.camera);
|
|
|
|
|
+ viewer.getCssRender().render(viewer.scene, viewer.camera);
|
|
|
|
|
+ css3DRenderer?.render(viewer.scene, viewer.camera);
|
|
|
});
|
|
});
|
|
|
|
|
|
|
|
// === 鼠标移动:实时更新坐标 + 浮动圆点跟随 ===
|
|
// === 鼠标移动:实时更新坐标 + 浮动圆点跟随 ===
|
|
@@ -374,6 +440,8 @@
|
|
|
currentCoord.y = wp.y;
|
|
currentCoord.y = wp.y;
|
|
|
currentCoord.z = wp.z;
|
|
currentCoord.z = wp.z;
|
|
|
}
|
|
}
|
|
|
|
|
+
|
|
|
|
|
+ css3DRenderer?.render(viewer.scene, viewer.camera);
|
|
|
});
|
|
});
|
|
|
}
|
|
}
|
|
|
|
|
|
|
@@ -399,6 +467,14 @@
|
|
|
(m.material as THREE.Material).dispose();
|
|
(m.material as THREE.Material).dispose();
|
|
|
});
|
|
});
|
|
|
allMeshes.length = 0;
|
|
allMeshes.length = 0;
|
|
|
|
|
+ allIconSprites.forEach((s) => {
|
|
|
|
|
+ viewer!.scene.remove(s);
|
|
|
|
|
+ (s.material as THREE.SpriteMaterial).map?.dispose();
|
|
|
|
|
+ s.material.dispose();
|
|
|
|
|
+ });
|
|
|
|
|
+ allIconSprites.length = 0;
|
|
|
|
|
+ allCssLabels.forEach((l) => viewer!.scene?.remove(l));
|
|
|
|
|
+ allCssLabels.length = 0;
|
|
|
}
|
|
}
|
|
|
markersMap.clear();
|
|
markersMap.clear();
|
|
|
if (viewer) {
|
|
if (viewer) {
|