董国庆
2 天以前 06b2be3bbb48e0275fbd25624c1cce54a7cac2b1
culture/src/views/strain-library/main-cell-library/record.vue
@@ -1,352 +1,438 @@
<template>
  <div class="record-page">
    <div class="page-header">
      <div class="header-left">
        <el-page-header @back="goBack" content="主细胞出入库记录"></el-page-header>
      </div>
      <div class="header-right">
        <el-button type="primary" icon="el-icon-plus" @click="handleAddRecord">新增记录</el-button>
      </div>
    </div>
    <el-card class="record-card">
      <div class="strain-info">
    <!-- 基本信息展示区域 -->
    <el-card class="header-box">
      <div class="header-content">
        <!-- 第一行 -->
        <div class="info-row">
          <div class="info-item">
          <div class="info-item left-column">
            <span class="label">菌种编号:</span>
            <span class="value">{{ strainInfo.strainNo }}</span>
            <span class="value">{{ detail.strainCode }}</span>
          </div>
          <div class="info-item">
            <span class="label">菌种名称:</span>
            <span class="value">{{ strainInfo.strainName }}</span>
          </div>
          <div class="info-item">
            <span class="label">菌种来源:</span>
            <span class="value">{{ strainInfo.source }}</span>
          </div>
        </div>
        <div class="info-row">
          <div class="info-item">
          <div class="info-item flex-column">
            <span class="label">鉴定方法:</span>
            <span class="value">{{ strainInfo.method }}</span>
            <span class="value">{{ detail.appraisalMethod }}</span>
          </div>
          <div class="info-item full">
            <span class="label">特征描述:</span>
            <span class="value">{{ strainInfo.certificate }}</span>
          <div class="info-item flex-column">
            <span class="label">保藏位置:</span>
            <span class="value">{{ detail.saveLocation }}</span>
          </div>
        </div>
        <!-- 第二行 -->
        <div class="info-row">
          <div class="info-item">
            <span class="label">菌种保存方法:</span>
            <span class="value">{{ strainInfo.storage }}</span>
          <div class="info-item left-column">
            <span class="label">菌种名称:</span>
            <span class="value">{{ detail.strainName }}</span>
          </div>
          <div class="info-item">
            <span class="label">保存位置:</span>
            <span class="value">{{ strainInfo.amount }}</span>
          </div>
          <div class="info-item">
            <span class="label">出入库状态:</span>
            <span class="value status">{{ strainInfo.statusText }}</span>
          <div class="info-item flex-column full-width">
            <span class="label">特性描述:</span>
            <span class="value">{{ detail.features }}</span>
          </div>
        </div>
      </div>
      <div class="record-timeline-container">
        <h3 class="section-title">出入库记录</h3>
        <RecordTimeline :list="recordList" />
        <!-- 第三行 -->
        <div class="info-row">
          <div class="info-item left-column">
            <span class="label">菌种来源:</span>
            <span class="value">{{ detail.strainSource }}</span>
          </div>
          <div class="info-item flex-column">
            <span class="label">菌种保存方法:</span>
            <span class="value">{{ detail.saveMethod }}</span>
          </div>
        </div>
      </div>
    </el-card>
    <!-- 新增记录弹窗 -->
    <el-dialog
      title="新增出入库记录"
      :visible.sync="dialogVisible"
      width="500px"
    <!-- 出入库记录表格 -->
    <TableCustom
      :queryForm="queryForm"
      :tableData="recordList"
      :total="total"
      @currentChange="handlePageChange"
    >
      <el-form ref="recordForm" :model="recordForm" :rules="recordRules" label-width="100px">
        <el-form-item label="操作类型" prop="type">
          <el-radio-group v-model="recordForm.type">
            <el-radio label="入库">入库</el-radio>
            <el-radio label="出库">出库</el-radio>
          </el-radio-group>
        </el-form-item>
        <el-form-item label="操作人" prop="operator">
          <el-input v-model="recordForm.operator" placeholder="请输入操作人"></el-input>
        </el-form-item>
        <el-form-item label="操作时间" prop="operateTime">
          <el-date-picker
            v-model="recordForm.operateTime"
            type="datetime"
            placeholder="选择日期时间"
            value-format="yyyy-MM-dd HH:mm:ss"
          ></el-date-picker>
        </el-form-item>
        <el-form-item label="保藏人" prop="reviewer">
          <el-input v-model="recordForm.reviewer" placeholder="请输入保藏人"></el-input>
        </el-form-item>
        <el-form-item label="操作数量" prop="amount">
          <el-input-number v-model="recordForm.amount" :min="1" :max="100"></el-input-number>
        </el-form-item>
        <el-form-item label="备注" prop="remarks">
          <el-input
            type="textarea"
            v-model="recordForm.remarks"
            :rows="3"
            placeholder="请输入备注"
          ></el-input>
        </el-form-item>
      </el-form>
      <span slot="footer" class="dialog-footer">
        <el-button @click="dialogVisible = false">取 消</el-button>
        <el-button type="primary" @click="submitRecord">确 定</el-button>
      </span>
    </el-dialog>
      <template #setting>
        <div class="tableTitle">
          <div class="flex a-center">
            <div
              class="title"
              :class="{ active: currentType === 'table' }"
              @click="handleTypeChange('table')"
            >
              原始细胞保藏出/入库登记表
            </div>
            <div
              class="drafts"
              :class="{ active: currentType === 'timeline' }"
              @click="handleTypeChange('timeline')"
            >
              原始细胞保藏出/入库时间轴
            </div>
          </div>
          <div class="flex a-center">
            <el-button
              v-if="roleType == 4"
              @click="handleAddRecord"
              class="el-icon-plus"
              type="primary"
              >新增出入库记录</el-button
            >
          </div>
        </div>
      </template>
      <template #table v-if="currentType === 'table'">
        <el-table-column prop="type" label="出库/入库">
          <template #default="{ row }">
            <span>
              {{ row.type === 1 ? "出库" : "入库" }}
            </span>
          </template>
        </el-table-column>
        <el-table-column prop="boundTime" label="操作时间" />
        <el-table-column prop="handleSignature" label="操作人签字">
          <template #default="{ row }">
            <el-image
              v-if="row.handleSignature"
              style="width: 100px; height: 100px"
              :src="row.handleSignature"
              :preview-src-list="[row.handleSignature]"
            >
            </el-image>
          </template>
        </el-table-column>
        <el-table-column prop="preserveSignature" label="菌种保藏人签字">
          <template #default="{ row }">
            <el-image
              v-if="row.preserveSignature"
              style="width: 100px; height: 100px"
              :src="row.preserveSignature"
              :preview-src-list="[row.preserveSignature]"
            >
            </el-image>
          </template>
        </el-table-column>
        <el-table-column prop="status" label="状态">
          <template #default="{ row }">
            <el-tag :type="row.preserveSignature ? 'success' : 'warning'">
              {{ row.preserveSignature ? "已确认" : "待确认" }}
            </el-tag>
          </template>
        </el-table-column>
        <el-table-column label="操作" width="180">
          <template #default="{ row }">
            <el-button
              v-if="!row.preserveSignature && roleType == 3"
              type="text"
              class="operation-btn"
              @click="handleConfirm(row)"
              >确认</el-button
            >
            <el-button
              type="text"
              class="operation-btn"
              @click="handleView(row)"
              >详情</el-button
            >
            <el-button v-if="roleType == 1" type="text" @click="handleDelete(row)">删除</el-button>
          </template>
        </el-table-column>
      </template>
      <template #tableCustom v-if="currentType === 'timeline'">
        <record-timeline :list="timelineList" />
      </template>
    </TableCustom>
    <!-- 详情弹窗 -->
    <record-detail-dialog
      :visible.sync="dialogVisible"
      :record-data="currentRecord"
      @close="handleDialogClose"
      @confirm="handleOutbound"
      :type="dialogType"
    />
    <!-- 新增出入库记录弹窗 -->
    <add-record-dialog
      :visible.sync="addDialogVisible"
      @confirm="handleAddRecordConfirm"
    />
  </div>
</template>
<script>
import RecordTimeline from '../strain-library-manage/components/RecordTimeline.vue'
import RecordDetailDialog from "../strain-library-manage/components/RecordDetailDialog.vue";
import AddRecordDialog from "../strain-library-manage/components/AddRecordDialog.vue";
import RecordTimeline from "../strain-library-manage/components/RecordTimeline.vue";
import {
  timeList,
  getDetail,
  addWarehousing,
  getDetailById,
  confirmWarehousing,
} from "./service";
export default {
  name: 'MainCellRecord',
  name: "StrainRecord",
  components: {
    RecordTimeline
    RecordDetailDialog,
    AddRecordDialog,
    RecordTimeline,
  },
  data() {
    return {
      strainId: '',
      strainInfo: {
        strainNo: 'M-2024001',
        strainName: '大肠杆菌BL21',
        source: '原始细胞库',
        method: '分子生物学鉴定',
        certificate: '常用表达宿主菌,含有DE3溶源体,适合蛋白表达',
        storage: '甘油冷冻',
        amount: 'M区-01-001',
        inventory: '100',
        status: '1',
        statusText: '已入库'
      currentType: "table",
      detail: {},
      currentPage: 1,
      pageSize: 10,
      total: 0,
      queryForm: {
        pageSize: 10,
        pageNum: 1,
      },
      recordList: [
        {
          type: '入库',
          operator: '张三',
          operateTime: '2024-05-01 10:30:00',
          reviewer: '李四',
          confirmTime: '2024-05-01 14:20:00'
        },
        {
          type: '出库',
          operator: '王五',
          operateTime: '2024-05-15 09:45:00',
          reviewer: '赵六',
          confirmTime: '2024-05-15 11:30:00'
        },
        {
          type: '入库',
          operator: '钱七',
          operateTime: '2024-05-20 14:00:00',
          reviewer: '孙八',
          confirmTime: '2024-05-20 16:15:00'
        }
      ],
      recordList: [],
      timelineList: [],
      dialogVisible: false,
      recordForm: {
        type: '入库',
        operator: '',
        operateTime: '',
        reviewer: '',
        amount: 1,
        remarks: ''
      },
      recordRules: {
        type: [
          { required: true, message: '请选择操作类型', trigger: 'change' }
        ],
        operator: [
          { required: true, message: '请输入操作人', trigger: 'blur' }
        ],
        operateTime: [
          { required: true, message: '请选择操作时间', trigger: 'change' }
        ],
        reviewer: [
          { required: true, message: '请输入保藏人', trigger: 'blur' }
        ],
        amount: [
          { required: true, message: '请输入操作数量', trigger: 'blur' }
        ]
      }
    }
      currentRecord: {},
      addDialogVisible: false,
      dialogType: "detail",
      roleType: "",
    };
  },
  created() {
    // 获取路由参数中的菌种ID
    this.strainId = this.$route.query.id
    // 实际项目中这里应该根据ID加载菌种信息和记录列表
    console.log('加载菌种ID:', this.strainId)
  activated() {
    this.roleType = JSON.parse(sessionStorage.getItem("userInfo")).roleType;
    // 获取路由参数中的菌种信息
    const strainId = this.$route.query.id;
    this.queryForm.id = strainId;
    if (strainId) {
      this.getStrainDetail(strainId);
      this.getRecordList();
    }
  },
  methods: {
    goBack() {
      this.$router.push('/strain-library/main-cell-library')
    handleDelete(row) {
      this.$confirm("确定删除该数据吗?", "提示", {
        confirmButtonText: "确定",
        cancelButtonText: "取消",
        type: "warning",
      }).then(() => {
        deleteWarehousing({ id: row.id }).then((res) => {
          this.$message.success("删除成功");
          this.getRecordList();
        });
      });
    },
    getStrainDetail(id) {
      // 这里应该调用接口获取菌种详情
      getDetail({ id }).then((res) => {
        this.detail = res;
      });
    },
    getRecordList() {
      // 这里应该调用接口获取出入库记录
      timeList(this.queryForm).then((res) => {
        this.timelineList = res.data;
      });
      getDetailById({ id: this.$route.query.id }).then((res) => {
        this.recordList = res.warehousingList.records;
        this.total = res.warehousingList.total;
      });
    },
    handleView(row) {
      this.dialogType = "detail";
      this.currentRecord = row;
      this.dialogVisible = true;
    },
    handleConfirm(row) {
      this.dialogType = "confirm";
      this.currentRecord = row;
      this.dialogVisible = true;
    },
    handlePageChange(page) {
      this.queryForm.pageNum = page;
      // 这里应该调用接口获取对应页码的数据
    },
    handleTypeChange(type) {
      this.currentType = type;
    },
    handleAddRecord() {
      this.dialogVisible = true
      this.resetRecordForm()
      this.addDialogVisible = true;
    },
    submitRecord() {
      this.$refs.recordForm.validate(valid => {
        if (valid) {
          // 表单验证通过,提交数据
          console.log('提交的记录数据:', this.recordForm)
          // 模拟添加记录到列表
          const newRecord = {
            type: this.recordForm.type,
            operator: this.recordForm.operator,
            operateTime: this.recordForm.operateTime,
            reviewer: this.recordForm.reviewer,
            confirmTime: new Date().toLocaleString()
          }
          // 添加到记录列表的开头
          this.recordList.unshift(newRecord)
          // 关闭弹窗
          this.dialogVisible = false
          // 显示成功消息
          this.$message.success('记录添加成功')
    handleDialogClose() {
      this.currentRecord = {};
      this.dialogVisible = false;
    },
    handleOutbound(data) {
      // 这里调用出库API
      confirmWarehousing({
        id: this.currentRecord.id,
        preserveSignature: data.preserveSignature,
      }).then((res) => {
        console.log(res);
        if (res.code == 200) {
          this.$message.success("操作成功");
          this.dialogVisible = false;
          // 刷新列表
          this.getRecordList();
        } else {
          this.$message.error('请正确填写表单')
          return false
          this.$message.error(res.msg);
        }
      })
      });
    },
    resetRecordForm() {
      this.recordForm = {
        type: '入库',
        operator: '',
        operateTime: '',
        reviewer: '',
        amount: 1,
        remarks: ''
      }
    }
  }
}
    handleAddRecordConfirm(record) {
      addWarehousing({ ...record, trainLibraryId: this.$route.query.id }).then(
        (res) => {
          this.$message.success("操作成功");
          this.getRecordList();
        }
      );
    },
    goBack() {
      this.$router.go(-1);
    },
  },
};
</script>
<style scoped lang="less">
<style lang="less" scoped>
.record-page {
  padding: 20px;
}
  min-height: 100vh;
.page-header {
  display: flex;
  justify-content: space-between;
  align-items: center;
  margin-bottom: 20px;
  .header-left {
    :deep(.el-page-header__content) {
      font-size: 18px;
      font-weight: bold;
      color: #333;
    }
  }
}
.record-card {
  background: #fff;
  border-radius: 16px;
  margin-bottom: 20px;
}
.strain-info {
  padding: 10px 0;
  .info-row {
    display: flex;
    flex-wrap: wrap;
    margin-bottom: 16px;
    &:last-child {
      margin-bottom: 0;
    }
  }
  .info-item {
    flex: 1;
    min-width: 200px;
    margin-right: 20px;
    &:last-child {
      margin-right: 0;
    }
    &.full {
      flex: 2;
    }
    .label {
      color: #606266;
      margin-right: 8px;
    }
    .value {
      color: #333;
      font-weight: 500;
      &.status {
        color: #67C23A;
      }
    }
  }
}
.record-timeline-container {
  margin-top: 30px;
  .section-title {
    font-size: 16px;
    color: #333;
  .header-box {
    margin-bottom: 20px;
    font-weight: 500;
  }
}
    border-radius: 16px;
    background: rgba(255, 255, 255, 0.8);
    height: 130px;
    overflow: hidden;
:deep(.el-dialog__body) {
  padding: 20px 30px;
}
    .header-content {
      color: rgba(0, 0, 0, 0.88);
      font-size: 14px;
      line-height: 1.5;
@media screen and (max-width: 768px) {
  .strain-info {
    .info-row {
      flex-direction: column;
      .info-item {
        margin-right: 0;
        margin-bottom: 10px;
      .info-row {
        display: flex;
        flex-wrap: wrap;
        margin-bottom: 8px;
        &:last-child {
          margin-bottom: 0;
        }
        .info-item {
          display: flex;
          align-items: flex-start;
          margin-right: 24px;
          margin-bottom: 6px;
          &.left-column {
            width: 33%;
            min-width: 200px;
          }
          &.flex-column {
            flex: 1;
            min-width: 150px;
          }
          &.full-width {
            flex: 1;
            min-width: 300px;
          }
          .label {
            color: #606266;
            margin-right: 8px;
            white-space: nowrap;
          }
          .value {
            flex: 1;
            color: #303133;
            word-break: break-all;
            display: -webkit-box;
            -webkit-line-clamp: 1;
            -webkit-box-orient: vertical;
            overflow: hidden;
            text-overflow: ellipsis;
          }
        }
      }
    }
  }
  .page-header {
    flex-direction: column;
    align-items: flex-start;
    .header-right {
      margin-top: 16px;
  .flex {
    display: flex;
    align-items: center;
  }
  .tableTitle {
    display: flex;
    padding-bottom: 20px;
    justify-content: space-between;
    align-items: center;
    .title {
      background: #fafafc;
      border-radius: 8px 8px 0px 0px;
      border: 1px solid #dcdfe6;
      font-weight: bold;
      font-size: 18px;
      color: #606266;
      cursor: pointer;
      height: 50px;
      line-height: 50px;
      width: 280px;
      text-align: center;
    }
    .drafts {
      background: #fafafc;
      border-radius: 8px 8px 0px 0px;
      border: 1px solid #dcdfe6;
      font-weight: 400;
      font-size: 18px;
      color: #606266;
      margin-left: 16px;
      cursor: pointer;
      height: 50px;
      line-height: 50px;
      width: 280px;
      text-align: center;
    }
    .active {
      color: #049c9a;
      background: #ffffff;
      border-radius: 8px 8px 0px 0px;
      border: 1px solid #049c9a;
    }
  }
  .timeline-container {
    padding: 20px;
    background: rgba(255, 255, 255, 0.8);
    .timeline-card {
      margin-bottom: 10px;
      background: rgba(255, 255, 255, 0.8);
      h4 {
        margin: 0 0 10px;
        font-size: 16px;
        font-weight: bold;
      }
      p {
        margin: 5px 0;
        font-size: 14px;
      }
    }
  }
  .operation-btn {
    margin-right: 12px;
  }
}
</style>
</style>