Merge branch 'main' of http://121.37.111.42:3000/ThbTech/gis-bi into main
This commit is contained in:
commit
ad1fa0ee92
@ -15,6 +15,9 @@ import org.springframework.web.bind.annotation.RequestParam;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
import org.yaml.snakeyaml.DumperOptions;
|
||||
import org.yaml.snakeyaml.Yaml;
|
||||
import org.yaml.snakeyaml.nodes.Node;
|
||||
import org.yaml.snakeyaml.nodes.Tag;
|
||||
import org.yaml.snakeyaml.representer.Representer;
|
||||
|
||||
import java.io.*;
|
||||
import java.nio.file.Files;
|
||||
@ -146,8 +149,8 @@ public class ProjectExportController {
|
||||
if (spring.containsKey("datasource")) {
|
||||
Map<String, Object> datasource = (Map<String, Object>) spring.get("datasource");
|
||||
datasource.put("url", "jdbc:mysql://localhost:3306/newdb");
|
||||
datasource.put("username", "root");
|
||||
datasource.put("password", "123456");
|
||||
datasource.put("username", "roor");
|
||||
datasource.put("password", "sadfa123456");
|
||||
}
|
||||
}
|
||||
|
||||
@ -163,13 +166,18 @@ public class ProjectExportController {
|
||||
// 写回 YAML 文件
|
||||
DumperOptions options = new DumperOptions();
|
||||
options.setDefaultFlowStyle(DumperOptions.FlowStyle.BLOCK);
|
||||
// 美化输出格式
|
||||
options.setPrettyFlow(true);
|
||||
// 缩进两个空格
|
||||
options.setIndent(2);
|
||||
|
||||
// 将String类型转换为PLAIN样式
|
||||
// Representer representer = new Representer(options) {
|
||||
// @Override
|
||||
// protected Node representScalar(Tag tag, String value, DumperOptions.ScalarStyle style) {
|
||||
// // 使用 PLAIN 样式(无引号)
|
||||
// return super.representScalar(tag, value, DumperOptions.ScalarStyle.PLAIN);
|
||||
// }
|
||||
// };
|
||||
// Yaml newYaml = new Yaml(representer,options);
|
||||
Yaml newYaml = new Yaml(options);
|
||||
|
||||
try (Writer writer = new FileWriter(configPath)) {
|
||||
newYaml.dump(root, writer);
|
||||
}
|
||||
|
@ -46,16 +46,19 @@ public class DatabaseService {
|
||||
|
||||
// 1. 获取所有要导出的表名
|
||||
List<String> tables = Arrays.asList("app_application", "app_dictionary", "app_menu", "app_module",
|
||||
"app_organization", "app_role", "app_role_menu", "app_role_users", "app_user", "area",
|
||||
"app_optlog", "app_organization", "app_role", "app_role_menu", "app_role_users", "app_user", "area",
|
||||
"core_chart_view", "core_dataset_group", "core_dataset_table", "core_dataset_table_field",
|
||||
"core_datasource", "core_de_engine", "core_driver", "data_visualization_info",
|
||||
"visualization_background", "visualization_background_image", "visualization_link_jump",
|
||||
"visualization_link_jump_info", "visualization_link_jump_target_view_info", "visualization_linkage",
|
||||
"visualization_linkage_field", "visualization_outer_params", "visualization_outer_params_info",
|
||||
"core_datasource", "core_de_engine", "core_driver", "core_sys_setting", "data_visualization_info",
|
||||
"snapshot_core_chart_view", "snapshot_visualization_link_jump", "snapshot_visualization_link_jump_info",
|
||||
"snapshot_visualization_link_jump_target_view_info", "snapshot_visualization_linkage",
|
||||
"snapshot_visualization_linkage_field", "snapshot_visualization_outer_params_info",
|
||||
"snapshot_visualization_outer_params_target_view_info", "visualization_background",
|
||||
"visualization_background_image", "visualization_link_jump", "visualization_link_jump_info",
|
||||
"visualization_link_jump_target_view_info", "visualization_linkage", "visualization_linkage_field",
|
||||
"visualization_outer_params", "visualization_outer_params_info",
|
||||
"visualization_outer_params_target_view_info", "visualization_subject");
|
||||
|
||||
for (String table : tables) {
|
||||
|
||||
// 2. 获取建表语句
|
||||
String createSql = getCreateTableSql(table);
|
||||
if ("app_application".equals(table)) {
|
||||
@ -103,7 +106,7 @@ public class DatabaseService {
|
||||
// 3. 获取数据插入语句
|
||||
List<String> insertSqls = getInsertSqls(table, conditions);
|
||||
result.add(new TableData(table, createSql, insertSqls));
|
||||
} else if ("core_chart_view".equals(table)) {
|
||||
} else if ("core_chart_view".equals(table) || "snapshot_core_chart_view".equals(table)) {
|
||||
|
||||
List<DataVisualizationInfo> dataVisualizationInfos =
|
||||
dataVisualizationInfoMapper.selectList(new LambdaQueryWrapper<DataVisualizationInfo>().eq(DataVisualizationInfo::getAppId, id).select(DataVisualizationInfo::getId));
|
||||
@ -120,7 +123,7 @@ public class DatabaseService {
|
||||
// 3. 获取数据插入语句
|
||||
List<String> insertSqls = getInsertSqls(table, conditions);
|
||||
result.add(new TableData(table, createSql, insertSqls));
|
||||
} else if ("visualization_link_jump".equals(table)) {
|
||||
} else if ("visualization_link_jump".equals(table) || "snapshot_visualization_link_jump".equals(table)) {
|
||||
List<DataVisualizationInfo> dataVisualizationInfos =
|
||||
dataVisualizationInfoMapper.selectList(new LambdaQueryWrapper<DataVisualizationInfo>().eq(DataVisualizationInfo::getAppId, id).select(DataVisualizationInfo::getId));
|
||||
|
||||
@ -138,7 +141,7 @@ public class DatabaseService {
|
||||
List<String> insertSqls = getInsertSqls(table, conditions);
|
||||
result.add(new TableData(table, createSql, insertSqls));
|
||||
|
||||
} else if ("visualization_link_jump_info".equals(table)) {
|
||||
} else if ("visualization_link_jump_info".equals(table) || "snapshot_visualization_link_jump_info".equals(table)) {
|
||||
List<Long> ids = visualizationLinkJumpMapper.selectLinkJumpIds(id);
|
||||
List<Condition> conditions = new ArrayList<>();
|
||||
if (!ids.isEmpty()) {
|
||||
@ -151,7 +154,8 @@ public class DatabaseService {
|
||||
// 3. 获取数据插入语句
|
||||
List<String> insertSqls = getInsertSqls(table, conditions);
|
||||
result.add(new TableData(table, createSql, insertSqls));
|
||||
} else if ("visualization_link_jump_target_view_info".equals(table)) {
|
||||
} else if ("visualization_link_jump_target_view_info".equals(table) ||
|
||||
"snapshot_visualization_link_jump_target_view_info".equals(table)) {
|
||||
List<Long> dvInfoIds = visualizationLinkJumpInfoMapper.selectInfoIds(id);
|
||||
List<Condition> conditions = new ArrayList<>();
|
||||
if (!dvInfoIds.isEmpty()) {
|
||||
@ -190,7 +194,7 @@ public class DatabaseService {
|
||||
// 3. 获取数据插入语句
|
||||
List<String> insertSqls = getInsertSqls(table, conditions);
|
||||
result.add(new TableData(table, createSql, insertSqls));
|
||||
}else if ("visualization_outer_params".equals(table)) {
|
||||
} else if ("visualization_outer_params".equals(table) || "snapshot_visualization_outer_params".equals(table)) {
|
||||
List<DataVisualizationInfo> dataVisualizationInfos =
|
||||
dataVisualizationInfoMapper.selectList(new LambdaQueryWrapper<DataVisualizationInfo>().eq(DataVisualizationInfo::getAppId, id).select(DataVisualizationInfo::getId));
|
||||
List<Condition> conditions = new ArrayList<>();
|
||||
@ -206,7 +210,7 @@ public class DatabaseService {
|
||||
// 3. 获取数据插入语句
|
||||
List<String> insertSqls = getInsertSqls(table, conditions);
|
||||
result.add(new TableData(table, createSql, insertSqls));
|
||||
}else if ("visualization_outer_params_info".equals(table)) {
|
||||
} else if ("visualization_outer_params_info".equals(table) || "snapshot_visualization_outer_params_info".equals(table)) {
|
||||
List<Long> outerParamIds = visualizationOuterParamsMapper.selectOuterParamIds(id);
|
||||
List<Condition> conditions = new ArrayList<>();
|
||||
if (!outerParamIds.isEmpty()) {
|
||||
@ -219,7 +223,8 @@ public class DatabaseService {
|
||||
// 3. 获取数据插入语句
|
||||
List<String> insertSqls = getInsertSqls(table, conditions);
|
||||
result.add(new TableData(table, createSql, insertSqls));
|
||||
}else if ("visualization_outer_params_target_view_info".equals(table)) {
|
||||
} else if ("visualization_outer_params_target_view_info".equals(table) ||
|
||||
"snapshot_visualization_outer_params_target_view_info".equals(table)) {
|
||||
List<Long> paramsInfoIds = visualizationOuterParamsInfoMapper.selectParamsInfoIds(id);
|
||||
List<Condition> conditions = new ArrayList<>();
|
||||
if (!paramsInfoIds.isEmpty()) {
|
||||
|
@ -225,7 +225,7 @@ public class MenuServiceImpl extends ServiceImpl<MenuMapper, Menu> implements IM
|
||||
String parentId = currentMenu.getParentid();
|
||||
|
||||
QueryWrapper<Menu> wrapper = new QueryWrapper<>();
|
||||
wrapper.eq("parentid", parentId);
|
||||
wrapper.eq("parentid", parentId).eq("app_id",currentMenu.getAppId());
|
||||
wrapper.orderByAsc("orderno");
|
||||
|
||||
List<Menu> siblings = menuMapper.selectList(wrapper);
|
||||
@ -267,7 +267,7 @@ public class MenuServiceImpl extends ServiceImpl<MenuMapper, Menu> implements IM
|
||||
|
||||
// 获取新父级下的最大序号
|
||||
QueryWrapper<Menu> wrapper = new QueryWrapper<>();
|
||||
wrapper.eq("parentid", targetParentId);
|
||||
wrapper.eq("parentid", targetParentId).eq("app_id",menu.getAppId());
|
||||
wrapper.select("orderno","code");
|
||||
wrapper.orderByDesc("orderno");
|
||||
List<Menu> list = menuMapper.selectList(wrapper);
|
||||
|
@ -15,7 +15,12 @@ public enum FileType {
|
||||
/**
|
||||
* GIF
|
||||
*/
|
||||
GIF("47494638", "gif");
|
||||
GIF("47494638", "gif"),
|
||||
|
||||
/**
|
||||
* GLB (Binary GLTF)
|
||||
*/
|
||||
GLB("676C5446", "glb");
|
||||
|
||||
private String value = "";
|
||||
private String ext = "";
|
||||
|
@ -1,8 +1,9 @@
|
||||
spring:
|
||||
datasource:
|
||||
url: jdbc:mysql://121.37.111.42:3306/dataease?useSSL=false&useUnicode=true&characterEncoding=utf-8&zeroDateTimeBehavior=convertToNull&transformedBitIsBoolean=true&serverTimezone=GMT%2B8&nullCatalogMeansCurrent=true&allowPublicKeyRetrieval=true
|
||||
# url: jdbc:mysql://121.37.111.42:3306/dataease?useSSL=false&useUnicode=true&characterEncoding=utf-8&zeroDateTimeBehavior=convertToNull&transformedBitIsBoolean=true&serverTimezone=GMT%2B8&nullCatalogMeansCurrent=true&allowPublicKeyRetrieval=true
|
||||
url: jdbc:mysql://192.168.1.58:3306/dataease?useSSL=false&useUnicode=true&characterEncoding=utf-8&zeroDateTimeBehavior=convertToNull&transformedBitIsBoolean=true&serverTimezone=GMT%2B8&nullCatalogMeansCurrent=true&allowPublicKeyRetrieval=true
|
||||
username: root
|
||||
password: mysql_F8ysiK@2024
|
||||
password: 123456 #mysql_F8ysiK@2024
|
||||
messages:
|
||||
basename: i18n/lic,i18n/core,i18n/permissions,i18n/xpack,i18n/sync
|
||||
flyway:
|
||||
@ -12,7 +13,11 @@ spring:
|
||||
locations: classpath:db/migration
|
||||
baseline-on-migrate: true
|
||||
out-of-order: true
|
||||
|
||||
servlet:
|
||||
multipart:
|
||||
enabled: true
|
||||
max-file-size: 50MB # 单个文件最大
|
||||
max-request-size: 100MB # 每次请求总文件最大
|
||||
mybatis-plus:
|
||||
mapper-locations: classpath:mybatis/*.xml
|
||||
|
||||
|
@ -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')
|
||||
}
|
||||
|
@ -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)
|
||||
})
|
||||
|
273
core/core-frontend/src/components/three-display/index.vue
Normal file
273
core/core-frontend/src/components/three-display/index.vue
Normal 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>
|
@ -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,
|
||||
|
@ -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)
|
||||
|
@ -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>
|
||||
|
@ -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',
|
||||
|
@ -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 文件',
|
||||
|
@ -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: '区间',
|
||||
|
@ -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,7 +3134,7 @@ 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 >
|
||||
{{ t('chart.result_filter') }}
|
||||
@ -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"
|
||||
|
@ -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'
|
||||
}
|
||||
]
|
||||
},
|
||||
|
@ -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)
|
||||
}
|
||||
}
|
@ -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',
|
||||
|
@ -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 仅对指定图表类型(如 map、three-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 {
|
||||
|
@ -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"
|
||||
|
@ -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 =
|
||||
|
@ -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
|
||||
|
@ -169,7 +169,7 @@ const contentStyle = computed(() => {
|
||||
}
|
||||
})
|
||||
|
||||
// 通过实时监听的方式直接添加组件
|
||||
// 通过实时监听的方式直接添加组件 symbolic-map
|
||||
const handleNew = newComponentInfo => {
|
||||
state.countTime++
|
||||
const { componentName, innerType, staticMap } = newComponentInfo
|
||||
|
Loading…
Reference in New Issue
Block a user