This commit is contained in:
高云鹏 2024-11-01 16:39:44 +08:00
commit 53c4025090
59 changed files with 1723 additions and 546 deletions

View File

@ -316,7 +316,12 @@ bool CRYDevice::publish_sensor_data(const std::string traceId, const char* comma
std::string outputConfig = Json::writeString(builder, jsonRoot);
if (traceId != "")
{
vLog(LOG_DEBUG, "send cmd: %s, payload: %d, %128s\n", command, outputConfig.length(), outputConfig.c_str());
vLog(LOG_DEBUG, "send cmd: %s, payload: %d\n", command, outputConfig.length());
} else {
std::string com = command;
if (com == "deviceEvent") {
vLog(LOG_DEBUG, "send cmd: %s, payload: %d\n", command, outputConfig.length());
}
}
int rc = websocket_write(outputConfig.c_str(), outputConfig.length());
if (rc != 0) {
@ -2014,7 +2019,7 @@ bool CRYDevice::publishdeviceEventData(void)
}
if (root.size()) {
//return publish_sensor_data("", "deviceEvent", root);
return publish_sensor_data("", "deviceEvent", root);
//vLog(LOG_DEBUG, "%s", root.toStyledString().c_str());
}

View File

@ -1,4 +1,4 @@
package com.das.modules.calc.domain.vo;
package com.das.modules.cache.domain;
import lombok.Data;
@ -9,6 +9,7 @@ import lombok.Data;
public class DeviceInfoCache {
private Long deviceId;
private String deviceCode;
private String deviceName;
private Integer objectType;
private Long parentDeviceId;
}

View File

@ -1,6 +1,6 @@
package com.das.modules.calc.service;
package com.das.modules.cache.service;
import com.das.modules.calc.domain.vo.DeviceInfoCache;
import com.das.modules.cache.domain.DeviceInfoCache;
import com.das.modules.equipment.entity.SysEquipment;
import com.das.modules.equipment.mapper.SysEquipmentMapper;
import jakarta.annotation.PostConstruct;
@ -61,6 +61,7 @@ public class CacheService {
DeviceInfoCache deviceInfoCache = new DeviceInfoCache();
deviceInfoCache.setDeviceId(equipment.getId());
deviceInfoCache.setDeviceCode(equipment.getCode());
deviceInfoCache.setDeviceName(equipment.getName());
deviceInfoCache.setObjectType(equipment.getObjectType());
deviceInfoCache.setParentDeviceId(equipment.getParentEquipmentId());
deviceInfoCaches.add(deviceInfoCache);
@ -68,8 +69,7 @@ public class CacheService {
deviceCodeIndex.put(deviceInfoCache.getDeviceCode(),i);
//创建Id索引
deviceIdIndex.put(equipment.getId(),i);
//关联风场缓存
// if (equipment.getObjectType().equals()
}
}

View File

@ -1,12 +1,11 @@
package com.das.modules.calc.functions;
import com.das.modules.calc.domain.vo.DeviceInfoCache;
import com.das.modules.calc.service.CacheService;
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.*;
import com.googlecode.aviator.utils.Env;
import lombok.extern.slf4j.Slf4j;
import java.util.HashMap;

View File

@ -1,11 +1,9 @@
package com.das.modules.calc.functions;
import com.das.modules.calc.domain.vo.DeviceInfoCache;
import com.das.modules.calc.service.CacheService;
import com.das.modules.data.domain.SnapshotValueQueryParam;
import com.das.modules.cache.domain.DeviceInfoCache;
import com.das.modules.cache.service.CacheService;
import com.das.modules.data.service.DataService;
import com.das.modules.node.domain.bo.CalculateRTData;
import com.googlecode.aviator.runtime.function.AbstractFunction;
import com.googlecode.aviator.runtime.function.AbstractVariadicFunction;
import com.googlecode.aviator.runtime.function.FunctionUtils;
import com.googlecode.aviator.runtime.type.AviatorObject;
@ -79,7 +77,7 @@ public class FunctionSaveCalcData extends AbstractVariadicFunction {
dataList.add(dt);
}
// dataService.updateCalFieldData(dataList);
dataService.updateCalFieldData(dataList);
return AviatorRuntimeJavaType.valueOf(0);
}
}

View File

@ -1,5 +1,6 @@
package com.das.modules.calc.service;
import com.das.modules.cache.service.CacheService;
import com.das.modules.calc.domain.entity.CalcModule;
import com.googlecode.aviator.AviatorEvaluatorInstance;
import com.googlecode.aviator.Expression;

View File

@ -1,6 +1,7 @@
package com.das.modules.calc.service;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.das.modules.cache.service.CacheService;
import com.das.modules.calc.domain.entity.CalcModule;
import com.das.modules.calc.functions.FunctionRealData;
import com.das.modules.calc.functions.FunctionSaveCalcData;

View File

@ -11,6 +11,7 @@ import com.zaxxer.hikari.HikariConfig;
import com.zaxxer.hikari.HikariDataSource;
import jakarta.annotation.PreDestroy;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.scheduling.annotation.Async;
import org.springframework.scheduling.annotation.EnableAsync;
@ -31,8 +32,6 @@ public class TDEngineService {
private HikariDataSource hikariDataSource;
private DataServiceImpl dataService;
@Value("${tdengine.url}")
private String url;
@ -348,10 +347,11 @@ public class TDEngineService {
sb.setLength(0);
sb.append("insert into ");
for (CalculateRTData dv : list) {
sb.append("c");
sb.append("c_");
sb.append(dv.getDeviceId());
sb.append("_").append(dv.getIotModelField());
sb.append(" using c_");
sb.append(dataService.deviceModelMap.get(dv.getDeviceId().toString()));
sb.append(dv.getModelCode());
sb.append("_").append(dv.getIotModelField());
sb.append(" tags (");
sb.append(dv.getDeviceId());

View File

@ -175,6 +175,7 @@ public class DataServiceImpl implements DataService {
for (CalculateRTData value : list) {
String key = String.format("RT:%s:%s", value.getDeviceId(), value.getIotModelField().toLowerCase());
keyValueMap.put(key, value.getDataValue());
value.setModelCode(deviceModelMap.get(value.getDeviceId().toString()));
}
adminRedisTemplate.mSet(keyValueMap);
tdEngineService.updateCalFieldValues(list);

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.data.service.impl.DataServiceImpl;
import com.das.modules.equipment.domain.dto.SysEquipmentDto;
import com.das.modules.equipment.domain.excel.SysEquipmentExcel;
import com.das.modules.equipment.domain.vo.SysEquipmentVo;
@ -58,6 +59,9 @@ public class SysEquipmentServiceImpl implements SysEquipmentService {
@Autowired
private SysIotModelMapper sysIotModelMapper;
@Autowired
private DataServiceImpl dataService;
@Override
public SysEquipmentVo creatSysEquipment(SysEquipmentDto sysEquipmentDto) {
//去除空格
@ -76,6 +80,10 @@ public class SysEquipmentServiceImpl implements SysEquipmentService {
sysEquipment.setUpdatedBy(sysUserVo.getAccount());
sysEquipment.setRevision(1);
sysEquipmentMapper.insert(sysEquipment);
//物模型不为空 增加设备物模型mapping缓存
if (sysEquipment.getIotModelId() != null){
dataService.deviceModelMap.put(sysEquipment.getId().toString(),dataService.iotModelMap.get(sysEquipment.getIotModelId().toString()));
}
SysEquipmentVo sysEquipmentVo = new SysEquipmentVo();
BeanCopyUtils.copy(sysEquipment, sysEquipmentVo);
return sysEquipmentVo;
@ -98,6 +106,9 @@ public class SysEquipmentServiceImpl implements SysEquipmentService {
sysEquipment.setUpdatedTime(new Date());
sysEquipment.setUpdatedBy(sysUserVo.getAccount());
sysEquipmentMapper.updateById(sysEquipment);
if (oldSysEquipment.getIotModelId() == null && sysEquipment.getIotModelId() != null){
dataService.deviceModelMap.put(sysEquipment.getId().toString(),dataService.iotModelMap.get(sysEquipment.getIotModelId().toString()));
}
SysEquipmentVo sysEquipmentVo = new SysEquipmentVo();
BeanCopyUtils.copy(sysEquipment, sysEquipmentVo);
return sysEquipmentVo;
@ -110,6 +121,8 @@ public class SysEquipmentServiceImpl implements SysEquipmentService {
throw new RuntimeException("该设备下有子设备,不能删除");
}
sysEquipmentMapper.deleteById(sysEquipmentDto.getId());
//删除缓存
dataService.deviceModelMap.remove(sysEquipmentDto.getId().toString());
}
@Override
@ -277,12 +290,30 @@ public class SysEquipmentServiceImpl implements SysEquipmentService {
}
}
sysEquipmentMapper.insertBatch(addSysEquipmentList);
for (SysEquipment item : addSysEquipmentList){
if (item.getIotModelId() != null){
String modelCode = dataService.iotModelMap.get(item.getIotModelId().toString());
dataService.deviceModelMap.put(item.getId().toString(),modelCode);
}
}
if (CollectionUtils.isNotEmpty(updateSysEquipmentList)) {
sysEquipmentMapper.updateBatchById(updateSysEquipmentList);
for (SysEquipment item : updateSysEquipmentList){
if (item.getIotModelId() != null){
String modelCode = dataService.iotModelMap.get(item.getIotModelId().toString());
dataService.deviceModelMap.put(item.getId().toString(),modelCode);
}
}
}
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);
}
}
}
}

View File

@ -20,4 +20,6 @@ public class CalculateRTData {
private Object dataValue;
private String iotModelField;
private String modelCode;
}

View File

@ -5,7 +5,7 @@ import com.das.common.constant.MeasType;
import com.das.common.constant.SysAuthorityIds;
import com.das.common.exceptions.ServiceException;
import com.das.common.result.R;
import com.das.modules.operation.domain.CommandInfoDto;
import com.das.modules.operation.domain.dto.CommandInfoDto;
import com.das.modules.operation.service.OperationService;
import jakarta.servlet.http.HttpServletRequest;
import lombok.extern.slf4j.Slf4j;

View File

@ -0,0 +1,36 @@
package com.das.modules.operation.controller;
import com.das.common.result.R;
import com.das.common.utils.PageDataInfo;
import com.das.modules.operation.domain.dto.EventLogDto;
import com.das.modules.operation.domain.vo.SysOperationLogVo;
import com.das.modules.operation.service.OperationLogService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@Slf4j
@RequestMapping("/api/operation")
@RestController
public class OperationLogController {
@Autowired
private OperationLogService operationLogService;
/**
* 获取事件记录
*/
@PostMapping("/getEventLogList")
public R<PageDataInfo<SysOperationLogVo>> getEventLogList(@RequestBody EventLogDto eventLogDto) {
if (eventLogDto.getPageNum() ==null){
eventLogDto.setPageNum(1);
}
if (eventLogDto.getPageSize() ==null){
eventLogDto.setPageSize(30);
}
return R.success(operationLogService.getEventLogList(eventLogDto));
}
}

View File

@ -1,4 +1,4 @@
package com.das.modules.operation.domain;
package com.das.modules.operation.domain.dto;
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
import com.fasterxml.jackson.databind.ser.std.ToStringSerializer;

View File

@ -0,0 +1,22 @@
package com.das.modules.operation.domain.dto;
import lombok.Data;
@Data
public class EventLogDto {
private String startTime;
private String endTime;
private String windTurbinesCode;
private String userName;
private Integer pageNum;
private Integer pageSize;
}

View File

@ -0,0 +1,70 @@
package com.das.modules.operation.domain.vo;
import com.fasterxml.jackson.annotation.JsonFormat;
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
import com.fasterxml.jackson.databind.ser.std.ToStringSerializer;
import lombok.Data;
import java.io.Serial;
import java.util.Date;
@Data
public class SysOperationLogVo {
@Serial
private static final long serialVersionUID = 1L;
/**
* 记录id
*/
@JsonSerialize(using = ToStringSerializer.class)
private Long id;
/**
* 操作人账号
*/
private String userId;
/**
* 操作人名称
*/
private String userName;
/**
* 操作时间
*/
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private Date optTime;
/**
* 操作人IP地址
*/
private String ipAddress;
/**
* 操作设备id
*/
private Long deviceId;
/**
* 设备属性编码
*/
private String attributeCode;
/**
* 设备属性名称
*/
private String attributeName;
/**
* 操作描述
*/
private String optDesc;
/**
* 设备名称
*/
private String name;
}

View File

@ -43,4 +43,6 @@ public class SysManualStatus extends BaseEntity {
*/
@TableField("status")
private Integer status;
private Integer count;
}

View File

@ -1,9 +1,12 @@
package com.das.modules.operation.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.das.modules.auth.mapper.BaseMapperPlus;
import com.das.modules.operation.entity.SysManualStatus;
import org.apache.ibatis.annotations.Mapper;
@Mapper
public interface SysManualStatusMapper extends BaseMapper<SysManualStatus> {
public interface SysManualStatusMapper extends BaseMapperPlus<SysManualStatus,SysManualStatus> {
int sysManualStatusInsertOrUpdate(SysManualStatus sysManualStatus);
}

View File

@ -1,9 +1,18 @@
package com.das.modules.operation.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.das.modules.auth.mapper.BaseMapperPlus;
import com.das.modules.operation.domain.dto.EventLogDto;
import com.das.modules.operation.domain.vo.SysOperationLogVo;
import com.das.modules.operation.entity.SysOperationLog;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
@Mapper
public interface SysOperationLogMapper extends BaseMapper<SysOperationLog> {
public interface SysOperationLogMapper extends BaseMapperPlus<SysOperationLog,SysOperationLog> {
IPage<SysOperationLogVo> getEventLogList(IPage<SysOperationLogVo> page ,
@Param("info") EventLogDto eventLogDto);
}

View File

@ -0,0 +1,10 @@
package com.das.modules.operation.service;
import com.das.common.utils.PageDataInfo;
import com.das.modules.operation.domain.dto.EventLogDto;
import com.das.modules.operation.domain.vo.SysOperationLogVo;
public interface OperationLogService {
PageDataInfo<SysOperationLogVo> getEventLogList(EventLogDto eventLogDto);
}

View File

@ -12,7 +12,7 @@ import com.das.modules.node.disruptor.TerminalMessageEventHandler;
import com.das.modules.node.domain.bo.TerminalMessage;
import com.das.modules.node.domain.vo.SysNodeVo;
import com.das.modules.node.mapper.SysNodeMapper;
import com.das.modules.operation.domain.CommandInfoDto;
import com.das.modules.operation.domain.dto.CommandInfoDto;
import com.das.modules.operation.entity.SysManualStatus;
import com.das.modules.operation.entity.SysOperationLog;
import com.das.modules.operation.mapper.SysManualStatusMapper;
@ -71,11 +71,11 @@ public class OperationService {
sysManualStatus.setDeviceId(cmdInfo.getDeviceId());
sysManualStatus.setAttributeCode(cmdInfo.getServiceCode());
sysManualStatus.setStatus( Integer.valueOf(cmdInfo.getOpValue().toString()));
sysManualStatusMapper.insert(sysManualStatus);
sysManualStatusMapper.sysManualStatusInsertOrUpdate(sysManualStatus);
// 2更新redis RT实时数据值
String key = String.format("RT:%s:%s", cmdInfo.getDeviceId(), cmdInfo.getServiceCode().toLowerCase());
// 存入redis
adminRedisTemplate.set(key, cmdInfo.getOpValue());
adminRedisTemplate.set(key, cmdInfo.getOpValue());
} else {
sendCommand(cmdInfo); //发送命令消息
}

View File

@ -0,0 +1,30 @@
package com.das.modules.operation.service.impl;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.das.common.utils.PageDataInfo;
import com.das.common.utils.PageQuery;
import com.das.modules.operation.domain.dto.EventLogDto;
import com.das.modules.operation.domain.vo.SysOperationLogVo;
import com.das.modules.operation.mapper.SysOperationLogMapper;
import com.das.modules.operation.service.OperationLogService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
@Service
public class OperationLogServiceImpl implements OperationLogService {
@Autowired
private SysOperationLogMapper sysOperationLogMapper;
/**
* 获取事件记录
*/
@Override
public PageDataInfo<SysOperationLogVo> getEventLogList(EventLogDto eventLogDto) {
PageQuery pageQuery = new PageQuery();
pageQuery.setPageNum(eventLogDto.getPageNum());
pageQuery.setPageSize(eventLogDto.getPageSize());
IPage<SysOperationLogVo> iPage = sysOperationLogMapper.getEventLogList(pageQuery.build(), eventLogDto);
return PageDataInfo.build(iPage.getRecords(), iPage.getTotal());
}
}

View File

@ -1,9 +1,10 @@
package com.das.modules.page.controller;
import com.das.common.result.R;
import com.das.modules.page.domian.HomeWindFarmRealDataVo;
import com.das.modules.page.domian.HomeWindTurbineMatrixDataVoVo;
import com.das.modules.data.domain.TSValueQueryParam;
import com.das.modules.page.domian.dto.WindFarmRealDataDto;
import com.das.modules.page.domian.vo.HomeWindFarmRealDataVo;
import com.das.modules.page.domian.vo.HomeWindTurbineMatrixDataVoVo;
import com.das.modules.page.service.HomeService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
@ -13,6 +14,7 @@ import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.List;
import java.util.Map;
/**
* 首页相关Controller
@ -46,4 +48,12 @@ public class HomeController {
}
/**
* 获取历史数据(1.获取功率趋势的曲线,2.发电量日 趋势的曲线)
*/
@PostMapping("/getHistoryData")
public R<Map<String, Map<String, Map<String, Object>>>> getHistoryData(@RequestBody TSValueQueryParam param) {
return R.success(homeService.getHistoryData(param));
}
}

View File

@ -1,4 +1,4 @@
package com.das.modules.page.domian;
package com.das.modules.page.domian.vo;
import lombok.Data;

View File

@ -1,4 +1,4 @@
package com.das.modules.page.domian;
package com.das.modules.page.domian.vo;
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
import com.fasterxml.jackson.databind.ser.std.ToStringSerializer;

View File

@ -1,10 +1,12 @@
package com.das.modules.page.service;
import com.das.modules.page.domian.HomeWindFarmRealDataVo;
import com.das.modules.page.domian.HomeWindTurbineMatrixDataVoVo;
import com.das.modules.data.domain.TSValueQueryParam;
import com.das.modules.page.domian.dto.WindFarmRealDataDto;
import com.das.modules.page.domian.vo.HomeWindFarmRealDataVo;
import com.das.modules.page.domian.vo.HomeWindTurbineMatrixDataVoVo;
import java.util.List;
import java.util.Map;
public interface HomeService {
/**
@ -22,30 +24,13 @@ public interface HomeService {
*/
HomeWindFarmRealDataVo getWindFarmRealData(WindFarmRealDataDto windFarmRealDataDto);
/**
* 接口3 获取功率趋势的曲线
* 页面左下角功率趋势
* 测点WindFarmActivePower全场总有功功率WindFarmAvgWindSpeed 全场平均风速
* 时间间隔 5分钟
* 时间区间从 今天的00:00:00 现在的时间
*/
/**
* 接口4 发电量日 趋势的曲线
* 页面右中角发电量趋势中的日
* 测点WindFarmDayProdEnergy 日发电量
* 时间间隔 1天
* 时间区间从 本月1号 到月末31号 一个月一天一条记录
* 同期就是去年对应时间
* 获取历史数据(1.获取功率趋势的曲线,2.发电量日趋势的曲线)
*/
Map<String, Map<String, Map<String, Object>>> getHistoryData(TSValueQueryParam param);
/**
* 接口5 发电量月 趋势的曲线
* 页面右中角发电量趋势中的日
* 测点WIndFarmMonthProdEnergy 月发电量
* 时间间隔 1月
* 时间区间从 今年的1月1日 年底12月31日 一年每个月一条记录
* 同期就是去年对应时间
*/
}

View File

@ -2,15 +2,19 @@ package com.das.modules.page.service.impl;
import com.das.common.constant.EquipmentTypeIds;
import com.das.common.utils.JsonUtils;
import com.das.modules.data.domain.SnapshotValueQueryParam;
import com.das.modules.data.domain.TSValueQueryParam;
import com.das.modules.data.service.impl.DataServiceImpl;
import com.das.modules.equipment.domain.dto.SysEquipmentDto;
import com.das.modules.equipment.domain.vo.SysEquipmentVo;
import com.das.modules.equipment.mapper.SysEquipmentMapper;
import com.das.modules.page.domian.HomeWindFarmRealDataVo;
import com.das.modules.page.domian.HomeWindTurbineMatrixDataVoVo;
import com.das.modules.page.domian.dto.WindFarmRealDataDto;
import com.das.modules.page.domian.vo.HomeWindFarmRealDataVo;
import com.das.modules.page.domian.vo.HomeWindTurbineMatrixDataVoVo;
import com.das.modules.page.service.HomeService;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
@ -18,6 +22,7 @@ import java.util.ArrayList;
import java.util.List;
import java.util.Map;
@Slf4j
@Service
public class HomeServiceImpl implements HomeService {
@ -145,4 +150,35 @@ public class HomeServiceImpl implements HomeService {
}
return homeWindFarmRealDataVo;
}
/**
* 获取历史数据(1.获取功率趋势的曲线,2.发电量日 趋势的曲线)
*/
@Override
public Map<String, Map<String, Map<String, Object>>> getHistoryData(TSValueQueryParam param) {
List<SnapshotValueQueryParam> devices = param.getDevices();
for (SnapshotValueQueryParam device : devices) {
if (StringUtils.isBlank(device.getDeviceId())){
//查询数据库中风电场设备取第一个风电场
SysEquipmentDto sysEquipmentDto = new SysEquipmentDto();
sysEquipmentDto.setObjectType(EquipmentTypeIds.EQUIPMENT_TYPE_WIND_FARM);
List<SysEquipmentVo> list = sysEquipmentMapper.queryEquipmentList(sysEquipmentDto);
if(list.isEmpty()){
throw new RuntimeException("系统中没有风电场台账信息");
}
device.setDeviceId(list.get(0).getId().toString());
}
}
Map<String, Map<String, Map<String, Object>>> mapResult = dataServiceImpl.queryTimeSeriesValues(param);
if (log.isDebugEnabled()){
log.debug("/api/home/getHistoryData is calling");
log.debug("参数:"+JsonUtils.toJsonString(param));
log.debug("返回值:"+JsonUtils.toJsonString(mapResult));
}
return mapResult;
}
}

View File

@ -0,0 +1,100 @@
server:
port: 8080
# SpringBoot中我们既可以使用Tomcat作为Http服务也可以用Undertow来代替。Undertow在高并发业务场景中性能优于Tomcat
undertow:
threads:
# 设置IO线程数, 它主要执行非阻塞的任务,它们会负责多个连接, 默认设置每个CPU核心一个线程
io: 16
# 阻塞任务线程池, 当执行类似servlet请求阻塞操作, undertow会从这个线程池中取得线程,它的值设置取决于系统的负载
worker: 400
# 以下的配置会影响buffer,这些buffer会用于服务器连接的IO操作,有点类似netty的池化内存管理
# 每块buffer的空间大小,越小的空间被利用越充分
buffer-size: 1024
# HTTP post内容的最大大小。当值为-1时默认值为大小是无限的
max-http-post-size: -1
# 是否分配的直接内存
direct-buffers: true
sa-token:
# token有效期单位秒
expireTime: 7200
refreshExpireTime: 604800
# 是否允许同一账号多终端登录默认为true
is-concurrent: true
spring:
application:
name: das
#json格式化全局配置,相当于@JsonFormat
jackson:
time-zone: GMT+8
date-format: yyyy-MM-dd HH:mm:ss
# 指定默认包含的熟悉NON_NULL表示只序列化非空属性
default-property-inclusion: non_null
# 配置文件上传大小限制
servlet:
multipart:
# 单个文件最大大小
max-file-size: 1024MB
# 多个文件总大小
max-request-size: 2048MB
datasource:
url: jdbc:postgresql://192.168.109.102:5432/das
username: das
password: qwaszx12
# # redis相关配置
data:
redis:
host: 192.168.109.195
database: 0
port: 6379
password:
client-type: lettuce
# 配置 xml 文件所在位置 配置全局的 主键策略,默认为 ASSIGN_ID 默认为 【雪花算法】 , auto 自增
mybatis-plus:
mapper-locations: classpath*:/mapper/**/*.xml
# 搜索指定包别名
typeAliasesPackage: com.das.**.entity
global-config:
# 关闭MP3.0自带的banner
banner: false
db-config:
id-type: ASSIGN_ID
# 逻辑删除
logic-not-delete-value: 0
logic-delete-value: 1
#字段策略
insert-strategy: not_null
update-strategy: not_null
where-strategy: not_empty
#驼峰下划线转换
table-underline: true
# 开启驼峰命名 默认开启驼峰命名
# mybatis-plus配置控制台打印完整带参数SQL语句
configuration:
map-underscore-to-camel-case: true
cache-enabled: false
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
# 验证码相关
captcha:
enabled: true
verify-type: calculate
expire: 120
das:
aes:
key: b6967ee87b86d85a
logging:
level:
com:
das: DEBUG
tdengine:
password: taosdata
url: jdbc:TAOS-RS://192.168.109.160:6041/das
username: root

View File

@ -0,0 +1,24 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.das.modules.operation.mapper.SysManualStatusMapper">
<insert id="sysManualStatusInsertOrUpdate" parameterType="com.das.modules.operation.entity.SysManualStatus">
<selectKey keyProperty="count" resultType="int" order="BEFORE">
select count(*) FROM sys_manual_status where device_id =#{deviceId} and attribute_code =#{attributeCode}
</selectKey>
<if test="count == 0">
INSERT INTO public.sys_manual_status
(id, device_id, attribute_code, status)
VALUES(#{id}, #{deviceId}, #{attributeCode}, #{status})
</if>
<if test="count > 0">
UPDATE public.sys_manual_status
SET status=#{status}
WHERE device_id=#{deviceId} AND attribute_code=#{attributeCode}
</if>
</insert>
</mapper>

View File

@ -0,0 +1,58 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.das.modules.operation.mapper.SysOperationLogMapper">
<resultMap type="com.das.modules.operation.domain.vo.SysOperationLogVo" id="EnumValuesMap">
<result property="id" column="id" jdbcType="BIGINT"/>
<result property="userId" column="user_id" jdbcType="VARCHAR"/>
<result property="userName" column="user_name" jdbcType="VARCHAR"/>
<result property="optTime" column="opt_time" jdbcType="VARCHAR"/>
<result property="ipAddress" column="ip_address" jdbcType="VARCHAR"/>
<result property="deviceId" column="device_id" jdbcType="BIGINT"/>
<result property="attributeCode" column="attribute_code" jdbcType="VARCHAR"/>
<result property="attributeName" column="attribute_name" jdbcType="VARCHAR"/>
<result property="optDesc" column="opt_desc" jdbcType="VARCHAR"/>
<result property="name" column="name" jdbcType="VARCHAR"/>
</resultMap>
<select id="getEventLogList" resultMap="EnumValuesMap">
select
sol.*,
se."name"
from sys_operation_log sol
left join
sys_equipment se on sol.device_id = se.id
<where>
<if test="info.startTime != null and info.endTime != null">
and sol.opt_time &gt;= to_timestamp(#{info.startTime}, 'YYYY-MM-DD HH24:MI:SS')
and sol.opt_time &lt;= to_timestamp(#{info.endTime}, 'YYYY-MM-DD HH24:MI:SS')
</if>
<if test="info.windTurbinesCode != null and info.windTurbinesCode != ''">
and se.code =#{info.windTurbinesCode}
</if>
<if test="info.userName != null and info.userName != ''">
and sol.user_name like concat('%',#{info.userName},'%')
</if>
</where>
order by sol.opt_time desc
</select>
<!-- <select id="getEventLogList" resultMap="EnumValuesMap">-->
<!-- select e.* from sys_operation_log e-->
<!-- <where>-->
<!-- <if test="info.startTime != null and info.endTime != null">-->
<!-- and e.opt_time &gt;= to_timestamp(#{info.startTime}, 'YYYY-MM-DD HH24:MI:SS')-->
<!-- and e.opt_time &lt;= to_timestamp(#{info.endTime}, 'YYYY-MM-DD HH24:MI:SS')-->
<!-- </if>-->
<!-- <if test="info.attributeCode != null and info.attributeCode != ''">-->
<!-- and e.attribute_code =#{info.attributeCode}-->
<!-- </if>-->
<!-- <if test="info.userName != null and info.userName != ''">-->
<!-- and e.user_name like concat('%',#{info.userName},'%')-->
<!-- </if>-->
<!-- </where>-->
<!-- order by e.opt_time desc-->
<!-- </select>-->
</mapper>

View File

@ -4,6 +4,8 @@
- [设备管理接口](api/equipment.md)
- [节点管理接口](api/node.md)
- [数据访问接口](api/data.md)
- [数据字段接口](api/enumPage.md)
- [人工操作接口](api/operation.md)
- [页面访问接口](api/pages/)
- [首页接口](api/pages/home.md)
- [数据采集](datacollect/)

195
docs/api/operation.md Normal file
View File

@ -0,0 +1,195 @@
# 操作相关模块
## API接口一览表
| 接口分类 | 接口描述 | API接口 | 权限 |
|---------|-----------------| ------------------------------------- | --------------------------- |
| 2.1操作记录 | 2.1.1获取相关操作记录 | /api/operation/getEventLogList | |
| 2.2手动操作 | 2.2.1设备遥控操作 下令 | /api/operation/command | SYS_AUTHORITY_ID_DEVICE_CTRL|
| | 2.2.2设备遥调操作 下令 | /api/operation/setPoint | SYS_AUTHORITY_ID_DEVICE_CTRL|
| | 2.2.3设备手工至位 不下令 | /api/operation/manualCommand | SYS_AUTHORITY_ID_DEVICE_CTRL|
### 2.1 操作相关模块接口
#### 2.1.1 获取相关操作记录
POST 请求接口
> /api/operation/getEventLogList
请求参数
```json
{
"startTime": "2024-10-21 23:00:00:00",
"endTime": "2024-10-31 23:00:00:00",YES
"windTurbinesCode": "SC-01",
"userName": "张三",
"pageNum": 1,
"pageSize": 10
}
```
入参描述
| 参数名 | 参数类型 | 可选 | 描述 |
| ------------ |---------|-----|--------|
| startTime | String | YES | 开始时间 |
| endTime | String | YES | 结束时间 |
| windTurbinesCode | String | YES | 风机编号 |
| userName | String | YES | 操作人员 |
| pageNum | Integer | NO | 当前页 |
| pageSize | Integer | NO | 每页显示大小 |
返回报文
```json
{
"code": 200,
"success": true,
"data": {
"total": 2,
"rows": [
{
"id": "1851903283326214146",
"userName": "张三",
"optTime": "2024-10-31 16:25:24",
"deviceId": 863256444266222,
"attributeCode": "testCode",
"attributeName": "测试遥控2",
"optDesc": "手动调试2",
"name": "A-001"
}
],
"code": 200,
"msg": "查询成功"
},
"msg": "操作成功"
}
```
返参描述
| 参数名 | 参数类型 | 可选 | 描述 |
|---------------|--------| ---- |--------|
| id | Long | 否 | id |
| optTime | String | 否 | 时间 |
| attributeCode | String | 否 | 风机编号 |
| attributeName | String | 否 | 操作类型 |
| optDesc | String | 否 | 操作详情 |
| userName | String | 否 | 操作员 |
| deviceId | Long | 否 | 操作设备id |
| name | String | 否 | 设备名称 |
### 2.2 手动操作相关接口
#### 2.2.1 设备遥控操作 下令
POST 请求接口
> /api/operation/command
请求参数
```json
{
"deviceId": 863256444266222,
"serviceCode": "setTurbineStop",
"serviceName": "风机停机指令",
"optDesc": "风机停机",
"opValue": 0
}
```
入参描述
| 参数名 | 参数类型 | 可选 | 描述 |
| ------------ |---------|-----|------|
| deviceId | Long | NO | 设备id |
| serviceCode | String | NO | 命令编码 |
| serviceName | String | YES | 遥控名称 |
| optDesc | String | YES | 风机停机、风机启动等操作描述 |
| opValue | Integer | NO | 遥控值 |
返回报文
```json
{
"code": 200,
"success": true,
"msg": "操作成功"
}
```
#### 2.2.2 设备遥调操作 下令
POST 请求接口
> /api/operation/setPoint
请求参数
```json
{
"deviceId": 863256444266222,
"serviceCode": "setGenSpeedLimitValue",
"serviceName": "发电机转速给定值",
"optDesc": "发电机转速给定值:12.45",
"opValue": 12.45
}
```
入参描述
| 参数名 | 参数类型 | 可选 | 描述 |
| ------------ |---------|-----|------|
| deviceId | Long | NO | 设备id |
| serviceCode | String | NO | 命令编码 |
| serviceName | String | YES | 遥控名称 |
| optDesc | String | YES | 设定值描述 |
| opValue | Integer | NO | 遥控值 |
返回报文
```json
{
"code": 200,
"success": true,
"msg": "操作成功"
}
```
#### 2.2.3 设备手工至位 不下令
POST 请求接口
> /api/operation/manualCommand
请求参数
```json
{
"deviceId": 863256444266222,
"serviceCode": "Locked",
"serviceName": "风机被锁定",
"optDesc": "风机锁定",
"opValue": 1
}
```
入参描述
| 参数名 | 参数类型 | 可选 | 描述 |
| ------------ |---------|-----|------|
| deviceId | Long | NO | 设备id |
| serviceCode | String | NO | 命令编码 |
| serviceName | String | YES | 遥控名称 |
| optDesc | String | YES | 风机锁定、风机解锁类似这样的描述 |
| opValue | Integer | NO | 遥控值 |
返回报文
```json
{
"code": 200,
"success": true,
"msg": "操作成功"
}
```

View File

@ -2,25 +2,19 @@
## API接口一览表
| 接口分类 | 接口描述 | 查询条件 | API接口 | 权限 |
|--------|-------------|------------------------------| ---------------------------- | ---------------------------- |
| 2.1 首页 | 2.1.1风场概况 | 实时数据,没有入参 | /api/home/windFarmOverview | |
| | 2.1.2今日运行状态 | 实时数据,没有入参 | /api/home/currentDayStatus | |
| | 2.1.3功率趋势 | 当天24小时数据5分钟间隔 | /api/home/powerTrends | |
| | 2.1.4风机矩阵 | 实时数据,没有参数 | /api/home/windTurbineMatrix | |
| | 2.1.5发电量概况 | 昨日数据; | /api/home/generationOverview | |
| | 2.1.6发电量趋势 | 日: 当前月的每日数据同期是去年数据当年每月月数据12个月 | /api/home/generationTrend | |
| | 2.1.7实时告警 | 近一个月内所有的报警数据,滚动显示;已确认的数据 按钮灰色;未确认的显示确认按钮 | /api/home/realTimeAlert | |
| | 2.1.8实时告警确认 | 告警记录ID | /api/home/realTimeAlertConfirm | |
| 接口分类 | 接口描述 | 查询条件 | API接口 | 权限 |
|--------|----------------|-----------| ---------------------------- | ---------------------------- |
| 2.1 首页 | 2.1.1风机矩阵 | 实时数据,没有入参 | /api/home/getWindTurbineMatrixData | |
| | 2.1.2获取风电场实时数据 | 风电场id | /api/home/getWindFarmRealData | |
## 2.1 首页相关接口
### 2.1.1 风场概况
### 2.1.1 风机矩阵
POST 请求接口
> /api/home/windFarmOverview
> /api/home/getWindTurbineMatrixData
请求参数
@ -32,366 +26,157 @@ POST 请求接口
```json
{
"code": 200,
"msg": "操作成功",
"success": true,
"data":
{
"power": 56.2,
"windSpeed": 45.3,
"dailyUsageHours": 20,
"monthlyUsageHours": 78
}
"code": 200,
"success": true,
"data": [
{
"irn": "1846101273013739522",
"name": "A-001",
"modelId": "1807685851882508289",
"model": "倍福1.5",
"belongLine": "线路1",
"standard": 1,
"nominalCapacity": 66.23,
"attributeMap": {
"iwindspeed": 10.84000015258789,
"iturbineoperationmode": 6,
"iyplevel": 0,
"ikwhthisday": 0,
"igenpower": 0,
"gridlostdetected": 0
}
}
],
"msg": "操作成功"
}
```
返参描述
| 参数名 | 参数类型 | 可选 | 描述 |
| ------------- |---------| ---- |--------|
| power | Double | 否 | 功率 |
| windSpeed | Double | 否 | 风速 |
| dailyUsageHours | Integer | 否 | 日利用小时数 |
| monthlyUsageHours | Integer | 否 | 月利用小时数 |
| ------------ |---------| ---- |--------|
| standard | Integer | 否 | 是否标杆 |
| nominalCapacity | Double | 否 | 容量 |
| iwindspeed | Double | 否 | 风速 |
| iyplevel | Double | 否 | 偏航运行模式 |
| ikwhthisday | Double | 否 | 日发电量 |
| igenpower | Double | 否 | 有功功率 |
| gridlostdetected | Double | 否 | 风机电网掉电 |
### 2.1.2 今日运行状态
### 2.1.2 获取风电场实时数据
POST 请求接口
> /api/home/currentDayStatus
> /api/home/getWindFarmRealData
请求参数
```json
{
"windFarmId":1846101273013739522
}
```
入参描述
注:实时数据,没有入参
| 参数名 | 参数类型 | 可选 | 描述 |
| ------------ |------| ---- |------|
| windFarmId | Long | yes | 风场id |
返回报文
```json
{
"code": 200,
"msg": "操作成功",
"success": true,
"data":
{
"windTurbineNum": 40,
"installedCapacity": 45.32,
"runCapacity": 20.2,
"runNum": 78,
"standbyCapacity": 63.2,
"standbyNum": 35,
"faultCapacity": 56.4,
"faultNum": 53,
"offlineCapacity": 16.4,
"offlineNum": 20
}
"code": 200,
"success": true,
"data": {
"windFarmId": 1848624295633317890,
"attributeMap": {
"windfarmactivepower": 111.01,
"windfarmavgwindspeed": 111.01,
"windfarmdayoperationhours": 111.01,
"windfarmmonthoperationhours": 111.01,
"windfarmdayprodenergy": 111.01,
"windfarmmonthprodenergy": 111.01,
"windfarmyearprodenergy": 111.01,
"windfarmtotalprodenergy": 111.01
}
},
"msg": "操作成功"
}
```
返参描述
| 参数名 | 参数类型 | 可选 | 描述 |
| ------------- |---------| ---- |------|
| windTurbineNum | Integer | 否 | 风机台数 |
| installedCapacity | Double | 否 | 装机容量 |
| runCapacity | Double | 否 | 运行容量 |
| runNum | Integer | 否 | 运行台数 |
| standbyCapacity | Double | 否 | 待机容量 |
| standbyNum | Integer | 否 | 待机台数 |
| faultCapacity | Double | 否 | 故障容量 |
| faultNum | Integer | 否 | 故障台数 |
| offlineCapacity | Double | 否 | 离线容量 |
| offlineNum | Integer | 否 | 离线台数 |
| windFarmId | Long | 否 | 风场id |
| windfarmactivepower | Double | 否 | 功率 |
| windfarmavgwindspeed | Double | 否 | 平均风速 |
| windfarmdayoperationhours | Double | 否 | 日利用小时 |
| windfarmmonthoperationhours | Double | 否 | 月利用小时 |
| windfarmdayprodenergy | Double | 否 | 日发电量 |
| windfarmmonthprodenergy | Double | 否 | 月发电量 |
| windfarmyearprodenergy | Double | 否 | 年发电量 |
| windfarmtotalprodenergy | Double | 否 | 总发电量 |
### 2.1.3 功率趋势
### 2.1.3 获取风电场历史数据
POST 请求接口
> /api/home/powerTrends
> /api/home/getHistoryData
请求参数
注:当天24小时数据5分钟间隔
返回报文
```json
{
"code": 200,
"msg": "操作成功",
"success": true,
"data": [
{
"power": 12.6,
"windSpeed": 56.3,
"dataTime": "2024-10-17 00:00:00"
},
{
"power": 10.6,
"windSpeed": 16.3,
"dataTime": "2024-10-17 00:05:00"
}
]
"startTime": "123452435324242",
"endTime": "123452435924242",
"devices": [
{
"deviceId":"129476828342323",
"attributes":["power","windSpeed"]
},
{
"deviceId":"129476828342324",
"attributes":["power","dailyUsageHours"]
}
],
"interval": "5m"
}
```
返参描述
| 参数名 | 参数类型 | 可选 | 描述 |
|-------------|--------| ---- |------|
| power | Double | 否 | 功率 |
| windSpeed | Double | 否 | 风速 |
| dataTime | String | 否 | 数据时间 |
### 2.1.4 风机矩阵
POST 请求接口
> /api/home/windTurbineMatrix
请求参数
注:实时数据
返回报文
```json
{
"code": 200,
"msg": "操作成功",
"success": true,
"data": [
{
"power": 12.6,
"windSpeed": 56.3,
"dayGeneration": 56.2,
"windStatus": "并网",
"standard": 1,
"windTurbine": "GDWT00001"
},
{
"power": 12.6,
"windSpeed": 56.3,
"dayGeneration": 56.2,
"windStatus": "待机",
"standard": 1,
"windTurbine": "GDWT00002"
}
]
}
```
返参描述
| 参数名 | 参数类型 | 可选 | 描述 |
|-------------|---------| ---- |---------|
| power | Double | 否 | 功率 |
| windSpeed | Double | 否 | 风速 |
| dayGeneration | Double | 否 | 日发电量 |
| windStatus | String | 否 | 风机状态 |
| standard | Integer | 否 | 是否为标杆机组 |
| windTurbine | String | 否 | 风机编码 |
### 2.1.5 发电量概况
POST 请求接口
> /api/home/generationOverview
请求参数
```json
{
"requestTime":"2024-10-16"
}
```
入参描述
| 参数名 | 参数类型 | 可选 | 描述 |
| ------------ | -------- | ---- |------|
| requestTime | String | yes | 请求时间 |
注:请求时间默认为:昨日时间
| startTime | String | no | 开始时间戳 |
| endTime | String | no | 结束时间戳 |
| devices.deviceId | String | no | 设备ID |
| devices.attributes | StringArray | no | 要查询实时数据的设备属性列表 |
| interval | String | yes | 抽样间隔,1a(毫秒),1s(秒),1m(分),1h(小时),1d(天),1w(周)。 忽略或者值为空时,返回原始数据(不抽样) |
| endTime | String | no | 结束时间戳 |
返回报文
```json
{
"code": 200,
"msg": "操作成功",
"success": true,
"data":
{
"dailyGeneration": 63.2,
"dayGeneration": 56.2,
"monthGeneration": 60.2,
"yearGeneration": 200.6,
"totalGeneration": 500.6
"code": 200,
"msg": "操作成功",
"success": true,
"data": {
//设备ID
"129476828342323":{
//属性名
"power": {
//时间戳列表
"times": [123452435924242,123452435924342,123452435924442,123452435924542],
//值列表
"values": [123.23,35.21,34.56,67]
} ,
//属性名
"windSpeed": {
"times": [123452435924242,123452435924342,123452435924442,123452435924542],
"values": [123.23,35.21,34.56,67]
}
},
.......
}
}
```
返参描述
| 参数名 | 参数类型 | 可选 | 描述 |
|-------------|---------| ---- |-------|
| dailyGeneration | Double | 否 | 当日发电量 |
| dayGeneration | Double | 否 | 日发电量 |
| monthGeneration | Double | 否 | 月发电量 |
| yearGeneration | Double | 否 | 年发电量 |
| totalGeneration | Double | 否 | 总发电量 |
### 2.1.6 发电量趋势
POST 请求接口
> /api/home/generationTrend
请求参数
```json
{
"timeType":"日"
}
```
注: 日:当前月的每日数据;同期是去年数据;月:当年每月月数据12个月
入参描述
| 参数名 | 参数类型 | 可选 | 描述 |
| ------------ | -------- | ---- |------|
| timeType | String | yes | 时间类型 |
返回报文
```json
{
"code": 200,
"msg": "操作成功",
"success": true,
"data": [
{
"currentPeriod": 56.3,
"samePeriod": 63.5,
"generationTime": "2024-10-01"
},
{
"currentPeriod": 66.3,
"samePeriod": 73.5,
"generationTime": "2024-10-02"
}
]
}
```
返参描述
| 参数名 | 参数类型 | 可选 | 描述 |
|-------------|---------| ---- |-------|
| currentPeriod | Double | 否 | 本期 |
| samePeriod | Double | 否 | 同期 |
| generationTime | Double | 否 | 发电量时间 |
### 2.1.7 实时告警
POST 请求接口
> /api/home/realTimeAlert
请求参数
注:近一个月内所有的报警数据,滚动显示;已确认的数据 按钮灰色;未确认的显示确认按钮
返回报文
```json
{
"code": 200,
"msg": "操作成功",
"success": true,
"data": [
{
"alertTime": "2024-10-16 12:16:42",
"windTurbine": "SC-01",
"alertContent": "故障",
"alertId": 4562366,
"confirmStatus": 0
},
{
"alertTime": "2024-10-16 12:16:42",
"windTurbine": "SC-01",
"alertContent": "待机",
"alertId": 4562366555,
"confirmStatus": 1
}
]
}
```
返参描述
| 参数名 | 参数类型 | 可选 | 描述 |
|-------------|---------| ---- |------|
| alertTime | String | 否 | 告警时间 |
| windTurbine | String | 否 | 风机编码 |
| alertContent | String | 否 | 告警内容 |
| alertId | Long | 否 | 告警id |
| confirmStatus | Integer | 否 | 确认状态 |
### 2.1.8 实时告警-确认
POST 请求接口
> /api/home/realTimeAlertConfirm
请求参数
```json
{
"alertId":45566222
}
```
入参描述
| 参数名 | 参数类型 | 可选 | 描述 |
| ------------ |------| ---- |------|
| alertId | Long | yes | 告警id |
返回报文
```json
{
"code": 200,
"msg": "操作成功",
"success": true,
"data": [
]
}
```
返参描述
| 参数名 | 参数类型 | 可选 | 描述 |
|-------------|---------| ---- |-------|

Binary file not shown.

After

Width:  |  Height:  |  Size: 263 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 615 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 598 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 606 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 389 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 365 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 423 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 348 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 348 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 419 KiB

View File

@ -1,3 +1,51 @@
# OpenEuler 24.03 (LTS) 安装部署
!> 等待更新
## 操作系统安装
OpenEuler的安装和Centos差不多这里不详细说明了就把几个建议步骤说下这样可以减少后期手动配置的麻烦。
![alt text](asserts/image-2.png)
> 语言这里选 `English`
![alt text](asserts/image-3.png)
> `Keyboard` 不修改。
> `Installation Source` 不修改。
> `Installation Destination` 这里要配置磁盘分区。(不是一定要这样配置,会的随意配置)
![alt text](asserts/image-4.png)
![alt text](asserts/image-5.png)
![alt text](asserts/image-6.png)
点左下角的`+`,新建分区
![alt text](asserts/image-7.png)
`Mount Point`填: /das, 大小不用填,默认会使用剩余所有空间。
![alt text](asserts/image-9.png)
点击 `Done`完成分区配置。
> `Lanage Support` 这个需要勾上中文。
![alt text](asserts/image-10.png)
> `Software Selection` 这里基础环境选择"Server", 右侧再勾上一个开发工具包,以防后续内网环境下编译安装程序时需要。
![alt text](asserts/image-11.png)
> `Network & Hostname` 根据实际情况配置。
> `Time & Date` 时区不用修改了。
下面的root账号密码设置下普通用户就不用创建了。
## 系统配置
!> 未完待续

View File

@ -1,8 +1,19 @@
<template>
<el-aside v-if="!navTabs.state.tabFullScreen" :class="'layout-aside-' + config.layout.layoutMode + ' ' + (config.layout.shrink ? 'shrink' : '')">
<el-aside
v-if="!navTabs.state.tabFullScreen"
:class="[
config.layout.menuCollapse ? '' : 'layout-aside-collapse',
'layout-aside-' + config.layout.layoutMode + ' ' + (config.layout.shrink ? 'shrink' : ''),
]"
>
<Logo v-if="config.layout.menuShowTopBar" />
<MenuVerticalChildren v-if="config.layout.layoutMode == 'Double'" />
<MenuVertical v-else />
<div class="layout-toggle-bar" @click="onMenuCollapse">
<div class="layout-toggle-bar__top"></div>
<div class="layout-toggle-bar__bottom"></div>
</div>
</el-aside>
</template>
@ -22,6 +33,14 @@ const config = useConfig()
const navTabs = useNavTabs()
const menuWidth = computed(() => config.menuWidth())
const onMenuCollapse = () => {
if (config.layout.menuCollapse) {
config.setLayout('menuCollapse', false)
} else {
config.setLayout('menuCollapse', true)
}
}
</script>
<style scoped lang="scss">
@ -43,6 +62,7 @@ const menuWidth = computed(() => config.menuWidth())
overflow: hidden;
transition: width 0.3s ease;
width: v-bind(menuWidth);
position: relative;
}
.shrink {
position: fixed;
@ -50,4 +70,60 @@ const menuWidth = computed(() => config.menuWidth())
left: 0;
z-index: 9999999;
}
.layout-toggle-bar {
cursor: pointer;
height: 72px;
width: 32px;
position: absolute;
top: calc(50% - 36px);
right: -9px;
.layout-toggle-bar__top {
background-color: rgba(191, 191, 191, 1);
position: absolute;
width: 4px;
border-radius: 2px;
height: 38px;
left: 14px;
transition:
background-color 0.3s cubic-bezier(0.4, 0, 0.2, 1),
transform 0.3s cubic-bezier(0.4, 0, 0.2, 1);
}
.layout-toggle-bar__bottom {
background-color: rgba(191, 191, 191, 1);
position: absolute;
width: 4px;
border-radius: 2px;
height: 38px;
left: 14px;
top: 34px;
transition:
background-color 0.3s cubic-bezier(0.4, 0, 0.2, 1),
transform 0.3s cubic-bezier(0.4, 0, 0.2, 1);
}
&:hover {
.layout-toggle-bar__top {
background-color: rgba(153, 153, 153, 1);
transform: rotate(-12deg) scale(1.15) translateY(-2px);
}
.layout-toggle-bar__bottom {
background-color: rgba(153, 153, 153, 1);
transform: rotate(12deg) scale(1.15) translateY(2px);
}
}
}
.layout-aside-collapse {
.layout-toggle-bar {
&:hover {
.layout-toggle-bar__top {
background-color: rgba(153, 153, 153, 1);
transform: rotate(12deg) scale(1.15) translateY(-2px);
}
.layout-toggle-bar__bottom {
background-color: rgba(153, 153, 153, 1);
transform: rotate(-12deg) scale(1.15) translateY(2px);
}
}
}
}
</style>

View File

@ -29,6 +29,10 @@ const state = reactive({
defaultActive: '',
})
const width = computed(() => {
return config.layout.menuCollapse ? '5px' : '20px'
})
const verticalMenusScrollbarHeight = computed(() => {
let menuTopBarHeight = 0
if (config.layout.menuShowTopBar) {
@ -70,7 +74,8 @@ onBeforeRouteUpdate((to) => {
}
.layouts-menu-vertical {
border: 0;
padding: 10px 20px 30px 20px;
width: 100%;
padding: 10px v-bind(width) 30px v-bind(width);
--el-menu-bg-color: transparent;
--el-menu-active-color: #ffffff;
li,

View File

@ -350,7 +350,7 @@ const menu = [
],
},
]
debugger
function transformNode(menu: any) {
menu = menu.map((node: any) => {
return {

View File

@ -10,7 +10,7 @@ export const useConfig = defineStore(
/* 全局 */
showDrawer: false,
// 是否收缩布局(小屏设备)
shrink: false,
shrink: true,
// 后台布局方式,可选值<Default|Classic|Streamline|Double>
layoutMode: 'Classic',
// 后台主页面切换动画,可选值<slide-right|slide-left|el-fade-in-linear|el-fade-in|el-zoom-in-center|el-zoom-in-top|el-zoom-in-bottom>
@ -70,7 +70,7 @@ export const useConfig = defineStore(
return layout.menuCollapse ? '0px' : layout.menuWidth + 'px'
}
// 菜单是否折叠
return layout.menuCollapse ? '64px' : layout.menuWidth + 'px'
return layout.menuCollapse ? '74px' : layout.menuWidth + 'px'
}
function setLang(val: string) {

View File

@ -218,12 +218,12 @@ const queryWindBlower = () => {
}
})
windBlowerValue.value = windBlowerList.value.map((item) => item.irn)
reportTableData.value = windBlowerList.value.map((val) => {
return {
deviceId: val.irn,
id: shortUuid(),
}
})
// reportTableData.value = windBlowerList.value.map((val) => {
// return {
// deviceId: val.irn,
// id: shortUuid(),
// }
// })
}
})
}
@ -236,7 +236,7 @@ const selectWindBlower = (val: any) => {
}
})
windBlowerValue.value = rankWindBlowerValue
reportTableData.value = reportTableData.value.filter((item: any) => windBlowerValue.value.includes(item.deviceId))
// reportTableData.value = reportTableData.value.filter((item: any) => windBlowerValue.value.includes(item.deviceId))
} else {
reportTableData.value = []
}
@ -394,6 +394,7 @@ const queryHistoryData = () => {
reportTableData.value = lastResults.flat()
if (!reportTableData.value.length) {
ElMessage.warning('查询数据为空!')
reportTableData.value = []
}
} else {
ElMessage.warning('查询数据为空!')
@ -434,7 +435,7 @@ const generateMissingData = (data: any, deviceIds: any) => {
if (!deviceData.length) {
const generatedData = firstNonEmptyArray.map((item: any) => {
const generatedItem = {
id: item.id + 1,
id: shortUuid(),
time: item.time,
deviceId: deviceId,
} as any

View File

@ -0,0 +1,308 @@
<template>
<div class="report" style="width: 100%; height: 100%">
<div class="topForm">
<div class="forms">
<el-space>
<div style="min-width: 30px">时间</div>
<el-date-picker
v-if="selectedIndex == 0"
style="height: 40px"
v-model="timeValue"
type="date"
value-format="YYYY-MM-DD"
placeholder="选择日期"
/>
<el-date-picker v-else style="height: 40px" v-model="timeValue" type="month" value-format="YYYY-MM" placeholder="选择月份" />
<div style="width: 20px"></div>
<div class="buttons">
<el-button class="button" :icon="Search" @click="queryHistoryData" type="primary">查询</el-button>
<el-button class="button" :icon="Upload" type="primary" plain>导出</el-button>
</div>
</el-space>
<div class="reportButtons">
<el-button
v-for="(button, index) in buttons"
:key="index"
:type="selectedIndex === index ? 'primary' : 'default'"
@click="selectButton(index)"
size="small"
>
{{ button }}
</el-button>
</div>
</div>
</div>
<el-table
class="mainReport"
:columns="reportTableColumn"
:data="reportTableData"
:row-key="(row: any) => row.id"
:row-style="tableRowClassName"
v-loading="reportLoading"
>
<el-table-column prop="time" label="时间" fixed width="180px" align="center"> </el-table-column>
<el-table-column
v-for="item in reportTableColumn.length ? reportTableColumn : [{ prop: '', label: '', unit: '' }]"
:key="item.prop"
:label="item.label"
:prop="item.prop"
align="center"
:width="150"
>
<template #header="scope">
<div class="header-cell">
<div style="margin-right: 2px">
{{ item.label }} <br />
{{ item.unit ? `(${item.unit})` : '' }}
</div>
</div>
</template>
<template #default="scope">
{{ scope.row[item.prop] || scope.row[item.prop] === 0 ? scope.row[item.prop] : '-' }}
</template>
</el-table-column>
</el-table>
</div>
</template>
<script setup lang="ts">
import { reactive, ref, nextTick, onMounted } from 'vue'
import { ElMessage, TableInstance } from 'element-plus'
import { Search, Upload } from '@element-plus/icons-vue'
import { WindBlowerList } from './type'
import { queryWindTurbinesPages, historyReq } from '/@/api/backend/statAnalysis/request'
import { shortUuid } from '/@/utils/random'
import { useI18n } from 'vue-i18n'
const { t } = useI18n()
const selectedIndex = ref(0)
const buttons = ['日报', '月报']
const attributeCodes = ref(['iKWhThisDay', 'iOperationHoursDay', 'iWindSpeed'])
const selectButton = (index: number) => {
if (selectedIndex.value == index) return
selectedIndex.value = index
refreshBaseTable()
}
const refreshBaseTable = () => {
timeValue.value = ''
reportTableColumn.value = []
reportTableData.value = []
if (selectedIndex.value == 0) {
attributeCodes.value = ['iKWhThisDay', 'iOperationHoursDay', 'iWindSpeed']
windBlowerList.value.forEach((item: WindBlowerList) => {
const generateEach = [
{ prop: item.irn + 'iKWhThisDay', label: item.name + '风机发电量', irn: item.irn, name: 'iKWhThisDay', unit: 'kWh' },
{ prop: item.irn + 'iOperationHoursDay', label: item.name + '风机等效小时', irn: item.irn, name: 'iOperationHoursDay', unit: 'h' },
{ prop: item.irn + 'iWindSpeed', label: item.name + '风机平均风速', irn: item.irn, name: 'iWindSpeed', unit: 'm/s' },
]
reportTableColumn.value.push(...generateEach)
})
} else {
attributeCodes.value = ['DayProdEnergy', 'DayOperationHours', 'iWindSpeed']
windBlowerList.value.forEach((item: WindBlowerList) => {
const generateEach = [
{ prop: item.irn + 'DayProdEnergy', label: item.name + '风机发电量', irn: item.irn, name: 'DayProdEnergy', unit: 'kWh' },
{ prop: item.irn + 'DayOperationHours', label: item.name + '风机等效小时', irn: item.irn, name: 'DayOperationHours', unit: 'h' },
{ prop: item.irn + 'iWindSpeed', label: item.name + '风机平均风速', irn: item.irn, name: 'iWindSpeed', unit: 'm/s' },
]
reportTableColumn.value.push(...generateEach)
})
}
}
const tableRowClassName = ({ rowIndex }: any) => {
if (rowIndex % 2 === 1) {
return { background: '#ebf3f9' }
}
return {}
}
const windBlowerList = ref<WindBlowerList[]>([])
//
const timeValue = ref('')
//
const interval = ref('')
//
const queryWindBlower = () => {
queryWindTurbinesPages().then((res) => {
if (res.code == 200) {
windBlowerList.value = res.data.map((item: WindBlowerList) => {
const generateEach = [
{ prop: item.irn + 'iKWhThisDay', label: item.name + '风机发电量', irn: item.irn, name: 'iKWhThisDay', unit: 'kWh' },
{
prop: item.irn + 'iOperationHoursDay',
label: item.name + '风机等效小时',
irn: item.irn,
name: 'iOperationHoursDay',
unit: 'h',
},
{ prop: item.irn + 'iWindSpeed', label: item.name + '风机平均风速', irn: item.irn, name: 'iWindSpeed', unit: 'm/s' },
]
// DayProdEnergy
// DayOperationHours
reportTableColumn.value.push(...generateEach)
return {
irn: item.irn,
name: item.name ?? '-',
modelId: item.modelId,
}
})
}
})
}
const reportLoading = ref(false)
const reportTableColumn = ref([]) as any
const reportTableData = ref([] as any)
const getFirstAndLastDate = (dateString: String) => {
const [year, month] = dateString.split('-').map(Number)
const firstDate = new Date(year, month - 1, 1)
const lastDate = new Date(year, month, 0)
const formattedFirstDate = `${year}-${String(month).padStart(2, '0')}-${String(firstDate.getDate()).padStart(2, '0')}`
const formattedLastDate = `${year}-${String(month).padStart(2, '0')}-${String(lastDate.getDate()).padStart(2, '0')}`
return [formattedFirstDate, formattedLastDate]
}
const queryHistoryData = () => {
if (!timeValue.value.length) return ElMessage.warning('请选择时间!')
reportLoading.value = true
const requestData = {
interval: '',
startTime: undefined,
endTime: undefined,
devices: windBlowerList.value.map((deviceId: any) => {
return {
deviceId: deviceId.irn,
attributes: [...attributeCodes.value],
}
}),
} as any
if (selectedIndex.value == 0) {
requestData.interval = '1h'
requestData.startTime = new Date(timeValue.value + ' 00:00:00').getTime()
requestData.endTime = new Date(timeValue.value + ' 23:59:59').getTime()
} else {
requestData.interval = '1d'
requestData.startTime = new Date(getFirstAndLastDate(timeValue.value)[0] + ' 00:00:00').getTime()
requestData.endTime = new Date(getFirstAndLastDate(timeValue.value)[1] + ' 23:59:59').getTime()
}
reportLoading.value = true
historyReq(requestData).then((res) => {
if (res.code == 200) {
const result = res.data
if (Object.keys(result)?.length) {
const transResults = [] as any
windBlowerList.value.forEach((device: any) => {
const realResult = result[device.irn]
if (realResult) {
let tableData = [] as any
attributeCodes.value.forEach((item: any) => {
if (Object.keys(realResult).includes(item)) {
tableData.push({
name: item,
times: realResult[item].times,
value: realResult[item].values,
deviceId: device.irn,
prop: device.irn + item,
})
}
})
const processedData = new Map()
if (tableData.length) {
tableData.forEach(({ name, times, value, deviceId, prop }: any) => {
times.forEach((time: number, index: number) => {
if (!processedData.has(time)) {
processedData.set(time, { id: shortUuid(), time: timestampToTime(time), deviceId, prop })
}
processedData.get(time)[prop] = value[index]
})
})
}
return transResults.push(Array.from(processedData.values()))
} else {
return transResults.push([])
}
})
const mergedData = transResults.flat().reduce((acc: any, current: any) => {
const found = acc.find((item: any) => item.time === current.time)
if (found) {
Object.keys(current).forEach((key) => {
if (key !== 'id' && key !== 'time' && key !== 'prop') {
found[key] = current[key]
}
})
} else {
acc.push({ ...current })
}
return acc
}, [])
reportTableData.value = mergedData
if (!reportTableData.value.length) {
ElMessage.warning('查询数据为空!')
reportTableData.value = []
}
reportLoading.value = false
} else {
ElMessage.warning('查询数据为空!')
reportTableData.value = []
reportLoading.value = false
}
} else {
reportLoading.value = false
ElMessage.warning('查询失败')
}
})
}
//
const timestampToTime = (timestamp: any) => {
let timestamps = timestamp ? timestamp : null
let date = new Date(timestamps)
let Y = date.getFullYear() + '-'
let M = (date.getMonth() + 1 < 10 ? '0' + (date.getMonth() + 1) : date.getMonth() + 1) + '-'
let D = (date.getDate() < 10 ? '0' + date.getDate() : date.getDate()) + ' '
let h = (date.getHours() < 10 ? '0' + date.getHours() : date.getHours()) + ':'
let m = (date.getMinutes() < 10 ? '0' + date.getMinutes() : date.getMinutes()) + ':'
let s = date.getSeconds() < 10 ? '0' + date.getSeconds() : date.getSeconds()
return Y + M + D + h + m + s
}
onMounted(() => {
queryWindBlower()
})
</script>
<style lang="scss" scoped>
.topForm {
// height: 90px;
.buttons {
margin-left: 10px;
display: flex;
justify-content: flex-end;
flex: 1;
}
.reportButtons {
width: 100%;
margin: 5px;
padding-right: 5px;
display: flex;
justify-content: flex-end;
}
}
.mainReport {
width: 100%;
height: calc(100% - 110px);
}
.commonSelect {
min-width: 250px;
:deep(.el-select__wrapper) {
height: 40px;
}
}
.button {
height: 40px;
}
</style>

View File

@ -312,7 +312,7 @@ const reportTableColumn = ref([
},
])
const reportTableData = ref([])
const reportTableData = ref([]) as any
const idCounter = ref(0)
const queryHistoryData = () => {
if (!windBlowerValue.value) return ElMessage.warning('请选择风机!')
@ -359,10 +359,15 @@ const queryHistoryData = () => {
})
})
}
reportTableData.value = Array.from(processedData.values()) as any
reportTableData.value = Array.from(processedData.values())
if (!reportTableData.value.length) {
ElMessage.warning('查询数据为空!')
reportTableData.value = []
}
reportLoading.value = false
} else {
ElMessage.warning('查询数据为空!')
reportTableData.value = []
reportLoading.value = false
}
} else {

View File

@ -2,7 +2,9 @@
<div class="report">
<el-container class="mainContainer">
<el-tabs v-model="activeIndex" class="demo-tabs" @tab-click="handleClick">
<el-tab-pane label="运行报表" name="1" class="runningReport"> 运行报表 </el-tab-pane>
<el-tab-pane label="运行报表" name="1" class="runningReport">
<RunningReport v-if="activeIndex == '1'"></RunningReport>
</el-tab-pane>
<el-tab-pane label="单机报表" name="2" class="singleReport">
<SingleReport v-if="activeIndex == '2'"></SingleReport>
</el-tab-pane>
@ -18,12 +20,13 @@
import { ref } from 'vue'
import SingleReport from './SingleReport.vue'
import MulipleReport from './MulipleReport.vue'
import RunningReport from './RunningReport.vue'
import { useI18n } from 'vue-i18n'
const { t } = useI18n()
import { useAdminInfo } from '/@/stores/adminInfo'
const adminInfo = useAdminInfo()
const activeIndex = ref('2')
const activeIndex = ref('1')
const handleClick = (val: any) => {
activeIndex.value = val.props.name
}
@ -45,12 +48,7 @@ const handleClick = (val: any) => {
height: calc(100% - 80px);
}
}
.runningReport {
.header {
width: 100%;
height: 60px;
}
}
.runningReport,
.singleReport,
.mltipleReport {
width: 100%;

View File

@ -43,8 +43,6 @@ import { reactive, ref, watch, defineEmits, onMounted, nextTick } from 'vue'
import { ElMessage } from 'element-plus'
import type { ModelAttributeFieldsEnums, GetModelAttributeType } from '/@/views/backend/auth/model/type'
import { getModelAttributeListReq, getRealValueListReq } from '/@/api/backend/deviceModel/request'
import { queryWindTurbinesPages, historyReq } from '/@/api/backend/statAnalysis/request'
import { WindBlowerList, RequestData, Devices } from './type'
import { useI18n } from 'vue-i18n'
const { t } = useI18n()
const props = defineProps({
@ -95,12 +93,6 @@ const tableColumn = [
const tableData = ref<any[]>([])
const emit = defineEmits(['handleSelections'])
const selectedRows = ref([] as any)
// const handleSelectionChange = (selection) => {
// selectedRows.value = selection
// emit('handleSelections', selectedRows.value)
// }
const windBlowerValue = ref('')
const windBlowerList = ref<WindBlowerList[]>([])
const selectWindBlower = (val: any) => {
getCompleteData()
}
@ -246,37 +238,12 @@ const pageSetting = reactive({
const getcurrentPage = () => {
getCompleteData()
}
const queryWindBlower = () => {
return new Promise((resolve) => {
queryWindTurbinesPages().then((res) => {
if (res.code == 200) {
windBlowerList.value = res.data.map((item: WindBlowerList) => {
return {
irn: item.irn,
name: item.name ?? '-',
modelId: item.modelId,
}
})
if (windBlowerList.value.length) {
windBlowerValue.value = windBlowerList.value[0].irn
}
resolve(windBlowerList.value)
}
})
})
}
watch(
() => props.show,
(newVal) => {
if (newVal) {
if (props.isMultiple) {
queryWindBlower().then((res) => {
getCompleteData()
})
} else {
getCompleteData()
}
getCompleteData()
}
},
{
@ -305,9 +272,7 @@ watch(
}
)
onMounted(() => {
// queryWindBlower()
})
onMounted(() => {})
</script>
<style lang="scss" scoped>

View File

@ -61,7 +61,7 @@ const props = defineProps({
},
})
const selectedIndex = ref(null)
const selectedIndex: any = ref(null)
const tableColumn = [
{
type: 'selection',
@ -98,12 +98,12 @@ const tableColumn = [
]
const tableData = ref<any[]>([])
const emit = defineEmits(['handleRadioChange'])
const handleRadioChange = (row) => {
const handleRadioChange = (row: any) => {
selectedIndex.value = row.attributeCode
emit('handleRadioChange', row)
}
const getAttributeList = () => {
const requestData: GetModelAttributeType = {
const requestData: any = {
iotModelId: props.iotModelId,
pageNum: pageSetting.current,
pageSize: pageSetting.pageSize,

View File

@ -3,6 +3,7 @@
<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>
<PowerCurveAnalysis v-if="activeIndex == 0"></PowerCurveAnalysis>
<TrendAnalysis v-if="activeIndex == 1"></TrendAnalysis>
<TrendComparison v-if="activeIndex == 2"></TrendComparison>
</div>
@ -13,12 +14,13 @@ import { useI18n } from 'vue-i18n'
import { useConfig } from '/@/stores/config'
import TrendAnalysis from './trendAnalysis.vue'
import TrendComparison from './trendComparison.vue'
import PowerCurveAnalysis from './powerCurveAnalysis.vue'
const config = useConfig()
const activeIndex = ref(1)
const activeIndex = ref(0)
const { t } = useI18n()
const headerList = [t('statAnalysis.PowerCurveAnalysis'), t('statAnalysis.trendAnalysis'), t('statAnalysis.trendComparison')]
const handleSelect = (index) => {
const handleSelect = (index: number) => {
activeIndex.value = index
}
</script>

View File

@ -0,0 +1,348 @@
<template>
<div class="contain">
<el-header class="headerPart">
<div class="topLeft">
<div class="selectPart">
<span>{{ t('statAnalysis.deviceId') }}</span>
<el-select v-model="statAnalysisDeviceId" :placeholder="'请选择' + t('statAnalysis.deviceId')" class="statAnalysisSelect">
<el-option v-for="v in statAnalysisSelectOptions.deviceId" :key="v.value" :label="v.label" :value="v.value"></el-option>
</el-select>
</div>
</div>
<div class="topRight">
<el-button type="primary" @click="statAnalysisOperate()">{{ t('statAnalysis.search') }}</el-button>
<el-button style="color: #0064aa" @click="statAnalysiImport()">{{ t('statAnalysis.import') }}</el-button>
<el-button style="color: #0064aa" @click="statAnalysisExport()">{{ t('statAnalysis.export') }}</el-button>
</div>
</el-header>
<div class="timeColumns">
<div class="headerPart">
<div class="topLeft">
<div class="selectPart">
<span>{{ t('statAnalysis.time') }}</span>
<el-date-picker
class="datetime-picker"
v-model="statAnalysisTime"
:type="statAnalysisInterval == '1d' ? 'daterange' : 'datetimerange'"
:value-format="statAnalysisInterval == '1d' ? 'YYYY-MM-DD' : 'YYYY-MM-DD HH:mm:ss'"
:teleported="false"
:shortcuts="shortcuts"
/>
</div>
<div class="selectPart">
<span>{{ t('statAnalysis.interval') }}</span>
<el-select v-model="statAnalysisInterval" :placeholder="'请选择' + t('statAnalysis.interval')" class="statAnalysisSelect">
<el-option v-for="v in statAnalysisSelectOptions.interval" :key="v.value" :label="v.label" :value="v.value"></el-option>
</el-select>
</div>
</div>
</div>
</div>
<div ref="chartContainer" style="width: 100%; height: 400px; border: 1px solid rgb(217, 217, 217)"></div>
</div>
</template>
<script setup lang="ts">
import { reactive, ref, onMounted } from 'vue'
import { useI18n } from 'vue-i18n'
import { queryWindTurbinesPages, historyReq } from '/@/api/backend/statAnalysis/request'
import { ElMessage } from 'element-plus'
import * as echarts from 'echarts'
import { index } from '/@/api/backend'
const { t } = useI18n()
const statAnalysisTime = ref('')
const statAnalysisInterval = ref('')
const statAnalysisDeviceId = ref('')
const statAnalysisSelectOptions: any = reactive({
interval: [
{ label: '五分钟', value: '5m' },
{ label: '十五分钟', value: '15m' },
{ label: '一小时', value: '1h' },
{ label: '一天', value: '1d' },
{ label: '原始', value: 'NONE' },
],
deviceId: [],
})
const chartContainer = ref<HTMLElement | null>(null)
const option: any = {
tooltip: {
formatter: function (params: any) {
return '功率:' + params.value[0] + 'KW ' + ' <br/>' + '风速:' + params.value[1] + 'm/s'
},
},
legend: {
icon: 'circle',
itemGap: 20,
itemWidth: 8,
itemHeight: 8,
data: [],
},
xAxis: {
type: 'value',
name: 'KW',
splitLine: {
show: false,
},
},
yAxis: {
name: 'm/s',
splitLine: {
show: false,
},
axisLine: {
show: false,
},
axisTick: {
show: false,
},
},
series: [],
grid: {
left: '3%',
right: '3%',
},
}
const chart: any = ref(null)
onMounted(() => {
if (chartContainer.value) {
chart.value = echarts.init(chartContainer.value)
chart.value.setOption(option)
}
queryWindTurbines()
})
const queryWindTurbines = () => {
queryWindTurbinesPages().then((res) => {
if (res.code == 200) {
statAnalysisSelectOptions.deviceId = res.data.map((item: any) => {
return {
value: item.irn,
label: item.name ?? '-',
iotModelId: item.modelId,
}
})
// console.log(statAnalysisSelectOptions.deviceId)
}
})
}
window.onresize = () => {
chart.value.resize()
}
const shortcuts = [
{
text: '今天',
value: () => {
const start = getFormattedDate(0) + ' 00:00:00'
const end = new Date()
return [start, end]
},
},
{
text: '昨天',
value: () => {
const start = getFormattedDate(-1) + ' 00:00:00'
const end = getFormattedDate(-1) + ' 23:59:59'
return [start, end]
},
},
{
text: '前3天',
value: () => {
const start = getFormattedDate(-3) + ' 00:00:00'
const end = getFormattedDate(-1) + ' 23:59:59'
return [start, end]
},
},
{
text: '本周',
value: () => {
return getDateRange('week')
},
},
{
text: '本月',
value: () => {
return getDateRange('month')
},
},
]
const getFormattedDate = (offset: number) => {
const date = new Date()
date.setDate(date.getDate() + offset)
const year = date.getFullYear()
const month = String(date.getMonth() + 1).padStart(2, '0')
const day = String(date.getDate()).padStart(2, '0')
return `${year}-${month}-${day}`
}
const getDateRange = (type: 'week' | 'month') => {
const today = new Date()
if (type === 'week') {
const dayOfWeek = today.getDay()
const startOfWeek = new Date(today)
startOfWeek.setDate(today.getDate() - dayOfWeek + (dayOfWeek === 0 ? -6 : 1))
startOfWeek.setHours(0, 0, 0, 0)
const endOfWeek = new Date(startOfWeek)
endOfWeek.setDate(startOfWeek.getDate() + 6)
endOfWeek.setHours(23, 59, 59, 999)
return [startOfWeek, endOfWeek]
}
if (type === 'month') {
const startOfMonth = new Date(today.getFullYear(), today.getMonth(), 1)
startOfMonth.setHours(0, 0, 0, 0)
const endOfMonth = new Date(today.getFullYear(), today.getMonth() + 1, 0)
endOfMonth.setHours(23, 59, 59, 999)
return [startOfMonth, endOfMonth]
}
}
const statAnalysisOperate = () => {
option.series = []
chart.value.setOption(option, { notMerge: true })
const requestData = {
devices: [
{
deviceId: statAnalysisDeviceId.value,
attributes: ['iGenPower', 'iWindSpeed'],
},
],
interval: statAnalysisInterval.value,
startTime: new Date(statAnalysisTime.value[0]).getTime(),
endTime: new Date(statAnalysisTime.value[1]).getTime(),
}
historyDataReq(requestData)
}
const historyDataReq = (data: any) => {
historyReq(data).then((res) => {
if (res.code == 200) {
const resData = res.data[statAnalysisDeviceId.value]
console.log(resData)
if (resData) {
const iGenPower = resData['iGenPower']['values']
const iWindSpeed = resData['iWindSpeed']['values']
const seriesData = iGenPower.map((item: any, index: number) => {
return [item, iWindSpeed[index]]
})
const series = {
type: 'scatter',
data: seriesData,
}
option.series.push(series)
console.log('🚀 ~ historyReq ~ option:', option)
chart.value.setOption(option)
}
} else {
ElMessage.warning('查询失败')
}
})
}
const statAnalysisExport = () => {}
const statAnalysiImport = () => {}
</script>
<style scoped lang="scss">
.statAnalysis {
height: 100%;
.headerPart {
padding: 20px;
display: flex;
justify-content: space-between;
align-items: center;
.topLeft {
display: flex;
.icon {
width: 40px;
height: 40px;
padding: 10px 0;
}
.selectPart {
display: flex;
justify-content: space-between;
align-items: center;
height: 40px;
margin-right: 20px;
span {
margin-right: 10px;
}
.customName {
width: fit-content;
margin-right: 10px;
}
.statAnalysisSelect {
width: 200px;
:deep(.el-select__wrapper) {
height: 40px;
}
:deep(.el-input__inner) {
height: 38px;
}
}
.max,
.min,
.average {
border-radius: 6px;
height: 40px;
width: 72px;
text-align: center;
line-height: 40px;
}
.max {
background: rgba(254, 55, 49, 0.2);
border: 1px solid #fe3731;
color: #fe3731;
}
.min {
background: rgba(0, 160, 150, 0.2);
border: 1px solid #00a096;
color: #00a096;
}
.average {
background: rgba(0, 100, 170, 0.2);
border: 1px solid #0064aa;
color: #0064aa;
}
}
.dialog-footer button:first-child {
margin-right: 10px;
}
}
.topRight {
display: flex;
justify-content: space-between;
.el-button {
width: 100px;
height: 40px;
}
}
}
.timeColumns {
max-height: 120px;
overflow: hidden;
.headerPart {
padding: 10px 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%;
height: 300px;
}
}
</style>

View File

@ -24,12 +24,7 @@
</div>
<div class="selectPart">
<span>{{ t('statAnalysis.interval') }}</span>
<el-select
v-model="statAnalysisSelect.interval"
@change="selectstatAnalysis('interval')"
:placeholder="'请选择' + t('statAnalysis.interval')"
class="statAnalysisSelect"
>
<el-select v-model="statAnalysisSelect.interval" :placeholder="'请选择' + t('statAnalysis.interval')" class="statAnalysisSelect">
<el-option v-for="v in statAnalysisSelectOptions.interval" :key="v.value" :label="v.label" :value="v.value"></el-option>
</el-select>
</div>
@ -83,7 +78,7 @@
<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"></div>
<div ref="chartContainer" style="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">
@ -103,10 +98,10 @@
</div>
</template>
<script setup lang="ts">
import { onUnmounted, reactive, ref, watch, nextTick, onMounted, computed } from 'vue'
import { reactive, ref, onMounted } from 'vue'
import { useI18n } from 'vue-i18n'
import { queryWindTurbinesPages, historyReq } from '/@/api/backend/statAnalysis/request'
import { ElMessage, ElMenu } from 'element-plus'
import { ElMessage } from 'element-plus'
import { DArrowRight, Plus, Delete } from '@element-plus/icons-vue'
import MeasurementPage from './analysisAttributes.vue'
import * as echarts from 'echarts'
@ -118,23 +113,22 @@ const statAnalysisSelect = reactive({
interval: '',
time: '',
})
const times = reactive([{ time: '' }])
const addTime = (index) => {
const times: any = reactive([{ time: '' }])
const addTime = (index: any) => {
times.push({ time: '' })
customName.push(String(index + 2))
customName.push(statAnalysisSelect.attributes + String(index + 2))
}
const switchTime = (index) => {
const switchTime = (index: number) => {
times.splice(index, 1)
customName.splice(index, 1)
calculate.splice(index, 1)
xData.splice(index, 1)
}
const timechange = (value) => {
const timechange = (value: any) => {
const count = getTimeIntervals(times[0][0], times[0][1])
const count1 = getTimeIntervals(times[value][0], times[value][1])
if (count !== count1) {
times[value] = { time: '' }
value = ElMessage.warning('查询时间点错误,请重新输入')
ElMessage.warning('查询时间点错误,请重新输入')
}
}
const isExpand = ref(false)
@ -145,7 +139,7 @@ const handleClick = () => {
const iotModelId = ref('')
const irn = ref('')
const attributesChange = () => {
const row = statAnalysisSelectOptions.deviceId.filter((item) => {
const row: any = statAnalysisSelectOptions.deviceId.filter((item: any) => {
return item.value == statAnalysisSelect.deviceId
})
if (row.length) {
@ -163,11 +157,11 @@ const deviceIdChange = () => {
}
const showMeasure = ref(false)
const selectedAttrRow = ref({
const selectedAttrRow: any = ref({
attributeCode: '',
attributeName: '',
})
const handleRadioChange = (value) => {
const handleRadioChange = (value: any) => {
const { attributeCode, attributeName } = { ...value }
selectedAttrRow.attributeCode = attributeCode
selectedAttrRow.attributeName = attributeName
@ -176,11 +170,14 @@ const selectstatAnalysisAttributes = () => {
statAnalysisSelect.attributes = selectedAttrRow.attributeName
statAnalysisSelect.attributeCode = selectedAttrRow.attributeCode
showMeasure.value = false
customName.forEach((item: any, index: number, arr: any) => {
arr[index] = statAnalysisSelect.attributes + String(index + 1)
})
}
const chartContainer = ref<HTMLElement | null>(null)
const option = {
const option: any = {
tooltip: {},
legend: {
icon: 'circle',
@ -190,7 +187,6 @@ const option = {
data: [],
},
xAxis: {
type: 'category',
data: [],
},
yAxis: {
@ -203,7 +199,7 @@ const option = {
},
}
const statAnalysisSelectOptions = reactive({
const statAnalysisSelectOptions: any = reactive({
interval: [
{ label: '五分钟', value: '5m' },
{ label: '十五分钟', value: '15m' },
@ -213,8 +209,8 @@ const statAnalysisSelectOptions = reactive({
],
deviceId: [],
})
const customName = reactive(['1'])
const chart = ref(null)
const customName = reactive([statAnalysisSelect.attributes + '1'])
const chart: any = ref(null)
onMounted(() => {
if (chartContainer.value) {
@ -226,7 +222,7 @@ onMounted(() => {
const queryWindTurbines = () => {
queryWindTurbinesPages().then((res) => {
if (res.code == 200) {
statAnalysisSelectOptions.deviceId = res.data.map((item) => {
statAnalysisSelectOptions.deviceId = res.data.map((item: any) => {
return {
value: item.irn,
label: item.name ?? '-',
@ -241,11 +237,6 @@ window.onresize = () => {
chart.value.resize()
}
const handleSelect = (index) => {
activeIndex.value = index
}
const selectstatAnalysis = () => {}
const shortcuts = [
{
text: '今天',
@ -284,7 +275,7 @@ const shortcuts = [
},
},
]
const getFormattedDate = (offset) => {
const getFormattedDate = (offset: number) => {
const date = new Date()
date.setDate(date.getDate() + offset)
@ -313,9 +304,9 @@ const getDateRange = (type: 'week' | 'month') => {
return [startOfMonth, endOfMonth]
}
}
const getTimeIntervals = (startTimestamp, endTimestamp) => {
const startDate = new Date(startTimestamp)
const endDate = new Date(endTimestamp)
const getTimeIntervals = (startTimestamp: number, endTimestamp: number) => {
const startDate: any = new Date(startTimestamp)
const endDate: any = new Date(endTimestamp)
let count = 0
switch (statAnalysisSelect.interval) {
@ -332,7 +323,7 @@ const getTimeIntervals = (startTimestamp, endTimestamp) => {
count = Math.floor((endDate - startDate) / (1 * 60 * 60 * 1000))
break
case '1d':
count = c((endDate - startDate) / (1 * 24 * 60 * 60 * 1000))
count = Math.floor((endDate - startDate) / (1 * 24 * 60 * 60 * 1000))
break
// default:
// throw new Error('Invalid interval')
@ -340,14 +331,14 @@ const getTimeIntervals = (startTimestamp, endTimestamp) => {
return count
}
const calculate = reactive([{ max: '', min: '', average: '' }])
var xDatas = []
const calculate: any = reactive([{ max: '', min: '', average: '' }])
var xDatas: any = []
const statAnalysisOperate = () => {
option.series = []
option.legend.data = []
xDatas = []
chart.value.setOption(option, { notMerge: true })
times.forEach((time, index) => {
times.forEach((time: any, index: number) => {
if (time[0] && time[1]) {
const requestData = {
devices: [
@ -365,7 +356,7 @@ const statAnalysisOperate = () => {
})
}
const historyDataReq = (data, index) => {
const historyDataReq = (data: any, index: number) => {
historyReq(data).then((res) => {
if (res.code == 200) {
const deviceId = statAnalysisSelect.deviceId
@ -374,19 +365,15 @@ const historyDataReq = (data, index) => {
if (resData) {
const xData = resData['times']
const yData = resData['values']
calculate[index] = {
max: Math.floor(Math.max(...yData)),
min: Math.floor(Math.min(...yData)),
average: Math.floor(yData.reduce((a, b) => a + b, 0) / yData.length),
}
calculate[index] = calculateStats(yData)
xDatas.push({
series: String(customName[index]),
data: xData,
})
option.tooltip = {
show: true,
formatter: function (params) {
const matchData = xDatas.filter((x) => x.series == params.seriesName)
formatter: function (params: any) {
const matchData = xDatas.filter((x: any) => x.series == params.seriesName)
const x = timestampToTime(matchData[0]['data'][params.dataIndex])
return `${params.marker} ${params.seriesName} <br/> ${x} <b>${params.data}</b>`
},
@ -413,7 +400,19 @@ const historyDataReq = (data, index) => {
const statAnalysisExport = () => {}
const statAnalysiImport = () => {}
const timestampToTime = (timestamp) => {
function calculateStats(numbers: any) {
const max = Math.max(...numbers)
const min = Math.min(...numbers)
const sum = numbers.reduce((acc: number, current: number) => acc + current, 0)
const average = sum / numbers.length
return {
max: max.toFixed(2),
min: min.toFixed(2),
average: average.toFixed(2),
}
}
const timestampToTime = (timestamp: any) => {
timestamp = timestamp ? timestamp : null
let date = new Date(timestamp)
let Y = date.getFullYear() + '-'
@ -482,7 +481,7 @@ const timestampToTime = (timestamp) => {
color: #0064aa;
}
.customName {
width: 80px;
width: fit-content;
margin-right: 10px;
}
}

View File

@ -84,7 +84,7 @@
<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"></div>
<div ref="chartContainer" style="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">
@ -116,8 +116,8 @@ const statAnalysisTime = ref('')
const statAnalysisInterval = ref('')
const statAnalysisDeviceId = reactive([''])
const statAnalysisAttributes = reactive([''])
const statAnalysisAttributeCode = reactive([])
const statAnalysisSelectOptions = reactive({
const statAnalysisAttributeCode: any = reactive([])
const statAnalysisSelectOptions: any = reactive({
interval: [
{ label: '五分钟', value: '5m' },
{ label: '十五分钟', value: '15m' },
@ -128,13 +128,12 @@ const statAnalysisSelectOptions = reactive({
deviceId: [],
})
const openModelIndex = ref(0)
const selectediotModelId = ref('')
const addDevice = (index) => {
const addDevice = (index: number) => {
statAnalysisDeviceId.push('')
statAnalysisAttributes.push('')
customName.push(String(index + 2))
}
const switchDevice = (index) => {
const switchDevice = (index: number) => {
statAnalysisDeviceId.splice(index, 1)
statAnalysisAttributes.splice(index, 1)
statAnalysisAttributeCode.splice(index, 1)
@ -148,8 +147,8 @@ const handleClick = () => {
}
const iotModelId = ref('')
const irn = ref('')
const selectAtteibutes = (index) => {
const row = statAnalysisSelectOptions.deviceId.filter((item) => {
const selectAtteibutes = (index: number) => {
const row = statAnalysisSelectOptions.deviceId.filter((item: any) => {
return item.value == statAnalysisDeviceId[index]
})
if (row.length) {
@ -162,7 +161,7 @@ const selectAtteibutes = (index) => {
}
}
const deviceIdChange = (index) => {
const deviceIdChange = (index: number) => {
statAnalysisAttributeCode[index] = ''
statAnalysisAttributes[index] = ''
}
@ -172,7 +171,7 @@ const selectedAttrRow = reactive({
attributeCode: '',
attributeName: '',
})
const handleRadioChange = (value) => {
const handleRadioChange = (value: any) => {
const { attributeCode, attributeName } = { ...value }
selectedAttrRow.attributeCode = attributeCode
selectedAttrRow.attributeName = attributeName
@ -181,11 +180,14 @@ const selectstatAnalysisAttributes = () => {
statAnalysisAttributes[openModelIndex.value] = selectedAttrRow.attributeName
statAnalysisAttributeCode[openModelIndex.value] = selectedAttrRow.attributeCode
showMeasure.value = false
customName.forEach((item: any, index: number, arr: any) => {
arr[openModelIndex.value] = statAnalysisAttributes[openModelIndex.value] + String(index + 1)
})
}
const chartContainer = ref<HTMLElement | null>(null)
const option = {
const option: any = {
tooltip: {},
legend: {
icon: 'circle',
@ -195,7 +197,6 @@ const option = {
data: [],
},
xAxis: {
type: 'category',
data: [],
axisLabel: {
rotate: 15,
@ -212,7 +213,7 @@ const option = {
}
const customName = reactive(['1'])
const chart = ref(null)
const chart: any = ref(null)
onMounted(() => {
if (chartContainer.value) {
chart.value = echarts.init(chartContainer.value)
@ -223,7 +224,7 @@ onMounted(() => {
const queryWindTurbines = () => {
queryWindTurbinesPages().then((res) => {
if (res.code == 200) {
statAnalysisSelectOptions.deviceId = res.data.map((item) => {
statAnalysisSelectOptions.deviceId = res.data.map((item: any) => {
return {
value: item.irn,
label: item.name ?? '-',
@ -238,11 +239,6 @@ window.onresize = () => {
chart.value.resize()
}
const handleSelect = (index) => {
activeIndex.value = index
}
const selectstatAnalysis = () => {}
const shortcuts = [
{
text: '今天',
@ -281,7 +277,7 @@ const shortcuts = [
},
},
]
const getFormattedDate = (offset) => {
const getFormattedDate = (offset: number) => {
const date = new Date()
date.setDate(date.getDate() + offset)
@ -315,8 +311,8 @@ const statAnalysisOperate = () => {
option.series = []
chart.value.setOption(option, { notMerge: true })
const attributes = statAnalysisAttributeCode
const devices = statAnalysisDeviceId.reduce((deviceId, curr, index) => {
const existing = deviceId.find((item) => item.deviceId === curr)
const devices = statAnalysisDeviceId.reduce((deviceId: any, curr, index) => {
const existing: any = deviceId.find((item: any) => item.deviceId === curr)
if (existing) {
existing.attributes.push(statAnalysisAttributeCode[index])
} else {
@ -330,10 +326,10 @@ const statAnalysisOperate = () => {
startTime: new Date(statAnalysisTime.value[0]).getTime(),
endTime: new Date(statAnalysisTime.value[1]).getTime(),
}
historyDataReq(requestData, devices)
historyDataReq(requestData)
}
const calculate = reactive([{ max: '', min: '', average: '' }])
const historyDataReq = (data, devices) => {
const calculate: any = reactive([{ max: '', min: '', average: '' }])
const historyDataReq = (data: any) => {
historyReq(data).then((res) => {
if (res.code == 200) {
const resData = res.data
@ -353,13 +349,9 @@ const historyDataReq = (data, devices) => {
type: 'line',
data: yData,
}
calculate[dataIndex] = {
max: Math.floor(Math.max(...yData)),
min: Math.floor(Math.min(...yData)),
average: Math.floor(yData.reduce((a, b) => a + b, 0) / yData.length),
}
calculate[dataIndex] = calculateStats(yData)
option.legend.data.push(customName[dataIndex])
option.xAxis.data = xData.map((item) => timestampToTime(item))
option.xAxis.data = xData.map((item: any) => timestampToTime(item))
option.series.push(seriesData)
chart.value.setOption(option)
})
@ -371,17 +363,28 @@ const historyDataReq = (data, devices) => {
})
}
const findAllOccurrences = (arr, target) => {
return arr.map((value, index) => (value === target ? index : -1)).filter((index) => index !== -1)
const findAllOccurrences = (arr: any, target: any) => {
return arr.map((value: any, index: number) => (value === target ? index : -1)).filter((index: number) => index !== -1)
}
const getCommonElements = (arr1, arr2) => {
return arr1.filter((item) => arr2.some((x) => x === item))
const getCommonElements = (arr1: any, arr2: any) => {
return arr1.filter((item: any) => arr2.some((x: any) => x === item))
}
const statAnalysisExport = () => {}
const statAnalysiImport = () => {}
function calculateStats(numbers: any) {
const max = Math.max(...numbers)
const min = Math.min(...numbers)
const sum = numbers.reduce((acc: number, current: number) => acc + current, 0)
const average = sum / numbers.length
const timestampToTime = (timestamp) => {
return {
max: max.toFixed(2),
min: min.toFixed(2),
average: average.toFixed(2),
}
}
const timestampToTime = (timestamp: any) => {
timestamp = timestamp ? timestamp : null
let date = new Date(timestamp)
let Y = date.getFullYear() + '-'
@ -417,7 +420,7 @@ const timestampToTime = (timestamp) => {
margin-right: 10px;
}
.customName {
width: 80px;
width: fit-content;
margin-right: 10px;
}
.statAnalysisSelect {

View File

@ -57,23 +57,26 @@ const viteConfig = ({ mode }: ConfigEnv): UserConfig => {
...createProxy(VITE_APP_PROXY),
},
},
esbuild: {
drop: ['console', 'debugger'],
},
build: {
cssCodeSplit: false,
sourcemap: false,
outDir: VITE_OUT_DIR,
emptyOutDir: true,
chunkSizeWarningLimit: 1500,
terserOptions: {
compress: {
keep_infinity: true,
// Used to delete console in production environment
drop_console: true,
drop_debugger: true,
},
output: {
comments: true, // 去掉注释内容
},
},
// terserOptions: {
// compress: {
// keep_infinity: true,
// // Used to delete console in production environment
// drop_console: true,
// drop_debugger: true,
// },
// output: {
// comments: true, // 去掉注释内容
// },
// },
rollupOptions: {
output: {
manualChunks: {