Compare commits

...

17 Commits

19560
package-lock.json generated

File diff suppressed because it is too large Load Diff

@ -24,7 +24,6 @@
"canvg": "^4.0.1", "canvg": "^4.0.1",
"element-plus": "^2.11.9", "element-plus": "^2.11.9",
"eslint-plugin-prettier": "^5.5.4", "eslint-plugin-prettier": "^5.5.4",
"eslint-plugin-vue": "^10.6.2",
"globals": "^16.5.0", "globals": "^16.5.0",
"highlight.js": "^11.11.1", "highlight.js": "^11.11.1",
"html2canvas": "^1.4.1", "html2canvas": "^1.4.1",
@ -37,7 +36,6 @@
"screenfull": "^6.0.2", "screenfull": "^6.0.2",
"sortablejs": "^1.15.6", "sortablejs": "^1.15.6",
"typescript-eslint": "^8.48.0", "typescript-eslint": "^8.48.0",
"vite-plugin-svg-icons": "^2.0.1",
"vue": "^3.5.25", "vue": "^3.5.25",
"vue-eslint-parser": "^10.2.0", "vue-eslint-parser": "^10.2.0",
"vue-i18n": "^9.14.5", "vue-i18n": "^9.14.5",
@ -56,7 +54,8 @@
"@vue/eslint-config-prettier": "^8.0.0", "@vue/eslint-config-prettier": "^8.0.0",
"@vue/eslint-config-typescript": "^12.0.0", "@vue/eslint-config-typescript": "^12.0.0",
"@vue/tsconfig": "^0.5.1", "@vue/tsconfig": "^0.5.1",
"eslint": "^9.39.1", "eslint": "8.57.0",
"eslint-plugin-vue": "9.4.0",
"less": "^4.6.3", "less": "^4.6.3",
"node-forge": "^1.3.2", "node-forge": "^1.3.2",
"npm-run-all2": "^6.2.6", "npm-run-all2": "^6.2.6",
@ -66,6 +65,8 @@
"unocss": "^66.6.6", "unocss": "^66.6.6",
"vite": "6.3.4", "vite": "6.3.4",
"vite-plugin-compression": "^0.5.1", "vite-plugin-compression": "^0.5.1",
"vite-plugin-dts": "^4.5.0",
"vite-plugin-svg-icons": "^2.0.1",
"vite-plugin-vue-devtools": "^7.7.9", "vite-plugin-vue-devtools": "^7.7.9",
"vue-tsc": "^2.2.12" "vue-tsc": "^2.2.12"
} }

File diff suppressed because it is too large Load Diff

@ -0,0 +1,44 @@
import http from '@/api';
import { ADMIN_MODULE } from '@/api/helper/prefix';
// 引入刚刚定义的类型
import type {
BlurDetectionDTO,
BlurDetectionUpdateDTO,
BlurDetectionVO
} from "@/api/types/blurDetection/BlurDetection";
/**
*
* : POST /blur-detection/add (@RequestBody)
* @param data BlurDetectionDTO
*/
export const addBlurDetectionApi = (data: BlurDetectionDTO) => {
return http.post<boolean>(ADMIN_MODULE + `/blur-detection/add`, data);
};
/**
*
* : PUT /blur-detection/update (@RequestBody)
* @param data BlurDetectionUpdateDTO
*/
export const updateBlurDetectionApi = (data: BlurDetectionUpdateDTO) => {
return http.put<boolean>(ADMIN_MODULE + `/blur-detection/update`, data);
};
/**
*
*/
export const addOrUpdateBlurDetectionApi = (data: BlurDetectionDTO | BlurDetectionUpdateDTO) => {
return http.post<boolean>(ADMIN_MODULE + `/blur-detection/addOrUpdate`, data);
}
/**
* id
* : GET /blur-detection/get (@RequestParam Long id)
* eq(BlurDetection::getCameraId, id) id cameraId
* @param params { id: number } id ID
*/
export const getBlurDetectionByCameraIdApi = (params: { id: number }) => {
return http.get<BlurDetectionVO>(ADMIN_MODULE + `/blur-detection/get`, params);
};

@ -0,0 +1,12 @@
import http from '@/api';
import {ADMIN_MODULE} from '@/api/helper/prefix';
import type {ComputerRoomRow} from "@/api/types/computerRoom/computerRoom";
/**
*
* @param params
* @returns {*}
*/
export const getListApi = (params: { substationId: number }) => {
return http.get<ComputerRoomRow[]>(ADMIN_MODULE + `/computerRoom/list`, params);
};

@ -8,3 +8,6 @@ export const getTreeData = () => {
export const getTreePreview=()=>{ export const getTreePreview=()=>{
return http.get<TreeNode[]>(ADMIN_MODULE + '/tree/preview'); return http.get<TreeNode[]>(ADMIN_MODULE + '/tree/preview');
} }
export const getTreeSubstation=()=>{
return http.get<TreeNode[]>(ADMIN_MODULE + '/tree/substation');
}

@ -14,3 +14,9 @@ export const refresh=(params:{id: number,deviceType: number})=>{
export const deviceLogin=(params:{id: number})=>{ export const deviceLogin=(params:{id: number})=>{
return http.get<Boolean>(ADMIN_MODULE+`/device/login`,params) return http.get<Boolean>(ADMIN_MODULE+`/device/login`,params)
} }
/**
* NVR
*/
export const captureHistoryAndCompose= (params: { ids: number[] | undefined })=>{
return http.get<Boolean>(ADMIN_MODULE+`/device/history/analysis`,params)
}

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

@ -0,0 +1,44 @@
/**
* (VO - )
* BlurDetectionVO
*/
export interface BlurDetectionVO {
/** 主键 */
id: number;
/** 摄像头id */
cameraId: number;
/** 摄像头名称 */
cameraName: string;
/** 开关0-关闭 1-开启 */
enable: number;
/** 上报的url */
url: string;
/** 创建时间 */
createTime: string;
/** 更新时间 */
updateTime: string;
}
/**
* (DTO)
* BlurDetectionDTO
*/
export interface BlurDetectionDTO {
/** 摄像头id */
cameraId: number;
/** 摄像头名称 */
cameraName?: string;
/** 开关0-关闭 1-开启 */
enable?: number;
/** 上报的url */
url?: string;
}
/**
* (UpdateDTO)
* BlurDetectionUpdateDTO
*/
export interface BlurDetectionUpdateDTO extends BlurDetectionDTO {
/** 主键ID (更新时必传) */
id: number;
}

@ -0,0 +1,7 @@
// list或detail返回结构
export type ComputerRoomRow = {
id?: number;
substationId?: number;
name?: string;
description?: string;
};

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

@ -0,0 +1,79 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Created with Inkscape (http://www.inkscape.org/) -->
<svg
width="70.760376mm"
height="70.760376mm"
viewBox="0 0 70.760376 70.760376"
version="1.1"
id="svg1"
inkscape:version="1.4.3 (0d15f75042, 2025-12-25)"
sodipodi:docname="Cam.svg"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg">
<sodipodi:namedview
id="namedview1"
pagecolor="#ffffff"
bordercolor="#000000"
borderopacity="0.25"
inkscape:showpageshadow="2"
inkscape:pageopacity="0.0"
inkscape:pagecheckerboard="0"
inkscape:deskcolor="#d1d1d1"
inkscape:document-units="mm"
inkscape:zoom="1.5999719"
inkscape:cx="163.12786"
inkscape:cy="179.37815"
inkscape:window-width="1939"
inkscape:window-height="1158"
inkscape:window-x="0"
inkscape:window-y="0"
inkscape:window-maximized="1"
inkscape:current-layer="layer1" />
<defs
id="defs1" />
<g
inkscape:label="图层 1"
inkscape:groupmode="layer"
id="layer1"
transform="translate(-24.149177,-57.838309)">
<circle
style="fill:#dfff8e;fill-opacity:1;stroke:#a8a8a8;stroke-width:4.02186;stroke-dasharray:none;stroke-opacity:1"
id="path1"
cx="59.529366"
cy="93.218498"
r="33.369259" />
<g
id="g8"
style="fill:#6bb6ff;fill-opacity:1;stroke:#000000;stroke-width:1.6;stroke-dasharray:none;stroke-opacity:1"
transform="translate(-15.653376,-30.811052)">
<path
sodipodi:type="star"
style="fill:#6bb6ff;fill-opacity:1;stroke:#000000;stroke-width:1.54655;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4.2;stroke-dasharray:none;stroke-opacity:1;paint-order:normal"
id="path6"
inkscape:flatsided="false"
sodipodi:sides="3"
sodipodi:cx="103.60217"
sodipodi:cy="112.25517"
sodipodi:r1="12.027572"
sodipodi:r2="6.0137858"
sodipodi:arg1="1.0471976"
sodipodi:arg2="2.0943951"
inkscape:rounded="0"
inkscape:randomized="0"
d="m 109.61596,122.67136 -9.02068,-5.2081 -9.020679,-5.20809 9.020679,-5.20809 9.02068,-5.20809 0,10.41618 z"
inkscape:transform-center-x="2.9948907"
transform="matrix(0.99600848,0,0,1.074601,-11.193569,3.4000286)"
inkscape:transform-center-y="2.2783516e-06" />
<rect
style="fill:#6bb6ff;fill-opacity:1;stroke:#000000;stroke-width:1.6;stroke-linecap:round;stroke-linejoin:round;stroke-dasharray:none;stroke-opacity:1"
id="rect6"
width="31.699444"
height="21.877115"
x="52.321907"
y="113.091" />
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 2.8 KiB

@ -21,8 +21,8 @@ interface SvgProps {
const props = withDefaults(defineProps<SvgProps>(), { const props = withDefaults(defineProps<SvgProps>(), {
prefix: 'icon', prefix: 'icon',
width: 16, width: 480,
height: 16 height: 480
}); });
const symbolId = computed(() => `#${props.prefix}-${props.name}`); const symbolId = computed(() => `#${props.prefix}-${props.name}`);

@ -3,7 +3,6 @@ import { createApp } from 'vue';
import pinia from '@/stores'; import pinia from '@/stores';
import '@/styles/index.scss'; import '@/styles/index.scss';
// element plus // element plus
import ElementPlus from 'element-plus'; import ElementPlus from 'element-plus';
// element css // element css
@ -14,6 +13,7 @@ import 'element-plus/theme-chalk/dark/css-vars.css';
// element icons // element icons
import * as Icons from '@element-plus/icons-vue'; import * as Icons from '@element-plus/icons-vue';
// vue i18n // vue i18n
import I18n from '@/languages'; import I18n from '@/languages';
@ -50,7 +50,6 @@ app.use(pinia);
app.use(router); app.use(router);
app.use(directives); app.use(directives);
app.use(I18n); app.use(I18n);
// app.use(router) // app.use(router)
app.use(hljsVuePlugin); app.use(hljsVuePlugin);

@ -0,0 +1,69 @@
import {defineStore} from "pinia";
import {computed, ref} from "vue";
export const useCameraBindStore=defineStore('cameraBind',()=>{
// 存储已绑定的摄像头id及其绑定的组件信息
const boundCameras=ref<Map<number,{
val:number,
type: string,
title: string
}>>(new Map());
// 检查摄像头是否已被绑定
const isCameraBound = (cameraId: number ) => {
return boundCameras.value.has(cameraId);
};
// 获取摄像头的绑定信息
const getCameraBindsInfo=(cameraId:number )=>{
return boundCameras.value.get(cameraId);
}
// 绑定摄像头
const bingCamera=(cameraId:number,obj:any)=>{
if(isCameraBound(cameraId))
{
return false;
}
boundCameras.value.set(cameraId,{
val:cameraId,
type:obj.type,
title:obj.title
});
return true;
}
// 解绑摄像头
const unbindCamera=(cameraId:number)=>{
boundCameras.value.delete(cameraId);
}
// 获取所有已经绑定的摄像头ID列表
const getBoundCameraIds=computed(()=>{
return Array.from(boundCameras.value.keys());
})
return {
boundCameras,
isCameraBound,
getCameraBindsInfo,
bingCamera,
unbindCamera,
getBoundCameraIds
}
},{
// 第三个参数:配置持久化
persist: {
key: 'camera-bind-store', // 存储在 localStorage 中的键名,可自定义
storage: localStorage, // 默认是 localStorage如果想关闭页面失效可改为 sessionStorage
serializer: {
// 序列化:将 Mp 转换为 [[key, value], [key, value]] 的二维数组存入 JSON
serialize: (state) => {
return JSON.stringify({
boundCameras: Array.from(state.boundCameras.entries())
});
},
// 反序列化:将 JSON 中的二维数组解析并重新构建为 Map 对象
deserialize: (value) => {
const parsed = JSON.parse(value);
return {
boundCameras: new Map(parsed.boundCameras)
};
}
}
}
})

@ -0,0 +1,108 @@
<script setup lang="ts">
import { onMounted, ref } from "vue";
import { Picture } from '@element-plus/icons-vue'; //
import type { ComputerRoomRow } from "@/api/types/computerRoom/computerRoom";
import {useRouter} from "vue-router";
const drawer = ref(false);
const paramsProps = ref();
const computerRoomList = ref<ComputerRoomRow[]>([]);
const router = useRouter();
//
const acceptParams = async (params: any) => {
paramsProps.value = params;
const res = await paramsProps.value.getListApi({
substationId: paramsProps.value.data.id
});
if (res.code === '0000') {
computerRoomList.value = res.data;
}
drawer.value = true;
}
//
const handleViewPlan = (room: ComputerRoomRow) => {
router.push({
path:'/teacher/teacher-preview',
query: {
roomId: room.id
}
})
}
defineExpose({
acceptParams
})
</script>
<template>
<el-drawer v-model="drawer" title="机房列表" size="400px">
<div class="room-list-container">
<template v-if="computerRoomList.length > 0">
<div v-for="room in computerRoomList" :key="room.id" class="room-item">
<div class="room-info">
<div class="room-name">{{ room.name }}</div>
<div class="room-desc" v-if="room.description">{{ room.description }}</div>
</div>
<div class="room-action">
<el-button type="primary" plain :icon="Picture" size="small" @click="handleViewPlan(room)">
查看平面图
</el-button>
</div>
</div>
</template>
<el-empty v-else description="暂无绑定平面图的机房" />
</div>
</el-drawer>
</template>
<style scoped lang="scss">
.room-list-container {
padding: 0 5px;
.room-item {
display: flex;
justify-content: space-between;
align-items: center;
padding: 16px 14px;
margin-bottom: 12px;
border: 1px solid #efefef;
border-radius: 6px;
transition: all 0.3s ease;
/* 悬浮时的交互反馈 */
&:hover {
box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.05);
border-color: #c6e2ff;
}
.room-info {
flex: 1;
overflow: hidden; /* 防止文字过长挤压按钮 */
margin-right: 15px;
.room-name {
font-size: 15px;
font-weight: 600;
color: #303133;
margin-bottom: 6px;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.room-desc {
font-size: 13px;
color: #909399;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
}
.room-action {
flex-shrink: 0; /* 保证按钮不会被压缩 */
}
}
}
</style>

@ -23,11 +23,17 @@
<template #default="{ node, data }"> <template #default="{ node, data }">
<span class="custom-tree-node"> <span class="custom-tree-node">
<el-icon v-if="data.type === 0"><OfficeBuilding /></el-icon> <el-icon v-if="data.type === 0"><OfficeBuilding /></el-icon>
<el-icon v-else-if="data.type === 1"><School /></el-icon> <div v-else-if="data.type===1">
<el-icon ><School /></el-icon>
</div>
<el-icon v-else-if="data.type === 2"><VideoCamera /></el-icon> <el-icon v-else-if="data.type === 2"><VideoCamera /></el-icon>
<span class="node-label" :class="{ 'ehv-text': data.type === 'STATION_EHV' }"> <span class="node-label" :class="{ 'ehv-text': data.type === 'STATION_EHV' }">
{{ node.label }} {{ node.label }}
</span> </span>
<div v-if="data.type===1" style="margin-left: 5px;">
<el-button link type="primary" @click="showComputeRoom(data)"></el-button>
<el-button link type="primary" @click="historicalDataAnalysis(data)"></el-button>
</div>
</span> </span>
</template> </template>
</el-tree> </el-tree>
@ -290,10 +296,24 @@
<el-table-column label="操作" fixed="right" width="250"> <el-table-column label="操作" fixed="right" width="250">
<template #default="{row}"> <template #default="{row}">
<el-button size="small" type="primary" link @click="openPlayDialog(row)" :loading="playLoading">预览</el-button> <el-button size="small" type="primary" link @click="openPlayDialog(row)" :loading="playLoading">预览</el-button>
<el-button size="small" type="primary" link @click="openControlDialog(row)"></el-button>
<el-button size="small" v-if="row.boxId" type="primary" link @click="openAlgDialog(row)"></el-button>
<el-button size="small" type="primary" link @click="openCameraEditDialog(row)"> </el-button> <el-button size="small" type="primary" link @click="openCameraEditDialog(row)"> </el-button>
<el-button size="small" type="warning" link @click="handleEditDel(row)"></el-button> <el-button size="small" type="warning" link @click="handleEditDel(row)"></el-button>
<el-dropdown
trigger="hover"
@command="(command:string) => handleCommand(command, row)"
style="margin-left: 12px; vertical-align: middle;"
>
<el-button link type="primary" size="small">
更多 <el-icon class="el-icon--right"><ArrowDown /></el-icon>
</el-button>
<template #dropdown>
<el-dropdown-menu>
<el-dropdown-item command="control">控制</el-dropdown-item>
<el-dropdown-item command="blur">模糊检测</el-dropdown-item>
<el-dropdown-item command="algo" divided>配置算法</el-dropdown-item>
</el-dropdown-menu>
</template>
</el-dropdown>
</template> </template>
</el-table-column> </el-table-column>
</el-table> </el-table>
@ -387,6 +407,46 @@
:curCamera="controlDialogObj.curCamera" :curCamera="controlDialogObj.curCamera"
/> />
</el-dialog> </el-dialog>
<!-- 模糊检测的弹框-->
<el-dialog
v-model="blurDialogObj.blurVisible"
:title="blurDialogObj.title"
width="500"
:before-close="handelCancelBlur"
>
<el-form label-width="120px" label-suffix=" :"
ref="blurFormRef"
:rules="{
url: [
{
required: true,
message: '请填写上报地址',
trigger: 'blur'
},
],
}"
:model="blurDialogObj.burDetectionVO">
<el-form-item label="开启模糊检测" >
<el-switch v-model="blurDialogObj.burDetectionVO.enable"
:active-value="1"
:inactive-value="0"></el-switch>
</el-form-item>
<el-form-item label="上报地址" v-if="blurDialogObj.burDetectionVO.enable===1" prop="url">
<el-input v-model="blurDialogObj.burDetectionVO.url" placeholder="请填写上报地址"></el-input>
</el-form-item>
</el-form>
<template #footer>
<div class="dialog-footer">
<el-button @click="handelCancelBlur"></el-button>
<el-button type="primary" @click="handelSubmitBlur">
确定
</el-button>
</div>
</template>
</el-dialog>
<!-- 机房平面图列表组件-->
<compute-room-model-list ref="computeRoomRef"/>
</div> </div>
</template> </template>
@ -394,7 +454,7 @@
import {onBeforeUnmount, onMounted, reactive, ref, watch} from 'vue'; import {onBeforeUnmount, onMounted, reactive, ref, watch} from 'vue';
// ArrowLeft, Refresh, Document // ArrowLeft, Refresh, Document
import {Delete, OfficeBuilding, Plus, School, VideoCamera} from '@element-plus/icons-vue'; import {ArrowDown, Delete, OfficeBuilding, Plus, School, VideoCamera} from '@element-plus/icons-vue';
import {getTreeData} from "@/api/modules/monitor/Tree.js"; import {getTreeData} from "@/api/modules/monitor/Tree.js";
import type {TreeNode} from "@/api/types/monitor/Tree"; import type {TreeNode} from "@/api/types/monitor/Tree";
import {createNvrApi, getNvrListApi, listByNode, removeNvrApi, updateNvrApi} from "@/api/modules/monitor/Nvr"; import {createNvrApi, getNvrListApi, listByNode, removeNvrApi, updateNvrApi} from "@/api/modules/monitor/Nvr";
@ -402,7 +462,7 @@ import type {NvrForm, NvrQuery, NvrRow} from "@/api/types/monitor/nvr";
import {type ElForm, ElMessage, ElMessageBox, type TreeInstance} from "element-plus"; import {type ElForm, ElMessage, ElMessageBox, type TreeInstance} from "element-plus";
import {getCameraListApi, removeCameraApi, updateCameraApi} from "@/api/modules/monitor/Camera"; import {getCameraListApi, removeCameraApi, updateCameraApi} from "@/api/modules/monitor/Camera";
import type {CameraForm, CameraQuery, CameraRow} from "@/api/types/monitor/camera"; import type {CameraForm, CameraQuery, CameraRow} from "@/api/types/monitor/camera";
import {deviceLogin, refresh} from "@/api/modules/monitor/device"; import {captureHistoryAndCompose, deviceLogin, refresh} from "@/api/modules/monitor/device";
import CameraControl from "@/views/sysmonitortree/sysMonitorTree/components/CameraControl.vue"; import CameraControl from "@/views/sysmonitortree/sysMonitorTree/components/CameraControl.vue";
import VideoPlayer from "@/views/sysmonitortree/sysMonitorTree/components/VideoPlayer.vue"; import VideoPlayer from "@/views/sysmonitortree/sysMonitorTree/components/VideoPlayer.vue";
import {useDictOptions} from "@/hooks/useDictOptions"; import {useDictOptions} from "@/hooks/useDictOptions";
@ -415,6 +475,10 @@ import {getAlgorithmTaskByCameraId} from "@/api/modules/monitor/algorithmTask";
import type {AlgorithmTaskVO, AlgTaskConfigDto} from "@/api/types/edgebox/EdgeBox"; import type {AlgorithmTaskVO, AlgTaskConfigDto} from "@/api/types/edgebox/EdgeBox";
import DataSync from "@/views/sysmonitortree/sysMonitorTree/components/DataSync.vue"; import DataSync from "@/views/sysmonitortree/sysMonitorTree/components/DataSync.vue";
import {fa} from "element-plus/es/locale"; import {fa} from "element-plus/es/locale";
import ComputeRoomModelList from "@/views/sysmonitortree/sysMonitorTree/components/ComputeRoomModelList.vue";
import {getListApi} from "@/api/modules/computerRoom/computerRoom";
import type {BlurDetectionUpdateDTO, BlurDetectionVO} from "@/api/types/blurDetection/BlurDetection";
import {addOrUpdateBlurDetectionApi, getBlurDetectionByCameraIdApi} from "@/api/modules/blurDetection/BlurDetection";
const refreshLoading=ref(false); const refreshLoading=ref(false);
const playLoading = ref(false); const playLoading = ref(false);
const filterText = ref(''); const filterText = ref('');
@ -465,6 +529,16 @@ const rules = reactive({
type: [{ required: true, message: '请填写类型' }], type: [{ required: true, message: '请填写类型' }],
channelDriver: [{ required: true, message: '请填写品牌' }], channelDriver: [{ required: true, message: '请填写品牌' }],
}); });
const computeRoomRef=ref();
//
const showComputeRoom=(data:any)=>{
console.log(data)
const params={
data,
getListApi
}
computeRoomRef.value.acceptParams(params)
}
// //
const ALGORITHM_MAP: Record<number, string> = { const ALGORITHM_MAP: Record<number, string> = {
8: "明烟明火检测", 8: "明烟明火检测",
@ -472,6 +546,19 @@ const ALGORITHM_MAP: Record<number, string> = {
58: "脸部抓拍", 58: "脸部抓拍",
1:"安全帽检测" 1:"安全帽检测"
}; };
const handleCommand = (command: string, row: any) => {
switch (command) {
case 'control':
openControlDialog(row);
break;
case 'blur':
openBlurDialog(row);
break;
case 'algo':
openAlgDialog(row);
break;
}
};
// //
const handleAlgSubmit=async (algForm:AlgorithmTaskVO)=>{ const handleAlgSubmit=async (algForm:AlgorithmTaskVO)=>{
try { try {
@ -735,7 +822,22 @@ const handleSubmit=async ()=>{
// //
const handleEditClose=()=>{ const handleEditClose=()=>{
editCameraDialog.editVisible=false; editCameraDialog.editVisible=false;
}
//
const historicalDataAnalysis=async (data:TreeNode)=>{
// nvrid
const nvrIds=data!.children?.map(item=>item.id);
if(nvrIds?.length==0) {
ElMessage.warning("当前变电站没有NVR设备");
return;
}
const res=await captureHistoryAndCompose({
ids:nvrIds
});
if(res.code==='0000')
{
ElMessage.success('后台任务正在处理中,请稍后查看结果');
}
} }
const cameraIds=[] as number[]; const cameraIds=[] as number[];
const deleteBatchLoading = ref(false); const deleteBatchLoading = ref(false);
@ -744,7 +846,7 @@ const handleBatchChannel=(data: CameraRow[])=>{
ids.length=0; ids.length=0;
for(let i=0;i<data.length;i++) for(let i=0;i<data.length;i++)
{ {
cameraIds.push(data[i].id); cameraIds.push(data[i]?.id);
} }
} }
// //
@ -840,6 +942,14 @@ const controlDialogObj=reactive({
id: -1, id: -1,
curCamera:{} curCamera:{}
}) })
//
const blurDialogObj=reactive({
blurVisible:false,
title:'',
burDetectionVO: {
enable: 0,
} as BlurDetectionUpdateDTO
})
// //
const playDialogObj=reactive({ const playDialogObj=reactive({
playVisible: false, playVisible: false,
@ -862,6 +972,48 @@ const openPlayDialog=(data:CameraRow) =>{
console.warn('channelId 不存在于当前数据中'); console.warn('channelId 不存在于当前数据中');
} }
} }
const blurFormRef=ref();
//
const handelSubmitBlur=async ()=>{
const res=await addOrUpdateBlurDetectionApi(blurDialogObj.burDetectionVO);
if(res.code=='0000')
{
ElMessage.success('提交成功');
blurDialogObj.blurVisible=false;
}else {
ElMessage.success('提交失败');
}
}
//
const openBlurDialog=async (data:CameraRow)=>{
if(data.status==0)
{
ElMessage.warning('摄像头不在线');
return;
}
blurDialogObj.blurVisible=true;
blurDialogObj.title='模糊检测';
if (data.id !== undefined) {
const res=await getBlurDetectionByCameraIdApi({
id: data.id
});
if(res.code=='0000' && res.data)
{
blurDialogObj.burDetectionVO=res.data;
}else {
blurDialogObj.burDetectionVO.cameraId=data.id;
blurDialogObj.burDetectionVO.cameraName=data.name ?? '';
}
} else {
console.warn('channelId 不存在于当前数据中');
}
}
//
const handelCancelBlur=()=>{
blurFormRef.value.resetFields();
blurDialogObj.burDetectionVO={};
blurDialogObj.blurVisible=false;
}
// //
const openControlDialog=(data:CameraRow)=>{ const openControlDialog=(data:CameraRow)=>{
if(data.status==0) if(data.status==0)
@ -882,7 +1034,7 @@ const handleClosePlay= async ()=>{
playDialogObj.playVisible=false playDialogObj.playVisible=false
} }
const handleClose=async ()=>{ const handleClose=async ()=>{
controlDialogObj.controlVisible=false controlDialogObj.controlVisible=false;
} }
// NVRid // NVRid
const NvrId=ref(); const NvrId=ref();
@ -1119,7 +1271,6 @@ onBeforeUnmount(() => {
} }
.sidebar { .sidebar {
width: 320px;
padding: 10px; padding: 10px;
display: flex; display: flex;
flex-direction: column; flex-direction: column;

@ -0,0 +1,240 @@
<template>
<div class="mt-preview">
<mt-preview ref="MtPreviewRef" @on-event-call-back="onEventCallBack"></mt-preview>
<!-- 数据模型 -->
<el-drawer v-model="drawerVisible" :modal="false" title="数据模型文件" modal-penetrable>
<el-radio-group class="vertical-radio-group" v-model="radio">
{{ drawerVisible }}
<el-radio v-for="item in fileNames" :key="item.id" :label="item.id">
{{ item.name }}
</el-radio>
</el-radio-group>
</el-drawer>
</div>
</template>
<script setup lang="ts">
// keep-alive
import {leftAsideStore} from "@/views/teacher/teacherStatistics/components/mt-edit/store/left-aside";
defineOptions({
name: 'PreviewIndex'
});
import MtPreview from '@/views/teacher/teacherStatistics/components/mt-preview/index.vue';
import {onMounted, ref, reactive, onUnmounted, getCurrentInstance} from 'vue';
import { ElMessage } from 'element-plus';
import { globalStore } from '@/views/teacher/teacherStatistics/components/mt-edit/store/global';
import { useExportJsonToDoneJson } from '@/views/teacher/teacherStatistics/components/mt-edit/composables';
import { objectDeepClone, randomString } from '@/views/teacher/teacherStatistics/components/mt-edit/utils';
import { modelApi } from '@/views/teacher/teacherStatistics/utils/request';
import vueSignalGaudy from "@/views/teacher/teacherStatistics/components/vue-xq-test/vue-signal-gaudy.vue";
import VueImg from "../teacherStatistics/components/vue-xq-test/vue-img.vue";
import VueCamera from "@/views/teacher/teacherStatistics/components/vue-xq-test/vue-camera.vue";
import {useRoute} from "vue-router";
const instance = getCurrentInstance();
instance?.appContext.app.component('vue-my-signal-gaudy', vueSignalGaudy);
instance?.appContext.app.component('vue-my-img', VueImg);
instance?.appContext.app.component('vue-my-camera', VueCamera);
leftAsideStore.registerConfig('工作组件', [
{
id: 'vue-my-signal-gaudy',
title: 'vue遥信02',
type: 'vue',
thumbnail: `${import.meta.env.BASE_URL}svgs/signal-gaudy.svg`,
// thumbnail:"/svgs/signal-gaudy.svg",
props: {
moduleType: {
type: 'inputTypeTag',
val: '遥信',
title: '四遥类型'
},
moduleId: {
type: 'inputSelectId',
val: '',
title: '绑定ID'
},
location: {
type: 'select',
val: 'bottom',
title: '名称位置',
options: [
{
value: 'top',
label: '上'
},
{
value: 'bottom',
label: '下'
},
{
value: 'left',
label: '左'
},
{
value: 'right',
label: '右'
}
]
}
}
},
{
id: 'vue-my-img',
title: '图形组件',
type: 'vue',
thumbnail: '/svgs/image.svg',
props: {
moduleId: {
type: 'upload',
val: '--',
title: '绑定'
}
}
},
{
id: 'vue-my-camera',
title: '监控组件',
type: 'vue',
thumbnail: '/svgs/image.svg',
props: {
moduleId: {
type: 'treeSelectModel',
val: '',
name: '',
title: '绑定摄像头',
}
}
}
]);
let drawerVisible = ref(false);
let fileNames: Array<{ id: string; name: string }> = reactive([]);
let radio = ref('-1');
//
let randomString1 = randomString();
//
function fileRead() {
fetch('/dataModes/index.json')
.then((response) => response.json())
.then((data) => {
fileNames = Object.assign(data);
})
.catch((error) => {
console.error('获取JSON文件错误', error);
});
}
const MtPreviewRef = ref<InstanceType<typeof MtPreview>>();
const onEventCallBack = (type: string, item_id: string) => {
console.log(type, item_id);
if (type == 'test-dialog') {
ElMessage.success(`获取到了id:${item_id}`);
}
};
//
function extractValFromProps(props: any) {
const result: Record<string, any> = {};
for (const key in props) {
if (props[key] && typeof props[key] === 'object' && 'val' in props[key]) {
result[key] = props[key].val;
}
}
return result;
}
function initLoad() {
let canvasCfg = globalStore.canvasCfg;
let gridCfg = globalStore.gridCfg;
// let json = globalStore.done_json;
let json: Array<any> = objectDeepClone(globalStore.done_json);
globalStore.done_json.forEach((item) => {
const extractedProps = extractValFromProps(item.props);
json.forEach((obj) => {
if (obj.id === item.id) {
obj.props = extractedProps;
}
});
});
let endJson = {
canvasCfg: canvasCfg,
gridCfg: gridCfg,
json: json
};
console.log('endJson', endJson);
MtPreviewRef.value?.setImportJson(endJson);
}
const route=useRoute();
async function newLoadModel() {
let endJson = {
menuType: ''
};
try {
const response = await modelApi.model_getModelData_post({
...endJson,
roomId: route.query.roomId
});
if (response.code == '0000') {
const result = response.data;
const { canvasCfg, gridCfg, importDoneJson } = useExportJsonToDoneJson(result);
// 1
const scaleX = window.innerWidth / canvasCfg.width;
const scaleY = window.innerHeight / canvasCfg.height;
canvasCfg.scale = Math.min(scaleX, scaleY); // canvasCfg
// importDoneJson props 使 reactive
const processedImportDoneJson = importDoneJson.map((item) => {
if (item.props) {
return {
...item,
props: reactive(item.props || {})
};
}
return {
...item,
props: reactive(item.props || {})
};
});
console.log('processedImportDoneJson:', processedImportDoneJson);
MtPreviewRef.value?.setNewImportJson({ canvasCfg, gridCfg, json: processedImportDoneJson });
console.log('globalStore:', globalStore);
console.log('MtPreviewRef.value', MtPreviewRef.value);
} else {
ElMessage.error(`数据模型加载失败: ${response.code} - ${response.message}`);
}
} catch (error) {
console.error('请求错误:', error);
ElMessage.error('网络请求失败');
}
}
onUnmounted(() => {
});
onMounted(() => {
newLoadModel();
});
</script>
<style scoped>
.vertical-radio-group {
display: flex;
flex-direction: column;
gap: 5px;
align-items: flex-start;
}
</style>

@ -0,0 +1,108 @@
<template>
<div class="room-select-container">
<!-- <div class="tree-header">请选择要绑定模型的机房</div>-->
<slot></slot>
<el-scrollbar max-height="50vh">
<el-tree
ref="roomTreeRef"
:data="treeData"
:props="treeProps"
node-key="id"
highlight-current
default-expand-all
@node-click="handleNodeClick"
>
<template #default="{ node, data }">
<span class="custom-tree-node">
<el-icon v-if="data.type === 'station'" color="#409EFC"><OfficeBuilding /></el-icon>
<el-icon v-else color="#67C23A"><Monitor /></el-icon>
<span style="margin-left: 5px">{{ node.label }}</span>
</span>
</template>
</el-tree>
</el-scrollbar>
<div class="selection-footer" v-if="selectedRoom">
已选机房<el-tag type="success">{{ selectedRoom.name }}</el-tag>
</div>
</div>
</template>
<script setup>
import { ref, onMounted } from 'vue'
import { OfficeBuilding, Monitor } from '@element-plus/icons-vue'
import { ElMessage } from 'element-plus'
import {getTreeSubstation} from "@/api/modules/monitor/Tree.js";
//
const emit = defineEmits(['room-selected'])
const roomTreeRef = ref(null)
const selectedRoom = ref(null)
//
const treeProps = {
children: 'children',
label: 'name'
}
// ()
const treeData = ref([])
//
const fetchTreeData =async () => {
// ry_computer_room
const res=await getTreeSubstation();
if(res.code==='0000')
{
treeData.value=res.data;
}
}
//
const handleNodeClick = (data, node) => {
//
if (data.type === 1) {
ElMessage.warning('请选择具体的机房,而不是变电站')
roomTreeRef.value.setCurrentKey(selectedRoom.value?.id || null) //
return
}
//
selectedRoom.value = data
// ID ID
emit('room-selected', {
roomId: data.id,
substationId: data.substationId,
roomName: data.name
})
}
onMounted(() => {
fetchTreeData()
})
</script>
<style scoped>
.room-select-container {
border-radius: 4px;
padding: 10px;
}
.tree-header {
font-size: 14px;
font-weight: bold;
margin-bottom: 15px;
color: #606266;
padding-bottom: 8px;
}
.custom-tree-node {
display: flex;
align-items: center;
font-size: 14px;
}
.selection-footer {
margin-top: 15px;
padding-top: 10px;
}
</style>

@ -0,0 +1,37 @@
<template>
<svg aria-hidden="true" class="svg-icon">
<use :href="symbolId" :fill="color" />
</svg>
</template>
<script setup>
import { computed } from 'vue'
const props = defineProps({
name: {
type: String,
required: true,
},
prefix: {
type: String,
default: 'icon',
},
//
color: {
type: String,
default: 'currentColor',
},
})
const symbolId = computed(() => `#${props.prefix}-${props.name}`)
</script>
<style scoped>
.svg-icon {
width: 1em;
height: 1em;
vertical-align: -0.15em;
fill: currentColor;
overflow: hidden;
}
</style>

@ -1,6 +1,6 @@
<template> <template>
<!-- <vueImg /> --> <!-- <vueImg /> -->
<div class="w-1/1 h-100vh"> <div class="relative flex-auto w-1/1 h-1/1">
<mt-edit <mt-edit
:use-thumbnail="true" :use-thumbnail="true"
@on-preview-click="onPreviewClick" @on-preview-click="onPreviewClick"
@ -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>
@ -17,13 +16,20 @@ import type { IExportJson } from '@/views/teacher/teacherStatistics/components/m
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 vueSignalGaudy from '@/views/teacher/teacherStatistics/components/vue-xq-test/vue-signal-gaudy.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
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';
// 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);
leftAsideStore.registerConfig('vue四遥组件', [ instance?.appContext.app.component('vue-my-img', VueImg);
instance?.appContext.app.component('vue-my-camera', VueCamera);
leftAsideStore.registerConfig('工作组件', [
{ {
id: 'vue-my-signal-gaudy', id: 'vue-my-signal-gaudy',
title: 'vue遥信02', title: 'vue遥信02',
@ -66,6 +72,33 @@ leftAsideStore.registerConfig('vue四遥组件', [
} }
} }
}, },
{
id: 'vue-my-img',
title: '图形组件',
type: 'vue',
thumbnail: '/svgs/image.svg',
props: {
moduleId: {
type: 'upload',
val: '--',
title: '绑定'
}
}
},
{
id: 'vue-my-camera',
title: '监控组件',
type: 'vue',
thumbnail: '/svgs/image.svg',
props: {
moduleId: {
type: 'treeSelectModel',
val: '',
name: '',
title: '绑定摄像头',
}
}
}
]); ]);
const electrical_modules_files = import.meta.glob('./assets/svgs/electrical/**.svg', { const electrical_modules_files = import.meta.glob('./assets/svgs/electrical/**.svg', {
@ -102,8 +135,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: {
@ -115,7 +147,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({

@ -0,0 +1,127 @@
<script setup lang="ts">
import {nextTick, ref} from "vue";
import type {TreeNode} from "@/api/types/monitor/Tree";
import {ElMessage, type TreeData, type TreeKey} from "element-plus";
import {useCameraBindStore} from "@/stores/modules/cameraBind";
const cameraBindStore=useCameraBindStore();
const filteredTreeData = ref<TreeNode[]>([]);
// ID
const currentCheckedId=ref<number>();
interface Params {
title: string;
rows: any;
getTreeData?: (() => Promise<any>) | undefined;
}
const visible = ref(false);
const treeRef=ref<any>(null);
const paramsProps=ref<Params>({
rows: null,
title:'',
getTreeData: undefined
})
const emit=defineEmits(['bindingCamera']);
//
const acceptParams=async (params:Params)=> {
paramsProps.value = params;
const res = await paramsProps.value.getTreeData!();
if (res.code == '0000') {
filteredTreeData.value = res.data;
}
currentCheckedId.value=paramsProps.value.rows.val;
console.log('当前被选中的节点的ID为',currentCheckedId.value);
visible.value = true;
await nextTick();
if(currentCheckedId.value)
{
treeRef.value.setCheckedKeys([currentCheckedId.value]);
}
else {
treeRef.value.setCheckedKeys([]);
}
}
//
const handleSubmit = async () => {
const checkedNodes = treeRef.value.getCheckedNodes();
// undefined
if (!checkedNodes || checkedNodes.length === 0) {
ElMessage.warning('请先选择一个设备');
return;
}
try {
// pinia
if(currentCheckedId.value!=checkedNodes[0])
{
cameraBindStore.unbindCamera(currentCheckedId.value);
}
emit('bindingCamera', checkedNodes[0]);
treeRef.value.setCheckedKeys([]);
currentCheckedId.value = null; //
visible.value = false;
} catch (error) {
console.log(error);
}
}
const handleCheck=(data: TreeNode, info: { checkedKeys: TreeKey[],checkedNodes: TreeData, halfCheckedKeys: TreeKey[], halfCheckedNodes: TreeData})=>{
//
const isChecked = info.checkedKeys.includes(data.id);
if(isChecked)
{
//
//
const isLeaf=!data.children || data.children.length === 0;
if(!isLeaf)
{
//
const newKeys=info.checkedKeys.filter(key => key !== data.id);
treeRef.value.setCheckedKeys(newKeys);
return;
}else {
treeRef.value.setCheckedKeys([data.id]);
}
}
}
//
defineExpose({
acceptParams
})
</script>
<template>
<el-dialog
v-model="visible"
class="tree-wrapper"
:title="paramsProps.title"
width="500px"
draggable >
<el-scrollbar height="400px">
<el-empty v-if="filteredTreeData.length === 0" description="暂无设备数据" :image-size="60" />
<el-tree-v2
v-else
ref="treeRef"
:height="400"
:data="filteredTreeData"
show-checkbox
check-strictly
@check="handleCheck"
node-key="id"
default-expand-all
:props="{
label: 'name',
children: 'children',
}"
class="transfer-tree"
/>
</el-scrollbar>
<template #footer>
<el-button @click="visible = false"> 取消</el-button>
<el-button type="primary" @click="handleSubmit"> </el-button>
</template>
</el-dialog>
</template>
<style scoped lang="scss">
</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,17 @@ 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); try {
const response = await fileStorageFileListApi(endJson);
tableData.value = []; tableData.value = [];
if (!response.data) { if (!response.data) {
elDialogLoading.value = false; elDialogLoading.value = false;
@ -296,16 +278,20 @@ 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;
}catch (error) {
console.log('error:', error);
elDialogLoading.value = false;
}
} }
onMounted(() => { onMounted(() => {
loadFileList(); loadFileList();

@ -101,6 +101,10 @@
<!-- 数据文件存储 --> <!-- 数据文件存储 -->
<el-button type="primary" size="small" @click="emits('onRightDrawer')"> <el-button type="primary" size="small" @click="emits('onRightDrawer')">
保存模型
</el-button>
<el-button type="primary" size="small" @click="emits('onModelList')">
模型文件 模型文件
</el-button> </el-button>
@ -177,6 +181,7 @@ const emits = defineEmits([
'onDrawLineClick', 'onDrawLineClick',
'onThumbnailClick', 'onThumbnailClick',
'onRightDrawer', 'onRightDrawer',
'onModelList',
'onImageDialog' 'onImageDialog'
]); ]);
const isDark = useDark({ const isDark = useDark({

@ -42,8 +42,7 @@
:offset="getOffset(index + 1)" :offset="getOffset(index + 1)"
> >
<el-image <el-image
draggable="false" style="width: 30px ; height: 30px; filter: brightness(0) invert(1);"
style="width: 60px ; height: 60px;"
:src="item.thumbnail" :src="item.thumbnail"
/> />
<template #content> <template #content>
@ -308,6 +307,8 @@ const onDelLocalFile = ({ id }: ILeftAsideConfigItem) => {
}; };
</script> </script>
<style> <style>
/* 折叠面板:透明背景 */ /* 折叠面板:透明背景 */
#mt-left-aside .el-collapse-item__header, #mt-left-aside .el-collapse-item__header,
#mt-left-aside .el-collapse-item__wrap { #mt-left-aside .el-collapse-item__wrap {
@ -349,8 +350,8 @@ const onDelLocalFile = ({ id }: ILeftAsideConfigItem) => {
} }
#mt-left-aside .w-40px:hover .el-image { #mt-left-aside .w-40px:hover .el-image {
transform: scale(1.18); transform: scale(1.18);
background: rgba(64, 158, 255, 0.18); background: rgba(64, 160, 255, 0.11);
box-shadow: 0 0 8px rgba(64, 158, 255, 0.4); /* box-shadow: 0 0 1px rgba(64, 158, 255, 0.4); */
} }
#mt-left-aside .w-40px:active .el-image { #mt-left-aside .w-40px:active .el-image {
transform: scale(0.92); transform: scale(0.92);

@ -9,7 +9,8 @@
> >
<template v-if="hasDeviceBindSlot" #deviceBind="{ item }"> <template v-if="hasDeviceBindSlot" #deviceBind="{ item }">
<slot name="deviceBind" :item="item" /> </template <slot name="deviceBind" :item="item" /> </template
></select-item-setting> >
</select-item-setting>
<!-- 设置2 --> <!-- 设置2 -->
<page-setting <page-setting
@ -17,7 +18,9 @@
v-model:canvasCfg="globalStore.canvasCfg" v-model:canvasCfg="globalStore.canvasCfg"
v-model:grid-cfg="globalStore.gridCfg" v-model:grid-cfg="globalStore.gridCfg"
v-model:canvas-location="globalStore.canvasCfg.drag_offset" v-model:canvas-location="globalStore.canvasCfg.drag_offset"
></page-setting> >
</page-setting>
</div> </div>
</template> </template>
<script setup lang="ts"> <script setup lang="ts">

@ -67,21 +67,21 @@
</template> </template>
</el-input-tag> </el-input-tag>
<!-- <el-upload <!-- -->
v-else-if="attr_item.type === 'upload' && !attr_item.disabled" <el-input
accept="image/*" style="width: 200px"
:action="BASE_URL + '/fileStorage/saveFile'" v-else-if="attr_item.type === 'treeSelectModel' && !attr_item.disabled"
:on-success="saveImageSuccess" v-model="attr_item.name"
tag-type="success"
size="small"
readonly
> >
<el-button type="primary">绑定图片</el-button> <template #prefix>
</el-upload> --> <el-icon @click="onTreeDialog(attr_item)"><Search /></el-icon>
</template>
</el-input>
<!-- inputTypeTag 类型标签 -->
<el-tag v-else-if="attr_item.type === 'inputTypeTag' && !attr_item.disabled">
{{ attr_item.val }}
</el-tag>
<!-- inputSelectId 输入类型标签 -->
<el-input-tag <el-input-tag
v-else-if="attr_item.type === 'inputSelectId' && !attr_item.disabled" v-else-if="attr_item.type === 'inputSelectId' && !attr_item.disabled"
:model-value="[attr_item.val]" :model-value="[attr_item.val]"
@ -107,6 +107,11 @@
@update-dialog-image-visible="updateDialogImageVisible" @update-dialog-image-visible="updateDialogImageVisible"
@binding-img="bindingImg" @binding-img="bindingImg"
/> />
<!-- 树型模型-->
<treeSelectModel
ref="treeSelectModel"
@bindingCamera="bindingCamera"
/>
<!-- 表单 --> <!-- 表单 -->
<el-dialog <el-dialog
v-model="dialogTableVisible" v-model="dialogTableVisible"
@ -177,22 +182,28 @@
<script setup lang="ts"> <script setup lang="ts">
import type {ILeftAsideConfigItemPublicProps} from '@/views/teacher/teacherStatistics/components/mt-edit/store/types'; import type {ILeftAsideConfigItemPublicProps} from '@/views/teacher/teacherStatistics/components/mt-edit/store/types';
import { import {
ElButton,
ElColorPicker,
ElFormItem, ElFormItem,
ElIcon,
ElInput, ElInput,
ElInputNumber, ElInputNumber, ElMessage,
ElSelect,
ElOption, ElOption,
ElSelect,
ElSwitch, ElSwitch,
ElColorPicker,
ElIcon,
ElButton,
type UploadFile type UploadFile
} from 'element-plus'; } from 'element-plus';
import {Search} from '@element-plus/icons-vue'; import {Search} from '@element-plus/icons-vue';
import JsonEdit from './json-edit.vue'; import JsonEdit from './json-edit.vue';
import { ref, computed, onMounted } from 'vue'; import {computed, onMounted, ref} from 'vue';
import { modelApi, BASE_URL } from '@/views/teacher/teacherStatistics/utils/request'; import imageModel
import imageModel from '@/views/teacher/teacherStatistics/components/mt-edit/components/layout/base-panel/imageModel.vue'; from '@/views/teacher/teacherStatistics/components/mt-edit/components/layout/base-panel/imageModel.vue';
import TreeSelectModel from "@/views/teacher/teacherStatistics/components/custom-componet/treeSelectModel.vue";
import {bindCamerasApi} from "@/api/modules/edgebox/EdgeBox";
import {getTreePreview} from "@/api/modules/monitor/Tree";
import {getBoundCameraListApi} from "@/api/modules/monitor/Camera";
import {useCameraBindStore} from "@/stores/modules/cameraBind";
type SelectItemPropsSettingProps = { type SelectItemPropsSettingProps = {
propsInfo: ILeftAsideConfigItemPublicProps | undefined; propsInfo: ILeftAsideConfigItemPublicProps | undefined;
}; };
@ -216,6 +227,7 @@ interface RecServiceType {
interface ExtendedParentWindow extends Window { interface ExtendedParentWindow extends Window {
Rec?: RecServiceType; Rec?: RecServiceType;
} }
const selectItemPropsSettingProps = withDefaults(defineProps<SelectItemPropsSettingProps>(), {}); const selectItemPropsSettingProps = withDefaults(defineProps<SelectItemPropsSettingProps>(), {});
// //
@ -256,11 +268,39 @@ function handleIconClick(obj: any) {
} }
let attrImg: any; let attrImg: any;
let attrTree=ref({
val: '',
name: '',
type: '',
title: '',
});
const treeSelectModel=ref();
// //
const onImageDialog = (obj: any) => { const onImageDialog = (obj: any) => {
dialogImageVisible.value = true; dialogImageVisible.value = true;
attrImg = obj; attrImg = obj;
console.log('打开绑定按钮',attrImg);
};
//
const onTreeDialog=(obj:any)=>{
const params = {
rows: obj,
title: '绑定设备',
getTreeData:getTreePreview,
}; };
attrTree.value = obj;
console.log('树型',attrTree.value)
treeSelectModel.value.acceptParams(params);
}
const cameraBindStore=useCameraBindStore();
//
const bindingCamera=(camera: any)=>{
//
cameraBindStore.bingCamera(camera.id,attrTree);
attrTree.value.val=camera.id;
attrTree.value.name=camera.name;
}
function bindingImg(obj: any) { function bindingImg(obj: any) {
console.log('绑定图片', obj, attrImg); console.log('绑定图片', obj, attrImg);
attrImg.val = obj.id; attrImg.val = obj.id;

@ -5,7 +5,6 @@
v-if="selectItemSettingProps.itemJson" v-if="selectItemSettingProps.itemJson"
class="select-none" class="select-none"
> >
<button @click="showTest"></button>
<!-- 配置 --> <!-- 配置 -->
<el-tab-pane label="配置" name="config"> <el-tab-pane label="配置" name="config">
<el-collapse v-model="activeNames"> <el-collapse v-model="activeNames">
@ -14,43 +13,6 @@
<el-form-item label="标题" size="small"> <el-form-item label="标题" size="small">
<el-input size="small" v-model="item_title"></el-input> <el-input size="small" v-model="item_title"></el-input>
</el-form-item> </el-form-item>
<!-- <el-form-item label="x轴坐标" size="small">
<el-input-number
size="small"
v-model="item_binfo_left"
@change="emits('addHistory')"
></el-input-number>
</el-form-item>
<el-form-item label="y轴坐标" size="small">
<el-input-number
size="small"
v-model="item_binfo_top"
@change="emits('addHistory')"
></el-input-number>
</el-form-item>
<el-form-item label="宽度" size="small" v-if="!is_line">
<el-input-number
size="small"
v-model="item_binfo_width"
@change="emits('addHistory')"
></el-input-number>
</el-form-item>
<el-form-item label="高度" size="small" v-if="!is_line">
<el-input-number
size="small"
v-model="item_binfo_height"
@change="emits('addHistory')"
></el-input-number>
</el-form-item>
<el-form-item label="旋转角度" size="small" v-if="!is_line">
<el-input-number
size="small"
v-model="item_binfo_angle"
@change="emits('addHistory')"
></el-input-number>
</el-form-item> -->
<el-form-item label="隐藏" size="small"> <el-form-item label="隐藏" size="small">
<el-switch size="small" v-model="item_hide" @change="emits('addHistory')"></el-switch> <el-switch size="small" v-model="item_hide" @change="emits('addHistory')"></el-switch>
</el-form-item> </el-form-item>
@ -58,51 +20,16 @@
<el-switch size="small" v-model="item_lock" @change="emits('addHistory')"></el-switch> <el-switch size="small" v-model="item_lock" @change="emits('addHistory')"></el-switch>
</el-form-item> </el-form-item>
<!-- <el-form-item v-if="!item_lock && !is_line" label="可缩放" size="small">
<el-switch size="small" v-model="item_resize"></el-switch>
</el-form-item>
<el-form-item
v-if="item_resize && !item_lock && !is_line"
label="等比缩放"
size="small"
>
<el-switch size="small" v-model="item_use_proportional_scaling"></el-switch>
</el-form-item> -->
<!-- <el-form-item v-if="!item_lock && !is_line" label="可旋转" size="small">
<el-switch size="small" v-model="item_rotate"></el-switch>
</el-form-item> -->
<!-- xq 自定义属性 --> <!-- xq 自定义属性 -->
<select-item-props-setting <select-item-props-setting
:propsInfo="item_props" :propsInfo="item_props"
@imgUpload="(val: any) => onUpdateModelValue(val)"
@selectById="(val: any) => selectModeTypeByGetId(val)" @selectById="(val: any) => selectModeTypeByGetId(val)"
></select-item-props-setting> ></select-item-props-setting>
</el-form> </el-form>
</el-collapse-item> </el-collapse-item>
<!-- <el-collapse-item title="动画配置" name="2">
<select-item-animate-setting
v-model:common-animates="item_common_animations"
></select-item-animate-setting>
</el-collapse-item> -->
</el-collapse> </el-collapse>
</el-tab-pane> </el-tab-pane>
<!-- 事件 -->
<!-- <el-tab-pane label="事件" name="event">
<select-item-event-setting
:done-json="selectItemSettingProps.doneJson"
v-model:item-events="item_events"
></select-item-event-setting>
</el-tab-pane> -->
<!-- 绑定 -->
<!-- <el-tab-pane label="绑定" name="bind_device">
<slot v-if="hasDeviceBindSlot" name="deviceBind" :item="selectItemSettingProps.itemJson" />
<el-empty v-else description="请传递插槽进行设备绑定页面显示" />
</el-tab-pane> -->
</el-tabs> </el-tabs>
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
@ -145,17 +72,16 @@ type SelectItemSettingProps = {
const imgStore = useImgStore(); const imgStore = useImgStore();
function showTest() { function showTest() {
// console.log('', selectItemSettingProps.itemJson.id); console.log('查看参数', selectItemSettingProps.itemJson);
// console.log('pinia', imgStore.imgList); // console.log('pinia', imgStore.imgList);
} }
const selectItemSettingProps = withDefaults(defineProps<SelectItemSettingProps>(), {}); const selectItemSettingProps = withDefaults(defineProps<SelectItemSettingProps>(), {});
const emits = defineEmits(['update:itemJson', 'addHistory', 'imgUpload', 'selectById']); const emits = defineEmits(['update:itemJson', 'addHistory', 'imgUpload', 'selectById']);
const slots = useSlots(); const slots = useSlots();
function onUpdateModelValue(fileId: string) {
emitter.emit(selectItemSettingProps.itemJson?.id || '', fileId || '');
}
// function onUpdateModelValue(uploadFile: UploadFile) { // function onUpdateModelValue(uploadFile: UploadFile) {
// console.log('', uploadFile.raw); // console.log('', uploadFile.raw);
@ -350,7 +276,6 @@ const item_props = computed({
return selectItemSettingProps.itemJson?.props; return selectItemSettingProps.itemJson?.props;
}, },
set: (value) => { set: (value) => {
console.log('右侧属性修改12');
emits('update:itemJson', { emits('update:itemJson', {
...selectItemSettingProps.itemJson, ...selectItemSettingProps.itemJson,
props: value props: value

@ -19,9 +19,12 @@ export const genExportJson = (
if (m.symbol) { if (m.symbol) {
delete m.symbol; delete m.symbol;
} }
let new_props = {}; let new_props: any = {};
for (const key in m.props) { for (const key in m.props) {
new_props = { ...new_props, ...{ [key]: m.props[key].val } }; new_props[key] = m.props[key].val;
if (m.props[key].name !== undefined) {
new_props[`${key}_name`] = m.props[key].name;
}
} }
return { return {
...m, ...m,
@ -54,7 +57,12 @@ export const useExportJsonToDoneJson = (json: IExportJson) => {
props = { ...props, ...objectDeepClone(find_props) }; props = { ...props, ...objectDeepClone(find_props) };
} }
for (const key in m.props) { for (const key in m.props) {
if (props[key]) props[key].val = m.props[key]; if (props[key]) {
props[key].val = m.props[key];
if (m.props[`${key}_name`] !== undefined) {
props[key].name = m.props[`${key}_name`];
}
}
} }
if (find_item?.symbol) { if (find_item?.symbol) {
symbol = find_item.symbol; symbol = find_item.symbol;

@ -3,10 +3,10 @@
<el-container class="h-1/1"> <el-container class="h-1/1">
<!-- 头部1 --> <!-- 头部1 -->
<el-header <el-header
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 --> >
<!-- &lt;!&ndash; 头部2 &ndash;&gt;-->
<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"
@ -36,6 +36,7 @@
@on-thumbnail-click="onThumbnailClick" @on-thumbnail-click="onThumbnailClick"
@on-draw-line-click="onDrawLineClick" @on-draw-line-click="onDrawLineClick"
@on-right-drawer="onRightDrawer" @on-right-drawer="onRightDrawer"
@on-model-list="onModelList"
@on-image-dialog="onImageDialog" @on-image-dialog="onImageDialog"
> >
</header-panel> </header-panel>
@ -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"
@ -83,18 +81,10 @@
</el-aside> </el-aside>
</el-container> </el-container>
<!-- 底部 -->
<!-- <el-footer height="40px" class="dark:bg-myDarkBgColor ct-border select-none">
<footer-panel></footer-panel>
</el-footer> -->
</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 +92,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,61 +109,43 @@
></done-tree> ></done-tree>
</el-drawer> </el-drawer>
<!-- 数据模型 --> <!-- 选择机房的弹框 -->
<el-drawer v-model="drawerVisible" :modal="false" title="数据模型文件121" modal-penetrable> <el-dialog v-model="drawerVisible" title="保存模型" width="50%"
<!-- <button @click="testData"></button> --> draggable
<!-- <el-button @click="resetSelection"></el-button> --> destroy-on-close
<!-- <el-button @click="getNode"></el-button> --> @close="drawerVisible = false"
<!-- @check="handleCheck" --> align-center>
<el-tree-v2 <RoomSelectTree @roomSelected="handleRoomSelected">
style="max-width: 600px; height: 100px" <div class="tree-header">
:data="treeData" 请选择要绑定模型的机房
:props="props" </div>
:height="450" </RoomSelectTree>
:show-checkbox="true"
:check-strictly="true"
:highlight-current="true"
ref="treeRef"
>
<template #default="{ node }">
<el-icon class="el-icon--left">
<Document v-if="node.isLeaf" />
<Folder v-else-if="!node.expanded" />
<FolderOpened v-else />
</el-icon>
<span class="prefix" :class="{ 'is-leaf': node.isLeaf }">{{ node.label }}</span>
<!-- <span>{{ node.label }}</span> -->
</template>
</el-tree-v2>
<template #footer> <template #footer>
<div class="drawer-footer"> <div class="dialog-footer">
<el-button >保存模型11</el-button> <el-button @click="doSaveModel"></el-button>
<el-button v-loading.fullscreen.lock="fullscreenLoading" <el-button @click="drawerVisible = false">取消</el-button>
>加载模型2</el-button
>
<!-- <el-button @click="coverModel"> </el-button> -->
<el-button > 移除模型 </el-button>
</div> </div>
</template> </template>
</el-drawer> </el-dialog>
<!-- 选择模型文件的弹框 -->
<!-- 数据保存弹框 --> <el-dialog v-model="modelListDialogVisible" title="模型文件列表" width="50%"
<el-dialog v-model="dialogFormVisible" title="数据模型保存" width="500"> draggable
<span>确定保存或者覆盖 {{ fileName }} 模型文件</span> destroy-on-close
@close="modelListDialogVisible = false"
align-center>
<RoomSelectTree @roomSelected="handleRoomSelected">
<div class="tree-header">
请选择机房模型
</div>
</RoomSelectTree>
<template #footer> <template #footer>
<div class="dialog-footer"> <div class="dialog-footer">
<el-button @click="dialogFormVisible = false">取消</el-button> <el-button @click="loadModel"></el-button>
<el-button <el-button @click="modelListDialogVisible = false">取消</el-button>
type="primary"
v-loading.fullscreen.lock="fullscreenLoading"
>
确定
</el-button>
</div> </div>
</template> </template>
</el-dialog> </el-dialog>
<!-- end -->
<!-- 图片模型 --> <!-- 图片模型 -->
<imageModel <imageModel
v-if="dialogImageVisible" v-if="dialogImageVisible"
@ -198,17 +166,7 @@ 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';
@ -219,6 +177,8 @@ 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';
import RoomSelectTree from "@/views/teacher/teacherStatistics/components/custom-componet/RoomSelectTree.vue";
interface Tree { interface Tree {
id: string; id: string;
@ -227,14 +187,7 @@ interface Tree {
children?: Tree[]; children?: Tree[];
} }
const props = {
value: 'id',
label: 'label',
children: 'children',
disabled: 'disabled'
};
let treeData: Tree[] = [];
function updateDialogImageVisible() { function updateDialogImageVisible() {
dialogImageVisible.value = false; dialogImageVisible.value = false;
@ -248,7 +201,7 @@ const onImageDialog = () => {
let drawerVisible = ref(false); let drawerVisible = ref(false);
let dialogFormVisible = ref(false); let dialogFormVisible = ref(false);
let dialogImageVisible = ref(false); let dialogImageVisible = ref(false);
let fileName = ref('');
let menuType = ref(''); let menuType = ref('');
type MtEditProps = { type MtEditProps = {
@ -286,15 +239,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 +268,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);
}; };
@ -356,23 +299,107 @@ const onImportYes = async () => {
ElMessage.error('导入失败,请检查数据格式'); ElMessage.error('导入失败,请检查数据格式');
} }
}; };
const roomData=ref();
const handleRoomSelected=(data:any)=>{
roomData.value=data;
}
//
async function doSaveModel() {
fullscreenLoading.value = true;
if(!roomData.value)
{
ElMessage.error('请选择机房');
return;
}
const { exportJson } = genExportJson(globalStore.canvasCfg, globalStore.gridCfg, globalStore.done_json);
let endJson = {
menuType: menuType.value,
canvasCfg: exportJson.canvasCfg,
gridCfg: exportJson.gridCfg,
json: exportJson.json
};
if (endJson.json.length == 0)
{
ElMessage.error('请添加模型');
return;
}
try {
const response = await saveOrUpdateModelData({
...endJson,
roomId: roomData.value.roomId,
substationId:roomData.value.substationId
});
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('网络请求失败');
}
}
async function loadModel() {
fullscreenLoading.value = false;
let endJson = {
menuType: ''
};
console.log('请求数据类型:', typeof JSON.stringify(endJson));
try {
const response = await getModelData({
...endJson,
roomId: roomData.value.roomId,
});
// 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;
}
}
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 = () => {
@ -382,16 +409,17 @@ const onDrawLineClick = (val: boolean) => {
line_append_enable.value = val; line_append_enable.value = val;
}; };
let fileNames: Array<{ id: string; name: string }> = reactive([]);
let radio = ref('-1');
// //
const fullscreenLoading = ref(false); const fullscreenLoading = ref(false);
const modelListDialogVisible = ref(false);
const onRightDrawer = () => { const onRightDrawer = () => {
drawerVisible.value = true; drawerVisible.value = true;
// fileRead(); // fileRead();
}; };
const onModelList=()=>{
modelListDialogVisible.value=true;
}
onMounted(() => { onMounted(() => {
// fileRead(); // fileRead();
@ -423,7 +451,6 @@ const setImportJson = (exportJson: IExportJson) => {
return true; return true;
}; };
const treeRef = ref<InstanceType<typeof ElTreeV2> | null>();
defineExpose({ defineExpose({
setImportJson setImportJson
}); });
@ -443,11 +470,21 @@ defineExpose({
.mt-edit-aside { .mt-edit-aside {
transition: width 0.3s; transition: width 0.3s;
} }
.el-container[data-v-3a4046f9] .el-header
{
height: 40px !important;
}
.vertical-radio-group { .vertical-radio-group {
display: flex; display: flex;
flex-direction: column; flex-direction: column;
gap: 5px; gap: 5px;
align-items: flex-start; align-items: flex-start;
} }
.tree-header {
font-size: 14px;
font-weight: bold;
margin-bottom: 15px;
color: #606266;
padding-bottom: 8px;
}
</style> </style>

@ -14,9 +14,7 @@ export const leftAsideStore: ILeftAside = reactive({
ElMessage.info(`title:${title}已被系统占用,请更换名称!`); ElMessage.info(`title:${title}已被系统占用,请更换名称!`);
return; return;
} }
if (leftAsideStore.config.has(title)) { // 已存在时静默覆盖,不再提示
ElMessage.info(`title:${title}已存在,已经将其配置覆盖`);
}
const cfg: ILeftAsideConfigItem[] = config.map((m) => { const cfg: ILeftAsideConfigItem[] = config.map((m) => {
if (m.type == 'svg') { if (m.type == 'svg') {
const { symbol_str, width, height } = svgToSymbol(m.svg!, m.id); const { symbol_str, width, height } = svgToSymbol(m.svg!, m.id);

@ -10,7 +10,9 @@ export type ILeftAsideConfigItemPublicPropsType =
| 'upload' | 'upload'
| 'inputSelectId' | 'inputSelectId'
| 'inputTypeTag' //inputTypeTag 必须和 inputSelectId 一起使用 | 'inputTypeTag' //inputTypeTag 必须和 inputSelectId 一起使用
| 'inputSelectImgId'; | 'inputSelectImgId'
| 'treeSelectModel'
;
// 开放注册配置 // 开放注册配置
export type ILeftAsideConfigItemPublicProps = Record< export type ILeftAsideConfigItemPublicProps = Record<
string, string,
@ -18,6 +20,7 @@ export type ILeftAsideConfigItemPublicProps = Record<
title: string; //显示在属性面板的标题 title: string; //显示在属性面板的标题
type: ILeftAsideConfigItemPublicPropsType; //属性的类型决定了修改属性的方式 type: ILeftAsideConfigItemPublicPropsType; //属性的类型决定了修改属性的方式
val: any; val: any;
name?: string;
options?: any; //比如说修改属性的时候用到了下拉框,这里面就可以放下拉框的选项 options?: any; //比如说修改属性的时候用到了下拉框,这里面就可以放下拉框的选项
disabled?: boolean; //如果禁用了将不会显示到右侧属性面板里,但是仍然可以通过代码修改属性 disabled?: boolean; //如果禁用了将不会显示到右侧属性面板里,但是仍然可以通过代码修改属性
} }

@ -197,7 +197,7 @@ defineExpose({
.scrollbar { .scrollbar {
width: 100%; width: 100%;
height: 94.7vh; height: 100%;
} }
.canvasArea { .canvasArea {

@ -1,170 +0,0 @@
<template>
<el-row :gutter="20" style="display: flex; align-items: center">
<el-col :span="24" class="centered-col">
<el-text class="mx-1" size="large" style="font-size: 16px; margin-bottom: 15px"
>配置说明</el-text
>
</el-col>
</el-row>
<el-row :gutter="20">
<el-col :span="24" class="centered-col">
<el-table
:data="tableData"
:header-cell-style="{ color: '#3b3b3b ' }"
:style="{ height: '200px' }"
>
<!-- 序号 -->
<el-table-column
type="index"
label="序号"
:resizable="false"
min-width="60"
align="center"
/>
<!-- 站端名称 -->
<el-table-column
prop="pilotLamp"
label="指示灯"
:resizable="false"
width="120"
align="center"
/>
<el-table-column label="正常时状态" :resizable="false" align="center" min-width="180">
<template #default="scope">
<el-tag v-if="!scope.row.order" type="info" :style="{ fontSize: computedSize }"
>不亮</el-tag
>
<el-tag v-else type="primary" :style="{ fontSize: computedSize }"></el-tag>
</template>
</el-table-column>
<!-- 事件内容 -->
<el-table-column
prop="explain"
label="说明"
:resizable="false"
min-width="280"
show-overflow-tooltip
align="left"
/>
</el-table>
</el-col>
</el-row>
</template>
<script lang="ts" setup>
import { ref, reactive, watch, onMounted, computed } from 'vue';
const props = defineProps({
fontFamily: {
type: String,
default: '黑体'
},
fontSize: {
type: Number,
default: 12
},
testColor: {
type: String,
default: '#ffffff'
}
});
const form = reactive({
fileId: null,
fileName: null,
fileDate: null
});
//
interface UnitException {
pilotLamp: string; //
order: boolean; //
explain: string; //
}
const tableData: UnitException[] = [
{
pilotLamp: '火灾',
order: false,
explain: '未隔离系统中一个或多个设备启动了控制器的报警,变亮'
},
{
pilotLamp: '故障',
order: false,
explain: '系统发生一个或多个故障,变亮'
},
{
pilotLamp: '隔离',
order: false,
explain: '系统中任何设备或防区被隔离,变亮'
},
{
pilotLamp: '主供电源',
order: true,
explain: '交流电源接通时亮'
},
{
pilotLamp: '备用电源',
order: false,
explain: '交流电源不接通,且备用电源(直流24V)接通时,变亮'
},
{
pilotLamp: '讯响器屏蔽',
order: false,
explain: '外部讯响器发生故障时,变亮'
},
{
pilotLamp: '测试模式',
order: false,
explain: '控制板处于测试方式时,变亮'
},
{
pilotLamp: '电源故障',
order: false,
explain: '主电或备电发生故障,变亮'
},
{
pilotLamp: '讯响器故障',
order: false,
explain: '外部讯响器发生故障,变亮'
},
{
pilotLamp: '接地故障',
order: false,
explain: '控制回路发生直流接地时,此信号灯亮'
},
{
pilotLamp: '系统故障',
order: false,
explain: '控制器中主处理器发生故障,变亮'
}
];
//
let computedSize = computed({
//
get() {
return props.fontSize + 'px';
}, //
set(val) {
console.log('有人修改了fullName', val);
}
});
</script>
<style scoped>
.centered-col .el-table {
font-size: v-bind('computedSize');
/* font-size: 12px; */
}
.centered-col {
display: flex;
justify-content: center; /* 水平居中 */
align-items: center; /* 垂直居中 */
}
</style>

@ -1,234 +0,0 @@
<template>
<div class="cardInfo">
<el-card shadow="card">
<h2 class="cardHead">{{ headline }}</h2>
<!-- 动态生成 el-row el-col -->
<template v-for="(chunk, rowIndex) in chunkArray(floorOne, 6)" :key="rowIndex">
<el-row :gutter="10" class="mb-4">
<el-col v-for="(item, colIndex) in chunk" :key="colIndex" :span="24 / chunk.length">
<div>
<!-- class="grid-content" -->
<el-tag v-if="item.policeType == '1'" type="primary">{{ item.name }}</el-tag>
<el-tag v-else-if="item.policeType == '2'" type="warning">{{ item.name }}</el-tag>
<el-tag v-else type="danger">{{ item.name }}</el-tag>
<!-- {{ item.name }} -->
</div>
</el-col>
</el-row>
</template>
</el-card>
</div>
</template>
<script lang="ts" setup>
import { reactive, ref, computed } from 'vue';
const props = defineProps({
fontFamily: {
type: String,
default: 'Segoe UI'
},
fontSize: {
type: Number,
default: 14
},
headline: {
type: String,
default: '自定义标题'
}
});
let computedSize = computed({
//
get() {
return props.fontSize + 'px';
}, //
set(val) {
console.log('有人修改了fullName', val);
}
});
interface CallThePolice {
id: string; //id
name: string; //
policeType: string; //
}
//
function chunkArray<T>(array: T[], size: number): T[][] {
const result: T[][] = [];
for (let i = 0; i < array.length; i += size) {
result.push(array.slice(i, i + size));
}
return result;
}
// policeType
function getBackgroundColor(policeType: string): string {
switch (policeType) {
case '1':
return ''; //
case '2':
return 'rgb(238, 190, 119)'; //
case '3':
return 'rgb(196, 86, 86)'; //
default:
return ''; //
}
}
let val = ref('1');
let floorOne = reactive<Array<CallThePolice>>([
{
id: '1',
name: '1号感烟',
policeType: '1'
},
{
id: '2',
name: '2号感烟',
policeType: '1'
},
{
id: '3',
name: '3号感烟',
policeType: '2'
},
{
id: '4',
name: '4号感烟',
policeType: '1'
},
{
id: '5',
name: '5号感烟',
policeType: '1'
},
{
id: '6',
name: '6号感烟',
policeType: '1'
},
{
id: '7',
name: '7号感烟',
policeType: '1'
},
{
id: '8',
name: '8号感烟',
policeType: '1'
},
{
id: '9',
name: '9号感烟',
policeType: '1'
},
{
id: '10',
name: '10号感烟',
policeType: '1'
},
{
id: '11',
name: '11号感烟',
policeType: '1'
},
{
id: '12',
name: '12号感烟',
policeType: '3'
},
{
id: '13',
name: '13号感烟',
policeType: '2'
},
{
id: '14',
name: '14号感烟',
policeType: '1'
},
{
id: '15',
name: '15号感烟',
policeType: '1'
},
{
id: '16',
name: '16号感烟',
policeType: '3'
},
{
id: '17',
name: '17号感烟',
policeType: '1'
},
{
id: '18',
name: '18号感烟',
policeType: '1'
},
{
id: '19',
name: '1号声光报警器',
policeType: '1'
},
{
id: '20',
name: '2号声光报警器',
policeType: '1'
},
{
id: '21',
name: '1号手报',
policeType: '1'
},
{
id: '22',
name: '2号手报',
policeType: '2'
}
]);
</script>
<style scoped>
/* 去除 el-card__body 的默认边距 */
:deep(.el-card__body) {
padding-top: 5px !important;
padding-bottom: 0 !important;
/* margin: 2 !important; */
}
.cardInfo {
margin: 0;
padding: 0;
width: 100%;
height: 100%;
/* max-width: 480px; */
/* color: v-bind('props.testColor'); */
font-family: v-bind('props.fontFamily');
/* font-family: 'Segoe UI'; */
font-size: v-bind('computedSize');
}
.card {
margin: 0;
padding: 0;
width: 100%;
height: 100%;
}
.cardHead {
margin: 0;
padding-bottom: 5px;
/* padding-bottom: 10px; */
text-align: center;
}
.grid-content {
/* background-color: #9e0000; */
display: flex;
flex-direction: column; /* 垂直排列 */
justify-content: center; /* 垂直居中 */
align-items: flex-start;
}
</style>

@ -1,501 +0,0 @@
<template>
<el-form :model="form" label-width="auto" style="max-width: 100%; margin-left: 2vh" inline>
<!-- 第一行 -->
<el-row :gutter="1">
<!-- 设备名称 -->
<el-col :span="6">
<el-form-item label="设备名称">
<el-input v-model="form.fileName" style="width: 100%" />
</el-form-item>
</el-col>
<!-- 所属展柜 -->
<el-col :span="6">
<el-form-item label="所属展柜">
<el-input v-model="form.fileName" />
</el-form-item>
</el-col>
<!-- 所属展柜 -->
<el-col :span="6">
<el-form-item label="子系统" style="width: 100%">
<el-select v-model="form.fileId" style="width: 100%">
<el-option label="消防系统" value="shanghai" />
<el-option label="识别系统" value="beijing" />
</el-select>
</el-form-item>
</el-col>
</el-row>
<!-- 第二行 -->
<el-row :gutter="1">
<el-col :span="6" style="display: flex; align-items: left">
<el-form-item label="告警级别" style="width: 100%">
<el-select v-model="form.fileDate" style="width: 100%">
<el-option label="危急" value="shanghai" />
<el-option label="一般" value="beijing" />
<el-option label="正常" value="chengdu" />
</el-select>
</el-form-item>
</el-col>
<!-- 创建时间 -->
<el-col :span="8">
<el-form-item label="时间" style="display: flex; align-items: left">
<el-date-picker
v-model="form.fileDate"
type="daterange"
range-separator="-"
start-placeholder="开始时间"
end-placeholder="结束时间"
size="default"
/>
</el-form-item>
</el-col>
<el-col :span="5"> </el-col>
<el-col :span="5">
<el-button type="primary">查询</el-button>
<el-button type="info">重置</el-button>
</el-col>
</el-row>
</el-form>
<el-table
:data="tableData"
style="width: 100%; margin-left: 2vh"
:row-class-name="tableRowClassName"
:header-cell-style="{ color: '#3b3b3b ' }"
:style="{ 'max-height': '470px' }"
>
<!-- 序号 -->
<el-table-column type="index" label="序号" :resizable="false" min-width="60" align="center" />
<!-- 站端名称 -->
<el-table-column
prop="inkanetName"
label="站端名称"
:resizable="false"
width="180"
align="center"
/>
<!-- 时间 -->
<el-table-column prop="date" label="时间" :resizable="false" min-width="180" align="center" />
<!-- 设备名称 -->
<el-table-column
prop="equipmentName"
label="设备名称"
:resizable="false"
min-width="180"
align="center"
/>
<!-- 事件内容 -->
<el-table-column
prop="eventContent"
label="事件内容"
:resizable="false"
min-width="280"
show-overflow-tooltip
align="center"
/>
<!-- 当前测量值 -->
<el-table-column
v-if="unauthorizedBool"
prop="measurementValue"
label="当前测量值"
:resizable="false"
min-width="180"
show-overflow-tooltip
align="center"
/>
<!-- 越限范围 -->
<el-table-column
v-if="unauthorizedBool"
prop="limit"
label="越限范围"
:resizable="false"
min-width="180"
show-overflow-tooltip
align="center"
/>
<!-- 偏移量 -->
<el-table-column
v-if="unauthorizedBool"
prop="offset"
label="偏移量"
:resizable="false"
min-width="180"
show-overflow-tooltip
align="center"
/>
<!-- 告警级别 -->
<el-table-column
v-if="wholeSiteMessageBool"
prop="alarmLevel"
label="告警级别"
:resizable="false"
align="center"
min-width="180"
>
<template #default="scope">
<el-tag v-if="scope.row.alarmLevel === 1" type="danger"></el-tag>
<el-tag v-else-if="scope.row.alarmLevel === 2" type="warning">一般</el-tag>
<el-tag v-else type="primary">正常</el-tag>
</template>
</el-table-column>
<!-- 所属子系统 -->
<el-table-column
prop="subsystem"
label="所属子系统"
:resizable="false"
min-width="180"
align="center"
/>
<!-- 处理结果 -->
<el-table-column
v-if="wholeSiteMessageBool || unauthorizedBool"
prop="processingStructure"
label="处理结果"
:resizable="false"
min-width="180"
align="center"
/>
<!-- 操作 -->
<el-table-column label="操作" :resizable="false" align="center" min-width="180" fixed="right">
<template #default="scope">
<el-button v-if="scope.row.operation === 0" type="primary" size="small"></el-button>
<el-tag v-else type="primary">已解决</el-tag>
</template>
</el-table-column>
</el-table>
<div style="margin-left: 2vh">
<el-pagination
v-model:current-page="currentPage"
v-model:page-size="pageSize"
:page-sizes="[10, 15, 20]"
:pager-count="3"
size="default"
layout=",total,sizes,-> ,prev, pager, next"
:total="pageTotal"
@size-change="handleSizeChange"
@current-change="handleCurrentChange"
/>
</div>
</template>
<script lang="ts" setup>
import { ref, reactive, watch, onMounted } from 'vue';
onMounted(() => {
loadRequestUrl(props.dataSource);
});
const props = defineProps({
dataSource: {
type: String,
default: '--'
}
});
let requestUrl = ref<string>();
let wholeSiteMessageBool = ref(false); //
let unitExceptionBool = ref(false); //
let unauthorizedBool = ref(false); //
const loadRequestUrl = (moduleId: string) => {
console.log('moduleId:', moduleId);
switch (moduleId) {
case 'wholeSiteMessage':
tableData.value = [];
wholeSiteMessageBool.value = true;
unitExceptionBool.value = false;
unauthorizedBool.value = false;
requestUrl.value = '/api/wholeSiteMessage';
//
tableData.value = wholeSiteMessageList;
break;
case 'unitException':
tableData.value = [];
wholeSiteMessageBool.value = false;
unitExceptionBool.value = true;
unauthorizedBool.value = false;
requestUrl.value = '/api/unitException';
//
tableData.value = unitExceptionList;
break;
case 'unauthorized':
tableData.value = [];
wholeSiteMessageBool.value = false;
unitExceptionBool.value = false;
unauthorizedBool.value = true;
requestUrl.value = '/api/unauthorized';
//
tableData.value = unauthorizedBoolList;
break;
}
};
watch(
() => props.dataSource,
(newVal, oldVal) => {
console.log('moduleId:', newVal, oldVal);
loadRequestUrl(newVal);
}
);
let currentPage = ref(1);
let pageSize = ref(10);
let pageTotal = ref(45);
const handleSizeChange = (val: number) => {
console.log('size点击:', `${val} items per page`);
};
const handleCurrentChange = (val: number) => {
console.log('Current点击:', `current page: ${val}`);
};
const form = reactive({
fileId: null,
fileName: null,
fileDate: null
});
//
interface WholeSiteMessage {
inkanetName: string; //
date: string; //
equipmentName: string; //
eventContent: string; //
alarmLevel: number; // 1 2 3
subsystem: string; //
processingStructure: string; //
operation: number; // 0 1
}
//
interface UnauthorizedBool {
inkanetName: string; //
date: string; //
equipmentName: string; //
eventContent: string; //
offset: number; //
limit: string; //
measurementValue: string; //
subsystem: string; //
processingStructure: string; //
operation: number; // 0 1
}
//
interface UnitException {
inkanetName: string; //
date: string; //
equipmentName: string; //
eventContent: string; //
subsystem: string; //
operation: number; // 0 1
}
const tableRowClassName = ({ row }: { row: WholeSiteMessage }) => {
if (row.alarmLevel === 1) {
return 'danger-row';
} else if (row.alarmLevel === 2) {
return 'warning-row';
}
return '';
};
let tableData = ref<any[]>([]);
const unitExceptionList: UnitException[] = [
{
inkanetName: '南河变电站',
date: '2026-02-04 20:48:08',
equipmentName: '3号感烟探头',
eventContent: '生产综合楼1楼蓄电池室3号感烟探头报警,生',
subsystem: '消防系统',
operation: 0
},
{
inkanetName: '南河变电站',
date: '2026-02-04 20:48:08',
equipmentName: '3号感烟探头',
eventContent: '生产综合楼1楼蓄电池室3号感烟探头报',
subsystem: '消防系统',
operation: 0
},
{
inkanetName: '南河变电站',
date: '2026-02-04 20:48:08',
equipmentName: '3号感烟探头',
eventContent: '生产综合楼1楼蓄电池室3号感烟探头报警',
subsystem: '消防系统',
operation: 0
}
];
const unauthorizedBoolList: UnauthorizedBool[] = [
{
inkanetName: '南河变电站',
date: '2026-02-04 20:48:08',
equipmentName: '3号感烟探头',
eventContent: '生产综合楼1楼蓄电池室3号感烟探头报警,生产综合楼1楼蓄电池室3号感烟探头报警',
offset: 10,
limit: '10-20',
measurementValue: '10.0',
subsystem: '消防系统',
processingStructure: '成功',
operation: 0
},
{
inkanetName: '南河变电站',
date: '2026-02-04 20:48:08',
equipmentName: '3号感烟探头',
eventContent: '生产综合楼1楼蓄电池室3号感烟探头报警,生产综合楼1楼蓄电池室3号感烟探头报警',
offset: 10,
limit: '10-20',
measurementValue: '10.0',
subsystem: '消防系统',
processingStructure: '成功',
operation: 0
},
{
inkanetName: '南河变电站',
date: '2026-02-04 20:48:08',
equipmentName: '3号感烟探头',
eventContent: '生产综合楼1楼蓄电池室3号感烟探头报警,生产综合楼1楼蓄电池室3号感烟探头报警',
offset: 10,
limit: '10-20',
measurementValue: '10.0',
subsystem: '消防系统',
processingStructure: '成功',
operation: 0
}
];
const wholeSiteMessageList: WholeSiteMessage[] = [
{
inkanetName: '南河变电站',
date: '2026-02-04 20:48:08',
equipmentName: '3号感烟探头',
eventContent: '生产综合楼1楼蓄电池室3号感烟探头报警,生产综合楼1楼蓄电池室3号感烟探头报警',
alarmLevel: 1,
subsystem: '消防系统',
processingStructure: '成功',
operation: 0
},
{
inkanetName: '南河变电站',
date: '2026-01-27 15:28:08',
equipmentName: '荣耀科技',
eventContent: '荣耀科技打卡指纹识别告警',
alarmLevel: 3,
subsystem: '指纹识别',
processingStructure: '成功',
operation: 0
},
{
inkanetName: '南河变电站',
date: '2026-01-25 12:48:08',
equipmentName: '电子围栏',
eventContent: '电子围栏一区(东一)入侵告警',
alarmLevel: 2,
subsystem: '安全防范',
processingStructure: '成功',
operation: 1
},
{
inkanetName: '南河变电站',
date: '2026-01-26 15:28:08',
equipmentName: '荣耀科技',
eventContent: '荣耀科技研发部人脸识别告警',
alarmLevel: 3,
subsystem: '人脸识别',
processingStructure: '成功',
operation: 0
},
{
inkanetName: '南河变电站',
date: '2026-02-04 20:48:08',
equipmentName: '3号感烟探头',
eventContent: '生产综合楼1楼蓄电池室3号感烟探头报警',
alarmLevel: 3,
subsystem: '消防系统',
processingStructure: '成功',
operation: 0
},
{
inkanetName: '南河变电站',
date: '2026-01-27 15:28:08',
equipmentName: '荣耀科技',
eventContent: '荣耀科技打卡指纹识别告警',
alarmLevel: 3,
subsystem: '指纹识别',
processingStructure: '成功',
operation: 0
},
{
inkanetName: '南河变电站',
date: '2026-01-25 12:48:08',
equipmentName: '电子围栏',
eventContent: '电子围栏一区(东一)入侵告警',
alarmLevel: 3,
subsystem: '安全防范',
processingStructure: '成功',
operation: 1
},
{
inkanetName: '南河变电站',
date: '2026-01-26 15:28:08',
equipmentName: '荣耀科技',
eventContent: '荣耀科技研发部人脸识别告警',
alarmLevel: 3,
subsystem: '人脸识别',
processingStructure: '成功',
operation: 0
},
{
inkanetName: '南河变电站',
date: '2026-02-04 20:48:08',
equipmentName: '3号感烟探头',
eventContent: '生产综合楼1楼蓄电池室3号感烟探头报警',
alarmLevel: 3,
subsystem: '消防系统',
processingStructure: '成功',
operation: 0
},
{
inkanetName: '南河变电站',
date: '2026-01-27 15:28:08',
equipmentName: '荣耀科技',
eventContent: '荣耀科技打卡指纹识别告警',
alarmLevel: 3,
subsystem: '指纹识别',
processingStructure: '成功',
operation: 0
}
];
</script>
<style>
.el-table .danger-row {
/* background-color: #3b3b3b !important; */
color: rgb(196, 86, 86) !important;
}
.el-table .warning-row {
/* background-color: #fdf6ec !important; */
color: rgb(184, 130, 48) !important;
}
</style>

@ -0,0 +1,96 @@
<template>
<div class="main-img" >
<el-icon :size="50" color="#67C23A" >
<SvgIcon name="Cam" @click="handlePlay" />
</el-icon>
</div>
<teleport to="body">
<el-dialog
v-model="customDraggingVisible"
title="摄像机画面"
:modal="false"
style="height: 60vh"
center
destroy-on-close
draggable
>
<div class="video-content">
<VideoPlayer :camera-id="VideoPlayerId" class="player-instance"/>
</div>
<template #footer>
<div class="dialog-footer">
<el-button @click="customDraggingVisible = false">取消</el-button>
<el-button type="primary" @click="customDraggingVisible = false"> 确定 </el-button>
</div>
</template>
</el-dialog>
</teleport>
</template>
<script setup lang="ts">
import { ref } from 'vue';
import { VideoCamera } from '@element-plus/icons-vue';
import {useCameraBindStore} from "@/stores/modules/cameraBind";
import VideoPlayer from "@/views/sysmonitortree/sysMonitorTree/components/VideoPlayer.vue";
import {ElMessage} from "element-plus";
import SvgIcon from "@/components/SvgIcon/index.vue";
const customDraggingVisible = ref(false);
const cameraBindStore=useCameraBindStore();
const props = defineProps({
definitionItemJson: {
type: Object,
default: () => ({})
},
moduleId: {
type: Number,
default: ''
}
});
const VideoPlayerId =ref();
//
const handlePlay = () => {
const id=Number(props.moduleId);
//
if(cameraBindStore.isCameraBound(id))
{
VideoPlayerId.value=id;
console.log('当前播放的摄像头消息', props.definitionItemJson)
customDraggingVisible.value = true;
}else {
ElMessage.warning('还未绑定摄像头');
}
};
</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%;
}
.video-content {
flex: 1;
width: 100%;
height: 100%;
overflow: hidden;
.player-instance {
width: 100%;
height: 100%;
}
}
</style>

@ -1,77 +0,0 @@
<template>
<div>
<el-table :data="tableData" style="width: 100%">
<el-table-column prop="date" label="Date" width="180" />
<el-table-column prop="name" label="Name" width="180" />
<el-table-column prop="address" label="Address" />
</el-table>
</div>
</template>
<script lang="ts" setup>
const tableData = [
{
date: '2016-05-03',
name: 'Tom',
address: 'No. 189, Grove St, Los Angeles'
},
{
date: '2016-05-02',
name: 'Tom',
address: 'No. 189, Grove St, Los Angeles'
},
{
date: '2016-05-04',
name: 'Tom',
address: 'No. 189, Grove St, Los Angeles'
},
{
date: '2016-05-01',
name: 'Tom',
address: 'No. 189, Grove St, Los Angeles'
}
];
</script>
<style scoped>
/* 修改表格整体背景色 */
::v-deep .el-table {
background-color: #40beff; /* 浅蓝色背景 */
}
/* 修改表头背景色和文字颜色 */
::v-deep .el-table th.el-table__cell {
background-color: #2e349e; /* 深蓝色表头 */
color: white; /* 白色文字 */
font-weight: bold;
}
/* 修改表头边框颜色 */
::v-deep .el-table th.el-table__cell {
border: 1px solid #151a3d; /* 深色边框 */
}
/* 修改奇偶行背景色 */
::v-deep .el-table .el-table__body-wrapper tr.el-table__row:nth-child(odd) {
background-color: #40beff; /* 奇数行使用浅蓝色 */
}
::v-deep .el-table .el-table__body-wrapper tr.el-table__row:nth-child(even) {
background-color: #2e349e; /* 偶数行使用深蓝色 */
}
/* 修改鼠标悬停时的行颜色 */
::v-deep .el-table .el-table__body tr:hover > td {
background-color: rgba(64, 190, 255, 0.7); /* 半透明浅蓝色悬停效果 */
}
/* 修改选中行的颜色 */
::v-deep .el-table .el-table__body tr.current-row > td {
background-color: #151a3d; /* 选中行使用最深蓝色 */
}
/* 修改单元格边框颜色 */
::v-deep .el-table td.el-table__cell {
border: 1px solid #151a3d; /* 深色边框 */
}
</style>

@ -1,311 +0,0 @@
<template>
<el-card class="card">
<h2 class="cardHead">基本信息</h2>
<div class="cardBody">
<el-row :gutter="0" style="display: flex; align-items: center">
<el-col :span="8" :offset="4"> 名称 </el-col>
<el-col :span="4">
<el-tag type="primary">火灾报警系统主机</el-tag>
</el-col>
</el-row>
<el-row :gutter="0" style="display: flex; align-items: center">
<el-col :span="8" :offset="4"> 产品型号 </el-col>
<el-col :span="4">
<el-tag type="primary">JB-QB-GST200</el-tag>
</el-col>
</el-row>
<el-row :gutter="0" style="display: flex; align-items: center">
<el-col :span="8" :offset="4"> 厂家 </el-col>
<el-col :span="4">
<el-tag type="primary">海湾公司</el-tag>
</el-col>
</el-row>
<el-row :gutter="0" style="display: flex; align-items: center">
<el-col :span="8" :offset="4"> 投运日期 </el-col>
<el-col :span="4">
<el-tag type="primary">2015-10-10</el-tag>
</el-col>
</el-row>
<el-row :gutter="0" style="display: flex; align-items: center">
<el-col :span="8" :offset="4"> 电源电压 </el-col>
<el-col :span="4">
<el-tag type="primary">220V AC</el-tag>
</el-col>
</el-row>
<el-row :gutter="0" style="display: flex; align-items: center">
<el-col :span="8" :offset="4"> 频率 </el-col>
<el-col :span="4">
<el-tag type="primary">50Hz</el-tag>
</el-col>
</el-row>
<el-row :gutter="0" style="display: flex; align-items: center">
<el-col :span="8" :offset="4"> 输出电压 </el-col>
<el-col :span="4">
<el-tag type="primary">24V DC</el-tag>
</el-col>
</el-row>
<el-row :gutter="0" style="display: flex; align-items: center">
<el-col :span="8" :offset="4"> 输出电流 </el-col>
<el-col :span="4">
<el-tag type="primary">5A</el-tag>
</el-col>
</el-row>
<el-row :gutter="0" style="display: flex; align-items: center">
<el-col :span="8" :offset="4"> 环境温度 </el-col>
<el-col :span="4">
<el-tag type="primary">-5 ~ 45</el-tag>
</el-col>
</el-row>
<el-row :gutter="0" style="display: flex; align-items: center">
<el-col :span="8" :offset="4"> 相对湿度 </el-col>
<el-col :span="4">
<el-tag type="primary">5% ~ 95%</el-tag>
</el-col>
</el-row>
<el-row :gutter="0" style="display: flex; align-items: center">
<el-col :span="8" :offset="4"> 维保单位 </el-col>
<el-col :span="4">
<el-tag type="primary">陕西消防科技有限公司</el-tag>
</el-col>
</el-row>
<el-row :gutter="0" style="display: flex; align-items: center">
<el-col :span="8" :offset="4"> 联系电话 </el-col>
<el-col :span="4">
<el-tag type="primary">188*****00 </el-tag>
</el-col>
</el-row>
</div>
</el-card>
<!-- :append-to-body="true" -->
<el-dialog
v-model="dialogTableVisible"
title="灭火动作流程"
style="height: 60vh"
width="45%"
:append-to-body="true"
>
<el-scrollbar height="42vh">
<el-timeline mode="alternate-reverse">
<el-timeline-item
v-for="(activity, index) in activities"
:key="index"
center
:timestamp="activity.timestamp"
>
{{ activity.content }}
</el-timeline-item>
</el-timeline>
</el-scrollbar>
<!-- 按钮 -->
<template #footer>
<div class="dialog-footer">
<el-button @click="dialogTableVisible = false">退出</el-button>
</div>
</template>
</el-dialog>
</template>
<script lang="ts" setup>
import { computed, ref, watch, onMounted } from 'vue';
let dialogTableVisible = ref(false);
const props = defineProps({
fontFamily: {
type: String,
default: 'Segoe UI'
},
fontSize: {
type: Number,
default: 14
},
testColor: {
type: String,
default: '#000000'
},
testBool: {
type: Boolean,
default: true
},
dataSource: {
type: String,
default: '--'
}
});
const activities = [
{
content: '灭火允许',
timestamp: '2018-04-15'
},
{
content: '手动启动',
timestamp: '2018-04-16'
},
{
content: '紧急停止',
timestamp: '2018-04-17'
},
{
content: '手动启动',
timestamp: '2018-04-18'
},
{
content: '灭火允许',
timestamp: '2018-04-15'
},
{
content: '手动启动',
timestamp: '2018-04-16'
},
{
content: '紧急停止',
timestamp: '2018-04-17'
},
{
content: '手动启动',
timestamp: '2018-04-18'
}
];
onMounted(() => {
loadDataSource(props.dataSource);
});
watch(
() => props.dataSource,
(newVal, oldVal) => {
loadDataSource(newVal);
}
);
//
let globalOverviewBool = ref(false);
//
let fireExtinguishingBool = ref(false);
//
let firePoliceBool = ref(false);
function loadDataSource(val: string) {
if (val === 'globalOverview') {
globalOverviewBool.value = true;
fireExtinguishingBool.value = false;
firePoliceBool.value = false;
} else if (val === 'fireExtinguishing') {
globalOverviewBool.value = false;
fireExtinguishingBool.value = true;
firePoliceBool.value = false;
} else if (val === 'firePolice') {
globalOverviewBool.value = false;
fireExtinguishingBool.value = false;
firePoliceBool.value = true;
}
}
let computedSize = computed({
//
get() {
return props.fontSize + 'px';
}, //
set(val) {
console.log('有人修改了fullName', val);
}
});
</script>
<style scoped>
.blue-circle {
width: 12px;
height: 12px;
border-radius: 50%;
background-color: #409eff;
display: inline-block;
margin-left: 5px;
vertical-align: middle;
}
.blueBordered {
background-color: rgb(217, 236, 255);
border: 2px solid rgb(160, 207, 255);
}
.redBordered {
background-color: rgb(253, 226, 226);
border: 2px solid rgb(250, 182, 182);
}
.greenBordered {
background-color: rgb(225, 243, 216);
border: 2px solid rgb(179, 225, 157);
}
:deep(.el-card__body) {
padding: 0;
padding-top: 10px;
padding-left: 10px;
}
:deep(.adTextDetailDialogClass .el-dialog__body) {
max-height: calc(100vh - 150px);
overflow: auto;
border-top: 1px solid #dfdfdf;
border-bottom: 1px solid #dfdfdf;
}
:deep(.adTextDetailDialogClass .el-dialog) {
position: fixed;
height: 20vh !important;
background: #000;
left: 0 !important;
right: 0 !important;
top: 0 !important;
bottom: 0 !important;
margin: auto !important;
}
.el-row .el-col {
margin-bottom: 6px; /* 设置上下间距 */
}
P:hover {
background-color: #f0f0f0; /* 悬停时的底色,您可以根据需要调整颜色 */
}
.card {
margin: 0;
padding: 0;
width: 100%;
height: 100%;
/* max-width: 480px; */
color: v-bind('props.testColor');
font-family: v-bind('props.fontFamily');
/* font-family: 'Segoe UI'; */
font-size: v-bind('computedSize');
}
.cardHead {
margin: 0;
padding: 0px;
padding-bottom: 5px;
text-align: center;
}
.cardBody {
width: 100%;
margin: 0 0%;
padding: 0px 0%;
/* padding-bottom: 15px; */
}
</style>

@ -1,93 +0,0 @@
<template>
<div class="cardInfo">
<el-card shadow="card" style="height: 100%">
<h2 class="cardHead">主机</h2>
<el-row :gutter="20">
<el-col :span="6">
<el-tag type="success">运行</el-tag>
</el-col>
<el-col :span="6">
<el-tag type="info">报警</el-tag>
</el-col>
<el-col :span="6">
<el-button key="primary" type="primary" size="small" text bg> 自动启动 </el-button>
</el-col>
<el-col :span="6">
<el-button key="info" type="info" size="small" text bg>手动启动</el-button>
</el-col>
</el-row>
</el-card>
</div>
</template>
<script lang="ts" setup>
import { reactive, ref, computed } from 'vue';
const props = defineProps({
fontFamily: {
type: String,
default: 'Segoe UI'
},
fontSize: {
type: Number,
default: 14
},
headline: {
type: String,
default: '自定义标题'
}
});
let computedSize = computed({
//
get() {
return props.fontSize + 'px';
}, //
set(val) {
console.log('有人修改了fullName', val);
}
});
</script>
<style scoped>
/* 去除 el-card__body 的默认边距 */
:deep(.el-card__body) {
padding-top: 5px !important;
padding-bottom: 0 !important;
/* margin: 2 !important; */
}
.cardInfo {
margin: 0;
padding: 0;
width: 100%;
height: 100%;
/* max-width: 480px; */
/* color: v-bind('props.testColor'); */
font-family: v-bind('props.fontFamily');
/* font-family: 'Segoe UI'; */
font-size: v-bind('computedSize');
}
.card {
margin: 0;
padding: 0;
width: 100%;
height: 100%;
}
.cardHead {
margin: 0;
padding-top: 10px;
padding-bottom: 15px;
/* padding-bottom: 10px; */
text-align: center;
}
.grid-content {
/* background-color: #9e0000; */
display: flex;
flex-direction: column; /* 垂直排列 */
justify-content: center; /* 垂直居中 */
align-items: flex-start;
}
</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>

@ -17,15 +17,14 @@
<script setup lang="ts"> <script setup lang="ts">
import { ref, onMounted, onUnmounted, watch } from 'vue'; import { ref, onMounted, onUnmounted, watch } from 'vue';
import { useImgStore } from '@/components/mt-edit/store/imgStore'; import emitter from '@/views/teacher/teacherStatistics/utils/emitter';
import emitter from '@/utils/emitter';
import { Picture } from '@element-plus/icons-vue'; import { Picture } from '@element-plus/icons-vue';
import { modelApi, BASE_URL } from '@/utils/request'; import { modelApi, BASE_URL } from '@/views/teacher/teacherStatistics/utils/request';
import { formatFilePath, formatTimestamp } from '@/utils/dataFormatter'; import { fileStorageFileListApi,getFileObj,File_Url,editFileNameByIdApi,deleteFileByIdApi } from '@/api/modules/teacher/teacherStatistics';
import { formatFilePath } from '@/views/teacher/teacherStatistics/utils/dataFormatter';
// const displayImageUrl = ref<string | null>(null); // const displayImageUrl = ref<string | null>(null);
const displayImageUrl = ref<string>(); const displayImageUrl = ref<string>();
const imgStore = useImgStore();
const props = defineProps({ const props = defineProps({
definitionItemJson: { definitionItemJson: {
@ -43,7 +42,7 @@ console.log('父传子1', props.definitionItemJson.id);
watch( watch(
() => props.moduleId, () => props.moduleId,
(newVal, oldVal) => { (newVal, oldVal) => {
console.log('moduleId:', newVal, oldVal); console.log(`moduleId:新值:${newVal},旧值:${oldVal}`);
loadFileList(newVal); loadFileList(newVal);
} }
); );
@ -52,23 +51,29 @@ let imgUrl = ref<string>();
// //
async function loadFileList(fileId: string) { async function loadFileList(fileId: string) {
console.log('loadFileList:', fileId);
let endJson = { let endJson = {
id: fileId id: fileId
}; };
const response = await modelApi.fileStorage_file_list_post(endJson); if(fileId==='--')
return;
// 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) => { if(response.data.rows)
imgUrl.value = formatFilePath(BASE_URL, value.filePath); {
response.data.rows.forEach((value: any) => {
imgUrl.value = formatFilePath(File_Url+'/admin', value.filePath);
}); });
console.log('imgUrl:', imgUrl); }
} }
emitter.on('imgUpload:' + props.definitionItemJson.id, (uploadFile) => { // emitter.on('imgUpload:' + props.definitionItemJson.id, (uploadFile) => {
console.log('imgUpload:', uploadFile); // console.log('imgUpload:', uploadFile);
displayImageUrl.value = uploadFile as string; // displayImageUrl.value = uploadFile as string;
}); // });
onUnmounted(() => { onUnmounted(() => {
console.log('组件卸载'); console.log('组件卸载');

@ -1,117 +0,0 @@
<template>
<div class="slider-demo-block">
<span class="demonstration">温度</span>
<el-slider v-model="temperatureVal" :format-tooltip="temperatureValConst" disabled />
</div>
<div class="slider-demo-block">
<span class="demonstration">湿度</span>
<el-slider v-model="humidityVal" :format-tooltip="humidityValConst" disabled />
</div>
<div class="slider-demo-block">
<span class="demonstration">风力</span>
<el-slider v-model="windVal" :format-tooltip="windValConst" disabled />
</div>
<div class="slider-demo-block">
<span class="demonstration">雨量</span>
<el-slider v-model="rainVal" :format-tooltip="rainValConst" disabled />
</div>
</template>
<script setup lang="ts">
import { ref, toRefs } from 'vue';
// let props = defineProps({
// temperatureVal: Number,
// humidityVal: Number,
// windVal: Number,
// rainVal: Number,
// draggable: Boolea
// })
const props = defineProps({
temperatureVal: {
type: Number,
default: 12
},
humidityVal: {
type: Number,
default: 12
},
windVal: {
type: Number,
default: 12
},
rainVal: {
type: Number,
default: 12
}
});
let { temperatureVal, humidityVal, windVal, rainVal } = toRefs(props);
// let value2 = ref<number>(props.humidityVal ?? 0);
// let value3 = ref<number>(props.windVal ?? 0);
// let value4 = ref<number>(props.rainVal ?? 0);
const formatTooltip = (val: number) => {
return val / 100;
};
//
const temperatureValConst = (val: number) => {
return val + ' ℃';
};
// 湿
const humidityValConst = (val: number) => {
return val + ' %RH';
};
// wind
const windValConst = (val: number) => {
return val / 10 + ' m/s';
};
// rainVal
const rainValConst = (val: number) => {
return val + ' mm';
};
</script>
<style scoped>
.slider-demo-block {
max-width: 300px;
display: flex;
align-items: center;
}
.slider-demo-block .el-slider {
margin-top: 0;
margin-left: 12px;
}
.slider-demo-block .demonstration {
font-size: 14px;
color: var(--el-text-color-secondary);
line-height: 35px;
flex: 1;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
margin-bottom: 0;
}
.slider-demo-block .demonstration + .el-slider {
flex: 0 0 70%;
}
/* 添加以下样式来缩小滑块圆圈 */
:deep(.el-slider__button) {
width: 14px !important; /* 默认是 20px */
height: 14px !important; /* 默认是 20px */
border-radius: 50%; /* 确保是圆形 */
}
/* 如果你也想缩小激活状态下的圆圈 */
:deep(.el-slider__button-wrapper:hover .el-slider__button) {
width: 20px !important; /* 悬停时稍微大一点 */
height: 20px !important;
}
</style>

@ -1,98 +0,0 @@
<template>
<el-card class="card">
<template #header>
<h2 class="cardHead">{{ props.headline }}</h2>
</template>
<div class="cardBody">
<p>2025-04-25 14:22:17 2号主变油色谱总烃超标</p>
<p>2018-03-21 15:28:11 220kV GIS室O2气体含量低报警2号传感器O2浓度15%</p>
<p>2018-04-25 14:22:17 2号主变油色谱总烃超标</p>
<p>2018-03-21 15:28:11 220kV GIS室O2气体含量低报警2号传感器O2浓度15%</p>
<p>2018-04-25 14:22:17 2号主变油色谱总烃超标</p>
<p>2018-03-21 15:28:11 220kV GIS室O2气体含量低报警2号传感器O2浓度15%</p>
</div>
</el-card>
</template>
<script lang="ts" setup>
import { computed } from 'vue';
import { globalStore } from '@/components/mt-edit/store/global';
const props = defineProps({
fontFamily: {
type: String,
default: 'Segoe UI'
},
fontSize: {
type: Number,
default: 14
},
testColor: {
type: String,
default: '#000000'
},
headline: {
type: String,
default: '自定义标题'
}
});
console.log();
function test2() {
console.log('props:', props);
}
let computedSize = computed({
//
get() {
return props.fontSize + 'px';
}, //
set(val) {
console.log('有人修改了fullName', val);
}
});
</script>
<style scoped>
:deep(.el-card__body) {
margin: 0;
padding: 0px;
}
:deep(.el-card__header) {
margin: 0;
padding: 10px 0px;
}
P {
margin: 3px 0px;
padding: 2px;
transition: background-color 0.3s ease;
}
P:hover {
background-color: #f0f0f0; /* 悬停时的底色,您可以根据需要调整颜色 */
}
.card {
margin: 0;
padding: 0;
height: 100%;
width: 100;
/* max-width: 480px; */
color: v-bind('props.testColor');
font-family: v-bind('props.fontFamily');
/* font-family: 'Segoe UI'; */
font-size: v-bind('computedSize');
}
.cardHead {
margin: 0;
padding: 0px;
text-align: center;
}
.cardBody {
margin: 0;
padding: 0px;
padding-bottom: 15px;
}
</style>

@ -1,336 +0,0 @@
<template>
<!-- <div style="display: flex; gap: 10px; align-items: center">
<el-popover
:width="300"
:visible="null"
popper-style="box-shadow: rgb(14 18 22 / 35%) 0px 10px 38px -10px, rgb(14 18 22 / 20%) 0px 10px 20px -15px; padding: 20px;"
>
<template #reference>
<el-text
style="font-weight: bold; text-align: center; display: block"
type="primary"
color="rgb(236, 245, 255)"
>
{{ computedVal }}
</el-text>
</template>
<template #default>
<div class="demo-rich-conent" style="display: flex; gap: 2px; flex-direction: column">
<div class="demo-progress">
<el-progress
type="dashboard"
:width="100"
:percentage="percentage"
:color="colors"
:format="customFormat"
/>
</div>
<el-row :gutter="6">
<el-col :span="12" :xs="24" :sm="12" :md="6" class="elColInfo">
<el-statistic title="绑定ID" :value="null">
<template #suffix>
<el-tag size="default" type="primary">{{ moduleId }}</el-tag>
</template>
</el-statistic>
</el-col>
<el-col :span="12" :xs="24" :sm="12" :md="12" class="elColInfo">
<el-statistic title="节点名称" :value="null">
<template #suffix>
<el-tag size="default" type="success">{{ modeName }}</el-tag>
</template>
</el-statistic>
</el-col>
<el-col :span="12" :xs="24" :sm="12" :md="6" class="elColInfo">
<el-statistic title="类型" :value="null">
<template #suffix>
<el-tag size="default" type="primary">{{ moduleType }}</el-tag>
</template>
</el-statistic>
</el-col>
</el-row>
</div>
</template>
</el-popover>
<el-tag type="primary">{{ modeName }}</el-tag>
</div> -->
<el-row v-if="location === 'top'">
<el-col :span="24" class="flex-center">
<el-tag type="primary" size="small" style="opacity: 0.9">
<el-text type="primary" style="max-width: 65px" size="small" truncated>
{{ modeName }}
</el-text>
</el-tag>
</el-col>
</el-row>
<el-row justify="center">
<el-col :span="12" class="flex-center" v-if="location === 'left'">
<el-tag type="primary" size="small" style="opacity: 0.9">
<el-text type="primary" style="max-width: 65px" size="small" truncated>
{{ modeName }}
</el-text>
</el-tag>
</el-col>
<el-col :span="12" class="flex-center">
<div style="display: flex; gap: 10px; align-items: center">
<el-popover
:width="300"
:disabled="true"
popper-style="box-shadow: rgb(14 18 22 / 35%) 0px 10px 38px -10px, rgb(14 18 22 / 20%) 0px 10px 20px -15px; padding: 20px;"
>
<template #reference>
<el-text style="font-weight: bold; text-align: center; display: block">
{{ computedVal }}
</el-text>
</template>
<template #default>
<div class="demo-rich-conent" style="display: flex; gap: 2px; flex-direction: column">
<div class="demo-progress">
<el-progress
type="dashboard"
:width="100"
:percentage="percentage"
:color="colors"
:format="customFormat"
/>
</div>
<el-row :gutter="6">
<el-col :span="12" :xs="24" :sm="12" :md="6" class="elColInfo">
<el-statistic title="绑定ID" :value="null">
<template #suffix>
<el-tag size="default" type="primary">{{ moduleId }}</el-tag>
</template>
</el-statistic>
</el-col>
<el-col :span="12" :xs="24" :sm="12" :md="12" class="elColInfo">
<el-statistic title="节点名称" :value="null">
<template #suffix>
<el-tag size="default" type="success">{{ modeName }}</el-tag>
</template>
</el-statistic>
</el-col>
<el-col :span="12" :xs="24" :sm="12" :md="6" class="elColInfo">
<el-statistic title="类型" :value="null">
<template #suffix>
<el-tag size="default" type="primary">{{ moduleType }}</el-tag>
</template>
</el-statistic>
</el-col>
</el-row>
</div>
</template>
</el-popover>
</div>
</el-col>
<el-col :span="12" class="flex-center" v-if="location === 'right'">
<el-tag type="primary" size="small" style="opacity: 0.9">
<el-text type="primary" style="max-width: 65px" size="small" truncated>
{{ modeName }}
</el-text>
</el-tag>
</el-col>
</el-row>
<el-row v-if="location === 'bottom'">
<el-col :span="24" class="flex-center">
<el-tag type="primary" size="small" style="opacity: 0.9">
<el-text type="primary" style="max-width: 65px; opacity: 1" size="small" truncated>
{{ modeName }}
</el-text>
</el-tag>
</el-col>
</el-row>
</template>
<script lang="ts" setup>
import { onMounted, ref, computed, onUnmounted, watch } from 'vue';
import { useNodeByModelsStore } from '@/components/mt-edit/store/nodeByModels';
import emitter from '@/utils/emitter';
const nodeByModelsStore = useNodeByModelsStore();
onMounted(() => {
loadingModuleById();
saveodeByModels();
});
onUnmounted(() => {
console.log('组件卸载');
deleteByModels();
emitter.off(props.definitionItemJson.id);
});
const props = defineProps({
moduleType: {
type: String,
default: '--'
},
moduleId: {
type: String,
default: '--'
},
definitionItemJson: {
type: Object,
default: () => ({})
},
location: {
type: String,
default: 'bottom'
}
});
console.log('itemjson:', props.definitionItemJson.id);
function saveodeByModels() {
if (props.moduleId == '' || props.moduleId == undefined || props.moduleId == '--') return;
nodeByModelsStore.saveOrUpdate(props.moduleId, props.definitionItemJson.id);
}
function deleteByModels() {
if (props.moduleId == '' || props.moduleId == undefined || props.moduleId == '--') return;
nodeByModelsStore.delete(props.moduleId, props.definitionItemJson.id);
}
watch(
() => props.moduleId,
(newVal, oldVal) => {
loadingModuleById();
nodeByModelsStore.change(newVal, oldVal, props.definitionItemJson.id);
}
);
emitter.on(props.definitionItemJson.id, (value) => {
console.log('触发事件', value);
loadingModuleById();
});
let moduleUnit = ref<string>();
let modeName = ref<string>();
let percentage = ref<number>();
function getModuleById(moduleId: string) {
const globalData = (window as any).globalData;
if (!globalData || moduleId == undefined || moduleId == '' || props.moduleId == '--') {
console.warn('globalData 未初始化');
return null;
}
// 访
if (globalData instanceof Map) {
// Map
return globalData.get(moduleId);
} else {
//
return globalData[moduleId];
}
}
function loadingModuleById() {
// 使访 globalData
let module = getModuleById(props.moduleId);
if (props.moduleId !== '' && props.moduleId !== undefined && props.moduleId !== '--' && module) {
if (module) {
modeName.value = module.node.name;
percentage.value = module.double;
moduleUnit.value = module.node.unit;
}
}
}
//
let computedVal = computed({
//
get() {
if (percentage.value == undefined && moduleUnit.value == undefined) return '--';
return percentage.value + '' + moduleUnit.value;
},
//
set() {}
});
const customFormat = () => {
//
if (percentage.value == undefined && moduleUnit.value == undefined) return '--';
return percentage.value + '' + moduleUnit.value;
};
const colors = [
{ color: '#f56c6c', percentage: 20 },
{ color: '#e6a23c', percentage: 40 },
{ color: '#5cb87a', percentage: 60 },
{ color: '#1989fa', percentage: 80 },
{ color: '#6f7ad3', percentage: 100 }
];
</script>
<style scoped>
/* 设置固定宽度和文本省略 */
.ellipsis-tag {
width: 65px; /* 设置固定宽度 */
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
display: inline-block;
align-items: center; /* 水平居中 */
}
.flex-center {
display: flex;
flex-direction: column; /* 垂直排列 */
justify-content: center; /* 垂直居中 */
align-items: center; /* 水平居中 */
}
.el-statistic {
/* 控制文字大小 */
--el-statistic-content-font-size: 16px;
}
.elColInfo {
text-align: center;
}
:deep(.el-progress__text) {
/* 控制文字大小 */
font-size: 16px !important;
}
.demo-progress .el-progress--line {
margin-bottom: 15px;
max-width: 500px;
}
.demo-progress .el-progress--circle {
margin-right: 15px;
}
.fontStyle {
color: rgb(51, 126, 204);
}
h3 {
margin: 0;
padding: 0;
text-align: center;
/* padding-top: 5px;
padding-bottom: 5px; */
}
.demo-radius .radius {
height: 28px;
min-width: 66px;
width: fit-content;
border: 2px solid rgba(51, 125, 204, 0.205);
border-radius: 0;
padding: 0 8px;
box-sizing: border-box;
}
</style>

@ -1,93 +0,0 @@
<template>
<div class="cardInfo">
<el-card shadow="card" style="height: 100%">
<h2 class="cardHead">主机1111</h2>
<el-row :gutter="20">
<el-col :span="6">
<el-tag type="success">运行</el-tag>
</el-col>
<el-col :span="6">
<el-tag type="info">报警</el-tag>
</el-col>
<el-col :span="6">
<el-button key="primary" type="primary" size="small" text bg> 自动启动 </el-button>
</el-col>
<el-col :span="6">
<el-button key="info" type="info" size="small" text bg>手动启动</el-button>
</el-col>
</el-row>
</el-card>
</div>
</template>
<script lang="ts" setup>
import { reactive, ref, computed } from 'vue';
const props = defineProps({
fontFamily: {
type: String,
default: 'Segoe UI'
},
fontSize: {
type: Number,
default: 14
},
headline: {
type: String,
default: '自定义标题'
}
});
let computedSize = computed({
//
get() {
return props.fontSize + 'px';
}, //
set(val) {
console.log('有人修改了fullName', val);
}
});
</script>
<style scoped>
/* 去除 el-card__body 的默认边距 */
:deep(.el-card__body) {
padding-top: 5px !important;
padding-bottom: 0 !important;
/* margin: 2 !important; */
}
.cardInfo {
margin: 0;
padding: 0;
width: 100%;
height: 100%;
/* max-width: 480px; */
/* color: v-bind('props.testColor'); */
font-family: v-bind('props.fontFamily');
/* font-family: 'Segoe UI'; */
font-size: v-bind('computedSize');
}
.card {
margin: 0;
padding: 0;
width: 100%;
height: 100%;
}
.cardHead {
margin: 0;
padding-top: 10px;
padding-bottom: 15px;
/* padding-bottom: 10px; */
text-align: center;
}
.grid-content {
/* background-color: #9e0000; */
display: flex;
flex-direction: column; /* 垂直排列 */
justify-content: center; /* 垂直居中 */
align-items: flex-start;
}
</style>

@ -1,150 +0,0 @@
<template>
<el-card class="card">
<h3 class="cardHead">光字牌</h3>
<div class="cardBody">
<el-row :gutter="0" style="display: flex; align-items: center" class="row-spacing">
<el-col :span="24" class="full-width-col">
<el-tag type="primary" size="large" class="full-width-tag">生产综合楼1楼火灾报警</el-tag>
</el-col>
</el-row>
<el-row :gutter="0" style="display: flex; align-items: center" class="row-spacing">
<el-col :span="24" class="full-width-col">
<el-tag type="danger" size="large" class="full-width-tag blinking-tag"
>生产综合楼2楼火灾报警</el-tag
>
</el-col>
</el-row>
<el-row :gutter="0" style="display: flex; align-items: center" class="row-spacing">
<el-col :span="24" class="full-width-col">
<el-tag type="primary" size="large" class="full-width-tag"
>220kV配电装置楼1楼火灾报警</el-tag
>
</el-col>
</el-row>
<el-row :gutter="0" style="display: flex; align-items: center" class="row-spacing">
<el-col :span="24" class="full-width-col">
<el-tag type="primary" size="large" class="full-width-tag"
>220kV配电装置楼2楼火灾报警</el-tag
>
</el-col>
</el-row>
<el-row :gutter="0" style="display: flex; align-items: center" class="row-spacing">
<el-col :span="24" class="full-width-col">
<el-tag type="primary" size="large" class="full-width-tag">主供电源故障</el-tag>
</el-col>
</el-row>
<el-row :gutter="0" style="display: flex; align-items: center" class="row-spacing">
<el-col :span="24" class="full-width-col">
<el-tag type="primary" size="large" class="full-width-tag">备用电源故障</el-tag>
</el-col>
</el-row>
<el-row :gutter="0" style="display: flex; align-items: center">
<el-col :span="24" class="full-width-col">
<el-tag type="primary" size="large" class="full-width-tag">接地故障</el-tag>
</el-col>
</el-row>
</div>
</el-card>
</template>
<script lang="ts" setup>
import { computed, ref, watch, onMounted } from 'vue';
const props = defineProps({
fontFamily: {
type: String,
default: 'Segoe UI'
},
fontSize: {
type: Number,
default: 14
},
testColor: {
type: String,
default: '#000000'
},
testBool: {
type: Boolean,
default: true
}
});
let computedSize = computed({
//
get() {
return props.fontSize + 'px';
}, //
set(val) {
console.log('有人修改了fullName', val);
}
});
</script>
<style scoped>
.row-spacing {
margin-bottom: 10px; /* 每行之间的间隔 */
}
.full-width-col {
display: flex;
justify-content: center; /* 水平居中 */
align-items: center; /* 垂直居中 */
}
.full-width-tag {
width: 100%; /* 填满父容器 */
text-align: center; /* 文字水平居中 */
display: flex; /* 使用 flexbox */
justify-content: center; /* 文字水平居中 */
align-items: center; /* 文字垂直居中 */
font-size: v-bind('computedSize');
}
/* 闪烁动画 */
.blinking-tag {
animation: blink 1s infinite; /* 1秒一次无限循环 */
}
@keyframes blink {
0%,
100% {
opacity: 1; /* 完全不透明 */
}
50% {
opacity: 0.3; /* 半透明 */
}
}
.card {
margin: 0;
padding: 0;
width: 100%;
height: 100%;
/* max-width: 480px; */
color: v-bind('props.testColor');
font-family: v-bind('props.fontFamily');
/* font-family: 'Segoe UI'; */
font-size: v-bind('computedSize' - 1);
}
.cardHead {
margin: 0;
padding: 0px;
padding-bottom: 5px;
text-align: center;
font-size: v-bind('computedSize' + 2);
}
.cardBody {
width: 100%;
margin: 0 0%;
padding: 0px 0%;
/* padding-bottom: 15px; */
}
</style>

@ -1,112 +0,0 @@
<template>
<!-- <el-input-number v-model="num" :step="2" class="custom-input-number" /> -->
<div style="display: flex; align-items: center">
<el-popover
:width="300"
popper-style="box-shadow: rgb(14 18 22 / 35%) 0px 10px 38px -10px, rgb(14 18 22 / 20%) 0px 10px 20px -15px; padding: 20px;"
>
<template #reference>
<el-icon class="iconStyle" :size="30">
<SwitchFilled />
</el-icon>
</template>
<template #default>
<div class="demo-rich-conent" style="display: flex; gap: 2px; flex-direction: column">
<div class="progress-container">
<div class="input-button-row">
<el-input-number v-model="num" :step="2" class="custom-input-number" />
<el-button-group size="small" direction="horizontal">
<el-button type="primary" :icon="Check" />
<el-button
type="primary"
color="rgb(177, 179, 184)"
@click="num = 0"
:icon="RefreshLeft"
/>
</el-button-group>
</div>
</div>
<el-row :gutter="6">
<el-col :span="12" :xs="24" :sm="12" :md="8" class="elColInfo">
<el-statistic title="绑定ID" value="1100101" />
</el-col>
<el-col :span="12" :xs="24" :sm="12" :md="8" class="elColInfo">
<el-statistic title="状态" :value="null">
<template #suffix>
<el-tag size="default" type="success">开启</el-tag>
</template>
</el-statistic>
</el-col>
<el-col :span="12" :xs="24" :sm="12" :md="8" class="elColInfo">
<el-statistic title="类型" :value="null">
<template #suffix>
<el-tag size="default" type="primary">遥调</el-tag>
</template>
</el-statistic>
</el-col>
</el-row>
</div>
</template>
</el-popover>
</div>
</template>
<script lang="ts" setup>
import { ref } from 'vue';
import {
ChatLineRound,
Male,
WarningFilled,
SwitchFilled,
Check,
RefreshLeft
} from '@element-plus/icons-vue';
const num = ref(5);
</script>
<style scoped>
.iconStyle {
size: 30;
color: #409eff;
}
.el-statistic {
/* 控制文字大小 */
--el-statistic-content-font-size: 16px;
}
.elColInfo {
text-align: center;
}
.progress-container {
width: 120px;
margin-top: 5px;
margin-bottom: 25px;
}
/* 或者通过CSS变量设置 */
/* :deep(.el-input-number) {
width: 20px;
} */
/* 如果要设置内部输入框的宽度 */
/* :deep(.el-input__wrapper) {
width: 20px;
} */
.input-button-row {
width: 300px;
display: flex;
gap: 35px; /* 设置组件之间的间距 */
align-items: center; /* 垂直居中对齐 */
}
.custom-input-number {
width: 140px; /* 设置自定义宽度 */
}
</style>

@ -1,375 +0,0 @@
<template>
<el-card class="card">
<h2 class="cardHead">运行信息</h2>
<div class="cardBody" v-if="globalOverviewBool">
<el-row :gutter="0" style="display: flex; align-items: center">
<el-col :span="8" :offset="4"> 设备状态 </el-col>
<el-col :span="4">
<el-tag type="primary">正常/中断</el-tag>
</el-col>
</el-row>
<el-row :gutter="0" style="display: flex; align-items: center">
<el-col :span="8" :offset="4"> 温度 </el-col>
<el-col :span="4">
<el-tag type="primary">23</el-tag>
</el-col>
</el-row>
<el-row :gutter="0" style="display: flex; align-items: center">
<el-col :span="8" :offset="4"> 湿度 </el-col>
<el-col :span="4">
<el-tag type="primary">45%RH</el-tag>
</el-col>
</el-row>
<el-row :gutter="0" style="display: flex; align-items: center">
<el-col :span="8" :offset="4"> 风力 </el-col>
<el-col :span="4">
<el-tag type="primary">三级5m/s</el-tag>
</el-col>
</el-row>
<el-row :gutter="0" style="display: flex; align-items: center">
<el-col :span="8" :offset="4"> 风向 </el-col>
<el-col :span="4">
<el-tag type="primary">东风</el-tag>
</el-col>
</el-row>
<el-row :gutter="0" style="display: flex; align-items: center">
<el-col :span="8" :offset="4"> 雨量 </el-col>
<el-col :span="4">
<el-tag type="primary">20mm</el-tag>
</el-col>
</el-row>
</div>
<div class="cardBody" v-if="fireExtinguishingBool">
<el-row style="display: flex; align-items: center">
<el-col :span="24">火灾报警系统</el-col>
</el-row>
<el-row :gutter="0" style="display: flex; align-items: center">
<el-col :span="8" :offset="1"
>设备状态
<el-tag type="primary">正常/故障</el-tag>
</el-col>
<el-col :span="8" :offset="2"
>通信状态
<el-tag type="primary">正常/中断</el-tag>
</el-col>
</el-row>
<el-row :gutter="0" style="display: flex; align-items: center">
<el-col :span="22" :offset="1"
>火灾总告警
<!-- 带边框的蓝色圆 -->
<div class="blue-circle redBordered"></div>
</el-col>
</el-row>
<el-divider style="margin-top: 10px; margin-bottom: 10px" />
<el-row style="display: flex; align-items: center">
<el-col :span="24">2号主变灭火系统排油注氮灭火</el-col>
</el-row>
<el-row :gutter="0" style="display: flex; align-items: center">
<el-col :span="7" :offset="1">
启动方式
<el-tag type="primary">自动/手动</el-tag>
</el-col>
<el-col :span="7" :offset="1"
>设备状态
<el-tag type="primary">正常/故障</el-tag>
</el-col>
<el-col :span="7" :offset="1"
>通信状态
<el-tag type="primary">正常/中断</el-tag>
</el-col>
</el-row>
<el-row :gutter="0" style="display: flex; align-items: center">
<el-col :span="7" :offset="1"
>火灾总告警
<div class="blue-circle redBordered"></div>
</el-col>
<el-col :span="7" :offset="1">
<el-popover placement="top" :width="170" trigger="hover" content="查看灭火允许满足条件">
<template #reference>
<el-text class="mx-1" type="primary">灭火允许</el-text>
</template>
</el-popover>
<!-- 带边框的蓝色圆 -->
<div class="blue-circle greenBordered"></div>
</el-col>
<el-col :span="7" :offset="1" style="display: flex; align-items: center">
<el-text class="mx-1" type="primary" @click="dialogTableVisible = true">灭火动作</el-text>
<!-- 带边框的蓝色圆 -->
<div class="blue-circle redBordered"></div>
</el-col>
</el-row>
<el-row :gutter="0" style="display: flex; align-items: center; margin-top: 8px">
<el-col :span="5" :offset="1">灭火手动操作</el-col>
<el-col :span="12">
<el-button type="primary" size="small">手动启动</el-button>
<el-button type="primary" size="small">紧急停止</el-button>
</el-col>
</el-row>
</div>
<div class="cardBody" v-if="firePoliceBool">
<el-row :gutter="0" style="display: flex; align-items: center">
<el-col :span="8" :offset="4"> 设备状态 </el-col>
<el-col :span="4">
<el-tag type="primary">正常/故障</el-tag>
</el-col>
</el-row>
<el-row :gutter="0" style="display: flex; align-items: center">
<el-col :span="8" :offset="4"> 通信状态 </el-col>
<el-col :span="4">
<el-tag type="primary">正常/中断</el-tag>
</el-col>
</el-row>
<el-row :gutter="0" style="display: flex; align-items: center">
<el-col :span="8" :offset="4"> 电源状态</el-col>
<el-col :span="4">
<!-- 主供电源工作/备供电源工作/电源中断 -->
<el-tag type="primary">主供电源工作</el-tag>
</el-col>
</el-row>
</div>
</el-card>
<!-- :append-to-body="true" -->
<el-dialog
v-model="dialogTableVisible"
title="灭火动作流程"
style="height: 60vh"
width="45%"
:append-to-body="true"
>
<el-scrollbar height="42vh">
<el-timeline mode="alternate-reverse">
<el-timeline-item
v-for="(activity, index) in activities"
:key="index"
center
:timestamp="activity.timestamp"
>
{{ activity.content }}
</el-timeline-item>
</el-timeline>
</el-scrollbar>
<!-- 按钮 -->
<template #footer>
<div class="dialog-footer">
<el-button @click="dialogTableVisible = false">退出</el-button>
</div>
</template>
</el-dialog>
</template>
<script lang="ts" setup>
import { computed, ref, watch, onMounted } from 'vue';
let dialogTableVisible = ref(false);
const props = defineProps({
fontFamily: {
type: String,
default: 'Segoe UI'
},
fontSize: {
type: Number,
default: 14
},
testColor: {
type: String,
default: '#000000'
},
testBool: {
type: Boolean,
default: true
},
dataSource: {
type: String,
default: '--'
}
});
const activities = [
{
content: '灭火允许',
timestamp: '2018-04-15'
},
{
content: '手动启动',
timestamp: '2018-04-16'
},
{
content: '紧急停止',
timestamp: '2018-04-17'
},
{
content: '手动启动',
timestamp: '2018-04-18'
},
{
content: '灭火允许',
timestamp: '2018-04-15'
},
{
content: '手动启动',
timestamp: '2018-04-16'
},
{
content: '紧急停止',
timestamp: '2018-04-17'
},
{
content: '手动启动',
timestamp: '2018-04-18'
}
];
onMounted(() => {
loadDataSource(props.dataSource);
});
watch(
() => props.dataSource,
(newVal, oldVal) => {
loadDataSource(newVal);
}
);
//
let globalOverviewBool = ref(false);
//
let fireExtinguishingBool = ref(false);
//
let firePoliceBool = ref(false);
function loadDataSource(val: string) {
if (val === 'globalOverview') {
globalOverviewBool.value = true;
fireExtinguishingBool.value = false;
firePoliceBool.value = false;
} else if (val === 'fireExtinguishing') {
globalOverviewBool.value = false;
fireExtinguishingBool.value = true;
firePoliceBool.value = false;
} else if (val === 'firePolice') {
globalOverviewBool.value = false;
fireExtinguishingBool.value = false;
firePoliceBool.value = true;
}
}
let computedSize = computed({
//
get() {
return props.fontSize + 'px';
}, //
set(val) {
console.log('有人修改了fullName', val);
}
});
</script>
<style scoped>
.blue-circle {
width: 12px;
height: 12px;
border-radius: 50%;
background-color: #409eff;
display: inline-block;
margin-left: 5px;
vertical-align: middle;
}
.blueBordered {
background-color: rgb(217, 236, 255);
border: 2px solid rgb(160, 207, 255);
}
.redBordered {
background-color: rgb(253, 226, 226);
border: 2px solid rgb(250, 182, 182);
}
.greenBordered {
background-color: rgb(225, 243, 216);
border: 2px solid rgb(179, 225, 157);
}
:deep(.el-card__body) {
padding: 0;
padding-top: 10px;
padding-left: 10px;
}
:deep(.adTextDetailDialogClass .el-dialog__body) {
max-height: calc(100vh - 150px);
overflow: auto;
border-top: 1px solid #dfdfdf;
border-bottom: 1px solid #dfdfdf;
}
:deep(.adTextDetailDialogClass .el-dialog) {
position: fixed;
height: 20vh !important;
background: #000;
left: 0 !important;
right: 0 !important;
top: 0 !important;
bottom: 0 !important;
margin: auto !important;
}
.el-row .el-col {
margin-bottom: 10px; /* 设置上下间距 */
}
P {
margin: 2px 0px;
padding: 0 15%;
transition: background-color 0.3s ease;
}
P:hover {
background-color: #f0f0f0; /* 悬停时的底色,您可以根据需要调整颜色 */
}
.card {
margin: 0;
padding: 0;
width: 100%;
height: 100%;
/* max-width: 480px; */
color: v-bind('props.testColor');
font-family: v-bind('props.fontFamily');
/* font-family: 'Segoe UI'; */
font-size: v-bind('computedSize');
}
.cardHead {
margin: 0;
padding: 0px;
padding-bottom: 5px;
text-align: center;
}
.cardBody {
width: 100%;
margin: 0 0%;
padding: 0px 0%;
/* padding-bottom: 15px; */
}
</style>

@ -1,93 +0,0 @@
<template>
<el-descriptions class="margin-top" :column="1" size="small" border>
<el-descriptions-item align="center">
<template #label align="center"> 描述 </template>
</el-descriptions-item>
<el-descriptions-item class="tag-info">
<template #label>
<div class="cell-item">
<el-icon class="iconStyle">
<tickets />
</el-icon>
灯光状态
</div>
</template>
<el-tag size="default" type="success">开启</el-tag>
</el-descriptions-item>
<el-descriptions-item>
<template #label>
<div class="cell-item">
<el-icon class="iconStyle">
<MostlyCloudy />
</el-icon>
湿度
</div>
</template>
80%
</el-descriptions-item>
<el-descriptions-item>
<template #label>
<div class="cell-item">
<el-icon class="iconStyle">
<Sunrise />
</el-icon>
温度
</div>
</template>
36%
</el-descriptions-item>
<el-descriptions-item>
<template #label>
<div class="cell-item">
<el-icon class="iconStyle">
<House />
</el-icon>
节点ID
</div>
</template>
2100210
</el-descriptions-item>
</el-descriptions>
</template>
<script setup lang="ts">
import { computed, ref } from 'vue';
import {
Iphone,
Location,
OfficeBuilding,
Tickets,
User,
House,
MostlyCloudy,
Sunrise
} from '@element-plus/icons-vue';
import type { ComponentSize } from 'element-plus';
const size = ref<ComponentSize>('default');
</script>
<style scoped>
.iconStyle {
marginright: 4px;
}
.el-descriptions {
margin-top: 20px;
}
.cell-item {
display: flex;
align-items: center;
}
.tag-info {
align-items: center;
}
.margin-top {
margin-top: 20px;
}
</style>

@ -1,55 +0,0 @@
<template>
<div style="display: flex; align-items: center">
<el-popover
:width="300"
popper-style="box-shadow: rgb(14 18 22 / 35%) 0px 10px 38px -10px, rgb(14 18 22 / 20%) 0px 10px 20px -15px; padding: 20px;"
>
<template #reference>
<el-switch v-model="value" size="large" active-text="" inactive-text="" />
</template>
<template #default>
<div class="demo-rich-conent" style="display: flex; gap: 2px; flex-direction: column">
<el-switch v-model="value" size="large" disabled />
<el-row :gutter="6">
<el-col :span="12" :xs="24" :sm="12" :md="8" class="elColInfo">
<el-statistic title="绑定ID" value="1100101" />
</el-col>
<el-col :span="12" :xs="24" :sm="12" :md="8" class="elColInfo">
<el-statistic title="状态" :value="null">
<template #suffix>
<el-tag size="default" type="success">开启</el-tag>
</template>
</el-statistic>
</el-col>
<el-col :span="12" :xs="24" :sm="12" :md="8" class="elColInfo">
<el-statistic title="类型" :value="null">
<template #suffix>
<el-tag size="default" type="primary">遥控</el-tag>
</template>
</el-statistic>
</el-col>
</el-row>
</div>
</template>
</el-popover>
</div>
</template>
<script lang="ts" setup>
import { ref } from 'vue';
const value = ref(true);
</script>
<style scoped>
.el-statistic {
/* 控制文字大小 */
--el-statistic-content-font-size: 16px;
}
.elColInfo {
text-align: center;
}
</style>

@ -1,635 +0,0 @@
<template>
<div class="table-container">
<el-card class="card" shadow="hover" :body-style="{ padding: '10px' }">
<h2 class="cardHead">运行信息</h2>
<div class="table-wrapper" v-loading="arr.length === 0">
<el-table
:data="arr"
:span-method="objectSpanMethods"
style="width: 100%"
class="full-height-table"
@cell-click="handleCellClick"
>
<el-table-column prop="floorName" label="楼层" />
<el-table-column prop="id" label="设备ID" />
<el-table-column prop="name" label="设备名称" />
<el-table-column label="绑定">
<template #default="{ row }">
<el-tag :type="row.isBinding ? 'primary' : 'info'">
{{ row.isBinding ? '绑定' : '待绑' }}
</el-tag>
</template>
</el-table-column>
<el-table-column label="设备状态">
<template #default="{ row }">
<el-tag :type="row.eState === 0 ? 'success' : 'warning'">
{{ row.eState === 0 ? '正常' : '故障' }}
</el-tag>
</template>
</el-table-column>
<el-table-column label="当前状态">
<template #default="{ row }">
<el-tag v-if="row.currentState === 0" type="success"></el-tag>
<el-tag v-else-if="row.currentState === 1" type="info">停止</el-tag>
<el-tag v-else type="error">报警</el-tag>
</template>
</el-table-column>
<el-table-column label="操作">
<template #default="{ row }">
<el-button type="primary" class="operation-btn" v-if="row.operation">
屏蔽
</el-button>
<el-button type="primary" class="operation-btn" v-else> </el-button>
</template>
</el-table-column>
</el-table>
</div>
<!-- <div class="table-wrapper">
<el-table
:data="tableData"
border
:span-method="objectSpanMethod"
class="full-height-table"
style="width: 100%"
@cell-click="handleCellClick"
>
<el-table-column prop="floorName" label="楼层" />
<el-table-column prop="id" label="设备ID" />
<el-table-column prop="name" label="设备名称" />
<el-table-column label="设备状态">
<template #default="{ row }">
<el-tag :type="row.eState === 0 ? 'success' : 'warning'">
{{ row.eState === 0 ? '正常' : '故障' }}
</el-tag>
</template>
</el-table-column>
<el-table-column label="当前状态">
<template #default="{ row }">
<el-tag v-if="row.currentState === 0" type="success"></el-tag>
<el-tag v-else-if="row.currentState === 1" type="info">停止</el-tag>
<el-tag v-else type="error">报警</el-tag>
</template>
</el-table-column>
<el-table-column label="操作">
<template #default="{ row }">
<el-button type="primary" class="operation-btn" v-if="row.operation">
屏蔽
</el-button>
<el-button type="primary" class="operation-btn" v-else> </el-button>
</template>
</el-table-column>
</el-table>
</div> -->
</el-card>
</div>
</template>
<script lang="ts" setup>
import type { TableColumnCtx } from 'element-plus';
import { ElMessage } from 'element-plus';
import { computed, onMounted, onUnmounted, ref, watch, reactive } from 'vue';
import { modelApi } from '@/utils/request';
import emitter from '@/utils/emitter';
const loading = ref(false);
//
/// <reference path="../../../global.d.ts" />
interface RecRuntimeData {
double: number;
node: {
name: string;
};
}
interface RecServiceType {
service: {
node: {
runtimes: Record<string, RecRuntimeData>;
};
};
}
interface ExtendedParentWindow extends Window {
Rec?: RecServiceType;
}
interface User {
id: string;
name: string;
amount1: string;
amount2: string;
amount3: number;
}
interface Equipment {
id: string; //ID
address?: string; //
name: string; //
eState: number; // 0 1
currentState: number; // 0 1 2
operation: boolean; // true false
}
interface FloorObj {
floorName: string; //
equipments: Equipment[];
}
// node
interface FloorNode {
roomId: string;
floorName: string;
eqpList: EquipmentNode[];
}
//node
interface EquipmentNode {
id: string; //ID
name: string; //
isBinding: boolean; //
eState: number; // 0 1
currentState: number; // 0 1 2
operation: boolean; // true false
}
function handleCellClick(obj: any) {
console.log('点击:', obj);
emitter.emit('binding-obj-facility-move', obj.id);
}
emitter.on('binding-data-update', () => {
console.log('@@binding-data-update');
dataInit();
});
const props = defineProps({
fontFamily: {
type: String,
default: '黑体'
},
fontSize: {
type: Number,
default: 12
},
testColor: {
type: String,
default: '#ffffff'
},
definitionItemJson: {
type: Object,
default: () => ({})
}
});
console.log('父传子itemjson:', props.definitionItemJson);
//
let computedSize = computed({
//
get() {
return props.fontSize + 'px';
}, //
set(val) {}
});
const arr = ref<any[]>([]);
let targetDataTable: FloorNode[] = [];
let initArrs: any = {};
//
async function dataInit() {
debugger;
initArrs = (window.parent as ExtendedParentWindow).Rec?.service.node.runtimes;
//
arr.value = [];
targetDataTable = [];
loading.value = true;
const response = await modelApi.oneRoomFor_Equipment_get();
if (response.code == 200 && response.data && response.data.length > 0) {
// console.log('', window.parent.Rec.service.node.runtimes);
response.data.forEach((floor: any) => {
let targetData: FloorNode = {
roomId: floor.roomId,
floorName: floor.roomName,
eqpList: []
};
let eqpList: EquipmentNode[] = [];
floor.eqpList.forEach((eqp: any) => {
let currentState = 1;
//
if (initArrs[eqp.nodes[1]].double != 1) currentState = 1;
else if (initArrs[eqp.nodes[2]].double == 1) currentState = 2;
else if (initArrs[eqp.nodes[1]].double == 1) currentState = 0;
let eqpNode: EquipmentNode = {
id: eqp.id,
name: initArrs[eqp.nodes[0]].node.name || '',
eState: initArrs[eqp.nodes[0]].double || 0,
currentState: currentState,
isBinding: eqp.isBinding,
operation: initArrs[eqp.nodes[3]].double == 1
};
eqpList.push(eqpNode);
});
targetData.eqpList = eqpList;
targetDataTable.push(targetData);
});
ElMessage.success('获取接口数据成功');
arr.value = targetDataTable.flatMap((floor) =>
floor.eqpList.map((equipment) => ({
...equipment,
floorName: floor.floorName,
roomId: floor.roomId
}))
);
} else {
ElMessage.error('获取接口数据失败');
}
}
//
const tableDatas = computed(() => {
return targetDataTable.flatMap((floor) =>
floor.eqpList.map((equipment) => ({
...equipment,
floorName: floor.floorName, //
roomId: floor.roomId //
}))
);
});
// onMounted
onMounted(async () => {
await dataInit();
});
onUnmounted(() => {
emitter.off('binding-data-update');
});
//
const floorData: FloorObj[] = [
{
floorName: '安全工器具室',
equipments: [
{
id: 'smokeDetector01',
name: '1号感烟探头',
eState: 0,
currentState: 0,
operation: true
},
{
id: 'smokeDetector02',
name: '2号感烟探头',
eState: 0,
currentState: 0,
operation: true
}
]
},
{
floorName: '蓄电池室',
equipments: [
{
id: 'smokeDetector03',
name: '3号感烟探头',
eState: 1,
currentState: 1,
operation: false
},
{
id: 'smokeDetector04',
name: '4号感烟探头',
eState: 0,
currentState: 0,
operation: true
}
]
},
{
floorName: '220kV保护室',
equipments: [
{
id: 'smokeDetector05',
name: '5号感烟探头',
eState: 0,
currentState: 2,
operation: false
},
{
id: 'smokeDetector06',
name: '6号感烟探头',
eState: 1,
currentState: 1,
operation: true
}
]
},
{
floorName: '380kV配电室',
equipments: [
{
id: 'smokeDetector07',
name: '7号感烟探头',
eState: 0,
currentState: 0,
operation: false
},
{
id: 'smokeDetector08',
name: '8号感烟探头',
eState: 0,
currentState: 2,
operation: true
}
]
},
{
floorName: '35kV高压室',
equipments: [
{
id: 'smokeDetector09',
name: '9号感烟探头',
eState: 1,
currentState: 1,
operation: false
},
{
id: 'smokeDetector10',
name: '10号感烟探头',
eState: 0,
currentState: 0,
operation: true
}
]
},
{
floorName: '2号接地变室',
equipments: [
{
id: 'smokeDetector11',
name: '11号感烟探头',
eState: 0,
currentState: 2,
operation: false
},
{
id: 'smokeDetector12',
name: '12号感烟探头',
eState: 1,
currentState: 1,
operation: true
}
]
},
{
floorName: '110kV电缆室',
equipments: [
{
id: 'smokeDetector13',
name: '13号感烟探头',
eState: 0,
currentState: 0,
operation: false
},
{
id: 'smokeDetector14',
name: '14号感烟探头',
eState: 0,
currentState: 0,
operation: true
}
]
},
{
floorName: '2号站用变室',
equipments: [
{
id: 'smokeDetector15',
name: '15号感烟探头',
eState: 0,
currentState: 0,
operation: false
},
{
id: 'smokeDetector16',
name: '16号感烟探头',
eState: 0,
currentState: 0,
operation: true
}
]
},
{
floorName: '生产综合楼1楼走廊',
equipments: [
{
id: 'smokeDetector17',
name: '17号感烟探头',
eState: 0,
currentState: 0,
operation: false
},
{
id: 'smokeDetector18',
name: '18号感烟探头',
eState: 0,
currentState: 0,
operation: true
},
{
id: '10028',
name: '1号声光报警器',
eState: 0,
currentState: 0,
operation: false
},
{
id: '10029',
name: '2号声光报警器',
eState: 0,
currentState: 0,
operation: true
},
{
id: '10030',
name: '1号手报',
eState: 0,
currentState: 0,
operation: false
},
{
id: '10031',
name: '2号手报',
eState: 0,
currentState: 0,
operation: true
}
]
}
];
interface SpanMethodProps {
row: any;
column: TableColumnCtx<User>;
rowIndex: number;
columnIndex: number;
}
const objectSpanMethods = ({ row, columnIndex }: SpanMethodProps) => {
if (columnIndex === 0) {
//
const floor = targetDataTable.find((f) => f.floorName === row.floorName);
if (floor) {
//
const firstEquipmentIndex = floor.eqpList.findIndex((e) => e.id === row.id);
if (firstEquipmentIndex === 0) {
//
return {
rowspan: floor.eqpList.length,
colspan: 1
};
} else {
//
return {
rowspan: 0,
colspan: 0
};
}
}
}
//
return {
rowspan: 1,
colspan: 1
};
};
const objectSpanMethod = ({ row, columnIndex }: SpanMethodProps) => {
if (columnIndex === 0) {
//
const floor = floorData.find((f) => f.floorName === row.floorName);
if (floor) {
//
const firstEquipmentIndex = floor.equipments.findIndex((e) => e.id === row.id);
if (firstEquipmentIndex === 0) {
//
return {
rowspan: floor.equipments.length,
colspan: 1
};
} else {
//
return {
rowspan: 0,
colspan: 0
};
}
}
}
//
return {
rowspan: 1,
colspan: 1
};
};
//
const tableData = computed(() => {
return floorData.flatMap((floor) =>
floor.equipments.map((equipment) => ({
...equipment,
floorName: floor.floorName, //
//
address: equipment.address || floor.floorName
}))
);
});
</script>
<style scoped>
.table-container {
width: 100%;
height: 100%;
display: flex;
flex-direction: column;
}
.card {
flex: 1;
display: flex;
flex-direction: column;
height: 100%;
}
.card :deep(.el-card__body) {
flex: 1;
display: flex;
flex-direction: column;
overflow: hidden;
padding: 10px;
}
.table-wrapper {
flex: 1;
overflow: hidden;
display: flex;
flex-direction: column;
}
.full-height-table {
flex: 1;
height: 100%;
}
.full-height-table :deep(.el-table__body-wrapper) {
flex: 1;
overflow-y: auto;
}
.cardHead {
margin: 0 0 15px 0;
padding: 0;
font-size: 16px;
font-weight: bold;
text-align: center;
color: #ffffff;
flex-shrink: 0;
}
.operation-btn {
width: 60px;
min-width: 50px;
padding: 8px 12px;
height: 30px;
}
/* 控制表头文字大小 */
.full-height-table :deep(.el-table__header th) {
font-size: 13px;
font-weight: bold;
}
/* 控制表格内容文字大小 */
.full-height-table :deep(.el-table__body td) {
font-size: v-bind('computedSize');
color: v-bind('props.testColor');
}
/* 控制标签文字大小 */
.full-height-table :deep(.el-tag) {
font-size: v-bind('computedSize'); /* 标签内的文字可以稍小一些 */
}
/* 控制按钮文字大小 */
.full-height-table :deep(.el-button) {
font-size: v-bind('computedSize');
color: v-bind('props.testColor');
}
</style>

@ -1,200 +0,0 @@
<template>
<div class="compact-table-container">
<el-table ref="singleTableRef" :data="tableData" size="small" style="width: 100%">
<el-table-column type="index" label="序号" />
<el-table-column prop="groupName" label="名称" />
<el-table-column prop="temperatureStr" label="温度" />
<el-table-column prop="humidityStr" label="湿度" />
</el-table>
</div>
<div style="margin-top: 10px" class="compact-button-container">
<el-button @click="getGroupDataList" size="small">获取数据</el-button>
<!-- <el-button @click="setCurrent()" size="small">Clear selection</el-button> -->
</div>
</template>
<script lang="ts" setup>
import { ref, reactive, onUnmounted, onMounted, toRefs, computed } from 'vue';
import { type ResponseMultipleValued, type GroupData } from '@/components/mt-edit/store/types';
import { useDataMapStore } from '@/utils/dataMapStore'; // store
import { storeToRefs } from 'pinia'; // store
interface User {
date: string;
name: string;
address: string;
}
const dataMapStore = useDataMapStore();
const { dataMap } = storeToRefs(dataMapStore);
onMounted(() => {
getGroupDataList();
});
onUnmounted(() => {});
function getvirtualMap(): ResponseMultipleValued {
return {
config: null,
data: [
{
groupName: '组名1001',
temperatureId: 10384,
humidityId: 10385
},
{
groupName: '组名1002',
temperatureId: 11384,
humidityId: 11385
},
{
groupName: '组名1003',
temperatureId: 12384,
humidityId: 12385
},
{
groupName: '组名1004',
temperatureId: 13384,
humidityId: 13385
}
],
headers: null,
request: null,
status: 200,
statusText: 'test'
};
}
interface GroupDatStr {
groupName: string;
temperatureStr: string;
humidityStr: string;
}
const tableData: Array<GroupDatStr> = reactive([]);
async function getGroupDataList() {
// const response: ResponseVue = await axios.get('/api/nrt');
const response: ResponseMultipleValued = getvirtualMap();
response.data.forEach((item) => {
let humidityData = '-';
if (dataMap.value[item.humidityId]) {
humidityData =
dataMap.value[item.humidityId].double + dataMap.value[item.humidityId].node.unit;
}
let temperatureData = '-';
if (dataMap.value[item.temperatureId]) {
temperatureData =
dataMap.value[item.temperatureId].double + dataMap.value[item.temperatureId].node.unit;
}
tableData.push({
groupName: item.groupName,
temperatureStr: humidityData,
humidityStr: temperatureData
});
});
}
const props = defineProps({
fontFamily: {
type: String,
default: '黑体'
},
fontSize: {
type: Number,
default: 12
},
testColor: {
type: String,
default: '#ffffff'
}
});
//
let computedSize = computed({
//
get() {
return props.fontSize + 'px';
}, //
set(val) {
console.log('有人修改了fullName', val);
}
});
</script>
<style scoped>
.compact-button-container {
max-width: 450px;
}
.compact-table-container {
width: 100%;
height: 100%;
overflow-y: auto;
transition: transform 0.3s ease;
}
.compact-table-container .el-table {
font-size: v-bind('computedSize');
}
.compact-table-container .el-table .el-table__cell {
padding: 2px 0;
}
.compact-table-container .el-table th.el-table__cell > .cell {
padding: 0 2px;
}
.compact-table-container .el-table td.el-table__cell > .cell {
padding: 2px 2px;
}
/* 修改表格整体背景色 */
::v-deep .el-table {
background-color: #40beff; /* 浅蓝色背景 */
}
/* 修改表头背景色和文字颜色 */
::v-deep .el-table th.el-table__cell {
/* background-color: #2e349e; 深蓝色表头 */
background-color: #21264e;
color: white; /* 白色文字 */
font-weight: bold;
}
/* 修改表头边框颜色 */
::v-deep .el-table th.el-table__cell {
border: 1px solid #151a3d; /* 深色边框 */
}
/* 修改奇偶行背景色 */
::v-deep .el-table .el-table__body-wrapper tr.el-table__row:nth-child(odd) {
background-color: #40beff; /* 奇数行使用浅蓝色 */
color: v-bind('props.testColor');
font-family: v-bind('props.fontFamily');
}
::v-deep .el-table .el-table__body-wrapper tr.el-table__row:nth-child(even) {
background-color: #2e349e; /* 偶数行使用深蓝色 */
color: v-bind('props.testColor');
font-family: v-bind('props.fontFamily');
}
/* 修改鼠标悬停时的行颜色 */
::v-deep .el-table .el-table__body tr:hover > td {
background-color: rgba(21, 26, 61, 0.7); /* 半透明深蓝色悬停效果,基于#151a3d */
}
/* 修改选中行的颜色 */
::v-deep .el-table .el-table__body tr.current-row > td {
background-color: #151a3d;
color: white;
}
/* 修改单元格边框颜色 */
::v-deep .el-table td.el-table__cell {
border: 1px solid #151a3d; /* 深色边框 */
}
</style>

@ -1,270 +0,0 @@
<template>
<!-- <div class="scale-controls" style="margin-bottom: 10px">
<el-button @click="zoomIn" size="small">放大</el-button>
<el-button @click="zoomOut" size="small">缩小</el-button>
<span style="margin-left: 10px">{{ Math.round(tablesCale * 100) }}%</span>
</div> -->
<div class="compact-table-container-wrapper">
<div class="compact-table-container">
<el-table :data="tableNode" class="compact-table" size="small" height="100%">
<el-table-column type="index" label="序号" />
<el-table-column prop="id" label="ID" />
<el-table-column prop="name" label="名称" />
<el-table-column prop="bType" label="类型" />
<el-table-column prop="dateValue" label="遥值" />
<el-table-column prop="runState" label="运行状态" />
<el-table-column prop="sig" label="区间类型" />
</el-table>
</div>
</div>
<div style="margin-top: 10px" class="compact-button-container">
<el-button @click="getDataList" size="small">获取数据</el-button>
<!-- <el-button @click="setCurrent()" size="small">Clear selection</el-button> -->
</div>
</template>
<script setup lang="ts">
import axios from 'axios';
import { ref, reactive, toRefs, onMounted, watch, computed } from 'vue';
import { type ResponseVue } from '@/components/mt-edit/store/types';
import { useDataMapStore } from '@/utils/dataMapStore';
import { globalStore } from '@/components/mt-edit/store/global';
const emit = defineEmits(['update:modelValue']);
let store = useDataMapStore();
interface NodeDTO {
id: string;
name: string;
bType: string;
dateValue?: string;
runState?: string;
sig?: string;
}
// let fontSize = ref('12px');
function getTypeNameByNum(value: number) {
switch (value) {
case 1:
return '遥信';
case 2:
return '遥测';
case 3:
return '遥控';
case 4:
return '遥调';
default:
return '其他';
}
}
const tableNode: Array<NodeDTO> = reactive([]);
const props = defineProps({
fontFamily: {
type: String,
default: '黑体'
},
fontSize: {
type: Number,
default: 12
},
testColor: {
type: String,
default: '#ffffff'
}
});
//
let computedSize = computed({
//
get() {
return props.fontSize + 'px';
}, //
set(val) {
console.log('有人修改了fullName', val);
}
});
function getRunState(value: number) {
switch (value) {
case 0:
return '不可用/不在线';
case 1:
return '工作状态';
case 2:
return '取代/屏蔽';
default:
return '未知状态';
}
}
function getNodeSigType(value: number) {
switch (value) {
case -2000:
return '未初始化';
case 0:
return '分/复归';
case 1:
return '合/告警';
case 5:
return '联动关';
case -10:
return '预警/低限联动';
case 10:
return '报警/高限联动';
case -20:
return '低限告警';
case 20:
return '高限告警';
default:
return '未知区间状态';
}
}
onMounted(() => {
getDataList();
});
async function getDataList() {
// GET
try {
const response: ResponseVue = await axios.get('/api/nrt');
// response.data
if (response.data) {
store.setDataMap(response.data);
for (const key in response.data) {
const dataItem = response.data[key];
let valStr = ' - ';
let val = dataItem.double;
if (dataItem.bType) {
switch (dataItem.bType) {
case 1: //
case 3: //
case 4: //
if (val > 0) valStr = '开';
else valStr = '关';
break;
case 2: //
valStr = dataItem.double + dataItem.node.unit;
break;
default:
valStr = ' - ';
break;
}
}
if (dataItem.double !== undefined && dataItem.double) {
valStr = dataItem.double + dataItem.node.unit;
}
tableNode.push({
id: dataItem.id.toString(),
name: dataItem.node.name,
bType: getTypeNameByNum(dataItem.bType),
dateValue: valStr,
runState: getRunState(dataItem.runState),
sig: getNodeSigType(dataItem.sig)
});
}
}
} catch (error) {
console.error('请求失败:', error);
}
}
</script>
<style scoped>
.compact-table-container-wrapper {
/* max-width: 1000px; */
/* max-height: 400px; */
width: 100%;
height: 100%;
overflow-y: auto;
}
.compact-table-container {
width: 100%;
height: 100%;
/* max-width: 500px; */
overflow-y: auto;
/* font-size: 20; */
fit: true;
transition: transform 0.3s ease;
}
.compact-table {
width: 100%;
height: 100%;
max-width: 100%;
max-height: 100%;
}
/* 修改表格整体背景色 */
::v-deep .el-table {
background-color: #40beff; /* 浅蓝色背景 */
}
/* 修改表头背景色和文字颜色 */
::v-deep .el-table th.el-table__cell {
/* background-color: #2e349e; 深蓝色表头 */
background-color: #21264e;
color: white; /* 白色文字 */
font-weight: bold;
}
/* 修改表头边框颜色 */
::v-deep .el-table th.el-table__cell {
border: 1px solid #151a3d; /* 深色边框 */
}
/* 修改奇偶行背景色 */
::v-deep .el-table .el-table__body-wrapper tr.el-table__row:nth-child(odd) {
background-color: #40beff; /* 奇数行使用浅蓝色 */
color: v-bind('props.testColor');
font-family: v-bind('props.fontFamily');
}
::v-deep .el-table .el-table__body-wrapper tr.el-table__row:nth-child(even) {
background-color: #2e349e; /* 偶数行使用深蓝色 */
color: v-bind('props.testColor');
font-family: v-bind('props.fontFamily');
}
/* 修改鼠标悬停时的行颜色 */
::v-deep .el-table .el-table__body tr:hover > td {
background-color: rgba(21, 26, 61, 0.7); /* 半透明深蓝色悬停效果,基于#151a3d */
}
/* 修改选中行的颜色 */
::v-deep .el-table .el-table__body tr.current-row > td {
background-color: #151a3d;
color: white;
}
/* 修改单元格边框颜色 */
::v-deep .el-table td.el-table__cell {
border: 1px solid #151a3d; /* 深色边框 */
}
.compact-table-container .el-table {
font-size: v-bind('computedSize');
/* font-size: 12px; */
}
.compact-table-container .el-table .el-table__cell {
padding: 2px 0;
}
.compact-table-container .el-table th.el-table__cell > .cell {
padding: 0 2px;
}
.compact-table-container .el-table td.el-table__cell > .cell {
padding: 2px 2px;
}
</style>

@ -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');

@ -3,11 +3,11 @@ export const API_CONFIG = {
// nginx // nginx
NGINX: 'http://localhost:8099', NGINX: 'http://localhost:8099',
// 测试环境 // 测试环境
LOCAL: 'http://localhost:8080' LOCAL: 'http://localhost:9991/api/admin'
}; };
interface ApiResponse { interface ApiResponse {
code: number; code: any;
message: string; message: string;
data: any; data: any;
} }

@ -1,5 +1,6 @@
// vite.config.mts // vite.config.mts
import { resolve } from 'path'; import { resolve } from 'path';
import path from 'path'
import { fileURLToPath, URL } from 'node:url'; import { fileURLToPath, URL } from 'node:url';
import { defineConfig, ConfigEnv, UserConfig, loadEnv } from 'vite'; import { defineConfig, ConfigEnv, UserConfig, loadEnv } from 'vite';
import vue from '@vitejs/plugin-vue'; import vue from '@vitejs/plugin-vue';
@ -37,6 +38,12 @@ export default defineConfig(({ mode }: ConfigEnv): UserConfig => {
// iconDirs: [resolve(process.cwd(), 'src/assets/icons')], // iconDirs: [resolve(process.cwd(), 'src/assets/icons')],
// symbolId: 'icon-[dir]-[name]' // symbolId: 'icon-[dir]-[name]'
// }), // }),
createSvgIconsPlugin({
// 指定需要缓存的图标文件夹
iconDirs: [path.resolve(process.cwd(), 'src/assets/svgs/icons')],
// 指定 symbolId 格式,也就是我们在使用时图标名称的格式
symbolId: 'icon-[dir]-[name]',
}),
UnoCSS({ UnoCSS({
// 在低版本浏览器上开发时会报错 Unexpected reserved word // 在低版本浏览器上开发时会报错 Unexpected reserved word
// 如果在开发环境需要兼容不支持顶级await的低版本浏览器例如Chrome(v87),就将下面的配置打开 // 如果在开发环境需要兼容不支持顶级await的低版本浏览器例如Chrome(v87),就将下面的配置打开

Loading…
Cancel
Save