This commit is contained in:
lizhenhua 2025-07-23 09:34:16 +08:00
parent 16ed8e8b61
commit f5812a995c
1 changed files with 388 additions and 0 deletions

View File

@ -0,0 +1,388 @@
<template>
<div class="dxf-viewer-root">
<div class="dxf-sidebar">
<div class="sidebar-title">图层列表</div>
<ul class="layer-list">
<li
v-for="(layer, idx) in layerList"
:key="layer.layerName"
:class="{ active: idx === selectedLayerIndex }"
@click="selectLayer(idx)"
>
{{ layer.layerName }} <span class="entity-count">({{ layer.entities.length }})</span>
</li>
</ul>
</div>
<div class="dxf-main">
<div class="entity-list">
<div class="entity-title">图元列表</div>
<ul>
<li
v-for="(entity, i) in selectedLayerEntities"
:key="entity.id"
:class="{ selected: selectedEntityId === entity.id }"
@click="selectEntity(entity.id)"
>
<span class="entity-type">{{ entity.entityType }}</span>
<span class="entity-id">#{{ entity.id }}</span>
</li>
</ul>
</div>
<div class="threejs-container" ref="threeContainer"></div>
</div>
</div>
</template>
<script>
import * as THREE from 'three'
import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls'
export default {
name: 'DxfLayerEntityViewer',
props: {
entities: {
type: Array,
required: true
}
},
data() {
return {
layerList: [],
selectedLayerIndex: 0,
selectedEntityId: null,
scene: null,
camera: null,
renderer: null,
controls: null,
entityMeshMap: {}, // id: mesh
scaleFactor: 1, //
offsetX: 0,
offsetY: 0,
minX: 0,
minY: 0,
maxX: 0,
maxY: 0
}
},
computed: {
selectedLayerEntities() {
if (!this.layerList.length) return []
return this.layerList[this.selectedLayerIndex].entities
}
},
watch: {
entities: {
immediate: true,
handler(val) {
this.prepareLayers(val)
this.$nextTick(() => {
this.initThree()
})
}
},
selectedLayerIndex() {
this.highlightEntities()
},
selectedEntityId() {
this.highlightEntities()
}
},
mounted() {
window.addEventListener('resize', this.onWindowResize)
},
beforeDestroy() {
window.removeEventListener('resize', this.onWindowResize)
if (this.renderer) {
this.renderer.dispose()
}
},
methods: {
prepareLayers(entities) {
//
const map = {}
entities.forEach(e => {
if (!map[e.layerName]) map[e.layerName] = []
map[e.layerName].push(e)
})
this.layerList = Object.keys(map).map(layerName => ({
layerName,
entities: map[layerName]
}))
this.selectedLayerIndex = 0
this.selectedEntityId = null
},
selectLayer(idx) {
this.selectedLayerIndex = idx
this.selectedEntityId = null
},
selectEntity(id) {
this.selectedEntityId = id
},
initThree() {
//
if (this.renderer && this.$refs.threeContainer) {
this.$refs.threeContainer.innerHTML = ''
}
this.scene = new THREE.Scene()
this.camera = new THREE.PerspectiveCamera(
60,
this.$refs.threeContainer.clientWidth / this.$refs.threeContainer.clientHeight,
0.1,
100000
)
this.renderer = new THREE.WebGLRenderer({ antialias: true, alpha: true })
this.renderer.setSize(
this.$refs.threeContainer.clientWidth,
this.$refs.threeContainer.clientHeight
)
this.renderer.setClearColor(0xf0f2f5, 1)
this.$refs.threeContainer.appendChild(this.renderer.domElement)
this.controls = new OrbitControls(this.camera, this.renderer.domElement)
this.controls.enableDamping = true
this.controls.dampingFactor = 0.05
this.controls.screenSpacePanning = true
//
this.scene.add(new THREE.AmbientLight(0xffffff, 0.6))
const dirLight = new THREE.DirectionalLight(0xffffff, 0.8)
dirLight.position.set(100, 100, 100)
this.scene.add(dirLight)
//
const gridHelper = new THREE.GridHelper(100, 10, 0x444444, 0x888888)
this.scene.add(gridHelper)
//
const axesHelper = new THREE.AxesHelper(50)
this.scene.add(axesHelper)
//
this.renderAllEntities()
this.animate()
},
renderAllEntities() {
//
let minX = Infinity, minY = Infinity, maxX = -Infinity, maxY = -Infinity
this.entityMeshMap = {}
this.entities.forEach(entity => {
const geometry = entity.geometry ? JSON.parse(entity.geometry) : null
if (!geometry) return
if (entity.entityType === 'LINE') {
if (Array.isArray(geometry.start)) {
minX = Math.min(minX, geometry.start[0], geometry.end[0])
minY = Math.min(minY, geometry.start[1], geometry.end[1])
maxX = Math.max(maxX, geometry.start[0], geometry.end[0])
maxY = Math.max(maxY, geometry.start[1], geometry.end[1])
}
} else if (entity.entityType === 'CIRCLE') {
if (Array.isArray(geometry.center)) {
minX = Math.min(minX, geometry.center[0] - geometry.radius)
minY = Math.min(minY, geometry.center[1] - geometry.radius)
maxX = Math.max(maxX, geometry.center[0] + geometry.radius)
maxY = Math.max(maxY, geometry.center[1] + geometry.radius)
}
}
})
this.minX = minX
this.minY = minY
this.maxX = maxX
this.maxY = maxY
this.offsetX = (minX + maxX) / 2
this.offsetY = (minY + maxY) / 2
//
const containerWidth = this.$refs.threeContainer.clientWidth
const containerHeight = this.$refs.threeContainer.clientHeight
const rangeX = maxX - minX
const rangeY = maxY - minY
const scaleX = (containerWidth * 0.8) / rangeX
const scaleY = (containerHeight * 0.8) / rangeY
this.scaleFactor = Math.min(scaleX, scaleY)
//
this.entities.forEach(entity => {
const geometry = entity.geometry ? JSON.parse(entity.geometry) : null
if (!geometry) return
const color = this.getColor(entity.color)
const material = new THREE.LineBasicMaterial({ color, linewidth: 1 })
let mesh = null
if (entity.entityType === 'LINE') {
const start = this.transformPoint(geometry.start)
const end = this.transformPoint(geometry.end)
const points = [start, end]
const lineGeometry = new THREE.BufferGeometry().setFromPoints(points)
mesh = new THREE.Line(lineGeometry, material)
} else if (entity.entityType === 'CIRCLE') {
const center = this.transformPoint(geometry.center)
const radius = geometry.radius * this.scaleFactor
const circleGeometry = new THREE.CircleGeometry(radius, 64)
mesh = new THREE.LineLoop(circleGeometry, material)
mesh.position.set(center.x, center.y, center.z)
}
if (mesh) {
mesh.userData = { entityId: entity.id, layerName: entity.layerName }
this.scene.add(mesh)
this.entityMeshMap[entity.id] = mesh
}
})
//
this.camera.position.set(0, 0, Math.max(containerWidth, containerHeight))
this.camera.lookAt(0, 0, 0)
if (this.controls) this.controls.update()
this.highlightEntities()
},
transformPoint(point) {
return new THREE.Vector3(
(point[0] - this.offsetX) * this.scaleFactor,
(point[1] - this.offsetY) * this.scaleFactor,
0
)
},
highlightEntities() {
//
Object.values(this.entityMeshMap).forEach(mesh => {
mesh.material.color.set(0xcccccc)
mesh.material.linewidth = 2
})
//
const currentLayer = this.layerList[this.selectedLayerIndex]
if (currentLayer) {
currentLayer.entities.forEach(entity => {
const mesh = this.entityMeshMap[entity.id]
if (mesh) mesh.material.color.set(0x409EFF)
})
}
//
if (this.selectedEntityId) {
const mesh = this.entityMeshMap[this.selectedEntityId]
if (mesh) mesh.material.color.set(0xff0000)
}
},
transformPoint(point) {
return new THREE.Vector3(
(point[0] - this.offsetX) * this.scaleFactor,
(point[1] - this.offsetY) * this.scaleFactor,
0
)
},
getColor(colorIndex) {
const colors = {
1: 0xff0000, 2: 0xffff00, 3: 0x00ff00, 4: 0x00ffff,
5: 0x0000ff, 6: 0xff00ff, 7: 0xffffff, 256: 0x000000
}
return colors[colorIndex] || 0x00aaff
},
animate() {
requestAnimationFrame(this.animate)
if (this.controls) this.controls.update()
if (this.renderer && this.scene && this.camera) {
this.renderer.render(this.scene, this.camera)
}
},
onWindowResize() {
if (this.camera && this.renderer && this.$refs.threeContainer) {
this.camera.aspect = this.$refs.threeContainer.clientWidth / this.$refs.threeContainer.clientHeight
this.camera.updateProjectionMatrix()
this.renderer.setSize(
this.$refs.threeContainer.clientWidth,
this.$refs.threeContainer.clientHeight
)
}
}
}
}
</script>
<style scoped>
.dxf-viewer-root {
display: flex;
width: 100%;
height: 600px;
background: #f0f2f5;
border-radius: 4px;
overflow: hidden;
box-shadow: 0 2px 12px 0 rgba(0,0,0,0.1);
}
.dxf-sidebar {
width: 200px;
background: #fff;
border-right: 1px solid #e4e7ed;
padding: 0 0 0 0;
overflow-y: auto;
}
.sidebar-title {
font-weight: bold;
font-size: 16px;
padding: 16px 0 8px 20px;
border-bottom: 1px solid #e4e7ed;
}
.layer-list {
list-style: none;
margin: 0;
padding: 0;
}
.layer-list li {
padding: 12px 20px;
cursor: pointer;
border-left: 4px solid transparent;
transition: background 0.2s, border-color 0.2s;
}
.layer-list li.active {
background: #f0f7ff;
border-left: 4px solid #409EFF;
color: #409EFF;
}
.entity-count {
color: #909399;
font-size: 12px;
margin-left: 8px;
}
.dxf-main {
flex: 1;
display: flex;
flex-direction: row;
height: 100%;
}
.entity-list {
width: 220px;
background: #f7f9fc;
border-right: 1px solid #e4e7ed;
padding: 0 0 0 0;
overflow-y: auto;
}
.entity-title {
font-weight: bold;
font-size: 15px;
padding: 16px 0 8px 20px;
border-bottom: 1px solid #e4e7ed;
}
.entity-list ul {
list-style: none;
margin: 0;
padding: 0;
}
.entity-list li {
padding: 10px 20px;
cursor: pointer;
border-left: 4px solid transparent;
transition: background 0.2s, border-color 0.2s;
display: flex;
align-items: center;
gap: 8px;
}
.entity-list li.selected {
background: #fff7f7;
border-left: 4px solid #ff4d4f;
color: #ff4d4f;
}
.entity-type {
font-weight: 600;
font-size: 13px;
}
.entity-id {
color: #bbb;
font-size: 12px;
}
.threejs-container {
flex: 1;
height: 100%;
background: #f0f2f5;
position: relative;
}
</style>