Compare commits

..

2 Commits

Author SHA1 Message Date
75a2623d8b config: 更新生产环境和预发布环境页面标题
- 将生产环境页面标题从"科伦普冷轧涂镀数智运营一体化平台"改为"MES一体化平台"
- 将预发布环境页面标题从"科伦普冷轧涂镀数智运营一体化平台"改为"MES一体化平台"
2026-03-09 16:58:25 +08:00
5d046be15b docs(app): 更新应用名称为MES一体化平台
- 将VUE_APP_TITLE从"科伦普冷轧涂镀数智运营一体化平台"更改为"MES一体化平台"
- 修改Greeting组件中的平台描述为"欢迎使用MES数智一体化平台"
- 将favicon链接从png格式更改为ico格式
- 替换index.vue中的关于页面内容为MES平台相关介绍
- 更新登录页面中的平台描述和公司信息
- 修改侧边栏Logo组件中的平台标题为"MES一体化平台"
- 注释掉dashboard demo组件中的全屏功能代码
2026-03-09 16:27:47 +08:00
343 changed files with 6005 additions and 27871 deletions

View File

@@ -11,7 +11,7 @@ import org.springframework.boot.context.metrics.buffering.BufferingApplicationSt
*/
//@SpringBootApplication
@SpringBootApplication
@SpringBootApplication(exclude = org.springframework.boot.autoconfigure.flyway.FlywayAutoConfiguration.class)
public class KLPApplication {
public static void main(String[] args) {

View File

@@ -130,6 +130,10 @@ spring:
# 多久检查一次连接的活性
keepaliveTime: 30000
flyway:
baseline-on-migrate: true # 第一次运行时建立记录,不执行历史脚本
clean-disabled: true # 禁止清空库
--- # redis 单机配置(单机与集群只能开启一个另一个需要注释掉)
spring:
redis:

View File

@@ -124,6 +124,10 @@ spring:
# 多久检查一次连接的活性
keepaliveTime: 30000
flyway:
baseline-on-migrate: true # 第一次运行时建立记录,不执行历史脚本
clean-disabled: true # 禁止清空库
--- # redis 单机配置(单机与集群只能开启一个另一个需要注释掉)
spring:
redis:

View File

@@ -101,6 +101,11 @@ spring:
deserialization:
# 允许对象忽略json中不存在的属性
fail_on_unknown_properties: false
# 实时更新数据库结构
flyway:
enabled: true
locations: classpath:db/migration
table: flyway_schema_history
# Sa-Token配置
sa-token:
@@ -337,9 +342,3 @@ stamp:
base-url: http://python-stamp-service.example.com # 替换为实际地址
api-key: changeme # 替换为实际鉴权信息
timeout-ms: 5000 # 可按需调整
# OEE配置
oee:
acid:
# 酸轧入场卷创建人wms_material_coil.create_by
coil-create-by: suanzhakuguan

View File

@@ -1,28 +0,0 @@
CREATE TABLE IF NOT EXISTS aps_quick_sheet (
quick_sheet_id BIGINT AUTO_INCREMENT PRIMARY KEY,
plan_date DATE NOT NULL COMMENT '计划日期',
line_id BIGINT NULL COMMENT '产线ID',
line_name VARCHAR(120) NULL COMMENT '产线名称',
plan_code VARCHAR(60) NOT NULL COMMENT '计划号',
order_code VARCHAR(80) NULL COMMENT '订单号',
customer_name VARCHAR(120) NULL COMMENT '客户',
salesman VARCHAR(60) NULL COMMENT '业务员',
product_name VARCHAR(120) NULL COMMENT '产品',
raw_material_id VARCHAR(64) NULL COMMENT '原料钢卷',
raw_coil_nos VARCHAR(255) NULL COMMENT '原料卷号',
raw_location VARCHAR(120) NULL COMMENT '钢卷位置',
raw_packaging VARCHAR(120) NULL COMMENT '包装要求',
raw_edge_req VARCHAR(120) NULL COMMENT '切边要求',
raw_coating_type VARCHAR(120) NULL COMMENT '镀层种类',
raw_net_weight DECIMAL(18, 3) NULL COMMENT '原料净重',
plan_qty DECIMAL(18, 3) NULL COMMENT '计划数量',
start_time DATETIME NULL COMMENT '开始时间',
end_time DATETIME NULL COMMENT '结束时间',
del_flag TINYINT DEFAULT 0 COMMENT '删除标记(0正常 1删除)',
create_by VARCHAR(64) NULL,
update_by VARCHAR(64) NULL,
create_time DATETIME DEFAULT CURRENT_TIMESTAMP,
update_time DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
INDEX idx_aps_quick_sheet_plan_date (plan_date),
INDEX idx_aps_quick_sheet_line_id (line_id)
) COMMENT = '快速排产表(Excel样式)';

View File

@@ -1,47 +0,0 @@
CREATE TABLE IF NOT EXISTS wms_furnace (
furnace_id BIGINT AUTO_INCREMENT PRIMARY KEY,
furnace_code VARCHAR(50) NOT NULL COMMENT '炉编号',
furnace_name VARCHAR(100) NOT NULL COMMENT '名称',
busy_flag TINYINT DEFAULT 0 COMMENT '是否忙碌(0否1是)',
status TINYINT DEFAULT 1 COMMENT '状态(0停用1启用)',
remark VARCHAR(500) NULL COMMENT '备注',
del_flag TINYINT DEFAULT 0 COMMENT '删除标记(0正常 1删除)',
create_by VARCHAR(64) NULL,
update_by VARCHAR(64) NULL,
create_time DATETIME DEFAULT CURRENT_TIMESTAMP,
update_time DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
UNIQUE KEY uk_wms_furnace_code (furnace_code)
) COMMENT = '退火炉信息表';
CREATE TABLE IF NOT EXISTS wms_furnace_plan (
plan_id BIGINT AUTO_INCREMENT PRIMARY KEY,
plan_no VARCHAR(60) NOT NULL COMMENT '计划号',
plan_start_time DATETIME NULL COMMENT '计划开始时间',
actual_start_time DATETIME NULL COMMENT '实际开始时间',
end_time DATETIME NULL COMMENT '结束时间',
target_furnace_id BIGINT NOT NULL COMMENT '目标炉子ID',
status TINYINT DEFAULT 0 COMMENT '计划状态(0未开始 1进行中 2已完成)',
remark VARCHAR(500) NULL COMMENT '备注',
del_flag TINYINT DEFAULT 0 COMMENT '删除标记(0正常 1删除)',
create_by VARCHAR(64) NULL,
update_by VARCHAR(64) NULL,
create_time DATETIME DEFAULT CURRENT_TIMESTAMP,
update_time DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
UNIQUE KEY uk_wms_furnace_plan_no (plan_no),
INDEX idx_wms_furnace_plan_furnace (target_furnace_id),
INDEX idx_wms_furnace_plan_status (status)
) COMMENT = '退火计划表';
CREATE TABLE IF NOT EXISTS wms_furnace_plan_coil (
plan_coil_id BIGINT AUTO_INCREMENT PRIMARY KEY,
plan_id BIGINT NOT NULL COMMENT '计划ID',
coil_id BIGINT NOT NULL COMMENT '钢卷ID',
del_flag TINYINT DEFAULT 0 COMMENT '删除标记(0正常 1删除)',
create_by VARCHAR(64) NULL,
update_by VARCHAR(64) NULL,
create_time DATETIME DEFAULT CURRENT_TIMESTAMP,
update_time DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
UNIQUE KEY uk_wms_furnace_plan_coil (plan_id, coil_id),
INDEX idx_wms_furnace_plan_coil_plan (plan_id),
INDEX idx_wms_furnace_plan_coil_coil (coil_id)
) COMMENT = '退火计划钢卷关系表';

View File

@@ -1,55 +0,0 @@
package com.klp.aps.controller;
import com.klp.aps.domain.dto.ApsQuickSheetQueryReq;
import com.klp.aps.domain.dto.ApsQuickSheetSaveReq;
import com.klp.aps.domain.vo.ApsQuickSheetRowVo;
import com.klp.aps.service.ApsQuickSheetService;
import com.klp.common.core.controller.BaseController;
import com.klp.common.core.domain.R;
import com.klp.common.helper.LoginHelper;
import lombok.RequiredArgsConstructor;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import java.util.List;
@RequiredArgsConstructor
@RestController
@RequestMapping("/aps/quick-sheet")
public class ApsQuickSheetController extends BaseController {
private final ApsQuickSheetService quickSheetService;
@GetMapping("/list")
public R<List<ApsQuickSheetRowVo>> list(@Validated ApsQuickSheetQueryReq req) {
return R.ok(quickSheetService.queryList(req));
}
@GetMapping("/preset")
public R<List<ApsQuickSheetRowVo>> preset(@RequestParam(value = "lineId", required = false) Long lineId) {
String salesman = LoginHelper.getNickName();
return R.ok(quickSheetService.buildPresetRows(lineId, salesman));
}
@PostMapping("/save")
public R<Void> save(@Validated @RequestBody ApsQuickSheetSaveReq req) {
quickSheetService.saveRows(req, getUsername());
return R.ok();
}
@GetMapping("/export")
public void export(@Validated ApsQuickSheetQueryReq req, javax.servlet.http.HttpServletResponse response) {
quickSheetService.exportExcel(req, response);
}
@PostMapping("/delete")
public R<Void> delete(@RequestParam("quickSheetId") Long quickSheetId) {
quickSheetService.deleteById(quickSheetId, getUsername());
return R.ok();
}
}

View File

@@ -1,20 +0,0 @@
package com.klp.aps.domain.dto;
import lombok.Data;
import org.springframework.format.annotation.DateTimeFormat;
import java.time.LocalDate;
@Data
public class ApsQuickSheetQueryReq {
@DateTimeFormat(pattern = "yyyy-MM-dd")
private LocalDate startDate;
@DateTimeFormat(pattern = "yyyy-MM-dd")
private LocalDate endDate;
private Long lineId;
private String customerName;
}

View File

@@ -1,35 +0,0 @@
package com.klp.aps.domain.dto;
import lombok.Data;
import javax.validation.constraints.NotEmpty;
import java.util.List;
@Data
public class ApsQuickSheetSaveReq {
@NotEmpty(message = "rows 不能为空")
private List<Row> rows;
@Data
public static class Row {
private Long quickSheetId;
private Long lineId;
private String lineName;
private String planCode;
private String orderCode;
private String customerName;
private String salesman;
private String productName;
private String rawMaterialId;
private String rawCoilNos;
private String rawLocation;
private String rawPackaging;
private String rawEdgeReq;
private String rawCoatingType;
private String rawNetWeight;
private String planQty;
private String startTime;
private String endTime;
}
}

View File

@@ -1,35 +0,0 @@
package com.klp.aps.domain.entity;
import lombok.Data;
import java.math.BigDecimal;
import java.time.LocalDate;
import java.time.LocalDateTime;
@Data
public class ApsQuickSheetEntity {
private Long quickSheetId;
private LocalDate planDate;
private Long lineId;
private String lineName;
private String planCode;
private String orderCode;
private String customerName;
private String salesman;
private String productName;
private String rawMaterialId;
private String rawCoilNos;
private String rawLocation;
private String rawPackaging;
private String rawEdgeReq;
private String rawCoatingType;
private BigDecimal rawNetWeight;
private BigDecimal planQty;
private LocalDateTime startTime;
private LocalDateTime endTime;
private String createBy;
private String updateBy;
private LocalDateTime createTime;
private LocalDateTime updateTime;
private Integer delFlag;
}

View File

@@ -1,28 +0,0 @@
package com.klp.aps.domain.vo;
import lombok.Data;
import java.math.BigDecimal;
import java.time.LocalDateTime;
@Data
public class ApsQuickSheetRowVo {
private Long quickSheetId;
private Long lineId;
private String lineName;
private String planCode;
private String orderCode;
private String customerName;
private String salesman;
private String productName;
private String rawMaterialId;
private String rawCoilNos;
private String rawLocation;
private String rawPackaging;
private String rawEdgeReq;
private String rawCoatingType;
private BigDecimal rawNetWeight;
private BigDecimal planQty;
private LocalDateTime startTime;
private LocalDateTime endTime;
}

View File

@@ -1,73 +0,0 @@
package com.klp.aps.mapper;
import com.klp.aps.domain.vo.ApsQuickSheetRowVo;
import com.klp.aps.domain.dto.ApsQuickSheetQueryReq;
import org.apache.ibatis.annotations.Param;
import org.apache.ibatis.annotations.Select;
import java.util.List;
public interface ApsQuickSheetMapper {
List<ApsQuickSheetRowVo> selectList(ApsQuickSheetQueryReq req);
@Select("SELECT COUNT(1) FROM aps_quick_sheet WHERE del_flag = 0 AND plan_date = #{planDate}")
int countToday(@Param("planDate") java.time.LocalDate planDate);
@Select("SELECT quick_sheet_id FROM aps_quick_sheet WHERE plan_code = #{planCode} AND del_flag = 0 LIMIT 1")
Long selectIdByPlanCode(@Param("planCode") String planCode);
@org.apache.ibatis.annotations.Insert("INSERT INTO aps_quick_sheet (line_id, line_name, plan_date, plan_code, order_code, customer_name, salesman, product_name, raw_material_id, raw_coil_nos, raw_location, raw_packaging, raw_edge_req, raw_coating_type, raw_net_weight, plan_qty, start_time, end_time, create_by, update_by, create_time, update_time, del_flag) "
+ "VALUES (#{lineId}, #{lineName}, #{planDate}, #{planCode}, #{orderCode}, #{customerName}, #{salesman}, #{productName}, #{rawMaterialId}, #{rawCoilNos}, #{rawLocation}, #{rawPackaging}, #{rawEdgeReq}, #{rawCoatingType}, #{rawNetWeight}, #{planQty}, #{startTime}, #{endTime}, #{createBy}, #{updateBy}, CURRENT_TIMESTAMP, CURRENT_TIMESTAMP, 0)")
int insertRow(@Param("lineId") Long lineId,
@Param("lineName") String lineName,
@Param("planDate") java.time.LocalDate planDate,
@Param("planCode") String planCode,
@Param("orderCode") String orderCode,
@Param("customerName") String customerName,
@Param("salesman") String salesman,
@Param("productName") String productName,
@Param("rawMaterialId") String rawMaterialId,
@Param("rawCoilNos") String rawCoilNos,
@Param("rawLocation") String rawLocation,
@Param("rawPackaging") String rawPackaging,
@Param("rawEdgeReq") String rawEdgeReq,
@Param("rawCoatingType") String rawCoatingType,
@Param("rawNetWeight") java.math.BigDecimal rawNetWeight,
@Param("planQty") java.math.BigDecimal planQty,
@Param("startTime") java.time.LocalDateTime startTime,
@Param("endTime") java.time.LocalDateTime endTime,
@Param("createBy") String createBy,
@Param("updateBy") String updateBy);
@org.apache.ibatis.annotations.Update("UPDATE aps_quick_sheet SET line_id = #{lineId}, line_name = #{lineName}, plan_code = #{planCode}, order_code = #{orderCode}, customer_name = #{customerName}, salesman = #{salesman}, product_name = #{productName}, raw_material_id = #{rawMaterialId}, raw_coil_nos = #{rawCoilNos}, raw_location = #{rawLocation}, raw_packaging = #{rawPackaging}, raw_edge_req = #{rawEdgeReq}, raw_coating_type = #{rawCoatingType}, raw_net_weight = #{rawNetWeight}, plan_qty = #{planQty}, start_time = #{startTime}, end_time = #{endTime}, update_by = #{updateBy}, update_time = CURRENT_TIMESTAMP WHERE quick_sheet_id = #{id}")
int updateRow(@Param("id") Long id,
@Param("lineId") Long lineId,
@Param("lineName") String lineName,
@Param("planCode") String planCode,
@Param("orderCode") String orderCode,
@Param("customerName") String customerName,
@Param("salesman") String salesman,
@Param("productName") String productName,
@Param("rawMaterialId") String rawMaterialId,
@Param("rawCoilNos") String rawCoilNos,
@Param("rawLocation") String rawLocation,
@Param("rawPackaging") String rawPackaging,
@Param("rawEdgeReq") String rawEdgeReq,
@Param("rawCoatingType") String rawCoatingType,
@Param("rawNetWeight") java.math.BigDecimal rawNetWeight,
@Param("planQty") java.math.BigDecimal planQty,
@Param("startTime") java.time.LocalDateTime startTime,
@Param("endTime") java.time.LocalDateTime endTime,
@Param("updateBy") String updateBy);
@org.apache.ibatis.annotations.Update("UPDATE aps_quick_sheet SET del_flag = 1, update_by = #{updateBy}, update_time = CURRENT_TIMESTAMP WHERE quick_sheet_id = #{id}")
int deleteRow(@Param("id") Long id, @Param("updateBy") String updateBy);
@org.apache.ibatis.annotations.Update("UPDATE aps_quick_sheet SET del_flag = 1, update_by = #{updateBy}, update_time = CURRENT_TIMESTAMP WHERE quick_sheet_id = #{id}")
int softDelete(@Param("id") Long id,
@Param("updateBy") String updateBy);
@org.apache.ibatis.annotations.Update("UPDATE aps_quick_sheet SET del_flag = 1, update_by = #{updateBy}, update_time = CURRENT_TIMESTAMP WHERE quick_sheet_id = #{id}")
int deleteById(@Param("id") Long id, @Param("updateBy") String updateBy);
}

View File

@@ -1,20 +0,0 @@
package com.klp.aps.service;
import com.klp.aps.domain.dto.ApsQuickSheetQueryReq;
import com.klp.aps.domain.dto.ApsQuickSheetSaveReq;
import com.klp.aps.domain.vo.ApsQuickSheetRowVo;
import javax.servlet.http.HttpServletResponse;
import java.util.List;
public interface ApsQuickSheetService {
List<ApsQuickSheetRowVo> queryList(ApsQuickSheetQueryReq req);
void saveRows(ApsQuickSheetSaveReq req, String operator);
List<ApsQuickSheetRowVo> buildPresetRows(Long lineId, String salesman);
void exportExcel(ApsQuickSheetQueryReq req, HttpServletResponse response);
void deleteById(Long id, String operator);
}

View File

@@ -1,214 +0,0 @@
package com.klp.aps.service.impl;
import com.klp.aps.domain.dto.ApsQuickSheetSaveReq;
import com.klp.aps.domain.vo.ApsQuickSheetRowVo;
import com.klp.aps.mapper.ApsQuickSheetMapper;
import com.klp.aps.service.ApsQuickSheetService;
import com.klp.common.exception.ServiceException;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import java.math.BigDecimal;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.ArrayList;
import java.util.List;
@RequiredArgsConstructor
@Service
public class ApsQuickSheetServiceImpl implements ApsQuickSheetService {
private final ApsQuickSheetMapper quickSheetMapper;
private static final DateTimeFormatter DATE_CODE = DateTimeFormatter.ofPattern("yyyyMMdd");
@Override
public List<ApsQuickSheetRowVo> queryList(com.klp.aps.domain.dto.ApsQuickSheetQueryReq req) {
return quickSheetMapper.selectList(req);
}
@Override
public void saveRows(ApsQuickSheetSaveReq req, String operator) {
if (req == null || req.getRows() == null) {
throw new ServiceException("保存数据不能为空");
}
for (ApsQuickSheetSaveReq.Row row : req.getRows()) {
if (row == null) continue;
Long id = row.getQuickSheetId();
Long lineId = row.getLineId();
String lineName = row.getLineName();
String planCode = row.getPlanCode();
String orderCode = row.getOrderCode();
String customerName = row.getCustomerName();
String salesman = row.getSalesman();
String productName = row.getProductName();
String rawMaterialId = row.getRawMaterialId();
String rawCoilNos = row.getRawCoilNos();
String rawLocation = row.getRawLocation();
String rawPackaging = row.getRawPackaging();
String rawEdgeReq = row.getRawEdgeReq();
String rawCoatingType = row.getRawCoatingType();
BigDecimal rawNetWeight = parseQty(row.getRawNetWeight());
BigDecimal planQty = parseQty(row.getPlanQty());
LocalDateTime startTime = parseTime(row.getStartTime());
LocalDateTime endTime = parseTime(row.getEndTime());
boolean hasAny = lineId != null || isNotBlank(lineName) || isNotBlank(planCode) || isNotBlank(orderCode)
|| isNotBlank(customerName) || isNotBlank(salesman) || isNotBlank(productName)
|| isNotBlank(rawMaterialId) || isNotBlank(rawCoilNos) || isNotBlank(rawLocation)
|| isNotBlank(rawPackaging) || isNotBlank(rawEdgeReq) || isNotBlank(rawCoatingType)
|| rawNetWeight != null || planQty != null || startTime != null || endTime != null;
if (!hasAny) {
continue;
}
if (id == null) {
if (!isNotBlank(planCode)) {
planCode = buildPlanCode();
}
quickSheetMapper.insertRow(lineId, lineName, LocalDate.now(), planCode, orderCode, customerName, salesman, productName,
rawMaterialId, rawCoilNos, rawLocation, rawPackaging, rawEdgeReq, rawCoatingType, rawNetWeight, planQty, startTime, endTime, operator, operator);
} else {
quickSheetMapper.updateRow(id, lineId, lineName, planCode, orderCode, customerName, salesman, productName,
rawMaterialId, rawCoilNos, rawLocation, rawPackaging, rawEdgeReq, rawCoatingType, rawNetWeight, planQty, startTime, endTime, operator);
}
}
}
@Override
public List<ApsQuickSheetRowVo> buildPresetRows(Long lineId, String salesman) {
List<ApsQuickSheetRowVo> rows = new ArrayList<>();
LocalDateTime now = LocalDateTime.now();
LocalDateTime end = now.plusHours(1);
for (int i = 0; i < 3; i++) {
ApsQuickSheetRowVo row = new ApsQuickSheetRowVo();
row.setLineId(lineId);
row.setLineName(lineId == null ? null : ("产线" + lineId));
row.setSalesman(salesman);
row.setPlanQty(BigDecimal.ZERO);
row.setStartTime(now);
row.setEndTime(end);
row.setPlanCode(buildPlanCode());
rows.add(row);
}
return rows;
}
@Override
public void exportExcel(com.klp.aps.domain.dto.ApsQuickSheetQueryReq req, javax.servlet.http.HttpServletResponse response) {
List<ApsQuickSheetRowVo> rows = queryList(req);
try (org.apache.poi.ss.usermodel.Workbook wb = new org.apache.poi.xssf.usermodel.XSSFWorkbook()) {
org.apache.poi.ss.usermodel.Sheet sheet = wb.createSheet("快速排产表");
int r = 0;
org.apache.poi.ss.usermodel.Row title = sheet.createRow(r++);
title.setHeightInPoints(36f);
org.apache.poi.ss.usermodel.Cell t0 = title.createCell(0);
t0.setCellValue("快速排产表Excel录入");
sheet.addMergedRegion(new org.apache.poi.ss.util.CellRangeAddress(0, 0, 0, 14));
org.apache.poi.ss.usermodel.CellStyle titleStyle = wb.createCellStyle();
titleStyle.setAlignment(org.apache.poi.ss.usermodel.HorizontalAlignment.CENTER);
titleStyle.setVerticalAlignment(org.apache.poi.ss.usermodel.VerticalAlignment.CENTER);
org.apache.poi.xssf.usermodel.XSSFFont titleFont = ((org.apache.poi.xssf.usermodel.XSSFWorkbook) wb).createFont();
titleFont.setBold(true);
titleFont.setFontHeightInPoints((short) 15);
titleStyle.setFont(titleFont);
t0.setCellStyle(titleStyle);
String[] headers = new String[]{
"产线", "计划号", "订单号", "客户", "业务员", "产品",
"原料钢卷", "原料卷号", "钢卷位置", "包装要求", "切边要求", "镀层种类",
"原料净重", "计划数量", "开始时间", "结束时间"
};
org.apache.poi.ss.usermodel.Row head = sheet.createRow(r++);
for (int i = 0; i < headers.length; i++) {
head.createCell(i).setCellValue(headers[i]);
}
if (rows != null) {
for (ApsQuickSheetRowVo row : rows) {
org.apache.poi.ss.usermodel.Row rr = sheet.createRow(r++);
int cc = 0;
rr.createCell(cc++).setCellValue(nvl(row.getLineName(), row.getLineId()));
rr.createCell(cc++).setCellValue(nvl(row.getPlanCode(), ""));
rr.createCell(cc++).setCellValue(nvl(row.getOrderCode(), ""));
rr.createCell(cc++).setCellValue(nvl(row.getCustomerName(), ""));
rr.createCell(cc++).setCellValue(nvl(row.getSalesman(), ""));
rr.createCell(cc++).setCellValue(nvl(row.getProductName(), ""));
rr.createCell(cc++).setCellValue(nvl(row.getRawMaterialId(), ""));
rr.createCell(cc++).setCellValue(nvl(row.getRawCoilNos(), ""));
rr.createCell(cc++).setCellValue(nvl(row.getRawLocation(), ""));
rr.createCell(cc++).setCellValue(nvl(row.getRawPackaging(), ""));
rr.createCell(cc++).setCellValue(nvl(row.getRawEdgeReq(), ""));
rr.createCell(cc++).setCellValue(nvl(row.getRawCoatingType(), ""));
rr.createCell(cc++).setCellValue(row.getRawNetWeight() == null ? "" : row.getRawNetWeight().toPlainString());
rr.createCell(cc++).setCellValue(row.getPlanQty() == null ? "" : row.getPlanQty().toPlainString());
rr.createCell(cc++).setCellValue(row.getStartTime() == null ? "" : row.getStartTime().toString());
rr.createCell(cc++).setCellValue(row.getEndTime() == null ? "" : row.getEndTime().toString());
}
}
for (int i = 0; i < headers.length; i++) {
sheet.autoSizeColumn(i, true);
int w = sheet.getColumnWidth(i);
sheet.setColumnWidth(i, Math.min(Math.max(w, 3000), 12000));
}
String filename = "aps_quick_sheet_" + System.currentTimeMillis() + ".xlsx";
String encoded = java.net.URLEncoder.encode(filename, java.nio.charset.StandardCharsets.UTF_8.name());
response.setCharacterEncoding(java.nio.charset.StandardCharsets.UTF_8.name());
response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet");
response.setHeader("Content-Disposition", "attachment; filename*=UTF-8''" + encoded);
try (javax.servlet.ServletOutputStream os = response.getOutputStream()) {
wb.write(os);
os.flush();
}
} catch (java.io.IOException e) {
throw new ServiceException("导出失败:" + e.getMessage());
}
}
private String buildPlanCode() {
LocalDate today = LocalDate.now();
int seq = quickSheetMapper.countToday(today) + 1;
return today.format(DATE_CODE) + String.format("%03d", seq);
}
private String nvl(Object v, Object fallback) {
if (v == null) return String.valueOf(fallback);
String s = String.valueOf(v);
return s == null ? String.valueOf(fallback) : s;
}
@Override
public void deleteById(Long id, String operator) {
if (id == null) return;
quickSheetMapper.deleteRow(id, operator);
}
private BigDecimal parseQty(String val) {
if (val == null || val.trim().isEmpty()) return null;
try {
return new BigDecimal(val.trim());
} catch (Exception e) {
return null;
}
}
private boolean isNotBlank(String val) {
return val != null && !val.trim().isEmpty();
}
private LocalDateTime parseTime(String val) {
if (val == null || val.trim().isEmpty()) return null;
String v = val.trim();
if (v.length() == 16) v = v + ":00";
try {
return LocalDateTime.parse(v, DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"));
} catch (Exception e) {
return null;
}
}
}

View File

@@ -1,44 +0,0 @@
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.klp.aps.mapper.ApsQuickSheetMapper">
<select id="selectList" parameterType="com.klp.aps.domain.dto.ApsQuickSheetQueryReq" resultType="com.klp.aps.domain.vo.ApsQuickSheetRowVo">
SELECT
quick_sheet_id AS quickSheetId,
line_id AS lineId,
line_name AS lineName,
plan_code AS planCode,
order_code AS orderCode,
customer_name AS customerName,
salesman,
product_name AS productName,
raw_material_id AS rawMaterialId,
raw_coil_nos AS rawCoilNos,
raw_location AS rawLocation,
raw_packaging AS rawPackaging,
raw_edge_req AS rawEdgeReq,
raw_coating_type AS rawCoatingType,
raw_net_weight AS rawNetWeight,
plan_qty AS planQty,
start_time AS startTime,
end_time AS endTime
FROM aps_quick_sheet
WHERE del_flag = 0
<if test="startDate != null">
AND start_time <![CDATA[>=]]> CONCAT(#{startDate}, ' 00:00:00')
</if>
<if test="endDate != null">
AND start_time <![CDATA[<=]]> CONCAT(#{endDate}, ' 23:59:59')
</if>
<if test="lineId != null">
AND line_id = #{lineId}
</if>
<if test="customerName != null and customerName != ''">
AND customer_name LIKE CONCAT('%', #{customerName}, '%')
</if>
ORDER BY quick_sheet_id DESC
</select>
</mapper>

View File

@@ -170,6 +170,16 @@
<artifactId>ip2region</artifactId>
</dependency>
<!-- 引入flyway -->
<dependency>
<groupId>org.flywaydb</groupId>
<artifactId>flyway-core</artifactId>
</dependency>
<dependency>
<groupId>org.flywaydb</groupId>
<artifactId>flyway-mysql</artifactId>
</dependency>
<!-- 动态数据源依赖 -->
<dependency>
<groupId>com.baomidou</groupId>

View File

@@ -0,0 +1,68 @@
package com.klp.common.config;
import javax.annotation.Resource;
import javax.sql.DataSource;
import com.baomidou.dynamic.datasource.DynamicRoutingDataSource;
import org.flywaydb.core.Flyway;
import org.flywaydb.core.api.output.MigrateResult;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.CommandLineRunner;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class FlywayConfig {
@Value("${spring.profiles.active}")
private String activeProfile;
@Value("${spring.flyway.baseline-on-migrate}")
private boolean baselineOnMigrate;
@Value("${spring.flyway.locations}")
private String locations;
@Value("${spring.flyway.table}")
private String table;
@Resource
private DataSource dataSource;
@Bean
public Flyway flyway() {
DataSource masterDataSource = ((DynamicRoutingDataSource) dataSource).getDataSource("master");
System.out.println("masterDataSource class: " + masterDataSource.getClass().getName());
// // 如果想显式拿底层 HikariDataSource
// if (masterDataSource instanceof ItemDataSource) {
// masterDataSource = ((ItemDataSource) masterDataSource).getRealDataSource();
// }
System.out.println("masterDataSource class: " + masterDataSource.getClass().getName());
return Flyway.configure()
.dataSource(masterDataSource) // 注意这里是真实主库 DataSource
.baselineOnMigrate(baselineOnMigrate)
.locations(locations)
.table(table)
.load();
}
@Bean
public CommandLineRunner flywayRunner(Flyway flyway) {
return args -> {
System.out.println("========== 当前环境: " + activeProfile + " ==========");
System.out.println("========== 开始执行 Flyway 数据库迁移 ==========");
MigrateResult result = flyway.migrate();
System.out.println("迁移成功版本数: " + result.migrationsExecuted);
result.migrations.forEach(m -> {
System.out.println("执行版本: " + m.version + ",描述: " + m.description);
});
System.out.println("========== Flyway 数据库迁移完成 ==========");
};
}
}

View File

@@ -12,7 +12,6 @@ import com.klp.pocket.acid.domain.vo.AcidOeeIdealCycleVo;
import com.klp.pocket.acid.domain.vo.AcidOeeLoss7Vo;
import com.klp.pocket.acid.domain.vo.Klptcm1ProStoppageVo;
import com.klp.pocket.acid.service.IAcidOeeService;
import com.klp.pocket.galvanize1.service.IGalvanizeOeeService;
import lombok.Data;
import lombok.RequiredArgsConstructor;
import org.apache.poi.xwpf.usermodel.ParagraphAlignment;
@@ -62,7 +61,6 @@ import java.util.stream.Collectors;
public class OeeReportController extends BaseController {
private final IAcidOeeService acidOeeService;
private final IGalvanizeOeeService galvanizeOeeService;
private final StringRedisTemplate stringRedisTemplate;
private final OeeReportJobService oeeReportJobService;
private final OeeWordAiAnalysisService oeeWordAiAnalysisService;
@@ -78,20 +76,26 @@ public class OeeReportController extends BaseController {
*
* 路由GET /oee/line/acid/summary
* 说明:
* - 支持 startDate/endDate 参数yyyy-MM-dd
* - 若不传则默认查询当前月份1号~今天);
* - 仅实时计算,不走缓存。
* - 不接受 start/end 参数固定返回“当前月份1号~今天)”的当月预计算结果
* - 优先从 Redis 当月缓存读取;若缓存缺失则实时计算一次当前月。
*/
@GetMapping("/acid/summary")
public R<List<AcidOeeDailySummaryVo>> getAcidSummary(
@RequestParam(required = false) String startDate,
@RequestParam(required = false) String endDate,
@RequestParam(required = false, defaultValue = "acid") String lineType
) {
String[] range = resolveDateRange(startDate, endDate);
List<AcidOeeDailySummaryVo> dailyList = isGalvanize(lineType)
? galvanizeOeeService.getDailySummary(range[0], range[1])
: acidOeeService.getDailySummary(range[0], range[1]);
public R<List<AcidOeeDailySummaryVo>> getAcidSummary() {
String yyyyMM = LocalDate.now().format(DateTimeFormatter.ofPattern("yyyyMM"));
String summaryKey = String.format("oee:report:month:summary:%s:SY", yyyyMM);
// 1. 优先从 Redis 读取当月预计算结果
String json = stringRedisTemplate.opsForValue().get(summaryKey);
if (StringUtils.isNotBlank(json)) {
List<AcidOeeDailySummaryVo> cached =
JSON.parseArray(json, AcidOeeDailySummaryVo.class);
return R.ok(cached);
}
// 2. 缓存缺失时,回退为实时计算当前月
String[] range = resolveDateRange(null, null);
List<AcidOeeDailySummaryVo> dailyList =
acidOeeService.getDailySummary(range[0], range[1]);
return R.ok(dailyList);
}
@@ -105,15 +109,21 @@ public class OeeReportController extends BaseController {
*/
@GetMapping("/acid/loss7")
public R<List<AcidOeeLoss7Vo>> getAcidLoss7(
@RequestParam(required = false, defaultValue = "50") Integer topN,
@RequestParam(required = false) String startDate,
@RequestParam(required = false) String endDate,
@RequestParam(required = false, defaultValue = "acid") String lineType
@RequestParam(required = false, defaultValue = "50") Integer topN
) {
String[] range = resolveDateRange(startDate, endDate);
List<AcidOeeLoss7Vo> lossList = isGalvanize(lineType)
? galvanizeOeeService.getLoss7Summary(range[0], range[1])
: acidOeeService.getLoss7Summary(range[0], range[1]);
String yyyyMM = LocalDate.now().format(DateTimeFormatter.ofPattern("yyyyMM"));
String loss7Key = String.format("oee:report:month:loss7:%s:SY", yyyyMM);
// 1. 优先从 Redis 读取当月 7 大损失预计算结果
String json = stringRedisTemplate.opsForValue().get(loss7Key);
List<AcidOeeLoss7Vo> lossList;
if (StringUtils.isNotBlank(json)) {
lossList = JSON.parseArray(json, AcidOeeLoss7Vo.class);
} else {
// 2. 缓存缺失时,回退为实时计算当前月
String[] range = resolveDateRange(null, null);
lossList = acidOeeService.getLoss7Summary(range[0], range[1]);
}
if (topN != null && topN > 0 && lossList.size() > topN) {
lossList = new ArrayList<>(lossList.subList(0, topN));
@@ -137,16 +147,14 @@ public class OeeReportController extends BaseController {
@RequestParam(required = false) String stopType,
@RequestParam(required = false) String keyword,
@RequestParam(required = false, defaultValue = "1") Integer pageNum,
@RequestParam(required = false, defaultValue = "10") Integer pageSize,
@RequestParam(required = false, defaultValue = "acid") String lineType
@RequestParam(required = false, defaultValue = "10") Integer pageSize
) {
// 事件明细底层按「日期」查询,这里从时间字符串中截取日期部分
String startDate = extractDateOrDefault(startTime, true);
String endDate = extractDateOrDefault(endTime, false);
List<Klptcm1ProStoppageVo> events = isGalvanize(lineType)
? galvanizeOeeService.getStoppageEvents(startDate, endDate)
: acidOeeService.getStoppageEvents(startDate, endDate);
List<Klptcm1ProStoppageVo> events =
acidOeeService.getStoppageEvents(startDate, endDate);
// 业务筛选stopType、关键字目前对 stopType / remark 做 contains 匹配)
List<Klptcm1ProStoppageVo> filtered = events.stream()
@@ -192,12 +200,9 @@ public class OeeReportController extends BaseController {
@GetMapping("/acid/idealCycle")
public R<AcidOeeIdealCycleVo> getAcidIdealCycle(
@RequestParam String startDate,
@RequestParam String endDate,
@RequestParam(required = false, defaultValue = "acid") String lineType
@RequestParam String endDate
) {
AcidOeeIdealCycleVo data = isGalvanize(lineType)
? galvanizeOeeService.getIdealCycle(startDate, endDate)
: acidOeeService.getIdealCycle(startDate, endDate);
AcidOeeIdealCycleVo data = acidOeeService.getIdealCycle(startDate, endDate);
return R.ok(data);
}
@@ -418,10 +423,6 @@ public class OeeReportController extends BaseController {
/**
* 若未显式传入日期范围,则默认当前月 [1号, 今天]。
*/
private boolean isGalvanize(String lineType) {
return "galvanize1".equalsIgnoreCase(lineType) || "galvanize".equalsIgnoreCase(lineType) || "dx".equalsIgnoreCase(lineType);
}
private String[] resolveDateRange(String startDate, String endDate) {
if (StringUtils.isNotBlank(startDate) && StringUtils.isNotBlank(endDate)) {
return new String[]{startDate, endDate};

View File

@@ -0,0 +1,137 @@
package com.klp.da.task;
import com.alibaba.fastjson2.JSON;
import com.klp.pocket.acid.domain.vo.AcidOeeDailySummaryVo;
import com.klp.pocket.acid.service.IAcidOeeService;
import lombok.Data;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;
import javax.annotation.PostConstruct;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.List;
import java.util.concurrent.TimeUnit;
/**
* 酸轧线 OEE 当月预计算任务
*
* 需求对应 docs/oee-report-design.md 第 12.2 节:
* - 项目启动完成后即计算当月 OEE 聚合结果并写入 Redis
* - 每天凌晨 04:00 重新计算当月数据并覆盖缓存。
*
* 当前仅实现酸轧线SY的当月日汇总预计算
* key 约定:
* - 汇总结果oee:report:month:summary:{yyyyMM}:SY
* - 元信息: oee:report:month:meta:{yyyyMM}:SY
*/
@Slf4j
@RequiredArgsConstructor
@Component
public class AcidOeeMonthTask {
/** Redis 缓存 key 模板:当月 OEE 汇总(酸轧线) */
private static final String SUMMARY_KEY_PATTERN = "oee:report:month:summary:%s:SY";
/** Redis 缓存 key 模板:当月元信息(酸轧线) */
private static final String META_KEY_PATTERN = "oee:report:month:meta:%s:SY";
private static final DateTimeFormatter YEAR_MONTH_FMT = DateTimeFormatter.ofPattern("yyyyMM");
private static final DateTimeFormatter DATE_FMT = DateTimeFormatter.ISO_DATE;
private static final DateTimeFormatter DATE_TIME_FMT = DateTimeFormatter.ISO_LOCAL_DATE_TIME;
private final IAcidOeeService acidOeeService;
private final StringRedisTemplate stringRedisTemplate;
/**
* 项目启动完成后立即计算一次当月酸轧 OEE 汇总并写入 Redis。
*/
@PostConstruct
public void init() {
try {
computeCurrentMonth("startup");
} catch (Exception e) {
log.error("[AcidOeeMonthTask] startup compute failed", e);
}
}
/**
* 每天凌晨 04:00 重新计算当月酸轧 OEE 汇总并覆盖 Redis 缓存。
*/
@Scheduled(cron = "0 0 4 * * ?")
public void scheduleDaily() {
try {
computeCurrentMonth("schedule-04");
} catch (Exception e) {
log.error("[AcidOeeMonthTask] 4am compute failed", e);
}
}
/**
* 计算当前月份从当月1号到今天的酸轧 OEE 日汇总,并写入 Redis。
*
* @param trigger 触发来源标记startup / schedule-04 等)
*/
private void computeCurrentMonth(String trigger) {
long startNs = System.nanoTime();
LocalDate now = LocalDate.now();
String yyyyMM = now.format(YEAR_MONTH_FMT);
LocalDate startDate = now.withDayOfMonth(1);
LocalDate endDate = now;
String startStr = startDate.format(DATE_FMT);
String endStr = endDate.format(DATE_FMT);
log.info("[AcidOeeMonthTask] trigger={}, computing acid OEE month summary for {} ({} ~ {})",
trigger, yyyyMM, startStr, endStr);
// 1. 调用 pocket 的 AcidOeeService 获取当月日汇总
List<AcidOeeDailySummaryVo> dailySummaryList = acidOeeService.getDailySummary(startStr, endStr);
// 2. 写入 Redissummary
String summaryKey = String.format(SUMMARY_KEY_PATTERN, yyyyMM);
String summaryJson = JSON.toJSONString(dailySummaryList);
stringRedisTemplate.opsForValue().set(summaryKey, summaryJson, 1, TimeUnit.DAYS);
long durationMs = (System.nanoTime() - startNs) / 1_000_000L;
// 3. 写入 Redismeta
Meta meta = new Meta();
meta.setComputedAt(LocalDateTime.now().format(DATE_TIME_FMT));
meta.setDurationMs(durationMs);
meta.setStartDate(startStr);
meta.setEndDate(endStr);
meta.setTrigger(trigger);
String metaKey = String.format(META_KEY_PATTERN, yyyyMM);
stringRedisTemplate.opsForValue().set(metaKey, JSON.toJSONString(meta), 1, TimeUnit.DAYS);
log.info("[AcidOeeMonthTask] compute finish for {} dailySize={}, durationMs={}ms, summaryKey={}",
yyyyMM, dailySummaryList.size(), durationMs, summaryKey);
}
/**
* 当月预计算元信息
*/
@Data
private static class Meta {
/** 计算完成时间ISO-8601 字符串) */
private String computedAt;
/** 计算耗时(毫秒) */
private long durationMs;
/** 统计起始日期yyyy-MM-dd */
private String startDate;
/** 统计结束日期yyyy-MM-dd */
private String endDate;
/** 触发来源startup / schedule-04 等) */
private String trigger;
}
}

View File

@@ -10,7 +10,7 @@ import lombok.RequiredArgsConstructor;
import javax.servlet.http.HttpServletResponse;
import javax.validation.constraints.*;
import org.springframework.util.StringUtils;
import org.flywaydb.core.internal.util.StringUtils;
import org.springframework.web.bind.annotation.*;
import org.springframework.validation.annotation.Validated;
import com.klp.common.annotation.RepeatSubmit;

View File

@@ -44,10 +44,6 @@ public class EqpAuxiliaryMaterial extends BaseEntity {
* 关联设备ID可为空通用辅料
*/
private Long equipmentId;
/**
* 机组1#机组、2#机组、公用机组等)
*/
private String unitTeam;
/**
* 当前库存数量
*/

View File

@@ -44,10 +44,6 @@ public class EqpSparePart extends BaseEntity {
* 关联设备ID可为空通用备件
*/
private Long equipmentId;
/**
* 机组1#机组、2#机组、公用机组等)
*/
private String unitTeam;
/**
* 当前库存数量
*/

View File

@@ -47,11 +47,6 @@ public class EqpAuxiliaryMaterialBo extends BaseEntity {
*/
private Long equipmentId;
/**
* 机组1#机组、2#机组、公用机组等)
*/
private String unitTeam;
/**
* 当前库存数量
*/

View File

@@ -47,11 +47,6 @@ public class EqpSparePartBo extends BaseEntity {
*/
private Long equipmentId;
/**
* 机组1#机组、2#机组、公用机组等)
*/
private String unitTeam;
/**
* 当前库存数量
*/

View File

@@ -56,12 +56,6 @@ public class EqpAuxiliaryMaterialVo {
@ExcelDictFormat(readConverterExp = "可=为空,通用辅料")
private Long equipmentId;
/**
* 机组1#机组、2#机组、公用机组等)
*/
@ExcelProperty(value = "机组")
private String unitTeam;
/**
* 当前库存数量
*/

View File

@@ -56,12 +56,6 @@ public class EqpSparePartVo {
@ExcelDictFormat(readConverterExp = "可=为空,通用备件")
private Long equipmentId;
/**
* 机组1#机组、2#机组、公用机组等)
*/
@ExcelProperty(value = "机组")
private String unitTeam;
/**
* 当前库存数量
*/

View File

@@ -66,7 +66,6 @@ public class EqpAuxiliaryMaterialServiceImpl implements IEqpAuxiliaryMaterialSer
lqw.eq(StringUtils.isNotBlank(bo.getAuxiliaryModel()), EqpAuxiliaryMaterial::getAuxiliaryModel, bo.getAuxiliaryModel());
lqw.eq(StringUtils.isNotBlank(bo.getUnit()), EqpAuxiliaryMaterial::getUnit, bo.getUnit());
lqw.eq(bo.getEquipmentId() != null, EqpAuxiliaryMaterial::getEquipmentId, bo.getEquipmentId());
lqw.like(StringUtils.isNotBlank(bo.getUnitTeam()), EqpAuxiliaryMaterial::getUnitTeam, bo.getUnitTeam());
lqw.eq(bo.getQuantity() != null, EqpAuxiliaryMaterial::getQuantity, bo.getQuantity());
return lqw;
}

View File

@@ -58,7 +58,6 @@ public class EqpSparePartServiceImpl implements IEqpSparePartService {
qw.eq(StringUtils.isNotBlank(bo.getModel()), "sp.model", bo.getModel());
qw.eq(StringUtils.isNotBlank(bo.getUnit()), "sp.unit", bo.getUnit());
qw.eq(bo.getEquipmentId() != null, "sp.equipment_id", bo.getEquipmentId());
qw.like(StringUtils.isNotBlank(bo.getUnitTeam()), "sp.unit_team", bo.getUnitTeam());
qw.eq(bo.getQuantity() != null, "sp.quantity", bo.getQuantity());
//逻辑删除
qw.eq("sp.del_flag", 0);
@@ -82,7 +81,6 @@ public class EqpSparePartServiceImpl implements IEqpSparePartService {
lqw.eq(StringUtils.isNotBlank(bo.getModel()), EqpSparePart::getModel, bo.getModel());
lqw.eq(StringUtils.isNotBlank(bo.getUnit()), EqpSparePart::getUnit, bo.getUnit());
lqw.eq(bo.getEquipmentId() != null, EqpSparePart::getEquipmentId, bo.getEquipmentId());
lqw.like(StringUtils.isNotBlank(bo.getUnitTeam()), EqpSparePart::getUnitTeam, bo.getUnitTeam());
lqw.eq(bo.getQuantity() != null, EqpSparePart::getQuantity, bo.getQuantity());
return lqw;
}

View File

@@ -11,7 +11,6 @@
<result property="auxiliaryModel" column="auxiliary_model"/>
<result property="unit" column="unit"/>
<result property="equipmentId" column="equipment_id"/>
<result property="unitTeam" column="unit_team"/>
<result property="quantity" column="quantity"/>
<result property="createBy" column="create_by"/>
<result property="updateBy" column="update_by"/>

View File

@@ -11,7 +11,6 @@
<result property="model" column="model"/>
<result property="unit" column="unit"/>
<result property="equipmentId" column="equipment_id"/>
<result property="unitTeam" column="unit_team"/>
<result property="quantity" column="quantity"/>
<result property="createBy" column="create_by"/>
<result property="updateBy" column="update_by"/>
@@ -28,7 +27,6 @@
sp.model,
sp.unit,
sp.equipment_id,
sp.unit_team,
em.equipment_name AS equipmentName,
sp.quantity,
sp.remark

View File

@@ -1,24 +0,0 @@
package com.klp.pocket.acid.domain.vo;
import lombok.Data;
import java.math.BigDecimal;
/**
* 酸轧OEE按日钢卷信息主库来源
*/
@Data
public class AcidOeeCoilInfoByDateVo {
/** 统计日期 yyyy-MM-dd */
private String statDate;
/** 当前钢卷号 */
private String coilNo;
/** 重量(吨) */
private BigDecimal weight;
/** 判级 */
private String qualityStatus;
}

View File

@@ -3,6 +3,7 @@ package com.klp.pocket.acid.domain.vo;
import lombok.Data;
import java.math.BigDecimal;
import java.util.Date;
/**
* 酸轧线OEE日汇总视图对象
@@ -44,36 +45,18 @@ public class AcidOeeDailySummaryVo {
/** 总产量(卷) */
private Long totalOutputCoil;
/** 良品量(A系列吨) */
/** 良品量(吨) */
private BigDecimal goodOutputTon;
/** 良品量(A系列卷) */
/** 良品量(卷) */
private Long goodOutputCoil;
/** 合格品量B系列 */
private BigDecimal qualifiedOutputTon;
/** 合格品量B系列 */
private Long qualifiedOutputCoil;
/** 合格品合计A+B */
private BigDecimal abOutputTon;
/** 合格品合计A+B */
private Long abOutputCoil;
/** 次品量C/D系列 */
/** 不良量(吨)= total_output_ton - good_output_ton */
private BigDecimal defectOutputTon;
/** 次品量C/D系列 */
/** 不良量(卷)= total_output_coil - good_output_coil */
private Long defectOutputCoil;
/** 待判级量O系列 */
private BigDecimal pendingOutputTon;
/** 待判级量O系列 */
private Long pendingOutputCoil;
/** 理论节拍min/吨;回归斜率) */
private BigDecimal idealCycleTimeMinPerTon;
@@ -89,18 +72,9 @@ public class AcidOeeDailySummaryVo {
/** 派生指标:性能稼动率(卷维度) */
private BigDecimal performanceCoil;
/** 派生指标:良品率A/总量) */
/** 派生指标:良品率 */
private BigDecimal quality;
/** 派生指标合格品率A+B/总量) */
private BigDecimal qualifiedRate;
/** 派生指标次品率C/D系列/总量) */
private BigDecimal defectRate;
/** 派生指标待判级率O系列/总量) */
private BigDecimal pendingRate;
/** 派生指标OEE建议以吨维度为主 */
private BigDecimal oee;
}

View File

@@ -1,22 +0,0 @@
package com.klp.pocket.acid.mapper;
import com.baomidou.dynamic.datasource.annotation.DS;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
import java.math.BigDecimal;
import java.util.List;
/**
* 酸轧线OEE酸轧库Mapper。
*/
@Mapper
@DS("acid")
public interface AcidOeeAcidMapper {
/**
* 查询卷级生产节拍min/吨),用于理论节拍计算。
*/
List<BigDecimal> selectCoilCycleMinPerTon(@Param("startDate") String startDate,
@Param("endDate") String endDate);
}

View File

@@ -0,0 +1,65 @@
package com.klp.pocket.acid.mapper;
import com.klp.pocket.acid.domain.vo.AcidOeeDailySummaryVo;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
import java.util.List;
/**
* 酸轧线OEE Mapper接口
*
* @author klp
* @date 2026-01-30
*/
@Mapper
public interface AcidOeeMapper {
/**
* 查询OEE日汇总按日期范围
* 聚合产量(吨/卷)、停机时间等
*
* @param startDate 开始日期yyyy-MM-dd
* @param endDate 结束日期yyyy-MM-dd
* @return 日汇总列表
*/
List<AcidOeeDailySummaryVo> selectDailySummary(@Param("startDate") String startDate,
@Param("endDate") String endDate);
/**
* 查询每日的钢卷号和重量(用于良品/次品判定)
*
* @param startDate 开始日期yyyy-MM-dd
* @param endDate 结束日期yyyy-MM-dd
* @return Map列表key为日期value为卷号和重量信息
*/
List<CoilInfoByDate> selectCoilInfoByDate(@Param("startDate") String startDate,
@Param("endDate") String endDate);
/**
* 查询卷级生产节拍min/吨),用于理论节拍计算。
*
* @param startDate 开始日期yyyy-MM-dd
* @param endDate 结束日期yyyy-MM-dd
* @return 每卷的生产节拍列表min/吨)
*/
List<java.math.BigDecimal> selectCoilCycleMinPerTon(@Param("startDate") String startDate,
@Param("endDate") String endDate);
/**
* 卷号信息内部类用于Mapper返回
*/
class CoilInfoByDate {
private String statDate;
private String coilNo;
private java.math.BigDecimal weight;
public String getStatDate() { return statDate; }
public void setStatDate(String statDate) { this.statDate = statDate; }
public String getCoilNo() { return coilNo; }
public void setCoilNo(String coilNo) { this.coilNo = coilNo; }
public java.math.BigDecimal getWeight() { return weight; }
public void setWeight(java.math.BigDecimal weight) { this.weight = weight; }
}
}

View File

@@ -1,32 +0,0 @@
package com.klp.pocket.acid.mapper;
import com.baomidou.dynamic.datasource.annotation.DS;
import com.klp.pocket.acid.domain.vo.AcidOeeCoilInfoByDateVo;
import com.klp.pocket.acid.domain.vo.AcidOeeDailySummaryVo;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
import java.util.List;
/**
* 酸轧线OEE主库Mapper。
*/
@Mapper
@DS("master")
public interface AcidOeeMasterMapper {
/**
* 查询OEE日汇总总产量来自主库 wms_material_coil
*/
List<AcidOeeDailySummaryVo> selectDailySummary(@Param("startDate") String startDate,
@Param("endDate") String endDate,
@Param("createBy") String createBy);
/**
* 查询每日钢卷重量与判级(来自主库 wms_material_coil
*/
List<AcidOeeCoilInfoByDateVo> selectCoilInfoByDate(@Param("startDate") String startDate,
@Param("endDate") String endDate,
@Param("createBy") String createBy);
}

View File

@@ -1,34 +1,25 @@
package com.klp.pocket.acid.service.impl;
import com.baomidou.dynamic.datasource.annotation.DS;
import com.klp.common.utils.StringUtils;
import com.klp.pocket.acid.domain.vo.AcidOeeCoilInfoByDateVo;
import com.klp.pocket.acid.domain.vo.AcidOeeDailySummaryVo;
import com.klp.pocket.acid.domain.vo.AcidOeeIdealCycleVo;
import com.klp.pocket.acid.domain.vo.AcidOeeLoss7Vo;
import com.klp.pocket.acid.domain.vo.Klptcm1ProStoppageVo;
import com.klp.pocket.acid.domain.bo.Klptcm1ProStoppageBo;
import com.klp.pocket.acid.mapper.AcidOeeMasterMapper;
import com.klp.pocket.acid.mapper.AcidOeeMapper;
import com.klp.pocket.acid.service.IAcidOeeService;
import com.klp.pocket.acid.service.IKlptcm1ProStoppageService;
import com.klp.pocket.common.service.ICoilQualityJudgeService;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.*;
import java.util.Calendar;
import java.util.Collections;
import java.util.Comparator;
import java.util.Date;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
/**
* 酸轧线OEE Service实现类
@@ -38,65 +29,53 @@ import java.util.Set;
*/
@Slf4j
@RequiredArgsConstructor
@DS("acid")
@Service
public class AcidOeeServiceImpl implements IAcidOeeService {
/** 次品判级集合C/D系列 */
private static final Set<String> CD_SERIES = new HashSet<>(
Arrays.asList("C+", "C", "C-", "D+", "D", "D-")
);
private final AcidOeeMasterMapper acidOeeMasterMapper;
/** 酸轧成品库库区ID */
private static final Long ACID_FINISHED_WAREHOUSE_ID = 1988150099140866050L;
/** 固定理论节拍min/吨) */
private static final BigDecimal FIXED_IDEAL_CYCLE = BigDecimal.valueOf(0.47);
private final AcidOeeMapper acidOeeMapper;
private final IKlptcm1ProStoppageService stoppageService;
@Value("${oee.acid.coil-create-by}")
private String acidCreateName;
private final ICoilQualityJudgeService coilQualityJudgeService;
@Override
public List<AcidOeeDailySummaryVo> getDailySummary(String startDate, String endDate) {
// 1. 查询基础日汇总(产量、停机时间等)
List<AcidOeeDailySummaryVo> summaries = acidOeeMasterMapper.selectDailySummary(
startDate,
endDate,
acidCreateName
);
List<AcidOeeDailySummaryVo> summaries = acidOeeMapper.selectDailySummary(startDate, endDate);
if (summaries == null || summaries.isEmpty()) {
return Collections.emptyList();
}
// 2. 查询停机事件,按日期聚合停机时间
Map<String, Long> downtimeByDate = aggregateDowntimeByDate(startDate, endDate);
// 3. 查询产量明细,用于良品/次品判定
Map<String, List<CoilInfo>> coilInfoByDate = getCoilNosByDate(startDate, endDate);
// 4. 先按天计算 dailyCycle = runTime/totalOutputTon再取中位数作为理论节拍
List<BigDecimal> dailyCycles = new ArrayList<>();
// 4. 理论节拍使用固定值0.47
BigDecimal idealCycleTon = FIXED_IDEAL_CYCLE;
// 5. 填充每个日汇总的完整数据
for (AcidOeeDailySummaryVo summary : summaries) {
String statDate = summary.getStatDate();
summary.setLineId("SY");
summary.setLineName("酸轧线");
// 填充停机时间
Long downtime = downtimeByDate.getOrDefault(statDate, 0L);
summary.setDowntimeMin(downtime);
// 计算运转时间
Long loadingTime = summary.getLoadingTimeMin() != null ? summary.getLoadingTimeMin() : 0L;
Long runTime = Math.max(0, loadingTime - downtime);
summary.setRunTimeMin(runTime);
BigDecimal totalOutputTon = summary.getTotalOutputTon();
if (runTime > 0 && totalOutputTon != null && totalOutputTon.compareTo(BigDecimal.ZERO) > 0) {
BigDecimal dailyCycle = BigDecimal.valueOf(runTime)
.divide(totalOutputTon, 6, RoundingMode.HALF_UP);
dailyCycles.add(dailyCycle);
}
}
dailyCycles.sort(BigDecimal::compareTo);
BigDecimal idealCycleTon = applyEightyPercent(median(dailyCycles));
// 5. 回填理论节拍、良品次品并计算派生指标
for (AcidOeeDailySummaryVo summary : summaries) {
String statDate = summary.getStatDate();
if (idealCycleTon != null) {
// 理论节拍:若尚未填充,则统一使用“优良日统计”得到的节拍
if (summary.getIdealCycleTimeMinPerTon() == null && idealCycleTon != null) {
summary.setIdealCycleTimeMinPerTon(idealCycleTon);
}
@@ -105,19 +84,18 @@ public class AcidOeeServiceImpl implements IAcidOeeService {
List<CoilInfo> coilInfos = coilInfoByDate.get(statDate);
calculateQualityOutput(summary, coilInfos);
} else {
// 没有卷明细时,全部归待判级
summary.setGoodOutputTon(BigDecimal.ZERO);
summary.setGoodOutputCoil(0L);
summary.setQualifiedOutputTon(BigDecimal.ZERO);
summary.setQualifiedOutputCoil(0L);
summary.setAbOutputTon(BigDecimal.ZERO);
summary.setAbOutputCoil(0L);
// 如果没有卷号,默认全部为良品(或根据业务规则处理)
summary.setGoodOutputTon(summary.getTotalOutputTon());
summary.setGoodOutputCoil(summary.getTotalOutputCoil());
summary.setDefectOutputTon(BigDecimal.ZERO);
summary.setDefectOutputCoil(0L);
summary.setPendingOutputTon(summary.getTotalOutputTon());
summary.setPendingOutputCoil(summary.getTotalOutputCoil());
}
// 填充理论节拍(从回归数据或缓存获取,这里暂时留空,由调用方填充)
// summary.setIdealCycleTimeMinPerTon(...);
// summary.setIdealCycleTimeMinPerCoil(...);
// 计算派生指标
calculateDerivedMetrics(summary);
}
@@ -149,11 +127,7 @@ public class AcidOeeServiceImpl implements IAcidOeeService {
@Override
public AcidOeeIdealCycleVo getIdealCycle(String startDate, String endDate) {
// 1) 取基础日汇总(产量、负荷时间等)
List<AcidOeeDailySummaryVo> daily = acidOeeMasterMapper.selectDailySummary(
startDate,
endDate,
acidCreateName
);
List<AcidOeeDailySummaryVo> daily = acidOeeMapper.selectDailySummary(startDate, endDate);
AcidOeeIdealCycleVo rsp = new AcidOeeIdealCycleVo();
rsp.setLineId("SY");
rsp.setLineName("酸轧线");
@@ -177,31 +151,23 @@ public class AcidOeeServiceImpl implements IAcidOeeService {
d.setRunTimeMin(Math.max(0, loading - downtime));
}
// 3) 理论节拍按“天维度”:每天(运转时间/总吨),再按日样本统计中位数用于展示
List<BigDecimal> dailyCycles = new ArrayList<>();
for (AcidOeeDailySummaryVo d : daily) {
Long run = d.getRunTimeMin();
BigDecimal ton = d.getTotalOutputTon();
if (run == null || run <= 0 || ton == null || ton.compareTo(BigDecimal.ZERO) <= 0) {
continue;
}
dailyCycles.add(BigDecimal.valueOf(run).divide(ton, 6, RoundingMode.HALF_UP));
}
dailyCycles.sort(BigDecimal::compareTo);
BigDecimal medianCycle = median(dailyCycles);
// 理论节拍:按“当天运转时间/当天总吨”逐日计算接口返回前乘以80%
BigDecimal idealCycle = applyEightyPercent(medianCycle);
rsp.setIdealCycleTimeMinPerTon(idealCycle);
// 展示字段保持为中位数
// 3) 卷级节拍 = (END_DATE - START_DATE)/出口重量,计算中位数用于展示不用于OEE计算
List<BigDecimal> coilCycles = acidOeeMapper.selectCoilCycleMinPerTon(startDate, endDate);
coilCycles.removeIf(c -> c == null || c.compareTo(BigDecimal.ZERO) <= 0);
coilCycles.sort(BigDecimal::compareTo);
BigDecimal medianCycle = median(coilCycles);
// 理论节拍使用固定值0.47用于OEE计算
rsp.setIdealCycleTimeMinPerTon(FIXED_IDEAL_CYCLE);
// 中位数理论节拍(用于展示)
rsp.setMedianCycleTimeMinPerTon(medianCycle);
// 样本天数:当前查询区间内有产量的自然日数量(与传入的日期范围一一对应)
rsp.setSampleDays(daily.size());
// 4) 日粒度对比数据:理论耗时 vs 实际运转时间(用于前端展示"有效性"
// 使用“中间50%样本平均理论节拍”计算理论耗时
// 使用固定值0.47计算理论耗时
List<AcidOeeIdealCycleVo.DailyComparePointVo> compare = new ArrayList<>();
if (idealCycle != null) {
if (FIXED_IDEAL_CYCLE != null) {
for (AcidOeeDailySummaryVo d : daily) {
BigDecimal ton = d.getTotalOutputTon();
Long run = d.getRunTimeMin();
@@ -209,7 +175,7 @@ public class AcidOeeServiceImpl implements IAcidOeeService {
AcidOeeIdealCycleVo.DailyComparePointVo p = new AcidOeeIdealCycleVo.DailyComparePointVo();
p.setStatDate(d.getStatDate());
p.setActualRunTimeMin(run);
p.setTheoreticalTimeMin(idealCycle.multiply(ton));
p.setTheoreticalTimeMin(FIXED_IDEAL_CYCLE.multiply(ton));
compare.add(p);
}
}
@@ -304,25 +270,25 @@ public class AcidOeeServiceImpl implements IAcidOeeService {
SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd");
Calendar cal = Calendar.getInstance();
for (Klptcm1ProStoppageVo event : events) {
if (event.getStartDate() == null || event.getDuration() == null || event.getDuration() <= 0) {
continue;
}
Date eventStart = event.getStartDate();
long durationSec = event.getDuration();
long durationMin = (durationSec + 59) / 60; // 向上取整,避免丢失秒数
// 计算停机结束时间
cal.setTime(eventStart);
cal.add(Calendar.SECOND, (int) durationSec);
Date eventEnd = cal.getTime();
// 如果停机事件在同一天,直接累加
String startDateStr = dateFormat.format(eventStart);
String endDateStr = dateFormat.format(eventEnd);
if (startDateStr.equals(endDateStr)) {
// 同一天,直接累加
downtimeMap.merge(startDateStr, durationMin, Long::sum);
@@ -334,23 +300,23 @@ public class AcidOeeServiceImpl implements IAcidOeeService {
cal.set(Calendar.SECOND, 0);
cal.set(Calendar.MILLISECOND, 0);
Date dayStart = cal.getTime();
Date currentDayStart = dayStart;
while (currentDayStart.before(eventEnd)) {
cal.setTime(currentDayStart);
cal.add(Calendar.DAY_OF_MONTH, 1);
Date nextDayStart = cal.getTime();
// 计算当前天的停机分钟数
Date dayEnd = nextDayStart.before(eventEnd) ? nextDayStart : eventEnd;
long dayMinutes = Math.max(0, (dayEnd.getTime() - Math.max(currentDayStart.getTime(), eventStart.getTime())) / (1000 * 60));
if (dayMinutes > 0) {
String dateKey = dateFormat.format(currentDayStart);
downtimeMap.merge(dateKey, dayMinutes, Long::sum);
}
currentDayStart = nextDayStart;
}
}
@@ -363,18 +329,13 @@ public class AcidOeeServiceImpl implements IAcidOeeService {
* 获取每日的钢卷号和重量(用于良品/次品判定)
*/
private Map<String, List<CoilInfo>> getCoilNosByDate(String startDate, String endDate) {
List<AcidOeeCoilInfoByDateVo> coilInfoList = acidOeeMasterMapper.selectCoilInfoByDate(
startDate,
endDate,
acidCreateName
);
List<AcidOeeMapper.CoilInfoByDate> coilInfoList = acidOeeMapper.selectCoilInfoByDate(startDate, endDate);
Map<String, List<CoilInfo>> result = new HashMap<>();
for (AcidOeeCoilInfoByDateVo info : coilInfoList) {
for (AcidOeeMapper.CoilInfoByDate info : coilInfoList) {
String date = info.getStatDate();
result.computeIfAbsent(date, k -> new ArrayList<>())
.add(new CoilInfo(info.getCoilNo(), info.getWeight(), info.getQualityStatus()));
.add(new CoilInfo(info.getCoilNo(), info.getWeight()));
}
return result;
@@ -384,64 +345,50 @@ public class AcidOeeServiceImpl implements IAcidOeeService {
* 卷号信息内部类
*/
private static class CoilInfo {
final String coilNo;
final BigDecimal weight;
final String qualityStatus;
CoilInfo(String coilNo, BigDecimal weight, String qualityStatus) {
CoilInfo(String coilNo, BigDecimal weight) {
this.coilNo = coilNo;
this.weight = weight;
this.qualityStatus = qualityStatus;
}
}
/**
* 计算质量细分产量A良品、B合格品、C/D次品、O待判级。
* 计算良品/次品产量
*/
private void calculateQualityOutput(AcidOeeDailySummaryVo summary, List<CoilInfo> coilInfos) {
BigDecimal aTon = BigDecimal.ZERO;
long aCoil = 0L;
BigDecimal bTon = BigDecimal.ZERO;
long bCoil = 0L;
BigDecimal cdTon = BigDecimal.ZERO;
long cdCoil = 0L;
BigDecimal oTon = BigDecimal.ZERO;
long oCoil = 0L;
BigDecimal goodTon = BigDecimal.ZERO;
long goodCoil = 0L;
BigDecimal defectTon = BigDecimal.ZERO;
long defectCoil = 0L;
for (CoilInfo coilInfo : coilInfos) {
String coilNo = coilInfo.coilNo;
BigDecimal coilWeight = coilInfo.weight != null ? coilInfo.weight : BigDecimal.ZERO;
String qualityStatus = StringUtils.upperCase(StringUtils.trim(coilInfo.qualityStatus));
if (StringUtils.isBlank(qualityStatus) || "O".equals(qualityStatus)) {
oTon = oTon.add(coilWeight);
oCoil++;
// 通过WMS判定良品/次品
Boolean isScrap = coilQualityJudgeService.isScrap(ACID_FINISHED_WAREHOUSE_ID, coilNo);
if (isScrap == null) {
// 匹配不到,忽略不计
continue;
}
if (qualityStatus.startsWith("A")) {
aTon = aTon.add(coilWeight);
aCoil++;
} else if (qualityStatus.startsWith("B")) {
bTon = bTon.add(coilWeight);
bCoil++;
} else if (CD_SERIES.contains(qualityStatus)) {
cdTon = cdTon.add(coilWeight);
cdCoil++;
if (Boolean.TRUE.equals(isScrap)) {
// 次品
defectTon = defectTon.add(coilWeight);
defectCoil++;
} else {
// 未识别等级统一归待判级
oTon = oTon.add(coilWeight);
oCoil++;
// 良品
goodTon = goodTon.add(coilWeight);
goodCoil++;
}
}
summary.setGoodOutputTon(aTon);
summary.setGoodOutputCoil(aCoil);
summary.setQualifiedOutputTon(bTon);
summary.setQualifiedOutputCoil(bCoil);
summary.setAbOutputTon(aTon.add(bTon));
summary.setAbOutputCoil(aCoil + bCoil);
summary.setDefectOutputTon(cdTon);
summary.setDefectOutputCoil(cdCoil);
summary.setPendingOutputTon(oTon);
summary.setPendingOutputCoil(oCoil);
summary.setGoodOutputTon(goodTon);
summary.setGoodOutputCoil(goodCoil);
summary.setDefectOutputTon(defectTon);
summary.setDefectOutputCoil(defectCoil);
}
/**
@@ -459,7 +406,6 @@ public class AcidOeeServiceImpl implements IAcidOeeService {
}
// 性能稼动率(吨维度)
// 口径:理论节拍单位为 min/吨 时,性能稼动率 = (理论节拍 × 实际产量) / 实际运转时间 × 100
Long runTime = summary.getRunTimeMin() != null ? summary.getRunTimeMin() : 0L;
BigDecimal idealCycleTon = summary.getIdealCycleTimeMinPerTon();
BigDecimal totalOutputTon = summary.getTotalOutputTon();
@@ -469,14 +415,12 @@ public class AcidOeeServiceImpl implements IAcidOeeService {
&& totalOutputTon != null
&& totalOutputTon.compareTo(BigDecimal.ZERO) > 0) {
BigDecimal idealTime = idealCycleTon.multiply(totalOutputTon);
BigDecimal performanceTon = idealTime
.divide(BigDecimal.valueOf(runTime), 4, RoundingMode.HALF_UP)
BigDecimal performanceTon = idealTime.divide(BigDecimal.valueOf(runTime), 4, RoundingMode.HALF_UP)
.multiply(BigDecimal.valueOf(100));
summary.setPerformanceTon(performanceTon);
}
// 性能稼动率(卷维度)
// 口径:理论节拍单位为 min/卷 时,性能稼动率 = (理论节拍 × 实际产量) / 实际运转时间 × 100
Long totalOutputCoil = summary.getTotalOutputCoil() != null ? summary.getTotalOutputCoil() : 0L;
BigDecimal idealCycleCoil = summary.getIdealCycleTimeMinPerCoil();
if (runTime > 0
@@ -484,35 +428,17 @@ public class AcidOeeServiceImpl implements IAcidOeeService {
&& idealCycleCoil.compareTo(BigDecimal.ZERO) > 0
&& totalOutputCoil > 0) {
BigDecimal idealTime = idealCycleCoil.multiply(BigDecimal.valueOf(totalOutputCoil));
BigDecimal performanceCoil = idealTime
.divide(BigDecimal.valueOf(runTime), 4, RoundingMode.HALF_UP)
BigDecimal performanceCoil = idealTime.divide(BigDecimal.valueOf(runTime), 4, RoundingMode.HALF_UP)
.multiply(BigDecimal.valueOf(100));
summary.setPerformanceCoil(performanceCoil);
}
// 质量细分比
// 良品
if (totalOutputTon != null && totalOutputTon.compareTo(BigDecimal.ZERO) > 0) {
BigDecimal aTon = summary.getGoodOutputTon() != null ? summary.getGoodOutputTon() : BigDecimal.ZERO;
BigDecimal bTon = summary.getQualifiedOutputTon() != null ? summary.getQualifiedOutputTon() : BigDecimal.ZERO;
BigDecimal cdTon = summary.getDefectOutputTon() != null ? summary.getDefectOutputTon() : BigDecimal.ZERO;
BigDecimal oTon = summary.getPendingOutputTon() != null ? summary.getPendingOutputTon() : BigDecimal.ZERO;
BigDecimal quality = aTon.divide(totalOutputTon, 4, RoundingMode.HALF_UP)
BigDecimal goodOutputTon = summary.getGoodOutputTon() != null ? summary.getGoodOutputTon() : BigDecimal.ZERO;
BigDecimal quality = goodOutputTon.divide(totalOutputTon, 4, RoundingMode.HALF_UP)
.multiply(BigDecimal.valueOf(100));
summary.setQuality(quality);
BigDecimal qualifiedRate = aTon.add(bTon)
.divide(totalOutputTon, 4, RoundingMode.HALF_UP)
.multiply(BigDecimal.valueOf(100));
summary.setQualifiedRate(qualifiedRate);
BigDecimal defectRate = cdTon.divide(totalOutputTon, 4, RoundingMode.HALF_UP)
.multiply(BigDecimal.valueOf(100));
summary.setDefectRate(defectRate);
BigDecimal pendingRate = oTon.divide(totalOutputTon, 4, RoundingMode.HALF_UP)
.multiply(BigDecimal.valueOf(100));
summary.setPendingRate(pendingRate);
}
// OEE以吨维度为主
@@ -537,15 +463,6 @@ public class AcidOeeServiceImpl implements IAcidOeeService {
return a.add(b).divide(BigDecimal.valueOf(2), 6, RoundingMode.HALF_UP);
}
/** 理论节拍返回前按业务口径乘以70% */
private BigDecimal applyEightyPercent(BigDecimal cycle) {
if (cycle == null) {
return null;
}
return cycle.multiply(BigDecimal.valueOf(0.7)).setScale(6, RoundingMode.HALF_UP);
}
/**
* 解析日期字符串为Date对象
*/

View File

@@ -18,7 +18,6 @@ public class CrmPdiPlan {
private Integer seqid;
private String coilid;
private String enterCoilNo;
private Integer dummyCoilFlag;
private String status;
private String planid;

View File

@@ -19,7 +19,6 @@ public class CrmPdoExcoil {
private String exitMatId;
private String entryMatId;
private String enterCoilNo;
private Integer subId;
private Double startPosition;
private Double endPosition;

View File

@@ -15,7 +15,6 @@ public class CrmPdiPlanBo extends BaseEntity {
private Integer seqid;
private String coilid;
private String enterCoilNo;
private Integer dummyCoilFlag;
private String status;
private String planid;

View File

@@ -15,7 +15,6 @@ public class CrmPdoExcoilBo extends BaseEntity {
private String exitMatId;
private String entryMatId;
private String enterCoilNo;
private Integer subId;
private Double startPosition;
private Double endPosition;

View File

@@ -12,7 +12,6 @@ public class CrmPdiPlanVo {
private Integer seqid;
private String coilid;
private String enterCoilNo;
private Integer dummyCoilFlag;
private String status;
private String planid;

View File

@@ -12,7 +12,6 @@ public class CrmPdoExcoilVo {
private String exitMatId;
private String entryMatId;
private String enterCoilNo;
private Integer subId;
private Double startPosition;
private Double endPosition;

View File

@@ -1,22 +0,0 @@
package com.klp.pocket.galvanize1.mapper;
import com.baomidou.dynamic.datasource.annotation.DS;
import com.klp.pocket.acid.domain.vo.AcidOeeCoilInfoByDateVo;
import com.klp.pocket.acid.domain.vo.AcidOeeDailySummaryVo;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
import java.util.List;
@Mapper
@DS("master")
public interface GalvanizeOeeMasterMapper {
List<AcidOeeDailySummaryVo> selectDailySummary(@Param("startDate") String startDate,
@Param("endDate") String endDate,
@Param("createBy") String createBy);
List<AcidOeeCoilInfoByDateVo> selectCoilInfoByDate(@Param("startDate") String startDate,
@Param("endDate") String endDate,
@Param("createBy") String createBy);
}

View File

@@ -1,19 +0,0 @@
package com.klp.pocket.galvanize1.service;
import com.klp.pocket.acid.domain.vo.AcidOeeDailySummaryVo;
import com.klp.pocket.acid.domain.vo.AcidOeeIdealCycleVo;
import com.klp.pocket.acid.domain.vo.AcidOeeLoss7Vo;
import com.klp.pocket.acid.domain.vo.Klptcm1ProStoppageVo;
import java.util.List;
public interface IGalvanizeOeeService {
List<AcidOeeDailySummaryVo> getDailySummary(String startDate, String endDate);
List<Klptcm1ProStoppageVo> getStoppageEvents(String startDate, String endDate);
List<AcidOeeLoss7Vo> getLoss7Summary(String startDate, String endDate);
AcidOeeIdealCycleVo getIdealCycle(String startDate, String endDate);
}

View File

@@ -18,6 +18,7 @@ import org.springframework.stereotype.Service;
import java.util.Collection;
import java.util.List;
import java.util.Map;
@RequiredArgsConstructor
@DS("galvanize1")
@@ -45,11 +46,11 @@ public class CrmPdiPlanServiceImpl implements ICrmPdiPlanService {
}
private LambdaQueryWrapper<CrmPdiPlan> buildQueryWrapper(CrmPdiPlanBo bo) {
Map<String, Object> params = bo.getParams();
LambdaQueryWrapper<CrmPdiPlan> lqw = Wrappers.lambdaQuery();
lqw.eq(bo.getId() != null, CrmPdiPlan::getId, bo.getId());
lqw.eq(bo.getSeqid() != null, CrmPdiPlan::getSeqid, bo.getSeqid());
lqw.eq(StringUtils.isNotBlank(bo.getCoilid()), CrmPdiPlan::getCoilid, bo.getCoilid());
lqw.eq(StringUtils.isNotBlank(bo.getEnterCoilNo()), CrmPdiPlan::getEnterCoilNo, bo.getEnterCoilNo());
lqw.eq(StringUtils.isNotBlank(bo.getStatus()), CrmPdiPlan::getStatus, bo.getStatus());
lqw.eq(StringUtils.isNotBlank(bo.getPlanid()), CrmPdiPlan::getPlanid, bo.getPlanid());
lqw.eq(StringUtils.isNotBlank(bo.getPlanType()), CrmPdiPlan::getPlanType, bo.getPlanType());

View File

@@ -18,6 +18,7 @@ import org.springframework.stereotype.Service;
import java.util.Collection;
import java.util.List;
import java.util.Map;
@RequiredArgsConstructor
@DS("galvanize1")
@@ -45,15 +46,15 @@ public class CrmPdoExcoilServiceImpl implements ICrmPdoExcoilService {
}
private LambdaQueryWrapper<CrmPdoExcoil> buildQueryWrapper(CrmPdoExcoilBo bo) {
Map<String, Object> params = bo.getParams();
LambdaQueryWrapper<CrmPdoExcoil> lqw = Wrappers.lambdaQuery();
lqw.eq(bo.getId() != null, CrmPdoExcoil::getId, bo.getId());
lqw.eq(StringUtils.isNotBlank(bo.getExitMatId()), CrmPdoExcoil::getExitMatId, bo.getExitMatId());
lqw.eq(StringUtils.isNotBlank(bo.getEntryMatId()), CrmPdoExcoil::getEntryMatId, bo.getEntryMatId());
lqw.eq(StringUtils.isNotBlank(bo.getEnterCoilNo()), CrmPdoExcoil::getEnterCoilNo, bo.getEnterCoilNo());
lqw.eq(bo.getPlanId() != null, CrmPdoExcoil::getPlanId, bo.getPlanId());
lqw.eq(StringUtils.isNotBlank(bo.getStatus()), CrmPdoExcoil::getStatus, bo.getStatus());
lqw.eq(StringUtils.isNotBlank(bo.getUnitCode()), CrmPdoExcoil::getUnitCode, bo.getUnitCode());
lqw.orderByDesc(CrmPdoExcoil::getCreateTime);
lqw.orderByDesc(CrmPdoExcoil::getId);
return lqw;
}

View File

@@ -1,274 +0,0 @@
package com.klp.pocket.galvanize1.service.impl;
import com.klp.common.utils.StringUtils;
import com.klp.pocket.acid.domain.vo.AcidOeeCoilInfoByDateVo;
import com.klp.pocket.acid.domain.vo.AcidOeeDailySummaryVo;
import com.klp.pocket.acid.domain.vo.AcidOeeIdealCycleVo;
import com.klp.pocket.acid.domain.vo.AcidOeeLoss7Vo;
import com.klp.pocket.acid.domain.vo.Klptcm1ProStoppageVo;
import com.klp.pocket.galvanize1.domain.bo.ProStoppageBo;
import com.klp.pocket.galvanize1.domain.vo.ProStoppageVo;
import com.klp.pocket.galvanize1.mapper.GalvanizeOeeMasterMapper;
import com.klp.pocket.galvanize1.service.IGalvanizeOeeService;
import com.klp.pocket.galvanize1.service.IProStoppageService;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.text.SimpleDateFormat;
import java.util.*;
@Slf4j
@RequiredArgsConstructor
@Service
public class GalvanizeOeeServiceImpl implements IGalvanizeOeeService {
private static final Set<String> CD_SERIES = new HashSet<>(Arrays.asList("C+", "C", "C-", "D+", "D", "D-"));
private final GalvanizeOeeMasterMapper galvanizeOeeMasterMapper;
private final IProStoppageService proStoppageService;
@Value("${oee.galvanize1.coil-create-by:duxinkuguan}")
private String galvanizeCreateBy;
@Override
public List<AcidOeeDailySummaryVo> getDailySummary(String startDate, String endDate) {
List<AcidOeeDailySummaryVo> summaries = galvanizeOeeMasterMapper.selectDailySummary(startDate, endDate, galvanizeCreateBy);
if (summaries == null || summaries.isEmpty()) return Collections.emptyList();
Map<String, Long> downtimeByDate = aggregateDowntimeByDate(startDate, endDate);
Map<String, List<CoilInfo>> coilInfoByDate = getCoilNosByDate(startDate, endDate);
List<BigDecimal> dailyCycles = new ArrayList<>();
for (AcidOeeDailySummaryVo s : summaries) {
s.setLineId("DX1");
s.setLineName("镀锌一线");
Long downtime = downtimeByDate.getOrDefault(s.getStatDate(), 0L);
s.setDowntimeMin(downtime);
Long loading = s.getLoadingTimeMin() == null ? 0L : s.getLoadingTimeMin();
Long run = Math.max(0, loading - downtime);
s.setRunTimeMin(run);
BigDecimal ton = s.getTotalOutputTon();
if (run > 0 && ton != null && ton.compareTo(BigDecimal.ZERO) > 0) {
dailyCycles.add(BigDecimal.valueOf(run).divide(ton, 6, RoundingMode.HALF_UP));
}
}
dailyCycles.sort(BigDecimal::compareTo);
BigDecimal ideal = applyEightyPercent(median(dailyCycles));
for (AcidOeeDailySummaryVo s : summaries) {
if (ideal != null) s.setIdealCycleTimeMinPerTon(ideal);
List<CoilInfo> coilInfos = coilInfoByDate.get(s.getStatDate());
if (coilInfos != null) {
calculateQualityOutput(s, coilInfos);
} else {
s.setGoodOutputTon(BigDecimal.ZERO);
s.setGoodOutputCoil(0L);
s.setQualifiedOutputTon(BigDecimal.ZERO);
s.setQualifiedOutputCoil(0L);
s.setAbOutputTon(BigDecimal.ZERO);
s.setAbOutputCoil(0L);
s.setDefectOutputTon(BigDecimal.ZERO);
s.setDefectOutputCoil(0L);
s.setPendingOutputTon(s.getTotalOutputTon());
s.setPendingOutputCoil(s.getTotalOutputCoil());
}
calculateDerivedMetrics(s);
}
return summaries;
}
@Override
public List<Klptcm1ProStoppageVo> getStoppageEvents(String startDate, String endDate) {
ProStoppageBo bo = new ProStoppageBo();
bo.setStartDate(parseDate(startDate));
bo.setEndDate(parseDate(endDate));
List<ProStoppageVo> list = proStoppageService.queryList(bo);
List<Klptcm1ProStoppageVo> out = new ArrayList<>();
for (ProStoppageVo p : list) {
Klptcm1ProStoppageVo v = new Klptcm1ProStoppageVo();
v.setStopid(p.getStopid());
v.setShift(p.getShift());
v.setCrew(p.getCrew());
v.setArea(p.getArea());
v.setUnit(p.getUnit());
v.setSeton(p.getSeton());
v.setStartDate(p.getStartDate());
v.setEndDate(p.getEndDate());
v.setRemark(p.getRemark());
v.setStopType(p.getStopType());
long durationSec = p.getDuration() == null ? 0L : Math.round(p.getDuration());
v.setDuration(durationSec);
out.add(v);
}
return out;
}
@Override
public List<AcidOeeLoss7Vo> getLoss7Summary(String startDate, String endDate) {
List<Klptcm1ProStoppageVo> events = getStoppageEvents(startDate, endDate);
if (events.isEmpty()) return Collections.emptyList();
Map<String, long[]> m = new HashMap<>();
long total = 0L;
for (Klptcm1ProStoppageVo e : events) {
String k = StringUtils.isBlank(e.getStopType()) ? "未分类" : e.getStopType();
long min = Math.max(1, (e.getDuration() == null ? 0L : e.getDuration()) / 60);
if (min <= 0) continue;
m.computeIfAbsent(k, x -> new long[2]);
m.get(k)[0] += min;
m.get(k)[1] += 1;
total += min;
}
List<AcidOeeLoss7Vo> out = new ArrayList<>();
for (Map.Entry<String, long[]> en : m.entrySet()) {
AcidOeeLoss7Vo vo = new AcidOeeLoss7Vo();
vo.setLossCategoryCode(en.getKey());
vo.setLossCategoryName(en.getKey());
vo.setLossTimeMin(en.getValue()[0]);
vo.setCount((int) en.getValue()[1]);
vo.setAvgDurationMin(BigDecimal.valueOf(en.getValue()[0]).divide(BigDecimal.valueOf(Math.max(1, en.getValue()[1])), 2, RoundingMode.HALF_UP));
vo.setLossTimeRate(total == 0 ? BigDecimal.ZERO : BigDecimal.valueOf(en.getValue()[0]).divide(BigDecimal.valueOf(total), 4, RoundingMode.HALF_UP).multiply(BigDecimal.valueOf(100)));
out.add(vo);
}
out.sort(Comparator.comparingLong(AcidOeeLoss7Vo::getLossTimeMin).reversed());
return out;
}
@Override
public AcidOeeIdealCycleVo getIdealCycle(String startDate, String endDate) {
List<AcidOeeDailySummaryVo> daily = getDailySummary(startDate, endDate);
AcidOeeIdealCycleVo rsp = new AcidOeeIdealCycleVo();
rsp.setLineId("DX1");
rsp.setLineName("镀锌一线");
rsp.setStartDate(startDate);
rsp.setEndDate(endDate);
if (daily.isEmpty()) {
rsp.setDailyComparePoints(Collections.emptyList());
rsp.setSampleDays(0);
return rsp;
}
List<BigDecimal> dailyCycles = new ArrayList<>();
List<AcidOeeIdealCycleVo.DailyComparePointVo> points = new ArrayList<>();
for (AcidOeeDailySummaryVo d : daily) {
if (d.getRunTimeMin() == null || d.getRunTimeMin() <= 0 || d.getTotalOutputTon() == null || d.getTotalOutputTon().compareTo(BigDecimal.ZERO) <= 0) continue;
BigDecimal c = BigDecimal.valueOf(d.getRunTimeMin()).divide(d.getTotalOutputTon(), 6, RoundingMode.HALF_UP);
dailyCycles.add(c);
}
dailyCycles.sort(BigDecimal::compareTo);
BigDecimal med = median(dailyCycles);
BigDecimal ideal = applyEightyPercent(med);
for (AcidOeeDailySummaryVo d : daily) {
if (ideal == null || d.getTotalOutputTon() == null || d.getRunTimeMin() == null) continue;
AcidOeeIdealCycleVo.DailyComparePointVo p = new AcidOeeIdealCycleVo.DailyComparePointVo();
p.setStatDate(d.getStatDate());
p.setActualRunTimeMin(d.getRunTimeMin());
p.setTheoreticalTimeMin(ideal.multiply(d.getTotalOutputTon()));
points.add(p);
}
rsp.setIdealCycleTimeMinPerTon(ideal);
rsp.setMedianCycleTimeMinPerTon(med);
rsp.setSampleDays(daily.size());
rsp.setDailyComparePoints(points);
return rsp;
}
private Map<String, List<CoilInfo>> getCoilNosByDate(String startDate, String endDate) {
List<AcidOeeCoilInfoByDateVo> list = galvanizeOeeMasterMapper.selectCoilInfoByDate(startDate, endDate, galvanizeCreateBy);
Map<String, List<CoilInfo>> map = new HashMap<>();
for (AcidOeeCoilInfoByDateVo i : list) {
map.computeIfAbsent(i.getStatDate(), k -> new ArrayList<>()).add(new CoilInfo(i.getWeight(), i.getQualityStatus()));
}
return map;
}
private void calculateQualityOutput(AcidOeeDailySummaryVo summary, List<CoilInfo> coilInfos) {
BigDecimal aTon = BigDecimal.ZERO, bTon = BigDecimal.ZERO, cdTon = BigDecimal.ZERO, oTon = BigDecimal.ZERO;
long a = 0, b = 0, cd = 0, o = 0;
for (CoilInfo c : coilInfos) {
BigDecimal w = c.weight == null ? BigDecimal.ZERO : c.weight;
String q = StringUtils.upperCase(StringUtils.trim(c.qualityStatus));
if (StringUtils.isBlank(q) || "O".equals(q)) {
oTon = oTon.add(w); o++; continue;
}
if (q.startsWith("A")) { aTon = aTon.add(w); a++; }
else if (q.startsWith("B")) { bTon = bTon.add(w); b++; }
else if (CD_SERIES.contains(q)) { cdTon = cdTon.add(w); cd++; }
else { oTon = oTon.add(w); o++; }
}
summary.setGoodOutputTon(aTon); summary.setGoodOutputCoil(a);
summary.setQualifiedOutputTon(bTon); summary.setQualifiedOutputCoil(b);
summary.setAbOutputTon(aTon.add(bTon)); summary.setAbOutputCoil(a + b);
summary.setDefectOutputTon(cdTon); summary.setDefectOutputCoil(cd);
summary.setPendingOutputTon(oTon); summary.setPendingOutputCoil(o);
}
private void calculateDerivedMetrics(AcidOeeDailySummaryVo s) {
long loading = s.getLoadingTimeMin() == null ? 0 : s.getLoadingTimeMin();
long downtime = s.getDowntimeMin() == null ? 0 : s.getDowntimeMin();
if (loading > 0) s.setAvailability(BigDecimal.valueOf(loading - downtime).divide(BigDecimal.valueOf(loading), 4, RoundingMode.HALF_UP).multiply(BigDecimal.valueOf(100)));
long run = s.getRunTimeMin() == null ? 0 : s.getRunTimeMin();
BigDecimal ideal = s.getIdealCycleTimeMinPerTon();
BigDecimal ton = s.getTotalOutputTon();
if (run > 0 && ideal != null && ideal.compareTo(BigDecimal.ZERO) > 0 && ton != null && ton.compareTo(BigDecimal.ZERO) > 0) {
s.setPerformanceTon(ideal.multiply(ton).divide(BigDecimal.valueOf(run), 4, RoundingMode.HALF_UP).multiply(BigDecimal.valueOf(100)));
}
if (ton != null && ton.compareTo(BigDecimal.ZERO) > 0) {
BigDecimal a = Optional.ofNullable(s.getGoodOutputTon()).orElse(BigDecimal.ZERO);
BigDecimal b = Optional.ofNullable(s.getQualifiedOutputTon()).orElse(BigDecimal.ZERO);
BigDecimal cd = Optional.ofNullable(s.getDefectOutputTon()).orElse(BigDecimal.ZERO);
BigDecimal o = Optional.ofNullable(s.getPendingOutputTon()).orElse(BigDecimal.ZERO);
s.setQuality(a.divide(ton, 4, RoundingMode.HALF_UP).multiply(BigDecimal.valueOf(100)));
s.setQualifiedRate(a.add(b).divide(ton, 4, RoundingMode.HALF_UP).multiply(BigDecimal.valueOf(100)));
s.setDefectRate(cd.divide(ton, 4, RoundingMode.HALF_UP).multiply(BigDecimal.valueOf(100)));
s.setPendingRate(o.divide(ton, 4, RoundingMode.HALF_UP).multiply(BigDecimal.valueOf(100)));
}
if (s.getAvailability() != null && s.getPerformanceTon() != null && s.getQuality() != null) {
s.setOee(s.getAvailability().multiply(s.getPerformanceTon()).multiply(s.getQuality()).divide(BigDecimal.valueOf(10000), 4, RoundingMode.HALF_UP));
}
}
private Map<String, Long> aggregateDowntimeByDate(String startDate, String endDate) {
List<Klptcm1ProStoppageVo> events = getStoppageEvents(startDate, endDate);
Map<String, Long> map = new HashMap<>();
SimpleDateFormat df = new SimpleDateFormat("yyyy-MM-dd");
for (Klptcm1ProStoppageVo e : events) {
if (e.getStartDate() == null || e.getDuration() == null || e.getDuration() <= 0) continue;
long min = Math.max(1, (e.getDuration() + 59) / 60);
map.merge(df.format(e.getStartDate()), min, Long::sum);
}
return map;
}
private BigDecimal median(List<BigDecimal> values) {
if (values == null || values.isEmpty()) return null;
int n = values.size();
if (n % 2 == 1) return values.get(n / 2);
return values.get(n / 2 - 1).add(values.get(n / 2)).divide(BigDecimal.valueOf(2), 6, RoundingMode.HALF_UP);
}
private BigDecimal applyEightyPercent(BigDecimal v) {
return v == null ? null : v.multiply(BigDecimal.valueOf(0.7)).setScale(6, RoundingMode.HALF_UP);
}
private Date parseDate(String dateStr) {
if (StringUtils.isBlank(dateStr)) return null;
try {
return new SimpleDateFormat("yyyy-MM-dd").parse(dateStr);
} catch (Exception e) {
return null;
}
}
private static class CoilInfo {
final BigDecimal weight;
final String qualityStatus;
CoilInfo(BigDecimal weight, String qualityStatus) {
this.weight = weight;
this.qualityStatus = qualityStatus;
}
}
}

View File

@@ -1,70 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.klp.pocket.galvanize1.mapper.GalvanizeOeeMasterMapper">
<resultMap id="AcidOeeDailySummaryResultMap" type="com.klp.pocket.acid.domain.vo.AcidOeeDailySummaryVo">
<result column="stat_date" property="statDate" jdbcType="VARCHAR"/>
<result column="line_id" property="lineId" jdbcType="VARCHAR"/>
<result column="line_name" property="lineName" jdbcType="VARCHAR"/>
<result column="planned_time_min" property="plannedTimeMin" jdbcType="BIGINT"/>
<result column="planned_downtime_min" property="plannedDowntimeMin" jdbcType="BIGINT"/>
<result column="loading_time_min" property="loadingTimeMin" jdbcType="BIGINT"/>
<result column="downtime_min" property="downtimeMin" jdbcType="BIGINT"/>
<result column="run_time_min" property="runTimeMin" jdbcType="BIGINT"/>
<result column="total_output_ton" property="totalOutputTon" jdbcType="DECIMAL"/>
<result column="total_output_coil" property="totalOutputCoil" jdbcType="BIGINT"/>
<result column="good_output_ton" property="goodOutputTon" jdbcType="DECIMAL"/>
<result column="good_output_coil" property="goodOutputCoil" jdbcType="BIGINT"/>
<result column="defect_output_ton" property="defectOutputTon" jdbcType="DECIMAL"/>
<result column="defect_output_coil" property="defectOutputCoil" jdbcType="BIGINT"/>
<result column="ideal_cycle_time_min_per_ton" property="idealCycleTimeMinPerTon" jdbcType="DECIMAL"/>
<result column="ideal_cycle_time_min_per_coil" property="idealCycleTimeMinPerCoil" jdbcType="DECIMAL"/>
</resultMap>
<select id="selectDailySummary" resultMap="AcidOeeDailySummaryResultMap">
SELECT
DATE_FORMAT(mc.create_time, '%Y-%m-%d') AS stat_date,
'DX1' AS line_id,
'镀锌一线' AS line_name,
1440 AS planned_time_min,
0 AS planned_downtime_min,
1440 AS loading_time_min,
0 AS downtime_min,
COALESCE(SUM(mc.net_weight / 1000), 0) AS total_output_ton,
COUNT(*) AS total_output_coil,
0 AS good_output_ton,
0 AS good_output_coil,
0 AS defect_output_ton,
0 AS defect_output_coil,
NULL AS ideal_cycle_time_min_per_ton,
NULL AS ideal_cycle_time_min_per_coil
FROM wms_material_coil mc
WHERE DATE(mc.create_time) BETWEEN #{startDate} AND #{endDate}
AND mc.create_by = #{createBy}
AND mc.del_flag = 0
AND mc.data_type = 1
AND mc.current_coil_no IS NOT NULL
AND mc.net_weight IS NOT NULL
AND mc.net_weight > 0
GROUP BY DATE_FORMAT(mc.create_time, '%Y-%m-%d')
ORDER BY stat_date ASC
</select>
<select id="selectCoilInfoByDate" resultType="com.klp.pocket.acid.domain.vo.AcidOeeCoilInfoByDateVo">
SELECT
DATE_FORMAT(mc.create_time, '%Y-%m-%d') AS statDate,
mc.current_coil_no AS coilNo,
(mc.net_weight / 1000) AS weight,
mc.quality_status AS qualityStatus
FROM wms_material_coil mc
WHERE DATE(mc.create_time) BETWEEN #{startDate} AND #{endDate}
AND mc.create_by = #{createBy}
AND mc.del_flag = 0
AND mc.data_type = 1
AND mc.current_coil_no IS NOT NULL
AND mc.net_weight IS NOT NULL
AND mc.net_weight > 0
ORDER BY mc.create_time ASC, mc.current_coil_no ASC
</select>
</mapper>

View File

@@ -1,25 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.klp.pocket.acid.mapper.AcidOeeAcidMapper">
<!-- 查询卷级生产节拍min/吨):(END_DATE - START_DATE)/EXIT_WEIGHT -->
<select id="selectCoilCycleMinPerTon" resultType="java.math.BigDecimal">
SELECT
TIMESTAMPDIFF(MINUTE, e.START_DATE, e.END_DATE) / e.EXIT_WEIGHT AS cycle_min_per_ton
FROM klptcm1_pdo_excoil e
WHERE 1 = 1
<if test="startDate != null and startDate != ''">
AND DATE(e.INSDATE) &gt;= #{startDate}
</if>
<if test="endDate != null and endDate != ''">
AND DATE(e.INSDATE) &lt;= #{endDate}
</if>
AND e.START_DATE IS NOT NULL
AND e.END_DATE IS NOT NULL
AND e.END_DATE &gt; e.START_DATE
AND e.EXIT_WEIGHT IS NOT NULL
AND e.EXIT_WEIGHT &gt; 0
AND TIMESTAMPDIFF(MINUTE, e.START_DATE, e.END_DATE) &gt; 0
</select>
</mapper>

View File

@@ -0,0 +1,95 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.klp.pocket.acid.mapper.AcidOeeMapper">
<!-- OEE日汇总结果映射 -->
<resultMap id="AcidOeeDailySummaryResultMap" type="com.klp.pocket.acid.domain.vo.AcidOeeDailySummaryVo">
<result column="stat_date" property="statDate" jdbcType="VARCHAR"/>
<result column="line_id" property="lineId" jdbcType="VARCHAR"/>
<result column="line_name" property="lineName" jdbcType="VARCHAR"/>
<result column="planned_time_min" property="plannedTimeMin" jdbcType="BIGINT"/>
<result column="planned_downtime_min" property="plannedDowntimeMin" jdbcType="BIGINT"/>
<result column="loading_time_min" property="loadingTimeMin" jdbcType="BIGINT"/>
<result column="downtime_min" property="downtimeMin" jdbcType="BIGINT"/>
<result column="run_time_min" property="runTimeMin" jdbcType="BIGINT"/>
<result column="total_output_ton" property="totalOutputTon" jdbcType="DECIMAL"/>
<result column="total_output_coil" property="totalOutputCoil" jdbcType="BIGINT"/>
<result column="good_output_ton" property="goodOutputTon" jdbcType="DECIMAL"/>
<result column="good_output_coil" property="goodOutputCoil" jdbcType="BIGINT"/>
<result column="defect_output_ton" property="defectOutputTon" jdbcType="DECIMAL"/>
<result column="defect_output_coil" property="defectOutputCoil" jdbcType="BIGINT"/>
<result column="ideal_cycle_time_min_per_ton" property="idealCycleTimeMinPerTon" jdbcType="DECIMAL"/>
<result column="ideal_cycle_time_min_per_coil" property="idealCycleTimeMinPerCoil" jdbcType="DECIMAL"/>
</resultMap>
<!-- 查询OEE日汇总 -->
<select id="selectDailySummary" resultMap="AcidOeeDailySummaryResultMap">
SELECT
DATE_FORMAT(e.INSDATE, '%Y-%m-%d') AS stat_date,
'SY' AS line_id,
'酸轧线' AS line_name,
-- 计划时间暂时使用24小时1440分钟后续可从计划表获取
1440 AS planned_time_min,
-- 计划停机暂时为0后续可从停机事件表中筛选stop_type='计划停机'的汇总
0 AS planned_downtime_min,
-- 负荷时间 = 计划时间 - 计划停机
1440 AS loading_time_min,
-- 停机时间在Service层通过停机事件表聚合填充
0 AS downtime_min,
-- 总产量(吨):出口重量总和
COALESCE(SUM(e.EXIT_WEIGHT), 0) AS total_output_ton,
-- 总产量(卷):记录数
COUNT(*) AS total_output_coil,
-- 良品/次品在Service层通过WMS判定后填充
0 AS good_output_ton,
0 AS good_output_coil,
0 AS defect_output_ton,
0 AS defect_output_coil,
-- 理论节拍在Service层通过回归数据填充
NULL AS ideal_cycle_time_min_per_ton,
NULL AS ideal_cycle_time_min_per_coil
FROM klptcm1_pdo_excoil e
WHERE DATE(e.INSDATE) BETWEEN #{startDate} AND #{endDate}
GROUP BY DATE_FORMAT(e.INSDATE, '%Y-%m-%d')
ORDER BY stat_date ASC
</select>
<!-- 查询每日的钢卷号和重量(用于良品/次品判定) -->
<select id="selectCoilInfoByDate" resultType="com.klp.pocket.acid.mapper.AcidOeeMapper$CoilInfoByDate">
SELECT
DATE_FORMAT(e.INSDATE, '%Y-%m-%d') AS statDate,
-- 当前钢卷号使用出口卷号excoilid或成品卷号encoilid
COALESCE(e.EXCOILID, e.ENCOILID) AS coilNo,
-- 重量(吨):出口重量
e.EXIT_WEIGHT AS weight
FROM klptcm1_pdo_excoil e
WHERE DATE(e.INSDATE) BETWEEN #{startDate} AND #{endDate}
AND e.EXIT_WEIGHT IS NOT NULL
AND e.EXIT_WEIGHT > 0
AND (e.EXCOILID IS NOT NULL OR e.ENCOILID IS NOT NULL)
ORDER BY e.INSDATE ASC, e.ENCOILID ASC
</select>
<!-- 查询卷级生产节拍min/吨):(END_DATE - START_DATE)/EXIT_WEIGHT -->
<select id="selectCoilCycleMinPerTon" resultType="java.math.BigDecimal">
SELECT
-- 生产节拍(分钟/吨)
TIMESTAMPDIFF(MINUTE, e.START_DATE, e.END_DATE) / e.EXIT_WEIGHT AS cycle_min_per_ton
FROM klptcm1_pdo_excoil e
WHERE 1 = 1
<if test="startDate != null and startDate != ''">
AND DATE(e.INSDATE) &gt;= #{startDate}
</if>
<if test="endDate != null and endDate != ''">
AND DATE(e.INSDATE) &lt;= #{endDate}
</if>
AND e.START_DATE IS NOT NULL
AND e.END_DATE IS NOT NULL
AND e.END_DATE &gt; e.START_DATE
AND e.EXIT_WEIGHT IS NOT NULL
AND e.EXIT_WEIGHT &gt; 0
AND TIMESTAMPDIFF(MINUTE, e.START_DATE, e.END_DATE) &gt; 0
</select>
</mapper>

View File

@@ -1,68 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.klp.pocket.acid.mapper.AcidOeeMasterMapper">
<!-- OEE日汇总结果映射 -->
<resultMap id="AcidOeeDailySummaryResultMap" type="com.klp.pocket.acid.domain.vo.AcidOeeDailySummaryVo">
<result column="stat_date" property="statDate" jdbcType="VARCHAR"/>
<result column="line_id" property="lineId" jdbcType="VARCHAR"/>
<result column="line_name" property="lineName" jdbcType="VARCHAR"/>
<result column="planned_time_min" property="plannedTimeMin" jdbcType="BIGINT"/>
<result column="planned_downtime_min" property="plannedDowntimeMin" jdbcType="BIGINT"/>
<result column="loading_time_min" property="loadingTimeMin" jdbcType="BIGINT"/>
<result column="downtime_min" property="downtimeMin" jdbcType="BIGINT"/>
<result column="run_time_min" property="runTimeMin" jdbcType="BIGINT"/>
<result column="total_output_ton" property="totalOutputTon" jdbcType="DECIMAL"/>
<result column="total_output_coil" property="totalOutputCoil" jdbcType="BIGINT"/>
<result column="good_output_ton" property="goodOutputTon" jdbcType="DECIMAL"/>
<result column="good_output_coil" property="goodOutputCoil" jdbcType="BIGINT"/>
<result column="defect_output_ton" property="defectOutputTon" jdbcType="DECIMAL"/>
<result column="defect_output_coil" property="defectOutputCoil" jdbcType="BIGINT"/>
<result column="ideal_cycle_time_min_per_ton" property="idealCycleTimeMinPerTon" jdbcType="DECIMAL"/>
<result column="ideal_cycle_time_min_per_coil" property="idealCycleTimeMinPerCoil" jdbcType="DECIMAL"/>
</resultMap>
<!-- 查询OEE日汇总总产量统一使用主库 wms_material_coil -->
<select id="selectDailySummary" resultMap="AcidOeeDailySummaryResultMap">
SELECT
DATE_FORMAT(mc.create_time, '%Y-%m-%d') AS stat_date,
'SY' AS line_id,
'酸轧线' AS line_name,
1440 AS planned_time_min,
0 AS planned_downtime_min,
1440 AS loading_time_min,
0 AS downtime_min,
COALESCE(SUM(mc.net_weight), 0) AS total_output_ton,
COUNT(*) AS total_output_coil,
0 AS good_output_ton,
0 AS good_output_coil,
0 AS defect_output_ton,
0 AS defect_output_coil,
NULL AS ideal_cycle_time_min_per_ton,
NULL AS ideal_cycle_time_min_per_coil
FROM wms_material_coil mc
WHERE DATE(mc.create_time) BETWEEN #{startDate} AND #{endDate}
AND mc.create_by = #{createBy}
AND mc.del_flag = 0
AND mc.net_weight IS NOT NULL
AND mc.net_weight > 0
GROUP BY DATE_FORMAT(mc.create_time, '%Y-%m-%d')
</select>
<!-- 查询每日的钢卷号、重量、判级主库wms_material_coil -->
<select id="selectCoilInfoByDate" resultType="com.klp.pocket.acid.domain.vo.AcidOeeCoilInfoByDateVo">
SELECT
DATE_FORMAT(mc.create_time, '%Y-%m-%d') AS statDate,
mc.current_coil_no AS coilNo,
(mc.net_weight) AS weight,
mc.quality_status AS qualityStatus
FROM wms_material_coil mc
WHERE DATE(mc.create_time) BETWEEN #{startDate} AND #{endDate}
AND mc.create_by = #{createBy}
AND mc.del_flag = 0
AND mc.net_weight IS NOT NULL
AND mc.net_weight > 0
</select>
</mapper>

View File

@@ -1,5 +1,5 @@
# 页面标题
VUE_APP_TITLE = 科伦普冷轧涂镀数智运营一体化平台
VUE_APP_TITLE = MES一体化平台
# 开发环境配置
ENV = 'development'

View File

@@ -1,5 +1,5 @@
# 页面标题
VUE_APP_TITLE = 科伦普冷轧涂镀数智运营一体化平台
VUE_APP_TITLE = MES一体化平台
# 生产环境配置
ENV = 'production'

View File

@@ -1,5 +1,5 @@
# 页面标题
VUE_APP_TITLE = 科伦普冷轧涂镀数智运营一体化平台
VUE_APP_TITLE = MES一体化平台
# 开发环境配置
ENV = 'development'

View File

@@ -5,7 +5,7 @@
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
<meta name="renderer" content="webkit">
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no">
<link rel="icon" href="<%= BASE_URL %>favicon.png">
<link rel="icon" href="<%= BASE_URL %>favicon.ico">
<title><%= webpackConfig.name %></title>
<!--[if lt IE 11]><script>window.location.href='/html/ie.html';</script><![endif]-->
<style>

View File

@@ -1,42 +0,0 @@
import request from '@/utils/request'
export function fetchQuickSheetList(params) {
return request({
url: '/aps/quick-sheet/list',
method: 'get',
params
})
}
export function fetchQuickSheetPreset(params) {
return request({
url: '/aps/quick-sheet/preset',
method: 'get',
params
})
}
export function saveQuickSheet(data) {
return request({
url: '/aps/quick-sheet/save',
method: 'post',
data
})
}
export function exportQuickSheet(params) {
return request({
url: '/aps/quick-sheet/export',
method: 'get',
params,
responseType: 'blob'
})
}
export function deleteQuickSheetRow(quickSheetId) {
return request({
url: '/aps/quick-sheet/delete',
method: 'post',
params: { quickSheetId }
})
}

View File

@@ -8,10 +8,3 @@ export function getAcidTypingPrefill(currentCoilNo) {
})
}
export function getGalvanize1TypingPrefill(params) {
return request({
url: '/pocket/galvanize1/crmPdoExcoil/list',
method: 'get',
params
})
}

View File

@@ -27,16 +27,3 @@ export function delOss(ossId) {
})
}
/**
* 上传文件
*/
export function uploadFile(file) {
const form = new FormData()
form.append('file', file)
return request({
url: '/system/oss/upload',
method: 'post',
data: form,
})
}

View File

@@ -1,62 +0,0 @@
import request from '@/utils/request'
// 查询退火炉列表
export function listAnnealFurnace(query) {
return request({
url: '/wms/anneal/furnace/list',
method: 'get',
params: query
})
}
// 查询退火炉详情
export function getAnnealFurnace(furnaceId) {
return request({
url: '/wms/anneal/furnace/' + furnaceId,
method: 'get'
})
}
// 新增退火炉
export function addAnnealFurnace(data) {
return request({
url: '/wms/anneal/furnace/add',
method: 'post',
data: data
})
}
// 修改退火炉
export function updateAnnealFurnace(data) {
return request({
url: '/wms/anneal/furnace/edit',
method: 'put',
data: data
})
}
// 启用停用
export function changeAnnealFurnaceStatus(data) {
return request({
url: '/wms/anneal/furnace/status',
method: 'put',
data: data
})
}
// 置忙/置闲
export function changeAnnealFurnaceBusy(data) {
return request({
url: '/wms/anneal/furnace/busy',
method: 'put',
data: data
})
}
// 删除退火炉
export function delAnnealFurnace(furnaceId) {
return request({
url: '/wms/anneal/furnace/' + furnaceId,
method: 'delete'
})
}

View File

@@ -1,9 +0,0 @@
import request from '@/utils/request'
// 查询退火总览信息
export function getAnnealOverview() {
return request({
url: '/wms/anneal/overview',
method: 'get'
})
}

View File

@@ -1,10 +0,0 @@
import request from '@/utils/request'
// 查询炉火实绩
export function getAnnealPerformance(query) {
return request({
url: '/wms/anneal/performance',
method: 'get',
params: query
})
}

View File

@@ -1,109 +0,0 @@
import request from '@/utils/request'
// 查询退火计划列表
export function listAnnealPlan(query) {
return request({
url: '/wms/anneal/plan/list',
method: 'get',
params: query
})
}
// 查询退火计划详情
export function getAnnealPlan(planId) {
return request({
url: '/wms/anneal/plan/' + planId,
method: 'get'
})
}
// 新增退火计划
export function addAnnealPlan(data) {
return request({
url: '/wms/anneal/plan/add',
method: 'post',
data: data
})
}
// 修改退火计划
export function updateAnnealPlan(data) {
return request({
url: '/wms/anneal/plan/edit',
method: 'put',
data: data
})
}
// 删除退火计划
export function delAnnealPlan(planId) {
return request({
url: '/wms/anneal/plan/' + planId,
method: 'delete'
})
}
// 更新计划状态
export function changeAnnealPlanStatus(data) {
return request({
url: '/wms/anneal/plan/status',
method: 'put',
data: data
})
}
// 入炉
export function inFurnace(data) {
return request({
url: '/wms/anneal/plan/in-furnace',
method: 'put',
data: data
})
}
// 完成退火
export function completeAnnealPlan(data) {
return request({
url: '/wms/anneal/plan/complete',
method: 'put',
data: data
})
}
// 查询计划钢卷列表
export function listAnnealPlanCoils(planId) {
return request({
url: '/wms/anneal/plan/coil/list',
method: 'get',
params: { planId }
})
}
// 绑定钢卷
export function bindAnnealPlanCoils(data) {
return request({
url: '/wms/anneal/plan/coil/bind',
method: 'post',
data: data
})
}
// 解绑钢卷
export function unbindAnnealPlanCoil(data) {
return request({
url: '/wms/anneal/plan/coil/unbind',
method: 'delete',
data: data
})
}
/**
* 更新钢卷绑定信息
*/
export function updateAnnealPlanCoil(data) {
return request({
url: '/wms/furnacePlanCoil',
method: 'put',
data: data
})
}

View File

@@ -42,27 +42,3 @@ export function delApproval(approvalId) {
method: 'delete'
})
}
/**
* 撤销审批
*/
export function withdrawApproval(approvalId) {
return request({
url: '/wms/approval/cancel',
method: 'post',
params: {
approvalId: approvalId
}
})
}
/**
* 按业务ID查询审批信息用于用印等业务
*/
export function getApprovalByBizId(bizId) {
return request({
url: '/wms/approval/getByBizId',
method: 'get',
params: { bizId }
})
}

View File

@@ -1,5 +1,4 @@
import request from '@/utils/request'
import { tansParams } from "@/utils/klp";
// 查询钢卷物料表列表
export function listMaterialCoil(query) {
@@ -116,8 +115,8 @@ export function splitMaterialCoil(data) {
// 钢卷合卷
export function mergeMaterialCoil(data) {
return request({
url: '/wms/materialCoil/merge',
method: 'post',
url: '/wms/materialCoil',
method: 'put',
data: data
})
}
@@ -190,8 +189,7 @@ export function listCoilWithIds(data) {
return request({
url: '/wms/materialCoil/listByPost',
method: 'post',
data,
timeout: 600000
data
})
}
@@ -204,14 +202,13 @@ export function cancelExportCoil(coilId) {
}
// 检查入场钢卷号或当前钢卷号是否合法(是否存在)
export function checkCoilNo({ currentCoilNo, enterCoilNo, coilId, supplierCoilNo }) {
export function checkCoilNo({ currentCoilNo, enterCoilNo, coilId }) {
return request({
url: '/wms/materialCoil/checkCoilNoDuplicate',
method: 'get',
params: {
currentCoilNo,
enterCoilNo,
supplierCoilNo,
coilId
}
})
@@ -255,19 +252,15 @@ export function restoreMaterialCoil(coilId) {
/**
* 开始分条,锁定钢卷
*/
export function startSpecialSplit(coilId, actionType) {
export function startSpecialSplit(coilId) {
if (!coilId) {
return Promise.reject(new Error('coilId is required'))
}
if (!actionType) {
return Promise.reject(new Error('actionType is required'))
}
return request({
url: '/wms/materialCoil/specialSplit/start',
method: 'post',
params: {
coilId,
actionType
coilId
}
})
}
@@ -352,18 +345,4 @@ export function categoryWidthStatistics() {
url: '/wms/materialCoil/statistics/categoryWidthStatistics',
method: 'get',
})
}
/**
* 导出钢卷的全部字段
*/
export function exportCoilWithAll(data) {
return request({
url: '/wms/materialCoil/exportAll',
method: 'post',
data: data,
transformRequest: [(params) => { return tansParams(params) }],
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
responseType: 'blob'
})
}

View File

@@ -42,16 +42,3 @@ export function delCoilStatisticsSummary(summaryId) {
method: 'delete'
})
}
// 检查今天是否已经创建过该类型的透视表
// 如果已经创建过返回该透视表的id
// 如果没有创建过返回null
export function checkCoilStatisticsSummaryExist(statType) {
return request({
url: '/wms/coilStatisticsSummary/checkToday',
method: 'get',
params: {
statType
}
})
}

View File

@@ -1,75 +0,0 @@
import request from '@/utils/request'
// 查询员工异动(入职/离职)列表
export function listEmployeeChange(query) {
return request({
url: '/wms/employeeChange/list',
method: 'get',
params: query
})
}
// 查询员工异动(入职/离职)详细
export function getEmployeeChange(changeId) {
return request({
url: '/wms/employeeChange/' + changeId,
method: 'get'
})
}
// 新增员工异动(入职/离职)
export function addEmployeeChange(data) {
return request({
url: '/wms/employeeChange',
method: 'post',
data: data
})
}
// 修改员工异动(入职/离职)
export function updateEmployeeChange(data) {
return request({
url: '/wms/employeeChange',
method: 'put',
data: data
})
}
// 删除员工异动(入职/离职)
export function delEmployeeChange(changeId) {
return request({
url: '/wms/employeeChange/' + changeId,
method: 'delete'
})
}
// 员工入职
export function employeeEntry(data) {
return request({
url: '/wms/employeeChange/entry',
method: 'post',
data: data
})
}
/**
* 员工离职
*/
export function employeeLeave(data) {
return request({
url: '/wms/employeeChange/leave',
method: 'post',
data: data
})
}
/**
* 员工转正
*/
export function employeeRegular(data) {
return request({
url: '/wms/employeeChange/regular',
method: 'post',
data: data
})
}

View File

@@ -1,53 +0,0 @@
import request from '@/utils/request'
// 查询员工转岗记录列表
export function listEmployeeTransfer(query) {
return request({
url: '/wms/employeeTransfer/list',
method: 'get',
params: query
})
}
// 查询员工转岗记录详细
export function getEmployeeTransfer(transferId) {
return request({
url: '/wms/employeeTransfer/' + transferId,
method: 'get'
})
}
// 新增员工转岗记录
export function addEmployeeTransfer(data) {
return request({
url: '/wms/employeeTransfer',
method: 'post',
data: data
})
}
// 修改员工转岗记录
export function updateEmployeeTransfer(data) {
return request({
url: '/wms/employeeTransfer',
method: 'put',
data: data
})
}
// 删除员工转岗记录
export function delEmployeeTransfer(transferId) {
return request({
url: '/wms/employeeTransfer/' + transferId,
method: 'delete'
})
}
// 员工转岗
export function transferEmployee(data) {
return request({
url: '/wms/employeeTransfer/transfer',
method: 'post',
data: data
})
}

View File

@@ -1,55 +1,11 @@
import request from '@/utils/request'
function parseDate(date) {
// 修复1参数名和内部变量名冲突改用tempDate
// 修复2如果传入的date为空/无效,默认使用当前时间
const tempDate = date ? new Date(date) : new Date();
// 获取年、月、日、时、分、秒(补零处理,确保是两位数)
const year = tempDate.getFullYear();
// 月份从0开始所以要+1不足两位补0
const month = String(tempDate.getMonth() + 1).padStart(2, '0');
const day = String(tempDate.getDate()).padStart(2, '0');
const hours = String(tempDate.getHours()).padStart(2, '0');
const minutes = String(tempDate.getMinutes()).padStart(2, '0');
const seconds = String(tempDate.getSeconds()).padStart(2, '0');
// 格式化为YYYY-mm-dd HH:mm:ss并返回
return `${year}-${month}-${day} ${hours}:${minutes}:${seconds}`;
}
// 查询钢卷待操作列表
export function listPendingAction(query) {
return request({
url: '/wms/coilPendingAction/list',
method: 'get',
params: query,
timeout: 600000
})
}
// 查询钢卷待操作列表(包含已删除记录)
// includeDeleted: 0=不包含已删除(默认), 1=包含已删除记录, 2=仅查询已删除记录
export function listPendingActionWithDeleted(query) {
return request({
url: '/wms/coilPendingAction/list',
method: 'get',
params: {
...query,
includeDeleted: 1
}
})
}
// 仅查询已删除的钢卷待操作列表
export function listDeletedPendingAction(query) {
return request({
url: '/wms/coilPendingAction/list',
method: 'get',
params: {
...query,
includeDeleted: 2
}
params: query
})
}
@@ -63,37 +19,19 @@ export function getPendingAction(actionId) {
// 新增钢卷待操作
export function addPendingAction(data) {
const payload = { ...data }
if (payload.processTime) {
payload.processTime = parseDate(payload.processTime)
}
if (payload.completeTime) {
payload.completeTime = parseDate(payload.completeTime)
}
return request({
url: '/wms/coilPendingAction',
method: 'post',
data: payload
data: data
})
}
// 修改钢卷待操作
export function updatePendingAction(data) {
const payload = { ...data }
if (payload.processTime) {
payload.processTime = parseDate(payload.processTime)
}
if (payload.completeTime) {
payload.completeTime = parseDate(payload.completeTime)
}
if (payload.scanTime) {
// 扫码日期格式化为yyyy-MM-dd'T'HH:mm:ss.SSSX
payload.scanTime = parseDate(payload.scanTime).replace(' ', 'T') + '.000Z'
}
return request({
url: '/wms/coilPendingAction',
method: 'put',
data: payload
data: data
})
}
@@ -146,12 +84,3 @@ export function exportPendingAction(query) {
})
}
/**
* 还原被删除的钢卷
*/
export function restorePendingAction(actionId) {
return request({
url: `/wms/coilPendingAction/restore/${actionId}`,
method: 'put'
})
}

View File

@@ -1,97 +0,0 @@
import request from '@/utils/request'
// 用印申请
export function listSealReq(query) {
return request({
url: '/wms/seal/list',
method: 'get',
params: query
})
}
export function getSealReq(bizId) {
return request({
url: `/wms/seal/${bizId}`,
method: 'get'
})
}
export function addSealReq(data) {
return request({
url: '/wms/seal',
method: 'post',
data
})
}
export function editSealReq(data) {
return request({
url: '/wms/seal',
method: 'put',
data
})
}
export function delSealReq(bizIds) {
return request({
url: `/wms/seal/${bizIds}`,
method: 'delete'
})
}
export function approveSealReq(bizId, approvalOpinion) {
return request({
url: `/wms/seal/${bizId}/approve`,
method: 'post',
params: { approvalOpinion }
})
}
export function rejectSealReq(bizId, approvalOpinion) {
return request({
url: `/wms/seal/${bizId}/reject`,
method: 'post',
params: { approvalOpinion }
})
}
export function cancelSealReq(bizId) {
return request({
url: `/wms/seal/${bizId}/cancel`,
method: 'post'
})
}
export function stampSealJava(bizId, data) {
const payload = {
targetFileUrl: String(data.targetFileUrl || ''),
stampImageUrl: String(data.stampImageUrl || ''),
pageNo: Number(data.pageNo) || 1,
xPx: Number(data.xPx) || 0,
yPx: Number(data.yPx) || 0,
viewportWidth: data.viewportWidth !== undefined && data.viewportWidth !== null ? Number(data.viewportWidth) : undefined,
viewportHeight: data.viewportHeight !== undefined && data.viewportHeight !== null ? Number(data.viewportHeight) : undefined
}
if (data.widthPx !== undefined && data.widthPx !== null) {
payload.widthPx = Number(data.widthPx)
}
if (data.heightPx !== undefined && data.heightPx !== null) {
payload.heightPx = Number(data.heightPx)
}
if (payload.viewportWidth === undefined) delete payload.viewportWidth
if (payload.viewportHeight === undefined) delete payload.viewportHeight
return request({
url: `/wms/seal/${bizId}/stamp/java`,
method: 'post',
data: payload
})
}
export function stampSealPython(bizId, data) {
return request({
url: `/wms/seal/${bizId}/stamp/python`,
method: 'post',
data
})
}

View File

@@ -1 +0,0 @@
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1773478276971" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="13723" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><path d="M630.568421 619.789474h-239.831579c-10.778947 0-18.863158 8.084211-18.863158 18.863158s8.084211 18.863158 18.863158 18.863157h239.831579c10.778947 0 18.863158-8.084211 18.863158-18.863157s-8.084211-18.863158-18.863158-18.863158z" fill="#101010" p-id="13724"></path><path d="M986.273684 714.105263l-78.147368-164.378947c-5.389474-13.473684-18.863158-21.557895-32.336842-21.557895H808.421053V115.873684c0-16.168421-13.473684-29.642105-29.642106-29.642105h-99.705263c-18.863158 0-32.336842 13.473684-32.336842 32.336842v24.252632h-48.505263v-24.252632c0-18.863158-13.473684-32.336842-32.336842-32.336842h-102.4c-18.863158 0-32.336842 13.473684-32.336842 32.336842v24.252632h-48.505263v-24.252632c0-18.863158-13.473684-32.336842-32.336843-32.336842H247.915789c-16.168421 0-29.642105 13.473684-29.642105 29.642105v414.989474H150.905263c-13.473684 0-26.947368 8.084211-32.336842 21.557895l-78.147368 164.378947c-2.694737 5.389474-2.694737 10.778947-2.694737 16.168421v177.852632c0 16.168421 13.473684 29.642105 29.642105 29.642105h164.378947c10.778947 0 21.557895-5.389474 29.642106-13.473684l37.726315-45.810527h431.157895l37.726316 48.505264c8.084211 8.084211 16.168421 13.473684 29.642105 13.473684h164.378948c16.168421 0 29.642105-13.473684 29.642105-29.642106v-177.852631c-2.694737-10.778947-2.694737-16.168421-5.389474-21.557895zM512 840.757895h-158.989474l21.557895-88.926316h269.473684l24.252632 88.926316H512z m137.431579-129.347369H369.178947l-97.010526-142.821052H749.136842l-99.705263 142.821052zM256 123.957895h86.231579v24.252631c0 18.863158 13.473684 32.336842 32.336842 32.336842h59.284211c18.863158 0 32.336842-13.473684 32.336842-32.336842v-24.252631h91.621052v24.252631c0 18.863158 13.473684 32.336842 32.336842 32.336842h59.284211c18.863158 0 32.336842-13.473684 32.336842-32.336842v-24.252631h86.231579v406.905263h-512V123.957895z m8.084211 730.273684l-37.726316 45.810526H72.757895v-169.768421l78.147368-161.68421h75.452632l113.178947 167.073684-26.947368 105.094737h-18.863158c-10.778947 0-21.557895 2.694737-29.642105 13.473684z m687.157894 45.810526h-153.6l-37.726316-48.505263c-8.084211-8.084211-16.168421-13.473684-29.642105-13.473684h-24.252631l-29.642106-105.094737 118.568421-167.073684h78.147369l78.147368 164.378947v169.768421z" fill="#101010" p-id="13725"></path><path d="M563.2 476.968421c24.252632-21.557895 32.336842-53.894737 29.642105-78.147368-2.694737-13.473684-8.084211-21.557895-18.863158-24.252632-8.084211-2.694737-16.168421 0-21.557894 2.694737l-2.694737 2.694737c-2.694737 2.694737-8.084211 8.084211-10.778948 8.08421 0 0 0-2.694737 2.694737-8.08421l2.694737-2.694737c5.389474-10.778947 18.863158-48.505263-32.336842-75.452632-5.389474-2.694737-10.778947-2.694737-16.168421 0-5.389474 2.694737-8.084211 8.084211-10.778947 13.473685 0 5.389474-2.694737 13.473684-2.694737 16.168421-2.694737 2.694737-10.778947 13.473684-16.168421 21.557894-10.778947 10.778947-18.863158 21.557895-24.252632 29.642106-8.084211 10.778947-10.778947 40.421053 0 64.673684 5.389474 16.168421 21.557895 35.031579 53.894737 43.115789 8.084211 2.694737 13.473684 2.694737 18.863158 2.694737 16.168421 2.694737 35.031579-2.694737 48.505263-16.168421z m-61.978947-18.863158c-13.473684-2.694737-21.557895-10.778947-26.947369-18.863158-5.389474-13.473684-2.694737-29.642105 0-32.336842 2.694737-5.389474 13.473684-16.168421 18.863158-24.252631 10.778947-13.473684 16.168421-18.863158 18.863158-24.252632 0 2.694737 0 5.389474-2.694737 8.084211v2.694736c-5.389474 10.778947-10.778947 21.557895-5.389474 40.421053 0 2.694737 2.694737 2.694737 2.694737 5.389474 10.778947 13.473684 24.252632 18.863158 32.336842 18.863158 5.389474 0 10.778947 0 13.473685-2.694737-2.694737 8.084211-8.084211 16.168421-13.473685 21.557894-10.778947 5.389474-24.252632 8.084211-37.726315 5.389474z" fill="#101010" p-id="13726"></path><path d="M417.684211 458.105263h-37.726316v-185.936842h264.08421V458.105263h-37.726316c-10.778947 0-18.863158 8.084211-18.863157 18.863158s8.084211 18.863158 18.863157 18.863158H646.736842c18.863158 0 35.031579-16.168421 35.031579-35.031579V269.473684c0-18.863158-16.168421-35.031579-35.031579-35.031579H377.263158c-18.863158 0-35.031579 16.168421-35.031579 35.031579v194.021053c0 18.863158 16.168421 35.031579 35.031579 35.031579h40.421053c10.778947 0 18.863158-8.084211 18.863157-18.863158s-8.084211-21.557895-18.863157-21.557895z" fill="#101010" p-id="13727"></path></svg>

Before

Width:  |  Height:  |  Size: 4.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 19 KiB

After

Width:  |  Height:  |  Size: 3.6 KiB

View File

@@ -18,9 +18,9 @@
</div>
<el-dialog title="选择钢卷" :visible.sync="dialogVisible" :width="dialogWidth" :close-on-click-modal="false"
@close="handleClose" append-to-body :fullscreen="orderBy">
@close="handleClose" append-to-body>
<!-- 搜索区域 -->
<el-form v-if="!rangeMode" inline :model="queryParams" class="search-form">
<el-form v-if="!rangeMode" :model="queryParams" class="search-form">
<!-- <el-form-item label="类型">
<el-select v-model="queryParams.selectType" placeholder="请选择类型" size="small">
<el-option label="成品" value="product" />
@@ -62,13 +62,12 @@
</el-form-item>
<el-form-item label="实际库区" v-if="orderBy">
<actual-warehouse-select v-model="queryParams.actualWarehouseId" placeholder="请选择实际库区" canSelectLevel2
canSelectDisabled :clearInput="false" clearable />
canSelectDisabled />
</el-form-item>
<el-form-item>
<el-button type="primary" icon="el-icon-search" size="small" @click="handleQuery">搜索</el-button>
<el-button icon="el-icon-refresh" size="small" @click="resetQuery">重置</el-button>
<el-checkbox v-if="orderBy" style="margin-left: 10px;" v-model="showCoilMap" size="small">显示钢卷地图</el-checkbox>
</el-form-item>
</el-form>
@@ -78,7 +77,6 @@
<!-- 自定义列 -->
<el-table-column v-for="column in renderColumns" :label="column.label" :align="column.align" :prop="column.prop"
:width="column.width" :show-overflow-tooltip="column.showOverflowTooltip" />
<el-table-column v-if="orderBy" label="品质" prop="qualityStatus"></el-table-column>
<el-table-column v-if="orderBy" label="切边" prop="trimmingRequirement"></el-table-column>
<el-table-column v-if="orderBy" label="包装" prop="packagingRequirement"></el-table-column>
</el-table>
@@ -95,7 +93,6 @@
<!-- 自定义列 -->
<el-table-column v-for="column in renderColumns" :label="column.label" :align="column.align"
:prop="column.prop" :width="column.width" :show-overflow-tooltip="column.showOverflowTooltip" />
<el-table-column v-if="orderBy" label="品质" prop="qualityStatus"></el-table-column>
<el-table-column v-if="orderBy" label="切边" prop="trimmingRequirement"></el-table-column>
<el-table-column v-if="orderBy" label="包装" prop="packagingRequirement"></el-table-column>
</el-table>
@@ -108,7 +105,6 @@
<el-table v-if="multiple && selectedCoils.length > 0" :data="selectedCoils">
<el-table-column v-for="column in renderColumns" :label="column.label" :align="column.align" :prop="column.prop"
:width="column.width" :show-overflow-tooltip="column.showOverflowTooltip" />
<el-table-column v-if="orderBy" label="品质" prop="qualityStatus"></el-table-column>
<el-table-column label="操作" width="50">
<template slot-scope="scope">
@@ -121,42 +117,23 @@
<el-button type="primary" @click="handleConfirm">确认选择</el-button>
<el-button @click="handleClose">取消</el-button>
</div>
<!-- 一个可以拖拽和调节大小的浮层 -->
<DragResizeBox v-if="showCoilMap" @size-change="handleSizeChange" storageKey="coil-map">
<div style="height: 100%; width: 100%; overflow-y: scroll; display: flex; background-color: #fff;">
<div style="min-width: 150px; position: sticky; top: 0;" v-loading="treeLoading">
<el-tree ref="warehouseTreeRef" :data="warehouseTree" :props="treeProps" node-key="actualWarehouseId"
@node-click="handleNodeClick" :expand-on-click-node="false" highlight-current class="warehouse-tree">
</el-tree>
</div>
<warehouse-bird-mini ref="warehouseBirdMini" v-loading="warehouseLoading" :warehouseList="warehouseList" :id="selectedNodeId"
:canToggle="false" :canRelease="false" />
</div>
</DragResizeBox>
</el-dialog>
</div>
</template>
<script>
import { listMaterialCoil } from '@/api/wms/coil';
import { listActualWarehouse } from "@/api/wms/actualWarehouse";
import { treeActualWarehouseTwoLevel } from "@/api/wms/actualWarehouse";
import MemoInput from '@/components/MemoInput/index.vue';
import MutiSelect from '@/components/MutiSelect/index.vue';
import { defaultColumns } from './data';
import ActualWarehouseSelect from '@/components/KLPService/ActualWarehouseSelect/index.vue';
import WarehouseBirdMini from '@/views/wms/warehouse/components/WarehouseBirdMini.vue';
import DragResizeBox from '@/components/DragResizeBox/index.vue';
export default {
name: 'CoilSelector',
components: {
MemoInput,
MutiSelect,
ActualWarehouseSelect,
WarehouseBirdMini,
DragResizeBox
ActualWarehouseSelect
},
dicts: ['coil_itemname', 'coil_material', 'coil_manufacturer'],
props: {
@@ -219,16 +196,10 @@ export default {
multiple: {
type: Boolean,
default: false
},
// 是否禁用O卷
disableO: {
type: Boolean,
default: false
},
}
},
data() {
return {
showCoilMap: false,
loading: false,
coilList: [],
total: 0,
@@ -252,12 +223,6 @@ export default {
columns: defaultColumns,
currentTab: 'my',
selectedCoils: [],
warehouseList: [],
selectedNodeId: null,
warehouseLoading: false,
warehouseTree: [],
treeProps: { label: "actualWarehouseName", children: "children" },
treeLoading: false,
};
},
computed: {
@@ -339,56 +304,8 @@ export default {
if (this.initialCoil) {
this.selectedCoil = this.initialCoil;
}
if (this.orderBy) {
this.getWarehouseTree();
}
},
methods: {
// 获取库位列表
getWarehouseList(parentId) {
this.warehouseLoading = true;
return listActualWarehouse({ parentId })
.then((res) => { this.warehouseList = res.data || []; this.warehouseLoading = false; })
.catch((err) => {
this.$message.error("获取库位数据失败:" + err.message);
this.warehouseList = [];
this.warehouseLoading = false;
});
},
handleNodeClick(data) {
console.log('data', data);
if (data.actualWarehouseType != 2) {
return;
}
this.selectedNodeId = data.actualWarehouseId;
this.getWarehouseList(data.actualWarehouseId);
},
// 获取树形数据
getWarehouseTree() {
this.treeLoading = true;
treeActualWarehouseTwoLevel()
.then((res) => { this.warehouseTree = res.data || []; })
.catch((err) => { this.$message.error("获取仓库树形数据失败:" + err.message); })
.finally(() => { this.treeLoading = false; });
},
// 处理大小变化
handleSizeChange(size) {
console.log('size', size);
this.$refs.warehouseBirdMini.resize();
},
// 处理实际库区选择变化
handleWarehouseChange(val) {
console.log('val', val);
if (!val) {
this.selectedNodeId = null;
this.warehouseList = [];
return;
}
if (val.pathIds.length == 2) {
this.selectedNodeId = val;
this.getWarehouseList(val.id);
}
},
// 动态生成表格行类名 - 综合处理选中、禁用等状态
getRowClassName({ row }) {
const classNames = [];
@@ -494,10 +411,6 @@ export default {
this.$message.warning('您没有权限选择此钢卷');
return; // 终止后续逻辑
}
if (this.disableO && row.qualityStatus == 'O') {
this.$message.warning('O卷不能选择');
return;
}
this.handleSelect(row);
},
@@ -626,6 +539,13 @@ export default {
margin-top: 4px;
}
.search-form {
margin-bottom: 20px;
display: flex;
align-items: center;
gap: 16px;
}
::v-deep .el-dialog__body {
padding: 20px;
max-height: calc(100vh - 200px);

View File

@@ -1,301 +0,0 @@
<template>
<div class="drag-resize-container" ref="containerRef">
<!-- 可拖拽调整的元素 -->
<div
class="draggable-element"
ref="elementRef"
:style="{
left: `${position.x}px`,
top: `${position.y}px`,
width: `${size.width}px`,
height: `${size.height}px`
}"
@mousedown="startDrag"
>
<!-- 元素内容区 -->
<div class="element-content">
<slot>可拖拽调整的元素</slot>
</div>
<!-- 右下角调整大小的控制点 -->
<div class="resize-handle" @mousedown="startResize"></div>
</div>
</div>
</template>
<script>
export default {
name: 'DragResizeBox',
props: {
// 初始位置
initPosition: {
type: Object,
default: () => ({ x: 100, y: 100 })
},
// 初始尺寸
initSize: {
type: Object,
default: () => ({ width: 200, height: 150 })
},
// 移除容器尺寸限制保留prop但默认值改为屏幕尺寸
containerSize: {
type: Object,
default: () => ({
width: window.innerWidth,
height: window.innerHeight
})
},
// 元素最小尺寸
minSize: {
type: Object,
default: () => ({ width: 100, height: 80 })
},
// 用于localStorage存储的唯一标识
storageKey: {
type: String,
default: ''
}
},
data() {
return {
// 当前位置
position: { x: 0, y: 0 },
// 当前尺寸
size: { width: 0, height: 0 },
// 拖拽状态
isDragging: false,
// 调整大小状态
isResizing: false,
// 鼠标初始位置
startMouse: { x: 0, y: 0 },
// 元素初始状态
startState: { x: 0, y: 0, width: 0, height: 0 }
};
},
mounted() {
// 初始化位置和尺寸优先从localStorage读取
this.initFromStorage();
// 监听全局鼠标移动和松开事件
document.addEventListener('mousemove', this.handleMouseMove);
document.addEventListener('mouseup', this.handleMouseUp);
// 监听窗口大小变化,更新屏幕尺寸
window.addEventListener('resize', this.updateScreenSize);
},
beforeDestroy() {
// 移除全局事件监听,防止内存泄漏
document.removeEventListener('mousemove', this.handleMouseMove);
document.removeEventListener('mouseup', this.handleMouseUp);
window.removeEventListener('resize', this.updateScreenSize);
},
methods: {
/**
* 从localStorage初始化位置和尺寸
* 有key时优先读取存储值无则使用props传入的初始值
*/
initFromStorage() {
if (this.storageKey) {
try {
const storageKey = `dnd-ps-${this.storageKey}`;
const storedData = localStorage.getItem(storageKey);
if (storedData) {
const { position, size } = JSON.parse(storedData);
// 验证存储的数据是否合法,防止异常值
const isValidPosition = position && typeof position.x === 'number' && typeof position.y === 'number';
const isValidSize = size && typeof size.width === 'number' && typeof size.height === 'number';
if (isValidPosition && isValidSize) {
// 使用存储的位置和尺寸(确保不小于最小尺寸)
this.position = {
x: Math.max(0, position.x),
y: Math.max(0, position.y)
};
this.size = {
width: Math.max(this.minSize.width, size.width),
height: Math.max(this.minSize.height, size.height)
};
return;
}
}
} catch (error) {
console.warn('读取拖拽元素存储数据失败,使用默认值:', error);
}
}
// 无存储数据或存储异常时使用props初始值
this.position = { ...this.initPosition };
this.size = { ...this.initSize };
},
/**
* 将当前位置和尺寸保存到localStorage
*/
saveToStorage() {
if (this.storageKey) {
try {
const storageKey = `dnd-ps-${this.storageKey}`;
const saveData = {
position: { ...this.position },
size: { ...this.size },
updateTime: new Date().getTime()
};
console.log('saveData', saveData);
localStorage.setItem(storageKey, JSON.stringify(saveData));
// 触发存储成功事件
this.$emit('save-success', saveData);
} catch (error) {
console.error('保存拖拽元素数据失败:', error);
this.$emit('save-fail', error);
}
}
},
/**
* 更新屏幕尺寸(窗口大小变化时)
*/
updateScreenSize() {
this.containerSize = {
width: window.innerWidth,
height: window.innerHeight
};
},
/**
* 开始拖拽(移动位置)
*/
startDrag(e) {
// 阻止事件冒泡,避免和调整大小冲突
if (e.target.classList.contains('resize-handle')) return;
this.isDragging = true;
// 记录鼠标初始位置
this.startMouse = { x: e.clientX, y: e.clientY };
// 记录元素初始位置
this.startState = {
x: this.position.x,
y: this.position.y,
width: this.size.width,
height: this.size.height
};
// 更改鼠标样式
document.body.style.cursor = 'move';
},
/**
* 开始调整大小
*/
startResize(e) {
e.stopPropagation(); // 阻止事件冒泡
this.isResizing = true;
// 记录鼠标初始位置
this.startMouse = { x: e.clientX, y: e.clientY };
// 记录元素初始尺寸
this.startState = {
x: this.position.x,
y: this.position.y,
width: this.size.width,
height: this.size.height
};
// 更改鼠标样式
document.body.style.cursor = 'se-resize';
},
/**
* 处理鼠标移动
*/
handleMouseMove(e) {
if (this.isDragging) {
// 计算鼠标移动的偏移量
const dx = e.clientX - this.startMouse.x;
const dy = e.clientY - this.startMouse.y;
// 核心修改:移除容器边界限制,仅限制不超出屏幕左侧/顶部(右侧/底部可任意移动)
this.position.x = Math.max(0, this.startState.x + dx);
this.position.y = Math.max(0, this.startState.y + dy);
// 触发位置变化事件
this.$emit('position-change', { ...this.position });
}
if (this.isResizing) {
// 计算鼠标移动的偏移量
const dx = e.clientX - this.startMouse.x;
const dy = e.clientY - this.startMouse.y;
// 调整大小仅限制最小尺寸,不限制屏幕边界
this.size.width = Math.max(this.minSize.width, this.startState.width + dx);
this.size.height = Math.max(this.minSize.height, this.startState.height + dy);
// 触发尺寸变化事件
this.$emit('size-change', { ...this.size });
}
},
/**
* 处理鼠标松开
*/
handleMouseUp() {
// 重置状态
this.isDragging = false;
this.isResizing = false;
// 恢复鼠标样式
document.body.style.cursor = 'default';
// 保存当前状态到localStorage有key时
this.saveToStorage();
// 触发结束事件
this.$emit('drag-end', { position: { ...this.position }, size: { ...this.size } });
}
}
};
</script>
<style scoped>
/* 容器样式:改为全屏且无视觉样式 */
.drag-resize-container {
position: fixed; /* 固定定位覆盖整个屏幕 */
top: 0;
left: 0;
width: 100vw;
height: 100vh;
pointer-events: none; /* 容器不拦截鼠标事件,不影响页面其他元素 */
box-sizing: border-box;
}
/* 可拖拽元素样式fixed定位确保基于屏幕移动 */
.draggable-element {
position: fixed;
border-radius: 4px;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
user-select: none; /* 禁止文本选中 */
box-sizing: border-box;
overflow: hidden;
pointer-events: auto; /* 元素本身响应鼠标事件 */
z-index: 9999; /* 确保元素在最上层 */
background-color: #ffffff; /* 添加背景色,提升可视性 */
}
/* 元素内容区 */
.element-content {
height: calc(100% - 20px);
padding: 10px;
cursor: move;
}
/* 调整大小控制点 */
.resize-handle {
position: absolute;
right: 0;
bottom: 0;
width: 20px;
height: 20px;
background-color: #1e88e5;
cursor: se-resize;
border-top-left-radius: 4px;
}
/* 控制点hover效果 */
.resize-handle:hover {
background-color: #1976d2;
}
</style>

View File

@@ -1,40 +1,73 @@
<template>
<div class="employee-selector">
<!-- 触发器 -->
<div class="trigger" @click="toggleDialog">
<div
class="trigger"
@click="toggleDialog"
>
<slot name="trigger">
<div class="default-trigger">
{{ displayText }}
</div>
</slot>
</div>
<!-- 选择对话框 -->
<el-dialog :title="title" :visible.sync="open" width="600px" append-to-body>
<el-input v-model="searchQuery" placeholder="请输入员工姓名或部门" clearable @keyup.enter.native="handleSearch">
<el-button slot="append" icon="el-icon-search" @click="handleSearch" />
<el-dialog
:title="title"
:visible.sync="open"
width="600px"
append-to-body
>
<el-input
v-model="searchQuery"
placeholder="请输入员工姓名或部门"
clearable
@keyup.enter.native="handleSearch"
>
<el-button
slot="append"
icon="el-icon-search"
@click="handleSearch"
/>
</el-input>
<!-- 已选员工列表多选模式 -->
<div v-if="multiple && selectedEmployees.length > 0" class="selected-list">
<div class="selected-list-title">已选员工 ({{ selectedEmployees.length }})</div>
<el-tag v-for="employee in selectedEmployees" :key="employee[keyField]" closable
@close="removeSelectedEmployee(employee)" class="selected-tag">
<el-tag
v-for="employee in selectedEmployees"
:key="employee[keyField]"
closable
@close="removeSelectedEmployee(employee)"
class="selected-tag"
>
{{ employee.name }} ({{ employee.dept }})
</el-tag>
</div>
<el-table v-loading="loading" :data="employeeList" style="width: 100%" height="400px" @row-click="handleRowClick"
:row-class-name="rowClassName">
<el-table
v-loading="loading"
:data="employeeList"
style="width: 100%"
height="400px"
@row-click="handleRowClick"
:row-class-name="rowClassName"
>
<el-table-column label="部门" align="center" prop="dept" />
<el-table-column label="姓名" align="center" prop="name" />
<el-table-column label="岗位工种" align="center" prop="jobType" />
<el-table-column label="联系电话" align="center" prop="phone" />
</el-table>
<div slot="footer" class="dialog-footer">
<el-button @click="cancelSelection">取消</el-button>
<el-button v-if="multiple" type="primary" @click="confirmSelection" :disabled="selectedEmployees.length === 0">
<el-button @click="open = false">取消</el-button>
<el-button
v-if="multiple"
type="primary"
@click="confirmSelection"
:disabled="selectedEmployees.length === 0"
>
确认选择 ({{ selectedEmployees.length }})
</el-button>
</div>
@@ -105,9 +138,7 @@ export default {
},
// 处理后的员工列表,包含禁用状态
employeeList() {
return this.rawEmployeeList.filter(employee => {
return employee.name?.includes(this.searchQuery) || employee.dept?.includes(this.searchQuery)
}).map(employee => ({
return this.rawEmployeeList.map(employee => ({
...employee,
disabled: this.disabledIdList.includes(employee.infoId.toString())
}))
@@ -142,20 +173,19 @@ export default {
}
this.open = !this.open
},
// 获取员工列表
getEmployeeList() {
this.loading = true
const params = {
pageNum: 1,
pageSize: 9999,
// name: this.searchQuery || undefined,
// dept: this.searchQuery || undefined
name: this.searchQuery || undefined,
dept: this.searchQuery || undefined
}
return new Promise((resolve) => {
listEmployeeInfo(params).then(response => {
// 前端筛选员工列表,根据姓名或部门
this.rawEmployeeList = response.rows;
this.rawEmployeeList = response.rows
this.loading = false
resolve()
}).catch(() => {
@@ -164,19 +194,19 @@ export default {
})
})
},
// 搜索员工
handleSearch() {
this.getEmployeeList()
},
// 选择员工
handleRowClick(row) {
// 检查员工是否被禁用
if (this.isDisabled(row)) {
return
}
if (this.multiple) {
// 多选模式:添加或移除员工
const index = this.selectedEmployees.findIndex(item => item[this.keyField] === row[this.keyField])
@@ -193,7 +223,7 @@ export default {
this.open = false
}
},
// 检查员工是否被禁用
isDisabled(row) {
const isDisabled = this.disabledIdList.includes(row[this.keyField].toString())
@@ -202,7 +232,7 @@ export default {
}
return isDisabled
},
// 确认选择(多选模式)
confirmSelection() {
const selectedIds = this.selectedEmployees.map(item => item[this.keyField])
@@ -210,25 +240,7 @@ export default {
this.$emit('change', this.selectedEmployees)
this.open = false
},
cancelSelection() {
if (this.multiple) {
if (this.value) {
// 多选模式:根据逗号分隔的字符串查找已选员工
this.findSelectedEmployees(this.value.split(','))
} else {
this.selectedEmployees = []
}
} else {
if (this.value) {
this.findSelectedEmployee(this.value)
} else {
this.selectedEmployee = {}
}
}
this.open = false
},
// 移除已选员工
removeSelectedEmployee(employee) {
const index = this.selectedEmployees.findIndex(item => item[this.keyField] === employee[this.keyField])
@@ -236,7 +248,7 @@ export default {
this.selectedEmployees.splice(index, 1)
}
},
// 表格行样式
rowClassName({ row }) {
// if (this.isDisabled(row)) {
@@ -247,7 +259,7 @@ export default {
}
return ''
},
// 检查员工是否已选
isSelected(row) {
if (this.multiple) {
@@ -256,7 +268,7 @@ export default {
return this.selectedEmployee[this.keyField] === row[this.keyField]
}
},
// 根据value查找选中的员工单选
findSelectedEmployee(value) {
if (this.employeeList.length > 0) {
@@ -274,18 +286,15 @@ export default {
})
}
},
// 根据value查找选中的员工多选
findSelectedEmployees(values) {
if (this.employeeList.length > 0) {
this.selectedEmployees = this.employeeList.filter(item => values.includes(item[this.keyField]))
// 因为员工可能出现重名所以需要去重保证selectedEmployees[i][this.keyField]是唯一的
this.selectedEmployees = this.selectedEmployees.filter((item, index, arr) => arr.findIndex(t => t[this.keyField] === item[this.keyField]) === index)
} else {
// 如果员工列表为空,先获取列表再查找
this.getEmployeeList().then(() => {
this.selectedEmployees = this.employeeList.filter(item => values.includes(item[this.keyField]))
this.selectedEmployees = this.selectedEmployees.filter((item, index, arr) => arr.findIndex(t => t[this.keyField] === item[this.keyField]) === index)
})
}
}

View File

@@ -1,119 +0,0 @@
<template>
<div class="file-list-container">
<el-table
:data="fileList"
border
size="small"
v-loading="loading"
style="width: 100%;"
>
<el-table-column
label="文件名"
prop="originalName"
min-width="200"
>
<template slot-scope="scope">
<i class="el-icon-document" style="margin-right: 8px;"></i>
{{ scope.row.originalName }}
</template>
</el-table-column>
<el-table-column
label="操作"
width="100"
align="center"
>
<template slot-scope="scope">
<el-button
type="text"
icon="el-icon-download"
@click="downloadFile(scope.row)"
size="small"
>
下载
</el-button>
</template>
</el-table-column>
</el-table>
<!-- 空数据提示 -->
<div v-if="fileList.length === 0 && !loading" class="empty-tip">
<el-empty description="暂无文件数据"></el-empty>
</div>
</div>
</template>
<script>
import { listByIds } from "@/api/system/oss";
export default {
name: "FileList",
props: {
ossIds: {
type: String,
default: '',
},
},
data() {
return {
fileList: [],
loading: false // 加载状态
}
},
watch: {
ossIds: {
handler(val) {
if (val) {
this.getFileList();
} else {
this.fileList = []; // 清空文件列表
}
},
immediate: true,
}
},
methods: {
async getFileList() {
if (!this.ossIds) return;
this.loading = true;
try {
let res = await listByIds(this.ossIds);
this.fileList = res.data || [];
} catch (error) {
this.$message.error('获取文件列表失败:' + (error.message || '未知错误'));
this.fileList = [];
} finally {
this.loading = false;
}
},
// 文件下载方法
downloadFile(file) {
if (!file || !file.ossId) {
this.$message.warning('文件下载地址不存在');
return;
}
this.$download.oss(file.ossId);
}
}
}
</script>
<style scoped>
.file-list-container {
width: 100%;
min-height: 100px;
}
.empty-tip {
display: flex;
justify-content: center;
align-items: center;
height: 200px;
}
::v-deep .el-table {
--el-table-header-text-color: #606266;
--el-table-row-hover-bg-color: #f5f7fa;
}
</style>

View File

@@ -66,7 +66,7 @@ export default {
// 文件类型, 例如['png', 'jpg', 'jpeg']
fileType: {
type: Array,
default: () => ["doc", "docx", "xls", "xlsx", "ppt", "txt", "pdf", 'png', 'jpg', 'jpeg', 'bmp', 'webp'],
default: () => ["doc", "xls", "xlsx", "ppt", "txt", "pdf", 'png', 'jpg', 'jpeg', 'bmp', 'webp'],
},
// 是否显示提示
isShowTip: {

View File

@@ -30,18 +30,6 @@
<span class="label">净重</span>
<span class="value">{{ netWeight }}</span>
</div>
<div class="info-item" v-if="length">
<span class="label">长度</span>
<span class="value">{{ length }}</span>
</div>
<div class="info-item" v-if="actualLength">
<span class="label">实测长度</span>
<span class="value">{{ actualLength }}</span>
</div>
<div class="info-item" v-if="actualWidth">
<span class="label">实测宽度</span>
<span class="value">{{ actualWidth }}</span>
</div>
<div class="info-item">
<span class="label">厂家卷号</span>
<span class="value">{{ supplierCoilNo }}</span>
@@ -99,16 +87,7 @@ export default {
},
supplierCoilNo() {
return this.coilInfo.supplierCoilNo || '-'
},
length() {
return this.coilInfo.length || '-'
},
actualLength() {
return this.coilInfo.actualLength || '-'
},
actualWidth() {
return this.coilInfo.actualWidth || '-'
},
}
},
methods: {
getCoilInfo() {

View File

@@ -9,7 +9,7 @@
<div class="el-table-container" ref="elTableWrapper" @mouseleave="handleTableLeave">
<!-- 原生 Table 核心透传 props/事件/插槽 -->
<el-table :ref="tableRef" v-bind="$attrs" v-on="$listeners" :class="['my-table', customClass]"
@cell-mouse-enter="handleCellEnter" @row-mouseleave="handleRowLeave" :height="height">
@cell-mouse-enter="handleCellEnter" @row-mouseleave="handleRowLeave">
<!-- 2. 透传原生内置插槽 empty 空数据插槽append 底部插槽等 -->
<template v-slot:empty="scope">
<slot name="empty" v-bind="scope"></slot>
@@ -80,10 +80,6 @@ export default {
columns: [],
title: '详细信息'
})
},
height: {
type: String,
default: ''
}
},
data() {

View File

@@ -1,86 +0,0 @@
<template>
<div class="time-input-group">
<el-date-picker v-model="dateValue" type="date" value-format="yyyy-MM-dd" placeholder="选择日期" style="width: 140px;" @change="updateDateTime" />
<span class="time-separator">@</span>
<el-input-number :controls="false" v-model="hourValue" placeholder="时" min="0" max="23" style="width: 60px;" @change="updateDateTime" />
<span class="time-separator">:</span>
<el-input-number :controls="false" v-model="minuteValue" placeholder="分" min="0" max="59" style="width: 60px;" @change="updateDateTime" />
<span class="time-separator">:00</span>
<el-button v-if="showNowButton" type="text" size="small" @click="setToNow" style="margin-left: 8px;">此刻</el-button>
</div>
</template>
<script>
export default {
name: 'TimeInput',
props: {
value: {
type: String,
default: ''
},
showNowButton: {
type: Boolean,
default: true
}
},
data() {
return {
dateValue: '',
hourValue: '',
minuteValue: ''
};
},
watch: {
value: {
handler(newValue) {
this.parseDateTime(newValue);
},
immediate: true
}
},
methods: {
parseDateTime(dateTimeStr) {
if (!dateTimeStr) {
this.dateValue = '';
this.hourValue = '';
this.minuteValue = '';
return;
}
const date = new Date(dateTimeStr);
if (!isNaN(date.getTime())) {
this.dateValue = date.toISOString().split('T')[0];
this.hourValue = date.getHours();
this.minuteValue = date.getMinutes();
}
},
updateDateTime() {
if (this.dateValue && this.hourValue !== '' && this.minuteValue !== '') {
const dateTimeStr = `${this.dateValue} ${this.hourValue}:${this.minuteValue}:00`;
this.$emit('input', dateTimeStr);
} else {
this.$emit('input', '');
}
},
setToNow() {
const now = new Date();
this.dateValue = now.toISOString().split('T')[0];
this.hourValue = now.getHours();
this.minuteValue = now.getMinutes();
this.updateDateTime();
}
}
};
</script>
<style scoped>
.time-input-group {
display: flex;
align-items: center;
gap: 8px;
}
.time-separator {
color: #909399;
font-size: 14px;
}
</style>

View File

@@ -35,7 +35,7 @@ export default {
},
data() {
return {
title: '科伦普一体化平台',
title: 'MES一体化平台',
logo: logoImg
}
}

View File

@@ -48,10 +48,6 @@ import KLPTable from '@/components/KLPUI/KLPTable/index.vue'
import MemoInput from '@/components/MemoInput/index.vue'
import CurrentCoilNo from '@/components/KLPService/Renderer/CurrentCoilNo.vue'
// 初始化所有列
import { initAllColumns } from '@/views/wms/report/js/column.js'
// 全局方法挂载
Vue.prototype.getDicts = getDicts
@@ -87,9 +83,6 @@ Vue.use(VueKonva);
Vue.use(dashboardBigPlugin)
DictData.install()
// 初始化所有列
initAllColumns()
/**
* If you don't want to use mock-server
* you want to use MockJs for mock api

View File

@@ -125,19 +125,6 @@ export const constantRoutes = [
meta: { title: '工厂总日历' }
}
]
},
{
path: '/wms/seal',
component: Layout,
hidden: true,
children: [
{
path: 'sealDetail/:bizId',
component: () => import('@/views/wms/seal/sealDetail'),
name: 'WmsSealDetail',
meta: { title: '用印详情' }
}
]
}
]

View File

@@ -1,748 +0,0 @@
<template>
<div class="aps-quick-sheet excel-theme">
<div class="sheet-toolbar">
<div class="sheet-actions">
<el-button size="small" icon="el-icon-refresh" @click="loadRows">刷新</el-button>
<el-button size="small" type="primary" icon="el-icon-finished" :loading="saving" @click="saveAll">保存</el-button>
<el-button size="small" icon="el-icon-plus" @click="addRow">新增行</el-button>
<el-button size="small" icon="el-icon-download" @click="exportCsv">导出</el-button>
<el-date-picker
v-model="filter.range"
type="daterange"
range-separator=""
start-placeholder="开始日期"
end-placeholder="结束日期"
value-format="yyyy-MM-dd"
size="small"
style="width: 240px"
@change="onFilterChange"
/>
</div>
<div class="preset-bar">
<span class="preset-title">预设方案</span>
<el-button
v-for="line in lineOptions"
:key="line.lineId"
size="mini"
plain
@click="applyPreset(line)"
>
{{ line.lineName || line.lineCode || ('产线' + line.lineId) }}
</el-button>
</div>
</div>
<div class="sheet-body">
<el-table
v-loading="loading"
:data="displayRows"
border
size="mini"
class="excel-table"
>
<el-table-column label="序号" width="60" align="center">
<template slot-scope="scope">
{{ ((pager.pageNum - 1) * pager.pageSize) + scope.$index + 1 }}
</template>
</el-table-column>
<el-table-column
v-for="col in flatColumns"
:key="col.prop || col.label"
:label="col.label"
:prop="col.prop"
:width="col.width"
:min-width="col.minWidth"
:align="col.align || 'center'"
:show-overflow-tooltip="col.showOverflowTooltip !== false"
>
<template slot-scope="scope">
<template v-if="col.prop === 'rawMaterialId'">
<el-button
size="mini"
type="text"
class="coil-picker-btn"
@click="openCoilPicker(scope.row)"
>
{{ scope.row.rawCoilNos || '选择原料钢卷' }}
</el-button>
</template>
<template v-else-if="isTimeField(col.prop)">
<el-date-picker
v-model="scope.row[col.prop]"
type="datetime"
value-format="yyyy-MM-dd HH:mm:ss"
size="mini"
style="width: 100%"
@change="onCellChange(scope.row)"
@input="onCellChange(scope.row)"
/>
</template>
<template v-else>
<el-autocomplete
v-if="col.prop === 'lineName'"
v-model="scope.row[col.prop]"
size="mini"
clearable
:fetch-suggestions="queryLineHistory"
@select="onLineHistorySelect(scope.row)"
@input="onLineHistoryInput(scope.row)"
/>
<el-input
v-else
v-model="scope.row[col.prop]"
size="mini"
clearable
@input="onCellChange(scope.row)"
/>
</template>
</template>
</el-table-column>
<el-table-column label="操作" width="70" fixed="right" align="center">
<template slot-scope="scope">
<el-button type="text" size="mini" @click="clearRow(scope.row)">清空</el-button>
</template>
</el-table-column>
</el-table>
<div class="pager-wrap">
<el-pagination
small
background
layout="prev, pager, next, total"
:current-page.sync="pager.pageNum"
:page-size="pager.pageSize"
:total="totalRows"
/>
</div>
</div>
<div v-if="missingRows.length" class="missing-tip">
<div v-for="msg in missingRows" :key="msg" class="missing-item">{{ msg }}</div>
</div>
<el-dialog title="选择原料钢卷" :visible.sync="coilPicker.visible" width="1280px" append-to-body>
<el-form :inline="true" size="small" class="coil-picker-form">
<el-form-item label="库区">
<el-select v-model="coilPicker.selectedWarehouseId" clearable filterable placeholder="全部库区" style="width: 220px" @change="onCoilFilterChange">
<el-option
v-for="w in coilPicker.warehouseOptions"
:key="w.actualWarehouseId"
:label="w.actualWarehouseName"
:value="w.actualWarehouseId"
/>
</el-select>
</el-form-item>
<el-form-item label="入场钢卷号">
<el-input v-model="coilPicker.enterCoilNo" clearable placeholder="支持模糊" style="width: 170px" @keyup.enter.native="searchCoils" />
</el-form-item>
<el-form-item label="当前钢卷号">
<el-input v-model="coilPicker.currentCoilNo" clearable placeholder="支持模糊" style="width: 170px" @keyup.enter.native="searchCoils" />
</el-form-item>
<el-form-item label="厂家">
<el-input v-model="coilPicker.manufacturer" clearable placeholder="支持模糊" style="width: 170px" @keyup.enter.native="searchCoils" />
</el-form-item>
<el-form-item>
<el-button size="mini" type="primary" @click="searchCoils">查询</el-button>
</el-form-item>
</el-form>
<div class="coil-stack-wrap" v-loading="coilPicker.loading">
<el-empty v-if="!coilColumnKeys.length" description="暂无可选原料钢卷" :image-size="60" />
<div v-else class="coil-bird-wrap">
<div class="coil-legend">
<span class="legend-item"><i class="dot layer1" />一层</span>
<span class="legend-item"><i class="dot layer2" />二层</span>
<span class="legend-item"><i class="dot occupied" />已占用</span>
</div>
<div class="coil-grid-scroll">
<div class="coil-col-ruler">
<div class="ruler-empty" />
<div v-for="col in coilColumnKeys" :key="`ruler-${col}`" class="ruler-col">{{ col }}</div>
</div>
<div class="coil-grid-main">
<div class="coil-row-ruler">
<div v-for="row in coilMaxRow" :key="`row-${row}`" class="ruler-row">{{ row }}</div>
</div>
<div class="coil-grid-columns">
<div v-for="col in coilColumnKeys" :key="`col-${col}`" class="coil-col-pair">
<div class="coil-layer">
<div
v-for="row in coilMaxRow"
:key="`l1-${col}-${row}`"
class="coil-cell layer1"
:class="{ occupied: !!(getCoilCell(col, row, 1) && getCoilCell(col, row, 1).rawMaterialId) }"
@click="selectCoilCell(col, row, 1)"
>
<template v-if="getCoilCell(col, row, 1)">
<div class="line1" :title="getCoilCell(col, row, 1).rawLocation">{{ getCoilCell(col, row, 1).rawLocation }}</div>
<div class="line2" :title="getCoilCell(col, row, 1).rawMaterialCode">{{ getCoilCell(col, row, 1).rawMaterialCode || '-' }}</div>
</template>
<template v-else>-</template>
</div>
</div>
<div class="coil-layer layer2-shift">
<div
v-for="row in (coilMaxRow - 1)"
:key="`l2-${col}-${row}`"
class="coil-cell layer2"
:class="{ occupied: !!(getCoilCell(col, row, 2) && getCoilCell(col, row, 2).rawMaterialId) }"
@click="selectCoilCell(col, row, 2)"
>
<template v-if="getCoilCell(col, row, 2)">
<div class="line1" :title="getCoilCell(col, row, 2).rawLocation">{{ getCoilCell(col, row, 2).rawLocation }}</div>
<div class="line2" :title="getCoilCell(col, row, 2).rawMaterialCode">{{ getCoilCell(col, row, 2).rawMaterialCode || '-' }}</div>
</template>
<template v-else>-</template>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<div class="coil-picker-foot"> {{ coilPicker.total || 0 }} 条钢卷按库位分布展示</div>
</el-dialog>
</div>
</template>
<script>
import { fetchQuickSheetList, fetchQuickSheetPreset, saveQuickSheet, exportQuickSheet, deleteQuickSheetRow } from '@/api/aps/quickSheet'
import { listProductionLine } from '@/api/wms/productionLine'
import { getMaterialCoilLocationGrid } from '@/api/wms/coil'
import { treeActualWarehouseTwoLevel } from '@/api/wms/actualWarehouse'
import { getTemplateByKey } from './sheets/templates'
import { saveAs } from 'file-saver'
export default {
name: 'ApsQuickSheet',
data() {
return {
loading: false,
saving: false,
rows: [],
lineOptions: [],
lineHistory: [],
dirtyIds: new Set(),
autoSaveTimer: null,
templateKey: 'unified',
filter: {
range: []
},
pager: {
pageNum: 1,
pageSize: 25
},
coilPicker: {
visible: false,
loading: false,
currentRow: null,
selectedWarehouseId: undefined,
enterCoilNo: '',
currentCoilNo: '',
manufacturer: '',
warehouseOptions: [],
coilGrid: {},
total: 0
}
}
},
computed: {
currentTemplate() {
return getTemplateByKey(this.templateKey)
},
flatColumns() {
const res = []
const loop = (cols) => {
;(cols || []).forEach(c => {
if (c.children && c.children.length) loop(c.children)
else res.push(c)
})
}
loop(this.currentTemplate.columns || [])
return res
},
displayRows() {
const [startAfter, endBefore] = this.filter.range || []
const filtered = this.rows.filter(r => {
if (!r.startTime) return true
const d = String(r.startTime).slice(0, 10)
if (startAfter && d < startAfter) return false
if (endBefore && d > endBefore) return false
return true
})
const start = (this.pager.pageNum - 1) * this.pager.pageSize
const page = filtered.slice(start, start + this.pager.pageSize)
if (page.length < this.pager.pageSize) {
return page.concat(this.buildEmptyRows(this.pager.pageSize - page.length))
}
return page
},
totalRows() {
const [startAfter, endBefore] = this.filter.range || []
return this.rows.filter(r => {
if (!r.startTime) return true
const d = String(r.startTime).slice(0, 10)
if (startAfter && d < startAfter) return false
if (endBefore && d > endBefore) return false
return true
}).length
},
missingRows() {
return this.collectMissing(this.displayRows)
},
coilColumnKeys() {
const keys = Object.keys(this.coilPicker.coilGrid || {})
return keys.map(v => Number(v)).filter(v => !Number.isNaN(v)).sort((a, b) => a - b)
},
coilMaxRow() {
let max = 0
Object.values(this.coilPicker.coilGrid || {}).forEach(col => {
const l1 = col.layer1 || []
const l2 = col.layer2 || []
;[...l1, ...l2].forEach(item => {
const row = Number(item._row || 0)
if (row > max) max = row
})
})
return max || 1
}
},
created() {
const today = new Date()
const start = `${today.getFullYear()}-${`${today.getMonth() + 1}`.padStart(2, '0')}-${`${today.getDate()}`.padStart(2, '0')}`
this.filter.range = [start, '']
this.loadRows()
this.loadLines()
},
beforeDestroy() {
if (this.autoSaveTimer) clearTimeout(this.autoSaveTimer)
},
methods: {
async loadLines() {
const res = await listProductionLine({ pageNum: 1, pageSize: 1000 })
this.lineOptions = res.rows || []
},
async loadRows() {
this.loading = true
try {
const res = await fetchQuickSheetList()
this.rows = (res.data || []).map(r => this.normalizeRow(r))
this.lineHistory = this.rows
.map(r => r.lineName)
.filter(v => v && v.trim())
.filter((v, i, arr) => arr.indexOf(v) === i)
} finally {
this.loading = false
}
},
buildEmptyRows(count) {
return Array.from({ length: count }).map(() => this.buildEmptyRow())
},
async applyPreset(line) {
if (!line || !line.lineId) return
const res = await fetchQuickSheetPreset({ lineId: line.lineId })
const preset = res.data || []
if (!preset.length) return
const targetRow = this.rows.find(r => !r.lineId && !r.lineName) || this.buildEmptyRow()
const base = this.normalizeRow(preset[0])
Object.keys(base).forEach(k => {
if (k === 'quickSheetId') return
targetRow[k] = base[k]
})
targetRow.lineId = line.lineId
targetRow.lineName = line.lineName || line.lineCode || ''
if (!this.rows.includes(targetRow)) {
this.rows.push(targetRow)
}
if (this.rows.length < 25) {
this.rows = this.rows.concat(this.buildEmptyRows(25 - this.rows.length))
}
this.markDirty(targetRow)
this.scheduleAutoSave()
},
async loadWarehouseOptions() {
const res = await treeActualWarehouseTwoLevel()
const tree = res.data || []
const leaf = []
tree.forEach(p => {
;(p.children || []).forEach(c => {
leaf.push({
actualWarehouseId: c.actualWarehouseId,
actualWarehouseName: `${p.actualWarehouseName || ''}/${c.actualWarehouseName || ''}`
})
})
})
this.coilPicker.warehouseOptions = leaf
},
async loadCoilsByWarehouse() {
if (!this.coilPicker.selectedWarehouseId) {
this.coilPicker.coilGrid = {}
this.coilPicker.total = 0
return
}
this.coilPicker.loading = true
try {
const gridRes = await getMaterialCoilLocationGrid({
itemType: 'raw_material',
actualWarehouseId: this.coilPicker.selectedWarehouseId,
enterCoilNo: this.coilPicker.enterCoilNo || undefined,
currentCoilNo: this.coilPicker.currentCoilNo || undefined,
manufacturer: this.coilPicker.manufacturer || undefined
})
const data = gridRes.data || {}
const warehouses = data.warehouses || []
const coils = (data.coils || []).map(item => ({
rawMaterialId: item.coilId,
rawMaterialCode: item.currentCoilNo || item.enterCoilNo || item.coilNo || '',
rawMaterialName: item.itemName || item.materialName || '',
specification: item.specification || '',
coilWeight: item.netWeight,
packageType: item.packagingRequirement || item.packageType,
edgeType: item.trimmingRequirement || item.edgeType,
zincLayer: item.coatingType || item.zincLayer,
surfaceTreatmentDesc: item.surfaceTreatmentDesc,
actualWarehouseId: item.actualWarehouseId,
actualWarehouseName: item.actualWarehouseName || '未分配库位',
rawLocation: item.actualWarehouseName || '未分配库位'
}))
this.coilPicker.total = coils.length
const coilByWarehouseId = {}
coils.forEach(c => {
if (c.actualWarehouseId && !coilByWarehouseId[c.actualWarehouseId]) {
coilByWarehouseId[c.actualWarehouseId] = c
}
})
const grid = {}
warehouses.forEach(w => {
const code = String(w.actualWarehouseCode || '')
const m = code.match(/^([A-Za-z0-9]{3})([^-]+)-(\d{2})-(\d+)$/)
if (!m) return
const col = Number(m[2])
const row = Number(m[3])
const layer = Number(m[4])
if (!grid[col]) grid[col] = { layer1: [], layer2: [] }
const coil = coilByWarehouseId[w.actualWarehouseId] || null
const cell = {
_col: col,
_row: row,
_layer: layer,
actualWarehouseId: w.actualWarehouseId,
actualWarehouseCode: w.actualWarehouseCode,
rawLocation: w.actualWarehouseName || w.actualWarehouseCode,
...(coil || {})
}
if (layer === 1) grid[col].layer1.push(cell)
else if (layer === 2) grid[col].layer2.push(cell)
})
Object.keys(grid).forEach(k => {
grid[k].layer1.sort((a, b) => Number(a._row) - Number(b._row))
grid[k].layer2.sort((a, b) => Number(a._row) - Number(b._row))
})
this.coilPicker.coilGrid = grid
} finally {
this.coilPicker.loading = false
}
},
searchCoils() {
this.loadCoilsByWarehouse()
},
onCoilFilterChange() {
this.loadCoilsByWarehouse()
},
openCoilPicker(row) {
this.coilPicker.currentRow = row
this.coilPicker.visible = true
this.loadWarehouseOptions().then(() => {
if (!this.coilPicker.selectedWarehouseId && this.coilPicker.warehouseOptions.length) {
this.coilPicker.selectedWarehouseId = this.coilPicker.warehouseOptions[0].actualWarehouseId
}
this.loadCoilsByWarehouse()
})
},
getCoilCell(col, row, layer) {
const c = this.coilPicker.coilGrid[col]
if (!c) return null
const list = layer === 1 ? (c.layer1 || []) : (c.layer2 || [])
return list.find(i => Number(i._row) === Number(row)) || null
},
selectCoilCell(col, row, layer) {
const item = this.getCoilCell(col, row, layer)
if (!item) return
if (!item.rawMaterialId) {
this.$message.warning('该库位当前无钢卷')
return
}
this.pickRawMaterial(item)
},
pickRawMaterial(item) {
const row = this.coilPicker.currentRow
if (!row) return
this.$set(row, 'rawMaterialId', item.rawMaterialId)
this.$set(row, 'rawCoilNos', item.rawMaterialCode || '')
this.$set(row, 'rawNetWeight', item.coilWeight != null ? item.coilWeight : '')
this.$set(row, 'rawPackaging', item.packageType || '')
this.$set(row, 'rawEdgeReq', item.edgeType || '')
this.$set(row, 'rawCoatingType', item.zincLayer || item.surfaceTreatmentDesc || '')
this.$set(row, 'rawLocation', item.rawLocation || '')
this.coilPicker.visible = false
this.onCellChange(row)
},
onCellChange(row) {
if (row && !this.rows.includes(row)) {
this.rows.push(row)
}
this.markDirty(row)
this.scheduleAutoSave()
},
queryLineHistory(query, cb) {
const list = this.lineHistory || []
const q = (query || '').trim().toLowerCase()
const res = list
.filter(item => !q || String(item).toLowerCase().includes(q))
.map(item => ({ value: item }))
cb(res)
},
onLineHistoryInput(row) {
if (!row) return
if (row.lineId && row.lineName && row.lineName.trim() !== String(row.lineName).trim()) {
row.lineId = null
}
this.onCellChange(row)
},
onLineHistorySelect(row) {
return () => {
this.onCellChange(row)
}
},
onFilterChange() {
this.pager.pageNum = 1
},
normalizeRow(row) {
const base = this.buildEmptyRow()
Object.keys(base).forEach(k => {
if (row && row[k] !== undefined && row[k] !== null) base[k] = row[k]
})
const idVal = row ? (row.quickSheetId !== undefined && row.quickSheetId !== null ? row.quickSheetId : row.id) : null
base.quickSheetId = idVal != null ? idVal : null
return base
},
buildEmptyRow() {
const row = { quickSheetId: null }
this.flatColumns.forEach(col => {
if (!col.prop) return
row[col.prop] = ''
})
return row
},
isRowEmpty(row) {
if (!row) return true
return !this.flatColumns.some(col => col.prop && row[col.prop])
},
isTimeField(prop) {
return prop === 'startTime' || prop === 'endTime'
},
markDirty(row) {
if (!row) return
if (!row._tmpId) row._tmpId = Math.random().toString(36).slice(2)
this.dirtyIds.add(row._tmpId)
},
async clearRow(row) {
if (!row) return
if (!row.quickSheetId) {
this.flatColumns.forEach(col => {
if (!col.prop) return
row[col.prop] = ''
})
row.quickSheetId = null
return
}
await deleteQuickSheetRow(row.quickSheetId)
this.flatColumns.forEach(col => {
if (!col.prop) return
row[col.prop] = ''
})
row.quickSheetId = null
},
scheduleAutoSave() {
if (this.autoSaveTimer) clearTimeout(this.autoSaveTimer)
this.autoSaveTimer = setTimeout(() => {
this.saveAll(true)
}, 800)
},
async saveAll(isAuto = false, payload) {
const rows = payload && payload.rows
? payload.rows
: this.rows.filter(r => r && r._tmpId && this.dirtyIds.has(r._tmpId))
.map(r => ({
...r,
quickSheetId: r.quickSheetId || null
}))
if (!rows.length) {
if (!isAuto) this.$message.warning('暂无可保存数据')
return
}
const missing = this.collectMissing(rows)
if (missing.length && !isAuto) {
this.$message.warning(`必填项未填写:${missing.join('')}`)
return
}
this.saving = true
try {
await saveQuickSheet({ rows })
if (!isAuto) this.$message.success('保存成功')
rows.forEach(r => {
if (r._tmpId) this.dirtyIds.delete(r._tmpId)
})
} finally {
this.saving = false
}
},
addRow() {
this.rows.push(this.buildEmptyRow())
if (this.rows.length < this.pager.pageSize) {
this.rows = this.rows.concat(this.buildEmptyRows(this.pager.pageSize - this.rows.length))
}
},
onFilterChange() {
this.pager.pageNum = 1
},
exportCsv() {
this.loading = true
exportQuickSheet()
.then(blob => {
const filename = `quick_sheet_${new Date().getTime()}.xlsx`
saveAs(new Blob([blob], { type: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet' }), filename)
})
.finally(() => {
this.loading = false
})
},
addRow() {
this.rows.unshift(this.buildEmptyRow())
this.pager.pageNum = 1
},
collectMissing(rows) {
const msgs = []
rows.forEach((row, idx) => {
if (this.isRowEmpty(row)) return
const line = idx + 1
const missing = []
if (!row.lineName && !row.lineId) missing.push('产线')
if (!row.planCode) missing.push('计划号')
if (!row.startTime) missing.push('开始时间')
if (!row.endTime) missing.push('结束时间')
if (missing.length) {
msgs.push(`${line}行缺少${missing.join('、')}`)
}
})
return msgs
}
}
}
</script>
<style scoped lang="scss">
.aps-quick-sheet {
padding: 8px;
background: #f7f9fc;
}
.sheet-toolbar,
.sheet-body {
border: 1px solid #eef2f7;
border-radius: 8px;
background: #fff;
}
.sheet-toolbar {
margin-bottom: 8px;
padding: 8px 12px;
display: flex;
align-items: center;
justify-content: space-between;
gap: 12px;
}
.sheet-actions { display: flex; gap: 8px; }
.preset-bar { display: flex; align-items: center; gap: 8px; font-size: 12px; color: #667085; }
.preset-title { font-weight: 600; color: #475467; }
::v-deep .excel-table {
border: 1px solid #edf1f7;
}
::v-deep .excel-table th.el-table__cell {
background: #fafbfe;
color: #5d6b82;
font-weight: 600;
border-right: 1px solid #edf1f7;
border-bottom: 1px solid #edf1f7;
padding: 4px 0;
}
::v-deep .excel-table td.el-table__cell {
border-right: 1px solid #f0f2f6;
border-bottom: 1px solid #f0f2f6;
padding: 1px 2px;
}
::v-deep .excel-table .el-table__row:hover > td {
background-color: #f7faff !important;
}
::v-deep .el-input__inner {
border-radius: 0;
height: 26px;
line-height: 26px;
border: none;
box-shadow: none;
background: transparent;
}
::v-deep .el-input__inner:focus {
border: none;
box-shadow: inset 0 0 0 1px #4a90e2;
background: #fff;
}
.missing-tip {
margin-top: 8px;
padding: 8px 12px;
background: #fff7e6;
border: 1px solid #ffd591;
border-radius: 6px;
font-size: 12px;
color: #d46b08;
}
.missing-item {
line-height: 1.6;
}
.coil-picker-btn {
padding: 0;
}
.coil-bird-wrap { border: 1px solid #ebeef5; border-radius: 6px; padding: 8px; }
.coil-legend { display: flex; gap: 14px; font-size: 12px; color: #606266; margin-bottom: 8px; }
.legend-item { display: inline-flex; align-items: center; gap: 4px; }
.dot { width: 8px; height: 8px; border-radius: 2px; display: inline-block; }
.dot.layer1 { background: #fff3e0; border: 1px solid #f5d7a1; }
.dot.layer2 { background: #e8f5e9; border: 1px solid #b7ddb9; }
.dot.occupied { background: #f6f8fb; border: 1px solid #d7dde5; }
.coil-grid-scroll { overflow: auto; max-height: 460px; }
.coil-col-ruler { display: flex; min-width: max-content; }
.ruler-empty { width: 28px; flex: none; }
.ruler-col { width: 138px; text-align: center; font-size: 12px; color: #606266; }
.coil-grid-main { display: flex; min-width: max-content; }
.coil-row-ruler { width: 28px; display: flex; flex-direction: column; }
.ruler-row { height: 56px; display: flex; align-items: center; justify-content: center; font-size: 12px; color: #909399; }
.coil-grid-columns { display: flex; gap: 8px; }
.coil-col-pair { display: grid; grid-template-columns: 1fr 1fr; width: 180px; border: 1px solid #eef2f7; border-radius: 4px; overflow: visible; }
.coil-layer { display: flex; flex-direction: column; }
.coil-layer.layer2-shift { margin-top: 28px; }
.coil-cell { height: 56px; border-bottom: 1px solid #eef2f7; border-right: 1px solid #eef2f7; padding: 4px; font-size: 11px; color: #909399; display: flex; align-items: center; justify-content: center; text-align: center; cursor: pointer; }
.coil-cell.layer1 { background: #fff3e0; color: #e67e22; }
.coil-cell.layer2 { background: #e8f5e9; color: #2e8b57; border-right: none; }
.coil-cell.occupied { font-weight: 600; }
.coil-cell .line1 { width: 100%; font-size: 11px; line-height: 1.1; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.coil-cell .line2 { width: 100%; font-size: 11px; line-height: 1.1; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.coil-picker-foot { margin-top: 8px; text-align: right; color: #909399; font-size: 12px; }
</style>

View File

@@ -1,225 +0,0 @@
<template>
<div class="aps-quick-sheet-preview excel-theme">
<div class="sheet-toolbar">
<div class="sheet-actions">
<el-date-picker
v-model="filter.range"
type="daterange"
range-separator=""
start-placeholder="开始日期"
end-placeholder="结束日期"
value-format="yyyy-MM-dd"
size="small"
style="width: 240px"
@change="onFilterChange"
/>
<el-select v-model="filter.lineId" clearable filterable size="small" placeholder="产线" style="width: 160px" @change="onFilterChange">
<el-option v-for="line in lineOptions" :key="line.lineId" :label="line.lineName || line.lineCode || ('产线' + line.lineId)" :value="line.lineId" />
</el-select>
<el-input v-model="filter.customer" size="small" clearable placeholder="客户" style="width: 160px" @input="onFilterChange" />
<el-button size="small" icon="el-icon-refresh" @click="loadRows">刷新</el-button>
<el-button size="small" icon="el-icon-download" @click="exportExcel">导出</el-button>
</div>
</div>
<div class="sheet-body">
<el-table
v-loading="loading"
:data="displayRows"
border
size="mini"
class="excel-table"
>
<el-table-column label="序号" width="60" align="center">
<template slot-scope="scope">
{{ ((pager.pageNum - 1) * pager.pageSize) + scope.$index + 1 }}
</template>
</el-table-column>
<el-table-column
v-for="col in flatColumns"
:key="col.prop || col.label"
:label="col.label"
:prop="col.prop"
:width="col.width"
:min-width="col.minWidth"
:align="col.align || 'center'"
:show-overflow-tooltip="col.showOverflowTooltip !== false"
/>
</el-table>
<div class="pager-wrap">
<el-pagination
small
background
layout="prev, pager, next, total"
:current-page.sync="pager.pageNum"
:page-size="pager.pageSize"
:total="totalRows"
/>
</div>
</div>
</div>
</template>
<script>
import { fetchQuickSheetList, exportQuickSheet } from '@/api/aps/quickSheet'
import { listProductionLine } from '@/api/wms/productionLine'
import { getTemplateByKey } from './sheets/templates'
import { saveAs } from 'file-saver'
export default {
name: 'ApsQuickSheetPreview',
data() {
return {
loading: false,
rows: [],
lineOptions: [],
filter: {
range: [],
lineId: null,
customer: ''
},
pager: {
pageNum: 1,
pageSize: 25
}
}
},
computed: {
currentTemplate() {
return getTemplateByKey('unified')
},
flatColumns() {
const res = []
const loop = (cols) => {
;(cols || []).forEach(c => {
if (c.children && c.children.length) loop(c.children)
else res.push(c)
})
}
loop(this.currentTemplate.columns || [])
return res
},
filteredRows() {
const [startAfter, endBefore] = this.filter.range || []
const lineName = (this.filter.lineName || '').trim()
const customer = (this.filter.customer || '').trim().toLowerCase()
return this.rows.filter(r => {
if (startAfter || endBefore) {
const d = r.startTime ? String(r.startTime).slice(0, 10) : ''
if (startAfter && d && d < startAfter) return false
if (endBefore && d && d > endBefore) return false
}
if (lineName && r.lineName !== lineName) return false
if (customer) {
const txt = String(r.customerName || '').toLowerCase()
if (!txt.includes(customer)) return false
}
return true
})
},
displayRows() {
const start = (this.pager.pageNum - 1) * this.pager.pageSize
return this.filteredRows.slice(start, start + this.pager.pageSize)
},
totalRows() {
return this.filteredRows.length
}
},
created() {
const today = new Date()
const start = `${today.getFullYear()}-${`${today.getMonth() + 1}`.padStart(2, '0')}-${`${today.getDate()}`.padStart(2, '0')}`
this.filter.range = [start, '']
this.loadRows()
this.loadLines()
},
methods: {
async loadLines() {
const res = await listProductionLine({ pageNum: 1, pageSize: 1000 })
this.lineOptions = res.rows || []
},
async loadRows() {
this.loading = true
try {
const params = this.buildQueryParams()
const res = await fetchQuickSheetList(params)
this.rows = (res.data || []).map(r => ({ ...r }))
} finally {
this.loading = false
}
},
onFilterChange() {
this.pager.pageNum = 1
this.loadRows()
},
exportExcel() {
const params = this.buildQueryParams()
exportQuickSheet(params)
.then(blob => {
const filename = `quick_sheet_preview_${new Date().getTime()}.xlsx`
saveAs(new Blob([blob], { type: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet' }), filename)
})
},
buildQueryParams() {
const [startDate, endDate] = this.filter.range || []
return {
startDate: startDate || undefined,
endDate: endDate || undefined,
lineId: this.filter.lineId || undefined,
customerName: this.filter.customer || undefined
}
}
}
}
</script>
<style scoped lang="scss">
.aps-quick-sheet-preview {
padding: 8px;
background: #f7f9fc;
}
.sheet-toolbar {
margin-bottom: 8px;
padding: 8px;
border: 1px solid #eef2f7;
border-radius: 8px;
background: #fff;
}
.sheet-actions {
display: flex;
gap: 8px;
align-items: center;
flex-wrap: wrap;
}
.sheet-body {
border: 1px solid #eef2f7;
border-radius: 8px;
background: #fff;
padding: 6px;
}
::v-deep .excel-table {
border: 1px solid #edf1f7;
}
::v-deep .excel-table th.el-table__cell {
background: #fafbfe;
color: #5d6b82;
font-weight: 600;
border-right: 1px solid #edf1f7;
border-bottom: 1px solid #edf1f7;
padding: 4px 0;
}
::v-deep .excel-table td.el-table__cell {
border-right: 1px solid #f0f2f6;
border-bottom: 1px solid #f0f2f6;
padding: 1px 2px;
}
.pager-wrap {
margin-top: 8px;
display: flex;
justify-content: flex-end;
}
</style>

View File

@@ -15,7 +15,7 @@ export const APS_SHEET_TEMPLATES = [
key: 'unified',
name: '统一排产表',
columns: [
{ label: '产线', prop: 'lineName', minWidth: 140 },
{ label: '产线', prop: 'lineName', minWidth: 120 },
{ label: '计划号', prop: 'planCode', minWidth: 140 },
{ label: '订单号', prop: 'orderCode', minWidth: 140 },
{ label: '客户', prop: 'customerName', minWidth: 140 },

View File

@@ -3,7 +3,7 @@
<img :src="avatar" class="user-avatar" alt="头像" />
<div class="greeting-text">
<div class="greeting-title">{{ greeting }}{{ name }}</div>
<div class="greeting-desc">欢迎使用科伦普冷轧涂渡数智一体化平台</div>
<div class="greeting-desc">欢迎使用MES数智一体化平台</div>
</div>
</div>
</template>

View File

@@ -12,10 +12,6 @@
</div>
</div>
<div class="oee-query-bar">
<el-select v-model="lineType" size="small" style="width: 120px">
<el-option label="酸轧线" value="acid" />
<el-option label="镀锌一线" value="galvanize1" />
</el-select>
<el-date-picker
v-model="queryRange"
type="daterange"
@@ -52,15 +48,6 @@
</div>
</el-card>
<el-alert
v-if="lineType === 'galvanize1'"
title="镀锌二级数据未完全使用,故而停机时间可能有错误"
type="warning"
:closable="false"
class="oee-top-warning"
show-icon
/>
<el-row :gutter="16" class="oee-main-row">
<!-- 左侧报表主体Word 风格 -->
<el-col :span="18">
@@ -92,26 +79,11 @@
<span>{{ formatPercent(scope.row.performanceTon) }}</span>
</template>
</el-table-column>
<el-table-column prop="quality" label="良品率(%)" align="center">
<el-table-column prop="quality" label="良品率 Q (%)" align="center">
<template slot-scope="scope">
<span>{{ formatPercent(scope.row.quality) }}</span>
</template>
</el-table-column>
<el-table-column prop="qualifiedRate" label="合格品率(%)" align="center">
<template slot-scope="scope">
<span>{{ formatPercent(scope.row.qualifiedRate) }}</span>
</template>
</el-table-column>
<el-table-column prop="defectRate" label="次品率 (%)" align="center">
<template slot-scope="scope">
<span>{{ formatPercent(scope.row.defectRate) }}</span>
</template>
</el-table-column>
<el-table-column prop="pendingRate" label="待判级率 (%)" align="center">
<template slot-scope="scope">
<span>{{ formatPercent(scope.row.pendingRate) }}</span>
</template>
</el-table-column>
<el-table-column prop="loadingTimeMin" label="负荷时间 (min)" align="center" />
<el-table-column prop="downtimeMin" label="停机时间 (min)" align="center" />
<el-table-column prop="runTimeMin" label="运转时间 (min)" align="center" />
@@ -121,26 +93,16 @@
</template>
</el-table-column>
<el-table-column prop="totalOutputCoil" label="总产量 (卷)" align="center" />
<el-table-column prop="goodOutputTon" label="良品(吨)" align="center">
<el-table-column prop="goodOutputTon" label="良品量 (吨)" align="center">
<template slot-scope="scope">
<span>{{ formatNumber(scope.row.goodOutputTon) }}</span>
</template>
</el-table-column>
<el-table-column prop="abOutputTon" label="合格(吨)" align="center">
<template slot-scope="scope">
<span>{{ formatNumber(scope.row.abOutputTon) }}</span>
</template>
</el-table-column>
<el-table-column prop="defectOutputTon" label="次品(吨)" align="center">
<el-table-column prop="defectOutputTon" label="次品量 (吨)" align="center">
<template slot-scope="scope">
<span>{{ formatNumber(scope.row.defectOutputTon) }}</span>
</template>
</el-table-column>
<el-table-column prop="pendingOutputTon" label="待判(吨)" align="center">
<template slot-scope="scope">
<span>{{ formatNumber(scope.row.pendingOutputTon) }}</span>
</template>
</el-table-column>
</el-table>
<!-- 日明细趋势表格风格方便导出 Word -->
@@ -171,26 +133,11 @@
{{ formatPercent(scope.row.performanceTon) }}
</template>
</el-table-column>
<el-table-column prop="quality" label="良品率 (%)">
<el-table-column prop="quality" label="Q (%)">
<template slot-scope="scope">
{{ formatPercent(scope.row.quality) }}
</template>
</el-table-column>
<el-table-column prop="qualifiedRate" label="合格品率 (%)">
<template slot-scope="scope">
{{ formatPercent(scope.row.qualifiedRate) }}
</template>
</el-table-column>
<el-table-column prop="defectRate" label="次品率 (%)">
<template slot-scope="scope">
{{ formatPercent(scope.row.defectRate) }}
</template>
</el-table-column>
<el-table-column prop="pendingRate" label="待判级率 (%)">
<template slot-scope="scope">
{{ formatPercent(scope.row.pendingRate) }}
</template>
</el-table-column>
<el-table-column prop="loadingTimeMin" label="负荷 (min)"/>
<el-table-column prop="downtimeMin" label="停机 (min)"/>
<el-table-column prop="runTimeMin" label="运转 (min)"/>
@@ -200,26 +147,16 @@
</template>
</el-table-column>
<el-table-column prop="totalOutputCoil" label="总产量 (卷)" />
<el-table-column prop="goodOutputTon" label="良品(吨)">
<el-table-column prop="goodOutputTon" label="良品 (吨)">
<template slot-scope="scope">
{{ formatNumber(scope.row.goodOutputTon) }}
</template>
</el-table-column>
<el-table-column prop="abOutputTon" label="合格(吨)">
<template slot-scope="scope">
{{ formatNumber(scope.row.abOutputTon) }}
</template>
</el-table-column>
<el-table-column prop="defectOutputTon" label="次品(吨)">
<el-table-column prop="defectOutputTon" label="次品 (吨)">
<template slot-scope="scope">
{{ formatNumber(scope.row.defectOutputTon) }}
</template>
</el-table-column>
<el-table-column prop="pendingOutputTon" label="待判(吨)">
<template slot-scope="scope">
{{ formatNumber(scope.row.pendingOutputTon) }}
</template>
</el-table-column>
</el-table>
<!-- OEE/A/P/Q 趋势图 -->
@@ -326,7 +263,7 @@
<ul class="formula-list">
<li>A时间稼动率 = (负荷时间 停机时间) / 负荷时间</li>
<li>P性能稼动率吨维度 = (理论节拍 × 产量吨) / 实际运转时间</li>
<li>Q良品率 = A系列 / 总产量吨</li>
<li>Q良品率 = 良品 / 总产量吨</li>
</ul>
</div>
@@ -336,9 +273,8 @@
<li><b>负荷时间</b>计划生产时间扣除计划停机后的时间</li>
<li><b>停机时间</b>所有停机/中断 stop_type 汇总的总时长</li>
<li><b>实际运转时间</b>负荷时间 停机时间</li>
<li><b>理论节拍</b>使用整体酸轧库中位数学习得出</li>
<li><b>A系列</b>良品<b>B系列</b>合格品<b>C/D系列</b>次品<b>O系列</b>待判级</li>
<li><b>合格品率(AB)</b> = (A+B) / 总量<b>次品率(CD)</b> = CD / 总量<b>待判级率(O)</b> = O / 总量</li>
<li><b>理论节拍</b>优良日统计口径得到的稳定节拍分钟/由理论节拍接口提供</li>
<li><b>良品/次品</b> WMS `quality_status` 判断C+/C/C-/D+/D/D- </li>
</ul>
</div>
@@ -396,13 +332,12 @@ export default {
const today = new Date()
const firstDay = new Date(today.getFullYear(), today.getMonth(), 1)
return {
lineType: 'acid',
queryRange: [
this.formatDate(firstDay),
this.formatDate(today)
],
pickerOptions: {
disabledDate() {
disabledDate(time) {
// 不限制选择范围,保留扩展空间
return false
}
@@ -430,18 +365,13 @@ export default {
availability: 0,
performanceTon: 0,
quality: 0,
qualifiedRate: 0,
defectRate: 0,
pendingRate: 0,
loadingTimeMin: 0,
downtimeMin: 0,
runTimeMin: 0,
totalOutputTon: 0,
totalOutputCoil: 0,
goodOutputTon: 0,
abOutputTon: 0,
defectOutputTon: 0,
pendingOutputTon: 0
defectOutputTon: 0
}
}
let sumLoading = 0
@@ -449,17 +379,12 @@ export default {
let sumRun = 0
let sumTotalTon = 0
let sumGoodTon = 0
let sumAbTon = 0
let sumDefectTon = 0
let sumPendingTon = 0
let sumCoil = 0
let sumOee = 0
let sumA = 0
let sumP = 0
let sumQ = 0
let sumQualifiedRate = 0
let sumDefectRate = 0
let sumPendingRate = 0
list.forEach(row => {
sumLoading += row.loadingTimeMin || 0
@@ -467,37 +392,28 @@ export default {
sumRun += row.runTimeMin || 0
sumTotalTon += Number(row.totalOutputTon || 0)
sumGoodTon += Number(row.goodOutputTon || 0)
sumAbTon += Number(row.abOutputTon || 0)
sumDefectTon += Number(row.defectOutputTon || 0)
sumPendingTon += Number(row.pendingOutputTon || 0)
sumCoil += row.totalOutputCoil || 0
sumOee += Number(row.oee || 0)
sumA += Number(row.availability || 0)
sumP += Number(row.performanceTon || 0)
sumQ += Number(row.quality || 0)
sumQualifiedRate += Number(row.qualifiedRate || 0)
sumDefectRate += Number(row.defectRate || 0)
sumPendingRate += Number(row.pendingRate || 0)
})
const n = list.length
const defectAgg = Math.max(0, sumTotalTon - sumGoodTon)
return {
oee: n ? sumOee / n : 0,
availability: n ? sumA / n : 0,
performanceTon: n ? sumP / n : 0,
quality: n ? sumQ / n : 0,
qualifiedRate: n ? sumQualifiedRate / n : 0,
defectRate: n ? sumDefectRate / n : 0,
pendingRate: n ? sumPendingRate / n : 0,
loadingTimeMin: sumLoading,
downtimeMin: sumDowntime,
runTimeMin: sumRun,
totalOutputTon: sumTotalTon,
totalOutputCoil: sumCoil,
goodOutputTon: sumGoodTon,
abOutputTon: sumAbTon,
defectOutputTon: sumDefectTon,
pendingOutputTon: sumPendingTon
defectOutputTon: defectAgg || sumDefectTon
}
}
},
@@ -556,7 +472,6 @@ export default {
buildQuery() {
const [start, end] = this.queryRange || []
return {
lineType: this.lineType,
startDate: start,
endDate: end
}
@@ -574,9 +489,9 @@ export default {
try {
const res = await fetchOeeSummary(this.buildQuery())
// 兼容后端直接返回数组或 TableDataInfo { rows, total } 两种结构
this.summaryList = res.data
} catch (e) {
this.$message.error('加载酸轧日汇总失败')
} finally {
@@ -589,7 +504,7 @@ export default {
const res = await fetchOeeLoss7(this.buildQuery())
// 兼容后端直接返回数组或 TableDataInfo { rows, total } 两种结构
this.loss7List = res.data
} catch (e) {
this.$message.error('加载酸轧 7 大损失失败')
} finally {
@@ -749,10 +664,6 @@ export default {
gap: 8px;
}
.oee-top-warning {
margin-bottom: 8px;
}
.oee-main-row {
margin-top: 8px;
}

View File

@@ -12,10 +12,10 @@ export default {
components: { Home },
mounted() {
// 确保容器DOM已渲染后对容器执行全屏
this.$nextTick(() => {
this.enterFullscreen()
this.addFullscreenListener()
})
// this.$nextTick(() => {
// this.enterFullscreen()
// this.addFullscreenListener()
// })
},
beforeDestroy() {
// 移除监听,避免内存泄漏
@@ -60,7 +60,7 @@ export default {
!document.mozFullScreenElement &&
!document.webkitFullscreenElement &&
!document.msFullscreenElement
if (isExit) {
// 退出全屏后返回上一页
this.$router.back()
@@ -91,4 +91,4 @@ export default {
height: 100%;
/* 可根据需要添加其他样式(如背景色等) */
}
</style>
</style>

View File

@@ -1,68 +1,71 @@
<template>
<div class="dashboard-editor-container">
<img src="http://kelunpuzhonggong.com/upload/img/20250427091033.jpg" alt="">
<div class="aboutus">
<el-row :gutter="30">
<!-- 左栏 -->
<el-col :span="12" :xs="24">
<div class="aboutus-title">
<h2>关于我们</h2>
<p>ABOUT US</p>
</div>
<div class="aboutus-left">
<p class="aboutus-desc">
嘉祥科伦普重工有限公司是山东省重点工程项目是济宁市工程之一也是科伦普产品结构调整重要的工程项目工程采用了外方技术总负责关键设备整体引进点采集成国内技术总成自主创新单体设备引进等多种建设方案保证了技术先进和人才的培养确保工程投产后达产达效
</p>
<p class="aboutus-desc">
科伦普冷轧重工有限公司是设计年产量150万吨能向广大用户提供热轧酸洗热轧镀锌冷硬罩式退火冷轧镀锌铝锌合金锌铝合金锌铝镁镀铬等各大类产品产品覆盖东北华北华东华南等地区
</p>
</div>
<!-- <div class="dashboard-editor-container">-->
<!-- <img src="http://kelunpuzhonggong.com/upload/img/20250427091033.jpg" alt="">-->
<!-- -->
<!-- <div class="aboutus">-->
<!-- <el-row :gutter="30">-->
<!-- &lt;!&ndash; 左栏 &ndash;&gt;-->
<!-- <el-col :span="12" :xs="24">-->
<!-- <div class="aboutus-title">-->
<!-- <h2>关于我们</h2>-->
<!-- <p>ABOUT US</p>-->
<!-- </div>-->
<!-- <div class="aboutus-left">-->
<!-- <p class="aboutus-desc">-->
<!-- MES一体化平台是面向制造企业车间执行层的生产信息化管理系统为企业提供包括制造数据管理计划排程管理生产调度管理库存管理质量管理人力资源管理工作中心管理等多项企业管理功能-->
<!-- </p>-->
<!-- <p class="aboutus-desc">-->
<!-- 平台通过互联网技术实现企业数字化转型提高生产效率降低成本提升产品质量实现精益化生产管理帮助企业实现智能制造目标-->
<!-- </p>-->
<!-- </div>-->
<statistic-group />
</el-col>
<!-- <statistic-group />-->
<!-- </el-col>-->
<!-- 右栏 -->
<el-col :span="12" :xs="24">
<img src="http://kelunpuzhonggong.com/upload/img/20251015103934.jpg" alt="">
<!-- <div class="aboutus-right">
<p class="aboutus-detail">
嘉祥科伦普重工有限公司成立于2017年8月注册资金33100万元主要经营高铁设备配件制造与销售模具制造与销售新材料技术研发高性能有色金属及合金材料销售机械零件零部件加工与销售金属材料制造与销售锌铝镁新材料等目前公司拥有10余项具有自主知识产权的发明专利技术综合技术水平达国内领先2024年公司主导产品国内市场占有率约占85%2024年总资产5.98亿元净资产4.49亿元收入3.95亿元公司现有员工238人研究与试验发展人员57人其中专职研究与试验发展人员52人外聘专家5人
</p>
<p class="aboutus-detail">
2024年公司新建科伦普合金新材料研发项目占地290亩采用国内先进的镀层核心技术和热处理工艺专业生产锌铝镁板材和镀铬板材致力于打造国内工艺链条最完善产品型号最丰富的涂镀新材料生产企业全部投产后可实现新增销售收入80亿元利税4.7亿元带动就业约500人项目主要生产的冷轧板锌铝镁涂层板镀铬涂层板镀锡涂层板等产品涵盖0.08MM-6.0MM区间60多种产品是国内单个企业产品种类最多的项目产品因其防锈耐氧化耐腐蚀高电导高稳定的优秀特性广泛应用于建筑结构件汽车制造轻工家电食品包装医疗器械电子通讯航空航天领域
</p>
</div> -->
</el-col>
</el-row>
</div>
<!-- &lt;!&ndash; 右栏 &ndash;&gt;-->
<!-- <el-col :span="12" :xs="24">-->
<!-- <img src="http://kelunpuzhonggong.com/upload/img/20251015103934.jpg" alt="">-->
<!-- &lt;!&ndash; <div class="aboutus-right">-->
<!-- <p class="aboutus-detail">-->
<!-- 嘉祥科伦普重工有限公司成立于2017年8月注册资金33100万元主要经营高铁设备配件制造与销售模具制造与销售新材料技术研发高性能有色金属及合金材料销售机械零件零部件加工与销售金属材料制造与销售锌铝镁新材料等目前公司拥有10余项具有自主知识产权的发明专利技术综合技术水平达国内领先2024年公司主导产品国内市场占有率约占85%2024年总资产5.98亿元净资产4.49亿元收入3.95亿元公司现有员工238人研究与试验发展人员57人其中专职研究与试验发展人员52人外聘专家5人-->
<!-- </p>-->
<!-- <p class="aboutus-detail">-->
<!-- 2024年公司新建科伦普合金新材料研发项目占地290亩采用国内先进的镀层核心技术和热处理工艺专业生产锌铝镁板材和镀铬板材致力于打造国内工艺链条最完善产品型号最丰富的涂镀新材料生产企业全部投产后可实现新增销售收入80亿元利税4.7亿元带动就业约500人项目主要生产的冷轧板锌铝镁涂层板镀铬涂层板镀锡涂层板等产品涵盖0.08MM-6.0MM区间60多种产品是国内单个企业产品种类最多的项目产品因其防锈耐氧化耐腐蚀高电导高稳定的优秀特性广泛应用于建筑结构件汽车制造轻工家电食品包装医疗器械电子通讯航空航天领域-->
<!-- </p>-->
<!-- </div> &ndash;&gt;-->
<!-- </el-col>-->
<!-- </el-row>-->
<!-- </div>-->
<!-- <div>
<statistic-group />
</div> -->
<!-- <AllApplications />
<el-row :gutter="10">
<el-col :span="18">
<el-empty description="办公模块定制开发中"></el-empty>
</el-col>
<el-col :span="6">
<mini-calendar />
</el-col>
</el-row> -->
</div>
<!-- &lt;!&ndash; <div>-->
<!-- <statistic-group />-->
<!-- </div> &ndash;&gt;-->
<!-- -->
<!-- &lt;!&ndash; <AllApplications /> -->
<!-- <el-row :gutter="10">-->
<!-- <el-col :span="18">-->
<!-- <el-empty description="办公模块定制开发中"></el-empty>-->
<!-- </el-col>-->
<!-- <el-col :span="6">-->
<!-- <mini-calendar />-->
<!-- </el-col>-->
<!-- </el-row> &ndash;&gt;-->
<!-- </div>-->
<Dashboard />
</template>
<script>
import StatisticGroup from '@/components/HomeModules/StatisticGroup.vue'
// import AllApplications from '@/components/HomeModules/AllApplications.vue'
// import MiniCalendar from '@/components/HomeModules/MiniCalendar.vue'
import Dashboard from '@/views/dashboard/demo.vue'
export default {
export default {
name: 'Index',
components: {
// PanelGroup,
StatisticGroup,
Dashboard,
// AllApplications,
// MiniCalendar,
},
@@ -193,4 +196,4 @@ export default {
}
}
}
</style>
</style>

View File

@@ -3,15 +3,14 @@
<div class="login-box">
<div class="login-left">
<div class="login-title-welcome">
<img src="../assets/logo/logo.png" alt="logo" class="logo-img" />
<span class="login-title">欢迎使用科伦普冷轧涂镀数智一体化平台</span>
<!-- <img src="../assets/logo/logo.png" alt="logo" class="logo-img" />-->
<span class="login-title">欢迎使用MES一体化平台</span>
</div>
<p>
嘉祥科伦普重工有限公司是山东省重点工程项目是济宁市工程之一也是科伦普产品结构调整重要的工程项目工程采用了外方技术总负责关键设备整体引进点采集成国内技术总成自主创新单体设备引进等多种建设方
保证了技术先进和人才的培养确保工程投产后达产达效
MES一体化平台是面向制造企业车间执行层的生产信息化管理系统为企业提供包括制造数据管理计划排程管理生产调度管理库存管理质量管理人力资源管理工作中心管理等多项企业管理功能
</p>
<p>
科伦普冷轧重工有限公司是设计年产量150万 能向广大用户提供热轧酸洗热轧镀锌冷硬罩式退火冷轧镀锌铝锌合金锌铝合金锌铝镁镀铬等各大类产品产品覆盖东北华北华东华南等地区
平台通过互联网技术实现企业数字化转型提高生产效率降低成本提升产品质量实现精益化生产管理
</p>
</div>
@@ -300,4 +299,4 @@ $--metal-gradient-light: linear-gradient(145deg, #f5f5f550, #ffffff50);
color: $--color-text-secondary; // 浅灰色文字
}
}
</style>
</style>

View File

@@ -33,9 +33,11 @@
@keyup.enter.native="handleQuery"
/>
</el-form-item>
<el-form-item label="机组" prop="unitTeam">
<dict-select v-model="queryParams.unitTeam" dict-type="sys_lines" placeholder="请选择机组" filterable />
</el-form-item>
<!-- <el-form-item label="关联设备" prop="equipmentId">
<el-select v-model="queryParams.equipmentId" placeholder="请选择关联设备" filterable>
<el-option v-for="item in equipmentList" :key="item.equipmentId" :label="item.equipmentName" :value="item.equipmentId" />
</el-select>
</el-form-item> -->
<el-form-item>
<el-button type="primary" icon="el-icon-search" size="mini" @click="handleQuery">搜索</el-button>
<el-button icon="el-icon-refresh" size="mini" @click="resetQuery">重置</el-button>
@@ -89,7 +91,6 @@
<el-table-column label="辅料名称" align="center" prop="auxiliaryName" />
<el-table-column label="辅料品类" align="center" prop="materialCategory" />
<el-table-column label="辅料品牌" align="center" prop="auxiliaryModel" />
<el-table-column label="机组" align="center" prop="unitTeam" />
<el-table-column label="计量单位" align="center" prop="unit" />
<!-- <el-table-column label="关联设备" align="center" prop="equipmentName" /> -->
<el-table-column label="当前库存" align="center" prop="quantity" />
@@ -146,9 +147,11 @@
<el-form-item label="计量单位" prop="unit">
<el-input v-model="form.unit" placeholder="请输入计量单位" />
</el-form-item>
<el-form-item label="机组" prop="unitTeam">
<dict-select v-model="form.unitTeam" dict-type="sys_lines" placeholder="请选择机组" filterable />
</el-form-item>
<!-- <el-form-item label="关联设备ID" prop="equipmentId">
<el-select v-model="form.equipmentId" placeholder="请选择关联设备ID" filterable>
<el-option v-for="item in equipmentList" :key="item.equipmentId" :label="item.equipmentName" :value="item.equipmentId" />
</el-select>
</el-form-item> -->
<el-form-item label="备注" prop="remark">
<el-input v-model="form.remark" type="textarea" placeholder="请输入内容" />
</el-form-item>
@@ -193,13 +196,11 @@
import { listAuxiliaryMaterial, getAuxiliaryMaterial, delAuxiliaryMaterial, addAuxiliaryMaterial, updateAuxiliaryMaterial } from "@/api/mes/eqp/auxiliaryMaterial";
import { changeStock } from "@/api/mes/eqp/auxiliaryMaterialChange";
import auxiliaryChange from '../components/pages/auxiliaryChange.vue';
import dictSelect from '@/components/DictSelect';
export default {
name: "Auxiliary",
components: {
auxiliaryChange,
dictSelect
auxiliaryChange
},
data() {
return {

View File

@@ -38,9 +38,6 @@
<el-option v-for="item in equipmentList" :key="item.equipmentId" :label="item.equipmentName" :value="item.equipmentId" />
</el-select>
</el-form-item>
<el-form-item label="机组" prop="unitTeam">
<dict-select v-model="queryParams.unitTeam" dict-type="sys_lines" placeholder="请选择机组" filterable />
</el-form-item>
<el-form-item>
<el-button type="primary" icon="el-icon-search" size="mini" @click="handleQuery">搜索</el-button>
<el-button icon="el-icon-refresh" size="mini" @click="resetQuery">重置</el-button>
@@ -95,7 +92,6 @@
<el-table-column label="物料品类" align="center" prop="materialCategory" />
<el-table-column label="备件型号" align="center" prop="model" />
<el-table-column label="计量单位" align="center" prop="unit" />
<el-table-column label="机组" align="center" prop="unitTeam" />
<el-table-column label="关联设备" align="center" prop="equipmentName" />
<el-table-column label="当前库存" align="center" prop="quantity" />
<el-table-column label="操作" align="center" class-name="small-padding fixed-width">
@@ -151,9 +147,6 @@
<el-form-item label="计量单位" prop="unit">
<el-input v-model="form.unit" placeholder="请输入计量单位" />
</el-form-item>
<el-form-item label="机组" prop="unitTeam">
<dict-select v-model="form.unitTeam" dict-type="sys_lines" placeholder="请选择机组" filterable />
</el-form-item>
<el-form-item label="关联设备ID" prop="equipmentId">
<el-select v-model="form.equipmentId" placeholder="请选择关联设备ID" filterable>
<el-option v-for="item in equipmentList" :key="item.equipmentId" :label="item.equipmentName" :value="item.equipmentId" />
@@ -204,14 +197,11 @@ import { listSparePart, getSparePart, delSparePart, addSparePart, updateSparePar
import { listEquipmentManagement } from "@/api/mes/eqp/equipmentManagement";
import { changeStock } from "@/api/mes/eqp/sparePartsChange";
import partChange from '../components/pages/partChange.vue';
import dictSelect from '@/components/DictSelect';
export default {
name: "SparePart",
components: {
partChange,
dictSelect
partChange
},
data() {
return {

View File

@@ -1,364 +0,0 @@
<template>
<div class="app-container">
<el-form :model="queryParams" ref="queryForm" size="small" :inline="true" v-show="showSearch" label-width="60px">
<el-form-item label="炉编号" prop="furnaceCode">
<el-input v-model="queryParams.furnaceCode" placeholder="请输入炉编号" clearable @keyup.enter.native="handleQuery" />
</el-form-item>
<el-form-item label="名称" prop="furnaceName">
<el-input v-model="queryParams.furnaceName" placeholder="请输入名称" clearable @keyup.enter.native="handleQuery" />
</el-form-item>
<el-form-item label="状态" prop="status">
<el-select v-model="queryParams.status" placeholder="请选择" clearable>
<el-option label="启用" :value="1" />
<el-option label="停用" :value="0" />
</el-select>
</el-form-item>
<el-form-item label="忙碌" prop="busyFlag">
<el-select v-model="queryParams.busyFlag" placeholder="请选择" clearable>
<el-option label="空闲" :value="0" />
<el-option label="忙碌" :value="1" />
</el-select>
</el-form-item>
<el-form-item>
<el-button type="primary" icon="el-icon-search" size="mini" @click="handleQuery">搜索</el-button>
<el-button icon="el-icon-refresh" size="mini" @click="resetQuery">重置</el-button>
</el-form-item>
</el-form>
<el-row :gutter="10" class="mb8">
<el-col :span="1.5">
<el-button type="primary" plain icon="el-icon-plus" size="mini" @click="handleAdd">新增</el-button>
</el-col>
<el-col :span="1.5">
<el-button type="success" plain icon="el-icon-edit" size="mini" :disabled="single" @click="handleUpdate">修改</el-button>
</el-col>
<el-col :span="1.5">
<el-button type="danger" plain icon="el-icon-delete" size="mini" :disabled="multiple" @click="handleDelete">删除</el-button>
</el-col>
<right-toolbar :showSearch.sync="showSearch" @queryTable="getList"></right-toolbar>
</el-row>
<div class="furnace-container">
<div v-for="item in furnaceList" :key="item.furnaceId" class="furnace-card">
<div class="furnace-header">
<svg-icon icon-class="furnace" :class="item.busyFlag === 1 ? 'furnace-busy' : 'furnace-idle'" />
<div>
<div class="furnace-name">{{ item.furnaceName }}</div>
<div class="furnace-code">{{ item.furnaceCode }}</div>
</div>
</div>
<div class="furnace-body">
<div class="furnace-line">状态<span :class="item.status === 1 ? 'active-text' : 'inactive-text'">{{ item.status === 1 ? '启用' : '停用' }}</span></div>
<div class="furnace-line">忙碌<span :class="item.busyFlag === 1 ? 'busy-text' : 'idle-text'">{{ item.busyFlag === 1 ? '忙碌' : '空闲' }}</span></div>
<!-- <div v-if="item.busyFlag === 1" class="furnace-line">
计划{{ item.currentPlanNo || '-' }}
</div>
<div v-if="item.busyFlag === 1" class="furnace-line">
当前钢卷{{ item.coilCount || 0 }}
</div>
<div v-if="item.busyFlag === 1" class="furnace-line">
预计剩余{{ formatCountdown(item.planEndTime) }}
</div> -->
<!-- <div v-else class="furnace-line">待入炉计划{{ planQueueCount(item.furnaceId) }}</div> -->
<div class="furnace-line">
备注{{ item.remark }}
</div>
<div class="furnace-actions">
<el-button size="mini" type="text" icon="el-icon-edit" @click="handleUpdate(item)">修改</el-button>
<el-button size="mini" type="text" icon="el-icon-delete" @click="handleDelete(item)">删除</el-button>
<!-- <el-button size="mini" type="text" icon="el-icon-setting" @click="handleToggleStatus(item)">
{{ item.status === 1 ? '停用' : '启用' }}
</el-button>
<el-button size="mini" type="text" icon="el-icon-time" @click="handleToggleBusy(item)">
{{ item.busyFlag === 1 ? '置闲' : '置忙' }}
</el-button> -->
</div>
</div>
</div>
</div>
<pagination v-show="total > 0" :total="total" :page.sync="queryParams.pageNum" :limit.sync="queryParams.pageSize" @pagination="getList" />
<el-dialog :title="title" :visible.sync="open" width="450px" append-to-body>
<el-form ref="form" :model="form" :rules="rules" label-width="100px">
<el-form-item label="炉编号" prop="furnaceCode">
<el-input v-model="form.furnaceCode" placeholder="请输入炉编号" />
</el-form-item>
<el-form-item label="名称" prop="furnaceName">
<el-input v-model="form.furnaceName" placeholder="请输入名称" />
</el-form-item>
<el-form-item label="状态" prop="status">
<el-radio-group v-model="form.status">
<el-radio :label="1">启用</el-radio>
<el-radio :label="0">停用</el-radio>
</el-radio-group>
</el-form-item>
<el-form-item label="忙碌" prop="busyFlag">
<el-radio-group v-model="form.busyFlag">
<el-radio :label="1">忙碌</el-radio>
<el-radio :label="0">空闲</el-radio>
</el-radio-group>
</el-form-item>
<el-form-item label="备注" prop="remark">
<el-input v-model="form.remark" type="textarea" placeholder="请输入备注" />
</el-form-item>
</el-form>
<div slot="footer" class="dialog-footer">
<el-button :loading="buttonLoading" type="primary" @click="submitForm"> </el-button>
<el-button @click="cancel"> </el-button>
</div>
</el-dialog>
</div>
</template>
<script>
import { listAnnealFurnace, getAnnealFurnace, addAnnealFurnace, updateAnnealFurnace, delAnnealFurnace, changeAnnealFurnaceStatus, changeAnnealFurnaceBusy } from "@/api/wms/annealFurnace";
import { listAnnealPlan } from "@/api/wms/annealPlan";
export default {
name: "AnnealFurnace",
data() {
return {
buttonLoading: false,
loading: true,
ids: [],
single: true,
multiple: true,
showSearch: true,
total: 0,
furnaceList: [],
planQueue: [],
title: "",
open: false,
queryParams: {
pageNum: 1,
pageSize: 20,
furnaceCode: undefined,
furnaceName: undefined,
status: undefined,
busyFlag: undefined,
},
form: {},
rules: {
furnaceCode: [{ required: true, message: "炉编号不能为空", trigger: "blur" }],
furnaceName: [{ required: true, message: "名称不能为空", trigger: "blur" }],
},
};
},
created() {
this.getList();
},
methods: {
getList() {
this.loading = true;
Promise.all([
listAnnealFurnace(this.queryParams),
listAnnealPlan({ pageNum: 1, pageSize: 100 })
]).then(([furnaceResponse, planResponse]) => {
this.furnaceList = furnaceResponse.rows;
this.total = furnaceResponse.total;
this.planQueue = planResponse.rows || [];
this.loading = false;
}).catch(() => {
this.loading = false;
});
},
planQueueCount(furnaceId) {
if (!this.planQueue) {
return 0;
}
return this.planQueue.filter(item => item.targetFurnaceId === furnaceId && item.status === 0).length;
},
formatCountdown(endTime) {
if (!endTime) return '-';
const end = new Date(endTime).getTime();
const now = Date.now();
let diff = Math.max(0, end - now);
const hours = Math.floor(diff / (1000 * 60 * 60));
diff %= 1000 * 60 * 60;
const minutes = Math.floor(diff / (1000 * 60));
diff %= 1000 * 60;
const seconds = Math.floor(diff / 1000);
return `${hours}小时${minutes}${seconds}`;
},
cancel() {
this.open = false;
this.reset();
},
reset() {
this.form = {
furnaceId: undefined,
furnaceCode: undefined,
furnaceName: undefined,
status: 1,
busyFlag: 0,
remark: undefined,
};
this.resetForm("form");
},
handleQuery() {
this.queryParams.pageNum = 1;
this.getList();
},
resetQuery() {
this.resetForm("queryForm");
this.handleQuery();
},
handleSelectionChange(selection) {
this.ids = selection.map(item => item.furnaceId);
this.single = selection.length !== 1;
this.multiple = !selection.length;
},
handleAdd() {
this.reset();
this.open = true;
this.title = "添加退火炉";
},
handleUpdate(row) {
this.loading = true;
this.reset();
const furnaceId = row.furnaceId || this.ids;
getAnnealFurnace(furnaceId).then(response => {
this.loading = false;
this.form = response.data;
if (this.form.status === null || this.form.status === undefined) {
this.form.status = 1;
}
if (this.form.busyFlag === null || this.form.busyFlag === undefined) {
this.form.busyFlag = 0;
}
this.open = true;
this.title = "修改退火炉";
});
},
submitForm() {
this.$refs["form"].validate(valid => {
if (valid) {
this.buttonLoading = true;
if (this.form.furnaceId != null) {
updateAnnealFurnace(this.form).then(() => {
this.$modal.msgSuccess("修改成功");
this.open = false;
this.getList();
}).finally(() => {
this.buttonLoading = false;
});
} else {
addAnnealFurnace(this.form).then(() => {
this.$modal.msgSuccess("新增成功");
this.open = false;
this.getList();
}).finally(() => {
this.buttonLoading = false;
});
}
}
});
},
handleDelete(row) {
const ids = row.furnaceId || this.ids;
this.$modal.confirm('是否确认删除炉子编号为"' + ids + '"的数据项?').then(() => {
this.loading = true;
return delAnnealFurnace(ids);
}).then(() => {
this.loading = false;
this.getList();
this.$modal.msgSuccess("删除成功");
}).finally(() => {
this.loading = false;
});
},
async handleToggleStatus(row) {
await this.$confirm(`确定${row.status === 1 ? '停用' : '启用'}该炉子吗?`, '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
});
this.loading = true;
await changeAnnealFurnaceStatus({
furnaceId: row.furnaceId,
status: row.status === 1 ? 0 : 1
});
this.loading = false;
this.getList();
},
async handleToggleBusy(row) {
await this.$confirm(`确定${row.busyFlag === 1 ? '置闲' : '置忙'}该炉子吗?`, '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
});
this.loading = true;
await changeAnnealFurnaceBusy({
furnaceId: row.furnaceId,
busyFlag: row.busyFlag === 1 ? 0 : 1
});
this.loading = false;
this.getList();
}
}
};
</script>
<style scoped>
.furnace-container {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(240px, 1fr));
gap: 12px;
margin-bottom: 16px;
}
.furnace-card {
border: 1px solid #f0f2f5;
border-radius: 8px;
padding: 12px;
background: #ffffff;
box-shadow: 0 1px 4px rgba(0, 0, 0, 0.04);
}
.furnace-header {
display: flex;
align-items: center;
gap: 10px;
margin-bottom: 8px;
}
.furnace-name {
font-weight: 600;
color: #303133;
}
.furnace-code {
font-size: 12px;
color: #909399;
}
.furnace-body {
font-size: 13px;
color: #606266;
}
.furnace-line {
margin-bottom: 4px;
}
.furnace-actions {
margin-top: 12px;
display: flex;
gap: 8px;
flex-wrap: wrap;
}
.furnace-idle {
font-size: 28px;
color: #303133;
}
.furnace-busy {
font-size: 28px;
color: #f56c6c;
}
.busy-text {
color: #f56c6c;
}
.idle-text {
color: #67c23a;
}
.active-text {
color: #67c23a;
}
.inactive-text {
color: #909399;
}
</style>

View File

@@ -1,332 +0,0 @@
·<template>
<div class="app-container">
<el-row :gutter="12" class="summary-row">
<el-col :xs="24" :sm="12" :md="6">
<div class="summary-card">
<div class="summary-title">当前计划数</div>
<div class="summary-value">{{ overview.totalPlanCount || 0 }}</div>
</div>
</el-col>
<el-col :xs="24" :sm="12" :md="6">
<div class="summary-card">
<div class="summary-title">退火炉(忙碌/全部)</div>
<div class="summary-value">{{ overview.furnaceBusyCount || 0 }}/{{ overview.furnaceTotal || 0 }}</div>
</div>
</el-col>
<el-col :xs="24" :sm="12" :md="6">
<div class="summary-card">
<div class="summary-title">待退火钢卷</div>
<div class="summary-value">{{ overview.pendingCoilCount || 0 }}</div>
</div>
</el-col>
<el-col :xs="24" :sm="12" :md="6">
<div class="summary-card">
<div class="summary-title">今日已完成</div>
<div class="summary-value">
计划 {{ overview.todayDonePlanCount || 0 }} | 钢卷 {{ overview.todayDoneCoilCount || 0 }}
</div>
</div>
</el-col>
</el-row>
<el-card shadow="never" class="panel">
<div slot="header" class="panel-title">退火炉状态</div>
<div class="furnace-grid">
<div v-for="item in overview.furnaces" :key="item.furnaceId" class="furnace-card">
<div class="furnace-header">
<svg-icon icon-class="furnace" :class="item.busyFlag === 1 ? 'furnace-busy' : 'furnace-idle'" />
<div>
<div class="furnace-name">{{ item.furnaceName }}</div>
<div class="furnace-code">{{ item.furnaceCode }}</div>
</div>
</div>
<div class="furnace-body">
<div class="furnace-line">状态<span :class="item.busyFlag === 1 ? 'busy-text' : 'idle-text'">{{ item.busyFlag === 1 ? '忙碌' : '空闲' }}</span></div>
<div v-if="item.busyFlag === 1" class="furnace-line">
计划{{ item.currentPlanNo || '-' }}
</div>
<div v-if="item.busyFlag === 1" class="furnace-line">
当前钢卷{{ item.coilCount || 0 }}
</div>
<div v-if="item.busyFlag === 1" class="furnace-line">
预计剩余{{ formatCountdown(item.planEndTime) }}
</div>
<div v-else class="furnace-line">待入炉计划{{ planQueueCount(item.furnaceId) }}</div>
</div>
</div>
</div>
</el-card>
<el-card shadow="never" class="panel">
<div slot="header" class="panel-title">计划队列</div>
<el-table :data="overview.planQueue" v-loading="loading">
<el-table-column label="计划号" prop="planNo" align="center" />
<el-table-column label="目标炉子" prop="targetFurnaceName" align="center" />
<el-table-column label="状态" prop="status" align="center">
<template slot-scope="scope">
<el-tag :type="statusTag(scope.row.status)">{{ statusLabel(scope.row.status) }}</el-tag>
</template>
</el-table-column>
<el-table-column label="计划时间" prop="planStartTime" align="center" width="160">
<template slot-scope="scope">
{{ parseTime(scope.row.planStartTime, '{y}-{m}-{d} {h}:{i}') }}
</template>
</el-table-column>
<el-table-column label="钢卷数" prop="coilCount" align="center" />
<el-table-column label="操作" align="center" width="160">
<template slot-scope="scope">
<el-button v-if="scope.row.status === 0" size="mini" type="primary" :disabled="!scope.row.coilCount" @click="handleInFurnace(scope.row)">入炉</el-button>
<el-button v-if="scope.row.status === 2" size="mini" type="success" @click="openCompleteDialog(scope.row)">完成</el-button>
</template>
</el-table-column>
</el-table>
</el-card>
<el-dialog title="完成退火" :visible.sync="completeOpen" width="720px" append-to-body>
<div class="complete-tip">请为每条钢卷分配实际库位未分配将无法完成</div>
<el-table :data="completeCoils" v-loading="completeLoading" height="360px">
<el-table-column label="入场钢卷号" prop="enterCoilNo" align="center" />
<el-table-column label="实际库位" align="center" width="260">
<template slot-scope="scope">
<ActualWarehouseSelect v-model="scope.row.actualWarehouseId" :clearInput="false" :showEmpty="false" :width="240" />
</template>
</el-table-column>
</el-table>
<div slot="footer" class="dialog-footer">
<el-button type="primary" @click="submitComplete"> </el-button>
<el-button @click="completeOpen = false"> </el-button>
</div>
</el-dialog>
</div>
</template>
<script>
import { getAnnealOverview } from "@/api/wms/annealOverview";
import { inFurnace, completeAnnealPlan, listAnnealPlanCoils } from "@/api/wms/annealPlan";
import ActualWarehouseSelect from "@/components/KLPService/ActualWarehouseSelect";
export default {
name: "AnnealOverview",
components: {
ActualWarehouseSelect
},
data() {
return {
loading: true,
overview: {
furnaces: [],
planQueue: []
},
timer: null,
completeOpen: false,
completeLoading: false,
completeCoils: [],
completePlanId: null,
actualWarehouseOptions: []
};
},
created() {
this.fetchOverview();
this.startTimer();
},
beforeDestroy() {
this.stopTimer();
},
methods: {
fetchOverview() {
this.loading = true;
getAnnealOverview().then(response => {
this.overview = response.data || { furnaces: [], planQueue: [] };
this.loading = false;
});
},
startTimer() {
this.stopTimer();
this.timer = setInterval(() => {
this.$forceUpdate();
}, 1000);
},
stopTimer() {
if (this.timer) {
clearInterval(this.timer);
this.timer = null;
}
},
openCompleteDialog(row) {
this.completePlanId = row.planId;
this.completeOpen = true;
this.completeLoading = true;
listAnnealPlanCoils(row.planId).then(response => {
this.completeCoils = (response.data || []).map(item => ({
coilId: item.coilId,
enterCoilNo: item.enterCoilNo,
actualWarehouseId: null
}));
this.completeLoading = false;
}).catch(() => {
this.completeLoading = false;
});
},
submitComplete() {
const missing = this.completeCoils.filter(item => !item.actualWarehouseId);
if (missing.length > 0) {
this.$message.warning('请先为所有钢卷分配实际库位');
return;
}
this.completeLoading = true;
completeAnnealPlan({
planId: this.completePlanId,
locations: this.completeCoils.map(item => ({
coilId: item.coilId,
actualWarehouseId: item.actualWarehouseId
}))
}).then(() => {
this.$message.success('已完成');
this.completeOpen = false;
this.fetchOverview();
}).finally(() => {
this.completeLoading = false;
});
},
planQueueCount(furnaceId) {
if (!this.overview.planQueue) {
return 0;
}
return this.overview.planQueue.filter(item => item.targetFurnaceId === furnaceId && item.status === 0).length;
},
statusLabel(status) {
const map = { 0: '未开始', 2: '进行中', 3: '已完成' };
return map[status] || '未开始';
},
statusTag(status) {
const map = { 0: 'info', 2: 'success', 3: 'success' };
return map[status] || 'info';
},
formatCountdown(endTime) {
if (!endTime) return '-';
const end = new Date(endTime).getTime();
const now = Date.now();
let diff = Math.max(0, end - now);
const hours = Math.floor(diff / (1000 * 60 * 60));
diff %= 1000 * 60 * 60;
const minutes = Math.floor(diff / (1000 * 60));
diff %= 1000 * 60;
const seconds = Math.floor(diff / 1000);
return `${hours}小时${minutes}${seconds}`;
},
handleInFurnace(row) {
const furnace = this.overview.furnaces.find(item => item.furnaceId === row.targetFurnaceId);
if (furnace && furnace.busyFlag === 1) {
this.$message.warning(`炉子正忙:计划${furnace.currentPlanNo || ''},钢卷${furnace.coilCount || 0}`);
return;
}
this.$confirm('点击入炉后钢卷将会被释放库位,是否确认入炉', '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
}).then(() => {
return inFurnace({ planId: row.planId });
}).then(() => {
row.status = 2;
this.$message.success('入炉成功');
this.fetchOverview();
});
}
}
};
</script>
<style scoped>
.summary-row {
margin-bottom: 12px;
}
.summary-card {
border: 1px solid #f0f2f5;
border-radius: 6px;
padding: 12px 16px;
height: 90px;
display: flex;
flex-direction: column;
justify-content: center;
background: #ffffff;
box-shadow: 0 1px 4px rgba(0, 0, 0, 0.04);
}
.summary-title {
font-size: 13px;
color: #909399;
}
.summary-value {
margin-top: 8px;
font-size: 18px;
font-weight: 600;
color: #303133;
}
.panel {
margin-bottom: 16px;
border: 1px solid #f0f2f5;
background: #ffffff;
box-shadow: 0 1px 4px rgba(0, 0, 0, 0.04);
}
.panel-title {
font-weight: 500;
color: #606266;
}
.furnace-grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(240px, 1fr));
gap: 12px;
}
.furnace-card {
border: 1px solid #f0f2f5;
border-radius: 8px;
padding: 12px;
background: #ffffff;
box-shadow: 0 1px 4px rgba(0, 0, 0, 0.04);
}
.furnace-header {
display: flex;
align-items: center;
gap: 10px;
margin-bottom: 8px;
}
.furnace-name {
font-weight: 600;
color: #303133;
}
.furnace-code {
font-size: 12px;
color: #909399;
}
.furnace-body {
font-size: 13px;
color: #606266;
}
.furnace-line {
margin-bottom: 4px;
}
.furnace-idle {
font-size: 28px;
color: #303133;
}
.furnace-busy {
font-size: 28px;
color: #f56c6c;
}
.busy-text {
color: #f56c6c;
}
.idle-text {
color: #67c23a;
}
:deep(.el-table th),
:deep(.el-table td) {
border-bottom: 1px solid #f0f2f5;
}
:deep(.el-table::before) {
background-color: transparent;
}
:deep(.el-card__header) {
border-bottom: 1px solid #f0f2f5;
background: #ffffff;
}
</style>

View File

@@ -1,132 +0,0 @@
<template>
<div class="app-container" v-loading="loading">
<el-form :model="queryParams" ref="queryForm" size="small" :inline="true" label-width="90px">
<el-form-item label="开始时间" prop="startTime">
<el-date-picker v-model="queryParams.startTime" type="datetime" value-format="yyyy-MM-dd HH:mm:ss"
placeholder="选择开始时间" />
</el-form-item>
<el-form-item label="结束时间" prop="endTime">
<el-date-picker v-model="queryParams.endTime" type="datetime" value-format="yyyy-MM-dd HH:mm:ss"
placeholder="选择结束时间" />
</el-form-item>
<el-form-item label="目标炉" prop="targetFurnaceId">
<el-select v-model="queryParams.targetFurnaceId" placeholder="请选择" clearable filterable>
<el-option v-for="item in furnaceOptions" :key="item.furnaceId" :label="item.furnaceName"
:value="item.furnaceId" />
</el-select>
</el-form-item>
<el-form-item label="计划号" prop="planNo">
<el-input v-model="queryParams.planNo" placeholder="请输入计划号" clearable />
</el-form-item>
<el-form-item label="入场钢卷号" prop="enterCoilNo">
<el-input v-model="queryParams.enterCoilNo" placeholder="请输入入场钢卷号" clearable />
</el-form-item>
<el-form-item>
<el-button type="primary" icon="el-icon-search" size="mini" @click="handleQuery">查询</el-button>
<el-button icon="el-icon-refresh" size="mini" @click="resetQuery">重置</el-button>
</el-form-item>
</el-form>
<el-descriptions title="统计信息" :column="3" class="summary-block" border>
<el-descriptions-item label="计划数量">{{ summary.planCount || 0 }}</el-descriptions-item>
<el-descriptions-item label="钢卷数量">{{ summary.coilCount || 0 }}</el-descriptions-item>
<el-descriptions-item label="总重量">{{ summary.totalWeight || 0 }} t</el-descriptions-item>
</el-descriptions>
<el-table :data="detailList" border height="calc(100vh - 330px)">
<el-table-column label="计划号" prop="planNo" align="center" />
<el-table-column label="目标炉" prop="targetFurnaceName" align="center" />
<el-table-column label="开始时间" prop="actualStartTime" align="center" width="160">
<template slot-scope="scope">
{{ parseTime(scope.row.actualStartTime, '{y}-{m}-{d} {h}:{i}') }}
</template>
</el-table-column>
<el-table-column label="结束时间" prop="endTime" align="center" width="160">
<template slot-scope="scope">
{{ parseTime(scope.row.endTime, '{y}-{m}-{d} {h}:{i}') }}
</template>
</el-table-column>
<el-table-column label="产品类型" align="center" width="250">
<template slot-scope="scope">
<ProductInfo v-if="scope.row.itemType == 'product'" :product="scope.row.product" />
<RawMaterialInfo v-else-if="scope.row.itemType === 'raw_material'" :material="scope.row.rawMaterial" />
</template>
</el-table-column>
<el-table-column label="逻辑库区" prop="warehouseName" align="center" />
<el-table-column label="入场钢卷号" prop="enterCoilNo" align="center" />
<el-table-column label="当前钢卷号" prop="currentCoilNo" align="center" />
<el-table-column label="重量(t)" prop="netWeight" align="center" />
</el-table>
</div>
</template>
<script>
import { getAnnealPerformance } from "@/api/wms/annealPerformance";
import { listAnnealFurnace } from "@/api/wms/annealFurnace";
import ProductInfo from "@/components/KLPService/Renderer/ProductInfo";
import RawMaterialInfo from "@/components/KLPService/Renderer/RawMaterialInfo";
import CoilNo from "@/components/KLPService/Renderer/CoilNo.vue";
export default {
name: "AnnealPerformance",
data() {
return {
loading: false,
queryParams: {
startTime: undefined,
endTime: undefined,
targetFurnaceId: undefined,
planNo: undefined,
enterCoilNo: undefined,
},
summary: {},
detailList: [],
furnaceOptions: [],
};
},
components: {
ProductInfo,
RawMaterialInfo,
CoilNo,
},
created() {
this.loadFurnaces();
this.handleQuery();
},
methods: {
loadFurnaces() {
listAnnealFurnace({ pageNum: 1, pageSize: 999, status: 1 }).then(response => {
this.furnaceOptions = response.rows || [];
});
},
handleQuery() {
this.loading = true;
getAnnealPerformance(this.queryParams).then(response => {
const data = response.data || {};
this.summary = data.summary || {};
this.detailList = data.details.map(item => {
return item.coils.map(coil => {
return {
...item,
...coil,
}
})
}).flat();
this.loading = false;
}).catch(() => {
this.loading = false;
});
},
resetQuery() {
this.resetForm("queryForm");
this.handleQuery();
}
}
};
</script>
<style scoped>
.summary-block {
margin: 12px 0 16px;
}
</style>

View File

@@ -1,732 +0,0 @@
<template>
<div class="app-container">
<el-form :model="queryParams" ref="queryForm" size="small" :inline="true" v-show="showSearch" label-width="90px">
<el-form-item label="计划号" prop="planNo">
<el-input v-model="queryParams.planNo" placeholder="请输入计划号" clearable @keyup.enter.native="handleQuery" />
</el-form-item>
<el-form-item label="目标炉" prop="targetFurnaceId">
<el-select v-model="queryParams.targetFurnaceId" placeholder="请选择" clearable filterable>
<el-option v-for="item in furnaceOptions" :key="item.furnaceId" :label="item.furnaceName"
:value="item.furnaceId" />
</el-select>
</el-form-item>
<el-form-item label="状态" prop="status">
<el-select v-model="queryParams.status" placeholder="请选择" clearable>
<el-option label="未开始" :value="0" />
<el-option label="进行中" :value="2" />
<el-option label="已完成" :value="3" />
</el-select>
</el-form-item>
<el-form-item>
<el-button type="primary" icon="el-icon-search" size="mini" @click="handleQuery">搜索</el-button>
<el-button icon="el-icon-refresh" size="mini" @click="resetQuery">重置</el-button>
</el-form-item>
</el-form>
<!-- <el-row :gutter="10" class="mb8">
<el-col :span="1.5">
<el-button type="primary" plain icon="el-icon-plus" size="mini" @click="handleAdd">新增</el-button>
</el-col>
<el-col :span="1.5">
<el-button type="success" plain icon="el-icon-edit" size="mini" :disabled="single"
@click="handleUpdate">修改</el-button>
</el-col>
<el-col :span="1.5">
<el-button type="danger" plain icon="el-icon-delete" size="mini" :disabled="multiple"
@click="handleDelete">删除</el-button>
</el-col>
<right-toolbar :showSearch.sync="showSearch" @queryTable="getList"></right-toolbar>
</el-row> -->
<KLPTable v-loading="loading" :data="planList" @selection-change="handleSelectionChange"
@row-click="handleRowClick">
<el-table-column type="selection" width="55" align="center" />
<el-table-column label="计划号" align="center" prop="planNo" />
<el-table-column label="计划时间" align="center" prop="planStartTime" width="160">
<template slot-scope="scope">
{{ parseTime(scope.row.planStartTime, '{y}-{m}-{d} {h}:{i}') }}
</template>
</el-table-column>
<el-table-column label="目标炉" align="center" prop="targetFurnaceName" />
<el-table-column label="状态" align="center" prop="status">
<template slot-scope="scope">
<el-tag :type="statusTag(scope.row.status)">{{ statusLabel(scope.row.status) }}</el-tag>
</template>
</el-table-column>
<el-table-column label="开始时间" align="center" prop="actualStartTime" width="160">
<template slot-scope="scope">
{{ parseTime(scope.row.actualStartTime || scope.row.planStartTime, '{y}-{m}-{d} {h}:{i}') }}
</template>
</el-table-column>
<el-table-column label="结束时间" align="center" prop="endTime" width="160">
<template slot-scope="scope">
{{ parseTime(scope.row.endTime, '{y}-{m}-{d} {h}:{i}') }}
</template>
</el-table-column>
<!-- <el-table-column label="操作" align="center" class-name="small-padding fixed-width" width="220">
<template slot-scope="scope">
<el-button size="mini" type="text" icon="el-icon-edit" @click.stop="handleUpdate(scope.row)">修改</el-button>
<el-button size="mini" type="text" icon="el-icon-delete" @click.stop="handleDelete(scope.row)">删除</el-button>
<el-button size="mini" type="text" icon="el-icon-s-operation"
@click.stop="openStatusDialog(scope.row)">状态</el-button>
<el-button v-if="scope.row.status === 0" size="mini" type="text" icon="el-icon-s-flag"
:disabled="!scope.row.coilCount" @click.stop="handleInFurnace(scope.row)">入炉</el-button>
<el-button v-if="scope.row.status === 2" size="mini" type="text" icon="el-icon-check"
@click.stop="handleComplete(scope.row)">完成</el-button>
</template>
</el-table-column> -->
</KLPTable>
<pagination v-show="total > 0" :total="total" :page.sync="queryParams.pageNum" :limit.sync="queryParams.pageSize"
@pagination="getList" />
<el-row :gutter="20" class="mt16">
<el-col :span="12">
<el-card shadow="never" class="panel-card">
<div slot="header" class="panel-header">
<span>领料列表</span>
<el-button size="mini" icon="el-icon-refresh" @click="getMaterialCoils">刷新</el-button>
</div>
<el-form :model="materialQueryParams" ref="materialQueryForm" size="small" :inline="true" class="mb8">
<el-form-item label="入场钢卷号" prop="enterCoilNo">
<el-input v-model="materialQueryParams.enterCoilNo" placeholder="请输入入场钢卷号" clearable
style="width: 160px" />
</el-form-item>
<el-form-item label="当前钢卷号" prop="currentCoilNo">
<el-input v-model="materialQueryParams.currentCoilNo" placeholder="请输入当前钢卷号" clearable
style="width: 160px" />
</el-form-item>
<el-form-item>
<el-button type="primary" icon="el-icon-search" size="mini" @click="handleMaterialQuery">搜索</el-button>
<el-button icon="el-icon-refresh" size="mini" @click="resetMaterialQuery">重置</el-button>
</el-form-item>
</el-form>
<div v-loading="materialLoading" class="material-grid">
<div v-if="materialList.length === 0" class="empty-tip">
<el-empty description="暂无待领物料" />
</div>
<div v-for="item in materialList" :key="item.coilId" class="material-card">
<div class="material-header">
<div class="material-text-wrap">
<div class="material-title">{{ item.currentCoilNo || '-' }}</div>
<div class="material-sub">入场{{ item.enterCoilNo || '-' }}</div>
</div>
<el-button type="primary" size="mini" @click="handleAddToPlan(item)"
:disabled="!currentPlan.planId">加入计划</el-button>
</div>
<div class="material-body">
<div class="material-row">厂家{{ item.supplierCoilNo || '-' }}</div>
<div class="material-row">库位{{ item.actualWarehouseName || '-' }}</div>
<div class="material-row">重量{{ item.netWeight || '-' }}t</div>
</div>
</div>
</div>
<pagination v-show="materialTotal > 0" :total="materialTotal" :page.sync="materialQueryParams.pageNum"
:limit.sync="materialQueryParams.pageSize" @pagination="getMaterialCoils" />
</el-card>
</el-col>
<el-col :span="12">
<el-card shadow="never" class="panel-card">
<div slot="header" class="panel-header">
<span>退火计划</span>
<div>
<el-button size="mini" icon="el-icon-refresh" @click="loadPlanCoils"
:disabled="!currentPlan.planId">刷新</el-button>
<el-button size="mini" type="primary" icon="el-icon-s-flag"
:disabled="!currentPlan.planId || currentPlan.status !== 0 || !currentPlan.coilCount"
@click="handleInFurnace(currentPlan)">入炉</el-button>
<el-button size="mini" type="success" icon="el-icon-check"
:disabled="!currentPlan.planId || currentPlan.status !== 2" @click="openCompleteDialog">完成退火</el-button>
</div>
</div>
<div v-if="!currentPlan.planId" class="empty-tip">
<el-empty description="请点击上方选择一个计划" />
</div>
<div v-else>
<div class="plan-summary">
<div>计划号{{ currentPlan.planNo }}</div>
<div>目标炉{{ currentPlan.targetFurnaceName || '-' }}</div>
<div>状态{{ statusLabel(currentPlan.status) }}</div>
<div>
<el-button size="mini" type="primary" icon="el-icon-s-flag" :disabled="currentPlan.status !== 0"
@click="handleInFurnace(currentPlan)">入炉</el-button>
</div>
</div>
<el-table :data="coilList" v-loading="coilLoading" class="light-table">
<el-table-column label="入场钢卷号" align="center" prop="enterCoilNo" />
<el-table-column label="创建时间" align="center" prop="createTime" width="200">
<template slot-scope="scope">
<el-date-picker style="width: 185px" v-model="scope.row.createTime" type="datetime" value-format="yyyy-MM-dd HH:mm:ss"
placeholder="选择创建时间" @change="handlePLanCoilChange(scope.row)"/>
</template>
</el-table-column>
<el-table-column label="钢卷去向" align="center" width="220">
<template slot-scope="scope">
<WarehouseSelect v-model="scope.row.logicWarehouseId" @change="handlePLanCoilChange(scope.row)"/>
</template>
</el-table-column>
<el-table-column label="钢卷层级" align="center" width="80">
<template slot-scope="scope">
<el-input v-model="scope.row.furnaceLevel" placeholder="请输入钢卷层级" @change="handlePLanCoilChange(scope.row)"/>
</template>
</el-table-column>
<el-table-column label="操作" align="center" width="100">
<template slot-scope="scope">
<el-button size="mini" type="text" icon="el-icon-delete" @click="handleUnbind(scope.row)">
{{ unbindLabel(currentPlan.status) }}
</el-button>
</template>
</el-table-column>
</el-table>
</div>
</el-card>
</el-col>
</el-row>
<el-dialog title="完成退火" :visible.sync="completeOpen" width="720px" append-to-body>
<div class="complete-tip">请为每条钢卷分配实际库位未分配将无法完成</div>
<el-table :data="completeCoils" v-loading="completeLoading" height="360px">
<el-table-column label="入场钢卷号" prop="enterCoilNo" align="center" />
<el-table-column label="钢卷去向" align="center" width="240">
<template slot-scope="scope">
<WarehouseSelect v-model="scope.row.warehouseId" />
</template>
</el-table-column>
</el-table>
<div slot="footer" class="dialog-footer">
<el-button type="primary" @click="submitComplete"> </el-button>
<el-button @click="completeOpen = false"> </el-button>
</div>
</el-dialog>
<el-dialog :title="title" :visible.sync="open" width="520px" append-to-body>
<el-form ref="form" :model="form" :rules="rules" label-width="110px">
<el-form-item label="计划号" prop="planNo">
<el-input v-model="form.planNo" placeholder="请输入计划号" />
</el-form-item>
<el-form-item label="计划开始" prop="planStartTime">
<el-date-picker clearable v-model="form.planStartTime" type="datetime" value-format="yyyy-MM-dd HH:mm:ss"
placeholder="请选择计划时间" />
</el-form-item>
<el-form-item label="目标炉" prop="targetFurnaceId">
<el-select v-model="form.targetFurnaceId" placeholder="请选择" filterable>
<el-option v-for="item in furnaceOptions" :key="item.furnaceId" :label="item.furnaceName"
:value="item.furnaceId" />
</el-select>
</el-form-item>
<el-form-item label="状态" prop="status">
<el-select v-model="form.status" placeholder="请选择">
<el-option label="未开始" :value="0" />
<el-option label="进行中" :value="2" />
<el-option label="已完成" :value="3" />
</el-select>
</el-form-item>
<el-form-item label="备注" prop="remark">
<el-input v-model="form.remark" type="textarea" placeholder="请输入备注" />
</el-form-item>
</el-form>
<div slot="footer" class="dialog-footer">
<el-button :loading="buttonLoading" type="primary" @click="submitForm"> </el-button>
<el-button @click="cancel"> </el-button>
</div>
</el-dialog>
<el-dialog title="更新状态" :visible.sync="statusOpen" width="360px" append-to-body>
<el-form label-width="90px">
<el-form-item label="状态">
<el-select v-model="statusForm.status" placeholder="请选择">
<el-option label="未开始" :value="0" />
<el-option label="进行中" :value="2" />
<el-option label="已完成" :value="3" />
</el-select>
</el-form-item>
</el-form>
<div slot="footer" class="dialog-footer">
<el-button type="primary" @click="submitStatus"> </el-button>
<el-button @click="statusOpen = false"> </el-button>
</div>
</el-dialog>
</div>
</template>
<script>
import { listAnnealPlan, updateAnnealPlanCoil, getAnnealPlan, addAnnealPlan, updateAnnealPlan, delAnnealPlan, changeAnnealPlanStatus, inFurnace, completeAnnealPlan, listAnnealPlanCoils, bindAnnealPlanCoils, unbindAnnealPlanCoil } from "@/api/wms/annealPlan";
import { listAnnealFurnace } from "@/api/wms/annealFurnace";
import { listMaterialCoil } from "@/api/wms/coil";
import WarehouseSelect from "@/components/KLPService/WarehouseSelect";
export default {
name: "AnnealPlan",
components: {
WarehouseSelect
},
data() {
return {
buttonLoading: false,
loading: true,
coilLoading: false,
ids: [],
single: true,
multiple: true,
showSearch: true,
total: 0,
planList: [],
furnaceOptions: [],
title: "",
open: false,
statusOpen: false,
queryParams: {
pageNum: 1,
pageSize: 20,
planNo: undefined,
targetFurnaceId: undefined,
status: undefined,
},
form: {},
rules: {
planNo: [{ required: true, message: "计划号不能为空", trigger: "blur" }],
targetFurnaceId: [{ required: true, message: "目标炉子不能为空", trigger: "change" }],
},
currentPlan: {},
coilList: [],
materialLoading: false,
materialTotal: 0,
materialList: [],
materialQueryParams: {
pageNum: 1,
pageSize: 20,
enterCoilNo: undefined,
currentCoilNo: undefined,
status: 0,
dataType: 1,
},
statusForm: {
planId: undefined,
status: undefined,
},
completeOpen: false,
completeLoading: false,
completePlanId: null,
completeCoils: [],
};
},
created() {
this.getList();
this.loadFurnaces();
this.getMaterialCoils();
this.loadActualWarehouses();
},
methods: {
getList() {
this.loading = true;
listAnnealPlan(this.queryParams).then(response => {
this.planList = response.rows;
this.total = response.total;
this.loading = false;
});
},
loadFurnaces() {
listAnnealFurnace({ pageNum: 1, pageSize: 999, status: 1 }).then(response => {
this.furnaceOptions = response.rows || [];
});
},
handleRowClick(row) {
this.currentPlan = row;
this.loadPlanCoils();
},
handlePLanCoilChange(row) {
updateAnnealPlanCoil(row).then(() => {
this.$message.success('已更新');
}).finally(() => {
this.loadPlanCoils();
});
},
getMaterialCoils() {
this.materialLoading = true;
listMaterialCoil(this.materialQueryParams).then(response => {
this.materialList = response.rows || [];
this.materialTotal = response.total || 0;
this.materialLoading = false;
});
},
handleMaterialQuery() {
this.materialQueryParams.pageNum = 1;
this.getMaterialCoils();
},
resetMaterialQuery() {
this.resetForm("materialQueryForm");
this.resetMaterialForm();
this.handleMaterialQuery();
},
openCompleteDialog() {
if (!this.currentPlan.planId) {
this.$message.warning('请先选择计划');
return;
}
if (this.currentPlan.status !== 2) {
this.$message.warning('计划未进行中,无法完成');
return;
}
this.completePlanId = this.currentPlan.planId;
this.completeOpen = true;
this.completeLoading = true;
listAnnealPlanCoils(this.currentPlan.planId).then(response => {
this.completeCoils = (response.data || []).map(item => ({
coilId: item.coilId,
enterCoilNo: item.enterCoilNo,
warehouseId: item.logicWarehouseId || null
}));
this.completeLoading = false;
}).catch(() => {
this.completeLoading = false;
});
},
handleAddToPlan(item) {
if (!this.currentPlan.planId) {
this.$message.warning('请先选择计划');
return;
}
if (this.currentPlan.status === 2) {
this.$message.warning('当前计划进行中,无法再领料');
return;
}
this.coilLoading = true;
bindAnnealPlanCoils({
planId: this.currentPlan.planId,
coilId: item.coilId
}).then(() => {
this.$message.success('已加入计划');
this.loadPlanCoils();
}).finally(() => {
this.coilLoading = false;
});
},
loadPlanCoils() {
if (!this.currentPlan.planId) {
this.coilList = [];
return;
}
this.coilLoading = true;
listAnnealPlanCoils(this.currentPlan.planId).then(response => {
// 按照层级排序,数字大的考前
this.coilList = response.data.sort((a, b) => b.furnaceLevel - a.furnaceLevel) || [];
this.coilLoading = false;
});
},
cancel() {
this.open = false;
this.reset();
},
reset() {
this.form = {
planId: undefined,
planNo: undefined,
planStartTime: undefined,
targetFurnaceId: undefined,
status: 0,
remark: undefined,
};
this.resetForm("form");
},
resetMaterialForm() {
this.materialQueryParams = {
pageNum: 1,
pageSize: 10,
enterCoilNo: undefined,
currentCoilNo: undefined,
status: 0,
dataType: 1,
};
},
handleQuery() {
this.queryParams.pageNum = 1;
this.getList();
},
resetQuery() {
this.resetForm("queryForm");
this.handleQuery();
},
handleSelectionChange(selection) {
this.ids = selection.map(item => item.planId);
this.single = selection.length !== 1;
this.multiple = !selection.length;
},
handleAdd() {
this.reset();
this.open = true;
this.title = "添加退火计划";
},
handleUpdate(row) {
this.loading = true;
this.reset();
const planId = row.planId || this.ids;
getAnnealPlan(planId).then(response => {
this.loading = false;
this.form = response.data;
this.open = true;
this.title = "修改退火计划";
});
},
submitForm() {
this.$refs["form"].validate(valid => {
if (valid) {
this.buttonLoading = true;
if (this.form.planId != null) {
updateAnnealPlan(this.form).then(() => {
this.$modal.msgSuccess("修改成功");
this.open = false;
this.getList();
}).finally(() => {
this.buttonLoading = false;
});
} else {
addAnnealPlan(this.form).then(() => {
this.$modal.msgSuccess("新增成功");
this.open = false;
this.getList();
}).finally(() => {
this.buttonLoading = false;
});
}
}
});
},
handleDelete(row) {
const ids = row.planId || this.ids;
this.$modal.confirm('是否确认删除计划编号为"' + ids + '"的数据项?').then(() => {
this.loading = true;
return delAnnealPlan(ids);
}).then(() => {
this.loading = false;
this.getList();
this.$modal.msgSuccess("删除成功");
}).finally(() => {
this.loading = false;
});
},
openStatusDialog(row) {
this.statusForm = {
planId: row.planId,
status: row.status,
};
this.statusOpen = true;
},
submitStatus() {
changeAnnealPlanStatus(this.statusForm).then(() => {
this.$modal.msgSuccess("状态已更新");
this.statusOpen = false;
this.getList();
});
},
async handleInFurnace(row) {
await this.$confirm('点击入炉后钢卷将会被释放库位,是否确认入炉', '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
});
this.loading = true;
await inFurnace({ planId: row.planId });
this.loading = false;
row.status = 2;
row.actualStartTime = new Date();
if (this.currentPlan.planId === row.planId) {
this.currentPlan.status = 2;
this.currentPlan.actualStartTime = row.actualStartTime;
this.loadPlanCoils();
}
this.getList();
this.$message.success('已入炉');
},
submitComplete() {
const locations = (this.completeCoils || []).map(item => ({
coilId: item.coilId,
warehouseId: item.warehouseId
}));
const missing = locations.filter(item => !item.warehouseId);
if (missing.length > 0) {
this.$message.warning('请先为所有钢卷分配实际库位');
return;
}
this.completeLoading = true;
completeAnnealPlan({
planId: this.completePlanId,
locations: locations
}).then(() => {
this.$message.success('已完成');
this.completeOpen = false;
this.getList();
this.loadPlanCoils();
}).finally(() => {
this.completeLoading = false;
});
},
handleUnbind(row) {
this.$confirm('确定解绑该钢卷吗?', '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
}).then(() => {
return unbindAnnealPlanCoil({
planId: this.currentPlan.planId,
coilId: row.coilId
});
}).then(() => {
this.$message.success('解绑成功');
this.loadPlanCoils();
});
},
statusLabel(status) {
if (status === 2) {
return '进行中';
}
if (status === 3) {
return '已完成';
}
return '未开始';
},
statusTag(status) {
if (status === 2) {
return 'success';
}
if (status === 3) {
return 'info';
}
return 'warning';
},
unbindLabel(status) {
if (status === 2) {
return '取消';
}
if (status === 3) {
return '撤回';
}
return '解绑';
}
}
};
</script>
<style scoped>
.mt16 {
margin-top: 16px;
}
.empty-tip {
margin-top: 10px;
}
.panel-card {
border: 1px solid #f0f2f5;
background: #ffffff;
}
.panel-header {
display: flex;
align-items: center;
justify-content: space-between;
}
/* ========== 修复在这里 ========== */
.material-grid {
display: grid;
/* 核心修复:去掉固定 4 列,改用自动填充,实现真正自适应 */
grid-template-columns: repeat(auto-fill, minmax(220px, 1fr));
gap: 12px;
min-height: 120px;
/* 必须加,让 grid 不受父级弹性压缩影响 */
width: 100%;
box-sizing: border-box;
}
/* 媒体查询只需要控制最小宽度即可,不用写死列数 */
@media (max-width: 768px) {
.material-grid {
grid-template-columns: 1fr;
}
}
/* =============================== */
.material-card {
border: 1px solid #e9ecf2;
border-radius: 8px;
padding: 12px;
background: #ffffff;
box-shadow: 0 1px 4px rgba(0, 0, 0, 0.04);
}
.material-header {
display: flex;
align-items: center;
justify-content: space-between;
gap: 8px;
margin-bottom: 8px;
/* 新增:让文字区域和按钮分行适配小宽度 */
flex-wrap: wrap;
}
/* 新增:文字容器,限制宽度并溢出省略 */
.material-text-wrap {
flex: 1;
min-width: 0;
/* 关键让flex子元素遵守宽度限制 */
}
.material-title {
font-weight: 600;
color: #303133;
/* 新增:标题溢出省略 */
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
max-width: 180px;
/* 可根据需要调整最大宽度 */
}
.material-sub {
font-size: 12px;
color: #909399;
/* 核心:入场钢卷号溢出省略 */
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
max-width: 180px;
/* 限制最大长度,避免挤压按钮 */
}
.material-body {
font-size: 12px;
color: #606266;
display: grid;
gap: 4px;
}
.material-row {
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.plan-summary {
display: grid;
grid-template-columns: repeat(3, minmax(120px, 1fr));
gap: 8px;
font-size: 12px;
color: #606266;
margin-bottom: 8px;
}
:deep(.el-card__header) {
border-bottom: 1px solid #f0f2f5;
background: #ffffff;
}
:deep(.el-table th),
:deep(.el-table td) {
border-bottom: 1px solid #f0f2f5;
}
:deep(.el-table::before) {
background-color: transparent;
}
</style>

View File

@@ -1,718 +0,0 @@
<template>
<div class="app-container">
<el-form :model="queryParams" ref="queryForm" size="small" :inline="true" v-show="showSearch" label-width="90px">
<el-form-item label="计划号" prop="planNo">
<el-input v-model="queryParams.planNo" placeholder="请输入计划号" clearable @keyup.enter.native="handleQuery" />
</el-form-item>
<el-form-item label="目标炉" prop="targetFurnaceId">
<el-select v-model="queryParams.targetFurnaceId" placeholder="请选择" clearable filterable>
<el-option v-for="item in furnaceOptions" :key="item.furnaceId" :label="item.furnaceName"
:value="item.furnaceId" />
</el-select>
</el-form-item>
<el-form-item label="状态" prop="status">
<el-select v-model="queryParams.status" placeholder="请选择" clearable>
<el-option label="未开始" :value="0" />
<el-option label="进行中" :value="2" />
<el-option label="已完成" :value="3" />
</el-select>
</el-form-item>
<el-form-item>
<el-button type="primary" icon="el-icon-search" size="mini" @click="handleQuery">搜索</el-button>
<el-button icon="el-icon-refresh" size="mini" @click="resetQuery">重置</el-button>
</el-form-item>
</el-form>
<el-row :gutter="10" class="mb8">
<el-col :span="1.5">
<el-button type="primary" plain icon="el-icon-plus" size="mini" @click="handleAdd">新增</el-button>
</el-col>
<el-col :span="1.5">
<el-button type="success" plain icon="el-icon-edit" size="mini" :disabled="single"
@click="handleUpdate">修改</el-button>
</el-col>
<el-col :span="1.5">
<el-button type="danger" plain icon="el-icon-delete" size="mini" :disabled="multiple"
@click="handleDelete">删除</el-button>
</el-col>
<right-toolbar :showSearch.sync="showSearch" @queryTable="getList"></right-toolbar>
</el-row>
<KLPTable v-loading="loading" :data="planList" @selection-change="handleSelectionChange"
@row-click="handleRowClick">
<el-table-column type="selection" width="55" align="center" />
<el-table-column label="计划号" align="center" prop="planNo" />
<el-table-column label="计划时间" align="center" prop="planStartTime" width="160">
<template slot-scope="scope">
{{ parseTime(scope.row.planStartTime, '{y}-{m}-{d} {h}:{i}') }}
</template>
</el-table-column>
<el-table-column label="目标炉" align="center" prop="targetFurnaceName" />
<el-table-column label="状态" align="center" prop="status">
<template slot-scope="scope">
<el-tag :type="statusTag(scope.row.status)">{{ statusLabel(scope.row.status) }}</el-tag>
</template>
</el-table-column>
<el-table-column label="开始时间" align="center" prop="actualStartTime" width="160">
<template slot-scope="scope">
{{ parseTime(scope.row.actualStartTime || scope.row.planStartTime, '{y}-{m}-{d} {h}:{i}') }}
</template>
</el-table-column>
<el-table-column label="结束时间" align="center" prop="endTime" width="160">
<template slot-scope="scope">
{{ parseTime(scope.row.endTime, '{y}-{m}-{d} {h}:{i}') }}
</template>
</el-table-column>
<el-table-column label="操作" align="center" class-name="small-padding fixed-width" width="220">
<template slot-scope="scope">
<el-button size="mini" type="text" icon="el-icon-edit" @click.stop="handleUpdate(scope.row)">修改</el-button>
<el-button size="mini" type="text" icon="el-icon-delete" @click.stop="handleDelete(scope.row)">删除</el-button>
<el-button size="mini" type="text" icon="el-icon-s-operation"
@click.stop="openStatusDialog(scope.row)">状态</el-button>
<!-- <el-button v-if="scope.row.status === 0" size="mini" type="text" icon="el-icon-s-flag"
:disabled="!scope.row.coilCount" @click.stop="handleInFurnace(scope.row)">入炉</el-button> -->
<el-button v-if="scope.row.status === 2" size="mini" type="text" icon="el-icon-check"
@click.stop="handleComplete(scope.row)">完成</el-button>
</template>
</el-table-column>
</KLPTable>
<pagination v-show="total > 0" :total="total" :page.sync="queryParams.pageNum" :limit.sync="queryParams.pageSize"
@pagination="getList" />
<el-row :gutter="20" class="mt16">
<el-col :span="12">
<el-card shadow="never" class="panel-card">
<div slot="header" class="panel-header">
<span>领料列表</span>
<el-button size="mini" icon="el-icon-refresh" @click="getMaterialCoils">刷新</el-button>
</div>
<el-form :model="materialQueryParams" ref="materialQueryForm" size="small" :inline="true" class="mb8">
<el-form-item label="入场钢卷号" prop="enterCoilNo">
<el-input v-model="materialQueryParams.enterCoilNo" placeholder="请输入入场钢卷号" clearable
style="width: 160px" />
</el-form-item>
<el-form-item label="当前钢卷号" prop="currentCoilNo">
<el-input v-model="materialQueryParams.currentCoilNo" placeholder="请输入当前钢卷号" clearable
style="width: 160px" />
</el-form-item>
<el-form-item>
<el-button type="primary" icon="el-icon-search" size="mini" @click="handleMaterialQuery">搜索</el-button>
<el-button icon="el-icon-refresh" size="mini" @click="resetMaterialQuery">重置</el-button>
</el-form-item>
</el-form>
<div v-loading="materialLoading" class="material-grid">
<div v-if="materialList.length === 0" class="empty-tip">
<el-empty description="暂无待领物料" />
</div>
<div v-for="item in materialList" :key="item.coilId" class="material-card">
<div class="material-header">
<div class="material-text-wrap">
<div class="material-title">{{ item.currentCoilNo || '-' }}</div>
<div class="material-sub">入场{{ item.enterCoilNo || '-' }}</div>
</div>
<el-button type="primary" size="mini" @click="handleAddToPlan(item)"
:disabled="!currentPlan.planId || currentPlan.status !== 0">加入计划</el-button>
</div>
<div class="material-body">
<div class="material-row">厂家{{ item.supplierCoilNo || '-' }}</div>
<div class="material-row">库位{{ item.actualWarehouseName || '-' }}</div>
<div class="material-row">重量{{ item.netWeight || '-' }}t</div>
</div>
</div>
</div>
<pagination v-show="materialTotal > 0" :total="materialTotal" :page.sync="materialQueryParams.pageNum"
:limit.sync="materialQueryParams.pageSize" @pagination="getMaterialCoils" />
</el-card>
</el-col>
<el-col :span="12">
<el-card shadow="never" class="panel-card">
<div slot="header" class="panel-header">
<span>退火计划</span>
<div>
<el-button size="mini" icon="el-icon-refresh" @click="loadPlanCoils"
:disabled="!currentPlan.planId">刷新</el-button>
<!-- <el-button size="mini" type="primary" icon="el-icon-s-flag"
:disabled="!currentPlan.planId || currentPlan.status !== 0 || !currentPlan.coilCount"
@click="handleInFurnace(currentPlan)">入炉</el-button> -->
<!-- <el-button size="mini" type="success" icon="el-icon-check"
:disabled="!currentPlan.planId || currentPlan.status !== 2" @click="openCompleteDialog">完成退火</el-button> -->
</div>
</div>
<div v-if="!currentPlan.planId" class="empty-tip">
<el-empty description="请点击上方选择一个计划" />
</div>
<div v-else>
<div class="plan-summary">
<div>计划号{{ currentPlan.planNo }}</div>
<div>目标炉{{ currentPlan.targetFurnaceName || '-' }}</div>
<div>状态{{ statusLabel(currentPlan.status) }}</div>
<!-- <div>
<el-button size="mini" type="primary" icon="el-icon-s-flag" :disabled="currentPlan.status !== 0"
@click="handleInFurnace(currentPlan)">入炉</el-button>
</div> -->
</div>
<el-table :data="coilList" v-loading="coilLoading" class="light-table">
<el-table-column label="入场钢卷号" align="center" prop="enterCoilNo" />
<el-table-column label="创建时间" align="center" prop="createTime">
<template slot-scope="scope">
{{ parseTime(scope.row.createTime, '{y}-{m}-{d} {h}:{i}') }}
</template>
</el-table-column>
<el-table-column label="实际库位" align="center" width="220">
<template slot-scope="scope">
<span>{{ scope.row.actualWarehouseName || '-' }}</span>
</template>
</el-table-column>
<el-table-column label="操作" align="center" width="100">
<template slot-scope="scope">
<el-button size="mini" type="text" icon="el-icon-delete" @click="handleUnbind(scope.row)">
{{ unbindLabel(currentPlan.status) }}
</el-button>
</template>
</el-table-column>
</el-table>
</div>
</el-card>
</el-col>
</el-row>
<el-dialog title="完成退火" :visible.sync="completeOpen" width="720px" append-to-body>
<div class="complete-tip">请为每条钢卷分配实际库位未分配将无法完成</div>
<el-table :data="completeCoils" v-loading="completeLoading" height="360px">
<el-table-column label="入场钢卷号" prop="enterCoilNo" align="center" />
<el-table-column label="钢卷去向" align="center" width="240">
<template slot-scope="scope">
<WarehouseSelect v-model="scope.row.warehouseId" />
</template>
</el-table-column>
</el-table>
<div slot="footer" class="dialog-footer">
<el-button type="primary" @click="submitComplete"> </el-button>
<el-button @click="completeOpen = false"> </el-button>
</div>
</el-dialog>
<el-dialog :title="title" :visible.sync="open" width="520px" append-to-body>
<el-form ref="form" :model="form" :rules="rules" label-width="110px">
<el-form-item label="计划号" prop="planNo">
<el-input v-model="form.planNo" placeholder="请输入计划号" />
</el-form-item>
<el-form-item label="计划开始" prop="planStartTime">
<el-date-picker clearable v-model="form.planStartTime" type="datetime" value-format="yyyy-MM-dd HH:mm:ss"
placeholder="请选择计划时间" />
</el-form-item>
<el-form-item label="目标炉" prop="targetFurnaceId">
<el-select v-model="form.targetFurnaceId" placeholder="请选择" filterable>
<el-option v-for="item in furnaceOptions" :key="item.furnaceId" :label="item.furnaceName"
:value="item.furnaceId" />
</el-select>
</el-form-item>
<el-form-item label="状态" prop="status">
<el-select v-model="form.status" placeholder="请选择">
<el-option label="未开始" :value="0" />
<el-option label="进行中" :value="2" />
<el-option label="已完成" :value="3" />
</el-select>
</el-form-item>
<el-form-item label="备注" prop="remark">
<el-input v-model="form.remark" type="textarea" placeholder="请输入备注" />
</el-form-item>
</el-form>
<div slot="footer" class="dialog-footer">
<el-button :loading="buttonLoading" type="primary" @click="submitForm"> </el-button>
<el-button @click="cancel"> </el-button>
</div>
</el-dialog>
<el-dialog title="更新状态" :visible.sync="statusOpen" width="360px" append-to-body>
<el-form label-width="90px">
<el-form-item label="状态">
<el-select v-model="statusForm.status" placeholder="请选择">
<el-option label="未开始" :value="0" />
<el-option label="进行中" :value="2" />
<el-option label="已完成" :value="3" />
</el-select>
</el-form-item>
</el-form>
<div slot="footer" class="dialog-footer">
<el-button type="primary" @click="submitStatus"> </el-button>
<el-button @click="statusOpen = false"> </el-button>
</div>
</el-dialog>
</div>
</template>
<script>
import { listAnnealPlan, getAnnealPlan, addAnnealPlan, updateAnnealPlan, delAnnealPlan, changeAnnealPlanStatus, inFurnace, completeAnnealPlan, listAnnealPlanCoils, bindAnnealPlanCoils, unbindAnnealPlanCoil } from "@/api/wms/annealPlan";
import { listAnnealFurnace } from "@/api/wms/annealFurnace";
import { listMaterialCoil } from "@/api/wms/coil";
import WarehouseSelect from "@/components/KLPService/WarehouseSelect";
export default {
name: "AnnealPlan",
components: {
WarehouseSelect
},
data() {
return {
buttonLoading: false,
loading: true,
coilLoading: false,
ids: [],
single: true,
multiple: true,
showSearch: true,
total: 0,
planList: [],
furnaceOptions: [],
title: "",
open: false,
statusOpen: false,
queryParams: {
pageNum: 1,
pageSize: 20,
planNo: undefined,
targetFurnaceId: undefined,
status: undefined,
},
form: {},
rules: {
planNo: [{ required: true, message: "计划号不能为空", trigger: "blur" }],
targetFurnaceId: [{ required: true, message: "目标炉子不能为空", trigger: "change" }],
},
currentPlan: {},
coilList: [],
materialLoading: false,
materialTotal: 0,
materialList: [],
materialQueryParams: {
pageNum: 1,
pageSize: 20,
enterCoilNo: undefined,
currentCoilNo: undefined,
status: 0,
dataType: 1,
},
statusForm: {
planId: undefined,
status: undefined,
},
completeOpen: false,
completeLoading: false,
completePlanId: null,
completeCoils: [],
};
},
created() {
this.getList();
this.loadFurnaces();
this.getMaterialCoils();
this.loadActualWarehouses();
},
methods: {
getList() {
this.loading = true;
listAnnealPlan(this.queryParams).then(response => {
this.planList = response.rows;
this.total = response.total;
this.loading = false;
});
},
loadFurnaces() {
listAnnealFurnace({ pageNum: 1, pageSize: 999, status: 1 }).then(response => {
this.furnaceOptions = response.rows || [];
});
},
handleRowClick(row) {
this.currentPlan = row;
this.loadPlanCoils();
},
getMaterialCoils() {
this.materialLoading = true;
listMaterialCoil(this.materialQueryParams).then(response => {
this.materialList = response.rows || [];
this.materialTotal = response.total || 0;
this.materialLoading = false;
});
},
handleMaterialQuery() {
this.materialQueryParams.pageNum = 1;
this.getMaterialCoils();
},
resetMaterialQuery() {
this.resetForm("materialQueryForm");
this.resetMaterialForm();
this.handleMaterialQuery();
},
openCompleteDialog() {
if (!this.currentPlan.planId) {
this.$message.warning('请先选择计划');
return;
}
if (this.currentPlan.status !== 2) {
this.$message.warning('计划未进行中,无法完成');
return;
}
this.completePlanId = this.currentPlan.planId;
this.completeOpen = true;
this.completeLoading = true;
listAnnealPlanCoils(this.currentPlan.planId).then(response => {
this.completeCoils = (response.data || []).map(item => ({
coilId: item.coilId,
enterCoilNo: item.enterCoilNo,
actualWarehouseId: item.actualWarehouseId || null
}));
this.completeLoading = false;
}).catch(() => {
this.completeLoading = false;
});
},
handleAddToPlan(item) {
if (!this.currentPlan.planId) {
this.$message.warning('请先选择计划');
return;
}
if (this.currentPlan.status === 2) {
this.$message.warning('当前计划进行中,无法再领料');
return;
}
this.coilLoading = true;
bindAnnealPlanCoils({
planId: this.currentPlan.planId,
coilId: item.coilId
}).then(() => {
this.$message.success('已加入计划');
this.loadPlanCoils();
}).finally(() => {
this.coilLoading = false;
});
},
loadPlanCoils() {
if (!this.currentPlan.planId) {
this.coilList = [];
return;
}
this.coilLoading = true;
listAnnealPlanCoils(this.currentPlan.planId).then(response => {
this.coilList = response.data || [];
this.coilLoading = false;
});
},
cancel() {
this.open = false;
this.reset();
},
reset() {
this.form = {
planId: undefined,
planNo: undefined,
planStartTime: undefined,
targetFurnaceId: undefined,
status: 0,
remark: undefined,
};
this.resetForm("form");
},
resetMaterialForm() {
this.materialQueryParams = {
pageNum: 1,
pageSize: 10,
enterCoilNo: undefined,
currentCoilNo: undefined,
status: 0,
dataType: 1,
};
},
handleQuery() {
this.queryParams.pageNum = 1;
this.getList();
},
resetQuery() {
this.resetForm("queryForm");
this.handleQuery();
},
handleSelectionChange(selection) {
this.ids = selection.map(item => item.planId);
this.single = selection.length !== 1;
this.multiple = !selection.length;
},
handleAdd() {
this.reset();
this.open = true;
this.title = "添加退火计划";
},
handleUpdate(row) {
this.loading = true;
this.reset();
const planId = row.planId || this.ids;
getAnnealPlan(planId).then(response => {
this.loading = false;
this.form = response.data;
this.open = true;
this.title = "修改退火计划";
});
},
submitForm() {
this.$refs["form"].validate(valid => {
if (valid) {
this.buttonLoading = true;
if (this.form.planId != null) {
updateAnnealPlan(this.form).then(() => {
this.$modal.msgSuccess("修改成功");
this.open = false;
this.getList();
}).finally(() => {
this.buttonLoading = false;
});
} else {
addAnnealPlan(this.form).then(() => {
this.$modal.msgSuccess("新增成功");
this.open = false;
this.getList();
}).finally(() => {
this.buttonLoading = false;
});
}
}
});
},
handleDelete(row) {
const ids = row.planId || this.ids;
this.$modal.confirm('是否确认删除计划编号为"' + ids + '"的数据项?').then(() => {
this.loading = true;
return delAnnealPlan(ids);
}).then(() => {
this.loading = false;
this.getList();
this.$modal.msgSuccess("删除成功");
}).finally(() => {
this.loading = false;
});
},
openStatusDialog(row) {
this.statusForm = {
planId: row.planId,
status: row.status,
};
this.statusOpen = true;
},
submitStatus() {
changeAnnealPlanStatus(this.statusForm).then(() => {
this.$modal.msgSuccess("状态已更新");
this.statusOpen = false;
this.getList();
});
},
async handleInFurnace(row) {
await this.$confirm('点击入炉后钢卷将会被释放库位,是否确认入炉', '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
});
this.loading = true;
await inFurnace({ planId: row.planId });
this.loading = false;
row.status = 2;
row.actualStartTime = new Date();
if (this.currentPlan.planId === row.planId) {
this.currentPlan.status = 2;
this.currentPlan.actualStartTime = row.actualStartTime;
this.loadPlanCoils();
}
this.getList();
this.$message.success('已入炉');
},
submitComplete() {
const locations = (this.completeCoils || []).map(item => ({
coilId: item.coilId,
actualWarehouseId: item.actualWarehouseId
}));
const missing = locations.filter(item => !item.actualWarehouseId);
if (missing.length > 0) {
this.$message.warning('请先为所有钢卷分配实际库位');
return;
}
this.completeLoading = true;
completeAnnealPlan({
planId: this.completePlanId,
locations: locations
}).then(() => {
this.$message.success('已完成');
this.completeOpen = false;
this.getList();
this.loadPlanCoils();
}).finally(() => {
this.completeLoading = false;
});
},
handleUnbind(row) {
this.$confirm('确定解绑该钢卷吗?', '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
}).then(() => {
return unbindAnnealPlanCoil({
planId: this.currentPlan.planId,
coilId: row.coilId
});
}).then(() => {
this.$message.success('解绑成功');
this.loadPlanCoils();
});
},
statusLabel(status) {
if (status === 2) {
return '进行中';
}
if (status === 3) {
return '已完成';
}
return '未开始';
},
statusTag(status) {
if (status === 2) {
return 'success';
}
if (status === 3) {
return 'info';
}
return 'warning';
},
unbindLabel(status) {
if (status === 2) {
return '取消';
}
if (status === 3) {
return '撤回';
}
return '解绑';
}
}
};
</script>
<style scoped>
.mt16 {
margin-top: 16px;
}
.empty-tip {
margin-top: 10px;
}
.panel-card {
border: 1px solid #f0f2f5;
background: #ffffff;
}
.panel-header {
display: flex;
align-items: center;
justify-content: space-between;
}
/* ========== 修复在这里 ========== */
.material-grid {
display: grid;
/* 核心修复:去掉固定 4 列,改用自动填充,实现真正自适应 */
grid-template-columns: repeat(auto-fill, minmax(220px, 1fr));
gap: 12px;
min-height: 120px;
/* 必须加,让 grid 不受父级弹性压缩影响 */
width: 100%;
box-sizing: border-box;
}
/* 媒体查询只需要控制最小宽度即可,不用写死列数 */
@media (max-width: 768px) {
.material-grid {
grid-template-columns: 1fr;
}
}
/* =============================== */
.material-card {
border: 1px solid #e9ecf2;
border-radius: 8px;
padding: 12px;
background: #ffffff;
box-shadow: 0 1px 4px rgba(0, 0, 0, 0.04);
}
.material-header {
display: flex;
align-items: center;
justify-content: space-between;
gap: 8px;
margin-bottom: 8px;
/* 新增:让文字区域和按钮分行适配小宽度 */
flex-wrap: wrap;
}
/* 新增:文字容器,限制宽度并溢出省略 */
.material-text-wrap {
flex: 1;
min-width: 0;
/* 关键让flex子元素遵守宽度限制 */
}
.material-title {
font-weight: 600;
color: #303133;
/* 新增:标题溢出省略 */
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
max-width: 180px;
/* 可根据需要调整最大宽度 */
}
.material-sub {
font-size: 12px;
color: #909399;
/* 核心:入场钢卷号溢出省略 */
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
max-width: 180px;
/* 限制最大长度,避免挤压按钮 */
}
.material-body {
font-size: 12px;
color: #606266;
display: grid;
gap: 4px;
}
.material-row {
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.plan-summary {
display: grid;
grid-template-columns: repeat(3, minmax(120px, 1fr));
gap: 8px;
font-size: 12px;
color: #606266;
margin-bottom: 8px;
}
:deep(.el-card__header) {
border-bottom: 1px solid #f0f2f5;
background: #ffffff;
}
:deep(.el-table th),
:deep(.el-table td) {
border-bottom: 1px solid #f0f2f5;
}
:deep(.el-table::before) {
background-color: transparent;
}
</style>

View File

@@ -13,9 +13,8 @@ export default {
return {
querys: {
dataType: 1,
orderByAbnormal: true,
// 筛选异常数量大于等于1的
// minAbnormalCount: 1
minAbnormalCount: 1
},
labelType: '2',
qrcode: false,

View File

@@ -65,9 +65,7 @@
<dict-tag :options="dict.type.coil_abnormal_position" :value="scope.row.position" />
</template>
</el-table-column>
<el-table-column label="开始位置" align="center" prop="startPosition" />
<el-table-column label="结束位置" align="center" prop="endPosition" />
<el-table-column label="缺陷长度" align="center" prop="length" />
<el-table-column label="长度坐标" align="center" prop="lengthCoord" />
<el-table-column label="缺陷代码" align="center" prop="defectCode">
<template slot-scope="scope">
<dict-tag :options="dict.type.coil_abnormal_code" :value="scope.row.defectCode" />
@@ -111,7 +109,35 @@
<!-- 添加或修改钢卷异常信息对话框 -->
<el-dialog :title="title" :visible.sync="open" width="600px" append-to-body>
<abnormal-form ref="abnormalForm" v-model="form" :show-coil-selector="showCoilSelector"></abnormal-form>
<el-form ref="form" :model="form" :rules="rules" label-width="80px">
<el-form-item label="钢卷ID" prop="coilId" v-if="!form.abnormalId && showCoilSelector">
<coil-selector v-model="form.coilId"></coil-selector>
</el-form-item>
<el-form-item label="位置" prop="position">
<el-radio-group v-model="form.position">
<el-radio-button v-for="dict in dict.type.coil_abnormal_position" :key="dict.value" :label="dict.value">{{
dict.label }}</el-radio-button>
</el-radio-group>
</el-form-item>
<el-form-item label="长度坐标" prop="lengthCoord">
<el-input v-model="form.lengthCoord" placeholder="请输入长度坐标" />
</el-form-item>
<el-form-item label="缺陷代码" prop="defectCode">
<el-radio-group v-model="form.defectCode">
<el-radio-button v-for="dict in dict.type.coil_abnormal_code" :key="dict.value" :label="dict.value">{{
dict.label }}</el-radio-button>
</el-radio-group>
</el-form-item>
<el-form-item label="程度" prop="degree">
<el-radio-group v-model="form.degree">
<el-radio-button v-for="dict in dict.type.coil_abnormal_degree" :key="dict.value" :label="dict.value">{{
dict.label }}</el-radio-button>
</el-radio-group>
</el-form-item>
<el-form-item label="备注" prop="remark">
<el-input type="textarea" v-model="form.remark" placeholder="请输入备注" />
</el-form-item>
</el-form>
<div slot="footer" class="dialog-footer">
<el-button :loading="buttonLoading" type="primary" @click="submitForm"> </el-button>
<el-button @click="cancel"> </el-button>
@@ -142,7 +168,6 @@ import { listMaterialCoil } from "@/api/wms/coil";
import CoilSelector from '@/components/CoilSelector'
import CoilNo from '@/components/KLPService/Renderer/CoilNo'
import CoilList from "./components/CoilList.vue";
import AbnormalForm from './components/AbnormalForm';
export default {
name: "CoilAbnormal",
@@ -150,8 +175,7 @@ export default {
components: {
CoilSelector,
CoilNo,
CoilList,
AbnormalForm
CoilList
},
data() {
return {
@@ -265,9 +289,7 @@ export default {
updateTime: undefined,
updateBy: undefined
};
if (this.$refs.abnormalForm) {
this.$refs.abnormalForm.resetFields();
}
this.resetForm("form");
},
/** 搜索按钮操作 */
handleQuery() {
@@ -313,11 +335,11 @@ export default {
},
/** 提交按钮 */
submitForm() {
this.$refs["abnormalForm"].validate(valid => {
this.$refs["form"].validate(valid => {
if (valid) {
this.buttonLoading = true;
if (this.form.abnormalId != null) {
updateCoilAbnormal({...this.form, length: this.form.endPosition - this.form.startPosition}).then(response => {
updateCoilAbnormal(this.form).then(response => {
this.$modal.msgSuccess("修改成功");
this.open = false;
this.getList();
@@ -325,7 +347,7 @@ export default {
this.buttonLoading = false;
});
} else {
addCoilAbnormal({...this.form, length: this.form.endPosition - this.form.startPosition}).then(response => {
addCoilAbnormal(this.form).then(response => {
this.$modal.msgSuccess("新增成功");
this.open = false;
this.getList();

Some files were not shown because too many files have changed in this diff Show More