三维地图的创建和三维地图的模型上传,保存和删除功能

This commit is contained in:
wangxk 2025-07-09 13:54:35 +08:00
parent 3cd4ab510c
commit 6cdc46fedc
18 changed files with 493 additions and 233 deletions

View File

@ -2,13 +2,13 @@ export default {
server: {
proxy: {
'/api/f': {
target: 'http://192.168.1.58:8100',
target: 'http://192.168.1.20:8100',
changeOrigin: true,
rewrite: path => path.replace(/^\/api\/f/, 'de2api')
},
// 使用 proxy 实例
'/api': {
target: 'http://192.168.1.58:8100',
target: 'http://192.168.1.20:8100',
changeOrigin: true,
rewrite: path => path.replace(/^\/api/, 'de2api')
}

View File

@ -386,7 +386,8 @@ const boardMoveActive = computed(() => {
'symbolic-map',
'heat-map',
't-heatmap',
'circle-packing'
'circle-packing',
'three-map'
]
return element.value.isPlugin || CHARTS.includes(element.value.innerType)
})

View File

@ -0,0 +1,273 @@
<template>
<div v-loading="loading">
<div class="all_conter">
<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 :ref="(el) => setCanvasRef(el, index)" class="model-canvas"></div>
</div>
</div>
</el-scrollbar>
</div>
</template>
<script lang="ts" setup>
import { ref, onMounted, onBeforeUnmount, nextTick } 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'
// 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'
const dvMainStore = dvMainStoreWithOut()
const { curComponent } = storeToRefs(dvMainStore)
const snapshotStore = snapshotStoreWithOut()
//
let threeModel: any = ref([])
let modelAll: any = ref([])
//
const canvasRefs = ref([])
// renderer / controls / scene
const scenes = []
const cameras = []
const renderers = []
const controlsList = []
let loading = ref(true)
// canvas
const setCanvasRef = (el, index) => {
if (el) {
canvasRefs.value[index] = el
}
}
//
const initScene = (container, modelPath, index) => {
const scene = new THREE.Scene()
scenes.push(scene)
const camera = new THREE.PerspectiveCamera(20, 198 / 148, 0.1, 1000)
cameras.push(camera)
const canvas = document.createElement('canvas')
container.appendChild(canvas)
const renderer = new THREE.WebGLRenderer({ canvas })
renderer.setSize(198, 148)
renderer.setPixelRatio(Math.min(1.5, window.devicePixelRatio)) //
renderers.push(renderer)
//
const ambientLight = new THREE.AmbientLight(0xffffff, 5)
scene.add(ambientLight)
//
camera.position.z = 5
//
const controls = new OrbitControls(camera, canvas)
controls.dampingFactor = 0.05
controls.enableDamping = true
controls.update()
controls.addEventListener('change', () => {
if (!renderers[index].autoClear) {
renderers[index].clear()
}
renderers[index].render(scene, camera)
})
controlsList.push(controls)
//
const dracoLoader = new DRACOLoader()
dracoLoader.setDecoderPath('./static/js/')
dracoLoader.setDecoderConfig({ type: 'js' })
const loader = new GLTFLoader()
loader.setDRACOLoader(dracoLoader)
loader.load(modelPath, (gltf) => {
const model = gltf.scene
scene.add(model)
//
const box = new THREE.Box3().setFromObject(model)
const center = box.getCenter(new THREE.Vector3())
//
model.position.sub(center)
// OrbitControls
controls.target.copy(center)
controls.update()
//
renderer.render(scene, camera)
}, undefined, (error) => {
console.error('Error loading model:', error)
})
}
//
function importclick() {
const avatar = document.getElementById('avatar');
avatar?.click();
}
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 })
modelAll.value = getmodelurl(threeModel.value)
curComponent.value.threeModel = threeModel.value
snapshotStore.recordSnapshotCacheToMobile('threeModel')
batchOptChange('threeModel')
nextTick(() => {
//
const newIndex = modelAll.value.length - 1
const container = canvasRefs.value[newIndex]
if (container) {
initScene(container, modelAll.value[newIndex].path, newIndex)
loading.value = false
}
})
var file: any = document.getElementById('avatar');
file.value = '';
})
}
function getmodelurl(modelPath: any) {
let urlArr = JSON.parse(JSON.stringify(modelPath))
urlArr.map((item: any) => {
item.path = imgUrlTrans(item.path)
})
return urlArr
}
const batchOptChange = (custom, property, value, subProp?) => {
dvMainStore.setChangeProperties({
custom: custom,
property: property,
value: value,
subProp: subProp
})
snapshotStore.recordSnapshotCache('renderChart')
}
const deleteModel = (index: number) => {
ElMessageBox.confirm(
'确定要删除该三维模型吗?',
'警告',
{
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning',
}
)
.then(() => {
// THREE
const scene = scenes[index]
const renderer = renderers[index]
const control = controlsList[index]
scene.clear()
renderer.dispose()
control.dispose()
//
scenes.splice(index, 1)
cameras.splice(index, 1)
renderers.splice(index, 1)
controlsList.splice(index, 1)
//
threeModel.value.splice(index, 1)
modelAll.value.splice(index, 1)
// Pinia store
curComponent.value.threeModel = threeModel.value
snapshotStore.recordSnapshotCacheToMobile('threeModel')
batchOptChange('threeModel')
})
}
// threeModel
onMounted(() => {
threeModel.value = curComponent.value.threeModel ? curComponent.value.threeModel : []
modelAll.value = getmodelurl(threeModel.value)
nextTick(() => {
canvasRefs.value.forEach((container, index) => {
if (container) {
initScene(container, modelAll.value[index].path, index)
}
})
loading.value = false
})
})
onBeforeUnmount(() => {
scenes.forEach(scene => {
scene.clear()
})
renderers.forEach(renderer => {
renderer.dispose()
})
controlsList.forEach(control => control.dispose())
scenes.length = 0
cameras.length = 0
renderers.length = 0
controlsList.length = 0
})
</script>
<style lang="less" scoped>
.model-container {
display: flex;
flex-wrap: wrap;
gap: 20px;
}
.model-wrapper {
width: 200px;
height: 150px;
background-color: #00000050;
margin: auto;
cursor: pointer;
will-change: transform; //
border: 1px solid #ffffff00;
border-radius: 5px;
overflow: hidden;
position: relative;
.positionbutton {
position: absolute;
top: 3px;
right: 3px;
}
.positionbutton:hover {
color: #1989fa;
}
}
.model-wrapper:hover {
border: 1px solid #1989fa;
}
.all_conter {
width: 100%;
display: flex;
justify-content: center;
margin-bottom: 20px;
}
</style>

View File

@ -176,7 +176,8 @@ const state = reactive({
'word-cloud',
'flow-map',
'bidirectional-bar',
'symbolic-map'
'symbolic-map',
'three-map'
],
linkageExcludeViewType: [
'richTextView',
@ -187,7 +188,8 @@ const state = reactive({
'word-cloud',
'flow-map',
'bidirectional-bar',
'symbolic-map'
'symbolic-map',
'three-map'
],
copyData: null,
hyperlinksSetVisible: false,

View File

@ -422,7 +422,7 @@ const openMessageLoading = cb => {
})
}
//
const mapChartTypes = ['bubble-map', 'flow-map', 'heat-map', 'map', 'symbolic-map']
const mapChartTypes = ['bubble-map', 'flow-map', 'heat-map', 'map', 'symbolic-map','three-map']
const htmlToImage = () => {
downLoading.value = mapChartTypes.includes(viewInfo.value.type) ? false : true
useEmitt().emitter.emit('renderChart-' + viewInfo.value.id)

View File

@ -1,26 +1,12 @@
<template>
<div>
<el-dropdown
:id="'view-track-bar-' + chartId"
:teleported="false"
trigger="click"
@visible-change="visibleChange"
>
<el-dropdown :id="'view-track-bar-' + chartId" :teleported="false" trigger="click" @visible-change="visibleChange">
<input id="input" ref="trackButton" type="button" hidden />
<template #dropdown>
<div :class="{ 'data-mobile': isDataVMobile }">
<el-dropdown-menu
class="track-menu"
:style="{ 'font-family': fontFamily }"
:append-to-body="false"
>
<el-dropdown-item
v-for="(item, key) in trackMenu"
:key="key"
@mousedown.stop
@click="trackMenuClick(item)"
><span class="menu-item">{{ state.i18n_map[item] }}</span></el-dropdown-item
>
<el-dropdown-menu class="track-menu" :style="{ 'font-family': fontFamily }" :append-to-body="false">
<el-dropdown-item v-for="(item, key) in trackMenu" :key="key" @mousedown.stop
@click="trackMenuClick(item)"><span class="menu-item">{{ state.i18n_map[item] }}</span></el-dropdown-item>
</el-dropdown-menu>
</div>
</template>

View File

@ -2003,6 +2003,7 @@ export default {
export_raw_details: 'Raw Details',
field_is_empty_export_error: 'No fields available, unable to export',
chart_symbolic_map: 'Symbolic map',
three_map: 'Three map',
symbolic: 'Symbolic',
symbolic_shape: 'Symbolic Shape',
symbolic_upload_hint: 'Supports SVG, JPG, JPEG, PNG files within 1MB',

View File

@ -1950,6 +1950,7 @@ export default {
export_raw_details: '導出原始明細',
field_is_empty_export_error: '目前無欄位無法匯出',
chart_symbolic_map: '符號地圖',
three_map: '三維地圖',
symbolic: '符號',
symbolic_shape: '符號形狀',
symbolic_upload_hint: '支持 1MB 以內的 SVG, JPG, JPEG, PNG 文件',

View File

@ -1956,6 +1956,7 @@ export default {
field_is_empty_export_error: '当前无字段无法导出',
chart_symbolic_map: '符号地图',
symbolic: '符号',
three_map: '三维地图',
symbolic_shape: '符号形状',
symbolic_upload_hint: '支持 1MB 以内的 SVG, JPG, JPEG, PNG 文件',
symbolic_range: '区间',

View File

@ -9,6 +9,7 @@ import icon_refresh_outlined from '@/assets/svg/icon_refresh_outlined.svg'
import icon_add_outlined from '@/assets/svg/icon_add_outlined.svg'
import icon_searchOutline_outlined from '@/assets/svg/icon_search-outline_outlined.svg'
import icon_copy_outlined from '@/assets/svg/icon_copy_outlined.svg'
import threeDisplay from '@/components/three-display/index.vue'
import {
PropType,
reactive,
@ -321,6 +322,8 @@ const chartStyleShow = computed(() => {
})
const chartViewInstance = computed(() => {
// console.log(chartViewManager.getChartView(view.value.render, view.value.type))
// debugger
return chartViewManager.getChartView(view.value.render, view.value.type)
})
const showAxis = (axis: AxisType) => chartViewInstance.value?.axis?.includes(axis)
@ -3131,9 +3134,9 @@ const deleteChartFieldItem = id => {
</el-row>
<!--filter-->
<el-row class="padding-lr drag-data no-top-border no-top-padding">
<el-row v-if="view.type != 'three-map'" class="padding-lr drag-data no-top-border no-top-padding">
<div class="form-draggable-title">
<span>
<span >
{{ t('chart.result_filter') }}
</span>
<el-tooltip
@ -3180,7 +3183,9 @@ const deleteChartFieldItem = id => {
<span>{{ $t('chart.filter') }}</span>
</el-button>
</el-row>
<div v-if="view.type == 'three-map'">
<threeDisplay :view="view" />
</div>
<el-row v-if="showAggregate" class="refresh-area">
<el-form-item
class="form-item no-margin-bottom"
@ -3306,7 +3311,7 @@ const deleteChartFieldItem = id => {
</el-row>
</el-footer>
</el-container>
</el-tab-pane>
</el-tab-pane>
<el-tab-pane
name="style"

View File

@ -1536,6 +1536,13 @@ export const CHART_TYPE_CONFIGS = [
value: 'symbolic-map',
title: t('chart.chart_symbolic_map'),
icon: 'symbolic-map'
},
{
render: 'antv',
category: 'map',
value: 'three-map',
title: t('chart.three_map'),
icon: 'three-map'
}
]
},

View File

@ -0,0 +1,145 @@
import {
L7ChartView,
L7Config,
L7DrawConfig,
L7Wrapper
} from '@/views/chart/components/js/panel/types/impl/l7'
import { MAP_EDITOR_PROPERTY_INNER } from '@/views/chart/components/js/panel/charts/map/common'
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'
/**
* 符号地图
*/
export class ThreeMap extends L7ChartView<Scene, L7Config> {
properties: EditorProperty[] = [
'background-overall-component',
'border-style',
'basic-style-selector',
'title-selector',
'label-selector',
'tooltip-selector',
'threshold'
]
propertyInner: EditorPropertyInner = {
...MAP_EDITOR_PROPERTY_INNER,
'basic-style-selector': [
'colors',
'alpha',
'mapBaseStyle',
'zoom',
'showLabel',
'autoFit',
'mapCenter',
'zoomLevel'
],
'label-selector': ['color', 'fontSize', 'showFields', 'customContent'],
'tooltip-selector': [
'color',
'fontSize',
'showFields',
'customContent',
'show',
'backgroundColor',
'carousel'
],
threshold: ['lineThreshold']
}
axis: AxisType[] = ['extLabel', 'extTooltip']
constructor() {
super('three-map', [])
}
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) // 重新绘制
})
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,
container,
mapKey,
basicStyle,
miscStyle,
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)
}
}

View File

@ -241,7 +241,7 @@ export function getRemark(chart) {
export const quotaViews = ['label', 'richTextView', 'indicator', 'gauge', 'liquid']
// 地图
const mapChartTypes = ['bubble-map', 'flow-map', 'heat-map', 'map', 'symbolic-map']
const mapChartTypes = ['bubble-map', 'flow-map', 'heat-map', 'map', 'symbolic-map','three-map']
// 分布图
const distributionChartTypes = [
'pie',

View File

@ -28,15 +28,7 @@ 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, Scene, TileLayer } from '@antv/l7'
import { GaodeMap } from '@antv/l7-maps';
//
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';
import { ExportImage, Scale, Fullscreen, Control} from '@antv/l7'
const { t } = useI18n()
const dvMainStore = dvMainStoreWithOut()
const { nowPanelTrackInfo, nowPanelJumpInfo, mobileInPc, embeddedCallBack, inMobile } =
@ -241,7 +233,7 @@ const calcData = async (view, callback) => {
callback?.()
})
} else {
if (['bubble-map', 'map', 'flow-map', 'heat-map'].includes(view.type)) {
if (['bubble-map', 'map', 'flow-map', 'heat-map','three-map'].includes(view.type)) {
await renderChart(view, callback)
}
callback?.()
@ -249,6 +241,7 @@ const calcData = async (view, callback) => {
}
let curView
const renderChart = async (view, callback?) => {
console.log('renderChart',view.type, callback)
if (!view) {
return
}
@ -263,6 +256,8 @@ const renderChart = async (view, callback?) => {
const chartView = chartViewManager.getChartView(view.render, view.type)
recursionTransObj(customAttrTrans, chart.customAttr, scale.value, terminal.value)
recursionTransObj(customStyleTrans, chart.customStyle, scale.value, terminal.value)
console.log('chart', chart)
console.log( chartView)
switch (chartView.library) {
case ChartLibraryType.L7_PLOT:
await renderL7Plot(chart, chartView as L7PlotChartView<any, any>, callback)
@ -344,7 +339,6 @@ const renderL7Plot = async (chart: ChartObj, chartView: L7PlotChartView<any, any
class SatelliteControl extends Control {
protected onAdd() {
// debugger
const btn = document.createElement('button');
btn.className = 'l7-control-button l7-satellite-control';
btn.innerHTML = '卫星';
@ -398,7 +392,6 @@ const renderL7 = async (chart: ChartObj, chartView: L7ChartView<any, any>, callb
chart: chart,
action
});
//
if (!scaleControl) {
scaleControl = new Scale({
@ -425,188 +418,12 @@ const renderL7 = async (chart: ChartObj, chartView: L7ChartView<any, any>, callb
myChart.getScene()?.addControl(satelliteControlInstance);
}
// ====== ======
threel7()
myChart?.render();
callback?.();
emit('resetLoading');
}, 500);
};
//
let threeJSLayerRef:any = null
const threel7 = () => {
// ====== ======
const scene = myChart.getScene(); //
scene.registerRenderService(ThreeRender);
threeJSLayerRef = new ThreeLayer({
enableMultiPassRenderer: false,
onAddMeshes: (threeScene: any, layer: any) => {
//
const camera = new THREE.PerspectiveCamera(
75,
scene.width / scene.height,
0.1,
1000000000
);
camera.position.set(0, 0, 10000000);
threeScene.add(camera);
layer.camera = camera; //
threeScene.add(new THREE.AmbientLight(0xffffff));
const sunlight: any = new THREE.DirectionalLight(0xffffff, 0.25);
sunlight.position.set(0, 80000000, 100000000);
sunlight.matrixWorldNeedsUpdate = true;
threeScene.add(sunlight);
// 使 Three.js glTFLoader
const manager: any = new THREE.LoadingManager()
manager.setURLModifier((url: any, path: any) => {
return (path || '') + url
})
const loader = new GLTFLoader(manager);
const dracoLoader = new DRACOLoader();
dracoLoader.setDecoderPath('./static/js/');
dracoLoader.setDecoderConfig({ type: 'js' });
loader.setDRACOLoader(dracoLoader);
loader.load(
`./static/3DModel/scene.glb`,
(gltf) => {
// debugger
const gltfScene: any = gltf.scene
// setDouble(gltfScene);
content = gltfScene
layer.adjustMeshToMap(gltfScene);
// gltfScene.scale.set(1000, 1000, 1000)
layer.setMeshScale(gltfScene, 10, 10, 10);
layer.setObjectLngLat(gltfScene, [116.397428, 39.91923], 0);
//
threeScene.add(new THREE.AmbientLight(0xffffff, 0.3));
// UV Mapping
gltfScene.traverse((node) => {
if (node.isMesh) {
const material = node.material;
if (material.map) {
const uv = node.geometry.attributes.uv;
for (let i = 0; i < uv.count; i++) {
uv.setX(i, /* 新的 U 值 */);
uv.setY(i, /* 新的 V 值 */);
}
uv.needsUpdate = true;
}
}
});
//
gltfScene.traverse((node) => {
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;
}
}
});
//
const sunlight = new THREE.DirectionalLight(0xffffff, 0.4);
sunlight.position.set(50, 100, 50);
sunlight.target.position.set(0, 0, 0);
sunlight.matrixWorldNeedsUpdate = true;
threeScene.add(sunlight);
//
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);
spotLight.angle = Math.PI / 4;
threeScene.add(spotLight);
//
scene.on('click', handleModelClick);
//
threeScene.add(gltfScene);
// //
layer.render();
},
);
},
}).animate(true);
scene.addLayer(threeJSLayerRef);
// ====== ======
}
//
const handleModelClick = (e: any) => {
// debugger
//
if (!threeJSLayerRef || !threeJSLayerRef.camera) {
console.warn('Camera not initialized');
return;
}
// 1.
const point = e.containerPoint || { x: e.originEvent.x, y: e.originEvent.y }; //
const scene = myChart.getScene();
const container = scene.getContainer();
const width = container.clientWidth;
const height = container.clientHeight;
// 2.
const mouse = new THREE.Vector2();
mouse.x = (point.x / width) * 2 - 1;
mouse.y = -(point.y / height) * 2 + 1;
// 3. 线
const raycaster = new THREE.Raycaster();
raycaster.setFromCamera(mouse, threeJSLayerRef.camera);
// 4.
const allObjects = [];
content.traverse(child => {
if (child.isMesh && child.visible) {
allObjects.push(child);
}
});
const intersects = raycaster.intersectObjects(allObjects);
// 5.
if (intersects.length > 0) {
const closest = intersects[0];
console.log('模型被点击:', closest.object);
// 6.
highlightSelectedObject(closest.object);
} else {
//
clearSelection();
}
};
//
const highlightSelectedObject = (object) => {
//
if (!object.userData.originalMaterial) {
object.userData.originalMaterial = object.material;
}
//
object.material = new THREE.MeshBasicMaterial({
color: 0xff0000,
transparent: true,
opacity: 0.7
});
};
//
const clearSelection = () => {
content.traverse(child => {
if (child.userData.originalMaterial) {
child.material = child.userData.originalMaterial;
delete child.userData.originalMaterial;
}
});
};
const pointClickTrans = () => {
@ -917,33 +734,53 @@ defineExpose({
let resizeObserver
const TOLERANCE = 0.01
const RESIZE_MONITOR_CHARTS = ['map', 'bubble-map', 'flow-map', 'heat-map']
const RESIZE_MONITOR_CHARTS = ['map', 'bubble-map', 'flow-map', 'heat-map','three-map']
onMounted(() => {
console.log('Three.js实际版本:', THREE.REVISION);
// 1. DOM
const containerDom = document.getElementById(containerId)
// 2. [, ]
const { offsetWidth, offsetHeight } = containerDom
const preSize = [offsetWidth, offsetHeight]
// 3. ResizeObserver
resizeObserver = new ResizeObserver(([entry] = []) => {
// 3.1 mapthree-map
if (!RESIZE_MONITOR_CHARTS.includes(view.value.type)) {
return
}
// 3.2
const [size] = entry.borderBoxSize || []
// 3.3
const widthOffsetPercent = (size.inlineSize - preSize[0]) / preSize[0]
const heightOffsetPercent = (size.blockSize - preSize[1]) / preSize[1]
// 3.4 TOLERANCE=0.01
if (Math.abs(widthOffsetPercent) < TOLERANCE && Math.abs(heightOffsetPercent) < TOLERANCE) {
return
}
// 3.5
if (myChart && preSize[1] > 1) {
renderChart(curView)
renderChart(curView) // 调用 [renderChart](file://e:\2项目\1gis_bi\gis-bi\core\core-frontend\src\views\chart\components\views\components\ChartComponentG2Plot.vue#L241-L273) 重绘图表
}
// 3.6
preSize[0] = size.inlineSize
preSize[1] = size.blockSize
})
resizeObserver.observe(containerDom)
useEmitt({ name: 'l7-prepare-picture', callback: preparePicture })
useEmitt({ name: 'l7-unprepare-picture', callback: unPreparePicture })
// Three.js
// 4.
resizeObserver.observe(containerDom)
// 5.
// 5.1 `l7-prepare-picture`
useEmitt({ name: 'l7-prepare-picture', callback: preparePicture })
// 5.2 `l7-unprepare-picture`
useEmitt({ name: 'l7-unprepare-picture', callback: unPreparePicture })
// 6. Three.js
// this.initThreeJs()
})
onBeforeUnmount(() => {
try {

View File

@ -598,7 +598,7 @@ const calcData = params => {
}
const showChartView = (...libs: ChartLibraryType[]) => {
if (view.value?.render && view.value?.type) {
if (view.value?.render && view.value?.type) {
const chartView = chartViewManager.getChartView(view.value.render, view.value.type)
return chartView && libs?.includes(chartView.library)
} else {
@ -1049,7 +1049,7 @@ const loadPluginCategory = data => {
}
const allEmptyCheck = computed(() => {
return ['rich-text', 'picture-group'].includes(element.value.innerType)
return ['rich-text', 'picture-group','three-map'].includes(element.value.innerType)
})
/**
* 标题提示的最大宽度
@ -1160,6 +1160,7 @@ const clearG2Tooltip = () => {
</div>
<!--这里去渲染不同图库的图表-->
<div v-if="allEmptyCheck || (chartAreaShow && !showEmpty)" style="flex: 1; overflow: hidden">
<plugin-component
v-if="view.plugin?.isPlugin && loadPlugin"
:jsname="view.plugin.staticMap['index']"
@ -1230,7 +1231,7 @@ const clearG2Tooltip = () => {
:font-family="fontFamily"
:active="active"
v-else-if="
showChartView(ChartLibraryType.G2_PLOT, ChartLibraryType.L7_PLOT, ChartLibraryType.L7)
showChartView(ChartLibraryType.G2_PLOT, ChartLibraryType.L7_PLOT, ChartLibraryType.L7) || view.type == 'three-map'
"
ref="chartComponent"
@onChartClick="chartClick"

View File

@ -124,7 +124,7 @@ const loadCanvasData = (dvId, weight?) => {
)
}
//
const mapChartTypes = ['bubble-map', 'flow-map', 'heat-map', 'map', 'symbolic-map']
const mapChartTypes = ['bubble-map', 'flow-map', 'heat-map', 'map', 'symbolic-map','three-map']
const downloadH2 = type => {
downloadStatus.value = true
const mapElementIds =

View File

@ -175,10 +175,10 @@ const handleNew = newComponentInfo => {
const { componentName, innerType, staticMap } = newComponentInfo
if (componentName) {
const { width, height, scale } = canvasStyleData.value
const component = findNewComponent(componentName, innerType, staticMap)
const component = findNewComponent(componentName, innerType, staticMap)//
component.style.top = ((height - component.style.height) * scale) / 200
component.style.left = ((width - component.style.width) * scale) / 200
component.id = guid()
component.id = guid() //id
const popComponents = componentData.value.filter(
ele => ele.category && ele.category === 'hidden'
)
@ -191,9 +191,9 @@ const handleNew = newComponentInfo => {
component.category = canvasState.value.curPointArea
component.commonBackground.backgroundColor = 'rgba(41, 41, 41, 1)'
}
changeComponentSizeWithScale(component)
changeComponentSizeWithScale(component) //
dvMainStore.addComponent({ component: component, index: undefined })
adaptCurThemeCommonStyle(component)
adaptCurThemeCommonStyle(component)///
snapshotStore.recordSnapshotCache('renderChart', component.id)
if (state.countTime > 10) {
state.sideShow = false

View File

@ -169,7 +169,7 @@ const contentStyle = computed(() => {
}
})
//
// symbolic-map
const handleNew = newComponentInfo => {
state.countTime++
const { componentName, innerType, staticMap } = newComponentInfo