5 Commits

52 changed files with 3677 additions and 584 deletions

View File

@@ -57,6 +57,7 @@ import org.jeecg.modules.xslmes.service.IMesXslRubberQuickTestStdService;
import org.jeecg.modules.xslmes.service.IMesXslWeightRecordService; import org.jeecg.modules.xslmes.service.IMesXslWeightRecordService;
import org.jeecg.modules.xslmes.service.MesXslStompNotifyService; import org.jeecg.modules.xslmes.service.MesXslStompNotifyService;
import org.jeecg.modules.xslmes.vo.MesXslRawMaterialCardBriefVO; import org.jeecg.modules.xslmes.vo.MesXslRawMaterialCardBriefVO;
import org.springframework.util.CollectionUtils;
import org.springframework.web.bind.annotation.*; import org.springframework.web.bind.annotation.*;
import java.math.BigDecimal; import java.math.BigDecimal;
@@ -1021,6 +1022,30 @@ public class MesXslDesktopAnonController {
//update-end---author:jiangxh ---date:2026-06-17 for【快检记录】桌面端胶料快检实验标准查询----------- //update-end---author:jiangxh ---date:2026-06-17 for【快检记录】桌面端胶料快检实验标准查询-----------
//update-begin---author:jiangxh ---date:2026-06-17 for【快检记录】桌面端胶料快检记录保存----------- //update-begin---author:jiangxh ---date:2026-06-17 for【快检记录】桌面端胶料快检记录保存-----------
@Operation(summary = "胶料快检记录-免密分页列表")
@GetMapping("/xslmes/mesXslRubberQuickTestRecord/anon/list")
public Result<IPage<MesXslRubberQuickTestRecord>> rubberQuickTestRecordAnonList(
MesXslRubberQuickTestRecord model,
@RequestParam(name = "pageNo", defaultValue = "1") Integer pageNo,
@RequestParam(name = "pageSize", defaultValue = "20") Integer pageSize,
HttpServletRequest req) {
QueryWrapper<MesXslRubberQuickTestRecord> qw = QueryGenerator.initQueryWrapper(model, req.getParameterMap());
qw.orderByDesc("create_time");
IPage<MesXslRubberQuickTestRecord> page = rubberQuickTestRecordService.page(new Page<>(pageNo, pageSize), qw);
return Result.OK(page);
}
@Operation(summary = "胶料快检记录-免密通过id查询含明细")
@GetMapping("/xslmes/mesXslRubberQuickTestRecord/anon/queryById")
public Result<MesXslRubberQuickTestRecord> rubberQuickTestRecordAnonQueryById(@RequestParam(name = "id") String id) {
MesXslRubberQuickTestRecord entity = rubberQuickTestRecordService.getById(id);
if (entity == null) {
return Result.error("未找到对应数据");
}
fillRubberQuickTestRecordDetails(entity);
return Result.OK(entity);
}
@Operation(summary = "胶料快检记录-免密添加") @Operation(summary = "胶料快检记录-免密添加")
@PostMapping("/xslmes/mesXslRubberQuickTestRecord/anon/add") @PostMapping("/xslmes/mesXslRubberQuickTestRecord/anon/add")
public Result<String> rubberQuickTestRecordAnonAdd(@RequestBody MesXslRubberQuickTestRecord record) { public Result<String> rubberQuickTestRecordAnonAdd(@RequestBody MesXslRubberQuickTestRecord record) {
@@ -1030,16 +1055,23 @@ public class MesXslDesktopAnonController {
if (oConvertUtils.isEmpty(record.getRubberMaterialName())) { if (oConvertUtils.isEmpty(record.getRubberMaterialName())) {
return Result.error("胶料名称不能为空"); return Result.error("胶料名称不能为空");
} }
List<MesXslRubberQuickTestRecordLine> lineList = record.getLineList(); if (CollectionUtils.isEmpty(record.getStdLineList())) {
if (lineList == null || lineList.isEmpty()) { return Result.error("请维护数据标准明细");
return Result.error("请维护检验明细");
} }
if (CollectionUtils.isEmpty(record.getRawLineList())) {
return Result.error("请维护试验结果明细");
}
//update-begin---author:jiangxh ---date:20260617 for【快检记录】桌面端同步须含曲线图数据-----------
if (CollectionUtils.isEmpty(record.getChartPointList())) {
return Result.error("请维护曲线图数据");
}
//update-end---author:jiangxh ---date:20260617 for【快检记录】桌面端同步须含曲线图数据-----------
try { try {
if (oConvertUtils.isEmpty(record.getRecordNo())) { if (oConvertUtils.isEmpty(record.getRecordNo())) {
record.setRecordNo(rubberQuickTestRecordService.generateDesktopRecordNo(record)); record.setRecordNo(rubberQuickTestRecordService.generateDesktopRecordNo(record));
} }
rubberQuickTestRecordService.fillQuickTestTypeForRecord(record); rubberQuickTestRecordService.fillStdAndTypeForRecord(record);
rubberQuickTestRecordService.saveMain(record, lineList); rubberQuickTestRecordService.saveMain(record, record.getLineList());
stompNotify.publishRubberQuickTestRecordChanged("add", record.getId()); stompNotify.publishRubberQuickTestRecordChanged("add", record.getId());
return Result.OK(record.getRecordNo()); return Result.OK(record.getRecordNo());
} catch (Exception e) { } catch (Exception e) {
@@ -1047,6 +1079,17 @@ public class MesXslDesktopAnonController {
return Result.error(e.getMessage()); return Result.error(e.getMessage());
} }
} }
private void fillRubberQuickTestRecordDetails(MesXslRubberQuickTestRecord entity) {
if (entity == null || oConvertUtils.isEmpty(entity.getId())) {
return;
}
String id = entity.getId();
entity.setStdLineList(rubberQuickTestRecordService.selectStdLinesByRecordId(id));
entity.setRawLineList(rubberQuickTestRecordService.selectRawLinesByRecordId(id));
entity.setChartPointList(rubberQuickTestRecordService.selectChartPointsByRecordId(id));
entity.setLineList(rubberQuickTestRecordService.selectLinesByRecordId(id));
}
//update-end---author:jiangxh ---date:2026-06-17 for【快检记录】桌面端胶料快检记录保存----------- //update-end---author:jiangxh ---date:2026-06-17 for【快检记录】桌面端胶料快检记录保存-----------
//update-begin---author:jiangxh ---date:20260618 for【快检实验标准】桌面端回填实验方法关联实验类型----------- //update-begin---author:jiangxh ---date:20260618 for【快检实验标准】桌面端回填实验方法关联实验类型-----------

View File

@@ -95,18 +95,23 @@ public class MesXslRawMaterialDemandPlanController {
private String buildGroupedSql( private String buildGroupedSql(
MesXslRawMaterialDemandPlanSummary query, boolean groupedByMachine, List<Object> params) { MesXslRawMaterialDemandPlanSummary query, boolean groupedByMachine, List<Object> params) {
String planDateExpr = "DATE(t.plan_date)";
String machineExpr = "COALESCE(NULLIF(TRIM(t.machine_name),''), '')"; String machineExpr = "COALESCE(NULLIF(TRIM(t.machine_name),''), '')";
String materialIdExpr = "COALESCE(NULLIF(TRIM(t.material_id),''), '')";
String erpCodeExpr = "COALESCE(NULLIF(TRIM(t.erp_code),''), '')"; String erpCodeExpr = "COALESCE(NULLIF(TRIM(t.erp_code),''), '')";
String rawMaterialExpr = "COALESCE(NULLIF(TRIM(t.raw_material_name),''), '')"; String rawMaterialExpr = "COALESCE(NULLIF(TRIM(t.raw_material_name),''), '')";
StringBuilder sql = new StringBuilder(); StringBuilder sql = new StringBuilder();
sql.append("SELECT "); sql.append("SELECT ");
sql.append(planDateExpr).append(" AS planDate, ");
if (groupedByMachine) { if (groupedByMachine) {
sql.append(machineExpr).append(" AS machineName, "); sql.append(machineExpr).append(" AS machineName, ");
} else { } else {
sql.append("'' AS machineName, "); sql.append("'' AS machineName, ");
} }
sql.append(erpCodeExpr) sql.append(materialIdExpr)
.append(" AS materialId, ")
.append(erpCodeExpr)
.append(" AS erpCode, ") .append(" AS erpCode, ")
.append(rawMaterialExpr) .append(rawMaterialExpr)
.append(" AS rawMaterialName, ") .append(" AS rawMaterialName, ")
@@ -128,12 +133,16 @@ public class MesXslRawMaterialDemandPlanController {
sql.append("AND ").append(machineExpr).append(" LIKE ? "); sql.append("AND ").append(machineExpr).append(" LIKE ? ");
params.add("%" + query.getMachineName().trim() + "%"); params.add("%" + query.getMachineName().trim() + "%");
} }
if (query != null && query.getPlanDate() != null) {
sql.append("AND ").append(planDateExpr).append(" = ? ");
params.add(new java.sql.Date(query.getPlanDate().getTime()));
}
sql.append("GROUP BY "); sql.append("GROUP BY ").append(planDateExpr).append(", ");
if (groupedByMachine) { if (groupedByMachine) {
sql.append(machineExpr).append(", "); sql.append(machineExpr).append(", ");
} }
sql.append(erpCodeExpr).append(", ").append(rawMaterialExpr); sql.append(materialIdExpr).append(", ").append(erpCodeExpr).append(", ").append(rawMaterialExpr);
return sql.toString(); return sql.toString();
} }
@@ -146,7 +155,9 @@ public class MesXslRawMaterialDemandPlanController {
int seq = 1; int seq = 1;
while (rs.next()) { while (rs.next()) {
MesXslRawMaterialDemandPlanSummary row = new MesXslRawMaterialDemandPlanSummary(); MesXslRawMaterialDemandPlanSummary row = new MesXslRawMaterialDemandPlanSummary();
row.setPlanDate(rs.getDate("planDate"));
row.setMachineName(groupedByMachine ? trim(rs.getString("machineName")) : ""); row.setMachineName(groupedByMachine ? trim(rs.getString("machineName")) : "");
row.setMaterialId(trim(rs.getString("materialId")));
row.setErpCode(trim(rs.getString("erpCode"))); row.setErpCode(trim(rs.getString("erpCode")));
row.setRawMaterialName(trim(rs.getString("rawMaterialName"))); row.setRawMaterialName(trim(rs.getString("rawMaterialName")));
row.setDemandWeight(rs.getBigDecimal("demandWeight")); row.setDemandWeight(rs.getBigDecimal("demandWeight"));
@@ -162,14 +173,20 @@ public class MesXslRawMaterialDemandPlanController {
private String buildOrderBy(boolean groupedByMachine) { private String buildOrderBy(boolean groupedByMachine) {
if (groupedByMachine) { if (groupedByMachine) {
return " ORDER BY machineName, rawMaterialName, erpCode"; return " ORDER BY planDate DESC, machineName, rawMaterialName, erpCode";
} }
return " ORDER BY rawMaterialName, erpCode"; return " ORDER BY planDate DESC, rawMaterialName, erpCode";
} }
private String buildRowId(MesXslRawMaterialDemandPlanSummary row, boolean groupedByMachine, int seq) { private String buildRowId(MesXslRawMaterialDemandPlanSummary row, boolean groupedByMachine, int seq) {
String machine = groupedByMachine ? StringUtils.defaultString(row.getMachineName()) : "ALL"; String machine = groupedByMachine ? StringUtils.defaultString(row.getMachineName()) : "ALL";
return machine + "_" + StringUtils.defaultString(row.getErpCode()) + "_" + seq; return String.valueOf(row.getPlanDate())
+ "_"
+ machine
+ "_"
+ StringUtils.defaultString(row.getMaterialId())
+ "_"
+ seq;
} }
private String trim(String value) { private String trim(String value) {

View File

@@ -23,8 +23,10 @@ import org.jeecg.modules.mes.material.service.IMesMaterialService;
import org.jeecg.modules.system.entity.SysUser; import org.jeecg.modules.system.entity.SysUser;
import org.jeecg.modules.system.service.ISysUserService; import org.jeecg.modules.system.service.ISysUserService;
import org.jeecg.modules.xslmes.entity.MesXslRubberQuickTestRecord; import org.jeecg.modules.xslmes.entity.MesXslRubberQuickTestRecord;
import org.jeecg.modules.xslmes.entity.MesXslRubberQuickTestRecordChartPoint;
import org.jeecg.modules.xslmes.entity.MesXslRubberQuickTestRecordLine; import org.jeecg.modules.xslmes.entity.MesXslRubberQuickTestRecordLine;
import org.jeecg.modules.xslmes.entity.MesXslRubberQuickTestRecordRawLine; import org.jeecg.modules.xslmes.entity.MesXslRubberQuickTestRecordRawLine;
import org.jeecg.modules.xslmes.entity.MesXslRubberQuickTestRecordStdLine;
import org.jeecg.modules.xslmes.entity.MesXslRubberQuickTestType; import org.jeecg.modules.xslmes.entity.MesXslRubberQuickTestType;
import org.jeecg.modules.xslmes.service.IMesXslRubberQuickTestRecordService; import org.jeecg.modules.xslmes.service.IMesXslRubberQuickTestRecordService;
import org.jeecg.modules.xslmes.service.IMesXslRubberQuickTestTypeService; import org.jeecg.modules.xslmes.service.IMesXslRubberQuickTestTypeService;
@@ -32,6 +34,7 @@ import org.jeecg.modules.xslmes.vo.MesXslRubberQuickTestRecordBatchFromMaterialV
import org.jeecg.modules.xslmes.vo.MesXslRubberQuickTestRecordPage; import org.jeecg.modules.xslmes.vo.MesXslRubberQuickTestRecordPage;
import org.springframework.beans.BeanUtils; import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.util.CollectionUtils;
import org.springframework.web.bind.annotation.*; import org.springframework.web.bind.annotation.*;
import org.springframework.web.servlet.ModelAndView; import org.springframework.web.servlet.ModelAndView;
@@ -117,16 +120,9 @@ public class MesXslRubberQuickTestRecordController
@RequiresPermissions("mes:mes_material:rubberQuickTestInspect") @RequiresPermissions("mes:mes_material:rubberQuickTestInspect")
@PostMapping(value = "/batchFromMaterial") @PostMapping(value = "/batchFromMaterial")
public Result<List<String>> batchFromMaterial(@RequestBody MesXslRubberQuickTestRecordBatchFromMaterialVO vo) { public Result<List<String>> batchFromMaterial(@RequestBody MesXslRubberQuickTestRecordBatchFromMaterialVO vo) {
try { //update-begin---author:jiangxh ---date:2026-06-22 for【快检记录】取消胶料列表批量生成改由桌面端同步-----------
//update-begin---author:jiangxh ---date:20260616 for【MES】胶料快检记录批量生成默认带出当前登录检验人----------- return Result.error("该功能已停用,请通过桌面端新增并同步胶料快检记录");
fillInspectorIfEmpty(vo); //update-end---author:jiangxh ---date:2026-06-22 for【快检记录】取消胶料列表批量生成改由桌面端同步-----------
//update-end---author:jiangxh ---date:20260616 for【MES】胶料快检记录批量生成默认带出当前登录检验人-----------
List<String> ids = mesXslRubberQuickTestRecordService.batchFromMaterial(vo);
return Result.OK("成功生成 " + ids.size() + " 条快检记录", ids);
} catch (Exception e) {
log.error(e.getMessage(), e);
return Result.error(e.getMessage());
}
} }
@AutoLog(value = "MES胶料快检记录-删除") @AutoLog(value = "MES胶料快检记录-删除")
@@ -154,6 +150,12 @@ public class MesXslRubberQuickTestRecordController
if (entity == null) { if (entity == null) {
return Result.error("未找到对应数据"); return Result.error("未找到对应数据");
} }
//update-begin---author:jiangxh ---date:2026-06-22 for【快检记录】查询含数据标准/试验结果/曲线图明细-----------
entity.setStdLineList(mesXslRubberQuickTestRecordService.selectStdLinesByRecordId(id));
entity.setRawLineList(mesXslRubberQuickTestRecordService.selectRawLinesByRecordId(id));
entity.setChartPointList(mesXslRubberQuickTestRecordService.selectChartPointsByRecordId(id));
entity.setLineList(mesXslRubberQuickTestRecordService.selectLinesByRecordId(id));
//update-end---author:jiangxh ---date:2026-06-22 for【快检记录】查询含数据标准/试验结果/曲线图明细-----------
return Result.OK(entity); return Result.OK(entity);
} }
@@ -173,6 +175,22 @@ public class MesXslRubberQuickTestRecordController
} }
//update-end---author:jiangxh ---date:2026-06-17 for【快检记录】查询原始数据明细----------- //update-end---author:jiangxh ---date:2026-06-17 for【快检记录】查询原始数据明细-----------
//update-begin---author:jiangxh ---date:2026-06-22 for【快检记录】查询数据标准明细与曲线图-----------
@Operation(summary = "MES胶料快检记录-查询数据标准明细")
@GetMapping(value = "/queryStdLineListByRecordId")
public Result<List<MesXslRubberQuickTestRecordStdLine>> queryStdLineListByRecordId(
@RequestParam(name = "id", required = true) String id) {
return Result.OK(mesXslRubberQuickTestRecordService.selectStdLinesByRecordId(id));
}
@Operation(summary = "MES胶料快检记录-查询曲线图数据点")
@GetMapping(value = "/queryChartPointListByRecordId")
public Result<List<MesXslRubberQuickTestRecordChartPoint>> queryChartPointListByRecordId(
@RequestParam(name = "id", required = true) String id) {
return Result.OK(mesXslRubberQuickTestRecordService.selectChartPointsByRecordId(id));
}
//update-end---author:jiangxh ---date:2026-06-22 for【快检记录】查询数据标准明细与曲线图-----------
@RequiresPermissions("mes:mes_xsl_rubber_quick_test_record:exportXls") @RequiresPermissions("mes:mes_xsl_rubber_quick_test_record:exportXls")
@RequestMapping(value = "/exportXls") @RequestMapping(value = "/exportXls")
public ModelAndView exportXls(HttpServletRequest request, MesXslRubberQuickTestRecord model) { public ModelAndView exportXls(HttpServletRequest request, MesXslRubberQuickTestRecord model) {
@@ -189,14 +207,22 @@ public class MesXslRubberQuickTestRecordController
if (main == null) { if (main == null) {
return "参数不能为空"; return "参数不能为空";
} }
if (oConvertUtils.isEmpty(main.getRubberMaterialId())) { //update-begin---author:jiangxh ---date:2026-06-22 for【快检记录】Web保存校验支持桌面端三类明细-----------
return "请选择胶料"; if (oConvertUtils.isNotEmpty(main.getRubberMaterialId())) {
MesMaterial material = mesMaterialService.getById(main.getRubberMaterialId());
if (material == null) {
return "所选胶料不存在";
}
main.setRubberMaterialName(material.getMaterialName());
} else if (oConvertUtils.isEmpty(main.getRubberMaterialName())) {
return "胶料名称不能为空";
} }
MesMaterial material = mesMaterialService.getById(main.getRubberMaterialId());
if (material == null) { if (!CollectionUtils.isEmpty(main.getStdLineList()) && !CollectionUtils.isEmpty(main.getRawLineList())) {
return "所选胶料不存在"; resolveInspector(main);
return null;
} }
main.setRubberMaterialName(material.getMaterialName()); //update-end---author:jiangxh ---date:2026-06-22 for【快检记录】Web保存校验支持桌面端三类明细-----------
if (oConvertUtils.isNotEmpty(main.getQuickTestTypeId())) { if (oConvertUtils.isNotEmpty(main.getQuickTestTypeId())) {
MesXslRubberQuickTestType type = mesXslRubberQuickTestTypeService.getById(main.getQuickTestTypeId()); MesXslRubberQuickTestType type = mesXslRubberQuickTestTypeService.getById(main.getQuickTestTypeId());

View File

@@ -3,8 +3,10 @@ package org.jeecg.modules.xslmes.entity;
import io.swagger.v3.oas.annotations.media.Schema; import io.swagger.v3.oas.annotations.media.Schema;
import java.io.Serializable; import java.io.Serializable;
import java.math.BigDecimal; import java.math.BigDecimal;
import java.util.Date;
import lombok.Data; import lombok.Data;
import org.jeecgframework.poi.excel.annotation.Excel; import org.jeecgframework.poi.excel.annotation.Excel;
import org.springframework.format.annotation.DateTimeFormat;
/** /**
* 原材料需求计划汇总(按原材料或按机台+原材料) * 原材料需求计划汇总(按原材料或按机台+原材料)
@@ -22,6 +24,14 @@ public class MesXslRawMaterialDemandPlanSummary implements Serializable {
@Schema(description = "机台") @Schema(description = "机台")
private String machineName; private String machineName;
@Excel(name = "计划日期", width = 14, format = "yyyy-MM-dd")
@DateTimeFormat(pattern = "yyyy-MM-dd")
@Schema(description = "计划日期")
private Date planDate;
@Schema(description = "物料ID(密炼物料信息ID)")
private String materialId;
@Excel(name = "ERP编号", width = 18) @Excel(name = "ERP编号", width = 18)
@Schema(description = "ERP编号") @Schema(description = "ERP编号")
private String erpCode; private String erpCode;

View File

@@ -32,8 +32,8 @@ public class MesXslRubberQuickTestRecord implements Serializable {
@TableId(type = IdType.ASSIGN_ID) @TableId(type = IdType.ASSIGN_ID)
private String id; private String id;
@Excel(name = "", width = 16) @Excel(name = "快检记录", width = 16)
@Schema(description = "单号JL+yyyyMMdd+4位流水") @Schema(description = "快检记录号(yyyyMMdd+4位流水+胶料名称")
private String recordNo; private String recordNo;
@Schema(description = "胶料ID mes_material.id") @Schema(description = "胶料ID mes_material.id")
@@ -46,14 +46,25 @@ public class MesXslRubberQuickTestRecord implements Serializable {
@Schema(description = "引用的实验标准ID") @Schema(description = "引用的实验标准ID")
private String stdId; private String stdId;
@Schema(description = "生产机台ID") @Excel(name = "实验标准", width = 20)
@Schema(description = "实验标准名称冗余")
private String stdName;
@Schema(description = "实验方法ID")
private String testMethodId;
@Excel(name = "实验方法", width = 20)
@Schema(description = "实验方法名称冗余")
private String testMethodName;
@Schema(description = "炼机台ID")
private String prodEquipmentLedgerId; private String prodEquipmentLedgerId;
@Excel(name = "生产机台", width = 16) @Excel(name = "机台", width = 16)
@Schema(description = "生产机台名称冗余") @Schema(description = "机台名称冗余")
private String prodEquipmentName; private String prodEquipmentName;
@Excel(name = "生产日期", width = 12, format = "yyyy-MM-dd") @Excel(name = "密炼日期", width = 12, format = "yyyy-MM-dd")
@JsonFormat(timezone = "GMT+8", pattern = "yyyy-MM-dd") @JsonFormat(timezone = "GMT+8", pattern = "yyyy-MM-dd")
@DateTimeFormat(pattern = "yyyy-MM-dd") @DateTimeFormat(pattern = "yyyy-MM-dd")
private Date productionDate; private Date productionDate;
@@ -69,7 +80,7 @@ public class MesXslRubberQuickTestRecord implements Serializable {
@Dict(dicCode = "xslmes_rubber_quick_test_work_team") @Dict(dicCode = "xslmes_rubber_quick_test_work_team")
private String workTeam; private String workTeam;
@Excel(name = "验次数", width = 10) @Excel(name = "验次数", width = 10)
private Integer inspectTimes; private Integer inspectTimes;
@Excel(name = "检验时间", width = 18, format = "yyyy-MM-dd HH:mm:ss") @Excel(name = "检验时间", width = 18, format = "yyyy-MM-dd HH:mm:ss")
@@ -86,17 +97,17 @@ public class MesXslRubberQuickTestRecord implements Serializable {
@Excel(name = "检验人", width = 12) @Excel(name = "检验人", width = 12)
private String inspectorRealname; private String inspectorRealname;
@Schema(description = "验类型ID") @Schema(description = "验类型ID")
private String quickTestTypeId; private String quickTestTypeId;
@Excel(name = "验类型", width = 16) @Excel(name = "验类型", width = 16)
private String quickTestTypeName; private String quickTestTypeName;
@Excel(name = "检验结果", width = 10, dicCode = "xslmes_rubber_quick_test_record_result") @Excel(name = "检验结果", width = 10, dicCode = "xslmes_rubber_quick_test_record_result")
@Dict(dicCode = "xslmes_rubber_quick_test_record_result") @Dict(dicCode = "xslmes_rubber_quick_test_record_result")
private String inspectResult; private String inspectResult;
@Excel(name = "生产计划", width = 16) @Excel(name = "密炼计划", width = 16)
private String productionPlanNo; private String productionPlanNo;
@Schema(description = "检验机台ID") @Schema(description = "检验机台ID")
@@ -134,4 +145,14 @@ public class MesXslRubberQuickTestRecord implements Serializable {
@TableField(exist = false) @TableField(exist = false)
@Schema(description = "原始数据明细(试验结果全部检测值)") @Schema(description = "原始数据明细(试验结果全部检测值)")
private List<MesXslRubberQuickTestRecordRawLine> rawLineList; private List<MesXslRubberQuickTestRecordRawLine> rawLineList;
//update-begin---author:jiangxh ---date:2026-06-22 for【快检记录】数据标准明细与曲线图-----------
@TableField(exist = false)
@Schema(description = "数据标准明细(实验标准快照)")
private List<MesXslRubberQuickTestRecordStdLine> stdLineList;
@TableField(exist = false)
@Schema(description = "曲线图数据点")
private List<MesXslRubberQuickTestRecordChartPoint> chartPointList;
//update-end---author:jiangxh ---date:2026-06-22 for【快检记录】数据标准明细与曲线图-----------
} }

View File

@@ -0,0 +1,59 @@
package org.jeecg.modules.xslmes.entity;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import com.fasterxml.jackson.annotation.JsonFormat;
import io.swagger.v3.oas.annotations.media.Schema;
import java.io.Serializable;
import java.math.BigDecimal;
import java.util.Date;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.experimental.Accessors;
import org.jeecgframework.poi.excel.annotation.Excel;
import org.springframework.format.annotation.DateTimeFormat;
/**
* MES 胶料快检记录曲线图数据点
*/
@Data
@TableName("mes_xsl_rubber_quick_test_record_chart_point")
@Accessors(chain = true)
@EqualsAndHashCode(callSuper = false)
@Schema(description = "MES胶料快检记录曲线图数据点")
public class MesXslRubberQuickTestRecordChartPoint implements Serializable {
private static final long serialVersionUID = 1L;
@TableId(type = IdType.ASSIGN_ID)
private String id;
@Schema(description = "主表ID mes_xsl_rubber_quick_test_record.id")
private String recordId;
@Excel(name = "时间(min)", width = 12, type = 10)
private BigDecimal timeMin;
@Excel(name = "上模温度", width = 12, type = 10)
private BigDecimal upperTemp;
@Excel(name = "下模温度", width = 12, type = 10)
private BigDecimal lowerTemp;
@Excel(name = "S'(dNm)", width = 12, type = 10)
private BigDecimal torqueS;
private Integer sortNo;
private String createBy;
@JsonFormat(timezone = "GMT+8", pattern = "yyyy-MM-dd HH:mm:ss")
@DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private Date createTime;
private String updateBy;
@JsonFormat(timezone = "GMT+8", pattern = "yyyy-MM-dd HH:mm:ss")
@DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private Date updateTime;
}

View File

@@ -0,0 +1,68 @@
package org.jeecg.modules.xslmes.entity;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import com.fasterxml.jackson.annotation.JsonFormat;
import io.swagger.v3.oas.annotations.media.Schema;
import java.io.Serializable;
import java.math.BigDecimal;
import java.util.Date;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.experimental.Accessors;
import org.jeecgframework.poi.excel.annotation.Excel;
import org.springframework.format.annotation.DateTimeFormat;
/**
* MES 胶料快检记录数据标准明细(实验标准快照)
*/
@Data
@TableName("mes_xsl_rubber_quick_test_record_std_line")
@Accessors(chain = true)
@EqualsAndHashCode(callSuper = false)
@Schema(description = "MES胶料快检记录数据标准明细")
public class MesXslRubberQuickTestRecordStdLine implements Serializable {
private static final long serialVersionUID = 1L;
@TableId(type = IdType.ASSIGN_ID)
private String id;
@Schema(description = "主表ID mes_xsl_rubber_quick_test_record.id")
private String recordId;
@Schema(description = "数据点ID")
private String dataPointId;
@Excel(name = "数据点", width = 18)
private String pointName;
@Excel(name = "下限值", width = 12, type = 10)
private BigDecimal lowerLimit;
@Excel(name = "上限值", width = 12, type = 10)
private BigDecimal upperLimit;
@Excel(name = "下限预警", width = 12, type = 10)
private BigDecimal lowerWarn;
@Excel(name = "上限预警", width = 12, type = 10)
private BigDecimal upperWarn;
@Excel(name = "目标值", width = 12, type = 10)
private BigDecimal targetValue;
private Integer sortNo;
private String createBy;
@JsonFormat(timezone = "GMT+8", pattern = "yyyy-MM-dd HH:mm:ss")
@DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private Date createTime;
private String updateBy;
@JsonFormat(timezone = "GMT+8", pattern = "yyyy-MM-dd HH:mm:ss")
@DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private Date updateTime;
}

View File

@@ -0,0 +1,6 @@
package org.jeecg.modules.xslmes.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import org.jeecg.modules.xslmes.entity.MesXslRubberQuickTestRecordChartPoint;
public interface MesXslRubberQuickTestRecordChartPointMapper extends BaseMapper<MesXslRubberQuickTestRecordChartPoint> {}

View File

@@ -0,0 +1,7 @@
package org.jeecg.modules.xslmes.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import org.jeecg.modules.xslmes.entity.MesXslRubberQuickTestRecordStdLine;
public interface MesXslRubberQuickTestRecordStdLineMapper extends BaseMapper<MesXslRubberQuickTestRecordStdLine> {
}

View File

@@ -5,8 +5,10 @@ import java.io.Serializable;
import java.util.Collection; import java.util.Collection;
import java.util.List; import java.util.List;
import org.jeecg.modules.xslmes.entity.MesXslRubberQuickTestRecord; import org.jeecg.modules.xslmes.entity.MesXslRubberQuickTestRecord;
import org.jeecg.modules.xslmes.entity.MesXslRubberQuickTestRecordChartPoint;
import org.jeecg.modules.xslmes.entity.MesXslRubberQuickTestRecordLine; import org.jeecg.modules.xslmes.entity.MesXslRubberQuickTestRecordLine;
import org.jeecg.modules.xslmes.entity.MesXslRubberQuickTestRecordRawLine; import org.jeecg.modules.xslmes.entity.MesXslRubberQuickTestRecordRawLine;
import org.jeecg.modules.xslmes.entity.MesXslRubberQuickTestRecordStdLine;
import org.jeecg.modules.xslmes.vo.MesXslRubberQuickTestRecordBatchFromMaterialVO; import org.jeecg.modules.xslmes.vo.MesXslRubberQuickTestRecordBatchFromMaterialVO;
public interface IMesXslRubberQuickTestRecordService extends IService<MesXslRubberQuickTestRecord> { public interface IMesXslRubberQuickTestRecordService extends IService<MesXslRubberQuickTestRecord> {
@@ -23,6 +25,15 @@ public interface IMesXslRubberQuickTestRecordService extends IService<MesXslRubb
List<MesXslRubberQuickTestRecordRawLine> selectRawLinesByRecordId(String recordId); List<MesXslRubberQuickTestRecordRawLine> selectRawLinesByRecordId(String recordId);
//update-begin---author:jiangxh ---date:2026-06-22 for【快检记录】数据标准明细与曲线图查询-----------
List<MesXslRubberQuickTestRecordStdLine> selectStdLinesByRecordId(String recordId);
List<MesXslRubberQuickTestRecordChartPoint> selectChartPointsByRecordId(String recordId);
/** 桌面端保存前:按实验标准回填标准名、方法名、实验类型 */
void fillStdAndTypeForRecord(MesXslRubberQuickTestRecord record);
//update-end---author:jiangxh ---date:2026-06-22 for【快检记录】数据标准明细与曲线图查询-----------
String generateRecordNo(MesXslRubberQuickTestRecord context); String generateRecordNo(MesXslRubberQuickTestRecord context);
//update-begin---author:jiangxh ---date:20260618 for【快检记录】桌面端快检记录号生成----------- //update-begin---author:jiangxh ---date:20260618 for【快检记录】桌面端快检记录号生成-----------

View File

@@ -2,8 +2,10 @@ package org.jeecg.modules.xslmes.service.impl;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage; import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.core.toolkit.IdWorker;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page; import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import java.math.BigDecimal;
import java.time.ZoneId; import java.time.ZoneId;
import java.time.format.DateTimeFormatter; import java.time.format.DateTimeFormatter;
import java.util.ArrayList; import java.util.ArrayList;
@@ -18,19 +20,24 @@ import java.util.stream.Collectors;
import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.StringUtils;
import org.jeecg.common.constant.CommonConstant; import org.jeecg.common.constant.CommonConstant;
import org.jeecg.modules.mes.material.entity.MesMaterial; import org.jeecg.modules.mes.material.entity.MesMaterial;
import org.jeecg.modules.mes.material.entity.MesMixerMaterial;
import org.jeecg.modules.mes.material.mapper.MesMaterialMapper; import org.jeecg.modules.mes.material.mapper.MesMaterialMapper;
import org.jeecg.modules.mes.material.mapper.MesMixerMaterialMapper;
import org.jeecg.modules.xslmes.entity.MesXslEquipmentLedger; import org.jeecg.modules.xslmes.entity.MesXslEquipmentLedger;
import org.jeecg.modules.xslmes.entity.MesXslFinalBatchPlan; import org.jeecg.modules.xslmes.entity.MesXslFinalBatchPlan;
import org.jeecg.modules.xslmes.entity.MesXslMasterBatchPlan; import org.jeecg.modules.xslmes.entity.MesXslMasterBatchPlan;
import org.jeecg.modules.xslmes.entity.MesXslMixingSpec; import org.jeecg.modules.xslmes.entity.MesXslMixingSpec;
import org.jeecg.modules.xslmes.entity.MesXslMixingProductionPlan; import org.jeecg.modules.xslmes.entity.MesXslMixingProductionPlan;
import org.jeecg.modules.xslmes.entity.MesXslMixingSpecMaterial;
import org.jeecg.modules.xslmes.mapper.MesXslEquipmentLedgerMapper; import org.jeecg.modules.xslmes.mapper.MesXslEquipmentLedgerMapper;
import org.jeecg.modules.xslmes.mapper.MesXslFinalBatchPlanMapper; import org.jeecg.modules.xslmes.mapper.MesXslFinalBatchPlanMapper;
import org.jeecg.modules.xslmes.mapper.MesXslMasterBatchPlanMapper; import org.jeecg.modules.xslmes.mapper.MesXslMasterBatchPlanMapper;
import org.jeecg.modules.xslmes.mapper.MesXslMixingSpecMapper; import org.jeecg.modules.xslmes.mapper.MesXslMixingSpecMapper;
import org.jeecg.modules.xslmes.mapper.MesXslMixingProductionPlanMapper; import org.jeecg.modules.xslmes.mapper.MesXslMixingProductionPlanMapper;
import org.jeecg.modules.xslmes.mapper.MesXslMixingSpecMaterialMapper;
import org.jeecg.modules.xslmes.service.IMesXslMixingProductionPlanService; import org.jeecg.modules.xslmes.service.IMesXslMixingProductionPlanService;
import org.jeecg.modules.xslmes.vo.MesXslMixingProductionPlanOrderOptionVO; import org.jeecg.modules.xslmes.vo.MesXslMixingProductionPlanOrderOptionVO;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional; import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.CollectionUtils; import org.springframework.util.CollectionUtils;
@@ -45,19 +52,28 @@ public class MesXslMixingProductionPlanServiceImpl
private final MesXslMasterBatchPlanMapper masterBatchPlanMapper; private final MesXslMasterBatchPlanMapper masterBatchPlanMapper;
private final MesXslFinalBatchPlanMapper finalBatchPlanMapper; private final MesXslFinalBatchPlanMapper finalBatchPlanMapper;
private final MesXslMixingSpecMapper mixingSpecMapper; private final MesXslMixingSpecMapper mixingSpecMapper;
private final MesXslMixingSpecMaterialMapper mixingSpecMaterialMapper;
private final MesMaterialMapper mesMaterialMapper; private final MesMaterialMapper mesMaterialMapper;
private final MesMixerMaterialMapper mesMixerMaterialMapper;
private final JdbcTemplate jdbcTemplate;
public MesXslMixingProductionPlanServiceImpl( public MesXslMixingProductionPlanServiceImpl(
MesXslEquipmentLedgerMapper equipmentLedgerMapper, MesXslEquipmentLedgerMapper equipmentLedgerMapper,
MesXslMasterBatchPlanMapper masterBatchPlanMapper, MesXslMasterBatchPlanMapper masterBatchPlanMapper,
MesXslFinalBatchPlanMapper finalBatchPlanMapper, MesXslFinalBatchPlanMapper finalBatchPlanMapper,
MesXslMixingSpecMapper mixingSpecMapper, MesXslMixingSpecMapper mixingSpecMapper,
MesMaterialMapper mesMaterialMapper) { MesXslMixingSpecMaterialMapper mixingSpecMaterialMapper,
MesMaterialMapper mesMaterialMapper,
MesMixerMaterialMapper mesMixerMaterialMapper,
JdbcTemplate jdbcTemplate) {
this.equipmentLedgerMapper = equipmentLedgerMapper; this.equipmentLedgerMapper = equipmentLedgerMapper;
this.masterBatchPlanMapper = masterBatchPlanMapper; this.masterBatchPlanMapper = masterBatchPlanMapper;
this.finalBatchPlanMapper = finalBatchPlanMapper; this.finalBatchPlanMapper = finalBatchPlanMapper;
this.mixingSpecMapper = mixingSpecMapper; this.mixingSpecMapper = mixingSpecMapper;
this.mixingSpecMaterialMapper = mixingSpecMaterialMapper;
this.mesMaterialMapper = mesMaterialMapper; this.mesMaterialMapper = mesMaterialMapper;
this.mesMixerMaterialMapper = mesMixerMaterialMapper;
this.jdbcTemplate = jdbcTemplate;
} }
@Override @Override
@@ -82,6 +98,211 @@ public class MesXslMixingProductionPlanServiceImpl
if (!saveList.isEmpty()) { if (!saveList.isEmpty()) {
this.saveBatch(saveList, 200); this.saveBatch(saveList, 200);
} }
rebuildRawMaterialDemandPlan(saveList);
}
private void rebuildRawMaterialDemandPlan(List<MesXslMixingProductionPlan> savedPlans) {
jdbcTemplate.update("DELETE FROM mes_xsl_raw_material_demand_plan");
if (CollectionUtils.isEmpty(savedPlans)) {
return;
}
List<MesXslMixingProductionPlan> validPlans =
savedPlans.stream()
.filter(Objects::nonNull)
.filter(p -> StringUtils.isNotBlank(p.getFormulaName()))
.filter(p -> p.getPlanDate() != null)
.filter(p -> p.getPlanCount() != null && p.getPlanCount() > 0)
.collect(Collectors.toList());
if (validPlans.isEmpty()) {
return;
}
List<String> formulaNames =
validPlans.stream()
.map(MesXslMixingProductionPlan::getFormulaName)
.map(StringUtils::trimToNull)
.filter(Objects::nonNull)
.distinct()
.collect(Collectors.toList());
if (formulaNames.isEmpty()) {
return;
}
List<MesXslMixingSpec> specs =
mixingSpecMapper.selectList(
new LambdaQueryWrapper<MesXslMixingSpec>()
.in(MesXslMixingSpec::getSpecName, formulaNames)
.and(
w ->
w.eq(MesXslMixingSpec::getDelFlag, CommonConstant.DEL_FLAG_0)
.or()
.isNull(MesXslMixingSpec::getDelFlag)));
if (specs.isEmpty()) {
return;
}
Map<String, String> specIdByName = new HashMap<>();
for (MesXslMixingSpec spec : specs) {
if (spec == null || StringUtils.isBlank(spec.getSpecName()) || StringUtils.isBlank(spec.getId())) {
continue;
}
specIdByName.putIfAbsent(spec.getSpecName().trim(), spec.getId());
}
if (specIdByName.isEmpty()) {
return;
}
List<MesXslMixingSpecMaterial> details =
mixingSpecMaterialMapper.selectList(
new LambdaQueryWrapper<MesXslMixingSpecMaterial>()
.in(MesXslMixingSpecMaterial::getMixingSpecId, specIdByName.values())
.ne(MesXslMixingSpecMaterial::getMaterialKind, "胶料"));
if (details.isEmpty()) {
return;
}
Map<String, List<MesXslMixingSpecMaterial>> detailBySpecId =
details.stream()
.filter(Objects::nonNull)
.filter(d -> StringUtils.isNotBlank(d.getMixingSpecId()))
.collect(Collectors.groupingBy(MesXslMixingSpecMaterial::getMixingSpecId));
if (detailBySpecId.isEmpty()) {
return;
}
List<String> mixerMaterialIds =
details.stream()
.map(MesXslMixingSpecMaterial::getMixerMaterialId)
.map(StringUtils::trimToNull)
.filter(Objects::nonNull)
.distinct()
.collect(Collectors.toList());
Map<String, MesMixerMaterial> mixerMaterialMap = new HashMap<>();
if (!mixerMaterialIds.isEmpty()) {
List<MesMixerMaterial> mixerMaterials =
mesMixerMaterialMapper.selectList(
new LambdaQueryWrapper<MesMixerMaterial>().in(MesMixerMaterial::getId, mixerMaterialIds));
for (MesMixerMaterial mixerMaterial : mixerMaterials) {
if (mixerMaterial != null && StringUtils.isNotBlank(mixerMaterial.getId())) {
mixerMaterialMap.put(mixerMaterial.getId(), mixerMaterial);
}
}
}
Map<String, RawDemandAggregate> aggregateMap = new HashMap<>();
for (MesXslMixingProductionPlan plan : validPlans) {
String specId = specIdByName.get(StringUtils.trimToEmpty(plan.getFormulaName()));
if (StringUtils.isBlank(specId)) {
continue;
}
List<MesXslMixingSpecMaterial> specDetailList = detailBySpecId.get(specId);
if (CollectionUtils.isEmpty(specDetailList)) {
continue;
}
BigDecimal planCount = BigDecimal.valueOf(plan.getPlanCount());
for (MesXslMixingSpecMaterial detail : specDetailList) {
if (detail == null || detail.getUnitWeight() == null) {
continue;
}
if ("胶料".equals(StringUtils.trimToEmpty(detail.getMaterialKind()))) {
continue;
}
MesMixerMaterial mixerMaterial =
mixerMaterialMap.get(StringUtils.trimToNull(detail.getMixerMaterialId()));
String erpCode =
StringUtils.defaultIfBlank(
mixerMaterial == null ? null : StringUtils.trimToNull(mixerMaterial.getErpCode()),
mixerMaterial == null ? null : StringUtils.trimToNull(mixerMaterial.getMaterialCode()));
String rawMaterialName =
StringUtils.defaultIfBlank(
StringUtils.trimToNull(detail.getMixerMaterialName()),
mixerMaterial == null ? null : StringUtils.trimToNull(mixerMaterial.getMaterialName()));
String mixerMaterialId =
StringUtils.defaultIfBlank(
StringUtils.trimToNull(detail.getMixerMaterialId()),
mixerMaterial == null ? null : StringUtils.trimToNull(mixerMaterial.getId()));
if (StringUtils.isBlank(rawMaterialName)) {
continue;
}
BigDecimal demandWeight = detail.getUnitWeight().multiply(planCount);
String planDateKey = plan.getPlanDate().toInstant().atZone(ZoneId.systemDefault()).toLocalDate().toString();
String key =
String.join(
"|",
planDateKey,
StringUtils.defaultString(mixerMaterialId),
StringUtils.defaultString(plan.getMachineId()),
StringUtils.defaultString(plan.getMachineName()),
StringUtils.defaultString(erpCode),
rawMaterialName);
RawDemandAggregate aggregate =
aggregateMap.computeIfAbsent(
key,
k ->
new RawDemandAggregate(
plan.getPlanDate(),
StringUtils.trimToNull(mixerMaterialId),
StringUtils.trimToNull(plan.getMachineId()),
StringUtils.trimToNull(plan.getMachineName()),
StringUtils.defaultString(erpCode),
rawMaterialName));
aggregate.demandWeight = aggregate.demandWeight.add(demandWeight);
}
}
if (aggregateMap.isEmpty()) {
return;
}
List<Object[]> batchArgs = new ArrayList<>();
Date now = new Date();
for (RawDemandAggregate aggregate : aggregateMap.values()) {
if (aggregate.demandWeight.compareTo(BigDecimal.ZERO) <= 0) {
continue;
}
batchArgs.add(
new Object[] {
IdWorker.getIdStr(),
aggregate.machineId,
aggregate.machineName,
aggregate.materialId,
StringUtils.defaultString(aggregate.erpCode),
aggregate.rawMaterialName,
aggregate.demandWeight,
aggregate.demandWeight,
BigDecimal.ZERO,
aggregate.planDate,
now,
now,
CommonConstant.DEL_FLAG_0
});
}
if (batchArgs.isEmpty()) {
return;
}
jdbcTemplate.batchUpdate(
"INSERT INTO mes_xsl_raw_material_demand_plan "
+ "(id, machine_id, machine_name, material_id, erp_code, raw_material_name, demand_weight, standard_weight, actual_weight, plan_date, create_time, update_time, del_flag) "
+ "VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)",
batchArgs);
}
private static class RawDemandAggregate {
private final Date planDate;
private final String materialId;
private final String machineId;
private final String machineName;
private final String erpCode;
private final String rawMaterialName;
private BigDecimal demandWeight = BigDecimal.ZERO;
private RawDemandAggregate(
Date planDate,
String materialId,
String machineId,
String machineName,
String erpCode,
String rawMaterialName) {
this.planDate = planDate;
this.materialId = materialId;
this.machineId = machineId;
this.machineName = machineName;
this.erpCode = erpCode;
this.rawMaterialName = rawMaterialName;
}
} }
private boolean hasBusinessData(MesXslMixingProductionPlan row) { private boolean hasBusinessData(MesXslMixingProductionPlan row) {

View File

@@ -19,14 +19,18 @@ import org.jeecg.modules.mes.material.service.IMesMaterialService;
import org.jeecg.modules.xslmes.common.XslMesBizConstants; import org.jeecg.modules.xslmes.common.XslMesBizConstants;
import org.jeecg.modules.xslmes.entity.MesXslRubberQuickTestMethod; import org.jeecg.modules.xslmes.entity.MesXslRubberQuickTestMethod;
import org.jeecg.modules.xslmes.entity.MesXslRubberQuickTestRecord; import org.jeecg.modules.xslmes.entity.MesXslRubberQuickTestRecord;
import org.jeecg.modules.xslmes.entity.MesXslRubberQuickTestRecordChartPoint;
import org.jeecg.modules.xslmes.entity.MesXslRubberQuickTestRecordLine; import org.jeecg.modules.xslmes.entity.MesXslRubberQuickTestRecordLine;
import org.jeecg.modules.xslmes.entity.MesXslRubberQuickTestRecordRawLine; import org.jeecg.modules.xslmes.entity.MesXslRubberQuickTestRecordRawLine;
import org.jeecg.modules.xslmes.entity.MesXslRubberQuickTestRecordStdLine;
import org.jeecg.modules.xslmes.entity.MesXslRubberQuickTestStd; import org.jeecg.modules.xslmes.entity.MesXslRubberQuickTestStd;
import org.jeecg.modules.xslmes.entity.MesXslRubberQuickTestStdLine; import org.jeecg.modules.xslmes.entity.MesXslRubberQuickTestStdLine;
import org.jeecg.modules.xslmes.entity.MesXslRubberQuickTestType; import org.jeecg.modules.xslmes.entity.MesXslRubberQuickTestType;
import org.jeecg.modules.xslmes.mapper.MesXslRubberQuickTestRecordChartPointMapper;
import org.jeecg.modules.xslmes.mapper.MesXslRubberQuickTestRecordLineMapper; import org.jeecg.modules.xslmes.mapper.MesXslRubberQuickTestRecordLineMapper;
import org.jeecg.modules.xslmes.mapper.MesXslRubberQuickTestRecordMapper; import org.jeecg.modules.xslmes.mapper.MesXslRubberQuickTestRecordMapper;
import org.jeecg.modules.xslmes.mapper.MesXslRubberQuickTestRecordRawLineMapper; import org.jeecg.modules.xslmes.mapper.MesXslRubberQuickTestRecordRawLineMapper;
import org.jeecg.modules.xslmes.mapper.MesXslRubberQuickTestRecordStdLineMapper;
import org.jeecg.modules.xslmes.service.IMesXslRubberQuickTestMethodService; import org.jeecg.modules.xslmes.service.IMesXslRubberQuickTestMethodService;
import org.jeecg.modules.xslmes.service.IMesXslRubberQuickTestRecordService; import org.jeecg.modules.xslmes.service.IMesXslRubberQuickTestRecordService;
import org.jeecg.modules.xslmes.service.IMesXslRubberQuickTestStdService; import org.jeecg.modules.xslmes.service.IMesXslRubberQuickTestStdService;
@@ -50,6 +54,12 @@ public class MesXslRubberQuickTestRecordServiceImpl
@Autowired @Autowired
private MesXslRubberQuickTestRecordRawLineMapper mesXslRubberQuickTestRecordRawLineMapper; private MesXslRubberQuickTestRecordRawLineMapper mesXslRubberQuickTestRecordRawLineMapper;
@Autowired
private MesXslRubberQuickTestRecordStdLineMapper mesXslRubberQuickTestRecordStdLineMapper;
@Autowired
private MesXslRubberQuickTestRecordChartPointMapper mesXslRubberQuickTestRecordChartPointMapper;
@Autowired @Autowired
private IMesXslRubberQuickTestStdService mesXslRubberQuickTestStdService; private IMesXslRubberQuickTestStdService mesXslRubberQuickTestStdService;
@@ -73,6 +83,10 @@ public class MesXslRubberQuickTestRecordServiceImpl
//update-begin---author:jiangxh ---date:2026-06-17 for【快检记录】保存试验结果原始数据明细----------- //update-begin---author:jiangxh ---date:2026-06-17 for【快检记录】保存试验结果原始数据明细-----------
insertRawLines(main.getId(), main.getRawLineList()); insertRawLines(main.getId(), main.getRawLineList());
//update-end---author:jiangxh ---date:2026-06-17 for【快检记录】保存试验结果原始数据明细----------- //update-end---author:jiangxh ---date:2026-06-17 for【快检记录】保存试验结果原始数据明细-----------
//update-begin---author:jiangxh ---date:2026-06-22 for【快检记录】保存数据标准明细与曲线图-----------
insertStdLines(main.getId(), main.getStdLineList());
insertChartPoints(main.getId(), main.getChartPointList());
//update-end---author:jiangxh ---date:2026-06-22 for【快检记录】保存数据标准明细与曲线图-----------
} }
@Override @Override
@@ -95,6 +109,16 @@ public class MesXslRubberQuickTestRecordServiceImpl
.eq(MesXslRubberQuickTestRecordRawLine::getRecordId, main.getId())); .eq(MesXslRubberQuickTestRecordRawLine::getRecordId, main.getId()));
insertRawLines(main.getId(), main.getRawLineList()); insertRawLines(main.getId(), main.getRawLineList());
//update-end---author:jiangxh ---date:2026-06-17 for【快检记录】更新时同步原始数据明细----------- //update-end---author:jiangxh ---date:2026-06-17 for【快检记录】更新时同步原始数据明细-----------
//update-begin---author:jiangxh ---date:2026-06-22 for【快检记录】更新时同步数据标准与曲线图-----------
mesXslRubberQuickTestRecordStdLineMapper.delete(
new LambdaQueryWrapper<MesXslRubberQuickTestRecordStdLine>()
.eq(MesXslRubberQuickTestRecordStdLine::getRecordId, main.getId()));
insertStdLines(main.getId(), main.getStdLineList());
mesXslRubberQuickTestRecordChartPointMapper.delete(
new LambdaQueryWrapper<MesXslRubberQuickTestRecordChartPoint>()
.eq(MesXslRubberQuickTestRecordChartPoint::getRecordId, main.getId()));
insertChartPoints(main.getId(), main.getChartPointList());
//update-end---author:jiangxh ---date:2026-06-22 for【快检记录】更新时同步数据标准与曲线图-----------
} }
private void insertLines(String recordId, List<MesXslRubberQuickTestRecordLine> lineList) { private void insertLines(String recordId, List<MesXslRubberQuickTestRecordLine> lineList) {
@@ -128,6 +152,40 @@ public class MesXslRubberQuickTestRecordServiceImpl
} }
//update-end---author:jiangxh ---date:2026-06-17 for【快检记录】原始数据明细入库----------- //update-end---author:jiangxh ---date:2026-06-17 for【快检记录】原始数据明细入库-----------
//update-begin---author:jiangxh ---date:2026-06-22 for【快检记录】数据标准明细与曲线图入库-----------
private void insertStdLines(String recordId, List<MesXslRubberQuickTestRecordStdLine> stdLineList) {
if (CollectionUtils.isEmpty(stdLineList)) {
return;
}
int sort = 0;
for (MesXslRubberQuickTestRecordStdLine line : stdLineList) {
if (line == null) {
continue;
}
line.setId(null);
line.setRecordId(recordId);
line.setSortNo(sort++);
mesXslRubberQuickTestRecordStdLineMapper.insert(line);
}
}
private void insertChartPoints(String recordId, List<MesXslRubberQuickTestRecordChartPoint> chartPointList) {
if (CollectionUtils.isEmpty(chartPointList)) {
return;
}
int sort = 0;
for (MesXslRubberQuickTestRecordChartPoint point : chartPointList) {
if (point == null) {
continue;
}
point.setId(null);
point.setRecordId(recordId);
point.setSortNo(sort++);
mesXslRubberQuickTestRecordChartPointMapper.insert(point);
}
}
//update-end---author:jiangxh ---date:2026-06-22 for【快检记录】数据标准明细与曲线图入库-----------
@Override @Override
@Transactional(rollbackFor = Exception.class) @Transactional(rollbackFor = Exception.class)
public void delMain(String id) { public void delMain(String id) {
@@ -135,6 +193,10 @@ public class MesXslRubberQuickTestRecordServiceImpl
new LambdaQueryWrapper<MesXslRubberQuickTestRecordLine>().eq(MesXslRubberQuickTestRecordLine::getRecordId, id)); new LambdaQueryWrapper<MesXslRubberQuickTestRecordLine>().eq(MesXslRubberQuickTestRecordLine::getRecordId, id));
mesXslRubberQuickTestRecordRawLineMapper.delete( mesXslRubberQuickTestRecordRawLineMapper.delete(
new LambdaQueryWrapper<MesXslRubberQuickTestRecordRawLine>().eq(MesXslRubberQuickTestRecordRawLine::getRecordId, id)); new LambdaQueryWrapper<MesXslRubberQuickTestRecordRawLine>().eq(MesXslRubberQuickTestRecordRawLine::getRecordId, id));
mesXslRubberQuickTestRecordStdLineMapper.delete(
new LambdaQueryWrapper<MesXslRubberQuickTestRecordStdLine>().eq(MesXslRubberQuickTestRecordStdLine::getRecordId, id));
mesXslRubberQuickTestRecordChartPointMapper.delete(
new LambdaQueryWrapper<MesXslRubberQuickTestRecordChartPoint>().eq(MesXslRubberQuickTestRecordChartPoint::getRecordId, id));
this.removeById(id); this.removeById(id);
} }
@@ -162,6 +224,22 @@ public class MesXslRubberQuickTestRecordServiceImpl
.orderByAsc(MesXslRubberQuickTestRecordRawLine::getSortNo)); .orderByAsc(MesXslRubberQuickTestRecordRawLine::getSortNo));
} }
@Override
public List<MesXslRubberQuickTestRecordStdLine> selectStdLinesByRecordId(String recordId) {
return mesXslRubberQuickTestRecordStdLineMapper.selectList(
new LambdaQueryWrapper<MesXslRubberQuickTestRecordStdLine>()
.eq(MesXslRubberQuickTestRecordStdLine::getRecordId, recordId)
.orderByAsc(MesXslRubberQuickTestRecordStdLine::getSortNo));
}
@Override
public List<MesXslRubberQuickTestRecordChartPoint> selectChartPointsByRecordId(String recordId) {
return mesXslRubberQuickTestRecordChartPointMapper.selectList(
new LambdaQueryWrapper<MesXslRubberQuickTestRecordChartPoint>()
.eq(MesXslRubberQuickTestRecordChartPoint::getRecordId, recordId)
.orderByAsc(MesXslRubberQuickTestRecordChartPoint::getSortNo));
}
//update-begin---author:jiangxh ---date:20260528 for【MES】胶料快检记录单号JL+日期+4位流水自动生成----------- //update-begin---author:jiangxh ---date:20260528 for【MES】胶料快检记录单号JL+日期+4位流水自动生成-----------
@Override @Override
public String generateRecordNo(MesXslRubberQuickTestRecord context) { public String generateRecordNo(MesXslRubberQuickTestRecord context) {
@@ -256,69 +334,52 @@ public class MesXslRubberQuickTestRecordServiceImpl
@Override @Override
public void fillQuickTestTypeForRecord(MesXslRubberQuickTestRecord record) { public void fillQuickTestTypeForRecord(MesXslRubberQuickTestRecord record) {
if (record == null || oConvertUtils.isNotEmpty(record.getQuickTestTypeId())) { fillStdAndTypeForRecord(record);
}
//update-begin---author:jiangxh ---date:2026-06-22 for【快检记录】回填实验标准/方法/类型-----------
@Override
public void fillStdAndTypeForRecord(MesXslRubberQuickTestRecord record) {
if (record == null) {
return; return;
} }
String testMethodId = null; String testMethodId = record.getTestMethodId();
if (oConvertUtils.isNotEmpty(record.getStdId())) { if (oConvertUtils.isNotEmpty(record.getStdId())) {
MesXslRubberQuickTestStd std = mesXslRubberQuickTestStdService.getById(record.getStdId()); MesXslRubberQuickTestStd std = mesXslRubberQuickTestStdService.getById(record.getStdId());
if (std != null) { if (std != null) {
testMethodId = std.getTestMethodId(); if (oConvertUtils.isEmpty(record.getStdName())) {
record.setStdName(std.getStdName());
}
if (oConvertUtils.isEmpty(testMethodId)) {
testMethodId = std.getTestMethodId();
record.setTestMethodId(testMethodId);
}
} }
} }
if (oConvertUtils.isEmpty(testMethodId)) { if (oConvertUtils.isNotEmpty(testMethodId) && oConvertUtils.isEmpty(record.getTestMethodName())) {
return; MesXslRubberQuickTestMethod method = mesXslRubberQuickTestMethodService.getById(testMethodId);
if (method != null) {
record.setTestMethodName(method.getMethodName());
if (oConvertUtils.isEmpty(record.getQuickTestTypeId())
&& oConvertUtils.isNotEmpty(method.getQuickTestTypeId())) {
fillQuickTestType(record, method.getQuickTestTypeId());
}
}
} }
MesXslRubberQuickTestMethod method = mesXslRubberQuickTestMethodService.getById(testMethodId); if (oConvertUtils.isNotEmpty(record.getQuickTestTypeId()) && oConvertUtils.isEmpty(record.getQuickTestTypeName())) {
if (method != null && oConvertUtils.isNotEmpty(method.getQuickTestTypeId())) { fillQuickTestType(record, record.getQuickTestTypeId());
fillQuickTestType(record, method.getQuickTestTypeId());
} }
} }
//update-end---author:jiangxh ---date:2026-06-22 for【快检记录】回填实验标准/方法/类型-----------
//update-end---author:jiangxh ---date:20260618 for【快检记录】桌面端快检记录号 yyyyMMdd+流水+胶料号----------- //update-end---author:jiangxh ---date:20260618 for【快检记录】桌面端快检记录号 yyyyMMdd+流水+胶料号-----------
//update-begin---author:jiangxh ---date:20260525 for【MES】胶料信息多选按实验标准批量生成快检记录----------- //update-begin---author:jiangxh ---date:20260525 for【MES】胶料信息多选按实验标准批量生成快检记录-----------
@Override @Override
@Transactional(rollbackFor = Exception.class) @Transactional(rollbackFor = Exception.class)
public List<String> batchFromMaterial(MesXslRubberQuickTestRecordBatchFromMaterialVO vo) { public List<String> batchFromMaterial(MesXslRubberQuickTestRecordBatchFromMaterialVO vo) {
if (vo == null || CollectionUtils.isEmpty(vo.getMaterialIds())) { //update-begin---author:jiangxh ---date:2026-06-22 for【快检记录】取消胶料列表批量生成改由桌面端同步-----------
throw new JeecgBootException("请至少选择一条胶料信息"); throw new JeecgBootException("胶料快检记录已改为由桌面端同步创建,请使用桌面端「胶料快检记录」功能");
} //update-end---author:jiangxh ---date:2026-06-22 for【快检记录】取消胶料列表批量生成改由桌面端同步-----------
List<String> createdIds = new ArrayList<>();
for (String materialId : vo.getMaterialIds()) {
if (oConvertUtils.isEmpty(materialId)) {
continue;
}
String mid = materialId.trim();
MesMaterial material = mesMaterialService.getById(mid);
if (material == null) {
throw new JeecgBootException("胶料不存在:" + mid);
}
MesXslRubberQuickTestStd std = findApprovedStdForMaterial(mid);
if (std == null) {
throw new JeecgBootException(
"胶料【" + material.getMaterialName() + "】未找到已启用且已批准的实验标准,请先在胶料快检实验标准中维护");
}
List<MesXslRubberQuickTestStdLine> stdLines = mesXslRubberQuickTestStdService.selectLinesByStdId(std.getId());
if (CollectionUtils.isEmpty(stdLines)) {
throw new JeecgBootException("胶料【" + material.getMaterialName() + "】关联的实验标准无明细数据");
}
MesXslRubberQuickTestRecord main = buildMainFromMaterial(material, std, vo);
List<MesXslRubberQuickTestRecordLine> recordLines = new ArrayList<>();
for (MesXslRubberQuickTestStdLine stdLine : stdLines) {
MesXslRubberQuickTestRecordLine rl = new MesXslRubberQuickTestRecordLine();
rl.setDataPointId(stdLine.getDataPointId());
rl.setInspectItem(stdLine.getPointName());
rl.setLowerLimit(stdLine.getLowerLimit());
rl.setUpperLimit(stdLine.getUpperLimit());
recordLines.add(rl);
}
saveMain(main, recordLines);
createdIds.add(main.getId());
}
if (createdIds.isEmpty()) {
throw new JeecgBootException("未生成任何快检记录");
}
return createdIds;
} }
private MesXslRubberQuickTestStd findApprovedStdForMaterial(String materialId) { private MesXslRubberQuickTestStd findApprovedStdForMaterial(String materialId) {

View File

@@ -0,0 +1,67 @@
-- 原材料需求计划补充物料ID和计划日期用于按日期+物料汇总
SET @ddl_sql = (
SELECT IFNULL(
CONCAT(
'ALTER TABLE `mes_xsl_raw_material_demand_plan` ',
GROUP_CONCAT(stmt SEPARATOR ', ')
),
'SELECT 1'
)
FROM (
SELECT
CASE
WHEN NOT EXISTS (
SELECT 1
FROM information_schema.COLUMNS
WHERE TABLE_SCHEMA = DATABASE()
AND TABLE_NAME = 'mes_xsl_raw_material_demand_plan'
AND COLUMN_NAME = 'material_id'
)
THEN 'ADD COLUMN `material_id` varchar(32) DEFAULT NULL COMMENT ''物料ID(密炼物料信息ID)'' AFTER `machine_name`'
ELSE NULL
END AS stmt
UNION ALL
SELECT
CASE
WHEN NOT EXISTS (
SELECT 1
FROM information_schema.COLUMNS
WHERE TABLE_SCHEMA = DATABASE()
AND TABLE_NAME = 'mes_xsl_raw_material_demand_plan'
AND COLUMN_NAME = 'plan_date'
)
THEN 'ADD COLUMN `plan_date` date DEFAULT NULL COMMENT ''计划日期'' AFTER `actual_weight`'
ELSE NULL
END AS stmt
UNION ALL
SELECT
CASE
WHEN NOT EXISTS (
SELECT 1
FROM information_schema.STATISTICS
WHERE TABLE_SCHEMA = DATABASE()
AND TABLE_NAME = 'mes_xsl_raw_material_demand_plan'
AND INDEX_NAME = 'idx_raw_material_demand_material'
)
THEN 'ADD KEY `idx_raw_material_demand_material` (`material_id`)'
ELSE NULL
END AS stmt
UNION ALL
SELECT
CASE
WHEN NOT EXISTS (
SELECT 1
FROM information_schema.STATISTICS
WHERE TABLE_SCHEMA = DATABASE()
AND TABLE_NAME = 'mes_xsl_raw_material_demand_plan'
AND INDEX_NAME = 'idx_raw_material_demand_date'
)
THEN 'ADD KEY `idx_raw_material_demand_date` (`plan_date`)'
ELSE NULL
END AS stmt
) t
WHERE t.stmt IS NOT NULL
);
PREPARE stmt FROM @ddl_sql;
EXECUTE stmt;
DEALLOCATE PREPARE stmt;

View File

@@ -0,0 +1,42 @@
-- MES 胶料快检记录主表扩展实验标准/方法字段新增数据标准明细与曲线图明细
SET NAMES utf8mb4;
ALTER TABLE `mes_xsl_rubber_quick_test_record`
ADD COLUMN `std_name` varchar(200) DEFAULT NULL COMMENT '实验标准名称冗余' AFTER `std_id`,
ADD COLUMN `test_method_id` varchar(32) DEFAULT NULL COMMENT '实验方法ID' AFTER `std_name`,
ADD COLUMN `test_method_name` varchar(200) DEFAULT NULL COMMENT '实验方法名称冗余' AFTER `test_method_id`;
CREATE TABLE IF NOT EXISTS `mes_xsl_rubber_quick_test_record_std_line` (
`id` varchar(32) NOT NULL COMMENT '主键',
`record_id` varchar(32) NOT NULL COMMENT '主表 mes_xsl_rubber_quick_test_record.id',
`data_point_id` varchar(32) DEFAULT NULL COMMENT '数据点ID',
`point_name` varchar(128) DEFAULT NULL COMMENT '数据点名称',
`lower_limit` decimal(18,6) DEFAULT NULL COMMENT '下限值',
`lower_warn` decimal(18,6) DEFAULT NULL COMMENT '下警告值',
`target_value` decimal(18,6) DEFAULT NULL COMMENT '目标值',
`upper_warn` decimal(18,6) DEFAULT NULL COMMENT '上警告值',
`upper_limit` decimal(18,6) DEFAULT NULL COMMENT '上限值',
`sort_no` int DEFAULT NULL COMMENT '排序号',
`create_by` varchar(32) DEFAULT NULL COMMENT '创建人',
`create_time` datetime DEFAULT NULL COMMENT '创建时间',
`update_by` varchar(32) DEFAULT NULL COMMENT '更新人',
`update_time` datetime DEFAULT NULL COMMENT '更新时间',
PRIMARY KEY (`id`),
KEY `idx_rubber_quick_test_record_std_record_id` (`record_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='MES胶料快检记录数据标准明细';
CREATE TABLE IF NOT EXISTS `mes_xsl_rubber_quick_test_record_chart_point` (
`id` varchar(32) NOT NULL COMMENT '主键',
`record_id` varchar(32) NOT NULL COMMENT '主表 mes_xsl_rubber_quick_test_record.id',
`time_min` decimal(10,4) DEFAULT NULL COMMENT '时间(min)',
`upper_temp` decimal(18,6) DEFAULT NULL COMMENT '上模温度',
`lower_temp` decimal(18,6) DEFAULT NULL COMMENT '下模温度',
`torque_s` decimal(18,6) DEFAULT NULL COMMENT 'S''(dNm)',
`sort_no` int DEFAULT NULL COMMENT '排序号',
`create_by` varchar(32) DEFAULT NULL COMMENT '创建人',
`create_time` datetime DEFAULT NULL COMMENT '创建时间',
`update_by` varchar(32) DEFAULT NULL COMMENT '更新人',
`update_time` datetime DEFAULT NULL COMMENT '更新时间',
PRIMARY KEY (`id`),
KEY `idx_rubber_quick_test_record_chart_record_id` (`record_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='MES胶料快检记录曲线图数据点';

View File

@@ -5,15 +5,6 @@
<a-button type="primary" v-auth="'mes:mes_material:add'" @click="handleAdd" preIcon="ant-design:plus-outlined">新增</a-button> <a-button type="primary" v-auth="'mes:mes_material:add'" @click="handleAdd" preIcon="ant-design:plus-outlined">新增</a-button>
<a-button type="primary" v-auth="'mes:mes_material:exportXls'" preIcon="ant-design:export-outlined" @click="onExportXls">导出</a-button> <a-button type="primary" v-auth="'mes:mes_material:exportXls'" preIcon="ant-design:export-outlined" @click="onExportXls">导出</a-button>
<j-upload-button type="primary" v-auth="'mes:mes_material:importExcel'" preIcon="ant-design:import-outlined" @click="onImportXls">导入</j-upload-button> <j-upload-button type="primary" v-auth="'mes:mes_material:importExcel'" preIcon="ant-design:import-outlined" @click="onImportXls">导入</j-upload-button>
<a-button
type="primary"
v-auth="'mes:mes_material:rubberQuickTestInspect'"
preIcon="ant-design:experiment-outlined"
:disabled="selectedRowKeys.length === 0"
@click="handleRubberQuickTest"
>
检验
</a-button>
<a-dropdown v-if="selectedRowKeys.length > 0"> <a-dropdown v-if="selectedRowKeys.length > 0">
<template #overlay> <template #overlay>
<a-menu> <a-menu>
@@ -38,10 +29,7 @@ import { useListPage } from '/@/hooks/system/useListPage';
import MesMaterialModal from './modules/MesMaterialModal.vue'; import MesMaterialModal from './modules/MesMaterialModal.vue';
import { columns, searchFormSchema } from './MesMaterial.data'; import { columns, searchFormSchema } from './MesMaterial.data';
import { batchDelete, deleteOne, getExportUrl, getImportUrl, list } from './MesMaterial.api'; import { batchDelete, deleteOne, getExportUrl, getImportUrl, list } from './MesMaterial.api';
import { batchFromMaterial } from '/@/views/xslmes/mesXslRubberQuickTestRecord/MesXslRubberQuickTestRecord.api';
import { useMessage } from '/@/hooks/web/useMessage';
const { createMessage } = useMessage();
const [registerModal, { openModal }] = useModal(); const [registerModal, { openModal }] = useModal();
const { tableContext, onExportXls, onImportXls } = useListPage({ const { tableContext, onExportXls, onImportXls } = useListPage({
tableProps: { tableProps: {
@@ -75,18 +63,6 @@ async function batchHandleDelete() {
function handleSuccess() { function handleSuccess() {
reload(); reload();
} }
async function handleRubberQuickTest() {
if (!selectedRowKeys.value?.length) {
createMessage.warning('请至少选择一条胶料');
return;
}
try {
await batchFromMaterial({ materialIds: [...selectedRowKeys.value] });
createMessage.success('快检记录已生成,请到「胶料快检记录」中编辑');
} catch (e: any) {
createMessage.error(e?.message || '生成失败');
}
}
function getTableAction(record) { function getTableAction(record) {
return [{ label: '编辑', onClick: handleEdit.bind(null, record), auth: 'mes:mes_material:edit' }]; return [{ label: '编辑', onClick: handleEdit.bind(null, record), auth: 'mes:mes_material:edit' }];
} }

View File

@@ -1,6 +1,7 @@
import { BasicColumn, FormSchema } from '/@/components/Table'; import { BasicColumn, FormSchema } from '/@/components/Table';
export const baseColumns: BasicColumn[] = [ export const baseColumns: BasicColumn[] = [
{ title: '计划日期', align: 'center', dataIndex: 'planDate', width: 130 },
{ title: 'ERP编号', align: 'center', dataIndex: 'erpCode', width: 180 }, { title: 'ERP编号', align: 'center', dataIndex: 'erpCode', width: 180 },
{ title: '原材料名称', align: 'center', dataIndex: 'rawMaterialName', width: 220, ellipsis: true }, { title: '原材料名称', align: 'center', dataIndex: 'rawMaterialName', width: 220, ellipsis: true },
{ title: '需求重量', align: 'center', dataIndex: 'demandWeight', width: 140 }, { title: '需求重量', align: 'center', dataIndex: 'demandWeight', width: 140 },
@@ -14,15 +15,17 @@ export const machineColumns: BasicColumn[] = [
]; ];
export const searchFormSchema: FormSchema[] = [ export const searchFormSchema: FormSchema[] = [
{ label: '计划日期', field: 'planDate', component: 'DatePicker', colProps: { span: 6 } },
{ label: 'ERP编号', field: 'erpCode', component: 'JInput', colProps: { span: 6 } }, { label: 'ERP编号', field: 'erpCode', component: 'JInput', colProps: { span: 6 } },
{ label: '原材料名称', field: 'rawMaterialName', component: 'JInput', colProps: { span: 6 } }, { label: '原材料名称', field: 'rawMaterialName', component: 'JInput', colProps: { span: 6 } },
]; ];
export const superQuerySchema = { export const superQuerySchema = {
machineName: { title: '机台', order: 0, view: 'text' }, planDate: { title: '计划日期', order: 0, view: 'date' },
erpCode: { title: 'ERP编号', order: 1, view: 'text' }, machineName: { title: '机台', order: 1, view: 'text' },
rawMaterialName: { title: '原材料名称', order: 2, view: 'text' }, erpCode: { title: 'ERP编号', order: 2, view: 'text' },
demandWeight: { title: '需求重量', order: 3, view: 'number' }, rawMaterialName: { title: '原材料名称', order: 3, view: 'text' },
standardWeight: { title: '标准重量', order: 4, view: 'number' }, demandWeight: { title: '需求重量', order: 4, view: 'number' },
actualWeight: { title: '实际重量', order: 5, view: 'number' }, standardWeight: { title: '标准重量', order: 5, view: 'number' },
actualWeight: { title: '实际重量', order: 6, view: 'number' },
}; };

View File

@@ -13,7 +13,9 @@ enum Api {
exportXls = '/xslmes/mesXslRubberQuickTestRecord/exportXls', exportXls = '/xslmes/mesXslRubberQuickTestRecord/exportXls',
queryById = '/xslmes/mesXslRubberQuickTestRecord/queryById', queryById = '/xslmes/mesXslRubberQuickTestRecord/queryById',
queryLineList = '/xslmes/mesXslRubberQuickTestRecord/queryLineListByRecordId', queryLineList = '/xslmes/mesXslRubberQuickTestRecord/queryLineListByRecordId',
batchFromMaterial = '/xslmes/mesXslRubberQuickTestRecord/batchFromMaterial', queryStdLineList = '/xslmes/mesXslRubberQuickTestRecord/queryStdLineListByRecordId',
queryRawLineList = '/xslmes/mesXslRubberQuickTestRecord/queryRawLineListByRecordId',
queryChartPointList = '/xslmes/mesXslRubberQuickTestRecord/queryChartPointListByRecordId',
} }
export const getExportUrl = Api.exportXls; export const getExportUrl = Api.exportXls;
@@ -25,7 +27,11 @@ export const queryById = (params: { id: string }) => defHttp.get({ url: Api.quer
export const queryLineListByRecordId = (params: { id: string }) => defHttp.get({ url: Api.queryLineList, params }); export const queryLineListByRecordId = (params: { id: string }) => defHttp.get({ url: Api.queryLineList, params });
export const batchFromMaterial = (params) => defHttp.post({ url: Api.batchFromMaterial, params }); export const queryStdLineListByRecordId = (params: { id: string }) => defHttp.get({ url: Api.queryStdLineList, params });
export const queryRawLineListByRecordId = (params: { id: string }) => defHttp.get({ url: Api.queryRawLineList, params });
export const queryChartPointListByRecordId = (params: { id: string }) => defHttp.get({ url: Api.queryChartPointList, params });
export const deleteOne = (params, handleSuccess) => { export const deleteOne = (params, handleSuccess) => {
return defHttp.delete({ url: Api.deleteOne, params }, { joinParamsToUrl: true }).then(() => { return defHttp.delete({ url: Api.deleteOne, params }, { joinParamsToUrl: true }).then(() => {

View File

@@ -4,14 +4,13 @@ import { JVxeColumn, JVxeTypes } from '/@/components/jeecg/JVxeTable/types';
const numProps = { style: { width: '100%' }, precision: 6 }; const numProps = { style: { width: '100%' }, precision: 6 };
export const columns: BasicColumn[] = [ export const columns: BasicColumn[] = [
{ title: '号', align: 'center', dataIndex: 'recordNo', width: 150 }, { title: '快检记录号', align: 'center', dataIndex: 'recordNo', width: 150 },
{ title: '胶料名称', align: 'center', dataIndex: 'rubberMaterialName', width: 140 }, { title: '胶料名称', align: 'center', dataIndex: 'rubberMaterialName', width: 140 },
{ title: '生产机台', align: 'center', dataIndex: 'prodEquipmentName', width: 120 }, { title: '机台', align: 'center', dataIndex: 'prodEquipmentName', width: 120 },
{ title: '生产日期', align: 'center', dataIndex: 'productionDate', width: 110 }, { title: '密炼日期', align: 'center', dataIndex: 'productionDate', width: 110 },
{ title: '车次编号', align: 'center', dataIndex: 'trainNo', width: 100 }, { title: '车次编号', align: 'center', dataIndex: 'trainNo', width: 100 },
{ title: '班次', align: 'center', dataIndex: 'workShift_dictText', width: 80 }, { title: '班次', align: 'center', dataIndex: 'workShift_dictText', width: 80 },
{ title: '班组', align: 'center', dataIndex: 'workTeam_dictText', width: 80 }, { title: '试验次数', align: 'center', dataIndex: 'inspectTimes', width: 90 },
{ title: '检验次数', align: 'center', dataIndex: 'inspectTimes', width: 90 },
{ title: '检验时间', align: 'center', dataIndex: 'inspectTime', width: 165 }, { title: '检验时间', align: 'center', dataIndex: 'inspectTime', width: 165 },
{ {
title: '检验人', title: '检验人',
@@ -20,22 +19,24 @@ export const columns: BasicColumn[] = [
width: 100, width: 100,
customRender: ({ record }) => record?.inspectorRealname || record?.inspectorUserId_dictText || '', customRender: ({ record }) => record?.inspectorRealname || record?.inspectorUserId_dictText || '',
}, },
{ title: '检验类型', align: 'center', dataIndex: 'quickTestTypeName', width: 120 }, { title: '实验标准', align: 'center', dataIndex: 'stdName', width: 120 },
{ title: '实验方法', align: 'center', dataIndex: 'testMethodName', width: 120 },
{ title: '实验类型', align: 'center', dataIndex: 'quickTestTypeName', width: 120 },
{ title: '检验结果', align: 'center', dataIndex: 'inspectResult_dictText', width: 90 }, { title: '检验结果', align: 'center', dataIndex: 'inspectResult_dictText', width: 90 },
{ title: '生产计划', align: 'center', dataIndex: 'productionPlanNo', width: 120 }, { title: '密炼计划', align: 'center', dataIndex: 'productionPlanNo', width: 120 },
{ title: '检验机台', align: 'center', dataIndex: 'inspectEquipmentName', width: 120 }, { title: '检验机台', align: 'center', dataIndex: 'inspectEquipmentName', width: 120 },
{ title: '胶料卡片号', align: 'center', dataIndex: 'rubberCardNo', width: 120 }, { title: '胶料卡片号', align: 'center', dataIndex: 'rubberCardNo', width: 120 },
{ title: '胶料批次', align: 'center', dataIndex: 'rubberBatchNo', width: 120 }, { title: '胶料批次', align: 'center', dataIndex: 'rubberBatchNo', width: 120 },
]; ];
export const searchFormSchema: FormSchema[] = [ export const searchFormSchema: FormSchema[] = [
{ label: '号', field: 'recordNo', component: 'Input', colProps: { span: 6 } }, { label: '快检记录号', field: 'recordNo', component: 'Input', colProps: { span: 6 } },
{ label: '胶料名称', field: 'rubberMaterialName', component: 'Input', colProps: { span: 6 } }, { label: '胶料名称', field: 'rubberMaterialName', component: 'Input', colProps: { span: 6 } },
{ {
label: '生产机台', label: '机台',
field: 'prodEquipmentLedgerId', field: 'prodEquipmentLedgerId',
component: 'JDictSelectTag', component: 'JDictSelectTag',
componentProps: { dictCode: 'mes_xsl_equipment_ledger,equipment_name,id', placeholder: '请选择生产机台' }, componentProps: { dictCode: 'mes_xsl_equipment_ledger,equipment_name,id', placeholder: '请选择机台' },
colProps: { span: 6 }, colProps: { span: 6 },
}, },
{ {
@@ -53,20 +54,13 @@ export const searchFormSchema: FormSchema[] = [
colProps: { span: 6 }, colProps: { span: 6 },
}, },
{ {
label: '班组', label: '实验类型',
field: 'workTeam',
component: 'JDictSelectTag',
componentProps: { dictCode: 'xslmes_rubber_quick_test_work_team', placeholder: '请选择班组' },
colProps: { span: 6 },
},
{
label: '检验类型',
field: 'quickTestTypeId', field: 'quickTestTypeId',
component: 'JSearchSelect', component: 'JSearchSelect',
componentProps: { componentProps: {
dict: 'mes_xsl_rubber_quick_test_type,type_name,id', dict: 'mes_xsl_rubber_quick_test_type,type_name,id',
async: true, async: true,
placeholder: '请选择验类型', placeholder: '请选择验类型',
}, },
colProps: { span: 6 }, colProps: { span: 6 },
}, },
@@ -82,7 +76,7 @@ export const searchFormSchema: FormSchema[] = [
}, },
colProps: { span: 6 }, colProps: { span: 6 },
}, },
{ label: '生产计划', field: 'productionPlanNo', component: 'Input', colProps: { span: 6 } }, { label: '密炼计划', field: 'productionPlanNo', component: 'Input', colProps: { span: 6 } },
{ label: '胶料批次', field: 'rubberBatchNo', component: 'Input', colProps: { span: 6 } }, { label: '胶料批次', field: 'rubberBatchNo', component: 'Input', colProps: { span: 6 } },
{ {
label: '检验结果', label: '检验结果',
@@ -103,7 +97,7 @@ export const formSchema: FormSchema[] = [
{ label: '', field: 'inspectorUsername', component: 'Input', show: false }, { label: '', field: 'inspectorUsername', component: 'Input', show: false },
{ label: '', field: 'quickTestTypeId', component: 'Input', show: false }, { label: '', field: 'quickTestTypeId', component: 'Input', show: false },
{ {
label: '号', label: '快检记录号',
field: 'recordNo', field: 'recordNo',
component: 'Input', component: 'Input',
componentProps: { readonly: true, placeholder: '保存时自动生成' }, componentProps: { readonly: true, placeholder: '保存时自动生成' },
@@ -115,13 +109,31 @@ export const formSchema: FormSchema[] = [
componentProps: { readonly: true }, componentProps: { readonly: true },
}, },
{ {
label: '生产机台', label: '实验标准',
field: 'stdName',
component: 'Input',
componentProps: { readonly: true },
},
{
label: '实验方法',
field: 'testMethodName',
component: 'Input',
componentProps: { readonly: true },
},
{
label: '实验类型',
field: 'quickTestTypeName',
component: 'Input',
componentProps: { readonly: true },
},
{
label: '炼机台',
field: 'prodEquipmentName', field: 'prodEquipmentName',
component: 'Input', component: 'Input',
slot: 'prodEquipmentPicker', slot: 'prodEquipmentPicker',
}, },
{ {
label: '生产日期', label: '密炼日期',
field: 'productionDate', field: 'productionDate',
component: 'DatePicker', component: 'DatePicker',
componentProps: { valueFormat: 'YYYY-MM-DD', style: { width: '100%' } }, componentProps: { valueFormat: 'YYYY-MM-DD', style: { width: '100%' } },
@@ -134,13 +146,7 @@ export const formSchema: FormSchema[] = [
componentProps: { dictCode: 'xslmes_rubber_quick_test_work_shift', placeholder: '请选择班次' }, componentProps: { dictCode: 'xslmes_rubber_quick_test_work_shift', placeholder: '请选择班次' },
}, },
{ {
label: '班组', label: '试验次数',
field: 'workTeam',
component: 'JDictSelectTag',
componentProps: { dictCode: 'xslmes_rubber_quick_test_work_team', placeholder: '请选择班组' },
},
{
label: '检验次数',
field: 'inspectTimes', field: 'inspectTimes',
component: 'InputNumber', component: 'InputNumber',
componentProps: { style: { width: '100%' }, min: 0, precision: 0 }, componentProps: { style: { width: '100%' }, min: 0, precision: 0 },
@@ -176,23 +182,13 @@ export const formSchema: FormSchema[] = [
}), }),
}, },
{ label: '', field: 'inspectorRealname', component: 'Input', show: false }, { label: '', field: 'inspectorRealname', component: 'Input', show: false },
{
label: '检验类型',
field: 'quickTestTypeId',
component: 'JSearchSelect',
componentProps: {
dict: 'mes_xsl_rubber_quick_test_type,type_name,id',
async: true,
placeholder: '请选择检验类型',
},
},
{ {
label: '检验结果', label: '检验结果',
field: 'inspectResult', field: 'inspectResult',
component: 'JDictSelectTag', component: 'JDictSelectTag',
componentProps: { dictCode: 'xslmes_rubber_quick_test_record_result', placeholder: '合格/不合格' }, componentProps: { dictCode: 'xslmes_rubber_quick_test_record_result', placeholder: '合格/不合格' },
}, },
{ label: '生产计划', field: 'productionPlanNo', component: 'Input' }, { label: '密炼计划', field: 'productionPlanNo', component: 'Input' },
{ {
label: '检验机台', label: '检验机台',
field: 'inspectEquipmentName', field: 'inspectEquipmentName',
@@ -203,6 +199,40 @@ export const formSchema: FormSchema[] = [
{ label: '胶料批次', field: 'rubberBatchNo', component: 'Input' }, { label: '胶料批次', field: 'rubberBatchNo', component: 'Input' },
]; ];
export const stdLineJVxeColumns: JVxeColumn[] = [
{ title: '', key: 'dataPointId', type: JVxeTypes.hidden },
{ title: '数据点', key: 'pointName', type: JVxeTypes.normal, width: 160, disabled: true },
{ title: '下限值', key: 'lowerLimit', type: JVxeTypes.normal, width: 100, disabled: true },
{ title: '下警告值', key: 'lowerWarn', type: JVxeTypes.normal, width: 100, disabled: true },
{ title: '目标值', key: 'targetValue', type: JVxeTypes.normal, width: 100, disabled: true },
{ title: '上警告值', key: 'upperWarn', type: JVxeTypes.normal, width: 100, disabled: true },
{ title: '上限值', key: 'upperLimit', type: JVxeTypes.normal, width: 100, disabled: true },
];
export const rawLineJVxeColumns: JVxeColumn[] = [
{ title: '', key: 'dataPointId', type: JVxeTypes.hidden },
{ title: '编号', key: 'rowNo', type: JVxeTypes.normal, width: 90, disabled: true },
{ title: '数据点', key: 'inspectItem', type: JVxeTypes.normal, width: 140, disabled: true },
{ title: '下限值', key: 'lowerLimit', type: JVxeTypes.normal, width: 90, disabled: true },
{ title: '上限值', key: 'upperLimit', type: JVxeTypes.normal, width: 90, disabled: true },
{
title: '检测值',
key: 'inspectValue',
type: JVxeTypes.inputNumber,
width: 100,
componentProps: numProps,
},
{
title: '行检验结果',
key: 'rowInspectResult',
type: JVxeTypes.select,
width: 110,
disabled: true,
dictCode: 'xslmes_rubber_quick_test_record_result',
},
];
/** @deprecated 历史汇总明细,仅兼容旧数据 */
export const lineJVxeColumns: JVxeColumn[] = [ export const lineJVxeColumns: JVxeColumn[] = [
{ title: '', key: 'dataPointId', type: JVxeTypes.hidden }, { title: '', key: 'dataPointId', type: JVxeTypes.hidden },
{ title: '检验项目', key: 'inspectItem', type: JVxeTypes.normal, width: 180, disabled: true }, { title: '检验项目', key: 'inspectItem', type: JVxeTypes.normal, width: 180, disabled: true },

View File

@@ -3,14 +3,14 @@
v-bind="$attrs" v-bind="$attrs"
destroyOnClose destroyOnClose
:title="title" :title="title"
width="1100px" width="1200px"
@register="registerModal" @register="registerModal"
@ok="handleSubmit" @ok="handleSubmit"
> >
<BasicForm @register="registerForm"> <BasicForm @register="registerForm">
<template #prodEquipmentPicker="{ model, field }"> <template #prodEquipmentPicker="{ model, field }">
<a-input-group compact style="display: flex; width: 100%"> <a-input-group compact style="display: flex; width: 100%">
<a-input v-model:value="model[field]" read-only placeholder="请选择生产机台" style="flex: 1" /> <a-input v-model:value="model[field]" read-only placeholder="请选择机台" style="flex: 1" />
<a-button type="primary" :disabled="isDetail" @click="openProdEquipmentSelect">选择</a-button> <a-button type="primary" :disabled="isDetail" @click="openProdEquipmentSelect">选择</a-button>
<a-button v-if="model.prodEquipmentLedgerId && !isDetail" @click="clearProdEquipment(model)">清除</a-button> <a-button v-if="model.prodEquipmentLedgerId && !isDetail" @click="clearProdEquipment(model)">清除</a-button>
</a-input-group> </a-input-group>
@@ -23,34 +23,86 @@
</a-input-group> </a-input-group>
</template> </template>
</BasicForm> </BasicForm>
<a-divider orientation="left">检验明细由实验标准带出不可增删</a-divider>
<JVxeTable <template v-if="useNewDetail">
v-if="tableReady" <a-divider orientation="left">数据标准明细</a-divider>
ref="lineTableRef" <JVxeTable
row-number v-if="tableReady"
keep-source row-number
:toolbar="false" keep-source
:insert-row="false" :toolbar="false"
:remove-btn="false" :insert-row="false"
:max-height="380" :remove-btn="false"
:loading="lineLoading" :max-height="260"
:columns="lineJVxeColumns" :loading="detailLoading"
:dataSource="lineDataSource" :columns="stdLineJVxeColumns"
:disabled="isDetail" :dataSource="stdLineDataSource"
/> disabled
/>
<a-divider orientation="left">试验结果明细</a-divider>
<JVxeTable
v-if="tableReady"
row-number
keep-source
:toolbar="false"
:insert-row="false"
:remove-btn="false"
:max-height="320"
:loading="detailLoading"
:columns="rawLineJVxeColumns"
:dataSource="rawLineDataSource"
:disabled="isDetail"
/>
<a-divider orientation="left">曲线图</a-divider>
<a-row :gutter="16">
<a-col :span="12">
<div class="chart-title">温度曲线上模/下模</div>
<div ref="tempChartRef" class="chart-box"></div>
</a-col>
<a-col :span="12">
<div class="chart-title">S'(dNm) 曲线</div>
<div ref="torqueChartRef" class="chart-box"></div>
</a-col>
</a-row>
</template>
<template v-else>
<a-divider orientation="left">检验明细(历史数据)</a-divider>
<JVxeTable
v-if="tableReady"
row-number
keep-source
:toolbar="false"
:insert-row="false"
:remove-btn="false"
:max-height="380"
:loading="detailLoading"
:columns="lineJVxeColumns"
:dataSource="lineDataSource"
:disabled="isDetail"
/>
</template>
<MesXslEquipmentLedgerSelectModal @register="registerLedgerModal" @select="onLedgerSelect" /> <MesXslEquipmentLedgerSelectModal @register="registerLedgerModal" @select="onLedgerSelect" />
</BasicModal> </BasicModal>
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>
import { computed, ref, unref } from 'vue'; import { computed, nextTick, ref, unref, watch, type Ref } from 'vue';
import { BasicModal, useModalInner, useModal } from '/@/components/Modal'; import { BasicModal, useModalInner, useModal } from '/@/components/Modal';
import { BasicForm, useForm } from '/@/components/Form/index'; import { BasicForm, useForm } from '/@/components/Form/index';
import type { JVxeTableInstance } from '/@/components/jeecg/JVxeTable/types';
import { useMessage } from '/@/hooks/web/useMessage'; import { useMessage } from '/@/hooks/web/useMessage';
import { useUserStore } from '/@/store/modules/user'; import { useUserStore } from '/@/store/modules/user';
import { formSchema, lineJVxeColumns } from '../MesXslRubberQuickTestRecord.data'; import { useECharts } from '/@/hooks/web/useECharts';
import { saveOrUpdate, queryById, queryLineListByRecordId } from '../MesXslRubberQuickTestRecord.api'; import {
formSchema,
lineJVxeColumns,
stdLineJVxeColumns,
rawLineJVxeColumns,
} from '../MesXslRubberQuickTestRecord.data';
import { saveOrUpdate, queryById, queryChartPointListByRecordId } from '../MesXslRubberQuickTestRecord.api';
import MesXslEquipmentLedgerSelectModal from '/@/views/xslmes/mesXslEquipInspectConfig/components/MesXslEquipmentLedgerSelectModal.vue'; import MesXslEquipmentLedgerSelectModal from '/@/views/xslmes/mesXslEquipInspectConfig/components/MesXslEquipmentLedgerSelectModal.vue';
const emit = defineEmits(['register', 'success']); const emit = defineEmits(['register', 'success']);
@@ -59,9 +111,19 @@
const isDetail = ref(false); const isDetail = ref(false);
const tableReady = ref(false); const tableReady = ref(false);
const lineLoading = ref(false); const detailLoading = ref(false);
const useNewDetail = ref(false);
const stdLineDataSource = ref<Recordable[]>([]);
const rawLineDataSource = ref<Recordable[]>([]);
const chartPointDataSource = ref<Recordable[]>([]);
const lineDataSource = ref<Recordable[]>([]); const lineDataSource = ref<Recordable[]>([]);
const lineTableRef = ref<JVxeTableInstance>();
const tempChartRef = ref<HTMLDivElement | null>(null);
const torqueChartRef = ref<HTMLDivElement | null>(null);
const { setOptions: setTempChartOptions, resize: resizeTempChart } = useECharts(tempChartRef as Ref<HTMLDivElement>);
const { setOptions: setTorqueChartOptions, resize: resizeTorqueChart } = useECharts(torqueChartRef as Ref<HTMLDivElement>);
const ledgerPickTarget = ref<'prod' | 'inspect'>('prod'); const ledgerPickTarget = ref<'prod' | 'inspect'>('prod');
const [registerForm, { resetFields, setFieldsValue, validate, setProps, getFieldsValue }] = useForm({ const [registerForm, { resetFields, setFieldsValue, validate, setProps, getFieldsValue }] = useForm({
@@ -75,19 +137,21 @@
const [registerModal, { setModalProps, closeModal }] = useModalInner(async (data) => { const [registerModal, { setModalProps, closeModal }] = useModalInner(async (data) => {
tableReady.value = false; tableReady.value = false;
stdLineDataSource.value = [];
rawLineDataSource.value = [];
chartPointDataSource.value = [];
lineDataSource.value = []; lineDataSource.value = [];
useNewDetail.value = false;
await resetFields(); await resetFields();
setModalProps({ confirmLoading: false, showCancelBtn: data?.showFooter, showOkBtn: data?.showFooter }); setModalProps({ confirmLoading: false, showCancelBtn: data?.showFooter, showOkBtn: data?.showFooter });
isDetail.value = !data?.showFooter; isDetail.value = !data?.showFooter;
setProps({ disabled: !data?.showFooter }); setProps({ disabled: !data?.showFooter });
if (data?.record?.id) { if (data?.record?.id) {
lineLoading.value = true; detailLoading.value = true;
try { try {
const mainRaw = await queryById({ id: data.record.id }); const mainRaw = await queryById({ id: data.record.id });
const m = (mainRaw as any)?.id != null ? mainRaw : (mainRaw as any)?.result ?? mainRaw; const m = (mainRaw as any)?.id != null ? mainRaw : (mainRaw as any)?.result ?? mainRaw;
const linesRaw = await queryLineListByRecordId({ id: data.record.id });
const list = Array.isArray(linesRaw) ? linesRaw : (linesRaw as any)?.result ?? [];
const patch: Recordable = { ...m }; const patch: Recordable = { ...m };
if (data?.showFooter && !patch.inspectorRealname && !patch.inspectorUserId) { if (data?.showFooter && !patch.inspectorRealname && !patch.inspectorUserId) {
const user = userStore.getUserInfo || {}; const user = userStore.getUserInfo || {};
@@ -96,16 +160,95 @@
patch.inspectorRealname = user.realname; patch.inspectorRealname = user.realname;
} }
await setFieldsValue(patch); await setFieldsValue(patch);
lineDataSource.value = list || [];
const stdLines = m?.stdLineList ?? [];
const rawLines = m?.rawLineList ?? [];
let chartPoints = m?.chartPointList ?? [];
const legacyLines = m?.lineList ?? [];
if (stdLines.length > 0 || rawLines.length > 0 || chartPoints.length > 0) {
useNewDetail.value = true;
stdLineDataSource.value = stdLines;
rawLineDataSource.value = rawLines;
if (!chartPoints.length && data?.record?.id) {
try {
const chartRes = await queryChartPointListByRecordId({ id: data.record.id });
chartPoints = Array.isArray(chartRes) ? chartRes : (chartRes as any)?.result ?? [];
} catch (_) {
chartPoints = [];
}
}
chartPointDataSource.value = chartPoints;
} else {
lineDataSource.value = legacyLines;
}
} finally { } finally {
lineLoading.value = false; detailLoading.value = false;
} }
} }
tableReady.value = true; tableReady.value = true;
if (useNewDetail.value) {
await scheduleRenderCharts(chartPointDataSource.value);
}
});
async function scheduleRenderCharts(points: Recordable[]) {
await nextTick();
await nextTick();
window.setTimeout(() => {
renderCharts(points);
resizeTempChart();
resizeTorqueChart();
}, 120);
}
watch(useNewDetail, async (visible) => {
if (visible && chartPointDataSource.value.length) {
await scheduleRenderCharts(chartPointDataSource.value);
}
}); });
const title = computed(() => (unref(isDetail) ? '快检记录详情' : '编辑胶料快检记录')); const title = computed(() => (unref(isDetail) ? '快检记录详情' : '编辑胶料快检记录'));
function toChartNumber(value: unknown) {
if (value === null || value === undefined || value === '') return null;
const num = Number(value);
return Number.isFinite(num) ? num : null;
}
function renderCharts(points: Recordable[]) {
if (!points?.length) {
setTempChartOptions({ title: { text: '暂无曲线数据', left: 'center', top: 'center', textStyle: { fontSize: 14 } } });
setTorqueChartOptions({ title: { text: '暂无曲线数据', left: 'center', top: 'center', textStyle: { fontSize: 14 } } });
return;
}
const sorted = [...points].sort((a, b) => (a.sortNo ?? 0) - (b.sortNo ?? 0));
const times = sorted.map((p) => toChartNumber(p.timeMin) ?? 0);
setTempChartOptions({
title: undefined,
tooltip: { trigger: 'axis' },
legend: { data: ['上模温度', '下模温度'] },
grid: { left: 48, right: 24, top: 40, bottom: 32 },
xAxis: { type: 'category', name: '时间(min)', data: times },
yAxis: { type: 'value', name: '温度()', min: 189, max: 201, interval: 3 },
series: [
{ name: '上模温度', type: 'line', smooth: true, data: sorted.map((p) => toChartNumber(p.upperTemp)) },
{ name: '下模温度', type: 'line', smooth: true, data: sorted.map((p) => toChartNumber(p.lowerTemp)) },
],
});
setTorqueChartOptions({
title: undefined,
tooltip: { trigger: 'axis' },
legend: { data: ["S'(dNm)"] },
grid: { left: 48, right: 24, top: 40, bottom: 32 },
xAxis: { type: 'category', name: '时间(min)', data: times },
yAxis: { type: 'value', name: "S'(dNm)", min: 0, max: 14.8 },
series: [{ name: "S'(dNm)", type: 'line', smooth: true, data: sorted.map((p) => toChartNumber(p.torqueS)) }],
});
}
function openProdEquipmentSelect() { function openProdEquipmentSelect() {
ledgerPickTarget.value = 'prod'; ledgerPickTarget.value = 'prod';
const vals = getFieldsValue(); const vals = getFieldsValue();
@@ -149,23 +292,36 @@
} }
try { try {
const values = await validate(); const values = await validate();
const lineRef = lineTableRef.value as any; const payload: Recordable = { ...values };
const tableData = (lineRef?.getTableData?.() || lineDataSource.value || []) as Recordable[];
const lineList = tableData if (unref(useNewDetail)) {
.filter((r) => r && r.inspectItem) payload.stdLineList = stdLineDataSource.value || [];
.map((r) => ({ payload.rawLineList = rawLineDataSource.value || [];
dataPointId: r.dataPointId, payload.chartPointList = chartPointDataSource.value || [];
inspectItem: r.inspectItem, if (!payload.stdLineList.length || !payload.rawLineList.length) {
lowerLimit: r.lowerLimit, createMessage.warning('数据标准明细与试验结果明细不能为空');
inspectValue: r.inspectValue, return;
upperLimit: r.upperLimit, }
})); } else {
if (!lineList.length) { const tableData = lineDataSource.value || [];
createMessage.warning('检验明细不能为空'); const lineList = tableData
return; .filter((r) => r && r.inspectItem)
.map((r) => ({
dataPointId: r.dataPointId,
inspectItem: r.inspectItem,
lowerLimit: r.lowerLimit,
inspectValue: r.inspectValue,
upperLimit: r.upperLimit,
}));
if (!lineList.length) {
createMessage.warning('检验明细不能为空');
return;
}
payload.lineList = lineList;
} }
setModalProps({ confirmLoading: true }); setModalProps({ confirmLoading: true });
await saveOrUpdate({ ...values, lineList }, true); await saveOrUpdate(payload, true);
closeModal(); closeModal();
emit('success'); emit('success');
} finally { } finally {
@@ -173,3 +329,15 @@
} }
} }
</script> </script>
<style scoped>
.chart-title {
font-size: 13px;
color: rgba(0, 0, 0, 0.65);
margin-bottom: 8px;
}
.chart-box {
width: 100%;
height: 220px;
}
</style>

View File

@@ -0,0 +1,11 @@
using Prism.Events;
namespace YY.Admin.Core.Events;
public class RubberQuickTestRecordChangedPayload
{
public string Action { get; set; } = string.Empty;
public string? RecordId { get; set; }
}
public class RubberQuickTestRecordChangedEvent : PubSubEvent<RubberQuickTestRecordChangedPayload> { }

View File

@@ -0,0 +1,44 @@
using YY.Admin.Core.Entity;
namespace YY.Admin.Core.Services;
public interface IRubberQuickTestRecordService
{
Task<RubberQuickTestRecordPageResult> PageAsync(
int pageNo,
int pageSize,
string? filterRecordNo = null,
string? filterRubberMaterialName = null,
string? filterPlanNo = null,
CancellationToken ct = default);
Task<MesXslRubberQuickTestRecord?> GetByIdAsync(string id, CancellationToken ct = default);
RubberQuickTestRecordLocalItem? GetByLocalId(string localId);
Task<RubberQuickTestRecordSaveResult> SaveAsync(MesXslRubberQuickTestRecord entity, CancellationToken ct = default);
/// <summary>更新本地未同步快检记录</summary>
Task<RubberQuickTestRecordSaveResult> UpdateLocalAsync(string localId, MesXslRubberQuickTestRecord entity, CancellationToken ct = default);
/// <summary>删除本地未同步的快检记录(已同步不可删)</summary>
bool DeleteUnsyncedLocal(string localId);
/// <summary>将本地未同步记录推送到 MES须已联网且密炼计划信息完整</summary>
Task<RubberQuickTestRecordSyncResult> SyncLocalAsync(string localId, CancellationToken ct = default);
string GenerateRecordNo(string rubberMaterialName);
}
public class RubberQuickTestRecordSyncResult
{
public bool Success { get; set; }
public string? Message { get; set; }
public string? RecordNo { get; set; }
}
public record RubberQuickTestRecordPageResult(
List<RubberQuickTestRecordListRow> Records,
long Total,
int PageNo,
int PageSize);

View File

@@ -7,6 +7,9 @@ public class MesXslRubberQuickTestRecord
public string? RubberMaterialId { get; set; } public string? RubberMaterialId { get; set; }
public string? RubberMaterialName { get; set; } public string? RubberMaterialName { get; set; }
public string? StdId { get; set; } public string? StdId { get; set; }
public string? StdName { get; set; }
public string? TestMethodId { get; set; }
public string? TestMethodName { get; set; }
public string? ProdEquipmentLedgerId { get; set; } public string? ProdEquipmentLedgerId { get; set; }
public string? ProdEquipmentName { get; set; } public string? ProdEquipmentName { get; set; }
public DateTime? ProductionDate { get; set; } public DateTime? ProductionDate { get; set; }
@@ -17,8 +20,18 @@ public class MesXslRubberQuickTestRecord
public string? InspectorUserId { get; set; } public string? InspectorUserId { get; set; }
public string? InspectorUsername { get; set; } public string? InspectorUsername { get; set; }
public string? InspectorRealname { get; set; } public string? InspectorRealname { get; set; }
public string? QuickTestTypeId { get; set; }
public string? QuickTestTypeName { get; set; }
public string? InspectResult { get; set; } public string? InspectResult { get; set; }
public string? ProductionPlanNo { get; set; } public string? ProductionPlanNo { get; set; }
public List<MesXslRubberQuickTestRecordLine>? LineList { get; set; } public DateTime? CreateTime { get; set; }
public List<MesXslRubberQuickTestRecordStdLine>? StdLineList { get; set; }
public List<MesXslRubberQuickTestRecordRawLine>? RawLineList { get; set; } public List<MesXslRubberQuickTestRecordRawLine>? RawLineList { get; set; }
public List<MesXslRubberQuickTestRecordChartPoint>? ChartPointList { get; set; }
/// <summary>列表展示:班次文本</summary>
public string? WorkShiftText { get; set; }
/// <summary>列表展示:是否合格</summary>
public string? InspectResultText { get; set; }
} }

View File

@@ -0,0 +1,13 @@
namespace YY.Admin.Core.Entity;
/// <summary>胶料快检记录曲线图数据点</summary>
public class MesXslRubberQuickTestRecordChartPoint
{
public string? Id { get; set; }
public string? RecordId { get; set; }
public decimal? TimeMin { get; set; }
public decimal? UpperTemp { get; set; }
public decimal? LowerTemp { get; set; }
public decimal? TorqueS { get; set; }
public int? SortNo { get; set; }
}

View File

@@ -0,0 +1,16 @@
namespace YY.Admin.Core.Entity;
/// <summary>胶料快检记录数据标准明细(实验标准快照)</summary>
public class MesXslRubberQuickTestRecordStdLine
{
public string? Id { get; set; }
public string? RecordId { get; set; }
public string? DataPointId { get; set; }
public string? PointName { get; set; }
public decimal? LowerLimit { get; set; }
public decimal? UpperLimit { get; set; }
public decimal? LowerWarn { get; set; }
public decimal? UpperWarn { get; set; }
public decimal? TargetValue { get; set; }
public int? SortNo { get; set; }
}

View File

@@ -0,0 +1,38 @@
namespace YY.Admin.Core.Entity;
/// <summary>胶料快检记录列表行</summary>
public class RubberQuickTestRecordListRow
{
public string? LocalId { get; set; }
public string? MesId { get; set; }
public string? RecordNo { get; set; }
public DateTime? ProductionDate { get; set; }
public string? ProdEquipmentName { get; set; }
public string? WorkShiftDisplay { get; set; }
public string? ProductionPlanNo { get; set; }
public string? RubberMaterialName { get; set; }
public string? StdName { get; set; }
public string? TestMethodName { get; set; }
public string? QuickTestTypeName { get; set; }
public string? TrainNo { get; set; }
public int? InspectTimes { get; set; }
public string? InspectorRealname { get; set; }
public DateTime? InspectDate { get; set; }
public string? InspectResultDisplay { get; set; }
public string SyncStatus { get; set; } = "Pending";
public string SyncStatusDisplay => SyncStatus switch
{
"Synced" => "已同步",
"Failed" => "同步失败",
_ => "未同步"
};
/// <summary>本地未同步记录可删除</summary>
public bool CanDelete => SyncStatus != "Synced" && !string.IsNullOrWhiteSpace(LocalId);
/// <summary>本地未同步记录可手动同步</summary>
public bool CanSync => SyncStatus != "Synced" && !string.IsNullOrWhiteSpace(LocalId);
/// <summary>本地未同步记录可编辑</summary>
public bool CanEdit => CanSync;
}

View File

@@ -0,0 +1,13 @@
namespace YY.Admin.Core.Entity;
/// <summary>桌面端本地胶料快检记录包装(含同步状态)</summary>
public class RubberQuickTestRecordLocalItem
{
public string LocalId { get; set; } = Guid.NewGuid().ToString("N");
public string? MesId { get; set; }
/// <summary>Pending / Synced / Failed</summary>
public string SyncStatus { get; set; } = "Pending";
public string? SyncError { get; set; }
public DateTime LocalCreateTime { get; set; } = DateTime.Now;
public MesXslRubberQuickTestRecord Record { get; set; } = new();
}

View File

@@ -0,0 +1,8 @@
namespace YY.Admin.Core.Entity;
public class RubberQuickTestRecordSaveResult
{
public MesXslRubberQuickTestRecord Record { get; set; } = new();
public string LocalId { get; set; } = string.Empty;
public string SyncStatus { get; set; } = "Pending";
}

View File

@@ -51,7 +51,7 @@ public class SysMenuSeedData : ISqlSugarEntitySeedData<SysMenu>
// 胶料快检实验标准(桌面端只读) // 胶料快检实验标准(桌面端只读)
new SysMenu{ Id=1300150011301, Pid=1300150000101, Title="胶料快检实验标准", Path="/xslmes/mesXslRubberQuickTestStd", Name="mesXslRubberQuickTestStd", Component="RubberQuickTestStdListView", Icon="&#xe7ce;", Type=MenuTypeEnum.Menu, CreateTime=DateTime.Parse("2022-02-10 00:00:00"), OrderNo=112 }, new SysMenu{ Id=1300150011301, Pid=1300150000101, Title="胶料快检实验标准", Path="/xslmes/mesXslRubberQuickTestStd", Name="mesXslRubberQuickTestStd", Component="RubberQuickTestStdListView", Icon="&#xe7ce;", Type=MenuTypeEnum.Menu, CreateTime=DateTime.Parse("2022-02-10 00:00:00"), OrderNo=112 },
// 胶料快检记录(紧接实验标准之后) // 胶料快检记录(紧接实验标准之后)
new SysMenu{ Id=1300150011201, Pid=1300150000101, Title="胶料快检记录", Path="/xslmes/rubberQuickTestOperation", Name="rubberQuickTestOperation", Component="RubberQuickTestOperationView", Icon="&#xe7de;", Type=MenuTypeEnum.Menu, CreateTime=DateTime.Parse("2022-02-10 00:00:00"), OrderNo=113 }, new SysMenu{ Id=1300150011201, Pid=1300150000101, Title="胶料快检记录", Path="/xslmes/rubberQuickTestRecord", Name="rubberQuickTestRecord", Component="RubberQuickTestRecordListView", Icon="&#xe7de;", Type=MenuTypeEnum.Menu, CreateTime=DateTime.Parse("2022-02-10 00:00:00"), OrderNo=113 },
// 密炼计划 // 密炼计划
new SysMenu{ Id=1300150011401, Pid=1300150000101, Title="密炼计划", Path="/xslmes/mesXslMixingProductionPlan", Name="mesXslMixingProductionPlan", Component="MixingProductionPlanListView", Icon="&#xe7ce;", Type=MenuTypeEnum.Menu, CreateTime=DateTime.Parse("2022-02-10 00:00:00"), OrderNo=114 }, new SysMenu{ Id=1300150011401, Pid=1300150000101, Title="密炼计划", Path="/xslmes/mesXslMixingProductionPlan", Name="mesXslMixingProductionPlan", Component="MixingProductionPlanListView", Icon="&#xe7ce;", Type=MenuTypeEnum.Menu, CreateTime=DateTime.Parse("2022-02-10 00:00:00"), OrderNo=114 },

View File

@@ -518,6 +518,9 @@ namespace YY.Admin.Core.SqlSugar
dbProvider.Updateable<SysMenu>() dbProvider.Updateable<SysMenu>()
.SetColumns(m => m.Title == "胶料快检记录") .SetColumns(m => m.Title == "胶料快检记录")
.SetColumns(m => m.Path == "/xslmes/rubberQuickTestRecord")
.SetColumns(m => m.Name == "rubberQuickTestRecord")
.SetColumns(m => m.Component == "RubberQuickTestRecordListView")
.SetColumns(m => m.OrderNo == 113) .SetColumns(m => m.OrderNo == 113)
.Where(m => m.Id == quickTestRecordMenuId) .Where(m => m.Id == quickTestRecordMenuId)
.ExecuteCommand(); .ExecuteCommand();

View File

@@ -0,0 +1,62 @@
using YY.Admin.Core;
namespace YY.Admin.Services.Service;
/// <summary>
/// 胶料快检相关 MES 只读数据(实验标准、密炼计划)专用轮询管理器。
/// </summary>
public class MesQuickTestDataPollManager : ISingletonDependency
{
public static readonly TimeSpan PollInterval = TimeSpan.FromSeconds(2);
private readonly List<(string Name, Func<Task> Task)> _tasks = [];
private readonly Timer _timer;
private readonly ILoggerService _logger;
private volatile bool _tickRunning;
public MesQuickTestDataPollManager(ILoggerService logger)
{
_logger = logger;
_timer = new Timer(OnTick, null, PollInterval, PollInterval);
_logger.Information($"[快检数据轮询] 已启动,间隔 {PollInterval.TotalSeconds} 秒");
}
public void Register(string name, Func<Task> pollTask)
{
lock (_tasks)
_tasks.Add((name, pollTask));
_logger.Information($"[快检数据轮询] 注册任务: {name}");
}
private void OnTick(object? _)
{
if (_tickRunning) return;
_ = Task.Run(async () =>
{
if (_tickRunning) return;
_tickRunning = true;
try
{
List<(string Name, Func<Task> Task)> snapshot;
lock (_tasks) snapshot = [.. _tasks];
foreach (var (name, task) in snapshot)
{
try
{
await task().ConfigureAwait(false);
}
catch (Exception ex)
{
_logger.Warning($"[快检数据轮询] 任务 [{name}] 异常: {ex.Message}");
}
}
}
finally
{
_tickRunning = false;
}
});
}
}

View File

@@ -53,17 +53,13 @@ public class MixingProductionPlanService : IMixingProductionPlanService, ISingle
LoadCacheFromDisk(); LoadCacheFromDisk();
_logger.Information($"[密炼计划] 服务初始化,缓存={_localCache.Count},在线={_networkMonitor.IsOnline}"); _logger.Information($"[密炼计划] 服务初始化,缓存={_localCache.Count},在线={_networkMonitor.IsOnline}");
_networkMonitor.StatusChanged += OnNetworkStatusChanged;
if (_networkMonitor.IsOnline)
_ = Task.Run(() => SyncFromRemoteAsync(CancellationToken.None));
} }
private string BaseUrl => (_configuration.GetValue<string>("JeecgIntegration:BaseUrl") ?? "http://localhost:8080/jeecg-boot").TrimEnd('/'); private string BaseUrl => (_configuration.GetValue<string>("JeecgIntegration:BaseUrl") ?? "http://localhost:8080/jeecg-boot").TrimEnd('/');
private int DefaultTenantId => (int?)_configuration.GetValue<long?>("JeecgIntegration:DefaultTenantId") ?? 1002; private int DefaultTenantId => (int?)_configuration.GetValue<long?>("JeecgIntegration:DefaultTenantId") ?? 1002;
private HttpClient CreateClient() => _httpClientFactory.CreateClient("JeecgApi"); private HttpClient CreateClient() => _httpClientFactory.CreateClient("JeecgApi");
public async Task<MixingProductionPlanPageResult> PageAsync( public Task<MixingProductionPlanPageResult> PageAsync(
int pageNo, int pageSize, int pageNo, int pageSize,
DateTime? planDateFrom = null, DateTime? planDateFrom = null,
DateTime? planDateTo = null, DateTime? planDateTo = null,
@@ -73,18 +69,7 @@ public class MixingProductionPlanService : IMixingProductionPlanService, ISingle
string? materialName = null, string? materialName = null,
CancellationToken ct = default) CancellationToken ct = default)
{ {
if (_networkMonitor.IsOnline) ct.ThrowIfCancellationRequested();
{
try
{
await SyncFromRemoteAsync(ct).ConfigureAwait(false);
}
catch (Exception ex)
{
_logger.Warning($"[密炼计划] 列表拉取失败,使用本地缓存:{ex.Message}");
}
}
List<MesXslMixingProductionPlan> source; List<MesXslMixingProductionPlan> source;
lock (_cacheLock) lock (_cacheLock)
source = _localCache.Select(Clone).ToList(); source = _localCache.Select(Clone).ToList();
@@ -95,25 +80,14 @@ public class MixingProductionPlanService : IMixingProductionPlanService, ISingle
.Skip(Math.Max(0, (pageNo - 1) * pageSize)) .Skip(Math.Max(0, (pageNo - 1) * pageSize))
.Take(pageSize) .Take(pageSize)
.ToList(); .ToList();
return new MixingProductionPlanPageResult(records, total, pageNo, pageSize); return Task.FromResult(new MixingProductionPlanPageResult(records, total, pageNo, pageSize));
} }
public async Task<List<MesXslMixingProductionPlan>> GetAllCachedAsync(CancellationToken ct = default) public Task<List<MesXslMixingProductionPlan>> GetAllCachedAsync(CancellationToken ct = default)
{ {
if (_networkMonitor.IsOnline) ct.ThrowIfCancellationRequested();
{
try
{
await SyncFromRemoteAsync(ct).ConfigureAwait(false);
}
catch (Exception ex)
{
_logger.Warning($"[密炼计划] 全量拉取失败,使用本地缓存:{ex.Message}");
}
}
lock (_cacheLock) lock (_cacheLock)
return _localCache.Select(Clone).ToList(); return Task.FromResult(_localCache.Select(Clone).ToList());
} }
public async Task<bool> SyncFromRemoteAsync(CancellationToken ct = default) public async Task<bool> SyncFromRemoteAsync(CancellationToken ct = default)
@@ -190,25 +164,6 @@ public class MixingProductionPlanService : IMixingProductionPlanService, ISingle
} }
} }
private void OnNetworkStatusChanged(bool isOnline)
{
if (!isOnline) return;
_ = Task.Run(async () =>
{
try
{
if (!await SyncFromRemoteAsync(CancellationToken.None).ConfigureAwait(false))
return;
_eventAggregator.GetEvent<MixingProductionPlanChangedEvent>()
.Publish(new MixingProductionPlanChangedPayload { Action = "reconnect" });
}
catch (Exception ex)
{
_logger.Warning($"[密炼计划] 重连同步失败:{ex.Message}");
}
});
}
private static bool IsPlanContentEqual(MesXslMixingProductionPlan a, MesXslMixingProductionPlan b) => private static bool IsPlanContentEqual(MesXslMixingProductionPlan a, MesXslMixingProductionPlan b) =>
string.Equals(GetPlanFingerprint(a), GetPlanFingerprint(b), StringComparison.Ordinal); string.Equals(GetPlanFingerprint(a), GetPlanFingerprint(b), StringComparison.Ordinal);

View File

@@ -3,33 +3,58 @@ using System.Text.Json;
using YY.Admin.Core; using YY.Admin.Core;
using YY.Admin.Core.Events; using YY.Admin.Core.Events;
using YY.Admin.Core.Services; using YY.Admin.Core.Services;
using YY.Admin.Services.Service;
namespace YY.Admin.Services.Service.MixingProductionPlan; namespace YY.Admin.Services.Service.MixingProductionPlan;
public class MixingProductionPlanSyncCoordinator : ISingletonDependency public class MixingProductionPlanSyncCoordinator : ISingletonDependency
{ {
private readonly IEventAggregator _eventAggregator; private readonly IEventAggregator _eventAggregator;
private readonly IMixingProductionPlanService _planService;
private readonly ILoggerService _logger; private readonly ILoggerService _logger;
public MixingProductionPlanSyncCoordinator( public MixingProductionPlanSyncCoordinator(
IEventAggregator eventAggregator, IEventAggregator eventAggregator,
SyncPollManager pollManager, IMixingProductionPlanService planService,
MesQuickTestDataPollManager pollManager,
ILoggerService logger) ILoggerService logger)
{ {
_eventAggregator = eventAggregator; _eventAggregator = eventAggregator;
_planService = planService;
_logger = logger; _logger = logger;
_eventAggregator.GetEvent<RemoteCommandReceivedEvent>() _eventAggregator.GetEvent<RemoteCommandReceivedEvent>()
.Subscribe(OnRemoteCommand, ThreadOption.BackgroundThread); .Subscribe(OnRemoteCommand, ThreadOption.BackgroundThread);
pollManager.Register("密炼计划", () => _eventAggregator.GetEvent<NetworkStatusChangedEvent>()
{ .Subscribe(OnNetworkStatusChanged, ThreadOption.BackgroundThread);
_eventAggregator.GetEvent<MixingProductionPlanChangedEvent>()
.Publish(new MixingProductionPlanChangedPayload { Action = "poll" }); pollManager.Register("密炼计划", () => PollSyncAsync("poll"));
return Task.CompletedTask;
});
_logger.Information("[密炼计划] MixingProductionPlanSyncCoordinator 已启动"); _logger.Information("[密炼计划] MixingProductionPlanSyncCoordinator 已启动");
_ = PollSyncAsync("startup");
}
private async Task PollSyncAsync(string action)
{
try
{
if (!await _planService.SyncFromRemoteAsync(CancellationToken.None).ConfigureAwait(false))
return;
_eventAggregator.GetEvent<MixingProductionPlanChangedEvent>()
.Publish(new MixingProductionPlanChangedPayload { Action = action });
}
catch (Exception ex)
{
_logger.Warning($"[密炼计划] 轮询同步失败:{ex.Message}");
}
}
private void OnNetworkStatusChanged(NetworkStatusChangedPayload payload)
{
if (!payload.IsOnline) return;
_ = Task.Run(() => PollSyncAsync("reconnect"));
} }
private void OnRemoteCommand(RemoteCommandPayload payload) private void OnRemoteCommand(RemoteCommandPayload payload)
@@ -53,7 +78,18 @@ public class MixingProductionPlanSyncCoordinator : ISingletonDependency
MixingProductionPlanId = idEl.ValueKind == JsonValueKind.String ? idEl.GetString() : null MixingProductionPlanId = idEl.ValueKind == JsonValueKind.String ? idEl.GetString() : null
}; };
_logger.Information($"[密炼计划] STOMP action={changed.Action}, id={changed.MixingProductionPlanId}"); _logger.Information($"[密炼计划] STOMP action={changed.Action}, id={changed.MixingProductionPlanId}");
_eventAggregator.GetEvent<MixingProductionPlanChangedEvent>().Publish(changed); _ = Task.Run(async () =>
{
try
{
if (await _planService.SyncFromRemoteAsync(CancellationToken.None).ConfigureAwait(false))
_eventAggregator.GetEvent<MixingProductionPlanChangedEvent>().Publish(changed);
}
catch (Exception ex)
{
_logger.Warning($"[密炼计划] STOMP 同步失败:{ex.Message}");
}
});
} }
catch (Exception ex) catch (Exception ex)
{ {

View File

@@ -0,0 +1,597 @@
using Microsoft.Extensions.Configuration;
using Prism.Events;
using System.IO;
using System.Net.Http;
using System.Text;
using System.Text.Json;
using System.Text.Json.Serialization;
using System.Web;
using YY.Admin.Core;
using YY.Admin.Core.Entity;
using YY.Admin.Core.Events;
using YY.Admin.Core.Services;
namespace YY.Admin.Services.Service.RubberQuickTest;
public class RubberQuickTestRecordService : IRubberQuickTestRecordService, ISingletonDependency
{
private readonly IHttpClientFactory _httpClientFactory;
private readonly IConfiguration _configuration;
private readonly INetworkMonitor _networkMonitor;
private readonly IEventAggregator _eventAggregator;
private readonly ILoggerService _logger;
private readonly SemaphoreSlim _syncLock = new(1, 1);
private readonly object _cacheLock = new();
private readonly string _cacheFilePath;
private List<RubberQuickTestRecordLocalItem> _localItems = new();
private static readonly JsonSerializerOptions _jsonOpts = new()
{
PropertyNameCaseInsensitive = true,
PropertyNamingPolicy = JsonNamingPolicy.CamelCase,
DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull,
Converters = { new NullableDateTimeJsonConverter() }
};
public RubberQuickTestRecordService(
IHttpClientFactory httpClientFactory,
IConfiguration configuration,
INetworkMonitor networkMonitor,
IEventAggregator eventAggregator,
ILoggerService logger)
{
_httpClientFactory = httpClientFactory;
_configuration = configuration;
_networkMonitor = networkMonitor;
_eventAggregator = eventAggregator;
_logger = logger;
var appDataDir = Path.Combine(
Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData),
"YY.Admin", "sync-cache");
Directory.CreateDirectory(appDataDir);
_cacheFilePath = Path.Combine(appDataDir, "rubber-quick-test-record-items.json");
LoadCacheFromDisk();
_logger.Information($"[快检记录同步] 初始化完成,本地记录={_localItems.Count}");
}
private string BaseUrl => (_configuration.GetValue<string>("JeecgIntegration:BaseUrl") ?? "http://localhost:8080/jeecg-boot").TrimEnd('/');
private int DefaultTenantId => (int?)_configuration.GetValue<long?>("JeecgIntegration:DefaultTenantId") ?? 1002;
private HttpClient CreateClient() => _httpClientFactory.CreateClient("JeecgApi");
public async Task<RubberQuickTestRecordPageResult> PageAsync(
int pageNo, int pageSize,
string? filterRecordNo = null,
string? filterRubberMaterialName = null,
string? filterPlanNo = null,
CancellationToken ct = default)
{
var rows = await BuildAllRowsAsync(ct).ConfigureAwait(false);
var filtered = ApplyFilters(rows, filterRecordNo, filterRubberMaterialName, filterPlanNo);
var total = filtered.Count;
var records = filtered
.OrderByDescending(r => r.InspectDate ?? DateTime.MinValue)
.Skip(Math.Max(0, (pageNo - 1) * pageSize))
.Take(pageSize)
.ToList();
return new RubberQuickTestRecordPageResult(records, total, pageNo, pageSize);
}
public async Task<MesXslRubberQuickTestRecord?> GetByIdAsync(string id, CancellationToken ct = default)
{
RubberQuickTestRecordLocalItem? local = null;
lock (_cacheLock)
{
local = _localItems.FirstOrDefault(x =>
string.Equals(x.LocalId, id, StringComparison.OrdinalIgnoreCase)
|| string.Equals(x.MesId, id, StringComparison.OrdinalIgnoreCase)
|| string.Equals(x.Record.RecordNo, id, StringComparison.OrdinalIgnoreCase));
}
if (local != null)
return CloneRecord(local.Record);
if (_networkMonitor.IsOnline)
{
try
{
var url = $"{BaseUrl}/xslmes/mesXslRubberQuickTestRecord/anon/queryById?id={Uri.EscapeDataString(id)}&tenantId={DefaultTenantId}";
using var client = CreateClient();
var resp = await client.GetAsync(url, ct).ConfigureAwait(false);
if (resp.IsSuccessStatusCode)
{
var json = await resp.Content.ReadAsStringAsync(ct).ConfigureAwait(false);
using var doc = JsonDocument.Parse(json);
if (doc.RootElement.TryGetProperty("result", out var resultEl))
return resultEl.Deserialize<MesXslRubberQuickTestRecord>(_jsonOpts);
}
}
catch (Exception ex)
{
_logger.Warning($"[快检记录详情] 远端查询异常 id={id}: {ex.Message}");
}
}
return null;
}
public RubberQuickTestRecordLocalItem? GetByLocalId(string localId)
{
lock (_cacheLock)
{
var item = _localItems.FirstOrDefault(x => string.Equals(x.LocalId, localId, StringComparison.OrdinalIgnoreCase));
return item == null ? null : CloneLocalItem(item);
}
}
public async Task<RubberQuickTestRecordSaveResult> SaveAsync(MesXslRubberQuickTestRecord entity, CancellationToken ct = default)
{
var record = CloneRecord(entity);
record.CreateTime ??= DateTime.Now;
record.InspectTime ??= record.CreateTime;
var item = new RubberQuickTestRecordLocalItem
{
LocalId = Guid.NewGuid().ToString("N"),
LocalCreateTime = DateTime.Now,
SyncStatus = "Pending",
Record = record
};
if (_networkMonitor.IsOnline)
{
try
{
var recordNo = await RemoteAddAsync(record, ct).ConfigureAwait(false);
if (!string.IsNullOrWhiteSpace(recordNo))
{
item.Record.RecordNo = recordNo;
item.SyncStatus = "Synced";
}
}
catch (Exception ex)
{
item.SyncStatus = "Failed";
item.SyncError = ex.Message;
_logger.Warning($"[快检记录新增] 远端失败,保留本地:{ex.Message}");
}
}
lock (_cacheLock)
{
_localItems.Add(CloneLocalItem(item));
SaveCacheToDiskUnsafe();
}
_eventAggregator.GetEvent<RubberQuickTestRecordChangedEvent>()
.Publish(new RubberQuickTestRecordChangedPayload { Action = "add", RecordId = item.LocalId });
return new RubberQuickTestRecordSaveResult
{
LocalId = item.LocalId,
SyncStatus = item.SyncStatus,
Record = CloneRecord(item.Record)
};
}
public async Task<RubberQuickTestRecordSaveResult> UpdateLocalAsync(string localId, MesXslRubberQuickTestRecord entity, CancellationToken ct = default)
{
if (string.IsNullOrWhiteSpace(localId))
throw new ArgumentException("无效的记录标识", nameof(localId));
var record = CloneRecord(entity);
RubberQuickTestRecordLocalItem? existing;
lock (_cacheLock)
{
existing = _localItems.FirstOrDefault(x =>
string.Equals(x.LocalId, localId.Trim(), StringComparison.OrdinalIgnoreCase));
}
if (existing == null)
throw new InvalidOperationException("未找到本地快检记录");
if (string.Equals(existing.SyncStatus, "Synced", StringComparison.OrdinalIgnoreCase))
throw new InvalidOperationException("已同步记录不可编辑");
record.CreateTime = existing.Record.CreateTime ?? record.CreateTime ?? DateTime.Now;
record.InspectTime = record.InspectTime ?? record.CreateTime;
if (string.IsNullOrWhiteSpace(record.RecordNo))
record.RecordNo = existing.Record.RecordNo;
var item = CloneLocalItem(existing);
item.Record = record;
item.SyncStatus = "Pending";
item.SyncError = null;
if (_networkMonitor.IsOnline && HasCompletePlanInfo(record))
{
try
{
var recordNo = await RemoteAddAsync(record, ct).ConfigureAwait(false);
if (!string.IsNullOrWhiteSpace(recordNo))
{
item.Record.RecordNo = recordNo;
item.SyncStatus = "Synced";
}
}
catch (Exception ex)
{
item.SyncStatus = "Failed";
item.SyncError = ex.Message;
_logger.Warning($"[快检记录修改] 远端失败,保留本地:{ex.Message}");
}
}
lock (_cacheLock)
{
var target = _localItems.FirstOrDefault(x =>
string.Equals(x.LocalId, localId.Trim(), StringComparison.OrdinalIgnoreCase));
if (target == null)
throw new InvalidOperationException("未找到本地快检记录");
target.Record = CloneRecord(item.Record);
target.SyncStatus = item.SyncStatus;
target.SyncError = item.SyncError;
SaveCacheToDiskUnsafe();
}
_eventAggregator.GetEvent<RubberQuickTestRecordChangedEvent>()
.Publish(new RubberQuickTestRecordChangedPayload { Action = "update", RecordId = localId });
return new RubberQuickTestRecordSaveResult
{
LocalId = localId,
SyncStatus = item.SyncStatus,
Record = CloneRecord(item.Record)
};
}
public bool DeleteUnsyncedLocal(string localId)
{
if (string.IsNullOrWhiteSpace(localId)) return false;
bool removed;
lock (_cacheLock)
{
var item = _localItems.FirstOrDefault(x =>
string.Equals(x.LocalId, localId.Trim(), StringComparison.OrdinalIgnoreCase));
if (item == null || string.Equals(item.SyncStatus, "Synced", StringComparison.OrdinalIgnoreCase))
return false;
removed = _localItems.Remove(item);
if (removed)
SaveCacheToDiskUnsafe();
}
if (!removed) return false;
_eventAggregator.GetEvent<RubberQuickTestRecordChangedEvent>()
.Publish(new RubberQuickTestRecordChangedPayload { Action = "delete", RecordId = localId });
_logger.Information($"[快检记录删除] 已删除本地未同步记录 localId={localId}");
return true;
}
public async Task<RubberQuickTestRecordSyncResult> SyncLocalAsync(string localId, CancellationToken ct = default)
{
if (string.IsNullOrWhiteSpace(localId))
return new RubberQuickTestRecordSyncResult { Success = false, Message = "无效的记录标识" };
if (!_networkMonitor.IsOnline)
return new RubberQuickTestRecordSyncResult { Success = false, Message = "当前离线,无法同步" };
RubberQuickTestRecordLocalItem? item;
lock (_cacheLock)
{
item = _localItems.FirstOrDefault(x =>
string.Equals(x.LocalId, localId.Trim(), StringComparison.OrdinalIgnoreCase));
}
if (item == null)
return new RubberQuickTestRecordSyncResult { Success = false, Message = "未找到本地快检记录" };
if (string.Equals(item.SyncStatus, "Synced", StringComparison.OrdinalIgnoreCase))
return new RubberQuickTestRecordSyncResult { Success = true, Message = "记录已同步", RecordNo = item.Record.RecordNo };
if (!HasCompletePlanInfo(item.Record))
return new RubberQuickTestRecordSyncResult { Success = false, Message = PlanInfoValidationMessage };
if (!await _syncLock.WaitAsync(0, ct).ConfigureAwait(false))
return new RubberQuickTestRecordSyncResult { Success = false, Message = "同步正在进行中,请稍后再试" };
try
{
var recordNo = await RemoteAddAsync(item.Record, ct).ConfigureAwait(false);
lock (_cacheLock)
{
var target = _localItems.FirstOrDefault(x => x.LocalId == item.LocalId);
if (target == null)
return new RubberQuickTestRecordSyncResult { Success = false, Message = "记录已被删除" };
if (!string.IsNullOrWhiteSpace(recordNo))
target.Record.RecordNo = recordNo;
target.SyncStatus = "Synced";
target.SyncError = null;
SaveCacheToDiskUnsafe();
}
_eventAggregator.GetEvent<RubberQuickTestRecordChangedEvent>()
.Publish(new RubberQuickTestRecordChangedPayload { Action = "sync", RecordId = item.LocalId });
return new RubberQuickTestRecordSyncResult
{
Success = true,
Message = "同步成功",
RecordNo = recordNo ?? item.Record.RecordNo
};
}
catch (Exception ex)
{
lock (_cacheLock)
{
var target = _localItems.FirstOrDefault(x => x.LocalId == item.LocalId);
if (target != null)
{
target.SyncStatus = "Failed";
target.SyncError = ex.Message;
SaveCacheToDiskUnsafe();
}
}
_logger.Warning($"[快检记录同步] 手动同步失败 localId={localId}: {ex.Message}");
return new RubberQuickTestRecordSyncResult { Success = false, Message = ex.Message };
}
finally
{
_syncLock.Release();
}
}
private const string PlanInfoValidationMessage = "请维护密炼计划信息(密炼计划、密炼日期、密炼机台)";
private static bool HasCompletePlanInfo(MesXslRubberQuickTestRecord record) =>
!string.IsNullOrWhiteSpace(record.ProductionPlanNo)
&& record.ProductionDate != null
&& !string.IsNullOrWhiteSpace(record.ProdEquipmentName);
public string GenerateRecordNo(string rubberMaterialName)
{
var dateStr = DateTime.Now.ToString("yyyyMMdd");
var material = rubberMaterialName.Trim();
int maxSeq = 0;
lock (_cacheLock)
{
foreach (var item in _localItems)
{
var no = item.Record.RecordNo;
if (string.IsNullOrWhiteSpace(no) || !no.StartsWith(dateStr, StringComparison.Ordinal) || !no.EndsWith(material, StringComparison.Ordinal))
continue;
var seqPart = no.Substring(dateStr.Length, Math.Min(4, no.Length - dateStr.Length - material.Length));
if (int.TryParse(seqPart, out var seq))
maxSeq = Math.Max(maxSeq, seq);
}
}
return dateStr + (maxSeq + 1).ToString("D4") + material;
}
private async Task<List<RubberQuickTestRecordListRow>> BuildAllRowsAsync(CancellationToken ct)
{
var rows = new List<RubberQuickTestRecordListRow>();
var seenRecordNos = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
lock (_cacheLock)
{
foreach (var item in _localItems)
{
rows.Add(ToListRow(item));
if (!string.IsNullOrWhiteSpace(item.Record.RecordNo))
seenRecordNos.Add(item.Record.RecordNo);
}
}
if (_networkMonitor.IsOnline)
{
try
{
foreach (var remote in await FetchRemoteListAsync(ct).ConfigureAwait(false))
{
if (!string.IsNullOrWhiteSpace(remote.RecordNo) && seenRecordNos.Contains(remote.RecordNo))
continue;
rows.Add(ToListRow(remote, "Synced", remote.Id));
}
}
catch (Exception ex)
{
_logger.Warning($"[快检记录列表] 远端拉取失败:{ex.Message}");
}
}
return rows;
}
private static RubberQuickTestRecordListRow ToListRow(RubberQuickTestRecordLocalItem item) =>
ToListRow(item.Record, item.SyncStatus, item.MesId, item.LocalId);
private static RubberQuickTestRecordListRow ToListRow(
MesXslRubberQuickTestRecord r,
string syncStatus,
string? mesId = null,
string? localId = null) => new()
{
LocalId = localId,
MesId = mesId ?? r.Id,
RecordNo = r.RecordNo,
ProductionDate = r.ProductionDate,
ProdEquipmentName = r.ProdEquipmentName,
WorkShiftDisplay = r.WorkShiftText ?? r.WorkShift,
ProductionPlanNo = r.ProductionPlanNo,
RubberMaterialName = r.RubberMaterialName,
StdName = r.StdName,
TestMethodName = r.TestMethodName,
QuickTestTypeName = r.QuickTestTypeName,
TrainNo = r.TrainNo,
InspectTimes = r.InspectTimes,
InspectorRealname = r.InspectorRealname,
InspectDate = r.CreateTime ?? r.InspectTime,
InspectResultDisplay = r.InspectResultText ?? (r.InspectResult == "1" ? "合格" : r.InspectResult == "0" ? "不合格" : ""),
SyncStatus = syncStatus
};
private async Task<string?> RemoteAddAsync(MesXslRubberQuickTestRecord entity, CancellationToken ct)
{
var url = $"{BaseUrl}/xslmes/mesXslRubberQuickTestRecord/anon/add?tenantId={DefaultTenantId}";
var payload = CloneRecord(entity);
payload.Id = null;
using var client = CreateClient();
var body = JsonSerializer.Serialize(payload, _jsonOpts);
using var content = new StringContent(body, Encoding.UTF8, "application/json");
var resp = await client.PostAsync(url, content, ct).ConfigureAwait(false);
var json = await resp.Content.ReadAsStringAsync(ct).ConfigureAwait(false);
using var doc = JsonDocument.Parse(json);
if (!doc.RootElement.TryGetProperty("success", out var successEl) || !successEl.GetBoolean())
{
var msg = doc.RootElement.TryGetProperty("message", out var msgEl) ? msgEl.GetString() : "保存失败";
throw new InvalidOperationException(msg ?? "保存失败");
}
if (doc.RootElement.TryGetProperty("result", out var resultEl) && resultEl.ValueKind == JsonValueKind.String)
return resultEl.GetString();
return entity.RecordNo;
}
private async Task<List<MesXslRubberQuickTestRecord>> FetchRemoteListAsync(CancellationToken ct)
{
var query = HttpUtility.ParseQueryString(string.Empty);
query["pageNo"] = "1";
query["pageSize"] = "10000";
query["tenantId"] = DefaultTenantId.ToString();
var url = $"{BaseUrl}/xslmes/mesXslRubberQuickTestRecord/anon/list?{query}";
using var client = CreateClient();
var resp = await client.GetAsync(url, ct).ConfigureAwait(false);
resp.EnsureSuccessStatusCode();
var json = await resp.Content.ReadAsStringAsync(ct).ConfigureAwait(false);
using var doc = JsonDocument.Parse(json);
var result = doc.RootElement.GetProperty("result");
return result.GetProperty("records").Deserialize<List<MesXslRubberQuickTestRecord>>(_jsonOpts) ?? new();
}
private static List<RubberQuickTestRecordListRow> ApplyFilters(
List<RubberQuickTestRecordListRow> source,
string? filterRecordNo,
string? filterRubberMaterialName,
string? filterPlanNo)
{
IEnumerable<RubberQuickTestRecordListRow> q = source;
if (!string.IsNullOrWhiteSpace(filterRecordNo))
q = q.Where(r => (r.RecordNo ?? "").Contains(filterRecordNo.Trim(), StringComparison.OrdinalIgnoreCase));
if (!string.IsNullOrWhiteSpace(filterRubberMaterialName))
q = q.Where(r => (r.RubberMaterialName ?? "").Contains(filterRubberMaterialName.Trim(), StringComparison.OrdinalIgnoreCase));
if (!string.IsNullOrWhiteSpace(filterPlanNo))
q = q.Where(r => (r.ProductionPlanNo ?? "").Contains(filterPlanNo.Trim(), StringComparison.OrdinalIgnoreCase));
return q.ToList();
}
private void LoadCacheFromDisk()
{
try
{
if (!File.Exists(_cacheFilePath)) return;
var data = JsonSerializer.Deserialize<List<RubberQuickTestRecordLocalItem>>(File.ReadAllText(_cacheFilePath), _jsonOpts);
_localItems = data ?? new();
}
catch { _localItems = new(); }
}
private void SaveCacheToDiskUnsafe() =>
File.WriteAllText(_cacheFilePath, JsonSerializer.Serialize(_localItems, _jsonOpts));
private static RubberQuickTestRecordLocalItem CloneLocalItem(RubberQuickTestRecordLocalItem src) => new()
{
LocalId = src.LocalId,
MesId = src.MesId,
SyncStatus = src.SyncStatus,
SyncError = src.SyncError,
LocalCreateTime = src.LocalCreateTime,
Record = CloneRecord(src.Record)
};
private static MesXslRubberQuickTestRecord CloneRecord(MesXslRubberQuickTestRecord src) => new()
{
Id = src.Id,
RecordNo = src.RecordNo,
RubberMaterialId = src.RubberMaterialId,
RubberMaterialName = src.RubberMaterialName,
StdId = src.StdId,
StdName = src.StdName,
TestMethodId = src.TestMethodId,
TestMethodName = src.TestMethodName,
ProdEquipmentLedgerId = src.ProdEquipmentLedgerId,
ProdEquipmentName = src.ProdEquipmentName,
ProductionDate = src.ProductionDate,
TrainNo = src.TrainNo,
WorkShift = src.WorkShift,
InspectTimes = src.InspectTimes,
InspectTime = src.InspectTime,
InspectorUserId = src.InspectorUserId,
InspectorUsername = src.InspectorUsername,
InspectorRealname = src.InspectorRealname,
QuickTestTypeId = src.QuickTestTypeId,
QuickTestTypeName = src.QuickTestTypeName,
InspectResult = src.InspectResult,
ProductionPlanNo = src.ProductionPlanNo,
CreateTime = src.CreateTime,
StdLineList = src.StdLineList?.Select(l => new MesXslRubberQuickTestRecordStdLine
{
Id = l.Id, RecordId = l.RecordId, DataPointId = l.DataPointId, PointName = l.PointName,
LowerLimit = l.LowerLimit, UpperLimit = l.UpperLimit, LowerWarn = l.LowerWarn,
UpperWarn = l.UpperWarn, TargetValue = l.TargetValue, SortNo = l.SortNo
}).ToList(),
RawLineList = src.RawLineList?.Select(l => new MesXslRubberQuickTestRecordRawLine
{
Id = l.Id, RecordId = l.RecordId, RowNo = l.RowNo, DataPointId = l.DataPointId,
InspectItem = l.InspectItem, LowerLimit = l.LowerLimit, UpperLimit = l.UpperLimit,
InspectValue = l.InspectValue, RowInspectResult = l.RowInspectResult, SortNo = l.SortNo
}).ToList(),
ChartPointList = src.ChartPointList?.Select(p => new MesXslRubberQuickTestRecordChartPoint
{
Id = p.Id, RecordId = p.RecordId, TimeMin = p.TimeMin, UpperTemp = p.UpperTemp,
LowerTemp = p.LowerTemp, TorqueS = p.TorqueS, SortNo = p.SortNo
}).ToList(),
WorkShiftText = src.WorkShiftText,
InspectResultText = src.InspectResultText
};
private sealed class NullableDateTimeJsonConverter : JsonConverter<DateTime?>
{
private static readonly string[] SupportedFormats =
[
"yyyy-MM-dd HH:mm:ss", "yyyy-MM-dd HH:mm:ss.fff",
"yyyy-MM-ddTHH:mm:ss", "yyyy-MM-ddTHH:mm:ss.fff",
"yyyy-MM-ddTHH:mm:ssZ", "yyyy-MM-ddTHH:mm:ss.fffZ",
"yyyy-MM-dd"
];
public override DateTime? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
{
if (reader.TokenType == JsonTokenType.Null) return null;
if (reader.TokenType == JsonTokenType.String)
{
var raw = reader.GetString();
if (string.IsNullOrWhiteSpace(raw)) return null;
if (DateTime.TryParseExact(raw, SupportedFormats,
System.Globalization.CultureInfo.InvariantCulture,
System.Globalization.DateTimeStyles.AssumeLocal, out var exact)) return exact;
if (DateTime.TryParse(raw, out var fallback)) return fallback;
}
throw new JsonException($"无法转换为 DateTime?token={reader.TokenType}");
}
public override void Write(Utf8JsonWriter writer, DateTime? value, JsonSerializerOptions options)
{
if (value == null) writer.WriteNullValue();
else writer.WriteStringValue(value.Value.ToString("yyyy-MM-dd HH:mm:ss"));
}
}
}

View File

@@ -54,35 +54,20 @@ public class RubberQuickTestStdService : IRubberQuickTestStdService, ISingletonD
LoadCacheFromDisk(); LoadCacheFromDisk();
_logger.Information($"[快检实验标准] 服务初始化,缓存={_localCache.Count},在线={_networkMonitor.IsOnline}"); _logger.Information($"[快检实验标准] 服务初始化,缓存={_localCache.Count},在线={_networkMonitor.IsOnline}");
_networkMonitor.StatusChanged += OnNetworkStatusChanged;
if (_networkMonitor.IsOnline)
_ = Task.Run(() => SyncFromRemoteAsync(CancellationToken.None));
} }
private string BaseUrl => (_configuration.GetValue<string>("JeecgIntegration:BaseUrl") ?? "http://localhost:8080/jeecg-boot").TrimEnd('/'); private string BaseUrl => (_configuration.GetValue<string>("JeecgIntegration:BaseUrl") ?? "http://localhost:8080/jeecg-boot").TrimEnd('/');
private int DefaultTenantId => (int?)_configuration.GetValue<long?>("JeecgIntegration:DefaultTenantId") ?? 1002; private int DefaultTenantId => (int?)_configuration.GetValue<long?>("JeecgIntegration:DefaultTenantId") ?? 1002;
private HttpClient CreateClient() => _httpClientFactory.CreateClient("JeecgApi"); private HttpClient CreateClient() => _httpClientFactory.CreateClient("JeecgApi");
public async Task<RubberQuickTestStdPageResult> PageAsync( public Task<RubberQuickTestStdPageResult> PageAsync(
int pageNo, int pageSize, int pageNo, int pageSize,
string? stdName = null, string? stdName = null,
string? rubberMaterialName = null, string? rubberMaterialName = null,
string? enableStatus = null, string? enableStatus = null,
CancellationToken ct = default) CancellationToken ct = default)
{ {
if (_networkMonitor.IsOnline) ct.ThrowIfCancellationRequested();
{
try
{
await SyncFromRemoteAsync(ct).ConfigureAwait(false);
}
catch (Exception ex)
{
_logger.Warning($"[快检实验标准] 列表拉取失败,使用本地缓存:{ex.Message}");
}
}
List<MesXslRubberQuickTestStd> source; List<MesXslRubberQuickTestStd> source;
lock (_cacheLock) lock (_cacheLock)
source = _localCache.Select(CloneMain).ToList(); source = _localCache.Select(CloneMain).ToList();
@@ -93,7 +78,7 @@ public class RubberQuickTestStdService : IRubberQuickTestStdService, ISingletonD
.Skip(Math.Max(0, (pageNo - 1) * pageSize)) .Skip(Math.Max(0, (pageNo - 1) * pageSize))
.Take(pageSize) .Take(pageSize)
.ToList(); .ToList();
return new RubberQuickTestStdPageResult(records, total, pageNo, pageSize); return Task.FromResult(new RubberQuickTestStdPageResult(records, total, pageNo, pageSize));
} }
public async Task<MesXslRubberQuickTestStd?> GetByIdAsync(string id, CancellationToken ct = default) public async Task<MesXslRubberQuickTestStd?> GetByIdAsync(string id, CancellationToken ct = default)
@@ -199,6 +184,8 @@ public class RubberQuickTestStdService : IRubberQuickTestStdService, ISingletonD
lock (_cacheLock) lock (_cacheLock)
localSnapshot = _localCache.Select(CloneMain).ToList(); localSnapshot = _localCache.Select(CloneMain).ToList();
await HydrateStdLinesAsync(records, localSnapshot, ct).ConfigureAwait(false);
var (merged, stats) = MesReadOnlyCacheMergeHelper.Merge( var (merged, stats) = MesReadOnlyCacheMergeHelper.Merge(
localSnapshot, localSnapshot,
records, records,
@@ -233,18 +220,76 @@ public class RubberQuickTestStdService : IRubberQuickTestStdService, ISingletonD
} }
} }
private void OnNetworkStatusChanged(bool isOnline) private async Task HydrateStdLinesAsync(
List<MesXslRubberQuickTestStd> remoteRecords,
List<MesXslRubberQuickTestStd> localSnapshot,
CancellationToken ct)
{ {
if (!isOnline) return; var localById = localSnapshot
_ = Task.Run(async () => .Where(x => !string.IsNullOrWhiteSpace(x.Id))
{ .ToDictionary(x => x.Id!, StringComparer.OrdinalIgnoreCase);
if (!await SyncFromRemoteAsync(CancellationToken.None).ConfigureAwait(false))
return; using var gate = new SemaphoreSlim(8);
_eventAggregator.GetEvent<RubberQuickTestStdChangedEvent>() var tasks = remoteRecords
.Publish(new RubberQuickTestStdChangedPayload { Action = "reconnect" }); .Where(r => !string.IsNullOrWhiteSpace(r.Id))
}); .Select(async record =>
{
await gate.WaitAsync(ct).ConfigureAwait(false);
try
{
localById.TryGetValue(record.Id!, out var local);
if (local != null
&& IsStdListContentEqual(record, local)
&& local.LineList is { Count: > 0 })
{
record.LineList = local.LineList.Select(CloneStdLine).ToList();
return;
}
var lines = await FetchLinesByStdIdAsync(record.Id!, ct).ConfigureAwait(false);
record.LineList = lines.Count > 0
? lines
: local?.LineList?.Select(CloneStdLine).ToList();
}
finally
{
gate.Release();
}
});
await Task.WhenAll(tasks).ConfigureAwait(false);
} }
private async Task<List<MesXslRubberQuickTestStdLine>> FetchLinesByStdIdAsync(string stdId, CancellationToken ct)
{
var url = $"{BaseUrl}/xslmes/mesXslRubberQuickTestStd/anon/queryLineListByStdId?id={Uri.EscapeDataString(stdId)}";
using var client = CreateClient();
using var resp = await client.GetAsync(url, ct).ConfigureAwait(false);
if (!resp.IsSuccessStatusCode)
return [];
var json = await resp.Content.ReadAsStringAsync(ct).ConfigureAwait(false);
using var doc = JsonDocument.Parse(json);
if (!doc.RootElement.TryGetProperty("result", out var resultEl))
return [];
return resultEl.Deserialize<List<MesXslRubberQuickTestStdLine>>(_jsonOpts) ?? [];
}
private static MesXslRubberQuickTestStdLine CloneStdLine(MesXslRubberQuickTestStdLine l) => new()
{
Id = l.Id,
StdId = l.StdId,
DataPointId = l.DataPointId,
PointName = l.PointName,
LowerLimit = l.LowerLimit,
UpperLimit = l.UpperLimit,
LowerWarn = l.LowerWarn,
UpperWarn = l.UpperWarn,
TargetValue = l.TargetValue,
SortNo = l.SortNo
};
private static List<MesXslRubberQuickTestStd> ApplyFilters( private static List<MesXslRubberQuickTestStd> ApplyFilters(
List<MesXslRubberQuickTestStd> source, List<MesXslRubberQuickTestStd> source,
string? stdName, string? stdName,

View File

@@ -2,33 +2,52 @@ using Prism.Events;
using System.Text.Json; using System.Text.Json;
using YY.Admin.Core; using YY.Admin.Core;
using YY.Admin.Core.Events; using YY.Admin.Core.Events;
using YY.Admin.Core.Services;
using YY.Admin.Services.Service;
namespace YY.Admin.Services.Service.RubberQuickTestStd; namespace YY.Admin.Services.Service.RubberQuickTestStd;
public class RubberQuickTestStdSyncCoordinator : ISingletonDependency public class RubberQuickTestStdSyncCoordinator : ISingletonDependency
{ {
private readonly IEventAggregator _eventAggregator; private readonly IEventAggregator _eventAggregator;
private readonly IRubberQuickTestStdService _stdService;
private readonly ILoggerService _logger; private readonly ILoggerService _logger;
public RubberQuickTestStdSyncCoordinator( public RubberQuickTestStdSyncCoordinator(
IEventAggregator eventAggregator, IEventAggregator eventAggregator,
SyncPollManager pollManager, IRubberQuickTestStdService stdService,
MesQuickTestDataPollManager pollManager,
ILoggerService logger) ILoggerService logger)
{ {
_eventAggregator = eventAggregator; _eventAggregator = eventAggregator;
_stdService = stdService;
_logger = logger; _logger = logger;
_eventAggregator.GetEvent<RemoteCommandReceivedEvent>() _eventAggregator.GetEvent<RemoteCommandReceivedEvent>()
.Subscribe(OnRemoteCommand, ThreadOption.BackgroundThread); .Subscribe(OnRemoteCommand, ThreadOption.BackgroundThread);
pollManager.Register("胶料快检实验标准", () => _eventAggregator.GetEvent<NetworkStatusChangedEvent>()
{ .Subscribe(OnNetworkStatusChanged, ThreadOption.BackgroundThread);
_eventAggregator.GetEvent<RubberQuickTestStdChangedEvent>()
.Publish(new RubberQuickTestStdChangedPayload { Action = "poll" }); pollManager.Register("胶料快检实验标准", () => PollSyncAsync("poll"));
return Task.CompletedTask;
});
_logger.Information("[快检实验标准] RubberQuickTestStdSyncCoordinator 已启动"); _logger.Information("[快检实验标准] RubberQuickTestStdSyncCoordinator 已启动");
_ = PollSyncAsync("startup");
}
private async Task PollSyncAsync(string action)
{
if (!await _stdService.SyncFromRemoteAsync(CancellationToken.None).ConfigureAwait(false))
return;
_eventAggregator.GetEvent<RubberQuickTestStdChangedEvent>()
.Publish(new RubberQuickTestStdChangedPayload { Action = action });
}
private void OnNetworkStatusChanged(NetworkStatusChangedPayload payload)
{
if (!payload.IsOnline) return;
_ = Task.Run(() => PollSyncAsync("reconnect"));
} }
private void OnRemoteCommand(RemoteCommandPayload payload) private void OnRemoteCommand(RemoteCommandPayload payload)
@@ -52,7 +71,11 @@ public class RubberQuickTestStdSyncCoordinator : ISingletonDependency
StdId = idEl.ValueKind == JsonValueKind.String ? idEl.GetString() : null StdId = idEl.ValueKind == JsonValueKind.String ? idEl.GetString() : null
}; };
_logger.Information($"[快检实验标准] STOMP action={changed.Action}, stdId={changed.StdId}"); _logger.Information($"[快检实验标准] STOMP action={changed.Action}, stdId={changed.StdId}");
_eventAggregator.GetEvent<RubberQuickTestStdChangedEvent>().Publish(changed); _ = Task.Run(async () =>
{
if (await _stdService.SyncFromRemoteAsync(CancellationToken.None).ConfigureAwait(false))
_eventAggregator.GetEvent<RubberQuickTestStdChangedEvent>().Publish(changed);
});
} }
catch (Exception ex) catch (Exception ex)
{ {

View File

@@ -0,0 +1,18 @@
namespace YY.Admin.Event
{
/// <summary>
/// 按 ViewName 关闭 Tab 并可激活指定列表页
/// </summary>
public class TabCloseRequestPayload
{
public string ViewNameToClose { get; set; } = string.Empty;
public string? ActivateViewName { get; set; }
public string? ActivateTabName { get; set; }
}
public class TabCloseRequestEvent : PubSubEvent<TabCloseRequestPayload>
{
}
}

View File

@@ -62,10 +62,10 @@ public class NetworkMonitor : INetworkMonitor
private async Task MonitorLoopAsync(CancellationToken cancellationToken) private async Task MonitorLoopAsync(CancellationToken cancellationToken)
{ {
// 启动后立即探活一次,避免首屏 10 秒内 IsOnline 恒为 false // 启动后立即探活一次,避免首屏秒内 IsOnline 恒为 false
await RunHttpProbeAndRecomputeAsync(cancellationToken).ConfigureAwait(false); await RunHttpProbeAndRecomputeAsync(cancellationToken).ConfigureAwait(false);
using var timer = new PeriodicTimer(TimeSpan.FromSeconds(10)); using var timer = new PeriodicTimer(TimeSpan.FromSeconds(5));
while (await timer.WaitForNextTickAsync(cancellationToken).ConfigureAwait(false)) while (await timer.WaitForNextTickAsync(cancellationToken).ConfigureAwait(false))
{ {
await RunHttpProbeAndRecomputeAsync(cancellationToken).ConfigureAwait(false); await RunHttpProbeAndRecomputeAsync(cancellationToken).ConfigureAwait(false);

View File

@@ -99,6 +99,7 @@ namespace YY.Admin
// 密炼物料皮重策略 // 密炼物料皮重策略
containerRegistry.RegisterForNavigation<MixerMaterialTareStrategyListView>(); containerRegistry.RegisterForNavigation<MixerMaterialTareStrategyListView>();
// 胶料快检记录操作台 // 胶料快检记录操作台
containerRegistry.RegisterForNavigation<RubberQuickTestRecordListView>();
containerRegistry.RegisterForNavigation<RubberQuickTestOperationView>(); containerRegistry.RegisterForNavigation<RubberQuickTestOperationView>();
// 胶料快检实验标准(只读) // 胶料快检实验标准(只读)
containerRegistry.RegisterForNavigation<RubberQuickTestStdListView>(); containerRegistry.RegisterForNavigation<RubberQuickTestStdListView>();

View File

@@ -89,9 +89,12 @@ public class SyncModule : IModule
containerRegistry.RegisterSingleton<PrintTemplateSyncCoordinator>(); containerRegistry.RegisterSingleton<PrintTemplateSyncCoordinator>();
containerRegistry.RegisterSingleton<PrintBizTemplateBindSyncCoordinator>(); containerRegistry.RegisterSingleton<PrintBizTemplateBindSyncCoordinator>();
// 胶料快检记录操作台 // 胶料快检记录:本地存储 + MES 同步
containerRegistry.RegisterSingleton<IRubberQuickTestRecordService, RubberQuickTestRecordService>();
containerRegistry.RegisterSingleton<IRubberQuickTestOperationService, RubberQuickTestOperationService>(); containerRegistry.RegisterSingleton<IRubberQuickTestOperationService, RubberQuickTestOperationService>();
// 快检相关 MES 只读数据 2 秒轮询(须在协调器之前注册)
containerRegistry.RegisterSingleton<MesQuickTestDataPollManager>();
// 胶料快检实验标准MES 只读同步) // 胶料快检实验标准MES 只读同步)
containerRegistry.RegisterSingleton<IRubberQuickTestStdService, RubberQuickTestStdService>(); containerRegistry.RegisterSingleton<IRubberQuickTestStdService, RubberQuickTestStdService>();
containerRegistry.RegisterSingleton<RubberQuickTestStdSyncCoordinator>(); containerRegistry.RegisterSingleton<RubberQuickTestStdSyncCoordinator>();
@@ -170,6 +173,8 @@ public class SyncModule : IModule
// 强制实例化打印模板同步协调器 // 强制实例化打印模板同步协调器
_ = containerProvider.Resolve<PrintTemplateSyncCoordinator>(); _ = containerProvider.Resolve<PrintTemplateSyncCoordinator>();
_ = containerProvider.Resolve<PrintBizTemplateBindSyncCoordinator>(); _ = containerProvider.Resolve<PrintBizTemplateBindSyncCoordinator>();
// 快检数据轮询须在协调器之前实例化,确保 Register 能正常接收
_ = containerProvider.Resolve<MesQuickTestDataPollManager>();
// 胶料快检实验标准只读同步协调器 // 胶料快检实验标准只读同步协调器
_ = containerProvider.Resolve<RubberQuickTestStdSyncCoordinator>(); _ = containerProvider.Resolve<RubberQuickTestStdSyncCoordinator>();
// 密炼计划只读同步协调器 // 密炼计划只读同步协调器

View File

@@ -153,6 +153,9 @@ namespace YY.Admin.ViewModels.Control
["mesXslMixerMaterialTareStrategy"] = "MixerMaterialTareStrategyListView", ["mesXslMixerMaterialTareStrategy"] = "MixerMaterialTareStrategyListView",
// 已实现页面:胶料快检记录操作台 // 已实现页面:胶料快检记录操作台
["RubberQuickTestRecordListView"] = "RubberQuickTestRecordListView",
["/xslmes/rubberQuickTestRecord"] = "RubberQuickTestRecordListView",
["rubberQuickTestRecord"] = "RubberQuickTestRecordListView",
["RubberQuickTestOperationView"] = "RubberQuickTestOperationView", ["RubberQuickTestOperationView"] = "RubberQuickTestOperationView",
["/xslmes/rubberQuickTestOperation"] = "RubberQuickTestOperationView", ["/xslmes/rubberQuickTestOperation"] = "RubberQuickTestOperationView",
["rubberQuickTestOperation"] = "RubberQuickTestOperationView", ["rubberQuickTestOperation"] = "RubberQuickTestOperationView",

View File

@@ -195,6 +195,7 @@ namespace YY.Admin.ViewModels
private SubscriptionToken? _openOrActivateTabToken; private SubscriptionToken? _openOrActivateTabToken;
private SubscriptionToken? _tabClosedToken; private SubscriptionToken? _tabClosedToken;
private SubscriptionToken? _tabCloseRequestToken;
private SubscriptionToken? _refreshTabToken; private SubscriptionToken? _refreshTabToken;
private SubscriptionToken? _loginOutToken; private SubscriptionToken? _loginOutToken;
@@ -235,6 +236,7 @@ namespace YY.Admin.ViewModels
// 订阅事件 // 订阅事件
_openOrActivateTabToken = _eventAggregator.GetEvent<TabSourceSelectedEvent>().Subscribe(OnOpenOrActivateTab); _openOrActivateTabToken = _eventAggregator.GetEvent<TabSourceSelectedEvent>().Subscribe(OnOpenOrActivateTab);
_tabClosedToken = _eventAggregator.GetEvent<TabClosedEvent>().Subscribe(OnTabClosed); _tabClosedToken = _eventAggregator.GetEvent<TabClosedEvent>().Subscribe(OnTabClosed);
_tabCloseRequestToken = _eventAggregator.GetEvent<TabCloseRequestEvent>().Subscribe(OnTabCloseRequest, ThreadOption.UIThread);
_refreshTabToken = _eventAggregator.GetEvent<TabRefreshEvent>().Subscribe(OnRefreshTab); _refreshTabToken = _eventAggregator.GetEvent<TabRefreshEvent>().Subscribe(OnRefreshTab);
_loginOutToken = _eventAggregator.GetEvent<SysUserEvents.LoginOutEvent>().Subscribe(Destroy); _loginOutToken = _eventAggregator.GetEvent<SysUserEvents.LoginOutEvent>().Subscribe(Destroy);
@@ -489,6 +491,48 @@ namespace YY.Admin.ViewModels
_ = NavigateToViewAsync(CommonConst.ContentRegion, tabItemModel.ViewName, tabItemModel.TabSource.NavigationParameter); _ = NavigateToViewAsync(CommonConst.ContentRegion, tabItemModel.ViewName, tabItemModel.TabSource.NavigationParameter);
} }
/// <summary>
/// 按 ViewName 关闭 Tab并可选激活指定页面
/// </summary>
private void OnTabCloseRequest(TabCloseRequestPayload payload)
{
if (payload == null || string.IsNullOrWhiteSpace(payload.ViewNameToClose))
{
return;
}
var tabsToClose = OpenTabs
.Where(t => t.IsClosable
&& string.Equals(t.ViewName, payload.ViewNameToClose, StringComparison.OrdinalIgnoreCase))
.ToList();
foreach (var tab in tabsToClose)
{
_eventAggregator.GetEvent<TabClosedEvent>().Publish(tab);
OpenTabs.Remove(tab);
}
if (string.IsNullOrWhiteSpace(payload.ActivateViewName))
{
return;
}
var existingTab = OpenTabs.FirstOrDefault(t =>
string.Equals(t.ViewName, payload.ActivateViewName, StringComparison.OrdinalIgnoreCase));
if (existingTab != null)
{
SelectedTab = existingTab;
return;
}
OnOpenOrActivateTab(new TabSource
{
Name = payload.ActivateTabName ?? payload.ActivateViewName,
Icon = "\ue7de",
ViewName = payload.ActivateViewName
});
}
/// <summary> /// <summary>
/// TabItem关闭回调 /// TabItem关闭回调
/// </summary> /// </summary>
@@ -764,6 +808,15 @@ namespace YY.Admin.ViewModels
_tabClosedToken = null; _tabClosedToken = null;
} }
if (_tabCloseRequestToken != null)
{
_eventAggregator
.GetEvent<TabCloseRequestEvent>()
.Unsubscribe(_tabCloseRequestToken);
_tabCloseRequestToken = null;
}
if (_refreshTabToken != null) if (_refreshTabToken != null)
{ {
_eventAggregator _eventAggregator

View File

@@ -0,0 +1,104 @@
using LiveChartsCore;
using LiveChartsCore.Defaults;
using LiveChartsCore.SkiaSharpView;
using Prism.Mvvm;
using System.Collections.ObjectModel;
using YY.Admin.Core.Entity;
namespace YY.Admin.ViewModels.RubberQuickTest;
public class RubberQuickTestRecordDetailDialogViewModel : BindableBase
{
private MesXslRubberQuickTestRecord? _record;
public MesXslRubberQuickTestRecord? Record
{
get => _record;
private set => SetProperty(ref _record, value);
}
private string? _inspectResultDisplay;
public string? InspectResultDisplay
{
get => _inspectResultDisplay;
private set => SetProperty(ref _inspectResultDisplay, value);
}
public ObservableCollection<MesXslRubberQuickTestRecordStdLine> StdLines { get; } = new();
public ObservableCollection<MesXslRubberQuickTestRecordRawLine> RawLines { get; } = new();
public ObservableCollection<ISeries> TemperatureSeries { get; }
public ObservableCollection<ISeries> TorqueSeries { get; }
public ObservableCollection<ObservablePoint> UpperTempValues { get; } = new();
public ObservableCollection<ObservablePoint> LowerTempValues { get; } = new();
public ObservableCollection<ObservablePoint> TorqueValues { get; } = new();
public Axis[] TemperatureXAxes { get; } = BuildTimeAxis();
public Axis[] TemperatureYAxes { get; } = BuildTempYAxis();
public Axis[] TorqueXAxes { get; } = BuildTimeAxis();
public Axis[] TorqueYAxes { get; } = BuildTorqueYAxis();
public RubberQuickTestRecordDetailDialogViewModel()
{
TemperatureSeries = new ObservableCollection<ISeries>
{
new LineSeries<ObservablePoint> { Name = "上模温度", Values = UpperTempValues, GeometrySize = 4, LineSmoothness = 0.3 },
new LineSeries<ObservablePoint> { Name = "下模温度", Values = LowerTempValues, GeometrySize = 4, LineSmoothness = 0.3 }
};
TorqueSeries = new ObservableCollection<ISeries>
{
new LineSeries<ObservablePoint> { Name = "S'(dNm)", Values = TorqueValues, GeometrySize = 4, LineSmoothness = 0.3 }
};
}
public void Initialize(MesXslRubberQuickTestRecord record)
{
Record = record;
InspectResultDisplay = record.InspectResult switch
{
"1" => "合格",
"0" => "不合格",
_ => record.InspectResultText ?? record.InspectResult ?? ""
};
StdLines.Clear();
foreach (var line in record.StdLineList ?? [])
StdLines.Add(line);
RawLines.Clear();
foreach (var line in record.RawLineList ?? [])
RawLines.Add(line);
UpperTempValues.Clear();
LowerTempValues.Clear();
TorqueValues.Clear();
foreach (var p in (record.ChartPointList ?? []).OrderBy(x => x.SortNo ?? 0))
{
var time = (double)(p.TimeMin ?? 0);
UpperTempValues.Add(new ObservablePoint(time, (double)(p.UpperTemp ?? 0)));
LowerTempValues.Add(new ObservablePoint(time, (double)(p.LowerTemp ?? 0)));
TorqueValues.Add(new ObservablePoint(time, (double)(p.TorqueS ?? 0)));
}
}
private static Axis[] BuildTimeAxis() => new[]
{
new Axis { Name = "时间(min)", MinLimit = 0, MaxLimit = 2, Labeler = v => v.ToString("0.0#") }
};
private static Axis[] BuildTempYAxis() => new[]
{
new Axis { Name = "温度(℃)", MinLimit = 189, MaxLimit = 201 }
};
private static readonly double[] TorqueYTicks = { 0.0, 3.0, 5.9, 8.9, 11.8, 14.8 };
private static Axis[] BuildTorqueYAxis() => new[]
{
new Axis
{
Name = "S'(dNm)",
MinLimit = 0,
MaxLimit = 14.8,
CustomSeparators = TorqueYTicks
}
};
}

View File

@@ -0,0 +1,284 @@
using HandyControl.Controls;
using Prism.Events;
using Prism.Navigation;
using System.Collections.ObjectModel;
using System.Diagnostics;
using YY.Admin.Core;
using YY.Admin.Core.Entity;
using YY.Admin.Core.Events;
using YY.Admin.Core.Helper;
using YY.Admin.Core.Services;
using YY.Admin.Event;
using YY.Admin.Module;
using YY.Admin.Services.Service;
namespace YY.Admin.ViewModels.RubberQuickTest;
public class RubberQuickTestRecordListViewModel : BaseViewModel
{
private readonly IRubberQuickTestRecordService _recordService;
private readonly IJeecgDictSyncService _dictSyncService;
private SubscriptionToken? _changedToken;
private ObservableCollection<RubberQuickTestRecordListRow> _records = new();
public ObservableCollection<RubberQuickTestRecordListRow> Records
{
get => _records;
set => SetProperty(ref _records, value);
}
private long _total;
public long Total { get => _total; set => SetProperty(ref _total, value); }
private int _pageNo = 1;
public int PageNo { get => _pageNo; set => SetProperty(ref _pageNo, value); }
private int _pageSize = 20;
public int PageSize { get => _pageSize; set => SetProperty(ref _pageSize, value); }
private string? _filterRecordNo;
public string? FilterRecordNo { get => _filterRecordNo; set => SetProperty(ref _filterRecordNo, value); }
private string? _filterRubberMaterialName;
public string? FilterRubberMaterialName { get => _filterRubberMaterialName; set => SetProperty(ref _filterRubberMaterialName, value); }
private string? _filterPlanNo;
public string? FilterPlanNo { get => _filterPlanNo; set => SetProperty(ref _filterPlanNo, value); }
public DelegateCommand SearchCommand { get; }
public DelegateCommand ResetCommand { get; }
public DelegateCommand AddCommand { get; }
public DelegateCommand<RubberQuickTestRecordListRow> ViewDetailCommand { get; }
public DelegateCommand<RubberQuickTestRecordListRow> EditCommand { get; }
public DelegateCommand<RubberQuickTestRecordListRow> DeleteCommand { get; }
public DelegateCommand<RubberQuickTestRecordListRow> SyncCommand { get; }
public DelegateCommand PrevPageCommand { get; }
public DelegateCommand NextPageCommand { get; }
public RubberQuickTestRecordListViewModel(
IRubberQuickTestRecordService recordService,
IJeecgDictSyncService dictSyncService,
IContainerExtension container,
IRegionManager regionManager) : base(container, regionManager)
{
_recordService = recordService;
_dictSyncService = dictSyncService;
SearchCommand = new DelegateCommand(async () => { PageNo = 1; await LoadAsync(); });
ResetCommand = new DelegateCommand(async () =>
{
FilterRecordNo = null;
FilterRubberMaterialName = null;
FilterPlanNo = null;
PageNo = 1;
await LoadAsync();
});
AddCommand = new DelegateCommand(OpenAddPage);
ViewDetailCommand = new DelegateCommand<RubberQuickTestRecordListRow>(async r => await ShowDetailAsync(r));
EditCommand = new DelegateCommand<RubberQuickTestRecordListRow>(OpenEditPage, r => r != null && r.CanEdit);
DeleteCommand = new DelegateCommand<RubberQuickTestRecordListRow>(
async r => await DeleteAsync(r),
r => r != null && r.CanDelete);
SyncCommand = new DelegateCommand<RubberQuickTestRecordListRow>(
async r => await SyncAsync(r),
r => r != null && r.CanSync);
PrevPageCommand = new DelegateCommand(async () => { if (PageNo > 1) { PageNo--; await LoadAsync(); } });
NextPageCommand = new DelegateCommand(async () => { if ((long)PageNo * PageSize < Total) { PageNo++; await LoadAsync(); } });
_changedToken = _eventAggregator.GetEvent<RubberQuickTestRecordChangedEvent>()
.Subscribe(async _ => await LoadAsync(), ThreadOption.UIThread);
_ = InitializeAsync();
}
private async Task InitializeAsync()
{
try
{
await UIHelper.WaitForRenderAsync();
await LoadAsync();
}
catch (Exception ex)
{
Debug.WriteLine($"快检记录列表初始化失败: {ex.Message}");
}
}
public async Task LoadAsync()
{
try
{
IsLoading = true;
var result = await _recordService.PageAsync(
PageNo, PageSize, FilterRecordNo, FilterRubberMaterialName, FilterPlanNo);
var shiftMap = (await _dictSyncService.GetDictOptionsAsync("xslmes_rubber_quick_test_work_shift", includeAll: false))
.ToDictionary(x => x.Value, x => x.Key);
foreach (var r in result.Records)
{
if (!string.IsNullOrWhiteSpace(r.WorkShiftDisplay) && shiftMap.TryGetValue(r.WorkShiftDisplay, out var txt))
r.WorkShiftDisplay = txt;
}
Records = new ObservableCollection<RubberQuickTestRecordListRow>(result.Records);
Total = result.Total;
}
catch (Exception ex)
{
Growl.Error($"加载快检记录失败:{ex.Message}");
}
finally
{
IsLoading = false;
}
}
private void OpenAddPage()
{
_eventAggregator.GetEvent<TabSourceSelectedEvent>().Publish(new TabSource
{
Name = "新增胶料快检记录",
Icon = "\ue7de",
ViewName = "RubberQuickTestOperationView"
});
}
private void OpenEditPage(RubberQuickTestRecordListRow? row)
{
if (row == null || !row.CanEdit || string.IsNullOrWhiteSpace(row.LocalId))
return;
if (_recordService.GetByLocalId(row.LocalId) == null)
{
Growl.Warning("未找到快检记录");
return;
}
var label = row.RecordNo ?? row.LocalId;
_eventAggregator.GetEvent<TabSourceSelectedEvent>().Publish(new TabSource
{
Name = $"编辑快检记录 {label}",
Icon = "\ue7de",
ViewName = "RubberQuickTestOperationView",
NavigationParameter = new NavigationParameters { { "editLocalId", row.LocalId } }
});
}
private async Task DeleteAsync(RubberQuickTestRecordListRow? row)
{
if (row == null || !row.CanDelete || string.IsNullOrWhiteSpace(row.LocalId))
return;
var label = row.RecordNo ?? row.LocalId;
var confirm = System.Windows.MessageBox.Show(
$"确定删除未同步的快检记录「{label}」?\n此操作仅删除本地记录不可恢复。",
"确认删除",
System.Windows.MessageBoxButton.YesNo,
System.Windows.MessageBoxImage.Warning);
if (confirm != System.Windows.MessageBoxResult.Yes)
return;
try
{
if (_recordService.DeleteUnsyncedLocal(row.LocalId))
{
Growl.Success("删除成功");
await LoadAsync();
}
else
{
Growl.Warning("仅未同步的本地记录可删除");
}
}
catch (Exception ex)
{
Growl.Error($"删除失败:{ex.Message}");
}
}
private async Task SyncAsync(RubberQuickTestRecordListRow? row)
{
if (row == null || !row.CanSync || string.IsNullOrWhiteSpace(row.LocalId))
return;
try
{
IsLoading = true;
var result = await _recordService.SyncLocalAsync(row.LocalId);
if (result.Success)
{
var no = result.RecordNo ?? row.RecordNo ?? row.LocalId;
Growl.Success($"同步成功,快检记录号:{no}");
await LoadAsync();
}
else
{
Growl.Warning(result.Message ?? "同步失败");
}
}
catch (Exception ex)
{
Growl.Error($"同步失败:{ex.Message}");
}
finally
{
IsLoading = false;
}
}
private async Task ShowDetailAsync(RubberQuickTestRecordListRow? row)
{
if (row == null) return;
try
{
if (!string.IsNullOrWhiteSpace(row.LocalId))
{
if (_recordService.GetByLocalId(row.LocalId) == null)
{
Growl.Warning("未找到快检记录详情");
return;
}
}
else if (!string.IsNullOrWhiteSpace(row.MesId))
{
var remote = await _recordService.GetByIdAsync(row.MesId);
if (remote == null)
{
Growl.Warning("未找到快检记录详情");
return;
}
}
else
{
Growl.Warning("无法定位快检记录");
return;
}
var parameters = new NavigationParameters { { "readOnly", true } };
if (!string.IsNullOrWhiteSpace(row.LocalId))
parameters.Add("localId", row.LocalId);
else
parameters.Add("mesId", row.MesId!);
var title = row.RecordNo ?? row.LocalId ?? row.MesId ?? "详情";
_eventAggregator.GetEvent<TabSourceSelectedEvent>().Publish(new TabSource
{
Name = $"快检记录 {title}",
Icon = "\ue7de",
ViewName = "RubberQuickTestOperationView",
NavigationParameter = parameters
});
}
catch (Exception ex)
{
Growl.Error($"打开详情失败:{ex.Message}");
}
}
protected override void CleanUp()
{
base.CleanUp();
if (_changedToken != null)
_eventAggregator.GetEvent<RubberQuickTestRecordChangedEvent>().Unsubscribe(_changedToken);
}
}

View File

@@ -90,7 +90,7 @@
<TextBlock Text="重置" Style="{StaticResource IconButtonStyle}"/> <TextBlock Text="重置" Style="{StaticResource IconButtonStyle}"/>
</StackPanel> </StackPanel>
</Button> </Button>
<TextBlock Text="数据来自 MES 密炼生产计划维护,桌面端只读;断网时显示本地缓存,联网后自动刷新" <TextBlock Text="数据来自 MES 密炼生产计划维护,每 2 秒自动同步;断网时显示本地缓存"
VerticalAlignment="Center" VerticalAlignment="Center"
Foreground="{DynamicResource SecondaryTextBrush}" Foreground="{DynamicResource SecondaryTextBrush}"
FontSize="12"/> FontSize="12"/>

View File

@@ -24,25 +24,41 @@
<Setter Property="Margin" Value="8,0,0,0"/> <Setter Property="Margin" Value="8,0,0,0"/>
<Setter Property="VerticalAlignment" Value="Center"/> <Setter Property="VerticalAlignment" Value="Center"/>
</Style> </Style>
<Style x:Key="DataGridCellCenterTextStyle" TargetType="TextBlock">
<Setter Property="HorizontalAlignment" Value="Center"/>
<Setter Property="TextAlignment" Value="Center"/>
<Setter Property="VerticalAlignment" Value="Center"/>
</Style>
<Style x:Key="DataGridCellCenterEditStyle" TargetType="TextBox">
<Setter Property="HorizontalContentAlignment" Value="Center"/>
<Setter Property="VerticalContentAlignment" Value="Center"/>
</Style>
</UserControl.Resources> </UserControl.Resources>
<Grid Margin="12"> <Grid Margin="12">
<Grid.RowDefinitions> <Grid.RowDefinitions>
<RowDefinition Height="80"/> <RowDefinition Height="52"/>
<RowDefinition Height="*"/> <RowDefinition Height="*"/>
<RowDefinition Height="56"/> <RowDefinition Height="56"/>
</Grid.RowDefinitions> </Grid.RowDefinitions>
<!-- 标题 --> <!-- 标题 -->
<Border Grid.Row="0" Background="{DynamicResource RegionBrush}" <Border Grid.Row="0" Background="{DynamicResource RegionBrush}"
BorderBrush="{DynamicResource BorderBrush}" BorderThickness="0,0,0,1" Margin="-12,0,-12,8"> BorderBrush="{DynamicResource BorderBrush}" BorderThickness="0,0,0,1" Margin="-12,0,-12,6">
<StackPanel Orientation="Horizontal" VerticalAlignment="Center" Margin="20,0"> <StackPanel Orientation="Horizontal" VerticalAlignment="Center" Margin="16,0">
<Border Width="40" Height="40" CornerRadius="8" Background="{DynamicResource PrimaryBrush}" Margin="0,0,12,0"> <Border Width="32" Height="32" CornerRadius="6" Background="{DynamicResource PrimaryBrush}" Margin="0,0,10,0">
<md:PackIcon Kind="Flask" Width="22" Height="22" Foreground="White" HorizontalAlignment="Center" VerticalAlignment="Center"/> <md:PackIcon Kind="Flask" Width="18" Height="18" Foreground="White" HorizontalAlignment="Center" VerticalAlignment="Center"/>
</Border> </Border>
<StackPanel VerticalAlignment="Center"> <StackPanel VerticalAlignment="Center">
<TextBlock Text="无转子流变仪 MDR S3L" FontSize="24" FontWeight="Bold"/> <StackPanel Orientation="Horizontal">
<TextBlock Text="胶料快检记录 · 密炼快检试验操作台" FontSize="12" Foreground="{DynamicResource SecondaryTextBrush}" Margin="0,4,0,0"/> <TextBlock Text="无转子流变仪 MDR S3L" FontSize="18" FontWeight="Bold"/>
<Border Background="#fff7e6" BorderBrush="#ffa940" BorderThickness="1" CornerRadius="4"
Padding="8,2" Margin="12,0,0,0"
Visibility="{Binding IsOfflineMode, Converter={StaticResource Boolean2VisibilityConverter}}">
<TextBlock Text="离线模式" FontSize="12" FontWeight="SemiBold" Foreground="#d46b08"/>
</Border>
</StackPanel>
<TextBlock Text="胶料快检记录 · 密炼快检试验操作台" FontSize="11" Foreground="{DynamicResource SecondaryTextBrush}" Margin="0,2,0,0"/>
</StackPanel> </StackPanel>
</StackPanel> </StackPanel>
</Border> </Border>
@@ -55,111 +71,157 @@
<ColumnDefinition Width="2*"/> <ColumnDefinition Width="2*"/>
</Grid.ColumnDefinitions> </Grid.ColumnDefinitions>
<!-- 左侧:曲线 + 试验结果 --> <!-- 左侧:曲线图 : 试验结果 ≈ 3 : 2 -->
<ScrollViewer Grid.Column="0" VerticalScrollBarVisibility="Auto"> <Grid Grid.Column="0">
<StackPanel> <Grid.RowDefinitions>
<Border Style="{StaticResource SectionBorderStyle}"> <RowDefinition Height="3*"/>
<StackPanel> <RowDefinition Height="2*"/>
<StackPanel Orientation="Horizontal" Margin="0,0,0,8"> </Grid.RowDefinitions>
<Border Width="4" Height="18" CornerRadius="2" Background="#1890ff"/>
<Grid Grid.Row="0">
<Grid.RowDefinitions>
<RowDefinition Height="*"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<Border Grid.Row="0" Style="{StaticResource SectionBorderStyle}" Padding="10,10,10,6" Margin="0,0,0,6">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<StackPanel Grid.Row="0" Orientation="Horizontal" Margin="0,0,0,4">
<Border Width="4" Height="16" CornerRadius="2" Background="#1890ff"/>
<TextBlock Text="温度(℃)曲线图" Style="{StaticResource SectionTitleStyle}"/> <TextBlock Text="温度(℃)曲线图" Style="{StaticResource SectionTitleStyle}"/>
<Button Content="刷新演示" Command="{Binding RefreshChartDemoCommand}" Style="{StaticResource ButtonDefault}" Height="28" Padding="10,0" Margin="12,0,0,0"/> <Button Content="刷新演示" Command="{Binding RefreshChartDemoCommand}" Style="{StaticResource ButtonDefault}" Height="26" Padding="8,0" Margin="10,0,0,0"
Visibility="{Binding IsEditable, Converter={StaticResource Boolean2VisibilityConverter}}"/>
</StackPanel> </StackPanel>
<TextBlock Text="{Binding ChartDemoHint}" FontSize="11" Foreground="{DynamicResource SecondaryTextBrush}" Margin="0,0,0,6"/> <TextBlock Grid.Row="1" Text="{Binding ChartDemoHint}" FontSize="10" Foreground="{DynamicResource SecondaryTextBrush}" Margin="0,0,0,4"
<Border Height="220" CornerRadius="4"> TextTrimming="CharacterEllipsis"/>
<Border Grid.Row="2" CornerRadius="4">
<lvc:CartesianChart Series="{Binding TemperatureSeries}" <lvc:CartesianChart Series="{Binding TemperatureSeries}"
XAxes="{Binding TemperatureXAxes}" XAxes="{Binding TemperatureXAxes}"
YAxes="{Binding TemperatureYAxes}" YAxes="{Binding TemperatureYAxes}"
LegendPosition="Top"/> LegendPosition="Top"/>
</Border> </Border>
</StackPanel> </Grid>
</Border> </Border>
<Border Style="{StaticResource SectionBorderStyle}"> <Border Grid.Row="1" Style="{StaticResource SectionBorderStyle}" Padding="10,10,10,6" Margin="0,0,0,0">
<StackPanel> <Grid>
<StackPanel Orientation="Horizontal" Margin="0,0,0,8"> <Grid.RowDefinitions>
<Border Width="4" Height="18" CornerRadius="2" Background="#1890ff"/> <RowDefinition Height="Auto"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<StackPanel Grid.Row="0" Orientation="Horizontal" Margin="0,0,0,4">
<Border Width="4" Height="16" CornerRadius="2" Background="#1890ff"/>
<TextBlock Text="S'(dNm)曲线图" Style="{StaticResource SectionTitleStyle}"/> <TextBlock Text="S'(dNm)曲线图" Style="{StaticResource SectionTitleStyle}"/>
</StackPanel> </StackPanel>
<Border Height="200" CornerRadius="4"> <Border Grid.Row="1" CornerRadius="4">
<lvc:CartesianChart Series="{Binding TorqueSeries}" <lvc:CartesianChart Series="{Binding TorqueSeries}"
XAxes="{Binding TorqueXAxes}" XAxes="{Binding TorqueXAxes}"
YAxes="{Binding TorqueYAxes}" YAxes="{Binding TorqueYAxes}"
LegendPosition="Top"/> LegendPosition="Top"/>
</Border> </Border>
</StackPanel> </Grid>
</Border> </Border>
</Grid>
<Border Style="{StaticResource SectionBorderStyle}"> <Border Grid.Row="1" Style="{StaticResource SectionBorderStyle}" Margin="0" Padding="10,10,10,8">
<StackPanel> <Grid>
<Grid Margin="0,0,0,8"> <Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="*"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<Grid Grid.Row="0" Margin="0,0,0,8">
<StackPanel Orientation="Horizontal"> <StackPanel Orientation="Horizontal">
<Border Width="4" Height="18" CornerRadius="2" Background="#1890ff"/> <Border Width="4" Height="18" CornerRadius="2" Background="#1890ff"/>
<TextBlock Text="试验结果" Style="{StaticResource SectionTitleStyle}"/> <TextBlock Text="试验结果" Style="{StaticResource SectionTitleStyle}"/>
</StackPanel> </StackPanel>
<StackPanel Orientation="Horizontal" HorizontalAlignment="Right"> <StackPanel Orientation="Horizontal" HorizontalAlignment="Right"
Visibility="{Binding IsEditable, Converter={StaticResource Boolean2VisibilityConverter}}">
<Button Content="新增检验行" Command="{Binding AddInspectRowCommand}" Style="{StaticResource ButtonPrimary}" Height="28" Padding="10,0" Margin="0,0,8,0"/> <Button Content="新增检验行" Command="{Binding AddInspectRowCommand}" Style="{StaticResource ButtonPrimary}" Height="28" Padding="10,0" Margin="0,0,8,0"/>
<Button Content="删除选中行" Command="{Binding RemoveInspectRowCommand}" Style="{StaticResource ButtonDanger}" Height="28" Padding="10,0"/> <Button Content="删除选中行" Command="{Binding RemoveInspectRowCommand}" Style="{StaticResource ButtonDanger}" Height="28" Padding="10,0"/>
</StackPanel> </StackPanel>
</Grid> </Grid>
<DataGrid x:Name="InspectResultGrid" <DataGrid Grid.Row="1"
x:Name="InspectResultGrid"
ItemsSource="{Binding InspectRows}" ItemsSource="{Binding InspectRows}"
AutoGenerateColumns="False" AutoGenerateColumns="False"
CanUserAddRows="False" CanUserAddRows="False"
CanUserDeleteRows="False" CanUserDeleteRows="False"
HeadersVisibility="Column" HeadersVisibility="Column"
MinHeight="160"
SelectionMode="Single" SelectionMode="Single"
IsReadOnly="False" IsReadOnly="{Binding IsReadOnly}"
SelectionChanged="InspectResultGrid_SelectionChanged"> RowHeight="30"
VerticalScrollBarVisibility="Auto"
HorizontalScrollBarVisibility="Auto"
SelectionChanged="InspectResultGrid_SelectionChanged"
Style="{StaticResource CusDataGridStyle}"
ColumnHeaderStyle="{StaticResource CusDataGridColumnHeaderStyle}">
</DataGrid> </DataGrid>
<TextBlock Text="请手填各数据点检测值,系统将根据数据标准上下限自动判定合格/不合格" FontSize="11" Foreground="{DynamicResource SecondaryTextBrush}" Margin="0,6,0,0"/> <TextBlock Grid.Row="2" Text="请手填各数据点检测值,系统将根据数据标准上下限自动判定合格/不合格" FontSize="11" Foreground="{DynamicResource SecondaryTextBrush}" Margin="0,6,0,0"
</StackPanel> Visibility="{Binding IsEditable, Converter={StaticResource Boolean2VisibilityConverter}}"/>
</Grid>
</Border> </Border>
</StackPanel> </Grid>
</ScrollViewer>
<!-- 右侧 --> <!-- 右侧:实时数据 + 试验信息(上)与 数据标准(下 2*,与左侧试验结果同高) -->
<ScrollViewer Grid.Column="2" VerticalScrollBarVisibility="Auto"> <Grid Grid.Column="2">
<StackPanel> <Grid.RowDefinitions>
<!-- 实时数据 --> <RowDefinition Height="3*"/>
<Border Style="{StaticResource SectionBorderStyle}"> <RowDefinition Height="2*"/>
<StackPanel> </Grid.RowDefinitions>
<StackPanel Orientation="Horizontal" Margin="0,0,0,12">
<Border Width="4" Height="18" CornerRadius="2" Background="#52c41a"/> <Grid Grid.Row="0">
<Grid.RowDefinitions>
<RowDefinition Height="*"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<!-- 实时数据(占用上部剩余空间) -->
<Border Grid.Row="0" Style="{StaticResource SectionBorderStyle}" Padding="10,8" Margin="0,0,0,8">
<Grid VerticalAlignment="Stretch">
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<StackPanel Grid.Row="0" Orientation="Horizontal" Margin="0,0,0,8">
<Border Width="4" Height="16" CornerRadius="2" Background="#52c41a"/>
<TextBlock Text="实时数据" Style="{StaticResource SectionTitleStyle}"/> <TextBlock Text="实时数据" Style="{StaticResource SectionTitleStyle}"/>
</StackPanel> </StackPanel>
<Grid> <Grid Grid.Row="1" VerticalAlignment="Center">
<Grid.RowDefinitions>
<RowDefinition/>
<RowDefinition/>
<RowDefinition/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions> <Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/> <ColumnDefinition Width="*"/>
<ColumnDefinition Width="*"/> <ColumnDefinition Width="*"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions> </Grid.ColumnDefinitions>
<TextBlock Text="上模温度(℃)" Foreground="{DynamicResource SecondaryTextBrush}" Margin="0,0,0,4"/> <StackPanel Grid.Column="0" Margin="0,0,6,0" VerticalAlignment="Center">
<TextBlock Grid.Row="1" Text="{Binding UpperMoldTemp, StringFormat={}{0:N2}}" FontSize="28" FontWeight="Bold" FontFamily="Consolas"/> <TextBlock Text="上模温度(℃)" FontSize="12" Foreground="{DynamicResource SecondaryTextBrush}" HorizontalAlignment="Center"/>
<TextBlock Grid.Column="1" Text="下模温度(℃)" Foreground="{DynamicResource SecondaryTextBrush}" Margin="0,0,0,4"/> <TextBlock Text="{Binding UpperMoldTemp, StringFormat={}{0:N2}}" FontSize="28" FontWeight="Bold" FontFamily="Consolas" Margin="0,6,0,0" HorizontalAlignment="Center"/>
<TextBlock Grid.Row="1" Grid.Column="1" Text="{Binding LowerMoldTemp, StringFormat={}{0:N2}}" FontSize="28" FontWeight="Bold" FontFamily="Consolas"/> </StackPanel>
<TextBlock Grid.Row="2" Grid.Column="0" Text="S'(dNm)" Foreground="{DynamicResource SecondaryTextBrush}" Margin="0,12,0,4"/> <StackPanel Grid.Column="1" Margin="0,0,6,0" VerticalAlignment="Center">
<TextBlock Grid.Row="2" Grid.Column="0" Margin="0,32,0,0" Text="{Binding TorqueS, StringFormat={}{0:N2}}" FontSize="28" FontWeight="Bold" FontFamily="Consolas"/> <TextBlock Text="下模温度(℃)" FontSize="12" Foreground="{DynamicResource SecondaryTextBrush}" HorizontalAlignment="Center"/>
<TextBlock Text="{Binding LowerMoldTemp, StringFormat={}{0:N2}}" FontSize="28" FontWeight="Bold" FontFamily="Consolas" Margin="0,6,0,0" HorizontalAlignment="Center"/>
</StackPanel>
<StackPanel Grid.Column="2" VerticalAlignment="Center">
<TextBlock Text="S'(dNm)" FontSize="12" Foreground="{DynamicResource SecondaryTextBrush}" HorizontalAlignment="Center"/>
<TextBlock Text="{Binding TorqueS, StringFormat={}{0:N2}}" FontSize="28" FontWeight="Bold" FontFamily="Consolas" Margin="0,6,0,0" HorizontalAlignment="Center"/>
</StackPanel>
</Grid> </Grid>
</StackPanel> </Grid>
</Border> </Border>
<!-- 试验信息 --> <!-- 试验信息 -->
<Border Style="{StaticResource SectionBorderStyle}"> <Border Grid.Row="1" Style="{StaticResource SectionBorderStyle}" Margin="0,0,0,0">
<StackPanel> <StackPanel>
<Grid Margin="0,0,0,12"> <Grid Margin="0,0,0,12">
<StackPanel Orientation="Horizontal"> <StackPanel Orientation="Horizontal">
<Border Width="4" Height="18" CornerRadius="2" Background="#1890ff"/> <Border Width="4" Height="18" CornerRadius="2" Background="#1890ff"/>
<TextBlock Text="试验信息" Style="{StaticResource SectionTitleStyle}"/> <TextBlock Text="试验信息" Style="{StaticResource SectionTitleStyle}"/>
</StackPanel> </StackPanel>
<Button Content="刷新本地数据" Command="{Binding RefreshPlansCommand}"
Style="{StaticResource ButtonDefault}" Height="26" Padding="8,0"
HorizontalAlignment="Right" VerticalAlignment="Center"/>
</Grid> </Grid>
<hc:Row Gutter="10"> <hc:Row Gutter="10">
@@ -179,6 +241,7 @@
<!-- 密炼日期 --> <!-- 密炼日期 -->
<hc:Col Span="12"> <hc:Col Span="12">
<hc:DatePicker SelectedDate="{Binding MixingDate}" <hc:DatePicker SelectedDate="{Binding MixingDate}"
IsEnabled="{Binding IsEditable}"
hc:InfoElement.Title="密炼日期" hc:InfoElement.Title="密炼日期"
hc:InfoElement.TitleWidth="90" hc:InfoElement.TitleWidth="90"
hc:InfoElement.TitlePlacement="Left" hc:InfoElement.TitlePlacement="Left"
@@ -190,6 +253,7 @@
<hc:Col Span="12"> <hc:Col Span="12">
<hc:ComboBox ItemsSource="{Binding MachineOptions}" <hc:ComboBox ItemsSource="{Binding MachineOptions}"
SelectedItem="{Binding SelectedMachine}" SelectedItem="{Binding SelectedMachine}"
IsEnabled="{Binding IsEditable}"
hc:InfoElement.Title="密炼机台" hc:InfoElement.Title="密炼机台"
hc:InfoElement.TitleWidth="90" hc:InfoElement.TitleWidth="90"
hc:InfoElement.TitlePlacement="Left" hc:InfoElement.TitlePlacement="Left"
@@ -203,6 +267,7 @@
<hc:ComboBox ItemsSource="{Binding ShiftOptions}" <hc:ComboBox ItemsSource="{Binding ShiftOptions}"
SelectedItem="{Binding SelectedShift}" SelectedItem="{Binding SelectedShift}"
DisplayMemberPath="Name" DisplayMemberPath="Name"
IsEnabled="{Binding IsEditable}"
hc:InfoElement.Title="班次" hc:InfoElement.Title="班次"
hc:InfoElement.TitleWidth="90" hc:InfoElement.TitleWidth="90"
hc:InfoElement.TitlePlacement="Left" hc:InfoElement.TitlePlacement="Left"
@@ -216,6 +281,7 @@
<hc:ComboBox ItemsSource="{Binding PlanOptions}" <hc:ComboBox ItemsSource="{Binding PlanOptions}"
SelectedItem="{Binding SelectedPlan}" SelectedItem="{Binding SelectedPlan}"
DisplayMemberPath="PlanMaterialNo" DisplayMemberPath="PlanMaterialNo"
IsEnabled="{Binding IsEditable}"
hc:InfoElement.Title="密炼计划" hc:InfoElement.Title="密炼计划"
hc:InfoElement.TitleWidth="90" hc:InfoElement.TitleWidth="90"
hc:InfoElement.TitlePlacement="Left" hc:InfoElement.TitlePlacement="Left"
@@ -224,21 +290,34 @@
hc:InfoElement.Symbol="*" hc:InfoElement.Symbol="*"
Margin="0,0,0,8"/> Margin="0,0,0,8"/>
</hc:Col> </hc:Col>
<!-- 胶料名称 --> <!-- 胶料名称(在线:计划带出;离线:手动选择) -->
<hc:Col Span="12"> <hc:Col Span="12">
<hc:TextBox Text="{Binding RubberMaterialName, Mode=OneWay}" <Grid Margin="0,0,0,8">
IsReadOnly="True" <hc:TextBox Text="{Binding RubberMaterialName, Mode=OneWay}"
hc:InfoElement.Title="胶料名称" IsReadOnly="True"
hc:InfoElement.TitleWidth="90" hc:InfoElement.Title="胶料名称"
hc:InfoElement.TitlePlacement="Left" hc:InfoElement.TitleWidth="90"
hc:InfoElement.Placeholder="选择计划后自动带出" hc:InfoElement.TitlePlacement="Left"
Margin="0,0,0,8"/> hc:InfoElement.Placeholder="选择计划后自动带出"
Visibility="{Binding IsOnlineMode, Converter={StaticResource Boolean2VisibilityConverter}}"/>
<hc:ComboBox ItemsSource="{Binding RubberMaterialOptions}"
SelectedItem="{Binding SelectedRubberMaterial}"
IsEnabled="{Binding IsEditable}"
hc:InfoElement.Title="胶料名称"
hc:InfoElement.TitleWidth="90"
hc:InfoElement.TitlePlacement="Left"
hc:InfoElement.Placeholder="从本地标准选择胶料"
hc:InfoElement.Necessary="True"
hc:InfoElement.Symbol="*"
Visibility="{Binding IsOfflineMode, Converter={StaticResource Boolean2VisibilityConverter}}"/>
</Grid>
</hc:Col> </hc:Col>
<!-- 实验标准 --> <!-- 实验标准 -->
<hc:Col Span="12"> <hc:Col Span="12">
<hc:ComboBox ItemsSource="{Binding StdOptions}" <hc:ComboBox ItemsSource="{Binding StdOptions}"
SelectedItem="{Binding SelectedStd}" SelectedItem="{Binding SelectedStd}"
DisplayMemberPath="StdName" DisplayMemberPath="StdName"
IsEnabled="{Binding IsEditable}"
hc:InfoElement.Title="实验标准" hc:InfoElement.Title="实验标准"
hc:InfoElement.TitleWidth="90" hc:InfoElement.TitleWidth="90"
hc:InfoElement.TitlePlacement="Left" hc:InfoElement.TitlePlacement="Left"
@@ -260,25 +339,27 @@
<!-- 车次 --> <!-- 车次 -->
<hc:Col Span="12"> <hc:Col Span="12">
<hc:TextBox Text="{Binding TrainNo, UpdateSourceTrigger=PropertyChanged}" <hc:TextBox Text="{Binding TrainNo, UpdateSourceTrigger=PropertyChanged}"
IsReadOnly="{Binding IsReadOnly}"
hc:InfoElement.Title="车次" hc:InfoElement.Title="车次"
hc:InfoElement.TitleWidth="90" hc:InfoElement.TitleWidth="90"
hc:InfoElement.TitlePlacement="Left" hc:InfoElement.TitlePlacement="Left"
hc:InfoElement.Placeholder="手填车次" hc:InfoElement.Placeholder="手填车次"
hc:InfoElement.Necessary="True" hc:InfoElement.Necessary="True"
hc:InfoElement.Symbol="*" hc:InfoElement.Symbol="*"
hc:InfoElement.ShowClearButton="True" hc:InfoElement.ShowClearButton="{Binding IsEditable}"
Margin="0,0,0,8"/> Margin="0,0,0,8"/>
</hc:Col> </hc:Col>
<!-- 试验次数 --> <!-- 试验次数 -->
<hc:Col Span="12"> <hc:Col Span="12">
<hc:TextBox Text="{Binding InspectTimesText, UpdateSourceTrigger=PropertyChanged}" <hc:TextBox Text="{Binding InspectTimesText, UpdateSourceTrigger=PropertyChanged}"
IsReadOnly="{Binding IsReadOnly}"
hc:InfoElement.Title="试验次数" hc:InfoElement.Title="试验次数"
hc:InfoElement.TitleWidth="90" hc:InfoElement.TitleWidth="90"
hc:InfoElement.TitlePlacement="Left" hc:InfoElement.TitlePlacement="Left"
hc:InfoElement.Placeholder="默认 1可修改" hc:InfoElement.Placeholder="默认 1可修改"
hc:InfoElement.Necessary="True" hc:InfoElement.Necessary="True"
hc:InfoElement.Symbol="*" hc:InfoElement.Symbol="*"
hc:InfoElement.ShowClearButton="True" hc:InfoElement.ShowClearButton="{Binding IsEditable}"
Margin="0,0,0,8"/> Margin="0,0,0,8"/>
</hc:Col> </hc:Col>
<!-- 检验结果(由试验结果区域自动判定,只读) --> <!-- 检验结果(由试验结果区域自动判定,只读) -->
@@ -303,55 +384,64 @@
</hc:Col> </hc:Col>
</hc:Row> </hc:Row>
<TextBlock Text="密炼计划、实验标准均来自桌面端本地缓存;按密炼日期筛选计划,再按胶料名称选择实验标准" <TextBlock Text="{Binding OfflineModeHint}"
FontSize="11"
Foreground="#d46b08"
Margin="0,8,0,0"
TextWrapping="Wrap"
Visibility="{Binding IsOfflineMode, Converter={StaticResource Boolean2VisibilityConverter}}"/>
<TextBlock Text="密炼计划、实验标准每 2 秒自动从 MES 同步到本地缓存;断网时使用本地数据"
FontSize="11" FontSize="11"
Foreground="{DynamicResource SecondaryTextBrush}" Foreground="{DynamicResource SecondaryTextBrush}"
Margin="0,8,0,0" Margin="0,8,0,0"
TextWrapping="Wrap"/> TextWrapping="Wrap"
Visibility="{Binding IsOnlineMode, Converter={StaticResource Boolean2VisibilityConverter}}"/>
</StackPanel> </StackPanel>
</Border> </Border>
</Grid>
<!-- 数据标准 --> <!-- 数据标准(高度与左侧试验结果一致) -->
<Border Style="{StaticResource SectionBorderStyle}"> <Border Grid.Row="1" Style="{StaticResource SectionBorderStyle}" Margin="0" Padding="10,10,10,8">
<StackPanel> <Grid>
<StackPanel Orientation="Horizontal" Margin="0,0,0,8"> <Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<StackPanel Grid.Row="0" Orientation="Horizontal" Margin="0,0,0,8">
<Border Width="4" Height="18" CornerRadius="2" Background="#faad14"/> <Border Width="4" Height="18" CornerRadius="2" Background="#faad14"/>
<TextBlock Text="数据标准" Style="{StaticResource SectionTitleStyle}"/> <TextBlock Text="数据标准" Style="{StaticResource SectionTitleStyle}"/>
</StackPanel> </StackPanel>
<DataGrid ItemsSource="{Binding DataPointColumns}" <DataGrid Grid.Row="1"
ItemsSource="{Binding DataPointColumns}"
AutoGenerateColumns="False" AutoGenerateColumns="False"
IsReadOnly="True" IsReadOnly="True"
HeadersVisibility="Column" HeadersVisibility="Column"
MaxHeight="240" RowHeight="30"
ColumnWidth="*"> VerticalScrollBarVisibility="Auto"
HorizontalScrollBarVisibility="Auto"
ColumnWidth="*"
Style="{StaticResource CusDataGridStyle}"
ColumnHeaderStyle="{StaticResource CusDataGridColumnHeaderStyle}">
<DataGrid.Columns> <DataGrid.Columns>
<DataGridTextColumn Header="数据点" Binding="{Binding PointName}" Width="5*"/> <DataGridTextColumn Header="数据点" Binding="{Binding PointName}" Width="5*"
<DataGridTextColumn Header="下限值" Binding="{Binding LowerLimit}" Width="2.5*"> ElementStyle="{StaticResource DataGridCellCenterTextStyle}"/>
<DataGridTextColumn.ElementStyle> <DataGridTextColumn Header="下限值" Binding="{Binding LowerLimit, StringFormat={}{0:N2}}" Width="2.5*"
<Style TargetType="TextBlock"> ElementStyle="{StaticResource DataGridCellCenterTextStyle}"/>
<Setter Property="HorizontalAlignment" Value="Center"/> <DataGridTextColumn Header="上限值" Binding="{Binding UpperLimit, StringFormat={}{0:N2}}" Width="2.5*"
</Style> ElementStyle="{StaticResource DataGridCellCenterTextStyle}"/>
</DataGridTextColumn.ElementStyle>
</DataGridTextColumn>
<DataGridTextColumn Header="上限值" Binding="{Binding UpperLimit}" Width="2.5*">
<DataGridTextColumn.ElementStyle>
<Style TargetType="TextBlock">
<Setter Property="HorizontalAlignment" Value="Center"/>
</Style>
</DataGridTextColumn.ElementStyle>
</DataGridTextColumn>
</DataGrid.Columns> </DataGrid.Columns>
</DataGrid> </DataGrid>
</StackPanel> </Grid>
</Border> </Border>
</StackPanel> </Grid>
</ScrollViewer>
</Grid> </Grid>
<!-- 底部操作 --> <!-- 底部操作 -->
<Border Grid.Row="2" Background="{DynamicResource RegionBrush}" BorderBrush="{DynamicResource BorderBrush}" BorderThickness="0,1,0,0" Margin="-12,8,-12,-12"> <Border Grid.Row="2" Background="{DynamicResource RegionBrush}" BorderBrush="{DynamicResource BorderBrush}" BorderThickness="0,1,0,0" Margin="-12,8,-12,-12">
<StackPanel Orientation="Horizontal" HorizontalAlignment="Right" Margin="20,0"> <StackPanel Orientation="Horizontal" HorizontalAlignment="Right" Margin="20,0">
<Button Content="保存胶料快检记录" Command="{Binding SaveCommand}" Style="{StaticResource ButtonPrimary}" Height="36" Width="160"/> <Button Content="{Binding SaveButtonText}" Command="{Binding SaveCommand}" Style="{StaticResource ButtonPrimary}" Height="36" MinWidth="160" Padding="12,0"
IsEnabled="{Binding CanSave}"
Visibility="{Binding ShowSaveButton, Converter={StaticResource Boolean2VisibilityConverter}}"/>
</StackPanel> </StackPanel>
</Border> </Border>
</Grid> </Grid>

View File

@@ -62,6 +62,9 @@ public partial class RubberQuickTestOperationView : UserControl
var vm = _vm ?? DataContext as RubberQuickTestOperationViewModel; var vm = _vm ?? DataContext as RubberQuickTestOperationViewModel;
if (vm == null) return; if (vm == null) return;
var centerTextStyle = (Style)FindResource("DataGridCellCenterTextStyle");
var centerEditStyle = (Style)FindResource("DataGridCellCenterEditStyle");
InspectResultGrid.Columns.Clear(); InspectResultGrid.Columns.Clear();
InspectResultGrid.Columns.Add(new DataGridTextColumn InspectResultGrid.Columns.Add(new DataGridTextColumn
@@ -72,7 +75,8 @@ public partial class RubberQuickTestOperationView : UserControl
Mode = BindingMode.OneWay Mode = BindingMode.OneWay
}, },
IsReadOnly = true, IsReadOnly = true,
Width = 80 Width = 80,
ElementStyle = centerTextStyle
}); });
for (int i = 0; i < vm.DataPointColumns.Count; i++) for (int i = 0; i < vm.DataPointColumns.Count; i++)
@@ -83,17 +87,23 @@ public partial class RubberQuickTestOperationView : UserControl
? $"数据点{columnIndex + 1}" ? $"数据点{columnIndex + 1}"
: col.PointName; : col.PointName;
var valueBinding = new Binding($"Cells[{columnIndex}].Value")
{
UpdateSourceTrigger = UpdateSourceTrigger.PropertyChanged,
Mode = vm.IsReadOnly ? BindingMode.OneWay : BindingMode.TwoWay,
TargetNullValue = string.Empty
};
if (vm.IsReadOnly)
valueBinding.StringFormat = "N2";
InspectResultGrid.Columns.Add(new DataGridTextColumn InspectResultGrid.Columns.Add(new DataGridTextColumn
{ {
Header = header, Header = header,
Binding = new Binding($"Cells[{columnIndex}].Value") Binding = valueBinding,
{
UpdateSourceTrigger = UpdateSourceTrigger.PropertyChanged,
Mode = BindingMode.TwoWay,
TargetNullValue = string.Empty
},
Width = 100, Width = 100,
IsReadOnly = false IsReadOnly = vm.IsReadOnly,
ElementStyle = centerTextStyle,
EditingElementStyle = centerEditStyle
}); });
} }
@@ -105,8 +115,11 @@ public partial class RubberQuickTestOperationView : UserControl
Mode = BindingMode.OneWay Mode = BindingMode.OneWay
}, },
IsReadOnly = true, IsReadOnly = true,
Width = 90 Width = 90,
ElementStyle = centerTextStyle
}); });
InspectResultGrid.IsReadOnly = vm.IsReadOnly;
} }
private void InspectResultGrid_SelectionChanged(object sender, SelectionChangedEventArgs e) private void InspectResultGrid_SelectionChanged(object sender, SelectionChangedEventArgs e)

View File

@@ -0,0 +1,130 @@
<UserControl x:Class="YY.Admin.Views.RubberQuickTest.RubberQuickTestRecordDetailDialogView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:hc="https://handyorg.github.io/handycontrol"
xmlns:prism="http://prismlibrary.com/"
xmlns:lvc="clr-namespace:LiveChartsCore.SkiaSharpView.WPF;assembly=LiveChartsCore.SkiaSharpView.WPF"
prism:ViewModelLocator.AutoWireViewModel="True"
mc:Ignorable="d"
Width="1100"
MinHeight="680">
<Grid Background="{DynamicResource ThirdlyRegionBrush}">
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="*"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<hc:SimplePanel Margin="20">
<TextBlock FontSize="18" Foreground="{DynamicResource PrimaryTextBrush}"
Text="胶料快检记录详情" HorizontalAlignment="Left"/>
<Button Width="22" Height="22" Command="hc:ControlCommands.Close"
Style="{StaticResource ButtonIcon}" Foreground="{DynamicResource PrimaryBrush}"
hc:IconElement.Geometry="{StaticResource ErrorGeometry}"
Padding="0" HorizontalAlignment="Right" VerticalAlignment="Top"/>
</hc:SimplePanel>
<hc:ScrollViewer Grid.Row="1" IsInertiaEnabled="True">
<StackPanel Margin="20,0,20,0">
<hc:Row Gutter="10">
<hc:Col Span="8">
<hc:TextBox Text="{Binding Record.RecordNo, Mode=OneWay}" IsReadOnly="True"
hc:InfoElement.Title="快检记录号" hc:InfoElement.TitleWidth="90" Margin="0,0,0,8"/>
</hc:Col>
<hc:Col Span="8">
<hc:TextBox Text="{Binding Record.ProductionDate, Mode=OneWay, StringFormat={}{0:yyyy-MM-dd}}" IsReadOnly="True"
hc:InfoElement.Title="密炼日期" hc:InfoElement.TitleWidth="90" Margin="0,0,0,8"/>
</hc:Col>
<hc:Col Span="8">
<hc:TextBox Text="{Binding Record.ProdEquipmentName, Mode=OneWay}" IsReadOnly="True"
hc:InfoElement.Title="密炼机台" hc:InfoElement.TitleWidth="90" Margin="0,0,0,8"/>
</hc:Col>
<hc:Col Span="8">
<hc:TextBox Text="{Binding Record.ProductionPlanNo, Mode=OneWay}" IsReadOnly="True"
hc:InfoElement.Title="密炼计划" hc:InfoElement.TitleWidth="90" Margin="0,0,0,8"/>
</hc:Col>
<hc:Col Span="8">
<hc:TextBox Text="{Binding Record.RubberMaterialName, Mode=OneWay}" IsReadOnly="True"
hc:InfoElement.Title="胶料名称" hc:InfoElement.TitleWidth="90" Margin="0,0,0,8"/>
</hc:Col>
<hc:Col Span="8">
<hc:TextBox Text="{Binding Record.StdName, Mode=OneWay}" IsReadOnly="True"
hc:InfoElement.Title="实验标准" hc:InfoElement.TitleWidth="90" Margin="0,0,0,8"/>
</hc:Col>
<hc:Col Span="8">
<hc:TextBox Text="{Binding Record.TestMethodName, Mode=OneWay}" IsReadOnly="True"
hc:InfoElement.Title="实验方法" hc:InfoElement.TitleWidth="90" Margin="0,0,0,8"/>
</hc:Col>
<hc:Col Span="8">
<hc:TextBox Text="{Binding Record.QuickTestTypeName, Mode=OneWay}" IsReadOnly="True"
hc:InfoElement.Title="实验类型" hc:InfoElement.TitleWidth="90" Margin="0,0,0,8"/>
</hc:Col>
<hc:Col Span="8">
<hc:TextBox Text="{Binding Record.TrainNo, Mode=OneWay}" IsReadOnly="True"
hc:InfoElement.Title="车次" hc:InfoElement.TitleWidth="90" Margin="0,0,0,8"/>
</hc:Col>
<hc:Col Span="8">
<hc:TextBox Text="{Binding Record.InspectTimes, Mode=OneWay}" IsReadOnly="True"
hc:InfoElement.Title="试验次数" hc:InfoElement.TitleWidth="90" Margin="0,0,0,8"/>
</hc:Col>
<hc:Col Span="8">
<hc:TextBox Text="{Binding Record.InspectorRealname, Mode=OneWay}" IsReadOnly="True"
hc:InfoElement.Title="检验人" hc:InfoElement.TitleWidth="90" Margin="0,0,0,8"/>
</hc:Col>
<hc:Col Span="8">
<hc:TextBox Text="{Binding Record.CreateTime, Mode=OneWay, StringFormat={}{0:yyyy-MM-dd HH:mm}}" IsReadOnly="True"
hc:InfoElement.Title="检验日期" hc:InfoElement.TitleWidth="90" Margin="0,0,0,8"/>
</hc:Col>
<hc:Col Span="8">
<hc:TextBox Text="{Binding InspectResultDisplay, Mode=OneWay}" IsReadOnly="True"
hc:InfoElement.Title="是否合格" hc:InfoElement.TitleWidth="90" Margin="0,0,0,8"/>
</hc:Col>
</hc:Row>
<TextBlock Text="温度曲线" FontWeight="SemiBold" Margin="0,8,0,6"/>
<Border Height="200" Margin="0,0,0,12">
<lvc:CartesianChart Series="{Binding TemperatureSeries}"
XAxes="{Binding TemperatureXAxes}"
YAxes="{Binding TemperatureYAxes}"
LegendPosition="Top"/>
</Border>
<TextBlock Text="S'曲线" FontWeight="SemiBold" Margin="0,0,0,6"/>
<Border Height="200" Margin="0,0,0,12">
<lvc:CartesianChart Series="{Binding TorqueSeries}"
XAxes="{Binding TorqueXAxes}"
YAxes="{Binding TorqueYAxes}"
LegendPosition="Top"/>
</Border>
<TextBlock Text="数据标准" FontWeight="SemiBold" Margin="0,0,0,6"/>
<DataGrid ItemsSource="{Binding StdLines}" AutoGenerateColumns="False" IsReadOnly="True"
MaxHeight="180" Margin="0,0,0,12">
<DataGrid.Columns>
<DataGridTextColumn Header="数据点" Binding="{Binding PointName}" Width="*"/>
<DataGridTextColumn Header="下限值" Binding="{Binding LowerLimit}" Width="100"/>
<DataGridTextColumn Header="上限值" Binding="{Binding UpperLimit}" Width="100"/>
</DataGrid.Columns>
</DataGrid>
<TextBlock Text="试验结果" FontWeight="SemiBold" Margin="0,0,0,6"/>
<DataGrid ItemsSource="{Binding RawLines}" AutoGenerateColumns="False" IsReadOnly="True"
MaxHeight="220">
<DataGrid.Columns>
<DataGridTextColumn Header="编号" Binding="{Binding RowNo}" Width="80"/>
<DataGridTextColumn Header="数据点" Binding="{Binding InspectItem}" Width="*"/>
<DataGridTextColumn Header="检测值" Binding="{Binding InspectValue}" Width="100"/>
<DataGridTextColumn Header="行结果" Binding="{Binding RowInspectResult}" Width="80"/>
</DataGrid.Columns>
</DataGrid>
</StackPanel>
</hc:ScrollViewer>
<StackPanel Grid.Row="2" Orientation="Horizontal" HorizontalAlignment="Right" Margin="20">
<Button Content="关闭" Command="hc:ControlCommands.Close" Style="{StaticResource ButtonDefault}" Width="90"/>
</StackPanel>
</Grid>
</UserControl>

View File

@@ -0,0 +1,9 @@
namespace YY.Admin.Views.RubberQuickTest;
public partial class RubberQuickTestRecordDetailDialogView
{
public RubberQuickTestRecordDetailDialogView()
{
InitializeComponent();
}
}

View File

@@ -0,0 +1,115 @@
<UserControl x:Class="YY.Admin.Views.RubberQuickTest.RubberQuickTestRecordListView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:hc="https://handyorg.github.io/handycontrol"
xmlns:md="http://materialdesigninxaml.net/winfx/xaml/themes"
xmlns:prism="http://prismlibrary.com/"
prism:ViewModelLocator.AutoWireViewModel="True">
<Grid Style="{StaticResource BaseViewStyle}">
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="*"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<Border Grid.Row="0" CornerRadius="4" Margin="0 0 -10 0">
<hc:Row>
<hc:Col Layout="{hc:ColLayout Xs=12, Sm=8, Md=6, Lg=4, Xl=4}">
<hc:TextBox Text="{Binding FilterRecordNo, UpdateSourceTrigger=PropertyChanged}"
Margin="0 0 10 10" hc:InfoElement.Title="快检记录号"
hc:InfoElement.TitlePlacement="Left" hc:InfoElement.TitleWidth="80"
hc:InfoElement.ShowClearButton="True">
<hc:TextBox.InputBindings>
<KeyBinding Key="Enter" Command="{Binding SearchCommand}"/>
</hc:TextBox.InputBindings>
</hc:TextBox>
</hc:Col>
<hc:Col Layout="{hc:ColLayout Xs=12, Sm=8, Md=6, Lg=4, Xl=4}">
<hc:TextBox Text="{Binding FilterRubberMaterialName, UpdateSourceTrigger=PropertyChanged}"
Margin="0 0 10 10" hc:InfoElement.Title="胶料名称"
hc:InfoElement.TitlePlacement="Left" hc:InfoElement.TitleWidth="80"
hc:InfoElement.ShowClearButton="True"/>
</hc:Col>
<hc:Col Layout="{hc:ColLayout Xs=12, Sm=8, Md=6, Lg=4, Xl=4}">
<hc:TextBox Text="{Binding FilterPlanNo, UpdateSourceTrigger=PropertyChanged}"
Margin="0 0 10 10" hc:InfoElement.Title="密炼计划"
hc:InfoElement.TitlePlacement="Left" hc:InfoElement.TitleWidth="80"
hc:InfoElement.ShowClearButton="True"/>
</hc:Col>
</hc:Row>
</Border>
<Border Grid.Row="1" Margin="0,10">
<hc:UniformSpacingPanel Spacing="10">
<Button Style="{StaticResource ButtonPrimary}" Command="{Binding SearchCommand}">
<StackPanel Orientation="Horizontal">
<md:PackIcon Kind="Search"/>
<TextBlock Text="搜索" Style="{StaticResource IconButtonStyle}"/>
</StackPanel>
</Button>
<Button Style="{StaticResource ButtonDefault}" Command="{Binding ResetCommand}">
<StackPanel Orientation="Horizontal">
<md:PackIcon Kind="Refresh"/>
<TextBlock Text="重置" Style="{StaticResource IconButtonStyle}"/>
</StackPanel>
</Button>
<Button Style="{StaticResource ButtonSuccess}" Command="{Binding AddCommand}">
<StackPanel Orientation="Horizontal">
<md:PackIcon Kind="Plus"/>
<TextBlock Text="新增" Style="{StaticResource IconButtonStyle}"/>
</StackPanel>
</Button>
</hc:UniformSpacingPanel>
</Border>
<DataGrid Grid.Row="2" ItemsSource="{Binding Records}" AutoGenerateColumns="False" IsReadOnly="True"
CanUserAddRows="False" SelectionMode="Single" HeadersVisibility="All"
Style="{StaticResource CusDataGridStyle}" ColumnHeaderStyle="{StaticResource CusDataGridColumnHeaderStyle}">
<DataGrid.Columns>
<DataGridTextColumn Header="快检记录号" Binding="{Binding RecordNo}" Width="160"/>
<DataGridTextColumn Header="密炼日期" Binding="{Binding ProductionDate, StringFormat='yyyy-MM-dd'}" Width="100"/>
<DataGridTextColumn Header="密炼机台" Binding="{Binding ProdEquipmentName}" Width="100"/>
<DataGridTextColumn Header="班次" Binding="{Binding WorkShiftDisplay}" Width="70"/>
<DataGridTextColumn Header="密炼计划" Binding="{Binding ProductionPlanNo}" Width="110"/>
<DataGridTextColumn Header="胶料名称" Binding="{Binding RubberMaterialName}" Width="120"/>
<DataGridTextColumn Header="实验标准" Binding="{Binding StdName}" Width="120"/>
<DataGridTextColumn Header="实验方法" Binding="{Binding TestMethodName}" Width="100"/>
<DataGridTextColumn Header="实验类型" Binding="{Binding QuickTestTypeName}" Width="100"/>
<DataGridTextColumn Header="车次" Binding="{Binding TrainNo}" Width="70"/>
<DataGridTextColumn Header="试验次数" Binding="{Binding InspectTimes}" Width="80"/>
<DataGridTextColumn Header="检验人" Binding="{Binding InspectorRealname}" Width="80"/>
<DataGridTextColumn Header="检验日期" Binding="{Binding InspectDate, StringFormat='yyyy-MM-dd HH:mm'}" Width="130"/>
<DataGridTextColumn Header="是否合格" Binding="{Binding InspectResultDisplay}" Width="80"/>
<DataGridTextColumn Header="同步状态" Binding="{Binding SyncStatusDisplay}" Width="80"/>
<DataGridTemplateColumn Header="操作" Width="270">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<Button Content="详情" Command="{Binding DataContext.ViewDetailCommand, RelativeSource={RelativeSource AncestorType=DataGrid}}"
CommandParameter="{Binding}" Style="{StaticResource ButtonPrimary}" Height="26" Padding="8,0" Margin="0,0,6,0"/>
<Button Content="编辑" Command="{Binding DataContext.EditCommand, RelativeSource={RelativeSource AncestorType=DataGrid}}"
CommandParameter="{Binding}" Style="{StaticResource ButtonDefault}" Height="26" Padding="8,0" Margin="0,0,6,0"
Visibility="{Binding CanEdit, Converter={StaticResource Boolean2VisibilityConverter}}"/>
<Button Content="同步" Command="{Binding DataContext.SyncCommand, RelativeSource={RelativeSource AncestorType=DataGrid}}"
CommandParameter="{Binding}" Style="{StaticResource ButtonSuccess}" Height="26" Padding="8,0" Margin="0,0,6,0"
Visibility="{Binding CanSync, Converter={StaticResource Boolean2VisibilityConverter}}"/>
<Button Content="删除" Command="{Binding DataContext.DeleteCommand, RelativeSource={RelativeSource AncestorType=DataGrid}}"
CommandParameter="{Binding}" Style="{StaticResource ButtonDanger}" Height="26" Padding="8,0"
Visibility="{Binding CanDelete, Converter={StaticResource Boolean2VisibilityConverter}}"/>
</StackPanel>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
</DataGrid.Columns>
</DataGrid>
<StackPanel Grid.Row="3" Orientation="Horizontal" HorizontalAlignment="Right" Margin="0,10,0,0">
<TextBlock Text="{Binding Total, StringFormat=共 {0} 条}" VerticalAlignment="Center" Margin="0,0,16,0"
Foreground="{DynamicResource SecondaryTextBrush}"/>
<Button Content="上一页" Command="{Binding PrevPageCommand}" Style="{StaticResource ButtonDefault}" Margin="0,0,4,0" Width="80"/>
<TextBlock Text="{Binding PageNo, StringFormat=第 {0} 页}" VerticalAlignment="Center" Margin="8,0"/>
<Button Content="下一页" Command="{Binding NextPageCommand}" Style="{StaticResource ButtonDefault}" Width="80"/>
</StackPanel>
</Grid>
</UserControl>

View File

@@ -0,0 +1,11 @@
using System.Windows.Controls;
namespace YY.Admin.Views.RubberQuickTest;
public partial class RubberQuickTestRecordListView : UserControl
{
public RubberQuickTestRecordListView()
{
InitializeComponent();
}
}

View File

@@ -66,7 +66,7 @@
<TextBlock Text="重置" Style="{StaticResource IconButtonStyle}"/> <TextBlock Text="重置" Style="{StaticResource IconButtonStyle}"/>
</StackPanel> </StackPanel>
</Button> </Button>
<TextBlock Text="数据来自 MES桌面端只读;断网时显示本地缓存" <TextBlock Text="数据来自 MES每 2 秒自动同步(含标准明细);断网时显示本地缓存"
VerticalAlignment="Center" VerticalAlignment="Center"
Foreground="{DynamicResource SecondaryTextBrush}" Foreground="{DynamicResource SecondaryTextBrush}"
FontSize="12"/> FontSize="12"/>