Browse Source

批次管理增加上传检测报告及界面优化

yawuga 1 month ago
parent
commit
6daf7d76ec
1 changed files with 434 additions and 11 deletions
  1. 434 11
      src/views/base/batch/index.vue

+ 434 - 11
src/views/base/batch/index.vue

@@ -228,6 +228,12 @@
             icon="el-icon-document"
             @click="handleUploadCertificate(scope.row)"
           >上传合格证</el-button>
+          <el-button
+            size="mini"
+            type="text"
+            icon="el-icon-document-copy"
+            @click="handleUploadReport(scope.row)"
+          >上传检测报告</el-button>
 
         </template>
       </el-table-column>
@@ -343,10 +349,25 @@
 
     <!-- 上传合格证弹窗 -->
     <el-dialog :title="certTitle" :visible.sync="certVisible" width="600px" append-to-body>
+      <!-- 顶部批次基础信息 -->
+      <div class="report-batch-info">
+        <div class="batch-info-item">
+          <span class="label">批次号:</span>
+          <span class="value">{{ certForm.batchNo }}</span>
+        </div>
+        <div class="batch-info-item">
+          <span class="label">商品名称:</span>
+          <span class="value">{{ certForm.productName }}</span>
+        </div>
+        <div class="batch-info-item">
+          <span class="label">农场名称:</span>
+          <span class="value">{{ certForm.farmName }}</span>
+        </div>
+      </div>
+
+      <el-divider></el-divider>
+
       <el-form ref="certForm" :model="certForm" :rules="certRules" label-width="100px">
-        <el-form-item label="批次号">
-          <span>{{ certForm.batchNo }}</span>
-        </el-form-item>
         <el-form-item label="合格证编号" prop="certNo">
           <el-input v-model="certForm.certNo" placeholder="请输入合格证编号" />
         </el-form-item>
@@ -366,9 +387,14 @@
           </el-select>
         </el-form-item>
         <el-form-item label="合格证文件" prop="certFiles">
-          <file-upload v-model="certForm.certFiles" :limit="5" />
+          <file-upload
+            v-model="certForm.certFiles"
+            :limit="1"
+            :file-type="['jpg', 'jpeg', 'png', 'pdf']"
+            :file-size="2"
+          />
           <div class="el-upload__tip">
-            支持上传多个文件,总数量不超过 5 个
+            仅支持上传 jpg/jpeg/png/pdf 格式文件,单个文件大小不超过 2MB,仅支持上传 1 个文件
           </div>
         </el-form-item>
       </el-form>
@@ -378,6 +404,124 @@
       </div>
     </el-dialog>
 
+    <!-- 上传检测报告弹窗 -->
+    <el-dialog :title="reportTitle" :visible.sync="reportVisible" width="760px" append-to-body>
+      <!-- 顶部批次基础信息 -->
+      <div class="report-batch-info">
+        <div class="batch-info-item">
+          <span class="label">批次号:</span>
+          <span class="value">{{ reportForm.batchNo }}</span>
+        </div>
+        <div class="batch-info-item">
+          <span class="label">商品名称:</span>
+          <span class="value">{{ reportForm.productName }}</span>
+        </div>
+        <div class="batch-info-item">
+          <span class="label">农场名称:</span>
+          <span class="value">{{ reportForm.farmName }}</span>
+        </div>
+      </div>
+
+      <el-divider></el-divider>
+
+      <!-- 检测报告列表 -->
+      <div class="report-list-container">
+        <el-form ref="reportFormRef" :model="reportForm" :rules="reportRules" label-width="0">
+          <div
+            v-for="(item, index) in reportForm.reportItems"
+            :key="index"
+            class="report-item"
+          >
+            <div class="report-item-header">
+              <span class="report-item-title">检测报告 {{ index + 1 }}</span>
+              <el-button
+                type="text"
+                size="small"
+                icon="el-icon-delete"
+                class="delete-btn"
+                @click="removeReportItem(index)"
+              >
+                删除
+              </el-button>
+            </div>
+
+            <div class="report-item-content">
+              <el-form-item
+                :label="'报告编号'"
+                :prop="'reportItems.' + index + '.reportNo'"
+                :rules="{
+                  required: true,
+                  message: '报告编号不能为空',
+                  trigger: 'blur'
+                }"
+                label-width="90px"
+              >
+                <el-input
+                  v-model="item.reportNo"
+                  placeholder="请输入报告编号"
+                  style="width: 100%"
+                />
+              </el-form-item>
+
+              <el-form-item
+                :label="'检测日期'"
+                :prop="'reportItems.' + index + '.reportDate'"
+                :rules="{
+                  required: true,
+                  message: '检测日期不能为空',
+                  trigger: 'change'
+                }"
+                label-width="90px"
+              >
+                <el-date-picker
+                  v-model="item.reportDate"
+                  type="date"
+                  value-format="yyyy-MM-dd"
+                  placeholder="请选择检测日期"
+                  style="width: 100%"
+                />
+              </el-form-item>
+
+              <el-form-item
+                :label="'检测文件'"
+                :prop="'reportItems.' + index + '.reportFile'"
+                :rules="{
+                  required: true,
+                  message: '请上传检测文件',
+                  trigger: 'change'
+                }"
+                label-width="90px"
+              >
+                <file-upload
+                  v-model="item.reportFile"
+                  :limit="1"
+                  :file-type="['jpg', 'jpeg', 'png', 'pdf']"
+                  :file-size="2"
+                />
+              </el-form-item>
+
+              <div class="report-tip">
+                <i class="el-icon-info"></i>
+                仅支持上传 jpg/jpeg/png/pdf 格式文件,单个文件大小不超过 2MB,每份检测报告仅支持上传 1 个文件;若原始报告为多页,请先合成为一张图片后上传
+              </div>
+            </div>
+          </div>
+        </el-form>
+
+        <!-- 新增报告项按钮 -->
+        <div class="add-report-btn">
+          <el-button type="dashed" icon="el-icon-plus" @click="addReportItem">
+            新增一份检测报告
+          </el-button>
+        </div>
+      </div>
+
+      <div slot="footer" class="dialog-footer">
+        <el-button type="primary" @click="submitReportForm">确 定</el-button>
+        <el-button @click="cancelReport">取 消</el-button>
+      </div>
+    </el-dialog>
+
   
   </div>
 </template>
@@ -441,6 +585,8 @@ export default {
         id: null,
         batchId: null,
         batchNo: "",
+        productName: "",
+        farmName: "",
         certNo: "",
         certIssueDate: null,
         certStatus: "pending",
@@ -458,7 +604,25 @@ export default {
           { required: true, message: "状态不能为空", trigger: "change" }
         ]
       },
-     
+
+      // 检测报告弹窗相关
+      reportVisible: false,
+      reportTitle: "上传检测报告",
+      reportForm: {
+        batchId: null,
+        batchNo: "",
+        productName: "",
+        farmName: "",
+        reportItems: [
+          {
+            reportNo: "",
+            reportDate: "",
+            reportFile: ""
+          }
+        ]
+      },
+      reportRules: {},
+
       // 查询参数
       queryParams: {
         pageNum: 1,
@@ -689,9 +853,11 @@ export default {
       this.resetCert()
       this.certForm.batchId = row.id
       this.certForm.batchNo = row.batchNo
+      this.certForm.productName = row.productName
+      this.certForm.farmName = row.farmName
       this.certVisible = true
       this.certTitle = "上传合格证"
-      
+
       // 查询该批次是否已有合格证
       listCertificate({ batchId: row.id }).then(response => {
         if (response.rows && response.rows.length > 0) {
@@ -701,6 +867,8 @@ export default {
             id: cert.id,
             batchId: cert.batchId,
             batchNo: row.batchNo,
+            productName: row.productName,
+            farmName: row.farmName,
             certNo: cert.certNo,
             certIssueDate: cert.certIssueDate,
             certStatus: cert.certStatus,
@@ -748,6 +916,8 @@ export default {
         id: null,
         batchId: null,
         batchNo: "",
+        productName: "",
+        farmName: "",
         certNo: "",
         certIssueDate: null,
         certStatus: "pending",
@@ -760,9 +930,38 @@ export default {
     submitCertForm() {
       this.$refs["certForm"].validate(valid => {
         if (valid) {
+          // 前端兜底校验:检查文件类型
+          const allowedTypes = ['jpg', 'jpeg', 'png', 'pdf']
+          const certFilesValue = this.certForm.certFiles
+
+          // 如果有上传文件,进行兜底校验
+          if (certFilesValue && (typeof certFilesValue === 'string' ? certFilesValue.trim() : (Array.isArray(certFilesValue) && certFilesValue.length > 0))) {
+            let fileUrls = []
+
+            if (typeof certFilesValue === 'string') {
+              fileUrls = certFilesValue.split(',')
+            } else if (Array.isArray(certFilesValue)) {
+              fileUrls = certFilesValue.map(file => file.url || file)
+            }
+
+            for (const fileUrl of fileUrls) {
+              const url = typeof fileUrl === 'string' ? fileUrl.trim() : (fileUrl.url || '')
+              if (!url) continue
+
+              // 获取文件扩展名(小写)
+              const ext = url.split('.').pop().toLowerCase()
+
+              // 检查文件类型
+              if (!allowedTypes.includes(ext)) {
+                this.$message.warning("合格证文件仅支持 jpg/jpeg/png/pdf 格式")
+                return
+              }
+            }
+          }
+
           // 确保 certFiles 是正确的格式
           let certFilesArray = []
-          
+
           if (Array.isArray(this.certForm.certFiles)) {
             // 如果已经是数组,确保每个文件都有 name 属性
             certFilesArray = this.certForm.certFiles.map(file => {
@@ -785,7 +984,7 @@ export default {
                   return file
                 })
               } else {
-                certFilesArray = [{ 
+                certFilesArray = [{
                   url: typeof parsed === 'string' ? parsed : parsed.url,
                   name: typeof parsed === 'string' ? parsed.split('/').pop() : parsed.url?.split('/').pop()
                 }]
@@ -804,7 +1003,7 @@ export default {
               }
             }
           }
-          
+
           // 准备提交数据(使用大驼峰命名)
           const data = {
             batchId: this.certForm.batchId,
@@ -814,7 +1013,7 @@ export default {
             // 关键:转为 JSON 字符串存储到数据库
             certFiles: JSON.stringify(certFilesArray)
           }
-          
+
           if (this.certForm.id != null) {
             // 修改
             updateCertificate({ ...data, id: this.certForm.id }).then(response => {
@@ -833,6 +1032,128 @@ export default {
         }
       })
     },
+
+    /** 打开上传检测报告弹窗 */
+    handleUploadReport(row) {
+      this.resetReport()
+      this.reportForm.batchId = row.id
+      this.reportForm.batchNo = row.batchNo
+      this.reportForm.productName = row.productName
+      this.reportForm.farmName = row.farmName
+      this.reportVisible = true
+      this.reportTitle = "上传检测报告"
+
+      // TODO: 查询该批次是否已有检测报告数据
+      // listReport({ batchId: row.id }).then(response => {
+      //   if (response.rows && response.rows.length > 0) {
+      //     // 已有检测报告,设置为编辑模式
+      //   }
+      // })
+    },
+
+    /** 取消检测报告 */
+    cancelReport() {
+      this.reportVisible = false
+      this.resetReport()
+    },
+
+    /** 重置检测报告表单 */
+    resetReport() {
+      this.reportForm = {
+        batchId: null,
+        batchNo: "",
+        productName: "",
+        farmName: "",
+        reportItems: [
+          {
+            reportNo: "",
+            reportDate: "",
+            reportFile: ""
+          }
+        ]
+      }
+      this.resetForm("reportFormRef")
+    },
+
+    /** 新增一条检测报告 */
+    addReportItem() {
+      this.reportForm.reportItems.push({
+        reportNo: "",
+        reportDate: "",
+        reportFile: ""
+      })
+    },
+
+    /** 删除一条检测报告 */
+    removeReportItem(index) {
+      if (this.reportForm.reportItems.length === 1) {
+        this.$message.warning("至少保留一条检测报告")
+        return
+      }
+      this.reportForm.reportItems.splice(index, 1)
+    },
+
+    /** 提交检测报告表单 */
+    submitReportForm() {
+      // 校验整个报告表单
+      this.$refs["reportFormRef"].validate(valid => {
+        if (valid) {
+          // 校验每条报告项 - file-upload 组件返回逗号分隔的字符串
+          const hasEmptyItem = this.reportForm.reportItems.some(item => {
+            // 报告编号和检测日期是字符串,直接判断非空
+            // reportFile 是上传组件返回的字符串,上传后会有值(如 "url1,url2")
+            const isReportFileEmpty = !item.reportFile || item.reportFile.trim() === ""
+            return !item.reportNo || !item.reportDate || isReportFileEmpty
+          })
+
+          if (hasEmptyItem) {
+            this.$message.warning("请完善每条检测报告的必填信息")
+            return
+          }
+
+          // 前端兜底校验:检查文件类型和大小
+          const allowedTypes = ['jpg', 'jpeg', 'png', 'pdf']
+          const maxSize = 2 * 1024 * 1024 // 2MB
+
+          for (let i = 0; i < this.reportForm.reportItems.length; i++) {
+            const item = this.reportForm.reportItems[i]
+            const files = item.reportFile
+
+            // 如果有上传文件,进行兜底校验
+            if (files && files.trim()) {
+              const fileUrls = files.split(',')
+              for (const fileUrl of fileUrls) {
+                const trimmedUrl = fileUrl.trim()
+                if (!trimmedUrl) continue
+
+                // 获取文件扩展名(小写)
+                const ext = trimmedUrl.split('.').pop().toLowerCase()
+
+                // 检查文件类型
+                if (!allowedTypes.includes(ext)) {
+                  this.$message.warning(`第 ${i + 1} 条检测报告的文件格式不正确,仅支持 jpg/jpeg/png/pdf 格式`)
+                  return
+                }
+
+                // 注意:这里无法直接获取文件大小进行校验
+                // 文件大小由上传组件在上传前进行限制
+                // 此处只能通过文件扩展名进行类型兜底校验
+              }
+            }
+          }
+
+          // 前端校验通过,打印数据
+          console.log("检测报告表单数据:", this.reportForm)
+          this.$message.success("前端校验通过,待接接口")
+
+          // TODO: 接接口时的提交逻辑
+          // const data = {
+          //   batchId: this.reportForm.batchId,
+          //   reportItems: this.reportForm.reportItems
+          // }
+        }
+      })
+    },
   }
 }
 </script>
@@ -917,4 +1238,106 @@ export default {
   text-align: center;
   padding-top: 10px;
 }
+
+/* 检测报告弹窗样式 */
+.report-batch-info {
+  display: flex;
+  flex-wrap: wrap;
+  gap: 20px;
+  padding: 10px 0;
+}
+
+.batch-info-item {
+  display: flex;
+  align-items: center;
+}
+
+.batch-info-item .label {
+  color: #909399;
+  font-size: 14px;
+}
+
+.batch-info-item .value {
+  color: #303133;
+  font-size: 14px;
+  font-weight: 500;
+}
+
+.report-list-container {
+  max-height: 450px;
+  overflow-y: auto;
+  padding-right: 5px;
+}
+
+.report-item {
+  border: 1px solid #ebeef5;
+  border-radius: 6px;
+  margin-bottom: 16px;
+  background-color: #fafafa;
+  transition: border-color 0.3s;
+}
+
+.report-item:hover {
+  border-color: #dcdfe6;
+}
+
+.report-item:last-of-type {
+  margin-bottom: 12px;
+}
+
+.report-item-header {
+  display: flex;
+  justify-content: space-between;
+  align-items: center;
+  padding: 10px 15px;
+  background-color: #ecf5ff;
+  border-bottom: 1px solid #e4e7ed;
+  border-radius: 6px 6px 0 0;
+}
+
+.report-item-title {
+  font-size: 14px;
+  font-weight: 500;
+  color: #409eff;
+}
+
+.delete-btn {
+  color: #f56c6c;
+}
+
+.delete-btn:hover {
+  color: #f78989;
+}
+
+.report-item-content {
+  padding: 15px;
+}
+
+.report-item-content .el-form-item {
+  margin-bottom: 14px;
+}
+
+.report-item-content .el-form-item:last-child {
+  margin-bottom: 0;
+}
+
+.report-tip {
+  font-size: 12px;
+  color: #909399;
+  margin-top: 8px;
+  line-height: 1.4;
+}
+
+.report-tip i {
+  margin-right: 4px;
+}
+
+.add-report-btn {
+  margin-top: 8px;
+}
+
+.add-report-btn .el-button {
+  width: 100%;
+  border-style: dashed;
+}
 </style>