diff --git a/aucma-production/andon.md b/aucma-production/andon.md index 3aebbdd..b3ae278 100644 --- a/aucma-production/andon.md +++ b/aucma-production/andon.md @@ -167,6 +167,45 @@ - `isFlag`:是否有效; - `remark`:备注。 +**展示字段配置(displayFields)写法示例** +用于前端大屏动态决定列显示,可用字符串、JSON 数组或对象: + +```text +1) 逗号分隔字符串(两张表共用同列) + "callCode,stationCode,eventStatus,priority,createTime" + +2) JSON 数组(两张表共用同列,可设置宽度/对齐) + [ + "callCode", + "stationCode", + { "field": "eventStatus", "label": "状态", "align": "center" } + ] + +3) JSON 对象区分“进行中”和“已关闭”列 +{ + "activeFields": [ + "callCode", + "stationCode", + { "field": "eventStatus", "label": "状态", "align": "center" }, + "priority", + "createTime" + ], + "closedFields": [ + "callCode", + "stationCode", + "eventStatus", + { "field": "responseEndTime", "label": "完成时间" }, + { "field": "resolution", "label": "解决措施" } + ] +} +``` + +解析逻辑(前端 `views/board/andonBoard/index.vue`): +- 为空或解析失败时,回退默认列(进行中:callCode/callTypeCode/stationCode/deviceCode/eventStatus/priority/createTime/description;已关闭:callCode/callTypeCode/stationCode/deviceCode/eventStatus/responseEndTime/resolution/cancelReason)。 +- 纯字符串或数组:进行中与已关闭共用同一列集合。 +- 对象:优先读取 `activeFields` / `closedFields`(或 `active` / `closed`),否则回退到 `fields` 或整对象键值。 +- 每列可附加 `label`、`width`、`align`;缺省时按字段名映射默认中文标题。 + 前端看板程序可根据 `boardCode` 读取配置,再按产线/工位范围实时拉取 AndonEvent 数据进行可视化展示。 --- diff --git a/aucma-production/src/main/java/com/aucma/production/controller/AndonBoardController.java b/aucma-production/src/main/java/com/aucma/production/controller/AndonBoardController.java new file mode 100644 index 0000000..dc8d7cc --- /dev/null +++ b/aucma-production/src/main/java/com/aucma/production/controller/AndonBoardController.java @@ -0,0 +1,218 @@ +package com.aucma.production.controller; + +import com.aucma.common.annotation.Anonymous; +import com.aucma.common.constant.AnDonConstants; +import com.aucma.common.core.domain.AjaxResult; +import com.aucma.common.utils.DateUtils; +import com.aucma.production.domain.AndonBoardConfig; +import com.aucma.production.domain.AndonEvent; +import com.aucma.production.service.IAndonBoardConfigService; +import com.aucma.production.service.IAndonEventService; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.RestController; + +import java.util.ArrayList; +import java.util.Comparator; +import java.util.Date; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +/** + * 安灯看板展示接口(对外展示使用) + */ +@RestController +@RequestMapping("/production/andonBoard") +public class AndonBoardController { + + /** 安灯看板配置服务 */ + @Autowired + private IAndonBoardConfigService andonBoardConfigService; + + /** 安灯事件服务 */ + @Autowired + private IAndonEventService andonEventService; + + /** + * 根据看板编码获取看板配置与事件数据。 + * + * 说明: + * - boardCode 作为对外展示的“唯一入口参数”; + * - 事件范围由看板配置的 productLineCode / stationCode 决定; + * - 返回 activeEvents(待处理/处理中)与 closedEvents(已解决/已取消)两组数据,便于大屏展示。 + * - 接口使用 @Anonymous 标记,可免登录访问(大屏/外部展示使用)。 + */ + @Anonymous + @GetMapping("/view") + public AjaxResult view(@RequestParam String boardCode) { + // 1) 参数校验:boardCode 必填 + if (boardCode == null || boardCode.trim().isEmpty()) { + return AjaxResult.error("boardCode不能为空"); + } + + // 2) 查询启用的看板配置(同一编码可能存在多条记录,取最新一条) + AndonBoardConfig config = selectActiveBoardConfig(boardCode.trim()); + if (config == null) { + return AjaxResult.error("看板配置不存在或未启用:" + boardCode); + } + + // 3) 根据看板配置的范围(产线/工位)查询事件 + List allEvents = selectEventsByConfig(config); + + // 4) 统计数量 + 拆分进行中/已关闭事件 + Map stats = buildStats(allEvents); + List activeEvents = filterByStatus(allEvents, true); + List closedEvents = filterByStatus(allEvents, false); + + // 5) 排序:进行中按状态/优先级/创建时间;已关闭按结束时间/更新时间 + sortActiveEvents(activeEvents); + sortClosedEvents(closedEvents); + + // 为大屏限制历史记录数量(避免数据过多影响渲染) + if (closedEvents.size() > 20) { + closedEvents = new ArrayList<>(closedEvents.subList(0, 20)); + } + + // 6) 组装返回数据(大屏依赖 config / stats / activeEvents / closedEvents / serverTime) + Map data = new HashMap<>(); + data.put("config", config); + data.put("stats", stats); + data.put("activeEvents", activeEvents); + data.put("closedEvents", closedEvents); + data.put("serverTime", DateUtils.getNowDate()); + return AjaxResult.success(data); + } + + /** + * 查询指定看板编码下启用的配置记录。 + * 若存在多条启用记录,则按更新时间(优先)/创建时间倒序取最新一条。 + */ + private AndonBoardConfig selectActiveBoardConfig(String boardCode) { + AndonBoardConfig q = new AndonBoardConfig(); + q.setBoardCode(boardCode); + q.setIsFlag(AnDonConstants.FLAG_VALID); + List list = andonBoardConfigService.selectAndonBoardConfigList(q); + if (list == null || list.isEmpty()) { + return null; + } + + // 按更新时间优先、否则按创建时间,倒序排序,取第一条(最新配置) + list.sort((a, b) -> { + Date ta = (a.getUpdateTime() != null ? a.getUpdateTime() : a.getCreateTime()); + Date tb = (b.getUpdateTime() != null ? b.getUpdateTime() : b.getCreateTime()); + if (ta == null && tb == null) return 0; + if (ta == null) return 1; + if (tb == null) return -1; + return tb.compareTo(ta); + }); + return list.get(0); + } + + /** + * 根据看板配置的作用域查询事件列表。 + * 仅当配置字段非空时才作为过滤条件;为空表示不按该维度过滤。 + */ + private List selectEventsByConfig(AndonBoardConfig config) { + AndonEvent q = new AndonEvent(); + q.setIsFlag(AnDonConstants.FLAG_VALID); + // 产线范围过滤 + if (config.getProductLineCode() != null && !config.getProductLineCode().trim().isEmpty()) { + q.setProductLineCode(config.getProductLineCode()); + } + // 工位/工序范围过滤 + if (config.getStationCode() != null && !config.getStationCode().trim().isEmpty()) { + q.setStationCode(config.getStationCode()); + } + return andonEventService.selectAndonEventList(q); + } + + /** + * 构建看板统计数据。 + * 返回字段:pending / processing / resolved / cancelled / active / total。 + */ + private Map buildStats(List events) { + long pending = 0; + long processing = 0; + long resolved = 0; + long cancelled = 0; + if (events != null) { + for (AndonEvent e : events) { + if (e == null) continue; + String s = e.getEventStatus(); + if (AnDonConstants.EventStatus.PENDING.equals(s)) pending++; + else if (AnDonConstants.EventStatus.PROCESSING.equals(s)) processing++; + else if (AnDonConstants.EventStatus.RESOLVED.equals(s)) resolved++; + else if (AnDonConstants.EventStatus.CANCELLED.equals(s)) cancelled++; + } + } + Map m = new HashMap<>(); + m.put("pending", pending); + m.put("processing", processing); + m.put("resolved", resolved); + m.put("cancelled", cancelled); + m.put("active", pending + processing); + m.put("total", events == null ? 0L : (long) events.size()); + return m; + } + + /** + * 按状态将事件分为“进行中”和“已关闭”两组。 + * active=true:待处理/处理中;active=false:已解决/已取消(以及其他未知状态)。 + */ + private List filterByStatus(List events, boolean active) { + List list = new ArrayList<>(); + if (events == null) return list; + for (AndonEvent e : events) { + if (e == null) continue; + String s = e.getEventStatus(); + // 待处理/处理中 视为进行中 + boolean isActive = AnDonConstants.EventStatus.PENDING.equals(s) || AnDonConstants.EventStatus.PROCESSING.equals(s); + if (active == isActive) { + list.add(e); + } + } + return list; + } + + /** + * 进行中事件排序: + * 1) 状态(待处理优先于处理中) + * 2) 优先级(数值越小越紧急;空值排最后) + * 3) 创建时间(越早越靠前;空值排最后) + */ + private void sortActiveEvents(List events) { + if (events == null) return; + events.sort(Comparator + .comparingInt((AndonEvent e) -> statusOrder(e.getEventStatus())) + .thenComparing(e -> e.getPriority() == null ? Long.MAX_VALUE : e.getPriority()) + .thenComparing(AndonEvent::getCreateTime, Comparator.nullsLast(Date::compareTo)) + ); + } + + /** + * 已关闭事件排序: + * 1) 解决/结束时间 responseEndTime 倒序(最近关闭的在前;空值排最后) + * 2) 更新时间 updateTime 倒序(空值排最后) + */ + private void sortClosedEvents(List events) { + if (events == null) return; + events.sort(Comparator + .comparing(AndonEvent::getResponseEndTime, Comparator.nullsLast(Comparator.reverseOrder())) + .thenComparing(AndonEvent::getUpdateTime, Comparator.nullsLast(Comparator.reverseOrder())) + ); + } + + /** + * 状态排序权重:待处理(0) < 处理中(1) < 已解决(2) < 已取消(3)。 + */ + private int statusOrder(String status) { + if (AnDonConstants.EventStatus.PENDING.equals(status)) return 0; + if (AnDonConstants.EventStatus.PROCESSING.equals(status)) return 1; + if (AnDonConstants.EventStatus.RESOLVED.equals(status)) return 2; + if (AnDonConstants.EventStatus.CANCELLED.equals(status)) return 3; + return 99; + } +}