Merge remote-tracking branch 'origin/0.8.X' into 0.8.X
This commit is contained in:
@@ -25,8 +25,12 @@ import com.klp.crm.domain.vo.CrmOrderItemVo;
|
||||
import com.klp.crm.domain.vo.CrmOrderItemExportVo;
|
||||
import com.klp.crm.domain.vo.CrmContractOrderFinanceVo;
|
||||
import com.klp.crm.domain.bo.CrmOrderItemBo;
|
||||
import com.klp.crm.domain.bo.OrderCoilQueryBo;
|
||||
import com.klp.crm.domain.bo.SalesmanCoilQueryBo;
|
||||
import com.klp.crm.domain.bo.ContractCoilQueryBo;
|
||||
import com.klp.crm.service.ICrmOrderItemService;
|
||||
import com.klp.domain.vo.WmsMaterialCoilVo;
|
||||
import com.klp.domain.bo.WmsMaterialCoilBo;
|
||||
import com.klp.common.core.page.TableDataInfo;
|
||||
|
||||
/**
|
||||
@@ -134,19 +138,63 @@ public class CrmOrderItemController extends BaseController {
|
||||
@PathVariable Long contractId) {
|
||||
return R.ok(iCrmOrderItemService.queryFinanceAndObjectionByContractId(contractId));
|
||||
}
|
||||
// 根据订单id查询发货单据中的配卷(分页)。
|
||||
@GetMapping("/coils/order/{orderId}")
|
||||
public TableDataInfo<WmsMaterialCoilVo> getCoilsByOrderId(@NotNull(message = "订单ID不能为空")
|
||||
@PathVariable Long orderId,
|
||||
PageQuery pageQuery) {
|
||||
return iCrmOrderItemService.queryCoilsByOrderId(orderId, pageQuery);
|
||||
// ==================== 钢卷查询(统一使用 POST + RequestBody BO) ====================
|
||||
|
||||
/**
|
||||
* 根据订单ID分页查询发货配卷,支持钢卷信息筛选
|
||||
*/
|
||||
@PostMapping("/coils/order/query")
|
||||
public TableDataInfo<WmsMaterialCoilVo> getCoilsByOrderId(@RequestBody @Validated OrderCoilQueryBo bo) {
|
||||
return iCrmOrderItemService.queryCoilsByOrderId(bo.getOrderId(), toPageQuery(bo), bo.getCoilBo());
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据销售员分页查询生产成果,支持钢卷信息筛选
|
||||
*/
|
||||
@PostMapping("/coils/salesman/query")
|
||||
public TableDataInfo<WmsMaterialCoilVo> getCoilsBySalesman(@RequestBody @Validated SalesmanCoilQueryBo bo) {
|
||||
return iCrmOrderItemService.queryCoilsBySalesman(bo.getSalesman(), toPageQuery(bo), bo.getCoilBo());
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据合同ID分页查询生产成果,支持钢卷信息筛选
|
||||
*/
|
||||
@PostMapping("/coils/contract/query")
|
||||
public TableDataInfo<WmsMaterialCoilVo> getCoilsByContractIdPaginated(@RequestBody @Validated ContractCoilQueryBo bo) {
|
||||
return iCrmOrderItemService.queryCoilsByContractIdPaginated(bo.getOrderId(), toPageQuery(bo), bo.getCoilBo());
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据订单ID统计配卷汇总,支持钢卷信息筛选
|
||||
*/
|
||||
@PostMapping("/coils/order/statistics")
|
||||
public R<java.util.Map<String, java.math.BigDecimal>> getCoilsByOrderIdStatistics(@RequestBody @Validated OrderCoilQueryBo bo) {
|
||||
return R.ok(iCrmOrderItemService.queryCoilsByOrderIdStatistics(bo.getOrderId(), bo.getCoilBo()));
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据销售员统计生产成果汇总,支持钢卷信息筛选
|
||||
*/
|
||||
@PostMapping("/coils/salesman/statistics")
|
||||
public R<java.util.Map<String, java.math.BigDecimal>> getCoilsBySalesmanStatistics(@RequestBody @Validated SalesmanCoilQueryBo bo) {
|
||||
return R.ok(iCrmOrderItemService.queryCoilsBySalesmanStatistics(bo.getSalesman(), bo.getCoilBo()));
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据合同ID统计生产成果汇总,支持钢卷信息筛选
|
||||
*/
|
||||
@PostMapping("/coils/contract/statistics")
|
||||
public R<java.util.Map<String, java.math.BigDecimal>> getCoilsByContractIdStatistics(@RequestBody @Validated ContractCoilQueryBo bo) {
|
||||
return R.ok(iCrmOrderItemService.queryCoilsByContractIdStatistics(bo.getOrderId(), bo.getCoilBo()));
|
||||
}
|
||||
|
||||
// ==================== 已有接口 ====================
|
||||
|
||||
// 根据合同id查询发货单据中的配卷。
|
||||
@GetMapping("/coils/contract/{contractId}")
|
||||
public R<List<WmsMaterialCoilVo>> getCoilsByContractId(@NotNull(message = "合同ID不能为空")
|
||||
@PathVariable Long contractId) {
|
||||
return R.ok(iCrmOrderItemService.queryCoilsByContractId(contractId));
|
||||
@PathVariable Long orderId) {
|
||||
return R.ok(iCrmOrderItemService.queryCoilsByContractId(orderId));
|
||||
}
|
||||
|
||||
// 根据客户id查询该客户下属订单关联的异议和财务信息。
|
||||
@@ -163,38 +211,16 @@ public class CrmOrderItemController extends BaseController {
|
||||
return R.ok(iCrmOrderItemService.queryCoilsByCustomerId(customerId));
|
||||
}
|
||||
|
||||
// 根据销售员查询生产成果(钢卷列表),分页
|
||||
@GetMapping("/coils/bySalesman")
|
||||
public TableDataInfo<WmsMaterialCoilVo> getCoilsBySalesman(@RequestParam String salesman,
|
||||
PageQuery pageQuery) {
|
||||
return iCrmOrderItemService.queryCoilsBySalesman(salesman, pageQuery);
|
||||
}
|
||||
// ==================== 私有辅助方法 ====================
|
||||
|
||||
// 根据合同ID查询生产成果(钢卷列表),分页
|
||||
@GetMapping("/coils/byContract/{contractId}")
|
||||
public TableDataInfo<WmsMaterialCoilVo> getCoilsByContractIdPaginated(@NotNull(message = "合同ID不能为空")
|
||||
@PathVariable Long contractId,
|
||||
PageQuery pageQuery) {
|
||||
return iCrmOrderItemService.queryCoilsByContractIdPaginated(contractId, pageQuery);
|
||||
}
|
||||
private PageQuery toPageQuery(OrderCoilQueryBo bo) { return buildPageQuery(bo.getPageNum(), bo.getPageSize()); }
|
||||
private PageQuery toPageQuery(SalesmanCoilQueryBo bo) { return buildPageQuery(bo.getPageNum(), bo.getPageSize()); }
|
||||
private PageQuery toPageQuery(ContractCoilQueryBo bo) { return buildPageQuery(bo.getPageNum(), bo.getPageSize()); }
|
||||
|
||||
// 根据订单ID统计配卷汇总
|
||||
@GetMapping("/coils/order/{orderId}/statistics")
|
||||
public R<java.util.Map<String, java.math.BigDecimal>> getCoilsByOrderIdStatistics(@NotNull(message = "订单ID不能为空")
|
||||
@PathVariable Long orderId) {
|
||||
return R.ok(iCrmOrderItemService.queryCoilsByOrderIdStatistics(orderId));
|
||||
}
|
||||
|
||||
// 根据销售员统计生产成果汇总
|
||||
@GetMapping("/coils/bySalesman/statistics")
|
||||
public R<java.util.Map<String, java.math.BigDecimal>> getCoilsBySalesmanStatistics(@RequestParam String salesman) {
|
||||
return R.ok(iCrmOrderItemService.queryCoilsBySalesmanStatistics(salesman));
|
||||
}
|
||||
|
||||
// 根据合同ID统计生产成果汇总
|
||||
@GetMapping("/coils/byContract/{contractId}/statistics")
|
||||
public R<java.util.Map<String, java.math.BigDecimal>> getCoilsByContractIdStatistics(@NotNull(message = "合同ID不能为空")
|
||||
@PathVariable Long contractId) {
|
||||
return R.ok(iCrmOrderItemService.queryCoilsByContractIdStatistics(contractId));
|
||||
private PageQuery buildPageQuery(Integer pageNum, Integer pageSize) {
|
||||
PageQuery pageQuery = new PageQuery();
|
||||
pageQuery.setPageNum(pageNum);
|
||||
pageQuery.setPageSize(pageSize);
|
||||
return pageQuery;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,31 @@
|
||||
package com.klp.crm.domain.bo;
|
||||
|
||||
import com.klp.domain.bo.WmsMaterialCoilBo;
|
||||
import lombok.Data;
|
||||
import javax.validation.constraints.NotNull;
|
||||
import java.io.Serializable;
|
||||
|
||||
/**
|
||||
* 合同钢卷查询请求对象
|
||||
*
|
||||
* @author klp
|
||||
* @date 2026-07-09
|
||||
*/
|
||||
@Data
|
||||
public class ContractCoilQueryBo implements Serializable {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
/** 合同ID(必填) */
|
||||
@NotNull(message = "合同ID不能为空")
|
||||
private Long orderId;
|
||||
|
||||
/** 分页页码 */
|
||||
private Integer pageNum;
|
||||
|
||||
/** 分页大小 */
|
||||
private Integer pageSize;
|
||||
|
||||
/** 钢卷筛选条件 */
|
||||
private WmsMaterialCoilBo coilBo;
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
package com.klp.crm.domain.bo;
|
||||
|
||||
import com.klp.domain.bo.WmsMaterialCoilBo;
|
||||
import lombok.Data;
|
||||
import javax.validation.constraints.NotNull;
|
||||
import java.io.Serializable;
|
||||
|
||||
/**
|
||||
* 订单钢卷查询请求对象
|
||||
*
|
||||
* @author klp
|
||||
* @date 2026-07-09
|
||||
*/
|
||||
@Data
|
||||
public class OrderCoilQueryBo implements Serializable {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
/** 订单ID(必填) */
|
||||
@NotNull(message = "订单ID不能为空")
|
||||
private Long orderId;
|
||||
|
||||
/** 分页页码 */
|
||||
private Integer pageNum;
|
||||
|
||||
/** 分页大小 */
|
||||
private Integer pageSize;
|
||||
|
||||
/** 钢卷筛选条件 */
|
||||
private WmsMaterialCoilBo coilBo;
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
package com.klp.crm.domain.bo;
|
||||
|
||||
import com.klp.domain.bo.WmsMaterialCoilBo;
|
||||
import lombok.Data;
|
||||
import javax.validation.constraints.NotBlank;
|
||||
import java.io.Serializable;
|
||||
|
||||
/**
|
||||
* 销售员钢卷查询请求对象
|
||||
*
|
||||
* @author klp
|
||||
* @date 2026-07-09
|
||||
*/
|
||||
@Data
|
||||
public class SalesmanCoilQueryBo implements Serializable {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
/** 销售员(必填) */
|
||||
@NotBlank(message = "销售员不能为空")
|
||||
private String salesman;
|
||||
|
||||
/** 分页页码 */
|
||||
private Integer pageNum;
|
||||
|
||||
/** 分页大小 */
|
||||
private Integer pageSize;
|
||||
|
||||
/** 钢卷筛选条件 */
|
||||
private WmsMaterialCoilBo coilBo;
|
||||
}
|
||||
@@ -11,6 +11,7 @@ import java.util.List;
|
||||
|
||||
import com.klp.crm.domain.vo.CrmContractOrderFinanceVo;
|
||||
import com.klp.domain.vo.WmsMaterialCoilVo;
|
||||
import com.klp.domain.bo.WmsMaterialCoilBo;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
@@ -59,12 +60,12 @@ public interface ICrmOrderItemService {
|
||||
/**
|
||||
* 根据订单ID查询发货单配卷(分页)
|
||||
*/
|
||||
TableDataInfo<WmsMaterialCoilVo> queryCoilsByOrderId(Long orderId, PageQuery pageQuery);
|
||||
TableDataInfo<WmsMaterialCoilVo> queryCoilsByOrderId(Long orderId, PageQuery pageQuery, WmsMaterialCoilBo coilBo);
|
||||
|
||||
/**
|
||||
* 根据合同ID查询发货单配卷
|
||||
*/
|
||||
List<WmsMaterialCoilVo> queryCoilsByContractId(Long contractId);
|
||||
List<WmsMaterialCoilVo> queryCoilsByContractId(Long orderId);
|
||||
|
||||
/**
|
||||
* 根据客户ID查询下属订单的异议和财务信息
|
||||
@@ -79,25 +80,25 @@ public interface ICrmOrderItemService {
|
||||
/**
|
||||
* 根据销售员查询生产成果(钢卷列表)分页
|
||||
*/
|
||||
TableDataInfo<WmsMaterialCoilVo> queryCoilsBySalesman(String salesman, PageQuery pageQuery);
|
||||
TableDataInfo<WmsMaterialCoilVo> queryCoilsBySalesman(String salesman, PageQuery pageQuery, WmsMaterialCoilBo coilBo);
|
||||
|
||||
/**
|
||||
* 根据合同ID查询生产成果(钢卷列表)分页
|
||||
*/
|
||||
TableDataInfo<WmsMaterialCoilVo> queryCoilsByContractIdPaginated(Long contractId, PageQuery pageQuery);
|
||||
TableDataInfo<WmsMaterialCoilVo> queryCoilsByContractIdPaginated(Long orderId, PageQuery pageQuery, WmsMaterialCoilBo coilBo);
|
||||
|
||||
/**
|
||||
* 根据订单ID统计配卷汇总数据(数量、毛重、净重)
|
||||
*/
|
||||
java.util.Map<String, java.math.BigDecimal> queryCoilsByOrderIdStatistics(Long orderId);
|
||||
java.util.Map<String, java.math.BigDecimal> queryCoilsByOrderIdStatistics(Long orderId, WmsMaterialCoilBo coilBo);
|
||||
|
||||
/**
|
||||
* 根据销售员统计生产成果汇总数据(数量、毛重、净重)
|
||||
*/
|
||||
java.util.Map<String, java.math.BigDecimal> queryCoilsBySalesmanStatistics(String salesman);
|
||||
java.util.Map<String, java.math.BigDecimal> queryCoilsBySalesmanStatistics(String salesman, WmsMaterialCoilBo coilBo);
|
||||
|
||||
/**
|
||||
* 根据合同ID统计生产成果汇总数据(数量、毛重、净重)
|
||||
*/
|
||||
java.util.Map<String, java.math.BigDecimal> queryCoilsByContractIdStatistics(Long contractId);
|
||||
java.util.Map<String, java.math.BigDecimal> queryCoilsByContractIdStatistics(Long orderId, WmsMaterialCoilBo coilBo);
|
||||
}
|
||||
|
||||
@@ -260,21 +260,21 @@ public class CrmOrderItemServiceImpl implements ICrmOrderItemService {
|
||||
}
|
||||
|
||||
@Override
|
||||
public CrmContractOrderFinanceVo queryFinanceAndObjectionByContractId(Long contractId) {
|
||||
public CrmContractOrderFinanceVo queryFinanceAndObjectionByContractId(Long orderId) {
|
||||
CrmContractOrderFinanceVo result = new CrmContractOrderFinanceVo();
|
||||
|
||||
LambdaQueryWrapper<CrmSalesObjection> objectionWrapper = new LambdaQueryWrapper<>();
|
||||
objectionWrapper.in(CrmSalesObjection::getOrderId, contractId);
|
||||
objectionWrapper.in(CrmSalesObjection::getOrderId, orderId);
|
||||
List<CrmSalesObjectionVo> objectionList = crmSalesObjectionMapper.selectVoList(objectionWrapper);
|
||||
result.setObjectionList(objectionList);
|
||||
|
||||
LambdaQueryWrapper<com.klp.domain.WmsReceivable> receivableWrapper = new LambdaQueryWrapper<>();
|
||||
receivableWrapper.in(WmsReceivable::getOrderId, contractId);
|
||||
receivableWrapper.in(WmsReceivable::getOrderId, orderId);
|
||||
List<WmsReceivableVo> receivableList = wmsReceivableMapper.selectVoList(receivableWrapper);
|
||||
result.setReceivableList(receivableList);
|
||||
|
||||
LambdaQueryWrapper<WmsDeliveryWaybill> waybillWrapper = new LambdaQueryWrapper<>();
|
||||
waybillWrapper.in(WmsDeliveryWaybill::getOrderId, contractId);
|
||||
waybillWrapper.in(WmsDeliveryWaybill::getOrderId, orderId);
|
||||
List<WmsDeliveryWaybillVo> wmsDeliveryWaybills = wmsDeliveryWaybillMapper.selectVoList(waybillWrapper);
|
||||
result.setWmsDeliveryWaybills(wmsDeliveryWaybills);
|
||||
|
||||
@@ -345,9 +345,9 @@ public class CrmOrderItemServiceImpl implements ICrmOrderItemService {
|
||||
* 根据合同ID解析关联的线圈ID串(通过crm_order→wms_coil_contract_rel)
|
||||
* @return 逗号分隔的coilId字符串,无数据返回null
|
||||
*/
|
||||
private String resolveCoilIdsByContractId(Long contractId) {
|
||||
private String resolveCoilIdsByContractId(Long orderId) {
|
||||
LambdaQueryWrapper<WmsCoilContractRel> relWrapper = new LambdaQueryWrapper<>();
|
||||
relWrapper.eq(WmsCoilContractRel::getContractId, contractId);
|
||||
relWrapper.eq(WmsCoilContractRel::getContractId, orderId);
|
||||
relWrapper.eq(WmsCoilContractRel::getDelFlag, 0);
|
||||
List<WmsCoilContractRel> rels = coilContractRelMapper.selectList(relWrapper);
|
||||
if (rels == null || rels.isEmpty()) {
|
||||
@@ -363,34 +363,40 @@ public class CrmOrderItemServiceImpl implements ICrmOrderItemService {
|
||||
// ==================== 分页列表查询 ====================
|
||||
|
||||
@Override
|
||||
public TableDataInfo<WmsMaterialCoilVo> queryCoilsByOrderId(Long orderId, PageQuery pageQuery) {
|
||||
public TableDataInfo<WmsMaterialCoilVo> queryCoilsByOrderId(Long orderId, PageQuery pageQuery, WmsMaterialCoilBo coilBo) {
|
||||
String coilIds = resolveCoilIdsByOrderId(orderId);
|
||||
if (coilIds == null) {
|
||||
return new TableDataInfo<>();
|
||||
}
|
||||
WmsMaterialCoilBo coilBo = new WmsMaterialCoilBo();
|
||||
if (coilBo == null) {
|
||||
coilBo = new WmsMaterialCoilBo();
|
||||
}
|
||||
coilBo.setCoilIds(coilIds);
|
||||
return iWmsMaterialCoilService.queryPageList(coilBo, pageQuery);
|
||||
}
|
||||
|
||||
@Override
|
||||
public TableDataInfo<WmsMaterialCoilVo> queryCoilsBySalesman(String salesman, PageQuery pageQuery) {
|
||||
public TableDataInfo<WmsMaterialCoilVo> queryCoilsBySalesman(String salesman, PageQuery pageQuery, WmsMaterialCoilBo coilBo) {
|
||||
String coilIds = resolveCoilIdsBySalesman(salesman);
|
||||
if (coilIds == null) {
|
||||
return new TableDataInfo<>();
|
||||
}
|
||||
WmsMaterialCoilBo coilBo = new WmsMaterialCoilBo();
|
||||
if (coilBo == null) {
|
||||
coilBo = new WmsMaterialCoilBo();
|
||||
}
|
||||
coilBo.setCoilIds(coilIds);
|
||||
return iWmsMaterialCoilService.queryPageList(coilBo, pageQuery);
|
||||
}
|
||||
|
||||
@Override
|
||||
public TableDataInfo<WmsMaterialCoilVo> queryCoilsByContractIdPaginated(Long contractId, PageQuery pageQuery) {
|
||||
public TableDataInfo<WmsMaterialCoilVo> queryCoilsByContractIdPaginated(Long contractId, PageQuery pageQuery, WmsMaterialCoilBo coilBo) {
|
||||
String coilIds = resolveCoilIdsByContractId(contractId);
|
||||
if (coilIds == null) {
|
||||
return new TableDataInfo<>();
|
||||
}
|
||||
WmsMaterialCoilBo coilBo = new WmsMaterialCoilBo();
|
||||
if (coilBo == null) {
|
||||
coilBo = new WmsMaterialCoilBo();
|
||||
}
|
||||
coilBo.setCoilIds(coilIds);
|
||||
return iWmsMaterialCoilService.queryPageList(coilBo, pageQuery);
|
||||
}
|
||||
@@ -398,34 +404,40 @@ public class CrmOrderItemServiceImpl implements ICrmOrderItemService {
|
||||
// ==================== 统计汇总查询 ====================
|
||||
|
||||
@Override
|
||||
public Map<String, java.math.BigDecimal> queryCoilsByOrderIdStatistics(Long orderId) {
|
||||
public Map<String, java.math.BigDecimal> queryCoilsByOrderIdStatistics(Long orderId, WmsMaterialCoilBo coilBo) {
|
||||
String coilIds = resolveCoilIdsByOrderId(orderId);
|
||||
if (coilIds == null) {
|
||||
return emptyStatistics();
|
||||
}
|
||||
WmsMaterialCoilBo coilBo = new WmsMaterialCoilBo();
|
||||
if (coilBo == null) {
|
||||
coilBo = new WmsMaterialCoilBo();
|
||||
}
|
||||
coilBo.setCoilIds(coilIds);
|
||||
return iWmsMaterialCoilService.getStatistics(coilBo);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<String, java.math.BigDecimal> queryCoilsBySalesmanStatistics(String salesman) {
|
||||
public Map<String, java.math.BigDecimal> queryCoilsBySalesmanStatistics(String salesman, WmsMaterialCoilBo coilBo) {
|
||||
String coilIds = resolveCoilIdsBySalesman(salesman);
|
||||
if (coilIds == null) {
|
||||
return emptyStatistics();
|
||||
}
|
||||
WmsMaterialCoilBo coilBo = new WmsMaterialCoilBo();
|
||||
if (coilBo == null) {
|
||||
coilBo = new WmsMaterialCoilBo();
|
||||
}
|
||||
coilBo.setCoilIds(coilIds);
|
||||
return iWmsMaterialCoilService.getStatistics(coilBo);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<String, java.math.BigDecimal> queryCoilsByContractIdStatistics(Long contractId) {
|
||||
String coilIds = resolveCoilIdsByContractId(contractId);
|
||||
public Map<String, java.math.BigDecimal> queryCoilsByContractIdStatistics(Long orderId, WmsMaterialCoilBo coilBo) {
|
||||
String coilIds = resolveCoilIdsByContractId(orderId);
|
||||
if (coilIds == null) {
|
||||
return emptyStatistics();
|
||||
}
|
||||
WmsMaterialCoilBo coilBo = new WmsMaterialCoilBo();
|
||||
if (coilBo == null) {
|
||||
coilBo = new WmsMaterialCoilBo();
|
||||
}
|
||||
coilBo.setCoilIds(coilIds);
|
||||
return iWmsMaterialCoilService.getStatistics(coilBo);
|
||||
}
|
||||
@@ -433,10 +445,10 @@ public class CrmOrderItemServiceImpl implements ICrmOrderItemService {
|
||||
// ==================== 已有方法(非分页) ====================
|
||||
|
||||
@Override
|
||||
public List<WmsMaterialCoilVo> queryCoilsByContractId(Long contractId) {
|
||||
public List<WmsMaterialCoilVo> queryCoilsByContractId(Long orderId) {
|
||||
|
||||
LambdaQueryWrapper<WmsDeliveryWaybill> waybillWrapper = new LambdaQueryWrapper<>();
|
||||
waybillWrapper.eq(WmsDeliveryWaybill::getOrderId, contractId);
|
||||
waybillWrapper.eq(WmsDeliveryWaybill::getOrderId, orderId);
|
||||
List<WmsDeliveryWaybill> waybills = wmsDeliveryWaybillMapper.selectList(waybillWrapper);
|
||||
if (waybills == null || waybills.isEmpty()) {
|
||||
return Collections.emptyList();
|
||||
|
||||
@@ -24,6 +24,7 @@ import com.klp.flow.domain.bo.SchProdScheduleItemMergeBo;
|
||||
import com.klp.flow.domain.bo.SchProdScheduleItemReceiveBo;
|
||||
import com.klp.flow.domain.bo.SchProdScheduleItemMergeValidateBo;
|
||||
import com.klp.flow.domain.bo.SchProdScheduleItemSortBo;
|
||||
import com.klp.flow.domain.bo.SchProdScheduleItemDispatchBo;
|
||||
import com.klp.flow.service.ISchProdScheduleItemService;
|
||||
import com.klp.common.core.page.TableDataInfo;
|
||||
|
||||
@@ -132,6 +133,18 @@ public class SchProdScheduleItemController extends BaseController {
|
||||
return toAjax(iSchProdScheduleItemService.receiveByBo(receiveBo));
|
||||
}
|
||||
|
||||
/**
|
||||
* 明细下发:逐条从 sch_prod_schedule_detail 下发到 sch_prod_schedule_item
|
||||
* 生产日期使用明细的 scheduleDate,支持一条一条明细独立下发
|
||||
* 若已下发过(同明细+同工序),先删除旧记录再重新插入
|
||||
*/
|
||||
@Log(title = "排产单主加明细可合并", businessType = BusinessType.INSERT)
|
||||
@RepeatSubmit()
|
||||
@PostMapping("/receiveFromDetail")
|
||||
public R<Void> receiveFromDetail(@Validated @RequestBody SchProdScheduleItemDispatchBo dispatchBo) {
|
||||
return toAjax(iSchProdScheduleItemService.receiveFromDetail(dispatchBo));
|
||||
}
|
||||
|
||||
/**
|
||||
* 合并校验:检查待合并记录来源产需单的 header 字段是否一致
|
||||
*/
|
||||
|
||||
@@ -6,6 +6,7 @@ import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.util.Date;
|
||||
|
||||
/**
|
||||
* 排产单明细对象 sch_prod_schedule_detail
|
||||
@@ -37,6 +38,10 @@ public class SchProdScheduleDetail extends BaseEntity {
|
||||
* 关联工序主表ID(sch_prod_process.process_id),表示该明细使用的工序配置
|
||||
*/
|
||||
private Long processId;
|
||||
/**
|
||||
* 排产日期
|
||||
*/
|
||||
private Date scheduleDate;
|
||||
/**
|
||||
* 规格 例:1.0X1250
|
||||
*/
|
||||
@@ -57,6 +62,10 @@ public class SchProdScheduleDetail extends BaseEntity {
|
||||
* 单行排产备注
|
||||
*/
|
||||
private String remark;
|
||||
/**
|
||||
* 是否下发(0=未下发, 1=已下发)
|
||||
*/
|
||||
private Long isIssued;
|
||||
/**
|
||||
* 删除标识 0正常 2删除
|
||||
*/
|
||||
|
||||
@@ -6,6 +6,7 @@ import lombok.EqualsAndHashCode;
|
||||
import javax.validation.constraints.*;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.util.Date;
|
||||
|
||||
/**
|
||||
* 排产单明细业务对象 sch_prod_schedule_detail
|
||||
@@ -38,6 +39,11 @@ public class SchProdScheduleDetailBo extends BaseEntity {
|
||||
*/
|
||||
private Long processId;
|
||||
|
||||
/**
|
||||
* 排产日期
|
||||
*/
|
||||
private Date scheduleDate;
|
||||
|
||||
/**
|
||||
* 规格 例:1.0X1250
|
||||
*/
|
||||
@@ -63,5 +69,9 @@ public class SchProdScheduleDetailBo extends BaseEntity {
|
||||
*/
|
||||
private String remark;
|
||||
|
||||
/**
|
||||
* 是否下发(0=未下发, 1=已下发)
|
||||
*/
|
||||
private Long isIssued;
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,69 @@
|
||||
package com.klp.flow.domain.bo;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
import javax.validation.constraints.NotEmpty;
|
||||
import javax.validation.constraints.NotNull;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 排产明细下发请求对象 —— 从 sch_prod_schedule_detail 逐条下发到 sch_prod_schedule_item
|
||||
*
|
||||
* @author klp
|
||||
* @date 2026-07-10
|
||||
*/
|
||||
@Data
|
||||
public class SchProdScheduleItemDispatchBo {
|
||||
|
||||
/**
|
||||
* 明细下发配置列表(每条明细独立下发,各自携带工序步骤)
|
||||
*/
|
||||
@NotEmpty(message = "明细下发配置不能为空")
|
||||
private List<DetailDispatchConfig> detailList;
|
||||
|
||||
/**
|
||||
* 单条明细的下发配置
|
||||
*/
|
||||
@Data
|
||||
public static class DetailDispatchConfig {
|
||||
/**
|
||||
* 排产明细ID(sch_prod_schedule_detail.schedule_detail_id)
|
||||
*/
|
||||
@NotNull(message = "明细ID不能为空")
|
||||
private Long scheduleDetailId;
|
||||
|
||||
/**
|
||||
* 工序ID(sch_prod_process.process_id),用于区分下发后按工序分组
|
||||
*/
|
||||
@NotNull(message = "工序ID不能为空")
|
||||
private Long processId;
|
||||
|
||||
/**
|
||||
* 工序步骤列表
|
||||
*/
|
||||
@NotEmpty(message = "工序步骤不能为空")
|
||||
private List<StepConfig> stepList;
|
||||
}
|
||||
|
||||
/**
|
||||
* 工序步骤配置
|
||||
*/
|
||||
@Data
|
||||
public static class StepConfig {
|
||||
/**
|
||||
* 步骤ID(sch_prod_process_step.step_id,下发后存为 actionId)
|
||||
*/
|
||||
@NotNull(message = "步骤ID不能为空")
|
||||
private Long stepId;
|
||||
|
||||
/**
|
||||
* 步骤顺序号
|
||||
*/
|
||||
private Long stepOrder;
|
||||
|
||||
/**
|
||||
* 步骤名称
|
||||
*/
|
||||
private String stepName;
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,7 @@
|
||||
package com.klp.flow.domain.vo;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.util.Date;
|
||||
import com.alibaba.excel.annotation.ExcelIgnoreUnannotated;
|
||||
import com.alibaba.excel.annotation.ExcelProperty;
|
||||
import com.baomidou.mybatisplus.annotation.TableField;
|
||||
@@ -46,6 +47,12 @@ public class SchProdScheduleDetailVo {
|
||||
@ExcelProperty(value = "关联工序主表ID")
|
||||
private Long processId;
|
||||
|
||||
/**
|
||||
* 排产日期
|
||||
*/
|
||||
@ExcelProperty(value = "排产日期")
|
||||
private Date scheduleDate;
|
||||
|
||||
/**
|
||||
* 规格 例:1.0X1250
|
||||
*/
|
||||
@@ -76,6 +83,13 @@ public class SchProdScheduleDetailVo {
|
||||
@ExcelProperty(value = "单行排产备注")
|
||||
private String remark;
|
||||
|
||||
/**
|
||||
* 是否下发(0=未下发, 1=已下发)
|
||||
*/
|
||||
@ExcelProperty(value = "是否下发", converter = ExcelDictConvert.class)
|
||||
@ExcelDictFormat(readConverterExp = "0=未下发,1=已下发")
|
||||
private Long isIssued;
|
||||
|
||||
/**
|
||||
* 关联的排产单主表信息
|
||||
*/
|
||||
|
||||
@@ -8,6 +8,7 @@ import com.klp.flow.domain.bo.SchProdScheduleItemMergeBo;
|
||||
import com.klp.flow.domain.bo.SchProdScheduleItemReceiveBo;
|
||||
import com.klp.flow.domain.bo.SchProdScheduleItemMergeValidateBo;
|
||||
import com.klp.flow.domain.bo.SchProdScheduleItemSortBo;
|
||||
import com.klp.flow.domain.bo.SchProdScheduleItemDispatchBo;
|
||||
import com.klp.common.core.page.TableDataInfo;
|
||||
import com.klp.common.core.domain.PageQuery;
|
||||
|
||||
@@ -76,4 +77,11 @@ public interface ISchProdScheduleItemService {
|
||||
* 保存已排产拖拽排序
|
||||
*/
|
||||
Boolean saveSort(SchProdScheduleItemSortBo sortBo);
|
||||
|
||||
/**
|
||||
* 明细下发:逐条从 sch_prod_schedule_detail 下发到 sch_prod_schedule_item
|
||||
* 若已下发过(同明细+同工序),先删除再重新插入
|
||||
* 生产日期使用明细自身的 scheduleDate,而非主表的 prodDate
|
||||
*/
|
||||
Boolean receiveFromDetail(SchProdScheduleItemDispatchBo dispatchBo);
|
||||
}
|
||||
|
||||
@@ -100,10 +100,13 @@ public class SchProdScheduleDetailServiceImpl implements ISchProdScheduleDetailS
|
||||
lqw.eq(bo.getScheduleId() != null, SchProdScheduleDetail::getScheduleId, bo.getScheduleId());
|
||||
lqw.eq(bo.getOrderDetailId() != null, SchProdScheduleDetail::getOrderDetailId, bo.getOrderDetailId());
|
||||
lqw.eq(bo.getProcessId() != null, SchProdScheduleDetail::getProcessId, bo.getProcessId());
|
||||
lqw.eq(bo.getScheduleDate() != null, SchProdScheduleDetail::getScheduleDate, bo.getScheduleDate());
|
||||
lqw.eq(StringUtils.isNotBlank(bo.getSpec()), SchProdScheduleDetail::getSpec, bo.getSpec());
|
||||
lqw.eq(StringUtils.isNotBlank(bo.getMaterial()), SchProdScheduleDetail::getMaterial, bo.getMaterial());
|
||||
lqw.eq(bo.getScheduleWeight() != null, SchProdScheduleDetail::getScheduleWeight, bo.getScheduleWeight());
|
||||
lqw.eq(StringUtils.isNotBlank(bo.getProductType()), SchProdScheduleDetail::getProductType, bo.getProductType());
|
||||
// 是否下发
|
||||
lqw.eq(bo.getIsIssued() != null, SchProdScheduleDetail::getIsIssued, bo.getIsIssued());
|
||||
return lqw;
|
||||
}
|
||||
|
||||
|
||||
@@ -14,6 +14,7 @@ import com.klp.flow.domain.bo.SchProdScheduleItemMergeBo;
|
||||
import com.klp.flow.domain.bo.SchProdScheduleItemReceiveBo;
|
||||
import com.klp.flow.domain.bo.SchProdScheduleItemMergeValidateBo;
|
||||
import com.klp.flow.domain.bo.SchProdScheduleItemSortBo;
|
||||
import com.klp.flow.domain.bo.SchProdScheduleItemDispatchBo;
|
||||
import com.klp.flow.domain.vo.SchProdScheduleItemVo;
|
||||
import com.klp.flow.domain.vo.SchProdScheduleItemMergeValidateVo;
|
||||
import com.klp.flow.domain.SchProdScheduleItem;
|
||||
@@ -480,6 +481,103 @@ public class SchProdScheduleItemServiceImpl implements ISchProdScheduleItemServi
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* 明细下发:逐条从 sch_prod_schedule_detail 下发到 sch_prod_schedule_item
|
||||
* 若已下发过(同明细+同工序步骤),先删除再重新插入,避免重复
|
||||
* 生产日期使用明细自身的 scheduleDate,保证多天排产的时间准确性
|
||||
*/
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public Boolean receiveFromDetail(SchProdScheduleItemDispatchBo dispatchBo) {
|
||||
List<SchProdScheduleItemDispatchBo.DetailDispatchConfig> detailList = dispatchBo.getDetailList();
|
||||
|
||||
for (SchProdScheduleItemDispatchBo.DetailDispatchConfig config : detailList) {
|
||||
Long detailId = config.getScheduleDetailId();
|
||||
|
||||
// 1. 查询排产明细
|
||||
SchProdScheduleDetail detail = schProdScheduleDetailMapper.selectById(detailId);
|
||||
if (detail == null) {
|
||||
throw new RuntimeException("排产明细不存在,scheduleDetailId=" + detailId);
|
||||
}
|
||||
|
||||
// 2. 查询产需单主表
|
||||
SchProdSchedule header = schProdScheduleMapper.selectById(detail.getScheduleId());
|
||||
if (header == null) {
|
||||
throw new RuntimeException("产需单不存在,scheduleId=" + detail.getScheduleId());
|
||||
}
|
||||
|
||||
// 3. 收集本次下发的步骤ID列表
|
||||
List<Long> stepIds = config.getStepList().stream()
|
||||
.map(SchProdScheduleItemDispatchBo.StepConfig::getStepId)
|
||||
.collect(Collectors.toList());
|
||||
|
||||
// 4. 删除该明细上一次下发的内容(同明细 + 同工序步骤),区分工序避免误删
|
||||
baseMapper.delete(
|
||||
Wrappers.<SchProdScheduleItem>lambdaQuery()
|
||||
.eq(SchProdScheduleItem::getScheduleDetailIds, String.valueOf(detailId))
|
||||
.in(SchProdScheduleItem::getActionId, stepIds)
|
||||
);
|
||||
|
||||
// 5. 根据工序步骤逐条生成新的排产单明细项
|
||||
List<SchProdScheduleItem> addList = new ArrayList<>();
|
||||
for (SchProdScheduleItemDispatchBo.StepConfig step : config.getStepList()) {
|
||||
SchProdScheduleItem item = new SchProdScheduleItem();
|
||||
|
||||
// —— 从 header 复制主表字段 ——
|
||||
item.setScheduleNo(header.getScheduleNo());
|
||||
item.setScheduleStatus(2L); // 已下达
|
||||
item.setTotalPlanWeight(header.getTotalPlanWeight());
|
||||
item.setRelContractNo(header.getRelContractNo());
|
||||
item.setBusinessUser(header.getBusinessUser());
|
||||
item.setBusinessPhone(header.getBusinessPhone());
|
||||
item.setOrderDate(header.getOrderDate());
|
||||
item.setCustomerName(header.getCustomerName());
|
||||
item.setDeliveryCycle(header.getDeliveryCycle());
|
||||
item.setUsePurpose(header.getUsePurpose());
|
||||
item.setProductType(header.getProductType());
|
||||
item.setThicknessTolerance(header.getThicknessTolerance());
|
||||
item.setWidthTolerance(header.getWidthTolerance());
|
||||
item.setSurfaceQuality(header.getSurfaceQuality());
|
||||
item.setSurfaceTreatment(header.getSurfaceTreatment());
|
||||
item.setInnerDiameter(header.getInnerDiameter());
|
||||
item.setOuterDiameter(header.getOuterDiameter());
|
||||
item.setPackReq(header.getPackReq());
|
||||
item.setCutEdgeReq(header.getCutEdgeReq());
|
||||
item.setSingleCoilWeight(header.getSingleCoilWeight());
|
||||
item.setWeightDeviation(header.getWeightDeviation());
|
||||
item.setOtherTechReq(header.getOtherTechReq());
|
||||
item.setPaymentDesc(header.getPaymentDesc());
|
||||
item.setRemark(header.getRemark());
|
||||
|
||||
// —— 生产日期使用明细自身的 scheduleDate,实现逐条明细独立时间 ——
|
||||
item.setProdDate(detail.getScheduleDate());
|
||||
|
||||
// —— 从 detail 复制明细字段 ——
|
||||
item.setSpec(detail.getSpec());
|
||||
item.setMaterial(detail.getMaterial());
|
||||
item.setScheduleWeight(detail.getScheduleWeight());
|
||||
item.setProductItem(detail.getProductType());
|
||||
item.setRowRemark(detail.getRemark());
|
||||
|
||||
// —— 工序步骤 ——
|
||||
item.setActionId(step.getStepId());
|
||||
|
||||
// —— 来源追溯(未合并,存单明细ID) ——
|
||||
item.setScheduleDetailIds(String.valueOf(detailId));
|
||||
|
||||
validEntityBeforeSave(item);
|
||||
addList.add(item);
|
||||
}
|
||||
|
||||
// 6. 批量插入新记录
|
||||
if (!addList.isEmpty()) {
|
||||
baseMapper.insertBatch(addList);
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* 合并校验:检查待合并记录来源产需单的 header 字段是否一致
|
||||
*/
|
||||
|
||||
@@ -9,11 +9,13 @@
|
||||
<result property="scheduleId" column="schedule_id"/>
|
||||
<result property="orderDetailId" column="order_detail_id"/>
|
||||
<result property="processId" column="process_id"/>
|
||||
<result property="scheduleDate" column="schedule_date"/>
|
||||
<result property="spec" column="spec"/>
|
||||
<result property="material" column="material"/>
|
||||
<result property="scheduleWeight" column="schedule_weight"/>
|
||||
<result property="productType" column="product_type"/>
|
||||
<result property="remark" column="remark"/>
|
||||
<result property="isIssued" column="is_issued"/>
|
||||
<result property="createBy" column="create_by"/>
|
||||
<result property="createTime" column="create_time"/>
|
||||
<result property="updateBy" column="update_by"/>
|
||||
|
||||
@@ -7,6 +7,8 @@ import lombok.EqualsAndHashCode;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.util.Date;
|
||||
import com.fasterxml.jackson.annotation.JsonFormat;
|
||||
import org.springframework.format.annotation.DateTimeFormat;
|
||||
|
||||
/**
|
||||
* 车间考核参数配置对象 perf_dept_config
|
||||
@@ -65,6 +67,8 @@ public class PerfDeptConfig extends BaseEntity {
|
||||
/**
|
||||
* 月份
|
||||
*/
|
||||
@JsonFormat(pattern = "yyyy-MM-dd")
|
||||
@DateTimeFormat(pattern = "yyyy-MM-dd")
|
||||
private Date month;
|
||||
/**
|
||||
* 备注
|
||||
|
||||
@@ -7,6 +7,7 @@ import lombok.EqualsAndHashCode;
|
||||
|
||||
import java.util.Date;
|
||||
import com.fasterxml.jackson.annotation.JsonFormat;
|
||||
import org.springframework.format.annotation.DateTimeFormat;
|
||||
|
||||
/**
|
||||
* 车间扣款/奖励项目配置对象 perf_dept_item_config
|
||||
@@ -45,6 +46,8 @@ public class PerfDeptItemConfig extends BaseEntity {
|
||||
/**
|
||||
* 月份
|
||||
*/
|
||||
@JsonFormat(pattern = "yyyy-MM-dd")
|
||||
@DateTimeFormat(pattern = "yyyy-MM-dd")
|
||||
private Date month;
|
||||
/**
|
||||
*
|
||||
|
||||
@@ -1,8 +1,11 @@
|
||||
package com.klp.perf.domain.bo;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonFormat;
|
||||
import com.klp.common.core.domain.BaseEntity;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
import org.springframework.format.annotation.DateTimeFormat;
|
||||
|
||||
import javax.validation.constraints.*;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
@@ -72,6 +75,8 @@ public class PerfDeptConfigBo extends BaseEntity {
|
||||
/**
|
||||
* 月份
|
||||
*/
|
||||
@JsonFormat(pattern = "yyyy-MM-dd")
|
||||
@DateTimeFormat(pattern = "yyyy-MM-dd")
|
||||
private Date month;
|
||||
|
||||
/**
|
||||
|
||||
@@ -7,6 +7,7 @@ import javax.validation.constraints.*;
|
||||
|
||||
import java.util.Date;
|
||||
import com.fasterxml.jackson.annotation.JsonFormat;
|
||||
import org.springframework.format.annotation.DateTimeFormat;
|
||||
|
||||
/**
|
||||
* 车间扣款/奖励项目配置业务对象 perf_dept_item_config
|
||||
@@ -47,6 +48,8 @@ public class PerfDeptItemConfigBo extends BaseEntity {
|
||||
/**
|
||||
* 月份
|
||||
*/
|
||||
@JsonFormat(pattern = "yyyy-MM-dd")
|
||||
@DateTimeFormat(pattern = "yyyy-MM-dd")
|
||||
private Date month;
|
||||
|
||||
/**
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import request from '@/utils/request'
|
||||
|
||||
// ==================== 配卷列表查询 ====================
|
||||
// ==================== 配卷列表查询(统一 POST + Body BO) ====================
|
||||
|
||||
// 指定客户的发货配卷列表
|
||||
export function listDeliveryCoilsByCustomer(customerId) {
|
||||
@@ -10,74 +10,76 @@ export function listDeliveryCoilsByCustomer(customerId) {
|
||||
})
|
||||
}
|
||||
|
||||
// 指定合同的发货配卷列表(分页)
|
||||
export function listDeliveryCoilsByOrder(orderId, query) {
|
||||
// 根据订单ID分页查询发货配卷
|
||||
export function listDeliveryCoilsByOrder(queryBo) {
|
||||
return request({
|
||||
url: `/crm/orderItem/coils/order/${orderId}`,
|
||||
method: 'get',
|
||||
params: query
|
||||
url: '/crm/orderItem/coils/order/query',
|
||||
method: 'post',
|
||||
data: queryBo
|
||||
})
|
||||
}
|
||||
|
||||
// 指定销售员的发货配卷列表(分页)
|
||||
export function listDeliveryCoilsByPrincipal(query) {
|
||||
// 根据销售员分页查询生产成果
|
||||
export function listProductCoilsBySalesman(queryBo) {
|
||||
return request({
|
||||
url: '/crm/orderItem/coils/salesman/query',
|
||||
method: 'post',
|
||||
data: queryBo
|
||||
})
|
||||
}
|
||||
|
||||
// 根据合同ID分页查询生产成果
|
||||
export function listProductCoilsByContract(queryBo) {
|
||||
return request({
|
||||
url: '/crm/orderItem/coils/contract/query',
|
||||
method: 'post',
|
||||
data: queryBo
|
||||
})
|
||||
}
|
||||
|
||||
// 指定负责人的计划发货配卷列表(分页)
|
||||
export function listDeliveryCoilsByPrincipal(queryBo) {
|
||||
return request({
|
||||
url: '/wms/deliveryWaybillDetail/coilListByPrincipal',
|
||||
method: 'get',
|
||||
params: query
|
||||
method: 'post',
|
||||
data: queryBo
|
||||
})
|
||||
}
|
||||
|
||||
// 指定销售员的生产成果列表(分页)
|
||||
export function listProductCoilsBySalesman(query) {
|
||||
return request({
|
||||
url: '/crm/orderItem/coils/bySalesman',
|
||||
method: 'get',
|
||||
params: query
|
||||
})
|
||||
}
|
||||
|
||||
// 指定合同的生产成果列表(分页)
|
||||
export function listProductCoilsByContract(contractId, query) {
|
||||
return request({
|
||||
url: `/crm/orderItem/coils/byContract/${contractId}`,
|
||||
method: 'get',
|
||||
params: query
|
||||
})
|
||||
}
|
||||
|
||||
// ==================== 配卷统计汇总 ====================
|
||||
// ==================== 配卷统计汇总(统一 POST + Body BO) ====================
|
||||
|
||||
// 根据订单ID统计发货配卷汇总
|
||||
export function getDeliveryCoilsStatisticsByOrder(orderId) {
|
||||
export function getDeliveryCoilsStatisticsByOrder(queryBo) {
|
||||
return request({
|
||||
url: `/crm/orderItem/coils/order/${orderId}/statistics`,
|
||||
method: 'get'
|
||||
url: '/crm/orderItem/coils/order/statistics',
|
||||
method: 'post',
|
||||
data: queryBo
|
||||
})
|
||||
}
|
||||
|
||||
// 根据销售员统计生产成果汇总
|
||||
export function getProductCoilsStatisticsBySalesman(salesman) {
|
||||
export function getProductCoilsStatisticsBySalesman(queryBo) {
|
||||
return request({
|
||||
url: '/crm/orderItem/coils/bySalesman/statistics',
|
||||
method: 'get',
|
||||
params: { salesman }
|
||||
url: '/crm/orderItem/coils/salesman/statistics',
|
||||
method: 'post',
|
||||
data: queryBo
|
||||
})
|
||||
}
|
||||
|
||||
// 根据合同ID统计生产成果汇总
|
||||
export function getProductCoilsStatisticsByContract(contractId) {
|
||||
export function getProductCoilsStatisticsByContract(queryBo) {
|
||||
return request({
|
||||
url: `/crm/orderItem/coils/byContract/${contractId}/statistics`,
|
||||
method: 'get'
|
||||
url: '/crm/orderItem/coils/contract/statistics',
|
||||
method: 'post',
|
||||
data: queryBo
|
||||
})
|
||||
}
|
||||
|
||||
// 根据负责人统计发货配卷汇总
|
||||
export function getDeliveryCoilsStatisticsByPrincipal(principal) {
|
||||
export function getDeliveryCoilsStatisticsByPrincipal(queryBo) {
|
||||
return request({
|
||||
url: '/wms/deliveryWaybillDetail/coilListByPrincipal/statistics',
|
||||
method: 'get',
|
||||
params: { principal }
|
||||
method: 'post',
|
||||
data: queryBo
|
||||
})
|
||||
}
|
||||
|
||||
@@ -44,13 +44,14 @@ export function delOrder(orderId) {
|
||||
}
|
||||
|
||||
/**
|
||||
* 查询该订单下的配卷(分页)
|
||||
* 查询该订单下的配卷(分页),支持钢卷信息筛选
|
||||
* @param {object} queryBo - { orderId, pageNum, pageSize, ...钢卷筛选条件 }
|
||||
*/
|
||||
export function listOrderPackaging(orderId, query) {
|
||||
export function listOrderPackaging(queryBo) {
|
||||
return request({
|
||||
url: `/crm/orderItem/coils/order/${orderId}`,
|
||||
method: 'get',
|
||||
params: query
|
||||
url: '/crm/orderItem/coils/order/query',
|
||||
method: 'post',
|
||||
data: queryBo
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
44
klp-ui/src/api/perf/appraisal.js
Normal file
44
klp-ui/src/api/perf/appraisal.js
Normal file
@@ -0,0 +1,44 @@
|
||||
import request from '@/utils/request'
|
||||
|
||||
// 查询月度绩效考核记录列表
|
||||
export function listPerfAppraisal(query) {
|
||||
return request({
|
||||
url: '/perf/appraisal/list',
|
||||
method: 'get',
|
||||
params: query
|
||||
})
|
||||
}
|
||||
|
||||
// 查询月度绩效考核记录详细
|
||||
export function getPerfAppraisal(id) {
|
||||
return request({
|
||||
url: '/perf/appraisal/' + id,
|
||||
method: 'get'
|
||||
})
|
||||
}
|
||||
|
||||
// 新增月度绩效考核记录
|
||||
export function addPerfAppraisal(data) {
|
||||
return request({
|
||||
url: '/perf/appraisal',
|
||||
method: 'post',
|
||||
data: data
|
||||
})
|
||||
}
|
||||
|
||||
// 修改月度绩效考核记录
|
||||
export function updatePerfAppraisal(data) {
|
||||
return request({
|
||||
url: '/perf/appraisal',
|
||||
method: 'put',
|
||||
data: data
|
||||
})
|
||||
}
|
||||
|
||||
// 删除月度绩效考核记录
|
||||
export function delPerfAppraisal(ids) {
|
||||
return request({
|
||||
url: '/perf/appraisal/' + ids,
|
||||
method: 'delete'
|
||||
})
|
||||
}
|
||||
44
klp-ui/src/api/perf/appraisalDetail.js
Normal file
44
klp-ui/src/api/perf/appraisalDetail.js
Normal file
@@ -0,0 +1,44 @@
|
||||
import request from '@/utils/request'
|
||||
|
||||
// 查询考核维度评分明细列表
|
||||
export function listPerfAppraisalDetail(query) {
|
||||
return request({
|
||||
url: '/perf/appraisalDetail/list',
|
||||
method: 'get',
|
||||
params: query
|
||||
})
|
||||
}
|
||||
|
||||
// 查询考核维度评分明细详细
|
||||
export function getPerfAppraisalDetail(id) {
|
||||
return request({
|
||||
url: '/perf/appraisalDetail/' + id,
|
||||
method: 'get'
|
||||
})
|
||||
}
|
||||
|
||||
// 新增考核维度评分明细
|
||||
export function addPerfAppraisalDetail(data) {
|
||||
return request({
|
||||
url: '/perf/appraisalDetail',
|
||||
method: 'post',
|
||||
data: data
|
||||
})
|
||||
}
|
||||
|
||||
// 修改考核维度评分明细
|
||||
export function updatePerfAppraisalDetail(data) {
|
||||
return request({
|
||||
url: '/perf/appraisalDetail',
|
||||
method: 'put',
|
||||
data: data
|
||||
})
|
||||
}
|
||||
|
||||
// 删除考核维度评分明细
|
||||
export function delPerfAppraisalDetail(ids) {
|
||||
return request({
|
||||
url: '/perf/appraisalDetail/' + ids,
|
||||
method: 'delete'
|
||||
})
|
||||
}
|
||||
44
klp-ui/src/api/perf/dept.js
Normal file
44
klp-ui/src/api/perf/dept.js
Normal file
@@ -0,0 +1,44 @@
|
||||
import request from '@/utils/request'
|
||||
|
||||
// 查询绩效系统部门列表
|
||||
export function listPerfDept(query) {
|
||||
return request({
|
||||
url: '/perf/dept/list',
|
||||
method: 'get',
|
||||
params: query
|
||||
})
|
||||
}
|
||||
|
||||
// 查询绩效系统部门详细
|
||||
export function getPerfDept(id) {
|
||||
return request({
|
||||
url: '/perf/dept/' + id,
|
||||
method: 'get'
|
||||
})
|
||||
}
|
||||
|
||||
// 新增绩效系统部门
|
||||
export function addPerfDept(data) {
|
||||
return request({
|
||||
url: '/perf/dept',
|
||||
method: 'post',
|
||||
data: data
|
||||
})
|
||||
}
|
||||
|
||||
// 修改绩效系统部门
|
||||
export function updatePerfDept(data) {
|
||||
return request({
|
||||
url: '/perf/dept',
|
||||
method: 'put',
|
||||
data: data
|
||||
})
|
||||
}
|
||||
|
||||
// 删除绩效系统部门
|
||||
export function delPerfDept(ids) {
|
||||
return request({
|
||||
url: '/perf/dept/' + ids,
|
||||
method: 'delete'
|
||||
})
|
||||
}
|
||||
44
klp-ui/src/api/perf/deptConfig.js
Normal file
44
klp-ui/src/api/perf/deptConfig.js
Normal file
@@ -0,0 +1,44 @@
|
||||
import request from '@/utils/request'
|
||||
|
||||
// 查询车间考核参数配置列表
|
||||
export function listPerfDeptConfig(query) {
|
||||
return request({
|
||||
url: '/perf/deptConfig/list',
|
||||
method: 'get',
|
||||
params: query
|
||||
})
|
||||
}
|
||||
|
||||
// 查询车间考核参数配置详细
|
||||
export function getPerfDeptConfig(id) {
|
||||
return request({
|
||||
url: '/perf/deptConfig/' + id,
|
||||
method: 'get'
|
||||
})
|
||||
}
|
||||
|
||||
// 新增车间考核参数配置
|
||||
export function addPerfDeptConfig(data) {
|
||||
return request({
|
||||
url: '/perf/deptConfig',
|
||||
method: 'post',
|
||||
data: data
|
||||
})
|
||||
}
|
||||
|
||||
// 修改车间考核参数配置
|
||||
export function updatePerfDeptConfig(data) {
|
||||
return request({
|
||||
url: '/perf/deptConfig',
|
||||
method: 'put',
|
||||
data: data
|
||||
})
|
||||
}
|
||||
|
||||
// 删除车间考核参数配置
|
||||
export function delPerfDeptConfig(ids) {
|
||||
return request({
|
||||
url: '/perf/deptConfig/' + ids,
|
||||
method: 'delete'
|
||||
})
|
||||
}
|
||||
44
klp-ui/src/api/perf/deptItemConfig.js
Normal file
44
klp-ui/src/api/perf/deptItemConfig.js
Normal file
@@ -0,0 +1,44 @@
|
||||
import request from '@/utils/request'
|
||||
|
||||
// 查询车间扣款/奖励项目配置列表
|
||||
export function listPerfDeptItemConfig(query) {
|
||||
return request({
|
||||
url: '/perf/deptItemConfig/list',
|
||||
method: 'get',
|
||||
params: query
|
||||
})
|
||||
}
|
||||
|
||||
// 查询车间扣款/奖励项目配置详细
|
||||
export function getPerfDeptItemConfig(id) {
|
||||
return request({
|
||||
url: '/perf/deptItemConfig/' + id,
|
||||
method: 'get'
|
||||
})
|
||||
}
|
||||
|
||||
// 新增车间扣款/奖励项目配置
|
||||
export function addPerfDeptItemConfig(data) {
|
||||
return request({
|
||||
url: '/perf/deptItemConfig',
|
||||
method: 'post',
|
||||
data: data
|
||||
})
|
||||
}
|
||||
|
||||
// 修改车间扣款/奖励项目配置
|
||||
export function updatePerfDeptItemConfig(data) {
|
||||
return request({
|
||||
url: '/perf/deptItemConfig',
|
||||
method: 'put',
|
||||
data: data
|
||||
})
|
||||
}
|
||||
|
||||
// 删除车间扣款/奖励项目配置
|
||||
export function delPerfDeptItemConfig(ids) {
|
||||
return request({
|
||||
url: '/perf/deptItemConfig/' + ids,
|
||||
method: 'delete'
|
||||
})
|
||||
}
|
||||
44
klp-ui/src/api/perf/deptSummary.js
Normal file
44
klp-ui/src/api/perf/deptSummary.js
Normal file
@@ -0,0 +1,44 @@
|
||||
import request from '@/utils/request'
|
||||
|
||||
// 查询车间月度汇总列表
|
||||
export function listPerfDeptSummary(query) {
|
||||
return request({
|
||||
url: '/perf/deptSummary/list',
|
||||
method: 'get',
|
||||
params: query
|
||||
})
|
||||
}
|
||||
|
||||
// 查询车间月度汇总详细
|
||||
export function getPerfDeptSummary(id) {
|
||||
return request({
|
||||
url: '/perf/deptSummary/' + id,
|
||||
method: 'get'
|
||||
})
|
||||
}
|
||||
|
||||
// 新增车间月度汇总
|
||||
export function addPerfDeptSummary(data) {
|
||||
return request({
|
||||
url: '/perf/deptSummary',
|
||||
method: 'post',
|
||||
data: data
|
||||
})
|
||||
}
|
||||
|
||||
// 修改车间月度汇总
|
||||
export function updatePerfDeptSummary(data) {
|
||||
return request({
|
||||
url: '/perf/deptSummary',
|
||||
method: 'put',
|
||||
data: data
|
||||
})
|
||||
}
|
||||
|
||||
// 删除车间月度汇总
|
||||
export function delPerfDeptSummary(ids) {
|
||||
return request({
|
||||
url: '/perf/deptSummary/' + ids,
|
||||
method: 'delete'
|
||||
})
|
||||
}
|
||||
44
klp-ui/src/api/perf/employeeInfo.js
Normal file
44
klp-ui/src/api/perf/employeeInfo.js
Normal file
@@ -0,0 +1,44 @@
|
||||
import request from '@/utils/request'
|
||||
|
||||
// 查询员工绩效薪资基本信息列表
|
||||
export function listPerfEmployeeInfo(query) {
|
||||
return request({
|
||||
url: '/perf/employeeInfo/list',
|
||||
method: 'get',
|
||||
params: query
|
||||
})
|
||||
}
|
||||
|
||||
// 查询员工绩效薪资基本信息详细
|
||||
export function getPerfEmployeeInfo(id) {
|
||||
return request({
|
||||
url: '/perf/employeeInfo/' + id,
|
||||
method: 'get'
|
||||
})
|
||||
}
|
||||
|
||||
// 新增员工绩效薪资基本信息
|
||||
export function addPerfEmployeeInfo(data) {
|
||||
return request({
|
||||
url: '/perf/employeeInfo',
|
||||
method: 'post',
|
||||
data: data
|
||||
})
|
||||
}
|
||||
|
||||
// 修改员工绩效薪资基本信息
|
||||
export function updatePerfEmployeeInfo(data) {
|
||||
return request({
|
||||
url: '/perf/employeeInfo',
|
||||
method: 'put',
|
||||
data: data
|
||||
})
|
||||
}
|
||||
|
||||
// 删除员工绩效薪资基本信息
|
||||
export function delPerfEmployeeInfo(ids) {
|
||||
return request({
|
||||
url: '/perf/employeeInfo/' + ids,
|
||||
method: 'delete'
|
||||
})
|
||||
}
|
||||
44
klp-ui/src/api/perf/positionTemplate.js
Normal file
44
klp-ui/src/api/perf/positionTemplate.js
Normal file
@@ -0,0 +1,44 @@
|
||||
import request from '@/utils/request'
|
||||
|
||||
// 查询岗位绩效考核模板列表
|
||||
export function listPerfPositionTemplate(query) {
|
||||
return request({
|
||||
url: '/perf/positionTemplate/list',
|
||||
method: 'get',
|
||||
params: query
|
||||
})
|
||||
}
|
||||
|
||||
// 查询岗位绩效考核模板详细
|
||||
export function getPerfPositionTemplate(id) {
|
||||
return request({
|
||||
url: '/perf/positionTemplate/' + id,
|
||||
method: 'get'
|
||||
})
|
||||
}
|
||||
|
||||
// 新增岗位绩效考核模板
|
||||
export function addPerfPositionTemplate(data) {
|
||||
return request({
|
||||
url: '/perf/positionTemplate',
|
||||
method: 'post',
|
||||
data: data
|
||||
})
|
||||
}
|
||||
|
||||
// 修改岗位绩效考核模板
|
||||
export function updatePerfPositionTemplate(data) {
|
||||
return request({
|
||||
url: '/perf/positionTemplate',
|
||||
method: 'put',
|
||||
data: data
|
||||
})
|
||||
}
|
||||
|
||||
// 删除岗位绩效考核模板
|
||||
export function delPerfPositionTemplate(ids) {
|
||||
return request({
|
||||
url: '/perf/positionTemplate/' + ids,
|
||||
method: 'delete'
|
||||
})
|
||||
}
|
||||
44
klp-ui/src/api/perf/salary.js
Normal file
44
klp-ui/src/api/perf/salary.js
Normal file
@@ -0,0 +1,44 @@
|
||||
import request from '@/utils/request'
|
||||
|
||||
// 查询月度薪资计算记录列表
|
||||
export function listPerfSalary(query) {
|
||||
return request({
|
||||
url: '/perf/salary/list',
|
||||
method: 'get',
|
||||
params: query
|
||||
})
|
||||
}
|
||||
|
||||
// 查询月度薪资计算记录详细
|
||||
export function getPerfSalary(id) {
|
||||
return request({
|
||||
url: '/perf/salary/' + id,
|
||||
method: 'get'
|
||||
})
|
||||
}
|
||||
|
||||
// 新增月度薪资计算记录
|
||||
export function addPerfSalary(data) {
|
||||
return request({
|
||||
url: '/perf/salary',
|
||||
method: 'post',
|
||||
data: data
|
||||
})
|
||||
}
|
||||
|
||||
// 修改月度薪资计算记录
|
||||
export function updatePerfSalary(data) {
|
||||
return request({
|
||||
url: '/perf/salary',
|
||||
method: 'put',
|
||||
data: data
|
||||
})
|
||||
}
|
||||
|
||||
// 删除月度薪资计算记录
|
||||
export function delPerfSalary(ids) {
|
||||
return request({
|
||||
url: '/perf/salary/' + ids,
|
||||
method: 'delete'
|
||||
})
|
||||
}
|
||||
44
klp-ui/src/api/perf/templateDimension.js
Normal file
44
klp-ui/src/api/perf/templateDimension.js
Normal file
@@ -0,0 +1,44 @@
|
||||
import request from '@/utils/request'
|
||||
|
||||
// 查询考核维度明细列表
|
||||
export function listPerfTemplateDimension(query) {
|
||||
return request({
|
||||
url: '/perf/templateDimension/list',
|
||||
method: 'get',
|
||||
params: query
|
||||
})
|
||||
}
|
||||
|
||||
// 查询考核维度明细详细
|
||||
export function getPerfTemplateDimension(id) {
|
||||
return request({
|
||||
url: '/perf/templateDimension/' + id,
|
||||
method: 'get'
|
||||
})
|
||||
}
|
||||
|
||||
// 新增考核维度明细
|
||||
export function addPerfTemplateDimension(data) {
|
||||
return request({
|
||||
url: '/perf/templateDimension',
|
||||
method: 'post',
|
||||
data: data
|
||||
})
|
||||
}
|
||||
|
||||
// 修改考核维度明细
|
||||
export function updatePerfTemplateDimension(data) {
|
||||
return request({
|
||||
url: '/perf/templateDimension',
|
||||
method: 'put',
|
||||
data: data
|
||||
})
|
||||
}
|
||||
|
||||
// 删除考核维度明细
|
||||
export function delPerfTemplateDimension(ids) {
|
||||
return request({
|
||||
url: '/perf/templateDimension/' + ids,
|
||||
method: 'delete'
|
||||
})
|
||||
}
|
||||
@@ -70,10 +70,10 @@ export function getBoundCoilStatisticsList(query) {
|
||||
}
|
||||
|
||||
// 按销售员查询订单明细的卷(分页)
|
||||
export function listDeliveryWaybillDetailBySaleman(query) {
|
||||
export function listDeliveryWaybillDetailBySaleman(queryBo) {
|
||||
return request({
|
||||
url: '/wms/deliveryWaybillDetail/coilListByPrincipal',
|
||||
method: 'get',
|
||||
params: query
|
||||
method: 'post',
|
||||
data: queryBo
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -371,7 +371,7 @@ export default {
|
||||
// 获取订单已发货的钢卷
|
||||
getShippedCoils() {
|
||||
if (this.orderId) {
|
||||
listOrderPackaging(this.orderId, { pageNum: 1, pageSize: 10000 }).then(response => {
|
||||
listOrderPackaging({ orderId: this.orderId, pageNum: 1, pageSize: 10000 }).then(response => {
|
||||
this.shippedCoils = response.rows || [];
|
||||
}).catch(() => {
|
||||
this.shippedCoils = [];
|
||||
@@ -458,7 +458,7 @@ export default {
|
||||
...this.form,
|
||||
coilIds: this.coilListToCsv(this.formCoilList)
|
||||
};
|
||||
|
||||
|
||||
if (this.form.objectionId != null) {
|
||||
updateSalesObjection(formData).then(response => {
|
||||
this.$modal.msgSuccess("修改成功");
|
||||
|
||||
@@ -59,6 +59,50 @@
|
||||
|
||||
<!-- 生产成果 -->
|
||||
<div v-show="activeTab === 'product'" v-loading="productCoilLoading">
|
||||
<el-form :inline="true" size="small" class="coil-filter-bar">
|
||||
<el-form-item label="入场卷号">
|
||||
<el-input v-model="productCoilBo.enterCoilNo" placeholder="请输入入场钢卷号" clearable @keyup.enter.native="handleProductFilter" style="width: 160px;" />
|
||||
</el-form-item>
|
||||
<el-form-item label="当前卷号">
|
||||
<el-input v-model="productCoilBo.currentCoilNo" placeholder="请输入当前钢卷号" clearable @keyup.enter.native="handleProductFilter" style="width: 160px;" />
|
||||
</el-form-item>
|
||||
<el-form-item label="逻辑库区">
|
||||
<warehouse-select v-model="productCoilBo.warehouseId" placeholder="请选择逻辑库位" style="width: 160px;" clearable />
|
||||
</el-form-item>
|
||||
<el-form-item label="物料类型">
|
||||
<el-select v-model="productCoilBo.itemType" placeholder="全部" clearable style="width: 100px;" @change="handleProductFilter">
|
||||
<el-option label="成品" value="product" />
|
||||
<el-option label="原料" value="raw_material" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="发货状态">
|
||||
<el-select v-model="productCoilBo.status" placeholder="全部" clearable style="width: 100px;">
|
||||
<el-option label="在库" :value="0" />
|
||||
<el-option label="已发货" :value="1" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="质量状态">
|
||||
<muti-select v-model="productCoilBo.qualityStatusCsv" :options="dict.type.coil_quality_status" placeholder="请选择品质" clearable style="width: 160px;" />
|
||||
</el-form-item>
|
||||
<template v-if="productCoilBo.itemType">
|
||||
<el-form-item label="品名">
|
||||
<muti-select v-model="productCoilBo.itemName" :options="dict.type.coil_itemname" placeholder="请选择物料" clearable style="width: 160px;" />
|
||||
</el-form-item>
|
||||
<el-form-item label="材质">
|
||||
<muti-select v-model="productCoilBo.itemMaterial" :options="dict.type.coil_material" placeholder="请选择材质" clearable style="width: 160px;" />
|
||||
</el-form-item>
|
||||
<el-form-item label="规格">
|
||||
<memo-input v-model="productCoilBo.itemSpecification" storageKey="coilSpec" placeholder="请选择规格" clearable style="width: 160px;" />
|
||||
</el-form-item>
|
||||
<el-form-item label="厂家">
|
||||
<muti-select v-model="productCoilBo.itemManufacturer" :options="dict.type.coil_manufacturer" placeholder="请选择厂家" clearable style="width: 160px;" />
|
||||
</el-form-item>
|
||||
</template>
|
||||
<el-form-item>
|
||||
<el-button type="primary" size="mini" @click="handleProductFilter">查询</el-button>
|
||||
<el-button size="mini" @click="resetProductFilter">重置</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<CoilTable ref="productCoilTable" :data="productCoilList || []" :showSelection="true"
|
||||
:pagination="productPagination" :total-statistics="productTotalStatistics"
|
||||
@selection-change="handleProductSelectionChange"
|
||||
@@ -74,6 +118,50 @@
|
||||
|
||||
<!-- 发货配卷 -->
|
||||
<div v-show="activeTab === 'coil'" v-loading="deliveryCoilLoading">
|
||||
<el-form :inline="true" size="small" class="coil-filter-bar">
|
||||
<el-form-item label="入场卷号">
|
||||
<el-input v-model="deliveryCoilBo.enterCoilNo" placeholder="请输入入场钢卷号" clearable @keyup.enter.native="handleDeliveryFilter" style="width: 160px;" />
|
||||
</el-form-item>
|
||||
<el-form-item label="当前卷号">
|
||||
<el-input v-model="deliveryCoilBo.currentCoilNo" placeholder="请输入当前钢卷号" clearable @keyup.enter.native="handleDeliveryFilter" style="width: 160px;" />
|
||||
</el-form-item>
|
||||
<el-form-item label="逻辑库区">
|
||||
<warehouse-select v-model="deliveryCoilBo.warehouseId" placeholder="请选择逻辑库位" style="width: 160px;" clearable />
|
||||
</el-form-item>
|
||||
<el-form-item label="物料类型">
|
||||
<el-select v-model="deliveryCoilBo.itemType" placeholder="全部" clearable style="width: 100px;" @change="handleDeliveryFilter">
|
||||
<el-option label="成品" value="product" />
|
||||
<el-option label="原料" value="raw_material" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="发货状态">
|
||||
<el-select v-model="deliveryCoilBo.status" placeholder="全部" clearable style="width: 100px;">
|
||||
<el-option label="在库" :value="0" />
|
||||
<el-option label="已发货" :value="1" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="质量状态">
|
||||
<muti-select v-model="deliveryCoilBo.qualityStatusCsv" :options="dict.type.coil_quality_status" placeholder="请选择品质" clearable style="width: 160px;" />
|
||||
</el-form-item>
|
||||
<template v-if="deliveryCoilBo.itemType">
|
||||
<el-form-item label="品名">
|
||||
<muti-select v-model="deliveryCoilBo.itemName" :options="dict.type.coil_itemname" placeholder="请选择物料" clearable style="width: 160px;" />
|
||||
</el-form-item>
|
||||
<el-form-item label="材质">
|
||||
<muti-select v-model="deliveryCoilBo.itemMaterial" :options="dict.type.coil_material" placeholder="请选择材质" clearable style="width: 160px;" />
|
||||
</el-form-item>
|
||||
<el-form-item label="规格">
|
||||
<memo-input v-model="deliveryCoilBo.itemSpecification" storageKey="coilSpec" placeholder="请选择规格" clearable style="width: 160px;" />
|
||||
</el-form-item>
|
||||
<el-form-item label="厂家">
|
||||
<muti-select v-model="deliveryCoilBo.itemManufacturer" :options="dict.type.coil_manufacturer" placeholder="请选择厂家" clearable style="width: 160px;" />
|
||||
</el-form-item>
|
||||
</template>
|
||||
<el-form-item>
|
||||
<el-button type="primary" size="mini" @click="handleDeliveryFilter">查询</el-button>
|
||||
<el-button size="mini" @click="resetDeliveryFilter">重置</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<CoilTable :data="deliveryCoilList || []"
|
||||
:pagination="deliveryPagination" :total-statistics="deliveryTotalStatistics"
|
||||
@page-change="handleDeliveryPageChange" />
|
||||
@@ -132,6 +220,9 @@ import { batchUpdateContractCoil } from "@/api/wms/coil";
|
||||
import FileList from "@/components/FileList";
|
||||
import DeliveryTable from "../../components/DeliveryTable.vue";
|
||||
import ContractPreview from "./ContractPreview.vue";
|
||||
import WarehouseSelect from "@/components/KLPService/WarehouseSelect";
|
||||
import MemoInput from "@/components/MemoInput";
|
||||
import MutiSelect from "@/components/MutiSelect";
|
||||
|
||||
// 导入可能需要的组件
|
||||
import OrderDetail from "../../components/OrderDetail.vue";
|
||||
@@ -154,8 +245,12 @@ export default {
|
||||
OrderEdit,
|
||||
ReceiveTable,
|
||||
OrderObjection,
|
||||
OrderRecord
|
||||
OrderRecord,
|
||||
WarehouseSelect,
|
||||
MemoInput,
|
||||
MutiSelect
|
||||
},
|
||||
dicts: ['coil_itemname', 'coil_material', 'coil_manufacturer', 'coil_quality_status'],
|
||||
props: {
|
||||
orderId: {
|
||||
type: [Number, String],
|
||||
@@ -205,10 +300,12 @@ export default {
|
||||
productCoilStatistics: {},
|
||||
productPagination: { total: 0, currentPage: 1, pageSize: 20 },
|
||||
productCoilLoading: false,
|
||||
productCoilBo: {},
|
||||
deliveryCoilList: [],
|
||||
deliveryCoilStatistics: {},
|
||||
deliveryPagination: { total: 0, currentPage: 1, pageSize: 20 },
|
||||
deliveryCoilLoading: false,
|
||||
deliveryCoilBo: {},
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
@@ -302,9 +399,11 @@ export default {
|
||||
},
|
||||
fetchDeliveryCoils() {
|
||||
this.deliveryCoilLoading = true;
|
||||
listDeliveryCoilsByOrder(this.orderId, {
|
||||
listDeliveryCoilsByOrder({
|
||||
orderId: this.orderId,
|
||||
pageNum: this.deliveryPagination.currentPage,
|
||||
pageSize: this.deliveryPagination.pageSize
|
||||
pageSize: this.deliveryPagination.pageSize,
|
||||
coilBo: this.deliveryCoilBo
|
||||
}).then(res => {
|
||||
this.deliveryCoilList = res.rows || [];
|
||||
this.deliveryPagination.total = res.total || 0;
|
||||
@@ -314,9 +413,11 @@ export default {
|
||||
},
|
||||
fetchProductCoils() {
|
||||
this.productCoilLoading = true;
|
||||
listProductCoilsByContract(this.orderId, {
|
||||
listProductCoilsByContract({
|
||||
orderId: this.orderId,
|
||||
pageNum: this.productPagination.currentPage,
|
||||
pageSize: this.productPagination.pageSize
|
||||
pageSize: this.productPagination.pageSize,
|
||||
...this.productCoilBo
|
||||
}).then(res => {
|
||||
this.productCoilList = res.rows || [];
|
||||
this.productPagination.total = res.total || 0;
|
||||
@@ -325,15 +426,35 @@ export default {
|
||||
});
|
||||
},
|
||||
fetchDeliveryCoilStatistics() {
|
||||
getDeliveryCoilsStatisticsByOrder(this.orderId).then(res => {
|
||||
getDeliveryCoilsStatisticsByOrder({ orderId: this.orderId, coilBo: this.deliveryCoilBo }).then(res => {
|
||||
this.deliveryCoilStatistics = res.data || {};
|
||||
});
|
||||
},
|
||||
fetchProductCoilStatistics() {
|
||||
getProductCoilsStatisticsByContract(this.orderId).then(res => {
|
||||
getProductCoilsStatisticsByContract({ ...this.productCoilBo, orderId: this.orderId }).then(res => {
|
||||
this.productCoilStatistics = res.data || {};
|
||||
});
|
||||
},
|
||||
handleProductFilter() {
|
||||
this.productCoilBo.selectType = this.productCoilBo.itemType || '';
|
||||
this.productPagination.currentPage = 1;
|
||||
this.fetchProductCoils();
|
||||
this.fetchProductCoilStatistics();
|
||||
},
|
||||
resetProductFilter() {
|
||||
this.productCoilBo = {};
|
||||
this.handleProductFilter();
|
||||
},
|
||||
handleDeliveryFilter() {
|
||||
this.deliveryCoilBo.selectType = this.deliveryCoilBo.itemType || '';
|
||||
this.deliveryPagination.currentPage = 1;
|
||||
this.fetchDeliveryCoils();
|
||||
this.fetchDeliveryCoilStatistics();
|
||||
},
|
||||
resetDeliveryFilter() {
|
||||
this.deliveryCoilBo = {};
|
||||
this.handleDeliveryFilter();
|
||||
},
|
||||
handleProductPageChange({ currentPage, pageSize }) {
|
||||
this.productPagination.currentPage = currentPage;
|
||||
this.productPagination.pageSize = pageSize;
|
||||
|
||||
@@ -398,7 +398,7 @@ export default {
|
||||
...this.form,
|
||||
coilIds: this.coilListToCsv(this.formCoilList)
|
||||
};
|
||||
|
||||
|
||||
if (this.form.objectionId != null) {
|
||||
updateSalesObjection(formData).then(response => {
|
||||
this.$modal.msgSuccess("修改成功");
|
||||
@@ -465,11 +465,11 @@ export default {
|
||||
path: '/wms/coil/' + coil.coilId,
|
||||
});
|
||||
},
|
||||
|
||||
|
||||
// 获取订单已发货的钢卷
|
||||
getShippedCoils() {
|
||||
if (this.form.orderId) {
|
||||
listOrderPackaging(this.form.orderId, { pageNum: 1, pageSize: 10000 }).then(response => {
|
||||
listOrderPackaging({ orderId: this.form.orderId, pageNum: 1, pageSize: 10000 }).then(response => {
|
||||
this.shippedCoils = response.rows || [];
|
||||
}).catch(() => {
|
||||
this.shippedCoils = [];
|
||||
|
||||
@@ -86,6 +86,28 @@
|
||||
</el-tab-pane>
|
||||
<el-tab-pane label="发货配卷" name="coil">
|
||||
<div class="order-record" v-if="activeTab === 'coil'">
|
||||
<el-form :inline="true" size="small" class="coil-filter-bar" style="margin-bottom: 8px;">
|
||||
<el-form-item label="钢卷号">
|
||||
<el-input v-model="coilBo.currentCoilNo" placeholder="当前钢卷号" clearable @keyup.enter.native="handleCoilFilter" style="width: 160px;" />
|
||||
</el-form-item>
|
||||
<el-form-item label="材料类型">
|
||||
<el-select v-model="coilBo.materialType" placeholder="全部" clearable style="width: 100px;">
|
||||
<el-option label="成品" value="成品" />
|
||||
<el-option label="原料" value="原料" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="发货状态">
|
||||
<el-select v-model="coilBo.status" placeholder="全部" clearable style="width: 100px;">
|
||||
<el-option label="在库" :value="0" />
|
||||
<el-option label="在途" :value="1" />
|
||||
<el-option label="已发货" :value="2" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button type="primary" size="mini" @click="handleCoilFilter">查询</el-button>
|
||||
<el-button size="mini" @click="resetCoilFilter">重置</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<!-- 发货配卷内容 -->
|
||||
<CoilTable :data="coilList" />
|
||||
</div>
|
||||
@@ -227,6 +249,7 @@ export default {
|
||||
customerList: [],
|
||||
contractList: [],
|
||||
coilList: [],
|
||||
coilBo: {},
|
||||
deliveryWaybillList: [],
|
||||
}
|
||||
},
|
||||
@@ -259,10 +282,22 @@ export default {
|
||||
},
|
||||
/** 查询发货配卷列表 */
|
||||
getCoilList() {
|
||||
listOrderPackaging(this.currentOrder.orderId, { pageNum: 1, pageSize: 10000 }).then(response => {
|
||||
listOrderPackaging({
|
||||
orderId: this.currentOrder.orderId,
|
||||
pageNum: 1,
|
||||
pageSize: 10000,
|
||||
coilBo: this.coilBo
|
||||
}).then(response => {
|
||||
this.coilList = response.rows || [];
|
||||
});
|
||||
},
|
||||
handleCoilFilter() {
|
||||
this.getCoilList();
|
||||
},
|
||||
resetCoilFilter() {
|
||||
this.coilBo = {};
|
||||
this.getCoilList();
|
||||
},
|
||||
/** 合同号改变事件 */
|
||||
handleContractChange(contractId) {
|
||||
const contract = this.contractList.find(item => item.contractId === contractId)
|
||||
@@ -398,4 +433,4 @@ export default {
|
||||
},
|
||||
}
|
||||
}
|
||||
</script>
|
||||
</script>
|
||||
|
||||
@@ -77,14 +77,96 @@
|
||||
</el-tab-pane>
|
||||
<el-tab-pane label="生产成果" name="production">
|
||||
<div v-loading="productCoilLoading">
|
||||
<CoilTable :data="productList || []" table-height="calc(100vh - 240px)"
|
||||
<el-form :inline="true" size="small" class="coil-filter-bar" style="margin-bottom: 8px;">
|
||||
<el-form-item label="入场卷号">
|
||||
<el-input v-model="productCoilBo.enterCoilNo" placeholder="请输入入场钢卷号" clearable @keyup.enter.native="handleProductFilter" style="width: 160px;" />
|
||||
</el-form-item>
|
||||
<el-form-item label="当前卷号">
|
||||
<el-input v-model="productCoilBo.currentCoilNo" placeholder="请输入当前钢卷号" clearable @keyup.enter.native="handleProductFilter" style="width: 160px;" />
|
||||
</el-form-item>
|
||||
<el-form-item label="逻辑库区">
|
||||
<warehouse-select v-model="productCoilBo.warehouseId" placeholder="请选择逻辑库位" style="width: 160px;" clearable />
|
||||
</el-form-item>
|
||||
<el-form-item label="物料类型">
|
||||
<el-select v-model="productCoilBo.itemType" placeholder="全部" clearable style="width: 100px;" @change="handleProductFilter">
|
||||
<el-option label="成品" value="product" />
|
||||
<el-option label="原料" value="raw_material" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="发货状态">
|
||||
<el-select v-model="productCoilBo.status" placeholder="全部" clearable style="width: 100px;">
|
||||
<el-option label="在库" :value="0" />
|
||||
<el-option label="已发货" :value="1" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="质量状态">
|
||||
<muti-select v-model="productCoilBo.qualityStatusCsv" :options="dict.type.coil_quality_status" placeholder="请选择品质" clearable style="width: 160px;" />
|
||||
</el-form-item>
|
||||
<template v-if="productCoilBo.itemType">
|
||||
<el-form-item label="品名">
|
||||
<muti-select v-model="productCoilBo.itemName" :options="dict.type.coil_itemname" placeholder="请选择物料" clearable style="width: 160px;" />
|
||||
</el-form-item>
|
||||
<el-form-item label="材质">
|
||||
<muti-select v-model="productCoilBo.itemMaterial" :options="dict.type.coil_material" placeholder="请选择材质" clearable style="width: 160px;" />
|
||||
</el-form-item>
|
||||
<el-form-item label="规格">
|
||||
<memo-input v-model="productCoilBo.itemSpecification" storageKey="coilSpec" placeholder="请选择规格" clearable style="width: 160px;" />
|
||||
</el-form-item>
|
||||
<el-form-item label="厂家">
|
||||
<muti-select v-model="productCoilBo.itemManufacturer" :options="dict.type.coil_manufacturer" placeholder="请选择厂家" clearable style="width: 160px;" />
|
||||
</el-form-item>
|
||||
</template>
|
||||
<el-form-item>
|
||||
<el-button type="primary" size="mini" @click="handleProductFilter">查询</el-button>
|
||||
<el-button size="mini" @click="resetProductFilter">重置</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<CoilTable :data="productList || []" table-height="calc(100vh - 290px)"
|
||||
:pagination="productPagination" :total-statistics="productTotalStatistics"
|
||||
@page-change="handleProductPageChange" />
|
||||
</div>
|
||||
</el-tab-pane>
|
||||
<el-tab-pane label="计划发货" name="planDelivery">
|
||||
<div v-loading="deliveryCoilLoading">
|
||||
<CoilTable :data="deliveryList || []" table-height="calc(100vh - 240px)"
|
||||
<el-form :inline="true" size="small" class="coil-filter-bar" style="margin-bottom: 8px;">
|
||||
<el-form-item label="入场卷号">
|
||||
<el-input v-model="deliveryCoilBo.enterCoilNo" placeholder="请输入入场钢卷号" clearable @keyup.enter.native="handleDeliveryFilter" style="width: 160px;" />
|
||||
</el-form-item>
|
||||
<el-form-item label="当前卷号">
|
||||
<el-input v-model="deliveryCoilBo.currentCoilNo" placeholder="请输入当前钢卷号" clearable @keyup.enter.native="handleDeliveryFilter" style="width: 160px;" />
|
||||
</el-form-item>
|
||||
<el-form-item label="逻辑库区">
|
||||
<warehouse-select v-model="deliveryCoilBo.warehouseId" placeholder="请选择逻辑库位" style="width: 160px;" clearable />
|
||||
</el-form-item>
|
||||
<el-form-item label="物料类型">
|
||||
<el-select v-model="deliveryCoilBo.itemType" placeholder="全部" clearable style="width: 100px;" @change="handleDeliveryFilter">
|
||||
<el-option label="成品" value="product" />
|
||||
<el-option label="原料" value="raw_material" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="质量状态">
|
||||
<muti-select v-model="deliveryCoilBo.qualityStatusCsv" :options="dict.type.coil_quality_status" placeholder="请选择品质" clearable style="width: 160px;" />
|
||||
</el-form-item>
|
||||
<template v-if="deliveryCoilBo.itemType">
|
||||
<el-form-item label="品名">
|
||||
<muti-select v-model="deliveryCoilBo.itemName" :options="dict.type.coil_itemname" placeholder="请选择物料" clearable style="width: 160px;" />
|
||||
</el-form-item>
|
||||
<el-form-item label="材质">
|
||||
<muti-select v-model="deliveryCoilBo.itemMaterial" :options="dict.type.coil_material" placeholder="请选择材质" clearable style="width: 160px;" />
|
||||
</el-form-item>
|
||||
<el-form-item label="规格">
|
||||
<memo-input v-model="deliveryCoilBo.itemSpecification" storageKey="coilSpec" placeholder="请选择规格" clearable style="width: 160px;" />
|
||||
</el-form-item>
|
||||
<el-form-item label="厂家">
|
||||
<muti-select v-model="deliveryCoilBo.itemManufacturer" :options="dict.type.coil_manufacturer" placeholder="请选择厂家" clearable style="width: 160px;" />
|
||||
</el-form-item>
|
||||
</template>
|
||||
<el-form-item>
|
||||
<el-button type="primary" size="mini" @click="handleDeliveryFilter">查询</el-button>
|
||||
<el-button size="mini" @click="resetDeliveryFilter">重置</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<CoilTable :data="deliveryList || []" table-height="calc(100vh - 290px)"
|
||||
:pagination="deliveryPagination" :total-statistics="deliveryTotalStatistics"
|
||||
@page-change="handleDeliveryPageChange" />
|
||||
</div>
|
||||
@@ -127,6 +209,9 @@ import { listData, getData, delData, addData, updateData } from "@/api/system/di
|
||||
import { optionselect as getDictOptionselect, getType } from "@/api/system/dict/type";
|
||||
import CoilTable from "../components/CoilTable.vue";
|
||||
import DeliveryTable from "../components/DeliveryTable.vue";
|
||||
import WarehouseSelect from "@/components/KLPService/WarehouseSelect";
|
||||
import MemoInput from "@/components/MemoInput";
|
||||
import MutiSelect from "@/components/MutiSelect";
|
||||
|
||||
import { listDeliveryWaybill } from "@/api/wms/deliveryWaybill";
|
||||
import { listCustomer } from "@/api/crm/customer";
|
||||
@@ -135,8 +220,8 @@ import { listProductCoilsBySalesman, getProductCoilsStatisticsBySalesman, listDe
|
||||
|
||||
export default {
|
||||
name: "Data",
|
||||
components: { DragResizePanel, CoilTable, DeliveryTable },
|
||||
dicts: ['sys_normal_disable'],
|
||||
components: { DragResizePanel, CoilTable, DeliveryTable, WarehouseSelect, MemoInput, MutiSelect },
|
||||
dicts: ['sys_normal_disable', 'coil_itemname', 'coil_material', 'coil_manufacturer', 'coil_quality_status'],
|
||||
data() {
|
||||
return {
|
||||
// 遮罩层
|
||||
@@ -181,6 +266,8 @@ export default {
|
||||
productPagination: { total: 0, currentPage: 1, pageSize: 20 },
|
||||
// 生产成果统计
|
||||
productCoilStatistics: {},
|
||||
// 生产成果筛选
|
||||
productCoilBo: {},
|
||||
// 生产成果加载
|
||||
productCoilLoading: false,
|
||||
// 计划发货列表
|
||||
@@ -189,6 +276,8 @@ export default {
|
||||
deliveryPagination: { total: 0, currentPage: 1, pageSize: 20 },
|
||||
// 计划发货统计
|
||||
deliveryCoilStatistics: {},
|
||||
// 计划发货筛选
|
||||
deliveryCoilBo: {},
|
||||
// 计划发货加载
|
||||
deliveryCoilLoading: false,
|
||||
// 发货单据列表
|
||||
@@ -292,7 +381,7 @@ export default {
|
||||
return Promise.resolve();
|
||||
}
|
||||
this.rightLoading = true;
|
||||
|
||||
|
||||
switch (tabName) {
|
||||
case 'customer':
|
||||
return listCustomer({
|
||||
@@ -349,9 +438,10 @@ export default {
|
||||
fetchProductCoils(salesman) {
|
||||
this.productCoilLoading = true;
|
||||
return listProductCoilsBySalesman({
|
||||
salesman: salesman,
|
||||
salesman,
|
||||
pageNum: this.productPagination.currentPage,
|
||||
pageSize: this.productPagination.pageSize
|
||||
pageSize: this.productPagination.pageSize,
|
||||
coilBo: this.productCoilBo
|
||||
}).then(res => {
|
||||
this.productList = res.rows || [];
|
||||
this.productPagination.total = res.total || 0;
|
||||
@@ -364,16 +454,29 @@ export default {
|
||||
});
|
||||
},
|
||||
fetchProductCoilStatistics(salesman) {
|
||||
getProductCoilsStatisticsBySalesman(salesman).then(res => {
|
||||
getProductCoilsStatisticsBySalesman({ salesman, coilBo: this.productCoilBo }).then(res => {
|
||||
this.productCoilStatistics = res.data || {};
|
||||
});
|
||||
},
|
||||
handleProductFilter() {
|
||||
this.productCoilBo.selectType = this.productCoilBo.itemType || '';
|
||||
this.productPagination.currentPage = 1;
|
||||
if (this.selectedItem) {
|
||||
this.fetchProductCoils(this.selectedItem.dictValue);
|
||||
this.fetchProductCoilStatistics(this.selectedItem.dictValue);
|
||||
}
|
||||
},
|
||||
resetProductFilter() {
|
||||
this.productCoilBo = {};
|
||||
this.handleProductFilter();
|
||||
},
|
||||
fetchDeliveryCoils(principal) {
|
||||
this.deliveryCoilLoading = true;
|
||||
return listDeliveryCoilsByPrincipal({
|
||||
principal: principal,
|
||||
principal,
|
||||
pageNum: this.deliveryPagination.currentPage,
|
||||
pageSize: this.deliveryPagination.pageSize
|
||||
pageSize: this.deliveryPagination.pageSize,
|
||||
coilBo: this.deliveryCoilBo
|
||||
}).then(res => {
|
||||
this.deliveryList = res.rows || [];
|
||||
this.deliveryPagination.total = res.total || 0;
|
||||
@@ -386,10 +489,22 @@ export default {
|
||||
});
|
||||
},
|
||||
fetchDeliveryCoilStatistics(principal) {
|
||||
getDeliveryCoilsStatisticsByPrincipal(principal).then(res => {
|
||||
getDeliveryCoilsStatisticsByPrincipal({ principal, coilBo: this.deliveryCoilBo }).then(res => {
|
||||
this.deliveryCoilStatistics = res.data || {};
|
||||
});
|
||||
},
|
||||
handleDeliveryFilter() {
|
||||
this.deliveryCoilBo.selectType = this.deliveryCoilBo.itemType || '';
|
||||
this.deliveryPagination.currentPage = 1;
|
||||
if (this.selectedItem) {
|
||||
this.fetchDeliveryCoils(this.selectedItem.dictValue);
|
||||
this.fetchDeliveryCoilStatistics(this.selectedItem.dictValue);
|
||||
}
|
||||
},
|
||||
resetDeliveryFilter() {
|
||||
this.deliveryCoilBo = {};
|
||||
this.handleDeliveryFilter();
|
||||
},
|
||||
handleProductPageChange({ currentPage, pageSize }) {
|
||||
this.productPagination.currentPage = currentPage;
|
||||
this.productPagination.pageSize = pageSize;
|
||||
|
||||
580
klp-ui/src/views/perf/dept/index.vue
Normal file
580
klp-ui/src/views/perf/dept/index.vue
Normal file
@@ -0,0 +1,580 @@
|
||||
<template>
|
||||
<div class="perf-dept-page">
|
||||
<div class="main-layout">
|
||||
<!-- Left: Department List -->
|
||||
<el-card class="dept-list-card">
|
||||
<div slot="header" class="card-header">
|
||||
<span class="header-title">部门列表</span>
|
||||
<el-tooltip content="点击后输入部门名称,按回车保存" placement="top" :open-delay="500">
|
||||
<el-button size="mini" type="primary" icon="el-icon-plus" :disabled="editingDeptId !== null" @click="startAddDept">新增</el-button>
|
||||
</el-tooltip>
|
||||
</div>
|
||||
<div class="dept-tip">左侧选择部门,右侧显示该部门的员工</div>
|
||||
<div v-loading="deptLoading" class="dept-custom-list">
|
||||
<!-- 新增行 -->
|
||||
<div v-if="showAddInput" class="dept-item dept-item--input">
|
||||
<el-input
|
||||
v-model="newDeptName"
|
||||
size="small"
|
||||
placeholder="请输入部门名称,按回车保存"
|
||||
ref="addInput"
|
||||
:disabled="deptSubmitting"
|
||||
@keyup.enter.native="submitAddDept"
|
||||
@keyup.esc.native="cancelAddDept"
|
||||
@blur="submitAddDept"
|
||||
>
|
||||
<template v-if="deptSubmitting" slot="prefix"><i class="el-icon-loading" /></template>
|
||||
</el-input>
|
||||
</div>
|
||||
<div
|
||||
v-for="dept in deptList"
|
||||
:key="dept.id"
|
||||
class="dept-item"
|
||||
:class="{ active: currentDeptId === dept.id }"
|
||||
@click="handleDeptSelect(dept)"
|
||||
>
|
||||
<!-- 编辑态 -->
|
||||
<template v-if="editingDeptId === dept.id">
|
||||
<el-input
|
||||
v-model="editDeptName"
|
||||
size="small"
|
||||
ref="editInput"
|
||||
:disabled="deptSubmitting"
|
||||
@keyup.enter.native="submitEditDept(dept)"
|
||||
@keyup.esc.native="cancelEditDept"
|
||||
@blur="submitEditDept(dept)"
|
||||
@click.stop
|
||||
>
|
||||
<template v-if="deptSubmitting" slot="prefix"><i class="el-icon-loading" /></template>
|
||||
</el-input>
|
||||
</template>
|
||||
<!-- 展示态 -->
|
||||
<template v-else>
|
||||
<span class="dept-name">{{ dept.deptName }}</span>
|
||||
<span class="dept-actions">
|
||||
<el-tooltip content="修改部门名称" placement="top" :open-delay="500">
|
||||
<el-button type="text" size="mini" icon="el-icon-edit" @click.stop="startEditDept(dept)">编辑</el-button>
|
||||
</el-tooltip>
|
||||
<el-tooltip content="删除此部门及其员工" placement="top" :open-delay="500">
|
||||
<el-button type="text" size="mini" icon="el-icon-delete" @click.stop="handleDelDept(dept)">删除</el-button>
|
||||
</el-tooltip>
|
||||
</span>
|
||||
</template>
|
||||
</div>
|
||||
<div v-if="!deptLoading && deptList.length === 0 && !showAddInput" class="dept-empty">暂无部门数据</div>
|
||||
</div>
|
||||
</el-card>
|
||||
|
||||
<!-- Right: Employee List -->
|
||||
<el-card class="employee-list-card">
|
||||
<div slot="header" class="card-header">
|
||||
<span class="header-title">员工列表</span>
|
||||
<div class="header-actions">
|
||||
<el-input v-model="empQuery.name" size="small" placeholder="姓名" clearable style="width: 140px" @keyup.enter.native="loadEmpList" />
|
||||
<el-select v-model="empQuery.isLeave" size="small" placeholder="在职状态" clearable style="width: 110px" @change="loadEmpList">
|
||||
<el-option label="在职" :value="0" />
|
||||
<el-option label="离职" :value="1" />
|
||||
</el-select>
|
||||
<el-button size="small" type="primary" icon="el-icon-search" @click="loadEmpList">查询</el-button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<el-table v-loading="empLoading" :data="empList" stripe :empty-text="currentDeptId ? '暂无员工数据' : '请先在左侧点击选择一个部门'">
|
||||
<el-table-column prop="name" label="姓名" min-width="100" />
|
||||
<el-table-column prop="dept" label="部门" min-width="120" />
|
||||
<el-table-column prop="jobType" label="岗位工种" min-width="120" />
|
||||
<el-table-column prop="isLeave" label="在职状态" min-width="90" align="center">
|
||||
<template slot-scope="scope">
|
||||
<el-tag :type="scope.row.isLeave === 0 ? 'success' : 'info'" size="small">{{ scope.row.isLeave === 0 ? '在职' : '离职' }}</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="baseSalary" label="底薪(元)" min-width="110" align="right" />
|
||||
<el-table-column prop="perfBase" label="绩效基数(元)" min-width="120" align="right" />
|
||||
<el-table-column prop="posCoeffDefault" label="岗位系数" min-width="100" align="center" />
|
||||
<el-table-column prop="remark" label="备注" min-width="160" show-overflow-tooltip />
|
||||
<el-table-column label="操作" width="100" align="center" fixed="right">
|
||||
<template slot-scope="scope">
|
||||
<el-tooltip content="修改该员工的绩效薪资信息" placement="top" :open-delay="500">
|
||||
<el-button type="text" size="mini" icon="el-icon-edit" @click="handleEditEmp(scope.row)">编辑</el-button>
|
||||
</el-tooltip>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
|
||||
<div class="pagination-wrapper">
|
||||
<el-pagination
|
||||
:current-page="empQuery.pageNum"
|
||||
:page-sizes="[10, 20, 50, 100]"
|
||||
:page-size="empQuery.pageSize"
|
||||
:total="empTotal"
|
||||
layout="total, sizes, prev, pager, next, jumper"
|
||||
@size-change="handleEmpSizeChange"
|
||||
@current-change="handleEmpPageChange"
|
||||
/>
|
||||
</div>
|
||||
</el-card>
|
||||
</div>
|
||||
|
||||
<!-- 底薪编辑弹窗 -->
|
||||
<el-dialog :visible.sync="empDialogVisible" title="修改薪资信息" width="480px" append-to-body @closed="resetEmpForm">
|
||||
<!-- 员工信息卡片 -->
|
||||
<div class="emp-info-card">
|
||||
<div class="emp-info-row" v-for="(item, idx) in empInfoRows" :key="idx" :class="{ 'emp-info-row--alt': idx % 2 === 1 }">
|
||||
<span class="emp-info-label">{{ item.label }}</span>
|
||||
<span class="emp-info-value" :class="{ 'emp-info-value--dim': !item.value }">{{ item.value || '-' }}</span>
|
||||
</div>
|
||||
</div>
|
||||
<!-- 薪资编辑区 -->
|
||||
<div class="emp-salary-section">
|
||||
<div class="emp-salary-title">薪资信息</div>
|
||||
<el-form ref="empFormRef" :model="empForm" :rules="empRules" label-width="96px" size="small">
|
||||
<el-form-item label="底薪(元)" prop="baseSalary">
|
||||
<el-input v-model="empForm.baseSalary" placeholder="请输入底薪" />
|
||||
</el-form-item>
|
||||
<el-form-item label="绩效基数(元)" prop="perfBase">
|
||||
<el-input v-model="empForm.perfBase" placeholder="请输入绩效基数" />
|
||||
</el-form-item>
|
||||
<el-form-item label="岗位系数" prop="posCoeffDefault">
|
||||
<el-input v-model="empForm.posCoeffDefault" placeholder="请输入岗位系数" />
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</div>
|
||||
<div slot="footer">
|
||||
<el-button @click="empDialogVisible = false">取消</el-button>
|
||||
<el-button type="primary" :loading="empSubmitting" @click="submitEmp">保存</el-button>
|
||||
</div>
|
||||
</el-dialog>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { listPerfDept, addPerfDept, updatePerfDept, delPerfDept } from '@/api/perf/dept'
|
||||
import { listEmployeeInfo, updateEmployeeInfo } from '@/api/wms/employeeInfo'
|
||||
|
||||
export default {
|
||||
name: 'PerfDept',
|
||||
data() {
|
||||
return {
|
||||
// 部门
|
||||
deptLoading: false,
|
||||
deptList: [],
|
||||
currentDeptId: null,
|
||||
currentDeptName: '',
|
||||
showAddInput: false,
|
||||
newDeptName: '',
|
||||
deptSubmitting: false,
|
||||
editingDeptId: null,
|
||||
editDeptName: '',
|
||||
|
||||
// 员工
|
||||
empLoading: false,
|
||||
empList: [],
|
||||
empTotal: 0,
|
||||
empQuery: {
|
||||
pageNum: 1,
|
||||
pageSize: 10,
|
||||
name: '',
|
||||
isLeave: null
|
||||
},
|
||||
empDialogVisible: false,
|
||||
empSubmitting: false,
|
||||
empForm: {},
|
||||
empRules: {
|
||||
baseSalary: [
|
||||
{ required: true, message: '请输入底薪', trigger: 'blur' },
|
||||
{ pattern: /^\d+(\.\d{1,2})?$/, message: '请输入有效的金额', trigger: 'blur' }
|
||||
],
|
||||
perfBase: [
|
||||
{ required: true, message: '请输入绩效基数', trigger: 'blur' },
|
||||
{ pattern: /^\d+(\.\d{1,2})?$/, message: '请输入有效的金额', trigger: 'blur' }
|
||||
],
|
||||
posCoeffDefault: [
|
||||
{ pattern: /^\d+(\.\d{1,2})?$/, message: '请输入有效的系数', trigger: 'blur' }
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
created() {
|
||||
this.loadDeptList()
|
||||
},
|
||||
computed: {
|
||||
empInfoRows() {
|
||||
return [
|
||||
{ label: '姓名', value: this.empForm.name },
|
||||
{ label: '部门', value: this.empForm.dept },
|
||||
{ label: '岗位工种', value: this.empForm.jobType },
|
||||
{ label: '在职状态', value: this.empForm.isLeave === 0 ? '在职' : this.empForm.isLeave === 1 ? '离职' : '' },
|
||||
{ label: '备注', value: this.empForm.remark }
|
||||
]
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
// ========== 部门 ==========
|
||||
loadDeptList() {
|
||||
this.deptLoading = true
|
||||
listPerfDept({}).then(res => {
|
||||
this.deptList = res.rows || []
|
||||
}).finally(() => {
|
||||
this.deptLoading = false
|
||||
})
|
||||
},
|
||||
handleDeptSelect(row) {
|
||||
if (row) {
|
||||
this.currentDeptId = row.id
|
||||
this.currentDeptName = row.deptName
|
||||
this.empQuery.pageNum = 1
|
||||
this.loadEmpList()
|
||||
} else {
|
||||
this.currentDeptId = null
|
||||
this.currentDeptName = ''
|
||||
this.empList = []
|
||||
this.empTotal = 0
|
||||
}
|
||||
},
|
||||
startAddDept() {
|
||||
this.showAddInput = true
|
||||
this.newDeptName = ''
|
||||
this.$nextTick(() => { this.$refs.addInput && this.$refs.addInput.focus() })
|
||||
},
|
||||
cancelAddDept() {
|
||||
this.showAddInput = false
|
||||
this.newDeptName = ''
|
||||
},
|
||||
submitAddDept() {
|
||||
const name = this.newDeptName.trim()
|
||||
if (!name) {
|
||||
this.showAddInput = false
|
||||
return
|
||||
}
|
||||
if (this.deptSubmitting) return
|
||||
this.deptSubmitting = true
|
||||
addPerfDept({ deptName: name, orderNum: 0, hasBaoSalary: 0, status: '0' }).then(() => {
|
||||
this.$message.success('新增成功')
|
||||
this.showAddInput = false
|
||||
this.newDeptName = ''
|
||||
this.loadDeptList()
|
||||
}).finally(() => {
|
||||
this.deptSubmitting = false
|
||||
})
|
||||
},
|
||||
startEditDept(dept) {
|
||||
this.editingDeptId = dept.id
|
||||
this.editDeptName = dept.deptName
|
||||
this.$nextTick(() => {
|
||||
const input = this.$refs.editInput
|
||||
if (input && input[0]) input[0].focus()
|
||||
})
|
||||
},
|
||||
cancelEditDept() {
|
||||
this.editingDeptId = null
|
||||
this.editDeptName = ''
|
||||
},
|
||||
submitEditDept(dept) {
|
||||
const name = this.editDeptName.trim()
|
||||
if (!name || name === dept.deptName) {
|
||||
this.editingDeptId = null
|
||||
return
|
||||
}
|
||||
if (this.deptSubmitting) return
|
||||
this.deptSubmitting = true
|
||||
updatePerfDept({ id: dept.id, deptName: name }).then(() => {
|
||||
this.$message.success('修改成功')
|
||||
this.editingDeptId = null
|
||||
this.loadDeptList()
|
||||
}).finally(() => {
|
||||
this.deptSubmitting = false
|
||||
})
|
||||
},
|
||||
handleDelDept(row) {
|
||||
this.$confirm(`确认删除部门"${row.deptName}"吗?删除后该部门下的员工数据也将一并移除。`, '提示', { type: 'warning' }).then(() => {
|
||||
delPerfDept(row.id).then(() => {
|
||||
this.$message.success('删除成功')
|
||||
if (this.currentDeptId === row.id) {
|
||||
this.currentDeptId = null
|
||||
this.currentDeptName = ''
|
||||
this.empList = []
|
||||
this.empTotal = 0
|
||||
}
|
||||
this.loadDeptList()
|
||||
})
|
||||
}).catch(() => {})
|
||||
},
|
||||
|
||||
// ========== 员工 ==========
|
||||
loadEmpList() {
|
||||
if (!this.currentDeptId) return
|
||||
this.empLoading = true
|
||||
const params = {
|
||||
...this.empQuery,
|
||||
dept: this.currentDeptName
|
||||
}
|
||||
// 处理 isLeave 空字符串,不传无效值
|
||||
if (params.isLeave === '' || params.isLeave === null || params.isLeave === undefined) {
|
||||
delete params.isLeave
|
||||
}
|
||||
listEmployeeInfo(params).then(res => {
|
||||
this.empList = res.rows || []
|
||||
this.empTotal = res.total || 0
|
||||
}).finally(() => {
|
||||
this.empLoading = false
|
||||
})
|
||||
},
|
||||
handleEmpSizeChange(val) {
|
||||
this.empQuery.pageSize = val
|
||||
this.loadEmpList()
|
||||
},
|
||||
handleEmpPageChange(val) {
|
||||
this.empQuery.pageNum = val
|
||||
this.loadEmpList()
|
||||
},
|
||||
handleEditEmp(row) {
|
||||
this.empForm = { ...row }
|
||||
this.empDialogVisible = true
|
||||
this.$nextTick(() => { this.$refs.empFormRef && this.$refs.empFormRef.clearValidate() })
|
||||
},
|
||||
submitEmp() {
|
||||
this.$refs.empFormRef.validate(valid => {
|
||||
if (!valid) return
|
||||
this.empSubmitting = true
|
||||
updateEmployeeInfo({
|
||||
infoId: this.empForm.infoId,
|
||||
baseSalary: this.empForm.baseSalary,
|
||||
perfBase: this.empForm.perfBase,
|
||||
posCoeffDefault: this.empForm.posCoeffDefault
|
||||
}).then(() => {
|
||||
this.$message.success('修改成功')
|
||||
this.empDialogVisible = false
|
||||
this.loadEmpList()
|
||||
}).finally(() => {
|
||||
this.empSubmitting = false
|
||||
})
|
||||
})
|
||||
},
|
||||
resetEmpForm() {
|
||||
this.empForm = {}
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.perf-dept-page {
|
||||
height: calc(100vh - 84px);
|
||||
padding: 0;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.main-layout {
|
||||
display: grid;
|
||||
grid-template-columns: 240px 1fr;
|
||||
gap: 12px;
|
||||
height: 100%;
|
||||
padding: 12px;
|
||||
}
|
||||
|
||||
.dept-list-card,
|
||||
.employee-list-card {
|
||||
border: none;
|
||||
box-shadow: none;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
overflow: hidden;
|
||||
::v-deep .el-card__header {
|
||||
border: none;
|
||||
padding: 10px 16px;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
::v-deep .el-card__body {
|
||||
border: none;
|
||||
flex: 1;
|
||||
overflow: hidden;
|
||||
}
|
||||
}
|
||||
|
||||
.dept-list-card ::v-deep .el-card__body {
|
||||
padding: 0;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.employee-list-card {
|
||||
::v-deep .el-card__body {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
}
|
||||
|
||||
.employee-list-card ::v-deep .el-table {
|
||||
border: none;
|
||||
flex: 1;
|
||||
&::before, &::after {
|
||||
display: none;
|
||||
}
|
||||
th {
|
||||
border: none;
|
||||
}
|
||||
td {
|
||||
border: none;
|
||||
}
|
||||
}
|
||||
|
||||
.card-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
|
||||
.header-title {
|
||||
font-size: 14px;
|
||||
font-weight: 600;
|
||||
color: #303133;
|
||||
}
|
||||
|
||||
.header-actions {
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
}
|
||||
|
||||
.dept-custom-list {
|
||||
flex: 1;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.dept-tip {
|
||||
padding: 6px 12px;
|
||||
font-size: 12px;
|
||||
color: #909399;
|
||||
background-color: #f4f4f5;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.dept-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding: 8px 12px;
|
||||
cursor: pointer;
|
||||
transition: background-color 0.15s;
|
||||
|
||||
&:hover {
|
||||
background-color: #f5f7fa;
|
||||
}
|
||||
|
||||
&.active {
|
||||
background-color: #ecf5ff;
|
||||
|
||||
.dept-name {
|
||||
color: #409eff;
|
||||
font-weight: 600;
|
||||
}
|
||||
}
|
||||
|
||||
.dept-name {
|
||||
font-size: 13px;
|
||||
color: #303133;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.dept-actions {
|
||||
display: flex;
|
||||
gap: 2px;
|
||||
}
|
||||
|
||||
&--input {
|
||||
padding: 4px 8px;
|
||||
background-color: #fff;
|
||||
}
|
||||
}
|
||||
|
||||
.dept-empty {
|
||||
padding: 24px 0;
|
||||
text-align: center;
|
||||
color: #c0c4cc;
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
.pagination-wrapper {
|
||||
margin-top: 12px;
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.emp-field-readonly {
|
||||
color: #606266;
|
||||
font-size: 13px;
|
||||
line-height: 32px;
|
||||
}
|
||||
|
||||
/* 员工信息卡片 */
|
||||
.emp-info-card {
|
||||
margin-bottom: 16px;
|
||||
border: 1px solid #ebeef5;
|
||||
border-radius: 4px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.emp-info-row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 0;
|
||||
min-height: 36px;
|
||||
transition: background-color 0.12s;
|
||||
|
||||
&:hover {
|
||||
background-color: #f5f7fa;
|
||||
}
|
||||
|
||||
&--alt {
|
||||
background-color: #fafbfc;
|
||||
}
|
||||
}
|
||||
|
||||
.emp-info-label {
|
||||
flex: 0 0 80px;
|
||||
padding: 6px 12px;
|
||||
font-size: 12px;
|
||||
color: #909399;
|
||||
text-align: right;
|
||||
letter-spacing: 1px;
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
.emp-info-value {
|
||||
flex: 1;
|
||||
padding: 6px 12px 6px 0;
|
||||
font-size: 14px;
|
||||
font-weight: 500;
|
||||
color: #303133;
|
||||
letter-spacing: 0.5px;
|
||||
|
||||
&--dim {
|
||||
color: #c0c4cc;
|
||||
font-weight: 400;
|
||||
}
|
||||
}
|
||||
|
||||
/* 薪资编辑区 */
|
||||
.emp-salary-section {
|
||||
.emp-salary-title {
|
||||
font-size: 13px;
|
||||
font-weight: 600;
|
||||
color: #606266;
|
||||
margin-bottom: 8px;
|
||||
padding-left: 2px;
|
||||
letter-spacing: 1px;
|
||||
}
|
||||
|
||||
::v-deep .el-form-item {
|
||||
margin-bottom: 14px;
|
||||
|
||||
&:last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
226
klp-ui/src/views/perf/index/DeptConfigPanel.vue
Normal file
226
klp-ui/src/views/perf/index/DeptConfigPanel.vue
Normal file
@@ -0,0 +1,226 @@
|
||||
<template>
|
||||
<div class="dept-config-panel" v-loading="loading">
|
||||
<div class="panel-body">
|
||||
<div v-if="!hasConfig && !loading" class="empty-state">
|
||||
<el-empty description="暂无考核参数配置">
|
||||
<el-button type="primary" size="small" @click="handleAdd">新增配置</el-button>
|
||||
</el-empty>
|
||||
</div>
|
||||
|
||||
<el-form v-else ref="formRef" :model="form" :rules="rules" label-width="130px" size="small" class="config-form">
|
||||
<el-row :gutter="24">
|
||||
<el-col :span="8">
|
||||
<el-form-item label="月产量目标" prop="monthlyProductionTarget">
|
||||
<el-input-number v-model="form.monthlyProductionTarget" :min="0" :precision="2" controls-position="right" style="width: 100%" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="8">
|
||||
<el-form-item label="产量单位" prop="productionUnit">
|
||||
<el-select v-model="form.productionUnit" placeholder="请选择" style="width: 100%">
|
||||
<el-option label="吨" value="吨" />
|
||||
<el-option label="m" value="m" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="8">
|
||||
<el-form-item label="绩效单价(元)" prop="perfUnitPrice">
|
||||
<el-input-number v-model="form.perfUnitPrice" :min="0" :precision="2" controls-position="right" style="width: 100%" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
|
||||
<el-row :gutter="24">
|
||||
<el-col :span="8">
|
||||
<el-form-item label="生产班数" prop="productionShifts">
|
||||
<el-input-number v-model="form.productionShifts" :min="0" :step="1" controls-position="right" style="width: 100%" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="8">
|
||||
<el-form-item label="停机保底班数" prop="guaranteedShifts">
|
||||
<el-input-number v-model="form.guaranteedShifts" :min="0" :step="1" controls-position="right" style="width: 100%" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="8">
|
||||
<el-form-item label="车间系数" prop="deptCoeff">
|
||||
<el-input-number v-model="form.deptCoeff" :min="0" :precision="2" :step="0.1" controls-position="right" style="width: 100%" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
|
||||
<el-row :gutter="24">
|
||||
<el-col :span="8">
|
||||
<el-form-item label="薪资模式" prop="salaryMode">
|
||||
<el-radio-group v-model="form.salaryMode">
|
||||
<el-radio :label="0">普通模式</el-radio>
|
||||
<el-radio :label="1">保底模式</el-radio>
|
||||
</el-radio-group>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="8" v-if="form.salaryMode === 1">
|
||||
<el-form-item label="保底薪资除数" prop="baseDivisor">
|
||||
<el-input-number v-model="form.baseDivisor" :min="1" :step="1" controls-position="right" style="width: 100%" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
|
||||
<el-row :gutter="24">
|
||||
<el-col :span="16">
|
||||
<el-form-item label="备注">
|
||||
<el-input v-model="form.remark" type="textarea" :rows="2" maxlength="200" show-word-limit />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
|
||||
<el-row>
|
||||
<el-col :span="24" style="text-align: center; padding-top: 12px;">
|
||||
<el-button type="primary" size="small" :loading="submitting" @click="handleSave">保存</el-button>
|
||||
<el-button size="small" v-if="!isNew" @click="handleReset">重置</el-button>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</el-form>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { listPerfDeptConfig, addPerfDeptConfig, updatePerfDeptConfig } from '@/api/perf/deptConfig'
|
||||
|
||||
export default {
|
||||
name: 'DeptConfigPanel',
|
||||
props: {
|
||||
deptId: { type: [Number, String], default: null },
|
||||
period: { type: String, default: '' }
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
loading: false,
|
||||
submitting: false,
|
||||
hasConfig: false,
|
||||
isNew: false,
|
||||
configId: null,
|
||||
form: this.getEmptyForm(),
|
||||
rules: {
|
||||
monthlyProductionTarget: [{ required: true, message: '请输入月产量目标', trigger: 'blur' }],
|
||||
productionUnit: [{ required: true, message: '请选择产量单位', trigger: 'change' }],
|
||||
perfUnitPrice: [{ required: true, message: '请输入绩效单价', trigger: 'blur' }],
|
||||
productionShifts: [{ required: true, message: '请输入生产班数', trigger: 'blur' }],
|
||||
deptCoeff: [{ required: true, message: '请输入车间系数', trigger: 'blur' }]
|
||||
}
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
deptId: { immediate: true, handler: 'loadData' },
|
||||
period: 'loadData'
|
||||
},
|
||||
methods: {
|
||||
getEmptyForm() {
|
||||
return {
|
||||
monthlyProductionTarget: null,
|
||||
productionUnit: '吨',
|
||||
perfUnitPrice: null,
|
||||
productionShifts: null,
|
||||
guaranteedShifts: 0,
|
||||
deptCoeff: 1.0,
|
||||
salaryMode: 0,
|
||||
baseDivisor: 60,
|
||||
remark: ''
|
||||
}
|
||||
},
|
||||
loadData() {
|
||||
if (!this.deptId || !this.period) return
|
||||
this.loading = true
|
||||
this.hasConfig = false
|
||||
this.isNew = false
|
||||
this.configId = null
|
||||
listPerfDeptConfig({ deptId: this.deptId, month: this.period + '-01' }).then(res => {
|
||||
const rows = res.rows || []
|
||||
if (rows.length > 0) {
|
||||
const item = rows[0]
|
||||
this.hasConfig = true
|
||||
this.isNew = false
|
||||
this.configId = item.id
|
||||
this.form = {
|
||||
monthlyProductionTarget: item.monthlyProductionTarget,
|
||||
productionUnit: item.productionUnit || '吨',
|
||||
perfUnitPrice: item.perfUnitPrice,
|
||||
productionShifts: item.productionShifts,
|
||||
guaranteedShifts: item.guaranteedShifts || 0,
|
||||
deptCoeff: item.deptCoeff ?? 1.0,
|
||||
salaryMode: item.salaryMode ?? 0,
|
||||
baseDivisor: item.baseDivisor ?? 60,
|
||||
remark: item.remark || ''
|
||||
}
|
||||
} else {
|
||||
this.hasConfig = false
|
||||
this.form = this.getEmptyForm()
|
||||
}
|
||||
}).finally(() => {
|
||||
this.loading = false
|
||||
})
|
||||
},
|
||||
handleAdd() {
|
||||
this.hasConfig = true
|
||||
this.isNew = true
|
||||
this.configId = null
|
||||
this.form = this.getEmptyForm()
|
||||
this.$nextTick(() => {
|
||||
this.$refs.formRef && this.$refs.formRef.clearValidate()
|
||||
})
|
||||
},
|
||||
handleSave() {
|
||||
this.$refs.formRef.validate(valid => {
|
||||
if (!valid) return
|
||||
this.submitting = true
|
||||
const data = {
|
||||
...this.form,
|
||||
deptId: this.deptId,
|
||||
month: this.period + '-01'
|
||||
}
|
||||
if (this.isNew) {
|
||||
addPerfDeptConfig(data).then(() => {
|
||||
this.$message.success('新增成功')
|
||||
this.loadData()
|
||||
}).finally(() => {
|
||||
this.submitting = false
|
||||
})
|
||||
} else {
|
||||
data.id = this.configId
|
||||
updatePerfDeptConfig(data).then(() => {
|
||||
this.$message.success('保存成功')
|
||||
this.loadData()
|
||||
}).finally(() => {
|
||||
this.submitting = false
|
||||
})
|
||||
}
|
||||
})
|
||||
},
|
||||
handleReset() {
|
||||
this.loadData()
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.dept-config-panel {
|
||||
height: 100%;
|
||||
overflow-y: auto;
|
||||
background-color: #fff;
|
||||
}
|
||||
|
||||
.panel-body {
|
||||
padding: 24px 24px 40px;
|
||||
}
|
||||
|
||||
.config-form {
|
||||
max-width: 900px;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
.empty-state {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
min-height: 300px;
|
||||
}
|
||||
</style>
|
||||
142
klp-ui/src/views/perf/index/PageHeader.vue
Normal file
142
klp-ui/src/views/perf/index/PageHeader.vue
Normal file
@@ -0,0 +1,142 @@
|
||||
<template>
|
||||
<div class="page-header">
|
||||
<div class="header-left">
|
||||
<span class="breadcrumb">{{ deptName }} · {{ period }}</span>
|
||||
</div>
|
||||
<div class="header-right">
|
||||
<div class="custom-tabs">
|
||||
<div
|
||||
v-for="tab in tabOptions"
|
||||
:key="tab.key"
|
||||
class="custom-tab"
|
||||
:class="{ active: activeTab === tab.key }"
|
||||
@click="$emit('update:activeTab', tab.key)"
|
||||
>{{ tab.label }}</div>
|
||||
</div>
|
||||
<div class="header-actions">
|
||||
<el-tooltip content="刷新" placement="top">
|
||||
<el-button class="refresh-btn" size="mini" icon="el-icon-refresh" circle @click="$emit('refresh')" />
|
||||
</el-tooltip>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'PageHeader',
|
||||
props: {
|
||||
deptName: { type: String, default: '' },
|
||||
period: { type: String, default: '' },
|
||||
activeTab: { type: String, default: '' },
|
||||
tabOptions: {
|
||||
type: Array,
|
||||
default: () => [
|
||||
{ key: 'perfConfig', label: '考核配置' },
|
||||
{ key: 'salary', label: '薪资明细' },
|
||||
{ key: 'summary', label: '统计' }
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.page-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
height: 40px;
|
||||
padding: 0 20px;
|
||||
border-bottom: 1px solid #e4e7ed;
|
||||
background-color: #fff;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.header-left {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.breadcrumb {
|
||||
font-size: 13px;
|
||||
color: #606266;
|
||||
white-space: nowrap;
|
||||
|
||||
&::before {
|
||||
content: '';
|
||||
display: inline-block;
|
||||
width: 3px;
|
||||
height: 14px;
|
||||
background-color: #409eff;
|
||||
border-radius: 2px;
|
||||
margin-right: 8px;
|
||||
vertical-align: middle;
|
||||
}
|
||||
}
|
||||
|
||||
.header-right {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
flex: 1;
|
||||
justify-content: flex-end;
|
||||
}
|
||||
|
||||
.custom-tabs {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0;
|
||||
}
|
||||
|
||||
.custom-tab {
|
||||
position: relative;
|
||||
padding: 0 16px;
|
||||
height: 40px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
font-size: 13px;
|
||||
color: #606266;
|
||||
cursor: pointer;
|
||||
transition: color 0.2s;
|
||||
user-select: none;
|
||||
|
||||
&:hover {
|
||||
color: #409eff;
|
||||
}
|
||||
|
||||
&.active {
|
||||
color: #409eff;
|
||||
font-weight: 600;
|
||||
|
||||
&::after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
left: 10px;
|
||||
right: 10px;
|
||||
height: 2px;
|
||||
background-color: #409eff;
|
||||
border-radius: 1px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.header-actions {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.refresh-btn {
|
||||
border: none;
|
||||
font-size: 14px;
|
||||
color: #909399;
|
||||
|
||||
&:hover {
|
||||
color: #409eff;
|
||||
background-color: #f0f5ff;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
669
klp-ui/src/views/perf/index/PerfConfigPanel.vue
Normal file
669
klp-ui/src/views/perf/index/PerfConfigPanel.vue
Normal file
@@ -0,0 +1,669 @@
|
||||
<template>
|
||||
<div class="perf-config-panel">
|
||||
<!-- Section 0: 考核参数 -->
|
||||
<div class="config-section">
|
||||
<div class="section-header">
|
||||
<span class="section-title">考核参数</span>
|
||||
<el-button size="small" type="primary" :loading="cfgSubmitting" @click="handleSaveCfg">{{ cfgId ? '保存' : '新增' }}</el-button>
|
||||
</div>
|
||||
<div class="cfg-form" v-loading="cfgLoading">
|
||||
<el-form v-if="cfgReady" ref="cfgFormRef" :model="cfgForm" :rules="cfgRules" label-width="110px" size="small" inline>
|
||||
<el-form-item label="月产量目标" prop="monthlyProductionTarget">
|
||||
<el-input v-model="cfgForm.monthlyProductionTarget" placeholder="月产量目标" style="width: 120px" />
|
||||
</el-form-item>
|
||||
<el-form-item label="产量单位" prop="productionUnit">
|
||||
<el-select v-model="cfgForm.productionUnit" style="width: 80px">
|
||||
<el-option label="吨" value="吨" />
|
||||
<el-option label="m" value="m" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="绩效单价(元)" prop="perfUnitPrice">
|
||||
<el-input v-model="cfgForm.perfUnitPrice" placeholder="绩效单价" style="width: 110px" />
|
||||
</el-form-item>
|
||||
<el-form-item label="生产班数" prop="productionShifts">
|
||||
<el-input v-model="cfgForm.productionShifts" placeholder="班数" style="width: 80px" />
|
||||
</el-form-item>
|
||||
<el-form-item label="保底班数" prop="guaranteedShifts">
|
||||
<el-input v-model="cfgForm.guaranteedShifts" placeholder="保底班数" style="width: 80px" />
|
||||
</el-form-item>
|
||||
<el-form-item label="车间系数" prop="deptCoeff">
|
||||
<el-input v-model="cfgForm.deptCoeff" placeholder="系数" style="width: 80px" />
|
||||
</el-form-item>
|
||||
<el-form-item label="薪资模式" prop="salaryMode">
|
||||
<el-radio-group v-model="cfgForm.salaryMode" size="small">
|
||||
<el-radio :label="0">普通</el-radio>
|
||||
<el-radio :label="1">保底</el-radio>
|
||||
</el-radio-group>
|
||||
</el-form-item>
|
||||
<el-form-item label="保底薪资除数" prop="baseDivisor" v-if="cfgForm.salaryMode === 1">
|
||||
<el-input v-model="cfgForm.baseDivisor" placeholder="除数" style="width: 80px" />
|
||||
</el-form-item>
|
||||
<el-form-item label="备注">
|
||||
<el-input v-model="cfgForm.remark" placeholder="备注" style="width: 200px" />
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Section 1: 扣款/奖励项目配置 -->
|
||||
<div class="config-section">
|
||||
<div class="section-header">
|
||||
<span class="section-title">扣款/奖励项目配置</span>
|
||||
<el-button size="small" type="primary" icon="el-icon-plus" @click="handleAddItem">新增项目</el-button>
|
||||
</div>
|
||||
<el-table :data="itemList" stripe size="small" v-loading="itemLoading" empty-text="暂无项目配置">
|
||||
<el-table-column label="类型" width="80" align="center">
|
||||
<template slot-scope="scope">
|
||||
<el-tag :type="scope.row.itemType === 2 ? 'success' : 'danger'" size="small">
|
||||
{{ scope.row.itemType === 2 ? '奖励' : '扣款' }}
|
||||
</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="itemName" label="项目名称" min-width="160" />
|
||||
<el-table-column label="月份" width="100" align="center">
|
||||
<template slot-scope="scope">{{ scope.row.month ? (scope.row.month).substring(0, 7) : '-' }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="sortOrder" label="排序" width="60" align="center" />
|
||||
<el-table-column label="启用" width="60" align="center">
|
||||
<template slot-scope="scope">
|
||||
<el-switch v-model="scope.row.isEnabled" :active-value="1" :inactive-value="0" size="small" @change="handleToggleItemEnabled(scope.row)" />
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="remark" label="备注" min-width="140" show-overflow-tooltip />
|
||||
<el-table-column label="操作" width="140" align="center" fixed="right">
|
||||
<template slot-scope="scope">
|
||||
<el-button type="text" size="mini" icon="el-icon-edit" @click="handleEditItem(scope.row)">编辑</el-button>
|
||||
<el-button type="text" size="mini" icon="el-icon-delete" style="color: #f56c6c" @click="handleDelItem(scope.row)">删除</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
</div>
|
||||
|
||||
<!-- Section 2: 岗位绩效考核模板 -->
|
||||
<div class="config-section" v-loading="ctxLoading">
|
||||
<div class="section-header">
|
||||
<span class="section-title">岗位绩效考核模板 <span class="section-hint">右键 tab 可编辑或删除</span></span>
|
||||
<el-button size="small" type="primary" icon="el-icon-plus" @click="handleAddTemplate">新增岗位</el-button>
|
||||
</div>
|
||||
<div v-if="templateList.length" class="template-tabs">
|
||||
<span
|
||||
v-for="tpl in templateList"
|
||||
:key="tpl.id"
|
||||
class="template-tab"
|
||||
:class="{ 'template-tab--active': activeTemplateId === String(tpl.id) }"
|
||||
@click="activeTemplateId = String(tpl.id); loadDims()"
|
||||
@contextmenu.prevent.stop="onTabContextMenu($event, tpl)"
|
||||
>{{ tpl.positionName }}</span>
|
||||
</div>
|
||||
|
||||
<!-- 维度表格 -->
|
||||
<div v-if="activeTpl" class="dim-section">
|
||||
<div class="dim-toolbar">
|
||||
<span class="dim-subtitle">{{ activeTpl.positionName }} — 考核维度</span>
|
||||
<el-button size="small" type="primary" icon="el-icon-plus" @click="handleAddDim">新增维度</el-button>
|
||||
</div>
|
||||
<el-table :data="dimList" stripe size="small" v-loading="dimLoading" empty-text="暂无维度配置">
|
||||
<el-table-column prop="seq" label="序号" width="60" align="center" />
|
||||
<el-table-column prop="dimName" label="考核维度" min-width="120" />
|
||||
<el-table-column prop="weight" label="权重" width="70" align="center" />
|
||||
<el-table-column prop="indicator" label="量化指标" min-width="140" show-overflow-tooltip />
|
||||
<el-table-column prop="targetValue" label="目标值" min-width="100" />
|
||||
<el-table-column prop="scoreRule" label="评分规则" min-width="180" show-overflow-tooltip />
|
||||
<el-table-column label="启用" width="60" align="center">
|
||||
<template slot-scope="scope">
|
||||
<el-switch v-model="scope.row.isEnabled" :active-value="1" :inactive-value="0" size="small" @change="handleToggleDimEnabled(scope.row)" />
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="操作" width="100" align="center">
|
||||
<template slot-scope="scope">
|
||||
<el-button type="text" size="mini" icon="el-icon-edit" @click="handleEditDim(scope.row)">编辑</el-button>
|
||||
<el-button type="text" size="mini" icon="el-icon-delete" style="color: #f56c6c" @click="handleDelDim(scope.row)">删除</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
</div>
|
||||
<div v-else class="dim-empty">点击 tab 查看维度,右键 tab 可编辑或删除模板</div>
|
||||
</div>
|
||||
|
||||
<!-- 右键菜单 -->
|
||||
<div
|
||||
v-show="ctxMenu.visible"
|
||||
class="ctx-menu"
|
||||
:style="{ left: ctxMenu.x + 'px', top: ctxMenu.y + 'px' }"
|
||||
>
|
||||
<div class="ctx-menu-item" @click="ctxEditTemplate">编辑模板</div>
|
||||
<div class="ctx-menu-item ctx-menu-item--danger" @click="ctxDelTemplate">删除模板</div>
|
||||
</div>
|
||||
|
||||
<!-- Item Dialog -->
|
||||
<el-dialog :visible.sync="itemDialogVisible" :title="itemDialogTitle" width="480px" append-to-body @closed="resetItemForm">
|
||||
<el-form ref="itemFormRef" :model="itemForm" :rules="itemRules" label-width="90px" size="small">
|
||||
<el-form-item label="类型" prop="itemType">
|
||||
<el-radio-group v-model="itemForm.itemType">
|
||||
<el-radio :label="1">扣款项目</el-radio>
|
||||
<el-radio :label="2">奖励项目</el-radio>
|
||||
</el-radio-group>
|
||||
</el-form-item>
|
||||
<el-form-item label="月份" prop="month">
|
||||
<el-date-picker v-model="itemForm.month" type="month" placeholder="选择月份" value-format="yyyy-MM-dd" style="width: 100%" />
|
||||
</el-form-item>
|
||||
<el-form-item label="项目名称" prop="itemName">
|
||||
<el-input v-model="itemForm.itemName" placeholder="请输入项目名称" />
|
||||
</el-form-item>
|
||||
<el-form-item label="排序">
|
||||
<el-input v-model="itemForm.sortOrder" placeholder="排序号" />
|
||||
</el-form-item>
|
||||
<el-form-item label="是否启用">
|
||||
<el-switch v-model="itemForm.isEnabled" :active-value="1" :inactive-value="0" />
|
||||
</el-form-item>
|
||||
<el-form-item label="备注">
|
||||
<el-input v-model="itemForm.remark" type="textarea" :rows="2" />
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<div slot="footer">
|
||||
<el-button @click="itemDialogVisible = false">取消</el-button>
|
||||
<el-button type="primary" :loading="itemSubmitting" @click="submitItem">保存</el-button>
|
||||
</div>
|
||||
</el-dialog>
|
||||
|
||||
<!-- Template Dialog -->
|
||||
<el-dialog :visible.sync="templateDialogVisible" :title="templateDialogTitle" width="480px" append-to-body @closed="resetTemplateForm">
|
||||
<el-form ref="templateFormRef" :model="templateForm" :rules="templateRules" label-width="90px" size="small">
|
||||
<el-form-item label="岗位名称" prop="positionName">
|
||||
<el-input v-model="templateForm.positionName" placeholder="与员工表岗位字段匹配,同时作为模板标题" />
|
||||
</el-form-item>
|
||||
<el-form-item label="是否启用">
|
||||
<el-switch v-model="templateForm.isEnabled" :active-value="1" :inactive-value="0" />
|
||||
</el-form-item>
|
||||
<el-form-item label="备注">
|
||||
<el-input v-model="templateForm.remark" type="textarea" :rows="2" />
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<div slot="footer">
|
||||
<el-button @click="templateDialogVisible = false">取消</el-button>
|
||||
<el-button type="primary" :loading="templateSubmitting" @click="submitTemplate">保存</el-button>
|
||||
</div>
|
||||
</el-dialog>
|
||||
|
||||
<!-- Dimension Form Dialog -->
|
||||
<el-dialog :visible.sync="dimFormVisible" :title="dimFormTitle" width="560px" append-to-body @closed="resetDimForm">
|
||||
<el-form ref="dimFormRef" :model="dimForm" :rules="dimRules" label-width="90px" size="small">
|
||||
<el-row :gutter="12">
|
||||
<el-col :span="8">
|
||||
<el-form-item label="序号" prop="seq">
|
||||
<el-input v-model="dimForm.seq" placeholder="如 1.1" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="8">
|
||||
<el-form-item label="考核维度" prop="dimName">
|
||||
<el-select v-model="dimForm.dimName" placeholder="请选择考核维度" style="width: 100%">
|
||||
<el-option label="生产效率" value="生产效率" />
|
||||
<el-option label="产品质量" value="产品质量" />
|
||||
<el-option label="成本控制" value="成本控制" />
|
||||
<el-option label="安全管理" value="安全管理" />
|
||||
<el-option label="设备维护" value="设备维护" />
|
||||
<el-option label="团队管理" value="团队管理" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="8">
|
||||
<el-form-item label="权重" prop="weight">
|
||||
<el-input v-model="dimForm.weight" placeholder="如 40%" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<el-row :gutter="12">
|
||||
<el-col :span="12">
|
||||
<el-form-item label="量化指标" prop="indicator">
|
||||
<el-input v-model="dimForm.indicator" placeholder="如:月产量达标率" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="目标值" prop="targetValue">
|
||||
<el-input v-model="dimForm.targetValue" placeholder="如:≥98%" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<el-form-item label="评分公式" prop="scoreFormula">
|
||||
<el-input v-model="dimForm.scoreFormula" placeholder="如: =IF(E4>0,25*(F4/E4),0)" />
|
||||
</el-form-item>
|
||||
<el-form-item label="评分规则" prop="scoreRule">
|
||||
<el-input v-model="dimForm.scoreRule" type="textarea" :rows="2" placeholder="评分规则文字描述" />
|
||||
</el-form-item>
|
||||
<el-form-item label="备注">
|
||||
<el-input v-model="dimForm.remark" type="textarea" :rows="2" />
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<div slot="footer">
|
||||
<el-button @click="dimFormVisible = false">取消</el-button>
|
||||
<el-button type="primary" :loading="dimSubmitting" @click="submitDim">保存</el-button>
|
||||
</div>
|
||||
</el-dialog>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { listPerfDeptItemConfig, addPerfDeptItemConfig, updatePerfDeptItemConfig, delPerfDeptItemConfig } from '@/api/perf/deptItemConfig'
|
||||
import { listPerfPositionTemplate, addPerfPositionTemplate, updatePerfPositionTemplate, delPerfPositionTemplate } from '@/api/perf/positionTemplate'
|
||||
import { listPerfTemplateDimension, addPerfTemplateDimension, updatePerfTemplateDimension, delPerfTemplateDimension } from '@/api/perf/templateDimension'
|
||||
import { listPerfDeptConfig, addPerfDeptConfig, updatePerfDeptConfig } from '@/api/perf/deptConfig'
|
||||
|
||||
export default {
|
||||
name: 'PerfConfigPanel',
|
||||
props: {
|
||||
deptId: { type: [Number, String], default: null },
|
||||
period: { type: String, default: '' }
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
pageLoading: false,
|
||||
|
||||
// Dept Config (考核参数)
|
||||
cfgLoading: false,
|
||||
cfgSubmitting: false,
|
||||
cfgReady: false,
|
||||
cfgId: null,
|
||||
cfgForm: { monthlyProductionTarget: '', productionUnit: '吨', perfUnitPrice: '', productionShifts: '', guaranteedShifts: '', deptCoeff: '', salaryMode: 0, baseDivisor: '', remark: '' },
|
||||
cfgRules: {
|
||||
monthlyProductionTarget: [{ required: true, message: '请输入月产量目标', trigger: 'blur' }],
|
||||
productionUnit: [{ required: true, message: '请选择产量单位', trigger: 'change' }],
|
||||
perfUnitPrice: [{ required: true, message: '请输入绩效单价', trigger: 'blur' }],
|
||||
productionShifts: [{ required: true, message: '请输入生产班数', trigger: 'blur' }],
|
||||
deptCoeff: [{ required: true, message: '请输入车间系数', trigger: 'blur' }]
|
||||
},
|
||||
|
||||
// Item Config
|
||||
itemLoading: false,
|
||||
itemList: [],
|
||||
itemDialogVisible: false,
|
||||
itemDialogTitle: '新增项目',
|
||||
itemSubmitting: false,
|
||||
itemForm: {},
|
||||
itemRules: {
|
||||
itemType: [{ required: true, message: '请选择类型', trigger: 'change' }],
|
||||
itemName: [{ required: true, message: '请输入项目名称', trigger: 'blur' }]
|
||||
},
|
||||
|
||||
// Template
|
||||
templateLoading: false,
|
||||
templateList: [],
|
||||
activeTemplateId: '',
|
||||
templateDialogVisible: false,
|
||||
templateDialogTitle: '新增模板',
|
||||
templateSubmitting: false,
|
||||
templateForm: {},
|
||||
templateRules: {
|
||||
positionName: [{ required: true, message: '请输入岗位名称', trigger: 'blur' }]
|
||||
},
|
||||
|
||||
// Context menu
|
||||
ctxMenu: { visible: false, x: 0, y: 0, tpl: null },
|
||||
ctxLoading: false,
|
||||
|
||||
// Dimension
|
||||
dimLoading: false,
|
||||
dimList: [],
|
||||
dimFormVisible: false,
|
||||
dimFormTitle: '新增维度',
|
||||
dimSubmitting: false,
|
||||
dimForm: {},
|
||||
dimRules: {
|
||||
dimName: [{ required: true, message: '请选择考核维度', trigger: 'change' }]
|
||||
}
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
activeTpl() {
|
||||
return this.templateList.find(t => String(t.id) === this.activeTemplateId) || null
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
deptId: { immediate: true, handler: 'loadAll' },
|
||||
period: 'loadAll'
|
||||
},
|
||||
methods: {
|
||||
loadAll() {
|
||||
if (!this.deptId) return
|
||||
this.loadDeptConfig()
|
||||
this.loadItems()
|
||||
this.loadTemplates()
|
||||
},
|
||||
// ======== Dept Config ========
|
||||
loadDeptConfig() {
|
||||
this.cfgLoading = true
|
||||
this.cfgReady = false
|
||||
listPerfDeptConfig({ deptId: this.deptId, month: this.period + '-01' }).then(res => {
|
||||
const rows = res.rows || []
|
||||
if (rows.length > 0) {
|
||||
const item = rows[0]
|
||||
this.cfgId = item.id
|
||||
this.cfgForm = {
|
||||
monthlyProductionTarget: item.monthlyProductionTarget ?? '',
|
||||
productionUnit: item.productionUnit || '吨',
|
||||
perfUnitPrice: item.perfUnitPrice ?? '',
|
||||
productionShifts: item.productionShifts ?? '',
|
||||
guaranteedShifts: item.guaranteedShifts ?? '',
|
||||
deptCoeff: item.deptCoeff ?? '',
|
||||
salaryMode: item.salaryMode ?? 0,
|
||||
baseDivisor: item.baseDivisor ?? '',
|
||||
remark: item.remark || ''
|
||||
}
|
||||
} else {
|
||||
this.cfgId = null
|
||||
this.cfgForm = { monthlyProductionTarget: '', productionUnit: '吨', perfUnitPrice: '', productionShifts: '', guaranteedShifts: '', deptCoeff: '', salaryMode: 0, baseDivisor: '', remark: '' }
|
||||
}
|
||||
this.cfgReady = true
|
||||
}).finally(() => { this.cfgLoading = false })
|
||||
},
|
||||
handleSaveCfg() {
|
||||
this.$refs.cfgFormRef.validate(valid => {
|
||||
if (!valid) return
|
||||
this.cfgSubmitting = true
|
||||
const data = { ...this.cfgForm, deptId: this.deptId, month: this.period + '-01' }
|
||||
const api = this.cfgId ? updatePerfDeptConfig : addPerfDeptConfig
|
||||
if (this.cfgId) data.id = this.cfgId
|
||||
api(data).then(() => {
|
||||
this.$message.success('保存成功')
|
||||
this.loadDeptConfig()
|
||||
}).finally(() => { this.cfgSubmitting = false })
|
||||
})
|
||||
},
|
||||
// ======== Items ========
|
||||
loadItems() {
|
||||
this.itemLoading = true
|
||||
listPerfDeptItemConfig({ deptId: this.deptId, month: this.period + '-01' }).then(res => {
|
||||
this.itemList = res.rows || []
|
||||
}).finally(() => { this.itemLoading = false })
|
||||
},
|
||||
handleAddItem() {
|
||||
this.itemDialogTitle = '新增项目'
|
||||
this.itemForm = { itemType: 1, itemName: '', sortOrder: 0, isEnabled: 1, remark: '', month: this.period + '-01' }
|
||||
this.itemDialogVisible = true
|
||||
this.$nextTick(() => { this.$refs.itemFormRef?.clearValidate() })
|
||||
},
|
||||
handleEditItem(row) {
|
||||
this.itemDialogTitle = '编辑项目'
|
||||
this.itemForm = { ...row }
|
||||
this.itemDialogVisible = true
|
||||
this.$nextTick(() => { this.$refs.itemFormRef?.clearValidate() })
|
||||
},
|
||||
handleDelItem(row) {
|
||||
this.$confirm(`确认删除项目"${row.itemName}"吗?`, '提示', { type: 'warning' }).then(() => {
|
||||
delPerfDeptItemConfig(row.id).then(() => {
|
||||
this.$message.success('删除成功')
|
||||
this.loadItems()
|
||||
})
|
||||
}).catch(() => {})
|
||||
},
|
||||
handleToggleItemEnabled(row) {
|
||||
updatePerfDeptItemConfig({ id: row.id, isEnabled: row.isEnabled })
|
||||
},
|
||||
submitItem() {
|
||||
this.$refs.itemFormRef.validate(valid => {
|
||||
if (!valid) return
|
||||
this.itemSubmitting = true
|
||||
const data = { ...this.itemForm, deptId: this.deptId }
|
||||
const api = data.id ? updatePerfDeptItemConfig : addPerfDeptItemConfig
|
||||
api(data).then(() => {
|
||||
this.$message.success(data.id ? '修改成功' : '新增成功')
|
||||
this.itemDialogVisible = false
|
||||
this.loadItems()
|
||||
}).finally(() => { this.itemSubmitting = false })
|
||||
})
|
||||
},
|
||||
resetItemForm() { this.itemForm = {} },
|
||||
|
||||
// ======== Templates ========
|
||||
loadTemplates() {
|
||||
this.templateLoading = true
|
||||
listPerfPositionTemplate({ deptId: this.deptId }).then(res => {
|
||||
this.templateList = res.rows || []
|
||||
if (this.templateList.length && !this.templateList.find(t => String(t.id) === this.activeTemplateId)) {
|
||||
this.activeTemplateId = String(this.templateList[0].id)
|
||||
this.loadDims()
|
||||
}
|
||||
}).finally(() => { this.templateLoading = false })
|
||||
},
|
||||
handleAddTemplate() {
|
||||
this.templateDialogTitle = '新增模板'
|
||||
this.templateForm = { positionName: '', isEnabled: 1, remark: '' }
|
||||
this.templateDialogVisible = true
|
||||
this.$nextTick(() => { this.$refs.templateFormRef?.clearValidate() })
|
||||
},
|
||||
|
||||
// 右键菜单
|
||||
onTabContextMenu(event, tpl) {
|
||||
this.$set(this.ctxMenu, 'tpl', tpl)
|
||||
this.$set(this.ctxMenu, 'x', event.pageX)
|
||||
this.$set(this.ctxMenu, 'y', event.pageY)
|
||||
this.$set(this.ctxMenu, 'visible', true)
|
||||
this.$nextTick(() => {
|
||||
const close = () => this.closeCtxMenu()
|
||||
document.addEventListener('click', close, { once: true })
|
||||
document.addEventListener('contextmenu', close, { once: true })
|
||||
})
|
||||
},
|
||||
closeCtxMenu() {
|
||||
this.ctxMenu.visible = false
|
||||
},
|
||||
ctxEditTemplate() {
|
||||
if (this.ctxLoading) return
|
||||
const tpl = this.ctxMenu.tpl
|
||||
this.ctxMenu.visible = false
|
||||
this.templateDialogTitle = '编辑模板'
|
||||
this.templateForm = { ...tpl }
|
||||
this.templateDialogVisible = true
|
||||
this.$nextTick(() => { this.$refs.templateFormRef?.clearValidate() })
|
||||
},
|
||||
ctxDelTemplate() {
|
||||
if (this.ctxLoading) return
|
||||
const tpl = this.ctxMenu.tpl
|
||||
this.ctxMenu.visible = false
|
||||
this.$confirm(`确认删除模板"${tpl.positionName}"吗?关联的维度也将一并删除。`, '提示', { type: 'warning' }).then(() => {
|
||||
this.ctxLoading = true
|
||||
delPerfPositionTemplate(tpl.id).then(() => {
|
||||
this.$message.success('删除成功')
|
||||
if (String(tpl.id) === this.activeTemplateId) this.activeTemplateId = ''
|
||||
this.loadTemplates()
|
||||
}).finally(() => { this.ctxLoading = false })
|
||||
}).catch(() => {})
|
||||
},
|
||||
submitTemplate() {
|
||||
this.$refs.templateFormRef.validate(valid => {
|
||||
if (!valid) return
|
||||
this.templateSubmitting = true
|
||||
const data = { ...this.templateForm, deptId: this.deptId, title: this.templateForm.positionName }
|
||||
const api = data.id ? updatePerfPositionTemplate : addPerfPositionTemplate
|
||||
api(data).then(() => {
|
||||
this.$message.success(data.id ? '修改成功' : '新增成功')
|
||||
this.templateDialogVisible = false
|
||||
this.loadTemplates()
|
||||
}).finally(() => { this.templateSubmitting = false })
|
||||
})
|
||||
},
|
||||
resetTemplateForm() { this.templateForm = {} },
|
||||
|
||||
// ======== Dimensions ========
|
||||
loadDims() {
|
||||
if (!this.activeTpl) { this.dimList = []; return }
|
||||
this.dimLoading = true
|
||||
listPerfTemplateDimension({ templateId: this.activeTpl.id }).then(res => {
|
||||
this.dimList = res.rows || []
|
||||
}).finally(() => { this.dimLoading = false })
|
||||
},
|
||||
handleAddDim() {
|
||||
this.dimFormTitle = '新增维度'
|
||||
this.dimForm = { seq: '', dimName: '', weight: '', indicator: '', targetValue: '', scoreFormula: '', scoreRule: '', isEnabled: 1, remark: '' }
|
||||
this.dimFormVisible = true
|
||||
this.$nextTick(() => { this.$refs.dimFormRef?.clearValidate() })
|
||||
},
|
||||
handleEditDim(row) {
|
||||
this.dimFormTitle = '编辑维度'
|
||||
this.dimForm = { ...row }
|
||||
this.dimFormVisible = true
|
||||
this.$nextTick(() => { this.$refs.dimFormRef?.clearValidate() })
|
||||
},
|
||||
handleDelDim(row) {
|
||||
this.$confirm(`确认删除维度"${row.dimName}"吗?`, '提示', { type: 'warning' }).then(() => {
|
||||
delPerfTemplateDimension(row.id).then(() => {
|
||||
this.$message.success('删除成功')
|
||||
this.loadDims()
|
||||
})
|
||||
}).catch(() => {})
|
||||
},
|
||||
handleToggleDimEnabled(row) {
|
||||
updatePerfTemplateDimension({ id: row.id, isEnabled: row.isEnabled })
|
||||
},
|
||||
submitDim() {
|
||||
this.$refs.dimFormRef.validate(valid => {
|
||||
if (!valid) return
|
||||
this.dimSubmitting = true
|
||||
const data = { ...this.dimForm, templateId: this.activeTpl.id }
|
||||
const api = data.id ? updatePerfTemplateDimension : addPerfTemplateDimension
|
||||
api(data).then(() => {
|
||||
this.$message.success(data.id ? '修改成功' : '新增成功')
|
||||
this.dimFormVisible = false
|
||||
this.loadDims()
|
||||
}).finally(() => { this.dimSubmitting = false })
|
||||
})
|
||||
},
|
||||
resetDimForm() { this.dimForm = {} }
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.perf-config-panel {
|
||||
padding: 8px;
|
||||
height: 100%;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.config-section {
|
||||
background-color: #fff;
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
|
||||
.section-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: 10px 12px;
|
||||
border-bottom: 1px solid #f0f0f0;
|
||||
}
|
||||
|
||||
.section-title {
|
||||
font-size: 14px;
|
||||
font-weight: 600;
|
||||
color: #303133;
|
||||
}
|
||||
|
||||
.section-hint {
|
||||
font-weight: 400;
|
||||
font-size: 12px;
|
||||
color: #c0c4cc;
|
||||
margin-left: 8px;
|
||||
}
|
||||
|
||||
.cfg-form {
|
||||
padding: 12px;
|
||||
|
||||
::v-deep .el-form-item {
|
||||
margin-bottom: 8px;
|
||||
margin-right: 16px;
|
||||
}
|
||||
}
|
||||
|
||||
::v-deep .el-table {
|
||||
border: none;
|
||||
&::before, &::after { display: none; }
|
||||
}
|
||||
|
||||
/* 自定义模板 tabs */
|
||||
.template-tabs {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 6px;
|
||||
padding: 8px 12px;
|
||||
border-bottom: 1px solid #ebeef5;
|
||||
}
|
||||
|
||||
.template-tab {
|
||||
display: inline-block;
|
||||
padding: 4px 14px;
|
||||
font-size: 12px;
|
||||
color: #606266;
|
||||
background: #f4f4f5;
|
||||
border-radius: 12px;
|
||||
cursor: pointer;
|
||||
user-select: none;
|
||||
transition: all 0.15s;
|
||||
white-space: nowrap;
|
||||
|
||||
&:hover {
|
||||
color: #409eff;
|
||||
background: #ecf5ff;
|
||||
}
|
||||
|
||||
&--active {
|
||||
color: #fff;
|
||||
background: #409eff;
|
||||
&:hover {
|
||||
color: #fff;
|
||||
background: #66b1ff;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* 维度区域 */
|
||||
.dim-section {
|
||||
padding: 8px 0 0;
|
||||
}
|
||||
|
||||
.dim-toolbar {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: 0 12px 8px;
|
||||
}
|
||||
|
||||
.dim-subtitle {
|
||||
font-size: 13px;
|
||||
font-weight: 500;
|
||||
color: #606266;
|
||||
}
|
||||
|
||||
.dim-empty {
|
||||
padding: 24px;
|
||||
text-align: center;
|
||||
color: #c0c4cc;
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
/* 右键菜单 */
|
||||
.ctx-menu {
|
||||
position: fixed;
|
||||
z-index: 9999;
|
||||
background: #fff;
|
||||
border: 1px solid #e4e7ed;
|
||||
border-radius: 4px;
|
||||
box-shadow: 0 2px 12px rgba(0,0,0,.12);
|
||||
padding: 4px 0;
|
||||
min-width: 120px;
|
||||
}
|
||||
|
||||
.ctx-menu-item {
|
||||
padding: 8px 16px;
|
||||
font-size: 13px;
|
||||
color: #606266;
|
||||
cursor: pointer;
|
||||
transition: background-color 0.15s;
|
||||
|
||||
&:hover { background-color: #f5f7fa; }
|
||||
|
||||
&--danger {
|
||||
color: #f56c6c;
|
||||
&:hover { background-color: #fef0f0; }
|
||||
}
|
||||
}
|
||||
</style>
|
||||
509
klp-ui/src/views/perf/index/SalaryPanel.vue
Normal file
509
klp-ui/src/views/perf/index/SalaryPanel.vue
Normal file
@@ -0,0 +1,509 @@
|
||||
<template>
|
||||
<div class="salary-panel">
|
||||
<!-- 工具栏 -->
|
||||
<div class="toolbar">
|
||||
<div class="toolbar-left">
|
||||
<el-input v-model="query.employeeId" size="small" placeholder="员工ID" clearable style="width: 140px" @keyup.enter.native="loadList" />
|
||||
<el-select v-model="query.status" size="small" placeholder="状态" clearable style="width: 110px" @change="loadList">
|
||||
<el-option label="草稿" :value="'0'" />
|
||||
<el-option label="已确认" :value="'1'" />
|
||||
<el-option label="已归档" :value="'2'" />
|
||||
</el-select>
|
||||
<el-button size="small" type="primary" icon="el-icon-search" @click="loadList">查询</el-button>
|
||||
</div>
|
||||
<div class="toolbar-right">
|
||||
<el-button size="small" type="primary" icon="el-icon-plus" @click="handleAdd">新增</el-button>
|
||||
<el-button size="small" type="success" icon="el-icon-s-operation" :loading="generating" @click="handleBatchGenerate">一键生成</el-button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 表格 -->
|
||||
<div class="table-wrap">
|
||||
<el-table :data="list" stripe size="small" v-loading="loading" empty-text="暂无薪资记录" class="inline-edit-table" border>
|
||||
<!-- 只读列 -->
|
||||
<!-- <el-table-column prop="employeeId" label="员工ID" width="90" /> -->
|
||||
<el-table-column prop="status" label="状态" width="70" align="center">
|
||||
<template slot-scope="scope">
|
||||
<el-tag :type="statusType(scope.row.status)" size="small">{{ statusLabel(scope.row.status) }}</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
|
||||
<!-- 薪资基数 -->
|
||||
<el-table-column label="底薪" width="90" align="right"><template slot-scope="s">
|
||||
<input v-model="s.row.baseSalary" class="cell-input cell-input--base" @change="markDirty(s.row)" />
|
||||
</template></el-table-column>
|
||||
<el-table-column label="绩效基数" width="90" align="right"><template slot-scope="s">
|
||||
<input v-model="s.row.perfBase" class="cell-input cell-input--base" @change="markDirty(s.row)" />
|
||||
</template></el-table-column>
|
||||
|
||||
<!-- 考核得分 -->
|
||||
<el-table-column label="考核得分" width="90" align="right"><template slot-scope="s">
|
||||
<input v-model="s.row.perfScore" class="cell-input cell-input--coeff" @change="markDirty(s.row)" />
|
||||
</template></el-table-column>
|
||||
|
||||
<!-- 系数 -->
|
||||
<el-table-column label="绩效系数" width="90" align="right"><template slot-scope="s">
|
||||
<input v-model="s.row.perfCoeff" class="cell-input cell-input--coeff" @change="markDirty(s.row)" />
|
||||
</template></el-table-column>
|
||||
<el-table-column label="车间系数" width="90" align="right"><template slot-scope="s">
|
||||
<input v-model="s.row.deptCoeff" class="cell-input cell-input--coeff" @change="markDirty(s.row)" />
|
||||
</template></el-table-column>
|
||||
<el-table-column label="岗位系数" width="90" align="right"><template slot-scope="s">
|
||||
<input v-model="s.row.posCoeff" class="cell-input cell-input--coeff" @change="markDirty(s.row)" />
|
||||
</template></el-table-column>
|
||||
<el-table-column label="固定系数" width="90" align="right"><template slot-scope="s">
|
||||
<input v-model="s.row.fixedCoeff" class="cell-input cell-input--coeff" @change="markDirty(s.row)" />
|
||||
</template></el-table-column>
|
||||
<el-table-column label="调整系数" width="90" align="right"><template slot-scope="s">
|
||||
<input v-model="s.row.adjCoeff" class="cell-input cell-input--coeff" @change="markDirty(s.row)" />
|
||||
</template></el-table-column>
|
||||
<el-table-column label="手动调整系数" width="100" align="right"><template slot-scope="s">
|
||||
<input v-model="s.row.adjCoeffManual" class="cell-input cell-input--coeff" @change="markDirty(s.row)" />
|
||||
</template></el-table-column>
|
||||
<el-table-column label="总系数" width="80" align="right"><template slot-scope="s">
|
||||
<input v-model="s.row.totalCoeff" class="cell-input cell-input--coeff" @change="markDirty(s.row)" />
|
||||
</template></el-table-column>
|
||||
|
||||
<!-- 绩效工资 -->
|
||||
<el-table-column label="绩效工资" width="100" align="right"><template slot-scope="s">
|
||||
<input v-model="s.row.perfWage" class="cell-input cell-input--amount" @change="markDirty(s.row)" />
|
||||
</template></el-table-column>
|
||||
|
||||
<!-- 动态扣款列 -->
|
||||
<el-table-column
|
||||
v-for="item in deductionItems" :key="'ded-'+item.itemName"
|
||||
:label="item.itemName" width="100" align="right"
|
||||
><template slot-scope="s">
|
||||
<input v-model="s.row._d[item.itemName]" class="cell-input cell-input--deduct" @change="onDeductChange(s.row); markDirty(s.row)" />
|
||||
</template></el-table-column>
|
||||
<el-table-column label="扣款合计" width="90" align="right">
|
||||
<template slot-scope="scope"><span class="cell-readonly cell-readonly--deduct">{{ scope.row._deductionsTotal }}</span></template>
|
||||
</el-table-column>
|
||||
|
||||
<!-- 动态奖励列 -->
|
||||
<el-table-column
|
||||
v-for="item in bonusItems" :key="'bon-'+item.itemName"
|
||||
:label="item.itemName" width="100" align="right"
|
||||
><template slot-scope="s">
|
||||
<input v-model="s.row._b[item.itemName]" class="cell-input cell-input--bonus" @change="onBonusChange(s.row); markDirty(s.row)" />
|
||||
</template></el-table-column>
|
||||
<el-table-column label="奖励合计" width="90" align="right">
|
||||
<template slot-scope="scope"><span class="cell-readonly cell-readonly--bonus">{{ scope.row._bonusesTotal }}</span></template>
|
||||
</el-table-column>
|
||||
|
||||
<!-- 金额 -->
|
||||
<el-table-column label="其他金额" width="90" align="right"><template slot-scope="s">
|
||||
<input v-model="s.row.otherAmount" class="cell-input cell-input--amount" @change="markDirty(s.row)" />
|
||||
</template></el-table-column>
|
||||
<el-table-column label="实领薪资" width="100" align="right"><template slot-scope="s">
|
||||
<input v-model="s.row.actualSalary" class="cell-input cell-input--amount" @change="markDirty(s.row)" />
|
||||
</template></el-table-column>
|
||||
|
||||
<!-- 参考值 -->
|
||||
<el-table-column label="固薪参考" width="90" align="right"><template slot-scope="s">
|
||||
<input v-model="s.row.fixedSalaryRef" class="cell-input cell-input--ref" @change="markDirty(s.row)" />
|
||||
</template></el-table-column>
|
||||
<el-table-column label="调薪参考" width="90" align="right"><template slot-scope="s">
|
||||
<input v-model="s.row.adjSalaryRef" class="cell-input cell-input--ref" @change="markDirty(s.row)" />
|
||||
</template></el-table-column>
|
||||
<el-table-column label="包薪每班参考" width="100" align="right"><template slot-scope="s">
|
||||
<input v-model="s.row.baoPerShiftRef" class="cell-input cell-input--ref" @change="markDirty(s.row)" />
|
||||
</template></el-table-column>
|
||||
<el-table-column label="包薪参考" width="90" align="right"><template slot-scope="s">
|
||||
<input v-model="s.row.baoSalaryRef" class="cell-input cell-input--ref" @change="markDirty(s.row)" />
|
||||
</template></el-table-column>
|
||||
<el-table-column label="总薪参考" width="90" align="right"><template slot-scope="s">
|
||||
<input v-model="s.row.totalSalaryRef" class="cell-input cell-input--ref" @change="markDirty(s.row)" />
|
||||
</template></el-table-column>
|
||||
|
||||
<!-- 备注 -->
|
||||
<el-table-column label="备注" min-width="140"><template slot-scope="s">
|
||||
<input v-model="s.row.remark" class="cell-input cell-input--remark" @change="markDirty(s.row)" />
|
||||
</template></el-table-column>
|
||||
|
||||
<!-- 操作 -->
|
||||
<el-table-column label="操作" width="90" align="center" fixed="right">
|
||||
<template slot-scope="scope">
|
||||
<el-button v-if="scope.row._dirty" type="text" size="mini" icon="el-icon-check" :loading="scope.row._saving" class="btn-save" @click="saveRow(scope.row)">保存</el-button>
|
||||
<el-button type="text" size="mini" icon="el-icon-delete" @click="handleDel(scope.row)">删除</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
</div>
|
||||
|
||||
<div class="pagination-wrapper">
|
||||
<el-pagination
|
||||
:current-page="query.pageNum"
|
||||
:page-sizes="[10, 20, 50, 100]"
|
||||
:page-size="query.pageSize"
|
||||
:total="total"
|
||||
layout="total, sizes, prev, pager, next, jumper"
|
||||
@size-change="handleSizeChange"
|
||||
@current-change="handlePageChange"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { listPerfSalary, addPerfSalary, updatePerfSalary, delPerfSalary } from '@/api/perf/salary'
|
||||
import { listEmployeeInfo } from '@/api/wms/employeeInfo'
|
||||
import { listPerfDeptItemConfig } from '@/api/perf/deptItemConfig'
|
||||
|
||||
export default {
|
||||
name: 'SalaryPanel',
|
||||
props: {
|
||||
deptId: { type: [Number, String], default: null },
|
||||
deptName: { type: String, default: '' },
|
||||
period: { type: String, default: '' }
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
loading: false,
|
||||
list: [],
|
||||
total: 0,
|
||||
query: {
|
||||
pageNum: 1,
|
||||
pageSize: 10,
|
||||
employeeId: '',
|
||||
status: ''
|
||||
},
|
||||
deductionItems: [],
|
||||
bonusItems: [],
|
||||
generating: false
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
deptId: { immediate: true, handler: 'onDeptChange' },
|
||||
period: 'onDeptChange'
|
||||
},
|
||||
methods: {
|
||||
fmt(val) {
|
||||
return val != null ? val : '-'
|
||||
},
|
||||
statusType(status) {
|
||||
return { '0': 'info', '1': 'warning', '2': 'success' }[status] || 'info'
|
||||
},
|
||||
statusLabel(status) {
|
||||
return { '0': '草稿', '1': '已确认', '2': '已归档' }[status] || '草稿'
|
||||
},
|
||||
onDeptChange() {
|
||||
this.loadItemConfig()
|
||||
this.loadList()
|
||||
},
|
||||
loadItemConfig() {
|
||||
if (!this.deptId || !this.period) return
|
||||
listPerfDeptItemConfig({ deptId: this.deptId, month: this.period + '-01' }).then(res => {
|
||||
const rows = res.rows || []
|
||||
this.deductionItems = rows.filter(r => r.itemType === 1 && r.isEnabled === 1)
|
||||
this.bonusItems = rows.filter(r => r.itemType === 2 && r.isEnabled === 1)
|
||||
}).catch(() => {
|
||||
this.deductionItems = []
|
||||
this.bonusItems = []
|
||||
})
|
||||
},
|
||||
/* 解析扣款/奖励 JSON 到行对象 */
|
||||
parseRowItems(row) {
|
||||
if (!row._d) {
|
||||
const d = {}
|
||||
try { Object.assign(d, JSON.parse(row.deductionsJson || '{}')) } catch {}
|
||||
this.deductionItems.forEach(item => { if (!(item.itemName in d)) d[item.itemName] = '' })
|
||||
this.$set(row, '_d', d)
|
||||
}
|
||||
if (!row._b) {
|
||||
const b = {}
|
||||
try { Object.assign(b, JSON.parse(row.bonusesJson || '{}')) } catch {}
|
||||
this.bonusItems.forEach(item => { if (!(item.itemName in b)) b[item.itemName] = '' })
|
||||
this.$set(row, '_b', b)
|
||||
}
|
||||
this.recalcTotals(row)
|
||||
},
|
||||
recalcTotals(row) {
|
||||
const sumD = Object.values(row._d || {}).reduce((s, v) => s + (parseFloat(v) || 0), 0)
|
||||
const sumB = Object.values(row._b || {}).reduce((s, v) => s + (parseFloat(v) || 0), 0)
|
||||
this.$set(row, '_deductionsTotal', sumD)
|
||||
this.$set(row, '_bonusesTotal', sumB)
|
||||
},
|
||||
onDeductChange(row) { this.recalcTotals(row) },
|
||||
onBonusChange(row) { this.recalcTotals(row) },
|
||||
markDirty(row) { this.$set(row, '_dirty', true) },
|
||||
|
||||
loadList() {
|
||||
if (!this.deptId || !this.period) return
|
||||
this.loading = true
|
||||
const params = {
|
||||
pageNum: this.query.pageNum,
|
||||
pageSize: this.query.pageSize,
|
||||
deptId: this.deptId,
|
||||
period: this.period,
|
||||
employeeId: this.query.employeeId || undefined,
|
||||
status: this.query.status || undefined
|
||||
}
|
||||
listPerfSalary(params).then(res => {
|
||||
this.list = (res.rows || []).map(r => {
|
||||
this.$set(r, '_dirty', false)
|
||||
this.$set(r, '_saving', false)
|
||||
this.parseRowItems(r)
|
||||
return r
|
||||
})
|
||||
this.total = res.total || 0
|
||||
}).finally(() => {
|
||||
this.loading = false
|
||||
})
|
||||
},
|
||||
handleSizeChange(val) {
|
||||
this.query.pageSize = val
|
||||
this.loadList()
|
||||
},
|
||||
handlePageChange(val) {
|
||||
this.query.pageNum = val
|
||||
this.loadList()
|
||||
},
|
||||
handleAdd() {
|
||||
const row = {
|
||||
employeeId: '',
|
||||
deptId: this.deptId,
|
||||
period: this.period,
|
||||
baseSalary: '',
|
||||
perfBase: '',
|
||||
perfScore: '',
|
||||
perfCoeff: '',
|
||||
deptCoeff: '',
|
||||
posCoeff: '',
|
||||
fixedCoeff: '',
|
||||
adjCoeff: '',
|
||||
adjCoeffManual: '',
|
||||
totalCoeff: '',
|
||||
perfWage: '',
|
||||
deductionsJson: '{}',
|
||||
bonusesJson: '{}',
|
||||
otherAmount: '',
|
||||
actualSalary: '',
|
||||
fixedSalaryRef: '',
|
||||
adjSalaryRef: '',
|
||||
baoPerShiftRef: '',
|
||||
baoSalaryRef: '',
|
||||
totalSalaryRef: '',
|
||||
status: '0',
|
||||
remark: '',
|
||||
_dirty: true,
|
||||
_saving: false,
|
||||
_d: {},
|
||||
_b: {},
|
||||
_deductionsTotal: 0,
|
||||
_bonusesTotal: 0
|
||||
}
|
||||
this.deductionItems.forEach(item => { row._d[item.itemName] = '' })
|
||||
this.bonusItems.forEach(item => { row._b[item.itemName] = '' })
|
||||
this.list.unshift(row)
|
||||
},
|
||||
saveRow(row) {
|
||||
this.$set(row, '_saving', true)
|
||||
const data = { ...row }
|
||||
// 序列化扣款/奖励 JSON
|
||||
data.deductionsJson = JSON.stringify(row._d || {})
|
||||
data.bonusesJson = JSON.stringify(row._b || {})
|
||||
data.deductionsTotal = row._deductionsTotal
|
||||
data.bonusesTotal = row._bonusesTotal
|
||||
// 清理内部字段
|
||||
delete data._d; delete data._b; delete data._dirty; delete data._saving
|
||||
delete data._deductionsTotal; delete data._bonusesTotal
|
||||
|
||||
const api = data.id ? updatePerfSalary : addPerfSalary
|
||||
api(data).then(res => {
|
||||
// 乐观更新:如果是新增,用返回值更新 id
|
||||
if (!data.id && res.data) {
|
||||
this.$set(row, 'id', res.data.id || res.data)
|
||||
}
|
||||
this.$set(row, '_dirty', false)
|
||||
this.$message.success('保存成功')
|
||||
}).catch(() => {
|
||||
this.$message.error('保存失败')
|
||||
this.loadList() // 失败则重新加载以回滚
|
||||
}).finally(() => {
|
||||
this.$set(row, '_saving', false)
|
||||
})
|
||||
},
|
||||
handleDel(row) {
|
||||
if (!row.id) {
|
||||
// 未保存的新行直接移除
|
||||
const idx = this.list.indexOf(row)
|
||||
if (idx >= 0) this.list.splice(idx, 1)
|
||||
return
|
||||
}
|
||||
this.$confirm(`确认删除该薪资记录吗?`, '提示', { type: 'warning' }).then(() => {
|
||||
delPerfSalary(row.id).then(() => {
|
||||
this.$message.success('删除成功')
|
||||
this.loadList()
|
||||
})
|
||||
}).catch(() => {})
|
||||
},
|
||||
handleBatchGenerate() {
|
||||
if (!this.deptName) {
|
||||
this.$message.warning('缺少部门名称')
|
||||
return
|
||||
}
|
||||
this.$confirm(`确认根据"${this.deptName}"下的在职员工,一键生成本月薪资记录吗?`, '提示', { type: 'info' }).then(() => {
|
||||
this.generating = true
|
||||
listEmployeeInfo({ dept: this.deptName, isLeave: 0, pageSize: 9999 }).then(empRes => {
|
||||
const employees = empRes.rows || []
|
||||
if (employees.length === 0) {
|
||||
this.$message.warning('该部门下暂无在职员工')
|
||||
return
|
||||
}
|
||||
listPerfSalary({ deptId: this.deptId, period: this.period, pageSize: 9999 }).then(salaryRes => {
|
||||
const existing = new Set((salaryRes.rows || []).map(r => String(r.employeeId)))
|
||||
const toInsert = employees.filter(e => !existing.has(String(e.employeeId) || String(e.infoId)))
|
||||
if (toInsert.length === 0) {
|
||||
this.$message.info('该部门下所有在职员工已有本月薪资记录')
|
||||
return
|
||||
}
|
||||
const emptyJson = JSON.stringify({})
|
||||
const tasks = toInsert.map(emp => addPerfSalary({
|
||||
employeeId: emp.employeeId || emp.infoId || '',
|
||||
deptId: this.deptId,
|
||||
period: this.period,
|
||||
baseSalary: emp.baseSalary || 0,
|
||||
perfBase: emp.perfBase || 0,
|
||||
posCoeff: emp.posCoeffDefault || 0,
|
||||
perfScore: 0,
|
||||
perfCoeff: 0,
|
||||
deptCoeff: 0,
|
||||
fixedCoeff: 0,
|
||||
adjCoeff: 0,
|
||||
adjCoeffManual: 0,
|
||||
totalCoeff: 0,
|
||||
perfWage: 0,
|
||||
deductionsJson: emptyJson,
|
||||
deductionsTotal: 0,
|
||||
bonusesJson: emptyJson,
|
||||
bonusesTotal: 0,
|
||||
otherAmount: 0,
|
||||
actualSalary: 0,
|
||||
fixedSalaryRef: '',
|
||||
adjSalaryRef: '',
|
||||
baoPerShiftRef: '',
|
||||
baoSalaryRef: '',
|
||||
totalSalaryRef: '',
|
||||
status: '0',
|
||||
remark: ''
|
||||
}))
|
||||
return Promise.allSettled(tasks)
|
||||
}).then(results => {
|
||||
if (!results) return
|
||||
const ok = results.filter(r => r.status === 'fulfilled').length
|
||||
const fail = results.filter(r => r.status === 'rejected').length
|
||||
this.$message.success(`生成完成:成功 ${ok} 条${fail > 0 ? `,失败 ${fail} 条` : ''}`)
|
||||
this.loadList()
|
||||
})
|
||||
}).finally(() => {
|
||||
this.generating = false
|
||||
})
|
||||
}).catch(() => {})
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.salary-panel {
|
||||
padding: 8px;
|
||||
height: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.toolbar {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 6px;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.toolbar-left,
|
||||
.toolbar-right {
|
||||
display: flex;
|
||||
gap: 6px;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.table-wrap {
|
||||
flex: 1;
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
::v-deep .inline-edit-table {
|
||||
border: none;
|
||||
&::before, &::after { display: none; }
|
||||
th { border: none; padding: 2px 4px; font-size: 12px; color: #909399; }
|
||||
td {
|
||||
border: none;
|
||||
border-right: 1px solid #ebeef5;
|
||||
padding: 0;
|
||||
}
|
||||
.el-table__body tr:hover td { background-color: #f5f7fa !important; }
|
||||
.cell { padding: 0; height: 100%; }
|
||||
}
|
||||
|
||||
/* 原生 input */
|
||||
.cell-input {
|
||||
width: 100%;
|
||||
height: 28px;
|
||||
border: none;
|
||||
outline: none;
|
||||
text-align: right;
|
||||
padding: 0 6px;
|
||||
font-size: 12px;
|
||||
color: #303133;
|
||||
font-family: inherit;
|
||||
box-sizing: border-box;
|
||||
display: block;
|
||||
line-height: 28px;
|
||||
transition: background-color 0.15s;
|
||||
|
||||
&:focus {
|
||||
background-color: #fff;
|
||||
box-shadow: 0 0 0 1px #409eff inset;
|
||||
border-radius: 0;
|
||||
}
|
||||
|
||||
&--base { background-color: #e8f4fd; }
|
||||
&--coeff { background-color: #fff8e1; }
|
||||
&--deduct { background-color: #ffebee; }
|
||||
&--bonus { background-color: #e8f5e9; }
|
||||
&--amount { background-color: #f3e5f5; }
|
||||
&--ref { background-color: #f5f5f5; }
|
||||
&--remark { background-color: #fafafa; text-align: left; }
|
||||
}
|
||||
|
||||
.cell-readonly {
|
||||
display: block;
|
||||
text-align: right;
|
||||
padding: 0 6px;
|
||||
font-size: 12px;
|
||||
color: #303133;
|
||||
font-weight: 500;
|
||||
line-height: 28px;
|
||||
height: 28px;
|
||||
|
||||
&--deduct { color: #e53935; }
|
||||
&--bonus { color: #43a047; }
|
||||
}
|
||||
|
||||
.btn-save {
|
||||
color: #67c23a !important;
|
||||
}
|
||||
|
||||
.pagination-wrapper {
|
||||
margin-top: 6px;
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.el-table .el-button + .el-button {
|
||||
margin-left: 0;
|
||||
}
|
||||
</style>
|
||||
240
klp-ui/src/views/perf/index/SummaryPanel.vue
Normal file
240
klp-ui/src/views/perf/index/SummaryPanel.vue
Normal file
@@ -0,0 +1,240 @@
|
||||
<template>
|
||||
<div class="summary-panel" v-loading="loading">
|
||||
<div v-if="!summary && !loading" class="empty-state">
|
||||
<el-empty description="暂无统计数据">
|
||||
<span slot="extra" style="color: #909399; font-size: 13px;">请确认已配置考核参数并生成薪资记录</span>
|
||||
</el-empty>
|
||||
</div>
|
||||
|
||||
<template v-if="summary">
|
||||
<!-- Overview Cards -->
|
||||
<div class="stat-cards">
|
||||
<div class="stat-card">
|
||||
<div class="stat-label">总人数</div>
|
||||
<div class="stat-value">{{ fmt(summary.totalEmployees) }}</div>
|
||||
</div>
|
||||
<div class="stat-card">
|
||||
<div class="stat-label">系数总计</div>
|
||||
<div class="stat-value">{{ fmt(summary.totalCoeffSum) }}</div>
|
||||
</div>
|
||||
<div class="stat-card">
|
||||
<div class="stat-label">平均考核得分</div>
|
||||
<div class="stat-value highlight">{{ fmt(summary.avgPerfScore) }}</div>
|
||||
</div>
|
||||
<div class="stat-card">
|
||||
<div class="stat-label">平均绩效系数</div>
|
||||
<div class="stat-value">{{ fmt(summary.avgPerfCoeff) }}</div>
|
||||
</div>
|
||||
<div class="stat-card">
|
||||
<div class="stat-label">月产量</div>
|
||||
<div class="stat-value">{{ fmt(summary.monthlyProduction) }}</div>
|
||||
</div>
|
||||
<div class="stat-card">
|
||||
<div class="stat-label">系数单价</div>
|
||||
<div class="stat-value highlight">{{ fmt(summary.unitPrice) }}</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Salary Breakdown Card -->
|
||||
<div class="detail-card">
|
||||
<div class="card-title">薪资构成</div>
|
||||
<div class="detail-grid">
|
||||
<div class="detail-item">
|
||||
<span class="detail-label">底薪合计</span>
|
||||
<span class="detail-value">{{ fmt(summary.totalBaseSalary) }}</span>
|
||||
</div>
|
||||
<div class="detail-item">
|
||||
<span class="detail-label">绩效工资合计</span>
|
||||
<span class="detail-value highlight">{{ fmt(summary.totalPerfWage) }}</span>
|
||||
</div>
|
||||
<div class="detail-item">
|
||||
<span class="detail-label">扣款合计</span>
|
||||
<span class="detail-value danger">{{ fmt(summary.totalDeductions) }}</span>
|
||||
</div>
|
||||
<div class="detail-item">
|
||||
<span class="detail-label">奖励合计</span>
|
||||
<span class="detail-value success">{{ fmt(summary.totalBonuses) }}</span>
|
||||
</div>
|
||||
<div class="detail-item">
|
||||
<span class="detail-label">其他合计</span>
|
||||
<span class="detail-value">{{ fmt(summary.totalOther) }}</span>
|
||||
</div>
|
||||
<div class="detail-item">
|
||||
<span class="detail-label">实领薪资合计</span>
|
||||
<span class="detail-value total">{{ fmt(summary.totalActualSalary) }}</span>
|
||||
</div>
|
||||
<div class="detail-item">
|
||||
<span class="detail-label">人均实领薪资</span>
|
||||
<span class="detail-value highlight">{{ fmt(summary.avgActualSalary) }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Status -->
|
||||
<div class="status-bar">
|
||||
<span class="status-label">状态</span>
|
||||
<el-tag :type="summary.status === 1 ? 'success' : 'info'" size="small">
|
||||
{{ summary.status === 1 ? '已确认' : '草稿' }}
|
||||
</el-tag>
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { listPerfDeptSummary } from '@/api/perf/deptSummary'
|
||||
|
||||
export default {
|
||||
name: 'SummaryPanel',
|
||||
props: {
|
||||
deptId: { type: [Number, String], default: null },
|
||||
period: { type: String, default: '' }
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
loading: false,
|
||||
summary: null
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
deptId: { immediate: true, handler: 'loadSummary' },
|
||||
period: 'loadSummary'
|
||||
},
|
||||
methods: {
|
||||
fmt(val) {
|
||||
return val != null ? val : '-'
|
||||
},
|
||||
loadSummary() {
|
||||
if (!this.deptId || !this.period) return
|
||||
this.loading = true
|
||||
this.summary = null
|
||||
listPerfDeptSummary({ deptId: this.deptId, period: this.period }).then(res => {
|
||||
const rows = res.rows || []
|
||||
this.summary = rows.length > 0 ? rows[0] : null
|
||||
}).finally(() => {
|
||||
this.loading = false
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.summary-panel {
|
||||
height: 100%;
|
||||
overflow-y: auto;
|
||||
background-color: #f5f7fa;
|
||||
padding: 16px;
|
||||
}
|
||||
|
||||
.empty-state {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
min-height: 300px;
|
||||
background-color: #fff;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
.stat-cards {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(6, 1fr);
|
||||
gap: 12px;
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
.stat-card {
|
||||
background-color: #fff;
|
||||
border-radius: 4px;
|
||||
padding: 20px 16px;
|
||||
text-align: center;
|
||||
box-shadow: 0 1px 2px rgba(0, 0, 0, 0.04);
|
||||
}
|
||||
|
||||
.stat-label {
|
||||
font-size: 12px;
|
||||
color: #909399;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.stat-value {
|
||||
font-size: 22px;
|
||||
font-weight: 700;
|
||||
color: #303133;
|
||||
|
||||
&.highlight {
|
||||
color: #409eff;
|
||||
}
|
||||
}
|
||||
|
||||
.detail-card {
|
||||
background-color: #fff;
|
||||
border-radius: 4px;
|
||||
padding: 16px 20px;
|
||||
margin-bottom: 16px;
|
||||
box-shadow: 0 1px 2px rgba(0, 0, 0, 0.04);
|
||||
}
|
||||
|
||||
.card-title {
|
||||
font-size: 14px;
|
||||
font-weight: 600;
|
||||
color: #303133;
|
||||
margin-bottom: 16px;
|
||||
padding-bottom: 10px;
|
||||
border-bottom: 1px solid #f0f0f0;
|
||||
}
|
||||
|
||||
.detail-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(4, 1fr);
|
||||
gap: 16px;
|
||||
}
|
||||
|
||||
.detail-item {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 4px;
|
||||
}
|
||||
|
||||
.detail-label {
|
||||
font-size: 12px;
|
||||
color: #909399;
|
||||
}
|
||||
|
||||
.detail-value {
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
color: #303133;
|
||||
|
||||
&.highlight {
|
||||
color: #409eff;
|
||||
}
|
||||
|
||||
&.danger {
|
||||
color: #f56c6c;
|
||||
}
|
||||
|
||||
&.success {
|
||||
color: #67c23a;
|
||||
}
|
||||
|
||||
&.total {
|
||||
color: #e6a23c;
|
||||
font-size: 18px;
|
||||
}
|
||||
}
|
||||
|
||||
.status-bar {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
padding: 12px 20px;
|
||||
background-color: #fff;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
.status-label {
|
||||
font-size: 13px;
|
||||
color: #606266;
|
||||
}
|
||||
</style>
|
||||
207
klp-ui/src/views/perf/index/YearMonthTabs.vue
Normal file
207
klp-ui/src/views/perf/index/YearMonthTabs.vue
Normal file
@@ -0,0 +1,207 @@
|
||||
<template>
|
||||
<div class="year-month-tabs">
|
||||
<!-- Year Tabs -->
|
||||
<div class="year-row">
|
||||
<div
|
||||
v-for="y in years"
|
||||
:key="y"
|
||||
class="year-tab"
|
||||
:class="{ active: currentYear === y }"
|
||||
@click="handleYearClick(y)"
|
||||
>{{ y }}年</div>
|
||||
</div>
|
||||
<!-- Month Tabs -->
|
||||
<div class="month-row" v-if="currentYear">
|
||||
<div
|
||||
v-for="m in 12"
|
||||
:key="m"
|
||||
class="month-tab"
|
||||
:class="{ active: currentYear === selectedYear && currentMonth === m }"
|
||||
:disabled="!isMonthAvailable(currentYear, m)"
|
||||
@click="handleMonthClick(m)"
|
||||
>{{ String(m).padStart(2, '0') }}月</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'YearMonthTabs',
|
||||
props: {
|
||||
/** 当前选中的年月,格式 { year, month } */
|
||||
value: {
|
||||
type: Object,
|
||||
default: null
|
||||
},
|
||||
/** 可选的年份范围偏移:往前推 N 年 */
|
||||
pastYears: {
|
||||
type: Number,
|
||||
default: 5
|
||||
},
|
||||
/** 可选的年份范围偏移:往后推 N 年 */
|
||||
futureYears: {
|
||||
type: Number,
|
||||
default: 1
|
||||
},
|
||||
/** 月份可用性判断函数 (year, month) => boolean,默认全部可用 */
|
||||
monthValidator: {
|
||||
type: Function,
|
||||
default: null
|
||||
}
|
||||
},
|
||||
data() {
|
||||
const now = new Date()
|
||||
const currentYear = now.getFullYear()
|
||||
const currentMonth = now.getMonth() + 1
|
||||
return {
|
||||
selectedYear: this.value?.year || currentYear,
|
||||
currentMonth: this.value?.month || currentMonth
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
currentYear: {
|
||||
get() { return this.selectedYear },
|
||||
set(v) { this.selectedYear = v }
|
||||
},
|
||||
years() {
|
||||
const now = new Date().getFullYear()
|
||||
const start = now - this.pastYears
|
||||
const end = now + this.futureYears
|
||||
const list = []
|
||||
for (let y = start; y <= end; y++) {
|
||||
list.push(y)
|
||||
}
|
||||
return list
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
value: {
|
||||
immediate: true,
|
||||
handler(v) {
|
||||
if (v && v.year && v.month) {
|
||||
this.selectedYear = v.year
|
||||
this.currentMonth = v.month
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
isMonthAvailable(year, month) {
|
||||
if (this.monthValidator) {
|
||||
return this.monthValidator(year, month)
|
||||
}
|
||||
// 默认不限制未来月份
|
||||
return true
|
||||
},
|
||||
handleYearClick(year) {
|
||||
this.selectedYear = year
|
||||
// 切换年份时,如果当前月份不可用则重置为1月
|
||||
if (!this.isMonthAvailable(year, this.currentMonth)) {
|
||||
this.currentMonth = 1
|
||||
}
|
||||
this.emitChange()
|
||||
},
|
||||
handleMonthClick(month) {
|
||||
if (!this.isMonthAvailable(this.selectedYear, month)) return
|
||||
this.currentMonth = month
|
||||
this.emitChange()
|
||||
},
|
||||
emitChange() {
|
||||
this.$emit('input', { year: this.selectedYear, month: this.currentMonth })
|
||||
this.$emit('change', { year: this.selectedYear, month: this.currentMonth })
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.year-month-tabs {
|
||||
background-color: #fff;
|
||||
border-bottom: 1px solid #e4e7ed;
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
.year-row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 0 16px;
|
||||
height: 36px;
|
||||
gap: 0;
|
||||
border-bottom: 1px solid #f0f0f0;
|
||||
}
|
||||
|
||||
.year-tab {
|
||||
position: relative;
|
||||
padding: 0 16px;
|
||||
height: 100%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
font-size: 13px;
|
||||
color: #606266;
|
||||
cursor: pointer;
|
||||
transition: color 0.2s;
|
||||
|
||||
&:hover {
|
||||
color: #409eff;
|
||||
}
|
||||
|
||||
&.active {
|
||||
color: #409eff;
|
||||
font-weight: 600;
|
||||
|
||||
&::after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
left: 12px;
|
||||
right: 12px;
|
||||
height: 2px;
|
||||
background-color: #409eff;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.month-row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 0 16px;
|
||||
height: 34px;
|
||||
gap: 0;
|
||||
}
|
||||
|
||||
.month-tab {
|
||||
padding: 0 10px;
|
||||
height: 100%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
font-size: 12px;
|
||||
color: #909399;
|
||||
cursor: pointer;
|
||||
transition: color 0.2s;
|
||||
position: relative;
|
||||
|
||||
&:hover:not([disabled]) {
|
||||
color: #409eff;
|
||||
}
|
||||
|
||||
&.active {
|
||||
color: #409eff;
|
||||
font-weight: 600;
|
||||
|
||||
&::after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
left: 6px;
|
||||
right: 6px;
|
||||
height: 2px;
|
||||
background-color: #409eff;
|
||||
}
|
||||
}
|
||||
|
||||
&[disabled] {
|
||||
color: #c0c4cc;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
206
klp-ui/src/views/perf/index/index.vue
Normal file
206
klp-ui/src/views/perf/index/index.vue
Normal file
@@ -0,0 +1,206 @@
|
||||
<template>
|
||||
<div class="perf-index-page">
|
||||
<!-- Top: Year-Month Tabs -->
|
||||
<YearMonthTabs v-model="selectedDate" @change="handleDateChange" />
|
||||
|
||||
<!-- Main: Left Dept List + Right Content -->
|
||||
<div class="main-layout">
|
||||
<!-- Left: Department List -->
|
||||
<div class="dept-panel">
|
||||
<div class="dept-panel-header">
|
||||
<span class="dept-panel-title">部门列表</span>
|
||||
</div>
|
||||
<div class="dept-list" v-loading="deptLoading">
|
||||
<div
|
||||
v-for="dept in deptList"
|
||||
:key="dept.id"
|
||||
class="dept-item"
|
||||
:class="{ active: currentDept && currentDept.id === dept.id }"
|
||||
@click="handleDeptSelect(dept)"
|
||||
>
|
||||
<span class="dept-name">{{ dept.deptName }}</span>
|
||||
</div>
|
||||
<div v-if="!deptLoading && deptList.length === 0" class="dept-empty">暂无部门数据</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Right: Content -->
|
||||
<div class="content-panel">
|
||||
<PageHeader
|
||||
v-if="currentDept"
|
||||
:dept-name="currentDept.deptName"
|
||||
:period="periodStr"
|
||||
:active-tab.sync="activeTab"
|
||||
@refresh="handleRefresh"
|
||||
/>
|
||||
<div v-else class="no-dept-tip">
|
||||
<span class="placeholder-text">请先在左侧选择一个部门</span>
|
||||
</div>
|
||||
|
||||
<div class="panel-container" v-if="currentDept">
|
||||
<PerfConfigPanel v-if="activeTab === 'perfConfig'" :dept-id="currentDept.id" :period="periodStr" />
|
||||
<SalaryPanel v-if="activeTab === 'salary'" :dept-id="currentDept.id" :dept-name="currentDept.deptName" :period="periodStr" />
|
||||
<SummaryPanel v-if="activeTab === 'summary'" :dept-id="currentDept.id" :period="periodStr" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import YearMonthTabs from './YearMonthTabs'
|
||||
import PageHeader from './PageHeader'
|
||||
import PerfConfigPanel from './PerfConfigPanel'
|
||||
import SalaryPanel from './SalaryPanel'
|
||||
import SummaryPanel from './SummaryPanel'
|
||||
import { listPerfDept } from '@/api/perf/dept'
|
||||
|
||||
export default {
|
||||
name: 'PerfIndex',
|
||||
components: { YearMonthTabs, PageHeader, PerfConfigPanel, SalaryPanel, SummaryPanel },
|
||||
data() {
|
||||
const now = new Date()
|
||||
return {
|
||||
deptLoading: false,
|
||||
deptList: [],
|
||||
currentDept: null,
|
||||
selectedDate: {
|
||||
year: now.getFullYear(),
|
||||
month: now.getMonth() + 1
|
||||
},
|
||||
activeTab: 'perfConfig'
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
periodStr() {
|
||||
const { year, month } = this.selectedDate
|
||||
return `${year}-${String(month).padStart(2, '0')}`
|
||||
}
|
||||
},
|
||||
created() {
|
||||
this.loadDeptList()
|
||||
},
|
||||
methods: {
|
||||
loadDeptList() {
|
||||
this.deptLoading = true
|
||||
listPerfDept({}).then(res => {
|
||||
this.deptList = res.rows || []
|
||||
}).finally(() => {
|
||||
this.deptLoading = false
|
||||
})
|
||||
},
|
||||
handleDeptSelect(dept) {
|
||||
this.currentDept = dept
|
||||
},
|
||||
handleDateChange(date) {
|
||||
this.selectedDate = date
|
||||
},
|
||||
handleRefresh() {
|
||||
// Force child components to reload by toggling through the tab
|
||||
// (Each panel watches deptId/period and will reload on change)
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.perf-index-page {
|
||||
height: calc(100vh - 84px);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
background-color: #f0f2f5;
|
||||
}
|
||||
|
||||
.main-layout {
|
||||
flex: 1;
|
||||
display: grid;
|
||||
grid-template-columns: 220px 1fr;
|
||||
gap: 0;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
// ======== Left: Department Panel ========
|
||||
.dept-panel {
|
||||
background-color: #fff;
|
||||
border-right: 1px solid #e4e7ed;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.dept-panel-header {
|
||||
padding: 14px 16px 10px;
|
||||
font-size: 14px;
|
||||
font-weight: 600;
|
||||
color: #303133;
|
||||
border-bottom: 1px solid #f0f0f0;
|
||||
}
|
||||
|
||||
.dept-list {
|
||||
flex: 1;
|
||||
overflow-y: auto;
|
||||
padding: 4px 0;
|
||||
}
|
||||
|
||||
.dept-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 10px 16px;
|
||||
cursor: pointer;
|
||||
transition: background-color 0.15s;
|
||||
|
||||
&:hover {
|
||||
background-color: #f5f7fa;
|
||||
}
|
||||
|
||||
&.active {
|
||||
background-color: #ecf5ff;
|
||||
|
||||
.dept-name {
|
||||
color: #409eff;
|
||||
font-weight: 600;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.dept-name {
|
||||
font-size: 14px;
|
||||
color: #303133;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.dept-empty {
|
||||
padding: 32px 0;
|
||||
text-align: center;
|
||||
color: #c0c4cc;
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
// ======== Right: Content Panel ========
|
||||
.content-panel {
|
||||
background-color: #fff;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.no-dept-tip {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.placeholder-text {
|
||||
font-size: 14px;
|
||||
color: #c0c4cc;
|
||||
}
|
||||
|
||||
.panel-container {
|
||||
flex: 1;
|
||||
overflow: hidden;
|
||||
}
|
||||
</style>
|
||||
@@ -1,98 +1,78 @@
|
||||
<template>
|
||||
<div class="app-container">
|
||||
<!-- 左右分栏布局 -->
|
||||
<div class="file-layout">
|
||||
<!-- 左侧边栏 -->
|
||||
<div class="file-sidebar">
|
||||
<!-- 统计卡片 -->
|
||||
<el-card class="summary-card" shadow="hover" :body-style="{ padding: '10px' }">
|
||||
<el-row :gutter="6">
|
||||
<el-col :span="12">
|
||||
<div class="summary-item">
|
||||
<span class="item-label">文件总数</span>
|
||||
<span class="item-value">{{ stats.totalFiles }}</span>
|
||||
</div>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<div class="summary-item">
|
||||
<span class="item-label">公开文件</span>
|
||||
<span class="item-value">{{ stats.publicFiles }}</span>
|
||||
</div>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<el-row :gutter="6" style="margin-top: 4px;">
|
||||
<el-col :span="12">
|
||||
<div class="summary-item">
|
||||
<span class="item-label">私有文件</span>
|
||||
<span class="item-value">{{ stats.privateFiles }}</span>
|
||||
</div>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<div class="summary-item">
|
||||
<span class="item-label">我上传的</span>
|
||||
<span class="item-value">{{ stats.myFiles }}</span>
|
||||
</div>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<!-- 顶部:统计卡片 + 工具栏 + 筛选标签 -->
|
||||
<div class="page-header">
|
||||
<div class="stats-area">
|
||||
<el-card class="stats-card" shadow="hover" :body-style="{ padding: '0' }">
|
||||
<div class="stats-grid">
|
||||
<div class="stats-item">
|
||||
<span class="stats-label">文件总数</span>
|
||||
<span class="stats-value">{{ stats.totalFiles }}</span>
|
||||
</div>
|
||||
<div class="stats-item">
|
||||
<span class="stats-label">公开文件</span>
|
||||
<span class="stats-value">{{ stats.publicFiles }}</span>
|
||||
</div>
|
||||
<div class="stats-item">
|
||||
<span class="stats-label">私有文件</span>
|
||||
<span class="stats-value">{{ stats.privateFiles }}</span>
|
||||
</div>
|
||||
<div class="stats-item">
|
||||
<span class="stats-label">我上传的</span>
|
||||
<span class="stats-value">{{ stats.myFiles }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</el-card>
|
||||
</div>
|
||||
|
||||
<!-- 搜索框 -->
|
||||
<div class="sidebar-section">
|
||||
<div class="header-main">
|
||||
<div class="filter-row">
|
||||
<div class="type-tags">
|
||||
<span
|
||||
v-for="opt in fileTypeOptions"
|
||||
:key="opt.value"
|
||||
class="type-tag"
|
||||
:class="{ active: (queryParams.fileType || '') === opt.value }"
|
||||
@click="handleTypeTabClick(opt.value)"
|
||||
>{{ opt.label }}</span>
|
||||
</div>
|
||||
<div class="filter-extra">
|
||||
<el-select v-model="queryParams.scopeType" placeholder="可见范围" clearable size="mini" @change="handleQuery" style="width:110px;">
|
||||
<el-option label="公开" :value="1" />
|
||||
<el-option label="私有" :value="2" />
|
||||
</el-select>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="toolbar-row">
|
||||
<el-input
|
||||
v-model="queryParams.fileName"
|
||||
prefix-icon="el-icon-search"
|
||||
placeholder="按名称搜索"
|
||||
clearable
|
||||
size="mini"
|
||||
style="width:200px;"
|
||||
@change="handleQuery"
|
||||
@clear="handleQuery"
|
||||
></el-input>
|
||||
</div>
|
||||
|
||||
<!-- 文件类型列表 -->
|
||||
<div class="sidebar-section sidebar-file-types">
|
||||
<div class="sidebar-label">文件类型</div>
|
||||
<div class="file-type-list">
|
||||
<div
|
||||
v-for="opt in fileTypeOptions"
|
||||
:key="opt.value"
|
||||
class="type-list-item"
|
||||
:class="{ active: (queryParams.fileType || '') === opt.value }"
|
||||
@click="handleTypeTabClick(opt.value)"
|
||||
>{{ opt.label }}</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<!-- 右侧主区域 -->
|
||||
<div class="file-main">
|
||||
<!-- 高级搜索 + 操作按钮 -->
|
||||
<div class="main-toolbar">
|
||||
<el-button icon="el-icon-sort" size="mini" @click="toggleQuery">高级搜索</el-button>
|
||||
/>
|
||||
<el-button type="primary" icon="el-icon-plus" size="mini" @click="handleAdd">上传文件</el-button>
|
||||
<el-button type="danger" icon="el-icon-delete" size="mini" :disabled="multiple" @click="handleDelete">删除</el-button>
|
||||
<span v-if="selectedCardIds.length > 0" class="selected-count">已选 {{ selectedCardIds.length }} 项</span>
|
||||
</div>
|
||||
|
||||
<div v-show="showQuery" class="advanced-search">
|
||||
<div class="advanced-search">
|
||||
<el-form :model="queryParams" inline size="mini" class="advanced-search-form">
|
||||
<el-form-item label="订单编号">
|
||||
<el-input v-model="queryParams.orderNo" placeholder="订单编号" clearable @change="handleQuery" style="width:140px;"></el-input>
|
||||
<el-input v-model="queryParams.orderNo" placeholder="订单编号" clearable @change="handleQuery" style="width:140px;" />
|
||||
</el-form-item>
|
||||
<el-form-item label="所属部门">
|
||||
<el-input v-model="queryParams.dept" placeholder="所属部门" clearable @change="handleQuery" style="width:140px;"></el-input>
|
||||
<el-input v-model="queryParams.dept" placeholder="所属部门" clearable @change="handleQuery" style="width:140px;" />
|
||||
</el-form-item>
|
||||
<el-form-item label="文件类型">
|
||||
<el-select v-model="queryParams.fileType" placeholder="文件类型" clearable @change="handleQuery" style="width:120px;">
|
||||
<el-option v-for="item in dict.type.sys_file_type" :key="item.value" :label="item.label" :value="item.value" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="可见范围">
|
||||
<el-select v-model="queryParams.scopeType" placeholder="可见范围" clearable @change="handleQuery" style="width:120px;">
|
||||
<el-option label="公开" :value="1" />
|
||||
<el-option label="私有" :value="2" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="上传时间">
|
||||
<el-date-picker
|
||||
v-model="dateRange"
|
||||
@@ -105,71 +85,56 @@
|
||||
clearable
|
||||
style="width:240px;"
|
||||
@change="handleQuery"
|
||||
></el-date-picker>
|
||||
/>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</div>
|
||||
|
||||
<!-- 文件表格 -->
|
||||
<div class="file-table-wrapper">
|
||||
<KLPTable v-loading="loading" :data="fileList" @selection-change="handleSelectionChange" highlight-current-row @row-click="handleRowClick">
|
||||
<el-table-column type="selection" width="50" align="center" />
|
||||
<el-table-column label="文件名称" align="center" prop="fileName" :show-overflow-tooltip="true" min-width="160">
|
||||
<template slot-scope="scope">
|
||||
<el-link type="primary" @click="handleShowInfo(scope.row)">{{ scope.row.fileName }}</el-link>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="上传人" align="center" prop="createByName" width="120" :show-overflow-tooltip="true" />
|
||||
<el-table-column label="所属部门" align="center" prop="dept" width="120" :show-overflow-tooltip="true" />
|
||||
<el-table-column label="订单编号" align="center" prop="orderNo" width="130" :show-overflow-tooltip="true" />
|
||||
<el-table-column label="文件类型" align="center" width="100">
|
||||
<template slot-scope="scope">
|
||||
<dict-tag :options="dict.type.sys_file_type" :value="scope.row.fileType"/>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="文件大小" align="center" width="90">
|
||||
<template slot-scope="scope">
|
||||
<span>{{ formatFileSize(scope.row.fileSize) }}</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="可见范围" align="center" width="80">
|
||||
<template slot-scope="scope">
|
||||
<el-tag :type="scope.row.scopeType === 1 ? 'success' : 'warning'" size="small">
|
||||
{{ scope.row.scopeType === 1 ? '公开' : '私有' }}
|
||||
</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="上传时间" align="center" width="155">
|
||||
<template slot-scope="scope">
|
||||
<span>{{ parseTime(scope.row.createTime) }}</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="浏览次数" align="center" width="75">
|
||||
<template slot-scope="scope">
|
||||
<span>{{ scope.row.viewCount != null ? scope.row.viewCount : 0 }}</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="操作" align="center" width="140" fixed="right">
|
||||
<template slot-scope="scope">
|
||||
<div class="action-cell">
|
||||
<el-button type="text" size="mini" icon="el-icon-view" @click.stop="handleShowInfo(scope.row)">详情</el-button>
|
||||
<el-button v-if="canEdit(scope.row)" type="text" size="mini" icon="el-icon-edit" @click.stop="handleUpdate(scope.row)">编辑</el-button>
|
||||
</div>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</KLPTable>
|
||||
|
||||
<pagination
|
||||
v-show="total>0"
|
||||
:total="total"
|
||||
:page.sync="queryParams.pageNum"
|
||||
:limit.sync="queryParams.pageSize"
|
||||
@pagination="getList"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 文件卡片网格 -->
|
||||
<div class="card-container" v-loading="loading">
|
||||
<div v-if="fileList.length > 0" class="card-grid">
|
||||
<div
|
||||
v-for="file in fileList"
|
||||
:key="file.fileId"
|
||||
class="file-card"
|
||||
:class="{ selected: isSelected(file.fileId) }"
|
||||
@click="handleCardClick(file)"
|
||||
>
|
||||
<div class="card-checkbox" @click.stop="toggleSelect(file)">
|
||||
<i v-if="isSelected(file.fileId)" class="el-icon-check"></i>
|
||||
</div>
|
||||
<div class="card-icon" :class="getFileIconClass(file)">
|
||||
<span class="card-ext">{{ getFileExt(file) }}</span>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<div class="card-name" :title="file.fileName">{{ file.fileName }}</div>
|
||||
<div class="card-uploader">{{ file.createByName || file.createBy }}</div>
|
||||
<div class="card-meta">
|
||||
<span>{{ formatFileSize(file.fileSize) }}</span>
|
||||
<span class="meta-sep">·</span>
|
||||
<span>{{ parseTime(file.createTime, '{mm}-{dd}') }}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="card-actions">
|
||||
<el-button type="text" size="mini" icon="el-icon-view" @click.stop="handleShowInfo(file)">详情</el-button>
|
||||
<el-button type="text" size="mini" icon="el-icon-download" @click.stop="downloadFile(file)">下载</el-button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<el-empty v-else-if="!loading" description="暂无文件" />
|
||||
</div>
|
||||
|
||||
<!-- 分页 -->
|
||||
<pagination
|
||||
v-show="total>0"
|
||||
:total="total"
|
||||
:page.sync="queryParams.pageNum"
|
||||
:limit.sync="queryParams.pageSize"
|
||||
@pagination="getList"
|
||||
/>
|
||||
|
||||
<!-- 上传/编辑对话框 -->
|
||||
<el-dialog :title="dialogTitle" :visible.sync="open" width="650px" append-to-body @close="handleDialogClose">
|
||||
<el-form ref="form" :model="form" :rules="rules" label-width="100px">
|
||||
@@ -225,66 +190,68 @@
|
||||
</el-dialog>
|
||||
|
||||
<!-- 文件详情弹窗(含预览) -->
|
||||
<el-dialog :title="infoTitle" :visible.sync="infoVisible" width="1100px" append-to-body top="5vh" @close="handleInfoClose">
|
||||
<el-dialog :title="infoTitle" :visible.sync="infoVisible" width="1200px" append-to-body top="3vh" @close="handleInfoClose">
|
||||
<div v-if="infoFile" class="info-dialog-body">
|
||||
<div class="info-preview">
|
||||
<ImagePreview v-if="infoFileCategory === 'image'" :src="infoFile.filePath" />
|
||||
<PdfPreview v-else-if="infoFileCategory === 'pdf'" :src="infoFile.filePath" />
|
||||
<DocxPreview v-else-if="infoFileCategory === 'docx'" :src="infoFile.filePath" />
|
||||
<XlsxPreview v-else-if="infoFileCategory === 'xlsx'" :src="infoFile.filePath" />
|
||||
<XlsPreview v-else-if="infoFileCategory === 'xls'" :src="infoFile.filePath" />
|
||||
<div v-else class="info-preview-empty">
|
||||
<el-empty description="暂不支持预览此文件类型" />
|
||||
<div class="info-main-row">
|
||||
<div class="info-preview">
|
||||
<ImagePreview v-if="infoFileCategory === 'image'" :src="infoFile.filePath" />
|
||||
<PdfPreview v-else-if="infoFileCategory === 'pdf'" :src="infoFile.filePath" />
|
||||
<DocxPreview v-else-if="infoFileCategory === 'docx'" :src="infoFile.filePath" />
|
||||
<XlsxPreview v-else-if="infoFileCategory === 'xlsx'" :src="infoFile.filePath" />
|
||||
<XlsPreview v-else-if="infoFileCategory === 'xls'" :src="infoFile.filePath" />
|
||||
<div v-else class="info-preview-empty">
|
||||
<el-empty description="暂不支持预览此文件类型" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="info-meta">
|
||||
<el-descriptions :column="1" border size="small">
|
||||
<el-descriptions-item label="文件名称">{{ infoFile.fileName }}</el-descriptions-item>
|
||||
<el-descriptions-item label="文件类型">
|
||||
<dict-tag :options="dict.type.sys_file_type" :value="infoFile.fileType"/>
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="文件大小">{{ formatFileSize(infoFile.fileSize) }}</el-descriptions-item>
|
||||
<el-descriptions-item label="文件后缀">{{ infoFile.suffix || '-' }}</el-descriptions-item>
|
||||
<el-descriptions-item label="浏览次数">{{ infoFile.viewCount != null ? infoFile.viewCount : 0 }} 次</el-descriptions-item>
|
||||
<el-descriptions-item label="订单编号">{{ infoFile.orderNo || '-' }}</el-descriptions-item>
|
||||
<el-descriptions-item label="所属部门">{{ infoFile.dept || '-' }}</el-descriptions-item>
|
||||
<el-descriptions-item label="可见范围">
|
||||
<el-tag :type="infoFile.scopeType === 1 ? 'success' : 'warning'" size="small">
|
||||
{{ infoFile.scopeType === 1 ? '公开' : '私有' }}
|
||||
</el-tag>
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="上传人">{{ infoFile.createByName || infoFile.createBy }}</el-descriptions-item>
|
||||
<el-descriptions-item label="上传时间">{{ parseTime(infoFile.createTime) }}</el-descriptions-item>
|
||||
<el-descriptions-item label="备注">{{ infoFile.remark || '-' }}</el-descriptions-item>
|
||||
</el-descriptions>
|
||||
<div style="margin-top:12px;text-align:right;">
|
||||
<el-button type="warning" plain size="mini" icon="el-icon-download" @click="handleExportInfo">导出</el-button>
|
||||
<el-button type="primary" size="mini" icon="el-icon-download" @click="downloadFile(infoFile)">下载</el-button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="info-meta">
|
||||
<el-descriptions :column="1" border size="small">
|
||||
<el-descriptions-item label="文件名称">{{ infoFile.fileName }}</el-descriptions-item>
|
||||
<el-descriptions-item label="文件类型">
|
||||
<dict-tag :options="dict.type.sys_file_type" :value="infoFile.fileType"/>
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="文件大小">{{ formatFileSize(infoFile.fileSize) }}</el-descriptions-item>
|
||||
<el-descriptions-item label="文件后缀">{{ infoFile.suffix || '-' }}</el-descriptions-item>
|
||||
<el-descriptions-item label="浏览次数">{{ infoFile.viewCount != null ? infoFile.viewCount : 0 }} 次</el-descriptions-item>
|
||||
<el-descriptions-item label="订单编号">{{ infoFile.orderNo || '-' }}</el-descriptions-item>
|
||||
<el-descriptions-item label="所属部门">{{ infoFile.dept || '-' }}</el-descriptions-item>
|
||||
<el-descriptions-item label="可见范围">
|
||||
<el-tag :type="infoFile.scopeType === 1 ? 'success' : 'warning'" size="small">
|
||||
{{ infoFile.scopeType === 1 ? '公开' : '私有' }}
|
||||
</el-tag>
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="上传人">{{ infoFile.createByName || infoFile.createBy }}</el-descriptions-item>
|
||||
<el-descriptions-item label="上传时间">{{ parseTime(infoFile.createTime) }}</el-descriptions-item>
|
||||
<el-descriptions-item label="备注">{{ infoFile.remark || '-' }}</el-descriptions-item>
|
||||
</el-descriptions>
|
||||
<!-- 评论区域 -->
|
||||
<div class="info-comment">
|
||||
<div class="comment-bar" @click="commentExpanded = !commentExpanded">
|
||||
<span><i class="el-icon-chat-dot-round"></i> 评论 ({{ comments.length }})</span>
|
||||
<span class="comment-toggle">{{ commentExpanded ? '收起' : '展开' }} <i :class="commentExpanded ? 'el-icon-arrow-up' : 'el-icon-arrow-down'"></i></span>
|
||||
</div>
|
||||
<div v-show="commentExpanded" class="comment-body">
|
||||
<div v-if="commentLoading" class="comment-hint"><i class="el-icon-loading"></i> 加载中...</div>
|
||||
<div v-else-if="comments.length === 0" class="comment-hint comment-empty">暂无评论</div>
|
||||
<div v-else class="comment-list">
|
||||
<div v-for="item in comments" :key="item.commentId" class="comment-item">
|
||||
<div class="comment-meta">
|
||||
<span class="comment-dept">{{ item.dept }}</span>
|
||||
<span class="comment-user">{{ item.createBy }}</span>
|
||||
<span class="comment-time">{{ parseTime(item.createTime) }}</span>
|
||||
</div>
|
||||
<div class="comment-text">{{ item.content }}</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="comment-input-area">
|
||||
<el-input v-model="commentInput" type="textarea" :rows="2" placeholder="输入评论..." resize="none" />
|
||||
<el-button type="primary" size="mini" @click="handleAddComment" :disabled="!commentInput.trim()" style="margin-top:6px;">发送</el-button>
|
||||
</div>
|
||||
</div>
|
||||
<!-- 评论区域单独放在下面 -->
|
||||
<div class="info-comment">
|
||||
<div class="comment-bar" @click="commentExpanded = !commentExpanded">
|
||||
<span><i class="el-icon-chat-dot-round"></i> 评论 ({{ comments.length }})</span>
|
||||
<span class="comment-toggle">{{ commentExpanded ? '收起' : '展开' }} <i :class="commentExpanded ? 'el-icon-arrow-up' : 'el-icon-arrow-down'"></i></span>
|
||||
</div>
|
||||
<div style="margin-top:12px;text-align:right;">
|
||||
<el-button type="warning" plain size="mini" icon="el-icon-download" @click="handleExportInfo">导出</el-button>
|
||||
<el-button type="primary" size="mini" icon="el-icon-download" @click="downloadFile(infoFile)">下载</el-button>
|
||||
<div v-show="commentExpanded" class="comment-body">
|
||||
<div v-if="commentLoading" class="comment-hint"><i class="el-icon-loading"></i> 加载中...</div>
|
||||
<div v-else-if="comments.length === 0" class="comment-hint comment-empty">暂无评论</div>
|
||||
<div v-else class="comment-list">
|
||||
<div v-for="item in comments" :key="item.commentId" class="comment-item">
|
||||
<div class="comment-meta">
|
||||
<span class="comment-dept">{{ item.dept }}</span>
|
||||
<span class="comment-user">{{ item.createBy }}</span>
|
||||
<span class="comment-time">{{ parseTime(item.createTime) }}</span>
|
||||
</div>
|
||||
<div class="comment-text">{{ item.content }}</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="comment-input-area">
|
||||
<el-input v-model="commentInput" type="textarea" :rows="2" placeholder="输入评论..." resize="none" />
|
||||
<el-button type="primary" size="mini" @click="handleAddComment" :disabled="!commentInput.trim()" style="margin-top:6px;">发送</el-button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -320,12 +287,11 @@ export default {
|
||||
loading: false,
|
||||
// 选中数组
|
||||
ids: [],
|
||||
selectedCardIds: [],
|
||||
// 非单个禁用
|
||||
single: true,
|
||||
// 非多个禁用
|
||||
multiple: true,
|
||||
// 显示高级搜索
|
||||
showQuery: false,
|
||||
// 总条数
|
||||
total: 0,
|
||||
// 文件列表
|
||||
@@ -433,6 +399,9 @@ export default {
|
||||
/** 查询文件列表 */
|
||||
getList() {
|
||||
this.loading = true
|
||||
this.selectedCardIds = []
|
||||
this.ids = []
|
||||
this.multiple = true
|
||||
const params = this.addDateRange(this.queryParams, this.dateRange)
|
||||
listFile(params).then(response => {
|
||||
this.fileList = response.rows
|
||||
@@ -450,17 +419,57 @@ export default {
|
||||
listFile({ pageNum: 1, pageSize: 1, createBy: this.$store.getters.name }).then(r => { this.stats.myFiles = r.total })
|
||||
listFile({ pageNum: 1, pageSize: 1 }).then(r => { this.stats.totalFiles = r.total })
|
||||
},
|
||||
/** 获取文件扩展名(大写,用于图标显示) */
|
||||
getFileExt(file) {
|
||||
const raw = file.suffix || file.fileName || ''
|
||||
const ext = raw.includes('.') ? raw.split('.').pop() : raw
|
||||
return ext.slice(0, 4).toUpperCase()
|
||||
},
|
||||
/** 文件图标颜色类 */
|
||||
getFileIconClass(file) {
|
||||
const ext = (file.suffix || file.fileName || '').toLowerCase()
|
||||
if (['doc', 'docx'].includes(ext)) return 'icon-word'
|
||||
if (['xls', 'xlsx'].includes(ext)) return 'icon-excel'
|
||||
if (['ppt', 'pptx'].includes(ext)) return 'icon-ppt'
|
||||
if (ext === 'pdf') return 'icon-pdf'
|
||||
if (['png', 'jpg', 'jpeg', 'gif', 'bmp', 'webp', 'svg', 'ico'].includes(ext)) return 'icon-image'
|
||||
if (['zip', 'rar', '7z', 'tar', 'gz'].includes(ext)) return 'icon-archive'
|
||||
if (['mp4', 'avi', 'mov', 'mkv', 'wmv', 'flv'].includes(ext)) return 'icon-video'
|
||||
if (['mp3', 'wav', 'flac', 'aac', 'ogg'].includes(ext)) return 'icon-audio'
|
||||
if (['txt', 'log', 'md'].includes(ext)) return 'icon-text'
|
||||
return 'icon-other'
|
||||
},
|
||||
/** 格式化文件大小 */
|
||||
formatFileSize(size) {
|
||||
if (!size) return '0 B'
|
||||
if (size < 1024) return size + ' B'
|
||||
if (size < 1024 * 1024) return (size / 1024).toFixed(2) + ' KB'
|
||||
return (size / 1024 / 1024).toFixed(2) + ' MB'
|
||||
if (size < 1024 * 1024) return (size / 1024).toFixed(1) + ' KB'
|
||||
return (size / 1024 / 1024).toFixed(1) + ' MB'
|
||||
},
|
||||
/** 是否可编辑 */
|
||||
canEdit(row) {
|
||||
return row.createBy === this.$store.getters.name
|
||||
},
|
||||
/** 卡片选中判断 */
|
||||
isSelected(fileId) {
|
||||
return this.selectedCardIds.includes(fileId)
|
||||
},
|
||||
/** 切换卡片选中 */
|
||||
toggleSelect(file) {
|
||||
const idx = this.selectedCardIds.indexOf(file.fileId)
|
||||
if (idx > -1) {
|
||||
this.selectedCardIds.splice(idx, 1)
|
||||
} else {
|
||||
this.selectedCardIds.push(file.fileId)
|
||||
}
|
||||
this.ids = [...this.selectedCardIds]
|
||||
this.single = this.selectedCardIds.length !== 1
|
||||
this.multiple = this.selectedCardIds.length === 0
|
||||
},
|
||||
/** 点击卡片(打开详情) */
|
||||
handleCardClick(file) {
|
||||
this.handleShowInfo(file)
|
||||
},
|
||||
/** 取消按钮 */
|
||||
cancel() {
|
||||
this.open = false
|
||||
@@ -501,10 +510,6 @@ export default {
|
||||
this.queryParams.pageNum = 1
|
||||
this.getList()
|
||||
},
|
||||
/** 切换高级搜索 */
|
||||
toggleQuery() {
|
||||
this.showQuery = !this.showQuery
|
||||
},
|
||||
/** 文件类型筛选切换 */
|
||||
handleTypeTabClick(value) {
|
||||
this.queryParams.fileType = value || undefined
|
||||
@@ -525,7 +530,7 @@ export default {
|
||||
}
|
||||
this.getList()
|
||||
},
|
||||
/** 多选框 */
|
||||
/** 多选框(兼容保留) */
|
||||
handleSelectionChange(selection) {
|
||||
this.ids = selection.map(item => item.fileId)
|
||||
this.single = selection.length !== 1
|
||||
@@ -719,126 +724,128 @@ export default {
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
/* 左右分栏 */
|
||||
.file-layout {
|
||||
/* ========== 页面顶部:统计 + 工具栏 + 筛选标签 ========== */
|
||||
.page-header {
|
||||
display: flex;
|
||||
align-items: stretch;
|
||||
gap: 0;
|
||||
height: calc(100vh - 100px);
|
||||
gap: 12px;
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
/* 左侧边栏 */
|
||||
.file-sidebar {
|
||||
/* 统计卡片区域 */
|
||||
.stats-area {
|
||||
width: 220px;
|
||||
flex-shrink: 0;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
padding-right: 12px;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.sidebar-section {
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
|
||||
.sidebar-label {
|
||||
font-size: 13px;
|
||||
font-weight: 500;
|
||||
color: #606266;
|
||||
margin-bottom: 6px;
|
||||
padding-left: 2px;
|
||||
}
|
||||
|
||||
/* 文件类型列表(纵向) */
|
||||
.sidebar-file-types {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
min-height: 0;
|
||||
}
|
||||
|
||||
.file-type-list {
|
||||
flex: 1;
|
||||
overflow-y: auto;
|
||||
border: 1px solid #ebeef5;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
.type-list-item {
|
||||
padding: 8px 12px;
|
||||
font-size: 13px;
|
||||
color: #606266;
|
||||
cursor: pointer;
|
||||
user-select: none;
|
||||
border-bottom: 1px solid #f2f2f2;
|
||||
transition: all 0.15s;
|
||||
}
|
||||
|
||||
.type-list-item:last-child {
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
.type-list-item:hover {
|
||||
color: #5F7BA0;
|
||||
background: #f5f7fa;
|
||||
}
|
||||
|
||||
.type-list-item.active {
|
||||
color: #fff;
|
||||
background: #5F7BA0;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
/* 操作列按钮对齐 */
|
||||
.action-cell {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 2px;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
/* 统计卡片 */
|
||||
.summary-card {
|
||||
.stats-card {
|
||||
height: 100%;
|
||||
border: none;
|
||||
box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.04);
|
||||
}
|
||||
|
||||
.summary-item {
|
||||
.stats-card /deep/ .el-card__body {
|
||||
height: 100%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.stats-grid {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1fr;
|
||||
gap: 4px;
|
||||
width: 100%;
|
||||
padding: 4px;
|
||||
}
|
||||
|
||||
.stats-item {
|
||||
text-align: center;
|
||||
padding: 6px 4px;
|
||||
padding: 2px 4px;
|
||||
border: 1px solid #ebeef5;
|
||||
border-radius: 2px;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
.item-label {
|
||||
.stats-label {
|
||||
display: block;
|
||||
font-size: 12px;
|
||||
font-size: 11px;
|
||||
color: #909399;
|
||||
margin-bottom: 2px;
|
||||
line-height: 1.4;
|
||||
}
|
||||
|
||||
.item-value {
|
||||
.stats-value {
|
||||
display: block;
|
||||
font-size: 20px;
|
||||
font-weight: bold;
|
||||
font-size: 18px;
|
||||
font-weight: 700;
|
||||
color: #1f2d3d;
|
||||
line-height: 1.2;
|
||||
line-height: 1.3;
|
||||
}
|
||||
|
||||
/* 右侧主区域 */
|
||||
.file-main {
|
||||
.header-main {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.main-toolbar {
|
||||
/* 工具栏行 */
|
||||
.toolbar-row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
margin-bottom: 8px;
|
||||
padding: 2px 0;
|
||||
}
|
||||
|
||||
.selected-count {
|
||||
font-size: 12px;
|
||||
color: #409eff;
|
||||
margin-left: 4px;
|
||||
}
|
||||
|
||||
/* 筛选标签行 */
|
||||
.filter-row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
margin-bottom: 6px;
|
||||
}
|
||||
|
||||
.type-tags {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 2px;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.type-tag {
|
||||
padding: 3px 14px;
|
||||
font-size: 12px;
|
||||
border-radius: 14px;
|
||||
cursor: pointer;
|
||||
color: #606266;
|
||||
background: #f5f7fa;
|
||||
border: 1px solid #e4e7ed;
|
||||
transition: all 0.2s;
|
||||
user-select: none;
|
||||
line-height: 22px;
|
||||
}
|
||||
|
||||
.type-tag:hover {
|
||||
color: #5F7BA0;
|
||||
border-color: #5F7BA0;
|
||||
background: #f0f4f8;
|
||||
}
|
||||
|
||||
.type-tag.active {
|
||||
color: #fff;
|
||||
background: #5F7BA0;
|
||||
border-color: #5F7BA0;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.filter-extra {
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
/* 高级搜索 */
|
||||
@@ -847,7 +854,7 @@ export default {
|
||||
border: 1px solid #ebeef5;
|
||||
border-radius: 4px;
|
||||
padding: 10px 16px;
|
||||
margin-bottom: 8px;
|
||||
margin-top: 8px;
|
||||
}
|
||||
|
||||
.advanced-search-form {
|
||||
@@ -860,27 +867,191 @@ export default {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
/* 表格区域 */
|
||||
.file-table-wrapper {
|
||||
flex: 1;
|
||||
/* ========== 文件卡片网格 ========== */
|
||||
.card-container {
|
||||
min-height: 300px;
|
||||
}
|
||||
|
||||
.card-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fill, minmax(185px, 1fr));
|
||||
gap: 14px;
|
||||
}
|
||||
|
||||
.file-card {
|
||||
position: relative;
|
||||
background: #fff;
|
||||
border: 1px solid #ebeef5;
|
||||
border-radius: 8px;
|
||||
padding: 20px 14px 14px;
|
||||
cursor: pointer;
|
||||
transition: all 0.25s;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
border: 1px solid #ebeef5;
|
||||
border-radius: 4px;
|
||||
overflow: hidden;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.file-table-wrapper .KLPTable {
|
||||
flex: 1;
|
||||
.file-card:hover {
|
||||
border-color: #c0c4cc;
|
||||
box-shadow: 0 4px 16px 0 rgba(0, 0, 0, 0.08);
|
||||
transform: translateY(-2px);
|
||||
}
|
||||
|
||||
/* 上传区域 */
|
||||
.upload-demo {
|
||||
.file-card.selected {
|
||||
border-color: #409eff;
|
||||
box-shadow: 0 0 0 2px rgba(64, 158, 255, 0.15), 0 4px 16px 0 rgba(0, 0, 0, 0.06);
|
||||
}
|
||||
|
||||
/* 卡片选中复选框 */
|
||||
.card-checkbox {
|
||||
position: absolute;
|
||||
top: 6px;
|
||||
right: 6px;
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
border: 1px solid #d0d5dd;
|
||||
border-radius: 50%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
background: #fff;
|
||||
opacity: 0;
|
||||
transition: opacity 0.2s;
|
||||
z-index: 2;
|
||||
}
|
||||
|
||||
.file-card:hover .card-checkbox {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.file-card.selected .card-checkbox {
|
||||
opacity: 1;
|
||||
background: #409eff;
|
||||
border-color: #409eff;
|
||||
}
|
||||
|
||||
.card-checkbox .el-icon-check {
|
||||
font-size: 12px;
|
||||
color: #fff;
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
/* 文件类型图标 */
|
||||
.card-icon {
|
||||
width: 64px;
|
||||
height: 64px;
|
||||
border-radius: 10px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
margin-bottom: 10px;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.card-ext {
|
||||
font-size: 13px;
|
||||
font-weight: 700;
|
||||
color: #fff;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.5px;
|
||||
}
|
||||
|
||||
/* 各文件类型颜色梯度 */
|
||||
.icon-word {
|
||||
background: linear-gradient(135deg, #4a90d9, #357abd);
|
||||
}
|
||||
.icon-excel {
|
||||
background: linear-gradient(135deg, #52b86a, #3d9e55);
|
||||
}
|
||||
.icon-ppt {
|
||||
background: linear-gradient(135deg, #f09b4a, #d9822e);
|
||||
}
|
||||
.icon-pdf {
|
||||
background: linear-gradient(135deg, #e06060, #cc4444);
|
||||
}
|
||||
.icon-image {
|
||||
background: linear-gradient(135deg, #a877d9, #8e5fc4);
|
||||
}
|
||||
.icon-archive {
|
||||
background: linear-gradient(135deg, #8a9bb0, #6d7e93);
|
||||
}
|
||||
.icon-video {
|
||||
background: linear-gradient(135deg, #4db8a8, #389e8e);
|
||||
}
|
||||
.icon-audio {
|
||||
background: linear-gradient(135deg, #e8924a, #d47d35);
|
||||
}
|
||||
.icon-text {
|
||||
background: linear-gradient(135deg, #90a4ae, #78909c);
|
||||
}
|
||||
.icon-other {
|
||||
background: linear-gradient(135deg, #b0bec5, #90a4ae);
|
||||
}
|
||||
|
||||
/* 卡片内容 */
|
||||
.card-body {
|
||||
text-align: center;
|
||||
width: 100%;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
/* 详情弹窗主体 */
|
||||
.card-name {
|
||||
font-size: 13px;
|
||||
color: #303133;
|
||||
font-weight: 500;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
line-height: 1.4;
|
||||
}
|
||||
|
||||
.card-uploader {
|
||||
font-size: 11px;
|
||||
color: #aaa;
|
||||
margin-top: 2px;
|
||||
line-height: 1.3;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
.card-meta {
|
||||
font-size: 11px;
|
||||
color: #909399;
|
||||
margin-top: 4px;
|
||||
line-height: 1.4;
|
||||
}
|
||||
|
||||
.meta-sep {
|
||||
margin: 0 4px;
|
||||
}
|
||||
|
||||
/* 卡片悬停操作按钮 */
|
||||
.card-actions {
|
||||
margin-top: 10px;
|
||||
display: flex;
|
||||
gap: 6px;
|
||||
opacity: 0;
|
||||
transition: opacity 0.2s;
|
||||
}
|
||||
|
||||
.file-card:hover .card-actions {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.card-actions .el-button {
|
||||
padding: 2px 8px;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
/* ========== 详情弹窗 ========== */
|
||||
.info-dialog-body {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.info-main-row {
|
||||
display: flex;
|
||||
gap: 16px;
|
||||
min-height: 400px;
|
||||
@@ -908,14 +1079,13 @@ export default {
|
||||
}
|
||||
|
||||
.info-meta {
|
||||
width: 320px;
|
||||
width: 340px;
|
||||
flex-shrink: 0;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
/* 详情弹窗评论区 */
|
||||
/* 评论区 */
|
||||
.info-comment {
|
||||
margin-top: 12px;
|
||||
border: 1px solid #ebeef5;
|
||||
border-radius: 4px;
|
||||
}
|
||||
@@ -999,10 +1169,3 @@ export default {
|
||||
border-top: 1px solid #f2f2f2;
|
||||
}
|
||||
</style>
|
||||
|
||||
<!-- 表格选中行高亮 -->
|
||||
<style>
|
||||
.file-main .el-table .current-row > td {
|
||||
background-color: #ecf5ff !important;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -633,8 +633,8 @@ export default {
|
||||
fullPath = '/' + parent + ownPath;
|
||||
}
|
||||
}
|
||||
// 目录(M)的路径作为子节点的父路径
|
||||
const currentParentPath = node.menuType === 'M' ? ownPath : parentPath;
|
||||
// 目录(M)的路径拼接祖先路径后作为子节点的父路径
|
||||
const currentParentPath = node.menuType === 'M' ? (parentPath ? parentPath + '/' + ownPath : ownPath) : parentPath;
|
||||
|
||||
result.push({
|
||||
menuName: prefix + node.menuName,
|
||||
|
||||
@@ -187,6 +187,29 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 已绑定的产需单卡片 -->
|
||||
<div class="detail-card">
|
||||
<div class="detail-card-header">
|
||||
<span>已绑定的产需单({{ boundReqList.length }} 条)</span>
|
||||
</div>
|
||||
<div class="detail-card-body" style="padding:0;" v-loading="boundReqLoading" element-loading-text="查询产需单中...">
|
||||
<el-table v-if="boundReqList.length > 0" :data="boundReqList" border size="small" class="aps-product-table">
|
||||
<el-table-column label="产需单号" prop="scheduleNo" min-width="140" />
|
||||
<el-table-column label="生产日期" prop="prodDate" width="100" align="center" />
|
||||
<el-table-column label="总重量(吨)" prop="totalPlanWeight" width="110" align="right" />
|
||||
<el-table-column label="占用重量(吨)" prop="relWeight" width="110" align="right" />
|
||||
<el-table-column label="状态" width="80" align="center">
|
||||
<template slot-scope="{ row }">
|
||||
<el-tag :type="reqStatusTag(row.scheduleStatus)" size="small">
|
||||
{{ reqStatusLabel(row.scheduleStatus) }}
|
||||
</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
<el-empty v-else description="暂未绑定产需单" :image-size="50" style="padding: 20px 0;" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 订单明细卡片 -->
|
||||
<div class="detail-card">
|
||||
<div class="detail-card-header">
|
||||
@@ -225,6 +248,7 @@
|
||||
|
||||
<script>
|
||||
import { listCrmOrder } from '@/api/aps/order'
|
||||
import { listRel, getRequirement } from '@/api/aps/requirement'
|
||||
import { parseProductContent } from '@/utils/productContent'
|
||||
import { ORDER_STATUS } from '@/views/crm/js/enum'
|
||||
import FileList from '@/components/FileList'
|
||||
@@ -241,6 +265,8 @@ export default {
|
||||
orderList: [],
|
||||
total: 0,
|
||||
currentOrder: null,
|
||||
boundReqList: [],
|
||||
boundReqLoading: false,
|
||||
productList: [],
|
||||
productName: '',
|
||||
totalQuantity: 0,
|
||||
@@ -295,6 +321,7 @@ export default {
|
||||
handleOrderClick(order) {
|
||||
this.currentOrder = order
|
||||
this.parseProductContent(order)
|
||||
this.loadBoundRequirements(order.orderId)
|
||||
},
|
||||
parseProductContent(order) {
|
||||
if (!order || !order.productContent) {
|
||||
@@ -317,6 +344,48 @@ export default {
|
||||
statusLabel(status) {
|
||||
const labels = { 0: '待生产', 1: '生产中', 2: '部分发货', 3: '已发货', 4: '已签收' }
|
||||
return labels[status] || '未知'
|
||||
},
|
||||
/** 加载已绑定的产需单 */
|
||||
loadBoundRequirements(orderId) {
|
||||
this.boundReqList = []
|
||||
this.boundReqLoading = true
|
||||
if (!orderId) { this.boundReqLoading = false; return }
|
||||
listRel({ orderId }).then(relRes => {
|
||||
const rels = (relRes.rows || []).filter(r => r.scheduleId)
|
||||
if (rels.length === 0) { this.boundReqLoading = false; return }
|
||||
const scheduleIds = [...new Set(rels.map(r => r.scheduleId))]
|
||||
Promise.all(scheduleIds.map(id => getRequirement(id).catch(() => null))).then(reqs => {
|
||||
const reqMap = {}
|
||||
reqs.forEach(r => {
|
||||
if (r && r.data) reqMap[r.data.scheduleId] = r.data
|
||||
})
|
||||
this.boundReqList = rels.reduce((arr, rel) => {
|
||||
const req = reqMap[rel.scheduleId]
|
||||
if (req) {
|
||||
arr.push({
|
||||
scheduleNo: req.scheduleNo || '-',
|
||||
prodDate: req.prodDate || '-',
|
||||
scheduleStatus: req.scheduleStatus,
|
||||
totalPlanWeight: req.totalPlanWeight,
|
||||
relWeight: rel.relWeight
|
||||
})
|
||||
}
|
||||
return arr
|
||||
}, [])
|
||||
this.boundReqLoading = false
|
||||
})
|
||||
}).catch(() => {
|
||||
this.boundReqList = []
|
||||
this.boundReqLoading = false
|
||||
})
|
||||
},
|
||||
reqStatusTag(status) {
|
||||
const map = { 0: 'info', 1: 'warning', 2: 'success', 3: 'danger' }
|
||||
return map[status] || 'info'
|
||||
},
|
||||
reqStatusLabel(status) {
|
||||
const labels = { 0: '草稿', 1: '待审核', 2: '已下达', 3: '已退回' }
|
||||
return labels[status] || '未知'
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -132,6 +132,50 @@
|
||||
</div>
|
||||
<div class="detail-card-body" style="padding: 12px;">
|
||||
<div v-loading="productCoilLoading">
|
||||
<el-form :inline="true" size="small" class="coil-filter-bar" style="margin-bottom: 8px;">
|
||||
<el-form-item label="入场卷号">
|
||||
<el-input v-model="productCoilBo.enterCoilNo" placeholder="请输入入场钢卷号" clearable @keyup.enter.native="handleProductFilter" style="width: 160px;" />
|
||||
</el-form-item>
|
||||
<el-form-item label="当前卷号">
|
||||
<el-input v-model="productCoilBo.currentCoilNo" placeholder="请输入当前钢卷号" clearable @keyup.enter.native="handleProductFilter" style="width: 160px;" />
|
||||
</el-form-item>
|
||||
<el-form-item label="逻辑库区">
|
||||
<warehouse-select v-model="productCoilBo.warehouseId" placeholder="请选择逻辑库位" style="width: 160px;" clearable />
|
||||
</el-form-item>
|
||||
<el-form-item label="物料类型">
|
||||
<el-select v-model="productCoilBo.itemType" placeholder="全部" clearable style="width: 100px;" @change="handleProductFilter">
|
||||
<el-option label="成品" value="product" />
|
||||
<el-option label="原料" value="raw_material" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="发货状态">
|
||||
<el-select v-model="productCoilBo.status" placeholder="全部" clearable style="width: 100px;">
|
||||
<el-option label="在库" :value="0" />
|
||||
<el-option label="已发货" :value="1" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="质量状态">
|
||||
<muti-select v-model="productCoilBo.qualityStatusCsv" :options="dict.type.coil_quality_status" placeholder="请选择品质" clearable style="width: 160px;" />
|
||||
</el-form-item>
|
||||
<template v-if="productCoilBo.itemType">
|
||||
<el-form-item label="品名">
|
||||
<muti-select v-model="productCoilBo.itemName" :options="dict.type.coil_itemname" placeholder="请选择物料" clearable style="width: 160px;" />
|
||||
</el-form-item>
|
||||
<el-form-item label="材质">
|
||||
<muti-select v-model="productCoilBo.itemMaterial" :options="dict.type.coil_material" placeholder="请选择材质" clearable style="width: 160px;" />
|
||||
</el-form-item>
|
||||
<el-form-item label="规格">
|
||||
<memo-input v-model="productCoilBo.itemSpecification" storageKey="coilSpec" placeholder="请选择规格" clearable style="width: 160px;" />
|
||||
</el-form-item>
|
||||
<el-form-item label="厂家">
|
||||
<muti-select v-model="productCoilBo.itemManufacturer" :options="dict.type.coil_manufacturer" placeholder="请选择厂家" clearable style="width: 160px;" />
|
||||
</el-form-item>
|
||||
</template>
|
||||
<el-form-item>
|
||||
<el-button type="primary" size="mini" @click="handleProductFilter">查询</el-button>
|
||||
<el-button size="mini" @click="resetProductFilter">重置</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<CoilTable ref="productCoilTable" :data="productCoilList || []" :showSelection="true"
|
||||
:pagination="productPagination" :total-statistics="productTotalStatistics"
|
||||
@selection-change="handleProductSelectionChange" @page-change="handleProductPageChange">
|
||||
@@ -199,11 +243,14 @@ import CoilTable from '@/views/crm/components/CoilTable.vue'
|
||||
import ContractSelect from '@/components/KLPService/ContractSelect'
|
||||
import { batchUpdateContractCoil } from '@/api/wms/coil'
|
||||
import { listProductCoilsByContract, getProductCoilsStatisticsByContract } from '@/api/crm/coil'
|
||||
import WarehouseSelect from '@/components/KLPService/WarehouseSelect'
|
||||
import MemoInput from '@/components/MemoInput'
|
||||
import MutiSelect from '@/components/MutiSelect'
|
||||
|
||||
export default {
|
||||
name: 'ApsOrderTrack',
|
||||
components: { CoilTable, ContractSelect },
|
||||
dicts: ['wip_pack_saleman'],
|
||||
components: { CoilTable, ContractSelect, WarehouseSelect, MemoInput, MutiSelect },
|
||||
dicts: ['wip_pack_saleman', 'coil_itemname', 'coil_material', 'coil_manufacturer', 'coil_quality_status'],
|
||||
data() {
|
||||
return {
|
||||
ORDER_STATUS,
|
||||
@@ -221,6 +268,7 @@ export default {
|
||||
productCoilStatistics: {},
|
||||
productPagination: { total: 0, currentPage: 1, pageSize: 20 },
|
||||
productCoilLoading: false,
|
||||
productCoilBo: {},
|
||||
batchTransferDialogVisible: false,
|
||||
batchTargetContractId: '',
|
||||
batchTransferCoilIds: [],
|
||||
@@ -387,9 +435,11 @@ export default {
|
||||
fetchProductCoils() {
|
||||
if (!this.currentOrder || !this.currentOrder.orderId) return
|
||||
this.productCoilLoading = true
|
||||
listProductCoilsByContract(this.currentOrder.orderId, {
|
||||
listProductCoilsByContract({
|
||||
orderId: this.currentOrder.orderId,
|
||||
pageNum: this.productPagination.currentPage,
|
||||
pageSize: this.productPagination.pageSize
|
||||
pageSize: this.productPagination.pageSize,
|
||||
...this.productCoilBo
|
||||
}).then(res => {
|
||||
this.productCoilList = res.rows || []
|
||||
this.productPagination.total = res.total || 0
|
||||
@@ -399,10 +449,20 @@ export default {
|
||||
},
|
||||
fetchProductCoilStatistics() {
|
||||
if (!this.currentOrder || !this.currentOrder.orderId) return
|
||||
getProductCoilsStatisticsByContract(this.currentOrder.orderId).then(res => {
|
||||
getProductCoilsStatisticsByContract({ ...this.productCoilBo, orderId: this.currentOrder.orderId }).then(res => {
|
||||
this.productCoilStatistics = res.data || {}
|
||||
})
|
||||
},
|
||||
handleProductFilter() {
|
||||
this.productCoilBo.selectType = this.productCoilBo.itemType || ''
|
||||
this.productPagination.currentPage = 1
|
||||
this.fetchProductCoils()
|
||||
this.fetchProductCoilStatistics()
|
||||
},
|
||||
resetProductFilter() {
|
||||
this.productCoilBo = {}
|
||||
this.handleProductFilter()
|
||||
},
|
||||
handleProductSelectionChange(selection) {
|
||||
this.selectedProductRows = selection
|
||||
},
|
||||
|
||||
@@ -26,6 +26,7 @@ import com.klp.domain.vo.WmsDeliveryWaybillDetailVo;
|
||||
import com.klp.domain.bo.WmsDeliveryWaybillDetailBo;
|
||||
import com.klp.service.IWmsDeliveryWaybillDetailService;
|
||||
import com.klp.domain.bo.WmsMaterialCoilBo;
|
||||
import com.klp.domain.bo.PrincipalCoilQueryBo;
|
||||
import com.klp.service.IWmsMaterialCoilService;
|
||||
import com.klp.common.core.page.TableDataInfo;
|
||||
|
||||
@@ -188,34 +189,34 @@ public class WmsDeliveryWaybillDetailController extends BaseController {
|
||||
|
||||
|
||||
/**
|
||||
* 根据负责人(principal)查询已发货绑定的钢卷列表(分页)
|
||||
* 根据负责人(principal)查询已发货绑定的钢卷列表(分页),支持钢卷信息筛选
|
||||
*/
|
||||
@GetMapping("/coilListByPrincipal")
|
||||
public TableDataInfo<WmsMaterialCoilVo> coilListByPrincipal(
|
||||
@RequestParam(required = false) String principal,
|
||||
PageQuery pageQuery) {
|
||||
List<Long> boundCoilIds = iWmsDeliveryWaybillDetailService.getBoundCoilIdsByPrincipal(principal);
|
||||
@PostMapping("/coilListByPrincipal")
|
||||
public TableDataInfo<WmsMaterialCoilVo> coilListByPrincipal(@RequestBody PrincipalCoilQueryBo bo) {
|
||||
List<Long> boundCoilIds = iWmsDeliveryWaybillDetailService.getBoundCoilIdsByPrincipal(bo.getPrincipal());
|
||||
if (boundCoilIds == null || boundCoilIds.isEmpty()) {
|
||||
return new TableDataInfo<>();
|
||||
}
|
||||
WmsMaterialCoilBo wmsMaterialCoilBo = new WmsMaterialCoilBo();
|
||||
wmsMaterialCoilBo.setCoilIds(boundCoilIds.stream().map(String::valueOf).collect(Collectors.joining(",")));
|
||||
return iWmsMaterialCoilService.queryPageList(wmsMaterialCoilBo, pageQuery);
|
||||
WmsMaterialCoilBo coilBo = bo.getCoilBo() != null ? bo.getCoilBo() : new WmsMaterialCoilBo();
|
||||
coilBo.setCoilIds(boundCoilIds.stream().map(String::valueOf).collect(Collectors.joining(",")));
|
||||
PageQuery pageQuery = new PageQuery();
|
||||
pageQuery.setPageNum(bo.getPageNum());
|
||||
pageQuery.setPageSize(bo.getPageSize());
|
||||
return iWmsMaterialCoilService.queryPageList(coilBo, pageQuery);
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据负责人(principal)统计已发货绑定钢卷的汇总数据(数量、毛重、净重)
|
||||
* 根据负责人(principal)统计已发货绑定钢卷的汇总数据,支持钢卷信息筛选
|
||||
*/
|
||||
@GetMapping("/coilListByPrincipal/statistics")
|
||||
public R<java.util.Map<String, java.math.BigDecimal>> getCoilListByPrincipalStatistics(
|
||||
@RequestParam(required = false) String principal) {
|
||||
List<Long> boundCoilIds = iWmsDeliveryWaybillDetailService.getBoundCoilIdsByPrincipal(principal);
|
||||
@PostMapping("/coilListByPrincipal/statistics")
|
||||
public R<java.util.Map<String, java.math.BigDecimal>> getCoilListByPrincipalStatistics(@RequestBody PrincipalCoilQueryBo bo) {
|
||||
List<Long> boundCoilIds = iWmsDeliveryWaybillDetailService.getBoundCoilIdsByPrincipal(bo.getPrincipal());
|
||||
if (boundCoilIds == null || boundCoilIds.isEmpty()) {
|
||||
return R.ok();
|
||||
}
|
||||
WmsMaterialCoilBo wmsMaterialCoilBo = new WmsMaterialCoilBo();
|
||||
wmsMaterialCoilBo.setCoilIds(boundCoilIds.stream().map(String::valueOf).collect(Collectors.joining(",")));
|
||||
return R.ok(iWmsMaterialCoilService.getStatistics(wmsMaterialCoilBo));
|
||||
WmsMaterialCoilBo coilBo = bo.getCoilBo() != null ? bo.getCoilBo() : new WmsMaterialCoilBo();
|
||||
coilBo.setCoilIds(boundCoilIds.stream().map(String::valueOf).collect(Collectors.joining(",")));
|
||||
return R.ok(iWmsMaterialCoilService.getStatistics(coilBo));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,28 @@
|
||||
package com.klp.domain.bo;
|
||||
|
||||
import lombok.Data;
|
||||
import java.io.Serializable;
|
||||
|
||||
/**
|
||||
* 负责人钢卷查询请求对象
|
||||
*
|
||||
* @author klp
|
||||
* @date 2026-07-09
|
||||
*/
|
||||
@Data
|
||||
public class PrincipalCoilQueryBo implements Serializable {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
/** 负责人 */
|
||||
private String principal;
|
||||
|
||||
/** 分页页码 */
|
||||
private Integer pageNum;
|
||||
|
||||
/** 分页大小 */
|
||||
private Integer pageSize;
|
||||
|
||||
/** 钢卷筛选条件 */
|
||||
private WmsMaterialCoilBo coilBo;
|
||||
}
|
||||
Reference in New Issue
Block a user