From 3471290659516cf21db3211a9053daff5f283e03 Mon Sep 17 00:00:00 2001
From: zhuguifei <312353457@qq.com>
Date: 星期五, 20 三月 2026 15:50:18 +0800
Subject: [PATCH] feat: 基础数据仪器管理、判定依据、判定依据明细

---
 RuoYi-Vue-Plus/ruoyi-modules/ruoyi-qa/src/main/java/org/dromara/qa/qm/domain/QmJudgeDetails.java                  |  100 +
 RuoYi-Vue-Plus/ruoyi-modules/ruoyi-qa/src/main/java/org/dromara/qa/qm/mapper/QmJudgeMapper.java                   |   15 
 ruoyi-plus-soybean/src/router/elegant/routes.ts                                                                   |   27 
 ruoyi-plus-soybean/src/typings/api/qm.judge-details.api.d.ts                                                      |   85 +
 ruoyi-plus-soybean/src/typings/api/qm.judge.api.d.ts                                                              |   77 +
 RuoYi-Vue-Plus/ruoyi-modules/ruoyi-qa/src/main/java/org/dromara/qa/md/service/impl/MdInstrumentServiceImpl.java   |  133 ++
 RuoYi-Vue-Plus/ruoyi-modules/ruoyi-qa/src/main/java/org/dromara/qa/qm/domain/bo/QmJudgeDetailsBo.java             |   99 +
 ruoyi-plus-soybean/src/service/api/md/instrument.ts                                                               |   35 
 ruoyi-plus-soybean/src/service/api/qm/judge-details.ts                                                            |   35 
 ruoyi-plus-soybean/src/views/qm/judge/index.vue                                                                   |  323 ++++
 RuoYi-Vue-Plus/ruoyi-modules/ruoyi-qa/src/main/java/org/dromara/qa/md/domain/MdInstrument.java                    |   69 +
 ruoyi-plus-soybean/src/views/md/instrument/index.vue                                                              |  247 +++
 ruoyi-plus-soybean/src/service/api/qm/judge.ts                                                                    |   35 
 ruoyi-plus-soybean/.gitignore                                                                                     |    2 
 RuoYi-Vue-Plus/ruoyi-modules/ruoyi-qa/src/main/java/org/dromara/qa/md/controller/MdInstrumentController.java      |  105 +
 RuoYi-Vue-Plus/ruoyi-modules/ruoyi-qa/src/main/java/org/dromara/qa/qm/service/impl/QmJudgeServiceImpl.java        |  174 ++
 RuoYi-Vue-Plus/ruoyi-modules/ruoyi-qa/src/main/resources/mapper/qa/qm/QmJudgeDetailsMapper.xml                    |    6 
 RuoYi-Vue-Plus/ruoyi-modules/ruoyi-qa/src/main/java/org/dromara/qa/qm/domain/vo/QmJudgeVo.java                    |  114 +
 RuoYi-Vue-Plus/ruoyi-modules/ruoyi-qa/src/main/java/org/dromara/qa/md/mapper/MdInstrumentMapper.java              |   15 
 RuoYi-Vue-Plus/ruoyi-modules/ruoyi-qa/src/main/java/org/dromara/qa/md/domain/vo/MdInstrumentVo.java               |   87 +
 RuoYi-Vue-Plus/ruoyi-modules/ruoyi-qa/src/main/java/org/dromara/qa/qm/controller/QmJudgeDetailsController.java    |  105 +
 ruoyi-plus-soybean/src/views/qm/judge/modules/judge-operate-drawer.vue                                            |  212 +++
 ruoyi-plus-soybean/src/views/qm/judge-details/modules/judge-details-operate-drawer.vue                            |  175 ++
 RuoYi-Vue-Plus/ruoyi-modules/ruoyi-qa/src/main/java/org/dromara/qa/qm/domain/vo/QmJudgeDetailsVo.java             |  116 +
 RuoYi-Vue-Plus/ruoyi-modules/ruoyi-qa/src/main/java/org/dromara/qa/qm/service/IQmJudgeService.java                |   68 +
 RuoYi-Vue-Plus/ruoyi-modules/ruoyi-qa/src/main/resources/mapper/qa/md/MdInstrumentMapper.xml                      |    6 
 ruoyi-plus-soybean/src/views/qm/judge/modules/judge-search.vue                                                    |   98 +
 ruoyi-plus-soybean/src/views/qm/judge-details/modules/judge-details-search.vue                                    |   83 +
 RuoYi-Vue-Plus/ruoyi-modules/ruoyi-qa/src/main/java/org/dromara/qa/md/domain/bo/MdInstrumentBo.java               |   69 +
 RuoYi-Vue-Plus/ruoyi-modules/ruoyi-qa/src/main/java/org/dromara/qa/qm/controller/QmJudgeController.java           |  105 +
 RuoYi-Vue-Plus/ruoyi-modules/ruoyi-qa/src/main/java/org/dromara/qa/qm/mapper/QmJudgeDetailsMapper.java            |   15 
 ruoyi-plus-soybean/src/views/md/instrument/modules/instrument-search.vue                                          |   82 +
 RuoYi-Vue-Plus/ruoyi-modules/ruoyi-qa/src/main/java/org/dromara/qa/qm/service/IQmJudgeDetailsService.java         |   68 +
 RuoYi-Vue-Plus/ruoyi-modules/ruoyi-qa/src/main/java/org/dromara/qa/qm/domain/bo/QmJudgeBo.java                    |   81 +
 ruoyi-plus-soybean/src/router/elegant/imports.ts                                                                  |    3 
 ruoyi-plus-soybean/src/typings/elegant-router.d.ts                                                                |    6 
 ruoyi-plus-soybean/src/views/qm/judge-details/index.vue                                                           |  262 ++++
 ruoyi-plus-soybean/src/typings/api/md.instrument.api.d.ts                                                         |   65 +
 ruoyi-plus-soybean/src/views/qm/checkitem/modules/checkitem-operate-drawer.vue                                    |   36 
 RuoYi-Vue-Plus/ruoyi-modules/ruoyi-qa/src/main/java/org/dromara/qa/md/service/IMdInstrumentService.java           |   68 +
 ruoyi-plus-soybean/src/views/md/instrument/modules/instrument-operate-drawer.vue                                  |  157 ++
 RuoYi-Vue-Plus/ruoyi-modules/ruoyi-qa/src/main/java/org/dromara/qa/qm/domain/QmJudge.java                         |   92 +
 RuoYi-Vue-Plus/ruoyi-modules/ruoyi-qa/src/main/java/org/dromara/qa/qm/service/impl/QmJudgeDetailsServiceImpl.java |  135 ++
 ruoyi-plus-soybean/src/router/elegant/transform.ts                                                                |    3 
 44 files changed, 3,892 insertions(+), 1 deletions(-)

diff --git a/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-qa/src/main/java/org/dromara/qa/md/controller/MdInstrumentController.java b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-qa/src/main/java/org/dromara/qa/md/controller/MdInstrumentController.java
new file mode 100644
index 0000000..df68e72
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-qa/src/main/java/org/dromara/qa/md/controller/MdInstrumentController.java
@@ -0,0 +1,105 @@
+package org.dromara.qa.md.controller;
+
+import java.util.List;
+
+import lombok.RequiredArgsConstructor;
+import jakarta.servlet.http.HttpServletResponse;
+import jakarta.validation.constraints.*;
+import cn.dev33.satoken.annotation.SaCheckPermission;
+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.excel.utils.ExcelUtil;
+import org.dromara.qa.md.domain.vo.MdInstrumentVo;
+import org.dromara.qa.md.domain.bo.MdInstrumentBo;
+import org.dromara.qa.md.service.IMdInstrumentService;
+import org.dromara.common.mybatis.core.page.TableDataInfo;
+
+/**
+ * 娴嬮噺浠櫒
+ *
+ * @author zhuguifei
+ * @date 2026-03-18
+ */
+@Validated
+@RequiredArgsConstructor
+@RestController
+@RequestMapping("/md/instrument")
+public class MdInstrumentController extends BaseController {
+
+    private final IMdInstrumentService mdInstrumentService;
+
+    /**
+     * 鏌ヨ娴嬮噺浠櫒鍒楄〃
+     */
+    @SaCheckPermission("md:instrument:list")
+    @GetMapping("/list")
+    public TableDataInfo<MdInstrumentVo> list(MdInstrumentBo bo, PageQuery pageQuery) {
+        return mdInstrumentService.queryPageList(bo, pageQuery);
+    }
+
+    /**
+     * 瀵煎嚭娴嬮噺浠櫒鍒楄〃
+     */
+    @SaCheckPermission("md:instrument:export")
+    @Log(title = "娴嬮噺浠櫒", businessType = BusinessType.EXPORT)
+    @PostMapping("/export")
+    public void export(MdInstrumentBo bo, HttpServletResponse response) {
+        List<MdInstrumentVo> list = mdInstrumentService.queryList(bo);
+        ExcelUtil.exportExcel(list, "娴嬮噺浠櫒", MdInstrumentVo.class, response);
+    }
+
+    /**
+     * 鑾峰彇娴嬮噺浠櫒璇︾粏淇℃伅
+     *
+     * @param id 涓婚敭
+     */
+    @SaCheckPermission("md:instrument:query")
+    @GetMapping("/{id}")
+    public R<MdInstrumentVo> getInfo(@NotNull(message = "涓婚敭涓嶈兘涓虹┖")
+                                     @PathVariable String id) {
+        return R.ok(mdInstrumentService.queryById(id));
+    }
+
+    /**
+     * 鏂板娴嬮噺浠櫒
+     */
+    @SaCheckPermission("md:instrument:add")
+    @Log(title = "娴嬮噺浠櫒", businessType = BusinessType.INSERT)
+    @RepeatSubmit()
+    @PostMapping()
+    public R<Void> add(@Validated(AddGroup.class) @RequestBody MdInstrumentBo bo) {
+        return toAjax(mdInstrumentService.insertByBo(bo));
+    }
+
+    /**
+     * 淇敼娴嬮噺浠櫒
+     */
+    @SaCheckPermission("md:instrument:edit")
+    @Log(title = "娴嬮噺浠櫒", businessType = BusinessType.UPDATE)
+    @RepeatSubmit()
+    @PutMapping()
+    public R<Void> edit(@Validated(EditGroup.class) @RequestBody MdInstrumentBo bo) {
+        return toAjax(mdInstrumentService.updateByBo(bo));
+    }
+
+    /**
+     * 鍒犻櫎娴嬮噺浠櫒
+     *
+     * @param ids 涓婚敭涓�
+     */
+    @SaCheckPermission("md:instrument:remove")
+    @Log(title = "娴嬮噺浠櫒", businessType = BusinessType.DELETE)
+    @DeleteMapping("/{ids}")
+    public R<Void> remove(@NotEmpty(message = "涓婚敭涓嶈兘涓虹┖")
+                          @PathVariable String[] ids) {
+        return toAjax(mdInstrumentService.deleteWithValidByIds(List.of(ids), true));
+    }
+}
diff --git a/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-qa/src/main/java/org/dromara/qa/md/domain/MdInstrument.java b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-qa/src/main/java/org/dromara/qa/md/domain/MdInstrument.java
new file mode 100644
index 0000000..7ee4aae
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-qa/src/main/java/org/dromara/qa/md/domain/MdInstrument.java
@@ -0,0 +1,69 @@
+package org.dromara.qa.md.domain;
+
+import org.dromara.common.mybatis.core.domain.BaseEntity;
+import com.baomidou.mybatisplus.annotation.*;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+
+import java.io.Serial;
+
+/**
+ * 娴嬮噺浠櫒瀵硅薄 md_instrument
+ *
+ * @author zhuguifei
+ * @date 2026-03-18
+ */
+@Data
+@TableName("md_instrument")
+public class MdInstrument {
+
+    @Serial
+    private static final long serialVersionUID = 1L;
+
+    /**
+     * 缂栫爜
+     */
+    private String id;
+
+    /**
+     * 浠櫒浠g爜
+     */
+    private String instrumentCode;
+
+    /**
+     * 浠櫒鍚嶇О
+     */
+    private String instrumentName;
+
+    /**
+     * 鎺ュ彛鏍囧噯
+     */
+    private String ifStd;
+
+    /**
+     * 鎺ュ彛鏍囧噯浠g爜
+     */
+    private Long isc;
+
+    /**
+     * 鎵�灞炶溅闂�
+     */
+    private String workShop;
+
+    /**
+     * 鍚敤
+     */
+    private Long enable;
+
+    /**
+     * 鍒犻櫎
+     */
+    private Long del;
+
+    /**
+     * 澶囨敞
+     */
+    private String instrumentDes;
+
+
+}
diff --git a/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-qa/src/main/java/org/dromara/qa/md/domain/bo/MdInstrumentBo.java b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-qa/src/main/java/org/dromara/qa/md/domain/bo/MdInstrumentBo.java
new file mode 100644
index 0000000..02607d4
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-qa/src/main/java/org/dromara/qa/md/domain/bo/MdInstrumentBo.java
@@ -0,0 +1,69 @@
+package org.dromara.qa.md.domain.bo;
+
+import org.dromara.qa.md.domain.MdInstrument;
+import org.dromara.common.mybatis.core.domain.BaseEntity;
+import org.dromara.common.core.validate.AddGroup;
+import org.dromara.common.core.validate.EditGroup;
+import io.github.linpeilie.annotations.AutoMapper;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+import jakarta.validation.constraints.*;
+
+/**
+ * 娴嬮噺浠櫒涓氬姟瀵硅薄 md_instrument
+ *
+ * @author zhuguifei
+ * @date 2026-03-18
+ */
+@Data
+@EqualsAndHashCode(callSuper = true)
+@AutoMapper(target = MdInstrument.class, reverseConvertGenerate = false)
+public class MdInstrumentBo extends BaseEntity {
+
+    /**
+     * 缂栫爜
+     */
+    private String id;
+
+    /**
+     * 浠櫒浠g爜
+     */
+    private String instrumentCode;
+
+    /**
+     * 浠櫒鍚嶇О
+     */
+    private String instrumentName;
+
+    /**
+     * 鎺ュ彛鏍囧噯
+     */
+    private String ifStd;
+
+    /**
+     * 鎺ュ彛鏍囧噯浠g爜
+     */
+    private Long isc;
+
+    /**
+     * 鎵�灞炶溅闂�
+     */
+    private String workShop;
+
+    /**
+     * 鍚敤
+     */
+    private Long enable;
+
+    /**
+     * 鍒犻櫎
+     */
+    private Long del;
+
+    /**
+     * 澶囨敞
+     */
+    private String instrumentDes;
+
+
+}
diff --git a/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-qa/src/main/java/org/dromara/qa/md/domain/vo/MdInstrumentVo.java b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-qa/src/main/java/org/dromara/qa/md/domain/vo/MdInstrumentVo.java
new file mode 100644
index 0000000..ea2e7fa
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-qa/src/main/java/org/dromara/qa/md/domain/vo/MdInstrumentVo.java
@@ -0,0 +1,87 @@
+package org.dromara.qa.md.domain.vo;
+
+import org.dromara.qa.md.domain.MdInstrument;
+import cn.idev.excel.annotation.ExcelIgnoreUnannotated;
+import cn.idev.excel.annotation.ExcelProperty;
+import org.dromara.common.excel.annotation.ExcelDictFormat;
+import org.dromara.common.excel.convert.ExcelDictConvert;
+import io.github.linpeilie.annotations.AutoMapper;
+import lombok.Data;
+
+import java.io.Serial;
+import java.io.Serializable;
+import java.util.Date;
+
+
+
+/**
+ * 娴嬮噺浠櫒瑙嗗浘瀵硅薄 md_instrument
+ *
+ * @author zhuguifei
+ * @date 2026-03-18
+ */
+@Data
+@ExcelIgnoreUnannotated
+@AutoMapper(target = MdInstrument.class)
+public class MdInstrumentVo implements Serializable {
+
+    @Serial
+    private static final long serialVersionUID = 1L;
+
+    /**
+     * 缂栫爜
+     */
+    @ExcelProperty(value = "缂栫爜")
+    private String id;
+
+    /**
+     * 浠櫒浠g爜
+     */
+    @ExcelProperty(value = "浠櫒浠g爜")
+    private String instrumentCode;
+
+    /**
+     * 浠櫒鍚嶇О
+     */
+    @ExcelProperty(value = "浠櫒鍚嶇О")
+    private String instrumentName;
+
+    /**
+     * 鎺ュ彛鏍囧噯
+     */
+    @ExcelProperty(value = "鎺ュ彛鏍囧噯")
+    private String ifStd;
+
+    /**
+     * 鎺ュ彛鏍囧噯浠g爜
+     */
+    @ExcelProperty(value = "鎺ュ彛鏍囧噯浠g爜")
+    private Long isc;
+
+    /**
+     * 鎵�灞炶溅闂�
+     */
+    @ExcelProperty(value = "鎵�灞炶溅闂�")
+    private String workShop;
+
+    /**
+     * 鍚敤
+     */
+    @ExcelProperty(value = "鍚敤", converter = ExcelDictConvert.class)
+    @ExcelDictFormat(dictType = "sys_yes_no")
+    private Long enable;
+
+    /**
+     * 鍒犻櫎
+     */
+    @ExcelProperty(value = "鍒犻櫎")
+    private Long del;
+
+    /**
+     * 澶囨敞
+     */
+    @ExcelProperty(value = "澶囨敞")
+    private String instrumentDes;
+
+
+}
diff --git a/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-qa/src/main/java/org/dromara/qa/md/mapper/MdInstrumentMapper.java b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-qa/src/main/java/org/dromara/qa/md/mapper/MdInstrumentMapper.java
new file mode 100644
index 0000000..36f5d42
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-qa/src/main/java/org/dromara/qa/md/mapper/MdInstrumentMapper.java
@@ -0,0 +1,15 @@
+package org.dromara.qa.md.mapper;
+
+import org.dromara.qa.md.domain.MdInstrument;
+import org.dromara.qa.md.domain.vo.MdInstrumentVo;
+import org.dromara.common.mybatis.core.mapper.BaseMapperPlus;
+
+/**
+ * 娴嬮噺浠櫒Mapper鎺ュ彛
+ *
+ * @author zhuguifei
+ * @date 2026-03-18
+ */
+public interface MdInstrumentMapper extends BaseMapperPlus<MdInstrument, MdInstrumentVo> {
+
+}
diff --git a/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-qa/src/main/java/org/dromara/qa/md/service/IMdInstrumentService.java b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-qa/src/main/java/org/dromara/qa/md/service/IMdInstrumentService.java
new file mode 100644
index 0000000..bd3ce83
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-qa/src/main/java/org/dromara/qa/md/service/IMdInstrumentService.java
@@ -0,0 +1,68 @@
+package org.dromara.qa.md.service;
+
+import org.dromara.qa.md.domain.vo.MdInstrumentVo;
+import org.dromara.qa.md.domain.bo.MdInstrumentBo;
+import org.dromara.common.mybatis.core.page.TableDataInfo;
+import org.dromara.common.mybatis.core.page.PageQuery;
+
+import java.util.Collection;
+import java.util.List;
+
+/**
+ * 娴嬮噺浠櫒Service鎺ュ彛
+ *
+ * @author zhuguifei
+ * @date 2026-03-18
+ */
+public interface IMdInstrumentService {
+
+    /**
+     * 鏌ヨ娴嬮噺浠櫒
+     *
+     * @param id 涓婚敭
+     * @return 娴嬮噺浠櫒
+     */
+    MdInstrumentVo queryById(String id);
+
+    /**
+     * 鍒嗛〉鏌ヨ娴嬮噺浠櫒鍒楄〃
+     *
+     * @param bo        鏌ヨ鏉′欢
+     * @param pageQuery 鍒嗛〉鍙傛暟
+     * @return 娴嬮噺浠櫒鍒嗛〉鍒楄〃
+     */
+    TableDataInfo<MdInstrumentVo> queryPageList(MdInstrumentBo bo, PageQuery pageQuery);
+
+    /**
+     * 鏌ヨ绗﹀悎鏉′欢鐨勬祴閲忎华鍣ㄥ垪琛�
+     *
+     * @param bo 鏌ヨ鏉′欢
+     * @return 娴嬮噺浠櫒鍒楄〃
+     */
+    List<MdInstrumentVo> queryList(MdInstrumentBo bo);
+
+    /**
+     * 鏂板娴嬮噺浠櫒
+     *
+     * @param bo 娴嬮噺浠櫒
+     * @return 鏄惁鏂板鎴愬姛
+     */
+    Boolean insertByBo(MdInstrumentBo bo);
+
+    /**
+     * 淇敼娴嬮噺浠櫒
+     *
+     * @param bo 娴嬮噺浠櫒
+     * @return 鏄惁淇敼鎴愬姛
+     */
+    Boolean updateByBo(MdInstrumentBo bo);
+
+    /**
+     * 鏍¢獙骞舵壒閲忓垹闄ゆ祴閲忎华鍣ㄤ俊鎭�
+     *
+     * @param ids     寰呭垹闄ょ殑涓婚敭闆嗗悎
+     * @param isValid 鏄惁杩涜鏈夋晥鎬ф牎楠�
+     * @return 鏄惁鍒犻櫎鎴愬姛
+     */
+    Boolean deleteWithValidByIds(Collection<String> ids, Boolean isValid);
+}
diff --git a/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-qa/src/main/java/org/dromara/qa/md/service/impl/MdInstrumentServiceImpl.java b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-qa/src/main/java/org/dromara/qa/md/service/impl/MdInstrumentServiceImpl.java
new file mode 100644
index 0000000..03c917e
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-qa/src/main/java/org/dromara/qa/md/service/impl/MdInstrumentServiceImpl.java
@@ -0,0 +1,133 @@
+package org.dromara.qa.md.service.impl;
+
+import org.dromara.common.core.utils.MapstructUtils;
+import org.dromara.common.core.utils.StringUtils;
+import org.dromara.common.mybatis.core.page.TableDataInfo;
+import org.dromara.common.mybatis.core.page.PageQuery;
+import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+import com.baomidou.mybatisplus.core.toolkit.Wrappers;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.stereotype.Service;
+import org.dromara.qa.md.domain.bo.MdInstrumentBo;
+import org.dromara.qa.md.domain.vo.MdInstrumentVo;
+import org.dromara.qa.md.domain.MdInstrument;
+import org.dromara.qa.md.mapper.MdInstrumentMapper;
+import org.dromara.qa.md.service.IMdInstrumentService;
+
+import java.util.List;
+import java.util.Map;
+import java.util.Collection;
+
+/**
+ * 娴嬮噺浠櫒Service涓氬姟灞傚鐞�
+ *
+ * @author zhuguifei
+ * @date 2026-03-18
+ */
+@Slf4j
+@RequiredArgsConstructor
+@Service
+public class MdInstrumentServiceImpl implements IMdInstrumentService {
+
+    private final MdInstrumentMapper baseMapper;
+
+    /**
+     * 鏌ヨ娴嬮噺浠櫒
+     *
+     * @param id 涓婚敭
+     * @return 娴嬮噺浠櫒
+     */
+    @Override
+    public MdInstrumentVo queryById(String id){
+        return baseMapper.selectVoById(id);
+    }
+
+    /**
+     * 鍒嗛〉鏌ヨ娴嬮噺浠櫒鍒楄〃
+     *
+     * @param bo        鏌ヨ鏉′欢
+     * @param pageQuery 鍒嗛〉鍙傛暟
+     * @return 娴嬮噺浠櫒鍒嗛〉鍒楄〃
+     */
+    @Override
+    public TableDataInfo<MdInstrumentVo> queryPageList(MdInstrumentBo bo, PageQuery pageQuery) {
+        LambdaQueryWrapper<MdInstrument> lqw = buildQueryWrapper(bo);
+        Page<MdInstrumentVo> result = baseMapper.selectVoPage(pageQuery.build(), lqw);
+        return TableDataInfo.build(result);
+    }
+
+    /**
+     * 鏌ヨ绗﹀悎鏉′欢鐨勬祴閲忎华鍣ㄥ垪琛�
+     *
+     * @param bo 鏌ヨ鏉′欢
+     * @return 娴嬮噺浠櫒鍒楄〃
+     */
+    @Override
+    public List<MdInstrumentVo> queryList(MdInstrumentBo bo) {
+        LambdaQueryWrapper<MdInstrument> lqw = buildQueryWrapper(bo);
+        return baseMapper.selectVoList(lqw);
+    }
+
+    private LambdaQueryWrapper<MdInstrument> buildQueryWrapper(MdInstrumentBo bo) {
+        Map<String, Object> params = bo.getParams();
+        LambdaQueryWrapper<MdInstrument> lqw = Wrappers.lambdaQuery();
+        lqw.like(StringUtils.isNotBlank(bo.getInstrumentCode()), MdInstrument::getInstrumentCode, bo.getInstrumentCode());
+        lqw.like(StringUtils.isNotBlank(bo.getInstrumentName()), MdInstrument::getInstrumentName, bo.getInstrumentName());
+        lqw.eq(bo.getEnable() != null, MdInstrument::getEnable, bo.getEnable());
+        return lqw;
+    }
+
+    /**
+     * 鏂板娴嬮噺浠櫒
+     *
+     * @param bo 娴嬮噺浠櫒
+     * @return 鏄惁鏂板鎴愬姛
+     */
+    @Override
+    public Boolean insertByBo(MdInstrumentBo bo) {
+        MdInstrument add = MapstructUtils.convert(bo, MdInstrument.class);
+        validEntityBeforeSave(add);
+        boolean flag = baseMapper.insert(add) > 0;
+        if (flag) {
+            bo.setId(add.getId());
+        }
+        return flag;
+    }
+
+    /**
+     * 淇敼娴嬮噺浠櫒
+     *
+     * @param bo 娴嬮噺浠櫒
+     * @return 鏄惁淇敼鎴愬姛
+     */
+    @Override
+    public Boolean updateByBo(MdInstrumentBo bo) {
+        MdInstrument update = MapstructUtils.convert(bo, MdInstrument.class);
+        validEntityBeforeSave(update);
+        return baseMapper.updateById(update) > 0;
+    }
+
+    /**
+     * 淇濆瓨鍓嶇殑鏁版嵁鏍¢獙
+     */
+    private void validEntityBeforeSave(MdInstrument entity){
+        //TODO 鍋氫竴浜涙暟鎹牎楠�,濡傚敮涓�绾︽潫
+    }
+
+    /**
+     * 鏍¢獙骞舵壒閲忓垹闄ゆ祴閲忎华鍣ㄤ俊鎭�
+     *
+     * @param ids     寰呭垹闄ょ殑涓婚敭闆嗗悎
+     * @param isValid 鏄惁杩涜鏈夋晥鎬ф牎楠�
+     * @return 鏄惁鍒犻櫎鎴愬姛
+     */
+    @Override
+    public Boolean deleteWithValidByIds(Collection<String> ids, Boolean isValid) {
+        if(isValid){
+            //TODO 鍋氫竴浜涗笟鍔′笂鐨勬牎楠�,鍒ゆ柇鏄惁闇�瑕佹牎楠�
+        }
+        return baseMapper.deleteByIds(ids) > 0;
+    }
+}
diff --git a/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-qa/src/main/java/org/dromara/qa/qm/controller/QmJudgeController.java b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-qa/src/main/java/org/dromara/qa/qm/controller/QmJudgeController.java
new file mode 100644
index 0000000..ec936c9
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-qa/src/main/java/org/dromara/qa/qm/controller/QmJudgeController.java
@@ -0,0 +1,105 @@
+package org.dromara.qa.qm.controller;
+
+import java.util.List;
+
+import lombok.RequiredArgsConstructor;
+import jakarta.servlet.http.HttpServletResponse;
+import jakarta.validation.constraints.*;
+import cn.dev33.satoken.annotation.SaCheckPermission;
+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.excel.utils.ExcelUtil;
+import org.dromara.qa.qm.domain.vo.QmJudgeVo;
+import org.dromara.qa.qm.domain.bo.QmJudgeBo;
+import org.dromara.qa.qm.service.IQmJudgeService;
+import org.dromara.common.mybatis.core.page.TableDataInfo;
+
+/**
+ * 鍒ゅ畾渚濇嵁
+ *
+ * @author zhuguifei
+ * @date 2026-03-18
+ */
+@Validated
+@RequiredArgsConstructor
+@RestController
+@RequestMapping("/qm/judge")
+public class QmJudgeController extends BaseController {
+
+    private final IQmJudgeService qmJudgeService;
+
+    /**
+     * 鏌ヨ鍒ゅ畾渚濇嵁鍒楄〃
+     */
+    @SaCheckPermission("qm:judge:list")
+    @GetMapping("/list")
+    public TableDataInfo<QmJudgeVo> list(QmJudgeBo bo, PageQuery pageQuery) {
+        return qmJudgeService.queryPageList(bo, pageQuery);
+    }
+
+    /**
+     * 瀵煎嚭鍒ゅ畾渚濇嵁鍒楄〃
+     */
+    @SaCheckPermission("qm:judge:export")
+    @Log(title = "鍒ゅ畾渚濇嵁", businessType = BusinessType.EXPORT)
+    @PostMapping("/export")
+    public void export(QmJudgeBo bo, HttpServletResponse response) {
+        List<QmJudgeVo> list = qmJudgeService.queryList(bo);
+        ExcelUtil.exportExcel(list, "鍒ゅ畾渚濇嵁", QmJudgeVo.class, response);
+    }
+
+    /**
+     * 鑾峰彇鍒ゅ畾渚濇嵁璇︾粏淇℃伅
+     *
+     * @param id 涓婚敭
+     */
+    @SaCheckPermission("qm:judge:query")
+    @GetMapping("/{id}")
+    public R<QmJudgeVo> getInfo(@NotNull(message = "涓婚敭涓嶈兘涓虹┖")
+                                     @PathVariable String id) {
+        return R.ok(qmJudgeService.queryById(id));
+    }
+
+    /**
+     * 鏂板鍒ゅ畾渚濇嵁
+     */
+    @SaCheckPermission("qm:judge:add")
+    @Log(title = "鍒ゅ畾渚濇嵁", businessType = BusinessType.INSERT)
+    @RepeatSubmit()
+    @PostMapping()
+    public R<Void> add(@Validated(AddGroup.class) @RequestBody QmJudgeBo bo) {
+        return toAjax(qmJudgeService.insertByBo(bo));
+    }
+
+    /**
+     * 淇敼鍒ゅ畾渚濇嵁
+     */
+    @SaCheckPermission("qm:judge:edit")
+    @Log(title = "鍒ゅ畾渚濇嵁", businessType = BusinessType.UPDATE)
+    @RepeatSubmit()
+    @PutMapping()
+    public R<Void> edit(@Validated(EditGroup.class) @RequestBody QmJudgeBo bo) {
+        return toAjax(qmJudgeService.updateByBo(bo));
+    }
+
+    /**
+     * 鍒犻櫎鍒ゅ畾渚濇嵁
+     *
+     * @param ids 涓婚敭涓�
+     */
+    @SaCheckPermission("qm:judge:remove")
+    @Log(title = "鍒ゅ畾渚濇嵁", businessType = BusinessType.DELETE)
+    @DeleteMapping("/{ids}")
+    public R<Void> remove(@NotEmpty(message = "涓婚敭涓嶈兘涓虹┖")
+                          @PathVariable String[] ids) {
+        return toAjax(qmJudgeService.deleteWithValidByIds(List.of(ids), true));
+    }
+}
diff --git a/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-qa/src/main/java/org/dromara/qa/qm/controller/QmJudgeDetailsController.java b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-qa/src/main/java/org/dromara/qa/qm/controller/QmJudgeDetailsController.java
new file mode 100644
index 0000000..c58d4d6
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-qa/src/main/java/org/dromara/qa/qm/controller/QmJudgeDetailsController.java
@@ -0,0 +1,105 @@
+package org.dromara.qa.qm.controller;
+
+import java.util.List;
+
+import lombok.RequiredArgsConstructor;
+import jakarta.servlet.http.HttpServletResponse;
+import jakarta.validation.constraints.*;
+import cn.dev33.satoken.annotation.SaCheckPermission;
+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.excel.utils.ExcelUtil;
+import org.dromara.qa.qm.domain.vo.QmJudgeDetailsVo;
+import org.dromara.qa.qm.domain.bo.QmJudgeDetailsBo;
+import org.dromara.qa.qm.service.IQmJudgeDetailsService;
+import org.dromara.common.mybatis.core.page.TableDataInfo;
+
+/**
+ * 鍒ゅ畾妯℃澘鏄庣粏
+ *
+ * @author 鏈辨椋�
+ * @date 2026-03-20
+ */
+@Validated
+@RequiredArgsConstructor
+@RestController
+@RequestMapping("/qm/judgeDetails")
+public class QmJudgeDetailsController extends BaseController {
+
+    private final IQmJudgeDetailsService qmJudgeDetailsService;
+
+    /**
+     * 鏌ヨ鍒ゅ畾妯℃澘鏄庣粏鍒楄〃
+     */
+    @SaCheckPermission("qm:judgeDetails:list")
+    @GetMapping("/list")
+    public TableDataInfo<QmJudgeDetailsVo> list(QmJudgeDetailsBo bo, PageQuery pageQuery) {
+        return qmJudgeDetailsService.queryPageList(bo, pageQuery);
+    }
+
+    /**
+     * 瀵煎嚭鍒ゅ畾妯℃澘鏄庣粏鍒楄〃
+     */
+    @SaCheckPermission("qm:judgeDetails:export")
+    @Log(title = "鍒ゅ畾妯℃澘鏄庣粏", businessType = BusinessType.EXPORT)
+    @PostMapping("/export")
+    public void export(QmJudgeDetailsBo bo, HttpServletResponse response) {
+        List<QmJudgeDetailsVo> list = qmJudgeDetailsService.queryList(bo);
+        ExcelUtil.exportExcel(list, "鍒ゅ畾妯℃澘鏄庣粏", QmJudgeDetailsVo.class, response);
+    }
+
+    /**
+     * 鑾峰彇鍒ゅ畾妯℃澘鏄庣粏璇︾粏淇℃伅
+     *
+     * @param id 涓婚敭
+     */
+    @SaCheckPermission("qm:judgeDetails:query")
+    @GetMapping("/{id}")
+    public R<QmJudgeDetailsVo> getInfo(@NotNull(message = "涓婚敭涓嶈兘涓虹┖")
+                                     @PathVariable String id) {
+        return R.ok(qmJudgeDetailsService.queryById(id));
+    }
+
+    /**
+     * 鏂板鍒ゅ畾妯℃澘鏄庣粏
+     */
+    @SaCheckPermission("qm:judgeDetails:add")
+    @Log(title = "鍒ゅ畾妯℃澘鏄庣粏", businessType = BusinessType.INSERT)
+    @RepeatSubmit()
+    @PostMapping()
+    public R<Void> add(@Validated(AddGroup.class) @RequestBody QmJudgeDetailsBo bo) {
+        return toAjax(qmJudgeDetailsService.insertByBo(bo));
+    }
+
+    /**
+     * 淇敼鍒ゅ畾妯℃澘鏄庣粏
+     */
+    @SaCheckPermission("qm:judgeDetails:edit")
+    @Log(title = "鍒ゅ畾妯℃澘鏄庣粏", businessType = BusinessType.UPDATE)
+    @RepeatSubmit()
+    @PutMapping()
+    public R<Void> edit(@Validated(EditGroup.class) @RequestBody QmJudgeDetailsBo bo) {
+        return toAjax(qmJudgeDetailsService.updateByBo(bo));
+    }
+
+    /**
+     * 鍒犻櫎鍒ゅ畾妯℃澘鏄庣粏
+     *
+     * @param ids 涓婚敭涓�
+     */
+    @SaCheckPermission("qm:judgeDetails:remove")
+    @Log(title = "鍒ゅ畾妯℃澘鏄庣粏", businessType = BusinessType.DELETE)
+    @DeleteMapping("/{ids}")
+    public R<Void> remove(@NotEmpty(message = "涓婚敭涓嶈兘涓虹┖")
+                          @PathVariable String[] ids) {
+        return toAjax(qmJudgeDetailsService.deleteWithValidByIds(List.of(ids), true));
+    }
+}
diff --git a/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-qa/src/main/java/org/dromara/qa/qm/domain/QmJudge.java b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-qa/src/main/java/org/dromara/qa/qm/domain/QmJudge.java
new file mode 100644
index 0000000..0363a1d
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-qa/src/main/java/org/dromara/qa/qm/domain/QmJudge.java
@@ -0,0 +1,92 @@
+package org.dromara.qa.qm.domain;
+
+import org.dromara.common.mybatis.core.domain.BaseEntity;
+import com.baomidou.mybatisplus.annotation.*;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+import java.util.Date;
+import com.fasterxml.jackson.annotation.JsonFormat;
+
+import java.io.Serial;
+
+/**
+ * 鍒ゅ畾渚濇嵁瀵硅薄 qm_judge
+ *
+ * @author zhuguifei
+ * @date 2026-03-18
+ */
+@Data
+@TableName("qm_judge")
+public class QmJudge {
+
+    @Serial
+    private static final long serialVersionUID = 1L;
+
+    /**
+     * 缂栫爜
+     */
+    private String id;
+
+    /**
+     * 鐗╂枡鐗屽彿浠g爜
+     */
+    private String matCode;
+
+    /**
+     * 鐗╂枡鐗屽彿
+     */
+    private String matName;
+
+    /**
+     * 鐗堟湰鍙�
+     */
+    @Version
+    private Long version;
+
+    /**
+     * 鐘舵��
+     */
+    private Long status;
+
+    /**
+     * 鍒ゅ畾瑙勭▼COD
+     */
+    private String stdCod;
+
+    /**
+     * 鍒涘缓鏃堕棿
+     */
+    private Date cdate;
+
+    /**
+     * 鐢熸晥浜�/鎿嶄綔浜哄憳
+     */
+    private String oper;
+
+    /**
+     * 澶囨敞
+     */
+    private String des;
+
+    /**
+     * 鐗╂枡绫诲瀷
+     */
+    private Long category;
+
+    /**
+     * 杈呮枡绫诲瀷缂栫爜
+     */
+    private String typeCode;
+
+    /**
+     * 杈呮枡绫诲瀷鍚嶇О
+     */
+    private String typeName;
+
+    /**
+     * 鍒ゅ畾鍚嶇О
+     */
+    private String judgeName;
+
+
+}
diff --git a/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-qa/src/main/java/org/dromara/qa/qm/domain/QmJudgeDetails.java b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-qa/src/main/java/org/dromara/qa/qm/domain/QmJudgeDetails.java
new file mode 100644
index 0000000..1b07cdf
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-qa/src/main/java/org/dromara/qa/qm/domain/QmJudgeDetails.java
@@ -0,0 +1,100 @@
+package org.dromara.qa.qm.domain;
+
+import org.dromara.common.mybatis.core.domain.BaseEntity;
+import com.baomidou.mybatisplus.annotation.*;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+
+import java.io.Serial;
+
+/**
+ * 鍒ゅ畾妯℃澘鏄庣粏瀵硅薄 qm_judge_details
+ *
+ * @author 鏈辨椋�
+ * @date 2026-03-20
+ */
+@Data
+@EqualsAndHashCode(callSuper = true)
+@TableName("qm_judge_details")
+public class QmJudgeDetails extends BaseEntity {
+
+    @Serial
+    private static final long serialVersionUID = 1L;
+
+    /**
+     * 缂栫爜
+     */
+    private String id;
+
+    /**
+     * 鍒ゅ畾涓绘爣璇�
+     */
+    private String judgeId;
+
+    /**
+     * 鍒ゅ畾椤笽TEM
+     */
+    private String itemCod;
+
+    /**
+     * 鍒ゅ畾椤筃AME
+     */
+    private String itemName;
+
+    /**
+     * 鏍囧噯鍊�
+     */
+    private Long value3;
+
+    /**
+     * 鍒ゅ畾鍊�1
+     */
+    private Long value1;
+
+    /**
+     * 鍒ゅ畾鍊�2
+     */
+    private Long value2;
+
+    /**
+     * 缂洪櫡浣嶇疆
+     */
+    private String location;
+
+    /**
+     * 鍒ゅ畾绾у埆 (A,B,C,D)
+     */
+    private String cls;
+
+    /**
+     * 鍒嗗�兼爣鍑� (鎵e垎鏍囧噯锛屽緱鍒嗘爣鍑�),姣斿涓嶅悎鏍间竴娆℃墸澶氬皯鍒�
+     */
+    private Long stdscore;
+
+    /**
+     * 鏍囪姝ら」鏄惁涓哄悎鎴愰」鐩紝姣斿澶栬锛屽疄闄呬笂鍏宠仈浜嗗緢澶氬瓙椤圭洰
+     */
+    private Long ismix;
+
+    /**
+     * 鑻ユ瀛楁鏈塙UID鍊硷紝琛ㄦ槑瀹冨彲鑳戒负鍏朵粬椤圭洰鐨勫瓙椤癸紝姣斿鈥滅┖澶粹��,瀹冧负鐑熸敮澶栬椤圭洰鐨勫瓙椤�
+     */
+    private String rid;
+
+    /**
+     * 鑼冨洿-澶囩敤
+     */
+    private Long category;
+
+    /**
+     * 澶囨敞
+     */
+    private String decisionDes;
+
+    /**
+     * 淇敼浜�
+     */
+    private String updateUser;
+
+
+}
diff --git a/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-qa/src/main/java/org/dromara/qa/qm/domain/bo/QmJudgeBo.java b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-qa/src/main/java/org/dromara/qa/qm/domain/bo/QmJudgeBo.java
new file mode 100644
index 0000000..95b597b
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-qa/src/main/java/org/dromara/qa/qm/domain/bo/QmJudgeBo.java
@@ -0,0 +1,81 @@
+package org.dromara.qa.qm.domain.bo;
+
+import org.dromara.qa.qm.domain.QmJudge;
+import org.dromara.common.mybatis.core.domain.BaseEntity;
+import org.dromara.common.core.validate.AddGroup;
+import org.dromara.common.core.validate.EditGroup;
+import io.github.linpeilie.annotations.AutoMapper;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+import jakarta.validation.constraints.*;
+import java.util.Date;
+import com.fasterxml.jackson.annotation.JsonFormat;
+
+/**
+ * 鍒ゅ畾渚濇嵁涓氬姟瀵硅薄 qm_judge
+ *
+ * @author zhuguifei
+ * @date 2026-03-18
+ */
+@Data
+@EqualsAndHashCode(callSuper = true)
+@AutoMapper(target = QmJudge.class, reverseConvertGenerate = false)
+public class QmJudgeBo extends BaseEntity {
+    private String id;
+    /**
+     * 鐗╂枡鐗屽彿浠g爜
+     */
+    private String matCode;
+
+    /**
+     * 鐗╂枡鐗屽彿
+     */
+    private String matName;
+
+    /**
+     * 鐘舵��
+     */
+    private Long status;
+
+    /**
+     * 鍒ゅ畾瑙勭▼COD
+     */
+    private String stdCod;
+
+    /**
+     * 鍒涘缓鏃堕棿
+     */
+    private Date cdate;
+
+    /**
+     * 鐢熸晥浜�/鎿嶄綔浜哄憳
+     */
+    private String oper;
+
+    /**
+     * 澶囨敞
+     */
+    private String des;
+
+    /**
+     * 鐗╂枡绫诲瀷
+     */
+    private Long category;
+
+    /**
+     * 杈呮枡绫诲瀷缂栫爜
+     */
+    private String typeCode;
+
+    /**
+     * 杈呮枡绫诲瀷鍚嶇О
+     */
+    private String typeName;
+
+    /**
+     * 鍒ゅ畾鍚嶇О
+     */
+    private String judgeName;
+
+
+}
diff --git a/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-qa/src/main/java/org/dromara/qa/qm/domain/bo/QmJudgeDetailsBo.java b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-qa/src/main/java/org/dromara/qa/qm/domain/bo/QmJudgeDetailsBo.java
new file mode 100644
index 0000000..9fd1f63
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-qa/src/main/java/org/dromara/qa/qm/domain/bo/QmJudgeDetailsBo.java
@@ -0,0 +1,99 @@
+package org.dromara.qa.qm.domain.bo;
+
+import org.dromara.qa.qm.domain.QmJudgeDetails;
+import org.dromara.common.mybatis.core.domain.BaseEntity;
+import org.dromara.common.core.validate.AddGroup;
+import org.dromara.common.core.validate.EditGroup;
+import io.github.linpeilie.annotations.AutoMapper;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+import jakarta.validation.constraints.*;
+
+/**
+ * 鍒ゅ畾妯℃澘鏄庣粏涓氬姟瀵硅薄 qm_judge_details
+ *
+ * @author 鏈辨椋�
+ * @date 2026-03-20
+ */
+@Data
+@EqualsAndHashCode(callSuper = true)
+@AutoMapper(target = QmJudgeDetails.class, reverseConvertGenerate = false)
+public class QmJudgeDetailsBo extends BaseEntity {
+
+    /**
+     * 缂栫爜
+     */
+    private String id;
+
+    /**
+     * 鍒ゅ畾涓绘爣璇�
+     */
+    private String judgeId;
+
+    /**
+     * 鍒ゅ畾椤笽TEM
+     */
+    private String itemCod;
+
+    /**
+     * 鍒ゅ畾椤筃AME
+     */
+    private String itemName;
+
+    /**
+     * 鏍囧噯鍊�
+     */
+    private Long value3;
+
+    /**
+     * 鍒ゅ畾鍊�1
+     */
+    private Long value1;
+
+    /**
+     * 鍒ゅ畾鍊�2
+     */
+    private Long value2;
+
+    /**
+     * 缂洪櫡浣嶇疆
+     */
+    private String location;
+
+    /**
+     * 鍒ゅ畾绾у埆 (A,B,C,D)
+     */
+    private String cls;
+
+    /**
+     * 鍒嗗�兼爣鍑� (鎵e垎鏍囧噯锛屽緱鍒嗘爣鍑�),姣斿涓嶅悎鏍间竴娆℃墸澶氬皯鍒�
+     */
+    private Long stdscore;
+
+    /**
+     * 鏍囪姝ら」鏄惁涓哄悎鎴愰」鐩紝姣斿澶栬锛屽疄闄呬笂鍏宠仈浜嗗緢澶氬瓙椤圭洰
+     */
+    private Long ismix;
+
+    /**
+     * 鑻ユ瀛楁鏈塙UID鍊硷紝琛ㄦ槑瀹冨彲鑳戒负鍏朵粬椤圭洰鐨勫瓙椤癸紝姣斿鈥滅┖澶粹��,瀹冧负鐑熸敮澶栬椤圭洰鐨勫瓙椤�
+     */
+    private String rid;
+
+    /**
+     * 鑼冨洿-澶囩敤
+     */
+    private Long category;
+
+    /**
+     * 澶囨敞
+     */
+    private String decisionDes;
+
+    /**
+     * 淇敼浜�
+     */
+    private String updateUser;
+
+
+}
diff --git a/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-qa/src/main/java/org/dromara/qa/qm/domain/vo/QmJudgeDetailsVo.java b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-qa/src/main/java/org/dromara/qa/qm/domain/vo/QmJudgeDetailsVo.java
new file mode 100644
index 0000000..f005ed1
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-qa/src/main/java/org/dromara/qa/qm/domain/vo/QmJudgeDetailsVo.java
@@ -0,0 +1,116 @@
+package org.dromara.qa.qm.domain.vo;
+
+import org.dromara.qa.qm.domain.QmJudgeDetails;
+import cn.idev.excel.annotation.ExcelIgnoreUnannotated;
+import cn.idev.excel.annotation.ExcelProperty;
+import org.dromara.common.excel.annotation.ExcelDictFormat;
+import org.dromara.common.excel.convert.ExcelDictConvert;
+import io.github.linpeilie.annotations.AutoMapper;
+import lombok.Data;
+
+import java.io.Serial;
+import java.io.Serializable;
+import java.util.Date;
+
+
+
+/**
+ * 鍒ゅ畾妯℃澘鏄庣粏瑙嗗浘瀵硅薄 qm_judge_details
+ *
+ * @author 鏈辨椋�
+ * @date 2026-03-20
+ */
+@Data
+@ExcelIgnoreUnannotated
+@AutoMapper(target = QmJudgeDetails.class)
+public class QmJudgeDetailsVo implements Serializable {
+
+    @Serial
+    private static final long serialVersionUID = 1L;
+
+    /**
+     * 鍒ゅ畾涓绘爣璇�
+     */
+    @ExcelProperty(value = "鍒ゅ畾涓绘爣璇�")
+    private String judgeId;
+
+    /**
+     * 鍒ゅ畾椤笽TEM
+     */
+    @ExcelProperty(value = "鍒ゅ畾椤笽TEM")
+    private String itemCod;
+
+    /**
+     * 鍒ゅ畾椤筃AME
+     */
+    @ExcelProperty(value = "鍒ゅ畾椤筃AME")
+    private String itemName;
+
+    /**
+     * 鏍囧噯鍊�
+     */
+    @ExcelProperty(value = "鏍囧噯鍊�")
+    private Long value3;
+
+    /**
+     * 鍒ゅ畾鍊�1
+     */
+    @ExcelProperty(value = "鍒ゅ畾鍊�1")
+    private Long value1;
+
+    /**
+     * 鍒ゅ畾鍊�2
+     */
+    @ExcelProperty(value = "鍒ゅ畾鍊�2")
+    private Long value2;
+
+    /**
+     * 缂洪櫡浣嶇疆
+     */
+    @ExcelProperty(value = "缂洪櫡浣嶇疆")
+    private String location;
+
+    /**
+     * 鍒ゅ畾绾у埆 (A,B,C,D)
+     */
+    @ExcelProperty(value = "鍒ゅ畾绾у埆 (A,B,C,D)")
+    private String cls;
+
+    /**
+     * 鍒嗗�兼爣鍑� (鎵e垎鏍囧噯锛屽緱鍒嗘爣鍑�),姣斿涓嶅悎鏍间竴娆℃墸澶氬皯鍒�
+     */
+    @ExcelProperty(value = "鍒嗗�兼爣鍑� (鎵e垎鏍囧噯锛屽緱鍒嗘爣鍑�),姣斿涓嶅悎鏍间竴娆℃墸澶氬皯鍒�")
+    private Long stdscore;
+
+    /**
+     * 鏍囪姝ら」鏄惁涓哄悎鎴愰」鐩紝姣斿澶栬锛屽疄闄呬笂鍏宠仈浜嗗緢澶氬瓙椤圭洰
+     */
+    @ExcelProperty(value = "鏍囪姝ら」鏄惁涓哄悎鎴愰」鐩紝姣斿澶栬锛屽疄闄呬笂鍏宠仈浜嗗緢澶氬瓙椤圭洰")
+    private Long ismix;
+
+    /**
+     * 鑻ユ瀛楁鏈塙UID鍊硷紝琛ㄦ槑瀹冨彲鑳戒负鍏朵粬椤圭洰鐨勫瓙椤癸紝姣斿鈥滅┖澶粹��,瀹冧负鐑熸敮澶栬椤圭洰鐨勫瓙椤�
+     */
+    @ExcelProperty(value = "鑻ユ瀛楁鏈塙UID鍊硷紝琛ㄦ槑瀹冨彲鑳戒负鍏朵粬椤圭洰鐨勫瓙椤癸紝姣斿鈥滅┖澶粹��,瀹冧负鐑熸敮澶栬椤圭洰鐨勫瓙椤�")
+    private String rid;
+
+    /**
+     * 鑼冨洿-澶囩敤
+     */
+    @ExcelProperty(value = "鑼冨洿-澶囩敤")
+    private Long category;
+
+    /**
+     * 澶囨敞
+     */
+    @ExcelProperty(value = "澶囨敞")
+    private String decisionDes;
+
+    /**
+     * 淇敼浜�
+     */
+    @ExcelProperty(value = "淇敼浜�")
+    private String updateUser;
+
+
+}
diff --git a/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-qa/src/main/java/org/dromara/qa/qm/domain/vo/QmJudgeVo.java b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-qa/src/main/java/org/dromara/qa/qm/domain/vo/QmJudgeVo.java
new file mode 100644
index 0000000..e0fd2d8
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-qa/src/main/java/org/dromara/qa/qm/domain/vo/QmJudgeVo.java
@@ -0,0 +1,114 @@
+package org.dromara.qa.qm.domain.vo;
+
+import java.util.Date;
+import com.fasterxml.jackson.annotation.JsonFormat;
+import org.dromara.common.translation.annotation.Translation;
+import org.dromara.common.translation.constant.TransConstant;
+import org.dromara.qa.qm.domain.QmJudge;
+import cn.idev.excel.annotation.ExcelIgnoreUnannotated;
+import cn.idev.excel.annotation.ExcelProperty;
+import org.dromara.common.excel.annotation.ExcelDictFormat;
+import org.dromara.common.excel.convert.ExcelDictConvert;
+import io.github.linpeilie.annotations.AutoMapper;
+import lombok.Data;
+
+import java.io.Serial;
+import java.io.Serializable;
+import java.util.Date;
+
+
+
+/**
+ * 鍒ゅ畾渚濇嵁瑙嗗浘瀵硅薄 qm_judge
+ *
+ * @author zhuguifei
+ * @date 2026-03-18
+ */
+@Data
+@ExcelIgnoreUnannotated
+@AutoMapper(target = QmJudge.class)
+public class QmJudgeVo implements Serializable {
+
+    @Serial
+    private static final long serialVersionUID = 1L;
+
+    /**
+     * 缂栫爜
+     */
+    @ExcelProperty(value = "缂栫爜")
+    private String id;
+
+    /**
+     * 鐗╂枡鐗屽彿浠g爜
+     */
+    @ExcelProperty(value = "鐗╂枡鐗屽彿浠g爜")
+    private String matCode;
+
+    /**
+     * 鐗╂枡鐗屽彿
+     */
+    @ExcelProperty(value = "鐗╂枡鐗屽彿")
+    private String matName;
+
+    /**
+     * 鐘舵��
+     */
+    @ExcelProperty(value = "鐘舵��")
+    private Long status;
+
+    /**
+     * 鍒ゅ畾瑙勭▼COD
+     */
+    @ExcelProperty(value = "鍒ゅ畾瑙勭▼COD")
+    private String stdCod;
+
+    /**
+     * 鍒ゅ畾瑙勭▼鍚嶇О
+     */
+    @ExcelProperty(value = "鍒ゅ畾瑙勭▼鍚嶇О")
+    private String stdName;
+
+    /**
+     * 鍒涘缓鏃堕棿
+     */
+    @ExcelProperty(value = "鍒涘缓鏃堕棿")
+    private Date cdate;
+
+    /**
+     * 鐢熸晥浜�/鎿嶄綔浜哄憳
+     */
+    @ExcelProperty(value = "鐢熸晥浜�/鎿嶄綔浜哄憳")
+    private String oper;
+
+    /**
+     * 澶囨敞
+     */
+    @ExcelProperty(value = "澶囨敞")
+    private String des;
+
+    /**
+     * 鐗╂枡绫诲瀷
+     */
+    @ExcelProperty(value = "鐗╂枡绫诲瀷")
+    private Long category;
+
+    /**
+     * 杈呮枡绫诲瀷缂栫爜
+     */
+    @ExcelProperty(value = "杈呮枡绫诲瀷缂栫爜")
+    private String typeCode;
+
+    /**
+     * 杈呮枡绫诲瀷鍚嶇О
+     */
+    @ExcelProperty(value = "杈呮枡绫诲瀷鍚嶇О")
+    private String typeName;
+
+    /**
+     * 鍒ゅ畾鍚嶇О
+     */
+    @ExcelProperty(value = "鍒ゅ畾鍚嶇О")
+    private String judgeName;
+
+
+}
diff --git a/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-qa/src/main/java/org/dromara/qa/qm/mapper/QmJudgeDetailsMapper.java b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-qa/src/main/java/org/dromara/qa/qm/mapper/QmJudgeDetailsMapper.java
new file mode 100644
index 0000000..8cae68a
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-qa/src/main/java/org/dromara/qa/qm/mapper/QmJudgeDetailsMapper.java
@@ -0,0 +1,15 @@
+package org.dromara.qa.qm.mapper;
+
+import org.dromara.qa.qm.domain.QmJudgeDetails;
+import org.dromara.qa.qm.domain.vo.QmJudgeDetailsVo;
+import org.dromara.common.mybatis.core.mapper.BaseMapperPlus;
+
+/**
+ * 鍒ゅ畾妯℃澘鏄庣粏Mapper鎺ュ彛
+ *
+ * @author 鏈辨椋�
+ * @date 2026-03-20
+ */
+public interface QmJudgeDetailsMapper extends BaseMapperPlus<QmJudgeDetails, QmJudgeDetailsVo> {
+
+}
diff --git a/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-qa/src/main/java/org/dromara/qa/qm/mapper/QmJudgeMapper.java b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-qa/src/main/java/org/dromara/qa/qm/mapper/QmJudgeMapper.java
new file mode 100644
index 0000000..221df76
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-qa/src/main/java/org/dromara/qa/qm/mapper/QmJudgeMapper.java
@@ -0,0 +1,15 @@
+package org.dromara.qa.qm.mapper;
+
+import org.dromara.qa.qm.domain.QmJudge;
+import org.dromara.qa.qm.domain.vo.QmJudgeVo;
+import org.dromara.common.mybatis.core.mapper.BaseMapperPlus;
+
+/**
+ * 鍒ゅ畾渚濇嵁Mapper鎺ュ彛
+ *
+ * @author zhuguifei
+ * @date 2026-03-18
+ */
+public interface QmJudgeMapper extends BaseMapperPlus<QmJudge, QmJudgeVo> {
+
+}
diff --git a/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-qa/src/main/java/org/dromara/qa/qm/service/IQmJudgeDetailsService.java b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-qa/src/main/java/org/dromara/qa/qm/service/IQmJudgeDetailsService.java
new file mode 100644
index 0000000..d6506ed
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-qa/src/main/java/org/dromara/qa/qm/service/IQmJudgeDetailsService.java
@@ -0,0 +1,68 @@
+package org.dromara.qa.qm.service;
+
+import org.dromara.qa.qm.domain.vo.QmJudgeDetailsVo;
+import org.dromara.qa.qm.domain.bo.QmJudgeDetailsBo;
+import org.dromara.common.mybatis.core.page.TableDataInfo;
+import org.dromara.common.mybatis.core.page.PageQuery;
+
+import java.util.Collection;
+import java.util.List;
+
+/**
+ * 鍒ゅ畾妯℃澘鏄庣粏Service鎺ュ彛
+ *
+ * @author 鏈辨椋�
+ * @date 2026-03-20
+ */
+public interface IQmJudgeDetailsService {
+
+    /**
+     * 鏌ヨ鍒ゅ畾妯℃澘鏄庣粏
+     *
+     * @param id 涓婚敭
+     * @return 鍒ゅ畾妯℃澘鏄庣粏
+     */
+    QmJudgeDetailsVo queryById(String id);
+
+    /**
+     * 鍒嗛〉鏌ヨ鍒ゅ畾妯℃澘鏄庣粏鍒楄〃
+     *
+     * @param bo        鏌ヨ鏉′欢
+     * @param pageQuery 鍒嗛〉鍙傛暟
+     * @return 鍒ゅ畾妯℃澘鏄庣粏鍒嗛〉鍒楄〃
+     */
+    TableDataInfo<QmJudgeDetailsVo> queryPageList(QmJudgeDetailsBo bo, PageQuery pageQuery);
+
+    /**
+     * 鏌ヨ绗﹀悎鏉′欢鐨勫垽瀹氭ā鏉挎槑缁嗗垪琛�
+     *
+     * @param bo 鏌ヨ鏉′欢
+     * @return 鍒ゅ畾妯℃澘鏄庣粏鍒楄〃
+     */
+    List<QmJudgeDetailsVo> queryList(QmJudgeDetailsBo bo);
+
+    /**
+     * 鏂板鍒ゅ畾妯℃澘鏄庣粏
+     *
+     * @param bo 鍒ゅ畾妯℃澘鏄庣粏
+     * @return 鏄惁鏂板鎴愬姛
+     */
+    Boolean insertByBo(QmJudgeDetailsBo bo);
+
+    /**
+     * 淇敼鍒ゅ畾妯℃澘鏄庣粏
+     *
+     * @param bo 鍒ゅ畾妯℃澘鏄庣粏
+     * @return 鏄惁淇敼鎴愬姛
+     */
+    Boolean updateByBo(QmJudgeDetailsBo bo);
+
+    /**
+     * 鏍¢獙骞舵壒閲忓垹闄ゅ垽瀹氭ā鏉挎槑缁嗕俊鎭�
+     *
+     * @param ids     寰呭垹闄ょ殑涓婚敭闆嗗悎
+     * @param isValid 鏄惁杩涜鏈夋晥鎬ф牎楠�
+     * @return 鏄惁鍒犻櫎鎴愬姛
+     */
+    Boolean deleteWithValidByIds(Collection<String> ids, Boolean isValid);
+}
diff --git a/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-qa/src/main/java/org/dromara/qa/qm/service/IQmJudgeService.java b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-qa/src/main/java/org/dromara/qa/qm/service/IQmJudgeService.java
new file mode 100644
index 0000000..9748a94
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-qa/src/main/java/org/dromara/qa/qm/service/IQmJudgeService.java
@@ -0,0 +1,68 @@
+package org.dromara.qa.qm.service;
+
+import org.dromara.qa.qm.domain.vo.QmJudgeVo;
+import org.dromara.qa.qm.domain.bo.QmJudgeBo;
+import org.dromara.common.mybatis.core.page.TableDataInfo;
+import org.dromara.common.mybatis.core.page.PageQuery;
+
+import java.util.Collection;
+import java.util.List;
+
+/**
+ * 鍒ゅ畾渚濇嵁Service鎺ュ彛
+ *
+ * @author zhuguifei
+ * @date 2026-03-18
+ */
+public interface IQmJudgeService {
+
+    /**
+     * 鏌ヨ鍒ゅ畾渚濇嵁
+     *
+     * @param id 涓婚敭
+     * @return 鍒ゅ畾渚濇嵁
+     */
+    QmJudgeVo queryById(String id);
+
+    /**
+     * 鍒嗛〉鏌ヨ鍒ゅ畾渚濇嵁鍒楄〃
+     *
+     * @param bo        鏌ヨ鏉′欢
+     * @param pageQuery 鍒嗛〉鍙傛暟
+     * @return 鍒ゅ畾渚濇嵁鍒嗛〉鍒楄〃
+     */
+    TableDataInfo<QmJudgeVo> queryPageList(QmJudgeBo bo, PageQuery pageQuery);
+
+    /**
+     * 鏌ヨ绗﹀悎鏉′欢鐨勫垽瀹氫緷鎹垪琛�
+     *
+     * @param bo 鏌ヨ鏉′欢
+     * @return 鍒ゅ畾渚濇嵁鍒楄〃
+     */
+    List<QmJudgeVo> queryList(QmJudgeBo bo);
+
+    /**
+     * 鏂板鍒ゅ畾渚濇嵁
+     *
+     * @param bo 鍒ゅ畾渚濇嵁
+     * @return 鏄惁鏂板鎴愬姛
+     */
+    Boolean insertByBo(QmJudgeBo bo);
+
+    /**
+     * 淇敼鍒ゅ畾渚濇嵁
+     *
+     * @param bo 鍒ゅ畾渚濇嵁
+     * @return 鏄惁淇敼鎴愬姛
+     */
+    Boolean updateByBo(QmJudgeBo bo);
+
+    /**
+     * 鏍¢獙骞舵壒閲忓垹闄ゅ垽瀹氫緷鎹俊鎭�
+     *
+     * @param ids     寰呭垹闄ょ殑涓婚敭闆嗗悎
+     * @param isValid 鏄惁杩涜鏈夋晥鎬ф牎楠�
+     * @return 鏄惁鍒犻櫎鎴愬姛
+     */
+    Boolean deleteWithValidByIds(Collection<String> ids, Boolean isValid);
+}
diff --git a/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-qa/src/main/java/org/dromara/qa/qm/service/impl/QmJudgeDetailsServiceImpl.java b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-qa/src/main/java/org/dromara/qa/qm/service/impl/QmJudgeDetailsServiceImpl.java
new file mode 100644
index 0000000..ccfb91d
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-qa/src/main/java/org/dromara/qa/qm/service/impl/QmJudgeDetailsServiceImpl.java
@@ -0,0 +1,135 @@
+package org.dromara.qa.qm.service.impl;
+
+import org.dromara.common.core.utils.MapstructUtils;
+import org.dromara.common.core.utils.StringUtils;
+import org.dromara.common.mybatis.core.page.TableDataInfo;
+import org.dromara.common.mybatis.core.page.PageQuery;
+import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+import com.baomidou.mybatisplus.core.toolkit.Wrappers;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.stereotype.Service;
+import org.dromara.qa.qm.domain.bo.QmJudgeDetailsBo;
+import org.dromara.qa.qm.domain.vo.QmJudgeDetailsVo;
+import org.dromara.qa.qm.domain.QmJudgeDetails;
+import org.dromara.qa.qm.mapper.QmJudgeDetailsMapper;
+import org.dromara.qa.qm.service.IQmJudgeDetailsService;
+
+import java.util.List;
+import java.util.Map;
+import java.util.Collection;
+
+/**
+ * 鍒ゅ畾妯℃澘鏄庣粏Service涓氬姟灞傚鐞�
+ *
+ * @author 鏈辨椋�
+ * @date 2026-03-20
+ */
+@Slf4j
+@RequiredArgsConstructor
+@Service
+public class QmJudgeDetailsServiceImpl implements IQmJudgeDetailsService {
+
+    private final QmJudgeDetailsMapper baseMapper;
+
+    /**
+     * 鏌ヨ鍒ゅ畾妯℃澘鏄庣粏
+     *
+     * @param id 涓婚敭
+     * @return 鍒ゅ畾妯℃澘鏄庣粏
+     */
+    @Override
+    public QmJudgeDetailsVo queryById(String id){
+        return baseMapper.selectVoById(id);
+    }
+
+    /**
+     * 鍒嗛〉鏌ヨ鍒ゅ畾妯℃澘鏄庣粏鍒楄〃
+     *
+     * @param bo        鏌ヨ鏉′欢
+     * @param pageQuery 鍒嗛〉鍙傛暟
+     * @return 鍒ゅ畾妯℃澘鏄庣粏鍒嗛〉鍒楄〃
+     */
+    @Override
+    public TableDataInfo<QmJudgeDetailsVo> queryPageList(QmJudgeDetailsBo bo, PageQuery pageQuery) {
+        LambdaQueryWrapper<QmJudgeDetails> lqw = buildQueryWrapper(bo);
+        Page<QmJudgeDetailsVo> result = baseMapper.selectVoPage(pageQuery.build(), lqw);
+        return TableDataInfo.build(result);
+    }
+
+    /**
+     * 鏌ヨ绗﹀悎鏉′欢鐨勫垽瀹氭ā鏉挎槑缁嗗垪琛�
+     *
+     * @param bo 鏌ヨ鏉′欢
+     * @return 鍒ゅ畾妯℃澘鏄庣粏鍒楄〃
+     */
+    @Override
+    public List<QmJudgeDetailsVo> queryList(QmJudgeDetailsBo bo) {
+        LambdaQueryWrapper<QmJudgeDetails> lqw = buildQueryWrapper(bo);
+        return baseMapper.selectVoList(lqw);
+    }
+
+    private LambdaQueryWrapper<QmJudgeDetails> buildQueryWrapper(QmJudgeDetailsBo bo) {
+        Map<String, Object> params = bo.getParams();
+        LambdaQueryWrapper<QmJudgeDetails> lqw = Wrappers.lambdaQuery();
+        lqw.eq(StringUtils.isNotBlank(bo.getJudgeId()), QmJudgeDetails::getJudgeId, bo.getJudgeId());
+        lqw.eq(StringUtils.isNotBlank(bo.getItemCod()), QmJudgeDetails::getItemCod, bo.getItemCod());
+        lqw.like(StringUtils.isNotBlank(bo.getItemName()), QmJudgeDetails::getItemName, bo.getItemName());
+        lqw.eq(StringUtils.isNotBlank(bo.getCls()), QmJudgeDetails::getCls, bo.getCls());
+        lqw.eq(bo.getIsmix() != null, QmJudgeDetails::getIsmix, bo.getIsmix());
+        return lqw;
+    }
+
+    /**
+     * 鏂板鍒ゅ畾妯℃澘鏄庣粏
+     *
+     * @param bo 鍒ゅ畾妯℃澘鏄庣粏
+     * @return 鏄惁鏂板鎴愬姛
+     */
+    @Override
+    public Boolean insertByBo(QmJudgeDetailsBo bo) {
+        QmJudgeDetails add = MapstructUtils.convert(bo, QmJudgeDetails.class);
+        validEntityBeforeSave(add);
+        boolean flag = baseMapper.insert(add) > 0;
+        if (flag) {
+            bo.setId(add.getId());
+        }
+        return flag;
+    }
+
+    /**
+     * 淇敼鍒ゅ畾妯℃澘鏄庣粏
+     *
+     * @param bo 鍒ゅ畾妯℃澘鏄庣粏
+     * @return 鏄惁淇敼鎴愬姛
+     */
+    @Override
+    public Boolean updateByBo(QmJudgeDetailsBo bo) {
+        QmJudgeDetails update = MapstructUtils.convert(bo, QmJudgeDetails.class);
+        validEntityBeforeSave(update);
+        return baseMapper.updateById(update) > 0;
+    }
+
+    /**
+     * 淇濆瓨鍓嶇殑鏁版嵁鏍¢獙
+     */
+    private void validEntityBeforeSave(QmJudgeDetails entity){
+        //TODO 鍋氫竴浜涙暟鎹牎楠�,濡傚敮涓�绾︽潫
+    }
+
+    /**
+     * 鏍¢獙骞舵壒閲忓垹闄ゅ垽瀹氭ā鏉挎槑缁嗕俊鎭�
+     *
+     * @param ids     寰呭垹闄ょ殑涓婚敭闆嗗悎
+     * @param isValid 鏄惁杩涜鏈夋晥鎬ф牎楠�
+     * @return 鏄惁鍒犻櫎鎴愬姛
+     */
+    @Override
+    public Boolean deleteWithValidByIds(Collection<String> ids, Boolean isValid) {
+        if(isValid){
+            //TODO 鍋氫竴浜涗笟鍔′笂鐨勬牎楠�,鍒ゆ柇鏄惁闇�瑕佹牎楠�
+        }
+        return baseMapper.deleteByIds(ids) > 0;
+    }
+}
diff --git a/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-qa/src/main/java/org/dromara/qa/qm/service/impl/QmJudgeServiceImpl.java b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-qa/src/main/java/org/dromara/qa/qm/service/impl/QmJudgeServiceImpl.java
new file mode 100644
index 0000000..55b949c
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-qa/src/main/java/org/dromara/qa/qm/service/impl/QmJudgeServiceImpl.java
@@ -0,0 +1,174 @@
+package org.dromara.qa.qm.service.impl;
+
+import org.dromara.common.core.utils.MapstructUtils;
+import org.dromara.common.core.utils.StringUtils;
+import org.dromara.common.mybatis.core.page.TableDataInfo;
+import org.dromara.common.mybatis.core.page.PageQuery;
+import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+import com.baomidou.mybatisplus.core.toolkit.Wrappers;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.dromara.qa.qm.domain.QmStd;
+import org.springframework.stereotype.Service;
+import org.dromara.qa.qm.domain.bo.QmJudgeBo;
+import org.dromara.qa.qm.domain.vo.QmJudgeVo;
+import org.dromara.qa.qm.domain.QmJudge;
+import org.dromara.qa.qm.mapper.QmJudgeMapper;
+import org.dromara.qa.qm.service.IQmJudgeService;
+import org.dromara.qa.qm.service.IQmStdService;
+import org.dromara.qa.qm.mapper.QmStdMapper;
+
+import java.util.List;
+import java.util.Map;
+import java.util.Collection;
+import java.util.Set;
+import java.util.stream.Collectors;
+import java.util.HashMap;
+
+/**
+ * 鍒ゅ畾渚濇嵁Service涓氬姟灞傚鐞�
+ *
+ * @author zhuguifei
+ * @date 2026-03-18
+ */
+@Slf4j
+@RequiredArgsConstructor
+@Service
+public class QmJudgeServiceImpl implements IQmJudgeService {
+
+    private final QmJudgeMapper baseMapper;
+    private final QmStdMapper qmStdMapper;
+
+    /**
+     * 鏌ヨ鍒ゅ畾渚濇嵁
+     *
+     * @param id 涓婚敭
+     * @return 鍒ゅ畾渚濇嵁
+     */
+    @Override
+    public QmJudgeVo queryById(String id){
+        return baseMapper.selectVoById(id);
+    }
+
+    /**
+     * 鍒嗛〉鏌ヨ鍒ゅ畾渚濇嵁鍒楄〃
+     *
+     * @param bo        鏌ヨ鏉′欢
+     * @param pageQuery 鍒嗛〉鍙傛暟
+     * @return 鍒ゅ畾渚濇嵁鍒嗛〉鍒楄〃
+     */
+    @Override
+    public TableDataInfo<QmJudgeVo> queryPageList(QmJudgeBo bo, PageQuery pageQuery) {
+        LambdaQueryWrapper<QmJudge> lqw = buildQueryWrapper(bo);
+        Page<QmJudgeVo> result = baseMapper.selectVoPage(pageQuery.build(), lqw);
+        queryQmStd(result);
+        return TableDataInfo.build(result);
+    }
+
+    private void queryQmStd(Page<QmJudgeVo> result) {
+        if (result == null || result.getRecords() == null || result.getRecords().isEmpty()) {
+            return;
+        }
+        List<QmJudgeVo> records = result.getRecords();
+
+        // 1. Collect all unique stdCode values
+        Set<String> stdCodes = records.stream()
+            .map(QmJudgeVo::getStdCod)
+            .filter(StringUtils::isNotBlank)
+            .collect(Collectors.toSet());
+
+        if (stdCodes.isEmpty()) {
+            return;
+        }
+
+        // 2. Query QmStd entities based on the collected stdCode values
+        LambdaQueryWrapper<QmStd> stdLqw = Wrappers.lambdaQuery();
+        stdLqw.in(QmStd::getId, stdCodes);
+        List<QmStd> qmStds = qmStdMapper.selectList(stdLqw);
+
+        // 3. Create a map from stdCode to stdName for easy lookup
+        Map<String, String> stdCodeToNameMap = qmStds.stream()
+            .collect(Collectors.toMap(QmStd::getId, QmStd::getStdName, (oldValue, newValue) -> oldValue));
+
+        // 4. Populate stdName for each QmJudgeVo
+        records.forEach(judgeVo ->
+            judgeVo.setStdName(stdCodeToNameMap.getOrDefault(judgeVo.getStdCod(), null))
+        );
+    }
+
+    /**
+     * 鏌ヨ绗﹀悎鏉′欢鐨勫垽瀹氫緷鎹垪琛�
+     *
+     * @param bo 鏌ヨ鏉′欢
+     * @return 鍒ゅ畾渚濇嵁鍒楄〃
+     */
+    @Override
+    public List<QmJudgeVo> queryList(QmJudgeBo bo) {
+        LambdaQueryWrapper<QmJudge> lqw = buildQueryWrapper(bo);
+        return baseMapper.selectVoList(lqw);
+    }
+
+    private LambdaQueryWrapper<QmJudge> buildQueryWrapper(QmJudgeBo bo) {
+        Map<String, Object> params = bo.getParams();
+        LambdaQueryWrapper<QmJudge> lqw = Wrappers.lambdaQuery();
+        lqw.like(StringUtils.isNotBlank(bo.getMatCode()), QmJudge::getMatCode, bo.getMatCode());
+        lqw.like(StringUtils.isNotBlank(bo.getMatName()), QmJudge::getMatName, bo.getMatName());
+        lqw.like(StringUtils.isNotBlank(bo.getJudgeName()), QmJudge::getJudgeName, bo.getJudgeName());
+        lqw.eq(bo.getStatus()!=null,QmJudge::getStatus, bo.getStatus());
+        lqw.eq(bo.getCategory()!=null,QmJudge::getCategory, bo.getCategory());
+        return lqw;
+    }
+
+    /**
+     * 鏂板鍒ゅ畾渚濇嵁
+     *
+     * @param bo 鍒ゅ畾渚濇嵁
+     * @return 鏄惁鏂板鎴愬姛
+     */
+    @Override
+    public Boolean insertByBo(QmJudgeBo bo) {
+        QmJudge add = MapstructUtils.convert(bo, QmJudge.class);
+        validEntityBeforeSave(add);
+        boolean flag = baseMapper.insert(add) > 0;
+        if (flag) {
+            bo.setId(add.getId());
+        }
+        return flag;
+    }
+
+    /**
+     * 淇敼鍒ゅ畾渚濇嵁
+     *
+     * @param bo 鍒ゅ畾渚濇嵁
+     * @return 鏄惁淇敼鎴愬姛
+     */
+    @Override
+    public Boolean updateByBo(QmJudgeBo bo) {
+        QmJudge update = MapstructUtils.convert(bo, QmJudge.class);
+        validEntityBeforeSave(update);
+        return baseMapper.updateById(update) > 0;
+    }
+
+    /**
+     * 淇濆瓨鍓嶇殑鏁版嵁鏍¢獙
+     */
+    private void validEntityBeforeSave(QmJudge entity){
+        //TODO 鍋氫竴浜涙暟鎹牎楠�,濡傚敮涓�绾︽潫
+    }
+
+    /**
+     * 鏍¢獙骞舵壒閲忓垹闄ゅ垽瀹氫緷鎹俊鎭�
+     *
+     * @param ids     寰呭垹闄ょ殑涓婚敭闆嗗悎
+     * @param isValid 鏄惁杩涜鏈夋晥鎬ф牎楠�
+     * @return 鏄惁鍒犻櫎鎴愬姛
+     */
+    @Override
+    public Boolean deleteWithValidByIds(Collection<String> ids, Boolean isValid) {
+        if(isValid){
+            //TODO 鍋氫竴浜涗笟鍔′笂鐨勬牎楠�,鍒ゆ柇鏄惁闇�瑕佹牎楠�
+        }
+        return baseMapper.deleteByIds(ids) > 0;
+    }
+}
diff --git a/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-qa/src/main/resources/mapper/qa/md/MdInstrumentMapper.xml b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-qa/src/main/resources/mapper/qa/md/MdInstrumentMapper.xml
new file mode 100644
index 0000000..783ea3f
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-qa/src/main/resources/mapper/qa/md/MdInstrumentMapper.xml
@@ -0,0 +1,6 @@
+<?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.MdInstrumentMapper">
+</mapper>
diff --git a/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-qa/src/main/resources/mapper/qa/qm/QmJudgeDetailsMapper.xml b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-qa/src/main/resources/mapper/qa/qm/QmJudgeDetailsMapper.xml
new file mode 100644
index 0000000..5564d04
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-qa/src/main/resources/mapper/qa/qm/QmJudgeDetailsMapper.xml
@@ -0,0 +1,6 @@
+<?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.qm.mapper.QmJudgeDetailsMapper">
+</mapper>
diff --git a/ruoyi-plus-soybean/.gitignore b/ruoyi-plus-soybean/.gitignore
index fdd159a..92f30a5 100755
--- a/ruoyi-plus-soybean/.gitignore
+++ b/ruoyi-plus-soybean/.gitignore
@@ -33,3 +33,5 @@
 yarn.lock
 
 .VSCodeCounter
+
+/pms/
diff --git a/ruoyi-plus-soybean/src/router/elegant/imports.ts b/ruoyi-plus-soybean/src/router/elegant/imports.ts
index 600d23d..56a0110 100755
--- a/ruoyi-plus-soybean/src/router/elegant/imports.ts
+++ b/ruoyi-plus-soybean/src/router/elegant/imports.ts
@@ -32,12 +32,15 @@
   demo_demo: () => import("@/views/demo/demo/index.vue"),
   demo_tree: () => import("@/views/demo/tree/index.vue"),
   home: () => import("@/views/home/index.vue"),
+  md_instrument: () => import("@/views/md/instrument/index.vue"),
   md_shift: () => import("@/views/md/shift/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"),
   monitor_operlog: () => import("@/views/monitor/operlog/index.vue"),
   qm_batch: () => import("@/views/qm/batch/index.vue"),
+  "qm_judge-details": () => import("@/views/qm/judge-details/index.vue"),
+  qm_judge: () => import("@/views/qm/judge/index.vue"),
   qm_std: () => import("@/views/qm/std/index.vue"),
   report_demo: () => import("@/views/report/demo/index.vue"),
   "report_silk-storage-output": () => import("@/views/report/silk-storage-output/index.vue"),
diff --git a/ruoyi-plus-soybean/src/router/elegant/routes.ts b/ruoyi-plus-soybean/src/router/elegant/routes.ts
index 193441e..1a339b1 100755
--- a/ruoyi-plus-soybean/src/router/elegant/routes.ts
+++ b/ruoyi-plus-soybean/src/router/elegant/routes.ts
@@ -190,6 +190,15 @@
     },
     children: [
       {
+        name: 'md_instrument',
+        path: '/md/instrument',
+        component: 'view.md_instrument',
+        meta: {
+          title: 'md_instrument',
+          i18nKey: 'route.md_instrument'
+        }
+      },
+      {
         name: 'md_shift',
         path: '/md/shift',
         component: 'view.md_shift',
@@ -266,6 +275,24 @@
         }
       },
       {
+        name: 'qm_judge',
+        path: '/qm/judge',
+        component: 'view.qm_judge',
+        meta: {
+          title: 'qm_judge',
+          i18nKey: 'route.qm_judge'
+        }
+      },
+      {
+        name: 'qm_judge-details',
+        path: '/qm/judge-details',
+        component: 'view.qm_judge-details',
+        meta: {
+          title: 'qm_judge-details',
+          i18nKey: 'route.qm_judge-details'
+        }
+      },
+      {
         name: 'qm_std',
         path: '/qm/std',
         component: 'view.qm_std',
diff --git a/ruoyi-plus-soybean/src/router/elegant/transform.ts b/ruoyi-plus-soybean/src/router/elegant/transform.ts
index 5667807..edd1a94 100755
--- a/ruoyi-plus-soybean/src/router/elegant/transform.ts
+++ b/ruoyi-plus-soybean/src/router/elegant/transform.ts
@@ -185,6 +185,7 @@
   "iframe-page": "/iframe-page/:url",
   "login": "/login/:module(pwd-login|code-login|register|reset-pwd|bind-wechat)?",
   "md": "/md",
+  "md_instrument": "/md/instrument",
   "md_shift": "/md/shift",
   "monitor": "/monitor",
   "monitor_cache": "/monitor/cache",
@@ -193,6 +194,8 @@
   "monitor_operlog": "/monitor/operlog",
   "qm": "/qm",
   "qm_batch": "/qm/batch",
+  "qm_judge": "/qm/judge",
+  "qm_judge-details": "/qm/judge-details",
   "qm_std": "/qm/std",
   "report": "/report",
   "report_demo": "/report/demo",
diff --git a/ruoyi-plus-soybean/src/service/api/md/instrument.ts b/ruoyi-plus-soybean/src/service/api/md/instrument.ts
new file mode 100644
index 0000000..10a7afd
--- /dev/null
+++ b/ruoyi-plus-soybean/src/service/api/md/instrument.ts
@@ -0,0 +1,35 @@
+import { request } from '@/service/request';
+
+/** 鑾峰彇娴嬮噺浠櫒鍒楄〃 */
+export function fetchGetInstrumentList (params?: Api.Md.InstrumentSearchParams) {
+    return request<Api.Md.InstrumentList>({
+        url: '/md/instrument/list',
+        method: 'get',
+        params
+    });
+}
+/** 鏂板娴嬮噺浠櫒 */
+export function fetchCreateInstrument (data: Api.Md.InstrumentOperateParams) {
+    return request<boolean>({
+        url: '/md/instrument',
+        method: 'post',
+        data
+    });
+}
+
+/** 淇敼娴嬮噺浠櫒 */
+export function fetchUpdateInstrument (data: Api.Md.InstrumentOperateParams) {
+    return request<boolean>({
+        url: '/md/instrument',
+        method: 'put',
+        data
+    });
+}
+
+/** 鎵归噺鍒犻櫎娴嬮噺浠櫒 */
+export function fetchBatchDeleteInstrument (ids: CommonType.IdType[]) {
+    return request<boolean>({
+        url: `/md/instrument/${ids.join(',')}`,
+        method: 'delete'
+    });
+}
diff --git a/ruoyi-plus-soybean/src/service/api/qm/judge-details.ts b/ruoyi-plus-soybean/src/service/api/qm/judge-details.ts
new file mode 100644
index 0000000..1a319fe
--- /dev/null
+++ b/ruoyi-plus-soybean/src/service/api/qm/judge-details.ts
@@ -0,0 +1,35 @@
+import { request } from '@/service/request';
+
+/** 鑾峰彇鍒ゅ畾妯℃澘鏄庣粏鍒楄〃 */
+export function fetchGetJudgeDetailsList (params?: Api.Qm.JudgeDetailsSearchParams) {
+    return request<Api.Qm.JudgeDetailsList>({
+        url: '/qm/judgeDetails/list',
+        method: 'get',
+        params
+    });
+}
+/** 鏂板鍒ゅ畾妯℃澘鏄庣粏 */
+export function fetchCreateJudgeDetails (data: Api.Qm.JudgeDetailsOperateParams) {
+    return request<boolean>({
+        url: '/qm/judgeDetails',
+        method: 'post',
+        data
+    });
+}
+
+/** 淇敼鍒ゅ畾妯℃澘鏄庣粏 */
+export function fetchUpdateJudgeDetails (data: Api.Qm.JudgeDetailsOperateParams) {
+    return request<boolean>({
+        url: '/qm/judgeDetails',
+        method: 'put',
+        data
+    });
+}
+
+/** 鎵归噺鍒犻櫎鍒ゅ畾妯℃澘鏄庣粏 */
+export function fetchBatchDeleteJudgeDetails (ids: CommonType.IdType[]) {
+    return request<boolean>({
+        url: `/qm/judgeDetails/${ids.join(',')}`,
+        method: 'delete'
+    });
+}
diff --git a/ruoyi-plus-soybean/src/service/api/qm/judge.ts b/ruoyi-plus-soybean/src/service/api/qm/judge.ts
new file mode 100644
index 0000000..2b4a94f
--- /dev/null
+++ b/ruoyi-plus-soybean/src/service/api/qm/judge.ts
@@ -0,0 +1,35 @@
+import { request } from '@/service/request';
+
+/** 鑾峰彇鍒ゅ畾渚濇嵁鍒楄〃 */
+export function fetchGetJudgeList (params?: Api.Qm.JudgeSearchParams) {
+    return request<Api.Qm.JudgeList>({
+        url: '/qm/judge/list',
+        method: 'get',
+        params
+    });
+}
+/** 鏂板鍒ゅ畾渚濇嵁 */
+export function fetchCreateJudge (data: Api.Qm.JudgeOperateParams) {
+    return request<boolean>({
+        url: '/qm/judge',
+        method: 'post',
+        data
+    });
+}
+
+/** 淇敼鍒ゅ畾渚濇嵁 */
+export function fetchUpdateJudge (data: Api.Qm.JudgeOperateParams) {
+    return request<boolean>({
+        url: '/qm/judge',
+        method: 'put',
+        data
+    });
+}
+
+/** 鎵归噺鍒犻櫎鍒ゅ畾渚濇嵁 */
+export function fetchBatchDeleteJudge (ids: CommonType.IdType[]) {
+    return request<boolean>({
+        url: `/qm/judge/${ids.join(',')}`,
+        method: 'delete'
+    });
+}
diff --git a/ruoyi-plus-soybean/src/typings/api/md.instrument.api.d.ts b/ruoyi-plus-soybean/src/typings/api/md.instrument.api.d.ts
new file mode 100644
index 0000000..f913592
--- /dev/null
+++ b/ruoyi-plus-soybean/src/typings/api/md.instrument.api.d.ts
@@ -0,0 +1,65 @@
+/**
+ * Namespace Api
+ *
+ * All backend api type
+ */
+declare namespace Api {
+    /**
+     * namespace Md
+     *
+     * backend api module: "Md"
+     */
+    namespace Md {
+        /** instrument */
+        type Instrument = Common.CommonRecord<{
+            /** 缂栫爜 */
+                id: CommonType.IdType; 
+            /** 浠櫒浠g爜 */
+                instrumentCode: string; 
+            /** 浠櫒鍚嶇О */
+                instrumentName: string; 
+            /** 鎺ュ彛鏍囧噯 */
+                ifStd: string; 
+            /** 鎺ュ彛鏍囧噯浠g爜 */
+                isc: number; 
+            /** 鎵�灞炶溅闂� */
+                workShop: string; 
+            /** 鍚敤 */
+                enable: number; 
+            /** 鍒犻櫎 */
+                del: number; 
+            /** 澶囨敞 */
+                instrumentDes: string; 
+        }>;
+
+        /** instrument search params */
+        type InstrumentSearchParams = CommonType.RecordNullable<
+            Pick<
+                Api.Md.Instrument,
+                        | 'instrumentCode'
+                        | 'instrumentName'
+                        | 'enable'
+            > &
+            Api.Common.CommonSearchParams
+        >;
+
+        /** instrument operate params */
+        type InstrumentOperateParams = CommonType.RecordNullable<
+            Pick<
+                Api.Md.Instrument,
+                        | 'id'
+                        | 'instrumentCode'
+                        | 'instrumentName'
+                        | 'ifStd'
+                        | 'isc'
+                        | 'workShop'
+                        | 'enable'
+                        | 'del'
+                        | 'instrumentDes'
+            >
+        >;
+
+        /** instrument list */
+        type InstrumentList = Api.Common.PaginatingQueryRecord<Instrument>;
+    }
+}
diff --git a/ruoyi-plus-soybean/src/typings/api/qm.judge-details.api.d.ts b/ruoyi-plus-soybean/src/typings/api/qm.judge-details.api.d.ts
new file mode 100644
index 0000000..1a542ce
--- /dev/null
+++ b/ruoyi-plus-soybean/src/typings/api/qm.judge-details.api.d.ts
@@ -0,0 +1,85 @@
+/**
+ * Namespace Api
+ *
+ * All backend api type
+ */
+declare namespace Api {
+    /**
+     * namespace Qm
+     *
+     * backend api module: "Qm"
+     */
+    namespace Qm {
+        /** judge details */
+        type JudgeDetails = Common.CommonRecord<{
+            /** 缂栫爜 */
+                id: CommonType.IdType; 
+            /** 鍒ゅ畾涓绘爣璇� */
+                judgeId: CommonType.IdType; 
+            /** 鍒ゅ畾椤笽TEM */
+                itemCod: string; 
+            /** 鍒ゅ畾椤筃AME */
+                itemName: string; 
+            /** 鏍囧噯鍊� */
+                value3: number; 
+            /** 鍒ゅ畾鍊�1 */
+                value1: number; 
+            /** 鍒ゅ畾鍊�2 */
+                value2: number; 
+            /** 缂洪櫡浣嶇疆 */
+                location: string; 
+            /** 鍒ゅ畾绾у埆 (A,B,C,D) */
+                cls: string; 
+            /** 鍒嗗�兼爣鍑� (鎵e垎鏍囧噯锛屽緱鍒嗘爣鍑�),姣斿涓嶅悎鏍间竴娆℃墸澶氬皯鍒� */
+                stdscore: number; 
+            /** 鏍囪姝ら」鏄惁涓哄悎鎴愰」鐩紝姣斿澶栬锛屽疄闄呬笂鍏宠仈浜嗗緢澶氬瓙椤圭洰 */
+                ismix: number; 
+            /** 鑻ユ瀛楁鏈塙UID鍊硷紝琛ㄦ槑瀹冨彲鑳戒负鍏朵粬椤圭洰鐨勫瓙椤癸紝姣斿鈥滅┖澶粹��,瀹冧负鐑熸敮澶栬椤圭洰鐨勫瓙椤� */
+                rid: CommonType.IdType; 
+            /** 鑼冨洿-澶囩敤 */
+                category: number; 
+            /** 澶囨敞 */
+                decisionDes: string; 
+            /** 淇敼浜� */
+                updateUser: string; 
+        }>;
+
+        /** judge details search params */
+        type JudgeDetailsSearchParams = CommonType.RecordNullable<
+            Pick<
+                Api.Qm.JudgeDetails,
+                        | 'judgeId'
+                        | 'itemCod'
+                        | 'itemName'
+                        | 'cls'
+                        | 'ismix'
+            > &
+            Api.Common.CommonSearchParams
+        >;
+
+        /** judge details operate params */
+        type JudgeDetailsOperateParams = CommonType.RecordNullable<
+            Pick<
+                Api.Qm.JudgeDetails,
+                        | 'id'
+                        | 'judgeId'
+                        | 'itemCod'
+                        | 'itemName'
+                        | 'value3'
+                        | 'value1'
+                        | 'value2'
+                        | 'location'
+                        | 'cls'
+                        | 'stdscore'
+                        | 'ismix'
+                        | 'rid'
+                        | 'category'
+                        | 'decisionDes'
+                        | 'updateUser'
+            >
+        >;
+
+        /** judge details list */
+        type JudgeDetailsList = Api.Common.PaginatingQueryRecord<JudgeDetails>;
+    }
+}
diff --git a/ruoyi-plus-soybean/src/typings/api/qm.judge.api.d.ts b/ruoyi-plus-soybean/src/typings/api/qm.judge.api.d.ts
new file mode 100644
index 0000000..04807e4
--- /dev/null
+++ b/ruoyi-plus-soybean/src/typings/api/qm.judge.api.d.ts
@@ -0,0 +1,77 @@
+/**
+ * Namespace Api
+ *
+ * All backend api type
+ */
+declare namespace Api {
+    /**
+     * namespace Qm
+     *
+     * backend api module: "Qm"
+     */
+    namespace Qm {
+        /** judge */
+        type Judge = Common.CommonRecord<{
+            /** 缂栫爜 */
+                id: CommonType.IdType;
+            /** 鐗╂枡鐗屽彿浠g爜 */
+                matCode: string;
+            /** 鐗╂枡鐗屽彿 */
+                matName: string;
+            /** 鐗堟湰鍙� */
+                version: number;
+            /** 鐘舵�� */
+                status: number;
+            /** 鍒ゅ畾瑙勭▼COD */
+                stdCod: string;
+            /** 鍒涘缓鏃堕棿 */
+                cdate: string;
+            /** 鐢熸晥浜�/鎿嶄綔浜哄憳 */
+                oper: string;
+            /** 澶囨敞 */
+                des: string;
+            /** 鐗╂枡绫诲瀷 */
+                category: number;
+            /** 杈呮枡绫诲瀷缂栫爜 */
+                typeCode: string;
+            /** 杈呮枡绫诲瀷鍚嶇О */
+                typeName: string;
+            /** 鍒ゅ畾鍚嶇О */
+                judgeName: string;
+        }>;
+
+        /** judge search params */
+        type JudgeSearchParams = CommonType.RecordNullable<
+            Pick<
+                Api.Qm.Judge,
+                        | 'matCode'
+                        | 'matName'
+                        | 'judgeName'
+                        | 'category'
+                        | 'status'
+            > &
+            Api.Common.CommonSearchParams
+        >;
+
+        /** judge operate params */
+        type JudgeOperateParams = CommonType.RecordNullable<
+            Pick<
+                Api.Qm.Judge,
+                        | 'matCode'
+                        | 'matName'
+                        | 'status'
+                        | 'stdCod'
+                        | 'cdate'
+                        | 'oper'
+                        | 'des'
+                        | 'category'
+                        | 'typeCode'
+                        | 'typeName'
+                        | 'judgeName'
+            >
+        >;
+
+        /** judge list */
+        type JudgeList = Api.Common.PaginatingQueryRecord<Judge>;
+    }
+}
diff --git a/ruoyi-plus-soybean/src/typings/elegant-router.d.ts b/ruoyi-plus-soybean/src/typings/elegant-router.d.ts
index 767e732..f9e689d 100755
--- a/ruoyi-plus-soybean/src/typings/elegant-router.d.ts
+++ b/ruoyi-plus-soybean/src/typings/elegant-router.d.ts
@@ -39,6 +39,7 @@
     "iframe-page": "/iframe-page/:url";
     "login": "/login/:module(pwd-login|code-login|register|reset-pwd|bind-wechat)?";
     "md": "/md";
+    "md_instrument": "/md/instrument";
     "md_shift": "/md/shift";
     "monitor": "/monitor";
     "monitor_cache": "/monitor/cache";
@@ -47,6 +48,8 @@
     "monitor_operlog": "/monitor/operlog";
     "qm": "/qm";
     "qm_batch": "/qm/batch";
+    "qm_judge": "/qm/judge";
+    "qm_judge-details": "/qm/judge-details";
     "qm_std": "/qm/std";
     "report": "/report";
     "report_demo": "/report/demo";
@@ -156,12 +159,15 @@
     | "demo_demo"
     | "demo_tree"
     | "home"
+    | "md_instrument"
     | "md_shift"
     | "monitor_cache"
     | "monitor_logininfor"
     | "monitor_online"
     | "monitor_operlog"
     | "qm_batch"
+    | "qm_judge-details"
+    | "qm_judge"
     | "qm_std"
     | "report_demo"
     | "report_silk-storage-output"
diff --git a/ruoyi-plus-soybean/src/views/md/instrument/index.vue b/ruoyi-plus-soybean/src/views/md/instrument/index.vue
new file mode 100644
index 0000000..9910a7d
--- /dev/null
+++ b/ruoyi-plus-soybean/src/views/md/instrument/index.vue
@@ -0,0 +1,247 @@
+<script setup lang="tsx">
+import { ref } from 'vue';
+import { NDivider, NTooltip } from 'naive-ui';
+import { fetchBatchDeleteInstrument, fetchGetInstrumentList } from '@/service/api/md/instrument';
+import { useAppStore } from '@/store/modules/app';
+import { useAuth } from '@/hooks/business/auth';
+import { useDownload } from '@/hooks/business/download';
+import { defaultTransform, useNaivePaginatedTable, useTableOperate } from '@/hooks/common/table';
+import { useDict } from '@/hooks/business/dict';
+import { $t } from '@/locales';
+import ButtonIcon from '@/components/custom/button-icon.vue';
+import InstrumentOperateDrawer from './modules/instrument-operate-drawer.vue';
+import InstrumentSearch from './modules/instrument-search.vue';
+
+defineOptions({
+  name: 'InstrumentList'
+});
+
+useDict('sys_yes_no');
+
+const appStore = useAppStore();
+const { download } = useDownload();
+const { hasAuth } = useAuth();
+
+const searchParams = ref<Api.Md.InstrumentSearchParams>({
+  pageNum: 1,
+  pageSize: 10,
+  instrumentCode: null,
+  instrumentName: null,
+  enable: null,
+  params: {}
+});
+
+const { columns, columnChecks, data, getData, getDataByPage, loading, mobilePagination, scrollX } =
+  useNaivePaginatedTable({
+  api: () => fetchGetInstrumentList(searchParams.value),
+  transform: response => defaultTransform(response),
+  onPaginationParamsChange: params => {
+    searchParams.value.pageNum = params.page;
+    searchParams.value.pageSize = params.pageSize;
+  },
+  columns: () => [
+    {
+      type: 'selection',
+      align: 'center',
+      width: 48
+    },
+    {
+      key: 'index',
+      title: $t('common.index'),
+      align: 'center',
+      width: 64,
+      render: (_, index) => index + 1
+    },
+    {
+      key: 'instrumentCode',
+      title: '浠櫒浠g爜',
+      align: 'center',
+      width: 120
+    },
+    {
+      key: 'instrumentName',
+      title: '浠櫒鍚嶇О',
+      align: 'center',
+      width: 200
+    },
+    {
+      key: 'ifStd',
+      title: '鎺ュ彛鏍囧噯',
+      align: 'center',
+      width: 120
+    },
+    {
+      key: 'isc',
+      title: '鎺ュ彛鏍囧噯浠g爜',
+      align: 'center',
+      width: 100
+    },
+    {
+      key: 'workShop',
+      title: '鎵�灞炶溅闂�',
+      align: 'center',
+      width: 120
+    },
+    {
+      key: 'enable',
+      title: '鍚敤',
+      align: 'center',
+      width: 120,
+      render(row) {
+        return <DictTag value={row.enable} dictCode="sys_yes_no" />;
+      }
+    },
+    {
+      key: 'del',
+      title: '鍒犻櫎',
+      align: 'center',
+      width: 120
+    },
+    {
+      key: 'instrumentDes',
+      title: '澶囨敞',
+      align: 'center',
+      width: 300,
+      render: row => (
+        <NTooltip trigger="hover">
+          {{
+            trigger: () => (
+              <div
+                style={{
+                  whiteSpace: 'nowrap',
+                  overflow: 'hidden',
+                  textOverflow: 'ellipsis'
+                }}
+              >
+                {row.instrumentDes}
+              </div>
+            ),
+            default: () => row.instrumentDes
+          }}
+        </NTooltip>
+      )
+    },
+    {
+      key: 'operate',
+      title: $t('common.operate'),
+      align: 'center',
+      width: 130,
+      render: row => {
+        const divider = () => {
+          if (!hasAuth('md:instrument:edit') || !hasAuth('md:instrument:remove')) {
+            return null;
+          }
+          return <NDivider vertical />;
+        };
+
+        const editBtn = () => {
+          if (!hasAuth('md:instrument:edit')) {
+            return null;
+          }
+          return (
+            <ButtonIcon
+              text
+              type="primary"
+              icon="material-symbols:drive-file-rename-outline-outline"
+              tooltipContent={$t('common.edit')}
+              onClick={() => edit(row.id)}
+            />
+          );
+        };
+
+        const deleteBtn = () => {
+          if (!hasAuth('md:instrument:remove')) {
+            return null;
+          }
+          return (
+            <ButtonIcon
+              text
+              type="error"
+              icon="material-symbols:delete-outline"
+              tooltipContent={$t('common.delete')}
+              popconfirmContent={$t('common.confirmDelete')}
+              onPositiveClick={() => handleDelete(row.id)}
+            />
+          );
+        };
+
+        return (
+          <div class="flex-center gap-8px">
+            {editBtn()}
+            {divider()}
+            {deleteBtn()}
+          </div>
+        );
+      }
+    }
+  ]
+});
+
+const { drawerVisible, operateType, editingData, handleAdd, handleEdit, checkedRowKeys, onBatchDeleted, onDeleted } =
+  useTableOperate(data, 'id', getData);
+
+async function handleBatchDelete() {
+  // request
+  const { error } = await fetchBatchDeleteInstrument(checkedRowKeys.value);
+  if (error) return;
+  onBatchDeleted();
+}
+
+async function handleDelete(id: CommonType.IdType) {
+  // request
+  const { error } = await fetchBatchDeleteInstrument([id]);
+  if (error) return;
+  onDeleted();
+}
+
+function edit(id: CommonType.IdType) {
+  handleEdit(id);
+}
+
+function handleExport() {
+  download('/md/instrument/export', searchParams.value, `娴嬮噺浠櫒_${new Date().getTime()}.xlsx`);
+}
+</script>
+
+<template>
+  <div class="min-h-500px flex-col-stretch gap-16px overflow-hidden lt-sm:overflow-auto">
+    <InstrumentSearch v-model:model="searchParams" @search="getDataByPage" />
+    <NCard title="娴嬮噺浠櫒鍒楄〃" :bordered="false" size="small" class="card-wrapper sm:flex-1-hidden">
+      <template #header-extra>
+        <TableHeaderOperation
+          v-model:columns="columnChecks"
+          :disabled-delete="checkedRowKeys.length === 0"
+          :loading="loading"
+          :show-add="hasAuth('md:instrument:add')"
+          :show-delete="hasAuth('md:instrument:remove')"
+          :show-export="hasAuth('md:instrument:export')"
+          @add="handleAdd"
+          @delete="handleBatchDelete"
+          @export="handleExport"
+          @refresh="getData"
+        />
+      </template>
+      <NDataTable
+        v-model:checked-row-keys="checkedRowKeys"
+        :columns="columns"
+        :data="data"
+        size="small"
+        :flex-height="!appStore.isMobile"
+        :scroll-x="scrollX"
+        :loading="loading"
+        remote
+        :row-key="row => row.id"
+        :pagination="mobilePagination"
+        class="sm:h-full"
+      />
+      <InstrumentOperateDrawer
+        v-model:visible="drawerVisible"
+        :operate-type="operateType"
+        :row-data="editingData"
+        @submitted="getDataByPage"
+      />
+    </NCard>
+  </div>
+</template>
+
+<style scoped></style>
diff --git a/ruoyi-plus-soybean/src/views/md/instrument/modules/instrument-operate-drawer.vue b/ruoyi-plus-soybean/src/views/md/instrument/modules/instrument-operate-drawer.vue
new file mode 100644
index 0000000..c607874
--- /dev/null
+++ b/ruoyi-plus-soybean/src/views/md/instrument/modules/instrument-operate-drawer.vue
@@ -0,0 +1,157 @@
+<script setup lang="ts">
+import { computed, ref, watch } from 'vue';
+import { NSelect } from 'naive-ui';
+import { jsonClone } from '@sa/utils';
+import { fetchCreateInstrument, fetchUpdateInstrument } from '@/service/api/md/instrument';
+import { useFormRules, useNaiveForm } from '@/hooks/common/form';
+import { $t } from '@/locales';
+
+defineOptions({
+  name: 'InstrumentOperateDrawer'
+});
+
+interface Props {
+  /** the type of operation */
+  operateType: NaiveUI.TableOperateType;
+  /** the edit row data */
+  rowData?: Api.Md.Instrument | null;
+}
+
+const props = defineProps<Props>();
+
+interface Emits {
+  (e: 'submitted'): void;
+}
+
+const emit = defineEmits<Emits>();
+
+const visible = defineModel<boolean>('visible', {
+  default: false
+});
+
+const { formRef, validate, restoreValidation } = useNaiveForm();
+const { createRequiredRule } = useFormRules();
+
+const title = computed(() => {
+  const titles: Record<NaiveUI.TableOperateType, string> = {
+    add: '鏂板娴嬮噺浠櫒',
+    edit: '缂栬緫娴嬮噺浠櫒'
+  };
+  return titles[props.operateType];
+});
+
+const enableOptions = [
+  { label: '鍚敤', value: 1 },
+  { label: '鍋滅敤', value: 0 }
+];
+
+const delOptions = [
+  { label: '姝e父', value: 0 },
+  { label: '鍒犻櫎', value: 1 }
+];
+
+type Model = Api.Md.InstrumentOperateParams;
+
+const model = ref<Model>(createDefaultModel());
+
+function createDefaultModel(): Model {
+  return {
+      id: '',
+      instrumentCode: '',
+      instrumentName: '',
+      ifStd: '',
+      isc: null,
+      workShop: '',
+      enable: 1,
+      del: 0,
+      instrumentDes: ''
+  };
+}
+
+type RuleKey = Extract<keyof Model, 'id'>;
+
+const rules: Record<RuleKey, App.Global.FormRule> = {
+  id: createRequiredRule('ud涓嶈兘涓虹┖')
+};
+
+function handleUpdateModelWhenEdit() {
+  model.value = createDefaultModel();
+
+  if (props.operateType === 'edit' && props.rowData) {
+    Object.assign(model.value, jsonClone(props.rowData));
+  }
+}
+
+function closeDrawer() {
+  visible.value = false;
+}
+
+async function handleSubmit() {
+  await validate();
+
+  const { id, instrumentCode, instrumentName, ifStd, isc, workShop, enable, del, instrumentDes } = model.value;
+
+  // request
+  if (props.operateType === 'add') {
+    const { error } = await fetchCreateInstrument({ id, instrumentCode, instrumentName, ifStd, isc, workShop, enable, del, instrumentDes });
+    if (error) return;
+  }
+
+  if (props.operateType === 'edit') {
+    const { error } = await fetchUpdateInstrument({ id, instrumentCode, instrumentName, ifStd, isc, workShop, enable, del, instrumentDes });
+    if (error) return;
+  }
+
+  window.$message?.success($t('common.updateSuccess'));
+  closeDrawer();
+  emit('submitted');
+}
+
+watch(visible, () => {
+  if (visible.value) {
+    handleUpdateModelWhenEdit();
+    restoreValidation();
+  }
+});
+</script>
+
+<template>
+  <NDrawer v-model:show="visible" :title="title" display-directive="show" :width="800" class="max-w-90%">
+    <NDrawerContent :title="title" :native-scrollbar="false" closable>
+      <NForm ref="formRef" :model="model" :rules="rules">
+        <NFormItem label="浠櫒浠g爜" path="instrumentCode">
+          <NInput v-model:value="model.instrumentCode" placeholder="璇疯緭鍏ヤ华鍣ㄤ唬鐮�" />
+        </NFormItem>
+        <NFormItem label="浠櫒鍚嶇О" path="instrumentName">
+          <NInput v-model:value="model.instrumentName" placeholder="璇疯緭鍏ヤ华鍣ㄥ悕绉�" />
+        </NFormItem>
+        <NFormItem label="鎺ュ彛鏍囧噯" path="ifStd">
+          <NInput v-model:value="model.ifStd" placeholder="璇疯緭鍏ユ帴鍙f爣鍑�" />
+        </NFormItem>
+        <NFormItem label="鎺ュ彛鏍囧噯浠g爜" path="isc">
+          <NInput v-model:value="model.isc" placeholder="璇疯緭鍏ユ帴鍙f爣鍑嗕唬鐮�" />
+        </NFormItem>
+        <NFormItem label="鎵�灞炶溅闂�" path="workShop">
+          <NInput v-model:value="model.workShop" placeholder="璇疯緭鍏ユ墍灞炶溅闂�" />
+        </NFormItem>
+        <NFormItem label="鍚敤" path="enable">
+          <NSelect v-model:value="model.enable as number" :options="enableOptions" placeholder="璇烽�夋嫨鍚敤" clearable />
+        </NFormItem>
+        <NFormItem label="鍒犻櫎" path="del">
+          <NSelect v-model:value="model.del as number" :options="delOptions" placeholder="璇烽�夋嫨鍒犻櫎" clearable />
+        </NFormItem>
+        <NFormItem label="澶囨敞" path="instrumentDes">
+          <NInput v-model:value="model.instrumentDes" placeholder="璇疯緭鍏ュ娉�" />
+        </NFormItem>
+      </NForm>
+      <template #footer>
+        <NSpace :size="16">
+          <NButton @click="closeDrawer">{{ $t('common.cancel') }}</NButton>
+          <NButton type="primary" @click="handleSubmit">{{ $t('common.confirm') }}</NButton>
+        </NSpace>
+      </template>
+    </NDrawerContent>
+  </NDrawer>
+</template>
+
+<style scoped></style>
diff --git a/ruoyi-plus-soybean/src/views/md/instrument/modules/instrument-search.vue b/ruoyi-plus-soybean/src/views/md/instrument/modules/instrument-search.vue
new file mode 100644
index 0000000..dde8682
--- /dev/null
+++ b/ruoyi-plus-soybean/src/views/md/instrument/modules/instrument-search.vue
@@ -0,0 +1,82 @@
+<script setup lang="ts">
+import { toRaw } from 'vue';
+import { jsonClone } from '@sa/utils';
+import { useNaiveForm } from '@/hooks/common/form';
+import { $t } from '@/locales';
+
+defineOptions({
+  name: 'InstrumentSearch'
+});
+
+interface Emits {
+  (e: 'search'): void;
+}
+
+const emit = defineEmits<Emits>();
+
+const { formRef, validate, restoreValidation } = useNaiveForm();
+
+const model = defineModel<Api.Md.InstrumentSearchParams>('model', { required: true });
+
+const defaultModel = jsonClone(toRaw(model.value));
+
+function resetModel() {
+  Object.assign(model.value, defaultModel);
+}
+
+async function reset() {
+  await restoreValidation();
+  resetModel();
+  emit('search');
+}
+
+async function search() {
+  await validate();
+  emit('search');
+}
+</script>
+
+<template>
+  <NCard :bordered="false" size="small" class="card-wrapper">
+    <NCollapse>
+      <NCollapseItem :title="$t('common.search')" name="md-instrument-search">
+        <NForm ref="formRef" :model="model" label-placement="left" :label-width="80">
+          <NGrid responsive="screen" item-responsive>
+            <NFormItemGi span="24 s:12 m:6" label="浠櫒浠g爜" label-width="auto" path="instrumentCode" class="pr-24px">
+              <NInput v-model:value="model.instrumentCode" placeholder="璇疯緭鍏ヤ华鍣ㄤ唬鐮�" />
+            </NFormItemGi>
+            <NFormItemGi span="24 s:12 m:6" label="浠櫒鍚嶇О" label-width="auto" path="instrumentName" class="pr-24px">
+              <NInput v-model:value="model.instrumentName" placeholder="璇疯緭鍏ヤ华鍣ㄥ悕绉�" />
+            </NFormItemGi>
+            <NFormItemGi span="24 s:12 m:6" label="鍚敤" label-width="auto" path="enable" class="pr-24px">
+              <DictSelect
+                v-model:value="model.enable"
+                placeholder="璇烽�夋嫨鍚敤"
+                dict-code="sys_yes_no"
+                clearable
+              />
+            </NFormItemGi>
+            <NFormItemGi :show-feedback="false" span="24" class="pr-24px">
+              <NSpace class="w-full" justify="end">
+                <NButton @click="reset">
+                  <template #icon>
+                    <icon-ic-round-refresh class="text-icon" />
+                  </template>
+                  {{ $t('common.reset') }}
+                </NButton>
+                <NButton type="primary" ghost @click="search">
+                  <template #icon>
+                    <icon-ic-round-search class="text-icon" />
+                  </template>
+                  {{ $t('common.search') }}
+                </NButton>
+              </NSpace>
+            </NFormItemGi>
+          </NGrid>
+        </NForm>
+      </NCollapseItem>
+    </NCollapse>
+  </NCard>
+</template>
+
+<style scoped></style>
diff --git a/ruoyi-plus-soybean/src/views/qm/checkitem/modules/checkitem-operate-drawer.vue b/ruoyi-plus-soybean/src/views/qm/checkitem/modules/checkitem-operate-drawer.vue
index 4037161..e4af1c5 100755
--- a/ruoyi-plus-soybean/src/views/qm/checkitem/modules/checkitem-operate-drawer.vue
+++ b/ruoyi-plus-soybean/src/views/qm/checkitem/modules/checkitem-operate-drawer.vue
@@ -6,6 +6,7 @@
   fetchGetRid,
   fetchUpdateCheckitem
 } from '@/service/api/qm/checkitem';
+import { fetchGetInstrumentList } from '@/service/api/md/instrument';
 import { useFormRules, useNaiveForm } from '@/hooks/common/form';
 import { $t } from '@/locales';
 
@@ -76,6 +77,7 @@
 
 const model = ref<Model>(createDefaultModel());
 const ridOptions = ref<{ label: string; value: string | number }[]>([]);
+const instrumentOptions = ref<{ label: string; value: string | number }[]>([]);
 
 async function fetchRidOptions(searchText?: string) {
   const params: any = {};
@@ -96,6 +98,26 @@
 
 function handleRidSelectSearch(query: string) {
   fetchRidOptions(query);
+}
+
+async function fetchInstrumentOptions(searchText?: string) {
+  const params: any = {};
+  if (searchText) {
+    params.instrumentName = searchText;
+  }
+  const { data } = await fetchGetInstrumentList(params);
+  if (data && data.rows) {
+    instrumentOptions.value = data.rows.map(item => ({
+      label: item.instrumentName,
+      value: item.instrumentCode
+    }));
+  } else {
+    instrumentOptions.value = [];
+  }
+}
+
+function handleInstrumentSelectSearch(query: string) {
+  fetchInstrumentOptions(query);
 }
 
 
@@ -139,6 +161,8 @@
 
   // Fetch options for rid
   await fetchRidOptions();
+  // Fetch options for instrument
+  await fetchInstrumentOptions();
 
   if (props.operateType === 'add' && props.stdCode) {
     model.value.stdCode = props.stdCode;
@@ -202,7 +226,17 @@
         <NFormItem label="妫�楠岄」鎻忚堪" path="itemDes">
           <NInput v-model:value="model.itemDes" placeholder="璇疯緭鍏ユ楠岄」鎻忚堪" />
         </NFormItem>
-
+        <NFormItem label="妫�娴嬩华鍣�" path="instrumentCode">
+          <NSelect
+            v-model:value="model.instrumentCode"
+            :options="instrumentOptions"
+            placeholder="璇烽�夋嫨妫�娴嬩华鍣�"
+            clearable
+            filterable
+            @search="handleInstrumentSelectSearch"
+            @focus="() => fetchInstrumentOptions()"
+          />
+        </NFormItem>
         <NFormItem label="浠櫒鎻忚堪" path="instrumentDes">
           <NInput v-model:value="model.instrumentDes" placeholder="璇疯緭鍏ヤ华鍣ㄦ弿杩�" />
         </NFormItem>
diff --git a/ruoyi-plus-soybean/src/views/qm/judge-details/index.vue b/ruoyi-plus-soybean/src/views/qm/judge-details/index.vue
new file mode 100644
index 0000000..a7aa061
--- /dev/null
+++ b/ruoyi-plus-soybean/src/views/qm/judge-details/index.vue
@@ -0,0 +1,262 @@
+<script setup lang="tsx">
+import { ref } from 'vue';
+import { NDivider } from 'naive-ui';
+import { fetchBatchDeleteJudgeDetails, fetchGetJudgeDetailsList } from '@/service/api/qm/judge-details';
+import { useAppStore } from '@/store/modules/app';
+import { useAuth } from '@/hooks/business/auth';
+import { useDownload } from '@/hooks/business/download';
+import { defaultTransform, useNaivePaginatedTable, useTableOperate } from '@/hooks/common/table';
+import { $t } from '@/locales';
+import ButtonIcon from '@/components/custom/button-icon.vue';
+import JudgeDetailsOperateDrawer from './modules/judge-details-operate-drawer.vue';
+import JudgeDetailsSearch from './modules/judge-details-search.vue';
+
+defineOptions({
+  name: 'JudgeDetailsList'
+});
+
+
+const appStore = useAppStore();
+const { download } = useDownload();
+const { hasAuth } = useAuth();
+
+const searchParams = ref<Api.Qm.JudgeDetailsSearchParams>({
+  pageNum: 1,
+  pageSize: 10,
+  judgeId: null,
+  itemCod: null,
+  itemName: null,
+  cls: null,
+  ismix: null,
+  params: {}
+});
+
+const { columns, columnChecks, data, getData, getDataByPage, loading, mobilePagination, scrollX } =
+  useNaivePaginatedTable({
+  api: () => fetchGetJudgeDetailsList(searchParams.value),
+  transform: response => defaultTransform(response),
+  onPaginationParamsChange: params => {
+    searchParams.value.pageNum = params.page;
+    searchParams.value.pageSize = params.pageSize;
+  },
+  columns: () => [
+    {
+      type: 'selection',
+      align: 'center',
+      width: 48
+    },
+    {
+      key: 'index',
+      title: $t('common.index'),
+      align: 'center',
+      width: 64,
+      render: (_, index) => index + 1
+    },
+    {
+      key: 'judgeId',
+      title: '鍒ゅ畾涓绘爣璇�',
+      align: 'center',
+      minWidth: 120
+    },
+    {
+      key: 'itemCod',
+      title: '鍒ゅ畾椤笽TEM',
+      align: 'center',
+      minWidth: 120
+    },
+    {
+      key: 'itemName',
+      title: '鍒ゅ畾椤筃AME',
+      align: 'center',
+      minWidth: 120
+    },
+    {
+      key: 'value3',
+      title: '鏍囧噯鍊�',
+      align: 'center',
+      minWidth: 120
+    },
+    {
+      key: 'value1',
+      title: '鍒ゅ畾鍊�1',
+      align: 'center',
+      minWidth: 120
+    },
+    {
+      key: 'value2',
+      title: '鍒ゅ畾鍊�2',
+      align: 'center',
+      minWidth: 120
+    },
+    {
+      key: 'location',
+      title: '缂洪櫡浣嶇疆',
+      align: 'center',
+      minWidth: 120
+    },
+    {
+      key: 'cls',
+      title: '鍒ゅ畾绾у埆 (A,B,C,D)',
+      align: 'center',
+      minWidth: 120
+    },
+    {
+      key: 'stdscore',
+      title: '鍒嗗�兼爣鍑� (鎵e垎鏍囧噯锛屽緱鍒嗘爣鍑�),姣斿涓嶅悎鏍间竴娆℃墸澶氬皯鍒�',
+      align: 'center',
+      minWidth: 120
+    },
+    {
+      key: 'ismix',
+      title: '鏍囪姝ら」鏄惁涓哄悎鎴愰」鐩紝姣斿澶栬锛屽疄闄呬笂鍏宠仈浜嗗緢澶氬瓙椤圭洰',
+      align: 'center',
+      minWidth: 120
+    },
+    {
+      key: 'rid',
+      title: '鑻ユ瀛楁鏈塙UID鍊硷紝琛ㄦ槑瀹冨彲鑳戒负鍏朵粬椤圭洰鐨勫瓙椤癸紝姣斿鈥滅┖澶粹��,瀹冧负鐑熸敮澶栬椤圭洰鐨勫瓙椤�',
+      align: 'center',
+      minWidth: 120
+    },
+    {
+      key: 'category',
+      title: '鑼冨洿-澶囩敤',
+      align: 'center',
+      minWidth: 120
+    },
+    {
+      key: 'decisionDes',
+      title: '澶囨敞',
+      align: 'center',
+      minWidth: 120
+    },
+    {
+      key: 'updateUser',
+      title: '淇敼浜�',
+      align: 'center',
+      minWidth: 120
+    },
+    {
+      key: 'operate',
+      title: $t('common.operate'),
+      align: 'center',
+      width: 130,
+      render: row => {
+        const divider = () => {
+          if (!hasAuth('qm:judgeDetails:edit') || !hasAuth('qm:judgeDetails:remove')) {
+            return null;
+          }
+          return <NDivider vertical />;
+        };
+
+        const editBtn = () => {
+          if (!hasAuth('qm:judgeDetails:edit')) {
+            return null;
+          }
+          return (
+            <ButtonIcon
+              text
+              type="primary"
+              icon="material-symbols:drive-file-rename-outline-outline"
+              tooltipContent={$t('common.edit')}
+              onClick={() => edit(row.id)}
+            />
+          );
+        };
+
+        const deleteBtn = () => {
+          if (!hasAuth('qm:judgeDetails:remove')) {
+            return null;
+          }
+          return (
+            <ButtonIcon
+              text
+              type="error"
+              icon="material-symbols:delete-outline"
+              tooltipContent={$t('common.delete')}
+              popconfirmContent={$t('common.confirmDelete')}
+              onPositiveClick={() => handleDelete(row.id)}
+            />
+          );
+        };
+
+        return (
+          <div class="flex-center gap-8px">
+            {editBtn()}
+            {divider()}
+            {deleteBtn()}
+          </div>
+        );
+      }
+    }
+  ]
+});
+
+const { drawerVisible, operateType, editingData, handleAdd, handleEdit, checkedRowKeys, onBatchDeleted, onDeleted } =
+  useTableOperate(data, 'id', getData);
+
+async function handleBatchDelete() {
+  // request
+  const { error } = await fetchBatchDeleteJudgeDetails(checkedRowKeys.value);
+  if (error) return;
+  onBatchDeleted();
+}
+
+async function handleDelete(id: CommonType.IdType) {
+  // request
+  const { error } = await fetchBatchDeleteJudgeDetails([id]);
+  if (error) return;
+  onDeleted();
+}
+
+function edit(id: CommonType.IdType) {
+  handleEdit(id);
+}
+
+function handleExport() {
+  download('/qm/judgeDetails/export', searchParams.value, `鍒ゅ畾妯℃澘鏄庣粏_${new Date().getTime()}.xlsx`);
+}
+</script>
+
+<template>
+  <div class="min-h-500px flex-col-stretch gap-16px overflow-hidden lt-sm:overflow-auto">
+    <JudgeDetailsSearch v-model:model="searchParams" @search="getDataByPage" />
+    <NCard title="鍒ゅ畾妯℃澘鏄庣粏鍒楄〃" :bordered="false" size="small" class="card-wrapper sm:flex-1-hidden">
+      <template #header-extra>
+        <TableHeaderOperation
+          v-model:columns="columnChecks"
+          :disabled-delete="checkedRowKeys.length === 0"
+          :loading="loading"
+          :show-add="hasAuth('qm:judgeDetails:add')"
+          :show-delete="hasAuth('qm:judgeDetails:remove')"
+          :show-export="hasAuth('qm:judgeDetails:export')"
+          @add="handleAdd"
+          @delete="handleBatchDelete"
+          @export="handleExport"
+          @refresh="getData"
+        />
+      </template>
+      <NDataTable
+        v-model:checked-row-keys="checkedRowKeys"
+        :columns="columns"
+        :data="data"
+        size="small"
+        :flex-height="!appStore.isMobile"
+        :scroll-x="scrollX"
+        :loading="loading"
+        remote
+        :row-key="row => row.id"
+        :pagination="mobilePagination"
+        class="sm:h-full"
+      />
+      <JudgeDetailsOperateDrawer
+        v-model:visible="drawerVisible"
+        :operate-type="operateType"
+        :row-data="editingData"
+        @submitted="getDataByPage"
+      />
+    </NCard>
+  </div>
+</template>
+
+<style scoped></style>
diff --git a/ruoyi-plus-soybean/src/views/qm/judge-details/modules/judge-details-operate-drawer.vue b/ruoyi-plus-soybean/src/views/qm/judge-details/modules/judge-details-operate-drawer.vue
new file mode 100644
index 0000000..fee65a3
--- /dev/null
+++ b/ruoyi-plus-soybean/src/views/qm/judge-details/modules/judge-details-operate-drawer.vue
@@ -0,0 +1,175 @@
+<script setup lang="ts">
+import { computed, ref, watch } from 'vue';
+import { jsonClone } from '@sa/utils';
+import { fetchCreateJudgeDetails, fetchUpdateJudgeDetails } from '@/service/api/qm/judge-details';
+import { useFormRules, useNaiveForm } from '@/hooks/common/form';
+import { $t } from '@/locales';
+
+defineOptions({
+  name: 'JudgeDetailsOperateDrawer'
+});
+
+interface Props {
+  /** the type of operation */
+  operateType: NaiveUI.TableOperateType;
+  /** the edit row data */
+  rowData?: Api.Qm.JudgeDetails | null;
+}
+
+const props = defineProps<Props>();
+
+interface Emits {
+  (e: 'submitted'): void;
+}
+
+const emit = defineEmits<Emits>();
+
+const visible = defineModel<boolean>('visible', {
+  default: false
+});
+
+const { formRef, validate, restoreValidation } = useNaiveForm();
+const { createRequiredRule } = useFormRules();
+
+const title = computed(() => {
+  const titles: Record<NaiveUI.TableOperateType, string> = {
+    add: '鏂板鍒ゅ畾妯℃澘鏄庣粏',
+    edit: '缂栬緫鍒ゅ畾妯℃澘鏄庣粏'
+  };
+  return titles[props.operateType];
+});
+
+type Model = Api.Qm.JudgeDetailsOperateParams;
+
+const model = ref<Model>(createDefaultModel());
+
+function createDefaultModel(): Model {
+  return {
+      id: '',
+      judgeId: '',
+      itemCod: '',
+      itemName: '',
+      value3: null,
+      value1: null,
+      value2: null,
+      location: '',
+      cls: '',
+      stdscore: null,
+      ismix: null,
+      rid: '',
+      category: null,
+      decisionDes: '',
+      updateUser: '',
+  };
+}
+
+type RuleKey = Extract<
+  keyof Model,
+  | 'id'
+>;
+
+const rules: Record<RuleKey, App.Global.FormRule> = {
+};
+
+function handleUpdateModelWhenEdit() {
+  model.value = createDefaultModel();
+
+  if (props.operateType === 'edit' && props.rowData) {
+    Object.assign(model.value, jsonClone(props.rowData));
+  }
+}
+
+function closeDrawer() {
+  visible.value = false;
+}
+
+async function handleSubmit() {
+  await validate();
+
+  const { id, judgeId, itemCod, itemName, value3, value1, value2, location, cls, stdscore, ismix, rid, category, decisionDes, updateUser } = model.value;
+
+  // request
+  if (props.operateType === 'add') {
+    const { error } = await fetchCreateJudgeDetails({ id, judgeId, itemCod, itemName, value3, value1, value2, location, cls, stdscore, ismix, rid, category, decisionDes, updateUser });
+    if (error) return;
+  }
+
+  if (props.operateType === 'edit') {
+    const { error } = await fetchUpdateJudgeDetails({ id, judgeId, itemCod, itemName, value3, value1, value2, location, cls, stdscore, ismix, rid, category, decisionDes, updateUser });
+    if (error) return;
+  }
+
+  window.$message?.success($t('common.updateSuccess'));
+  closeDrawer();
+  emit('submitted');
+}
+
+watch(visible, () => {
+  if (visible.value) {
+    handleUpdateModelWhenEdit();
+    restoreValidation();
+  }
+});
+</script>
+
+<template>
+  <NDrawer v-model:show="visible" :title="title" display-directive="show" :width="800" class="max-w-90%">
+    <NDrawerContent :title="title" :native-scrollbar="false" closable>
+      <NForm ref="formRef" :model="model" :rules="rules">
+        <NFormItem label="缂栫爜" path="id">
+          <NInput v-model:value="model.id" placeholder="璇疯緭鍏ョ紪鐮�" />
+        </NFormItem>
+        <NFormItem label="鍒ゅ畾涓绘爣璇�" path="judgeId">
+          <NInput v-model:value="model.judgeId" placeholder="璇疯緭鍏ュ垽瀹氫富鏍囪瘑" />
+        </NFormItem>
+        <NFormItem label="鍒ゅ畾椤笽TEM" path="itemCod">
+          <NInput v-model:value="model.itemCod" placeholder="璇疯緭鍏ュ垽瀹氶」ITEM" />
+        </NFormItem>
+        <NFormItem label="鍒ゅ畾椤筃AME" path="itemName">
+          <NInput v-model:value="model.itemName" placeholder="璇疯緭鍏ュ垽瀹氶」NAME" />
+        </NFormItem>
+        <NFormItem label="鏍囧噯鍊�" path="value3">
+          <NInput v-model:value="model.value3" placeholder="璇疯緭鍏ユ爣鍑嗗��" />
+        </NFormItem>
+        <NFormItem label="鍒ゅ畾鍊�1" path="value1">
+          <NInput v-model:value="model.value1" placeholder="璇疯緭鍏ュ垽瀹氬��1" />
+        </NFormItem>
+        <NFormItem label="鍒ゅ畾鍊�2" path="value2">
+          <NInput v-model:value="model.value2" placeholder="璇疯緭鍏ュ垽瀹氬��2" />
+        </NFormItem>
+        <NFormItem label="缂洪櫡浣嶇疆" path="location">
+          <NInput v-model:value="model.location" placeholder="璇疯緭鍏ョ己闄蜂綅缃�" />
+        </NFormItem>
+        <NFormItem label="鍒ゅ畾绾у埆 (A,B,C,D)" path="cls">
+          <NInput v-model:value="model.cls" placeholder="璇疯緭鍏ュ垽瀹氱骇鍒� (A,B,C,D)" />
+        </NFormItem>
+        <NFormItem label="鍒嗗�兼爣鍑� (鎵e垎鏍囧噯锛屽緱鍒嗘爣鍑�),姣斿涓嶅悎鏍间竴娆℃墸澶氬皯鍒�" path="stdscore">
+          <NInput v-model:value="model.stdscore" placeholder="璇疯緭鍏ュ垎鍊兼爣鍑� (鎵e垎鏍囧噯锛屽緱鍒嗘爣鍑�),姣斿涓嶅悎鏍间竴娆℃墸澶氬皯鍒�" />
+        </NFormItem>
+        <NFormItem label="鏍囪姝ら」鏄惁涓哄悎鎴愰」鐩紝姣斿澶栬锛屽疄闄呬笂鍏宠仈浜嗗緢澶氬瓙椤圭洰" path="ismix">
+          <NInput v-model:value="model.ismix" placeholder="璇疯緭鍏ユ爣璁版椤规槸鍚︿负鍚堟垚椤圭洰锛屾瘮濡傚瑙傦紝瀹為檯涓婂叧鑱斾簡寰堝瀛愰」鐩�" />
+        </NFormItem>
+        <NFormItem label="鑻ユ瀛楁鏈塙UID鍊硷紝琛ㄦ槑瀹冨彲鑳戒负鍏朵粬椤圭洰鐨勫瓙椤癸紝姣斿鈥滅┖澶粹��,瀹冧负鐑熸敮澶栬椤圭洰鐨勫瓙椤�" path="rid">
+          <NInput v-model:value="model.rid" placeholder="璇疯緭鍏ヨ嫢姝ゅ瓧娈垫湁UUID鍊硷紝琛ㄦ槑瀹冨彲鑳戒负鍏朵粬椤圭洰鐨勫瓙椤癸紝姣斿鈥滅┖澶粹��,瀹冧负鐑熸敮澶栬椤圭洰鐨勫瓙椤�" />
+        </NFormItem>
+        <NFormItem label="鑼冨洿-澶囩敤" path="category">
+          <NInput v-model:value="model.category" placeholder="璇疯緭鍏ヨ寖鍥�-澶囩敤" />
+        </NFormItem>
+        <NFormItem label="澶囨敞" path="decisionDes">
+          <NInput v-model:value="model.decisionDes" placeholder="璇疯緭鍏ュ娉�" />
+        </NFormItem>
+        <NFormItem label="淇敼浜�" path="updateUser">
+          <NInput v-model:value="model.updateUser" placeholder="璇疯緭鍏ヤ慨鏀逛汉" />
+        </NFormItem>
+      </NForm>
+      <template #footer>
+        <NSpace :size="16">
+          <NButton @click="closeDrawer">{{ $t('common.cancel') }}</NButton>
+          <NButton type="primary" @click="handleSubmit">{{ $t('common.confirm') }}</NButton>
+        </NSpace>
+      </template>
+    </NDrawerContent>
+  </NDrawer>
+</template>
+
+<style scoped></style>
diff --git a/ruoyi-plus-soybean/src/views/qm/judge-details/modules/judge-details-search.vue b/ruoyi-plus-soybean/src/views/qm/judge-details/modules/judge-details-search.vue
new file mode 100644
index 0000000..df74c16
--- /dev/null
+++ b/ruoyi-plus-soybean/src/views/qm/judge-details/modules/judge-details-search.vue
@@ -0,0 +1,83 @@
+<script setup lang="ts">
+import { toRaw } from 'vue';
+import { jsonClone } from '@sa/utils';
+import { useNaiveForm } from '@/hooks/common/form';
+import { $t } from '@/locales';
+
+defineOptions({
+  name: 'JudgeDetailsSearch'
+});
+
+interface Emits {
+  (e: 'search'): void;
+}
+
+const emit = defineEmits<Emits>();
+
+const { formRef, validate, restoreValidation } = useNaiveForm();
+
+const model = defineModel<Api.Qm.JudgeDetailsSearchParams>('model', { required: true });
+
+const defaultModel = jsonClone(toRaw(model.value));
+
+function resetModel() {
+  Object.assign(model.value, defaultModel);
+}
+
+async function reset() {
+  await restoreValidation();
+  resetModel();
+  emit('search');
+}
+
+async function search() {
+  await validate();
+  emit('search');
+}
+</script>
+
+<template>
+  <NCard :bordered="false" size="small" class="card-wrapper">
+    <NCollapse>
+      <NCollapseItem :title="$t('common.search')" name="qm-judge-details-search">
+        <NForm ref="formRef" :model="model" label-placement="left" :label-width="80">
+          <NGrid responsive="screen" item-responsive>
+            <NFormItemGi span="24 s:12 m:6" label="鍒ゅ畾涓绘爣璇�" label-width="auto" path="judgeId" class="pr-24px">
+              <NInput v-model:value="model.judgeId" placeholder="璇疯緭鍏ュ垽瀹氫富鏍囪瘑" />
+            </NFormItemGi>
+            <NFormItemGi span="24 s:12 m:6" label="鍒ゅ畾椤笽TEM" label-width="auto" path="itemCod" class="pr-24px">
+              <NInput v-model:value="model.itemCod" placeholder="璇疯緭鍏ュ垽瀹氶」ITEM" />
+            </NFormItemGi>
+            <NFormItemGi span="24 s:12 m:6" label="鍒ゅ畾椤筃AME" label-width="auto" path="itemName" class="pr-24px">
+              <NInput v-model:value="model.itemName" placeholder="璇疯緭鍏ュ垽瀹氶」NAME" />
+            </NFormItemGi>
+            <NFormItemGi span="24 s:12 m:6" label="鍒ゅ畾绾у埆 (A,B,C,D)" label-width="auto" path="cls" class="pr-24px">
+              <NInput v-model:value="model.cls" placeholder="璇疯緭鍏ュ垽瀹氱骇鍒� (A,B,C,D)" />
+            </NFormItemGi>
+            <NFormItemGi span="24 s:12 m:6" label="鏍囪姝ら」鏄惁涓哄悎鎴愰」鐩紝姣斿澶栬锛屽疄闄呬笂鍏宠仈浜嗗緢澶氬瓙椤圭洰" label-width="auto" path="ismix" class="pr-24px">
+              <NInput v-model:value="model.ismix" placeholder="璇疯緭鍏ユ爣璁版椤规槸鍚︿负鍚堟垚椤圭洰锛屾瘮濡傚瑙傦紝瀹為檯涓婂叧鑱斾簡寰堝瀛愰」鐩�" />
+            </NFormItemGi>
+            <NFormItemGi :show-feedback="false" span="24" class="pr-24px">
+              <NSpace class="w-full" justify="end">
+                <NButton @click="reset">
+                  <template #icon>
+                    <icon-ic-round-refresh class="text-icon" />
+                  </template>
+                  {{ $t('common.reset') }}
+                </NButton>
+                <NButton type="primary" ghost @click="search">
+                  <template #icon>
+                    <icon-ic-round-search class="text-icon" />
+                  </template>
+                  {{ $t('common.search') }}
+                </NButton>
+              </NSpace>
+            </NFormItemGi>
+          </NGrid>
+        </NForm>
+      </NCollapseItem>
+    </NCollapse>
+  </NCard>
+</template>
+
+<style scoped></style>
diff --git a/ruoyi-plus-soybean/src/views/qm/judge/index.vue b/ruoyi-plus-soybean/src/views/qm/judge/index.vue
new file mode 100644
index 0000000..80ff821
--- /dev/null
+++ b/ruoyi-plus-soybean/src/views/qm/judge/index.vue
@@ -0,0 +1,323 @@
+<script setup lang="tsx">
+import { ref } from 'vue';
+import { NDivider, NTooltip } from 'naive-ui';
+import { fetchBatchDeleteJudge, fetchGetJudgeList } from '@/service/api/qm/judge';
+import { useAppStore } from '@/store/modules/app';
+import { useAuth } from '@/hooks/business/auth';
+import { useDownload } from '@/hooks/business/download';
+import { defaultTransform, useNaivePaginatedTable, useTableOperate } from '@/hooks/common/table';
+import { $t } from '@/locales';
+import ButtonIcon from '@/components/custom/button-icon.vue';
+import JudgeOperateDrawer from './modules/judge-operate-drawer.vue';
+import JudgeSearch from './modules/judge-search.vue';
+
+defineOptions({
+  name: 'JudgeList'
+});
+
+
+const appStore = useAppStore();
+const { download } = useDownload();
+const { hasAuth } = useAuth();
+
+const searchParams = ref<Api.Qm.JudgeSearchParams>({
+  pageNum: 1,
+  pageSize: 10,
+  matCode: null,
+  matName: null,
+  judgeName: null,
+  category: 0, // 榛樿閫夋嫨鎴愬搧
+  status: -1, // 榛樿閫夋嫨鍏ㄩ儴
+  params: {}
+});
+
+const { columns, columnChecks, data, getData, getDataByPage, loading, mobilePagination, scrollX } =
+  useNaivePaginatedTable({
+  api: () => {
+    const params = { ...searchParams.value };
+    if (params.status === -1) {
+      delete params.status;
+    }
+    return fetchGetJudgeList(params);
+  },
+  transform: response => defaultTransform(response),
+  onPaginationParamsChange: params => {
+    searchParams.value.pageNum = params.page;
+    searchParams.value.pageSize = params.pageSize;
+  },
+  columns: () => [
+    {
+      type: 'selection',
+      align: 'center',
+      width: 48
+    },
+    {
+      key: 'index',
+      title: $t('common.index'),
+      align: 'center',
+      width: 64,
+      render: (_, index) => index + 1
+    },
+    {
+      key: 'judgeName',
+      title: '鍒ゅ畾鍚嶇О',
+      align: 'center',
+      width: 200,
+      render: row => (
+        <NTooltip trigger="hover">
+          {{
+            trigger: () => (
+              <div
+                style={{
+                  whiteSpace: 'nowrap',
+                  overflow: 'hidden',
+                  textOverflow: 'ellipsis'
+                }}
+              >
+                {row.judgeName}
+              </div>
+            ),
+            default: () => row.judgeName
+          }}
+        </NTooltip>
+      )
+    },
+    {
+      key: 'category',
+      title: '鐗╂枡绫诲瀷',
+      align: 'center',
+      width: 100,
+      render: row => {
+        if (row.category === 0) {
+          return '鎴愬搧';
+        }
+        if (row.category === 1) {
+          return '杈呮枡';
+        }
+        return String(row.category);
+      }
+    },
+    {
+      key: 'typeName',
+      title: '杈呮枡绫诲瀷',
+      align: 'center',
+      width: 100,
+      render: row => (
+        <NTooltip trigger="hover">
+          {{
+            trigger: () => (
+              <div
+                style={{
+                  whiteSpace: 'nowrap',
+                  overflow: 'hidden',
+                  textOverflow: 'ellipsis'
+                }}
+              >
+                {row.typeName}
+              </div>
+            ),
+            default: () => row.typeName
+          }}
+        </NTooltip>
+      )
+    },
+    {
+      key: 'matName',
+      title: '鐗╂枡鐗屽彿',
+      align: 'center',
+      width: 180,
+      render: row => (
+        <NTooltip trigger="hover">
+          {{
+            trigger: () => (
+              <div
+                style={{
+                  whiteSpace: 'nowrap',
+                  overflow: 'hidden',
+                  textOverflow: 'ellipsis'
+                }}
+              >
+                {row.matName}
+              </div>
+            ),
+            default: () => row.matName
+          }}
+        </NTooltip>
+      )
+    },
+    {
+      key: 'matCode',
+      title: '鐗╂枡鐗屽彿浠g爜',
+      align: 'center',
+      width: 150
+    },
+    {
+      key: 'version',
+      title: '鐗堟湰鍙�',
+      align: 'center',
+      width: 100
+    },
+    {
+      key: 'status',
+      title: '鐘舵��',
+      align: 'center',
+      width: 100,
+      render: row => {
+        if (row.status === 1) {
+          return '鍚敤';
+        }
+        if (row.status === 0) {
+          return '褰掓。';
+        }
+        return String(row.status);
+      }
+    },
+    {
+      key: 'stdName',
+      title: '鍒ゅ畾瑙勭▼',
+      align: 'center',
+      width: 200
+    },
+    {
+      key: 'cdate',
+      title: '鍒涘缓鏃堕棿',
+      align: 'center',
+      width: 180
+    },
+    {
+      key: 'oper',
+      title: '鎿嶄綔浜�',
+      align: 'center',
+      width: 100
+    },
+    {
+      key: 'des',
+      title: '澶囨敞',
+      align: 'center',
+      width: 200
+    },
+    {
+      key: 'operate',
+      title: $t('common.operate'),
+      align: 'center',
+      fixed: 'right',
+      width: 130,
+      render: row => {
+        const divider = () => {
+          if (!hasAuth('qm:judge:edit') || !hasAuth('qm:judge:remove')) {
+            return null;
+          }
+          return <NDivider vertical />;
+        };
+
+        const editBtn = () => {
+          if (!hasAuth('qm:judge:edit')) {
+            return null;
+          }
+          return (
+            <ButtonIcon
+              text
+              type="primary"
+              icon="material-symbols:drive-file-rename-outline-outline"
+              tooltipContent={$t('common.edit')}
+              onClick={() => edit(row.id)}
+            />
+          );
+        };
+
+        const deleteBtn = () => {
+          if (!hasAuth('qm:judge:remove')) {
+            return null;
+          }
+          return (
+            <ButtonIcon
+              text
+              type="error"
+              icon="material-symbols:delete-outline"
+              tooltipContent={$t('common.delete')}
+              popconfirmContent={$t('common.confirmDelete')}
+              onPositiveClick={() => handleDelete(row.id)}
+            />
+          );
+        };
+
+        return (
+          <div class="flex-center gap-8px">
+            {editBtn()}
+            {divider()}
+            {deleteBtn()}
+          </div>
+        );
+      }
+    }
+  ]
+});
+
+const { drawerVisible, operateType, editingData, handleAdd, handleEdit, checkedRowKeys, onBatchDeleted, onDeleted } =
+  useTableOperate(data, 'id', getData);
+
+async function handleBatchDelete() {
+  // request
+  const { error } = await fetchBatchDeleteJudge(checkedRowKeys.value);
+  if (error) return;
+  onBatchDeleted();
+}
+
+async function handleDelete(id: CommonType.IdType) {
+  // request
+  const { error } = await fetchBatchDeleteJudge([id]);
+  if (error) return;
+  onDeleted();
+}
+
+function edit(id: CommonType.IdType) {
+  handleEdit(id);
+}
+
+function handleExport() {
+  download('/qm/judge/export', searchParams.value, `鍒ゅ畾渚濇嵁_${new Date().getTime()}.xlsx`);
+}
+</script>
+
+<template>
+  <div class="min-h-500px flex-col-stretch gap-16px overflow-hidden lt-sm:overflow-auto">
+    <JudgeSearch v-model:model="searchParams" @search="getDataByPage" />
+    <NCard title="鍒ゅ畾渚濇嵁鍒楄〃" :bordered="false" size="small" class="card-wrapper sm:flex-1-hidden">
+      <template #header-extra>
+        <TableHeaderOperation
+          v-model:columns="columnChecks"
+          :disabled-delete="checkedRowKeys.length === 0"
+          :loading="loading"
+          :show-add="hasAuth('qm:judge:add')"
+          :show-delete="hasAuth('qm:judge:remove')"
+          :show-export="hasAuth('qm:judge:export')"
+          @add="handleAdd"
+          @delete="handleBatchDelete"
+          @export="handleExport"
+          @refresh="getData"
+        />
+      </template>
+      <NDataTable
+        v-model:checked-row-keys="checkedRowKeys"
+        :columns="columns"
+        :data="data"
+        size="small"
+        :flex-height="!appStore.isMobile"
+        :scroll-x="scrollX"
+        :loading="loading"
+        remote
+        :row-key="row => row.id"
+        :pagination="mobilePagination"
+        class="sm:h-full"
+      />
+      <JudgeOperateDrawer
+        v-model:visible="drawerVisible"
+        :operate-type="operateType"
+        :row-data="editingData"
+        @submitted="getDataByPage"
+      />
+    </NCard>
+  </div>
+</template>
+
+<style scoped></style>
diff --git a/ruoyi-plus-soybean/src/views/qm/judge/modules/judge-operate-drawer.vue b/ruoyi-plus-soybean/src/views/qm/judge/modules/judge-operate-drawer.vue
new file mode 100644
index 0000000..2eb4cbb
--- /dev/null
+++ b/ruoyi-plus-soybean/src/views/qm/judge/modules/judge-operate-drawer.vue
@@ -0,0 +1,212 @@
+<script setup lang="ts">
+import { computed, ref, watch, onMounted } from 'vue';
+import { NSelect, SelectOption } from 'naive-ui';
+import { jsonClone } from '@sa/utils';
+import { fetchCreateJudge, fetchUpdateJudge } from '@/service/api/qm/judge';
+import { fetchGetStdList } from '@/service/api/qm/std';
+
+import { useFormRules, useNaiveForm } from '@/hooks/common/form';
+import { $t } from '@/locales';
+
+defineOptions({
+  name: 'JudgeOperateDrawer'
+});
+
+interface Props {
+  /** the type of operation */
+  operateType: NaiveUI.TableOperateType;
+  /** the edit row data */
+  rowData?: Api.Qm.Judge | null;
+}
+
+const props = defineProps<Props>();
+
+interface Emits {
+  (e: 'submitted'): void;
+}
+
+const emit = defineEmits<Emits>();
+
+const visible = defineModel<boolean>('visible', {
+  default: false
+});
+
+const { formRef, validate, restoreValidation } = useNaiveForm();
+const { createRequiredRule } = useFormRules();
+
+const title = computed(() => {
+  const titles: Record<NaiveUI.TableOperateType, string> = {
+    add: '鏂板鍒ゅ畾渚濇嵁',
+    edit: '缂栬緫鍒ゅ畾渚濇嵁'
+  };
+  return titles[props.operateType];
+});
+
+const statusOptions: SelectOption[] = [
+  { label: '鍚敤', value: 1 },
+  { label: '褰掓。', value: 0 }
+];
+
+const categoryOptions: SelectOption[] = [
+  { label: '鎴愬搧', value: 0 },
+  { label: '杈呮枡', value: 1 }
+];
+
+
+const stdOptions = ref<SelectOption[]>([]);
+
+type Model = Api.Qm.JudgeOperateParams;
+
+const model = ref<Model>(createDefaultModel());
+
+function createDefaultModel(): Model {
+  return {
+      matCode: '',
+      matName: '',
+      status: null,
+      stdCod: '',
+      cdate: null,
+      oper: '',
+      des: '',
+      category: null,
+      typeCode: '',
+      typeName: '',
+      judgeName: ''
+  };
+}
+
+type RuleKey = Extract<
+  keyof Model,
+  | 'id'
+>;
+
+const rules: Record<RuleKey, App.Global.FormRule> = {
+};
+
+function handleUpdateModelWhenEdit() {
+  model.value = createDefaultModel();
+
+  if (props.operateType === 'edit' && props.rowData) {
+    Object.assign(model.value, jsonClone(props.rowData));
+  }
+}
+
+function closeDrawer() {
+  visible.value = false;
+}
+
+async function fetchStdOptions() {
+    const response = await fetchGetStdList({ category: model.value.category });
+  const stdListData = response.data?.rows || [];
+
+  if (stdListData.length > 0) {
+    stdOptions.value = stdListData.map((item: Api.Qm.Std) => ({
+        label: item.stdName + item.stdCode,
+        value: String(item.id)
+      }));
+  }
+}
+
+async function handleSubmit() {
+  await validate();
+
+  const { matCode, matName, status, stdCod, cdate, oper, des, category, typeCode, typeName, judgeName } = model.value;
+
+  // request
+  if (props.operateType === 'add') {
+    const { error } = await fetchCreateJudge({ matCode, matName, status, stdCod, cdate, oper, des, category, typeCode, typeName, judgeName });
+    if (error) return;
+  }
+
+  if (props.operateType === 'edit') {
+    const { error } = await fetchUpdateJudge({ matCode, matName, status, stdCod, cdate, oper, des, category, typeCode, typeName, judgeName });
+    if (error) return;
+  }
+
+  window.$message?.success($t('common.updateSuccess'));
+  closeDrawer();
+  emit('submitted');
+}
+
+watch(visible, () => {
+  if (visible.value) {
+    handleUpdateModelWhenEdit();
+    restoreValidation();
+
+    // Fetch stdOptions when the drawer becomes visible
+    fetchStdOptions();
+  }
+});
+
+watch(() => model.value.category, () => {
+  fetchStdOptions();
+});
+</script>
+
+<template>
+  <NDrawer v-model:show="visible" :title="title" display-directive="show" :width="800" class="max-w-90%">
+    <NDrawerContent :title="title" :native-scrollbar="false" closable>
+      <NForm ref="formRef" :model="model" :rules="rules">
+        <NFormItem label="鍒ゅ畾鍚嶇О" path="judgeName">
+          <NInput v-model:value="model.judgeName" placeholder="璇疯緭鍏ュ垽瀹氬悕绉�" />
+        </NFormItem>
+        <NFormItem label="鐗╂枡鐗屽彿浠g爜" path="matCode">
+          <NInput v-model:value="model.matCode" placeholder="璇疯緭鍏ョ墿鏂欑墝鍙蜂唬鐮�" />
+        </NFormItem>
+        <NFormItem label="鐗╂枡鐗屽彿" path="matName">
+          <NInput v-model:value="model.matName" placeholder="璇疯緭鍏ョ墿鏂欑墝鍙�" />
+        </NFormItem>
+        <NFormItem label="鐗╂枡绫诲瀷" path="category">
+          <NSelect
+            v-model:value="model.category"
+            :options="categoryOptions"
+            placeholder="璇烽�夋嫨鐗╂枡绫诲瀷"
+          />
+        </NFormItem>
+        <NFormItem label="鐘舵��" path="status">
+          <NSelect
+            v-model:value="model.status"
+            :options="statusOptions"
+            placeholder="璇烽�夋嫨鐘舵��"
+          />
+        </NFormItem>
+        <NFormItem label="鍒ゅ畾瑙勭▼" path="stdCod">
+          <NSelect
+            v-model:value="model.stdCod"
+            :options="stdOptions"
+            placeholder="璇烽�夋嫨鍒ゅ畾瑙勭▼"
+          />
+        </NFormItem>
+        <NFormItem label="鍒涘缓鏃堕棿" path="cdate">
+          <NDatePicker
+            v-model:formatted-value="model.cdate"
+            type="datetime"
+            value-format="yyyy-MM-dd HH:mm:ss"
+            clearable
+          />
+        </NFormItem>
+        <NFormItem label="鐢熸晥浜�/鎿嶄綔浜哄憳" path="oper">
+          <NInput v-model:value="model.oper" placeholder="璇疯緭鍏ョ敓鏁堜汉/鎿嶄綔浜哄憳" />
+        </NFormItem>
+        <NFormItem label="澶囨敞" path="des">
+          <NInput v-model:value="model.des" placeholder="璇疯緭鍏ュ娉�" />
+        </NFormItem>
+
+        <NFormItem label="杈呮枡绫诲瀷缂栫爜" path="typeCode">
+          <NInput v-model:value="model.typeCode" placeholder="璇疯緭鍏ヨ緟鏂欑被鍨嬬紪鐮�" />
+        </NFormItem>
+        <NFormItem label="杈呮枡绫诲瀷鍚嶇О" path="typeName">
+          <NInput v-model:value="model.typeName" placeholder="璇疯緭鍏ヨ緟鏂欑被鍨嬪悕绉�" />
+        </NFormItem>
+      </NForm>
+      <template #footer>
+        <NSpace :size="16">
+          <NButton @click="closeDrawer">{{ $t('common.cancel') }}</NButton>
+          <NButton type="primary" @click="handleSubmit">{{ $t('common.confirm') }}</NButton>
+        </NSpace>
+      </template>
+    </NDrawerContent>
+  </NDrawer>
+</template>
+
+<style scoped></style>
diff --git a/ruoyi-plus-soybean/src/views/qm/judge/modules/judge-search.vue b/ruoyi-plus-soybean/src/views/qm/judge/modules/judge-search.vue
new file mode 100644
index 0000000..235b8a3
--- /dev/null
+++ b/ruoyi-plus-soybean/src/views/qm/judge/modules/judge-search.vue
@@ -0,0 +1,98 @@
+<script setup lang="ts">
+import { toRaw } from 'vue';
+import { jsonClone } from '@sa/utils';
+import { useNaiveForm } from '@/hooks/common/form';
+import { $t } from '@/locales';
+
+defineOptions({
+  name: 'JudgeSearch'
+});
+
+interface Emits {
+  (e: 'search'): void;
+}
+
+const emit = defineEmits<Emits>();
+
+const { formRef, validate, restoreValidation } = useNaiveForm();
+
+const model = defineModel<Api.Qm.JudgeSearchParams>('model', { required: true });
+
+const defaultModel = jsonClone(toRaw(model.value));
+
+function resetModel() {
+  Object.assign(model.value, defaultModel);
+}
+
+async function reset() {
+  await restoreValidation();
+  resetModel();
+  emit('search');
+}
+
+async function search() {
+  await validate();
+  emit('search');
+}
+</script>
+
+<template>
+  <NCard :bordered="false" size="small" class="card-wrapper">
+    <NCollapse>
+      <NCollapseItem :title="$t('common.search')" name="qm-judge-search">
+        <NForm ref="formRef" :model="model" label-placement="left" :label-width="80">
+          <NGrid responsive="screen" item-responsive>
+            <NFormItemGi span="24 s:12 m:6" label="鐗╂枡绫诲瀷" label-width="auto" path="category" class="pr-24px">
+              <NSelect
+                v-model:value="model.category"
+                :options="[
+                  { label: '鎴愬搧', value: 0 },
+                  { label: '杈呮枡', value: 1 }
+                ]"
+                placeholder="璇烽�夋嫨鐗╂枡绫诲瀷"
+              />
+            </NFormItemGi>
+
+            <NFormItemGi span="24 s:12 m:6" label="鐗╂枡鐗屽彿浠g爜" label-width="auto" path="matCode" class="pr-24px">
+              <NInput v-model:value="model.matCode" placeholder="璇疯緭鍏ョ墿鏂欑墝鍙蜂唬鐮�" />
+            </NFormItemGi>
+            <NFormItemGi span="24 s:12 m:6" label="鐗╂枡鐗屽彿" label-width="auto" path="matName" class="pr-24px">
+              <NInput v-model:value="model.matName" placeholder="璇疯緭鍏ョ墿鏂欑墝鍙�" />
+            </NFormItemGi>
+            <NFormItemGi span="24 s:12 m:6" label="鍒ゅ畾鍚嶇О" label-width="auto" path="judgeName" class="pr-24px">
+              <NInput v-model:value="model.judgeName" placeholder="璇疯緭鍏ュ垽瀹氬悕绉�" />
+            </NFormItemGi>
+            <NFormItemGi span="24 s:12 m:6" label="鏄惁鍚敤" label-width="auto" path="status" class="pr-24px">
+              <NSelect
+                v-model:value="model.status"
+                :options="[
+                  { label: '鍏ㄩ儴', value: -1 },
+                  { label: '鍚敤', value: 1 },
+                  { label: '褰掓。', value: 0 }
+                ]"
+              />
+            </NFormItemGi>
+            <NFormItemGi :show-feedback="false" span="24" class="pr-24px">
+              <NSpace class="w-full" justify="end">
+                <NButton @click="reset">
+                  <template #icon>
+                    <icon-ic-round-refresh class="text-icon" />
+                  </template>
+                  {{ $t('common.reset') }}
+                </NButton>
+                <NButton type="primary" ghost @click="search">
+                  <template #icon>
+                    <icon-ic-round-search class="text-icon" />
+                  </template>
+                  {{ $t('common.search') }}
+                </NButton>
+              </NSpace>
+            </NFormItemGi>
+          </NGrid>
+        </NForm>
+      </NCollapseItem>
+    </NCollapse>
+  </NCard>
+</template>
+
+<style scoped></style>

--
Gitblit v1.9.3