This commit is contained in:
duyufeng
2024-08-30 10:28:44 +08:00
43 changed files with 3270 additions and 285 deletions

View File

@@ -0,0 +1,10 @@
import { BASE_URL } from './index';
export enum coldAndHeatSourcesApi {
getUserWaterPumpState = `${BASE_URL}/api/tempSysCtrl/getUserWaterPumpState`, // 用户水泵查询最新状态
getLandWaterPumpState = `${BASE_URL}/api/tempSysCtrl/getLandWaterPumpState`, // 地源水泵查询最新状态
getLandHeatPumpState = `${BASE_URL}/api/tempSysCtrl/getLandHeatPumpState`, //螺旋式地源热泵 - 查询最新状态
getEnergyTankState = `${BASE_URL}/api/tempSysCtrl/getEnergyTankState`, //冷热水双蓄储能罐 - 查询最新状态
getCoolPumpState = `${BASE_URL}/api/tempSysCtrl/getCoolPumpState`, //释冷泵 - 查询最新状态
getAirHeatPumpState = `${BASE_URL}/api/tempSysCtrl/getAirHeatPumpState`, //空气源热泵 - 查询最新状态
}

View File

@@ -2,6 +2,8 @@
const prefix = '/carbon-smart/api';
// 通风系统相关接口
export enum waterSys {
// 首页 ====================================================
// 获得污水池状态
getPool1 = prefix + '/waterSysCtrl/getSewagePoolState',
// 获得阀门状态
@@ -10,4 +12,15 @@ export enum waterSys {
getPool2 = prefix + '/waterSysCtrl/getCollectPoolState',
// 获得水泵状态
getPump = prefix + '/waterSysCtrl/getPumpState',
// 提交场景模式修改
submitList = prefix + '/waterSysCtrl/changeToSceneMode',
// 计划 tab1 ===============================================
submitTableData = prefix + '/waterSysCtrl/refreshPlanStatus',
// 日志 tab2 ===============================================
// 获得设备日志
getLog = prefix + '/waterSysInfo/pageAbleLog',
// 获得日志详情
getLogDetail = prefix + '/waterSysInfo/fullLog',
}

View File

@@ -119,25 +119,25 @@ const equipmentControl = {
},
],
},
// {
// path: 'waterSystem',
// name: 'waterSystem',
// meta: { title: '给排水系统', hideChildren: true, icon: 'shebeiqunkong' },
// component: Base,
// redirect: { name: 'waternControlSystemIndex' },
// children: [
// {
// path: 'index',
// name: 'waternControlSystemIndex',
// component: () => import('/@/view/equipmentControl/waterSystem/index.vue'),
// meta: {
// title: '给排水系统',
// keepAlive: false,
// // backApi: [],
// },
// },
// ],
// },
{
path: 'waterSystem',
name: 'waterSystem',
meta: { title: '给排水系统', hideChildren: true, icon: 'shebeiqunkong' },
component: Base,
redirect: { name: 'waternControlSystemIndex' },
children: [
{
path: 'index',
name: 'waternControlSystemIndex',
component: () => import('/@/view/equipmentControl/waterSystem/index.vue'),
meta: {
title: '给排水系统',
keepAlive: false,
// backApi: [],
},
},
],
},
{
path: 'planToAdd',
name: 'planToAdd',

View File

@@ -10,10 +10,43 @@ import FileSaver from 'file-saver';
// export default exportExcel;
// 导出excel文件
export function exportExcel (tableColumns,data,fillName,isMerge,start,end) {
/**
*
* @param {*} tableColumns 表头
* @param {*} data 数据
* @param {*} fillName 文件名
* @param {*} isMerge 是否合并单元格
* @param {*} firstKey 第一个字段的key用来判断序号列是否合并
* @param {*} start 合并单元格开始列
* @param {*} end 合并单元格结束列
* @returns
*/
export function exportExcel (tableColumns,data,fillName,isMerge = false, firstKey = '',start = 0, end = 0) {
debugger
if (!data || data.length == 0) {
return;
}
if (isMerge) {
// 需要合并序号
for (let i = 0; i < data.length; i++) {
// 自定义单元格内容,这里返回序号
if (i == 0) {
data[i].index = 1;
// return 1;
} else if (data[i - 1][firstKey] == data[i][firstKey]) {
data[i].index = data[i - 1].index;
// return data.value[index].index;
} else {
data[i].index = data[i - 1].index + 1;
}
}
} else {
// 不需要合并序号
for (let i = 0; i < data.length; i++) {
data[i].index = i + 1;
}
}
// 创建工作簿
const workbook = new ExcelJS.Workbook();
// 添加工作表名为sheet1
@@ -33,27 +66,22 @@ export function exportExcel (tableColumns,data,fillName,isMerge,start,end) {
columns.push({
header: tableColumns[i].title,
key: tableColumns[i].dataIndex,
width: tableColumns[i].width / 5,
width: tableColumns[i].width ? tableColumns[i].width / 5 : 20,
});
}
}
//传入的数据
const list = data;
//格式化数据
const datas = formatJson(filterVal, list);
// // 导出数据列表
// const data = [
// { 姓名: '张三', 年龄: 18, 身高: 175, 体重: 74 },
// { 姓名: '李四', 年龄: 22, 身高: 177, 体重: 84 },
// { 姓名: '王五', 年龄: 53, 身高: 155, 体重: 64 },
// ];
// 获取表头所有键
// const headers = Object.keys(data[0]);
// 获取表头
sheet1.columns = columns;
// // 将标题写入第一行
// sheet1.addRow(tHeader);
// 将数据写入工作表
datas.forEach((row) => {
// const values = Object.values(row);
@@ -62,80 +90,30 @@ export function exportExcel (tableColumns,data,fillName,isMerge,start,end) {
// 判断是否合并单元格
if (isMerge) {
debugger
// 遍历列,从第一列到 end 列
// 遍历列,从 start 列到 end 列
for (let col = start; col <= end; col++) {
// let mergeStartRow = 2; // 每次新列开始时,重置起始行
for (let row = 3; row <= datas.length + 1; row++) {
const currentCellValue = sheet1.getCell(row, col).value;
const previousCellValue = sheet1.getCell(row - 1, col).value;
let currentCellValue_1 = ''
let previousCellValue_1 = ''
// 从第二列开始就要看前一列是否已经合并
let isMerged = true
if (col > 1) {
currentCellValue_1 = sheet1.getCell(row, col-1).value;
previousCellValue_1 = sheet1.getCell(row - 1, col-1).value;
isMerged = ifMerged(sheet1,row - 1, col-1,row, col-1)
}
if (currentCellValue === previousCellValue && currentCellValue_1 === previousCellValue_1) {
// 当前列有变化,检查是需要合并前面的单元格
// if (mergeStartRow < row - 1) {
// 只有在前面的行有超过1个时才合并
sheet1.mergeCells(row, col, row - 1, col);
// }
// 更新起始行
// mergeStartRow = row;
if (currentCellValue === previousCellValue && isMerged) {
// 检查是上边需要合并的单元格是否已经合并
const mergeInfo = getMergeInfo(sheet1, row - 1, col)
if ( mergeInfo.isMerged ) {
sheet1.unMergeCells( mergeInfo.startRow, col, row - 1, col);
sheet1.mergeCells(mergeInfo.startRow, col, row, col);
} else {
sheet1.mergeCells(row, col, row - 1, col);
}
}
// // 如果是最后一行,检查是否需要合并
// if (row === datas.length + 1 && mergeStartRow < row) {
// sheet1.mergeCells(mergeStartRow, col, row, col);
// }
}
}
// 从第一列开始逐列检查,前提是前面的列已合并
// for (let col = start; col <= end; col++) {
// let startRow = 2; // 从数据开始的第二行开始检查
// let endRow = 2;
// while (endRow <= sheet1.rowCount) {
// let currentValue = sheet1.getCell(endRow, col).value;
// let prevValue = sheet1.getCell(endRow - 1, col).value;
// // 如果当前值等于前一个值,且前面的列是合并的,则继续合并
// if (currentValue === prevValue) {
// let mergeAllowed = true;
// for (let prevCol = 1; prevCol < col; prevCol++) {
// let range = sheet1.getCell(endRow - 1, prevCol).master;
// if (range && range.row !== startRow) {
// mergeAllowed = false;
// break;
// }
// }
// if (mergeAllowed) {
// endRow++;
// } else {
// // 不允许合并,直接移动起始行到当前行
// startRow = endRow;
// endRow++;
// }
// } else {
// // 当前值不等于前一个值或合并不允许,进行合并操作
// if (startRow < endRow - 1) {
// sheet1.mergeCells(startRow, col, endRow - 1, col);
// }
// startRow = endRow;
// endRow++;
// }
// }
// // 处理最后一段相同的单元格
// if (startRow < endRow - 1) {
// sheet1.mergeCells(startRow, col, endRow - 1, col);
// }
// }
}
@@ -208,3 +186,43 @@ function formatJson (filterVal, jsonData) {
return jsonData.map((v) => filterVal.map((j) => v[j]));
};
/**
* 获取给定行和列的单元格是否为合并单元格,并返回合并起始行
* @param {Worksheet} worksheet - ExcelJS 工作表对象
* @param {number} row - 单元格的行号 (从 1 开始)
* @param {number} col - 单元格的列号 (从 1 开始)
* @returns {Object} - 返回一个对象,包含 isMerged 和 startRow 属性
*/
function getMergeInfo(worksheet, row, col) {
// 遍历所有的合并范围
for (const mergeRange in worksheet._merges) {
if (worksheet._merges.hasOwnProperty(mergeRange)) {
const { top, left, bottom, right } = worksheet._merges[mergeRange];
// 检查行列是否在当前合并范围内
if (row >= top && row <= bottom && col >= left && col <= right) {
return { isMerged: true, startRow: top }; // 找到合并范围,返回合并信息
}
}
}
return { isMerged: false, startRow: null }; // 单元格不在任何合并范围内
}
// 函数:检查两个单元格是否属于同一个合并区域
function ifMerged(worksheet, row1, col1, row2, col2) {
const merges = worksheet._merges; // 获取所有的合并区域
for (let mergeAddress in merges) {
const mergeRange = merges[mergeAddress];
const { top, left, bottom, right } = mergeRange;
const isCell1InRange = (row1 >= top && row1 <= bottom && col1 >= left && col1 <= right);
const isCell2InRange = (row2 >= top && row2 <= bottom && col2 >= left && col2 <= right);
if (isCell1InRange && isCell2InRange) {
return true;
}
}
return false;
}

View File

@@ -48,7 +48,7 @@
</a-form>
</a-card>
</div>
<div style="display: flex; margin-top: 20px; height: calc(84% - 20px)">
<div style="display: flex; margin-top: 20px; height: calc(85% - 20px)">
<div class="detailTable">
<ns-view-list-table v-bind="tableConfig" :model="data" ref="mainRef" :scroll="{ x: 1280 }">
<template #bodyCell="{ column, text, record }">
@@ -523,6 +523,7 @@
{
title: '更新时间',
width: 150,
ellipsis: true,
dataIndex: 'updateTime',
},
],
@@ -826,7 +827,7 @@
padding: 16px;
}
.search {
height: 16%;
height: 15%;
}
.detailTable {
width: 70%;

View File

@@ -13,6 +13,9 @@
<a-button type="primary" ghost style="margin-left: 6px" @click="reset">重置</a-button>
</span>
</template>
<template #bodyCell="{ column, text }">
<span>{{ text || '-' }}</span>
</template>
</a-table>
<!-- <a-pagination
:current="queryParams.pageNum"

View File

@@ -146,7 +146,7 @@ export const drawerColumns = [
dataIndex: 'dataSources',
},
];
export const setFactorConfig = (orgId) => {
export const setFactorConfig = (orgId, treeId, tableId) => {
return ref({
api: carbonEmissionFactorLibrary.getTableList,
params: { orgId, pageNum: 1, pageSize: 9999, emissionList: [0] },
@@ -155,7 +155,8 @@ export const setFactorConfig = (orgId) => {
icon: 'deviceType',
title: '排放分类',
},
params: { orgId},
selectedKeys: treeId,
params: { orgId },
dynamicParams: { emissionList: 'id[]' },
defaultExpandAll: true,
// checkable:true,
@@ -175,7 +176,10 @@ export const setFactorConfig = (orgId) => {
],
},
},
rowSelection: { type: 'radio' },
rowSelection: {
type: 'radio',
selectedRowKeys: tableId,
},
columns: [
{
title: '序号',

View File

@@ -297,9 +297,7 @@
indexName: '能源种类', // 匹配类型字段
message: [
{ label: '1、若必填项未填写则不能进行导入操作' },
{ label: `2、当重复时则更新数据。` },
{ label: '3、数据将从模版的第五行进行导入。' },
{ label: '4、文件导入勿超过5MB。' },
{ label: '2、文件导入勿超过5MB。' },
],
},
},

View File

@@ -21,20 +21,22 @@
:auto-expand-parent="autoExpandParent"
:selectedKeys="selectedKeys"
:tree-data="gData"
:show-line="{ showLeafIcon: false }"
show-line
@expand="onExpand"
@select="onSelect"
style="padding: 0 16px !important">
<template #title="data">
<span
<!-- <span
v-if="data.energyType && searchValue && data.energyType.indexOf(searchValue) > -1">
{{ data.energyType.substring(0, data.energyType.indexOf(searchValue)) }}
<span style="color: #f50">{{ searchValue }}</span>
{{
data.energyType.substring(data.energyType.indexOf(searchValue) + searchValue.length)
}}
</span>
<span v-else>{{ data.energyType }}</span>
</span> -->
<span v-if="data.code">{{ truncatedName(data.energyType + data.code) }}</span>
<span v-else>{{ truncatedName(data.energyType) }}</span>
</template>
</a-tree>
</div>
@@ -130,8 +132,8 @@
</template>
<script lang="ts" setup>
import { ref, watch, toRaw, defineExpose } from 'vue';
import type { TreeProps } from 'ant-design-vue';
import { ref, watch, toRaw, defineExpose, nextTick } from 'vue';
import { message, TreeProps } from 'ant-design-vue';
import { Pagination, Modal } from 'ant-design-vue';
import { columns, drawerColumns } from '../config';
import { http } from '/nerv-lib/util/http';
@@ -225,6 +227,12 @@
expandedKeys.value = keys;
autoExpandParent.value = false;
};
const truncatedName = (name) => {
if (name.length > 8) {
return name.substring(0, 8) + '...';
}
return name;
};
// 被选中的树节点
const energyType = ref();
const onSelect = (selectedKey: string[], info: any) => {
@@ -245,16 +253,22 @@
return null;
})
.filter((item, i, self) => item && self.indexOf(item) === i);
expandedKeys.value = expanded;
// expandedKeys.value = expanded;
searchValue.value = value;
autoExpandParent.value = true;
});
// 查询因子分类树数据
const onSearchTreeData = () => {};
const getTreeQuery = ref({
orgId: orgId.value,
});
const onSearchTreeData = () => {
getTreeQuery.value.energyType = searchValue.value;
getTreeData();
};
const statsId = ref();
// 获取因子分类树数据
const getTreeData = () => {
fetch(quickCalculation.carbonQuickTree, { orgId: orgId.value }).then((res) => {
fetch(quickCalculation.carbonQuickTree, getTreeQuery.value).then((res) => {
gData.value = res.data;
energyType.value = gData.value[0].children[0].id;
statsId.value = gData.value[0].children[0].id;
@@ -271,9 +285,12 @@
});
const tableData = ref([]);
const emissionSources = ref();
const treeId = ref([]);
const tableId = ref([]);
const tableConfig = ref({
title: '排放因子库',
api: quickCalculation.queryCarbonEmissionPage,
rowSelection: null,
params: {
orgId,
energyType,
@@ -306,12 +323,6 @@
className: 'carbonEmissionSuffix',
dataIndex: 'carbonEmissionSuffix',
},
{
title: '更新时间',
className: 'updateTime',
dataIndex: 'updateTime',
ellipsis: true,
},
{
title: '启用时间',
className: 'startTime',
@@ -327,6 +338,12 @@
className: 'dataSources',
dataIndex: 'dataSources',
},
{
title: '更新时间',
className: 'updateTime',
dataIndex: 'updateTime',
ellipsis: true,
},
],
columnActions: {
title: '操作',
@@ -342,8 +359,10 @@
formState.value.factorId = record.factorId;
text.value = '编辑';
visible.value = true;
emissionSources.value = record.factorId; //todo
queryData.value.factorId = emissionSources.value; //todo
emissionSources.value = record.factorId;
queryData.value.factorId = emissionSources.value;
treeId.value = [record.treeId];
tableId.value = [record.factorId];
getNewTable();
},
},
@@ -450,6 +469,7 @@
selectedRowKeys.value = [];
formState.value = {};
formRef.value.resetFields();
message.success('操作成功!');
mainRef.value?.nsTableRef.reload();
});
} else {
@@ -458,6 +478,7 @@
selectedRowKeys.value = [];
formState.value = {};
formRef.value.resetFields();
message.success('操作成功!');
mainRef.value?.nsTableRef.reload();
});
}
@@ -487,9 +508,15 @@
};
const openVisible = ref(false);
const setFactorRef = ref();
const config = setFactorConfig(orgId.value);
const config = setFactorConfig(orgId.value, treeId.value, tableId.value);
const selectFactor = () => {
openVisible.value = true;
nextTick(() => {
setFactorRef.value.nsTableRef.params.emissionList = treeId.value;
setFactorRef.value.nsTableRef.treeElRef.selectedKeys = treeId.value;
setFactorRef.value.nsTableRef.rowSelection.selectedRowKeys = tableId.value;
setFactorRef.value.nsTableRef.reload();
});
};
const btnClick = () => {
let selectRowKeys = setFactorRef.value?.nsTableRef.tableState.selectedRowKeys;
@@ -624,6 +651,25 @@
:deep(.ant-modal-footer) {
border-top: 10px solid #f0f0f0 !important;
}
:deep(.ns-table-container) {
background: white;
}
:deep(.ns-part-tree) {
border-radius: 8px;
background: rgba(255, 255, 255, 1);
box-shadow: 0px 2px 20px rgb(69 123 234 / 20%);
}
:deep(.ns-table-search) {
border-radius: 8px;
background: rgba(255, 255, 255, 1);
box-shadow: 0px 2px 20px rgb(69 123 234 / 20%);
}
:deep(.ns-table-main) {
margin-top: 20px !important;
border-radius: 8px;
background: rgba(255, 255, 255, 1);
box-shadow: 0px 2px 20px rgb(69 123 234 / 20%);
}
</style>
<style scoped>
th.column-money,

View File

@@ -1193,11 +1193,11 @@
return regex.test(filename);
};
const beforeUpload: UploadProps['beforeUpload'] = (file) => {
const filename = file.name;
if (!isValidFileName(filename)) {
message.error('文件名不符合规则');
return Upload.LIST_IGNORE; // 阻止文件上传
}
// const filename = file.name;
// if (!isValidFileName(filename)) {
// message.error('文件名不符合规则');
// return Upload.LIST_IGNORE; // 阻止文件上传
// }
return false;
};
const handleChange = (info: UploadChangeParam) => {
@@ -1284,14 +1284,16 @@
NsMessage.warn('请选择因子');
return;
} else {
if (newTableData.value.emissionFactorUnits === carbonEmission.value) {
if (newTableData.value[0].emissionFactorUnits === carbonEmission.value) {
newTableData.value = setFactorRef.value?.nsTableRef.tableState.selectedRows;
selectedRowKeysEdit.value = setFactorRef.value?.nsTableRef.tableState.selectedRowKeys;
editFormState.value.emissionFactors = newTableData.value[0].emissionFactors;
editFormState.value.factorId = selectedRowKeysEdit.value[0];
openVisible.value = false;
} else {
NsMessage.warn('因子值单位不统一,请重新选择!');
NsMessage.warn(
'因子值单位与当前因子值单位(' + carbonEmission.value + ')不统一,请重新选择!',
);
}
}
};

View File

@@ -39,11 +39,7 @@
placeholder="请输入报告名称" />
</a-form-item>
<a-form-item ref="name" label="报告年度" name="reportYear">
<a-date-picker
v-model:value="formState.reportYear"
@openChange="openChange"
picker="year"
valueFormat="YYYY" />
<a-date-picker v-model:value="formState.reportYear" picker="year" valueFormat="YYYY" />
</a-form-item>
<a-form-item ref="name" label="适用标准" name="genericStandard">
<a-input
@@ -64,7 +60,6 @@
<a-form-item ref="name" label="报告范围" name="reportScope">
<a-range-picker
v-model:value="formState.reportScope"
:defaultPickerValue="defaultPickerValue"
picker="month"
:disabledDate="disabledDate"
valueFormat="YYYY-MM" />
@@ -111,20 +106,22 @@
const selectChange = (value) => {
formState.value.reportScope = '';
};
const defaultPickerValue = ref([
dayjs('2020'), // 默认开始日期
dayjs('2020'), // 默认结束日期
]);
const openChange = (status) => {
if (status === false) {
if (formState.value.reportYear) {
defaultPickerValue.value = [
dayjs(formState.value.reportYear),
dayjs(formState.value.reportYear),
];
}
}
};
// const defaultPickerValue = ref([]);
// const open = ref(false);
// const openChange = (status) => {
// debugger;
// open.value = status;
// if (formState.value.reportYear) {
// defaultPickerValue.value = [
// dayjs(formState.value.reportYear),
// dayjs(formState.value.reportYear),
// ];
// open.value = false;
// nextTick(() => {
// open.value = status; // 重新打开日期选择框
// });
// }
// };
// 定义form表单的必填
const rules: Record<string, Rule[]> = {
reportName: [{ required: true, message: '请输入报告名称', trigger: 'change' }],

View File

@@ -417,7 +417,6 @@
const editData = (record) => {
open.value = true;
if (record.isLastYear !== undefined) {
formState.value.ids = [record.id];
if (record.lastYear === '是') {
formState.value.isLastYear = 1;
disabled.value = false;
@@ -425,10 +424,12 @@
formState.value.isLastYear = 0;
disabled.value = true;
}
formState.value.conversionRate = record.conversionRate;
formState.value.lastYearList = [record.lastYearActualUsage];
formState.value.budget = record.budget;
}
formState.value.ids = [record.id];
formState.value.conversionRate = record.conversionRate;
formState.value.lastYearList = [record.lastYearActualUsage];
formState.value.ids = [record.id];
formState.value.budget = record.budget;
};
const disabled = ref(true);
const selectChange = (value) => {
@@ -995,6 +996,9 @@
:deep(.ant-card-bordered) {
border: unset;
}
:deep(.ant-input-number-handler-wrap){
display: none;
}
</style>
<style scoped>
.editable-row-operations a {

View File

@@ -674,7 +674,7 @@
}
.contant {
width: 100%;
height: calc(94% - 5vh);
height: calc(94% - 5vh - 24px);
overflow-y: auto;
padding: 12px;
.chartsPart {

View File

@@ -30,6 +30,7 @@
v-model:pageSize="pagination.pageSize"
show-size-changer
:total="pagination.total"
show-less-items
@change="getTable(true)" />
<div style="width: 100%; height: 40px"></div>

View File

@@ -16,17 +16,41 @@
<div style="width: 100%; height: 20px; color: rgb(128, 255, 255)">
{{ item.deviceInfoName }}
</div>
<div style="width: 100%; height: 20px">
模式: <span style="color: #fff">{{ item.type }}</span>
<div v-if="item.autoStatus" style="width: 100%; height: 20px">
模式: <span style="color: #fff">{{ item.autoStatus.label }}</span>
</div>
<div style="width: 100%; height: 20px">
设定温度: <span style="color: #fff">{{ item.number }}</span>
<div v-if="item.temp" style="width: 100%; height: 20px">
设定温度: <span style="color: #fff">{{ item.temp }} {{ item.tempUnit }}</span>
</div>
<img
style="position: absolute; width: 135px; height: 130px; left: -20px; top: 40px"
:src="item.url" />
</div>
</template>
<!-- 空气源 - 传感器 -->
<template v-for="(item, index) in airSourceSensor" :key="index">
<div
style="
width: 135px;
height: 200px;
position: relative;
font-size: 12px;
position: absolute;
color: #ffff80;
z-index: 2;
"
:style="{ left: item.style.mLeft, bottom: item.style.mBottom }">
<div style="width: 100%; height: 20px">
出水温度: <span style="color: #fff">{{ item.temp }} {{ item.tempUnit }}</span>
</div>
<div style="width: 100%; height: 20px">
流量: <span style="color: #fff">{{ item.traffic }} {{ item.trafficUnit }}</span>
</div>
<img
style="position: absolute; width: 28px; height: 28px; left: -20px; top: 40px"
:src="item.url" />
</div>
</template>
<!-- 水泵 -->
<div
style="
@@ -37,7 +61,7 @@
bottom: 50%;
position: absolute;
">
<a-switch
<!-- <a-switch
:checked="selectAllCheckbox === 1 ? true : false"
size="small"
:disabled="true"
@@ -46,10 +70,34 @@
'blue-background': selectAllCheckbox === 1 ? true : false,
'grey-background': selectAllCheckbox === 1 ? false : true,
}"
@change="toggleAllSelection" />
@change="toggleAllSelection" /> -->
<img style="display: flex; width: 111px; height: 100px" :src="waterPumpSrc" />
<div style="width: 100%; height: 20px; color: rgb(128, 255, 255)"> 水泵 </div>
</div>
<!-- 水泵 传感器 -->
<template v-for="(item, index) in waterSensor" :key="index">
<div
style="
width: 135px;
height: 200px;
position: relative;
font-size: 12px;
position: absolute;
color: #ffff80;
z-index: 2;
"
:style="{ left: item.style.mLeft, bottom: item.style.mBottom }">
<div style="width: 100%; height: 20px">
温度: <span style="color: #fff">{{ item.temp }} {{ item.tempUnit }}</span>
</div>
<div style="width: 100%; height: 20px">
流量: <span style="color: #fff">{{ item.traffic }} {{ item.trafficUnit }}</span>
</div>
<img
style="position: absolute; width: 28px; height: 28px; left: -20px; top: 40px"
:src="item.url" />
</div>
</template>
<!-- 螺杆式地源热泵 -->
<template v-for="(item, index) in screwGeothermalHeatPump" :key="index">
<div
@@ -69,11 +117,11 @@
<div style="width: 100%; height: 20px; color: rgb(128, 255, 255); z-index: 2">
{{ item.deviceInfoName }}
</div>
<div style="width: 100%; height: 20px">
模式: <span style="color: #fff">{{ item.type }}</span>
<div v-if="item.autoStatus" style="width: 100%; height: 20px">
模式: <span style="color: #fff">{{ item.autoStatus.label }}</span>
</div>
<div style="width: 100%; height: 20px">
设定温度: <span style="color: #fff">{{ item.number }}</span>
<div v-if="item.temp" style="width: 100%; height: 20px">
设定温度: <span style="color: #fff">{{ item.temp }} {{ item.tempUnit }}</span>
</div>
</div>
</template>
@@ -90,15 +138,17 @@
z-index: 2;
"
:style="{ left: item.style.mLeft, bottom: item.style.mBottom }">
<div style="width: 100%; height: 20px; color: rgb(128, 255, 255)">
<div
style="
width: 100%;
height: 20px;
color: rgb(128, 255, 255);
position: absolute;
top: 40px;
left: 5px;
">
{{ item.deviceInfoName }}
</div>
<div style="width: 100%; height: 20px">
出水温度: <span style="color: #fff">{{ item.number }}</span>
</div>
<div style="width: 100%; height: 20px">
流量: <span style="color: #fff">{{ item.lNumber }}</span>
</div>
<img
style="position: absolute; width: 117.42px; height: 106.31px; left: -20px; top: 60px"
:src="item.url" />
@@ -117,17 +167,14 @@
z-index: 2;
"
:style="{ left: item.style.mLeft, bottom: item.style.mBottom }">
<div style="width: 100%; height: 20px; color: rgb(128, 255, 255)">
<div style="width: 100%; height: 20px; color: rgb(128, 255, 255); margin-top: 20px">
{{ item.deviceInfoName }}
</div>
<div style="width: 100%; height: 20px">
出水温度: <span style="color: #fff">{{ item.number }}</span>
<div v-if="item.temp" style="width: 100%; height: 20px">
出水温度: <span style="color: #fff">{{ item.temp }} {{ item.tempUnit }}</span>
</div>
<div style="width: 100%; height: 20px">
容量: <span style="color: #fff">{{ item.rNumber }}</span>
</div>
<div style="width: 100%; height: 20px">
流量: <span style="color: #fff">{{ item.lNumber }}</span>
<div v-if="item.temp" style="width: 100%; height: 20px">
容量: <span style="color: #fff">{{ item.vol }} {{ item.volUnit }}</span>
</div>
<img
style="position: absolute; width: 110px; height: 110px; left: -20px; top: 80px"
@@ -146,13 +193,14 @@
"
:style="{ left: item.style.mLeft, bottom: item.style.mBottom }">
<a-switch
:checked="item.user === 1 ? true : false"
v-if="item.switchStatus"
:checked="item?.switchStatus?.value === 1 ? true : false"
size="small"
:disabled="true"
style="position: absolute; left: 30px; bottom: 0px; z-index: 2"
:class="{
'blue-background': item.user === 1 ? true : false,
'grey-background': item.user === 1 ? false : true,
'blue-background': item?.switchStatus?.value === 1 ? true : false,
'grey-background': item?.switchStatus?.value === 1 ? false : true,
}" />
<img
style="
@@ -168,7 +216,7 @@
font-size: 12px;
position: absolute;
left: -40px;
top: 30px;
top: 40px;
transform: rotateZ(-24deg);
"
>{{ item.deviceInfoName }}</div
@@ -192,21 +240,43 @@
style="width: 226.19px; height: 176.19px; transform: rotateY(13deg)"
:src="manifoldSrc" />
</div>
<!-- 集水器 传感器 -->
<template v-for="(item, index) in manifoldSensor" :key="index">
<div
style="
width: 135px;
height: 200px;
position: relative;
font-size: 12px;
position: absolute;
color: #ffff80;
z-index: 2;
"
:style="{ left: item.style.mLeft, bottom: item.style.mBottom }">
<div style="width: 100%; height: 20px">
回水温度: <span style="color: #fff">{{ item.temp }} {{ item.tempUnit }}</span>
</div>
<div style="width: 100%; height: 20px">
流量: <span style="color: #fff">{{ item.traffic }} {{ item.trafficUnit }}</span>
</div>
<img
style="
position: absolute;
width: 28px;
height: 28px;
left: -20px;
top: 40px;
transform: rotateX(-4deg) rotateY(180deg) rotateZ(1deg);
"
:src="item.url" />
</div>
</template>
<!-- 定压补水装置 -->
<template v-for="(item, index) in pressureWater" :key="index">
<div
style="width: 137px; height: 137px; position: relative; position: absolute; z-index: 2"
:style="{ left: item.style.mLeft, bottom: item.style.mBottom }">
<img style="width: 137px; height: 127px; transform: rotateY(157deg)" :src="item.url" />
<a-switch
:checked="item.user === 1 ? true : false"
size="small"
:disabled="true"
style="position: absolute; left: 40px; bottom: 0px"
:class="{
'blue-background': item.user === 1 ? true : false,
'grey-background': item.user === 1 ? false : true,
}" />
<div
style="
width: 100%;
@@ -217,9 +287,6 @@
font-size: 12px;
">
<div> {{ item.deviceInfoName }}</div>
<div style="width: 100%; height: 20px; color: #ffff80">
压差: <span style="color: #fff">{{ item.yc }}</span>
</div>
</div>
</div>
</template>
@@ -294,13 +361,14 @@
"
:style="{ left: item.style.mLeft, bottom: item.style.mBottom }">
<a-switch
:checked="item.user === 1 ? true : false"
v-if="item.switchStatus"
:checked="item.switchStatus.value === 1 ? true : false"
size="small"
:disabled="true"
style="position: absolute; left: 30px; bottom: 0px; z-index: 2"
:class="{
'blue-background': item.user === 1 ? true : false,
'grey-background': item.user === 1 ? false : true,
'blue-background': item.switchStatus.value === 1 ? true : false,
'grey-background': item.switchStatus.value === 1 ? false : true,
}" />
<img
style="
@@ -356,6 +424,30 @@
>
<img style="width: 290.75px; height: 215.29px" :src="soilCouplerSrc" />
</div>
<!-- 土壤 传感器 -->
<template v-for="(item, index) in soilCouplerSensor" :key="index">
<div
style="
width: 135px;
height: 80px;
position: relative;
font-size: 12px;
position: absolute;
color: #ffff80;
z-index: 2;
"
:style="{ left: item.style.mLeft, bottom: item.style.mBottom }">
<div style="width: 100%; height: 20px">
供水温度: <span style="color: #fff">{{ item.temp }} {{ item.tempUnit }}</span>
</div>
<div style="width: 100%; height: 20px">
流量: <span style="color: #fff">{{ item.traffic }} {{ item.trafficUnit }}</span>
</div>
<img
style="position: absolute; width: 28px; height: 28px; left: -20px; top: 40px"
:src="item.url" />
</div>
</template>
<!-- 线 -->
<template v-for="(item, index) in line" :key="index">
<div
@@ -370,11 +462,27 @@
}">
</div>
</template>
<!-- 箭头 -->
<template v-for="(item, index) in arrow" :key="index">
<div
style="width: 50px; height: 20px; position: absolute"
:style="{
left: item.style.mLeft,
bottom: item.style.mBottom,
transform: item.style.transform,
}">
<img style="width: 50px; height: 20px" :src="item.url" />
</div>
</template>
</div>
</template>
<script setup lang="ts">
import { ref, onMounted, onUnmounted } from 'vue';
import { http } from '/nerv-lib/util';
import { coldAndHeatSourcesApi } from '/@/api/coldAndHeatSources';
import { ventilating } from '/@/api/ventilatingSystem';
//图片资源
import airSourceThermalCollapseSrc from '../image/coldAndHeatSources/airSourceThermalCollapse.png';
import waterPumpSrc from '../image/coldAndHeatSources/waterPump.png';
@@ -386,10 +494,24 @@
import softenedWaterTankSrc from '../image/coldAndHeatSources/softenedWaterTank.png';
import waterProcessorSrc from '../image/coldAndHeatSources/waterProcessor.png';
import soilCouplerSrc from '../image/coldAndHeatSources/soilCoupler.png';
import sensorSrc from '../image/coldAndHeatSources/sensor.png';
import blueGif from '../image/coldAndHeatSources/blue.gif';
import bluePng from '../image/coldAndHeatSources/blue.png';
import greenGif from '../image/coldAndHeatSources/green.gif';
import greenPng from '../image/coldAndHeatSources/green.png';
import arrowSrc from '../image/coldAndHeatSources/arrow.svg';
// 定位数据
import {
userWaterPumpPosition,
waterPumpPosition,
airSourceThermalCollapsePosition,
screwGeothermalHeatPumpPosition,
airSourceSensorPosition,
} from './position';
// 全局变量
import { items } from '/@/store/item';
// 全局变量
const state = items();
const line = ref([
//水泵线-热泵
{
@@ -759,9 +881,6 @@
]);
const airSourceThermalCollapse = ref([
{
deviceInfoName: '1#空气源热泵',
type: '制热',
number: '40℃',
style: {
mLeft: '17%',
mBottom: '54%',
@@ -769,9 +888,6 @@
url: airSourceThermalCollapseSrc,
},
{
deviceInfoName: '2#空气源热泵',
type: '制热',
number: '40℃',
style: {
mLeft: '24%',
mBottom: '59%',
@@ -779,9 +895,6 @@
url: airSourceThermalCollapseSrc,
},
{
deviceInfoName: '3#空气源热泵',
type: '制热',
number: '40℃',
style: {
mLeft: '31%',
mBottom: '66%',
@@ -789,9 +902,6 @@
url: airSourceThermalCollapseSrc,
},
{
deviceInfoName: '4#空气源热泵',
type: '制热',
number: '40℃',
style: {
mLeft: '38%',
mBottom: '73%',
@@ -802,9 +912,6 @@
//螺杆式地源热泵
const screwGeothermalHeatPump = ref([
{
deviceInfoName: '1#螺杆式地源热泵',
type: '制热',
number: '40℃',
style: {
mLeft: '9.5%',
mBottom: '22.5%',
@@ -812,9 +919,6 @@
url: screwGeothermalHeatPumpSrc,
},
{
deviceInfoName: '2#螺杆式地源热泵',
type: '制热',
number: '40℃',
style: {
mLeft: '18.5%',
mBottom: '31.5%',
@@ -826,8 +930,6 @@
const diluteCoolingPump = ref([
{
deviceInfoName: '稀冷泵',
number: '40℃',
lNumber: '139 m3/h',
style: {
mLeft: '30%',
mBottom: '29%',
@@ -838,56 +940,40 @@
//冷热水双蓄储能罐
const coldWater = ref([
{
deviceInfoName: '冷热水双蓄储能罐',
number: '40℃',
lNumber: '139 m3/h',
rNumber: '135L',
url: coldWaterSrc,
style: {
mLeft: '36%',
mBottom: '39%',
},
url: coldWaterSrc,
},
]);
//用户水泵
const userWaterPump = ref([
{
deviceInfoName: '1#用户水泵',
number: '40℃',
user: 1,
url: waterPumpSrc,
style: {
mLeft: '50%',
mBottom: '55.5%',
},
url: waterPumpSrc,
},
{
deviceInfoName: '2#用户水泵',
number: '40℃',
user: 1,
url: waterPumpSrc,
style: {
mLeft: '55%',
mBottom: '51%',
},
url: waterPumpSrc,
},
{
deviceInfoName: '3#用户水泵',
number: '40℃',
user: 1,
url: waterPumpSrc,
style: {
mLeft: '59%',
mBottom: '47%',
},
url: waterPumpSrc,
},
]);
//地源水泵
const waterPump = ref([
{
deviceInfoName: '1#地源水泵',
number: '40℃',
user: 1,
style: {
mLeft: '65%',
mBottom: '41%',
@@ -895,9 +981,6 @@
url: waterPumpSrc,
},
{
deviceInfoName: '2#地源水泵',
number: '40℃',
user: 1,
style: {
mLeft: '70%',
mBottom: '36%',
@@ -905,9 +988,6 @@
url: waterPumpSrc,
},
{
deviceInfoName: '3#地源水泵',
number: '40℃',
user: 1,
style: {
mLeft: '75%',
mBottom: '31%',
@@ -928,9 +1008,287 @@
url: pressureWaterSrc,
},
]);
const selectAllCheckbox = ref(1);
onMounted(() => {});
onUnmounted(() => {});
//水泵 - 螺杆式 传感器
const waterSensor = ref([
{
url: sensorSrc,
style: {
mLeft: '9%',
mBottom: '27%',
},
},
]);
//集水器 传感器
const manifoldSensor = ref([
{
url: sensorSrc,
style: {
mLeft: '81%',
mBottom: '51%',
},
},
]);
//土壤 传感器
const soilCouplerSensor = ref([
{
url: sensorSrc,
style: {
mLeft: '79%',
mBottom: '13%',
},
},
]);
//空气源 - 传感器
const airSourceSensor = ref([
{
style: {
mLeft: '26.5%',
mBottom: '34%',
},
url: sensorSrc,
},
{
style: {
mLeft: '32.5%',
mBottom: '39%',
},
url: sensorSrc,
},
{
style: {
mLeft: '38.5%',
mBottom: '44.5%',
},
url: sensorSrc,
},
{
style: {
mLeft: '47.5%',
mBottom: '52.5%',
},
url: sensorSrc,
},
]);
//箭头
const arrow = ref([
{
url: arrowSrc,
style: {
mLeft: '6%',
mBottom: '42.5%',
transform: 'rotateZ(36deg)',
},
},
{
url: arrowSrc,
style: {
mLeft: '25%',
mBottom: '18%',
transform: 'rotateZ(36deg)',
},
},
{
url: arrowSrc,
style: {
mLeft: '76%',
mBottom: '13%',
transform: 'rotateZ(37deg)',
},
},
{
url: arrowSrc,
style: {
mLeft: '78%',
mBottom: '72%',
transform: 'rotateZ(149deg)',
},
},
]);
//获取用户水泵数据
const getUserWaterPump = () => {
http
.get(coldAndHeatSourcesApi.getUserWaterPumpState, {
projectId: state.projectId,
siteId: state.siteId,
floor: 1,
})
.then((res) => {
if (res.msg === 'success') {
res.data.forEach((item: any, index: any) => {
userWaterPump.value[index] = {
deviceInfoName: item.deviceInfoName,
...item.record,
url: waterPumpSrc,
style: userWaterPumpPosition[index].style,
};
});
}
});
};
//获取地源水泵数据
const getLandWaterPumpState = () => {
http
.get(coldAndHeatSourcesApi.getLandWaterPumpState, {
projectId: state.projectId,
siteId: state.siteId,
floor: 1,
})
.then((res) => {
if (res.msg === 'success') {
res.data.forEach((item: any, index: any) => {
waterPump.value[index] = {
deviceInfoName: item.deviceInfoName,
...item.record,
url: waterPumpSrc,
style: waterPumpPosition[index].style,
};
});
}
});
};
//获取空气源热泵
const getAirHeatPumpState = () => {
http
.get(coldAndHeatSourcesApi.getAirHeatPumpState, {
projectId: state.projectId,
siteId: state.siteId,
floor: 1,
})
.then((res) => {
if (res.msg === 'success') {
res.data.forEach((item: any, index: any) => {
airSourceThermalCollapse.value[index] = {
deviceInfoName: item.deviceInfoName,
...item.record,
url: airSourceThermalCollapseSrc,
style: airSourceThermalCollapsePosition[index].style,
};
});
}
});
};
//获取螺杆式地源热泵
const getLandHeatPumpState = () => {
http
.get(coldAndHeatSourcesApi.getLandHeatPumpState, {
projectId: state.projectId,
siteId: state.siteId,
floor: 1,
})
.then((res) => {
if (res.msg === 'success') {
res.data.forEach((item: any, index: any) => {
screwGeothermalHeatPump.value[index] = {
deviceInfoName: item.deviceInfoName,
...item.record,
url: screwGeothermalHeatPumpSrc,
style: screwGeothermalHeatPumpPosition[index].style,
};
});
}
});
};
//冷热水双蓄储能罐
const getEnergyTankState = () => {
http
.get(coldAndHeatSourcesApi.getEnergyTankState, {
projectId: state.projectId,
siteId: state.siteId,
floor: 1,
})
.then((res) => {
if (res.msg === 'success') {
res.data.forEach((item: any) => {
coldWater.value[0] = {
deviceInfoName: item.deviceInfoName,
...item.record,
};
});
}
});
};
//获取多功能传感器
const getSensorData = () => {
http
.get(ventilating.getSensorData, {
projectId: state.projectId,
siteId: state.siteId,
floor: 1,
})
.then((res) => {
if (res.msg === 'success') {
res.data.forEach((item: any, index: any) => {
if (index < 4) {
airSourceSensor.value[index] = {
deviceInfoName: item.deviceInfoName,
...item.record,
url: sensorSrc,
style: airSourceSensorPosition[index].style,
};
}
});
// 水泵传感器
let data = res.data[Math.floor(Math.random() * res.data.length)];
waterSensor.value[0] = {
deviceInfoName: data.deviceInfoName,
...data.record,
url: sensorSrc,
style: {
mLeft: '9%',
mBottom: '27%',
},
};
// 集水器 -传感器
data = res.data[Math.floor(Math.random() * res.data.length)];
manifoldSensor.value[0] = {
deviceInfoName: data.deviceInfoName,
...data.record,
url: sensorSrc,
style: {
mLeft: '81%',
mBottom: '51%',
},
};
// 土壤 -传感器
data = res.data[Math.floor(Math.random() * res.data.length)];
soilCouplerSensor.value[0] = {
deviceInfoName: data.deviceInfoName,
...data.record,
url: sensorSrc,
style: {
mLeft: '79%',
mBottom: '13%',
},
};
}
});
};
//获取地源水泵设备
const getData = () => {
//获取用户水泵数据
getUserWaterPump();
//获取地源水泵数据
getLandWaterPumpState();
//获取空气源热泵数据
getAirHeatPumpState();
//螺杆式地源热泵
getLandHeatPumpState();
//冷热水双蓄储能罐
getEnergyTankState();
//获取多功能传感器
getSensorData();
};
//定时
// const intervalId = setInterval(getList, 60000);
onMounted(() => {
getData();
});
onUnmounted(() => {
// 这里写销毁时需要执行的逻辑
// clearInterval(intervalId);
});
</script>
<style lang="less" scoped>
.box-cold {

View File

@@ -0,0 +1,111 @@
//用户水泵位置
export const userWaterPumpPosition = [
{
style: {
mLeft: '50%',
mBottom: '55.5%',
},
},
{
style: {
mLeft: '55%',
mBottom: '51%',
},
},
{
style: {
mLeft: '59%',
mBottom: '47%',
},
},
];
//地源水泵位置
export const waterPumpPosition = [
{
style: {
mLeft: '65%',
mBottom: '41%',
},
},
{
style: {
mLeft: '70%',
mBottom: '36%',
},
},
{
style: {
mLeft: '75%',
mBottom: '31%',
},
},
];
//空气源热泵
export const airSourceThermalCollapsePosition = [
{
style: {
mLeft: '17%',
mBottom: '54%',
},
},
{
style: {
mLeft: '24%',
mBottom: '59%',
},
},
{
style: {
mLeft: '31%',
mBottom: '66%',
},
},
{
style: {
mLeft: '38%',
mBottom: '73%',
},
},
];
//获取螺杆式地源热泵
export const screwGeothermalHeatPumpPosition = [
{
style: {
mLeft: '9.5%',
mBottom: '22.5%',
},
},
{
style: {
mLeft: '18.5%',
mBottom: '31.5%',
},
},
];
// 空气源 - 传感器
export const airSourceSensorPosition = [
{
style: {
mLeft: '26.5%',
mBottom: '34%',
},
},
{
style: {
mLeft: '32.5%',
mBottom: '39%',
},
},
{
style: {
mLeft: '38.5%',
mBottom: '44.5%',
},
},
{
style: {
mLeft: '47.5%',
mBottom: '52.5%',
},
},
];

View File

@@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<svg version="1.1" xmlns:xlink="http://www.w3.org/1999/xlink" width="50px" height="22px" xmlns="http://www.w3.org/2000/svg">
<g transform="matrix(1 0 0 1 -1191 -569 )">
<path d="M 49.73958333333333 7.581274382314695 C 49.91319444444444 7.752925877763329 50 7.972258344169916 50 8.239271781534459 L 50 13.732119635890768 C 50 13.999133073255308 49.91319444444444 14.2184655396619 49.73958333333333 14.390117035110533 C 49.56597222222222 14.561768530559167 49.34413580246914 14.647594278283485 49.074074074074076 14.647594278283485 L 12.962962962962962 14.647594278283485 L 12.962962962962962 21.055916775032507 C 12.962962962962962 21.456436931079324 12.779706790123457 21.732986562635453 12.413194444444445 21.88556566970091 C 12.046682098765434 22.038144776766362 11.709104938271606 21.99046380580841 11.400462962962964 21.742522756827046 L 0.28935185185185186 11.729518855656698 C 0.09645061728395089 11.538794971824878 0 11.31946250541829 0 11.071521456436932 C 0 10.804508019072387 0.09645061728395089 10.575639358474207 0.28935185185185186 10.384915474642392 L 11.400462962962964 0.2574772431729524 C 11.709104938271606 -0.009536194191596348 12.046682098765434 -0.06675335934114024 12.413194444444445 0.08582574772431828 C 12.779706790123457 0.2574772431729524 12.962962962962962 0.5340268747290853 12.962962962962962 0.9154746423927169 L 12.962962962962962 7.3237971391417425 L 49.074074074074076 7.3237971391417425 C 49.34413580246914 7.3237971391417425 49.56597222222222 7.409622886866061 49.73958333333333 7.581274382314695 Z " fill-rule="nonzero" fill="#ffff80" stroke="none" transform="matrix(1 0 0 1 1191 569 )" />
</g>
</svg>

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.4 KiB

View File

@@ -30,6 +30,7 @@
v-model:pageSize="pagination.pageSize"
show-size-changer
:total="pagination.total"
show-less-items
@change="getTable(true)" />
<div style="width: 100%; height: 40px"></div>

View File

@@ -9,10 +9,10 @@
<div
:style="{
color: {
'0': '#ccc',
'1': 'rgba(255, 165, 0, 1)',
'2': 'rgb(57, 215, 187)',
'3': 'rgb(255, 0, 0)',
'4': '#ccc',
}[record.executeStatus.value],
}">
{{ record.executeStatus.label }}</div
@@ -36,7 +36,7 @@
import { NsMessage } from '/nerv-lib/component';
import { getEnum } from '/@/api';
import { http } from '/nerv-lib/util';
import dayjs, { Dayjs } from 'dayjs';
//页面 创建
const orgId = ref('');
const projectId = ref('');
@@ -46,6 +46,7 @@
projectId.value = results.projectId;
const mainRef = ref(null);
const modalFormRef = ref(null);
const addPlan = () => {
if (mainRef.value.nsTableRef.treeElRef.selectedRow.node.planLib) {
http
@@ -69,6 +70,10 @@
? record.startTime.substring(0, 10) + ' - ' + record.endTime.substring(0, 10)
: '未配置时间';
};
const disabledDate = (current: Dayjs) => {
// Can not select days before today and today
return current && current < dayjs().endOf('day');
};
const nsModalFormConfig = ref({
api: planToAddApi.updPlan,
data: {},
@@ -92,6 +97,7 @@
componentProps: {
valueFormat: 'YYYY-MM-DD hh:mm:ss',
placeholder: ['开始日期', '结束日期'],
disabledDate: disabledDate,
},
rules: [
{

View File

@@ -85,9 +85,9 @@
:class="{ btn: true, selected: button.selected }"
class="zmhlbtn"
@click="changeLine(button)">
<div v-if="button.lockStatus" class="btn-back">
<!-- <div v-if="button.lockStatus" class="btn-back">
<stop-outlined />
</div>
</div> -->
{{ button.name }}
</button>
<div style="margin-top: 10px">

View File

@@ -30,6 +30,7 @@
v-model:pageSize="pagination.pageSize"
show-size-changer
:total="pagination.total"
show-less-items
@change="getTable(true)" />
<div style="width: 100%; height: 40px"></div>

View File

@@ -0,0 +1,429 @@
<template>
<table class="custom-table table1">
<thead>
<tr :style="{ background: 'rgba(35,45,69)' }">
<th>序号</th>
<th>执行时间</th>
<th>操作内容</th>
<th>操作人</th>
<th>状态</th>
</tr>
</thead>
<tbody>
<tr
:style="{ color: row.ctrlResult == 1 ? 'red' : 'white' }"
v-for="(row, index) in dataSource"
:key="index"
@click="handleRowClick(row.id, index)"
:class="index === trIndex ? 'isTrIndex' : ''">
<td>{{ index + 1 }}</td>
<td>{{ row.startTime }}</td>
<td>{{ row.operationContent }}</td>
<td>{{ row.createUser }}</td>
<td>{{ row.ctrlResult ? '失败' : '成功' }}</td>
</tr>
</tbody>
</table>
<a-pagination
style="margin-top: 10px; text-align: right"
v-model:current="pagination.pageNum"
v-model:pageSize="pagination.pageSize"
show-size-changer
:total="pagination.total"
@change="getTable(true)" />
<div style="width: 100%; height: 40px"></div>
<div class="out-dialog" :class="{ showDialog: logModalVisible }" v-if="logModalVisible">
<div class="content">
<div>
<div class="div-operation"></div>
<span class="text-operation">变更内容 </span>
</div>
<div>
<button :class="{ btn: true, selected: activeButton == 1 }" @click="changeBtn(1)"
>阀门</button
>
<button :class="{ btn: true, selected: activeButton == 2 }" @click="changeBtn(2)"
>水泵</button
>
</div>
<div class="device-list" v-if="activeButton == 1">
<div class="device-list-item" v-for="(item, index) in valveLogList" :key="index">
<div class="list-item-title">
<div class="item-title">
<img src="../images/device1.png" alt="" />
<span>{{ item.deviceGroupName }}</span>
</div>
</div>
<div class="list-item-main">
<div>
<div class="info">开度</div>
<div class="text">
<span>{{ item.openPercentBefore + '%' }}</span>
<img src="/asset/image/bulbLogo/22406.png" alt="" />
<span>{{ item.openPercentAfter + '%' }}</span>
</div>
</div>
<div></div>
</div>
</div>
<a-empty style="margin-top: 100px" v-if="valveLogList.length == 0">
<template #description> <span style="color: white">暂无数据</span></template>
</a-empty>
</div>
<div class="device-list" v-if="activeButton == 2">
<div class="device-list-item" v-for="(item, index) in pumpLogList" :key="index">
<div class="list-item-title">
<div class="item-title">
<img src="../images/device2.png" alt="" />
<span>{{ item.deviceGroupName }}</span>
</div>
</div>
<div class="list-item-main">
<div>
<div class="info">频率</div>
<div class="text">
<span>{{ item.frequencyBefore + 'MHz' }}</span>
<img src="/asset/image/bulbLogo/22406.png" alt="" />
<span>{{ item.frequencyAfter + 'MHz' }}</span>
</div>
</div>
<div>
<div class="info">开关</div>
<div class="text">
<span>{{ item.switchStatusBefore.label }}</span>
<img src="/asset/image/bulbLogo/22406.png" alt="" />
<span>{{ item.switchStatusAfter.label }}</span>
</div>
</div>
</div>
</div>
<a-empty style="margin-top: 100px" v-if="pumpLogList.length == 0">
<template #description> <span style="color: white">暂无数据</span></template>
</a-empty>
</div>
</div>
<div style="width: 100%; height: 160px"></div>
<div class="button-box">
<button class="cancel" @click="logModalVisible = false">关闭</button>
</div>
</div>
<div class="div-add">
<button class="add" @click="reset">刷新</button>
</div>
</template>
<script setup lang="ts">
import { ref, onMounted } from 'vue';
import { message } from 'ant-design-vue';
import { Pagination } from 'ant-design-vue';
import { http } from '/nerv-lib/util/http';
import { waterSys } from '/@/api/waterSystem';
// 全局变量
import { items } from '/@/store/item';
// 初始化 =======================================================
// 组件
defineOptions({
components: {
'a-pagination': Pagination,
},
});
// 初始化
onMounted(() => {
getTable();
});
// 全局变量
const state = items();
// 日志业务 ======================================================
// 分页设置
const pagination = ref({
pageSize: 10,
pageNum: 1,
total: 0,
});
// 表格数据
const dataSource = ref([]);
// 当前选中表格行
let trIndex = ref(-1);
// 获得表格数据
const getTable = (changePage = false) => {
state.setLoading(true);
// 如果是切换页面,则清除当前序列、关闭弹窗
if (changePage) {
trIndex.value = -1;
logModalVisible.value = false;
pumpLogList.value.length = 0;
valveLogList.value.length = 0;
}
http
.get(waterSys.getLog, {
pageSize: pagination.value.pageSize,
pageNum: pagination.value.pageNum,
})
.then((res) => {
state.setLoading(false);
let data = res.data;
dataSource.value = data.records;
pagination.value.total = data.total;
})
.catch(() => {
state.setLoading(false);
});
};
// 刷新功能(右下角)
const reset = () => {
trIndex.value = -1;
logModalVisible.value = false;
pagination.value = {
pageSize: 10,
pageNum: 1,
total: 0,
};
getTable();
};
// 点击日志行事件
const handleRowClick = (id: any, index: any) => {
trIndex.value = index;
getLogDetail(id);
};
// 日志详情业务 ==================================================
// 日志详情显隐
const logModalVisible = ref(false);
const getLogDetail = (id: any) => {
state.setLoading(true);
http
.get(waterSys.getLogDetail, { logId: id })
.then((res) => {
state.setLoading(false);
const data = res.data;
if (data.pumpLogList.length || data.valveLogList.length) {
// 显示模态框
logModalVisible.value = true;
pumpLogList.value = data.pumpLogList;
valveLogList.value = data.valveLogList;
} else {
return message.info('返回值无效');
}
})
.catch(() => {
state.setLoading(false);
});
};
// 当前选中的设备类型 阀门=1/水泵=2
const activeButton = ref(1);
// 切换设备类型
const changeBtn = (key: number) => {
activeButton.value = key;
};
// 水泵数据
const pumpLogList = ref<any>([]);
// 水阀数据
const valveLogList = ref<any>([]);
// 向外暴露方法
defineExpose({
reset,
});
</script>
<style lang="less" scoped>
@import '../../style/dialogStyle.less';
// 右下角添加按钮
.div-add {
height: 64px;
display: flex;
justify-content: flex-end;
align-items: center;
position: fixed;
bottom: 0;
right: 0;
margin-right: 20px;
.add {
width: 74px;
height: 40px;
opacity: 1;
border-radius: 4px;
background: rgba(67, 136, 251, 1);
border: rgba(67, 136, 251, 1);
font-size: 14px;
font-weight: 400;
color: rgba(255, 255, 255, 1);
cursor: pointer;
}
}
// 表格
.custom-table {
border-collapse: collapse;
width: 416px;
min-height: 60px;
max-height: 500px;
overflow-y: auto;
cursor: pointer;
color: rgba(255, 255, 255, 1);
}
.custom-table th,
.custom-table td {
border: 1px solid rgba(163, 192, 243, 1);
text-align: left;
padding: 8px;
text-align: center;
}
.table1 {
margin-top: 20px;
width: 100%;
border: 1px solid rgba(255, 255, 255);
border-radius: 5px;
background: rgba(255, 255, 255, 0.1);
.tabReboot,
.tabDelete {
border: none;
background-color: rgba(0, 0, 0, 0);
font-size: 14px;
font-weight: 400;
letter-spacing: 0;
line-height: 20px;
color: rgba(67, 136, 251, 1);
}
.tabReboot {
margin-right: 8px;
}
.isTrIndex {
background: rgba(67, 136, 251, 1);
}
}
::v-deep(.ant-transfer) {
// 屏蔽自带的hover效果
.ant-transfer-list-content-item:hover {
background: black;
}
}
.btn {
width: 92px;
height: 40px;
border-radius: 4px;
opacity: 1;
margin-top: 10px;
margin-left: 15px;
font-size: 14px;
font-weight: 400;
opacity: 1;
border: 1px solid rgba(207, 212, 219, 1);
line-height: 20.27px;
color: white;
text-align: center;
vertical-align: top;
background-color: rgba(255, 255, 255, 0.1);
}
.selected {
background: linear-gradient(180deg, rgba(201, 245, 255, 1) 0%, rgba(138, 215, 255, 1) 100%);
color: rgba(0, 61, 90, 1);
border: 1px solid white;
}
.btn:hover {
background-color: rgba(207, 212, 219, 1);
}
.btn:active {
background-color: rgba(102, 102, 102, 1);
color: white;
}
.device-list {
margin-left: 15px;
margin-top: 15px;
width: calc(100% - 15px);
font-size: 13px;
height: auto;
display: flex;
gap: 15px;
flex-direction: column;
.device-list-item {
width: calc(100% - 15px);
box-sizing: border-box;
padding: 10px;
border: 2px solid #03407e;
border-radius: 4px;
background: rgba(0, 177, 255, 0.2);
display: flex;
gap: 10px;
flex-direction: column;
.list-item-title {
color: white;
display: flex;
justify-content: space-between;
.item-title {
img {
width: 25px;
}
span {
margin-left: 10px;
font-size: 16px;
}
}
.revoke {
text-align: center;
border: none;
border-radius: 4px;
padding: 5px 15px;
background: linear-gradient(
180deg,
rgba(255, 187, 0, 1) 0%,
rgba(255, 112, 3, 1) 91.21%,
rgba(255, 129, 3, 1) 100%
);
cursor: pointer;
}
}
.list-item-main {
display: flex;
> div {
flex: 1;
display: flex;
gap: 8px;
> .info {
text-align: center;
width: 6em;
height: 2.5em;
line-height: 2.5em;
border-radius: 4px;
color: white;
background: linear-gradient(
180deg,
rgba(86, 221, 253, 1) 0%,
rgba(25, 176, 255, 1) 100%
);
}
> .text {
:first-child {
color: white;
line-height: 2.5em;
}
img {
padding: 0 5px;
}
:last-child {
line-height: 2.5em;
color: red;
}
}
}
}
}
}
</style>

View File

@@ -0,0 +1,381 @@
<template>
<div class="div-add">
<button class="add" @click="addModal">添加</button>
<a-popconfirm
title="是否提交以上修改?"
placement="bottomLeft"
ok-text="确定"
cancel-text="取消"
@confirm="sendTable">
<button class="add" style="margin-left: 20px">执行</button>
</a-popconfirm>
</div>
<div class="buttons">
<span style="color: red; padding-top: 20px">*以下修改需执行后生效</span>
<div class="plans">
<button class="plan enabled" style="margin-right: 10px" @click="togglePlan(1)">
计划启用
</button>
<button class="plan disabled" @click="togglePlan(3)"> 计划禁用 </button>
</div>
</div>
<table class="custom-table table1">
<thead>
<tr :style="{ background: 'rgba(35,45,69)' }">
<th>序号</th>
<th>执行时间</th>
<th>计划名称</th>
<th>状态</th>
<th>操作</th>
</tr>
</thead>
<tbody>
<tr v-for="(row, index) in dataSource" v-show="row.executeStatus.value != 0" :key="index">
<td>{{ index + 1 }}</td>
<td>{{ row.startTime }}</td>
<td>{{ row.planName }}</td>
<td>
<button
:style="{
'font-size': '12px',
width: '5em',
background: 'rgb(47, 47, 47)',
color: setStateColor(row.executeStatus.value),
border: '1px solid',
}">
{{ setStateText(row.executeStatus.value) }}
</button>
</td>
<td>
<div class="tabReboot" @click="startPlan(row)">启用</div>
<a-popconfirm
title="此操作将移除该数据"
ok-text="确定"
cancel-text="取消"
placement="topRight"
@confirm="deletePlan(row)">
<div class="tabDelete">删除</div>
</a-popconfirm>
</td>
</tr>
</tbody>
</table>
<div class="out-dialog" v-if="addVisible">
<div class="content">
<div class="div-operation"></div>
<span class="text-operation">计划库</span>
</div>
<div style="margin-top: 20px">
<a-transfer
v-model:target-keys="targetKeys"
:data-source="transferData"
show-search
:filter-option="filterOption"
:render="(item: any) => item.title"
@change="handleChange"
:style="{ color: 'rgba(255,255,255,1)' }"
@search="handleSearch"
:listStyle="{ border: '2px solid rgba(25,74,125,1)', height: 'calc(100vh - 200px)' }" />
</div>
<div style="width: 100%; height: 60px"></div>
<div class="button-box">
<button class="cancel" @click="addVisible = false">取消</button>
<button class="execute" @click="sendPlan">确定</button>
</div>
</div>
</template>
<script setup lang="ts">
import { ref, onMounted } from 'vue';
// 请求
import { http } from '/nerv-lib/util/http';
import { planManage } from '/@/api/planManage';
import { waterSys } from '/@/api/waterSystem';
import { message } from 'ant-design-vue';
import { getEnum } from '/@/api';
// 全局变量
import { items } from '/@/store/item';
// 初始化 ===========================================================
onMounted(() => {
// 获得枚举
getStateEnum();
// 获得穿梭框 与 表格数据
reset();
});
// 全局变量
const state = items();
// 获得任务状态枚举
const getStateEnum = async () => {
let enumData = await getEnum({ params: { enumType: 'PlanExecuteStatus' } });
stateList.value = enumData.data;
};
/**
* @method resetAll 刷新3个tab中的全部数据修改计划会影响tab1数据信息生成tab3日志
*/
const emit = defineEmits(['resetAll']);
// tab页部分 ========================================================
// 状态枚举
const stateList = ref([]);
// 设置枚举的颜色 与 文本
const setStateColor = (state: number) => {
if (state == 0) {
return '#ccc';
} else if (state == 1) {
return 'orange';
} else if (state == 2) {
return 'rgb(57, 215, 187)';
} else if (state == 3) {
return 'rgb(255, 0, 0)';
}
};
// 设置枚举的文本
const setStateText = (state: number) => {
const res = stateList.value.find((item) => {
return item.value == state;
});
return res.label;
};
// 计划启用/禁用事件
const togglePlan = (state: number) => {
dataSource.value.forEach((item: any) => {
// 执行中 无法修改为待执行
if (state == 1) {
if (item.executeStatus.value != 2) {
item.executeStatus.value = state;
}
// 任何状态都可以修改为 暂停
} else {
item.executeStatus.value = state;
}
});
};
// 表格数据
const dataSource = ref([]);
// 获得表格数据
const getTable = () => {
http
.get(planManage.getTableData, {
projectId: state.projectId,
siteId: state.siteId,
// 设备类型(1照明,2空调,3排风扇,4风幕机,5电动窗,6给排水)
ctrlType: 6,
})
.then((res) => {
dataSource.value = res.data;
});
};
// 删除表格中的计划(将当前任意状态,修改为未启用 =0
const deletePlan = (row: any) => {
row.executeStatus.value = 0;
};
// 重启表格中的计划(将当前任意状态,修改为待执行 = 1
const startPlan = (row: any) => {
if (row.executeStatus.value == 1) {
return message.info('该数据已是待执行状态,无需再次修改');
}
if (row.executeStatus.value == 2) {
return message.info('执行中的状态已被启用,无需修改');
}
row.executeStatus.value = 1;
};
// 将对表格的修改统一发送
const sendTable = () => {
if (!dataSource.value.length) {
return message.info('没有任何数据可以提交');
}
state.setLoading(true);
http
.post(
waterSys.submitTableData +
`?projectId=${state.projectId}${state.siteId ? `&siteId=${state.siteId}` : ''}`,
dataSource.value,
)
.then((res) => {
state.setLoading(false);
if (res.retcode == 0) {
message.success('操作成功');
// 刷新数据
emit('resetAll');
} else {
message.info(res.msg);
}
})
.catch(() => {
state.setLoading(false);
});
};
const reset = () => {
// 计划表格
getTable();
// 穿梭框原始数据
getLeftPlan();
};
// tab页弹窗部分 ====================================================
// 添加弹窗控制变量
const addVisible = ref(false);
// 打开弹窗
const addModal = () => {
addVisible.value = true;
};
// 穿梭框部分 =======================================================
// 穿梭框数据
const transferData = ref([]) as any;
// 获得穿梭框原始数据
const getLeftPlan = () => {
http
.get(planManage.getTransData, {
projectId: state.projectId,
siteId: state.siteId,
// 设备类型(1照明,2空调,3排风扇,4风幕机,5电动窗,6给排水)
ctrlType: 6,
})
.then((res) => {
let arr: Array<Object> = [];
res.data.forEach((item: any) => {
arr.push({
key: item.id,
title: item.planName,
});
});
transferData.value = arr;
});
};
const handleChange = (keys: string[], direction: string, moveKeys: string[]) => {
console.log(keys, direction, moveKeys);
};
const handleSearch = (dir: string, value: string) => {
console.log('search:', dir, value);
};
// 穿梭框选中数据
const targetKeys = ref<string[]>([]);
// 将穿梭框选中的计划提交
const sendPlan = () => {
if (targetKeys.value.length < 1) {
return message.info('没有选择任何计划');
}
http.post(planManage.submitTransData, targetKeys.value).then(() => {
message.success('添加成功');
// 如果发送成功,则刷新表格
reset();
});
};
const filterOption = (inputValue: string, option: any) => {
return option.description.indexOf(inputValue) > -1;
};
// 向外暴露方法
defineExpose({
reset,
});
</script>
<style lang="less" scoped>
@import '../../style/dialogStyle.less';
.buttons {
display: flex;
justify-content: space-between;
.plan {
border: none;
font-size: 14px;
font-weight: 400;
border-radius: 5px;
width: 88px;
height: 32px;
color: white;
cursor: pointer;
margin: 15px 0;
vertical-align: middle;
}
.plan.enabled {
background: linear-gradient(180deg, rgba(103, 222, 0, 1) 0%, rgba(0, 181, 6, 1) 100%);
}
.plan.disabled {
background-color: red;
}
.plan:disabled {
cursor: not-allowed;
}
}
// 右下角添加按钮
.div-add {
height: 64px;
display: flex;
justify-content: flex-end;
align-items: center;
position: fixed;
bottom: 0;
right: 0;
margin-right: 10px;
.add {
width: 74px;
height: 40px;
opacity: 1;
border-radius: 4px;
background: rgba(67, 136, 251, 1);
border: rgba(67, 136, 251, 1);
font-size: 14px;
font-weight: 400;
color: rgba(255, 255, 255, 1);
cursor: pointer;
}
}
// 表格
.custom-table {
border-collapse: collapse;
width: 416px;
height: 40px;
color: rgba(255, 255, 255, 1);
}
.custom-table th,
.custom-table td {
border: 1px solid rgba(163, 192, 243, 1);
text-align: left;
padding: 8px;
text-align: center;
}
.table1 {
width: 100%;
border: 1px solid rgba(255, 255, 255);
border-radius: 5px;
background: rgba(255, 255, 255, 0.1);
// 表格中的操作按钮
.tabReboot,
.tabDelete {
border: none;
display: inline-block;
background-color: rgba(0, 0, 0, 0);
font-size: 14px;
font-weight: 400;
letter-spacing: 0;
line-height: 20px;
color: rgba(67, 136, 251, 1);
cursor: pointer;
}
.tabReboot {
margin-right: 8px;
}
.tabReboot::active {
color: white !important;
}
.tabDelete::active {
color: white;
}
}
::v-deep(.ant-transfer) {
// 屏蔽自带的hover效果
.ant-transfer-list-content-item:hover {
background: black;
}
}
</style>

View File

@@ -0,0 +1,282 @@
import { ref } from 'vue';
// 流动线条样式与定位
export const linePosition = [
// 雨水池 - 控制阀
{
left: '4%',
top: '44%',
transform: 'rotateZ(-30deg)',
transformOrigin: 'left',
zIndex: '6',
width: '6%',
},
{
left: '4%',
top: '84%',
transform: 'rotateZ(-30deg)',
transformOrigin: 'left',
zIndex: '6',
width: '6%',
},
// 控制阀 - 进水阀
{ left: '12%', top: '34%', width: '10%' },
{ left: '12%', top: '74%', width: '10%' },
// 进水阀 - 集水池
{ left: '23%', top: '34%', width: '8%' },
{ left: '23%', top: '74%', width: '8%' },
// 集水池 - 排水泵 - 横线
{ left: '35%', top: '34%', width: '4%' },
{ left: '35%', top: '74%', width: '4%' },
// 上半集水池右侧分线
{ left: '39%', top: '34%', transform: 'rotateZ(90deg)', transformOrigin: 'left', width: '4%' },
{ left: '39%', top: '34%', transform: 'rotateZ(-90deg)', transformOrigin: 'left', width: '4%' },
// 下半集水池右侧分线
{ left: '39%', top: '74%', transform: 'rotateZ(90deg)', transformOrigin: 'left', width: '4%' },
{ left: '39%', top: '74%', transform: 'rotateZ(-90deg)', transformOrigin: 'left', width: '4%' },
// 上半-左侧水泵分线
{ left: '39%', top: '25%', width: '8%' },
{ left: '39%', top: '43%', width: '8%' },
// 下半-左侧水泵分线
{ left: '39%', top: '65%', width: '8%' },
{ left: '39%', top: '83%', width: '8%' },
// 水泵右侧合线 下半
{ left: '47%', top: '83%', transform: 'rotateZ(-90deg)', transformOrigin: 'left', width: '14%' },
// 水泵右侧合线 上半
{ left: '47%', top: '25%', transform: 'rotateZ(90deg)', transformOrigin: 'left', width: '12%' },
// 汇入总闸连线
{ left: '47%', top: '52%', width: '4%' },
// 汇入总集水池
{ left: '51%', top: '52%', transform: 'rotateZ(-25deg)', transformOrigin: 'left', width: '7%' },
// 汇入总排水闸
{ left: '58%', top: '45%', width: '9%' },
// 汇入市政管道 - 途径水泵2
{ left: '68%', top: '45%', width: '28%' },
// 总排水闸 - 总排水泵1 上半
{ left: '75%', top: '45%', transform: 'rotateZ(-90deg)', transformOrigin: 'left', width: '10%' },
{ left: '75%', top: '23%', width: '11%' },
{ left: '86%', top: '22.5%', transform: 'rotateZ(90deg)', transformOrigin: 'left', width: '10%' },
// 总排水闸 - 总排水泵3 下半
{ left: '75%', top: '45%', transform: 'rotateZ(90deg)', transformOrigin: 'left', width: '9%' },
{ left: '75%', top: '65%', width: '11%' },
{
left: '86%',
top: '65%',
transform: 'rotateZ(-90deg)',
transformOrigin: 'left',
width: '9%',
},
];
/**
* 1. 设备数量是固定的
* 2. 设备顺序是固定的
* 3. 此处数据用于渲染设备图标,后端返回数据后,将依次插入
* @param icon 决定调用的设备图标:污水池=1/集水池=2/控制阀=3//进水阀=4//排水泵=5
* @param type 设备类型:污水池=1/阀门=2/集水池=3/水泵=4
* @param open 水泵的开关状态 开=true/关=false
* @param control 是否可以被操作是否显示顶部按钮水池为false
* @param edited 是否已经被编辑(决定显示编辑 或 撤销)
*/
// 污水池
export const device1 = ref([
{
control: false,
type: 1,
icon: 1,
styleObject: {
left: '1%',
top: '40%',
zIndex: '9',
},
},
{
control: false,
type: 1,
icon: 1,
styleObject: {
left: '1%',
top: '80%',
zIndex: '9',
},
},
]);
// 阀门
export const device2 = ref([
{
control: true,
open: true,
type: 2,
icon: 3,
edited: false,
styleObject: {
left: '8%',
top: '28%',
},
},
{
control: true,
open: false,
type: 2,
icon: 3,
edited: false,
styleObject: {
left: '8%',
top: '68%',
},
},
{
control: true,
open: true,
type: 2,
icon: 4,
edited: false,
styleObject: {
left: '20%',
top: '28%',
},
},
{
control: true,
open: true,
type: 2,
icon: 4,
edited: false,
styleObject: {
left: '20%',
top: '68%',
},
},
{
control: true,
open: true,
type: 2,
icon: 3,
edited: false,
styleObject: {
left: '48%',
top: '46%',
},
},
{
control: true,
open: true,
type: 2,
icon: 3,
edited: false,
styleObject: {
left: '65%',
top: '38%',
},
},
]);
// 集水池
export const device3 = ref([
{
control: false,
type: 3,
icon: 2,
styleObject: {
left: '30%',
top: '68%',
},
},
{
control: false,
type: 3,
icon: 2,
styleObject: {
left: '30%',
top: '28%',
},
},
{
control: false,
type: 3,
icon: 2,
styleObject: {
left: '56%',
top: '40%',
},
},
]);
// 水泵
export const device4 = ref([
{
control: true,
open: false,
type: 4,
icon: 5,
edited: false,
styleObject: {
left: '40%',
top: '20%',
},
},
{
control: true,
open: true,
type: 4,
icon: 5,
edited: false,
styleObject: {
left: '40%',
top: '40%',
},
},
{
control: true,
open: true,
type: 4,
icon: 5,
edited: false,
styleObject: {
left: '40%',
top: '60%',
},
},
{
control: true,
open: true,
type: 4,
icon: 5,
edited: false,
styleObject: {
left: '40%',
top: '80%',
},
},
// 右上3水泵
{
control: true,
open: true,
type: 4,
icon: 5,
edited: false,
styleObject: {
left: '78%',
top: '20%',
},
},
{
control: true,
open: true,
type: 4,
icon: 5,
edited: false,
styleObject: {
left: '78%',
top: '40%',
},
},
{
control: true,
open: true,
type: 4,
icon: 5,
edited: false,
styleObject: {
left: '78%',
top: '60%',
},
},
]);

View File

@@ -0,0 +1,150 @@
<template>
<div class="left-top">
<div class="info">
<div class="title-item">
<div class="title-back">设备状态</div>
</div>
<div class="info-item">
<div v-for="(item, index) in deviceState" :key="index">
<img v-if="item.type == props.state" :src="item.icon" alt="" />
<img v-else :src="item.default" alt="" />
<div class="mode-item-text">{{ item.name }}</div>
</div>
</div>
</div>
<div class="mode">
<div class="title-item">
<div class="title-back">控制模式</div>
<div class="title-button">计划启用</div>
</div>
<div class="mode-item"></div>
</div>
</div>
</template>
<script setup lang="ts">
import { ref, onMounted } from 'vue';
// 全局变量
import { items } from '/@/store/item';
// 图像资源引入
import type1 from './images/type1.png';
import type2 from './images/type2.png';
import type3 from './images/type3.png';
import type1off from './images/type1off.png';
import type2off from './images/type2off.png';
import type3off from './images/type3off.png';
// 传入的值
const props = defineProps({
// 设备状态
state: {
type: Number,
},
});
const deviceState = [
{
icon: type1,
default: type1off,
name: '运行',
type: 1,
},
{
icon: type2,
default: type2off,
name: '故障',
type: 2,
},
{
icon: type3,
default: type3off,
name: '强排',
type: 3,
},
];
//页面 创建
onMounted(() => {});
</script>
<style lang="less" scoped>
.left-top {
--pad: 12px;
position: absolute;
left: var(--pad);
top: var(--pad);
width: auto;
height: 110px;
display: flex;
z-index: 99;
gap: var(--pad);
> div {
background: black;
display: flex;
flex-direction: column;
gap: 3px;
box-sizing: border-box;
padding: 10px;
border-radius: 4px;
.title-item {
padding: 5px;
position: relative;
.title-back {
margin-left: 10px;
padding-left: 5px;
background: linear-gradient(to right, #1aaefb, transparent);
width: 7em;
color: white;
position: relative;
font-size: 14px;
vertical-align: middle;
}
.title-back::before {
position: absolute;
content: '';
display: block;
width: 5px;
height: 100%;
left: -12px;
background: #1aaefb;
}
.title-button {
position: absolute;
right: 2px;
top: 2px;
padding: 3px 8px;
color: white;
border-radius: 4px;
user-select: none;
cursor: pointer;
background: linear-gradient(180deg, rgba(103, 222, 0, 1) 0%, rgba(0, 181, 6, 1) 100%);
}
}
}
// 设备状态
.info {
width: 230px;
.info-item {
display: flex;
padding-top: 3px;
div {
flex: 1;
display: flex;
flex-direction: column;
gap: 5px;
align-items: center;
.mode-item-text {
color: white;
user-select: none;
}
img {
width: 23px;
display: block;
user-select: none;
}
}
}
}
// 控制模式
.mode {
width: 200px;
.mode-item {
}
}
}
</style>

View File

@@ -0,0 +1,291 @@
<template>
<div class="deviceItem" :style="info.styleObject">
<!-- 点击编辑弹出框 -->
<a-popover
color="rgba(0, 0, 0, 0.8)"
placement="right"
v-model:visible="visible"
trigger="click">
<template #content>
<div class="item-box">
<div class="item-box-title">
<img v-if="info.type == 2" src="./images/device1.png" alt="" />
<img v-if="info.type == 4" src="./images/device2.png" alt="" />
<span>{{ info.name }}</span>
</div>
<!-- 开关 -->
<div v-if="info.type == 4" class="item-box-switch">
<div>开关</div>
<a-switch style="margin-top: 3px" size="small" v-model:checked="info.open" />
</div>
<div v-if="info.type == 2" class="item-box-range">
<div>开度</div>
<!-- <a-slider v-model:value="info.value" :tooltip-visible="true" :marks="range" :step="1"> -->
<a-slider v-model:value="info.value" :marks="range" :step="1">
<template #mark="{ label, point }">
<template v-if="point === 100">
<strong>{{ label }}</strong>
</template>
<template v-else>{{ label }}</template>
</template>
</a-slider>
</div>
<div v-if="info.type == 4" class="item-box-range">
<div>频率</div>
<a-slider v-model:value="info.value" :marks="range" :step="1">
<template #mark="{ label, point }">
<template v-if="point === 100">
<strong>{{ label }}</strong>
</template>
<template v-else>{{ label }}</template>
</template>
</a-slider>
</div>
<div class="item-box-button">
<a-button class="item-btn" @click="refresh">刷新</a-button>
<a-popconfirm
title="此操作只保存修改,需右下角按钮提交"
ok-text="确定"
cancel-text="取消"
@confirm="editDevice">
<a-button class="item-btn" type="primary">执行</a-button>
</a-popconfirm>
</div>
</div>
</template>
<div v-if="!info.edited && info.control" class="device-button-back">
<img src="./images/edit.png" alt="" />
编辑</div
>
</a-popover>
<a-popconfirm
title="撤销将移除已保存的修改"
ok-text="确定"
cancel-text="取消"
@confirm="backConfirm">
<div v-if="info.edited && info.control" class="device-button">
<img src="./images/back.png" alt="" />
撤销</div
>
</a-popconfirm>
<!-- 设备图标 - 当前共5种单位 -->
<img v-if="info.icon == 1" src="./images/pond1.png" alt="" />
<img v-if="info.icon == 2" src="./images/pond2.png" alt="" />
<img v-if="info.icon == 3" src="./images/valve1.png" alt="" />
<img v-if="info.icon == 4" src="./images/valve2.png" alt="" />
<img v-if="info.icon == 5" style="width: 70px; height: 70px" src="./images/pump.png" alt="" />
<div class="info-name">
<span>{{ info.name }}</span>
<div class="img-box">
<img
v-if="(info.type == 2 || info.type == 4) && info.state == 0"
src="./images/alarm1.png"
alt="" />
<img
v-if="(info.type == 2 || info.type == 4) && info.state > 0"
src="./images/alarm2.png"
alt="" />
</div>
</div>
<!-- 只有水池会显示容量 -->
<div class="info-value" v-if="info.type == 1 || info.type == 3">
容量 : {{ info.value + info.unit }}</div
>
</div>
</template>
<script setup lang="ts">
import { ref, onMounted, computed } from 'vue';
import { message } from 'ant-design-vue';
// 图像资源引入
// 初始化 ===============================================================
//页面 创建
onMounted(() => {});
// 传入的值
const props = defineProps({
// 设备类型
info: {
type: Object,
default: () => {},
},
});
// 父组件传递的数据
const info = computed(() => props.info);
// 组件交互 =============================================================
// 用于控制弹窗的显示状态
const visible = ref(false);
// 滑动条显示规则
const range = ref({
0: {
style: {
color: '#0DFFFF',
},
label: 'min',
},
100: {
style: {
color: '#0DFFFF',
},
label: 'max',
},
});
// 刷新事件
const refresh = () => {
info.value.value = info.value.oldVal;
};
// 撤销事件
const backConfirm = () => {
info.value.value = info.value.oldVal;
info.value.edited = false;
// 如果为水泵
if (info.value.type == 4) {
console.log(info.value);
info.value.open = info.value.opened;
}
};
// 编辑-执行事件
const editDevice = () => {
// 未产生修改
if (info.value.value == info.value.oldVal && info.value.open === info.value.opened) {
return message.info('未产生任何修改');
}
// 产生修改
visible.value = false;
info.value.edited = true;
message.success('保存成功');
};
</script>
<style lang="less" scoped>
// 设备的图像
.deviceItem {
--size: 90px;
width: var(--size);
height: var(--size);
position: absolute;
top: 20%;
text-align: center;
// 默认使用3
z-index: 3;
> img {
user-select: none;
}
// 设备名
.info-name {
color: white;
font-size: 15px;
text-align: center;
position: relative;
> span {
vertical-align: middle;
}
.img-box {
display: inline-block;
position: absolute;
margin-left: 8px;
width: 22px;
> img {
width: 22px;
}
}
}
// 设备容量(仅水池)
.info-value {
text-align: center;
color: #23fdab;
font-size: 13px;
}
// 撤销按钮
.device-button {
position: absolute;
top: -35px;
left: 0;
right: 0;
margin: auto;
width: 5em;
padding: 5px 0;
text-align: center;
border-radius: 4px;
font-weight: 700;
cursor: pointer;
background: linear-gradient(#ffd700, #ffa403);
color: #674330;
user-select: none;
> img {
width: 13px;
}
}
// 编辑按钮
.device-button-back {
position: absolute;
top: -35px;
left: 0;
right: 0;
margin: auto;
width: 5em;
padding: 5px 0;
text-align: center;
border-radius: 4px;
font-weight: 700;
cursor: pointer;
background: linear-gradient(#00f92c, #00fe9f);
color: #003d5a;
user-select: none;
> img {
width: 13px;
}
}
> img {
width: 100%;
height: 100%;
}
}
// 弹出框样式
.ant-popover-inner {
// 弹出框内部容器
.item-box {
width: 250px;
display: flex;
flex-direction: column;
gap: 15px;
color: white;
img {
width: 20px;
margin-right: 10px;
vertical-align: middle;
}
}
// 开关控件
.item-box-switch {
padding: 10px 15px;
display: flex;
justify-content: space-between;
border-radius: 4px;
background: rgba(67, 136, 251, 0.3);
.ant-switch-checked {
background: linear-gradient(180deg, rgba(1, 206, 255, 1) 0%, rgba(0, 150, 229, 1) 100%);
}
}
// 滑动条控件
.item-box-range {
padding: 10px 15px 10px 15px;
box-sizing: border-box;
border-radius: 4px;
background: rgba(67, 136, 251, 0.3);
.ant-slider-handle {
border: 2px solid red !important;
}
}
// 底部按钮区
.item-box-button {
text-align: right;
.item-btn {
text-align: center;
margin-left: 10px;
}
}
}
</style>

View File

@@ -0,0 +1,30 @@
<template>
<div class="line-item" :style="position"></div>
</template>
<script setup lang="ts">
import { computed } from 'vue';
// 传入的值
const props = defineProps({
// 线条
position: {
type: Object,
default: () => {},
},
});
const position = computed(() => {
return props.position;
});
</script>
<style lang="less" scoped>
.line-item {
position: absolute;
height: 10px;
width: 250px;
z-index: 1;
background-image: url(./images/back.gif);
background-color: rgba(13, 255, 164, 0.3);
background-size: 200px 10px;
border-radius: 5px;
}
</style>

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.8 KiB

View File

@@ -0,0 +1,663 @@
<template>
<div class="main">
<!-- 左上角设备信息面板 -->
<deviceInfo :state="deviceState" />
<!-- 示意图 -->
<div class="map">
<!-- 污水池图标 -->
<deviceItem v-for="(item, index) in device1" :key="index" :info="item" />
<!-- 阀门图标 -->
<deviceItem v-for="(item, index) in device2" :key="index" :info="item" />
<!-- 集水池图标 -->
<deviceItem v-for="(item, index) in device3" :key="index" :info="item" />
<!-- 排水泵图标 -->
<deviceItem v-for="(item, index) in device4" :key="index" :info="item" />
<!-- 市政管道图标 -->
<div class="pipe">
<div>市政管道</div>
<img src="./images/pipe.png" alt="" />
</div>
<!-- 设备图标底部连线 -->
<deviceLine v-for="(item, index) in linePosition" :key="index" :position="item" />
</div>
<!-- 右下角按钮 -->
<div class="buttons">
<a-button type="primary" @click="openDrawer1">执行</a-button>
<a-button type="primary" @click="resetAll">全部撤销</a-button>
</div>
<!-- 页面右侧抽屉开关 -->
<div class="right-button">
<div>计划与日志</div>
<img @click="visible = true" src="./images/open.png" alt="" />
</div>
<!-- 右侧 计划日志抽屉 -->
<a-drawer
v-model:visible="visible"
class="drawer-item"
width="496"
placement="right"
:body-style="{ background: 'rgba(0, 0, 0)', opacity: 0.8 }"
:closable="false"
id="drawer"
:maskStyle="{ 'background-color': 'rgba(0, 0, 0, 0)' }">
<a-tabs v-model:activeKey="activeKey">
<a-tab-pane key="1" tab="计划列表" force-render>
<planTab ref="tabs1Ref" @reset-all="resetDrawer" />
</a-tab-pane>
<a-tab-pane key="2" tab="日志">
<logTab ref="tabs2Ref" @reset-all="resetDrawer" />
</a-tab-pane>
</a-tabs>
</a-drawer>
<!-- 右侧 操作队列 -->
<a-drawer
v-model:visible="visible1"
class="drawer-item"
width="496"
placement="right"
:body-style="{ background: 'rgba(0, 0, 0)', opacity: 0.8 }"
:closable="false"
id="drawer"
:maskStyle="{ 'background-color': 'rgba(0, 0, 0, 0)' }">
<a-tabs v-model:activeKey="activeKey1">
<a-tab-pane key="1" tab="操作队列" force-render>
<div>
<a-badge :offset="[-5, 12]" :count="valveList.length">
<button :class="{ btn: true, selected: activeButton == 1 }" @click="changeBtn(1)"
>阀门</button
>
</a-badge>
<a-badge :offset="[-5, 12]" :count="pumpList.length">
<button :class="{ btn: true, selected: activeButton == 2 }" @click="changeBtn(2)"
>水泵</button
>
</a-badge>
</div>
<div class="device-list" v-if="activeButton == 1">
<div class="device-list-item" v-for="(item, index) in valveList" :key="index">
<div class="list-item-title">
<div class="item-title">
<img src="./images/device1.png" alt="" />
<span>{{ item.name }}</span>
</div>
<div class="revoke" @click="revoke(item.id, index, 1)">撤销</div>
</div>
<div class="list-item-main">
<div>
<div class="info">开度</div>
<div class="text">
<span>{{ item.oldVal + item.unit }}</span>
<img src="/asset/image/bulbLogo/22406.png" alt="" />
<span>{{ item.value + item.unit }}</span>
</div>
</div>
<div></div>
</div>
</div>
<a-empty style="margin-top: 100px" v-if="valveList.length == 0">
<template #description> <span style="color: white">暂无数据</span></template>
</a-empty>
</div>
<div class="device-list" v-if="activeButton == 2">
<div class="device-list-item" v-for="(item, index) in pumpList" :key="index">
<div class="list-item-title">
<div class="item-title">
<img src="./images/device2.png" alt="" />
<span>{{ item.name }}</span>
</div>
<div class="revoke" @click="revoke(item.id, index, 2)">撤销</div>
</div>
<div class="list-item-main">
<div>
<div class="info">频率</div>
<div class="text">
<span>{{ item.oldVal + item.unit }}</span>
<img src="/asset/image/bulbLogo/22406.png" alt="" />
<span>{{ item.value + item.unit }}</span>
</div>
</div>
<div>
<div class="info">开关</div>
<div class="text">
<span>{{ item.opened ? '开' : '关' }}</span>
<img src="/asset/image/bulbLogo/22406.png" alt="" />
<span>{{ item.open == 1 ? '开' : '关' }}</span>
</div>
</div>
</div>
</div>
<a-empty style="margin-top: 100px" v-if="pumpList.length == 0">
<template #description> <span style="color: white">暂无数据</span></template>
</a-empty>
</div>
<div style="width: 100%; height: 100px"></div>
<div class="button-box">
<button class="cancel" @click="visible1 = false">取消</button>
<a-popconfirm
title="此操作将提交以上修改内容"
ok-text="确定"
cancel-text="取消"
placement="bottomRight"
@confirm="submitChange">
<button class="execute">执行</button>
</a-popconfirm>
</div>
</a-tab-pane>
</a-tabs>
</a-drawer>
</div>
</template>
<script setup lang="ts">
import { ref, onMounted } from 'vue';
import { Modal, message } from 'ant-design-vue';
import deviceInfo from './deviceInfo.vue';
import deviceLine from './deviceLine.vue';
import deviceItem from './deviceItem.vue';
import planTab from './component/planTab.vue';
import logTab from './component/logTab.vue';
import { linePosition, device1, device2, device3, device4 } from './device';
// 网络请求
import { http } from '/nerv-lib/util/http';
import { waterSys } from '/@/api/waterSystem';
// 全局变量
import { items } from '/@/store/item';
// 初始化 ===========================================================================================
const state = items();
onMounted(() => {
// 获得所有设备状态
getAllDevice();
});
const deviceState = 1;
// 计划与日志 抽屉业务 =========================================================================================
// 计划与日志
const visible = ref(false);
// 当前选中的tab
const activeKey = ref('1');
// 执行 抽屉业务 =========================================================================================
// 执行
const visible1 = ref(false);
// 当前选中的tab
const activeKey1 = '1';
// 当前选中的设备类型 阀门=1/水泵=2
const activeButton = ref(1);
// 切换设备类型
const changeBtn = (key: number) => {
activeButton.value = key;
};
// 当前修改的水泵数据
const pumpList = ref<any>([]);
// 当前修改的水阀数据
const valveList = ref<any>([]);
// 打开右侧抽屉
const openDrawer1 = () => {
valveList.value = device2.value.filter((item: any) => {
return item.edited;
});
pumpList.value = device4.value.filter((item: any) => {
return item.edited;
});
if (valveList.value.length || pumpList.value.length) {
visible1.value = true;
} else {
message.info('未产生任何修改');
}
};
// 右侧抽屉 - 撤回
const revoke = (id: any, index: number, type: number) => {
if (type == 1) {
valveList.value.splice(index, 1);
device2.value.forEach((item: any) => {
if (item.id == id) {
item.value = item.oldVal;
item.edited = false;
}
});
} else if (type == 2) {
pumpList.value.splice(index, 1);
device4.value.forEach((item: any) => {
if (item.id == id) {
item.value = item.oldVal;
item.open = item.opened;
item.edited = false;
}
});
}
};
const submitChange = () => {
let valveList = [];
device2.value.forEach((item: any) => {
if (item.edited) {
valveList.push({
deviceGroup: item.id,
openPercent: item.value,
});
}
});
let pumpList = [];
device4.value.forEach((item: any) => {
if (item.edited) {
pumpList.push({
deviceGroup: item.id,
frequency: item.value,
switchStatus: +item.open,
});
}
});
state.setLoading(true);
http
.post(waterSys.submitList, {
projectId: state.projectId,
siteId: state.siteId,
valveList,
pumpList,
})
.then((res) => {
let data = res.data;
state.setLoading(false);
// 修改请求发送了,但操作时产生了失败结果
if (res.retcode != 0) {
// 直接提示并跳出
return message.warning(res.msg);
}
// 所有修改均生效
if (data.allSucceed) {
message.success('修改完成');
// allSucceed不为true则至少有一条数据修改失败
} else {
message.info(`${data.successList.length}条修改成功,${data.failList.length}条修改失败`);
}
// 将所有已修改状态的数据还原
resetEdit();
visible1.value = false;
getAllDevice();
});
};
const resetEdit = () => {
device2.value.forEach((item) => {
item.edited = false;
});
device4.value.forEach((item) => {
item.edited = false;
});
};
// 设备数据业务 ==========================================================================================
// 当其中一个tab产生了数据修改可以调用该方法重置所有tab
const resetDrawer = () => {
try {
// tab1重置
tabs1Ref.value.reset();
} catch {}
try {
// tab2重置
tabs2Ref.value.reset();
} catch {}
};
// 抽屉tab1组件的引用
const tabs1Ref = ref();
// 抽屉tab2组件的引用
const tabs2Ref = ref();
// 撤销所有修改
const resetAll = () => {
Modal.confirm({
title: '提示信息',
content: '该操作将还原已编辑内容',
onOk() {
// 水阀
device2.value.forEach((item: any) => {
if (item.edited) {
item.value = item.oldVal;
item.edited = false;
}
});
// 水泵 水泵包含
device4.value.forEach((item: any) => {
if (item.edited) {
item.value = item.oldVal;
item.open = item.opened;
item.edited = false;
}
});
},
onCancel() {},
});
};
// 获得所有设备状态
const getAllDevice = () => {
getDevice(1);
getDevice(2);
getDevice(3);
getDevice(4);
};
/**
* 获取一个设备类型的数据
* @param type 污水池=1/阀门=2/集水池=3/水泵=4
*/
const getDevice = (type: number) => {
// 请求地址
let url = '';
if (type == 1) {
url = waterSys.getPool1;
} else if (type == 2) {
url = waterSys.getValve;
} else if (type == 3) {
url = waterSys.getPool2;
} else if (type == 4) {
url = waterSys.getPump;
}
http
.get(url, {
projectId: state.projectId,
siteId: state.siteId,
})
.then((res) => {
let data = res.data;
// 污水池数据
if (type == 1) {
device1.value.forEach((item: any, index: number) => {
let result = data[index];
// 污水池名称
item.name = result.deviceInfoName;
// 污水池容量
item.value = result.record.capacity ? result.record.capacity : '--';
// 单位
item.unit = result.record.capacityUnit ? result.record.capacityUnit : '';
});
}
// 阀门数据
if (type == 2) {
device2.value.forEach((item: any, index: number) => {
let result = data[index];
// 阀门名称
item.name = result.deviceGroupName;
// 阀门ID
item.id = result.deviceGroup;
// 编辑状态重置
item.edited = false;
// 设备状态(是否正常)
item.state = result.record.runStatus.value != null ? result.record.runStatus.value : -1;
// 开度-新值
item.value = result.record.openPercent ? result.record.openPercent : 0;
// 开度-旧值(用于判断旧值是否被修改)
item.oldVal = result.record.openPercent ? result.record.openPercent : null;
// 单位
item.unit = result.record.openPercentUnit ? result.record.openPercentUnit : '';
});
}
// 集水池数据
if (type == 3) {
device3.value.forEach((item: any, index: number) => {
let result = data[index];
// 集水池名称
item.name = result.deviceInfoName;
// 集水池容量
item.value = result.record.capacity ? result.record.capacity : '--';
// 单位
item.unit = result.record.capacityUnit ? result.record.capacityUnit : '';
});
}
// 水泵数据
if (type == 4) {
device4.value.forEach((item: any, index: number) => {
let result = data[index];
// 水泵名称
item.name = result.deviceGroupName;
// 水泵ID
item.id = result.deviceGroup;
// 编辑状态重置
item.edited = false;
// 水泵的开启状态-新值
item.open = result.record.switchStatus.value == 1 ? true : false;
// 水泵的开启状态-旧值(用于判断是否被修改)
item.opened = result.record.switchStatus.value == 1 ? true : false;
// 设备状态(是否正常)
item.state = result.record.runStatus.value != null ? result.record.runStatus.value : -1;
// 频率-新值
item.value = result.record.frequency ? result.record.frequency : 0;
// 频率-旧值(用于判断是否被修改)
item.oldVal = result.record.frequency ? result.record.frequency : null;
// 单位
item.unit = result.record.frequencyUnit ? result.record.frequencyUnit : '';
});
}
});
};
</script>
<style lang="less" scoped>
.main {
width: 100%;
height: 100%;
position: relative;
background: linear-gradient(to bottom, rgb(35, 102, 165), rgb(1, 19, 81));
// 图例区域
.map {
width: 85vw;
height: 38vw;
position: relative;
left: 0;
right: 0;
top: 0;
bottom: 0;
margin: auto;
// 右侧 市政管道
.pipe {
width: 120px;
height: 120px;
position: absolute;
text-align: center;
left: 92%;
top: 40%;
z-index: 3;
transform: translateY(-60px);
img {
height: 100%;
}
div {
width: inherit;
color: white;
position: absolute;
top: -1.5em;
font-size: 16px;
}
}
}
// 右下角按钮
.buttons {
position: absolute;
right: 15px;
bottom: 15px;
display: flex;
gap: 15px;
height: 40px;
> button {
height: 40px;
}
}
.right-button {
height: 25px;
position: absolute;
top: 0;
bottom: 0;
right: 10px;
margin: auto;
display: flex;
z-index: 99;
gap: 10px;
color: #0dffff;
> img {
width: 25px;
height: 25px;
border-radius: 2px;
vertical-align: middle;
cursor: pointer;
user-select: none;
}
}
.drawer-item {
width: 100px;
height: 100px;
border: 2px solid red;
}
}
// 抽屉顶部tab按钮样式
:deep(.ant-tabs-tab-btn) {
color: white;
}
.btn {
width: 92px;
height: 40px;
border-radius: 4px;
opacity: 1;
margin-top: 10px;
margin-left: 15px;
font-size: 14px;
font-weight: 400;
opacity: 1;
border: 1px solid rgba(207, 212, 219, 1);
line-height: 20.27px;
color: white;
text-align: center;
vertical-align: top;
background-color: rgba(255, 255, 255, 0.1);
}
.selected {
background: linear-gradient(180deg, rgba(201, 245, 255, 1) 0%, rgba(138, 215, 255, 1) 100%);
color: rgba(0, 61, 90, 1);
border: 1px solid white;
}
.btn:hover {
background-color: rgba(207, 212, 219, 1);
}
.btn:active {
background-color: rgba(102, 102, 102, 1);
color: white;
}
.device-list {
margin-left: 15px;
margin-top: 15px;
width: 100%;
height: auto;
display: flex;
gap: 15px;
flex-direction: column;
.device-list-item {
width: calc(100% - 15px);
box-sizing: border-box;
padding: 10px;
border: 2px solid #03407e;
border-radius: 4px;
background: rgba(0, 177, 255, 0.2);
display: flex;
gap: 10px;
flex-direction: column;
.list-item-title {
color: white;
display: flex;
justify-content: space-between;
.item-title {
img {
width: 25px;
}
span {
margin-left: 10px;
font-size: 16px;
}
}
.revoke {
text-align: center;
border: none;
border-radius: 4px;
padding: 5px 15px;
background: linear-gradient(
180deg,
rgba(255, 187, 0, 1) 0%,
rgba(255, 112, 3, 1) 91.21%,
rgba(255, 129, 3, 1) 100%
);
cursor: pointer;
}
}
.list-item-main {
display: flex;
font-size: 13px;
> div {
flex: 1;
display: flex;
gap: 8px;
> .info {
text-align: center;
width: 6em;
height: 2.5em;
line-height: 2.5em;
border-radius: 4px;
color: white;
background: linear-gradient(
180deg,
rgba(86, 221, 253, 1) 0%,
rgba(25, 176, 255, 1) 100%
);
}
> .text {
:first-child {
color: white;
line-height: 2.5em;
}
img {
padding: 0 5px;
}
:last-child {
line-height: 2.5em;
color: red;
}
}
}
}
}
}
.button-box {
width: 100%;
box-sizing: border-box;
padding: 10px;
height: 60px;
position: absolute;
background-color: transparent;
text-align: right;
bottom: 0;
left: 0;
right: 0;
.execute,
.cancel {
margin-right: 10px;
width: 74px;
height: 40px;
opacity: 1;
cursor: pointer;
border-radius: 4px;
font-size: 14px;
font-weight: 400;
border: 0;
margin-left: 10px;
}
.execute {
background: rgb(67, 136, 251);
color: white;
}
.cancel {
background: white;
color: black;
}
}
</style>

View File

@@ -52,6 +52,10 @@
if (graphRef.value) {
graphRef.value.downloadChart();
}
} else {
if (tableRef.value) {
tableRef.value.export1();
}
}
};

View File

@@ -16,6 +16,7 @@
</template>
<script lang="ts">
import { exportExcel } from '/@/util/ExcelUtil.js';
import { defineComponent, watch, ref, onMounted } from 'vue';
import type { TableColumnType } from 'ant-design-vue';
import { Pagination } from 'ant-design-vue';
@@ -245,6 +246,9 @@
columns.value = columnA;
total.value = dataList.value.length;
onChange(1, 10);
}; // 导出excel文件
const export1 = () => {
exportExcel(columns.value, dataList.value, '历史数据导出', true, 'deviceName', 1, 4);
};
onMounted(() => {
init();
@@ -257,6 +261,7 @@
total,
onChange,
x,
export1,
};
},
});

View File

@@ -15,17 +15,17 @@
:tree-data="treeData1"
@change="changeDeviceType" />
<a-spin :spinning="treeLoading">
<a-tree
v-model:expandedKeys="expandedKeys"
v-model:selectedKeys="selectedKeys"
v-model:checkedKeys="checkedKeys"
:show-line="{ showLeafIcon: false }"
checkable
:height="560"
style="width: 100%; overflow-y: auto; margin-bottom: 10px; margin-top: 10px"
:tree-data="treeData2" />
</a-spin>
<!-- <a-spin :spinning="treeLoading"> -->
<a-tree
v-model:expandedKeys="expandedKeys"
v-model:selectedKeys="selectedKeys"
v-model:checkedKeys="checkedKeys"
:show-line="{ showLeafIcon: false }"
checkable
:height="560"
style="width: 100%; overflow-y: auto; margin-bottom: 10px; margin-top: 10px"
:tree-data="treeData2" />
<!-- </a-spin> -->
<!-- <div class="fixed-bottom"> -->
<div>

View File

@@ -18,6 +18,8 @@
</template>
<script lang="ts">
import ExcelJS from 'exceljs';
import FileSaver from 'file-saver';
import { defineComponent, ref, inject, watch, onMounted } from 'vue';
export default defineComponent({
@@ -130,6 +132,153 @@
},
{ deep: true },
);
// 导出excel文件
// 因为有多级表头,特殊处理,没有用公共方法
const export1 = () => {
if (!data.value || data.value.length == 0) {
return;
}
// 不需要合并序号
for (let i = 0; i < data.value.length; i++) {
data.value[i].index = i + 1;
}
// 创建工作簿
const workbook = new ExcelJS.Workbook();
// 添加工作表名为sheet1
const sheet1 = workbook.addWorksheet('sheet1');
// 字段名
let filterVal = [
'name',
'value',
'yoyDiff',
'yoyRate',
'momDiff',
'momRate',
'zongxiangDiff',
'zongxiangRate',
];
// 表格columns
// 添加多行表头
sheet1.addRows([
['设备/节点', '统计值', '同比', '', '环比', '', '纵向对比', ''],
['', '', '△差值', '增长率', '△差值', '增长率', '△差值', '增长率'],
]);
// 合并单元格来实现多行表头
sheet1.mergeCells('A1:A2'); // 合并 '设备/节点' 单元格
sheet1.mergeCells('B1:B2'); // 合并 '统计值' 单元格
sheet1.mergeCells('C1:D1'); // 合并 '同比' 单元格
sheet1.mergeCells('E1:F1'); // 合并 '环比' 单元格
sheet1.mergeCells('G1:H1'); // 合并 '纵向对比' 单元格
//传入的数据
const list = data.value;
//格式化数据
const datas = formatJson(filterVal, list);
// 将数据写入工作表
datas.forEach((row: any) => {
// const values = Object.values(row);
sheet1.addRow(row);
});
let column = sheet1.columns;
for (let i = 0; i < column.length; i++) {
column[i].width = 20;
}
// 修改所有单元格样式
// 遍历每一行
sheet1.eachRow((row) => {
// 遍历每个单元格
row.eachCell((cell) => {
// 设置边框样式
cell.border = {
top: { style: 'thin' },
left: { style: 'thin' },
bottom: { style: 'thin' },
right: { style: 'thin' },
};
// 设置居中对齐
cell.alignment = {
vertical: 'middle',
horizontal: 'center',
};
});
});
// 获取标题行数据
const titleCell1 = sheet1.getRow(1);
// 设置行高为30
titleCell1.height = 30;
// 设置标题行单元格样式
titleCell1.eachCell((cell) => {
// 设置标题行背景颜色为黄色
cell.fill = {
type: 'pattern',
pattern: 'solid',
fgColor: { argb: 'FFFFFF' },
};
// 设置标题行字体
cell.font = {
// color: { argb: 'FF0000' }, //颜色为红色
bold: true, // 字体粗体
size: 18, // 设置字体大小为18
};
});
const titleCell2 = sheet1.getRow(2);
// 设置行高为30
titleCell2.height = 30;
// 设置标题行单元格样式
titleCell2.eachCell((cell) => {
// 设置标题行背景颜色为黄色
cell.fill = {
type: 'pattern',
pattern: 'solid',
fgColor: { argb: 'FFFFFF' },
};
// 设置标题行字体
cell.font = {
// color: { argb: 'FF0000' }, //颜色为红色
bold: true, // 字体粗体
size: 18, // 设置字体大小为18
};
});
// 获取第二行到最后一行的内容数据
const bodyRows = sheet1.getRows(3, sheet1.rowCount);
if (bodyRows) {
// 处理内容行的数据
bodyRows.forEach((bodyRow) => {
// 设置行高为20
bodyRow.height = 20;
bodyRow.eachCell((cell) => {
cell.font = {
size: 16, // 设置内容行字体大小为16
};
});
});
}
// 导出表格文件
workbook.xlsx
.writeBuffer()
.then((buffer) => {
let file = new Blob([buffer], { type: 'application/octet-stream' });
FileSaver.saveAs(file, '分析数据导出.xlsx');
})
.catch((error) => console.log('Error writing excel export', error));
};
/**
* 格式化表格数据
* @filterVal 格式头
* @jsonData 用来格式化的表格数据
*/
function formatJson(filterVal: any, jsonData: any) {
return jsonData.map((v: any) => filterVal.map((j: any) => v[j]));
}
onMounted(() => {
// 深度拷贝
data.value = JSON.parse(JSON.stringify(pageData.analysisTableList));
@@ -165,6 +314,7 @@
rowSelection,
selectedKey,
setStandard,
export1,
};
},
});

View File

@@ -16,6 +16,7 @@
</template>
<script lang="ts">
import { exportExcel } from '/@/util/ExcelUtil.js';
import { defineComponent, watch, inject, ref, onMounted } from 'vue';
import type { TableColumnType } from 'ant-design-vue';
import { Pagination } from 'ant-design-vue';
@@ -152,6 +153,9 @@
columns.value = columnA;
total.value = dataList.value.length;
onChange(1, 10);
}; // 导出excel文件
const export1 = () => {
exportExcel(columns.value, dataList.value, '图表数据导出');
};
onMounted(() => {
init();
@@ -167,6 +171,7 @@
columns,
total,
onChange,
export1,
};
},
});

View File

@@ -28,7 +28,7 @@
</template> -->
</a-tabs>
<div class="button">
<ns-icon name="xiazai" size="18" style="margin-right: 10px" @click="downloadChart" />
<ns-icon name="xiazai" size="18" style="margin-right: 10px" @click="download" />
<ns-icon :name="iconName" size="18" style="margin-right: 10px" @click="change" />
</div>
</div>
@@ -74,14 +74,26 @@
name: 'EnvironmentMonitorIndex', // 与页面路由name一致缓存才可生效
});
const downloadChart = () => {
if (activeKey.value == '1' && isGraph) {
if (graphRef.value) {
graphRef.value.downloadChart();
const download = () => {
if (activeKey.value == '1') {
if (isGraph.value) {
if (graphRef.value) {
graphRef.value.downloadChart();
}
} else {
if (tableRef.value) {
tableRef.value.export1();
}
}
} else {
if (analysisGraphRef.value) {
analysisGraphRef.value.downloadChart();
if (isGraph.value) {
if (analysisGraphRef.value) {
analysisGraphRef.value.downloadChart();
}
} else {
if (analysisTableRef.value) {
analysisTableRef.value.export1();
}
}
}
};

View File

@@ -35,17 +35,17 @@
v-if="mode == '0'"
@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"
:show-line="{ showLeafIcon: false }"
checkable
:height="600"
style="width: 100%; overflow-y: auto; margin-bottom: 10px; margin-top: 10px"
:tree-data="treeData2">
<!-- <template #title="{ title }">
<!-- <a-spin :spinning="treeLoading"> -->
<a-tree
v-model:expandedKeys="expandedKeys"
v-model:selectedKeys="selectedKeys"
v-model:checkedKeys="checkedKeys"
:show-line="{ showLeafIcon: false }"
checkable
:height="500"
style="width: 100%; overflow-y: auto; margin-bottom: 10px; margin-top: 10px"
:tree-data="treeData2">
<!-- <template #title="{ title }">
<span v-if="title.indexOf(mode == '0' ? deviceName : pointName) > -1">
{{ title.substr(0, title.indexOf(mode == '0' ? deviceName : pointName)) }}
<span style="color: #f50">{{ mode == '0' ? deviceName : pointName }}</span>
@@ -58,8 +58,8 @@
</span>
<span v-else>{{ title }}</span>
</template> -->
</a-tree>
</a-spin>
</a-tree>
<!-- </a-spin> -->
<!-- <div class="fixed-bottom"> -->
<div>

View File

@@ -206,10 +206,7 @@
};
// 导出excel文件
const export1 = () => {
for (let i = 0; i < data.value.length; i++) {
data.value[i].index = i + 1;
}
exportExcel(tableColumns.value, data.value, '平均数据导出', false);
exportExcel(tableColumns.value, data.value, '平均数据导出');
};
onMounted(async () => {
// 获取频率

View File

@@ -288,6 +288,10 @@
// 获取表格数据
const getTableList = () => {
loading.value = true;
tableColumns.value = [];
data.value = [];
total.value = 0;
pageData.value = [];
let environmentType = '';
for (let i = 0; i < typeList.value.length; i++) {
if (typeList.value[i].value == typeValue.value) {
@@ -353,7 +357,7 @@
};
// 导出excel文件
const export1 = () => {
exportExcel(tableColumns.value, data.value, '历史数据导出', true, 1, 3);
exportExcel(tableColumns.value, data.value, '历史数据导出', true, 'location', 1, 3);
};
onMounted(async () => {
// 获取频率

View File

@@ -120,9 +120,9 @@
})
.then((res) => {
treeData.value = transform(get(res, resultField));
//默认选择第一个 并刷新列表
selectedKeys.value = [];
if (formConfig.value.callList && formConfig.value.defaultSelection) {
//默认选择第一个 并刷新列表
selectedKeys.value = [];
handleSelect([treeData.value[0].id], {
selected: true,
event: 'select',

View File

@@ -129,11 +129,13 @@ export const importFile = (
headers: { token: Cookies.get(`${import.meta.env.VITE_PUBLIC_PATH}-nervsid`) },
})
.then((res) => {
if (res) {
if (res.data.data) {
NsMessage.success('导入成功', 1, () => {
reload && reload();
successBack && successBack(res);
});
} else {
NsMessage.error(res.data.msg || '导入失败');
}
})
.catch((err) => {