From 06f6eb3b6159323a26e55cc15ef3000786931e24 Mon Sep 17 00:00:00 2001
From: zhuguifei <312353457@qq.com>
Date: 星期五, 13 三月 2026 13:14:14 +0800
Subject: [PATCH] feat(新增基础数据判断规程、以及判定规程明细):

---
 ruoyi-plus-soybean/src/router/elegant/routes.ts                                                                |    9 
 .gitignore                                                                                                     |    4 
 ruoyi-plus-soybean/src/views/qm/std/index.vue                                                                  |  109 ++-
 RuoYi-Vue-Plus/ruoyi-modules/ruoyi-qa/src/main/java/org/dromara/qa/qm/service/IQmCheckitemService.java         |   70 ++
 ruoyi-plus-soybean/src/typings/api/qm.checkitem.api.d.ts                                                       |   88 +++
 RuoYi-Vue-Plus/ruoyi-modules/ruoyi-qa/src/main/java/org/dromara/qa/qm/mapper/QmCheckitemMapper.java            |   15 
 ruoyi-plus-soybean/src/views/qm/std/modules/std-sub-table.vue                                                  |  151 +++++
 ruoyi-plus-soybean/src/service/api/qm/checkitem.ts                                                             |   45 +
 RuoYi-Vue-Plus/ruoyi-modules/ruoyi-qa/src/main/java/org/dromara/qa/qm/controller/QmCheckitemController.java    |  114 ++++
 ruoyi-plus-soybean/src/views/qm/checkitem/index.vue                                                            |  223 ++++++++
 RuoYi-Vue-Plus/ruoyi-modules/ruoyi-qa/src/main/java/org/dromara/qa/qm/domain/vo/QmCheckitemVo.java             |  130 ++++
 ruoyi-plus-soybean/src/router/elegant/imports.ts                                                               |    1 
 ruoyi-plus-soybean/src/typings/elegant-router.d.ts                                                             |    2 
 RuoYi-Vue-Plus/ruoyi-modules/ruoyi-qa/src/main/resources/mapper/qa/qm/QmCheckitemMapper.xml                    |    6 
 ruoyi-plus-soybean/src/views/qm/checkitem/modules/checkitem-operate-drawer.vue                                 |  178 ++++++
 RuoYi-Vue-Plus/ruoyi-modules/ruoyi-qa/src/main/java/org/dromara/qa/qm/domain/bo/QmCheckitemBo.java             |  105 +++
 RuoYi-Vue-Plus/ruoyi-modules/ruoyi-qa/src/main/java/org/dromara/qa/qm/service/impl/QmCheckitemServiceImpl.java |  164 ++++++
 RuoYi-Vue-Plus/ruoyi-modules/ruoyi-qa/src/main/java/org/dromara/qa/qm/domain/QmCheckitem.java                  |  105 +++
 ruoyi-plus-soybean/src/views/qm/checkitem/modules/checkitem-search.vue                                         |   74 ++
 ruoyi-plus-soybean/src/router/elegant/transform.ts                                                             |    1 
 20 files changed, 1,556 insertions(+), 38 deletions(-)

diff --git a/.gitignore b/.gitignore
index 37ef30b..28e2b8a 100644
--- a/.gitignore
+++ b/.gitignore
@@ -2,4 +2,6 @@
 **/logs
 **/target
 **/.vscode
-**/.flattened-pom.xml
\ No newline at end of file
+**/.flattened-pom.xml
+RuoYi-Vue-Plus/.msp
+RuoYi-Vue-Plus/gzdams
diff --git a/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-qa/src/main/java/org/dromara/qa/qm/controller/QmCheckitemController.java b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-qa/src/main/java/org/dromara/qa/qm/controller/QmCheckitemController.java
new file mode 100644
index 0000000..f7f7c51
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-qa/src/main/java/org/dromara/qa/qm/controller/QmCheckitemController.java
@@ -0,0 +1,114 @@
+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.QmCheckitemVo;
+import org.dromara.qa.qm.domain.bo.QmCheckitemBo;
+import org.dromara.qa.qm.service.IQmCheckitemService;
+import org.dromara.common.mybatis.core.page.TableDataInfo;
+
+/**
+ * 瑙勭▼妫�楠岄」鐩�
+ *
+ * @author zhuguifei
+ * @date 2026-03-12
+ */
+@Validated
+@RequiredArgsConstructor
+@RestController
+@RequestMapping("/qm/checkitem")
+public class QmCheckitemController extends BaseController {
+
+    private final IQmCheckitemService qmCheckitemService;
+
+    /**
+     * 鏌ヨ瑙勭▼妫�楠岄」鐩垪琛�
+     */
+    @SaCheckPermission("qm:checkitem:list")
+    @GetMapping("/list")
+    public TableDataInfo<QmCheckitemVo> list(QmCheckitemBo bo, PageQuery pageQuery) {
+        return qmCheckitemService.queryPageList(bo, pageQuery);
+    }
+
+    @SaCheckPermission("qm:checkitem:list")
+    @GetMapping("/tree")
+    public TableDataInfo<QmCheckitemVo> tree(@RequestParam @NotBlank(message = "stdCode涓嶈兘涓虹┖") String stdCode,
+                                             @RequestParam(required = false) Integer page,
+                                             @RequestParam(required = false) Integer rows) {
+        List<QmCheckitemVo> list = qmCheckitemService.queryTreeListByStdCode(stdCode);
+        return TableDataInfo.build(list);
+    }
+
+    /**
+     * 瀵煎嚭瑙勭▼妫�楠岄」鐩垪琛�
+     */
+    @SaCheckPermission("qm:checkitem:export")
+    @Log(title = "瑙勭▼妫�楠岄」鐩�", businessType = BusinessType.EXPORT)
+    @PostMapping("/export")
+    public void export(QmCheckitemBo bo, HttpServletResponse response) {
+        List<QmCheckitemVo> list = qmCheckitemService.queryList(bo);
+        ExcelUtil.exportExcel(list, "瑙勭▼妫�楠岄」鐩�", QmCheckitemVo.class, response);
+    }
+
+    /**
+     * 鑾峰彇瑙勭▼妫�楠岄」鐩缁嗕俊鎭�
+     *
+     * @param id 涓婚敭
+     */
+    @SaCheckPermission("qm:checkitem:query")
+    @GetMapping("/{id}")
+    public R<QmCheckitemVo> getInfo(@NotNull(message = "涓婚敭涓嶈兘涓虹┖")
+                                     @PathVariable String id) {
+        return R.ok(qmCheckitemService.queryById(id));
+    }
+
+    /**
+     * 鏂板瑙勭▼妫�楠岄」鐩�
+     */
+    @SaCheckPermission("qm:checkitem:add")
+    @Log(title = "瑙勭▼妫�楠岄」鐩�", businessType = BusinessType.INSERT)
+    @RepeatSubmit()
+    @PostMapping()
+    public R<Void> add(@Validated(AddGroup.class) @RequestBody QmCheckitemBo bo) {
+        return toAjax(qmCheckitemService.insertByBo(bo));
+    }
+
+    /**
+     * 淇敼瑙勭▼妫�楠岄」鐩�
+     */
+    @SaCheckPermission("qm:checkitem:edit")
+    @Log(title = "瑙勭▼妫�楠岄」鐩�", businessType = BusinessType.UPDATE)
+    @RepeatSubmit()
+    @PutMapping()
+    public R<Void> edit(@Validated(EditGroup.class) @RequestBody QmCheckitemBo bo) {
+        return toAjax(qmCheckitemService.updateByBo(bo));
+    }
+
+    /**
+     * 鍒犻櫎瑙勭▼妫�楠岄」鐩�
+     *
+     * @param ids 涓婚敭涓�
+     */
+    @SaCheckPermission("qm:checkitem:remove")
+    @Log(title = "瑙勭▼妫�楠岄」鐩�", businessType = BusinessType.DELETE)
+    @DeleteMapping("/{ids}")
+    public R<Void> remove(@NotEmpty(message = "涓婚敭涓嶈兘涓虹┖")
+                          @PathVariable String[] ids) {
+        return toAjax(qmCheckitemService.deleteWithValidByIds(List.of(ids), true));
+    }
+}
diff --git a/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-qa/src/main/java/org/dromara/qa/qm/domain/QmCheckitem.java b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-qa/src/main/java/org/dromara/qa/qm/domain/QmCheckitem.java
new file mode 100644
index 0000000..b98ae97
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-qa/src/main/java/org/dromara/qa/qm/domain/QmCheckitem.java
@@ -0,0 +1,105 @@
+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_checkitem
+ *
+ * @author zhuguifei
+ * @date 2026-03-12
+ */
+@Data
+@TableName("qm_checkitem")
+public class QmCheckitem {
+
+    @Serial
+    private static final long serialVersionUID = 1L;
+
+    /**
+     * 缂栫爜
+     */
+    @TableId(value = "id")
+    private String id;
+
+    /**
+     * 妫�楠岄」鐩唬鐮�
+     */
+    private String itemCode;
+
+    /**
+     * 妫�楠岄」鐩悕绉�
+     */
+    private String itemName;
+
+    /**
+     * 鍗曚綅
+     */
+    private String unit;
+
+    /**
+     * 鍚敤
+     */
+    private Long enable;
+
+    /**
+     * 鍒犻櫎
+     */
+    private Long del;
+
+    /**
+     * 妫�楠岄」鎻忚堪
+     */
+    private String itemDes;
+
+    /**
+     * 瑙勭▼浠g爜
+     */
+    private String stdCode;
+
+    /**
+     * 浠櫒鎻忚堪
+     */
+    private String instrumentDes;
+
+    /**
+     * 缂洪櫡浣嶇疆-澶栬鐢�
+     */
+    private String location;
+
+    /**
+     * 鍒咥,B,C,D鍥涗釜绾у埆
+     */
+    private String checkLevel;
+
+    /**
+     * 鏄惁鍚堟垚椤�
+     */
+    private Long ismix;
+
+    /**
+     * 鍏宠仈椤笽D
+     */
+    private String rid;
+
+    /**
+     * 绫诲埆 0:鎴愬搧 1杈呮枡
+     */
+    private Long category;
+
+    /**
+     * 浠櫒缂栫爜
+     */
+    private String instrumentCode;
+
+    /**
+     * 鍒嗗��
+     */
+    private Long score;
+
+
+}
diff --git a/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-qa/src/main/java/org/dromara/qa/qm/domain/bo/QmCheckitemBo.java b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-qa/src/main/java/org/dromara/qa/qm/domain/bo/QmCheckitemBo.java
new file mode 100644
index 0000000..20900a2
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-qa/src/main/java/org/dromara/qa/qm/domain/bo/QmCheckitemBo.java
@@ -0,0 +1,105 @@
+package org.dromara.qa.qm.domain.bo;
+
+import org.dromara.qa.qm.domain.QmCheckitem;
+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_checkitem
+ *
+ * @author zhuguifei
+ * @date 2026-03-12
+ */
+@Data
+@EqualsAndHashCode(callSuper = true)
+@AutoMapper(target = QmCheckitem.class, reverseConvertGenerate = false)
+public class QmCheckitemBo extends BaseEntity {
+
+    /**
+     * 缂栫爜
+     */
+    @NotBlank(message = "缂栫爜涓嶈兘涓虹┖", groups = { EditGroup.class })
+    private String id;
+
+    /**
+     * 妫�楠岄」鐩唬鐮�
+     */
+    private String itemCode;
+
+    /**
+     * 妫�楠岄」鐩悕绉�
+     */
+    private String itemName;
+
+    /**
+     * 鍗曚綅
+     */
+    private String unit;
+
+    /**
+     * 鍚敤
+     */
+    private Long enable;
+
+    /**
+     * 鍒犻櫎
+     */
+    private Long del;
+
+    /**
+     * 妫�楠岄」鎻忚堪
+     */
+    private String itemDes;
+
+    /**
+     * 瑙勭▼浠g爜
+     */
+    private String stdCode;
+
+    /**
+     * 浠櫒鎻忚堪
+     */
+    private String instrumentDes;
+
+    /**
+     * 缂洪櫡浣嶇疆-澶栬鐢�
+     */
+    private String location;
+
+    /**
+     * 鍒咥,B,C,D鍥涗釜绾у埆
+     */
+    private String checkLevel;
+
+    /**
+     * 鏄惁鍚堟垚椤�
+     */
+    private Long ismix;
+
+    /**
+     * 鍏宠仈椤笽D
+     */
+    private String rid;
+
+    /**
+     * 绫诲埆 0:鎴愬搧 1杈呮枡
+     */
+    private Long category;
+
+    /**
+     * 浠櫒缂栫爜
+     */
+    private String instrumentCode;
+
+    /**
+     * 鍒嗗��
+     */
+    private Long score;
+
+
+}
diff --git a/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-qa/src/main/java/org/dromara/qa/qm/domain/vo/QmCheckitemVo.java b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-qa/src/main/java/org/dromara/qa/qm/domain/vo/QmCheckitemVo.java
new file mode 100644
index 0000000..3c8d5e4
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-qa/src/main/java/org/dromara/qa/qm/domain/vo/QmCheckitemVo.java
@@ -0,0 +1,130 @@
+package org.dromara.qa.qm.domain.vo;
+
+import org.dromara.qa.qm.domain.QmCheckitem;
+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;
+import java.util.List;
+
+
+
+/**
+ * 瑙勭▼妫�楠岄」鐩鍥惧璞� qm_checkitem
+ *
+ * @author zhuguifei
+ * @date 2026-03-12
+ */
+@Data
+@ExcelIgnoreUnannotated
+@AutoMapper(target = QmCheckitem.class)
+public class QmCheckitemVo implements Serializable {
+
+    @Serial
+    private static final long serialVersionUID = 1L;
+
+    /**
+     * 缂栫爜
+     */
+    @ExcelProperty(value = "缂栫爜")
+    private String id;
+
+    /**
+     * 妫�楠岄」鐩唬鐮�
+     */
+    @ExcelProperty(value = "妫�楠岄」鐩唬鐮�")
+    private String itemCode;
+
+    /**
+     * 妫�楠岄」鐩悕绉�
+     */
+    @ExcelProperty(value = "妫�楠岄」鐩悕绉�")
+    private String itemName;
+
+    /**
+     * 鍗曚綅
+     */
+    @ExcelProperty(value = "鍗曚綅")
+    private String unit;
+
+    /**
+     * 鍚敤
+     */
+    @ExcelProperty(value = "鍚敤")
+    private Long enable;
+
+    /**
+     * 鍒犻櫎
+     */
+    @ExcelProperty(value = "鍒犻櫎")
+    private Long del;
+
+    /**
+     * 妫�楠岄」鎻忚堪
+     */
+    @ExcelProperty(value = "妫�楠岄」鎻忚堪")
+    private String itemDes;
+
+    /**
+     * 瑙勭▼浠g爜
+     */
+    @ExcelProperty(value = "瑙勭▼浠g爜")
+    private String stdCode;
+
+    /**
+     * 浠櫒鎻忚堪
+     */
+    @ExcelProperty(value = "浠櫒鎻忚堪")
+    private String instrumentDes;
+
+    /**
+     * 缂洪櫡浣嶇疆-澶栬鐢�
+     */
+    @ExcelProperty(value = "缂洪櫡浣嶇疆-澶栬鐢�")
+    private String location;
+
+    /**
+     * 鍒咥,B,C,D鍥涗釜绾у埆
+     */
+    @ExcelProperty(value = "鍒咥,B,C,D鍥涗釜绾у埆")
+    private String checkLevel;
+
+    /**
+     * 鏄惁鍚堟垚椤�
+     */
+    @ExcelProperty(value = "鏄惁鍚堟垚椤�")
+    private Long ismix;
+
+    /**
+     * 鍏宠仈椤笽D
+     */
+    @ExcelProperty(value = "鍏宠仈椤笽D")
+    private String rid;
+
+    /**
+     * 绫诲埆 0:鎴愬搧 1杈呮枡
+     */
+    @ExcelProperty(value = "绫诲埆 0:鎴愬搧 1杈呮枡")
+    private Long category;
+
+    /**
+     * 浠櫒缂栫爜
+     */
+    @ExcelProperty(value = "浠櫒缂栫爜")
+    private String instrumentCode;
+
+    /**
+     * 鍒嗗��
+     */
+    @ExcelProperty(value = "鍒嗗��")
+    private Long score;
+
+    private List<QmCheckitemVo> children;
+
+}
diff --git a/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-qa/src/main/java/org/dromara/qa/qm/mapper/QmCheckitemMapper.java b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-qa/src/main/java/org/dromara/qa/qm/mapper/QmCheckitemMapper.java
new file mode 100644
index 0000000..4994563
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-qa/src/main/java/org/dromara/qa/qm/mapper/QmCheckitemMapper.java
@@ -0,0 +1,15 @@
+package org.dromara.qa.qm.mapper;
+
+import org.dromara.qa.qm.domain.QmCheckitem;
+import org.dromara.qa.qm.domain.vo.QmCheckitemVo;
+import org.dromara.common.mybatis.core.mapper.BaseMapperPlus;
+
+/**
+ * 瑙勭▼妫�楠岄」鐩甅apper鎺ュ彛
+ *
+ * @author zhuguifei
+ * @date 2026-03-12
+ */
+public interface QmCheckitemMapper extends BaseMapperPlus<QmCheckitem, QmCheckitemVo> {
+
+}
diff --git a/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-qa/src/main/java/org/dromara/qa/qm/service/IQmCheckitemService.java b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-qa/src/main/java/org/dromara/qa/qm/service/IQmCheckitemService.java
new file mode 100644
index 0000000..c201b8b
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-qa/src/main/java/org/dromara/qa/qm/service/IQmCheckitemService.java
@@ -0,0 +1,70 @@
+package org.dromara.qa.qm.service;
+
+import org.dromara.qa.qm.domain.vo.QmCheckitemVo;
+import org.dromara.qa.qm.domain.bo.QmCheckitemBo;
+import org.dromara.common.mybatis.core.page.TableDataInfo;
+import org.dromara.common.mybatis.core.page.PageQuery;
+
+import java.util.Collection;
+import java.util.List;
+
+/**
+ * 瑙勭▼妫�楠岄」鐩甋ervice鎺ュ彛
+ *
+ * @author zhuguifei
+ * @date 2026-03-12
+ */
+public interface IQmCheckitemService {
+
+    /**
+     * 鏌ヨ瑙勭▼妫�楠岄」鐩�
+     *
+     * @param id 涓婚敭
+     * @return 瑙勭▼妫�楠岄」鐩�
+     */
+    QmCheckitemVo queryById(String id);
+
+    /**
+     * 鍒嗛〉鏌ヨ瑙勭▼妫�楠岄」鐩垪琛�
+     *
+     * @param bo        鏌ヨ鏉′欢
+     * @param pageQuery 鍒嗛〉鍙傛暟
+     * @return 瑙勭▼妫�楠岄」鐩垎椤靛垪琛�
+     */
+    TableDataInfo<QmCheckitemVo> queryPageList(QmCheckitemBo bo, PageQuery pageQuery);
+
+    /**
+     * 鏌ヨ绗﹀悎鏉′欢鐨勮绋嬫楠岄」鐩垪琛�
+     *
+     * @param bo 鏌ヨ鏉′欢
+     * @return 瑙勭▼妫�楠岄」鐩垪琛�
+     */
+    List<QmCheckitemVo> queryList(QmCheckitemBo bo);
+
+    List<QmCheckitemVo> queryTreeListByStdCode(String stdCode);
+
+    /**
+     * 鏂板瑙勭▼妫�楠岄」鐩�
+     *
+     * @param bo 瑙勭▼妫�楠岄」鐩�
+     * @return 鏄惁鏂板鎴愬姛
+     */
+    Boolean insertByBo(QmCheckitemBo bo);
+
+    /**
+     * 淇敼瑙勭▼妫�楠岄」鐩�
+     *
+     * @param bo 瑙勭▼妫�楠岄」鐩�
+     * @return 鏄惁淇敼鎴愬姛
+     */
+    Boolean updateByBo(QmCheckitemBo 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/QmCheckitemServiceImpl.java b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-qa/src/main/java/org/dromara/qa/qm/service/impl/QmCheckitemServiceImpl.java
new file mode 100644
index 0000000..a8299aa
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-qa/src/main/java/org/dromara/qa/qm/service/impl/QmCheckitemServiceImpl.java
@@ -0,0 +1,164 @@
+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.QmCheckitemBo;
+import org.dromara.qa.qm.domain.vo.QmCheckitemVo;
+import org.dromara.qa.qm.domain.QmCheckitem;
+import org.dromara.qa.qm.mapper.QmCheckitemMapper;
+import org.dromara.qa.qm.service.IQmCheckitemService;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+import java.util.Collection;
+
+/**
+ * 瑙勭▼妫�楠岄」鐩甋ervice涓氬姟灞傚鐞�
+ *
+ * @author zhuguifei
+ * @date 2026-03-12
+ */
+@Slf4j
+@RequiredArgsConstructor
+@Service
+public class QmCheckitemServiceImpl implements IQmCheckitemService {
+
+    private final QmCheckitemMapper baseMapper;
+
+    /**
+     * 鏌ヨ瑙勭▼妫�楠岄」鐩�
+     *
+     * @param id 涓婚敭
+     * @return 瑙勭▼妫�楠岄」鐩�
+     */
+    @Override
+    public QmCheckitemVo queryById(String id){
+        return baseMapper.selectVoById(id);
+    }
+
+    /**
+     * 鍒嗛〉鏌ヨ瑙勭▼妫�楠岄」鐩垪琛�
+     *
+     * @param bo        鏌ヨ鏉′欢
+     * @param pageQuery 鍒嗛〉鍙傛暟
+     * @return 瑙勭▼妫�楠岄」鐩垎椤靛垪琛�
+     */
+    @Override
+    public TableDataInfo<QmCheckitemVo> queryPageList(QmCheckitemBo bo, PageQuery pageQuery) {
+        LambdaQueryWrapper<QmCheckitem> lqw = buildQueryWrapper(bo);
+        Page<QmCheckitemVo> result = baseMapper.selectVoPage(pageQuery.build(), lqw);
+        return TableDataInfo.build(result);
+    }
+
+    /**
+     * 鏌ヨ绗﹀悎鏉′欢鐨勮绋嬫楠岄」鐩垪琛�
+     *
+     * @param bo 鏌ヨ鏉′欢
+     * @return 瑙勭▼妫�楠岄」鐩垪琛�
+     */
+    @Override
+    public List<QmCheckitemVo> queryList(QmCheckitemBo bo) {
+        LambdaQueryWrapper<QmCheckitem> lqw = buildQueryWrapper(bo);
+        return baseMapper.selectVoList(lqw);
+    }
+
+    @Override
+    public List<QmCheckitemVo> queryTreeListByStdCode(String stdCode) {
+        List<QmCheckitemVo> roots = selectTreeNodes(stdCode, null);
+        for (QmCheckitemVo root : roots) {
+            List<QmCheckitemVo> children = selectTreeNodes(stdCode, root.getId());
+            for (QmCheckitemVo child : children) {
+                List<QmCheckitemVo> grandchildren = selectTreeNodes(stdCode, child.getId());
+                child.setChildren(grandchildren);
+            }
+            root.setChildren(children);
+        }
+        return roots;
+    }
+
+    private List<QmCheckitemVo> selectTreeNodes(String stdCode, String rid) {
+        if (StringUtils.isBlank(stdCode)) {
+            return new ArrayList<>();
+        }
+        LambdaQueryWrapper<QmCheckitem> lqw = Wrappers.lambdaQuery();
+        lqw.eq(QmCheckitem::getStdCode, stdCode);
+        lqw.ne(QmCheckitem::getDel, 1L);
+        if (rid == null) {
+            lqw.isNull(QmCheckitem::getRid);
+        } else {
+            lqw.eq(QmCheckitem::getRid, rid);
+        }
+        lqw.orderByAsc(QmCheckitem::getId);
+        return baseMapper.selectVoList(lqw);
+    }
+
+    private LambdaQueryWrapper<QmCheckitem> buildQueryWrapper(QmCheckitemBo bo) {
+        Map<String, Object> params = bo.getParams();
+        LambdaQueryWrapper<QmCheckitem> lqw = Wrappers.lambdaQuery();
+        lqw.orderByAsc(QmCheckitem::getId);
+        lqw.like(StringUtils.isNotBlank(bo.getItemCode()), QmCheckitem::getItemCode, bo.getItemCode());
+        lqw.like(StringUtils.isNotBlank(bo.getItemName()), QmCheckitem::getItemName, bo.getItemName());
+        return lqw;
+    }
+
+    /**
+     * 鏂板瑙勭▼妫�楠岄」鐩�
+     *
+     * @param bo 瑙勭▼妫�楠岄」鐩�
+     * @return 鏄惁鏂板鎴愬姛
+     */
+    @Override
+    public Boolean insertByBo(QmCheckitemBo bo) {
+        QmCheckitem add = MapstructUtils.convert(bo, QmCheckitem.class);
+        validEntityBeforeSave(add);
+        boolean flag = baseMapper.insert(add) > 0;
+        if (flag) {
+            bo.setId(add.getId());
+        }
+        return flag;
+    }
+
+    /**
+     * 淇敼瑙勭▼妫�楠岄」鐩�
+     *
+     * @param bo 瑙勭▼妫�楠岄」鐩�
+     * @return 鏄惁淇敼鎴愬姛
+     */
+    @Override
+    public Boolean updateByBo(QmCheckitemBo bo) {
+        QmCheckitem update = MapstructUtils.convert(bo, QmCheckitem.class);
+        validEntityBeforeSave(update);
+        return baseMapper.updateById(update) > 0;
+    }
+
+    /**
+     * 淇濆瓨鍓嶇殑鏁版嵁鏍¢獙
+     */
+    private void validEntityBeforeSave(QmCheckitem 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/qm/QmCheckitemMapper.xml b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-qa/src/main/resources/mapper/qa/qm/QmCheckitemMapper.xml
new file mode 100644
index 0000000..b918fa2
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-qa/src/main/resources/mapper/qa/qm/QmCheckitemMapper.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.QmCheckitemMapper">
+</mapper>
diff --git a/ruoyi-plus-soybean/src/router/elegant/imports.ts b/ruoyi-plus-soybean/src/router/elegant/imports.ts
index 4d0ffc3..c268c52 100755
--- a/ruoyi-plus-soybean/src/router/elegant/imports.ts
+++ b/ruoyi-plus-soybean/src/router/elegant/imports.ts
@@ -38,6 +38,7 @@
   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_checkitem: () => import("@/views/qm/checkitem/index.vue"),
   qm_std: () => import("@/views/qm/std/index.vue"),
   system_client: () => import("@/views/system/client/index.vue"),
   system_config: () => import("@/views/system/config/index.vue"),
diff --git a/ruoyi-plus-soybean/src/router/elegant/routes.ts b/ruoyi-plus-soybean/src/router/elegant/routes.ts
index 35c99c3..28f3fb5 100755
--- a/ruoyi-plus-soybean/src/router/elegant/routes.ts
+++ b/ruoyi-plus-soybean/src/router/elegant/routes.ts
@@ -266,6 +266,15 @@
         }
       },
       {
+        name: 'qm_checkitem',
+        path: '/qm/checkitem',
+        component: 'view.qm_checkitem',
+        meta: {
+          title: 'qm_checkitem',
+          i18nKey: 'route.qm_checkitem'
+        }
+      },
+      {
         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 fac85aa..4c31383 100755
--- a/ruoyi-plus-soybean/src/router/elegant/transform.ts
+++ b/ruoyi-plus-soybean/src/router/elegant/transform.ts
@@ -193,6 +193,7 @@
   "monitor_operlog": "/monitor/operlog",
   "qm": "/qm",
   "qm_batch": "/qm/batch",
+  "qm_checkitem": "/qm/checkitem",
   "qm_std": "/qm/std",
   "social-callback": "/social-callback",
   "system": "/system",
diff --git a/ruoyi-plus-soybean/src/service/api/qm/checkitem.ts b/ruoyi-plus-soybean/src/service/api/qm/checkitem.ts
new file mode 100644
index 0000000..19497c5
--- /dev/null
+++ b/ruoyi-plus-soybean/src/service/api/qm/checkitem.ts
@@ -0,0 +1,45 @@
+import { request } from '@/service/request';
+
+/** 鑾峰彇瑙勭▼妫�楠岄」鐩垪琛� */
+export function fetchGetCheckitemList (params?: Api.Qm.CheckitemSearchParams) {
+    return request<Api.Qm.CheckitemList>({
+        url: '/qm/checkitem/list',
+        method: 'get',
+        params
+    });
+}
+
+/** 鑾峰彇瑙勭▼妫�楠岄」鐩垪琛� */
+export function fetchGetCheckitemTree (params?: Api.Qm.CheckitemSearchParams) {
+  return request<Api.Qm.CheckitemList>({
+    url: '/qm/checkitem/tree',
+    method: 'get',
+    params
+  });
+}
+
+/** 鏂板瑙勭▼妫�楠岄」鐩� */
+export function fetchCreateCheckitem (data: Api.Qm.CheckitemOperateParams) {
+    return request<boolean>({
+        url: '/qm/checkitem',
+        method: 'post',
+        data
+    });
+}
+
+/** 淇敼瑙勭▼妫�楠岄」鐩� */
+export function fetchUpdateCheckitem (data: Api.Qm.CheckitemOperateParams) {
+    return request<boolean>({
+        url: '/qm/checkitem',
+        method: 'put',
+        data
+    });
+}
+
+/** 鎵归噺鍒犻櫎瑙勭▼妫�楠岄」鐩� */
+export function fetchBatchDeleteCheckitem (ids: CommonType.IdType[]) {
+    return request<boolean>({
+        url: `/qm/checkitem/${ids.join(',')}`,
+        method: 'delete'
+    });
+}
diff --git a/ruoyi-plus-soybean/src/typings/api/qm.checkitem.api.d.ts b/ruoyi-plus-soybean/src/typings/api/qm.checkitem.api.d.ts
new file mode 100644
index 0000000..f92cc04
--- /dev/null
+++ b/ruoyi-plus-soybean/src/typings/api/qm.checkitem.api.d.ts
@@ -0,0 +1,88 @@
+/**
+ * Namespace Api
+ *
+ * All backend api type
+ */
+declare namespace Api {
+    /**
+     * namespace Qm
+     *
+     * backend api module: "Qm"
+     */
+    namespace Qm {
+        /** checkitem */
+        type Checkitem = Common.CommonRecord<{
+            /** 缂栫爜 */
+                id: CommonType.IdType;
+            /** 妫�楠岄」鐩唬鐮� */
+                itemCode: string;
+            /** 妫�楠岄」鐩悕绉� */
+                itemName: string;
+            /** 鍗曚綅 */
+                unit: string;
+            /** 鍚敤 */
+                enable: number;
+            /** 鍒犻櫎 */
+                del: number;
+            /** 妫�楠岄」鎻忚堪 */
+                itemDes: string;
+            /** 瑙勭▼浠g爜 */
+                stdCode: string;
+            /** 浠櫒鎻忚堪 */
+                instrumentDes: string;
+            /** 缂洪櫡浣嶇疆-澶栬鐢� */
+                location: string;
+            /** 鍒咥,B,C,D鍥涗釜绾у埆 */
+                checkLevel: string;
+            /** 鏄惁鍚堟垚椤� */
+                ismix: number;
+            /** 鍏宠仈椤笽D */
+                rid: CommonType.IdType;
+            /** 绫诲埆 0:鎴愬搧 1杈呮枡 */
+                category: number;
+            /** 浠櫒缂栫爜 */
+                instrumentCode: string;
+            /** 鍒嗗�� */
+                score: number;
+            /** 瀛愯妭鐐� */
+                children?: Api.Qm.Checkitem[];
+        }>;
+
+        /** checkitem search params */
+        type CheckitemSearchParams = CommonType.RecordNullable<
+            Pick<
+                Api.Qm.Checkitem,
+                        | 'itemCode'
+                        | 'itemName'
+                        | 'stdCode'
+            > &
+            Api.Common.CommonSearchParams
+        >;
+
+        /** checkitem operate params */
+        type CheckitemOperateParams = CommonType.RecordNullable<
+            Pick<
+                Api.Qm.Checkitem,
+                        | 'id'
+                        | 'itemCode'
+                        | 'itemName'
+                        | 'unit'
+                        | 'enable'
+                        | 'del'
+                        | 'itemDes'
+                        | 'stdCode'
+                        | 'instrumentDes'
+                        | 'location'
+                        | 'checkLevel'
+                        | 'ismix'
+                        | 'rid'
+                        | 'category'
+                        | 'instrumentCode'
+                        | 'score'
+            >
+        >;
+
+        /** checkitem list */
+        type CheckitemList = Api.Common.PaginatingQueryRecord<Checkitem>;
+    }
+}
diff --git a/ruoyi-plus-soybean/src/typings/elegant-router.d.ts b/ruoyi-plus-soybean/src/typings/elegant-router.d.ts
index 013ab97..621aca8 100755
--- a/ruoyi-plus-soybean/src/typings/elegant-router.d.ts
+++ b/ruoyi-plus-soybean/src/typings/elegant-router.d.ts
@@ -47,6 +47,7 @@
     "monitor_operlog": "/monitor/operlog";
     "qm": "/qm";
     "qm_batch": "/qm/batch";
+    "qm_checkitem": "/qm/checkitem";
     "qm_std": "/qm/std";
     "social-callback": "/social-callback";
     "system": "/system";
@@ -157,6 +158,7 @@
     | "monitor_online"
     | "monitor_operlog"
     | "qm_batch"
+    | "qm_checkitem"
     | "qm_std"
     | "system_client"
     | "system_config"
diff --git a/ruoyi-plus-soybean/src/views/qm/checkitem/index.vue b/ruoyi-plus-soybean/src/views/qm/checkitem/index.vue
new file mode 100644
index 0000000..133ff75
--- /dev/null
+++ b/ruoyi-plus-soybean/src/views/qm/checkitem/index.vue
@@ -0,0 +1,223 @@
+<script setup lang="tsx">
+import { ref } from 'vue';
+import { NDivider } from 'naive-ui';
+import { fetchBatchDeleteCheckitem, fetchGetCheckitemList } from '@/service/api/qm/checkitem';
+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 CheckitemOperateDrawer from './modules/checkitem-operate-drawer.vue';
+import CheckitemSearch from './modules/checkitem-search.vue';
+
+defineOptions({
+  name: 'CheckitemList'
+});
+
+
+const appStore = useAppStore();
+const { download } = useDownload();
+const { hasAuth } = useAuth();
+
+const searchParams = ref<Api.Qm.CheckitemSearchParams>({
+  pageNum: 1,
+  pageSize: 10,
+  itemCode: null,
+  itemName: null,
+  params: {}
+});
+
+const { columns, columnChecks, data, getData, getDataByPage, loading, mobilePagination, scrollX } =
+  useNaivePaginatedTable({
+  api: () => fetchGetCheckitemList(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: 'itemCode',
+      title: '椤圭洰浠g爜',
+      align: 'center',
+      minWidth: 120
+    },
+    {
+      key: 'itemName',
+      title: '椤圭洰鍚嶇О',
+      align: 'center',
+      minWidth: 120
+    },
+    {
+      key: 'location',
+      title: '浣嶇疆',
+      align: 'center',
+      minWidth: 120
+    },
+    {
+      key: 'instrumentDes',
+      title: '浠櫒鎻忚堪',
+      align: 'center',
+      minWidth: 120
+    },
+    {
+      key: 'itemDes',
+      title: '椤圭洰鎻忚堪',
+      align: 'center',
+      width: 200
+    },
+    {
+      key: 'ismix',
+      title: '鏄惁鍚堟垚椤�',
+      align: 'center',
+      minWidth: 120
+    },
+    {
+      key: 'score',
+      title: '鍒嗗��',
+      align: 'center',
+      minWidth: 120
+    },
+    {
+      key: 'enable',
+      title: '鍚敤',
+      align: 'center',
+      minWidth: 120
+    },
+    {
+      key: 'operate',
+      title: $t('common.operate'),
+      align: 'center',
+      width: 130,
+      render: row => {
+        const divider = () => {
+          if (!hasAuth('qm:checkitem:edit') || !hasAuth('qm:checkitem:remove')) {
+            return null;
+          }
+          return <NDivider vertical />;
+        };
+
+        const editBtn = () => {
+          if (!hasAuth('qm:checkitem: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:checkitem: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 fetchBatchDeleteCheckitem(checkedRowKeys.value);
+  if (error) return;
+  onBatchDeleted();
+}
+
+async function handleDelete(id: CommonType.IdType) {
+  // request
+  const { error } = await fetchBatchDeleteCheckitem([id]);
+  if (error) return;
+  onDeleted();
+}
+
+function edit(id: CommonType.IdType) {
+  handleEdit(id);
+}
+
+function handleExport() {
+  download('/qm/checkitem/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">
+    <CheckitemSearch 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:checkitem:add')"
+          :show-delete="hasAuth('qm:checkitem:remove')"
+          :show-export="hasAuth('qm:checkitem: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"
+      />
+      <CheckitemOperateDrawer
+        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/checkitem/modules/checkitem-operate-drawer.vue b/ruoyi-plus-soybean/src/views/qm/checkitem/modules/checkitem-operate-drawer.vue
new file mode 100644
index 0000000..54c4ccc
--- /dev/null
+++ b/ruoyi-plus-soybean/src/views/qm/checkitem/modules/checkitem-operate-drawer.vue
@@ -0,0 +1,178 @@
+<script setup lang="ts">
+import { computed, ref, watch } from 'vue';
+import { jsonClone } from '@sa/utils';
+import { fetchCreateCheckitem, fetchUpdateCheckitem } from '@/service/api/qm/checkitem';
+import { useFormRules, useNaiveForm } from '@/hooks/common/form';
+import { $t } from '@/locales';
+
+defineOptions({
+  name: 'CheckitemOperateDrawer'
+});
+
+interface Props {
+  /** the type of operation */
+  operateType: NaiveUI.TableOperateType;
+  /** the edit row data */
+  rowData?: Api.Qm.Checkitem | 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.CheckitemOperateParams;
+
+const model = ref<Model>(createDefaultModel());
+
+function createDefaultModel(): Model {
+  return {
+      id: '',
+      itemCode: '',
+      itemName: '',
+      unit: '',
+      enable: null,
+      del: null,
+      itemDes: '',
+      stdCode: '',
+      instrumentDes: '',
+      location: '',
+      checkLevel: '',
+      ismix: null,
+      rid: '',
+      category: null,
+      instrumentCode: '',
+      score: null
+  };
+}
+
+type RuleKey = Extract<
+  keyof Model,
+  | 'id'
+>;
+
+const rules: Record<RuleKey, App.Global.FormRule> = {
+  id: createRequiredRule('缂栫爜涓嶈兘涓虹┖'),
+};
+
+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, itemCode, itemName, unit, enable, del, itemDes, stdCode, instrumentDes, location, checkLevel, ismix, rid, category, instrumentCode, score } = model.value;
+
+  // request
+  if (props.operateType === 'add') {
+    const { error } = await fetchCreateCheckitem({ itemCode, itemName, unit, enable, del, itemDes, stdCode, instrumentDes, location, checkLevel, ismix, rid, category, instrumentCode, score });
+    if (error) return;
+  }
+
+  if (props.operateType === 'edit') {
+    const { error } = await fetchUpdateCheckitem({ id, itemCode, itemName, unit, enable, del, itemDes, stdCode, instrumentDes, location, checkLevel, ismix, rid, category, instrumentCode, score });
+    if (error) return;
+  }
+
+  window.$message?.success($t('common.updateSuccess'));
+  closeDrawer();
+  emit('submitted');
+}
+
+watch(visible, () => {
+  if (visible.value) {
+    handleUpdateModelWhenEdit();
+    restoreValidation();
+    getTreeList();
+  }
+});
+</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="itemCode">
+          <NInput v-model:value="model.itemCode" placeholder="璇疯緭鍏ユ楠岄」鐩唬鐮�" />
+        </NFormItem>
+        <NFormItem label="妫�楠岄」鐩悕绉�" path="itemName">
+          <NInput v-model:value="model.itemName" placeholder="璇疯緭鍏ユ楠岄」鐩悕绉�" />
+        </NFormItem>
+        <NFormItem label="鍗曚綅" path="unit">
+          <NInput v-model:value="model.unit" placeholder="璇疯緭鍏ュ崟浣�" />
+        </NFormItem>
+        <NFormItem label="鍚敤" path="enable">
+          <NInput v-model:value="model.enable" placeholder="璇疯緭鍏ュ惎鐢�" />
+        </NFormItem>
+        <NFormItem label="鍒犻櫎" path="del">
+          <NInput v-model:value="model.del" placeholder="璇疯緭鍏ュ垹闄�" />
+        </NFormItem>
+        <NFormItem label="妫�楠岄」鎻忚堪" path="itemDes">
+          <NInput v-model:value="model.itemDes" placeholder="璇疯緭鍏ユ楠岄」鎻忚堪" />
+        </NFormItem>
+        <NFormItem label="瑙勭▼浠g爜" path="stdCode">
+          <NInput v-model:value="model.stdCode" placeholder="璇疯緭鍏ヨ绋嬩唬鐮�" />
+        </NFormItem>
+        <NFormItem label="浠櫒鎻忚堪" path="instrumentDes">
+          <NInput v-model:value="model.instrumentDes" placeholder="璇疯緭鍏ヤ华鍣ㄦ弿杩�" />
+        </NFormItem>
+        <NFormItem label="缂洪櫡浣嶇疆-澶栬鐢�" path="location">
+          <NInput v-model:value="model.location" placeholder="璇疯緭鍏ョ己闄蜂綅缃�-澶栬鐢�" />
+        </NFormItem>
+        <NFormItem label="鍒咥,B,C,D鍥涗釜绾у埆" path="checkLevel">
+          <NInput v-model:value="model.checkLevel" placeholder="璇疯緭鍏ュ垎A,B,C,D鍥涗釜绾у埆" />
+        </NFormItem>
+        <NFormItem label="鏄惁鍚堟垚椤�" path="ismix">
+          <NInput v-model:value="model.ismix" placeholder="璇疯緭鍏ユ槸鍚﹀悎鎴愰」" />
+        </NFormItem>
+        <NFormItem label="鍏宠仈椤笽D" path="rid">
+          <NInput v-model:value="model.rid" placeholder="璇疯緭鍏ュ叧鑱旈」ID" />
+        </NFormItem>
+        <NFormItem label="绫诲埆 0:鎴愬搧 1杈呮枡" path="category">
+          <NInput v-model:value="model.category" placeholder="璇疯緭鍏ョ被鍒� 0:鎴愬搧 1杈呮枡" />
+        </NFormItem>
+        <NFormItem label="浠櫒缂栫爜" path="instrumentCode">
+          <NInput v-model:value="model.instrumentCode" placeholder="璇疯緭鍏ヤ华鍣ㄧ紪鐮�" />
+        </NFormItem>
+        <NFormItem label="鍒嗗��" path="score">
+          <NInput v-model:value="model.score" 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/checkitem/modules/checkitem-search.vue b/ruoyi-plus-soybean/src/views/qm/checkitem/modules/checkitem-search.vue
new file mode 100644
index 0000000..d940e0c
--- /dev/null
+++ b/ruoyi-plus-soybean/src/views/qm/checkitem/modules/checkitem-search.vue
@@ -0,0 +1,74 @@
+<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: 'CheckitemSearch'
+});
+
+interface Emits {
+  (e: 'search'): void;
+}
+
+const emit = defineEmits<Emits>();
+
+const { formRef, validate, restoreValidation } = useNaiveForm();
+
+const model = defineModel<Api.Qm.CheckitemSearchParams>('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-checkitem-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="itemCode" class="pr-24px">
+              <NInput v-model:value="model.itemCode" placeholder="璇疯緭鍏ユ楠岄」鐩唬鐮�" />
+            </NFormItemGi>
+            <NFormItemGi span="24 s:12 m:6" label="妫�楠岄」鐩悕绉�" label-width="auto" path="itemName" class="pr-24px">
+              <NInput v-model:value="model.itemName" 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/std/index.vue b/ruoyi-plus-soybean/src/views/qm/std/index.vue
index f4f6b03..6eaee42 100644
--- a/ruoyi-plus-soybean/src/views/qm/std/index.vue
+++ b/ruoyi-plus-soybean/src/views/qm/std/index.vue
@@ -11,6 +11,7 @@
 import ButtonIcon from '@/components/custom/button-icon.vue';
 import StdOperateDrawer from './modules/std-operate-drawer.vue';
 import StdSearch from './modules/std-search.vue';
+import StdSubTable from './modules/std-sub-table.vue';
 
 defineOptions({
   name: 'StdList'
@@ -20,6 +21,8 @@
 const appStore = useAppStore();
 const { download } = useDownload();
 const { hasAuth } = useAuth();
+
+const selectedStdId = ref<CommonType.IdType | null>(null);
 
 const searchParams = ref<Api.Qm.StdSearchParams>({
   pageNum: 1,
@@ -76,7 +79,9 @@
       minWidth: 120,
       render: row => {
         const v = String(row?.category ?? '');
-        return v === '0' ? '鎴愬搧' : v === '1' ? '杈呮枡' : v;
+        if (v === '0') return '鎴愬搧';
+        if (v === '1') return '杈呮枡';
+        return v;
       }
     },
     {
@@ -99,7 +104,9 @@
       minWidth: 120,
       render: row => {
         const v = String(row?.enable ?? '');
-        return v === '0' ? '鍋滅敤' : v === '1' ? '鍚敤' : v;
+        if (v === '0') return '鍋滅敤';
+        if (v === '1') return '鍚敤';
+        return v;
       }
     },
     {
@@ -183,47 +190,75 @@
 function handleExport() {
   download('/qm/std/export', searchParams.value, `鍒ゅ畾瑙勭▼_${new Date().getTime()}.xlsx`);
 }
+
+function handleRowClick(row: any) {
+  return {
+    onClick: (e: MouseEvent) => {
+      const target = e.target as HTMLElement | null;
+      if (target?.closest('.n-checkbox') || target?.closest('.n-button') || target?.closest('a')) return;
+      selectedStdId.value = row.id;
+    },
+    style: 'cursor: pointer;'
+  };
+}
 </script>
 
 <template>
   <div class="min-h-500px flex-col-stretch gap-16px overflow-hidden lt-sm:overflow-auto">
     <StdSearch 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:std:add')"
-          :show-delete="hasAuth('qm:std:remove')"
-          :show-export="hasAuth('qm:std:export')"
-          @add="handleAdd"
-          @delete="handleBatchDelete"
-          @export="handleExport"
-          @refresh="getData"
-        />
-      </template>
-      <NDataTable
-        v-model:checked-row-keys="checkedRowKeys"
-        :columns="columns"
-        :data="data"
+    <div class="flex-col-stretch gap-16px sm:flex-1-hidden">
+      <NCard
+        title="鍒ゅ畾瑙勭▼鍒楄〃"
+        :bordered="false"
         size="small"
-        :flex-height="!appStore.isMobile"
-        :scroll-x="scrollX"
-        :loading="loading"
-        remote
-        :row-key="row => row.id"
-        :pagination="mobilePagination"
-        class="sm:h-full"
-      />
-      <StdOperateDrawer
-        v-model:visible="drawerVisible"
-        :operate-type="operateType"
-        :row-data="editingData"
-        @submitted="getDataByPage"
-      />
-    </NCard>
+        class="card-wrapper flex-col-stretch sm:flex-1-hidden"
+        :content-style="{ flex: 1, overflow: 'hidden', display: 'flex', flexDirection: 'column' }"
+      >
+        <template #header-extra>
+          <TableHeaderOperation
+            v-model:columns="columnChecks"
+            :disabled-delete="checkedRowKeys.length === 0"
+            :loading="loading"
+            :show-add="hasAuth('qm:std:add')"
+            :show-delete="hasAuth('qm:std:remove')"
+            :show-export="hasAuth('qm:std: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"
+          :row-props="handleRowClick"
+          class="flex-1-hidden"
+        />
+        <StdOperateDrawer
+          v-model:visible="drawerVisible"
+          :operate-type="operateType"
+          :row-data="editingData"
+          @submitted="getDataByPage"
+        />
+      </NCard>
+      <StdSubTable :std-id="selectedStdId" class="sm:flex-1-hidden" />
+    </div>
   </div>
 </template>
 
-<style scoped></style>
+<style scoped>
+:deep(.n-data-table-th),
+:deep(.n-data-table-td) {
+  padding: 4px 6px;
+  overflow: hidden;
+}
+
+</style>
diff --git a/ruoyi-plus-soybean/src/views/qm/std/modules/std-sub-table.vue b/ruoyi-plus-soybean/src/views/qm/std/modules/std-sub-table.vue
new file mode 100644
index 0000000..301a5c2
--- /dev/null
+++ b/ruoyi-plus-soybean/src/views/qm/std/modules/std-sub-table.vue
@@ -0,0 +1,151 @@
+<script setup lang="tsx">
+import { computed, ref, watch } from 'vue';
+import { useLoading } from '@sa/hooks';
+import { $t } from '@/locales';
+import { fetchGetCheckitemTree } from '@/service/api/qm/checkitem';
+import type { DataTableColumns, DataTableRowKey } from 'naive-ui';
+
+defineOptions({
+  name: 'StdSubTable'
+});
+
+interface Props {
+  stdId?: CommonType.IdType | null;
+}
+
+const props = withDefaults(defineProps<Props>(), {
+  stdId: null
+});
+
+interface StdSubRow extends Api.Qm.Checkitem {
+  children?: StdSubRow[];
+}
+
+const { loading, startLoading, endLoading } = useLoading();
+const rows = ref<StdSubRow[]>([]);
+
+async function getSubList() {
+  if (!props.stdId) {
+    rows.value = [];
+    return;
+  }
+
+  startLoading();
+  try {
+    const { data, error } = await fetchGetCheckitemTree({
+      pageNum: 1,
+      pageSize: 9999,
+      stdCode: String(props.stdId)
+    });
+    if (error) {
+      rows.value = [];
+      return;
+    }
+
+    rows.value = data.rows;
+  } finally {
+    endLoading();
+  }
+}
+
+watch(
+  () => props.stdId,
+  async () => {
+    await getSubList();
+  },
+  { immediate: true }
+);
+
+const columns = computed<DataTableColumns<StdSubRow>>(() => [
+  {
+    key: 'index',
+    title: $t('common.index'),
+    align: 'center',
+    width: 56,
+    render: (_: any, index: number) => index + 1
+  },
+  {
+    key: 'itemName',
+    title: '椤圭洰鍚嶇О',
+    align: 'left',
+    width: 400,
+    tree: true
+  } as any,
+  {
+    key: 'itemCode',
+    title: '椤圭洰浠g爜',
+    align: 'center',
+    width: 180
+  },
+  {
+    key: 'unit',
+    title: '鍗曚綅',
+    align: 'center',
+    width: 80
+  },
+  {
+    key: 'enable',
+    title: '鍚敤',
+    align: 'center',
+    width: 72,
+    render: row => (String(row.enable) === '1' ? '鍚敤' : '鍋滅敤')
+  },
+  {
+    key: 'score',
+    title: '鍒嗗��',
+    align: 'center',
+    width: 80
+  },
+  {
+    key: 'itemDes',
+    title: '鎻忚堪',
+    align: 'center',
+    minWidth: 160
+  }
+]);
+
+function rowKey(row: StdSubRow): DataTableRowKey {
+  return String(row.id);
+}
+</script>
+
+<template>
+  <NCard
+    title="瑙勭▼鏄庣粏"
+    :bordered="false"
+    size="small"
+    class="flex-col-stretch card-wrapper"
+    :content-style="{ flex: 1, overflow: 'hidden', display: 'flex', flexDirection: 'column' }"
+  >
+    <template #header-extra>
+      <span class="text-13px text-gray-500">{{ props.stdId ? `STD CODE锛�${props.stdId}` : '' }}</span>
+    </template>
+
+    <NSpin :show="loading" class="h-full" content-class="h-full">
+      <div v-if="!props.stdId" class="h-full flex-center text-gray-400">璇风偣鍑讳笂鏂硅〃鏍艰鏌ョ湅鏄庣粏</div>
+      <NDataTable
+        v-else
+        :columns="columns as any"
+        :data="rows"
+        size="small"
+        flex-height
+        children-key="children"
+        :row-key="rowKey"
+        default-expand-all
+        striped
+        style="height: 200px"
+      />
+    </NSpin>
+  </NCard>
+</template>
+
+<style scoped>
+:deep(.n-card__content) {
+  padding: 8px 12px;
+}
+
+:deep(.n-data-table-th),
+:deep(.n-data-table-td) {
+  padding: 4px 6px;
+}
+</style>

--
Gitblit v1.9.3