From 92ddfac6cea39eb0b0a0a81b11294998787f4b9e Mon Sep 17 00:00:00 2001
From: zhuguifei <312353457@qq.com>
Date: 星期五, 17 四月 2026 15:23:46 +0800
Subject: [PATCH] feat: 1.新增物料、物料类型、材料检验统计等表维护功能  2.完善判定依据-判断依据明细  3.新增判断依据、物料管理等字典翻译类  4.成品物料批次-原始数据维护页面

---
 RuoYi-Vue-Plus/ruoyi-modules/ruoyi-qa/src/main/java/org/dromara/qa/qm/domain/QmMatcheck.java                                            |  184 ++
 RuoYi-Vue-Plus/ruoyi-modules/ruoyi-qa/src/main/java/org/dromara/sc/md/domain/vo/MdMatVo.java                                            |  118 +
 RuoYi-Vue-Plus/ruoyi-modules/ruoyi-qa/src/main/java/org/dromara/sc/md/mapper/MdMatTypeMapper.java                                       |   15 
 RuoYi-Vue-Plus/ruoyi-modules/ruoyi-qa/src/main/java/org/dromara/sc/md/domain/vo/MdMatTypeVo.java                                        |  106 +
 ruoyi-plus-soybean/src/service/api/md/mat.ts                                                                                            |   35 
 ruoyi-plus-soybean/src/typings/api/md.mat-type.api.d.ts                                                                                 |   76 
 RuoYi-Vue-Plus/ruoyi-modules/ruoyi-qa/src/main/java/org/dromara/qa/qm/mapper/QmMatcheckMapper.java                                      |   38 
 ruoyi-plus-soybean/src/views/md/mat-type/index.vue                                                                                      |  241 ++
 ruoyi-plus-soybean/src/views/md/mat/modules/mat-operate-drawer.vue                                                                      |  184 ++
 RuoYi-Vue-Plus/ruoyi-modules/ruoyi-qa/src/main/java/org/dromara/qa/qm/domain/vo/QmMatcheckVo.java                                       |  255 +++
 ruoyi-plus-soybean/src/views/md/mat/index.vue                                                                                           |  283 +++
 ruoyi-plus-soybean/src/typings/api/md.mat.api.d.ts                                                                                      |   83 +
 RuoYi-Vue-Plus/ruoyi-modules/ruoyi-qa/src/main/java/org/dromara/sc/md/domain/bo/MdMatTypeBo.java                                        |   87 +
 RuoYi-Vue-Plus/ruoyi-modules/ruoyi-qa/src/main/java/org/dromara/sc/md/service/impl/MdMatServiceImpl.java                                |  147 +
 RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-translation/src/main/java/org/dromara/common/translation/core/impl/MatNameTranslationImpl.java |   28 
 RuoYi-Vue-Plus/ruoyi-modules/ruoyi-qa/src/main/java/org/dromara/sc/md/controller/MdMatController.java                                   |  105 +
 RuoYi-Vue-Plus/ruoyi-modules/ruoyi-qa/src/main/resources/mapper/qa/qm/QmMatcheckMapper.xml                                              |   67 
 RuoYi-Vue-Plus/ruoyi-modules/ruoyi-qa/src/main/resources/mapper/sc/md/MdMatMapper.xml                                                   |    6 
 ruoyi-plus-soybean/src/views/md/mat-type/modules/mat-type-operate-drawer.vue                                                            |  171 ++
 RuoYi-Vue-Plus/ruoyi-modules/ruoyi-qa/src/main/java/org/dromara/qa/qm/service/IQmMatcheckService.java                                   |   85 +
 RuoYi-Vue-Plus/ruoyi-modules/ruoyi-qa/src/main/resources/mapper/sc/md/MdMatTypeMapper.xml                                               |    6 
 RuoYi-Vue-Plus/ruoyi-modules/ruoyi-qa/src/main/java/org/dromara/qa/qm/service/impl/QmMatcheckServiceImpl.java                           |  154 +
 RuoYi-Vue-Plus/ruoyi-modules/ruoyi-qa/src/main/java/org/dromara/sc/md/service/impl/MdMatTypeServiceImpl.java                            |  138 +
 ruoyi-plus-soybean/src/typings/api/qm.matcheck.api.d.ts                                                                                 |  165 +
 RuoYi-Vue-Plus/ruoyi-modules/ruoyi-qa/src/main/java/org/dromara/sc/md/controller/MdMatTypeController.java                               |  105 +
 RuoYi-Vue-Plus/ruoyi-modules/ruoyi-qa/src/main/java/org/dromara/sc/md/domain/MdMatType.java                                             |   87 +
 RuoYi-Vue-Plus/ruoyi-modules/ruoyi-qa/src/main/java/org/dromara/sc/md/domain/bo/MdMatBo.java                                            |   97 +
 RuoYi-Vue-Plus/ruoyi-modules/ruoyi-qa/src/main/java/org/dromara/qa/qm/domain/bo/QmMatcheckBo.java                                       |  183 ++
 RuoYi-Vue-Plus/ruoyi-modules/ruoyi-qa/src/main/java/org/dromara/sc/md/mapper/MdMatMapper.java                                           |   15 
 ruoyi-plus-soybean/src/views/md/mat/modules/mat-search.vue                                                                              |  119 +
 ruoyi-plus-soybean/src/views/qm/judge-details/modules/judge-details-sub-table.vue                                                       |  414 ++++
 ruoyi-plus-soybean/src/views/qm/matcheck/index.vue                                                                                      |  268 +++
 ruoyi-plus-soybean/src/service/api/qm/matcheck.ts                                                                                       |   57 
 ruoyi-plus-soybean/src/service/api/md/mat-type.ts                                                                                       |   35 
 ruoyi-plus-soybean/src/views/qm/matcheck/modules/matcheck-search.vue                                                                    |   93 +
 RuoYi-Vue-Plus/ruoyi-modules/ruoyi-qa/src/main/resources/mapper/sc/test                                                                 |    0 
 RuoYi-Vue-Plus/ruoyi-modules/ruoyi-qa/src/main/java/org/dromara/sc/md/service/IMdMatService.java                                        |   68 
 RuoYi-Vue-Plus/ruoyi-modules/ruoyi-qa/src/main/java/org/dromara/sc/md/domain/MdMat.java                                                 |   97 +
 RuoYi-Vue-Plus/ruoyi-modules/ruoyi-qa/src/main/java/org/dromara/qa/qm/controller/QmMatcheckController.java                              |  122 +
 RuoYi-Vue-Plus/ruoyi-modules/ruoyi-qa/src/main/java/org/dromara/sc/md/service/IMdMatTypeService.java                                    |   68 
 ruoyi-plus-soybean/src/views/md/mat-type/modules/mat-type-search.vue                                                                    |   99 +
 ruoyi-plus-soybean/src/views/qm/matcheck/modules/matcheck-operate-drawer.vue                                                            |  266 +++
 RuoYi-Vue-Plus/ruoyi-modules/ruoyi-qa/src/main/java/org/dromara/sc/test                                                                 |    0 
 43 files changed, 4,970 insertions(+), 0 deletions(-)

diff --git a/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-translation/src/main/java/org/dromara/common/translation/core/impl/MatNameTranslationImpl.java b/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-translation/src/main/java/org/dromara/common/translation/core/impl/MatNameTranslationImpl.java
new file mode 100755
index 0000000..39d48cf
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-translation/src/main/java/org/dromara/common/translation/core/impl/MatNameTranslationImpl.java
@@ -0,0 +1,28 @@
+package org.dromara.common.translation.core.impl;
+
+import cn.hutool.core.convert.Convert;
+import lombok.AllArgsConstructor;
+import org.dromara.common.core.service.MatService;
+import org.dromara.common.core.service.UserService;
+import org.dromara.common.core.utils.StringUtils;
+import org.dromara.common.translation.annotation.TranslationType;
+import org.dromara.common.translation.constant.TransConstant;
+import org.dromara.common.translation.core.TranslationInterface;
+
+/**
+ * matName缈昏瘧瀹炵幇
+ *
+ * @author zhuguifei
+ */
+@AllArgsConstructor
+@TranslationType(type = TransConstant.MAT_CODE_TO_NAME)
+public class MatNameTranslationImpl implements TranslationInterface<String> {
+
+    private final MatService matService;
+
+    @Override
+    public String translation(Object key, String other) {
+        if(key == null) return  "";
+        return matService.selectMatNameByCode(key.toString());
+    }
+}
diff --git a/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-qa/src/main/java/org/dromara/qa/qm/controller/QmMatcheckController.java b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-qa/src/main/java/org/dromara/qa/qm/controller/QmMatcheckController.java
new file mode 100644
index 0000000..5bf78c1
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-qa/src/main/java/org/dromara/qa/qm/controller/QmMatcheckController.java
@@ -0,0 +1,122 @@
+package org.dromara.qa.qm.controller;
+
+import java.util.List;
+import java.util.Map;
+
+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.QmMatcheckVo;
+import org.dromara.qa.qm.domain.bo.QmMatcheckBo;
+import org.dromara.qa.qm.service.IQmMatcheckService;
+import org.dromara.common.mybatis.core.page.TableDataInfo;
+
+/**
+ * 鏉愭枡妫�楠岀粺璁�
+ *
+ * @author zhuguifei
+ * @date 2026-04-15
+ */
+@Validated
+@RequiredArgsConstructor
+@RestController
+@RequestMapping("/qm/matcheck")
+public class QmMatcheckController extends BaseController {
+
+    private final IQmMatcheckService qmMatcheckService;
+
+    /**
+     * 鏌ヨ鏉愭枡妫�楠岀粺璁″垪琛�
+     */
+    @SaCheckPermission("qm:matcheck:list")
+    @GetMapping("/list")
+    public TableDataInfo<QmMatcheckVo> list(QmMatcheckBo bo, PageQuery pageQuery) {
+        return qmMatcheckService.queryPageList(bo, pageQuery);
+    }
+
+
+    @SaCheckPermission("qm:matcheck:list")
+    @GetMapping("/listCheckItem")
+    public R<List<Map<String, Object>>> listCheckItem(@RequestParam String judgeId) {
+        return R.ok(qmMatcheckService.getQmCheckItem(judgeId));
+    }
+
+    /**
+     * 鎸夋壒娆″拰鐗屽彿鏌ヨ鏉愭枡妫�楠岀粺璁�(澶氳〃)
+     */
+    @SaCheckPermission("qm:matcheck:list")
+    @GetMapping("/listQmMatcheck")
+    public TableDataInfo<QmMatcheckVo> listQmMatcheck(QmMatcheckBo bo, PageQuery pageQuery) {
+        return TableDataInfo.build(qmMatcheckService.listQmMatcheck(bo), pageQuery.build());
+    }
+
+    /**
+     * 瀵煎嚭鏉愭枡妫�楠岀粺璁″垪琛�
+     */
+    @SaCheckPermission("qm:matcheck:export")
+    @Log(title = "鏉愭枡妫�楠岀粺璁�", businessType = BusinessType.EXPORT)
+    @PostMapping("/export")
+    public void export(QmMatcheckBo bo, HttpServletResponse response) {
+        List<QmMatcheckVo> list = qmMatcheckService.queryList(bo);
+        ExcelUtil.exportExcel(list, "鏉愭枡妫�楠岀粺璁�", QmMatcheckVo.class, response);
+    }
+
+    /**
+     * 鑾峰彇鏉愭枡妫�楠岀粺璁¤缁嗕俊鎭�
+     *
+     * @param id 涓婚敭
+     */
+    @SaCheckPermission("qm:matcheck:query")
+    @GetMapping("/{id}")
+    public R<QmMatcheckVo> getInfo(@NotNull(message = "涓婚敭涓嶈兘涓虹┖")
+                                     @PathVariable String id) {
+        return R.ok(qmMatcheckService.queryById(id));
+    }
+
+    /**
+     * 鏂板鏉愭枡妫�楠岀粺璁�
+     */
+    @SaCheckPermission("qm:matcheck:add")
+    @Log(title = "鏉愭枡妫�楠岀粺璁�", businessType = BusinessType.INSERT)
+    @RepeatSubmit()
+    @PostMapping()
+    public R<Void> add(@Validated(AddGroup.class) @RequestBody QmMatcheckBo bo) {
+        return toAjax(qmMatcheckService.insertByBo(bo));
+    }
+
+    /**
+     * 淇敼鏉愭枡妫�楠岀粺璁�
+     */
+    @SaCheckPermission("qm:matcheck:edit")
+    @Log(title = "鏉愭枡妫�楠岀粺璁�", businessType = BusinessType.UPDATE)
+    @RepeatSubmit()
+    @PutMapping()
+    public R<Void> edit(@Validated(EditGroup.class) @RequestBody QmMatcheckBo bo) {
+        return toAjax(qmMatcheckService.updateByBo(bo));
+    }
+
+    /**
+     * 鍒犻櫎鏉愭枡妫�楠岀粺璁�
+     *
+     * @param ids 涓婚敭涓�
+     */
+    @SaCheckPermission("qm:matcheck:remove")
+    @Log(title = "鏉愭枡妫�楠岀粺璁�", businessType = BusinessType.DELETE)
+    @DeleteMapping("/{ids}")
+    public R<Void> remove(@NotEmpty(message = "涓婚敭涓嶈兘涓虹┖")
+                          @PathVariable String[] ids) {
+        return toAjax(qmMatcheckService.deleteWithValidByIds(List.of(ids), true));
+    }
+}
diff --git a/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-qa/src/main/java/org/dromara/qa/qm/domain/QmMatcheck.java b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-qa/src/main/java/org/dromara/qa/qm/domain/QmMatcheck.java
new file mode 100644
index 0000000..e3f73e4
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-qa/src/main/java/org/dromara/qa/qm/domain/QmMatcheck.java
@@ -0,0 +1,184 @@
+package org.dromara.qa.qm.domain;
+
+import com.baomidou.mybatisplus.annotation.*;
+import lombok.Data;
+
+import java.io.Serial;
+import java.util.Date;
+
+/**
+ * 鏉愭枡妫�楠岀粺璁″璞� qm_matcheck
+ *
+ * @author zhuguifei
+ * @date 2026-04-15
+ */
+@Data
+@TableName("qm_matcheck")
+public class QmMatcheck  {
+
+    @Serial
+    private static final long serialVersionUID = 1L;
+
+    /**
+     * id
+     */
+    @TableId(value = "id")
+    private String id;
+
+    /**
+     * 姹囨�昏〃ID
+     */
+    private String pid;
+
+    /**
+     * 妫�楠屾壒娆�
+     */
+    private String batchCode;
+
+    /**
+     * 鐗屽彿
+     */
+    private String matCode;
+
+    /**
+     * 浠櫒缂栧彿
+     */
+    private String instrumentCode;
+
+    /**
+     * 鎶�鏈姹�
+     */
+    private String techReq;
+
+    /**
+     * 妫�楠屼緷鎹�
+     */
+    private String checkStd;
+
+    /**
+     * 娴嬭瘯鐜
+     */
+    private String testEnv;
+
+    /**
+     * 妫�楠岄」鐩瓹OD
+     */
+    private String itemCode;
+
+    /**
+     * 鐩樺彿
+     */
+    private String subBatchCode;
+
+    /**
+     * 姣忕洏妫�楠屾暟閲�
+     */
+    private Long sampleNumber;
+
+    /**
+     * 鍙栨牱绫诲瀷
+     */
+    private Long sampleType;
+
+    /**
+     * 妫�楠屽憳
+     */
+    private String checkName;
+
+    /**
+     * 妫�楠屾椂闂�
+     */
+    private Date checkTime;
+
+    /**
+     * 澶嶆牳鍛�
+     */
+    private String reviewName;
+
+    /**
+     * 澶嶆牳鏃堕棿
+     */
+    private Date reviewTime;
+
+    /**
+     * 鏈�澶у��
+     */
+    private Double maxval;
+
+    /**
+     * 鏈�灏忓��
+     */
+    private Double minval;
+
+    /**
+     * 骞冲潎鍊�
+     */
+    private Double avgval;
+
+    /**
+     * SD鍊�
+     */
+    private Double sdval;
+
+    /**
+     * CV鍊�
+     */
+    private Double cvval;
+
+    /**
+     * CPK鍊�
+     */
+    private Double cpkval;
+
+    /**
+     * 瓒呮爣鏁�
+     */
+    private Double badval;
+
+    /**
+     * 鍒ゅ畾
+     */
+    private String judge;
+
+    /**
+     * 鍗曢」鍒ゅ畾
+     */
+    private String singlejudge;
+
+    /**
+     * 鐗堟湰鍚嶇О
+     */
+    private String verName;
+
+    /**
+     * 鐗堟湰缂栧彿
+     */
+    private String verCode;
+
+    /**
+     * 淇濆瓨鏈�
+     */
+    private String archDate;
+
+    /**
+     * 鍒犻櫎鏍囧織
+     */
+    private Long del;
+
+    /**
+     * 0-鏈笂浼爉es   1-宸蹭笂浼�
+     */
+    private String flag;
+
+    /**
+     * 涓婁紶mes鏃堕棿
+     */
+    private Date toMesTime;
+
+    /**
+     * 澶囨敞
+     */
+    private String chkDes;
+
+
+}
diff --git a/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-qa/src/main/java/org/dromara/qa/qm/domain/bo/QmMatcheckBo.java b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-qa/src/main/java/org/dromara/qa/qm/domain/bo/QmMatcheckBo.java
new file mode 100644
index 0000000..98378e5
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-qa/src/main/java/org/dromara/qa/qm/domain/bo/QmMatcheckBo.java
@@ -0,0 +1,183 @@
+package org.dromara.qa.qm.domain.bo;
+
+import org.dromara.qa.qm.domain.QmMatcheck;
+import org.dromara.common.mybatis.core.domain.BaseEntity;
+import io.github.linpeilie.annotations.AutoMapper;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+
+import java.util.Date;
+
+/**
+ * 鏉愭枡妫�楠岀粺璁′笟鍔″璞� qm_matcheck
+ *
+ * @author zhuguifei
+ * @date 2026-04-15
+ */
+@Data
+@EqualsAndHashCode(callSuper = true)
+@AutoMapper(target = QmMatcheck.class, reverseConvertGenerate = false)
+public class QmMatcheckBo extends BaseEntity {
+
+    /**
+     * id
+     */
+    private String id;
+
+    /**
+     * 姹囨�昏〃ID
+     */
+    private String pid;
+
+    /**
+     * 妫�楠屾壒娆�
+     */
+    private String batchCode;
+
+    /**
+     * 鐗屽彿
+     */
+    private String matCode;
+
+    /**
+     * 浠櫒缂栧彿
+     */
+    private String instrumentCode;
+
+    /**
+     * 鎶�鏈姹�
+     */
+    private String techReq;
+
+    /**
+     * 妫�楠屼緷鎹�
+     */
+    private String checkStd;
+
+    /**
+     * 娴嬭瘯鐜
+     */
+    private String testEnv;
+
+    /**
+     * 妫�楠岄」鐩瓹OD
+     */
+    private String itemCode;
+
+    /**
+     * 鐩樺彿
+     */
+    private String subBatchCode;
+
+    /**
+     * 姣忕洏妫�楠屾暟閲�
+     */
+    private Long sampleNumber;
+
+    /**
+     * 鍙栨牱绫诲瀷
+     */
+    private Long sampleType;
+
+    /**
+     * 妫�楠屽憳
+     */
+    private String checkName;
+
+    /**
+     * 妫�楠屾椂闂�
+     */
+    private Date checkTime;
+
+    /**
+     * 澶嶆牳鍛�
+     */
+    private String reviewName;
+
+    /**
+     * 澶嶆牳鏃堕棿
+     */
+    private Date reviewTime;
+
+    /**
+     * 鏈�澶у��
+     */
+    private Double maxval;
+
+    /**
+     * 鏈�灏忓��
+     */
+    private Double minval;
+
+    /**
+     * 骞冲潎鍊�
+     */
+    private Double avgval;
+
+    /**
+     * SD鍊�
+     */
+    private Double sdval;
+
+    /**
+     * CV鍊�
+     */
+    private Double cvval;
+
+    /**
+     * CPK鍊�
+     */
+    private Double cpkval;
+
+    /**
+     * 瓒呮爣鏁�
+     */
+    private Double badval;
+
+    /**
+     * 鍒ゅ畾
+     */
+    private String judge;
+
+    /**
+     * 鍗曢」鍒ゅ畾
+     */
+    private String singlejudge;
+
+    /**
+     * 鐗堟湰鍚嶇О
+     */
+    private String verName;
+
+    /**
+     * 鐗堟湰缂栧彿
+     */
+    private String verCode;
+
+    /**
+     * 淇濆瓨鏈�
+     */
+    private String archDate;
+
+    /**
+     * 鍒犻櫎鏍囧織
+     */
+    private Long del;
+
+    /**
+     * 0-鏈笂浼爉es   1-宸蹭笂浼�
+     */
+    private String flag;
+
+    /**
+     * 涓婁紶mes鏃堕棿
+     */
+    private Date toMesTime;
+
+    /**
+     * 澶囨敞
+     */
+    private String chkDes;
+
+
+}
diff --git a/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-qa/src/main/java/org/dromara/qa/qm/domain/vo/QmMatcheckVo.java b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-qa/src/main/java/org/dromara/qa/qm/domain/vo/QmMatcheckVo.java
new file mode 100644
index 0000000..30a2376
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-qa/src/main/java/org/dromara/qa/qm/domain/vo/QmMatcheckVo.java
@@ -0,0 +1,255 @@
+package org.dromara.qa.qm.domain.vo;
+
+import com.fasterxml.jackson.annotation.JsonFormat;
+import org.dromara.qa.qm.domain.QmMatcheck;
+import cn.idev.excel.annotation.ExcelIgnoreUnannotated;
+import cn.idev.excel.annotation.ExcelProperty;
+import io.github.linpeilie.annotations.AutoMapper;
+import lombok.Data;
+
+import java.io.Serial;
+import java.io.Serializable;
+import java.util.Date;
+
+
+
+/**
+ * 鏉愭枡妫�楠岀粺璁¤鍥惧璞� qm_matcheck
+ *
+ * @author zhuguifei
+ * @date 2026-04-15
+ */
+@Data
+@ExcelIgnoreUnannotated
+@AutoMapper(target = QmMatcheck.class)
+public class QmMatcheckVo implements Serializable {
+
+    @Serial
+    private static final long serialVersionUID = 1L;
+
+    /**
+     * id
+     */
+    @ExcelProperty(value = "id")
+    private String id;
+
+    /**
+     * 姹囨�昏〃ID
+     */
+    @ExcelProperty(value = "姹囨�昏〃ID")
+    private String pid;
+
+    /**
+     * 妫�楠屾壒娆�
+     */
+    @ExcelProperty(value = "妫�楠屾壒娆�")
+    private String batchCode;
+
+    /**
+     * 鐗屽彿
+     */
+    @ExcelProperty(value = "鐗屽彿")
+    private String matCode;
+
+    /**
+     * 浠櫒缂栧彿
+     */
+    @ExcelProperty(value = "浠櫒缂栧彿")
+    private String instrumentCode;
+
+    /**
+     * 鎶�鏈姹�
+     */
+    @ExcelProperty(value = "鎶�鏈姹�")
+    private String techReq;
+
+    /**
+     * 妫�楠屼緷鎹�
+     */
+    @ExcelProperty(value = "妫�楠屼緷鎹�")
+    private String checkStd;
+
+    /**
+     * 娴嬭瘯鐜
+     */
+    @ExcelProperty(value = "娴嬭瘯鐜")
+    private String testEnv;
+
+    /**
+     * 妫�楠岄」鐩瓹OD
+     */
+    @ExcelProperty(value = "妫�楠岄」鐩瓹OD")
+    private String itemCode;
+
+    /**
+     * 妫�楠岄」鐩悕绉�
+     */
+    private String itemName;
+
+    /**
+     * 璁惧
+     */
+    private String eqp;
+
+    /**
+     * 鐩樺彿
+     */
+    @ExcelProperty(value = "鐩樺彿")
+    private String subBatchCode;
+
+    /**
+     * 姣忕洏妫�楠屾暟閲�
+     */
+    @ExcelProperty(value = "姣忕洏妫�楠屾暟閲�")
+    private Long sampleNumber;
+
+    /**
+     * 鍙栨牱绫诲瀷
+     */
+    @ExcelProperty(value = "鍙栨牱绫诲瀷")
+    private Long sampleType;
+
+    /**
+     * 妫�楠屽憳
+     */
+    @ExcelProperty(value = "妫�楠屽憳")
+    private String checkName;
+
+    /**
+     * 妫�楠屾椂闂�
+     */
+    @ExcelProperty(value = "妫�楠屾椂闂�")
+    @JsonFormat(pattern = "yyyy-MM-dd")
+    private Date checkTime;
+
+    /**
+     * 澶嶆牳鍛�
+     */
+    @ExcelProperty(value = "澶嶆牳鍛�")
+    private String reviewName;
+
+    /**
+     * 澶嶆牳鏃堕棿
+     */
+    @ExcelProperty(value = "澶嶆牳鏃堕棿")
+    @JsonFormat(pattern = "yyyy-MM-dd")
+    private Date reviewTime;
+
+    /**
+     * 鏈�澶у��
+     */
+    @ExcelProperty(value = "鏈�澶у��")
+    private Double maxval;
+
+    /**
+     * 鏈�灏忓��
+     */
+    @ExcelProperty(value = "鏈�灏忓��")
+    private Double minval;
+
+    /**
+     * 骞冲潎鍊�
+     */
+    @ExcelProperty(value = "骞冲潎鍊�")
+    private Double avgval;
+
+    /**
+     * SD鍊�
+     */
+    @ExcelProperty(value = "SD鍊�")
+    private Double sdval;
+
+    /**
+     * CV鍊�
+     */
+    @ExcelProperty(value = "CV鍊�")
+    private Double cvval;
+
+    /**
+     * CPK鍊�
+     */
+    @ExcelProperty(value = "CPK鍊�")
+    private Double cpkval;
+
+    /**
+     * 瓒呮爣鏁�
+     */
+    @ExcelProperty(value = "瓒呮爣鏁�")
+    private Double badval;
+
+    /**
+     * 鍒ゅ畾
+     */
+    @ExcelProperty(value = "鍒ゅ畾")
+    private String judge;
+
+    /**
+     * 鍗曢」鍒ゅ畾
+     */
+    @ExcelProperty(value = "鍗曢」鍒ゅ畾")
+    private String singlejudge;
+
+    /**
+     * 鐗堟湰鍚嶇О
+     */
+    @ExcelProperty(value = "鐗堟湰鍚嶇О")
+    private String verName;
+
+    /**
+     * 鐗堟湰缂栧彿
+     */
+    @ExcelProperty(value = "鐗堟湰缂栧彿")
+    private String verCode;
+
+    /**
+     * 淇濆瓨鏈�
+     */
+    @ExcelProperty(value = "淇濆瓨鏈�")
+    private String archDate;
+
+    /**
+     * 鍒犻櫎鏍囧織
+     */
+    @ExcelProperty(value = "鍒犻櫎鏍囧織")
+    private Long del;
+
+    /**
+     * 0-鏈笂浼爉es   1-宸蹭笂浼�
+     */
+    @ExcelProperty(value = "0-鏈笂浼爉es   1-宸蹭笂浼�")
+    private String flag;
+
+    /**
+     * 涓婁紶mes鏃堕棿
+     */
+    @ExcelProperty(value = "涓婁紶mes鏃堕棿")
+    private Date toMesTime;
+
+    /**
+     * 澶囨敞
+     */
+    @ExcelProperty(value = "澶囨敞")
+    private String chkDes;
+
+    /**
+     * 妫�楠屼汉灞曠ず鍚�
+     */
+    private String checker;
+
+    /**
+     * 鍒ゅ畾瑙勭▼code(闈炴暟鎹簱瀛楁)
+     */
+    private String judgeCode;
+
+    /**
+     * 鏄惁鐢熸垚30鏀儫鏄庣粏(闈炴暟鎹簱瀛楁)
+     */
+    private String generateDetails;
+
+    /**
+     * 妫�娴嬬被鍨�(闈炴暟鎹簱瀛楁)
+     */
+    private String checkType;
+
+
+}
diff --git a/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-qa/src/main/java/org/dromara/qa/qm/mapper/QmMatcheckMapper.java b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-qa/src/main/java/org/dromara/qa/qm/mapper/QmMatcheckMapper.java
new file mode 100644
index 0000000..6c6fbaa
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-qa/src/main/java/org/dromara/qa/qm/mapper/QmMatcheckMapper.java
@@ -0,0 +1,38 @@
+package org.dromara.qa.qm.mapper;
+
+import org.apache.ibatis.annotations.Result;
+import org.apache.ibatis.annotations.Results;
+import org.dromara.qa.qm.domain.bo.QmMatcheckBo;
+import org.dromara.qa.qm.domain.QmMatcheck;
+import org.dromara.qa.qm.domain.vo.QmMatcheckVo;
+import org.dromara.common.mybatis.core.mapper.BaseMapperPlus;
+import org.apache.ibatis.annotations.Param;
+import org.apache.ibatis.annotations.Select;
+
+import java.util.List;
+import java.util.Map;
+
+/**
+ * 鏉愭枡妫�楠岀粺璁apper鎺ュ彛
+ *
+ * @author zhuguifei
+ * @date 2026-04-15
+ */
+public interface QmMatcheckMapper extends BaseMapperPlus<QmMatcheck, QmMatcheckVo> {
+    @Results(id = "qmCheckItemMap", value = {
+            @Result(property = "itemCode", column = "item_code"),
+            @Result(property = "itemName", column = "item_name")
+    })
+    @Select("SELECT b.item_code, a.item_name FROM QM_JUDGE_DETAILS a " +
+            "JOIN QM_CHECKITEM b ON a.item_cod = b.id " +
+            "WHERE a.judge_id = #{judgeId} AND a.rid IS NULL AND a.category IN (0)")
+    List<Map<String, Object>> getQmCheckItem(@Param("judgeId") String judgeId);
+
+    /**
+     * 鎸夋壒娆″拰鐗屽彿鏌ヨ鏉愭枡妫�楠岀粺璁�(澶氳〃)
+     *
+     * @param bo 鏌ヨ鏉′欢
+     * @return 鏉愭枡妫�楠岀粺璁″垪琛�
+     */
+    List<QmMatcheckVo> listQmMatcheck(@Param("bo") QmMatcheckBo bo);
+}
diff --git a/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-qa/src/main/java/org/dromara/qa/qm/service/IQmMatcheckService.java b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-qa/src/main/java/org/dromara/qa/qm/service/IQmMatcheckService.java
new file mode 100644
index 0000000..6f7f7da
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-qa/src/main/java/org/dromara/qa/qm/service/IQmMatcheckService.java
@@ -0,0 +1,85 @@
+package org.dromara.qa.qm.service;
+
+import org.dromara.qa.qm.domain.vo.QmMatcheckVo;
+import org.dromara.qa.qm.domain.bo.QmMatcheckBo;
+import org.dromara.common.mybatis.core.page.TableDataInfo;
+import org.dromara.common.mybatis.core.page.PageQuery;
+
+import java.util.Collection;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * 鏉愭枡妫�楠岀粺璁ervice鎺ュ彛
+ *
+ * @author zhuguifei
+ * @date 2026-04-15
+ */
+public interface IQmMatcheckService {
+
+    /**
+     * 鏌ヨ鏉愭枡妫�楠岀粺璁�
+     *
+     * @param id 涓婚敭
+     * @return 鏉愭枡妫�楠岀粺璁�
+     */
+    QmMatcheckVo queryById(String id);
+
+    /**
+     * 鍒嗛〉鏌ヨ鏉愭枡妫�楠岀粺璁″垪琛�
+     *
+     * @param bo        鏌ヨ鏉′欢
+     * @param pageQuery 鍒嗛〉鍙傛暟
+     * @return 鏉愭枡妫�楠岀粺璁″垎椤靛垪琛�
+     */
+    TableDataInfo<QmMatcheckVo> queryPageList(QmMatcheckBo bo, PageQuery pageQuery);
+
+    /**
+     * 鏌ヨ绗﹀悎鏉′欢鐨勬潗鏂欐楠岀粺璁″垪琛�
+     *
+     * @param bo 鏌ヨ鏉′欢
+     * @return 鏉愭枡妫�楠岀粺璁″垪琛�
+     */
+    List<QmMatcheckVo> queryList(QmMatcheckBo bo);
+
+    /**
+     * 鏂板鏉愭枡妫�楠岀粺璁�
+     *
+     * @param bo 鏉愭枡妫�楠岀粺璁�
+     * @return 鏄惁鏂板鎴愬姛
+     */
+    Boolean insertByBo(QmMatcheckBo bo);
+
+    /**
+     * 淇敼鏉愭枡妫�楠岀粺璁�
+     *
+     * @param bo 鏉愭枡妫�楠岀粺璁�
+     * @return 鏄惁淇敼鎴愬姛
+     */
+    Boolean updateByBo(QmMatcheckBo bo);
+
+    /**
+     * 鏍¢獙骞舵壒閲忓垹闄ゆ潗鏂欐楠岀粺璁′俊鎭�
+     *
+     * @param ids     寰呭垹闄ょ殑涓婚敭闆嗗悎
+     * @param isValid 鏄惁杩涜鏈夋晥鎬ф牎楠�
+     * @return 鏄惁鍒犻櫎鎴愬姛
+     */
+    Boolean deleteWithValidByIds(Collection<String> ids, Boolean isValid);
+
+    /**
+     * 鑾峰彇妫�楠岄」鐩�
+     *
+     * @param judgeId 鍒ゅ畾ID
+     * @return 妫�楠岄」鐩垪琛�
+     */
+    List<Map<String, Object>> getQmCheckItem(String judgeId);
+
+    /**
+     * 鎸夋壒娆″拰鐗屽彿鏌ヨ鏉愭枡妫�楠岀粺璁�(澶氳〃)
+     *
+     * @param bo 鏌ヨ鏉′欢
+     * @return 鏉愭枡妫�楠岀粺璁″垪琛�
+     */
+    List<QmMatcheckVo> listQmMatcheck(QmMatcheckBo bo);
+}
diff --git a/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-qa/src/main/java/org/dromara/qa/qm/service/impl/QmMatcheckServiceImpl.java b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-qa/src/main/java/org/dromara/qa/qm/service/impl/QmMatcheckServiceImpl.java
new file mode 100644
index 0000000..721519d
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-qa/src/main/java/org/dromara/qa/qm/service/impl/QmMatcheckServiceImpl.java
@@ -0,0 +1,154 @@
+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.QmMatcheckBo;
+import org.dromara.qa.qm.domain.vo.QmMatcheckVo;
+import org.dromara.qa.qm.domain.QmMatcheck;
+import org.dromara.qa.qm.mapper.QmMatcheckMapper;
+import org.dromara.qa.qm.service.IQmMatcheckService;
+
+import java.util.List;
+import java.util.Map;
+import java.util.Collection;
+
+/**
+ * 鏉愭枡妫�楠岀粺璁ervice涓氬姟灞傚鐞�
+ *
+ * @author zhuguifei
+ * @date 2026-04-15
+ */
+@Slf4j
+@RequiredArgsConstructor
+@Service
+public class QmMatcheckServiceImpl implements IQmMatcheckService {
+
+    private final QmMatcheckMapper baseMapper;
+
+    /**
+     * 鏌ヨ鏉愭枡妫�楠岀粺璁�
+     *
+     * @param id 涓婚敭
+     * @return 鏉愭枡妫�楠岀粺璁�
+     */
+    @Override
+    public QmMatcheckVo queryById(String id){
+        return baseMapper.selectVoById(id);
+    }
+
+    /**
+     * 鍒嗛〉鏌ヨ鏉愭枡妫�楠岀粺璁″垪琛�
+     *
+     * @param bo        鏌ヨ鏉′欢
+     * @param pageQuery 鍒嗛〉鍙傛暟
+     * @return 鏉愭枡妫�楠岀粺璁″垎椤靛垪琛�
+     */
+    @Override
+    public TableDataInfo<QmMatcheckVo> queryPageList(QmMatcheckBo bo, PageQuery pageQuery) {
+        LambdaQueryWrapper<QmMatcheck> lqw = buildQueryWrapper(bo);
+        Page<QmMatcheckVo> result = baseMapper.selectVoPage(pageQuery.build(), lqw);
+        return TableDataInfo.build(result);
+    }
+
+    /**
+     * 鏌ヨ绗﹀悎鏉′欢鐨勬潗鏂欐楠岀粺璁″垪琛�
+     *
+     * @param bo 鏌ヨ鏉′欢
+     * @return 鏉愭枡妫�楠岀粺璁″垪琛�
+     */
+    @Override
+    public List<QmMatcheckVo> queryList(QmMatcheckBo bo) {
+        LambdaQueryWrapper<QmMatcheck> lqw = buildQueryWrapper(bo);
+        return baseMapper.selectVoList(lqw);
+    }
+
+    private LambdaQueryWrapper<QmMatcheck> buildQueryWrapper(QmMatcheckBo bo) {
+        LambdaQueryWrapper<QmMatcheck> lqw = Wrappers.lambdaQuery();
+        lqw.like(StringUtils.isNotBlank(bo.getBatchCode()), QmMatcheck::getBatchCode, bo.getBatchCode());
+        lqw.eq(StringUtils.isNotBlank(bo.getMatCode()), QmMatcheck::getMatCode, bo.getMatCode());
+        lqw.like(StringUtils.isNotBlank(bo.getCheckName()), QmMatcheck::getCheckName, bo.getCheckName());
+        return lqw;
+    }
+
+    /**
+     * 鏂板鏉愭枡妫�楠岀粺璁�
+     *
+     * @param bo 鏉愭枡妫�楠岀粺璁�
+     * @return 鏄惁鏂板鎴愬姛
+     */
+    @Override
+    public Boolean insertByBo(QmMatcheckBo bo) {
+        QmMatcheck add = MapstructUtils.convert(bo, QmMatcheck.class);
+        validEntityBeforeSave(add);
+        boolean flag = baseMapper.insert(add) > 0;
+        if (flag) {
+            bo.setId(add.getId());
+        }
+        return flag;
+    }
+
+    /**
+     * 淇敼鏉愭枡妫�楠岀粺璁�
+     *
+     * @param bo 鏉愭枡妫�楠岀粺璁�
+     * @return 鏄惁淇敼鎴愬姛
+     */
+    @Override
+    public Boolean updateByBo(QmMatcheckBo bo) {
+        QmMatcheck update = MapstructUtils.convert(bo, QmMatcheck.class);
+        validEntityBeforeSave(update);
+        return baseMapper.updateById(update) > 0;
+    }
+
+    /**
+     * 淇濆瓨鍓嶇殑鏁版嵁鏍¢獙
+     */
+    private void validEntityBeforeSave(QmMatcheck entity){
+        //TODO 鍋氫竴浜涙暟鎹牎楠�,濡傚敮涓�绾︽潫
+    }
+
+    /**
+     * 鏍¢獙骞舵壒閲忓垹闄ゆ潗鏂欐楠岀粺璁′俊鎭�
+     *
+     * @param ids     寰呭垹闄ょ殑涓婚敭闆嗗悎
+     * @param isValid 鏄惁杩涜鏈夋晥鎬ф牎楠�
+     * @return 鏄惁鍒犻櫎鎴愬姛
+     */
+    @Override
+    public Boolean deleteWithValidByIds(Collection<String> ids, Boolean isValid) {
+        if(isValid){
+            //TODO 鍋氫竴浜涗笟鍔′笂鐨勬牎楠�,鍒ゆ柇鏄惁闇�瑕佹牎楠�
+        }
+        return baseMapper.deleteByIds(ids) > 0;
+    }
+
+    /**
+     * 鑾峰彇妫�楠岄」鐩�
+     *
+     * @param judgeId 鍒ゅ畾ID
+     * @return 妫�楠岄」鐩垪琛�
+     */
+    @Override
+    public List<Map<String, Object>> getQmCheckItem(String judgeId) {
+        return baseMapper.getQmCheckItem(judgeId);
+    }
+
+    /**
+     * 鎸夋壒娆″拰鐗屽彿鏌ヨ鏉愭枡妫�楠岀粺璁�(澶氳〃)
+     *
+     * @param bo 鏌ヨ鏉′欢
+     * @return 鏉愭枡妫�楠岀粺璁″垪琛�
+     */
+    @Override
+    public List<QmMatcheckVo> listQmMatcheck(QmMatcheckBo bo) {
+        return baseMapper.listQmMatcheck(bo);
+    }
+}
diff --git a/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-qa/src/main/java/org/dromara/sc/md/controller/MdMatController.java b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-qa/src/main/java/org/dromara/sc/md/controller/MdMatController.java
new file mode 100644
index 0000000..038b934
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-qa/src/main/java/org/dromara/sc/md/controller/MdMatController.java
@@ -0,0 +1,105 @@
+package org.dromara.sc.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.sc.md.domain.vo.MdMatVo;
+import org.dromara.sc.md.domain.bo.MdMatBo;
+import org.dromara.sc.md.service.IMdMatService;
+import org.dromara.common.mybatis.core.page.TableDataInfo;
+
+/**
+ * 鐗╂枡
+ *
+ * @author zhuguifei
+ * @date 2026-04-13
+ */
+@Validated
+@RequiredArgsConstructor
+@RestController
+@RequestMapping("/md/mat")
+public class MdMatController extends BaseController {
+
+    private final IMdMatService mdMatService;
+
+    /**
+     * 鏌ヨ鐗╂枡鍒楄〃
+     */
+    @SaCheckPermission("md:mat:list")
+    @GetMapping("/list")
+    public TableDataInfo<MdMatVo> list(MdMatBo bo, PageQuery pageQuery) {
+        return mdMatService.queryPageList(bo, pageQuery);
+    }
+
+    /**
+     * 瀵煎嚭鐗╂枡鍒楄〃
+     */
+    @SaCheckPermission("md:mat:export")
+    @Log(title = "鐗╂枡", businessType = BusinessType.EXPORT)
+    @PostMapping("/export")
+    public void export(MdMatBo bo, HttpServletResponse response) {
+        List<MdMatVo> list = mdMatService.queryList(bo);
+        ExcelUtil.exportExcel(list, "鐗╂枡", MdMatVo.class, response);
+    }
+
+    /**
+     * 鑾峰彇鐗╂枡璇︾粏淇℃伅
+     *
+     * @param id 涓婚敭
+     */
+    @SaCheckPermission("md:mat:query")
+    @GetMapping("/{id}")
+    public R<MdMatVo> getInfo(@NotNull(message = "涓婚敭涓嶈兘涓虹┖")
+                                     @PathVariable String id) {
+        return R.ok(mdMatService.queryById(id));
+    }
+
+    /**
+     * 鏂板鐗╂枡
+     */
+    @SaCheckPermission("md:mat:add")
+    @Log(title = "鐗╂枡", businessType = BusinessType.INSERT)
+    @RepeatSubmit()
+    @PostMapping()
+    public R<Void> add(@Validated(AddGroup.class) @RequestBody MdMatBo bo) {
+        return toAjax(mdMatService.insertByBo(bo));
+    }
+
+    /**
+     * 淇敼鐗╂枡
+     */
+    @SaCheckPermission("md:mat:edit")
+    @Log(title = "鐗╂枡", businessType = BusinessType.UPDATE)
+    @RepeatSubmit()
+    @PutMapping()
+    public R<Void> edit(@Validated(EditGroup.class) @RequestBody MdMatBo bo) {
+        return toAjax(mdMatService.updateByBo(bo));
+    }
+
+    /**
+     * 鍒犻櫎鐗╂枡
+     *
+     * @param ids 涓婚敭涓�
+     */
+    @SaCheckPermission("md:mat:remove")
+    @Log(title = "鐗╂枡", businessType = BusinessType.DELETE)
+    @DeleteMapping("/{ids}")
+    public R<Void> remove(@NotEmpty(message = "涓婚敭涓嶈兘涓虹┖")
+                          @PathVariable String[] ids) {
+        return toAjax(mdMatService.deleteWithValidByIds(List.of(ids), true));
+    }
+}
diff --git a/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-qa/src/main/java/org/dromara/sc/md/controller/MdMatTypeController.java b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-qa/src/main/java/org/dromara/sc/md/controller/MdMatTypeController.java
new file mode 100644
index 0000000..2729d63
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-qa/src/main/java/org/dromara/sc/md/controller/MdMatTypeController.java
@@ -0,0 +1,105 @@
+package org.dromara.sc.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.sc.md.domain.vo.MdMatTypeVo;
+import org.dromara.sc.md.domain.bo.MdMatTypeBo;
+import org.dromara.sc.md.service.IMdMatTypeService;
+import org.dromara.common.mybatis.core.page.TableDataInfo;
+
+/**
+ * 鐗╂枡绫诲瀷
+ *
+ * @author zhuguifei
+ * @date 2026-04-13
+ */
+@Validated
+@RequiredArgsConstructor
+@RestController
+@RequestMapping("/md/matType")
+public class MdMatTypeController extends BaseController {
+
+    private final IMdMatTypeService mdMatTypeService;
+
+    /**
+     * 鏌ヨ鐗╂枡绫诲瀷鍒楄〃
+     */
+    @SaCheckPermission("md:matType:list")
+    @GetMapping("/list")
+    public TableDataInfo<MdMatTypeVo> list(MdMatTypeBo bo, PageQuery pageQuery) {
+        return mdMatTypeService.queryPageList(bo, pageQuery);
+    }
+
+    /**
+     * 瀵煎嚭鐗╂枡绫诲瀷鍒楄〃
+     */
+    @SaCheckPermission("md:matType:export")
+    @Log(title = "鐗╂枡绫诲瀷", businessType = BusinessType.EXPORT)
+    @PostMapping("/export")
+    public void export(MdMatTypeBo bo, HttpServletResponse response) {
+        List<MdMatTypeVo> list = mdMatTypeService.queryList(bo);
+        ExcelUtil.exportExcel(list, "鐗╂枡绫诲瀷", MdMatTypeVo.class, response);
+    }
+
+    /**
+     * 鑾峰彇鐗╂枡绫诲瀷璇︾粏淇℃伅
+     *
+     * @param id 涓婚敭
+     */
+    @SaCheckPermission("md:matType:query")
+    @GetMapping("/{id}")
+    public R<MdMatTypeVo> getInfo(@NotNull(message = "涓婚敭涓嶈兘涓虹┖")
+                                     @PathVariable String id) {
+        return R.ok(mdMatTypeService.queryById(id));
+    }
+
+    /**
+     * 鏂板鐗╂枡绫诲瀷
+     */
+    @SaCheckPermission("md:matType:add")
+    @Log(title = "鐗╂枡绫诲瀷", businessType = BusinessType.INSERT)
+    @RepeatSubmit()
+    @PostMapping()
+    public R<Void> add(@Validated(AddGroup.class) @RequestBody MdMatTypeBo bo) {
+        return toAjax(mdMatTypeService.insertByBo(bo));
+    }
+
+    /**
+     * 淇敼鐗╂枡绫诲瀷
+     */
+    @SaCheckPermission("md:matType:edit")
+    @Log(title = "鐗╂枡绫诲瀷", businessType = BusinessType.UPDATE)
+    @RepeatSubmit()
+    @PutMapping()
+    public R<Void> edit(@Validated(EditGroup.class) @RequestBody MdMatTypeBo bo) {
+        return toAjax(mdMatTypeService.updateByBo(bo));
+    }
+
+    /**
+     * 鍒犻櫎鐗╂枡绫诲瀷
+     *
+     * @param ids 涓婚敭涓�
+     */
+    @SaCheckPermission("md:matType:remove")
+    @Log(title = "鐗╂枡绫诲瀷", businessType = BusinessType.DELETE)
+    @DeleteMapping("/{ids}")
+    public R<Void> remove(@NotEmpty(message = "涓婚敭涓嶈兘涓虹┖")
+                          @PathVariable String[] ids) {
+        return toAjax(mdMatTypeService.deleteWithValidByIds(List.of(ids), true));
+    }
+}
diff --git a/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-qa/src/main/java/org/dromara/sc/md/domain/MdMat.java b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-qa/src/main/java/org/dromara/sc/md/domain/MdMat.java
new file mode 100644
index 0000000..389590c
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-qa/src/main/java/org/dromara/sc/md/domain/MdMat.java
@@ -0,0 +1,97 @@
+package org.dromara.sc.md.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;
+
+/**
+ * 鐗╂枡瀵硅薄 MD_MAT
+ *
+ * @author zhuguifei
+ * @date 2026-04-13
+ */
+@Data
+@TableName("MD_MAT")
+public class MdMat {
+
+    @Serial
+    private static final long serialVersionUID = 1L;
+
+    /**
+     * id
+     */
+    @TableId(value = "ID")
+    private String id;
+
+    /**
+     * 鐗╂枡浠g爜
+     */
+    private String code;
+
+    /**
+     * 鍏ㄧО
+     */
+    private String name;
+
+    /**
+     * 绠�绉�
+     */
+    private String simpleName;
+
+    /**
+     * 鎻忚堪
+     */
+    private String des;
+
+    /**
+     * 鍗曚綅
+     */
+    private String uid;
+
+    /**
+     * 鏈�鍚庢洿鏂版椂闂�
+     */
+    private Date lastUpdateTime;
+
+    /**
+     * 鍚敤
+     */
+    private Long enable;
+
+    /**
+     * 鍒犻櫎
+     */
+    private String del;
+
+    /**
+     * 鐗╂枡绫诲瀷
+     */
+    private String tid;
+
+    /**
+     * 鍒涘缓浜�
+     */
+    private String createUserName;
+
+    /**
+     * 鍒涘缓鏃堕棿
+     */
+    private Date createUserTime;
+
+    /**
+     * 鏇存柊浜�
+     */
+    private String updateUserName;
+
+    /**
+     * 鏇存柊鏃堕棿
+     */
+    private Date updateUserTime;
+
+
+}
diff --git a/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-qa/src/main/java/org/dromara/sc/md/domain/MdMatType.java b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-qa/src/main/java/org/dromara/sc/md/domain/MdMatType.java
new file mode 100644
index 0000000..273e37f
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-qa/src/main/java/org/dromara/sc/md/domain/MdMatType.java
@@ -0,0 +1,87 @@
+package org.dromara.sc.md.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;
+
+/**
+ * 鐗╂枡绫诲瀷瀵硅薄 MD_MAT_TYPE
+ *
+ * @author zhuguifei
+ * @date 2026-04-13
+ */
+@Data
+@TableName("MD_MAT_TYPE")
+public class MdMatType {
+
+    @Serial
+    private static final long serialVersionUID = 1L;
+
+    /**
+     * id
+     */
+    @TableId(value = "ID")
+    private String id;
+
+    /**
+     * 鐗╂枡缁勫閿�
+     */
+    private String gid;
+
+    /**
+     * 缂栫爜
+     */
+    private String code;
+
+    /**
+     * 鍚嶇О
+     */
+    private String name;
+
+    /**
+     * 鎻忚堪
+     */
+    private String des;
+
+    /**
+     * 鍚敤
+     */
+    private Long enable;
+
+    /**
+     * 鍒犻櫎
+     */
+    private String del;
+
+    /**
+     * mes缂栫爜
+     */
+    private String mesCode;
+
+    /**
+     * 鍒涘缓浜�
+     */
+    private String createUserName;
+
+    /**
+     * 鍒涘缓鏃堕棿
+     */
+    private Date createUserTime;
+
+    /**
+     * 鏇存柊浜�
+     */
+    private String updateUserName;
+
+    /**
+     * 鏇存柊鏃堕棿
+     */
+    private Date updateUserTime;
+
+
+}
diff --git a/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-qa/src/main/java/org/dromara/sc/md/domain/bo/MdMatBo.java b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-qa/src/main/java/org/dromara/sc/md/domain/bo/MdMatBo.java
new file mode 100644
index 0000000..8937469
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-qa/src/main/java/org/dromara/sc/md/domain/bo/MdMatBo.java
@@ -0,0 +1,97 @@
+package org.dromara.sc.md.domain.bo;
+
+import org.dromara.sc.md.domain.MdMat;
+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;
+
+/**
+ * 鐗╂枡涓氬姟瀵硅薄 MD_MAT
+ *
+ * @author zhuguifei
+ * @date 2026-04-13
+ */
+@Data
+@EqualsAndHashCode(callSuper = true)
+@AutoMapper(target = MdMat.class, reverseConvertGenerate = false)
+public class MdMatBo extends BaseEntity {
+
+    /**
+     * id
+     */
+    @NotBlank(message = "id涓嶈兘涓虹┖", groups = { EditGroup.class })
+    private String id;
+
+    /**
+     * 鐗╂枡浠g爜
+     */
+    private String code;
+
+    /**
+     * 鍏ㄧО
+     */
+    private String name;
+
+    /**
+     * 绠�绉�
+     */
+    private String simpleName;
+
+    /**
+     * 鎻忚堪
+     */
+    private String des;
+
+    /**
+     * 鍗曚綅
+     */
+    private String uid;
+
+    /**
+     * 鏈�鍚庢洿鏂版椂闂�
+     */
+    private Date lastUpdateTime;
+
+    /**
+     * 鍚敤
+     */
+    private Long enable;
+
+    /**
+     * 鍒犻櫎
+     */
+    private String del;
+
+    /**
+     * 鐗╂枡绫诲瀷
+     */
+    private String tid;
+
+    /**
+     * 鍒涘缓浜�
+     */
+    private String createUserName;
+
+    /**
+     * 鍒涘缓鏃堕棿
+     */
+    private Date createUserTime;
+
+    /**
+     * 鏇存柊浜�
+     */
+    private String updateUserName;
+
+    /**
+     * 鏇存柊鏃堕棿
+     */
+    private Date updateUserTime;
+
+
+}
diff --git a/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-qa/src/main/java/org/dromara/sc/md/domain/bo/MdMatTypeBo.java b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-qa/src/main/java/org/dromara/sc/md/domain/bo/MdMatTypeBo.java
new file mode 100644
index 0000000..570defe
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-qa/src/main/java/org/dromara/sc/md/domain/bo/MdMatTypeBo.java
@@ -0,0 +1,87 @@
+package org.dromara.sc.md.domain.bo;
+
+import org.dromara.sc.md.domain.MdMatType;
+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;
+
+/**
+ * 鐗╂枡绫诲瀷涓氬姟瀵硅薄 MD_MAT_TYPE
+ *
+ * @author zhuguifei
+ * @date 2026-04-13
+ */
+@Data
+@EqualsAndHashCode(callSuper = true)
+@AutoMapper(target = MdMatType.class, reverseConvertGenerate = false)
+public class MdMatTypeBo extends BaseEntity {
+
+    /**
+     * id
+     */
+    @NotBlank(message = "id涓嶈兘涓虹┖", groups = { EditGroup.class })
+    private String id;
+
+    /**
+     * 鐗╂枡缁勫閿�
+     */
+    private String gid;
+
+    /**
+     * 缂栫爜
+     */
+    private String code;
+
+    /**
+     * 鍚嶇О
+     */
+    private String name;
+
+    /**
+     * 鎻忚堪
+     */
+    private String des;
+
+    /**
+     * 鍚敤
+     */
+    private Long enable;
+
+    /**
+     * 鍒犻櫎
+     */
+    private String del;
+
+    /**
+     * mes缂栫爜
+     */
+    private String mesCode;
+
+    /**
+     * 鍒涘缓浜�
+     */
+    private String createUserName;
+
+    /**
+     * 鍒涘缓鏃堕棿
+     */
+    private Date createUserTime;
+
+    /**
+     * 鏇存柊浜�
+     */
+    private String updateUserName;
+
+    /**
+     * 鏇存柊鏃堕棿
+     */
+    private Date updateUserTime;
+
+
+}
diff --git a/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-qa/src/main/java/org/dromara/sc/md/domain/vo/MdMatTypeVo.java b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-qa/src/main/java/org/dromara/sc/md/domain/vo/MdMatTypeVo.java
new file mode 100644
index 0000000..e84d889
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-qa/src/main/java/org/dromara/sc/md/domain/vo/MdMatTypeVo.java
@@ -0,0 +1,106 @@
+package org.dromara.sc.md.domain.vo;
+
+import java.util.Date;
+import com.fasterxml.jackson.annotation.JsonFormat;
+import org.dromara.sc.md.domain.MdMatType;
+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_MAT_TYPE
+ *
+ * @author zhuguifei
+ * @date 2026-04-13
+ */
+@Data
+@ExcelIgnoreUnannotated
+@AutoMapper(target = MdMatType.class)
+public class MdMatTypeVo implements Serializable {
+
+    @Serial
+    private static final long serialVersionUID = 1L;
+
+    /**
+     * id
+     */
+    @ExcelProperty(value = "id")
+    private String id;
+
+    /**
+     * 鐗╂枡缁勫閿�
+     */
+    @ExcelProperty(value = "鐗╂枡缁勫閿�")
+    private String gid;
+
+    /**
+     * 缂栫爜
+     */
+    @ExcelProperty(value = "缂栫爜")
+    private String code;
+
+    /**
+     * 鍚嶇О
+     */
+    @ExcelProperty(value = "鍚嶇О")
+    private String name;
+
+    /**
+     * 鎻忚堪
+     */
+    @ExcelProperty(value = "鎻忚堪")
+    private String des;
+
+    /**
+     * 鍚敤
+     */
+    @ExcelProperty(value = "鍚敤")
+    private Long enable;
+
+    /**
+     * 鍒犻櫎
+     */
+    @ExcelProperty(value = "鍒犻櫎")
+    private String del;
+
+    /**
+     * mes缂栫爜
+     */
+    @ExcelProperty(value = "mes缂栫爜")
+    private String mesCode;
+
+    /**
+     * 鍒涘缓浜�
+     */
+    @ExcelProperty(value = "鍒涘缓浜�")
+    private String createUserName;
+
+    /**
+     * 鍒涘缓鏃堕棿
+     */
+    @ExcelProperty(value = "鍒涘缓鏃堕棿")
+    private Date createUserTime;
+
+    /**
+     * 鏇存柊浜�
+     */
+    @ExcelProperty(value = "鏇存柊浜�")
+    private String updateUserName;
+
+    /**
+     * 鏇存柊鏃堕棿
+     */
+    @ExcelProperty(value = "鏇存柊鏃堕棿")
+    private Date updateUserTime;
+
+
+}
diff --git a/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-qa/src/main/java/org/dromara/sc/md/domain/vo/MdMatVo.java b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-qa/src/main/java/org/dromara/sc/md/domain/vo/MdMatVo.java
new file mode 100644
index 0000000..56be849
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-qa/src/main/java/org/dromara/sc/md/domain/vo/MdMatVo.java
@@ -0,0 +1,118 @@
+package org.dromara.sc.md.domain.vo;
+
+import java.util.Date;
+import com.fasterxml.jackson.annotation.JsonFormat;
+import org.dromara.sc.md.domain.MdMat;
+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_MAT
+ *
+ * @author zhuguifei
+ * @date 2026-04-13
+ */
+@Data
+@ExcelIgnoreUnannotated
+@AutoMapper(target = MdMat.class)
+public class MdMatVo implements Serializable {
+
+    @Serial
+    private static final long serialVersionUID = 1L;
+
+    /**
+     * id
+     */
+    @ExcelProperty(value = "id")
+    private String id;
+
+    /**
+     * 鐗╂枡浠g爜
+     */
+    @ExcelProperty(value = "鐗╂枡浠g爜")
+    private String code;
+
+    /**
+     * 鍏ㄧО
+     */
+    @ExcelProperty(value = "鍏ㄧО")
+    private String name;
+
+    /**
+     * 绠�绉�
+     */
+    @ExcelProperty(value = "绠�绉�")
+    private String simpleName;
+
+    /**
+     * 鎻忚堪
+     */
+    @ExcelProperty(value = "鎻忚堪")
+    private String des;
+
+    /**
+     * 鍗曚綅
+     */
+    @ExcelProperty(value = "鍗曚綅")
+    private String uid;
+
+    /**
+     * 鏈�鍚庢洿鏂版椂闂�
+     */
+    @ExcelProperty(value = "鏈�鍚庢洿鏂版椂闂�")
+    private Date lastUpdateTime;
+
+    /**
+     * 鍚敤
+     */
+    @ExcelProperty(value = "鍚敤")
+    private Long enable;
+
+    /**
+     * 鍒犻櫎
+     */
+    @ExcelProperty(value = "鍒犻櫎")
+    private String del;
+
+    /**
+     * 鐗╂枡绫诲瀷
+     */
+    @ExcelProperty(value = "鐗╂枡绫诲瀷")
+    private String tid;
+
+    /**
+     * 鍒涘缓浜�
+     */
+    @ExcelProperty(value = "鍒涘缓浜�")
+    private String createUserName;
+
+    /**
+     * 鍒涘缓鏃堕棿
+     */
+    @ExcelProperty(value = "鍒涘缓鏃堕棿")
+    private Date createUserTime;
+
+    /**
+     * 鏇存柊浜�
+     */
+    @ExcelProperty(value = "鏇存柊浜�")
+    private String updateUserName;
+
+    /**
+     * 鏇存柊鏃堕棿
+     */
+    @ExcelProperty(value = "鏇存柊鏃堕棿")
+    private Date updateUserTime;
+
+
+}
diff --git a/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-qa/src/main/java/org/dromara/sc/md/mapper/MdMatMapper.java b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-qa/src/main/java/org/dromara/sc/md/mapper/MdMatMapper.java
new file mode 100644
index 0000000..0c41b40
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-qa/src/main/java/org/dromara/sc/md/mapper/MdMatMapper.java
@@ -0,0 +1,15 @@
+package org.dromara.sc.md.mapper;
+
+import org.dromara.sc.md.domain.MdMat;
+import org.dromara.sc.md.domain.vo.MdMatVo;
+import org.dromara.common.mybatis.core.mapper.BaseMapperPlus;
+
+/**
+ * 鐗╂枡Mapper鎺ュ彛
+ *
+ * @author zhuguifei
+ * @date 2026-04-13
+ */
+public interface MdMatMapper extends BaseMapperPlus<MdMat, MdMatVo> {
+
+}
diff --git a/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-qa/src/main/java/org/dromara/sc/md/mapper/MdMatTypeMapper.java b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-qa/src/main/java/org/dromara/sc/md/mapper/MdMatTypeMapper.java
new file mode 100644
index 0000000..2e962df
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-qa/src/main/java/org/dromara/sc/md/mapper/MdMatTypeMapper.java
@@ -0,0 +1,15 @@
+package org.dromara.sc.md.mapper;
+
+import org.dromara.sc.md.domain.MdMatType;
+import org.dromara.sc.md.domain.vo.MdMatTypeVo;
+import org.dromara.common.mybatis.core.mapper.BaseMapperPlus;
+
+/**
+ * 鐗╂枡绫诲瀷Mapper鎺ュ彛
+ *
+ * @author zhuguifei
+ * @date 2026-04-13
+ */
+public interface MdMatTypeMapper extends BaseMapperPlus<MdMatType, MdMatTypeVo> {
+
+}
diff --git a/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-qa/src/main/java/org/dromara/sc/md/service/IMdMatService.java b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-qa/src/main/java/org/dromara/sc/md/service/IMdMatService.java
new file mode 100644
index 0000000..0b9c397
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-qa/src/main/java/org/dromara/sc/md/service/IMdMatService.java
@@ -0,0 +1,68 @@
+package org.dromara.sc.md.service;
+
+import org.dromara.sc.md.domain.vo.MdMatVo;
+import org.dromara.sc.md.domain.bo.MdMatBo;
+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-04-13
+ */
+public interface IMdMatService {
+
+    /**
+     * 鏌ヨ鐗╂枡
+     *
+     * @param id 涓婚敭
+     * @return 鐗╂枡
+     */
+    MdMatVo queryById(String id);
+
+    /**
+     * 鍒嗛〉鏌ヨ鐗╂枡鍒楄〃
+     *
+     * @param bo        鏌ヨ鏉′欢
+     * @param pageQuery 鍒嗛〉鍙傛暟
+     * @return 鐗╂枡鍒嗛〉鍒楄〃
+     */
+    TableDataInfo<MdMatVo> queryPageList(MdMatBo bo, PageQuery pageQuery);
+
+    /**
+     * 鏌ヨ绗﹀悎鏉′欢鐨勭墿鏂欏垪琛�
+     *
+     * @param bo 鏌ヨ鏉′欢
+     * @return 鐗╂枡鍒楄〃
+     */
+    List<MdMatVo> queryList(MdMatBo bo);
+
+    /**
+     * 鏂板鐗╂枡
+     *
+     * @param bo 鐗╂枡
+     * @return 鏄惁鏂板鎴愬姛
+     */
+    Boolean insertByBo(MdMatBo bo);
+
+    /**
+     * 淇敼鐗╂枡
+     *
+     * @param bo 鐗╂枡
+     * @return 鏄惁淇敼鎴愬姛
+     */
+    Boolean updateByBo(MdMatBo 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/sc/md/service/IMdMatTypeService.java b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-qa/src/main/java/org/dromara/sc/md/service/IMdMatTypeService.java
new file mode 100644
index 0000000..ac89e41
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-qa/src/main/java/org/dromara/sc/md/service/IMdMatTypeService.java
@@ -0,0 +1,68 @@
+package org.dromara.sc.md.service;
+
+import org.dromara.sc.md.domain.vo.MdMatTypeVo;
+import org.dromara.sc.md.domain.bo.MdMatTypeBo;
+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-04-13
+ */
+public interface IMdMatTypeService {
+
+    /**
+     * 鏌ヨ鐗╂枡绫诲瀷
+     *
+     * @param id 涓婚敭
+     * @return 鐗╂枡绫诲瀷
+     */
+    MdMatTypeVo queryById(String id);
+
+    /**
+     * 鍒嗛〉鏌ヨ鐗╂枡绫诲瀷鍒楄〃
+     *
+     * @param bo        鏌ヨ鏉′欢
+     * @param pageQuery 鍒嗛〉鍙傛暟
+     * @return 鐗╂枡绫诲瀷鍒嗛〉鍒楄〃
+     */
+    TableDataInfo<MdMatTypeVo> queryPageList(MdMatTypeBo bo, PageQuery pageQuery);
+
+    /**
+     * 鏌ヨ绗﹀悎鏉′欢鐨勭墿鏂欑被鍨嬪垪琛�
+     *
+     * @param bo 鏌ヨ鏉′欢
+     * @return 鐗╂枡绫诲瀷鍒楄〃
+     */
+    List<MdMatTypeVo> queryList(MdMatTypeBo bo);
+
+    /**
+     * 鏂板鐗╂枡绫诲瀷
+     *
+     * @param bo 鐗╂枡绫诲瀷
+     * @return 鏄惁鏂板鎴愬姛
+     */
+    Boolean insertByBo(MdMatTypeBo bo);
+
+    /**
+     * 淇敼鐗╂枡绫诲瀷
+     *
+     * @param bo 鐗╂枡绫诲瀷
+     * @return 鏄惁淇敼鎴愬姛
+     */
+    Boolean updateByBo(MdMatTypeBo 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/sc/md/service/impl/MdMatServiceImpl.java b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-qa/src/main/java/org/dromara/sc/md/service/impl/MdMatServiceImpl.java
new file mode 100644
index 0000000..1575845
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-qa/src/main/java/org/dromara/sc/md/service/impl/MdMatServiceImpl.java
@@ -0,0 +1,147 @@
+package org.dromara.sc.md.service.impl;
+
+import com.baomidou.dynamic.datasource.annotation.DS;
+import org.dromara.common.core.service.MatService;
+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.sc.md.domain.bo.MdMatBo;
+import org.dromara.sc.md.domain.vo.MdMatVo;
+import org.dromara.sc.md.domain.MdMat;
+import org.dromara.sc.md.mapper.MdMatMapper;
+import org.dromara.sc.md.service.IMdMatService;
+
+import java.util.List;
+import java.util.Map;
+import java.util.Collection;
+
+/**
+ * 鐗╂枡Service涓氬姟灞傚鐞�
+ *
+ * @author zhuguifei
+ * @date 2026-04-13
+ */
+@DS("oracle_sc")
+@Slf4j
+@RequiredArgsConstructor
+@Service
+public class MdMatServiceImpl implements IMdMatService, MatService {
+
+    private final MdMatMapper baseMapper;
+
+    /**
+     * 鏌ヨ鐗╂枡
+     *
+     * @param id 涓婚敭
+     * @return 鐗╂枡
+     */
+    @Override
+    public MdMatVo queryById(String id){
+        return baseMapper.selectVoById(id);
+    }
+
+    /**
+     * 鍒嗛〉鏌ヨ鐗╂枡鍒楄〃
+     *
+     * @param bo        鏌ヨ鏉′欢
+     * @param pageQuery 鍒嗛〉鍙傛暟
+     * @return 鐗╂枡鍒嗛〉鍒楄〃
+     */
+    @Override
+    public TableDataInfo<MdMatVo> queryPageList(MdMatBo bo, PageQuery pageQuery) {
+        LambdaQueryWrapper<MdMat> lqw = buildQueryWrapper(bo);
+        Page<MdMatVo> result = baseMapper.selectVoPage(pageQuery.build(), lqw);
+        return TableDataInfo.build(result);
+    }
+
+    /**
+     * 鏌ヨ绗﹀悎鏉′欢鐨勭墿鏂欏垪琛�
+     *
+     * @param bo 鏌ヨ鏉′欢
+     * @return 鐗╂枡鍒楄〃
+     */
+    @Override
+    public List<MdMatVo> queryList(MdMatBo bo) {
+        LambdaQueryWrapper<MdMat> lqw = buildQueryWrapper(bo);
+        return baseMapper.selectVoList(lqw);
+    }
+
+    private LambdaQueryWrapper<MdMat> buildQueryWrapper(MdMatBo bo) {
+        Map<String, Object> params = bo.getParams();
+        LambdaQueryWrapper<MdMat> lqw = Wrappers.lambdaQuery();
+        lqw.orderByAsc(MdMat::getId);
+        lqw.eq(StringUtils.isNotBlank(bo.getCode()), MdMat::getCode, bo.getCode());
+        lqw.like(StringUtils.isNotBlank(bo.getName()), MdMat::getName, bo.getName());
+        lqw.like(StringUtils.isNotBlank(bo.getSimpleName()), MdMat::getSimpleName, bo.getSimpleName());
+        lqw.eq(bo.getEnable() != null, MdMat::getEnable, bo.getEnable());
+        lqw.eq(StringUtils.isNotBlank(bo.getTid()), MdMat::getTid, bo.getTid());
+        return lqw;
+    }
+
+    /**
+     * 鏂板鐗╂枡
+     *
+     * @param bo 鐗╂枡
+     * @return 鏄惁鏂板鎴愬姛
+     */
+    @Override
+    public Boolean insertByBo(MdMatBo bo) {
+        MdMat add = MapstructUtils.convert(bo, MdMat.class);
+        validEntityBeforeSave(add);
+        boolean flag = baseMapper.insert(add) > 0;
+        if (flag) {
+            bo.setId(add.getId());
+        }
+        return flag;
+    }
+
+    /**
+     * 淇敼鐗╂枡
+     *
+     * @param bo 鐗╂枡
+     * @return 鏄惁淇敼鎴愬姛
+     */
+    @Override
+    public Boolean updateByBo(MdMatBo bo) {
+        MdMat update = MapstructUtils.convert(bo, MdMat.class);
+        validEntityBeforeSave(update);
+        return baseMapper.updateById(update) > 0;
+    }
+
+    /**
+     * 淇濆瓨鍓嶇殑鏁版嵁鏍¢獙
+     */
+    private void validEntityBeforeSave(MdMat entity){
+        //TODO 鍋氫竴浜涙暟鎹牎楠�,濡傚敮涓�绾︽潫
+    }
+
+    /**
+     * 鏍¢獙骞舵壒閲忓垹闄ょ墿鏂欎俊鎭�
+     *
+     * @param ids     寰呭垹闄ょ殑涓婚敭闆嗗悎
+     * @param isValid 鏄惁杩涜鏈夋晥鎬ф牎楠�
+     * @return 鏄惁鍒犻櫎鎴愬姛
+     */
+    @Override
+    public Boolean deleteWithValidByIds(Collection<String> ids, Boolean isValid) {
+        if(isValid){
+            //TODO 鍋氫竴浜涗笟鍔′笂鐨勬牎楠�,鍒ゆ柇鏄惁闇�瑕佹牎楠�
+        }
+        return baseMapper.deleteByIds(ids) > 0;
+    }
+
+    @Override
+    public String selectMatNameByCode(String matCode) {
+        LambdaQueryWrapper<MdMat> lqw = Wrappers.lambdaQuery();
+        lqw.eq(MdMat::getCode,matCode);
+        MdMatVo mdMatVo = baseMapper.selectVoOne(lqw);
+        return mdMatVo.getName();
+    }
+}
diff --git a/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-qa/src/main/java/org/dromara/sc/md/service/impl/MdMatTypeServiceImpl.java b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-qa/src/main/java/org/dromara/sc/md/service/impl/MdMatTypeServiceImpl.java
new file mode 100644
index 0000000..8a43315
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-qa/src/main/java/org/dromara/sc/md/service/impl/MdMatTypeServiceImpl.java
@@ -0,0 +1,138 @@
+package org.dromara.sc.md.service.impl;
+
+import com.baomidou.dynamic.datasource.annotation.DS;
+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.sc.md.domain.bo.MdMatTypeBo;
+import org.dromara.sc.md.domain.vo.MdMatTypeVo;
+import org.dromara.sc.md.domain.MdMatType;
+import org.dromara.sc.md.mapper.MdMatTypeMapper;
+import org.dromara.sc.md.service.IMdMatTypeService;
+
+import java.util.List;
+import java.util.Map;
+import java.util.Collection;
+
+/**
+ * 鐗╂枡绫诲瀷Service涓氬姟灞傚鐞�
+ *
+ * @author zhuguifei
+ * @date 2026-04-13
+ */
+@DS("oracle_sc")
+@Slf4j
+@RequiredArgsConstructor
+@Service
+public class MdMatTypeServiceImpl implements IMdMatTypeService {
+
+    private final MdMatTypeMapper baseMapper;
+
+    /**
+     * 鏌ヨ鐗╂枡绫诲瀷
+     *
+     * @param id 涓婚敭
+     * @return 鐗╂枡绫诲瀷
+     */
+    @Override
+    public MdMatTypeVo queryById(String id){
+        return baseMapper.selectVoById(id);
+    }
+
+    /**
+     * 鍒嗛〉鏌ヨ鐗╂枡绫诲瀷鍒楄〃
+     *
+     * @param bo        鏌ヨ鏉′欢
+     * @param pageQuery 鍒嗛〉鍙傛暟
+     * @return 鐗╂枡绫诲瀷鍒嗛〉鍒楄〃
+     */
+    @Override
+    public TableDataInfo<MdMatTypeVo> queryPageList(MdMatTypeBo bo, PageQuery pageQuery) {
+        LambdaQueryWrapper<MdMatType> lqw = buildQueryWrapper(bo);
+        Page<MdMatTypeVo> result = baseMapper.selectVoPage(pageQuery.build(), lqw);
+        return TableDataInfo.build(result);
+    }
+
+    /**
+     * 鏌ヨ绗﹀悎鏉′欢鐨勭墿鏂欑被鍨嬪垪琛�
+     *
+     * @param bo 鏌ヨ鏉′欢
+     * @return 鐗╂枡绫诲瀷鍒楄〃
+     */
+    @Override
+    public List<MdMatTypeVo> queryList(MdMatTypeBo bo) {
+        LambdaQueryWrapper<MdMatType> lqw = buildQueryWrapper(bo);
+        return baseMapper.selectVoList(lqw);
+    }
+
+    private LambdaQueryWrapper<MdMatType> buildQueryWrapper(MdMatTypeBo bo) {
+        Map<String, Object> params = bo.getParams();
+        LambdaQueryWrapper<MdMatType> lqw = Wrappers.lambdaQuery();
+        lqw.orderByAsc(MdMatType::getId);
+        lqw.like(StringUtils.isNotBlank(bo.getCode()), MdMatType::getCode, bo.getCode());
+        lqw.like(StringUtils.isNotBlank(bo.getName()), MdMatType::getName, bo.getName());
+        lqw.eq(bo.getEnable() != null, MdMatType::getEnable, bo.getEnable());
+        lqw.eq(StringUtils.isNotBlank(bo.getDel()), MdMatType::getDel, bo.getDel());
+        lqw.like(StringUtils.isNotBlank(bo.getMesCode()), MdMatType::getMesCode, bo.getMesCode());
+        return lqw;
+    }
+
+    /**
+     * 鏂板鐗╂枡绫诲瀷
+     *
+     * @param bo 鐗╂枡绫诲瀷
+     * @return 鏄惁鏂板鎴愬姛
+     */
+    @Override
+    public Boolean insertByBo(MdMatTypeBo bo) {
+        MdMatType add = MapstructUtils.convert(bo, MdMatType.class);
+        validEntityBeforeSave(add);
+        boolean flag = baseMapper.insert(add) > 0;
+        if (flag) {
+            bo.setId(add.getId());
+        }
+        return flag;
+    }
+
+    /**
+     * 淇敼鐗╂枡绫诲瀷
+     *
+     * @param bo 鐗╂枡绫诲瀷
+     * @return 鏄惁淇敼鎴愬姛
+     */
+    @Override
+    public Boolean updateByBo(MdMatTypeBo bo) {
+        MdMatType update = MapstructUtils.convert(bo, MdMatType.class);
+        validEntityBeforeSave(update);
+        return baseMapper.updateById(update) > 0;
+    }
+
+    /**
+     * 淇濆瓨鍓嶇殑鏁版嵁鏍¢獙
+     */
+    private void validEntityBeforeSave(MdMatType 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/sc/test b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-qa/src/main/java/org/dromara/sc/test
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-qa/src/main/java/org/dromara/sc/test
diff --git a/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-qa/src/main/resources/mapper/qa/qm/QmMatcheckMapper.xml b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-qa/src/main/resources/mapper/qa/qm/QmMatcheckMapper.xml
new file mode 100644
index 0000000..180a1a7
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-qa/src/main/resources/mapper/qa/qm/QmMatcheckMapper.xml
@@ -0,0 +1,67 @@
+<?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.QmMatcheckMapper">
+
+    <select id="listQmMatcheck" resultType="org.dromara.qa.qm.domain.vo.QmMatcheckVo">
+        SELECT
+            a.ID AS id,
+            a.PID AS pid,
+            a.BATCH_CODE AS batchCode,
+            a.MAT_CODE AS matCode,
+            a.INSTRUMENT_CODE AS instrumentCode,
+            a.TECH_REQ AS techReq,
+            a.CHECK_STD AS checkStd,
+            a.TEST_ENV AS testEnv,
+            a.ITEM_CODE AS itemCode,
+            d.ITEM_NAME AS itemName,
+            CAST(NULL AS VARCHAR(100)) AS eqp,
+            a.SUB_BATCH_CODE AS subBatchCode,
+            a.SAMPLE_NUMBER AS sampleNumber,
+            a.SAMPLE_TYPE AS sampleType,
+            a.CHECK_NAME AS checkName,
+            a.CHECK_TIME AS checkTime,
+            a.REVIEW_NAME AS reviewName,
+            a.REVIEW_TIME AS reviewTime,
+            a.MAXVAL AS maxval,
+            a.MINVAL AS minval,
+            a.AVGVAL AS avgval,
+            a.SDVAL AS sdval,
+            a.CVVAL AS cvval,
+            a.CPKVAL AS cpkval,
+            a.BADVAL AS badval,
+            a.JUDGE AS judge,
+            a.SINGLEJUDGE AS singlejudge,
+            a.VER_NAME AS verName,
+            a.VER_CODE AS verCode,
+            a.ARCH_DATE AS archDate,
+            a.DEL AS del,
+            a.FLAG AS flag,
+            a.TO_MES_TIME AS toMesTime,
+            a.CHK_DES AS chkDes,
+            CAST(NULL AS VARCHAR(200)) AS checker,
+            b.JUDGE_CODE AS judgeCode,
+            CAST(NULL AS VARCHAR(10)) AS generateDetails,
+            CAST(NULL AS VARCHAR(10)) AS checkType
+        FROM QM_MATCHECK a
+            JOIN QM_BATCH b ON b.BATCH_CODE = a.BATCH_CODE
+                AND b.MAT_CODE = a.MAT_CODE
+            JOIN QM_JUDGE c ON b.JUDGE_CODE = c.ID
+            JOIN QM_CHECKITEM d ON d.STD_CODE = c.STD_COD
+                AND d.ITEM_CODE = a.ITEM_CODE
+                AND d.RID IS NULL
+                AND d.DEL != 1
+        <where>
+            <if test="bo.batchCode != null and bo.batchCode != ''">
+                AND a.BATCH_CODE = #{bo.batchCode}
+            </if>
+            <if test="bo.matCode != null and bo.matCode != ''">
+                AND a.MAT_CODE = #{bo.matCode}
+            </if>
+            AND (a.DEL = 0 OR a.DEL IS NULL)
+            AND b.DELETED != '1'
+        </where>
+        ORDER BY a.ITEM_CODE, a.SUB_BATCH_CODE
+    </select>
+</mapper>
diff --git a/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-qa/src/main/resources/mapper/sc/md/MdMatMapper.xml b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-qa/src/main/resources/mapper/sc/md/MdMatMapper.xml
new file mode 100644
index 0000000..afb20ab
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-qa/src/main/resources/mapper/sc/md/MdMatMapper.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.sc.md.mapper.MdMatMapper">
+</mapper>
diff --git a/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-qa/src/main/resources/mapper/sc/md/MdMatTypeMapper.xml b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-qa/src/main/resources/mapper/sc/md/MdMatTypeMapper.xml
new file mode 100644
index 0000000..5659381
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-qa/src/main/resources/mapper/sc/md/MdMatTypeMapper.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.sc.md.mapper.MdMatTypeMapper">
+</mapper>
diff --git a/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-qa/src/main/resources/mapper/sc/test b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-qa/src/main/resources/mapper/sc/test
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-qa/src/main/resources/mapper/sc/test
diff --git a/ruoyi-plus-soybean/src/service/api/md/mat-type.ts b/ruoyi-plus-soybean/src/service/api/md/mat-type.ts
new file mode 100644
index 0000000..da2cdd3
--- /dev/null
+++ b/ruoyi-plus-soybean/src/service/api/md/mat-type.ts
@@ -0,0 +1,35 @@
+import { request } from '@/service/request';
+
+/** 鑾峰彇鐗╂枡绫诲瀷鍒楄〃 */
+export function fetchGetMatTypeList (params?: Api.Md.MatTypeSearchParams) {
+    return request<Api.Md.MatTypeList>({
+        url: '/md/matType/list',
+        method: 'get',
+        params
+    });
+}
+/** 鏂板鐗╂枡绫诲瀷 */
+export function fetchCreateMatType (data: Api.Md.MatTypeOperateParams) {
+    return request<boolean>({
+        url: '/md/matType',
+        method: 'post',
+        data
+    });
+}
+
+/** 淇敼鐗╂枡绫诲瀷 */
+export function fetchUpdateMatType (data: Api.Md.MatTypeOperateParams) {
+    return request<boolean>({
+        url: '/md/matType',
+        method: 'put',
+        data
+    });
+}
+
+/** 鎵归噺鍒犻櫎鐗╂枡绫诲瀷 */
+export function fetchBatchDeleteMatType (ids: CommonType.IdType[]) {
+    return request<boolean>({
+        url: `/md/matType/${ids.join(',')}`,
+        method: 'delete'
+    });
+}
diff --git a/ruoyi-plus-soybean/src/service/api/md/mat.ts b/ruoyi-plus-soybean/src/service/api/md/mat.ts
new file mode 100644
index 0000000..dcd6389
--- /dev/null
+++ b/ruoyi-plus-soybean/src/service/api/md/mat.ts
@@ -0,0 +1,35 @@
+import { request } from '@/service/request';
+
+/** 鑾峰彇鐗╂枡鍒楄〃 */
+export function fetchGetMatList (params?: Api.Md.MatSearchParams) {
+    return request<Api.Md.MatList>({
+        url: '/md/mat/list',
+        method: 'get',
+        params
+    });
+}
+/** 鏂板鐗╂枡 */
+export function fetchCreateMat (data: Api.Md.MatOperateParams) {
+    return request<boolean>({
+        url: '/md/mat',
+        method: 'post',
+        data
+    });
+}
+
+/** 淇敼鐗╂枡 */
+export function fetchUpdateMat (data: Api.Md.MatOperateParams) {
+    return request<boolean>({
+        url: '/md/mat',
+        method: 'put',
+        data
+    });
+}
+
+/** 鎵归噺鍒犻櫎鐗╂枡 */
+export function fetchBatchDeleteMat (ids: CommonType.IdType[]) {
+    return request<boolean>({
+        url: `/md/mat/${ids.join(',')}`,
+        method: 'delete'
+    });
+}
diff --git a/ruoyi-plus-soybean/src/service/api/qm/matcheck.ts b/ruoyi-plus-soybean/src/service/api/qm/matcheck.ts
new file mode 100644
index 0000000..8353c54
--- /dev/null
+++ b/ruoyi-plus-soybean/src/service/api/qm/matcheck.ts
@@ -0,0 +1,57 @@
+import { request } from '@/service/request';
+
+/** 鑾峰彇鏉愭枡妫�楠岀粺璁″垪琛� */
+export function fetchGetMatcheckList (params?: Api.Qm.MatcheckSearchParams) {
+    return request<Api.Qm.MatcheckList>({
+        url: '/qm/matcheck/list',
+        method: 'get',
+        params
+    });
+}
+
+export function fetchCheckItemList (params?: Api.Qm.MatcheckSearchParams) {
+  return request<Api.Qm.MatcheckCustomItem[]>({
+    url: '/qm/matcheck/listCheckItem',
+    method: 'get',
+    params
+  });
+}
+
+
+/**
+ * 澶氳〃鏌ヨ
+ * @param params
+ */
+export function fetchGetQmMatcheckList (params?: Api.Qm.MatcheckSearchParams) {
+  return request<Api.Qm.MatcheckList[]>({
+    url: '/qm/matcheck/listQmMatcheck',
+    method: 'get',
+    params
+  });
+}
+
+/** 鏂板鏉愭枡妫�楠岀粺璁� */
+export function fetchCreateMatcheck (data: Api.Qm.MatcheckOperateParams) {
+    return request<boolean>({
+        url: '/qm/matcheck',
+        method: 'post',
+        data
+    });
+}
+
+/** 淇敼鏉愭枡妫�楠岀粺璁� */
+export function fetchUpdateMatcheck (data: Api.Qm.MatcheckOperateParams) {
+    return request<boolean>({
+        url: '/qm/matcheck',
+        method: 'put',
+        data
+    });
+}
+
+/** 鎵归噺鍒犻櫎鏉愭枡妫�楠岀粺璁� */
+export function fetchBatchDeleteMatcheck (ids: CommonType.IdType[]) {
+    return request<boolean>({
+        url: `/qm/matcheck/${ids.join(',')}`,
+        method: 'delete'
+    });
+}
diff --git a/ruoyi-plus-soybean/src/typings/api/md.mat-type.api.d.ts b/ruoyi-plus-soybean/src/typings/api/md.mat-type.api.d.ts
new file mode 100644
index 0000000..aa4ccc5
--- /dev/null
+++ b/ruoyi-plus-soybean/src/typings/api/md.mat-type.api.d.ts
@@ -0,0 +1,76 @@
+/**
+ * Namespace Api
+ *
+ * All backend api type
+ */
+declare namespace Api {
+    /**
+     * namespace Md
+     *
+     * backend api module: "Md"
+     */
+    namespace Md {
+        /** mat type */
+        type MatType = Common.CommonRecord<{
+            /** id */
+                id: CommonType.IdType; 
+            /** 鐗╂枡缁勫閿� */
+                gid: CommonType.IdType; 
+            /** 缂栫爜 */
+                code: string; 
+            /** 鍚嶇О */
+                name: string; 
+            /** 鎻忚堪 */
+                des: string; 
+            /** 鍚敤 */
+                enable: number; 
+            /** 鍒犻櫎 */
+                del: string; 
+            /** mes缂栫爜 */
+                mesCode: string; 
+            /** 鍒涘缓浜� */
+                createUserName: string; 
+            /** 鍒涘缓鏃堕棿 */
+                createUserTime: string; 
+            /** 鏇存柊浜� */
+                updateUserName: string; 
+            /** 鏇存柊鏃堕棿 */
+                updateUserTime: string; 
+        }>;
+
+        /** mat type search params */
+        type MatTypeSearchParams = CommonType.RecordNullable<
+            Pick<
+                Api.Md.MatType,
+                        | 'code'
+                        | 'name'
+                        | 'enable'
+                        | 'del'
+                        | 'mesCode'
+            > &
+            Api.Common.CommonSearchParams
+        >;
+
+        /** mat type operate params */
+        type MatTypeOperateParams = CommonType.RecordNullable<
+            Pick<
+                Api.Md.MatType,
+                        | 'id'
+                        | 'gid'
+                        | 'code'
+                        | 'name'
+                        | 'des'
+                        | 'enable'
+                        | 'del'
+                        | 'mesCode'
+                        | 'createUserName'
+                        | 'createUserTime'
+                        | 'updateUserName'
+                        | 'updateUserTime'
+            >
+        >;
+
+        /** mat type list */
+        type MatTypeList = Api.Common.PaginatingQueryRecord<MatType>;
+    }
+}
diff --git a/ruoyi-plus-soybean/src/typings/api/md.mat.api.d.ts b/ruoyi-plus-soybean/src/typings/api/md.mat.api.d.ts
new file mode 100644
index 0000000..5ed69f4
--- /dev/null
+++ b/ruoyi-plus-soybean/src/typings/api/md.mat.api.d.ts
@@ -0,0 +1,83 @@
+/**
+ * Namespace Api
+ *
+ * All backend api type
+ */
+declare namespace Api {
+    /**
+     * namespace Md
+     *
+     * backend api module: "Md"
+     */
+    namespace Md {
+        /** mat */
+        type Mat = Common.CommonRecord<{
+            /** id */
+                id: CommonType.IdType; 
+            /** 鐗╂枡浠g爜 */
+                code: string; 
+            /** 鍏ㄧО */
+                name: string; 
+            /** 绠�绉� */
+                simpleName: string; 
+            /** 鎻忚堪 */
+                des: string; 
+            /** 鍗曚綅 */
+                uid: CommonType.IdType; 
+            /** 鏈�鍚庢洿鏂版椂闂� */
+                lastUpdateTime: string; 
+            /** 鍚敤 */
+                enable: number; 
+            /** 鍒犻櫎 */
+                del: string; 
+            /** 鐗╂枡绫诲瀷 */
+                tid: CommonType.IdType; 
+            /** 鍒涘缓浜� */
+                createUserName: string; 
+            /** 鍒涘缓鏃堕棿 */
+                createUserTime: string; 
+            /** 鏇存柊浜� */
+                updateUserName: string; 
+            /** 鏇存柊鏃堕棿 */
+                updateUserTime: string; 
+        }>;
+
+        /** mat search params */
+        type MatSearchParams = CommonType.RecordNullable<
+            Pick<
+                Api.Md.Mat,
+                        | 'code'
+                        | 'name'
+                        | 'simpleName'
+                        | 'enable'
+                        | 'del'
+                        | 'tid'
+            > &
+            Api.Common.CommonSearchParams
+        >;
+
+        /** mat operate params */
+        type MatOperateParams = CommonType.RecordNullable<
+            Pick<
+                Api.Md.Mat,
+                        | 'id'
+                        | 'code'
+                        | 'name'
+                        | 'simpleName'
+                        | 'des'
+                        | 'uid'
+                        | 'lastUpdateTime'
+                        | 'enable'
+                        | 'del'
+                        | 'tid'
+                        | 'createUserName'
+                        | 'createUserTime'
+                        | 'updateUserName'
+                        | 'updateUserTime'
+            >
+        >;
+
+        /** mat list */
+        type MatList = Api.Common.PaginatingQueryRecord<Mat>;
+    }
+}
diff --git a/ruoyi-plus-soybean/src/typings/api/qm.matcheck.api.d.ts b/ruoyi-plus-soybean/src/typings/api/qm.matcheck.api.d.ts
new file mode 100644
index 0000000..c340104
--- /dev/null
+++ b/ruoyi-plus-soybean/src/typings/api/qm.matcheck.api.d.ts
@@ -0,0 +1,165 @@
+/**
+ * Namespace Api
+ *
+ * All backend api type
+ */
+declare namespace Api {
+    /**
+     * namespace Qm
+     *
+     * backend api module: "Qm"
+     */
+    namespace Qm {
+        /** matcheck */
+        type Matcheck = Common.CommonRecord<{
+            /** id */
+                id: CommonType.IdType;
+            /** 姹囨�昏〃ID */
+                pid: CommonType.IdType;
+            /** 妫�楠屾壒娆� */
+                batchCode: string;
+            /** 鐗屽彿 */
+                matCode: string;
+            /** 浠櫒缂栧彿 */
+                instrumentCode: string;
+            /** 鎶�鏈姹� */
+                techReq: string;
+            /** 妫�楠屼緷鎹� */
+                checkStd: string;
+            /** 娴嬭瘯鐜 */
+                testEnv: string;
+            /** 妫�楠岄」鐩瓹OD */
+                itemCode: string;
+            /** 妫�楠岄」鐩悕绉� */
+                itemName: string;
+            /** 璁惧 */
+                eqp: string;
+            /** 鐩樺彿 */
+                subBatchCode: string;
+            /** 姣忕洏妫�楠屾暟閲� */
+                sampleNumber: number;
+            /** 鍙栨牱绫诲瀷 */
+                sampleType: number;
+            /** 妫�楠屽憳 */
+                checkName: string;
+            /** 妫�楠屾椂闂� */
+                checkTime: string;
+            /** 澶嶆牳鍛� */
+                reviewName: string;
+            /** 澶嶆牳鏃堕棿 */
+                reviewTime: string;
+            /** 鏈�澶у�� */
+                maxval: number;
+            /** 鏈�灏忓�� */
+                minval: number;
+            /** 骞冲潎鍊� */
+                avgval: number;
+            /** SD鍊� */
+                sdval: number;
+            /** CV鍊� */
+                cvval: number;
+            /** CPK鍊� */
+                cpkval: number;
+            /** 瓒呮爣鏁� */
+                badval: number;
+            /** 鍒ゅ畾 */
+                judge: string;
+            /** 鍗曢」鍒ゅ畾 */
+                singlejudge: string;
+            /** 鐗堟湰鍚嶇О */
+                verName: string;
+            /** 鐗堟湰缂栧彿 */
+                verCode: string;
+            /** 淇濆瓨鏈� */
+                archDate: string;
+            /** 鍒犻櫎鏍囧織 */
+                del: number;
+            /** 0-鏈笂浼爉es 1-宸蹭笂浼� */
+                flag: string;
+            /** 涓婁紶MES鏃堕棿 */
+                toMesTime: string;
+            /** 澶囨敞 */
+                chkDes: string;
+            /** 妫�楠屼汉灞曠ず鍚� */
+                checker: string;
+            /** 鍒ゅ畾瑙勭▼ code锛堥潪鏁版嵁搴撳瓧娈碉級 */
+                judgeCode: string;
+            /** 鏄惁鐢熸垚 30 鏀儫鏄庣粏锛堥潪鏁版嵁搴撳瓧娈碉級 */
+                generateDetails: string;
+            /** 妫�娴嬬被鍨嬶紙闈炴暟鎹簱瀛楁锛� */
+                checkType: string;
+        }>;
+
+        /** matcheck search params */
+        type MatcheckSearchParams = CommonType.RecordNullable<
+            Pick<
+                Api.Qm.Matcheck,
+                        | 'batchCode'
+                        | 'judgeCode'
+                        | 'matCode'
+                        | 'checkName'
+                        | 'checkTime'
+                        | 'reviewName'
+                        | 'reviewTime'
+            > &
+            Api.Common.CommonSearchParams & {
+                /** 妫�楠岄」涓嬫媺绛夋帴鍙d娇鐢ㄧ殑鍒ゅ畾瑙勭▼ id */
+                judgeId?: string;
+            }
+        >;
+
+        /** matcheck operate params */
+        type MatcheckOperateParams = CommonType.RecordNullable<
+            Pick<
+                Api.Qm.Matcheck,
+                        | 'id'
+                        | 'pid'
+                        | 'batchCode'
+                        | 'matCode'
+                        | 'instrumentCode'
+                        | 'techReq'
+                        | 'checkStd'
+                        | 'testEnv'
+                        | 'itemCode'
+                        | 'itemName'
+                        | 'eqp'
+                        | 'subBatchCode'
+                        | 'sampleNumber'
+                        | 'sampleType'
+                        | 'checkName'
+                        | 'checkTime'
+                        | 'reviewName'
+                        | 'reviewTime'
+                        | 'maxval'
+                        | 'minval'
+                        | 'avgval'
+                        | 'sdval'
+                        | 'cvval'
+                        | 'cpkval'
+                        | 'badval'
+                        | 'judge'
+                        | 'singlejudge'
+                        | 'verName'
+                        | 'verCode'
+                        | 'archDate'
+                        | 'del'
+                        | 'flag'
+                        | 'toMesTime'
+                        | 'chkDes'
+                        | 'checker'
+                        | 'judgeCode'
+                        | 'generateDetails'
+                        | 'checkType'
+            >
+        >;
+
+        /** matcheck list */
+        type MatcheckList = Api.Common.PaginatingQueryRecord<Matcheck>;
+
+        /** custom matcheck item */
+        type MatcheckCustomItem = {
+            itemCode: string;
+            itemName: string;
+        };
+    }
+}
diff --git a/ruoyi-plus-soybean/src/views/md/mat-type/index.vue b/ruoyi-plus-soybean/src/views/md/mat-type/index.vue
new file mode 100644
index 0000000..c3fdd16
--- /dev/null
+++ b/ruoyi-plus-soybean/src/views/md/mat-type/index.vue
@@ -0,0 +1,241 @@
+<script setup lang="tsx">
+import { ref } from 'vue';
+import { NDivider } from 'naive-ui';
+import { fetchBatchDeleteMatType, fetchGetMatTypeList } from '@/service/api/md/mat-type';
+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 MatTypeOperateDrawer from './modules/mat-type-operate-drawer.vue';
+import MatTypeSearch from './modules/mat-type-search.vue';
+
+defineOptions({
+  name: 'MatTypeList'
+});
+
+
+const appStore = useAppStore();
+const { download } = useDownload();
+const { hasAuth } = useAuth();
+
+const searchParams = ref<Api.Md.MatTypeSearchParams>({
+  pageNum: 1,
+  pageSize: 10,
+  code: null,
+  name: null,
+  enable: 1,
+  del: '0',
+  mesCode: null,
+  params: {}
+});
+
+const { columns, columnChecks, data, getData, getDataByPage, loading, mobilePagination, scrollX } =
+  useNaivePaginatedTable({
+  api: () => fetchGetMatTypeList(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: 'code',
+      title: '缂栫爜',
+      align: 'center',
+      minWidth: 120
+    },
+    {
+      key: 'name',
+      title: '鍚嶇О',
+      align: 'center',
+      minWidth: 120
+    },
+    {
+      key: 'des',
+      title: '鎻忚堪',
+      align: 'center',
+      minWidth: 120
+    },
+    {
+      key: 'enable',
+      title: '鍚敤',
+      align: 'center',
+      minWidth: 120,
+      render: row => (row.enable == 1 ? '鏄�' : '鍚�')
+    },
+    {
+      key: 'del',
+      title: '鍒犻櫎',
+      align: 'center',
+      minWidth: 120,
+      render: row => (row.del == 1 ? '鏄�' : '鍚�')
+    },
+    {
+      key: 'mesCode',
+      title: 'mes缂栫爜',
+      align: 'center',
+      minWidth: 120
+    },
+    {
+      key: 'createUserName',
+      title: '鍒涘缓浜�',
+      align: 'center',
+      minWidth: 120
+    },
+    {
+      key: 'createUserTime',
+      title: '鍒涘缓鏃堕棿',
+      align: 'center',
+      minWidth: 120
+    },
+    {
+      key: 'updateUserName',
+      title: '鏇存柊浜�',
+      align: 'center',
+      minWidth: 120
+    },
+    {
+      key: 'updateUserTime',
+      title: '鏇存柊鏃堕棿',
+      align: 'center',
+      minWidth: 120
+    },
+    {
+      key: 'operate',
+      title: $t('common.operate'),
+      align: 'center',
+      fixed: 'right',
+      width: 130,
+      render: row => {
+        const divider = () => {
+          if (!hasAuth('md:matType:edit') || !hasAuth('md:matType:remove')) {
+            return null;
+          }
+          return <NDivider vertical />;
+        };
+
+        const editBtn = () => {
+          if (!hasAuth('md:matType: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:matType: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 fetchBatchDeleteMatType(checkedRowKeys.value);
+  if (error) return;
+  onBatchDeleted();
+}
+
+async function handleDelete(id: CommonType.IdType) {
+  // request
+  const { error } = await fetchBatchDeleteMatType([id]);
+  if (error) return;
+  onDeleted();
+}
+
+function edit(id: CommonType.IdType) {
+  handleEdit(id);
+}
+
+function handleExport() {
+  download('/md/matType/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">
+    <MatTypeSearch 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:matType:add')"
+          :show-delete="hasAuth('md:matType:remove')"
+          :show-export="hasAuth('md:matType: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"
+      />
+      <MatTypeOperateDrawer
+        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/mat-type/modules/mat-type-operate-drawer.vue b/ruoyi-plus-soybean/src/views/md/mat-type/modules/mat-type-operate-drawer.vue
new file mode 100644
index 0000000..c46a545
--- /dev/null
+++ b/ruoyi-plus-soybean/src/views/md/mat-type/modules/mat-type-operate-drawer.vue
@@ -0,0 +1,171 @@
+<script setup lang="ts">
+import { computed, ref, watch } from 'vue';
+import { jsonClone } from '@sa/utils';
+import { fetchCreateMatType, fetchUpdateMatType } from '@/service/api/md/mat-type';
+import { useFormRules, useNaiveForm } from '@/hooks/common/form';
+import { $t } from '@/locales';
+
+defineOptions({
+  name: 'MatTypeOperateDrawer'
+});
+
+interface Props {
+  /** the type of operation */
+  operateType: NaiveUI.TableOperateType;
+  /** the edit row data */
+  rowData?: Api.Md.MatType | 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.Md.MatTypeOperateParams;
+
+const model = ref<Model>(createDefaultModel());
+
+function createDefaultModel(): Model {
+  return {
+      id: '',
+      gid: '',
+      code: '',
+      name: '',
+      des: '',
+      enable: null,
+      del: '',
+      mesCode: '',
+      createUserName: '',
+      createUserTime: null,
+      updateUserName: '',
+      updateUserTime: null
+  };
+}
+
+type RuleKey = Extract<
+  keyof Model,
+  | 'id'
+>;
+
+const rules: Record<RuleKey, App.Global.FormRule> = {
+  id: createRequiredRule('id涓嶈兘涓虹┖'),
+};
+
+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, gid, code, name, des, enable, del, mesCode, createUserName, createUserTime, updateUserName, updateUserTime } = model.value;
+
+  // request
+  if (props.operateType === 'add') {
+    const { error } = await fetchCreateMatType({ gid, code, name, des, enable, del, mesCode, createUserName, createUserTime, updateUserName, updateUserTime });
+    if (error) return;
+  }
+
+  if (props.operateType === 'edit') {
+    const { error } = await fetchUpdateMatType({ id, gid, code, name, des, enable, del, mesCode, createUserName, createUserTime, updateUserName, updateUserTime });
+    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="gid">
+          <NInput v-model:value="model.gid" placeholder="璇疯緭鍏ョ墿鏂欑粍澶栭敭" />
+        </NFormItem>
+        <NFormItem label="缂栫爜" path="code">
+          <NInput v-model:value="model.code" placeholder="璇疯緭鍏ョ紪鐮�" />
+        </NFormItem>
+        <NFormItem label="鍚嶇О" path="name">
+          <NInput v-model:value="model.name" placeholder="璇疯緭鍏ュ悕绉�" />
+        </NFormItem>
+        <NFormItem label="鎻忚堪" path="des">
+          <NInput v-model:value="model.des" 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="mes缂栫爜" path="mesCode">
+          <NInput v-model:value="model.mesCode" placeholder="璇疯緭鍏es缂栫爜" />
+        </NFormItem>
+        <NFormItem label="鍒涘缓浜�" path="createUserName">
+          <NInput v-model:value="model.createUserName" placeholder="璇疯緭鍏ュ垱寤轰汉" />
+        </NFormItem>
+        <NFormItem label="鍒涘缓鏃堕棿" path="createUserTime">
+          <NDatePicker
+            v-model:formatted-value="model.createUserTime"
+            type="datetime"
+            value-format="yyyy-MM-dd HH:mm:ss"
+            clearable
+          />
+        </NFormItem>
+        <NFormItem label="鏇存柊浜�" path="updateUserName">
+          <NInput v-model:value="model.updateUserName" placeholder="璇疯緭鍏ユ洿鏂颁汉" />
+        </NFormItem>
+        <NFormItem label="鏇存柊鏃堕棿" path="updateUserTime">
+          <NDatePicker
+            v-model:formatted-value="model.updateUserTime"
+            type="datetime"
+            value-format="yyyy-MM-dd HH:mm:ss"
+            clearable
+          />
+        </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/mat-type/modules/mat-type-search.vue b/ruoyi-plus-soybean/src/views/md/mat-type/modules/mat-type-search.vue
new file mode 100644
index 0000000..a55c423
--- /dev/null
+++ b/ruoyi-plus-soybean/src/views/md/mat-type/modules/mat-type-search.vue
@@ -0,0 +1,99 @@
+<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: 'MatTypeSearch'
+});
+
+interface Emits {
+  (e: 'search'): void;
+}
+
+const emit = defineEmits<Emits>();
+
+const { formRef, validate, restoreValidation } = useNaiveForm();
+
+const model = defineModel<Api.Md.MatTypeSearchParams>('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-mat-type-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="code" class="pr-24px">
+              <NInput v-model:value="model.code" placeholder="璇疯緭鍏ョ紪鐮�" />
+            </NFormItemGi>
+            <NFormItemGi span="24 s:12 m:6" label="鍚嶇О" label-width="auto" path="name" class="pr-24px">
+              <NInput v-model:value="model.name" placeholder="璇疯緭鍏ュ悕绉�" />
+            </NFormItemGi>
+            <NFormItemGi span="24 s:12 m:6" label="鍚敤" label-width="auto" path="enable" class="pr-24px">
+              <NSelect
+                v-model:value="model.enable"
+                placeholder="璇烽�夋嫨鍚敤"
+                :options="[
+                  { label: '鏄�', value: 1 },
+                  { label: '鍚�', value: 0 }
+                ]"
+                clearable
+              />
+            </NFormItemGi>
+            <NFormItemGi span="24 s:12 m:6" label="鍒犻櫎" label-width="auto" path="del" class="pr-24px">
+              <NSelect
+                v-model:value="model.del"
+                placeholder="璇烽�夋嫨鍒犻櫎"
+                :options="[
+                  { label: '鏄�', value: '1' },
+                  { label: '鍚�', value: '0' }
+                ]"
+                clearable
+              />
+            </NFormItemGi>
+            <NFormItemGi span="24 s:12 m:6" label="mes缂栫爜" label-width="auto" path="mesCode" class="pr-24px">
+              <NInput v-model:value="model.mesCode" placeholder="璇疯緭鍏es缂栫爜" />
+            </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/md/mat/index.vue b/ruoyi-plus-soybean/src/views/md/mat/index.vue
new file mode 100644
index 0000000..b292a2e
--- /dev/null
+++ b/ruoyi-plus-soybean/src/views/md/mat/index.vue
@@ -0,0 +1,283 @@
+<script setup lang="tsx">
+import { onMounted, ref } from 'vue';
+import { NDivider } from 'naive-ui';
+import { fetchBatchDeleteMat, fetchGetMatList } from '@/service/api/md/mat';
+import { fetchGetMatTypeList } from '@/service/api/md/mat-type';
+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 MatOperateDrawer from './modules/mat-operate-drawer.vue';
+import MatSearch from './modules/mat-search.vue';
+
+defineOptions({
+  name: 'MatList'
+});
+
+
+const appStore = useAppStore();
+const { download } = useDownload();
+const { hasAuth } = useAuth();
+
+const matTypeOptions = ref<CommonType.Option[]>([]);
+
+async function getMatTypeOptions() {
+  const { data: typeData } = await fetchGetMatTypeList();
+  if (typeData) {
+    matTypeOptions.value = typeData.rows.map(item => ({
+      label: item.name,
+      value: item.code
+    }));
+  }
+}
+
+onMounted(() => {
+  getMatTypeOptions();
+});
+
+const searchParams = ref<Api.Md.MatSearchParams>({
+  pageNum: 1,
+  pageSize: 10,
+  code: null,
+  name: null,
+  simpleName: null,
+  enable: 1,
+  del: '0',
+  tid: null,
+  params: {}
+});
+
+const { columns, columnChecks, data, getData, getDataByPage, loading, mobilePagination, scrollX } =
+  useNaivePaginatedTable({
+  api: () => fetchGetMatList(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: 'code',
+      title: '鐗╂枡浠g爜',
+      align: 'center',
+      minWidth: 120
+    },
+    {
+      key: 'name',
+      title: '鍏ㄧО',
+      align: 'center',
+      minWidth: 120
+    },
+    {
+      key: 'simpleName',
+      title: '绠�绉�',
+      align: 'center',
+      minWidth: 120
+    },
+    {
+      key: 'des',
+      title: '鎻忚堪',
+      align: 'center',
+      minWidth: 120
+    },
+    {
+      key: 'uid',
+      title: '鍗曚綅',
+      align: 'center',
+      minWidth: 120
+    },
+    {
+      key: 'lastUpdateTime',
+      title: '鏈�鍚庢洿鏂版椂闂�',
+      align: 'center',
+      minWidth: 120
+    },
+    {
+      key: 'enable',
+      title: '鍚敤',
+      align: 'center',
+      minWidth: 120,
+      render: row => (row.enable === 1 ? '鏄�' : '鍚�')
+    },
+    {
+      key: 'del',
+      title: '鍒犻櫎',
+      align: 'center',
+      minWidth: 120,
+      render: row => (row.del === '1' ? '鏄�' : '鍚�')
+    },
+    {
+      key: 'tid',
+      title: '鐗╂枡绫诲瀷',
+      align: 'center',
+      minWidth: 120,
+      render: row => {
+        if (row.tid) {
+          return matTypeOptions.value.find(item => String(item.value) === String(row.tid))?.label || row.tid;
+        }
+        return '';
+      }
+    },
+    {
+      key: 'createUserName',
+      title: '鍒涘缓浜�',
+      align: 'center',
+      minWidth: 120
+    },
+    {
+      key: 'createUserTime',
+      title: '鍒涘缓鏃堕棿',
+      align: 'center',
+      minWidth: 120
+    },
+    {
+      key: 'updateUserName',
+      title: '鏇存柊浜�',
+      align: 'center',
+      minWidth: 120
+    },
+    {
+      key: 'updateUserTime',
+      title: '鏇存柊鏃堕棿',
+      align: 'center',
+      minWidth: 120
+    },
+    {
+      key: 'operate',
+      title: $t('common.operate'),
+      align: 'center',
+      fixed: 'right',
+      width: 130,
+      render: row => {
+        const divider = () => {
+          if (!hasAuth('md:mat:edit') || !hasAuth('md:mat:remove')) {
+            return null;
+          }
+          return <NDivider vertical />;
+        };
+
+        const editBtn = () => {
+          if (!hasAuth('md:mat: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:mat: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 fetchBatchDeleteMat(checkedRowKeys.value);
+  if (error) return;
+  onBatchDeleted();
+}
+
+async function handleDelete(id: CommonType.IdType) {
+  // request
+  const { error } = await fetchBatchDeleteMat([id]);
+  if (error) return;
+  onDeleted();
+}
+
+function edit(id: CommonType.IdType) {
+  handleEdit(id);
+}
+
+function handleExport() {
+  download('/md/mat/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">
+    <MatSearch 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:mat:add')"
+          :show-delete="hasAuth('md:mat:remove')"
+          :show-export="hasAuth('md:mat: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"
+      />
+      <MatOperateDrawer
+        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/mat/modules/mat-operate-drawer.vue b/ruoyi-plus-soybean/src/views/md/mat/modules/mat-operate-drawer.vue
new file mode 100644
index 0000000..4977e2b
--- /dev/null
+++ b/ruoyi-plus-soybean/src/views/md/mat/modules/mat-operate-drawer.vue
@@ -0,0 +1,184 @@
+<script setup lang="ts">
+import { computed, ref, watch } from 'vue';
+import { jsonClone } from '@sa/utils';
+import { fetchCreateMat, fetchUpdateMat } from '@/service/api/md/mat';
+import { useFormRules, useNaiveForm } from '@/hooks/common/form';
+import { $t } from '@/locales';
+
+defineOptions({
+  name: 'MatOperateDrawer'
+});
+
+interface Props {
+  /** the type of operation */
+  operateType: NaiveUI.TableOperateType;
+  /** the edit row data */
+  rowData?: Api.Md.Mat | 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.Md.MatOperateParams;
+
+const model = ref<Model>(createDefaultModel());
+
+function createDefaultModel(): Model {
+  return {
+      id: '',
+      code: '',
+      name: '',
+      simpleName: '',
+      des: '',
+      uid: '',
+      lastUpdateTime: null,
+      enable: null,
+      del: '',
+      tid: '',
+      createUserName: '',
+      createUserTime: null,
+      updateUserName: '',
+      updateUserTime: null
+  };
+}
+
+type RuleKey = Extract<
+  keyof Model,
+  | 'id'
+>;
+
+const rules: Record<RuleKey, App.Global.FormRule> = {
+  id: createRequiredRule('id涓嶈兘涓虹┖'),
+};
+
+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, code, name, simpleName, des, uid, lastUpdateTime, enable, del, tid, createUserName, createUserTime, updateUserName, updateUserTime } = model.value;
+
+  // request
+  if (props.operateType === 'add') {
+    const { error } = await fetchCreateMat({ code, name, simpleName, des, uid, lastUpdateTime, enable, del, tid, createUserName, createUserTime, updateUserName, updateUserTime });
+    if (error) return;
+  }
+
+  if (props.operateType === 'edit') {
+    const { error } = await fetchUpdateMat({ id, code, name, simpleName, des, uid, lastUpdateTime, enable, del, tid, createUserName, createUserTime, updateUserName, updateUserTime });
+    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="code">
+          <NInput v-model:value="model.code" placeholder="璇疯緭鍏ョ墿鏂欎唬鐮�" />
+        </NFormItem>
+        <NFormItem label="鍏ㄧО" path="name">
+          <NInput v-model:value="model.name" placeholder="璇疯緭鍏ュ叏绉�" />
+        </NFormItem>
+        <NFormItem label="绠�绉�" path="simpleName">
+          <NInput v-model:value="model.simpleName" placeholder="璇疯緭鍏ョ畝绉�" />
+        </NFormItem>
+        <NFormItem label="鎻忚堪" path="des">
+          <NInput v-model:value="model.des" placeholder="璇疯緭鍏ユ弿杩�" />
+        </NFormItem>
+        <NFormItem label="鍗曚綅" path="uid">
+          <NInput v-model:value="model.uid" placeholder="璇疯緭鍏ュ崟浣�" />
+        </NFormItem>
+        <NFormItem label="鏈�鍚庢洿鏂版椂闂�" path="lastUpdateTime">
+          <NDatePicker
+            v-model:formatted-value="model.lastUpdateTime"
+            type="datetime"
+            value-format="yyyy-MM-dd HH:mm:ss"
+            clearable
+          />
+        </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="tid">
+          <NInput v-model:value="model.tid" placeholder="璇疯緭鍏ョ墿鏂欑被鍨�" />
+        </NFormItem>
+        <NFormItem label="鍒涘缓浜�" path="createUserName">
+          <NInput v-model:value="model.createUserName" placeholder="璇疯緭鍏ュ垱寤轰汉" />
+        </NFormItem>
+        <NFormItem label="鍒涘缓鏃堕棿" path="createUserTime">
+          <NDatePicker
+            v-model:formatted-value="model.createUserTime"
+            type="datetime"
+            value-format="yyyy-MM-dd HH:mm:ss"
+            clearable
+          />
+        </NFormItem>
+        <NFormItem label="鏇存柊浜�" path="updateUserName">
+          <NInput v-model:value="model.updateUserName" placeholder="璇疯緭鍏ユ洿鏂颁汉" />
+        </NFormItem>
+        <NFormItem label="鏇存柊鏃堕棿" path="updateUserTime">
+          <NDatePicker
+            v-model:formatted-value="model.updateUserTime"
+            type="datetime"
+            value-format="yyyy-MM-dd HH:mm:ss"
+            clearable
+          />
+        </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/mat/modules/mat-search.vue b/ruoyi-plus-soybean/src/views/md/mat/modules/mat-search.vue
new file mode 100644
index 0000000..36f9d22
--- /dev/null
+++ b/ruoyi-plus-soybean/src/views/md/mat/modules/mat-search.vue
@@ -0,0 +1,119 @@
+<script setup lang="ts">
+import { onMounted, ref, toRaw } from 'vue';
+import { jsonClone } from '@sa/utils';
+import { fetchGetMatTypeList } from '@/service/api/md/mat-type';
+import { useNaiveForm } from '@/hooks/common/form';
+import { $t } from '@/locales';
+
+defineOptions({
+  name: 'MatSearch'
+});
+
+interface Emits {
+  (e: 'search'): void;
+}
+
+const emit = defineEmits<Emits>();
+
+const { formRef, validate, restoreValidation } = useNaiveForm();
+
+const model = defineModel<Api.Md.MatSearchParams>('model', { required: true });
+
+const defaultModel = jsonClone(toRaw(model.value));
+
+const matTypeOptions = ref<CommonType.Option[]>([]);
+
+async function getMatTypeOptions() {
+  const { data } = await fetchGetMatTypeList();
+  if (data) {
+    matTypeOptions.value = data.rows.map(item => ({
+      label: item.name,
+      value: item.code
+    }));
+  }
+}
+
+onMounted(() => {
+  getMatTypeOptions();
+});
+
+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-mat-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="code" class="pr-24px">
+              <NInput v-model:value="model.code" placeholder="璇疯緭鍏ョ墿鏂欎唬鐮�" />
+            </NFormItemGi>
+            <NFormItemGi span="24 s:12 m:6" label="鍏ㄧО" label-width="auto" path="name" class="pr-24px">
+              <NInput v-model:value="model.name" placeholder="璇疯緭鍏ュ叏绉�" />
+            </NFormItemGi>
+            <NFormItemGi span="24 s:12 m:6" label="绠�绉�" label-width="auto" path="simpleName" class="pr-24px">
+              <NInput v-model:value="model.simpleName" placeholder="璇疯緭鍏ョ畝绉�" />
+            </NFormItemGi>
+            <NFormItemGi span="24 s:12 m:6" label="鍚敤" label-width="auto" path="enable" class="pr-24px">
+              <NSelect
+                v-model:value="model.enable"
+                placeholder="璇烽�夋嫨鍚敤"
+                :options="[
+                  { label: '鏄�', value: 1 },
+                  { label: '鍚�', value: 0 }
+                ]"
+                clearable
+              />
+            </NFormItemGi>
+            <NFormItemGi span="24 s:12 m:6" label="鍒犻櫎" label-width="auto" path="del" class="pr-24px">
+              <NSelect
+                v-model:value="model.del"
+                placeholder="璇烽�夋嫨鍒犻櫎"
+                :options="[
+                  { label: '鏄�', value: '1' },
+                  { label: '鍚�', value: '0' }
+                ]"
+                clearable
+              />
+            </NFormItemGi>
+            <NFormItemGi span="24 s:12 m:6" label="鐗╂枡绫诲瀷" label-width="auto" path="tid" class="pr-24px">
+              <NSelect v-model:value="model.tid" placeholder="璇烽�夋嫨鐗╂枡绫诲瀷" :options="matTypeOptions" 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/judge-details/modules/judge-details-sub-table.vue b/ruoyi-plus-soybean/src/views/qm/judge-details/modules/judge-details-sub-table.vue
new file mode 100644
index 0000000..67101a2
--- /dev/null
+++ b/ruoyi-plus-soybean/src/views/qm/judge-details/modules/judge-details-sub-table.vue
@@ -0,0 +1,414 @@
+<script setup lang="tsx">
+import { computed, onBeforeUnmount, onMounted, ref, watch } from 'vue';
+import type { DataTableRowKey } from 'naive-ui';
+import { NDivider } from 'naive-ui';
+import { jsonClone } from '@sa/utils';
+import { fetchBatchDeleteJudgeDetails, fetchGetJudgeDetailsTree } from '@/service/api/qm/judge-details';
+import { useAuth } from '@/hooks/business/auth';
+import { useNaiveTable } from '@/hooks/common/table';
+import { $t } from '@/locales';
+import ButtonIcon from '@/components/custom/button-icon.vue';
+import JudgeDetailsOperateDrawer from './judge-details-operate-drawer.vue';
+
+defineOptions({
+  name: 'JudgeDetailsSubTable'
+});
+
+interface Props {
+  judgeId?: CommonType.IdType | null;
+}
+
+const props = withDefaults(defineProps<Props>(), {
+  judgeId: null
+});
+
+interface JudgeDetailsSubRow extends Api.Qm.JudgeDetails {
+  children?: JudgeDetailsSubRow[];
+}
+
+const { hasAuth } = useAuth();
+
+const checkedRowKeys = ref<CommonType.IdType[]>([]);
+const drawerVisible = ref(false);
+const operateType = ref<NaiveUI.TableOperateType>('add');
+const editingData = ref<Api.Qm.JudgeDetails | null>(null);
+const showFullscreen = ref(false);
+const fullscreenStyle = ref<Record<string, string>>({});
+
+function handleAdd() {
+  if (!props.judgeId) {
+    window.$message?.warning('璇峰厛閫夋嫨鍒ゅ畾渚濇嵁');
+    return;
+  }
+  operateType.value = 'add';
+  editingData.value = { judgeId: props.judgeId } as any;
+  drawerVisible.value = true;
+}
+
+function handleEdit(row: JudgeDetailsSubRow) {
+  operateType.value = 'edit';
+  editingData.value = jsonClone(row);
+  drawerVisible.value = true;
+}
+
+async function handleBatchDelete() {
+  if (checkedRowKeys.value.length === 0) return;
+  const { error } = await fetchBatchDeleteJudgeDetails(checkedRowKeys.value);
+  if (error) return;
+  window.$message?.success($t('common.deleteSuccess'));
+  checkedRowKeys.value = [];
+  await getData();
+}
+
+async function handleDelete(id: CommonType.IdType) {
+  const { error } = await fetchBatchDeleteJudgeDetails([id]);
+  if (error) return;
+  window.$message?.success($t('common.deleteSuccess'));
+  await getData();
+}
+
+const {
+  columns,
+  columnChecks,
+  data: rows,
+  loading,
+  getData,
+  scrollX
+} = useNaiveTable<Api.Common.PaginatingQueryRecord<Api.Qm.JudgeDetails>, Api.Qm.JudgeDetails>({
+  api: async () => {
+    if (!props.judgeId) return { rows: [], total: 0, pageNum: 1 } as any;
+    const { data } = await fetchGetJudgeDetailsTree({
+      pageNum: 1,
+      pageSize: 9999,
+      judgeId: props.judgeId
+    });
+    return data;
+  },
+  columns: () => [
+    {
+      type: 'selection',
+      align: 'center',
+      width: 48
+    },
+    {
+      key: 'index',
+      title: $t('common.index'),
+      align: 'center',
+      width: 56,
+      render: (_: any, index: number) => index + 1
+    },
+    {
+      key: 'itemName',
+      title: '鍒ゅ畾椤�',
+      align: 'left',
+      width: 250,
+      tree: true
+    } as any,
+    {
+      key: 'value3',
+      title: '鏍囧噯鍊�',
+      align: 'center',
+      width: 100
+    },
+    {
+      key: 'value1',
+      title: '涓嬮檺鍊�',
+      align: 'center',
+      width: 100
+    },
+    {
+      key: 'value2',
+      title: '涓婇檺鍊�',
+      align: 'center',
+      width: 100
+    },
+    {
+      key: 'cls',
+      title: '绾у埆',
+      align: 'center',
+      width: 80
+    },
+    {
+      key: 'stdscore',
+      title: '鍒嗗��',
+      align: 'center',
+      minWidth: 150
+    },
+    {
+      key: 'decisionDes',
+      title: '澶囨敞',
+      align: 'center',
+      minWidth: 150
+    },
+    {
+      key: 'updateUser',
+      title: '淇敼浜�',
+      align: 'center',
+      minWidth: 150
+    },
+    {
+      key: 'operate',
+      title: $t('common.operate'),
+      fixed: 'right',
+      align: 'center',
+      width: 130,
+      render: (row: any) => {
+        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={() => handleEdit(row)}
+            />
+          );
+        };
+
+        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>
+        );
+      }
+    }
+  ],
+  transform: response => {
+    return response?.rows || [];
+  }
+});
+
+const onFullscreenKeydown = (event: KeyboardEvent) => {
+  if (event.key === 'Escape') {
+    showFullscreen.value = false;
+  }
+};
+
+const cardTitle = computed(() => `鍒ゅ畾鏄庣粏缁存姢`);
+
+watch(
+  () => props.judgeId,
+  async () => {
+    checkedRowKeys.value = [];
+    await getData();
+  },
+  { immediate: true }
+);
+
+watch(
+  showFullscreen,
+  visible => {
+    if (visible) {
+      updateFullscreenStyle();
+      window.addEventListener('keydown', onFullscreenKeydown);
+      window.addEventListener('resize', updateFullscreenStyle);
+      window.addEventListener('scroll', updateFullscreenStyle, true);
+      return;
+    }
+    window.removeEventListener('keydown', onFullscreenKeydown);
+    window.removeEventListener('resize', updateFullscreenStyle);
+    window.removeEventListener('scroll', updateFullscreenStyle, true);
+  },
+  { immediate: true }
+);
+
+onBeforeUnmount(() => {
+  window.removeEventListener('keydown', onFullscreenKeydown);
+  window.removeEventListener('resize', updateFullscreenStyle);
+  window.removeEventListener('scroll', updateFullscreenStyle, true);
+});
+
+onMounted(() => {
+  updateFullscreenStyle();
+});
+
+function updateFullscreenStyle() {
+  const container = document.querySelector<HTMLElement>('.judge-content-area');
+  if (!container) {
+    fullscreenStyle.value = {
+      position: 'fixed',
+      inset: '0',
+      zIndex: '20'
+    };
+    return;
+  }
+  const rect = container.getBoundingClientRect();
+  fullscreenStyle.value = {
+    position: 'fixed',
+    left: `${rect.left}px`,
+    top: `${rect.top}px`,
+    width: `${rect.width}px`,
+    height: `${rect.height}px`,
+    zIndex: '20'
+  };
+}
+
+function rowKey(row: JudgeDetailsSubRow): DataTableRowKey {
+  return String(row.id);
+}
+</script>
+
+<template>
+  <div class="flex-col-stretch">
+    <NCard
+      :title="cardTitle"
+      :bordered="false"
+      size="small"
+      class="flex-col-stretch card-wrapper flex-1-hidden"
+      :content-style="{ flex: 1, overflow: 'hidden', display: 'flex', flexDirection: 'column' }"
+    >
+      <template #header-extra>
+        <NSpace align="center">
+          <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="false"
+            @add="handleAdd"
+            @delete="handleBatchDelete"
+            @refresh="getData"
+          />
+          <NButton size="small" @click="showFullscreen = true">
+            <template #icon>
+              <icon-mdi-fullscreen class="text-icon" />
+            </template>
+            鍏ㄥ睆
+          </NButton>
+        </NSpace>
+      </template>
+
+      <NSpin :show="loading" class="h-full" content-class="h-full flex-col-stretch flex-1-hidden">
+        <div v-if="!props.judgeId" class="h-full flex-center text-gray-400">璇风偣鍑讳笂鏂硅〃鏍艰鏌ョ湅鏄庣粏</div>
+        <NDataTable
+          v-else
+          v-model:checked-row-keys="checkedRowKeys"
+          :columns="columns"
+          :data="rows"
+          size="small"
+          flex-height
+          children-key="children"
+          :row-key="rowKey"
+          default-expand-all
+          striped
+          class="flex-1-hidden"
+        />
+      </NSpin>
+    </NCard>
+
+    <Teleport to="body">
+      <div v-if="showFullscreen" class="fullscreen-mask" :style="fullscreenStyle" @click.self="showFullscreen = false">
+        <NCard
+          :title="cardTitle"
+          :bordered="false"
+          size="small"
+          class="fullscreen-card flex-col-stretch"
+          :content-style="{ flex: 1, overflow: 'hidden', display: 'flex', flexDirection: 'column' }"
+        >
+          <template #header-extra>
+            <NSpace align="center">
+              <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="false"
+                @add="handleAdd"
+                @delete="handleBatchDelete"
+                @refresh="getData"
+              />
+              <NButton size="small" ghost type="error" @click="showFullscreen = false">
+                <template #icon>
+                  <icon-mdi-close class="text-icon" />
+                </template>
+                鍏抽棴
+              </NButton>
+            </NSpace>
+          </template>
+
+          <NSpin :show="loading" class="h-full" content-class="h-full">
+            <div v-if="!props.judgeId" class="h-full flex-center text-gray-400">璇风偣鍑讳笂鏂硅〃鏍艰鏌ョ湅鏄庣粏</div>
+            <NDataTable
+              v-else
+              v-model:checked-row-keys="checkedRowKeys"
+              :columns="columns"
+              :data="rows"
+              size="small"
+              flex-height
+              children-key="children"
+              :row-key="rowKey"
+              striped
+              class="fullscreen-table"
+            />
+          </NSpin>
+        </NCard>
+      </div>
+    </Teleport>
+
+    <JudgeDetailsOperateDrawer
+      v-model:visible="drawerVisible"
+      :operate-type="operateType"
+      :row-data="editingData"
+      @submitted="getData"
+    />
+  </div>
+</template>
+
+<style scoped>
+:deep(.n-card__content) {
+  padding: 8px 12px;
+}
+
+:deep(.n-data-table-th),
+:deep(.n-data-table-td) {
+  padding: 4px 6px;
+}
+
+.fullscreen-mask {
+  position: absolute;
+  inset: 0;
+  z-index: 20;
+  background: #fff;
+  padding: 0;
+  display: flex;
+}
+
+.fullscreen-card {
+  width: 100%;
+  height: 100%;
+}
+
+.fullscreen-table {
+  height: 100%;
+}
+</style>
diff --git a/ruoyi-plus-soybean/src/views/qm/matcheck/index.vue b/ruoyi-plus-soybean/src/views/qm/matcheck/index.vue
new file mode 100644
index 0000000..6203c03
--- /dev/null
+++ b/ruoyi-plus-soybean/src/views/qm/matcheck/index.vue
@@ -0,0 +1,268 @@
+<script setup lang="tsx">
+import { ref } from 'vue';
+import { NDivider } from 'naive-ui';
+import { fetchBatchDeleteMatcheck, fetchGetQmMatcheckList } from '@/service/api/qm/matcheck';
+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 MatcheckOperateDrawer from './modules/matcheck-operate-drawer.vue';
+import { useRoute } from 'vue-router';
+
+defineOptions({
+  name: 'MatcheckList'
+});
+
+
+const appStore = useAppStore();
+const { download } = useDownload();
+const { hasAuth } = useAuth();
+
+const route = useRoute();
+function getSingleQueryValue(query: string | string[] | null | undefined) {
+  if (Array.isArray(query)) return query[0] || '';
+  return query || '';
+}
+
+const judgeCode = getSingleQueryValue(route.query.judgeCode);
+const batchCode = getSingleQueryValue(route.query.batchCode);
+const matCode = getSingleQueryValue(route.query.matCode);
+
+const searchParams = ref<Api.Qm.MatcheckSearchParams>({
+  pageNum: 1,
+  pageSize: 10,
+  batchCode,
+  judgeCode,
+  matCode,
+  checkName: null,
+  checkTime: null,
+  reviewName: null,
+  reviewTiem: null,
+  params: {}
+});
+
+const { columns, columnChecks, data, getData, getDataByPage, loading, mobilePagination, scrollX } =
+  useNaivePaginatedTable({
+  api: () => fetchGetQmMatcheckList(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: 'itemName',
+      title: '妫�楠岄」',
+      align: 'center',
+      minWidth: 120
+    },
+    {
+      key: 'maxval',
+      title: '鏈�澶у��/鐑熶笣璐ㄩ噺(鍚湯鐜�)',
+      align: 'center',
+      minWidth: 170
+    },
+    {
+      key: 'minval',
+      title: '鏈�灏忓��/鐑熸湯璐ㄩ噺(鍚湯鐜�)',
+      align: 'center',
+      minWidth: 170
+    },
+    {
+      key: 'avgval',
+      title: '骞冲潎鍊�/鍚湯鐜�',
+      align: 'center',
+      minWidth: 130
+    },
+    {
+      key: 'sdval',
+      title: 'SD鍊�',
+      align: 'center',
+      minWidth: 100
+    },
+    {
+      key: 'cvval',
+      title: 'CV鍊�',
+      align: 'center',
+      minWidth: 100
+    },
+    {
+      key: 'cpkval',
+      title: 'CPK鍊�',
+      align: 'center',
+      minWidth: 100
+    },
+    {
+      key: 'badval',
+      title: '瓒呮爣鏁�',
+      align: 'center',
+      minWidth: 100
+    },
+    {
+      key: 'checker',
+      title: '妫�楠屽憳',
+      align: 'center',
+      minWidth: 100
+    },
+    {
+      key: 'checkTime',
+      title: '妫�楠屾椂闂�',
+      align: 'center',
+      minWidth: 120
+    },
+    {
+      key: 'reviewName',
+      title: '澶嶆鍛�',
+      align: 'center',
+      minWidth: 100
+    },
+    {
+      key: 'reviewTime',
+      title: '澶嶆鏃堕棿',
+      align: 'center',
+      minWidth: 120
+    },
+    {
+      key: 'chkDes',
+      title: '鎻忚堪',
+      align: 'center',
+      minWidth: 120
+    },
+    {
+      key: 'operate',
+      title: $t('common.operate'),
+      align: 'center',
+      fixed: 'right',
+      width: 130,
+      render: row => {
+        const divider = () => {
+          if (!hasAuth('qm:matcheck:edit') || !hasAuth('qm:matcheck:remove')) {
+            return null;
+          }
+          return <NDivider vertical />;
+        };
+
+        const editBtn = () => {
+          if (!hasAuth('qm:matcheck: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:matcheck: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 fetchBatchDeleteMatcheck(checkedRowKeys.value);
+  if (error) return;
+  onBatchDeleted();
+}
+
+async function handleDelete(id: CommonType.IdType) {
+  // request
+  const { error } = await fetchBatchDeleteMatcheck([id]);
+  if (error) return;
+  onDeleted();
+}
+
+function edit(id: CommonType.IdType) {
+  handleEdit(id);
+}
+
+function handleExport() {
+  download('/qm/matcheck/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">
+    <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:matcheck:add')"
+          :show-delete="hasAuth('qm:matcheck:remove')"
+          :show-export="hasAuth('qm:matcheck: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"
+      />
+      <MatcheckOperateDrawer
+        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/matcheck/modules/matcheck-operate-drawer.vue b/ruoyi-plus-soybean/src/views/qm/matcheck/modules/matcheck-operate-drawer.vue
new file mode 100644
index 0000000..8115cdf
--- /dev/null
+++ b/ruoyi-plus-soybean/src/views/qm/matcheck/modules/matcheck-operate-drawer.vue
@@ -0,0 +1,266 @@
+<script setup lang="ts">
+import { computed, onMounted, ref, watch } from 'vue';
+import { jsonClone } from '@sa/utils';
+import { fetchCreateMatcheck, fetchUpdateMatcheck, fetchCheckItemList } from '@/service/api/qm/matcheck';
+import { useFormRules, useNaiveForm } from '@/hooks/common/form';
+import { $t } from '@/locales';
+import { useRoute } from 'vue-router';
+
+defineOptions({
+  name: 'MatcheckOperateDrawer'
+});
+
+interface Props {
+  /** the type of operation */
+  operateType: NaiveUI.TableOperateType;
+  /** the edit row data */
+  rowData?: Api.Qm.Matcheck | 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 route = useRoute();
+
+// 妫�楠岄」閫夐」
+const checkItemOptions = ref<CommonType.Option[]>([]);
+
+// 鑾峰彇妫�楠岄」閫夐」
+async function getCheckItemOptions() {
+  const judgeCode = Array.isArray(route.query.judgeCode) ? route.query.judgeCode[0] : route.query.judgeCode;
+  if (!judgeCode) {
+    checkItemOptions.value = [];
+    return;
+  }
+
+  const { data, error } = await fetchCheckItemList({ judgeId: judgeCode });
+  if (!error && data) {
+    checkItemOptions.value = data.map(item => ({
+      label: item.itemName, // 鎺ュ彛杩斿洖鐨勫瓧娈垫槸 item_name
+      value: item.itemCode // 鎺ュ彛杩斿洖鐨勫瓧娈垫槸 item_code
+    }));
+  }
+}
+
+const title = computed(() => {
+  const titles: Record<NaiveUI.TableOperateType, string> = {
+    add: '鏂板鏉愭枡妫�楠岀粺璁�',
+    edit: '缂栬緫鏉愭枡妫�楠岀粺璁�'
+  };
+  return titles[props.operateType];
+});
+
+type Model = Api.Qm.MatcheckOperateParams;
+
+const model = ref<Model>(createDefaultModel());
+
+const generateDetailsChecked = computed<string | number | boolean | undefined>({
+  get() {
+    return model.value.generateDetails ?? '0';
+  },
+  set(value) {
+    model.value.generateDetails = String(value ?? '0');
+  }
+});
+
+
+function createDefaultModel(): Model {
+  // 鑾峰彇褰撳墠鏃ユ湡锛屾牸寮忎负 yyyy-MM-dd
+  const today = new Date().toISOString().split('T')[0];
+  const matCode = (route.query.matCode as string) || '';
+
+  return {
+      id: '',
+      pid: '',
+      batchCode: route.query.batchCode as string || '',
+      matCode,
+      instrumentCode: '',
+      techReq: '',
+      checkStd: '',
+      testEnv: '',
+      itemCode: '',
+      subBatchCode: '',
+      sampleNumber: null,
+      sampleType: null,
+      checkName: '',
+      checkTime: today,
+      reviewName: '',
+      reviewTime: null,
+      maxval: null,
+      minval: null,
+      avgval: null,
+      sdval: null,
+      cvval: null,
+      cpkval: null,
+      badval: null,
+      judge: '',
+      singlejudge: '',
+      verName: '',
+      verCode: '',
+      archDate: '',
+      del: null,
+      flag: '',
+      toMesTime: null,
+      chkDes: '',
+      generateDetails: '0'
+  };
+}
+
+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));
+    model.value.generateDetails = model.value.generateDetails ?? '0';
+  }
+}
+
+function closeDrawer() {
+  visible.value = false;
+}
+
+async function handleSubmit() {
+  await validate();
+
+  const { id, pid, batchCode, matCode, instrumentCode, techReq, checkStd, testEnv, itemCode, subBatchCode, sampleNumber, sampleType, checkName, checkTime, reviewName, reviewTime, maxval, minval, avgval, sdval, cvval, cpkval, badval, judge, singlejudge, verName, verCode, archDate, del, flag, toMesTime, chkDes, generateDetails } = model.value;
+
+  // request
+  if (props.operateType === 'add') {
+    const { error } = await fetchCreateMatcheck({ id, pid, batchCode, matCode, instrumentCode, techReq, checkStd, testEnv, itemCode, subBatchCode, sampleNumber, sampleType, checkName, checkTime, reviewName, reviewTime, maxval, minval, avgval, sdval, cvval, cpkval, badval, judge, singlejudge, verName, verCode, archDate, del, flag, toMesTime, chkDes, generateDetails });
+    if (error) return;
+  }
+
+  if (props.operateType === 'edit') {
+    const { error } = await fetchUpdateMatcheck({ id, pid, batchCode, matCode, instrumentCode, techReq, checkStd, testEnv, itemCode, subBatchCode, sampleNumber, sampleType, checkName, checkTime, reviewName, reviewTime, maxval, minval, avgval, sdval, cvval, cpkval, badval, judge, singlejudge, verName, verCode, archDate, del, flag, toMesTime, chkDes, generateDetails });
+    if (error) return;
+  }
+
+  window.$message?.success($t('common.updateSuccess'));
+  closeDrawer();
+  emit('submitted');
+}
+
+onMounted(() => {
+  getCheckItemOptions();
+});
+
+watch(visible, () => {
+  if (visible.value) {
+    handleUpdateModelWhenEdit();
+    restoreValidation();
+    getCheckItemOptions();
+  }
+});
+</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" label-placement="left" :label-width="180">
+        <NFormItem label="鎵规鍙�" path="batchCode">
+          <NInput v-model:value="model.batchCode" placeholder="鎵规鍙�" :disabled="true" />
+        </NFormItem>
+        <NFormItem label="妫�楠岄」" path="itemCode">
+          <NSelect
+            v-model:value="model.itemCode"
+            placeholder="璇烽�夋嫨妫�楠岄」"
+            :options="checkItemOptions"
+            clearable
+            filterable
+          />
+        </NFormItem>
+        <NFormItem label="鐩樺彿" path="subBatchCode">
+          <NInput v-model:value="model.subBatchCode" placeholder="璇疯緭鍏ョ洏鍙�" />
+        </NFormItem>
+        <NFormItem path="maxval">
+          <template #label>
+            鏈�澶у��/<br>
+            鐑熶笣璐ㄩ噺/<br>
+            (鍚湯鐜�)
+          </template>
+          <NInputNumber v-model:value="model.maxval" placeholder="璇疯緭鍏ユ渶澶у��" class="w-full" />
+        </NFormItem>
+        <NFormItem path="minval">
+          <template #label>
+            鏈�灏忓��/<br>
+            鐑熸湯璐ㄩ噺/<br>
+            (鍚湯鐜�)
+          </template>
+          <NInputNumber v-model:value="model.minval" placeholder="璇疯緭鍏ユ渶灏忓��" class="w-full" />
+        </NFormItem>
+        <NFormItem path="avgval">
+          <template #label>
+            骞冲潎鍊�/<br>
+            鍚湯鐜�
+          </template>
+          <NInputNumber v-model:value="model.avgval" placeholder="璇疯緭鍏ュ钩鍧囧��" class="w-full" />
+        </NFormItem>
+        <NFormItem label="SD鍊�" path="sdval">
+          <NInputNumber v-model:value="model.sdval" placeholder="璇疯緭鍏D鍊�" class="w-full" />
+        </NFormItem>
+        <NFormItem label="CV鍊�" path="cvval">
+          <NInputNumber v-model:value="model.cvval" placeholder="璇疯緭鍏V鍊�" class="w-full" />
+        </NFormItem>
+        <NFormItem label="CPK鍊�" path="cpkval">
+          <NInputNumber v-model:value="model.cpkval" placeholder="璇疯緭鍏PK鍊�" class="w-full" />
+        </NFormItem>
+        <NFormItem label="瓒呮爣鏁�" path="badval">
+          <NInputNumber v-model:value="model.badval" placeholder="璇疯緭鍏ヨ秴鏍囨暟" class="w-full" />
+        </NFormItem>
+        <NFormItem label="妫�楠屾棩鏈�" path="checkTime">
+          <NDatePicker
+            v-model:formatted-value="model.checkTime"
+            type="date"
+            value-format="yyyy-MM-dd"
+            clearable
+          />
+        </NFormItem>
+        <NFormItem label="鍒ゅ畾" path="judge">
+          <NInput v-model:value="model.judge" placeholder="璇疯緭鍏ュ垽瀹�" />
+        </NFormItem>
+        <NFormItem label="鍗曢」鍒ゅ畾" path="singlejudge">
+          <NInput v-model:value="model.singlejudge" placeholder="璇疯緭鍏ュ崟椤瑰垽瀹�" />
+        </NFormItem>
+        <NFormItem label="鎻忚堪" path="chkDes">
+          <NInput v-model:value="model.chkDes" placeholder="璇疯緭鍏ユ弿杩�" />
+        </NFormItem>
+        <NFormItem label="鐢熸垚鏄庣粏" path="generateDetails">
+          <NCheckbox v-model:checked="generateDetailsChecked" checked-value="1" unchecked-value="0" />
+        </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>
+.n-form-item-label {
+  white-space: pre-line;
+  line-height: 1.4;
+}
+</style>
diff --git a/ruoyi-plus-soybean/src/views/qm/matcheck/modules/matcheck-search.vue b/ruoyi-plus-soybean/src/views/qm/matcheck/modules/matcheck-search.vue
new file mode 100644
index 0000000..bc73db1
--- /dev/null
+++ b/ruoyi-plus-soybean/src/views/qm/matcheck/modules/matcheck-search.vue
@@ -0,0 +1,93 @@
+<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: 'MatcheckSearch'
+});
+
+interface Emits {
+  (e: 'search'): void;
+}
+
+const emit = defineEmits<Emits>();
+
+const { formRef, validate, restoreValidation } = useNaiveForm();
+
+const model = defineModel<Api.Qm.MatcheckSearchParams>('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-matcheck-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="matCode" class="pr-24px">
+              <NInput v-model:value="model.matCode" placeholder="璇疯緭鍏ョ墝鍙�" />
+            </NFormItemGi>
+            <NFormItemGi span="24 s:12 m:6" label="妫�楠屽憳" label-width="auto" path="checkName" class="pr-24px">
+              <NInput v-model:value="model.checkName" placeholder="璇疯緭鍏ユ楠屽憳" />
+            </NFormItemGi>
+            <NFormItemGi span="24 s:12 m:6" label="妫�楠屾椂闂�" label-width="auto" path="checkTime" class="pr-24px">
+              <NDatePicker
+                v-model:formatted-value="model.checkTime"
+                type="datetime"
+                value-format="yyyy-MM-dd HH:mm:ss"
+                clearable
+              />
+            </NFormItemGi>
+            <NFormItemGi span="24 s:12 m:6" label="澶嶆牳鍛�" label-width="auto" path="reviewName" class="pr-24px">
+              <NInput v-model:value="model.reviewName" placeholder="璇疯緭鍏ュ鏍稿憳" />
+            </NFormItemGi>
+            <NFormItemGi span="24 s:12 m:6" label="澶嶆牳鏃堕棿" label-width="auto" path="reviewTiem" class="pr-24px">
+              <NDatePicker
+                v-model:formatted-value="model.reviewTiem"
+                type="datetime"
+                value-format="yyyy-MM-dd HH:mm:ss"
+                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>

--
Gitblit v1.9.3