This commit is contained in:
高云鹏 2024-11-26 17:36:58 +08:00
commit 1f01735820
30 changed files with 408 additions and 95 deletions

View File

@ -0,0 +1,13 @@
package com.das.modules.cache.domain;
import lombok.Data;
@Data
public class IotFieldInfoCache {
private String attributeCode;
private String attributeName;
private Integer attributeType;
private Integer porder;
private Integer highspeed;
private Integer datatype;
}

View File

@ -0,0 +1,12 @@
package com.das.modules.cache.domain;
import lombok.Data;
import java.util.concurrent.ConcurrentHashMap;
@Data
public class IotModelInfoCache {
private Long iotModelId;
private String iodModelCode;
private ConcurrentHashMap<String,IotFieldInfoCache> fieldInfoCache;
}

View File

@ -10,4 +10,10 @@ public interface CacheService {
* @return
*/
EquipmentCache getEquipmentCache();
/**
* 获取物模型缓存接口
* @return
*/
IotModelCache getIotModelCache();
}

View File

@ -33,4 +33,10 @@ public interface EquipmentCache {
* @return
*/
DeviceInfoCache getDeviceInfoCacheById(Long deviceId);
/**
* 移除指定设备缓存
* @param deviceId
*/
void removeDeviceCache(Long deviceId);
}

View File

@ -0,0 +1,5 @@
package com.das.modules.cache.service;
public interface IotModelCache {
}

View File

@ -2,6 +2,7 @@ package com.das.modules.cache.service.impl;
import com.das.modules.cache.service.CacheService;
import com.das.modules.cache.service.EquipmentCache;
import com.das.modules.cache.service.IotModelCache;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
@ -9,10 +10,20 @@ import org.springframework.stereotype.Service;
public class CacheServiceImpl implements CacheService {
@Autowired
EquipmentCache equipmentCache;;
EquipmentCache equipmentCache;
@Autowired
IotModelCache iotModelCache;
@Override
public EquipmentCache getEquipmentCache() {
return equipmentCache;
}
@Override
public IotModelCache getIotModelCache() {
return iotModelCache;
}
}

View File

@ -110,4 +110,13 @@ public class EquipmentCacheImpl implements EquipmentCache {
}
return null;
}
@Override
public void removeDeviceCache(Long deviceId) {
Integer index = deviceIdIndex.get(deviceId);
if (index != null) {
deviceInfoCaches.remove(index);
}
}
}

View File

@ -0,0 +1,24 @@
package com.das.modules.cache.service.impl;
import com.das.modules.cache.service.IotModelCache;
import com.das.modules.equipment.mapper.SysIotModelMapper;
import jakarta.annotation.PostConstruct;
import jakarta.annotation.PreDestroy;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
@Service
public class IotModelCacheImpl implements IotModelCache {
@Autowired
SysIotModelMapper sysIotModelMapper;
@PostConstruct
public void init(){
}
@PreDestroy
public void destroy(){
}
}

View File

@ -0,0 +1,90 @@
package com.das.modules.calc.functions;
import cn.hutool.core.date.DateUnit;
import cn.hutool.core.date.DateUtil;
import com.das.modules.cache.domain.DeviceInfoCache;
import com.das.modules.cache.service.CacheService;
import com.das.modules.data.domain.SnapshotValueQueryParam;
import com.das.modules.data.service.DataService;
import com.googlecode.aviator.runtime.function.AbstractFunction;
import com.googlecode.aviator.runtime.type.AviatorNil;
import com.googlecode.aviator.runtime.type.AviatorObject;
import com.googlecode.aviator.runtime.type.AviatorRuntimeJavaType;
import lombok.Data;
import lombok.extern.slf4j.Slf4j;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
/**
* Aviator扩展函数 - 获取时间维度内最早的一条数据
* 函数格式: topv(deviceId, attr, timedim)
* timedim: day - month - year -
* 返回值数值 nil - 获取错误
*/
@Slf4j
public class FunctionTopValue extends AbstractFunction {
private DataService dataService = null;
private CacheService cacheService = null;
private ConcurrentHashMap<String,CacheValue> cacheValues = new ConcurrentHashMap<>();
public FunctionTopValue(DataService dataService, CacheService cacheService) {
this.dataService = dataService;
this.cacheService = cacheService;
}
@Override
public String getName() {
return "topv";
}
@Override
public AviatorObject call(Map<String, Object> env, AviatorObject deviceCode, AviatorObject attr, AviatorObject timeDim) {
//设备Code
String code = (String)deviceCode.getValue(env);
String attrName = (String)attr.getValue(env);
String timeDimName = (String)timeDim.getValue(env);
DeviceInfoCache deviceInfoCache = cacheService.getEquipmentCache().getDeviceInfoCacheByCode(code);
if (deviceInfoCache == null) {
return AviatorNil.NIL;
}
String key = String.format("%d_%s_%s", deviceInfoCache.getDeviceId(), attrName, timeDimName);
//根据维度获取维度时间
Date curTimeValue = null;
switch (timeDimName) {
case "day":
curTimeValue = DateUtil.beginOfDay(DateUtil.date());
break;
case "month":
curTimeValue = DateUtil.beginOfMonth(DateUtil.date());
break;
case "year":
curTimeValue = DateUtil.beginOfYear(DateUtil.date());
break;
default:
return AviatorNil.NIL;
}
CacheValue cacheValue = cacheValues.get(key);
if (cacheValue != null) {
//缓存中存在检查是否过期
if (cacheValue.getCurTimeValue() != null && DateUtil.compare(cacheValue.getCurTimeValue(), curTimeValue) == 0) {
return AviatorRuntimeJavaType.valueOf(cacheValue.value);
}
}
//未找到缓存查询时序API获取数据
return AviatorRuntimeJavaType.valueOf(1);
}
@Data
class CacheValue{
private Double value;
private Date curTimeValue;
}
}

View File

@ -31,6 +31,7 @@ public class CalcJob implements Job {
}
Expression expression = instance.getCachedExpressionByKey(calcModule.getName());
if (expression == null) {
log.error("expression is null, calcModule={}", calcModule.getName());
throw new JobExecutionException("expression is null");
}
Map<String,Object> envs = expression.newEnv("G_DEVICES", cacheService.getEquipmentCache().getDevicesCache());

View File

@ -126,6 +126,7 @@ public class CalcService {
try{
//预编译脚本
aviator.compile(scriptModule.getName(), scriptModule.getScript(), true);
log.info("[预编译脚本] - {}", scriptModule.getName());
startCalcJob(scriptModule);
}
catch (Exception e){

View File

@ -0,0 +1,9 @@
package com.das.modules.data.domain;
import lombok.Data;
@Data
public class RTValue {
private Object value;
private Long time;
}

View File

@ -17,5 +17,5 @@ public interface DataService {
void updateCalFieldData(List<CalculateRTData> values);
Double getTimeTopValue(Long devcieId, String attr, long startTime, long endTime);
}

View File

@ -4,6 +4,7 @@ import cn.hutool.core.collection.ListUtil;
import cn.hutool.core.util.StrUtil;
import com.das.common.utils.PageDataInfo;
import com.das.modules.data.domain.DeviceEventInfo;
import com.das.modules.data.domain.RTValue;
import com.das.modules.equipment.domain.vo.IotModelFieldVo;
import com.das.modules.node.domain.bo.CalculateRTData;
import com.das.modules.node.domain.bo.RTData;
@ -681,7 +682,7 @@ public class TDEngineService {
}
}
if (pageSize != null) {
sb.append(" limit ").append(offset).append(",").append(pageSize);
sb.append(" desc limit ").append(offset).append(",").append(pageSize);
total = getEventCount(eventLevel, startTime, endTime, deviceCodeList);
}
@ -702,6 +703,7 @@ public class TDEngineService {
deviceEventInfo.setConfirmTime(rs.getLong("confirm_time"));
deviceEventInfo.setDeviceCode(rs.getString("device_code"));
deviceEventInfo.setDeviceId(rs.getString("device_id"));
deviceEventInfo.setDeviceName(rs.getString("device_name"));
result.add(deviceEventInfo);
}
} catch (Exception e) {
@ -781,4 +783,27 @@ public class TDEngineService {
hikariDataSource.close();
}
}
public Double getTimeTopValue(String tableName, String attr, long startTime, long endTime) {
StringBuffer sb = new StringBuffer(256);
sb.append("select ");
sb.append("first(");
sb.append(attr);
sb.append(") as value");
sb.append(" from ");
sb.append(tableName);
sb.append(" where ");
sb.append(String.format(" updatetime >= %d and updatetime < %d ", startTime, endTime));
Double result = null;
try (Connection conn = hikariDataSource.getConnection();
Statement smt = conn.createStatement();
ResultSet rs = smt.executeQuery(sb.toString())) {
if (rs.next()) {
result = rs.getDouble("value");
}
} catch (Exception e) {
log.error("获取数据异常", e);
}
return result;
}
}

View File

@ -4,6 +4,8 @@ import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.collection.ListUtil;
import com.das.common.exceptions.ServiceException;
import com.das.common.utils.AdminRedisTemplate;
import com.das.modules.cache.domain.DeviceInfoCache;
import com.das.modules.cache.service.CacheService;
import com.das.modules.data.domain.SnapshotValueQueryParam;
import com.das.modules.data.domain.TSValueQueryParam;
import com.das.modules.data.service.DataService;
@ -19,6 +21,7 @@ import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.util.CollectionUtils;
import org.springframework.util.StopWatch;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
@ -45,6 +48,9 @@ public class DataServiceImpl implements DataService {
@Autowired
SysIotModelMapper sysIotModelMapper;
@Autowired
CacheService cacheService;
//key:deviceId value:modelCode
public ConcurrentHashMap<String, String> deviceModelMap = new ConcurrentHashMap<>(10000);
@ -141,7 +147,8 @@ public class DataServiceImpl implements DataService {
}
private Map<String, Map<String, Map<String, Object>>> queryHistoryCurveValues(Long irn, Date startTime, Date endTime, String interval, String fill, List<String> attributes) {
StopWatch stopWatch = new StopWatch();
stopWatch.start("prepare resources");
String iotModelCode = sysIotModelFieldMapper.queryModelCodeByDeviceId(irn);
Map<String, Object> highSpeedFieldMap = highIotFieldMap.get(iotModelCode);
Map<String, Object> lowSpeedFieldMap = lowIotFieldMap.get(iotModelCode);
@ -160,11 +167,15 @@ public class DataServiceImpl implements DataService {
calField.add(field);
}
}
stopWatch.stop();
stopWatch.start("HighSpeedValues");
Map<String, Map<String, Map<String, Object>>> result = new HashMap<>();
if (!CollectionUtils.isEmpty(highSpeedField)) {
Map<String, Map<String, Map<String, Object>>> highHistoryCurve = tdEngineService.fetchHighHistoryCurve(irn, startTime, endTime, interval, highSpeedField);
result.putAll(highHistoryCurve);
}
stopWatch.stop();
stopWatch.start("LowSpeedValues");
if (!CollectionUtils.isEmpty(lowSpeedField)) {
Map<String, Map<String, Map<String, Object>>> lowHistoryCurve = tdEngineService.fetchLowHistoryCurve(irn, startTime, endTime, interval, lowSpeedField);
if (result.get(irn.toString()) == null) {
@ -173,6 +184,8 @@ public class DataServiceImpl implements DataService {
result.get(irn.toString()).putAll(lowHistoryCurve.get(irn.toString()));
}
}
stopWatch.stop();
stopWatch.start("CalculateValues");
if (!CollectionUtils.isEmpty(calField)){
ListUtil.page(calField,COMMIT_COUNT,list -> {
for (String item : list){
@ -185,6 +198,8 @@ public class DataServiceImpl implements DataService {
}
});
}
stopWatch.stop();
log.debug("查询历史数据耗时: {}", stopWatch.prettyPrint());
return result;
}
@ -236,4 +251,21 @@ public class DataServiceImpl implements DataService {
tdEngineService.initIotModel(allIotModel, highIotFieldMap, lowIotFieldMap, calculateIotFieldMap);
}
/**
* 获取指定时间区间内的最早的数据
* @param devcieId
* @param attr
* @param startTime
* @param endTime
* @return
*/
@Override
public Double getTimeTopValue(Long devcieId, String attr, long startTime, long endTime){
DeviceInfoCache deviceInfoCacheById = cacheService.getEquipmentCache().getDeviceInfoCacheById(devcieId);
if (deviceInfoCacheById == null) {
return null;
}
String tableName = "";
return tdEngineService.getTimeTopValue(tableName, attr, startTime, endTime);
}
}

View File

@ -15,6 +15,7 @@ import com.das.common.utils.PageQuery;
import com.das.common.utils.SequenceUtils;
import com.das.modules.auth.domain.vo.SysUserVo;
import com.das.modules.auth.mapper.SysOrgMapper;
import com.das.modules.cache.service.CacheService;
import com.das.modules.data.service.impl.DataServiceImpl;
import com.das.modules.equipment.domain.dto.SysEquipmentDto;
import com.das.modules.equipment.domain.excel.SysEquipmentExcel;
@ -62,6 +63,9 @@ public class SysEquipmentServiceImpl implements SysEquipmentService {
@Autowired
private DataServiceImpl dataService;
@Autowired
private CacheService cacheService;
@Override
public SysEquipmentVo creatSysEquipment(SysEquipmentDto sysEquipmentDto) {
//去除空格
@ -84,6 +88,8 @@ public class SysEquipmentServiceImpl implements SysEquipmentService {
if (sysEquipment.getIotModelId() != null){
dataService.deviceModelMap.put(sysEquipment.getId().toString(),dataService.iotModelMap.get(sysEquipment.getIotModelId().toString()));
}
//更新设备缓存
cacheService.getEquipmentCache().refreshDeviceCache(sysEquipment.getId());
SysEquipmentVo sysEquipmentVo = new SysEquipmentVo();
BeanCopyUtils.copy(sysEquipment, sysEquipmentVo);
return sysEquipmentVo;
@ -111,6 +117,8 @@ public class SysEquipmentServiceImpl implements SysEquipmentService {
if (oldModelSysEquipInfo.getIotModelId() == null && sysEquipment.getIotModelId() != null){
dataService.deviceModelMap.put(sysEquipment.getId().toString(),dataService.iotModelMap.get(sysEquipment.getIotModelId().toString()));
}
//更新设备缓存
cacheService.getEquipmentCache().refreshDeviceCache(sysEquipment.getId());
SysEquipmentVo sysEquipmentVo = new SysEquipmentVo();
BeanCopyUtils.copy(sysEquipment, sysEquipmentVo);
return sysEquipmentVo;
@ -124,6 +132,8 @@ public class SysEquipmentServiceImpl implements SysEquipmentService {
}
sysEquipmentMapper.deleteById(sysEquipmentDto.getId());
//删除缓存
//更新设备缓存
cacheService.getEquipmentCache().refreshDeviceCache(sysEquipmentDto.getId());
dataService.deviceModelMap.remove(sysEquipmentDto.getId().toString());
}
@ -297,6 +307,8 @@ public class SysEquipmentServiceImpl implements SysEquipmentService {
String modelCode = dataService.iotModelMap.get(item.getIotModelId().toString());
dataService.deviceModelMap.put(item.getId().toString(),modelCode);
}
//更新设备缓存
cacheService.getEquipmentCache().refreshDeviceCache(item.getId());
}
if (CollectionUtils.isNotEmpty(updateSysEquipmentList)) {
sysEquipmentMapper.updateBatchById(updateSysEquipmentList);
@ -305,16 +317,17 @@ public class SysEquipmentServiceImpl implements SysEquipmentService {
String modelCode = dataService.iotModelMap.get(item.getIotModelId().toString());
dataService.deviceModelMap.put(item.getId().toString(),modelCode);
}
//更新设备缓存
cacheService.getEquipmentCache().refreshDeviceCache(item.getId());
}
}
if (CollectionUtils.isNotEmpty(delSysEquipmentList)) {
// 删除设备
sysEquipmentMapper.deleteBatchIds(delSysEquipmentList);
for (SysEquipment item : updateSysEquipmentList){
if (item.getIotModelId() != null){
String modelCode = dataService.iotModelMap.get(item.getIotModelId().toString());
dataService.deviceModelMap.put(item.getId().toString(),modelCode);
}
for (SysEquipment item : delSysEquipmentList){
dataService.deviceModelMap.remove(item.getId().toString());
//更新设备缓存
cacheService.getEquipmentCache().removeDeviceCache(item.getId());
}
}

View File

@ -26,6 +26,7 @@ import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.node.ObjectNode;
import com.lmax.disruptor.BlockingWaitStrategy;
import com.lmax.disruptor.RingBuffer;
import com.lmax.disruptor.YieldingWaitStrategy;
import com.lmax.disruptor.dsl.Disruptor;
@ -95,7 +96,7 @@ public class NodeMessageServiceImpl extends TextWebSocketHandler implements Node
//初始化高性能队列
int cpu = Runtime.getRuntime().availableProcessors();
int bufferSize = 1024 * 4;
disruptor = new Disruptor<>(TerminalMessage::new, bufferSize, DaemonThreadFactory.INSTANCE, ProducerType.MULTI, new YieldingWaitStrategy());
disruptor = new Disruptor<>(TerminalMessage::new, bufferSize, DaemonThreadFactory.INSTANCE, ProducerType.MULTI, new BlockingWaitStrategy());
//
TerminalMessageWorkerHandler[] workerHandlers = new TerminalMessageWorkerHandler[cpu];
for (int i = 0; i < cpu; i++) {

View File

@ -34,5 +34,11 @@ public class HomeWindTurbineMatrixDataVoVo {
*/
private Double nominalCapacity;
private String deviceCode;
private Map<String,Object> attributeMap;
}

View File

@ -11,7 +11,7 @@ import java.util.List;
public interface TemperatureDashboardService {
/**
* 根据设备id获取该设备对应的温度相关测点的限制配置信息
* @param deviceId 设备id
* @param temperatureLimitDto 设备信息
* @return 限制配置列表(如果限制未设置 则不返回对应测点信息)
*/
List<TemperatureLimitVo> getTemperatureLimitByDeviceId(TemperatureLimitDto temperatureLimitDto);

View File

@ -82,6 +82,7 @@ public class HomeServiceImpl implements HomeService {
homeWindRealTimeVoResult.setIrn(item.getId());
homeWindRealTimeVoResult.setName(item.getName());
homeWindRealTimeVoResult.setModel(item.getModel());
homeWindRealTimeVoResult.setDeviceCode(item.getCode());
homeWindRealTimeVoResult.setModelId(item.getIotModelId());
homeWindRealTimeVoResult.setBelongLine(item.getBelongLine());
homeWindRealTimeVoResult.setStandard(item.getStandard());

View File

@ -9,10 +9,7 @@ import com.das.modules.page.service.TemperatureDashboardService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.*;
@Service
public class TemperatureDashboardServiceImpl implements TemperatureDashboardService {
@ -62,7 +59,8 @@ public class TemperatureDashboardServiceImpl implements TemperatureDashboardServ
}
});
}
return new ArrayList<>(map.values());
ArrayList<TemperatureLimitVo> temperatureLimitVos = new ArrayList<>(map.values());
temperatureLimitVos.sort(Comparator.comparing(TemperatureLimitVo::getMeasPointName));
return temperatureLimitVos;
}
}

View File

@ -40,16 +40,16 @@ spring:
# 多个文件总大小
max-request-size: 2048MB
datasource:
url: jdbc:postgresql://192.168.109.102:5432/das
url: jdbc:postgresql://192.168.109.187:5432/das
username: das
password: qwaszx12
# # redis相关配置
data:
redis:
host: 192.168.109.195
host: 192.168.109.187
database: 0
port: 6379
password:
password: zaq12WSX
client-type: lettuce

4
ui/.gitignore vendored Normal file
View File

@ -0,0 +1,4 @@
.idea
das-dn/.vscode
ui/**/dist
das/**/target

View File

@ -43,8 +43,12 @@
<div class="tabsPart">
<el-table :data="alarmsTableData" class="tablePart" highlight-current-row>
<el-table-column prop="eventTimeFormate" :label="AlarmsFieldsEnums['alarmTime']" align="center"> </el-table-column>
<el-table-column prop="deviceCode" :label="AlarmsFieldsEnums['airBlowerNumber']" align="center"> </el-table-column>
<el-table-column prop="eventText" :label="AlarmsFieldsEnums['faultDescription']" align="center"> </el-table-column>
<el-table-column prop="devicecodeName" :label="AlarmsFieldsEnums['airBlowerNumber']" align="center"> </el-table-column>
<el-table-column prop="eventText" :label="AlarmsFieldsEnums['faultDescription']" align="center">
<template #default="{ row }">
<span v-html="formatText(row.eventText)"></span>
</template>
</el-table-column>
<el-table-column prop="eventLevel" :label="AlarmsFieldsEnums['alarmType']" align="center">
<template #default="scope">
<div class="tip" v-if="scope.row.eventLevel === 0">提示</div>
@ -199,6 +203,7 @@ const getalarmsList = () => {
return {
...item,
eventTimeFormate: timestampToTime(item.eventTime),
devicecodeName: item.deviceName + item.deviceCode,
}
})
} else {
@ -209,6 +214,13 @@ const getalarmsList = () => {
ElMessage.error(err?.response?.data?.msg ?? '查询失败')
})
}
const formatText = (text: any) => {
text = text.replace(/ 动作/g, '<span style="color:#a03b1d;"> 动作</span>')
text = text.replace(/复归/g, '<span style="color:#39baf4;">复归</span>')
return text
}
const timestampToTime = (timestamp: any) => {
timestamp = timestamp ? timestamp : null
let date = new Date(timestamp)

View File

@ -96,7 +96,7 @@
@mouseout="hideButton"
>
<span>{{item.eventTimeFormate}}</span>
<span>{{item.deviceCode}}</span>
<span>{{item.deviceName}}</span>
<span>{{item.eventText}}</span>
<span v-show="showConfirmButton" >
<a style="color: #0277b3; cursor: pointer;" @click="open(item)" >确认</a>
@ -346,8 +346,7 @@ const getTableData = (deviceCode) => {
startTime: new Date(new Date().toLocaleDateString()).getTime(),
endTime: Date.now(),
deviceCode: deviceCode,
limit: 100,
eventLevel:2
// eventLevel:2
}
// console.log(JSON.stringify(data))
getAlarmListReq(data).then((res) => {

View File

@ -537,17 +537,17 @@ const getTableData = () => {
multipleSelection.value.forEach(item => {
if (item.attributeCode) {
const attributeCodeLower = item.attributeCode.toLowerCase();
// if(attributeCodeLower==='ipitchangle1'||attributeCodeLower==='ipitchangle2'||attributeCodeLower==='ipitchangle3'){
/* tableColumnEnds.value.push({
label: '变桨角度',
unit:'',
prop: 'ipitchangle',
if(item.unit==""||item.unit==undefined){
tableColumnEnds.value.push({
label: item.attributeName,
unit:item.unit,
prop: attributeCodeLower,
align: 'center',
custom: 'header',
name: 'ipitchangle',
title: '变桨角度',
});*/
//}else{
name: item.attributeCode,
title: item.attributeName,
});
}else{
tableColumnEnds.value.push({
label: item.attributeName+'\n'+item.unit,
unit:item.unit,
@ -557,7 +557,8 @@ const getTableData = () => {
name: item.attributeCode,
title: item.attributeName,
});
//}
}
}
});
tableColumn.value = [...tableItem0, ...tableColumnEnds.value];

View File

@ -1,7 +1,17 @@
<template>
<div class="statAnalysis">
<el-menu :default-active="activeIndex" class="headerList" mode="horizontal" @select="handleSelect">
<el-menu-item v-for="(item, index) in headerList" :index="index" :key="index"> {{ item }} </el-menu-item>
<el-menu-item index="0" key="0"> {{ headerList[0] }} </el-menu-item>
<el-popover placement="top-start" :width="200" popper-class="admin-info-box" trigger="hover" content="同一测点,不同时间段对比">
<template #reference>
<el-menu-item index="1" key="1"> {{ headerList[1] }} </el-menu-item>
</template>
</el-popover>
<el-popover placement="top-start" :width="200" popper-class="admin-info-box" trigger="hover" content="同一时间段,不同测点对比">
<template #reference>
<el-menu-item index="2" key="2"> {{ headerList[2] }} </el-menu-item>
</template>
</el-popover>
</el-menu>
<PowerCurveAnalysis v-if="activeIndex == 0"></PowerCurveAnalysis>
<TrendAnalysis v-if="activeIndex == 1"></TrendAnalysis>
@ -19,7 +29,6 @@ const config = useConfig()
const activeIndex = ref(0)
const { t } = useI18n()
const headerList = [t('statAnalysis.PowerCurveAnalysis'), t('statAnalysis.trendAnalysis'), t('statAnalysis.trendComparison')]
const handleSelect = (index: number) => {
activeIndex.value = index
}

View File

@ -54,7 +54,7 @@
</div>
</div>
</div>
<div ref="chartContainer" style="width: 100%; height: 400px; border: 1px solid rgb(217, 217, 217)"></div>
<div ref="chartContainer" style="position: absolute; bottom: 0px; width: 100%; height: 400px; border: 1px solid rgb(217, 217, 217)"></div>
</div>
</template>
<script setup lang="ts">
@ -199,7 +199,7 @@ const shortcuts = [
text: '今天',
value: () => {
const start = getFormattedDate(0) + ' 00:00:00'
const end = new Date()
const end = getFormattedDate(0) + ' 23:59:59'
return [start, end]
},
},

View File

@ -34,7 +34,7 @@
<el-button style="color: #0064aa" @click="statAnalysisExport()">{{ t('statAnalysis.export') }}</el-button>
</div>
</el-header>
<div class="timeColumns" :class="{ expand: isExpand }">
<div class="timeColumns">
<div class="headerPart" v-for="(time, index) in times" :key="index">
<div class="topLeft">
<div class="selectPart">
@ -75,10 +75,7 @@
</div>
</div>
</div>
<div v-if="times.length > 2" class="ralIcon" @click="handleClick">
<el-icon :size="20" color="#0064AA"><DArrowRight /></el-icon>
</div>
<div ref="chartContainer" style="width: 100%; height: 400px; border: 1px solid rgb(217, 217, 217)"></div>
<div ref="chartContainer" style="position: absolute; bottom: 0px; width: 100%; height: 400px; border: 1px solid rgb(217, 217, 217)"></div>
<el-dialog v-model="showMeasure" title="测点名称" :width="800">
<template #header>
<div class="measureSlotHeader">
@ -131,10 +128,6 @@ const timechange = (value: any) => {
ElMessage.warning('查询时间点错误,请重新输入')
}
}
const isExpand = ref(false)
const handleClick = () => {
isExpand.value = !isExpand.value
}
const iotModelId = ref('')
const irn = ref('')
@ -258,7 +251,7 @@ const shortcuts = [
text: '今天',
value: () => {
const start = getFormattedDate(0) + ' 00:00:00'
const end = new Date()
const end = getFormattedDate(0) + ' 23:59:59'
return [start, end]
},
},
@ -396,14 +389,27 @@ const historyDataReq = (promises: any) => {
const deviceId = statAnalysisSelect.deviceId
const attributeCode = statAnalysisSelect.attributeCode
results.forEach((res: any, index: number) => {
console.log(times[index])
const resData = (res && deviceId in res && res[deviceId][attributeCode]) || undefined
const xData = resData['times']
const yData = resData['values']
if (!yData.length) {
if (!resData['values'].length) {
ElMessage.info(`${customName[index]}数据为空`)
return
}
calculate.value[index] = calculateStats(yData)
const alltimes = getTimestamps(
new Date(times[index][0]).getTime(),
new Date(times[index][1]).getTime(),
statAnalysisSelect.interval || '5m'
)
console.log(alltimes)
console.log('🚀 ~ results.forEach ~ resData:', resData)
const fillData = fillMissingData(alltimes, resData)
console.log('🚀 ~ results.forEach ~ fillData:', fillData)
const xData = fillData['times']
const yData = fillData['values']
console.log(xData)
calculate.value[index] = calculateStats(resData['values'])
xDatas.push({
series: String(customName[index]),
data: xData,
@ -484,6 +490,52 @@ function calculateStats(numbers: any) {
average: average.toFixed(2),
}
}
const getTimestamps = (start: any, end: any, interval: any) => {
let timestamps = []
let current = start
while (current < end) {
timestamps.push(current)
switch (interval) {
case '5m':
current += 5 * 60 * 1000
break
case '1d':
current += 24 * 60 * 60 * 1000
break
case '15m':
current += 15 * 60 * 1000
break
case 'NONE':
current += 40 * 1000
break
case '1h':
current += 60 * 60 * 1000
break
default:
throw new Error('Unsupported interval')
}
}
return timestamps
}
const fillMissingData = (intervals: any, data: any) => {
const { times, values } = data
const filledTimes: any = []
const filledValues: any = []
intervals.forEach((time: any) => {
const index = times.indexOf(time)
filledTimes.push(time)
if (index !== -1) {
filledValues.push(values[index])
} else {
filledValues.push('')
}
})
return { times: filledTimes, values: filledValues }
}
const timestampToTime = (timestamp: any) => {
timestamp = timestamp ? timestamp : null
let date = new Date(timestamp)
@ -498,6 +550,9 @@ const timestampToTime = (timestamp: any) => {
<style scoped lang="scss">
.statAnalysis {
height: 100%;
.contain {
height: calc(100% - 60px);
}
.headerPart {
padding: 20px;
display: flex;
@ -572,24 +627,11 @@ const timestampToTime = (timestamp: any) => {
}
}
.timeColumns {
max-height: 120px;
overflow: hidden;
height: calc(100% - 480px);
overflow-y: auto;
.headerPart {
padding: 10px 20px;
padding: 2px 20px;
}
&.expand {
max-height: max-content;
height: auto;
overflow: inherit;
}
}
.timeColumns.expand + div.ralIcon {
transform: rotate(-90deg);
}
.ralIcon {
width: 100%;
text-align: center;
transform: rotate(90deg);
}
#myEChart {
width: 100%;

View File

@ -26,7 +26,7 @@
<el-button style="color: #0064aa" @click="statAnalysisExport()">{{ t('statAnalysis.export') }}</el-button>
</div>
</el-header>
<div class="timeColumns" :class="{ expand: isExpand }">
<div class="timeColumns">
<div class="headerPart" v-for="(deviceId, index) in statAnalysisDeviceId" :key="index">
<div class="topLeft">
<div class="selectPart">
@ -81,10 +81,7 @@
</div>
</div>
</div>
<div v-if="statAnalysisDeviceId.length > 2" class="ralIcon" @click="handleClick">
<el-icon :size="20" color="#0064AA"><DArrowRight /></el-icon>
</div>
<div ref="chartContainer" style="width: 100%; height: 400px; border: 1px solid rgb(217, 217, 217)"></div>
<div ref="chartContainer" style="position: absolute; bottom: 0px; width: 100%; height: 400px; border: 1px solid rgb(217, 217, 217)"></div>
<el-dialog v-model="showMeasure" title="测点名称" :width="800">
<template #header>
<div class="measureSlotHeader">
@ -141,10 +138,6 @@ const switchDevice = (index: number) => {
calculate.value.splice(index, 1)
}
const isExpand = ref(false)
const handleClick = () => {
isExpand.value = !isExpand.value
}
const iotModelId = ref('')
const irn = ref('')
const selectAtteibutes = (index: number) => {
@ -260,7 +253,7 @@ const shortcuts = [
text: '今天',
value: () => {
const start = getFormattedDate(0) + ' 00:00:00'
const end = new Date()
const end = getFormattedDate(0) + ' 23:59:59'
return [start, end]
},
},
@ -457,6 +450,9 @@ const timestampToTime = (timestamp: any) => {
<style scoped lang="scss">
.statAnalysis {
height: 100%;
.contain {
height: calc(100% - 60px);
}
.headerPart {
padding: 20px;
display: flex;
@ -531,24 +527,11 @@ const timestampToTime = (timestamp: any) => {
}
}
.timeColumns {
max-height: 120px;
overflow: hidden;
height: calc(100% - 480px);
overflow-y: auto;
.headerPart {
padding: 10px 20px;
padding: 2px 20px;
}
&.expand {
max-height: max-content;
height: auto;
overflow: inherit;
}
}
.timeColumns.expand + div.ralIcon {
transform: rotate(-90deg);
}
.ralIcon {
width: 100%;
text-align: center;
transform: rotate(90deg);
}
#myEChart {
width: 100%;