fix/风机联动配置页面等
This commit is contained in:
parent
b9c284dde2
commit
4e1c0ba8fc
|
@ -1,90 +0,0 @@
|
|||
/*
|
||||
* @Author: 季万俊
|
||||
* @Date: 2025-10-15 16:35:10
|
||||
* @Description:
|
||||
*/
|
||||
import request from '@/utils/request'
|
||||
|
||||
// 查询生成表数据
|
||||
export function listTable(query) {
|
||||
return request({
|
||||
url: '/tool/gen/list',
|
||||
method: 'get',
|
||||
params: query
|
||||
})
|
||||
}
|
||||
// 查询db数据库列表
|
||||
export function listDbTable(query) {
|
||||
return request({
|
||||
url: '/tool/gen/db/list',
|
||||
method: 'get',
|
||||
params: query
|
||||
})
|
||||
}
|
||||
|
||||
// 查询表详细信息
|
||||
export function getGenTable(tableId) {
|
||||
return request({
|
||||
url: '/tool/gen/' + tableId,
|
||||
method: 'get'
|
||||
})
|
||||
}
|
||||
|
||||
// 修改代码生成信息
|
||||
export function updateGenTable(data) {
|
||||
return request({
|
||||
url: '/tool/gen',
|
||||
method: 'put',
|
||||
data: data
|
||||
})
|
||||
}
|
||||
|
||||
// 导入表
|
||||
export function importTable(data) {
|
||||
return request({
|
||||
url: '/tool/gen/importTable',
|
||||
method: 'post',
|
||||
params: data
|
||||
})
|
||||
}
|
||||
|
||||
// 创建表
|
||||
export function createTable(data) {
|
||||
return request({
|
||||
url: '/tool/gen/createTable',
|
||||
method: 'post',
|
||||
params: data
|
||||
})
|
||||
}
|
||||
|
||||
// 预览生成代码
|
||||
export function previewTable(tableId) {
|
||||
return request({
|
||||
url: '/tool/gen/preview/' + tableId,
|
||||
method: 'get'
|
||||
})
|
||||
}
|
||||
|
||||
// 删除表数据
|
||||
export function delTable(tableId) {
|
||||
return request({
|
||||
url: '/tool/gen/' + tableId,
|
||||
method: 'delete'
|
||||
})
|
||||
}
|
||||
|
||||
// 生成代码(自定义路径)
|
||||
export function genCode(tableName) {
|
||||
return request({
|
||||
url: '/tool/gen/genCode/' + tableName,
|
||||
method: 'get'
|
||||
})
|
||||
}
|
||||
|
||||
// 同步数据库
|
||||
export function synchDb(tableName) {
|
||||
return request({
|
||||
url: '/tool/gen/synchDb/' + tableName,
|
||||
method: 'get'
|
||||
})
|
||||
}
|
|
@ -0,0 +1 @@
|
|||
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1760607440762" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="9824" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><path d="M737.28 506.42944c68.28032 109.24032 102.4 183.13216 102.4 221.67552 0 58.5728-45.8752 106.00448-102.4 106.00448S634.88 786.67776 634.88 728.10496c0-38.5024 34.11968-112.4352 102.4-221.67552z m45.75232-312.60672a102.4 102.4 0 0 1 0 144.83456L471.04 650.69056a143.36 143.36 0 1 1-143.36-144.26112l-2.12992 0.04096 312.68864-312.64768a102.4 102.4 0 0 1 144.7936 0zM737.28 634.88c-13.63968 42.76224-20.48 62.42304-20.48 76.75904 0 21.504 9.17504 38.912 20.48 38.912s20.48-17.408 20.48-38.912c0-14.336-6.84032-33.9968-20.48-76.75904z m-10.32192-383.71328a20.48 20.48 0 0 0-28.95872 0l-343.40864 343.36768a61.44 61.44 0 1 0 28.75392 29.20448l343.61344-343.6544a20.48 20.48 0 0 0 0-28.91776z" fill="#444444" p-id="9825"></path></svg>
|
After Width: | Height: | Size: 1.0 KiB |
|
@ -0,0 +1 @@
|
|||
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1760607371164" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="5823" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><path d="M512 981.333333c259.072-0.341333 468.992-210.261333 469.333333-469.333333-0.341333-259.072-210.261333-468.992-469.333333-469.333333-259.072 0.341333-468.992 210.261333-469.333333 469.333333 0.341333 259.072 210.261333 468.992 469.333333 469.333333z m0 42.666667C229.354667 1023.68 0.32 794.645333 0 512 0.32 229.354667 229.354667 0.32 512 0c282.645333 0.32 511.68 229.354667 512 512-0.32 282.645333-229.354667 511.68-512 512z" fill="#333333" p-id="5824"></path><path d="M325.333333 325.610667a168.32 168.32 0 0 0 168.256 168.32h25.941334V157.376h-25.941334a168.32 168.32 0 0 0-168.256 168.256z m96.32 454.186666a168.32 168.32 0 0 0 61.632-229.930666l-12.949333-22.378667-291.626667 168.256 12.928 22.442667a168.32 168.32 0 0 0 229.952 61.610666h0.064z m349.376-318.677333a168.32 168.32 0 0 0-229.866666 61.632l-12.928 22.421333 291.477333 168.32 12.928-22.421333a168.32 168.32 0 0 0-61.610667-229.952z" fill="#333333" p-id="5825"></path></svg>
|
After Width: | Height: | Size: 1.2 KiB |
|
@ -0,0 +1 @@
|
|||
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1760607420456" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="8784" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><path d="M888.207 759.168l-328.02-550.935a55.244 55.244 0 0 0-75.975-20.727 56 56 0 0 0-20.44 20.727L135.457 759.168c-15.333 27.166-6.051 61.807 20.755 77.352a55.342 55.342 0 0 0 27.671 7.5h656.408c42.568-0.514 69.7-47.269 48-84.847h-0.084z m-376.36 20.769c-26.236 0.007-47.507-21.54-47.514-48.116s21.238-48.158 47.467-48.165h0.047c26.223 0.007 47.481 21.575 47.467 48.165-0.011 26.562-21.262 48.095-47.467 48.116z m48.848-181.128c-1.032 27.368-23.757 48.706-50.751 47.657-25.553-0.986-46.046-21.755-47.018-47.657V396.981c1.032-27.361 23.75-48.706 50.758-47.657 25.539 0.986 46.032 21.755 47.011 47.657v201.828z" fill="#DADADA" p-id="8785"></path></svg>
|
After Width: | Height: | Size: 977 B |
|
@ -0,0 +1 @@
|
|||
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1760607389345" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="6872" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><path d="M949.333333 170.666667c-17.066667 0-32 14.933333-32 32v10.666666H512c-164.266667 0-298.666667 134.4-298.666667 298.666667 0 38.4 6.4 72.533333 19.2 106.666667H106.666667v-10.666667c0-17.066667-14.933333-32-32-32S42.666667 590.933333 42.666667 608v213.333333c0 17.066667 14.933333 32 32 32S106.666667 838.4 106.666667 821.333333V810.666667h405.333333c164.266667 0 298.666667-134.4 298.666667-298.666667 0-38.4-6.4-72.533333-19.2-106.666667H917.333333v10.666667c0 17.066667 14.933333 32 32 32s32-14.933333 32-32v-213.333333c0-17.066667-14.933333-32-32-32zM512 682.666667c-93.866667 0-170.666667-76.8-170.666667-170.666667s76.8-170.666667 170.666667-170.666667 170.666667 76.8 170.666667 170.666667-76.8 170.666667-170.666667 170.666667z" p-id="6873"></path><path d="M608 497.066667l17.066667-17.066667c6.4-6.4 6.4-17.066667 0-23.466667-6.4-6.4-17.066667-6.4-23.466667 0l-40.533333 40.533334h-32v-32l40.533333-40.533334c6.4-6.4 6.4-17.066667 0-23.466666s-17.066667-6.4-23.466667 0l-17.066666 17.066666v-14.933333c0-8.533333-8.533333-17.066667-17.066667-17.066667s-17.066667 8.533333-17.066667 17.066667v14.933333l-17.066666-17.066666c-6.4-6.4-17.066667-6.4-23.466667 0-6.4 6.4-6.4 17.066667 0 23.466666l40.533333 40.533334v32h-32l-40.533333-40.533334c-6.4-6.4-17.066667-6.4-23.466667 0-6.4 6.4-6.4 17.066667 0 23.466667l17.066667 17.066667h-14.933333c-8.533333 0-17.066667 8.533333-17.066667 17.066666s8.533333 17.066667 17.066667 17.066667h14.933333l-17.066667 17.066667c-2.133333 2.133333-4.266667 8.533333-4.266666 12.8 0 8.533333 8.533333 17.066667 17.066666 17.066666 4.266667 0 8.533333-2.133333 12.8-4.266666l40.533334-40.533334h32v32l-40.533334 40.533334c-6.4 6.4-6.4 17.066667 0 23.466666 6.4 6.4 17.066667 6.4 23.466667 0l17.066667-17.066666v14.933333c0 8.533333 8.533333 17.066667 17.066666 17.066667s17.066667-8.533333 17.066667-17.066667v-14.933333l17.066667 17.066666c4.266667 4.266667 8.533333 4.266667 12.8 4.266667 4.266667 0 8.533333-2.133333 12.8-4.266667 6.4-6.4 6.4-17.066667 0-23.466666L533.333333 565.333333V533.333333h32l40.533334 40.533334c4.266667 4.266667 8.533333 4.266667 12.8 4.266666 4.266667 0 8.533333-2.133333 12.8-4.266666 6.4-6.4 6.4-17.066667 0-23.466667l-17.066667-17.066667h14.933333c8.533333 0 17.066667-8.533333 17.066667-17.066666s-8.533333-17.066667-17.066667-17.066667h-21.333333z" p-id="6874"></path></svg>
|
After Width: | Height: | Size: 2.6 KiB |
|
@ -193,3 +193,7 @@ aside {
|
|||
margin-bottom: 10px;
|
||||
}
|
||||
}
|
||||
|
||||
.margin0 {
|
||||
margin: 0 !important;
|
||||
}
|
||||
|
|
|
@ -1,3 +1,8 @@
|
|||
<!--
|
||||
* @Author: 季万俊
|
||||
* @Date: 2025-09-26 11:23:42
|
||||
* @Description:
|
||||
-->
|
||||
<template>
|
||||
<section class="app-main">
|
||||
<router-view v-slot="{ Component, route }">
|
||||
|
@ -38,10 +43,10 @@ function addIframe() {
|
|||
<style lang="scss" scoped>
|
||||
.app-main {
|
||||
/* 50= navbar 50 */
|
||||
min-height: calc(100vh - 50px);
|
||||
height: calc(100vh - 84px);
|
||||
width: 100%;
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.fixed-header + .app-main {
|
||||
|
|
|
@ -234,6 +234,71 @@ export const constantRoutes = [
|
|||
},
|
||||
]
|
||||
},
|
||||
{
|
||||
name: "linkConfig",
|
||||
alwaysShow: true,
|
||||
component: Layout,
|
||||
hidden: false,
|
||||
name: "linkConfig",
|
||||
path: "/linkConfig",
|
||||
redirect: "noRedirect",
|
||||
meta: {
|
||||
icon: "link",
|
||||
link: null,
|
||||
noCache: false,
|
||||
title: "联动配置管理",
|
||||
},
|
||||
children: [
|
||||
{
|
||||
component: () => import('@/views/linkConfig/alarmConfig'),
|
||||
hidden: false,
|
||||
name: "alarmConfig",
|
||||
path: "alarmConfig",
|
||||
meta: {
|
||||
icon: "gjdj",
|
||||
link: null,
|
||||
noCache: false,
|
||||
title: "报警等级配置",
|
||||
},
|
||||
},
|
||||
{
|
||||
component: () => import('@/views/linkConfig/fanLinkConfig'),
|
||||
hidden: false,
|
||||
name: "fanLinkConfig",
|
||||
path: "fanLinkConfig",
|
||||
meta: {
|
||||
icon: "fan",
|
||||
link: null,
|
||||
noCache: false,
|
||||
title: "风机联动阈值配置",
|
||||
},
|
||||
},
|
||||
{
|
||||
component: () => import('@/views/linkConfig/pumpLinkConfig'),
|
||||
hidden: false,
|
||||
name: "pumpLinkConfig",
|
||||
path: "pumpLinkConfig",
|
||||
meta: {
|
||||
icon: "pump",
|
||||
link: null,
|
||||
noCache: false,
|
||||
title: "水泵联动阈值配置",
|
||||
},
|
||||
},
|
||||
{
|
||||
component: () => import('@/views/linkConfig/sensorConfig'),
|
||||
hidden: false,
|
||||
name: "sensorConfig",
|
||||
path: "sensorConfig",
|
||||
meta: {
|
||||
icon: "cgq",
|
||||
link: null,
|
||||
noCache: false,
|
||||
title: "传感器报警阈值",
|
||||
},
|
||||
},
|
||||
]
|
||||
},
|
||||
|
||||
{
|
||||
name: "Equipment",
|
||||
|
@ -439,6 +504,31 @@ export const constantRoutes = [
|
|||
title: "角色管理",
|
||||
},
|
||||
},
|
||||
{
|
||||
component: () => import('@/views/system/dict/index'),
|
||||
hidden: false,
|
||||
name: "dict",
|
||||
path: "dict",
|
||||
meta: {
|
||||
icon: "dict",
|
||||
link: null,
|
||||
noCache: false,
|
||||
title: "字典管理",
|
||||
},
|
||||
},
|
||||
// {
|
||||
// component: () => import('@/views/system/menu/index'),
|
||||
// hidden: false,
|
||||
// name: "menu",
|
||||
// path: "menu",
|
||||
// meta: {
|
||||
// icon: "menu",
|
||||
// link: null,
|
||||
// noCache: false,
|
||||
// title: "菜单管理",
|
||||
// },
|
||||
// },
|
||||
|
||||
{
|
||||
component: () => import('@/views/system/log/index'),
|
||||
hidden: false,
|
||||
|
|
|
@ -59,7 +59,7 @@ export const useUserStore = defineStore('user', {
|
|||
const avatar =
|
||||
user.avatar == "" || user.avatar == null
|
||||
? require("@/assets/images/profile.jpg")
|
||||
: process.env.VITE_VUE_APP_BASE_API + user.avatar
|
||||
: process.env.VITE_VITE_VUE_APP_BASE_API + user.avatar
|
||||
if (res.roles && res.roles.length > 0) {
|
||||
console.log("获取用户信息")
|
||||
// 验证返回的roles是否是一个非空数组
|
||||
|
|
|
@ -19,8 +19,10 @@ export function useDict(...args) {
|
|||
// res.value[dictType] = dicts
|
||||
// } else {
|
||||
getDicts(dictType).then(resp => {
|
||||
res.value[dictType] = resp.data.map(p => ({ label: p.dictLabel, value: p.dictValue, elTagType: p.listClass, elTagClass: p.cssClass }))
|
||||
useDictStore().setDict(dictType, res.value[dictType])
|
||||
if(resp && resp.data) {
|
||||
res.value[dictType] = resp.data.map(p => ({ label: p.dictLabel, value: p.dictValue, elTagType: p.listClass, elTagClass: p.cssClass }))
|
||||
useDictStore().setDict(dictType, res.value[dictType])
|
||||
}
|
||||
})
|
||||
// }
|
||||
})
|
||||
|
|
|
@ -23,11 +23,11 @@
|
|||
<el-button :icon="Search" circle @click="handleQuery" />
|
||||
<el-button :icon="RefreshLeft" circle @click="handleQuery" />
|
||||
</el-form-item>
|
||||
<el-form-item style="margin-left: 16px; float: right">
|
||||
<el-form-item style="margin-left: 16px; float: right" class="margin0">
|
||||
<el-button type="primary" plain icon="Plus" @click="handleAdd"
|
||||
>新增</el-button
|
||||
>
|
||||
<el-button type="warning" plain icon="Download">导入</el-button>
|
||||
<el-button type="warning" plain icon="Download" class="margin0">导入</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
|
||||
|
@ -293,7 +293,6 @@ const cancel = () => {
|
|||
.meter-monitoring-page {
|
||||
padding: 20px;
|
||||
background-color: #f8fafc;
|
||||
min-height: calc(100vh - 64px); /* 适配顶部导航高度 */
|
||||
}
|
||||
|
||||
/* 卡片样式:圆角 + 悬浮阴影,增强层次感 */
|
||||
|
|
|
@ -5,7 +5,6 @@
|
|||
<div class="device-tree-panel">
|
||||
<div class="tree-header">
|
||||
<div class="search-add">
|
||||
<!-- 修复:确保 ElInput 标签闭合,图标组件正确引用 -->
|
||||
<ElInput
|
||||
v-model="searchDevice"
|
||||
placeholder="请输入设备"
|
||||
|
@ -14,29 +13,44 @@
|
|||
></ElInput>
|
||||
</div>
|
||||
</div>
|
||||
<!-- 修复:ElTree 标签闭合,避免自闭合语法不兼容 -->
|
||||
<ElTree
|
||||
ref="deviceTreeRef"
|
||||
:data="deviceTreeData"
|
||||
:props="treeProps"
|
||||
:filter-node-method="filterNode"
|
||||
:current-node-key="currentNodeKey"
|
||||
:current-node-id="currentSelectedNode"
|
||||
:disabled="isDisabled"
|
||||
default-expand-all
|
||||
highlight-current
|
||||
@node-click="handleNodeClick"
|
||||
class="device-tree"
|
||||
></ElTree>
|
||||
|
||||
<!-- 自定义设备列表 -->
|
||||
<div class="custom-tree">
|
||||
<div
|
||||
v-for="group in filteredTreeData"
|
||||
:key="group.label"
|
||||
class="tree-group"
|
||||
>
|
||||
<div class="group-label">{{ group.label }}</div>
|
||||
<div
|
||||
v-for="item in group.children"
|
||||
:key="item.label"
|
||||
class="tree-item"
|
||||
:class="{
|
||||
'selected': isItemSelected(item.label),
|
||||
'disabled': isItemDisabled && !isItemSelected(item.label)
|
||||
}"
|
||||
@click="handleItemClick(item)"
|
||||
>
|
||||
<div class="item-content">
|
||||
<Camera class="item-icon"></Camera>
|
||||
<span class="item-label">{{ item.label }}</span>
|
||||
</div>
|
||||
<div v-if="isItemSelected(item.label)" class="selected-indicator">
|
||||
<el-icon><Check /></el-icon>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 右侧视频墙面板 -->
|
||||
<div class="video-wall-panel">
|
||||
<div class="video-header">
|
||||
<!-- 修复:图标组件闭合,添加类名语法正确 -->
|
||||
<Camera class="icon"></Camera>
|
||||
<span>监控视频</span>
|
||||
</div>
|
||||
<!-- 修复:style 绑定语法完整,英文逗号,闭合大括号 -->
|
||||
|
||||
<div
|
||||
class="video-container"
|
||||
:style="{
|
||||
|
@ -44,7 +58,6 @@
|
|||
gridTemplateRows: gridRows
|
||||
}"
|
||||
>
|
||||
<!-- 修复:v-for key 唯一(暂用index,实际建议用设备ID),标签闭合 -->
|
||||
<div
|
||||
v-for="(video, index) in videoList"
|
||||
:key="`video-${index}`"
|
||||
|
@ -58,7 +71,6 @@
|
|||
<div class="video-placeholder">
|
||||
<Camera class="icon"></Camera>
|
||||
<span>{{ video.name }}</span>
|
||||
<!-- 修复:Close 图标标签闭合,@click.stop 语法正确 -->
|
||||
<Close
|
||||
v-if="video.name !== '待选择区域'"
|
||||
class="close-icon"
|
||||
|
@ -70,7 +82,6 @@
|
|||
|
||||
<!-- 布局切换按钮 -->
|
||||
<div class="layout-switch">
|
||||
<!-- 修复:v-for key 唯一,ElButton 标签闭合 -->
|
||||
<ElButton
|
||||
v-for="item in buttonType"
|
||||
:key="`layout-${item.type}`"
|
||||
|
@ -88,14 +99,11 @@
|
|||
</template>
|
||||
|
||||
<script setup>
|
||||
// 修复:确保所有导入正确,无拼写错误,逗号分隔规范
|
||||
import { ref, computed, watch, onMounted, nextTick } from 'vue';
|
||||
import { ElInput, ElTree, ElButton, ElMessage } from 'element-plus';
|
||||
import { Search, Camera, Close } from '@element-plus/icons-vue';
|
||||
import { ref, computed, watch, onMounted } from 'vue';
|
||||
import { ElInput, ElButton, ElMessage } from 'element-plus';
|
||||
import { Search, Camera, Close, Check } from '@element-plus/icons-vue';
|
||||
|
||||
// 修复:响应式变量定义完整,无遗漏赋值
|
||||
const searchDevice = ref('');
|
||||
const deviceTreeRef = ref(null);
|
||||
const deviceTreeData = ref([
|
||||
{
|
||||
label: "公共区域",
|
||||
|
@ -110,32 +118,19 @@ const deviceTreeData = ref([
|
|||
]
|
||||
}
|
||||
]);
|
||||
const currentLayout = ref(1);
|
||||
const videoList = ref([
|
||||
{ name: "待选择区域", bgColor: "#e6f7ff", isSelected: false }
|
||||
]);
|
||||
const selectedNodes = ref([]);
|
||||
const currentSelectedNode = ref(null);
|
||||
|
||||
// 修复:常量定义无语法错误,对象逗号规范
|
||||
const currentLayout = ref(1);
|
||||
// 重构:使用对象来记录每个位置的设备,key为位置索引,value为设备信息
|
||||
const videoPositions = ref({});
|
||||
const selectedNodes = ref([]);
|
||||
|
||||
const buttonType = [
|
||||
{ label: "1x1", type: 1 },
|
||||
{ label: "2x2", type: 2 },
|
||||
{ label: "3x3", type: 3 }
|
||||
];
|
||||
const treeProps = {
|
||||
children: "children",
|
||||
label: "label"
|
||||
};
|
||||
const currentNodeKey = "label";
|
||||
|
||||
// 修复:计算属性函数闭合完整,逻辑无语法错误
|
||||
const isDisabled = computed(() => {
|
||||
if (currentLayout.value === 1) return false;
|
||||
if (currentLayout.value === 2) return selectedNodes.value.length === 4;
|
||||
return selectedNodes.value.length === 9;
|
||||
});
|
||||
|
||||
// 计算属性
|
||||
const gridColumns = computed(() => {
|
||||
switch (currentLayout.value) {
|
||||
case 1: return "1fr";
|
||||
|
@ -158,107 +153,138 @@ const maxCapacity = computed(() => {
|
|||
return currentLayout.value === 1 ? 1 : (currentLayout.value === 2 ? 4 : 9);
|
||||
});
|
||||
|
||||
// 修复:函数定义完整,括号/大括号闭合,无非法字符
|
||||
const filterNode = (value, data) => {
|
||||
if (!value) return true;
|
||||
return data.label.toLowerCase().includes(value.toLowerCase());
|
||||
};
|
||||
|
||||
const updateVideoList = () => {
|
||||
const capacity = maxCapacity.value;
|
||||
videoList.value = Array.from({ length: capacity }, (_, index) => {
|
||||
const deviceName = selectedNodes.value[index];
|
||||
return {
|
||||
name: deviceName || "待选择区域",
|
||||
bgColor: deviceName ? `rgba(64, 158, 255, ${0.1 + (index % 5) * 0.15})` : "#e6f7ff",
|
||||
isSelected: !!deviceName
|
||||
};
|
||||
});
|
||||
};
|
||||
|
||||
const handleNodeClick = (data) => {
|
||||
if (currentLayout.value === 1) {
|
||||
selectedNodes.value = [data.label];
|
||||
updateVideoList();
|
||||
return;
|
||||
}
|
||||
|
||||
const isExist = selectedNodes.value.includes(data.label);
|
||||
if (isExist) {
|
||||
selectedNodes.value = selectedNodes.value.filter(item => item !== data.label);
|
||||
} else {
|
||||
if (selectedNodes.value.length >= maxCapacity.value) {
|
||||
const layoutLabel = buttonType.find(item => item.type === currentLayout.value).label;
|
||||
ElMessage.warning(`当前${layoutLabel}模式最多选择${maxCapacity.value}个视频,请先移除一个`);
|
||||
return;
|
||||
}
|
||||
selectedNodes.value.push(data.label);
|
||||
}
|
||||
updateVideoList();
|
||||
};
|
||||
|
||||
const syncTreeSelection = () => {
|
||||
if (currentLayout.value === 1) {
|
||||
currentSelectedNode.value = selectedNodes.value[0] || null;
|
||||
return;
|
||||
}
|
||||
|
||||
nextTick(() => {
|
||||
const treeNodes = document.querySelectorAll(".device-tree .el-tree-node");
|
||||
treeNodes.forEach(node => {
|
||||
const labelElem = node.querySelector(".el-tree-node__label");
|
||||
if (!labelElem) return;
|
||||
const label = labelElem.textContent.trim();
|
||||
if (selectedNodes.value.includes(label)) {
|
||||
node.classList.add("is-selected");
|
||||
} else {
|
||||
node.classList.remove("is-selected");
|
||||
}
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
const changeLayout = (layoutType) => {
|
||||
currentLayout.value = layoutType;
|
||||
selectedNodes.value = [];
|
||||
updateVideoList();
|
||||
};
|
||||
|
||||
const handleVideoClick = (index) => {
|
||||
const video = videoList.value[index];
|
||||
if (video.name !== "待选择区域") {
|
||||
currentSelectedNode.value = video.name;
|
||||
}
|
||||
};
|
||||
|
||||
const removeVideo = (index) => {
|
||||
const removedItem = selectedNodes.value[index];
|
||||
if (!removedItem) return;
|
||||
selectedNodes.value = selectedNodes.value.filter((_, i) => i !== index);
|
||||
updateVideoList();
|
||||
ElMessage.info(`已移除${removedItem}`);
|
||||
};
|
||||
|
||||
// 修复:watch 监听语法正确,回调函数闭合
|
||||
watch(searchDevice, (value) => {
|
||||
if (deviceTreeRef.value) {
|
||||
deviceTreeRef.value.filter(value);
|
||||
}
|
||||
const isItemDisabled = computed(() => {
|
||||
return selectedNodes.value.length >= maxCapacity.value;
|
||||
});
|
||||
|
||||
watch(selectedNodes, (newVal) => {
|
||||
syncTreeSelection();
|
||||
}, { immediate: true }); // 新增immediate,初始化时同步选中状态
|
||||
|
||||
// 修复:onMounted 钩子函数闭合,样式添加无语法错误
|
||||
onMounted(() => {
|
||||
const style = document.createElement("style");
|
||||
// 修复:样式字符串无换行错误,引号闭合
|
||||
style.textContent = `
|
||||
.device-tree .el-tree-node.is-selected .el-tree-node__content {
|
||||
background-color: #e6f7ff;
|
||||
color: #409eff;
|
||||
// 视频列表,根据位置信息生成
|
||||
const videoList = computed(() => {
|
||||
const capacity = maxCapacity.value;
|
||||
const list = [];
|
||||
|
||||
for (let i = 0; i < capacity; i++) {
|
||||
if (videoPositions.value[i]) {
|
||||
// 该位置有设备
|
||||
const device = videoPositions.value[i];
|
||||
list.push({
|
||||
name: device.label,
|
||||
bgColor: `rgba(64, 158, 255, ${0.1 + (i % 5) * 0.15})`,
|
||||
isSelected: false
|
||||
});
|
||||
} else {
|
||||
// 该位置为空
|
||||
list.push({
|
||||
name: "待选择区域",
|
||||
bgColor: "#e6f7ff",
|
||||
isSelected: false
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
return list;
|
||||
});
|
||||
|
||||
// 过滤树数据
|
||||
const filteredTreeData = computed(() => {
|
||||
if (!searchDevice.value) return deviceTreeData.value;
|
||||
|
||||
return deviceTreeData.value.map(group => ({
|
||||
...group,
|
||||
children: group.children.filter(item =>
|
||||
item.label.toLowerCase().includes(searchDevice.value.toLowerCase())
|
||||
)
|
||||
})).filter(group => group.children.length > 0);
|
||||
});
|
||||
|
||||
// 判断项目是否被选中
|
||||
const isItemSelected = (label) => {
|
||||
return selectedNodes.value.includes(label);
|
||||
};
|
||||
|
||||
// 查找第一个可用的位置
|
||||
const findAvailablePosition = () => {
|
||||
const capacity = maxCapacity.value;
|
||||
for (let i = 0; i < capacity; i++) {
|
||||
if (!videoPositions.value[i]) {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
return -1;
|
||||
};
|
||||
|
||||
// 查找设备所在的位置
|
||||
const findDevicePosition = (label) => {
|
||||
for (const [position, device] of Object.entries(videoPositions.value)) {
|
||||
if (device.label === label) {
|
||||
return parseInt(position);
|
||||
}
|
||||
}
|
||||
return -1;
|
||||
};
|
||||
|
||||
// 处理项目点击
|
||||
const handleItemClick = (item) => {
|
||||
const itemLabel = item.label;
|
||||
const position = findDevicePosition(itemLabel);
|
||||
|
||||
if (position !== -1) {
|
||||
// 设备已存在,取消选择
|
||||
delete videoPositions.value[position];
|
||||
selectedNodes.value = selectedNodes.value.filter(label => label !== itemLabel);
|
||||
ElMessage.info(`已移除${itemLabel}`);
|
||||
} else {
|
||||
// 设备不存在,尝试添加
|
||||
if (selectedNodes.value.length >= maxCapacity.value) {
|
||||
ElMessage.warning(`当前${buttonType.find(b => b.type === currentLayout.value)?.label}模式最多选择${maxCapacity.value}个视频`);
|
||||
return;
|
||||
}
|
||||
|
||||
const availablePosition = findAvailablePosition();
|
||||
if (availablePosition !== -1) {
|
||||
videoPositions.value[availablePosition] = item;
|
||||
selectedNodes.value.push(itemLabel);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// 切换布局
|
||||
const changeLayout = (layoutType) => {
|
||||
currentLayout.value = layoutType;
|
||||
videoPositions.value = {};
|
||||
selectedNodes.value = [];
|
||||
};
|
||||
|
||||
// 处理视频点击
|
||||
const handleVideoClick = (index) => {
|
||||
// 重置所有视频的选中状态
|
||||
const updatedList = [...videoList.value];
|
||||
updatedList.forEach((video, i) => {
|
||||
video.isSelected = i === index && video.name !== "待选择区域";
|
||||
});
|
||||
|
||||
// 注意:由于videoList是计算属性,这里直接修改不会持久化
|
||||
// 我们可以通过其他方式处理选中状态,或者如果需要持久化选中状态,可以添加一个响应式变量
|
||||
};
|
||||
|
||||
// 移除视频
|
||||
const removeVideo = (index) => {
|
||||
if (videoPositions.value[index]) {
|
||||
const removedDevice = videoPositions.value[index];
|
||||
delete videoPositions.value[index];
|
||||
selectedNodes.value = selectedNodes.value.filter(label => label !== removedDevice.label);
|
||||
ElMessage.info(`已移除${removedDevice.label}`);
|
||||
}
|
||||
};
|
||||
|
||||
// 监听搜索
|
||||
watch(searchDevice, () => {
|
||||
// 搜索功能已在 computed 中处理
|
||||
});
|
||||
|
||||
onMounted(() => {
|
||||
// 初始化样式
|
||||
const style = document.createElement("style");
|
||||
style.textContent = `
|
||||
.video-item .close-icon {
|
||||
position: absolute;
|
||||
top: 5px;
|
||||
|
@ -274,10 +300,6 @@ onMounted(() => {
|
|||
.video-item:hover .close-icon {
|
||||
opacity: 1;
|
||||
}
|
||||
.el-tree.is-disabled .el-tree-node__content {
|
||||
cursor: not-allowed;
|
||||
opacity: 0.7;
|
||||
}
|
||||
.video-header .icon,
|
||||
.video-placeholder .icon {
|
||||
color: #409eff;
|
||||
|
@ -295,7 +317,6 @@ onMounted(() => {
|
|||
</script>
|
||||
|
||||
<style scoped>
|
||||
/* 修复:样式选择器无语法错误,分号闭合 */
|
||||
.monitor-page {
|
||||
height: calc(100vh - 84px);
|
||||
background-color: #f5f7fa;
|
||||
|
@ -310,7 +331,7 @@ onMounted(() => {
|
|||
}
|
||||
|
||||
.device-tree-panel {
|
||||
flex: 0 0 220px;
|
||||
flex: 0 0 280px;
|
||||
background-color: #fff;
|
||||
border-radius: 8px;
|
||||
box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.1);
|
||||
|
@ -333,12 +354,74 @@ onMounted(() => {
|
|||
width: 100%;
|
||||
}
|
||||
|
||||
.device-tree {
|
||||
.custom-tree {
|
||||
flex: 1;
|
||||
overflow-y: auto;
|
||||
padding: 8px;
|
||||
}
|
||||
|
||||
.tree-group {
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
.group-label {
|
||||
font-weight: 600;
|
||||
color: #303133;
|
||||
padding: 8px 0;
|
||||
border-bottom: 1px solid #f0f0f0;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.tree-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding: 8px 12px;
|
||||
margin: 4px 0;
|
||||
border-radius: 4px;
|
||||
cursor: pointer;
|
||||
transition: all 0.2s ease;
|
||||
}
|
||||
|
||||
.tree-item:hover {
|
||||
background-color: #f5f7fa;
|
||||
}
|
||||
|
||||
.tree-item.selected {
|
||||
background-color: #e6f7ff;
|
||||
color: #409eff;
|
||||
}
|
||||
|
||||
.tree-item.disabled:not(.selected) {
|
||||
opacity: 0.5;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
|
||||
.item-content {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.item-icon {
|
||||
color: #909399;
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
.tree-item.selected .item-icon {
|
||||
color: #409eff;
|
||||
}
|
||||
|
||||
.item-label {
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.selected-indicator {
|
||||
color: #409eff;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.video-wall-panel {
|
||||
flex: 1;
|
||||
background-color: #fff;
|
||||
|
@ -376,8 +459,14 @@ onMounted(() => {
|
|||
position: relative;
|
||||
cursor: pointer;
|
||||
transition: all 0.2s ease;
|
||||
min-height: 120px;
|
||||
}
|
||||
.item-icon {
|
||||
width: 18px;
|
||||
}
|
||||
.icon {
|
||||
width: 30px;
|
||||
}
|
||||
|
||||
.video-item:hover {
|
||||
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
|
|
@ -14,8 +14,8 @@
|
|||
<el-button type="primary" @click="handleQuery">查询</el-button>
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item style="margin-left: 16px; float: right">
|
||||
<el-button type="primary" plain icon="Plus" @click="handleAdd">新增</el-button>
|
||||
<el-form-item style="margin-left: 16px; float: right" class="margin0">
|
||||
<el-button type="primary" plain icon="Plus" @click="handleAdd" class="margin0">新增</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
|
||||
|
@ -254,7 +254,6 @@ const handleCurrentChange = (val) => {
|
|||
.area-management-page {
|
||||
padding: 20px;
|
||||
background-color: #f8fafc;
|
||||
min-height: calc(100vh - 64px); /* 适配顶部导航高度 */
|
||||
}
|
||||
|
||||
/* 卡片样式:圆角 + 悬浮阴影增强层次感 */
|
||||
|
|
|
@ -28,8 +28,8 @@
|
|||
<el-button type="primary" @click="handleQuery">查询</el-button>
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item style="margin-left: 16px; float: right">
|
||||
<el-button type="primary" plain icon="Plus" @click="handleAdd">新增</el-button>
|
||||
<el-form-item style="margin-left: 16px; float: right" class="margin0">
|
||||
<el-button type="primary" plain icon="Plus" @click="handleAdd" class="margin0">新增</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
|
||||
|
@ -294,7 +294,6 @@ const handleCurrentChange = (val) => {
|
|||
.cabin-management-page {
|
||||
padding: 20px;
|
||||
background-color: #f8fafc;
|
||||
min-height: calc(100vh - 64px); /* 适配顶部导航高度 */
|
||||
}
|
||||
|
||||
/* 卡片样式:圆角 + 悬浮阴影增强层次感 */
|
||||
|
|
|
@ -237,7 +237,6 @@ const padZero = (num) => {
|
|||
.meter-monitoring-page {
|
||||
padding: 20px;
|
||||
background-color: #f8fafc;
|
||||
min-height: calc(100vh - 64px); /* 适配顶部导航高度 */
|
||||
}
|
||||
|
||||
/* 卡片样式:圆角 + 悬浮阴影,增强层次感 */
|
||||
|
|
|
@ -88,7 +88,7 @@
|
|||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<div class="trend-chart" style="height: 200px">
|
||||
<div class="trend-chart">
|
||||
<div ref="powerChartRef" class="chart-container"></div>
|
||||
</div>
|
||||
</el-card>
|
||||
|
@ -118,7 +118,7 @@
|
|||
</div>
|
||||
</div>
|
||||
<!-- 原有:横向柱状图(当月/上月/今年/去年) -->
|
||||
<div class="energy-chart" style="height: 180px">
|
||||
<div class="energy-chart" style="height: calc(100% - 47px)">
|
||||
<div ref="energyChartRef" class="chart-container"></div>
|
||||
</div>
|
||||
</el-card>
|
||||
|
@ -599,7 +599,12 @@ onBeforeUnmount(() => {
|
|||
.video-monitor-card {
|
||||
height: calc(100% - 404px);
|
||||
}
|
||||
|
||||
::v-deep .el-card__body {
|
||||
height: calc(100% - 44px);
|
||||
}
|
||||
::v-deep .power-trend-card .el-card__body {
|
||||
height: calc(100% - 80px);
|
||||
}
|
||||
::v-deep .video-monitor-card .el-card__body {
|
||||
height: calc(100% - 44px);
|
||||
}
|
||||
|
|
|
@ -346,7 +346,6 @@ const getList = () => {
|
|||
.alarm-management-page {
|
||||
padding: 20px;
|
||||
background-color: #f8fafc;
|
||||
min-height: calc(100vh - 64px);
|
||||
}
|
||||
|
||||
.page-card {
|
||||
|
|
|
@ -311,7 +311,7 @@
|
|||
import { ref, reactive, toRefs, onUnmounted } from 'vue';
|
||||
import { ElMessage, ElMessageBox } from 'element-plus';
|
||||
import { User, Edit, Upload, Plus } from '@element-plus/icons-vue';
|
||||
import ExcelExporter from "./../../utils/ExcelExporter"
|
||||
import ExcelExporter from "@/utils/ExcelExporter"
|
||||
|
||||
// 用户权限
|
||||
const isAdmin = ref(true);
|
||||
|
@ -721,7 +721,6 @@ onUnmounted(() => {
|
|||
.maintenance-page {
|
||||
padding: 20px;
|
||||
background-color: #f8fafc;
|
||||
min-height: calc(100vh - 64px);
|
||||
}
|
||||
|
||||
.page-card {
|
||||
|
|
|
@ -42,10 +42,11 @@
|
|||
<el-button type="primary" plain @click="handleExport">导出</el-button>
|
||||
</el-form-item>
|
||||
<!-- 新增巡检按钮 -->
|
||||
<el-form-item style="margin-left: auto;float: right;">
|
||||
<el-form-item style="margin-left: auto;float: right;" class="margin0">
|
||||
<el-button
|
||||
type="primary"
|
||||
icon="Plus"
|
||||
class="margin0"
|
||||
@click="handleAddInspection"
|
||||
v-if="isAdmin"
|
||||
>新增巡检</el-button>
|
||||
|
@ -627,7 +628,6 @@ onUnmounted(() => {
|
|||
.inspection-page {
|
||||
padding: 20px;
|
||||
background-color: #f8fafc;
|
||||
min-height: calc(100vh - 64px);
|
||||
}
|
||||
|
||||
.page-card {
|
||||
|
|
|
@ -356,7 +356,6 @@ onMounted(() => {
|
|||
.report-management-page {
|
||||
padding: 20px;
|
||||
background-color: #f8fafc;
|
||||
min-height: calc(100vh - 64px); /* 适配顶部导航高度 */
|
||||
}
|
||||
|
||||
.page-card {
|
||||
|
|
|
@ -70,10 +70,7 @@
|
|||
</div>
|
||||
<div class="canvas-wrapper">
|
||||
<!-- 底层Canvas:仅绘制背景图(静态内容) -->
|
||||
<canvas
|
||||
ref="bgCanvasRef"
|
||||
class="bg-canvas"
|
||||
></canvas>
|
||||
<canvas ref="bgCanvasRef" class="bg-canvas"></canvas>
|
||||
<!-- 上层Canvas:绘制图表、标记点等动态内容 -->
|
||||
<canvas
|
||||
ref="canvasRef"
|
||||
|
@ -90,6 +87,9 @@
|
|||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="charts-content">
|
||||
<div ref="chartRef" class="line-chart"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="progress-container spslider">
|
||||
|
@ -138,6 +138,9 @@
|
|||
:modelValue="tableModelValue"
|
||||
@update:modelValue="updateTableModelValue"
|
||||
></tableEle>
|
||||
|
||||
<roomPage :info="roomInfo"></roomPage>
|
||||
|
||||
</div>
|
||||
</template>
|
||||
|
||||
|
@ -158,6 +161,8 @@ import {
|
|||
import typesDictionary from "../utils/equipmentType"; //设备类型字典
|
||||
import videoEle from "./components/videoEle.vue";
|
||||
import tableEle from "./components/tableEle.vue";
|
||||
import * as echarts from "echarts";
|
||||
import roomPage from "./components/roomPage.vue";
|
||||
|
||||
const tableModelValue = ref(false);
|
||||
// 新增:底层背景Canvas引用
|
||||
|
@ -170,6 +175,8 @@ const bgCtx = ref(null);
|
|||
const ctx = ref(null);
|
||||
const img = new Image();
|
||||
|
||||
const roomInfo = ref({});
|
||||
|
||||
let _typesDictionary = reactive(typesDictionary);
|
||||
|
||||
// 恢复points定义 - 点标记数据 设备数据
|
||||
|
@ -246,9 +253,8 @@ const MOUSE_CURRENT_Y = ref(0);
|
|||
|
||||
//悬浮展示设备 摄像头基本信息相关(保持不变)
|
||||
const activeMarker = ref({ target: "" });
|
||||
const Shrink = ref(false);
|
||||
|
||||
const SELECT_ACTION_TYPE = ref('');
|
||||
const SELECT_ACTION_TYPE = ref("");
|
||||
|
||||
//背景图片/标记图片等(保持不变)
|
||||
const backgroundImage = new URL("../assets/gl.png", import.meta.url).href;
|
||||
|
@ -276,7 +282,10 @@ const icons = {
|
|||
};
|
||||
|
||||
icons.baiye.src = new URL("../assets/icon/baiye.png", import.meta.url).href;
|
||||
icons.dianbiao.src = new URL("../assets/icon/dianbiao.png", import.meta.url).href;
|
||||
icons.dianbiao.src = new URL(
|
||||
"../assets/icon/dianbiao.png",
|
||||
import.meta.url
|
||||
).href;
|
||||
icons.hongwai.src = new URL("../assets/icon/hongwai.png", import.meta.url).href;
|
||||
icons.jiawan.src = new URL("../assets/icon/jiawan.png", import.meta.url).href;
|
||||
icons.jingai.src = new URL("../assets/icon/jingai.png", import.meta.url).href;
|
||||
|
@ -303,6 +312,170 @@ const updateTableModelValue = (value) => {
|
|||
tableModelValue.value = value;
|
||||
};
|
||||
|
||||
// 图表引用和实例
|
||||
const chartRef = ref(null);
|
||||
let chartInstance = null;
|
||||
|
||||
// 状态变量
|
||||
const darkMode = ref(true);
|
||||
const chartData = ref({
|
||||
xAxis: [
|
||||
"1月",
|
||||
"2月",
|
||||
"3月",
|
||||
"4月",
|
||||
"5月",
|
||||
"6月",
|
||||
"7月",
|
||||
"8月",
|
||||
"9月",
|
||||
"10月",
|
||||
"11月",
|
||||
"12月",
|
||||
],
|
||||
series: [
|
||||
{
|
||||
name: "产品A",
|
||||
data: [120, 132, 101, 134, 90, 230, 210, 230, 180, 230, 210, 250],
|
||||
color: "#42b983", // 柔和的绿色
|
||||
},
|
||||
{
|
||||
name: "产品B",
|
||||
data: [220, 182, 191, 234, 290, 330, 310, 330, 380, 330, 310, 350],
|
||||
color: "#3498db", // 柔和的蓝色
|
||||
},
|
||||
{
|
||||
name: "产品C",
|
||||
data: [150, 232, 201, 154, 190, 330, 410, 330, 380, 430, 410, 450],
|
||||
color: "#f39c12", // 柔和的橙色
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
// 初始化图表
|
||||
const initChart = () => {
|
||||
// 销毁已有实例
|
||||
if (chartInstance) {
|
||||
chartInstance.dispose();
|
||||
}
|
||||
|
||||
// 创建新实例
|
||||
chartInstance = echarts.init(chartRef.value);
|
||||
|
||||
// 设置图表选项
|
||||
const option = {
|
||||
backgroundColor: darkMode.value
|
||||
? "rgba(30, 30, 30, 0.7)"
|
||||
: "rgba(255, 255, 255, 0.7)",
|
||||
tooltip: {
|
||||
trigger: "axis",
|
||||
backgroundColor: darkMode.value
|
||||
? "rgba(40, 40, 40, 0.9)"
|
||||
: "rgba(255, 255, 255, 0.9)",
|
||||
borderColor: darkMode.value ? "#555" : "#ddd",
|
||||
textStyle: {
|
||||
color: darkMode.value ? "#fff" : "#333",
|
||||
},
|
||||
padding: 10,
|
||||
borderRadius: 6,
|
||||
},
|
||||
legend: {
|
||||
data: chartData.value.series.map((item) => item.name),
|
||||
top: 10,
|
||||
textStyle: {
|
||||
color: darkMode.value ? "#eee" : "#555",
|
||||
},
|
||||
},
|
||||
grid: {
|
||||
left: "3%",
|
||||
right: "4%",
|
||||
bottom: "3%",
|
||||
containLabel: true,
|
||||
},
|
||||
xAxis: {
|
||||
type: "category",
|
||||
boundaryGap: false,
|
||||
data: chartData.value.xAxis,
|
||||
axisLine: {
|
||||
lineStyle: {
|
||||
color: darkMode.value ? "#555" : "#ddd",
|
||||
},
|
||||
},
|
||||
axisLabel: {
|
||||
color: darkMode.value ? "#bbb" : "#666",
|
||||
},
|
||||
splitLine: {
|
||||
show: true,
|
||||
lineStyle: {
|
||||
color: darkMode.value
|
||||
? "rgba(255, 255, 255, 0.05)"
|
||||
: "rgba(0, 0, 0, 0.05)",
|
||||
},
|
||||
},
|
||||
},
|
||||
yAxis: {
|
||||
type: "value",
|
||||
axisLine: {
|
||||
lineStyle: {
|
||||
color: darkMode.value ? "#555" : "#ddd",
|
||||
},
|
||||
},
|
||||
axisLabel: {
|
||||
color: darkMode.value ? "#bbb" : "#666",
|
||||
},
|
||||
splitLine: {
|
||||
show: true,
|
||||
lineStyle: {
|
||||
color: darkMode.value
|
||||
? "rgba(255, 255, 255, 0.05)"
|
||||
: "rgba(0, 0, 0, 0.05)",
|
||||
},
|
||||
},
|
||||
},
|
||||
series: chartData.value.series.map((item) => ({
|
||||
name: item.name,
|
||||
type: "line",
|
||||
data: item.data,
|
||||
symbol: "circle",
|
||||
symbolSize: 6,
|
||||
emphasis: {
|
||||
symbolSize: 8,
|
||||
},
|
||||
lineStyle: {
|
||||
width: 2,
|
||||
color: item.color,
|
||||
},
|
||||
itemStyle: {
|
||||
color: item.color,
|
||||
borderWidth: 2,
|
||||
borderColor: darkMode.value ? "#333" : "#fff",
|
||||
},
|
||||
areaStyle: {
|
||||
color: {
|
||||
type: "linear",
|
||||
x: 0,
|
||||
y: 0,
|
||||
x2: 0,
|
||||
y2: 1,
|
||||
colorStops: [
|
||||
{
|
||||
offset: 0,
|
||||
color: item.color + "80", // 透明度80%
|
||||
},
|
||||
{
|
||||
offset: 1,
|
||||
color: item.color + "00", // 透明度0%
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
})),
|
||||
};
|
||||
|
||||
// 设置选项
|
||||
chartInstance.setOption(option);
|
||||
};
|
||||
|
||||
const pointsToPartiData = (data) => {
|
||||
let _partiData = {};
|
||||
let areaId = 0;
|
||||
|
@ -327,7 +500,7 @@ const handleTabbar = (e) => {
|
|||
d.isSelected = false;
|
||||
}
|
||||
});
|
||||
if(e.isSelected) {
|
||||
if (e.isSelected) {
|
||||
SELECT_ACTION_TYPE.value = e.value;
|
||||
} else {
|
||||
SELECT_ACTION_TYPE.value = "";
|
||||
|
@ -401,15 +574,15 @@ const drawBackground = () => {
|
|||
|
||||
// 初始化Canvas(修改:同时初始化两层Canvas)
|
||||
const initCanvas = () => {
|
||||
nextTick(()=> {
|
||||
nextTick(() => {
|
||||
// 初始化上层主Canvas
|
||||
ctx.value = canvasRef.value.getContext("2d");
|
||||
// 初始化底层背景Canvas
|
||||
bgCtx.value = bgCanvasRef.value.getContext("2d");
|
||||
|
||||
|
||||
// 加载背景图(加载完成后绘制底层背景)
|
||||
loadImage();
|
||||
})
|
||||
});
|
||||
};
|
||||
|
||||
const getIcon = (id) => {
|
||||
|
@ -444,18 +617,18 @@ const resizeCanvas = () => {
|
|||
// 同时设置两层Canvas的大小(保持一致)
|
||||
const clientWidth = canvasRef.value.clientWidth;
|
||||
const clientHeight = canvasRef.value.clientHeight;
|
||||
|
||||
|
||||
canvasRef.value.width = clientWidth;
|
||||
canvasRef.value.height = clientHeight;
|
||||
bgCanvasRef.value.width = clientWidth;
|
||||
bgCanvasRef.value.height = clientHeight;
|
||||
|
||||
|
||||
canvasWidthNum.value = clientWidth;
|
||||
canvasHeightNum.value = clientHeight;
|
||||
|
||||
|
||||
BL.value = clientHeight / StandardC;
|
||||
getCurrentArea();
|
||||
|
||||
|
||||
// 窗口 resize 时需要重新绘制背景
|
||||
drawBackground();
|
||||
// 同时重绘上层内容
|
||||
|
@ -481,7 +654,14 @@ const getCurrentArea = () => {
|
|||
};
|
||||
|
||||
const isCGQ = (type) => {
|
||||
if (type == 26 || type == 30 || type == 31 || type == 32 || type == 34 || type == 35) {
|
||||
if (
|
||||
type == 26 ||
|
||||
type == 30 ||
|
||||
type == 31 ||
|
||||
type == 32 ||
|
||||
type == 34 ||
|
||||
type == 35
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
|
@ -516,7 +696,7 @@ const drawTooltip = () => {
|
|||
const canvasY = activeMarker.value.y * BL.value * scale.value + offsetY.value;
|
||||
|
||||
let x = canvasX - tooltipWidth / 2;
|
||||
let y = canvasY - tooltipHeight - (20 * scale.value);
|
||||
let y = canvasY - tooltipHeight - 20 * scale.value;
|
||||
|
||||
const canvasWidth = canvasRef.value.width;
|
||||
const canvasHeight = canvasRef.value.height;
|
||||
|
@ -536,7 +716,13 @@ const drawTooltip = () => {
|
|||
ctxTooltip.lineTo(x + tooltipWidth - radius, y);
|
||||
ctxTooltip.arcTo(x + tooltipWidth, y, x + tooltipWidth, y + radius, radius);
|
||||
ctxTooltip.lineTo(x + tooltipWidth, y + tooltipHeight - radius);
|
||||
ctxTooltip.arcTo(x + tooltipWidth, y + tooltipHeight, x + tooltipWidth - radius, y + tooltipHeight, radius);
|
||||
ctxTooltip.arcTo(
|
||||
x + tooltipWidth,
|
||||
y + tooltipHeight,
|
||||
x + tooltipWidth - radius,
|
||||
y + tooltipHeight,
|
||||
radius
|
||||
);
|
||||
ctxTooltip.lineTo(x + radius, y + tooltipHeight);
|
||||
ctxTooltip.arcTo(x, y + tooltipHeight, x, y + tooltipHeight - radius, radius);
|
||||
ctxTooltip.lineTo(x, y + radius);
|
||||
|
@ -580,7 +766,7 @@ const draw = (alpha = 1) => {
|
|||
const canvasHeight = canvasRef.value.height;
|
||||
|
||||
BL.value = canvasHeight / StandardC;
|
||||
|
||||
|
||||
// 清除上层画布(只清除动态内容)
|
||||
ctx.value.clearRect(0, 0, canvasWidth, canvasHeight);
|
||||
|
||||
|
@ -613,8 +799,11 @@ const draw = (alpha = 1) => {
|
|||
for (const key in points) {
|
||||
let _points = points[key];
|
||||
_points.forEach((point) => {
|
||||
if(SELECT_ACTION_TYPE.value && point.TypeId !== SELECT_ACTION_TYPE.value) {
|
||||
return
|
||||
if (
|
||||
SELECT_ACTION_TYPE.value &&
|
||||
point.TypeId !== SELECT_ACTION_TYPE.value
|
||||
) {
|
||||
return;
|
||||
}
|
||||
let status = getIconStatus(point.TypeId);
|
||||
|
||||
|
@ -662,13 +851,19 @@ const draw = (alpha = 1) => {
|
|||
ctx.lineTo(x + width - radius, y);
|
||||
ctx.arcTo(x + width, y, x + width, y + radius, radius);
|
||||
ctx.lineTo(x + width, y + height - radius);
|
||||
ctx.arcTo(x + width, y + height, x + width - radius, y + height, radius);
|
||||
ctx.arcTo(
|
||||
x + width,
|
||||
y + height,
|
||||
x + width - radius,
|
||||
y + height,
|
||||
radius
|
||||
);
|
||||
ctx.lineTo(x + radius, y + height);
|
||||
ctx.arcTo(x, y + height, x, y + height - radius, radius);
|
||||
ctx.lineTo(x, y + radius);
|
||||
ctx.arcTo(x, y, x + radius, y, radius);
|
||||
ctx.closePath();
|
||||
|
||||
|
||||
if (isStroke) {
|
||||
ctx.stroke();
|
||||
} else {
|
||||
|
@ -677,42 +872,50 @@ const draw = (alpha = 1) => {
|
|||
}
|
||||
|
||||
// 添加文字传感器数值(带圆角边框和半透明背景)
|
||||
if(point.TypeId == 26) {
|
||||
if (point.TypeId == 26) {
|
||||
// 设置字体样式
|
||||
ctx.value.font = `10px Arial`;
|
||||
const textColor = "#FFF"; // 白色字体
|
||||
const borderColor = "rgba(100, 180, 255, 0.9)"; // 浅蓝色边框
|
||||
const bgColor = "rgba(100, 180, 255, 0.3)"; // 同色系半透明背景
|
||||
|
||||
const bgColor = "rgba(100, 180, 255, 0.4)"; // 同色系半透明背景
|
||||
|
||||
let _value = point.Data[0].Value; // 传感器数值value
|
||||
|
||||
|
||||
// 计算文字位置(图标右侧10px处,垂直居中对齐)
|
||||
const textX = point.x * BL.value + (iSize * BL.value) / scale.value / 2 + 1;
|
||||
const textY = point.y * BL.value + 15; // 垂直居中调整
|
||||
|
||||
const textX = point.x * BL.value;
|
||||
const textY = point.y * BL.value + 20; // 垂直居中调整
|
||||
|
||||
// 测量文本宽度,用于计算背景框大小
|
||||
const textMetrics = ctx.value.measureText(_value);
|
||||
const padding = 3; // 文字周围的内边距
|
||||
const borderRadius = 3; // 圆角半径
|
||||
|
||||
|
||||
// 计算背景框的位置和大小
|
||||
const bgX = textX - padding + 5;
|
||||
const bgX = textX - padding - textMetrics.width / 2;
|
||||
const bgY = textY - 10; // 基于10px字体的位置调整
|
||||
const bgWidth = textMetrics.width + padding * 2;
|
||||
const bgHeight = 14; // 适合10px字体的高度
|
||||
|
||||
|
||||
// 绘制圆角背景
|
||||
ctx.value.fillStyle = bgColor;
|
||||
roundedRect(ctx.value, bgX, bgY, bgWidth, bgHeight, borderRadius);
|
||||
|
||||
roundedRect(ctx.value, bgX, bgY + 10, bgWidth, bgHeight, borderRadius);
|
||||
|
||||
// 绘制圆角边框
|
||||
ctx.value.strokeStyle = borderColor;
|
||||
ctx.value.lineWidth = 1;
|
||||
roundedRect(ctx.value, bgX, bgY, bgWidth, bgHeight, borderRadius, true);
|
||||
|
||||
roundedRect(
|
||||
ctx.value,
|
||||
bgX,
|
||||
bgY + 10,
|
||||
bgWidth,
|
||||
bgHeight,
|
||||
borderRadius,
|
||||
true
|
||||
);
|
||||
|
||||
// 绘制文字(确保在最上层)
|
||||
ctx.value.fillStyle = textColor;
|
||||
ctx.value.fillText(_value, textX + 4, textY);
|
||||
ctx.value.fillText(_value, textX - textMetrics.width / 2, textY + 10);
|
||||
}
|
||||
|
||||
if (point.target == "device" && point.IsOpen) {
|
||||
|
@ -727,7 +930,11 @@ const draw = (alpha = 1) => {
|
|||
ctx.value.strokeStyle = window.customConfigUrl.faultColor;
|
||||
ctx.value.lineWidth = (4 / scale.value) * BL.value;
|
||||
ctx.value.stroke();
|
||||
} else if (point.target == "device" && !isCGQ(point.TypeId) && point.TypeId != 66) {
|
||||
} else if (
|
||||
point.target == "device" &&
|
||||
!isCGQ(point.TypeId) &&
|
||||
point.TypeId != 66
|
||||
) {
|
||||
ctx.value.strokeStyle = window.customConfigUrl.closeColor;
|
||||
ctx.value.lineWidth = (4 / scale.value) * BL.value;
|
||||
ctx.value.stroke();
|
||||
|
@ -759,9 +966,8 @@ const animate = () => {
|
|||
|
||||
// 鼠标移动处理(保持不变,只影响上层Canvas)
|
||||
let mouseMoveTimeout = null;
|
||||
const MOUSE_MOVE_DELAY = 16;
|
||||
const onMouseMove = (e) => {
|
||||
if(!BL.value) return;
|
||||
if (!BL.value) return;
|
||||
if (dragging.value === null) {
|
||||
if (mouseMoveTimeout) {
|
||||
cancelAnimationFrame(mouseMoveTimeout);
|
||||
|
@ -808,7 +1014,8 @@ const onMouseMove = (e) => {
|
|||
activeReferenceLine.value = closestLine;
|
||||
if (closestLine !== null) {
|
||||
const easingFactor = 0.2;
|
||||
currentPoint.y = currentPoint.y + (closestLine - currentPoint.y) * easingFactor;
|
||||
currentPoint.y =
|
||||
currentPoint.y + (closestLine - currentPoint.y) * easingFactor;
|
||||
if (Math.abs(currentPoint.y - closestLine) < 10) {
|
||||
currentPoint.y = closestLine;
|
||||
}
|
||||
|
@ -919,15 +1126,22 @@ const startDrag = (e) => {
|
|||
};
|
||||
|
||||
const mouseUp = () => {
|
||||
endDrag();
|
||||
console.log('mouseUp');
|
||||
endDrag(1);
|
||||
updateProgress();
|
||||
};
|
||||
|
||||
const mouseLeave = () => {
|
||||
endDrag();
|
||||
console.log('mouseLeave');
|
||||
endDrag(2);
|
||||
};
|
||||
|
||||
const endDrag = () => {
|
||||
const endDrag = (type) => {
|
||||
|
||||
if(type == 1 && dragging.value == "point") {
|
||||
// 拖拽图标结束时 可以调用接口上传坐标信息
|
||||
console.log(currentPoint,"=====> currentPoint");
|
||||
}
|
||||
dragging.value = null;
|
||||
handleMouseOut();
|
||||
getCurrentArea();
|
||||
|
@ -979,7 +1193,10 @@ const handleWheel = (e) => {
|
|||
|
||||
const zoomIntensity = 0.1;
|
||||
const wheel = e.deltaY < 0 ? 1 : -1;
|
||||
const newScale = Math.max(0.75, Math.min(2, scale.value + wheel * zoomIntensity));
|
||||
const newScale = Math.max(
|
||||
0.75,
|
||||
Math.min(2, scale.value + wheel * zoomIntensity)
|
||||
);
|
||||
|
||||
scalePercent.value = Math.round(newScale * 100);
|
||||
|
||||
|
@ -987,7 +1204,7 @@ const handleWheel = (e) => {
|
|||
offsetY.value = mouseY - imgY * newScale;
|
||||
|
||||
scale.value = newScale;
|
||||
|
||||
|
||||
// 缩放时需要重新绘制背景
|
||||
drawBackground();
|
||||
// 同时重绘上层内容
|
||||
|
@ -1005,7 +1222,9 @@ const handleCanvasClick = (e) => {
|
|||
const originalY = (y - offsetY.value) / scale.value;
|
||||
|
||||
console.log(
|
||||
`点击坐标: (${(originalX / BL.value).toFixed(1)}, ${(originalY / BL.value).toFixed(1)})`
|
||||
`点击坐标: (${(originalX / BL.value).toFixed(1)}, ${(
|
||||
originalY / BL.value
|
||||
).toFixed(1)})`
|
||||
);
|
||||
|
||||
let clickedPoint = false;
|
||||
|
@ -1053,7 +1272,7 @@ const resetView = () => {
|
|||
selectedPointId.value = null;
|
||||
progress.value = 0;
|
||||
selectAreaId.value = 1;
|
||||
|
||||
|
||||
// 重置背景
|
||||
drawBackground();
|
||||
// 重置上层内容
|
||||
|
@ -1182,6 +1401,7 @@ const upDialogZindex = (e) => {
|
|||
// 初始化
|
||||
onMounted(() => {
|
||||
getJson();
|
||||
initChart();
|
||||
window.addEventListener("resize", resizeCanvas);
|
||||
initCanvas();
|
||||
// animate(); // 根据原代码注释,按需启用
|
||||
|
@ -1327,7 +1547,8 @@ body {
|
|||
}
|
||||
|
||||
/* 新增:两层Canvas样式,确保完全重叠 */
|
||||
.bg-canvas, .main-canvas {
|
||||
.bg-canvas,
|
||||
.main-canvas {
|
||||
display: block;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
|
@ -1428,9 +1649,8 @@ button:active {
|
|||
background: rgba(255, 255, 255, 0.1);
|
||||
backdrop-filter: blur(4px);
|
||||
border-radius: 4px;
|
||||
margin-top: 15px;
|
||||
position: absolute;
|
||||
bottom: 30px;
|
||||
top: 30px;
|
||||
left: 50%;
|
||||
transform: translateX(-50%);
|
||||
box-shadow: 0 4px 10px rgba(255, 255, 255, 0.1);
|
||||
|
@ -1452,6 +1672,24 @@ button:active {
|
|||
width: 100%;
|
||||
}
|
||||
|
||||
.charts-content {
|
||||
width: 800px;
|
||||
height: 240px;
|
||||
background: rgba(255, 255, 255, 0.1);
|
||||
backdrop-filter: blur(4px);
|
||||
border-radius: 4px;
|
||||
position: absolute;
|
||||
bottom: 30px;
|
||||
left: 50%;
|
||||
transform: translateX(-50%);
|
||||
box-shadow: 0 4px 10px rgba(255, 255, 255, 0.1);
|
||||
display: flex;
|
||||
.line-chart {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
/deep/ .el-slider__button {
|
||||
height: 10px !important;
|
||||
width: 10px !important;
|
||||
|
|
|
@ -185,7 +185,6 @@ const handleCurrentChange = (val) => {
|
|||
.operation-record-page {
|
||||
padding: 20px;
|
||||
background-color: #f8fafc;
|
||||
min-height: calc(100vh - 64px); /* 适配顶部导航高度 */
|
||||
}
|
||||
|
||||
/* 卡片样式:圆角 + 悬浮阴影增强层次感 */
|
||||
|
|
|
@ -16,8 +16,8 @@
|
|||
<el-form-item>
|
||||
<el-button type="primary" @click="handleQuery">查询</el-button>
|
||||
</el-form-item>
|
||||
<el-form-item style="margin-left: 16px; float: right">
|
||||
<el-button plain @click="handleAdd">新增</el-button>
|
||||
<el-form-item style="margin-left: 16px; float: right" class="margin0">
|
||||
<el-button plain @click="handleAdd" class="margin0">新增</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
|
||||
|
@ -444,7 +444,6 @@ const getList = () => {};
|
|||
.meter-monitoring-page {
|
||||
padding: 20px;
|
||||
background-color: #f8fafc;
|
||||
min-height: calc(100vh - 64px); /* 适配顶部导航高度 */
|
||||
}
|
||||
|
||||
/* 卡片样式:圆角 + 悬浮阴影,增强层次感 */
|
||||
|
|
Loading…
Reference in New Issue