20260408:1、数据上报物模型公式计算bug修复;2、部门/公司支持展示自有logo、公司名称;3、只要设备存在上报imei等数据即刻保存
This commit is contained in:
parent
4048df2cca
commit
fd04f3c74c
@ -118,6 +118,11 @@ public class SysDept extends BaseEntity {
|
||||
* 机构类型
|
||||
*/
|
||||
private Integer deptType;
|
||||
|
||||
/**
|
||||
* 机构Logo URL
|
||||
*/
|
||||
private String logo;
|
||||
/**
|
||||
* 是否显示自己
|
||||
*/
|
||||
@ -151,6 +156,14 @@ public class SysDept extends BaseEntity {
|
||||
this.deptType = deptType;
|
||||
}
|
||||
|
||||
public String getLogo() {
|
||||
return logo;
|
||||
}
|
||||
|
||||
public void setLogo(String logo) {
|
||||
this.logo = logo;
|
||||
}
|
||||
|
||||
public String getUserName() {
|
||||
return userName;
|
||||
}
|
||||
|
||||
@ -15,7 +15,7 @@ import java.util.regex.Pattern;
|
||||
/**
|
||||
* 字符串公式计算工具
|
||||
*/
|
||||
public class CaculateUtils {
|
||||
public class CalculateUtils {
|
||||
|
||||
/**
|
||||
* /*
|
||||
@ -26,14 +26,15 @@ public class CaculateUtils {
|
||||
/**
|
||||
* 公式计算 字符串
|
||||
*
|
||||
* @param exeStr
|
||||
* @param exeStr 公式:例:{AngY}-(0.0047*({T}-20))
|
||||
* @param replaceMap K:identifier,V:值
|
||||
*/
|
||||
public static BigDecimal execute(String exeStr, Map<String, String> replaceMap) {
|
||||
// 替换掉占位符
|
||||
exeStr = caculateReplace(exeStr, replaceMap);
|
||||
exeStr = calculateReplace(exeStr, replaceMap);
|
||||
exeStr = exeStr.replaceAll("\\s*", "");
|
||||
List<String> suffixList = suffixHandle(exeStr);
|
||||
return caculateAnalyse(suffixList);
|
||||
return calculateAnalyse(suffixList);
|
||||
|
||||
}
|
||||
|
||||
@ -41,26 +42,25 @@ public class CaculateUtils {
|
||||
* 公式计算 后序list
|
||||
*
|
||||
* @param suffixList
|
||||
* @return
|
||||
* @return 计算结果
|
||||
*/
|
||||
public static BigDecimal caculateAnalyse(List<String> suffixList) {
|
||||
public static BigDecimal calculateAnalyse(List<String> suffixList) {
|
||||
|
||||
BigDecimal a = BigDecimal.ZERO;
|
||||
BigDecimal b = BigDecimal.ZERO;
|
||||
BigDecimal b;
|
||||
// 构建一个操作数栈 每当获得操作符号时取出最上面两个数进行计算。
|
||||
Deque<BigDecimal> caculateStack = new ArrayDeque<>();
|
||||
Deque<BigDecimal> calculateStack = new ArrayDeque<>();
|
||||
if (suffixList.size() > 1) {
|
||||
|
||||
for (int i = 0; i < suffixList.size(); i++) {
|
||||
String temp = suffixList.get(i);
|
||||
for (String temp : suffixList) {
|
||||
if (symbol.contains(temp)) {
|
||||
b = caculateStack.pop();
|
||||
a = caculateStack.pop();
|
||||
a = caculate(a, b, temp.toCharArray()[0]);
|
||||
caculateStack.push(a);
|
||||
b = calculateStack.pop();
|
||||
a = calculateStack.pop();
|
||||
a = calculate(a, b, temp.toCharArray()[0]);
|
||||
calculateStack.push(a);
|
||||
} else {
|
||||
if (isNumber(suffixList.get(i))) {
|
||||
caculateStack.push(new BigDecimal(suffixList.get(i)));
|
||||
if (isNumber(temp)) {
|
||||
calculateStack.push(new BigDecimal(temp));
|
||||
} else {
|
||||
throw new RuntimeException("公式异常!");
|
||||
}
|
||||
@ -85,7 +85,7 @@ public class CaculateUtils {
|
||||
* @param symbol
|
||||
* @return
|
||||
*/
|
||||
public static BigDecimal caculate(BigDecimal a, BigDecimal b, char symbol) {
|
||||
public static BigDecimal calculate(BigDecimal a, BigDecimal b, char symbol) {
|
||||
switch (symbol) {
|
||||
case '+': {
|
||||
return a.add(b).stripTrailingZeros();
|
||||
@ -158,9 +158,9 @@ public class CaculateUtils {
|
||||
char con = stack.peek();
|
||||
if (con == '(') {
|
||||
stack.pop();
|
||||
} else {
|
||||
res.add(String.valueOf(stack.pop()));
|
||||
break; // 只匹配当前这一对括号
|
||||
}
|
||||
res.add(String.valueOf(stack.pop()));
|
||||
}
|
||||
continue;
|
||||
}
|
||||
@ -208,18 +208,18 @@ public class CaculateUtils {
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断是否为数 字符串
|
||||
* 判断是否为数字
|
||||
*
|
||||
* @param str
|
||||
* @return
|
||||
* @param str 字符串
|
||||
* @return true:数字;false:非数字
|
||||
*/
|
||||
public static boolean isNumber(String str) {
|
||||
Pattern pattern = Pattern.compile("[-+]?[0-9]+\\.{0,1}[0-9]*");
|
||||
Pattern pattern = Pattern.compile("[-+]?[0-9]+\\.?[0-9]*");
|
||||
Matcher isNum = pattern.matcher(str);
|
||||
return isNum.matches();
|
||||
}
|
||||
|
||||
public static String caculateReplace(String str, Map<String, String> map) {
|
||||
public static String calculateReplace(String str, Map<String, String> map) {
|
||||
for (Map.Entry<String, String> entry : map.entrySet()) {
|
||||
// 对于包含特殊字符的占位符(如 {identifier}),需要转义正则表达式特殊字符
|
||||
String key = entry.getKey();
|
||||
@ -343,26 +343,26 @@ public class CaculateUtils {
|
||||
ModbusDataType type = ModbusDataType.convert(dataType);
|
||||
switch (type) {
|
||||
case U_SHORT:
|
||||
value = CaculateUtils.toUnSign16(val);
|
||||
value = CalculateUtils.toUnSign16(val);
|
||||
break;
|
||||
case SHORT:
|
||||
case LONG_ABCD:
|
||||
value = val + "";
|
||||
break;
|
||||
case LONG_CDAB:
|
||||
value = CaculateUtils.toSign32_CDAB(val);
|
||||
value = CalculateUtils.toSign32_CDAB(val);
|
||||
break;
|
||||
case U_LONG_ABCD:
|
||||
value = CaculateUtils.toUnSign32_ABCD(val);
|
||||
value = CalculateUtils.toUnSign32_ABCD(val);
|
||||
break;
|
||||
case U_LONG_CDAB:
|
||||
value = CaculateUtils.toUnSign32_CDAB(val);
|
||||
value = CalculateUtils.toUnSign32_CDAB(val);
|
||||
break;
|
||||
case FLOAT_ABCD:
|
||||
value = CaculateUtils.toFloat32_ABCD(bytes) + "";
|
||||
value = CalculateUtils.toFloat32_ABCD(bytes) + "";
|
||||
break;
|
||||
case FLOAT_CDAB: // todo:待改枚举页面枚举
|
||||
value = CaculateUtils.toFloat32_DCBA(bytes) + "";
|
||||
value = CalculateUtils.toFloat32_DCBA(bytes) + "";
|
||||
break;
|
||||
}
|
||||
}
|
||||
@ -401,16 +401,17 @@ public class CaculateUtils {
|
||||
// replaceMap.put("B", "2.5");
|
||||
// replaceMap.put("C", "3.5");
|
||||
// replaceMap.put("D", "4.5");
|
||||
replaceMap.put("AngZ", "-10");
|
||||
// replaceMap.put("%s", "1");
|
||||
replaceMap.put("{AngY}", "-0.10590162");
|
||||
replaceMap.put("{T}", "21.656084");
|
||||
// replaceMap.put("%s1", "5");
|
||||
BigDecimal execute = execute("AngZ+88.59", replaceMap);
|
||||
String formula = "{AngY}-(0.0047*({T}-20))";
|
||||
BigDecimal execute = execute(formula, replaceMap);
|
||||
System.out.println(execute);
|
||||
|
||||
Map<String, String> map = new HashMap<>();
|
||||
/* Map<String, String> map = new HashMap<>();
|
||||
map.put("%s", "10");
|
||||
String caculate = caculateReplace("%s*2", map);
|
||||
System.out.println(caculate);
|
||||
String calculate = calculateReplace("%s*2", map);
|
||||
System.out.println(calculate);
|
||||
System.out.println(execute("%s%3.00", map));
|
||||
|
||||
String s4 = toUnSign16(-1);
|
||||
@ -451,7 +452,7 @@ public class CaculateUtils {
|
||||
int val1 = -6553510;
|
||||
byte[] bytes2 = intToBytes2(val1);
|
||||
int i1 = bytesToInt2(bytes2);
|
||||
System.out.println(i1);
|
||||
System.out.println(i1);*/
|
||||
}
|
||||
|
||||
}
|
||||
@ -1,7 +1,7 @@
|
||||
package com.fastbee.common.utils.gateway;
|
||||
|
||||
|
||||
import com.fastbee.common.utils.CaculateUtils;
|
||||
import com.fastbee.common.utils.CalculateUtils;
|
||||
import com.fastbee.common.utils.gateway.protocol.ByteUtils;
|
||||
import org.apache.commons.lang3.ArrayUtils;
|
||||
|
||||
@ -141,7 +141,7 @@ public class CRC16Utils {
|
||||
byte[] byte8 = ByteUtils.hexToByte(crc8);
|
||||
int b = CRC8(byte8);
|
||||
System.out.println(b);
|
||||
float v = CaculateUtils.toFloat32_DCBA(bytes);
|
||||
float v = CalculateUtils.toFloat32_DCBA(bytes);
|
||||
System.out.println(v);
|
||||
}
|
||||
|
||||
|
||||
@ -314,25 +314,25 @@ public class DeviceDebugLogAspect {
|
||||
}
|
||||
|
||||
// 更新IMEI、ICCID、longitude、latitude
|
||||
if (StringUtils.isEmpty(device.getImei()) || StringUtils.isEmpty(device.getIccid())) {
|
||||
Map<String, String> atCommandMap = ATCommandParserUtils.parseATResponseFromHex(context.sourceData);
|
||||
if (!atCommandMap.isEmpty()) {
|
||||
Device updateDevice = new Device();
|
||||
if (StringUtils.isEmpty(device.getImei())) {
|
||||
updateDevice.setImei(atCommandMap.get("IMEI"));
|
||||
String imei = atCommandMap.get("IMEI");
|
||||
if (StringUtils.isNotEmpty(imei)) {
|
||||
updateDevice.setImei(imei);
|
||||
}
|
||||
if (StringUtils.isEmpty(device.getIccid())) {
|
||||
updateDevice.setIccid(atCommandMap.get("ICCID"));
|
||||
String iccid = atCommandMap.get("ICCID");
|
||||
if (StringUtils.isNotEmpty(iccid)) {
|
||||
updateDevice.setIccid(iccid);
|
||||
}
|
||||
if (device.getLongitude() == null && device.getLatitude() == null) {
|
||||
String lbsValue = atCommandMap.get("LBS");
|
||||
if (lbsValue != null) {
|
||||
if (StringUtils.isNotEmpty(lbsValue)) {
|
||||
String[] lbsArray = lbsValue.split(",");
|
||||
if (lbsArray.length == 2) {
|
||||
updateDevice.setLongitude(new BigDecimal(lbsArray[1]));
|
||||
updateDevice.setLatitude(new BigDecimal(lbsArray[0]));
|
||||
}
|
||||
}
|
||||
}
|
||||
updateDevice.setDeviceId(device.getDeviceId());
|
||||
updateDevice.setUpdateBy("DTU");
|
||||
updateDevice.setUpdateTime(DateUtils.getNowDate());
|
||||
|
||||
@ -8,7 +8,7 @@ import com.fastbee.common.core.notify.config.WeChatConfigParams;
|
||||
import com.fastbee.common.core.notify.msg.WechatMsgParams;
|
||||
import com.fastbee.common.core.redis.RedisKeyBuilder;
|
||||
import com.fastbee.common.enums.DataLevelEnum;
|
||||
import com.fastbee.common.utils.CaculateUtils;
|
||||
import com.fastbee.common.utils.CalculateUtils;
|
||||
import com.fastbee.common.utils.StringUtils;
|
||||
import com.fastbee.common.utils.wechat.WechatUtils;
|
||||
import com.fastbee.common.wechat.WeChatAppResult;
|
||||
@ -317,7 +317,7 @@ public class ThingsModelPushHandler {
|
||||
replaceMap.put("{" + refKey + "}", val);
|
||||
}
|
||||
try {
|
||||
Object computed = CaculateUtils.execute(model.getFormula(), replaceMap);
|
||||
Object computed = CalculateUtils.execute(model.getFormula(), replaceMap);
|
||||
String computedStr = computed != null ? computed.toString() : null;
|
||||
return new ComputedValue(computedStr, maxTs);
|
||||
} catch (Exception e) {
|
||||
|
||||
@ -13,7 +13,7 @@ import com.fastbee.common.core.protocol.modbus.ModbusCode;
|
||||
import com.fastbee.common.core.redis.RedisCache;
|
||||
import com.fastbee.common.core.thingsModel.ThingsModelSimpleItem;
|
||||
import com.fastbee.common.enums.FunctionReplyStatus;
|
||||
import com.fastbee.common.utils.CaculateUtils;
|
||||
import com.fastbee.common.utils.CalculateUtils;
|
||||
import com.fastbee.common.utils.DateUtils;
|
||||
import com.fastbee.common.utils.StringUtils;
|
||||
import com.fastbee.common.utils.gateway.CRC16Utils;
|
||||
@ -229,7 +229,7 @@ public class ModbusProtocol implements IProtocol {
|
||||
//普通取值,应该只有一个数据,将identity与address替换
|
||||
ThingsModelSimpleItem simpleItem = new ThingsModelSimpleItem();
|
||||
simpleItem.setId(config.getIdentifier());
|
||||
simpleItem.setValue(CaculateUtils.handleToUnSign16(data[0] + "", config.getDataType()));
|
||||
simpleItem.setValue(CalculateUtils.handleToUnSign16(data[0] + "", config.getDataType()));
|
||||
simpleItem.setTs(DateUtils.getNowDate());
|
||||
simpleItemList.add(simpleItem);
|
||||
}
|
||||
@ -301,11 +301,11 @@ public class ModbusProtocol implements IProtocol {
|
||||
|
||||
if (quantity == 1) {
|
||||
// 单个寄存器
|
||||
String value = CaculateUtils.handleToUnSign16(Long.parseLong(handleHex, 16) + "", modbusConfig.getDataType());
|
||||
String value = CalculateUtils.handleToUnSign16(Long.parseLong(handleHex, 16) + "", modbusConfig.getDataType());
|
||||
simpleItem.setValue(value);
|
||||
} else if (quantity == 2) {
|
||||
// 浮点数,2个寄存器
|
||||
String value = CaculateUtils.parseValue(modbusConfig.getDataType(), handleHex);
|
||||
String value = CalculateUtils.parseValue(modbusConfig.getDataType(), handleHex);
|
||||
simpleItem.setValue(value);
|
||||
} else {
|
||||
log.warn("不支持的quantity值:{},配置地址:{}", quantity, configAddress);
|
||||
|
||||
@ -2,7 +2,7 @@ package com.fastbee.modbus.test;
|
||||
|
||||
import com.fastbee.common.core.mq.message.DeviceData;
|
||||
import com.fastbee.common.core.protocol.modbus.ModbusCode;
|
||||
import com.fastbee.common.utils.CaculateUtils;
|
||||
import com.fastbee.common.utils.CalculateUtils;
|
||||
import com.fastbee.common.utils.gateway.protocol.ByteUtils;
|
||||
import com.fastbee.modbus.codec.ModbusDecoder;
|
||||
import com.fastbee.modbus.codec.ModbusEncoder;
|
||||
@ -78,7 +78,7 @@ public class ModbusTest {
|
||||
System.out.println(s1);
|
||||
String hexToBytes = "41EE8000";
|
||||
byte[] bytes = ByteBufUtil.decodeHexDump(hexToBytes);
|
||||
String v = CaculateUtils.toFloat(bytes);
|
||||
String v = CalculateUtils.toFloat(bytes);
|
||||
System.out.println(v);
|
||||
|
||||
System.out.println(ByteUtils.intToHex(-32768));
|
||||
|
||||
@ -15,7 +15,7 @@ import com.fastbee.common.core.redis.RedisKeyBuilder;
|
||||
import com.fastbee.common.core.thingsModel.ThingsModelSimpleItem;
|
||||
import com.fastbee.common.enums.FunctionReplyStatus;
|
||||
import com.fastbee.common.exception.ServiceException;
|
||||
import com.fastbee.common.utils.CaculateUtils;
|
||||
import com.fastbee.common.utils.CalculateUtils;
|
||||
import com.fastbee.common.utils.DateUtils;
|
||||
import com.fastbee.common.utils.StringUtils;
|
||||
import com.fastbee.common.utils.modbus.BitUtils;
|
||||
@ -265,7 +265,7 @@ public class ModbusTcpProtocol implements IProtocol {
|
||||
//普通取值,应该只有一个数据,将identity与address替换
|
||||
ThingsModelSimpleItem simpleItem = new ThingsModelSimpleItem();
|
||||
simpleItem.setId(config.getIdentifier());
|
||||
simpleItem.setValue(CaculateUtils.handleToUnSign16(data[0] + "", config.getDataType()));
|
||||
simpleItem.setValue(CalculateUtils.handleToUnSign16(data[0] + "", config.getDataType()));
|
||||
simpleItem.setTs(DateUtils.getNowDate());
|
||||
simpleItemList.add(simpleItem);
|
||||
}
|
||||
@ -306,13 +306,13 @@ public class ModbusTcpProtocol implements IProtocol {
|
||||
simpleItem.setTs(DateUtils.getNowDate());
|
||||
if (quantity == 1) {
|
||||
//将identity与address替换
|
||||
simpleItem.setValue(CaculateUtils.handleToUnSign16(data[i] + "", modbusConfig.getDataType()));
|
||||
simpleItem.setValue(CalculateUtils.handleToUnSign16(data[i] + "", modbusConfig.getDataType()));
|
||||
simpleItemList.add(simpleItem);
|
||||
dataHex = dataHex.substring(4);
|
||||
} else if (quantity == 2) {
|
||||
//这里做数据处理
|
||||
String handleHex = dataHex.substring(0, 8);
|
||||
String value = CaculateUtils.parseValue(modbusConfig.getDataType(), handleHex);
|
||||
String value = CalculateUtils.parseValue(modbusConfig.getDataType(), handleHex);
|
||||
simpleItem.setValue(value);
|
||||
simpleItemList.add(simpleItem);
|
||||
//处理dataHex
|
||||
|
||||
@ -5,7 +5,7 @@ import com.fastbee.common.enums.scenemodel.SceneModelVariableTypeEnum;
|
||||
import com.fastbee.common.utils.DateUtils;
|
||||
import com.fastbee.common.utils.SecurityUtils;
|
||||
import com.fastbee.common.utils.StringUtils;
|
||||
import com.fastbee.common.utils.CaculateUtils;
|
||||
import com.fastbee.common.utils.CalculateUtils;
|
||||
import com.fastbee.iot.cache.SceneModelTagCache;
|
||||
import com.fastbee.iot.domain.*;
|
||||
import com.fastbee.iot.model.AlertCountVO;
|
||||
@ -380,7 +380,7 @@ public class DataCenterServiceImpl implements DataCenterService {
|
||||
replaceMap.put("{" + entry.getKey() + "}", val != null ? val : "0");
|
||||
}
|
||||
try {
|
||||
String computed = CaculateUtils.execute(model.getFormula(), replaceMap).toString();
|
||||
String computed = CalculateUtils.execute(model.getFormula(), replaceMap).toString();
|
||||
HistoryModel hm = new HistoryModel();
|
||||
hm.setTime(minute);
|
||||
hm.setIdentity(calcIdentifier);
|
||||
|
||||
@ -19,7 +19,7 @@ import com.fastbee.common.core.thingsModel.ThingsModelSimpleItem;
|
||||
import com.fastbee.common.core.thingsModel.ThingsModelValuesInput;
|
||||
import com.fastbee.common.enums.*;
|
||||
import com.fastbee.common.exception.ServiceException;
|
||||
import com.fastbee.common.utils.CaculateUtils;
|
||||
import com.fastbee.common.utils.CalculateUtils;
|
||||
import com.fastbee.common.utils.DateUtils;
|
||||
import com.fastbee.common.utils.SecurityUtils;
|
||||
import com.fastbee.common.utils.StringUtils;
|
||||
@ -583,7 +583,7 @@ public class DeviceServiceImpl implements IDeviceService {
|
||||
|
||||
// 执行计算
|
||||
try {
|
||||
value = String.valueOf(CaculateUtils.execute(formula, params));
|
||||
value = String.valueOf(CalculateUtils.execute(formula, params));
|
||||
} catch (Exception e) {
|
||||
log.error("=>计算公式执行失败[{}],标识符:[{}],公式:[{}],错误:{}", input.getProductId(), identity, formula, e.getMessage());
|
||||
}
|
||||
|
||||
@ -22,10 +22,11 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
|
||||
<result property="updateBy" column="update_by" />
|
||||
<result property="updateTime" column="update_time" />
|
||||
<result property="deptType" column="dept_type" />
|
||||
<result property="logo" column="logo" />
|
||||
</resultMap>
|
||||
|
||||
<sql id="selectDeptVo">
|
||||
select d.dept_id, d.dept_user_id, d.parent_id, d.ancestors, d.dept_name, d.order_num, d.leader, d.phone, d.email, d.status, d.del_flag, d.create_by, d.create_time, d.dept_type
|
||||
select d.dept_id, d.dept_user_id, d.parent_id, d.ancestors, d.dept_name, d.order_num, d.leader, d.phone, d.email, d.status, d.del_flag, d.create_by, d.create_time, d.dept_type, d.logo
|
||||
from sys_dept d
|
||||
</sql>
|
||||
|
||||
@ -61,7 +62,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
|
||||
</select>
|
||||
|
||||
<select id="selectDeptById" parameterType="Long" resultMap="SysDeptResult">
|
||||
select d.dept_id, d.parent_id, d.ancestors, d.dept_name, d.order_num, d.leader, d.phone, d.email, d.status, d.dept_type, d.dept_user_id,
|
||||
select d.dept_id, d.parent_id, d.ancestors, d.dept_name, d.order_num, d.leader, d.phone, d.email, d.status, d.dept_type, d.dept_user_id, d.logo,
|
||||
(select dept_name from sys_dept where dept_id = d.parent_id) parent_name
|
||||
from sys_dept d
|
||||
where d.dept_id = #{deptId}
|
||||
@ -135,6 +136,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
|
||||
<if test="status != null">status,</if>
|
||||
<if test="createBy != null and createBy != ''">create_by,</if>
|
||||
<if test="deptType != null and deptType != ''">dept_type,</if>
|
||||
<if test="logo != null and logo != ''">logo,</if>
|
||||
create_time
|
||||
)values(
|
||||
<if test="deptId != null and deptId != 0">#{deptId},</if>
|
||||
@ -149,6 +151,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
|
||||
<if test="status != null">#{status},</if>
|
||||
<if test="createBy != null and createBy != ''">#{createBy},</if>
|
||||
<if test="deptType != null and deptType != ''">#{deptType},</if>
|
||||
<if test="logo != null and logo != ''">#{logo},</if>
|
||||
sysdate()
|
||||
)
|
||||
</insert>
|
||||
@ -166,6 +169,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
|
||||
<if test="email != null">email = #{email},</if>
|
||||
<if test="status != null and status != ''">status = #{status},</if>
|
||||
<if test="updateBy != null and updateBy != ''">update_by = #{updateBy},</if>
|
||||
<if test="logo != null">logo = #{logo},</if>
|
||||
update_time = sysdate()
|
||||
</set>
|
||||
where dept_id = #{deptId}
|
||||
|
||||
@ -38,6 +38,7 @@
|
||||
<result property="leader" column="leader" />
|
||||
<result property="status" column="dept_status" />
|
||||
<result property="deptUserId" column="dept_user_id"/>
|
||||
<result property="logo" column="logo" />
|
||||
<result property="deptUserName" column="dept_user_name"/>
|
||||
</resultMap>
|
||||
|
||||
@ -52,7 +53,7 @@
|
||||
|
||||
<sql id="selectUserVo">
|
||||
select u.user_id, u.dept_id, u.user_name, u.nick_name, u.email, u.avatar, u.phonenumber, u.password, u.sex, u.status, u.del_flag, u.login_ip, u.login_date, u.create_by, u.create_time, u.remark,
|
||||
d.dept_id, d.parent_id, d.ancestors, d.dept_name, d.order_num, d.leader, d.status as dept_status, d.dept_user_id,
|
||||
d.dept_id, d.parent_id, d.ancestors, d.dept_name, d.order_num, d.leader, d.status as dept_status, d.dept_user_id,d.logo,
|
||||
r.role_id, r.role_name, r.role_key, r.role_sort, r.data_scope, r.status as role_status,
|
||||
(select su.user_name from sys_user su where su.user_id = d.dept_user_id) as dept_user_name,
|
||||
ap.language,ap.time_zone
|
||||
|
||||
165
yack-web/docs/data-center-history-chart-solution.md
Normal file
165
yack-web/docs/data-center-history-chart-solution.md
Normal file
@ -0,0 +1,165 @@
|
||||
# 数据中心 · 历史记录 · 曲线趋势图改造方案
|
||||
|
||||
## 1. 背景与问题
|
||||
|
||||
### 1.1 业务场景
|
||||
|
||||
路径:**数据中心 → 历史记录**([`history.vue`](../src/views/dataCenter/history.vue))。
|
||||
|
||||
页面含三个 Tab:**设备**、**网关设备**、**场景**,各自有「曲线趋势图」或网关下的计算量 / 多设备对比 / 单子设备折线图。
|
||||
|
||||
### 1.2 原始痛点
|
||||
|
||||
| 问题 | 说明 |
|
||||
|------|------|
|
||||
| 多曲线「压成直线」 | 多物模型量级差异大(如位移 168、角度 3、电压 12),共用单 Y 轴时范围被拉大,小幅波动在图上不可见。 |
|
||||
| 多 Y 轴布局异常 | 为每条曲线建隐藏 Y 轴时,ECharts 会为多轴计算偏移,曲线一多时绘图区被挤到一侧。 |
|
||||
| 图例占用空间 | 网关多设备对比图例项极多,默认换行占满高度,挤压 grid。 |
|
||||
| X 轴可读性 | 时间点密集时标签重叠。 |
|
||||
| 垂直空间不足 | 固定 480px 高度时,几十条归一化曲线仍显拥挤。 |
|
||||
|
||||
---
|
||||
|
||||
## 2. 总体思路
|
||||
|
||||
1. **独立 Y 轴模式**:不再使用「每 series 一个 Y 轴」,改为 **按每条曲线自身 min-max 归一化到 [-100, 100]**,**共用一条数值 Y 轴**,避免多轴布局副作用;**Tooltip 始终展示原始值**。
|
||||
2. **共享 Y 轴模式**:保留原逻辑,单 Y 轴 + `scale: true`,便于对比绝对量级。
|
||||
3. **归一化坐标展示**:独立模式下 Y 轴可见,刻度约 -100%~100%,并附说明性网格线。
|
||||
4. **网关图**:图例与 grid 与设备 Tab 对齐策略;X 轴旋转 + 对齐刻度;必要时配合底部留白与 dataZoom。
|
||||
5. **动态高度**:按 **series 数量** 增加图表容器高度(有上下限),并在 DOM 更新后 `resize`。
|
||||
|
||||
---
|
||||
|
||||
## 3. 功能开关
|
||||
|
||||
- **数据字段**:`independentYAxis`(默认 `true`)。
|
||||
- **UI**:设备 / 网关 / 场景三个图表卡片头部 **「独立Y轴 | 共享Y轴」** 开关。
|
||||
- **切换行为**:`handleYAxisModeChange` 在 `$nextTick` 内按当前 Tab 与已有数据调用对应 `draw*Line`,无需重新请求接口。
|
||||
|
||||
---
|
||||
|
||||
## 4. 核心实现
|
||||
|
||||
### 4.1 归一化(独立模式)
|
||||
|
||||
**方法**:`applyIndependentScale(series)`
|
||||
|
||||
- 对每条 `series.data` 中可解析为数字的点,取该 series 全体有效点的 `min`、`max`。
|
||||
- 映射公式:`value = ((num - min) / (max - min)) * 200 - 100`,落在 **[-100, 100]**。
|
||||
- **常量序列**(`max === min`):映射为 **0**(中间水平线)。
|
||||
- 每个数据点存为 `{ value, _original }`,`_original` 为归一化前的原始值(兼容负数与非数字展示)。
|
||||
|
||||
**返回的 Y 轴配置**(单轴):
|
||||
|
||||
- `min: -100`, `max: 100`
|
||||
- `axisLine` / `axisTick` 显示
|
||||
- `axisLabel.formatter`: 显示为 `xx%`(表示相对本曲线量程的位置,非物理单位)
|
||||
- `splitLine`:虚线网格,便于读趋势
|
||||
|
||||
### 4.2 Tooltip
|
||||
|
||||
**方法**:`independentScaleTooltip()`
|
||||
|
||||
- `trigger: 'axis'`
|
||||
- `formatter`:时间轴标签 + 各 series **原始值**(从 `p.data._original` 读取)
|
||||
|
||||
### 4.3 共享模式
|
||||
|
||||
- `yAxis`: `{ type: 'value', scale: true }`
|
||||
- `tooltip`: `{ trigger: 'axis' }`(默认)
|
||||
|
||||
### 4.4 涉及的绘图方法
|
||||
|
||||
| 方法 | Tab / 场景 |
|
||||
|------|------------|
|
||||
| `drawDevLine` | 设备 |
|
||||
| `drawGwCalcLine` | 网关 · 计算量 |
|
||||
| `drawGwSingleLine` | 网关 · 单子设备 |
|
||||
| `drawSubCompareLine` | 网关 · 多设备同一变量对比 |
|
||||
| `drawSceneLine` | 场景 |
|
||||
|
||||
以上方法在 `independentYAxis === true` 时均走 `applyIndependentScale` + `independentScaleTooltip`。
|
||||
|
||||
### 4.5 网关图布局要点
|
||||
|
||||
- `legend`: `align: 'right'`, `left: '3%'`, `top: '3%'`(与多行图例协调)
|
||||
- `grid.top: '30%'`,为图例留出区域,避免与曲线重叠
|
||||
- `xAxis`: `boundaryGap: true`, `axisTick.alignWithLabel: true`, `axisLabel.rotate: 30`
|
||||
- `dataZoom`: `inside` + 滑块,便于时间范围缩放
|
||||
- `toolbox`: 数据视图、还原、保存图片(与设备 Tab 能力对齐)
|
||||
|
||||
### 4.6 动态图表高度
|
||||
|
||||
**数据字段**(像素):
|
||||
|
||||
- `devChartHeight`, `gwCalcChartHeight`, `gwSingleChartHeight`, `subCompareChartHeight`, `sceneChartHeight`(默认 480)
|
||||
|
||||
**方法**:`calcChartHeight(seriesCount)`
|
||||
|
||||
```text
|
||||
height = clamp(480, seriesCount * 14 + 360, 1200)
|
||||
```
|
||||
|
||||
即:最少 480px,随曲线数量增加,**最多 1200px**。
|
||||
|
||||
**模板**:各图表容器使用 `:style="{ height: xxxChartHeight + 'px', ... }"`。
|
||||
|
||||
**绘制流程**:
|
||||
|
||||
1. 先算好 `series` 与 `series.length`,赋值对应 `xxxChartHeight`
|
||||
2. `this.$nextTick(() => { init/clear → setOption → chart.resize() })`
|
||||
保证先根据新高度更新 DOM,再初始化/重绘 ECharts。
|
||||
|
||||
---
|
||||
|
||||
## 5. 数据流(简图)
|
||||
|
||||
```mermaid
|
||||
flowchart LR
|
||||
subgraph input [数据]
|
||||
API[历史接口]
|
||||
end
|
||||
subgraph mode [模式]
|
||||
IND[独立Y轴]
|
||||
SHR[共享Y轴]
|
||||
end
|
||||
subgraph render [渲染]
|
||||
NORM[逐条 min-max 到 -100~100]
|
||||
ONEAXIS[单 Y 轴]
|
||||
TT[Tooltip 原始值]
|
||||
end
|
||||
API --> IND
|
||||
API --> SHR
|
||||
IND --> NORM --> ONEAXIS
|
||||
ONEAXIS --> TT
|
||||
SHR --> ONEAXIS
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 6. 使用说明(给产品/测试)
|
||||
|
||||
1. **独立 Y 轴(推荐多曲线看趋势)**:纵轴为各曲线**自身量程内的相对位置**(-100%~100%),对比不同物理量时看形状;**真实数值以 Tooltip 与下方表格为准**。
|
||||
2. **共享 Y 轴**:纵轴为**真实数值**,适合量级接近、需比大小的变量;量级差很大时仍可能出现「看起来像直线」。
|
||||
3. **曲线很多时**:图会自动变高(上限约 1200px),页面可纵向滚动查看全图。
|
||||
|
||||
---
|
||||
|
||||
## 7. 相关文件
|
||||
|
||||
| 文件 | 说明 |
|
||||
|------|------|
|
||||
| [`yack-web/src/views/dataCenter/history.vue`](../src/views/dataCenter/history.vue) | 全部图表逻辑、开关、高度、归一化 |
|
||||
| [`yack-web/src/api/data/center.js`](../src/api/data/center.js) | 历史数据接口(未因本方案变更协议) |
|
||||
|
||||
---
|
||||
|
||||
## 8. 版本与维护建议
|
||||
|
||||
- 若需调整「多高算够」,改 `calcChartHeight` 中的系数 `14`、基础 `360`、上下限 `480`/`1200` 即可。
|
||||
- 若产品要求 Y 轴显示「归一化」文案而非 `%`,可在 `applyIndependentScale` 返回的 `axisLabel` 中改为固定文案或 `name`/`nameLocation`。
|
||||
- 新增同类折线图时,复用:`applyIndependentScale` / `independentScaleTooltip` / `calcChartHeight` + `$nextTick` + `resize` 模式。
|
||||
|
||||
---
|
||||
|
||||
*文档对应实现:历史记录页曲线趋势图(多 Tab、独立/共享 Y 轴、归一化 -100~100、动态高度)。*
|
||||
@ -32,12 +32,16 @@ export default {
|
||||
sideTheme() {
|
||||
return this.$store.state.settings.sideTheme;
|
||||
},
|
||||
dept() {
|
||||
return this.$store.state.user.dept;
|
||||
},
|
||||
logo() {
|
||||
const deptLogo = this.dept && this.dept.logo;
|
||||
return deptLogo ? process.env.VUE_APP_BASE_API + deptLogo : logoImg;
|
||||
},
|
||||
title() {
|
||||
return (this.dept && this.dept.deptName) || '岩安测控';
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
title: '岩安测控',
|
||||
logo: logoImg,
|
||||
};
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
@ -74,6 +74,9 @@
|
||||
<el-form-item :label="$t('system.dept.780956-0')" prop="deptName">
|
||||
<el-input v-model="form.deptName" :placeholder="$t('system.dept.780956-1')" />
|
||||
</el-form-item>
|
||||
<el-form-item label="机构Logo" prop="logo">
|
||||
<imageUpload :value="form.logo" :limit="1" :fileSize="2" @input="form.logo = $event" />
|
||||
</el-form-item>
|
||||
<el-form-item :label="$t('system.dept.780956-3')" prop="deptType">
|
||||
<el-select v-model="form.deptType" :placeholder="$t('system.dept.780956-12')" clearable size="small" style="width: 100%" :disabled="this.form.deptId != null">
|
||||
<el-option v-for="option in deptTypeList" :key="option.value" :label="option.label" :value="option.value"></el-option>
|
||||
@ -126,11 +129,12 @@
|
||||
import { listDept, getDept, delDept, addDept, updateDept, listDeptExcludeChild, getDeptType } from '@/api/system/dept';
|
||||
import Treeselect from '@riophae/vue-treeselect';
|
||||
import '@riophae/vue-treeselect/dist/vue-treeselect.css';
|
||||
import imageUpload from '@/components/ImageUpload/index';
|
||||
|
||||
export default {
|
||||
name: 'Dept',
|
||||
dicts: ['sys_normal_disable', 'department_type'],
|
||||
components: { Treeselect },
|
||||
components: { Treeselect, imageUpload },
|
||||
data() {
|
||||
const equalToPassword = (rule, value, callback) => {
|
||||
if (this.form.password !== value) {
|
||||
@ -251,6 +255,7 @@ export default {
|
||||
status: '0',
|
||||
deptType: 0,
|
||||
showOwner: false,
|
||||
logo: undefined,
|
||||
};
|
||||
this.resetForm('form');
|
||||
},
|
||||
|
||||
Loading…
Reference in New Issue
Block a user