feat(md): 添加称重盒子维护功能
- 实现称重盒子的增删改查功能
- 添加单个和批量校准功能
- 实现批量配置校准周期功能
- 添加称重盒子复制功能
- 更新路由配置和类型定义
- 添加相关国际化配置
- 实现Java后端时间差计算工具方法
| | |
| | | }; |
| | | } |
| | | |
| | | // è·å两个æ¶é´çå·®å¼ |
| | | public static long getTimeDifference( Date nowDate,Date endDate, TimeUnit unit) { |
| | | // è®¡ç®æ¶é´å·®ï¼åä½ä¸ºæ¯«ç§ï¼åç»å¯¹å¼é¿å
è´æ° |
| | | long diffInMillis = endDate.getTime() - nowDate.getTime(); |
| | | |
| | | // æ ¹æ®ç®æ åä½è½¬æ¢æ¶é´å·® |
| | | return switch (unit) { |
| | | case DAYS -> diffInMillis / TimeUnit.DAYS.toMillis(1); |
| | | case HOURS -> diffInMillis / TimeUnit.HOURS.toMillis(1); |
| | | case MINUTES -> diffInMillis / TimeUnit.MINUTES.toMillis(1); |
| | | case SECONDS -> diffInMillis / TimeUnit.SECONDS.toMillis(1); |
| | | case MILLISECONDS -> diffInMillis; |
| | | case MICROSECONDS -> TimeUnit.MILLISECONDS.toMicros(diffInMillis); |
| | | case NANOSECONDS -> TimeUnit.MILLISECONDS.toNanos(diffInMillis); |
| | | }; |
| | | } |
| | | |
| | | /** |
| | | * 计ç®ä¸¤ä¸ªæ¥æä¹é´çæ¶é´å·®ï¼å¹¶ä»¥å¤©ãå°æ¶ååéçæ ¼å¼è¿å |
| | | * |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | package org.dromara.qa.md.controller; |
| | | |
| | | import java.util.List; |
| | | import java.util.Map; |
| | | |
| | | import lombok.RequiredArgsConstructor; |
| | | import jakarta.validation.constraints.*; |
| | | import cn.dev33.satoken.annotation.SaCheckPermission; |
| | | import org.dromara.qa.md.domain.bo.BatchCalibrateBo; |
| | | import org.dromara.qa.md.domain.bo.BatchConfigBo; |
| | | import org.dromara.qa.md.domain.WeighingBox; |
| | | import org.dromara.qa.md.service.IWeighingBoxService; |
| | | import org.springframework.web.bind.annotation.*; |
| | | import org.springframework.validation.annotation.Validated; |
| | | import org.dromara.common.idempotent.annotation.RepeatSubmit; |
| | | import org.dromara.common.log.annotation.Log; |
| | | import org.dromara.common.web.core.BaseController; |
| | | import org.dromara.common.mybatis.core.page.PageQuery; |
| | | import org.dromara.common.core.domain.R; |
| | | import org.dromara.common.core.validate.AddGroup; |
| | | import org.dromara.common.core.validate.EditGroup; |
| | | import org.dromara.common.log.enums.BusinessType; |
| | | import org.dromara.common.mybatis.core.page.TableDataInfo; |
| | | |
| | | /** |
| | | * ç§°éçåæ§å¶å¨ |
| | | * |
| | | * @author ruoyi |
| | | * @date 2026-04-09 |
| | | */ |
| | | @Validated |
| | | @RequiredArgsConstructor |
| | | @RestController |
| | | @RequestMapping("/md/weighingBox") |
| | | public class WeighingBoxController extends BaseController { |
| | | |
| | | private final IWeighingBoxService weighingBoxService; |
| | | |
| | | /** |
| | | * æ¥è¯¢ç§°éçåå表 |
| | | */ |
| | | @SaCheckPermission("md:weighingBox:list") |
| | | @GetMapping("/list") |
| | | public TableDataInfo<WeighingBox> list(WeighingBox weighingBox, PageQuery pageQuery) { |
| | | return weighingBoxService.queryPageList(weighingBox, pageQuery); |
| | | } |
| | | |
| | | /** |
| | | * è·åç§°éçå详ç»ä¿¡æ¯ |
| | | * |
| | | * @param id ä¸»é® |
| | | */ |
| | | @SaCheckPermission("md:weighingBox:query") |
| | | @GetMapping("/{id}") |
| | | public R<WeighingBox> getInfo(@NotNull(message = "主é®ä¸è½ä¸ºç©º") |
| | | @PathVariable Long id) { |
| | | WeighingBox box = weighingBoxService.getById(id); |
| | | if (box != null) { |
| | | weighingBoxService.calculateCalibStatus(box); |
| | | } |
| | | return R.ok(box); |
| | | } |
| | | |
| | | /** |
| | | * æ°å¢ç§°éçå |
| | | */ |
| | | @SaCheckPermission("md:weighingBox:add") |
| | | @Log(title = "ç§°éçå", businessType = BusinessType.INSERT) |
| | | @RepeatSubmit() |
| | | @PostMapping() |
| | | public R<Void> add(@Validated(AddGroup.class) @RequestBody WeighingBox weighingBox) { |
| | | return toAjax(weighingBoxService.insertWeighingBox(weighingBox)); |
| | | } |
| | | |
| | | /** |
| | | * ä¿®æ¹ç§°éçå |
| | | */ |
| | | @SaCheckPermission("md:weighingBox:edit") |
| | | @Log(title = "ç§°éçå", businessType = BusinessType.UPDATE) |
| | | @RepeatSubmit() |
| | | @PutMapping() |
| | | public R<Void> edit(@Validated(EditGroup.class) @RequestBody WeighingBox weighingBox) { |
| | | return toAjax(weighingBoxService.updateWeighingBox(weighingBox)); |
| | | } |
| | | |
| | | /** |
| | | * å é¤ç§°éçå |
| | | * |
| | | * @param ids 主é®ä¸² |
| | | */ |
| | | @SaCheckPermission("md:weighingBox:remove") |
| | | @Log(title = "ç§°éçå", businessType = BusinessType.DELETE) |
| | | @DeleteMapping("/{ids}") |
| | | public R<Void> remove(@NotEmpty(message = "主é®ä¸è½ä¸ºç©º") |
| | | @PathVariable Long[] ids) { |
| | | return toAjax(weighingBoxService.deleteWeighingBoxByIds(ids)); |
| | | } |
| | | |
| | | /** |
| | | * æ§è¡åä¸ªæ ¡å |
| | | */ |
| | | @SaCheckPermission("md:weighingBox:calibrate") |
| | | @Log(title = "ç§°éçåæ ¡å", businessType = BusinessType.UPDATE) |
| | | @PostMapping("/calibrate") |
| | | public R<Void> calibrate(@RequestBody Map<String, Object> params) { |
| | | try { |
| | | Long boxId = Long.valueOf(params.get("boxId").toString()); |
| | | String calibDate = params.get("calibDate").toString(); |
| | | java.math.BigDecimal actualWeight = params.containsKey("actualWeight") && params.get("actualWeight") != null ? new java.math.BigDecimal(params.get("actualWeight").toString()) : null; |
| | | String note = params.containsKey("note") ? params.get("note").toString() : null; |
| | | |
| | | java.text.SimpleDateFormat sdf = new java.text.SimpleDateFormat("yyyy-MM-dd"); |
| | | java.util.Date date = sdf.parse(calibDate); |
| | | return toAjax(weighingBoxService.calibrate(boxId, date, actualWeight, note)); |
| | | } catch (Exception e) { |
| | | return R.fail("æ¥ææ ¼å¼é误"); |
| | | } |
| | | } |
| | | |
| | | /** |
| | | * æ¹éæ ¡å |
| | | */ |
| | | @SaCheckPermission("md:weighingBox:batchCalibrate") |
| | | @Log(title = "ç§°éçåæ¹éæ ¡å", businessType = BusinessType.UPDATE) |
| | | @PostMapping("/batchCalibrate") |
| | | public R<Map<String, Object>> batchCalibrate(@RequestBody BatchCalibrateBo batchCalibrateDTO) { |
| | | Map<String, Object> result = weighingBoxService.batchCalibrate(batchCalibrateDTO); |
| | | return R.ok(result); |
| | | } |
| | | |
| | | /** |
| | | * ç»ä¸é
ç½®æ ¡å卿 |
| | | */ |
| | | @SaCheckPermission("md:weighingBox:batchConfig") |
| | | @Log(title = "ç§°éçåç»ä¸é
ç½®", businessType = BusinessType.UPDATE) |
| | | @PostMapping("/batchConfig") |
| | | public R<Void> batchConfig(@RequestBody BatchConfigBo batchConfigDTO) { |
| | | return toAjax(weighingBoxService.batchConfig(batchConfigDTO)); |
| | | } |
| | | |
| | | /** |
| | | * æ¹éæ´æ°ç¶æ |
| | | */ |
| | | @SaCheckPermission("md:weighingBox:batchUpdateStatus") |
| | | @Log(title = "ç§°éç忹鿴æ°ç¶æ", businessType = BusinessType.UPDATE) |
| | | @PostMapping("/batchUpdateStatus") |
| | | public R<Void> batchUpdateStatus(@RequestBody Map<String, Object> params) { |
| | | List<Long> boxIds = (List<Long>) params.get("boxIds"); |
| | | Integer activeStatus = (Integer) params.get("activeStatus"); |
| | | return toAjax(weighingBoxService.batchUpdateStatus(boxIds, activeStatus)); |
| | | } |
| | | |
| | | /** |
| | | * å¤å¶çå |
| | | */ |
| | | @Log(title = "ç§°éçåå¤å¶", businessType = BusinessType.INSERT) |
| | | @PostMapping("/copy") |
| | | public R<Map<String, Object>> copy(@RequestBody Map<String, Object> params) { |
| | | try { |
| | | Long sourceId = Long.valueOf(params.get("sourceId").toString()); |
| | | Integer count = Integer.valueOf(params.get("count").toString()); |
| | | Map<String, Object> result = weighingBoxService.copyBox(sourceId, count); |
| | | return R.ok(result); |
| | | } catch (Exception e) { |
| | | return R.fail(e.getMessage()); |
| | | } |
| | | } |
| | | |
| | | /** |
| | | * è·åæ ¡åç¶æç»è®¡ |
| | | */ |
| | | @GetMapping("/statistics") |
| | | public R<Map<String, Integer>> statistics() { |
| | | Map<String, Integer> statistics = weighingBoxService.getStatistics(); |
| | | return R.ok(statistics); |
| | | } |
| | | } |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | package org.dromara.qa.md.domain; |
| | | |
| | | import com.baomidou.mybatisplus.annotation.IdType; |
| | | import com.baomidou.mybatisplus.annotation.TableId; |
| | | import com.baomidou.mybatisplus.annotation.TableName; |
| | | import com.fasterxml.jackson.annotation.JsonFormat; |
| | | import lombok.Data; |
| | | |
| | | import java.io.Serializable; |
| | | import java.math.BigDecimal; |
| | | import java.util.Date; |
| | | |
| | | /** |
| | | * éç¨æ ¡åè®°å½è¡¨ |
| | | * |
| | | * @author ruoyi |
| | | * @date 2026-04-09 |
| | | */ |
| | | @Data |
| | | @TableName("qm_calibration_record") |
| | | public class CalibrationRecord implements Serializable |
| | | { |
| | | private static final long serialVersionUID = 1L; |
| | | |
| | | /** 主é®ID */ |
| | | @TableId(type = IdType.ASSIGN_ID) |
| | | private Long id; |
| | | |
| | | /** æ ¡å对象类å(weighing_box/instrument) */ |
| | | private String targetType; |
| | | |
| | | /** æ ¡å对象ID */ |
| | | private Long targetId; |
| | | |
| | | /** 对象ç¼å·ï¼åä½å¿«ç
§ï¼ */ |
| | | private String targetCode; |
| | | |
| | | /** 对象åç§°ï¼åä½å¿«ç
§ï¼ */ |
| | | private String targetName; |
| | | |
| | | /** æ ¡åæ¥æ */ |
| | | @JsonFormat(pattern = "yyyy-MM-dd") |
| | | private Date calibDate; |
| | | |
| | | /** 彿¶çæ ¡å卿ï¼å¿«ç
§ï¼ */ |
| | | private Integer calibCycleDays; |
| | | |
| | | /** æ åééï¼å¿«ç
§ï¼ */ |
| | | private BigDecimal standardWeight; |
| | | |
| | | /** æ¬æ¬¡å®æµéé */ |
| | | private BigDecimal actualWeight; |
| | | |
| | | /** åå·®å¼ï¼å®æµ-æ åï¼ */ |
| | | private BigDecimal deviation; |
| | | |
| | | /** åå·®ç¾åæ¯(%) */ |
| | | private BigDecimal deviationPct; |
| | | |
| | | /** æ ¡ååç䏿¬¡æ ¡åæ¥æ */ |
| | | @JsonFormat(pattern = "yyyy-MM-dd") |
| | | private Date prevCalibDate; |
| | | |
| | | /** æ ¡åå计ç®ç䏿¬¡æ ¡åæ¥æ */ |
| | | @JsonFormat(pattern = "yyyy-MM-dd") |
| | | private Date nextCalibDate; |
| | | |
| | | /** æ¹æ¬¡å·ï¼æ¹éæ ¡åæ¶å¡«å
ï¼ */ |
| | | private String batchId; |
| | | |
| | | /** æ ¡å夿³¨ */ |
| | | private String note; |
| | | |
| | | /** æä½äºº */ |
| | | private String operator; |
| | | |
| | | /** è®°å½å建æ¶é´ */ |
| | | @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") |
| | | private Date createTime; |
| | | } |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | package org.dromara.qa.md.domain; |
| | | |
| | | import com.baomidou.mybatisplus.annotation.IdType; |
| | | import com.baomidou.mybatisplus.annotation.TableField; |
| | | import com.baomidou.mybatisplus.annotation.TableId; |
| | | import com.baomidou.mybatisplus.annotation.TableName; |
| | | import com.fasterxml.jackson.annotation.JsonFormat; |
| | | import lombok.Data; |
| | | |
| | | import java.io.Serializable; |
| | | import java.math.BigDecimal; |
| | | import java.util.Date; |
| | | |
| | | /** |
| | | * ç§°éçå主表 |
| | | * |
| | | * @author ruoyi |
| | | * @date 2026-04-09 |
| | | */ |
| | | @Data |
| | | @TableName("qm_weighing_box") |
| | | public class WeighingBox implements Serializable |
| | | { |
| | | private static final long serialVersionUID = 1L; |
| | | |
| | | /** 主é®ID */ |
| | | @TableId(type = IdType.ASSIGN_ID) |
| | | private Long id; |
| | | |
| | | /** çååç§° */ |
| | | private String name; |
| | | |
| | | /** çåç¼å·ï¼å
¨å±å¯ä¸ï¼ */ |
| | | private String code; |
| | | |
| | | /** æ åéé */ |
| | | private BigDecimal weight; |
| | | |
| | | /** ééåä½(g/kg/mg) */ |
| | | private String unit; |
| | | |
| | | /** åæ¾ä½ç½® */ |
| | | private String location; |
| | | |
| | | /** æ ¡å卿ï¼å¤©ï¼ */ |
| | | private Integer calibCycleDays; |
| | | |
| | | /** æåæéå¤©æ° */ |
| | | private Integer remindDays; |
| | | |
| | | /** 䏿¬¡æ ¡åæ¥æ */ |
| | | @JsonFormat(pattern = "yyyy-MM-dd") |
| | | private Date lastCalibDate; |
| | | |
| | | /** 䏿¬¡æ ¡åæ¥æï¼ç³»ç»èªå¨è®¡ç®ï¼ */ |
| | | @JsonFormat(pattern = "yyyy-MM-dd") |
| | | private Date nextCalibDate; |
| | | |
| | | /** å¯ç¨ç¶æ(1å¯ç¨ 0åç¨) */ |
| | | private Integer activeStatus; |
| | | |
| | | /** 夿³¨æè¿° */ |
| | | private String description; |
| | | |
| | | /** å 餿 è®°(0æ£å¸¸ 1å é¤) */ |
| | | private Integer delFlag; |
| | | |
| | | /** å建人 */ |
| | | private String createBy; |
| | | |
| | | /** å建æ¶é´ */ |
| | | @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") |
| | | private Date createTime; |
| | | |
| | | /** æ´æ°äºº */ |
| | | private String updateBy; |
| | | |
| | | /** æ´æ°æ¶é´ */ |
| | | @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") |
| | | private Date updateTime; |
| | | |
| | | // æ©å±å段 |
| | | // æé¤æ |
| | | @TableField(exist = false) |
| | | private String calibStatus; |
| | | @TableField(exist = false) |
| | | private Integer calibDaysLeft; |
| | | } |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | package org.dromara.qa.md.domain.bo; |
| | | |
| | | import lombok.Data; |
| | | |
| | | import java.math.BigDecimal; |
| | | import java.util.Date; |
| | | import java.util.List; |
| | | |
| | | /** |
| | | * æ¹éæ ¡åDTO |
| | | * |
| | | * @author ruoyi |
| | | * @date 2026-04-09 |
| | | */ |
| | | @Data |
| | | public class BatchCalibrateBo |
| | | { |
| | | private List<Long> boxIds; |
| | | private Date calibDate; |
| | | private String note; |
| | | private List<CalibrateItem> items; |
| | | |
| | | @Data |
| | | public static class CalibrateItem |
| | | { |
| | | private Long boxId; |
| | | private BigDecimal actualWeight; |
| | | } |
| | | } |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | package org.dromara.qa.md.domain.bo; |
| | | |
| | | import lombok.Data; |
| | | |
| | | import java.util.List; |
| | | |
| | | /** |
| | | * æ¹éé
ç½®DTO |
| | | * |
| | | * @author ruoyi |
| | | * @date 2026-04-09 |
| | | */ |
| | | @Data |
| | | public class BatchConfigBo |
| | | { |
| | | private Integer calibCycleDays; |
| | | private Integer remindDays; |
| | | private String applyScope; |
| | | private List<Long> boxIds; |
| | | } |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | package org.dromara.qa.md.domain.bo; |
| | | |
| | | import lombok.Data; |
| | | |
| | | import java.math.BigDecimal; |
| | | import java.util.Date; |
| | | |
| | | /** |
| | | * ç§°éçåDTO |
| | | * |
| | | * @author ruoyi |
| | | * @date 2026-04-09 |
| | | */ |
| | | @Data |
| | | public class WeighingBoxBo |
| | | { |
| | | private Long id; |
| | | private String name; |
| | | private String code; |
| | | private BigDecimal weight; |
| | | private String unit; |
| | | private String location; |
| | | private Integer calibCycleDays; |
| | | private Integer remindDays; |
| | | private Date lastCalibDate; |
| | | private Integer activeStatus; |
| | | private String description; |
| | | } |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | package org.dromara.qa.md.mapper; |
| | | |
| | | import com.baomidou.mybatisplus.core.mapper.BaseMapper; |
| | | import org.dromara.qa.md.domain.CalibrationRecord; |
| | | |
| | | import java.util.List; |
| | | |
| | | /** |
| | | * æ ¡åè®°å½Mapperæ¥å£ |
| | | * |
| | | * @author ruoyi |
| | | * @date 2026-04-09 |
| | | */ |
| | | public interface CalibrationRecordMapper extends BaseMapper<CalibrationRecord> |
| | | { |
| | | /** |
| | | * æ¥è¯¢æ ¡åè®°å½å表 |
| | | * |
| | | * @param record æ ¡åè®°å½ |
| | | * @return æ ¡åè®°å½éå |
| | | */ |
| | | public List<CalibrationRecord> selectCalibrationRecordList(CalibrationRecord record); |
| | | |
| | | /** |
| | | * æ ¹æ®ç®æ IDæ¥è¯¢æ ¡åè®°å½ |
| | | * |
| | | * @param targetType ç®æ ç±»å |
| | | * @param targetId ç®æ ID |
| | | * @return æ ¡åè®°å½éå |
| | | */ |
| | | public List<CalibrationRecord> selectByTargetId(String targetType, Long targetId); |
| | | |
| | | /** |
| | | * æ¹éæå
¥æ ¡åè®°å½ |
| | | * |
| | | * @param records æ ¡åè®°å½å表 |
| | | * @return ç»æ |
| | | */ |
| | | public int batchInsert(List<CalibrationRecord> records); |
| | | } |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | package org.dromara.qa.md.mapper; |
| | | |
| | | import com.baomidou.mybatisplus.core.mapper.BaseMapper; |
| | | import org.dromara.qa.md.domain.WeighingBox; |
| | | import org.dromara.common.mybatis.core.page.TableDataInfo; |
| | | import org.dromara.common.mybatis.core.page.PageQuery; |
| | | |
| | | import java.util.List; |
| | | |
| | | /** |
| | | * ç§°éçåMapperæ¥å£ |
| | | * |
| | | * @author ruoyi |
| | | * @date 2026-04-09 |
| | | */ |
| | | public interface WeighingBoxMapper extends BaseMapper<WeighingBox> |
| | | { |
| | | /** |
| | | * å页æ¥è¯¢ç§°éçåå表 |
| | | * |
| | | * @param weighingBox æ¥è¯¢æ¡ä»¶ |
| | | * @param pageQuery å页忰 |
| | | * @return ç§°éçåå页å表 |
| | | */ |
| | | public TableDataInfo<WeighingBox> selectPageList(WeighingBox weighingBox, PageQuery pageQuery); |
| | | |
| | | /** |
| | | * æ¥è¯¢ç§°éçåå表 |
| | | * |
| | | * @param weighingBox ç§°éçå |
| | | * @return ç§°éçåéå |
| | | */ |
| | | public List<WeighingBox> selectWeighingBoxList(WeighingBox weighingBox); |
| | | |
| | | /** |
| | | * æ¥è¯¢å·²å¯ç¨çç§°éçåï¼ç¨äºæéä»»å¡ï¼ |
| | | * |
| | | * @return ç§°éçåéå |
| | | */ |
| | | public List<WeighingBox> selectActiveWeighingBoxes(); |
| | | |
| | | /** |
| | | * æ¹éæ´æ°ç§°éçåç¶æ |
| | | * |
| | | * @param boxIds çåIDå表 |
| | | * @param activeStatus ç¶æ |
| | | * @return ç»æ |
| | | */ |
| | | public int batchUpdateStatus(List<Long> boxIds, Integer activeStatus); |
| | | |
| | | /** |
| | | * æ¹éæ´æ°æ ¡å卿 |
| | | * |
| | | * @param boxIds çåIDå表 |
| | | * @param calibCycleDays æ ¡å卿 |
| | | * @param remindDays æéå¤©æ° |
| | | * @return ç»æ |
| | | */ |
| | | public int batchUpdateCalibConfig(List<Long> boxIds, Integer calibCycleDays, Integer remindDays); |
| | | |
| | | /** |
| | | * æ¥è¯¢ç§°éçåå表ï¼å¸¦æ ¡åç¶æè¿æ»¤ï¼ |
| | | * |
| | | * @param weighingBox ç§°éçå |
| | | * @return ç§°éçåéå |
| | | */ |
| | | public List<WeighingBox> selectWeighingBoxListWithCalibStatus(WeighingBox weighingBox); |
| | | |
| | | /** |
| | | * æ¥è¯¢ç§°éçåæ»æ°ï¼å¸¦æ ¡åç¶æè¿æ»¤ï¼ |
| | | * |
| | | * @param weighingBox ç§°éçå |
| | | * @return ç§°éçåæ»æ° |
| | | */ |
| | | public long selectWeighingBoxCountWithCalibStatus(WeighingBox weighingBox); |
| | | } |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | package org.dromara.qa.md.service; |
| | | |
| | | import com.baomidou.mybatisplus.extension.service.IService; |
| | | import org.dromara.qa.md.domain.CalibrationRecord; |
| | | |
| | | import java.util.List; |
| | | |
| | | /** |
| | | * æ ¡åè®°å½æå¡æ¥å£ |
| | | * |
| | | * @author ruoyi |
| | | * @date 2026-04-09 |
| | | */ |
| | | public interface ICalibrationRecordService extends IService<CalibrationRecord> |
| | | { |
| | | /** |
| | | * æ¥è¯¢æ ¡åè®°å½å表 |
| | | * |
| | | * @param calibrationRecord æ ¡åè®°å½ |
| | | * @return æ ¡åè®°å½éå |
| | | */ |
| | | public List<CalibrationRecord> selectCalibrationRecordList(CalibrationRecord calibrationRecord); |
| | | |
| | | /** |
| | | * æ ¹æ®ç®æ IDæ¥è¯¢æ ¡åè®°å½ |
| | | * |
| | | * @param targetType ç®æ ç±»å |
| | | * @param targetId ç®æ ID |
| | | * @return æ ¡åè®°å½éå |
| | | */ |
| | | public List<CalibrationRecord> selectByTargetId(String targetType, Long targetId); |
| | | |
| | | /** |
| | | * æ¹éæå
¥æ ¡åè®°å½ |
| | | * |
| | | * @param records æ ¡åè®°å½å表 |
| | | * @return ç»æ |
| | | */ |
| | | public int batchInsert(List<CalibrationRecord> records); |
| | | } |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | package org.dromara.qa.md.service; |
| | | |
| | | import com.baomidou.mybatisplus.extension.service.IService; |
| | | import org.dromara.qa.md.domain.bo.BatchCalibrateBo; |
| | | import org.dromara.qa.md.domain.bo.BatchConfigBo; |
| | | import org.dromara.qa.md.domain.WeighingBox; |
| | | import org.dromara.common.mybatis.core.page.TableDataInfo; |
| | | import org.dromara.common.mybatis.core.page.PageQuery; |
| | | |
| | | import java.util.List; |
| | | import java.util.Map; |
| | | |
| | | /** |
| | | * ç§°éçåæå¡æ¥å£ |
| | | * |
| | | * @author ruoyi |
| | | * @date 2026-04-09 |
| | | */ |
| | | public interface IWeighingBoxService extends IService<WeighingBox> |
| | | { |
| | | /** |
| | | * å页æ¥è¯¢ç§°éçåå表 |
| | | * |
| | | * @param weighingBox æ¥è¯¢æ¡ä»¶ |
| | | * @param pageQuery å页忰 |
| | | * @return ç§°éçåå页å表 |
| | | */ |
| | | public TableDataInfo<WeighingBox> queryPageList(WeighingBox weighingBox, PageQuery pageQuery); |
| | | |
| | | /** |
| | | * æ¥è¯¢ç§°éçåå表 |
| | | * |
| | | * @param weighingBox ç§°éçå |
| | | * @return ç§°éçåéå |
| | | */ |
| | | public List<WeighingBox> selectWeighingBoxList(WeighingBox weighingBox); |
| | | |
| | | /** |
| | | * æ°å¢ç§°éçå |
| | | * |
| | | * @param weighingBox ç§°éçå |
| | | * @return ç»æ |
| | | */ |
| | | public int insertWeighingBox(WeighingBox weighingBox); |
| | | |
| | | /** |
| | | * ä¿®æ¹ç§°éçå |
| | | * |
| | | * @param weighingBox ç§°éçå |
| | | * @return ç»æ |
| | | */ |
| | | public int updateWeighingBox(WeighingBox weighingBox); |
| | | |
| | | /** |
| | | * æ¹éå é¤ç§°éçå |
| | | * |
| | | * @param boxIds éè¦å é¤çç§°éçåID |
| | | * @return ç»æ |
| | | */ |
| | | public int deleteWeighingBoxByIds(Long[] boxIds); |
| | | |
| | | /** |
| | | * æ§è¡åä¸ªæ ¡å |
| | | * |
| | | * @param boxId çåID |
| | | * @param calibDate æ ¡åæ¥æ |
| | | * @param actualWeight å®é
éé |
| | | * @param note 夿³¨ |
| | | * @return ç»æ |
| | | */ |
| | | public int calibrate(Long boxId, java.util.Date calibDate, java.math.BigDecimal actualWeight, String note); |
| | | |
| | | /** |
| | | * æ¹éæ ¡å |
| | | * |
| | | * @param batchCalibrateDTO æ¹éæ ¡ååæ° |
| | | * @return ç»æ |
| | | */ |
| | | public Map<String, Object> batchCalibrate(BatchCalibrateBo batchCalibrateDTO); |
| | | |
| | | /** |
| | | * ç»ä¸é
ç½®æ ¡å卿 |
| | | * |
| | | * @param batchConfigDTO æ¹éé
ç½®åæ° |
| | | * @return ç»æ |
| | | */ |
| | | public int batchConfig(BatchConfigBo batchConfigDTO); |
| | | |
| | | /** |
| | | * æ¹éæ´æ°ç¶æ |
| | | * |
| | | * @param boxIds çåIDå表 |
| | | * @param activeStatus ç¶æ |
| | | * @return ç»æ |
| | | */ |
| | | public int batchUpdateStatus(List<Long> boxIds, Integer activeStatus); |
| | | |
| | | /** |
| | | * å¤å¶çå |
| | | * |
| | | * @param sourceId æºçåID |
| | | * @param count å¤å¶æ°é |
| | | * @return ç»æ |
| | | */ |
| | | public Map<String, Object> copyBox(Long sourceId, Integer count); |
| | | |
| | | /** |
| | | * è·åæ ¡åç¶æç»è®¡ |
| | | * |
| | | * @return ç»è®¡ç»æ |
| | | */ |
| | | public Map<String, Integer> getStatistics(); |
| | | |
| | | /** |
| | | * è®¡ç®æ ¡åç¶æ |
| | | * |
| | | * @param weighingBox ç§°éçå |
| | | * @return ç§°éçåï¼å«ç¶æä¿¡æ¯ï¼ |
| | | */ |
| | | public WeighingBox calculateCalibStatus(WeighingBox weighingBox); |
| | | } |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | package org.dromara.qa.md.service.impl; |
| | | |
| | | import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; |
| | | import org.dromara.qa.md.domain.CalibrationRecord; |
| | | import org.dromara.qa.md.mapper.CalibrationRecordMapper; |
| | | import org.dromara.qa.md.service.ICalibrationRecordService; |
| | | import org.springframework.stereotype.Service; |
| | | |
| | | import java.util.List; |
| | | |
| | | /** |
| | | * æ ¡åè®°å½æå¡å®ç° |
| | | * |
| | | * @author ruoyi |
| | | * @date 2026-04-09 |
| | | */ |
| | | @Service |
| | | public class CalibrationRecordServiceImpl extends ServiceImpl<CalibrationRecordMapper, CalibrationRecord> implements ICalibrationRecordService |
| | | { |
| | | @Override |
| | | public List<CalibrationRecord> selectCalibrationRecordList(CalibrationRecord calibrationRecord) |
| | | { |
| | | return baseMapper.selectCalibrationRecordList(calibrationRecord); |
| | | } |
| | | |
| | | @Override |
| | | public List<CalibrationRecord> selectByTargetId(String targetType, Long targetId) |
| | | { |
| | | CalibrationRecord record = new CalibrationRecord(); |
| | | record.setTargetType(targetType); |
| | | record.setTargetId(targetId); |
| | | return baseMapper.selectCalibrationRecordList(record); |
| | | } |
| | | |
| | | @Override |
| | | public int batchInsert(List<CalibrationRecord> records) |
| | | { |
| | | return baseMapper.batchInsert(records); |
| | | } |
| | | } |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | package org.dromara.qa.md.service.impl; |
| | | |
| | | import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; |
| | | import com.baomidou.mybatisplus.extension.plugins.pagination.Page; |
| | | import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; |
| | | |
| | | import org.dromara.common.core.utils.DateUtils; |
| | | import org.dromara.common.mybatis.core.page.PageQuery; |
| | | import org.dromara.common.mybatis.core.page.TableDataInfo; |
| | | import org.dromara.qa.md.domain.bo.BatchCalibrateBo; |
| | | import org.dromara.qa.md.domain.bo.BatchConfigBo; |
| | | import org.dromara.qa.md.domain.CalibrationRecord; |
| | | import org.dromara.qa.md.domain.WeighingBox; |
| | | import org.dromara.qa.md.mapper.WeighingBoxMapper; |
| | | import org.dromara.qa.md.service.ICalibrationRecordService; |
| | | import org.dromara.qa.md.service.IWeighingBoxService; |
| | | import org.dromara.qa.md.service.INotificationService; |
| | | import org.springframework.beans.factory.annotation.Autowired; |
| | | import org.springframework.stereotype.Service; |
| | | import org.springframework.transaction.annotation.Transactional; |
| | | |
| | | import java.math.BigDecimal; |
| | | import java.util.*; |
| | | import java.util.concurrent.TimeUnit; |
| | | |
| | | /** |
| | | * ç§°éçåæå¡å®ç° |
| | | * |
| | | * @author ruoyi |
| | | * @date 2026-04-09 |
| | | */ |
| | | @Service |
| | | public class WeighingBoxServiceImpl extends ServiceImpl<WeighingBoxMapper, WeighingBox> implements IWeighingBoxService |
| | | { |
| | | @Autowired |
| | | private WeighingBoxMapper weighingBoxMapper; |
| | | |
| | | @Autowired |
| | | private ICalibrationRecordService calibrationRecordService; |
| | | |
| | | @Autowired |
| | | private INotificationService notificationService; |
| | | |
| | | @Override |
| | | public TableDataInfo<WeighingBox> queryPageList(WeighingBox weighingBox, PageQuery pageQuery) { |
| | | // æ£æ¥æ¯å¦éè¦ææ ¡åç¶æè¿æ»¤ |
| | | if (weighingBox.getCalibStatus() != null && !weighingBox.getCalibStatus().isEmpty()) { |
| | | // 使ç¨å¸¦æ ¡åç¶æè¿æ»¤çæ¥è¯¢ |
| | | long total = weighingBoxMapper.selectWeighingBoxCountWithCalibStatus(weighingBox); |
| | | List<WeighingBox> records = weighingBoxMapper.selectWeighingBoxListWithCalibStatus(weighingBox); |
| | | |
| | | // æå¨å页 |
| | | int pageNum = pageQuery.getPageNum(); |
| | | int pageSize = pageQuery.getPageSize(); |
| | | int start = (pageNum - 1) * pageSize; |
| | | int end = Math.min(start + pageSize, records.size()); |
| | | List<WeighingBox> pageRecords = start < records.size() ? records.subList(start, end) : new ArrayList<>(); |
| | | |
| | | // è®¡ç®æ¯ä¸ªçåçæ ¡åç¶æï¼ä¿æä¸å端ä¸è´ï¼ |
| | | for (WeighingBox box : pageRecords) { |
| | | calculateCalibStatus(box); |
| | | } |
| | | |
| | | // æå»ºè¿åç»æ |
| | | TableDataInfo<WeighingBox> tableDataInfo = new TableDataInfo<>(); |
| | | tableDataInfo.setRows(pageRecords); |
| | | tableDataInfo.setTotal(total); |
| | | tableDataInfo.setCode(200); |
| | | tableDataInfo.setMsg("æ¥è¯¢æå"); |
| | | return tableDataInfo; |
| | | } else { |
| | | // 使ç¨å¸¸è§å页æ¥è¯¢ |
| | | Page<WeighingBox> page = new Page<>(pageQuery.getPageNum(), pageQuery.getPageSize()); |
| | | |
| | | // æå»ºæ¥è¯¢æ¡ä»¶ |
| | | LambdaQueryWrapper<WeighingBox> queryWrapper = new com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper<>(); |
| | | queryWrapper.eq(WeighingBox::getDelFlag, 0); |
| | | |
| | | if (weighingBox.getName() != null && !weighingBox.getName().isEmpty()) { |
| | | queryWrapper.like(WeighingBox::getName, weighingBox.getName()) |
| | | .or().like(WeighingBox::getCode, weighingBox.getName()) |
| | | .or().like(WeighingBox::getDescription, weighingBox.getName()) |
| | | .or().like(WeighingBox::getLocation, weighingBox.getName()); |
| | | } |
| | | if (weighingBox.getActiveStatus() != null) { |
| | | queryWrapper.eq(WeighingBox::getActiveStatus, weighingBox.getActiveStatus()); |
| | | } |
| | | |
| | | queryWrapper.orderByDesc(WeighingBox::getCreateTime); |
| | | |
| | | // æ§è¡å页æ¥è¯¢ |
| | | Page<WeighingBox> resultPage = baseMapper.selectPage(page, queryWrapper); |
| | | |
| | | // è®¡ç®æ¯ä¸ªçåçæ ¡åç¶æ |
| | | for (WeighingBox box : resultPage.getRecords()) { |
| | | calculateCalibStatus(box); |
| | | } |
| | | |
| | | return TableDataInfo.build(resultPage); |
| | | } |
| | | } |
| | | |
| | | @Override |
| | | public List<WeighingBox> selectWeighingBoxList(WeighingBox weighingBox) |
| | | { |
| | | List<WeighingBox> list = weighingBoxMapper.selectWeighingBoxList(weighingBox); |
| | | for (WeighingBox box : list) |
| | | { |
| | | calculateCalibStatus(box); |
| | | } |
| | | return list; |
| | | } |
| | | |
| | | @Override |
| | | public int insertWeighingBox(WeighingBox weighingBox) |
| | | { |
| | | // 计ç®ä¸æ¬¡æ ¡åæ¥æ |
| | | if (weighingBox.getLastCalibDate() != null && weighingBox.getCalibCycleDays() != null) |
| | | { |
| | | Date nextCalibDate = DateUtils.addDays(weighingBox.getLastCalibDate(), weighingBox.getCalibCycleDays()); |
| | | weighingBox.setNextCalibDate(nextCalibDate); |
| | | } |
| | | return baseMapper.insert(weighingBox); |
| | | } |
| | | |
| | | @Override |
| | | public int updateWeighingBox(WeighingBox weighingBox) |
| | | { |
| | | // 计ç®ä¸æ¬¡æ ¡åæ¥æ |
| | | if (weighingBox.getLastCalibDate() != null && weighingBox.getCalibCycleDays() != null) |
| | | { |
| | | Date nextCalibDate = DateUtils.addDays(weighingBox.getLastCalibDate(), weighingBox.getCalibCycleDays()); |
| | | weighingBox.setNextCalibDate(nextCalibDate); |
| | | } |
| | | return baseMapper.updateById(weighingBox); |
| | | } |
| | | |
| | | @Override |
| | | public int deleteWeighingBoxByIds(Long[] boxIds) |
| | | { |
| | | return baseMapper.deleteBatchIds(Arrays.asList(boxIds)); |
| | | } |
| | | |
| | | @Override |
| | | @Transactional |
| | | public int calibrate(Long boxId, Date calibDate, BigDecimal actualWeight, String note) |
| | | { |
| | | WeighingBox box = baseMapper.selectById(boxId); |
| | | if (box == null) |
| | | { |
| | | return 0; |
| | | } |
| | | |
| | | // ä¿åæ ¡ååçç¶æ |
| | | Date prevCalibDate = box.getLastCalibDate(); |
| | | |
| | | // æ´æ°çåä¿¡æ¯ |
| | | box.setLastCalibDate(calibDate); |
| | | box.setNextCalibDate(DateUtils.addDays(calibDate, box.getCalibCycleDays())); |
| | | baseMapper.updateById(box); |
| | | |
| | | // 计ç®åå·® |
| | | BigDecimal deviation = null; |
| | | BigDecimal deviationPct = null; |
| | | if (actualWeight != null) |
| | | { |
| | | deviation = actualWeight.subtract(box.getWeight()); |
| | | if (box.getWeight().compareTo(BigDecimal.ZERO) > 0) |
| | | { |
| | | deviationPct = deviation.divide(box.getWeight(), 3, BigDecimal.ROUND_HALF_UP).multiply(new BigDecimal(100)); |
| | | } |
| | | } |
| | | |
| | | // åå»ºæ ¡åè®°å½ |
| | | CalibrationRecord record = new CalibrationRecord(); |
| | | record.setTargetType("weighing_box"); |
| | | record.setTargetId(boxId); |
| | | record.setTargetCode(box.getCode()); |
| | | record.setTargetName(box.getName()); |
| | | record.setCalibDate(calibDate); |
| | | record.setCalibCycleDays(box.getCalibCycleDays()); |
| | | record.setStandardWeight(box.getWeight()); |
| | | record.setActualWeight(actualWeight); |
| | | record.setDeviation(deviation); |
| | | record.setDeviationPct(deviationPct); |
| | | record.setPrevCalibDate(prevCalibDate); |
| | | record.setNextCalibDate(box.getNextCalibDate()); |
| | | record.setOperator("admin"); // å®é
åºè¯¥ä»å½åç¨æ·è·å |
| | | record.setNote(note); |
| | | record.setCreateTime(new Date()); |
| | | calibrationRecordService.save(record); |
| | | |
| | | return 1; |
| | | } |
| | | |
| | | @Override |
| | | @Transactional |
| | | public Map<String, Object> batchCalibrate(BatchCalibrateBo batchCalibrateDTO) |
| | | { |
| | | List<Long> boxIds = batchCalibrateDTO.getBoxIds(); |
| | | Date calibDate = batchCalibrateDTO.getCalibDate(); |
| | | String note = batchCalibrateDTO.getNote(); |
| | | List<BatchCalibrateBo.CalibrateItem> items = batchCalibrateDTO.getItems(); |
| | | |
| | | Map<Long, BigDecimal> actualWeightMap = new HashMap<>(); |
| | | if (items != null) |
| | | { |
| | | for (BatchCalibrateBo.CalibrateItem item : items) |
| | | { |
| | | actualWeightMap.put(item.getBoxId(), item.getActualWeight()); |
| | | } |
| | | } |
| | | |
| | | String batchId = UUID.randomUUID().toString(); |
| | | List<CalibrationRecord> records = new ArrayList<>(); |
| | | List<Map<String, Object>> results = new ArrayList<>(); |
| | | |
| | | for (Long boxId : boxIds) |
| | | { |
| | | WeighingBox box = baseMapper.selectById(boxId); |
| | | if (box == null) |
| | | { |
| | | continue; |
| | | } |
| | | |
| | | // ä¿åæ ¡ååçç¶æ |
| | | Date prevCalibDate = box.getLastCalibDate(); |
| | | |
| | | // æ´æ°çåä¿¡æ¯ |
| | | box.setLastCalibDate(calibDate); |
| | | box.setNextCalibDate(DateUtils.addDays(calibDate, box.getCalibCycleDays())); |
| | | baseMapper.updateById(box); |
| | | |
| | | // 计ç®åå·® |
| | | BigDecimal actualWeight = actualWeightMap.get(boxId); |
| | | BigDecimal deviation = null; |
| | | BigDecimal deviationPct = null; |
| | | if (actualWeight != null) |
| | | { |
| | | deviation = actualWeight.subtract(box.getWeight()); |
| | | if (box.getWeight().compareTo(BigDecimal.ZERO) > 0) |
| | | { |
| | | deviationPct = deviation.divide(box.getWeight(), 3, BigDecimal.ROUND_HALF_UP).multiply(new BigDecimal(100)); |
| | | } |
| | | } |
| | | |
| | | // åå»ºæ ¡åè®°å½ |
| | | CalibrationRecord record = new CalibrationRecord(); |
| | | record.setTargetType("weighing_box"); |
| | | record.setTargetId(boxId); |
| | | record.setTargetCode(box.getCode()); |
| | | record.setTargetName(box.getName()); |
| | | record.setCalibDate(calibDate); |
| | | record.setCalibCycleDays(box.getCalibCycleDays()); |
| | | record.setStandardWeight(box.getWeight()); |
| | | record.setActualWeight(actualWeight); |
| | | record.setDeviation(deviation); |
| | | record.setDeviationPct(deviationPct); |
| | | record.setPrevCalibDate(prevCalibDate); |
| | | record.setNextCalibDate(box.getNextCalibDate()); |
| | | record.setBatchId(batchId); |
| | | record.setOperator("admin"); // å®é
åºè¯¥ä»å½åç¨æ·è·å |
| | | record.setNote(note); |
| | | record.setCreateTime(new Date()); |
| | | records.add(record); |
| | | |
| | | // åå¤è¿åç»æ |
| | | Map<String, Object> result = new HashMap<>(); |
| | | result.put("boxId", boxId); |
| | | result.put("nextCalibDate", box.getNextCalibDate()); |
| | | results.add(result); |
| | | } |
| | | |
| | | // æ¹éæå
¥æ ¡åè®°å½ |
| | | if (!records.isEmpty()) |
| | | { |
| | | calibrationRecordService.batchInsert(records); |
| | | } |
| | | |
| | | Map<String, Object> resultMap = new HashMap<>(); |
| | | resultMap.put("successCount", boxIds.size()); |
| | | resultMap.put("failCount", 0); |
| | | resultMap.put("results", results); |
| | | return resultMap; |
| | | } |
| | | |
| | | @Override |
| | | @Transactional |
| | | public int batchConfig(BatchConfigBo batchConfigDTO) |
| | | { |
| | | Integer calibCycleDays = batchConfigDTO.getCalibCycleDays(); |
| | | Integer remindDays = batchConfigDTO.getRemindDays(); |
| | | String applyScope = batchConfigDTO.getApplyScope(); |
| | | List<Long> boxIds = batchConfigDTO.getBoxIds(); |
| | | |
| | | List<Long> targetBoxIds = new ArrayList<>(); |
| | | if ("all".equals(applyScope)) |
| | | { |
| | | // ææå·²å¯ç¨ççå |
| | | List<WeighingBox> boxes = weighingBoxMapper.selectActiveWeighingBoxes(); |
| | | for (WeighingBox box : boxes) |
| | | { |
| | | targetBoxIds.add(box.getId()); |
| | | } |
| | | } |
| | | else if ("selected".equals(applyScope) && boxIds != null) |
| | | { |
| | | targetBoxIds.addAll(boxIds); |
| | | } |
| | | |
| | | if (targetBoxIds.isEmpty()) |
| | | { |
| | | return 0; |
| | | } |
| | | |
| | | // æ¹éæ´æ°æ ¡åé
ç½® |
| | | int result = weighingBoxMapper.batchUpdateCalibConfig(targetBoxIds, calibCycleDays, remindDays); |
| | | |
| | | // éæ°è®¡ç®ä¸æ¬¡æ ¡åæ¥æ |
| | | for (Long boxId : targetBoxIds) |
| | | { |
| | | WeighingBox box = baseMapper.selectById(boxId); |
| | | if (box.getLastCalibDate() != null) |
| | | { |
| | | box.setNextCalibDate(DateUtils.addDays(box.getLastCalibDate(), calibCycleDays)); |
| | | baseMapper.updateById(box); |
| | | } |
| | | } |
| | | |
| | | return result; |
| | | } |
| | | |
| | | @Override |
| | | public int batchUpdateStatus(List<Long> boxIds, Integer activeStatus) |
| | | { |
| | | return weighingBoxMapper.batchUpdateStatus(boxIds, activeStatus); |
| | | } |
| | | |
| | | @Override |
| | | public Map<String, Object> copyBox(Long sourceId, Integer count) |
| | | { |
| | | WeighingBox sourceBox = baseMapper.selectById(sourceId); |
| | | if (sourceBox == null) |
| | | { |
| | | throw new RuntimeException("æºçåä¸åå¨"); |
| | | } |
| | | Date nextCalibDate = null; |
| | | List<Long> newIds = new ArrayList<>(); |
| | | List<String> newCodes = new ArrayList<>(); |
| | | // 计ç®ä¸æ¬¡æ ¡åæ¥æ |
| | | if ( sourceBox.getCalibCycleDays() != null) |
| | | { |
| | | nextCalibDate = DateUtils.addDays(new Date(), sourceBox.getCalibCycleDays()); |
| | | } |
| | | for (int i = 1; i <= count; i++) |
| | | { |
| | | WeighingBox newBox = new WeighingBox(); |
| | | newBox.setName(sourceBox.getName() + "-" + (char)('a' + i - 1)); |
| | | newBox.setCode(sourceBox.getCode() + "-" + (char)('a' + i - 1)); |
| | | newBox.setWeight(sourceBox.getWeight()); |
| | | newBox.setUnit(sourceBox.getUnit()); |
| | | newBox.setLocation(sourceBox.getLocation()); |
| | | newBox.setCalibCycleDays(sourceBox.getCalibCycleDays()); |
| | | newBox.setRemindDays(sourceBox.getRemindDays()); |
| | | newBox.setActiveStatus(sourceBox.getActiveStatus()); |
| | | newBox.setDescription(sourceBox.getDescription()); |
| | | newBox.setLastCalibDate(new Date()); |
| | | newBox.setNextCalibDate(nextCalibDate); |
| | | newBox.setCreateBy("admin"); // å®é
åºè¯¥ä»å½åç¨æ·è·å |
| | | newBox.setCreateTime(new Date()); |
| | | baseMapper.insert(newBox); |
| | | newIds.add(newBox.getId()); |
| | | newCodes.add(newBox.getCode()); |
| | | } |
| | | |
| | | Map<String, Object> result = new HashMap<>(); |
| | | result.put("ids", newIds); |
| | | result.put("codes", newCodes); |
| | | return result; |
| | | } |
| | | |
| | | @Override |
| | | public Map<String, Integer> getStatistics() |
| | | { |
| | | Map<String, Integer> statistics = new HashMap<>(); |
| | | statistics.put("total", Math.toIntExact(baseMapper.selectCount(null))); |
| | | |
| | | // å·²å¯ç¨ |
| | | int activeCount = Math.toIntExact(baseMapper.selectCount(new LambdaQueryWrapper<WeighingBox>() |
| | | .eq(WeighingBox::getActiveStatus, 1) |
| | | .eq(WeighingBox::getDelFlag, 0))); |
| | | statistics.put("active", activeCount); |
| | | |
| | | // å·²åç¨ |
| | | int inactiveCount = Math.toIntExact(baseMapper.selectCount(new LambdaQueryWrapper<WeighingBox>() |
| | | .eq(WeighingBox::getActiveStatus, 0) |
| | | .eq(WeighingBox::getDelFlag, 0))); |
| | | statistics.put("inactive", inactiveCount); |
| | | |
| | | // æ ¡åç¶æç»è®¡ |
| | | List<WeighingBox> boxes = baseMapper.selectList(new LambdaQueryWrapper<WeighingBox>() |
| | | .eq(WeighingBox::getDelFlag, 0) |
| | | .eq(WeighingBox::getActiveStatus, 1)); |
| | | |
| | | int normal = 0, warning = 0, overdue = 0, unset = 0; |
| | | for (WeighingBox box : boxes) |
| | | { |
| | | calculateCalibStatus(box); |
| | | String status = box.getCalibStatus(); |
| | | if ("normal".equals(status)) normal++; |
| | | else if ("warning".equals(status)) warning++; |
| | | else if ("overdue".equals(status)) overdue++; |
| | | else if ("unset".equals(status)) unset++; |
| | | } |
| | | |
| | | statistics.put("normal", normal); |
| | | statistics.put("warning", warning); |
| | | statistics.put("overdue", overdue); |
| | | statistics.put("unset", unset); |
| | | |
| | | return statistics; |
| | | } |
| | | |
| | | @Override |
| | | public WeighingBox calculateCalibStatus(WeighingBox weighingBox) |
| | | { |
| | | Date nextCalibDate = weighingBox.getNextCalibDate(); |
| | | if (nextCalibDate == null) |
| | | { |
| | | weighingBox.setCalibStatus("unset"); |
| | | weighingBox.setCalibDaysLeft(null); |
| | | return weighingBox; |
| | | } |
| | | |
| | | long daysLeft = DateUtils.getTimeDifference(new Date(), nextCalibDate, TimeUnit.DAYS); |
| | | weighingBox.setCalibDaysLeft((int) daysLeft); |
| | | |
| | | if (daysLeft < 0) |
| | | { |
| | | weighingBox.setCalibStatus("overdue"); |
| | | } |
| | | else if (daysLeft <= weighingBox.getRemindDays()) |
| | | { |
| | | weighingBox.setCalibStatus("warning"); |
| | | } |
| | | else |
| | | { |
| | | weighingBox.setCalibStatus("normal"); |
| | | } |
| | | |
| | | return weighingBox; |
| | | } |
| | | } |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | <?xml version="1.0" encoding="UTF-8" ?> |
| | | <!DOCTYPE mapper |
| | | PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" |
| | | "http://mybatis.org/dtd/mybatis-3-mapper.dtd"> |
| | | <mapper namespace="org.dromara.qa.md.mapper.CalibrationRecordMapper"> |
| | | |
| | | <resultMap type="org.dromara.qa.md.domain.CalibrationRecord" id="CalibrationRecordResult"> |
| | | <id property="id" column="id"/> |
| | | <result property="targetType" column="target_type"/> |
| | | <result property="targetId" column="target_id"/> |
| | | <result property="targetCode" column="target_code"/> |
| | | <result property="targetName" column="target_name"/> |
| | | <result property="calibDate" column="calib_date"/> |
| | | <result property="calibCycleDays" column="calib_cycle_days"/> |
| | | <result property="standardWeight" column="standard_weight"/> |
| | | <result property="actualWeight" column="actual_weight"/> |
| | | <result property="deviation" column="deviation"/> |
| | | <result property="deviationPct" column="deviation_pct"/> |
| | | <result property="prevCalibDate" column="prev_calib_date"/> |
| | | <result property="nextCalibDate" column="next_calib_date"/> |
| | | <result property="batchId" column="batch_id"/> |
| | | <result property="note" column="note"/> |
| | | <result property="operator" column="operator"/> |
| | | <result property="createTime" column="create_time"/> |
| | | </resultMap> |
| | | |
| | | <sql id="selectCalibrationRecordVo"> |
| | | select id, target_type, target_id, target_code, target_name, calib_date, calib_cycle_days, standard_weight, actual_weight, deviation, deviation_pct, prev_calib_date, next_calib_date, batch_id, note, operator, create_time from qm_calibration_record |
| | | </sql> |
| | | |
| | | <select id="selectCalibrationRecordList" parameterType="org.dromara.qa.md.domain.CalibrationRecord" resultMap="CalibrationRecordResult"> |
| | | <include refid="selectCalibrationRecordVo"/> |
| | | <where> |
| | | <if test="targetType != null and targetType != ''"> |
| | | and target_type = #{targetType} |
| | | </if> |
| | | <if test="targetId != null"> |
| | | and target_id = #{targetId} |
| | | </if> |
| | | <if test="calibDate != null"> |
| | | and calib_date = #{calibDate} |
| | | </if> |
| | | <if test="batchId != null and batchId != ''"> |
| | | and batch_id = #{batchId} |
| | | </if> |
| | | </where> |
| | | order by calib_date desc |
| | | </select> |
| | | |
| | | <select id="selectByTargetId" resultMap="CalibrationRecordResult"> |
| | | <include refid="selectCalibrationRecordVo"/> |
| | | where target_type = #{targetType} and target_id = #{targetId} |
| | | order by calib_date desc |
| | | </select> |
| | | |
| | | <insert id="batchInsert" parameterType="java.util.List"> |
| | | insert into qm_calibration_record (target_type, target_id, target_code, target_name, calib_date, calib_cycle_days, standard_weight, actual_weight, deviation, deviation_pct, prev_calib_date, next_calib_date, batch_id, note, operator, create_time) |
| | | values |
| | | <foreach collection="list" item="item" separator=","> |
| | | (#{item.targetType}, #{item.targetId}, #{item.targetCode}, #{item.targetName}, #{item.calibDate}, #{item.calibCycleDays}, #{item.standardWeight}, #{item.actualWeight}, #{item.deviation}, #{item.deviationPct}, #{item.prevCalibDate}, #{item.nextCalibDate}, #{item.batchId}, #{item.note}, #{item.operator}, #{item.createTime}) |
| | | </foreach> |
| | | </insert> |
| | | |
| | | </mapper> |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | <?xml version="1.0" encoding="UTF-8" ?> |
| | | <!DOCTYPE mapper |
| | | PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" |
| | | "http://mybatis.org/dtd/mybatis-3-mapper.dtd"> |
| | | <mapper namespace="org.dromara.qa.md.mapper.WeighingBoxMapper"> |
| | | |
| | | <resultMap type="org.dromara.qa.md.domain.WeighingBox" id="WeighingBoxResult"> |
| | | <id property="id" column="id"/> |
| | | <result property="name" column="name"/> |
| | | <result property="code" column="code"/> |
| | | <result property="weight" column="weight"/> |
| | | <result property="unit" column="unit"/> |
| | | <result property="location" column="location"/> |
| | | <result property="calibCycleDays" column="calib_cycle_days"/> |
| | | <result property="remindDays" column="remind_days"/> |
| | | <result property="lastCalibDate" column="last_calib_date"/> |
| | | <result property="nextCalibDate" column="next_calib_date"/> |
| | | <result property="activeStatus" column="active_status"/> |
| | | <result property="description" column="description"/> |
| | | <result property="delFlag" column="del_flag"/> |
| | | <result property="createBy" column="create_by"/> |
| | | <result property="createTime" column="create_time"/> |
| | | <result property="updateBy" column="update_by"/> |
| | | <result property="updateTime" column="update_time"/> |
| | | </resultMap> |
| | | |
| | | <sql id="selectWeighingBoxVo"> |
| | | select id, name, code, weight, unit, location, calib_cycle_days, remind_days, last_calib_date, next_calib_date, active_status, description, del_flag, create_by, create_time, update_by, update_time from qm_weighing_box |
| | | </sql> |
| | | |
| | | <select id="selectPageList" resultType="org.dromara.qa.md.domain.WeighingBox"> |
| | | <include refid="selectWeighingBoxVo"/> |
| | | <where> |
| | | del_flag = '0' |
| | | <if test="weighingBox.name != null and weighingBox.name != ''"> |
| | | and name like concat('%', #{weighingBox.name}, '%') |
| | | </if> |
| | | <if test="weighingBox.code != null and weighingBox.code != ''"> |
| | | and code like concat('%', #{weighingBox.code}, '%') |
| | | </if> |
| | | <if test="weighingBox.activeStatus != null"> |
| | | and active_status = #{weighingBox.activeStatus} |
| | | </if> |
| | | </where> |
| | | order by create_time desc |
| | | </select> |
| | | |
| | | <select id="selectWeighingBoxList" parameterType="org.dromara.qa.md.domain.WeighingBox" resultMap="WeighingBoxResult"> |
| | | <include refid="selectWeighingBoxVo"/> |
| | | <where> |
| | | <if test="delFlag != null"> |
| | | and del_flag = #{delFlag} |
| | | </if> |
| | | <if test="name != null and name != ''"> |
| | | and name like concat('%', #{name}, '%') |
| | | </if> |
| | | <if test="code != null and code != ''"> |
| | | and code like concat('%', #{code}, '%') |
| | | </if> |
| | | <if test="location != null and location != ''"> |
| | | and location like concat('%', #{location}, '%') |
| | | </if> |
| | | <if test="activeStatus != null"> |
| | | and active_status = #{activeStatus} |
| | | </if> |
| | | </where> |
| | | order by create_time desc |
| | | </select> |
| | | |
| | | <select id="selectActiveWeighingBoxes" resultMap="WeighingBoxResult"> |
| | | <include refid="selectWeighingBoxVo"/> |
| | | where del_flag = 0 and active_status = 1 |
| | | </select> |
| | | |
| | | <update id="batchUpdateStatus"> |
| | | update qm_weighing_box set active_status = #{activeStatus} where id in |
| | | <foreach collection="boxIds" item="id" open="(" separator="," close=")"> |
| | | ${id} |
| | | </foreach> |
| | | </update> |
| | | |
| | | <update id="batchUpdateCalibConfig"> |
| | | update qm_weighing_box set calib_cycle_days = #{calibCycleDays}, remind_days = #{remindDays} where id in |
| | | <foreach collection="boxIds" item="id" open="(" separator="," close=")"> |
| | | ${id} |
| | | </foreach> |
| | | </update> |
| | | |
| | | <!-- å¸¦æ ¡åç¶æè¿æ»¤çæ¥è¯¢ --> |
| | | <select id="selectWeighingBoxListWithCalibStatus" parameterType="org.dromara.qa.md.domain.WeighingBox" resultMap="WeighingBoxResult"> |
| | | select * from ( |
| | | select |
| | | id, name, code, weight, unit, location, calib_cycle_days, remind_days, |
| | | last_calib_date, next_calib_date, active_status, description, |
| | | del_flag, create_by, create_time, update_by, update_time, |
| | | case |
| | | when next_calib_date is null then 'unset' |
| | | when current_date > next_calib_date then 'overdue' |
| | | when current_date + interval '1 day' * remind_days >= next_calib_date then 'warning' |
| | | else 'normal' |
| | | end as calib_status |
| | | from qm_weighing_box |
| | | where del_flag = 0 |
| | | ) as box |
| | | where 1=1 |
| | | <if test="name != null and name != ''"> |
| | | and box.name like concat('%', #{name}, '%') |
| | | </if> |
| | | <if test="code != null and code != ''"> |
| | | and box.code like concat('%', #{code}, '%') |
| | | </if> |
| | | <if test="location != null and location != ''"> |
| | | and box.location like concat('%', #{location}, '%') |
| | | </if> |
| | | <if test="activeStatus != null"> |
| | | and box.active_status = #{activeStatus} |
| | | </if> |
| | | <if test="calibStatus != null and calibStatus != ''"> |
| | | and box.calib_status = #{calibStatus} |
| | | </if> |
| | | order by box.create_time desc |
| | | </select> |
| | | |
| | | <!-- å¸¦æ ¡åç¶æè¿æ»¤çè®¡æ° --> |
| | | <select id="selectWeighingBoxCountWithCalibStatus" parameterType="org.dromara.qa.md.domain.WeighingBox" resultType="java.lang.Long"> |
| | | select count(*) from ( |
| | | select |
| | | id, name, code, weight, unit, location, calib_cycle_days, remind_days, |
| | | last_calib_date, next_calib_date, active_status, description, |
| | | del_flag, create_by, create_time, update_by, update_time, |
| | | case |
| | | when next_calib_date is null then 'unset' |
| | | when current_date > next_calib_date then 'overdue' |
| | | when current_date + interval '1 day' * remind_days >= next_calib_date then 'warning' |
| | | else 'normal' |
| | | end as calib_status |
| | | from qm_weighing_box |
| | | where del_flag = 0 |
| | | ) as box |
| | | where 1=1 |
| | | <if test="name != null and name != ''"> |
| | | and box.name like concat('%', #{name}, '%') |
| | | </if> |
| | | <if test="code != null and code != ''"> |
| | | and box.code like concat('%', #{code}, '%') |
| | | </if> |
| | | <if test="location != null and location != ''"> |
| | | and box.location like concat('%', #{location}, '%') |
| | | </if> |
| | | <if test="activeStatus != null"> |
| | | and box.active_status = #{activeStatus} |
| | | </if> |
| | | <if test="calibStatus != null and calibStatus != ''"> |
| | | and box.calib_status = #{calibStatus} |
| | | </if> |
| | | </select> |
| | | |
| | | </mapper> |
| | |
| | | 'analy_store-silk': '', |
| | | md: '', |
| | | md_shift: '', |
| | | qm: '', |
| | | qm_batch: '', |
| | | qm_std: '', |
| | | qm: 'åºç¡æ°æ®ç®¡ç', |
| | | qm_batch: 'æ¹æ¬¡ç®¡ç', |
| | | qm_std: 'æ å管ç', |
| | | qm_weighing_box: 'ç§°éçåç»´æ¤', |
| | | report: '', |
| | | report_demo: '', |
| | | 'report_silk-storage-output': '', |
| | |
| | | home: () => import("@/views/home/index.vue"), |
| | | md_instrument: () => import("@/views/md/instrument/index.vue"), |
| | | md_shift: () => import("@/views/md/shift/index.vue"), |
| | | "md_weighing-box": () => import("@/views/md/weighing-box/index.vue"), |
| | | monitor_cache: () => import("@/views/monitor/cache/index.vue"), |
| | | monitor_logininfor: () => import("@/views/monitor/logininfor/index.vue"), |
| | | monitor_online: () => import("@/views/monitor/online/index.vue"), |
| | |
| | | title: 'md_shift', |
| | | i18nKey: 'route.md_shift' |
| | | } |
| | | }, |
| | | { |
| | | name: 'md_weighing-box', |
| | | path: '/md/weighing-box', |
| | | component: 'view.md_weighing-box', |
| | | meta: { |
| | | title: 'md_weighing-box', |
| | | i18nKey: 'route.md_weighing-box' |
| | | } |
| | | } |
| | | ] |
| | | }, |
| | |
| | | "md": "/md", |
| | | "md_instrument": "/md/instrument", |
| | | "md_shift": "/md/shift", |
| | | "md_weighing-box": "/md/weighing-box", |
| | | "monitor": "/monitor", |
| | | "monitor_cache": "/monitor/cache", |
| | | "monitor_logininfor": "/monitor/logininfor", |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | import { request } from '@/service/request'; |
| | | |
| | | /** |
| | | * ç§°éçåAPI |
| | | */ |
| | | export const weighingBoxApi = { |
| | | /** |
| | | * æ¥è¯¢ç§°éçåå表 |
| | | */ |
| | | getList: (params: any) => request({ |
| | | url: '/md/weighingBox/list', |
| | | method: 'get', |
| | | params |
| | | }), |
| | | |
| | | /** |
| | | * è·åç§°éçå详æ
|
| | | */ |
| | | getInfo: (id: number) => request({ |
| | | url: `/md/weighingBox/${id}`, |
| | | method: 'get' |
| | | }), |
| | | |
| | | /** |
| | | * æ°å¢ç§°éçå |
| | | */ |
| | | add: (data: any) => request({ |
| | | url: '/md/weighingBox', |
| | | method: 'post', |
| | | data |
| | | }), |
| | | |
| | | /** |
| | | * ä¿®æ¹ç§°éçå |
| | | */ |
| | | edit: (data: any) => request({ |
| | | url: '/md/weighingBox', |
| | | method: 'put', |
| | | data |
| | | }), |
| | | |
| | | /** |
| | | * å é¤ç§°éçå |
| | | */ |
| | | remove: (ids: number[]) => request({ |
| | | url: `/md/weighingBox/${ids.join(',')}`, |
| | | method: 'delete' |
| | | }), |
| | | |
| | | /** |
| | | * æ§è¡åä¸ªæ ¡å |
| | | */ |
| | | calibrate: (data: { |
| | | boxId: number; |
| | | calibDate: string; |
| | | actualWeight?: number; |
| | | note?: string; |
| | | }) => request({ |
| | | url: '/md/weighingBox/calibrate', |
| | | method: 'post', |
| | | data |
| | | }), |
| | | |
| | | /** |
| | | * æ¹éæ ¡å |
| | | */ |
| | | batchCalibrate: (data: any) => request({ |
| | | url: '/md/weighingBox/batchCalibrate', |
| | | method: 'post', |
| | | data |
| | | }), |
| | | |
| | | /** |
| | | * ç»ä¸é
ç½®æ ¡å卿 |
| | | */ |
| | | batchConfig: (data: any) => request({ |
| | | url: '/md/weighingBox/batchConfig', |
| | | method: 'post', |
| | | data |
| | | }), |
| | | |
| | | /** |
| | | * æ¹éæ´æ°ç¶æ |
| | | */ |
| | | batchUpdateStatus: (data: { |
| | | boxIds: number[]; |
| | | activeStatus: number; |
| | | }) => request({ |
| | | url: '/md/weighingBox/batchUpdateStatus', |
| | | method: 'post', |
| | | data |
| | | }), |
| | | |
| | | /** |
| | | * å¤å¶çå |
| | | */ |
| | | copy: (data: { |
| | | sourceId: number; |
| | | count: number; |
| | | }) => request({ |
| | | url: '/md/weighingBox/copy', |
| | | method: 'post', |
| | | data |
| | | }), |
| | | |
| | | /** |
| | | * è·åæ ¡åç¶æç»è®¡ |
| | | */ |
| | | getStatistics: () => request({ |
| | | url: '/md/weighingBox/statistics', |
| | | method: 'get' |
| | | }) |
| | | }; |
| | |
| | | "md": "/md"; |
| | | "md_instrument": "/md/instrument"; |
| | | "md_shift": "/md/shift"; |
| | | "md_weighing-box": "/md/weighing-box"; |
| | | "monitor": "/monitor"; |
| | | "monitor_cache": "/monitor/cache"; |
| | | "monitor_logininfor": "/monitor/logininfor"; |
| | |
| | | | "home" |
| | | | "md_instrument" |
| | | | "md_shift" |
| | | | "md_weighing-box" |
| | | | "monitor_cache" |
| | | | "monitor_logininfor" |
| | | | "monitor_online" |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | <template> |
| | | <div class="app-container"> |
| | | <!-- 页颿 é¢ --> |
| | | <div class="page-header"> |
| | | <div class="page-header-left"> |
| | | <h1>ç§°éçåç»´æ¤</h1> |
| | | </div> |
| | | <div class="header-actions"> |
| | | |
| | | <n-button type="primary" @click="handleAdd"> |
| | | + æ°å¢çå |
| | | </n-button> |
| | | </div> |
| | | </div> |
| | | |
| | | <!-- æç´¢æ --> |
| | | <div class="search-bar"> |
| | | <n-input v-model:value="searchParams.name" placeholder="æç´¢åç§°ãç¼å·ãä½ç½®..." style="width: 250px;" /> |
| | | |
| | | <n-button type="primary" @click="handleSearch" style="width: 80px;"> |
| | | æç´¢ |
| | | </n-button> |
| | | <!-- å¿«æ·æä½æé® --> |
| | | <div class="quick-actions"> |
| | | <!-- <n-button @click="handleBatchCalibrate" > |
| | | <span class="action-icon">ð§</span> æ¹éæ ¡å <n-tag class="action-tag" size="small" type="error">{{ statistics.overdue || 0 }}</n-tag> |
| | | </n-button> --> |
| | | <n-button @click="handleBatchConfig('all')" > |
| | | <span class="action-icon">ð</span> æ¹éé
ç½®æ ¡å卿 |
| | | </n-button> |
| | | <!-- <n-button @click="handleBatchActivate" > |
| | | <span class="action-icon">â
</span> æ¹éå¯ç¨ |
| | | </n-button> |
| | | <n-button @click="handleBatchDeactivate" > |
| | | <span class="action-icon">â</span> æ¹éåç¨ |
| | | </n-button> --> |
| | | <n-button @click="filterByCalibStatus('overdue')" > |
| | | <span class="action-icon">ð¨</span> ä»
ç龿 <n-tag class="action-tag" size="small" type="error">{{ statistics.overdue || 0 }}</n-tag> |
| | | </n-button> |
| | | <n-button @click="filterByCalibStatus('warning')" > |
| | | <span class="action-icon">â ï¸</span> å³å°å°æ <n-tag class="action-tag" size="small" type="warning">{{ statistics.warning || 0 }}</n-tag> |
| | | </n-button> |
| | | <!-- <n-button @click="handleCopy" > |
| | | <span class="action-icon">ð</span> å¤å¶çå |
| | | </n-button> --> |
| | | <n-button @click="resetSearch" > |
| | | <span class="action-icon">ð</span> éç½®çé |
| | | </n-button> |
| | | </div> |
| | | </div> |
| | | |
| | | <!-- ç»è®¡å¡ç --> |
| | | <div class="stats-container"> |
| | | <div class="stat-card total" @click="filterByType('total')"> |
| | | <div class="stat-header"> |
| | | <div class="stat-icon">ð¦</div> |
| | | <div class="stat-title">çåæ»æ°</div> |
| | | </div> |
| | | <div class="stat-value">{{ statistics.total || 0 }}</div> |
| | | <div class="stat-sub">å·²å¯ç¨ {{ statistics.active || 0 }} · å·²åç¨ {{ statistics.inactive || 0 }}</div> |
| | | </div> |
| | | <div class="stat-card normal" @click="filterByCalibStatus('normal')"> |
| | | <div class="stat-header"> |
| | | <div class="stat-icon">â
</div> |
| | | <div class="stat-title">æ£å¸¸</div> |
| | | </div> |
| | | <div class="stat-value">{{ statistics.normal || 0 }}</div> |
| | | <div class="stat-sub">è·ç¦»æ ¡å > 7天</div> |
| | | </div> |
| | | <div class="stat-card warning" @click="filterByCalibStatus('warning')"> |
| | | <div class="stat-header"> |
| | | <div class="stat-icon">â ï¸</div> |
| | | <div class="stat-title">å³å°å°æ</div> |
| | | </div> |
| | | <div class="stat-value">{{ statistics.warning || 0 }}</div> |
| | | <div class="stat-sub">7天å
éæ ¡å</div> |
| | | </div> |
| | | <div class="stat-card overdue" @click="filterByCalibStatus('overdue')"> |
| | | <div class="stat-header"> |
| | | <div class="stat-icon">ð¨</div> |
| | | <div class="stat-title">已龿</div> |
| | | </div> |
| | | <div class="stat-value">{{ statistics.overdue || 0 }}</div> |
| | | <div class="stat-sub">æ ¡åå·²è¿ææªå®æ</div> |
| | | </div> |
| | | <div class="stat-card inactive" @click="filterByActiveStatus(0)"> |
| | | <div class="stat-header"> |
| | | <div class="stat-icon">â</div> |
| | | <div class="stat-title">å·²åç¨</div> |
| | | </div> |
| | | <div class="stat-value">{{ statistics.inactive || 0 }}</div> |
| | | <div class="stat-sub">æä¸ä½¿ç¨</div> |
| | | </div> |
| | | </div> |
| | | |
| | | <!-- æ¹éæä½æ --> |
| | | <div class="batch-bar" v-if="selectedRows.length > 0"> |
| | | <span class="batch-info">å·²éä¸ <strong>{{ selectedRows.length }}</strong> 项</span> |
| | | <div class="batch-actions"> |
| | | <n-button type="primary" size="small" tertiary @click="handleBatchCalibrate"> |
| | | <span class="action-icon">ð§</span> æ¹éæ ¡å |
| | | </n-button> |
| | | <n-button type="success" size="small" tertiary @click="handleBatchActivate"> |
| | | <span class="action-icon">â
</span> æ¹éå¯ç¨ |
| | | </n-button> |
| | | <n-button type="tertiary" size="small" tertiary @click="handleBatchDeactivate"> |
| | | <span class="action-icon">â</span> æ¹éåç¨ |
| | | </n-button> |
| | | <n-button type="warning" size="small" tertiary @click="handleBatchConfig('selected')"> |
| | | <span class="action-icon">ð</span> æ¹éé
ç½®æ ¡å卿 |
| | | </n-button> |
| | | <n-button type="error" size="small" tertiary @click="handleDelete(undefined)"> |
| | | <span class="action-icon">ðï¸</span> æ¹éå é¤ |
| | | </n-button> |
| | | <n-button size="small" @click="clearSelection"> |
| | | åæ¶éæ© |
| | | </n-button> |
| | | </div> |
| | | </div> |
| | | |
| | | <!-- æ°æ®è¡¨æ ¼ --> |
| | | <div class="table-container"> |
| | | <n-data-table |
| | | :columns="columns" |
| | | :data="data" |
| | | :loading="loading" |
| | | :pagination="mobilePagination" |
| | | :row-key="(row) => row.id" |
| | | :checked-row-keys="checkedRowKeys" |
| | | @update:checked-row-keys="handleChecked" |
| | | :bordered="true" |
| | | :single-line="true" |
| | | > |
| | | <template #body-cell="{ column, row }"> |
| | | <template v-if="column.key === 'calibStatus'"> |
| | | <span class="calib-badge" :class="row.calibStatus"> |
| | | {{ getCalibIcon(row.calibStatus) }} {{ getCalibLabel(row.calibStatus) }} |
| | | <span v-if="row.calibStatus === 'warning' || row.calibStatus === 'overdue'" class="days-left"> |
| | | {{ row.daysLeft ? `· å©ä½${row.daysLeft}天` : '' }} |
| | | </span> |
| | | </span> |
| | | </template> |
| | | <template v-else-if="column.key === 'activeStatus'"> |
| | | <span class="status-tag active"> |
| | | å¯ç¨ |
| | | </span> |
| | | </template> |
| | | <template v-else-if="column.key === 'calibCycleDays'"> |
| | | æ¯ {{ row.calibCycleDays }} 天 |
| | | </template> |
| | | <template v-else-if="column.key === 'actions'"> |
| | | <div class="actions"> |
| | | <n-button size="small" circle @click="handleView(row)"> |
| | | <SvgIcon icon="lucide:eye" /> |
| | | </n-button> |
| | | <n-button size="small" circle @click="handleCalibrate(row)"> |
| | | <SvgIcon icon="lucide:refresh-ccw" /> |
| | | </n-button> |
| | | <n-button size="small" circle @click="handleEdit(row)"> |
| | | <SvgIcon icon="lucide:edit" /> |
| | | </n-button> |
| | | <n-button size="small" circle type="error" @click="handleDelete([row.id])"> |
| | | <SvgIcon icon="lucide:trash-2" /> |
| | | </n-button> |
| | | </div> |
| | | </template> |
| | | </template> |
| | | </n-data-table> |
| | | </div> |
| | | |
| | | <!-- æ°å¢/ç¼è¾å¼¹çªç»ä»¶ --> |
| | | <WeighingBoxOperate |
| | | v-model:visible="drawerVisible" |
| | | :operateType="operateType" |
| | | :rowData="currentRow" |
| | | @submitted="handleSubmitted" |
| | | /> |
| | | |
| | | <!-- æ ¡åå¼¹çªç»ä»¶ --> |
| | | <WeighingBoxCalibrate |
| | | v-model:visible="calibrateVisible" |
| | | :rowData="currentRow" |
| | | @submitted="handleSubmitted" |
| | | /> |
| | | |
| | | <!-- æ¹éæ ¡åå¼¹çªç»ä»¶ --> |
| | | <WeighingBoxBatchCalibrate |
| | | v-model:visible="batchCalibrateVisible" |
| | | :selectedRows="selectedRows" |
| | | @submitted="handleSubmitted" |
| | | /> |
| | | |
| | | <!-- ç»ä¸é
置弹çªç»ä»¶ --> |
| | | <WeighingBoxBatchConfig |
| | | v-model:visible="batchConfigVisible" |
| | | :selectedRows="selectedRows" |
| | | :type="applyScope" |
| | | @submitted="handleSubmitted" |
| | | /> |
| | | |
| | | <!-- å¤å¶å¼¹çªç»ä»¶ --> |
| | | <WeighingBoxCopy |
| | | v-model:visible="copyVisible" |
| | | :rowData="currentRow" |
| | | @submitted="handleSubmitted" |
| | | /> |
| | | </div> |
| | | </template> |
| | | |
| | | <script setup lang="tsx"> |
| | | import { ref, reactive, computed, onMounted } from 'vue'; |
| | | import ButtonIcon from '@/components/custom/button-icon.vue'; |
| | | import { NDivider, useDialog } from 'naive-ui'; |
| | | import { weighingBoxApi } from '@/service/api/md/weighing-box'; |
| | | import { useMessage } from 'naive-ui'; |
| | | import SvgIcon from '@/components/custom/svg-icon.vue'; |
| | | import { defaultTransform, useNaivePaginatedTable } from '@/hooks/common/table'; |
| | | import WeighingBoxOperate from './modules/weighing-box-operate.vue'; |
| | | import WeighingBoxCalibrate from './modules/weighing-box-calibrate.vue'; |
| | | import WeighingBoxBatchCalibrate from './modules/weighing-box-batch-calibrate.vue'; |
| | | import WeighingBoxBatchConfig from './modules/weighing-box-batch-config.vue'; |
| | | import WeighingBoxCopy from './modules/weighing-box-copy.vue'; |
| | | |
| | | const message = useMessage(); |
| | | const dialog = useDialog(); |
| | | |
| | | // æç´¢åæ° |
| | | const searchParams = reactive({ |
| | | pageNum: 1, |
| | | pageSize: 10, |
| | | name: '', |
| | | code: '', |
| | | activeStatus: undefined, |
| | | calibStatus: undefined, |
| | | location: undefined |
| | | }); |
| | | |
| | | // ç»è®¡æ°æ® |
| | | const statistics = ref({ |
| | | total: 0, |
| | | active: 0, |
| | | inactive: 0, |
| | | normal: 0, |
| | | warning: 0, |
| | | overdue: 0, |
| | | unset: 0 |
| | | }); |
| | | |
| | | // éä¸çè¡ |
| | | const checkedRowKeys = ref<number[]>([]); |
| | | const selectedRows = computed(() => { |
| | | return data.value.filter(row => checkedRowKeys.value.includes(row.id)); |
| | | }); |
| | | |
| | | // ä½¿ç¨ useNaivePaginatedTable 管çè¡¨æ ¼æ°æ® |
| | | const { columns, data, getData, getDataByPage, loading, mobilePagination, scrollX } = useNaivePaginatedTable({ |
| | | api: () => weighingBoxApi.getList(searchParams), |
| | | transform: response => defaultTransform(response), |
| | | onPaginationParamsChange: params => { |
| | | searchParams.pageNum = params.page; |
| | | searchParams.pageSize = params.pageSize; |
| | | }, |
| | | columns: () => [ |
| | | { |
| | | type: 'selection', |
| | | width: 40 |
| | | }, |
| | | { |
| | | title: 'ç¼å·', |
| | | key: 'code', |
| | | width: 120, |
| | | sorter: true |
| | | }, |
| | | { |
| | | title: 'åç§°', |
| | | key: 'name', |
| | | width: 150, |
| | | sorter: true |
| | | }, |
| | | { |
| | | title: 'éé', |
| | | key: 'weight', |
| | | width: 100, |
| | | render: (row) => `${row.weight} ${row.unit || ''}` |
| | | }, |
| | | { |
| | | title: 'ä½ç½®', |
| | | key: 'location', |
| | | width: 150 |
| | | }, |
| | | { |
| | | title: 'æ ¡å卿', |
| | | key: 'calibCycleDays', |
| | | width: 120, |
| | | sorter: true |
| | | }, |
| | | { |
| | | title: '䏿¬¡æ ¡å', |
| | | key: 'nextCalibDate', |
| | | width: 120, |
| | | sorter: true |
| | | }, |
| | | { |
| | | title: 'æ ¡åç¶æ', |
| | | key: 'calibStatus', |
| | | width: 150, |
| | | render: (row) => { |
| | | return ( |
| | | <span class={`calib-badge ${row.calibStatus}`}> |
| | | {getCalibIcon(row.calibStatus)} {getCalibLabel(row.calibStatus)} |
| | | {row.calibStatus === 'warning' || row.calibStatus === 'overdue' ? |
| | | <span class="days-left">{row.daysLeft ? `· å©ä½${row.daysLeft}天` : ''}</span> : |
| | | '' |
| | | } |
| | | </span> |
| | | ); |
| | | } |
| | | }, |
| | | { |
| | | title: 'å¯ç¨', |
| | | key: 'activeStatus', |
| | | width: 80, |
| | | render: (row) => { |
| | | const isActive = row.activeStatus === 1; |
| | | return ( |
| | | <span class={`status-tag ${isActive ? 'active' : 'inactive'}`}> |
| | | {isActive ? 'å¯ç¨' : 'åç¨'} |
| | | </span> |
| | | ); |
| | | } |
| | | }, |
| | | { |
| | | title: 'æä½', |
| | | key: 'actions', |
| | | width: 130, |
| | | fixed: 'right', |
| | | render: (row) => { |
| | | const divider = () => { |
| | | return <NDivider vertical />; |
| | | }; |
| | | |
| | | const editBtn = () => { |
| | | return ( |
| | | <ButtonIcon |
| | | text |
| | | type="primary" |
| | | icon="material-symbols:drive-file-rename-outline-outline" |
| | | tooltipContent="ç¼è¾" |
| | | onClick={() => handleEdit(row)} |
| | | /> |
| | | ); |
| | | }; |
| | | |
| | | // æ ¡åæé® |
| | | const calibrateBtn = () => { |
| | | return ( |
| | | <ButtonIcon |
| | | text |
| | | type="primary" |
| | | icon="material-symbols:build-outline-rounded" |
| | | tooltipContent="æ ¡å" |
| | | onClick={() => handleCalibrate(row)} |
| | | /> |
| | | ); |
| | | }; |
| | | // å¤å¶æé® |
| | | const copyBtn = () => { |
| | | return ( |
| | | <ButtonIcon |
| | | text |
| | | type="primary" |
| | | icon="material-symbols:copy-all-outline" |
| | | tooltipContent="å¤å¶" |
| | | onClick={() => handleCopy(row)} |
| | | /> |
| | | ); |
| | | }; |
| | | |
| | | // å é¤æé® |
| | | const deleteBtn = () => { |
| | | return ( |
| | | <ButtonIcon |
| | | text |
| | | type="error" |
| | | icon="material-symbols:delete-outline" |
| | | tooltipContent="å é¤" |
| | | popconfirmContent="ç¡®å®å é¤åï¼" |
| | | onPositiveClick={() => handleDelete([row.id])} |
| | | /> |
| | | ); |
| | | }; |
| | | |
| | | return ( |
| | | <div class="flex-center gap-8px"> |
| | | {editBtn()} |
| | | {divider()} |
| | | {calibrateBtn()} |
| | | {divider()} |
| | | {copyBtn()} |
| | | {divider()} |
| | | {deleteBtn()} |
| | | </div> |
| | | ); |
| | | } |
| | | } |
| | | ] |
| | | }); |
| | | |
| | | // å¼¹çªç¸å
³ç¶æ |
| | | const drawerVisible = ref(false); |
| | | const calibrateVisible = ref(false); |
| | | const batchCalibrateVisible = ref(false); |
| | | const batchConfigVisible = ref(false); |
| | | const applyScope = ref('all'); |
| | | const copyVisible = ref(false); |
| | | const operateType = ref('add'); |
| | | const currentRow = ref({}); |
| | | |
| | | // è·åæ ¡åç¶æå¾æ |
| | | const getCalibIcon = (status) => { |
| | | const icons = { |
| | | normal: 'â
', |
| | | warning: 'â ï¸', |
| | | overdue: 'ð¨', |
| | | unset: 'â¹ï¸' |
| | | }; |
| | | return icons[status] || 'â¹ï¸'; |
| | | }; |
| | | |
| | | // è·åæ ¡åç¶ææ ç¾ |
| | | const getCalibLabel = (status) => { |
| | | const labels = { |
| | | normal: 'æ£å¸¸', |
| | | warning: 'å³å°å°æ', |
| | | overdue: 'å·²è¿æ', |
| | | unset: 'æªè®¾ç½®' |
| | | }; |
| | | return labels[status] || 'æªè®¾ç½®'; |
| | | }; |
| | | |
| | | // ææ ¡åç¶æçé |
| | | const filterByCalibStatus = (status) => { |
| | | searchParams.calibStatus = status; |
| | | searchParams.activeStatus = undefined; |
| | | handleSearch(); |
| | | }; |
| | | |
| | | // æå¯ç¨ç¶æçé |
| | | const filterByActiveStatus = (status) => { |
| | | searchParams.activeStatus = status; |
| | | searchParams.calibStatus = undefined; |
| | | handleSearch(); |
| | | }; |
| | | |
| | | // æç±»åçé |
| | | const filterByType = (type) => { |
| | | if (type === 'total') { |
| | | // æ¾ç¤ºææçå |
| | | searchParams.calibStatus = undefined; |
| | | searchParams.activeStatus = undefined; |
| | | } |
| | | handleSearch(); |
| | | }; |
| | | |
| | | // æ¸
é¤éæ© |
| | | const clearSelection = () => { |
| | | checkedRowKeys.value = []; |
| | | }; |
| | | |
| | | // å¯¼åº |
| | | const handleExport = () => { |
| | | message.info('导åºåè½å¼åä¸'); |
| | | }; |
| | | |
| | | // 导å
¥ |
| | | const handleImport = () => { |
| | | message.info('导å
¥åè½å¼åä¸'); |
| | | }; |
| | | |
| | | // æ¥ç |
| | | const handleView = (row: any) => { |
| | | message.info('æ¥çåè½å¼åä¸'); |
| | | }; |
| | | |
| | | // å è½½ç»è®¡æ°æ® |
| | | const loadStatistics = async () => { |
| | | try { |
| | | const response = await weighingBoxApi.getStatistics(); |
| | | statistics.value = response.data || {}; |
| | | } catch (error) { |
| | | console.error('å è½½ç»è®¡æ°æ®å¤±è´¥', error); |
| | | } |
| | | }; |
| | | |
| | | // æç´¢ |
| | | const handleSearch = () => { |
| | | searchParams.pageNum = 1; |
| | | getDataByPage(); |
| | | }; |
| | | |
| | | // éç½®æç´¢ |
| | | const resetSearch = () => { |
| | | Object.keys(searchParams).forEach(key => { |
| | | if (key !== 'pageNum' && key !== 'pageSize') { |
| | | searchParams[key] = undefined; |
| | | } |
| | | }); |
| | | searchParams.pageNum = 1; |
| | | getDataByPage(); |
| | | }; |
| | | |
| | | // å¤çéä¸ |
| | | const handleChecked = (keys: number[]) => { |
| | | checkedRowKeys.value = keys; |
| | | }; |
| | | |
| | | // æ°å¢ |
| | | const handleAdd = () => { |
| | | operateType.value = 'add'; |
| | | currentRow.value = {}; |
| | | drawerVisible.value = true; |
| | | }; |
| | | |
| | | // ç¼è¾ |
| | | const handleEdit = (row: any) => { |
| | | operateType.value = 'edit'; |
| | | currentRow.value = row; |
| | | drawerVisible.value = true; |
| | | }; |
| | | |
| | | // æ ¡å |
| | | const handleCalibrate = (row: any) => { |
| | | currentRow.value = row; |
| | | calibrateVisible.value = true; |
| | | }; |
| | | |
| | | // æ¹éæ ¡å |
| | | const handleBatchCalibrate = () => { |
| | | if (selectedRows.value.length === 0) { |
| | | message.warning('è¯·éæ©è¦æ ¡åççå'); |
| | | return; |
| | | } |
| | | batchCalibrateVisible.value = true; |
| | | }; |
| | | |
| | | // æ¹éé
ç½® |
| | | const handleBatchConfig = (type: string) => { |
| | | |
| | | batchConfigVisible.value = true; |
| | | applyScope.value = type; |
| | | }; |
| | | |
| | | // æ¹éå¯ç¨ |
| | | const handleBatchActivate = async () => { |
| | | if (selectedRows.value.length === 0) { |
| | | message.warning('è¯·éæ©è¦å¯ç¨ççå'); |
| | | return; |
| | | } |
| | | |
| | | dialog.warning({ |
| | | title: 'æ¹éå¯ç¨ç¡®è®¤', |
| | | content: `ç¡®å®è¦å¯ç¨éä¸ç ${selectedRows.value.length} 个çååï¼`, |
| | | positiveText: 'ç¡®å®', |
| | | negativeText: 'åæ¶', |
| | | onPositiveClick: async () => { |
| | | try { |
| | | const response = await weighingBoxApi.batchUpdateStatus({ |
| | | boxIds: selectedRows.value.map(row => row.id), |
| | | activeStatus: 1 |
| | | }); |
| | | const res = response.response.data; |
| | | if (res.code === 200) { |
| | | message.success('æ¹éå¯ç¨æå'); |
| | | getDataByPage(); |
| | | loadStatistics(); |
| | | } else { |
| | | message.error(response.msg || 'æ¹éå¯ç¨å¤±è´¥'); |
| | | } |
| | | } catch (error) { |
| | | message.error('æ¹éå¯ç¨å¤±è´¥'); |
| | | } |
| | | } |
| | | }); |
| | | }; |
| | | |
| | | // æ¹éåç¨ |
| | | const handleBatchDeactivate = async () => { |
| | | if (selectedRows.value.length === 0) { |
| | | message.warning('è¯·éæ©è¦åç¨ççå'); |
| | | return; |
| | | } |
| | | |
| | | dialog.warning({ |
| | | title: 'æ¹éåç¨ç¡®è®¤', |
| | | content: `ç¡®å®è¦åç¨éä¸ç ${selectedRows.value.length} 个çååï¼`, |
| | | positiveText: 'ç¡®å®', |
| | | negativeText: 'åæ¶', |
| | | onPositiveClick: async () => { |
| | | try { |
| | | const response = await weighingBoxApi.batchUpdateStatus({ |
| | | boxIds: selectedRows.value.map(row => row.id), |
| | | activeStatus: 0 |
| | | }); |
| | | const res = response.response.data; |
| | | |
| | | if (res.code === 200) { |
| | | message.success('æ¹éåç¨æå'); |
| | | getDataByPage(); |
| | | loadStatistics(); |
| | | } else { |
| | | message.error(res.msg || 'æ¹éåç¨å¤±è´¥'); |
| | | } |
| | | } catch (error) { |
| | | message.error('æ¹éåç¨å¤±è´¥'); |
| | | } |
| | | } |
| | | }); |
| | | }; |
| | | |
| | | // å¤å¶ |
| | | const handleCopy = (row: any) => { |
| | | currentRow.value = row; |
| | | copyVisible.value = true; |
| | | }; |
| | | |
| | | // å é¤ |
| | | const handleDelete = async (ids?: number[]) => { |
| | | const deleteIds = ids || selectedRows.value.map(row => row.id); |
| | | |
| | | console.log(deleteIds); |
| | | if (deleteIds.length === 0) { |
| | | message.warning('è¯·éæ©è¦å é¤ççå'); |
| | | return; |
| | | } |
| | | |
| | | dialog.error({ |
| | | title: 'æ¹éå é¤ç¡®è®¤', |
| | | content: `ç¡®å®è¦å é¤éä¸ç ${deleteIds.length} 个çååï¼æ¤æä½ä¸å¯æ¢å¤ã`, |
| | | positiveText: 'ç¡®å®å é¤', |
| | | negativeText: 'åæ¶', |
| | | onPositiveClick: async () => { |
| | | try { |
| | | const response = await weighingBoxApi.remove(deleteIds); |
| | | |
| | | console.log("å é¤ååº:", response.response.data); |
| | | const res = response.response.data; |
| | | if (res.code === 200) { |
| | | message.success('å 餿å'); |
| | | getDataByPage(); |
| | | loadStatistics(); |
| | | } else { |
| | | message.error(res.msg || 'å é¤å¤±è´¥'); |
| | | } |
| | | } catch (error) { |
| | | message.error('å é¤å¤±è´¥'); |
| | | } |
| | | } |
| | | }); |
| | | }; |
| | | |
| | | // æäº¤åå¤ç |
| | | const handleSubmitted = () => { |
| | | getDataByPage(); |
| | | loadStatistics(); |
| | | }; |
| | | |
| | | // åå§å |
| | | onMounted(() => { |
| | | getData(); |
| | | loadStatistics(); |
| | | }); |
| | | </script> |
| | | |
| | | <style scoped> |
| | | .app-container { |
| | | padding: 20px; |
| | | } |
| | | |
| | | /* 页颿 é¢ */ |
| | | .page-header { |
| | | display: flex; |
| | | justify-content: space-between; |
| | | align-items: flex-start; |
| | | margin-bottom: 20px; |
| | | } |
| | | |
| | | .page-header-left .breadcrumb { |
| | | font-size: 14px; |
| | | color: #666; |
| | | margin-bottom: 8px; |
| | | } |
| | | |
| | | .page-header-left h1 { |
| | | font-size: 24px; |
| | | font-weight: 600; |
| | | color: #333; |
| | | margin: 0; |
| | | } |
| | | |
| | | .header-actions { |
| | | display: flex; |
| | | gap: 10px; |
| | | align-items: center; |
| | | } |
| | | |
| | | /* æç´¢æ */ |
| | | .search-bar { |
| | | background: #fff; |
| | | border-radius: 8px; |
| | | padding: 16px; |
| | | box-shadow: 0 2px 4px rgba(0, 0, 0, 0.08); |
| | | margin-bottom: 20px; |
| | | display: flex; |
| | | align-items: center; |
| | | gap: 12px; |
| | | flex-wrap: wrap; |
| | | } |
| | | |
| | | /* å¿«æ·æä½æé® */ |
| | | .quick-actions { |
| | | display: flex; |
| | | align-items: center; |
| | | gap: 8px; |
| | | flex-wrap: wrap; |
| | | } |
| | | |
| | | .action-icon { |
| | | margin-right: 4px; |
| | | } |
| | | |
| | | /* ç»è®¡å¡ç */ |
| | | .stats-container { |
| | | display: grid; |
| | | grid-template-columns: repeat(5, 1fr); |
| | | gap: 16px; |
| | | margin-bottom: 20px; |
| | | } |
| | | |
| | | .stat-card { |
| | | background: #fff; |
| | | border-radius: 8px; |
| | | padding: 16px; |
| | | box-shadow: 0 2px 4px rgba(0, 0, 0, 0.08); |
| | | position: relative; |
| | | overflow: hidden; |
| | | display: flex; |
| | | flex-direction: column; |
| | | } |
| | | |
| | | .stat-card::before { |
| | | content: ''; |
| | | position: absolute; |
| | | top: 0; |
| | | left: 0; |
| | | right: 0; |
| | | height: 4px; |
| | | } |
| | | |
| | | .stat-card.total::before { |
| | | background: #3b82f6; |
| | | } |
| | | |
| | | .stat-card.normal::before { |
| | | background: #10b981; |
| | | } |
| | | |
| | | .stat-card.warning::before { |
| | | background: #f59e0b; |
| | | } |
| | | |
| | | .stat-card.overdue::before { |
| | | background: #ef4444; |
| | | } |
| | | |
| | | .stat-card.inactive::before { |
| | | background: #6b7280; |
| | | } |
| | | |
| | | .stat-header { |
| | | display: flex; |
| | | align-items: center; |
| | | gap: 8px; |
| | | margin-bottom: 8px; |
| | | } |
| | | |
| | | .stat-icon { |
| | | font-size: 15px; |
| | | } |
| | | |
| | | .stat-card .stat-title { |
| | | font-size: 14px; |
| | | color: #666; |
| | | margin: 0; |
| | | } |
| | | |
| | | .stat-card .stat-value { |
| | | font-size: 28px; |
| | | font-weight: 700; |
| | | color: #333; |
| | | margin-bottom: 4px; |
| | | } |
| | | |
| | | .stat-card .stat-sub { |
| | | font-size: 12px; |
| | | color: #999; |
| | | } |
| | | |
| | | .search-input { |
| | | width: 250px; |
| | | } |
| | | |
| | | /* æ¹éæä½æ */ |
| | | .batch-bar { |
| | | background: #e6f7ff; |
| | | border: 1px solid #91d5ff; |
| | | border-radius: 8px; |
| | | padding: 12px 16px; |
| | | margin-bottom: 20px; |
| | | display: flex; |
| | | align-items: center; |
| | | justify-content: space-between; |
| | | } |
| | | |
| | | .batch-info { |
| | | font-size: 14px; |
| | | color: #1890ff; |
| | | font-weight: 500; |
| | | } |
| | | |
| | | .batch-actions { |
| | | display: flex; |
| | | gap: 8px; |
| | | } |
| | | |
| | | /* è¡¨æ ¼å®¹å¨ */ |
| | | .table-container { |
| | | background: #fff; |
| | | border-radius: 8px; |
| | | box-shadow: 0 2px 4px rgba(0, 0, 0, 0.08); |
| | | overflow: hidden; |
| | | } |
| | | |
| | | :deep(.n-data-table) { |
| | | border-radius: 8px; |
| | | overflow: hidden; |
| | | } |
| | | |
| | | :deep(.n-data-table th) { |
| | | background: #f8f9fa; |
| | | font-size: 14px; |
| | | font-weight: 600; |
| | | color: #666; |
| | | text-align: left; |
| | | padding: 12px 16px; |
| | | border-bottom: 1px solid #e5e7eb; |
| | | } |
| | | |
| | | :deep(.n-data-table td) { |
| | | padding: 12px 16px; |
| | | font-size: 14px; |
| | | color: #333; |
| | | border-bottom: 1px solid #f0f0f0; |
| | | vertical-align: middle; |
| | | } |
| | | |
| | | :deep(.n-data-table tr:hover) { |
| | | background: #fafbfc; |
| | | } |
| | | |
| | | :deep(.n-data-table tr.n-data-table__row--selected) { |
| | | background: #e6f7ff; |
| | | } |
| | | |
| | | :deep(.n-data-table .n-data-table__pagination) { |
| | | padding: 16px; |
| | | border-top: 1px solid #f0f0f0; |
| | | } |
| | | |
| | | /* ç¶ææ ç¾ */ |
| | | .status-tag { |
| | | display: inline-flex; |
| | | align-items: center; |
| | | justify-content: center; |
| | | padding: 2px 8px; |
| | | border-radius: 12px; |
| | | font-size: 12px; |
| | | font-weight: 500; |
| | | } |
| | | |
| | | .status-tag.active { |
| | | background: #f6ffed; |
| | | color: #52c41a; |
| | | border: 1px solid #b7eb8f; |
| | | } |
| | | |
| | | .status-tag.inactive { |
| | | background: #f5f5f5; |
| | | color: #999; |
| | | border: 1px solid #d9d9d9; |
| | | } |
| | | |
| | | /* æ ¡åç¶ææ ç¾ */ |
| | | .calib-badge { |
| | | display: inline-flex; |
| | | align-items: center; |
| | | gap: 4px; |
| | | padding: 4px 12px; |
| | | border-radius: 12px; |
| | | font-size: 12px; |
| | | font-weight: 500; |
| | | } |
| | | |
| | | .calib-badge.normal { |
| | | background: #f6ffed; |
| | | color: #52c41a; |
| | | border: 1px solid #b7eb8f; |
| | | } |
| | | |
| | | .calib-badge.warning { |
| | | background: #fffbe6; |
| | | color: #d48806; |
| | | border: 1px solid #ffe58f; |
| | | } |
| | | |
| | | .calib-badge.overdue { |
| | | background: #fff2f0; |
| | | color: #ff4d4f; |
| | | border: 1px solid #ffccc7; |
| | | } |
| | | |
| | | .calib-badge.unset { |
| | | background: #f5f5f5; |
| | | color: #999; |
| | | border: 1px solid #d9d9d9; |
| | | } |
| | | |
| | | .calib-badge .days-left { |
| | | font-size: 11px; |
| | | opacity: 0.8; |
| | | } |
| | | |
| | | /* æä½æé® */ |
| | | .actions { |
| | | display: flex; |
| | | gap: 4px; |
| | | } |
| | | |
| | | .action-btn { |
| | | width: 32px; |
| | | height: 32px; |
| | | border-radius: 50%; |
| | | border: 1px solid #e5e7eb; |
| | | background: #fff; |
| | | cursor: pointer; |
| | | display: flex; |
| | | align-items: center; |
| | | justify-content: center; |
| | | transition: all 0.2s ease; |
| | | } |
| | | |
| | | .action-btn:hover { |
| | | background: #f5f5f5; |
| | | border-color: #1890ff; |
| | | } |
| | | |
| | | .action-btn.delete:hover { |
| | | background: #fff2f0; |
| | | border-color: #ff4d4f; |
| | | } |
| | | |
| | | .action-btn .icon { |
| | | font-size: 14px; |
| | | } |
| | | |
| | | .flex-center { |
| | | display: flex; |
| | | align-items: center; |
| | | justify-content: center; |
| | | } |
| | | |
| | | .gap-8px { |
| | | gap: 8px; |
| | | } |
| | | |
| | | /* ååºå¼è°æ´ */ |
| | | @media (max-width: 1200px) { |
| | | .stats-container { |
| | | grid-template-columns: repeat(3, 1fr); |
| | | } |
| | | } |
| | | |
| | | @media (max-width: 768px) { |
| | | .stats-container { |
| | | grid-template-columns: repeat(2, 1fr); |
| | | } |
| | | |
| | | .search-bar { |
| | | flex-direction: column; |
| | | align-items: stretch; |
| | | } |
| | | |
| | | .quick-actions { |
| | | flex-direction: column; |
| | | align-items: stretch; |
| | | } |
| | | |
| | | .page-header { |
| | | flex-direction: column; |
| | | align-items: stretch; |
| | | } |
| | | |
| | | .header-actions { |
| | | margin-top: 12px; |
| | | } |
| | | } |
| | | |
| | | /* æ¨¡ææ¡æ ·å¼ */ |
| | | :deep(.n-modal-card) { |
| | | border-radius: 12px; |
| | | overflow: hidden; |
| | | box-shadow: 0 8px 24px rgba(0, 0, 0, 0.15); |
| | | } |
| | | |
| | | :deep(.n-modal-card .n-modal-card__header) { |
| | | padding: 16px 20px; |
| | | border-bottom: 1px solid #e5e7eb; |
| | | display: flex; |
| | | align-items: center; |
| | | justify-content: space-between; |
| | | } |
| | | |
| | | :deep(.n-modal-card .n-modal-card__header-title) { |
| | | font-size: 16px; |
| | | font-weight: 600; |
| | | color: #333; |
| | | } |
| | | |
| | | :deep(.n-modal-card .n-modal-card__body) { |
| | | padding: 20px; |
| | | overflow-y: auto; |
| | | flex: 1; |
| | | } |
| | | |
| | | :deep(.n-modal-card .n-modal-card__footer) { |
| | | padding: 16px 20px; |
| | | border-top: 1px solid #e5e7eb; |
| | | display: flex; |
| | | justify-content: flex-end; |
| | | gap: 10px; |
| | | } |
| | | |
| | | /* è¡¨åæ ·å¼ */ |
| | | .form-grid { |
| | | display: grid; |
| | | grid-template-columns: 1fr 1fr; |
| | | |
| | | } |
| | | |
| | | .form-group { |
| | | display: flex; |
| | | flex-direction: column; |
| | | } |
| | | |
| | | .form-group.full { |
| | | grid-column: 1 / -1; |
| | | } |
| | | |
| | | .form-label { |
| | | font-size: 14px; |
| | | font-weight: 500; |
| | | color: #333; |
| | | margin-bottom: 8px; |
| | | } |
| | | |
| | | .form-label .required { |
| | | color: #ff4d4f; |
| | | margin-left: 4px; |
| | | } |
| | | |
| | | .form-row { |
| | | display: flex; |
| | | gap: 10px; |
| | | align-items: center; |
| | | } |
| | | |
| | | .form-hint { |
| | | font-size: 12px; |
| | | color: #999; |
| | | margin-top: 4px; |
| | | } |
| | | |
| | | .form-divider { |
| | | grid-column: 1 / -1; |
| | | border: none; |
| | | border-top: 1px solid #e5e7eb; |
| | | margin: 12px 0; |
| | | } |
| | | |
| | | .form-section-title { |
| | | grid-column: 1 / -1; |
| | | font-size: 14px; |
| | | font-weight: 600; |
| | | color: #1890ff; |
| | | margin: 12px 0 8px; |
| | | } |
| | | |
| | | /* é¢è®¾æ¨¡æ¿ */ |
| | | .preset-bar { |
| | | grid-column: 1 / -1; |
| | | display: flex; |
| | | align-items: center; |
| | | gap: 8px; |
| | | flex-wrap: wrap; |
| | | margin-bottom: 12px; |
| | | } |
| | | |
| | | .preset-chip { |
| | | padding: 6px 16px; |
| | | border-radius: 20px; |
| | | border: 1px solid #e5e7eb; |
| | | font-size: 14px; |
| | | color: #666; |
| | | cursor: pointer; |
| | | transition: all 0.2s ease; |
| | | background: #fff; |
| | | } |
| | | |
| | | .preset-chip:hover { |
| | | border-color: #1890ff; |
| | | color: #1890ff; |
| | | } |
| | | |
| | | .preset-chip.active { |
| | | background: #1890ff; |
| | | color: #fff; |
| | | border-color: #1890ff; |
| | | } |
| | | |
| | | /* é
ç½®æ¨¡æ¿ */ |
| | | .config-template-grid { |
| | | display: grid; |
| | | grid-template-columns: repeat(3, 1fr); |
| | | gap: 12px; |
| | | margin-bottom: 20px; |
| | | } |
| | | |
| | | .template-card { |
| | | border: 2px solid #e5e7eb; |
| | | border-radius: 8px; |
| | | padding: 16px; |
| | | cursor: pointer; |
| | | transition: all 0.2s ease; |
| | | text-align: center; |
| | | background: #fff; |
| | | } |
| | | |
| | | .template-card:hover { |
| | | border-color: #91d5ff; |
| | | box-shadow: 0 2px 8px rgba(24, 144, 255, 0.1); |
| | | } |
| | | |
| | | .template-card.selected { |
| | | border-color: #1890ff; |
| | | box-shadow: 0 2px 8px rgba(24, 144, 255, 0.2); |
| | | background: #e6f7ff; |
| | | } |
| | | |
| | | .template-card .t-icon { |
| | | font-size: 24px; |
| | | margin-bottom: 8px; |
| | | } |
| | | |
| | | .template-card .t-name { |
| | | font-size: 14px; |
| | | font-weight: 600; |
| | | color: #333; |
| | | margin-bottom: 4px; |
| | | } |
| | | |
| | | .template-card .t-desc { |
| | | font-size: 12px; |
| | | color: #666; |
| | | } |
| | | |
| | | .template-card .t-custom-input { |
| | | width: 80px; |
| | | margin-top: 8px; |
| | | } |
| | | |
| | | /* åºç¨èå´ */ |
| | | .apply-target { |
| | | margin-top: 16px; |
| | | } |
| | | |
| | | .radio-group { |
| | | display: flex; |
| | | gap: 20px; |
| | | margin-top: 8px; |
| | | flex-wrap: wrap; |
| | | } |
| | | |
| | | .radio-item { |
| | | display: flex; |
| | | align-items: center; |
| | | gap: 8px; |
| | | font-size: 14px; |
| | | color: #333; |
| | | cursor: pointer; |
| | | } |
| | | |
| | | /* æ¹éæ ¡åå表 */ |
| | | .batch-cali-list { |
| | | max-height: 300px; |
| | | overflow-y: auto; |
| | | border: 1px solid #e5e7eb; |
| | | border-radius: 8px; |
| | | margin: 12px 0; |
| | | } |
| | | |
| | | .batch-cali-item { |
| | | display: flex; |
| | | align-items: center; |
| | | padding: 12px 16px; |
| | | border-bottom: 1px solid #f0f0f0; |
| | | font-size: 14px; |
| | | } |
| | | |
| | | .batch-cali-item:last-child { |
| | | border-bottom: none; |
| | | } |
| | | |
| | | .batch-cali-item .box-info { |
| | | flex: 1; |
| | | } |
| | | |
| | | .batch-cali-item .box-name { |
| | | font-weight: 500; |
| | | color: #333; |
| | | margin-bottom: 4px; |
| | | } |
| | | |
| | | .batch-cali-item .box-meta { |
| | | font-size: 12px; |
| | | color: #666; |
| | | } |
| | | |
| | | .batch-cali-summary { |
| | | background: #f8f9fa; |
| | | border-radius: 8px; |
| | | padding: 12px 16px; |
| | | margin-top: 12px; |
| | | font-size: 14px; |
| | | color: #666; |
| | | } |
| | | |
| | | /* æ ¡åä¿¡æ¯ */ |
| | | .calib-info { |
| | | background: #f8f9fa; |
| | | border-radius: 8px; |
| | | padding: 16px; |
| | | margin-bottom: 20px; |
| | | border: 1px solid #e5e7eb; |
| | | } |
| | | |
| | | .calib-info .info-row { |
| | | display: flex; |
| | | justify-content: space-between; |
| | | padding: 8px 0; |
| | | font-size: 14px; |
| | | border-bottom: 1px solid #e5e7eb; |
| | | } |
| | | |
| | | .calib-info .info-row:last-child { |
| | | border-bottom: none; |
| | | } |
| | | |
| | | .calib-info .info-label { |
| | | color: #666; |
| | | font-weight: 500; |
| | | } |
| | | |
| | | .calib-info .info-value { |
| | | color: #333; |
| | | } |
| | | |
| | | /* æ¨¡ææ¡åºé¨ */ |
| | | .modal-footer { |
| | | width: 100%; |
| | | display: flex; |
| | | justify-content: space-between; |
| | | align-items: center; |
| | | } |
| | | |
| | | .modal-hint { |
| | | font-size: 12px; |
| | | color: #999; |
| | | } |
| | | |
| | | .modal-footer-right { |
| | | display: flex; |
| | | gap: 10px; |
| | | } |
| | | |
| | | /* å¼å
³ */ |
| | | .switch-wrap { |
| | | display: flex; |
| | | align-items: center; |
| | | gap: 10px; |
| | | } |
| | | |
| | | .switch-label { |
| | | font-size: 14px; |
| | | color: #666; |
| | | } |
| | | .action-tag { |
| | | font-size: 12px; |
| | | font-weight: 500; |
| | | margin-left: 8px; |
| | | padding: 2px 8px; |
| | | border-radius: 15px; |
| | | } |
| | | </style> |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | <template> |
| | | <n-modal |
| | | :show="visible" |
| | | @update:show="(value) => emit('update:visible', value)" |
| | | title="ð§ ä¸é®æ¹éæ ¡å" |
| | | preset="card" |
| | | size="large" |
| | | :style="bodyStyle" |
| | | > |
| | | <n-grid :cols="2" :x-gap="12" :y-gap="12"> |
| | | <n-form-item-gi label="æ ¡åæ¥æ" required> |
| | | <n-date-picker v-model:value="form.calibDate" type="date" /> |
| | | </n-form-item-gi> |
| | | <n-form-item-gi label="ç»ä¸å¤æ³¨"> |
| | | <n-input v-model:value="form.note" placeholder="æ¹éæ ¡å夿³¨ï¼å¯éï¼" /> |
| | | </n-form-item-gi> |
| | | </n-grid> |
| | | <div> |
| | | <div style="display: flex; align-items: center; justify-content: space-between; margin-bottom: 8px;"> |
| | | <label class="form-label">éæ©ççå</label> |
| | | <n-checkbox v-model:checked="form.useStandardWeight"> |
| | | å
¨é¨å¡«å
¥æ åéé |
| | | </n-checkbox> |
| | | </div> |
| | | <div class="batch-cali-list"> |
| | | <div v-for="box in selectedRows" :key="box.id" class="batch-cali-item"> |
| | | <div class="box-info"> |
| | | <div class="box-name">{{ box.location }} · {{ box.name }} · {{ box.code }}</div> |
| | | </div> |
| | | <div style="display: flex; align-items: center;"> |
| | | <n-input |
| | | v-model:value="box.actualWeight" |
| | | type="number" |
| | | placeholder="è¾å
¥å®é
éé" |
| | | style="width: 120px;" |
| | | /> |
| | | <span style="margin-left: 8px; color: #666;">{{ box.unit || 'g' }}</span> |
| | | </div> |
| | | </div> |
| | | </div> |
| | | <div class="batch-cali-summary"> |
| | | <span>å
± <strong>{{ selectedRows.length }}</strong> 个çå</span> |
| | | </div> |
| | | </div> |
| | | <template #footer> |
| | | <div class="modal-footer"> |
| | | <span class="modal-hint">æ ¡ååèªå¨æ´æ°ä¸æ¬¡æ ¡åæ¥æ</span> |
| | | <div class="modal-footer-right"> |
| | | <n-button @click="handleCancel">åæ¶</n-button> |
| | | <n-button type="success" @click="handleSubmit">â
确认å
¨é¨æ ¡å</n-button> |
| | | </div> |
| | | </div> |
| | | </template> |
| | | </n-modal> |
| | | </template> |
| | | |
| | | <script setup lang="ts"> |
| | | import { ref, reactive, watch } from 'vue'; |
| | | import { weighingBoxApi } from '@/service/api/md/weighing-box'; |
| | | import { useMessage } from 'naive-ui'; |
| | | |
| | | const props = defineProps({ |
| | | visible: { |
| | | type: Boolean, |
| | | default: false |
| | | }, |
| | | selectedRows: { |
| | | type: Array, |
| | | default: () => [] |
| | | } |
| | | }); |
| | | |
| | | const emit = defineEmits(['update:visible', 'submitted']); |
| | | |
| | | const message = useMessage(); |
| | | |
| | | const form = reactive({ |
| | | calibDate: null, |
| | | note: '', |
| | | useStandardWeight: false |
| | | }); |
| | | |
| | | const bodyStyle = { |
| | | width: '700px' |
| | | }; |
| | | |
| | | watch(() => props.visible, (newValue) => { |
| | | if (newValue) { |
| | | form.calibDate = new Date(); |
| | | form.note = ''; |
| | | form.useStandardWeight = false; |
| | | // 为æ¯ä¸ªéä¸ççåæ·»å actualWeight 屿§ |
| | | props.selectedRows.forEach(box => { |
| | | box.actualWeight = ''; |
| | | }); |
| | | } |
| | | }); |
| | | |
| | | // çå¬ useStandardWeight åå |
| | | watch(() => form.useStandardWeight, (value) => { |
| | | if (value) { |
| | | props.selectedRows.forEach(box => { |
| | | box.actualWeight = box.weight; |
| | | }); |
| | | } |
| | | }); |
| | | |
| | | const handleCancel = () => { |
| | | emit('update:visible', false); |
| | | }; |
| | | |
| | | const handleSubmit = async () => { |
| | | try { |
| | | const items = props.selectedRows.map(row => ({ |
| | | boxId: row.id, |
| | | actualWeight: row.actualWeight |
| | | })); |
| | | |
| | | const response = await weighingBoxApi.batchCalibrate({ |
| | | boxIds: props.selectedRows.map(row => row.id), |
| | | calibDate: form.calibDate, |
| | | note: form.note, |
| | | items: items |
| | | }); |
| | | const res = response.response.data; |
| | | |
| | | if (res.code === 200) { |
| | | message.success('æ¹éæ ¡åæå'); |
| | | emit('update:visible', false); |
| | | emit('submitted'); |
| | | } else { |
| | | message.error(res.msg || 'æ¹éæ ¡å失败'); |
| | | } |
| | | } catch (error) { |
| | | message.error('æ¹éæ ¡å失败'); |
| | | } |
| | | }; |
| | | </script> |
| | | |
| | | <style scoped> |
| | | /* è¡¨åæ ·å¼ */ |
| | | .form-label { |
| | | font-size: 14px; |
| | | font-weight: 500; |
| | | color: #333; |
| | | margin-bottom: 8px; |
| | | } |
| | | |
| | | /* æ¹éæ ¡åå表 */ |
| | | .batch-cali-list { |
| | | max-height: 300px; |
| | | overflow-y: auto; |
| | | border: 1px solid #e5e7eb; |
| | | border-radius: 8px; |
| | | margin: 12px 0; |
| | | } |
| | | |
| | | .batch-cali-item { |
| | | display: flex; |
| | | align-items: center; |
| | | padding: 12px 16px; |
| | | border-bottom: 1px solid #f0f0f0; |
| | | font-size: 14px; |
| | | } |
| | | |
| | | .batch-cali-item:last-child { |
| | | border-bottom: none; |
| | | } |
| | | |
| | | .batch-cali-item .box-info { |
| | | flex: 1; |
| | | } |
| | | |
| | | .batch-cali-item .box-name { |
| | | color: #333; |
| | | margin-bottom: 4px; |
| | | } |
| | | |
| | | .batch-cali-item .box-meta { |
| | | font-size: 12px; |
| | | color: #666; |
| | | } |
| | | |
| | | .batch-cali-summary { |
| | | background: #f8f9fa; |
| | | border-radius: 8px; |
| | | padding: 12px 16px; |
| | | margin-top: 12px; |
| | | font-size: 14px; |
| | | color: #666; |
| | | } |
| | | |
| | | /* æ¨¡ææ¡åºé¨ */ |
| | | .modal-footer { |
| | | width: 100%; |
| | | display: flex; |
| | | justify-content: space-between; |
| | | align-items: center; |
| | | } |
| | | |
| | | .modal-hint { |
| | | font-size: 12px; |
| | | color: #999; |
| | | } |
| | | |
| | | .modal-footer-right { |
| | | display: flex; |
| | | gap: 10px; |
| | | } |
| | | </style> |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | <template> |
| | | <n-modal |
| | | :show="visible" |
| | | @update:show="(value) => emit('update:visible', value)" |
| | | title="ð ç»ä¸é
ç½®æ ¡å卿" |
| | | preset="card" |
| | | size="medium" |
| | | :style="bodyStyle" |
| | | > |
| | | <label class="form-label">éæ©å¨ææ¨¡æ¿</label> |
| | | <div class="config-template-grid"> |
| | | <div class="template-card" :class="{ selected: selectedTemplate === 7 }" @click="selectTemplate(7)"> |
| | | <div class="t-icon">â¡</div> |
| | | <div class="t-name">é«é¢</div> |
| | | <div class="t-desc">æ¯ 7 å¤©æ ¡å</div> |
| | | </div> |
| | | <div class="template-card" :class="{ selected: selectedTemplate === 14 }" @click="selectTemplate(14)"> |
| | | <div class="t-icon">ð</div> |
| | | <div class="t-name">æ å</div> |
| | | <div class="t-desc">æ¯ 14 å¤©æ ¡å</div> |
| | | </div> |
| | | <div class="template-card" :class="{ selected: selectedTemplate === 30 }" @click="selectTemplate(30)"> |
| | | <div class="t-icon">ð
</div> |
| | | <div class="t-name">常è§</div> |
| | | <div class="t-desc">æ¯ 30 å¤©æ ¡å</div> |
| | | </div> |
| | | <div class="template-card" :class="{ selected: selectedTemplate === 90 }" @click="selectTemplate(90)"> |
| | | <div class="t-icon">ð</div> |
| | | <div class="t-name">å£åº¦</div> |
| | | <div class="t-desc">æ¯ 90 å¤©æ ¡å</div> |
| | | </div> |
| | | <div class="template-card" :class="{ selected: selectedTemplate === 360 }" @click="selectTemplate(360)"> |
| | | <div class="t-icon">ðï¸</div> |
| | | <div class="t-name">年度</div> |
| | | <div class="t-desc">æ¯ 360 å¤©æ ¡å</div> |
| | | </div> |
| | | <div class="template-card" :class="{ selected: selectedTemplate === 'custom' }" @click="selectTemplate('custom')"> |
| | | <div class="t-icon">âï¸</div> |
| | | <div class="t-name">èªå®ä¹</div> |
| | | <n-input-number v-model:value="customDays" placeholder="天æ°" class="t-custom-input" /> |
| | | </div> |
| | | </div> |
| | | |
| | | <div class="form-group" style="margin-top: 16px;"> |
| | | <label class="form-label">æåæé天æ°</label> |
| | | <div class="form-row"> |
| | | <n-input-number v-model:value="form.remindDays" style="max-width: 100px;" /> |
| | | <span class="form-unit">天ï¼åºç¨å°ææéä¸çåï¼</span> |
| | | </div> |
| | | </div> |
| | | |
| | | <div class="apply-target" style="margin-top: 16px;"> |
| | | <label class="form-label">åºç¨èå´</label> |
| | | <div class="radio-group"> |
| | | <label class="radio-item" @click="selectApplyScope('all')"> |
| | | <n-radio :checked="form.applyScope === 'all'" name="applyScope" value="all" /> |
| | | å
¨é¨å·²å¯ç¨çå |
| | | </label> |
| | | <label class="radio-item" @click="selectApplyScope('selected')"> |
| | | <n-radio :checked="form.applyScope === 'selected'" name="applyScope" value="selected" /> |
| | | ä»
éä¸é¡¹ï¼{{ selectedRows.length }}ä¸ªï¼ |
| | | </label> |
| | | </div> |
| | | </div> |
| | | <template #footer> |
| | | <div class="modal-footer"> |
| | | <span class="modal-hint">å°è¦çéä¸çåçæ ¡å卿åæé天æ°</span> |
| | | <div class="modal-footer-right"> |
| | | <n-button @click="handleCancel">åæ¶</n-button> |
| | | <n-button type="primary" @click="handleSubmit">â
åºç¨é
ç½®</n-button> |
| | | </div> |
| | | </div> |
| | | </template> |
| | | </n-modal> |
| | | </template> |
| | | |
| | | <script setup lang="ts"> |
| | | import { ref, reactive, watch } from 'vue'; |
| | | import { weighingBoxApi } from '@/service/api/md/weighing-box'; |
| | | import { useMessage } from 'naive-ui'; |
| | | |
| | | const props = defineProps({ |
| | | visible: { |
| | | type: Boolean, |
| | | default: false |
| | | }, |
| | | selectedRows: { |
| | | type: Array, |
| | | default: () => [] |
| | | }, |
| | | type: { |
| | | type: String, |
| | | default: 'all' |
| | | } |
| | | }); |
| | | |
| | | const emit = defineEmits(['update:visible', 'submitted']); |
| | | |
| | | const message = useMessage(); |
| | | |
| | | const form = reactive({ |
| | | calibCycleDays: 30, |
| | | remindDays: 7, |
| | | applyScope: props.type |
| | | }); |
| | | |
| | | const customDays = ref(30); |
| | | const selectedTemplate = ref(30); |
| | | |
| | | const bodyStyle = { |
| | | width: '600px' |
| | | }; |
| | | |
| | | watch(() => props.visible, (newValue) => { |
| | | if (newValue) { |
| | | form.calibCycleDays = 30; |
| | | form.remindDays = 7; |
| | | form.applyScope = props.type; |
| | | customDays.value = 30; |
| | | selectedTemplate.value = 30; |
| | | } |
| | | }); |
| | | |
| | | const selectTemplate = (days) => { |
| | | selectedTemplate.value = days; |
| | | if (days === 'custom') { |
| | | form.calibCycleDays = customDays.value; |
| | | } else { |
| | | form.calibCycleDays = days; |
| | | } |
| | | }; |
| | | |
| | | const selectApplyScope = (scope) => { |
| | | form.applyScope = scope; |
| | | }; |
| | | |
| | | const handleCancel = () => { |
| | | emit('update:visible', false); |
| | | }; |
| | | |
| | | const handleSubmit = async () => { |
| | | try { |
| | | const response = await weighingBoxApi.batchConfig({ |
| | | calibCycleDays: form.calibCycleDays, |
| | | remindDays: form.remindDays, |
| | | applyScope: form.applyScope, |
| | | boxIds: props.selectedRows.map(row => row.id) |
| | | }); |
| | | const res = response.response.data; |
| | | |
| | | if (res.code === 200) { |
| | | message.success('æ¹éé
ç½®æå'); |
| | | emit('update:visible', false); |
| | | emit('submitted'); |
| | | } else { |
| | | message.error(res.msg || 'æ¹éé
置失败'); |
| | | } |
| | | } catch (error) { |
| | | message.error('æ¹éé
置失败'); |
| | | } |
| | | }; |
| | | </script> |
| | | |
| | | <style scoped> |
| | | /* è¡¨åæ ·å¼ */ |
| | | .form-label { |
| | | font-size: 14px; |
| | | font-weight: 500; |
| | | color: #333; |
| | | margin-bottom: 8px; |
| | | } |
| | | |
| | | .form-row { |
| | | display: flex; |
| | | gap: 10px; |
| | | align-items: center; |
| | | } |
| | | |
| | | .form-unit { |
| | | font-size: 14px; |
| | | color: #666; |
| | | } |
| | | |
| | | /* é
ç½®æ¨¡æ¿ */ |
| | | .config-template-grid { |
| | | display: grid; |
| | | grid-template-columns: repeat(3, 1fr); |
| | | gap: 12px; |
| | | margin-bottom: 20px; |
| | | } |
| | | |
| | | .template-card { |
| | | border: 2px solid #e5e7eb; |
| | | border-radius: 8px; |
| | | padding: 16px; |
| | | cursor: pointer; |
| | | transition: all 0.2s ease; |
| | | text-align: center; |
| | | background: #fff; |
| | | } |
| | | |
| | | .template-card:hover { |
| | | border-color: #91d5ff; |
| | | box-shadow: 0 2px 8px rgba(24, 144, 255, 0.1); |
| | | } |
| | | |
| | | .template-card.selected { |
| | | border-color: #1890ff; |
| | | box-shadow: 0 2px 8px rgba(24, 144, 255, 0.2); |
| | | background: #e6f7ff; |
| | | } |
| | | |
| | | .template-card .t-icon { |
| | | font-size: 24px; |
| | | margin-bottom: 8px; |
| | | } |
| | | |
| | | .template-card .t-name { |
| | | font-size: 14px; |
| | | font-weight: 600; |
| | | color: #333; |
| | | margin-bottom: 4px; |
| | | } |
| | | |
| | | .template-card .t-desc { |
| | | font-size: 12px; |
| | | color: #666; |
| | | } |
| | | |
| | | .template-card .t-custom-input { |
| | | margin-top: 8px; |
| | | } |
| | | |
| | | /* åºç¨èå´ */ |
| | | .apply-target { |
| | | margin-top: 16px; |
| | | } |
| | | |
| | | .radio-group { |
| | | display: flex; |
| | | gap: 20px; |
| | | margin-top: 8px; |
| | | flex-wrap: wrap; |
| | | } |
| | | |
| | | .radio-item { |
| | | display: flex; |
| | | align-items: center; |
| | | gap: 8px; |
| | | font-size: 14px; |
| | | color: #333; |
| | | cursor: pointer; |
| | | } |
| | | |
| | | /* æ¨¡ææ¡åºé¨ */ |
| | | .modal-footer { |
| | | width: 100%; |
| | | display: flex; |
| | | justify-content: space-between; |
| | | align-items: center; |
| | | } |
| | | |
| | | .modal-hint { |
| | | font-size: 12px; |
| | | color: #999; |
| | | } |
| | | |
| | | .modal-footer-right { |
| | | display: flex; |
| | | gap: 10px; |
| | | } |
| | | </style> |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | <template> |
| | | <n-modal |
| | | :show="visible" |
| | | @update:show="(value) => emit('update:visible', value)" |
| | | title="ð§ å®ææ ¡å" |
| | | preset="card" |
| | | size="medium" |
| | | :style="bodyStyle" |
| | | > |
| | | <div class="calib-info"> |
| | | <div class="info-row"> |
| | | <span class="info-label">çååç§°</span> |
| | | <span class="info-value">{{ form.name }}</span> |
| | | </div> |
| | | <div class="info-row"> |
| | | <span class="info-label">çåç¼å·</span> |
| | | <span class="info-value">{{ form.code }}</span> |
| | | </div> |
| | | <div class="info-row"> |
| | | <span class="info-label">æå¨ä½ç½®</span> |
| | | <span class="info-value">{{ form.location }}</span> |
| | | </div> |
| | | </div> |
| | | <div class="form-group"> |
| | | <label class="form-label">æ¬æ¬¡æ ¡åæ¥æ <span class="required">*</span></label> |
| | | <n-date-picker v-model:value="form.calibDate" type="date" /> |
| | | </div> |
| | | <div class="form-group"> |
| | | <label class="form-label">æ¬æ¬¡å®æµééï¼å¯éï¼</label> |
| | | <div class="form-row"> |
| | | <n-input-number v-model:value="form.actualWeight" placeholder="宿µéé" /> |
| | | <span style="color: #666;">{{ props.rowData.unit || 'g' }}</span> |
| | | </div> |
| | | </div> |
| | | <div class="form-group"> |
| | | <label class="form-label">æ ¡å夿³¨</label> |
| | | <n-input v-model:value="form.note" type="textarea" placeholder="æ ¡åç»æãå差说æçâ¦" /> |
| | | </div> |
| | | <template #footer> |
| | | <div class="modal-footer"> |
| | | <div class="modal-footer-right"> |
| | | <n-button @click="handleCancel">åæ¶</n-button> |
| | | <n-button type="success" @click="handleSubmit">â
ç¡®è®¤æ ¡å宿</n-button> |
| | | </div> |
| | | </div> |
| | | </template> |
| | | </n-modal> |
| | | </template> |
| | | |
| | | <script setup lang="ts"> |
| | | import { ref, reactive, watch } from 'vue'; |
| | | import { weighingBoxApi } from '@/service/api/md/weighing-box'; |
| | | import { useMessage } from 'naive-ui'; |
| | | |
| | | const props = defineProps({ |
| | | visible: { |
| | | type: Boolean, |
| | | default: false |
| | | }, |
| | | rowData: { |
| | | type: Object, |
| | | default: () => ({}) |
| | | } |
| | | }); |
| | | |
| | | const emit = defineEmits(['update:visible', 'submitted']); |
| | | |
| | | const message = useMessage(); |
| | | |
| | | const form = reactive({ |
| | | id: undefined, |
| | | name: '', |
| | | code: '', |
| | | weight: '', |
| | | calibDate: null, |
| | | actualWeight: null, |
| | | note: '' |
| | | }); |
| | | |
| | | const bodyStyle = { |
| | | width: '500px' |
| | | }; |
| | | |
| | | watch(() => props.visible, (newValue) => { |
| | | if (newValue && props.rowData) { |
| | | form.id = props.rowData.id; |
| | | form.name = props.rowData.name; |
| | | form.code = props.rowData.code; |
| | | form.weight = props.rowData.weight; |
| | | form.calibDate = new Date(); |
| | | form.actualWeight = null; |
| | | form.note = ''; |
| | | } |
| | | }); |
| | | |
| | | const handleCancel = () => { |
| | | emit('update:visible', false); |
| | | }; |
| | | |
| | | const handleSubmit = async () => { |
| | | try { |
| | | const response = await weighingBoxApi.calibrate({ |
| | | boxId: form.id, |
| | | calibDate: form.calibDate, |
| | | actualWeight: form.actualWeight, |
| | | note: form.note |
| | | }); |
| | | const res = response.response.data; |
| | | |
| | | if (res.code === 200) { |
| | | message.success('æ ¡åæå'); |
| | | emit('update:visible', false); |
| | | emit('submitted'); |
| | | } else { |
| | | message.error(res.msg || 'æ ¡å失败'); |
| | | } |
| | | } catch (error) { |
| | | message.error('æ ¡å失败'); |
| | | } |
| | | }; |
| | | </script> |
| | | |
| | | <style scoped> |
| | | /* æ ¡åä¿¡æ¯ */ |
| | | .calib-info { |
| | | background: #f8f9fa; |
| | | border-radius: 8px; |
| | | padding: 16px; |
| | | margin-bottom: 20px; |
| | | border: 1px solid #e5e7eb; |
| | | } |
| | | |
| | | .calib-info .info-row { |
| | | display: flex; |
| | | justify-content: space-between; |
| | | padding: 8px 0; |
| | | font-size: 14px; |
| | | border-bottom: 1px solid #e5e7eb; |
| | | } |
| | | |
| | | .calib-info .info-row:last-child { |
| | | border-bottom: none; |
| | | } |
| | | |
| | | .calib-info .info-label { |
| | | color: #666; |
| | | font-weight: 500; |
| | | } |
| | | |
| | | .calib-info .info-value { |
| | | color: #333; |
| | | } |
| | | |
| | | /* è¡¨åæ ·å¼ */ |
| | | .form-group { |
| | | display: flex; |
| | | flex-direction: column; |
| | | margin-bottom: 16px; |
| | | } |
| | | |
| | | .form-label { |
| | | font-size: 14px; |
| | | font-weight: 500; |
| | | color: #333; |
| | | margin-bottom: 8px; |
| | | } |
| | | |
| | | .form-label .required { |
| | | color: #ff4d4f; |
| | | margin-left: 4px; |
| | | } |
| | | |
| | | .form-row { |
| | | display: flex; |
| | | gap: 10px; |
| | | align-items: center; |
| | | } |
| | | |
| | | /* æ¨¡ææ¡åºé¨ */ |
| | | .modal-footer { |
| | | width: 100%; |
| | | display: flex; |
| | | justify-content: flex-end; |
| | | align-items: center; |
| | | } |
| | | |
| | | .modal-footer-right { |
| | | display: flex; |
| | | gap: 10px; |
| | | } |
| | | </style> |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | <template> |
| | | <n-modal |
| | | :show="visible" |
| | | @update:show="(value) => emit('update:visible', value)" |
| | | title="ð å¤å¶ç§°éçå" |
| | | preset="card" |
| | | size="medium" |
| | | :style="bodyStyle" |
| | | > |
| | | <div class="form-group"> |
| | | <label class="form-label">æºçå</label> |
| | | <n-input :value="form.sourceName" disabled /> |
| | | </div> |
| | | <div class="form-group" style="margin-top: 12px;"> |
| | | <label class="form-label">å¤å¶æ°é</label> |
| | | <n-input-number v-model:value="form.count" placeholder="请è¾å
¥å¤å¶æ°é" min="1" max="20" style="max-width: 100px;" /> |
| | | <div class="form-hint">ç³»ç»å°èªå¨éå¢ç¼å·åç¼ï¼å¦ WBOX-2024-001-a, -b, -câ¦</div> |
| | | </div> |
| | | <template #footer> |
| | | <div class="modal-footer"> |
| | | <div class="modal-footer-right"> |
| | | <n-button @click="handleCancel">åæ¶</n-button> |
| | | <n-button type="primary" @click="handleSubmit">ð 确认å¤å¶</n-button> |
| | | </div> |
| | | </div> |
| | | </template> |
| | | </n-modal> |
| | | </template> |
| | | |
| | | <script setup lang="ts"> |
| | | import { ref, reactive, watch } from 'vue'; |
| | | import { weighingBoxApi } from '@/service/api/md/weighing-box'; |
| | | import { useMessage } from 'naive-ui'; |
| | | |
| | | const props = defineProps({ |
| | | visible: { |
| | | type: Boolean, |
| | | default: false |
| | | }, |
| | | rowData: { |
| | | type: Object, |
| | | default: () => ({}) |
| | | } |
| | | }); |
| | | |
| | | const emit = defineEmits(['update:visible', 'submitted']); |
| | | |
| | | const message = useMessage(); |
| | | |
| | | const form = reactive({ |
| | | sourceId: undefined, |
| | | sourceName: '', |
| | | count: 1 |
| | | }); |
| | | |
| | | const bodyStyle = { |
| | | width: '400px' |
| | | }; |
| | | |
| | | watch(() => props.visible, (newValue) => { |
| | | if (newValue && props.rowData) { |
| | | form.sourceId = props.rowData.id; |
| | | form.sourceName = props.rowData.name; |
| | | form.count = 1; |
| | | } |
| | | }); |
| | | |
| | | const handleCancel = () => { |
| | | emit('update:visible', false); |
| | | }; |
| | | |
| | | const handleSubmit = async () => { |
| | | try { |
| | | const response = await weighingBoxApi.copy({ |
| | | sourceId: form.sourceId, |
| | | count: form.count |
| | | }); |
| | | const res = response.response.data; |
| | | |
| | | if (res.code === 200) { |
| | | message.success('å¤å¶æå'); |
| | | emit('update:visible', false); |
| | | emit('submitted'); |
| | | } else { |
| | | message.error(res.msg || 'å¤å¶å¤±è´¥'); |
| | | } |
| | | } catch (error) { |
| | | message.error('å¤å¶å¤±è´¥'); |
| | | } |
| | | }; |
| | | </script> |
| | | |
| | | <style scoped> |
| | | /* è¡¨åæ ·å¼ */ |
| | | .form-group { |
| | | display: flex; |
| | | flex-direction: column; |
| | | margin-bottom: 16px; |
| | | } |
| | | |
| | | .form-label { |
| | | font-size: 14px; |
| | | font-weight: 500; |
| | | color: #333; |
| | | margin-bottom: 8px; |
| | | } |
| | | |
| | | .form-hint { |
| | | font-size: 12px; |
| | | color: #999; |
| | | margin-top: 4px; |
| | | } |
| | | |
| | | /* æ¨¡ææ¡åºé¨ */ |
| | | .modal-footer { |
| | | width: 100%; |
| | | display: flex; |
| | | justify-content: flex-end; |
| | | align-items: center; |
| | | } |
| | | |
| | | .modal-footer-right { |
| | | display: flex; |
| | | gap: 10px; |
| | | } |
| | | </style> |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | <template> |
| | | <n-modal |
| | | :show="visible" |
| | | @update:show="(value) => emit('update:visible', value)" |
| | | :title="operateType === 'add' ? 'ð¦ æ°å¢ç§°éçå' : 'ð¦ ç¼è¾ç§°éçå'" |
| | | preset="card" |
| | | size="medium" |
| | | :style="bodyStyle" |
| | | :bordered="false" |
| | | > |
| | | <n-form ref="formRef" :model="form" :rules="rules" label-placement="top"> |
| | | <n-grid :cols="2" :x-gap="12" :y-gap="0"> |
| | | <!-- åºæ¬ä¿¡æ¯ --> |
| | | <n-grid-item :span="2"> |
| | | <div class="form-section-title">ð¦ åºæ¬ä¿¡æ¯</div> |
| | | </n-grid-item> |
| | | |
| | | <n-form-item-gi path="name" label="çååç§°" required> |
| | | <n-input v-model:value="form.name" placeholder="å¦ï¼æ åç ç ç A-01" /> |
| | | </n-form-item-gi> |
| | | |
| | | <n-form-item-gi path="code" label="çåç¼å·" required> |
| | | <n-input v-model:value="form.code" placeholder="å¦ï¼WBOX-2024-001" /> |
| | | </n-form-item-gi> |
| | | |
| | | <n-form-item-gi path="weight" label="æ åéé" required> |
| | | <div class="form-row"> |
| | | <n-input-number v-model:value="form.weight" placeholder="500.00" /> |
| | | <n-input v-model:value="form.unit" placeholder="g" style="max-width: 80px;" /> |
| | | </div> |
| | | </n-form-item-gi> |
| | | |
| | | <n-form-item-gi path="location" label="åæ¾ä½ç½®"> |
| | | <n-input v-model:value="form.location" placeholder="å¦ï¼1å·äº§çº¿Â·è´¨æ£å°A" /> |
| | | </n-form-item-gi> |
| | | |
| | | <!-- æ ¡å设置 --> |
| | | <n-grid-item :span="2"> |
| | | <hr class="form-divider"> |
| | | <div class="form-section-title">ð æ ¡å设置</div> |
| | | </n-grid-item> |
| | | |
| | | <n-grid-item :span="2"> |
| | | <div class="form-group full"> |
| | | <label class="form-label">éæ©é¢è®¾å¨ææ¨¡æ¿</label> |
| | | <div class="preset-bar"> |
| | | <span class="preset-chip" @click="setPresetCycle(7)">æ¯ 7 天</span> |
| | | <span class="preset-chip" @click="setPresetCycle(14)">æ¯ 14 天</span> |
| | | <span class="preset-chip active" @click="setPresetCycle(30)">æ¯ 30 天</span> |
| | | <span class="preset-chip" @click="setPresetCycle(60)">æ¯ 60 天</span> |
| | | <span class="preset-chip" @click="setPresetCycle(90)">æ¯ 90 天</span> |
| | | <span class="preset-chip" @click="setPresetCycle(180)">æ¯ 180 天</span> |
| | | </div> |
| | | </div> |
| | | </n-grid-item> |
| | | |
| | | <n-form-item-gi path="calibCycleDays" label="æ ¡å卿" required> |
| | | <div class="form-row"> |
| | | <n-input-number v-model:value="form.calibCycleDays" placeholder="30" /> |
| | | <span class="form-unit">天</span> |
| | | </div> |
| | | </n-form-item-gi> |
| | | |
| | | <n-form-item-gi path="remindDays" label="æåæé天æ°"> |
| | | <div class="form-row"> |
| | | <n-input-number v-model:value="form.remindDays" placeholder="7" /> |
| | | <span class="form-unit">天</span> |
| | | </div> |
| | | </n-form-item-gi> |
| | | |
| | | <n-form-item-gi label="䏿¬¡æ ¡åæ¥æ"> |
| | | <n-date-picker v-model:value="form.lastCalibDate" type="date" /> |
| | | </n-form-item-gi> |
| | | |
| | | <n-form-item-gi label="å¯ç¨ç¶æ"> |
| | | <div class="switch-wrap"> |
| | | <n-switch v-model:value="form.activeStatus" /> |
| | | <span class="switch-label">{{ form.activeStatus ? 'å·²å¯ç¨' : 'å·²åç¨' }}</span> |
| | | </div> |
| | | </n-form-item-gi> |
| | | |
| | | <!-- 夿³¨æè¿° --> |
| | | <n-grid-item :span="2"> |
| | | <hr class="form-divider"> |
| | | </n-grid-item> |
| | | |
| | | <n-form-item-gi label="夿³¨æè¿°" :span="2"> |
| | | <n-input v-model:value="form.description" type="textarea" placeholder="å¯å¡«åçåè§æ ¼è¯´æã注æäºé¡¹çâ¦" /> |
| | | </n-form-item-gi> |
| | | </n-grid> |
| | | </n-form> |
| | | <template #footer> |
| | | <div class="modal-footer"> |
| | | <span class="modal-hint">ð¡ æ Ctrl+Enter å¿«éä¿å</span> |
| | | <div class="modal-footer-right"> |
| | | <n-button @click="handleCancel">åæ¶</n-button> |
| | | <n-button type="primary" @click="handleSubmit">ä¿å</n-button> |
| | | </div> |
| | | </div> |
| | | </template> |
| | | </n-modal> |
| | | </template> |
| | | |
| | | <script setup lang="ts"> |
| | | import { ref, reactive, watch } from 'vue'; |
| | | import { weighingBoxApi } from '@/service/api/md/weighing-box'; |
| | | import { useMessage } from 'naive-ui'; |
| | | |
| | | const props = defineProps({ |
| | | visible: { |
| | | type: Boolean, |
| | | default: false |
| | | }, |
| | | operateType: { |
| | | type: String, |
| | | default: 'add' |
| | | }, |
| | | rowData: { |
| | | type: Object, |
| | | default: () => ({}) |
| | | } |
| | | }); |
| | | |
| | | const emit = defineEmits(['update:visible', 'submitted']); |
| | | |
| | | const message = useMessage(); |
| | | const formRef = ref(); |
| | | |
| | | const form = reactive({ |
| | | id: undefined, |
| | | name: '', |
| | | code: '', |
| | | weight: null, |
| | | unit: 'g', |
| | | location: '', |
| | | calibCycleDays: 30, |
| | | remindDays: 7, |
| | | lastCalibDate: null, |
| | | activeStatus: true, |
| | | description: '' |
| | | }); |
| | | |
| | | const rules = { |
| | | name: [{ required: true, message: '请è¾å
¥çååç§°', trigger: 'blur' }], |
| | | code: [{ required: true, message: '请è¾å
¥çåç¼å·', trigger: 'blur' }], |
| | | weight: [{ type: 'number', required: true, message: '请è¾å
¥æ åéé', trigger: 'blur' }], |
| | | unit: [{ required: true, message: '请è¾å
¥åä½', trigger: 'blur' }], |
| | | calibCycleDays: [{ type: 'number', required: true, message: '请è¾å
¥æ ¡å卿', trigger: 'blur' }] |
| | | }; |
| | | |
| | | const bodyStyle = { |
| | | width: '800px' |
| | | }; |
| | | |
| | | watch(() => props.visible, (newValue) => { |
| | | if (newValue) { |
| | | if (props.operateType === 'edit' && props.rowData) { |
| | | console.log("ç¼è¾æ°æ®:", props.rowData); |
| | | Object.assign(form, props.rowData); |
| | | // å°å¯ç¨ç¶æè½¬æ¢ä¸ºæ°å¼ |
| | | form.activeStatus = form.activeStatus === 1; |
| | | form.weight = Number(form.weight); |
| | | } else { |
| | | console.log("æ°å¢æ°æ®:", form); |
| | | form.id = undefined; |
| | | form.name = ''; |
| | | form.code = ''; |
| | | form.weight = null; |
| | | form.unit = 'g'; |
| | | form.location = ''; |
| | | form.calibCycleDays = 30; |
| | | form.remindDays = 7; |
| | | form.activeStatus = true; |
| | | form.description = ''; |
| | | form.lastCalibDate = null; |
| | | |
| | | } |
| | | } |
| | | }); |
| | | |
| | | const setPresetCycle = (days) => { |
| | | form.calibCycleDays = days; |
| | | }; |
| | | |
| | | const handleCancel = () => { |
| | | emit('update:visible', false); |
| | | }; |
| | | |
| | | const handleSubmit = async () => { |
| | | if (!formRef.value) return; |
| | | try { |
| | | await formRef.value.validate(); |
| | | // å°å¯ç¨ç¶æè½¬æ¢ä¸ºæ°å¼ |
| | | form.activeStatus = form.activeStatus ? 1 : 0; |
| | | |
| | | let response; |
| | | if (form.id) { |
| | | response = await weighingBoxApi.edit(form); |
| | | } else { |
| | | response = await weighingBoxApi.add(form); |
| | | } |
| | | const res = response.response.data; |
| | | |
| | | if (res.code === 200) { |
| | | message.success(form.id ? 'ä¿®æ¹æå' : 'æ°å¢æå'); |
| | | emit('update:visible', false); |
| | | emit('submitted'); |
| | | } else { |
| | | message.error(res.msg || 'æä½å¤±è´¥'); |
| | | } |
| | | } catch (error: any) { |
| | | console.error('Form validation failed', error); |
| | | if (error.message) { |
| | | message.error(error.message); |
| | | } else { |
| | | message.error('æä½å¤±è´¥'); |
| | | } |
| | | } |
| | | }; |
| | | </script> |
| | | |
| | | <style scoped> |
| | | /* è¡¨åæ ·å¼ */ |
| | | .form-grid { |
| | | display: grid; |
| | | grid-template-columns: 1fr 1fr; |
| | | } |
| | | |
| | | .form-group { |
| | | display: flex; |
| | | flex-direction: column; |
| | | } |
| | | |
| | | .form-group.full { |
| | | grid-column: 1 / -1; |
| | | } |
| | | |
| | | .form-label { |
| | | font-size: 14px; |
| | | font-weight: 500; |
| | | color: #333; |
| | | margin-bottom: 8px; |
| | | } |
| | | |
| | | .form-label .required { |
| | | color: #ff4d4f; |
| | | margin-left: 4px; |
| | | } |
| | | |
| | | .form-row { |
| | | display: flex; |
| | | gap: 10px; |
| | | align-items: center; |
| | | } |
| | | |
| | | .form-hint { |
| | | font-size: 12px; |
| | | color: #999; |
| | | margin-top: 4px; |
| | | } |
| | | |
| | | .form-divider { |
| | | grid-column: 1 / -1; |
| | | border: none; |
| | | border-top: 1px solid #e5e7eb; |
| | | margin: 12px 0; |
| | | } |
| | | |
| | | .form-section-title { |
| | | grid-column: 1 / -1; |
| | | font-size: 14px; |
| | | font-weight: 600; |
| | | color: #1890ff; |
| | | margin: 12px 0 8px; |
| | | } |
| | | |
| | | /* é¢è®¾æ¨¡æ¿ */ |
| | | .preset-bar { |
| | | grid-column: 1 / -1; |
| | | display: flex; |
| | | align-items: center; |
| | | gap: 8px; |
| | | flex-wrap: wrap; |
| | | margin-bottom: 12px; |
| | | } |
| | | |
| | | .preset-chip { |
| | | padding: 6px 16px; |
| | | border-radius: 20px; |
| | | border: 1px solid #e5e7eb; |
| | | font-size: 14px; |
| | | color: #666; |
| | | cursor: pointer; |
| | | transition: all 0.2s ease; |
| | | background: #fff; |
| | | } |
| | | |
| | | .preset-chip:hover { |
| | | border-color: #1890ff; |
| | | color: #1890ff; |
| | | } |
| | | |
| | | .preset-chip.active { |
| | | background: #1890ff; |
| | | color: #fff; |
| | | border-color: #1890ff; |
| | | } |
| | | |
| | | /* æ¨¡ææ¡åºé¨ */ |
| | | .modal-footer { |
| | | width: 100%; |
| | | display: flex; |
| | | justify-content: space-between; |
| | | align-items: center; |
| | | } |
| | | |
| | | .modal-hint { |
| | | font-size: 12px; |
| | | color: #999; |
| | | } |
| | | |
| | | .modal-footer-right { |
| | | display: flex; |
| | | gap: 10px; |
| | | } |
| | | |
| | | /* å¼å
³ */ |
| | | .switch-wrap { |
| | | display: flex; |
| | | align-items: center; |
| | | gap: 10px; |
| | | } |
| | | |
| | | .switch-label { |
| | | font-size: 14px; |
| | | color: #666; |
| | | } |
| | | </style> |