增强原材料卡片管理功能,新增免密接口和数据处理逻辑,支持原材料卡片的增删改查操作。更新前端视图以支持多行拆码明细拼接,优化用户体验和系统实时数据同步能力。
This commit is contained in:
@@ -203,7 +203,16 @@ public class BrowserUtils {
|
||||
|
||||
/** 判断请求是否来自移动端 */
|
||||
public static boolean isMobile(HttpServletRequest request) {
|
||||
String ua = request.getHeader("User-Agent").toLowerCase();
|
||||
// 某些场景(如服务端内部 HttpClient、监控探针、客户端在响应前断开连接)请求不带 User-Agent,
|
||||
// 必须先做 null/空判断,否则会在全局异常处理器中触发二次 NPE,掩盖原始异常并污染日志。
|
||||
if (request == null) {
|
||||
return false;
|
||||
}
|
||||
String header = request.getHeader("User-Agent");
|
||||
if (header == null || header.isEmpty()) {
|
||||
return false;
|
||||
}
|
||||
String ua = header.toLowerCase();
|
||||
String type = "(phone|pad|pod|iphone|ipod|ios|ipad|android|mobile|blackberry|iemobile|mqqbrowser|juc|fennec|wosbrowser|browserng|webos|symbian|windows phone)";
|
||||
Pattern pattern = Pattern.compile(type);
|
||||
return pattern.matcher(ua).find();
|
||||
|
||||
@@ -205,6 +205,8 @@ public class ShiroConfig {
|
||||
filterChainDefinitionMap.put("/xslmes/mesXslWeightRecord/anon/**", "anon");
|
||||
// MES原料入场记录免密接口(供桌面端调用)
|
||||
filterChainDefinitionMap.put("/xslmes/mesXslRawMaterialEntry/anon/**", "anon");
|
||||
// MES原材料卡片免密接口(供桌面端调用)
|
||||
filterChainDefinitionMap.put("/xslmes/mesXslRawMaterialCard/anon/**", "anon");
|
||||
// MES密炼物料管理免密接口(供桌面端调用)
|
||||
filterChainDefinitionMap.put("/mes/material/mixerMaterial/anon/**", "anon");
|
||||
// 系统分类字典免密接口(供桌面端调用)
|
||||
|
||||
@@ -14,11 +14,13 @@ import org.jeecg.common.system.query.QueryGenerator;
|
||||
import org.jeecg.common.util.oConvertUtils;
|
||||
import org.jeecg.modules.xslmes.constant.MesXslCustomerBizStatus;
|
||||
import org.jeecg.modules.xslmes.entity.MesXslCustomer;
|
||||
import org.jeecg.modules.xslmes.entity.MesXslRawMaterialCard;
|
||||
import org.jeecg.modules.xslmes.entity.MesXslRawMaterialEntry;
|
||||
import org.jeecg.modules.xslmes.entity.MesXslSupplier;
|
||||
import org.jeecg.modules.xslmes.entity.MesXslVehicle;
|
||||
import org.jeecg.modules.xslmes.entity.MesXslWeightRecord;
|
||||
import org.jeecg.modules.xslmes.service.IMesXslCustomerService;
|
||||
import org.jeecg.modules.xslmes.service.IMesXslRawMaterialCardService;
|
||||
import org.jeecg.modules.xslmes.service.IMesXslRawMaterialEntryService;
|
||||
import org.jeecg.modules.xslmes.service.IMesXslSupplierService;
|
||||
import org.jeecg.modules.xslmes.service.IMesXslVehicleService;
|
||||
@@ -49,6 +51,7 @@ public class MesXslDesktopAnonController {
|
||||
private final IMesXslSupplierService supplierService;
|
||||
private final IMesXslWeightRecordService weightRecordService;
|
||||
private final IMesXslRawMaterialEntryService rawMaterialEntryService;
|
||||
private final IMesXslRawMaterialCardService rawMaterialCardService;
|
||||
private final MesXslStompNotifyService stompNotify;
|
||||
|
||||
// ═══════════════════════════ 车辆管理 ═══════════════════════════
|
||||
@@ -502,6 +505,66 @@ public class MesXslDesktopAnonController {
|
||||
return Result.OK("批量删除成功!");
|
||||
}
|
||||
|
||||
// ═══════════════════════════ 原材料卡片 ═══════════════════════════
|
||||
|
||||
@Operation(summary = "原材料卡片-免密分页列表查询")
|
||||
@GetMapping("/xslmes/mesXslRawMaterialCard/anon/list")
|
||||
public Result<IPage<MesXslRawMaterialCard>> rawMaterialCardAnonList(
|
||||
MesXslRawMaterialCard entity,
|
||||
@RequestParam(name = "pageNo", defaultValue = "1") Integer pageNo,
|
||||
@RequestParam(name = "pageSize", defaultValue = "10") Integer pageSize,
|
||||
HttpServletRequest req) {
|
||||
QueryWrapper<MesXslRawMaterialCard> qw = QueryGenerator.initQueryWrapper(entity, req.getParameterMap());
|
||||
qw.orderByDesc("create_time");
|
||||
IPage<MesXslRawMaterialCard> page = rawMaterialCardService.page(new Page<>(pageNo, pageSize), qw);
|
||||
return Result.OK(page);
|
||||
}
|
||||
|
||||
@Operation(summary = "原材料卡片-免密通过id查询")
|
||||
@GetMapping("/xslmes/mesXslRawMaterialCard/anon/queryById")
|
||||
public Result<MesXslRawMaterialCard> rawMaterialCardAnonQueryById(@RequestParam(name = "id") String id) {
|
||||
MesXslRawMaterialCard entity = rawMaterialCardService.getById(id);
|
||||
return entity != null ? Result.OK(entity) : Result.error("未找到对应数据");
|
||||
}
|
||||
|
||||
@Operation(summary = "原材料卡片-免密添加")
|
||||
@PostMapping("/xslmes/mesXslRawMaterialCard/anon/add")
|
||||
public Result<String> rawMaterialCardAnonAdd(@RequestBody MesXslRawMaterialCard entity) {
|
||||
rawMaterialCardService.save(entity);
|
||||
stompNotify.publishRawMaterialCardChanged("add", entity.getId());
|
||||
return Result.OK("添加成功!");
|
||||
}
|
||||
|
||||
@Operation(summary = "原材料卡片-免密编辑")
|
||||
@RequestMapping(value = "/xslmes/mesXslRawMaterialCard/anon/edit", method = {RequestMethod.PUT, RequestMethod.POST})
|
||||
public Result<String> rawMaterialCardAnonEdit(@RequestBody MesXslRawMaterialCard entity) {
|
||||
if (oConvertUtils.isEmpty(entity.getId())) {
|
||||
return Result.error("主键不能为空");
|
||||
}
|
||||
boolean ok = rawMaterialCardService.updateById(entity);
|
||||
if (!ok) {
|
||||
return Result.error("数据已被他人修改,请刷新后重试");
|
||||
}
|
||||
stompNotify.publishRawMaterialCardChanged("edit", entity.getId());
|
||||
return Result.OK("编辑成功!");
|
||||
}
|
||||
|
||||
@Operation(summary = "原材料卡片-免密删除")
|
||||
@DeleteMapping("/xslmes/mesXslRawMaterialCard/anon/delete")
|
||||
public Result<String> rawMaterialCardAnonDelete(@RequestParam(name = "id") String id) {
|
||||
rawMaterialCardService.removeById(id);
|
||||
stompNotify.publishRawMaterialCardChanged("delete", id);
|
||||
return Result.OK("删除成功!");
|
||||
}
|
||||
|
||||
@Operation(summary = "原材料卡片-免密批量删除")
|
||||
@DeleteMapping("/xslmes/mesXslRawMaterialCard/anon/deleteBatch")
|
||||
public Result<String> rawMaterialCardAnonDeleteBatch(@RequestParam(name = "ids") String ids) {
|
||||
rawMaterialCardService.removeByIds(Arrays.asList(ids.split(",")));
|
||||
stompNotify.publishRawMaterialCardChanged("batchDelete", ids);
|
||||
return Result.OK("批量删除成功!");
|
||||
}
|
||||
|
||||
// ─────────────────────────── 车辆私有辅助 ────────────────────────────
|
||||
|
||||
private void applyWeightBillType(MesXslWeightRecord record) {
|
||||
|
||||
@@ -0,0 +1,155 @@
|
||||
package org.jeecg.modules.xslmes.controller;
|
||||
|
||||
import java.util.Arrays;
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import jakarta.servlet.http.HttpServletResponse;
|
||||
import org.jeecg.common.api.vo.Result;
|
||||
import org.jeecg.common.system.query.QueryGenerator;
|
||||
import org.jeecg.modules.xslmes.entity.MesXslRawMaterialCard;
|
||||
import org.jeecg.modules.xslmes.service.IMesXslRawMaterialCardService;
|
||||
import org.jeecg.modules.xslmes.service.MesXslStompNotifyService;
|
||||
|
||||
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
|
||||
import com.baomidou.mybatisplus.core.metadata.IPage;
|
||||
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
import org.jeecg.common.system.base.controller.JeecgController;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
import org.springframework.web.servlet.ModelAndView;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import org.jeecg.common.aspect.annotation.AutoLog;
|
||||
import org.apache.shiro.authz.annotation.RequiresPermissions;
|
||||
|
||||
/**
|
||||
* @Description: 原材料卡片
|
||||
* @Author: jeecg-boot
|
||||
* @Date: 2026-05-11
|
||||
* @Version: V1.0
|
||||
*/
|
||||
@Tag(name = "原材料卡片")
|
||||
@RestController
|
||||
@RequestMapping("/xslmes/mesXslRawMaterialCard")
|
||||
@Slf4j
|
||||
public class MesXslRawMaterialCardController extends JeecgController<MesXslRawMaterialCard, IMesXslRawMaterialCardService> {
|
||||
@Autowired
|
||||
private IMesXslRawMaterialCardService mesXslRawMaterialCardService;
|
||||
@Autowired
|
||||
private MesXslStompNotifyService stompNotify;
|
||||
|
||||
/**
|
||||
* 分页列表查询
|
||||
*/
|
||||
@Operation(summary = "原材料卡片-分页列表查询")
|
||||
@GetMapping(value = "/list")
|
||||
public Result<IPage<MesXslRawMaterialCard>> queryPageList(MesXslRawMaterialCard mesXslRawMaterialCard,
|
||||
@RequestParam(name = "pageNo", defaultValue = "1") Integer pageNo,
|
||||
@RequestParam(name = "pageSize", defaultValue = "10") Integer pageSize,
|
||||
HttpServletRequest req) {
|
||||
QueryWrapper<MesXslRawMaterialCard> queryWrapper = QueryGenerator.initQueryWrapper(mesXslRawMaterialCard, req.getParameterMap());
|
||||
Page<MesXslRawMaterialCard> page = new Page<>(pageNo, pageSize);
|
||||
IPage<MesXslRawMaterialCard> pageList = mesXslRawMaterialCardService.page(page, queryWrapper);
|
||||
return Result.OK(pageList);
|
||||
}
|
||||
|
||||
/**
|
||||
* 添加
|
||||
*/
|
||||
@AutoLog(value = "原材料卡片-添加")
|
||||
@Operation(summary = "原材料卡片-添加")
|
||||
@RequiresPermissions("xslmes:mes_xsl_raw_material_card:add")
|
||||
@PostMapping(value = "/add")
|
||||
public Result<String> add(@RequestBody MesXslRawMaterialCard mesXslRawMaterialCard) {
|
||||
mesXslRawMaterialCardService.save(mesXslRawMaterialCard);
|
||||
stompNotify.publishRawMaterialCardChanged("add", mesXslRawMaterialCard.getId());
|
||||
return Result.OK("添加成功!");
|
||||
}
|
||||
|
||||
/**
|
||||
* 编辑
|
||||
*/
|
||||
@AutoLog(value = "原材料卡片-编辑")
|
||||
@Operation(summary = "原材料卡片-编辑")
|
||||
@RequiresPermissions("xslmes:mes_xsl_raw_material_card:edit")
|
||||
@RequestMapping(value = "/edit", method = {RequestMethod.PUT, RequestMethod.POST})
|
||||
public Result<String> edit(@RequestBody MesXslRawMaterialCard mesXslRawMaterialCard) {
|
||||
mesXslRawMaterialCardService.updateById(mesXslRawMaterialCard);
|
||||
stompNotify.publishRawMaterialCardChanged("edit", mesXslRawMaterialCard.getId());
|
||||
return Result.OK("编辑成功!");
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置优先出库(列表可直接切换)
|
||||
*/
|
||||
@AutoLog(value = "原材料卡片-设置优先出库")
|
||||
@Operation(summary = "原材料卡片-设置优先出库")
|
||||
@RequiresPermissions("xslmes:mes_xsl_raw_material_card:edit")
|
||||
@PutMapping(value = "/updatePriority")
|
||||
public Result<String> updatePriority(@RequestParam String id, @RequestParam String priorityPickup) {
|
||||
MesXslRawMaterialCard card = new MesXslRawMaterialCard();
|
||||
card.setId(id);
|
||||
card.setPriorityPickup(priorityPickup);
|
||||
mesXslRawMaterialCardService.updateById(card);
|
||||
stompNotify.publishRawMaterialCardChanged("edit", id);
|
||||
return Result.OK("更新成功!");
|
||||
}
|
||||
|
||||
/**
|
||||
* 通过id删除
|
||||
*/
|
||||
@AutoLog(value = "原材料卡片-通过id删除")
|
||||
@Operation(summary = "原材料卡片-通过id删除")
|
||||
@RequiresPermissions("xslmes:mes_xsl_raw_material_card:delete")
|
||||
@DeleteMapping(value = "/delete")
|
||||
public Result<String> delete(@RequestParam(name = "id", required = true) String id) {
|
||||
mesXslRawMaterialCardService.removeById(id);
|
||||
stompNotify.publishRawMaterialCardChanged("delete", id);
|
||||
return Result.OK("删除成功!");
|
||||
}
|
||||
|
||||
/**
|
||||
* 批量删除
|
||||
*/
|
||||
@AutoLog(value = "原材料卡片-批量删除")
|
||||
@Operation(summary = "原材料卡片-批量删除")
|
||||
@RequiresPermissions("xslmes:mes_xsl_raw_material_card:deleteBatch")
|
||||
@DeleteMapping(value = "/deleteBatch")
|
||||
public Result<String> deleteBatch(@RequestParam(name = "ids", required = true) String ids) {
|
||||
this.mesXslRawMaterialCardService.removeByIds(Arrays.asList(ids.split(",")));
|
||||
stompNotify.publishRawMaterialCardChanged("batchDelete", ids);
|
||||
return Result.OK("批量删除成功!");
|
||||
}
|
||||
|
||||
/**
|
||||
* 通过id查询
|
||||
*/
|
||||
@Operation(summary = "原材料卡片-通过id查询")
|
||||
@GetMapping(value = "/queryById")
|
||||
public Result<MesXslRawMaterialCard> queryById(@RequestParam(name = "id", required = true) String id) {
|
||||
MesXslRawMaterialCard mesXslRawMaterialCard = mesXslRawMaterialCardService.getById(id);
|
||||
if (mesXslRawMaterialCard == null) {
|
||||
return Result.error("未找到对应数据");
|
||||
}
|
||||
return Result.OK(mesXslRawMaterialCard);
|
||||
}
|
||||
|
||||
/**
|
||||
* 导出excel
|
||||
*/
|
||||
@RequiresPermissions("xslmes:mes_xsl_raw_material_card:exportXls")
|
||||
@RequestMapping(value = "/exportXls")
|
||||
public ModelAndView exportXls(HttpServletRequest request, MesXslRawMaterialCard mesXslRawMaterialCard) {
|
||||
return super.exportXls(request, mesXslRawMaterialCard, MesXslRawMaterialCard.class, "原材料卡片");
|
||||
}
|
||||
|
||||
/**
|
||||
* 通过excel导入数据
|
||||
*/
|
||||
@RequiresPermissions("xslmes:mes_xsl_raw_material_card:importExcel")
|
||||
@RequestMapping(value = "/importExcel", method = RequestMethod.POST)
|
||||
public Result<?> importExcel(HttpServletRequest request, HttpServletResponse response) {
|
||||
return super.importExcel(request, response, MesXslRawMaterialCard.class);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,133 @@
|
||||
package org.jeecg.modules.xslmes.entity;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.Date;
|
||||
import java.math.BigDecimal;
|
||||
import com.baomidou.mybatisplus.annotation.IdType;
|
||||
import com.baomidou.mybatisplus.annotation.TableId;
|
||||
import com.baomidou.mybatisplus.annotation.TableName;
|
||||
import lombok.Data;
|
||||
import com.fasterxml.jackson.annotation.JsonFormat;
|
||||
import org.springframework.format.annotation.DateTimeFormat;
|
||||
import org.jeecgframework.poi.excel.annotation.Excel;
|
||||
import org.jeecg.common.aspect.annotation.Dict;
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.EqualsAndHashCode;
|
||||
import lombok.experimental.Accessors;
|
||||
|
||||
/**
|
||||
* @Description: 原材料卡片
|
||||
* @Author: jeecg-boot
|
||||
* @Date: 2026-05-11
|
||||
* @Version: V1.0
|
||||
*/
|
||||
@Data
|
||||
@TableName("mes_xsl_raw_material_card")
|
||||
@Accessors(chain = true)
|
||||
@EqualsAndHashCode(callSuper = false)
|
||||
@Schema(description = "原材料卡片")
|
||||
public class MesXslRawMaterialCard implements Serializable {
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
@TableId(type = IdType.ASSIGN_ID)
|
||||
@Schema(description = "主键")
|
||||
private String id;
|
||||
|
||||
@Excel(name = "条码", width = 20)
|
||||
@Schema(description = "条码")
|
||||
private String barcode;
|
||||
|
||||
@Excel(name = "批次号", width = 20)
|
||||
@Schema(description = "批次号")
|
||||
private String batchNo;
|
||||
|
||||
@Excel(name = "入场日期", width = 15, format = "yyyy-MM-dd")
|
||||
@JsonFormat(timezone = "GMT+8", pattern = "yyyy-MM-dd")
|
||||
@DateTimeFormat(pattern = "yyyy-MM-dd")
|
||||
@Schema(description = "入场日期")
|
||||
private Date entryDate;
|
||||
|
||||
@Schema(description = "物料ID")
|
||||
private String materialId;
|
||||
|
||||
@Excel(name = "物料名称", width = 20)
|
||||
@Schema(description = "物料名称")
|
||||
private String materialName;
|
||||
|
||||
@Excel(name = "物料描述", width = 30)
|
||||
@Schema(description = "物料描述")
|
||||
private String materialDesc;
|
||||
|
||||
@Schema(description = "供应商ID")
|
||||
private String supplierId;
|
||||
|
||||
@Excel(name = "供应商名称", width = 20)
|
||||
@Schema(description = "供应商名称")
|
||||
private String supplierName;
|
||||
|
||||
@Excel(name = "厂家物料名称", width = 20)
|
||||
@Schema(description = "厂家物料名称")
|
||||
private String manufacturerMaterialName;
|
||||
|
||||
@Excel(name = "保质期", width = 15)
|
||||
@Schema(description = "保质期")
|
||||
private String shelfLife;
|
||||
|
||||
@Excel(name = "总重", width = 12)
|
||||
@Schema(description = "总重")
|
||||
private BigDecimal totalWeight;
|
||||
|
||||
@Excel(name = "剩余重量", width = 12)
|
||||
@Schema(description = "剩余重量")
|
||||
private BigDecimal remainingWeight;
|
||||
|
||||
@Excel(name = "剩余数量", width = 12)
|
||||
@Schema(description = "剩余数量")
|
||||
private Integer remainingQuantity;
|
||||
|
||||
@Excel(name = "状态", width = 10, dicCode = "xslmes_card_status")
|
||||
@Dict(dicCode = "xslmes_card_status")
|
||||
@Schema(description = "状态(xslmes_card_status:1正常 0异常)")
|
||||
private String status;
|
||||
|
||||
@Excel(name = "检测结果", width = 12, dicCode = "xslmes_test_result")
|
||||
@Dict(dicCode = "xslmes_test_result")
|
||||
@Schema(description = "检测结果(xslmes_test_result:0未检 1合格 2不合格)")
|
||||
private String testResult;
|
||||
|
||||
@Excel(name = "库区", width = 15)
|
||||
@Schema(description = "库区")
|
||||
private String warehouseArea;
|
||||
|
||||
@Excel(name = "卸货人", width = 15)
|
||||
@Schema(description = "卸货人")
|
||||
private String unloadOperator;
|
||||
|
||||
@Excel(name = "优先出库", width = 10, dicCode = "yn")
|
||||
@Dict(dicCode = "yn")
|
||||
@Schema(description = "设置优先出库(yn:1是 0否)")
|
||||
private String priorityPickup;
|
||||
|
||||
/**创建人*/
|
||||
@Schema(description = "创建人")
|
||||
private String createBy;
|
||||
|
||||
/**创建日期*/
|
||||
@JsonFormat(timezone = "GMT+8", pattern = "yyyy-MM-dd HH:mm:ss")
|
||||
@DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
|
||||
@Schema(description = "创建日期")
|
||||
private Date createTime;
|
||||
|
||||
/**更新人*/
|
||||
@Schema(description = "更新人")
|
||||
private String updateBy;
|
||||
|
||||
/**更新日期*/
|
||||
@JsonFormat(timezone = "GMT+8", pattern = "yyyy-MM-dd HH:mm:ss")
|
||||
@DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
|
||||
@Schema(description = "更新日期")
|
||||
private Date updateTime;
|
||||
|
||||
@Schema(description = "租户ID")
|
||||
private Integer tenantId;
|
||||
}
|
||||
@@ -1,8 +1,8 @@
|
||||
package org.jeecg.modules.xslmes.entity;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.Date;
|
||||
import java.math.BigDecimal;
|
||||
import java.util.Date;
|
||||
import com.baomidou.mybatisplus.annotation.IdType;
|
||||
import com.baomidou.mybatisplus.annotation.TableId;
|
||||
import com.baomidou.mybatisplus.annotation.TableName;
|
||||
@@ -88,17 +88,19 @@ public class MesXslRawMaterialEntry implements Serializable {
|
||||
@Schema(description = "总重(KG)")
|
||||
private BigDecimal totalWeight;
|
||||
|
||||
@Excel(name = "总份数", width = 10)
|
||||
@Schema(description = "总份数")
|
||||
private Integer totalPortions;
|
||||
// 总份数 / 每份总重 / 每份包数:从 数值类型 升级为 字符串类型,
|
||||
// 支持桌面端「拆码明细」多行拼接保存(如 20/1/ 与 100/200/)。
|
||||
@Excel(name = "总份数", width = 12)
|
||||
@Schema(description = "总份数(支持多行拆码明细拼接,如 20/1/)")
|
||||
private String totalPortions;
|
||||
|
||||
@Excel(name = "每份总重(KG)", width = 12)
|
||||
@Schema(description = "每份总重(KG)")
|
||||
private BigDecimal portionWeight;
|
||||
@Excel(name = "每份总重(KG)", width = 14)
|
||||
@Schema(description = "每份总重(KG)(支持多行拆码明细拼接,如 100/200/)")
|
||||
private String portionWeight;
|
||||
|
||||
@Excel(name = "每份包数", width = 10)
|
||||
@Schema(description = "每份包数")
|
||||
private Integer portionPackages;
|
||||
@Excel(name = "每份包数", width = 12)
|
||||
@Schema(description = "每份包数(支持多行拆码明细拼接)")
|
||||
private String portionPackages;
|
||||
|
||||
@Excel(name = "检测结果", width = 12, dicCode = "xslmes_test_result")
|
||||
@Dict(dicCode = "xslmes_test_result")
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
package org.jeecg.modules.xslmes.mapper;
|
||||
|
||||
import org.jeecg.modules.xslmes.entity.MesXslRawMaterialCard;
|
||||
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||
|
||||
/**
|
||||
* @Description: 原材料卡片
|
||||
* @Author: jeecg-boot
|
||||
* @Date: 2026-05-11
|
||||
* @Version: V1.0
|
||||
*/
|
||||
public interface MesXslRawMaterialCardMapper extends BaseMapper<MesXslRawMaterialCard> {
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
|
||||
<mapper namespace="org.jeecg.modules.xslmes.mapper.MesXslRawMaterialCardMapper">
|
||||
</mapper>
|
||||
@@ -0,0 +1,13 @@
|
||||
package org.jeecg.modules.xslmes.service;
|
||||
|
||||
import org.jeecg.modules.xslmes.entity.MesXslRawMaterialCard;
|
||||
import com.baomidou.mybatisplus.extension.service.IService;
|
||||
|
||||
/**
|
||||
* @Description: 原材料卡片
|
||||
* @Author: jeecg-boot
|
||||
* @Date: 2026-05-11
|
||||
* @Version: V1.0
|
||||
*/
|
||||
public interface IMesXslRawMaterialCardService extends IService<MesXslRawMaterialCard> {
|
||||
}
|
||||
@@ -55,6 +55,11 @@ public class MesXslStompNotifyService {
|
||||
publish("/topic/sync/sys-categories", "SYS_CATEGORY_CHANGED", "categoryId", categoryId, action);
|
||||
}
|
||||
|
||||
/** 广播原材料卡片数据变更事件到 /topic/sync/mes-raw-material-cards */
|
||||
public void publishRawMaterialCardChanged(String action, String cardId) {
|
||||
publish("/topic/sync/mes-raw-material-cards", "RAW_MATERIAL_CARD_CHANGED", "cardId", cardId, action);
|
||||
}
|
||||
|
||||
/** 广播数据字典变更事件到 /topic/sync/sys-dicts */
|
||||
public void publishSysDictChanged(String action, String dictId) {
|
||||
publish("/topic/sync/sys-dicts", "SYS_DICT_CHANGED", "dictId", dictId, action);
|
||||
|
||||
@@ -0,0 +1,17 @@
|
||||
package org.jeecg.modules.xslmes.service.impl;
|
||||
|
||||
import org.jeecg.modules.xslmes.entity.MesXslRawMaterialCard;
|
||||
import org.jeecg.modules.xslmes.mapper.MesXslRawMaterialCardMapper;
|
||||
import org.jeecg.modules.xslmes.service.IMesXslRawMaterialCardService;
|
||||
import org.springframework.stereotype.Service;
|
||||
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
|
||||
|
||||
/**
|
||||
* @Description: 原材料卡片
|
||||
* @Author: jeecg-boot
|
||||
* @Date: 2026-05-11
|
||||
* @Version: V1.0
|
||||
*/
|
||||
@Service
|
||||
public class MesXslRawMaterialCardServiceImpl extends ServiceImpl<MesXslRawMaterialCardMapper, MesXslRawMaterialCard> implements IMesXslRawMaterialCardService {
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
-- ============================================================
|
||||
-- 原料入场记录表:「总份数 / 每份总重(KG) / 每份包数」由数值类型升级为字符串类型
|
||||
-- 背景:桌面端「拆码明细」支持多行(如 20/1,100/200),需要把拼接结果整体保存。
|
||||
-- 原始类型:total_portions int、portion_weight decimal(10,2)、portion_packages int
|
||||
-- 目标类型:均为 varchar(64)
|
||||
-- ============================================================
|
||||
|
||||
-- 1) total_portions → varchar(64)
|
||||
SET @col_type := (
|
||||
SELECT DATA_TYPE FROM information_schema.COLUMNS
|
||||
WHERE TABLE_SCHEMA = DATABASE()
|
||||
AND TABLE_NAME = 'mes_xsl_raw_material_entry'
|
||||
AND COLUMN_NAME = 'total_portions'
|
||||
);
|
||||
SET @ddl := IF(@col_type IS NULL OR @col_type IN ('varchar','char','text'),
|
||||
'SELECT 1',
|
||||
'ALTER TABLE `mes_xsl_raw_material_entry` MODIFY COLUMN `total_portions` varchar(64) DEFAULT NULL COMMENT ''总份数(支持拆码明细多行拼接,如 20/1/)'''
|
||||
);
|
||||
PREPARE s FROM @ddl; EXECUTE s; DEALLOCATE PREPARE s;
|
||||
|
||||
-- 2) portion_weight → varchar(64)
|
||||
SET @col_type := (
|
||||
SELECT DATA_TYPE FROM information_schema.COLUMNS
|
||||
WHERE TABLE_SCHEMA = DATABASE()
|
||||
AND TABLE_NAME = 'mes_xsl_raw_material_entry'
|
||||
AND COLUMN_NAME = 'portion_weight'
|
||||
);
|
||||
SET @ddl := IF(@col_type IS NULL OR @col_type IN ('varchar','char','text'),
|
||||
'SELECT 1',
|
||||
'ALTER TABLE `mes_xsl_raw_material_entry` MODIFY COLUMN `portion_weight` varchar(64) DEFAULT NULL COMMENT ''每份总重(KG)(支持拆码明细多行拼接,如 100/200/)'''
|
||||
);
|
||||
PREPARE s FROM @ddl; EXECUTE s; DEALLOCATE PREPARE s;
|
||||
|
||||
-- 3) portion_packages → varchar(64)
|
||||
SET @col_type := (
|
||||
SELECT DATA_TYPE FROM information_schema.COLUMNS
|
||||
WHERE TABLE_SCHEMA = DATABASE()
|
||||
AND TABLE_NAME = 'mes_xsl_raw_material_entry'
|
||||
AND COLUMN_NAME = 'portion_packages'
|
||||
);
|
||||
SET @ddl := IF(@col_type IS NULL OR @col_type IN ('varchar','char','text'),
|
||||
'SELECT 1',
|
||||
'ALTER TABLE `mes_xsl_raw_material_entry` MODIFY COLUMN `portion_packages` varchar(64) DEFAULT NULL COMMENT ''每份包数(支持拆码明细多行拼接)'''
|
||||
);
|
||||
PREPARE s FROM @ddl; EXECUTE s; DEALLOCATE PREPARE s;
|
||||
@@ -0,0 +1,121 @@
|
||||
-- 原材料卡片:建表 + 字典 + 菜单权限(幂等)
|
||||
|
||||
-- ===================== 1. 建表 =====================
|
||||
CREATE TABLE IF NOT EXISTS `mes_xsl_raw_material_card` (
|
||||
`id` varchar(32) NOT NULL COMMENT '主键',
|
||||
`barcode` varchar(100) DEFAULT NULL COMMENT '条码',
|
||||
`batch_no` varchar(100) DEFAULT NULL COMMENT '批次号',
|
||||
`entry_date` date DEFAULT NULL COMMENT '入场日期',
|
||||
`material_id` varchar(32) DEFAULT NULL COMMENT '物料ID',
|
||||
`material_name` varchar(200) DEFAULT NULL COMMENT '物料名称',
|
||||
`material_desc` text COMMENT '物料描述',
|
||||
`supplier_id` varchar(32) DEFAULT NULL COMMENT '供应商ID',
|
||||
`supplier_name` varchar(200) DEFAULT NULL COMMENT '供应商名称',
|
||||
`manufacturer_material_name` varchar(200) DEFAULT NULL COMMENT '厂家物料名称',
|
||||
`shelf_life` varchar(50) DEFAULT NULL COMMENT '保质期',
|
||||
`total_weight` decimal(12,3) DEFAULT NULL COMMENT '总重',
|
||||
`remaining_weight` decimal(12,3) DEFAULT NULL COMMENT '剩余重量',
|
||||
`remaining_quantity` int DEFAULT NULL COMMENT '剩余数量',
|
||||
`status` varchar(10) DEFAULT NULL COMMENT '状态(字典 xslmes_card_status:1正常 0异常)',
|
||||
`test_result` varchar(10) DEFAULT NULL COMMENT '检测结果(字典 xslmes_test_result:0未检 1合格 2不合格)',
|
||||
`warehouse_area` varchar(100) DEFAULT NULL COMMENT '库区',
|
||||
`unload_operator` varchar(100) DEFAULT NULL COMMENT '卸货人',
|
||||
`priority_pickup` varchar(2) DEFAULT '0' COMMENT '设置优先出库(字典 yn:1是 0否)',
|
||||
`create_by` varchar(50) DEFAULT NULL COMMENT '创建人',
|
||||
`create_time` datetime DEFAULT NULL COMMENT '创建时间',
|
||||
`update_by` varchar(50) DEFAULT NULL COMMENT '更新人',
|
||||
`update_time` datetime DEFAULT NULL COMMENT '更新时间',
|
||||
`tenant_id` int DEFAULT 1002 COMMENT '租户ID',
|
||||
PRIMARY KEY (`id`),
|
||||
KEY `idx_rmc_barcode` (`barcode`),
|
||||
KEY `idx_rmc_batch_no` (`batch_no`),
|
||||
KEY `idx_rmc_entry_date` (`entry_date`),
|
||||
KEY `idx_rmc_material_id` (`material_id`),
|
||||
KEY `idx_rmc_supplier_id` (`supplier_id`),
|
||||
KEY `idx_rmc_status` (`status`),
|
||||
KEY `idx_rmc_priority` (`priority_pickup`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='原材料卡片';
|
||||
|
||||
-- ===================== 2. 状态字典(xslmes_card_status)=====================
|
||||
INSERT INTO `sys_dict` (`id`, `dict_name`, `dict_code`, `description`, `del_flag`, `create_by`, `create_time`, `type`, `tenant_id`)
|
||||
SELECT REPLACE(UUID(), '-', ''), 'MES卡片状态', 'xslmes_card_status', '原材料卡片状态:正常/异常', 0, 'admin', NOW(), 0, 1002
|
||||
FROM DUAL WHERE NOT EXISTS (SELECT 1 FROM `sys_dict` WHERE `dict_code` = 'xslmes_card_status' AND `del_flag` = 0);
|
||||
|
||||
-- 字典项:正常
|
||||
INSERT INTO `sys_dict_item` (`id`, `dict_id`, `item_text`, `item_value`, `description`, `sort_order`, `status`, `create_by`, `create_time`)
|
||||
SELECT REPLACE(UUID(), '-', ''), d.id, '正常', '1', '正常', 1, 1, 'admin', NOW()
|
||||
FROM `sys_dict` d
|
||||
WHERE d.`dict_code` = 'xslmes_card_status'
|
||||
AND NOT EXISTS (SELECT 1 FROM `sys_dict_item` i WHERE i.`dict_id` = d.id AND i.`item_value` = '1');
|
||||
|
||||
-- 字典项:异常
|
||||
INSERT INTO `sys_dict_item` (`id`, `dict_id`, `item_text`, `item_value`, `description`, `sort_order`, `status`, `create_by`, `create_time`)
|
||||
SELECT REPLACE(UUID(), '-', ''), d.id, '异常', '0', '异常', 2, 1, 'admin', NOW()
|
||||
FROM `sys_dict` d
|
||||
WHERE d.`dict_code` = 'xslmes_card_status'
|
||||
AND NOT EXISTS (SELECT 1 FROM `sys_dict_item` i WHERE i.`dict_id` = d.id AND i.`item_value` = '0');
|
||||
|
||||
-- ===================== 3. 菜单权限(父菜单:MES XSL 1900000000000000300)=====================
|
||||
INSERT INTO `sys_permission` (`id`, `parent_id`, `name`, `url`, `component`, `is_route`, `component_name`, `redirect`, `menu_type`, `perms`, `perms_type`, `sort_no`, `always_show`, `icon`, `is_leaf`, `keep_alive`, `hidden`, `hide_tab`, `description`, `create_by`, `create_time`, `update_by`, `update_time`, `del_flag`, `rule_flag`, `status`, `internal_or_external`)
|
||||
SELECT '1900000000000000540', '1900000000000000300', '原材料卡片', '/xslmes/mesXslRawMaterialCard', 'xslmes/mesXslRawMaterialCard/MesXslRawMaterialCardList', 1, NULL, NULL, 1, NULL, '0', 12.00, 0, 'ant-design:credit-card-outlined', 0, 1, 0, 0, '原材料卡片', 'admin', NOW(), 'admin', NOW(), 0, 0, '1', 0
|
||||
FROM DUAL WHERE NOT EXISTS (SELECT 1 FROM `sys_permission` WHERE `id` = '1900000000000000540');
|
||||
|
||||
INSERT INTO `sys_permission` (`id`, `parent_id`, `name`, `url`, `component`, `is_route`, `component_name`, `redirect`, `menu_type`, `perms`, `perms_type`, `sort_no`, `always_show`, `icon`, `is_leaf`, `keep_alive`, `hidden`, `hide_tab`, `description`, `create_by`, `create_time`, `update_by`, `update_time`, `del_flag`, `rule_flag`, `status`, `internal_or_external`)
|
||||
SELECT '1900000000000000541', '1900000000000000540', '添加', NULL, NULL, 0, NULL, NULL, 2, 'xslmes:mes_xsl_raw_material_card:add', '1', 1.00, 0, NULL, 1, 0, 0, 0, NULL, 'admin', NOW(), 'admin', NOW(), 0, 0, '1', 0
|
||||
FROM DUAL WHERE NOT EXISTS (SELECT 1 FROM `sys_permission` WHERE `id` = '1900000000000000541');
|
||||
|
||||
INSERT INTO `sys_permission` (`id`, `parent_id`, `name`, `url`, `component`, `is_route`, `component_name`, `redirect`, `menu_type`, `perms`, `perms_type`, `sort_no`, `always_show`, `icon`, `is_leaf`, `keep_alive`, `hidden`, `hide_tab`, `description`, `create_by`, `create_time`, `update_by`, `update_time`, `del_flag`, `rule_flag`, `status`, `internal_or_external`)
|
||||
SELECT '1900000000000000542', '1900000000000000540', '编辑', NULL, NULL, 0, NULL, NULL, 2, 'xslmes:mes_xsl_raw_material_card:edit', '1', 2.00, 0, NULL, 1, 0, 0, 0, NULL, 'admin', NOW(), 'admin', NOW(), 0, 0, '1', 0
|
||||
FROM DUAL WHERE NOT EXISTS (SELECT 1 FROM `sys_permission` WHERE `id` = '1900000000000000542');
|
||||
|
||||
INSERT INTO `sys_permission` (`id`, `parent_id`, `name`, `url`, `component`, `is_route`, `component_name`, `redirect`, `menu_type`, `perms`, `perms_type`, `sort_no`, `always_show`, `icon`, `is_leaf`, `keep_alive`, `hidden`, `hide_tab`, `description`, `create_by`, `create_time`, `update_by`, `update_time`, `del_flag`, `rule_flag`, `status`, `internal_or_external`)
|
||||
SELECT '1900000000000000543', '1900000000000000540', '删除', NULL, NULL, 0, NULL, NULL, 2, 'xslmes:mes_xsl_raw_material_card:delete', '1', 3.00, 0, NULL, 1, 0, 0, 0, NULL, 'admin', NOW(), 'admin', NOW(), 0, 0, '1', 0
|
||||
FROM DUAL WHERE NOT EXISTS (SELECT 1 FROM `sys_permission` WHERE `id` = '1900000000000000543');
|
||||
|
||||
INSERT INTO `sys_permission` (`id`, `parent_id`, `name`, `url`, `component`, `is_route`, `component_name`, `redirect`, `menu_type`, `perms`, `perms_type`, `sort_no`, `always_show`, `icon`, `is_leaf`, `keep_alive`, `hidden`, `hide_tab`, `description`, `create_by`, `create_time`, `update_by`, `update_time`, `del_flag`, `rule_flag`, `status`, `internal_or_external`)
|
||||
SELECT '1900000000000000544', '1900000000000000540', '批量删除', NULL, NULL, 0, NULL, NULL, 2, 'xslmes:mes_xsl_raw_material_card:deleteBatch', '1', 4.00, 0, NULL, 1, 0, 0, 0, NULL, 'admin', NOW(), 'admin', NOW(), 0, 0, '1', 0
|
||||
FROM DUAL WHERE NOT EXISTS (SELECT 1 FROM `sys_permission` WHERE `id` = '1900000000000000544');
|
||||
|
||||
INSERT INTO `sys_permission` (`id`, `parent_id`, `name`, `url`, `component`, `is_route`, `component_name`, `redirect`, `menu_type`, `perms`, `perms_type`, `sort_no`, `always_show`, `icon`, `is_leaf`, `keep_alive`, `hidden`, `hide_tab`, `description`, `create_by`, `create_time`, `update_by`, `update_time`, `del_flag`, `rule_flag`, `status`, `internal_or_external`)
|
||||
SELECT '1900000000000000545', '1900000000000000540', '导出', NULL, NULL, 0, NULL, NULL, 2, 'xslmes:mes_xsl_raw_material_card:exportXls', '1', 5.00, 0, NULL, 1, 0, 0, 0, NULL, 'admin', NOW(), 'admin', NOW(), 0, 0, '1', 0
|
||||
FROM DUAL WHERE NOT EXISTS (SELECT 1 FROM `sys_permission` WHERE `id` = '1900000000000000545');
|
||||
|
||||
INSERT INTO `sys_permission` (`id`, `parent_id`, `name`, `url`, `component`, `is_route`, `component_name`, `redirect`, `menu_type`, `perms`, `perms_type`, `sort_no`, `always_show`, `icon`, `is_leaf`, `keep_alive`, `hidden`, `hide_tab`, `description`, `create_by`, `create_time`, `update_by`, `update_time`, `del_flag`, `rule_flag`, `status`, `internal_or_external`)
|
||||
SELECT '1900000000000000546', '1900000000000000540', '导入', NULL, NULL, 0, NULL, NULL, 2, 'xslmes:mes_xsl_raw_material_card:importExcel', '1', 6.00, 0, NULL, 1, 0, 0, 0, NULL, 'admin', NOW(), 'admin', NOW(), 0, 0, '1', 0
|
||||
FROM DUAL WHERE NOT EXISTS (SELECT 1 FROM `sys_permission` WHERE `id` = '1900000000000000546');
|
||||
|
||||
-- ===================== 4. 角色菜单授权(admin 角色)=====================
|
||||
INSERT INTO `sys_role_permission` (`id`, `role_id`, `permission_id`, `data_rule_ids`, `operate_date`, `operate_ip`)
|
||||
SELECT REPLACE(UUID(), '-', ''), r.id, '1900000000000000540', NULL, NOW(), '127.0.0.1'
|
||||
FROM `sys_role` r WHERE r.`role_code` = 'admin'
|
||||
AND NOT EXISTS (SELECT 1 FROM `sys_role_permission` rp WHERE rp.`role_id` = r.id AND rp.`permission_id` = '1900000000000000540');
|
||||
|
||||
INSERT INTO `sys_role_permission` (`id`, `role_id`, `permission_id`, `data_rule_ids`, `operate_date`, `operate_ip`)
|
||||
SELECT REPLACE(UUID(), '-', ''), r.id, '1900000000000000541', NULL, NOW(), '127.0.0.1'
|
||||
FROM `sys_role` r WHERE r.`role_code` = 'admin'
|
||||
AND NOT EXISTS (SELECT 1 FROM `sys_role_permission` rp WHERE rp.`role_id` = r.id AND rp.`permission_id` = '1900000000000000541');
|
||||
|
||||
INSERT INTO `sys_role_permission` (`id`, `role_id`, `permission_id`, `data_rule_ids`, `operate_date`, `operate_ip`)
|
||||
SELECT REPLACE(UUID(), '-', ''), r.id, '1900000000000000542', NULL, NOW(), '127.0.0.1'
|
||||
FROM `sys_role` r WHERE r.`role_code` = 'admin'
|
||||
AND NOT EXISTS (SELECT 1 FROM `sys_role_permission` rp WHERE rp.`role_id` = r.id AND rp.`permission_id` = '1900000000000000542');
|
||||
|
||||
INSERT INTO `sys_role_permission` (`id`, `role_id`, `permission_id`, `data_rule_ids`, `operate_date`, `operate_ip`)
|
||||
SELECT REPLACE(UUID(), '-', ''), r.id, '1900000000000000543', NULL, NOW(), '127.0.0.1'
|
||||
FROM `sys_role` r WHERE r.`role_code` = 'admin'
|
||||
AND NOT EXISTS (SELECT 1 FROM `sys_role_permission` rp WHERE rp.`role_id` = r.id AND rp.`permission_id` = '1900000000000000543');
|
||||
|
||||
INSERT INTO `sys_role_permission` (`id`, `role_id`, `permission_id`, `data_rule_ids`, `operate_date`, `operate_ip`)
|
||||
SELECT REPLACE(UUID(), '-', ''), r.id, '1900000000000000544', NULL, NOW(), '127.0.0.1'
|
||||
FROM `sys_role` r WHERE r.`role_code` = 'admin'
|
||||
AND NOT EXISTS (SELECT 1 FROM `sys_role_permission` rp WHERE rp.`role_id` = r.id AND rp.`permission_id` = '1900000000000000544');
|
||||
|
||||
INSERT INTO `sys_role_permission` (`id`, `role_id`, `permission_id`, `data_rule_ids`, `operate_date`, `operate_ip`)
|
||||
SELECT REPLACE(UUID(), '-', ''), r.id, '1900000000000000545', NULL, NOW(), '127.0.0.1'
|
||||
FROM `sys_role` r WHERE r.`role_code` = 'admin'
|
||||
AND NOT EXISTS (SELECT 1 FROM `sys_role_permission` rp WHERE rp.`role_id` = r.id AND rp.`permission_id` = '1900000000000000545');
|
||||
|
||||
INSERT INTO `sys_role_permission` (`id`, `role_id`, `permission_id`, `data_rule_ids`, `operate_date`, `operate_ip`)
|
||||
SELECT REPLACE(UUID(), '-', ''), r.id, '1900000000000000546', NULL, NOW(), '127.0.0.1'
|
||||
FROM `sys_role` r WHERE r.`role_code` = 'admin'
|
||||
AND NOT EXISTS (SELECT 1 FROM `sys_role_permission` rp WHERE rp.`role_id` = r.id AND rp.`permission_id` = '1900000000000000546');
|
||||
@@ -0,0 +1,49 @@
|
||||
import { defHttp } from '/@/utils/http/axios';
|
||||
import { useMessage } from '/@/hooks/web/useMessage';
|
||||
|
||||
const { createConfirm } = useMessage();
|
||||
|
||||
enum Api {
|
||||
list = '/xslmes/mesXslRawMaterialCard/list',
|
||||
save = '/xslmes/mesXslRawMaterialCard/add',
|
||||
edit = '/xslmes/mesXslRawMaterialCard/edit',
|
||||
deleteOne = '/xslmes/mesXslRawMaterialCard/delete',
|
||||
deleteBatch = '/xslmes/mesXslRawMaterialCard/deleteBatch',
|
||||
importExcel = '/xslmes/mesXslRawMaterialCard/importExcel',
|
||||
exportXls = '/xslmes/mesXslRawMaterialCard/exportXls',
|
||||
updatePriority = '/xslmes/mesXslRawMaterialCard/updatePriority',
|
||||
}
|
||||
|
||||
export const getExportUrl = Api.exportXls;
|
||||
export const getImportUrl = Api.importExcel;
|
||||
|
||||
export const list = (params) => defHttp.get({ url: Api.list, params });
|
||||
|
||||
export const deleteOne = (params, handleSuccess) => {
|
||||
return defHttp.delete({ url: Api.deleteOne, params }, { joinParamsToUrl: true }).then(() => {
|
||||
handleSuccess();
|
||||
});
|
||||
};
|
||||
|
||||
export const batchDelete = (params, handleSuccess) => {
|
||||
createConfirm({
|
||||
iconType: 'warning',
|
||||
title: '确认删除',
|
||||
content: '是否删除选中数据',
|
||||
okText: '确认',
|
||||
cancelText: '取消',
|
||||
onOk: () => {
|
||||
return defHttp.delete({ url: Api.deleteBatch, data: params }, { joinParamsToUrl: true }).then(() => {
|
||||
handleSuccess();
|
||||
});
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
export const saveOrUpdate = (params, isUpdate) => {
|
||||
let url = isUpdate ? Api.edit : Api.save;
|
||||
return defHttp.post({ url: url, params });
|
||||
};
|
||||
|
||||
export const updatePriority = (id: string, priorityPickup: string) =>
|
||||
defHttp.put({ url: Api.updatePriority, params: { id, priorityPickup } }, { joinParamsToUrl: true });
|
||||
@@ -0,0 +1,304 @@
|
||||
import { BasicColumn } from '/@/components/Table';
|
||||
import { FormSchema } from '/@/components/Table';
|
||||
import { render } from '/@/utils/common/renderUtils';
|
||||
|
||||
export const columns: BasicColumn[] = [
|
||||
{
|
||||
title: '条码',
|
||||
align: 'center',
|
||||
dataIndex: 'barcode',
|
||||
width: 160,
|
||||
},
|
||||
{
|
||||
title: '批次号',
|
||||
align: 'center',
|
||||
dataIndex: 'batchNo',
|
||||
width: 160,
|
||||
},
|
||||
{
|
||||
title: '入场日期',
|
||||
align: 'center',
|
||||
dataIndex: 'entryDate',
|
||||
width: 110,
|
||||
customRender: ({ text }) => {
|
||||
return !text ? '' : text.length > 10 ? text.substr(0, 10) : text;
|
||||
},
|
||||
},
|
||||
{
|
||||
title: '物料名称',
|
||||
align: 'center',
|
||||
dataIndex: 'materialName',
|
||||
width: 160,
|
||||
},
|
||||
{
|
||||
title: '供应商名称',
|
||||
align: 'center',
|
||||
dataIndex: 'supplierName',
|
||||
width: 150,
|
||||
},
|
||||
{
|
||||
title: '厂家物料名称',
|
||||
align: 'center',
|
||||
dataIndex: 'manufacturerMaterialName',
|
||||
width: 150,
|
||||
},
|
||||
{
|
||||
title: '保质期',
|
||||
align: 'center',
|
||||
dataIndex: 'shelfLife',
|
||||
width: 110,
|
||||
},
|
||||
{
|
||||
title: '总重',
|
||||
align: 'center',
|
||||
dataIndex: 'totalWeight',
|
||||
width: 90,
|
||||
},
|
||||
{
|
||||
title: '剩余重量',
|
||||
align: 'center',
|
||||
dataIndex: 'remainingWeight',
|
||||
width: 90,
|
||||
},
|
||||
{
|
||||
title: '剩余数量',
|
||||
align: 'center',
|
||||
dataIndex: 'remainingQuantity',
|
||||
width: 90,
|
||||
},
|
||||
{
|
||||
title: '状态',
|
||||
align: 'center',
|
||||
dataIndex: 'status_dictText',
|
||||
width: 80,
|
||||
},
|
||||
{
|
||||
title: '检测结果',
|
||||
align: 'center',
|
||||
dataIndex: 'testResult_dictText',
|
||||
width: 90,
|
||||
},
|
||||
{
|
||||
title: '库区',
|
||||
align: 'center',
|
||||
dataIndex: 'warehouseArea',
|
||||
width: 100,
|
||||
},
|
||||
{
|
||||
title: '卸货人',
|
||||
align: 'center',
|
||||
dataIndex: 'unloadOperator',
|
||||
width: 90,
|
||||
},
|
||||
{
|
||||
title: '优先出库',
|
||||
align: 'center',
|
||||
dataIndex: 'priorityPickup',
|
||||
width: 90,
|
||||
customRender: ({ text }) => {
|
||||
return render.renderSwitch(text, [{ text: '是', value: '1' }, { text: '否', value: '0' }]);
|
||||
},
|
||||
},
|
||||
{
|
||||
title: '创建时间',
|
||||
align: 'center',
|
||||
dataIndex: 'createTime',
|
||||
width: 160,
|
||||
},
|
||||
];
|
||||
|
||||
export const searchFormSchema: FormSchema[] = [
|
||||
{
|
||||
label: '条码',
|
||||
field: 'barcode',
|
||||
component: 'JInput',
|
||||
colProps: { span: 6 },
|
||||
},
|
||||
{
|
||||
label: '批次号',
|
||||
field: 'batchNo',
|
||||
component: 'JInput',
|
||||
colProps: { span: 6 },
|
||||
},
|
||||
{
|
||||
label: '物料名称',
|
||||
field: 'materialName',
|
||||
component: 'JInput',
|
||||
colProps: { span: 6 },
|
||||
},
|
||||
{
|
||||
label: '供应商名称',
|
||||
field: 'supplierName',
|
||||
component: 'JInput',
|
||||
colProps: { span: 6 },
|
||||
},
|
||||
{
|
||||
label: '状态',
|
||||
field: 'status',
|
||||
component: 'JDictSelectTag',
|
||||
componentProps: { dictCode: 'xslmes_card_status' },
|
||||
colProps: { span: 6 },
|
||||
},
|
||||
{
|
||||
label: '检测结果',
|
||||
field: 'testResult',
|
||||
component: 'JDictSelectTag',
|
||||
componentProps: { dictCode: 'xslmes_test_result' },
|
||||
colProps: { span: 6 },
|
||||
},
|
||||
{
|
||||
label: '入场日期',
|
||||
field: 'entryDate',
|
||||
component: 'RangePicker',
|
||||
componentProps: { showTime: false, valueFormat: 'YYYY-MM-DD' },
|
||||
colProps: { span: 8 },
|
||||
},
|
||||
];
|
||||
|
||||
export const formSchema: FormSchema[] = [
|
||||
{
|
||||
label: '',
|
||||
field: 'id',
|
||||
component: 'Input',
|
||||
show: false,
|
||||
},
|
||||
{
|
||||
label: '条码',
|
||||
field: 'barcode',
|
||||
component: 'Input',
|
||||
componentProps: { placeholder: '请输入条码' },
|
||||
colProps: { span: 12 },
|
||||
},
|
||||
{
|
||||
label: '批次号',
|
||||
field: 'batchNo',
|
||||
component: 'Input',
|
||||
componentProps: { placeholder: '请输入批次号' },
|
||||
colProps: { span: 12 },
|
||||
},
|
||||
{
|
||||
label: '入场日期',
|
||||
field: 'entryDate',
|
||||
component: 'DatePicker',
|
||||
componentProps: { showTime: false, valueFormat: 'YYYY-MM-DD', placeholder: '请选择入场日期' },
|
||||
colProps: { span: 12 },
|
||||
},
|
||||
{
|
||||
label: '物料ID',
|
||||
field: 'materialId',
|
||||
component: 'Input',
|
||||
show: false,
|
||||
},
|
||||
{
|
||||
label: '物料名称',
|
||||
field: 'materialName',
|
||||
component: 'Input',
|
||||
componentProps: { placeholder: '请输入物料名称' },
|
||||
colProps: { span: 12 },
|
||||
},
|
||||
{
|
||||
label: '物料描述',
|
||||
field: 'materialDesc',
|
||||
component: 'InputTextArea',
|
||||
componentProps: { placeholder: '请输入物料描述', rows: 2 },
|
||||
colProps: { span: 24 },
|
||||
},
|
||||
{
|
||||
label: '供应商ID',
|
||||
field: 'supplierId',
|
||||
component: 'Input',
|
||||
show: false,
|
||||
},
|
||||
{
|
||||
label: '供应商名称',
|
||||
field: 'supplierName',
|
||||
component: 'Input',
|
||||
componentProps: { placeholder: '请输入供应商名称' },
|
||||
colProps: { span: 12 },
|
||||
},
|
||||
{
|
||||
label: '厂家物料名称',
|
||||
field: 'manufacturerMaterialName',
|
||||
component: 'Input',
|
||||
componentProps: { placeholder: '请输入厂家物料名称' },
|
||||
colProps: { span: 12 },
|
||||
},
|
||||
{
|
||||
label: '保质期',
|
||||
field: 'shelfLife',
|
||||
component: 'Input',
|
||||
componentProps: { placeholder: '请输入保质期' },
|
||||
colProps: { span: 12 },
|
||||
},
|
||||
{
|
||||
label: '总重',
|
||||
field: 'totalWeight',
|
||||
component: 'InputNumber',
|
||||
componentProps: { placeholder: '请输入总重', precision: 3 },
|
||||
colProps: { span: 12 },
|
||||
},
|
||||
{
|
||||
label: '剩余重量',
|
||||
field: 'remainingWeight',
|
||||
component: 'InputNumber',
|
||||
componentProps: { placeholder: '请输入剩余重量', precision: 3 },
|
||||
colProps: { span: 12 },
|
||||
},
|
||||
{
|
||||
label: '剩余数量',
|
||||
field: 'remainingQuantity',
|
||||
component: 'InputNumber',
|
||||
componentProps: { placeholder: '请输入剩余数量' },
|
||||
colProps: { span: 12 },
|
||||
},
|
||||
{
|
||||
label: '状态',
|
||||
field: 'status',
|
||||
component: 'JDictSelectTag',
|
||||
componentProps: { dictCode: 'xslmes_card_status', placeholder: '请选择状态' },
|
||||
colProps: { span: 12 },
|
||||
},
|
||||
{
|
||||
label: '检测结果',
|
||||
field: 'testResult',
|
||||
component: 'JDictSelectTag',
|
||||
componentProps: { dictCode: 'xslmes_test_result', placeholder: '请选择检测结果' },
|
||||
colProps: { span: 12 },
|
||||
},
|
||||
{
|
||||
label: '库区',
|
||||
field: 'warehouseArea',
|
||||
component: 'Input',
|
||||
componentProps: { placeholder: '请输入库区' },
|
||||
colProps: { span: 12 },
|
||||
},
|
||||
{
|
||||
label: '卸货人',
|
||||
field: 'unloadOperator',
|
||||
component: 'Input',
|
||||
componentProps: { placeholder: '请输入卸货人' },
|
||||
colProps: { span: 12 },
|
||||
},
|
||||
{
|
||||
label: '优先出库',
|
||||
field: 'priorityPickup',
|
||||
component: 'JSwitch',
|
||||
componentProps: { options: ['1', '0'] },
|
||||
colProps: { span: 12 },
|
||||
},
|
||||
];
|
||||
|
||||
export const superQuerySchema = {
|
||||
barcode: { title: '条码', order: 0, view: 'text' },
|
||||
batchNo: { title: '批次号', order: 1, view: 'text' },
|
||||
entryDate: { title: '入场日期', order: 2, view: 'date' },
|
||||
materialName: { title: '物料名称', order: 3, view: 'text' },
|
||||
supplierName: { title: '供应商名称', order: 4, view: 'text' },
|
||||
totalWeight: { title: '总重', order: 5, view: 'number' },
|
||||
remainingWeight: { title: '剩余重量', order: 6, view: 'number' },
|
||||
remainingQuantity: { title: '剩余数量', order: 7, view: 'number' },
|
||||
status: { title: '状态', order: 8, view: 'list', dictCode: 'xslmes_card_status' },
|
||||
testResult: { title: '检测结果', order: 9, view: 'list', dictCode: 'xslmes_test_result' },
|
||||
warehouseArea: { title: '库区', order: 10, view: 'text' },
|
||||
createTime: { title: '创建时间', order: 11, view: 'datetime' },
|
||||
};
|
||||
@@ -0,0 +1,172 @@
|
||||
<template>
|
||||
<div>
|
||||
<!--引用表格-->
|
||||
<BasicTable @register="registerTable" :rowSelection="rowSelection">
|
||||
<!--插槽:table标题-->
|
||||
<template #tableTitle>
|
||||
<a-button type="primary" v-auth="'xslmes:mes_xsl_raw_material_card:add'" @click="handleAdd" preIcon="ant-design:plus-outlined"> 新增</a-button>
|
||||
<a-button type="primary" v-auth="'xslmes:mes_xsl_raw_material_card:exportXls'" preIcon="ant-design:export-outlined" @click="onExportXls"> 导出</a-button>
|
||||
<j-upload-button type="primary" v-auth="'xslmes:mes_xsl_raw_material_card:importExcel'" preIcon="ant-design:import-outlined" @click="onImportXls">导入</j-upload-button>
|
||||
<a-dropdown v-if="selectedRowKeys.length > 0">
|
||||
<template #overlay>
|
||||
<a-menu>
|
||||
<a-menu-item key="1" @click="batchHandleDelete">
|
||||
<Icon icon="ant-design:delete-outlined" />
|
||||
删除
|
||||
</a-menu-item>
|
||||
</a-menu>
|
||||
</template>
|
||||
<a-button v-auth="'xslmes:mes_xsl_raw_material_card:deleteBatch'">批量操作
|
||||
<Icon icon="mdi:chevron-down" />
|
||||
</a-button>
|
||||
</a-dropdown>
|
||||
<!-- 高级查询 -->
|
||||
<super-query :config="superQueryConfig" @search="handleSuperQuery" />
|
||||
</template>
|
||||
<!--操作栏-->
|
||||
<template #action="{ record }">
|
||||
<TableAction :actions="getTableAction(record)" :dropDownActions="getDropDownAction(record)" />
|
||||
</template>
|
||||
<!--字段回显插槽-->
|
||||
<template v-slot:bodyCell="{ column, record }">
|
||||
<!-- 优先出库:列表直接切换 -->
|
||||
<template v-if="column.dataIndex === 'priorityPickup'">
|
||||
<a-switch
|
||||
:checked="record.priorityPickup === '1'"
|
||||
checked-children="是"
|
||||
un-checked-children="否"
|
||||
@change="(checked) => handlePriorityChange(record, checked)"
|
||||
/>
|
||||
</template>
|
||||
</template>
|
||||
</BasicTable>
|
||||
<!-- 表单区域 -->
|
||||
<MesXslRawMaterialCardModal @register="registerModal" @success="handleSuccess" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" name="xslmes-mesXslRawMaterialCard" setup>
|
||||
import { ref, reactive } from 'vue';
|
||||
import { BasicTable, useTable, TableAction } from '/@/components/Table';
|
||||
import { useModal } from '/@/components/Modal';
|
||||
import { useListPage } from '/@/hooks/system/useListPage';
|
||||
import MesXslRawMaterialCardModal from './components/MesXslRawMaterialCardModal.vue';
|
||||
import { columns, searchFormSchema, superQuerySchema } from './MesXslRawMaterialCard.data';
|
||||
import { list, deleteOne, batchDelete, getImportUrl, getExportUrl, updatePriority } from './MesXslRawMaterialCard.api';
|
||||
import { useMessage } from '/@/hooks/web/useMessage';
|
||||
|
||||
const { createMessage } = useMessage();
|
||||
const queryParam = reactive<any>({});
|
||||
const checkedKeys = ref<Array<string | number>>([]);
|
||||
const [registerModal, { openModal }] = useModal();
|
||||
const { prefixCls, tableContext, onExportXls, onImportXls } = useListPage({
|
||||
tableProps: {
|
||||
title: '原材料卡片',
|
||||
api: list,
|
||||
columns,
|
||||
canResize: true,
|
||||
formConfig: {
|
||||
schemas: searchFormSchema,
|
||||
autoSubmitOnEnter: true,
|
||||
showAdvancedButton: true,
|
||||
fieldMapToTime: [
|
||||
['entryDate', ['entryDate_begin', 'entryDate_end'], 'YYYY-MM-DD'],
|
||||
],
|
||||
},
|
||||
actionColumn: {
|
||||
width: 120,
|
||||
fixed: 'right',
|
||||
},
|
||||
beforeFetch: (params) => {
|
||||
return Object.assign(params, queryParam);
|
||||
},
|
||||
},
|
||||
exportConfig: {
|
||||
name: '原材料卡片',
|
||||
url: getExportUrl,
|
||||
params: queryParam,
|
||||
},
|
||||
importConfig: {
|
||||
url: getImportUrl,
|
||||
success: handleSuccess,
|
||||
},
|
||||
});
|
||||
|
||||
const [registerTable, { reload }, { rowSelection, selectedRowKeys }] = tableContext;
|
||||
const superQueryConfig = reactive(superQuerySchema);
|
||||
|
||||
function handleSuperQuery(params) {
|
||||
Object.keys(params).map((k) => {
|
||||
queryParam[k] = params[k];
|
||||
});
|
||||
reload();
|
||||
}
|
||||
|
||||
function handleAdd() {
|
||||
openModal(true, { isUpdate: false, showFooter: true });
|
||||
}
|
||||
|
||||
function handleEdit(record: Recordable) {
|
||||
openModal(true, { record, isUpdate: true, showFooter: true });
|
||||
}
|
||||
|
||||
function handleDetail(record: Recordable) {
|
||||
openModal(true, { record, isUpdate: true, showFooter: false });
|
||||
}
|
||||
|
||||
async function handleDelete(record) {
|
||||
await deleteOne({ id: record.id }, handleSuccess);
|
||||
}
|
||||
|
||||
async function batchHandleDelete() {
|
||||
await batchDelete({ ids: selectedRowKeys.value }, handleSuccess);
|
||||
}
|
||||
|
||||
async function handlePriorityChange(record, checked: boolean) {
|
||||
try {
|
||||
await updatePriority(record.id, checked ? '1' : '0');
|
||||
record.priorityPickup = checked ? '1' : '0';
|
||||
createMessage.success('优先出库设置已更新');
|
||||
} catch {
|
||||
createMessage.error('更新失败,请重试');
|
||||
}
|
||||
}
|
||||
|
||||
function handleSuccess() {
|
||||
(selectedRowKeys.value = []) && reload();
|
||||
}
|
||||
|
||||
function getTableAction(record) {
|
||||
return [
|
||||
{
|
||||
label: '编辑',
|
||||
onClick: handleEdit.bind(null, record),
|
||||
auth: 'xslmes:mes_xsl_raw_material_card:edit',
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
function getDropDownAction(record) {
|
||||
return [
|
||||
{
|
||||
label: '详情',
|
||||
onClick: handleDetail.bind(null, record),
|
||||
},
|
||||
{
|
||||
label: '删除',
|
||||
popConfirm: {
|
||||
title: '是否确认删除',
|
||||
confirm: handleDelete.bind(null, record),
|
||||
placement: 'topLeft',
|
||||
},
|
||||
auth: 'xslmes:mes_xsl_raw_material_card:delete',
|
||||
},
|
||||
];
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
:deep(.ant-picker-range) {
|
||||
width: 100%;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,66 @@
|
||||
<template>
|
||||
<BasicModal v-bind="$attrs" @register="registerModal" destroyOnClose :title="title" :width="1000" @ok="handleSubmit">
|
||||
<BasicForm @register="registerForm" name="MesXslRawMaterialCardForm" />
|
||||
</BasicModal>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { ref, computed, unref } from 'vue';
|
||||
import { BasicModal, useModalInner } from '/@/components/Modal';
|
||||
import { BasicForm, useForm } from '/@/components/Form/index';
|
||||
import { formSchema } from '../MesXslRawMaterialCard.data';
|
||||
import { saveOrUpdate } from '../MesXslRawMaterialCard.api';
|
||||
|
||||
const emit = defineEmits(['register', 'success']);
|
||||
const isUpdate = ref(true);
|
||||
const isDetail = ref(false);
|
||||
|
||||
const [registerForm, { setProps, resetFields, setFieldsValue, validate, scrollToField }] = useForm({
|
||||
labelWidth: 120,
|
||||
schemas: formSchema,
|
||||
showActionButtonGroup: false,
|
||||
baseColProps: { span: 12 },
|
||||
});
|
||||
|
||||
const [registerModal, { setModalProps, closeModal }] = useModalInner(async (data) => {
|
||||
await resetFields();
|
||||
setModalProps({ confirmLoading: false, showCancelBtn: !!data?.showFooter, showOkBtn: !!data?.showFooter });
|
||||
isUpdate.value = !!data?.isUpdate;
|
||||
isDetail.value = !!data?.showFooter;
|
||||
if (unref(isUpdate)) {
|
||||
await setFieldsValue({ ...data.record });
|
||||
}
|
||||
setProps({ disabled: !data?.showFooter });
|
||||
});
|
||||
|
||||
const title = computed(() => (!unref(isUpdate) ? '新增' : !unref(isDetail) ? '详情' : '编辑'));
|
||||
|
||||
async function handleSubmit(v) {
|
||||
try {
|
||||
let values = await validate();
|
||||
setModalProps({ confirmLoading: true });
|
||||
await saveOrUpdate(values, isUpdate.value);
|
||||
closeModal();
|
||||
emit('success');
|
||||
} catch ({ errorFields }: any) {
|
||||
if (errorFields) {
|
||||
const firstField = errorFields[0];
|
||||
if (firstField) {
|
||||
scrollToField(firstField.name, { behavior: 'smooth', block: 'center' });
|
||||
}
|
||||
}
|
||||
return Promise.reject(errorFields);
|
||||
} finally {
|
||||
setModalProps({ confirmLoading: false });
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
:deep(.ant-input-number) {
|
||||
width: 100%;
|
||||
}
|
||||
:deep(.ant-calendar-picker) {
|
||||
width: 100%;
|
||||
}
|
||||
</style>
|
||||
@@ -156,22 +156,23 @@ export const formSchema: FormSchema[] = [
|
||||
componentProps: { min: 0, precision: 2, placeholder: '请输入总重', style: { width: '100%' } },
|
||||
},
|
||||
{
|
||||
// 字段升级为字符串类型,支持桌面端拆码明细多行拼接(如 20/1/)
|
||||
label: '总份数',
|
||||
field: 'totalPortions',
|
||||
component: 'InputNumber',
|
||||
componentProps: { min: 0, placeholder: '请输入总份数', style: { width: '100%' } },
|
||||
component: 'Input',
|
||||
componentProps: { placeholder: '请输入总份数(多行明细用 / 拼接)', style: { width: '100%' } },
|
||||
},
|
||||
{
|
||||
label: '每份总重(KG)',
|
||||
field: 'portionWeight',
|
||||
component: 'InputNumber',
|
||||
componentProps: { min: 0, precision: 2, placeholder: '请输入每份总重', style: { width: '100%' } },
|
||||
component: 'Input',
|
||||
componentProps: { placeholder: '请输入每份总重(多行明细用 / 拼接)', style: { width: '100%' } },
|
||||
},
|
||||
{
|
||||
label: '每份包数',
|
||||
field: 'portionPackages',
|
||||
component: 'InputNumber',
|
||||
componentProps: { min: 0, placeholder: '请输入每份包数', style: { width: '100%' } },
|
||||
component: 'Input',
|
||||
componentProps: { placeholder: '请输入每份包数(多行明细用 / 拼接)', style: { width: '100%' } },
|
||||
},
|
||||
{
|
||||
label: '检测结果',
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
using Prism.Events;
|
||||
|
||||
namespace YY.Admin.Core.Events;
|
||||
|
||||
public class RawMaterialCardChangedPayload
|
||||
{
|
||||
public string Action { get; set; } = string.Empty;
|
||||
public string? CardId { get; set; }
|
||||
}
|
||||
|
||||
public class RawMaterialCardChangedEvent : PubSubEvent<RawMaterialCardChangedPayload>
|
||||
{
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
using YY.Admin.Core.Entity;
|
||||
|
||||
namespace YY.Admin.Core.Services;
|
||||
|
||||
public record RawMaterialCardPageResult(List<MesXslRawMaterialCard> Records, long Total, int Current, int Size);
|
||||
|
||||
public interface IRawMaterialCardService
|
||||
{
|
||||
Task<RawMaterialCardPageResult> PageAsync(int pageNo, int pageSize,
|
||||
string? barcode = null, string? batchNo = null, string? materialName = null,
|
||||
string? supplierName = null, string? status = null, CancellationToken ct = default);
|
||||
Task<MesXslRawMaterialCard?> GetByIdAsync(string id, CancellationToken ct = default);
|
||||
Task<bool> AddAsync(MesXslRawMaterialCard card, CancellationToken ct = default);
|
||||
Task<bool> EditAsync(MesXslRawMaterialCard card, CancellationToken ct = default);
|
||||
Task<bool> DeleteAsync(string id, CancellationToken ct = default);
|
||||
Task<bool> DeleteBatchAsync(string ids, CancellationToken ct = default);
|
||||
Task<bool> UpdatePriorityAsync(string id, string priorityPickup, CancellationToken ct = default);
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
namespace YY.Admin.Core.Entity;
|
||||
|
||||
public class MesXslRawMaterialCard
|
||||
{
|
||||
public string? Id { get; set; }
|
||||
public string? Barcode { get; set; }
|
||||
public string? BatchNo { get; set; }
|
||||
public DateTime? EntryDate { get; set; }
|
||||
public string? MaterialId { get; set; }
|
||||
public string? MaterialName { get; set; }
|
||||
public string? MaterialDesc { get; set; }
|
||||
public string? SupplierId { get; set; }
|
||||
public string? SupplierName { get; set; }
|
||||
public string? ManufacturerMaterialName { get; set; }
|
||||
public string? ShelfLife { get; set; }
|
||||
public decimal? TotalWeight { get; set; }
|
||||
public decimal? RemainingWeight { get; set; }
|
||||
public int? RemainingQuantity { get; set; }
|
||||
|
||||
/// <summary>状态:1正常 0异常(字典 xslmes_card_status)</summary>
|
||||
public string? Status { get; set; }
|
||||
|
||||
/// <summary>检测结果:0未检 1合格 2不合格(字典 xslmes_test_result)</summary>
|
||||
public string? TestResult { get; set; }
|
||||
|
||||
public string? WarehouseArea { get; set; }
|
||||
public string? UnloadOperator { get; set; }
|
||||
|
||||
/// <summary>优先出库:1是 0否</summary>
|
||||
public string? PriorityPickup { get; set; }
|
||||
|
||||
public string? CreateBy { get; set; }
|
||||
public DateTime? CreateTime { get; set; }
|
||||
public string? UpdateBy { get; set; }
|
||||
public DateTime? UpdateTime { get; set; }
|
||||
public int? TenantId { get; set; }
|
||||
|
||||
public string StatusText => Status switch
|
||||
{
|
||||
"1" => "正常",
|
||||
"0" => "异常",
|
||||
_ => Status ?? ""
|
||||
};
|
||||
|
||||
public string TestResultText => TestResult switch
|
||||
{
|
||||
"0" => "未检",
|
||||
"1" => "合格",
|
||||
"2" => "不合格",
|
||||
_ => TestResult ?? ""
|
||||
};
|
||||
|
||||
public bool PriorityPickupBool => PriorityPickup == "1";
|
||||
public string PriorityPickupText => PriorityPickup == "1" ? "是" : "否";
|
||||
|
||||
public string EntryDateText => EntryDate.HasValue ? EntryDate.Value.ToString("yyyy-MM-dd") : "";
|
||||
}
|
||||
@@ -17,9 +17,12 @@ public class MesXslRawMaterialEntry
|
||||
public string? ManufacturerMaterialName { get; set; }
|
||||
public string? ShelfLife { get; set; }
|
||||
public double? TotalWeight { get; set; }
|
||||
public int? TotalPortions { get; set; }
|
||||
public double? PortionWeight { get; set; }
|
||||
public int? PortionPackages { get; set; }
|
||||
|
||||
// 总份数 / 每份总重 / 每份包数:与后端同步升级为字符串,
|
||||
// 用于持久化「拆码明细」多行拼接(如 20/1/、100/200/)。
|
||||
public string? TotalPortions { get; set; }
|
||||
public string? PortionWeight { get; set; }
|
||||
public string? PortionPackages { get; set; }
|
||||
|
||||
/// <summary>检测结果:0未检 1合格 2不合格</summary>
|
||||
public string? TestResult { get; set; }
|
||||
|
||||
@@ -42,6 +42,8 @@ public class SysMenuSeedData : ISqlSugarEntitySeedData<SysMenu>
|
||||
new SysMenu{ Id=1300150010701, Pid=1300150000101, Title="原料入场记录", Path="/xslmes/mesXslRawMaterialEntry", Name="mesXslRawMaterialEntry", Component="RawMaterialEntryListView", Icon="", Type=MenuTypeEnum.Menu, CreateTime=DateTime.Parse("2022-02-10 00:00:00"), OrderNo=106 },
|
||||
// 新增原料入场记录(独立页面)
|
||||
new SysMenu{ Id=1300150010801, Pid=1300150000101, Title="新增原料入场记录", Path="/xslmes/rawMaterialEntryOperation", Name="rawMaterialEntryOperation", Component="RawMaterialEntryOperationView", Icon="", Type=MenuTypeEnum.Menu, CreateTime=DateTime.Parse("2022-02-10 00:00:00"), OrderNo=107 },
|
||||
// 原材料卡片
|
||||
new SysMenu{ Id=1300150010901, Pid=1300150000101, Title="原材料卡片", Path="/xslmes/mesXslRawMaterialCard", Name="mesXslRawMaterialCard", Component="RawMaterialCardListView", Icon="", Type=MenuTypeEnum.Menu, CreateTime=DateTime.Parse("2022-02-10 00:00:00"), OrderNo=108 },
|
||||
|
||||
#endregion
|
||||
|
||||
|
||||
@@ -215,6 +215,7 @@ public class SysTenantMenuSeedData : ISqlSugarEntitySeedData<SysTenantMenu>
|
||||
new SysTenantMenu(){ TenantId=1300000000001,MenuId= 1300300040501 },
|
||||
new SysTenantMenu(){ TenantId=1300000000001,MenuId = 1300200010201},
|
||||
new SysTenantMenu() { TenantId = 1300000000001,MenuId = 1300600040101},
|
||||
new SysTenantMenu(){ TenantId=1300000000001, MenuId=1300150010901 },
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,735 @@
|
||||
using Microsoft.Extensions.Configuration;
|
||||
using System.Net.Http;
|
||||
using System.IO;
|
||||
using System.Text;
|
||||
using System.Text.Json;
|
||||
using System.Text.Json.Serialization;
|
||||
using System.Web;
|
||||
using Prism.Events;
|
||||
using YY.Admin.Core;
|
||||
using YY.Admin.Core.Entity;
|
||||
using YY.Admin.Core.Events;
|
||||
using YY.Admin.Core.Services;
|
||||
|
||||
namespace YY.Admin.Services.Service.RawMaterialCard;
|
||||
|
||||
public class RawMaterialCardService : IRawMaterialCardService, 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 _pendingOpsFilePath;
|
||||
private readonly string _cacheFilePath;
|
||||
private List<RawMaterialCardPendingOperation> _pendingOps = new();
|
||||
private List<MesXslRawMaterialCard> _localCache = new();
|
||||
|
||||
private static readonly JsonSerializerOptions _jsonOpts = new()
|
||||
{
|
||||
PropertyNameCaseInsensitive = true,
|
||||
PropertyNamingPolicy = JsonNamingPolicy.CamelCase,
|
||||
Converters = { new NullableDateTimeJsonConverter() }
|
||||
};
|
||||
|
||||
public RawMaterialCardService(
|
||||
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);
|
||||
_pendingOpsFilePath = Path.Combine(appDataDir, "mes-xsl-raw-material-card-pending-ops.json");
|
||||
_cacheFilePath = Path.Combine(appDataDir, "mes-xsl-raw-material-card-cache.json");
|
||||
|
||||
LoadPendingOpsFromDisk();
|
||||
LoadCacheFromDisk();
|
||||
_logger.Information($"[原材料卡片] 服务初始化完成,缓存={_localCache.Count},待上传={_pendingOps.Count},在线={_networkMonitor.IsOnline}");
|
||||
|
||||
_networkMonitor.StatusChanged += OnNetworkStatusChanged;
|
||||
if (_networkMonitor.IsOnline)
|
||||
{
|
||||
_ = Task.Run(() => SyncAfterReconnectAsync(CancellationToken.None));
|
||||
}
|
||||
}
|
||||
|
||||
private const int MaxPendingRetries = 5;
|
||||
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<RawMaterialCardPageResult> PageAsync(int pageNo, int pageSize,
|
||||
string? barcode = null, string? batchNo = null, string? materialName = null,
|
||||
string? supplierName = null, string? status = null, CancellationToken ct = default)
|
||||
{
|
||||
List<MesXslRawMaterialCard>? source = null;
|
||||
if (_networkMonitor.IsOnline)
|
||||
{
|
||||
try
|
||||
{
|
||||
source = await FetchRemoteListAsync(ct).ConfigureAwait(false);
|
||||
lock (_cacheLock)
|
||||
{
|
||||
_localCache = source.Select(Clone).ToList();
|
||||
SaveCacheToDiskUnsafe();
|
||||
}
|
||||
_logger.Information($"[原材料卡片] 远端拉取成功 count={source.Count}");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
source = null;
|
||||
_logger.Warning($"[原材料卡片] 远端拉取失败,回退缓存:{ex.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
lock (_cacheLock)
|
||||
{
|
||||
source ??= _localCache.Select(Clone).ToList();
|
||||
source = ApplyPendingOpsSnapshotUnsafe(source);
|
||||
}
|
||||
|
||||
var filtered = ApplyFilters(source, barcode, batchNo, materialName, supplierName, status);
|
||||
var total = filtered.Count;
|
||||
var records = filtered.Skip(Math.Max(0, (pageNo - 1) * pageSize)).Take(pageSize).ToList();
|
||||
return new RawMaterialCardPageResult(records, total, pageNo, pageSize);
|
||||
}
|
||||
|
||||
public async Task<MesXslRawMaterialCard?> GetByIdAsync(string id, CancellationToken ct = default)
|
||||
{
|
||||
if (_networkMonitor.IsOnline)
|
||||
{
|
||||
try
|
||||
{
|
||||
var url = $"{BaseUrl}/xslmes/mesXslRawMaterialCard/anon/queryById?id={Uri.EscapeDataString(id)}&tenantId={DefaultTenantId}";
|
||||
using var client = CreateClient();
|
||||
var resp = await client.GetAsync(url, ct).ConfigureAwait(false);
|
||||
if (!resp.IsSuccessStatusCode) return null;
|
||||
var json = await resp.Content.ReadAsStringAsync(ct).ConfigureAwait(false);
|
||||
using var doc = JsonDocument.Parse(json);
|
||||
if (!doc.RootElement.TryGetProperty("result", out var resultEl)) return null;
|
||||
return resultEl.Deserialize<MesXslRawMaterialCard>(_jsonOpts);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.Warning($"[原材料卡片] 远端查询异常,回退缓存 id={id}: {ex.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
lock (_cacheLock)
|
||||
{
|
||||
return _localCache.FirstOrDefault(c => string.Equals(c.Id, id, StringComparison.OrdinalIgnoreCase)) is { } found
|
||||
? Clone(found) : null;
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<bool> AddAsync(MesXslRawMaterialCard card, CancellationToken ct = default)
|
||||
{
|
||||
if (!card.TenantId.HasValue || card.TenantId.Value <= 0)
|
||||
card.TenantId = DefaultTenantId;
|
||||
|
||||
var local = Clone(card);
|
||||
if (string.IsNullOrWhiteSpace(local.Id))
|
||||
local.Id = $"local-{Guid.NewGuid():N}";
|
||||
|
||||
if (_networkMonitor.IsOnline)
|
||||
{
|
||||
try
|
||||
{
|
||||
var ok = await RemoteAddAsync(local, ct).ConfigureAwait(false);
|
||||
if (ok)
|
||||
{
|
||||
UpsertLocalCache(local);
|
||||
return true;
|
||||
}
|
||||
_logger.Warning($"[原材料卡片] 远端新增返回失败 id={local.Id}");
|
||||
return false;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.Warning($"[原材料卡片] 远端新增异常,转离线入队 id={local.Id}: {ex.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
EnqueuePendingOperation(new RawMaterialCardPendingOperation
|
||||
{
|
||||
OpType = RawMaterialCardOperationType.Add,
|
||||
CardId = local.Id,
|
||||
Card = local,
|
||||
CreatedAt = DateTime.UtcNow
|
||||
});
|
||||
UpsertLocalCache(local);
|
||||
return true;
|
||||
}
|
||||
|
||||
public async Task<bool> EditAsync(MesXslRawMaterialCard card, CancellationToken ct = default)
|
||||
{
|
||||
if (!card.TenantId.HasValue || card.TenantId.Value <= 0)
|
||||
card.TenantId = DefaultTenantId;
|
||||
var local = Clone(card);
|
||||
|
||||
if (_networkMonitor.IsOnline)
|
||||
{
|
||||
try
|
||||
{
|
||||
var (ok, _) = await RemoteEditAsync(local, ct).ConfigureAwait(false);
|
||||
if (ok)
|
||||
{
|
||||
UpsertLocalCache(local);
|
||||
return true;
|
||||
}
|
||||
_logger.Warning($"[原材料卡片] 远端修改返回失败 id={local.Id}");
|
||||
return false;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.Warning($"[原材料卡片] 远端修改异常,转离线入队 id={local.Id}: {ex.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
EnqueuePendingOperation(new RawMaterialCardPendingOperation
|
||||
{
|
||||
OpType = RawMaterialCardOperationType.Edit,
|
||||
CardId = local.Id,
|
||||
Card = local,
|
||||
AnchorUpdateTime = local.UpdateTime,
|
||||
CreatedAt = DateTime.UtcNow
|
||||
});
|
||||
UpsertLocalCache(local);
|
||||
return true;
|
||||
}
|
||||
|
||||
public async Task<bool> DeleteAsync(string id, CancellationToken ct = default)
|
||||
{
|
||||
if (_networkMonitor.IsOnline)
|
||||
{
|
||||
try
|
||||
{
|
||||
var ok = await RemoteDeleteAsync(id, ct).ConfigureAwait(false);
|
||||
if (ok)
|
||||
{
|
||||
RemoveFromLocalCache(id);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.Warning($"[原材料卡片] 远端删除异常,转离线入队 id={id}: {ex.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
DateTime? anchor;
|
||||
lock (_cacheLock)
|
||||
{
|
||||
anchor = _localCache.FirstOrDefault(c => string.Equals(c.Id, id, StringComparison.OrdinalIgnoreCase))?.UpdateTime;
|
||||
}
|
||||
EnqueuePendingOperation(new RawMaterialCardPendingOperation
|
||||
{
|
||||
OpType = RawMaterialCardOperationType.Delete,
|
||||
CardId = id,
|
||||
AnchorUpdateTime = anchor,
|
||||
CreatedAt = DateTime.UtcNow
|
||||
});
|
||||
RemoveFromLocalCache(id);
|
||||
return true;
|
||||
}
|
||||
|
||||
public async Task<bool> DeleteBatchAsync(string ids, CancellationToken ct = default)
|
||||
{
|
||||
var idList = ids.Split(',', StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries);
|
||||
var allSuccess = true;
|
||||
foreach (var id in idList)
|
||||
allSuccess &= await DeleteAsync(id, ct).ConfigureAwait(false);
|
||||
return allSuccess;
|
||||
}
|
||||
|
||||
public async Task<bool> UpdatePriorityAsync(string id, string priorityPickup, CancellationToken ct = default)
|
||||
{
|
||||
if (_networkMonitor.IsOnline)
|
||||
{
|
||||
try
|
||||
{
|
||||
var url = $"{BaseUrl}/xslmes/mesXslRawMaterialCard/updatePriority?id={Uri.EscapeDataString(id)}&priorityPickup={Uri.EscapeDataString(priorityPickup)}&tenantId={DefaultTenantId}";
|
||||
using var client = CreateClient();
|
||||
var resp = await client.PutAsync(url, null, ct).ConfigureAwait(false);
|
||||
var ok = resp.IsSuccessStatusCode && await IsSuccessResultAsync(resp, ct).ConfigureAwait(false);
|
||||
if (ok)
|
||||
{
|
||||
UpdateLocalPriority(id, priorityPickup);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.Warning($"[原材料卡片] 远端优先出库更新异常 id={id}: {ex.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
UpdateLocalPriority(id, priorityPickup);
|
||||
return true;
|
||||
}
|
||||
|
||||
// ─────────────────────────── Remote helpers ────────────────────────────
|
||||
|
||||
private async Task<List<MesXslRawMaterialCard>> FetchRemoteListAsync(CancellationToken ct)
|
||||
{
|
||||
var query = HttpUtility.ParseQueryString(string.Empty);
|
||||
query["pageNo"] = "1";
|
||||
query["pageSize"] = "10000";
|
||||
query["tenantId"] = DefaultTenantId.ToString();
|
||||
var url = $"{BaseUrl}/xslmes/mesXslRawMaterialCard/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<MesXslRawMaterialCard>>(_jsonOpts) ?? new();
|
||||
}
|
||||
|
||||
private async Task<MesXslRawMaterialCard?> FetchRemoteSingleAsync(string id, CancellationToken ct)
|
||||
{
|
||||
try
|
||||
{
|
||||
var url = $"{BaseUrl}/xslmes/mesXslRawMaterialCard/anon/queryById?id={Uri.EscapeDataString(id)}&tenantId={DefaultTenantId}";
|
||||
using var client = CreateClient();
|
||||
var resp = await client.GetAsync(url, ct).ConfigureAwait(false);
|
||||
if (!resp.IsSuccessStatusCode) return null;
|
||||
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<MesXslRawMaterialCard>(_jsonOpts);
|
||||
return null;
|
||||
}
|
||||
catch { return null; }
|
||||
}
|
||||
|
||||
private async Task<bool> RemoteAddAsync(MesXslRawMaterialCard card, CancellationToken ct)
|
||||
{
|
||||
var url = $"{BaseUrl}/xslmes/mesXslRawMaterialCard/anon/add?tenantId={DefaultTenantId}";
|
||||
var payload = Clone(card);
|
||||
if (IsLocalTempId(payload.Id)) payload.Id = null;
|
||||
return await PostJsonAsync(url, payload, ct).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
private async Task<(bool Ok, bool IsVersionConflict)> RemoteEditAsync(MesXslRawMaterialCard card, CancellationToken ct)
|
||||
{
|
||||
var url = $"{BaseUrl}/xslmes/mesXslRawMaterialCard/anon/edit?tenantId={DefaultTenantId}";
|
||||
return await PostJsonCheckVersionAsync(url, card, ct).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
private async Task<bool> RemoteDeleteAsync(string id, CancellationToken ct)
|
||||
{
|
||||
var url = $"{BaseUrl}/xslmes/mesXslRawMaterialCard/anon/delete?id={Uri.EscapeDataString(id)}&tenantId={DefaultTenantId}";
|
||||
using var client = CreateClient();
|
||||
var resp = await client.DeleteAsync(url, ct).ConfigureAwait(false);
|
||||
return resp.IsSuccessStatusCode && await IsSuccessResultAsync(resp, ct).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
private async Task<bool> PostJsonAsync(string url, object body, CancellationToken ct)
|
||||
{
|
||||
var content = new StringContent(JsonSerializer.Serialize(body, _jsonOpts), Encoding.UTF8, "application/json");
|
||||
using var client = CreateClient();
|
||||
var resp = await client.PostAsync(url, content, ct).ConfigureAwait(false);
|
||||
return resp.IsSuccessStatusCode && await IsSuccessResultAsync(resp, ct).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
private async Task<(bool Ok, bool IsVersionConflict)> PostJsonCheckVersionAsync(string url, object body, CancellationToken ct)
|
||||
{
|
||||
var content = new StringContent(JsonSerializer.Serialize(body, _jsonOpts), Encoding.UTF8, "application/json");
|
||||
using var client = CreateClient();
|
||||
var resp = await client.PostAsync(url, content, ct).ConfigureAwait(false);
|
||||
if (!resp.IsSuccessStatusCode) return (false, false);
|
||||
try
|
||||
{
|
||||
var json = await resp.Content.ReadAsStringAsync(ct).ConfigureAwait(false);
|
||||
using var doc = JsonDocument.Parse(json);
|
||||
int code = 200;
|
||||
if (doc.RootElement.TryGetProperty("code", out var codeEl)) code = codeEl.GetInt32();
|
||||
if (code == 200) return (true, false);
|
||||
if (doc.RootElement.TryGetProperty("message", out var msgEl))
|
||||
{
|
||||
var msg = msgEl.GetString() ?? "";
|
||||
if (msg.Contains("已被他人修改")) return (false, true);
|
||||
}
|
||||
return (false, false);
|
||||
}
|
||||
catch { return (true, false); }
|
||||
}
|
||||
|
||||
private static async Task<bool> IsSuccessResultAsync(HttpResponseMessage resp, CancellationToken ct)
|
||||
{
|
||||
try
|
||||
{
|
||||
var json = await resp.Content.ReadAsStringAsync(ct).ConfigureAwait(false);
|
||||
using var doc = JsonDocument.Parse(json);
|
||||
if (doc.RootElement.TryGetProperty("code", out var code)) return code.GetInt32() == 200;
|
||||
if (doc.RootElement.TryGetProperty("success", out var success)) return success.GetBoolean();
|
||||
return true;
|
||||
}
|
||||
catch { return true; }
|
||||
}
|
||||
|
||||
// ─────────────────────────── Reconnect sync ────────────────────────────
|
||||
|
||||
private void OnNetworkStatusChanged(bool isOnline)
|
||||
{
|
||||
if (!isOnline) return;
|
||||
_ = Task.Run(() => SyncAfterReconnectAsync(CancellationToken.None));
|
||||
}
|
||||
|
||||
private async Task SyncAfterReconnectAsync(CancellationToken ct)
|
||||
{
|
||||
_logger.Information("[原材料卡片] 开始执行重连同步");
|
||||
var pushResult = await PushPendingOnReconnectAsync(ct).ConfigureAwait(false);
|
||||
|
||||
if (!_networkMonitor.IsOnline) return;
|
||||
|
||||
try
|
||||
{
|
||||
var remote = await FetchRemoteListAsync(ct).ConfigureAwait(false);
|
||||
lock (_cacheLock)
|
||||
{
|
||||
_localCache = remote.Select(Clone).ToList();
|
||||
SaveCacheToDiskUnsafe();
|
||||
}
|
||||
_eventAggregator.GetEvent<RawMaterialCardChangedEvent>().Publish(new RawMaterialCardChangedPayload { Action = "pull" });
|
||||
_logger.Information($"[原材料卡片] 重连全量回拉成功 count={remote.Count}");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.Warning($"[原材料卡片] 重连全量回拉失败:{ex.Message}");
|
||||
}
|
||||
|
||||
var hasActivity = pushResult.PushedCount > 0 || pushResult.ConflictCount > 0 || pushResult.NewRecordsPushed > 0;
|
||||
if (hasActivity)
|
||||
{
|
||||
_eventAggregator.GetEvent<SyncConflictEvent>().Publish(new SyncConflictPayload
|
||||
{
|
||||
EntityName = "原材料卡片",
|
||||
PushedCount = pushResult.PushedCount,
|
||||
ConflictCount = pushResult.ConflictCount,
|
||||
NewRecordsPushed = pushResult.NewRecordsPushed
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private sealed record PendingReplayResult(bool Ok, bool IsConflict, string? EntityId);
|
||||
|
||||
private async Task<PushPendingResult> PushPendingOnReconnectAsync(CancellationToken ct)
|
||||
{
|
||||
if (!await _syncLock.WaitAsync(0, ct).ConfigureAwait(false))
|
||||
return new PushPendingResult(0, 0, 0);
|
||||
try
|
||||
{
|
||||
List<RawMaterialCardPendingOperation> snapshot;
|
||||
lock (_cacheLock) { snapshot = _pendingOps.OrderBy(x => x.CreatedAt).ToList(); }
|
||||
_logger.Information($"[原材料卡片] 开始推送 pending={snapshot.Count}");
|
||||
|
||||
int pushed = 0, conflicts = 0, newPushed = 0;
|
||||
foreach (var op in snapshot)
|
||||
{
|
||||
if (!_networkMonitor.IsOnline) break;
|
||||
|
||||
lock (_cacheLock)
|
||||
{
|
||||
if (!_pendingOps.Any(x => x.Id == op.Id)) continue;
|
||||
}
|
||||
|
||||
var result = await ExecutePendingOperationAsync(op, ct).ConfigureAwait(false);
|
||||
if (!result.Ok)
|
||||
{
|
||||
lock (_cacheLock)
|
||||
{
|
||||
op.RetryCount++;
|
||||
if (op.RetryCount >= MaxPendingRetries)
|
||||
{
|
||||
_pendingOps.RemoveAll(x => x.Id == op.Id);
|
||||
SavePendingOpsToDiskUnsafe();
|
||||
continue;
|
||||
}
|
||||
SavePendingOpsToDiskUnsafe();
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
if (result.IsConflict)
|
||||
{
|
||||
conflicts++;
|
||||
if (!string.IsNullOrWhiteSpace(result.EntityId))
|
||||
RemovePendingOpsByCardId(result.EntityId!);
|
||||
continue;
|
||||
}
|
||||
|
||||
lock (_cacheLock)
|
||||
{
|
||||
if (op.OpType == RawMaterialCardOperationType.Add) newPushed++;
|
||||
else pushed++;
|
||||
_pendingOps.RemoveAll(x => x.Id == op.Id);
|
||||
SavePendingOpsToDiskUnsafe();
|
||||
}
|
||||
}
|
||||
|
||||
return new PushPendingResult(pushed, conflicts, newPushed);
|
||||
}
|
||||
finally { _syncLock.Release(); }
|
||||
}
|
||||
|
||||
private async Task<PendingReplayResult> ExecutePendingOperationAsync(RawMaterialCardPendingOperation op, CancellationToken ct)
|
||||
{
|
||||
try
|
||||
{
|
||||
switch (op.OpType)
|
||||
{
|
||||
case RawMaterialCardOperationType.Add:
|
||||
{
|
||||
var ok = op.Card != null && await RemoteAddAsync(op.Card, ct).ConfigureAwait(false);
|
||||
return ok ? new(true, false, op.CardId) : new(false, false, null);
|
||||
}
|
||||
case RawMaterialCardOperationType.Edit:
|
||||
{
|
||||
if (op.Card == null || string.IsNullOrWhiteSpace(op.Card.Id)) return new(false, false, null);
|
||||
var id = op.Card.Id;
|
||||
var remote = await FetchRemoteSingleAsync(id, ct).ConfigureAwait(false);
|
||||
if (remote != null && op.AnchorUpdateTime != null && remote.UpdateTime != op.AnchorUpdateTime)
|
||||
{
|
||||
UpsertLocalCache(remote);
|
||||
return new(true, true, id);
|
||||
}
|
||||
var (ok, isConflict) = await RemoteEditAsync(op.Card, ct).ConfigureAwait(false);
|
||||
if (isConflict)
|
||||
{
|
||||
var fresh = await FetchRemoteSingleAsync(id, ct).ConfigureAwait(false);
|
||||
if (fresh != null) UpsertLocalCache(fresh);
|
||||
return new(true, true, id);
|
||||
}
|
||||
return ok ? new(true, false, id) : new(false, false, null);
|
||||
}
|
||||
case RawMaterialCardOperationType.Delete:
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(op.CardId)) return new(false, false, null);
|
||||
var id = op.CardId!;
|
||||
var remote = await FetchRemoteSingleAsync(id, ct).ConfigureAwait(false);
|
||||
if (remote == null) return new(true, false, id);
|
||||
if (op.AnchorUpdateTime != null && remote.UpdateTime != op.AnchorUpdateTime)
|
||||
{
|
||||
UpsertLocalCache(remote);
|
||||
return new(true, true, id);
|
||||
}
|
||||
var ok = await RemoteDeleteAsync(id, ct).ConfigureAwait(false);
|
||||
return ok ? new(true, false, id) : new(false, false, null);
|
||||
}
|
||||
default:
|
||||
return new(true, false, null);
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.Warning($"[原材料卡片] 执行pending异常 op={op.OpType}: {ex.Message}");
|
||||
return new(false, false, null);
|
||||
}
|
||||
}
|
||||
|
||||
// ─────────────────────────── Local cache helpers ────────────────────────────
|
||||
|
||||
private void RemovePendingOpsByCardId(string cardId)
|
||||
{
|
||||
lock (_cacheLock)
|
||||
{
|
||||
_pendingOps.RemoveAll(x =>
|
||||
(!string.IsNullOrWhiteSpace(x.CardId) && string.Equals(x.CardId, cardId, StringComparison.OrdinalIgnoreCase)) ||
|
||||
(x.Card?.Id != null && string.Equals(x.Card.Id, cardId, StringComparison.OrdinalIgnoreCase)));
|
||||
SavePendingOpsToDiskUnsafe();
|
||||
}
|
||||
}
|
||||
|
||||
private void EnqueuePendingOperation(RawMaterialCardPendingOperation op)
|
||||
{
|
||||
lock (_cacheLock) { _pendingOps.Add(op); SavePendingOpsToDiskUnsafe(); }
|
||||
}
|
||||
|
||||
private void UpsertLocalCache(MesXslRawMaterialCard card)
|
||||
{
|
||||
lock (_cacheLock)
|
||||
{
|
||||
var idx = _localCache.FindIndex(c => string.Equals(c.Id, card.Id, StringComparison.OrdinalIgnoreCase));
|
||||
if (idx >= 0) _localCache[idx] = Clone(card);
|
||||
else _localCache.Insert(0, Clone(card));
|
||||
SaveCacheToDiskUnsafe();
|
||||
}
|
||||
}
|
||||
|
||||
private void RemoveFromLocalCache(string id)
|
||||
{
|
||||
lock (_cacheLock)
|
||||
{
|
||||
_localCache.RemoveAll(c => string.Equals(c.Id, id, StringComparison.OrdinalIgnoreCase));
|
||||
SaveCacheToDiskUnsafe();
|
||||
}
|
||||
}
|
||||
|
||||
private void UpdateLocalPriority(string id, string priorityPickup)
|
||||
{
|
||||
lock (_cacheLock)
|
||||
{
|
||||
var item = _localCache.FirstOrDefault(c => string.Equals(c.Id, id, StringComparison.OrdinalIgnoreCase));
|
||||
if (item != null) { item.PriorityPickup = priorityPickup; SaveCacheToDiskUnsafe(); }
|
||||
}
|
||||
}
|
||||
|
||||
private List<MesXslRawMaterialCard> ApplyPendingOpsSnapshotUnsafe(List<MesXslRawMaterialCard> source)
|
||||
{
|
||||
var map = source.Where(c => !string.IsNullOrWhiteSpace(c.Id))
|
||||
.ToDictionary(c => c.Id!, Clone, StringComparer.OrdinalIgnoreCase);
|
||||
|
||||
foreach (var op in _pendingOps.OrderBy(x => x.CreatedAt))
|
||||
{
|
||||
switch (op.OpType)
|
||||
{
|
||||
case RawMaterialCardOperationType.Add:
|
||||
case RawMaterialCardOperationType.Edit:
|
||||
if (op.Card != null && !string.IsNullOrWhiteSpace(op.Card.Id))
|
||||
map[op.Card.Id] = Clone(op.Card);
|
||||
break;
|
||||
case RawMaterialCardOperationType.Delete:
|
||||
if (!string.IsNullOrWhiteSpace(op.CardId)) map.Remove(op.CardId);
|
||||
break;
|
||||
}
|
||||
}
|
||||
return map.Values.ToList();
|
||||
}
|
||||
|
||||
private static List<MesXslRawMaterialCard> ApplyFilters(List<MesXslRawMaterialCard> source,
|
||||
string? barcode, string? batchNo, string? materialName, string? supplierName, string? status)
|
||||
{
|
||||
IEnumerable<MesXslRawMaterialCard> q = source;
|
||||
if (!string.IsNullOrWhiteSpace(barcode))
|
||||
q = q.Where(c => (c.Barcode ?? "").Contains(barcode, StringComparison.OrdinalIgnoreCase));
|
||||
if (!string.IsNullOrWhiteSpace(batchNo))
|
||||
q = q.Where(c => (c.BatchNo ?? "").Contains(batchNo, StringComparison.OrdinalIgnoreCase));
|
||||
if (!string.IsNullOrWhiteSpace(materialName))
|
||||
q = q.Where(c => (c.MaterialName ?? "").Contains(materialName, StringComparison.OrdinalIgnoreCase));
|
||||
if (!string.IsNullOrWhiteSpace(supplierName))
|
||||
q = q.Where(c => (c.SupplierName ?? "").Contains(supplierName, StringComparison.OrdinalIgnoreCase));
|
||||
if (!string.IsNullOrWhiteSpace(status))
|
||||
q = q.Where(c => string.Equals(c.Status, status, StringComparison.OrdinalIgnoreCase));
|
||||
return q.OrderByDescending(c => c.CreateTime ?? DateTime.MinValue).ToList();
|
||||
}
|
||||
|
||||
private void LoadPendingOpsFromDisk()
|
||||
{
|
||||
try
|
||||
{
|
||||
if (!File.Exists(_pendingOpsFilePath)) return;
|
||||
var data = JsonSerializer.Deserialize<List<RawMaterialCardPendingOperation>>(File.ReadAllText(_pendingOpsFilePath), _jsonOpts);
|
||||
_pendingOps = data ?? new();
|
||||
}
|
||||
catch (Exception ex) { _pendingOps = new(); _logger.Warning($"[原材料卡片] 载入待上传失败:{ex.Message}"); }
|
||||
}
|
||||
|
||||
private void LoadCacheFromDisk()
|
||||
{
|
||||
try
|
||||
{
|
||||
if (!File.Exists(_cacheFilePath)) return;
|
||||
var data = JsonSerializer.Deserialize<List<MesXslRawMaterialCard>>(File.ReadAllText(_cacheFilePath), _jsonOpts);
|
||||
_localCache = data ?? new();
|
||||
}
|
||||
catch (Exception ex) { _localCache = new(); _logger.Warning($"[原材料卡片] 载入缓存失败:{ex.Message}"); }
|
||||
}
|
||||
|
||||
private void SavePendingOpsToDiskUnsafe() =>
|
||||
File.WriteAllText(_pendingOpsFilePath, JsonSerializer.Serialize(_pendingOps, _jsonOpts));
|
||||
|
||||
private void SaveCacheToDiskUnsafe() =>
|
||||
File.WriteAllText(_cacheFilePath, JsonSerializer.Serialize(_localCache, _jsonOpts));
|
||||
|
||||
private static MesXslRawMaterialCard Clone(MesXslRawMaterialCard input) => new()
|
||||
{
|
||||
Id = input.Id,
|
||||
Barcode = input.Barcode,
|
||||
BatchNo = input.BatchNo,
|
||||
EntryDate = input.EntryDate,
|
||||
MaterialId = input.MaterialId,
|
||||
MaterialName = input.MaterialName,
|
||||
MaterialDesc = input.MaterialDesc,
|
||||
SupplierId = input.SupplierId,
|
||||
SupplierName = input.SupplierName,
|
||||
ManufacturerMaterialName = input.ManufacturerMaterialName,
|
||||
ShelfLife = input.ShelfLife,
|
||||
TotalWeight = input.TotalWeight,
|
||||
RemainingWeight = input.RemainingWeight,
|
||||
RemainingQuantity = input.RemainingQuantity,
|
||||
Status = input.Status,
|
||||
TestResult = input.TestResult,
|
||||
WarehouseArea = input.WarehouseArea,
|
||||
UnloadOperator = input.UnloadOperator,
|
||||
PriorityPickup = input.PriorityPickup,
|
||||
CreateBy = input.CreateBy,
|
||||
CreateTime = input.CreateTime,
|
||||
UpdateBy = input.UpdateBy,
|
||||
UpdateTime = input.UpdateTime,
|
||||
TenantId = input.TenantId
|
||||
};
|
||||
|
||||
private static bool IsLocalTempId(string? id) =>
|
||||
!string.IsNullOrWhiteSpace(id) && id.StartsWith("local-", StringComparison.OrdinalIgnoreCase);
|
||||
|
||||
private sealed class RawMaterialCardPendingOperation
|
||||
{
|
||||
public string Id { get; set; } = Guid.NewGuid().ToString("N");
|
||||
public RawMaterialCardOperationType OpType { get; set; }
|
||||
public string? CardId { get; set; }
|
||||
public MesXslRawMaterialCard? Card { get; set; }
|
||||
public DateTime? AnchorUpdateTime { get; set; }
|
||||
public DateTime CreatedAt { get; set; } = DateTime.UtcNow;
|
||||
public int RetryCount { get; set; } = 0;
|
||||
}
|
||||
|
||||
private enum RawMaterialCardOperationType { Add = 1, Edit = 2, Delete = 3 }
|
||||
|
||||
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($"无法将 JSON 值转换为 DateTime?,token={reader.TokenType}");
|
||||
}
|
||||
|
||||
public override void Write(Utf8JsonWriter writer, DateTime? value, JsonSerializerOptions options)
|
||||
{
|
||||
if (value.HasValue) { writer.WriteStringValue(value.Value.ToString("yyyy-MM-dd HH:mm:ss")); return; }
|
||||
writer.WriteNullValue();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,85 @@
|
||||
using Prism.Events;
|
||||
using System.Text.Json;
|
||||
using YY.Admin.Core;
|
||||
using YY.Admin.Core.Events;
|
||||
using YY.Admin.Core.Services;
|
||||
|
||||
namespace YY.Admin.Services.Service.RawMaterialCard;
|
||||
|
||||
/// <summary>
|
||||
/// 监听 STOMP 收到的原材料卡片变更信号,转发为 Prism 事件,触发列表刷新。
|
||||
/// </summary>
|
||||
public class RawMaterialCardSyncCoordinator : ISingletonDependency
|
||||
{
|
||||
private readonly IEventAggregator _eventAggregator;
|
||||
private readonly ILoggerService _logger;
|
||||
private SubscriptionToken? _remoteCommandToken;
|
||||
private SubscriptionToken? _networkStatusToken;
|
||||
|
||||
public RawMaterialCardSyncCoordinator(
|
||||
IEventAggregator eventAggregator,
|
||||
SyncPollManager pollManager,
|
||||
ILoggerService logger)
|
||||
{
|
||||
_eventAggregator = eventAggregator;
|
||||
_logger = logger;
|
||||
|
||||
_remoteCommandToken = _eventAggregator
|
||||
.GetEvent<RemoteCommandReceivedEvent>()
|
||||
.Subscribe(OnRemoteCommand, ThreadOption.BackgroundThread);
|
||||
_networkStatusToken = _eventAggregator
|
||||
.GetEvent<NetworkStatusChangedEvent>()
|
||||
.Subscribe(OnNetworkStatusChanged, ThreadOption.BackgroundThread);
|
||||
|
||||
pollManager.Register("原材料卡片", () =>
|
||||
{
|
||||
_eventAggregator.GetEvent<RawMaterialCardChangedEvent>()
|
||||
.Publish(new RawMaterialCardChangedPayload { Action = "poll" });
|
||||
return Task.CompletedTask;
|
||||
});
|
||||
|
||||
_logger.Information("[原材料卡片推送] RawMaterialCardSyncCoordinator 已启动");
|
||||
}
|
||||
|
||||
private void OnNetworkStatusChanged(NetworkStatusChangedPayload payload)
|
||||
{
|
||||
if (!payload.IsOnline) return;
|
||||
_logger.Information("[原材料卡片推送] 网络恢复,触发补偿刷新");
|
||||
_eventAggregator.GetEvent<RawMaterialCardChangedEvent>()
|
||||
.Publish(new RawMaterialCardChangedPayload { Action = "reconnect" });
|
||||
}
|
||||
|
||||
private void OnRemoteCommand(RemoteCommandPayload payload)
|
||||
{
|
||||
try
|
||||
{
|
||||
var json = payload.CommandJson ?? string.Empty;
|
||||
if (string.IsNullOrWhiteSpace(json)) return;
|
||||
|
||||
using var doc = JsonDocument.Parse(json);
|
||||
if (!doc.RootElement.TryGetProperty("cmd", out var cmdEl)) return;
|
||||
var cmd = cmdEl.GetString() ?? string.Empty;
|
||||
if (!cmd.Equals("RAW_MATERIAL_CARD_CHANGED", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
_logger.Information($"[原材料卡片推送] 非原材料卡片命令 cmd={cmd},忽略");
|
||||
return;
|
||||
}
|
||||
|
||||
doc.RootElement.TryGetProperty("action", out var actionEl);
|
||||
doc.RootElement.TryGetProperty("cardId", out var idEl);
|
||||
|
||||
var changedPayload = new RawMaterialCardChangedPayload
|
||||
{
|
||||
Action = actionEl.GetString() ?? string.Empty,
|
||||
CardId = idEl.ValueKind == JsonValueKind.String ? idEl.GetString() : null
|
||||
};
|
||||
|
||||
_logger.Information($"[原材料卡片推送] 收到变更信号: action={changedPayload.Action}, cardId={changedPayload.CardId}");
|
||||
_eventAggregator.GetEvent<RawMaterialCardChangedEvent>().Publish(changedPayload);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.Warning($"[原材料卡片推送] 处理 STOMP 信号失败: {ex.Message}");
|
||||
}
|
||||
}
|
||||
}
|
||||
187
yy-admin-master/YY.Admin/Controls/DateTimeListPicker.xaml
Normal file
187
yy-admin-master/YY.Admin/Controls/DateTimeListPicker.xaml
Normal file
@@ -0,0 +1,187 @@
|
||||
<UserControl x:Class="YY.Admin.Controls.DateTimeListPicker"
|
||||
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"
|
||||
x:Name="Root"
|
||||
mc:Ignorable="d"
|
||||
d:DesignHeight="32" d:DesignWidth="220">
|
||||
|
||||
<UserControl.Resources>
|
||||
<!-- 时/分/秒 列表项样式:紧凑高亮,鼠标悬浮浅蓝 -->
|
||||
<Style x:Key="DTLP_TimeListBoxItemStyle" TargetType="ListBoxItem">
|
||||
<Setter Property="Padding" Value="0"/>
|
||||
<Setter Property="HorizontalContentAlignment" Value="Stretch"/>
|
||||
<Setter Property="Background" Value="Transparent"/>
|
||||
<Setter Property="Template">
|
||||
<Setter.Value>
|
||||
<ControlTemplate TargetType="ListBoxItem">
|
||||
<Border x:Name="Bd" Background="{TemplateBinding Background}" Padding="0">
|
||||
<ContentPresenter HorizontalAlignment="Stretch"/>
|
||||
</Border>
|
||||
<ControlTemplate.Triggers>
|
||||
<Trigger Property="IsMouseOver" Value="True">
|
||||
<Setter TargetName="Bd" Property="Background" Value="#EAF3FF"/>
|
||||
</Trigger>
|
||||
<Trigger Property="IsSelected" Value="True">
|
||||
<Setter TargetName="Bd" Property="Background" Value="#1E88E5"/>
|
||||
<Setter Property="Foreground" Value="White"/>
|
||||
</Trigger>
|
||||
</ControlTemplate.Triggers>
|
||||
</ControlTemplate>
|
||||
</Setter.Value>
|
||||
</Setter>
|
||||
</Style>
|
||||
|
||||
<!-- 时/分/秒 列表通用样式 -->
|
||||
<Style x:Key="DTLP_TimeListBoxStyle" TargetType="ListBox">
|
||||
<Setter Property="BorderThickness" Value="1,0,0,0"/>
|
||||
<Setter Property="BorderBrush" Value="#EEEEEE"/>
|
||||
<Setter Property="Width" Value="56"/>
|
||||
<Setter Property="Height" Value="200"/>
|
||||
<Setter Property="ScrollViewer.VerticalScrollBarVisibility" Value="Hidden"/>
|
||||
<Setter Property="ScrollViewer.HorizontalScrollBarVisibility" Value="Disabled"/>
|
||||
<Setter Property="ItemContainerStyle" Value="{StaticResource DTLP_TimeListBoxItemStyle}"/>
|
||||
</Style>
|
||||
|
||||
<!-- 文本框右侧日历图标按钮 -->
|
||||
<Style x:Key="DTLP_IconToggleStyle" TargetType="ToggleButton">
|
||||
<Setter Property="Background" Value="Transparent"/>
|
||||
<Setter Property="BorderThickness" Value="0"/>
|
||||
<Setter Property="Cursor" Value="Hand"/>
|
||||
<Setter Property="Focusable" Value="False"/>
|
||||
<Setter Property="Template">
|
||||
<Setter.Value>
|
||||
<ControlTemplate TargetType="ToggleButton">
|
||||
<Border Background="{TemplateBinding Background}" Padding="2">
|
||||
<ContentPresenter HorizontalAlignment="Center" VerticalAlignment="Center"/>
|
||||
</Border>
|
||||
</ControlTemplate>
|
||||
</Setter.Value>
|
||||
</Setter>
|
||||
<Style.Triggers>
|
||||
<Trigger Property="IsMouseOver" Value="True">
|
||||
<Setter Property="Background" Value="#F2F6FA"/>
|
||||
</Trigger>
|
||||
</Style.Triggers>
|
||||
</Style>
|
||||
</UserControl.Resources>
|
||||
|
||||
<Grid>
|
||||
<!-- 顶部:TextBox + 日历图标按钮(合一外观) -->
|
||||
<Grid>
|
||||
<hc:TextBox x:Name="PART_TextBox"
|
||||
Text="{Binding DisplayText, ElementName=Root, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
|
||||
hc:InfoElement.Placeholder="{Binding Placeholder, ElementName=Root}"
|
||||
Padding="6,4,28,4"
|
||||
VerticalContentAlignment="Center"
|
||||
KeyDown="OnTextBoxKeyDown"
|
||||
LostFocus="OnTextBoxLostFocus"/>
|
||||
|
||||
<ToggleButton x:Name="PART_Toggle"
|
||||
HorizontalAlignment="Right" VerticalAlignment="Center"
|
||||
Width="24" Height="24" Margin="0,0,4,0"
|
||||
Style="{StaticResource DTLP_IconToggleStyle}"
|
||||
IsChecked="{Binding IsDropDownOpen, ElementName=Root, Mode=TwoWay}">
|
||||
<Path Width="14" Height="14" Stretch="Uniform" Fill="#888888"
|
||||
Data="M19,4H17V3a1,1,0,0,0-2,0V4H9V3A1,1,0,0,0,7,3V4H5A2,2,0,0,0,3,6V20a2,2,0,0,0,2,2H19a2,2,0,0,0,2-2V6A2,2,0,0,0,19,4Zm0,16H5V10H19ZM5,8V6H7V7A1,1,0,0,0,9,7V6h6V7a1,1,0,0,0,2,0V6h2V8Z"/>
|
||||
</ToggleButton>
|
||||
</Grid>
|
||||
|
||||
<!-- 弹出层 -->
|
||||
<Popup x:Name="PART_Popup"
|
||||
IsOpen="{Binding IsDropDownOpen, ElementName=Root, Mode=TwoWay}"
|
||||
PlacementTarget="{Binding ElementName=PART_TextBox}"
|
||||
Placement="Bottom" VerticalOffset="2"
|
||||
StaysOpen="False"
|
||||
AllowsTransparency="True"
|
||||
PopupAnimation="Fade">
|
||||
<Border Background="White"
|
||||
BorderBrush="#DCDCDC" BorderThickness="1"
|
||||
Padding="8"
|
||||
SnapsToDevicePixels="True">
|
||||
<Border.Effect>
|
||||
<DropShadowEffect Color="#80000000" BlurRadius="10" ShadowDepth="2" Opacity="0.25"/>
|
||||
</Border.Effect>
|
||||
<Grid>
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="*"/>
|
||||
<RowDefinition Height="Auto"/>
|
||||
</Grid.RowDefinitions>
|
||||
|
||||
<!-- 上:日历 + 时/分/秒三列 -->
|
||||
<Grid Grid.Row="0">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="Auto"/>
|
||||
<ColumnDefinition Width="Auto"/>
|
||||
<ColumnDefinition Width="Auto"/>
|
||||
<ColumnDefinition Width="Auto"/>
|
||||
</Grid.ColumnDefinitions>
|
||||
|
||||
<Calendar x:Name="PART_Calendar" Grid.Column="0"
|
||||
SelectedDate="{Binding PendingDate, ElementName=Root, Mode=TwoWay}"
|
||||
DisplayDate="{Binding PendingDate, ElementName=Root, Mode=OneWay}"
|
||||
Margin="0,0,4,0"/>
|
||||
|
||||
<ListBox x:Name="PART_HourList" Grid.Column="1"
|
||||
Style="{StaticResource DTLP_TimeListBoxStyle}"
|
||||
ItemsSource="{Binding Hours, ElementName=Root}"
|
||||
SelectedItem="{Binding PendingHour, ElementName=Root, Mode=TwoWay}">
|
||||
<ListBox.ItemTemplate>
|
||||
<DataTemplate>
|
||||
<TextBlock Text="{Binding StringFormat={}{0:00}}"
|
||||
TextAlignment="Center" Padding="0,4"
|
||||
FontSize="13"/>
|
||||
</DataTemplate>
|
||||
</ListBox.ItemTemplate>
|
||||
</ListBox>
|
||||
|
||||
<ListBox x:Name="PART_MinuteList" Grid.Column="2"
|
||||
Style="{StaticResource DTLP_TimeListBoxStyle}"
|
||||
ItemsSource="{Binding Minutes, ElementName=Root}"
|
||||
SelectedItem="{Binding PendingMinute, ElementName=Root, Mode=TwoWay}">
|
||||
<ListBox.ItemTemplate>
|
||||
<DataTemplate>
|
||||
<TextBlock Text="{Binding StringFormat={}{0:00}}"
|
||||
TextAlignment="Center" Padding="0,4"
|
||||
FontSize="13"/>
|
||||
</DataTemplate>
|
||||
</ListBox.ItemTemplate>
|
||||
</ListBox>
|
||||
|
||||
<ListBox x:Name="PART_SecondList" Grid.Column="3"
|
||||
Style="{StaticResource DTLP_TimeListBoxStyle}"
|
||||
ItemsSource="{Binding Seconds, ElementName=Root}"
|
||||
SelectedItem="{Binding PendingSecond, ElementName=Root, Mode=TwoWay}">
|
||||
<ListBox.ItemTemplate>
|
||||
<DataTemplate>
|
||||
<TextBlock Text="{Binding StringFormat={}{0:00}}"
|
||||
TextAlignment="Center" Padding="0,4"
|
||||
FontSize="13"/>
|
||||
</DataTemplate>
|
||||
</ListBox.ItemTemplate>
|
||||
</ListBox>
|
||||
</Grid>
|
||||
|
||||
<!-- 下:此刻 / 确定 -->
|
||||
<Border Grid.Row="1" BorderThickness="0,1,0,0" BorderBrush="#EEEEEE" Margin="0,6,0,0" Padding="0,6,0,0">
|
||||
<DockPanel LastChildFill="False">
|
||||
<Button DockPanel.Dock="Left" Content="此刻"
|
||||
Click="OnNowClick"
|
||||
Background="Transparent" BorderThickness="0"
|
||||
Foreground="#1E88E5" Cursor="Hand"
|
||||
Padding="4,2" FontSize="12"
|
||||
ToolTip="一键回填当前时间"/>
|
||||
<Button DockPanel.Dock="Right" Content="确定"
|
||||
Click="OnConfirmClick"
|
||||
Background="#1E88E5" Foreground="White"
|
||||
BorderThickness="0"
|
||||
Padding="16,4" Cursor="Hand" FontSize="12"/>
|
||||
</DockPanel>
|
||||
</Border>
|
||||
</Grid>
|
||||
</Border>
|
||||
</Popup>
|
||||
</Grid>
|
||||
</UserControl>
|
||||
272
yy-admin-master/YY.Admin/Controls/DateTimeListPicker.xaml.cs
Normal file
272
yy-admin-master/YY.Admin/Controls/DateTimeListPicker.xaml.cs
Normal file
@@ -0,0 +1,272 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
using System.Windows.Input;
|
||||
|
||||
namespace YY.Admin.Controls;
|
||||
|
||||
/// <summary>
|
||||
/// 自定义日期时间选择器。
|
||||
/// 弹出层布局:左侧 <see cref="Calendar"/> 日历 + 右侧时/分/秒 三列 <see cref="ListBox"/>,
|
||||
/// 底部「此刻 / 确定」按钮,用于替代 HandyControl 默认的「圆盘表盘 + 上午下午」风格 DateTimePicker。
|
||||
/// </summary>
|
||||
public partial class DateTimeListPicker : UserControl
|
||||
{
|
||||
public DateTimeListPicker()
|
||||
{
|
||||
InitializeComponent();
|
||||
Loaded += (_, _) => SyncDisplay();
|
||||
}
|
||||
|
||||
#region 公共依赖属性
|
||||
|
||||
/// <summary>选择的日期时间,双向绑定。</summary>
|
||||
public static readonly DependencyProperty SelectedDateTimeProperty =
|
||||
DependencyProperty.Register(
|
||||
nameof(SelectedDateTime), typeof(DateTime?), typeof(DateTimeListPicker),
|
||||
new FrameworkPropertyMetadata(
|
||||
null,
|
||||
FrameworkPropertyMetadataOptions.BindsTwoWayByDefault,
|
||||
OnSelectedDateTimeChanged));
|
||||
|
||||
public DateTime? SelectedDateTime
|
||||
{
|
||||
get => (DateTime?)GetValue(SelectedDateTimeProperty);
|
||||
set => SetValue(SelectedDateTimeProperty, value);
|
||||
}
|
||||
|
||||
/// <summary>占位符(空值时显示在 TextBox 上的提示文字)。</summary>
|
||||
public static readonly DependencyProperty PlaceholderProperty =
|
||||
DependencyProperty.Register(
|
||||
nameof(Placeholder), typeof(string), typeof(DateTimeListPicker),
|
||||
new PropertyMetadata("请选择日期时间"));
|
||||
|
||||
public string Placeholder
|
||||
{
|
||||
get => (string)GetValue(PlaceholderProperty);
|
||||
set => SetValue(PlaceholderProperty, value);
|
||||
}
|
||||
|
||||
/// <summary>显示格式,默认 yyyy-MM-dd HH:mm:ss。</summary>
|
||||
public static readonly DependencyProperty DateTimeFormatProperty =
|
||||
DependencyProperty.Register(
|
||||
nameof(DateTimeFormat), typeof(string), typeof(DateTimeListPicker),
|
||||
new PropertyMetadata("yyyy-MM-dd HH:mm:ss", OnDateTimeFormatChanged));
|
||||
|
||||
public string DateTimeFormat
|
||||
{
|
||||
get => (string)GetValue(DateTimeFormatProperty);
|
||||
set => SetValue(DateTimeFormatProperty, value);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region 内部依赖属性
|
||||
|
||||
/// <summary>弹出层是否打开。</summary>
|
||||
public static readonly DependencyProperty IsDropDownOpenProperty =
|
||||
DependencyProperty.Register(
|
||||
nameof(IsDropDownOpen), typeof(bool), typeof(DateTimeListPicker),
|
||||
new PropertyMetadata(false, OnIsDropDownOpenChanged));
|
||||
|
||||
public bool IsDropDownOpen
|
||||
{
|
||||
get => (bool)GetValue(IsDropDownOpenProperty);
|
||||
set => SetValue(IsDropDownOpenProperty, value);
|
||||
}
|
||||
|
||||
/// <summary>TextBox 当前显示的文字。用户可手工编辑,回车/失焦时尝试解析。</summary>
|
||||
public static readonly DependencyProperty DisplayTextProperty =
|
||||
DependencyProperty.Register(
|
||||
nameof(DisplayText), typeof(string), typeof(DateTimeListPicker),
|
||||
new PropertyMetadata(string.Empty));
|
||||
|
||||
public string DisplayText
|
||||
{
|
||||
get => (string)GetValue(DisplayTextProperty);
|
||||
set => SetValue(DisplayTextProperty, value);
|
||||
}
|
||||
|
||||
/// <summary>弹出层内未确认的日期值。</summary>
|
||||
public static readonly DependencyProperty PendingDateProperty =
|
||||
DependencyProperty.Register(
|
||||
nameof(PendingDate), typeof(DateTime?), typeof(DateTimeListPicker),
|
||||
new PropertyMetadata(null));
|
||||
|
||||
public DateTime? PendingDate
|
||||
{
|
||||
get => (DateTime?)GetValue(PendingDateProperty);
|
||||
set => SetValue(PendingDateProperty, value);
|
||||
}
|
||||
|
||||
public static readonly DependencyProperty PendingHourProperty =
|
||||
DependencyProperty.Register(nameof(PendingHour), typeof(int), typeof(DateTimeListPicker), new PropertyMetadata(0));
|
||||
|
||||
public int PendingHour
|
||||
{
|
||||
get => (int)GetValue(PendingHourProperty);
|
||||
set => SetValue(PendingHourProperty, value);
|
||||
}
|
||||
|
||||
public static readonly DependencyProperty PendingMinuteProperty =
|
||||
DependencyProperty.Register(nameof(PendingMinute), typeof(int), typeof(DateTimeListPicker), new PropertyMetadata(0));
|
||||
|
||||
public int PendingMinute
|
||||
{
|
||||
get => (int)GetValue(PendingMinuteProperty);
|
||||
set => SetValue(PendingMinuteProperty, value);
|
||||
}
|
||||
|
||||
public static readonly DependencyProperty PendingSecondProperty =
|
||||
DependencyProperty.Register(nameof(PendingSecond), typeof(int), typeof(DateTimeListPicker), new PropertyMetadata(0));
|
||||
|
||||
public int PendingSecond
|
||||
{
|
||||
get => (int)GetValue(PendingSecondProperty);
|
||||
set => SetValue(PendingSecondProperty, value);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
// 注意:这三个集合必须用字段初始化器赋值,而不是放到构造函数体内。
|
||||
// 原因:XAML 中通过 {Binding Hours, ElementName=Root} 绑定到普通 CLR 只读属性,
|
||||
// 绑定在 InitializeComponent() 执行时就建立。若赋值滞后到构造函数体内,
|
||||
// 绑定第一次求值时 Hours 仍为 null,且不会再有变更通知,导致弹出层右侧三列为空。
|
||||
|
||||
/// <summary>0-23 小时列表。</summary>
|
||||
public List<int> Hours { get; } = Enumerable.Range(0, 24).ToList();
|
||||
|
||||
/// <summary>0-59 分钟列表。</summary>
|
||||
public List<int> Minutes { get; } = Enumerable.Range(0, 60).ToList();
|
||||
|
||||
/// <summary>0-59 秒列表。</summary>
|
||||
public List<int> Seconds { get; } = Enumerable.Range(0, 60).ToList();
|
||||
|
||||
private bool _suppressTextSync;
|
||||
|
||||
private static void OnSelectedDateTimeChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
|
||||
{
|
||||
((DateTimeListPicker)d).SyncDisplay();
|
||||
}
|
||||
|
||||
private static void OnDateTimeFormatChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
|
||||
{
|
||||
((DateTimeListPicker)d).SyncDisplay();
|
||||
}
|
||||
|
||||
private static void OnIsDropDownOpenChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
|
||||
{
|
||||
var picker = (DateTimeListPicker)d;
|
||||
if ((bool)e.NewValue)
|
||||
{
|
||||
picker.SyncPendingFromSelected();
|
||||
picker.Dispatcher.BeginInvoke(new Action(picker.ScrollListBoxesToSelection),
|
||||
System.Windows.Threading.DispatcherPriority.Loaded);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>将 <see cref="SelectedDateTime"/> 同步到 TextBox 显示文本。</summary>
|
||||
private void SyncDisplay()
|
||||
{
|
||||
_suppressTextSync = true;
|
||||
try
|
||||
{
|
||||
DisplayText = SelectedDateTime.HasValue
|
||||
? SelectedDateTime.Value.ToString(DateTimeFormat, CultureInfo.InvariantCulture)
|
||||
: string.Empty;
|
||||
}
|
||||
finally
|
||||
{
|
||||
_suppressTextSync = false;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>弹出层打开时,把当前选中值(或当前时间)拷贝到 Pending 字段。</summary>
|
||||
private void SyncPendingFromSelected()
|
||||
{
|
||||
var dt = SelectedDateTime ?? DateTime.Now;
|
||||
PendingDate = dt.Date;
|
||||
PendingHour = dt.Hour;
|
||||
PendingMinute = dt.Minute;
|
||||
PendingSecond = dt.Second;
|
||||
}
|
||||
|
||||
/// <summary>将三列时间 ListBox 滚动到当前选中项。</summary>
|
||||
private void ScrollListBoxesToSelection()
|
||||
{
|
||||
PART_HourList?.ScrollIntoView(PendingHour);
|
||||
PART_MinuteList?.ScrollIntoView(PendingMinute);
|
||||
PART_SecondList?.ScrollIntoView(PendingSecond);
|
||||
}
|
||||
|
||||
private void OnNowClick(object sender, RoutedEventArgs e)
|
||||
{
|
||||
var now = DateTime.Now;
|
||||
PendingDate = now.Date;
|
||||
PendingHour = now.Hour;
|
||||
PendingMinute = now.Minute;
|
||||
PendingSecond = now.Second;
|
||||
ScrollListBoxesToSelection();
|
||||
}
|
||||
|
||||
private void OnConfirmClick(object sender, RoutedEventArgs e)
|
||||
{
|
||||
var date = PendingDate ?? DateTime.Today;
|
||||
SelectedDateTime = new DateTime(
|
||||
date.Year, date.Month, date.Day,
|
||||
ClampTime(PendingHour, 23),
|
||||
ClampTime(PendingMinute, 59),
|
||||
ClampTime(PendingSecond, 59));
|
||||
IsDropDownOpen = false;
|
||||
}
|
||||
|
||||
private static int ClampTime(int value, int max)
|
||||
{
|
||||
if (value < 0) return 0;
|
||||
if (value > max) return max;
|
||||
return value;
|
||||
}
|
||||
|
||||
private void OnTextBoxKeyDown(object sender, KeyEventArgs e)
|
||||
{
|
||||
if (e.Key == Key.Enter)
|
||||
{
|
||||
TryParseDisplayText();
|
||||
e.Handled = true;
|
||||
}
|
||||
}
|
||||
|
||||
private void OnTextBoxLostFocus(object sender, RoutedEventArgs e)
|
||||
{
|
||||
TryParseDisplayText();
|
||||
}
|
||||
|
||||
/// <summary>解析当前 TextBox 文本到 <see cref="SelectedDateTime"/>,失败则回滚显示。</summary>
|
||||
private void TryParseDisplayText()
|
||||
{
|
||||
if (_suppressTextSync) return;
|
||||
|
||||
if (string.IsNullOrWhiteSpace(DisplayText))
|
||||
{
|
||||
SelectedDateTime = null;
|
||||
return;
|
||||
}
|
||||
|
||||
if (DateTime.TryParseExact(DisplayText, DateTimeFormat, CultureInfo.InvariantCulture, DateTimeStyles.None, out var exact))
|
||||
{
|
||||
SelectedDateTime = exact;
|
||||
}
|
||||
else if (DateTime.TryParse(DisplayText, CultureInfo.CurrentCulture, DateTimeStyles.None, out var fallback))
|
||||
{
|
||||
SelectedDateTime = fallback;
|
||||
}
|
||||
else
|
||||
{
|
||||
// 输入非法,恢复为原值
|
||||
SyncDisplay();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -154,6 +154,10 @@ public class StompWebSocketService : ISignalRService
|
||||
await SendFrameAsync(
|
||||
BuildSubscribeFrame("sub-mes-raw-material-entries", "/topic/sync/mes-raw-material-entries"),
|
||||
cancellationToken).ConfigureAwait(false);
|
||||
// 原材料卡片变更:订阅 /topic/sync/mes-raw-material-cards
|
||||
await SendFrameAsync(
|
||||
BuildSubscribeFrame("sub-mes-raw-material-cards", "/topic/sync/mes-raw-material-cards"),
|
||||
cancellationToken).ConfigureAwait(false);
|
||||
|
||||
// 订阅服务端 PONG 回复(应用层假在线检测)
|
||||
await SendFrameAsync(
|
||||
|
||||
@@ -13,6 +13,7 @@ using YY.Admin.Views.MixerMaterial;
|
||||
using YY.Admin.ViewModels.Vehicle;
|
||||
using YY.Admin.Views.Vehicle;
|
||||
using YY.Admin.Views.WeightRecord;
|
||||
using YY.Admin.Views.RawMaterialCard;
|
||||
using YY.Admin.Views.RawMaterialEntry;
|
||||
|
||||
namespace YY.Admin
|
||||
@@ -75,6 +76,8 @@ namespace YY.Admin
|
||||
containerRegistry.RegisterForNavigation<RawMaterialEntryListView>();
|
||||
// 新增原料入场记录(独立页面)
|
||||
containerRegistry.RegisterForNavigation<RawMaterialEntryOperationView>();
|
||||
// 原材料卡片
|
||||
containerRegistry.RegisterForNavigation<RawMaterialCardListView>();
|
||||
}
|
||||
}
|
||||
public class DialogWindow : Window, IDialogWindow
|
||||
|
||||
@@ -19,6 +19,7 @@ using YY.Admin.Services.Service.Customer;
|
||||
using YY.Admin.Services.Service.Dict;
|
||||
using YY.Admin.Services.Service.MixerMaterial;
|
||||
using YY.Admin.Services.Service.Supplier;
|
||||
using YY.Admin.Services.Service.RawMaterialCard;
|
||||
using YY.Admin.Services.Service.RawMaterialEntry;
|
||||
using YY.Admin.Services.Service.Vehicle;
|
||||
using YY.Admin.Services.Service.WeightRecord;
|
||||
@@ -57,6 +58,9 @@ public class SyncModule : IModule
|
||||
// 原料入场记录:免密 API 直连 + STOMP 实时通知
|
||||
containerRegistry.RegisterSingleton<IRawMaterialEntryService, RawMaterialEntryService>();
|
||||
containerRegistry.RegisterSingleton<RawMaterialEntrySyncCoordinator>();
|
||||
// 原材料卡片:免密 API 直连 + STOMP 实时通知
|
||||
containerRegistry.RegisterSingleton<IRawMaterialCardService, RawMaterialCardService>();
|
||||
containerRegistry.RegisterSingleton<RawMaterialCardSyncCoordinator>();
|
||||
// 分类字典:启动同步 + 断线重连补刷
|
||||
containerRegistry.RegisterSingleton<CategorySyncCoordinator>();
|
||||
// 数据字典:启动同步 + 断线重连补刷
|
||||
@@ -121,6 +125,8 @@ public class SyncModule : IModule
|
||||
_ = containerProvider.Resolve<MixerMaterialSyncCoordinator>();
|
||||
// 强制实例化原料入场记录同步协调器
|
||||
_ = containerProvider.Resolve<RawMaterialEntrySyncCoordinator>();
|
||||
// 强制实例化原材料卡片同步协调器
|
||||
_ = containerProvider.Resolve<RawMaterialCardSyncCoordinator>();
|
||||
// 强制实例化分类字典同步协调器
|
||||
_ = containerProvider.Resolve<CategorySyncCoordinator>();
|
||||
// 强制实例化数据字典同步协调器
|
||||
|
||||
@@ -130,7 +130,12 @@ namespace YY.Admin.ViewModels.Control
|
||||
// 已实现页面:新增原料入场记录
|
||||
["RawMaterialEntryOperationView"] = "RawMaterialEntryOperationView",
|
||||
["/xslmes/rawMaterialEntryOperation"] = "RawMaterialEntryOperationView",
|
||||
["rawMaterialEntryOperation"] = "RawMaterialEntryOperationView"
|
||||
["rawMaterialEntryOperation"] = "RawMaterialEntryOperationView",
|
||||
|
||||
// 已实现页面:原材料卡片
|
||||
["RawMaterialCardListView"] = "RawMaterialCardListView",
|
||||
["/xslmes/mesXslRawMaterialCard"] = "RawMaterialCardListView",
|
||||
["mesXslRawMaterialCard"] = "RawMaterialCardListView"
|
||||
};
|
||||
|
||||
private MenuItem? _selectedMenuItem;
|
||||
|
||||
@@ -0,0 +1,160 @@
|
||||
using HandyControl.Controls;
|
||||
using HandyControl.Tools.Extension;
|
||||
using System.Collections.ObjectModel;
|
||||
using YY.Admin.Core;
|
||||
using YY.Admin.Core.Entity;
|
||||
using YY.Admin.Core.Services;
|
||||
using YY.Admin.Services.Service;
|
||||
|
||||
namespace YY.Admin.ViewModels.RawMaterialCard;
|
||||
|
||||
public class RawMaterialCardEditDialogViewModel : BaseViewModel, IDialogResultable<bool>
|
||||
{
|
||||
private readonly IRawMaterialCardService _cardService;
|
||||
private readonly IJeecgDictSyncService _dictSyncService;
|
||||
|
||||
private MesXslRawMaterialCard? _card;
|
||||
public MesXslRawMaterialCard? Card
|
||||
{
|
||||
get => _card;
|
||||
set => SetProperty(ref _card, value);
|
||||
}
|
||||
|
||||
public bool IsAddMode => string.IsNullOrWhiteSpace(Card?.Id);
|
||||
public string DialogTitle => IsAddMode ? "新增原材料卡片" : "编辑原材料卡片";
|
||||
|
||||
public ObservableCollection<KeyValuePair<string, string>> StatusOptions { get; } = new();
|
||||
public ObservableCollection<KeyValuePair<string, string>> TestResultOptions { get; } = new();
|
||||
|
||||
private bool _result;
|
||||
public bool Result { get => _result; set => SetProperty(ref _result, value); }
|
||||
public Action? CloseAction { get; set; }
|
||||
|
||||
public DelegateCommand SaveCommand { get; }
|
||||
public DelegateCommand CancelCommand { get; }
|
||||
|
||||
public RawMaterialCardEditDialogViewModel(
|
||||
IRawMaterialCardService cardService,
|
||||
IJeecgDictSyncService dictSyncService,
|
||||
IContainerExtension container,
|
||||
IRegionManager regionManager) : base(container, regionManager)
|
||||
{
|
||||
_cardService = cardService;
|
||||
_dictSyncService = dictSyncService;
|
||||
SaveCommand = new DelegateCommand(async () => await SaveAsync());
|
||||
CancelCommand = new DelegateCommand(() => CloseAction?.Invoke());
|
||||
_ = LoadDictOptionsAsync();
|
||||
}
|
||||
|
||||
private async Task LoadDictOptionsAsync()
|
||||
{
|
||||
try
|
||||
{
|
||||
var statusOpts = await _dictSyncService.GetDictOptionsAsync("xslmes_card_status");
|
||||
var testResultOpts = await _dictSyncService.GetDictOptionsAsync("xslmes_test_result");
|
||||
|
||||
StatusOptions.Clear();
|
||||
foreach (var item in statusOpts) StatusOptions.Add(item);
|
||||
if (StatusOptions.Count == 0)
|
||||
{
|
||||
StatusOptions.Add(new KeyValuePair<string, string>("正常", "1"));
|
||||
StatusOptions.Add(new KeyValuePair<string, string>("异常", "0"));
|
||||
}
|
||||
|
||||
TestResultOptions.Clear();
|
||||
foreach (var item in testResultOpts) TestResultOptions.Add(item);
|
||||
if (TestResultOptions.Count == 0)
|
||||
{
|
||||
TestResultOptions.Add(new KeyValuePair<string, string>("未检", "0"));
|
||||
TestResultOptions.Add(new KeyValuePair<string, string>("合格", "1"));
|
||||
TestResultOptions.Add(new KeyValuePair<string, string>("不合格", "2"));
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
StatusOptions.Clear();
|
||||
StatusOptions.Add(new KeyValuePair<string, string>("正常", "1"));
|
||||
StatusOptions.Add(new KeyValuePair<string, string>("异常", "0"));
|
||||
|
||||
TestResultOptions.Clear();
|
||||
TestResultOptions.Add(new KeyValuePair<string, string>("未检", "0"));
|
||||
TestResultOptions.Add(new KeyValuePair<string, string>("合格", "1"));
|
||||
TestResultOptions.Add(new KeyValuePair<string, string>("不合格", "2"));
|
||||
}
|
||||
}
|
||||
|
||||
public void InitializeForAdd()
|
||||
{
|
||||
Card = new MesXslRawMaterialCard
|
||||
{
|
||||
Status = "1",
|
||||
TestResult = "0",
|
||||
PriorityPickup = "0",
|
||||
EntryDate = DateTime.Today
|
||||
};
|
||||
RaisePropertyChanged(nameof(IsAddMode));
|
||||
RaisePropertyChanged(nameof(DialogTitle));
|
||||
}
|
||||
|
||||
public void InitializeForEdit(MesXslRawMaterialCard card)
|
||||
{
|
||||
Card = new MesXslRawMaterialCard
|
||||
{
|
||||
Id = card.Id,
|
||||
Barcode = card.Barcode,
|
||||
BatchNo = card.BatchNo,
|
||||
EntryDate = card.EntryDate,
|
||||
MaterialId = card.MaterialId,
|
||||
MaterialName = card.MaterialName,
|
||||
MaterialDesc = card.MaterialDesc,
|
||||
SupplierId = card.SupplierId,
|
||||
SupplierName = card.SupplierName,
|
||||
ManufacturerMaterialName = card.ManufacturerMaterialName,
|
||||
ShelfLife = card.ShelfLife,
|
||||
TotalWeight = card.TotalWeight,
|
||||
RemainingWeight = card.RemainingWeight,
|
||||
RemainingQuantity = card.RemainingQuantity,
|
||||
Status = card.Status,
|
||||
TestResult = card.TestResult,
|
||||
WarehouseArea = card.WarehouseArea,
|
||||
UnloadOperator = card.UnloadOperator,
|
||||
PriorityPickup = card.PriorityPickup,
|
||||
TenantId = card.TenantId,
|
||||
UpdateTime = card.UpdateTime
|
||||
};
|
||||
RaisePropertyChanged(nameof(IsAddMode));
|
||||
RaisePropertyChanged(nameof(DialogTitle));
|
||||
}
|
||||
|
||||
private async Task SaveAsync()
|
||||
{
|
||||
if (Card == null) return;
|
||||
if (string.IsNullOrWhiteSpace(Card.MaterialName))
|
||||
{
|
||||
HandyControl.Controls.MessageBox.Warning("物料名称不能为空!");
|
||||
return;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
bool ok;
|
||||
if (IsAddMode)
|
||||
{
|
||||
ok = await _cardService.AddAsync(Card);
|
||||
if (ok) HandyControl.Controls.MessageBox.Success("新增原材料卡片成功!");
|
||||
else { HandyControl.Controls.MessageBox.Error("新增原材料卡片失败!"); return; }
|
||||
}
|
||||
else
|
||||
{
|
||||
ok = await _cardService.EditAsync(Card);
|
||||
if (!ok) { HandyControl.Controls.MessageBox.Error("编辑原材料卡片失败!"); return; }
|
||||
}
|
||||
Result = ok;
|
||||
CloseAction?.Invoke();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
HandyControl.Controls.MessageBox.Error($"操作失败:{ex.Message}");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,272 @@
|
||||
using HandyControl.Controls;
|
||||
using HandyControl.Tools.Extension;
|
||||
using Prism.Events;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Diagnostics;
|
||||
using System.Windows;
|
||||
using YY.Admin.Core;
|
||||
using YY.Admin.Core.Events;
|
||||
using YY.Admin.Core.Helper;
|
||||
using YY.Admin.Core.Entity;
|
||||
using YY.Admin.Core.Services;
|
||||
using YY.Admin.Services.Service;
|
||||
using YY.Admin.Views.RawMaterialCard;
|
||||
|
||||
namespace YY.Admin.ViewModels.RawMaterialCard;
|
||||
|
||||
public class RawMaterialCardListViewModel : BaseViewModel
|
||||
{
|
||||
private readonly IRawMaterialCardService _cardService;
|
||||
private readonly IJeecgDictSyncService _dictSyncService;
|
||||
private SubscriptionToken? _changedToken;
|
||||
private SubscriptionToken? _syncConflictToken;
|
||||
|
||||
private ObservableCollection<MesXslRawMaterialCard> _cards = new();
|
||||
public ObservableCollection<MesXslRawMaterialCard> Cards
|
||||
{
|
||||
get => _cards;
|
||||
set => SetProperty(ref _cards, 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? _filterBarcode;
|
||||
public string? FilterBarcode { get => _filterBarcode; set => SetProperty(ref _filterBarcode, value); }
|
||||
|
||||
private string? _filterBatchNo;
|
||||
public string? FilterBatchNo { get => _filterBatchNo; set => SetProperty(ref _filterBatchNo, value); }
|
||||
|
||||
private string? _filterMaterialName;
|
||||
public string? FilterMaterialName { get => _filterMaterialName; set => SetProperty(ref _filterMaterialName, value); }
|
||||
|
||||
private string? _filterSupplierName;
|
||||
public string? FilterSupplierName { get => _filterSupplierName; set => SetProperty(ref _filterSupplierName, value); }
|
||||
|
||||
private string? _filterStatus;
|
||||
public string? FilterStatus { get => _filterStatus; set => SetProperty(ref _filterStatus, value); }
|
||||
|
||||
public ObservableCollection<KeyValuePair<string, string>> StatusOptions { get; } = new();
|
||||
public ObservableCollection<KeyValuePair<string, string>> TestResultOptions { get; } = new();
|
||||
|
||||
public DelegateCommand SearchCommand { get; }
|
||||
public DelegateCommand ResetCommand { get; }
|
||||
public DelegateCommand AddCommand { get; }
|
||||
public DelegateCommand<MesXslRawMaterialCard> EditCommand { get; }
|
||||
public DelegateCommand<MesXslRawMaterialCard> DeleteCommand { get; }
|
||||
public DelegateCommand<MesXslRawMaterialCard> TogglePriorityCommand { get; }
|
||||
public DelegateCommand PrevPageCommand { get; }
|
||||
public DelegateCommand NextPageCommand { get; }
|
||||
|
||||
public RawMaterialCardListViewModel(
|
||||
IRawMaterialCardService cardService,
|
||||
IJeecgDictSyncService dictSyncService,
|
||||
IContainerExtension container,
|
||||
IRegionManager regionManager) : base(container, regionManager)
|
||||
{
|
||||
_cardService = cardService;
|
||||
_dictSyncService = dictSyncService;
|
||||
|
||||
SearchCommand = new DelegateCommand(async () => { PageNo = 1; await LoadAsync(); });
|
||||
ResetCommand = new DelegateCommand(async () =>
|
||||
{
|
||||
FilterBarcode = null; FilterBatchNo = null; FilterMaterialName = null;
|
||||
FilterSupplierName = null; FilterStatus = null; PageNo = 1;
|
||||
await LoadAsync();
|
||||
});
|
||||
AddCommand = new DelegateCommand(async () => await ShowAddDialogAsync());
|
||||
EditCommand = new DelegateCommand<MesXslRawMaterialCard>(async c => await ShowEditDialogAsync(c));
|
||||
DeleteCommand = new DelegateCommand<MesXslRawMaterialCard>(async c => await DeleteAsync(c));
|
||||
TogglePriorityCommand = new DelegateCommand<MesXslRawMaterialCard>(async c => await TogglePriorityAsync(c));
|
||||
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<RawMaterialCardChangedEvent>()
|
||||
.Subscribe(async p => await OnChangedAsync(p), ThreadOption.UIThread);
|
||||
_syncConflictToken = _eventAggregator.GetEvent<SyncConflictEvent>()
|
||||
.Subscribe(OnSyncConflict, ThreadOption.UIThread);
|
||||
|
||||
_ = InitializeAsync();
|
||||
}
|
||||
|
||||
private async Task OnChangedAsync(RawMaterialCardChangedPayload payload)
|
||||
{
|
||||
if (payload.Action == "edit" && !string.IsNullOrWhiteSpace(payload.CardId))
|
||||
await RefreshSingleAsync(payload.CardId!);
|
||||
else
|
||||
await LoadAsync();
|
||||
}
|
||||
|
||||
private async Task RefreshSingleAsync(string cardId)
|
||||
{
|
||||
try
|
||||
{
|
||||
var updated = await _cardService.GetByIdAsync(cardId);
|
||||
if (updated == null) return;
|
||||
var idx = Cards.ToList().FindIndex(c => string.Equals(c.Id, cardId, StringComparison.OrdinalIgnoreCase));
|
||||
if (idx >= 0) Cards[idx] = updated;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Debug.WriteLine($"[原材料卡片] 单条刷新失败: {ex.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
private void OnSyncConflict(SyncConflictPayload payload)
|
||||
{
|
||||
if (!string.Equals(payload.EntityName, "原材料卡片", StringComparison.OrdinalIgnoreCase)) return;
|
||||
var parts = new List<string>();
|
||||
if (payload.PushedCount > 0) parts.Add($"已同步 {payload.PushedCount} 条本地改动到服务器");
|
||||
if (payload.NewRecordsPushed > 0) parts.Add($"已上传 {payload.NewRecordsPushed} 条本地新增记录");
|
||||
if (payload.ConflictCount > 0) parts.Add($"{payload.ConflictCount} 条记录与服务器版本冲突,已保留服务器版本");
|
||||
if (parts.Count == 0) return;
|
||||
var message = string.Join("\n", parts);
|
||||
if (payload.ConflictCount > 0) Growl.Warning(message);
|
||||
else Growl.Success(message);
|
||||
}
|
||||
|
||||
private async Task InitializeAsync()
|
||||
{
|
||||
try
|
||||
{
|
||||
await LoadDictOptionsAsync();
|
||||
await UIHelper.WaitForRenderAsync();
|
||||
await LoadAsync();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Debug.WriteLine($"[原材料卡片] 初始化失败: {ex.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
private async Task LoadDictOptionsAsync()
|
||||
{
|
||||
try
|
||||
{
|
||||
var statusOpts = await _dictSyncService.GetDictOptionsAsync("xslmes_card_status", includeAll: true);
|
||||
var testResultOpts = await _dictSyncService.GetDictOptionsAsync("xslmes_test_result", includeAll: true);
|
||||
|
||||
StatusOptions.Clear();
|
||||
foreach (var item in statusOpts) StatusOptions.Add(item);
|
||||
if (StatusOptions.Count == 0) StatusOptions.Add(new KeyValuePair<string, string>("全部", ""));
|
||||
|
||||
TestResultOptions.Clear();
|
||||
foreach (var item in testResultOpts) TestResultOptions.Add(item);
|
||||
if (TestResultOptions.Count == 0) TestResultOptions.Add(new KeyValuePair<string, string>("全部", ""));
|
||||
}
|
||||
catch
|
||||
{
|
||||
StatusOptions.Clear();
|
||||
StatusOptions.Add(new KeyValuePair<string, string>("全部", ""));
|
||||
StatusOptions.Add(new KeyValuePair<string, string>("正常", "1"));
|
||||
StatusOptions.Add(new KeyValuePair<string, string>("异常", "0"));
|
||||
|
||||
TestResultOptions.Clear();
|
||||
TestResultOptions.Add(new KeyValuePair<string, string>("全部", ""));
|
||||
TestResultOptions.Add(new KeyValuePair<string, string>("未检", "0"));
|
||||
TestResultOptions.Add(new KeyValuePair<string, string>("合格", "1"));
|
||||
TestResultOptions.Add(new KeyValuePair<string, string>("不合格", "2"));
|
||||
}
|
||||
}
|
||||
|
||||
public async Task LoadAsync()
|
||||
{
|
||||
try
|
||||
{
|
||||
IsLoading = true;
|
||||
var result = await _cardService.PageAsync(PageNo, PageSize,
|
||||
FilterBarcode, FilterBatchNo, FilterMaterialName, FilterSupplierName, FilterStatus);
|
||||
Cards = new ObservableCollection<MesXslRawMaterialCard>(result.Records);
|
||||
Total = result.Total;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Growl.Error($"加载原材料卡片失败:{ex.Message}");
|
||||
}
|
||||
finally
|
||||
{
|
||||
IsLoading = false;
|
||||
}
|
||||
}
|
||||
|
||||
private async Task ShowAddDialogAsync()
|
||||
{
|
||||
try
|
||||
{
|
||||
var result = await HandyControl.Controls.Dialog.Show<RawMaterialCardEditDialogView>()
|
||||
.Initialize<RawMaterialCardEditDialogViewModel>(vm => vm.InitializeForAdd())
|
||||
.GetResultAsync<bool>();
|
||||
if (result) await LoadAsync();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Growl.Error($"打开新增对话框失败:{ex.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
private async Task ShowEditDialogAsync(MesXslRawMaterialCard card)
|
||||
{
|
||||
if (card == null) return;
|
||||
try
|
||||
{
|
||||
var result = await HandyControl.Controls.Dialog.Show<RawMaterialCardEditDialogView>()
|
||||
.Initialize<RawMaterialCardEditDialogViewModel>(vm => vm.InitializeForEdit(card))
|
||||
.GetResultAsync<bool>();
|
||||
if (result) await LoadAsync();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Growl.Error($"打开编辑对话框失败:{ex.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
private async Task DeleteAsync(MesXslRawMaterialCard card)
|
||||
{
|
||||
if (card?.Id == null) return;
|
||||
var confirm = System.Windows.MessageBox.Show(
|
||||
$"确定删除原材料卡片(条码:{card.Barcode})?此操作不可恢复!",
|
||||
"确认删除", MessageBoxButton.OKCancel, MessageBoxImage.Question);
|
||||
if (confirm != System.Windows.MessageBoxResult.OK) return;
|
||||
|
||||
var ok = await _cardService.DeleteAsync(card.Id);
|
||||
if (ok) { Growl.Success("删除成功!"); await LoadAsync(); }
|
||||
else Growl.Error("删除失败!");
|
||||
}
|
||||
|
||||
private async Task TogglePriorityAsync(MesXslRawMaterialCard card)
|
||||
{
|
||||
if (card?.Id == null) return;
|
||||
var newVal = card.PriorityPickup == "1" ? "0" : "1";
|
||||
var ok = await _cardService.UpdatePriorityAsync(card.Id, newVal);
|
||||
if (ok)
|
||||
{
|
||||
card.PriorityPickup = newVal;
|
||||
RaisePropertyChanged(nameof(Cards));
|
||||
}
|
||||
else
|
||||
{
|
||||
Growl.Error("优先出库设置失败!");
|
||||
}
|
||||
}
|
||||
|
||||
protected override void CleanUp()
|
||||
{
|
||||
base.CleanUp();
|
||||
if (_changedToken != null)
|
||||
{
|
||||
_eventAggregator.GetEvent<RawMaterialCardChangedEvent>().Unsubscribe(_changedToken);
|
||||
_changedToken = null;
|
||||
}
|
||||
if (_syncConflictToken != null)
|
||||
{
|
||||
_eventAggregator.GetEvent<SyncConflictEvent>().Unsubscribe(_syncConflictToken);
|
||||
_syncConflictToken = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -9,17 +9,21 @@ using YY.Admin.Core.Entity;
|
||||
using YY.Admin.Core.Services;
|
||||
using YY.Admin.Services.Service;
|
||||
using YY.Admin.Views.RawMaterialEntry;
|
||||
using YY.Admin.ViewModels.WeightRecord;
|
||||
using YY.Admin.Views.WeightRecord;
|
||||
|
||||
namespace YY.Admin.ViewModels.RawMaterialEntry;
|
||||
|
||||
public class RawMaterialEntryEditDialogViewModel : BaseViewModel, IDialogResultable<bool>
|
||||
{
|
||||
private readonly IRawMaterialEntryService _entryService;
|
||||
/// <summary>供子页面(如独立新增页右侧面板)复用列表/详情查询能力。</summary>
|
||||
protected IRawMaterialEntryService EntryService => _entryService;
|
||||
private readonly IJeecgDictSyncService _dictSyncService;
|
||||
private readonly IMixerMaterialService _mixerMaterialService;
|
||||
|
||||
// 加载完物料后用于回填 Edit 模式选中项
|
||||
private string? _pendingMaterialId;
|
||||
protected string? _pendingMaterialId;
|
||||
|
||||
private MesXslRawMaterialEntry? _entry;
|
||||
public MesXslRawMaterialEntry? Entry
|
||||
@@ -28,7 +32,7 @@ public class RawMaterialEntryEditDialogViewModel : BaseViewModel, IDialogResulta
|
||||
set => SetProperty(ref _entry, value);
|
||||
}
|
||||
|
||||
private MesMixerMaterial? _selectedMaterial;
|
||||
protected MesMixerMaterial? _selectedMaterial;
|
||||
public MesMixerMaterial? SelectedMaterial
|
||||
{
|
||||
get => _selectedMaterial;
|
||||
@@ -94,19 +98,6 @@ public class RawMaterialEntryEditDialogViewModel : BaseViewModel, IDialogResulta
|
||||
{
|
||||
if (Entry == null || Entry.TotalWeight == value) return;
|
||||
Entry.TotalWeight = value;
|
||||
RecalculatePortionWeight();
|
||||
RaisePropertyChanged(nameof(Entry));
|
||||
}
|
||||
}
|
||||
|
||||
public int? TotalPortionsInput
|
||||
{
|
||||
get => Entry?.TotalPortions;
|
||||
set
|
||||
{
|
||||
if (Entry == null || Entry.TotalPortions == value) return;
|
||||
Entry.TotalPortions = value;
|
||||
RecalculatePortionWeight();
|
||||
RaisePropertyChanged(nameof(Entry));
|
||||
}
|
||||
}
|
||||
@@ -137,6 +128,8 @@ public class RawMaterialEntryEditDialogViewModel : BaseViewModel, IDialogResulta
|
||||
public DelegateCommand ClearMaterialCommand { get; }
|
||||
public DelegateCommand OpenWeightRecordPickerCommand { get; }
|
||||
public DelegateCommand ClearWeightRecordCommand { get; }
|
||||
public DelegateCommand OpenSupplierPickerCommand { get; }
|
||||
public DelegateCommand ClearSupplierCommand { get; }
|
||||
|
||||
public RawMaterialEntryEditDialogViewModel(
|
||||
IRawMaterialEntryService entryService,
|
||||
@@ -157,6 +150,8 @@ public class RawMaterialEntryEditDialogViewModel : BaseViewModel, IDialogResulta
|
||||
ClearMaterialCommand = new DelegateCommand(ClearMaterialSelection);
|
||||
OpenWeightRecordPickerCommand = new DelegateCommand(async () => await OpenWeightRecordPickerAsync());
|
||||
ClearWeightRecordCommand = new DelegateCommand(ClearWeightRecordSelection);
|
||||
OpenSupplierPickerCommand = new DelegateCommand(async () => await OpenSupplierPickerAsync());
|
||||
ClearSupplierCommand = new DelegateCommand(ClearSupplierSelection);
|
||||
SplitCodeDetails.CollectionChanged += OnSplitCodeDetailsCollectionChanged;
|
||||
_ = LoadAllAsync();
|
||||
}
|
||||
@@ -166,7 +161,7 @@ public class RawMaterialEntryEditDialogViewModel : BaseViewModel, IDialogResulta
|
||||
await Task.WhenAll(LoadDictOptionsAsync(), LoadMaterialOptionsAsync());
|
||||
}
|
||||
|
||||
private async Task LoadMaterialOptionsAsync()
|
||||
protected async Task LoadMaterialOptionsAsync()
|
||||
{
|
||||
try
|
||||
{
|
||||
@@ -228,11 +223,13 @@ public class RawMaterialEntryEditDialogViewModel : BaseViewModel, IDialogResulta
|
||||
});
|
||||
PopulateOptions(StatusOptions, statusOpts, Array.Empty<KeyValuePair<string, string>>());
|
||||
ApplyDefaultEntryStatusForAdd();
|
||||
ApplyHiddenFieldDefaultsForAdd();
|
||||
}
|
||||
catch
|
||||
{
|
||||
FillFallbackOptions();
|
||||
ApplyDefaultEntryStatusForAdd();
|
||||
ApplyHiddenFieldDefaultsForAdd();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -270,7 +267,7 @@ public class RawMaterialEntryEditDialogViewModel : BaseViewModel, IDialogResulta
|
||||
PopulateOptions(IsSpecialAdoptionOptions, Array.Empty<KeyValuePair<string, string>>(), ynDefault);
|
||||
}
|
||||
|
||||
private async Task AutoGenerateBarcodeAsync(string materialCode)
|
||||
protected async Task AutoGenerateBarcodeAsync(string materialCode)
|
||||
{
|
||||
IsGenerating = true;
|
||||
try
|
||||
@@ -286,7 +283,7 @@ public class RawMaterialEntryEditDialogViewModel : BaseViewModel, IDialogResulta
|
||||
finally { IsGenerating = false; }
|
||||
}
|
||||
|
||||
public void InitializeForAdd()
|
||||
public virtual void InitializeForAdd()
|
||||
{
|
||||
_selectedMaterial = null;
|
||||
RaisePropertyChanged(nameof(SelectedMaterial));
|
||||
@@ -299,10 +296,10 @@ public class RawMaterialEntryEditDialogViewModel : BaseViewModel, IDialogResulta
|
||||
};
|
||||
InitializeSplitCodeDetailsFromEntry();
|
||||
ApplyDefaultEntryStatusForAdd();
|
||||
ApplyHiddenFieldDefaultsForAdd();
|
||||
RaisePropertyChanged(nameof(IsAddMode));
|
||||
RaisePropertyChanged(nameof(DialogTitle));
|
||||
RaisePropertyChanged(nameof(TotalWeightInput));
|
||||
RaisePropertyChanged(nameof(TotalPortionsInput));
|
||||
RaisePropertyChanged(nameof(IsSpecialAdoptionValue));
|
||||
RaisePropertyChanged(nameof(SplitTotalPortionsDisplay));
|
||||
RaisePropertyChanged(nameof(SplitPortionWeightDisplay));
|
||||
@@ -346,20 +343,20 @@ public class RawMaterialEntryEditDialogViewModel : BaseViewModel, IDialogResulta
|
||||
RaisePropertyChanged(nameof(IsAddMode));
|
||||
RaisePropertyChanged(nameof(DialogTitle));
|
||||
RaisePropertyChanged(nameof(TotalWeightInput));
|
||||
RaisePropertyChanged(nameof(TotalPortionsInput));
|
||||
RaisePropertyChanged(nameof(IsSpecialAdoptionValue));
|
||||
RaisePropertyChanged(nameof(SplitTotalPortionsDisplay));
|
||||
RaisePropertyChanged(nameof(SplitPortionWeightDisplay));
|
||||
RaisePropertyChanged(nameof(SplitPortionPackagesDisplay));
|
||||
}
|
||||
|
||||
private async Task SaveAsync()
|
||||
protected virtual async Task SaveAsync()
|
||||
{
|
||||
if (Entry == null) return;
|
||||
try
|
||||
{
|
||||
ApplyFirstSplitDetailToEntry();
|
||||
RecalculatePortionWeight();
|
||||
ApplySplitDetailsToEntry();
|
||||
ApplyDefaultEntryStatusForAdd();
|
||||
ApplyHiddenFieldDefaultsForAdd();
|
||||
bool ok;
|
||||
if (IsAddMode)
|
||||
{
|
||||
@@ -481,6 +478,34 @@ public class RawMaterialEntryEditDialogViewModel : BaseViewModel, IDialogResulta
|
||||
RaisePropertyChanged(nameof(Entry));
|
||||
}
|
||||
|
||||
private async Task OpenSupplierPickerAsync()
|
||||
{
|
||||
SupplierPickerDialogViewModel? pickerVm = null;
|
||||
bool confirmed;
|
||||
try
|
||||
{
|
||||
confirmed = await HandyControl.Controls.Dialog.Show<SupplierPickerDialogView>()
|
||||
.Initialize<SupplierPickerDialogViewModel>(vm => pickerVm = vm)
|
||||
.GetResultAsync<bool>();
|
||||
}
|
||||
catch { return; }
|
||||
|
||||
if (!confirmed || pickerVm?.SelectedSupplier == null || Entry == null) return;
|
||||
|
||||
var selected = pickerVm.SelectedSupplier;
|
||||
Entry.SupplierId = selected.Id;
|
||||
Entry.SupplierName = selected.SupplierName;
|
||||
RaisePropertyChanged(nameof(Entry));
|
||||
}
|
||||
|
||||
private void ClearSupplierSelection()
|
||||
{
|
||||
if (Entry == null) return;
|
||||
Entry.SupplierId = null;
|
||||
Entry.SupplierName = null;
|
||||
RaisePropertyChanged(nameof(Entry));
|
||||
}
|
||||
|
||||
private void RecalculateShelfLife(MesMixerMaterial? material)
|
||||
{
|
||||
if (Entry == null || material?.ShelfLifeDays == null || material.ShelfLifeDays <= 0)
|
||||
@@ -491,23 +516,7 @@ public class RawMaterialEntryEditDialogViewModel : BaseViewModel, IDialogResulta
|
||||
Entry.ShelfLife = DateTime.Now.Date.AddDays(material.ShelfLifeDays.Value).ToString("yyyy-MM-dd");
|
||||
}
|
||||
|
||||
private void RecalculatePortionWeight()
|
||||
{
|
||||
if (Entry == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (Entry.TotalWeight.HasValue && Entry.TotalPortions.HasValue && Entry.TotalPortions.Value > 0)
|
||||
{
|
||||
Entry.PortionWeight = Math.Round(Entry.TotalWeight.Value / Entry.TotalPortions.Value, 2, MidpointRounding.AwayFromZero);
|
||||
return;
|
||||
}
|
||||
|
||||
Entry.PortionWeight = null;
|
||||
}
|
||||
|
||||
private void ApplyDefaultEntryStatusForAdd()
|
||||
protected void ApplyDefaultEntryStatusForAdd()
|
||||
{
|
||||
if (!IsAddMode || Entry == null || !string.IsNullOrWhiteSpace(Entry.Status))
|
||||
{
|
||||
@@ -526,19 +535,92 @@ public class RawMaterialEntryEditDialogViewModel : BaseViewModel, IDialogResulta
|
||||
Entry.Status = "0";
|
||||
}
|
||||
|
||||
private void InitializeSplitCodeDetailsFromEntry()
|
||||
/// <summary>
|
||||
/// 新增模式下为已在前端隐藏的字段补充默认值,确保保存到后端时不为空:
|
||||
/// 检测结果=未检、检测状态=送样、打印标记=未打印、入库结存=否。
|
||||
/// 字典就绪时取字典 code,未就绪时回退到约定的常用 code。
|
||||
/// </summary>
|
||||
protected void ApplyHiddenFieldDefaultsForAdd()
|
||||
{
|
||||
if (!IsAddMode || Entry == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (string.IsNullOrWhiteSpace(Entry.TestResult))
|
||||
{
|
||||
Entry.TestResult = ResolveDefaultOptionValue(TestResultOptions, "未检", "0");
|
||||
}
|
||||
if (string.IsNullOrWhiteSpace(Entry.TestStatus))
|
||||
{
|
||||
Entry.TestStatus = ResolveDefaultOptionValue(TestStatusOptions, "送样", "0");
|
||||
}
|
||||
if (string.IsNullOrWhiteSpace(Entry.PrintFlag))
|
||||
{
|
||||
Entry.PrintFlag = ResolveDefaultOptionValue(PrintFlagOptions, "未打印", "0");
|
||||
}
|
||||
if (string.IsNullOrWhiteSpace(Entry.StockBalance))
|
||||
{
|
||||
Entry.StockBalance = ResolveDefaultOptionValue(StockBalanceOptions, "否", "0");
|
||||
}
|
||||
|
||||
RaisePropertyChanged(nameof(Entry));
|
||||
}
|
||||
|
||||
/// <summary>在字典选项中按 Key(显示标签)查找对应 Value(实际 code),找不到则回退到 fallback。</summary>
|
||||
private static string ResolveDefaultOptionValue(
|
||||
ObservableCollection<KeyValuePair<string, string>> options,
|
||||
string keyLabel,
|
||||
string fallbackValue)
|
||||
{
|
||||
var match = options.FirstOrDefault(x =>
|
||||
string.Equals(x.Key?.Trim(), keyLabel, StringComparison.OrdinalIgnoreCase));
|
||||
return !string.IsNullOrWhiteSpace(match.Value) ? match.Value : fallbackValue;
|
||||
}
|
||||
|
||||
protected void InitializeSplitCodeDetailsFromEntry()
|
||||
{
|
||||
SplitCodeDetails.Clear();
|
||||
SplitCodeDetails.Add(new RawMaterialSplitDetailItem
|
||||
|
||||
// 三个字段是按拆码明细多行拼接的字符串(末尾带 /),解析回明细列表
|
||||
var portionsArr = SplitJoinedValues(Entry?.TotalPortions);
|
||||
var weightArr = SplitJoinedValues(Entry?.PortionWeight);
|
||||
var packagesArr = SplitJoinedValues(Entry?.PortionPackages);
|
||||
var rowCount = Math.Max(1, Math.Max(portionsArr.Length, Math.Max(weightArr.Length, packagesArr.Length)));
|
||||
|
||||
for (var i = 0; i < rowCount; i++)
|
||||
{
|
||||
Portions = Entry?.TotalPortions,
|
||||
PortionWeight = Entry?.PortionWeight,
|
||||
PortionPackages = Entry?.PortionPackages,
|
||||
WarehouseLocation = Entry?.WarehouseLocation
|
||||
});
|
||||
SplitCodeDetails.Add(new RawMaterialSplitDetailItem
|
||||
{
|
||||
Portions = TryParseInt(GetAt(portionsArr, i)),
|
||||
PortionWeight = TryParseDouble(GetAt(weightArr, i)),
|
||||
PortionPackages = TryParseInt(GetAt(packagesArr, i)),
|
||||
// 库位是单值字段,仅赋给首行
|
||||
WarehouseLocation = i == 0 ? Entry?.WarehouseLocation : null,
|
||||
});
|
||||
}
|
||||
|
||||
RaisePropertyChanged(nameof(SplitCodeTableHeight));
|
||||
}
|
||||
|
||||
private static string[] SplitJoinedValues(string? value)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(value)) return Array.Empty<string>();
|
||||
return value
|
||||
.Split('/', StringSplitOptions.RemoveEmptyEntries)
|
||||
.Select(x => x.Trim())
|
||||
.Where(x => x.Length > 0)
|
||||
.ToArray();
|
||||
}
|
||||
|
||||
private static string? GetAt(string[] arr, int index) => index < arr.Length ? arr[index] : null;
|
||||
|
||||
private static int? TryParseInt(string? text)
|
||||
=> int.TryParse(text, NumberStyles.Integer, CultureInfo.InvariantCulture, out var v) ? v : null;
|
||||
|
||||
private static double? TryParseDouble(string? text)
|
||||
=> double.TryParse(text, NumberStyles.Float, CultureInfo.InvariantCulture, out var v) ? v : null;
|
||||
|
||||
private void AddSplitDetailRow()
|
||||
{
|
||||
SplitCodeDetails.Add(new RawMaterialSplitDetailItem());
|
||||
@@ -625,24 +707,29 @@ public class RawMaterialEntryEditDialogViewModel : BaseViewModel, IDialogResulta
|
||||
return value.Value.ToString("0.##", CultureInfo.InvariantCulture);
|
||||
}
|
||||
|
||||
private void ApplyFirstSplitDetailToEntry()
|
||||
/// <summary>
|
||||
/// 把 SplitCodeDetails 全部明细行的「份数 / 每份重量 / 每份包数」按 "x/y/z/" 拼接后持久化到 Entry,
|
||||
/// 库位仍取首行(业务上库位是单值)。与 SplitTotalPortionsDisplay 等只读展示属性同规则。
|
||||
/// </summary>
|
||||
private void ApplySplitDetailsToEntry()
|
||||
{
|
||||
if (Entry == null || SplitCodeDetails.Count == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
if (Entry == null) return;
|
||||
|
||||
var first = SplitCodeDetails[0];
|
||||
Entry.TotalPortions = first.Portions;
|
||||
Entry.PortionWeight = first.PortionWeight;
|
||||
Entry.PortionPackages = first.PortionPackages;
|
||||
Entry.WarehouseLocation = first.WarehouseLocation;
|
||||
Entry.TotalPortions = JoinSplitValue(it => it.Portions?.ToString(CultureInfo.InvariantCulture), true);
|
||||
Entry.PortionWeight = JoinSplitValue(it => FormatNullableDecimal(it.PortionWeight), true);
|
||||
Entry.PortionPackages = JoinSplitValue(it => it.PortionPackages?.ToString(CultureInfo.InvariantCulture), true);
|
||||
|
||||
if (SplitCodeDetails.Count > 0)
|
||||
{
|
||||
Entry.WarehouseLocation = SplitCodeDetails[0].WarehouseLocation;
|
||||
}
|
||||
}
|
||||
|
||||
private double CalculateSplitCodeTableHeight()
|
||||
{
|
||||
const double headerHeight = 36d;
|
||||
const double rowHeight = 36d;
|
||||
// 与拆码明细 XAML 行高保持一致:表头 40px、数据行 44px
|
||||
const double headerHeight = 40d;
|
||||
const double rowHeight = 44d;
|
||||
const double framePadding = 16d;
|
||||
const double minRows = 1d;
|
||||
const double maxRows = 6d;
|
||||
|
||||
@@ -1,17 +1,288 @@
|
||||
using Prism.Commands;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.IO;
|
||||
using System.Text.Json;
|
||||
using YY.Admin.Core.Entity;
|
||||
using YY.Admin.Core.Services;
|
||||
using YY.Admin.Services.Service;
|
||||
|
||||
namespace YY.Admin.ViewModels.RawMaterialEntry;
|
||||
|
||||
/// <summary>
|
||||
/// 「新增原料入场记录」独立页面:左侧表单逻辑继承编辑 VM,右侧展示当日入场简要列表并支持选中回填模板。
|
||||
/// </summary>
|
||||
public class RawMaterialEntryOperationViewModel : RawMaterialEntryEditDialogViewModel
|
||||
{
|
||||
private const int TodayListFetchSize = 5000;
|
||||
private readonly IRawMaterialCardService _rawMaterialCardService;
|
||||
|
||||
private static readonly JsonSerializerOptions LayoutJsonOpts = new()
|
||||
{
|
||||
PropertyNameCaseInsensitive = true,
|
||||
WriteIndented = true
|
||||
};
|
||||
|
||||
private static string LayoutFilePath => Path.Combine(
|
||||
Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData),
|
||||
"YY.Admin",
|
||||
"raw-material-entry-add-layout.json");
|
||||
|
||||
private bool _suppressTodaySelectionReaction;
|
||||
|
||||
public ObservableCollection<MesXslRawMaterialEntry> TodayEntries { get; } = new();
|
||||
|
||||
private MesXslRawMaterialEntry? _selectedTodayEntry;
|
||||
public MesXslRawMaterialEntry? SelectedTodayEntry
|
||||
{
|
||||
get => _selectedTodayEntry;
|
||||
set
|
||||
{
|
||||
if (!SetProperty(ref _selectedTodayEntry, value)) return;
|
||||
if (_suppressTodaySelectionReaction || value == null) return;
|
||||
_ = ApplyTodayRowToFormAsync(value);
|
||||
}
|
||||
}
|
||||
|
||||
private bool _isRightPanelExpanded = true;
|
||||
public bool IsRightPanelExpanded
|
||||
{
|
||||
get => _isRightPanelExpanded;
|
||||
set
|
||||
{
|
||||
if (!SetProperty(ref _isRightPanelExpanded, value)) return;
|
||||
SaveLayoutState();
|
||||
}
|
||||
}
|
||||
|
||||
private double _expandedRightPanelWidth = 280;
|
||||
/// <summary>右侧面板展开时的目标宽度(像素),由 GridSplitter 拖拽结束时回写。</summary>
|
||||
public double ExpandedRightPanelWidth
|
||||
{
|
||||
get => _expandedRightPanelWidth;
|
||||
private set
|
||||
{
|
||||
var v = Math.Clamp(value, 200, 560);
|
||||
if (SetProperty(ref _expandedRightPanelWidth, v))
|
||||
SaveLayoutState();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>仅当入场记录已保存(有 Id)且存在拆码明细时允许生成原材料卡片。</summary>
|
||||
public bool CanGenerateCards => !string.IsNullOrWhiteSpace(Entry?.Id) && SplitCodeDetails.Count > 0;
|
||||
|
||||
public DelegateCommand ToggleRightPanelCommand { get; }
|
||||
public DelegateCommand RefreshTodayEntriesCommand { get; }
|
||||
public DelegateCommand GenerateRawMaterialCardsCommand { get; }
|
||||
|
||||
public RawMaterialEntryOperationViewModel(
|
||||
IRawMaterialEntryService entryService,
|
||||
IJeecgDictSyncService dictSyncService,
|
||||
IMixerMaterialService mixerMaterialService,
|
||||
IRawMaterialCardService rawMaterialCardService,
|
||||
IContainerExtension container,
|
||||
IRegionManager regionManager)
|
||||
: base(entryService, dictSyncService, mixerMaterialService, container, regionManager)
|
||||
{
|
||||
_rawMaterialCardService = rawMaterialCardService;
|
||||
LoadLayoutState();
|
||||
ToggleRightPanelCommand = new DelegateCommand(() => IsRightPanelExpanded = !IsRightPanelExpanded);
|
||||
RefreshTodayEntriesCommand = new DelegateCommand(async () => await LoadTodayEntriesAsync());
|
||||
GenerateRawMaterialCardsCommand = new DelegateCommand(async () => await GenerateRawMaterialCardsAsync());
|
||||
SplitCodeDetails.CollectionChanged += (_, _) => RaisePropertyChanged(nameof(CanGenerateCards));
|
||||
}
|
||||
|
||||
public override void InitializeForAdd()
|
||||
{
|
||||
_suppressTodaySelectionReaction = true;
|
||||
_selectedTodayEntry = null;
|
||||
RaisePropertyChanged(nameof(SelectedTodayEntry));
|
||||
_suppressTodaySelectionReaction = false;
|
||||
base.InitializeForAdd();
|
||||
RaisePropertyChanged(nameof(CanGenerateCards));
|
||||
}
|
||||
|
||||
/// <summary>页面首次加载时拉取「今日」列表(由 View Loaded 调用)。</summary>
|
||||
public async Task LoadTodayEntriesOnFirstShowAsync() => await LoadTodayEntriesAsync();
|
||||
|
||||
/// <summary>用户在拖拽结束 GridSplitter 后提交实际列宽。</summary>
|
||||
public void CommitRightPanelWidthFromView(double actualWidth)
|
||||
{
|
||||
if (!IsRightPanelExpanded || actualWidth < 1) return;
|
||||
ExpandedRightPanelWidth = actualWidth;
|
||||
}
|
||||
|
||||
private async Task LoadTodayEntriesAsync()
|
||||
{
|
||||
try
|
||||
{
|
||||
IsLoading = true;
|
||||
var result = await EntryService.PageAsync(1, TodayListFetchSize);
|
||||
var today = DateTime.Today;
|
||||
var rows = result.Records
|
||||
.Where(e => IsTodayEntry(e, today))
|
||||
.OrderByDescending(e => e.EntryTime ?? e.CreateTime ?? DateTime.MinValue)
|
||||
.ToList();
|
||||
TodayEntries.Clear();
|
||||
foreach (var r in rows)
|
||||
TodayEntries.Add(r);
|
||||
}
|
||||
catch
|
||||
{
|
||||
// 列表失败不阻断左侧新增(与物料加载策略一致)
|
||||
}
|
||||
finally
|
||||
{
|
||||
IsLoading = false;
|
||||
}
|
||||
}
|
||||
|
||||
private static bool IsTodayEntry(MesXslRawMaterialEntry e, DateTime today)
|
||||
{
|
||||
var byEntry = e.EntryTime?.Date == today;
|
||||
var byCreate = e.CreateTime?.Date == today;
|
||||
return byEntry || byCreate;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 点击右侧今日记录:直接以「编辑模式」加载源记录到左侧表单(保留 Id/条码/批次号),
|
||||
/// 不再走「新增模板」逻辑,避免重新生成条码、覆盖原数据。
|
||||
/// </summary>
|
||||
private async Task ApplyTodayRowToFormAsync(MesXslRawMaterialEntry src)
|
||||
{
|
||||
// 复用基类 InitializeForEdit:保留 Id/Barcode/BatchNo/状态等所有字段,标题自动切到「编辑原料入场记录」
|
||||
base.InitializeForEdit(src);
|
||||
RaisePropertyChanged(nameof(CanGenerateCards));
|
||||
|
||||
// 若物料列表此前未加载导致选中项未回填,则补一次拉取(与编辑弹窗逻辑一致)
|
||||
if (_pendingMaterialId != null)
|
||||
await LoadMaterialOptionsAsync();
|
||||
}
|
||||
|
||||
/// <summary>编辑保存成功后:刷新右侧今日列表并切回新增态,避免连续误改同一条。</summary>
|
||||
protected override async Task SaveAsync()
|
||||
{
|
||||
var wasEdit = !IsAddMode;
|
||||
await base.SaveAsync();
|
||||
RaisePropertyChanged(nameof(CanGenerateCards));
|
||||
if (wasEdit && Result && CloseAction == null)
|
||||
{
|
||||
HandyControl.Controls.MessageBox.Success("编辑成功!");
|
||||
await LoadTodayEntriesAsync();
|
||||
InitializeForAdd();
|
||||
}
|
||||
}
|
||||
|
||||
private async Task GenerateRawMaterialCardsAsync()
|
||||
{
|
||||
if (Entry == null || string.IsNullOrWhiteSpace(Entry.Id))
|
||||
{
|
||||
HandyControl.Controls.MessageBox.Warning("请先保存入场记录再生成原材料卡片!");
|
||||
return;
|
||||
}
|
||||
if (SplitCodeDetails.Count == 0)
|
||||
{
|
||||
HandyControl.Controls.MessageBox.Warning("当前入场记录无拆分明细,无法生成原材料卡片!");
|
||||
return;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
IsLoading = true;
|
||||
var globalIndex = 1;
|
||||
var baseBarcode = Entry.Barcode ?? "";
|
||||
var failCount = 0;
|
||||
|
||||
foreach (var detail in SplitCodeDetails)
|
||||
{
|
||||
var portions = detail.Portions ?? 0;
|
||||
for (var i = 0; i < portions; i++)
|
||||
{
|
||||
var card = new MesXslRawMaterialCard
|
||||
{
|
||||
Barcode = baseBarcode + globalIndex.ToString("D3"),
|
||||
BatchNo = Entry.BatchNo,
|
||||
EntryDate = Entry.EntryTime?.Date ?? DateTime.Today,
|
||||
MaterialId = Entry.MaterialId,
|
||||
MaterialName = Entry.MaterialName,
|
||||
SupplierId = Entry.SupplierId,
|
||||
SupplierName = Entry.SupplierName,
|
||||
ManufacturerMaterialName = Entry.ManufacturerMaterialName,
|
||||
ShelfLife = Entry.ShelfLife,
|
||||
TotalWeight = detail.PortionWeight.HasValue ? (decimal?)detail.PortionWeight.Value : null,
|
||||
RemainingWeight = detail.PortionWeight.HasValue ? (decimal?)detail.PortionWeight.Value : null,
|
||||
RemainingQuantity = detail.PortionPackages,
|
||||
WarehouseArea = detail.WarehouseLocation,
|
||||
UnloadOperator = Entry.UnloadOperator,
|
||||
Status = "1",
|
||||
TestResult = "0",
|
||||
PriorityPickup = "0",
|
||||
TenantId = Entry.TenantId
|
||||
};
|
||||
var ok = await _rawMaterialCardService.AddAsync(card);
|
||||
if (!ok) failCount++;
|
||||
globalIndex++;
|
||||
}
|
||||
}
|
||||
|
||||
// 更新打印状态为「已打印」
|
||||
Entry.PrintFlag = "1";
|
||||
await EntryService.EditAsync(Entry);
|
||||
RaisePropertyChanged(nameof(Entry));
|
||||
|
||||
var total = globalIndex - 1;
|
||||
if (failCount == 0)
|
||||
HandyControl.Controls.MessageBox.Success($"已生成 {total} 张原材料卡片,打印状态已更新为「已打印」!");
|
||||
else
|
||||
HandyControl.Controls.MessageBox.Warning($"共生成 {total} 张,其中 {failCount} 张失败,请检查网络后重试!");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
HandyControl.Controls.MessageBox.Error($"生成原材料卡片失败:{ex.Message}");
|
||||
}
|
||||
finally
|
||||
{
|
||||
IsLoading = false;
|
||||
}
|
||||
}
|
||||
|
||||
private void LoadLayoutState()
|
||||
{
|
||||
try
|
||||
{
|
||||
if (!File.Exists(LayoutFilePath)) return;
|
||||
var json = File.ReadAllText(LayoutFilePath);
|
||||
var dto = JsonSerializer.Deserialize<AddPageLayoutDto>(json, LayoutJsonOpts);
|
||||
if (dto == null) return;
|
||||
if (dto.ExpandedWidth is > 0 and < 2000)
|
||||
_expandedRightPanelWidth = Math.Clamp(dto.ExpandedWidth.Value, 200, 560);
|
||||
if (dto.IsExpanded.HasValue)
|
||||
_isRightPanelExpanded = dto.IsExpanded.Value;
|
||||
RaisePropertyChanged(nameof(ExpandedRightPanelWidth));
|
||||
RaisePropertyChanged(nameof(IsRightPanelExpanded));
|
||||
}
|
||||
catch { /* 布局文件损坏时忽略 */ }
|
||||
}
|
||||
|
||||
private void SaveLayoutState()
|
||||
{
|
||||
try
|
||||
{
|
||||
var dir = Path.GetDirectoryName(LayoutFilePath);
|
||||
if (!string.IsNullOrEmpty(dir))
|
||||
Directory.CreateDirectory(dir);
|
||||
var dto = new AddPageLayoutDto
|
||||
{
|
||||
ExpandedWidth = _expandedRightPanelWidth,
|
||||
IsExpanded = _isRightPanelExpanded
|
||||
};
|
||||
File.WriteAllText(LayoutFilePath, JsonSerializer.Serialize(dto, LayoutJsonOpts));
|
||||
}
|
||||
catch { /* 写本地失败时不影响业务 */ }
|
||||
}
|
||||
|
||||
private sealed class AddPageLayoutDto
|
||||
{
|
||||
public double? ExpandedWidth { get; set; }
|
||||
public bool? IsExpanded { get; set; }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,223 @@
|
||||
<UserControl x:Class="YY.Admin.Views.RawMaterialCard.RawMaterialCardEditDialogView"
|
||||
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/"
|
||||
prism:ViewModelLocator.AutoWireViewModel="True"
|
||||
mc:Ignorable="d"
|
||||
Width="780"
|
||||
MinHeight="480">
|
||||
|
||||
<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="{Binding DialogTitle}" HorizontalAlignment="Left" VerticalAlignment="Top"/>
|
||||
<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" Margin="0,4,4,0"/>
|
||||
</hc:SimplePanel>
|
||||
|
||||
<!-- 表单区域 -->
|
||||
<hc:ScrollViewer Grid.Row="1" IsInertiaEnabled="True">
|
||||
<StackPanel Margin="20,0,20,0">
|
||||
<hc:Row Gutter="10">
|
||||
|
||||
<!-- 条码 -->
|
||||
<hc:Col Span="12">
|
||||
<hc:TextBox Text="{Binding Card.Barcode, UpdateSourceTrigger=PropertyChanged}"
|
||||
hc:InfoElement.Title="条码"
|
||||
hc:InfoElement.TitleWidth="90"
|
||||
hc:InfoElement.TitlePlacement="Left"
|
||||
hc:InfoElement.Placeholder="请输入条码"
|
||||
hc:InfoElement.ShowClearButton="True"
|
||||
Margin="0,0,0,16"/>
|
||||
</hc:Col>
|
||||
|
||||
<!-- 批次号 -->
|
||||
<hc:Col Span="12">
|
||||
<hc:TextBox Text="{Binding Card.BatchNo, UpdateSourceTrigger=PropertyChanged}"
|
||||
hc:InfoElement.Title="批次号"
|
||||
hc:InfoElement.TitleWidth="90"
|
||||
hc:InfoElement.TitlePlacement="Left"
|
||||
hc:InfoElement.Placeholder="请输入批次号"
|
||||
hc:InfoElement.ShowClearButton="True"
|
||||
Margin="0,0,0,16"/>
|
||||
</hc:Col>
|
||||
|
||||
<!-- 入场日期 -->
|
||||
<hc:Col Span="12">
|
||||
<hc:DatePicker SelectedDate="{Binding Card.EntryDate}"
|
||||
hc:InfoElement.Title="入场日期"
|
||||
hc:InfoElement.TitleWidth="90"
|
||||
hc:InfoElement.TitlePlacement="Left"
|
||||
hc:InfoElement.Placeholder="请选择入场日期"
|
||||
Margin="0,0,0,16"/>
|
||||
</hc:Col>
|
||||
|
||||
<!-- 物料名称 -->
|
||||
<hc:Col Span="12">
|
||||
<hc:TextBox Text="{Binding Card.MaterialName, UpdateSourceTrigger=PropertyChanged}"
|
||||
hc:InfoElement.Title="物料名称"
|
||||
hc:InfoElement.TitleWidth="90"
|
||||
hc:InfoElement.TitlePlacement="Left"
|
||||
hc:InfoElement.Placeholder="请输入物料名称"
|
||||
hc:InfoElement.Necessary="True"
|
||||
hc:InfoElement.Symbol="*"
|
||||
hc:InfoElement.ShowClearButton="True"
|
||||
Margin="0,0,0,16"/>
|
||||
</hc:Col>
|
||||
|
||||
<!-- 供应商名称 -->
|
||||
<hc:Col Span="12">
|
||||
<hc:TextBox Text="{Binding Card.SupplierName, UpdateSourceTrigger=PropertyChanged}"
|
||||
hc:InfoElement.Title="供应商名称"
|
||||
hc:InfoElement.TitleWidth="90"
|
||||
hc:InfoElement.TitlePlacement="Left"
|
||||
hc:InfoElement.Placeholder="请输入供应商名称"
|
||||
hc:InfoElement.ShowClearButton="True"
|
||||
Margin="0,0,0,16"/>
|
||||
</hc:Col>
|
||||
|
||||
<!-- 厂家物料名称 -->
|
||||
<hc:Col Span="12">
|
||||
<hc:TextBox Text="{Binding Card.ManufacturerMaterialName, UpdateSourceTrigger=PropertyChanged}"
|
||||
hc:InfoElement.Title="厂家物料名称"
|
||||
hc:InfoElement.TitleWidth="90"
|
||||
hc:InfoElement.TitlePlacement="Left"
|
||||
hc:InfoElement.Placeholder="请输入厂家物料名称"
|
||||
hc:InfoElement.ShowClearButton="True"
|
||||
Margin="0,0,0,16"/>
|
||||
</hc:Col>
|
||||
|
||||
<!-- 保质期 -->
|
||||
<hc:Col Span="12">
|
||||
<hc:TextBox Text="{Binding Card.ShelfLife, UpdateSourceTrigger=PropertyChanged}"
|
||||
hc:InfoElement.Title="保质期"
|
||||
hc:InfoElement.TitleWidth="90"
|
||||
hc:InfoElement.TitlePlacement="Left"
|
||||
hc:InfoElement.Placeholder="请输入保质期"
|
||||
hc:InfoElement.ShowClearButton="True"
|
||||
Margin="0,0,0,16"/>
|
||||
</hc:Col>
|
||||
|
||||
<!-- 库区 -->
|
||||
<hc:Col Span="12">
|
||||
<hc:TextBox Text="{Binding Card.WarehouseArea, UpdateSourceTrigger=PropertyChanged}"
|
||||
hc:InfoElement.Title="库区"
|
||||
hc:InfoElement.TitleWidth="90"
|
||||
hc:InfoElement.TitlePlacement="Left"
|
||||
hc:InfoElement.Placeholder="请输入库区"
|
||||
hc:InfoElement.ShowClearButton="True"
|
||||
Margin="0,0,0,16"/>
|
||||
</hc:Col>
|
||||
|
||||
<!-- 卸货人 -->
|
||||
<hc:Col Span="12">
|
||||
<hc:TextBox Text="{Binding Card.UnloadOperator, UpdateSourceTrigger=PropertyChanged}"
|
||||
hc:InfoElement.Title="卸货人"
|
||||
hc:InfoElement.TitleWidth="90"
|
||||
hc:InfoElement.TitlePlacement="Left"
|
||||
hc:InfoElement.Placeholder="请输入卸货人"
|
||||
hc:InfoElement.ShowClearButton="True"
|
||||
Margin="0,0,0,16"/>
|
||||
</hc:Col>
|
||||
|
||||
<!-- 总重 -->
|
||||
<hc:Col Span="12">
|
||||
<hc:NumericUpDown Value="{Binding Card.TotalWeight}"
|
||||
Minimum="0"
|
||||
DecimalPlaces="3"
|
||||
Style="{StaticResource NumericUpDownPlus}"
|
||||
hc:InfoElement.Title="总重(kg)"
|
||||
hc:InfoElement.TitleWidth="90"
|
||||
hc:InfoElement.TitlePlacement="Left"
|
||||
hc:InfoElement.Placeholder="请输入总重"
|
||||
hc:InfoElement.ShowClearButton="True"
|
||||
Margin="0,0,0,16"/>
|
||||
</hc:Col>
|
||||
|
||||
<!-- 剩余重量 -->
|
||||
<hc:Col Span="12">
|
||||
<hc:NumericUpDown Value="{Binding Card.RemainingWeight}"
|
||||
Minimum="0"
|
||||
DecimalPlaces="3"
|
||||
Style="{StaticResource NumericUpDownPlus}"
|
||||
hc:InfoElement.Title="剩余重量"
|
||||
hc:InfoElement.TitleWidth="90"
|
||||
hc:InfoElement.TitlePlacement="Left"
|
||||
hc:InfoElement.ShowClearButton="True"
|
||||
Margin="0,0,0,16"/>
|
||||
</hc:Col>
|
||||
|
||||
<!-- 剩余数量 -->
|
||||
<hc:Col Span="12">
|
||||
<hc:NumericUpDown Value="{Binding Card.RemainingQuantity}"
|
||||
Minimum="0"
|
||||
DecimalPlaces="0"
|
||||
Style="{StaticResource NumericUpDownPlus}"
|
||||
hc:InfoElement.Title="剩余数量"
|
||||
hc:InfoElement.TitleWidth="90"
|
||||
hc:InfoElement.TitlePlacement="Left"
|
||||
hc:InfoElement.ShowClearButton="True"
|
||||
Margin="0,0,0,16"/>
|
||||
</hc:Col>
|
||||
|
||||
<!-- 状态 -->
|
||||
<hc:Col Span="12">
|
||||
<hc:ComboBox SelectedValuePath="Value"
|
||||
DisplayMemberPath="Key"
|
||||
ItemsSource="{Binding StatusOptions}"
|
||||
SelectedValue="{Binding Card.Status}"
|
||||
hc:InfoElement.Title="状态"
|
||||
hc:InfoElement.TitleWidth="90"
|
||||
hc:InfoElement.TitlePlacement="Left"
|
||||
Margin="0,0,0,16"/>
|
||||
</hc:Col>
|
||||
|
||||
<!-- 检测结果 -->
|
||||
<hc:Col Span="12">
|
||||
<hc:ComboBox SelectedValuePath="Value"
|
||||
DisplayMemberPath="Key"
|
||||
ItemsSource="{Binding TestResultOptions}"
|
||||
SelectedValue="{Binding Card.TestResult}"
|
||||
hc:InfoElement.Title="检测结果"
|
||||
hc:InfoElement.TitleWidth="90"
|
||||
hc:InfoElement.TitlePlacement="Left"
|
||||
Margin="0,0,0,16"/>
|
||||
</hc:Col>
|
||||
|
||||
<!-- 物料描述 -->
|
||||
<hc:Col Span="24">
|
||||
<hc:TextBox Text="{Binding Card.MaterialDesc, UpdateSourceTrigger=PropertyChanged}"
|
||||
hc:InfoElement.Title="物料描述"
|
||||
hc:InfoElement.TitleWidth="90"
|
||||
hc:InfoElement.TitlePlacement="Left"
|
||||
hc:InfoElement.Placeholder="请输入物料描述"
|
||||
TextWrapping="Wrap"
|
||||
AcceptsReturn="True"
|
||||
MinHeight="60"
|
||||
MaxHeight="100"
|
||||
VerticalScrollBarVisibility="Auto"
|
||||
Margin="0,0,0,16"/>
|
||||
</hc:Col>
|
||||
|
||||
</hc:Row>
|
||||
</StackPanel>
|
||||
</hc:ScrollViewer>
|
||||
|
||||
<!-- 按钮区域 -->
|
||||
<StackPanel Grid.Row="2" Orientation="Horizontal" HorizontalAlignment="Center" Margin="20">
|
||||
<Button Content="取消" Command="{Binding CancelCommand}" Style="{StaticResource ButtonDefault}" Margin="0,0,15,0" Width="100"/>
|
||||
<Button Content="确定" Command="{Binding SaveCommand}" Style="{StaticResource ButtonPrimary}" Width="100"/>
|
||||
</StackPanel>
|
||||
|
||||
</Grid>
|
||||
</UserControl>
|
||||
@@ -0,0 +1,11 @@
|
||||
using System.Windows.Controls;
|
||||
|
||||
namespace YY.Admin.Views.RawMaterialCard;
|
||||
|
||||
public partial class RawMaterialCardEditDialogView : UserControl
|
||||
{
|
||||
public RawMaterialCardEditDialogView()
|
||||
{
|
||||
InitializeComponent();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,177 @@
|
||||
<UserControl x:Class="YY.Admin.Views.RawMaterialCard.RawMaterialCardListView"
|
||||
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:md="http://materialdesigninxaml.net/winfx/xaml/themes"
|
||||
xmlns:prism="http://prismlibrary.com/"
|
||||
prism:ViewModelLocator.AutoWireViewModel="True"
|
||||
mc:Ignorable="d">
|
||||
|
||||
<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=6, Xl=4}">
|
||||
<hc:TextBox Text="{Binding FilterBarcode, UpdateSourceTrigger=PropertyChanged}"
|
||||
Margin="0 0 10 10"
|
||||
hc:InfoElement.Title="条码"
|
||||
hc:InfoElement.TitlePlacement="Left"
|
||||
hc:InfoElement.TitleWidth="65"
|
||||
hc:InfoElement.Placeholder="请输入条码"
|
||||
hc:InfoElement.ShowClearButton="True"/>
|
||||
</hc:Col>
|
||||
<hc:Col Layout="{hc:ColLayout Xs=12, Sm=8, Md=6, Lg=6, Xl=4}">
|
||||
<hc:TextBox Text="{Binding FilterBatchNo, UpdateSourceTrigger=PropertyChanged}"
|
||||
Margin="0 0 10 10"
|
||||
hc:InfoElement.Title="批次号"
|
||||
hc:InfoElement.TitlePlacement="Left"
|
||||
hc:InfoElement.TitleWidth="65"
|
||||
hc:InfoElement.Placeholder="请输入批次号"
|
||||
hc:InfoElement.ShowClearButton="True"/>
|
||||
</hc:Col>
|
||||
<hc:Col Layout="{hc:ColLayout Xs=12, Sm=8, Md=6, Lg=6, Xl=4}">
|
||||
<hc:TextBox Text="{Binding FilterMaterialName, UpdateSourceTrigger=PropertyChanged}"
|
||||
Margin="0 0 10 10"
|
||||
hc:InfoElement.Title="物料名称"
|
||||
hc:InfoElement.TitlePlacement="Left"
|
||||
hc:InfoElement.TitleWidth="65"
|
||||
hc:InfoElement.Placeholder="请输入物料名称"
|
||||
hc:InfoElement.ShowClearButton="True"/>
|
||||
</hc:Col>
|
||||
<hc:Col Layout="{hc:ColLayout Xs=12, Sm=8, Md=6, Lg=6, Xl=4}">
|
||||
<hc:TextBox Text="{Binding FilterSupplierName, UpdateSourceTrigger=PropertyChanged}"
|
||||
Margin="0 0 10 10"
|
||||
hc:InfoElement.Title="供应商"
|
||||
hc:InfoElement.TitlePlacement="Left"
|
||||
hc:InfoElement.TitleWidth="65"
|
||||
hc:InfoElement.Placeholder="请输入供应商名称"
|
||||
hc:InfoElement.ShowClearButton="True"/>
|
||||
</hc:Col>
|
||||
<hc:Col Layout="{hc:ColLayout Xs=12, Sm=8, Md=6, Lg=6, Xl=4}">
|
||||
<hc:ComboBox SelectedValuePath="Value"
|
||||
DisplayMemberPath="Key"
|
||||
ItemsSource="{Binding StatusOptions}"
|
||||
SelectedValue="{Binding FilterStatus}"
|
||||
Margin="0 0 10 10"
|
||||
hc:InfoElement.Title="状态"
|
||||
hc:InfoElement.TitlePlacement="Left"
|
||||
hc:InfoElement.TitleWidth="65"
|
||||
hc:InfoElement.Placeholder="请选择状态"
|
||||
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 ButtonPrimary}" 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 Cards}"
|
||||
AutoGenerateColumns="False"
|
||||
IsReadOnly="True"
|
||||
CanUserAddRows="False"
|
||||
SelectionMode="Extended"
|
||||
SelectionUnit="FullRow"
|
||||
RowHeaderWidth="55"
|
||||
GridLinesVisibility="Horizontal"
|
||||
HorizontalGridLinesBrush="#FFEDEDED"
|
||||
VerticalGridLinesBrush="Transparent"
|
||||
HeadersVisibility="All"
|
||||
ColumnHeaderStyle="{StaticResource CusDataGridColumnHeaderStyle}"
|
||||
Style="{StaticResource CusDataGridStyle}"
|
||||
hc:DataGridAttach.ShowSelectAllButton="True"
|
||||
VerticalScrollBarVisibility="Auto"
|
||||
HorizontalScrollBarVisibility="Auto">
|
||||
<DataGrid.RowHeaderTemplate>
|
||||
<DataTemplate>
|
||||
<CheckBox IsChecked="{Binding IsSelected, RelativeSource={RelativeSource AncestorType=DataGridRow}}"/>
|
||||
</DataTemplate>
|
||||
</DataGrid.RowHeaderTemplate>
|
||||
<DataGrid.Columns>
|
||||
<DataGridTextColumn Header="条码" Binding="{Binding Barcode}" CellStyle="{StaticResource CusDataGridCellStyle}" Width="150"/>
|
||||
<DataGridTextColumn Header="批次号" Binding="{Binding BatchNo}" CellStyle="{StaticResource CusDataGridCellStyle}" Width="130"/>
|
||||
<DataGridTextColumn Header="入场日期" Binding="{Binding EntryDateText}" CellStyle="{StaticResource CusDataGridCellStyle}" Width="100"/>
|
||||
<DataGridTextColumn Header="物料名称" Binding="{Binding MaterialName}" CellStyle="{StaticResource CusDataGridCellStyle}" Width="150"/>
|
||||
<DataGridTextColumn Header="供应商名称" Binding="{Binding SupplierName}" CellStyle="{StaticResource CusDataGridCellStyle}" Width="140"/>
|
||||
<DataGridTextColumn Header="厂家物料名称" Binding="{Binding ManufacturerMaterialName}" CellStyle="{StaticResource CusDataGridCellStyle}" Width="130"/>
|
||||
<DataGridTextColumn Header="保质期" Binding="{Binding ShelfLife}" CellStyle="{StaticResource CusDataGridCellStyle}" Width="90"/>
|
||||
<DataGridTextColumn Header="总重(kg)" Binding="{Binding TotalWeight, StringFormat=N3}" CellStyle="{StaticResource CusDataGridCellStyle}" Width="90"/>
|
||||
<DataGridTextColumn Header="剩余重量" Binding="{Binding RemainingWeight, StringFormat=N3}" CellStyle="{StaticResource CusDataGridCellStyle}" Width="90"/>
|
||||
<DataGridTextColumn Header="剩余数量" Binding="{Binding RemainingQuantity}" CellStyle="{StaticResource CusDataGridCellStyle}" Width="80"/>
|
||||
<DataGridTextColumn Header="状态" Binding="{Binding StatusText}" CellStyle="{StaticResource CusDataGridCellStyle}" Width="70"/>
|
||||
<DataGridTextColumn Header="检测结果" Binding="{Binding TestResultText}" CellStyle="{StaticResource CusDataGridCellStyle}" Width="80"/>
|
||||
<DataGridTextColumn Header="库区" Binding="{Binding WarehouseArea}" CellStyle="{StaticResource CusDataGridCellStyle}" Width="90"/>
|
||||
<DataGridTextColumn Header="卸货人" Binding="{Binding UnloadOperator}" CellStyle="{StaticResource CusDataGridCellStyle}" Width="80"/>
|
||||
<DataGridTemplateColumn Header="优先出库" Width="90">
|
||||
<DataGridTemplateColumn.CellTemplate>
|
||||
<DataTemplate>
|
||||
<CheckBox IsChecked="{Binding PriorityPickupBool, Mode=OneWay}"
|
||||
HorizontalAlignment="Center" VerticalAlignment="Center"
|
||||
Command="{Binding DataContext.TogglePriorityCommand, RelativeSource={RelativeSource AncestorType=DataGrid}}"
|
||||
CommandParameter="{Binding}"/>
|
||||
</DataTemplate>
|
||||
</DataGridTemplateColumn.CellTemplate>
|
||||
</DataGridTemplateColumn>
|
||||
<DataGridTemplateColumn Header="操作" Width="160">
|
||||
<DataGridTemplateColumn.CellTemplate>
|
||||
<DataTemplate>
|
||||
<hc:UniformSpacingPanel Spacing="6" HorizontalAlignment="Center">
|
||||
<Button Content="编辑"
|
||||
Style="{StaticResource ButtonPrimary}"
|
||||
FontSize="12" Height="26" Padding="8,0"
|
||||
Command="{Binding DataContext.EditCommand, RelativeSource={RelativeSource AncestorType=DataGrid}}"
|
||||
CommandParameter="{Binding}"/>
|
||||
<Button Content="删除"
|
||||
Style="{StaticResource ButtonDanger}"
|
||||
FontSize="12" Height="26" Padding="8,0"
|
||||
Command="{Binding DataContext.DeleteCommand, RelativeSource={RelativeSource AncestorType=DataGrid}}"
|
||||
CommandParameter="{Binding}"/>
|
||||
</hc:UniformSpacingPanel>
|
||||
</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"
|
||||
Foreground="{DynamicResource PrimaryTextBrush}"/>
|
||||
<Button Content="下一页" Command="{Binding NextPageCommand}" Style="{StaticResource ButtonDefault}" Width="80"/>
|
||||
</StackPanel>
|
||||
</Grid>
|
||||
</UserControl>
|
||||
@@ -0,0 +1,11 @@
|
||||
using System.Windows.Controls;
|
||||
|
||||
namespace YY.Admin.Views.RawMaterialCard;
|
||||
|
||||
public partial class RawMaterialCardListView : UserControl
|
||||
{
|
||||
public RawMaterialCardListView()
|
||||
{
|
||||
InitializeComponent();
|
||||
}
|
||||
}
|
||||
@@ -4,6 +4,7 @@
|
||||
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:ctrls="clr-namespace:YY.Admin.Controls"
|
||||
xmlns:prism="http://prismlibrary.com/"
|
||||
prism:ViewModelLocator.AutoWireViewModel="True"
|
||||
mc:Ignorable="d"
|
||||
@@ -109,16 +110,15 @@
|
||||
Margin="0,0,0,8"/>
|
||||
</hc:Col>
|
||||
|
||||
<!-- 入场时间 -->
|
||||
<!-- 入场时间:DateTimePicker 弹出层自带「此刻 / 确定」,可同时选择年/月/日/时/分/秒 -->
|
||||
<hc:Col Span="8">
|
||||
<DockPanel Margin="0,0,0,8" LastChildFill="True">
|
||||
<TextBlock DockPanel.Dock="Left" Text="入场时间" Width="80"
|
||||
VerticalAlignment="Center"
|
||||
Foreground="{DynamicResource PrimaryTextBrush}"/>
|
||||
<hc:DateTimePicker SelectedDateTime="{Binding Entry.EntryTime}"
|
||||
hc:InfoElement.Placeholder="请选择入场时间"
|
||||
hc:InfoElement.ShowClearButton="True"
|
||||
Height="34"/>
|
||||
<ctrls:DateTimeListPicker SelectedDateTime="{Binding Entry.EntryTime, Mode=TwoWay}"
|
||||
DateTimeFormat="yyyy-MM-dd HH:mm:ss"
|
||||
Placeholder="请选择入场时间"/>
|
||||
</DockPanel>
|
||||
</hc:Col>
|
||||
|
||||
@@ -219,47 +219,38 @@
|
||||
Margin="0,0,0,8"/>
|
||||
</hc:Col>
|
||||
|
||||
<!-- 总份数 -->
|
||||
<!-- 总份数 / 每份总重 / 每份包数:字段升级为字符串,支持多行明细拼接(如 20/1/) -->
|
||||
<hc:Col Span="8">
|
||||
<hc:NumericUpDown Value="{Binding TotalPortionsInput}"
|
||||
Minimum="0"
|
||||
DecimalPlaces="0"
|
||||
Height="34"
|
||||
Style="{StaticResource NumericUpDownPlus}"
|
||||
hc:InfoElement.Title="总份数"
|
||||
hc:InfoElement.TitleWidth="80"
|
||||
hc:InfoElement.TitlePlacement="Left"
|
||||
hc:InfoElement.Placeholder="请输入总份数"
|
||||
Margin="0,0,0,8"/>
|
||||
<hc:TextBox Text="{Binding Entry.TotalPortions, UpdateSourceTrigger=PropertyChanged}"
|
||||
Height="34"
|
||||
hc:InfoElement.Title="总份数"
|
||||
hc:InfoElement.TitleWidth="80"
|
||||
hc:InfoElement.TitlePlacement="Left"
|
||||
hc:InfoElement.Placeholder="请输入总份数(多行明细用 / 拼接)"
|
||||
hc:InfoElement.ShowClearButton="True"
|
||||
Margin="0,0,0,8"/>
|
||||
</hc:Col>
|
||||
|
||||
<!-- 每份总重(KG) -->
|
||||
<hc:Col Span="8">
|
||||
<hc:NumericUpDown Value="{Binding Entry.PortionWeight}"
|
||||
Minimum="0"
|
||||
DecimalPlaces="2"
|
||||
Height="34"
|
||||
Style="{StaticResource NumericUpDownPlus}"
|
||||
hc:InfoElement.Title="每份总重(KG)"
|
||||
hc:InfoElement.TitleWidth="80"
|
||||
hc:InfoElement.TitlePlacement="Left"
|
||||
hc:InfoElement.Placeholder="自动按总重/总份数计算"
|
||||
IsReadOnly="True"
|
||||
Margin="0,0,0,8"/>
|
||||
<hc:TextBox Text="{Binding Entry.PortionWeight, UpdateSourceTrigger=PropertyChanged}"
|
||||
Height="34"
|
||||
hc:InfoElement.Title="每份总重(KG)"
|
||||
hc:InfoElement.TitleWidth="80"
|
||||
hc:InfoElement.TitlePlacement="Left"
|
||||
hc:InfoElement.Placeholder="请输入每份总重(多行明细用 / 拼接)"
|
||||
hc:InfoElement.ShowClearButton="True"
|
||||
Margin="0,0,0,8"/>
|
||||
</hc:Col>
|
||||
|
||||
<!-- 每份包数 -->
|
||||
<hc:Col Span="8">
|
||||
<hc:NumericUpDown Value="{Binding Entry.PortionPackages}"
|
||||
Minimum="0"
|
||||
DecimalPlaces="0"
|
||||
Height="34"
|
||||
Style="{StaticResource NumericUpDownPlus}"
|
||||
hc:InfoElement.Title="每份包数"
|
||||
hc:InfoElement.TitleWidth="80"
|
||||
hc:InfoElement.TitlePlacement="Left"
|
||||
hc:InfoElement.Placeholder="请输入每份包数"
|
||||
Margin="0,0,0,8"/>
|
||||
<hc:TextBox Text="{Binding Entry.PortionPackages, UpdateSourceTrigger=PropertyChanged}"
|
||||
Height="34"
|
||||
hc:InfoElement.Title="每份包数"
|
||||
hc:InfoElement.TitleWidth="80"
|
||||
hc:InfoElement.TitlePlacement="Left"
|
||||
hc:InfoElement.Placeholder="请输入每份包数(多行明细用 / 拼接)"
|
||||
hc:InfoElement.ShowClearButton="True"
|
||||
Margin="0,0,0,8"/>
|
||||
</hc:Col>
|
||||
|
||||
<!-- 检测结果 -->
|
||||
@@ -374,10 +365,9 @@
|
||||
<TextBlock DockPanel.Dock="Left" Text="特采时间" Width="80"
|
||||
VerticalAlignment="Center"
|
||||
Foreground="{DynamicResource PrimaryTextBrush}"/>
|
||||
<hc:DateTimePicker SelectedDateTime="{Binding Entry.SpecialAdoptionTime}"
|
||||
hc:InfoElement.Placeholder="请选择特采时间"
|
||||
hc:InfoElement.ShowClearButton="True"
|
||||
Height="34"/>
|
||||
<ctrls:DateTimeListPicker SelectedDateTime="{Binding Entry.SpecialAdoptionTime, Mode=TwoWay}"
|
||||
DateTimeFormat="yyyy-MM-dd HH:mm:ss"
|
||||
Placeholder="请选择特采时间"/>
|
||||
</DockPanel>
|
||||
</hc:Col>
|
||||
|
||||
|
||||
@@ -4,10 +4,38 @@
|
||||
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:ctrls="clr-namespace:YY.Admin.Controls"
|
||||
xmlns:core="clr-namespace:YY.Admin.Core.Entity;assembly=YY.Admin.Core"
|
||||
xmlns:prism="http://prismlibrary.com/"
|
||||
prism:ViewModelLocator.AutoWireViewModel="True"
|
||||
mc:Ignorable="d">
|
||||
|
||||
<UserControl.Resources>
|
||||
<!-- 右侧今日列表:单选行选中态浅蓝背景 -->
|
||||
<Style x:Key="TodayEntryListBoxItemStyle" TargetType="ListBoxItem">
|
||||
<Setter Property="Padding" Value="0"/>
|
||||
<Setter Property="HorizontalContentAlignment" Value="Stretch"/>
|
||||
<Setter Property="Background" Value="Transparent"/>
|
||||
<Setter Property="Template">
|
||||
<Setter.Value>
|
||||
<ControlTemplate TargetType="ListBoxItem">
|
||||
<Border x:Name="Bd" Background="{TemplateBinding Background}" Padding="6,4">
|
||||
<ContentPresenter/>
|
||||
</Border>
|
||||
<ControlTemplate.Triggers>
|
||||
<Trigger Property="IsMouseOver" Value="True">
|
||||
<Setter TargetName="Bd" Property="Background" Value="#FFF5F5F5"/>
|
||||
</Trigger>
|
||||
<Trigger Property="IsSelected" Value="True">
|
||||
<Setter TargetName="Bd" Property="Background" Value="#E3F2FD"/>
|
||||
</Trigger>
|
||||
</ControlTemplate.Triggers>
|
||||
</ControlTemplate>
|
||||
</Setter.Value>
|
||||
</Setter>
|
||||
</Style>
|
||||
</UserControl.Resources>
|
||||
|
||||
<Grid Style="{StaticResource BaseViewStyle}">
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto"/>
|
||||
@@ -15,38 +43,89 @@
|
||||
<RowDefinition Height="Auto"/>
|
||||
</Grid.RowDefinitions>
|
||||
|
||||
<hc:SimplePanel Margin="24,16,24,12">
|
||||
<Grid Grid.Row="0" Margin="24,16,24,12">
|
||||
<TextBlock FontSize="18"
|
||||
Foreground="{DynamicResource PrimaryTextBrush}"
|
||||
Text="新增原料入场记录"
|
||||
Text="{Binding DialogTitle}"
|
||||
HorizontalAlignment="Left"
|
||||
VerticalAlignment="Center"/>
|
||||
</hc:SimplePanel>
|
||||
<!-- 收起/展开与右侧列表分离:置于页头右上,与标题同行 -->
|
||||
<!-- 显式绑定 UserControl.DataContext:避免页眉 DataContext 非本页 ViewModel 时 Toggle 无效 -->
|
||||
<Button HorizontalAlignment="Right"
|
||||
VerticalAlignment="Center"
|
||||
Command="{Binding DataContext.ToggleRightPanelCommand, RelativeSource={RelativeSource AncestorType=UserControl}}"
|
||||
Click="ToggleTodayPanelButton_OnClick"
|
||||
Padding="12,6"
|
||||
FontSize="12"
|
||||
MinHeight="32">
|
||||
<Button.Style>
|
||||
<Style TargetType="Button" BasedOn="{StaticResource ButtonDefault}">
|
||||
<Setter Property="Content" Value="收起"/>
|
||||
<Setter Property="ToolTip" Value="收起右侧「今日入场」列表"/>
|
||||
<Style.Triggers>
|
||||
<DataTrigger Binding="{Binding DataContext.IsRightPanelExpanded, RelativeSource={RelativeSource AncestorType=UserControl}}" Value="False">
|
||||
<Setter Property="Content" Value="展开"/>
|
||||
<Setter Property="ToolTip" Value="展开右侧「今日入场」列表"/>
|
||||
</DataTrigger>
|
||||
</Style.Triggers>
|
||||
</Style>
|
||||
</Button.Style>
|
||||
</Button>
|
||||
</Grid>
|
||||
|
||||
<hc:ScrollViewer Grid.Row="1" IsInertiaEnabled="True" HorizontalScrollBarVisibility="Disabled">
|
||||
<Grid Grid.Row="1" x:Name="MainSplitRoot">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="*" MinWidth="320"/>
|
||||
<ColumnDefinition x:Name="SplitterCol" Width="4"/>
|
||||
<ColumnDefinition x:Name="RightPaneCol" Width="280" MinWidth="0"/>
|
||||
</Grid.ColumnDefinitions>
|
||||
|
||||
<hc:ScrollViewer Grid.Column="0" IsInertiaEnabled="True" HorizontalScrollBarVisibility="Disabled">
|
||||
<StackPanel x:Name="RootPanel" Margin="24,8,24,8">
|
||||
<TextBlock Text="基础资料"
|
||||
FontSize="14"
|
||||
FontSize="13"
|
||||
FontWeight="SemiBold"
|
||||
Foreground="{DynamicResource PrimaryTextBrush}"
|
||||
Margin="0,0,0,6"/>
|
||||
<hc:Row Gutter="16">
|
||||
|
||||
<hc:Col Span="16">
|
||||
<Grid Margin="0,0,0,4">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="80"/>
|
||||
<ColumnDefinition Width="*"/>
|
||||
<ColumnDefinition Width="60"/>
|
||||
<ColumnDefinition Width="32"/>
|
||||
</Grid.ColumnDefinitions>
|
||||
<TextBlock Grid.Column="0" Text="密炼物料" VerticalAlignment="Center"
|
||||
FontSize="14" Foreground="{DynamicResource PrimaryTextBrush}"/>
|
||||
<Border Grid.Column="1" CornerRadius="4" Height="34"
|
||||
BorderBrush="{DynamicResource BorderBrush}" BorderThickness="1"
|
||||
Background="{DynamicResource ThirdlyRegionBrush}">
|
||||
<TextBlock Text="{Binding SelectedMaterialDisplay}" VerticalAlignment="Center"
|
||||
Margin="8,0" FontSize="13" TextTrimming="CharacterEllipsis">
|
||||
<!-- 带横竖线的表格式表单 -->
|
||||
<Border BorderThickness="1" BorderBrush="{DynamicResource BorderBrush}" Margin="0,0,0,8">
|
||||
<Grid>
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="36"/> <!-- 密炼物料 -->
|
||||
<RowDefinition Height="38"/> <!-- 批次号 / 入场时间 -->
|
||||
<RowDefinition Height="36"/> <!-- 榜单号 -->
|
||||
<RowDefinition Height="36"/> <!-- 供料客户 / 供应商名称 -->
|
||||
<RowDefinition Height="36"/> <!-- 厂家物料名称 / 保质期 -->
|
||||
<RowDefinition Height="36"/> <!-- 总重 / 总份数 -->
|
||||
<RowDefinition Height="36"/> <!-- 每份总重 / 每份包数 -->
|
||||
<RowDefinition Height="36"/> <!-- 库位 / 卸货人 -->
|
||||
<RowDefinition Height="60"/> <!-- 备注 -->
|
||||
</Grid.RowDefinitions>
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="90"/>
|
||||
<ColumnDefinition Width="*"/>
|
||||
<ColumnDefinition Width="90"/>
|
||||
<ColumnDefinition Width="*"/>
|
||||
</Grid.ColumnDefinitions>
|
||||
|
||||
<!-- ===== Row 0: 密炼物料 ===== -->
|
||||
<Border Grid.Row="0" Grid.Column="0"
|
||||
Background="{DynamicResource SecondaryRegionBrush}"
|
||||
BorderThickness="0,0,1,1" BorderBrush="{DynamicResource BorderBrush}">
|
||||
<TextBlock Text="密炼物料" HorizontalAlignment="Center" VerticalAlignment="Center"
|
||||
FontSize="12" Foreground="{DynamicResource PrimaryTextBrush}"/>
|
||||
</Border>
|
||||
<Border Grid.Row="0" Grid.Column="1" Grid.ColumnSpan="3"
|
||||
BorderThickness="0,0,0,1" BorderBrush="{DynamicResource BorderBrush}" Padding="6,0">
|
||||
<Grid>
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="*"/>
|
||||
<ColumnDefinition Width="Auto"/>
|
||||
<ColumnDefinition Width="Auto"/>
|
||||
</Grid.ColumnDefinitions>
|
||||
<TextBlock Grid.Column="0" Text="{Binding SelectedMaterialDisplay}"
|
||||
VerticalAlignment="Center" FontSize="13" TextTrimming="CharacterEllipsis">
|
||||
<TextBlock.Style>
|
||||
<Style TargetType="TextBlock">
|
||||
<Setter Property="Foreground" Value="{DynamicResource SecondaryTextBrush}"/>
|
||||
@@ -58,243 +137,337 @@
|
||||
</Style>
|
||||
</TextBlock.Style>
|
||||
</TextBlock>
|
||||
</Border>
|
||||
<Button Grid.Column="2" Content="选 择"
|
||||
Command="{Binding OpenMaterialPickerCommand}"
|
||||
Style="{StaticResource ButtonPrimary}"
|
||||
Height="34" Margin="6,0,0,0" FontSize="12"/>
|
||||
<Button Grid.Column="3"
|
||||
Command="{Binding ClearMaterialCommand}"
|
||||
Style="{StaticResource ButtonIcon}"
|
||||
Height="34" Width="28" Margin="4,0,0,0" Padding="0"
|
||||
ToolTip="清除物料"
|
||||
Foreground="{DynamicResource SecondaryTextBrush}"
|
||||
hc:IconElement.Geometry="{StaticResource ErrorGeometry}"/>
|
||||
</Grid>
|
||||
</hc:Col>
|
||||
<Button Grid.Column="1" Content="选 择"
|
||||
Command="{Binding OpenMaterialPickerCommand}"
|
||||
Style="{StaticResource ButtonPrimary}"
|
||||
Height="26" Margin="6,0,0,0" FontSize="12" Padding="10,0"/>
|
||||
<Button Grid.Column="2"
|
||||
Command="{Binding ClearMaterialCommand}"
|
||||
Style="{StaticResource ButtonIcon}"
|
||||
Height="26" Width="24" Margin="4,0,0,0" Padding="0"
|
||||
ToolTip="清除物料"
|
||||
Foreground="{DynamicResource SecondaryTextBrush}"
|
||||
hc:IconElement.Geometry="{StaticResource ErrorGeometry}"/>
|
||||
</Grid>
|
||||
</Border>
|
||||
|
||||
<hc:Col Span="8">
|
||||
<hc:TextBox Text="{Binding Entry.MaterialName}"
|
||||
Height="34"
|
||||
IsReadOnly="True"
|
||||
hc:InfoElement.Title="物料名称"
|
||||
hc:InfoElement.TitleWidth="80"
|
||||
hc:InfoElement.TitlePlacement="Left"
|
||||
Foreground="{DynamicResource SecondaryTextBrush}"
|
||||
Margin="0,0,0,4"/>
|
||||
</hc:Col>
|
||||
<!-- ===== Row 1: 批次号 / 入场时间 ===== -->
|
||||
<Border Grid.Row="1" Grid.Column="0"
|
||||
Background="{DynamicResource SecondaryRegionBrush}"
|
||||
BorderThickness="0,0,1,1" BorderBrush="{DynamicResource BorderBrush}">
|
||||
<TextBlock Text="批次号" HorizontalAlignment="Center" VerticalAlignment="Center"
|
||||
FontSize="12" Foreground="{DynamicResource PrimaryTextBrush}"/>
|
||||
</Border>
|
||||
<Border Grid.Row="1" Grid.Column="1"
|
||||
BorderThickness="0,0,1,1" BorderBrush="{DynamicResource BorderBrush}" Padding="6,0">
|
||||
<TextBlock Text="{Binding Entry.BatchNo}" VerticalAlignment="Center" FontSize="13"
|
||||
Foreground="{DynamicResource SecondaryTextBrush}" TextTrimming="CharacterEllipsis"/>
|
||||
</Border>
|
||||
<Border Grid.Row="1" Grid.Column="2"
|
||||
Background="{DynamicResource SecondaryRegionBrush}"
|
||||
BorderThickness="0,0,1,1" BorderBrush="{DynamicResource BorderBrush}">
|
||||
<TextBlock Text="入场时间" HorizontalAlignment="Center" VerticalAlignment="Center"
|
||||
FontSize="12" Foreground="{DynamicResource PrimaryTextBrush}"/>
|
||||
</Border>
|
||||
<Border Grid.Row="1" Grid.Column="3"
|
||||
BorderThickness="0,0,0,1" BorderBrush="{DynamicResource BorderBrush}" Padding="4,3">
|
||||
<!-- 自定义日期时间选择器:日历 + 时/分/秒 三列 + 此刻/确定 -->
|
||||
<ctrls:DateTimeListPicker SelectedDateTime="{Binding Entry.EntryTime, Mode=TwoWay}"
|
||||
DateTimeFormat="yyyy-MM-dd HH:mm:ss"
|
||||
Placeholder="请选择入场时间"/>
|
||||
</Border>
|
||||
|
||||
<hc:Col Span="8">
|
||||
<hc:TextBox Text="{Binding Entry.Barcode, UpdateSourceTrigger=PropertyChanged}"
|
||||
Height="34"
|
||||
IsReadOnly="True"
|
||||
hc:InfoElement.Title="条码"
|
||||
hc:InfoElement.TitleWidth="80"
|
||||
hc:InfoElement.TitlePlacement="Left"
|
||||
hc:InfoElement.Placeholder="自动生成"
|
||||
hc:InfoElement.ShowClearButton="True"
|
||||
Margin="0,0,0,4"/>
|
||||
</hc:Col>
|
||||
|
||||
<hc:Col Span="8">
|
||||
<hc:TextBox Text="{Binding Entry.BatchNo, UpdateSourceTrigger=PropertyChanged}"
|
||||
Height="34"
|
||||
IsReadOnly="True"
|
||||
hc:InfoElement.Title="批次号"
|
||||
hc:InfoElement.TitleWidth="80"
|
||||
hc:InfoElement.TitlePlacement="Left"
|
||||
hc:InfoElement.Placeholder="自动生成"
|
||||
hc:InfoElement.ShowClearButton="True"
|
||||
Margin="0,0,0,4"/>
|
||||
</hc:Col>
|
||||
|
||||
<hc:Col Span="8">
|
||||
<DockPanel Margin="0,0,0,4" LastChildFill="True">
|
||||
<TextBlock DockPanel.Dock="Left" Text="入场时间" Width="80"
|
||||
VerticalAlignment="Center"
|
||||
Foreground="{DynamicResource PrimaryTextBrush}"/>
|
||||
<hc:DateTimePicker SelectedDateTime="{Binding Entry.EntryTime}"
|
||||
hc:InfoElement.Placeholder="请选择入场时间"
|
||||
hc:InfoElement.ShowClearButton="True"
|
||||
Height="34"/>
|
||||
</DockPanel>
|
||||
</hc:Col>
|
||||
|
||||
<hc:Col Span="8">
|
||||
<Grid Margin="0,0,0,4">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="80"/>
|
||||
<ColumnDefinition Width="*"/>
|
||||
<ColumnDefinition Width="60"/>
|
||||
<ColumnDefinition Width="32"/>
|
||||
</Grid.ColumnDefinitions>
|
||||
<TextBlock Grid.Column="0" Text="榜单号" VerticalAlignment="Center"
|
||||
FontSize="14" Foreground="{DynamicResource PrimaryTextBrush}"/>
|
||||
<Border Grid.Column="1" CornerRadius="4" Height="34"
|
||||
BorderBrush="{DynamicResource BorderBrush}" BorderThickness="1"
|
||||
Background="{DynamicResource ThirdlyRegionBrush}">
|
||||
<TextBlock Text="{Binding Entry.BillNo}" VerticalAlignment="Center"
|
||||
Margin="8,0" FontSize="13" TextTrimming="CharacterEllipsis"
|
||||
<!-- ===== Row 2: 榜单号 ===== -->
|
||||
<Border Grid.Row="2" Grid.Column="0"
|
||||
Background="{DynamicResource SecondaryRegionBrush}"
|
||||
BorderThickness="0,0,1,1" BorderBrush="{DynamicResource BorderBrush}">
|
||||
<TextBlock Text="榜单号" HorizontalAlignment="Center" VerticalAlignment="Center"
|
||||
FontSize="12" Foreground="{DynamicResource PrimaryTextBrush}"/>
|
||||
</Border>
|
||||
<Border Grid.Row="2" Grid.Column="1" Grid.ColumnSpan="3"
|
||||
BorderThickness="0,0,0,1" BorderBrush="{DynamicResource BorderBrush}" Padding="6,0">
|
||||
<Grid>
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="*"/>
|
||||
<ColumnDefinition Width="Auto"/>
|
||||
<ColumnDefinition Width="Auto"/>
|
||||
</Grid.ColumnDefinitions>
|
||||
<TextBlock Grid.Column="0" Text="{Binding Entry.BillNo}"
|
||||
VerticalAlignment="Center" FontSize="13" TextTrimming="CharacterEllipsis"
|
||||
Foreground="{DynamicResource PrimaryTextBrush}"/>
|
||||
</Border>
|
||||
<Button Grid.Column="2" Content="选 择"
|
||||
Command="{Binding OpenWeightRecordPickerCommand}"
|
||||
Style="{StaticResource ButtonPrimary}"
|
||||
Height="34" Margin="6,0,0,0" FontSize="12"/>
|
||||
<Button Grid.Column="3"
|
||||
Command="{Binding ClearWeightRecordCommand}"
|
||||
Style="{StaticResource ButtonIcon}"
|
||||
Height="34" Width="28" Margin="4,0,0,0" Padding="0"
|
||||
ToolTip="清除榜单"
|
||||
Foreground="{DynamicResource SecondaryTextBrush}"
|
||||
hc:IconElement.Geometry="{StaticResource ErrorGeometry}"/>
|
||||
</Grid>
|
||||
</hc:Col>
|
||||
<Button Grid.Column="1" Content="选 择"
|
||||
Command="{Binding OpenWeightRecordPickerCommand}"
|
||||
Style="{StaticResource ButtonPrimary}"
|
||||
Height="26" Margin="6,0,0,0" FontSize="12" Padding="10,0"/>
|
||||
<Button Grid.Column="2"
|
||||
Command="{Binding ClearWeightRecordCommand}"
|
||||
Style="{StaticResource ButtonIcon}"
|
||||
Height="26" Width="24" Margin="4,0,0,0" Padding="0"
|
||||
ToolTip="清除榜单"
|
||||
Foreground="{DynamicResource SecondaryTextBrush}"
|
||||
hc:IconElement.Geometry="{StaticResource ErrorGeometry}"/>
|
||||
</Grid>
|
||||
</Border>
|
||||
|
||||
<hc:Col Span="8">
|
||||
<hc:TextBox Text="{Binding Entry.SupplyCustomer, UpdateSourceTrigger=PropertyChanged}"
|
||||
Height="34"
|
||||
hc:InfoElement.Title="供料客户"
|
||||
hc:InfoElement.TitleWidth="80"
|
||||
hc:InfoElement.TitlePlacement="Left"
|
||||
hc:InfoElement.Placeholder="请输入供料客户"
|
||||
hc:InfoElement.ShowClearButton="True"
|
||||
Margin="0,0,0,4"/>
|
||||
</hc:Col>
|
||||
<!-- ===== Row 3: 供料客户 / 供应商名称 ===== -->
|
||||
<Border Grid.Row="3" Grid.Column="0"
|
||||
Background="{DynamicResource SecondaryRegionBrush}"
|
||||
BorderThickness="0,0,1,1" BorderBrush="{DynamicResource BorderBrush}">
|
||||
<TextBlock Text="供料客户" HorizontalAlignment="Center" VerticalAlignment="Center"
|
||||
FontSize="12" Foreground="{DynamicResource PrimaryTextBrush}"/>
|
||||
</Border>
|
||||
<Border Grid.Row="3" Grid.Column="1"
|
||||
BorderThickness="0,0,1,1" BorderBrush="{DynamicResource BorderBrush}" Padding="4,0">
|
||||
<Grid>
|
||||
<TextBox Text="{Binding Entry.SupplyCustomer, UpdateSourceTrigger=PropertyChanged}"
|
||||
VerticalContentAlignment="Center" BorderThickness="0" Background="Transparent"
|
||||
FontSize="13" Foreground="{DynamicResource PrimaryTextBrush}"/>
|
||||
<TextBlock IsHitTestVisible="False" VerticalAlignment="Center" Margin="4,0"
|
||||
FontSize="13" Foreground="{DynamicResource SecondaryTextBrush}" Opacity="0.6"
|
||||
Text="请填写供料客户">
|
||||
<TextBlock.Style>
|
||||
<Style TargetType="TextBlock">
|
||||
<Setter Property="Visibility" Value="Collapsed"/>
|
||||
<Style.Triggers>
|
||||
<DataTrigger Binding="{Binding Entry.SupplyCustomer}" Value="">
|
||||
<Setter Property="Visibility" Value="Visible"/>
|
||||
</DataTrigger>
|
||||
<DataTrigger Binding="{Binding Entry.SupplyCustomer}" Value="{x:Null}">
|
||||
<Setter Property="Visibility" Value="Visible"/>
|
||||
</DataTrigger>
|
||||
</Style.Triggers>
|
||||
</Style>
|
||||
</TextBlock.Style>
|
||||
</TextBlock>
|
||||
</Grid>
|
||||
</Border>
|
||||
<Border Grid.Row="3" Grid.Column="2"
|
||||
Background="{DynamicResource SecondaryRegionBrush}"
|
||||
BorderThickness="0,0,1,1" BorderBrush="{DynamicResource BorderBrush}">
|
||||
<TextBlock Text="供应商名称" HorizontalAlignment="Center" VerticalAlignment="Center"
|
||||
FontSize="12" Foreground="{DynamicResource PrimaryTextBrush}"/>
|
||||
</Border>
|
||||
<Border Grid.Row="3" Grid.Column="3"
|
||||
BorderThickness="0,0,0,1" BorderBrush="{DynamicResource BorderBrush}" Padding="4,0">
|
||||
<Grid>
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="*"/>
|
||||
<ColumnDefinition Width="Auto"/>
|
||||
<ColumnDefinition Width="Auto"/>
|
||||
</Grid.ColumnDefinitions>
|
||||
<Grid Grid.Column="0">
|
||||
<TextBox Text="{Binding Entry.SupplierName, UpdateSourceTrigger=PropertyChanged}"
|
||||
VerticalContentAlignment="Center" BorderThickness="0" Background="Transparent"
|
||||
FontSize="13" Foreground="{DynamicResource PrimaryTextBrush}"/>
|
||||
<TextBlock IsHitTestVisible="False" VerticalAlignment="Center" Margin="4,0"
|
||||
FontSize="13" Foreground="{DynamicResource SecondaryTextBrush}" Opacity="0.6"
|
||||
Text="自动带出,或手动选择/填写">
|
||||
<TextBlock.Style>
|
||||
<Style TargetType="TextBlock">
|
||||
<Setter Property="Visibility" Value="Collapsed"/>
|
||||
<Style.Triggers>
|
||||
<DataTrigger Binding="{Binding Entry.SupplierName}" Value="">
|
||||
<Setter Property="Visibility" Value="Visible"/>
|
||||
</DataTrigger>
|
||||
<DataTrigger Binding="{Binding Entry.SupplierName}" Value="{x:Null}">
|
||||
<Setter Property="Visibility" Value="Visible"/>
|
||||
</DataTrigger>
|
||||
</Style.Triggers>
|
||||
</Style>
|
||||
</TextBlock.Style>
|
||||
</TextBlock>
|
||||
</Grid>
|
||||
<Button Grid.Column="1" Content="选 择"
|
||||
Command="{Binding OpenSupplierPickerCommand}"
|
||||
Style="{StaticResource ButtonPrimary}"
|
||||
Height="26" Margin="4,0,0,0" FontSize="12" Padding="8,0"/>
|
||||
<Button Grid.Column="2"
|
||||
Command="{Binding ClearSupplierCommand}"
|
||||
Style="{StaticResource ButtonIcon}"
|
||||
Height="26" Width="24" Margin="2,0,0,0" Padding="0"
|
||||
ToolTip="清除供应商"
|
||||
Foreground="{DynamicResource SecondaryTextBrush}"
|
||||
hc:IconElement.Geometry="{StaticResource ErrorGeometry}"/>
|
||||
</Grid>
|
||||
</Border>
|
||||
|
||||
<hc:Col Span="8">
|
||||
<hc:TextBox Text="{Binding Entry.SupplierName, UpdateSourceTrigger=PropertyChanged}"
|
||||
Height="34"
|
||||
hc:InfoElement.Title="供应商名称"
|
||||
hc:InfoElement.TitleWidth="80"
|
||||
hc:InfoElement.TitlePlacement="Left"
|
||||
hc:InfoElement.Placeholder="根据榜单自动带出"
|
||||
hc:InfoElement.ShowClearButton="True"
|
||||
IsReadOnly="True"
|
||||
Margin="0,0,0,4"/>
|
||||
</hc:Col>
|
||||
<!-- ===== Row 4: 厂家物料名称 / 保质期 ===== -->
|
||||
<Border Grid.Row="4" Grid.Column="0"
|
||||
Background="{DynamicResource SecondaryRegionBrush}"
|
||||
BorderThickness="0,0,1,1" BorderBrush="{DynamicResource BorderBrush}">
|
||||
<TextBlock Text="厂家物料名称" HorizontalAlignment="Center" VerticalAlignment="Center"
|
||||
FontSize="11" Foreground="{DynamicResource PrimaryTextBrush}"
|
||||
TextWrapping="Wrap" TextAlignment="Center"/>
|
||||
</Border>
|
||||
<Border Grid.Row="4" Grid.Column="1"
|
||||
BorderThickness="0,0,1,1" BorderBrush="{DynamicResource BorderBrush}" Padding="6,0">
|
||||
<TextBlock Text="{Binding Entry.ManufacturerMaterialName}" VerticalAlignment="Center"
|
||||
FontSize="13" Foreground="{DynamicResource SecondaryTextBrush}" TextTrimming="CharacterEllipsis"/>
|
||||
</Border>
|
||||
<Border Grid.Row="4" Grid.Column="2"
|
||||
Background="{DynamicResource SecondaryRegionBrush}"
|
||||
BorderThickness="0,0,1,1" BorderBrush="{DynamicResource BorderBrush}">
|
||||
<TextBlock Text="保质期" HorizontalAlignment="Center" VerticalAlignment="Center"
|
||||
FontSize="12" Foreground="{DynamicResource PrimaryTextBrush}"/>
|
||||
</Border>
|
||||
<Border Grid.Row="4" Grid.Column="3"
|
||||
BorderThickness="0,0,0,1" BorderBrush="{DynamicResource BorderBrush}" Padding="6,0">
|
||||
<TextBlock Text="{Binding Entry.ShelfLife}" VerticalAlignment="Center" FontSize="13"
|
||||
Foreground="{DynamicResource SecondaryTextBrush}" TextTrimming="CharacterEllipsis"/>
|
||||
</Border>
|
||||
|
||||
<hc:Col Span="8">
|
||||
<hc:TextBox Text="{Binding Entry.ManufacturerMaterialName, UpdateSourceTrigger=PropertyChanged}"
|
||||
Height="34"
|
||||
hc:InfoElement.Title="厂家物料名称"
|
||||
hc:InfoElement.TitleWidth="80"
|
||||
hc:InfoElement.TitlePlacement="Left"
|
||||
hc:InfoElement.Placeholder="自动带出厂家别名"
|
||||
hc:InfoElement.ShowClearButton="True"
|
||||
IsReadOnly="True"
|
||||
Margin="0,0,0,4"/>
|
||||
</hc:Col>
|
||||
<!-- ===== Row 5: 总重(KG) / 总份数 ===== -->
|
||||
<Border Grid.Row="5" Grid.Column="0"
|
||||
Background="{DynamicResource SecondaryRegionBrush}"
|
||||
BorderThickness="0,0,1,1" BorderBrush="{DynamicResource BorderBrush}">
|
||||
<TextBlock Text="总重(KG)" HorizontalAlignment="Center" VerticalAlignment="Center"
|
||||
FontSize="12" Foreground="{DynamicResource PrimaryTextBrush}"/>
|
||||
</Border>
|
||||
<Border Grid.Row="5" Grid.Column="1"
|
||||
BorderThickness="0,0,1,1" BorderBrush="{DynamicResource BorderBrush}" Padding="4,4">
|
||||
<hc:NumericUpDown Value="{Binding TotalWeightInput}"
|
||||
Minimum="0" DecimalPlaces="2"
|
||||
Style="{StaticResource NumericUpDownPlus}"
|
||||
hc:InfoElement.Placeholder="请输入总重"/>
|
||||
</Border>
|
||||
<Border Grid.Row="5" Grid.Column="2"
|
||||
Background="{DynamicResource SecondaryRegionBrush}"
|
||||
BorderThickness="0,0,1,1" BorderBrush="{DynamicResource BorderBrush}">
|
||||
<TextBlock Text="总份数" HorizontalAlignment="Center" VerticalAlignment="Center"
|
||||
FontSize="12" Foreground="{DynamicResource PrimaryTextBrush}"/>
|
||||
</Border>
|
||||
<Border Grid.Row="5" Grid.Column="3"
|
||||
BorderThickness="0,0,0,1" BorderBrush="{DynamicResource BorderBrush}" Padding="6,0">
|
||||
<TextBlock Text="{Binding SplitTotalPortionsDisplay}" VerticalAlignment="Center"
|
||||
FontSize="13" Foreground="{DynamicResource SecondaryTextBrush}"/>
|
||||
</Border>
|
||||
|
||||
<hc:Col Span="8">
|
||||
<hc:TextBox Text="{Binding Entry.ShelfLife, UpdateSourceTrigger=PropertyChanged}"
|
||||
Height="34"
|
||||
hc:InfoElement.Title="保质期"
|
||||
hc:InfoElement.TitleWidth="80"
|
||||
hc:InfoElement.TitlePlacement="Left"
|
||||
hc:InfoElement.Placeholder="自动计算到期日期"
|
||||
hc:InfoElement.ShowClearButton="True"
|
||||
IsReadOnly="True"
|
||||
Margin="0,0,0,4"/>
|
||||
</hc:Col>
|
||||
<!-- ===== Row 6: 每份总重(KG) / 每份包数 ===== -->
|
||||
<Border Grid.Row="6" Grid.Column="0"
|
||||
Background="{DynamicResource SecondaryRegionBrush}"
|
||||
BorderThickness="0,0,1,1" BorderBrush="{DynamicResource BorderBrush}">
|
||||
<TextBlock Text="每份总重(KG)" HorizontalAlignment="Center" VerticalAlignment="Center"
|
||||
FontSize="11" Foreground="{DynamicResource PrimaryTextBrush}"
|
||||
TextWrapping="Wrap" TextAlignment="Center"/>
|
||||
</Border>
|
||||
<Border Grid.Row="6" Grid.Column="1"
|
||||
BorderThickness="0,0,1,1" BorderBrush="{DynamicResource BorderBrush}" Padding="6,0">
|
||||
<TextBlock Text="{Binding SplitPortionWeightDisplay}" VerticalAlignment="Center"
|
||||
FontSize="13" Foreground="{DynamicResource SecondaryTextBrush}"/>
|
||||
</Border>
|
||||
<Border Grid.Row="6" Grid.Column="2"
|
||||
Background="{DynamicResource SecondaryRegionBrush}"
|
||||
BorderThickness="0,0,1,1" BorderBrush="{DynamicResource BorderBrush}">
|
||||
<TextBlock Text="每份包数" HorizontalAlignment="Center" VerticalAlignment="Center"
|
||||
FontSize="12" Foreground="{DynamicResource PrimaryTextBrush}"/>
|
||||
</Border>
|
||||
<Border Grid.Row="6" Grid.Column="3"
|
||||
BorderThickness="0,0,0,1" BorderBrush="{DynamicResource BorderBrush}" Padding="6,0">
|
||||
<TextBlock Text="{Binding SplitPortionPackagesDisplay}" VerticalAlignment="Center"
|
||||
FontSize="13" Foreground="{DynamicResource SecondaryTextBrush}"/>
|
||||
</Border>
|
||||
|
||||
<hc:Col Span="8">
|
||||
<hc:NumericUpDown Value="{Binding TotalWeightInput}"
|
||||
Minimum="0"
|
||||
DecimalPlaces="2"
|
||||
Height="34"
|
||||
Style="{StaticResource NumericUpDownPlus}"
|
||||
hc:InfoElement.Title="总重(KG)"
|
||||
hc:InfoElement.TitleWidth="80"
|
||||
hc:InfoElement.TitlePlacement="Left"
|
||||
hc:InfoElement.Placeholder="请输入总重"
|
||||
Margin="0,0,0,4"/>
|
||||
</hc:Col>
|
||||
<!-- ===== Row 7: 库位 / 卸货人 ===== -->
|
||||
<Border Grid.Row="7" Grid.Column="0"
|
||||
Background="{DynamicResource SecondaryRegionBrush}"
|
||||
BorderThickness="0,0,1,1" BorderBrush="{DynamicResource BorderBrush}">
|
||||
<TextBlock Text="库位" HorizontalAlignment="Center" VerticalAlignment="Center"
|
||||
FontSize="12" Foreground="{DynamicResource PrimaryTextBrush}"/>
|
||||
</Border>
|
||||
<Border Grid.Row="7" Grid.Column="1"
|
||||
BorderThickness="0,0,1,1" BorderBrush="{DynamicResource BorderBrush}" Padding="4,0">
|
||||
<Grid>
|
||||
<TextBox Text="{Binding Entry.WarehouseLocation, UpdateSourceTrigger=PropertyChanged}"
|
||||
VerticalContentAlignment="Center" BorderThickness="0" Background="Transparent"
|
||||
FontSize="13" Foreground="{DynamicResource PrimaryTextBrush}"/>
|
||||
<TextBlock IsHitTestVisible="False" VerticalAlignment="Center" Margin="4,0"
|
||||
FontSize="13" Foreground="{DynamicResource SecondaryTextBrush}" Opacity="0.6"
|
||||
Text="请填写库位">
|
||||
<TextBlock.Style>
|
||||
<Style TargetType="TextBlock">
|
||||
<Setter Property="Visibility" Value="Collapsed"/>
|
||||
<Style.Triggers>
|
||||
<DataTrigger Binding="{Binding Entry.WarehouseLocation}" Value="">
|
||||
<Setter Property="Visibility" Value="Visible"/>
|
||||
</DataTrigger>
|
||||
<DataTrigger Binding="{Binding Entry.WarehouseLocation}" Value="{x:Null}">
|
||||
<Setter Property="Visibility" Value="Visible"/>
|
||||
</DataTrigger>
|
||||
</Style.Triggers>
|
||||
</Style>
|
||||
</TextBlock.Style>
|
||||
</TextBlock>
|
||||
</Grid>
|
||||
</Border>
|
||||
<Border Grid.Row="7" Grid.Column="2"
|
||||
Background="{DynamicResource SecondaryRegionBrush}"
|
||||
BorderThickness="0,0,1,1" BorderBrush="{DynamicResource BorderBrush}">
|
||||
<TextBlock Text="卸货人" HorizontalAlignment="Center" VerticalAlignment="Center"
|
||||
FontSize="12" Foreground="{DynamicResource PrimaryTextBrush}"/>
|
||||
</Border>
|
||||
<Border Grid.Row="7" Grid.Column="3"
|
||||
BorderThickness="0,0,0,1" BorderBrush="{DynamicResource BorderBrush}" Padding="4,0">
|
||||
<Grid>
|
||||
<TextBox Text="{Binding Entry.UnloadOperator, UpdateSourceTrigger=PropertyChanged}"
|
||||
VerticalContentAlignment="Center" BorderThickness="0" Background="Transparent"
|
||||
FontSize="13" Foreground="{DynamicResource PrimaryTextBrush}"/>
|
||||
<TextBlock IsHitTestVisible="False" VerticalAlignment="Center" Margin="4,0"
|
||||
FontSize="13" Foreground="{DynamicResource SecondaryTextBrush}" Opacity="0.6"
|
||||
Text="请填写卸货人">
|
||||
<TextBlock.Style>
|
||||
<Style TargetType="TextBlock">
|
||||
<Setter Property="Visibility" Value="Collapsed"/>
|
||||
<Style.Triggers>
|
||||
<DataTrigger Binding="{Binding Entry.UnloadOperator}" Value="">
|
||||
<Setter Property="Visibility" Value="Visible"/>
|
||||
</DataTrigger>
|
||||
<DataTrigger Binding="{Binding Entry.UnloadOperator}" Value="{x:Null}">
|
||||
<Setter Property="Visibility" Value="Visible"/>
|
||||
</DataTrigger>
|
||||
</Style.Triggers>
|
||||
</Style>
|
||||
</TextBlock.Style>
|
||||
</TextBlock>
|
||||
</Grid>
|
||||
</Border>
|
||||
|
||||
<hc:Col Span="8">
|
||||
<hc:TextBox Text="{Binding SplitTotalPortionsDisplay, Mode=OneWay}"
|
||||
Height="34"
|
||||
hc:InfoElement.Title="总份数"
|
||||
hc:InfoElement.TitleWidth="80"
|
||||
hc:InfoElement.TitlePlacement="Left"
|
||||
hc:InfoElement.Placeholder="根据拆码明细自动拼接"
|
||||
IsReadOnly="True"
|
||||
Margin="0,0,0,4"/>
|
||||
</hc:Col>
|
||||
<!-- ===== Row 8: 备注(末行,无内部底边线) ===== -->
|
||||
<Border Grid.Row="8" Grid.Column="0"
|
||||
Background="{DynamicResource SecondaryRegionBrush}"
|
||||
BorderThickness="0,0,1,0" BorderBrush="{DynamicResource BorderBrush}">
|
||||
<TextBlock Text="备注" HorizontalAlignment="Center" VerticalAlignment="Center"
|
||||
FontSize="12" Foreground="{DynamicResource PrimaryTextBrush}"/>
|
||||
</Border>
|
||||
<Border Grid.Row="8" Grid.Column="1" Grid.ColumnSpan="3" Padding="4,4">
|
||||
<Grid>
|
||||
<TextBox Text="{Binding Entry.Remark, UpdateSourceTrigger=PropertyChanged}"
|
||||
TextWrapping="Wrap" AcceptsReturn="True"
|
||||
VerticalScrollBarVisibility="Auto"
|
||||
BorderThickness="0" Background="Transparent"
|
||||
FontSize="13" VerticalContentAlignment="Top"
|
||||
Foreground="{DynamicResource PrimaryTextBrush}"/>
|
||||
<TextBlock IsHitTestVisible="False" VerticalAlignment="Top" Margin="4,0"
|
||||
FontSize="13" Foreground="{DynamicResource SecondaryTextBrush}" Opacity="0.6"
|
||||
Text="请填写备注">
|
||||
<TextBlock.Style>
|
||||
<Style TargetType="TextBlock">
|
||||
<Setter Property="Visibility" Value="Collapsed"/>
|
||||
<Style.Triggers>
|
||||
<DataTrigger Binding="{Binding Entry.Remark}" Value="">
|
||||
<Setter Property="Visibility" Value="Visible"/>
|
||||
</DataTrigger>
|
||||
<DataTrigger Binding="{Binding Entry.Remark}" Value="{x:Null}">
|
||||
<Setter Property="Visibility" Value="Visible"/>
|
||||
</DataTrigger>
|
||||
</Style.Triggers>
|
||||
</Style>
|
||||
</TextBlock.Style>
|
||||
</TextBlock>
|
||||
</Grid>
|
||||
</Border>
|
||||
|
||||
<hc:Col Span="8">
|
||||
<hc:TextBox Text="{Binding SplitPortionWeightDisplay, Mode=OneWay}"
|
||||
Height="34"
|
||||
hc:InfoElement.Title="每份总重(KG)"
|
||||
hc:InfoElement.TitleWidth="80"
|
||||
hc:InfoElement.TitlePlacement="Left"
|
||||
hc:InfoElement.Placeholder="根据拆码明细自动拼接"
|
||||
IsReadOnly="True"
|
||||
Margin="0,0,0,4"/>
|
||||
</hc:Col>
|
||||
|
||||
<hc:Col Span="8">
|
||||
<hc:TextBox Text="{Binding SplitPortionPackagesDisplay, Mode=OneWay}"
|
||||
Height="34"
|
||||
hc:InfoElement.Title="每份包数"
|
||||
hc:InfoElement.TitleWidth="80"
|
||||
hc:InfoElement.TitlePlacement="Left"
|
||||
hc:InfoElement.Placeholder="根据拆码明细自动拼接"
|
||||
IsReadOnly="True"
|
||||
Margin="0,0,0,4"/>
|
||||
</hc:Col>
|
||||
|
||||
<hc:Col Span="8">
|
||||
<hc:TextBox Text="{Binding Entry.WarehouseLocation, UpdateSourceTrigger=PropertyChanged}"
|
||||
Height="34"
|
||||
hc:InfoElement.Title="库位"
|
||||
hc:InfoElement.TitleWidth="80"
|
||||
hc:InfoElement.TitlePlacement="Left"
|
||||
hc:InfoElement.Placeholder="请输入库位"
|
||||
hc:InfoElement.ShowClearButton="True"
|
||||
Margin="0,0,0,4"/>
|
||||
</hc:Col>
|
||||
|
||||
<hc:Col Span="8">
|
||||
<hc:TextBox Text="{Binding Entry.UnloadOperator, UpdateSourceTrigger=PropertyChanged}"
|
||||
Height="34"
|
||||
hc:InfoElement.Title="卸货人"
|
||||
hc:InfoElement.TitleWidth="80"
|
||||
hc:InfoElement.TitlePlacement="Left"
|
||||
hc:InfoElement.Placeholder="请输入卸货人"
|
||||
hc:InfoElement.ShowClearButton="True"
|
||||
Margin="0,0,0,4"/>
|
||||
</hc:Col>
|
||||
|
||||
<hc:Col Span="8">
|
||||
<hc:ComboBox SelectedValuePath="Value"
|
||||
DisplayMemberPath="Key"
|
||||
ItemsSource="{Binding IsSpecialAdoptionOptions}"
|
||||
SelectedValue="{Binding IsSpecialAdoptionValue}"
|
||||
Height="34"
|
||||
hc:InfoElement.Title="是否特采"
|
||||
hc:InfoElement.TitleWidth="80"
|
||||
hc:InfoElement.TitlePlacement="Left"
|
||||
hc:InfoElement.Placeholder="请选择是否特采"
|
||||
Margin="0,0,0,4"/>
|
||||
</hc:Col>
|
||||
|
||||
<hc:Col Span="24">
|
||||
<hc:TextBox Text="{Binding Entry.Remark, UpdateSourceTrigger=PropertyChanged}"
|
||||
hc:InfoElement.Title="备注"
|
||||
hc:InfoElement.TitleWidth="80"
|
||||
hc:InfoElement.TitlePlacement="Left"
|
||||
hc:InfoElement.Placeholder="请输入备注"
|
||||
hc:InfoElement.ShowClearButton="True"
|
||||
TextWrapping="Wrap"
|
||||
AcceptsReturn="True"
|
||||
HorizontalAlignment="Stretch"
|
||||
Height="64"
|
||||
VerticalScrollBarVisibility="Auto"
|
||||
Margin="0,0,0,4"/>
|
||||
</hc:Col>
|
||||
|
||||
</hc:Row>
|
||||
</Grid>
|
||||
</Border>
|
||||
|
||||
<StackPanel>
|
||||
<StackPanel.Style>
|
||||
@@ -325,10 +498,9 @@
|
||||
<TextBlock DockPanel.Dock="Left" Text="特采时间" Width="80"
|
||||
VerticalAlignment="Center"
|
||||
Foreground="{DynamicResource PrimaryTextBrush}"/>
|
||||
<hc:DateTimePicker SelectedDateTime="{Binding Entry.SpecialAdoptionTime}"
|
||||
hc:InfoElement.Placeholder="请选择特采时间"
|
||||
hc:InfoElement.ShowClearButton="True"
|
||||
Height="34"/>
|
||||
<ctrls:DateTimeListPicker SelectedDateTime="{Binding Entry.SpecialAdoptionTime, Mode=TwoWay}"
|
||||
DateTimeFormat="yyyy-MM-dd HH:mm:ss"
|
||||
Placeholder="请选择特采时间"/>
|
||||
</DockPanel>
|
||||
</hc:Col>
|
||||
</hc:Row>
|
||||
@@ -415,11 +587,11 @@
|
||||
</TextBlock.Style>
|
||||
</TextBlock>
|
||||
|
||||
<!-- 数据行 -->
|
||||
<!-- 数据行:行高对齐表头 40px,输入框给足上下边距避免边框被裁切 -->
|
||||
<ItemsControl ItemsSource="{Binding SplitCodeDetails}">
|
||||
<ItemsControl.ItemTemplate>
|
||||
<DataTemplate>
|
||||
<Grid Height="36">
|
||||
<Grid Height="44">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="80"/>
|
||||
<ColumnDefinition Width="130"/>
|
||||
@@ -430,33 +602,38 @@
|
||||
<Border Grid.Column="0" BorderBrush="{DynamicResource BorderBrush}" BorderThickness="0,0,0,1">
|
||||
<TextBox Text="{Binding Portions, UpdateSourceTrigger=PropertyChanged}"
|
||||
HorizontalContentAlignment="Center" VerticalContentAlignment="Center"
|
||||
VerticalAlignment="Center" Height="32"
|
||||
BorderBrush="#D9D9D9" BorderThickness="1"
|
||||
Background="White" FontSize="13" Margin="4,4"/>
|
||||
Background="White" FontSize="13" Margin="4,0"/>
|
||||
</Border>
|
||||
<Border Grid.Column="1" BorderBrush="{DynamicResource BorderBrush}" BorderThickness="0,0,0,1">
|
||||
<TextBox Text="{Binding PortionWeight, UpdateSourceTrigger=PropertyChanged}"
|
||||
HorizontalContentAlignment="Center" VerticalContentAlignment="Center"
|
||||
VerticalAlignment="Center" Height="32"
|
||||
BorderBrush="#D9D9D9" BorderThickness="1"
|
||||
Background="White" FontSize="13" Margin="4,4"/>
|
||||
Background="White" FontSize="13" Margin="4,0"/>
|
||||
</Border>
|
||||
<Border Grid.Column="2" BorderBrush="{DynamicResource BorderBrush}" BorderThickness="0,0,0,1">
|
||||
<TextBox Text="{Binding PortionPackages, UpdateSourceTrigger=PropertyChanged}"
|
||||
HorizontalContentAlignment="Center" VerticalContentAlignment="Center"
|
||||
VerticalAlignment="Center" Height="32"
|
||||
BorderBrush="#D9D9D9" BorderThickness="1"
|
||||
Background="White" FontSize="13" Margin="4,4"/>
|
||||
Background="White" FontSize="13" Margin="4,0"/>
|
||||
</Border>
|
||||
<Border Grid.Column="3" BorderBrush="{DynamicResource BorderBrush}" BorderThickness="0,0,0,1">
|
||||
<TextBox Text="{Binding WarehouseLocation, UpdateSourceTrigger=PropertyChanged}"
|
||||
HorizontalContentAlignment="Center" VerticalContentAlignment="Center"
|
||||
HorizontalContentAlignment="Stretch" VerticalContentAlignment="Center"
|
||||
VerticalAlignment="Center" Height="32"
|
||||
BorderBrush="#D9D9D9" BorderThickness="1"
|
||||
Background="White" FontSize="13" Padding="8,0" Margin="4,4"/>
|
||||
Background="White" FontSize="13" Padding="8,0" Margin="4,0"/>
|
||||
</Border>
|
||||
<Border Grid.Column="4" BorderBrush="{DynamicResource BorderBrush}" BorderThickness="0,0,0,1">
|
||||
<Button Content="删除"
|
||||
Command="{Binding DataContext.RemoveSplitDetailCommand, RelativeSource={RelativeSource AncestorType=ItemsControl}}"
|
||||
CommandParameter="{Binding}"
|
||||
Style="{StaticResource ButtonDanger}"
|
||||
Padding="6,2" Margin="12,4" FontSize="11"/>
|
||||
VerticalAlignment="Center" Height="28"
|
||||
Padding="6,0" Margin="12,0" FontSize="11"/>
|
||||
</Border>
|
||||
</Grid>
|
||||
</DataTemplate>
|
||||
@@ -466,11 +643,96 @@
|
||||
</StackPanel>
|
||||
</Border>
|
||||
</StackPanel>
|
||||
</hc:ScrollViewer>
|
||||
</hc:ScrollViewer>
|
||||
|
||||
<GridSplitter x:Name="RightPaneSplitter"
|
||||
Grid.Column="1"
|
||||
Width="4"
|
||||
HorizontalAlignment="Stretch"
|
||||
VerticalAlignment="Stretch"
|
||||
ResizeBehavior="PreviousAndNext"
|
||||
ShowsPreview="True"
|
||||
Cursor="SizeWE"
|
||||
Background="{DynamicResource RegionBrush}"
|
||||
PreviewStyle="{StaticResource GridSplitterPreviewStyle}"
|
||||
DragCompleted="RightPaneSplitter_OnDragCompleted"/>
|
||||
|
||||
<Border Grid.Column="2"
|
||||
BorderBrush="{DynamicResource BorderBrush}"
|
||||
BorderThickness="1,0,0,0"
|
||||
Background="{DynamicResource RegionBrush}">
|
||||
<DockPanel LastChildFill="True">
|
||||
<Border DockPanel.Dock="Top" BorderBrush="{DynamicResource BorderBrush}" BorderThickness="0,0,0,1" Padding="8,6">
|
||||
<Grid>
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="*"/>
|
||||
<ColumnDefinition Width="Auto"/>
|
||||
</Grid.ColumnDefinitions>
|
||||
<StackPanel Orientation="Vertical">
|
||||
<TextBlock Text="今日入场" FontWeight="SemiBold" FontSize="13"
|
||||
Foreground="{DynamicResource PrimaryTextBrush}"/>
|
||||
<TextBlock Text="按入场/创建日期为本日的记录" FontSize="11" Opacity="0.65"
|
||||
Foreground="{DynamicResource SecondaryTextBrush}" TextWrapping="Wrap"/>
|
||||
</StackPanel>
|
||||
<Button Grid.Column="1" Content="刷新" Margin="4,0"
|
||||
Command="{Binding RefreshTodayEntriesCommand}"
|
||||
Style="{StaticResource ButtonPrimary}" Padding="8,2" FontSize="11" Height="26"/>
|
||||
</Grid>
|
||||
</Border>
|
||||
|
||||
<Border DockPanel.Dock="Top" Background="{DynamicResource SecondaryRegionBrush}" Height="28" Padding="8,0">
|
||||
<Grid>
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="*"/>
|
||||
<ColumnDefinition Width="*"/>
|
||||
</Grid.ColumnDefinitions>
|
||||
<TextBlock Text="条码" FontWeight="SemiBold" FontSize="11"
|
||||
VerticalAlignment="Center"
|
||||
Foreground="{DynamicResource PrimaryTextBrush}"/>
|
||||
<TextBlock Grid.Column="1" Text="物料" FontWeight="SemiBold" FontSize="11"
|
||||
Margin="6,0,0,0" VerticalAlignment="Center"
|
||||
Foreground="{DynamicResource PrimaryTextBrush}"/>
|
||||
</Grid>
|
||||
</Border>
|
||||
|
||||
<ListBox ItemsSource="{Binding TodayEntries}"
|
||||
SelectedItem="{Binding SelectedTodayEntry, Mode=TwoWay}"
|
||||
ScrollViewer.HorizontalScrollBarVisibility="Disabled"
|
||||
BorderThickness="0"
|
||||
Background="Transparent"
|
||||
ItemContainerStyle="{StaticResource TodayEntryListBoxItemStyle}">
|
||||
<ListBox.ItemTemplate>
|
||||
<DataTemplate DataType="{x:Type core:MesXslRawMaterialEntry}">
|
||||
<Grid>
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="*"/>
|
||||
<ColumnDefinition Width="*"/>
|
||||
</Grid.ColumnDefinitions>
|
||||
<TextBlock Grid.Column="0" Text="{Binding Barcode}" FontSize="12"
|
||||
TextTrimming="CharacterEllipsis"
|
||||
Foreground="{DynamicResource PrimaryTextBrush}"
|
||||
ToolTip="{Binding Barcode}"/>
|
||||
<TextBlock Grid.Column="1" Text="{Binding MaterialName}" FontSize="12"
|
||||
TextTrimming="CharacterEllipsis" Margin="6,0,0,0"
|
||||
Foreground="{DynamicResource SecondaryTextBrush}"
|
||||
ToolTip="{Binding MaterialName}"/>
|
||||
</Grid>
|
||||
</DataTemplate>
|
||||
</ListBox.ItemTemplate>
|
||||
</ListBox>
|
||||
</DockPanel>
|
||||
</Border>
|
||||
</Grid>
|
||||
|
||||
<StackPanel Grid.Row="2" Orientation="Horizontal" HorizontalAlignment="Center" Margin="0,12,0,20">
|
||||
<Button Content="重置" Command="{Binding ResetCommand}" Style="{StaticResource ButtonDefault}" Margin="0,0,15,0" Width="100"/>
|
||||
<Button Content="保存" Command="{Binding SaveCommand}" Style="{StaticResource ButtonPrimary}" Width="100"/>
|
||||
<Button Content="保存" Command="{Binding SaveCommand}" Style="{StaticResource ButtonPrimary}" Width="100" Margin="0,0,15,0"/>
|
||||
<Button Content="生成原材料卡片"
|
||||
Command="{Binding GenerateRawMaterialCardsCommand}"
|
||||
Style="{StaticResource ButtonDefault}"
|
||||
Width="130"
|
||||
IsEnabled="{Binding CanGenerateCards}"
|
||||
ToolTip="根据拆码明细生成原材料卡片(需先保存入场记录)"/>
|
||||
</StackPanel>
|
||||
</Grid>
|
||||
</UserControl>
|
||||
|
||||
@@ -1,30 +1,118 @@
|
||||
using System.ComponentModel;
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
using System.Windows.Controls.Primitives;
|
||||
using YY.Admin.ViewModels.RawMaterialEntry;
|
||||
|
||||
namespace YY.Admin.Views.RawMaterialEntry;
|
||||
|
||||
public partial class RawMaterialEntryOperationView : UserControl
|
||||
{
|
||||
private RawMaterialEntryOperationViewModel? _vm;
|
||||
private bool _initialized;
|
||||
|
||||
public RawMaterialEntryOperationView()
|
||||
{
|
||||
InitializeComponent();
|
||||
Loaded += OnLoaded;
|
||||
DataContextChanged += OnDataContextChanged;
|
||||
Unloaded += (_, _) => DetachVm();
|
||||
}
|
||||
|
||||
private void OnDataContextChanged(object sender, DependencyPropertyChangedEventArgs e)
|
||||
{
|
||||
DetachVm();
|
||||
if (DataContext is RawMaterialEntryOperationViewModel vm)
|
||||
{
|
||||
_vm = vm;
|
||||
vm.PropertyChanged += OnVmPropertyChanged;
|
||||
}
|
||||
|
||||
ApplySplitLayout();
|
||||
}
|
||||
|
||||
private void DetachVm()
|
||||
{
|
||||
if (_vm != null)
|
||||
{
|
||||
_vm.PropertyChanged -= OnVmPropertyChanged;
|
||||
_vm = null;
|
||||
}
|
||||
}
|
||||
|
||||
private void OnVmPropertyChanged(object? sender, PropertyChangedEventArgs e)
|
||||
{
|
||||
// null/空 表示“所有属性”通知(Prism/BindableBase 批量刷新时),需同步分割布局
|
||||
if (!string.IsNullOrEmpty(e.PropertyName)
|
||||
&& e.PropertyName is not nameof(RawMaterialEntryOperationViewModel.IsRightPanelExpanded)
|
||||
&& e.PropertyName is not nameof(RawMaterialEntryOperationViewModel.ExpandedRightPanelWidth))
|
||||
return;
|
||||
|
||||
_ = Dispatcher.InvokeAsync(ApplySplitLayout);
|
||||
}
|
||||
|
||||
/// <summary>按钮 Click 在 Command 之后执行,用于兜底刷新列宽(不重复切换状态)。</summary>
|
||||
private void ToggleTodayPanelButton_OnClick(object sender, RoutedEventArgs e) => ApplySplitLayout();
|
||||
|
||||
private void OnLoaded(object sender, RoutedEventArgs e)
|
||||
{
|
||||
if (_initialized)
|
||||
{
|
||||
return;
|
||||
}
|
||||
// 兜底:XAML 解析 prism:AutoWireViewModel 时设置 DataContext 早于构造函数订阅 DataContextChanged,
|
||||
// 首次进入页面 OnDataContextChanged 会错过回调,导致 _vm 为 null、PropertyChanged 未挂接,
|
||||
// 表现为「首次点击展开/收起按钮不生效」。这里在 Loaded 时补一次订阅。
|
||||
EnsureVmAttached();
|
||||
|
||||
if (DataContext is RawMaterialEntryEditDialogViewModel vm)
|
||||
ApplySplitLayout();
|
||||
|
||||
if (DataContext is RawMaterialEntryOperationViewModel vm && !_initialized)
|
||||
{
|
||||
vm.InitializeForAdd();
|
||||
_initialized = true;
|
||||
_ = vm.LoadTodayEntriesOnFirstShowAsync();
|
||||
}
|
||||
}
|
||||
|
||||
private void EnsureVmAttached()
|
||||
{
|
||||
if (_vm != null) return;
|
||||
if (DataContext is RawMaterialEntryOperationViewModel vm)
|
||||
{
|
||||
_vm = vm;
|
||||
vm.PropertyChanged += OnVmPropertyChanged;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 根据 ViewModel 同步右侧栏与分割条:展开时使用持久化宽度;折叠时右栏与分割条占宽均为 0(完全隐藏)。
|
||||
/// </summary>
|
||||
private void ApplySplitLayout()
|
||||
{
|
||||
var vm = _vm ?? DataContext as RawMaterialEntryOperationViewModel;
|
||||
if (vm == null) return;
|
||||
|
||||
if (vm.IsRightPanelExpanded)
|
||||
{
|
||||
RightPaneSplitter.Visibility = Visibility.Visible;
|
||||
SplitterCol.Width = new GridLength(4);
|
||||
RightPaneCol.Width = new GridLength(vm.ExpandedRightPanelWidth);
|
||||
}
|
||||
else
|
||||
{
|
||||
RightPaneSplitter.Visibility = Visibility.Collapsed;
|
||||
SplitterCol.Width = new GridLength(0);
|
||||
RightPaneCol.Width = new GridLength(0);
|
||||
}
|
||||
|
||||
MainSplitRoot.InvalidateMeasure();
|
||||
MainSplitRoot.UpdateLayout();
|
||||
}
|
||||
|
||||
private void RightPaneSplitter_OnDragCompleted(object sender, DragCompletedEventArgs e)
|
||||
{
|
||||
var vm = _vm ?? DataContext as RawMaterialEntryOperationViewModel;
|
||||
if (vm == null || !vm.IsRightPanelExpanded || e.Canceled) return;
|
||||
|
||||
var w = RightPaneCol.ActualWidth;
|
||||
if (w >= 120)
|
||||
vm.CommitRightPanelWidthFromView(w);
|
||||
}
|
||||
}
|
||||
|
||||
Binary file not shown.
@@ -8,6 +8,7 @@
|
||||
".NETCoreApp,Version=v8.0": {
|
||||
"YY.Admin.Services/1.0.0": {
|
||||
"dependencies": {
|
||||
"Microsoft.Extensions.Http": "8.0.0",
|
||||
"YY.Admin.Core": "1.0.0",
|
||||
"runtimepack.Microsoft.Windows.SDK.NET.Ref": "10.0.19041.57"
|
||||
},
|
||||
@@ -456,6 +457,32 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"Microsoft.Extensions.Diagnostics/8.0.0": {
|
||||
"dependencies": {
|
||||
"Microsoft.Extensions.Configuration": "9.0.8",
|
||||
"Microsoft.Extensions.Diagnostics.Abstractions": "8.0.0",
|
||||
"Microsoft.Extensions.Options.ConfigurationExtensions": "8.0.0"
|
||||
},
|
||||
"runtime": {
|
||||
"lib/net8.0/Microsoft.Extensions.Diagnostics.dll": {
|
||||
"assemblyVersion": "8.0.0.0",
|
||||
"fileVersion": "8.0.23.53103"
|
||||
}
|
||||
}
|
||||
},
|
||||
"Microsoft.Extensions.Diagnostics.Abstractions/8.0.0": {
|
||||
"dependencies": {
|
||||
"Microsoft.Extensions.DependencyInjection.Abstractions": "9.0.8",
|
||||
"Microsoft.Extensions.Options": "9.0.8",
|
||||
"System.Diagnostics.DiagnosticSource": "9.0.8"
|
||||
},
|
||||
"runtime": {
|
||||
"lib/net8.0/Microsoft.Extensions.Diagnostics.Abstractions.dll": {
|
||||
"assemblyVersion": "8.0.0.0",
|
||||
"fileVersion": "8.0.23.53103"
|
||||
}
|
||||
}
|
||||
},
|
||||
"Microsoft.Extensions.FileProviders.Abstractions/9.0.8": {
|
||||
"dependencies": {
|
||||
"Microsoft.Extensions.Primitives": "9.0.8"
|
||||
@@ -488,6 +515,22 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"Microsoft.Extensions.Http/8.0.0": {
|
||||
"dependencies": {
|
||||
"Microsoft.Extensions.Configuration.Abstractions": "9.0.8",
|
||||
"Microsoft.Extensions.DependencyInjection.Abstractions": "9.0.8",
|
||||
"Microsoft.Extensions.Diagnostics": "8.0.0",
|
||||
"Microsoft.Extensions.Logging": "9.0.8",
|
||||
"Microsoft.Extensions.Logging.Abstractions": "9.0.8",
|
||||
"Microsoft.Extensions.Options": "9.0.8"
|
||||
},
|
||||
"runtime": {
|
||||
"lib/net8.0/Microsoft.Extensions.Http.dll": {
|
||||
"assemblyVersion": "8.0.0.0",
|
||||
"fileVersion": "8.0.23.53103"
|
||||
}
|
||||
}
|
||||
},
|
||||
"Microsoft.Extensions.Logging/9.0.8": {
|
||||
"dependencies": {
|
||||
"Microsoft.Extensions.DependencyInjection": "9.0.8",
|
||||
@@ -525,6 +568,21 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"Microsoft.Extensions.Options.ConfigurationExtensions/8.0.0": {
|
||||
"dependencies": {
|
||||
"Microsoft.Extensions.Configuration.Abstractions": "9.0.8",
|
||||
"Microsoft.Extensions.Configuration.Binder": "9.0.8",
|
||||
"Microsoft.Extensions.DependencyInjection.Abstractions": "9.0.8",
|
||||
"Microsoft.Extensions.Options": "9.0.8",
|
||||
"Microsoft.Extensions.Primitives": "9.0.8"
|
||||
},
|
||||
"runtime": {
|
||||
"lib/net8.0/Microsoft.Extensions.Options.ConfigurationExtensions.dll": {
|
||||
"assemblyVersion": "8.0.0.0",
|
||||
"fileVersion": "8.0.23.53103"
|
||||
}
|
||||
}
|
||||
},
|
||||
"Microsoft.Extensions.Primitives/9.0.8": {
|
||||
"runtime": {
|
||||
"lib/net8.0/Microsoft.Extensions.Primitives.dll": {
|
||||
@@ -1703,6 +1761,20 @@
|
||||
"path": "microsoft.extensions.dependencyinjection.abstractions/9.0.8",
|
||||
"hashPath": "microsoft.extensions.dependencyinjection.abstractions.9.0.8.nupkg.sha512"
|
||||
},
|
||||
"Microsoft.Extensions.Diagnostics/8.0.0": {
|
||||
"type": "package",
|
||||
"serviceable": true,
|
||||
"sha512": "sha512-3PZp/YSkIXrF7QK7PfC1bkyRYwqOHpWFad8Qx+4wkuumAeXo1NHaxpS9LboNA9OvNSAu+QOVlXbMyoY+pHSqcw==",
|
||||
"path": "microsoft.extensions.diagnostics/8.0.0",
|
||||
"hashPath": "microsoft.extensions.diagnostics.8.0.0.nupkg.sha512"
|
||||
},
|
||||
"Microsoft.Extensions.Diagnostics.Abstractions/8.0.0": {
|
||||
"type": "package",
|
||||
"serviceable": true,
|
||||
"sha512": "sha512-JHYCQG7HmugNYUhOl368g+NMxYE/N/AiclCYRNlgCY9eVyiBkOHMwK4x60RYMxv9EL3+rmj1mqHvdCiPpC+D4Q==",
|
||||
"path": "microsoft.extensions.diagnostics.abstractions/8.0.0",
|
||||
"hashPath": "microsoft.extensions.diagnostics.abstractions.8.0.0.nupkg.sha512"
|
||||
},
|
||||
"Microsoft.Extensions.FileProviders.Abstractions/9.0.8": {
|
||||
"type": "package",
|
||||
"serviceable": true,
|
||||
@@ -1724,6 +1796,13 @@
|
||||
"path": "microsoft.extensions.filesystemglobbing/9.0.8",
|
||||
"hashPath": "microsoft.extensions.filesystemglobbing.9.0.8.nupkg.sha512"
|
||||
},
|
||||
"Microsoft.Extensions.Http/8.0.0": {
|
||||
"type": "package",
|
||||
"serviceable": true,
|
||||
"sha512": "sha512-cWz4caHwvx0emoYe7NkHPxII/KkTI8R/LC9qdqJqnKv2poTJ4e2qqPGQqvRoQ5kaSA4FU5IV3qFAuLuOhoqULQ==",
|
||||
"path": "microsoft.extensions.http/8.0.0",
|
||||
"hashPath": "microsoft.extensions.http.8.0.0.nupkg.sha512"
|
||||
},
|
||||
"Microsoft.Extensions.Logging/9.0.8": {
|
||||
"type": "package",
|
||||
"serviceable": true,
|
||||
@@ -1745,6 +1824,13 @@
|
||||
"path": "microsoft.extensions.options/9.0.8",
|
||||
"hashPath": "microsoft.extensions.options.9.0.8.nupkg.sha512"
|
||||
},
|
||||
"Microsoft.Extensions.Options.ConfigurationExtensions/8.0.0": {
|
||||
"type": "package",
|
||||
"serviceable": true,
|
||||
"sha512": "sha512-0f4DMRqEd50zQh+UyJc+/HiBsZ3vhAQALgdkcQEalSH1L2isdC7Yj54M3cyo5e+BeO5fcBQ7Dxly8XiBBcvRgw==",
|
||||
"path": "microsoft.extensions.options.configurationextensions/8.0.0",
|
||||
"hashPath": "microsoft.extensions.options.configurationextensions.8.0.0.nupkg.sha512"
|
||||
},
|
||||
"Microsoft.Extensions.Primitives/9.0.8": {
|
||||
"type": "package",
|
||||
"serviceable": true,
|
||||
|
||||
Binary file not shown.
@@ -2285,6 +2285,7 @@
|
||||
},
|
||||
"YY.Admin.Services/1.0.0": {
|
||||
"dependencies": {
|
||||
"Microsoft.Extensions.Http": "10.0.7",
|
||||
"YY.Admin.Core": "1.0.0"
|
||||
},
|
||||
"runtime": {
|
||||
|
||||
Binary file not shown.
Binary file not shown.
Reference in New Issue
Block a user