Compare commits

36 Commits

Author SHA1 Message Date
fks-xuxinyue
5ed7449392 taskId:083 remark:"commit" 2024-07-11 09:17:20 +08:00
fks-yangshouda
a698c69b7c 设备中心 - 能耗监测 - 图表 左侧树和右侧组件联动 2024-07-11 08:45:20 +08:00
xuziqiang
5fde3175f1 fix: tree组件显示异常 2024-07-10 15:17:14 +08:00
xuziqiang
149f44a8dd feat: 调整树样式 2024-07-10 14:51:42 +08:00
fks-xuxinyue
060ac6d486 taskid:067 remark:commit 2024-07-10 11:01:46 +08:00
xuziqiang
7b624f6549 Merge branch 'temp' of http://123.60.103.97:3000/xuziqiang/SaaS-lib into temp 2024-07-09 18:04:10 +08:00
xuziqiang
717b2aad72 feat: 分组管理设备树 2024-07-09 18:04:09 +08:00
fks-yangshouda
f98a8be0b9 Merge branch 'temp' of http://123.60.103.97:3000/xuziqiang/SaaS-lib into temp 2024-07-09 17:35:59 +08:00
fks-yangshouda
de2202e360 1.监控中心 - 设备监测 对接接口 2024-07-09 17:35:45 +08:00
xuziqiang
6b93422b08 Merge branch 'temp' of http://123.60.103.97:3000/xuziqiang/SaaS-lib into temp 2024-07-09 16:27:22 +08:00
xuziqiang
30286253e7 feat: 分组管理 2024-07-09 16:27:21 +08:00
fks-yangshouda
a0d928feaa 1.监控中心 - 设备监测 左侧树与右侧组件联动
2.对接设备类型接口
2024-07-09 16:08:31 +08:00
zhaohy
50ca1daca4 add:对接设备告警 对接设备告警 设备类型 设备名称 设备点位 2024-07-09 15:56:18 +08:00
fks-yangshouda
fb1980d73a 1.设备监控 图表下载 时间选择改为三天
2.能耗监控 - 图表前台页面
2024-07-09 10:10:17 +08:00
xuziqiang
ab17c4a1f9 fix: 获取字典数据方法 2024-07-08 17:44:07 +08:00
xuziqiang
aab4c9a600 fix: 高度调整 2024-07-08 17:32:10 +08:00
fks-xuxinyue
0855659d74 taskId:067 remark:"commit" 2024-07-08 11:45:01 +08:00
zhaohy
3abdde797b fix: 还原工作组件 修改告警设置页面 2024-07-08 11:24:24 +08:00
zhaohy
d004a06ec1 fix:修改样式 2024-07-08 10:53:27 +08:00
fks-yangshouda
76248c0f70 Merge branch 'temp' of http://123.60.103.97:3000/xuziqiang/SaaS-lib into temp 2024-07-08 10:42:46 +08:00
fks-yangshouda
6e294e13eb 注释环境监测路由 2024-07-08 10:42:35 +08:00
zhaohy
7a9bea2f8e Merge branch 'temp' of http://123.60.103.97:3000/xuziqiang/SaaS-lib into temp 2024-07-08 10:40:28 +08:00
zhaohy
77052ae284 fix:还原工作组件 2024-07-08 10:40:26 +08:00
chenpingsen
33eb153885 add:照明系统页面修改/修改中 2024-07-08 10:34:43 +08:00
28723
a3a5b63ea5 Merge branch 'temp' of http://123.60.103.97:3000/xuziqiang/SaaS-lib into temp 2024-07-08 10:32:55 +08:00
chenpingsen
7a20eb8603 Merge branch 'temp' of http://123.60.103.97:3000/xuziqiang/SaaS-lib into temp 2024-07-08 10:19:53 +08:00
28723
dc29be2718 Merge branch 'temp' of http://123.60.103.97:3000/xuziqiang/SaaS-lib into temp 2024-07-08 10:13:13 +08:00
28723
f28b224c91 taskId:067 remark:"commit" 2024-07-08 10:13:07 +08:00
fks-yangshouda
bfd6282a99 Merge branch 'temp' of http://123.60.103.97:3000/xuziqiang/SaaS-lib into temp 2024-07-08 10:09:33 +08:00
28723
64cfd47d50 Merge branch 'temp' of http://123.60.103.97:3000/xuziqiang/SaaS-lib into temp 2024-07-08 10:05:22 +08:00
28723
796d2fcf63 碳排因子库页面代码提交 2024-07-08 10:04:04 +08:00
fks-yangshouda
287dc9262b * add: 添加监控中心-设备监测页面 2024-07-08 10:03:22 +08:00
xuziqiang
f39fb299c4 Merge branch 'temp' of http://123.60.103.97:3000/xuziqiang/SaaS-lib into temp 2024-07-08 10:02:30 +08:00
chenpingsen
9ad9446cae add:照明系统样式 2024-07-08 10:02:05 +08:00
28723
1d4b76492d 碳排因子库页面 2024-07-08 09:48:44 +08:00
zhaohy
a16b26370f add:添加告警管理内容 告警统揽和告警设置 修改公共组件 适配多种情况 2024-07-08 08:24:18 +08:00
64 changed files with 10087 additions and 2215 deletions

View File

@@ -43,6 +43,20 @@
</script>
<style lang="less" scoped>
:deep(.ns-content-main) {
.ant-tabs-content {
height: 100%;
}
}
// :deep(.ant-menu-item),
// :deep(.ant-menu-submenu-title) {
// &:not(.ant-menu-item-selected) {
// // &:not(.ant-menu-item-active) {
// .anticon {
// color: rgb(141, 150, 163);
// }
// }
// }
// :deep(.ns-content) {
// // padding-top: 80px !important;
// }

View File

@@ -0,0 +1,5 @@
export enum deviceAlarms {
getTableList = '/carbon-smart/api/AlarmEquipment/selectAlarmEquipment', //设备告警分页
addOrUpNewData = '/carbon-smart/api/AlarmEquipment/creatOrUpdate', //设备告警添加 修海
del = '/carbon-smart/api/AlarmEquipment/delete', //设备告警删除
}

View File

@@ -0,0 +1,15 @@
// 碳排因子库接口
export enum carbonEmissionFactorLibrary {
getTableList = '/carbon-smart/api/carbon/emission/factor/queryCarbonFactorPage',
creatOrUpdate = '/carbon-smart/api/carbon/emission/factor/creatOrUpdate',
del = '/carbon-smart/api/carbon/emission/factor/del',
getEmissionProcess = '/carbon-smart/api/carbon/emission/factor/getEmissionProcess',
findById = '/carbon-smart/api/carbon/emission/factor/findById',
getCarbonFactorTree = '/carbon-smart/api/carbon/emission/type/getCarbonFactorTree',
creat = '/carbon-smart/api/carbon/emission/type/creatOrUpdate',
delTreeNode = '/carbon-smart/api/carbon/emission/type/del',
}
// 碳排管理-碳排统计接口
export enum energyConsumption {
pageList = '/carbon-smart/api/carbon/stats/pageList',
}

View File

@@ -3,9 +3,12 @@ export enum device {
queryDeviceTree = `${BASE_URL}/deviceInfo/queryDeviceTree`, // 左侧树
queryDevicePage = `${BASE_URL}/deviceInfo/queryDevicePage`, // 列表
dropArea = `${BASE_URL}/deviceInfo/dropArea`, // 查询下拉区域
queryDevicePoint = `${BASE_URL}/deviceInfo/queryDevicePoint`, // 获取设备点位
}
export enum group {
queryDeviceGroupTree = `${BASE_URL}/deviceGroup/queryDeviceGroupTree`, // 左侧树
creatOrUpdate = `${BASE_URL}/deviceGroup/creatOrUpdate`, // 左侧树节点新增编辑
del = `${BASE_URL}/deviceGroup/del`,
move = `${BASE_URL}/deviceGroup/move`,
}

View File

@@ -1,3 +1,6 @@
import { get } from 'lodash-es';
import { http } from '/nerv-lib/util/http';
/***
*配置接口 格式 module:Array<resource>
*/
@@ -6,4 +9,32 @@ export const apiModule = {
};
export const BASE_URL = '/carbon-smart';
export const dict = `${BASE_URL}/client/dict/listByKey`;
interface dictHttpConfig {
api?: string;
keyField?: string;
params: object;
transform?: Function;
}
/**
* 获取字典数据(首次获取,后续读缓存)
*/
export const dict = async ({
api = `${BASE_URL}/client/dict/listByKey`,
params = {},
keyField = 'dicKey',
transform = (res: any) => res,
}: dictHttpConfig) => {
const dictMap = JSON.parse(sessionStorage.getItem('dictMap') || '{}') as Object;
const key = get(params, keyField) as keyof typeof dictMap;
if (!dictMap.hasOwnProperty(key)) {
const res = await http.post(api, params);
const options = get(transform(res), `data.${key}`);
dictMap[key] = options;
sessionStorage.setItem('dictMap', JSON.stringify(dictMap));
}
return Promise.resolve({ data: { data: get(dictMap, key) } });
};

View File

@@ -1,13 +1,14 @@
<template>
<ns-modal
ref="modalRef"
centered
v-bind="extraModalConfig"
destroyOnClose
v-model:visible="visible"
:title="title"
:okButtonProps="buttonProps"
@ok="handleOk">
<ns-form ref="formRef" :schemas="schemas" :model="formData" formLayout="formVertical" />
<ns-form ref="formRef" :schemas="schemas" :model="formData" formLayout="vertical" />
</ns-modal>
</template>
@@ -21,6 +22,7 @@
api: string | object | Function;
data?: object;
extraModalConfig?: object;
success?: Function;
};
const route = useRoute();
const { httpRequest } = useApi();
@@ -59,14 +61,15 @@
formRef.value
.triggerSubmit()
.then((data: any) => {
const { api } = props;
const { api, success } = props;
const requestConfig: HttpRequestConfig = { method: 'POST' };
const { params } = route;
httpRequest({ api, params: data, pathParams: params, requestConfig })
.then(() => {
.then((res) => {
NsMessage.success('操作成功', 1, () => {
toggle();
success && success(res);
});
})
.finally(() => {

View File

@@ -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="1720144479404" class="icon" viewBox="0 0 1142 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="2572" xmlns:xlink="http://www.w3.org/1999/xlink" width="17.84375" height="16"><path d="M1102.769231 39.384615v945.23077H39.384615V39.384615h1063.384616m0-39.384615H39.384615a39.384615 39.384615 0 0 0-39.384615 39.384615v945.23077a39.384615 39.384615 0 0 0 39.384615 39.384615h1063.384616a39.384615 39.384615 0 0 0 39.384615-39.384615V39.384615a39.384615 39.384615 0 0 0-39.384615-39.384615z" fill="#4D4D4D" p-id="2573"></path><path d="M39.384615 393.846154h1063.384616v39.384615H39.384615zM39.384615 590.769231h1063.384616v39.384615H39.384615zM39.384615 787.692308h1063.384616v39.384615H39.384615zM39.384615 196.923077h1063.384616v39.384615H39.384615z" fill="#B3B3B3" p-id="2574"></path><path d="M315.076923 196.923077v787.692308H275.692308V196.923077zM590.769231 196.923077v787.692308h-39.384616V196.923077zM866.461538 196.923077v787.692308h-39.384615V196.923077z" fill="#B3B3B3" p-id="2575"></path><path d="M39.384615 39.384615h1063.384616v157.538462H39.384615z" fill="#05AFC8" p-id="2576"></path></svg>

After

Width:  |  Height:  |  Size: 1.2 KiB

View File

@@ -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="1720145764410" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="4680" xmlns:xlink="http://www.w3.org/1999/xlink" width="16" height="16"><path d="M495.611479 159.364238C285.562631 159.364238 115.284768 329.642102 115.284768 539.690949S285.562631 920.01766 495.611479 920.01766 875.93819 749.739797 875.93819 539.690949H518.216336c-12.484662 0-22.604857-10.120194-22.604857-22.604856V159.364238z" fill="#839BFB" p-id="4681"></path><path d="M562.860927 495.046358h368.459161c0-215.978102-175.085916-391.064018-391.064017-391.064018v368.459161c0 12.484662 10.120194 22.604857 22.604856 22.604857z" fill="#839BFB" fill-opacity=".6" p-id="4682"></path></svg>

After

Width:  |  Height:  |  Size: 838 B

View File

@@ -0,0 +1,10 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="8.98974609375" height="8.14453125" viewBox="0 0 8.98974609375 8.14453125" fill="none">
<path d="M7.93101 4.53462L6.23624 4.53462L5.52032 3.46466L6.49265 1.79796L5.44381 0L3.32621 0L2.27236 1.79796L3.29704 3.54617L2.62652 4.54987L1.05875 4.54987L0 6.34726L1.05875 8.1452L3.17144 8.1452L4.06128 6.62695L4.93729 6.62695L5.81886 8.13029L7.93102 8.13029L8.98978 6.33258L7.93101 4.53462ZM3.19734 4.59392L3.8643 3.59569L5.00553 3.59569L5.73168 4.68208L4.88461 6.1274L4.09644 6.1274L3.19734 4.59392Z" fill="url(#linear_fill_60_2513)" >
</path>
<defs>
<linearGradient id="linear_fill_60_2513" x1="4.494873046875" y1="0" x2="4.494873046875" y2="8.14453125" gradientUnits="userSpaceOnUse">
<stop offset="0" stop-color="#4DACE6" />
<stop offset="1" stop-color="#2A93D5" />
</linearGradient>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 888 B

View File

@@ -0,0 +1,12 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" width="36px" height="36px" viewBox="0 0 36 36" enable-background="new 0 0 36 36" xml:space="preserve"> <image id="image0" width="36" height="36" x="0" y="0"
xlink:href="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACQAAAAkBAMAAAATLoWrAAAAIGNIUk0AAHomAACAhAAA+gAAAIDo
AAB1MAAA6mAAADqYAAAXcJy6UTwAAAASUExURQAAAI2Vo46Wo4uXo42Wo////3MrvScAAAAEdFJO
UwCAv0BHJ479AAAAAWJLR0QF+G/pxwAAAAd0SU1FB+gHBAISL9sg4ewAAABSSURBVCjPY2CgCxBU
cVJGFRFxAQIUMUYXMBBAV+Ti4ogQYXZxQVfGBBNygAuZwIQQOlXwCDnBhVzgAL+QA8vQFcIELi4Y
nsQUcnHBEMMiREsAALpDPLVxCYzSAAAAJXRFWHRkYXRlOmNyZWF0ZQAyMDI0LTA3LTA0VDAyOjE4
OjQ3KzAwOjAwPdooNQAAACV0RVh0ZGF0ZTptb2RpZnkAMjAyNC0wNy0wNFQwMjoxODo0NyswMDow
MEyHkIkAAAAodEVYdGRhdGU6dGltZXN0YW1wADIwMjQtMDctMDRUMDI6MTg6NDcrMDA6MDAbkrFW
AAAAAElFTkSuQmCC" />
</svg>

After

Width:  |  Height:  |  Size: 1.0 KiB

View File

@@ -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="1720061440927" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="11363" xmlns:xlink="http://www.w3.org/1999/xlink" width="16" height="16"><path d="M659.925333 128a74.666667 74.666667 0 0 1 71.338667 52.618667L754.56 256H821.333333c64.8 0 117.333333 52.533333 117.333334 117.333333v426.666667c0 64.8-52.533333 117.333333-117.333334 117.333333H202.666667c-64.8 0-117.333333-52.533333-117.333334-117.333333V373.333333c0-64.8 52.533333-117.333333 117.333334-117.333333h66.773333l23.296-75.381333A74.666667 74.666667 0 0 1 364.074667 128h295.850666zM512 405.333333c-88.362667 0-160 71.637333-160 160 0 88.362667 71.637333 160 160 160 88.362667 0 160-71.637333 160-160 0-88.362667-71.637333-160-160-160z m0 256a96 96 0 1 0 0-192 96 96 0 0 0 0 192z" p-id="11364"></path></svg>

After

Width:  |  Height:  |  Size: 954 B

View File

@@ -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="1720061108318" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="10061" xmlns:xlink="http://www.w3.org/1999/xlink" width="16" height="16"><path d="M512 12L94.71 138v314.92C94.71 657.7 263.86 880.16 512 1012c248.14-131.84 417.29-354.3 417.29-559.08V138z m209.91 510H562v159.91H462V522H302.09V422H462V262.09h100V422h159.91z" p-id="10062"></path></svg>

After

Width:  |  Height:  |  Size: 535 B

View File

@@ -0,0 +1,10 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="9.1005859375" height="9.099609375" viewBox="0 0 9.1005859375 9.099609375" fill="none">
<path d="M7.01762 4.69157L2.30136 4.69157C2.04227 4.66303 1.84615 4.44409 1.84615 4.18343C1.84615 3.92276 2.04227 3.70384 2.30136 3.67525L7.01762 3.67525C7.27673 3.70384 7.47285 3.92276 7.47285 4.18343C7.47285 4.44409 7.27673 4.66303 7.01762 4.69157ZM7.01762 6.69587L2.30136 6.69587C2.04227 6.6673 1.84615 6.44837 1.84615 6.18771C1.84615 5.92704 2.04227 5.70809 2.30136 5.67952L7.01762 5.67952C7.27673 5.70809 7.47285 5.92704 7.47285 6.18771C7.47285 6.44837 7.27673 6.6673 7.01762 6.69587ZM9.09498 8.33346L9.09498 1.98149C9.09498 1.21918 8.46167 1.27564 8.46167 1.27564L4.88941 1.27564C4.77006 1.27634 4.65399 1.23653 4.56021 1.16271C4.56021 1.16271 4.40816 0.880585 4.12926 0.428757C3.87605 -0.0795288 3.54708 0.00531769 3.54708 0.00531769L0.785301 0.00531769C0 0.00531769 0 0.824379 0 0.824379L0 8.27722C0 9.2087 0.633301 9.09628 0.633301 9.09628L8.53744 9.09628C9.19643 9.09547 9.09498 8.33345 9.09498 8.33345L9.09498 8.33346Z" fill="url(#linear_fill_60_2485)" >
</path>
<defs>
<linearGradient id="linear_fill_60_2485" x1="4.55029296875" y1="0" x2="4.55029296875" y2="9.099609375" gradientUnits="userSpaceOnUse">
<stop offset="0" stop-color="#4DACE6" />
<stop offset="1" stop-color="#2A93D5" />
</linearGradient>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 1.4 KiB

View File

@@ -0,0 +1,4 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="14.5244140625" height="14.5294189453125" viewBox="0 0 14.5244140625 14.5294189453125" fill="none">
<path d="M6.61912 12.2301C6.6032 12.5346 6.44482 12.8138 6.19166 12.9837L3.73703 14.4259C3.46877 14.5639 3.15036 14.5639 2.8821 14.4259L0.427467 12.9837C0.174295 12.8138 0.0159187 12.5346 0 12.2301L0 9.34584C0.0162582 9.04147 0.174555 8.76241 0.427467 8.59227L2.88209 7.15012C3.15031 7.01189 3.46881 7.01189 3.73703 7.15012L6.19165 8.59337C6.44482 8.76326 6.6032 9.04247 6.61912 9.34694L6.61912 12.2301ZM14.5239 12.2301C14.508 12.5346 14.3497 12.8138 14.0965 12.9837L11.6419 14.4259C11.3736 14.5639 11.0552 14.5639 10.7869 14.4259L8.33231 12.9837C8.079 12.814 7.92058 12.5347 7.90484 12.2301L7.90484 9.34584C7.92075 9.04137 8.07913 8.76217 8.33231 8.59227L10.7869 7.14902C11.0551 7.01079 11.3736 7.01079 11.6419 7.14902L14.0965 8.59117C14.3495 8.76118 14.5079 9.04031 14.524 9.34475L14.5239 12.2301ZM10.5842 5.18465C10.5683 5.48912 10.4099 5.76833 10.1567 5.93822L7.70211 7.38147C7.43394 7.51988 7.11535 7.51988 6.84718 7.38147L4.39256 5.93822C4.13938 5.76833 3.981 5.48912 3.96508 5.18465L3.96508 2.30036C3.98117 1.99594 4.13951 1.7168 4.39256 1.54678L6.84718 0.103527C7.11544 -0.0345116 7.43386 -0.0345116 7.70212 0.103527L10.1567 1.54568C10.41 1.71546 10.5685 1.99473 10.5842 2.29926L10.5842 5.18465Z" fill="currentColor" >
</path>
</svg>

After

Width:  |  Height:  |  Size: 1.4 KiB

View File

@@ -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="1720430904326" class="icon" viewBox="0 0 1354 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="3403" xmlns:xlink="http://www.w3.org/1999/xlink" width="21.15625" height="16"><path d="M1034.83248 645.406063l-191.508238 191.508238a53.622507 53.622507 0 0 1-75.964301 0l-191.508238-191.508238a53.622507 53.622507 0 0 1 0-75.964301 53.622507 53.622507 0 0 1 75.965301 0l102.775054 102.775054V303.884205a53.622507 53.622507 0 1 1 107.245014 0v368.332611l95.753119-102.775054a53.622507 53.622507 0 0 1 75.965301 0 53.622507 53.622507 0 0 1 1.276988 75.964301z m262.364587-175.548385a365.141641 365.141641 0 0 0-97.030108-98.94609 378.546518 378.546518 0 0 0-29.364729-123.202867 397.697341 397.697341 0 0 0-89.369178-127.671825 404.719277 404.719277 0 0 0-133.418773-91.28516A408.549242 408.549242 0 0 0 792.894706 0.026a400.251318 400.251318 0 0 0-225.977921 67.665377 410.464224 410.464224 0 0 0-97.669102 91.285161 261.088598 261.088598 0 0 0-63.836412-7.65993 247.044727 247.044727 0 0 0-176.18738 70.857348 245.129745 245.129745 0 0 0-72.134336 202.360139 324.925011 324.925011 0 0 0-84.263225 72.77333A308.965158 308.965158 0 0 0 0.053 702.22054a310.242146 310.242146 0 0 0 97.029107 228.531898 331.307952 331.307952 0 0 0 105.969025 69.58136 330.030964 330.030964 0 0 0 127.670826 23.619782h656.233963A360.034688 360.034688 0 0 0 1242.298572 919.261544a360.034688 360.034688 0 0 0 81.071254-116.181932 354.288741 354.288741 0 0 0 27.449748-138.523725 344.713829 344.713829 0 0 0-54.260501-194.699209z" fill="#5656FB" p-id="3404"></path></svg>

After

Width:  |  Height:  |  Size: 1.7 KiB

View File

@@ -0,0 +1,50 @@
const Base = () => import('/nerv-lib/saas/view/system/layout/content.vue');
const alarmManagement = {
path: '/alarmManagement',
name: 'AlarmManagement',
meta: { title: '告警管理', icon: 'gaojingguanli', index: 4 },
redirect: { name: 'alarmManagement' },
children: [
{
path: 'alarmOverview',
name: 'AlarmOverview',
meta: { title: '告警总览', hideChildren: true, icon: 'gaojingguanli' },
component: Base,
redirect: { name: 'alarmOverviewIndex' },
children: [
{
path: 'index',
name: 'alarmOverviewIndex',
// component: () => import('/nerv-lib/saas/view/menuManage/index.vue'),
component: () => import('/@/view/alarmManagement/alarmOverview/index.vue'),
meta: {
title: '告警总览',
keepAlive: true,
// backApi: [],
},
},
],
},
{
path: 'alarmSettings',
name: 'AlarmSettings',
meta: { title: '告警设置', hideChildren: true, icon: 'gaojingguanli' },
component: Base,
redirect: { name: 'alarmSettingsIndex' },
children: [
{
path: 'index',
name: 'alarmSettingsIndex',
// component: () => import('/nerv-lib/saas/view/menuManage/index.vue'),
component: () => import('/@/view/alarmManagement/alarmSettings/index.vue'),
meta: {
title: '告警设置',
keepAlive: true,
// backApi: [],
},
},
],
},
],
};
export default alarmManagement;

View File

@@ -0,0 +1,50 @@
const Base = () => import('/nerv-lib/saas/view/system/layout/content.vue');
const equipment = {
path: '/carbonEmissionManage',
name: 'CarbonEmissionManage',
meta: { title: '碳排管理', icon: 'tanpaiguanli', index: 1 },
redirect: { name: 'CarbonEmissionStatistics' },
children: [
{
path: 'carbonEmissionStatistics',
name: 'CarbonEmissionStatistics',
meta: { title: '碳排统计', hideChildren: true, icon: 'tanpaiguanli' },
component: Base,
redirect: { name: 'CarbonEmissionStatisticsIndex' },
children: [
{
path: 'index',
name: 'CarbonEmissionStatisticsIndex',
// component: () => import('/nerv-lib/saas/view/menuManage/index.vue'),
component: () => import('/@/view/carbonEmissionManage/carbonEmissionStatistics/index.vue'),
meta: {
title: '碳排统计',
keepAlive: true,
// backApi: [],
},
},
],
},
{
path: 'carbonEmissionFactorLibrary',
name: 'CarbonEmissionFactorLibrary',
meta: { title: '碳排因子库', hideChildren: true, icon: 'tanpaiguanli' },
component: Base,
redirect: { name: 'CarbonEmissionFactorLibraryIndex' },
children: [
{
path: 'index',
name: 'CarbonEmissionFactorLibraryIndex',
// component: () => import('/nerv-lib/saas/view/menuManage/index.vue'),
component: () => import('/@/view/carbonEmissionManage/carbonEmissionFactorLibrary/index.vue'),
meta: {
title: '碳排因子库',
keepAlive: true,
// backApi: [],
},
},
],
},
],
};
export default equipment;

View File

@@ -0,0 +1,86 @@
const Base = () => import('/nerv-lib/saas/view/system/layout/content.vue');
const equipment = {
path: '/monitor',
name: 'Monitor',
meta: { title: '监控中心', icon: 'jiankongzhongxin', index: 1 },
redirect: { name: 'EnvironmentMonitor' },
children: [
// {
// path: 'environmentMonitor',
// name: 'EnvironmentMonitor',
// meta: { title: '环境监测', hideChildren: true, icon: 'huanjingjiance' },
// component: Base,
// redirect: { name: 'EnvironmentMonitorIndex' },
// children: [
// {
// path: 'index',
// name: 'EnvironmentMonitorIndex',
// component: () => import('/@/view/monitor/environmentMonitor/index.vue'),
// meta: {
// title: '环境监测',
// keepAlive: true,
// // backApi: [],
// },
// },
// ],
// },
{
path: 'deviceMonitor',
name: 'DeviceMonitor',
meta: { title: '设备监测', hideChildren: true, icon: 'huanjingjiance' },
component: Base,
redirect: { name: 'DeviceMonitorIndex' },
children: [
{
path: 'index',
name: 'DeviceMonitorIndex',
component: () => import('/@/view/monitor/deviceMonitor/index.vue'),
meta: {
title: '设备监测',
keepAlive: true,
// backApi: [],
},
},
],
},
{
path: 'energyMonitor',
name: 'EnergyMonitor',
meta: { title: '能耗监测', hideChildren: true, icon: 'huanjingjiance' },
component: Base,
redirect: { name: 'EnergyMonitorIndex' },
children: [
{
path: 'index',
name: 'EnergyMonitorIndex',
component: () => import('/@/view/monitor/energyMonitor/index.vue'),
meta: {
title: '能耗监测',
keepAlive: true,
// backApi: [],
},
},
],
},
// {
// path: 'group',
// name: 'Group',
// meta: { title: '分组管理', hideChildren: true, icon: 'shebeiguanli' },
// component: Base,
// redirect: { name: 'GroupIndex' },
// children: [
// {
// path: 'index',
// name: 'GroupIndex',
// component: () => import('/@/view/monitor/group/index.vue'),
// meta: {
// title: '分组管理',
// keepAlive: true,
// // backApi: [],
// },
// },
// ],
// },
],
};
export default equipment;

View File

@@ -0,0 +1,101 @@
<template>
<div class="box">
<div class="box-top">
<div v-for="index in 3" :key="index" class="box-top-item">
<div class="item-box">
<div class="item-box-left">
<div class="item-box-left-title">设备告警 (今日处理 / 总数) </div>
<div class="iem-box-left-number">
10 / 13
<span
style="
color: #04d919;
font-size: 14px;
margin-left: 5px;
font-weight: 700;
font-style: normal;
"
>+10%</span
>
</div>
</div>
<div class="item-box-right">
<img width="54px" height="54px" src="../../../../src/icon/gaojingguanli.svg" />
</div>
</div>
</div>
</div>
<div style="flex: 4; width: 100%">2</div>
<div style="flex: 4; width: 100%">3</div>
</div>
</template>
<script lang="ts" setup>
import { ref, onMounted } from 'vue'; // 从 Vue 中导入 ref、onMounted 和 watchEffect
defineOptions({
name: 'alarmOverview', // 与页面路由name一致缓存才可生效
});
const info = ref({});
onMounted(() => {});
</script>
<style lang="less" scoped>
.box {
width: 100%;
height: 100%;
// border: 1px solid red;
display: flex;
flex-direction: column;
gap: 5px;
background-color: #f0f1f4;
box-sizing: border-box;
.box-top {
flex: 1;
width: 100%;
height: 100%;
display: flex;
flex-direction: row;
gap: 5px;
.box-top-item {
height: 100%;
flex: 1;
gap: 5px;
background-color: white;
border-radius: 4px;
padding: 20px;
.item-box {
width: 100%;
height: 100%;
display: flex;
.item-box-left {
flex: 9;
height: 100%;
padding: 0px !important;
.item-box-left-title {
color: rgba(0, 0, 0, 0.45);
font-weight: 400;
font-style: normal;
font-size: 14px;
color: #aaaaaa;
font-kerning: normal;
font-family: '微软雅黑', sans-serif;
}
.iem-box-left-number {
color: #000000;
font-weight: 700;
font-family: 'Arial Negreta', 'Arial Normal', 'Arial', sans-serif;
font-style: normal;
font-size: 30px;
}
}
.item-box-right {
flex: 1;
height: 100%;
display: grid;
place-items: center;
}
}
}
}
}
</style>

View File

@@ -0,0 +1,287 @@
<!-- 配置设备告警 -->
<template>
<ns-view-list-table v-if="show" ref="mainRef" class="table" v-bind="tableConfig">
<template #bodyCell="{ record, column }">
<template v-if="column.dataIndex === 'enableRules'">
<a-switch
:checked="record.enableRules === 1 ? true : false"
:class="{
'blue-background': record.enableRules === 1 ? true : false,
'grey-background': record.enableRules === 1 ? false : true,
}"
@click="clickSwitch({ enableRules: record.enableRules, record: record })" />
</template>
<template v-if="column.dataIndex === 'equipmentInfo'">
{{
record?.enableRules
? record.enableRules + '>' + record.deviceType + '>' + record.deviceName
: '-'
}}
</template>
</template>
</ns-view-list-table>
<!-- 新增or编辑界面 -->
<editConfigureEnergyAlarm ref="editConfigureEnergyAlarms" />
</template>
<script lang="ts">
import { ref, createVNode } from 'vue';
import { http } from '/nerv-lib/util';
import data from '../notificationManagementMock.json';
import { NsMessage, NsModal } from '/nerv-lib/component';
import editConfigureEnergyAlarm from './editConfigureEnergyAlarm.vue';
import { deviceAlarms } from '/@/api/alarmSettings/deviceAlarms';
import { ExclamationCircleOutlined } from '@ant-design/icons-vue';
export default {
components: { editConfigureEnergyAlarm },
setup() {
//设备告警数据
const configureDeviceAlarmsData = ref({});
const show = ref(false);
const tableConfig = ref({});
const mainRef = ref({});
const editConfigureEnergyAlarms = ref({});
const mockData = ref(data.listData);
const clickSwitch = (data: any) => {
NsModal.confirm({
title: '启用状态',
icon: createVNode(ExclamationCircleOutlined),
content: '确定' + (data.record.enableRules === 1 ? '关闭' : '启用') + '?',
onOk: () => {
http
.post(deviceAlarms.addOrUpNewData, {
id: data.record.id,
enableRules: data.record.enableRules === 1 ? 0 : 1,
})
.then(() => {
NsMessage.success('操作成功');
mainRef.value?.nsTableRef.reload();
});
},
});
};
const doWnload = (url: any) => {
const a = document.createElement('a');
document.body.appendChild(a);
a.href = encodeURI(url);
//设置下载的文件名
// a.download = fileName.value;
//触发a标签的点击事件进行下载
a.click();
};
const setconfigureDeviceAlarmsData = (value: any) => {
configureDeviceAlarmsData.value = value;
show.value = true;
tableConfig.value = {
title: '告警规则',
api: deviceAlarms.getTableList,
value: mockData.value,
headerActions: [
{
label: '新增',
name: 'RoleTypeAdd',
type: 'primary',
handle: () => {
editConfigureEnergyAlarms.value.toggle();
},
},
{
label: '导入',
name: 'groupImport',
type: 'primary',
extra: {
// api: props.postImportApi, // 导入接口名
title: '设备信息', // 弹窗title
templateName: 'whiteListUser', // 所使用的文件名称
indexName: '设备id', // 匹配类型字段
message: [
{ label: '1若必填项未填写则不能进行导入操作' },
{ label: `2、当重复时则更新数据。` },
{ label: '3数据将从模版的第五行进行导入' },
{ label: '4文件导入勿超过5MB' },
],
},
},
{
label: '导出',
name: 'groupExports',
type: 'primary',
handle: () => {
doWnload('/hx-ai-intelligent/asset/file/whiteListUser.xlsx');
},
},
{
label: '批量删除',
type: 'primary',
name: 'userBatchDel',
dynamicDisabled: (data: any) => {
return data.list.length === 0;
},
confirm: true,
isReload: true,
isClearCheck: true,
// api: origanizemanage.batchDel,
dynamicParams: { userIds: 'userId[]' },
},
],
columns: [
{
title: '序号',
dataIndex: 'address',
customRender: (text: any) => {
return text.index + 1;
},
},
{
title: '规则id',
dataIndex: 'ruleId',
},
{
title: '设备信息/节点信息',
dataIndex: 'equipmentInfo',
},
{
title: '对比类型',
dataIndex: 'valueType',
},
{
title: '告警点位',
dataIndex: 'devicePoint',
},
{
title: '判断条件',
dataIndex: 'ruleType',
},
{
title: '异常描述',
dataIndex: 'abnormalDescription',
textEllipsis: true,
},
{
title: '启用通知',
dataIndex: 'enableRules',
},
],
// rowSelection: null, 选择按钮
columnActions: {
title: '操作',
actions: [
{
label: '编辑',
name: 'FeedBackDetail',
dynamicParams: ['uuid', 'appealType'],
handle: (data: any) => {
editConfigureEnergyAlarms.value.toggle(data);
},
},
{
label: '删除',
name: 'FeedBackDetail',
dynamicParams: ['uuid', 'appealType'],
confirm: true,
handle: () => {
// mockData.value.splice(0, 1);
},
},
],
},
formConfig: {
title: value.errorCode,
schemas: [
{
field: 'provider',
label: '设备名称',
component: 'NsInput',
componentProps: {
placeholder: '请输入设备名称',
},
},
{
field: 'provider',
label: '设备点位',
component: 'nsSelectApi',
componentProps: {
api: '/api/community/objs/DictItem',
params: {
pageSize: 100,
code: 'MZ',
},
placeholder: '请选择设备点位',
resultField: 'data',
labelField: 'dictName',
valueField: 'dictValue',
immediate: true,
autoSelectFirst: false,
},
},
{
field: 'payWay',
label: '启用状态',
component: 'NsSelect',
componentProps: {
placeholder: '请选择启用状态',
options: [
{
label: '启用',
value: '1',
},
{
label: '关闭',
value: '0',
},
],
},
},
{
field: 'provider',
label: '异常描述',
component: 'NsInput',
componentProps: {
placeholder: '请输入异常描述关键字',
},
},
],
},
params: { id: value.id },
// pagination: { pageSizeOptions: false },
rowKey: 'id',
};
};
return {
configureDeviceAlarmsData,
show,
clickSwitch,
doWnload,
tableConfig,
editConfigureEnergyAlarms,
setconfigureDeviceAlarmsData,
};
},
};
</script>
<style lang="less" scoped>
.blue-background.ant-switch-checked {
background-color: linear-gradient(
180deg,
rgba(1, 206, 255, 1) 0%,
rgba(0, 150, 229, 1) 100%
) !important;
}
.grey-background.ant-switch {
background-color: grey !important;
}
.blue-background.ant-switch-checked .ant-switch-handle {
background-color: linear-gradient(
180deg,
rgba(1, 206, 255, 1) 0%,
rgba(0, 150, 229, 1) 100%
) !important;
}
.grey-background.ant-switch .ant-switch-handle {
background-color: grey !important;
}
</style>

View File

@@ -0,0 +1,421 @@
<template>
<ns-drawer
v-model:visible="visible"
width="520"
:title="infoObject?.id ? '编辑告警规则' : '新增告警规则'"
:footer-style="{ textAlign: 'right' }"
:ok="btnClick"
:cancel="handleClose"
placement="right">
<div style="padding: 18px; width: 100%; overflow: hidden">
<a-form ref="formRef" :model="infoObject" :rules="rules">
<a-form-item ref="site" label="站点" name="site">
<a-tree-select
v-model:value="infoObject.site"
show-search
style="width: 100%"
:dropdown-style="{ maxHeight: '400px', overflow: 'auto' }"
placeholder="请选择站点"
allow-clear
tree-default-expand-all
:tree-data="zdTreeData"
tree-node-filter-prop="label">
<!-- 特殊处理情况 -->
<!-- <template #title="{ value: val, label }">
<b v-if="val === 'parent 1-1'" style="color: #08c">sss</b>
<template v-else>{{ label }}</template>
</template> -->
</a-tree-select>
</a-form-item>
<a-form-item label="数据来源" name="sbtype">
<a-cascader
v-model:value="infoObject.sbtype"
:options="sbOptions"
:show-search="{ filter }"
placeholder="请选择数据来源" />
</a-form-item>
<a-form-item label="设备节点" name="jdDevice">
<a-tree-select
v-model:value="infoObject.jdDevice"
style="width: 100%"
placeholder="请选择设备节点"
:tree-line="true && { showLeafIcon: false }"
:tree-data="jdTreeData" />
</a-form-item>
<a-form-item label="启用规则" name="delivery">
<a-switch
v-model:checked="infoObject.delivery"
:class="{
'blue-background': infoObject.delivery,
'grey-background': !infoObject.delivery,
}" />
</a-form-item>
<a-form-item label="异常描述" name="desc">
<a-textarea
v-model:value="infoObject.desc"
style="height: 32px"
:autoSize="{ minRows: 1, maxRows: 1 }"
show-count
:maxlength="30" />
</a-form-item>
<a-form-item label="规则类型" name="resource">
<a-radio-group v-model:value="infoObject.resource">
<a-radio value="1"> (and) </a-radio>
<a-radio value="2"> (or) </a-radio>
</a-radio-group>
</a-form-item>
<a-form-item label="对比类型1" name="qzType">
<a-cascader
v-model:value="infoObject.sbtype"
:options="sbOptions"
placeholder="请选择数据来源" />
</a-form-item>
<template v-for="index in infoObject.alarmList?.length" :key="index">
<div style="width: 100%; display: flex; margin-left: 42px; padding: 12px">
<span style="line-height: 32px">{{ `逻辑${index}:` }}</span>
<a-select
v-model:value="infoObject.alarmList[index - 1].logic"
style="width: 70px; margin-left: 12px"
:options="ljOptions"
@change="handleQzChange" />
<span style="line-height: 32px; margin-left: 32px">{{ `数值${index}:` }}</span>
<a-input
style="width: 65px; margin-left: 6px"
type="number"
v-model:value="infoObject.alarmList[index - 1].num" />
<div
style="width: 70px; align-items: center; cursor: pointer"
@click="deleteAlarmList(index - 1)">
<img
style="width: 14px; margin: 0 12px"
src="https://files.axshare.com/gsc/4T0UQR/5a/e6/81/5ae6813d499c422383c7a15dd956523f/images/设备规则/u72.svg?pageId=cbce6e61-bc6a-4283-802d-993fce6151c0" />
</div>
</div>
<div
v-if="
infoObject.alarmList[index - 1].num === null ||
infoObject.alarmList[index - 1].logic === null
"
style="width: 100%; color: #ff4d4f; text-align: center; margin-bottom: 5px">
请选择正确的逻辑{{ index }} 或 输入正确的数值{{ index }}
</div>
</template>
<div
v-if="infoObject.alarmList?.length < 2"
style="width: 100%; color: #ff4d4f; text-align: center; margin-bottom: 5px">
逻辑至少2条
</div>
<div style="width: 100%; margin-top: 12px; display: flex; justify-content: flex-end">
<a-button type="primary" @click="addAlarmList"> 新增</a-button>
</div>
</a-form>
</div>
</ns-drawer>
</template>
<script lang="ts" setup>
import { NsMessage } from '/nerv-lib/component';
import { ref, toRaw } from 'vue';
import type { CascaderProps, TreeSelectProps, SelectProps } from 'ant-design-vue';
import type { ShowSearchType } from 'ant-design-vue/es/cascader';
import { device } from '/@/api/deviceManage';
import { http } from '/nerv-lib/util';
import type { Rule } from 'ant-design-vue/es/form';
const visible = ref(false);
//表单数据
const infoObject = ref({
id: null,
site: null,
jdDevice: null,
resource: null,
desc: null,
sbtype: null,
delivery: null,
alarmList: [{ logic: null, num: null }],
});
//删除的逻辑列表
const delAlarmList = ref([]);
const formRef = ref();
//站点数
const zdTreeData = ref<TreeSelectProps['treeData']>([
{
label: '铁路总局(T01)',
value: 'T01',
children: [
{
label: '济阳站(T0101)',
value: 'T0101',
},
],
},
]);
//设备类型树
const sbOptions: CascaderProps['options'] = [
{
value: '3',
label: '3.电梯',
children: [
{
value: '301',
label: '301.扶梯',
},
{
value: '302',
label: '301.直梯',
},
],
},
{
value: '4',
label: '4.冷热源',
children: [
{
value: '401',
label: '402.冷水机组',
},
{
value: '402',
label: '403.热泵机组',
},
{
value: '403',
label: '403.锅炉',
},
{
value: '404',
label: '404.水处理机组',
},
{
value: '405',
label: '405.板式热交换机组',
},
],
},
];
//节点数据
const jdTreeData = ref([
{
title: 'parent 1',
value: 'parent 1',
children: [
{
title: 'parent 1-0',
value: 'parent 1-0',
children: [
{
title: 'my leaf',
value: 'leaf1',
},
{
title: 'your leaf',
value: 'leaf2',
},
],
},
{
title: 'parent 1-1',
value: 'parent 1-1',
},
],
},
]);
//设备点位
const dwOptions = ref<SelectProps['options']>([
{ value: '电压 (U)', label: '电压 (U)', code: '1' },
{ value: '电流 (I)', label: '电流 (I)', code: '2' },
{ value: '电流 (I)', label: 'a相电流 (Ia)', code: '3' },
]);
const filter: ShowSearchType['filter'] = (inputValue: any, path: any) => {
return path.some(
(option: any) => option.label.toLowerCase().indexOf(inputValue.toLowerCase()) > -1,
);
};
//取值类型
const qzOptions = ref<SelectProps['options']>([
{ value: '实时值', label: '实时值', code: '1' },
{ value: '平均值', label: '平均值', code: '2' },
]);
//逻辑
const ljOptions = ref<SelectProps['options']>([
{ value: '1', label: '≥' },
{ value: '2', label: '>' },
{ value: '3', label: '≤' },
{ value: '4', label: '<' },
{ value: '5', label: '=' },
]);
//设备点位方法
const filterOption = (input: string, option: any) => {
console.log('搜索', option.value);
return option.value.toLowerCase().indexOf(input.toLowerCase()) >= 0;
};
const handleFocus = () => {
console.log('点击');
};
const handleChange = (value: string) => {
console.log(`selected ${value}`);
console.log('选择', infoObject.value.sbAdress);
//获得code
dwOptions.value.forEach((item) => {
if (item.value === value) {
console.log('符合', item.code);
}
});
};
const handleQzChange = (value: string) => {
//获得code
qzOptions.value.forEach((item) => {
if (item.value === value) {
console.log('符合', item.code);
}
});
};
const emit = defineEmits(['editObject']);
//父调子 页面显示方法
const toggle = (value: any) => {
//获取设备类型
http.post(device.queryDeviceTree, null).then((res) => {
console.log(res, '请求出来的数据');
NsMessage.success('操作成功');
});
//判断 是新增 还是修改
if (value) {
infoObject.value = value;
} else {
infoObject.value = {
id: null,
site: null,
jdDevice: null,
resource: null,
desc: null,
sbtype: null,
delivery: null,
alarmList: [{ logic: null, num: null }],
};
}
visible.value = !visible.value;
};
//表单 判断规格
const rules: Record<string, Rule[]> = {
site: [{ required: true, message: '请选择站点', trigger: 'change' }],
sbtype: [{ required: true, message: '请选择设备类型', trigger: 'change' }],
delivery: [{ required: true, message: '请选择启用规则', trigger: 'change' }],
jdDevice: [{ required: true, message: '请选择设备名称', trigger: 'change' }],
sbAdress: [{ required: true, message: '请选择设备点位', trigger: 'change' }],
qzType: [{ required: true, message: '请选择取值类型', trigger: 'change' }],
resource: [{ required: true, message: '请选择规则类型', trigger: 'change' }],
desc: [{ required: true, message: '请输入异常描述', trigger: 'blur' }],
alarm: [{ required: true, message: '请选择逻辑', trigger: 'blur' }],
number: [{ required: true, message: '请输入数值', trigger: 'blur' }],
};
// 开关选择
const changeSwitch = () => {
console.log(infoObject.value.selectSwitch, '开关');
};
// 确认按钮
const btnClick = () => {
console.log(infoObject.value, '数据');
infoObject.value.alarmList.forEach((item) => {
if (item.logic === null || item.num === null) {
// NsMessage.error('请选择逻辑和数值');
return;
}
});
if (infoObject.value.alarmList.length < 2) {
NsMessage.error('请选择逻辑和数值');
return;
}
//数据是否验证通过
formRef.value.validate().then(() => {
console.log('values', infoObject, toRaw(infoObject));
delAlarmList.value = [];
});
//调用接口
// http
// .post(props.api, data)
// .then(() => {
// isLoading.value = false;
// NsMessage.success('操作成功', 1, () => {
// navigateBack();
// });
// })
// .catch(() => {
// isLoading.value = false;
// });
};
//取消按钮
const handleClose = () => {
// 清楚校验错误信息
formRef.value.resetFields();
//对象清空
infoObject.value = {
id: null,
site: null,
resource: null,
desc: null,
sbtype: null,
delivery: null,
alarmList: [{ alarm: null, number: null }],
};
visible.value = false;
delAlarmList.value = [];
};
// 新增逻辑列表
const addAlarmList = () => {
if (infoObject.value.alarmList) {
infoObject.value.alarmList.push({ logic: null, num: null });
} else {
infoObject.value.alarmList = [{ logic: null, num: null }];
}
};
// 删除 逻辑列表、
const deleteAlarmList = (index: number) => {
if (infoObject.value.alarmList[index]?.id) {
delAlarmList.value.push(infoObject.value.alarmList[index]);
}
infoObject.value.alarmList.splice(index, 1);
};
defineExpose({
toggle,
handleClose,
dwOptions,
formRef,
});
</script>
<style scoped lang="less">
.drawerContainer {
height: 100%;
display: flex;
justify-content: space-between;
}
.blue-background.ant-switch-checked {
background-color: linear-gradient(
180deg,
rgba(1, 206, 255, 1) 0%,
rgba(0, 150, 229, 1) 100%
) !important;
}
.grey-background.ant-switch {
background-color: grey !important;
}
.blue-background.ant-switch-checked .ant-switch-handle {
background-color: linear-gradient(
180deg,
rgba(1, 206, 255, 1) 0%,
rgba(0, 150, 229, 1) 100%
) !important;
}
.grey-background.ant-switch .ant-switch-handle {
background-color: grey !important;
}
/deep/ .ant-form-item-label {
z-index: 20;
text-align: right;
width: 23%;
}
</style>

View File

@@ -0,0 +1,235 @@
<template>
<ns-drawer
v-model:visible="visible"
width="520"
:title="infoObject?.id ? '修改能源告警' : '新增能源告警'"
:footer-style="{ textAlign: 'right' }"
:ok="btnClick"
:cancel="handleClose"
placement="right">
<ns-form ref="formRef" :schemas="schemas" :model="infoObject" formLayout="vertical" />
<div style="margin-left: 52px">
应用规则:
<a-switch
:checked="infoObject?.enableRules === 1 ? true : false"
:class="{
'blue-background': infoObject?.enableRules === 1 ? true : false,
'grey-background': infoObject?.enableRules === 1 ? false : true,
}"
style="margin-left: 6px"
@change="changeSwitch" />
</div>
</ns-drawer>
</template>
<script lang="ts" setup>
import { ref } from 'vue';
import { NsMessage } from '/nerv-lib/component';
import { http } from '/nerv-lib/util';
// import { deviceAlarms } from '/@/api/alarmSettings/deviceAlarms';
const visible = ref(false);
//表单数据
const infoObject = ref({
id: null,
accountNo: null,
enableRules: 0,
});
const formRef = ref();
const emit = defineEmits(['editObject']);
const toggle = (value: any) => {
//判断 是新增 还是修改
if (value) {
infoObject.value = value;
} else {
infoObject.value = {
id: null,
accountNo: null,
enableRules: 0,
};
}
visible.value = !visible.value;
};
const schemas = [
{
field: 'basicInfo',
label: '',
displayFormItem: false,
class: 'ns-form-item-full',
component: 'NsChildForm',
componentProps: {
schemas: [
{
field: 'alarmTitle',
label: '告警标题',
component: 'NsInput',
rules: [
{
required: true,
message: '告警标题不能为空',
trigger: 'change',
},
],
componentProps: {
placeholder: '请输入告警标题',
maxLength: 20,
},
},
{
field: 'repetitions',
label: '重复次数',
rules: [
{
required: true,
message: '重复次数不能为空',
trigger: 'change',
},
],
component: 'NsSelect',
componentProps: {
allowClear: true,
placeholder: '请选择重复次数',
options: [
{
label: '单次',
value: 1,
},
{
label: '重复',
value: 2,
},
{
label: '累计',
value: 3,
},
],
},
},
{
field: 'priority',
label: '优先级',
component: 'NsSelect',
rules: [
{
required: true,
message: '优先级不能为空',
trigger: 'change',
},
],
componentProps: {
allowClear: true,
placeholder: '请选择优先级',
options: [
{
label: '紧急',
value: 1,
},
{
label: '重要',
value: 2,
},
{
label: '一般',
value: 3,
},
],
},
},
{
field: 'priority',
label: '监测频率',
component: 'NsSelect',
rules: [
{
required: true,
message: '监测频率不能为空',
trigger: 'change',
},
],
componentProps: {
allowClear: true,
placeholder: '请选择监测频率',
componentProps: {
api: '/api/community/objs/DictItem',
params: {
pageSize: 100,
code: 'MZ',
},
placeholder: '请选择设备点位',
resultField: 'data',
labelField: 'dictName',
valueField: 'dictValue',
immediate: true,
autoSelectFirst: false,
},
},
},
],
},
},
];
//开关
const changeSwitch = () => {
switch (infoObject.value.enableRules) {
case 1:
infoObject.value.enableRules = 0;
break;
case 0:
infoObject.value.enableRules = 1;
break;
}
};
const btnClick = () => {
//表单校验
formRef.value.triggerSubmit().then(() => {
//调用接口
// http.post(deviceAlarms.addOrUpNewData, infoObject.value).then(() => {
// NsMessage.success('操作成功');
// visible.value = false;
// emit('editObject', null);
// });
});
};
const handleClose = () => {
// 清楚校验错误信息
formRef.value.formElRef.clearValidate();
console.log(infoObject.value);
visible.value = false;
NsMessage.success('操作成功');
};
defineExpose({
toggle,
handleClose,
formRef,
});
</script>
<style scoped lang="less">
.drawerContainer {
height: 100%;
display: flex;
justify-content: space-between;
}
.blue-background.ant-switch-checked {
background-color: linear-gradient(
180deg,
rgba(1, 206, 255, 1) 0%,
rgba(0, 150, 229, 1) 100%
) !important;
}
.grey-background.ant-switch {
background-color: grey !important;
}
.blue-background.ant-switch-checked .ant-switch-handle {
background-color: linear-gradient(
180deg,
rgba(1, 206, 255, 1) 0%,
rgba(0, 150, 229, 1) 100%
) !important;
}
.grey-background.ant-switch .ant-switch-handle {
background-color: grey !important;
}
</style>

View File

@@ -0,0 +1,288 @@
<!-- 配置设备告警 -->
<template>
<ns-view-list-table v-if="show" ref="mainRef" class="table" v-bind="tableConfig">
<template #bodyCell="{ record, column }">
<template v-if="column.dataIndex === 'enableRules'">
<a-switch
:checked="record.enableRules === 1 ? true : false"
:class="{
'blue-background': record.enableRules === 1 ? true : false,
'grey-background': record.enableRules === 1 ? false : true,
}"
@click="clickSwitch({ enableRules: record.enableRules, record: record })" />
</template>
<template v-if="column.dataIndex === 'equipmentInfo'">
{{
record?.enableRules
? record.enableRules + '>' + record.deviceType + '>' + record.deviceName
: '-'
}}
</template>
</template>
</ns-view-list-table>
<!-- 新增or编辑界面 -->
<editConfigureDeviceAlarm ref="editConfigureDeviceAlarms" />
</template>
<script lang="ts">
import { ref, createVNode } from 'vue';
import { http } from '/nerv-lib/util';
import data from '../notificationManagementMock.json';
import { NsMessage, NsModal } from '/nerv-lib/component';
import editConfigureDeviceAlarm from '../equipmentAlarm/editConfigureDeviceAlarm.vue';
import { deviceAlarms } from '/@/api/alarmSettings/deviceAlarms';
import { ExclamationCircleOutlined } from '@ant-design/icons-vue';
export default {
components: { editConfigureDeviceAlarm },
setup() {
//设备告警数据
const configureDeviceAlarmsData = ref({});
const show = ref(false);
const tableConfig = ref({});
const mainRef = ref({});
const editConfigureDeviceAlarms = ref({});
const mockData = ref(data.listData);
const clickSwitch = (data: any) => {
NsModal.confirm({
title: '启用状态',
icon: createVNode(ExclamationCircleOutlined),
content: '确定' + (data.record.enableRules === 1 ? '关闭' : '启用') + '?',
onOk: () => {
http
.post(deviceAlarms.addOrUpNewData, {
id: data.record.id,
enableRules: data.record.enableRules === 1 ? 0 : 1,
})
.then(() => {
NsMessage.success('操作成功');
mainRef.value?.nsTableRef.reload();
});
},
});
};
const doWnload = (url: any) => {
const a = document.createElement('a');
document.body.appendChild(a);
a.href = encodeURI(url);
//设置下载的文件名
// a.download = fileName.value;
//触发a标签的点击事件进行下载
a.click();
};
const setconfigureDeviceAlarmsData = (value: any) => {
configureDeviceAlarmsData.value = value;
show.value = true;
tableConfig.value = {
title: '告警规则',
api: deviceAlarms.getTableList,
value: mockData.value,
headerActions: [
{
label: '新增',
name: 'RoleTypeAdd',
type: 'primary',
handle: () => {
editConfigureDeviceAlarms.value.toggle();
},
},
{
label: '导入',
name: 'groupImport',
type: 'primary',
extra: {
// api: props.postImportApi, // 导入接口名
title: '设备信息', // 弹窗title
templateName: 'whiteListUser', // 所使用的文件名称
indexName: '设备id', // 匹配类型字段
message: [
{ label: '1若必填项未填写则不能进行导入操作' },
{ label: `2、当重复时则更新数据。` },
{ label: '3数据将从模版的第五行进行导入' },
{ label: '4文件导入勿超过5MB' },
],
},
},
{
label: '导出',
name: 'groupExports',
type: 'primary',
handle: () => {
doWnload('/hx-ai-intelligent/asset/file/whiteListUser.xlsx');
},
},
{
label: '批量删除',
type: 'primary',
name: 'userBatchDel',
dynamicDisabled: (data: any) => {
return data.list.length === 0;
},
confirm: true,
isReload: true,
isClearCheck: true,
// api: origanizemanage.batchDel,
dynamicParams: { userIds: 'userId[]' },
},
],
columns: [
{
title: '序号',
dataIndex: 'address',
customRender: (text: any) => {
return text.index + 1;
},
},
{
title: '规则id',
dataIndex: 'ruleId',
},
{
title: '设备信息',
dataIndex: 'equipmentInfo',
},
{
title: '告警点位',
dataIndex: 'devicePoint',
},
{
title: '判断条件',
dataIndex: 'ruleType',
},
{
title: '取值类型',
dataIndex: 'valueType',
textEllipsis: true,
},
{
title: '异常描述',
dataIndex: 'abnormalDescription',
},
{
title: '启用通知',
dataIndex: 'enableRules',
},
],
// rowSelection: null, 选择按钮
columnActions: {
title: '操作',
actions: [
{
label: '编辑',
name: 'FeedBackDetail',
dynamicParams: ['uuid', 'appealType'],
handle: (data: any) => {
editConfigureDeviceAlarms.value.toggle(data);
},
},
{
label: '删除',
name: 'FeedBackDetail',
dynamicParams: ['uuid', 'appealType'],
confirm: true,
handle: () => {
// mockData.value.splice(0, 1);
console.log(data, 'xxxxxxx');
},
},
],
},
formConfig: {
title: value.errorCode,
schemas: [
{
field: 'provider',
label: '设备名称',
component: 'NsInput',
componentProps: {
placeholder: '请输入设备名称',
},
},
{
field: 'provider',
label: '设备点位',
component: 'nsSelectApi',
componentProps: {
api: '/api/community/objs/DictItem',
params: {
pageSize: 100,
code: 'MZ',
},
placeholder: '请选择设备点位',
resultField: 'data',
labelField: 'dictName',
valueField: 'dictValue',
immediate: true,
autoSelectFirst: false,
},
},
{
field: 'payWay',
label: '启用状态',
component: 'NsSelect',
componentProps: {
placeholder: '请选择启用状态',
options: [
{
label: '启用',
value: '1',
},
{
label: '关闭',
value: '0',
},
],
},
},
{
field: 'provider',
label: '异常描述',
component: 'NsInput',
componentProps: {
placeholder: '请输入异常描述关键字',
},
},
],
},
params: { id: value.id },
// pagination: { pageSizeOptions: false },
rowKey: 'id',
};
};
return {
configureDeviceAlarmsData,
show,
clickSwitch,
doWnload,
tableConfig,
editConfigureDeviceAlarms,
setconfigureDeviceAlarmsData,
};
},
};
</script>
<style lang="less" scoped>
.blue-background.ant-switch-checked {
background-color: linear-gradient(
180deg,
rgba(1, 206, 255, 1) 0%,
rgba(0, 150, 229, 1) 100%
) !important;
}
.grey-background.ant-switch {
background-color: grey !important;
}
.blue-background.ant-switch-checked .ant-switch-handle {
background-color: linear-gradient(
180deg,
rgba(1, 206, 255, 1) 0%,
rgba(0, 150, 229, 1) 100%
) !important;
}
.grey-background.ant-switch .ant-switch-handle {
background-color: grey !important;
}
</style>

View File

@@ -0,0 +1,489 @@
<template>
<ns-drawer
v-model:visible="visible"
width="520"
:title="infoObject?.id ? '编辑告警规则' : '新增告警规则'"
:footer-style="{ textAlign: 'right' }"
:ok="btnClick"
:cancel="handleClose"
placement="right">
<div style="padding: 18px; width: 100%; overflow: hidden">
<a-form ref="formRef" :model="infoObject" :rules="rules">
<a-form-item ref="site" label="站点" name="site">
<a-tree-select
v-model:value="infoObject.site"
show-search
style="width: 100%"
:dropdown-style="{ maxHeight: '400px', overflow: 'auto' }"
placeholder="请选择站点"
allow-clear
tree-default-expand-all
:tree-data="zdTreeData"
tree-node-filter-prop="label" />
</a-form-item>
<a-form-item label="设备类型" name="deviceType">
<a-cascader
v-model:value="infoObject.deviceType"
:options="deviceTypeTreeData"
:field-names="{
children: 'children',
label: 'deviceType',
value: 'id',
}"
:show-search="{ filter }"
@change="selectDeviceType"
placeholder="请选择设备类型" />
</a-form-item>
<a-form-item label="设备名称" name="deviceName">
<a-select
v-model:value="infoObject.deviceName"
:disabled="!(infoObject && infoObject.deviceType && infoObject.deviceType.length > 0)"
style="width: 100%"
:autoClearSearchValue="true"
@change="selectDevice"
placeholder="请选择设备名称">
<template v-for="(item, index) in deviceNameTreeData" :key="index">
<a-select-option :value="item.id">
{{ item.deviceName }}
</a-select-option>
</template>
</a-select>
</a-form-item>
<a-form-item label="设备点位" name="devicePoint">
<a-select
v-model:value="infoObject.devicePoint"
show-search
placeholder="请选择设备点位"
:disabled="!infoObject?.deviceName"
style="width: 100%"
:options="dwOptions"
:filter-option="filterOption"
@focus="handleFocus"
@change="handleChange" />
</a-form-item>
<a-form-item label="启用规则" name="enableRules">
<a-switch
:checked="infoObject.enableRules === 1 ? true : false"
:class="{
'blue-background': infoObject.enableRules === 1 ? true : false,
'grey-background': infoObject.enableRules === 1 ? false : true,
}"
@click="clickSwitch" />
</a-form-item>
<a-form-item label="取值类型" name="valueType">
<a-select
v-model:value="infoObject.valueType"
show-search
placeholder="请选择取值类型"
style="width: 100%"
:options="qzOptions"
:filter-option="filterOption"
@change="handleQzChange" />
</a-form-item>
<a-form-item label="异常描述" name="abnormalDescription">
<a-textarea
v-model:value="infoObject.abnormalDescription"
style="height: 32px"
:autoSize="{ minRows: 1, maxRows: 1 }"
show-count
:maxlength="30" />
</a-form-item>
<a-form-item label="规则类型" name="ruleType">
<a-radio-group v-model:value="infoObject.ruleType">
<a-radio value="1"> (and) </a-radio>
<a-radio value="2"> (or) </a-radio>
</a-radio-group>
</a-form-item>
<template v-for="index in infoObject.alarmList?.length" :key="index">
<div
style="
width: 100%;
display: flex;
margin-left: 42px;
padding: 12px;
border-color: #ff4d4f !important;
">
<span style="line-height: 32px">{{ `逻辑${index}:` }}</span>
<a-select
v-model:value="infoObject.alarmList[index - 1].logic"
style="width: 70px; margin-left: 12px"
:options="ljOptions"
@change="handleQzChange" />
<span style="line-height: 32px; margin-left: 32px">{{ `数值${index}:` }}</span>
<a-input
style="width: 65px; margin-left: 6px"
type="number"
v-model:value="infoObject.alarmList[index - 1].num" />
<div
style="width: 70px; align-items: center; cursor: pointer"
@click="deleteAlarmList(index - 1)">
<img
style="width: 14px; margin: 0 12px"
src="https://files.axshare.com/gsc/4T0UQR/5a/e6/81/5ae6813d499c422383c7a15dd956523f/images/设备规则/u72.svg?pageId=cbce6e61-bc6a-4283-802d-993fce6151c0" />
</div>
</div>
<div
v-if="
infoObject.alarmList[index - 1].num === null ||
infoObject.alarmList[index - 1].logic === null
"
style="width: 100%; color: #ff4d4f; text-align: center; margin-bottom: 5px">
请选择正确的逻辑{{ index }} 或 输入正确的数值{{ index }}
</div>
</template>
<div
v-if="infoObject?.alarmList?.length < 2"
style="width: 100%; color: #ff4d4f; text-align: center; margin-bottom: 5px">
逻辑至少2条
</div>
<div style="width: 100%; margin-top: 12px; display: flex; justify-content: flex-end">
<a-button type="primary" @click="addAlarmList"> 新增</a-button>
</div>
</a-form>
</div>
</ns-drawer>
</template>
<script lang="ts" setup>
import { NsMessage } from '/nerv-lib/component';
import { ref, toRaw, watch } from 'vue';
import type { CascaderProps, TreeSelectProps, SelectProps } from 'ant-design-vue';
import type { ShowSearchType } from 'ant-design-vue/es/cascader';
import { device } from '/@/api/deviceManage';
import { http } from '/nerv-lib/util';
import type { Rule } from 'ant-design-vue/es/form';
import { async } from '@antv/x6/lib/registry/marker/async';
const visible = ref(false);
//表单数据
const infoObject = ref({
id: null,
site: null,
ruleType: null,
abnormalDescription: null,
deviceType: [],
devicePoint: null,
valueType: null,
deviceName: null,
enableRules: 0,
alarmList: [{ id: null, alarm: null, number: null, isDelete: '0' }],
});
//删除的逻辑列表
const delAlarmList = ref([]);
const formRef = ref();
//组织数
const orgId = ref('');
const result = JSON.parse(sessionStorage.getItem('ORGID')!);
orgId.value = result;
//站点数
const zdTreeData = ref<TreeSelectProps['treeData']>([
{
label: '铁路总局(T01)',
value: 'T01',
children: [
{
label: '济阳站(T0101)',
value: 'T0101',
},
],
},
]);
//设备类型树
let deviceTypeTreeData = ref([]);
//设备树
let deviceNameTreeData = ref([]);
//选择设备类型方法
const selectDeviceType = (value: any, selectedOptions: any) => {
//获取该类型设备
getDevicePage({
orgId: orgId.value,
code: selectedOptions[selectedOptions.length - 1].code,
pageNum: 1,
pageSize: 999,
});
};
//选择设备方法
const selectDevice = (value: string) => {
console.log(`selected ${value}`);
};
//获取设备列表
const getDevicePage = (value: any) => {
http.post(device.queryDevicePage, value).then((res) => {
if (res.msg === 'success') {
deviceNameTreeData.value = res.data.records;
}
});
};
//获取设备点位
const getDevicePoint = (value: any) => {
http.post(device.queryDevicePoint, value).then((res) => {
if (res.msg === 'success') {
console.log(res, '数据');
}
});
};
//设备点位
const dwOptions = ref<SelectProps['options']>([
{ value: '电压 (U)', label: '电压 (U)', code: '1' },
{ value: '电流 (I)', label: '电流 (I)', code: '2' },
{ value: '电流 (I)', label: 'a相电流 (Ia)', code: '3' },
]);
const filter: ShowSearchType['filter'] = (inputValue: any, path: any) => {
return path.some(
(option: any) => option.label.toLowerCase().indexOf(inputValue.toLowerCase()) > -1,
);
};
//取值类型
const qzOptions = ref<SelectProps['options']>([
{ value: '实时值', label: '实时值', code: '1' },
{ value: '平均值', label: '平均值', code: '2' },
]);
//逻辑
const ljOptions = ref<SelectProps['options']>([
{ value: '1', label: '≥' },
{ value: '2', label: '>' },
{ value: '3', label: '≤' },
{ value: '4', label: '<' },
{ value: '5', label: '=' },
]);
//设备点位方法
const filterOption = (input: string, option: any) => {
console.log('搜索', option.value);
return option.value.toLowerCase().indexOf(input.toLowerCase()) >= 0;
};
const handleFocus = () => {
console.log('点击');
};
const handleChange = (value: string) => {
console.log(`selected ${value}`);
console.log('选择', infoObject.value.devicePoint);
//获得code
dwOptions.value.forEach((item) => {
if (item.value === value) {
console.log('符合', item.code);
}
});
};
const handleQzChange = (value: string) => {
//获得code
qzOptions.value.forEach((item) => {
if (item.value === value) {
console.log('符合', item.code);
}
});
};
//开关
const clickSwitch = () => {
if (infoObject.value.enableRules === 1) {
infoObject.value.enableRules = 0;
} else {
infoObject.value.enableRules = 1;
}
};
const emit = defineEmits(['editObject']);
// 定义一个递归函数来查找每一级的id 设备类型回显 层级方法
function findParentIds(tree: any, targetId: number, result: any) {
for (let item of tree) {
if (item.children && item.children.length > 0) {
if (item.children.some((child: any) => child.id === targetId)) {
result.unshift(item.id); // 将当前节点的id添加到结果数组的最前面
findParentIds(tree, item.id, result); // 递归查找父级节点的id
break; // 找到后可以退出循环
}
}
}
}
// 递归 获取当前 选择的设备类型 对象 用来获取设备列表
const findNodeById = (nodes: any, id: any) => {
for (let node of nodes) {
if (node.id === id) {
//获取设备树
getDevicePage({ orgId: orgId.value, code: node.code, pageNum: 1, pageSize: 99 });
return;
} else if (node.children.length > 0) {
const found = findNodeById(node.children, id);
if (found) {
//获取设备树
getDevicePage({ orgId: orgId.value, code: found.code, pageNum: 1, pageSize: 99 });
return;
}
}
}
return null; // 没有找到目标节点,返回 null
};
//父调子 页面显示方法
const toggle = async (value: any) => {
//获取设备类型
await http
.post(device.queryDeviceTree, { orgId: orgId.value, pageNum: 1, pageSize: 10 })
.then((res) => {
if (res.msg === 'success') {
deviceTypeTreeData.value = res.data;
}
});
//判断 是新增 还是修改
if (value) {
infoObject.value = value;
// 获取 选择的设备类型对象
let selectDevice = ref([value.deviceType]);
findNodeById(deviceTypeTreeData.value, value.deviceType);
//获取设备点位
getDevicePoint({ id: 1 });
// 修改时
findParentIds(deviceTypeTreeData.value, value.deviceType, selectDevice.value);
infoObject.value.deviceType = selectDevice.value;
} else {
//获取设备树
infoObject.value = {
id: null,
site: null,
ruleType: null,
abnormalDescription: null,
deviceType: [],
devicePoint: null,
valueType: null,
deviceName: null,
enableRules: 0,
alarmList: [{ id: null, alarm: null, number: null, isDelete: '0' }],
};
}
visible.value = !visible.value;
};
//表单 判断规格
const rules: infoObject<string, Rule[]> = {
site: [{ required: true, message: '请选择站点', trigger: 'change' }],
deviceType: [{ required: true, message: '请选择设备类型', trigger: 'change' }],
enableRules: [{ required: true, message: '请选择启用规则', trigger: 'change' }],
deviceName: [{ required: true, message: '请选择设备名称', trigger: 'change' }],
devicePoint: [{ required: true, message: '请选择设备点位', trigger: 'change' }],
valueType: [{ required: true, message: '请选择取值类型', trigger: 'change' }],
ruleType: [{ required: true, message: '请选择规则类型', trigger: 'change' }],
abnormalDescription: [{ required: true, message: '请输入异常描述', trigger: 'blur' }],
alarm: [{ required: true, message: '请选择逻辑', trigger: 'blur' }],
number: [{ required: true, message: '请输入数值', trigger: 'blur' }],
};
// 开关选择
const changeSwitch = () => {
console.log(infoObject.value.selectSwitch, '开关');
};
// 确认按钮
const btnClick = () => {
console.log(infoObject.value, '数据');
infoObject.value.alarmList.forEach((item) => {
if (item.logic === null || item.num === null) {
// NsMessage.error('请选择逻辑和数值');
return;
}
});
if (infoObject.value.alarmList.length < 2) {
NsMessage.error('请选择逻辑和数值');
return;
}
//数据是否验证通过
formRef.value.validate().then(() => {
console.log('values', infoObject.value);
let data = toRaw(infoObject);
//逻辑列表
data.alarmList = [...infoObject.value.alarmList, ...delAlarmList.value];
//设备类型 只取最后一级的id
data.deviceType = infoObject.value.deviceType[infoObject.value.deviceType.length - 1];
delAlarmList.value = [];
});
//调用接口
// http
// .post(props.api, data)
// .then(() => {
// isLoading.value = false;
// NsMessage.success('操作成功', 1, () => {
// navigateBack();
// });
// })
// .catch(() => {
// isLoading.value = false;
// });
};
//取消按钮
const handleClose = () => {
// 清楚校验错误信息
formRef.value.resetFields();
//对象清空
infoObject.value = {
id: null,
site: null,
ruleType: null,
abnormalDescription: null,
deviceType: [],
devicePoint: null,
enableRules: 0,
alarmList: [{ id: null, alarm: null, number: null, isDelete: '0' }],
};
visible.value = false;
delAlarmList.value = [];
};
// 新增逻辑列表
const addAlarmList = () => {
if (infoObject.value.alarmList) {
infoObject.value.alarmList.push({ logic: null, num: null });
} else {
infoObject.value.alarmList = [{ logic: null, num: null }];
}
};
// 删除 逻辑列表、
const deleteAlarmList = (index: number) => {
// 确保 index 在有效范围内
if (index < infoObject.value.alarmList.length && index >= 0) {
const alarmItemToDelete = infoObject.value.alarmList[index];
if (alarmItemToDelete?.id) {
// 添加到 delAlarmList 中,并标记为已删除
delAlarmList.value.push({ ...alarmItemToDelete, isDelete: '1' });
}
// 从 infoObject 中删除该元素
infoObject.value.alarmList.splice(index, 1);
} else {
console.error('Invalid index: ', index);
}
};
defineExpose({
toggle,
handleClose,
dwOptions,
formRef,
});
</script>
<style scoped lang="less">
.drawerContainer {
height: 100%;
display: flex;
justify-content: space-between;
}
.blue-background.ant-switch-checked {
background-color: linear-gradient(
180deg,
rgba(1, 206, 255, 1) 0%,
rgba(0, 150, 229, 1) 100%
) !important;
}
.grey-background.ant-switch {
background-color: grey !important;
}
.blue-background.ant-switch-checked .ant-switch-handle {
background-color: linear-gradient(
180deg,
rgba(1, 206, 255, 1) 0%,
rgba(0, 150, 229, 1) 100%
) !important;
}
.grey-background.ant-switch .ant-switch-handle {
background-color: grey !important;
}
/deep/ .ant-form-item-label {
z-index: 20;
text-align: right;
width: 23%;
}
</style>

View File

@@ -0,0 +1,258 @@
<template>
<ns-drawer
v-model:visible="visible"
width="520"
:title="infoObject?.id ? '修改设备告警' : '新增设备告警'"
:footer-style="{ textAlign: 'right' }"
:ok="btnClick"
:cancel="handleClose"
placement="right">
<ns-form ref="formRef" :schemas="schemas" :model="infoObject" formLayout="vertical" />
<div style="margin-left: 52px">
应用规则:
<a-switch
:checked="infoObject?.enableRules === 1 ? true : false"
:class="{
'blue-background': infoObject?.enableRules === 1 ? true : false,
'grey-background': infoObject?.enableRules === 1 ? false : true,
}"
style="margin-left: 6px"
@change="changeSwitch" />
</div>
</ns-drawer>
</template>
<script lang="ts" setup>
import { ref } from 'vue';
import { NsMessage } from '/nerv-lib/component';
import { http } from '/nerv-lib/util';
import { deviceAlarms } from '/@/api/alarmSettings/deviceAlarms';
const visible = ref(false);
//表单数据
const infoObject = ref({
id: null,
accountNo: null,
enableRules: 0,
});
const formRef = ref();
const emit = defineEmits(['editObject']);
const toggle = (value: any) => {
//判断 是新增 还是修改
if (value) {
infoObject.value = value;
} else {
infoObject.value = {
id: null,
accountNo: null,
enableRules: 0,
};
}
visible.value = !visible.value;
};
const schemas = [
{
field: 'basicInfo',
label: '',
displayFormItem: false,
class: 'ns-form-item-full',
component: 'NsChildForm',
componentProps: {
schemas: [
{
field: 'alarmTitle',
label: '告警标题',
component: 'NsInput',
rules: [
{
required: true,
message: '告警标题不能为空',
trigger: 'change',
},
],
componentProps: {
placeholder: '请输入告警标题',
maxLength: 20,
},
},
{
field: 'repetitions',
label: '重复次数',
rules: [
{
required: true,
message: '重复次数不能为空',
trigger: 'change',
},
],
component: 'NsSelect',
componentProps: {
allowClear: true,
placeholder: '请选择重复次数',
options: [
{
label: '单次',
value: 1,
},
{
label: '重复',
value: 2,
},
{
label: '累计',
value: 3,
},
],
},
},
{
field: 'monitorTime',
label: '检测时长',
component: 'NsInputNumber',
rules: [
{
required: true,
validator: (rules: any, value: any, cbfn: any) => {
if (value && /^[0-9]*$/.test(value)) {
cbfn();
} else {
cbfn('请输入正确的监测时长');
}
},
trigger: 'change',
},
],
componentProps: {
placeholder: '请输入监测时长',
// maxLength: 30,
},
},
{
field: 'monitorTimeUnit',
label: '监测时长单位',
component: 'NsSelect',
rules: [
{
required: true,
message: '监测时长单位不能为空',
trigger: 'change',
},
],
componentProps: {
allowClear: true,
placeholder: '请选择监测时长单位',
options: [
{
label: '分',
value: 1,
},
{
label: '时',
value: 2,
},
{
label: '天',
value: 3,
},
],
},
},
{
field: 'priority',
label: '优先级',
component: 'NsSelect',
rules: [
{
required: true,
message: '优先级不能为空',
trigger: 'change',
},
],
componentProps: {
allowClear: true,
placeholder: '请选择优先级',
options: [
{
label: '紧急',
value: 1,
},
{
label: '重要',
value: 2,
},
{
label: '一般',
value: 3,
},
],
},
},
],
},
},
];
//开关
const changeSwitch = () => {
switch (infoObject.value.enableRules) {
case 1:
infoObject.value.enableRules = 0;
break;
case 0:
infoObject.value.enableRules = 1;
break;
}
};
const btnClick = () => {
//表单校验
formRef.value.triggerSubmit().then(() => {
//调用接口
http.post(deviceAlarms.addOrUpNewData, infoObject.value).then(() => {
NsMessage.success('操作成功');
visible.value = false;
emit('editObject', null);
});
});
};
const handleClose = () => {
// 清楚校验错误信息
formRef.value.formElRef.clearValidate();
console.log(infoObject.value);
visible.value = false;
NsMessage.success('操作成功');
};
defineExpose({
toggle,
handleClose,
formRef,
});
</script>
<style scoped lang="less">
.drawerContainer {
height: 100%;
display: flex;
justify-content: space-between;
}
.blue-background.ant-switch-checked {
background-color: linear-gradient(
180deg,
rgba(1, 206, 255, 1) 0%,
rgba(0, 150, 229, 1) 100%
) !important;
}
.grey-background.ant-switch {
background-color: grey !important;
}
.blue-background.ant-switch-checked .ant-switch-handle {
background-color: linear-gradient(
180deg,
rgba(1, 206, 255, 1) 0%,
rgba(0, 150, 229, 1) 100%
) !important;
}
.grey-background.ant-switch .ant-switch-handle {
background-color: grey !important;
}
</style>

View File

@@ -0,0 +1,229 @@
<template>
<div class="box">
<a-tabs default-active-key="1" @change="callback">
<a-tab-pane key="1" tab="通知管理">
<ns-view-list-table v-bind="notificationConfig" ref="mainRef">
<template #bodyCell="{ record, column }">
<template v-if="column.dataIndex === 'enableRules'">
<a-switch
:checked="record.enableRules === 1 ? true : false"
:class="{
'blue-background': record.enableRules === 1 ? true : false,
'grey-background': record.enableRules === 1 ? false : true,
}"
@change="clickSwitch({ enableRules: record.enableRules, record: record })" />
</template>
</template>
</ns-view-list-table>
<!-- 联系方式 -->
</a-tab-pane>
<a-tab-pane key="2" tab="设备告警" force-render>
<ns-view-list-table
v-show="equipmentAlarm"
ref="mainRefEquipmentAlarm"
class="table"
v-bind="equipmentAlarmConfig">
<template #bodyCell="{ record, column }">
<template v-if="column.dataIndex === 'enableRules'">
<a-switch
:checked="record.enableRules === 1 ? true : false"
:class="{
'blue-background': record.enableRules === 1 ? true : false,
'grey-background': record.enableRules === 1 ? false : true,
}"
@change="clickSwitch({ enableRules: record.enableRules, record: record })" />
</template>
<template v-if="column.dataIndex === 'monitor'">
{{
record?.monitorTime && record?.monitorTimeUnit
? record?.monitorTime + record?.monitorTimeUnit
: '-'
}}
</template>
</template>
</ns-view-list-table>
<a-button
v-if="!equipmentAlarm"
type="primary"
style="position: absolute; right: 130px; z-index: 99; top: 80px; height: 30px"
@click="backequipmentAlarm"
>返回</a-button
>
<!-- 新增 编辑 设备告警 -->
<editeEquipmentAlarm ref="editEquipmentAlarm" @editObject="editObject" />
<!-- 配置设备告警-->
<configureDeviceAlarms v-show="!equipmentAlarm" ref="configureDeviceAlarms" />
</a-tab-pane>
<a-tab-pane key="3" tab="能源告警">
<ns-view-list-table
v-bind="energyAlarmConfig"
v-show="energyAlarm"
ref="mainEnergyAlarmConfig"
class="table">
<template #bodyCell="{ record, column }">
<template v-if="column.dataIndex === 'enableRules'">
<a-switch
:checked="record.enableRules === 1 ? true : false"
:class="{
'blue-background': record.enableRules === 1 ? true : false,
'grey-background': record.enableRules === 1 ? false : true,
}"
@click="clickSwitch({ enableRules: record.enableRules, record: record })" />
</template>
</template>
</ns-view-list-table>
<a-button
v-if="!energyAlarm"
type="primary"
style="position: absolute; right: 130px; z-index: 99; top: 80px; height: 30px"
@click="backenergyAlarm"
>返回</a-button
>
<!-- 新增 编辑 能源告警 -->
<editeEnergyAlarm ref="editeEnergyAlarm" @editObject="editObject" />
<!-- 配置能源告警-->
<configureEnergyAlarms v-show="!energyAlarm" ref="configureEnergyAlarms" />
</a-tab-pane>
</a-tabs>
</div>
</template>
<script lang="ts">
import { notificationtableConfig } from './ts/notificationManagementConfig';
import { equipmentAlarmTableConfig } from './ts/equipmentAlarmConfig';
import { energyAlarmConfigs } from './ts/energyAlarmConfig';
import { ref, createVNode } from 'vue';
import { http } from '/nerv-lib/util';
import { NsMessage, NsModal } from '/nerv-lib/component';
import editeEquipmentAlarm from './equipmentAlarm/editeEquipmentAlarm.vue';
import editeEnergyAlarm from './energyAlarm/editeEnergyAlarm.vue';
import configureEnergyAlarms from './energyAlarm/configureEnergyAlarms.vue';
import configureDeviceAlarms from './equipmentAlarm/configureDeviceAlarms.vue';
import { deviceAlarms } from '/@/api/alarmSettings/deviceAlarms';
import { ExclamationCircleOutlined } from '@ant-design/icons-vue';
export default {
name: 'AlarmSettings',
components: {
editeEquipmentAlarm,
configureDeviceAlarms,
configureEnergyAlarms,
editeEnergyAlarm,
},
setup() {
const mainRef = ref();
const mainRefEquipmentAlarm = ref();
const mainEnergyAlarmConfig = ref();
const editEquipmentAlarm = ref();
const editeEnergyAlarm = ref();
const configureDeviceAlarms = ref();
const configureEnergyAlarms = ref();
const equipmentAlarm = ref(true);
const energyAlarm = ref(true);
const notificationConfig = notificationtableConfig(null, mainEnergyAlarmConfig, null);
//能源告警配置
const energyAlarmConfig = energyAlarmConfigs(
editeEnergyAlarm,
mainRefEquipmentAlarm,
energyAlarm,
configureEnergyAlarms,
);
//设备告警配置
const equipmentAlarmConfig = equipmentAlarmTableConfig(
editEquipmentAlarm,
mainRefEquipmentAlarm,
equipmentAlarm,
configureDeviceAlarms,
);
const callback = (key: any) => {
console.log(key);
};
//返回设备告警
const backequipmentAlarm = () => {
equipmentAlarm.value = !equipmentAlarm.value;
configureDeviceAlarms.value.show = false;
};
const backenergyAlarm = () => {
energyAlarm.value = !energyAlarm.value;
configureEnergyAlarms.value.show = false;
};
const clickSwitch = (data: any) => {
console.log(data.record.enableRules === 1 ? '关闭' : '启用');
NsModal.confirm({
title: '启用状态',
icon: createVNode(ExclamationCircleOutlined),
content: '确定' + (data.record.enableRules === 1 ? '关闭' : '启用') + '吗?',
onOk: () => {
http
.post(deviceAlarms.addOrUpNewData, {
id: data.record.id,
enableRules: data.record.enableRules === 1 ? 0 : 1,
})
.then(() => {
NsMessage.success('操作成功');
mainRefEquipmentAlarm.value?.nsTableRef.reload();
});
},
});
};
// 编辑或添加成功 刷新列表
const editObject = () => {
mainRefEquipmentAlarm.value?.nsTableRef.reload();
// console.log(newList.value.formFinish, '数据');
};
return {
callback,
notificationConfig,
energyAlarmConfig,
equipmentAlarmConfig,
editObject,
clickSwitch,
editEquipmentAlarm,
editeEnergyAlarm,
configureDeviceAlarms,
configureEnergyAlarms,
equipmentAlarm,
energyAlarm,
backequipmentAlarm,
backenergyAlarm,
mainRef,
mainRefEquipmentAlarm,
mainEnergyAlarmConfig,
};
},
};
</script>
<style lang="less" scoped>
.box {
width: 100%;
height: 100%;
display: flex;
flex-direction: column;
justify-content: space-between;
}
.full-height {
height: 100%; /* 设置高度为父容器高度 */
}
.blue-background.ant-switch-checked {
background-color: linear-gradient(
180deg,
rgba(1, 206, 255, 1) 0%,
rgba(0, 150, 229, 1) 100%
) !important;
}
.grey-background.ant-switch {
background-color: grey !important;
}
.blue-background.ant-switch-checked .ant-switch-handle {
background-color: linear-gradient(
180deg,
rgba(1, 206, 255, 1) 0%,
rgba(0, 150, 229, 1) 100%
) !important;
}
.grey-background.ant-switch .ant-switch-handle {
background-color: grey !important;
}
</style>

View File

@@ -0,0 +1,108 @@
{
"listData":[
{
"id": "d4",
"isDel": "0",
"officesId": "84",
"deviceCode": "37430200143",
"deviceName": "地听测试电表",
"category": "1",
"type": "1001",
"energyCount": "1",
"serialNumber": "69",
"pidCode": null,
"brand": "",
"types": "",
"manufacturer": "elit non in",
"contacts": "ad reprehenderit",
"phonenumber": "34",
"position": "in esse commodo1",
"activeState": "1",
"measurementDirection": "1",
"deviceMagnification": 62,
"deviceAccuracy": "89",
"frequency": "anim consequat irure",
"standardFrequency": "ut elit",
"deviceHead": "pariatur ex velit",
"constructor": "84566",
"voltageType": "cillum aliquip reprehenderit",
"pt": 61,
"ct": 64,
"communicationProtocol": "cupidatat nisi ea ad",
"ip": "",
"port": "",
"com": "",
"slaveAddress": "",
"dlt": "",
"conversionIdentifier": "48",
"multiplicationAdjustment": "1",
"accessMethod": "1",
"replacementFrequency": "0",
"dataDetail": "sit",
"insertTime": null,
"children": null,
"devicePointList": null,
"insertUser": null,
"priority": "1",
"alarmTitle": "电压异常告警",
"errorCode": "A001",
"monitorTime":"1",
"repetitions":"1",
"monitorTimeUnit": "分",
"enableRules": "1",
"isUse":true
} , {
"id": "d5",
"isDel": "0",
"officesId": "84",
"deviceCode": "37430200143",
"deviceName": "地听测试电表",
"category": "1",
"type": "1001",
"energyCount": "1",
"serialNumber": "69",
"pidCode": null,
"brand": "",
"types": "",
"manufacturer": "elit non in",
"contacts": "ad reprehenderit",
"phonenumber": "34",
"position": "in esse commodo2",
"activeState": "1",
"measurementDirection": "1",
"deviceMagnification": 62,
"deviceAccuracy": "89",
"frequency": "anim consequat irure",
"standardFrequency": "ut elit",
"deviceHead": "pariatur ex velit",
"constructor": "84566",
"voltageType": "cillum aliquip reprehenderit",
"pt": 61,
"ct": 64,
"communicationProtocol": "cupidatat nisi ea ad",
"ip": "",
"port": "",
"com": "",
"slaveAddress": "",
"dlt": "",
"conversionIdentifier": "48",
"multiplicationAdjustment": "1",
"accessMethod": "1",
"replacementFrequency": "0",
"dataDetail": "sit",
"insertTime": null,
"children": null,
"devicePointList": null,
"insertUser": null,
"priority": "1",
"alarmTitle": "电压异常告警",
"errorCode": "A001",
"monitorTime":"1",
"repetitions":"1",
"monitorTimeUnit": "分",
"enableRules": "0",
"isUse":true
}
]
}

View File

@@ -0,0 +1,197 @@
import data from '../notificationManagementMock.json';
import { http } from '/nerv-lib/util';
import { NsMessage } from '/nerv-lib/component';
import { deviceAlarms } from '/@/api/alarmSettings/deviceAlarms';
import { ref } from 'vue';
const tableKeyMap = [
{
title: '序号',
dataIndex: 'address',
customRender: (text: any) => {
return text.index + 1;
},
},
{
title: '优先级',
dataIndex: 'priority',
},
{
title: '告警标题',
dataIndex: 'alarmTitle',
},
{
title: '错误码',
dataIndex: 'errorCode',
},
{
title: '重复次数',
dataIndex: 'repetitions',
textEllipsis: true,
},
{
title: '监测时长',
dataIndex: 'monitor',
},
{
title: '是否启用',
dataIndex: 'enableRules',
},
];
const mockData = ref(data.listData);
const doWnload = (url: any) => {
const a = document.createElement('a');
document.body.appendChild(a);
a.href = encodeURI(url);
//设置下载的文件名
// a.download = fileName.value;
//触发a标签的点击事件进行下载
a.click();
};
export const energyAlarmConfigs = (
editeEnergyAlarm: any,
mainRefEquipmentAlarm: any,
energyAlarm: any,
configureDeviceAlarms: any,
) => {
return {
title: '告警规则',
api: deviceAlarms.getTableList,
value: mockData.value,
headerActions: [
{
label: '新增',
name: 'RoleTypeAdd',
type: 'primary',
handle: () => {
editeEnergyAlarm.value.toggle();
},
},
{
label: '导入',
name: 'groupImport',
type: 'primary',
extra: {
// api: props.postImportApi, // 导入接口名
title: '设备信息', // 弹窗title
templateName: 'whiteListUser', // 所使用的文件名称
indexName: '设备id', // 匹配类型字段
message: [
{ label: '1、若必填项未填写则不能进行导入操作' },
{ label: `2、当重复时则更新数据。` },
{ label: '3、数据将从模版的第五行进行导入。' },
{ label: '4、文件导入勿超过5MB。' },
],
},
},
{
label: '导出',
name: 'groupExports',
type: 'primary',
handle: () => {
doWnload('/hx-ai-intelligent/asset/file/whiteListUser.xlsx');
},
},
],
columns: tableKeyMap,
// rowSelection: null, 选择按钮
columnActions: {
title: '操作',
actions: [
{
label: '编辑',
name: 'FeedBackDetail',
dynamicParams: ['uuid', 'appealType'],
handle: (data: any) => {
editeEnergyAlarm.value.toggle(data);
},
},
{
label: '配置',
name: 'FeedBackDetail',
dynamicParams: ['uuid', 'appealType'],
handle: (data: any) => {
energyAlarm.value = !energyAlarm.value;
configureDeviceAlarms.value.setconfigureDeviceAlarmsData(data);
},
},
{
label: '删除',
name: 'FeedBackDetail',
dynamicParams: ['uuid', 'appealType'],
confirm: true,
handle: (data: any) => {
http.post(deviceAlarms.del, { id: data.id }).then(() => {
NsMessage.success('操作成功');
mainRefEquipmentAlarm.value?.nsTableRef.reload();
});
},
},
],
},
formConfig: {
schemas: [
{
field: 'priority',
label: '告警优先级',
component: 'NsSelect',
componentProps: {
placeholder: '请选择告警优先级',
options: [
{
label: '紧急',
value: 1,
},
{
label: '重要',
value: 2,
},
{
label: '一般',
value: 3,
},
],
},
},
{
field: 'alarmTitle',
label: '告警标题',
component: 'NsInput',
componentProps: {
placeholder: '请输入告警标题关键字',
},
},
{
field: 'errorCode',
label: '错误码',
component: 'NsInput',
componentProps: {
placeholder: '请输入告警错误码',
},
},
{
field: 'enableRules',
label: '启用状态',
component: 'NsSelect',
componentProps: {
placeholder: '请选择启用状态',
options: [
{
label: '启用',
value: 1,
},
{
label: '关闭',
value: 0,
},
],
},
},
],
},
// pagination: { pageSizeOptions: false },
rowKey: 'id',
};
};

View File

@@ -0,0 +1,197 @@
import data from '../notificationManagementMock.json';
import { http } from '/nerv-lib/util';
import { NsMessage } from '/nerv-lib/component';
import { deviceAlarms } from '/@/api/alarmSettings/deviceAlarms';
import { ref } from 'vue';
const tableKeyMap = [
{
title: '序号',
dataIndex: 'address',
customRender: (text: any) => {
return text.index + 1;
},
},
{
title: '优先级',
dataIndex: 'priority',
},
{
title: '告警标题',
dataIndex: 'alarmTitle',
},
{
title: '错误码',
dataIndex: 'errorCode',
},
{
title: '重复次数',
dataIndex: 'repetitions',
textEllipsis: true,
},
{
title: '监测时长',
dataIndex: 'monitor',
},
{
title: '是否启用',
dataIndex: 'enableRules',
},
];
const mockData = ref(data.listData);
const doWnload = (url: any) => {
const a = document.createElement('a');
document.body.appendChild(a);
a.href = encodeURI(url);
//设置下载的文件名
// a.download = fileName.value;
//触发a标签的点击事件进行下载
a.click();
};
export const equipmentAlarmTableConfig = (
editEquipmentAlarm: any,
mainRefEquipmentAlarm: any,
equipmentAlarm: any,
configureDeviceAlarms: any,
) => {
return {
title: '告警规则',
api: deviceAlarms.getTableList,
value: mockData.value,
headerActions: [
{
label: '新增',
name: 'RoleTypeAdd',
type: 'primary',
handle: () => {
editEquipmentAlarm.value.toggle();
},
},
{
label: '导入',
name: 'groupImport',
type: 'primary',
extra: {
// api: props.postImportApi, // 导入接口名
title: '设备信息', // 弹窗title
templateName: 'whiteListUser', // 所使用的文件名称
indexName: '设备id', // 匹配类型字段
message: [
{ label: '1、若必填项未填写则不能进行导入操作' },
{ label: `2、当重复时则更新数据。` },
{ label: '3、数据将从模版的第五行进行导入。' },
{ label: '4、文件导入勿超过5MB。' },
],
},
},
{
label: '导出',
name: 'groupExports',
type: 'primary',
handle: () => {
doWnload('/hx-ai-intelligent/asset/file/whiteListUser.xlsx');
},
},
],
columns: tableKeyMap,
// rowSelection: null, 选择按钮
columnActions: {
title: '操作',
actions: [
{
label: '编辑',
name: 'FeedBackDetail',
dynamicParams: ['uuid', 'appealType'],
handle: (data: any) => {
editEquipmentAlarm.value.toggle(data);
},
},
{
label: '配置',
name: 'FeedBackDetail',
dynamicParams: ['uuid', 'appealType'],
handle: (data: any) => {
equipmentAlarm.value = !equipmentAlarm.value;
configureDeviceAlarms.value.setconfigureDeviceAlarmsData(data);
},
},
{
label: '删除',
name: 'FeedBackDetail',
dynamicParams: ['uuid', 'appealType'],
confirm: true,
handle: (data: any) => {
http.post(deviceAlarms.del, { id: data.id }).then(() => {
NsMessage.success('操作成功');
mainRefEquipmentAlarm.value?.nsTableRef.reload();
});
},
},
],
},
formConfig: {
schemas: [
{
field: 'priority',
label: '告警优先级',
component: 'NsSelect',
componentProps: {
placeholder: '请选择告警优先级',
options: [
{
label: '紧急',
value: 1,
},
{
label: '重要',
value: 2,
},
{
label: '一般',
value: 3,
},
],
},
},
{
field: 'alarmTitle',
label: '告警标题',
component: 'NsInput',
componentProps: {
placeholder: '请输入告警标题关键字',
},
},
{
field: 'errorCode',
label: '错误码',
component: 'NsInput',
componentProps: {
placeholder: '请输入错误码关键字',
},
},
{
field: 'enableRules',
label: '启用状态',
component: 'NsSelect',
componentProps: {
placeholder: '请选择启用状态',
options: [
{
label: '启用',
value: 1,
},
{
label: '关闭',
value: 0,
},
],
},
},
],
},
// pagination: { pageSizeOptions: false },
rowKey: 'id',
};
};

View File

@@ -0,0 +1,151 @@
import { dateUtil } from '/nerv-lib/util/date-util';
import data from '../notificationManagementMock.json';
import { http } from '/nerv-lib/util';
import { ref } from 'vue';
const tableKeyMap = [
{
title: '序号',
dataIndex: 'address',
customRender: (text: any) => {
return text.index + 1;
},
},
{
title: '优先级',
dataIndex: 'id',
},
{
title: '告警类型',
dataIndex: 'deviceCode',
},
{
title: '告警标题',
dataIndex: 'deviceName',
},
{
title: '错误码',
dataIndex: 'position',
},
{
title: '通知名单',
dataIndex: 'position',
textEllipsis: true,
},
{
title: '通知方式',
dataIndex: 'position',
},
{
title: '启用通知',
dataIndex: 'enableRules',
},
];
const mockData = ref(data.listData);
export const notificationtableConfig = (el) => {
return {
title: '告警规则',
// api: '/carbon_emission/device/getDeviceList',
value: mockData.value,
headerActions: [{}],
columns: tableKeyMap,
// rowSelection: null, 选择按钮
columnActions: {
title: '操作',
actions: [
{
label: '联系方式',
name: 'FeedBackDetail',
dynamicParams: ['uuid', 'appealType'],
handle: (data: any) => {
console.log(data, '联系方式');
},
},
],
},
formConfig: {
schemas: [
{
field: 'name',
label: '告警类型',
component: 'NsSelect',
componentProps: {
placeholder: '请选告警类型',
options: [
{
label: '设备告警',
value: '1',
},
{
label: '网关告警',
value: '2',
},
{
label: '能耗告警',
value: '3',
},
],
},
},
{
field: 'provider',
label: '告警优先级',
component: 'NsSelect',
componentProps: {
placeholder: '请选择告警优先级',
options: [
{
label: '紧急',
value: '1',
},
{
label: '重要',
value: '1',
},
{
label: '一般',
value: '2',
},
],
},
},
{
field: 'provider',
label: '告警标题',
component: 'NsInput',
componentProps: {
placeholder: '请输入告警标题关键字',
},
},
{
field: 'provider',
label: '错误码',
component: 'NsInput',
componentProps: {
placeholder: '请输入告警错误码',
},
},
{
field: 'payWay',
label: '启用状态',
component: 'NsSelect',
componentProps: {
placeholder: '请选择启用状态',
options: [
{
label: '启用',
value: '1',
},
{
label: '关闭',
value: '0',
},
],
},
},
],
},
// pagination: { pageSizeOptions: false },
rowKey: 'id',
};
};

View File

@@ -0,0 +1,283 @@
import { ref } from 'vue';
import { http } from '/nerv-lib/util';
import { origanizemanage } from '/@/api/origanizemanage';
import { carbonEmissionFactorLibrary } from '/@/api/carbonEmissionFactorLibrary';
export const formConfig = (disabled) => {
return ref([
{
field: 'fields',
component: 'NsChildForm',
componentProps: {
schemas: [
{
label: '排放源',
field: 'emissionSources',
component: 'NsInput',
componentProps: {
placeholder: '请输入排放源',
maxLength: 20,
},
rules: [
{
required: true,
message: '请输入排放源',
},
],
},
{
field: 'emissionType',
label: '排放分类',
component: 'NsCascader',
fieldMap: ['emissionType'],
componentProps: {
placeholder: '请选择排放分类',
api: carbonEmissionFactorLibrary.getCarbonFactorTree,
fieldNames: { label: 'emissionName', value: 'id' },
showSearch: true,
},
rules: [
{
required: true,
message: '请选择排放分类',
},
],
},
{
field: 'emissionGas',
label: '排放气体',
component: 'NsSelect',
componentProps: {
allowClear: true,
placeholder: '请选择排放气体',
options: [
{
label: 'CO2',
value: 'CO2',
},
{
label: 'CO2e',
value: 'CO2e',
},
{
label: 'SF6',
value:'SF6',
},
{
label: 'CH4',
value: 'CH4',
},
{
label: 'PFCs',
value: 'PFCs',
},
{
label: 'HFCs',
value: 'HFCs',
},
],
},
rules: [
{
required: true,
message: '请选择排放气体',
},
],
},
// {
// field: 'emissionProcess',
// label: '排放环节',
// component: 'NsSelectApi',
// componentProps: {
// api: carbonEmissionFactorLibrary.getEmissionProcess,
// params: {
// emissionType: sessionStorage.getItem('checkedTreeNode'),
// },
// resultField: 'data',
// labelField: 'emissionProcess',
// valueField: 'emissionProcess',
// immediate: true,
// autoSelectFirst: false,
// filterOption:false,
// showSearch:true,
// autoClearSearchValue:false
// },
// },
{
label: '排放环节',
field: 'emissionProcess',
component: 'NsInput',
componentProps: {
placeholder: '请输入排放环节',
maxLength: 20,
},
rules: [
{
required: true,
message: '请输入排放环节',
},
],
},
{
label: '排放因子',
field: 'emissionFactors',
component: 'NsInputNumber',
componentProps: {
placeholder: '请输入排放因子值',
maxLength: 20,
},
rules: [
{
required: true,
message: '请输入排放因子值',
trigger: 'change',
},
],
},
{
field: 'carbonEmissionPrefix',
label: '碳排前缀',
component: 'NsSelect',
componentProps: {
disabled: true,
allowClear: true,
defaultValue: 't',
placeholder: '请选择碳排前缀',
options: [
{
label: 'g',
value: 'g',
},
{
label: 'kg',
value: 'kg',
},
{
label: 't',
value: 't',
},
],
},
rules: [
{
required: true,
message: '请选择碳排前缀',
},
],
},
{
label: '碳排后缀',
field: 'carbonEmissionSuffix',
component: 'NsInput',
componentProps: {
placeholder: '请输入碳排后缀',
maxLength: 20,
},
rules: [
{
required: true,
message: '请输入碳排后缀',
},
],
},
{
label: '已引用数',
field: 'numberOfReferences',
component: 'NsInput',
componentProps: {
defaultValue: 10,
disabled: true,
maxLength: 20,
},
},
{
label: '参考文献',
field: 'reference',
component: 'NsTextarea',
componentProps: {
placeholder: '请输入参考文献',
maxLength: 20,
},
},
],
},
},
]);
};
const options = ref([]);
const getUserPerList = (transform, params = {}) => {
return http.post(origanizemanage.queryUserPerList, { ...params }).then((res) => {
return res.data?.map((item) => {
item = { ...item, ...transform(item) };
return item;
});
});
};
export const formConfig2 = (casData: any) => {
return ref([
{
field: 'information',
component: 'NsCascader',
componentProps: {
placeholder: '请选择',
displayRender: ({ labels, selectedOptions }: any) => {
console.log(labels, selectedOptions);
casData.value = selectedOptions.map(({ label, value }) => {
return { label, value };
});
return labels.join('/');
},
loadData: (selectedOptions, options) => {
console.log(selectedOptions, options, 'selectedOptions, options');
const targetOption = selectedOptions[selectedOptions.length - 1];
let transForm, params;
// load options lazily
if (!selectedOptions.length) {
transForm = (data) => {
data['label'] = data.orgName;
data['value'] = data.orgId;
data['isLeaf'] = false;
data['level'] = 1;
return data;
};
getUserPerList(transForm).then((res) => {
options.value = [...res];
});
}
const id = targetOption?.value;
const level = targetOption?.level;
if (targetOption) {
targetOption.loading = true;
}
if (level === 1) {
transForm = (data) => {
data['label'] = data.deptName;
data['value'] = data.deptId;
data['isLeaf'] = false;
data['level'] = 2;
return data;
};
params = { orgId: id };
} else if (level === 2) {
transForm = (data) => {
data['label'] = data.roleName;
data['value'] = data.roleId;
data['level'] = 3;
return data;
};
params = { deptId: id };
}
if (targetOption) {
getUserPerList(transForm, { ...params }).then((res) => {
targetOption.loading = false;
targetOption.children = [...res];
});
}
},
},
},
]);
};

View File

@@ -0,0 +1,712 @@
<!-- @format -->
<template>
<div class="main">
<div class="left">
<div class="top">
<a-form style="width: 100%;margin: 0 auto;">
<div class="ns-form-title"><span>排放分类</span></div>
<div style="padding: 0 16px !important;width: 100%;">
<a-row>
<a-col :span="24" style="margin-bottom: 16px;">
<a-input-search
v-model:value="selectTreeDataValue"
placeholder="请输入关键词"
@search="onSearchTreeData"
/>
</a-col>
</a-row>
</div>
</a-form>
<a-tree
v-if="gData && gData.length > 0"
class="draggable-tree"
style="padding: 0 16px !important;"
draggable
show-line
checkable
block-node
:tree-data="gData"
:checkedKeys="checkedTreeNodeKeys"
:selectedKeys="selectedKeys"
@dragenter="onDragEnter"
@drop="onDrop"
@check="checkTreeNode"
@select="onSelect"
:expanded-keys="expandedKeys"
:auto-expand-parent="autoExpandParent"
@expand="onExpand">
<template #title="{ emissionName }">
<span v-if="emissionName && selectTreeDataValue && emissionName.indexOf(selectTreeDataValue) > -1">
{{ emissionName.substring(0, emissionName.indexOf(selectTreeDataValue)) }}
<span style="color: #f50">{{ selectTreeDataValue }}</span>
{{ emissionName.substring(emissionName.indexOf(selectTreeDataValue) + selectTreeDataValue.length) }}
</span>
<span v-else>{{ emissionName }}</span>
</template>
</a-tree>
<a-popover v-if="showOperation" placement="rightTop" trigger="focus">
<template #content>
<div style="display: flex;flex-direction: column;">
<a-button type="text" @click="editTreeNodeData">编辑</a-button>
<a-button type="text" @click="addTreeNodeData">新增子节点</a-button>
<a-button type="text">上移</a-button>
<a-button type="text">下移</a-button>
<a-button type="text" @click="deleteTreeNode">删除</a-button>
</div>
</template>
<MoreOutlined style="position: absolute;right: 0;top: 16%;font-size: 25px;cursor: pointer;" />
</a-popover>
<div class="addTreeNode">
<a-button type="primary" style="width:100%;" @click="addTreeNodeData">新增</a-button>
</div>
</div>
</div>
<div class="right">
<ns-view-list-table v-bind="tableConfig" :model="data" ref="mainRef" :scroll="{ x: 2000}"/>
</div>
<!-- 新增树节点 -->
<ns-modal :visible="treeNodeAdd" :title="operationTree" @ok="handleOk" @cancel="handleCancel">
<ns-input
v-model:value="addTreeNode"
class="input"
placeholder="请输入排放类型"/>
</ns-modal>
<!-- 新增数据库数据 -->
<a-drawer
:width="500"
:visible="visible"
:body-style="{ paddingBottom: '80px' }"
:footer-style="{ textAlign: 'right' }"
destroyOnClose
@close="onClose">
<ns-form
ref="formRef"
:schemas="formSchema"
:model="formData"
class="form"
:wrapperCol="{ span: 20 }"
formLayout="vertical" />
<template #footer>
<a-button style="margin-right: 8px" @click="onClose">取消</a-button>
<a-button type="primary" @click="onEdit">确定</a-button>
</template>
</a-drawer>
</div>
</template>
<script lang="ts" setup>
import { MoreOutlined,ExclamationCircleOutlined } from '@ant-design/icons-vue';
import { Modal } from 'ant-design-vue';
import { computed, createVNode, defineComponent, reactive, ref, watchEffect,watch } from 'vue';
import { http } from '/nerv-lib/util/http';
import { NsMessage, NsModal } from '/nerv-lib/component';
import { formConfig, formConfig2 } from './config';
import { carbonEmissionFactorLibrary } from '/@/api/carbonEmissionFactorLibrary';
import type {
AntTreeNodeDragEnterEvent,
AntTreeNodeDropEvent,
TreeProps,
} from 'ant-design-vue/es/tree';
import { log } from 'node:console';
defineOptions({ name: 'OrderListIndex' });
const selectTreeDataValue = ref<string>('');
const mainRef = ref();
const data = reactive({});
let formData = ref({});
const formRef = ref();
const visible = ref(false);
const searchValue = ref<string>('');
const searchValue2 = ref<string>('');
const disabled = ref(false);
const treeNodeAdd = ref<boolean>(false);
const operationTree = ref<string>('新增');
const showOperation = ref(false)
const opMap: any = ref({
type: 'add',
fuc: () => {},
record: {},
});
watchEffect(() => {
disabled.value = opMap.value.type === 'edit';
});
const formSchema = formConfig(disabled);
const casData = ref([]);
const treeData = ref([]);
const userAuthList = ref([]);
const orgId = JSON.parse(sessionStorage.getItem('userInfo')).orgId;
const dynamicDisabled = computed(() => {
return formRef.value?.validateResult && userAuthList.value?.length;
});
const fetch = (api, params = { orgId }) => {
return http.post(api, params);
};
// 树结构
const x = 3;
const y = 2;
const z = 1;
const genData: TreeProps['treeData'] = [];
const checkedTreeNodeKeys = ref<string[]>(['0-0']);
const generateData = (_level: number, _preKey?: string, _tns?: TreeProps['treeData']) => {
const preKey = _preKey || '0';
const tns = _tns || genData;
const children = [];
for (let i = 0; i < x; i++) {
const key = `${preKey}-${i}`;
tns.push({ title: key, key });
if (i < y) {
children.push(key);
}
}
if (_level < 0) {
return tns;
}
const level = _level - 1;
children.forEach((key, index) => {
tns[index].children = [];
return generateData(level, key, tns[index].children);
});
};
generateData(z);
type TreeDataItem = TreeProps['treeData'][number];
const gData = ref<TreeProps['treeData']>(genData);
const onDragEnter = (info: AntTreeNodeDragEnterEvent) => {
console.log(info);
// expandedKeys 需要展开时
// expandedKeys.value = info.expandedKeys;
};
const onDrop = (info: AntTreeNodeDropEvent) => {
console.log(info);
const dropKey = info.node.key;
const dragKey = info.dragNode.key;
const dropPos = info.node.pos.split('-');
const dropPosition = info.dropPosition - Number(dropPos[dropPos.length - 1]);
const loop = (data: TreeProps['treeData'], key: string | number, callback: any) => {
data.forEach((item, index) => {
if (item.key === key) {
return callback(item, index, data);
}
if (item.children) {
return loop(item.children, key, callback);
}
});
};
const data = [...gData.value];
// Find dragObject
let dragObj: TreeDataItem;
loop(data, dragKey, (item: TreeDataItem, index: number, arr: TreeProps['treeData']) => {
arr.splice(index, 1);
dragObj = item;
});
if (!info.dropToGap) {
// Drop on the content
loop(data, dropKey, (item: TreeDataItem) => {
item.children = item.children || [];
/// where to insert 示例添加到头部,可以是随意位置
item.children.unshift(dragObj);
});
} else if (
(info.node.children || []).length > 0 && // Has children
info.node.expanded && // Is expanded
dropPosition === 1 // On the bottom gap
) {
loop(data, dropKey, (item: TreeDataItem) => {
item.children = item.children || [];
// where to insert 示例添加到头部,可以是随意位置
item.children.unshift(dragObj);
});
} else {
let ar: TreeProps['treeData'] = [];
let i = 0;
loop(data, dropKey, (_item: TreeDataItem, index: number, arr: TreeProps['treeData']) => {
ar = arr;
i = index;
});
if (dropPosition === -1) {
ar.splice(i, 0, dragObj);
} else {
ar.splice(i + 1, 0, dragObj);
}
}
gData.value = data;
};
const dataList: TreeProps['treeData'] = [];
const generateList = (data: TreeProps['treeData']) => {
for (let i = 0; i < data.length; i++) {
const node = data[i];
const key = node.key;
dataList.push({ key, title: key });
if (node.children) {
generateList(node.children);
}
}
};
generateList(genData);
const getParentKey = (
key: string | number,
tree: TreeProps['treeData'],
): string | number | undefined => {
let parentKey;
for (let i = 0; i < tree.length; i++) {
const node = tree[i];
if (node.children) {
if (node.children.some(item => item.key === key)) {
parentKey = node.key;
} else if (getParentKey(key, node.children)) {
parentKey = getParentKey(key, node.children);
}
}
}
return parentKey;
};
const expandedKeys = ref<(string | number)[]>([]);
const autoExpandParent = ref<boolean>(true);
const onExpand = (keys: string[]) => {
expandedKeys.value = keys;
autoExpandParent.value = false;
};
watch(selectTreeDataValue, value => {
const expanded = dataList
.map((item: TreeProps['treeData'][number]) => {
if (item.title.indexOf(value) > -1) {
return getParentKey(item.key, gData.value);
}
return null;
})
.filter((item, i, self) => item && self.indexOf(item) === i);
expandedKeys.value = expanded;
selectTreeDataValue.value = value;
autoExpandParent.value = true;
});
// 筛选树结构中的数据
const onSearchTreeData = (selectTreeDataValue: string) => {
console.log('use value', selectTreeDataValue);
console.log('or use this.value', value.value);
};
// 点击数据点的复选框
const checkedIds = ref([])
const emissionType = ref()
const checkTreeNode = (checkedKeys, info) => {
checkedTreeNodeKeys.value = checkedKeys
checkedIds.value = []
info.checkedNodes.forEach(item=>{
checkedIds.value.push(item.id)
})
sessionStorage.setItem('checkedTreeNode', checkedIds.value);
emissionType.value = checkedIds.value.join(',')
mainRef.value?.nsTableRef.reload();
}
// 点击新增树节点
const addTreeNodeData = () => {
operationTree.value='新增'
treeNodeAdd.value = true;
editTreeNode.value.type = 'create'
};
// 编辑树节点
const editTreeNodeData = () => {
operationTree.value='编辑'
treeNodeAdd.value = true;
editTreeNode.value.type = 'update'
}
// 删除树节点
const deleteTreeNode = () => {
Modal.confirm({
title: '警告',
icon: createVNode(ExclamationCircleOutlined),
content: '确定要删除么?',
okText: '确认',
okType: 'primary',
cancelText: '取消',
onOk() {
http.post(carbonEmissionFactorLibrary.delTreeNode,editTreeNode.value).then(() => {
getOrgTree()
NsMessage.success('操作成功');
});
},
onCancel() {
console.log('Cancel');
},
});
}
// 新增/编辑树节点点击确定
const addTreeNode =ref()
const handleOk = (e: MouseEvent) => {
editTreeNode.value.emissionName = addTreeNode.value
http.post(carbonEmissionFactorLibrary.creat,editTreeNode.value).then(() => {
getOrgTree()
NsMessage.success('操作成功');
addTreeNode.value = ''
treeNodeAdd.value = false;
});
};
const handleCancel = () => {
addTreeNode.value = ''
treeNodeAdd.value = false;
};
// 获取排放分类树
const getOrgTree = (params?) => {
fetch(carbonEmissionFactorLibrary.getCarbonFactorTree, params).then((res) => {
gData.value = res.data
// 找到匹配的节点数据
const selectedNodes = [];
checkedTreeNodeKeys.value.forEach(key => {
const [parentId, childId] = key.split('-').map(Number);
if (parentId >= 0 && childId >= 0 && gData.value[parentId]?.children?.[childId]) {
selectedNodes.push(gData.value[parentId]);
}
});
// 获取默认选中节点的所有id
getDefaultIds(selectedNodes)
});
};
const defaultIds = ref([])
const getDefaultIds = (selectedNodes) => {
selectedNodes.forEach(items => {
defaultIds.value.push(items.id)
if(items.children){
getDefaultIds(items.children)
}
})
emissionType.value = defaultIds.value.join(',')
checkedIds.value = defaultIds.value
sessionStorage.setItem('checkedTreeNode', checkedIds.value);
}
getOrgTree();
// 被选中的树节点
const editTreeNode = ref({})
const onSelect = (selectedKeys: string[], info: any) => {
if(info.selected){
showOperation.value = true
editTreeNode.value = {
id:info.selectedNodes[0].id,
level:info.selectedNodes[0].level,
dataNumber:info.selectedNodes[0].dataNumber,
sortNumber:info.selectedNodes[0].sortNumber,
parentEmissionId:info.selectedNodes[0].parentEmissionId,
}
}
};
const onSearch = () => {
console.log(searchValue.value);
getOrgTree({ orgName: searchValue.value, orgId });
};
const tableFetch = (params) => {
console.log(params, 'sdfasfasdfasdfasdf');
tableConfig.value.params = {
...mainRef.value.params,
...params,
};
setTimeout(() => {
mainRef.value?.nsTableRef.reload();
}, 100);
};
const handleSelect = (selectedKeys: any, info: any) => {
fetch(carbonEmissionFactorLibrary.queryDeptTree, { orgId: info.node?.orgInfo.orgId }).then((res) => {
treeData2.value = res.data;
});
tableFetch({ orgId: info.node?.orgInfo.orgId });
};
const onClose = () => {
visible.value = false;
formData.value = {};
userAuthList.value.splice(0);
};
const onEdit = () => {
formRef.value?.triggerSubmit().then(() => {
console.log(formData.value, 'formData.value');
// if (!userAuthList.value.length) {
// NsMessage.error('请添加用户权限');
// return;
// }
opMap.value.fuc &&
opMap.value.fuc({ ...formData.value });
});
};
const tableConfig = ref({
title: '数据库',
api: carbonEmissionFactorLibrary.getTableList,
params: {
orgId,
emissionType
},
headerActions: [
{
label: '新增',
name: 'userAdd',
type: 'primary',
handle: () => {
opMap.value.type = 'add';
setTimeout(() => {
formData.value = {
carbonEmissionPrefix:'t',
numberOfReferences:'10'
};
userAuthList.value.splice(0);
});
opMap.value.fuc = (formData: any) => {
formData.emissionType = formData.emissionType[formData.emissionType.length - 1]
return http.post(carbonEmissionFactorLibrary.creatOrUpdate, formData).then(() => {
mainRef.value?.nsTableRef.reload();
visible.value = false;
NsMessage.success('操作成功');
});
};
visible.value = true;
},
},
{
label: '导入',
type: 'primary',
name: 'userImport',
handle: () => {},
},
{
label: '导出',
type: 'primary',
name: 'userExports',
},
{
label: '批量删除',
type: 'primary',
name: 'userBatchDel',
dynamicDisabled: (data: any) => {
return data.list.length === 0;
},
confirm: true,
isReload: true,
isClearCheck: true,
api: carbonEmissionFactorLibrary.del,
dynamicParams: { id: 'id[]' },
},
],
columns: [
{
title: 'id',
customRender: (text: any) => {
return text.index + 1;
},
},
{
title: '排放源',
dataIndex: 'emissionSources',
},
{
title: '排放类型',
dataIndex: 'emissionTypeColumn',
},
{
title: '排放气体',
dataIndex: 'emissionGas',
},
{
title: '排放环节',
dataIndex: 'emissionProcess',
},
{
title: '排放因子',
dataIndex: 'emissionFactors',
},
{
title: '排放因子单位',
dataIndex: 'emissionFactorUnits',
},
{
title: '数据来源',
dataIndex: 'dataSources',
},
{
title: '数据库',
dataIndex: 'carbonDatabase',
},
{
title: '参考文献',
dataIndex: 'reference',
},
{
title: '引用数量',
dataIndex: 'numberOfReferences',
},
],
columnActions: {
title: '操作',
actions: [
{
label: '编辑',
name: 'userEdit',
handle: (record: any) => {
userAuthList.value.splice(0);
setTimeout(() => {
console.log(record.id);
http.post(carbonEmissionFactorLibrary.findById,{ id: record.id } ).then((res) => {
formData.value = res.data;
});
}, 10);
opMap.value.type = 'edit';
opMap.value.fuc = (formData: any) => {
return http.post(carbonEmissionFactorLibrary.creatOrUpdate, formData).then(() => {
mainRef.value?.nsTableRef.reload();
visible.value = false;
NsMessage.success('操作成功');
});
};
visible.value = true;
},
},
{
label: '删除',
name: 'userDelete',
dynamicParams: 'id',
confirm: true,
isReload: true,
api: carbonEmissionFactorLibrary.del,
},
],
},
formConfig: {
schemas: [
{
field: 'emissionSources',
label: '排放源',
component: 'NsInput',
componentProps: {
placeholder: '请输入排放源',
maxLength: 30,
},
},
{
field: 'emissionProcess',
label: '排放环节',
component: 'NsSelect',
componentProps: {
allowClear: true,
placeholder: '请选择排放环节',
options: [
{
label: '消费环节',
value: 0,
}
],
},
},
{
field: 'emissionGas',
label: '排放气体',
component: 'NsSelect',
componentProps: {
allowClear: true,
placeholder: '请选择排放气体',
options: [
{
label: 'CO2',
value: 0,
},
{
label: 'CO2e',
value: 1,
},
],
},
},
{
field: 'carbonDatabase',
label: '数据库名称',
component: 'NsSelect',
componentProps: {
allowClear: true,
placeholder: '请选择数据库名称',
showSearch: true,
options: [
{
label: '正常',
value: '正常',
},
{
label: '冻结',
value: '冻结',
},
],
},
},
{
field: 'reference',
label: '文献关键字',
component: 'NsInput',
componentProps: {
placeholder: '请输入文献关键字',
maxLength: 30,
},
},
],
params: {},
},
// pagination: { defaultPageSize: 10 },
rowKey: 'id',
});
</script>
<style lang="less" scoped>
.main {
background-color: @ns-content-bg;
display: flex;
}
.left {
width: 300px;
// max-height: calc(100vh - 96px);
margin-right: @ns-gap;
min-width: fit-content;
> div {
border-radius: @ns-border-radius;
background-color: @white;
// box-shadow: @ns-content-box-shadow;
flex: 1;
}
display: flex;
flex-direction: column;
.top{
position: relative;
.addTreeNode{
width: 100%;
padding: 16px;
position: absolute;
bottom: 0;
}
}
}
.right {
flex: 1;
min-width: 0;
}
.top {
overflow: auto;
// height: 50%;
// border-bottom: 5px solid rgb(229, 235, 240);
// overflow-y: auto;
}
.ns-form-title{
font-weight: bold;
user-select: text;
padding: 16px;
margin-bottom: 16px;
padding-bottom: 10px;
border-bottom: 1px solid #e9e9e9;
}
</style>

View File

@@ -0,0 +1,45 @@
import { ref } from 'vue';
import { origanizemanage } from '/@/api/origanizemanage';
import { http } from '/nerv-lib/saas';
export const mockData = ref([
{
id: 3,
zhanghao: 'axb',
name: '张三',
sex: '男',
phone: '123456789',
email: '1234567889',
relation: '1',
role: '1',
status: '1',
},
]);
export const mockData2 = ref([
{
information: '铁路局1/产品部1/产品总监1',
},
]);
export const treeData = ref([
{
title: '铁路总局',
key: '0-0',
children: [
{ title: '济阳站', key: '0-0-0' },
{ title: '临沂站', key: '0-0-1' },
],
},
]);
export const treeData2 = [
{
title: '全部',
key: '0-0',
children: [
{ title: '产品部', key: '0-0-0' },
{ title: '运维部', key: '0-0-1' },
],
},
];

View File

@@ -0,0 +1,74 @@
export const tableColumns = [
{
title: '序号',
customRender: (text: any) => {
return text.index + 1;
},
},
{
title: '能源种类',
dataIndex: 'money',
},
{
title: '计量单位',
className: 'column-money',
dataIndex: 'money',
},
{
title: '全年',
dataIndex: 'address',
},
{
title: '1月',
dataIndex: 'address',
},
{
title: '2月',
dataIndex: 'address',
},
{
title: '3月',
dataIndex: 'address',
},
{
title: '4月',
dataIndex: 'address',
},
{
title: '5月',
dataIndex: 'address',
},
{
title: '6月',
dataIndex: 'address',
},
{
title: '7月',
dataIndex: 'address',
},
{
title: '8月',
dataIndex: 'address',
},
{
title: '9月',
dataIndex: 'address',
},
{
title: '10月',
dataIndex: 'address',
},
{
title: '11月',
dataIndex: 'address',
},
{
title: '12月',
dataIndex: 'address',
},
{
title: '操作',
key: 'action',
width: 130
},
];

View File

@@ -0,0 +1,251 @@
<template>
<div>
<a-table
:columns="tableColumns"
:data-source="data"
bordered
:pagination="false"
:scroll="{ x: 2000 }">
<template #bodyCell="{ column, text }">
<template v-if="column.key === 'action'">
<span>
<a>编辑</a>
<a-divider type="vertical" />
<a>删除</a>
</span>
</template>
</template>
<template #title>
<a-date-picker v-model:value="selectYear" picker="year" />
<div class="buttonGroup">
<a-button type="primary" @click="addNewData">新增</a-button>
<a-button type="primary">导入</a-button>
<a-button type="primary">导出</a-button>
<a-button type="primary">模板下载</a-button>
<a-button type="primary">上传凭证</a-button>
<a-button type="primary">凭证下载</a-button>
</div>
</template>
</a-table>
<a-pagination
:current="current"
:page-size="pageSize"
:total="0"
:show-total="(total, range) => `${range[0]}-${range[1]} of ${total} items`" />
<!-- 新增数据库数据 -->
<a-drawer
:width="500"
:visible="visible"
:body-style="{ paddingBottom: '80px' }"
:footer-style="{ textAlign: 'right' }"
destroyOnClose
@close="onClose">
<a-form
ref="formRef"
:model="formState"
:rules="rules"
:label-col="labelCol"
:wrapper-col="wrapperCol"
>
<a-form-item ref="name" label="能源种类" name="name">
<a-input v-model:value="formState.name" />
</a-form-item>
<a-form-item label="计量单位">
<a-cascader v-model:value="value" :options="options" placeholder="Please select" />
</a-form-item>
<a-form-item label="自动采集节点">
<a-tree-select
v-model:value="treeValue"
style="width: 300px"
placeholder="Please select"
:tree-line="true"
:tree-data="treeData"
tree-node-filter-prop="title"
>
<template #title="{ value: val, title }">
<b v-if="val === 'parent 1-1'" style="color: #08c">sss</b>
<template v-else>{{ title }}</template>
</template>
</a-tree-select>
</a-form-item>
<a-form-item label="计算碳排" name="resource">
<a-radio-group v-model:value="formState.resource">
<a-radio value="1">是</a-radio>
<a-radio value="2">否</a-radio>
</a-radio-group>
</a-form-item>
<a-form-item label="排放类型" name="region">
<a-select v-model:value="formState.region" placeholder="请选择排放类型">
<a-select-option value="shanghai">Zone one</a-select-option>
<a-select-option value="beijing">Zone two</a-select-option>
</a-select>
</a-form-item>
<a-form-item label="1" name="delivery">
<a-switch v-model:checked="formState.delivery" />
</a-form-item>
</a-form>
<template #footer>
<a-button style="margin-right: 8px" @click="onClose">取消</a-button>
<a-button type="primary" @click="onSubmit">确定</a-button>
</template>
</a-drawer>
</div>
</template>
<script lang="ts" setup>
import { ref,reactive, toRaw } from 'vue';
import type { UnwrapRef } from 'vue';
import type { Rule } from 'ant-design-vue/es/form';
import type { CascaderProps,TreeSelectProps } from 'ant-design-vue';
import type { Dayjs } from 'dayjs';
import { http } from '/nerv-lib/util/http';
import { tableColumns } from '../config';
import { energyConsumption } from '/@/api/carbonEmissionFactorLibrary';
defineOptions({
name: 'EnergyConsumption', // 与页面路由name一致缓存才可生效
});
const orgId = JSON.parse(sessionStorage.getItem('userInfo')).orgId;
const fetch = (api, params = { orgId }) => {
return http.post(api, params);
};
const selectYear = ref<Dayjs>();
const current = ref<number>(1);
const pageSize = ref<number>(10);
const visible = ref(false);
const value = ref<string[]>([]);
const data = ref([]);
interface FormState {
name: string;
region: string | undefined;
delivery: boolean;
resource: string;
}
const formRef = ref();
const labelCol = { span: 5 };
const wrapperCol = { span: 13 };
const formState: UnwrapRef<FormState> = reactive({
name: '',
region: undefined,
delivery: false,
resource: '',
});
// 定义form表单的必填
const rules: Record<string, Rule[]> = {
name: [
{ required: true, message: '请输入能源种类', trigger: 'change' },
],
resource: [{ required: true, message: 'Please select activity resource', trigger: 'change' }],
};
// 定义计量单位级联选择的变量
const options: CascaderProps['options'] = [
{
value: 'zhejiang',
label: 'Zhejiang',
children: [
{
value: 'hangzhou',
label: 'Hangzhou',
children: [
{
value: 'xihu',
label: 'West Lake',
},
],
},
],
},
{
value: 'jiangsu',
label: 'Jiangsu',
children: [
{
value: 'nanjing',
label: 'Nanjing',
children: [
{
value: 'zhonghuamen',
label: 'Zhong Hua Men',
},
],
},
],
},
];
// 定义自动采集节点数的变量
const treeValue = ref<string>();
const treeData = ref<TreeSelectProps['treeData']>([
{
title: 'parent 1',
value: 'parent 1',
children: [
{
title: 'parent 1-0',
value: 'parent 1-0',
children: [
{
title: 'my leaf',
value: 'leaf1',
},
{
title: 'your leaf',
value: 'leaf2',
},
],
},
{
title: 'parent 1-1',
value: 'parent 1-1',
},
],
},
]);
// 获取表格数据
const getTableList = () => {
fetch(energyConsumption.pageList).then((res) => {
console.log(res,'aaaaaa');
});
};
getTableList()
// 点击确定提交
const onSubmit = () => {
formRef.value
.validate()
.then(() => {
console.log('values', formState, toRaw(formState));
})
.catch(error => {
console.log('error', error);
});
};
const resetForm = () => {
formRef.value.resetFields();
};
// 点击新增按钮
const addNewData = () => {
visible.value = true
}
// 关闭新增抽屉
const onClose = () => {
visible.value = false;
};
</script>
<style scoped lang="less">
::v-deep .ant-table-title{
display: flex;
}
::v-deep .ant-table-container{
padding: 0px 16px;
}
.buttonGroup{
margin-left: 1vw;
width: 30vw;
display: flex;
justify-content: space-around;
}
</style>
<style scoped>
th.column-money,
td.column-money {
text-align: right !important;
}
</style>

View File

@@ -0,0 +1,18 @@
<template>
<a-tabs v-model:activeKey="activeKey">
<a-tab-pane key="1" tab="能耗统计">
<energyConsumption ref="energyConsumptionRef" />
</a-tab-pane>
<a-tab-pane key="2" tab="碳排统计" force-render>Content of Tab Pane 2</a-tab-pane>
<a-tab-pane key="3" tab="碳排速算">Content of Tab Pane 3</a-tab-pane>
</a-tabs>
</template>
<script lang="ts" setup>
import { ref } from 'vue';
import energyConsumption from './energyConsumption/index.vue';
defineOptions({
name: 'CarbonEmissionStatisticsIndex', // 与页面路由name一致缓存才可生效
});
const activeKey = ref('1');
</script>

View File

@@ -0,0 +1,506 @@
{
"data":[
{
"title": "家居照明",
"key": "1",
"children": [
{
"title": "灯泡",
"key": "1-1",
"children": [
{
"title": "LED灯泡",
"key": "1-1-1",
"children": [
{
"title": "E27 LED灯泡",
"key": "1-1-1-1",
"attr": {
"瓦特": "7W",
"光通量": "500lm",
"色温": "2700K"
}
},
{
"title": "E14 小灯泡",
"key": "1-1-1-2",
"attr": {
"瓦特": "4W",
"光通量": "250lm",
"色温": "6500K"
}
}
]
},
{
"title": "节能灯",
"key": "1-1-2",
"children": [
{
"title": "E27 节能灯泡",
"key": "1-1-2-1",
"attr": {
"瓦特": "11W",
"光通量": "800lm",
"色温": "6500K"
}
}
]
}
]
},
{
"title": "灯具",
"key": "1-2",
"children": [
{
"title": "吊灯",
"key": "1-2-1",
"children": [
{
"title": "水晶吊灯",
"key": "1-2-1-1",
"attr": {
"尺寸": "Φ60cm",
"适用面积": "15-20㎡"
}
},
{
"title": "现代简约吊灯",
"key": "1-2-1-2",
"attr": {
"尺寸": "Φ52cm",
"适用面积": "10-15㎡"
}
}
]
},
{
"title": "台灯",
"key": "1-2-2",
"children": [
{
"title": "护眼台灯",
"key": "1-2-2-1",
"attr": {
"瓦特": "18W",
"调光调色": "是"
}
},
{
"title": "折叠臂台灯",
"key": "1-2-2-2",
"attr": {
"瓦特": "14W",
"调光调色": "否"
}
}
]
}
]
},
{
"title": "开关插座",
"key": "1-3",
"children": [
{
"title": "智能开关",
"key": "1-3-1",
"children": [
{
"title": "触控式智能开关",
"key": "1-3-1-1",
"attr": {
"控制方式": "触控/远程",
"兼容性": "ZigBee/WiFi"
}
}
]
},
{
"title": "插座",
"key": "1-3-2",
"children": [
{
"title": "多功能插座",
"key": "1-3-2-1",
"attr": {
"插孔类型": "2/3插",
"USB接口": "有"
}
}
]
}
]
}
]
},
{
"title": "电梯",
"key": "3",
"children": [
{
"title": "扶梯",
"key": "301"
},
{
"title": "直梯",
"key": "302"
}
]
},
{
"title": "冷源源",
"key": "4",
"children": [
{
"title": "通风及空调设备",
"key": "5",
"children": [
{
"title": "组合式空调机组",
"key": "501"
},
{
"title": "新风机组",
"key": "502"
},
{
"title": "精密空调",
"key": "503"
},
{
"title": "风机盘管",
"key": "504"
},
{
"title": "VAV",
"key": "505"
},
{
"title": "室外多联机",
"key": "506"
},
{
"title": "风幕机",
"key": "507"
},
{
"title": "球喷",
"key": "508"
},
{
"title": "送风机",
"key": "509"
},
{
"title": "排风机",
"key": "510"
},
{
"title": "排风兼排烟机",
"key": "511"
},
{
"title": "通风机",
"key": "512"
},
{
"title": "风阀",
"key": "513"
},
{
"title": "风柱式空调",
"key": "514"
}
]
}
]
},
{
"title": "照明",
"key": "6",
"children": [
{
"title": "多功能传感器",
"key": "701"
},
{
"title": "照度传感器",
"key": "702"
},
{
"title": "噪声传感器",
"key": "703"
}
]
}
],
"dataSource":[
{
"id": "d4",
"isDel": "0",
"officesId": "84",
"deviceCode": "37430200143",
"deviceName": "地听测试电表",
"category": "1",
"type": "1001",
"energyCount": "1",
"serialNumber": "69",
"pidCode": null,
"brand": "",
"types": "",
"manufacturer": "elit non in",
"contacts": "ad reprehenderit",
"phonenumber": "34",
"position": "in esse commodo",
"activeState": "1",
"measurementDirection": "1",
"deviceMagnification": 62,
"deviceAccuracy": "89",
"frequency": "anim consequat irure",
"standardFrequency": "ut elit",
"deviceHead": "pariatur ex velit",
"constructor": "84566",
"voltageType": "cillum aliquip reprehenderit",
"pt": 61,
"ct": 64,
"communicationProtocol": "cupidatat nisi ea ad",
"ip": "",
"port": "",
"com": "",
"slaveAddress": "",
"dlt": "",
"conversionIdentifier": "48",
"multiplicationAdjustment": "1",
"accessMethod": "1",
"replacementFrequency": "0",
"dataDetail": "sit",
"insertTime": null,
"children": null,
"devicePointList": null,
"insertUser": null
},
{
"id": "d1",
"isDel": "0",
"officesId": "84",
"deviceCode": "37430200144",
"deviceName": "地听测试2",
"category": "1",
"type": "1001",
"energyCount": "1",
"serialNumber": "69",
"pidCode": null,
"brand": "",
"types": "",
"manufacturer": "elit non in",
"contacts": "ad reprehenderit",
"phonenumber": "34",
"position": "in esse commodo",
"activeState": "1",
"measurementDirection": "1",
"deviceMagnification": 62,
"deviceAccuracy": "89",
"frequency": "anim consequat irure",
"standardFrequency": "ut elit",
"deviceHead": "pariatur ex velit",
"constructor": "84566",
"voltageType": "cillum aliquip reprehenderit",
"pt": 61,
"ct": 64,
"communicationProtocol": "802",
"ip": "10.5.36.0",
"port": "6000",
"com": "",
"slaveAddress": "123测试",
"dlt": "",
"conversionIdentifier": "48",
"multiplicationAdjustment": "1",
"accessMethod": "1",
"replacementFrequency": "0",
"dataDetail": "sit",
"insertTime": "2024-02-28 11:26:58",
"children": null,
"devicePointList": null,
"insertUser": null
},
{
"id": "d2",
"isDel": "0",
"officesId": "84",
"deviceCode": "1235623",
"deviceName": "测试设备2",
"category": "1",
"type": "1001",
"energyCount": "是",
"serialNumber": "69",
"pidCode": null,
"brand": "",
"types": "",
"manufacturer": "elit non in",
"contacts": "ad reprehenderit",
"phonenumber": "34",
"position": "in esse commodo",
"activeState": "1",
"measurementDirection": "1",
"deviceMagnification": 62,
"deviceAccuracy": "89",
"frequency": "anim consequat irure",
"standardFrequency": "ut elit",
"deviceHead": "pariatur ex velit",
"constructor": null,
"voltageType": "cillum aliquip reprehenderit",
"pt": 61,
"ct": 64,
"communicationProtocol": "cupidatat nisi ea ad",
"ip": "",
"port": "",
"com": "",
"slaveAddress": "",
"dlt": "",
"conversionIdentifier": "48",
"multiplicationAdjustment": "1",
"accessMethod": "1",
"replacementFrequency": "0",
"dataDetail": "sit",
"insertTime": "2024-02-28 11:31:57",
"children": null,
"devicePointList": null,
"insertUser": null
},
{
"id": "d7",
"isDel": "0",
"officesId": "",
"deviceCode": "0213",
"deviceName": "测试",
"category": "1",
"type": "1001",
"energyCount": "1",
"serialNumber": "",
"pidCode": null,
"brand": "6da085e5-956d-4000-bd3c-ebb01a9c99a1",
"types": "d7a8aede-b821-4ff2-953d-601a20e5a948",
"manufacturer": "",
"contacts": "",
"phonenumber": null,
"position": "",
"activeState": "",
"measurementDirection": "",
"deviceMagnification": null,
"deviceAccuracy": null,
"frequency": "",
"standardFrequency": "",
"deviceHead": "",
"constructor": "",
"voltageType": "",
"pt": null,
"ct": null,
"communicationProtocol": "",
"ip": "",
"port": "",
"com": "",
"slaveAddress": "",
"dlt": "",
"conversionIdentifier": "1",
"multiplicationAdjustment": "1",
"accessMethod": "",
"replacementFrequency": "0",
"dataDetail": "",
"insertTime": "2024-03-14 20:01:53",
"children": null,
"devicePointList": null,
"insertUser": ""
},
{
"id": "d3",
"isDel": "0",
"officesId": "84",
"deviceCode": "81",
"deviceName": "设备名称1111",
"category": "1",
"type": "1001",
"energyCount": "1",
"serialNumber": "69",
"pidCode": null,
"brand": "6da085e5-956d-4000-bd3c-ebb01a9c99a1",
"types": "d7a8aede-b821-4ff2-953d-601a20e5a948",
"manufacturer": "elit non in",
"contacts": "ad reprehenderit",
"phonenumber": "34",
"position": "in esse commodo",
"activeState": "1",
"measurementDirection": "1",
"deviceMagnification": 62,
"deviceAccuracy": "89",
"frequency": "anim consequat irure",
"standardFrequency": "ut elit",
"deviceHead": "pariatur ex velit",
"constructor": "84566",
"voltageType": "cillum aliquip reprehenderit",
"pt": 61,
"ct": 64,
"communicationProtocol": "cupidatat nisi ea ad",
"ip": "",
"port": "",
"com": "",
"slaveAddress": "",
"dlt": "",
"conversionIdentifier": "48",
"multiplicationAdjustment": "1",
"accessMethod": "1",
"replacementFrequency": "0",
"dataDetail": "sit",
"insertTime": "2024-03-15 17:34:24",
"children": null,
"devicePointList": null,
"insertUser": null
},
{
"id": "d43fdfff_02_0001",
"isDel": "0",
"officesId": "843fdffff213d2d3",
"deviceCode": "00037430200143",
"deviceName": "应感者酸严",
"category": "1",
"type": "1001",
"energyCount": "esse consequat",
"serialNumber": "69",
"pidCode": null,
"brand": "",
"types": "",
"manufacturer": "elit non in",
"contacts": "ad reprehenderit",
"phonenumber": "34",
"position": "in esse commodo",
"activeState": "1",
"measurementDirection": "1",
"deviceMagnification": 62,
"deviceAccuracy": "89",
"frequency": "anim consequat irure",
"standardFrequency": "ut elit",
"deviceHead": "pariatur ex velit",
"constructor": "84566",
"voltageType": "cillum aliquip reprehenderit",
"pt": 61,
"ct": 64,
"communicationProtocol": "cupidatat nisi ea ad",
"ip": "",
"port": "",
"com": "",
"slaveAddress": "",
"dlt": "",
"conversionIdentifier": "48",
"multiplicationAdjustment": "1",
"accessMethod": "1",
"replacementFrequency": "0",
"dataDetail": "sit",
"insertTime": "2024-04-29 11:12:43",
"children": null,
"devicePointList": null,
"insertUser": null
}
]
}

View File

@@ -0,0 +1,159 @@
// 页面容器
.lighting-box {
// width: 100%;
// height: 100%;
position: relative;
// background: linear-gradient(#badaff, #8cabeb, #7095de);
.drawer-box {
width: 30px;
height: 40px;
border-radius: 2px;
position: fixed;
right: 0;
top: 0;
bottom: 0;
margin: auto;
background: rgba(0, 0 ,0 ,0.5);
display: flex;
justify-content: center;
align-items: center;
cursor: pointer;
.drawer-icon {
}
}
// 照明设备功能总容器
.lighting-img-box {
position: relative;
width: 1280px;
height: 720px;
user-select: none;
background-image: url(../image/bg.jpg);
// 由于背景是俯视图,会产生有交点的透视效果,故使用透视属性
perspective: 1000px;
perspective-origin: 850px -160px;
// 左上角区域切换功能
.btn-box {
width: 120px;
position: sticky;
top: 10px;
left: 10px;
display: flex;
flex-direction: column;
gap: 8px;
.btn-item {
cursor: pointer;
width: 100%;
height: 40px;
border-radius: 4px;
background: rgba(39, 120, 255, 1);
border: 1px solid rgba(51, 199, 255, 1);
box-shadow: 0px 10px 15px rgba(0, 54, 136, 0.3);
font-size: 18px;
color: white;
}
.btn-item:hover {
color: black;
}
}
// 大区分区
.area{
position: absolute;
bottom: 170px;
left: 240px;
width: 780px;
height: 240px;
transform: rotateX(79deg) rotateZ(-22deg) skew(29deg);
display: flex;
gap: 8px;
.area1 {
width: 170px;
background: rgba(0, 251, 91, 0.3);
border: 2px solid rgb(0, 251, 91);
display: flex;
}
.area2 {
width: 240px;
background: rgba(255, 165, 0, 0.3);
border: 2px solid rgb(255, 165, 0);
display: flex;
}
.area3 {
width: 110px;
background: rgba(255, 0, 0, 0.3);
border: 2px solid rgb(255, 0, 0);
}
.area4 {
flex: 1;
background: rgba(80, 236, 244, 0.3);
border: 2px solid rgb(80, 236, 244);
}
.area-item:hover {
border:2px solid white;
}
.area-item {
cursor: pointer;
transition: all ease 0.2s;
>.light-group {
height: 100%;
flex: 1;
display:flex;
justify-content: center;
align-items: center;
.group-shadow1 {
width: 35px;
height: 150px;
border-radius: 20px;
background: rgba(0, 0, 0, 0.1);
}
.group-shadow2 {
width: 35px;
height: 180px;
border-radius: 20px;
background: rgba(0, 0, 0, 0.1);
}
.group-shadow3 {
width: 40px;
height: 180px;
border-radius: 20px;
background: rgba(0, 0, 0, 0.1);
}
.group-shadow4 {
width: 40px;
height: 160px;
border-radius: 20px;
background: rgba(0, 0, 0, 0.1);
}
}
.group-shadow {
transition: all ease 0.2s;
}
.group-shadow:hover {
border: 2px solid white;
}
}
}
}
}
.ns-content-main {
position: relative;
// 抽屉伸缩按钮
.drawer-box {
width: 30px;
height: 40px;
border-radius: 2px;
position: fixed;
right: 0;
top: 0;
bottom: 0;
margin: auto;
background: rgba(0, 0 ,0 ,0.5);
display: flex;
justify-content: center;
align-items: center;
cursor: pointer;
.drawer-icon {
}
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,135 @@
<template>
<a-popover color="rgba(0, 0, 0, 0.8)">
<template #content>
<div class="light-tag">
<div class="light-tag-tit">
<div>
<img src="/asset/image//bulbLogo/21962.png" alt="" />
<span class="tag-text">{{ result.name }}</span>
</div>
<button class="right-button">{{ type == 1 ? '正常' : '关闭' }}</button>
</div>
<div class="light-tag-box">
<div class="tag-box-item">
<img src="/asset/image//bulbLogo/22496.png" alt="" />
<span class="title">控制模式</span>
<span class="result">自动</span>
</div>
<div class="tag-box-item">
<img src="/asset/image//bulbLogo/22496.png" alt="" />
<span class="title">亮度</span>
<span class="result">{{ result.brightness }}</span>
</div>
<div class="tag-box-item">
<img src="/asset/image//bulbLogo/22496.png" alt="" />
<span class="title">控制场景</span>
<span class="result">{{ result.automatic2 }}</span>
</div>
<div class="tag-box-item">
<img src="/asset/image//bulbLogo/22496.png" alt="" />
<span class="title">色温</span>
<span class="result">{{ result.brightness3 }}</span>
</div>
</div>
</div>
</template>
<div class="icon-box" :style="styleObject">
<img v-if="type == 1" class="icon-item" src="/asset/image/bulbLogo/22394.png" alt="">
<img v-if="type == 2" class="icon-item" src="/asset/image/bulbLogo/22396.png" alt="">
<img v-if="type == 3" class="icon-item" src="/asset/image/bulbLogo/22400.png" alt="">
</div>
</a-popover>
</template>
<script setup lang="ts">
import { ref } from 'vue';
defineProps({
styleObject: Object,
type: Number
});
const result = ref({
id: '1',
name: '站厅照明 1区',
manual: '手动',
automatic: '自动',
brightness: '100lux',
brightness2: '30lux',
manual2: '手动',
automatic2: '舒适',
brightness3: '4000k',
brightness4: '3800k',
});
</script>
<style lang="less" scoped>
.icon-box {
width: 25px;
height: 25px;
position: absolute;
.icon-item {
width: 25px;
cursor: pointer;
transition: all ease 0.1s;
}
.icon-item:active {
transform: scale(1.2);
}
}
// 悬浮窗外部
.light-tag {
display: flex;
flex-direction: column;
gap: 10px;
// 悬浮窗标题
.light-tag-tit {
display: flex;
justify-content: space-between;
vertical-align: middle;
height: 30px;
.tag-text {
font-size: 16px;
font-weight: 700;
color: white;
padding-left: 10px;
}
.right-button {
width: 50px;
height: 26px;
background: rgba(57, 215, 187, 0.1);
border: 1px solid rgba(57, 215, 187, 1);
font-size: 12px;
color: rgba(57, 215, 187, 1);
}
}
}
// 悬浮窗底部4格子
.light-tag-box {
display: grid;
grid-template-columns: 1fr 1fr;
grid-template-rows: 1fr 1fr;
grid-gap: 10px;
.tag-box-item {
position: relative;
display: flex;
justify-content: space-between;
img {
width: 120px;
vertical-align: middle;
}
.title {
position: absolute;
left: 60px;
top: 13px;
color: rgba(64, 255, 252, 1);
}
.result {
line-height: 50px;
font-size: 16px;
font-weight: 700;
color: white;
};
}
}
</style>

View File

@@ -62,7 +62,12 @@ const mockData = ref(data.listData);
export const formSchema = [
{
field: 'isCreate',
component: 'NsInput',
component: 'NsCheck',
show: false,
},
{
field: 'isCreatSon',
component: 'NsCheck',
show: false,
},
{
@@ -71,7 +76,17 @@ export const formSchema = [
show: false,
},
{
field: 'isCreatSon',
field: 'energyType',
component: 'NsInput',
show: false,
},
{
field: 'id',
component: 'NsInput',
show: false,
},
{
field: 'pid',
component: 'NsInput',
show: false,
},
@@ -95,10 +110,10 @@ export const formSchema = [
component: 'NsSelectApi',
componentProps: {
placeholder: '请选择节点类型(必填)',
api: dict,
params: { dicKey: 'COUNT_POINT' },
api: () => dict({ params: { dicKey: 'COUNT_POINT' } }),
// params: { dicKey: 'COUNT_POINT' },
immediate: true,
resultField: 'data.COUNT_POINT',
// resultField: 'data.COUNT_POINT',
labelField: 'cnValue',
valueField: 'cnValue',
},
@@ -118,6 +133,7 @@ export const treeConfig = (orgId) => {
title: '能耗分组',
},
params: { orgId },
showLine: { showLeafIcon: false },
api: group.queryDeviceGroupTree,
// api: () => {
// return new Promise((resolve) => {
@@ -127,7 +143,7 @@ export const treeConfig = (orgId) => {
// });
// },
transform: (data) => {
return [{ title: '全部', key: 'all', selectable: false, children: data }];
return [{ pointName: '全部', id: 'all', selectable: false, children: data }];
},
formConfig: {
schemas: [
@@ -137,13 +153,14 @@ export const treeConfig = (orgId) => {
component: 'NsSelectApi',
autoSubmit: true,
componentProps: {
api: dict,
params: { dicKey: 'ENERGY_TYPE' },
api: () => dict({ params: { dicKey: 'ENERGY_TYPE' } }),
// params: { dicKey: 'ENERGY_TYPE' },
immediate: true,
resultField: 'data.ENERGY_TYPE',
// resultField: 'data.ENERGY_TYPE',
labelField: 'cnValue',
valueField: 'cnValue',
placeholder: '请选择能耗种类',
autoSelectFirst: true,
},
},
{

View File

@@ -10,20 +10,23 @@
<NsModalFrom ref="modalFormRef" v-bind="nsModalFormConfig" />
<div class="groupContainer">
<div class="tree">
<ns-tree-api v-bind="tConfig" @select="handleSelect">
<ns-tree-api ref="treeRef" v-bind="tConfig" @select="handleSelect">
<template #title="data">
<div class="treeRow">
<span>{{ data.title }}</span>
<div>
<ns-icon :name="data.pointType !== '计算节点' ? 'fenzujiedian' : 'jisuanjiedian'" />
<span style="padding-left: 8px">{{ data.pointName }}</span>
</div>
<a-dropdown>
<ns-icon name="actionMore" size="14" class="actionMore" />
<template #overlay>
<a-menu>
<a-menu-item
v-for="(item, index) in actionList"
:key="index"
@click="item.func(data)">
<span>{{ item.title }}</span>
</a-menu-item>
<template v-for="(item, index) in filterAction(actionList, data)" :key="index">
<!-- 全部节点只需要新增子节点 -->
<a-menu-item @click="item.func(data)">
<span>{{ item.title }}</span>
</a-menu-item>
</template>
</a-menu>
</template>
</a-dropdown>
@@ -45,6 +48,7 @@
import { NsMessage, NsModal } from '/nerv-lib/component';
import NsModalFrom from '/@/components/ns-modal-form.vue';
import { group } from '/@/api/deviceManage';
import { http } from '/nerv-lib/util/http';
type opType = 'up' | 'down';
const { getParams } = useParams();
@@ -52,6 +56,7 @@
const editDrawerRef = ref();
const editGroupRef = ref();
const editFormulaRef = ref();
const treeRef = ref();
const defaultType = ref(true);
const result = JSON.parse(sessionStorage.getItem('ORGID')!);
const config = tableConfig(editDrawerRef, editGroupRef, editFormulaRef);
@@ -65,6 +70,9 @@
extraModalConfig: {
bodyStyle: { paddingBottom: 0 },
},
success: () => {
treeRef.value?.treeReload();
},
});
nextTick(() => {
console.log(modalFormRef.value, 'modal');
@@ -72,36 +80,69 @@
const addNodeSon = (data) => {
console.log(data);
nsModalFormConfig.value.title = '新增';
nsModalFormConfig.value.data = {
pointName: '新增',
isCreate: true,
isCreatSon: false,
orgId: result,
energyType: data.formModel?.energyType,
};
if (data.id !== 'all') {
nsModalFormConfig.value.data.pid = data.id;
nsModalFormConfig.value.data.isCreatSon = true;
}
modalFormRef.value?.toggle();
};
const editNode = (data) => {
console.log(data);
nsModalFormConfig.value.title = '编辑';
nsModalFormConfig.value.data = { pointName: 123 };
setTimeout(() => {
nsModalFormConfig.value.data = data;
}, 1);
modalFormRef.value?.toggle();
data.value = { pointName: 'qwe' };
};
const moveNode = (data, type: opType) => {
console.log(data);
const flag = type === 'up';
http.post(group.move, { ...data, isUp: flag }).then(() => {
treeRef.value?.treeReload();
NsMessage.success('操作成功');
});
};
const deleteNode = (a) => {
const deleteNode = (record) => {
NsModal.confirm({
centered: true,
title: '提示',
content: '确定删除吗?',
onOk: () => {
console.log(a);
http.post(group.del, { id: record.id }).then(() => {
treeRef.value?.treeReload();
treeRef.value?.clearSelectedKeys();
NsMessage.success('操作成功');
});
},
});
};
const filterAction = (data) => {};
const filterAction = (actions, data) => {
let list: string | any[] = ['addNodeSon', 'editNode', 'deleteNode'];
//判断逻辑先后不可颠倒
// 最后一个以及中间的不会包含isFirst即支持上移
if (!data?.isFirst) {
list.push('moveUp');
}
// 第一个以及中间的不会包含isEnd即支持下移
if (!data?.isEnd) {
list.push('moveDown');
}
// 只有一条即不支持上移也不支持下移
if (data.isOnlyOne) {
list = ['addNodeSon', 'editNode', 'deleteNode'];
}
// 全部根节点
if (data.id === 'all') {
list = ['addNodeSon'];
}
return actions.filter(({ key }) => list.includes(key));
};
const actionList = [
{ title: '新增子节点', key: 'addNodeSon', func: (data) => addNodeSon(data) },
{ title: '编辑', key: 'editNode', func: (data) => editNode(data) },

View File

@@ -33,6 +33,7 @@ export const tableConfig = (orgId) => {
},
},
rowSelection: null,
scroll: { x: 2000 },
columns: [
{
title: '设备名称',
@@ -74,7 +75,7 @@ export const tableConfig = (orgId) => {
{
title: '设备厂商纳税人识别号',
dataIndex: 'deviceRatepay',
textNumber: 8,
textNumber: 9,
textEllipsis: true,
},
{

View File

@@ -0,0 +1,180 @@
import { dateUtil } from '/nerv-lib/util/date-util';
import data from './mock.json';
export const tableConfig = {
title: '设备台账',
// api: '/carbon_emission/device/getDeviceList',
value: data.dataSource,
treeConfig: {
header: {
icon: 'orgLink',
title: '设备类别',
},
defaultExpandAll: true,
api: () => {
return new Promise((resolve) => {
setTimeout(() => {
resolve(data);
}, 100);
});
},
formConfig: {
schemas: [
{
field: 'name',
label: '设备名称',
component: 'NsInput',
autoSubmit: true,
componentProps: {
placeholder: '请输入',
},
},
],
},
},
params: {
page: 0,
pageSize: 10,
},
rowSelection: null,
columns: [
{
title: '设备名称',
dataIndex: 'id',
},
{
title: '设备型号',
dataIndex: 'deviceCode',
},
{
title: 'SN码',
dataIndex: 'deviceName',
textNumber: 8,
textEllipsis: true,
},
{
title: '设备一级区域',
dataIndex: 'position',
},
{
title: '设备二级区域',
dataIndex: 'position',
},
{
title: '设备详细位置',
dataIndex: 'position',
},
{
title: '设备规格',
dataIndex: 'position',
},
{
title: '设备厂商纳税人识别号',
dataIndex: 'position',
},
{
title: '厂商联系人',
dataIndex: 'position',
},
{
title: '设备描述',
dataIndex: 'position',
},
{
title: 'IP地址',
dataIndex: 'position',
},
{
title: '生产日期',
dataIndex: 'position',
},
{
title: '采购日期',
dataIndex: 'position',
},
{
title: '启用日期',
dataIndex: 'position',
},
{
title: '设备成本(元)',
dataIndex: 'position',
},
{
title: '使用期限',
dataIndex: 'position',
},
{
title: '额定功率',
dataIndex: 'position',
},
{
title: '特殊参数',
dataIndex: 'position',
},
],
formConfig: {
schemas: [
{
field: 'name',
label: '设备名称',
component: 'NsInput',
componentProps: {
placeholder: '请输入',
},
},
{
field: 'provider',
label: '设备厂商',
component: 'NsInput',
componentProps: {
placeholder: '请输入',
},
},
{
field: 'payWay',
label: '设备区域',
component: 'NsSelect',
componentProps: {
placeholder: '请选择',
options: [
{
label: '全部',
value: '',
},
],
},
},
{
field: 'createTime',
label: '生产日期',
component: 'NsRangePicker',
fieldMap: ['queryStartDate', 'queryEndDate'],
componentProps: {
valueFormat: 'YYYY-MM-DD',
},
},
{
field: 'createTime1',
label: '采购日期',
component: 'NsRangePicker',
fieldMap: ['queryStartDate', 'queryEndDate'],
componentProps: {
valueFormat: 'YYYY-MM-DD',
},
},
{
field: 'createTime2',
label: '启用日期',
component: 'NsRangePicker',
fieldMap: ['queryStartDate', 'queryEndDate'],
componentProps: {
valueFormat: 'YYYY-MM-DD',
},
},
],
params: {},
},
// pagination: { pageSizeOptions: false },
rowKey: 'uuid',
};

View File

@@ -0,0 +1,136 @@
<template>
<div ref="graphChart" style="width: 100%; height: 80%"></div>
</template>
<script lang="ts">
import { defineComponent, onMounted, ref, watch } from 'vue';
import * as echarts from 'echarts';
import { inject } from 'vue';
export default defineComponent({
// eslint-disable-next-line vue/multi-word-component-names
name: 'Graph',
setup() {
let data = ref<any[]>([]);
interface PageData {
tableList: any[];
tableColumns: any[];
graphList: any[];
}
const pageData = inject<PageData>('pageData');
if (!pageData) {
throw new Error('pageData is not provided');
}
// 监听 pageData 的变化
watch(
() => pageData as PageData,
(_newValue, _oldValue) => {
// 执行你的逻辑代码
draw();
},
{ deep: true },
);
const graphChart = ref(null);
let chartInstance: echarts.ECharts | null = null;
const draw = () => {
data.value = pageData.graphList;
if (chartInstance) {
chartInstance.dispose();
}
chartInstance = echarts.init(graphChart.value);
var seriesList = [];
var date = [];
var legendList: string | any[] = [];
for (let i = 0; i < data.value.length; i++) {
date.push(data.value[i].date);
for (let j = 0; j < data.value[i].data.length; j++) {
if (seriesList.length < j + 1) {
seriesList.push({
name: data.value[i].data[j].name,
data: [data.value[i].data[j].value],
type: 'line',
smooth: true,
});
} else {
seriesList[j].data.push(data.value[i].data[j].value);
}
if (legendList.length == 0 || legendList.length < j + 1) {
legendList.push(data.value[i].data[j].name);
}
}
}
const option = {
legend: {
data: legendList,
orient: 'horizontal',
bottom: 30,
},
tooltip: {
trigger: 'axis',
formatter: (params: any) => {
const date = params[0].name;
const values = params
.map((param: any) => {
const unit = data.value.find((d) => d.date === date)?.unit || '';
return `<tr>
<td>${param.marker}${param.seriesName}</td>
<td style="text-align: right;">${param.value} ${unit}</td>
</tr>`;
})
.join('');
return `<div>${date}</div><table style="width: 100%;">${values}</table>`;
},
},
xAxis: {
type: 'category',
data: date,
},
yAxis: {
type: 'value',
},
dataZoom: [
{
height: 10,
start: 0,
end: 100,
handleSize: '300%', // 设置滑块的大小
bottom: 15,
},
],
series: seriesList,
};
chartInstance = echarts.init(graphChart.value);
chartInstance.setOption(option);
};
onMounted(() => {
draw();
});
// 下载图表
const downloadChart = () => {
if (chartInstance) {
const base64 = chartInstance.getDataURL({
type: 'png',
backgroundColor: '#fff',
});
const link = document.createElement('a');
link.href = base64;
link.download = 'chart.png';
link.click();
}
};
return {
draw,
graphChart,
downloadChart,
};
},
});
</script>
<style lang="less" scoped></style>

View File

@@ -0,0 +1,19 @@
<!-- eslint-disable vue/multi-word-component-names -->
<template>
<page />
</template>
<script setup>
import { reactive, provide } from 'vue';
import page from './page.vue';
// 创建一个响应式对象
const pageData = reactive({
tableList: [],
tableColumns: [],
graphList: [],
});
// 使用 provide 函数来提供这个响应式对象
provide('pageData', pageData);
</script>

View File

@@ -0,0 +1,81 @@
<!-- eslint-disable vue/multi-word-component-names -->
<template>
<a-row type="flex">
<a-col :span="4">
<div style="padding: 0 20px; width: 100%; height: 100%">
<tree ref="treeRef" />
</div>
</a-col>
<a-col :span="20">
<div style="width: 100%; height: 100%">
<div class="ns-right-title">
<span>历史数据</span>
<div class="button">
<ns-icon name="xiazai" size="18" style="margin-right: 10px" @click="downloadChart" />
<ns-icon :name="iconName" size="18" style="margin-right: 10px" @click="change" />
</div>
</div>
<graph ref="graphRef" v-if="isGraph" />
<environment-table ref="tableRef" v-else />
</div>
</a-col>
</a-row>
</template>
<script lang="ts" setup>
import { ref } from 'vue';
import tree from './tree/index.vue';
import graph from './graph/index.vue';
import environmentTable from './table/index.vue';
const iconName = ref('biaoge');
const treeRef = ref();
const graphRef = ref();
const tableRef = ref();
let isGraph = ref(true);
defineOptions({
name: 'EnvironmentMonitorIndex', // 与页面路由name一致缓存才可生效
});
const downloadChart = () => {
if (isGraph.value) {
if (graphRef.value) {
graphRef.value.downloadChart();
}
}
};
function change() {
isGraph.value = !isGraph.value;
if (iconName.value == 'biaoge') {
iconName.value = 'bingtu';
} else {
iconName.value = 'biaoge';
}
}
</script>
<style lang="less" scoped>
.ns-right-title {
display: flex;
justify-content: space-between;
align-items: center;
user-select: text;
margin-bottom: 5px;
padding-bottom: 10px;
padding-top: 10px;
border-bottom: 1px solid #e9e9e9;
> span {
padding-left: 10px;
line-height: 20px;
}
}
.button {
display: inline-block;
padding-right: 10px;
}
</style>

View File

@@ -0,0 +1,179 @@
<template>
<a-table :columns="columns" :data-source="data" bordered />
</template>
<script lang="ts">
import { defineComponent, watch, ref, onMounted } from 'vue';
import type { TableColumnType } from 'ant-design-vue';
import { inject } from 'vue';
// let data: any[] = [];
export default defineComponent({
name: 'EnvironmentTable',
setup() {
let data = ref<any[]>([]);
let columns = ref<TableColumnType[]>([]);
interface PageData {
tableList: any[];
tableColumns: any[];
graphList: any[];
}
const pageData = inject<PageData>('pageData');
if (!pageData) {
throw new Error('pageData is not provided');
}
// 监听 pageData 的变化
watch(
() => pageData as PageData,
(_newValue, _oldValue) => {
data.value = pageData.tableList;
let columnA: any[] = [...column];
columnA.push(...pageData.tableColumns);
columns.value = columnA;
// pageData.graphList;
// 执行你的逻辑代码
},
{ deep: true },
);
const getRowSpan = (dataIndex: string, record: any, data: any, dependents: string[] = []) => {
let rowSpan = 1;
for (let i = data.indexOf(record) + 1; i < data.length; i++) {
let shouldMerge = true;
for (const dependent of dependents) {
if (data[i][dependent] !== record[dependent]) {
shouldMerge = false;
break;
}
}
if (shouldMerge && data[i][dataIndex] === record[dataIndex]) {
rowSpan++;
} else {
break;
}
}
return rowSpan;
};
const column: TableColumnType[] = [
{
title: '序号',
dataIndex: 'key',
customCell: (record, rowIndex) => {
if (rowIndex == undefined) {
return {
rowSpan: 0,
colSpan: 0,
};
}
const rowSpan = getRowSpan('name', record, data.value);
if (rowIndex != 0 && data.value[rowIndex - 1].key == record.key) {
return {
rowSpan: 0,
colSpan: 0,
};
}
return {
rowSpan: rowSpan,
};
},
},
{
title: '设备名称',
dataIndex: 'name',
customCell: (record, rowIndex) => {
if (rowIndex == undefined) {
return {
rowSpan: 0,
colSpan: 0,
};
}
const rowSpan = getRowSpan('name', record, data.value);
if (rowIndex != 0 && data.value[rowIndex - 1].name == record.name) {
return {
rowSpan: 0,
colSpan: 0,
};
}
return {
rowSpan: rowSpan,
};
},
},
{
title: '设备点位',
dataIndex: 'position',
customCell: (record, rowIndex) => {
if (rowIndex == undefined) {
return {
rowSpan: 0,
colSpan: 0,
};
}
const rowSpan = getRowSpan('position', record, data.value, ['name']);
if (
rowIndex != 0 &&
data.value[rowIndex - 1].name == record.name &&
data.value[rowIndex - 1].position == record.position
) {
return {
rowSpan: 0,
colSpan: 0,
};
}
return {
rowSpan: rowSpan,
};
},
},
{
title: '计量单位',
dataIndex: 'unit',
customCell: (record, rowIndex) => {
if (rowIndex == undefined) {
return {
rowSpan: 0,
colSpan: 0,
};
}
const rowSpan = getRowSpan('unit', record, data.value, ['name', 'position']);
if (
rowIndex != 0 &&
data.value[rowIndex - 1].name == record.name &&
data.value[rowIndex - 1].position == record.position &&
data.value[rowIndex - 1].unit == record.unit
) {
return {
rowSpan: 0,
colSpan: 0,
};
}
return {
rowSpan: rowSpan,
};
},
},
];
onMounted(() => {
data.value = pageData.tableList;
let columnA: any[] = [...column];
columnA.push(...pageData.tableColumns);
columns.value = columnA;
});
return {
data,
column,
columns,
pageData,
};
},
});
</script>
<style lang="less" scoped></style>

View File

@@ -0,0 +1,532 @@
<!-- eslint-disable vue/v-on-event-hyphenation -->
<template>
<div class="parent-container">
<div class="ns-tree-title">
<span>设备列表</span>
</div>
<a-tree-select
v-model:value="value"
style="width: 100%"
:tree-line="treeLine && { showLeafIcon }"
:tree-data="treeData1"
@change="changeDeviceType" />
<a-spin :spinning="treeLoading">
<a-tree
v-model:expandedKeys="expandedKeys"
v-model:selectedKeys="selectedKeys"
v-model:checkedKeys="checkedKeys"
checkable
:height="340"
style="width: 100%; overflow-y: auto; margin-bottom: 10px; margin-top: 10px"
:tree-data="treeData2" />
</a-spin>
<div class="fixed-bottom">
<a-divider />
<a-select
ref="select"
v-model:value="selectedValue"
placeholder="请选择点位"
style="width: 100%; margin-bottom: 10px"
:options="options1" />
<a-select
v-model:value="frequencyValue"
placeholder="请选择频率"
style="width: 100%; margin-bottom: 10px"
:options="options2" />
<a-range-picker
:value="hackValue || dateRange"
:disabled-date="disabledDate"
@change="onChange"
@openChange="onOpenChange"
@calendarChange="onCalendarChange"
style="width: 100%; margin-bottom: 10px"
:placeholder="['请选择日期', '请选择日期']" />
<a-button type="primary" style="width: 100%; margin-bottom: 10px" @click="getSelect">
查询
</a-button>
<a-button type="primary" style="width: 100%; margin-bottom: 10px" @click="getSelect11">
模拟不同数据查询
</a-button>
</div>
</div>
</template>
<script lang="ts">
import type { TreeSelectProps, SelectProps } from 'ant-design-vue';
import { defineComponent, ref, onMounted } from 'vue';
import { Dayjs } from 'dayjs';
import { inject } from 'vue';
import { http } from '/nerv-lib/util';
import { device } from '/@/api/deviceManage';
export default defineComponent({
// eslint-disable-next-line vue/multi-word-component-names
name: 'Tree',
setup() {
const treeLoading = ref(false);
const treeLine = ref(true);
const showLeafIcon = ref(false);
const value = ref<string>();
const orgId = ref('');
const result = JSON.parse(sessionStorage.getItem('ORGID')!);
orgId.value = result;
const treeData1 = ref<TreeSelectProps['treeData']>([]);
http.post(device.queryDeviceTree, { orgId: orgId.value }).then((res) => {
treeData1.value = formatTreeData(res.data);
});
const formatTreeData = (data: any) => {
return data.map((item: any) => ({
title: item.code + '.' + item.deviceType,
value: item.code,
children: item.children ? formatTreeData(item.children) : [],
}));
};
const treeData2 = ref<TreeSelectProps['treeData']>([]);
const changeDeviceType = (val: any, label: any) => {
treeLoading.value = true;
http
.post(device.queryDevicePage, {
code: val,
orgId: orgId.value,
pageNum: 1,
pageSize: 1000,
})
.then((res) => {
if (!val) {
val = '999999999';
}
if (!label) {
label = ['所有设备'];
}
let records = res.data.records;
records.forEach((item: any) => {
(item.title = item.deviceModel + ' ' + item.deviceName), (item.key = item.id);
});
let a: TreeSelectProps['treeData'] = [{ title: label[0], key: val, children: records }];
treeData2.value = a;
expandedKeys.value = [val];
})
.finally(() => {
treeLoading.value = false;
});
};
const expandedKeys = ref<string[]>([]);
const selectedKeys = ref<string[]>([]);
const checkedKeys = ref<string[]>([]);
const options1 = ref<SelectProps['options']>([]);
const options2 = ref<SelectProps['options']>([
{
value: '1',
label: '5分钟',
},
{
value: '2',
label: '10分钟',
},
{
value: '3',
label: '30分钟',
},
{
value: '4',
label: '1小时',
},
]);
const selectedValue = ref<string | undefined>();
const frequencyValue = ref<string | undefined>();
const dateRange = ref<[Dayjs, Dayjs] | undefined>();
const getDianWeiList = () => {
console.log('getDianWeiList 被调用');
options1.value = [
{ value: '1', label: 'A 项电压' },
{ value: '2', label: 'B 项电压' },
{ value: '3', label: 'C 项电压' },
{ value: '4', label: 'AB 线电压' },
{ value: '5', label: 'BC 线电压' },
{ value: '6', label: 'A 项电流' },
{ value: '7', label: 'B 项电流' },
];
};
interface PageData {
tableList: any[];
tableColumns: any[];
graphList: any[];
}
const pageData = inject<PageData>('pageData');
if (!pageData) {
throw new Error('pageData is not provided');
}
// 查询数据后放入pageData
const getSelect = () => {
pageData.tableList = [
{
key: '1',
name: 'AC_002暖通电表',
position: 'A 相电压',
unit: 'V',
date: '2023-12-01',
'1:00': '3626',
},
{
key: '1',
name: 'AC_002暖通电表',
position: 'A 相电压',
unit: 'V',
date: '2023-12-01',
'1:00': '3626',
},
{
key: '2',
name: 'AC_003照明电表',
position: 'A 相电压',
unit: 'V',
date: '2023-12-01',
'1:00': '3626',
},
{
key: '2',
name: 'AC_003照明电表',
position: 'A 相电压',
unit: 'V',
date: '2023-12-01',
'1:00': '3626',
},
{
key: '3',
name: 'AC_004给排水电表',
position: 'A 相电压',
unit: 'V',
date: '2023-12-01',
'1:00': '3626',
},
];
pageData.tableColumns = [
{
title: '日期',
dataIndex: 'date',
},
{
title: '1:00',
dataIndex: '1:00',
},
];
pageData.graphList = [
{
date: '2023-12-01 0:00',
unit: 'V',
data: [
{
name: 'AC_002暖通电表',
value: '21',
},
{
name: 'AC_003照明电表',
value: '36',
},
{
name: 'AC_004给排水电表',
value: '5',
},
],
},
{
date: '2023-12-02 0:00',
unit: 'V',
data: [
{
name: 'AC_002暖通电表',
value: '26',
},
{
name: 'AC_003照明电表',
value: '25',
},
{
name: 'AC_004给排水电表',
value: '47',
},
],
},
{
date: '2023-12-03 0:00',
unit: 'V',
data: [
{
name: 'AC_002暖通电表',
value: '18',
},
{
name: 'AC_003照明电表',
value: '22',
},
{
name: 'AC_004给排水电表',
value: '26',
},
],
},
{
date: '2023-12-04 0:00',
unit: 'V',
data: [
{
name: 'AC_002暖通电表',
value: '40',
},
{
name: 'AC_003照明电表',
value: '15',
},
{
name: 'AC_004给排水电表',
value: '12',
},
],
},
{
date: '2023-12-05 0:00',
unit: 'V',
data: [
{
name: 'AC_002暖通电表',
value: '15',
},
{
name: 'AC_003照明电表',
value: '18',
},
{
name: 'AC_004给排水电表',
value: '15',
},
],
},
{
date: '2023-12-06 0:00',
unit: 'V',
data: [
{
name: 'AC_002暖通电表',
value: '15',
},
{
name: 'AC_003照明电表',
value: '18',
},
{
name: 'AC_004给排水电表',
value: '15',
},
],
},
];
};
// 测试查询 模拟不同数据
const getSelect11 = () => {
pageData.tableList = [
{
key: '1',
name: 'AC_002暖通电表',
position: 'A 相电压',
unit: 'V',
date: '2023-12-01',
'1:00': '3626',
'2:00': '2222',
},
{
key: '1',
name: 'AC_002暖通电表',
position: 'A 相电压',
unit: 'V',
date: '2023-12-01',
'1:00': '3626',
'2:00': '2222',
},
{
key: '2',
name: 'AC_003照明电表',
position: 'A 相电压',
unit: 'V',
date: '2023-12-01',
'1:00': '3626',
'2:00': '2222',
},
{
key: '2',
name: 'AC_003照明电表',
position: 'A 相电压',
unit: 'V',
date: '2023-12-01',
'1:00': '3626',
'2:00': '2222',
},
{
key: '3',
name: 'AC_004给排水电表',
position: 'A 相电压',
unit: 'V',
date: '2023-12-01',
'1:00': '3626',
'2:00': '2222',
},
];
pageData.tableColumns = [
{
title: '日期',
dataIndex: 'date',
},
{
title: '1:00',
dataIndex: '1:00',
},
{
title: '2:00',
dataIndex: '2:00',
},
];
pageData.graphList = [
{
date: '2023-12-01 0:00',
unit: 'V',
data: [
{
name: 'AC_002暖通电表',
value: '21',
},
{
name: 'AC_003照明电表',
value: '36',
},
{
name: 'AC_004给排水电表',
value: '5',
},
{
name: 'AC_0051111111',
value: '14',
},
],
},
{
date: '2023-12-02 0:00',
unit: 'V',
data: [
{
name: 'AC_002暖通电表',
value: '26',
},
{
name: 'AC_003照明电表',
value: '25',
},
{
name: 'AC_004给排水电表',
value: '47',
},
{
name: 'AC_0051111111',
value: '28',
},
],
},
];
};
type RangeValue = [Dayjs, Dayjs];
const dates = ref<RangeValue>();
const hackValue = ref<RangeValue>();
const onChange = (val: RangeValue) => {
dateRange.value = val;
};
const onOpenChange = (open: boolean) => {
if (open) {
dates.value = [] as any;
hackValue.value = [] as any;
} else {
hackValue.value = undefined;
}
};
const disabledDate = (current: Dayjs) => {
if (!dates.value || (dates.value as any).length === 0) {
return false;
}
const tooLate = dates.value[0] && current.diff(dates.value[0], 'days') > 2;
const tooEarly = dates.value[1] && dates.value[1].diff(current, 'days') > 2;
return tooEarly || tooLate;
};
const onCalendarChange = (val: RangeValue) => {
dates.value = val;
};
onMounted(() => {
getDianWeiList();
changeDeviceType(null, null);
});
return {
treeLine,
showLeafIcon,
value,
treeData1,
treeData2,
expandedKeys,
selectedKeys,
checkedKeys,
options1,
options2,
selectedValue,
frequencyValue,
dateRange,
getDianWeiList,
getSelect,
getSelect11,
disabledDate,
onCalendarChange,
onOpenChange,
onChange,
hackValue,
pageData,
changeDeviceType,
treeLoading,
};
},
});
</script>
<style lang="less" scoped>
.ns-tree-title {
user-select: text;
margin-bottom: 5px;
padding-bottom: 10px;
padding-top: 10px;
border-bottom: 1px solid #e9e9e9;
> span {
padding-left: 10px;
line-height: 20px;
}
}
.parent-container {
position: relative;
height: 100%;
}
.fixed-bottom {
position: absolute;
bottom: 0;
width: 100%;
margin-bottom: 10px;
}
</style>

View File

@@ -0,0 +1,126 @@
<template>
<div ref="graphGraphchart" style="width: 100%; height: 80%"></div>
</template>
<script lang="ts">
import { defineComponent, onMounted, ref, inject, watch } from 'vue';
import * as echarts from 'echarts';
export default defineComponent({
name: 'GraphGraph',
setup() {
let data = ref<any[]>([]);
interface PageData {
graphTableList: any[];
graphTableColumns: any[];
graphGraphList: any[];
}
const pageData = inject<PageData>('pageData');
if (!pageData) {
throw new Error('pageData is not provided');
}
// 监听 pageData 的变化
watch(
() => pageData as PageData,
(_newValue, _oldValue) => {
// 执行你的逻辑代码
draw();
},
{ deep: true },
);
const graphGraphchart = ref(null);
let chartInstance: echarts.ECharts | null = null;
const draw = () => {
data.value = pageData.graphGraphList;
if (chartInstance) {
chartInstance.dispose();
}
chartInstance = echarts.init(graphGraphchart.value);
var seriesList = [];
var date = [];
var legendList: string | any[] = [];
for (let i = 0; i < data.value.length; i++) {
date.push(data.value[i].date);
for (let j = 0; j < data.value[i].data.length; j++) {
if (seriesList.length < j + 1) {
seriesList.push({
name: data.value[i].data[j].name,
data: [data.value[i].data[j].value],
type: 'line',
smooth: true,
});
} else {
seriesList[j].data.push(data.value[i].data[j].value);
}
if (legendList.length == 0 || legendList.length < j + 1) {
legendList.push(data.value[i].data[j].name);
}
}
}
const option = {
legend: {
data: legendList,
orient: 'horizontal',
bottom: 0,
left: 60,
},
tooltip: {
trigger: 'axis',
formatter: (params: any) => {
const date = params[0].name;
const values = params
.map((param: any) => {
const unit = data.value.find((d) => d.date === date)?.unit || '';
return `<tr>
<td>${param.marker}${param.seriesName}</td>
<td style="text-align: right;">${param.value} ${unit}</td>
</tr>`;
})
.join('');
return `<div>${date}</div><table style="width: 100%;">${values}</table>`;
},
},
xAxis: {
type: 'category',
data: date,
},
yAxis: {
type: 'value',
},
series: seriesList,
};
chartInstance = echarts.init(graphGraphchart.value);
chartInstance.setOption(option);
};
onMounted(() => {
draw();
});
// 下载图表
const downloadChart = () => {
if (chartInstance) {
const base64 = chartInstance.getDataURL({
type: 'png',
backgroundColor: '#fff',
});
const link = document.createElement('a');
link.href = base64;
link.download = 'chart.png';
link.click();
}
};
return {
graphGraphchart,
downloadChart,
};
},
});
</script>
<style lang="less" scoped></style>

View File

@@ -0,0 +1,122 @@
<template>
<a-table :columns="columns" :data-source="data" bordered />
</template>
<script lang="ts">
import { defineComponent, watch, inject, ref, onMounted } from 'vue';
import type { TableColumnType } from 'ant-design-vue';
export default defineComponent({
name: 'EnvironmentTable',
setup() {
let data = ref<any[]>([]);
let columns = ref<TableColumnType[]>([]);
interface PageData {
graphTableList: any[];
graphTableColumns: any[];
graphGraphList: any[];
}
const pageData = inject<PageData>('pageData');
if (!pageData) {
throw new Error('pageData is not provided');
}
// 监听 pageData 的变化
watch(
() => pageData as PageData,
(_newValue, _oldValue) => {
data.value = pageData.graphTableList;
let columnA: any[] = [...column];
columnA.push(...pageData.graphTableColumns);
columns.value = columnA;
// pageData.graphList;
// 执行你的逻辑代码
},
{ deep: true },
);
const getRowSpan = (dataIndex: string, record: any, data: any, dependents: string[] = []) => {
let rowSpan = 1;
for (let i = data.indexOf(record) + 1; i < data.length; i++) {
let shouldMerge = true;
for (const dependent of dependents) {
if (data[i][dependent] !== record[dependent]) {
shouldMerge = false;
break;
}
}
if (shouldMerge && data[i][dataIndex] === record[dataIndex]) {
rowSpan++;
} else {
break;
}
}
return rowSpan;
};
const column: TableColumnType[] = [
{
title: '设备/组名',
dataIndex: 'name',
customCell: (record, rowIndex) => {
if (rowIndex == undefined) {
return {
rowSpan: 0,
colSpan: 0,
};
}
const rowSpan = getRowSpan('name', record, data.value);
if (rowIndex != 0 && data.value[rowIndex - 1].name == record.name) {
return {
rowSpan: 0,
colSpan: 0,
};
}
return {
rowSpan: rowSpan,
};
},
},
{
title: '参数名称',
dataIndex: 'paramName',
customCell: (record, rowIndex) => {
if (rowIndex == undefined) {
return {
rowSpan: 0,
colSpan: 0,
};
}
const rowSpan = getRowSpan('paramName', record, data.value, ['name']);
if (rowIndex != 0 && data.value[rowIndex - 1].name == record.name) {
return {
rowSpan: 0,
colSpan: 0,
};
}
return {
rowSpan: rowSpan,
};
},
},
];
onMounted(() => {
data.value = pageData.graphTableList;
let columnA: any[] = [...column];
columnA.push(...pageData.graphTableColumns);
columns.value = columnA;
});
return {
data,
column,
columns,
};
},
});
</script>
<style lang="less" scoped></style>

View File

@@ -0,0 +1,19 @@
<!-- eslint-disable vue/multi-word-component-names -->
<template>
<page />
</template>
<script setup>
import { reactive, provide } from 'vue';
import page from './page.vue';
// 创建一个响应式对象
const pageData = reactive({
graphTableList: [],
graphTableColumns: [],
graphGraphList: [],
});
// 使用 provide 函数来提供这个响应式对象
provide('pageData', pageData);
</script>

View File

@@ -0,0 +1,88 @@
<!-- eslint-disable vue/multi-word-component-names -->
<template>
<a-tabs v-model:activeKey="activeKey" style="height: 8%">
<a-tab-pane key="1" tab="图表" />
<a-tab-pane key="2" tab="分析" />
</a-tabs>
<a-row type="flex" style="height: 92%">
<a-col :span="4">
<div style="padding: 0 20px; width: 100%; height: 100%">
<tree ref="treeRef" />
</div>
</a-col>
<a-col :span="20">
<div style="width: 100%; height: 100%">
<div class="ns-right-title">
<span>统计数据</span>
<div class="button">
<ns-icon name="xiazai" size="18" style="margin-right: 10px" @click="downloadChart" />
<ns-icon :name="iconName" size="18" style="margin-right: 10px" @click="change" />
</div>
</div>
<div v-if="activeKey == '1'" style="height: 90%">
<graph-graph ref="graphRef" v-if="isGraph" />
<environment-table ref="tableRef" v-else />
</div>
</div>
</a-col>
</a-row>
</template>
<script lang="ts" setup>
import { ref } from 'vue';
import tree from './tree/index.vue';
import graphGraph from './graphGraph/index.vue';
import environmentTable from './graphTable/index.vue';
const iconName = ref('biaoge');
const treeRef = ref();
const graphRef = ref();
const tableRef = ref();
let isGraph = ref(true);
const activeKey = ref('1');
defineOptions({
name: 'EnvironmentMonitorIndex', // 与页面路由name一致缓存才可生效
});
const downloadChart = () => {
if (activeKey.value == '1' && isGraph) {
if (graphRef.value) {
graphRef.value.downloadChart();
}
}
};
function change() {
isGraph.value = !isGraph.value;
if (iconName.value == 'biaoge') {
iconName.value = 'bingtu';
} else {
iconName.value = 'biaoge';
}
}
</script>
<style lang="less" scoped>
.ns-right-title {
display: flex;
justify-content: space-between;
align-items: center;
user-select: text;
margin-bottom: 5px;
padding-bottom: 10px;
padding-top: 10px;
border-bottom: 1px solid #e9e9e9;
> span {
padding-left: 10px;
line-height: 20px;
}
}
.button {
display: inline-block;
padding-right: 10px;
}
</style>

View File

@@ -0,0 +1,166 @@
<template>
<a-table :columns="columns" :data-source="data" bordered />
</template>
<script lang="ts">
import { defineComponent } from 'vue';
import type { TableColumnType } from 'ant-design-vue';
const data = [
{
key: '1',
name: 'AC_002暖通电表',
position: 'A 相电压',
unit: 'V',
date: '2023-12-01',
'1:00': '3626',
},
{
key: '1',
name: 'AC_002暖通电表',
position: 'A 相电压',
unit: 'V',
date: '2023-12-01',
'1:00': '3626',
},
{
key: '2',
name: 'AC_003照明电表',
position: 'A 相电压',
unit: 'V',
date: '2023-12-01',
'1:00': '3626',
},
{
key: '2',
name: 'AC_003照明电表',
position: 'A 相电压',
unit: 'V',
date: '2023-12-01',
'1:00': '3626',
},
{
key: '3',
name: 'AC_004给排水电表',
position: 'A 相电压',
unit: 'V',
date: '2023-12-01',
'1:00': '3626',
},
];
export default defineComponent({
name: 'EnvironmentTable',
setup() {
const getRowSpan = (dataIndex: string, record, data, dependents: string[] = []) => {
let rowSpan = 1;
for (let i = data.indexOf(record) + 1; i < data.length; i++) {
let shouldMerge = true;
for (const dependent of dependents) {
if (data[i][dependent] !== record[dependent]) {
shouldMerge = false;
break;
}
}
if (shouldMerge && data[i][dataIndex] === record[dataIndex]) {
rowSpan++;
} else {
break;
}
}
return rowSpan;
};
const columns: TableColumnType[] = [
{
title: '序号',
dataIndex: 'key',
customCell: (record, rowIndex) => {
const rowSpan = getRowSpan('name', record, data);
if (rowIndex != 0 && data[rowIndex - 1].key == record.key) {
return {
rowSpan: 0,
colSpan: 0,
};
}
return {
rowSpan: rowSpan,
};
},
},
{
title: '设备名称',
dataIndex: 'name',
customCell: (record, rowIndex) => {
const rowSpan = getRowSpan('name', record, data);
if (rowIndex != 0 && data[rowIndex - 1].name == record.name) {
return {
rowSpan: 0,
colSpan: 0,
};
}
return {
rowSpan: rowSpan,
};
},
},
{
title: '设备点位',
dataIndex: 'position',
customCell: (record, rowIndex) => {
const rowSpan = getRowSpan('position', record, data, ['name']);
if (
rowIndex != 0 &&
data[rowIndex - 1].name == record.name &&
data[rowIndex - 1].position == record.position
) {
return {
rowSpan: 0,
colSpan: 0,
};
}
return {
rowSpan: rowSpan,
};
},
},
{
title: '计量单位',
dataIndex: 'unit',
customCell: (record, rowIndex) => {
const rowSpan = getRowSpan('unit', record, data, ['name', 'position']);
if (
rowIndex != 0 &&
data[rowIndex - 1].name == record.name &&
data[rowIndex - 1].position == record.position &&
data[rowIndex - 1].unit == record.unit
) {
return {
rowSpan: 0,
colSpan: 0,
};
}
return {
rowSpan: rowSpan,
};
},
},
{
title: '日期',
dataIndex: 'date',
},
{
title: '1:00',
dataIndex: '1:00',
},
];
return {
data,
columns,
};
},
});
</script>
<style lang="less" scoped></style>

View File

@@ -0,0 +1,620 @@
<!-- eslint-disable vue/v-on-event-hyphenation -->
<template>
<div class="parent-container">
<div class="ns-tree-title">
<span>数据点位</span>
</div>
<a-select
ref="select"
v-model:value="selectedValue"
placeholder="请选择能耗类型"
style="width: 100%; margin-bottom: 10px"
:options="options1"
@change="changeEnergyType" />
<a-radio-group
v-model:value="mode"
@change="changeMode"
style="padding-bottom: 10px; width: 100%">
<a-radio-button value="1" style="width: 50%; text-align: center" :disabled="shebei"
>设备</a-radio-button
>
<a-radio-button value="2" style="width: 50%; text-align: center">节点</a-radio-button>
</a-radio-group>
<a-input
v-model:value="deviceName"
placeholder="请输入设备名称"
v-if="mode == '1'"
@change="changeMode" />
<a-input v-model:value="pointName" placeholder="请输入节点名称" v-else @change="changeMode" />
<a-spin :spinning="treeLoading">
<a-tree
v-model:expandedKeys="expandedKeys"
v-model:selectedKeys="selectedKeys"
v-model:checkedKeys="checkedKeys"
checkable
:height="300"
style="width: 100%; overflow-y: auto; margin-bottom: 10px; margin-top: 10px"
:tree-data="treeData2" />
</a-spin>
<div class="fixed-bottom">
<a-divider />
<a-select
v-model:value="frequencyValue"
placeholder="请选择采集频率"
style="width: 100%; margin-bottom: 10px"
:options="options2" />
<a-range-picker
:value="hackValue || dateRange"
:disabled-date="disabledDate"
@change="onChange"
@openChange="onOpenChange"
@calendarChange="onCalendarChange"
style="width: 100%; margin-bottom: 10px"
:placeholder="['请选择日期', '请选择日期']" />
<a-button type="primary" style="width: 100%; margin-bottom: 10px" @click="getSelect">
查询
</a-button>
<!-- <a-button type="primary" style="width: 100%; margin-bottom: 10px" @click="getSelect11">
模拟不同数据查询
</a-button> -->
</div>
</div>
</template>
<script lang="ts">
import type { TreeSelectProps, SelectProps } from 'ant-design-vue';
import { defineComponent, ref, onMounted, inject } from 'vue';
import { Dayjs } from 'dayjs';
import { http } from '/nerv-lib/util';
import { device, group } from '/@/api/deviceManage';
import { dict } from '/@/api';
export default defineComponent({
// eslint-disable-next-line vue/multi-word-component-names
name: 'Tree',
setup() {
const value = ref<string>();
const deviceName = ref<string>();
const pointName = ref<string>();
const treeLoading = ref(false);
const shebei = ref(false);
// 树打开节点
const expandedKeys = ref<string[]>([]);
// 树勾选节点
const selectedKeys = ref<string[]>([]);
// 树点击节点
const checkedKeys = ref<string[]>([]);
// 能耗类型list
const options1 = ref<SelectProps['options']>([]);
// 采集频率list
const options2 = ref<SelectProps['options']>([]);
const mode = ref<String>('1');
// 能耗类型
const selectedValue = ref<string | undefined>();
// 采集频率
const frequencyValue = ref<string | undefined>();
// 时间
const dateRange = ref<[Dayjs, Dayjs] | undefined>();
// 页面初始化参数
const getOptionsList = async () => {
try {
const options = await dict({ params: { dicKey: 'ENERGY_TYPE' } });
options.data.data.forEach((item: any) => {
(item.value = item.cnValue), (item.label = item.cnValue);
});
options1.value = options.data.data; // 根据返回数据的结构来更新 options1.value
} catch (error) {
console.error('Failed to fetch options:', error);
}
options2.value = [
{
value: '1',
label: '5分钟',
},
{
value: '2',
label: '10分钟',
},
{
value: '3',
label: '30分钟',
},
{
value: '4',
label: '1小时',
},
];
};
interface PageData {
graphTableList: any[];
graphTableColumns: any[];
graphGraphList: any[];
}
const pageData = inject<PageData>('pageData');
if (!pageData) {
throw new Error('pageData is not provided');
}
// 选择能耗类型
const changeEnergyType = () => {
if (selectedValue.value == '8') {
shebei.value = true;
mode.value = '2';
} else {
shebei.value = false;
}
changeMode();
};
const getSelect = () => {
pageData.graphTableList = [
{
key: '1',
name: 'AC_002暖通电表',
paramName: '用电量(kWh)',
date: '2023-12-01',
'1:00': '3626',
},
{
key: '1',
name: 'AC_002暖通电表',
paramName: '用电量(kWh)',
date: '2023-12-01',
'1:00': '3626',
},
{
key: '2',
name: 'AC_003照明电表',
paramName: '用电量(kWh)',
date: '2023-12-01',
'1:00': '3626',
},
{
key: '2',
name: 'AC_003照明电表',
paramName: '用电量(kWh)',
date: '2023-12-01',
'1:00': '3626',
},
{
key: '3',
name: 'AC_004给排水电表',
paramName: '用电量(kWh)',
date: '2023-12-01',
'1:00': '3626',
},
];
pageData.graphTableColumns = [
{
title: '日期',
dataIndex: 'date',
},
{
title: '1:00',
dataIndex: '1:00',
},
];
pageData.graphGraphList = [
{
date: '2023-12-01 0:00',
unit: 'V',
data: [
{
name: 'AC_002暖通电表',
value: '21',
},
{
name: 'AC_003照明电表',
value: '36',
},
{
name: 'AC_004给排水电表',
value: '5',
},
],
},
{
date: '2023-12-02 0:00',
unit: 'V',
data: [
{
name: 'AC_002暖通电表',
value: '26',
},
{
name: 'AC_003照明电表',
value: '25',
},
{
name: 'AC_004给排水电表',
value: '47',
},
],
},
{
date: '2023-12-03 0:00',
unit: 'V',
data: [
{
name: 'AC_002暖通电表',
value: '18',
},
{
name: 'AC_003照明电表',
value: '22',
},
{
name: 'AC_004给排水电表',
value: '26',
},
],
},
{
date: '2023-12-04 0:00',
unit: 'V',
data: [
{
name: 'AC_002暖通电表',
value: '40',
},
{
name: 'AC_003照明电表',
value: '15',
},
{
name: 'AC_004给排水电表',
value: '12',
},
],
},
{
date: '2023-12-05 0:00',
unit: 'V',
data: [
{
name: 'AC_002暖通电表',
value: '15',
},
{
name: 'AC_003照明电表',
value: '18',
},
{
name: 'AC_004给排水电表',
value: '15',
},
],
},
];
};
const getSelect11 = () => {
pageData.graphTableList = [
{
key: '1',
name: 'AC_002暖通电表',
paramName: '用电量(kWh)',
date: '2023-12-01',
'1:00': '3626',
'2:00': '3626',
},
{
key: '1',
name: 'AC_002暖通电表',
paramName: '用电量(kWh)',
date: '2023-12-01',
'1:00': '3626',
'2:00': '3626',
},
{
key: '2',
name: 'AC_003照明电表',
paramName: '用电量(kWh)',
date: '2023-12-01',
'1:00': '3626',
'2:00': '3626',
},
{
key: '2',
name: 'AC_003照明电表',
paramName: '用电量(kWh)',
date: '2023-12-01',
'1:00': '3626',
'2:00': '3626',
},
{
key: '3',
name: 'AC_004给排水电表',
paramName: '用电量(kWh)',
date: '2023-12-01',
'1:00': '3626',
'2:00': '3626',
},
];
pageData.graphTableColumns = [
{
title: '日期',
dataIndex: 'date',
},
{
title: '1:00',
dataIndex: '1:00',
},
{
title: '2:00',
dataIndex: '2:00',
},
];
pageData.graphGraphList = [
{
date: '2023-12-01 0:00',
unit: 'V',
data: [
{
name: 'AC_002暖通电表',
value: '21',
},
{
name: 'AC_003照明电表',
value: '36',
},
{
name: 'AC_004给排水电表',
value: '5',
},
{
name: 'AC_005333333333',
value: '22',
},
],
},
{
date: '2023-12-02 0:00',
unit: 'V',
data: [
{
name: 'AC_002暖通电表',
value: '26',
},
{
name: 'AC_003照明电表',
value: '25',
},
{
name: 'AC_004给排水电表',
value: '47',
},
{
name: 'AC_005333333333',
value: '28',
},
],
},
{
date: '2023-12-03 0:00',
unit: 'V',
data: [
{
name: 'AC_002暖通电表',
value: '18',
},
{
name: 'AC_003照明电表',
value: '22',
},
{
name: 'AC_004给排水电表',
value: '26',
},
{
name: 'AC_005333333333',
value: '44',
},
],
},
{
date: '2023-12-04 0:00',
unit: 'V',
data: [
{
name: 'AC_002暖通电表',
value: '40',
},
{
name: 'AC_003照明电表',
value: '15',
},
{
name: 'AC_004给排水电表',
value: '12',
},
{
name: 'AC_005333333333',
value: '26',
},
],
},
{
date: '2023-12-05 0:00',
unit: 'V',
data: [
{
name: 'AC_002暖通电表',
value: '15',
},
{
name: 'AC_003照明电表',
value: '18',
},
{
name: 'AC_004给排水电表',
value: '15',
},
{
name: 'AC_005333333333',
value: '35',
},
],
},
];
};
const orgId = ref('');
const result = JSON.parse(sessionStorage.getItem('ORGID')!);
orgId.value = result;
const treeData2 = ref<TreeSelectProps['treeData']>([]);
// 切换树结构
const changeMode = () => {
treeLoading.value = true;
if (mode.value == '1') {
http
.post(device.queryDevicePage, {
deviceName: deviceName.value,
energyType: selectedValue.value,
orgId: orgId.value,
pageNum: 1,
pageSize: 9999,
})
.then((res) => {
let val = '999999999';
let label = '所有设备';
let records = res.data.records;
records.forEach((item: any) => {
(item.title = item.deviceModel + ' ' + item.deviceName), (item.key = item.id);
});
let a: TreeSelectProps['treeData'] = [{ title: label, key: val, children: records }];
treeData2.value = a;
expandedKeys.value = [val];
})
.finally(() => {
treeLoading.value = false;
});
} else {
http
.post(group.queryDeviceGroupTree, {
pointName: pointName.value,
energyType: selectedValue.value,
orgId: orgId.value,
})
.then((res) => {
treeData2.value = [];
treeData2.value = formatTreeData(pointName.value ? res.data[0].searchList : res.data);
expandedKeys.value = getAllKeys(treeData2.value);
})
.finally(() => {
treeLoading.value = false;
});
}
};
const getAllKeys = (data: any) => {
let keys: any[] = [];
data.forEach((item: any) => {
if (item.children) {
keys.push(item.key);
keys = keys.concat(getAllKeys(item.children));
}
});
return keys;
};
const formatTreeData = (data: any) => {
return data.map((item: any) => ({
title: item.pointName,
key: item.id,
children: item.children ? formatTreeData(item.children) : [],
}));
};
type RangeValue = [Dayjs, Dayjs];
const dates = ref<RangeValue>();
const hackValue = ref<RangeValue>();
const disabledDate = (current: Dayjs) => {
if (!dates.value || (dates.value as any).length === 0) {
return false;
}
const tooLate = dates.value[0] && current.diff(dates.value[0], 'days') > 2;
const tooEarly = dates.value[1] && dates.value[1].diff(current, 'days') > 2;
return tooEarly || tooLate;
};
const onOpenChange = (open: boolean) => {
if (open) {
dates.value = [] as any;
hackValue.value = [] as any;
} else {
hackValue.value = undefined;
}
};
const onChange = (val: RangeValue) => {
dateRange.value = val;
};
const onCalendarChange = (val: RangeValue) => {
dates.value = val;
};
onMounted(() => {
getOptionsList();
changeMode();
});
return {
value,
deviceName,
pointName,
treeData2,
expandedKeys,
selectedKeys,
checkedKeys,
options1,
options2,
mode,
selectedValue,
frequencyValue,
dateRange,
getOptionsList,
getSelect,
getSelect11,
changeMode,
disabledDate,
onCalendarChange,
onOpenChange,
onChange,
hackValue,
treeLoading,
changeEnergyType,
shebei,
};
},
});
</script>
<style lang="less" scoped>
.ns-tree-title {
user-select: text;
margin-bottom: 5px;
padding-bottom: 10px;
padding-top: 10px;
border-bottom: 1px solid #e9e9e9;
> span {
padding-left: 10px;
line-height: 20px;
}
}
.parent-container {
position: relative;
height: 100%;
}
.fixed-bottom {
position: absolute;
bottom: 0;
width: 100%;
margin-bottom: 10px;
}
</style>

View File

@@ -8,6 +8,7 @@
<a-col :span="8" class="tree">
<a-tree
ref="treeRef"
:showLine="{ showLeafIcon: false }"
v-if="deptTreeData?.length"
:tree-data="deptTreeData"
:selectedKeys="deptTreeSelectedKeys"
@@ -87,6 +88,7 @@
</a-space>
<a-tree
v-if="roleTreeData.length"
:showLine="{ showLeafIcon: false }"
:tree-data="roleTreeData"
:selectedKeys="roleTreeSelectedKeys"
blockNode

View File

@@ -26,6 +26,7 @@
</div>
<a-drawer
:width="500"
title=" "
:visible="visible"
:body-style="{ paddingBottom: '80px' }"
:footer-style="{ textAlign: 'right' }"
@@ -545,7 +546,8 @@
{
label: '新增',
name: 'userAdd',
type: 'primary',
style: { marginBottom: '16px' },
// type: 'primary',
handle: () => {
addformvisible.value = true;
},
@@ -644,6 +646,9 @@
:deep(.ns-table-main) {
border-top: 0;
padding: 0;
.drawerTable {
margin-top: 16px;
}
}
}
</style>

View File

@@ -8,7 +8,7 @@
ref="formElRef"
:model="formModel">
<div v-if="showAction && showExpandAll" class="ns-form-title ns-title-extra-box">
<span>查询</span>
<span> {{ title ? title : '查询' }}</span>
<a-button type="link" class="ns-operate-expand" @click="expandAll = !expandAll">
<template v-if="expandAll">
收起筛选
@@ -107,6 +107,7 @@
const isInitDefaultValueRef = ref(false);
const expandRef = ref(props.expand);
const expandAll = ref(props.expandAll);
const title = ref(props.title);
const formModel = computed(() => {
return props.model;
});
@@ -315,6 +316,7 @@
splitNumber,
finish,
expandAll,
title,
};
},
});

View File

@@ -18,4 +18,5 @@ export const formProps = {
// 收起全部
expandAll: PropTypes.bool.def(true), // 默认是否全部展开
showExpandAll: PropTypes.bool.def(true), // 是否展示右上角的全部收起展开
title: PropTypes.bool.def(false), // 是否展示右上角的全部收起展开
};

View File

@@ -34,6 +34,7 @@ export const tableProps = {
scroll: PropTypes.object.def(() => tableConfig.scroll), //操作栏
size: PropTypes.string.def('middle'),
showBack: PropTypes.bool.def(false),
bordered: PropTypes.bool.def(true),
pageField: PropTypes.string.def(tableConfig.pageField),
pageCountField: PropTypes.string.def(tableConfig.pageCount),
listField: PropTypes.string.def(tableConfig.listField),

View File

@@ -30,6 +30,7 @@
:expand="expand"
:showExpand="showExpand"
:expandAll="expandAll"
:title="formConfig.title"
:showExpandAll="showExpandAll"
:model="formModel"
@finish="formFinish" />

View File

@@ -16,6 +16,7 @@ export const treeProps = {
},
treeData: PropTypes.array.def([]),
params: PropTypes.object.def(() => ({})),
showLine: PropTypes.object.def(() => ({ showLeafIcon: false })),
dynamicParams: PropTypes.oneOfType([
PropTypes.string,
PropTypes.array,

View File

@@ -1,11 +1,11 @@
<template>
<a-spin :spinning="treeState.loading">
<div class="ns-tree-form">
<div v-show="header" class="ns-tree-title">
<div v-if="header" class="ns-tree-title">
<ns-icon :name="header.icon" size="14" />
<span>{{ header.title }}</span>
</div>
<div v-show="!formConfig?.schema">
<div v-if="!formConfig?.schema">
<ns-form ref="formElRef" v-bind="formConfig" :model="formModel" @finish="formFinish" />
</div>
</div>
@@ -80,7 +80,7 @@
treeState.loading = loading;
};
const httpPrams = computed(() => {
return { ...route.params, ...route.query, ...props.params };
return { ...route.params, ...route.query, ...props.params, ...formModel };
});
const getData = (params = {}) => {
@@ -105,6 +105,15 @@
};
getData();
const treeReload = (params) => {
getData(params);
};
const clearSelectedKeys = () => {
selectedKeys.value = [];
};
defineExpose({ treeReload, clearSelectedKeys });
</script>
<style lang="less" scoped>
@gap: 16px;