<template>
<div
@click="handleItemClick"
:class="{ active: isActive }"
class="flex pr-1 justify-between cursor-pointer text-sm items-center text-gray-600 hover:text-blue-600 hover:bg-gray-50 py-2 rounded"
>
<div class="label">
{{ item.name }}
</div>
<!-- 图层从未被添加,为+号 -->
<div class="action" v-if="!ishasLayer" @click="handleAddClick">
<svg
t="1744775956629"
class="icon h-3 w-3 text-gray-400 cursor-pointer hover:text-gray-600"
viewBox="0 0 1024 1024"
version="1.1"
xmlns="http://www.w3.org/2000/svg"
p-id="2776"
width="96"
height="96"
fill="currentColor"
>
<path
d="M864 415H610V161c0-53.3-43.7-97-97-97s-97 43.7-97 97v254H162c-53.3 0-97 43.6-97 97 0 53.3 43.7 97 97 97h254v254c0 53.3 43.7 97 97 97s97-43.7 97-97V609h254c53.3 0 97-43.7 97-97 0-53.4-43.7-97-97-97z"
p-id="2777"
></path>
</svg>
</div>
<!-- 图层已被添加,为-号 -->
<div class="action" v-else @click="handleDelClick">
<svg
t="1744775956629"
class="icon h-3 w-3 text-gray-400 cursor-pointer hover:text-gray-600"
viewBox="0 0 1024 1024"
version="1.1"
xmlns="http://www.w3.org/2000/svg"
p-id="2776"
width="96"
height="96"
fill="currentColor"
>
<path
d="M864 415H161c-53.3 0-97 43.7-97 97s43.7 97 97 97h703c53.3 0 97-43.7 97-97s-43.7-97-97-97z"
p-id="2777"
></path>
</svg>
</div>
</div>
</template>
<script setup>
import { fromJSON } from 'postcss'
import { defineProps, onMounted, inject, ref, computed } from 'vue'
import { addLayerToViewer, delLayerFromViewer } from '@/libs/layerUtil'
import { getTrueViewer, getViewer } from '@/libs/viewer'
import store from '@/store/index.js'
import * as Cesium from 'cesium'
import { fetchData, fly } from '@/layout/utils/todo.js'
// 计算属性,某id图层是否存在
const ishasLayer = computed(() => {
return store.getters['LayerData/hasLayerData'](props.item.id)
})
// 使用 ref 存储服务器数据
const jsonData = ref(null)
// 计算属性基于已加载的数据
// computed 属性不能直接返回异步函数的结果。fetchData 是一个异步函数,返回的是 Promise,而 computed 属性期望的是同步返回值。
const dataArray = computed(() => {
return jsonData.value?.WMS_Capabilities?.Capability?.Layer?.Layer || []
})
// 在组件挂载时或需要时加载数据
// fetchData 返回 Promise:你的 fetchData 函数是异步的,返回的是 Promise 对象
const loadData = async () => {
jsonData.value = await fetchData()
}
const props = defineProps({
item: {
type: Object,
default: () => {}
}
})
const isActive = computed(() => {
return store.state.LayerData.selectLayer?.id === props.item.id
})
// 注入关闭面板的方法
const closePanel = inject('closeDataAddPanel')
/*
* 处理图层项点击事件
* @param {Object} item - 图层项对象
*/
const handleItemClick = async () => {
store.commit('LayerData/setSelectLayer', props.item)
}
/**
* 处理添加图层点击事件
*/
// const handleAddClick = async () => {
// // 1. 获取Viewer实例
// const viewer = getViewer()
// if (!viewer) {
// console.error('Viewer实例未找到')
// return
// }
// // 2. 检查图层是否已存在
// const layerId = props.item.id
// if (store.getters['LayerData/hasLayerData'](layerId)) {
// // 弹窗提示图层已存在
// alert('图层已存在,请勿重复添加。')
// return
// }
// // 3.PANO图层另行处理
// if (props.item.type === 'PANO' && props.item.subType === 'line') {
// console.log('PANO图层,开始加载线路数据')
// // 获得真实的Viewer实例
// const trueviewer = getTrueViewer()
// if (!trueviewer) {
// console.error('Viewer实例未找到')
// return
// }
// try {
// // 存储数据源引用
// const dataSource1 = ref([])
// // 加载第一个GeoJSON数据源
// dataSource1.value = await Cesium.GeoJsonDataSource.load(props.item.url1, {
// stroke: Cesium.Color.HOTPINK,
// fill: Cesium.Color.PINK.withAlpha(0.3),
// strokeWidth: 3,
// clampToGround: true // 贴地显示
// })
// // dataSource1.name = `${props.item.id}_line1`
// trueviewer.dataSources.add(dataSource1.value)
// // // 加载第二个GeoJSON数据源
// // const dataSource2 = await Cesium.GeoJsonDataSource.load(props.item.url2, {
// // stroke: Cesium.Color.HOTPINK,
// // fill: Cesium.Color.RED.withAlpha(0.3),
// // strokeWidth: 3,
// // clampToGround: true // 贴地显示
// // })
// // dataSource2.name = `${props.item.id}_line2`
// // viewer.dataSources.add(dataSource2)
// // dataSources.value.push(dataSource2)
// // // 添加到状态管理
// // const layerData = {
// // option: {
// // ...props.item,
// // dataSources: dataSources.value // 保存数据源引用
// // },
// // panel_visible: true,
// // id: props.item.id,
// // type: 'system',
// // name: props.item.name || 'PANO线路数据'
// // }
// // store.commit('LayerData/addLayerData', layerData)
// // 使用更可靠的flyTo方法
// trueviewer.flyTo(dataSource1.value, {
// duration: 3,
// offset: new Cesium.HeadingPitchRange(0, -0.5, 0)
// })
// // 关闭面板
// closePanel?.()
// } catch (error) {
// console.error('加载PANO线路数据失败:', error)
// alert(`加载线路数据失败: ${error.message}`)
// // 清理已加载的数据源(如果有)
// dataSources.value.forEach((ds) => {
// if (viewer.dataSources.contains(ds)) {
// viewer.dataSources.remove(ds)
// }
// })
// }
// } else {
// console.log('非PANO图层,开始添加图层')
// // 非PANO图层处理
// try {
// // 3. 准备图层数据
// const layerData = {
// option: props.item,
// panel_visible: true,
// id: layerId,
// type: 'system'
// }
// console.log('正在添加图层,ID:', layerId)
// // 4. 添加到地图和状态管理
// const layerInstance = addLayerToViewer(props.item, viewer)
// store.commit('LayerData/addLayerData', layerData)
// // 5. 飞行到图层范围
// // 确保数据已加载
// if (!jsonData.value) {
// await loadData() // 等待数据加载完成
// }
// fly(dataArray.value, viewer, layerInstance, props.item)
// // 关闭面板
// closePanel?.()
// } catch (error) {
// console.error('添加图层失败:', error)
// alert(`操作失败: ${error.message}`)
// }
// }
// }
const handleAddClick = async () => {
const viewer = getViewer()
if (!viewer) {
console.error('Viewer实例未找到')
return
}
const layerId = props.item.id
if (store.getters['LayerData/hasLayerData'](layerId)) {
alert('图层已存在,请勿重复添加。')
return
}
if (props.item.type === 'PANO' && props.item.subType === 'line') {
console.log('PANO图层,开始加载线路数据')
const trueviewer = getTrueViewer()
if (!trueviewer) {
console.error('Viewer实例未找到')
return
}
try {
trueviewer.scene.requestRenderMode = false // 暂停渲染模式
// 1. 并行加载点数据和线数据
const [pointsResponse, linesResponse] = await Promise.all([
fetch(props.item.url1),
fetch(props.item.url2)
])
const [pointsGeoJson, linesGeoJson] = await Promise.all([
pointsResponse.json(),
linesResponse.json()
])
// 创建数组存储所有图元
const linePrimitives = [] // 存储所有线图元
const pointCollection = new Cesium.PointPrimitiveCollection() // 点图元集合
// ==================== 线数据优化处理 ====================
// 处理每条线特征
linesGeoJson.features.forEach((feature, index) => {
const geometryType = feature.geometry.type
// 处理 LineString 类型
if (geometryType === 'LineString') {
const coords = feature.geometry.coordinates
const primitive = processLineString(coords, index)
if (primitive) linePrimitives.push(primitive)
}
// 处理 MultiLineString 类型
else if (geometryType === 'MultiLineString') {
const multiCoords = feature.geometry.coordinates
multiCoords.forEach((lineCoords, lineIndex) => {
const primitive = processLineString(lineCoords, index, lineIndex)
if (primitive) linePrimitives.push(primitive)
})
}
})
// 辅助函数:处理单个 LineString
function processLineString(coords, featureIndex, lineIndex = 0) {
// 检查是否为有效线数据
if (!Array.isArray(coords)) {
console.warn(`第${featureIndex + 1}条线数据无效: 坐标不是数组`)
return null
}
if (coords.length < 2) {
console.warn(`第${featureIndex + 1}条线数据无效: 至少需要2个点`)
return null
}
// 转换坐标并创建位置数组
const positions = []
let isValid = true
coords.forEach((coord) => {
if (!Array.isArray(coord)) {
console.warn(`第${featureIndex + 1}条线的坐标点格式无效`)
isValid = false
return
}
const lon = Number(coord[0])
const lat = Number(coord[1])
if (isNaN(lon)) {
console.warn(`第${featureIndex + 1}条线的经度无效: ${coord[0]}`)
isValid = false
return
}
if (isNaN(lat)) {
console.warn(`第${featureIndex + 1}条线的纬度无效: ${coord[1]}`)
isValid = false
return
}
positions.push(Cesium.Cartesian3.fromDegrees(lon, lat))
})
if (!isValid || positions.length < 2) {
console.warn(`第${featureIndex + 1}条线数据无效`)
return null
}
// 为每条线创建独立的图元实例
const linePrimitive = new Cesium.Primitive({
geometryInstances: new Cesium.GeometryInstance({
geometry: new Cesium.PolylineGeometry({
positions: positions,
width: 8.0, // 线宽
vertexFormat: Cesium.PolylineColorAppearance.VERTEX_FORMAT,
arcType: Cesium.ArcType.GEODESIC // 使用大地线
}),
attributes: {
color: Cesium.ColorGeometryInstanceAttribute.fromColor(
Cesium.Color.HOTPINK.withAlpha(0.7)
),
depthFailColor: Cesium.ColorGeometryInstanceAttribute.fromColor(
Cesium.Color.HOTPINK.withAlpha(0.7)
)
},
id: `pano-line-${featureIndex}-${lineIndex}`
}),
appearance: new Cesium.PolylineColorAppearance({
translucent: true,
aboveGround: true
}),
asynchronous: false,
depthFailAppearance: new Cesium.PolylineColorAppearance({
translucent: true,
aboveGround: true
})
})
// 添加到场景
trueviewer.scene.primitives.add(linePrimitive)
return linePrimitive
}
// ==================== 点数据处理 ====================
// 2. 创建点图元集合
pointsGeoJson.features.forEach((feature, i) => {
const coord = feature.geometry.coordinates
if (Array.isArray(coord) && coord.length >= 2) {
const lon = Number(coord[0])
const lat = Number(coord[1])
if (!isNaN(lon) && !isNaN(lat)) {
pointCollection.add({
position: Cesium.Cartesian3.fromDegrees(lon, lat),
color: Cesium.Color.RED.withAlpha(0.9),
pixelSize: 8,
id: `pano-point-${i}`
})
}
}
})
// 将点集合添加到场景
trueviewer.scene.primitives.add(pointCollection)
// ==================== 计算并飞向视图范围(优化版) ====================
// 收集所有线图元和点图元的坐标点用于计算范围
const allPositions = []
// 添加线坐标(确保使用WGS84椭球体)
linesGeoJson.features.forEach((feature) => {
const geometryType = feature.geometry.type
const coordinates = feature.geometry.coordinates
if (geometryType === 'LineString' && Array.isArray(coordinates)) {
coordinates.forEach((coord) => {
if (Array.isArray(coord) && coord.length >= 2) {
const lon = Number(coord[0])
const lat = Number(coord[1])
if (!isNaN(lon) && !isNaN(lat)) {
allPositions.push(
Cesium.Cartesian3.fromDegrees(
lon,
lat,
0,
Cesium.Ellipsoid.WGS84
)
)
}
}
})
}
// 新增 MultiLineString 支持
else if (
geometryType === 'MultiLineString' &&
Array.isArray(coordinates)
) {
// MultiLineString 的 coordinates 是一个二维数组
coordinates.forEach((lineString) => {
if (Array.isArray(lineString)) {
lineString.forEach((coord) => {
if (Array.isArray(coord) && coord.length >= 2) {
const lon = Number(coord[0])
const lat = Number(coord[1])
if (!isNaN(lon) && !isNaN(lat)) {
allPositions.push(
Cesium.Cartesian3.fromDegrees(
lon,
lat,
0,
Cesium.Ellipsoid.WGS84
)
)
}
}
})
}
})
}
})
// 添加点坐标(同样使用WGS84椭球体)
pointsGeoJson.features.forEach((feature) => {
const coord = feature.geometry.coordinates
if (Array.isArray(coord) && coord.length >= 2) {
const lon = Number(coord[0])
const lat = Number(coord[1])
if (!isNaN(lon) && !isNaN(lat)) {
allPositions.push(
Cesium.Cartesian3.fromDegrees(lon, lat, 0, Cesium.Ellipsoid.WGS84)
)
}
}
})
if (allPositions.length === 0) {
console.error('无法计算视图范围: 没有有效坐标点')
viewer.delegate.scene.requestRenderMode = true
return
}
// 计算精确的包围球(扩大5%边界确保完全包含)
const boundingSphere = Cesium.BoundingSphere.fromPoints(allPositions)
boundingSphere.radius *= 1.05
// 恢复渲染
viewer.delegate.scene.requestRenderMode = true
// 计算飞行参数
const radius = boundingSphere.radius
const flyToHeight = Cesium.Math.clamp(
radius * 3.0, // 基础高度为半径的3倍
500, // 最低高度限制
20000 // 最高高度限制
)
const pitchAngle = -Cesium.Math.toRadians(30) // 固定30度俯视角
// 直接使用flyToBoundingSphere方案(更可靠)
viewer.delegate.camera.flyToBoundingSphere(boundingSphere, {
duration: 2.5,
offset: new Cesium.HeadingPitchRange(
0, // 正北方向
pitchAngle, // 俯视角
flyToHeight // 飞行高度
),
easingFunction: Cesium.EasingFunction.CUBIC_IN_OUT,
complete: () => {
console.log('飞行完成')
// 微调确保最佳视角
viewer.delegate.camera.moveBackward(radius * 0.1)
}
})
// 存入Vuex状态管理
const layerData = {
option: {
itemdata: props.item,
linePrimitives: linePrimitives, // 存储所有线图元
pointCollection: pointCollection // 存储所有点图元
},
panel_visible: true,
id: props.item.id,
type: 'system_pano',
name: props.item.name
}
store.commit('LayerData/addLayerData', layerData)
// 恢复渲染并关闭面板
trueviewer.scene.requestRenderMode = true
closePanel?.()
} catch (error) {
console.error('PANO图层加载失败:', error)
// 确保发生错误时恢复渲染
if (viewer?.delegate?.scene) {
viewer.delegate.scene.requestRenderMode = true
}
throw error
}
} else {
console.log('非PANO图层,开始添加图层')
// 非PANO图层处理
try {
// 3. 准备图层数据
const layerData = {
option: props.item,
panel_visible: true,
id: layerId,
type: 'system'
}
console.log('正在添加图层,ID:', layerId)
// 4. 添加到地图和状态管理
const layerInstance = addLayerToViewer(props.item, viewer)
store.commit('LayerData/addLayerData', layerData)
// 5. 飞行到图层范围
// 确保数据已加载
if (!jsonData.value) {
await loadData() // 等待数据加载完成
}
fly(dataArray.value, viewer, layerInstance, props.item)
// 关闭面板
closePanel?.()
} catch (error) {
console.error('添加图层失败:', error)
alert(`操作失败: ${error.message}`)
}
}
}
// // 处理删除图层点击事件
// const handleDelClick = async () => {
// // 1. 获取Viewer实例
// const viewer = getViewer()
// if (!viewer) {
// console.error('Viewer实例未找到')
// return
// }
// if (props.item.type === 'PANO' && props.item.subType === 'line') {
// // 需要deepseek完善
// } else {
// // 2. 从地图上删除图层
// delLayerFromViewer(props.item, viewer)
// // 3. 更新状态管理
// store.commit('LayerData/delLayerData', {
// id: props.item.id,
// type: 'system'
// })
// }
// }
const handleDelClick = async () => {
const viewer = getViewer()
if (!viewer) {
console.error('Viewer实例未找到')
return
}
if (props.item.type === 'PANO' && props.item.subType === 'line') {
const trueviewer = getTrueViewer()
if (!trueviewer) {
console.error('TrueViewer实例未找到')
return
}
try {
// 1. 从store获取图层数据
const layerData = store.getters['LayerData/getLayerDataById'](
props.item.id
)
if (!layerData) {
console.warn(`未找到ID为 ${props.item.id} 的图层数据`)
return
}
// 2. 从场景中移除所有图元
const { linePrimitives, pointCollection } = layerData.option
// // 移除线图元
// if (linePrimitives && Array.isArray(linePrimitives)) {
// console.log("1 正在移除线图元")
// linePrimitives.forEach((primitive) => {
// if (primitive && !primitive.isDestroyed) {
// trueviewer.scene.primitives.remove(primitive)
// }
// })
// }
// // 移除点图元集合
// if (pointCollection) {
// console.log("2 正在移除点图元集合")
// trueviewer.scene.primitives.remove(pointCollection)
// }
// 2. 正确销毁图元
destroyPanoPrimitives(trueviewer, linePrimitives, pointCollection)
// 4. 从状态管理中移除
store.commit('LayerData/delLayerData', {
id: props.item.id,
type: 'system_pano'
})
console.log(`成功删除PANO图层 ${props.item.id}`)
} catch (error) {
console.error('删除PANO图层失败:', error)
alert(`删除图层失败: ${error.message}`)
}
} else {
// 非PANO图层的删除逻辑(保持原样)
delLayerFromViewer(props.item, viewer)
store.commit('LayerData/delLayerData', {
id: props.item.id,
type: 'system'
})
}
}
// 在组件内部定义销毁函数
const destroyPanoPrimitives = (viewer, linePrimitives, pointCollection) => {
const scene = viewer.scene
const primitives = scene.primitives
// 销毁线图元
if (Array.isArray(linePrimitives)) {
linePrimitives.forEach(primitive => {
if (primitive && !primitive.isDestroyed) {
if (primitives.contains(primitive)) {
scene.primitives.remove(primitive) // true表示同时销毁
} else {
primitive.destroy()
}
}
})
}
// 销毁点图元集合
if (pointCollection && !pointCollection.isDestroyed) {
if (primitives.contains(pointCollection)) {
scene.primitives.remove(pointCollection)
} else {
pointCollection.destroy()
}
}
scene.requestRender()
}
onMounted(() => {
// loadData() // 在需要时调用 loadData()
})
</script>
<style scoped>
.active {
background-color: #f0f9ff;
color: #3b82f6;
font-weight: bold;
}
</style>
能不能看着我的代码,进行修改?你先学习了解我的代码,然后在代码里解决问题。
最新发布