This commit is contained in:
zh
2025-09-23 11:30:01 +08:00
parent 20e294adab
commit c72b3035bf
38 changed files with 2331 additions and 82 deletions

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 547 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 818 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

View File

@ -0,0 +1,89 @@
import request from '@/axios/request'
//模型库
export const ModelApi = {
//创建模型库
createModelDB: async (data: any) => {
return await request.post({
url: `/modelLibrary/createModelLibrary`,
data
})
},
//导入模型库
importModelDB: async (data: any) => {
return await request.post({
url: `/modelLibrary/importModelLibrary`,
data
})
},
//删除模型
delModel: async (data: any) => {
return await request.post({
url: `/modelLibrary/deleteModel`,
data,
})
},
//删除模型类型
delModelType: async (data: any) => {
return await request.post({
url: `/modelLibrary/deleteModelType`,
data,
})
},
//添加模型类型
addModelType: async (data: any) => {
return await request.post({
url: `/modelLibrary/addModelType`,
data
})
},
//模型类型列表
modelTypeList: async () => {
return await request.get({
url: `/modelLibrary/modelTypeList`
})
},
//添加模型文件
addModel: async (data: any) => {
return await request.post({
url: `/modelLibrary/addModelFile`,
data,
headers: {
'Content-Type': 'multipart/form-data'
}
})
},
//根据模型类型查看模型列表
showModelByType: async (data: any) => {
return await request.post({
url: `/modelLibrary/modelList`,
data,
// headers: {
// 'content-type': 'application/x-www-form-urlencoded'
// }
})
},
//更新模型名称和封面
updatePoster: async (data: any) => {
return await request.post({
url: `/modelLibrary/uploadModelInfo`,
data,
headers: {
'content-type': 'multipart/form-data'
}
})
},
//默认模型参数设置
modelSetting: async (data: any) => {
return await request.post({
url: `/businessConfig/addBusinessConfig`,
data,
})
},
//获取默认模型参数设置
getModelSetting: async () => {
return await request.get({
url: `/businessConfig/list`
})
},
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 924 B

View File

@ -42,7 +42,7 @@ export const initMapData = async (type, data, cd) => {
entityObject = new YJ.Obj.EllipseObject(window.earth, data)
break
case 'model':
entityObject = new YJ.Obj.ModelObject(window.earth, data)
entityObject = new YJ.Obj.Model(window.earth, data)
break
case 'terrain':
data.host = baseURL

View File

@ -74,7 +74,7 @@
<template #default="{ row }">
<!-- <el-button size="small" @click="handleEdit(row)">编辑</el-button> -->
<el-button size="small" @click="handleEdit(row)">预览</el-button>
<el-button size="small" @click="handleEdit(row)">更换缩略图</el-button>
<el-button size="small" @click="changePhoto(row)">更换缩略图</el-button>
<el-button size="small" @click="handleDelete(row)">删除</el-button>
</template>
</el-table-column>
@ -301,6 +301,10 @@ const handleEdit = (row: ModelItem) => {
console.log('编辑模型', row)
}
const changePhoto = (row: ModelItem) => {
console.log('编辑模型', row)
}
const handleDelete = (row: ModelItem) => {
console.log('删除模型', row)
}

View File

@ -1,13 +1,18 @@
<template>
<div class="model-management-container">
<div class="equipment_title" style="margin-bottom: 10px">
<el-button color="#004b4b" style="border: 1px solid rgba(0, 255, 255, 0.5)">
<el-button color="#004b4b" style="border: 1px solid rgba(0, 255, 255, 0.5)" @click="importModelDB">
<template #icon>
<svg-icon name="leading_in" />
</template>
<span>选择模型库</span>
</el-button>
<el-button color="#004b4b" style="border: 1px solid rgba(0, 255, 255, 0.5)">
<el-button
color="#004b4b"
style="border: 1px solid rgba(0, 255, 255, 0.5)"
@click="createModelDB"
>
<template #icon>
<svg-icon name="leading_in" />
</template>
@ -26,7 +31,6 @@
:data="typeTreeData"
draggable
ref="treeRef"
default-expand-all
node-key="id"
@node-click="handleTypeClick"
@node-contextmenu="handleContextMenu"
@ -69,13 +73,12 @@
<el-card shadow="hover">
<el-table :data="modelList" border style="width: 100%">
<el-table-column type="index" label="序号" width="60" align="center" />
<el-table-column prop="name" label="模型名称" />
<el-table-column prop="modelName" label="模型名称" />
<el-table-column prop="thumbnail" label="缩略图" width="100">
<template #default="{ row }">
<el-image
style="width: 80px; height: 60px"
@click="showImage(row)"
:src="row.thumbnail"
:src="'http://127.0.0.1:8848' + row.poster"
fit="cover"
/>
</template>
@ -83,7 +86,7 @@
<el-table-column label="操作">
<template #default="{ row }">
<!-- <el-button size="small" @click="handleEdit(row)">编辑</el-button> -->
<el-button size="small" @click="handleEdit(row)">预览</el-button>
<el-button size="small" @click="showImage(row)">预览</el-button>
<el-button size="small" @click="handleEdit(row)">更换缩略图</el-button>
<el-button size="small" @click="handleDelete(row)">删除</el-button>
</template>
@ -107,25 +110,47 @@
:menu-items="contextMenu.items"
@command="handleMenuCommand"
/>
<!-- 添加类型弹窗 -->
<el-dialog v-model="dialogVisible" title="添加模型类型" width="30%" :before-close="handleClose">
<el-input v-model="modelType" placeholder="请输入模型类型" />
<template #footer>
<span class="dialog-footer">
<el-button @click="closeDialog">取消</el-button>
<el-button type="primary" @click="addType"> 确定 </el-button>
</span>
</template>
</el-dialog>
<input ref="fileInput" type="file" hidden @change="handleFileChange" />
</div>
</template>
<script lang="ts" setup>
import { ref, reactive, nextTick, onMounted } from 'vue'
import { ref, reactive, onMounted } from 'vue'
import type { TableColumnCtx } from 'element-plus'
import contextMenuCom from './contentMenu.vue'
import Sortable from 'sortablejs'
import { inject } from 'vue'
import type { DragEvents } from 'element-plus/es/components/tree/src/model/useDragNode'
import type { AllowDropType, NodeDropType, RenderContentContext } from 'element-plus'
import { ModelApi } from '@/api/model/index'
import { ElMessage, ElMessageBox } from 'element-plus'
import {
$sendElectronChanel,
$recvElectronChanel,
$changeComponentShow
} from '@/utils/communication'
const fs = require("fs");
const eventBus: any = inject('bus')
var clickTreeNode: any = reactive({})
interface TypeNode {
id: string
label: string
parentId: string | null
children?: TypeNode[]
createdAt?: string | null
updatedAt?: string | null
}
interface ModelItem {
@ -133,28 +158,10 @@ interface ModelItem {
typeId: string
name: string
thumbnail: string
modelId: string
}
const typeTreeData = ref<TypeNode[]>([
{
id: '1',
label: '一级分类1',
parentId: null,
children: [
{ id: '1-1', label: '二级分类1-1', parentId: '1' },
{ id: '1-2', label: '二级分类1-2', parentId: '1' }
]
},
{
id: '2',
label: '一级分类2',
parentId: null,
children: [
{ id: '2-1', label: '二级分类2-1', parentId: '2' },
{ id: '2-2', label: '二级分类2-2', parentId: '2' }
]
}
])
const typeTreeData = ref<TypeNode[]>([])
const modelList = ref<ModelItem[]>([])
const currentTypeId = ref<string>('')
@ -171,6 +178,140 @@ const treeRef = ref()
// @ts-ignore
var sortableInstance: any = reactive(null)
//--------------添加模型类型----------------
var dialogVisible: any = ref(false)
var modelType: any = ref('')
const handleClose = () => {
dialogVisible.value = false
}
const closeDialog = () => {
modelType.value = ''
dialogVisible.value = false
}
const addType = () => {
if (!modelType.value) {
return ElMessage.warning('请输入模型类型名称')
}
let params = {
name: modelType.value,
parentId: clickTreeNode && clickTreeNode.id ? clickTreeNode.id : undefined
}
ModelApi.addModelType(params).then((res) => {
if (res.code == 0 || res.code == 200) {
ElMessage.success('添加成功')
getModelList()
}
})
modelType.value = ''
dialogVisible.value = false
}
//获取模型列表
eventBus.on('settingPop', (data) => {
if (data) {
//关闭弹框时更新模型列表
// getModelList()
getModelListByType(showImageRow.modelTypeId)
showImageRow = null
}
})
const getModelList = async () => {
const res: any = await ModelApi.modelTypeList()
if (res.code == 0 || res.code == 200) {
let data = transformNestedJson(res.data, 'name', 'label')
typeTreeData.value = data
}
}
const transformNestedJson = (data, oldKey, newKey) => {
if (Array.isArray(data)) {
return data.map((item) => transformNestedJson(item, oldKey, newKey))
} else if (data && typeof data === 'object') {
const newObj = {}
for (const key in data) {
// 替换键名
const currentKey = key === oldKey ? newKey : key
// 递归处理子元素
newObj[currentKey] = transformNestedJson(data[key], oldKey, newKey)
}
return newObj
}
return data
}
//------------导入模型--------------
const fileInput = ref<HTMLInputElement>()
const triggerUpload = () => {
fileInput.value?.click()
}
const handleFileChange = (e: Event) => {
const files = (e.target as HTMLInputElement).files
if (files) {
const formData = new FormData()
formData.append('files', files[0])
formData.append('modelTypeId', clickTreeNode.id)
ModelApi.addModel(formData).then((res) => {
if (res.code == 0 || res.code == 200) {
ElMessage.success('导入成功')
getModelListByType(clickTreeNode.id)
}
})
// 此处可添加文件验证或预处理逻辑
}
}
//创建模型库
const createModelDB = async () => {
let option = {
title: '创建模型库',
filename: 'YJEarth.model',
filters: [{ name: '保存库文件', extensions: ['model'] }]
}
$sendElectronChanel('saveFile', option)
$recvElectronChanel('selectedFileItem', (e, path) => {
if (path) {
let index = path.lastIndexOf('/')
let model_lib_path = path.slice(0, index)
let model_lib_name = path.slice(index + 1)
ModelApi.createModelDB({ name: model_lib_name, path: model_lib_path }).then((res) => {
if (res.code == 0 || res.code == 200) {
ElMessage.success('创建成功')
getModelList()
}
})
}
})
}
//导入模型库
const importModelDB = ()=>{
let option = {
properties: ["openFile"],
filters: [
{
name: '模型库', //、底图
extensions: ['model'],
},
],
};
$sendElectronChanel("open-directory-dialog", option);
$recvElectronChanel("selectedItem", (e, path) => {
if (path.length) addModelDB(path[0]);
});
}
const addModelDB = (path)=>{
let formData = new FormData()
formData.append('modelPath',path)
ModelApi.importModelDB(formData).then(res=>{
if (res.code == 0 || res.code == 200) {
ElMessage.success('导入成功')
getModelList()
}
})
}
//拖拽
type Node = RenderContentContext['node']
@ -211,9 +352,9 @@ const allowDrag = (draggingNode: Node) => {
//拖拽结束
//---------------------查看缩略图--------------------
var showImageRow = null
const showImage = (row: any) => {
console.log(row, 'image')
showImageRow = row
eventBus.emit('imagePopDialog', row)
eventBus.emit('settingPop', false)
}
@ -242,6 +383,7 @@ const handleContextMenu = (event: MouseEvent, row: TypeNode) => {
{ command: 'delete', label: '删除', icon: 'delModel' }
]
}
clickTreeNode = row
contextMenu.visible = true
}
@ -256,18 +398,28 @@ const toggleExpand = (row: any) => {
row.collapse()
} else {
row.expand()
// getModelListByType(row.data.id)
}
} else {
// getModelListByType(row.data.id)
}
getModelListByType(row.data.id)
currentTypeId.value = row.id
loadModelsByType(row.id)
// loadModelsByType(row.id)
contextMenu.visible && (contextMenu.visible = false)
}
const getModelListByType = (id) => {
let formData = new FormData()
formData.append('modelTypeId', id)
ModelApi.showModelByType(formData).then((res) => {
modelList.value = res.data
})
}
const divContextMenu = (event: MouseEvent) => {
event.preventDefault()
contextMenu.x = event.clientX
contextMenu.y = event.clientY
console.log('空白右击')
clickTreeNode = null
contextMenu.items = [{ command: 'addType', label: '添加类型', icon: 'add' }]
contextMenu.visible = true
@ -275,9 +427,14 @@ const divContextMenu = (event: MouseEvent) => {
const handleMenuCommand = (command: string) => {
const row = contextMenu.currentRow
if (!row) return
if (!row) {
//添加类型
handleAddType()
} else {
switch (command) {
case 'addType':
handleAddType()
break
case 'add-child':
handleAddChildType(row)
break
@ -292,6 +449,7 @@ const handleMenuCommand = (command: string) => {
break
}
}
}
const handleTypeClick = (row: TypeNode) => {
console.log(row)
@ -307,27 +465,30 @@ const handleClick = () => {
const loadModelsByType = (typeId: string) => {
// 模拟数据加载
modelList.value = [
{
id: '1',
typeId: typeId,
name: `模型_${typeId}_1`,
thumbnail: 'https://picsum.photos/200/150?random=1'
},
{
id: '2',
typeId: typeId,
name: `模型_${typeId}_2`,
thumbnail: 'https://picsum.photos/200/150?random=2'
}
// {
// id: '1',
// typeId: typeId,
// name: `模型_${typeId}_1`,
// thumbnail: 'https://picsum.photos/200/150?random=1'
// },
// {
// id: '2',
// typeId: typeId,
// name: `模型_${typeId}_2`,
// thumbnail: 'https://picsum.photos/200/150?random=2'
// }
]
}
const handleAddType = () => {
dialogVisible.value = true
}
const handleAddChildType = (row: TypeNode) => {
console.log('添加子类型', row)
dialogVisible.value = true
}
const handleImportModel = (row: TypeNode) => {
console.log('导入模型到', row)
triggerUpload()
}
const handleRenameType = (row: TypeNode) => {
@ -335,16 +496,147 @@ const handleRenameType = (row: TypeNode) => {
}
const handleDeleteType = (row: TypeNode) => {
console.log('删除类型', row)
ElMessageBox.confirm('是否删除数据?', '警告', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
})
.then(() => {
let formData = new FormData()
formData.append('modelTypeId', row.id)
ModelApi.delModelType(formData).then((res) => {
if (res.code == 0 || res.code == 200) {
ElMessage.success('删除成功')
getModelList()
}
})
})
.catch(() => {})
}
const handleEdit = (row: ModelItem) => {
console.log('编辑模型', row)
updatePoster(row)
}
const updatePoster1 = (row, flag = false, path = '') => {
let cb = (c) => {
const formData = new FormData()
formData.append('modelId', row.id)
formData.append('file', c)
ModelApi.updatePoster(formData).then((res) => {
if (res.code == 0 || res.code == 200) {
getModelListByType(row.modelTypeId)
ElMessage.success('添加成功')
}
})
}
if (!flag) {
let option = {
properties: ['openFile'],
filters: [
{
name: '图片',
extensions: ['png', 'jpg', 'jpeg']
}
]
}
$sendElectronChanel('open-directory-dialog', option)
$recvElectronChanel('selectedItem', (e, paths) => {
if (paths.length) {
cb(paths[0])
}
})
} else {
cb(path)
}
}
const updatePoster = (row, flag = false, path = "")=> {
let that = this;
let cb = (c) => {
const formData = new FormData()
formData.append('modelId', row.id)
formData.append('file', c)
ModelApi.updatePoster(formData).then((res) => {
if (res.code == 0 || res.code == 200) {
getModelListByType(row.modelTypeId)
ElMessage.success('添加成功')
}
})
};
if (!flag) {
let option = {
properties: ["openFile"],
filters: [
{
name: "图片",
extensions: ["png", "jpg", "jpeg"],
},
],
};
$sendElectronChanel("open-directory-dialog", option);
$recvElectronChanel("selectedItem", (e, paths) => {
if (paths.length) {
// 取出路径中的文件名
let name = getNamefromPath(paths[0]);
// 读取文件
fs.readFile(paths[0], (err, data) => {
const blob = new Blob([data], {
type: "image/png, image/jpeg, image/jpg",
});
var file = new File([blob], `${name}` + ".png");
cb(file);
});
// cb(paths[0]);
}
});
} else {
cb(path);
}
}
const getNamefromPath = (path) => {
let index = 1;
if (path.endsWith(".json")) {
index = 2;
}
let arr = path.split("/");
let str = arr[arr.length - index];
let name = str;
if (!path.endsWith(".json")) {
let arr1 = str.split(".");
arr1.pop();
name = arr1.join(".");
}
return name;
}
const handleDelete = (row: ModelItem) => {
console.log('删除模型', row)
ElMessageBox.confirm('是否删除数据?', '警告', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
})
.then(() => {
let formData = new FormData()
formData.append('modelId', row.id)
ModelApi.delModel(formData).then((res) => {
if (res.code == 0 || res.code == 200) {
ElMessage.success('删除成功')
getModelListByType(row.modelTypeId)
}
})
})
.catch(() => {})
}
onMounted(() => {
getModelList()
})
</script>
<style scoped>
@ -486,4 +778,23 @@ const handleDelete = (row: ModelItem) => {
color: rgba(255, 255, 255, 1) !important;
font-size: 12px !important;
}
::v-deep .el-input {
width: 300px;
margin-left: 30px;
--el-input-placeholder-color: rgba(173, 241, 255, 1) !important;
--el-input-placeholder-font-size: 14px;
--el-input-text-color: #fff !important;
--el-input-border-color: rgba(var(--color-sdk-base-rgb), 0.5) !important;
--el-input-hover-border-color: rgba(var(--color-sdk-base-rgb), 0.5) !important;
--el-input-focus-border-color: rgba(var(--color-sdk-base-rgb), 0.5) !important;
}
::v-deep .el-input__wrapper {
background-color: rgba(0, 0, 0, 0.5) !important;
}
::v-deep .el-dialog__title {
text-shadow: 0px 0px 9px var(--color-sdk-text-shadow) !important;
color: rgba(255, 255, 255, 1) !important;
}
</style>

View File

@ -1,6 +1,6 @@
<template>
<div class="set_pup">
<el-dialog v-model="isShowPup" :modal="false" draggable>
<el-dialog v-model="isShowPup" :modal="false" draggable :close-on-click-modal='false' :destroy-on-close='true'>
<template #header>
<div class="set_pup_header">
<div class="system_title">
@ -49,6 +49,7 @@ const activeName = ref('authorize')
const isShowPup = ref(false)
const open = () => {
isShowPup.value = true
activeName.value = 'authorize'
}
const close = () => {
isShowPup.value = false

View File

@ -10,8 +10,8 @@
<template #content>
<div class="content">
<span class="custom-divider"></span>
<div class="imageCon">
<img class="image" :src="rowData.thumbnail" alt="" />
<div class="imageCon" ref="threeCanvas">
<!-- <img class="image" :src="rowData.thumbnail" alt="" /> -->
</div>
<div class="inputCon">
<span class="label">模型名称</span>
@ -33,19 +33,110 @@
import { ref, reactive } from 'vue'
import { inject } from 'vue'
import Dialog from '@/components/dialog/baseDialog.vue'
import * as THREE from 'three'
import { GLTFLoader } from 'three/examples/jsm/loaders/GLTFLoader'
import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls.js'
import { debounce } from '@/utils'
import { ModelApi } from '@/api/model/index'
import { ElMessage } from 'element-plus'
const baseDialog: any = ref(null)
const eventBus: any = inject('bus')
const viewPointHeight: any = ref(1.8)
var rowData: any = reactive([])
var imageData: any = ref(null)
var newData: any = reactive({
name: ''
})
eventBus.on('imagePopDialog', (data) => {
console.log(data, 'data')
rowData = data
rowData.name = data.modelName
baseDialog.value?.open()
setTimeout(() => {
initThreeJS()
loadModel()
animate()
})
})
//----------------模型预览---------------
const threeCanvas = ref(null)
let scene, camera, renderer, model, controls
const initThreeJS = () => {
// 创建场景
scene = new THREE.Scene()
scene.background = new THREE.Color(0xbfd1e5)
// scene.fog = new THREE.Fog(0xbfd1e5, 200, 1000)
// 创建相机
camera = new THREE.PerspectiveCamera(
75,
threeCanvas.value.clientWidth / threeCanvas.value.clientHeight,
0.1,
1000
)
camera.position.set(0, 1, 5)
camera.lookAt(0, 0, 0)
// 创建渲染器并添加到DOM中
renderer = new THREE.WebGLRenderer({
preserveDrawingBuffer: true,
antialias: true
})
renderer.setSize(threeCanvas.value.clientWidth, threeCanvas.value.clientHeight)
renderer.setPixelRatio(window.devicePixelRatio)
threeCanvas.value.appendChild(renderer.domElement)
// 控制器
controls = new OrbitControls(camera, renderer.domElement)
controls.enableDamping = true // 启用阻尼,增加平滑度
controls.dampingFactor = 0.05 // 阻尼系数默认为0.25
controls.screenSpacePanning = false // 平移时是否在屏幕空间中移动默认false即在世界空间中移动
controls.minDistance = 1 // 最小距离,默认无限制
controls.maxDistance = 500 // 最大距离,默认无限制
controls.enableZoom = true // 是否可以缩放默认true
controls.zoomSpeed = 1.0 // 缩放速度默认1.0
controls.rotateSpeed = 1.0 // 旋转速度默认1.0
controls.addEventListener('change', render) // 监听控制器变化进行重渲染
// 添加环境光和点光源
const ambientLight = new THREE.AmbientLight(0x404040, 1) // 环境光
scene.add(ambientLight)
const directionalLight = new THREE.DirectionalLight(0xffffff, 1)
scene.add(directionalLight)
const light = new THREE.HemisphereLight(0xffffbb, 0x080820, 1)
scene.add(light)
const pointLight = new THREE.PointLight(0xffffff, 0.8) // 点光源
pointLight.position.set(10, 10, 10)
pointLight.lookAt(0, 0, 0)
scene.add(pointLight)
}
const loadModel = () => {
const loader = new GLTFLoader()
loader.load(
'http://127.0.0.1:8848' + rowData.data, // 模型路径
function (gltf) {
// onLoad回调函数
model = gltf.scene
model.position.set(0, 0, 0)
scene.add(model) // 将模型添加到场景中
},
undefined, // onProgress回调函数可选参数用于处理加载进度等这里不使用所以设置为undefined或提供具体实现函数。
function (error) {
// onError回调函数处理加载错误等。
console.error('An error happened while loading the model', error)
}
)
}
const render = () => {
renderer.render(scene, camera) // 重渲染当前场景和相机状态,通常用于控制器监听事件中调用
}
const animate = () => {
requestAnimationFrame(animate) // 循环调用animate函数实现动画效果。
controls.update() // 更新控制器状态
renderer.render(scene, camera) // 渲染场景和相机。
}
const clangeViewPointHeight = () => {}
const viewPointHeightInput = () => {
@ -57,16 +148,37 @@ const viewPointHeightInput = () => {
}
}
const closeCallBack = (e) => {
renderer.domElement.remove() // 从DOM中移除渲染器。
imageData.value = null
rowData = []
newData.name = ''
//打开系统设置弹框
eventBus.emit('settingPop', true)
}
var posterLoading: any = ref(false)
const setImage = (e) => {
baseDialog.value?.close()
renderer.render(scene, camera) // 确保场景已渲染
const canvas = renderer.domElement
canvas.toBlob((blob) => {
imageData.value = blob
ElMessage.warning('点击保存是会应用当前图片')
}, 'image/png')
}
const close = (e) => {
baseDialog.value?.close()
}
const save = (e) => {
const formData = new FormData()
formData.append('modelId', rowData.id)
newData.name && formData.append('modelName', newData.name)
imageData.value && formData.append('file', imageData.value)
ModelApi.updatePoster(formData).then((res) => {
if (res.code == 0 || res.code == 200) {
ElMessage.success('添加成功')
}
})
baseDialog.value?.close()
}
</script>

View File

@ -0,0 +1,525 @@
<template>
<div class="set_pup">
<el-dialog v-model="isShowPup" :modal="false" draggable>
<template #header>
<div class="set_pup_header">
<div class="system_title">
{{ t('model.title') }}
</div>
</div>
</template>
<div class="set_detail">
<div class="top">
<el-input
v-model="modelName"
class="w-50 m-2"
placeholder="请输入模型名称进行搜索"
:suffix-icon="Search"
/>
<button @click="setting" class="btn">
<svg-icon
name="sys_set"
:size="12"
color="rgba(var(--color-sdk-base-rgb), 1)"
style="margin-right: 5px"
></svg-icon
>默认模型参数设置
</button>
</div>
<div class="content">
<!-- 左侧Tab导航 -->
<el-tabs
v-model="activeTab"
tab-position="left"
class="model-tabs"
@tab-click="handleTabClick"
>
<el-tab-pane
v-for="(category, index) in categories"
:key="index"
:label="category.name"
:name="index.toString()"
>
</el-tab-pane>
</el-tabs>
<div class="model-gallery" ref="galleryRef" @scroll="handleScroll">
<div
v-for="(category, cIndex) in categories"
:key="cIndex"
class="model-section"
:data-category="cIndex"
>
<h2 class="section-title">{{ category.name }}</h2>
<div class="model-grid">
<div v-for="(model, mIndex) in category.models" :key="mIndex" class="model-item">
<div class="imgbg">
<el-image :src="model.thumbnail" fit="cover" class="thumbnail">
<template #error>
<div class="image-error">加载失败</div>
</template>
</el-image>
</div>
<div class="model-name">{{ model.name }}</div>
</div>
</div>
</div>
<div v-show="loading" class="loading-more">加载中...</div>
</div>
</div>
</div>
</el-dialog>
</div>
</template>
<script setup lang="ts">
import { useI18n } from 'vue-i18n'
import { Search } from '@element-plus/icons-vue'
import { ref, onMounted, onBeforeUnmount } from 'vue'
import type { TabsPaneContext } from 'element-plus'
const { t } = useI18n()
const isShowPup = ref(false)
const eventBus: any = inject('bus')
var modelName = ref('')
//tab
interface Model {
id: string
name: string
thumbnail: string
}
interface Category {
id: string
name: string
models: Model[]
}
// 模拟数据
const categories = ref<Category[]>([
{
id: '0',
name: '建筑模型',
models: Array(10)
.fill(0)
.map((_, i) => ({
id: `b${i}`,
name: `建筑_${i}`,
thumbnail: `https://picsum.photos/300/200?random=${i}`
}))
},
{
id: '1',
name: '海军车',
models: Array(10)
.fill(0)
.map((_, i) => ({
id: `b${i}`,
name: `建筑_${i}`,
thumbnail: `https://picsum.photos/300/200?random=${i}`
}))
},
{
id: '2',
name: '装备车',
models: Array(10)
.fill(0)
.map((_, i) => ({
id: `b${i}`,
name: `建筑_${i}`,
thumbnail: `https://picsum.photos/300/200?random=${i}`
}))
}
// 其他分类...
])
const activeTab = ref('0')
const galleryRef = ref<HTMLElement>()
const loading = ref(false)
let observer: IntersectionObserver | null = null
// 处理Tab点击
const handleTabClick = (tab: TabsPaneContext) => {
const section = document.querySelector(`.model-section[data-category="${tab.index}"]`)
section?.scrollIntoView({ behavior: 'smooth' })
}
// 初始化交叉观察器
const initObserver = () => {
observer = new IntersectionObserver(
(entries) => {
entries.forEach((entry) => {
if (entry.isIntersecting) {
const categoryIndex = parseInt(entry.target.getAttribute('data-category') || '0')
activeTab.value = categoryIndex.toString()
// 加载更多检测
if (categoryIndex === categories.value.length - 1 && entry.intersectionRatio > 0.5) {
loadMoreData()
}
}
})
},
{
threshold: [0, 0.5, 1]
}
)
document.querySelectorAll('.model-section').forEach((section) => {
observer?.observe(section)
})
}
// 模拟加载更多数据
const loadMoreData = () => {
if (loading.value) return
loading.value = true
setTimeout(() => {
categories.value.push({
id: `${categories.value.length + 1}`,
name: `新增分类_${categories.value.length}`,
models: Array(10)
.fill(0)
.map((_, i) => ({
id: `n${i}`,
name: `新增模型_${i}`,
thumbnail: `https://picsum.photos/300/200?random=${i + 100}`
}))
})
loading.value = false
}, 1000)
}
// 滚动时检测可见区域
const handleScroll = () => {
if (!galleryRef.value) return
const scrollPosition = galleryRef.value.scrollTop + 100
const sections = Array.from(document.querySelectorAll('.model-section'))
sections.forEach((section, index) => {
const rect = section.getBoundingClientRect()
if (rect.top <= 450 && rect.bottom >= 450) {
activeTab.value = index.toString()
}
})
}
onMounted(() => {
initObserver()
})
onBeforeUnmount(() => {
observer?.disconnect()
})
//end
eventBus.on('openModel', (data) => {
isShowPup.value = data
})
const open = () => {
isShowPup.value = true
}
const close = () => {
isShowPup.value = false
}
const setting = () => {
isShowPup.value = false
eventBus.emit('openModelSetting', true)
}
defineExpose({
open,
close
})
</script>
<style lang="scss" scoped>
.set_pup {
width: 40vw;
height: 50vh;
:deep(.el-dialog) {
background:
linear-gradient(180deg, rgba(0, 255, 255, 0.2) 0%, rgba(0, 255, 255, 0) 100%),
rgba(0, 0, 0, 0.6);
border: 1px solid #00c9ff;
padding-left: 0 !important;
}
:deep(.el-dialog__body) {
padding: 0 !important;
}
:deep(.el-dialog__headerbtn) {
height: 30px;
width: 30px;
border-bottom-left-radius: 80%;
background-color: #008989;
&:hover {
background-color: #00ffff;
.el-dialog__close {
color: rgba(0, 66, 66, 1); // 悬停时改变关闭图标为红色
}
}
}
:deep(.el-dialog__headerbtn .el-dialog__close) {
color: #fff;
}
.set_pup_header {
width: 100%;
height: 100%;
// background-color: #00ffff;
display: flex;
justify-content: center;
align-items: center;
padding-bottom: 20px;
.system_title {
background: url('@/assets/images/titlebg.png') no-repeat;
background-size: 100% 100%;
width: 229px;
height: 34px;
line-height: 34px;
text-align: center;
font-family: 'alimamashuheiti';
font-size: 18px;
color: #fff;
font-weight: 700;
}
}
.set_detail {
box-sizing: border-box;
// height: 50vh;
:deep(
.el-tabs--left .el-tabs__active-bar.is-left,
.el-tabs--left .el-tabs__active-bar.is-right,
.el-tabs--right .el-tabs__active-bar.is-left,
.el-tabs--right .el-tabs__active-bar.is-right
) {
width: 3px;
background: rgba(0, 255, 255, 1);
height: 40px !important;
}
:deep(
.el-tabs--left .el-tabs__nav-wrap.is-left::after,
.el-tabs--left .el-tabs__nav-wrap.is-right::after,
.el-tabs--right .el-tabs__nav-wrap.is-left::after,
.el-tabs--right .el-tabs__nav-wrap.is-right::after
) {
width: 3px;
}
:deep(.el-tabs__nav-wrap::after) {
background: rgba(204, 204, 204, 0.5);
border-radius: 4px;
}
// .switchmy {
// display: flex;
// flex-wrap: wrap;
// margin-top: 15px;
// .center {
// width: 33%;
// margin-bottom: 15px;
// }
// }
:deep(.el-tabs__content) {
height: 50vh;
width: 80%;
padding: 0 10px;
overflow: hidden;
overflow-y: scroll;
color: #fff;
}
// 美化滚动条
:deep(.el-tabs__content::-webkit-scrollbar) {
width: 5px;
height: 5px;
}
:deep(.el-tabs__content::-webkit-scrollbar-thumb) {
background-color: #0ff;
border-radius: 5px;
}
:deep(.el-tabs__content::-webkit-scrollbar-track) {
background-color: rgba(0, 255, 255, 0.2);
}
:deep(.el-tabs__item) {
width: 8vw;
color: #fff !important;
font-size: 1.1rem;
font-family: 黑体;
font-weight: 500;
display: flex;
align-items: center;
justify-content: center;
margin-bottom: 3px;
box-sizing: border-box;
}
:deep(.el-tabs__item:hover) {
background: linear-gradient(
90deg,
rgba(0, 255, 255, 0) 0%,
rgba(0, 255, 255, 0.5) 48.91%,
rgba(0, 255, 255, 0) 100%
);
border: 1px solid;
box-sizing: border-box;
border-image: linear-gradient(
90deg,
rgba(0, 255, 255, 0) 0%,
rgba(0, 255, 255, 1) 55.55%,
rgba(0, 255, 255, 0) 100%
)
1;
}
:deep(.el-tabs__item.is-active) {
background: linear-gradient(
90deg,
rgba(0, 255, 255, 0) 0%,
rgba(0, 255, 255, 0.5) 48.91%,
rgba(0, 255, 255, 0) 100%
) !important;
border: 0.1px solid;
// box-sizing: border-box;
border-image: linear-gradient(
90deg,
rgba(0, 255, 255, 0) 0%,
rgba(0, 255, 255, 1) 55.55%,
rgba(0, 255, 255, 0) 100%
)
1 !important;
}
:deep(.el-tabs__header) {
height: 50vh !important;
width: 8vw;
overflow-y: auto;
overflow-x: hidden;
}
:deep(.el-tabs__nav-next, .el-tabs__nav-prev) {
color: #fff;
}
}
.el-input {
width: 300px;
margin-left: 30px;
--el-input-placeholder-color: rgba(173, 241, 255, 1) !important;
--el-input-placeholder-font-size: 14px;
--el-input-text-color: #fff;
--el-input-border-color: rgba(var(--color-sdk-base-rgb), 0.5) !important;
--el-input-hover-border-color: rgba(var(--color-sdk-base-rgb), 0.5) !important;
--el-input-focus-border-color: rgba(var(--color-sdk-base-rgb), 0.5) !important;
}
::v-deep .el-input__wrapper {
background-color: rgba(0, 0, 0, 0.5) !important;
}
.btn {
float: right;
height: 32px;
line-height: 32px;
background: rgba(var(--color-sdk-base-rgb), 0.2) !important;
border: 1px solid rgba(var(--color-sdk-base-rgb), 1) !important;
border-radius: 4px;
color: rgba(var(--color-sdk-base-rgb), 1) !important;
padding: 0 15px;
}
.content {
margin-top: 20px;
height: 400px;
}
}
</style>
<style scoped>
.model-container {
display: flex;
height: 100vh;
}
.model-tabs {
width: 130px;
height: 100%;
float: left;
}
.model-gallery {
flex: 1;
/* padding: 20px; */
overflow-y: auto;
height: 100%;
width: calc(100% - 130px);
float: left;
}
.model-section {
min-height: 10vh;
margin-bottom: 40px;
}
.model-grid {
display: flex;
flex-wrap: wrap;
/* justify-content: space-around; */
gap: 20px;
}
.model-item {
border-radius: 8px;
overflow: hidden;
/* box-shadow: 0 2px 12px rgba(0, 0, 0, 0.1); */
}
.model-name {
width: 100%;
height: 30px;
line-height: 30px;
text-align: center;
color: rgba(255, 255, 255, 1);
}
.imgbg {
width: 70px;
height: 70px;
background: url('@/assets/images/model-bg.png') no-repeat;
background-size: 100% 100%;
}
.thumbnail {
width: 66px;
height: 66px;
margin-left: 2px;
margin-top: 2px;
}
.loading-more {
text-align: center;
padding: 20px;
}
::v-deep .el-tabs__content {
display: none !important;
}
/* 修改滚动条轨道的颜色 */
::v-deep ::-webkit-scrollbar-track {
background: rgba(var(--color-sdk-base-rgb), 0.1) !important;
}
/* 修改滚动条滑块的样式 */
::v-deep ::-webkit-scrollbar-thumb {
background: rgba(var(--color-sdk-base-rgb), 1) !important;
border-radius: 10px;
}
/* 当滑块被激活(用户点击或拖动时) */
::v-deep ::-webkit-scrollbar-thumb:hover {
background: rgba(var(--color-sdk-base-rgb), 1) !important;
}
</style>
<style>
::-webkit-scrollbar {
width: 5px;
height: 5px;
}
/* 定义背景颜色和圆角 */
::-webkit-scrollbar-thumb {
border-radius: 1em;
background-color: rgba(50, 50, 50, 0.3);
}
/* //定义滚动条轨道 内阴影+圆角 */
::-webkit-scrollbar-track {
border-radius: 1em;
background-color: rgba(50, 50, 50, 0.1);
}
</style>

View File

@ -8,26 +8,413 @@
</div>
</div>
</template>
<div class="set_detail"></div>
<div class="set_detail">
<div class="top">
<el-input
v-model="modelName"
class="w-50 m-2"
placeholder="请输入模型名称进行搜索"
:suffix-icon="Search"
/>
<button @click="setting" class="btn">
<svg-icon
name="sys_set"
class="setIcon"
:size="12"
color="rgba(255,255,255, 1)"
style="margin-right: 5px"
></svg-icon
>默认模型参数设置
</button>
</div>
<div class="content">
<!-- 左侧Tab导航 -->
<div class="treeCon">
<el-tree style="max-width: 600px" :data="typeTreeData" ref="treeRef" node-key="id">
<template #default="{ node, data }">
<!-- <span> {{ node.label }}</span> -->
<span
:class="{
'primary-type': !(node.childNodes.length != 0),
'selected-text': node.id === currentTypeId
}"
@click.stop="toggleExpand(node)"
class="allowDrag"
>
<svg-icon
:name="node.expanded ? 'arrow' : 'more'"
:size="12"
color="rgba(0, 255, 255, 1)"
style="margin-right: 5px; margin-left: 5px"
v-if="node.childNodes.length != 0"
@click.stop="toggleExpand(node)"
></svg-icon>
{{ node.label }}</span
>
</template>
</el-tree>
</div>
<div class="model-gallery" ref="galleryRef">
<div class="model-section">
<!-- <h2 class="section-title">{{ categories[Number(currentTypeId)].name }}</h2> -->
<div class="model-grid">
<div
v-for="(model, mIndex) in categories"
:key="mIndex"
class="model-item"
@click="modelClick(mIndex, model)"
>
<div class="imgbg">
<el-image
:src="'http://127.0.0.1:8848' + model.poster"
fit="cover"
class="thumbnail"
>
<template #error>
<div class="image-error">加载失败</div>
</template>
</el-image>
</div>
<div class="model-name" :class="{ isactive: activeIndex == mIndex }">
{{ model.modelName }}
</div>
</div>
</div>
</div>
<div v-show="loading" class="loading-more">加载中...</div>
</div>
</div>
</div>
</el-dialog>
</div>
</template>
<script setup lang="ts">
import { useI18n } from 'vue-i18n'
import { Search } from '@element-plus/icons-vue'
import { ref, onMounted, onBeforeUnmount } from 'vue'
import { ModelApi } from '@/api/model/index'
import type { TabsPaneContext } from 'element-plus'
import { useTreeNode } from '../tree/hooks/treeNode'
import { TreeApi } from '@/api/tree'
const { t } = useI18n()
const { findParentId, findTreeIndex, cusAddNodes } = useTreeNode()
const isShowPup = ref(false)
const eventBus: any = inject('bus')
var modelName = ref('')
//tab
interface Model {
id: string
name: string
thumbnail: string
}
interface Category {
id: string
modelName: string
poster: string
data: string
}
// 模拟数据
const categories = ref<Category[]>([])
const activeTab = ref('0')
const galleryRef = ref<HTMLElement>()
const loading = ref(false)
let observer: IntersectionObserver | null = null
// 处理Tab点击
const handleTabClick = (tab: TabsPaneContext) => {
const section = document.querySelector(`.model-section[data-category="${tab.index}"]`)
section?.scrollIntoView({ behavior: 'smooth' })
}
// 初始化交叉观察器
const initObserver = () => {
observer = new IntersectionObserver(
(entries) => {
entries.forEach((entry) => {
if (entry.isIntersecting) {
const categoryIndex = parseInt(entry.target.getAttribute('data-category') || '0')
activeTab.value = categoryIndex.toString()
// 加载更多检测
if (categoryIndex === categories.value.length - 1 && entry.intersectionRatio > 0.5) {
loadMoreData()
}
}
})
},
{
threshold: [0, 0.5, 1]
}
)
document.querySelectorAll('.model-section').forEach((section) => {
observer?.observe(section)
})
}
// 模拟加载更多数据
const loadMoreData = () => {
if (loading.value) return
loading.value = true
setTimeout(() => {
categories.value.push({
id: `${categories.value.length + 1}`,
name: `新增分类_${categories.value.length}`,
models: Array(10)
.fill(0)
.map((_, i) => ({
id: `n${i}`,
name: `新增模型_${i}`,
thumbnail: `https://picsum.photos/300/200?random=${i + 100}`
}))
})
loading.value = false
}, 1000)
}
// 滚动时检测可见区域
const handleScroll = () => {
if (!galleryRef.value) return
const scrollPosition = galleryRef.value.scrollTop + 100
const sections = Array.from(document.querySelectorAll('.model-section'))
sections.forEach((section, index) => {
const rect = section.getBoundingClientRect()
if (rect.top <= 450 && rect.bottom >= 450) {
activeTab.value = index.toString()
}
})
}
//-----------tree-----------
const currentTypeId = ref<string>('')
var activeIndex: any = ref(null)
interface TypeNode {
id: string
label: string
parentId: string | null
children?: TypeNode[]
}
const typeTreeData = ref<TypeNode[]>([])
const toggleExpand = (row: any) => {
console.log('opoop')
if (row.childNodes.length != 0) {
// row._expanded = !row._expanded
// 这里需要调用el-table的toggleRowExpansion方法
// 需要通过ref获取table实例
// tableRef.value?.toggleRowExpansion(row, row._expanded)
if (row.expanded) {
row.collapse()
} else {
row.expand()
}
}
getModelListByType(row.data.id)
currentTypeId.value = row.id
// loadModelsByType(row.id)
}
let typeArr = {
point: '点',
line: '线',
area: '面'
}
const modelClick = (index, row) => {
console.log(row, 'dddddd')
activeIndex.value = index
// let selectedNode = window.treeObj.getSelectedNodes()[0]
// if(!selectedNode){
// ElMessage.warning('请选择需要添加的文件夹')
// return
// }
if (!isSetting) {
ElMessage.warning('请先设置模型默认参数')
return
}
isShowPup.value = false
let id = new YJ.Tools().randomString()
let models = new YJ.Obj.BatchModel(
window.earth,
{
id: id,
type: typeArr[isSetting.key],
spacing: isSetting['value'] * 1,
url: 'http://127.0.0.1:8848' + isSetting[row.data]
},
function (data) {
console.log('data,url,source_id', data, row)
// posiArr.forEach((item, index) => {
// let model = new Model(that.sdk, {
// id: 'model' + index,
// show: that.options.show,
// url: that.options.url,
// position: item,
// rotate: that.options.type == '点' ? undefined : { x: 0, y: 0, z: array[1] && (array[1][index] || array[1]) }
// })
// that.pointArr.push(model)
// })
renderModel(data, row)
}
)
}
const renderModel = async (data, model) => {
let selectedNode = window.treeObj.getSelectedNodes()[0]
let z
if (data.positions.length > 0) {
data.positions.forEach(async (position, index) => {
// let source_id = this.$md5(new Date().getTime() + model.model_name+index);
let id = new YJ.Tools().randomString()
if (data.type == '面') {
z = data.rotate.z
} else if (data.type == '线') {
z = data.rotate[index]
}
let option = {
id: id,
position,
name: model.modelName + index,
show: true,
scale: 1,
url: model.data,
maximumScale: 1,
host: 'http://127.0.0.1:8848',
rotate: {
x: 0,
y: 0,
z
}
}
let Model = await new YJ.Obj.Model(window.earth, option)
let DbOption = {
params: option,
id,
sourceName: model.modelName + index,
sourceType: 'model',
parentId: selectedNode
? selectedNode.sourceType == 'directory'
? selectedNode.id
: selectedNode.parentId
: undefined
}
TreeApi.addOtherSource(DbOption)
DbOption.isShow = true
DbOption.params = JSON.stringify(DbOption.params)
cusAddNodes(window.treeObj, DbOption.parentId, [DbOption])
// //鼠标右键点击事件
Model.onRightClick = () => {}
// window._entityMap.set(option.id, Model);
// Model.onClick = () => {
// leftClick(node);
// };
// let detailOption = JSON.parse(JSON.stringify(Model.options));
// detailOption.url = model.model_id + ".glb";
// let node = getNodeData(DbOption, detailOption);
// addSource(node).then((res) => {
// if ([0, 200].includes(res.code)) {
// // cusRenderNode(DbOption) DbOption.p_id
// cusAddNodes(this.treeObj, DbOption.p_id, [node]);
// }
// });
})
}
}
const getModelListByType = (id) => {
let formData = new FormData()
formData.append('modelTypeId', id)
ModelApi.showModelByType(formData).then((res) => {
console.log(res.data, 'cccc')
categories.value = res.data
})
}
const getModelList = async () => {
const res: any = await ModelApi.modelTypeList()
if (res.code == 0 || res.code == 200) {
let data = transformNestedJson(res.data, 'name', 'label')
typeTreeData.value = data
}
}
const transformNestedJson = (data, oldKey, newKey) => {
if (Array.isArray(data)) {
return data.map((item) => transformNestedJson(item, oldKey, newKey))
} else if (data && typeof data === 'object') {
const newObj = {}
for (const key in data) {
// 替换键名
const currentKey = key === oldKey ? newKey : key
// 递归处理子元素
newObj[currentKey] = transformNestedJson(data[key], oldKey, newKey)
}
return newObj
}
return data
}
onMounted(() => {
initObserver()
})
onBeforeUnmount(() => {
observer?.disconnect()
})
//end
eventBus.on('openModel', (data) => {
isShowPup.value = data
if (data) {
getModelList()
getSetting()
}
})
eventBus.on('closeModelSet', (data) => {
isShowPup.value = data
if (data) {
console.log('设置后')
getSetting()
}
})
//查看是否有设置模型设置
var isSetting = null
const getSetting = () => {
ModelApi.getModelSetting().then((res) => {
if (res.code == 0 || res.code == 200) {
isSetting = res.data[res.data.length - 1]
console.log(res, 'resres')
}
})
}
const open = () => {
isShowPup.value = true
}
const close = () => {
isShowPup.value = false
}
const setting = () => {
isShowPup.value = false
eventBus.emit('openModelSetting', (true, isSetting))
}
defineExpose({
open,
@ -73,7 +460,7 @@ defineExpose({
align-items: center;
padding-bottom: 20px;
.system_title {
background: url('../../../../../assets/images/titlebg.png') no-repeat;
background: url('@/assets/images/titlebg.png') no-repeat;
background-size: 100% 100%;
width: 229px;
height: 34px;
@ -200,5 +587,176 @@ defineExpose({
color: #fff;
}
}
.el-input {
width: 300px;
margin-left: 30px;
--el-input-placeholder-color: rgba(173, 241, 255, 1) !important;
--el-input-placeholder-font-size: 14px;
--el-input-text-color: #fff;
--el-input-border-color: rgba(var(--color-sdk-base-rgb), 0.5) !important;
--el-input-hover-border-color: rgba(var(--color-sdk-base-rgb), 0.5) !important;
--el-input-focus-border-color: rgba(var(--color-sdk-base-rgb), 0.5) !important;
}
::v-deep .el-input__wrapper {
background-color: rgba(0, 0, 0, 0.5) !important;
}
.btn {
float: right;
height: 32px;
line-height: 32px;
background: rgba(var(--color-sdk-base-rgb), 0.2) !important;
border: 1px solid rgba(var(--color-sdk-base-rgb), 0.5) !important;
border-radius: 4px;
color: #fff !important;
padding: 0 15px;
}
.btn:hover {
color: rgba(var(--color-sdk-base-rgb), 1) !important;
border: 1px solid rgba(var(--color-sdk-base-rgb), 1) !important;
.setIcon {
color: rgba(var(--color-sdk-base-rgb), 1) !important;
}
}
.content {
margin-top: 20px;
height: 400px;
}
}
</style>
<style lang="scss" scoped>
.model-container {
display: flex;
height: 100vh;
}
.model-tabs {
width: 130px;
height: 100%;
float: left;
}
.treeCon {
width: 140px;
height: 100%;
float: left;
border-right: 1px solid rgba(204, 204, 204, 0.2);
}
.model-gallery {
flex: 1;
/* padding: 20px; */
overflow-y: auto;
height: 100%;
width: calc(100% - 160px);
float: left;
margin-left: 10px;
}
.model-section {
min-height: 10vh;
margin-bottom: 40px;
}
.model-grid {
display: flex;
flex-wrap: wrap;
/* justify-content: space-around; */
gap: 20px;
}
.model-name {
width: 100%;
height: 30px;
line-height: 30px;
text-align: center;
color: rgba(255, 255, 255, 1);
}
.isactive {
color: rgba(var(--color-sdk-base-rgb), 1) !important;
}
.model-item {
border-radius: 8px;
overflow: hidden;
/* box-shadow: 0 2px 12px rgba(0, 0, 0, 0.1); */
}
.model-item:hover {
cursor: pointer !important;
color: rgba(var(--color-sdk-base-rgb), 1) !important;
.model-name {
color: rgba(var(--color-sdk-base-rgb), 1) !important;
}
}
.imgbg {
width: 70px;
height: 70px;
background: url('@/assets/images/model-bg.png') no-repeat;
background-size: 100% 100%;
}
.thumbnail {
width: 66px;
height: 66px;
margin-left: 2px;
margin-top: 2px;
}
.loading-more {
text-align: center;
padding: 20px;
}
::v-deep .el-tabs__content {
display: none !important;
}
/* 修改滚动条轨道的颜色 */
::v-deep ::-webkit-scrollbar-track {
background: rgba(var(--color-sdk-base-rgb), 0.1) !important;
}
/* 修改滚动条滑块的样式 */
::v-deep ::-webkit-scrollbar-thumb {
background: rgba(var(--color-sdk-base-rgb), 1) !important;
border-radius: 10px;
}
/* 当滑块被激活(用户点击或拖动时) */
::v-deep ::-webkit-scrollbar-thumb:hover {
background: rgba(var(--color-sdk-base-rgb), 1) !important;
}
</style>
<style>
::-webkit-scrollbar {
width: 5px;
height: 5px;
}
/* 定义背景颜色和圆角 */
::-webkit-scrollbar-thumb {
border-radius: 1em;
background-color: rgba(50, 50, 50, 0.3);
}
/* //定义滚动条轨道 内阴影+圆角 */
::-webkit-scrollbar-track {
border-radius: 1em;
background-color: rgba(50, 50, 50, 0.1);
}
/* tree */
.el-tree-node__content > .el-tree-node__expand-icon {
display: none !important;
}
.el-tree {
background: transparent !important;
--el-tree-node-hover-bg-color: rgba(var(--color-sdk-base-rgb), 0.2) !important;
color: rgba(255, 255, 255, 1) !important;
/* font-size: 12px !important; */
width: 130px;
float: left;
margin-left: 10px;
}
::v-deep .el-text {
color: rgba(255, 255, 255, 1) !important;
font-size: 12px !important;
}
.selected-text {
color: rgba(var(--color-sdk-base-rgb), 1) !important; /* Element UI主色可自定义 */
}
::v-deep .el-tree--highlight-current .el-tree-node.is-current > .el-tree-node__content {
border-right: 1px solid rgba(var(--color-sdk-base-rgb), 0.2) !important;
}
</style>

View File

@ -0,0 +1,479 @@
<template>
<Dialog
ref="baseDialog"
:title="title + '属性'"
left="180px"
top="100px"
className="polygon"
:closeCallback="closeCallback"
>
<template #content>
<span class="custom-divider"></span>
<div class="div-item">
<div class="row" style="align-items: flex-start">
<div class="col">
<span class="label">名称</span>
<input class="input" maxlength="40" type="text" v-model="entityOptions.name" />
</div>
<div class="col">
<span class="label">面颜色</span>
<div class="color" ref="colorRef"></div>
</div>
</div>
</div>
<div class="div-item">
<div class="row" style="align-items: flex-start">
<div class="col">
<span class="label">经度</span>
<input class="input" maxlength="40" type="text" v-model="entityOptions.name" />
</div>
<div class="col">
<span class="label">最大比例</span>
<div
class="input-number input-number-unit-1 height-box"
:class="{ disabled: heightMode == 2 }"
>
<input
class="input height"
type="number"
title=""
min="-9999999"
max="999999999"
v-model="height"
/>
<span class="unit"></span>
<span class="arrow"></span>
</div>
</div>
</div>
<div class="row" style="align-items: flex-start">
<div class="col">
<span class="label">纬度</span>
<input class="input" maxlength="40" type="text" v-model="entityOptions.name" />
</div>
<div class="col">
<span class="label">最小像素</span>
<div
class="input-number input-number-unit-1 height-box"
:class="{ disabled: heightMode == 2 }"
>
<input
class="input height"
type="number"
title=""
min="-9999999"
max="999999999"
v-model="height"
/>
<span class="unit"></span>
<span class="arrow"></span>
</div>
</div>
</div>
<div class="row" style="align-items: flex-start">
<div class="col">
<span class="label">高度</span>
<div
class="input-number input-number-unit-1 height-box"
:class="{ disabled: heightMode == 2 }"
>
<input
class="input height"
type="number"
title=""
min="-9999999"
max="999999999"
v-model="height"
/>
<span class="unit">m</span>
<span class="arrow"></span>
</div>
</div>
<div class="col">
<span class="label">固定大小</span>
<input class="btn-switch" type="checkbox" v-model="entityOptions.labelShow" />
</div>
</div>
</div>
<div class="div-item">
<div class="row">
<el-tabs v-model="activeName">
<el-tab-pane label="属性信息" name="1">
<attribute :entityOptions="entityOptions"></attribute>
</el-tab-pane>
<el-tab-pane label="方向信息" name="2">
<div class="row">
<div class="row">
<p class="lable-left-line">旋转</p>
</div>
<div class="row">
<div class="col">
<span class="label">x </span>
<input
type="range"
min="0"
max="1"
step="0.01"
v-model="entityOptions.transparency"
/>
<div
class="input-number input-number-unit-1"
style="width: auto; margin-left: 10px"
>
<input
style="width: 100px"
type="number"
title=""
min="0"
max="360"
v-model="entityOptions.transparency"
/>
<span class="unit">°</span>
<span class="arrow"></span>
</div>
</div>
</div>
<div class="row">
<div class="col">
<span class="label">y </span>
<input
type="range"
min="0"
max="1"
step="0.01"
v-model="entityOptions.transparency"
/>
<div
class="input-number input-number-unit-1"
style="width: auto; margin-left: 10px"
>
<input
style="width: 100px"
type="number"
title=""
min="0"
max="360"
v-model="entityOptions.transparency"
/>
<span class="unit">°</span>
<span class="arrow"></span>
</div>
</div>
</div>
<div class="row">
<div class="col">
<span class="label">z </span>
<input
type="range"
min="0"
max="1"
step="0.01"
v-model="entityOptions.transparency"
/>
<div
class="input-number input-number-unit-1"
style="width: auto; margin-left: 10px"
>
<input
style="width: 100px"
type="number"
title=""
min="0"
max="360"
v-model="entityOptions.transparency"
/>
<span class="unit">°</span>
<span class="arrow"></span>
</div>
</div>
</div>
</div>
<div class="row">
<div class="row">
<div width="46%">
<p class="lable-left-line">缩放</p>
<el-checkbox
v-model="entityOptions.transparency"
label="是否等比例缩放"
size="large"
/>
</div>
</div>
</div>
</el-tab-pane>
<el-tab-pane label="标准风格" name="3">
<div class="row">
<!-- <div class="col">
<span class="label">面颜色</span>
<div class="color" ref="colorRef"></div>
</div> -->
<div class="col">
<span class="label">描边颜色</span>
<div class="lineColor" ref="lineColorRef"></div>
</div>
<div class="col">
<span class="label">描边宽度</span>
<div class="input-number input-number-unit-2">
<input
class="input"
type="number"
title=""
min="0"
max="99"
v-model="entityOptions.lineWidth"
/>
<span class="unit">px</span>
<span class="arrow"></span>
</div>
</div>
</div>
</el-tab-pane>
<el-tab-pane label="标签风格" name="4">
<labelStyle :type="title" :entityOptions="entityOptions"></labelStyle>
</el-tab-pane>
</el-tabs>
</div>
</div>
<span class="custom-divider"></span>
</template>
<template #footer>
<div style="position: absolute; left: 24px; display: flex">
<button @click="nodeEdit">
<svg class="icon-edit">
<use xlink:href="#yj-icon-edit"></use></svg
>二次编辑
</button>
<button style="margin-left: 10px" @click="translate">
<svg class="icon-py">
<use xlink:href="#yj-icon-py"></use></svg
>平移
</button>
</div>
<button @click="remove">删除</button>
<button @click="confirm">确定</button>
<button @click="close">关闭</button>
</template>
</Dialog>
</template>
<script setup lang="ts">
import { ref } from 'vue'
import { inject } from 'vue'
import { TreeApi } from '@/api/tree'
import Dialog from '@/components/dialog/baseDialog.vue'
import attribute from './attribute.vue'
import labelStyle from './labelStyle.vue'
import { useTreeNode } from '@/views/components/tree/hooks/treeNode'
const { cusUpdateNode } = useTreeNode()
const title = ref('面')
const baseDialog: any = ref(null)
const eventBus: any = inject('bus')
const options = ref({})
const colorRef = ref(null)
const lineColorRef = ref(null)
eventBus.on('openPolygonEdit', () => {
baseDialog.value?.open()
})
const area = ref(0)
const areaUnit = ref('m2')
const height = ref(10)
const heightModeData = ref([
{
name: '海拔高度',
value: '海拔高度',
key: 0
},
{
name: '相对地表',
value: '相对地表',
key: 1
},
{
name: '依附模型',
value: '依附模型',
key: 2
}
])
const activeName = ref('1')
const activeTd = ref({
index: -1,
name: ''
})
const positions = ref([])
const heightMode = ref(0)
const entityOptions: any = ref({})
let originalOptions: any
let that: any
const open = async (id: any, type: any) => {
if (type && type === 'rectangle') {
title.value = '矩形'
} else if (type && type === 'rendezvous') {
title.value = '集结地'
}
that = window.earth.entityMap.get(id)
originalOptions = structuredClone(that.options)
entityOptions.value = that
heightMode.value = entityOptions.value.heightMode
area.value = entityOptions.value.areaByMeter
positions.value = structuredClone(that.options.positions)
that.areaChangeCallBack = () => {
switch (areaUnit.value) {
case 'm2': //平方米
area.value = entityOptions.value.areaByMeter
break
case 'km2': //平方千米
area.value = Number((entityOptions.value.areaByMeter / 1000000).toFixed(8))
break
case 'mu': //亩
area.value = Number((entityOptions.value.areaByMeter / 666.6666667).toFixed(4))
break
case 'ha': //公顷
area.value = Number((entityOptions.value.areaByMeter / 10000).toFixed(6))
break
default:
area.value = entityOptions.value.areaByMeter
}
}
heightModeChange(heightMode.value)
baseDialog.value?.open()
await nextTick()
let colorPicker = new window.YJColorPicker({
el: colorRef.value,
size: 'mini', //颜色box类型
alpha: true, //是否开启透明度
defaultColor: entityOptions.value.color,
disabled: false, //是否禁止打开颜色选择器
openPickerAni: 'opacity', //打开颜色选择器动画
sure: (color) => {
entityOptions.value.color = color
}, //点击确认按钮事件回调
clear: () => {
entityOptions.value.color = 'rgba(255,255,255,1)'
} //点击清空按钮事件回调
})
let linecolorPicker = new window.YJColorPicker({
el: lineColorRef.value,
size: 'mini', //颜色box类型
alpha: true, //是否开启透明度
defaultColor: entityOptions.value.lineColor,
disabled: false, //是否禁止打开颜色选择器
openPickerAni: 'opacity', //打开颜色选择器动画
sure: (color) => {
entityOptions.value.lineColor = color
}, //点击确认按钮事件回调
clear: () => {
entityOptions.value.lineColor = 'rgba(255,255,255,1)'
} //点击清空按钮事件回调
})
}
const heightModeChange = (val) => {
that.heightMode = heightMode.value
}
const heightConfirm = () => {
if (entityOptions.value.operate.positionEditing) {
that.positionEditing = false
entityOptions.value.height = Number(
(entityOptions.value.height + Number(height.value)).toFixed(2)
)
} else {
that.closeNodeEdit(this)
that.heightMode = that.heightMode
setTimeout(() => {
entityOptions.value.height = Number(
(entityOptions.value.height + Number(height.value)).toFixed(2)
)
}, 100)
}
}
const inputDblclick = async (event, i, anme) => {
if (heightMode.value == 2) {
return
}
activeTd.value = {
index: i,
name: anme
}
await nextTick()
let inputElm = event.target.getElementsByClassName('input')[0]
if (inputElm) {
inputElm.focus()
}
}
const inputBlurCallBack = (event, i, name, digit = 2) => {
activeTd.value = {
index: -1,
name: ''
}
}
const translate = () => {
that.openPositionEditing(() => {
entityOptions.value.options.positions = structuredClone(that.options.positions)
})
}
const closeCallback = () => {
entityOptions.value.originalOptions = structuredClone(originalOptions)
that.positionEditing = false
entityOptions.value.closeNodeEdit()
entityOptions.value.reset()
eventBus.emit('destroyComponent')
}
const nodeEdit = () => {
that.nodeEdit((e, positions, areaByMeter) => {
console.log('positions', positions)
entityOptions.value.options.positions = structuredClone(positions)
})
}
const confirm = () => {
originalOptions = structuredClone(that.options)
baseDialog.value?.close()
let params = structuredClone(that.options)
delete params.host
let params2 = {
id: params.id,
sourceName: params.name,
params: params,
isShow: params.show ? 1 : 0
}
TreeApi.updateDirectoryInfo(params2)
cusUpdateNode({ id: params.id, sourceName: params.name, params: JSON.stringify(params) })
}
const close = () => {
baseDialog.value?.close()
}
const remove = () => {
that.remove()
close()
}
watch(
() => areaUnit.value,
(val) => {
if ((entityOptions.value.areaByMeter || entityOptions.value.areaByMeter == 0) && that) {
that.areaChangeCallBack()
}
},
{ immediate: true }
)
defineExpose({
open,
close
})
</script>
<style scoped lang="scss"></style>

View File

@ -0,0 +1,159 @@
<template>
<Dialog
ref="baseDialog"
title="默认模型参数设置"
left="180px"
top="100px"
:closeCallback="closeCallBack"
>
<template #content>
<span class="custom-divider"></span>
<div class="div-item">
<div class="row">
<div class="col">
<span class="label">添加方式</span>
<div class="input-number input-number-unit-1">
<el-select
v-model="type"
class="m-2"
placeholder="请选择添加方式"
style="width: 240px"
@change="typeChange"
>
<el-option
v-for="item in options"
:key="item.value"
:label="item.label"
:value="item.value"
/>
</el-select>
</div>
</div>
<div class="col" v-show="showDis">
<span class="label">间距</span>
<div class="input-number input-number-unit-1">
<input
id="modelDistance"
type="number"
title=""
min="0"
max="999999"
step="0.1"
placeholder="请输入数值"
v-model="distance"
@input="viewPointHeightInput"
@change="clangeViewPointHeight"
/>
<span class="unit">m</span>
<span class="arrow"></span>
</div>
</div>
</div>
</div>
</template>
<template #footer>
<button @click="save">保存</button>
<button @click="close">关闭</button>
</template>
</Dialog>
</template>
<script setup lang="ts">
import { ref, reactive } from 'vue'
import { inject } from 'vue'
import { ModelApi } from '@/api/model/index'
import Dialog from '@/components/dialog/baseDialog.vue'
import { ElMessage, ElMessageBox } from 'element-plus'
const baseDialog: any = ref(null)
const eventBus: any = inject('bus')
const viewPointHeight: any = ref(1.8)
var visibility: any = reactive([])
var showDis: any = ref(false)
var type: any = ref('point')
var distance: any = ref(null)
const options = [
{
value: 'point',
label: '点'
},
{
value: 'line',
label: '线'
},
{
value: 'area',
label: '面'
}
]
eventBus.on('openModelSetting', (show,data) => {
baseDialog.value?.open()
show&& (type.value = show.key)
show&& (distance.value = show.value * 1)
typeChange()
console.log(show,'data')
})
const typeChange = () => {
switch (type.value) {
case 'point':
showDis.value = false
break
case 'line':
showDis.value = true
break
case 'area':
showDis.value = true
break
}
}
const clangeViewPointHeight = () => {}
const viewPointHeightInput = () => {
let dom: any = document.getElementById('modelDistance')
if (viewPointHeight.value < dom.min * 1) {
viewPointHeight.value = dom.min * 1
} else if (viewPointHeight.value > dom.max * 1) {
viewPointHeight.value = dom.max * 1
}
}
const closeCallBack = (e) => {
type.value = 'point'
distance.value = null
eventBus.emit('closeModelSet', true)
}
const close = () => {
baseDialog.value?.close()
}
const save = () => {
ModelApi.modelSetting({
key: type.value,
value: showDis.value ? distance.value : ''
}).then((res) => {
if (res.code == 0 || res.code == 200) {
ElMessage.success('设置成功')
baseDialog.value?.close()
}
})
}
</script>
<style scoped lang="scss">
::v-deep .el-select__wrapper {
background-color: rgba(0, 0, 0, 0.5) !important;
box-shadow: 0 0 0 1px rgba(var(--color-sdk-base-rgb), 0.5) inset !important;
}
::v-deep .el-select {
--el-select-input-focus-border-color: rgba(var(--color-sdk-base-rgb), 0.5) !important;
--el-select-text-color: #fff;
--el-select-border-color: rgba(var(--color-sdk-base-rgb), 0.5) !important;
--el-select-hover-border-color: rgba(var(--color-sdk-base-rgb), 0.5) !important;
--el-select-multiple-input-color: #fff !important;
}
::v-deep .el-select__placeholder {
color: rgba(204, 204, 204, 1) !important;
}
</style>

View File

@ -10,8 +10,14 @@
<firstMenu class="absolute zIndex9" ref="firstMenuRef"></firstMenu>
<!--底部菜单-->
<bottomMenu class="absolute zIndex9" ref="bottomMenuRef"></bottomMenu>
<input type="file" id="fileInputlink" style="display: none" multiple accept=".jpeg,.png,.jpg,.mp4,.pdf"
@input="uploadFile" />
<input
type="file"
id="fileInputlink"
style="display: none"
multiple
accept=".jpeg,.png,.jpg,.mp4,.pdf"
@input="uploadFile"
/>
<!-- 多点视线分析 -->
<Visibility ref="visibility"></Visibility>
@ -37,6 +43,8 @@
<!-- 图标列表 -->
<selectImg ref="selectImgRef"></selectImg>
<modelSetting ref="modelsetting"></modelSetting>
<modelObject ref="modelobject"></modelObject>
</template>
<script setup lang="ts">
@ -97,6 +105,8 @@ import imagePop from '../components/propertyBox/imagePop.vue'
import model from '../components/propertyBox/model.vue'
import waterSurface from '../components/propertyBox/waterSurface.vue'
import { addMapSource } from '../../common/addMapSource'
import modelSetting from '../components/propertyBox/modelSetting.vue'
import modelObject from '../components/propertyBox/modelObject.vue'
import { GisApi } from '@/api/gisApi'
@ -219,6 +229,8 @@ eventBus.on('openDialog', async (sourceType: any, id: any) => {
break
case 'scanStereoscopic':
currentComponent.value = radarScanStereoscopic
case 'model':
currentComponent.value = modelObject
await nextTick()
dynamicComponentRef.value?.open(id)
break
@ -268,8 +280,7 @@ eventBus.on('destroyComponent', (id) => {
dynamicComponentRef.value?.close()
currentComponent.value = undefined
}
}
else {
} else {
currentComponent.value = undefined
}
})
@ -298,7 +309,7 @@ const createEarth = async () => {
// })
break
case 'attribute':
let node = window.treeObj.getNodeByParam("id", object.id, null);
let node = window.treeObj.getNodeByParam('id', object.id, null)
rightMenus.edit.callback(eventBus, node)
break
}
@ -361,9 +372,9 @@ const getStatus = (time) => {
const currentTimestamp = Date.now()
if (timestamp > currentTimestamp) {
(window as any).checkAuthIsValid = true
;(window as any).checkAuthIsValid = true
} else {
(window as any).checkAuthIsValid = false
;(window as any).checkAuthIsValid = false
}
}