Commit f8da175f authored by wanghao's avatar wanghao

1 托盘和 设备 数据绑定界面开发中

parent 2b92be96
...@@ -55,6 +55,15 @@ public class TStoreyInfoController extends BaseController ...@@ -55,6 +55,15 @@ public class TStoreyInfoController extends BaseController
return util.exportExcel(list, "老化层信息数据"); return util.exportExcel(list, "老化层信息数据");
} }
/**
* 获取老化层信息详细信息
*/
@GetMapping(value = "/queryByDepartmentId/{fEquipmentId}")
public AjaxResult queryByDepartmentId(@PathVariable("fEquipmentId") Long fEquipmentId)
{
return AjaxResult.success(tStoreyInfoService.queryByDepartmentId(fEquipmentId));
}
/** /**
* 获取老化层信息详细信息 * 获取老化层信息详细信息
*/ */
......
...@@ -7,12 +7,16 @@ import com.serotonin.modbus4j.exception.ModbusTransportException; ...@@ -7,12 +7,16 @@ import com.serotonin.modbus4j.exception.ModbusTransportException;
import com.zehong.framework.modbus4j.Modbus4jUtils; import com.zehong.framework.modbus4j.Modbus4jUtils;
import com.zehong.system.domain.TEquipmentAlarmData; import com.zehong.system.domain.TEquipmentAlarmData;
import com.zehong.system.domain.TEquipmentInfo; import com.zehong.system.domain.TEquipmentInfo;
import com.zehong.system.domain.TStoreyInfo;
import com.zehong.system.domain.modbus.ModbusDeviceData; import com.zehong.system.domain.modbus.ModbusDeviceData;
import com.zehong.system.service.ITEquipmentAlarmDataService; import com.zehong.system.service.ITEquipmentAlarmDataService;
import com.zehong.system.service.ITEquipmentInfoService; import com.zehong.system.service.ITEquipmentInfoService;
import com.zehong.system.service.ITStoreyInfoService;
import com.zehong.web.task.AgingCabinetInspectionAndPowerCheckTask; import com.zehong.web.task.AgingCabinetInspectionAndPowerCheckTask;
import com.zehong.web.task.PowerOffCommandEvent;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController; import org.springframework.web.bind.annotation.RestController;
...@@ -42,6 +46,9 @@ public class TestTaskController { ...@@ -42,6 +46,9 @@ public class TestTaskController {
@Resource @Resource
private ITEquipmentAlarmDataService tEquipmentAlarmDataService; private ITEquipmentAlarmDataService tEquipmentAlarmDataService;
@Resource
private ApplicationEventPublisher eventPublisher; // 新增事件发布器
/** /**
* 五分钟一次 * 五分钟一次
* 1.老化柜、标定柜巡查 * 1.老化柜、标定柜巡查
...@@ -80,16 +87,14 @@ public class TestTaskController { ...@@ -80,16 +87,14 @@ public class TestTaskController {
} }
List<CompletableFuture<ModbusDeviceData>> futures = equipmentInfos.stream().map(equipmentInfo -> CompletableFuture.supplyAsync(() -> { List<CompletableFuture<ModbusDeviceData>> futures = equipmentInfos.stream().map(equipmentInfo -> CompletableFuture.supplyAsync(() -> {
ModbusMaster master = null; ModbusMaster master = null;
// 构造结果对象
ModbusDeviceData deviceData;
// 10 层 // 10 层
List<Integer> registerOffsets = Arrays.asList(0, 1, 2, 3, 4, 5, 6, 7, 8, 9); List<Integer> registerOffsets = Arrays.asList(0, 1, 2, 3, 4, 5, 6, 7, 8, 9);
try { try {
master = Modbus4jUtils.getMaster(equipmentInfo.getfIp(), equipmentInfo.getfPort()); master = Modbus4jUtils.getMaster(equipmentInfo.getfIp(), equipmentInfo.getfPort());
Map<Integer, Object> integerObjectMap = Modbus4jUtils.batchReadAgingCabinetStatus(master, registerOffsets); Map<Integer, Object> integerObjectMap = Modbus4jUtils.batchReadAgingCabinetStatus(master, registerOffsets);
// 返回结果 // 返回结果 无错误
return createErrorData(equipmentInfo,"","",integerObjectMap); return createErrorData(equipmentInfo, "", "", integerObjectMap);
} catch (ModbusInitException e) { } catch (ModbusInitException e) {
// 记录异常数据 // 记录异常数据
...@@ -104,16 +109,16 @@ public class TestTaskController { ...@@ -104,16 +109,16 @@ public class TestTaskController {
// 返回错误信息 // 返回错误信息
Map<Integer, Object> errorMap = new HashMap<>(); Map<Integer, Object> errorMap = new HashMap<>();
// 返回结果 // 返回结果
return createErrorData(equipmentInfo,"2","Modbus初始化失败",errorMap); return createErrorData(equipmentInfo, "2", "Modbus初始化失败", errorMap);
// 层有错误返回 柜可能连不上也在这个报错里面 // 层有错误返回 柜可能连不上也在这个报错里面
} catch (ModbusTransportException e) { } catch (ModbusTransportException e) {
// 网线没插好通讯不上 // 网线没插好通讯不上
if(e.getMessage().equals("java.net.SocketTimeoutException: connect timed out")) { if (e.getMessage().equals("java.net.SocketTimeoutException: connect timed out")) {
// 记录异常数据 // 记录异常数据
alarmData.setfAlarmType("01"); //01.老化柜 02.机械臂 03.老化层 04.点位 alarmData.setfAlarmType("01"); //01.老化柜 02.机械臂 03.老化层 04.点位
alarmData.setfEquipmentCode(equipmentInfo.getfEquipmentCode()); alarmData.setfEquipmentCode(equipmentInfo.getfEquipmentCode());
alarmData.setfAlarmData("定时任务巡检:老化柜网线没插好"); alarmData.setfAlarmData("定时任务巡检:老化柜网线没插好");
// 线接错误 // 线接错误
} else { } else {
// 记录异常数据 // 记录异常数据
alarmData.setfAlarmType("01"); //01.老化柜 02.机械臂 03.老化层 04.点位 alarmData.setfAlarmType("01"); //01.老化柜 02.机械臂 03.老化层 04.点位
...@@ -128,17 +133,17 @@ public class TestTaskController { ...@@ -128,17 +133,17 @@ public class TestTaskController {
Map<Integer, Object> errorMap = new HashMap<>(); Map<Integer, Object> errorMap = new HashMap<>();
// 网线没插好通讯不上 // 网线没插好通讯不上
// 返回结果 // 返回结果
if(e.getMessage().equals("java.net.SocketTimeoutException: connect timed out")) { if (e.getMessage().equals("java.net.SocketTimeoutException: connect timed out")) {
return createErrorData(equipmentInfo,"2","网线没插好",errorMap); return createErrorData(equipmentInfo, "2", "定时任务巡检:网线没插好", errorMap);
} else { } else {
// 线接错误 // 线接错误
// 返回结果 // 返回结果
return createErrorData(equipmentInfo,"2","通信线路没接好",errorMap); return createErrorData(equipmentInfo, "2", "定时任务巡检:通信线路没接好", errorMap);
} }
} catch (ErrorResponseException e) { } catch (ErrorResponseException e) {
Map<Integer, Object> errorMap = new HashMap<>(); Map<Integer, Object> errorMap = new HashMap<>();
// 返回结果 // 返回结果
return createErrorData(equipmentInfo,"2","ErrorResponseException",errorMap); return createErrorData(equipmentInfo, "2", "定时任务巡检:ErrorResponseException", errorMap);
} finally { } finally {
if (master != null) { if (master != null) {
master.destroy(); master.destroy();
...@@ -168,15 +173,16 @@ public class TestTaskController { ...@@ -168,15 +173,16 @@ public class TestTaskController {
return deferredResult; return deferredResult;
} }
private ModbusDeviceData createErrorData(TEquipmentInfo equipmentInfo,String status, private ModbusDeviceData createErrorData(TEquipmentInfo equipmentInfo, String status,
String errorReason,Map<Integer, Object> integerObjectMap) { String errorReason, Map<Integer, Object> integerObjectMap) {
ModbusDeviceData deviceData = new ModbusDeviceData(); ModbusDeviceData deviceData = new ModbusDeviceData();
deviceData.setId(equipmentInfo.getfEquipmentId());
deviceData.setfIp(equipmentInfo.getfIp()); deviceData.setfIp(equipmentInfo.getfIp());
deviceData.setfPort(equipmentInfo.getfPort()); deviceData.setfPort(equipmentInfo.getfPort());
deviceData.setDeviceCode(equipmentInfo.getfEquipmentCode()); deviceData.setDeviceCode(equipmentInfo.getfEquipmentCode());
deviceData.setDeviceStatus(status); deviceData.setDeviceStatus(status);
deviceData.setErrorReason(errorReason); deviceData.setErrorReason(errorReason);
if(integerObjectMap != null){ if (integerObjectMap != null) {
deviceData.setRegisterValues(integerObjectMap.entrySet().stream() deviceData.setRegisterValues(integerObjectMap.entrySet().stream()
.collect(Collectors.toMap( .collect(Collectors.toMap(
Map.Entry::getKey, Map.Entry::getKey,
...@@ -188,28 +194,34 @@ public class TestTaskController { ...@@ -188,28 +194,34 @@ public class TestTaskController {
// 处理 老化柜 是 运行 开始 空闲 // 处理 老化柜 是 运行 开始 空闲
private List<ModbusDeviceData> processDeviceData(List<ModbusDeviceData> deviceDataList) { private List<ModbusDeviceData> processDeviceData(List<ModbusDeviceData> deviceDataList) {
for (ModbusDeviceData modbusDeviceData : deviceDataList) { for (ModbusDeviceData modbusDeviceData : deviceDataList) {
Map<Integer, String> registerValues = modbusDeviceData.getRegisterValues(); Map<Integer, String> registerValues = modbusDeviceData.getRegisterValues();
boolean isRun = false; // 大于0 说明 柜能通信;小于0 说明柜 不能通信
for (Map.Entry<Integer, String> entry : registerValues.entrySet()) { if(registerValues.size() > 0) {
Integer registerOffset = entry.getKey(); boolean isRun = false;
String registerValue = entry.getValue(); for (Map.Entry<Integer, String> entry : registerValues.entrySet()) {
if ("true".equals(registerValue)) { Integer registerOffset = entry.getKey();
isRun = true; String registerValue = entry.getValue();
// 要给这个 层 发断电的 指令 if ("true".equals(registerValue)) {
} else { isRun = true;
try { // 要给这个 层 发断电的 指令
ModbusMaster master = Modbus4jUtils.getMaster(modbusDeviceData.getfIp(), modbusDeviceData.getfPort()); } else {
Modbus4jUtils.writeCoil(master, 1, registerOffset, false); // 发布断电指令事件(不再直接执行)
} catch (ModbusInitException | ModbusTransportException e) { eventPublisher.publishEvent(new PowerOffCommandEvent(
throw new RuntimeException(e); this,
modbusDeviceData.getDeviceCode(),
modbusDeviceData.getfIp(),
modbusDeviceData.getfPort(),
registerOffset
));
} }
} }
} if (isRun) {
if(isRun) { modbusDeviceData.setDeviceStatus("1");
modbusDeviceData.setDeviceStatus("1"); } else {
} else { modbusDeviceData.setDeviceStatus("0");
modbusDeviceData.setDeviceStatus("0"); }
} }
} }
return deviceDataList; return deviceDataList;
......
package com.zehong.web.task;
import com.serotonin.modbus4j.ModbusMaster;
import com.serotonin.modbus4j.exception.ModbusInitException;
import com.serotonin.modbus4j.exception.ModbusTransportException;
import com.zehong.framework.modbus4j.Modbus4jUtils;
import com.zehong.system.domain.TEquipmentAlarmData;
import com.zehong.system.domain.TStoreyInfo;
import com.zehong.system.service.ITEquipmentAlarmDataService;
import com.zehong.system.service.ITStoreyInfoService;
import org.springframework.context.event.EventListener;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Component;
import javax.annotation.Resource;
/**
* @author lenovo
* @date 2025/6/17
* @description TODO
*/
@Component
public class AllCommandHandler {
private static final org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(AllCommandHandler.class);
@Resource
private ITEquipmentAlarmDataService alarmDataService;
@Resource
private ITStoreyInfoService tStoreyInfoService;
@Async // 异步执行
@EventListener(PowerOffCommandEvent.class)
public void handlePowerOffCommand(PowerOffCommandEvent event) {
try {
String storeyCode = event.getDeviceCode() + "-" + event.getLayer();
log.info("需要发送断电指令 - 设备:{} 层:{}", event.getDeviceCode(), event.getLayer());
TStoreyInfo tStoreyInfo = tStoreyInfoService.selectTStoreyInfoByCode(storeyCode);
if (tStoreyInfo == null) {
TEquipmentAlarmData alarmData = new TEquipmentAlarmData();
// 记录异常数据
alarmData.setfAlarmType("01"); //01.老化柜 02.机械臂 03.老化层 04.点位
alarmData.setfEquipmentCode(event.getDeviceCode());
alarmData.setfAlarmData("下属" + storeyCode + "号老化层不存在");
alarmDataService.insertTEquipmentAlarmData(alarmData);
} else {
ModbusMaster master = Modbus4jUtils.getMaster(tStoreyInfo.getfIp(), tStoreyInfo.getfPort());
Modbus4jUtils.writeCoil(master, 1, event.getLayer(), false);
log.info("已发送断电指令 - 设备:{} 层:{}", event.getDeviceCode(), event.getLayer());
master.destroy();
}
} catch (ModbusInitException | ModbusTransportException e) {
log.error("断电指令执行失败 - 设备:{} 层:{}", event.getDeviceCode(), event.getLayer(), e);
// 记录异常
TEquipmentAlarmData alarmData = new TEquipmentAlarmData();
alarmData.setfAlarmType("03"); // 老化层
alarmData.setfEquipmentCode(event.getDeviceCode());
alarmData.setfAlarmData("断电指令执行失败: " + e.getMessage());
alarmDataService.insertTEquipmentAlarmData(alarmData);
}
}
}
package com.zehong.web.task;
import org.springframework.context.ApplicationEvent;
/**
* @author lenovo
* @date 2025/6/17
* @description 断电event
*/
public class PowerOffCommandEvent extends ApplicationEvent {
private final String ip;
private final int port;
private final int layer;
private final String deviceCode;
public PowerOffCommandEvent(Object source, String deviceCode, String ip, int port, int layer) {
super(source);
this.deviceCode = deviceCode;
this.ip = ip;
this.port = port;
this.layer = layer;
}
// Getters
public String getIp() { return ip; }
public int getPort() { return port; }
public int getLayer() { return layer; }
public String getDeviceCode() { return deviceCode; }
}
package com.zehong.system.domain; package com.zehong.system.domain;
import java.util.Date; import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import com.fasterxml.jackson.annotation.JsonFormat; import com.fasterxml.jackson.annotation.JsonFormat;
import org.apache.commons.lang3.builder.ToStringBuilder; import org.apache.commons.lang3.builder.ToStringBuilder;
import org.apache.commons.lang3.builder.ToStringStyle; import org.apache.commons.lang3.builder.ToStringStyle;
...@@ -17,6 +20,14 @@ public class TStoreyInfo extends BaseEntity ...@@ -17,6 +20,14 @@ public class TStoreyInfo extends BaseEntity
{ {
private static final long serialVersionUID = 1L; private static final long serialVersionUID = 1L;
/**状态*/
private static Map<String,String> statusStrMap = new HashMap<String,String>(){{
put("0","空闲");
put("1","运行");
put("2","故障");
put("3","断电");
}};
/** 层ID */ /** 层ID */
private Long fStoreyId; private Long fStoreyId;
...@@ -40,6 +51,8 @@ public class TStoreyInfo extends BaseEntity ...@@ -40,6 +51,8 @@ public class TStoreyInfo extends BaseEntity
@Excel(name = "状态:0空闲,1运行,2故障,3断电") @Excel(name = "状态:0空闲,1运行,2故障,3断电")
private String fStatus; private String fStatus;
private String statusStr;
/** 老化开始时间 */ /** 老化开始时间 */
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
@Excel(name = "更新时间", width = 30, dateFormat = "yyyy-MM-dd HH:mm:ss") @Excel(name = "更新时间", width = 30, dateFormat = "yyyy-MM-dd HH:mm:ss")
...@@ -106,6 +119,15 @@ public class TStoreyInfo extends BaseEntity ...@@ -106,6 +119,15 @@ public class TStoreyInfo extends BaseEntity
{ {
return fStatus; return fStatus;
} }
public String getStatusStr() {
return statusStrMap.get(fStatus);
}
public void setStatusStr(String statusStr) {
this.statusStr = statusStr;
}
public void setfPort(Integer fPort) public void setfPort(Integer fPort)
{ {
this.fPort = fPort; this.fPort = fPort;
......
...@@ -8,6 +8,10 @@ import java.util.Map; ...@@ -8,6 +8,10 @@ import java.util.Map;
* @description modbus设备数据 * @description modbus设备数据
*/ */
public class ModbusDeviceData { public class ModbusDeviceData {
// equipmentId
private Long id;
private String deviceCode; private String deviceCode;
/** IP地址 */ /** IP地址 */
...@@ -75,4 +79,12 @@ public class ModbusDeviceData { ...@@ -75,4 +79,12 @@ public class ModbusDeviceData {
public void setfPort(Integer fPort) { public void setfPort(Integer fPort) {
this.fPort = fPort; this.fPort = fPort;
} }
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
} }
...@@ -35,6 +35,8 @@ public interface TStoreyInfoMapper ...@@ -35,6 +35,8 @@ public interface TStoreyInfoMapper
*/ */
public List<TStoreyInfo> selectTStoreyInfoList(TStoreyInfo tStoreyInfo); public List<TStoreyInfo> selectTStoreyInfoList(TStoreyInfo tStoreyInfo);
public List<TStoreyInfo> queryByDepartmentId(Long fEquipmentId);
/** /**
* 新增老化层信息 * 新增老化层信息
* *
......
...@@ -35,6 +35,8 @@ public interface ITStoreyInfoService ...@@ -35,6 +35,8 @@ public interface ITStoreyInfoService
*/ */
public List<TStoreyInfo> selectTStoreyInfoList(TStoreyInfo tStoreyInfo); public List<TStoreyInfo> selectTStoreyInfoList(TStoreyInfo tStoreyInfo);
public List<TStoreyInfo> queryByDepartmentId(Long fDepartmentId);
/** /**
* 新增老化层信息 * 新增老化层信息
* *
......
...@@ -55,6 +55,16 @@ public class TStoreyInfoServiceImpl implements ITStoreyInfoService ...@@ -55,6 +55,16 @@ public class TStoreyInfoServiceImpl implements ITStoreyInfoService
return tStoreyInfoMapper.selectTStoreyInfoList(tStoreyInfo); return tStoreyInfoMapper.selectTStoreyInfoList(tStoreyInfo);
} }
/**
* 根据设备id 查询层信息
* @param fEquipmentId f
* @return r
*/
@Override
public List<TStoreyInfo> queryByDepartmentId(Long fEquipmentId) {
return tStoreyInfoMapper.queryByDepartmentId(fEquipmentId);
}
/** /**
* 新增老化层信息 * 新增老化层信息
* *
......
...@@ -18,7 +18,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" ...@@ -18,7 +18,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
</resultMap> </resultMap>
<sql id="selectTStoreyInfoVo"> <sql id="selectTStoreyInfoVo">
select f_storey_id, f_equipment_id, f_storey_code, f_tray_code, f_ip, f_status, f_port, f_aging_start_time, f_update_time, f_create_time, f_alarm_time from t_storey_info select f_storey_id, f_equipment_id, f_storey_code, f_ip, f_status, f_port, f_aging_start_time, f_update_time, f_create_time, f_alarm_time from t_storey_info
</sql> </sql>
<select id="selectTStoreyInfoList" parameterType="TStoreyInfo" resultMap="TStoreyInfoResult"> <select id="selectTStoreyInfoList" parameterType="TStoreyInfo" resultMap="TStoreyInfoResult">
...@@ -34,6 +34,10 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" ...@@ -34,6 +34,10 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
<if test="fAlarmTime != null "> and f_alarm_time = #{fAlarmTime}</if> <if test="fAlarmTime != null "> and f_alarm_time = #{fAlarmTime}</if>
</where> </where>
</select> </select>
<select id="queryByDepartmentId" parameterType="long" resultMap="TStoreyInfoResult">
<include refid="selectTStoreyInfoVo"/>
where f_equipment_id = #{fEquipmentId}
</select>
<select id="selectTStoreyInfoById" parameterType="Long" resultMap="TStoreyInfoResult"> <select id="selectTStoreyInfoById" parameterType="Long" resultMap="TStoreyInfoResult">
<include refid="selectTStoreyInfoVo"/> <include refid="selectTStoreyInfoVo"/>
......
...@@ -9,6 +9,14 @@ export function listStorey(query) { ...@@ -9,6 +9,14 @@ export function listStorey(query) {
}) })
} }
// 根据设备id查询老化层信息列表
export function queryByDepartmentId(fDepartmentId) {
return request({
url: '/storey/queryByDepartmentId/' +fDepartmentId,
method: 'get'
})
}
// 查询老化层信息详细 // 查询老化层信息详细
export function getStorey(fStoreyId) { export function getStorey(fStoreyId) {
return request({ return request({
......
...@@ -29,7 +29,7 @@ ...@@ -29,7 +29,7 @@
placement="top" placement="top"
> >
<el-card <el-card
:class="statusMap[item.status]" :class="statusMap[item.deviceStatus]"
style=" style="
width: 150px; width: 150px;
height: 150px; height: 150px;
...@@ -59,49 +59,13 @@ export default { ...@@ -59,49 +59,13 @@ export default {
return { return {
// 示例数据格式,实际从后端获取 // 示例数据格式,实际从后端获取
cabinets: [ cabinets: [
{ id: 1, status: 'available' },
{ id: 2, status: 'occupied' },
{ id: 3, status: 'default' },
{ id: 4, status: 'default' },
{ id: 5, status: 'default' },
{ id: 6, status: 'default' },
{ id: 7, status: 'default' },
{ id: 8, status: 'default' },
{ id: 9, status: 'default' },
{ id: 10, status: 'default' },
{ id: 11, status: 'default' },
{ id: 12, status: 'default' },
{ id: 13, status: 'default' },
{ id: 14, status: 'default' },
{ id: 15, status: 'default' },
{ id: 16, status: 'default' },
{ id: 17, status: 'default' },
{ id: 18, status: 'default' },
{ id: 19, status: 'default' },
{ id: 20, status: 'default' },
{ id: 21, status: 'default' },
{ id: 22, status: 'default' },
{ id: 23, status: 'default' },
{ id: 24, status: 'default' },
{ id: 25, status: 'default' },
{ id: 26, status: 'default' },
{ id: 27, status: 'default' },
{ id: 28, status: 'default' },
{ id: 29, status: 'default' },
{ id: 30, status: 'default' },
{ id: 31, status: 'default' },
{ id: 32, status: 'default' },
{ id: 33, status: 'default' },
{ id: 34, status: 'default' },
{ id: 35, status: 'default' },
{ id: 36, status: 'default' },
// ...共36个 // ...共36个
], ],
// 状态对应的颜色类名 // 状态对应的颜色类名
statusMap: { statusMap: {
default: 'default', 0: 'default',
available: 'available', 1: 'available',
occupied: 'occupied' 2: 'occupied'
}, },
agingCabinetList: null agingCabinetList: null
}; };
...@@ -118,27 +82,27 @@ export default { ...@@ -118,27 +82,27 @@ export default {
} }
}, },
mounted() { mounted() {
this.testAgingCabinetAndPowerCheck();
//this.testAgingCabinetAndPowerCheck();
}, },
methods: { methods: {
handleCardClick(item) { handleCardClick(item) {
this.msgSuccess("你想看层信息是吧"); // 触发事件传递 cabinetId 给父组件
this.$emit('cabinet-click', item);
}, },
getTooltipContent(item) { getTooltipContent(item) {
switch (item.status) { switch (item.deviceStatus) {
case 'occupied': case '2':
return item.faultReason || '无故障详情'; return item.errorReason || '无故障详情';
case 'available': case '1':
return '运行中'; return '运行中';
case 'default': case '0':
default: default:
return '空闲'; return '空闲';
} }
}, },
testAgingCabinetAndPowerCheck() { testAgingCabinetAndPowerCheck() {
getAgingCabinetAndPowerCheck().then(response => { getAgingCabinetAndPowerCheck().then(response => {
this.agingCabinetList = response; this.cabinets = response;
} }
); );
} }
......
<template> <template>
<div class="screen-container"> <div class="screen-container">
<!-- 返回按钮 -->
<div class="back-button" @click="goBack">
<i class="el-icon-back"></i> 返回老化柜看板
</div>
<!-- 整体容器 --> <!-- 整体容器 -->
<div class="content-container"> <div class="content-container">
<!-- 柜体容器 --> <!-- 柜体容器 -->
<div class="cabinet-body"> <div class="cabinet-body">
<!-- 标题 --> <!-- 标题 -->
<div class="title" ref="title">2号柜</div> <div class="title" ref="title">{{ modbusDeviceData.id }}号柜</div>
<div class="cabinet-and-lines"> <div class="cabinet-and-lines">
<!-- 柜体 --> <!-- 柜体 -->
<div class="cabinet" ref="cabinetContainer"> <div class="cabinet" ref="cabinetContainer">
<div <div
v-for="(layer, index) in layers" v-for="(layer, index) in layers"
:key="layer.name" :key="layer.id"
class="layer" class="layer"
:style="getLayerStyle(index)" :style="getLayerStyle(index)"
:class="['layer-depth', `layer-${index}`]" :class="[
'layer-depth',
`layer-${index}`,
getStatusClass(layer.fStatus) // 动态状态类
]"
@click="selectLayer(index)" @click="selectLayer(index)"
> >
{{ layer.name }} <div class="layer-content">
<div class="layer-name">{{ layer.name }}</div>
<div class="layer-status">{{ getStatusText(layer.fStatus) }}</div>
</div>
</div> </div>
</div> </div>
</div> </div>
...@@ -32,29 +44,32 @@ ...@@ -32,29 +44,32 @@
<!-- 托盘信息展示区域 --> <!-- 托盘信息展示区域 -->
<div class="tray-header"> <div class="tray-header">
<span class="tray-label">托盘</span> <span class="tray-label">托盘</span>
<span class="tray-id">TP-{{ trayInfo.id }}</span> <span class="tray-id">TP-{{ trayInfo.id || '--' }}</span>
</div> </div>
<div class="tray-info"> <div class="tray-info">
<div class="info-row"> <div class="info-row">
<div class="info-label">所在柜体:</div> <div class="info-label">所在柜体:</div>
<div class="info-value">2号柜 - {{ trayInfo.layer }}</div> <div class="info-value">{{ modbusDeviceData.id }}号柜 - {{ trayInfo.layer.split("-")[1] || '--' }}</div>
</div> </div>
<div class="info-row"> <div class="info-row">
<div class="info-label">产品型号:</div> <div class="info-label">产品型号:</div>
<div class="info-value">{{ trayInfo.productModel }}</div> <div class="info-value">{{ trayInfo.productModel || '--' }}</div>
</div> </div>
<div class="info-row"> <div class="info-row">
<div class="info-label">当前状态:</div> <div class="info-label">当前状态:</div>
<div class="info-value" :class="trayInfo.statusClass">{{ trayInfo.status }}</div> <div class="info-value" :class="trayInfo.statusClass || 'status-idle'">
{{ trayInfo.status || '--' }}
</div>
</div> </div>
<div class="info-row"> <div class="info-row">
<div class="info-label">老化时长</div> <div class="info-label">老化开始时间</div>
<div class="info-value">{{ trayInfo.agingTime }} 小时</div> <div class="info-value">{{ formatDateTime(trayInfo.fAgingStartTime) || '--' }}</div>
</div> </div>
<div class="info-row"> <div class="info-row">
<div class="info-label">温度</div> <div class="info-label">老化时长</div>
<div class="info-value">{{ trayInfo.temperature }}°C</div> <div class="info-value aging-time">{{ agingTimeDisplay }}</div>
</div> </div>
</div> </div>
...@@ -75,43 +90,181 @@ ...@@ -75,43 +90,181 @@
</template> </template>
<script> <script>
import { queryByDepartmentId } from "@/api/storey/storey";
export default { export default {
name: "AgingLayer", name: "AgingLayer",
props: {
modbusDeviceData: {
type: Object,
default: () => ({})
}
},
data() { data() {
return { return {
layers: Array.from({ length: 10 }, (_, i) => ({ layers: [],
name: `第${i + 1}层`, trayInfo: {},
})), agingTimer: null,
trayInfo: { agingStartTime: null,
id: "202307-015", agingTimeDisplay: "00:00:00"
layer: "第3层",
productModel: "PQC-1002",
status: "运行中",
statusClass: "status-running",
agingTime: "24",
temperature: "65",
},
}; };
}, },
watch: {
modbusDeviceData: {
immediate: true,
handler(newVal) {
if (newVal && newVal.id) {
this.loadCabinetData(newVal.id);
}
}
},
trayInfo: {
deep: true,
handler(newVal) {
if (newVal.fAgingStartTime) {
this.startAgingTimer(newVal.fAgingStartTime);
} else {
this.stopAgingTimer();
this.agingTimeDisplay = "00:00:00";
}
}
}
},
mounted() {
if (this.modbusDeviceData && this.modbusDeviceData.id) {
this.loadCabinetData(this.modbusDeviceData.id);
}
},
beforeDestroy() {
this.stopAgingTimer();
},
methods: { methods: {
// 格式化日期时间
formatDateTime(dateString) {
if (!dateString) return "--";
const date = new Date(dateString);
return date.toLocaleString('zh-CN', {
year: 'numeric',
month: '2-digit',
day: '2-digit',
hour: '2-digit',
minute: '2-digit',
second: '2-digit'
});
},
// 启动老化计时器
startAgingTimer(startTime) {
this.stopAgingTimer();
this.agingStartTime = new Date(startTime);
// 立即更新一次
this.updateAgingTime();
// 每秒更新一次
this.agingTimer = setInterval(() => {
this.updateAgingTime();
}, 1000);
},
// 更新老化时间显示
updateAgingTime() {
if (!this.agingStartTime) return;
const now = new Date();
const diff = Math.floor((now - this.agingStartTime) / 1000); // 秒数
const hours = Math.floor(diff / 3600);
const minutes = Math.floor((diff % 3600) / 60);
const seconds = diff % 60;
this.agingTimeDisplay = `${this.padZero(hours)}:${this.padZero(minutes)}:${this.padZero(seconds)}`;
},
// 停止老化计时器
stopAgingTimer() {
if (this.agingTimer) {
clearInterval(this.agingTimer);
this.agingTimer = null;
}
},
// 数字补零
padZero(num) {
return num.toString().padStart(2, '0');
},
// 加载柜子详情数据
loadCabinetData(id) {
queryByDepartmentId(id).then(res => {
if (res.code === 200 && res.data.length > 0) {
this.layers = res.data.map((item, i) => ({
...item,
name: item.fStoreyCode,
id: item.fStoreyId,
layer: item.fStoreyCode,
productModel: `PQC-${1000 + i}`,
status: this.getStatusText(item.fStatus),
statusClass: this.getStatusClass(item.fStatus),
fAgingStartTime: item.fAgingStartTime
}));
// 默认选中第一层
if (this.layers.length > 0) {
this.selectLayer(0);
}
} else {
this.$message.error("加载层数据失败");
console.error("加载数据失败:", res.message);
}
}).catch(error => {
this.$message.error("加载数据异常");
console.error("加载数据异常:", error);
});
},
// 返回老化柜看板
goBack() {
this.$emit('go-back');
},
// 选择层
selectLayer(index) { selectLayer(index) {
// 模拟选择不同层时托盘信息的变化 this.trayInfo = { ...this.layers[index] };
this.trayInfo = {
id: `202307-${(index + 1).toString().padStart(3, '0')}`,
layer: this.layers[index].name,
productModel: `PQC-${1000 + index}`,
status: index % 3 === 0 ? "待机" : "运行中",
statusClass: index % 3 === 0 ? "status-idle" : "status-running",
agingTime: (24 + index).toString(),
temperature: (65 + index).toString(),
};
}, },
// 操作按钮方法
loadTray() { loadTray() {
this.$message.success("上料操作已执行"); this.$message.success("上料操作已执行");
}, },
powerOn() { powerOn() {
this.$message.success("上电操作已执行"); this.$message.success("上电操作已执行");
}, },
// 获取状态类名
getStatusClass(status) {
switch (status) {
case '0': return 'status-idle';
case '1': return 'status-running';
case '2': return 'status-fault';
case '3': return 'status-power_outage';
default: return 'status-idle';
}
},
// 获取状态文本
getStatusText(status) {
switch (status) {
case '0': return '空闲';
case '1': return '运行中';
case '2': return '故障';
case '3': return '断电';
default: return '未知状态';
}
},
// 获取层样式
getLayerStyle(index) { getLayerStyle(index) {
const verticalSpacing = 70; const verticalSpacing = 70;
return { return {
...@@ -120,12 +273,63 @@ export default { ...@@ -120,12 +273,63 @@ export default {
height: '60px', height: '60px',
zIndex: index, zIndex: index,
}; };
}, }
} }
}; };
</script> </script>
<style scoped> <style scoped>
/* 状态背景色 */
.status-idle {
background: linear-gradient(to bottom, rgba(255, 255, 255, 0.15), rgba(0, 0, 0, 0.2));
border-color: rgba(100, 180, 255, 0.6) !important;
}
.status-running {
background: linear-gradient(to bottom, rgba(100, 255, 100, 0.15), rgba(0, 80, 0, 0.2)) !important;
border-color: rgba(100, 255, 100, 0.8) !important;
}
.status-fault {
background: linear-gradient(to bottom, rgba(255, 100, 100, 0.15), rgba(80, 0, 0, 0.2)) !important;
border-color: rgba(255, 100, 100, 0.8) !important;
}
.status-power_outage {
background: linear-gradient(to bottom, rgba(150, 150, 150, 0.15), rgba(50, 50, 50, 0.2)) !important;
border-color: rgba(150, 150, 150, 0.8) !important;
}
/* 层内容样式 */
.layer-content {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
width: 100%;
height: 100%;
}
.layer-name {
font-size: 16px;
font-weight: bold;
margin-bottom: 4px;
}
.layer-status {
font-size: 12px;
opacity: 0.9;
}
/* 老化时间样式 */
.aging-time {
font-family: 'Courier New', monospace;
font-size: 16px;
letter-spacing: 1px;
color: #64ffaa;
}
/* 其他样式保持不变(原有样式) */
.screen-container { .screen-container {
color: white; color: white;
font-family: 'Arial', sans-serif; font-family: 'Arial', sans-serif;
...@@ -139,7 +343,6 @@ export default { ...@@ -139,7 +343,6 @@ export default {
background: radial-gradient(circle at center, #041740 0%, #030b2a 70%); background: radial-gradient(circle at center, #041740 0%, #030b2a 70%);
} }
/* 新增:内容容器 */
.content-container { .content-container {
display: flex; display: flex;
width: 100%; width: 100%;
...@@ -149,7 +352,6 @@ export default { ...@@ -149,7 +352,6 @@ export default {
padding: 0 50px; padding: 0 50px;
} }
/* 柜体容器 - 使用Flexbox对齐 */
.cabinet-body { .cabinet-body {
display: flex; display: flex;
align-items: flex-start; align-items: flex-start;
...@@ -157,7 +359,6 @@ export default { ...@@ -157,7 +359,6 @@ export default {
position: relative; position: relative;
} }
/* 标题样式 */
.title { .title {
font-size: 24px; font-size: 24px;
font-weight: bold; font-weight: bold;
...@@ -198,7 +399,6 @@ export default { ...@@ -198,7 +399,6 @@ export default {
.layer { .layer {
position: absolute; position: absolute;
left: 0; left: 0;
background: linear-gradient(to bottom, rgba(255,255,255,0.15), rgba(0,0,0,0.2));
border: 1px solid rgba(100, 180, 255, 0.6); border: 1px solid rgba(100, 180, 255, 0.6);
box-shadow: box-shadow:
0 4px 12px rgba(0, 0, 0, 0.6), 0 4px 12px rgba(0, 0, 0, 0.6),
...@@ -229,7 +429,6 @@ export default { ...@@ -229,7 +429,6 @@ export default {
rgba(100, 180, 255, 0.4); rgba(100, 180, 255, 0.4);
} }
/* 右侧内容区域 */
.right-content { .right-content {
flex: 1; flex: 1;
margin-left: 80px; margin-left: 80px;
...@@ -318,7 +517,7 @@ export default { ...@@ -318,7 +517,7 @@ export default {
} }
.info-label { .info-label {
width: 100px; width: 130px;
color: #64c8ff; color: #64c8ff;
font-weight: bold; font-weight: bold;
} }
...@@ -338,6 +537,16 @@ export default { ...@@ -338,6 +537,16 @@ export default {
font-weight: bold; font-weight: bold;
} }
.status-fault {
color: #ff3d3d;
font-weight: bold;
}
.status-power_outage {
color: #9e9e9e;
font-weight: bold;
}
.tray-actions { .tray-actions {
display: flex; display: flex;
justify-content: flex-end; justify-content: flex-end;
...@@ -389,7 +598,6 @@ export default { ...@@ -389,7 +598,6 @@ export default {
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='white'%3E%3Cpath d='M16 9v4.66l-3.5 3.51V19h-1v-1.83L8 13.65V9h8m0-6h-2v4h-4V3H8v4h-.01C6.9 6.99 6 7.89 6 8.98v5.52L9.5 18v3h5v-3l3.5-3.5V9c0-1.1-.9-2-2-2V3z'/%3E%3C/svg%3E"); background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='white'%3E%3Cpath d='M16 9v4.66l-3.5 3.51V19h-1v-1.83L8 13.65V9h8m0-6h-2v4h-4V3H8v4h-.01C6.9 6.99 6 7.89 6 8.98v5.52L9.5 18v3h5v-3l3.5-3.5V9c0-1.1-.9-2-2-2V3z'/%3E%3C/svg%3E");
} }
/* 添加光效 */
.glow-effect { .glow-effect {
position: absolute; position: absolute;
top: 0; top: 0;
...@@ -400,4 +608,19 @@ export default { ...@@ -400,4 +608,19 @@ export default {
pointer-events: none; pointer-events: none;
z-index: -1; z-index: -1;
} }
.back-button {
cursor: pointer;
margin-bottom: 20px;
width: 145px;
color: #409EFF;
display: inline-flex;
align-items: center;
transition: all 0.3s;
}
.back-button:hover {
color: #66b1ff;
transform: translateX(-5px);
}
</style> </style>
<template>
<div class="tray-binding-container">
<!-- 头部区域 -->
<div class="header">
<h1><i class="fas fa-qrcode"></i> 托盘数据绑定系统</h1>
<p>通过扫描枪扫描设备条码,自动将设备绑定到托盘指定位置</p>
</div>
<!-- 托盘ID绑定区域 -->
<div class="tray-id-section">
<div class="tray-display">
<div class="tray-label">当前托盘:</div>
<div class="tray-id">{{ trayId || 'TP-未绑定' }}</div>
</div>
<div class="scan-section">
<input
type="text"
class="scan-input"
placeholder="扫描托盘条码..."
v-model="trayInput"
@keyup.enter="setTrayId"
ref="trayInput"
>
<button class="bind-btn" @click="setTrayId">
<i class="fas fa-barcode"></i> 绑定托盘
</button>
</div>
</div>
<!-- 设备矩阵区域 -->
<div class="devices-grid-container">
<h2 class="grid-title"><i class="fas fa-microchip"></i> 设备绑定矩阵 (8×9)</h2>
<div class="devices-grid">
<div
v-for="(device, index) in devices"
:key="index"
class="device-cell"
:class="{
'active': activeCell === index,
'empty': !device.value
}"
@click="setActiveCell(index)"
>
<div class="device-id">
{{ device.value || '+' }}
</div>
<div class="position-indicator">
{{ getPositionLabel(index) }}
</div>
</div>
</div>
</div>
<!-- 设备扫描区域 -->
<div class="scan-section" style="align-items: center;">
<input
type="text"
class="scan-input"
placeholder="扫描设备条码..."
v-model="deviceInput"
@keyup.enter="addDevice"
ref="deviceInput"
>
<div class="status-bar">
<i class="fas fa-info-circle"></i>
已绑定设备: <span class="filled-count">{{ filledCount }}</span>/72
</div>
</div>
<!-- 操作按钮区域 -->
<div class="binding-controls">
<button class="reset-btn" @click="resetAll">
<i class="fas fa-redo"></i> 重置所有
</button>
<button
class="bind-btn"
@click="bindTray"
:disabled="filledCount === 0 || !trayId"
>
<i class="fas fa-paper-plane"></i> 提交绑定
</button>
</div>
<!-- 使用说明区域 -->
<div class="instructions">
<h3><i class="fas fa-lightbulb"></i> 使用说明</h3>
<ul>
<li>1. 首先扫描<span class="highlight">托盘条码</span>并点击绑定托盘按钮</li>
<li>2. 点击矩阵中的单元格或按顺序扫描<span class="highlight">设备条码</span>(扫描枪自动识别)</li>
<li>3. 设备条码会自动填充到当前激活的单元格中</li>
<li>4. 可以手动点击任何单元格进行修改或重新扫描</li>
<li>5. 完成所有设备绑定后,点击<span class="highlight">提交绑定</span>按钮</li>
<li><i class="fas fa-bolt scanner-icon"></i> 提示:使用扫描枪时,请确保输入框获得焦点</li>
</ul>
</div>
</div>
</template>
<script>
import {queryByDepartmentId} from "@/api/storey/storey";
export default {
name: "TrayBinding",
data() {
return {
// 托盘ID
trayId: '',
trayInput: '',
// 设备矩阵数据 (8x9 = 72个设备)
devices: Array(72).fill().map((_, i) => ({
id: i + 1,
value: '',
row: Math.floor(i / 9) + 1,
col: (i % 9) + 1
})),
// 当前激活的单元格
activeCell: 0,
// 设备输入
deviceInput: ''
};
},
computed: {
// 计算已填充的设备数量
filledCount() {
return this.devices.filter(d => d.value).length;
}
},
methods: {
// 设置托盘ID
setTrayId() {
if (this.trayInput) {
this.trayId = `TP-${this.trayInput}`;
this.trayInput = '';
this.$nextTick(() => {
this.$refs.deviceInput.focus();
});
}
},
// 设置当前激活的单元格
setActiveCell(index) {
this.activeCell = index;
this.$nextTick(() => {
this.$refs.deviceInput.focus();
});
},
// 添加设备到当前激活单元格
addDevice() {
if (this.deviceInput) {
this.devices[this.activeCell].value = this.deviceInput;
this.deviceInput = '';
// 自动移动到下一个单元格
if (this.activeCell < 71) {
this.activeCell++;
} else {
// 如果已经是最后一个,则回到第一个
this.activeCell = 0;
}
}
},
// 获取位置标签 (行-列)
getPositionLabel(index) {
const row = Math.floor(index / 9) + 1;
const col = (index % 9) + 1;
return `${row}-${col}`;
},
// 绑定托盘
bindTray() {
const boundDevices = this.devices
.filter(d => d.value)
.map(d => ({
position: this.getPositionLabel(d.id - 1),
deviceId: d.value
}));
alert(`托盘 ${this.trayId} 绑定成功!\n绑定设备数量: ${boundDevices.length}`);
console.log('托盘ID:', this.trayId);
console.log('绑定设备:', boundDevices);
},
// 重置所有数据
resetAll() {
if (confirm('确定要重置所有数据吗?')) {
this.trayId = '';
this.trayInput = '';
this.deviceInput = '';
this.devices = this.devices.map(d => ({ ...d, value: '' }));
this.activeCell = 0;
this.$nextTick(() => {
this.$refs.trayInput.focus();
});
}
}
},
mounted() {
// 初始化时聚焦到托盘输入框
this.$nextTick(() => {
this.$refs.trayInput.focus();
});
}
};
</script>
<style scoped>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
}
body {
background: linear-gradient(135deg, #1a2a6c, #2c3e50);
min-height: 100vh;
display: flex;
justify-content: center;
align-items: center;
padding: 20px;
color: #fff;
}
.tray-binding-container {
width: 100%;
max-width: 1200px;
background: rgba(10, 20, 40, 0.85);
border-radius: 20px;
box-shadow: 0 15px 35px rgba(0, 0, 0, 0.5);
overflow: hidden;
border: 1px solid rgba(100, 180, 255, 0.3);
padding: 30px;
position: relative;
}
.header {
text-align: center;
margin-bottom: 30px;
position: relative;
}
.header h1 {
font-size: 2.8rem;
background: linear-gradient(to right, #4facfe, #00f2fe);
-webkit-background-clip: text;
color: transparent;
margin-bottom: 15px;
text-shadow: 0 0 15px rgba(79, 172, 254, 0.5);
}
.header p {
color: #a0d2ff;
font-size: 1.2rem;
max-width: 700px;
margin: 0 auto;
line-height: 1.6;
}
.tray-id-section {
display: flex;
justify-content: center;
align-items: center;
gap: 20px;
margin-bottom: 40px;
flex-wrap: wrap;
}
.tray-display {
background: rgba(0, 40, 80, 0.4);
border: 2px solid rgba(64, 158, 255, 0.6);
border-radius: 15px;
padding: 25px 40px;
display: flex;
align-items: center;
gap: 20px;
box-shadow: 0 5px 15px rgba(0, 0, 0, 0.3);
}
.tray-label {
font-size: 1.4rem;
color: #64c8ff;
font-weight: bold;
}
.tray-id {
font-size: 2.2rem;
font-weight: bold;
letter-spacing: 3px;
background: linear-gradient(to right, #64c8ff, #3a7bd5);
-webkit-background-clip: text;
color: transparent;
min-width: 250px;
text-align: center;
}
.scan-section {
display: flex;
flex-direction: column;
gap: 15px;
}
.scan-input {
background: rgba(0, 30, 60, 0.5);
border: 2px solid #409EFF;
border-radius: 50px;
padding: 15px 25px;
color: white;
font-size: 1.2rem;
width: 300px;
transition: all 0.3s;
}
.scan-input:focus {
outline: none;
border-color: #64c8ff;
box-shadow: 0 0 15px rgba(100, 200, 255, 0.5);
}
.devices-grid-container {
margin-bottom: 40px;
}
.grid-title {
text-align: center;
font-size: 1.8rem;
color: #64ffda;
margin-bottom: 25px;
text-shadow: 0 0 10px rgba(100, 255, 218, 0.3);
}
.devices-grid {
display: grid;
grid-template-columns: repeat(9, 1fr);
gap: 12px;
margin: 0 auto;
max-width: 900px;
}
.device-cell {
background: rgba(30, 60, 100, 0.4);
border: 2px solid rgba(64, 158, 255, 0.5);
border-radius: 8px;
aspect-ratio: 1;
display: flex;
justify-content: center;
align-items: center;
cursor: pointer;
transition: all 0.3s;
position: relative;
overflow: hidden;
}
.device-cell:hover {
transform: translateY(-5px);
box-shadow: 0 8px 15px rgba(0, 0, 0, 0.3);
border-color: #64c8ff;
background: rgba(40, 80, 140, 0.5);
}
.device-cell.active {
border-color: #64ffda;
background: rgba(100, 255, 218, 0.15);
box-shadow: 0 0 15px rgba(100, 255, 218, 0.4);
}
.device-id {
font-size: 1.1rem;
font-weight: bold;
color: #fff;
text-align: center;
word-break: break-all;
padding: 5px;
}
.device-cell.empty .device-id {
color: #a0d2ff;
font-size: 1.8rem;
opacity: 0.7;
}
.position-indicator {
position: absolute;
bottom: 5px;
right: 5px;
font-size: 0.7rem;
color: rgba(255, 255, 255, 0.6);
}
.binding-controls {
display: flex;
justify-content: center;
gap: 30px;
margin-top: 40px;
}
.bind-btn {
background: linear-gradient(to right, #11998e, #38ef7d);
color: white;
border: none;
padding: 18px 45px;
font-size: 1.4rem;
font-weight: bold;
border-radius: 50px;
cursor: pointer;
display: flex;
align-items: center;
gap: 15px;
transition: all 0.3s;
box-shadow: 0 5px 15px rgba(0, 0, 0, 0.3);
}
.bind-btn:hover {
transform: translateY(-5px);
box-shadow: 0 8px 20px rgba(17, 153, 142, 0.5);
}
.bind-btn:disabled {
background: linear-gradient(to right, #555, #777);
cursor: not-allowed;
transform: none;
box-shadow: none;
}
.reset-btn {
background: linear-gradient(to right, #ff416c, #ff4b2b);
color: white;
border: none;
padding: 18px 45px;
font-size: 1.4rem;
font-weight: bold;
border-radius: 50px;
cursor: pointer;
display: flex;
align-items: center;
gap: 15px;
transition: all 0.3s;
box-shadow: 0 5px 15px rgba(0, 0, 0, 0.3);
}
.reset-btn:hover {
transform: translateY(-5px);
box-shadow: 0 8px 20px rgba(255, 65, 108, 0.5);
}
.status-bar {
text-align: center;
margin-top: 30px;
padding: 15px;
background: rgba(0, 30, 60, 0.5);
border-radius: 10px;
font-size: 1.2rem;
color: #64ffda;
display: flex;
justify-content: center;
align-items: center;
gap: 15px;
}
.filled-count {
font-weight: bold;
color: #64c8ff;
font-size: 1.4rem;
}
.instructions {
background: rgba(0, 30, 60, 0.4);
border-radius: 15px;
padding: 25px;
margin-top: 40px;
border: 1px solid rgba(100, 200, 255, 0.3);
}
.instructions h3 {
color: #64ffda;
margin-bottom: 15px;
font-size: 1.5rem;
}
.instructions ul {
padding-left: 25px;
}
.instructions li {
margin-bottom: 10px;
line-height: 1.6;
color: #a0d2ff;
}
.scanner-icon {
color: #64ffda;
animation: pulse 2s infinite;
}
@keyframes pulse {
0% { opacity: 0.6; }
50% { opacity: 1; }
100% { opacity: 0.6; }
}
.highlight {
background: rgba(100, 255, 218, 0.2);
padding: 2px 5px;
border-radius: 4px;
}
/* 响应式设计 */
@media (max-width: 900px) {
.devices-grid {
grid-template-columns: repeat(6, 1fr);
max-width: 600px;
}
.tray-id-section {
flex-direction: column;
}
}
@media (max-width: 600px) {
.devices-grid {
grid-template-columns: repeat(4, 1fr);
max-width: 400px;
}
.tray-display {
padding: 15px 25px;
}
.tray-id {
font-size: 1.8rem;
min-width: 200px;
}
.bind-btn, .reset-btn {
padding: 15px 30px;
font-size: 1.2rem;
}
}
</style>
...@@ -7,7 +7,7 @@ ...@@ -7,7 +7,7 @@
<!-- 顶部导航栏 --> <!-- 顶部导航栏 -->
<div class="header"> <div class="header">
<div class="system-title-wrapper"> <div class="system-title-wrapper">
<div class="system-title">监控数字化视频管控平台</div> <div class="system-title">泽宏老化监控平台</div>
<div class="title-line"></div> <!-- 标题下的横线 --> <div class="title-line"></div> <!-- 标题下的横线 -->
</div> </div>
...@@ -23,7 +23,7 @@ ...@@ -23,7 +23,7 @@
<span class="icon">{{ item.icon }}</span> <span class="icon">{{ item.icon }}</span>
<span class="text">{{ item.text }}</span> <span class="text">{{ item.text }}</span>
<!-- 选中时显示的底部线条 --> <!-- 选中时显示的底部线条 -->
<div v-if="selectedMenu === index" class="active-line"></div> <div v-if="(selectedMenu === 3 ? 0 : selectedMenu) === index" class="active-line"></div>
</div> </div>
</div> </div>
...@@ -37,7 +37,15 @@ ...@@ -37,7 +37,15 @@
<div class="content-area"> <div class="content-area">
<div class="scroll-container"> <div class="scroll-container">
<transition name="fade" mode="out-in"> <transition name="fade" mode="out-in">
<component :is="currentComponent" /> <!-- <component :is="currentComponent" />-->
<!-- 在动态组件中传递参数 -->
<!-- 添加返回事件处理 -->
<component
:is="currentComponent"
:modbusDeviceData="modbusDeviceData"
@cabinet-click="handleCabinetClick"
@go-back="handleGoBack"
/>
</transition> </transition>
</div> </div>
</div> </div>
...@@ -49,29 +57,50 @@ ...@@ -49,29 +57,50 @@
import AgingCabinetBoard from './components/AgingCabinetBoard.vue' import AgingCabinetBoard from './components/AgingCabinetBoard.vue'
import AgingLayer from './components/AgingLayer' import AgingLayer from './components/AgingLayer'
import RealTimeData from './components/RealTimeData' import RealTimeData from './components/RealTimeData'
import TrayBinding from "@/views/screen/components/TrayBinding";
export default { export default {
components: { components: {
AgingCabinetBoard, AgingCabinetBoard,
AgingLayer, AgingLayer,
RealTimeData RealTimeData,
TrayBinding
}, },
data() { data() {
return { return {
selectedMenu: 0, // 默认选中第一个 selectedMenu: 0, // 默认选中第一个
menuItems: [ menuItems: [
{ icon: '📷', text: '老化柜看板', component: 'AgingCabinetBoard' }, { icon: '📷', text: '老化柜看板', component: 'AgingCabinetBoard' },
{ icon: '📊', text: '老化层看板', component: 'AgingLayer' }, { icon: '📊', text: '托盘绑定', component: 'TrayBinding' },
{ icon: '📦', text: '实时数据', component: 'RealTimeData' } { icon: '📦', text: '实时数据', component: 'RealTimeData' },
], ],
selectMenuItems: [
{ icon: '📷', text: '老化柜看板', component: 'AgingCabinetBoard' },
{ icon: '📊', text: '托盘绑定', component: 'TrayBinding' },
{ icon: '📦', text: '实时数据', component: 'RealTimeData' },
{ icon: '🔍', text: '老化层详情', component: 'AgingLayer' } // 新增菜单项
],
modbusDeviceData: null // 存储传递的柜子ID
} }
}, },
computed: { computed: {
currentComponent() { currentComponent() {
return this.menuItems[this.selectedMenu].component; return this.selectMenuItems[this.selectedMenu].component;
}, },
}, },
methods: { methods: {
// 处理返回事件
handleGoBack() {
// 返回老化柜看板(菜单索引 0)
this.selectedMenu = 0;
this.modbusDeviceData = null;
},
// 接收从 AgingCabinetBoard 传递的柜子ID
handleCabinetClick(modbusDeviceData) {
this.modbusDeviceData = modbusDeviceData;
// 切换到 AgingLayer 组件(对应菜单索引 3)
this.selectedMenu = 3;
},
selectMenu(index) { selectMenu(index) {
// if(index === 3) { // if(index === 3) {
// this.goToAdmin(); // this.goToAdmin();
...@@ -87,10 +116,6 @@ export default { ...@@ -87,10 +116,6 @@ export default {
goToAdmin() { goToAdmin() {
this.$router.push('/index') // 或者 '/dashboard' 如果已经登录 this.$router.push('/index') // 或者 '/dashboard' 如果已经登录
}, },
handleMenuSelect(index) {
// 可以在这里根据 index 做更多操作,比如加载数据等
this.activeMenu = index;
}
} }
} }
</script> </script>
...@@ -152,6 +177,18 @@ export default { ...@@ -152,6 +177,18 @@ export default {
margin-top: 10px; margin-top: 10px;
} }
/* 选中时的底部线条 */
.active-line {
position: absolute;
bottom: -6px; /* 略微低于盒子 */
left: 0;
right: 0;
height: 2px;
background: linear-gradient(to right, transparent, #409EFF, transparent);
animation: lineGrow 0.3s forwards;
}
/* 菜单组 */ /* 菜单组 */
.menu-group { .menu-group {
display: flex; display: flex;
...@@ -166,7 +203,8 @@ export default { ...@@ -166,7 +203,8 @@ export default {
gap: 8px; gap: 8px;
padding: 10px 20px; padding: 10px 20px;
position: relative; position: relative;
transform: rotate(-2deg); /* 整体倾斜一点,模拟菱形外观 */ /* 整体倾斜一点,模拟菱形外观 */
transform: rotate(0deg);
font-family: Arial, sans-serif; font-family: Arial, sans-serif;
color: white; color: white;
z-index: 1; z-index: 1;
...@@ -175,7 +213,7 @@ export default { ...@@ -175,7 +213,7 @@ export default {
} }
.diamond-box:hover { .diamond-box:hover {
transform: rotate(-2deg) scale(1.05); transform: rotate(0deg) scale(1.05);
} }
.diamond-box::before { .diamond-box::before {
...@@ -186,7 +224,7 @@ export default { ...@@ -186,7 +224,7 @@ export default {
right: 0; right: 0;
bottom: 0; bottom: 0;
border: 1px solid #409EFF; border: 1px solid #409EFF;
transform: rotate(2deg); transform: rotate(0deg);
box-sizing: border-box; box-sizing: border-box;
z-index: -1; z-index: -1;
background-color: rgba(64, 158, 255, 0.1); background-color: rgba(64, 158, 255, 0.1);
...@@ -226,19 +264,9 @@ export default { ...@@ -226,19 +264,9 @@ export default {
/* 动画效果 */ /* 动画效果 */
@keyframes lineMove { @keyframes lineMove {
0% { transform: scaleX(1); } 0% { transform: scaleX(1); }
100% { transform: scaleX(0.9); } 100% { transform: scaleX(1); }
} }
/* 选中时的底部线条 */
.active-line {
position: absolute;
bottom: -6px; /* 略微低于盒子 */
left: 0;
right: 0;
height: 2px;
background: linear-gradient(to right, transparent, #409EFF, transparent);
animation: lineGrow 0.3s forwards;
}
/* 动画:从中间向两边展开 */ /* 动画:从中间向两边展开 */
@keyframes lineGrow { @keyframes lineGrow {
...@@ -275,7 +303,7 @@ export default { ...@@ -275,7 +303,7 @@ export default {
@keyframes fadeIn { @keyframes fadeIn {
from { from {
opacity: 0; opacity: 0;
transform: translateY(10px); transform: translateY(0);
} }
to { to {
opacity: 1; opacity: 1;
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment