| | |
| | | <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> |