双击模型上传到地图和模型在地图上的选定

This commit is contained in:
wangxk 2025-07-16 15:15:01 +08:00
parent 22a311bd21
commit c09321e258
4 changed files with 254 additions and 78 deletions

View File

@ -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(() => {

View File

@ -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,47 +67,33 @@ 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) {
basicStyle = parseJson(chart.customAttr).basicStyle
miscStyle = parseJson(chart.customAttr).misc
}
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[] = []
scene.render()
return new L7Wrapper(scene, config)
return new L7Wrapper(scene, []) // 只保留地图底图
}
}

View File

@ -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>

View File

@ -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) {