广丰卷烟厂数采质量分析系统
zhuguifei
2026-03-06 b12c9e77a6b6a7b410ac421c5a3d68da88823460
feat: 烟丝单柜产耗统计
已添加3个文件
已修改15个文件
1032 ■■■■ 文件已修改
RuoYi-Vue-Plus/ruoyi-modules/ruoyi-qa/src/main/java/org/dromara/qa/analy/domain/vo/StoreSilkDetailVo.java 55 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
RuoYi-Vue-Plus/ruoyi-modules/ruoyi-qa/src/main/java/org/dromara/qa/analy/domain/vo/StoreSilkInfoVo.java 16 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
RuoYi-Vue-Plus/ruoyi-modules/ruoyi-qa/src/main/java/org/dromara/qa/analy/service/impl/StoreSilkInfoServiceImpl.java 448 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
RuoYi-Vue-Plus/ruoyi-modules/ruoyi-qa/src/main/java/org/dromara/qa/md/domain/bo/MdShiftBo.java 5 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
RuoYi-Vue-Plus/ruoyi-modules/ruoyi-qa/src/main/java/org/dromara/qa/md/service/OracleShiftReader.java 21 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
RuoYi-Vue-Plus/开发日志.md 5 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-plus-soybean/src/typings/api/analy.store-silk.api.d.ts 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-plus-soybean/src/typings/api/qm.batch.api.d.ts 12 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-plus-soybean/src/views/analy/feed-match/modules/feed-match-operate-drawer.vue 30 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-plus-soybean/src/views/analy/hoister/modules/hoister-data-operate-drawer.vue 73 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-plus-soybean/src/views/analy/packer/modules/packer-data-operate-drawer.vue 44 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-plus-soybean/src/views/analy/store-silk/index.vue 120 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-plus-soybean/src/views/analy/store-silk/modules/store-silk-detail.vue 173 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-plus-soybean/src/views/analy/store-silk/modules/store-silk-operate-drawer.vue 8 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-plus-soybean/src/views/md/shift/modules/shift-operate-drawer.vue 8 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-plus-soybean/src/views/md/shift/modules/shift-search.vue 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-plus-soybean/src/views/qm/batch/modules/batch-operate-drawer.vue 8 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-plus-soybean/src/views/qm/batch/modules/batch-search.vue 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
RuoYi-Vue-Plus/ruoyi-modules/ruoyi-qa/src/main/java/org/dromara/qa/analy/domain/vo/StoreSilkDetailVo.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,55 @@
package org.dromara.qa.analy.domain.vo;
import lombok.Data;
import java.io.Serializable;
import java.util.Date;
/**
 * å‚¨ä¸æŸœäº§é‡æ˜Žç»† VO
 * ç”¨äºŽä¿å­˜æ¯æ¬¡ç»Ÿè®¡äº§é‡çš„子项(卷接/包装)
 */
@Data
public class StoreSilkDetailVo implements Serializable {
    private static final long serialVersionUID = 1L;
    /**
     * å–‚丝机号 (如 fs01)
     */
    private String fsNum;
    /**
     * å‚¨ä¸æŸœå· (如 1)
     */
    private String siloNum;
    /**
     * ç®¡é“号 (如 pipe01)
     */
    private String pipeNum;
    /**
     * æœºå°å· (如 1)
     */
    private String equNo;
    /**
     * ç­æ¬¡ä»£ç  (1-早班 2-中班)
     */
    private String shiftCode;
    /**
     * ç­æ¬¡å¼€å§‹æ—¶é—´ (包含日期)
     */
    private Date shiftStartTime;
    /**
     * ç­æ¬¡ç»“束时间 (包含日期)
     */
    private Date shiftEndTime;
    /**
     * è¯¥ç­æ¬¡å†…计算出的产量
     */
    private Double output;
}
RuoYi-Vue-Plus/ruoyi-modules/ruoyi-qa/src/main/java/org/dromara/qa/analy/domain/vo/StoreSilkInfoVo.java
@@ -1,8 +1,10 @@
package org.dromara.qa.analy.domain.vo;
import java.util.Date;
import java.util.List;
import org.dromara.qa.analy.domain.StoreSilkInfo;
import org.dromara.qa.md.domain.bo.MdShiftBo;
import cn.idev.excel.annotation.ExcelIgnoreUnannotated;
import cn.idev.excel.annotation.ExcelProperty;
import io.github.linpeilie.annotations.AutoMapper;
@@ -92,5 +94,19 @@
    @ExcelProperty(value = "柜子号(末位)")
    private String siloid;
    // å‡ºæ–™å¼€å§‹ç»“束时间跨越班次列表
    private List<MdShiftBo> distShiftList;
    private Double rollerOutput;
    private Double packerOutput;
    /**
     * å·æŽ¥æœºäº§é‡æ˜Žç»†
     */
    private List<StoreSilkDetailVo> rollerDetailList;
    /**
     * åŒ…装机产量明细
     */
    private List<StoreSilkDetailVo> packerDetailList;
}
RuoYi-Vue-Plus/ruoyi-modules/ruoyi-qa/src/main/java/org/dromara/qa/analy/service/impl/StoreSilkInfoServiceImpl.java
@@ -1,5 +1,8 @@
package org.dromara.qa.analy.service.impl;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.toolkit.BeanUtils;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.dromara.common.core.utils.MapstructUtils;
import org.dromara.common.core.utils.StringUtils;
import org.dromara.common.mybatis.core.page.TableDataInfo;
@@ -9,6 +12,15 @@
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.dromara.qa.analy.domain.FeedmatchTimeData;
import org.dromara.qa.analy.domain.PackerTimeData;
import org.dromara.qa.analy.domain.RollerTimeData;
import org.dromara.qa.analy.domain.vo.StoreSilkDetailVo;
import org.dromara.qa.analy.mapper.RollerTimeDataMapper;
import org.dromara.qa.md.domain.MdShift;
import org.dromara.qa.md.domain.bo.MdShiftBo;
import org.dromara.qa.md.mapper.MdShiftMapper;
import org.dromara.qa.md.service.OracleShiftReader;
import org.springframework.stereotype.Service;
import org.dromara.qa.analy.domain.bo.StoreSilkInfoBo;
import org.dromara.qa.analy.domain.vo.StoreSilkInfoVo;
@@ -17,9 +29,16 @@
import org.dromara.qa.analy.service.IStoreSilkInfoService;
import org.dromara.qa.analy.mapper.FeedmatchTimeDataMapper;
import java.util.List;
import java.util.Map;
import java.util.Collection;
import java.lang.reflect.Field;
import java.sql.Timestamp;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.*;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.time.ZoneId;
import java.time.format.DateTimeFormatter;
/**
 * å‚¨ä¸æŸœäº§é‡Service业务层处理
@@ -34,6 +53,10 @@
    private final StoreSilkInfoMapper baseMapper;
    private final FeedmatchTimeDataMapper feedmatchTimeDataMapper;
    private final RollerTimeDataMapper rollerTimeDataMapper;
    private final org.dromara.qa.analy.mapper.PackerTimeDataMapper packerTimeDataMapper;
    private final MdShiftMapper mdShiftMapper;
    private final OracleShiftReader oracleShiftReader;
    /**
     * æŸ¥è¯¢å‚¨ä¸æŸœäº§é‡
@@ -42,7 +65,7 @@
     * @return å‚¨ä¸æŸœäº§é‡
     */
    @Override
    public StoreSilkInfoVo queryById(Long id){
    public StoreSilkInfoVo queryById(Long id) {
        return baseMapper.selectVoById(id);
    }
@@ -61,19 +84,426 @@
        queryFeedmatchData(result);
        return TableDataInfo.build(result);
    }
    private void queryFeedmatchData(Page<StoreSilkInfoVo> page) {
        if (page == null || page.getRecords() == null || page.getRecords().isEmpty()) {
            return;
        }
        // æŸ¥è¯¢æ•°é‡‡ç³»ç»Ÿç­æ¬¡æ—¶é—´ï¼ˆç‹¬ç«‹ä½¿ç”¨ oracle æ•°æ®æºï¼‰
        List<MdShift> mdShifts = oracleShiftReader.listAll();
        List<StoreSilkInfoVo> storeSilkInfoList = page.getRecords();
        for (int i = 0; i < storeSilkInfoList.size(); i++) {
            //
            //储丝柜
            StoreSilkInfoVo storeSilkInfoVo = storeSilkInfoList.get(i);
            //出料开始时间
            Date distimebegin = storeSilkInfoVo.getDistimebegin();
            //出料结束时间
            Date distimeend = storeSilkInfoVo.getDistimeend();
            //储丝柜柜号
            String siloid = storeSilkInfoVo.getSiloid();
            if (StringUtils.isEmpty(siloid)) continue;
            int lastIndex = siloid.lastIndexOf("_");
            String containerNum = siloid.substring(lastIndex + 1);
            if (StringUtils.isEmpty(containerNum)) continue;
            //根据出料开始时间查询喂丝机->储丝柜->机台对应关系(feedmatch_time_data)
            Timestamp targetTime = new Timestamp(distimebegin.getTime() + 10 * 60 * 1000); // æŸ¥è¯¢å‡ºæ–™10分钟后的第一条记录,保证数据准确性
            LambdaQueryWrapper<FeedmatchTimeData> lqw = new LambdaQueryWrapper<>();
            lqw.ge(FeedmatchTimeData::getTime, targetTime)
                    .le(FeedmatchTimeData::getTime, distimeend) // ä¸èƒ½å¤§äºŽå‡ºæ–™ç»“束时间
                    .orderByAsc(FeedmatchTimeData::getTime)
                    .last("LIMIT 1");
            FeedmatchTimeData feedMatch = feedmatchTimeDataMapper.selectOne(lqw);
            if (feedMatch == null) {
                // TODO  æ·»åŠ æç¤º
                continue;
            }
            // feedMatch è½¬map  TODO é€†è½¬map需验证key是否会重复
            //fsRevMap是逆转map     key->喂丝机对应的储丝柜号   value-> fs + åºå·
            Map<String, String> fsRevMap = new HashMap<>();
            //pipeRevMap是逆转map   key->机组对应的喂丝机和管道  value-> pipe + åºå·
            Map<String, String> pipeRevMap = new HashMap<>();
            Map<String, String> pipeMap = new HashMap<>();
            Field[] fields = feedMatch.getClass().getDeclaredFields();
            for (Field field : fields) {
                field.setAccessible(true);
                Object value = null;
                try {
                    value = field.get(feedMatch);
                } catch (IllegalAccessException e) {
                    throw new RuntimeException(e);
                }
                if (field.getName().startsWith("fs") && value != null) {
                    fsRevMap.put(value.toString(), field.getName());
                } else if (field.getName().startsWith("pipe") && value != null) {
                    pipeRevMap.put(value.toString(), field.getName());
                    pipeMap.put(field.getName(),value.toString());
                }
            }
            // æ ¹æ®å‚¨ä¸æŸœå·èŽ·å–å–‚ä¸æœºå·
            String fsNum = fsRevMap.get(containerNum);
            if (StringUtils.isEmpty(fsNum)) {
                // TODO   å–‚丝机号空返回信息
                continue;
            }
            if (pipeRevMap.isEmpty()) {
                // TODO   ç®¡é“号空返回信息
                continue;
            }
            // List存->  å–‚丝机对应的机组(如 pipe01 pipe02 ä»£è¡¨1#、2#卷接机组)
            List<String> pipeList = new ArrayList<>();
            for (Map.Entry<String, String> entry : pipeRevMap.entrySet()) {
                //fsNum第三位是喂丝机序号
                if (entry.getKey().length() > 1 && entry.getKey().startsWith(fsNum.substring(2, 3))) {
                    pipeList.add(entry.getValue());
                }
            }
            if (pipeList.isEmpty()) {
                //TODO æ·»åŠ æç¤º
                continue;
            }
            // æ ¹æ®å‡ºæ–™å¼€å§‹ç»“束时间,查询该柜料在哪几个班次生产
            List<MdShiftBo> distShiftList = calcShiftSpans(distimebegin, distimeend, mdShifts);
            storeSilkInfoVo.setDistShiftList(distShiftList);
            if (distShiftList.isEmpty()) continue;
            //查询日期和班次内卷接机组的产量
            ZoneId zone = ZoneId.systemDefault();
            // å·åŒ…产量统计
            Double rollerOutput = 0.0;
            Double packerOutput = 0.0;
            // æ˜Žç»†åˆ—表
            List<StoreSilkDetailVo> rollerDetailList = new ArrayList<>();
            List<StoreSilkDetailVo> packerDetailList = new ArrayList<>();
            for (int s = 0; s < distShiftList.size(); s++) {
                MdShiftBo shiftBo = distShiftList.get(s);
                if (shiftBo.getDay() == null || StringUtils.isEmpty(shiftBo.getCode())) {
                    continue;
                }
                String shift = shiftBo.getCode();
                // è§£æžç­æ¬¡æ—¶é—´
                // 1. èŽ·å–ç­æ¬¡é…ç½®çš„å¼€å§‹å’Œç»“æŸæ—¶é—´å­—ç¬¦ä¸² (格式如 "07:30:00")
                String stimStr = shiftBo.getStim();
                String etimStr = shiftBo.getEtim();
                if (StringUtils.isEmpty(stimStr) || StringUtils.isEmpty(etimStr)) {
                    continue;
                }
                // 2. è¡¥å…¨ç§’数(如果配置仅为 HH:mm)
                if (stimStr.length() == 5) stimStr += ":00";
                if (etimStr.length() == 5) etimStr += ":00";
                // 3. ç»“合日期解析为 LocalDateTime
                // æ³¨æ„ï¼šshiftBo.getDay() æ˜¯è¯¥ç­æ¬¡çš„归属日期
                String dateStr = shiftBo.getDay().toInstant().atZone(zone).toLocalDate().toString();
                DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
                LocalDateTime shiftStart = LocalDateTime.parse(dateStr + " " + stimStr, formatter);
                LocalDateTime shiftEnd = LocalDateTime.parse(dateStr + " " + etimStr, formatter);
                // 4. å¤„理跨天班次:如果 ç»“束时间 <= å¼€å§‹æ—¶é—´ï¼Œè¯´æ˜Žè·¨å¤©ï¼Œç»“束时间需 +1 å¤©
                if (!shiftEnd.isAfter(shiftStart)) {
                    shiftEnd = shiftEnd.plusDays(1);
                }
                // è½¬æ¢ Date å¯¹è±¡ç”¨äºŽæ¯”较(兼容现有逻辑)
                Date stimDate = Date.from(shiftStart.atZone(zone).toInstant());
                Date etimDate = Date.from(shiftEnd.atZone(zone).toInstant());
                // è®¡ç®—班次结束前10分钟的时间点
                String shiftEndStr = shiftEnd.format(formatter);
                String tenMinBeforeShiftEnd = shiftEnd.minusMinutes(10).format(formatter);
                // æ ¹æ®å·æŽ¥æœºç»„和班次获取产量(pipeList = å¤šå°‘个机组)
                for (int j = 0; j < pipeList.size(); j++) {
                    String pipe = pipeList.get(j);
                    // æå–机组号
                    String equNo = pipe.replaceAll("\\D+", "");
                    //管道号
                    String channel = pipeMap.get("pipe" + equNo);
                    if (channel != null && channel.length() > 0) {
                        channel = channel.substring(channel.length() - 1);
                    }else {
                        channel = "";
                    }
                    //注意4号管道以后对应的机组需要+1,机组4号空缺跳到5号
                    try {
                        int equ = Integer.parseInt(equNo);
                        equNo = String.format("%02d", equ >= 4 ? equ + 1 : equ);
                    } catch (Exception e) {
                        e.printStackTrace();
                        // TODO æ·»åŠ æç¤º
                        continue;
                    }
                    // æ‹¼æŽ¥å·æŽ¥æœºçš„key,中间 "1" ä»£è¡¨å·æŽ¥æœºï¼Œæ ¼å¼ï¼šç­æ¬¡ + "1" + æœºç»„号
                    String key = shift + "1" + equNo;
                    // æ‹¼æŽ¥åŒ…装机的key,中间 "2" ä»£è¡¨åŒ…装机,格式:班次 + "2" + æœºç»„号
                    String packerKey = shift + "2" + equNo;
                    // ================= å·æŽ¥æœºäº§é‡ç»Ÿè®¡ =================
                    Double currentRollerOutput = 0.0;
                    // 1. æŸ¥è¯¢ç­æ¬¡ç»“束时的产量快照
                    LambdaQueryWrapper<RollerTimeData> rlqw = new LambdaQueryWrapper<>();
                    rlqw.le(RollerTimeData::getTime, shiftEndStr)
                            .eq(RollerTimeData::getKey, key)
                            .ge(RollerTimeData::getTime, tenMinBeforeShiftEnd)
                            .orderByDesc(RollerTimeData::getTime)
                            .isNotNull(RollerTimeData::getQty)
                            .gt(RollerTimeData::getQty, 0)
                            .last("LIMIT 1");
                    RollerTimeData rData = rollerTimeDataMapper.selectOne(rlqw);
                    if (rData != null) {
                        // åˆå§‹äº§é‡è®¾ä¸ºç­æ¬¡ç»“束时的累计值
                        currentRollerOutput += rData.getQty();
                        // 2. å¤„理“扣头”:出料开始时间 > ç­æ¬¡å¼€å§‹æ—¶é—´
                        if (distimebegin.after(stimDate)) {
                            LocalDateTime distBeginTime = LocalDateTime.ofInstant(distimebegin.toInstant(), zone);
                            String distBeginStr = distBeginTime.format(formatter);
                            String tenMinBeforeDistBegin = distBeginTime.minusMinutes(10).format(formatter);
                            LambdaQueryWrapper<RollerTimeData> beginRlqw = new LambdaQueryWrapper<>();
                            beginRlqw.le(RollerTimeData::getTime, distBeginStr)
                                    .eq(RollerTimeData::getKey, key)
                                    .ge(RollerTimeData::getTime, tenMinBeforeDistBegin)
                                    .orderByDesc(RollerTimeData::getTime)
                                    .isNotNull(RollerTimeData::getQty)
                                    .gt(RollerTimeData::getQty, 0)
                                    .last("LIMIT 1");
                            RollerTimeData rBeginData = rollerTimeDataMapper.selectOne(beginRlqw);
                            if (rBeginData != null) {
                                currentRollerOutput -= rBeginData.getQty();
                            }
                        }
                        // 3. å¤„理“扣尾”:出料结束时间 < ç­æ¬¡ç»“束时间
                        if (etimDate.after(distimeend)) {
                            LocalDateTime distEndTime = LocalDateTime.ofInstant(distimeend.toInstant(), zone);
                            String distEndStr = distEndTime.format(formatter);
                            String tenMinBeforeDistEnd = distEndTime.minusMinutes(10).format(formatter);
                            LambdaQueryWrapper<RollerTimeData> endRlqw = new LambdaQueryWrapper<>();
                            endRlqw.le(RollerTimeData::getTime, distEndStr)
                                    .eq(RollerTimeData::getKey, key)
                                    .ge(RollerTimeData::getTime, tenMinBeforeDistEnd)
                                    .orderByDesc(RollerTimeData::getTime)
                                    .isNotNull(RollerTimeData::getQty)
                                    .gt(RollerTimeData::getQty, 0)
                                    .last("LIMIT 1");
                            RollerTimeData rEndData = rollerTimeDataMapper.selectOne(endRlqw);
                            if (rEndData != null) {
                                double qtyDelta = rData.getQty() - rEndData.getQty();
                                if (qtyDelta > 0) {
                                    currentRollerOutput -= qtyDelta;
                                }
                            }
                        }
                    }
                    // ç´¯è®¡æ€»äº§é‡
                    rollerOutput += currentRollerOutput;
                    // è®°å½•卷接机明细
                    if (currentRollerOutput > 0) {
                        StoreSilkDetailVo detail = new StoreSilkDetailVo();
                        detail.setFsNum(fsNum.substring(2, 3));
                        detail.setSiloNum(containerNum);
                        detail.setPipeNum(channel);
                        detail.setEquNo(equNo);
                        detail.setShiftCode(shift);
                        detail.setShiftStartTime(stimDate);
                        detail.setShiftEndTime(etimDate);
                        detail.setOutput(currentRollerOutput);
                        rollerDetailList.add(detail);
                    }
                    // ================= åŒ…装机产量统计 =================
                    Double currentPackerOutput = 0.0;
                    // 1. æŸ¥è¯¢ç­æ¬¡ç»“束时的产量快照
                    LambdaQueryWrapper<PackerTimeData> plqw = new LambdaQueryWrapper<>();
                    plqw.le(PackerTimeData::getTime, shiftEndStr)
                            .eq(PackerTimeData::getKey, packerKey)
                            .ge(PackerTimeData::getTime, tenMinBeforeShiftEnd)
                            .orderByDesc(PackerTimeData::getTime)
                            .isNotNull(PackerTimeData::getQty)
                            .gt(PackerTimeData::getQty, 0)
                            .last("LIMIT 1");
                    PackerTimeData pData = packerTimeDataMapper.selectOne(plqw);
                    if (pData != null) {
                        // åˆå§‹äº§é‡è®¾ä¸ºç­æ¬¡ç»“束时的累计值
                        currentPackerOutput += pData.getQty();
                        // 2. å¤„理“扣头”
                        if (distimebegin.after(stimDate)) {
                            LocalDateTime distBeginTime = LocalDateTime.ofInstant(distimebegin.toInstant(), zone);
                            String distBeginStr = distBeginTime.format(formatter);
                            String tenMinBeforeDistBegin = distBeginTime.minusMinutes(10).format(formatter);
                            LambdaQueryWrapper<PackerTimeData> beginPlqw = new LambdaQueryWrapper<>();
                            beginPlqw.le(PackerTimeData::getTime, distBeginStr)
                                    .eq(PackerTimeData::getKey, packerKey)
                                    .ge(PackerTimeData::getTime, tenMinBeforeDistBegin)
                                    .orderByDesc(PackerTimeData::getTime)
                                    .isNotNull(PackerTimeData::getQty)
                                    .gt(PackerTimeData::getQty, 0)
                                    .last("LIMIT 1");
                            PackerTimeData pBeginData = packerTimeDataMapper.selectOne(beginPlqw);
                            if (pBeginData != null) {
                                currentPackerOutput -= pBeginData.getQty();
                            }
                        }
                        // 3. å¤„理“扣尾”
                        if (etimDate.after(distimeend)) {
                            LocalDateTime distEndTime = LocalDateTime.ofInstant(distimeend.toInstant(), zone);
                            String distEndStr = distEndTime.format(formatter);
                            String tenMinBeforeDistEnd = distEndTime.minusMinutes(10).format(formatter);
                            LambdaQueryWrapper<PackerTimeData> endPlqw = new LambdaQueryWrapper<>();
                            endPlqw.le(PackerTimeData::getTime, distEndStr)
                                    .eq(PackerTimeData::getKey, packerKey)
                                    .ge(PackerTimeData::getTime, tenMinBeforeDistEnd)
                                    .orderByDesc(PackerTimeData::getTime)
                                    .isNotNull(PackerTimeData::getQty)
                                    .gt(PackerTimeData::getQty, 0)
                                    .last("LIMIT 1");
                            PackerTimeData pEndData = packerTimeDataMapper.selectOne(endPlqw);
                            if (pEndData != null) {
                                double qtyDelta = pData.getQty() - pEndData.getQty();
                                if (qtyDelta > 0) {
                                    currentPackerOutput -= qtyDelta;
                                }
                            }
                        }
                    }
                    // ç´¯è®¡æ€»äº§é‡
                    packerOutput += currentPackerOutput;
                    // è®°å½•包装机明细
                    if (currentPackerOutput > 0) {
                        StoreSilkDetailVo detail = new StoreSilkDetailVo();
                        detail.setFsNum(fsNum.substring(2, 3));
                        detail.setSiloNum(containerNum);
                        detail.setPipeNum(channel);
                        detail.setEquNo(equNo);
                        detail.setShiftCode(shift);
                        detail.setShiftStartTime(stimDate);
                        detail.setShiftEndTime(etimDate);
                        detail.setOutput(currentPackerOutput);
                        packerDetailList.add(detail);
                    }
                }
            }
            storeSilkInfoVo.setRollerOutput(rollerOutput);
            storeSilkInfoVo.setPackerOutput(packerOutput);
            storeSilkInfoVo.setRollerDetailList(rollerDetailList);
            storeSilkInfoVo.setPackerDetailList(packerDetailList);
        }
    }
    /**
     * è®¡ç®—出料区间 [begin, end) æ¶‰åŠåˆ°çš„班次(仅 code=1、2 çš„æ—©/中班)
     * è¿”回 Map<yyyy-MM-dd, code>,表示该日期上的该班次与出料区间存在时间重叠
     * è‹¥åŒä¸€æ—¥æœŸæ—©/中班均有重叠,value ä½¿ç”¨é€—号拼接,如 "1,2"
     * <p>
     * ç®€åŒ–思路:
     * 1. å°† begin/end è½¬ä¸º LocalDateTime,取出覆盖的所有自然日
     * 2. å¯¹æ¯ä¸€å¤©æŒ‰ç­æ¬¡çš„ stim/etim ç”Ÿæˆç­æ¬¡æ—¶é—´çª— [shiftStart, shiftEnd)
     * - è‹¥ etim <= stim,视为跨天班次:shiftEnd = å½“天日期 + 1 å¤© + etim
     * 3. ç”¨ç®€å•的区间重叠判断:shiftEnd > begin && shiftStart < end
     */
    private List<MdShiftBo> calcShiftSpans(Date begin, Date end, List<MdShift> mdShifts) {
        List<MdShiftBo> result = new ArrayList<>();
        // åŸºç¡€æ ¡éªŒ
        if (begin == null || end == null || !end.after(begin) || mdShifts == null || mdShifts.isEmpty()) {
            return result;
        }
        // ä»…保留早班(code=1)与中班(code=2)
        // åŒæ—¶å¿½ç•¥ stim/etim ä¸ºç©ºçš„班次
        ZoneId zone = ZoneId.systemDefault();
        LocalDateTime intervalStart = LocalDateTime.ofInstant(begin.toInstant(), zone);
        LocalDateTime intervalEnd = LocalDateTime.ofInstant(end.toInstant(), zone);
        LocalDate day = intervalStart.toLocalDate();
        LocalDate lastDay = intervalEnd.toLocalDate();
        // è§£æžæ—¶é—´æ ¼å¼ï¼šæ”¯æŒ "H:mm" æˆ– "H:mm:ss"
        DateTimeFormatter tf = DateTimeFormatter.ofPattern("H:mm[:ss]");
        Set<String> seen = new HashSet<>();
        // æŒ‰å¤©éåŽ†è¦†ç›–èŒƒå›´
        while (!day.isAfter(lastDay)) {
            for (MdShift s : mdShifts) {
                // ä»…æ—©/中班
                String code = s.getCode();
                if (!"1".equals(code) && !"2".equals(code)) {
                    continue;
                }
                String st = s.getStim();
                String et = s.getEtim();
                if (st == null || et == null) {
                    continue;
                }
                // è§£æžç­æ¬¡èµ·æ­¢æ—¶é—´
                LocalTime stt;
                LocalTime ett;
                try {
                    stt = LocalTime.parse(st.trim(), tf);
                    ett = LocalTime.parse(et.trim(), tf);
                } catch (Exception ignore) {
                    // æ—¶é—´æ ¼å¼å¼‚常则跳过该班次
                    continue;
                }
                // ç”Ÿæˆå½“天该班次的时间窗
                LocalDateTime shiftStart = LocalDateTime.of(day, stt);
                LocalDateTime shiftEnd = ett.isAfter(stt) || ett.equals(stt)
                        ? LocalDateTime.of(day, ett)
                        : LocalDateTime.of(day.plusDays(1), ett); // è·¨å¤©å¤„理
                // åˆ¤æ–­åŒºé—´æ˜¯å¦é‡å ï¼š [shiftStart, shiftEnd) ä¸Ž [intervalStart, intervalEnd)
                if (shiftEnd.isAfter(intervalStart) && shiftStart.isBefore(intervalEnd)) {
                    Date shiftDay = Date.from(day.atStartOfDay(zone).toInstant());
                    String dedupeKey = shiftDay.getTime() + "-" + code;
                    if (seen.add(dedupeKey)) {
                        MdShiftBo bo = new MdShiftBo();
                        bo.setId(s.getId());
                        bo.setWsId(s.getWsId());
                        bo.setCode(s.getCode());
                        bo.setName(s.getName());
                        bo.setStim(s.getStim());
                        bo.setEtim(s.getEtim());
                        bo.setSeq(s.getSeq());
                        bo.setEnable(s.getEnable());
                        bo.setDel(s.getDel());
                        bo.setCreateUserName(s.getCreateUserName());
                        bo.setCreateUserTime(s.getCreateUserTime());
                        bo.setUpdateUserName(s.getUpdateUserName());
                        bo.setUpdateUserTime(s.getUpdateUserTime());
                        bo.setDay(shiftDay);
                        result.add(bo);
                    }
                }
            }
            day = day.plusDays(1);
        }
        result.sort(Comparator
                .comparing(MdShiftBo::getDay, Comparator.nullsLast(Date::compareTo))
                .thenComparing(MdShiftBo::getCode, Comparator.nullsLast(String::compareTo)));
        return result;
    }
    /**
@@ -98,6 +528,8 @@
        lqw.eq(bo.getDistimebegin() != null, StoreSilkInfo::getDistimebegin, bo.getDistimebegin());
        lqw.eq(bo.getDistimeend() != null, StoreSilkInfo::getDistimeend, bo.getDistimeend());
        lqw.eq(StringUtils.isNotBlank(bo.getSiloid()), StoreSilkInfo::getSiloid, bo.getSiloid());
        lqw.between(params.get("beginTime") != null && params.get("endTime") != null,
                StoreSilkInfo::getDistimeend, params.get("beginTime"), params.get("endTime"));
        return lqw;
    }
@@ -134,7 +566,7 @@
    /**
     * ä¿å­˜å‰çš„æ•°æ®æ ¡éªŒ
     */
    private void validEntityBeforeSave(StoreSilkInfo entity){
    private void validEntityBeforeSave(StoreSilkInfo entity) {
        //TODO åšä¸€äº›æ•°æ®æ ¡éªŒ,如唯一约束
    }
@@ -147,7 +579,7 @@
     */
    @Override
    public Boolean deleteWithValidByIds(Collection<Long> ids, Boolean isValid) {
        if(isValid){
        if (isValid) {
            //TODO åšä¸€äº›ä¸šåŠ¡ä¸Šçš„æ ¡éªŒ,判断是否需要校验
        }
        return baseMapper.deleteByIds(ids) > 0;
RuoYi-Vue-Plus/ruoyi-modules/ruoyi-qa/src/main/java/org/dromara/qa/md/domain/bo/MdShiftBo.java
@@ -86,5 +86,10 @@
     */
    private Date updateUserTime;
    /**
     * ç­æ¬¡æ—¥æœŸ
     */
    private Date day;
}
RuoYi-Vue-Plus/ruoyi-modules/ruoyi-qa/src/main/java/org/dromara/qa/md/service/OracleShiftReader.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,21 @@
package org.dromara.qa.md.service;
import com.baomidou.dynamic.datasource.annotation.DS;
import lombok.RequiredArgsConstructor;
import org.dromara.qa.md.domain.MdShift;
import org.dromara.qa.md.mapper.MdShiftMapper;
import org.springframework.stereotype.Service;
import java.util.List;
@DS("oracle")
@Service
@RequiredArgsConstructor
public class OracleShiftReader {
    private final MdShiftMapper mdShiftMapper;
    public List<MdShift> listAll() {
        return mdShiftMapper.selectList();
    }
}
RuoYi-Vue-Plus/¿ª·¢ÈÕÖ¾.md
@@ -12,5 +12,8 @@
#### 20260302
- å¾…办事项
- [ ] ç›®å‰å‚¨ä¸æŸœä¿¡æ¯ä¿¡æ¯æ²¡æœ‰ä»Žè§†å›¾ä¸­èŽ·å–ï¼Œä½¿ç”¨oracle_store_silk表模拟
- [ ] ç›®å‰å‚¨ä¸æŸœä¿¡æ¯ä¿¡æ¯æ²¡æœ‰ä»Žè§†å›¾ä¸­èŽ·å–ï¼Œä½¿ç”¨oracle_store_silk表模拟->使用制丝车间真实视图数据源
- [ ] å–‚丝机对应关系,fs开头储存的真实值是什么,目前测试数据使用的是 fs11 -> å‚¨ä¸æŸœå·
- [ ] ç¡®è®¤1台喂丝机对应的2台储丝柜是依次供丝还是可以并行供丝(<span style="color: red;">***并行供丝无法准确计算产量***</span>)
- [ ] ç¡®è®¤ å–‚丝机对应关系中(key->pipe01    value->1#机组对应的喂丝机和管道)value不会重复  (<span style="color: red;">***重复将导致计算逻辑出现错误***</span>)
ruoyi-plus-soybean/src/typings/api/analy.store-silk.api.d.ts
@@ -33,7 +33,7 @@
      /** å‚¨ä¸æŸœå‡ºæ–™ç»“束时间 */
      distimeend: string;
      /** æŸœå­å·(末位) */
      siloid: CommonType.IdType;
      siloid: string;
    }>;
    /** store silk search params */
ruoyi-plus-soybean/src/typings/api/qm.batch.api.d.ts
@@ -112,7 +112,17 @@
    type BatchSearchParams = CommonType.RecordNullable<
      Pick<
        Api.Qm.Batch,
        'batchCode' | 'typ' | 'eqpCode' | 'matCode' | 'batchDate' | 'flag' | 'deleted' | 'category' | 'state'
        | 'batchCode'
        | 'typ'
        | 'eqpCode'
        | 'matCode'
        | 'batchDate'
        | 'flag'
        | 'toMesDate'
        | 'fromMesDate'
        | 'deleted'
        | 'category'
        | 'state'
      > &
        Api.Common.CommonSearchParams
    >;
ruoyi-plus-soybean/src/views/analy/feed-match/modules/feed-match-operate-drawer.vue
@@ -250,49 +250,49 @@
          <NInput v-model:value="model.fs42" :rows="3" type="textarea" placeholder="请输入4#喂丝机对应的第二个储丝柜" />
        </NFormItem>
        <NFormItem label="1#机组对应的喂丝机和管道" path="pipe01">
          <NInput v-model:value="model.pipe01" placeholder="请输入1#机组对应的喂丝机和管道" />
          <NInputNumber v-model:value="model.pipe01" placeholder="请输入1#机组对应的喂丝机和管道" class="w-full" />
        </NFormItem>
        <NFormItem label="2#机组对应的喂丝机和管道" path="pipe02">
          <NInput v-model:value="model.pipe02" placeholder="请输入2#机组对应的喂丝机和管道" />
          <NInputNumber v-model:value="model.pipe02" placeholder="请输入2#机组对应的喂丝机和管道" class="w-full" />
        </NFormItem>
        <NFormItem label="3#机组对应的喂丝机和管道" path="pipe03">
          <NInput v-model:value="model.pipe03" placeholder="请输入3#机组对应的喂丝机和管道" />
          <NInputNumber v-model:value="model.pipe03" placeholder="请输入3#机组对应的喂丝机和管道" class="w-full" />
        </NFormItem>
        <NFormItem label="4#机组对应的喂丝机和管道" path="pipe04">
          <NInput v-model:value="model.pipe04" placeholder="请输入4#机组对应的喂丝机和管道" />
          <NInputNumber v-model:value="model.pipe04" placeholder="请输入4#机组对应的喂丝机和管道" class="w-full" />
        </NFormItem>
        <NFormItem label="5#机组对应的喂丝机和管道" path="pipe05">
          <NInput v-model:value="model.pipe05" placeholder="请输入5#机组对应的喂丝机和管道" />
          <NInputNumber v-model:value="model.pipe05" placeholder="请输入5#机组对应的喂丝机和管道" class="w-full" />
        </NFormItem>
        <NFormItem label="6#机组对应的喂丝机和管道" path="pipe06">
          <NInput v-model:value="model.pipe06" placeholder="请输入6#机组对应的喂丝机和管道" />
          <NInputNumber v-model:value="model.pipe06" placeholder="请输入6#机组对应的喂丝机和管道" class="w-full" />
        </NFormItem>
        <NFormItem label="7#机组对应的喂丝机和管道" path="pipe07">
          <NInput v-model:value="model.pipe07" placeholder="请输入7#机组对应的喂丝机和管道" />
          <NInputNumber v-model:value="model.pipe07" placeholder="请输入7#机组对应的喂丝机和管道" class="w-full" />
        </NFormItem>
        <NFormItem label="8#机组对应的喂丝机和管道" path="pipe08">
          <NInput v-model:value="model.pipe08" placeholder="请输入8#机组对应的喂丝机和管道" />
          <NInputNumber v-model:value="model.pipe08" placeholder="请输入8#机组对应的喂丝机和管道" class="w-full" />
        </NFormItem>
        <NFormItem label="9#机组对应的喂丝机和管道" path="pipe09">
          <NInput v-model:value="model.pipe09" placeholder="请输入9#机组对应的喂丝机和管道" />
          <NInputNumber v-model:value="model.pipe09" placeholder="请输入9#机组对应的喂丝机和管道" class="w-full" />
        </NFormItem>
        <NFormItem label="10#机组对应的喂丝机和管道" path="pipe10">
          <NInput v-model:value="model.pipe10" placeholder="请输入10#机组对应的喂丝机和管道" />
          <NInputNumber v-model:value="model.pipe10" placeholder="请输入10#机组对应的喂丝机和管道" class="w-full" />
        </NFormItem>
        <NFormItem label="11#机组对应的喂丝机和管道" path="pipe11">
          <NInput v-model:value="model.pipe11" placeholder="请输入11#机组对应的喂丝机和管道" />
          <NInputNumber v-model:value="model.pipe11" placeholder="请输入11#机组对应的喂丝机和管道" class="w-full" />
        </NFormItem>
        <NFormItem label="12#机组对应的喂丝机和管道" path="pipe12">
          <NInput v-model:value="model.pipe12" placeholder="请输入12#机组对应的喂丝机和管道" />
          <NInputNumber v-model:value="model.pipe12" placeholder="请输入12#机组对应的喂丝机和管道" class="w-full" />
        </NFormItem>
        <NFormItem label="喂丝机状态 1-连接 0-断开" path="wsjState">
          <NInput v-model:value="model.wsjState" placeholder="请输入喂丝机状态 1-连接 0-断开" />
          <NInputNumber v-model:value="model.wsjState" placeholder="请输入喂丝机状态 1-连接 0-断开" class="w-full" />
        </NFormItem>
        <NFormItem label="班次" path="shift">
          <NInput v-model:value="model.shift" placeholder="请输入班次" />
          <NInputNumber v-model:value="model.shift" placeholder="请输入班次" class="w-full" />
        </NFormItem>
        <NFormItem label="机台" path="equNo">
          <NInput v-model:value="model.equNo" placeholder="请输入机台" />
          <NInputNumber v-model:value="model.equNo" placeholder="请输入机台" class="w-full" />
        </NFormItem>
        <NFormItem label="备注" path="remark">
          <NInput v-model:value="model.remark" placeholder="请输入备注" />
ruoyi-plus-soybean/src/views/analy/hoister/modules/hoister-data-operate-drawer.vue
@@ -251,7 +251,6 @@
  if (visible.value) {
    handleUpdateModelWhenEdit();
    restoreValidation();
    getTreeList();
  }
});
</script>
@@ -272,112 +271,112 @@
          <NInput v-model:value="model.key" :rows="3" type="textarea" placeholder="请输入key" />
        </NFormItem>
        <NFormItem label="网络状态  0异常,1正常" path="online">
          <NInput v-model:value="model.online" placeholder="请输入网络状态  0异常,1正常" />
          <NInputNumber v-model:value="model.online" placeholder="请输入网络状态  0异常,1正常" class="w-full" />
        </NFormItem>
        <NFormItem label="产量" path="qty">
          <NInput v-model:value="model.qty" placeholder="请输入产量" />
          <NInputNumber v-model:value="model.qty" placeholder="请输入产量" class="w-full" />
        </NFormItem>
        <NFormItem label="1#提升机状态" path="tState1">
          <NInput v-model:value="model.tState1" placeholder="请输入1#提升机状态" />
          <NInputNumber v-model:value="model.tState1" placeholder="请输入1#提升机状态" class="w-full" />
        </NFormItem>
        <NFormItem label="2#提升机状态" path="tState2">
          <NInput v-model:value="model.tState2" placeholder="请输入2#提升机状态" />
          <NInputNumber v-model:value="model.tState2" placeholder="请输入2#提升机状态" class="w-full" />
        </NFormItem>
        <NFormItem label="3#提升机状态" path="tState3">
          <NInput v-model:value="model.tState3" placeholder="请输入3#提升机状态" />
          <NInputNumber v-model:value="model.tState3" placeholder="请输入3#提升机状态" class="w-full" />
        </NFormItem>
        <NFormItem label="4#提升机状态" path="tState4">
          <NInput v-model:value="model.tState4" placeholder="请输入4#提升机状态" />
          <NInputNumber v-model:value="model.tState4" placeholder="请输入4#提升机状态" class="w-full" />
        </NFormItem>
        <NFormItem label="5#提升机状态" path="tState5">
          <NInput v-model:value="model.tState5" placeholder="请输入5#提升机状态" />
          <NInputNumber v-model:value="model.tState5" placeholder="请输入5#提升机状态" class="w-full" />
        </NFormItem>
        <NFormItem label="6#提升机状态" path="tState6">
          <NInput v-model:value="model.tState6" placeholder="请输入6#提升机状态" />
          <NInputNumber v-model:value="model.tState6" placeholder="请输入6#提升机状态" class="w-full" />
        </NFormItem>
        <NFormItem label="7#提升机状态" path="tState7">
          <NInput v-model:value="model.tState7" placeholder="请输入7#提升机状态" />
          <NInputNumber v-model:value="model.tState7" placeholder="请输入7#提升机状态" class="w-full" />
        </NFormItem>
        <NFormItem label="8#提升机状态" path="tState8">
          <NInput v-model:value="model.tState8" placeholder="请输入8#提升机状态" />
          <NInputNumber v-model:value="model.tState8" placeholder="请输入8#提升机状态" class="w-full" />
        </NFormItem>
        <NFormItem label="9#提升机状态" path="tState9">
          <NInput v-model:value="model.tState9" placeholder="请输入9#提升机状态" />
          <NInputNumber v-model:value="model.tState9" placeholder="请输入9#提升机状态" class="w-full" />
        </NFormItem>
        <NFormItem label="10#提升机状态" path="tState10">
          <NInput v-model:value="model.tState10" placeholder="请输入10#提升机状态" />
          <NInputNumber v-model:value="model.tState10" placeholder="请输入10#提升机状态" class="w-full" />
        </NFormItem>
        <NFormItem label="11#提升机状态" path="tState11">
          <NInput v-model:value="model.tState11" placeholder="请输入11#提升机状态" />
          <NInputNumber v-model:value="model.tState11" placeholder="请输入11#提升机状态" class="w-full" />
        </NFormItem>
        <NFormItem label="12#提升机状态" path="tState12">
          <NInput v-model:value="model.tState12" placeholder="请输入12#提升机状态" />
          <NInputNumber v-model:value="model.tState12" placeholder="请输入12#提升机状态" class="w-full" />
        </NFormItem>
        <NFormItem label="1#排包机状态" path="pState1">
          <NInput v-model:value="model.pState1" placeholder="请输入1#排包机状态" />
          <NInputNumber v-model:value="model.pState1" placeholder="请输入1#排包机状态" class="w-full" />
        </NFormItem>
        <NFormItem label="2#排包机状态" path="pState2">
          <NInput v-model:value="model.pState2" placeholder="请输入2#排包机状态" />
          <NInputNumber v-model:value="model.pState2" placeholder="请输入2#排包机状态" class="w-full" />
        </NFormItem>
        <NFormItem label="3#排包机状态" path="pState3">
          <NInput v-model:value="model.pState3" placeholder="请输入3#排包机状态" />
          <NInputNumber v-model:value="model.pState3" placeholder="请输入3#排包机状态" class="w-full" />
        </NFormItem>
        <NFormItem label="4#排包机状态" path="pState4">
          <NInput v-model:value="model.pState4" placeholder="请输入4#排包机状态" />
          <NInputNumber v-model:value="model.pState4" placeholder="请输入4#排包机状态" class="w-full" />
        </NFormItem>
        <NFormItem label="1#提升机产量" path="tQty1">
          <NInput v-model:value="model.tQty1" placeholder="请输入1#提升机产量" />
          <NInputNumber v-model:value="model.tQty1" placeholder="请输入1#提升机产量" class="w-full" />
        </NFormItem>
        <NFormItem label="2#提升机产量" path="tQty2">
          <NInput v-model:value="model.tQty2" placeholder="请输入2#提升机产量" />
          <NInputNumber v-model:value="model.tQty2" placeholder="请输入2#提升机产量" class="w-full" />
        </NFormItem>
        <NFormItem label="3#提升机产量" path="tQty3">
          <NInput v-model:value="model.tQty3" placeholder="请输入3#提升机产量" />
          <NInputNumber v-model:value="model.tQty3" placeholder="请输入3#提升机产量" class="w-full" />
        </NFormItem>
        <NFormItem label="4#提升机产量" path="tQty4">
          <NInput v-model:value="model.tQty4" placeholder="请输入4#提升机产量" />
          <NInputNumber v-model:value="model.tQty4" placeholder="请输入4#提升机产量" class="w-full" />
        </NFormItem>
        <NFormItem label="5#提升机产量" path="tQty5">
          <NInput v-model:value="model.tQty5" placeholder="请输入5#提升机产量" />
          <NInputNumber v-model:value="model.tQty5" placeholder="请输入5#提升机产量" class="w-full" />
        </NFormItem>
        <NFormItem label="6#提升机产量" path="tQty6">
          <NInput v-model:value="model.tQty6" placeholder="请输入6#提升机产量" />
          <NInputNumber v-model:value="model.tQty6" placeholder="请输入6#提升机产量" class="w-full" />
        </NFormItem>
        <NFormItem label="7#提升机产量" path="tQty7">
          <NInput v-model:value="model.tQty7" placeholder="请输入7#提升机产量" />
          <NInputNumber v-model:value="model.tQty7" placeholder="请输入7#提升机产量" class="w-full" />
        </NFormItem>
        <NFormItem label="8#提升机产量" path="tQty8">
          <NInput v-model:value="model.tQty8" placeholder="请输入8#提升机产量" />
          <NInputNumber v-model:value="model.tQty8" placeholder="请输入8#提升机产量" class="w-full" />
        </NFormItem>
        <NFormItem label="9#提升机产量" path="tQty9">
          <NInput v-model:value="model.tQty9" placeholder="请输入9#提升机产量" />
          <NInputNumber v-model:value="model.tQty9" placeholder="请输入9#提升机产量" class="w-full" />
        </NFormItem>
        <NFormItem label="10#提升机产量" path="tQty10">
          <NInput v-model:value="model.tQty10" placeholder="请输入10#提升机产量" />
          <NInputNumber v-model:value="model.tQty10" placeholder="请输入10#提升机产量" class="w-full" />
        </NFormItem>
        <NFormItem label="11#提升机产量" path="tQty11">
          <NInput v-model:value="model.tQty11" placeholder="请输入11#提升机产量" />
          <NInputNumber v-model:value="model.tQty11" placeholder="请输入11#提升机产量" class="w-full" />
        </NFormItem>
        <NFormItem label="12#提升机产量" path="tQty12">
          <NInput v-model:value="model.tQty12" placeholder="请输入12#提升机产量" />
          <NInputNumber v-model:value="model.tQty12" placeholder="请输入12#提升机产量" class="w-full" />
        </NFormItem>
        <NFormItem label="1#排包机产量" path="pQty1">
          <NInput v-model:value="model.pQty1" placeholder="请输入1#排包机产量" />
          <NInputNumber v-model:value="model.pQty1" placeholder="请输入1#排包机产量" class="w-full" />
        </NFormItem>
        <NFormItem label="2#排包机产量" path="pQty2">
          <NInput v-model:value="model.pQty2" placeholder="请输入2#排包机产量" />
          <NInputNumber v-model:value="model.pQty2" placeholder="请输入2#排包机产量" class="w-full" />
        </NFormItem>
        <NFormItem label="3#排包机产量" path="pQty3">
          <NInput v-model:value="model.pQty3" placeholder="请输入3#排包机产量" />
          <NInputNumber v-model:value="model.pQty3" placeholder="请输入3#排包机产量" class="w-full" />
        </NFormItem>
        <NFormItem label="4#排包机产量" path="pQty4">
          <NInput v-model:value="model.pQty4" placeholder="请输入4#排包机产量" />
          <NInputNumber v-model:value="model.pQty4" placeholder="请输入4#排包机产量" class="w-full" />
        </NFormItem>
        <NFormItem label="班次" path="shift">
          <NInput v-model:value="model.shift" placeholder="请输入班次" />
          <NInputNumber v-model:value="model.shift" placeholder="请输入班次" class="w-full" />
        </NFormItem>
        <NFormItem label="设备" path="equNo">
          <NInput v-model:value="model.equNo" placeholder="请输入设备" />
          <NInputNumber v-model:value="model.equNo" placeholder="请输入设备" class="w-full" />
        </NFormItem>
        <NFormItem label="备注" path="remark">
          <NInput v-model:value="model.remark" placeholder="请输入备注" />
ruoyi-plus-soybean/src/views/analy/packer/modules/packer-data-operate-drawer.vue
@@ -212,74 +212,74 @@
          <NInput v-model:value="model.key" :rows="3" type="textarea" placeholder="请输入key" />
        </NFormItem>
        <NFormItem label="网络状态(0异常,1正常)" path="online">
          <NInput v-model:value="model.online" placeholder="请输入网络状态(0异常,1正常)" />
          <NInputNumber v-model:value="model.online" placeholder="请输入网络状态(0异常,1正常)" class="w-full" />
        </NFormItem>
        <NFormItem label="产量" path="qty">
          <NInput v-model:value="model.qty" placeholder="请输入产量" />
          <NInputNumber v-model:value="model.qty" placeholder="请输入产量" class="w-full" />
        </NFormItem>
        <NFormItem label="剔除产量" path="badQty">
          <NInput v-model:value="model.badQty" placeholder="请输入剔除产量" />
          <NInputNumber v-model:value="model.badQty" placeholder="请输入剔除产量" class="w-full" />
        </NFormItem>
        <NFormItem label="小盒膜消耗" path="xiaohemoVal">
          <NInput v-model:value="model.xiaohemoVal" placeholder="请输入小盒膜消耗" />
          <NInputNumber v-model:value="model.xiaohemoVal" placeholder="请输入小盒膜消耗" class="w-full" />
        </NFormItem>
        <NFormItem label="条盒膜消耗" path="tiaohemoVal">
          <NInput v-model:value="model.tiaohemoVal" placeholder="请输入条盒膜消耗" />
          <NInputNumber v-model:value="model.tiaohemoVal" placeholder="请输入条盒膜消耗" class="w-full" />
        </NFormItem>
        <NFormItem label="小盒纸消耗" path="xiaohezhiVal">
          <NInput v-model:value="model.xiaohezhiVal" placeholder="请输入小盒纸消耗" />
          <NInputNumber v-model:value="model.xiaohezhiVal" placeholder="请输入小盒纸消耗" class="w-full" />
        </NFormItem>
        <NFormItem label="条盒纸消耗" path="tiaohezhiVal">
          <NInput v-model:value="model.tiaohezhiVal" placeholder="请输入条盒纸消耗" />
          <NInputNumber v-model:value="model.tiaohezhiVal" placeholder="请输入条盒纸消耗" class="w-full" />
        </NFormItem>
        <NFormItem label="内衬纸消耗" path="neichenzhiVal">
          <NInput v-model:value="model.neichenzhiVal" placeholder="请输入内衬纸消耗" />
          <NInputNumber v-model:value="model.neichenzhiVal" placeholder="请输入内衬纸消耗" class="w-full" />
        </NFormItem>
        <NFormItem label="运行时间" path="runTime">
          <NInput v-model:value="model.runTime" placeholder="请输入运行时间" />
          <NInputNumber v-model:value="model.runTime" placeholder="请输入运行时间" class="w-full" />
        </NFormItem>
        <NFormItem label="停机时间" path="stopTime">
          <NInput v-model:value="model.stopTime" placeholder="请输入停机时间" />
          <NInputNumber v-model:value="model.stopTime" placeholder="请输入停机时间" class="w-full" />
        </NFormItem>
        <NFormItem label="停机次数" path="stopTimes">
          <NInput v-model:value="model.stopTimes" placeholder="请输入停机次数" />
          <NInputNumber v-model:value="model.stopTimes" placeholder="请输入停机次数" class="w-full" />
        </NFormItem>
        <NFormItem label="车速" path="speed">
          <NInput v-model:value="model.speed" placeholder="请输入车速" />
          <NInputNumber v-model:value="model.speed" placeholder="请输入车速" class="w-full" />
        </NFormItem>
        <NFormItem label="运行状态(-1 æ–­ç½‘ 0停止 1低速运行 2正常运行)" path="runStatus">
          <NRadioGroup v-model:value="model.runStatus">
            <NSpace>
              <NRadio value="0" label="请选择字典生成" />
              <NRadio :value="0" label="请选择字典生成" />
            </NSpace>
          </NRadioGroup>
        </NFormItem>
        <NFormItem label="提升机产量" path="tsQty">
          <NInput v-model:value="model.tsQty" placeholder="请输入提升机产量" />
          <NInputNumber v-model:value="model.tsQty" placeholder="请输入提升机产量" class="w-full" />
        </NFormItem>
        <NFormItem label="主机产量(小包机)" path="mainQty">
          <NInput v-model:value="model.mainQty" placeholder="请输入主机产量(小包机)" />
          <NInputNumber v-model:value="model.mainQty" placeholder="请输入主机产量(小包机)" class="w-full" />
        </NFormItem>
        <NFormItem label="主机剔除量" path="mainBadQty">
          <NInput v-model:value="model.mainBadQty" placeholder="请输入主机剔除量" />
          <NInputNumber v-model:value="model.mainBadQty" placeholder="请输入主机剔除量" class="w-full" />
        </NFormItem>
        <NFormItem label="透包机产量" path="tbjQty">
          <NInput v-model:value="model.tbjQty" placeholder="请输入透包机产量" />
          <NInputNumber v-model:value="model.tbjQty" placeholder="请输入透包机产量" class="w-full" />
        </NFormItem>
        <NFormItem label="透包机剔除好包" path="tbjGdQty">
          <NInput v-model:value="model.tbjGdQty" placeholder="请输入透包机剔除好包" />
          <NInputNumber v-model:value="model.tbjGdQty" placeholder="请输入透包机剔除好包" class="w-full" />
        </NFormItem>
        <NFormItem label="透包机剔除坏包" path="tbjBadQty">
          <NInput v-model:value="model.tbjBadQty" placeholder="请输入透包机剔除坏包" />
          <NInputNumber v-model:value="model.tbjBadQty" placeholder="请输入透包机剔除坏包" class="w-full" />
        </NFormItem>
        <NFormItem label="排包机产量" path="pbjQty">
          <NInput v-model:value="model.pbjQty" placeholder="请输入排包机产量" />
          <NInputNumber v-model:value="model.pbjQty" placeholder="请输入排包机产量" class="w-full" />
        </NFormItem>
        <NFormItem label="班次" path="shift">
          <NSelect
            v-model:value="model.shift"
            placeholder="请选择班次"
            :options="[{ value: '0', label: '请选择字典生成' }]"
            :options="[{ value: 0, label: '请选择字典生成' }]"
            clearable
          />
        </NFormItem>
@@ -287,7 +287,7 @@
          <NSelect
            v-model:value="model.equNo"
            placeholder="请选择设备"
            :options="[{ value: '0', label: '请选择字典生成' }]"
            :options="[{ value: 0, label: '请选择字典生成' }]"
            clearable
          />
        </NFormItem>
ruoyi-plus-soybean/src/views/analy/store-silk/index.vue
@@ -10,6 +10,7 @@
import ButtonIcon from '@/components/custom/button-icon.vue';
import StoreSilkOperateDrawer from './modules/store-silk-operate-drawer.vue';
import StoreSilkSearch from './modules/store-silk-search.vue';
import StoreSilkDetail from './modules/store-silk-detail.vue';
defineOptions({
  name: 'StoreSilkList'
@@ -18,6 +19,9 @@
const appStore = useAppStore();
const { download } = useDownload();
const { hasAuth } = useAuth();
const selectedRollerDetailList = ref<any[]>([]);
const selectedPackerDetailList = ref<any[]>([]);
const searchParams = ref<Api.Analy.StoreSilkSearchParams>({
  pageNum: 1,
@@ -50,11 +54,6 @@
    },
    columns: () => [
      {
        type: 'selection',
        align: 'center',
        width: 48
      },
      {
        key: 'index',
        title: $t('common.index'),
        align: 'center',
@@ -74,16 +73,26 @@
        minWidth: 130
      },
      {
        key: 'roller',
        title: '卷接产量',
        key: 'rollerOutput',
        title: '卷接产量(ç®±)',
        align: 'center',
        minWidth: 100
        minWidth: 120,
        render: row => {
          const v = calcRollerBox((row as any).rollerOutput);
          if (v === null) return '-';
          return v.toFixed(1);
        }
      },
      {
        key: 'packer',
        title: '包装产量',
        key: 'packerOutput',
        title: '包装产量(ç®±)',
        align: 'center',
        minWidth: 100
        minWidth: 120,
        render: row => {
          const v = calcPackerBox((row as any).packerOutput);
          if (v === null) return '-';
          return v.toFixed(1);
        }
      },
      {
        key: 'actualstarttime',
@@ -120,60 +129,6 @@
        title: '柜子号(末位)',
        align: 'center',
        width: 160
      },
      {
        key: 'operate',
        title: $t('common.operate'),
        align: 'center',
        fixed: 'right',
        width: 130,
        render: row => {
          const divider = () => {
            if (!hasAuth('analy:storeSilk:edit') || !hasAuth('analy:storeSilk:remove')) {
              return null;
            }
            return <NDivider vertical />;
          };
          const editBtn = () => {
            if (!hasAuth('analy:storeSilk:edit')) {
              return null;
            }
            return (
              <ButtonIcon
                text
                type="primary"
                icon="material-symbols:drive-file-rename-outline-outline"
                tooltipContent={$t('common.edit')}
                onClick={() => edit(row.id)}
              />
            );
          };
          const deleteBtn = () => {
            if (!hasAuth('analy:storeSilk:remove')) {
              return null;
            }
            return (
              <ButtonIcon
                text
                type="error"
                icon="material-symbols:delete-outline"
                tooltipContent={$t('common.delete')}
                popconfirmContent={$t('common.confirmDelete')}
                onPositiveClick={() => handleDelete(row.id)}
              />
            );
          };
          return (
            <div class="flex-center gap-8px">
              {editBtn()}
              {divider()}
              {deleteBtn()}
            </div>
          );
        }
      }
    ]
  });
@@ -202,6 +157,30 @@
function handleExport() {
  download('/analy/storeSilk/export', searchParams.value, `储丝柜产量_${new Date().getTime()}.xlsx`);
}
function calcRollerBox(val: unknown) {
  if (val === null || val === undefined) return null;
  const v = Number(val) / 50;
  if (!Number.isFinite(v)) return null;
  return v;
}
function calcPackerBox(val: unknown) {
  if (val === null || val === undefined) return null;
  const v = Number(val) / 10 / 250;
  if (!Number.isFinite(v)) return null;
  return v;
}
function handleRowClick(row: any) {
  return {
    onClick: () => {
      selectedRollerDetailList.value = row.rollerDetailList || [];
      selectedPackerDetailList.value = row.packerDetailList || [];
    },
    style: 'cursor: pointer;'
  };
}
</script>
<template>
@@ -213,8 +192,8 @@
          v-model:columns="columnChecks"
          :disabled-delete="checkedRowKeys.length === 0"
          :loading="loading"
          :show-add="hasAuth('analy:storeSilk:add')"
          :show-delete="hasAuth('analy:storeSilk:remove')"
          :show-add="false"
          :show-delete="false"
          :show-export="hasAuth('analy:storeSilk:export')"
          @add="handleAdd"
          @delete="handleBatchDelete"
@@ -243,7 +222,6 @@
        </TableHeaderOperation>
      </template>
      <NDataTable
        v-model:checked-row-keys="checkedRowKeys"
        :columns="columns"
        :data="data"
        :size="tableSize"
@@ -253,6 +231,7 @@
        remote
        :row-key="row => row.id"
        :pagination="mobilePagination"
        :row-props="handleRowClick"
        class="sm:h-full"
      />
      <StoreSilkOperateDrawer
@@ -262,6 +241,11 @@
        @submitted="getDataByPage"
      />
    </NCard>
    <StoreSilkDetail
      :roller-detail-list="selectedRollerDetailList"
      :packer-detail-list="selectedPackerDetailList"
      class="h-[192px] overflow-hidden"
    />
  </div>
</template>
ruoyi-plus-soybean/src/views/analy/store-silk/modules/store-silk-detail.vue
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,173 @@
<script setup lang="tsx">
import { computed, ref } from 'vue';
import { NDataTable } from 'naive-ui';
import { $t } from '@/locales';
defineOptions({
  name: 'StoreSilkDetail'
});
interface StoreSilkDetailVo {
  fsNum: string;
  siloNum: string;
  pipeNum: string;
  equNo: string;
  shiftCode: string;
  shiftStartTime: string;
  shiftEndTime: string;
  output: number | null;
}
interface Props {
  rollerDetailList?: StoreSilkDetailVo[];
  packerDetailList?: StoreSilkDetailVo[];
}
const props = withDefaults(defineProps<Props>(), {
  rollerDetailList: () => [],
  packerDetailList: () => []
});
function calcRollerBox(val: unknown) {
  if (val === null || val === undefined) return null;
  const v = Number(val) / 50;
  if (!Number.isFinite(v)) return null;
  return v;
}
function calcPackerBox(val: unknown) {
  if (val === null || val === undefined) return null;
  const v = Number(val) / 10 / 250;
  if (!Number.isFinite(v)) return null;
  return v;
}
type DetailType = 'roller' | 'packer';
const detailType = ref<DetailType>('roller');
const detailList = computed(() => {
  return detailType.value === 'roller' ? props.rollerDetailList : props.packerDetailList;
});
const columns: NaiveUI.TableColumn<StoreSilkDetailVo>[] = [
  {
    key: 'index',
    title: $t('common.index'),
    align: 'center',
    width: 64,
    render: (_: any, index: number) => index + 1
  },
  {
    key: 'fsNum',
    title: '喂丝机号',
    align: 'center',
    width: 100
  },
  {
    key: 'siloNum',
    title: '储丝柜号',
    align: 'center',
    width: 100
  },
  {
    key: 'pipeNum',
    title: '管道号',
    align: 'center',
    width: 100
  },
  {
    key: 'equNo',
    title: '机台号',
    align: 'center',
    width: 100
  },
  {
    key: 'shiftCode',
    title: '班次代码',
    align: 'center',
    width: 100,
    render: (row: StoreSilkDetailVo) => {
      const map: Record<string, string> = {
        '1': '早班',
        '2': '中班',
        '3': '晚班'
      };
      return map[row.shiftCode] || row.shiftCode;
    }
  },
  {
    key: 'shiftStartTime',
    title: '班次开始时间',
    align: 'center',
    width: 160
  },
  {
    key: 'shiftEndTime',
    title: '班次结束时间',
    align: 'center',
    width: 160
  },
  {
    key: 'output',
    title: '产量(千支)',
    align: 'center',
    width: 120,
    render: (row: StoreSilkDetailVo) => {
      if (row.output === null || row.output === undefined) return '-';
      const v = Number(row.output);
      if (!Number.isFinite(v)) return '-';
      return v;
    }
  },
  {
    key: 'outputBox',
    title: '产量(ç®±)',
    align: 'center',
    width: 100,
    render: (row: StoreSilkDetailVo) => {
      const v = detailType.value === 'roller' ? calcRollerBox(row.output) : calcPackerBox(row.output);
      if (v === null) return '-';
      return v.toFixed(1);
    }
  }
];
</script>
<template>
  <NCard
    title="产量明细"
    :bordered="false"
    size="small"
    class="card-wrapper flex-col-stretch"
    :content-style="{ flex: 1, overflow: 'hidden', display: 'flex', flexDirection: 'column' }"
  >
    <template #header-extra>
      <NSpace :size="8">
        <NButton size="small" ghost :type="detailType === 'roller' ? 'primary' : 'default'" @click="detailType = 'roller'">
          å·æŽ¥
        </NButton>
        <NButton size="small" ghost :type="detailType === 'packer' ? 'primary' : 'default'" @click="detailType = 'packer'">
          åŒ…装
        </NButton>
      </NSpace>
    </template>
    <div v-if="!detailList || detailList.length === 0" class="flex-center h-full text-gray-400">
      è¯·ç‚¹å‡»ä¸Šæ–¹è¡¨æ ¼è¡ŒæŸ¥çœ‹è¯¦æƒ…
    </div>
    <NDataTable
      v-else
      :columns="columns as any"
      :data="detailList"
      flex-height
      class="flex-1-hidden"
    />
  </NCard>
</template>
<style scoped>
:deep(.n-card__content) {
  padding: 8px 12px;
}
</style>
ruoyi-plus-soybean/src/views/analy/store-silk/modules/store-silk-operate-drawer.vue
@@ -162,16 +162,16 @@
          />
        </NFormItem>
        <NFormItem label="投料重量" path="jobinput">
          <NInput v-model:value="model.jobinput" placeholder="请输入投料重量" />
          <NInputNumber v-model:value="model.jobinput" placeholder="请输入投料重量" class="w-full" />
        </NFormItem>
        <NFormItem label="损耗重量" path="weightloss">
          <NInput v-model:value="model.weightloss" placeholder="请输入损耗重量" />
          <NInputNumber v-model:value="model.weightloss" placeholder="请输入损耗重量" class="w-full" />
        </NFormItem>
        <NFormItem label="出叶丝率" path="slkrate">
          <NInput v-model:value="model.slkrate" placeholder="请输入出叶丝率" />
          <NInputNumber v-model:value="model.slkrate" placeholder="请输入出叶丝率" class="w-full" />
        </NFormItem>
        <NFormItem label="储丝柜重量" path="weight">
          <NInput v-model:value="model.weight" placeholder="请输入储丝柜重量" />
          <NInputNumber v-model:value="model.weight" placeholder="请输入储丝柜重量" class="w-full" />
        </NFormItem>
        <NFormItem label="储丝柜出料开始时间" path="distimebegin">
          <NDatePicker
ruoyi-plus-soybean/src/views/md/shift/modules/shift-operate-drawer.vue
@@ -154,7 +154,7 @@
    <NDrawerContent :title="title" :native-scrollbar="false" closable>
      <NForm ref="formRef" :model="model" :rules="rules">
        <NFormItem label="车间外键" path="wsId">
          <NInput v-model:value="model.wsId" placeholder="请输入车间外键" />
          <NInputNumber v-model:value="model.wsId as any" placeholder="请输入车间外键" class="w-full" />
        </NFormItem>
        <NFormItem label="编码" path="code">
          <NInput v-model:value="model.code" placeholder="请输入编码" />
@@ -169,13 +169,13 @@
          <NInput v-model:value="model.etim" placeholder="请输入班次结束时间" />
        </NFormItem>
        <NFormItem label="排序" path="seq">
          <NInput v-model:value="model.seq" placeholder="请输入排序" />
          <NInputNumber v-model:value="model.seq" placeholder="请输入排序" class="w-full" />
        </NFormItem>
        <NFormItem label="启用" path="enable">
          <NInput v-model:value="model.enable" placeholder="请输入启用" />
          <NInputNumber v-model:value="model.enable" placeholder="请输入启用" class="w-full" />
        </NFormItem>
        <NFormItem label="删除" path="del">
          <NInput v-model:value="model.del" placeholder="请输入删除" />
          <NInputNumber v-model:value="model.del" placeholder="请输入删除" class="w-full" />
        </NFormItem>
        <NFormItem label="创建用户" path="createUserName">
          <NInput v-model:value="model.createUserName" placeholder="请输入创建用户" />
ruoyi-plus-soybean/src/views/md/shift/modules/shift-search.vue
@@ -49,7 +49,7 @@
              <NInput v-model:value="model.name" placeholder="请输入名称" />
            </NFormItemGi>
            <NFormItemGi span="24 s:12 m:6" label="启用" label-width="auto" path="enable" class="pr-24px">
              <NInput v-model:value="model.enable" placeholder="请输入启用" />
              <NInputNumber v-model:value="model.enable" placeholder="请输入启用" class="w-full" />
            </NFormItemGi>
            <NFormItemGi :show-feedback="false" span="24" class="pr-24px">
              <NSpace class="w-full" justify="end">
ruoyi-plus-soybean/src/views/qm/batch/modules/batch-operate-drawer.vue
@@ -344,7 +344,7 @@
          <NInput v-model:value="model.enabled" placeholder="请输入启用标志" />
        </NFormItem>
        <NFormItem label="到货总量" path="totalNum">
          <NInput v-model:value="model.totalNum" placeholder="请输入到货总量" />
          <NInputNumber v-model:value="model.totalNum" placeholder="请输入到货总量" class="w-full" />
        </NFormItem>
        <NFormItem label="综合判定" path="results">
          <NInput v-model:value="model.results" placeholder="请输入综合判定" />
@@ -395,7 +395,7 @@
          />
        </NFormItem>
        <NFormItem label="删除标志" path="deleted">
          <NInput v-model:value="model.deleted" placeholder="请输入删除标志" />
          <NInputNumber v-model:value="model.deleted" placeholder="请输入删除标志" class="w-full" />
        </NFormItem>
        <NFormItem label="批次描述" path="batchDes">
          <NInput v-model:value="model.batchDes" placeholder="请输入批次描述" />
@@ -413,13 +413,13 @@
          <NInput v-model:value="model.boxno" placeholder="请输入装箱号" />
        </NFormItem>
        <NFormItem label="父批次号" path="pid">
          <NInput v-model:value="model.pid" placeholder="请输入父批次号" />
          <NInput v-model:value="model.pid as any" placeholder="请输入父批次号" />
        </NFormItem>
        <NFormItem label="复核人" path="reviewer">
          <NInput v-model:value="model.reviewer" placeholder="请输入复核人" />
        </NFormItem>
        <NFormItem label="复检次数" path="rvcount">
          <NInput v-model:value="model.rvcount" placeholder="请输入复检次数" />
          <NInputNumber v-model:value="model.rvcount" placeholder="请输入复检次数" class="w-full" />
        </NFormItem>
        <NFormItem label="批次状态" path="state">
          <NInput v-model:value="model.state" placeholder="请输入批次状态" />
ruoyi-plus-soybean/src/views/qm/batch/modules/batch-search.vue
@@ -116,7 +116,7 @@
              />
            </NFormItemGi>
            <NFormItemGi span="24 s:12 m:8" label="删除标志" label-width="auto" path="deleted" class="pr-24px">
              <NInput v-model:value="model.deleted" placeholder="请输入删除标志" />
              <NInputNumber v-model:value="model.deleted" placeholder="请输入删除标志" class="w-full" />
            </NFormItemGi>
            <NFormItemGi span="24 s:12 m:8" label="类别" label-width="auto" path="category" class="pr-24px">
              <NSelect v-model:value="model.category" :options="categoryOptions" placeholder="请选择类别" clearable />