开发摄像头组件

dev_0.0.1_xq
谢庆 2 weeks ago
parent f5e692c0cb
commit fac2e58868

@ -1,9 +1,64 @@
import http from '@/api'; import http from '@/api';
import { ADMIN_MODULE } from '@/api/helper/prefix'; import { ADMIN_MODULE } from '@/api/helper/prefix';
import type { IPage } from '@/api/types'; import type { IPage } from '@/api/types';
import type { TeacherStatisticsQuery, TeacherStatisticsRow, TeacherStatisticsForm } from '@/api/types/teacher/teacherStatistics'; import type { TeacherStatisticsQuery, TeacherStatisticsRow, TeacherStatisticsForm ,FileStorageQue,FileEntityDTO} from '@/api/types/teacher/teacherStatistics';
import type { UploadRawFile } from 'element-plus/es/components/upload/src/upload'; import type { UploadRawFile } from 'element-plus/es/components/upload/src/upload';
import type { AxiosRequestConfig } from 'axios'; import type { AxiosRequestConfig } from 'axios';
export const File_Url = 'http://127.0.0.1:9991/api'
//http://localhost:8080/data/model/getModelData
export const getModelData = (params: {menuType:string}) => {
return http.post(ADMIN_MODULE + `/data/model/getModelData`, params);
};
/**
*
* @param params
* @returns
*/
export const saveOrUpdateModelData = (params: any) => {
return http.post(ADMIN_MODULE + `/data/model/saveOrUpdate/modelData`, params);
};
/**
*
* @param params
* @returns
*/
export const deleteFileByIdApi = (params: { id: string}) => {
return http.get(ADMIN_MODULE + `/fileStorage/deleteFileById`, params);
};
/**
*
* @param params
* @returns
*/
export const editFileNameByIdApi = (params: { id: string; fileName: string }) => {
return http.post(ADMIN_MODULE + `/fileStorage/editFileNameById`, params);
};
/**
*
* @param url
* @returns
*/
export const getFileObj = (url: string|undefined) => {
if(url === undefined) return '';
return File_Url+ADMIN_MODULE + url;
};
/**
*
* @param params
* @returns {*}
*/
export const fileStorageFileListApi = (params: FileStorageQue) => {
return http.post<IPage<FileEntityDTO>>(ADMIN_MODULE + `/fileStorage/file/list`, params);
};
/** /**
* *
* @param params * @param params

@ -1,5 +1,23 @@
import type { IPageQuery } from '@/api/types'; import type { IPageQuery } from '@/api/types';
// 文件实体DTO
export type FileEntityDTO = {
id?: string;
fileName?: string;
addTime?: number;
filePath?: string;
};
// 查询条件
export type FileStorageQue = IPageQuery & {
id?: string;
fileName?: string;
startTime?: number;
endTime?: number;
};
// 查询条件 // 查询条件
export type TeacherStatisticsQuery = IPageQuery & { export type TeacherStatisticsQuery = IPageQuery & {
year?: string; year?: string;

@ -8,7 +8,6 @@
@on-save-click="onSaveClick" @on-save-click="onSaveClick"
@on-thumbnail-click="onThumbnailClick" @on-thumbnail-click="onThumbnailClick"
></mt-edit> ></mt-edit>
</div> </div>
</template> </template>
@ -16,18 +15,19 @@
import type { IExportJson } from '@/views/teacher/teacherStatistics/components/mt-edit/components/types'; import type { IExportJson } from '@/views/teacher/teacherStatistics/components/mt-edit/components/types';
import { useGenThumbnail } from '@/views/teacher/teacherStatistics/components/mt-edit/composables/thumbnail'; import { useGenThumbnail } from '@/views/teacher/teacherStatistics/components/mt-edit/composables/thumbnail';
import MtEdit from '@/views/teacher/teacherStatistics/components/mt-edit'; import MtEdit from '@/views/teacher/teacherStatistics/components/mt-edit';
import {getCurrentInstance, onBeforeUnmount} from 'vue'; import { getCurrentInstance, onBeforeUnmount } from 'vue';
import {leftAsideStore} from "@/views/teacher/teacherStatistics/components/mt-edit/store/left-aside"; import { leftAsideStore } from '@/views/teacher/teacherStatistics/components/mt-edit/store/left-aside';
// xq // xq
import vueSignalGaudy from '@/views/teacher/teacherStatistics/components/vue-xq-test/vue-signal-gaudy.vue'; import vueSignalGaudy from '@/views/teacher/teacherStatistics/components/vue-xq-test/vue-signal-gaudy.vue';
import VueImg from '@/views/teacher/teacherStatistics/components/vue-xq-test/vue-img.vue'; import VueImg from '@/views/teacher/teacherStatistics/components/vue-xq-test/vue-img.vue';
// F:\vue\workspace\Real-time-monitoring-frontend\src\views\teacher\teacherStatistics\components\vue-xq-test\vue-camera.vue
import VueCamera from '@/views/teacher/teacherStatistics/components/vue-xq-test/vue-camera.vue';
const instance = getCurrentInstance(); const instance = getCurrentInstance();
instance?.appContext.app.component('vue-my-signal-gaudy', vueSignalGaudy); instance?.appContext.app.component('vue-my-signal-gaudy', vueSignalGaudy);
instance?.appContext.app.component('vue-my-img', VueImg); instance?.appContext.app.component('vue-my-img', VueImg);
instance?.appContext.app.component('vue-my-camera', VueCamera);
leftAsideStore.registerConfig('工作组件', [ leftAsideStore.registerConfig('工作组件', [
{ {
@ -85,6 +85,19 @@ leftAsideStore.registerConfig('工作组件', [
} }
} }
}, },
{
id: 'vue-my-camera',
title: '监控组件',
type: 'vue',
thumbnail: '/svgs/image.svg',
props: {
moduleId: {
type: 'upload',
val: '--',
title: '绑定'
}
}
}
]); ]);
const electrical_modules_files = import.meta.glob('./assets/svgs/electrical/**.svg', { const electrical_modules_files = import.meta.glob('./assets/svgs/electrical/**.svg', {
@ -121,8 +134,7 @@ for (const key in electrical_stroke_modules_files) {
id: name, id: name,
title: name, title: name,
type: 'svg', type: 'svg',
thumbnail: thumbnail: 'data:image/svg+xml;utf8,' + encodeURIComponent(electrical_stroke_modules_files[key]),
'data:image/svg+xml;utf8,' + encodeURIComponent(electrical_stroke_modules_files[key]),
svg: electrical_stroke_modules_files[key], svg: electrical_stroke_modules_files[key],
props: { props: {
stroke: { stroke: {
@ -134,7 +146,6 @@ for (const key in electrical_stroke_modules_files) {
}); });
} }
const onPreviewClick = (exportJson: IExportJson) => { const onPreviewClick = (exportJson: IExportJson) => {
sessionStorage.setItem('exportJson', JSON.stringify(exportJson)); sessionStorage.setItem('exportJson', JSON.stringify(exportJson));
// const routeUrl = router.resolve({ // const routeUrl = router.resolve({
@ -153,6 +164,4 @@ const onThumbnailClick = () => {
}; };
</script> </script>
<style scoped> <style scoped></style>
</style>

@ -48,7 +48,7 @@
<el-upload <el-upload
accept="image/*" accept="image/*"
:action="BASE_URL + '/fileStorage/saveFile'" :action="File_Url+'/admin' + '/fileStorage/saveFile'"
:on-success="saveImageSuccess" :on-success="saveImageSuccess"
:show-file-list="false" :show-file-list="false"
style="width: 100%; padding: 0 10px" style="width: 100%; padding: 0 10px"
@ -77,31 +77,11 @@
<el-table-column v-if="isEdit || isDelete || isBinding" fixed="right" label="操作"> <el-table-column v-if="isEdit || isDelete || isBinding" fixed="right" label="操作">
<template #default="scope"> <template #default="scope">
<el-button <el-button v-if="isEdit" link type="primary" size="small" @click="transmitFileEditName(scope.row.name, scope.row.id)">
v-if="isEdit"
link
type="primary"
size="small"
@click="transmitFileEditName(scope.row.name, scope.row.id)"
>
编辑 编辑
</el-button> </el-button>
<el-button <el-button v-if="isDelete" link type="danger" size="small" @click="deleteFileById(scope.row.id)"></el-button>
v-if="isDelete" <el-button v-if="isBinding" link type="success" size="small" @click="bindingImg(scope.row)"></el-button>
link
type="danger"
size="small"
@click="deleteFileById(scope.row.id)"
>删除</el-button
>
<el-button
v-if="isBinding"
link
type="success"
size="small"
@click="bindingImg(scope.row)"
>绑定</el-button
>
</template> </template>
</el-table-column> </el-table-column>
</el-table> </el-table>
@ -138,19 +118,24 @@
</el-dialog> </el-dialog>
<!-- 独立的图片预览浮层 --> <!-- 独立的图片预览浮层 -->
<el-image-viewer <el-image-viewer v-if="previewVisible" :url-list="[currentPreviewUrl]" :z-index="3000" @close="previewVisible = false" />
v-if="previewVisible"
:url-list="[currentPreviewUrl]"
:z-index="3000"
@close="previewVisible = false"
/>
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import { reactive, computed, ref, onMounted } from 'vue'; import { reactive, computed, ref, onMounted } from 'vue';
import { ElDialog, ElButton, ElMessage } from 'element-plus'; import { ElDialog, ElButton, ElMessage } from 'element-plus';
import {BASE_URL, modelApi} from "@/views/teacher/teacherStatistics/utils/request"; import { ADMIN_MODULE } from '@/api/helper/prefix';
import {formatFilePath, formatTimestamp} from "@/views/teacher/teacherStatistics/utils/dataFormatter"; import { BASE_URL, modelApi } from '@/views/teacher/teacherStatistics/utils/request';
import { formatFilePath, formatTimestamp } from '@/views/teacher/teacherStatistics/utils/dataFormatter';
// src/api/modules/teacher/teacherStatistics.ts
import { fileStorageFileListApi,getFileObj,File_Url,editFileNameByIdApi,deleteFileByIdApi } from '@/api/modules/teacher/teacherStatistics';
import type {
TeacherStatisticsQuery,
TeacherStatisticsRow,
TeacherStatisticsForm,
FileStorageQue,
FileEntityDTO
} from '@/api/types/teacher/teacherStatistics';
const props = defineProps({ const props = defineProps({
dialogImageVisible: { dialogImageVisible: {
@ -185,7 +170,7 @@ const bindingImg = (obj: any) => {
const visible = computed({ const visible = computed({
get: () => props.dialogImageVisible, get: () => props.dialogImageVisible,
set: (val) => emit('updateDialogImageVisible', val) set: val => emit('updateDialogImageVisible', val)
}); });
const form = reactive({ const form = reactive({
@ -212,10 +197,8 @@ async function fileEditName() {
id: editFileId.value, id: editFileId.value,
fileName: editFileName.value fileName: editFileName.value
}; };
const response = await modelApi.fileStorage_editFileNameById_post(endJson); const response = await editFileNameByIdApi(endJson);
response.code == 200 response.code == '0000' ? ElMessage.success(response.data||'') : ElMessage.error(response.message + ',请联系管理员');
? ElMessage.success(response.data)
: ElMessage.error(response.message + ',请联系管理员');
loadFileList(); loadFileList();
//end //end
@ -229,10 +212,8 @@ async function deleteFileById(fileId: string) {
let endJson = { let endJson = {
id: fileId id: fileId
}; };
const response = await modelApi.fileStorage_deleteFileById_get(endJson); const response = await deleteFileByIdApi(endJson);
response.code == 200 response.code == '0000' ? ElMessage.success(response.data||'') : ElMessage.error(response.message + ',请联系管理员');
? ElMessage.success(response.data)
: ElMessage.error(response.message + ',请联系管理员');
loadFileList(); loadFileList();
} }
@ -279,16 +260,16 @@ function showPreview(url: string) {
// //
async function loadFileList() { async function loadFileList() {
elDialogLoading.value = true; elDialogLoading.value = true;
let endJson = { let endJson: FileStorageQue = {
pageNum: currentPage.value, page: currentPage.value || 1,
pageSize: pageSize.value, limit: pageSize.value || 4,
id: form.fileId, id: form.fileId || undefined,
fileName: form.fileName, fileName: form.fileName || undefined,
startTime: form.fileDate ? new Date(form.fileDate[0]).getTime() : null, startTime: form.fileDate ? new Date(form.fileDate[0]).getTime() : undefined,
endTime: form.fileDate ? new Date(form.fileDate[1]).getTime() : null endTime: form.fileDate ? new Date(form.fileDate[1]).getTime() : undefined
}; };
console.log('endJson:', endJson); // const response = await modelApi.fileStorage_file_list_post(endJson);
const response = await modelApi.fileStorage_file_list_post(endJson); const response = await fileStorageFileListApi(endJson);
tableData.value = []; tableData.value = [];
if (!response.data) { if (!response.data) {
elDialogLoading.value = false; elDialogLoading.value = false;
@ -296,13 +277,13 @@ async function loadFileList() {
} }
pageTotal.value = response.data.total; pageTotal.value = response.data.total;
currentPage.value = response.data.current; currentPage.value = response.data.current;
pageSize.value = response.data.size; pageSize.value = response.data.limit;
response.data.list.forEach((value: any) => { response.data.rows.forEach((value: FileEntityDTO) => {
let date = formatTimestamp(value.addTime); let date = formatTimestamp(value.addTime);
let id = value.id; let id = value.id;
let name = value.fileName; let name = value.fileName;
let imgUrl = formatFilePath(BASE_URL, value.filePath); let imgUrl = getFileObj(value.filePath);
tableData.value.push({ date, id, name, imgUrl }); tableData.value.push({ date, id, name, imgUrl });
}); });
elDialogLoading.value = false; elDialogLoading.value = false;

@ -6,7 +6,8 @@
height="45px" height="45px"
class="dark:bg-myDarkBgColor cb-border p-0 select-none" class="dark:bg-myDarkBgColor cb-border p-0 select-none"
@mousedown="mainPanelRef?.stopListenerKeyDown()" @mousedown="mainPanelRef?.stopListenerKeyDown()"
> <!-- 头部2 --> >
<!-- 头部2 -->
<header-panel <header-panel
v-model:leftAside="aside_state.left_show" v-model:leftAside="aside_state.left_show"
v-model:rightAside="aside_state.right_show" v-model:rightAside="aside_state.right_show"
@ -55,10 +56,7 @@
</el-aside> </el-aside>
<!-- 中间2 渲染组件 --> <!-- 中间2 渲染组件 -->
<el-main <el-main class="dark:bg-myMainDarkBgColor" @mousedown="mainPanelRef?.beginListenerKeyDown()">
class="dark:bg-myMainDarkBgColor"
@mousedown="mainPanelRef?.beginListenerKeyDown()"
>
<main-panel <main-panel
ref="mainPanelRef" ref="mainPanelRef"
:group-enabled="header_group_enabled" :group-enabled="header_group_enabled"
@ -90,11 +88,7 @@
</el-container> </el-container>
<!-- 导入数据 --> <!-- 导入数据 -->
<el-dialog <el-dialog v-model="import_visible" title="数据导入" @close="mainPanelRef?.beginListenerKeyDown()">
v-model="import_visible"
title="数据导入"
@close="mainPanelRef?.beginListenerKeyDown()"
>
<import-json ref="importJsonRef"></import-json> <import-json ref="importJsonRef"></import-json>
<template #footer> <template #footer>
<el-button type="primary" @click="onImportYes"></el-button> <el-button type="primary" @click="onImportYes"></el-button>
@ -102,11 +96,7 @@
</el-dialog> </el-dialog>
<!-- 导出数据 --> <!-- 导出数据 -->
<el-dialog <el-dialog v-model="export_visible" title="数据导出" @close="mainPanelRef?.beginListenerKeyDown()">
v-model="export_visible"
title="数据导出"
@close="mainPanelRef?.beginListenerKeyDown()"
>
<export-json <export-json
:done-json="objectDeepClone(globalStore.done_json)" :done-json="objectDeepClone(globalStore.done_json)"
:canvas-cfg="globalStore.canvasCfg" :canvas-cfg="globalStore.canvasCfg"
@ -123,12 +113,20 @@
></done-tree> ></done-tree>
</el-drawer> </el-drawer>
<!-- xxxx -->
<el-dialog v-model="drawerVisible" title="数据模型" width="35%">
<span>注意保存后会覆盖之前的模型文件</span>
<template #footer>
<div class="dialog-footer">
<el-button @click="doSaveModel"></el-button>
<el-button @click="loadModel" type="primary"> 加载</el-button>
</div>
</template>
</el-dialog>
<!-- end -->
<!-- 数据模型 --> <!-- 数据模型 -->
<el-drawer v-model="drawerVisible" :modal="false" title="数据模型文件121" modal-penetrable> <!-- <el-drawer v-model="drawerVisible" :modal="false" title="数据模型文件121" modal-penetrable>
<!-- <button @click="testData"></button> -->
<!-- <el-button @click="resetSelection"></el-button> -->
<!-- <el-button @click="getNode"></el-button> -->
<!-- @check="handleCheck" -->
<el-tree-v2 <el-tree-v2
style="max-width: 600px; height: 100px" style="max-width: 600px; height: 100px"
:data="treeData" :data="treeData"
@ -146,21 +144,17 @@
<FolderOpened v-else /> <FolderOpened v-else />
</el-icon> </el-icon>
<span class="prefix" :class="{ 'is-leaf': node.isLeaf }">{{ node.label }}</span> <span class="prefix" :class="{ 'is-leaf': node.isLeaf }">{{ node.label }}</span>
<!-- <span>{{ node.label }}</span> -->
</template> </template>
</el-tree-v2> </el-tree-v2>
<template #footer> <template #footer>
<div class="drawer-footer"> <div class="drawer-footer">
<el-button >保存模型11</el-button> <el-button @click="doSaveModel">11</el-button>
<el-button v-loading.fullscreen.lock="fullscreenLoading" <el-button @click="loadModel" v-loading.fullscreen.lock="fullscreenLoading">加载模型2</el-button>
>加载模型2</el-button <el-button> 移除模型 </el-button>
>
<!-- <el-button @click="coverModel"> </el-button> -->
<el-button > 移除模型 </el-button>
</div> </div>
</template> </template>
</el-drawer> </el-drawer> -->
<!-- 数据保存弹框 --> <!-- 数据保存弹框 -->
<el-dialog v-model="dialogFormVisible" title="数据模型保存" width="500"> <el-dialog v-model="dialogFormVisible" title="数据模型保存" width="500">
@ -168,12 +162,7 @@
<template #footer> <template #footer>
<div class="dialog-footer"> <div class="dialog-footer">
<el-button @click="dialogFormVisible = false">取消</el-button> <el-button @click="dialogFormVisible = false">取消</el-button>
<el-button <el-button type="primary" v-loading.fullscreen.lock="fullscreenLoading"> 确定 </el-button>
type="primary"
v-loading.fullscreen.lock="fullscreenLoading"
>
确定
</el-button>
</div> </div>
</template> </template>
</el-dialog> </el-dialog>
@ -198,19 +187,9 @@ import LeftAside from '@/views/teacher/teacherStatistics/components/mt-edit/comp
import MainPanel from '@/views/teacher/teacherStatistics/components/mt-edit/components/layout/main-panel/index.vue'; import MainPanel from '@/views/teacher/teacherStatistics/components/mt-edit/components/layout/main-panel/index.vue';
import RightAside from '@/views/teacher/teacherStatistics/components/mt-edit/components/layout/right-aside/index.vue'; import RightAside from '@/views/teacher/teacherStatistics/components/mt-edit/components/layout/right-aside/index.vue';
import { leftAsideStore } from '@/views/teacher/teacherStatistics/components/mt-edit/store/left-aside'; import { leftAsideStore } from '@/views/teacher/teacherStatistics/components/mt-edit/store/left-aside';
import { import { ElContainer, ElHeader, ElAside, ElMain, ElDialog, ElDrawer, ElButton, ElMessage, ElTreeV2 } from 'element-plus';
ElContainer,
ElHeader,
ElAside,
ElMain,
ElDialog,
ElDrawer,
ElButton,
ElMessage,
ElTreeV2
} from 'element-plus';
import { globalStore } from '@/views/teacher/teacherStatistics/components/mt-edit/store/global'; import { globalStore } from '@/views/teacher/teacherStatistics/components/mt-edit/store/global';
import {computed, reactive, ref, useSlots, onMounted, getCurrentInstance, onBeforeUnmount} from 'vue'; import { computed, reactive, ref, useSlots, onMounted, getCurrentInstance, onBeforeUnmount } from 'vue';
import DoneTree from '@/views/teacher/teacherStatistics/components/mt-edit/components/done-tree/index.vue'; import DoneTree from '@/views/teacher/teacherStatistics/components/mt-edit/components/done-tree/index.vue';
import { cacheStore } from './store/cache'; import { cacheStore } from './store/cache';
import ExportJson from '@/views/teacher/teacherStatistics/components/mt-edit/components/export-json/index.vue'; import ExportJson from '@/views/teacher/teacherStatistics/components/mt-edit/components/export-json/index.vue';
@ -219,6 +198,7 @@ import { objectDeepClone } from './utils';
import { genExportJson, useExportJsonToDoneJson } from './composables'; import { genExportJson, useExportJsonToDoneJson } from './composables';
import type { IExportJson } from './components/types'; import type { IExportJson } from './components/types';
import imageModel from '@/views/teacher/teacherStatistics/components/mt-edit/components/layout/base-panel/imageModel.vue'; import imageModel from '@/views/teacher/teacherStatistics/components/mt-edit/components/layout/base-panel/imageModel.vue';
import { saveOrUpdateModelData, getModelData } from '@/api/modules/teacher/teacherStatistics';
interface Tree { interface Tree {
id: string; id: string;
@ -286,15 +266,13 @@ const header_group_enabled = computed(() => {
}); });
const header_un_group_enabled = computed(() => { const header_un_group_enabled = computed(() => {
if (globalStore.selected_items_id.length === 1) { if (globalStore.selected_items_id.length === 1) {
const item = globalStore.done_json.find((f) => f.id === globalStore.selected_items_id[0]); const item = globalStore.done_json.find(f => f.id === globalStore.selected_items_id[0]);
return item?.type === 'group'; return item?.type === 'group';
} }
return false; return false;
}); });
const header_align_enabled = computed(() => { const header_align_enabled = computed(() => {
const selected_items = globalStore.done_json.filter( const selected_items = globalStore.done_json.filter(f => globalStore.selected_items_id.includes(f.id) && f.type !== 'sys-line');
(f) => globalStore.selected_items_id.includes(f.id) && f.type !== 'sys-line'
);
return selected_items.length > 1; return selected_items.length > 1;
}); });
const import_visible = ref(false); const import_visible = ref(false);
@ -317,21 +295,13 @@ const onTreeUpdateSelectedItemsId = (id: string) => {
globalStore.setSingleSelect(id); globalStore.setSingleSelect(id);
}; };
const onDoneTreeUpdateSelectedIdHide = (id: string) => { const onDoneTreeUpdateSelectedIdHide = (id: string) => {
const item = globalStore.done_json.find((f) => f.id === id); const item = globalStore.done_json.find(f => f.id === id);
if (item) { if (item) {
item.hide = !item.hide; item.hide = !item.hide;
} }
}; };
const onAlignSelected = ( const onAlignSelected = (
type: type: 'left' | 'horizontally' | 'right' | 'top' | 'vertically' | 'bottom' | 'horizontal-distribution' | 'vertical-distribution'
| 'left'
| 'horizontally'
| 'right'
| 'top'
| 'vertically'
| 'bottom'
| 'horizontal-distribution'
| 'vertical-distribution'
) => { ) => {
mainPanelRef.value?.onAlignSelected(type); mainPanelRef.value?.onAlignSelected(type);
}; };
@ -357,22 +327,137 @@ const onImportYes = async () => {
} }
}; };
//
async function doSaveModel() {
fullscreenLoading.value = true;
const { exportJson } = genExportJson(globalStore.canvasCfg, globalStore.gridCfg, globalStore.done_json);
let endJson = {
menuType: menuType.value,
canvasCfg: exportJson.canvasCfg,
gridCfg: exportJson.gridCfg,
json: exportJson.json
};
try {
const response = await saveOrUpdateModelData(endJson);
// const response = await modelApi.saveOrUpdate_modelData_post(endJson);
if (response.code == '0000') {
fullscreenLoading.value = false;
dialogFormVisible.value = false;
ElMessage.success('数据模型保存成功');
drawerVisible.value = false;
} else {
fullscreenLoading.value = false;
dialogFormVisible.value = false;
console.error('保存失败:', response.code, response.message);
//
console.error('服务器返回错误信息:', response.message);
ElMessage.error(`数据模型保存失败: ${response.code} - ${response.message}`);
drawerVisible.value = false;
}
} catch (error) {
drawerVisible.value = false;
fullscreenLoading.value = false;
dialogFormVisible.value = false;
console.error('请求错误:', error);
ElMessage.error('网络请求失败');
}
}
function saveModel() {
let nodes = treeRef.value?.getCheckedNodes();
console.log(nodes?.length);
if (nodes?.length === 0) {
ElMessage.warning('请至少选择一个模型文件');
} else {
dialogFormVisible.value = true;
fileName.value = nodes?.[0].label;
menuType.value = nodes?.[0].id;
}
}
async function loadModel() {
fullscreenLoading.value = false;
let endJson = {
menuType: ''
};
console.log('请求数据类型:', typeof JSON.stringify(endJson));
try {
const response = await getModelData(endJson);
// const response = await modelApi.model_getModelData_post(endJson);
console.log('响应状态:', typeof response);
if (response.code == '0000') {
ElMessage.success('请求数据成功');
const result = response.data;
const { canvasCfg, gridCfg, importDoneJson } = useExportJsonToDoneJson(result as IExportJson);
globalStore.canvasCfg = canvasCfg;
globalStore.gridCfg = gridCfg;
globalStore.setGlobalStoreDoneJson(importDoneJson);
fullscreenLoading.value = false;
drawerVisible.value = false;
} else {
fullscreenLoading.value = false;
ElMessage.error(`数据模型加载失败: ${response.code} - ${response.message}`);
drawerVisible.value = false;
}
} catch (error) {
fullscreenLoading.value = false;
console.error('请求错误:', error);
ElMessage.error('网络请求失败');
drawerVisible.value = false;
}
}
// async function loadModel() {
// fullscreenLoading.value = false;
// let nodes = treeRef.value?.getCheckedNodes();
// console.log(nodes?.length);
// if (nodes?.length != 0) {
// // if (nodes?.length === 0) {
// fullscreenLoading.value = false;
// ElMessage.warning('');
// } else {
// let endJson = {
// menuType: ''
// // menuType: nodes?.[0].id
// };
// console.log('', typeof JSON.stringify(endJson));
// try {
// const response = await getModelData(endJson);
// // const response = await modelApi.model_getModelData_post(endJson);
// console.log('', typeof response);
// if (response.code == '0000') {
// ElMessage.success('');
// const result = response.data;
// const { canvasCfg, gridCfg, importDoneJson } = useExportJsonToDoneJson(result as IExportJson);
// globalStore.canvasCfg = canvasCfg;
// globalStore.gridCfg = gridCfg;
// globalStore.setGlobalStoreDoneJson(importDoneJson);
// fullscreenLoading.value = false;
// } else {
// fullscreenLoading.value = false;
// ElMessage.error(`: ${response.code} - ${response.message}`);
// }
// } catch (error) {
// fullscreenLoading.value = false;
// console.error('', error);
// ElMessage.error('');
// }
// }
// }
const onPreviewClick = () => { const onPreviewClick = () => {
// json // json
const { exportJson } = genExportJson( const { exportJson } = genExportJson(globalStore.canvasCfg, globalStore.gridCfg, globalStore.done_json);
globalStore.canvasCfg,
globalStore.gridCfg,
globalStore.done_json
);
emits('onPreviewClick', exportJson); emits('onPreviewClick', exportJson);
}; };
const onSaveClick = () => { const onSaveClick = () => {
// json // json
const { exportJson } = genExportJson( const { exportJson } = genExportJson(globalStore.canvasCfg, globalStore.gridCfg, globalStore.done_json);
globalStore.canvasCfg,
globalStore.gridCfg,
globalStore.done_json
);
emits('onSaveClick', exportJson); emits('onSaveClick', exportJson);
}; };
const onThumbnailClick = () => { const onThumbnailClick = () => {

@ -0,0 +1,54 @@
<template>
<div class="main-img" @click="customDraggingVisible = true">
<el-button type="primary" :icon="VideoCamera" class="full-size-btn" />
</div>
<teleport to="body">
<el-dialog
v-model="customDraggingVisible"
class="custom-dragging-style"
title="摄像机画面"
width="500"
:modal="false"
draggable
>
<span>这是摄像机画面尝试拖动它看看效果</span>
<template #footer>
<div class="dialog-footer">
<el-button @click="customDraggingVisible = false">Cancel</el-button>
<el-button type="primary" @click="customDraggingVisible = false"> Confirm </el-button>
</div>
</template>
</el-dialog>
</teleport>
</template>
<script setup lang="ts">
import { ref } from 'vue';
import { VideoCamera } from '@element-plus/icons-vue';
const customDraggingVisible = ref(false);
</script>
<style scoped>
:global(.custom-dragging-style.is-dragging) {
border: 2px dashed var(--el-color-primary);
opacity: 0.65;
}
.main-img {
width: 100%;
height: 100%;
display: flex;
justify-content: center;
align-items: center;
}
.full-size-btn {
width: 100%;
height: 100%;
padding: 0;
min-width: 100%;
min-height: 100%;
}
</style>

@ -1,70 +0,0 @@
<template>
<el-upload class="upload-demo" action="http://localhost:8080/data/model/saveImage">
<el-button type="primary">Click to upload</el-button>
</el-upload>
<button @click="getImages"></button>
<!-- style="max-width: 300px" -->
<div class="img-container">
<img
draggable="false"
style="max-width: 300px"
class="display-img"
:src="displayImageUrl"
alt="Display Image"
/>
</div>
</template>
<script lang="ts" setup>
import { ref, onUnmounted } from 'vue';
import { ElMessage, ElMessageBox } from 'element-plus';
import type { UploadProps, UploadUserFile } from 'element-plus';
let displayImageUrl = ref<string>('');
async function getImages() {
console.log('获取图片');
const response = await fetch('http://localhost:8080/data/model/getImageUrl', {
method: 'GET',
headers: {
'Content-Type': 'application/json',
Accept: '*/*'
}
});
// JSONfile
const data = await response.json();
// data.filebase64
if (data.file && typeof data.file === 'string') {
// base64使
if (data.file.startsWith('data:image')) {
displayImageUrl.value = data.file;
} else {
// base64
displayImageUrl.value = `data:image/png;base64,${data.file}`;
}
}
console.log('获取图片数据:', displayImageUrl.value);
}
// URL
onUnmounted(() => {
if (displayImageUrl.value) {
URL.revokeObjectURL(displayImageUrl.value);
}
});
const fileList = ref<UploadUserFile[]>([
{
name: 'element-plus-logo.svg',
url: 'https://element-plus.org/images/element-plus-logo.svg'
},
{
name: 'element-plus-logo2.svg',
url: 'https://element-plus.org/images/element-plus-logo.svg'
}
]);
</script>

@ -20,6 +20,7 @@ import { ref, onMounted, onUnmounted, watch } from 'vue';
import emitter from '@/views/teacher/teacherStatistics/utils/emitter'; import emitter from '@/views/teacher/teacherStatistics/utils/emitter';
import { Picture } from '@element-plus/icons-vue'; import { Picture } from '@element-plus/icons-vue';
import { modelApi, BASE_URL } from '@/views/teacher/teacherStatistics/utils/request'; import { modelApi, BASE_URL } from '@/views/teacher/teacherStatistics/utils/request';
import { fileStorageFileListApi,getFileObj,File_Url,editFileNameByIdApi,deleteFileByIdApi } from '@/api/modules/teacher/teacherStatistics';
import { formatFilePath } from '@/views/teacher/teacherStatistics/utils/dataFormatter'; import { formatFilePath } from '@/views/teacher/teacherStatistics/utils/dataFormatter';
// const displayImageUrl = ref<string | null>(null); // const displayImageUrl = ref<string | null>(null);
@ -53,12 +54,13 @@ async function loadFileList(fileId: string) {
let endJson = { let endJson = {
id: fileId id: fileId
}; };
const response = await modelApi.fileStorage_file_list_post(endJson); // const response = await modelApi.fileStorage_file_list_post(endJson);
const response = await fileStorageFileListApi(endJson);
if (!response.data) { if (!response.data) {
return; return;
} }
response.data.list.forEach((value: any) => { response.data.rows.forEach((value: any) => {
imgUrl.value = formatFilePath(BASE_URL, value.filePath); imgUrl.value = formatFilePath(File_Url+'/admin', value.filePath);
}); });
console.log('imgUrl:', imgUrl); console.log('imgUrl:', imgUrl);
} }

@ -1,312 +0,0 @@
<template>
<div
id="plane"
:style="{
left: infoRef.style.left + 'px',
top: infoRef.style.top + 'px',
display: infoRef.style.display
}"
>
<p>机柜名称{{ infoRef.curCabinet.name }}</p>
<p>机柜温度{{ infoRef.curCabinet.temperature }}°</p>
<p>使用情况{{ infoRef.curCabinet.count }}/{{ infoRef.curCabinet.capacity }}</p>
</div>
<div ref="containerRef" class="container"></div>
</template>
<script setup lang="ts">
import {
WebGLRenderer,
PerspectiveCamera,
Scene,
Color,
Texture,
Mesh,
TextureLoader,
MeshBasicMaterial,
Raycaster,
Vector2,
MeshStandardMaterial,
Object3D,
RepeatWrapping
} from 'three';
import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls';
import { ref, reactive, onMounted, onUnmounted, watchEffect } from 'vue';
import TWEEN from '@tweenjs/tween.js';
import { GLTFLoader } from 'three/examples/jsm/loaders/GLTFLoader';
const containerRef = ref<HTMLDivElement>();
const rendererRef = ref<WebGLRenderer>();
const cameraRef = ref<PerspectiveCamera>();
const controlRef = ref<OrbitControls>();
const maps: Map<string, Texture> = new Map();
const cabinets: Mesh[] = [];
const scene = new Scene();
const raycaster = new Raycaster();
const pointer = new Vector2();
const curCabinetRef = ref<Mesh | null>();
const infoRef = reactive({
style: { left: 0, top: 0, display: 'none' },
curCabinet: {
name: 'Loading……',
temperature: 0,
capacity: 0,
count: 0
}
});
//
function initCamera() {
const container = containerRef.value!;
cameraRef.value = new PerspectiveCamera(
45,
container.clientWidth / container.clientHeight, // 使
0.1,
1000
);
const start = { x: 0, y: 0, z: 0 };
new TWEEN.Tween(start)
.to({ x: 0, y: 10, z: 15 }, 800)
.easing(TWEEN.Easing.Quadratic.InOut)
.onUpdate(() => {
const { x, y, z } = start;
cameraRef.value?.position.set(x, y, z);
})
.start();
controlRef.value = new OrbitControls(cameraRef.value, rendererRef.value?.domElement);
controlRef.value.autoRotate = false;
controlRef.value.autoRotateSpeed = 1;
controlRef.value.dampingFactor = 0.2;
controlRef.value.rotateSpeed = 0.3; // 1.0
controlRef.value.enableDamping = true;
controlRef.value.enableRotate = true;
controlRef.value.minDistance = 10;
controlRef.value.maxPolarAngle = Math.PI * 0.5;
}
//
function initRenderer() {
const container = containerRef.value!;
rendererRef.value = new WebGLRenderer({
antialias: true, // 齿
alpha: true //
});
rendererRef.value.setClearColor(new Color('#212d36'));
rendererRef.value.setSize(container.clientWidth, container.clientHeight); // 使
rendererRef.value.shadowMap.enabled = true;
}
//
function render() {
requestAnimationFrame(render);
if (cameraRef.value) {
rendererRef.value?.render(scene, cameraRef.value);
}
controlRef.value?.update();
TWEEN.update();
}
//
function crtTexture(imgName: string) {
let curTexture = maps.get(imgName);
if (!curTexture) {
curTexture = new TextureLoader().load('/models/' + imgName);
curTexture.flipY = false;
curTexture.wrapS = RepeatWrapping;
curTexture.wrapT = RepeatWrapping;
maps.set(imgName, curTexture);
}
return curTexture;
}
//
function changeMat(obj: Mesh, map: Texture | null, color: Color) {
if (map) {
obj.material = new MeshBasicMaterial({
map: crtTexture(map.name)
});
} else {
obj.material = new MeshBasicMaterial({ color });
}
}
//
function onMouseOverCabinet({ name }: { name: string }) {
infoRef.style.display = 'block';
infoRef.curCabinet.name = name;
infoRef.curCabinet.temperature = Math.round(Math.random() * 100);
const capacity = Math.ceil(200 * Math.random());
infoRef.curCabinet.capacity = capacity;
infoRef.curCabinet.count = capacity - 5;
}
function onMouseOutCabinet() {
infoRef.style.display = 'none';
}
//
function onMouseMoveCabinet(left: number, top: number) {
infoRef.style.left = left;
infoRef.style.top = top;
}
//
// function selectCabinet(x: number, y: number) {
// const container = rendererRef.value!.domElement;
// const { width, height } = container;
// pointer.set((x / width) * 2 - 1, -(y / height) * 2 + 1);
// if (cameraRef.value) {
// raycaster.setFromCamera(pointer, cameraRef.value);
// }
// const intersect = raycaster.intersectObjects(cabinets)[0];
// let intersectObj = intersect ? (intersect.object as Mesh) : null;
// if (curCabinetRef.value && curCabinetRef.value !== intersectObj) {
// const material = curCabinetRef.value.material as MeshBasicMaterial;
// material.setValues({
// map: maps.get('cabinet.jpg')
// });
// }
// if (intersectObj) {
// onMouseMoveCabinet(x, y);
// if (intersectObj !== curCabinetRef.value) {
// curCabinetRef.value = intersectObj;
// const material = intersectObj.material as MeshBasicMaterial;
// material.setValues({
// map: maps.get('cabinet-hover.jpg')
// });
// onMouseOverCabinet(intersectObj);
// }
// } else if (curCabinetRef.value) {
// curCabinetRef.value = null;
// onMouseOutCabinet();
// }
// }
function selectCabinet(x: number, y: number) {
const container = rendererRef.value!.domElement;
const { width, height } = container;
// Y
pointer.set((x / width) * 2 - 1, -(y / height) * 2 + 1);
if (cameraRef.value) {
raycaster.setFromCamera(pointer, cameraRef.value);
}
const intersect = raycaster.intersectObjects(cabinets)[0];
let intersectObj = intersect ? (intersect.object as Mesh) : null;
if (curCabinetRef.value && curCabinetRef.value !== intersectObj) {
const material = curCabinetRef.value.material as MeshBasicMaterial;
material.setValues({
map: maps.get('cabinet.jpg')
});
}
if (intersectObj) {
onMouseMoveCabinet(x, y);
if (intersectObj !== curCabinetRef.value) {
curCabinetRef.value = intersectObj;
const material = intersectObj.material as MeshBasicMaterial;
material.setValues({
map: maps.get('cabinet-hover.jpg')
});
onMouseOverCabinet(intersectObj);
}
} else if (curCabinetRef.value) {
curCabinetRef.value = null;
onMouseOutCabinet();
}
}
//
// function mouseMove({ clientX, clientY }: { clientX: number; clientY: number }) {
// console.log('mouseMove', clientX, clientY);
// selectCabinet(clientX, clientY);
// }
function mouseMove(event: MouseEvent) {
const container = containerRef.value!;
const rect = container.getBoundingClientRect(); //
const x = event.clientX - rect.left; // X
const y = event.clientY - rect.top; // Y
selectCabinet(x, y);
}
//
let resizeObserver: ResizeObserver | null = null;
onMounted(() => {
crtTexture('cabinet-hover.jpg');
containerRef.value!.onmousemove = mouseMove;
const loader = new GLTFLoader();
loader.load('/models/machineRoom.gltf', (gltf: any) => {
gltf.scene.children.forEach((obj: Object3D) => {
console.log('obj', obj);
if (obj instanceof Mesh) {
const { map, color } = obj.material as MeshStandardMaterial;
changeMat(obj, map, color);
if (obj.name.includes('cabinet')) {
cabinets.push(obj);
}
}
});
scene.add(...gltf.scene.children);
});
initRenderer();
initCamera();
render();
//
resizeObserver = new ResizeObserver(() => {
const container = containerRef.value!;
cameraRef.value!.aspect = container.clientWidth / container.clientHeight;
cameraRef.value!.updateProjectionMatrix();
rendererRef.value!.setSize(container.clientWidth, container.clientHeight);
});
resizeObserver.observe(containerRef.value!);
});
onUnmounted(() => {
// ResizeObserver
if (resizeObserver) {
resizeObserver.disconnect();
resizeObserver = null;
}
});
watchEffect(() => {
if (rendererRef.value) {
containerRef.value?.append(rendererRef.value.domElement);
}
});
</script>
<style scoped>
.container {
position: relative; /* 确保容器定位正确 */
width: 100%;
height: 100%;
overflow: hidden; /* 防止滚动条干扰 */
}
#plane {
position: absolute;
top: 0;
left: 0;
background-color: rgba(0, 0, 0, 0.5);
color: #b1acac;
padding: 0 18px;
transform: translate(12px, -100%);
display: block;
z-index: 9999; /* 提高层级,确保在最上层 */
}
</style>

@ -1,243 +0,0 @@
<template>
<div ref="containerRef" class="container"></div>
</template>
<script lang="ts" setup>
import { onMounted, onUnmounted, ref } from 'vue';
import {
AxesHelper,
Color,
PerspectiveCamera,
Scene,
WebGLRenderer,
AmbientLight,
DirectionalLight,
PointLight,
MeshStandardMaterial,
Vector2,
Raycaster,
CanvasTexture,
SpriteMaterial,
Sprite,
Box3,
Vector3,
Mesh
} from 'three';
import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls';
import { GLTFLoader } from 'three/examples/jsm/loaders/GLTFLoader';
import { DRACOLoader } from 'three/examples/jsm/loaders/DRACOLoader';
const containerRef = ref<HTMLDivElement>();
//
const scene = new Scene();
//
const camera = new PerspectiveCamera(
45,
containerRef.value?.clientWidth! / containerRef.value?.clientHeight!,
0.1,
1000
);
camera.position.set(-10, 10, 20);
camera.lookAt(scene.position);
//
const renderer = new WebGLRenderer({
antialias: true, // 齿
alpha: true, //
preserveDrawingBuffer: true //
});
renderer.setClearColor(new Color('#131519'));
renderer.setSize(containerRef.value?.clientWidth!, containerRef.value?.clientHeight!);
renderer.shadowMap.enabled = true;
renderer.setPixelRatio(window.devicePixelRatio || 1); //
//
const orbitControls = new OrbitControls(camera, renderer.domElement);
orbitControls.autoRotate = false;
orbitControls.autoRotateSpeed = 5;
orbitControls.dampingFactor = 0.1;
orbitControls.enableDamping = true;
orbitControls.enableRotate = true;
orbitControls.minDistance = 10;
orbitControls.maxPolarAngle = Math.PI * 0.5;
//
scene.add(new AxesHelper(20));
//
const ambientLight = new AmbientLight('#ffffff', 1.2); // 0.6 1.0
scene.add(ambientLight);
//
const directionalLight = new DirectionalLight('#ffffff', 1.4); // 0.2 0.8
scene.add(directionalLight);
directionalLight.position.set(20, 20, 10);
// //
const pointLight = new PointLight('#ffffff', 1.4, 1800); // 0.5 1.0
scene.add(pointLight);
pointLight.position.set(0, 40, 0);
//
const canvas = document.createElement('canvas');
canvas.width = 250;
canvas.height = 250;
const ctx = canvas.getContext('2d');
if (ctx) {
ctx.fillStyle = 'rgba(0,0,0,.7)';
ctx.fillRect(0, 0, 200, 100);
ctx.fillStyle = 'white';
ctx.font = '21px 黑体';
ctx.textAlign = 'center';
ctx.textBaseline = 'middle';
ctx.fillText('遥测100111', 100, 30);
ctx.fillText('温度25℃', 90, 65);
}
const scaleFactor = 5;
const texture = new CanvasTexture(canvas);
const material = new SpriteMaterial({ map: texture });
const sprite1 = new Sprite(material);
sprite1.scale.set(scaleFactor, scaleFactor, scaleFactor);
sprite1.position.set(-9.644502679789724, 5.815449539931501 + 0.5, 21.66345220492572);
function animate() {
requestAnimationFrame(animate);
orbitControls.update();
renderer.render(scene, camera);
}
const arr = [
'Layer0073',
'Layer0043',
'Layer0097',
'Layer0098',
'Layer0096',
'Layer0099',
'Layer00100',
'Layer0095',
'Layer0072'
];
let pitchObj = ref<Mesh | null>(null);
let pitchMaterial = ref<any>(null);
// ResizeObserver
let resizeObserver: ResizeObserver | null = null;
onMounted(() => {
const container = containerRef.value!;
container.appendChild(renderer.domElement);
//
camera.aspect = container.clientWidth / container.clientHeight;
camera.updateProjectionMatrix();
renderer.setSize(container.clientWidth, container.clientHeight);
//
resizeObserver = new ResizeObserver(() => {
camera.aspect = container.clientWidth / container.clientHeight;
camera.updateProjectionMatrix();
renderer.setSize(container.clientWidth, container.clientHeight);
});
resizeObserver.observe(container);
const loader = new GLTFLoader();
const dracoLoader = new DRACOLoader();
loader.setDRACOLoader(dracoLoader);
loader.load('/models/pipeline/new_model.glb', (gltf: any) => {
gltf.scene.children.forEach((child: any) => {
console.log(child.name, child);
//
if (child.name === '平面') {
child.position.set(child.position.x, child.position.y - 45, child.position.z);
child.material = new MeshStandardMaterial({
color: new Color('#FFFFFF')
});
}
if (child.children && child.children.length > 0) {
child.traverse((child1: any) => {
if (child1.isMesh && arr.includes(child1.name)) {
const box = new Box3().setFromObject(child1);
const worldPosition = new Vector3();
child1.getWorldPosition(worldPosition);
const sprite = new Sprite(material.clone());
sprite.scale.set(scaleFactor, scaleFactor, scaleFactor);
sprite.position.set(
worldPosition.x * 0.05,
(worldPosition.y + box.min.y / 4) * 0.05,
worldPosition.z * 0.05
);
scene.add(sprite);
}
});
}
});
gltf.scene.scale.set(0.05, 0.05, 0.05);
scene.add(gltf.scene);
});
// 线
const raycaster = new Raycaster();
const mouse = new Vector2();
//
renderer.domElement.addEventListener('mousedown', (event: MouseEvent) => {
const rect = container.getBoundingClientRect();
const x = event.clientX - rect.left;
const y = event.clientY - rect.top;
mouse.x = (x / container.clientWidth) * 2 - 1;
mouse.y = -(y / container.clientHeight) * 2 + 1;
raycaster.setFromCamera(mouse, camera);
const intersects = raycaster.intersectObjects(scene.children, true);
console.log(intersects);
if (intersects.length > 0) {
console.log('点击了物体', intersects[0].object.name);
// Mesh
if (arr.includes(intersects[0].object.name) && (intersects[0].object as any).isMesh) {
const clickedMesh = intersects[0].object as Mesh;
if (pitchObj.value) pitchObj.value.material = pitchMaterial.value;
pitchObj.value = clickedMesh;
//
if (clickedMesh.material) {
pitchMaterial.value = Array.isArray(clickedMesh.material)
? clickedMesh.material[0].clone()
: clickedMesh.material.clone();
}
console.log(intersects[0]);
clickedMesh.material = new MeshStandardMaterial({
color: new Color('#973939')
});
} else {
if (pitchObj.value && pitchMaterial.value) pitchObj.value.material = pitchMaterial.value;
}
}
});
animate();
});
onUnmounted(() => {
if (resizeObserver) {
resizeObserver.disconnect();
resizeObserver = null;
}
});
</script>
<style scoped>
.container {
width: 100%;
height: 100%;
}
</style>

@ -1,473 +0,0 @@
<template>
<div class="scene-wrapper">
<div ref="containerRef" class="container" />
<!-- 固定位置的控制按钮 -->
<div class="control-buttons">
<el-button type="primary" @click="displayModel('group_sd')" :icon="Bell" />
<el-button type="danger" @click="displayModel('group_voice')" :icon="MagicStick" />
<el-button type="warning" @click="displayModel('group_manual')" :icon="Star" />
<el-button type="warning" @click="displayModel('all')" :icon="Compass" />
<el-button type="warning" @click="camera.position.set(-50, 45, -140)" :icon="Refresh" />
</div>
</div>
</template>
<script lang="ts" setup>
import {
Check,
Delete,
Edit,
Message,
Search,
Star,
Bell,
MagicStick,
Refresh,
Compass
} from '@element-plus/icons-vue';
import { onMounted, onUnmounted, ref, nextTick } from 'vue';
import {
AxesHelper,
Color,
PerspectiveCamera,
Scene,
WebGLRenderer,
AmbientLight,
DirectionalLight,
PointLight,
MeshStandardMaterial,
Vector2,
Raycaster,
CanvasTexture,
SpriteMaterial,
Sprite,
Box3,
Vector3,
HemisphereLight,
Mesh
} from 'three';
import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls';
import { GLTFLoader } from 'three/examples/jsm/loaders/GLTFLoader';
import { DRACOLoader } from 'three/examples/jsm/loaders/DRACOLoader';
const containerRef = ref<HTMLDivElement>();
const conditionTagRef = ref<any>();
const showTooltip = ref(false);
//
const scene = new Scene();
//
const camera = new PerspectiveCamera(
45,
containerRef.value?.clientWidth! / containerRef.value?.clientHeight!,
0.1,
1000
);
camera.position.set(-50, 45, -140);
camera.lookAt(scene.position);
const axesHelper = new AxesHelper(100);
scene.add(axesHelper);
//
const renderer = new WebGLRenderer({
antialias: true, // 齿
alpha: true, //
preserveDrawingBuffer: true //
});
renderer.setClearColor(new Color('#131519'));
renderer.setSize(containerRef.value?.clientWidth!, containerRef.value?.clientHeight!);
renderer.shadowMap.enabled = true;
renderer.setPixelRatio(window.devicePixelRatio || 1); //
//
const orbitControls = new OrbitControls(camera, renderer.domElement);
orbitControls.autoRotate = false;
orbitControls.autoRotateSpeed = 5;
orbitControls.dampingFactor = 0.1;
orbitControls.enableDamping = true;
orbitControls.enableRotate = true;
orbitControls.minDistance = 10;
orbitControls.maxPolarAngle = Math.PI * 0.5;
//
scene.add(new AxesHelper(20));
//
const ambientLight = new AmbientLight('#ffffff', 1.8); // 0.6 1.0
scene.add(ambientLight);
//
const directionalLight = new DirectionalLight('#ffffff', 1.8); // 0.2 0.8
scene.add(directionalLight);
directionalLight.position.set(20, 20, 10);
// //
const pointLight = new PointLight('#ffffff', 1.8, 1800); // 0.5 1.0
scene.add(pointLight);
pointLight.position.set(0, 40, 0);
//
// const ctx = canvas.getContext('2d');
// if (ctx) {
// ctx.fillStyle = 'rgba(0,0,0,.7)';
// ctx.fillRect(0, 0, 200, 100);
// ctx.fillStyle = 'white';
// ctx.font = '21px ';
// ctx.textAlign = 'center';
// ctx.textBaseline = 'middle';
// }
// const scaleFactor = 5;
// const texture = new CanvasTexture(canvas);
// const material = new SpriteMaterial({ map: texture });
// const sprite1 = new Sprite(material);
// sprite1.scale.set(scaleFactor, scaleFactor, scaleFactor);
// sprite1.position.set(-9.644502679789724, 5.815449539931501 + 0.5, 21.66345220492572);
function animate() {
requestAnimationFrame(animate);
orbitControls.update();
renderer.render(scene, camera);
}
let pitchObj = ref<any>();
let pitchMaterial = ref<any>();
// ResizeObserver
let resizeObserver: ResizeObserver | null = null;
let groupSdArrs: any[] = [];
let groupVoiceArrs: any[] = [];
let groupManualArrs: any[] = [];
function displayModel(modelName: string) {
if (modelName === 'group_sd') {
groupSdArrs.forEach((obj) => {
obj.visible = true;
});
groupVoiceArrs.forEach((obj) => {
obj.visible = false;
});
groupManualArrs.forEach((obj) => {
obj.visible = false;
});
} else if (modelName === 'group_voice') {
groupVoiceArrs.forEach((obj) => {
obj.visible = true;
});
groupSdArrs.forEach((obj) => {
obj.visible = false;
});
groupManualArrs.forEach((obj) => {
obj.visible = false;
});
} else if (modelName === 'group_manual') {
groupManualArrs.forEach((obj) => {
obj.visible = true;
});
groupVoiceArrs.forEach((obj) => {
obj.visible = false;
});
groupSdArrs.forEach((obj) => {
obj.visible = false;
});
} else if (modelName === 'all') {
groupSdArrs.forEach((obj) => {
obj.visible = true;
});
groupVoiceArrs.forEach((obj) => {
obj.visible = true;
});
groupManualArrs.forEach((obj) => {
obj.visible = true;
});
}
}
onMounted(() => {
const container = containerRef.value!;
container.appendChild(renderer.domElement);
//
camera.aspect = container.clientWidth / container.clientHeight;
camera.updateProjectionMatrix();
renderer.setSize(container.clientWidth, container.clientHeight);
//
resizeObserver = new ResizeObserver(() => {
camera.aspect = container.clientWidth / container.clientHeight;
camera.updateProjectionMatrix();
renderer.setSize(container.clientWidth, container.clientHeight);
});
resizeObserver.observe(container);
const loader = new GLTFLoader();
const dracoLoader = new DRACOLoader();
loader.setDRACOLoader(dracoLoader);
//public\models\scene\backe_edit_6.glb
loader.load('/models/scene/backe_edit_10.glb', (gltf: any) => {
console.log(gltf.scene.children);
gltf.scene.children.forEach((child: any) => {
//
if (child.name && child.name.includes('group_sd')) {
groupSdArrs.push(child); //
} else if (child.name && child.name.includes('group_voice')) {
groupVoiceArrs.push(child); //
} else if (child.name && child.name.includes('group_manual')) {
groupManualArrs.push(child); //
}
//
if (child.name && child.name.includes('group_sd_g')) {
// child3D
if (child && typeof child.updateWorldMatrix === 'function') {
try {
const box = new Box3().setFromObject(child);
const size = box.getSize(new Vector3());
const center = box.getCenter(new Vector3());
// console.log(':', size);
// console.log(':', center);
//
const randomTelemetry = Math.floor(Math.random() * 9000) + 1000;
const canvas = document.createElement('canvas');
canvas.width = 250;
canvas.height = 250;
const ctx = canvas.getContext('2d');
if (ctx) {
ctx.fillStyle = 'rgba(0,0,0,.7)';
ctx.fillRect(0, 0, 200, 100);
ctx.fillStyle = 'white';
ctx.font = '21px 黑体';
ctx.textAlign = 'center';
ctx.textBaseline = 'middle';
}
const scaleFactor = 5;
const texture = new CanvasTexture(canvas);
const material = new SpriteMaterial({ map: texture });
// ctx
if (ctx) {
ctx.fillText(`遥测:${randomTelemetry}`, 100, 30);
ctx.fillText(`名称:烟感报警器`, 100, 65);
}
const sprite = new Sprite(material);
sprite.scale.set(scaleFactor, scaleFactor, scaleFactor);
sprite.position.set(center.x - 0.5, center.y + (size.y * 2) / 3, center.z);
groupSdArrs.push(sprite); //
scene.add(sprite);
} catch (error) {
console.error('处理锥体时出错:', error);
}
} else {
console.warn('无效的3D对象:', child);
}
}
//
if (child.name && child.name.includes('group_voice_g')) {
// child3D
if (child && typeof child.updateWorldMatrix === 'function') {
try {
const box = new Box3().setFromObject(child);
const size = box.getSize(new Vector3());
const center = box.getCenter(new Vector3());
// console.log(':', size);
// console.log(':', center);
//
const randomTelemetry = Math.floor(Math.random() * 9000) + 1000;
const canvas = document.createElement('canvas');
canvas.width = 250;
canvas.height = 250;
const ctx = canvas.getContext('2d');
if (ctx) {
ctx.fillStyle = 'rgba(0,0,0,.7)';
ctx.fillRect(0, 0, 200, 100);
ctx.fillStyle = 'white';
ctx.font = '21px 黑体';
ctx.textAlign = 'center';
ctx.textBaseline = 'middle';
}
const scaleFactor = 5;
const texture = new CanvasTexture(canvas);
const material = new SpriteMaterial({ map: texture });
// ctx
if (ctx) {
ctx.fillText(`遥测:${randomTelemetry}`, 100, 30);
ctx.fillText(`名称:光感报警器`, 100, 65);
}
const sprite = new Sprite(material);
sprite.scale.set(scaleFactor, scaleFactor, scaleFactor);
sprite.position.set(center.x - 5, center.y + (size.y * 1) / 3, center.z);
groupVoiceArrs.push(sprite); //
scene.add(sprite);
} catch (error) {
console.error('处理锥体时出错:', error);
}
} else {
console.warn('无效的3D对象:', child);
}
}
//
if (child.name && child.name.includes('group_manual_g')) {
// child3D
if (child && typeof child.updateWorldMatrix === 'function') {
try {
const box = new Box3().setFromObject(child);
const size = box.getSize(new Vector3());
const center = box.getCenter(new Vector3());
// console.log(':', size);
// console.log(':', center);
//
const randomTelemetry = Math.floor(Math.random() * 9000) + 1000;
const canvas = document.createElement('canvas');
canvas.width = 250;
canvas.height = 250;
const ctx = canvas.getContext('2d');
if (ctx) {
ctx.fillStyle = 'rgba(0,0,0,.7)';
ctx.fillRect(0, 0, 200, 100);
ctx.fillStyle = 'white';
ctx.font = '21px 黑体';
ctx.textAlign = 'center';
ctx.textBaseline = 'middle';
}
const scaleFactor = 5;
const texture = new CanvasTexture(canvas);
const material = new SpriteMaterial({ map: texture });
// ctx
if (ctx) {
ctx.fillText(`遥测:${randomTelemetry}`, 100, 30);
ctx.fillText(`名称:手动报警器`, 100, 65);
}
const sprite = new Sprite(material);
sprite.scale.set(scaleFactor, scaleFactor, scaleFactor);
sprite.position.set(center.x + 5, center.y + (size.y * 1) / 3, center.z - 5);
groupManualArrs.push(sprite); //
scene.add(sprite);
} catch (error) {
console.error('处理锥体时出错:', error);
}
} else {
console.warn('无效的3D对象:', child);
}
}
});
gltf.scene.scale.set(1, 1, 1);
scene.add(gltf.scene);
});
// 线
const raycaster = new Raycaster();
const mouse = new Vector2();
//
renderer.domElement.addEventListener('mousedown', (event: MouseEvent) => {
const rect = container.getBoundingClientRect();
const x = event.clientX - rect.left;
const y = event.clientY - rect.top;
mouse.x = (x / container.clientWidth) * 2 - 1;
mouse.y = -(y / container.clientHeight) * 2 + 1;
raycaster.setFromCamera(mouse, camera);
const intersects = raycaster.intersectObjects(scene.children, true);
});
animate();
});
onUnmounted(() => {
if (resizeObserver) {
resizeObserver.disconnect();
resizeObserver = null;
}
});
</script>
<style scoped>
.scene-wrapper {
position: relative;
width: 100%;
height: 100%;
}
.container {
width: 100%;
height: 100%;
position: relative;
}
.control-buttons {
position: absolute;
top: 25%;
left: 2%;
display: flex;
flex-direction: column;
gap: 20px;
z-index: 100;
align-items: flex-start; /* 确保所有按钮左对齐 */
}
/* Element Plus圆形按钮样式调整 */
.control-buttons :deep(.el-button) {
width: 40px !important;
height: 40px !important;
min-width: 40px !important;
padding: 0 !important;
display: flex !important;
align-items: center !important; /* 垂直居中 */
margin: 0 !important;
/* 确保所有按钮尺寸一致 */
box-sizing: border-box !important;
}
.control-btn {
padding: 8px 16px;
background: rgba(0, 0, 0, 0.7);
color: white;
border: 1px solid #4fc3f7;
border-radius: 4px;
cursor: pointer;
font-size: 14px;
transition: all 0.3s ease;
min-width: 80px;
}
/* .control-btn:hover {
background: rgba(79, 195, 247, 0.3);
transform: translateY(-2px);
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.3);
}
.control-btn:active {
transform: translateY(0);
} */
/* 响应式调整 */
/* @media (max-width: 768px) {
.control-buttons {
top: 10px;
right: 10px;
gap: 8px;
}
.control-btn {
padding: 6px 12px;
font-size: 12px;
min-width: 70px;
}
} */
</style>

@ -1,388 +0,0 @@
<template>
<!-- <div ref="containerRef" class="container" /> -->
<el-row class="main-container">
<el-col :span="10" style="height: 100%">
<div ref="containerRef" class="container" />
</el-col>
<el-col :span="14" class="info-col">
<el-card class="card" :body-style="{ padding: '10px' }">
<h2 class="cardHead">单个装置基本信息</h2>
<div class="cardBody">
<el-row :gutter="0" class="info-row">
<el-col :span="8">厂家</el-col>
<el-col :span="16">
<el-tag type="primary">海湾公司</el-tag>
</el-col>
</el-row>
<el-row :gutter="0" class="info-row">
<el-col :span="8">投运日期</el-col>
<el-col :span="16">
<el-tag type="primary">2014-08-08</el-tag>
</el-col>
</el-row>
<el-row :gutter="0" class="info-row">
<el-col :span="8">电源电压</el-col>
<el-col :span="16">
<el-tag type="primary">220V AC</el-tag>
</el-col>
</el-row>
<el-row :gutter="0" class="info-row">
<el-col :span="8">布设位置</el-col>
<el-col :span="16">
<el-tag type="primary">35kV高压室</el-tag>
</el-col>
</el-row>
<el-row :gutter="0" class="info-row">
<el-col :span="8">供电电压</el-col>
<el-col :span="16">
<el-tag type="primary">18-30V DC</el-tag>
</el-col>
</el-row>
<el-row :gutter="0" class="info-row">
<el-col :span="8">输出电流</el-col>
<el-col :span="16">
<el-tag type="primary">5A</el-tag>
</el-col>
</el-row>
<el-row :gutter="0" class="info-row">
<el-col :span="8">IP等级</el-col>
<el-col :span="16">
<el-tag type="primary">IP30</el-tag>
</el-col>
</el-row>
<el-row :gutter="0" class="info-row">
<el-col :span="8">相对湿度</el-col>
<el-col :span="16">
<el-tag type="primary">5% ~ 95%</el-tag>
</el-col>
</el-row>
<el-row :gutter="0" class="info-row">
<el-col :span="8">接线方式</el-col>
<el-col :span="16">
<el-tag type="primary">两线制(L+L-)</el-tag>
</el-col>
</el-row>
<el-row :gutter="0" class="info-row">
<el-col :span="8">尺寸(长x高x宽)</el-col>
<el-col :span="16">
<el-tag type="primary">350mm x 225mm x 125mm</el-tag>
</el-col>
</el-row>
<el-row :gutter="0" class="info-row">
<el-col :span="8">报警灵敏度范围</el-col>
<el-col :span="16">
<el-tag type="primary">0.005% 20%obs/m</el-tag>
</el-col>
</el-row>
<el-row :gutter="0" class="info-row">
<el-col :span="8">运行条件</el-col>
<el-col :span="16">
<el-tag ref="conditionTagRef" type="primary" @mouseenter="checkOverflow">
探测器环境(-5°C至45°C) / 采样空气(-20°C至60°C) / 湿度(5%至95%RH)
</el-tag>
</el-col>
</el-row>
</div>
</el-card>
</el-col>
</el-row>
</template>
<script lang="ts" setup>
import { onMounted, onUnmounted, ref, nextTick } from 'vue';
import {
AxesHelper,
Color,
PerspectiveCamera,
Scene,
WebGLRenderer,
AmbientLight,
DirectionalLight,
PointLight,
MeshStandardMaterial,
Vector2,
Raycaster,
CanvasTexture,
SpriteMaterial,
Sprite,
Box3,
Vector3,
HemisphereLight
} from 'three';
import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls';
import { GLTFLoader } from 'three/examples/jsm/loaders/GLTFLoader';
import { DRACOLoader } from 'three/examples/jsm/loaders/DRACOLoader';
const containerRef = ref<HTMLDivElement>();
const conditionTagRef = ref<any>();
const showTooltip = ref(false);
//
const checkOverflow = () => {
nextTick(() => {
if (conditionTagRef.value) {
const element = conditionTagRef.value.$el || conditionTagRef.value;
showTooltip.value = element.scrollWidth > element.clientWidth;
}
});
};
//
const scene = new Scene();
//
const camera = new PerspectiveCamera(
45,
containerRef.value?.clientWidth! / containerRef.value?.clientHeight!,
0.1,
1000
);
camera.position.set(-40, 20, 30);
camera.lookAt(scene.position);
//
const renderer = new WebGLRenderer({
antialias: true, // 齿
alpha: true, //
preserveDrawingBuffer: true //
});
renderer.setClearColor(new Color('#131519'));
renderer.setSize(containerRef.value?.clientWidth!, containerRef.value?.clientHeight!);
renderer.shadowMap.enabled = true;
renderer.setPixelRatio(window.devicePixelRatio || 1); //
//
const orbitControls = new OrbitControls(camera, renderer.domElement);
orbitControls.autoRotate = false;
orbitControls.autoRotateSpeed = 5;
orbitControls.dampingFactor = 0.1;
orbitControls.enableDamping = true;
orbitControls.enableRotate = true;
orbitControls.minDistance = 10;
orbitControls.maxPolarAngle = Math.PI * 0.5;
//
scene.add(new AxesHelper(20));
//
const ambientLight = new AmbientLight('#ffffff', 1.8); // 0.6 1.0
scene.add(ambientLight);
//
const directionalLight = new DirectionalLight('#ffffff', 1.8); // 0.2 0.8
scene.add(directionalLight);
directionalLight.position.set(20, 20, 10);
// //
const pointLight = new PointLight('#ffffff', 1.8, 1800); // 0.5 1.0
scene.add(pointLight);
pointLight.position.set(0, 40, 0);
//
const canvas = document.createElement('canvas');
canvas.width = 250;
canvas.height = 250;
const ctx = canvas.getContext('2d');
if (ctx) {
ctx.fillStyle = 'rgba(0,0,0,.7)';
ctx.fillRect(0, 0, 200, 100);
ctx.fillStyle = 'white';
ctx.font = '21px 黑体';
ctx.textAlign = 'center';
ctx.textBaseline = 'middle';
ctx.fillText('遥测100111', 100, 30);
ctx.fillText('温度25℃', 90, 65);
}
const scaleFactor = 5;
const texture = new CanvasTexture(canvas);
const material = new SpriteMaterial({ map: texture });
const sprite1 = new Sprite(material);
sprite1.scale.set(scaleFactor, scaleFactor, scaleFactor);
sprite1.position.set(-9.644502679789724, 5.815449539931501 + 0.5, 21.66345220492572);
function animate() {
requestAnimationFrame(animate);
orbitControls.update();
renderer.render(scene, camera);
}
let pitchObj = ref<any>();
let pitchMaterial = ref<any>();
// ResizeObserver
let resizeObserver: ResizeObserver | null = null;
onMounted(() => {
const container = containerRef.value!;
container.appendChild(renderer.domElement);
//
camera.aspect = container.clientWidth / container.clientHeight;
camera.updateProjectionMatrix();
renderer.setSize(container.clientWidth, container.clientHeight);
//
resizeObserver = new ResizeObserver(() => {
camera.aspect = container.clientWidth / container.clientHeight;
camera.updateProjectionMatrix();
renderer.setSize(container.clientWidth, container.clientHeight);
});
resizeObserver.observe(container);
const loader = new GLTFLoader();
const dracoLoader = new DRACOLoader();
loader.setDRACOLoader(dracoLoader);
loader.load('/models/smokeSiren/smokeSiren.glb', (gltf: any) => {
console.log('3D:', gltf);
//
// const box = new Box3().setFromObject(gltf.scene);
// const size = box.getSize(new Vector3());
// const center = box.getCenter(new Vector3());
// console.log(':');
// console.log(':', box.min);
// console.log(':', box.max);
// console.log(':', center);
// console.log(' (××):', size);
// console.log(' (X):', size.x);
// console.log(' (Y):', size.y);
// console.log(' (Z):', size.z);
// const sprite = new Sprite(material);
// sprite.scale.set(scaleFactor, scaleFactor, scaleFactor);
// sprite.position.set(size.x / 2 - 5, size.y / 2 - 5, size.z / 2);
// scene.add(sprite);
gltf.scene.scale.set(1, 1, 1);
scene.add(gltf.scene);
});
// 线
const raycaster = new Raycaster();
const mouse = new Vector2();
//
renderer.domElement.addEventListener('mousedown', (event: MouseEvent) => {
const rect = container.getBoundingClientRect();
const x = event.clientX - rect.left;
const y = event.clientY - rect.top;
mouse.x = (x / container.clientWidth) * 2 - 1;
mouse.y = -(y / container.clientHeight) * 2 + 1;
raycaster.setFromCamera(mouse, camera);
const intersects = raycaster.intersectObjects(scene.children, true);
});
animate();
});
onUnmounted(() => {
if (resizeObserver) {
resizeObserver.disconnect();
resizeObserver = null;
}
});
</script>
<style scoped>
.main-container {
height: 100%;
width: 100%;
margin: 0;
padding: 0;
}
.container {
width: 100%;
height: 100%;
position: relative; /* 确保定位上下文 */
}
.info-col {
height: 100%;
overflow: hidden;
}
.card {
height: 100%;
display: flex;
flex-direction: column;
margin: 0;
padding: 0;
}
.card :deep(.el-card__body) {
flex: 1;
overflow-y: auto;
padding: 10px;
}
.cardHead {
margin: 0 0 15px 0;
padding: 0;
font-size: 16px;
font-weight: bold;
text-align: center;
color: #ffffff;
}
.cardBody {
font-size: 12px;
}
.info-row {
margin-bottom: 8px;
display: flex;
align-items: center;
}
.info-row :deep(.el-col) {
display: flex;
align-items: center;
}
.info-row :deep(.el-tag) {
/* width: 100%; */
justify-content: left;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
max-width: 100%;
display: flex;
align-items: center;
height: 100%;
}
.ellipsis-tag {
width: 100%;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
display: flex;
align-items: center;
height: 100%;
}
.condition-tag {
display: flex;
align-items: center;
justify-content: flex-start;
}
</style>

@ -1,323 +0,0 @@
<template>
<div class="scene-wrapper">
<div ref="containerRef" class="container" />
<!-- 固定位置的控制按钮 -->
<div class="control-buttons">
<el-button type="primary" @click="displayModel('group_sd')" :icon="Bell" />
<el-button type="danger" @click="displayModel('group_voice')" :icon="MagicStick" />
<el-button type="warning" @click="displayModel('group_manual')" :icon="Star" />
<el-button type="warning" @click="displayModel('all')" :icon="Compass" />
<el-button type="warning" @click="camera.position.set(-50, 45, -140)" :icon="Refresh" />
</div>
</div>
</template>
<script lang="ts" setup>
import {
Check,
Delete,
Edit,
Message,
Search,
Star,
Bell,
MagicStick,
Refresh,
Compass
} from '@element-plus/icons-vue';
import { onMounted, onUnmounted, ref, nextTick } from 'vue';
import {
AxesHelper,
Color,
PerspectiveCamera,
Scene,
WebGLRenderer,
AmbientLight,
DirectionalLight,
PointLight,
MeshStandardMaterial,
Vector2,
Raycaster,
CanvasTexture,
SpriteMaterial,
Sprite,
Box3,
Vector3,
HemisphereLight,
Mesh
} from 'three';
import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls';
import { GLTFLoader } from 'three/examples/jsm/loaders/GLTFLoader';
import { DRACOLoader } from 'three/examples/jsm/loaders/DRACOLoader';
const containerRef = ref<HTMLDivElement>();
const conditionTagRef = ref<any>();
const showTooltip = ref(false);
//
const scene = new Scene();
//
const camera = new PerspectiveCamera(
45,
containerRef.value?.clientWidth! / containerRef.value?.clientHeight!,
0.1,
1000
);
camera.position.set(-50, 45, -140);
camera.lookAt(scene.position);
const axesHelper = new AxesHelper(100);
scene.add(axesHelper);
//
const renderer = new WebGLRenderer({
antialias: true, // 齿
alpha: true, //
preserveDrawingBuffer: true //
});
renderer.setClearColor(new Color('#131519'));
renderer.setSize(containerRef.value?.clientWidth!, containerRef.value?.clientHeight!);
renderer.shadowMap.enabled = true;
renderer.setPixelRatio(window.devicePixelRatio || 1); //
//
const orbitControls = new OrbitControls(camera, renderer.domElement);
orbitControls.autoRotate = false;
orbitControls.autoRotateSpeed = 5;
orbitControls.dampingFactor = 0.1;
orbitControls.enableDamping = true;
orbitControls.enableRotate = true;
orbitControls.minDistance = 10;
orbitControls.maxPolarAngle = Math.PI * 0.5;
//
scene.add(new AxesHelper(20));
//
const ambientLight = new AmbientLight('#ffffff', 1.8); // 0.6 1.0
scene.add(ambientLight);
//
const directionalLight = new DirectionalLight('#ffffff', 1.8); // 0.2 0.8
scene.add(directionalLight);
directionalLight.position.set(20, 20, 10);
// //
const pointLight = new PointLight('#ffffff', 1.8, 1800); // 0.5 1.0
scene.add(pointLight);
pointLight.position.set(0, 40, 0);
//
// const ctx = canvas.getContext('2d');
// if (ctx) {
// ctx.fillStyle = 'rgba(0,0,0,.7)';
// ctx.fillRect(0, 0, 200, 100);
// ctx.fillStyle = 'white';
// ctx.font = '21px ';
// ctx.textAlign = 'center';
// ctx.textBaseline = 'middle';
// }
// const scaleFactor = 5;
// const texture = new CanvasTexture(canvas);
// const material = new SpriteMaterial({ map: texture });
// const sprite1 = new Sprite(material);
// sprite1.scale.set(scaleFactor, scaleFactor, scaleFactor);
// sprite1.position.set(-9.644502679789724, 5.815449539931501 + 0.5, 21.66345220492572);
function animate() {
requestAnimationFrame(animate);
orbitControls.update();
renderer.render(scene, camera);
}
let pitchObj = ref<any>();
let pitchMaterial = ref<any>();
// ResizeObserver
let resizeObserver: ResizeObserver | null = null;
let groupSdArrs: any[] = [];
let groupVoiceArrs: any[] = [];
let groupManualArrs: any[] = [];
function displayModel(modelName: string) {
if (modelName === 'group_sd') {
groupSdArrs.forEach((obj) => {
obj.visible = true;
});
groupVoiceArrs.forEach((obj) => {
obj.visible = false;
});
groupManualArrs.forEach((obj) => {
obj.visible = false;
});
} else if (modelName === 'group_voice') {
groupVoiceArrs.forEach((obj) => {
obj.visible = true;
});
groupSdArrs.forEach((obj) => {
obj.visible = false;
});
groupManualArrs.forEach((obj) => {
obj.visible = false;
});
} else if (modelName === 'group_manual') {
groupManualArrs.forEach((obj) => {
obj.visible = true;
});
groupVoiceArrs.forEach((obj) => {
obj.visible = false;
});
groupSdArrs.forEach((obj) => {
obj.visible = false;
});
} else if (modelName === 'all') {
groupSdArrs.forEach((obj) => {
obj.visible = true;
});
groupVoiceArrs.forEach((obj) => {
obj.visible = true;
});
groupManualArrs.forEach((obj) => {
obj.visible = true;
});
}
}
onMounted(() => {
const container = containerRef.value!;
container.appendChild(renderer.domElement);
//
camera.aspect = container.clientWidth / container.clientHeight;
camera.updateProjectionMatrix();
renderer.setSize(container.clientWidth, container.clientHeight);
//
resizeObserver = new ResizeObserver(() => {
camera.aspect = container.clientWidth / container.clientHeight;
camera.updateProjectionMatrix();
renderer.setSize(container.clientWidth, container.clientHeight);
});
resizeObserver.observe(container);
const loader = new GLTFLoader();
const dracoLoader = new DRACOLoader();
loader.setDRACOLoader(dracoLoader);
//public\models\scene\test.glb
loader.load('/models/scene/test.glb', (gltf: any) => {
console.log(gltf.scene.children);
gltf.scene.scale.set(1, 1, 1);
scene.add(gltf.scene);
});
// public\models\texture\alpha01.png
// 线
const raycaster = new Raycaster();
const mouse = new Vector2();
//
renderer.domElement.addEventListener('mousedown', (event: MouseEvent) => {
const rect = container.getBoundingClientRect();
const x = event.clientX - rect.left;
const y = event.clientY - rect.top;
mouse.x = (x / container.clientWidth) * 2 - 1;
mouse.y = -(y / container.clientHeight) * 2 + 1;
raycaster.setFromCamera(mouse, camera);
const intersects = raycaster.intersectObjects(scene.children, true);
});
animate();
});
onUnmounted(() => {
if (resizeObserver) {
resizeObserver.disconnect();
resizeObserver = null;
}
});
</script>
<style scoped>
.scene-wrapper {
position: relative;
width: 100%;
height: 100%;
}
.container {
width: 100%;
height: 100%;
position: relative;
}
.control-buttons {
position: absolute;
top: 25%;
left: 2%;
display: flex;
flex-direction: column;
gap: 20px;
z-index: 100;
align-items: flex-start; /* 确保所有按钮左对齐 */
}
/* Element Plus圆形按钮样式调整 */
.control-buttons :deep(.el-button) {
width: 40px !important;
height: 40px !important;
min-width: 40px !important;
padding: 0 !important;
display: flex !important;
align-items: center !important; /* 垂直居中 */
margin: 0 !important;
/* 确保所有按钮尺寸一致 */
box-sizing: border-box !important;
}
.control-btn {
padding: 8px 16px;
background: rgba(0, 0, 0, 0.7);
color: white;
border: 1px solid #4fc3f7;
border-radius: 4px;
cursor: pointer;
font-size: 14px;
transition: all 0.3s ease;
min-width: 80px;
}
/* .control-btn:hover {
background: rgba(79, 195, 247, 0.3);
transform: translateY(-2px);
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.3);
}
.control-btn:active {
transform: translateY(0);
} */
/* 响应式调整 */
/* @media (max-width: 768px) {
.control-buttons {
top: 10px;
right: 10px;
gap: 8px;
}
.control-btn {
padding: 6px 12px;
font-size: 12px;
min-width: 70px;
}
} */
</style>

@ -7,7 +7,8 @@
* @param timestamp * @param timestamp
* @returns * @returns
*/ */
export function formatTimestamp(timestamp: number): string { export function formatTimestamp(timestamp: number | undefined): string {
if (timestamp === undefined) return '-.-';
const date = new Date(timestamp); const date = new Date(timestamp);
const year = date.getFullYear(); const year = date.getFullYear();
const month = String(date.getMonth() + 1).padStart(2, '0'); const month = String(date.getMonth() + 1).padStart(2, '0');

Loading…
Cancel
Save