酸轧数据同步,轧辊新增产线新增各种产线

This commit is contained in:
2026-05-14 15:12:30 +08:00
parent 4a5b9544a4
commit 49561a125e
41 changed files with 2054 additions and 319 deletions

View File

@@ -0,0 +1,20 @@
-- =====================================================================
-- 轧辊管理多产线支持:为 4 张轧辊表添加 line_id 外键列
-- 存量数据默认为 NULL业务侧按需补填或忽略NULL 表示未归属产线)
-- =====================================================================
ALTER TABLE mes_roll_info
ADD COLUMN line_id BIGINT NULL COMMENT '产线ID关联 wms_production_line.line_id' AFTER roll_id,
ADD KEY idx_line_id (line_id);
ALTER TABLE mes_roll_change
ADD COLUMN line_id BIGINT NULL COMMENT '产线ID关联 wms_production_line.line_id' AFTER change_id,
ADD KEY idx_line_stand (line_id, stand_no);
ALTER TABLE mes_roll_standby
ADD COLUMN line_id BIGINT NULL COMMENT '产线ID关联 wms_production_line.line_id' AFTER standby_id,
ADD KEY idx_line_stand (line_id, stand_no);
ALTER TABLE mes_roll_grind
ADD COLUMN line_id BIGINT NULL COMMENT '产线ID关联 wms_production_line.line_id' AFTER grind_id,
ADD KEY idx_line_id (line_id);

View File

@@ -38,21 +38,21 @@ public class MesRollChangeController extends BaseController {
}
/**
* 查询指定机架当前在机轧辊(最近一次换辊记录)
* GET /mes/rollChange/current?standNo=1%23
* 查询指定产线+机架当前在机轧辊(最近一次换辊记录)
* GET /mes/rollChange/current?lineId=xxx&standNo=1%23
*/
@GetMapping("/current")
public R<MesRollChangeVo> current(@RequestParam String standNo) {
return R.ok(iMesRollChangeService.queryCurrentByStand(standNo));
public R<MesRollChangeVo> current(Long lineId, @RequestParam String standNo) {
return R.ok(iMesRollChangeService.queryCurrentByStand(lineId, standNo));
}
/**
* 查询各机架各辊位实时工作绩效(工作长度/卷数/重量)
* GET /mes/rollChange/performance
* 查询指定产线各机架各辊位实时工作绩效(工作长度/卷数/重量)
* GET /mes/rollChange/performance?lineId=xxx
*/
@GetMapping("/performance")
public R<Map<String, Map<String, Object>>> performance() {
return R.ok(iMesRollChangeService.queryRollPerformance());
public R<Map<String, Map<String, Object>>> performance(Long lineId) {
return R.ok(iMesRollChangeService.queryRollPerformance(lineId));
}
/** 详情 */

View File

@@ -41,16 +41,16 @@ public class MesRollInfoController extends BaseController {
return iMesRollInfoService.queryPageList(bo, pageQuery);
}
/** 状态统计卡片数据 */
/** 状态统计卡片数据lineId 为空时统计全部产线) */
@GetMapping("/stats")
public R<Map<String, Object>> stats() {
return R.ok(iMesRollInfoService.queryStatusStats());
public R<Map<String, Object>> stats(Long lineId) {
return R.ok(iMesRollInfoService.queryStatusStats(lineId));
}
/** 轧辊编号下拉列表(按辊型 + 状态过滤,均可为空) */
/** 轧辊编号下拉列表(lineId / rollType / status 均可为空) */
@GetMapping("/options")
public R<List<String>> options(String rollType, String status) {
return R.ok(iMesRollInfoService.queryRollNoList(rollType, status));
public R<List<String>> options(Long lineId, String rollType, String status) {
return R.ok(iMesRollInfoService.queryRollNoList(lineId, rollType, status));
}
/** 详情 */
@@ -90,13 +90,13 @@ public class MesRollInfoController extends BaseController {
}
/**
* 从换辊记录同步在线状态
* 从换辊记录同步在线状态lineId 为空时同步全部产线)
* 将当前各机架在机轧辊的状态强制修正为 Online解决状态数据不一致问题
*/
@Log(title = "轧辊库-状态同步", businessType = BusinessType.UPDATE)
@PostMapping("/syncStatus")
public R<Void> syncStatus() {
iMesRollInfoService.syncStatusFromChange();
public R<Void> syncStatus(Long lineId) {
iMesRollInfoService.syncStatusFromChange(lineId);
return R.ok();
}

View File

@@ -28,12 +28,12 @@ public class MesRollStandbyController extends BaseController {
private final IMesRollStandbyService iMesRollStandbyService;
/**
* 查询指定机架的下批轧辊列表
* GET /mes/rollStandby/list?standNo=1%23
* 查询指定产线+机架的下批轧辊列表
* GET /mes/rollStandby/list?lineId=xxx&standNo=1%23
*/
@GetMapping("/list")
public R<List<MesRollStandbyVo>> list(@RequestParam String standNo) {
return R.ok(iMesRollStandbyService.queryByStand(standNo));
public R<List<MesRollStandbyVo>> list(Long lineId, @RequestParam String standNo) {
return R.ok(iMesRollStandbyService.queryByStand(lineId, standNo));
}
/** 详情 */
@@ -66,12 +66,12 @@ public class MesRollStandbyController extends BaseController {
}
/**
* 清空指定机架的全部下批轧辊
* DELETE /mes/rollStandby/clear?standNo=1%23
* 清空指定产线+机架的全部下批轧辊
* DELETE /mes/rollStandby/clear?lineId=xxx&standNo=1%23
*/
@Log(title = "下批轧辊", businessType = BusinessType.DELETE)
@DeleteMapping("/clear")
public R<Void> clear(@RequestParam String standNo) {
return toAjax(iMesRollStandbyService.clearByStand(standNo));
public R<Void> clear(Long lineId, @RequestParam String standNo) {
return toAjax(iMesRollStandbyService.clearByStand(lineId, standNo));
}
}

View File

@@ -12,8 +12,7 @@ import java.util.Date;
/**
* 换辊记录 mes_roll_change
* 四辊轧机,双机架1# / 2#
* 每次换辊记录 4 支辊:上工作辊、下工作辊、上支撑辊、下支撑辊
* 四辊轧机,支持多产线,每次换辊记录 4 支辊:上工作辊、下工作辊、上支撑辊、下支撑辊
*/
@Data
@EqualsAndHashCode(callSuper = true)
@@ -25,6 +24,9 @@ public class MesRollChange extends BaseEntity {
@TableId(value = "change_id")
private Long changeId;
/** 产线ID */
private Long lineId;
/** 换辊编号 */
private String changeNo;

View File

@@ -46,6 +46,9 @@ public class MesRollGrind extends BaseEntity {
@TableId(value = "grind_id")
private Long grindId;
/** 产线ID */
private Long lineId;
/** 轧辊ID */
private Long rollId;

View File

@@ -23,6 +23,9 @@ public class MesRollInfo extends BaseEntity {
@TableId(value = "roll_id")
private Long rollId;
/** 产线ID */
private Long lineId;
/** 轧辊编号 */
private String rollNo;

View File

@@ -30,6 +30,9 @@ public class MesRollStandby extends BaseEntity {
@TableId(value = "standby_id")
private Long standbyId;
/** 产线ID */
private Long lineId;
/** 机架号1# / 2# */
private String standNo;

View File

@@ -18,6 +18,9 @@ public class MesRollChangeBo extends BaseEntity {
private Long changeId;
/** 产线ID查询过滤 / 新增归属) */
private Long lineId;
private String changeNo;
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")

View File

@@ -18,6 +18,9 @@ public class MesRollGrindBo extends BaseEntity {
private Long grindId;
/** 产线ID查询过滤用 */
private Long lineId;
@NotNull(message = "轧辊ID不能为空")
private Long rollId;

View File

@@ -22,6 +22,9 @@ public class MesRollInfoBo extends BaseEntity {
@NotNull(message = "轧辊ID不能为空", groups = EditGroup.class)
private Long rollId;
/** 产线ID查询过滤 / 新增归属) */
private Long lineId;
@NotBlank(message = "轧辊编号不能为空", groups = {AddGroup.class, EditGroup.class})
private String rollNo;

View File

@@ -19,6 +19,9 @@ public class MesRollStandbyBo extends BaseEntity {
private Long standbyId;
/** 产线ID查询过滤 / 新增归属) */
private Long lineId;
/** 机架号1# / 2# */
@NotBlank(message = "机架号不能为空", groups = AddGroup.class)
private String standNo;

View File

@@ -17,6 +17,8 @@ public class MesRollChangeVo {
private Long changeId;
private Long lineId;
private String changeNo;
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")

View File

@@ -19,6 +19,12 @@ public class MesRollInfoVo {
@ExcelProperty("轧辊ID")
private Long rollId;
@ExcelProperty("产线ID")
private Long lineId;
@ExcelProperty("产线名称")
private String lineName;
@ExcelProperty("轧辊编号")
private String rollNo;

View File

@@ -16,6 +16,8 @@ public class MesRollStandbyVo {
private Long standbyId;
private Long lineId;
/** 机架号1# / 2# */
private String standNo;

View File

@@ -13,27 +13,30 @@ import java.util.Map;
*/
public interface MesRollChangeMapper extends BaseMapperPlus<MesRollChangeMapper, MesRollChange, MesRollChangeVo> {
/** 查询指定机架最新一次换辊记录(当前在机轧辊) */
MesRollChangeVo selectLatestByStand(@Param("standNo") String standNo);
/** 查询指定产线+机架最新一次换辊记录(当前在机轧辊) */
MesRollChangeVo selectLatestByStand(@Param("lineId") Long lineId, @Param("standNo") String standNo);
/**
* 按辊位查询该位置最新一次有记录的换辊(支持部分换辊)
* posType: upperWr / lowerWr / upperBr / lowerBr
*/
MesRollChangeVo selectLatestByStandAndPosition(@Param("standNo") String standNo,
MesRollChangeVo selectLatestByStandAndPosition(@Param("lineId") Long lineId,
@Param("standNo") String standNo,
@Param("posType") String posType);
/**
* 组合查询机架各辊位当前实际在机状态(每个位置独立取最新非空记录)
* 组合查询指定产线+机架各辊位当前实际在机状态(每个位置独立取最新非空记录)
* 返回 mapkey 同 MesRollChangeVo 字段名camelCase
*/
Map<String, Object> selectCurrentStateByStand(@Param("standNo") String standNo);
Map<String, Object> selectCurrentStateByStand(@Param("lineId") Long lineId, @Param("standNo") String standNo);
/**
* 查询同机架下一次换辊时间(任意辊位有换辊即触发)
* 查询同产线+机架下一次换辊时间(任意辊位有换辊即触发)
* 返回 null 表示该辊仍在机
*/
Date selectNextChangeTime(@Param("standNo") String standNo, @Param("changeTime") Date changeTime);
Date selectNextChangeTime(@Param("lineId") Long lineId,
@Param("standNo") String standNo,
@Param("changeTime") Date changeTime);
/**
* 统计指定时间区间内的卷料生产统计:

View File

@@ -15,13 +15,17 @@ public interface MesRollInfoMapper extends BaseMapperPlus<MesRollInfoMapper, Mes
/**
* 按状态统计数量(用于总览卡片)
* lineId 为 null 时统计全部产线
*/
List<Map<String, Object>> selectStatusStats();
List<Map<String, Object>> selectStatusStats(@Param("lineId") Long lineId);
/**
* 查询轧辊编号列表(用于下拉选择,可按辊型状态过滤)
* 查询轧辊编号列表(用于下拉选择,可按产线、辊型状态过滤)
* lineId 为 null 时不过滤产线
*/
List<String> selectRollNoList(@Param("rollType") String rollType, @Param("status") String status);
List<String> selectRollNoList(@Param("lineId") Long lineId,
@Param("rollType") String rollType,
@Param("status") String status);
/**
* 按轧辊编号查询(用于换辊时同步状态)

View File

@@ -12,9 +12,9 @@ import java.util.List;
*/
public interface MesRollStandbyMapper extends BaseMapperPlus<MesRollStandbyMapper, MesRollStandby, MesRollStandbyVo> {
/** 按机架查询下批轧辊列表 */
List<MesRollStandbyVo> selectByStand(@Param("standNo") String standNo);
/** 按产线+机架查询下批轧辊列表 */
List<MesRollStandbyVo> selectByStand(@Param("lineId") Long lineId, @Param("standNo") String standNo);
/** 清空机架的下批轧辊 */
int clearByStand(@Param("standNo") String standNo);
/** 清空指定产线+机架的下批轧辊 */
int clearByStand(@Param("lineId") Long lineId, @Param("standNo") String standNo);
}

View File

@@ -15,8 +15,8 @@ public interface IMesRollChangeService {
MesRollChangeVo queryById(Long changeId);
/** 查询指定机架当前在机轧辊(最新一次换辊记录) */
MesRollChangeVo queryCurrentByStand(String standNo);
/** 查询指定产线+机架当前在机轧辊(最新一次换辊记录) */
MesRollChangeVo queryCurrentByStand(Long lineId, String standNo);
TableDataInfo<MesRollChangeVo> queryPageList(MesRollChangeBo bo, PageQuery pageQuery);
@@ -28,9 +28,9 @@ public interface IMesRollChangeService {
Boolean deleteWithValidByIds(Collection<Long> ids, Boolean isValid);
/**
* 查询各机架各辊位的实时工作绩效(工作长度/卷数/重量)
* 查询指定产线各机架各辊位的实时工作绩效(工作长度/卷数/重量)
* 返回结构: { posType -> { standNo -> { rollNo, workLength, coilCount, totalWeight } } }
* posType: upperBr / upperWr / lowerWr / lowerBr
*/
Map<String, Map<String, Object>> queryRollPerformance();
Map<String, Map<String, Object>> queryRollPerformance(Long lineId);
}

View File

@@ -20,11 +20,16 @@ public interface IMesRollInfoService {
List<MesRollInfoVo> queryList(MesRollInfoBo bo);
/** 按状态统计(总览卡片) */
Map<String, Object> queryStatusStats();
/**
* 按状态统计(总览卡片)
* lineId 为 null 时统计全部产线
*/
Map<String, Object> queryStatusStats(Long lineId);
/** 轧辊编号下拉列表status 为 null 时不过滤状态) */
List<String> queryRollNoList(String rollType, String status);
/**
* 轧辊编号下拉列表lineId / rollType / status 均可为 null不过滤对应维度
*/
List<String> queryRollNoList(Long lineId, String rollType, String status);
Long insertByBo(MesRollInfoBo bo);
@@ -40,6 +45,7 @@ public interface IMesRollInfoService {
* 每个机架最新一次换辊记录中的 4 支辊 → Online
* 下批辊列表中的辊 → Standby已由 addStandby 维护,此处不重复处理);
* 其余不变,仅修正当前在机辊的状态数据不一致问题。
* lineId 为 null 时同步全部产线;指定 lineId 时仅同步该产线。
*/
void syncStatusFromChange();
void syncStatusFromChange(Long lineId);
}

View File

@@ -12,8 +12,8 @@ public interface IMesRollStandbyService {
MesRollStandbyVo queryById(Long standbyId);
/** 查询指定机架的下批轧辊列表 */
List<MesRollStandbyVo> queryByStand(String standNo);
/** 查询指定产线+机架的下批轧辊列表 */
List<MesRollStandbyVo> queryByStand(Long lineId, String standNo);
/** 新增下批轧辊,同步将该辊状态更新为 Standby */
Long addStandby(MesRollStandbyBo bo);
@@ -24,6 +24,6 @@ public interface IMesRollStandbyService {
/** 删除某条下批轧辊,同步将该辊状态恢复为 Offline */
Boolean deleteById(Long standbyId);
/** 清空指定机架的全部下批轧辊,并将对应辊状态恢复为 Offline */
Boolean clearByStand(String standNo);
/** 清空指定产线+机架的全部下批轧辊,并将对应辊状态恢复为 Offline */
Boolean clearByStand(Long lineId, String standNo);
}

View File

@@ -44,11 +44,12 @@ public class MesRollChangeServiceImpl implements IMesRollChangeService {
}
@Override
public MesRollChangeVo queryCurrentByStand(String standNo) {
public MesRollChangeVo queryCurrentByStand(Long lineId, String standNo) {
// 组合各辊位最新状态,转换为 VO
Map<String, Object> state = baseMapper.selectCurrentStateByStand(standNo);
Map<String, Object> state = baseMapper.selectCurrentStateByStand(lineId, standNo);
if (state == null) return null;
MesRollChangeVo vo = new MesRollChangeVo();
vo.setLineId(lineId);
vo.setStandNo(standNo);
vo.setUpperWrNo(str(state.get("upperWrNo")));
vo.setUpperWrDia(decimal(state.get("upperWrDia")));
@@ -70,7 +71,7 @@ public class MesRollChangeServiceImpl implements IMesRollChangeService {
Page<MesRollChangeVo> result = baseMapper.selectVoPage(pageQuery.build(), lqw);
// 为每条换辊记录计算:工作长度、卷数、重量
for (MesRollChangeVo vo : result.getRecords()) {
Date endTime = baseMapper.selectNextChangeTime(vo.getStandNo(), vo.getChangeTime());
Date endTime = baseMapper.selectNextChangeTime(vo.getLineId(), vo.getStandNo(), vo.getChangeTime());
Map<String, Object> stats = baseMapper.selectCoilStats(vo.getChangeTime(), endTime);
fillStatsToVo(vo, stats);
}
@@ -81,12 +82,12 @@ public class MesRollChangeServiceImpl implements IMesRollChangeService {
private static final List<String> STANDS = Arrays.asList("1#", "2#");
@Override
public Map<String, Map<String, Object>> queryRollPerformance() {
public Map<String, Map<String, Object>> queryRollPerformance(Long lineId) {
Map<String, Map<String, Object>> result = new HashMap<>(4);
for (String posType : POS_TYPES) {
Map<String, Object> byStand = new HashMap<>(2);
for (String standNo : STANDS) {
MesRollChangeVo rec = baseMapper.selectLatestByStandAndPosition(standNo, posType);
MesRollChangeVo rec = baseMapper.selectLatestByStandAndPosition(lineId, standNo, posType);
Map<String, Object> cell = new HashMap<>(6);
if (rec != null) {
cell.put("rollNo", getRollNoByPos(rec, posType));
@@ -106,9 +107,9 @@ public class MesRollChangeServiceImpl implements IMesRollChangeService {
}
/** 若该辊位要换新辊,则把当前在机的旧辊下线 */
private void offlineIfReplaced(String standNo, String posType, String newRollNo) {
private void offlineIfReplaced(Long lineId, String standNo, String posType, String newRollNo) {
if (StringUtils.isBlank(newRollNo)) return;
MesRollChangeVo cur = baseMapper.selectLatestByStandAndPosition(standNo, posType);
MesRollChangeVo cur = baseMapper.selectLatestByStandAndPosition(lineId, standNo, posType);
if (cur != null) {
String oldRollNo = getRollNoByPos(cur, posType);
if (StringUtils.isNotBlank(oldRollNo)) {
@@ -158,6 +159,7 @@ public class MesRollChangeServiceImpl implements IMesRollChangeService {
private LambdaQueryWrapper<MesRollChange> buildQueryWrapper(MesRollChangeBo bo) {
LambdaQueryWrapper<MesRollChange> lqw = Wrappers.lambdaQuery();
lqw.eq(bo.getLineId() != null, MesRollChange::getLineId, bo.getLineId());
lqw.eq(StringUtils.isNotBlank(bo.getStandNo()), MesRollChange::getStandNo, bo.getStandNo());
lqw.eq(StringUtils.isNotBlank(bo.getChangeType()), MesRollChange::getChangeType, bo.getChangeType());
lqw.eq(StringUtils.isNotBlank(bo.getChangeStatus()), MesRollChange::getChangeStatus, bo.getChangeStatus());
@@ -184,10 +186,10 @@ public class MesRollChangeServiceImpl implements IMesRollChangeService {
add.setChangeType("三级换辊");
}
// 按辊位独立换辊:只下线被替换的那个位置的旧辊
offlineIfReplaced(add.getStandNo(), "upperWr", add.getUpperWrNo());
offlineIfReplaced(add.getStandNo(), "lowerWr", add.getLowerWrNo());
offlineIfReplaced(add.getStandNo(), "upperBr", add.getUpperBrNo());
offlineIfReplaced(add.getStandNo(), "lowerBr", add.getLowerBrNo());
offlineIfReplaced(add.getLineId(), add.getStandNo(), "upperWr", add.getUpperWrNo());
offlineIfReplaced(add.getLineId(), add.getStandNo(), "lowerWr", add.getLowerWrNo());
offlineIfReplaced(add.getLineId(), add.getStandNo(), "upperBr", add.getUpperBrNo());
offlineIfReplaced(add.getLineId(), add.getStandNo(), "lowerBr", add.getLowerBrNo());
baseMapper.insert(add);

View File

@@ -52,8 +52,8 @@ public class MesRollInfoServiceImpl implements IMesRollInfoService {
}
@Override
public Map<String, Object> queryStatusStats() {
List<Map<String, Object>> rows = baseMapper.selectStatusStats();
public Map<String, Object> queryStatusStats(Long lineId) {
List<Map<String, Object>> rows = baseMapper.selectStatusStats(lineId);
Map<String, Object> result = new HashMap<String, Object>(8);
int total = 0;
for (Map<String, Object> row : rows) {
@@ -67,12 +67,13 @@ public class MesRollInfoServiceImpl implements IMesRollInfoService {
}
@Override
public List<String> queryRollNoList(String rollType, String status) {
return baseMapper.selectRollNoList(rollType, status);
public List<String> queryRollNoList(Long lineId, String rollType, String status) {
return baseMapper.selectRollNoList(lineId, rollType, status);
}
private LambdaQueryWrapper<MesRollInfo> buildQueryWrapper(MesRollInfoBo bo) {
LambdaQueryWrapper<MesRollInfo> lqw = Wrappers.lambdaQuery();
lqw.eq(bo.getLineId() != null, MesRollInfo::getLineId, bo.getLineId());
lqw.like(StringUtils.isNotBlank(bo.getRollNo()), MesRollInfo::getRollNo, bo.getRollNo());
lqw.eq(StringUtils.isNotBlank(bo.getRollType()), MesRollInfo::getRollType, bo.getRollType());
lqw.eq(StringUtils.isNotBlank(bo.getStatus()), MesRollInfo::getStatus, bo.getStatus());
@@ -120,11 +121,11 @@ public class MesRollInfoServiceImpl implements IMesRollInfoService {
}
@Override
public void syncStatusFromChange() {
public void syncStatusFromChange(Long lineId) {
// 按辊位独立取最新在机辊,确保各位置状态正确
for (String standNo : Arrays.asList("1#", "2#")) {
for (String posType : Arrays.asList("upperWr", "lowerWr", "upperBr", "lowerBr")) {
MesRollChangeVo rec = rollChangeMapper.selectLatestByStandAndPosition(standNo, posType);
MesRollChangeVo rec = rollChangeMapper.selectLatestByStandAndPosition(lineId, standNo, posType);
if (rec == null) continue;
String rollNo = getRollNoByPos(rec, posType);
if (StringUtils.isNotBlank(rollNo)) {

View File

@@ -31,8 +31,8 @@ public class MesRollStandbyServiceImpl implements IMesRollStandbyService {
}
@Override
public List<MesRollStandbyVo> queryByStand(String standNo) {
return baseMapper.selectByStand(standNo);
public List<MesRollStandbyVo> queryByStand(Long lineId, String standNo) {
return baseMapper.selectByStand(lineId, standNo);
}
@Override
@@ -70,10 +70,10 @@ public class MesRollStandbyServiceImpl implements IMesRollStandbyService {
@Override
@Transactional(rollbackFor = Exception.class)
public Boolean clearByStand(String standNo) {
public Boolean clearByStand(Long lineId, String standNo) {
// 先查出所有辊号再清空
List<MesRollStandbyVo> list = baseMapper.selectByStand(standNo);
int rows = baseMapper.clearByStand(standNo);
List<MesRollStandbyVo> list = baseMapper.selectByStand(lineId, standNo);
int rows = baseMapper.clearByStand(lineId, standNo);
for (MesRollStandbyVo vo : list) {
if (StringUtils.isNotBlank(vo.getRollNo())) {
// 只有仍处于 Standby 状态时才回退为 Offline换辊后已变 Online 的不动)

View File

@@ -2,26 +2,28 @@
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.klp.mes.roll.mapper.MesRollChangeMapper">
<!-- 查询指定机架最新一次换辊记录(即当前在机轧辊) -->
<!-- 查询指定产线+机架最新一次换辊记录(即当前在机轧辊) -->
<select id="selectLatestByStand" resultType="com.klp.mes.roll.domain.vo.MesRollChangeVo">
SELECT change_id, change_no, change_time, stand_no, change_type, change_status, operator,
SELECT change_id, line_id, change_no, change_time, stand_no, change_type, change_status, operator,
upper_wr_no, upper_wr_dia, lower_wr_no, lower_wr_dia,
upper_br_no, upper_br_dia, lower_br_no, lower_br_dia,
remark, create_time
FROM mes_roll_change
WHERE del_flag = 0
AND stand_no = #{standNo}
<if test="lineId != null">AND line_id = #{lineId}</if>
ORDER BY change_time DESC, change_id DESC
LIMIT 1
</select>
<!-- 按辊位查最新非空换辊记录(支持部分换辊) -->
<select id="selectLatestByStandAndPosition" resultType="com.klp.mes.roll.domain.vo.MesRollChangeVo">
SELECT change_id, change_no, change_time, stand_no, operator,
SELECT change_id, line_id, change_no, change_time, stand_no, operator,
upper_wr_no, upper_wr_dia, lower_wr_no, lower_wr_dia,
upper_br_no, upper_br_dia, lower_br_no, lower_br_dia
FROM mes_roll_change
WHERE del_flag = 0 AND stand_no = #{standNo}
<if test="lineId != null">AND line_id = #{lineId}</if>
<choose>
<when test="posType == 'upperWr'">AND upper_wr_no IS NOT NULL AND upper_wr_no != ''</when>
<when test="posType == 'lowerWr'">AND lower_wr_no IS NOT NULL AND lower_wr_no != ''</when>
@@ -35,24 +37,25 @@
<!-- 组合查询各辊位当前实际在机状态(每个位置独立取最新非空记录) -->
<select id="selectCurrentStateByStand" resultType="map">
SELECT
(SELECT upper_wr_no FROM mes_roll_change WHERE stand_no = #{standNo} AND del_flag = 0 AND upper_wr_no IS NOT NULL AND upper_wr_no != '' ORDER BY change_time DESC, change_id DESC LIMIT 1) AS upperWrNo,
(SELECT upper_wr_dia FROM mes_roll_change WHERE stand_no = #{standNo} AND del_flag = 0 AND upper_wr_no IS NOT NULL AND upper_wr_no != '' ORDER BY change_time DESC, change_id DESC LIMIT 1) AS upperWrDia,
(SELECT lower_wr_no FROM mes_roll_change WHERE stand_no = #{standNo} AND del_flag = 0 AND lower_wr_no IS NOT NULL AND lower_wr_no != '' ORDER BY change_time DESC, change_id DESC LIMIT 1) AS lowerWrNo,
(SELECT lower_wr_dia FROM mes_roll_change WHERE stand_no = #{standNo} AND del_flag = 0 AND lower_wr_no IS NOT NULL AND lower_wr_no != '' ORDER BY change_time DESC, change_id DESC LIMIT 1) AS lowerWrDia,
(SELECT upper_br_no FROM mes_roll_change WHERE stand_no = #{standNo} AND del_flag = 0 AND upper_br_no IS NOT NULL AND upper_br_no != '' ORDER BY change_time DESC, change_id DESC LIMIT 1) AS upperBrNo,
(SELECT upper_br_dia FROM mes_roll_change WHERE stand_no = #{standNo} AND del_flag = 0 AND upper_br_no IS NOT NULL AND upper_br_no != '' ORDER BY change_time DESC, change_id DESC LIMIT 1) AS upperBrDia,
(SELECT lower_br_no FROM mes_roll_change WHERE stand_no = #{standNo} AND del_flag = 0 AND lower_br_no IS NOT NULL AND lower_br_no != '' ORDER BY change_time DESC, change_id DESC LIMIT 1) AS lowerBrNo,
(SELECT lower_br_dia FROM mes_roll_change WHERE stand_no = #{standNo} AND del_flag = 0 AND lower_br_no IS NOT NULL AND lower_br_no != '' ORDER BY change_time DESC, change_id DESC LIMIT 1) AS lowerBrDia,
(SELECT MAX(change_time) FROM mes_roll_change WHERE stand_no = #{standNo} AND del_flag = 0) AS changeTime
(SELECT upper_wr_no FROM mes_roll_change WHERE stand_no = #{standNo} AND del_flag = 0 <if test="lineId != null">AND line_id = #{lineId}</if> AND upper_wr_no IS NOT NULL AND upper_wr_no != '' ORDER BY change_time DESC, change_id DESC LIMIT 1) AS upperWrNo,
(SELECT upper_wr_dia FROM mes_roll_change WHERE stand_no = #{standNo} AND del_flag = 0 <if test="lineId != null">AND line_id = #{lineId}</if> AND upper_wr_no IS NOT NULL AND upper_wr_no != '' ORDER BY change_time DESC, change_id DESC LIMIT 1) AS upperWrDia,
(SELECT lower_wr_no FROM mes_roll_change WHERE stand_no = #{standNo} AND del_flag = 0 <if test="lineId != null">AND line_id = #{lineId}</if> AND lower_wr_no IS NOT NULL AND lower_wr_no != '' ORDER BY change_time DESC, change_id DESC LIMIT 1) AS lowerWrNo,
(SELECT lower_wr_dia FROM mes_roll_change WHERE stand_no = #{standNo} AND del_flag = 0 <if test="lineId != null">AND line_id = #{lineId}</if> AND lower_wr_no IS NOT NULL AND lower_wr_no != '' ORDER BY change_time DESC, change_id DESC LIMIT 1) AS lowerWrDia,
(SELECT upper_br_no FROM mes_roll_change WHERE stand_no = #{standNo} AND del_flag = 0 <if test="lineId != null">AND line_id = #{lineId}</if> AND upper_br_no IS NOT NULL AND upper_br_no != '' ORDER BY change_time DESC, change_id DESC LIMIT 1) AS upperBrNo,
(SELECT upper_br_dia FROM mes_roll_change WHERE stand_no = #{standNo} AND del_flag = 0 <if test="lineId != null">AND line_id = #{lineId}</if> AND upper_br_no IS NOT NULL AND upper_br_no != '' ORDER BY change_time DESC, change_id DESC LIMIT 1) AS upperBrDia,
(SELECT lower_br_no FROM mes_roll_change WHERE stand_no = #{standNo} AND del_flag = 0 <if test="lineId != null">AND line_id = #{lineId}</if> AND lower_br_no IS NOT NULL AND lower_br_no != '' ORDER BY change_time DESC, change_id DESC LIMIT 1) AS lowerBrNo,
(SELECT lower_br_dia FROM mes_roll_change WHERE stand_no = #{standNo} AND del_flag = 0 <if test="lineId != null">AND line_id = #{lineId}</if> AND lower_br_no IS NOT NULL AND lower_br_no != '' ORDER BY change_time DESC, change_id DESC LIMIT 1) AS lowerBrDia,
(SELECT MAX(change_time) FROM mes_roll_change WHERE stand_no = #{standNo} AND del_flag = 0 <if test="lineId != null">AND line_id = #{lineId}</if>) AS changeTime
</select>
<!-- 查询同机架在本次换辊之后最近一次换辊的时间 -->
<!-- 查询同产线+机架在本次换辊之后最近一次换辊的时间 -->
<select id="selectNextChangeTime" resultType="java.util.Date">
SELECT MIN(change_time)
FROM mes_roll_change
WHERE del_flag = 0
AND stand_no = #{standNo}
AND change_time > #{changeTime}
<if test="lineId != null">AND line_id = #{lineId}</if>
</select>
<!--

View File

@@ -8,6 +8,7 @@
SELECT status, COUNT(*) AS cnt
FROM mes_roll_info
WHERE del_flag = 0
<if test="lineId != null">AND line_id = #{lineId}</if>
GROUP BY status
</select>
@@ -15,6 +16,7 @@
SELECT roll_no FROM mes_roll_info
WHERE del_flag = 0
AND status != 'Scrapped'
<if test="lineId != null">AND line_id = #{lineId}</if>
<if test="rollType != null and rollType != ''">
AND roll_type = #{rollType}
</if>

View File

@@ -2,10 +2,10 @@
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.klp.mes.roll.mapper.MesRollStandbyMapper">
<!-- 按机架查询下批轧辊,附带辊位中文标签 -->
<!--产线+机架查询下批轧辊,附带辊位中文标签 -->
<select id="selectByStand" resultType="com.klp.mes.roll.domain.vo.MesRollStandbyVo">
SELECT
standby_id, stand_no, roll_no, roll_type, position,
standby_id, line_id, stand_no, roll_no, roll_type, position,
CASE
WHEN roll_type = 'BR' AND position = 'UP' THEN '上支撑辊'
WHEN roll_type = 'WR' AND position = 'UP' THEN '上工作辊'
@@ -17,17 +17,19 @@
FROM mes_roll_standby
WHERE del_flag = 0
AND stand_no = #{standNo}
<if test="lineId != null">AND line_id = #{lineId}</if>
ORDER BY
FIELD(roll_type, 'BR', 'WR', 'WR', 'BR'),
FIELD(position, 'UP', 'UP', 'DOWN', 'DOWN')
</select>
<!-- 逻辑删除指定机架所有下批轧辊(清空) -->
<!-- 逻辑删除指定产线+机架所有下批轧辊(清空) -->
<update id="clearByStand">
UPDATE mes_roll_standby
SET del_flag = 1
WHERE del_flag = 0
AND stand_no = #{standNo}
<if test="lineId != null">AND line_id = #{lineId}</if>
</update>
</mapper>

View File

@@ -1,6 +1,6 @@
import request from '@/utils/request'
// 查询换辊记录分页列表(支持按机架、类型、时间筛选)
// 查询换辊记录分页列表(支持按产线、机架、类型、时间筛选)
export function listRollChange(query) {
return request({
url: '/mes/rollChange/list',
@@ -9,20 +9,21 @@ export function listRollChange(query) {
})
}
// 查询指定机架当前在机轧辊(最近一次换辊记录)
export function getCurrentRolls(standNo) {
// 查询指定产线+机架当前在机轧辊(最近一次换辊记录)
export function getCurrentRolls(lineId, standNo) {
return request({
url: '/mes/rollChange/current',
method: 'get',
params: { standNo }
params: { lineId, standNo }
})
}
// 查询各机架各辊位实时工作绩效workLength/coilCount/totalWeight
export function getRollPerformance() {
// 查询指定产线各机架各辊位实时工作绩效workLength/coilCount/totalWeight
export function getRollPerformance(lineId) {
return request({
url: '/mes/rollChange/performance',
method: 'get'
method: 'get',
params: { lineId }
})
}

View File

@@ -9,20 +9,21 @@ export function listRollInfo(query) {
})
}
// 查询状态统计卡片
export function getRollStats() {
// 查询状态统计卡片lineId 为空时统计全部产线)
export function getRollStats(lineId) {
return request({
url: '/mes/rollInfo/stats',
method: 'get'
method: 'get',
params: { lineId }
})
}
// 查询轧辊编号下拉rollType/status 均可为空,不传则不过滤)
export function listRollOptions(rollType, status) {
// 查询轧辊编号下拉(lineId/rollType/status 均可为空,不传则不过滤)
export function listRollOptions(lineId, rollType, status) {
return request({
url: '/mes/rollInfo/options',
method: 'get',
params: { rollType, status }
params: { lineId, rollType, status }
})
}
@@ -68,11 +69,12 @@ export function scrapRollInfo(rollId) {
})
}
// 从换辊记录同步在线状态
export function syncRollStatus() {
// 从换辊记录同步在线状态lineId 为空时同步全部产线)
export function syncRollStatus(lineId) {
return request({
url: '/mes/rollInfo/syncStatus',
method: 'post'
method: 'post',
params: { lineId }
})
}

View File

@@ -1,11 +1,11 @@
import request from '@/utils/request'
// 查询指定机架下批轧辊列表
export function listRollStandby(standNo) {
// 查询指定产线+机架下批轧辊列表
export function listRollStandby(lineId, standNo) {
return request({
url: '/mes/rollStandby/list',
method: 'get',
params: { standNo }
params: { lineId, standNo }
})
}
@@ -43,11 +43,11 @@ export function delRollStandby(standbyId) {
})
}
// 清空指定机架全部下批轧辊
export function clearRollStandby(standNo) {
// 清空指定产线+机架全部下批轧辊
export function clearRollStandby(lineId, standNo) {
return request({
url: '/mes/rollStandby/clear',
method: 'delete',
params: { standNo }
params: { lineId, standNo }
})
}

View File

@@ -2,7 +2,21 @@
<div class="app-container grind-page">
<div class="grind-layout">
<!-- 左侧轧辊选择 -->
<!-- 产线 Tab -->
<div class="line-tabs">
<div
:class="['lt-item', filterLineId === null ? 'lt-item--active' : '']"
@click="handleLineTab(null)"
>全部</div>
<div
v-for="l in sortedProductionLines"
:key="l.lineId"
:class="['lt-item', filterLineId === l.lineId ? 'lt-item--active' : '']"
@click="handleLineTab(l.lineId)"
>{{ l.lineName }}</div>
</div>
<!-- 轧辊列表 -->
<div class="grind-left">
<el-card shadow="never" class="grind-card h-full">
<div slot="header" class="card-header">
@@ -11,12 +25,6 @@
<!-- 搜索过滤 -->
<div class="roll-filter">
<!-- 产线筛选暂时写死双机架 -->
<el-select v-model="filterLine" size="small" placeholder="选择产线"
style="width:100%;margin-bottom:8px" @change="filterRolls">
<el-option label="全部产线" value="" />
<el-option label="双机架产线" value="双机架" />
</el-select>
<el-input v-model="filterNo" size="small" placeholder="编号搜索" prefix-icon="el-icon-search"
clearable @input="filterRolls" style="margin-bottom:8px" />
<el-radio-group v-model="filterType" size="small" @change="filterRolls" style="margin-bottom:8px">
@@ -39,6 +47,7 @@
<span :class="['ri-status', 'st-' + r.status]">{{ statusLabel(r.status) }}</span>
<span class="ri-dia">φ{{ r.currentDia != null ? r.currentDia : r.initialDia }}</span>
</div>
<div v-if="filterLineId === null && r.lineName" class="ri-line">{{ r.lineName }}</div>
</div>
<div v-if="!rollLoading && filteredRolls.length === 0" class="roll-empty">暂无数据</div>
</div>
@@ -229,13 +238,18 @@
<script>
import { listRollInfo, getRollInfo } from '@/api/mes/roll/rollInfo'
import { listRollGrind, addRollGrind, updateRollGrind, delRollGrind, getMonthlyStats } from '@/api/mes/roll/rollGrind'
import { listProductionLine } from '@/api/wms/productionLine'
import rollLineMixin from '../rollLineMixin'
export default {
name: 'GrindRoom',
mixins: [rollLineMixin],
data() {
return {
// 产线定义(暂写死,后续接后端)
LINES: [{ label: '双机架产线', value: '双机架' }],
// 产线列表
productionLines: [],
filterLineId: null,
lineTabOrder: [], // 本地记录的点击顺序(最近点过的在前)
// 左侧辊列表
rollLoading: false,
@@ -243,7 +257,6 @@ export default {
filteredRolls: [],
filterNo: '',
filterType: '',
filterLine: '',
// 右侧选中辊
selectedRollId: null,
@@ -270,6 +283,19 @@ export default {
return this.$store.state.user.name || this.$store.getters.name || ''
},
// 产线 tab 按本地点击顺序排序
sortedProductionLines() {
const order = this.lineTabOrder
return [...this.productionLines].sort((a, b) => {
const ai = order.indexOf(a.lineId)
const bi = order.indexOf(b.lineId)
if (ai === -1 && bi === -1) return 0
if (ai === -1) return 1
if (bi === -1) return -1
return ai - bi
})
},
// 表格数据:新增时在顶部插入一个编辑行占位
tableData() {
if (this.editRow && this.editRow.__isNew) {
@@ -279,26 +305,46 @@ export default {
}
},
created() {
this.loadRolls()
},
created() {},
methods: {
onLineResolved() {
this.filterLineId = this.lineId
const uid = this.$store.state.user?.userId || 0
try { this.lineTabOrder = JSON.parse(localStorage.getItem(`grind_line_order_${uid}`) || '[]') } catch {}
listProductionLine({ pageNum: 1, pageSize: 100 }).then(res => {
this.productionLines = res.rows || []
})
this.loadRolls()
},
// ── 轧辊列表 ──────────────────────────────────────
loadRolls() {
this.rollLoading = true
listRollInfo({ pageNum: 1, pageSize: 500 }).then(res => {
listRollInfo({ pageNum: 1, pageSize: 500, lineId: this.filterLineId }).then(res => {
this.allRolls = res.rows || []
this.filterRolls()
}).finally(() => { this.rollLoading = false })
},
handleLineTab(lineId) {
if (this.filterLineId === lineId) return
this.filterLineId = lineId
if (lineId !== null) {
this.lineTabOrder = [lineId, ...this.lineTabOrder.filter(id => id !== lineId)]
const uid = this.$store.state.user?.userId || 0
localStorage.setItem(`grind_line_order_${uid}`, JSON.stringify(this.lineTabOrder))
}
if (this.editRow) this.cancelEdit()
this.selectedRollId = null
this.selectedRoll = null
this.grindList = []
this.loadRolls()
},
filterRolls() {
this.filteredRolls = this.allRolls.filter(r => {
const matchNo = !this.filterNo || r.rollNo.includes(this.filterNo)
const matchType = !this.filterType || r.rollType === this.filterType
// 后续 mes_roll_info 增加 line_code 字段后改为 r.lineCode === this.filterLine
const matchLine = !this.filterLine || this.filterLine === '双机架'
return matchNo && matchType && matchLine
return matchNo && matchType
})
},
selectRoll(r) {
@@ -428,8 +474,38 @@ export default {
.grind-page { background: #f4f5f7; height: 100%; }
.grind-layout { display: flex; gap: 12px; height: 100%; }
/* 左侧 */
.grind-left { width: 240px; flex-shrink: 0; }
/* 产线 Tab 列 */
.line-tabs {
width: 64px;
flex-shrink: 0;
display: flex;
flex-direction: column;
gap: 2px;
padding-top: 2px;
}
.lt-item {
padding: 10px 4px;
text-align: center;
font-size: 12px;
color: #5f6368;
background: #fff;
border: 1px solid #dcdee0;
border-radius: 4px;
cursor: pointer;
word-break: break-all;
line-height: 1.4;
transition: all .15s;
}
.lt-item:hover { background: #f0f6ff; color: #409eff; border-color: #c6d9f5; }
.lt-item--active {
background: #409eff;
color: #fff;
border-color: #409eff;
font-weight: 600;
}
/* 轧辊列表 */
.grind-left { width: 220px; flex-shrink: 0; }
.grind-right { flex: 1; min-width: 0; }
.grind-card { border: 1px solid #dcdee0; border-radius: 4px; }
@@ -451,6 +527,7 @@ export default {
.ri-no { font-family: 'Consolas', monospace; font-size: 13px; font-weight: 600; color: #1f2329; }
.ri-meta { display: flex; align-items: center; gap: 6px; margin-top: 3px; }
.ri-dia { font-size: 11px; color: #9aa0a6; }
.ri-line { font-size: 10px; color: #b0b3bb; margin-top: 2px; }
.ri-status { font-size: 11px; }
.roll-empty { text-align: center; color: #c0c4cc; padding: 20px 0; font-size: 12px; }

View File

@@ -46,14 +46,8 @@
<!-- 搜索栏 -->
<el-form :model="queryParams" ref="queryForm" size="small" :inline="true" v-show="showSearch" label-width="68px">
<el-form-item label="所属产线">
<!-- 暂时写死双机架后续接后端 line_code 字段 -->
<el-select v-model="queryParams.lineCode" placeholder="全部产线" clearable style="width:130px" @change="handleQuery">
<el-option label="双机架产线" value="双机架" />
</el-select>
</el-form-item>
<el-form-item label="轧辊编号" prop="rollNo">
<el-input v-model="queryParams.rollNo" placeholder="请输入轧辊编号" clearable @keyup.enter.native="handleQuery" />
<el-input v-model="queryParams.rollNo" placeholder="请输入轧辊编号" clearable @keyup.enter.native="handleQuery" />
</el-form-item>
<el-form-item label="辊型" prop="rollType">
<el-select v-model="queryParams.rollType" placeholder="全部" clearable style="width:120px">
@@ -225,9 +219,11 @@
<script>
import { listRollInfo, getRollStats, getRollInfo, addRollInfo, updateRollInfo, delRollInfo, scrapRollInfo, syncRollStatus } from '@/api/mes/roll/rollInfo'
import rollLineMixin from '../rollLineMixin'
export default {
name: 'RollOverview',
mixins: [rollLineMixin],
data() {
return {
loading: false,
@@ -242,7 +238,7 @@ export default {
queryParams: {
pageNum: 1,
pageSize: 20,
lineCode: undefined, // 产线筛选(暂写死双机架,后续接字段)
lineId: undefined,
rollNo: undefined,
rollType: undefined,
status: undefined
@@ -255,11 +251,12 @@ export default {
ids: []
}
},
created() {
this.getStats()
this.getList()
},
methods: {
onLineResolved() {
this.queryParams.lineId = this.lineId
this.getStats()
this.getList()
},
getList() {
this.loading = true
listRollInfo(this.queryParams).then(res => {
@@ -269,14 +266,14 @@ export default {
}).catch(() => { this.loading = false })
},
getStats() {
getRollStats().then(res => { this.stats = res.data || {} })
getRollStats(this.lineId).then(res => { this.stats = res.data || {} })
},
handleQuery() {
this.queryParams.pageNum = 1
this.getList()
},
resetQuery() {
this.queryParams.lineCode = undefined
this.queryParams.lineId = this.lineId
this.resetForm('queryForm')
this.handleQuery()
},
@@ -287,6 +284,7 @@ export default {
},
handleAdd() {
this.reset()
this.form.lineId = this.lineId
this.title = '新增轧辊'
this.open = true
},
@@ -320,7 +318,7 @@ export default {
},
handleSyncStatus() {
this.$modal.confirm('将从换辊记录中重新推算各机架当前在机轧辊的状态,确认执行?').then(() => {
return syncRollStatus()
return syncRollStatus(this.lineId)
}).then(() => {
this.getList()
this.getStats()

View File

@@ -0,0 +1,22 @@
/**
* 从 URL query.lineId 读取产线 ID供各轧辊页面共用。
* 组件覆盖 onLineResolved() 执行初始化加载。
*
* 菜单路径示例:/mes/roll/overview?lineId=1
*/
export default {
computed: {
lineId() {
const v = this.$route.query.lineId
return v != null ? Number(v) : null
}
},
created() {
this.onLineResolved()
},
methods: {
onLineResolved() {}
}
}

View File

@@ -0,0 +1,707 @@
<template>
<div class="app-container working-roll-page">
<!-- 顶部行作业辊一览2/3+ 可用轧辊1/3 -->
<div class="top-row">
<!-- 作业辊一览 -->
<el-card shadow="never" class="roll-table-card top-row__main">
<div slot="header" class="card-header">
<span class="card-title"><i class="el-icon-s-grid" /> 作业辊一览</span>
<span class="header-meta" v-if="current.changeTime">末次换辊{{ current.changeTime }}</span>
<span style="margin-left:auto;display:flex;align-items:center;gap:6px">
<el-button size="mini" icon="el-icon-refresh-right" @click="handleOpenChange">换辊</el-button>
<el-divider direction="vertical" />
<el-button type="success" size="mini" icon="el-icon-plus" @click="handleAddStandby">添加下批</el-button>
<el-button type="danger" size="mini" icon="el-icon-delete" @click="handleClearStandby"
:disabled="!standbyList.length">清空下批</el-button>
</span>
</div>
<el-table
ref="mainTable"
:data="mergedRows"
v-loading="loadingCurrent || loadingStandby"
:row-class-name="rowClass"
:span-method="mergeParamCol"
border
size="small"
style="width:100%"
>
<el-table-column label="参数" width="156" align="left">
<template slot-scope="scope">
<span :class="['param-label', scope.row.group]">{{ scope.row.label }}</span>
</template>
</el-table-column>
<el-table-column label="当前在机轧辊" align="center" min-width="120">
<template slot-scope="scope">
<param-cell :data="scope.row.cur" />
</template>
</el-table-column>
<el-table-column label="下批轧辊" align="center" min-width="120">
<template slot-scope="scope">
<div
:class="['sb-cell',
scope.row.sb && scope.row.sb.standbyId ? 'sb-cell--del' :
scope.row.rollType ? 'sb-cell--add' : '']"
@click="handleSbCellClick(scope.row.sb, scope.row.rollType, scope.row.position)"
>
<param-cell :data="scope.row.sb" />
</div>
</template>
</el-table-column>
</el-table>
</el-card>
<!-- 右侧可用辊 + 工作绩效 -->
<div class="top-row__aside aside-col">
<!-- 可用轧辊离线 -->
<el-card shadow="never" class="roll-table-card aside-panel">
<div slot="header" class="card-header">
<span class="card-title"><i class="el-icon-files" /> 可用轧辊离线</span>
<el-button size="mini" icon="el-icon-refresh" style="margin-left:auto" @click="loadOfflineRolls">刷新</el-button>
</div>
<el-table v-loading="offlineLoading" :data="offlineRolls" size="small" :height="asideHalfH" style="width:100%">
<el-table-column label="辊型" align="center" prop="rollType" width="52">
<template slot-scope="scope">
<el-tag size="mini" :type="scope.row.rollType === 'WR' ? 'primary' : 'warning'">{{ scope.row.rollType }}</el-tag>
</template>
</el-table-column>
<el-table-column label="轧辊编号" align="center" prop="rollNo" min-width="90" show-overflow-tooltip />
<el-table-column label="辊径(mm)" align="center" width="76">
<template slot-scope="scope">{{ scope.row.currentDia != null ? scope.row.currentDia : scope.row.initialDia }}</template>
</el-table-column>
<el-table-column label="粗糙度" align="center" prop="roughness" width="64" />
</el-table>
</el-card>
<!-- 工作绩效 -->
<el-card shadow="never" class="roll-table-card aside-panel">
<div slot="header" class="card-header">
<span class="card-title"><i class="el-icon-data-analysis" /> 工作绩效实时</span>
<el-button size="mini" icon="el-icon-refresh" style="margin-left:auto" @click="loadRollPerformance">刷新</el-button>
</div>
<el-table v-loading="perfLoading" :data="perfRows" size="small" :height="asideHalfH" border style="width:100%">
<el-table-column label="辊位" align="center" prop="label" width="80" />
<el-table-column label="轧辊编号" align="center" width="110">
<template slot-scope="scope"><span class="perf-roll">{{ scope.row.rollNo || '—' }}</span></template>
</el-table-column>
<el-table-column label="工作长度(m)" align="center" width="100">
<template slot-scope="scope">{{ scope.row.workLength != null ? scope.row.workLength : '—' }}</template>
</el-table-column>
<el-table-column label="过卷数" align="center" width="70">
<template slot-scope="scope">{{ scope.row.coilCount != null ? scope.row.coilCount : '—' }}</template>
</el-table-column>
</el-table>
</el-card>
</div>
</div>
<!-- 换辊历史 -->
<el-card shadow="never" class="roll-table-card" style="margin-top:16px">
<div slot="header" class="card-header">
<span class="card-title"><i class="el-icon-document" /> 换辊历史</span>
<el-form :inline="true" size="small" style="margin-left:auto;margin-bottom:0">
<el-form-item label="类型" style="margin-bottom:0">
<el-select v-model="historyQuery.changeType" placeholder="全部" clearable style="width:110px" @change="loadHistory">
<el-option label="计划换辊" value="计划换辊" />
<el-option label="紧急换辊" value="紧急换辊" />
</el-select>
</el-form-item>
<el-form-item label="时间起" style="margin-bottom:0">
<el-date-picker v-model="historyQuery.changeTime" type="datetime"
value-format="yyyy-MM-dd HH:mm:ss" placeholder="开始时间"
style="width:160px" @change="loadHistory" />
</el-form-item>
</el-form>
</div>
<KLPTable v-loading="historyLoading" :data="historyList">
<el-table-column label="换辊编号" align="center" prop="changeNo" width="130" />
<el-table-column label="换辊时间" align="center" prop="changeTime" width="160" />
<el-table-column label="类型" align="center" prop="changeType" width="100">
<template slot-scope="scope">
<el-tag size="small" :type="scope.row.changeType === '紧急换辊' ? 'danger' : ''">{{ scope.row.changeType }}</el-tag>
</template>
</el-table-column>
<el-table-column label="操作人" align="center" prop="operator" width="80" />
<el-table-column label="换辊辊组" align="left" min-width="160">
<template slot-scope="scope">
<div class="roll-group-cell">
<span v-if="scope.row.upperBrNo" class="rg-item"><b>上BR</b> {{ scope.row.upperBrNo }}<em v-if="scope.row.upperBrDia"> φ{{ scope.row.upperBrDia }}</em></span>
<span v-if="scope.row.upperWrNo" class="rg-item"><b>上WR</b> {{ scope.row.upperWrNo }}<em v-if="scope.row.upperWrDia"> φ{{ scope.row.upperWrDia }}</em></span>
<span v-if="scope.row.lowerWrNo" class="rg-item"><b>下WR</b> {{ scope.row.lowerWrNo }}<em v-if="scope.row.lowerWrDia"> φ{{ scope.row.lowerWrDia }}</em></span>
<span v-if="scope.row.lowerBrNo" class="rg-item"><b>下BR</b> {{ scope.row.lowerBrNo }}<em v-if="scope.row.lowerBrDia"> φ{{ scope.row.lowerBrDia }}</em></span>
<span v-if="!scope.row.upperBrNo && !scope.row.upperWrNo && !scope.row.lowerWrNo && !scope.row.lowerBrNo" style="color:#c0c4cc"></span>
</div>
</template>
</el-table-column>
<el-table-column label="工作长度(m)" align="center" prop="workLength" width="100" />
<el-table-column label="过卷数" align="center" prop="coilCount" width="72" />
<el-table-column label="过卷重量" align="center" prop="totalWeight" width="90" />
<el-table-column label="备注" align="left" prop="remark" min-width="100" show-overflow-tooltip />
<el-table-column label="操作" align="center" width="110" fixed="right">
<template slot-scope="scope">
<el-button size="mini" type="text" icon="el-icon-edit" @click="handleEditHistory(scope.row)">补录</el-button>
<el-button size="mini" type="text" icon="el-icon-delete" style="color:#c5221f"
@click="handleDeleteHistory(scope.row)">删除</el-button>
</template>
</el-table-column>
</KLPTable>
<pagination
v-show="historyTotal > 0"
:total="historyTotal"
:page.sync="historyQuery.pageNum"
:limit.sync="historyQuery.pageSize"
@pagination="loadHistory"
/>
</el-card>
<!-- 换辊确认弹窗 -->
<el-dialog title="确认换辊" :visible.sync="changeOpen" width="420px" append-to-body @close="resetChangeForm">
<div class="roll-preview">
<div class="roll-preview__title">以下轧辊将被换入</div>
<div class="roll-preview__item">
<span class="rp-label">上支撑辊</span>
<span class="rp-val">{{ changeForm.upperBrNo || '—' }}</span>
<span class="rp-dia" v-if="changeForm.upperBrDia">φ{{ changeForm.upperBrDia }} mm</span>
</div>
<div class="roll-preview__item">
<span class="rp-label">上工作辊</span>
<span class="rp-val">{{ changeForm.upperWrNo || '—' }}</span>
<span class="rp-dia" v-if="changeForm.upperWrDia">φ{{ changeForm.upperWrDia }} mm</span>
</div>
<div class="roll-preview__item">
<span class="rp-label">下工作辊</span>
<span class="rp-val">{{ changeForm.lowerWrNo || '—' }}</span>
<span class="rp-dia" v-if="changeForm.lowerWrDia">φ{{ changeForm.lowerWrDia }} mm</span>
</div>
<div class="roll-preview__item">
<span class="rp-label">下支撑辊</span>
<span class="rp-val">{{ changeForm.lowerBrNo || '—' }}</span>
<span class="rp-dia" v-if="changeForm.lowerBrDia">φ{{ changeForm.lowerBrDia }} mm</span>
</div>
</div>
<div class="roll-preview__tip">换辊时间和操作人将由系统自动记录</div>
<div slot="footer">
<el-button type="primary" :loading="changeSubmitting" @click="submitChange">确认换辊</el-button>
<el-button @click="changeOpen = false"> </el-button>
</div>
</el-dialog>
<!-- 换辊历史补录/编辑对话框 -->
<el-dialog title="换辊记录补录" :visible.sync="historyEditOpen" width="560px" append-to-body @close="historyEditForm = {}">
<el-form ref="historyEditForm" :model="historyEditForm" label-width="100px" size="small">
<el-row :gutter="16">
<el-col :span="12">
<el-form-item label="换辊类型">
<el-select v-model="historyEditForm.changeType" style="width:100%">
<el-option label="计划换辊" value="计划换辊" />
<el-option label="紧急换辊" value="紧急换辊" />
<el-option label="三级换辊" value="三级换辊" />
</el-select>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="换辊时间">
<el-date-picker v-model="historyEditForm.changeTime" type="datetime"
value-format="yyyy-MM-dd HH:mm:ss" style="width:100%" />
</el-form-item>
</el-col>
</el-row>
<el-form-item label="操作人">
<el-input v-model="historyEditForm.operator" />
</el-form-item>
<el-divider content-position="left">工作辊</el-divider>
<el-row :gutter="16">
<el-col :span="12"><el-form-item label="上工作辊"><el-input v-model="historyEditForm.upperWrNo" /></el-form-item></el-col>
<el-col :span="12"><el-form-item label="上工作辊径"><el-input-number v-model="historyEditForm.upperWrDia" :precision="2" :min="0" style="width:100%" /></el-form-item></el-col>
</el-row>
<el-row :gutter="16">
<el-col :span="12"><el-form-item label="下工作辊"><el-input v-model="historyEditForm.lowerWrNo" /></el-form-item></el-col>
<el-col :span="12"><el-form-item label="下工作辊径"><el-input-number v-model="historyEditForm.lowerWrDia" :precision="2" :min="0" style="width:100%" /></el-form-item></el-col>
</el-row>
<el-divider content-position="left">支撑辊</el-divider>
<el-row :gutter="16">
<el-col :span="12"><el-form-item label="上支撑辊"><el-input v-model="historyEditForm.upperBrNo" /></el-form-item></el-col>
<el-col :span="12"><el-form-item label="上支撑辊径"><el-input-number v-model="historyEditForm.upperBrDia" :precision="2" :min="0" style="width:100%" /></el-form-item></el-col>
</el-row>
<el-row :gutter="16">
<el-col :span="12"><el-form-item label="下支撑辊"><el-input v-model="historyEditForm.lowerBrNo" /></el-form-item></el-col>
<el-col :span="12"><el-form-item label="下支撑辊径"><el-input-number v-model="historyEditForm.lowerBrDia" :precision="2" :min="0" style="width:100%" /></el-form-item></el-col>
</el-row>
<el-form-item label="备注">
<el-input v-model="historyEditForm.remark" type="textarea" :rows="2" />
</el-form-item>
</el-form>
<div slot="footer">
<el-button type="primary" @click="submitHistoryEdit"> </el-button>
<el-button @click="historyEditOpen = false"> </el-button>
</div>
</el-dialog>
<!-- 添加下批轧辊对话框 -->
<el-dialog title="添加下批轧辊" :visible.sync="standbyOpen" width="460px" append-to-body @close="resetStandbyForm">
<el-form ref="standbyForm" :model="standbyForm" :rules="standbyRules" label-width="100px">
<el-row :gutter="16">
<el-col :span="12">
<el-form-item label="辊型" prop="rollType">
<el-input
v-if="standbyFromCell"
:value="standbyForm.rollType === 'WR' ? '工作辊 WR' : '支撑辊 BR'"
readonly style="width:100%"
/>
<el-select v-else v-model="standbyForm.rollType" placeholder="请选择" style="width:100%"
@change="handleStandbyRollTypeChange">
<el-option label="工作辊 WR" value="WR" />
<el-option label="支撑辊 BR" value="BR" />
</el-select>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="辊位" prop="position">
<el-input
v-if="standbyFromCell"
:value="standbyForm.position === 'UP' ? '上辊' : '下辊'"
readonly style="width:100%"
/>
<el-select v-else v-model="standbyForm.position" placeholder="请选择" style="width:100%">
<el-option label="上辊" value="UP" />
<el-option label="下辊" value="DOWN" />
</el-select>
</el-form-item>
</el-col>
</el-row>
<el-form-item label="轧辊编号" prop="rollNo">
<el-select v-model="standbyForm.rollNo" placeholder="请选择" filterable style="width:100%">
<el-option
v-for="no in standbyForm.rollType === 'WR' ? wrOptions : brOptions"
:key="no" :label="no" :value="no"
/>
</el-select>
</el-form-item>
<el-form-item label="就绪时间">
<el-date-picker v-model="standbyForm.readyTime" type="datetime"
value-format="yyyy-MM-dd HH:mm:ss" placeholder="默认当前时间" style="width:100%" />
</el-form-item>
<el-form-item label="备注">
<el-input v-model="standbyForm.remark" type="textarea" :rows="2" />
</el-form-item>
</el-form>
<div slot="footer">
<el-button type="primary" @click="submitStandby"> </el-button>
<el-button @click="standbyOpen = false"> </el-button>
</div>
</el-dialog>
</div>
</template>
<script>
import { getCurrentRolls, listRollChange, addRollChange, updateRollChange, delRollChange, getRollPerformance } from '@/api/mes/roll/rollChange'
import { listRollStandby, addRollStandby, delRollStandby, clearRollStandby } from '@/api/mes/roll/rollStandby'
import { listRollOptions, listRollInfo } from '@/api/mes/roll/rollInfo'
import rollLineMixin from '../rollLineMixin'
const STAND_NO = '1#'
const ParamCell = {
name: 'ParamCell',
props: { data: { type: Object, default: null } },
render(h) {
const d = this.data
if (!d || d.val == null || d.val === '') return h('span', { class: 'cell-empty' }, '—')
return h('span', { class: 'cell-main' }, String(d.val))
}
}
export default {
name: 'WorkingRollSingle',
components: { ParamCell },
mixins: [rollLineMixin],
data() {
return {
current: {},
loadingCurrent: false,
standbyList: [],
loadingStandby: false,
rollInfoMap: {},
historyLoading: false,
historyList: [],
historyTotal: 0,
historyQuery: { pageNum: 1, pageSize: 15, standNo: STAND_NO, changeType: undefined, changeTime: undefined },
wrOptions: [],
brOptions: [],
historyEditOpen: false,
historyEditForm: {},
offlineRolls: [],
offlineLoading: false,
perfData: {},
perfLoading: false,
asideHalfH: 200,
changeOpen: false,
changeSubmitting: false,
changeForm: {},
standbyOpen: false,
standbyForm: {},
standbyFromCell: false,
standbyRules: {
rollType: [{ required: true, message: '请选择辊型', trigger: 'change' }],
position: [{ required: true, message: '请选择辊位', trigger: 'change' }],
rollNo: [{ required: true, message: '请选择轧辊编号', trigger: 'change' }]
}
}
},
computed: {
mergedRows() {
const c = this.current || {}
const ri = this.rollInfoMap
const cv = (val, rollNo) => ({
val: (val == null || val === '') ? null : val,
sub: rollNo || null
})
const rv = (rollNo, field) => {
const info = rollNo ? ri[rollNo] : null
const val = info ? info[field] : null
return { val: (val == null || val === '') ? null : val, sub: rollNo || null }
}
const sv = (rollType, pos, field) => {
const item = this.standbyList.find(i => i.rollType === rollType && i.position === pos)
if (!item) return { val: null, sub: null }
let val = item[field]
if ((val == null || val === '') && item.rollNo) {
const info = ri[item.rollNo]
if (info) {
val = field === 'diameter'
? (info.currentDia != null ? info.currentDia : info.initialDia)
: info[field]
}
}
return { val: (val == null || val === '') ? null : val, sub: item.rollNo, standbyId: item.standbyId }
}
const sm = (rollType, pos) => {
const item = this.standbyList.find(i => i.rollType === rollType && i.position === pos)
if (!item || !item.rollNo) return { val: null, sub: null }
const info = ri[item.rollNo]
return { val: info ? (info.material || null) : null, sub: item.rollNo, standbyId: item.standbyId }
}
return [
{ label: '上支承辊直径(mm)', group: 'br', rollType: 'BR', position: 'UP',
cur: cv(c.upperBrDia, c.upperBrNo), sb: sv('BR', 'UP', 'diameter') },
{ label: '上工作辊直径(mm)', group: 'wr', rollType: 'WR', position: 'UP',
cur: cv(c.upperWrDia, c.upperWrNo), sb: sv('WR', 'UP', 'diameter') },
{ label: '上工作辊凸度', group: 'wr', rollType: 'WR', position: 'UP',
cur: rv(c.upperWrNo, 'crown'), sb: sv('WR', 'UP', 'crown') },
{ label: '上工作辊粗糙度(μm)', group: 'wr', rollType: 'WR', position: 'UP',
cur: rv(c.upperWrNo, 'roughness'), sb: sv('WR', 'UP', 'roughness') },
{ label: '工作辊类型', group: 'wr', rollType: 'WR', position: 'UP',
cur: rv(c.upperWrNo, 'material'), sb: sm('WR', 'UP') },
{ label: '下工作辊直径(mm)', group: 'wr', rollType: 'WR', position: 'DOWN',
cur: cv(c.lowerWrDia, c.lowerWrNo), sb: sv('WR', 'DOWN', 'diameter') },
{ label: '下工作辊凸度', group: 'wr', rollType: 'WR', position: 'DOWN',
cur: rv(c.lowerWrNo, 'crown'), sb: sv('WR', 'DOWN', 'crown') },
{ label: '下工作辊粗糙度(μm)', group: 'wr', rollType: 'WR', position: 'DOWN',
cur: rv(c.lowerWrNo, 'roughness'), sb: sv('WR', 'DOWN', 'roughness') },
{ label: '下支承辊直径(mm)', group: 'br', rollType: 'BR', position: 'DOWN',
cur: cv(c.lowerBrDia, c.lowerBrNo), sb: sv('BR', 'DOWN', 'diameter') }
]
},
perfRows() {
const standData = (this.perfData['upperBr'] || {})
const d = (key) => {
const obj = (this.perfData[key] || {})[STAND_NO] || {}
return { rollNo: obj.rollNo, workLength: obj.workLength, coilCount: obj.coilCount }
}
return [
{ label: '上支撑辊', ...d('upperBr') },
{ label: '上工作辊', ...d('upperWr') },
{ label: '下工作辊', ...d('lowerWr') },
{ label: '下支撑辊', ...d('lowerBr') }
]
}
},
created() {},
mounted() {
this.$nextTick(this.syncAsideHeight)
window.addEventListener('resize', this.syncAsideHeight)
},
beforeDestroy() {
window.removeEventListener('resize', this.syncAsideHeight)
},
methods: {
onLineResolved() {
this.loadCurrent()
this.loadStandby()
this.loadHistory()
this.loadOfflineRolls()
this.loadRollPerformance()
this.loadRollOptions()
this.loadRollInfoMap()
},
loadCurrent() {
this.loadingCurrent = true
getCurrentRolls(this.lineId, STAND_NO).then(res => {
this.current = res.data || {}
this.$nextTick(this.syncAsideHeight)
}).finally(() => { this.loadingCurrent = false })
},
loadStandby() {
this.loadingStandby = true
listRollStandby(this.lineId, STAND_NO).then(res => {
this.standbyList = res.data || []
}).finally(() => { this.loadingStandby = false })
},
loadHistory() {
this.historyLoading = true
listRollChange({ ...this.historyQuery, lineId: this.lineId }).then(res => {
this.historyList = res.rows || []
this.historyTotal = res.total || 0
}).finally(() => { this.historyLoading = false })
},
loadRollOptions() {
listRollOptions(this.lineId, 'WR', 'Offline').then(res => { this.wrOptions = res.data || [] })
listRollOptions(this.lineId, 'BR', 'Offline').then(res => { this.brOptions = res.data || [] })
},
loadRollInfoMap() {
listRollInfo({ lineId: this.lineId, pageNum: 1, pageSize: 500 }).then(res => {
const map = {}
;(res.rows || []).forEach(r => { map[r.rollNo] = r })
this.rollInfoMap = map
})
},
loadRollPerformance() {
this.perfLoading = true
getRollPerformance(this.lineId).then(res => {
this.perfData = res.data || {}
}).finally(() => { this.perfLoading = false })
},
loadOfflineRolls() {
this.offlineLoading = true
listRollInfo({ lineId: this.lineId, status: 'Offline', pageNum: 1, pageSize: 30 }).then(res => {
this.offlineRolls = res.rows || []
}).finally(() => { this.offlineLoading = false })
},
rowClass({ row }) {
if (row.group === 'br') return 'row-br'
return ''
},
mergeParamCol({ columnIndex }) {
if (columnIndex === 0) return [1, 1]
},
handleOpenChange() {
const ri = this.rollInfoMap
const pick = (rollType, pos) => {
const item = this.standbyList.find(i => i.rollType === rollType && i.position === pos)
if (!item) return { no: undefined, dia: undefined }
const info = ri[item.rollNo]
const dia = item.diameter != null ? item.diameter
: (info ? (info.currentDia != null ? info.currentDia : info.initialDia) : undefined)
return { no: item.rollNo, dia }
}
const uwr = pick('WR', 'UP'), lwr = pick('WR', 'DOWN')
const ubr = pick('BR', 'UP'), lbr = pick('BR', 'DOWN')
this.changeForm = {
standNo: STAND_NO,
lineId: this.lineId,
changeType: undefined,
upperWrNo: uwr.no, upperWrDia: uwr.dia,
lowerWrNo: lwr.no, lowerWrDia: lwr.dia,
upperBrNo: ubr.no, upperBrDia: ubr.dia,
lowerBrNo: lbr.no, lowerBrDia: lbr.dia
}
this.changeOpen = true
},
submitChange() {
if (this.changeSubmitting) return
this.changeSubmitting = true
addRollChange(this.changeForm).then(() => {
this.$modal.msgSuccess('换辊成功')
this.changeOpen = false
clearRollStandby(this.lineId, STAND_NO).finally(() => {
this.loadCurrent()
this.loadStandby()
this.loadHistory()
this.loadOfflineRolls()
this.loadRollPerformance()
})
}).finally(() => { this.changeSubmitting = false })
},
resetChangeForm() {
this.changeForm = {}
this.changeSubmitting = false
},
handleSbCellClick(cell, rollType, position) {
if (cell && cell.standbyId) {
this.handleDelStandby(cell.standbyId)
} else if (rollType) {
this.standbyForm = { standNo: STAND_NO, rollType, position, rollNo: undefined }
this.standbyFromCell = true
this.standbyOpen = true
}
},
handleAddStandby() {
this.standbyForm = { standNo: STAND_NO, rollType: undefined, position: undefined, rollNo: undefined }
this.standbyFromCell = false
this.standbyOpen = true
},
handleStandbyRollTypeChange() {
this.$set(this.standbyForm, 'rollNo', undefined)
},
submitStandby() {
this.$refs.standbyForm.validate(valid => {
if (!valid) return
addRollStandby({ ...this.standbyForm, lineId: this.lineId }).then(() => {
this.$modal.msgSuccess('已添加到下批轧辊')
this.standbyOpen = false
this.loadStandby()
this.loadRollOptions()
this.loadOfflineRolls()
})
})
},
resetStandbyForm() {
this.standbyForm = {}
this.standbyFromCell = false
this.$nextTick(() => { this.$refs.standbyForm && this.$refs.standbyForm.clearValidate() })
},
handleDelStandby(standbyId) {
this.$modal.confirm('确认移除该下批轧辊?移除后该辊状态将恢复为"离线"。').then(() => {
return delRollStandby(standbyId)
}).then(() => {
this.$modal.msgSuccess('已移除')
this.loadStandby()
this.loadRollOptions()
this.loadOfflineRolls()
})
},
handleClearStandby() {
this.$modal.confirm('确认清空全部下批轧辊?').then(() => {
return clearRollStandby(this.lineId, STAND_NO)
}).then(() => {
this.$modal.msgSuccess('已清空')
this.loadStandby()
})
},
handleEditHistory(row) {
this.historyEditForm = { ...row }
this.historyEditOpen = true
},
submitHistoryEdit() {
updateRollChange(this.historyEditForm).then(() => {
this.$modal.msgSuccess('保存成功')
this.historyEditOpen = false
this.loadHistory()
})
},
handleDeleteHistory(row) {
this.$modal.confirm('确认删除该换辊记录?此操作不可恢复。').then(() => {
return delRollChange(row.changeId)
}).then(() => {
this.$modal.msgSuccess('已删除')
this.loadHistory()
})
},
syncAsideHeight() {
const el = this.$refs.mainTable && this.$refs.mainTable.$el
if (!el) return
const totalH = el.offsetHeight
const gap = 12
const headerH = 48
const half = Math.max(100, Math.floor((totalH - gap) / 2) - headerH)
this.asideHalfH = half
}
}
}
</script>
<style scoped>
.working-roll-page { background: #f4f5f7; }
.roll-table-card { border: 1px solid #dcdee0; border-radius: 4px; }
.top-row { display: flex; gap: 12px; align-items: stretch; }
.top-row__main { flex: 2; min-width: 0; }
.top-row__aside { flex: 1; min-width: 0; }
.aside-col { display: flex; flex-direction: column; gap: 12px; }
.aside-panel { flex: 1; min-width: 0; }
.roll-group-cell { display: flex; flex-wrap: wrap; gap: 4px; line-height: 1.5; }
.rg-item { font-size: 12px; color: #3d4b5c; }
.rg-item b { color: #5f6368; font-weight: 600; margin-right: 2px; }
.rg-item em { font-style: normal; color: #9aa0a6; font-size: 11px; }
.card-header { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; }
.card-title { font-size: 13px; font-weight: 600; color: #3d4b5c; white-space: nowrap; }
.header-meta { font-size: 11px; color: #8f9099; white-space: nowrap; }
.param-label { font-size: 12px; color: #3d4b5c; }
.cell-main { font-family: 'Consolas', 'Courier New', monospace; font-size: 13px; font-weight: 600; color: #1f2329; }
.cell-empty { color: #c0c4cc; font-size: 12px; }
.perf-roll { font-family: 'Consolas', monospace; font-size: 12px; font-weight: 600; color: #1f2329; }
.roll-preview { background: #f7f8fa; border: 1px solid #e4e6eb; border-radius: 4px; padding: 10px 12px; }
.roll-preview__title { font-size: 11px; color: #8f9099; margin-bottom: 8px; letter-spacing: .5px; }
.roll-preview__item { display: flex; align-items: center; gap: 6px; padding: 4px 0; }
.rp-label { font-size: 12px; color: #8f9099; width: 56px; flex-shrink: 0; }
.rp-val { font-size: 13px; font-weight: 600; color: #1f2329; font-family: 'Consolas', monospace; flex: 1; }
.rp-dia { font-size: 11px; color: #9aa0a6; }
.roll-preview__tip { font-size: 11px; color: #b0b3bb; margin-top: 10px; text-align: center; }
.sb-cell { display: block; min-height: 24px; border-radius: 2px; cursor: default; }
.sb-cell--add { cursor: pointer; background: #f0f9f4; }
.sb-cell--add:hover { background: #d4edda; }
.sb-cell--del { cursor: pointer; background: #fff7f7; }
.sb-cell--del:hover { background: #ffe4e4; }
::v-deep .row-br td { background: #fafafa !important; }
</style>
<style>
.el-table .editing-row { background: #fffbf0 !important; }
</style>

View File

@@ -381,6 +381,7 @@
import { getCurrentRolls, listRollChange, addRollChange, updateRollChange, delRollChange, getRollPerformance } from '@/api/mes/roll/rollChange'
import { listRollStandby, addRollStandby, delRollStandby, clearRollStandby } from '@/api/mes/roll/rollStandby'
import { listRollOptions, listRollInfo } from '@/api/mes/roll/rollInfo'
import rollLineMixin from '../rollLineMixin'
const ParamCell = {
name: 'ParamCell',
@@ -409,6 +410,7 @@ const PerfCell = {
export default {
name: 'WorkingRoll',
components: { ParamCell, PerfCell },
mixins: [rollLineMixin],
data() {
return {
current: { '1#': {}, '2#': {} },
@@ -576,11 +578,7 @@ export default {
}
},
created() {
this.loadAll()
this.loadRollOptions()
this.loadRollInfoMap()
},
created() {},
mounted() {
this.$nextTick(this.syncAsideHeight)
@@ -592,6 +590,12 @@ export default {
},
methods: {
onLineResolved() {
this.loadAll()
this.loadRollOptions()
this.loadRollInfoMap()
},
getDefaultChangeForm(standNo) {
return {
standNo: standNo || undefined,
@@ -631,14 +635,14 @@ export default {
loadRollPerformance() {
this.perfLoading = true
getRollPerformance().then(res => {
getRollPerformance(this.lineId).then(res => {
this.perfData = res.data || {}
}).finally(() => { this.perfLoading = false })
},
loadCurrent(standNo) {
this.$set(this.loadingCurrent, standNo, true)
getCurrentRolls(standNo).then(res => {
getCurrentRolls(this.lineId, standNo).then(res => {
this.$set(this.current, standNo, res.data || {})
this.$set(this.loadingCurrent, standNo, false)
this.$nextTick(this.syncAsideHeight)
@@ -647,7 +651,7 @@ export default {
loadStandby(standNo) {
this.$set(this.loadingStandby, standNo, true)
listRollStandby(standNo).then(res => {
listRollStandby(this.lineId, standNo).then(res => {
this.$set(this.standbyList, standNo, res.data || [])
this.$set(this.loadingStandby, standNo, false)
}).catch(() => { this.$set(this.loadingStandby, standNo, false) })
@@ -655,7 +659,7 @@ export default {
loadHistory() {
this.historyLoading = true
listRollChange(this.historyQuery).then(res => {
listRollChange({ ...this.historyQuery, lineId: this.lineId }).then(res => {
this.historyList = res.rows || []
this.historyTotal = res.total || 0
this.historyLoading = false
@@ -663,13 +667,13 @@ export default {
},
loadRollOptions() {
// 下批辊选择只显示离线Offline状态的轧辊
listRollOptions('WR', 'Offline').then(res => { this.wrOptions = res.data || [] })
listRollOptions('BR', 'Offline').then(res => { this.brOptions = res.data || [] })
// 下批辊选择只显示离线Offline状态的轧辊,限定当前产线
listRollOptions(this.lineId, 'WR', 'Offline').then(res => { this.wrOptions = res.data || [] })
listRollOptions(this.lineId, 'BR', 'Offline').then(res => { this.brOptions = res.data || [] })
},
loadRollInfoMap() {
listRollInfo({ pageNum: 1, pageSize: 500 }).then(res => {
listRollInfo({ lineId: this.lineId, pageNum: 1, pageSize: 500 }).then(res => {
const map = {}
;(res.rows || []).forEach(r => { map[r.rollNo] = r })
this.rollInfoMap = map
@@ -708,6 +712,7 @@ export default {
this.changeForm = {
...this.getDefaultChangeForm(standNo),
lineId: this.lineId,
upperWrNo: uwr.no, upperWrDia: uwr.dia,
lowerWrNo: lwr.no, lowerWrDia: lwr.dia,
upperBrNo: ubr.no, upperBrDia: ubr.dia,
@@ -722,7 +727,7 @@ export default {
addRollChange(this.changeForm).then(() => {
this.$modal.msgSuccess('换辊成功')
this.changeOpen = false
clearRollStandby(standNo).finally(() => {
clearRollStandby(this.lineId, standNo).finally(() => {
this.loadCurrent(standNo)
this.loadStandby(standNo)
this.loadHistory()
@@ -761,7 +766,7 @@ export default {
submitStandby() {
this.$refs.standbyForm.validate(valid => {
if (!valid) return
addRollStandby(this.standbyForm).then(() => {
addRollStandby({ ...this.standbyForm, lineId: this.lineId }).then(() => {
this.$modal.msgSuccess('已添加到下批轧辊')
this.standbyOpen = false
this.loadStandby(this.standbyForm.standNo)
@@ -787,7 +792,7 @@ export default {
},
handleClearStandby(standNo) {
this.$modal.confirm('确认清空 ' + standNo + ' 机架的全部下批轧辊?').then(() => {
return clearRollStandby(standNo)
return clearRollStandby(this.lineId, standNo)
}).then(() => {
this.$modal.msgSuccess('已清空')
this.loadStandby(standNo)
@@ -818,7 +823,7 @@ export default {
// ── 可用离线辊列表 ────────────────────────────────────────
loadOfflineRolls() {
this.offlineLoading = true
listRollInfo({ status: 'Offline', pageNum: 1, pageSize: 30 }).then(res => {
listRollInfo({ lineId: this.lineId, status: 'Offline', pageNum: 1, pageSize: 30 }).then(res => {
this.offlineRolls = res.rows || []
}).finally(() => {
this.offlineLoading = false

View File

@@ -0,0 +1,247 @@
<template>
<div class="app-container acid-op-page">
<el-row :gutter="16">
<!-- 左侧合卷表单 -->
<el-col :span="15">
<div class="op-card">
<div class="op-header">
<span class="op-title">合卷操作</span>
<el-tag size="mini" type="info" style="margin-left:8px">actionType = 200</el-tag>
</div>
<el-form ref="form" :model="form" :rules="rules" label-width="110px" size="small">
<!-- 源卷列表动态 -->
<div class="sub-section-label">源卷列表</div>
<div v-for="(item, idx) in form.sourceCoils" :key="idx" class="source-row">
<el-form-item
:label="'源卷 ' + (idx + 1)"
:prop="'sourceCoils.' + idx + '.enterCoilNo'"
:rules="[{ required: true, message: '请输入源卷号', trigger: 'blur' }]"
style="margin-bottom:8px"
>
<el-input v-model="item.enterCoilNo" :placeholder="'入场钢卷号 ' + (idx + 1)"
clearable style="width:calc(100% - 36px)"
@keyup.enter.native="onSourceCoilInput(item)"
@blur="onSourceCoilInput(item)" />
<el-button icon="el-icon-minus" size="mini" type="danger" circle
style="margin-left:6px" :disabled="form.sourceCoils.length <= 1"
@click="removeSourceCoil(idx)" />
</el-form-item>
</div>
<el-button size="mini" type="primary" plain icon="el-icon-plus"
style="margin-left:110px;margin-bottom:12px" @click="addSourceCoil">
添加源卷
</el-button>
<el-divider content-position="left" style="margin:4px 0 12px">合卷结果</el-divider>
<el-row :gutter="16">
<el-col :span="12">
<el-form-item label="合卷后卷号" prop="targetCoilNo">
<el-input v-model="form.targetCoilNo" placeholder="请输入合卷后钢卷号" clearable />
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="合卷后重量(t)" prop="mergedWeight">
<el-input-number v-model="form.mergedWeight" :precision="3" :min="0"
:controls="false" style="width:100%" placeholder="—" />
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="16">
<el-col :span="12">
<el-form-item label="出口厚度(mm)" prop="exitThickness">
<el-input-number v-model="form.exitThickness" :precision="3" :min="0"
:controls="false" style="width:100%" placeholder="—" />
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="出口宽度(mm)" prop="exitWidth">
<el-input-number v-model="form.exitWidth" :precision="1" :min="0"
:controls="false" style="width:100%" placeholder="—" />
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="16">
<el-col :span="12">
<el-form-item label="班组" prop="team">
<el-select v-model="form.team" placeholder="请选择" style="width:100%" clearable>
<el-option label="甲班" value="甲" />
<el-option label="乙班" value="乙" />
<el-option label="丙班" value="丙" />
</el-select>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="备注">
<el-input v-model="form.remark" placeholder="选填" clearable />
</el-form-item>
</el-col>
</el-row>
<div style="text-align:right;padding-top:4px">
<el-button @click="resetForm">重置</el-button>
<el-button type="primary" :loading="submitting" @click="submitForm">确认合卷</el-button>
</div>
</el-form>
</div>
<!-- 历史记录 -->
<div class="op-card" style="margin-top:12px">
<div class="op-header">
<span class="op-title">最近合卷记录</span>
<el-button size="mini" icon="el-icon-refresh" style="margin-left:auto" @click="loadHistory">刷新</el-button>
</div>
<el-table v-loading="historyLoading" :data="historyList" size="mini" border style="width:100%">
<el-table-column prop="enterCoilNo" label="源卷号" min-width="120" show-overflow-tooltip />
<el-table-column prop="currentCoilNo" label="合卷后卷号" min-width="120" show-overflow-tooltip />
<el-table-column prop="exitThickness" label="出口厚(mm)" width="90" align="right" />
<el-table-column prop="exitWidth" label="出口宽(mm)" width="90" align="right" />
<el-table-column prop="netWeight" label="重量(t)" width="80" align="right" />
<el-table-column prop="team" label="班组" width="60" align="center" />
<el-table-column prop="createTime" label="录入时间" width="150" />
</el-table>
<pagination v-show="historyTotal > 0" :total="historyTotal"
:page.sync="historyQuery.pageNum" :limit.sync="historyQuery.pageSize"
@pagination="loadHistory" style="margin-top:6px" />
</div>
</el-col>
<!-- 右侧L2 匹配面板以第一个源卷为锚点 -->
<el-col :span="9">
<l2-match-panel :hot-coil-id="l2HotCoilId" @fill="applyL2Fill" />
</el-col>
</el-row>
</div>
</template>
<script>
import { addCoilWarehouseOperationLog, listCoilWarehouseOperationLog } from '@/api/wms/coilWarehouseOperationLog'
import L2MatchPanel from '../panels/L2MatchPanel.vue'
export default {
name: 'AcidMerge',
components: { L2MatchPanel },
data() {
return {
form: this.defaultForm(),
rules: {
targetCoilNo: [{ required: true, message: '合卷后卷号不能为空', trigger: 'blur' }],
},
submitting: false,
l2HotCoilId: '',
historyLoading: false,
historyList: [],
historyTotal: 0,
historyQuery: { pageNum: 1, pageSize: 10, actionType: 200 },
}
},
created() {
this.loadHistory()
},
methods: {
defaultForm() {
return {
sourceCoils: [{ enterCoilNo: '' }],
targetCoilNo: '',
mergedWeight: undefined,
exitThickness: undefined,
exitWidth: undefined,
team: undefined,
remark: '',
actionType: 200,
}
},
addSourceCoil() {
this.form.sourceCoils.push({ enterCoilNo: '' })
},
removeSourceCoil(idx) {
this.form.sourceCoils.splice(idx, 1)
// 重新推算锚点
this.l2HotCoilId = (this.form.sourceCoils[0]?.enterCoilNo || '').trim()
},
onSourceCoilInput(item) {
// 以列表第一个源卷的卷号作为 L2 查询锚点
const first = (this.form.sourceCoils[0]?.enterCoilNo || '').trim()
this.l2HotCoilId = first
},
applyL2Fill(data) {
if (data.exit_thick != null) this.form.exitThickness = parseFloat(data.exit_thick)
if (data.exit_width != null) this.form.exitWidth = parseFloat(data.exit_width)
if (data.entry_weight != null) this.form.mergedWeight = parseFloat(data.entry_weight)
this.$message.success('L2 数据已写入表单')
},
submitForm() {
this.$refs.form.validate(valid => {
if (!valid) return
this.submitting = true
// 将源卷列表序列化放入 remark 或 enterCoilNo多对一合卷用逗号分隔
const sourceNos = this.form.sourceCoils.map(s => s.enterCoilNo).filter(Boolean).join(',')
const payload = {
enterCoilNo: sourceNos,
currentCoilNo: this.form.targetCoilNo,
exitThickness: this.form.exitThickness,
exitWidth: this.form.exitWidth,
netWeight: this.form.mergedWeight,
team: this.form.team,
remark: this.form.remark,
actionType: 200,
}
addCoilWarehouseOperationLog(payload).then(() => {
this.$modal.msgSuccess('合卷操作已记录')
this.resetForm()
this.loadHistory()
}).finally(() => { this.submitting = false })
})
},
resetForm() {
this.form = this.defaultForm()
this.l2HotCoilId = ''
this.$nextTick(() => { this.$refs.form && this.$refs.form.clearValidate() })
},
loadHistory() {
this.historyLoading = true
listCoilWarehouseOperationLog(this.historyQuery).then(res => {
this.historyList = res.rows || []
this.historyTotal = res.total || 0
}).finally(() => { this.historyLoading = false })
},
}
}
</script>
<style scoped>
.acid-op-page { background: #f5f7fa; }
.op-card {
background: #fff;
border: 1px solid #e4e7ed;
border-radius: 4px;
padding: 16px;
}
.op-header {
display: flex;
align-items: center;
margin-bottom: 16px;
padding-bottom: 10px;
border-bottom: 2px solid #67c23a;
}
.op-title { font-size: 15px; font-weight: 600; color: #303133; }
.sub-section-label {
font-size: 12px;
color: #909399;
margin: 0 0 8px 110px;
font-weight: 500;
}
.source-row { position: relative; }
</style>

View File

@@ -0,0 +1,237 @@
<template>
<div class="app-container acid-op-page">
<el-row :gutter="16">
<!-- 左侧操作表单 + 历史记录 -->
<el-col :span="15">
<!-- 新增表单 -->
<div class="op-card">
<div class="op-header">
<span class="op-title">酸轧操作录入</span>
<el-tag size="mini" type="info" style="margin-left:8px">actionType = 11</el-tag>
</div>
<el-form ref="form" :model="form" :rules="rules" label-width="110px" size="small">
<el-row :gutter="16">
<el-col :span="12">
<el-form-item label="入场钢卷号" prop="enterCoilNo">
<el-input v-model="form.enterCoilNo" placeholder="回车自动查询 L2"
clearable @keyup.enter.native="onEnterCoilInput"
@blur="onEnterCoilInput" @clear="clearL2" />
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="出口钢卷号" prop="currentCoilNo">
<el-input v-model="form.currentCoilNo" placeholder="请输入出口钢卷号" clearable />
</el-form-item>
</el-col>
</el-row>
<el-divider content-position="left" style="margin:8px 0 12px">出口实绩</el-divider>
<el-row :gutter="16">
<el-col :span="8">
<el-form-item label="出口厚度(mm)" prop="exitThickness">
<el-input-number v-model="form.exitThickness" :precision="3" :min="0"
:controls="false" style="width:100%" placeholder="—" />
</el-form-item>
</el-col>
<el-col :span="8">
<el-form-item label="出口宽度(mm)" prop="exitWidth">
<el-input-number v-model="form.exitWidth" :precision="1" :min="0"
:controls="false" style="width:100%" placeholder="—" />
</el-form-item>
</el-col>
<el-col :span="8">
<el-form-item label="出口长度(m)" prop="exitLength">
<el-input-number v-model="form.exitLength" :precision="1" :min="0"
:controls="false" style="width:100%" placeholder="—" />
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="16">
<el-col :span="8">
<el-form-item label="入口重量(t)" prop="entryWeight">
<el-input-number v-model="form.entryWeight" :precision="3" :min="0"
:controls="false" style="width:100%" placeholder="—" />
</el-form-item>
</el-col>
<el-col :span="8">
<el-form-item label="出口重量(t)" prop="exitWeight">
<el-input-number v-model="form.exitWeight" :precision="3" :min="0"
:controls="false" style="width:100%" placeholder="—" />
</el-form-item>
</el-col>
<el-col :span="8">
<el-form-item label="工艺编码" prop="processCode">
<el-input v-model="form.processCode" placeholder="—" clearable />
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="16">
<el-col :span="8">
<el-form-item label="班组" prop="team">
<el-select v-model="form.team" placeholder="请选择" style="width:100%" clearable>
<el-option label="甲班" value="甲" />
<el-option label="乙班" value="乙" />
<el-option label="丙班" value="丙" />
</el-select>
</el-form-item>
</el-col>
<el-col :span="16">
<el-form-item label="备注">
<el-input v-model="form.remark" placeholder="选填" clearable />
</el-form-item>
</el-col>
</el-row>
<div style="text-align:right;padding-top:4px">
<el-button @click="resetForm">重置</el-button>
<el-button type="primary" :loading="submitting" @click="submitForm">新增录入</el-button>
</div>
</el-form>
</div>
<!-- 历史记录 -->
<div class="op-card" style="margin-top:12px">
<div class="op-header">
<span class="op-title">最近录入记录</span>
<el-button size="mini" icon="el-icon-refresh" style="margin-left:auto" @click="loadHistory">刷新</el-button>
</div>
<el-table v-loading="historyLoading" :data="historyList" size="mini" border style="width:100%">
<el-table-column prop="enterCoilNo" label="入场钢卷号" min-width="120" show-overflow-tooltip />
<el-table-column prop="currentCoilNo" label="出口钢卷号" min-width="120" show-overflow-tooltip />
<el-table-column prop="exitThickness" label="出口厚(mm)" width="90" align="right" />
<el-table-column prop="exitWidth" label="出口宽(mm)" width="90" align="right" />
<el-table-column prop="exitLength" label="长度(m)" width="80" align="right" />
<el-table-column prop="entryWeight" label="入口重(t)" width="80" align="right" />
<el-table-column prop="team" label="班组" width="60" align="center" />
<el-table-column prop="createTime" label="录入时间" width="150" />
</el-table>
<pagination v-show="historyTotal > 0" :total="historyTotal"
:page.sync="historyQuery.pageNum" :limit.sync="historyQuery.pageSize"
@pagination="loadHistory" style="margin-top:6px" />
</div>
</el-col>
<!-- 右侧L2 匹配面板 -->
<el-col :span="9">
<l2-match-panel :hot-coil-id="l2HotCoilId" @fill="applyL2Fill" />
</el-col>
</el-row>
</div>
</template>
<script>
import { addCoilWarehouseOperationLog, listCoilWarehouseOperationLog } from '@/api/wms/coilWarehouseOperationLog'
import L2MatchPanel from '../panels/L2MatchPanel.vue'
export default {
name: 'AcidNormal',
components: { L2MatchPanel },
data() {
return {
form: this.defaultForm(),
rules: {
enterCoilNo: [{ required: true, message: '入场钢卷号不能为空', trigger: 'blur' }],
},
submitting: false,
l2HotCoilId: '',
historyLoading: false,
historyList: [],
historyTotal: 0,
historyQuery: { pageNum: 1, pageSize: 10, actionType: 11 },
}
},
created() {
this.loadHistory()
},
methods: {
defaultForm() {
return {
enterCoilNo: '',
currentCoilNo: '',
exitThickness: undefined,
exitWidth: undefined,
exitLength: undefined,
entryWeight: undefined,
exitWeight: undefined,
processCode: '',
team: undefined,
remark: '',
actionType: 11,
}
},
onEnterCoilInput() {
const v = (this.form.enterCoilNo || '').trim()
this.l2HotCoilId = v || ''
},
clearL2() {
this.l2HotCoilId = ''
},
applyL2Fill(data) {
if (data.exit_thick != null) this.form.exitThickness = parseFloat(data.exit_thick)
if (data.exit_width != null) this.form.exitWidth = parseFloat(data.exit_width)
if (data.exit_length != null) this.form.exitLength = parseFloat(data.exit_length)
if (data.entry_weight != null) this.form.entryWeight = parseFloat(data.entry_weight)
if (data.process_code) this.form.processCode = data.process_code
this.$message.success('L2 数据已写入表单')
},
submitForm() {
this.$refs.form.validate(valid => {
if (!valid) return
this.submitting = true
addCoilWarehouseOperationLog({ ...this.form }).then(() => {
this.$modal.msgSuccess('录入成功')
this.resetForm()
this.loadHistory()
}).finally(() => { this.submitting = false })
})
},
resetForm() {
const coilNo = this.form.enterCoilNo
this.form = this.defaultForm()
this.$nextTick(() => {
this.$refs.form && this.$refs.form.clearValidate()
})
},
loadHistory() {
this.historyLoading = true
listCoilWarehouseOperationLog(this.historyQuery).then(res => {
this.historyList = res.rows || []
this.historyTotal = res.total || 0
}).finally(() => { this.historyLoading = false })
},
}
}
</script>
<style scoped>
.acid-op-page { background: #f5f7fa; }
.op-card {
background: #fff;
border: 1px solid #e4e7ed;
border-radius: 4px;
padding: 16px;
}
.op-header {
display: flex;
align-items: center;
margin-bottom: 16px;
padding-bottom: 10px;
border-bottom: 2px solid #409eff;
}
.op-title { font-size: 15px; font-weight: 600; color: #303133; }
</style>

View File

@@ -0,0 +1,280 @@
<template>
<div class="app-container acid-op-page">
<el-row :gutter="16">
<!-- 左侧分卷表单 -->
<el-col :span="15">
<div class="op-card">
<div class="op-header">
<span class="op-title">分卷操作</span>
<el-tag size="mini" type="info" style="margin-left:8px">actionType = 520</el-tag>
</div>
<el-form ref="form" :model="form" :rules="rules" label-width="110px" size="small">
<!-- 源卷 -->
<el-row :gutter="16">
<el-col :span="12">
<el-form-item label="源卷号" prop="enterCoilNo">
<el-input v-model="form.enterCoilNo" placeholder="回车自动查询 L2" clearable
@keyup.enter.native="onEnterCoilInput"
@blur="onEnterCoilInput" @clear="clearL2" />
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="原卷重量(t)">
<el-input-number v-model="form.sourceWeight" :precision="3" :min="0"
:controls="false" style="width:100%" placeholder="—" />
</el-form-item>
</el-col>
</el-row>
<el-divider content-position="left" style="margin:4px 0 12px">子卷明细</el-divider>
<!-- 子卷列表动态 -->
<el-row v-for="(child, idx) in form.children" :key="idx"
:gutter="12" style="margin-bottom:8px" align="middle" type="flex">
<el-col :span="1" style="text-align:center;color:#909399;font-size:12px">
{{ idx + 1 }}
</el-col>
<el-col :span="10">
<el-form-item
:prop="'children.' + idx + '.coilNo'"
:rules="[{ required: true, message: '请输入子卷号', trigger: 'blur' }]"
style="margin-bottom:0"
>
<el-input v-model="child.coilNo" :placeholder="'子卷号 ' + (idx + 1)" clearable size="small" />
</el-form-item>
</el-col>
<el-col :span="7">
<el-form-item style="margin-bottom:0">
<el-input-number v-model="child.weight" :precision="3" :min="0"
:controls="false" style="width:100%" placeholder="重量(t)" size="small" />
</el-form-item>
</el-col>
<el-col :span="6">
<el-form-item style="margin-bottom:0">
<el-input-number v-model="child.length" :precision="1" :min="0"
:controls="false" style="width:100%" placeholder="长度(m)" size="small" />
</el-form-item>
</el-col>
<el-col :span="1">
<el-button icon="el-icon-minus" size="mini" type="danger" circle
:disabled="form.children.length <= 1"
@click="removeChild(idx)" />
</el-col>
</el-row>
<div style="display:flex;align-items:center;margin-left:12px;margin-bottom:12px;gap:12px">
<el-button size="mini" type="primary" plain icon="el-icon-plus" @click="addChild">
添加子卷
</el-button>
<span v-if="weightLeft != null" :class="['weight-hint', weightLeft < 0 ? 'over' : '']">
已分配 {{ weightAssigned.toFixed(3) }} t
<template v-if="form.sourceWeight">
/ 剩余 <b>{{ weightLeft.toFixed(3) }} t</b>
</template>
</span>
</div>
<el-row :gutter="16">
<el-col :span="8">
<el-form-item label="出口厚度(mm)">
<el-input-number v-model="form.exitThickness" :precision="3" :min="0"
:controls="false" style="width:100%" placeholder="—" />
</el-form-item>
</el-col>
<el-col :span="8">
<el-form-item label="出口宽度(mm)">
<el-input-number v-model="form.exitWidth" :precision="1" :min="0"
:controls="false" style="width:100%" placeholder="—" />
</el-form-item>
</el-col>
<el-col :span="8">
<el-form-item label="班组">
<el-select v-model="form.team" placeholder="请选择" style="width:100%" clearable>
<el-option label="甲班" value="甲" />
<el-option label="乙班" value="乙" />
<el-option label="丙班" value="丙" />
</el-select>
</el-form-item>
</el-col>
</el-row>
<el-form-item label="备注">
<el-input v-model="form.remark" placeholder="选填" clearable />
</el-form-item>
<div style="text-align:right;padding-top:4px">
<el-button @click="resetForm">重置</el-button>
<el-button type="primary" :loading="submitting" @click="submitForm">确认分卷</el-button>
</div>
</el-form>
</div>
<!-- 历史记录 -->
<div class="op-card" style="margin-top:12px">
<div class="op-header">
<span class="op-title">最近分卷记录</span>
<el-button size="mini" icon="el-icon-refresh" style="margin-left:auto" @click="loadHistory">刷新</el-button>
</div>
<el-table v-loading="historyLoading" :data="historyList" size="mini" border style="width:100%">
<el-table-column prop="enterCoilNo" label="源卷号" min-width="120" show-overflow-tooltip />
<el-table-column prop="currentCoilNo" label="子卷号" min-width="120" show-overflow-tooltip />
<el-table-column prop="exitThickness" label="出口厚(mm)" width="90" align="right" />
<el-table-column prop="exitWidth" label="出口宽(mm)" width="90" align="right" />
<el-table-column prop="netWeight" label="重量(t)" width="80" align="right" />
<el-table-column prop="team" label="班组" width="60" align="center" />
<el-table-column prop="createTime" label="录入时间" width="150" />
</el-table>
<pagination v-show="historyTotal > 0" :total="historyTotal"
:page.sync="historyQuery.pageNum" :limit.sync="historyQuery.pageSize"
@pagination="loadHistory" style="margin-top:6px" />
</div>
</el-col>
<!-- 右侧L2 匹配面板 -->
<el-col :span="9">
<l2-match-panel :hot-coil-id="l2HotCoilId" @fill="applyL2Fill" />
</el-col>
</el-row>
</div>
</template>
<script>
import { addCoilWarehouseOperationLog, listCoilWarehouseOperationLog } from '@/api/wms/coilWarehouseOperationLog'
import L2MatchPanel from '../panels/L2MatchPanel.vue'
export default {
name: 'AcidSplit',
components: { L2MatchPanel },
data() {
return {
form: this.defaultForm(),
rules: {
enterCoilNo: [{ required: true, message: '源卷号不能为空', trigger: 'blur' }],
},
submitting: false,
l2HotCoilId: '',
historyLoading: false,
historyList: [],
historyTotal: 0,
historyQuery: { pageNum: 1, pageSize: 10, actionType: 520 },
}
},
computed: {
weightAssigned() {
return this.form.children.reduce((s, c) => s + (c.weight || 0), 0)
},
weightLeft() {
if (!this.form.sourceWeight) return null
return this.form.sourceWeight - this.weightAssigned
},
},
created() {
this.loadHistory()
},
methods: {
defaultForm() {
return {
enterCoilNo: '',
sourceWeight: undefined,
children: [{ coilNo: '', weight: undefined, length: undefined }],
exitThickness: undefined,
exitWidth: undefined,
team: undefined,
remark: '',
actionType: 520,
}
},
addChild() {
this.form.children.push({ coilNo: '', weight: undefined, length: undefined })
},
removeChild(idx) {
this.form.children.splice(idx, 1)
},
onEnterCoilInput() {
this.l2HotCoilId = (this.form.enterCoilNo || '').trim()
},
clearL2() {
this.l2HotCoilId = ''
},
applyL2Fill(data) {
if (data.exit_thick != null) this.form.exitThickness = parseFloat(data.exit_thick)
if (data.exit_width != null) this.form.exitWidth = parseFloat(data.exit_width)
if (data.entry_weight != null) this.form.sourceWeight = parseFloat(data.entry_weight)
this.$message.success('L2 数据已写入表单')
},
submitForm() {
this.$refs.form.validate(valid => {
if (!valid) return
this.submitting = true
// 每个子卷提交一条记录
const records = this.form.children.map(child => ({
enterCoilNo: this.form.enterCoilNo,
currentCoilNo: child.coilNo,
netWeight: child.weight,
exitLength: child.length,
exitThickness: this.form.exitThickness,
exitWidth: this.form.exitWidth,
team: this.form.team,
remark: this.form.remark,
actionType: 520,
}))
// 串行提交(也可并行,视后端是否支持批量而定)
const submitAll = records.reduce((p, rec) => {
return p.then(() => addCoilWarehouseOperationLog(rec))
}, Promise.resolve())
submitAll.then(() => {
this.$modal.msgSuccess(`分卷操作已记录(共 ${records.length} 条子卷)`)
this.resetForm()
this.loadHistory()
}).finally(() => { this.submitting = false })
})
},
resetForm() {
this.form = this.defaultForm()
this.l2HotCoilId = ''
this.$nextTick(() => { this.$refs.form && this.$refs.form.clearValidate() })
},
loadHistory() {
this.historyLoading = true
listCoilWarehouseOperationLog(this.historyQuery).then(res => {
this.historyList = res.rows || []
this.historyTotal = res.total || 0
}).finally(() => { this.historyLoading = false })
},
}
}
</script>
<style scoped>
.acid-op-page { background: #f5f7fa; }
.op-card {
background: #fff;
border: 1px solid #e4e7ed;
border-radius: 4px;
padding: 16px;
}
.op-header {
display: flex;
align-items: center;
margin-bottom: 16px;
padding-bottom: 10px;
border-bottom: 2px solid #e6a23c;
}
.op-title { font-size: 15px; font-weight: 600; color: #303133; }
.weight-hint { font-size: 12px; color: #606266; }
.weight-hint.over { color: #f56c6c; }
.weight-hint b { color: #67c23a; }
.weight-hint.over b { color: #f56c6c; }
</style>

View File

@@ -0,0 +1,188 @@
<template>
<div class="l2-panel">
<!-- 最佳匹配 -->
<div class="panel-block">
<div class="pb-header">
<span class="pb-title">最佳匹配</span>
<el-tag v-if="bestMatch" size="mini" type="success" style="margin-left:6px">热卷号已找到</el-tag>
<el-tag v-else-if="matchLoading" size="mini" type="info" style="margin-left:6px">查询中</el-tag>
<el-tag v-else-if="hotCoilId" size="mini" type="warning" style="margin-left:6px">未找到</el-tag>
<el-tag v-else size="mini" style="margin-left:6px">待输入</el-tag>
</div>
<template v-if="bestMatch">
<div class="match-grid">
<div class="mg-item" v-for="f in PLAN_FIELDS" :key="f.key">
<span class="mg-label">{{ f.label }}</span>
<span class="mg-value">{{ formatPlanField(f.key, bestMatch[f.key]) }}</span>
</div>
</div>
<el-button type="primary" size="small" style="width:100%;margin-top:10px"
icon="el-icon-download" @click="$emit('fill', bestMatch)">
写入表单
</el-button>
</template>
<div v-else-if="matchLoading" class="pb-empty"><i class="el-icon-loading" /></div>
<div v-else class="pb-empty">{{ hotCoilId ? '未找到匹配的 L2 计划记录' : '输入入场钢卷号后自动查询' }}</div>
</div>
<!-- 最近20条 -->
<div class="panel-block" style="margin-top:12px">
<div class="pb-header">
<span class="pb-title">最近 20 条生产记录</span>
<el-button type="text" size="mini" icon="el-icon-refresh"
style="margin-left:auto" :loading="recentLoading" @click="loadRecentList">刷新</el-button>
</div>
<el-table :data="recentList" size="mini" v-loading="recentLoading"
max-height="400" border style="width:100%">
<el-table-column label="热卷号" min-width="110" show-overflow-tooltip>
<template slot-scope="{ row }">
<span :class="(row.hot_coilid || row.encoilid) === hotCoilId ? 'hot-match' : ''">
{{ row.hot_coilid || row.encoilid || '—' }}
</span>
</template>
</el-table-column>
<el-table-column label="出口厚" width="72" align="right">
<template slot-scope="{ row }">{{ row.exit_thick != null ? row.exit_thick : '—' }}</template>
</el-table-column>
<el-table-column label="出口宽" width="72" align="right">
<template slot-scope="{ row }">{{ row.exit_width != null ? row.exit_width : '—' }}</template>
</el-table-column>
<el-table-column label="长度(m)" width="76" align="right">
<template slot-scope="{ row }">{{ row.exit_length != null ? row.exit_length : '—' }}</template>
</el-table-column>
<el-table-column label="重量(t)" width="72" align="right">
<template slot-scope="{ row }">{{ row.entry_weight != null ? row.entry_weight : '—' }}</template>
</el-table-column>
<el-table-column label="" width="52" fixed="right" align="center">
<template slot-scope="{ row }">
<el-button type="text" size="mini" @click="$emit('fill', row)">写入</el-button>
</template>
</el-table-column>
</el-table>
</div>
</div>
</template>
<script>
import { getTimingPlanDetailByHotcoilId, getExcoilList } from '@/api/l2/timing'
const PLAN_FIELDS = [
{ key: 'hot_coilid', label: '热卷号' },
{ key: 'status', label: '状态' },
{ key: 'process_code', label: '工艺编码' },
{ key: 'entry_thick', label: '入口厚(mm)' },
{ key: 'entry_width', label: '入口宽(mm)' },
{ key: 'entry_weight', label: '入口重量(t)' },
{ key: 'exit_thick', label: '出口厚(mm)' },
{ key: 'exit_width', label: '出口宽(mm)' },
{ key: 'exit_length', label: '出口长度(m)' },
{ key: 'park_type', label: '包装要求' },
{ key: 'trimming', label: '切边要求' },
]
export default {
name: 'L2MatchPanel',
props: {
hotCoilId: { type: String, default: '' }
},
data() {
return {
PLAN_FIELDS,
bestMatch: null,
matchLoading: false,
recentList: [],
recentLoading: false,
}
},
watch: {
hotCoilId(val) {
if (val) this.loadBestMatch(val)
else this.bestMatch = null
}
},
created() {
this.loadRecentList()
if (this.hotCoilId) this.loadBestMatch(this.hotCoilId)
},
methods: {
loadBestMatch(hotCoilId) {
this.bestMatch = null
this.matchLoading = true
getTimingPlanDetailByHotcoilId(hotCoilId).then(res => {
const fr = res?.data?.firstRow
this.bestMatch = (fr && Object.keys(fr).length > 0) ? fr : null
}).catch(() => {}).finally(() => { this.matchLoading = false })
},
loadRecentList() {
this.recentLoading = true
getExcoilList(1, 20).then(res => {
const rows = res?.data?.rows || []
this.recentList = rows.map(row => this.normalizeExcoilRow(row))
}).catch(() => {}).finally(() => { this.recentLoading = false })
},
formatPlanField(key, val) {
if (val == null) return '—'
if (key === 'trimming') {
if (val === 1 || val === '1') return '净边料'
if (val === 0 || val === '0') return '毛边料'
}
return val
},
normalizeExcoilRow(row) {
const g = k => row[k] != null ? row[k] : (row[k.toUpperCase()] != null ? row[k.toUpperCase()] : null)
return {
hot_coilid: g('hot_coilid'),
encoilid: g('encoilid'),
excoilid: g('excoilid'),
exit_thick: g('exit_thick'),
exit_width: g('exit_width'),
exit_length: g('exit_length'),
entry_weight: g('used_entry_weight') != null ? g('used_entry_weight')
: (g('meas_exit_weight') != null ? g('meas_exit_weight') : g('entry_weight')),
}
}
}
}
</script>
<style scoped>
.l2-panel { font-size: 13px; }
.panel-block {
background: #fff;
border: 1px solid #e4e7ed;
border-radius: 4px;
padding: 12px;
}
.pb-header {
display: flex;
align-items: center;
margin-bottom: 10px;
padding-bottom: 8px;
border-bottom: 1px solid #f0f2f5;
}
.pb-title { font-weight: 600; color: #303133; font-size: 13px; }
.match-grid {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 6px 12px;
}
.mg-item { display: flex; flex-direction: column; }
.mg-label { font-size: 11px; color: #909399; }
.mg-value { font-size: 13px; color: #303133; font-weight: 500; }
.pb-empty {
text-align: center;
color: #c0c4cc;
padding: 20px 0;
font-size: 12px;
}
.hot-match { color: #67c23a; font-weight: 600; }
</style>

View File

@@ -26,25 +26,9 @@
<!-- 主内容区 - 左右布局 -->
<div class="content-wrapper">
<!-- 左侧酸轧信息推荐 -->
<!-- 左侧L2 匹配面板 -->
<div>
<!-- 酸连轧最近10条记录展示 -->
<el-card class="recent-records-card" v-if="acidRecentRecords && acidRecentRecords.length > 0">
<div slot="header" class="card-header">
<span><i class="el-icon-time"></i> 酸连轧最近记录</span>
</div>
<el-table :data="acidRecentRecords" stripe size="small" @row-click="handleClickRecord">
<el-table-column prop="currentCoilNo" label="加工前卷号" show-overflow-tooltip></el-table-column>
<el-table-column prop="excoilId" label="出口卷号" show-overflow-tooltip></el-table-column>
<el-table-column prop="exitWeight" label="出口重量(t)" width="100">
<template slot-scope="scope">
{{ scope.row.exitWeight ? scope.row.exitWeight + ' t' : '—' }}
</template>
</el-table-column>
<el-table-column prop="team" label="班组" width="80"></el-table-column>
</el-table>
</el-card>
<l2-match-panel :hot-coil-id="l2HotCoilId" @fill="applyL2Fill" />
</div>
<!-- 右侧更新表单 -->
<div>
@@ -138,35 +122,6 @@
</el-col>
</el-row>
<el-row>
<el-col :span="8">
<el-form-item label="质量状态" prop="qualityStatus">
<el-select v-model="updateForm.qualityStatus" placeholder="请选择质量状态" style="width: 100%">
<el-option v-for="item in dict.type.coil_quality_status" :key="item.value" :label="item.label"
:value="item.value" />
</el-select>
</el-form-item>
</el-col>
<el-col :span="8">
<el-form-item label="切边要求" prop="trimmingRequirement">
<el-select v-model="updateForm.trimmingRequirement" placeholder="请选择切边要求" style="width: 100%">
<el-option label="净边料" value="净边料" />
<el-option label="毛边料" value="毛边料" />
</el-select>
</el-form-item>
</el-col>
<el-col :span="8">
<el-form-item label="包装要求" prop="packagingRequirement">
<el-select v-model="updateForm.packagingRequirement" placeholder="请选择包装要求" style="width: 100%">
<el-option label="裸包" value="裸包" />
<el-option label="普包" value="普包" />
<el-option label="简包" value="简包" />
<el-option label="精包" value="精包" />
</el-select>
</el-form-item>
</el-col>
</el-row>
<el-row>
<el-col :span="8">
<el-form-item label="毛重(t)" prop="grossWeight">
@@ -361,8 +316,6 @@
<script>
import { getMaterialCoil, updateMaterialCoil, getFirstHeatCoilMaterial } from '@/api/wms/coil';
import { completeAction, getPendingAction } from '@/api/wms/pendingAction';
// import { getTimingPlanList } from '@/api/l2/timing'
import { getAcidTypingPrefill } from '@/api/pocket/acidTyping';
import { saveCoilCache, getCoilCacheByCoilId, delCoilCache } from '@/api/wms/coilCache';
import ActualWarehouseSelect from "@/components/KLPService/ActualWarehouseSelect";
import RawMaterialSelect from "@/components/KLPService/RawMaterialSelect";
@@ -373,6 +326,7 @@ import AbnormalForm from './components/AbnormalForm';
import { generateCoilNoPrefix } from "@/utils/coil/coilNo";
import { addCoilContractRel } from "@/api/wms/coilContractRel";
import ContractSelect from "@/components/KLPService/ContractSelect";
import L2MatchPanel from './panels/L2MatchPanel.vue';
export default {
@@ -384,7 +338,8 @@ export default {
WarehouseSelect,
TimeInput,
AbnormalForm,
ContractSelect
ContractSelect,
L2MatchPanel,
},
dicts: ['coil_quality_status', 'coil_abnormal_position', 'coil_abnormal_code', 'coil_abnormal_degree', 'coil_business_purpose'],
data() {
@@ -479,9 +434,6 @@ export default {
type: 'info',
title: ''
},
isAcidRolling: false,
// 酸连轧最近记录
acidRecentRecords: [],
// 异常信息
abnormals: [],
// 异常表单弹窗
@@ -507,6 +459,9 @@ export default {
};
},
computed: {
l2HotCoilId() {
return (this.currentInfo && this.currentInfo.enterCoilNo) || ''
},
// 动态显示标签
getItemLabel() {
if (this.updateForm.materialType === '成品') {
@@ -530,98 +485,13 @@ export default {
// 从路由参数获取coilId和actionId
const coilId = this.$route.query.coilId;
const actionId = this.$route.query.actionId;
let actionType = ''
const pendingActionRes = await getPendingAction(actionId)
actionType = pendingActionRes.data.actionType
// 填写生产开始时间
this.$set(this.updateForm, 'productionStartTime', pendingActionRes.data.createTime)
this.isAcidRolling = (actionType == 11)
if (this.isAcidRolling) {
this.acidPrefill.visible = true
this.acidPrefill.type = 'info'
this.acidPrefill.title = '正在结合酸轧二级系统自动填写部分信息...'
}
if (coilId) {
await this.loadCoilInfo(coilId);
if (this.isAcidRolling) {
const currentCoilNo = this.currentInfo && this.currentInfo.currentCoilNo
if (!currentCoilNo) {
this.acidPrefill.type = 'warning'
this.acidPrefill.title = '当前钢卷号为空'
} else {
try {
const prefillRes = await getAcidTypingPrefill(currentCoilNo)
const prefill = prefillRes && prefillRes.data
if (!prefill || prefill.length === 0) {
this.acidPrefill.type = 'info'
this.acidPrefill.title = '未在二级系统中查找到对应信息,请自行填写'
} else {
// 处理返回的列表数据
if (Array.isArray(prefill)) {
this.acidRecentRecords = prefill;
// 使用第一条记录填充表单(保持原有逻辑)
const firstRecord = prefill[0];
if (firstRecord) {
if (firstRecord.exitWeight != null && firstRecord.exitWeight !== '') {
const w = Number(firstRecord.exitWeight)
if (!Number.isNaN(w)) {
this.$set(this.updateForm, 'grossWeight', w)
this.$set(this.updateForm, 'netWeight', w)
}
}
if (firstRecord.exitLength != null && firstRecord.exitLength !== '') {
const len = Number(firstRecord.exitLength)
if (!Number.isNaN(len)) {
this.$set(this.updateForm, 'length', len)
}
}
if (firstRecord.team) {
this.$set(this.updateForm, 'team', firstRecord.team)
}
}
} else {
// 为了兼容旧版本的单个对象返回格式
if (prefill.exitWeight != null && prefill.exitWeight !== '') {
const w = Number(prefill.exitWeight)
if (!Number.isNaN(w)) {
this.$set(this.updateForm, 'grossWeight', w)
this.$set(this.updateForm, 'netWeight', w)
}
}
if (prefill.exitLength != null && prefill.exitLength !== '') {
const len = Number(prefill.exitLength)
if (!Number.isNaN(len)) {
this.$set(this.updateForm, 'length', len)
}
}
if (prefill.team) {
this.$set(this.updateForm, 'team', prefill.team)
}
}
this.acidPrefill.type = 'success'
this.acidPrefill.title = '已结合酸轧二级系统完成部分信息填写'
console.log('[typing] acid rolling prefill applied:', prefill)
}
} catch (e) {
console.error('[typing] acid rolling prefill request failed:', e)
this.acidPrefill.type = 'error'
this.acidPrefill.title = '未在二级系统中查找到对应信息,故自动填写失败'
}
}
}
}
const currentCoilNoPrefix = generateCoilNoPrefix()
@@ -653,6 +523,36 @@ export default {
}
},
methods: {
applyL2Fill(data) {
if (data.entry_weight != null) {
const w = parseFloat(data.entry_weight)
if (!Number.isNaN(w)) {
this.$set(this.updateForm, 'grossWeight', w)
this.$set(this.updateForm, 'netWeight', w)
}
}
if (data.exit_length != null) {
const len = parseFloat(data.exit_length)
if (!Number.isNaN(len)) {
this.$set(this.updateForm, 'length', len)
this.$set(this.updateForm, 'actualLength', len)
}
}
if (data.exit_thick != null) this.$set(this.updateForm, 'actualThickness', parseFloat(data.exit_thick))
if (data.exit_width != null) this.$set(this.updateForm, 'actualWidth', parseFloat(data.exit_width))
// 包装要求
if (data.park_type != null && data.park_type !== '') {
this.$set(this.updateForm, 'packagingRequirement', data.park_type)
}
// 切边要求1=净边料0=毛边料
if (data.trimming != null) {
const t = String(data.trimming)
if (t === '1') this.$set(this.updateForm, 'trimmingRequirement', '净边料')
else if (t === '0') this.$set(this.updateForm, 'trimmingRequirement', '毛边料')
}
this.$message.success('L2 数据已写入表单')
},
// 处理材料类型变化
handleMaterialTypeChange(value) {
// 清空物品选择
@@ -667,17 +567,6 @@ export default {
}
},
handleClickRecord(row) {
this.updateForm = {
...this.updateForm,
currentCoilNo: row.excoilId,
team: row.team,
netWeight: row.exitWeight,
grossWeight: row.exitWeight,
length: row.exitLength,
}
},
// 加载钢卷信息
async loadCoilInfo(coilId) {
try {
@@ -1050,7 +939,7 @@ export default {
.content-wrapper {
display: grid;
margin-top: 10px;
grid-template-columns: 600px 1fr;
grid-template-columns: 340px 1fr;
gap: 10px;
align-items: stretch; // 改为stretch让子元素高度一致
}

View File

@@ -1,6 +1,7 @@
-- 轧辊库
CREATE TABLE mes_roll_info (
roll_id BIGINT NOT NULL AUTO_INCREMENT,
line_id BIGINT NULL COMMENT '产线ID关联 wms_production_line.line_id',
roll_no VARCHAR(50) NOT NULL COMMENT '轧辊编号',
roll_type VARCHAR(10) NOT NULL COMMENT '辊型: WR=工作辊 / BR=支撑辊',
status VARCHAR(20) NOT NULL DEFAULT 'Offline' COMMENT 'Online/Standby/Offline/Scrapped',
@@ -20,12 +21,14 @@ CREATE TABLE mes_roll_info (
update_by VARCHAR(64), update_time DATETIME,
remark VARCHAR(500),
PRIMARY KEY (roll_id),
UNIQUE KEY uk_roll_no (roll_no)
UNIQUE KEY uk_roll_no (roll_no),
KEY idx_line_id (line_id)
) ENGINE=InnoDB COMMENT='轧辊库';
-- 换辊记录(四辊轧机,双机架
-- 换辊记录(四辊轧机,支持多产线
CREATE TABLE mes_roll_change (
change_id BIGINT NOT NULL AUTO_INCREMENT,
line_id BIGINT NULL COMMENT '产线ID关联 wms_production_line.line_id',
change_no VARCHAR(50) COMMENT '换辊编号',
change_time DATETIME COMMENT '换辊时间',
stand_no VARCHAR(10) COMMENT '机架号: 1# / 2#',
@@ -46,12 +49,14 @@ CREATE TABLE mes_roll_change (
update_by VARCHAR(64), update_time DATETIME,
remark VARCHAR(500),
PRIMARY KEY (change_id),
KEY idx_line_stand (line_id, stand_no),
KEY idx_stand_time (stand_no, change_time)
) ENGINE=InnoDB COMMENT='换辊记录';
-- 下批轧辊(每条=一个辊位的备用辊)
-- 下批轧辊(每条=一个辊位的备用辊,支持多产线
CREATE TABLE mes_roll_standby (
standby_id BIGINT NOT NULL AUTO_INCREMENT,
line_id BIGINT NULL COMMENT '产线ID关联 wms_production_line.line_id',
stand_no VARCHAR(10) COMMENT '机架号: 1# / 2#',
roll_no VARCHAR(50) COMMENT '轧辊编号',
roll_type VARCHAR(10) COMMENT 'WR=工作辊 / BR=支撑辊',
@@ -64,5 +69,6 @@ CREATE TABLE mes_roll_standby (
create_by VARCHAR(64), create_time DATETIME,
update_by VARCHAR(64), update_time DATETIME,
remark VARCHAR(500),
PRIMARY KEY (standby_id)
PRIMARY KEY (standby_id),
KEY idx_line_stand (line_id, stand_no)
) ENGINE=InnoDB COMMENT='下批轧辊(待换上)';