双击模型上传到地图和模型在地图上的选定
This commit is contained in:
parent
22a311bd21
commit
c09321e258
@ -1,13 +1,14 @@
|
||||
<template>
|
||||
<div v-loading="loading">
|
||||
<div class="all_conter">
|
||||
<el-button type="primary" @click="importclick()" style="width: 200px;">导入三维模型</el-button>
|
||||
<el-button type="primary" @click="importclick()" style="width: 200px;">上传三维模型</el-button>
|
||||
<input type="file" id="avatar" style="display:none" @change="changeFiless">
|
||||
</div>
|
||||
<el-scrollbar height="59vh">
|
||||
<div class="model-container">
|
||||
<div v-for="(model, index) in modelAll" :key="index" class="model-wrapper">
|
||||
<div class="positionbutton" @click="deleteModel(index)">删除</div>
|
||||
<!-- -->
|
||||
<div v-for="(model, index) in modelAll" :key="index" class="model-wrapper" @dblclick="putInModel(model, index)">
|
||||
<div class="positionbutton" @click.stop="deleteModel(index)">删除</div>
|
||||
<div :ref="(el) => setCanvasRef(el, index)" class="model-canvas"></div>
|
||||
</div>
|
||||
</div>
|
||||
@ -16,21 +17,40 @@
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { ref, onMounted, onBeforeUnmount, nextTick } from 'vue'
|
||||
import { ref, onMounted, onBeforeUnmount, nextTick,PropType,toRefs,computed } from 'vue'
|
||||
import { ElMessage, ElMessageBox } from 'element-plus'
|
||||
import * as THREE from 'three'
|
||||
import { GLTFLoader } from 'three/examples/jsm/loaders/GLTFLoader'
|
||||
import { DRACOLoader } from 'three/examples/jsm/loaders/DRACOLoader'
|
||||
import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls'
|
||||
import { uploadFileResult } from '@/api/staticResource'
|
||||
import { BASE_VIEW_CONFIG } from '@/views/chart/components/editor/util/chart'
|
||||
//保存 1.拿数据
|
||||
import { dvMainStoreWithOut } from '@/store/modules/data-visualization/dvMain'
|
||||
import { snapshotStoreWithOut } from '@/store/modules/data-visualization/snapshot'
|
||||
import { storeToRefs } from 'pinia'
|
||||
import { imgUrlTrans } from '@/utils/imgUtils'
|
||||
import { useI18n } from '@/hooks/web/useI18n'
|
||||
import { useEmitt } from '@/hooks/web/useEmitt'
|
||||
const { t } = useI18n()
|
||||
// import eventBus from '@/utils/eventBus'
|
||||
const dvMainStore = dvMainStoreWithOut()
|
||||
const { curComponent } = storeToRefs(dvMainStore)
|
||||
const {
|
||||
curComponent,
|
||||
mobileInPc,
|
||||
componentData
|
||||
} = storeToRefs(dvMainStore)
|
||||
const snapshotStore = snapshotStoreWithOut()
|
||||
const props = defineProps({
|
||||
view: {
|
||||
type: Object as PropType<ChartObj>,
|
||||
required: false,
|
||||
default() {
|
||||
return { ...BASE_VIEW_CONFIG }
|
||||
}
|
||||
},
|
||||
})
|
||||
const { view } = toRefs(props)
|
||||
// 模型路径
|
||||
let threeModel: any = ref([])
|
||||
let modelAll: any = ref([])
|
||||
@ -122,16 +142,20 @@ function importclick() {
|
||||
const avatar = document.getElementById('avatar');
|
||||
avatar?.click();
|
||||
}
|
||||
//保存之前页面保存
|
||||
function saveModel(model: any) {
|
||||
curComponent.value.threeModel = model
|
||||
snapshotStore.recordSnapshotCacheToMobile('threeModel')
|
||||
batchOptChange('threeModel')
|
||||
}
|
||||
function changeFiless(e: any) {
|
||||
loading.value = true
|
||||
const files = new FormData()
|
||||
files.append('file', e.target.files[0])
|
||||
uploadFileResult(e.target.files[0], fileUrl => {
|
||||
threeModel.value.push({ path: fileUrl })
|
||||
threeModel.value.push({ path: fileUrl, display: false })
|
||||
modelAll.value = getmodelurl(threeModel.value)
|
||||
curComponent.value.threeModel = threeModel.value
|
||||
snapshotStore.recordSnapshotCacheToMobile('threeModel')
|
||||
batchOptChange('threeModel')
|
||||
saveModel(threeModel.value)
|
||||
nextTick(() => {
|
||||
// 手动重新渲染新添加的模型
|
||||
const newIndex = modelAll.value.length - 1
|
||||
@ -152,7 +176,7 @@ function getmodelurl(modelPath: any) {
|
||||
})
|
||||
return urlArr
|
||||
}
|
||||
const batchOptChange = (custom, property, value, subProp?) => {
|
||||
const batchOptChange = (custom: any, property, value, subProp?) => {
|
||||
dvMainStore.setChangeProperties({
|
||||
custom: custom,
|
||||
property: property,
|
||||
@ -190,15 +214,64 @@ const deleteModel = (index: number) => {
|
||||
// 删除数据源中的模型
|
||||
threeModel.value.splice(index, 1)
|
||||
modelAll.value.splice(index, 1)
|
||||
|
||||
// 同步保存到 Pinia store
|
||||
curComponent.value.threeModel = threeModel.value
|
||||
snapshotStore.recordSnapshotCacheToMobile('threeModel')
|
||||
batchOptChange('threeModel')
|
||||
saveModel(threeModel.value)
|
||||
calcData(view.value, true, 'updateQuery')
|
||||
})
|
||||
|
||||
}
|
||||
// 监听 threeModel 的变化
|
||||
|
||||
//触发双击击事件,放入三维模型
|
||||
function putInModel(model: any, index: any) {
|
||||
threeModel.value[index].display = true
|
||||
modelAll.value = getmodelurl(threeModel.value)
|
||||
saveModel(threeModel.value)
|
||||
calcData(view.value, true, 'updateQuery')
|
||||
}
|
||||
//刷新图层事件
|
||||
const calcData = (view, resetDrill = false, updateQuery = '') => {
|
||||
if (
|
||||
view.refreshTime === '' ||
|
||||
parseFloat(view.refreshTime).toString() === 'NaN' ||
|
||||
parseFloat(view.refreshTime) < 1
|
||||
) {
|
||||
ElMessage.error(t('chart.only_input_number'))
|
||||
return
|
||||
}
|
||||
if (resetDrill) {
|
||||
useEmitt().emitter.emit('resetDrill-' + view.id, 0)
|
||||
} else {
|
||||
if (mobileInPc.value) {
|
||||
//移动端设计
|
||||
useEmitt().emitter.emit('onMobileStatusChange', {
|
||||
type: 'componentStyleChange',
|
||||
value: { type: 'calcData', component: JSON.parse(JSON.stringify(view)) }
|
||||
})
|
||||
} else {
|
||||
useEmitt().emitter.emit('calcData-' + view.id, view)
|
||||
snapshotStore.recordSnapshotCache('renderChart', view.id)
|
||||
}
|
||||
}
|
||||
snapshotStore.recordSnapshotCache('calcData', view.id)
|
||||
if (updateQuery === 'updateQuery') {
|
||||
queryList.value.forEach(ele => {
|
||||
useEmitt().emitter.emit(`updateQueryCriteria${ele.id}`)
|
||||
})
|
||||
}
|
||||
}
|
||||
const queryList = computed(() => {
|
||||
let arr = []
|
||||
componentData.value.forEach(com => {
|
||||
if (com.innerType === 'VQuery') {
|
||||
arr.push(com)
|
||||
}
|
||||
if ('DeTabs' === com.innerType) {
|
||||
com.propValue.forEach(itx => {
|
||||
arr = [...itx.componentData.filter(item => item.innerType === 'VQuery'), ...arr]
|
||||
})
|
||||
}
|
||||
})
|
||||
return arr
|
||||
})
|
||||
|
||||
onMounted(() => {
|
||||
threeModel.value = curComponent.value.threeModel ? curComponent.value.threeModel : []
|
||||
@ -211,7 +284,7 @@ onMounted(() => {
|
||||
})
|
||||
loading.value = false
|
||||
})
|
||||
|
||||
calcData(view.value, true, 'updateQuery')
|
||||
})
|
||||
|
||||
onBeforeUnmount(() => {
|
||||
|
@ -8,19 +8,23 @@ import { MAP_EDITOR_PROPERTY_INNER } from '@/views/chart/components/js/panel/cha
|
||||
import {
|
||||
parseJson,
|
||||
} from '@/views/chart/components/js/util'
|
||||
import { deepCopy } from '@/utils/utils'
|
||||
import { Scene } from '@antv/l7-scene'
|
||||
import {
|
||||
getMapCenter,
|
||||
getMapScene,
|
||||
getMapStyle,
|
||||
mapRendered
|
||||
} from '@/views/chart/components/js/panel/common/common_antv'
|
||||
import { HeatmapLayer } from '@antv/l7-layers'
|
||||
import { DEFAULT_BASIC_STYLE } from '@/views/chart/components/editor/util/chart'
|
||||
// import { dvMainStoreWithOut } from '@/store/modules/data-visualization/dvMain'
|
||||
// import { storeToRefs } from 'pinia'
|
||||
// const dvMainStore = dvMainStoreWithOut()
|
||||
// const { curComponent } = storeToRefs(dvMainStore)
|
||||
/**
|
||||
* 符号地图
|
||||
* 三维地图
|
||||
*/
|
||||
// eventBus.on('createModel', createModel)
|
||||
// function createModel(model){
|
||||
// console.log('jiehsou',model)
|
||||
// }
|
||||
export class ThreeMap extends L7ChartView<Scene, L7Config> {
|
||||
properties: EditorProperty[] = [
|
||||
'background-overall-component',
|
||||
@ -63,22 +67,19 @@ export class ThreeMap extends L7ChartView<Scene, L7Config> {
|
||||
}
|
||||
|
||||
async drawChart(drawOption: L7DrawConfig<L7Config>) {
|
||||
// console.log('111111111')
|
||||
// debugger
|
||||
const { chart, container, action } = drawOption
|
||||
const containerDom = document.getElementById(container)
|
||||
const rect = containerDom?.getBoundingClientRect()
|
||||
// 修改后
|
||||
|
||||
if (rect?.height <= 0) {
|
||||
// 容器不可见时,监听尺寸变化并重试绘制
|
||||
const observer = new ResizeObserver(() => {
|
||||
observer.disconnect()
|
||||
this.drawChart(drawOption) // 重新绘制
|
||||
this.drawChart(drawOption)
|
||||
})
|
||||
observer.observe(containerDom)
|
||||
return new L7Wrapper(drawOption.chartObj?.getScene(), [])
|
||||
}
|
||||
const xAxis = deepCopy(chart.xAxis)
|
||||
|
||||
let basicStyle
|
||||
let miscStyle
|
||||
if (chart.customAttr) {
|
||||
@ -88,22 +89,11 @@ export class ThreeMap extends L7ChartView<Scene, L7Config> {
|
||||
|
||||
const mapKey = await this.getMapKey()
|
||||
const mapStyle = getMapStyle(mapKey, basicStyle)
|
||||
|
||||
let center = getMapCenter(basicStyle)
|
||||
// 联动时,聚焦到数据点,多个取第一个
|
||||
if (
|
||||
chart.chartExtRequest?.linkageFilters?.length &&
|
||||
xAxis?.length === 2 &&
|
||||
chart.data?.tableRow.length
|
||||
) {
|
||||
// 经度
|
||||
const lng = chart.data?.tableRow?.[0][chart.xAxis[0].gisbiName]
|
||||
// 纬度
|
||||
const lat = chart.data?.tableRow?.[0][chart.xAxis[1].gisbiName]
|
||||
center = [lng, lat]
|
||||
}
|
||||
|
||||
const chartObj = drawOption.chartObj as unknown as L7Wrapper<L7Config, Scene>
|
||||
let scene = chartObj?.getScene()
|
||||
|
||||
scene = await getMapScene(
|
||||
chart,
|
||||
scene,
|
||||
@ -114,32 +104,11 @@ export class ThreeMap extends L7ChartView<Scene, L7Config> {
|
||||
mapStyle,
|
||||
center
|
||||
)
|
||||
this.configZoomButton(chart, scene, mapKey)
|
||||
const config: L7Config = new HeatmapLayer({
|
||||
name: 'line',
|
||||
blend: 'normal',
|
||||
autoFit: !(basicStyle.autoFit === false)
|
||||
})
|
||||
.source(chart.data?.data, {
|
||||
parser: {
|
||||
type: 'json',
|
||||
x: 'x',
|
||||
y: 'y'
|
||||
}
|
||||
})
|
||||
.size('value', [0, 1.0]) // weight映射通道
|
||||
.shape(basicStyle.heatMapType ?? DEFAULT_BASIC_STYLE.heatMapType)
|
||||
|
||||
config.style({
|
||||
intensity: basicStyle.heatMapIntensity ?? DEFAULT_BASIC_STYLE.heatMapIntensity,
|
||||
radius: basicStyle.heatMapRadius ?? DEFAULT_BASIC_STYLE.heatMapRadius,
|
||||
rampColors: {
|
||||
colors: basicStyle.colors.reverse(),
|
||||
positions: [0, 0.11, 0.22, 0.33, 0.44, 0.55, 0.66, 0.77, 0.88, 1.0]
|
||||
}
|
||||
})
|
||||
// const configList: L7Config[] = []
|
||||
this.configZoomButton(chart, scene, mapKey)
|
||||
|
||||
scene.render()
|
||||
return new L7Wrapper(scene, config)
|
||||
return new L7Wrapper(scene, []) // 只保留地图底图
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -29,10 +29,16 @@ import { isDashboard, trackBarStyleCheck } from '@/utils/canvasUtils'
|
||||
import { useEmitt } from '@/hooks/web/useEmitt'
|
||||
import { L7ChartView } from '@/views/chart/components/js/panel/types/impl/l7'
|
||||
import { useI18n } from '@/hooks/web/useI18n'
|
||||
import { ExportImage, Scale, Fullscreen, Control} from '@antv/l7'
|
||||
import { ExportImage, Scale, Fullscreen, Control } from '@antv/l7'
|
||||
import { imgUrlTrans } from '@/utils/imgUtils'
|
||||
//三维导入
|
||||
import * as THREE from 'three';
|
||||
import { GLTFLoader } from 'three/examples/jsm/loaders/GLTFLoader';
|
||||
import { DRACOLoader } from 'three/examples/jsm/loaders/DRACOLoader.js' // 控制器
|
||||
import { ThreeLayer, ThreeRender } from '@antv/l7-three';
|
||||
const { t } = useI18n()
|
||||
const dvMainStore = dvMainStoreWithOut()
|
||||
const { nowPanelTrackInfo, nowPanelJumpInfo, mobileInPc, embeddedCallBack, inMobile } =
|
||||
const { nowPanelTrackInfo, nowPanelJumpInfo, mobileInPc, embeddedCallBack, inMobile, curComponent } =
|
||||
storeToRefs(dvMainStore)
|
||||
const { emitter } = useEmitt()
|
||||
const props = defineProps({
|
||||
@ -234,7 +240,7 @@ const calcData = async (view, callback) => {
|
||||
callback?.()
|
||||
})
|
||||
} else {
|
||||
if (['bubble-map', 'map', 'flow-map', 'heat-map','three-map'].includes(view.type)) {
|
||||
if (['bubble-map', 'map', 'flow-map', 'heat-map', 'three-map'].includes(view.type)) {
|
||||
await renderChart(view, callback)
|
||||
}
|
||||
callback?.()
|
||||
@ -443,6 +449,17 @@ const renderL7 = async (chart: ChartObj, chartView: L7ChartView<any, any>, callb
|
||||
satelliteControlInstance = new SatelliteControl({ position: 'bottomright' });
|
||||
myChart.getScene()?.addControl(satelliteControlInstance);
|
||||
}
|
||||
if (view.value.type == 'three-map') {
|
||||
// debugger
|
||||
if (curComponent.value && curComponent.value.threeModel && curComponent.value.threeModel.length > 0) {
|
||||
curComponent.value.threeModel.forEach((item: any) => {
|
||||
if (item.display) {
|
||||
threel7(imgUrlTrans(item.path))
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
}
|
||||
// ====== 修复完成 ======
|
||||
myChart?.render();
|
||||
callback?.();
|
||||
@ -450,7 +467,118 @@ const renderL7 = async (chart: ChartObj, chartView: L7ChartView<any, any>, callb
|
||||
}, 500);
|
||||
};
|
||||
|
||||
//三维模型集成
|
||||
let threeJSLayerRef: any = null
|
||||
let modelthree: any = null
|
||||
const loadedModel = ref<THREE.Group | null>(null)
|
||||
const threel7 = (modelUrl: any) => {
|
||||
const scene = myChart.getScene();
|
||||
scene.registerRenderService(ThreeRender);
|
||||
// 直接创建并保存相机实例
|
||||
threeJSLayerRef = new ThreeLayer({
|
||||
enableMultiPassRenderer: false,
|
||||
onAddMeshes: (threeScene: any, layer: any) => {
|
||||
// 初始化光源
|
||||
threeScene.add(new THREE.AmbientLight(0xffffff));
|
||||
const sunlight = new THREE.DirectionalLight(0xffffff, 0.25);
|
||||
sunlight.position.set(0, 80000000, 100000000);
|
||||
threeScene.add(sunlight);
|
||||
|
||||
// 模型加载配置
|
||||
const manager = new THREE.LoadingManager();
|
||||
manager.setURLModifier((url: any, path: any) => (path || '') + url);
|
||||
|
||||
const loader = new GLTFLoader(manager);
|
||||
const dracoLoader = new DRACOLoader();
|
||||
dracoLoader.setDecoderPath('./static/js/');
|
||||
dracoLoader.setDecoderConfig({ type: 'js' });
|
||||
loader.setDRACOLoader(dracoLoader);
|
||||
|
||||
loader.load(modelUrl, (gltf) => {
|
||||
const gltfScene = gltf.scene;
|
||||
modelthree = gltfScene
|
||||
// 调整模型位置
|
||||
layer.adjustMeshToMap(gltfScene);
|
||||
layer.setMeshScale(gltfScene, 10, 10, 10);
|
||||
layer.setObjectLngLat(gltfScene, [116.397428, 39.91923], 0);
|
||||
gltfScene.updateMatrixWorld(true); // 更新矩阵以保证坐标正确
|
||||
|
||||
// 添加环境光
|
||||
threeScene.add(new THREE.AmbientLight(0xffffff, 0.3));
|
||||
|
||||
// 处理材质
|
||||
gltfScene.traverse((node: any) => {
|
||||
if (node.isMesh) {
|
||||
const material = node.material;
|
||||
if (material.map) {
|
||||
material.map.repeat.set(1, 1);
|
||||
material.map.offset.set(0, 0);
|
||||
material.map.wrapS = THREE.ClampToEdgeWrapping;
|
||||
material.map.wrapT = THREE.ClampToEdgeWrapping;
|
||||
}
|
||||
}
|
||||
});
|
||||
// 确保模型正确加载碰撞体积
|
||||
gltfScene.traverse((child: any) => {
|
||||
if (child.isMesh) {
|
||||
child.geometry.computeBoundingBox();
|
||||
child.geometry.computeBoundingSphere();
|
||||
}
|
||||
});
|
||||
|
||||
// 添加光源
|
||||
const dirLight = new THREE.DirectionalLight(0xffffff, 0.4);
|
||||
dirLight.position.set(50, 100, 50);
|
||||
threeScene.add(dirLight);
|
||||
|
||||
const pointLight = new THREE.PointLight(0xffffff, 0.6, 1000);
|
||||
pointLight.position.set(10, 15, -5);
|
||||
threeScene.add(pointLight);
|
||||
|
||||
const spotLight = new THREE.SpotLight(0xffffff, 0.8);
|
||||
spotLight.position.set(10, 25, 15);
|
||||
threeScene.add(spotLight);
|
||||
loadedModel.value = gltfScene
|
||||
threeScene.add(gltfScene);
|
||||
layer.render();
|
||||
});
|
||||
}
|
||||
}).animate(true);
|
||||
|
||||
scene.addLayer(threeJSLayerRef);
|
||||
scene.on('click', (ev) => {
|
||||
const size = scene?.map?.getSize();
|
||||
const mouse = new THREE.Vector2();
|
||||
const raycaster = new THREE.Raycaster();
|
||||
let currentCamera = threeJSLayerRef.getRenderCamera();
|
||||
mouse.x = (ev.pixel.x / size.width) * 2 - 1;
|
||||
mouse.y = -(ev.pixel.y / size.height) * 2 + 1;
|
||||
raycaster.setFromCamera(mouse, currentCamera);
|
||||
const intersects = raycaster.intersectObjects([modelthree], true);
|
||||
if (intersects.length > 0) {
|
||||
const object = intersects[0].object;
|
||||
console.log(object)
|
||||
createBoundingBox(object as THREE.Object3D);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
// ====== 三维模型集成完成 ======
|
||||
// 定义包围盒变量
|
||||
const selectedBoundingBox = ref<THREE.BoxHelper | null>(null);
|
||||
|
||||
// 创建包围盒函数
|
||||
function createBoundingBox(model: THREE.Object3D) {
|
||||
// 移除之前的包围盒
|
||||
if (selectedBoundingBox.value) {
|
||||
threeJSLayerRef.scene.remove(selectedBoundingBox.value);
|
||||
selectedBoundingBox.value = null;
|
||||
}
|
||||
// 创建新的包围盒
|
||||
const boxHelper = new THREE.BoxHelper(model, 0xffff00); // 黄色
|
||||
threeJSLayerRef.scene.add(boxHelper);
|
||||
selectedBoundingBox.value = boxHelper;
|
||||
}
|
||||
|
||||
const pointClickTrans = () => {
|
||||
if (embeddedCallBack.value === 'yes') {
|
||||
@ -760,7 +888,7 @@ defineExpose({
|
||||
|
||||
let resizeObserver
|
||||
const TOLERANCE = 0.01
|
||||
const RESIZE_MONITOR_CHARTS = ['map', 'bubble-map', 'flow-map', 'heat-map','three-map']
|
||||
const RESIZE_MONITOR_CHARTS = ['map', 'bubble-map', 'flow-map', 'heat-map', 'three-map']
|
||||
onMounted(() => {
|
||||
// 1. 获取当前图表容器的 DOM 元素
|
||||
const containerDom = document.getElementById(containerId)
|
||||
@ -808,15 +936,21 @@ onMounted(() => {
|
||||
// 6. 预留 Three.js 初始化逻辑(当前未启用)
|
||||
// 示例:this.initThreeJs()
|
||||
})
|
||||
// 清理包围盒
|
||||
onBeforeUnmount(() => {
|
||||
try {
|
||||
myChart?.destroy()
|
||||
resizeObserver?.disconnect()
|
||||
} catch (e) {
|
||||
console.warn(e)
|
||||
}
|
||||
myChart?.destroy();
|
||||
resizeObserver?.disconnect();
|
||||
|
||||
})
|
||||
// 移除包围盒
|
||||
if (selectedBoundingBox.value) {
|
||||
threeJSLayerRef.getScene().remove(selectedBoundingBox.value);
|
||||
selectedBoundingBox.value = null;
|
||||
}
|
||||
} catch (e) {
|
||||
console.warn(e);
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
|
@ -192,7 +192,7 @@ const handleNew = newComponentInfo => {
|
||||
component.commonBackground.backgroundColor = 'rgba(41, 41, 41, 1)'
|
||||
}
|
||||
changeComponentSizeWithScale(component) //确保组件在不同比例下保持视觉比例协调
|
||||
dvMainStore.addComponent({ component: component, index: undefined })
|
||||
dvMainStore.addComponent({ component: component, index: undefined })//添加组件
|
||||
adaptCurThemeCommonStyle(component)//用户切换亮/暗色模式时,自动更新所有组件的样式(如文字颜色、背景色)。
|
||||
snapshotStore.recordSnapshotCache('renderChart', component.id)
|
||||
if (state.countTime > 10) {
|
||||
|
Loading…
Reference in New Issue
Block a user