puzhibing
6 天以前 0db8c9b94a9ac655549d5eba5c2a6c92ac386c06
culture/src/views/strain-library/validation/primitive-cell/confirm-detail.vue
@@ -1,71 +1,138 @@
<template>
  <Card class="confirm-detail-page">
    <el-card class="top-card">
    <!-- 顶部信息卡片 - 优化排版 -->
    <el-card class="top-card" shadow="never">
      <el-row :gutter="24" class="top-info-row">
        <el-col :span="8" class="info-col">
          <div class="info-item"><span class="label">菌种来源:</span>{{ detail.source }}</div>
          <div class="info-item"><span class="label">鉴别菌株编号:</span>{{ detail.strainNo }}</div>
          <div class="info-item"><span class="label">鉴别菌株名称:</span>{{ detail.strainName }}</div>
        <el-col :span="24" class="info-col source-col">
          <div class="info-item source-item">
            <span class="label">菌种来源:</span>
            <el-tooltip
                :content="detail.strainSource || '暂无数据'"
                placement="top"
                :disabled="!detail.strainSource || detail.strainSource.length < 30"
                popper-class="source-tooltip"
                effect="dark"
            >
              <span class="source-text">{{ detail.strainSource || '暂无数据' }}</span>
            </el-tooltip>
          </div>
        </el-col>
        <el-col :span="8" class="info-col">
          <div class="info-item"><span class="label">验证实验编号:</span>{{ detail.verifyNo }}</div>
          <div class="info-item"><span class="label">实验时间:</span>{{ detail.experimentTime }}</div>
          <div class="info-item"></div>
          <div class="info-item">
            <span class="label">鉴别菌株编号:</span>
            <span class="value">{{ detail.identifyingStrainCode || '-' }}</span>
          </div>
          <div class="info-item">
            <span class="label">鉴别菌株名称:</span>
            <span class="value">{{ detail.identifyingStrainName || '-' }}</span>
          </div>
        </el-col>
        <el-col :span="8" class="info-col ">
          <div class="info-item sign-label"><span class="label">菌种实验员签字</span></div>
          <div class="info-item signature-item">
        <el-col :span="8" class="info-col">
          <div class="info-item">
            <span class="label">验证实验编号:</span>
            <span class="value">{{ detail.validationExperimentCode || '-' }}</span>
          </div>
          <div class="info-item">
            <span class="label">实验时间:</span>
            <span class="value">{{ detail.experimentTime | formatDate }}</span>
          </div>
          <div class="info-item">
            <span class="label">签字时间:</span>
            <span class="value">{{ detail.handleTime || '-' }}</span>
          </div>
        </el-col>
        <el-col :span="8" class="info-col sign-col">
          <div class="sign-wrapper">
            <div class="sign-label-text">菌种实验员签字</div>
            <div class="signature-area">
              <img v-if="detail.signature" :src="detail.signature" alt="签字" />
              <el-image
                  v-if="detail.handleSignature"
                  :src="getFullUrl(detail.handleSignature)"
                  :preview-src-list="[getFullUrl(detail.handleSignature)]"
                  alt="签字"
                  fit="contain"
              />
              <span v-else class="waiting-text">暂无签名</span>
            </div>
          </div>
        </el-col>
      </el-row>
    </el-card>
    <div class="section-card" style="margin-top: 24px;">
      <el-form label-width="100px" label-position="top">
        <el-form-item label="实验结论">
          <el-input type="textarea" v-model="detail.conclusion" :rows="3" placeholder="请输入" />
        </el-form-item>
        <el-form-item label="批准菌株用途">
          <el-checkbox-group v-model="detail.usage">
            <el-checkbox label="传代" />
            <el-checkbox label="菌种保藏" />
            <el-checkbox label="废弃" />
          </el-checkbox-group>
        </el-form-item>
    <!-- 实验结论 & 用途 -->
    <div class="section-card">
      <el-form ref="form" label-width="100px" :model="detail" :rules="rules" label-position="top">
        <el-row :gutter="24">
          <el-col :span="14">
            <el-form-item label="实验结论" prop="result">
              <el-input
                  type="textarea"
                  v-model="detail.result"
                  :rows="3"
                  placeholder="请输入实验结论"
                  maxlength="500"
                  show-word-limit
              />
            </el-form-item>
          </el-col>
          <el-col :span="10">
            <el-form-item label="批准菌株用途" prop="purpose">
              <el-checkbox-group v-model="detail.purpose">
                <el-checkbox :label="1">传代</el-checkbox>
                <el-checkbox :label="2">菌种保藏</el-checkbox>
                <el-checkbox :label="3">废弃</el-checkbox>
              </el-checkbox-group>
            </el-form-item>
          </el-col>
        </el-row>
      </el-form>
    </div>
    <el-button type="primary" @click="handleSave" class="el-icon-plus"> 新增</el-button>
    <EditConditionDialog
      :visible.sync="dialogVisible"
      :isEdit="dialogIsEdit"
      :isFixed="dialogIsFixed"
      :value="dialogValue"
      @ok="handleDialogOk"
    />
    <div class="section-card" style="margin-top: 12px;">
      <el-table :data="tableData" border style="width: 100%;" :row-class-name="getRowClassName">
        <el-table-column prop="condition" label="菌种培养工艺条件" />
        <el-table-column prop="record" label="菌种培养工艺实况记录" />
        <el-table-column prop="process" label="菌种培养标准工艺" />
        <el-table-column label="操作" width="120">
    <!-- 表格区域 -->
    <div class="table-section">
      <div class="table-header">
        <span class="table-title">菌种培养工艺</span>
        <el-button type="primary" size="small" icon="el-icon-plus" @click="handleAdd">新增</el-button>
      </div>
      <el-table
          :data="tableData"
          border
          style="width: 100%"
          :row-class-name="getRowClassName"
          stripe
      >
        <el-table-column prop="conditions" label="菌种培养工艺条件" min-width="180" />
        <el-table-column prop="record" label="菌种培养工艺实况记录" min-width="200" />
        <el-table-column prop="workmanship" label="菌种培养标准工艺" min-width="200" />
        <el-table-column label="操作" width="160" align="center">
          <template #default="{ row }">
            <el-button type="text" @click="handleEdit(row)">编辑</el-button>
            <el-button type="text" @click="handleDetail(row)">详情2</el-button>
            <el-button type="text" size="small" @click="handleEdit(row)">编辑</el-button>
            <el-button type="text" size="small" @click="handleDetail(row)">详情</el-button>
          </template>
        </el-table-column>
      </el-table>
    </div>
    <!-- 底部按钮 -->
    <div class="footer-btns">
      <el-button type="primary" @click="handleSave">保存</el-button>
      <el-button @click="handleDraft">存稿</el-button>
      <el-button type="primary" size="medium" @click="handleSave">保存</el-button>
      <el-button size="medium" @click="handleDraft">存稿</el-button>
    </div>
    <!-- 弹窗 -->
    <EditConditionDialog
        :visible.sync="dialogVisible"
        :isEdit="dialogIsEdit"
        :isFixed="dialogIsFixed"
        :value="dialogValue"
        @ok="handleDialogOk"
    />
    <DetailConditionDialog
      :visible.sync="detailDialogVisible"
      :value="detailDialogValue"
        :visible.sync="detailDialogVisible"
        :value="detailDialogValue"
    />
  </Card>
</template>
@@ -73,74 +140,258 @@
<script>
import EditConditionDialog from './EditConditionDialog.vue'
import DetailConditionDialog from './DetailConditionDialog.vue'
import {detail, confirm, saveDraft} from './service'
import moment from 'moment'
import { getFullUrl } from '@/utils/utils'
export default {
  name: 'ConfirmDetail',
  components: { EditConditionDialog, DetailConditionDialog },
  data() {
    return {
      detail: {
        source: '内容的内容内容内容',
        strainNo: '3411234',
        strainName: '名称名称名称',
        verifyNo: '34133214',
        experimentTime: '2025-1-23 11:10:28',
        signature: '', // 签名图片url
        conclusion: '',
        usage: []
        strainSource: '',
        identifyingStrainCode: '',
        identifyingStrainName: '',
        validationExperimentCode: '',
        experimentTime: '',
        handleSignature: '',
        result: '',
        purpose: []
      },
      activeTab: 'condition',
      rules: {
        result: [
          { required: true, message: '请输入实验结论', trigger: 'blur' }
        ],
        purpose: [
          { required: true, message: '请选择菌种用途', trigger: 'change' }
        ]
      },
      initialTableData: [
        { condition: '平板培养基', record: '文字内容文字内容文字内容文字内容文字内容文字内容', process: '文字内容文字内容文字内容文字内容文字内容文字内容' },
        { condition: '培养温度', record: '', process: '' },
        { condition: '培养时间', record: '', process: '' },
        { condition: '摇瓶培养基', record: '', process: '' },
        { condition: '接种量', record: '', process: '' },
        { condition: '培养时间', record: '', process: '' },
        { condition: '发酵时间', record: '', process: '' },
        { condition: '检测数据及结果', record: '', process: '' }
        { conditions: '平板培养基', record: '', workmanship: '' },
        { conditions: '培养温度', record: '', workmanship: '' },
        { conditions: '培养时间', record: '', workmanship: '' },
        { conditions: '摇瓶培养基', record: '', workmanship: '' },
        { conditions: '接种量', record: '', workmanship: '' },
        { conditions: '培养时间', record: '', workmanship: '' },
        { conditions: '发酵时间', record: '', workmanship: '' },
        { conditions: '检测数据及结果', record: '', workmanship: '' }
      ],
      tableData: [
        { condition: '平板培养基', record: '文字内容文字内容文字内容文字内容文字内容文字内容', process: '文字内容文字内容文字内容文字内容文字内容文字内容' },
        { condition: '培养温度', record: '', process: '' },
        { condition: '培养时间', record: '', process: '' },
        { condition: '摇瓶培养基', record: '', process: '' },
        { condition: '接种量', record: '', process: '' },
        { condition: '培养时间', record: '', process: '' },
        { condition: '发酵时间', record: '', process: '' },
        { condition: '检测数据及结果', record: '', process: '' }
      ],
      tableData: [],
      dialogVisible: false,
      dialogIsEdit: false,
      dialogIsFixed: false,
      dialogValue: {},
      dialogIndex: null,
      detailDialogVisible: false,
      detailDialogValue: {}
      detailDialogValue: {},
      cacheForm: null,
      cacheFormtableData: null,
      isFirstLoad: true,
      isLoading: false,
    }
  },
  filters: {
    formatDate(date) {
      if (date) {
        return moment(date).format('YYYY-MM-DD')
      }
      return ''
    }
  },
  // ========== 只使用 activated ==========
  activated() {
    console.log('activated, isFirstLoad:', this.isFirstLoad)
    // 1. 首次加载 - 初始化数据并请求
    if (this.isFirstLoad) {
      this.isFirstLoad = false
      // 初始化表格数据(显示默认行)
      this.tableData = this.initialTableData.map(item => ({ ...item }))
      if (this.$route.query.id) {
        this.getDetail()
      }
      return
    }
    // 2. 从缓存恢复数据(非首次加载)
    if (this.cacheForm) {
      this.detail = JSON.parse(JSON.stringify(this.cacheForm))
      this.tableData = JSON.parse(JSON.stringify(this.cacheFormtableData))
      this.restorePurpose()
    }
    // 3. 如果路由参数变化,重新加载数据
    if (this.$route.query.id && this.detail.id && this.$route.query.id !== String(this.detail.id)) {
      this.getDetail()
    }
  },
  deactivated() {
    // 缓存当前数据
    this.cacheForm = JSON.parse(JSON.stringify(this.detail))
    this.cacheFormtableData = JSON.parse(JSON.stringify(this.tableData))
    console.log('deactivated - 数据已缓存')
  },
  // ========== 移除 mounted,或保留仅用于调试 ==========
  mounted() {
    // 不做任何初始化,所有逻辑都在 activated 中
    console.log('mounted - 仅用于调试,无初始化逻辑')
  },
  // ========== 方法 ==========
  methods: {
    getFullUrl(url) {
      if (url) {
        return getFullUrl(url)
      }
      return ''
    },
    restorePurpose() {
      if (!this.detail.purpose) {
        this.detail.purpose = []
        return
      }
      let purposeArray = []
      if (typeof this.detail.purpose === 'string') {
        purposeArray = this.detail.purpose.split(',').map(item => Number(item.trim())).filter(num => !isNaN(num))
      } else if (Array.isArray(this.detail.purpose)) {
        purposeArray = this.detail.purpose
      }
      this.detail.purpose = purposeArray
    },
    getDetail() {
      if (this.isLoading) {
        console.log('正在加载中,跳过重复请求')
        return
      }
      const id = this.$route.query.id
      if (!id) {
        console.warn('缺少 id 参数')
        return
      }
      this.isLoading = true
      console.log('开始请求详情数据, id:', id)
      detail({ id })
          .then(res => {
            console.log('详情数据返回:', res)
            const data = res.data || res
            // 处理 purpose
            let purposeArray = []
            if (data.purpose) {
              if (typeof data.purpose === 'string') {
                purposeArray = data.purpose.split(',').map(item => Number(item.trim())).filter(num => !isNaN(num))
              } else if (Array.isArray(data.purpose)) {
                purposeArray = data.purpose
              }
            }
            // 处理 tableData
            const tableList = data.list && data.list.length > 0 ? data.list : this.initialTableData.map(item => ({ ...item }))
            // 更新数据
            this.detail = {
              ...data,
              handleSignature: data.handleSignature || '',
              purpose: purposeArray
            }
            this.tableData = tableList
            // 更新缓存
            this.cacheForm = JSON.parse(JSON.stringify(this.detail))
            this.cacheFormtableData = JSON.parse(JSON.stringify(this.tableData))
          })
          .catch(err => {
            console.error('获取详情失败:', err)
            this.$message.error('加载数据失败,请重试')
            // 加载失败时使用初始数据
            this.tableData = this.initialTableData.map(item => ({ ...item }))
          })
          .finally(() => {
            this.isLoading = false
          })
    },
    handleAdd() {
      this.dialogVisible = true
      this.dialogIsEdit = false
      this.dialogIsFixed = false
      this.dialogValue = {}
      this.dialogIndex = null
    },
    handleEdit(row) {
      const idx = this.tableData.indexOf(row)
      this.dialogVisible = true
      this.dialogIsEdit = true
      this.dialogIsFixed = idx < this.initialTableData.length
      this.dialogIsFixed = idx < this.initialTableData?.length
      this.dialogValue = { ...row }
      this.dialogIndex = idx
    },
    handleDetail(row) {
      this.detailDialogVisible = true
      this.detailDialogValue = { ...row }
    },
    handleSave() {
      this.dialogVisible = true
      this.dialogIsEdit = false
      this.dialogIsFixed = false
      this.dialogValue = { condition: '', record: '', process: '' }
      this.dialogIndex = null
      this.$refs.form.validate((valid) => {
        if (valid) {
          const payload = {
            ...this.detail,
            list: this.tableData || [],
            purpose: (this.detail.purpose || []).join(','),
            isDraft: 0
          }
          confirm(payload).then(res => {
            if (res.code == 200) {
              this.$message.success('保存成功')
              this.cacheForm = null
              this.cacheFormtableData = null
              this.$closeCurrentView('/strain/validation/primitive-cell')
            } else {
              this.$message.error(res.msg || '保存失败')
            }
          })
        } else {
          this.$message.error('请填写必填项')
          return false
        }
      })
    },
    handleDraft() {
      // 存稿逻辑
      this.$refs.form.validate((valid) => {
        if (valid) {
          const payload = {
            ...this.detail,
            list: this.tableData || [],
            purpose: (this.detail.purpose || []).join(',')
          }
          saveDraft(payload).then(res => {
            if (res.code == 200) {
              this.$message.success('存稿成功')
              this.cacheForm = null
              this.cacheFormtableData = null
              this.$closeCurrentView('/strain/validation/primitive-cell')
            } else {
              this.$message.error(res.msg || '存稿失败')
            }
          })
        }
      })
    },
    handleDialogOk(val) {
      if (this.dialogIsEdit && this.dialogIndex !== null) {
        this.$set(this.tableData, this.dialogIndex, val)
@@ -148,107 +399,320 @@
        this.tableData.push(val)
      }
    },
    getRowClassName({ rowIndex }) {
      return rowIndex < this.initialTableData.length ? 'fixed-row' : '';
      return rowIndex < this.initialTableData?.length ? 'fixed-row' : ''
    }
  }
}
</script>
<style scoped lang="less">
.confirm-detail-page {
  padding: 20px;
  background: #f5f7fa;
  min-height: 100vh;
}
// ========== 顶部卡片 ==========
.top-card {
  margin-bottom: 0;
  background: rgba(239, 239, 239, 1);
  border-radius: 16px;
  margin-bottom: 20px;
  background: #e3e3e3;
  border-radius: 12px;
  border: none;
  box-shadow: 0 2px 12px rgba(0, 0, 0, 0.06);
  /deep/ .el-card__body {
    padding: 20px 24px;
  }
}
.top-info-row {
  display: flex;
  align-items: stretch;
}
.info-col {
  display: flex;
  flex-direction: column;
  justify-content: center;
  padding: 2px 0;
}
.info-item {
  display: flex;
  font-size: 15px;
  height: 45px;
  line-height: 45px;
  align-items: center;
  font-size: 14px;
  line-height: 38px;
  padding: 1px 0;
  .label {
    color: #8c8f9c;
    font-weight: 500;
    flex-shrink: 0;
    min-width: 110px;
  }
  .value {
    color: #303133;
    font-weight: 400;
  }
}
.label {
  color: #666;
// ========== 菌种来源 ==========
.source-item {
  width: 500px;
  .source-text {
    flex: 1;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
    color: #303133;
    cursor: pointer;
    padding: 0 4px;
    font-weight: 400;
    transition: color 0.2s;
    &:hover {
      color: #409eff;
    }
  }
}
// ========== 签名区域 ==========
.sign-col {
  align-items: center;
  justify-content: center;
}
.sign-wrapper {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 6px;
  width: 100%;
}
.sign-label-text {
  font-size: 14px;
  color: #8c8f9c;
  font-weight: 500;
}
.sign-col {
  align-items: center;
  text-align: center;
}
.sign-label {
  justify-content: center;
}
.signature-item {
  justify-content: center;
}
.signature-area {
  min-height: 80px;
  min-width: 120px;
  background: #f5f7fa;
  border: 1px solid #dcdfe6;
  border-radius: 4px;
  width: 130px;
  height: 66px;
  background: #fafbfc;
  border: 1.5px dashed #d9dce6;
  border-radius: 8px;
  display: flex;
  align-items: center;
  justify-content: center;
  margin-top: 8px;
}
  overflow: hidden;
  transition: border-color 0.2s;
.signature-area img {
  max-width: 100%;
  max-height: 100%;
  display: block;
  &:hover {
    border-color: #409eff;
  }
  .el-image {
    width: 100%;
    height: 100%;
    object-fit: contain;
  }
}
.waiting-text {
  color: #909399;
  font-size: 14px;
  color: #b0b3bf;
  font-size: 13px;
}
.sign-time {
  justify-content: center;
  text-align: center;
  color: #666;
  font-size: 14px;
}
// ========== 实验结论卡片 ==========
.section-card {
  margin-bottom: 0;
  background: #fff;
  border-radius: 12px;
  padding: 20px 24px;
  margin-bottom: 16px;
  box-shadow: 0 2px 12px rgba(0, 0, 0, 0.06);
  /deep/ .el-form-item {
    margin-bottom: 0;
  }
  /deep/ .el-form-item__label {
    font-weight: 500;
    color: #303133;
  }
  /deep/ .el-checkbox-group {
    padding-top: 4px;
  }
  /deep/ .el-checkbox {
    margin-right: 24px;
    &:last-child {
      margin-right: 0;
    }
  }
}
// ========== 表格区域 ==========
.table-section {
  background: #fff;
  border-radius: 12px;
  padding: 16px 20px 20px;
  box-shadow: 0 2px 12px rgba(0, 0, 0, 0.06);
  margin-bottom: 20px;
}
.table-header {
  display: flex;
  justify-content: space-between;
  align-items: center;
  margin-bottom: 14px;
  .table-title {
    font-size: 15px;
    font-weight: 600;
    color: #303133;
    position: relative;
    padding-left: 12px;
    &::before {
      content: '';
      position: absolute;
      left: 0;
      top: 50%;
      transform: translateY(-50%);
      width: 3px;
      height: 16px;
      background: #409eff;
      border-radius: 2px;
    }
  }
}
/deep/ .el-table {
  border-radius: 8px;
  overflow: hidden;
  th.el-table__cell {
    background-color: #f5f7fa;
    color: #303133;
    font-weight: 600;
  }
  .fixed-row {
    background-color: #f0f9ff !important;
  }
}
// ========== 底部按钮 ==========
.footer-btns {
  display: flex;
  justify-content: center;
  padding: 24px;
  padding-bottom: 0;
  gap: 24px;
  padding: 8px 0 4px;
  gap: 20px;
  .el-button {
    width: 150px;
    width: 140px;
    border-radius: 8px;
    font-weight: 500;
    letter-spacing: 1px;
  }
}
::v-deep(.fixed-row) {
  background-color: rgb(228, 248, 250) !important;
// ========== Tooltip ==========
/deep/ .source-tooltip {
  max-width: 320px !important;
  min-width: 120px !important;
  word-break: break-word;
  line-height: 1.7;
  padding: 10px 16px;
  font-size: 13px;
  border-radius: 6px;
}
@media (max-width: 900px) {
  .info-col {
    height: auto;
// ========== 响应式 ==========
@media (max-width: 992px) {
  .confirm-detail-page {
    padding: 12px;
    .top-info-row {
      flex-direction: column;
      gap: 8px;
    }
    .info-col {
      width: 100% !important;
      flex: none !important;
    }
    .sign-col {
      align-items: flex-start;
    }
    .sign-wrapper {
      flex-direction: row;
      gap: 16px;
      align-items: center;
    }
    .signature-area {
      width: 100px;
      height: 56px;
    }
    .section-card /deep/ .el-col {
      width: 100% !important;
      flex: none !important;
      margin-bottom: 12px;
      &:last-child {
        margin-bottom: 0;
      }
    }
  }
}
</style>
@media (max-width: 576px) {
  .confirm-detail-page {
    padding: 8px;
    .top-card /deep/ .el-card__body {
      padding: 14px 16px;
    }
    .section-card {
      padding: 14px 16px;
    }
    .table-section {
      padding: 12px 14px 16px;
    }
    .footer-btns {
      flex-direction: column;
      align-items: center;
      gap: 12px;
      .el-button {
        width: 100%;
        max-width: 180px;
      }
    }
    .info-item {
      font-size: 13px;
      line-height: 34px;
      .label {
        min-width: 90px;
        font-size: 13px;
      }
    }
  }
}
</style>