董国庆
2025-05-27 70d21cc56b187572a682389fd647cdbe219303d7
Merge branch 'main' of http://120.76.84.145:10101/gitblit/r/H5/leshan-laboratory
1个文件已添加
9个文件已修改
1305 ■■■■■ 已修改文件
culture/src/router/index.js 18 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
culture/src/views/strain-library/breeding-record/SlantRecordDialog.vue 174 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
culture/src/views/strain-library/breeding-record/add.vue 465 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
culture/src/views/strain-library/breeding-record/confirm-preserve-dialog.vue 146 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
culture/src/views/strain-library/breeding-record/confirm-storage-dialog.vue 22 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
culture/src/views/strain-library/breeding-record/index.vue 255 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
culture/src/views/strain-library/breeding-record/inoculation-slope-record-dialog.vue 99 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
culture/src/views/strain-library/breeding-record/preserve-strain-record-dialog.vue 73 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
culture/src/views/strain-library/breeding-record/separation-record-dialog.vue 16 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
culture/src/views/strain-library/breeding-record/service.js 37 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
culture/src/router/index.js
@@ -286,6 +286,24 @@
        component: () => import("../views/strain-library/breeding-record/add"),
      },
      {
        path: "edit-breeding-record",
        name: "EditBreedingRecord",
        meta: {
          title: "编辑菌种选育保藏记录",
          hide: true,
        },
        component: () => import("../views/strain-library/breeding-record/add"),
      },
      {
        path: "detail-breeding-record",
        name: "DetailBreedingRecord",
        meta: {
          title: "菌种选育保藏记录详情",
          hide: true,
        },
        component: () => import("../views/strain-library/breeding-record/add"),
      },
      {
        path: "validation",
        meta: {
          title: "菌种验证数据资料",
culture/src/views/strain-library/breeding-record/SlantRecordDialog.vue
@@ -1,15 +1,15 @@
<template>
 <el-dialog :visible.sync="visible" title="新增培养皿观察记录" width="80%" @close="handleClose">
  <el-dialog :visible.sync="visible" :title="editData ? '编辑培养皿观察记录' : '新增培养皿观察记录'" width="80%" @close="handleClose">
    <el-form :model="form" :rules="rules" ref="formRef" label-width="120px" label-position="top">
      <el-row :gutter="24">
        <el-col :span="12">
          <el-form-item label="分离菌落编号" prop="colonyNo" required>
            <el-input v-model="form.colonyNo" placeholder="请输入分离菌落编号" />
          <el-form-item label="分离菌落编号" prop="separateColonyCode">
            <el-input :disabled="roleType!=4" v-model="form.separateColonyCode" placeholder="请输入分离菌落编号" />
          </el-form-item>
        </el-col>
        <el-col :span="12">
          <el-form-item label="形状强壮度排名" prop="rank" required>
            <el-input v-model="form.rank" placeholder="请输入形状强壮度排名" />
          <el-form-item label="形状强壮度排名" prop="strength">
            <el-input :disabled="roleType!=4" v-model="form.strength" placeholder="请输入形状强壮度排名" />
          </el-form-item>
        </el-col>
      </el-row>
@@ -22,22 +22,16 @@
      </el-table-column>
      <el-table-column prop="desc" label="形态记录">
        <template #default="{ row }">
          <el-input v-model="row.desc" placeholder="请输入形态记录" style="width: 100%;" />
          <el-input class="el-input-full" style="width: 100%;" :disabled="roleType!=4" v-model="row.desc" placeholder="请输入形态记录"
            @blur="handleDescBlur(row)" />
        </template>
      </el-table-column>
      <el-table-column prop="images" label="拍照上传" width="120">
        <template #default="{ row }">
          <el-upload
            :file-list="row.images"
            list-type="picture-card"
            :on-preview="file => handlePreview(row, file)"
          <el-upload :file-list="row.images" :disabled="roleType!=4" list-type="picture-card" :on-preview="file => handlePreview(row, file)"
            :on-remove="(file, fileList) => handleRemove(row, file, fileList)"
            :on-success="(res, file, fileList) => handleUpload(row, file, fileList)"
            :before-upload="beforeUpload"
            action="#"
            :limit="5"
            class="mini-upload"
          >
            :on-success="(res, file, fileList) => handleUpload(row, file, fileList)" :before-upload="beforeUpload"
            action="#" :limit="5" class="mini-upload">
            <i class="el-icon-plus"></i>
          </el-upload>
        </template>
@@ -48,49 +42,92 @@
        </template>
      </el-table-column>
    </el-table>
    <div style="text-align: center;">
    <div style="text-align: center;" v-if="roleType==4">
      <el-button type="primary" @click="handleOk">保存</el-button>
    </div>
    <el-dialog :visible.sync="previewVisible" width="400px">
      <img :src="previewImg" alt="图片预览" style="width: 100%;" />
    </el-dialog>
  </el-dialog>
</template>
<script>
import moment from 'moment'
export default {
  name: 'SlantRecordDialog',
  props: {
    visible: Boolean,
    value: {
    editData: {
      type: Object,
      default: () => ({ colonyNo: '', rank: '', records: [] })
      default: null
    }
  },
  data() {
    return {
      roleType: JSON.parse(sessionStorage.getItem('userInfo')).roleType,
      form: {
        colonyNo: '',
        rank: ''
        separateColonyCode: '',
        strength: '',
        breedingPreserveId: '',
        createBy: '',
        createTime: '',
        disabled: 0,
        id: '',
        images: '',
        morphologicalRecord: ''
      },
      rules: {
        colonyNo: [{ required: true, message: '请输入分离菌落编号', trigger: 'blur' }],
        rank: [{ required: true, message: '请输入形状强壮度排名', trigger: 'blur' }]
        separateColonyCode: [{ required: true, message: '请输入分离菌落编号', trigger: 'blur' }],
        strength: [{ required: true, message: '请输入形状强壮度排名', trigger: 'blur' }]
      },
      tableData: [],
      previewVisible: false,
      previewImg: ''
      tableData: Array.from({ length: 10 }, (_, i) => ({
        index: i + 1,
        desc: '',
        time: '',
        images: []
      }))
    }
  },
  watch: {
    value: {
    editData: {
      immediate: true,
      handler(val) {
        this.form.colonyNo = val.colonyNo || ''
        this.form.rank = val.rank || ''
        this.tableData = (val.records && val.records.length === 10)
          ? val.records.map((item, i) => ({ ...item, index: i + 1 }))
          : Array.from({ length: 10 }, (_, i) => ({ index: i + 1, desc: '', images: [], time: this.getNowTime() }))
        if (val) {
          this.form = { ...val }
          if (val.morphologicalRecord) {
            try {
              const records = JSON.parse(val.morphologicalRecord)
              this.tableData = records.map((record, index) => ({
                index: index + 1,
                desc: record.desc || '',
                time: record.time || this.getNowTime(),
                images: record.images || []
              }))
              while (this.tableData.length < 10) {
                this.tableData.push({
                  index: this.tableData.length + 1,
                  desc: '',
                  time: '',
                  images: []
                })
              }
            } catch (e) {
              console.error('解析形态记录数据失败:', e)
              this.tableData = Array.from({ length: 10 }, (_, i) => ({
                index: i + 1,
                desc: '',
                time: '',
                images: []
              }))
            }
          } else {
            this.tableData = Array.from({ length: 10 }, (_, i) => ({
              index: i + 1,
              desc: '',
              time: '',
              images: []
            }))
          }
        } else {
          this.reset()
        }
      }
    },
    visible(val) {
@@ -102,28 +139,46 @@
  methods: {
    getNowTime() {
      const d = new Date()
      return `${d.getFullYear()}-${d.getMonth() + 1}-${d.getDate()} ${d.getHours()}:${d.getMinutes()}:${d.getSeconds()}`
      return moment(d).format('YYYY-MM-DD HH:mm:ss')
    },
    reset() {
      this.form.colonyNo = ''
      this.form.rank = ''
      this.tableData = Array.from({ length: 10 }, (_, i) => ({ index: i + 1, desc: '', images: [], time: this.getNowTime() }))
      this.form = {
        separateColonyCode: '',
        strength: '',
        breedingPreserveId: '',
        createBy: '',
        createTime: this.getNowTime(),
        disabled: 0,
        id: '',
        images: '',
        morphologicalRecord: ''
      }
    },
    handleOk() {
      this.$refs.formRef.validate(valid => {
        if (!valid) return
        // 校验每行形态记录必填
        for (let i = 0; i < this.tableData.length; i++) {
          if (!this.tableData[i].desc) {
            this.$message.error(`第${i + 1}次形态记录不能为空`)
            return
          }
        const morphologicalRecord = this.tableData
          .filter(row => row.desc || (row.images && row.images.length > 0))
          .map(row => ({
            desc: row.desc,
            time: row.time,
            images: row.images
          }))
        const submitData = {
          ...this.form,
          morphologicalRecord: JSON.stringify(morphologicalRecord)
        }
        this.$emit('ok', {
          colonyNo: this.form.colonyNo,
          rank: this.form.rank,
          records: this.tableData
        })
        this.$emit('ok', submitData)
        this.reset()
        this.tableData = Array.from({ length: 10 }, (_, i) => ({
          index: i + 1,
          desc: '',
          time: '',
          images: []
        }))
        this.handleClose()
      })
    },
@@ -150,32 +205,47 @@
    handlePreview(row, file) {
      this.previewImg = file.url
      this.previewVisible = true
    },
    handleDescBlur(row) {
      console.log(row)
      if (row.desc) {
        row.time = this.getNowTime()
        this.$forceUpdate()
      }
    }
  }
}
</script>
<style scoped lang="less">
::v-deep(.el-input__inner) {
  width: 100% !important;
}
::v-deep(.el-upload--picture-card) {
  width: 40px !important;
  height: 40px !important;
  line-height: 40px !important;
}
::v-deep(.mini-upload .el-upload-list--picture-card .el-upload-list__item) {
  width: 40px !important;
  height: 40px !important;
}
::v-deep(.mini-upload .el-upload-list--picture-card .el-upload-list__item-thumbnail) {
  width: 40px !important;
  height: 40px !important;
  object-fit: cover;
}
::v-deep(.mini-upload .el-upload-list--picture-card .el-upload-list__item-preview),
::v-deep(.mini-upload .el-upload-list--picture-card .el-upload-list__item-delete) {
  width: 18px;
  height: 18px;
  font-size: 14px;
}
::v-deep(.el-upload--picture-card) {
  width: 40px !important;
  height: 40px !important;
@@ -184,8 +254,10 @@
  align-items: center;
  justify-content: center;
}
::v-deep(.mini-upload .el-upload--picture-card i.el-icon-plus) {
  font-size: 18px;      /* 缩小icon */
  font-size: 18px;
  /* 缩小icon */
  color: #999;
  display: flex;
  align-items: center;
culture/src/views/strain-library/breeding-record/add.vue
@@ -21,36 +21,39 @@
        <!-- 来源菌株 -->
        <el-row v-if="activeTab === 'strain'" :gutter="10">
          <el-col :xs="24" :sm="12" :md="12" :lg="8" :xl="8">
            <el-form-item label="菌株编号" prop="strainCode" required>
              <el-input v-model="form.strainCode" class="w-380" placeholder="请输入菌株编号" />
            <el-form-item label="菌株编号" prop="strainCode">
              <el-input :disabled="$route.query.isDetail" v-model="form.strainCode" class="w-380"
                placeholder="请输入菌株编号" />
            </el-form-item>
          </el-col>
          <el-col :xs="24" :sm="12" :md="12" :lg="8" :xl="8">
            <el-form-item label="菌株名称" prop="strainName" required>
              <el-input v-model="form.strainName" class="w-380" placeholder="请输入菌株名称" />
            <el-form-item label="菌株名称" prop="strainName">
              <el-input :disabled="$route.query.isDetail" v-model="form.strainName" class="w-380"
                placeholder="请输入菌株名称" />
            </el-form-item>
          </el-col>
          <el-col :xs="24" :sm="12" :md="12" :lg="8" :xl="8">
            <el-form-item label="培养基配方" prop="formula" required>
              <el-input v-model="form.formula" class="w-380" placeholder="请输入培养基配方" />
            <el-form-item label="培养基配方" prop="formula">
              <el-input :disabled="$route.query.isDetail" v-model="form.formula" class="w-380" placeholder="请输入培养基配方" />
            </el-form-item>
          </el-col>
        </el-row>
        <!-- 来源物资 -->
        <el-row v-if="activeTab === 'material'" :gutter="10">
          <el-col :span="24">
            <el-form-item label="来源物资、时间及批号" prop="sourceMaterialTimeBatchNumber" required>
              <el-input v-model="form.sourceMaterialTimeBatchNumber" placeholder="请输入物资编号" />
            <el-form-item label="来源物资、时间及批号" prop="sourceMaterialTimeBatchNumber">
              <el-input :disabled="$route.query.isDetail" v-model="form.sourceMaterialTimeBatchNumber"
                placeholder="请输入物资编号" />
            </el-form-item>
          </el-col>
          <el-col :span="24">
            <el-form-item label="培养基配方" prop="materialName" required>
              <el-input v-model="form.materialName" placeholder="请输入物资名称" />
            <el-form-item label="培养基配方" prop="formula">
              <el-input :disabled="$route.query.isDetail" v-model="form.formula" placeholder="请输入培养基配方" />
            </el-form-item>
          </el-col>
          <el-col :span="24">
            <el-form-item label="分离菌落编号" prop="separateColonyNumber" required>
              <el-input v-model="form.separateColonyNumber" placeholder="请输入物资描述" />
            <el-form-item label="分离菌落编号" prop="separateColonyNumber">
              <el-input :disabled="$route.query.isDetail" v-model="form.separateColonyNumber" placeholder="请输入分离菌落编号" />
            </el-form-item>
          </el-col>
        </el-row>
@@ -62,23 +65,33 @@
        </div>
        <el-row :gutter="10">
          <el-col :xs="24" :sm="12" :md="12" :lg="6" :xl="6">
            <el-form-item label="培养基" prop="cultureMedium" required>
              <el-input v-model="form.cultureMedium" class="w-380" placeholder="请输入培养基" />
            <el-form-item label="培养基" prop="cultureMedium">
              <el-input :disabled="$route.query.isDetail" v-model="form.cultureMedium" class="w-380"
                placeholder="请输入培养基" />
            </el-form-item>
          </el-col>
          <el-col :xs="24" :sm="12" :md="12" :lg="6" :xl="6">
            <el-form-item label="培养温度" prop="temperature" required>
              <el-input v-model="form.temperature" class="w-380" placeholder="请输入培养温度" />
            <el-form-item label="培养温度" prop="temperature">
              <el-input :disabled="$route.query.isDetail" v-model="form.temperature" class="w-380"
                placeholder="请输入培养温度" />
            </el-form-item>
          </el-col>
          <el-col :xs="24" :sm="12" :md="12" :lg="6" :xl="6">
            <el-form-item label="需氧类型" prop="aerobicType" required>
              <el-input v-model="form.aerobicType" class="w-380" placeholder="请输入需氧类型" />
            <el-form-item label="需氧类型" prop="aerobicType">
              <el-select :disabled="$route.query.isDetail" v-model="form.aerobicType" class="w-380"
                placeholder="请选择需氧类型">
                <el-option label="专性需氧" value="1"></el-option>
                <el-option label="专性厌氧" value="2"></el-option>
                <el-option label="兼性需氧" value="3"></el-option>
                <el-option label="耐氧厌氧" value="4"></el-option>
              </el-select>
            </el-form-item>
          </el-col>
          <el-col :xs="24" :sm="12" :md="12" :lg="6" :xl="6">
            <el-form-item label="培养时间" prop="cultureTime" required>
              <el-input v-model="form.cultureTime" class="w-380" placeholder="请输入培养时间" />
            <el-form-item label="培养时间" prop="cultureTime">
              <el-input :disabled="$route.query.isDetail" v-model="form.cultureTime" class="w-380"
                placeholder="请输入培养时间" />
              <!-- <el-date-picker v-model="form.cultureTime" class="w-380" placeholder="请选择培养时间" type="date" value-format="yyyy-MM-dd" /> -->
            </el-form-item>
          </el-col>
        </el-row>
@@ -88,23 +101,27 @@
            <div>一、培养皿分离记录</div>
          </div>
          <div class="header-title-right">
            <el-button @click="handleAddSeparation" class="el-icon-circle-plus-outline" type="primary">
            <el-button @click="handleAddSeparation" v-if="!$route.query.isDetail&&[1,4].includes(roleType)" class="el-icon-circle-plus-outline"
              type="primary">
              新增培养皿分离记录</el-button>
          </div>
        </div>
        <Table :data="form.separationOfCultureDishesList" :height="null" :queryForm="queryForm" :total="0">
          <el-table-column width="100" type="index" label="培养皿序号" />
          <el-table-column prop="separateBacterialColoniesCode" label="分离菌落编号" />
          <el-table-column prop="operatorSignature" label="接种操作人签字" >
          <el-table-column prop="handleSignature" label="接种操作人签字">
            <template slot-scope="scope">
              <el-image  :preview-src-list="[scope.row.operatorSignature]" style="width: 40px;" :src="scope.row.operatorSignature" alt="操作人签字" />
              <el-image :preview-src-list="[scope.row.handleSignature]" style="width: 40px;"
                :src="scope.row.handleSignature" alt="操作人签字" />
            </template>
          </el-table-column>
          <el-table-column prop="createTime" label="操作时间" />
          <el-table-column prop="address" label="操作">
            <template slot-scope="scope">
              <el-button type="text" @click="handleEditSeparation(scope.row, scope.$index)">编辑</el-button>
              <el-button type="text" @click="handleDeleteSeparation(scope.$index)">删除</el-button>
          <el-table-column prop="address" label="操作" v-if="!$route.query.isDetail">
            <template slot-scope="scope" v-if="[1,4].includes(roleType)">
              <el-button type="text" @click="handleEditSeparation(scope.row, scope.$index)"
                v-if="!$route.query.isDetail">编辑</el-button>
              <el-button type="text" @click="handleDeleteSeparation(scope.$index)"
                v-if="!$route.query.isDetail">删除</el-button>
            </template>
          </el-table-column>
        </Table>
@@ -114,21 +131,21 @@
            <div>二、培养皿生物学形态观察记录</div>
          </div>
          <div class="header-title-right">
            <el-button @click="showObservationDialog = true" class="el-icon-circle-plus-outline" type="primary">
            <el-button @click="handleAddObservation" v-if="!$route.query.isDetail&&[1,4].includes(roleType)" class="el-icon-circle-plus-outline" type="primary">
              新增观察记录</el-button>
          </div>
        </div>
        <Table :height="null" :queryForm="queryForm" :total="0">
          <template>
            <el-table-column prop="age" label="分离菌落编号" />
            <el-table-column prop="age" label="形状强壮度排名" />
            <el-table-column prop="address" label="操作">
              <template slot-scope="scope"><el-button type="text"
                @click="handleEdit(scope.row)">形态记录</el-button>
                <el-button type="text"
                @click="handleEdit(scope.row)">删除</el-button></template>
            </el-table-column>
          </template>
        <Table :data="form.observationOfPetriDishes" :height="null" :queryForm="queryForm" :total="0">
          <el-table-column prop="separateColonyCode" label="分离菌落编号" />
          <el-table-column prop="strength" label="形状强壮度排名" />
          <el-table-column prop="address" label="操作">
            <template slot-scope="scope">
              <!-- <el-button type="text" @click="handleEditObservation(scope.row)" v-if="!$route.query.isDetail">编辑</el-button> -->
              <el-button type="text" @click="handleEditObservation(scope.row)">形态记录</el-button>
              <el-button type="text" @click="handleDeleteObservation(scope.$index)"
                v-if="!$route.query.isDetail&&[1,4].includes(roleType)">删除</el-button>
            </template>
          </el-table-column>
        </Table>
        <div class="header-title" style="margin-top: 20px;">
          <div class="header-title-left">
@@ -136,76 +153,101 @@
            <div>三、接种斜面记录</div>
          </div>
          <div class="header-title-right">
            <el-button @click="showInoculationDialog = true" class="el-icon-circle-plus-outline" type="primary">
            <el-button @click="handleAddInoculation" v-if="!$route.query.isDetail&&[1,4].includes(roleType)" class="el-icon-circle-plus-outline" type="primary">
              新增斜面记录</el-button>
          </div>
        </div>
        <Table :height="null" :queryForm="queryForm" :total="0">
          <template>
            <el-table-column prop="age" label="分离菌落编号" />
            <el-table-column prop="age" label="接种斜面编号" />
            <el-table-column prop="age" label="接种操作人" />
            <el-table-column prop="age" label="接种操作人签字" />
            <el-table-column prop="age" label="接种操作时间" />
            <el-table-column prop="age" label="入库/废弃" />
            <el-table-column prop="age" label="入库总数(只)" />
            <el-table-column prop="age" label="菌种保藏人签字" />
            <el-table-column prop="age" label="入库保藏/废弃时间" />
            <el-table-column prop="address" label="操作">
              <template slot-scope="scope">
                <el-button type="text"
                @click="handleEdit(scope.row)">删除</el-button></template>
            </el-table-column>
          </template>
        </Table>
        <el-table :data="form.vaccinationSlopes" border style="width: 100%; margin-bottom: 16px;"
          :span-method="inoculationRowSpan">
          <el-table-column prop="separateColonyCode" label="分离菌落编号" />
          <el-table-column prop="vaccinationSlopeCode" label="接种斜面编号" />
          <el-table-column prop="handleName" label="接种操作人" />
          <el-table-column prop="handleSignature" label="接种操作人签字">
            <template slot-scope="scope">
              <el-image v-if="scope.row.handleSignature" :preview-src-list="[scope.row.handleSignature]"
                style="width: 40px;" :src="scope.row.handleSignature" alt="操作人签字" />
            </template>
          </el-table-column>
          <el-table-column prop="handleTime" label="接种操作时间" />
          <el-table-column prop="handleType" label="入库/废弃">
            <template slot-scope="scope">
              {{ scope.row.handleType === '保存' ? '入库' : '废弃' }}
            </template>
          </el-table-column>
          <el-table-column label="入库总数(只)">
            <template slot-scope="scope">
              <span v-if="scope.$index === firstInoculationOrDiscardIndex">{{ totalInoculationCount }}</span>
            </template>
          </el-table-column>
          <el-table-column prop="preserveSignature" label="菌种保藏人签字">
            <template slot-scope="scope">
              <el-image v-if="scope.row.preserveSignature" :preview-src-list="[scope.row.preserveSignature]"
                style="width: 40px;" :src="scope.row.preserveSignature" alt="操作人签字" />
            </template>
          </el-table-column>
          <el-table-column prop="preserveTime" label="入库保藏/废弃时间" />
          <el-table-column label="操作" v-if="[1,3,4].includes(roleType)&&!$route.query.isDetail">
            <template slot-scope="scope">
              <el-button type="text" @click="handleEditInoculation(scope.row, scope.$index)" v-if="!$route.query.isDetail&&[1,4].includes(roleType)">编辑</el-button>
              <el-button type="text" @click="handleConfirmStorageClick(scope.row, scope.$index)" v-if="$route.query.isDetail&&[3].includes(roleType)">确认入库</el-button>
              <el-button type="text" @click="handleDeleteInoculation(scope.$index)" v-if="!$route.query.isDetail&&!scope.row.preserveSignature&&[1,4].includes(roleType)">删除</el-button>
            </template>
          </el-table-column>
        </el-table>
        <div class="header-title" style="margin-top: 20px;">
          <div class="header-title-left">
            <img src="@/assets/public/headercard.png" />
            <div>四、菌种保藏记录</div>
          </div>
          <div class="header-title-right">
            <el-button @click="showPreserveDialog = true" class="el-icon-circle-plus-outline" type="primary">
            <el-button @click="handleAddPreserve" v-if="!$route.query.isDetail&&[1,4].includes(roleType)" class="el-icon-circle-plus-outline" type="primary">
              新增菌种保藏记录</el-button>
          </div>
        </div>
        <Table :height="null" :queryForm="queryForm" :total="0">
          <template>
            <el-table-column prop="age" label="用于保藏的菌种编号" />
            <el-table-column prop="age" label="实验验证结论" />
            <el-table-column prop="age" label="保藏方法" />
            <el-table-column prop="age" label="保藏菌种编号" />
            <el-table-column prop="age" label="菌种保藏人签字" />
            <el-table-column prop="age" label="保藏时间" />
            <el-table-column prop="address" label="操作">
              <template slot-scope="scope">
                <el-button type="text"
                @click="handleEdit(scope.row)">删除</el-button></template>
            </el-table-column>
          </template>
        <Table :data="form.culturePreservations" :height="null" :queryForm="queryForm" :total="0">
          <el-table-column prop="forPreserveCode" label="用于保藏的菌种编号" />
          <el-table-column prop="verificationConclusion" label="实验验证结论" />
          <el-table-column prop="preserveMethod" label="保藏方法" />
          <el-table-column prop="preserveCode" label="保藏菌种编号" />
          <el-table-column prop="handleSignature" label="菌种操作人签字">
            <template slot-scope="scope">
              <el-image v-if="scope.row.handleSignature" :preview-src-list="[scope.row.handleSignature]"
                style="width: 40px;" :src="scope.row.handleSignature" alt="签字" />
            </template>
          </el-table-column>
          <el-table-column prop="handleTime" label="操作人" />
          <el-table-column prop="preserveSignature" label="菌种保藏人签字">
            <template slot-scope="scope">
              <el-image v-if="scope.row.preserveSignature" :preview-src-list="[scope.row.preserveSignature]"
                style="width: 40px;" :src="scope.row.preserveSignature" alt="签字" />
            </template>
          </el-table-column>
          <el-table-column prop="preserveTime" label="保藏时间" />
          <el-table-column label="操作" v-if="[1,3,4].includes(roleType)&&!$route.query.isDetail">
            <template slot-scope="scope">
              <el-button type="text" @click="handleEditPreserve(scope.row, scope.$index)" v-if="!$route.query.isDetail&&[1,4].includes(roleType)">编辑</el-button>
              <el-button type="text" @click="handleConfirmPreserve(scope.row, scope.$index)" v-if="$route.query.isDetail&&[3].includes(roleType)">确认入库</el-button>
              <el-button type="text" @click="handleDeletePreserve(scope.$index)" v-if="!$route.query.isDetail&&!scope.row.preserveSignature&&[1,4].includes(roleType)">删除</el-button>
            </template>
          </el-table-column>
        </Table>
        <!-- 弹窗组件 -->
        <SeparationRecordDialog
          :visible.sync="showSeparationDialog"
          :editData="editSeparationData"
          @confirm="handleSeparationConfirm"
          @close="resetSeparationEdit"
        />
        <SlantRecordDialog
          :visible.sync="showObservationDialog"
          @ok="handleObservationConfirm"
        />
        <InoculationSlopeRecordDialog
          :visible.sync="showInoculationDialog"
          @save="handleInoculationConfirm"
        />
        <PreserveStrainRecordDialog
          :visible.sync="showPreserveDialog"
          @save="handlePreserveConfirm"
        />
        <div class="end-btn" style="margin-top: 20px;">
          <el-button type="primary" @click="handleSubmit">发送</el-button>
          <el-button type="default">存草稿</el-button>
        <SeparationRecordDialog :visible.sync="showSeparationDialog" :editData="editSeparationData"
          @confirm="handleSeparationConfirm" @close="resetSeparationEdit" />
        <SlantRecordDialog :visible.sync="showObservationDialog" :editData="editObservationData"
          @ok="handleObservationConfirm" />
        <InoculationSlopeRecordDialog :visible.sync="showInoculationDialog" :editData="editInoculationData"
          @save="handleInoculationConfirm" />
        <PreserveStrainRecordDialog :visible.sync="showPreserveDialog" :editData="editPreserveData"
          @save="handlePreserveConfirm" />
        <ConfirmStorageDialog :visible.sync="showConfirmStorageDialog" :editData="editInoculationData"
          @confirm="handleConfirmStorage" />
        <ConfirmPreserveDialog :visible.sync="showConfirmPreserveDialog" :editData="editPreserveData"
          @confirm="handleConfirmPreserveSubmit" />
        <div class="end-btn" style="margin-top: 20px;" v-if="!$route.query.isDetail">
          <el-button type="primary" @click="handleSubmit(1)">提交</el-button>
          <el-button type="primary" @click="handleSubmit(3)">同步给保藏人</el-button>
          <el-button type="default" @click="handleSubmit(2)">存草稿</el-button>
        </div>
      </el-form>
    </Card>
@@ -217,7 +259,10 @@
import SlantRecordDialog from "./SlantRecordDialog.vue";
import InoculationSlopeRecordDialog from "./inoculation-slope-record-dialog.vue";
import PreserveStrainRecordDialog from "./preserve-strain-record-dialog.vue";
import {add} from './service'
import ConfirmStorageDialog from "./confirm-storage-dialog.vue";
import ConfirmPreserveDialog from "./confirm-preserve-dialog.vue";
import { add, detail, edit } from './service'
export default {
  components: {
    AiEditor,
@@ -225,6 +270,8 @@
    SlantRecordDialog,
    InoculationSlopeRecordDialog,
    PreserveStrainRecordDialog,
    ConfirmStorageDialog,
    ConfirmPreserveDialog,
  },
  name: "AddBreedingRecord",
  data() {
@@ -264,6 +311,7 @@
        approvalComment: '',
        approver: '',
        approveTime: '',
        culturePreservations: [], // 新增:菌种保藏记录数组
      },
      rules: {
        strainCode: [{ required: true, message: "请输入菌株编号", trigger: "blur" }],
@@ -288,38 +336,158 @@
      showObservationDialog: false,
      showInoculationDialog: false,
      showPreserveDialog: false,
      showConfirmStorageDialog: false,
      showConfirmPreserveDialog: false,
      editSeparationIndex: null,
      editSeparationData: null,
      editObservationData: null,
      editInoculationIndex: null,
      editInoculationData: null,
      editPreserveIndex: null,
      editPreserveData: null,
      roleType: JSON.parse(sessionStorage.getItem('userInfo')).roleType,  // 1.超级管理员 2.审批人 3.工程师 4.实验员
    };
  },
  computed: {
    totalInoculationCount() {
      // 只统计入库(保存)
      return this.form.vaccinationSlopes
        .filter(item => item.handleType === '保存')
        .length;
    },
    firstInoculationOrDiscardIndex() {
      // 第一个入库或废弃行
      return this.form.vaccinationSlopes.findIndex(item => item.handleType === '保存' || item.handleType === '废弃');
    }
  },
  mounted() {
    if (this.$route.query.id) {
      this.getDetail()
    }
  },
  methods: {
    async getDetail() {
      const res = await detail({ id: this.$route.query.id })
      if (res.preserveSource == 1) {
        this.activeTab = 'strain'
      } else {
        this.activeTab = 'material'
      }
      res.vaccinationSlopes = res.vaccinationSlopes.map(item => {
        return {
          ...item,
          handleType: item.handleType === 1 ? '保存' : '废弃'
        }
      })
      this.form = res
    },
    handleSeparationConfirm(data) {
      console.log('分离', data)
      if (this.editSeparationIndex !== null) {
        // 编辑
        this.$set(this.form.separationOfCultureDishesList, this.editSeparationIndex, data);
        this.resetSeparationEdit();
      } else {
        // 新增
        console.log('新增', data)
        this.form.separationOfCultureDishesList.push(data);
      }
    },
    handleObservationConfirm(data) {
      console.log("培养皿观察记录确认", data);
      if (this.editObservationData) {
        // 编辑
        const index = this.form.observationOfPetriDishes.findIndex(item => item.id === data.id);
        if (index !== -1) {
          this.$set(this.form.observationOfPetriDishes, index, data);
        }
      } else {
        // 新增
        this.form.observationOfPetriDishes.push({
          ...data,
          id: Date.now().toString(), // 临时ID,实际应该由后端生成
        });
      }
      this.editObservationData = null;
    },
    handleInoculationConfirm(data) {
      console.log("接种斜面记录确认", data);
      console.log('接种', data)
      if (this.editInoculationIndex !== null) {
        // 编辑模式:更新指定索引的数据
        this.$set(this.form.vaccinationSlopes, this.editInoculationIndex, {
          ...data,
          // 保留原有的ID和其他必要字段
          id: this.form.vaccinationSlopes[this.editInoculationIndex].id
        });
      } else {
        // 新增模式:添加新数据
        this.form.vaccinationSlopes.push({
          ...data,
          id: Date.now().toString() // 临时ID,实际应该由后端生成
        });
      }
      this.showInoculationDialog = false;
      this.editInoculationIndex = null;
      this.editInoculationData = null;
    },
    handlePreserveConfirm(data) {
      console.log("菌种保藏记录确认", data);
      if (this.editPreserveIndex !== null) {
        // 编辑
        this.$set(this.form.culturePreservations, this.editPreserveIndex, data);
      } else {
        // 新增
        this.form.culturePreservations.push(data);
      }
      this.showPreserveDialog = false;
      this.editPreserveIndex = null;
      this.editPreserveData = null;
    },
    handleSubmit() {
    handleSubmit(type) {
      if (this.activeTab === 'material') {
        this.form.preserveSource = 2
      } else {
        this.form.preserveSource = 1
      }
      if (type == 1) {
        this.form.status = 1
      } else if (type == 3) {
        this.form.isDraft = 2
      } else {
        this.form.isDraft = 3
      }
      this.$refs.form.validate(async (valid) => {
        if (!valid) return;
        try {
          await add(this.form);
          this.$message.success('添加成功');
          // 可选:跳转或重置表单
          // this.$router.push('/strain-library/breeding-record');
          if (this.form.vaccinationSlopes.length > 0) {
            let arr = this.form.vaccinationSlopes
            this.form.vaccinationSlopes = arr.map(item => {
              return {
                ...item,
                handleType: item.handleType === '保存' ? 1 : 2,
              }
            })
          }
          if (this.form.id && this.form.isDraft == 0) {
            edit(this.form).then(res => {
              if (res.code === 200) {
                this.$message.success('编辑成功');
                this.$router.back();
              } else {
                this.$message.error('编辑失败');
              }
            })
          } else {
            add(this.form).then(res => {
              if (res.code === 200) {
                this.$message.success('添加成功');
                this.$router.back();
              } else {
                this.$message.error('添加失败');
              }
            });
          }
        } catch (e) {
          this.$message.error('添加失败');
        }
@@ -342,6 +510,95 @@
      this.editSeparationData = null;
      this.showSeparationDialog = true;
    },
    handleAddObservation() {
      this.editObservationData = null;
      this.showObservationDialog = true;
    },
    handleEditObservation(row) {
      this.editObservationData = { ...row };
      this.showObservationDialog = true;
    },
    handleDeleteObservation(index) {
      this.form.observationOfPetriDishes.splice(index, 1);
    },
    handleAddInoculation() {
      this.editInoculationIndex = null;
      this.editInoculationData = null;
      this.showInoculationDialog = true;
    },
    handleEditInoculation(row, index) {
      this.editInoculationIndex = index;
      // 深拷贝当前行数据,避免直接修改原数据
      this.editInoculationData = JSON.parse(JSON.stringify(row));
      this.showInoculationDialog = true;
    },
    handleConfirmStorageClick(row, index) {
      this.editInoculationIndex = index;
      // 深拷贝当前行数据,避免直接修改原数据
      this.editInoculationData = JSON.parse(JSON.stringify(row));
      this.showConfirmStorageDialog = true;
    },
    handleConfirmStorage(data) {
      // 更新对应行的数据,保留原有数据并更新签名
      if (this.editInoculationIndex !== null) {
        this.$set(this.form.vaccinationSlopes, this.editInoculationIndex, {
          ...this.form.vaccinationSlopes[this.editInoculationIndex],
          preserveSignature: data.preserveSignature, // 更新签名数据
          status: '保存' // 更新状态为保存
        });
      }
      this.showConfirmStorageDialog = false;
      this.editInoculationIndex = null;
      this.editInoculationData = null;
    },
    handleDeleteInoculation(index) {
      this.form.vaccinationSlopes.splice(index, 1);
    },
    inoculationRowSpan({ row, rowIndex, columnIndex }) {
      // 入库总数(只)列合并
      if (columnIndex === 6) {
        // 合并所有入库或废弃行
        const mergeCount = this.form.vaccinationSlopes.filter(item => item.handleType === '保存' || item.handleType === '废弃').length;
        if (rowIndex === this.firstInoculationOrDiscardIndex) {
          return { rowspan: mergeCount, colspan: 1 };
        } else if (row.handleType === '保存' || row.handleType === '废弃') {
          return { rowspan: 0, colspan: 0 };
        }
      }
    },
    handleAddPreserve() {
      this.editPreserveIndex = null;
      this.editPreserveData = null;
      this.showPreserveDialog = true;
    },
    handleEditPreserve(row, index) {
      this.editPreserveIndex = index;
      this.editPreserveData = { ...row };
      this.showPreserveDialog = true;
    },
    handleDeletePreserve(index) {
      this.form.culturePreservations.splice(index, 1);
    },
    handleConfirmPreserve(row, index) {
      this.editPreserveIndex = index;
      // 深拷贝当前行数据,避免直接修改原数据
      this.editPreserveData = JSON.parse(JSON.stringify(row));
      this.showConfirmPreserveDialog = true;
    },
    handleConfirmPreserveSubmit(data) {
      console.log('确认保藏', data)
      // 更新对应行的数据,保留原有数据并更新签名
      if (this.editPreserveIndex !== null) {
        this.$set(this.form.culturePreservations, this.editPreserveIndex, {
          ...this.form.culturePreservations[this.editPreserveIndex],
          preserveSignature: data.preserveSignature, // 更新签名数据
          preserveTime: data.preserveTime,
        });
      }
      this.showConfirmPreserveDialog = false;
      this.editPreserveIndex = null;
      this.editPreserveData = null;
    },
  },
};
</script>
culture/src/views/strain-library/breeding-record/confirm-preserve-dialog.vue
New file
@@ -0,0 +1,146 @@
<template>
    <el-dialog
      :visible.sync="visible"
      title=""
      width="520px"
      :close-on-click-modal="false"
      custom-class="record-detail-dialog"
      @close="handleClose"
    >
      <div class="dialog-content">
        <div class="confirm-tip">
          是否确认该项菌种信息?
          <span class="danger">确认后将无法再次编辑菌种传代项内容</span>
        </div>
        <el-form :model="form" :rules="rules" ref="form" label-position="top">
          <el-form-item required>
            <template #label>
              <span>菌种保藏人签字</span>
              <el-button type="primary" class="sign-btn" @click="showSignature = true">签名</el-button>
            </template>
            <div class="signature-area" :class="{ 'waiting': !form.preserveSignature }">
              <template v-if="form.preserveSignature">
                <img :src="form.preserveSignature" alt="菌种保藏人签字" />
              </template>
              <template v-else>
                <span class="waiting-text">等待确认</span>
              </template>
            </div>
          </el-form-item>
        </el-form>
      </div>
      <div class="footer-btns">
        <el-button @click="handleClose" style="margin-right: 16px;">取消</el-button>
        <el-button type="primary" @click="handleConfirm">确认</el-button>
      </div>
      <signature-canvas :visible.sync="showSignature" @confirm="handleSignatureConfirm" />
    </el-dialog>
  </template>
  <script>
  import SignatureCanvas from '@/components/SignatureCanvas.vue';
  import moment from 'moment';
  export default {
    name: 'ConfirmStorageDialog',
    components: { SignatureCanvas },
    props: {
      visible: {
        type: Boolean,
        default: false
      },
      data: {
        type: Object,
        default: () => ({})
      }
    },
    data() {
      return {
        form: {
          preserveSignature: '',
          preserveTime:''
        },
        rules: {
          preserveSignature: [
            { required: true, message: '请签名', trigger: 'change' }
          ]
        },
        showSignature: false
      }
    },
    methods: {
      handleClose() {
        this.$emit('update:visible', false)
      },
      handleConfirm() {
        this.$refs.form.validate(valid => {
          if (!valid) return
          const confirmData = {
            ...this.data,
            preserveTime: moment().format('YYYY-MM-DD HH:mm:ss'),
            preserveSignature: this.form.preserveSignature
          }
          this.$emit('confirm', confirmData)
          this.handleClose()
        })
      },
      handleSignatureConfirm(dataUrl) {
        this.form.preserveSignature = dataUrl
        this.showSignature = false
      }
    }
  }
  </script>
  <style lang="less" scoped>
  .confirm-tip {
    color: #f5222d;
    font-size: 16px;
    margin-bottom: 24px;
    .danger {
      margin-left: 12px;
    }
  }
  .signature-area {
    height: 120px;
    width: 100%;
    background: #F5F7FA;
    border-radius: 4px;
    display: flex;
    align-items: center;
    justify-content: center;
    border: 1px solid #DCDFE6;
    overflow: hidden;
    padding: 0;
  }
  .signature-area.waiting {
    border-style: dashed;
    background: #FAFAFA;
  }
  .signature-area img {
    width: 100%;
    height: 100%;
    object-fit: cover;
    display: block;
  }
  .waiting-text {
    color: #909399;
    font-size: 14px;
  }
  .sign-btn {
    height: 32px;
    border-radius: 4px;
    font-size: 14px;
    padding: 0 20px;
    font-weight: 400;
    margin-left: 12px;
  }
  .footer-btns {
    display: flex;
    justify-content: center;
    padding: 24px;
    padding-top: 0;
    .el-button {
      width: 150px;
    }
  }
  </style>
culture/src/views/strain-library/breeding-record/confirm-storage-dialog.vue
@@ -18,9 +18,9 @@
            <span>菌种保藏人签字</span>
            <el-button type="primary" class="sign-btn" @click="showSignature = true">签名</el-button>
          </template>
          <div class="signature-area" :class="{ 'waiting': !form.signature }">
            <template v-if="form.signature">
              <img :src="form.signature" alt="菌种保藏人签字" />
          <div class="signature-area" :class="{ 'waiting': !form.preserveSignature }">
            <template v-if="form.preserveSignature">
              <img :src="form.preserveSignature" alt="菌种保藏人签字" />
            </template>
            <template v-else>
              <span class="waiting-text">等待确认</span>
@@ -46,15 +46,19 @@
    visible: {
      type: Boolean,
      default: false
    },
    data: {
      type: Object,
      default: () => ({})
    }
  },
  data() {
    return {
      form: {
        signature: ''
        preserveSignature: ''
      },
      rules: {
        signature: [
        preserveSignature: [
          { required: true, message: '请签名', trigger: 'change' }
        ]
      },
@@ -68,12 +72,16 @@
    handleConfirm() {
      this.$refs.form.validate(valid => {
        if (!valid) return
        this.$emit('confirm', { ...this.form })
        const confirmData = {
          ...this.data,
          preserveSignature: this.form.preserveSignature
        }
        this.$emit('confirm', confirmData)
        this.handleClose()
      })
    },
    handleSignatureConfirm(dataUrl) {
      this.form.signature = dataUrl
      this.form.preserveSignature = dataUrl
      this.showSignature = false
    }
  }
culture/src/views/strain-library/breeding-record/index.vue
@@ -1,68 +1,24 @@
<template>
  <div class="list">
    <TableCustom :queryForm="form" :tableData="tableData" :total="total">
    <TableCustom :queryForm="form" :tableData="tableData" :height="null" :total="total">
      <template #search>
        <el-form ref="searchForm" :model="form" :rules="rules" labelWidth="auto" inline>
          <el-form-item label="需氧类型">
            <el-input v-model="form.aerobicType" placeholder="请输入"></el-input>
          </el-form-item>
          <el-form-item label="创建人">
            <el-input v-model="form.createBy" placeholder="请输入"></el-input>
          </el-form-item>
          <el-form-item label="创建时间">
            <el-date-picker v-model="form.createTime" type="date" placeholder="选择日期" value-format="yyyy-MM-dd"></el-date-picker>
          </el-form-item>
          <el-form-item label="培养基">
            <el-input v-model="form.cultureMedium" placeholder="请输入"></el-input>
          </el-form-item>
          <el-form-item label="培养时间">
            <el-date-picker v-model="form.cultureTime" type="date" placeholder="选择日期" value-format="yyyy-MM-dd"></el-date-picker>
          </el-form-item>
          <el-form-item label="是否删除">
            <el-select v-model="form.disabled" placeholder="请选择">
              <el-option label="否" :value="0" />
              <el-option label="是" :value="1" />
            </el-select>
          </el-form-item>
          <el-form-item label="培养基配方">
            <el-input v-model="form.formula" placeholder="请输入"></el-input>
          </el-form-item>
          <el-form-item label="主键id">
            <el-input v-model="form.id" placeholder="请输入"></el-input>
          </el-form-item>
          <el-form-item label="来源类型">
            <el-select v-model="form.preserveSource" placeholder="请选择">
              <el-option label="来源菌株" :value="1" />
              <el-option label="来源物资" :value="2" />
            </el-select>
          </el-form-item>
          <el-form-item label="分离菌落编号">
            <el-input v-model="form.separateColonyNumber" placeholder="请输入"></el-input>
          <el-form-item label="培养基">
            <el-input v-model="form.cultureMedium" placeholder="请输入"></el-input>
          </el-form-item>
          <el-form-item label="来源物资存值批号">
            <el-input v-model="form.sourceMaterialTimeBatchNumber" placeholder="请输入"></el-input>
          </el-form-item>
          <el-form-item label="状态">
            <el-select v-model="form.status" placeholder="请选择">
              <el-option label="提交" :value="1" />
              <el-option label="审核通过" :value="2" />
              <el-option label="驳回" :value="3" />
          <el-form-item label="需氧类型">
            <el-select v-model="form.aerobicType" placeholder="请选择">
              <el-option label="专性需氧" :value="1" />
              <el-option label="专性厌氧" :value="2" />
              <el-option label="兼性需氧" :value="3" />
              <el-option label="耐氧厌氧" :value="4" />
            </el-select>
          </el-form-item>
          <el-form-item label="菌种编号">
            <el-input v-model="form.strainCode" placeholder="请输入"></el-input>
          </el-form-item>
          <el-form-item label="菌种名称">
            <el-input v-model="form.strainName" placeholder="请输入"></el-input>
          </el-form-item>
          <el-form-item label="培养温度">
            <el-input v-model="form.temperature" placeholder="请输入"></el-input>
          </el-form-item>
          <el-form-item label="修改人">
            <el-input v-model="form.updateBy" placeholder="请输入"></el-input>
          </el-form-item>
          <el-form-item label="修改时间">
            <el-date-picker v-model="form.updateTime" type="date" placeholder="选择日期" value-format="yyyy-MM-dd"></el-date-picker>
          </el-form-item>
          <el-form-item label="">
            <el-button type="default" @click="resetForm">重置</el-button>
@@ -76,79 +32,71 @@
            <div class="title" :class="{ active: currentType === 'list' }" @click="handleTypeChange('list')">
              菌种选育保藏记录
            </div>
            <div class="drafts" :class="{ active: currentType === 'draft' }" @click="handleTypeChange('draft')">
            <div class="drafts" v-if="roleType==4" :class="{ active: currentType === 'draft' }" @click="handleTypeChange('draft')">
              草稿箱
            </div>
          </div>
          <div class="flex a-center">
          <div class="flex a-center" v-if="roleType==4">
            <el-button @click="handleNewStrain" class="el-icon-plus" type="primary"
              style="margin-right: 12px">新增保藏记录</el-button>
          </div>
        </div>
      </template>
      <template #table>
        <el-table-column prop="preserveSource" label="来源类型"></el-table-column>
        <el-table-column prop="preserveSource" label="来源类型">
          <template slot-scope="scope">
            <span>{{ scope.row.preserveSource === 1 ? '来源菌株' : '来源物资' }}</span>
          </template>
        </el-table-column>
        <el-table-column prop="cultureMedium" label="培养基"></el-table-column>
        <el-table-column prop="formula" label="培养基配方"></el-table-column>
        <el-table-column prop="temperature" label="培养温度"></el-table-column>
        <el-table-column prop="aerobicType" label="需氧类型"></el-table-column>
        <el-table-column prop="aerobicType" label="需氧类型">
          <template slot-scope="scope">
            <span>{{ scope.row.aerobicType == 1 ? '专性需氧' : scope.row.aerobicType == 2 ? '专性厌氧' : scope.row.aerobicType
              ==
              3 ? '兼性需氧' : '耐氧厌氧'}}</span>
          </template>
        </el-table-column>
        <el-table-column prop="createBy" label="创建人"></el-table-column>
        <el-table-column prop="createTime" label="创建时间"></el-table-column>
        <el-table-column prop="cultureTime" label="培养时间"></el-table-column>
        <el-table-column prop="strainCode" label="菌种编号"></el-table-column>
        <el-table-column prop="strainName" label="菌种名称"></el-table-column>
        <el-table-column label="操作" width="250">
          <template slot-scope="scope">
            <el-button type="text" @click="handleDetail(scope.row)">详情</el-button>
            <el-button type="text" @click="handleEdit(scope.row)">编辑</el-button>
            <el-button type="text" @click="handleDelete(scope.row)">删除</el-button>
            <el-button type="text" @click="handleEdit(scope.row)"
              v-if="([1].includes(roleType) || ([1, 4].includes(roleType) && currentType != 'list'))">编辑</el-button>
            <el-button type="text" @click="handleDelete(scope.row)"
              v-if="[1].includes(roleType) || ([1, 4].includes(roleType) && currentType != 'list')">删除</el-button>
          </template>
        </el-table-column>
      </template>
    </TableCustom>
    <el-pagination
      :current-page="form.pageNum"
      :page-size="form.pageSize"
      :total="total"
      @current-change="handlePageChange"
      @size-change="handleSizeChange"
      layout="total, sizes, prev, pager, next, jumper"
      :page-sizes="[10, 20, 50, 100]"
      style="margin-top: 20px; text-align: right;"
    />
    <ShowDelConfirm :show="showDelConfirm" @close="showDelConfirm = false" @confirm="handleDelConfirm" />
  </div>
</template>
<script>
import { getList } from './service'
import { getList, del } from './service'
import ShowDelConfirm from '@/components/showDelConfirm'
export default {
  name: "BreedingRecord",
  components: {},
  components: {
    ShowDelConfirm
  },
  data() {
    return {
      currentType: "list", // 当前显示类型:list-列表,draft-草稿箱
      form: {
        aerobicType: '',
        createBy: '',
        createTime: '',
        cultureMedium: '',
        cultureTime: '',
        disabled: '',
        formula: '',
        id: '',
        preserveSource: '',
        separateColonyNumber: '',
        sourceMaterialTimeBatchNumber: '',
        status: '',
        strainCode: '',
        strainName: '',
        temperature: '',
        updateBy: '',
        updateTime: '',
        pageNum: 1,
        pageSize: 10,
        isDraft: 0,
      },
      showDelConfirm: false,
      rules: {
        aerobicType: [
          { required: true, message: '需氧类型不能为空', trigger: 'blur' }
@@ -167,65 +115,9 @@
        ],
      },
      tableData: [],
      delData: null,
      total: 0,
      // 模拟数据
      mockListData: [
        {
          planCode: "PLAN-2024-001",
          planName: "2024年度实验室设备升级方案",
          stage: "规划阶段",
          creator: "张三",
          createTime: "2024-03-15",
          status: "pending",
          approver: "李四",
          approveTime: "2024-03-16",
        },
        {
          planCode: "PLAN-2024-002",
          planName: "实验室安全管理制度更新方案",
          stage: "实施阶段",
          creator: "王五",
          createTime: "2024-03-14",
          status: "approved",
          approver: "赵六",
          approveTime: "2024-03-15",
        },
        {
          planCode: "PLAN-2024-003",
          planName: "实验室人员培训计划",
          stage: "准备阶段",
          creator: "孙七",
          createTime: "2024-03-13",
          status: "rejected",
          approver: "周八",
          approveTime: "2024-03-14",
        },
      ],
      mockDraftData: [
        {
          planCode: "DRAFT-2024-001",
          planName: "实验室设备采购计划(草稿)",
          stage: "规划阶段",
          creator: "张三",
          createTime: "2024-03-16",
          status: "draft",
          approver: "",
          approveTime: "",
        },
        {
          planCode: "DRAFT-2024-002",
          planName: "实验室改造方案(草稿)",
          stage: "准备阶段",
          creator: "李四",
          createTime: "2024-03-15",
          status: "draft",
          approver: "",
          approveTime: "",
        },
      ],
      approvalDialogVisible: false,
      approvalDialogType: "approve",
      currentApprovalData: null,
      roleType: JSON.parse(sessionStorage.getItem('userInfo')).roleType,
    };
  },
  created() {
@@ -235,26 +127,15 @@
    resetForm() {
      this.form = {
        aerobicType: '',
        createBy: '',
        createTime: '',
        cultureMedium: '',
        cultureTime: '',
        disabled: '',
        formula: '',
        id: '',
        preserveSource: '',
        separateColonyNumber: '',
        sourceMaterialTimeBatchNumber: '',
        status: '',
        strainCode: '',
        strainName: '',
        temperature: '',
        updateBy: '',
        updateTime: '',
        pageNum: 1,
        pageSize: 10,
        isDraft: this.currentType === 'draft' ? 1 : 0,
      };
      this.getTableData();
    },
    handleNewStrain() {
      this.$router.push({
@@ -303,42 +184,36 @@
        path: "/dataManagement/addPlan",
      });
    },
    handleApprove(row) {
      this.currentApprovalData = row;
      this.approvalDialogType = "approve";
      this.approvalDialogVisible = true;
    },
    handleApproveSubmit(data) {
      // 处理审批通过
      console.log("审批通过:", data);
      this.approvalDialogVisible = false;
      this.$message.success("审批通过成功");
      this.getTableData();
    },
    handleRejectSubmit(data) {
      // 处理审批驳回
      console.log("审批驳回:", data);
      this.approvalDialogVisible = false;
      this.$message.success("审批驳回成功");
      this.getTableData();
    },
    handleRevokeApprove(row) {
      // 实现撤销审批逻辑
      console.log("撤销审批数据:", row);
    },
    handleEdit(row) {
      // 实现编辑逻辑
      console.log("编辑数据:", row);
    },
    handleDelete(row) {
      // 实现删除逻辑
      console.log("删除数据:", row);
      this.showDelConfirm = true
      this.delData = row
    },
    handleDelConfirm() {
      del({ id: this.delData.id }).then(res => {
        this.$message.success('删除成功');
        this.showDelConfirm = false
        this.getTableData();
      })
    },
    handleDetail(row) {
      this.currentApprovalData = row;
      this.approvalDialogType = "view";
      this.approvalDialogVisible = true;
      this.$router.push({
        path: "/strain/detail-breeding-record",
        query: {
          id: row.id,
          isDetail: true
        }
      });
    },
    handleEdit(row) {
      this.$router.push({
        path: "/strain/edit-breeding-record",
        query: {
          id: row.id,
          isEdit: true
        }
      });
    },
    handleTypeChange(type) {
      this.currentType = type;
      this.form.isDraft = type === 'draft' ? 1 : 0;
@@ -347,7 +222,7 @@
    },
    getTableData() {
      getList({ ...this.form }).then(res => {
        this.tableData = res.data.list || [];
        this.tableData = res.data.records || [];
        this.total = res.data.total || 0;
      }).catch(() => {
        this.tableData = [];
culture/src/views/strain-library/breeding-record/inoculation-slope-record-dialog.vue
@@ -14,42 +14,42 @@
    >
      <el-row :gutter="20">
        <el-col :span="12">
          <el-form-item label="分离菌落编号" prop="colonyCode" required>
            <el-input v-model="form.colonyCode" placeholder="请输入" />
          <el-form-item label="分离菌落编号" prop="separateColonyCode">
            <el-input v-model="form.separateColonyCode" placeholder="请输入" />
          </el-form-item>
        </el-col>
        <el-col :span="12">
          <el-form-item label="接种斜面编号" prop="slopeCode" required>
            <el-input v-model="form.slopeCode" placeholder="请输入" />
          <el-form-item label="接种斜面编号" prop="vaccinationSlopeCode">
            <el-input v-model="form.vaccinationSlopeCode" placeholder="请输入" />
          </el-form-item>
        </el-col>
      </el-row>
      <el-form-item label="保存/废弃" prop="status" required>
      <el-form-item label="保存/废弃" prop="handleType">
        <el-button
          :type="form.status === '保存' ? 'primary' : 'default'"
          @click="form.status = '保存'"
          :type="form.handleType === '保存' ? 'primary' : 'default'"
          @click="form.handleType = '保存'"
          >保存</el-button
        >
        <el-button
          :type="form.status === '废弃' ? 'primary' : 'default'"
          @click="form.status = '废弃'"
          :type="form.handleType === '废弃' ? 'primary' : 'default'"
          @click="form.handleType = '废弃'"
          >废弃</el-button
        >
      </el-form-item>
      <el-row :gutter="20">
        <el-col :span="12">
          <el-form-item label="菌种入库时间" prop="storageTime" required>
          <el-form-item label="菌种入库时间" prop="preserveTime">
            <el-input
              v-model="form.storageTime"
              v-model="form.preserveTime"
              disabled
              placeholder="自动回填"
            />
          </el-form-item>
        </el-col>
        <el-col :span="12">
          <el-form-item label="接种操作时间" prop="operationTime" required>
          <el-form-item label="接种操作时间" prop="handleTime">
            <el-input
              v-model="form.operationTime"
              v-model="form.handleTime"
              disabled
              placeholder="自动填入"
            />
@@ -58,19 +58,24 @@
      </el-row>
      <el-row :gutter="20">
        <el-col :span="12">
          <el-form-item required>
          <el-form-item>
            <template #label>
              <span>接种操作人签字</span>
              <el-button type="primary" class="sign-btn" @click="showSignature = true">签名</el-button>
            </template>
            <div class="signature-area" :class="{ 'waiting': !form.signature }">
              <template v-if="form.signature">
                <img :src="form.signature" alt="接种操作人签字" />
            <div class="signature-area" :class="{ 'waiting': !form.handleSignature }">
              <template v-if="form.handleSignature">
                <img :src="form.handleSignature" alt="接种操作人签字" />
              </template>
              <template v-else>
                <span class="waiting-text">等待确认</span>
              </template>
            </div>
          </el-form-item>
        </el-col>
        <el-col :span="12">
          <el-form-item label="接种操作人" prop="handleName">
            <el-input v-model="form.handleName" disabled placeholder="自动填入" />
          </el-form-item>
        </el-col>
      </el-row>
@@ -88,31 +93,50 @@
export default {
  components: { SignatureCanvas },
  props: { visible: Boolean },
  props: {
    visible: Boolean,
    editData: {
      type: Object,
      default: () => ({})
    }
  },
  data() {
    return {
      form: {
        colonyCode: "",
        slopeCode: "",
        status: "保存",
        storageTime: this.getNowTime(),
        operationTime: this.getNowTime(),
        signature: "",
        separateColonyCode: "",
        vaccinationSlopeCode: "",
        handleType: "保存",
        preserveTime: this.getNowTime(),
        handleTime: this.getNowTime(),
        handleSignature: "",
        handleName: JSON.parse(sessionStorage.getItem('userInfo'))?.nickName || '',
        handleId: JSON.parse(sessionStorage.getItem('userInfo'))?.userId || '',
      },
      rules: {
        colonyCode: [
        separateColonyCode: [
          { required: true, message: "请输入分离菌落编号", trigger: "blur" },
        ],
        slopeCode: [
        vaccinationSlopeCode: [
          { required: true, message: "请输入接种斜面编号", trigger: "blur" },
        ],
        status: [
        handleType: [
          { required: true, message: "请选择保存/废弃", trigger: "change" },
        ],
        signature: [{ required: true, message: "请签名", trigger: "change" }],
        handleSignature: [{ required: true, message: "请签名", trigger: "change" }],
      },
      showSignature: false,
    };
  },
  watch: {
    visible(val) {
      if (val && this.editData) {
        // 当对话框显示且有编辑数据时,进行数据回显
        this.form = {
          ...this.form,
          ...this.editData
        };
      }
    }
  },
  methods: {
    getNowTime() {
@@ -130,18 +154,35 @@
      );
    },
    handleSave() {
      console.log(this.form)
      this.$refs.form.validate((valid) => {
        if (valid) {
          this.$emit("save", { ...this.form });
          this.handleClose();
        }
      });
    },
    handleClose() {
      this.$emit("update:visible", false);
      // 重置表单数据
      this.form = {
        separateColonyCode: "",
        vaccinationSlopeCode: "",
        handleType: "保存",
        preserveTime: this.getNowTime(),
        handleTime: this.getNowTime(),
        handleSignature: "",
        handleName: JSON.parse(sessionStorage.getItem('userInfo'))?.nickName || '',
        handleId: JSON.parse(sessionStorage.getItem('userInfo'))?.userId || '',
      };
      // 重置表单验证
      this.$nextTick(() => {
        this.$refs.form && this.$refs.form.clearValidate();
      });
    },
    handleSignatureConfirm(dataUrl) {
      this.form.signature = dataUrl;
      this.form.handleSignature = dataUrl;
      this.showSignature = false;
    },
  },
culture/src/views/strain-library/breeding-record/preserve-strain-record-dialog.vue
@@ -14,36 +14,36 @@
    >
      <el-row :gutter="20">
        <el-col :span="12">
          <el-form-item label="用于保藏的菌种编号" prop="strainCode" required>
            <el-input v-model="form.strainCode" placeholder="请输入" />
          <el-form-item label="用于保藏的菌种编号" prop="forPreserveCode">
            <el-input v-model="form.forPreserveCode" placeholder="请输入" />
          </el-form-item>
        </el-col>
      </el-row>
      <el-form-item label="实验验证结论" prop="experimentConclusion" required>
        <el-input type="textarea" :rows="4" v-model="form.experimentConclusion" placeholder="请输入" />
      <el-form-item label="实验验证结论" prop="verificationConclusion">
        <el-input type="textarea" :rows="4" v-model="form.verificationConclusion" placeholder="请输入" />
      </el-form-item>
      <el-row :gutter="20">
        <el-col :span="12">
          <el-form-item label="保藏方法" prop="preserveMethod" required>
          <el-form-item label="保藏方法" prop="preserveMethod">
            <el-input v-model="form.preserveMethod" placeholder="请输入" />
          </el-form-item>
        </el-col>
        <el-col :span="12">
          <el-form-item label="保藏菌种编号" prop="preserveStrainCode" required>
            <el-input v-model="form.preserveStrainCode" placeholder="请输入" />
          <el-form-item label="保藏菌种编号" prop="preserveCode">
            <el-input v-model="form.preserveCode" placeholder="请输入" />
          </el-form-item>
        </el-col>
      </el-row>
      <el-row :gutter="20">
        <el-col :span="12">
          <el-form-item required>
          <el-form-item>
            <template #label>
              <span>操作人签字</span>
              <span>操作人签名</span>
              <el-button type="primary" class="sign-btn" @click="showSignature = true">签名</el-button>
            </template>
            <div class="signature-area" :class="{ 'waiting': !form.signature }">
              <template v-if="form.signature">
                <img :src="form.signature" alt="操作人签字" />
            <div class="signature-area" :class="{ 'waiting': !form.handleSignature }">
              <template v-if="form.handleSignature">
                <img :src="form.handleSignature" alt="操作人签名" />
              </template>
              <template v-else>
                <span class="waiting-text">等待确认</span>
@@ -63,40 +63,59 @@
<script>
import SignatureCanvas from '@/components/SignatureCanvas.vue';
import moment from 'moment';
export default {
  components: { SignatureCanvas },
  props: { visible: Boolean },
  props: {
    visible: Boolean,
    editData: {
      type: Object,
      default: () => ({})
    }
  },
  data() {
    return {
      form: {
        strainCode: '',
        experimentConclusion: '',
        forPreserveCode: '',
        verificationConclusion: '',
        preserveMethod: '',
        preserveStrainCode: '',
        signature: '',
        preserveCode: '',
        handleTime: '',
        handleSignature: '',
      },
      rules: {
        strainCode: [
          { required: true, message: '请输入用于保藏的菌种编号', trigger: 'blur' },
        forPreserveCode: [
          { required: true, message: '请输入用于保藏菌种编号', trigger: 'blur' },
        ],
        experimentConclusion: [
        verificationConclusion: [
          { required: true, message: '请输入实验验证结论', trigger: 'blur' },
        ],
        preserveMethod: [
          { required: true, message: '请输入保藏方法', trigger: 'blur' },
        ],
        preserveStrainCode: [
        preserveCode: [
          { required: true, message: '请输入保藏菌种编号', trigger: 'blur' },
        ],
        signature: [
        preserveSignature: [
          { required: true, message: '请签名', trigger: 'change' },
        ],
      },
      showSignature: false,
    };
  },
  watch: {
    visible(val) {
      if (val && this.editData) {
        this.form = {
          ...this.form,
          ...this.editData
        };
      }
    }
  },
  methods: {
    handleSave() {
      this.form.handleTime = moment().format('YYYY-MM-DD HH:mm:ss');
      this.$refs.form.validate((valid) => {
        if (valid) {
          this.$emit('save', { ...this.form });
@@ -106,9 +125,17 @@
    },
    handleClose() {
      this.$emit('update:visible', false);
      this.form = {
        forPreserveCode: '',
        verificationConclusion: '',
        preserveMethod: '',
        preserveCode: '',
        handleSignature: '',
        handleTime: '',
      };
    },
    handleSignatureConfirm(dataUrl) {
      this.form.signature = dataUrl;
      this.form.handleSignature = dataUrl;
      this.showSignature = false;
    },
  },
culture/src/views/strain-library/breeding-record/separation-record-dialog.vue
@@ -18,9 +18,9 @@
              <span>操作人签字</span>
              <el-button type="primary" class="sign-btn" @click="showSignature = true">签名</el-button>
            </template>
            <div class="signature-area" :class="{ 'waiting': !formData.operatorSignature }">
              <template v-if="formData.operatorSignature">
                <img :src="formData.operatorSignature" alt="操作人签字" />
            <div class="signature-area" :class="{ 'waiting': !formData.handleSignature }">
              <template v-if="formData.handleSignature">
                <img :src="formData.handleSignature" alt="操作人签字" />
              </template>
              <template v-else>
                <span class="waiting-text">等待确认</span>
@@ -57,7 +57,7 @@
      return {
        formData: {
          separateBacterialColoniesCode: '',
          operatorSignature: ''
          handleSignature: ''
        },
        showSignature: false,
        isEdit: false
@@ -80,7 +80,7 @@
      resetForm() {
        this.formData = {
          separateBacterialColoniesCode: '',
          operatorSignature: ''
          handleSignature: ''
        }
      },
      handleClose() {
@@ -89,7 +89,7 @@
        this.resetForm()
      },
      handleConfirm() {
        if (!this.formData.operatorSignature) {
        if (!this.formData.handleSignature) {
          this.$message.warning('请先签名')
          return
        }
@@ -98,12 +98,12 @@
        this.$emit('confirm', this.formData)
        this.formData = {
          separateBacterialColoniesCode: '',
          operatorSignature: ''
          handleSignature: ''
        }
        this.handleClose()
      },
      handleSignatureConfirm(dataUrl) {
        this.formData.operatorSignature = dataUrl
        this.formData.handleSignature = dataUrl
        this.showSignature = false
      }
    }
culture/src/views/strain-library/breeding-record/service.js
@@ -2,19 +2,42 @@
// 列表
export const getList = (data) => {
  return axios.post('/api/t-breeding-and-preservation/pageList', { ...data })
  console.log('qweqwe', data);
  return axios.post('/api/t-breeding-and-preservation/pageList', {
    ...data
  })
}
//新增
export const add = (data) => {
  console.log('qweqwe',data);
  return axios.post('/api/t-breeding-and-preservation/add', { ...data })
  console.log('qweqwe', data);
  return axios.post('/api/t-breeding-and-preservation/add', {
    ...data
  })
}
//编辑
export const edit = (data) => {
    console.log('qweqwe',data);
    return axios.post('/api/t-breeding-and-preservation/update', { ...data })
  }
  console.log('qweqwe', data);
  return axios.post('/api/t-breeding-and-preservation/update', {
    ...data
  })
}
// 删除
export const del = (data) => {
  return axios.delete('/open/t_project_team/deleteBreedingAndPreservation', {
    params: data
  })
}
// 详情
export const detail = (data) => {
  return axios.get('/open/t-breeding-and-preservation/getDetailById', {
    params: data
  })
}