This commit is contained in:
jhd
2026-05-30 10:47:52 +08:00
48 changed files with 2758 additions and 354 deletions

View File

@@ -76,6 +76,16 @@ public class ApsPlanDetailController extends BaseController {
return toAjax(iApsPlanDetailService.insertByBo(bo));
}
/**
* 批量新增排产单明细
*/
@Log(title = "排产单明细", businessType = BusinessType.INSERT)
@RepeatSubmit()
@PostMapping("/batch")
public R<Void> batchAdd(@Validated(AddGroup.class) @RequestBody List<ApsPlanDetailBo> boList) {
return toAjax(iApsPlanDetailService.insertBatchByBo(boList));
}
/**
* 修改排产单明细
*/

View File

@@ -226,5 +226,9 @@ public class ApsPlanDetailBo extends BaseEntity {
*/
private String remark;
/**
* 排产日期(字符串格式,例如 '2025-12-29')
*/
private String detailDate;
}

View File

@@ -184,6 +184,10 @@ public class ApsPlanDetail extends BaseEntity {
* 备注
*/
private String remark;
/**
* 排产日期(字符串格式,例如 '2025-12-29')
*/
private String detailDate;
/**
* 删除标记(0正常 1删除)
*/

View File

@@ -261,6 +261,12 @@ public class ApsPlanDetailVo {
@ExcelProperty(value = "备注")
private String remark;
/**
* 排产日期(字符串格式,例如 '2025-12-29')
*/
@ExcelProperty(value = "排产日期")
private String detailDate;
/**
* 技术附件
*/

View File

@@ -43,6 +43,11 @@ public interface IApsPlanDetailService {
*/
Boolean insertByBo(ApsPlanDetailBo bo);
/**
* 批量新增排产单明细
*/
Boolean insertBatchByBo(List<ApsPlanDetailBo> boList);
/**
* 修改排产单明细
*/

View File

@@ -101,6 +101,7 @@ public class ApsPlanDetailServiceImpl implements IApsPlanDetailService {
qw.eq(StringUtils.isNotBlank(bo.getSampleReq()), "d.sample_req", bo.getSampleReq());
qw.eq(bo.getStartTime() != null, "d.start_time", bo.getStartTime());
qw.eq(bo.getEndTime() != null, "d.end_time", bo.getEndTime());
qw.eq(StringUtils.isNotBlank(bo.getDetailDate()), "d.detail_date", bo.getDetailDate());
//根据创建时间倒叙
qw.orderByDesc("d.create_time");
return qw;
@@ -168,6 +169,7 @@ public class ApsPlanDetailServiceImpl implements IApsPlanDetailService {
lqw.eq(StringUtils.isNotBlank(bo.getSampleReq()), ApsPlanDetail::getSampleReq, bo.getSampleReq());
lqw.eq(bo.getStartTime() != null, ApsPlanDetail::getStartTime, bo.getStartTime());
lqw.eq(bo.getEndTime() != null, ApsPlanDetail::getEndTime, bo.getEndTime());
lqw.eq(StringUtils.isNotBlank(bo.getDetailDate()), ApsPlanDetail::getDetailDate, bo.getDetailDate());
return lqw;
}
@@ -185,6 +187,16 @@ public class ApsPlanDetailServiceImpl implements IApsPlanDetailService {
return flag;
}
/**
* 批量新增排产单明细
*/
@Override
public Boolean insertBatchByBo(List<ApsPlanDetailBo> boList) {
List<ApsPlanDetail> list = BeanUtil.copyToList(boList, ApsPlanDetail.class);
list.forEach(this::validEntityBeforeSave);
return baseMapper.insertBatch(list);
}
/**
* 修改排产单明细
*/

View File

@@ -44,6 +44,7 @@
<result property="startTime" column="start_time"/>
<result property="endTime" column="end_time"/>
<result property="remark" column="remark"/>
<result property="detailDate" column="detail_date"/>
<result property="delFlag" column="del_flag"/>
<result property="createBy" column="create_by"/>
<result property="updateBy" column="update_by"/>

View File

@@ -29,6 +29,7 @@ import java.io.UnsupportedEncodingException;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.Set;
/**
* Excel相关处理
@@ -149,6 +150,33 @@ public class ExcelUtil {
builder.doWrite(list);
}
/**
* 导出excel仅导出指定列
*
* @param list 导出数据集合
* @param sheetName 工作表的名称
* @param clazz 实体类
* @param includeColumnFieldNames 需要导出的字段名集合Java字段名非Excel列名
* @param response 响应体
*/
public static <T> void exportExcel(List<T> list, String sheetName, Class<T> clazz,
Set<String> includeColumnFieldNames,
HttpServletResponse response) {
try {
resetResponse(sheetName, response);
ServletOutputStream os = response.getOutputStream();
EasyExcel.write(os, clazz)
.autoCloseStream(false)
.registerWriteHandler(new LongestMatchColumnWidthStyleStrategy())
.registerConverter(new ExcelBigNumberConvert())
.includeColumnFieldNames(includeColumnFieldNames)
.sheet(sheetName)
.doWrite(list);
} catch (IOException e) {
throw new RuntimeException("导出Excel异常");
}
}
/**
* 单表多数据模板导出 模板格式为 {.属性}
*

View File

@@ -0,0 +1,99 @@
package com.klp.mes.eqp.controller;
import java.util.List;
import java.util.Arrays;
import lombok.RequiredArgsConstructor;
import javax.servlet.http.HttpServletResponse;
import javax.validation.constraints.*;
import org.springframework.web.bind.annotation.*;
import org.springframework.validation.annotation.Validated;
import com.klp.common.annotation.RepeatSubmit;
import com.klp.common.annotation.Log;
import com.klp.common.core.controller.BaseController;
import com.klp.common.core.domain.PageQuery;
import com.klp.common.core.domain.R;
import com.klp.common.core.validate.AddGroup;
import com.klp.common.core.validate.EditGroup;
import com.klp.common.enums.BusinessType;
import com.klp.common.utils.poi.ExcelUtil;
import com.klp.mes.eqp.domain.vo.EqpEquipmentInspectionApprovalVo;
import com.klp.mes.eqp.domain.bo.EqpEquipmentInspectionApprovalBo;
import com.klp.mes.eqp.service.IEqpEquipmentInspectionApprovalService;
import com.klp.common.core.page.TableDataInfo;
/**
* 设备巡检审批(按产线+时间范围审批)
*
* @author klp
* @date 2026-05-29
*/
@Validated
@RequiredArgsConstructor
@RestController
@RequestMapping("/eqp/equipmentInspectionApproval")
public class EqpEquipmentInspectionApprovalController extends BaseController {
private final IEqpEquipmentInspectionApprovalService iEqpEquipmentInspectionApprovalService;
/**
* 查询设备巡检审批(按产线+时间范围审批)列表
*/
@GetMapping("/list")
public TableDataInfo<EqpEquipmentInspectionApprovalVo> list(EqpEquipmentInspectionApprovalBo bo, PageQuery pageQuery) {
return iEqpEquipmentInspectionApprovalService.queryPageList(bo, pageQuery);
}
/**
* 导出设备巡检审批(按产线+时间范围审批)列表
*/
@Log(title = "设备巡检审批(按产线+时间范围审批)", businessType = BusinessType.EXPORT)
@PostMapping("/export")
public void export(EqpEquipmentInspectionApprovalBo bo, HttpServletResponse response) {
List<EqpEquipmentInspectionApprovalVo> list = iEqpEquipmentInspectionApprovalService.queryList(bo);
ExcelUtil.exportExcel(list, "设备巡检审批(按产线+时间范围审批)", EqpEquipmentInspectionApprovalVo.class, response);
}
/**
* 获取设备巡检审批(按产线+时间范围审批)详细信息
*
* @param approvalId 主键
*/
@GetMapping("/{approvalId}")
public R<EqpEquipmentInspectionApprovalVo> getInfo(@NotNull(message = "主键不能为空")
@PathVariable Long approvalId) {
return R.ok(iEqpEquipmentInspectionApprovalService.queryById(approvalId));
}
/**
* 新增设备巡检审批(按产线+时间范围审批)
*/
@Log(title = "设备巡检审批(按产线+时间范围审批)", businessType = BusinessType.INSERT)
@RepeatSubmit()
@PostMapping()
public R<Void> add(@Validated(AddGroup.class) @RequestBody EqpEquipmentInspectionApprovalBo bo) {
return toAjax(iEqpEquipmentInspectionApprovalService.insertByBo(bo));
}
/**
* 修改设备巡检审批(按产线+时间范围审批)
*/
@Log(title = "设备巡检审批(按产线+时间范围审批)", businessType = BusinessType.UPDATE)
@RepeatSubmit()
@PutMapping()
public R<Void> edit(@Validated(EditGroup.class) @RequestBody EqpEquipmentInspectionApprovalBo bo) {
return toAjax(iEqpEquipmentInspectionApprovalService.updateByBo(bo));
}
/**
* 删除设备巡检审批(按产线+时间范围审批)
*
* @param approvalIds 主键串
*/
@Log(title = "设备巡检审批(按产线+时间范围审批)", businessType = BusinessType.DELETE)
@DeleteMapping("/{approvalIds}")
public R<Void> remove(@NotEmpty(message = "主键不能为空")
@PathVariable Long[] approvalIds) {
return toAjax(iEqpEquipmentInspectionApprovalService.deleteWithValidByIds(Arrays.asList(approvalIds), true));
}
}

View File

@@ -0,0 +1,75 @@
package com.klp.mes.eqp.domain;
import com.baomidou.mybatisplus.annotation.*;
import com.klp.common.core.domain.BaseEntity;
import lombok.Data;
import lombok.EqualsAndHashCode;
import java.util.Date;
import com.fasterxml.jackson.annotation.JsonFormat;
/**
* 设备巡检审批(按产线+时间范围审批)对象 eqp_equipment_inspection_approval
*
* @author klp
* @date 2026-05-29
*/
@Data
@EqualsAndHashCode(callSuper = true)
@TableName("eqp_equipment_inspection_approval")
public class EqpEquipmentInspectionApproval extends BaseEntity {
private static final long serialVersionUID=1L;
/**
* 审批ID 主键
*/
@TableId(value = "approval_id")
private Long approvalId;
/**
* 产线ID对应你刚改的bigint类型
*/
private Long productionLine;
/**
* 巡检开始时间
*/
private Date insStartTime;
/**
* 巡检结束时间
*/
private Date insEndTime;
/**
* 申请人
*/
private String applyUser;
/**
* 申请时间
*/
private Date applyTime;
/**
* 审批状态 1=待审批 2=已通过 3=已驳回 4=已撤销
*/
private Integer approvalStatus;
/**
* 审批人
*/
private String approvalUser;
/**
* 审批时间
*/
private Date approvalTime;
/**
* 审批意见
*/
private String approvalOpinion;
/**
* 备注
*/
private String remark;
/**
* 删除标识 0正常 2删除
*/
@TableLogic
private Long delFlag;
}

View File

@@ -0,0 +1,78 @@
package com.klp.mes.eqp.domain.bo;
import com.klp.common.core.domain.BaseEntity;
import lombok.Data;
import lombok.EqualsAndHashCode;
import javax.validation.constraints.*;
import java.util.Date;
import com.fasterxml.jackson.annotation.JsonFormat;
/**
* 设备巡检审批(按产线+时间范围审批)业务对象 eqp_equipment_inspection_approval
*
* @author klp
* @date 2026-05-29
*/
@Data
@EqualsAndHashCode(callSuper = true)
public class EqpEquipmentInspectionApprovalBo extends BaseEntity {
/**
* 审批ID 主键
*/
private Long approvalId;
/**
* 产线ID对应你刚改的bigint类型
*/
private Long productionLine;
/**
* 巡检开始时间
*/
private Date insStartTime;
/**
* 巡检结束时间
*/
private Date insEndTime;
/**
* 申请人
*/
private String applyUser;
/**
* 申请时间
*/
private Date applyTime;
/**
* 审批状态 1=待审批 2=已通过 3=已驳回 4=已撤销
*/
private Integer approvalStatus;
/**
* 审批人
*/
private String approvalUser;
/**
* 审批时间
*/
private Date approvalTime;
/**
* 审批意见
*/
private String approvalOpinion;
/**
* 备注
*/
private String remark;
}

View File

@@ -0,0 +1,92 @@
package com.klp.mes.eqp.domain.vo;
import java.util.Date;
import com.fasterxml.jackson.annotation.JsonFormat;
import com.alibaba.excel.annotation.ExcelIgnoreUnannotated;
import com.alibaba.excel.annotation.ExcelProperty;
import com.klp.common.annotation.ExcelDictFormat;
import com.klp.common.convert.ExcelDictConvert;
import lombok.Data;
/**
* 设备巡检审批(按产线+时间范围审批)视图对象 eqp_equipment_inspection_approval
*
* @author klp
* @date 2026-05-29
*/
@Data
@ExcelIgnoreUnannotated
public class EqpEquipmentInspectionApprovalVo {
private static final long serialVersionUID = 1L;
/**
* 审批ID 主键
*/
@ExcelProperty(value = "审批ID 主键")
private Long approvalId;
/**
* 产线ID对应你刚改的bigint类型
*/
@ExcelProperty(value = "产线ID", converter = ExcelDictConvert.class)
@ExcelDictFormat(readConverterExp = "对=应你刚改的bigint类型")
private Long productionLine;
/**
* 巡检开始时间
*/
@ExcelProperty(value = "巡检开始时间")
private Date insStartTime;
/**
* 巡检结束时间
*/
@ExcelProperty(value = "巡检结束时间")
private Date insEndTime;
/**
* 申请人
*/
@ExcelProperty(value = "申请人")
private String applyUser;
/**
* 申请时间
*/
@ExcelProperty(value = "申请时间")
private Date applyTime;
/**
* 审批状态 1=待审批 2=已通过 3=已驳回 4=已撤销
*/
@ExcelProperty(value = "审批状态 1=待审批 2=已通过 3=已驳回 4=已撤销")
private Integer approvalStatus;
/**
* 审批人
*/
@ExcelProperty(value = "审批人")
private String approvalUser;
/**
* 审批时间
*/
@ExcelProperty(value = "审批时间")
private Date approvalTime;
/**
* 审批意见
*/
@ExcelProperty(value = "审批意见")
private String approvalOpinion;
/**
* 备注
*/
@ExcelProperty(value = "备注")
private String remark;
}

View File

@@ -0,0 +1,15 @@
package com.klp.mes.eqp.mapper;
import com.klp.mes.eqp.domain.EqpEquipmentInspectionApproval;
import com.klp.mes.eqp.domain.vo.EqpEquipmentInspectionApprovalVo;
import com.klp.common.core.mapper.BaseMapperPlus;
/**
* 设备巡检审批(按产线+时间范围审批Mapper接口
*
* @author klp
* @date 2026-05-29
*/
public interface EqpEquipmentInspectionApprovalMapper extends BaseMapperPlus<EqpEquipmentInspectionApprovalMapper, EqpEquipmentInspectionApproval, EqpEquipmentInspectionApprovalVo> {
}

View File

@@ -0,0 +1,49 @@
package com.klp.mes.eqp.service;
import com.klp.mes.eqp.domain.EqpEquipmentInspectionApproval;
import com.klp.mes.eqp.domain.vo.EqpEquipmentInspectionApprovalVo;
import com.klp.mes.eqp.domain.bo.EqpEquipmentInspectionApprovalBo;
import com.klp.common.core.page.TableDataInfo;
import com.klp.common.core.domain.PageQuery;
import java.util.Collection;
import java.util.List;
/**
* 设备巡检审批(按产线+时间范围审批Service接口
*
* @author klp
* @date 2026-05-29
*/
public interface IEqpEquipmentInspectionApprovalService {
/**
* 查询设备巡检审批(按产线+时间范围审批)
*/
EqpEquipmentInspectionApprovalVo queryById(Long approvalId);
/**
* 查询设备巡检审批(按产线+时间范围审批)列表
*/
TableDataInfo<EqpEquipmentInspectionApprovalVo> queryPageList(EqpEquipmentInspectionApprovalBo bo, PageQuery pageQuery);
/**
* 查询设备巡检审批(按产线+时间范围审批)列表
*/
List<EqpEquipmentInspectionApprovalVo> queryList(EqpEquipmentInspectionApprovalBo bo);
/**
* 新增设备巡检审批(按产线+时间范围审批)
*/
Boolean insertByBo(EqpEquipmentInspectionApprovalBo bo);
/**
* 修改设备巡检审批(按产线+时间范围审批)
*/
Boolean updateByBo(EqpEquipmentInspectionApprovalBo bo);
/**
* 校验并批量删除设备巡检审批(按产线+时间范围审批)信息
*/
Boolean deleteWithValidByIds(Collection<Long> ids, Boolean isValid);
}

View File

@@ -0,0 +1,125 @@
package com.klp.mes.eqp.service.impl;
import cn.hutool.core.bean.BeanUtil;
import com.klp.common.core.page.TableDataInfo;
import com.klp.common.core.domain.PageQuery;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.klp.common.helper.LoginHelper;
import com.klp.common.utils.StringUtils;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import com.klp.mes.eqp.domain.bo.EqpEquipmentInspectionApprovalBo;
import com.klp.mes.eqp.domain.vo.EqpEquipmentInspectionApprovalVo;
import com.klp.mes.eqp.domain.EqpEquipmentInspectionApproval;
import com.klp.mes.eqp.mapper.EqpEquipmentInspectionApprovalMapper;
import com.klp.mes.eqp.service.IEqpEquipmentInspectionApprovalService;
import java.util.Date;
import java.util.List;
import java.util.Map;
import java.util.Collection;
/**
* 设备巡检审批(按产线+时间范围审批Service业务层处理
*
* @author klp
* @date 2026-05-29
*/
@RequiredArgsConstructor
@Service
public class EqpEquipmentInspectionApprovalServiceImpl implements IEqpEquipmentInspectionApprovalService {
private final EqpEquipmentInspectionApprovalMapper baseMapper;
/**
* 查询设备巡检审批(按产线+时间范围审批)
*/
@Override
public EqpEquipmentInspectionApprovalVo queryById(Long approvalId){
return baseMapper.selectVoById(approvalId);
}
/**
* 查询设备巡检审批(按产线+时间范围审批)列表
*/
@Override
public TableDataInfo<EqpEquipmentInspectionApprovalVo> queryPageList(EqpEquipmentInspectionApprovalBo bo, PageQuery pageQuery) {
LambdaQueryWrapper<EqpEquipmentInspectionApproval> lqw = buildQueryWrapper(bo);
Page<EqpEquipmentInspectionApprovalVo> result = baseMapper.selectVoPage(pageQuery.build(), lqw);
return TableDataInfo.build(result);
}
/**
* 查询设备巡检审批(按产线+时间范围审批)列表
*/
@Override
public List<EqpEquipmentInspectionApprovalVo> queryList(EqpEquipmentInspectionApprovalBo bo) {
LambdaQueryWrapper<EqpEquipmentInspectionApproval> lqw = buildQueryWrapper(bo);
return baseMapper.selectVoList(lqw);
}
private LambdaQueryWrapper<EqpEquipmentInspectionApproval> buildQueryWrapper(EqpEquipmentInspectionApprovalBo bo) {
Map<String, Object> params = bo.getParams();
LambdaQueryWrapper<EqpEquipmentInspectionApproval> lqw = Wrappers.lambdaQuery();
lqw.eq(bo.getProductionLine() != null, EqpEquipmentInspectionApproval::getProductionLine, bo.getProductionLine());
lqw.eq(bo.getInsStartTime() != null, EqpEquipmentInspectionApproval::getInsStartTime, bo.getInsStartTime());
lqw.eq(bo.getInsEndTime() != null, EqpEquipmentInspectionApproval::getInsEndTime, bo.getInsEndTime());
lqw.eq(StringUtils.isNotBlank(bo.getApplyUser()), EqpEquipmentInspectionApproval::getApplyUser, bo.getApplyUser());
lqw.eq(bo.getApplyTime() != null, EqpEquipmentInspectionApproval::getApplyTime, bo.getApplyTime());
lqw.eq(bo.getApprovalStatus() != null, EqpEquipmentInspectionApproval::getApprovalStatus, bo.getApprovalStatus());
lqw.eq(StringUtils.isNotBlank(bo.getApprovalUser()), EqpEquipmentInspectionApproval::getApprovalUser, bo.getApprovalUser());
lqw.eq(bo.getApprovalTime() != null, EqpEquipmentInspectionApproval::getApprovalTime, bo.getApprovalTime());
lqw.eq(StringUtils.isNotBlank(bo.getApprovalOpinion()), EqpEquipmentInspectionApproval::getApprovalOpinion, bo.getApprovalOpinion());
return lqw;
}
/**
* 新增设备巡检审批(按产线+时间范围审批)
*/
@Override
public Boolean insertByBo(EqpEquipmentInspectionApprovalBo bo) {
EqpEquipmentInspectionApproval add = BeanUtil.toBean(bo, EqpEquipmentInspectionApproval.class);
add.setApplyUser(LoginHelper.getNickName());
add.setApplyTime(new Date());
validEntityBeforeSave(add);
boolean flag = baseMapper.insert(add) > 0;
if (flag) {
bo.setApprovalId(add.getApprovalId());
}
return flag;
}
/**
* 修改设备巡检审批(按产线+时间范围审批)
*/
@Override
public Boolean updateByBo(EqpEquipmentInspectionApprovalBo bo) {
EqpEquipmentInspectionApproval update = BeanUtil.toBean(bo, EqpEquipmentInspectionApproval.class);
if (bo.getApprovalStatus() != null && (bo.getApprovalStatus() == 2 || bo.getApprovalStatus() == 3)) {
update.setApprovalUser(LoginHelper.getNickName());
update.setApprovalTime(new Date());
}
validEntityBeforeSave(update);
return baseMapper.updateById(update) > 0;
}
/**
* 保存前的数据校验
*/
private void validEntityBeforeSave(EqpEquipmentInspectionApproval entity){
//TODO 做一些数据校验,如唯一约束
}
/**
* 批量删除设备巡检审批(按产线+时间范围审批)
*/
@Override
public Boolean deleteWithValidByIds(Collection<Long> ids, Boolean isValid) {
if(isValid){
//TODO 做一些业务上的校验,判断是否需要校验
}
return baseMapper.deleteBatchIds(ids) > 0;
}
}

View File

@@ -94,6 +94,10 @@ public class QcInspectionTask extends BaseEntity {
* 附件路径(多个用英文逗号分隔)
*/
private String attachmentFiles;
/**
* 厂家卷号集合,多个使用英文逗号分隔
*/
private String supplierCoilNos;
/**
* 删除标志0=正常1=已删除)

View File

@@ -108,4 +108,9 @@ public class QcInspectionTaskBo extends BaseEntity {
* 附件路径(多个用英文逗号分隔)
*/
private String attachmentFiles;
/**
* 厂家卷号集合,多个使用英文逗号分隔
*/
private String supplierCoilNos;
}

View File

@@ -134,6 +134,11 @@ public class QcInspectionTaskVo {
*/
private String attachmentFiles;
/**
* 厂家卷号集合,多个使用英文逗号分隔
*/
private String supplierCoilNos;
private List<WmsMaterialCoilVo> coilList;

View File

@@ -136,6 +136,7 @@ public class QcInspectionTaskServiceImpl implements IQcInspectionTaskService {
lqw.eq(StringUtils.isNotBlank(bo.getResult()), QcInspectionTask::getResult, bo.getResult());
lqw.like(StringUtils.isNotBlank(bo.getCoilIds()), QcInspectionTask::getCoilIds, bo.getCoilIds());
lqw.like(StringUtils.isNotBlank(bo.getEnterCoilNos()), QcInspectionTask::getEnterCoilNos, bo.getEnterCoilNos());
lqw.like(StringUtils.isNotBlank(bo.getSupplierCoilNos()), QcInspectionTask::getSupplierCoilNos, bo.getSupplierCoilNos());
return lqw;
}

View File

@@ -0,0 +1,27 @@
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.klp.mes.eqp.mapper.EqpEquipmentInspectionApprovalMapper">
<resultMap type="com.klp.mes.eqp.domain.EqpEquipmentInspectionApproval" id="EqpEquipmentInspectionApprovalResult">
<result property="approvalId" column="approval_id"/>
<result property="productionLine" column="production_line"/>
<result property="insStartTime" column="ins_start_time"/>
<result property="insEndTime" column="ins_end_time"/>
<result property="applyUser" column="apply_user"/>
<result property="applyTime" column="apply_time"/>
<result property="approvalStatus" column="approval_status"/>
<result property="approvalUser" column="approval_user"/>
<result property="approvalTime" column="approval_time"/>
<result property="approvalOpinion" column="approval_opinion"/>
<result property="remark" column="remark"/>
<result property="createBy" column="create_by"/>
<result property="createTime" column="create_time"/>
<result property="updateBy" column="update_by"/>
<result property="updateTime" column="update_time"/>
<result property="delFlag" column="del_flag"/>
</resultMap>
</mapper>

View File

@@ -23,6 +23,7 @@
<result property="attachmentFiles" column="attachment_files"/>
<result property="coilIds" column="coil_ids"/>
<result property="enterCoilNos" column="enter_coil_nos"/>
<result property="supplierCoilNos" column="supplier_coil_nos"/>
<result property="delFlag" column="del_flag"/>
<result property="createTime" column="create_time"/>
<result property="createBy" column="create_by"/>

View File

@@ -26,6 +26,15 @@ export function addPlanDetail(data) {
})
}
// 批量新增排产单明细
export function addPlanDetailBatch(data) {
return request({
url: '/aps/planDetail/batch',
method: 'post',
data: data
})
}
// 修改排产单明细
export function updatePlanDetail(data) {
return request({

View File

@@ -0,0 +1,39 @@
import request from '@/utils/request'
export function listEquipmentInspectionApproval(query) {
return request({
url: '/eqp/equipmentInspectionApproval/list',
method: 'get',
params: query
})
}
export function getEquipmentInspectionApproval(approvalId) {
return request({
url: '/eqp/equipmentInspectionApproval/' + approvalId,
method: 'get'
})
}
export function addEquipmentInspectionApproval(data) {
return request({
url: '/eqp/equipmentInspectionApproval',
method: 'post',
data: data
})
}
export function updateEquipmentInspectionApproval(data) {
return request({
url: '/eqp/equipmentInspectionApproval',
method: 'put',
data: data
})
}
export function delEquipmentInspectionApproval(approvalId) {
return request({
url: '/eqp/equipmentInspectionApproval/' + approvalId,
method: 'delete'
})
}

View File

@@ -489,3 +489,11 @@ export function listLightCoil(data) {
data: data
})
}
// 获取可导出的列元数据
export function getExportColumns() {
return request({
url: '/wms/materialCoil/exportColumns',
method: 'get',
})
}

View File

@@ -61,6 +61,14 @@ export function listBoundCoil(query) {
})
}
export function getBoundCoilStatisticsList(query) {
return request({
url: '/wms/deliveryWaybillDetail/statistics',
method: 'get',
params: query
})
}
// 按销售员查询订单明细的卷
export function listDeliveryWaybillDetailBySaleman(principal) {
return request({

View File

@@ -79,6 +79,10 @@
canSelectDisabled :clearInput="false" clearable />
</el-form-item>
<el-form-item label="备注" prop="remark" v-if="orderBy">
<el-input v-model="queryParams.remark" placeholder="请输入备注" clearable size="small" />
</el-form-item>
<el-form-item>
<el-button v-if="multiple" type="primary" size="small" icon="el-icon-check"

View File

@@ -10,6 +10,7 @@
<div>
<el-button type="primary" plain @click="handleAdd">新增明细</el-button>
<el-button type="success" plain @click="handleBatchAdd">批量新增</el-button>
<el-button type="info" plain @click="handleImport">导入</el-button>
<el-button type="danger" plain @click="handleBatchDelete"
:disabled="selectedRows.length === 0">批量删除</el-button>
<el-button type="warning" plain @click="handleBatchTransfer"
@@ -60,6 +61,11 @@
</el-table-column>
<!-- 订单信息 -->
<el-table-column label="排产日期" align="center" prop="detailDate" width="120">
<template slot-scope="scope">
<el-input v-model="scope.row.detailDate" style="background-color: #fff3e6;" />
</template>
</el-table-column>
<el-table-column label="订单号" align="center" prop="orderCode" width="200">
<template slot-scope="scope">
<el-input v-model="scope.row.orderCode" style="background-color: #fff3e6;">
@@ -491,16 +497,169 @@
:loading="buttonLoading">确认新增</el-button>
</div>
</el-dialog>
<!-- 导入对话框 -->
<el-dialog title="导入排产单明细" :visible.sync="importDialogVisible" width="900px" append-to-body
:close-on-click-modal="false" @close="resetImportState">
<div class="import-container">
<el-steps :active="importStep" align-center finish-status="success">
<el-step title="下载模板" description="下载并填写Excel模板" />
<el-step title="上传文件" description="选择填好的文件" />
<el-step title="校验数据" description="验证数据完整性" />
<el-step title="开始导入" description="预览并确认导入" />
</el-steps>
<div class="import-step-content">
<!-- Step 0: 下载模板 -->
<div v-show="importStep === 0" class="step-body">
<div class="step-download">
<i class="el-icon-download step-icon"></i>
<p>请先下载Excel模板按格式填写排产单明细数据</p>
<el-button type="primary" icon="el-icon-download" @click="downloadTemplate">下载导入模板</el-button>
</div>
</div>
<!-- Step 1: 上传文件 -->
<div v-show="importStep === 1" class="step-body">
<div class="step-upload">
<el-upload ref="importUpload" class="custom-upload" drag :auto-upload="false" :show-file-list="false"
:on-change="handleImportFileChange" accept=".xlsx,.xls">
<div class="upload-zone-body">
<div class="upload-zone-icon">
<i class="el-icon-upload2"></i>
</div>
<p class="upload-zone-title">将文件拖到此处<span>点击选择</span></p>
<p class="upload-zone-tip">支持 .xlsx / .xls 格式</p>
</div>
</el-upload>
<div v-if="importFile" class="parse-result" :class="errorList.length > 0 ? 'parse-error' : 'parse-ok'">
<div class="parse-result-header">
<i :class="errorList.length > 0 ? 'el-icon-warning' : 'el-icon-circle-check'"></i>
<span class="parse-result-filename">{{ importFile.name }}</span>
<span class="parse-result-count">解析 {{ rawData.length }} 条数据</span>
</div>
<div v-if="errorList.length > 0" class="error-list">
<el-table :data="errorList" border max-height="150">
<el-table-column prop="rowNum" label="行号" width="80" />
<el-table-column prop="errorMsg" label="错误信息" />
</el-table>
</div>
</div>
</div>
</div>
<!-- Step 2: 校验数据 -->
<div v-show="importStep === 2" class="step-body">
<div class="step-validate">
<p class="step-desc">点击下方按钮校验数据完整性已解析 {{ rawData.length }} 条数据</p>
<el-button type="success" icon="el-icon-check" @click="handleValidateData"
:loading="validateLoading" size="medium" style="margin-bottom: 16px">
校验数据
</el-button>
<div v-if="errorList.length > 0" class="error-list">
<el-alert :title="'校验失败,共 ' + errorList.length + ' 条错误'" type="error" show-icon :closable="false" style="margin-bottom: 10px" />
<el-table :data="errorList" border max-height="200">
<el-table-column prop="rowNum" label="行号" width="80" />
<el-table-column prop="errorMsg" label="错误信息" />
</el-table>
</div>
<div v-if="isValidated && errorList.length === 0" class="validate-ok">
<el-alert title="数据校验通过!" type="success" show-icon :closable="false" />
</div>
</div>
</div>
<!-- Step 3: 预览并导入 -->
<div v-show="importStep === 3" class="step-body">
<div class="step-import-container">
<div class="data-preview">
<div class="section-title">数据预览 {{ tableData.length }} </div>
<el-table :data="tableData" border max-height="280" style="margin-top: 10px">
<el-table-column type="index" label="#" width="50" />
<el-table-column prop="detailDate" label="排产日期" width="100" />
<el-table-column prop="contractCode" label="合同号" width="120" />
<el-table-column prop="deliveryDate" label="交货期" width="100" />
<el-table-column prop="customerName" label="客户名称" width="120" />
<el-table-column prop="productName" label="产品名称" width="100" show-overflow-tooltip />
<el-table-column prop="usageReq" label="客户用途" width="100" show-overflow-tooltip />
<el-table-column prop="productMaterial" label="材质" width="80" />
<el-table-column prop="rollingThick" label="成品厚度" width="80" />
<el-table-column prop="productWidth" label="成品宽度" width="80" />
<el-table-column prop="planWeight" label="数量(吨)" width="90" />
<el-table-column prop="surfaceTreatment" label="表面处理" width="80" />
<el-table-column prop="productPackaging" label="包装要求" width="80" />
<el-table-column prop="remark" label="其他要求" width="120" show-overflow-tooltip />
</el-table>
</div>
<div v-if="importStatus === 'processing'" class="import-progress">
<el-progress :percentage="importProgress" />
<p>正在导入{{ importedCount }} / {{ totalCount }}</p>
</div>
<div v-if="importStatus === 'finished'" class="import-result">
<el-alert :title="'导入完成!共成功导入 ' + importedCount + ' 条数据'" type="success" show-icon :closable="false" />
</div>
</div>
</div>
</div>
</div>
<div slot="footer" class="dialog-footer">
<el-button v-if="importStep > 0" @click="importStep--" :disabled="importStatus === 'processing'">上一步</el-button>
<el-button @click="importDialogVisible = false">关闭</el-button>
<el-button v-if="importStep === 0" type="primary" @click="downloadTemplate">下载模板</el-button>
<el-button v-if="importStep === 0" type="primary" @click="importStep = 1">已下载下一步</el-button>
<el-button v-if="importStep === 1" type="primary" @click="goValidateStep" :disabled="!importFile || rawData.length === 0">
下一步
</el-button>
<el-button v-if="importStep === 2" type="primary" @click="goImportStep" :disabled="!isValidated || errorList.length > 0">
下一步
</el-button>
<el-button v-if="importStep === 3 && importStatus !== 'finished'" type="primary" @click="startImport"
:loading="importLoading">
开始导入
</el-button>
</div>
</el-dialog>
</div>
</template>
<script>
import { updatePlanDetail, listPlanDetail, addPlanDetail, delPlanDetail } from "@/api/aps/planDetail";
import * as XLSX from 'xlsx'
import { updatePlanDetail, listPlanDetail, addPlanDetail, addPlanDetailBatch, delPlanDetail } from "@/api/aps/planDetail";
import { getPlanSheet, listPlanSheet } from "@/api/aps/planSheet";
import { listOrder } from '@/api/crm/order';
import { listOrderItem } from '@/api/crm/orderItem'
import PlanSheetList from "@/views/aps/planSheet/PlanSheetList.vue";
const TEMPLATE_HEADERS = [
'排产日期', '合同号', '交货期', '业务员', '客户名称', '产品名称',
'客户用途', '材质', '成品厚度', '成品宽度', '数量(吨)',
'厚度范围', '宽度范围', '表面质量', '表面处理', '包装要求',
'切边要求', '其他要求'
]
const HEADER_MAP = {
'排产日期': 'detailDate',
'合同号': 'contractCode',
'交货期': 'deliveryDate',
'业务员': 'salesman',
'客户名称': 'customerName',
'产品名称': 'productName',
'客户用途': 'usageReq',
'材质': 'productMaterial',
'成品厚度': 'rollingThick',
'成品宽度': 'productWidth',
'数量(吨)': 'planWeight',
'厚度范围': 'markCoatThick',
'宽度范围': 'productEdgeReq',
'表面质量': 'coatingG',
'表面处理': 'surfaceTreatment',
'包装要求': 'productPackaging',
'切边要求': 'widthReq',
'其他要求': 'remark'
}
export default {
name: "PlanSheet",
dicts: ['sys_lines'],
@@ -625,6 +784,20 @@ export default {
selectedRows: [],
// 是否批量操作(批量转单)
isBatchTransfer: false,
// 导入对话框
importDialogVisible: false,
importStep: 0,
importFile: null,
rawData: [],
tableData: [],
errorList: [],
isValidated: false,
importProgress: 0,
importedCount: 0,
totalCount: 0,
importStatus: 'idle',
validateLoading: false,
importLoading: false,
};
},
created() {
@@ -1166,6 +1339,218 @@ export default {
this.$message.error('批量新增失败');
});
});
},
// 打开导入对话框
handleImport() {
this.importDialogVisible = true
this.resetImportState()
},
// 重置导入状态
resetImportState() {
this.importStep = 0
this.importFile = null
this.rawData = []
this.tableData = []
this.errorList = []
this.isValidated = false
this.importProgress = 0
this.importedCount = 0
this.totalCount = 0
this.importStatus = 'idle'
this.validateLoading = false
this.importLoading = false
this.$nextTick(() => {
if (this.$refs.importUpload) {
this.$refs.importUpload.clearFiles()
}
})
},
// 跳转到校验步骤
goValidateStep() {
if (!this.importFile || this.rawData.length === 0) return
this.importStep = 2
},
// 跳转到导入步骤
goImportStep() {
if (!this.isValidated || this.errorList.length > 0) return
this.importStep = 3
},
// 下载导入模板
downloadTemplate() {
const templateData = [
TEMPLATE_HEADERS,
['', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '']
]
const wb = XLSX.utils.book_new()
const ws = XLSX.utils.aoa_to_sheet(templateData)
ws['!cols'] = TEMPLATE_HEADERS.map(() => ({ wch: 14 }))
XLSX.utils.book_append_sheet(wb, ws, '排产单明细')
XLSX.writeFile(wb, '排产单明细导入模板.xlsx')
},
// 导入文件选择变化
handleImportFileChange(file) {
this.isValidated = false
this.errorList = []
this.tableData = []
this.importStatus = 'idle'
this.importFile = file.raw
this.readExcel()
},
// 读取Excel文件内容
readExcel() {
if (!this.importFile) return
try {
const fileReader = new FileReader()
fileReader.readAsArrayBuffer(this.importFile)
fileReader.onload = (e) => {
try {
const data = new Uint8Array(e.target.result)
const workbook = XLSX.read(data, { type: 'array' })
const sheetName = workbook.SheetNames[0]
const worksheet = workbook.Sheets[sheetName]
const jsonData = XLSX.utils.sheet_to_json(worksheet, { header: 1 })
this.validateHeaders(jsonData[0])
this.rawData = jsonData.slice(1).filter(row => row.some(cell => cell !== undefined && cell !== null && cell !== ''))
this.formatExcel()
if (this.errorList.length === 0) {
this.importStatus = 'parsed'
this.$message.success(`成功解析Excel共读取到 ${this.rawData.length} 条数据`)
}
} catch (error) {
this.errorList = [{ rowNum: 0, errorMsg: '解析Excel失败' + error.message }]
}
}
} catch (error) {
this.errorList = [{ rowNum: 0, errorMsg: '读取文件失败:' + error.message }]
}
},
// 校验表头
validateHeaders(headers) {
if (headers.length !== TEMPLATE_HEADERS.length) {
this.errorList.push({
rowNum: 1,
errorMsg: `表头列数不匹配,要求${TEMPLATE_HEADERS.length}列,实际${headers.length}`
})
return
}
headers.forEach((header, index) => {
if (String(header || '').trim() !== TEMPLATE_HEADERS[index]) {
this.errorList.push({
rowNum: 1,
errorMsg: `${index + 1}列表头错误,要求:"${TEMPLATE_HEADERS[index]}",实际:"${header}"`
})
}
})
if (this.errorList.length > 0) {
this.$message.error('Excel表头格式不符合要求请检查')
}
},
// 格式化Excel数据
formatExcel() {
this.tableData = []
if (this.rawData.length === 0) return
const currentMaxSeqNo = this.planDetailList.length > 0
? Math.max(...this.planDetailList.map(item => parseInt(item.bizSeqNo) || 0))
: 0
this.rawData.forEach((row, index) => {
const rowObj = { bizSeqNo: currentMaxSeqNo + index + 1 }
TEMPLATE_HEADERS.forEach((header, colIndex) => {
const field = HEADER_MAP[header]
const cellValue = row[colIndex]
if (cellValue !== undefined && cellValue !== null && cellValue !== '') {
rowObj[field] = String(cellValue).trim()
}
})
this.tableData.push(rowObj)
})
},
// 校验数据
handleValidateData() {
if (this.validateLoading) return
if (this.rawData.length === 0) {
this.$message.warning('暂无数据可校验')
return
}
this.validateLoading = true
this.errorList = []
try {
this.tableData.forEach((row, i) => {
const rowNum = i + 2
if (!row.contractCode || row.contractCode.trim() === '') {
this.errorList.push({ rowNum, errorMsg: '合同号不能为空' })
}
if (!row.customerName || row.customerName.trim() === '') {
this.errorList.push({ rowNum, errorMsg: '客户名称不能为空' })
}
if (!row.productName || row.productName.trim() === '') {
this.errorList.push({ rowNum, errorMsg: '产品名称不能为空' })
}
if (!row.productMaterial || row.productMaterial.trim() === '') {
this.errorList.push({ rowNum, errorMsg: '材质不能为空' })
}
if (row.planWeight && isNaN(Number(row.planWeight))) {
this.errorList.push({ rowNum, errorMsg: '数量(吨)必须是数字' })
}
})
this.isValidated = true
if (this.errorList.length > 0) {
this.$message.error(`数据校验失败,共 ${this.errorList.length} 条错误`)
} else {
this.$message.success('数据校验通过,可以开始导入')
this.importStep = 3
}
} catch (error) {
this.errorList.push({ rowNum: 0, errorMsg: '校验数据时发生错误:' + error.message })
} finally {
this.validateLoading = false
}
},
// 开始导入
startImport() {
if (this.importLoading) return
if (!this.isValidated) {
this.$message.warning('请先校验数据')
return
}
if (this.errorList.length > 0) {
this.$message.warning('请先修正校验错误后再导入')
return
}
if (this.tableData.length === 0) {
this.$message.warning('暂无数据可导入')
return
}
this.$confirm(`确认导入 ${this.tableData.length} 条排产单明细?`, '导入确认', {
confirmButtonText: '确认导入',
cancelButtonText: '取消',
type: 'warning'
}).then(() => {
this.doImport()
}).catch(() => {})
},
// 执行导入
doImport() {
this.importLoading = true
this.importStatus = 'processing'
this.totalCount = this.tableData.length
this.importedCount = 0
this.importProgress = 0
const boList = this.tableData.map(row => ({
...row,
planSheetId: this.currentPlanSheetId
}))
addPlanDetailBatch(boList).then(() => {
this.importedCount = this.totalCount
this.importProgress = 100
this.importStatus = 'finished'
this.$message.success(`导入完成!共成功导入 ${this.importedCount} 条数据`)
this.getList()
}).catch(error => {
this.importStatus = 'idle'
this.$message.error('导入失败:' + (error.message || '未知错误'))
}).finally(() => {
this.importLoading = false
})
}
}
};
@@ -1259,4 +1644,211 @@ export default {
margin-bottom: 15px;
color: #303133;
}
.import-container {
padding: 10px 0;
}
.import-step-content {
margin-top: 24px;
min-height: 320px;
}
.step-body {
padding: 20px 0;
}
.step-download {
text-align: center;
padding: 40px 0;
}
.step-download .step-icon {
font-size: 48px;
color: #409eff;
margin-bottom: 16px;
}
.step-download p {
font-size: 14px;
color: #606266;
margin-bottom: 20px;
}
.step-upload {
display: flex;
flex-direction: column;
align-items: center;
}
.custom-upload {
width: 100%;
}
.custom-upload ::v-deep .el-upload {
width: 100%;
}
.custom-upload ::v-deep .el-upload-dragger {
width: 100%;
height: auto;
padding: 40px 20px;
border: 2px dashed #dcdfe6;
border-radius: 8px;
background: #fafbfc;
transition: all .3s ease;
display: flex;
align-items: center;
justify-content: center;
}
.custom-upload ::v-deep .el-upload-dragger:hover {
border-color: #409eff;
background: #ecf5ff;
}
.custom-upload ::v-deep .el-upload-dragger.is-dragover {
border-color: #409eff;
background: #ecf5ff;
box-shadow: 0 0 0 3px rgba(64, 158, 255, 0.15);
}
.upload-zone-body {
display: flex;
flex-direction: column;
align-items: center;
gap: 8px;
}
.upload-zone-icon {
width: 64px;
height: 64px;
border-radius: 50%;
background: linear-gradient(135deg, #ecf5ff 0%, #d9ecff 100%);
display: flex;
align-items: center;
justify-content: center;
margin-bottom: 8px;
}
.upload-zone-icon i {
font-size: 28px;
color: #409eff;
}
.upload-zone-title {
font-size: 15px;
color: #606266;
margin: 0;
}
.upload-zone-title span {
color: #409eff;
cursor: pointer;
}
.upload-zone-tip {
font-size: 12px;
color: #c0c4cc;
margin: 0;
}
.parse-result {
width: 100%;
margin-top: 16px;
border-radius: 8px;
overflow: hidden;
}
.parse-result-header {
display: flex;
align-items: center;
gap: 10px;
padding: 12px 16px;
font-size: 13px;
}
.parse-result-header i {
font-size: 18px;
}
.parse-result-filename {
font-weight: 500;
flex: 1;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.parse-result-count {
white-space: nowrap;
font-weight: 500;
}
.parse-result.parse-ok {
border: 1px solid #b7eb8f;
}
.parse-result.parse-ok .parse-result-header {
background: #f6ffed;
color: #52c41a;
}
.parse-result.parse-ok i {
color: #52c41a;
}
.parse-result.parse-error {
border: 1px solid #ffa39e;
}
.parse-result.parse-error .parse-result-header {
background: #fff1f0;
color: #f5222d;
}
.parse-result.parse-error i {
color: #f5222d;
}
.step-validate {
padding: 0 40px;
text-align: center;
}
.step-validate .step-desc {
font-size: 14px;
color: #606266;
margin-bottom: 16px;
}
.validate-ok {
margin-top: 16px;
}
.step-import-container {
padding: 0;
}
.error-list {
margin-top: 16px;
}
.data-preview {
margin-top: 0;
}
.import-progress {
margin-top: 20px;
text-align: center;
}
.import-progress p {
margin-top: 8px;
color: #606266;
}
.import-result {
margin-top: 20px;
}
</style>

View File

@@ -63,7 +63,6 @@
<el-button type="primary" size="mini" @click="showAddDetail=true">+ 明细列</el-button>
<el-button size="mini" type="success" @click="openMetricPicker">+ 指标列</el-button>
<el-button size="mini" plain @click="openMetricMgr">指标管理</el-button>
<el-button size="mini" plain @click="openCopyCfg">复制配置</el-button>
</div>
</div>
<el-table :data="allCols" border stripe size="mini" highlight-current-row @dragover.native.prevent @drop.native="onNativeDrop" @current-change="curIdx = allCols.indexOf($event)" @selection-change="selCol=$event">

View File

@@ -2,12 +2,12 @@
<div class="contract-tabs">
<div v-if="orderId" class="tabs-content">
<el-tabs v-model="activeTab" type="border-card">
<el-tab-pane label="订单编辑" name="edit" v-hasPermi="['crm:order:edit']">
<!-- <el-tab-pane label="订单编辑" name="edit" v-hasPermi="['crm:order:edit']">
<div class="order-detail" v-if="activeTab === 'edit'">
<el-descriptions title="订单明细" />
<OrderDetail :orderId="currentOrder.orderId" />
</div>
</el-tab-pane>
</el-tab-pane> -->
<el-tab-pane label="财务状态" name="finance" v-hasPermi="['crm:order:finance']">
<div class="order-finance" v-if="activeTab === 'finance'">
<!-- 财务状态内容 -->

View File

@@ -0,0 +1,251 @@
<template>
<div class="app-container">
<el-form :model="queryParams" ref="queryForm" size="small" :inline="true" v-show="showSearch" label-width="68px">
<el-form-item label="产线">
<el-select v-model="queryParams.productionLine" placeholder="请选择产线" clearable @change="handleQuery" style="width: 150px;">
<el-option v-for="item in lineList" :key="item.lineId" :label="item.lineName" :value="item.lineId" />
</el-select>
</el-form-item>
<el-form-item label="审批状态" prop="approvalStatus">
<el-select v-model="queryParams.approvalStatus" placeholder="请选择状态" clearable @change="handleQuery" style="width: 120px;">
<el-option label="待审批" :value="1" />
<el-option label="已通过" :value="2" />
<el-option label="已驳回" :value="3" />
<el-option label="已撤销" :value="4" />
</el-select>
</el-form-item>
<el-form-item>
<el-button type="primary" icon="el-icon-search" size="mini" @click="handleQuery">搜索</el-button>
<el-button icon="el-icon-refresh" size="mini" @click="resetQuery">重置</el-button>
</el-form-item>
</el-form>
<el-row :gutter="10" class="mb8" v-if="!readonly">
<el-col :span="1.5">
<el-button
type="danger"
plain
icon="el-icon-delete"
size="mini"
:disabled="multiple"
@click="handleDelete"
>删除</el-button>
</el-col>
<right-toolbar :showSearch.sync="showSearch" @queryTable="getList"></right-toolbar>
</el-row>
<el-table v-loading="loading" :data="approvalList" @selection-change="handleSelectionChange">
<el-table-column type="selection" width="55" align="center" v-if="!readonly" />
<el-table-column label="产线" align="center" prop="productionLine" width="100">
<template slot-scope="scope">
{{ getLineName(scope.row.productionLine) }}
</template>
</el-table-column>
<el-table-column label="巡检开始时间" align="center" prop="insStartTime" width="180">
<template slot-scope="scope">
<span>{{ parseTime(scope.row.insStartTime, '{y}-{m}-{d} {h}:{i}:{s}') }}</span>
</template>
</el-table-column>
<el-table-column label="巡检结束时间" align="center" prop="insEndTime" width="180">
<template slot-scope="scope">
<span>{{ parseTime(scope.row.insEndTime, '{y}-{m}-{d} {h}:{i}:{s}') }}</span>
</template>
</el-table-column>
<el-table-column label="申请人" align="center" prop="applyUser" width="100" />
<el-table-column label="申请时间" align="center" prop="applyTime" width="180">
<template slot-scope="scope">
<span>{{ parseTime(scope.row.applyTime, '{y}-{m}-{d} {h}:{i}:{s}') }}</span>
</template>
</el-table-column>
<el-table-column label="审批状态" align="center" prop="approvalStatus" width="90">
<template slot-scope="scope">
<el-tag v-if="scope.row.approvalStatus === 1" type="warning">待审批</el-tag>
<el-tag v-else-if="scope.row.approvalStatus === 2" type="success">已通过</el-tag>
<el-tag v-else-if="scope.row.approvalStatus === 3" type="danger">已驳回</el-tag>
<el-tag v-else-if="scope.row.approvalStatus === 4" type="info">已撤销</el-tag>
</template>
</el-table-column>
<el-table-column label="审批人" align="center" prop="approvalUser" width="100" />
<el-table-column label="审批时间" align="center" prop="approvalTime" width="180">
<template slot-scope="scope">
<span>{{ parseTime(scope.row.approvalTime, '{y}-{m}-{d} {h}:{i}:{s}') }}</span>
</template>
</el-table-column>
<el-table-column label="审批意见" align="center" prop="approvalOpinion" show-overflow-tooltip />
<el-table-column label="备注" align="center" prop="remark" show-overflow-tooltip />
<el-table-column label="操作" align="center" width="280" fixed="right">
<template slot-scope="scope">
<el-button size="mini" type="text" icon="el-icon-view" @click="handleDetail(scope.row)">详情</el-button>
<el-button v-if="scope.row.approvalStatus === 1" size="mini" type="text" icon="el-icon-check" @click="handleApprove(scope.row)">通过</el-button>
<el-button v-if="scope.row.approvalStatus === 1" size="mini" type="text" icon="el-icon-close" @click="handleReject(scope.row)">驳回</el-button>
<el-button v-if="scope.row.approvalStatus === 1 || scope.row.approvalStatus === 4" size="mini" type="text" icon="el-icon-delete" @click="handleDelete(scope.row)">删除</el-button>
</template>
</el-table-column>
</el-table>
<pagination
v-show="total>0"
:total="total"
:page.sync="queryParams.pageNum"
:limit.sync="queryParams.pageSize"
@pagination="getList"
/>
<el-dialog :title="dialogTitle" :visible.sync="dialogVisible" width="500px" append-to-body>
<el-form ref="dialogForm" :model="dialogForm" :rules="dialogRules" label-width="80px">
<el-form-item label="审批意见" prop="approvalOpinion">
<el-input v-model="dialogForm.approvalOpinion" type="textarea" rows="3" placeholder="请输入审批意见" />
</el-form-item>
</el-form>
<div slot="footer" class="dialog-footer">
<el-button :loading="buttonLoading" type="primary" @click="submitApproval"> </el-button>
<el-button @click="dialogVisible = false"> </el-button>
</div>
</el-dialog>
</div>
</template>
<script>
import { listEquipmentInspectionApproval, delEquipmentInspectionApproval, updateEquipmentInspectionApproval } from "@/api/mes/eqp/equipmentInspectionApproval";
import { listProductionLine } from "@/api/wms/productionLine";
export default {
name: "EquipmentInspectionApproval",
props: {
readonly: {
type: Boolean,
default: false,
},
defaultStatus: {
type: Number,
default: undefined,
},
},
data() {
return {
buttonLoading: false,
loading: true,
ids: [],
single: true,
multiple: true,
showSearch: true,
total: 0,
approvalList: [],
dialogTitle: "",
dialogVisible: false,
approvalAction: "",
currentRow: null,
lineList: [],
queryParams: {
pageNum: 1,
pageSize: 10,
productionLine: undefined,
approvalStatus: this.defaultStatus,
},
dialogForm: {
approvalOpinion: "",
},
dialogRules: {},
};
},
created() {
this.loadLineList();
},
methods: {
async loadLineList() {
try {
const res = await listProductionLine({ pageSize: 999 });
if (res.rows) this.lineList = res.rows;
this.getList();
} catch (e) { console.error('加载产线列表失败', e); }
},
getLineName(lineId) {
const found = this.lineList.find(l => l.lineId === lineId);
return found ? found.lineName : '';
},
getList() {
this.loading = true;
listEquipmentInspectionApproval(this.queryParams).then(response => {
this.approvalList = response.rows;
this.total = response.total;
this.loading = false;
});
},
handleQuery() {
this.queryParams.pageNum = 1;
this.getList();
},
resetQuery() {
this.resetForm("queryForm");
this.handleQuery();
},
handleSelectionChange(selection) {
this.ids = selection.map(item => item.approvalId);
this.single = selection.length !== 1;
this.multiple = !selection.length;
},
formatQueryDate(date) {
if (!date) return '';
const d = new Date(date);
return `${d.getFullYear()}-${String(d.getMonth() + 1).padStart(2, "0")}-${String(d.getDate()).padStart(2, "0")}`;
},
handleDetail(row) {
this.$router.push({
path: '/eqp/day',
query: {
productionLine: row.productionLine,
startDate: this.formatQueryDate(row.insStartTime),
endDate: this.formatQueryDate(row.insEndTime),
}
});
},
handleApprove(row) {
this.currentRow = row;
this.approvalAction = 'approve';
this.dialogTitle = "审批通过";
this.dialogForm.approvalOpinion = "";
this.dialogVisible = true;
},
handleReject(row) {
this.currentRow = row;
this.approvalAction = 'reject';
this.dialogTitle = "审批驳回";
this.dialogForm.approvalOpinion = "";
this.dialogVisible = true;
},
submitApproval() {
this.$refs["dialogForm"].validate(valid => {
if (valid) {
this.buttonLoading = true;
const status = this.approvalAction === 'approve' ? 2 : 3;
updateEquipmentInspectionApproval({
approvalId: this.currentRow.approvalId,
approvalStatus: status,
approvalOpinion: this.dialogForm.approvalOpinion,
}).then(() => {
const msg = this.approvalAction === 'approve' ? '审批通过' : '已驳回';
this.$modal.msgSuccess(msg);
this.dialogVisible = false;
this.getList();
}).finally(() => {
this.buttonLoading = false;
});
}
});
},
handleDelete(row) {
const approvalIds = row.approvalId || this.ids;
this.$modal.confirm('是否确认删除审批记录编号为"' + approvalIds + '"的数据项?').then(() => {
this.loading = true;
return delEquipmentInspectionApproval(approvalIds);
}).then(() => {
this.loading = false;
this.getList();
this.$modal.msgSuccess("删除成功");
}).catch(() => {
this.loading = false;
});
},
},
};
</script>

View File

@@ -8,12 +8,16 @@
@change="handleQuery" style="width: 260px;" />
</el-form-item>
<el-form-item label="产线">
<dict-select v-model="productionLine" dict-type="sys_lines" placeholder="请选择产线"
clearable @change="handleQuery" style="width: 150px;" />
<el-select v-model="productionLine" placeholder="请选择产线" clearable @change="handleQuery" style="width: 150px;">
<el-option v-for="item in lineList" :key="item.lineId" :label="item.lineName" :value="item.lineId" />
</el-select>
</el-form-item>
<el-form-item>
<el-button type="primary" @click="handleQuery">查询</el-button>
</el-form-item>
<el-form-item v-hasPermi="['mes:eqp:submit']">
<el-button type="success" icon="el-icon-s-promotion" @click="handleSubmitForApproval">送检</el-button>
</el-form-item>
</el-form>
</el-row>
@@ -105,17 +109,26 @@
<script>
import { listEquipmentPart } from "@/api/mes/eqp/equipmentPart";
import { listEquipmentInspectionRecord } from "@/api/mes/eqp/equipmentInspectionRecord";
import { listProductionLine } from "@/api/wms/productionLine";
import { addEquipmentInspectionApproval } from "@/api/mes/eqp/equipmentInspectionApproval";
export default {
name: "DailyInspectionReport",
dicts: ['sys_lines'],
data() {
const d = new Date();
const today = `${d.getFullYear()}-${String(d.getMonth() + 1).padStart(2, "0")}-${String(d.getDate()).padStart(2, "0")}`;
const routeQuery = this.$route && this.$route.query || {};
const hasRouteQuery = !!(routeQuery.productionLine || (routeQuery.startDate && routeQuery.endDate));
return {
loading: false,
dateRange: [this.getToday(), this.getToday()],
productionLine: '酸轧线',
dateRange: (routeQuery.startDate && routeQuery.endDate)
? [routeQuery.startDate, routeQuery.endDate]
: [today, today],
productionLine: routeQuery.productionLine ? Number(routeQuery.productionLine) : 2,
lineList: [],
partList: [],
records: [],
hasRouteQuery,
};
},
computed: {
@@ -125,6 +138,10 @@ export default {
partName: cl.partName || p.inspectPart,
})));
},
selectedLineName() {
const found = this.lineList.find(l => l.lineId === this.productionLine);
return found ? found.lineName : '';
},
tableData() {
const recordMap = {};
this.records.forEach(r => {
@@ -227,13 +244,14 @@ export default {
this.loading = true;
try {
const partParams = {};
if (this.productionLine) partParams.productionLine = this.productionLine;
const productionLine = this.productionLine;
if (productionLine) partParams.productionLine = productionLine;
const recordParams = {
startInspectTime: this.dateRange[0] + ' 00:00:00',
endInspectTime: this.dateRange[1] + ' 23:59:59',
pageSize: 9999,
};
if (this.productionLine) recordParams.productionLine = this.productionLine;
if (productionLine) recordParams.productionLine = productionLine;
const [partRes, recordRes] = await Promise.all([
listEquipmentPart(partParams),
listEquipmentInspectionRecord(recordParams),
@@ -246,9 +264,43 @@ export default {
this.loading = false;
}
},
handleSubmitForApproval() {
if (!this.dateRange || this.dateRange.length !== 2) {
this.$modal.msgWarning("请选择时间段");
return;
}
if (!this.productionLine) {
this.$modal.msgWarning("请选择产线");
return;
}
this.$modal.confirm('确认将该时间段"' + this.dateRange[0] + '至' + this.dateRange[1] + '"的巡检日报提交审批吗?').then(() => {
this.loading = true;
return addEquipmentInspectionApproval({
productionLine: this.productionLine,
insStartTime: this.dateRange[0] + ' 00:00:00',
insEndTime: this.dateRange[1] + ' 23:59:59',
});
}).then(() => {
this.$modal.msgSuccess("送检成功");
this.loading = false;
}).catch(() => {
this.loading = false;
});
},
async loadLineList() {
try {
const res = await listProductionLine({ pageSize: 999 });
if (res.rows) this.lineList = res.rows;
if (!this.hasRouteQuery && this.lineList.length > 0) {
const suanYa = this.lineList.find(l => l.lineName === '酸轧线');
this.productionLine = suanYa ? suanYa.lineId : this.lineList[0].lineId;
}
this.handleQuery();
} catch (e) { console.error('加载产线列表失败', e); }
},
},
mounted() {
this.handleQuery();
this.loadLineList();
},
};
</script>

View File

@@ -1,7 +1,8 @@
<template>
<div class="app-container">
<dict-select v-model="sharedQueryParams.productionLine" dict-type="sys_lines" placeholder="请选择产线" renderType="radio"
clearable kisv @change="handleQuery" />
<el-radio-group v-model="sharedQueryParams.productionLine" @change="handleQuery" size="small" style="margin-bottom: 10px;">
<el-radio-button v-for="item in lineList" :key="item.lineId" :label="item.lineId">{{ item.lineName }}</el-radio-button>
</el-radio-group>
<DragResizePanel direction="horizontal" :initialSize="500" :minSize="350"
style="height: calc(100vh - 164px); margin-top: 10px;">
@@ -132,7 +133,9 @@
<el-dialog :title="partTitle" :visible.sync="partOpen" width="500px" append-to-body>
<el-form ref="partForm" :model="partForm" :rules="partRules" label-width="80px">
<el-form-item label="产线" prop="productionLine">
<dict-select v-model="partForm.productionLine" dict-type="sys_lines" kisv placeholder="请选择产线" />
<el-select v-model="partForm.lineId" placeholder="请选择产线" style="width: 100%;">
<el-option v-for="item in lineList" :key="item.lineId" :label="item.lineName" :value="item.lineId" />
</el-select>
</el-form-item>
<el-form-item label="产线段" prop="lineSection">
<el-input v-model="partForm.lineSection" placeholder="请输入产线段" />
@@ -206,20 +209,21 @@ import html2canvas from 'html2canvas';
import { PDFDocument } from 'pdf-lib';
import { listEquipmentPart, getEquipmentPart, delEquipmentPart, addEquipmentPart, updateEquipmentPart } from "@/api/mes/eqp/equipmentPart";
import { listEquipmentChecklist, getEquipmentChecklist, delEquipmentChecklist, addEquipmentChecklist, updateEquipmentChecklist } from "@/api/mes/eqp/equipmentChecklist";
import { listProductionLine } from '@/api/wms/productionLine'
export default {
name: "EquipmentPartChecklist",
components: { DragResizePanel, QRCode },
dicts: ['sys_lines'],
data() {
return {
showSearch: true,
// Shared query params (productionLine & lineSection)
sharedQueryParams: {
productionLine: '酸轧线',
lineId: null,
lineSection: undefined
},
lineList: [],
// Part (left side)
partLoading: false,
@@ -285,13 +289,28 @@ export default {
computed: {
currentPartId() {
return this.currentPart?.partId || "";
},
selectedLineName() {
const found = this.lineList.find(l => l.lineId === this.sharedQueryParams.lineId);
return found ? found.lineName : '';
}
},
created() {
this.getPartList();
this.getChecklistList();
this.loadLineList();
},
methods: {
async loadLineList() {
try {
const res = await listProductionLine({ pageSize: 999 });
if (res.rows) this.lineList = res.rows;
if (this.lineList.length > 0) {
const suanYa = this.lineList.find(l => l.lineName === '酸轧线');
this.sharedQueryParams.productionLine = suanYa ? suanYa.lineId : this.lineList[0].lineId;
}
this.getPartList();
this.getChecklistList();
} catch (e) { console.error('加载产线列表失败', e); }
},
// ========== Shared ==========
applySharedParams() {
this.partQueryParams.productionLine = this.sharedQueryParams.productionLine;
@@ -357,6 +376,7 @@ export default {
partId: undefined,
inspectPart: undefined,
remark: undefined,
lineId: undefined,
productionLine: undefined,
lineSection: undefined
};
@@ -368,8 +388,8 @@ export default {
},
handlePartAdd() {
this.resetPartForm();
if (this.sharedQueryParams.productionLine) {
this.partForm.productionLine = this.sharedQueryParams.productionLine;
if (this.sharedQueryParams.lineId) {
this.partForm.lineId = this.sharedQueryParams.lineId;
}
if (this.sharedQueryParams.lineSection) {
this.partForm.lineSection = this.sharedQueryParams.lineSection;
@@ -384,6 +404,10 @@ export default {
getEquipmentPart(partId).then(response => {
this.partLoading = false;
this.partForm = response.data;
if (this.partForm.productionLine && this.lineList.length) {
const line = this.lineList.find(l => l.lineName === this.partForm.productionLine);
if (line) this.$set(this.partForm, 'lineId', line.lineId);
}
this.partOpen = true;
this.partTitle = "修改检验部位";
});
@@ -392,6 +416,10 @@ export default {
this.$refs["partForm"].validate(valid => {
if (valid) {
this.partButtonLoading = true;
if (this.partForm.lineId) {
const line = this.lineList.find(l => l.lineId === this.partForm.lineId);
if (line) this.partForm.productionLine = line.lineName;
}
if (this.partForm.partId != null) {
updateEquipmentPart(this.partForm).then(response => {
this.$modal.msgSuccess("修改成功");

View File

@@ -1,14 +1,11 @@
<template>
<div class="app-container">
<el-form :model="queryParams" ref="queryForm" size="small" :inline="true" v-show="showSearch" label-width="68px">
<!-- <el-form-item label="检验清单ID" prop="checkId">
<el-input
v-model="queryParams.checkId"
placeholder="请输入检验清单ID"
clearable
@keyup.enter.native="handleQuery"
/>
</el-form-item> -->
<el-form-item label="产线">
<el-select v-model="queryParams.productionLine" placeholder="请选择产线" clearable @change="handleQuery" style="width: 150px;">
<el-option v-for="item in lineList" :key="item.lineId" :label="item.lineName" :value="item.lineId" />
</el-select>
</el-form-item>
<el-form-item label="班次" prop="shift">
<el-select v-model="queryParams.shift" placeholder="请选择班次" clearable
@change="handleQuery" style="width: 150px;">
@@ -198,6 +195,7 @@
<script>
import { listEquipmentInspectionRecord, getEquipmentInspectionRecord, delEquipmentInspectionRecord, addEquipmentInspectionRecord, updateEquipmentInspectionRecord } from "@/api/mes/eqp/equipmentInspectionRecord";
import { listProductionLine } from "@/api/wms/productionLine";
import ChecklistSelect from "@/components/ChecklistSelect";
export default {
@@ -227,6 +225,7 @@ export default {
open: false,
// 时间段筛选
dateRange: undefined,
lineList: [],
// 查询参数
queryParams: {
pageNum: 1,
@@ -238,6 +237,7 @@ export default {
runStatus: undefined,
inspector: undefined,
abnormalDesc: undefined,
productionLine: 2,
},
// 表单参数
form: {},
@@ -247,9 +247,16 @@ export default {
};
},
created() {
this.getList();
this.loadLineList();
},
methods: {
async loadLineList() {
try {
const res = await listProductionLine({ pageSize: 999 });
if (res.rows) this.lineList = res.rows;
this.getList();
} catch (e) { console.error('加载产线列表失败', e); }
},
/** 查询设备巡检记录列表 */
getList() {
this.loading = true;

View File

@@ -13,6 +13,47 @@
</div>
</div>
<!-- 最近排产单 -->
<div class="plan-sheet-container">
<div v-if="planSheetList.length > 0" class="plan-sheet-section">
<el-descriptions :column="1" border :title="'最近排产单(' + planSheetLineName + ''" size="small" />
<el-tabs v-model="activePlanSheetId" type="card" size="mini">
<el-tab-pane v-for="sheet in planSheetList" :key="sheet.planSheetId" :name="sheet.planSheetId"
:label="sheet.planCode || ('排产单' + sheet.planSheetId)" />
</el-tabs>
<el-table v-loading="planSheetLoading" :data="activePlanSheetDetails" border stripe size="mini"
max-height="300">
<el-table-column type="index" label="序号" width="50" />
<el-table-column label="订单信息" header-align="center">
<el-table-column prop="contractCode" label="合同号" width="140" show-overflow-tooltip />
<el-table-column prop="customerName" label="客户" width="120" show-overflow-tooltip />
<el-table-column prop="salesman" label="业务员" width="100" show-overflow-tooltip />
</el-table-column>
<el-table-column label="成品信息" header-align="center">
<el-table-column prop="productName" label="成品名称" width="140" show-overflow-tooltip />
<el-table-column prop="productMaterial" label="材质" width="100" />
<el-table-column prop="coatingG" label="镀层g" width="80" />
<el-table-column prop="productWidth" label="成品宽度" width="100" />
<el-table-column prop="rollingThick" label="轧制厚度" width="100" />
<el-table-column prop="markCoatThick" label="标丝厚度" width="100" />
<el-table-column prop="tonSteelLengthRange" label="长度区间(m)" width="120" />
<el-table-column prop="planQty" label="数量" width="80" />
<el-table-column prop="planWeight" label="重量" width="100" />
<el-table-column prop="surfaceTreatment" label="表面处理" width="110" show-overflow-tooltip />
<el-table-column prop="widthReq" label="切边要求" width="110" show-overflow-tooltip />
<el-table-column prop="productPackaging" label="包装要求" width="100" show-overflow-tooltip />
<el-table-column prop="productEdgeReq" label="宽度要求" width="100" show-overflow-tooltip />
<el-table-column prop="usageReq" label="用途" width="100" show-overflow-tooltip />
</el-table-column>
<el-table-column prop="remark" label="备注" width="140" show-overflow-tooltip />
</el-table>
</div>
<div v-else-if="planSheetLineName && !planSheetLoading" class="plan-sheet-section">
<el-descriptions :column="1" border :title="'最近排产单(' + planSheetLineName + ''" size="small" />
<el-empty description="今天暂无排产单" :image-size="60" />
</div>
</div>
<!-- 流程图区域 -->
<div class="flow-container">
<!-- 左侧源卷列表 -->
@@ -90,39 +131,6 @@
</div>
</div>
<!-- 今日排产单 -->
<div v-if="planSheetList.length > 0" class="plan-sheet-section">
<el-descriptions :column="1" border :title="'最近排产单(' + planSheetLineName + ''" size="small" />
<el-tabs v-model="activePlanSheetId" type="card" size="mini">
<el-tab-pane v-for="sheet in planSheetList" :key="sheet.planSheetId"
:name="sheet.planSheetId"
:label="sheet.planCode || ('排产单' + sheet.planSheetId)" />
</el-tabs>
<el-table v-loading="planSheetLoading" :data="activePlanSheetDetails" border stripe size="mini" max-height="300">
<el-table-column type="index" label="#" width="40" />
<el-table-column prop="bizSeqNo" label="序号" width="55" />
<el-table-column label="订单信息">
<el-table-column prop="orderCode" label="订单号" width="130" show-overflow-tooltip />
<el-table-column prop="contractCode" label="合同号" width="130" show-overflow-tooltip />
<el-table-column prop="customerName" label="客户" width="100" show-overflow-tooltip />
<el-table-column prop="salesman" label="业务员" width="80" show-overflow-tooltip />
</el-table-column>
<el-table-column label="成品信息">
<el-table-column prop="productName" label="成品名称" width="130" show-overflow-tooltip />
<el-table-column prop="productMaterial" label="材质" width="80" />
<el-table-column prop="coatingG" label="镀层g" width="70" />
<el-table-column prop="productWidth" label="宽度" width="80" />
<el-table-column prop="rollingThick" label="轧厚" width="80" />
<el-table-column prop="planQty" label="数量" width="65" />
<el-table-column prop="planWeight" label="重量" width="80" />
</el-table-column>
<el-table-column prop="remark" label="备注" width="100" show-overflow-tooltip />
</el-table>
</div>
<div v-else-if="planSheetLineName && !planSheetLoading" class="plan-sheet-section">
<el-descriptions :column="1" border :title="'最近排产单(' + planSheetLineName + ''" size="small" />
<el-empty description="今天暂无排产单" :image-size="60" />
</div>
</div>
<!-- 右侧目标卷信息 -->
@@ -1031,7 +1039,7 @@ export default {
<style scoped lang="scss">
.merge-coil-container {
padding: 20px;
padding: 12px;
background: #f5f7fa;
min-height: calc(100vh - 84px);
}
@@ -1042,8 +1050,8 @@ export default {
justify-content: space-between;
align-items: center;
background: #fff;
padding: 16px 20px;
margin-bottom: 20px;
padding: 10px 16px;
margin-bottom: 12px;
border-radius: 4px;
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
}
@@ -1065,12 +1073,12 @@ export default {
/* 流程图容器 */
.flow-container {
display: flex;
gap: 20px;
gap: 12px;
background: #fff;
padding: 20px;
padding: 12px;
border-radius: 4px;
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
min-height: 600px;
min-height: 500px;
}
.flow-left {
@@ -1096,8 +1104,8 @@ export default {
font-size: 16px;
font-weight: 500;
color: #303133;
margin-bottom: 16px;
padding-left: 12px;
margin-bottom: 10px;
padding-left: 10px;
border-left: 4px solid #0066cc;
display: flex;
justify-content: space-between;
@@ -1106,10 +1114,10 @@ export default {
/* 源卷列表 */
.source-list {
max-height: 500px;
max-height: calc(100vh - 280px);
overflow-y: auto;
padding-right: 10px;
margin-bottom: 20px;
margin-bottom: 0;
&::-webkit-scrollbar {
width: 6px;
@@ -1125,7 +1133,7 @@ export default {
background: #fff;
border: 1px solid #e4e7ed;
border-radius: 8px;
margin-bottom: 10px;
margin-bottom: 8px;
transition: all 0.3s;
overflow: hidden;
@@ -1136,7 +1144,7 @@ export default {
}
.source-coil-header {
padding: 10px 14px;
padding: 8px 12px;
display: flex;
align-items: center;
gap: 10px;
@@ -1144,8 +1152,8 @@ export default {
}
.source-number {
width: 24px;
height: 24px;
width: 22px;
height: 22px;
border-radius: 50%;
background: #0066cc;
color: #fff;
@@ -1172,7 +1180,7 @@ export default {
}
.source-coil-body {
padding: 10px 14px;
padding: 8px 12px;
&:empty {
text-align: center;
@@ -1259,7 +1267,7 @@ export default {
.detail-label {
color: #909399;
min-width: 80px;
min-width: 72px;
flex-shrink: 0;
}
@@ -1339,29 +1347,29 @@ export default {
border-radius: 8px;
overflow: hidden;
box-shadow: 0 4px 12px rgba(0, 102, 204, 0.15);
margin-bottom: 20px;
margin-bottom: 12px;
}
.target-coil-header {
background: #0066cc;
color: #fff;
padding: 16px 20px;
padding: 10px 14px;
display: flex;
align-items: center;
gap: 10px;
font-size: 16px;
font-size: 14px;
font-weight: 600;
}
.target-coil-body {
padding: 20px;
padding: 12px;
}
/* 双列表单布局 */
.form-row {
display: flex;
gap: 16px;
margin-bottom: 12px;
gap: 10px;
margin-bottom: 10px;
&:last-child {
margin-bottom: 0;
@@ -1527,7 +1535,26 @@ export default {
}
}
/* 排产单区域 */
.plan-sheet-container {
background: #fff;
border-radius: 4px;
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
padding: 12px;
margin-bottom: 12px;
}
.plan-sheet-section {
margin-top: 16px;
margin-top: 0;
}
// 减少表单项间距
::v-deep .el-form-item {
margin-bottom: 10px;
}
::v-deep .el-form-item--small .el-form-item__content,
::v-deep .el-form-item--small .el-form-item__label {
line-height: 32px;
}
</style>

View File

@@ -1,6 +1,12 @@
<template>
<div class="app-container">
<el-form :model="queryParams" ref="queryForm" size="small" :inline="true" v-show="showSearch" label-width="80px">
<el-form-item label="发货计划" prop="planId" v-if="showWaybill">
<el-select v-model="queryParams.planId" placeholder="请输入发货计划名称搜索" filterable remote clearable
:remote-method="remoteSearchWaybill" :loading="waybillLoading" style="width: 220px" @change="handleQuery">
<el-option v-for="item in waybillOptions" :key="item.planId" :label="item.planName" :value="item.planId" />
</el-select>
</el-form-item>
<el-form-item label="入场卷号" prop="enterCoilNo">
<el-input v-model="queryParams.enterCoilNo" placeholder="请输入入场钢卷号" clearable
@keyup.enter.native="handleQuery" />
@@ -10,8 +16,8 @@
@keyup.enter.native="handleQuery" />
</el-form-item>
<el-form-item label="逻辑库位" prop="warehouseId" v-if="useWarehouseIds">
<muti-select v-model="warehouseIds" placeholder="请选择逻辑库位"
style="width: 100%; display: inline-block;" clearable :options="warehouseOptions">
<muti-select v-model="warehouseIds" placeholder="请选择逻辑库位" style="width: 100%; display: inline-block;" clearable
:options="warehouseOptions">
</muti-select>
</el-form-item>
<el-form-item label="逻辑库位" prop="warehouseId" v-else-if="!hideWarehouseQuery && !leftWarehouseQuery">
@@ -68,8 +74,8 @@
</el-select>
</el-form-item>
<el-form-item label="品质">
<muti-select v-model="queryParams.qualityStatusCsv" :options="dict.type.coil_quality_status"
placeholder="请选择品质" clearable />
<muti-select v-model="queryParams.qualityStatusCsv" :options="dict.type.coil_quality_status" placeholder="请选择品质"
clearable />
</el-form-item>
<el-form-item v-if="showWaybill" label="发货状态">
@@ -97,6 +103,8 @@
<el-button type="primary" icon="el-icon-search" size="mini" @click="handleQuery">搜索</el-button>
<el-button icon="el-icon-refresh" size="mini" @click="resetQuery">重置</el-button>
<el-button icon="el-icon-download" size="mini" @click="handleNewExport" v-if="showNewExport">导出</el-button>
<el-button type="danger" plain icon="el-icon-close" size="mini" :disabled="multiple" :loading="buttonLoading"
@click="handleBatchRemoveFromWaybill">批量移出发货单</el-button>
</el-form-item>
<!-- <el-form-item style="float: right;" v-if="showWaybill" v-loading="loading">
@@ -123,6 +131,9 @@
<el-button type="info" plain icon="el-icon-printer" size="mini" :disabled="multiple"
@click="handleBatchPrintLabel">批量打印标签</el-button>
</el-col>
<!-- <el-col :span="2" v-if="showWaybill">
</el-col> -->
<el-col :span="1.5" v-if="showOrderBy">
<el-checkbox v-model="queryParams.orderBy" v-loading="loading" @change="getList"
label="orderBy">按实际库区排序</el-checkbox>
@@ -132,20 +143,20 @@
</el-row>
<div style="display: flex; align-items: flex-start;">
<div v-if="leftWarehouseQuery"
:style="{
<div v-if="leftWarehouseQuery" :style="{
width: '220px',
height: showAbnormal ? 'calc(100vh - 400px)' : 'calc(100vh - 300px)',
backgroundColor: '#ffffff',
overflowY: 'auto',
overflowX: 'hidden' }">
overflowX: 'hidden'
}">
<warehouse-tree warehouseType="logic" @node-click="handleWarehouseNodeClick" />
</div>
<div style="flex: 1; width: 100%; overflow: hidden;">
<KLPTable v-loading="loading" :data="materialCoilList" @selection-change="handleSelectionChange"
:floatLayer="true" :floatLayerConfig="floatLayerConfig" @row-click="handleRowClick"
:height="showAbnormal ? 'calc(100vh - 400px)' : 'calc(100vh - 300px)'" border>
<!-- <el-table-column type="selection" width="55" align="center" /> -->
<el-table-column v-if="showWaybill" type="selection" width="55" align="center" />
<el-table-column label="入场钢卷号" align="center" prop="enterCoilNo">
<template slot-scope="scope">
<coil-no :coil-no="scope.row.enterCoilNo"></coil-no>
@@ -321,7 +332,8 @@
</el-table-column>
<el-table-column prop="action" label="操作" align="center" class-name="small-padding fixed-width" v-if="!moreColumn">
<el-table-column prop="action" label="操作" align="center" class-name="small-padding fixed-width"
v-if="!moreColumn">
<template slot-scope="scope">
<el-button size="mini" type="text" icon="el-icon-view" @click="handlePreviewLabel(scope.row)">
预览标签
@@ -567,7 +579,8 @@
<el-form :model="judgeForm" label-width="120px">
<el-form-item label="修改质量状态">
<el-select v-model="judgeForm.qualityStatus" placeholder="请选择质量状态" style="width: 200px">
<el-option v-for="item in dict.type.coil_quality_status" :key="item.value" :value="item.value" :label="item.label" />
<el-option v-for="item in dict.type.coil_quality_status" :key="item.value" :value="item.value"
:label="item.label" />
</el-select>
</el-form-item>
<el-form-item label="改判原因">
@@ -639,13 +652,15 @@
<el-table-column label="操作">
<template slot-scope="scope">
<el-button size="mini" type="text" @click="openCoilSelection(scope.row)">选择钢卷</el-button>
<el-button size="mini" type="text" style="color: #f56c6c;" @click="deleteTempOrder(scope.row)">删除</el-button>
<el-button size="mini" type="text" style="color: #f56c6c;"
@click="deleteTempOrder(scope.row)">删除</el-button>
</template>
</el-table-column>
</el-table>
<!-- 当没有数据时显示提示 -->
<div v-if="!tempOrderList || tempOrderList.length === 0" style="text-align: center; padding: 50px; color: #909399;">
<div v-if="!tempOrderList || tempOrderList.length === 0"
style="text-align: center; padding: 50px; color: #909399;">
<i class="el-icon-document" style="font-size: 48px; margin-bottom: 10px;"></i>
<div>暂无暂存单据</div>
<div style="font-size: 12px; margin-top: 5px;">点击右上角"创建暂存单据"开始使用</div>
@@ -681,7 +696,8 @@
<!-- 下方钢卷列表 -->
<div style="height: 500px;">
<el-table v-loading="coilLoading" :data="availableCoils" @selection-change="handleCoilSelection" border height="450">
<el-table v-loading="coilLoading" :data="availableCoils" @selection-change="handleCoilSelection" border
height="450">
<el-table-column type="selection" width="55" align="center" />
<el-table-column label="入场钢卷号" align="center" prop="enterCoilNo">
<template slot-scope="scope">
@@ -720,14 +736,9 @@
</el-table>
<div style="margin-top: 10px; text-align: center;">
<el-pagination
@size-change="handleCoilSizeChange"
@current-change="handleCoilCurrentChange"
:current-page="coilQueryParams.pageNum"
:page-sizes="[10, 20, 50, 100]"
:page-size="coilQueryParams.pageSize"
layout="total, sizes, prev, pager, next, jumper"
:total="coilTotal">
<el-pagination @size-change="handleCoilSizeChange" @current-change="handleCoilCurrentChange"
:current-page="coilQueryParams.pageNum" :page-sizes="[10, 20, 50, 100]"
:page-size="coilQueryParams.pageSize" layout="total, sizes, prev, pager, next, jumper" :total="coilTotal">
</el-pagination>
</div>
</div>
@@ -774,7 +785,8 @@
<el-table-column label="备注" align="center" prop="remark" show-overflow-tooltip />
<el-table-column label="操作" align="center" width="100">
<template slot-scope="scope">
<el-button size="mini" type="text" style="color: #f56c6c;" @click="removeCoilFromOrder(scope.$index)">移除</el-button>
<el-button size="mini" type="text" style="color: #f56c6c;"
@click="removeCoilFromOrder(scope.$index)">移除</el-button>
</template>
</el-table-column>
</el-table>
@@ -797,7 +809,7 @@ import {
getCoilStatisticsList,
listWithAdjustRecordCoil
} from "@/api/wms/coil";
import { listBoundCoil } from "@/api/wms/deliveryWaybillDetail";
import { listBoundCoil, getBoundCoilStatisticsList } from "@/api/wms/deliveryWaybillDetail";
import { addPendingAction } from "@/api/wms/pendingAction";
import { listTransferOrderItem } from "@/api/wms/transferOrderItem";
import { listCoilQualityRejudge } from "@/api/wms/coilQualityRejudge";
@@ -829,6 +841,7 @@ import DragResizeBox from '@/components/DragResizeBox/index.vue';
import ProcessFlow from '../components/ProcessFlow.vue';
import WarehouseTree from '@/components/KLPService/WarehouseTree/index.vue';
import { listDeliveryWaybillDetail, delDeliveryWaybillDetail } from "@/api/wms/deliveryWaybillDetail";
import { listDeliveryPlan } from "@/api/wms/deliveryPlan";
import { addCoilQualityRejudge } from "@/api/wms/coilQualityRejudge";
export default {
@@ -978,6 +991,8 @@ export default {
showProcessFlow: false,
// 遮罩层
loading: true,
waybillLoading: false,
waybillOptions: [],
// 追溯加载中
traceLoading: false,
// 选中数组
@@ -1019,6 +1034,7 @@ export default {
status: '',
updateTime: undefined,
orderBy: false,
planId: undefined,
...this.querys,
},
// 表单参数
@@ -1217,6 +1233,9 @@ export default {
if (this.useWarehouseIds) {
this.warehouseIds = this.warehouseOptions.map(item => item.value).join(',');
}
if (this.showWaybill) {
this.remoteSearchWaybill();
}
this.getList();
// 初始化暂存单据列表
this.loadTempOrderList();
@@ -1531,6 +1550,50 @@ export default {
})
// 打开一个弹窗列出查询到的所有单据明细
},
async handleBatchRemoveFromWaybill() {
const selectedRows = this.materialCoilList.filter(item => this.ids.includes(item.coilId));
if (selectedRows.length === 0) {
this.$message.warning('请先勾选要移除的钢卷');
return;
}
this.$modal.confirm(`确认要将选中的 ${selectedRows.length} 个钢卷从发货单中移除吗?`, {
title: '批量移除',
type: 'warning',
}).then(async () => {
this.buttonLoading = true;
const detailIds = [];
const failCoils = [];
for (const row of selectedRows) {
try {
const res = await listDeliveryWaybillDetail({ coilId: row.coilId });
if (res.rows.length === 1) {
detailIds.push(res.rows[0].detailId);
} else {
failCoils.push(row.currentCoilNo);
}
} catch (e) {
failCoils.push(row.currentCoilNo);
}
}
if (detailIds.length === 0) {
this.$message.error('未找到任何可移除的发货单明细');
this.buttonLoading = false;
return;
}
try {
await delDeliveryWaybillDetail(detailIds.join(','));
if (failCoils.length > 0) {
this.$message.warning(`移除完成:成功 ${detailIds.length} 个,失败 ${failCoils.length} 个(${failCoils.join('、')}`);
} else {
this.$message.success(`成功移除 ${detailIds.length} 个钢卷`);
}
} catch (e) {
this.$message.error('批量移除失败');
}
this.buttonLoading = false;
this.getList();
});
},
// 处理重贴标签
handleReplaceLabel(row) {
updateMaterialCoilSimple({
@@ -1756,15 +1819,15 @@ export default {
this.loading = false;
})
// 获取统计数据:已发货的数量和未发货的数量
listBoundCoil({ ...query, status: 0 }).then(res => {
listBoundCoil({ ...query, status: 0, pageNum: 1, pageSize: 1 }).then(res => {
this.unshippedCount = res.total;
})
// 获取统计数据:已发货的数量和未发货的数量
listBoundCoil({ ...query, status: 1 }).then(res => {
listBoundCoil({ ...query, status: 1, pageNum: 1, pageSize: 1 }).then(res => {
this.shippedCount = res.total;
})
getCoilStatisticsList(query).then(res => {
getBoundCoilStatisticsList(query).then(res => {
this.statistics = res.data || [];
})
return;
@@ -1892,6 +1955,14 @@ export default {
};
this.resetForm("form");
},
remoteSearchWaybill(query) {
this.waybillLoading = true;
listDeliveryPlan({ planName: query, pageNum: 1, pageSize: 20, planType: 0 }).then(res => {
this.waybillOptions = res.rows || [];
}).finally(() => {
this.waybillLoading = false;
});
},
/** 搜索按钮操作 */
handleQuery() {
this.queryParams.pageNum = 1;

View File

@@ -12,6 +12,47 @@
</div>
</div>
<!-- 最近排产单 -->
<div class="plan-sheet-container">
<div v-if="planSheetList.length > 0" class="plan-sheet-section">
<el-descriptions :column="1" border title="最近排产单(酸轧线)" size="small" />
<el-tabs v-model="activePlanSheetId" type="card" size="mini">
<el-tab-pane v-for="sheet in planSheetList" :key="sheet.planSheetId" :name="sheet.planSheetId"
:label="sheet.planCode || ('排产单' + sheet.planSheetId)" />
</el-tabs>
<el-table v-loading="planSheetLoading" :data="activePlanSheetDetails" border stripe size="mini"
max-height="300">
<el-table-column type="index" label="序号" width="50" />
<el-table-column label="订单信息" header-align="center">
<el-table-column prop="contractCode" label="合同号" width="140" show-overflow-tooltip />
<el-table-column prop="customerName" label="客户" width="120" show-overflow-tooltip />
<el-table-column prop="salesman" label="业务员" width="100" show-overflow-tooltip />
</el-table-column>
<el-table-column label="成品信息" header-align="center">
<el-table-column prop="productName" label="成品名称" width="140" show-overflow-tooltip />
<el-table-column prop="productMaterial" label="材质" width="100" />
<el-table-column prop="coatingG" label="镀层g" width="80" />
<el-table-column prop="productWidth" label="成品宽度" width="100" />
<el-table-column prop="rollingThick" label="轧制厚度" width="100" />
<el-table-column prop="markCoatThick" label="标丝厚度" width="100" />
<el-table-column prop="tonSteelLengthRange" label="长度区间(m)" width="120" />
<el-table-column prop="planQty" label="数量" width="80" />
<el-table-column prop="planWeight" label="重量" width="100" />
<el-table-column prop="surfaceTreatment" label="表面处理" width="110" show-overflow-tooltip />
<el-table-column prop="widthReq" label="切边要求" width="110" show-overflow-tooltip />
<el-table-column prop="productPackaging" label="包装要求" width="100" show-overflow-tooltip />
<el-table-column prop="productEdgeReq" label="宽度要求" width="100" show-overflow-tooltip />
<el-table-column prop="usageReq" label="用途" width="100" show-overflow-tooltip />
</el-table-column>
<el-table-column prop="remark" label="备注" width="140" show-overflow-tooltip />
</el-table>
</div>
<div v-else-if="!planSheetLoading" class="plan-sheet-section">
<el-descriptions :column="1" border title="最近排产单(酸轧线)" size="small" />
<el-empty description="今天暂无排产单" :image-size="60" />
</div>
</div>
<!-- 流程图区域 -->
<div class="flow-container">
<!-- 左侧母卷信息 + 排产信息 -->
@@ -84,39 +125,6 @@
</div>
</div>
<!-- 今日排产单 -->
<div v-if="planSheetList.length > 0" class="plan-sheet-section">
<el-descriptions :column="1" border title="最近排产单(酸轧线)" size="small" />
<el-tabs v-model="activePlanSheetId" type="card" size="mini">
<el-tab-pane v-for="sheet in planSheetList" :key="sheet.planSheetId"
:name="sheet.planSheetId"
:label="sheet.planCode || ('排产单' + sheet.planSheetId)" />
</el-tabs>
<el-table v-loading="planSheetLoading" :data="activePlanSheetDetails" border stripe size="mini" max-height="300">
<el-table-column type="index" label="#" width="40" />
<el-table-column prop="bizSeqNo" label="序号" width="55" />
<el-table-column label="订单信息">
<el-table-column prop="orderCode" label="订单号" width="130" show-overflow-tooltip />
<el-table-column prop="contractCode" label="合同号" width="130" show-overflow-tooltip />
<el-table-column prop="customerName" label="客户" width="100" show-overflow-tooltip />
<el-table-column prop="salesman" label="业务员" width="80" show-overflow-tooltip />
</el-table-column>
<el-table-column label="成品信息">
<el-table-column prop="productName" label="成品名称" width="130" show-overflow-tooltip />
<el-table-column prop="productMaterial" label="材质" width="80" />
<el-table-column prop="coatingG" label="镀层g" width="70" />
<el-table-column prop="productWidth" label="宽度" width="80" />
<el-table-column prop="rollingThick" label="轧厚" width="80" />
<el-table-column prop="planQty" label="数量" width="65" />
<el-table-column prop="planWeight" label="重量" width="80" />
</el-table-column>
<el-table-column prop="remark" label="备注" width="100" show-overflow-tooltip />
</el-table>
</div>
<div v-else-if="!planSheetLoading" class="plan-sheet-section">
<el-descriptions :column="1" border title="最近排产单(酸轧线)" size="small" />
<el-empty description="今天暂无排产单" :image-size="60" />
</div>
</div>
<!-- 右侧子卷列表 -->
@@ -139,7 +147,7 @@
class="btn-remove"></el-button>
</div>
<div class="sub-coil-body">
<el-form size="small" label-width="90px">
<el-form size="small" label-width="80px">
<div class="form-row">
<el-form-item label="卷号" required class="form-item-half">
<el-input v-model="item.currentCoilNo" placeholder="输入子卷卷号" :disabled="readonly"></el-input>
@@ -178,7 +186,8 @@
</el-select>
</el-form-item>
<el-form-item label="切边要求" prop="trimmingRequirement" class="form-item-half">
<el-select v-model="item.trimmingRequirement" placeholder="请选择" style="width: 100%" :disabled="readonly">
<el-select v-model="item.trimmingRequirement" placeholder="请选择" style="width: 100%"
:disabled="readonly">
<el-option label="净边料" value="净边料" />
<el-option label="毛边料" value="毛边料" />
</el-select>
@@ -190,7 +199,8 @@
<el-input v-model="item.packingStatus" placeholder="请输入原料材质" :disabled="readonly" />
</el-form-item>
<el-form-item label="包装要求" prop="packagingRequirement" class="form-item-half">
<el-select v-model="item.packagingRequirement" placeholder="请选择" style="width: 100%" :disabled="readonly">
<el-select v-model="item.packagingRequirement" placeholder="请选择" style="width: 100%"
:disabled="readonly">
<el-option label="裸包" value="裸包" />
<el-option label="普包" value="普包" />
<el-option label="简包" value="简包" />
@@ -216,14 +226,14 @@
<div class="form-row">
<el-form-item label="长度(m)" class="form-item-half">
<el-input-number :controls="false" v-model="item.length" placeholder="请输入长度"
:step="0.01" :disabled="readonly">
<el-input-number :controls="false" v-model="item.length" placeholder="请输入长度" :step="0.01"
:disabled="readonly">
<template slot="append"></template>
</el-input-number>
</el-form-item>
<el-form-item label="实测长度(m)" prop="actualLength" class="form-item-half">
<el-input-number :controls="false" v-model="item.actualLength" placeholder="实测长度"
:step="0.01" :disabled="readonly">
<el-input-number :controls="false" v-model="item.actualLength" placeholder="实测长度" :step="0.01"
:disabled="readonly">
<template slot="append">m</template>
</el-input-number>
</el-form-item>
@@ -231,14 +241,14 @@
<div class="form-row">
<el-form-item label="实测厚度(mm)" prop="actualThickness" class="form-item-half">
<el-input-number :controls="false" v-model="item.actualThickness" placeholder="实测厚度"
:step="0.01" :disabled="readonly">
<el-input-number :controls="false" v-model="item.actualThickness" placeholder="实测厚度" :step="0.01"
:disabled="readonly">
<template slot="append">mm</template>
</el-input-number>
</el-form-item>
<el-form-item label="实测宽度(mm)" prop="actualWidth" class="form-item-half">
<el-input-number :controls="false" v-model="item.actualWidth" placeholder="实测宽度"
:step="0.01" :disabled="readonly">
<el-input-number :controls="false" v-model="item.actualWidth" placeholder="实测宽度" :step="0.01"
:disabled="readonly">
<template slot="append">mm</template>
</el-input-number>
</el-form-item>
@@ -961,7 +971,7 @@ export default {
<style scoped lang="scss">
.split-coil-container {
padding: 20px;
padding: 12px;
background: #f5f7fa;
min-height: calc(100vh - 84px);
}
@@ -972,8 +982,8 @@ export default {
justify-content: space-between;
align-items: center;
background: #fff;
padding: 16px 20px;
margin-bottom: 20px;
padding: 10px 16px;
margin-bottom: 12px;
border-radius: 4px;
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
}
@@ -995,16 +1005,16 @@ export default {
/* 流程图容器 */
.flow-container {
display: flex;
gap: 20px;
gap: 12px;
background: #fff;
padding: 20px;
padding: 12px;
border-radius: 4px;
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
min-height: 600px;
min-height: 500px;
}
.flow-left {
flex: 0 0 340px;
flex: 0 0 320px;
overflow-y: auto;
}
@@ -1017,8 +1027,8 @@ export default {
font-size: 16px;
font-weight: 500;
color: #303133;
margin-bottom: 16px;
padding-left: 12px;
margin-bottom: 10px;
padding-left: 10px;
border-left: 4px solid #0066cc;
display: flex;
justify-content: space-between;
@@ -1045,24 +1055,24 @@ export default {
.coil-header {
background: #0066cc;
color: #fff;
padding: 16px 20px;
padding: 10px 14px;
display: flex;
align-items: center;
gap: 10px;
i {
font-size: 20px;
font-size: 18px;
}
.coil-title {
flex: 1;
font-size: 16px;
font-size: 14px;
font-weight: 600;
}
}
.coil-body {
padding: 20px;
padding: 12px;
&.two-col {
display: grid;
@@ -1074,7 +1084,7 @@ export default {
.coil-info-row {
display: flex;
align-items: flex-start;
margin-bottom: 12px;
margin-bottom: 8px;
&:last-child {
margin-bottom: 0;
@@ -1082,8 +1092,8 @@ export default {
.label {
color: #909399;
font-size: 14px;
min-width: 100px;
font-size: 13px;
min-width: 90px;
flex-shrink: 0;
}
@@ -1146,9 +1156,9 @@ export default {
/* 子卷列表 */
.split-list {
max-height: 500px;
max-height: calc(100vh - 280px);
overflow-y: auto;
margin-bottom: 20px;
margin-bottom: 0;
padding-right: 10px;
&::-webkit-scrollbar {
@@ -1165,7 +1175,7 @@ export default {
background: #fff;
border: 1px solid #e4e7ed;
border-radius: 8px;
margin-bottom: 16px;
margin-bottom: 10px;
transition: all 0.3s;
&:hover {
@@ -1176,7 +1186,7 @@ export default {
.sub-coil-header {
background: #f5f7fa;
padding: 12px 16px;
padding: 8px 12px;
display: flex;
justify-content: space-between;
align-items: center;
@@ -1184,11 +1194,11 @@ export default {
}
.sub-coil-number {
font-size: 16px;
font-size: 14px;
font-weight: 600;
color: #0066cc;
width: 32px;
height: 32px;
width: 28px;
height: 28px;
border-radius: 50%;
background: #ecf5ff;
display: flex;
@@ -1206,76 +1216,13 @@ export default {
}
.sub-coil-body {
padding: 16px;
}
/* 汇总卡片 */
.summary-card {
background: #ecf5ff;
border: 1px solid #d9ecff;
border-radius: 8px;
padding: 16px 20px;
display: flex;
justify-content: space-around;
}
.summary-item {
display: flex;
flex-direction: column;
align-items: center;
gap: 8px;
}
.summary-label {
font-size: 13px;
color: #606266;
}
.summary-value {
font-size: 20px;
font-weight: 600;
color: #0066cc;
&.error {
color: #f56c6c;
}
}
// 优化按钮文字颜色
// 实心primary按钮白色文字
::v-deep .el-button--primary.el-button--small:not(.is-plain) {
color: #fff;
}
// plain按钮和text按钮蓝色文字
::v-deep .el-button--primary.el-button--small.is-plain,
::v-deep .el-button--text {
color: #409eff;
&:hover {
color: #66b1ff;
}
}
// 修复数字输入框的上下箭头溢出
.sub-coil-body {
::v-deep input[type="number"] {
appearance: textfield;
-moz-appearance: textfield;
&::-webkit-outer-spin-button,
&::-webkit-inner-spin-button {
-webkit-appearance: none;
appearance: none;
margin: 0;
}
}
padding: 12px;
}
/* 双列表单布局 */
.form-row {
display: flex;
gap: 16px;
gap: 10px;
margin-bottom: 0;
&:last-child {
@@ -1293,8 +1240,26 @@ export default {
}
/* 排产单区域 */
.plan-sheet-container {
background: #fff;
border-radius: 4px;
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
padding: 12px;
margin-bottom: 12px;
}
.plan-sheet-section {
margin-top: 16px;
margin-top: 0;
}
// 减少表单项间距
::v-deep .el-form-item {
margin-bottom: 10px;
}
::v-deep .el-form-item--small .el-form-item__content,
::v-deep .el-form-item--small .el-form-item__label {
line-height: 32px;
}
// 异常信息样式

View File

@@ -16,30 +16,34 @@
<span><i class="el-icon-s-order"></i> {{ currentLineName }}排产计划</span>
</div>
<el-tabs v-model="activePlanSheetId" type="card" size="mini">
<el-tab-pane v-for="sheet in planSheetList" :key="sheet.planSheetId"
:name="sheet.planSheetId"
<el-tab-pane v-for="sheet in planSheetList" :key="sheet.planSheetId" :name="sheet.planSheetId"
:label="sheet.planCode || ('排产单' + sheet.planSheetId)" />
</el-tabs>
<div style="overflow-x: auto;">
<el-table :data="activePlanSheetDetails" size="small" max-height="300" stripe border>
<el-table-column label="订单号" prop="orderCode" width="160" />
<el-table-column label="合同号" prop="contractCode" width="160" />
<el-table-column label="客户" prop="customerName" width="100" />
<el-table-column label="业务员" prop="salesman" width="80" />
<el-table-column label="成品名称" prop="productName" width="120" />
<el-table-column label="成品材质" prop="productMaterial" width="100" />
<el-table-column label="成品镀层" prop="coatingG" width="90" />
<el-table-column label="成品宽度" prop="productWidth" width="90" />
<el-table-column label="轧制厚度" prop="rollingThick" width="90" />
<el-table-column label="标签厚度" prop="markCoatThick" width="90" />
<el-table-column label="吨钢长度区间" prop="tonSteelLengthRange" width="120" />
<el-table-column label="数量" prop="planQty" width="70" />
<el-table-column label="重量" prop="planWeight" width="90" />
<el-table-column label="表面处理" prop="surfaceTreatment" width="100" />
<el-table-column label="切边要求" prop="widthReq" width="90" />
<el-table-column label="包装要求" prop="productPackaging" width="90" />
<el-table-column label="宽度要求" prop="productEdgeReq" width="90" />
<el-table-column label="用途" prop="usageReq" width="100" />
<el-table-column type="index" label="序号" width="50" />
<el-table-column label="订单信息" header-align="center">
<el-table-column prop="contractCode" label="合同号" width="140" show-overflow-tooltip />
<el-table-column prop="customerName" label="客户" width="120" show-overflow-tooltip />
<el-table-column prop="salesman" label="业务员" width="100" show-overflow-tooltip />
</el-table-column>
<el-table-column label="成品信息" header-align="center">
<el-table-column prop="productName" label="成品名称" width="140" show-overflow-tooltip />
<el-table-column prop="productMaterial" label="材质" width="100" />
<el-table-column prop="coatingG" label="镀层g" width="80" />
<el-table-column prop="productWidth" label="成品宽度" width="100" />
<el-table-column prop="rollingThick" label="轧制厚度" width="100" />
<el-table-column prop="markCoatThick" label="标丝厚度" width="100" />
<el-table-column prop="tonSteelLengthRange" label="长度区间(m)" width="120" />
<el-table-column prop="planQty" label="数量" width="80" />
<el-table-column prop="planWeight" label="重量" width="100" />
<el-table-column prop="surfaceTreatment" label="表面处理" width="110" show-overflow-tooltip />
<el-table-column prop="widthReq" label="切边要求" width="110" show-overflow-tooltip />
<el-table-column prop="productPackaging" label="包装要求" width="100" show-overflow-tooltip />
<el-table-column prop="productEdgeReq" label="宽度要求" width="100" show-overflow-tooltip />
<el-table-column prop="usageReq" label="用途" width="100" show-overflow-tooltip />
</el-table-column>
<el-table-column prop="remark" label="备注" width="140" show-overflow-tooltip />
</el-table>
</div>
</el-card>
@@ -68,7 +72,8 @@
<div v-if="matchedSpec" style="margin-bottom:10px">
<el-tag type="success" size="small">
<i class="el-icon-s-order" style="margin-right:4px" />
已匹配规程{{ matchedSpec.specCode }}{{ matchedSpec.specName ? ' - ' + matchedSpec.specName : '' }} / {{ matchedSpec.versionCode }}
已匹配规程{{ matchedSpec.specCode }}{{ matchedSpec.specName ? ' - ' + matchedSpec.specName : '' }} / {{
matchedSpec.versionCode }}
</el-tag>
</div>
<el-form ref="updateForm" :model="updateForm" :rules="rules" label-width="86px" size="small">

View File

@@ -56,6 +56,11 @@
</el-select>
</el-form-item>
<el-form-item label="备注" prop="remark">
<el-input v-model="queryParams.remark" placeholder="请输入备注" clearable
@keyup.enter.native="handleQuery" />
</el-form-item>
<el-form-item>
<el-button type="primary" icon="el-icon-search" size="mini" @click="handleQuery">搜索</el-button>
<el-button icon="el-icon-refresh" size="mini" @click="resetQuery">重置</el-button>
@@ -178,7 +183,25 @@
<!-- 如果没有绑定订单这里是使用手机号字段来存储手填的订单编号 -->
<el-form-item label="订单编号" prop="principalPhone" v-if="!form.orderId">
<div style="display: flex; gap: 10px; align-items: center;">
<el-input v-model="form.principalPhone" placeholder="请输入订单编号" style="flex: 1;" />
<el-autocomplete
v-model="form.principalPhone"
:fetch-suggestions="queryOrderSuggestions"
placeholder="请输入订单编号"
style="flex: 1;"
value-key="orderCode"
@select="onOrderAutoSelect"
clearable
:highlight-first-item="true"
popper-class="order-autocomplete-popper"
>
<template slot-scope="{ item }">
<div class="order-suggestion-item">
<span class="suggestion-code">{{ item.orderCode }}</span>
<span class="suggestion-company">{{ item.companyName }}</span>
<span class="suggestion-salesman">{{ item.salesman }}</span>
</div>
</template>
</el-autocomplete>
<el-button type="primary" size="small" @click="bindOrder">绑定订单</el-button>
</div>
</el-form-item>
@@ -344,6 +367,8 @@ export default {
// 订单搜索关键词
orderQuery: '',
orderId: '',
// 订单自动补全
orderSuggestLoading: false,
};
},
created() {
@@ -645,6 +670,52 @@ export default {
this.form.principal = row.salesman;
this.orderDialogVisible = false;
},
/** 订单编号自动补全 */
queryOrderSuggestions(queryString, callback) {
if (!queryString || queryString.trim() === '') {
callback([]);
return;
}
this.orderSuggestLoading = true;
listOrder({ pageNum: 1, pageSize: 10, keyword: queryString }).then(response => {
const suggestions = (response.rows || []).map(item => ({
...item,
value: item.orderCode
}));
this.orderSuggestLoading = false;
callback(suggestions);
}).catch(() => {
this.orderSuggestLoading = false;
callback([]);
});
},
/** 自动补全选择订单 */
onOrderAutoSelect(item) {
const existing = this.deliveryWaybillList.find(
d => d.orderId === item.orderId || d.orderCode === item.orderCode
);
if (existing) {
this.$confirm(
`订单"${item.orderCode}"已绑定在发货单"${existing.waybillName}"中,确定继续绑定?`,
'提示',
{ confirmButtonText: '确定', cancelButtonText: '取消', type: 'warning' }
).then(() => {
this.doBindOrder(item);
}).catch(() => {
this.form.principalPhone = item.orderCode;
});
} else {
this.doBindOrder(item);
}
},
/** 执行订单绑定并填写信息 */
doBindOrder(item) {
this.form.orderId = item.orderId;
this.form.orderCode = item.orderCode;
this.form.consigneeUnit = item.companyName;
this.form.principal = item.salesman;
this.form.principalPhone = item.orderCode;
},
/** 打印发货单 */
handlePrint(row, printType) {
this.loading = true;
@@ -856,4 +927,58 @@ export default {
::v-deep .el-pagination {
margin-top: 6px !important;
}
.order-suggestion-item {
display: flex;
align-items: center;
gap: 12px;
padding: 4px 0;
}
.suggestion-code {
font-weight: 500;
color: #303133;
min-width: 120px;
}
.suggestion-company {
color: #909399;
font-size: 12px;
flex: 1;
}
.suggestion-salesman {
color: #409eff;
font-size: 12px;
}
</style>
<style>
.order-autocomplete-popper {
min-width: 380px !important;
}
.order-autocomplete-popper .order-suggestion-item {
display: flex;
align-items: center;
gap: 12px;
padding: 4px 0;
}
.order-autocomplete-popper .suggestion-code {
font-weight: 500;
color: #303133;
min-width: 120px;
}
.order-autocomplete-popper .suggestion-company {
color: #909399;
font-size: 12px;
flex: 1;
}
.order-autocomplete-popper .suggestion-salesman {
color: #409eff;
font-size: 12px;
}
</style>

View File

@@ -24,7 +24,12 @@
<div class="schedule-table-wrapper">
<el-table v-loading="loading" :data="attendanceData" border stripe>
<el-table-column prop="employeeName" label="员工" width="120" fixed="left" />
<el-table-column label="员工" width="150" fixed="left">
<template slot-scope="scope">
<span>{{ scope.row.employeeName }}</span>
<el-button type="text" size="mini" icon="el-icon-view" @click="handlePersonDetail(scope.row)"></el-button>
</template>
</el-table-column>
<el-table-column v-for="date in dateList" :key="date" :label="formatDateLabel(date)" width="150" align="center">
<template slot-scope="scope">
<div class="attendance-cell" :class="getAttendanceStatusClass(scope.row[date])"
@@ -385,6 +390,81 @@
</div>
</el-dialog>
<!-- 个人考勤总览 -->
<el-dialog :title="personDialogTitle" :visible.sync="personDialogVisible" width="900px" append-to-body>
<div v-if="currentPersonData" v-loading="personLoading">
<el-descriptions title="考勤汇总" :column="5" border style="margin-bottom: 20px;">
<el-descriptions-item label="姓名">{{ currentPersonData.employeeName }}</el-descriptions-item>
<el-descriptions-item label="正常">{{ personSummary.normal }}</el-descriptions-item>
<el-descriptions-item label="迟到/早退">{{ personSummary.abnormal }}</el-descriptions-item>
<el-descriptions-item label="半天旷工">{{ personSummary.absentHalf }}</el-descriptions-item>
<el-descriptions-item label="全天旷工">{{ personSummary.absentFull }}</el-descriptions-item>
<el-descriptions-item label="无记录">{{ personSummary.noRecord }}</el-descriptions-item>
<el-descriptions-item label="迟到合计">{{ personSummary.totalLateMinutes }}分钟</el-descriptions-item>
<el-descriptions-item label="早退合计">{{ personSummary.totalEarlyMinutes }}分钟</el-descriptions-item>
<el-descriptions-item label="总扣款">¥{{ personSummary.totalDeduct }}</el-descriptions-item>
<el-descriptions-item label="统计天数">{{ dateList.length }}</el-descriptions-item>
</el-descriptions>
<el-divider content-position="left">考勤明细{{ dateList.length }}</el-divider>
<!-- 表格视图小数据量(14) 特大数据量(>62) -->
<el-table v-if="personViewMode === 'table'" :data="personDetailTableData" border stripe size="small" max-height="400">
<el-table-column prop="workDate" label="日期" width="120" fixed="left" />
<el-table-column prop="shiftName" label="班次" width="120" />
<el-table-column prop="overallStatus" label="总体状态" width="100">
<template slot-scope="scope">
<span :class="getStatusClass(scope.row.overallStatus)">{{ getStatusText(scope.row.overallStatus) }}</span>
</template>
</el-table-column>
<el-table-column prop="p1FirstCheck" label="上班打卡" width="160">
<template slot-scope="scope">{{ formatTime(scope.row.p1FirstCheck) }}</template>
</el-table-column>
<el-table-column prop="p1LastCheck" label="下班打卡" width="160">
<template slot-scope="scope">{{ formatTime(scope.row.p1LastCheck) }}</template>
</el-table-column>
<el-table-column prop="p1LateMinutes" label="迟到(分)" width="80" align="center">
<template slot-scope="scope">
<span v-if="scope.row.p1LateMinutes > 0" class="late-info">{{ scope.row.p1LateMinutes }}</span>
<span v-else>0</span>
</template>
</el-table-column>
<el-table-column prop="p1EarlyMinutes" label="早退(分)" width="80" align="center">
<template slot-scope="scope">
<span v-if="scope.row.p1EarlyMinutes > 0" class="early-info">{{ scope.row.p1EarlyMinutes }}</span>
<span v-else>0</span>
</template>
</el-table-column>
<el-table-column prop="p1Deduct" label="扣款" width="80" align="center">
<template slot-scope="scope">¥{{ scope.row.p1Deduct || '0.00' }}</template>
</el-table-column>
<el-table-column prop="remark" label="备注" min-width="120" show-overflow-tooltip />
</el-table>
<!-- 日历视图中等数据量(15~62) -->
<div v-if="personViewMode === 'calendar'" class="calendar-view">
<div v-for="month in personCalendarMonths" :key="month.key" class="calendar-month">
<h4 class="calendar-month-title">{{ month.label }}</h4>
<div class="calendar-grid">
<div class="calendar-header" v-for="w in calendarWeekDays" :key="w">{{ w }}</div>
<div v-for="(cell, idx) in month.cells" :key="idx"
class="calendar-cell" :class="getCalendarCellClass(cell)">
<span class="calendar-day">{{ cell.day }}</span>
<span v-if="cell.date && cell.status" class="calendar-status">{{ getStatusText(cell.status) }}</span>
</div>
</div>
</div>
<div class="calendar-legend">
<span class="legend-item"><span class="legend-dot normal"></span>正常</span>
<span class="legend-item"><span class="legend-dot late"></span>迟到/早退</span>
<span class="legend-item"><span class="legend-dot absent-half"></span>半天旷工</span>
<span class="legend-item"><span class="legend-dot absent-full"></span>全天旷工</span>
<span class="legend-item"><span class="legend-dot no-record"></span>无记录</span>
</div>
</div>
</div>
</el-dialog>
<el-dialog title="考勤比对" :visible.sync="checkOpen" width="700px" append-to-body>
<el-form ref="checkForm" :model="checkForm" :rules="checkRules" label-width="80px">
<el-form-item label="开始日期" prop="startDate">
@@ -515,7 +595,12 @@ export default {
selectedUserIds: [],
detailRecordsLoading: false,
detailRecordsList: []
detailRecordsList: [],
personDialogVisible: false,
personDialogTitle: '',
currentPersonData: null,
personLoading: false,
};
},
computed: {
@@ -533,7 +618,88 @@ export default {
key: String(emp.infoId),
label: emp.name + '' + emp.dept + ''
}))
},
personSummary() {
if (!this.currentPersonData) return { normal: 0, abnormal: 0, absentHalf: 0, absentFull: 0, noRecord: 0, totalLateMinutes: 0, totalEarlyMinutes: 0, totalDeduct: '0.00' };
let normal = 0, abnormal = 0, absentHalf = 0, absentFull = 0, noRecord = 0;
let totalLate = 0, totalEarly = 0, totalDeduct = 0;
this.dateList.forEach(date => {
const record = this.currentPersonData[date];
if (!record) {
noRecord++;
return;
}
const status = record.overallStatus;
if (status === 'normal') normal++;
else if (status === 'absent_half') absentHalf++;
else if (status === 'absent_full') absentFull++;
else if (status === 'abnormal' || status === 'late_warn' || status === 'late_one' || status === 'late_two' || status === 'early_warn' || status === 'early_one' || status === 'early_two' || status === 'missed') abnormal++;
totalLate += Number(record.p1LateMinutes || 0) + Number(record.p2LateMinutes || 0);
totalEarly += Number(record.p1EarlyMinutes || 0) + Number(record.p2EarlyMinutes || 0);
totalDeduct += Number(record.p1Deduct || 0) + Number(record.p2Deduct || 0) + Number(record.totalDeduct || 0);
});
return { normal, abnormal, absentHalf, absentFull, noRecord, totalLateMinutes: totalLate, totalEarlyMinutes: totalEarly, totalDeduct: totalDeduct.toFixed(2) };
},
personViewMode() {
const days = this.dateList.length;
if (days <= 14) return 'table';
if (days <= 62) return 'calendar';
return 'table';
},
personDetailTableData() {
if (!this.currentPersonData) return [];
return this.dateList.map(date => {
const record = this.currentPersonData[date];
if (!record) {
return { workDate: date, shiftName: '-', overallStatus: null, p1FirstCheck: null, p1LastCheck: null, p2FirstCheck: null, p2LastCheck: null, p1LateMinutes: 0, p1EarlyMinutes: 0, p1Deduct: 0, p2LateMinutes: 0, p2EarlyMinutes: 0, p2Deduct: 0, totalDeduct: 0, remark: '' };
}
return {
workDate: date,
...record,
p1LateMinutes: record.p1LateMinutes || 0,
p1EarlyMinutes: record.p1EarlyMinutes || 0,
};
});
},
calendarWeekDays() {
return ['日', '一', '二', '三', '四', '五', '六'];
},
personCalendarMonths() {
if (!this.dateList || this.dateList.length === 0) return [];
const months = {};
this.dateList.forEach(date => {
const d = new Date(date);
const key = `${d.getFullYear()}-${String(d.getMonth() + 1).padStart(2, '0')}`;
if (!months[key]) {
months[key] = { key, label: `${d.getFullYear()}${d.getMonth() + 1}`, dates: [] };
}
months[key].dates.push(date);
});
return Object.values(months).map(month => {
const firstDate = new Date(month.dates[0]);
const year = firstDate.getFullYear();
const monthNum = firstDate.getMonth();
const firstDay = new Date(year, monthNum, 1);
const lastDay = new Date(year, monthNum + 1, 0);
const startDayOfWeek = firstDay.getDay();
const totalDays = lastDay.getDate();
const cells = [];
for (let i = 0; i < startDayOfWeek; i++) {
cells.push({ day: '', date: null });
}
for (let day = 1; day <= totalDays; day++) {
const dateStr = `${year}-${String(monthNum + 1).padStart(2, '0')}-${String(day).padStart(2, '0')}`;
const record = this.currentPersonData ? this.currentPersonData[dateStr] : null;
cells.push({
day,
date: dateStr,
status: record ? record.overallStatus : null,
inRange: month.dates.includes(dateStr),
});
}
return { ...month, cells };
});
},
},
created() {
this.getAllEmployees().then(() => {
@@ -1049,6 +1215,30 @@ export default {
}).finally(() => {
this.loading = false
})
},
handlePersonDetail(row) {
this.currentPersonData = row
this.personDialogTitle = row.employeeName + ' - 考勤总览'
this.personDialogVisible = true
},
getCalendarCellClass(cell) {
if (!cell.date) return 'cal-empty'
if (!cell.status) return 'cal-no-record'
switch (cell.status) {
case 'normal': return 'cal-normal'
case 'absent_half': return 'cal-absent-half'
case 'absent_full': return 'cal-absent-full'
case 'abnormal':
case 'late_warn':
case 'late_one':
case 'late_two':
case 'early_warn':
case 'early_one':
case 'early_two':
case 'missed':
return 'cal-abnormal'
default: return 'cal-default'
}
}
}
};
@@ -1229,4 +1419,128 @@ export default {
margin-right: 10px;
white-space: nowrap;
}
.early-info {
color: #f56c6c;
}
.calendar-view {
margin-top: 10px;
}
.calendar-month {
margin-bottom: 24px;
}
.calendar-month-title {
margin: 0 0 8px 0;
font-size: 14px;
color: #303133;
}
.calendar-grid {
display: grid;
grid-template-columns: repeat(7, 1fr);
gap: 2px;
border: 1px solid #ebeef5;
border-radius: 4px;
overflow: hidden;
}
.calendar-header {
text-align: center;
padding: 6px 4px;
background: #f5f7fa;
font-size: 12px;
color: #909399;
font-weight: bold;
}
.calendar-cell {
min-height: 50px;
padding: 4px;
background: #fff;
text-align: center;
font-size: 12px;
border-right: 1px solid #ebeef5;
border-bottom: 1px solid #ebeef5;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
}
.calendar-day {
font-weight: bold;
color: #303133;
font-size: 13px;
}
.calendar-status {
font-size: 10px;
margin-top: 2px;
}
.cal-empty {
background: #f9f9f9;
color: #c0c4cc;
}
.cal-no-record {
background: #fdf6ec;
color: #e6a23c;
}
.cal-normal {
background: #f0f9eb;
color: #67c23a;
}
.cal-abnormal {
background: #fef0f0;
color: #f56c6c;
}
.cal-absent-half {
background: #f4f4f5;
color: #909399;
}
.cal-absent-full {
background: #e9e9eb;
color: #606266;
}
.cal-default {
background: #f5f7fa;
color: #c0c4cc;
}
.calendar-legend {
display: flex;
gap: 16px;
margin-top: 12px;
flex-wrap: wrap;
font-size: 12px;
}
.legend-item {
display: flex;
align-items: center;
gap: 4px;
color: #606266;
}
.legend-dot {
display: inline-block;
width: 12px;
height: 12px;
border-radius: 2px;
}
.legend-dot.normal { background: #f0f9eb; border: 1px solid #67c23a; }
.legend-dot.late { background: #fef0f0; border: 1px solid #f56c6c; }
.legend-dot.absent-half { background: #f4f4f5; border: 1px solid #909399; }
.legend-dot.absent-full { background: #e9e9eb; border: 1px solid #606266; }
.legend-dot.no-record { background: #fdf6ec; border: 1px solid #e6a23c; }
</style>

View File

@@ -56,6 +56,7 @@
<el-form-item prop="endTime">
<el-button type="primary" @click="getList">查询</el-button>
<el-button type="primary" @click="exportData">导出</el-button>
<el-button type="primary" @click="openCustomExport">自定义导出</el-button>
<el-button type="primary" @click="settingVisible = true">列设置</el-button>
<el-button type="primary" @click="saveReport">保存报表</el-button>
</el-form-item>
@@ -84,11 +85,45 @@
</el-radio-group>
<columns-setting :reportType="activeColumnConfig"></columns-setting>
</el-dialog>
<!-- 自定义导出列选择弹窗 -->
<el-dialog title="自定义导出 - 选择导出列" :visible.sync="customExportVisible" width="750px">
<div class="custom-export-toolbar">
<el-input v-model="columnSearch" placeholder="搜索列名" prefix-icon="el-icon-search" clearable size="small" style="width: 220px" />
<div class="custom-export-actions">
<el-button size="small" @click="selectAllColumns">全选</el-button>
<el-button size="small" @click="invertColumns">反选</el-button>
<el-button size="small" @click="selectedColumns = []">清空</el-button>
</div>
</div>
<div class="custom-export-body">
<el-checkbox-group v-model="selectedColumns">
<div v-for="(group, gName) in groupedColumns" :key="gName" class="column-group">
<div class="column-group-title">{{ gName }}</div>
<div class="column-group-items">
<el-checkbox
v-for="field in group"
:key="field.key"
:label="field.key"
:style="{ display: columnSearch && !filterMatch(field) ? 'none' : '' }"
>{{ field.label }}</el-checkbox>
</div>
</div>
</el-checkbox-group>
</div>
<div slot="footer" class="custom-export-footer">
<span class="selected-tip">已选 <b>{{ selectedColumns.length }}</b> / {{ flatColumns.length }} </span>
<el-button @click="customExportVisible = false">取消</el-button>
<el-button type="primary" @click="doCustomExport" :disabled="selectedColumns.length === 0">
导出选中列
</el-button>
</div>
</el-dialog>
</div>
</template>
<script>
import { listCoilWithIds } from "@/api/wms/coil";
import { listCoilWithIds, getExportColumns } from "@/api/wms/coil";
import {
listPendingAction,
} from '@/api/wms/pendingAction';
@@ -146,6 +181,20 @@ export default {
return {
activeColumnConfig: 'coil-report-receive',
settingVisible: false,
customExportVisible: false,
exportColumns: {},
selectedColumns: [],
columnSearch: '',
columnGroups: {
'基本信息': ['itemTypeDesc', 'warehouseName', 'actualWarehouseName', 'dataTypeText'],
'钢卷号': ['enterCoilNo', 'supplierCoilNo', 'currentCoilNo'],
'时间': ['createTime', 'exportTime', 'exportBy'],
'物理属性': ['netWeight', 'length', 'specification', 'actualThickness'],
'材质属性': ['material', 'manufacturer', 'surfaceTreatmentDesc', 'zincLayer', 'packingStatus', 'temperGrade', 'coatingType'],
'用途': ['purpose', 'businessPurpose'],
'状态': ['qualityStatus', 'statusDesc', 'isRelatedToOrderText'],
'其他': ['itemName', 'itemId', 'packagingRequirement', 'trimmingRequirement', 'transferType', 'saleName', 'remark', 'team'],
},
list: [],
defaultStartTime: startTime,
defaultEndTime: endTime,
@@ -221,6 +270,21 @@ export default {
coilIds() {
return this.list.map(item => item.coilId).join(',')
},
groupedColumns() {
const result = {}
Object.entries(this.columnGroups).forEach(([groupName, fieldKeys]) => {
const items = fieldKeys
.filter(key => this.exportColumns[key])
.map(key => ({ key, label: this.exportColumns[key] }))
if (items.length) {
result[groupName] = items
}
})
return result
},
flatColumns() {
return Object.values(this.groupedColumns).flat()
},
},
methods: {
@@ -282,6 +346,33 @@ export default {
coilIds: this.coilIds,
}, `materialCoil_${new Date().getTime()}.xlsx`)
},
// 打开自定义导出弹窗
openCustomExport() {
getExportColumns().then(res => {
this.exportColumns = res.data
this.selectedColumns = []
this.customExportVisible = true
})
},
// 执行自定义导出
doCustomExport() {
this.customExportVisible = false
this.download('wms/materialCoil/exportCustom', {
coilIds: this.coilIds,
columns: this.selectedColumns.join(','),
}, `materialCoil_${new Date().getTime()}.xlsx`)
},
filterMatch(field) {
const keyword = this.columnSearch.toLowerCase()
return !keyword || field.label.toLowerCase().includes(keyword) || field.key.toLowerCase().includes(keyword)
},
selectAllColumns() {
this.selectedColumns = this.flatColumns.map(f => f.key)
},
invertColumns() {
const allKeys = this.flatColumns.map(f => f.key)
this.selectedColumns = allKeys.filter(k => !this.selectedColumns.includes(k))
},
saveReport() {
this.loading = true
saveReportFile(this.coilIds, {
@@ -310,4 +401,53 @@ export default {
}
</script>
<style scoped></style>
<style scoped>
.custom-export-toolbar {
display: flex;
align-items: center;
justify-content: space-between;
margin-bottom: 16px;
padding-bottom: 12px;
border-bottom: 1px solid #ebeef5;
}
.custom-export-actions {
display: flex;
gap: 8px;
}
.custom-export-body {
max-height: 420px;
overflow-y: auto;
}
.column-group {
margin-bottom: 14px;
}
.column-group-title {
font-size: 13px;
font-weight: 600;
color: #606266;
margin-bottom: 8px;
padding-left: 2px;
border-left: 3px solid #409eff;
padding-left: 8px;
}
.column-group-items {
display: grid;
grid-template-columns: repeat(3, 1fr);
gap: 4px 12px;
}
.column-group-items .el-checkbox {
margin-right: 0;
}
.custom-export-footer {
display: flex;
align-items: center;
justify-content: space-between;
}
.selected-tip {
font-size: 13px;
color: #909399;
}
.selected-tip b {
color: #409eff;
}
</style>

View File

@@ -654,7 +654,7 @@ export default {
const [lossRes, outRes] = await Promise.all([
listCoilWithIds({ ...this.queryParams, actionIds: lossActionIds.join(',') || '', startTime: '', endTime: '', selectType: 'raw_material' }),
listCoilWithIds({ ...this.queryParams, coilIds: outIds.join(',') || '', startTime: '', endTime: '', selectType: 'product' }),
listCoilWithIds({ ...this.queryParams, coilIds: outIds.join(',') || '', startTime: '', endTime: '', byCreateTimeStart: this.queryParams.startTime, byCreateTimeEnd: this.queryParams.endTime, selectType: 'product' }),
]);
this.lossList = lossRes.rows.map(item => {
@@ -692,7 +692,7 @@ export default {
const [lossRes, outRes] = await Promise.all([
listCoilWithIds({ ...this.queryParams, actionIds: lossActionIds.join(',') || '', startTime: '', endTime: '', selectType: 'raw_material' }),
listCoilWithIds({ ...this.queryParams, coilIds: outIds.join(',') || '', startTime: '', endTime: '', selectType: 'product' }),
listCoilWithIds({ ...this.queryParams, coilIds: outIds.join(',') || '', startTime: '', endTime: '', byCreateTimeStart: this.queryParams.startTime, byCreateTimeEnd: this.queryParams.endTime, selectType: 'product' }),
]);
if (this.reportType === 'out') {

View File

@@ -9,6 +9,9 @@
<el-tab-pane label="质保书审批" name="fourth" v-hasPermi="['qc:certificate:approve']">
<CertificateBook />
</el-tab-pane>
<el-tab-pane label="设备送检记录" name="fifth" v-hasPermi="['mes:eqp:approval']">
<EquipmentInspectionApproval readonly :defaultStatus="1" />
</el-tab-pane>
<el-tab-pane label="其他代办" name="second">
<el-empty description="暂无其他代办事项" />
</el-tab-pane>
@@ -19,6 +22,7 @@
import TranferCoilTable from '@/views/wms/coil/views/base/tranfer.vue'
import InspectionTask from '@/views/mes/qc/inspection/task.vue'
import CertificateBook from '@/views/mes/qc/certificate/book.vue'
import EquipmentInspectionApproval from '@/views/mes/eqp/check/approval.vue'
export default {
name: 'TodoIndex',
@@ -26,6 +30,7 @@
TranferCoilTable,
InspectionTask,
CertificateBook,
EquipmentInspectionApproval,
},
data() {
return {

View File

@@ -134,9 +134,12 @@ public class WmsDeliveryWaybillDetailController extends BaseController {
WmsMaterialCoilBo bo,
PageQuery pageQuery,
@RequestParam(required = false) @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss") Date startTime,
@RequestParam(required = false) @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss") Date endTime) {
@RequestParam(required = false) @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss") Date endTime,
@RequestParam(required = false) Long planId) {
List<Long> boundCoilIds;
if (startTime != null || endTime != null) {
if (planId != null) {
boundCoilIds = iWmsDeliveryWaybillDetailService.getBoundCoilIdsByPlanId(planId, startTime, endTime);
} else if (startTime != null || endTime != null) {
boundCoilIds = iWmsDeliveryWaybillDetailService.getBoundCoilIdsByTimeRange(startTime, endTime);
} else {
boundCoilIds = iWmsDeliveryWaybillDetailService.getBoundCoilIds();
@@ -157,9 +160,12 @@ public class WmsDeliveryWaybillDetailController extends BaseController {
public R<java.util.Map<String, java.math.BigDecimal>> getStatistics(
WmsMaterialCoilBo bo,
@RequestParam(required = false) @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss") Date startTime,
@RequestParam(required = false) @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss") Date endTime) {
@RequestParam(required = false) @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss") Date endTime,
@RequestParam(required = false) Long planId) {
List<Long> boundCoilIds;
if (startTime != null || endTime != null) {
if (planId != null) {
boundCoilIds = iWmsDeliveryWaybillDetailService.getBoundCoilIdsByPlanId(planId, startTime, endTime);
} else if (startTime != null || endTime != null) {
boundCoilIds = iWmsDeliveryWaybillDetailService.getBoundCoilIdsByTimeRange(startTime, endTime);
} else {
boundCoilIds = iWmsDeliveryWaybillDetailService.getBoundCoilIds();

View File

@@ -5,6 +5,9 @@ import java.math.BigDecimal;
import java.util.List;
import java.util.Map;
import java.util.Arrays;
import java.util.Set;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.stream.Collectors;
import java.util.HashMap;
@@ -30,6 +33,7 @@ import com.klp.common.core.validate.AddGroup;
import com.klp.common.core.validate.EditGroup;
import com.klp.common.enums.BusinessType;
import com.klp.common.utils.poi.ExcelUtil;
import com.klp.common.utils.StringUtils;
import com.klp.domain.bo.WmsMaterialCoilBo;
import com.klp.domain.bo.WmsMaterialCoilReportSummaryBo;
import com.klp.domain.vo.dashboard.CoilTrimStatisticsVo;
@@ -130,6 +134,70 @@ public class WmsMaterialCoilController extends BaseController {
List<WmsMaterialCoilExportVo> list = iWmsMaterialCoilService.queryExportList(bo);
ExcelUtil.exportExcel(list, "钢卷物料表", WmsMaterialCoilExportVo.class, response);
}
/**
* 个性化导出:前端传入要导出的字段名,仅导出选中列
* columns 参数为逗号分隔的 Java 字段名,如 "itemTypeDesc,enterCoilNo,netWeight"
* 不传 columns 时等同于 /exportAll 导出全部字段
*/
@Log(title = "钢卷物料表", businessType = BusinessType.EXPORT)
@PostMapping("/exportCustom")
public void exportCustom(WmsMaterialCoilBo bo,
@RequestParam(required = false) String columns,
HttpServletResponse response) {
List<WmsMaterialCoilAllExportVo> list = iWmsMaterialCoilService.queryExportListAll(bo);
if (StringUtils.isNotBlank(columns)) {
Set<String> includeFields = new HashSet<>(Arrays.asList(columns.split(",")));
ExcelUtil.exportExcel(list, "钢卷物料表", WmsMaterialCoilAllExportVo.class, includeFields, response);
} else {
ExcelUtil.exportExcel(list, "钢卷物料表", WmsMaterialCoilAllExportVo.class, response);
}
}
/**
* 获取可导出的列元数据(供前端列选择器使用)
* 返回 { "fieldName": "中文列名" } 的映射,基于完整导出字段
*/
@GetMapping("/exportColumns")
public R<Map<String, String>> getExportColumns() {
Map<String, String> columns = new LinkedHashMap<>();
columns.put("itemTypeDesc", "类型");
columns.put("warehouseName", "逻辑库区");
columns.put("actualWarehouseName", "实际库区");
columns.put("enterCoilNo", "入场卷号");
columns.put("supplierCoilNo", "厂家卷号");
columns.put("currentCoilNo", "成品卷号");
columns.put("createTime", "日期");
columns.put("exportTime", "发货时间");
columns.put("exportBy", "发货人");
columns.put("netWeight", "重量");
columns.put("purpose", "用途");
columns.put("trimmingRequirement", "切边要求");
columns.put("packagingRequirement", "包装种类");
columns.put("qualityStatus", "产品质量");
columns.put("packingStatus", "原料材质");
columns.put("statusDesc", "库存状态");
columns.put("remark", "备注");
columns.put("itemName", "名称");
columns.put("length", "长度");
columns.put("specification", "规格");
columns.put("material", "材质");
columns.put("manufacturer", "厂家");
columns.put("surfaceTreatmentDesc", "表面处理");
columns.put("zincLayer", "锌层");
columns.put("itemId", "物品ID");
columns.put("dataTypeText", "数据类型");
columns.put("temperGrade", "调制度");
columns.put("coatingType", "镀层种类");
columns.put("businessPurpose", "业务用途");
columns.put("isRelatedToOrderText", "是否与订单相关");
columns.put("saleName", "销售人员");
columns.put("actualThickness", "实测厚度");
columns.put("transferType", "调拨类型");
columns.put("team", "班组");
return R.ok(columns);
}
/**
* 导出钢卷物料表列表(完整字段版本)
* 导出全部字段

View File

@@ -78,6 +78,7 @@ public class WmsMaterialCoilAllExportVo {
private BigDecimal netWeight;
// 班组
@ExcelProperty(value = "班组")
private String team;
/**

View File

@@ -73,4 +73,9 @@ public interface IWmsDeliveryWaybillDetailService {
* 根据负责人(principal)查询已绑定的钢卷ID列表
*/
List<Long> getBoundCoilIdsByPrincipal(String principal);
/**
* 根据发货计划ID查询已绑定的钢卷ID列表可结合时间段筛选
*/
List<Long> getBoundCoilIdsByPlanId(Long planId, Date startTime, Date endTime);
}

View File

@@ -338,6 +338,33 @@ public class WmsDeliveryWaybillDetailServiceImpl implements IWmsDeliveryWaybillD
return list.stream().map(WmsDeliveryWaybillDetail::getCoilId).distinct().collect(Collectors.toList());
}
@Override
public List<Long> getBoundCoilIdsByPlanId(Long planId, Date startTime, Date endTime) {
LambdaQueryWrapper<WmsDeliveryWaybill> waybillWrapper = Wrappers.lambdaQuery();
waybillWrapper.eq(WmsDeliveryWaybill::getPlanId, planId);
waybillWrapper.eq(WmsDeliveryWaybill::getDelFlag, 0);
if (startTime != null) {
waybillWrapper.ge(WmsDeliveryWaybill::getDeliveryTime, startTime);
}
if (endTime != null) {
waybillWrapper.le(WmsDeliveryWaybill::getDeliveryTime, endTime);
}
List<WmsDeliveryWaybill> waybills = wmsDeliveryWaybillMapper.selectList(waybillWrapper);
if (waybills == null || waybills.isEmpty()) {
return Collections.emptyList();
}
List<Long> waybillIds = waybills.stream().map(WmsDeliveryWaybill::getWaybillId).collect(Collectors.toList());
LambdaQueryWrapper<WmsDeliveryWaybillDetail> detailWrapper = Wrappers.lambdaQuery();
detailWrapper.in(WmsDeliveryWaybillDetail::getWaybillId, waybillIds);
detailWrapper.isNotNull(WmsDeliveryWaybillDetail::getCoilId);
detailWrapper.eq(WmsDeliveryWaybillDetail::getDelFlag, 0);
detailWrapper.select(WmsDeliveryWaybillDetail::getCoilId);
List<WmsDeliveryWaybillDetail> list = baseMapper.selectList(detailWrapper);
return list.stream().map(WmsDeliveryWaybillDetail::getCoilId).distinct().collect(Collectors.toList());
}
@Override
public List<Long> getBoundCoilIdsByPrincipal(String principal) {
if (principal == null || principal.trim().isEmpty()) {

View File

@@ -630,6 +630,8 @@ public class WmsMaterialCoilServiceImpl implements IWmsMaterialCoilService {
qw.eq(StringUtils.isNotBlank(bo.getBusinessPurpose()), "mc.business_purpose", bo.getBusinessPurpose());
// 是否与订单相关0=否1=是)
qw.eq(bo.getIsRelatedToOrder() != null, "mc.is_related_to_order", bo.getIsRelatedToOrder());
// 加上remark的模糊匹配
qw.like(StringUtils.isNotBlank(bo.getRemark()), "mc.remark", bo.getRemark());
//逻辑删除
qw.eq("mc.del_flag", 0);
// 统一处理 warehouseId 与 warehouseIds