From 8ac7e0ca090ab5ce0f8435e8af6f78a23c0dd6e0 Mon Sep 17 00:00:00 2001 From: pyt <626651354@qq.com> Date: 星期一, 19 五月 2025 18:04:45 +0800 Subject: [PATCH] feat --- culture/src/views/strain-library/main-cell-library/service.js | 56 culture/src/views/strain-library/production-cell-library/index.vue | 495 ++-- culture/src/views/strain-library/main-cell-library/record.vue | 684 +++-- culture/src/views/pedigree-chart/components/PlanForm.vue | 6 culture/src/views/strain-library/main-cell-library/add.vue | 573 +++- culture/src/views/strain-library/strain-library-manage/components/RecordDetailDialog.vue | 42 culture/src/router/index.js | 1 culture/src/views/strain-library/strain-library-manage/record.vue | 791 +++--- culture/src/views/pedigree-chart/add.vue | 43 culture/src/views/strain-library/production-cell-library/add.vue | 384 +++ culture/src/views/strain-library/production-cell-library/record.vue | 864 +++---- culture/src/views/strain-library/strain-library-manage/index.vue | 766 ++++--- culture/src/views/pedigree-chart/index.vue | 52 culture/src/views/pedigree-chart/service.js | 11 culture/src/views/strain-library/main-cell-library/index.vue | 851 ++++---- culture/src/views/pedigree-chart/components/ParentForm.vue | 6 culture/src/views/strain-library/strain-library-manage/add.vue | 11 culture/src/views/strain-library/strain-library-manage/service.js | 15 culture/src/views/strain-library/strain-library-manage/components/StrainDetail.vue | 459 ++- culture/src/views/strain-library/production-cell-library/service.js | 56 20 files changed, 3,506 insertions(+), 2,660 deletions(-) diff --git a/culture/src/router/index.js b/culture/src/router/index.js index 83f0c9e..e433d4a 100644 --- a/culture/src/router/index.js +++ b/culture/src/router/index.js @@ -182,7 +182,6 @@ name: "StrainRecord", meta: { title: "出入库记录", - keepAlive: true, hide: true, }, component: () => diff --git a/culture/src/views/pedigree-chart/add.vue b/culture/src/views/pedigree-chart/add.vue index 84daec1..58e06e3 100644 --- a/culture/src/views/pedigree-chart/add.vue +++ b/culture/src/views/pedigree-chart/add.vue @@ -3,14 +3,18 @@ <el-form :model="form" :rules="rules" ref="pedigreeForm" label-position="top" class="strain-form"> <div class="card"> <div class="form-items-row"> - <el-form-item label="菌种源" prop="strainSource" required> + <el-form-item label="菌种源" required> <div class="flex-row"> <div class="input-wrapper"> - <el-input v-model="form.strainSource" placeholder="请输入" class="fixed-width-input"></el-input> + <el-form-item prop="strainSourceStart" style="margin-bottom: 0;"> + <el-input v-model="form.strainSourceStart" placeholder="请输入" class="fixed-width-input"></el-input> + </el-form-item> </div> <span class="form-text">代—</span> <div class="input-wrapper"> - <el-input v-model="form.generation" placeholder="请输入" class="fixed-width-input"></el-input> + <el-form-item prop="strainSourceEnd" style="margin-bottom: 0;"> + <el-input v-model="form.strainSourceEnd" placeholder="请输入" class="fixed-width-input"></el-input> + </el-form-item> </div> <span class="form-text">细胞库</span> </div> @@ -54,11 +58,12 @@ <div class="strain-flow-chart"> <div id="mountNode"></div> </div> + <el-button type="primary" @click="handleSubmit" style="width: 150px;">保存</el-button> + </div> <div class="end-btn"> - <el-button type="primary" @click="handleSubmit">提交</el-button> - <el-button @click="handleDraft">存草稿</el-button> - <el-button @click="handleCancel">取消</el-button> + <!-- <el-button @click="handleDraft">存草稿</el-button> + <el-button @click="handleCancel">取消</el-button> --> </div> </el-form> @@ -95,15 +100,18 @@ return { signatureVisible: false, form: { - strainSource: "", - generation: "", + strainSourceStart: "", + strainSourceEnd: "", cellBank: "", strainNo: "", strainName: "", remarks: "", }, rules: { - strainSource: [ + strainSourceStart: [ + { required: true, message: "请输入菌种源", trigger: "blur" }, + ], + strainSourceEnd: [ { required: true, message: "请输入菌种源", trigger: "blur" }, ], strainNo: [ @@ -177,9 +185,8 @@ }, handleSignatureConfirm(signatureImage) { this.confirmStorageDialogVisible = false; - console.log("submit form with signature:", signatureImage); if (this.nodeType === 1) { - this.handleAddParent(this.nodeData) + this.handleAddParent({...this.nodeData,signature:signatureImage.signature}) } else if (this.nodeType === 2) { this.handleAddPlan(this.nodeData) } else if (this.nodeType === 3) { @@ -480,8 +487,8 @@ this.$refs.parentForm.openInitData({ strainName: this.form.strainName, strainNo: this.form.strainNo, - strainSource: this.form.strainSource, - generation: this.form.generation, + strainSourceStart: this.form.strainSourceStart, + strainSourceEnd: this.form.strainSourceEnd, }); } }) @@ -526,6 +533,8 @@ } }, handleAddParent(value) { + console.log(value); + const parentId = `parent-${++this.nodeCount}`; this.graphData.nodes.push({ id: parentId, @@ -658,8 +667,8 @@ label: nodeModel.label, strainName: this.form.strainName, strainNo: this.form.strainNo, - strainSource: this.form.strainSource, - generation: this.form.generation, + strainSourceStart: this.form.strainSourceStart, + strainSourceEnd: this.form.strainSourceEnd, }) } else { this.$refs.planForm.openInitData({ @@ -667,8 +676,8 @@ label: nodeModel.label, strainName: this.form.strainName, strainNo: this.form.strainNo, - strainSource: this.form.strainSource, - generation: this.form.generation, + strainSourceStart: this.form.strainSourceStart, + strainSourceEnd: this.form.strainSourceEnd, }) } }, diff --git a/culture/src/views/pedigree-chart/components/ParentForm.vue b/culture/src/views/pedigree-chart/components/ParentForm.vue index d7bf108..f997918 100644 --- a/culture/src/views/pedigree-chart/components/ParentForm.vue +++ b/culture/src/views/pedigree-chart/components/ParentForm.vue @@ -3,14 +3,14 @@ <el-dialog :title="parentForm.status === 'detail' ? '母代详情' : '新增母代'" :visible.sync="addParentDialogVisible" width="40%" :close-on-click-modal="false"> <el-form :model="parentForm" ref="parentForm" label-position="top"> - <el-form-item label="菌种源" prop="strainSource"> + <el-form-item label="菌种源" prop="strainSourceStart"> <div class="flex-row"> <div class="input-wrapper"> - <el-input disabled v-model="parentForm.strainSource" class="fixed-width-input"></el-input> + <el-input disabled v-model="parentForm.strainSourceStart" class="fixed-width-input"></el-input> </div> <span class="form-text">代—</span> <div class="input-wrapper"> - <el-input disabled v-model="parentForm.generation" class="fixed-width-input"></el-input> + <el-input disabled v-model="parentForm.strainSourceEnd" class="fixed-width-input"></el-input> </div> <span class="form-text">细胞库</span> </div> diff --git a/culture/src/views/pedigree-chart/components/PlanForm.vue b/culture/src/views/pedigree-chart/components/PlanForm.vue index 441f636..df58249 100644 --- a/culture/src/views/pedigree-chart/components/PlanForm.vue +++ b/culture/src/views/pedigree-chart/components/PlanForm.vue @@ -3,14 +3,14 @@ <el-dialog :title="planForm.status === 'detail' ? '传代计划数详情' : '设置传代计划数'" :visible.sync="planDialogVisible" width="40%" :close-on-click-modal="false"> <el-form :model="planForm" :rules="planRules" ref="planForm" label-position="top"> - <el-form-item label="菌种源" prop="strainSource"> + <el-form-item label="菌种源" prop="strainSourceStart"> <div class="flex-row"> <div class="input-wrapper"> - <el-input disabled v-model="planForm.strainSource" class="fixed-width-input"></el-input> + <el-input disabled v-model="planForm.strainSourceStart" class="fixed-width-input"></el-input> </div> <span class="form-text">代—</span> <div class="input-wrapper"> - <el-input disabled v-model="planForm.generation" class="fixed-width-input"></el-input> + <el-input disabled v-model="planForm.strainSourceEnd" class="fixed-width-input"></el-input> </div> <span class="form-text">细胞库</span> </div> diff --git a/culture/src/views/pedigree-chart/index.vue b/culture/src/views/pedigree-chart/index.vue index d2a854c..fe2022e 100644 --- a/culture/src/views/pedigree-chart/index.vue +++ b/culture/src/views/pedigree-chart/index.vue @@ -4,17 +4,20 @@ <template #search> <el-form :model="form" labelWidth="auto" inline> <el-form-item label="菌种编号:"> - <el-input v-model="form.planName" placeholder="请输入"></el-input> + <el-input v-model="form.strainCode" placeholder="请输入"></el-input> </el-form-item> <el-form-item label="菌种名称:"> - <el-input v-model="form.planCode" placeholder="请输入"></el-input> + <el-input v-model="form.strainName" placeholder="请输入"></el-input> </el-form-item> <el-form-item label="起传类型"> - <el-input v-model="form.creator" placeholder="请输入"></el-input> + <el-select v-model="form.generationType" placeholder="请选择"> + <el-option label="母代" :value="1"></el-option> + <el-option label="祖代" :value="2"></el-option> + </el-select> </el-form-item> <el-form-item label=""> <el-button type="default" @click="resetForm">重置</el-button> - <el-button type="primary" @click="handleSearch">查询</el-button> + <el-button style="margin-left: 10px;" type="primary" @click="handleSearch">查询</el-button> </el-form-item> </el-form> </template> @@ -59,6 +62,7 @@ </template> <script> +import { getList } from "./service"; export default { name: "PedigreeChart", components: {}, @@ -66,12 +70,11 @@ return { currentType: "list", // 当前显示类型:list-列表,draft-草稿箱 form: { - planName: "", - planCode: "", - creator: "", - createTime: [], - approver: "", - status: "", + strainCode: "", + strainName: "", + generationType: "", + pageNum: 1, + pageSize: 10 }, tableData: [], total: 0, @@ -146,12 +149,11 @@ }, resetForm() { this.form = { - planName: "", - planCode: "", - creator: "", - createTime: [], - approver: "", - status: "", + strainCode: "", + strainName: "", + generationType: "", + pageNum: 1, + pageSize: 10 }; }, handleNewStrain() { @@ -160,8 +162,8 @@ }); }, handleSearch() { - // 实现查询逻辑 - console.log("查询条件:", this.form); + this.form.pageNum = 1; + this.getTableData(); }, getStatusType(status) { const statusMap = { @@ -229,14 +231,12 @@ this.getTableData(); }, getTableData() { - // 根据currentType请求不同的数据 - if (this.currentType === "list") { - this.tableData = this.mockListData; - this.total = this.mockListData.length; - } else { - this.tableData = this.mockDraftData; - this.total = this.mockDraftData.length; - } + getList(this.form).then(res => { + if (res.code === 200) { + this.tableData = res.data.list; + this.total = res.data.total; + } + }); }, }, }; diff --git a/culture/src/views/pedigree-chart/service.js b/culture/src/views/pedigree-chart/service.js new file mode 100644 index 0000000..4a1a7db --- /dev/null +++ b/culture/src/views/pedigree-chart/service.js @@ -0,0 +1,11 @@ +import axios from '@/utils/request'; + +// 列表 +export const getList = (data) => { + return axios.post('/api/t-pedigree-chart/pageList', { ...data }) +} + +// 删除菌种库 +export const deleteStrainLibrary = (params) => { + return axios.delete('/open/t-train-library/deleteById', { params }) +} \ No newline at end of file diff --git a/culture/src/views/strain-library/main-cell-library/add.vue b/culture/src/views/strain-library/main-cell-library/add.vue index 39b15f6..3e1e100 100644 --- a/culture/src/views/strain-library/main-cell-library/add.vue +++ b/culture/src/views/strain-library/main-cell-library/add.vue @@ -1,190 +1,429 @@ <template> - <Card> - <el-form - :model="form" - :rules="rules" - ref="strainForm" - label-position="top" - class="strain-form" - > - <div class="form-grid"> - <el-form-item label="菌种编号" prop="strainNo" required> - <el-input v-model="form.strainNo" placeholder="请输入"></el-input> - </el-form-item> - <el-form-item label="菌种名称" prop="strainName" required> - <el-input v-model="form.strainName" placeholder="请输入"></el-input> - </el-form-item> - <el-form-item label="菌种来源" prop="source" required> - <el-input v-model="form.source" placeholder="请输入"></el-input> - </el-form-item> - <el-form-item label="鉴定方法" prop="identificationMethod" required> - <el-input v-model="form.identificationMethod" placeholder="请输入"></el-input> - </el-form-item> - <el-form-item label="特征描述" prop="characteristics" required> - <el-input v-model="form.characteristics" placeholder="请输入"></el-input> - </el-form-item> - <el-form-item label="菌种保存方法" prop="preservationMethod" required> - <el-input v-model="form.preservationMethod" placeholder="请输入"></el-input> - </el-form-item> - <el-form-item label="保存位置" prop="storageLocation" required> - <el-input v-model="form.storageLocation" placeholder="请输入"></el-input> - </el-form-item> - </div> + <Card> + <el-form :model="form" :rules="rules" ref="strainForm" label-position="top" class="strain-form"> + <div class="form-row three-columns"> + <el-form-item label="菌种编号" prop="strainCode"> + <el-input v-model="form.strainCode" placeholder="请输入"></el-input> + </el-form-item> + <el-form-item label="菌种名称" prop="strainName"> + <el-input v-model="form.strainName" placeholder="请输入"></el-input> + </el-form-item> + <el-form-item label="菌种来源" prop="strainSource"> + <el-input v-model="form.strainSource" placeholder="请输入"></el-input> + </el-form-item> + </div> - <div class="form-row"> - <el-form-item label="备注" prop="remarks" class="full-width"> - <el-input - type="textarea" - v-model="form.remarks" - :rows="4" - placeholder="请输入" - ></el-input> - </el-form-item> - </div> - <div class="end-btn" style="margin-top: 38px"> - <el-button type="primary" @click="handleSubmit">提交</el-button> - <el-button @click="handleDraft">存草稿</el-button> - </div> - </el-form> + <div class="form-row"> + <el-form-item label="鉴定方法" prop="appraisalMethod"> + <el-input v-model="form.appraisalMethod" placeholder="请输入"></el-input> + </el-form-item> + </div> - <!-- 签字确认组件 --> - <SignatureCanvas - :visible.sync="signatureVisible" - @confirm="handleSignatureConfirm" - /> - </Card> + <div class="form-row"> + <el-form-item label="特征描述" prop="features" class="full-width"> + <el-input type="textarea" v-model="form.features" :rows="4" placeholder="请输入"></el-input> + </el-form-item> + </div> + + <div class="form-row three-columns"> + <el-form-item label="保藏位置" prop="saveLocation"> + <el-input v-model="form.saveLocation" placeholder="请输入"></el-input> + </el-form-item> + <el-form-item label="菌种保存方法" prop="saveMethod"> + <el-input v-model="form.saveMethod" placeholder="请输入"></el-input> + </el-form-item> + <div class="form-item-placeholder"></div> + </div> + + <div class="form-row"> + <el-form-item label="备注" prop="remarks" class="full-width"> + <el-input type="textarea" v-model="form.remark" :rows="4" placeholder="请输入"></el-input> + </el-form-item> + </div> + + <div class="end-btn" style="margin-top: 38px"> + <el-button type="primary" @click="handleSubmit(0)">提交</el-button> + <el-button v-if="!$route.query.id" type="primary" @click="handleBatchAdd">批量新增</el-button> + <el-button @click="handleSubmit(1)">存草稿</el-button> + </div> + </el-form> + + <!-- 批量新增弹窗 --> + <el-dialog title="批量新增" :visible.sync="batchAddDialogVisible" width="520px" :close-on-click-modal="false" + :close-on-press-escape="false" custom-class="batch-add-dialog"> + <div class="dialog-content"> + <el-form :model="batchForm" ref="batchFormRef" label-position="top" class="batch-form"> + <el-form-item label="批量新增数量" prop="count" + :rules="[{ required: true, message: '请输入批量新增数量', trigger: 'blur' }]"> + <el-input v-model.number="batchForm.count" placeholder="请输入" /> + </el-form-item> + </el-form> + <div class="dialog-notice"> + <p>注意:操作批量新增后,系统会自动生成对应数量的菌种数据,</p> + <p>这些菌种的基础信息内容都是一致的,唯独菌种编号内容系统</p> + <p>不会自动生成,需要操作员自行编辑</p> + </div> + </div> + <template #footer> + <div class="end-btn"> + <el-button type="primary" @click="handleConfirmBatchAdd">确认新增</el-button> + </div> + </template> + </el-dialog> + + <!-- 签字确认组件 --> + <SignatureCanvas :visible.sync="signatureVisible" @confirm="handleSignatureConfirm" /> + </Card> </template> <script> import SignatureCanvas from '@/components/SignatureCanvas.vue' +import { add, edit, getDetail, addBatch } from './service' export default { - name: 'AddMainCell', - components: { - SignatureCanvas - }, - data() { - return { - signatureVisible: false, - form: { - strainNo: '', - strainName: '', - source: '', - identificationMethod: '', - characteristics: '', - storageLocation: '', - preservationMethod: '', - remarks: '' - }, - rules: { - strainNo: [{ required: true, message: '请输入菌种编号', trigger: 'blur' }], - strainName: [{ required: true, message: '请输入菌种名称', trigger: 'blur' }], - source: [{ required: true, message: '请输入菌种来源', trigger: 'blur' }], - identificationMethod: [{ required: true, message: '请输入鉴定方法', trigger: 'blur' }], - characteristics: [{ required: true, message: '请输入特征描述', trigger: 'blur' }], - storageLocation: [{ required: true, message: '请输入保存位置', trigger: 'blur' }], - preservationMethod: [{ required: true, message: '请输入菌种保存方法', trigger: 'blur' }] - } - } - }, - methods: { - handleSubmit() { - this.$refs.strainForm.validate((valid) => { - if (valid) { - this.signatureVisible = true - } - }) - }, - handleDraft() { - // 实现存草稿逻辑 - console.log('save draft', this.form) - this.$message.success('草稿保存成功') - }, - handleSignatureConfirm(signatureImage) { - this.signatureVisible = false - // 处理提交逻辑 - console.log('submit form with signature:', this.form, signatureImage) - this.$message.success('提交成功') - this.$router.back() - } - } + name: 'StrainLibraryManageAdd', + components: { + SignatureCanvas + }, + data() { + return { + batchAddDialogVisible: false, + signatureVisible: false, + currentAction: '', // 'submit' or 'batchAdd' + batchForm: { + count: '' + }, + form: { + strainCode: '', + strainName: '', + source: '', + appraisalMethod: '', + characteristics: '', + storageLocation: '', + preservationMethod: '', + remark: '' + }, + rules: { + strainCode: [{ + validator: (rule, value, callback) => { + if (this.currentAction === 'submit' && !value) { + callback(new Error('请输入菌种编号')); + } else { + callback(); + } + }, + trigger: 'change' + }], + strainName: [{ required: true, message: '请输入菌种名称', trigger: 'blur' }], + strainSource: [{ required: true, message: '请输入菌种来源', trigger: 'blur' }], + appraisalMethod: [{ required: true, message: '请输入鉴定方法', trigger: 'blur' }], + features: [{ required: true, message: '请输入特征描述', trigger: 'blur' }], + saveLocation: [{ required: true, message: '请输入保存位置', trigger: 'blur' }], + saveMethod: [{ required: true, message: '请输入菌种保存方法', trigger: 'blur' }] + } + } + }, + activated() { + if (this.$route.query.id) { + getDetail({ id: this.$route.query.id }).then(res => { + this.form = res + }) + } + }, + watch: { + '$route.query.id'() { + this.form = { + strainCode: '', + strainName: '', + source: '', + appraisalMethod: '', + characteristics: '', + storageLocation: '', + preservationMethod: '', + remark: '' + } + } + }, + methods: { + handleSubmit(isDraft) { + this.currentAction = 'submit' + this.$refs.strainForm.validate((valid) => { + if (valid) { + this.form.isDraft = isDraft + if (isDraft == 1) { + //存草稿 + this.handleSignatureConfirm('') + } else { + this.signatureVisible = true + } + } + }) + }, + handleBatchAdd() { + this.currentAction = 'batchAdd' + this.$refs.strainForm.validate((valid) => { + if (valid) { + this.batchAddDialogVisible = true + } + }) + }, + handleConfirmBatchAdd() { + this.$refs.batchFormRef.validate((valid) => { + if (valid) { + this.batchAddDialogVisible = false + this.signatureVisible = true + } + }) + }, + async handleSignatureConfirm(signatureImage) { + let requestData = { + strainCode: this.form.strainCode, + strainName: this.form.strainName, + strainSource: this.form.strainSource, + appraisalMethod: this.form.appraisalMethod, + features: this.form.features, + saveLocation: this.form.saveLocation, + saveMethod: this.form.saveMethod, + remark: this.form.remark, + signature: signatureImage, + type: 2, + }; + if (this.currentAction === 'batchAdd') { + requestData.batchCount = this.batchForm.count; + } else { + requestData.isDraft = this.form.isDraft + } + try { + if (this.$route.query.id) { + requestData.id = this.$route.query.id; + await edit(requestData); + } else if (this.currentAction === 'batchAdd') { + await addBatch(requestData); + } else { + await add(requestData); + } + this.signatureVisible = false; + this.$router.back(); + this.$message.success('操作成功'); + } catch (error) { + this.$message.error('操作失败'); + } + } + } } </script> <style scoped lang="less"> -.strain-form { - padding: 0 40px; +.add-strain { + height: 100%; + background: #F5F7FA; - .form-grid { - display: grid; - grid-template-columns: repeat(3, 1fr); - gap: 24px; - margin-bottom: 24px; - - @media screen and (max-width: 1200px) { - grid-template-columns: repeat(2, 1fr); - } - - @media screen and (max-width: 768px) { - grid-template-columns: 1fr; - } - } + .form-card { + background: #fff; + border-radius: 8px; + } +} - .form-row { - display: flex; - flex-wrap: wrap; - gap: 24px; - margin-bottom: 24px; +.header-title { + margin-bottom: 24px; - .el-form-item { - margin-bottom: 0; + &-left { + display: flex; + align-items: center; - &.full-width { - width: 100%; - } - } - } + img { + width: 20px; + height: 20px; + margin-right: 8px; + } - :deep(.el-form-item__label) { - font-weight: normal; - color: #606266; - padding-bottom: 8px; - line-height: 20px; - } - - :deep(.el-form-item__content) { - line-height: unset; - } - - :deep(.el-input__inner) { - border-radius: 4px; - height: 36px; - line-height: 36px; - } - - :deep(.el-textarea__inner) { - border-radius: 4px; - padding: 8px 12px; - min-height: 120px; - } + div { + font-size: 18px; + font-weight: bold; + color: #303133; + } + } } .end-btn { - display: flex; - align-items: center; - justify-content: center; - gap: 10px; + display: flex; + align-items: center; + justify-content: center; + gap: 10px; - :deep(.el-button) { - width: 180px; - height: 36px; - padding: 0; - display: flex; - align-items: center; - justify-content: center; - font-size: 14px; - border-radius: 4px; - margin: 0; - } + button { + width: 180px; + height: 36px; + // background: #409EFF; + } } -</style> + +.strain-form { + padding: 0 40px; + + .form-row { + display: flex; + flex-wrap: wrap; + gap: 24px; + margin-bottom: 24px; + + &.three-columns { + + .el-form-item, + .form-item-placeholder { + flex: 1; + min-width: 280px; + + @media screen and (max-width: 1200px) { + min-width: calc(50% - 12px); + } + + @media screen and (max-width: 768px) { + min-width: 100%; + } + } + + .form-item-placeholder { + @media screen and (max-width: 1200px) { + display: none; + } + } + } + + .el-form-item { + margin-bottom: 0; + + &.full-width { + width: 100%; + } + } + } + + :deep(.el-form-item__label) { + font-weight: normal; + color: #606266; + padding-bottom: 8px; + line-height: 20px; + } + + :deep(.el-form-item__content) { + line-height: unset; + } + + :deep(.el-input__inner) { + border-radius: 4px; + height: 36px; + line-height: 36px; + } + + :deep(.el-textarea__inner) { + border-radius: 4px; + padding: 8px 12px; + min-height: 120px; + } +} + +.batch-add-dialog { + :deep(.el-dialog__header) { + margin: 0; + padding: 20px; + text-align: center; + border-bottom: 1px solid #EBEEF5; + + .el-dialog__title { + font-size: 16px; + font-weight: 600; + color: #303133; + } + } + + :deep(.el-dialog__body) { + padding: 20px; + } + + .dialog-content { + display: flex; + flex-direction: column; + align-items: center; + } + + .batch-form { + width: 100%; + display: flex; + flex-direction: column; + align-items: center; + + :deep(.el-form-item) { + width: 320px; + margin-bottom: 0; + } + + :deep(.el-form-item__label) { + width: 100%; + color: #606266; + font-weight: normal; + padding-bottom: 8px; + + &::before { + color: #F56C6C; + } + } + + :deep(.el-input) { + width: 100%; + + input { + width: 100%; + } + } + } + + .dialog-notice { + margin-top: 16px; + text-align: center; + + p { + margin: 0; + line-height: 22px; + color: #606266; + font-size: 14px; + } + } + + :deep(.el-dialog__footer) { + padding: 0 20px 20px; + text-align: center; + + .el-button { + width: 180px; + height: 36px; + padding: 0; + display: flex; + align-items: center; + justify-content: center; + font-size: 14px; + border-radius: 4px; + margin: 0; + } + } +} + +.end-btn { + display: flex; + align-items: center; + justify-content: center; + gap: 10px; + + :deep(.el-button) { + width: 180px; + height: 36px; + padding: 0; + display: flex; + align-items: center; + justify-content: center; + font-size: 14px; + border-radius: 4px; + margin: 0; + } +} +</style> \ No newline at end of file diff --git a/culture/src/views/strain-library/main-cell-library/index.vue b/culture/src/views/strain-library/main-cell-library/index.vue index 991ed6b..4346e86 100644 --- a/culture/src/views/strain-library/main-cell-library/index.vue +++ b/culture/src/views/strain-library/main-cell-library/index.vue @@ -1,474 +1,491 @@ <template> - <div class="list"> - <el-card class="header-box"> - <div class="box-title"> - <img src="@/assets/public/notice.png" class="header-icon"> - <span>菌种源保藏出/入主细胞库登记表说明</span> - <el-button type="text" class="view-more" @click="handleViewMore">查看全部 >></el-button> - </div> - <div class="header-content" :class="{ 'collapsed': true }"> - <p>1、菌种全部集中登记在【菌种源保藏出/入主细胞库登记表】,请分类管理。</p> - <p>1.1 接种入主细胞库(现代-M)的菌株经过培养和保藏。</p> - <p>1.2 从原始细胞库转入的菌株需要按照标准程序进行记录和管理。</p> - <p>1.3 主细胞库的菌株可用于科研项目和工业生产中的应用研究。</p> - </div> + <div class="list"> + <el-card class="header-box"> + <div class="box-title"> + <img src="@/assets/public/notice.png" class="header-icon" /> + <span>菌种源保藏出/入细胞库登记表说明</span> + <el-button type="text" class="view-more" @click="handleViewMore" + >查看全部 >></el-button + > + </div> + <div class="header-content" :class="{ collapsed: true }"> + <p> + 1. 菌种全部集中登记在【菌种源保藏出/入细胞库登记表】,菌种来源有 3 + 条路径。1.1 是沙土管或甘油管的源头菌种;入原始细胞库(祖代-O)。1.2 + 是斜面的源头菌种;接种入主细胞库(祖代-O)。经过育种、验证后,菌种保藏为甘油管或沙土管的,入原始细胞库(祖代-0)1.3 + 是含菌物质自己分离后获得的斜面源头菌种,接种入主细胞库;经生产验证后,保藏为沙土管或甘油管,入原始细胞库(祖代-O)。 + 2. + 菌种细胞库,分类入三库,进行传代运行管理。三类库存空间进行区分,保藏菌种。2.1 + 原始细胞库(祖代-O)、2.2 主细胞库(母代-M)、2.3 + 生产细胞库(子代-S)、(孙代-G)3. 细胞库编码规则3.1 + 细胞库编码规则:DD-M-240919-01-(O-0109-01)DD:代表项目组。M:“O”代表祖代原始细胞库,”M“代表母代主细胞库,”S“代表子代生产细胞库,“G”代表孙代生产细胞库。240919:代表在 + 24 年 9 月 19 + 接种批次的菌种;或收到外来菌种时间的入库批次。01:代表两位序列号。(O-0109-01):代表传代菌种的编号3.1.1 + 传代编码方式演例:祖代:DD-O-240919-01 + 传母代:DD-M-241017-01-(O-091901)DD-M-241017-02-(O-091901)DD-M-241017-03-(O-091901)子代:DD-S-241019-01-(M-1017-02)版权归奥利元生物所有,禁止外传。DD-S-241019-02-(M-1017-03)孙代:DD-G-241109-01-(S-1019-02)3.1.2 + 编码规则实现了编码唯一,编码可溯源,编码直观、方便。3.2 + 细胞库说明:3.2.1 + 直接购买、自行从(土壤、相关物料、商品)等分离出来菌株进入原始细胞库。3.2.2 + 从原始细胞库中选取出来再次纯化、改造、提高性能的菌株进入主细胞库。3.2.3 + 主细胞库中选取出稳定,生产性能良好的菌株扩培后保种进入生产细胞库。4. + 菌种选育-保藏过程编号说明4.1 菌种选育时,培养皿的编号可使用 a-01、a-02 + 等用于清晰形态观察记录;菌落编号使用序号 1/2/3等。4.2 + 接种斜面菌种编码(-O)使用原始细胞库编码;斜面转菌种保藏使用与斜面一致的编码(-O);斜面传代入主细胞库的传代菌种,按编码器规则编码(-M)。 + </p> + </div> - <!-- 查看全部弹窗 --> - <el-dialog - title="菌种源保藏出/入主细胞库登记表说明" - :visible.sync="dialogVisible" - width="50%" - class="view-all-dialog" + <!-- 查看全部弹窗 --> + <el-dialog + title="菌种源保藏出/入细胞库登记表说明" + :visible.sync="dialogVisible" + width="50%" + class="view-all-dialog" + > + <div class="dialog-content"> + <p> + 1. 菌种全部集中登记在【菌种源保藏出/入细胞库登记表】,菌种来源有 3 + 条路径。1.1 是沙土管或甘油管的源头菌种;入原始细胞库(祖代-O)。1.2 + 是斜面的源头菌种;接种入主细胞库(祖代-O)。经过育种、验证后,菌种保藏为甘油管或沙土管的,入原始细胞库(祖代-0)1.3 + 是含菌物质自己分离后获得的斜面源头菌种,接种入主细胞库;经生产验证后,保藏为沙土管或甘油管,入原始细胞库(祖代-O)。2. + 菌种细胞库,分类入三库,进行传代运行管理。三类库存空间进行区分,保藏菌种。2.1 + 原始细胞库(祖代-O)、2.2 主细胞库(母代-M)、2.3 + 生产细胞库(子代-S)、(孙代-G)3. 细胞库编码规则3.1 + 细胞库编码规则:DD-M-240919-01-(O-0109-01)DD:代表项目组。M:“O”代表祖代原始细胞库,”M“代表母代主细胞库,”S“代表子代生产细胞库,“G”代表孙代生产细胞库。240919:代表在 + 24 年 9 月 19 + 接种批次的菌种;或收到外来菌种时间的入库批次。01:代表两位序列号。(O-0109-01):代表传代菌种的编号3.1.1 + 传代编码方式演例:祖代:DD-O-240919-01 + 传母代:DD-M-241017-01-(O-091901)DD-M-241017-02-(O-091901)DD-M-241017-03-(O-091901)子代:DD-S-241019-01-(M-1017-02)版权归奥利元生物所有,禁止外传。DD-S-241019-02-(M-1017-03)孙代:DD-G-241109-01-(S-1019-02)3.1.2 + 编码规则实现了编码唯一,编码可溯源,编码直观、方便。3.2 + 细胞库说明:3.2.1 + 直接购买、自行从(土壤、相关物料、商品)等分离出来菌株进入原始细胞库。3.2.2 + 从原始细胞库中选取出来再次纯化、改造、提高性能的菌株进入主细胞库。3.2.3 + 主细胞库中选取出稳定,生产性能良好的菌株扩培后保种进入生产细胞库。4. + 菌种选育-保藏过程编号说明4.1 菌种选育时,培养皿的编号可使用 + a-01、a-02 等用于清晰形态观察记录;菌落编号使用序号 1/2/3等。4.2 + 接种斜面菌种编码(-O)使用原始细胞库编码;斜面转菌种保藏使用与斜面一致的编码(-O);斜面传代入主细胞库的传代菌种,按编码器规则编码(-M)。 + </p> + </div> + </el-dialog> + </el-card> + + <!-- Table --> + <TableCustom + :queryForm="queryForm" + :tableData="tableData" + :total="total" + @currentChange="handleCurrentChange" + @sizeChange="handleSizeChange" + > + <template #search> + <el-form :model="form" label-width="auto" inline> + <el-form-item label="菌种编号:"> + <el-input v-model="form.strainNo" 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 v-if="roleType == 4" label="状态:"> + <el-select v-model="form.status" placeholder="请选择"> + <el-option label="全部" value=""></el-option> + <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-form-item class="search-btn-box"> + <el-button type="default" @click="resetForm">重置</el-button> + <el-button type="primary" @click="searchData">查询</el-button> + </el-form-item> + </el-form> + </template> + + <template #setting> + <div class="tableTitle"> + <div class="flex a-center"> + <div + class="title" + :class="{ active: currentType === 'list' }" + @click="handleTypeChange('list')" > - <div class="dialog-content"> - <p>1、菌种全部集中登记在【菌种源保藏出/入主细胞库登记表】,请分类管理。</p> - <p>1.1 接种入主细胞库(现代-M)的菌株经过培养和保藏。</p> - <p>1.2 从原始细胞库转入的菌株需要按照标准程序进行记录和管理。</p> - <p>1.3 主细胞库的菌株可用于科研项目和工业生产中的应用研究。</p> - <p>1.4 菌株转出时需要严格记录去向和用途,确保可追溯性。</p> - <p>1.5 主细胞库的菌株保存应当遵循标准操作规程,确保活性和稳定性。</p> - </div> - </el-dialog> - </el-card> + 主细胞列表 + </div> + <div + class="drafts" + :class="{ active: currentType === 'draft' }" + @click="handleTypeChange('draft')" + > + 草稿箱 + </div> + </div> + <div v-if="roleType == 4" class="flex a-center"> + <el-button + @click="handleNewStrain" + class="el-icon-plus" + type="primary" + style="margin-right: 12px" + >新增主细胞</el-button + > + <el-button + @click="handleNewStrain" + class="el-icon-plus" + type="primary" + >批量新增</el-button + > + </div> + </div> + </template> - <!-- Table --> - <TableCustom :queryForm="queryForm" :tableData="tableData" :total="total" @currentChange="handleCurrentChange" - @sizeChange="handleSizeChange"> - <template #search> - <el-form :model="form" label-width="auto" inline> - <el-form-item label="菌种编号:"> - <el-input v-model="form.strainNo" 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-select v-model="form.status" placeholder="请选择"> - <el-option label="全部" value=""></el-option> - <el-option label="已入库" value="1"></el-option> - <el-option label="已出库" value="2"></el-option> - <el-option label="入库待确认" value="3"></el-option> - </el-select> - </el-form-item> - <el-form-item class="search-btn-box"> - <el-button type="default" @click="resetForm">重置</el-button> - <el-button type="primary" @click="searchData">查询</el-button> - </el-form-item> - </el-form> - </template> - - <template #setting> - <div class="tableTitle"> - <div class="flex a-center"> - <div class="title" :class="{ active: currentType === 'list' }" - @click="handleTypeChange('list')"> - 主细胞列表</div> - <div class="drafts" :class="{ active: currentType === 'draft' }" - @click="handleTypeChange('draft')"> - 草稿箱</div> - </div> - <div class="flex a-center"> - <el-button @click="handleNewStrain" class="el-icon-plus" type="primary" style="margin-right: 12px;">新增主细胞</el-button> - <el-button @click="handleBatchAdd" class="el-icon-plus" type="primary">批量新增</el-button> - </div> - </div> - </template> - - <template #table> - <el-table-column prop="strainNo" label="菌种编号" /> - <el-table-column prop="strainName" label="菌种名称" /> - <el-table-column prop="source" label="菌种来源" /> - <el-table-column prop="method" label="鉴定方法" /> - <el-table-column prop="certificate" label="特征描述" /> - <el-table-column prop="storage" label="菌种保存方法" /> - <el-table-column prop="amount" label="保存位置" /> - <el-table-column prop="inventory" label="库存余量" /> - <el-table-column prop="notes" label="备注" /> - <el-table-column prop="status" label="当前状态"> - <template #default="{ row }"> - <el-tag :type="getStatusType(row.status)">{{ getStatusText(row.status) }}</el-tag> - </template> - </el-table-column> - <el-table-column label="操作" width="200"> - <template #default="{ row }"> - <el-button type="text" @click="handleDetail(row)">详情</el-button> - <el-button type="text" @click="handleEdit(row)">编辑</el-button> - <el-button type="text" @click="handleRecord(row)">出入库记录</el-button> - </template> - </el-table-column> - </template> - </TableCustom> - <StrainDetail - :visible.sync="detailVisible" - :detail="currentDetail" - /> - </div> + <template #table> + <el-table-column prop="strainCode" label="菌种编号" /> + <el-table-column prop="strainName" label="菌种名称" /> + <el-table-column prop="strainSource" label="菌种来源" /> + <el-table-column prop="appraisalMethod" label="鉴定方法" /> + <el-table-column prop="features" label="特征描述" /> + <el-table-column prop="saveMethod" label="菌种保存方法" /> + <el-table-column prop="saveLocation" label="保藏位置" /> + <el-table-column prop="stock" label="库存余量" /> + <el-table-column prop="remark" label="备注" /> + <el-table-column + v-if="currentType === 'list'" + prop="status" + label="当前状态" + > + <template #default="{ row }"> + <el-tag :type="getStatusType(row.status)">{{ + getStatusText(row.status) + }}</el-tag> + </template> + </el-table-column> + <el-table-column label="操作" width="200"> + <template #default="{ row }"> + <el-button type="text" @click="handleDetail(row)">详情</el-button> + <el-button v-if="row.status == 2 || row.status == 4" type="text" @click="handleEdit(row)">编辑</el-button> + <el-button + v-if="currentType === 'list'" + type="text" + @click="handleRecord(row)" + >出入库记录</el-button + > + <el-button v-if="roleType == 1" type="text" @click="handleDelete(row)">删除</el-button> + </template> + </el-table-column> + </template> + </TableCustom> + <StrainDetail :visible.sync="detailVisible" :detail="currentDetail" /> + </div> </template> <script> -import StrainDetail from '../strain-library-manage/components/StrainDetail.vue' +import StrainDetail from "../strain-library-manage/components/StrainDetail.vue"; +import { getList, deleteStrainLibrary } from "../strain-library-manage/service"; export default { - name: 'MainCellLibrary', - components: { - StrainDetail + name: "StrainLibraryManage", + components: { + StrainDetail, + }, + data() { + return { + dialogVisible: false, + currentType: "list", + detailVisible: false, + currentDetail: {}, + form: { + strainNo: "", + strainName: "", + status: "", + }, + queryForm: { + pageSize: 10, + pageNum: 1, + }, + total: 800, + tableData: [], + roleType: "", + }; + }, + activated() { + this.searchData(); + // 角色类型 1=超级管理员 2=审批人 3=工程师 4=实验员 + this.roleType = JSON.parse(sessionStorage.getItem("userInfo")).roleType; + }, + methods: { + handleDelete(row) { + this.$confirm("确定删除该数据吗?", "提示", { + confirmButtonText: "确定", + cancelButtonText: "取消", + type: "warning", + }).then(() => { + deleteStrainLibrary({ id: row.id }).then((res) => { + this.$message.success("删除成功"); + this.searchData(); + }); + }); }, - data() { - return { - dialogVisible: false, - currentType: 'list', - detailVisible: false, - currentDetail: {}, - form: { - strainNo: '', - strainName: '', - status: '' - }, - queryForm: { - pageSize: 10, - pageNum: 1 - }, - total: 800, - tableData: [ - { - strainNo: 'M-2024001', - strainName: '大肠杆菌BL21', - source: '原始细胞库', - method: '分子生物学鉴定', - certificate: '常用表达宿主菌,含有DE3溶源体,适合蛋白表达', - storage: '甘油冷冻', - amount: 'M区-01-001', - inventory: '100', - notes: '高效表达宿主', - status: '1' - }, - { - strainNo: 'M-2024002', - strainName: '乳酸菌L.plantarum', - source: '菌种保藏中心', - method: '16S rDNA测序', - certificate: '革兰氏阳性杆菌,产乳酸,益生特性', - storage: '冷冻保存', - amount: 'M区-02-005', - inventory: '80', - notes: '发酵剂开发', - status: '1' - }, - { - strainNo: 'M-2024003', - strainName: '酵母S.cerevisiae', - source: '原始细胞库', - method: '生理生化鉴定', - certificate: '椭圆形单细胞真菌,高效发酵能力', - storage: '斜面培养', - amount: 'M区-03-002', - inventory: '60', - notes: '酒精发酵', - status: '2' - }, - { - strainNo: 'M-2024004', - strainName: '枯草芽孢杆菌', - source: '环境样本分离', - method: '形态学观察和生化鉴定', - certificate: '革兰氏阳性芽孢杆菌,可产多种酶类', - storage: '冻干保存', - amount: 'M区-01-003', - inventory: '90', - notes: '工业酶生产', - status: '3' - }, - { - strainNo: 'M-2024005', - strainName: '链霉菌S.griseus', - source: '原始细胞库', - method: 'PCR鉴定', - certificate: '丝状菌,产生灰色气生菌丝和分生孢子', - storage: '液氮保存', - amount: 'M区-04-001', - inventory: '70', - notes: '抗生素研究', - status: '1' - } - ] - } + handleRecord(row) { + this.$router.push({ + path: `/strain-library/strain-library-manage/record?id=${row.id}`, + }); }, - methods: { - handleViewMore() { - this.dialogVisible = true; - }, - resetForm() { - this.form = { - strainNo: '', - strainName: '', - status: '' - } - this.searchData() - }, - searchData() { - // 模拟搜索逻辑 - const { strainNo, strainName, status } = this.form - let filteredData = [...this.tableData] - - if (strainNo) { - filteredData = filteredData.filter(item => - item.strainNo.toLowerCase().includes(strainNo.toLowerCase()) - ) - } - if (strainName) { - filteredData = filteredData.filter(item => - item.strainName.toLowerCase().includes(strainName.toLowerCase()) - ) - } - if (status) { - filteredData = filteredData.filter(item => - item.status === status - ) - } - - this.total = filteredData.length - // 实际项目中这里应该调用API - console.log('搜索条件:', this.form) - console.log('分页信息:', this.queryForm) - }, - handleNewStrain() { - this.$router.push('/strain-library/main-cell-library/add') - // Implement new strain logic - }, - handleBatchAdd() { - // Implement batch add logic - }, - handleDetail(row) { - this.currentDetail = row; - this.detailVisible = true; - }, - handleEdit(row) { - // Implement edit logic - }, - handleRecord(row) { - this.$router.push({ - path: '/strain-library/strain-library-manage/record', - query: { - id: row.strainNo - } - }) - }, - handleCurrentChange(page) { - this.queryForm.pageNum = page - // Implement page change logic - }, - handleSizeChange(size) { - this.queryForm.pageSize = size - // Implement size change logic - }, - handleTypeChange(type) { - this.currentType = type; - // Implement type change logic - }, - getStatusType(status) { - const types = { - 1: 'success', - 2: 'info', - 3: 'warning' - } - return types[status] || 'info' - }, - getStatusText(status) { - const texts = { - 1: '已入库', - 2: '已出库', - 3: '入库待确认' - } - return texts[status] || '未知状态' - } - } -} + handleNewStrain() { + this.$router.push({ path: "/strain-library/main-cell-library/add" }); + }, + handleEdit(row) { + this.$router.push({ + path: `/strain-library/main-cell-library/add?id=${row.id}`, + }); + }, + handleDetail(row) { + this.currentDetail = row; + this.detailVisible = true; + }, + handleViewMore() { + this.dialogVisible = true; + }, + resetForm() { + this.form = { + strainNo: "", + strainName: "", + status: "", + }; + this.searchData(); + }, + searchData() { + const params = { + pageNum: this.queryForm.pageNum, + pageSize: this.queryForm.pageSize, + strainCode: this.form.strainNo, + strainName: this.form.strainName, + isDraft: this.currentType === "draft" ? 1 : 0, + status: this.form.status, + type: 2, + }; + console.log(params); + + getList(params) + .then((res) => { + if (res.code === 200) { + this.tableData = res.data.records; + this.total = res.data.total; + } + }) + .catch((err) => { + this.$message.error("数据加载失败"); + }); + }, + handleCurrentChange(page) { + this.queryForm.pageNum = page; + this.searchData(); + }, + handleSizeChange(size) { + this.queryForm.pageSize = size; + this.searchData(); + }, + handleTypeChange(type) { + this.currentType = type; + this.searchData(); + }, + getStatusType(status) { + const types = { + 1: "warning", + 2: "warning", + 3: "success", + 4: "success", + }; + return types[status] || "info"; + }, + getStatusText(status) { + const texts = { + 1: "已出库", + 2: "出库待确认", + 3: "已入库", + 4: "入库待确认", + }; + return texts[status] || "未知状态"; + }, + }, +}; </script> <style scoped lang="less"> .list { - padding: 20px; + padding: 20px; } .header-box { - margin-bottom: 20px; - border-radius: 16px; - background: rgba(255, 255, 255, 0.8); + margin-bottom: 20px; + border-radius: 16px; + background: rgba(255, 255, 255, 0.8); - .box-title { - display: flex; - align-items: center; - font-size: 18px; - font-weight: bold; - margin-bottom: 15px; - position: relative; + .box-title { + display: flex; + align-items: center; + font-size: 18px; + font-weight: bold; + margin-bottom: 15px; + position: relative; - .header-icon { - width: 20px; - height: 20px; - margin-right: 10px; - } - - .view-more { - position: absolute; - right: 0; - color: #049C9A; - } + .header-icon { + width: 20px; + height: 20px; + margin-right: 10px; } - .header-content { - color: rgba(0, 0, 0, 0.88); - font-size: 14px; - line-height: 1.8; - margin-left: 30px; - transition: max-height 0.3s ease-in-out; - overflow: hidden; - - &.collapsed { - max-height: 48px; - overflow: hidden; - } - - p { - margin: 5px 0; - } + .view-more { + position: absolute; + right: 0; + color: #049c9a; } + } + + .header-content { + color: rgba(0, 0, 0, 0.88); + font-size: 14px; + line-height: 1.8; + margin-left: 30px; + transition: max-height 0.3s ease-in-out; + overflow: hidden; + + &.collapsed { + max-height: 48px; + overflow: hidden; + } + + p { + margin: 5px 0; + } + } } .search-form { - margin-bottom: 20px; - background: #F5F7FA; - padding: 24px; - border-radius: 8px; + margin-bottom: 20px; + background: #f5f7fa; + padding: 24px; + border-radius: 8px; - .el-form-item { - margin-right: 20px; - margin-bottom: 0; - } + .el-form-item { + margin-right: 20px; + margin-bottom: 0; + } - .el-button { - margin-left: 10px; - } + .el-button { + margin-left: 10px; + } } .action-buttons { - margin-bottom: 20px; + margin-bottom: 20px; - .el-button { - margin-right: 10px; - } + .el-button { + margin-right: 10px; + } } .tab-container { - display: flex; - margin-bottom: 20px; + display: flex; + margin-bottom: 20px; - .tab { - padding: 10px 30px; - border: 1px solid #DCDFE6; - border-bottom: none; - border-radius: 8px 8px 0 0; - cursor: pointer; - margin-right: 10px; - background: #F5F7FA; + .tab { + padding: 10px 30px; + border: 1px solid #dcdfe6; + border-bottom: none; + border-radius: 8px 8px 0 0; + cursor: pointer; + margin-right: 10px; + background: #f5f7fa; - &.active { - background: #fff; - border-color: #049C9A; - color: #049C9A; - font-weight: bold; - } + &.active { + background: #fff; + border-color: #049c9a; + color: #049c9a; + font-weight: bold; } + } } .flex { - display: flex; - align-items: center; -} - -.a-center { - align-items: center; + display: flex; + align-items: center; } .tableTitle { - display: flex; - padding-bottom: 20px; - justify-content: space-between; - align-items: center; + 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; - width: unset; - cursor: pointer; - height: 50px; - line-height: 50px; - width: 166px; - text-align: center; + .title { + background: #fafafc; + border-radius: 8px 8px 0px 0px; + border: 1px solid #dcdfe6; + font-weight: bold; + font-size: 18px; + color: #606266; + width: unset; + cursor: pointer; + height: 50px; + line-height: 50px; + width: 166px; + 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: 166px; + 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: 166px; - text-align: center; - } - - .active { - color: #049c9a; - background: #ffffff; - border-radius: 8px 8px 0px 0px; - border: 1px solid #049c9a; - - } + .active { + color: #049c9a; + background: #ffffff; + border-radius: 8px 8px 0px 0px; + border: 1px solid #049c9a; + } } .view-all-dialog { - :deep(.el-dialog__header) { - padding: 20px; - border-bottom: 1px solid #EBEEF5; - margin-right: 0; - - .el-dialog__title { - font-size: 18px; - font-weight: bold; - color: #303133; - } + :deep(.el-dialog__header) { + padding: 20px; + border-bottom: 1px solid #ebeef5; + margin-right: 0; + + .el-dialog__title { + font-size: 18px; + font-weight: bold; + color: #303133; } + } - :deep(.el-dialog__body) { - padding: 20px; + :deep(.el-dialog__body) { + padding: 20px; - .dialog-content { - font-size: 14px; - line-height: 1.8; - color: #606266; + .dialog-content { + font-size: 14px; + line-height: 1.8; + color: #606266; - p { - margin: 12px 0; - - &:first-child { - margin-top: 0; - } - - &:last-child { - margin-bottom: 0; - } - } + p { + margin: 12px 0; + + &:first-child { + margin-top: 0; } + + &:last-child { + margin-bottom: 0; + } + } } + } } -</style> \ No newline at end of file +</style> diff --git a/culture/src/views/strain-library/main-cell-library/record.vue b/culture/src/views/strain-library/main-cell-library/record.vue index e434d70..aef2643 100644 --- a/culture/src/views/strain-library/main-cell-library/record.vue +++ b/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> \ No newline at end of file +</style> diff --git a/culture/src/views/strain-library/main-cell-library/service.js b/culture/src/views/strain-library/main-cell-library/service.js new file mode 100644 index 0000000..08f789d --- /dev/null +++ b/culture/src/views/strain-library/main-cell-library/service.js @@ -0,0 +1,56 @@ +import axios from '@/utils/request'; + +// 列表 +export const getList = (data) => { + return axios.post('/api/t-train-library/pageList', { ...data }) +} + +// 新增 +export const add = (data) => { + return axios.post('/api/t-train-library/add', { ...data }) +} + +// 编辑 +export const edit = (data) => { + return axios.post('/api/t-train-library/update', { ...data }) +} + +// 查看详情 +export const getDetail = (params) => { + return axios.get('/open/t-train-library/getDetailEditById', { params }) +} + +// 批量新增 +export const addBatch = (data) => { + return axios.post('/api/t-train-library/addBatch', data) +} + +// 查看菌种库详情 +export const getDetailById = (data) => { + return axios.post('/open/t-train-library/getDetailById', { ...data }) +} + +// 获取菌种库出入库时间轴列表 +export const timeList = (data) => { + return axios.post('/api/t-train-library/timeList?id='+data.id, { ...data }) +} + +// 新增菌种库出入记录 +export const addWarehousing = (data) => { + return axios.post('/open/t-train-library/addWarehousing', { ...data }) +} + +// 确认出入库 +export const confirmWarehousing = (data) => { + return axios.post('/api/t-train-library/confirm', { ...data }) +} + +// 删除菌种库 +export const deleteStrainLibrary = (params) => { + return axios.delete('/open/t-train-library/deleteById', { params }) +} + +// 删除菌种库出入库记录 +export const deleteWarehousing = (params) => { + return axios.delete('/open/t-train-library/deleteWarehousingById', { params }) +} diff --git a/culture/src/views/strain-library/production-cell-library/add.vue b/culture/src/views/strain-library/production-cell-library/add.vue index 132d57e..058aa82 100644 --- a/culture/src/views/strain-library/production-cell-library/add.vue +++ b/culture/src/views/strain-library/production-cell-library/add.vue @@ -1,141 +1,294 @@ <template> <Card> - <el-form - :model="form" - :rules="rules" - ref="strainForm" - label-position="top" - class="strain-form" - > - <div class="form-grid"> - <el-form-item label="菌种编号" prop="strainNo" required> - <el-input v-model="form.strainNo" placeholder="请输入"></el-input> + <el-form :model="form" :rules="rules" ref="strainForm" label-position="top" class="strain-form"> + <div class="form-row three-columns"> + <el-form-item label="菌种编号" prop="strainCode"> + <el-input v-model="form.strainCode" placeholder="请输入"></el-input> </el-form-item> - <el-form-item label="菌种名称" prop="strainName" required> + <el-form-item label="菌种名称" prop="strainName"> <el-input v-model="form.strainName" placeholder="请输入"></el-input> </el-form-item> - <el-form-item label="菌种来源" prop="source" required> - <el-input v-model="form.source" placeholder="请输入"></el-input> - </el-form-item> - <el-form-item label="鉴定方法" prop="identificationMethod" required> - <el-input v-model="form.identificationMethod" placeholder="请输入"></el-input> - </el-form-item> - <el-form-item label="特征描述" prop="characteristics" required> - <el-input v-model="form.characteristics" placeholder="请输入"></el-input> - </el-form-item> - <el-form-item label="菌种保存方法" prop="preservationMethod" required> - <el-input v-model="form.preservationMethod" placeholder="请输入"></el-input> - </el-form-item> - <el-form-item label="保存位置" prop="storageLocation" required> - <el-input v-model="form.storageLocation" placeholder="请输入"></el-input> + <el-form-item label="菌种来源" prop="strainSource"> + <el-input v-model="form.strainSource" placeholder="请输入"></el-input> </el-form-item> </div> <div class="form-row"> + <el-form-item label="鉴定方法" prop="appraisalMethod"> + <el-input v-model="form.appraisalMethod" placeholder="请输入"></el-input> + </el-form-item> + </div> + + <div class="form-row"> + <el-form-item label="特征描述" prop="features" class="full-width"> + <el-input type="textarea" v-model="form.features" :rows="4" placeholder="请输入"></el-input> + </el-form-item> + </div> + + <div class="form-row three-columns"> + <el-form-item label="保藏位置" prop="saveLocation"> + <el-input v-model="form.saveLocation" placeholder="请输入"></el-input> + </el-form-item> + <el-form-item label="菌种保存方法" prop="saveMethod"> + <el-input v-model="form.saveMethod" placeholder="请输入"></el-input> + </el-form-item> + <div class="form-item-placeholder"></div> + </div> + + <div class="form-row"> <el-form-item label="备注" prop="remarks" class="full-width"> - <el-input - type="textarea" - v-model="form.remarks" - :rows="4" - placeholder="请输入" - ></el-input> + <el-input type="textarea" v-model="form.remark" :rows="4" placeholder="请输入"></el-input> </el-form-item> </div> <div class="end-btn" style="margin-top: 38px"> - <el-button type="primary" @click="handleSubmit">提交</el-button> - <el-button @click="handleDraft">存草稿</el-button> + <el-button type="primary" @click="handleSubmit(0)">提交</el-button> + <el-button v-if="!$route.query.id" type="primary" @click="handleBatchAdd">批量新增</el-button> + <el-button @click="handleSubmit(1)">存草稿</el-button> </div> </el-form> + <!-- 批量新增弹窗 --> + <el-dialog title="批量新增" :visible.sync="batchAddDialogVisible" width="520px" :close-on-click-modal="false" + :close-on-press-escape="false" custom-class="batch-add-dialog"> + <div class="dialog-content"> + <el-form :model="batchForm" ref="batchFormRef" label-position="top" class="batch-form"> + <el-form-item label="批量新增数量" prop="count" + :rules="[{ required: true, message: '请输入批量新增数量', trigger: 'blur' }]"> + <el-input v-model.number="batchForm.count" placeholder="请输入" /> + </el-form-item> + </el-form> + <div class="dialog-notice"> + <p>注意:操作批量新增后,系统会自动生成对应数量的菌种数据,</p> + <p>这些菌种的基础信息内容都是一致的,唯独菌种编号内容系统</p> + <p>不会自动生成,需要操作员自行编辑</p> + </div> + </div> + <template #footer> + <div class="end-btn"> + <el-button type="primary" @click="handleConfirmBatchAdd">确认新增</el-button> + </div> + </template> + </el-dialog> + <!-- 签字确认组件 --> - <SignatureCanvas - :visible.sync="signatureVisible" - @confirm="handleSignatureConfirm" - /> + <SignatureCanvas :visible.sync="signatureVisible" @confirm="handleSignatureConfirm" /> </Card> </template> <script> import SignatureCanvas from '@/components/SignatureCanvas.vue' +import { add, edit, getDetail, addBatch } from './service' export default { - name: 'AddProductionCell', + name: 'StrainLibraryManageAdd', components: { SignatureCanvas }, data() { return { + batchAddDialogVisible: false, signatureVisible: false, + currentAction: '', // 'submit' or 'batchAdd' + batchForm: { + count: '' + }, form: { - strainNo: '', + strainCode: '', strainName: '', source: '', - identificationMethod: '', + appraisalMethod: '', characteristics: '', storageLocation: '', preservationMethod: '', - remarks: '' + remark: '' }, rules: { - strainNo: [{ required: true, message: '请输入菌种编号', trigger: 'blur' }], + strainCode: [{ + validator: (rule, value, callback) => { + if (this.currentAction === 'submit' && !value) { + callback(new Error('请输入菌种编号')); + } else { + callback(); + } + }, + trigger: 'change' + }], strainName: [{ required: true, message: '请输入菌种名称', trigger: 'blur' }], - source: [{ required: true, message: '请输入菌种来源', trigger: 'blur' }], - identificationMethod: [{ required: true, message: '请输入鉴定方法', trigger: 'blur' }], - characteristics: [{ required: true, message: '请输入特征描述', trigger: 'blur' }], - storageLocation: [{ required: true, message: '请输入保存位置', trigger: 'blur' }], - preservationMethod: [{ required: true, message: '请输入菌种保存方法', trigger: 'blur' }] + strainSource: [{ required: true, message: '请输入菌种来源', trigger: 'blur' }], + appraisalMethod: [{ required: true, message: '请输入鉴定方法', trigger: 'blur' }], + features: [{ required: true, message: '请输入特征描述', trigger: 'blur' }], + saveLocation: [{ required: true, message: '请输入保存位置', trigger: 'blur' }], + saveMethod: [{ required: true, message: '请输入菌种保存方法', trigger: 'blur' }] + } + } + }, + activated() { + if (this.$route.query.id) { + getDetail({ id: this.$route.query.id }).then(res => { + this.form = res + }) + } + }, + watch: { + '$route.query.id'() { + this.form = { + strainCode: '', + strainName: '', + source: '', + appraisalMethod: '', + characteristics: '', + storageLocation: '', + preservationMethod: '', + remark: '' } } }, methods: { - handleSubmit() { + handleSubmit(isDraft) { + this.currentAction = 'submit' this.$refs.strainForm.validate((valid) => { if (valid) { + this.form.isDraft = isDraft + if (isDraft == 1) { + //存草稿 + this.handleSignatureConfirm('') + } else { + this.signatureVisible = true + } + } + }) + }, + handleBatchAdd() { + this.currentAction = 'batchAdd' + this.$refs.strainForm.validate((valid) => { + if (valid) { + this.batchAddDialogVisible = true + } + }) + }, + handleConfirmBatchAdd() { + this.$refs.batchFormRef.validate((valid) => { + if (valid) { + this.batchAddDialogVisible = false this.signatureVisible = true } }) }, - handleDraft() { - // 实现存草稿逻辑 - console.log('save draft', this.form) - this.$message.success('草稿保存成功') - }, - handleSignatureConfirm(signatureImage) { - this.signatureVisible = false - // 处理提交逻辑 - console.log('submit form with signature:', this.form, signatureImage) - this.$message.success('提交成功') - this.$router.back() + async handleSignatureConfirm(signatureImage) { + let requestData = { + strainCode: this.form.strainCode, + strainName: this.form.strainName, + strainSource: this.form.strainSource, + appraisalMethod: this.form.appraisalMethod, + features: this.form.features, + saveLocation: this.form.saveLocation, + saveMethod: this.form.saveMethod, + remark: this.form.remark, + signature: signatureImage, + type: 3, + }; + if (this.currentAction === 'batchAdd') { + requestData.batchCount = this.batchForm.count; + } else { + requestData.isDraft = this.form.isDraft + } + try { + if (this.$route.query.id) { + requestData.id = this.$route.query.id; + await edit(requestData); + } else if (this.currentAction === 'batchAdd') { + await addBatch(requestData); + } else { + await add(requestData); + } + this.signatureVisible = false; + this.$router.back(); + this.$message.success('操作成功'); + } catch (error) { + this.$message.error('操作失败'); + } } } } </script> <style scoped lang="less"> -.strain-form { - padding: 0 40px; +.add-strain { + height: 100%; + background: #F5F7FA; - .form-grid { - display: grid; - grid-template-columns: repeat(3, 1fr); - gap: 24px; - margin-bottom: 24px; - - @media screen and (max-width: 1200px) { - grid-template-columns: repeat(2, 1fr); + .form-card { + background: #fff; + border-radius: 8px; + } +} + +.header-title { + margin-bottom: 24px; + + &-left { + display: flex; + align-items: center; + + img { + width: 20px; + height: 20px; + margin-right: 8px; } - - @media screen and (max-width: 768px) { - grid-template-columns: 1fr; + + div { + font-size: 18px; + font-weight: bold; + color: #303133; } } +} + +.end-btn { + display: flex; + align-items: center; + justify-content: center; + gap: 10px; + + button { + width: 180px; + height: 36px; + // background: #409EFF; + } +} + +.strain-form { + padding: 0 40px; .form-row { display: flex; flex-wrap: wrap; gap: 24px; margin-bottom: 24px; + + &.three-columns { + + .el-form-item, + .form-item-placeholder { + flex: 1; + min-width: 280px; + + @media screen and (max-width: 1200px) { + min-width: calc(50% - 12px); + } + + @media screen and (max-width: 768px) { + min-width: 100%; + } + } + + .form-item-placeholder { + @media screen and (max-width: 1200px) { + display: none; + } + } + } .el-form-item { margin-bottom: 0; @@ -170,6 +323,91 @@ } } +.batch-add-dialog { + :deep(.el-dialog__header) { + margin: 0; + padding: 20px; + text-align: center; + border-bottom: 1px solid #EBEEF5; + + .el-dialog__title { + font-size: 16px; + font-weight: 600; + color: #303133; + } + } + + :deep(.el-dialog__body) { + padding: 20px; + } + + .dialog-content { + display: flex; + flex-direction: column; + align-items: center; + } + + .batch-form { + width: 100%; + display: flex; + flex-direction: column; + align-items: center; + + :deep(.el-form-item) { + width: 320px; + margin-bottom: 0; + } + + :deep(.el-form-item__label) { + width: 100%; + color: #606266; + font-weight: normal; + padding-bottom: 8px; + + &::before { + color: #F56C6C; + } + } + + :deep(.el-input) { + width: 100%; + + input { + width: 100%; + } + } + } + + .dialog-notice { + margin-top: 16px; + text-align: center; + + p { + margin: 0; + line-height: 22px; + color: #606266; + font-size: 14px; + } + } + + :deep(.el-dialog__footer) { + padding: 0 20px 20px; + text-align: center; + + .el-button { + width: 180px; + height: 36px; + padding: 0; + display: flex; + align-items: center; + justify-content: center; + font-size: 14px; + border-radius: 4px; + margin: 0; + } + } +} + .end-btn { display: flex; align-items: center; @@ -188,4 +426,4 @@ margin: 0; } } -</style> +</style> \ No newline at end of file diff --git a/culture/src/views/strain-library/production-cell-library/index.vue b/culture/src/views/strain-library/production-cell-library/index.vue index 1364cbf..31cfb28 100644 --- a/culture/src/views/strain-library/production-cell-library/index.vue +++ b/culture/src/views/strain-library/production-cell-library/index.vue @@ -2,36 +2,80 @@ <div class="list"> <el-card class="header-box"> <div class="box-title"> - <img src="@/assets/public/notice.png" class="header-icon"> - <span>【菌种源保藏出/入细胞库登记表】说明</span> - <el-button type="text" class="view-more" @click="handleViewMore">查看全部 >></el-button> + <img src="@/assets/public/notice.png" class="header-icon" /> + <span>菌种源保藏出/入细胞库登记表说明</span> + <el-button type="text" class="view-more" @click="handleViewMore" + >查看全部 >></el-button + > </div> - <div class="header-content" :class="{ 'collapsed': true }"> - <p>1、菌种全部集中登记在【菌种源保藏出/入细胞库登记表】,请将来源有3 类菌经。</p> - <p>1.1 原净土管理日油性的源头菌种:入细胞细胞库(现代-O)。</p> - <p>1.2 是到菌的源头菌种:接种入主细胞库(现代-O),经过百种、验证后,菌种被保存日油管理沙土菌种,入细胞细胞库(现代-O)。</p> - <p>1.3 是否菌种能自己分离后获得的源头菌种,接种入主细胞库:经过产验证后,保藏为少土管理日油管,入细胞细胞库(现代-O)。</p> + <div class="header-content" :class="{ collapsed: true }"> + <p> + 1. 菌种全部集中登记在【菌种源保藏出/入细胞库登记表】,菌种来源有 3 + 条路径。1.1 是沙土管或甘油管的源头菌种;入原始细胞库(祖代-O)。1.2 + 是斜面的源头菌种;接种入主细胞库(祖代-O)。经过育种、验证后,菌种保藏为甘油管或沙土管的,入原始细胞库(祖代-0)1.3 + 是含菌物质自己分离后获得的斜面源头菌种,接种入主细胞库;经生产验证后,保藏为沙土管或甘油管,入原始细胞库(祖代-O)。 + 2. + 菌种细胞库,分类入三库,进行传代运行管理。三类库存空间进行区分,保藏菌种。2.1 + 原始细胞库(祖代-O)、2.2 主细胞库(母代-M)、2.3 + 生产细胞库(子代-S)、(孙代-G)3. 细胞库编码规则3.1 + 细胞库编码规则:DD-M-240919-01-(O-0109-01)DD:代表项目组。M:“O”代表祖代原始细胞库,”M“代表母代主细胞库,”S“代表子代生产细胞库,“G”代表孙代生产细胞库。240919:代表在 + 24 年 9 月 19 + 接种批次的菌种;或收到外来菌种时间的入库批次。01:代表两位序列号。(O-0109-01):代表传代菌种的编号3.1.1 + 传代编码方式演例:祖代:DD-O-240919-01 + 传母代:DD-M-241017-01-(O-091901)DD-M-241017-02-(O-091901)DD-M-241017-03-(O-091901)子代:DD-S-241019-01-(M-1017-02)版权归奥利元生物所有,禁止外传。DD-S-241019-02-(M-1017-03)孙代:DD-G-241109-01-(S-1019-02)3.1.2 + 编码规则实现了编码唯一,编码可溯源,编码直观、方便。3.2 + 细胞库说明:3.2.1 + 直接购买、自行从(土壤、相关物料、商品)等分离出来菌株进入原始细胞库。3.2.2 + 从原始细胞库中选取出来再次纯化、改造、提高性能的菌株进入主细胞库。3.2.3 + 主细胞库中选取出稳定,生产性能良好的菌株扩培后保种进入生产细胞库。4. + 菌种选育-保藏过程编号说明4.1 菌种选育时,培养皿的编号可使用 a-01、a-02 + 等用于清晰形态观察记录;菌落编号使用序号 1/2/3等。4.2 + 接种斜面菌种编码(-O)使用原始细胞库编码;斜面转菌种保藏使用与斜面一致的编码(-O);斜面传代入主细胞库的传代菌种,按编码器规则编码(-M)。 + </p> </div> <!-- 查看全部弹窗 --> <el-dialog - title="菌种源保藏出/入生产细胞库登记表说明" + title="菌种源保藏出/入细胞库登记表说明" :visible.sync="dialogVisible" width="50%" class="view-all-dialog" > <div class="dialog-content"> - <p>1、菌种全部集中登记在【菌种源保藏出/入细胞库登记表】,请将来源有3 类菌经。</p> - <p>1.1 原净土管理日油性的源头菌种:入细胞细胞库(现代-O)。</p> - <p>1.2 是到菌的源头菌种:接种入主细胞库(现代-O),经过百种、验证后,菌种被保存日油管理沙土菌种,入细胞细胞库(现代-O)。</p> - <p>1.3 是否菌种能自己分离后获得的源头菌种,接种入主细胞库:经过产验证后,保藏为少土管理日油管,入细胞细胞库(现代-O)。</p> + <p> + 1. 菌种全部集中登记在【菌种源保藏出/入细胞库登记表】,菌种来源有 3 + 条路径。1.1 是沙土管或甘油管的源头菌种;入原始细胞库(祖代-O)。1.2 + 是斜面的源头菌种;接种入主细胞库(祖代-O)。经过育种、验证后,菌种保藏为甘油管或沙土管的,入原始细胞库(祖代-0)1.3 + 是含菌物质自己分离后获得的斜面源头菌种,接种入主细胞库;经生产验证后,保藏为沙土管或甘油管,入原始细胞库(祖代-O)。2. + 菌种细胞库,分类入三库,进行传代运行管理。三类库存空间进行区分,保藏菌种。2.1 + 原始细胞库(祖代-O)、2.2 主细胞库(母代-M)、2.3 + 生产细胞库(子代-S)、(孙代-G)3. 细胞库编码规则3.1 + 细胞库编码规则:DD-M-240919-01-(O-0109-01)DD:代表项目组。M:“O”代表祖代原始细胞库,”M“代表母代主细胞库,”S“代表子代生产细胞库,“G”代表孙代生产细胞库。240919:代表在 + 24 年 9 月 19 + 接种批次的菌种;或收到外来菌种时间的入库批次。01:代表两位序列号。(O-0109-01):代表传代菌种的编号3.1.1 + 传代编码方式演例:祖代:DD-O-240919-01 + 传母代:DD-M-241017-01-(O-091901)DD-M-241017-02-(O-091901)DD-M-241017-03-(O-091901)子代:DD-S-241019-01-(M-1017-02)版权归奥利元生物所有,禁止外传。DD-S-241019-02-(M-1017-03)孙代:DD-G-241109-01-(S-1019-02)3.1.2 + 编码规则实现了编码唯一,编码可溯源,编码直观、方便。3.2 + 细胞库说明:3.2.1 + 直接购买、自行从(土壤、相关物料、商品)等分离出来菌株进入原始细胞库。3.2.2 + 从原始细胞库中选取出来再次纯化、改造、提高性能的菌株进入主细胞库。3.2.3 + 主细胞库中选取出稳定,生产性能良好的菌株扩培后保种进入生产细胞库。4. + 菌种选育-保藏过程编号说明4.1 菌种选育时,培养皿的编号可使用 + a-01、a-02 等用于清晰形态观察记录;菌落编号使用序号 1/2/3等。4.2 + 接种斜面菌种编码(-O)使用原始细胞库编码;斜面转菌种保藏使用与斜面一致的编码(-O);斜面传代入主细胞库的传代菌种,按编码器规则编码(-M)。 + </p> </div> </el-dialog> </el-card> <!-- Table --> - <TableCustom :queryForm="queryForm" :tableData="tableData" :total="total" @currentChange="handleCurrentChange" - @sizeChange="handleSizeChange"> + <TableCustom + :queryForm="queryForm" + :tableData="tableData" + :total="total" + @currentChange="handleCurrentChange" + @sizeChange="handleSizeChange" + > <template #search> <el-form :model="form" label-width="auto" inline> <el-form-item label="菌种编号:"> @@ -40,15 +84,13 @@ <el-form-item label="菌种名称:"> <el-input v-model="form.strainName" placeholder="请输入"></el-input> </el-form-item> - <el-form-item label="状态:"> + <el-form-item v-if="roleType == 4" label="状态:"> <el-select v-model="form.status" placeholder="请选择"> <el-option label="全部" value=""></el-option> - <el-option - v-for="item in statusOptions" - :key="item.value" - :label="item.label" - :value="item.value"> - </el-option> + <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-form-item class="search-btn-box"> @@ -61,236 +103,207 @@ <template #setting> <div class="tableTitle"> <div class="flex a-center"> - <div class="title" :class="{ active: currentType === 'list' }" - @click="handleTypeChange('list')"> - 生产细胞列表</div> - <div class="drafts" :class="{ active: currentType === 'draft' }" - @click="handleTypeChange('draft')"> - 草稿箱</div> + <div + class="title" + :class="{ active: currentType === 'list' }" + @click="handleTypeChange('list')" + > + 生产细胞列表 + </div> + <div + class="drafts" + :class="{ active: currentType === 'draft' }" + @click="handleTypeChange('draft')" + > + 草稿箱 + </div> </div> - <div class="flex a-center"> - <el-button @click="handleAdd" class="el-icon-plus" type="primary" style="margin-right: 12px;">新增生产细胞</el-button> - <el-button @click="handleAdd" class="el-icon-plus" type="primary" style="margin-right: 12px;">批量新增</el-button> + <div v-if="roleType == 4" class="flex a-center"> + <el-button + @click="handleNewStrain" + class="el-icon-plus" + type="primary" + style="margin-right: 12px" + >新增生产细胞</el-button + > + <el-button + @click="handleNewStrain" + class="el-icon-plus" + type="primary" + >批量新增</el-button + > </div> </div> </template> <template #table> - <el-table-column type="selection" width="55" /> - <el-table-column prop="strainNo" label="菌种编号" width="150" /> - <el-table-column prop="strainName" label="菌种名称" width="180" /> - <el-table-column prop="source" label="菌种来源" width="150" /> - <el-table-column prop="preservationMethod" label="鉴定方法" width="120" /> - <el-table-column prop="storageLocation" label="特征描述" width="150" /> - <el-table-column prop="inventory" label="菌种保存方法" width="100" /> - <el-table-column prop="inventory" label="保存位置" width="100" /> - <el-table-column prop="inventory" label="库存余量" width="100" /> - <el-table-column prop="inventory" label="备注" /> - <el-table-column prop="status" label="状态" width="100"> - <template slot-scope="scope"> - <el-tag :type="getStatusType(scope.row.status)">{{ scope.row.status }}</el-tag> + <el-table-column prop="strainCode" label="菌种编号" /> + <el-table-column prop="strainName" label="菌种名称" /> + <el-table-column prop="strainSource" label="菌种来源" /> + <el-table-column prop="appraisalMethod" label="鉴定方法" /> + <el-table-column prop="features" label="特征描述" /> + <el-table-column prop="saveMethod" label="菌种保存方法" /> + <el-table-column prop="saveLocation" label="保藏位置" /> + <el-table-column prop="stock" label="库存余量" /> + <el-table-column prop="remark" label="备注" /> + <el-table-column + v-if="currentType === 'list'" + prop="status" + label="当前状态" + > + <template #default="{ row }"> + <el-tag :type="getStatusType(row.status)">{{ + getStatusText(row.status) + }}</el-tag> </template> </el-table-column> - <el-table-column label="操作" width="150" fixed="right"> - <template slot-scope="scope"> - <el-button type="text" @click="handleView(scope.row)">详情</el-button> - <el-button type="text" @click="handleEdit(scope.row)">编辑</el-button> - <el-button type="text" @click="handleDelete(scope.row)" class="delete-btn">删除</el-button> + <el-table-column label="操作" width="200"> + <template #default="{ row }"> + <el-button type="text" @click="handleDetail(row)">详情</el-button> + <el-button v-if="row.status == 2 || row.status == 4" type="text" @click="handleEdit(row)">编辑</el-button> + <el-button + v-if="currentType === 'list'" + type="text" + @click="handleRecord(row)" + >出入库记录</el-button + > + <el-button v-if="roleType == 1" type="text" @click="handleDelete(row)">删除</el-button> </template> </el-table-column> </template> </TableCustom> - - <!-- 删除确认对话框 --> - <el-dialog - title="确认删除" - :visible.sync="deleteDialogVisible" - width="30%"> - <div class="delete-dialog-content"> - <i class="el-icon-warning-outline warning-icon"></i> - <span>确定要删除该菌种记录吗?删除后将无法恢复。</span> - </div> - <span slot="footer" class="dialog-footer"> - <el-button @click="deleteDialogVisible = false">取消</el-button> - <el-button type="danger" @click="confirmDelete">确定</el-button> - </span> - </el-dialog> + <StrainDetail :visible.sync="detailVisible" :detail="currentDetail" /> </div> </template> <script> +import StrainDetail from "../strain-library-manage/components/StrainDetail.vue"; +import { getList, deleteStrainLibrary } from "../strain-library-manage/service"; + export default { - name: 'ProductionCellLibrary', + name: "StrainLibraryManage", + components: { + StrainDetail, + }, data() { return { dialogVisible: false, - currentType: 'list', + currentType: "list", + detailVisible: false, + currentDetail: {}, form: { - strainNo: '', - strainName: '', - status: '' + strainNo: "", + strainName: "", + status: "", }, queryForm: { pageSize: 10, - pageNum: 1 + pageNum: 1, }, - total: 100, - loading: false, - sourceOptions: [ - { value: '主细胞库', label: '主细胞库' }, - { value: '工作细胞库', label: '工作细胞库' }, - { value: '外部来源', label: '外部来源' } - ], - statusOptions: [ - { value: '正常', label: '正常' }, - { value: '缺货', label: '缺货' }, - { value: '异常', label: '异常' }, - { value: '已停用', label: '已停用' } - ], + total: 800, tableData: [], - selectedRows: [], - deleteDialogVisible: false, - deleteRow: null - } + roleType: "", + }; }, - created() { - this.fetchData(); + activated() { + this.searchData(); + // 角色类型 1=超级管理员 2=审批人 3=工程师 4=实验员 + this.roleType = JSON.parse(sessionStorage.getItem("userInfo")).roleType; }, methods: { + handleDelete(row) { + this.$confirm("确定删除该数据吗?", "提示", { + confirmButtonText: "确定", + cancelButtonText: "取消", + type: "warning", + }).then(() => { + deleteStrainLibrary({ id: row.id }).then((res) => { + this.$message.success("删除成功"); + this.searchData(); + }); + }); + }, + handleRecord(row) { + this.$router.push({ + path: `/strain-library/strain-library-manage/record?id=${row.id}`, + }); + }, + handleNewStrain() { + this.$router.push({ path: "/strain-library/production-cell-library/add" }); + }, + handleEdit(row) { + this.$router.push({ + path: `/strain-library/production-cell-library/add?id=${row.id}`, + }); + }, + handleDetail(row) { + this.currentDetail = row; + this.detailVisible = true; + }, handleViewMore() { this.dialogVisible = true; }, resetForm() { this.form = { - strainNo: '', - strainName: '', - status: '' - } - this.searchData() + strainNo: "", + strainName: "", + status: "", + }; + this.searchData(); }, searchData() { - this.queryForm.pageNum = 1; - this.fetchData(); - }, - // 获取数据 - fetchData() { - this.loading = true; - - // 构建请求参数 const params = { - page: this.queryForm.pageNum, + pageNum: this.queryForm.pageNum, pageSize: this.queryForm.pageSize, - ...this.form + strainCode: this.form.strainNo, + strainName: this.form.strainName, + isDraft: this.currentType === "draft" ? 1 : 0, + status: this.form.status, + type: 3, }; - - // 模拟API请求 - setTimeout(() => { - // 模拟数据,实际项目中应替换为真实API调用 - const mockData = []; - for (let i = 1; i <= 10; i++) { - mockData.push({ - id: `${i}`, - strainNo: `PCLS-2023-${String(i).padStart(3, '0')}`, - strainName: `枯草芽孢杆菌生产株${i}`, - source: i % 3 === 0 ? '外部来源' : (i % 2 === 0 ? '工作细胞库' : '主细胞库'), - preservationMethod: i % 2 === 0 ? '冻干保存' : '超低温冷冻保存', - storageLocation: `A区-A-${100 + i}-冷藏柜`, - inventory: 10 + i, - status: i % 4 === 0 ? '异常' : (i % 3 === 0 ? '缺货' : (i % 2 === 0 ? '已停用' : '正常')), - preparationDate: `2023-05-${String(i).padStart(2, '0')}`, - expiryDate: `2024-05-${String(i).padStart(2, '0')}` - }); - } - - this.tableData = mockData; - this.total = 100; // 模拟总数 - this.loading = false; - }, 500); + getList(params) + .then((res) => { + if (res.code === 200) { + this.tableData = res.data.records; + this.total = res.data.total; + } + }) + .catch((err) => { + this.$message.error("数据加载失败"); + }); }, - - // 状态标签类型 - getStatusType(status) { - switch(status) { - case '正常': - return 'success'; - case '缺货': - return 'warning'; - case '异常': - return 'danger'; - case '已停用': - return 'info'; - default: - return 'info'; - } - }, - handleCurrentChange(page) { - this.queryForm.pageNum = page - this.fetchData(); + this.queryForm.pageNum = page; + this.searchData(); }, handleSizeChange(size) { - this.queryForm.pageSize = size - this.queryForm.pageNum = 1 - this.fetchData(); + this.queryForm.pageSize = size; + this.searchData(); }, handleTypeChange(type) { this.currentType = type; - this.fetchData(); + this.searchData(); }, - - // 表格多选 - handleSelectionChange(selection) { - this.selectedRows = selection; + getStatusType(status) { + const types = { + 1: "warning", + 2: "warning", + 3: "success", + 4: "success", + }; + return types[status] || "info"; }, - - // 新增菌种 - handleAdd() { - this.$router.push('/strain-library/production-cell-library/add'); + getStatusText(status) { + const texts = { + 1: "已出库", + 2: "出库待确认", + 3: "已入库", + 4: "入库待确认", + }; + return texts[status] || "未知状态"; }, - - // 查看菌种详情 - handleView(row) { - this.$router.push(`/strain-library/production-cell-library/record/${row.id}`); - }, - - // 编辑菌种 - handleEdit(row) { - this.$router.push(`/strain-library/production-cell-library/edit/${row.id}`); - }, - - // 删除菌种 - handleDelete(row) { - this.deleteRow = row; - this.deleteDialogVisible = true; - }, - - // 确认删除 - confirmDelete() { - if (!this.deleteRow) return; - - // 模拟API请求 - this.$message({ - type: 'success', - message: `删除成功: ${this.deleteRow.strainNo} - ${this.deleteRow.strainName}` - }); - - // 移除本地数据 - const index = this.tableData.findIndex(item => item.id === this.deleteRow.id); - if (index !== -1) { - this.tableData.splice(index, 1); - } - - this.deleteDialogVisible = false; - this.deleteRow = null; - }, - - // 导出 - handleExport() { - this.$message.info('生产细胞库菌种导出功能开发中'); - // 实际项目中应实现导出逻辑 - } - } -} + }, +}; </script> <style scoped lang="less"> @@ -320,7 +333,7 @@ .view-more { position: absolute; right: 0; - color: #049C9A; + color: #049c9a; } } @@ -343,8 +356,50 @@ } } -.search-btn-box { - margin-left: auto; +.search-form { + margin-bottom: 20px; + background: #f5f7fa; + padding: 24px; + border-radius: 8px; + + .el-form-item { + margin-right: 20px; + margin-bottom: 0; + } + + .el-button { + margin-left: 10px; + } +} + +.action-buttons { + margin-bottom: 20px; + + .el-button { + margin-right: 10px; + } +} + +.tab-container { + display: flex; + margin-bottom: 20px; + + .tab { + padding: 10px 30px; + border: 1px solid #dcdfe6; + border-bottom: none; + border-radius: 8px 8px 0 0; + cursor: pointer; + margin-right: 10px; + background: #f5f7fa; + + &.active { + background: #fff; + border-color: #049c9a; + color: #049c9a; + font-weight: bold; + } + } } .flex { @@ -371,7 +426,6 @@ line-height: 50px; width: 166px; text-align: center; - } .drafts { @@ -394,28 +448,15 @@ background: #ffffff; border-radius: 8px 8px 0px 0px; border: 1px solid #049c9a; - - } -} - -.delete-dialog-content { - display: flex; - align-items: center; - padding: 20px 0; - - .warning-icon { - font-size: 24px; - color: #E6A23C; - margin-right: 10px; } } .view-all-dialog { :deep(.el-dialog__header) { padding: 20px; - border-bottom: 1px solid #EBEEF5; + border-bottom: 1px solid #ebeef5; margin-right: 0; - + .el-dialog__title { font-size: 18px; font-weight: bold; @@ -433,11 +474,11 @@ p { margin: 12px 0; - + &:first-child { margin-top: 0; } - + &:last-child { margin-bottom: 0; } @@ -445,8 +486,4 @@ } } } - -.delete-btn { - color: #F56C6C; -} -</style> \ No newline at end of file +</style> diff --git a/culture/src/views/strain-library/production-cell-library/record.vue b/culture/src/views/strain-library/production-cell-library/record.vue index b23bd7a..aef2643 100644 --- a/culture/src/views/strain-library/production-cell-library/record.vue +++ b/culture/src/views/strain-library/production-cell-library/record.vue @@ -1,538 +1,438 @@ <template> - <div class="production-cell-record"> - <!-- 页面头部 --> - <div class="page-header"> - <div class="header-left"> - <el-button icon="el-icon-arrow-left" @click="$router.go(-1)">返回</el-button> - <h2>生产细胞库菌种详情</h2> - </div> - <div class="header-actions"> - <el-button type="primary" plain icon="el-icon-edit" @click="handleEdit">编辑</el-button> - <el-button type="primary" plain icon="el-icon-printer" @click="handlePrint">打印</el-button> - </div> - </div> + <div class="record-page"> + <!-- 基本信息展示区域 --> + <el-card class="header-box"> + <div class="header-content"> + <!-- 第一行 --> + <div class="info-row"> + <div class="info-item left-column"> + <span class="label">菌种编号:</span> + <span class="value">{{ detail.strainCode }}</span> + </div> + <div class="info-item flex-column"> + <span class="label">鉴定方法:</span> + <span class="value">{{ detail.appraisalMethod }}</span> + </div> + <div class="info-item flex-column"> + <span class="label">保藏位置:</span> + <span class="value">{{ detail.saveLocation }}</span> + </div> + </div> - <!-- 菌种信息卡片 --> - <el-card class="strain-card" v-loading="loading"> - <div slot="header" class="card-header"> - <span class="card-title">菌种信息</span> - <div class="status-tag"> - <el-tag :type="getStatusType(strainData.status)">{{ strainData.status }}</el-tag> + <!-- 第二行 --> + <div class="info-row"> + <div class="info-item left-column"> + <span class="label">菌种名称:</span> + <span class="value">{{ detail.strainName }}</span> + </div> + <div class="info-item flex-column full-width"> + <span class="label">特性描述:</span> + <span class="value">{{ detail.features }}</span> + </div> </div> - </div> - - <div class="strain-info"> - <div class="info-item"> - <span class="label">菌种编号:</span> - <span class="value">{{ strainData.strainNo }}</span> - </div> - <div class="info-item"> - <span class="label">菌种名称:</span> - <span class="value">{{ strainData.strainName }}</span> - </div> - <div class="info-item"> - <span class="label">菌种来源:</span> - <span class="value">{{ strainData.source }}</span> - </div> - <div class="info-item"> - <span class="label">生产批次:</span> - <span class="value">{{ strainData.batchNo }}</span> - </div> - <div class="info-item"> - <span class="label">保存方法:</span> - <span class="value">{{ strainData.preservationMethod }}</span> - </div> - <div class="info-item"> - <span class="label">保存位置:</span> - <span class="value">{{ strainData.storageLocation }}</span> - </div> - <div class="info-item"> - <span class="label">当前库存:</span> - <span class="value">{{ strainData.inventory }} 份</span> - </div> - <div class="info-item"> - <span class="label">状态:</span> - <span class="value">{{ strainData.status }}</span> - </div> - <div class="info-item"> - <span class="label">更新时间:</span> - <span class="value">{{ strainData.updateTime }}</span> - </div> - <div class="info-item"> - <span class="label">制备日期:</span> - <span class="value">{{ strainData.preparationDate }}</span> - </div> - <div class="info-item"> - <span class="label">有效期至:</span> - <span class="value">{{ strainData.expiryDate }}</span> - </div> - <div class="info-item full-width"> - <span class="label">特征描述:</span> - <div class="value description">{{ strainData.description }}</div> - </div> - - <div class="info-item full-width" v-if="strainData.certificateUrl"> - <span class="label">质量证书:</span> - <div class="value"> - <el-button type="text" @click="handleViewCertificate">查看证书</el-button> - <el-button type="text" @click="handleDownloadCertificate">下载证书</el-button> + + <!-- 第三行 --> + <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-card class="record-card" v-loading="loadingRecords"> - <div slot="header" class="card-header"> - <span class="card-title">使用记录</span> - <el-button type="text" @click="handleAddUsage">新增使用记录</el-button> - </div> - - <el-table - :data="usageRecords" - style="width: 100%" - :empty-text="usageRecords.length ? '' : '暂无使用记录'" - > - <el-table-column prop="date" label="使用日期" width="120"></el-table-column> - <el-table-column prop="amount" label="使用数量" width="100"></el-table-column> - <el-table-column prop="operator" label="操作人" width="120"></el-table-column> - <el-table-column prop="project" label="项目名称"></el-table-column> - <el-table-column prop="batchNo" label="生产批次" width="150"></el-table-column> - <el-table-column prop="purpose" label="使用目的"></el-table-column> - <el-table-column label="操作" width="120"> - <template slot-scope="scope"> - <el-button type="text" size="small" @click="handleViewUsageDetail(scope.row)">查看</el-button> - <el-button type="text" size="small" @click="handleEditUsage(scope.row)">编辑</el-button> + <!-- 出入库记录表格 --> + <TableCustom + :queryForm="queryForm" + :tableData="recordList" + :total="total" + @currentChange="handlePageChange" + > + <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> - - <div class="pagination-container" v-if="usageRecords.length"> - <el-pagination - background - layout="prev, pager, next" - :total="usageTotalCount" - :current-page.sync="usageCurrentPage" - :page-size="usagePageSize" - @current-change="handleUsagePageChange"> - </el-pagination> - </div> - </el-card> - - <!-- 测试记录 --> - <el-card class="record-card" v-loading="loadingTests"> - <div slot="header" class="card-header"> - <span class="card-title">测试记录</span> - <el-button type="text" @click="handleAddTest">新增测试记录</el-button> - </div> - - <el-table - :data="testRecords" - style="width: 100%" - :empty-text="testRecords.length ? '' : '暂无测试记录'" - > - <el-table-column prop="date" label="测试日期" width="120"></el-table-column> - <el-table-column prop="type" label="测试类型" width="150"></el-table-column> - <el-table-column prop="operator" label="操作人" width="120"></el-table-column> - <el-table-column prop="result" label="测试结果"> - <template slot-scope="scope"> - <el-tag :type="scope.row.result === '合格' ? 'success' : scope.row.result === '不合格' ? 'danger' : 'info'"> - {{ scope.row.result }} + <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 prop="remark" label="备注"></el-table-column> - <el-table-column label="操作" width="120"> - <template slot-scope="scope"> - <el-button type="text" size="small" @click="handleViewTestDetail(scope.row)">查看</el-button> - <el-button type="text" size="small" @click="handleEditTest(scope.row)">编辑</el-button> + <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> - </el-table> - - <div class="pagination-container" v-if="testRecords.length"> - <el-pagination - background - layout="prev, pager, next" - :total="testTotalCount" - :current-page.sync="testCurrentPage" - :page-size="testPageSize" - @current-change="handleTestPageChange"> - </el-pagination> - </div> - </el-card> + </template> + <template #tableCustom v-if="currentType === 'timeline'"> + <record-timeline :list="timelineList" /> + </template> + </TableCustom> - <!-- 证书预览对话框 --> - <el-dialog - title="证书预览" - :visible.sync="certificateDialogVisible" - width="70%"> - <div v-if="strainData.certificateUrl" class="certificate-preview"> - <iframe v-if="strainData.certificateUrl.endsWith('.pdf')" :src="strainData.certificateUrl" width="100%" height="500"></iframe> - <img v-else :src="strainData.certificateUrl" style="max-width: 100%; max-height: 500px;" /> - </div> - </el-dialog> + <!-- 详情弹窗 --> + <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 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: 'ProductionCellLibraryRecord', + name: "StrainRecord", + components: { + RecordDetailDialog, + AddRecordDialog, + RecordTimeline, + }, data() { return { - loading: false, - loadingRecords: false, - loadingTests: false, - strainId: '', - strainData: { - id: '', - strainNo: '', - strainName: '', - source: '', - batchNo: '', - preservationMethod: '', - storageLocation: '', - inventory: 0, - status: '', - description: '', - preparationDate: '', - expiryDate: '', - updateTime: '', - certificateUrl: '' + currentType: "table", + detail: {}, + currentPage: 1, + pageSize: 10, + total: 0, + queryForm: { + pageSize: 10, + pageNum: 1, }, - certificateDialogVisible: false, - - // 使用记录分页数据 - usageRecords: [], - usageCurrentPage: 1, - usagePageSize: 10, - usageTotalCount: 0, - - // 测试记录分页数据 - testRecords: [], - testCurrentPage: 1, - testPageSize: 10, - testTotalCount: 0 - } + recordList: [], + timelineList: [], + dialogVisible: false, + currentRecord: {}, + addDialogVisible: false, + dialogType: "detail", + roleType: "", + }; }, - created() { - this.strainId = this.$route.params.id; - this.fetchStrainData(); - this.fetchUsageRecords(); - this.fetchTestRecords(); + 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: { - // 获取菌种详情 - fetchStrainData() { - this.loading = true; - - // 模拟API请求 - setTimeout(() => { - // 模拟数据,实际项目中应替换为真实API调用 - this.strainData = { - id: this.strainId, - strainNo: 'PCLS-2023-001', - strainName: '枯草芽孢杆菌生产株', - source: '主细胞库', - batchNo: 'P20230515-001', - preservationMethod: '冻干保存', - storageLocation: 'A区-A-102-冷藏柜', - inventory: 12, - status: '正常', - description: '本菌种为工业生产级别枯草芽孢杆菌生产株,由主细胞库转入,经过严格筛选和稳定性测试。该菌株具有高产蛋白酶能力,发酵条件适应性强,适合大规模工业化生产。产品稳定性好,批次间差异小,可用于洗涤用酶制剂、食品加工酶制剂等多种产品的生产。', - preparationDate: '2023-05-10', - expiryDate: '2024-05-10', - updateTime: '2023-05-15 14:30:22', - certificateUrl: '/api/strain-library/certificates/sample.pdf' - }; - - this.loading = false; - }, 500); + handleDelete(row) { + this.$confirm("确定删除该数据吗?", "提示", { + confirmButtonText: "确定", + cancelButtonText: "取消", + type: "warning", + }).then(() => { + deleteWarehousing({ id: row.id }).then((res) => { + this.$message.success("删除成功"); + this.getRecordList(); + }); + }); }, - - // 获取使用记录 - fetchUsageRecords() { - this.loadingRecords = true; - - // 模拟API请求 - setTimeout(() => { - // 模拟数据,实际项目中应替换为真实API调用 - this.usageRecords = [ - { - id: '1', - date: '2023-06-05', - amount: 2, - operator: '张工', - project: '酶制剂研发项目', - batchNo: 'E20230605-001', - purpose: '小试生产' - }, - { - id: '2', - date: '2023-07-12', - amount: 3, - operator: '李工', - project: '蛋白酶产品项目', - batchNo: 'E20230712-002', - purpose: '中试生产' - }, - { - id: '3', - date: '2023-08-18', - amount: 5, - operator: '王工', - project: '工业酶制剂生产', - batchNo: 'E20230818-003', - purpose: '规模化生产' - } - ]; - - this.usageTotalCount = 3; - this.loadingRecords = false; - }, 600); + getStrainDetail(id) { + // 这里应该调用接口获取菌种详情 + getDetail({ id }).then((res) => { + this.detail = res; + }); }, - - // 获取测试记录 - fetchTestRecords() { - this.loadingTests = true; - - // 模拟API请求 - setTimeout(() => { - // 模拟数据,实际项目中应替换为真实API调用 - this.testRecords = [ - { - id: '1', - date: '2023-05-15', - type: '活力测定', - operator: '刘工', - result: '合格', - remark: '酶活性达标,符合生产要求' - }, - { - id: '2', - date: '2023-05-15', - type: '纯度检测', - operator: '张工', - result: '合格', - remark: '纯度>98%,无杂菌污染' - }, - { - id: '3', - date: '2023-05-16', - type: '稳定性测试', - operator: '李工', - result: '合格', - remark: '常温保存7天活力下降小于5%' - } - ]; - - this.testTotalCount = 3; - this.loadingTests = false; - }, 700); + 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; + }); }, - - // 状态标签类型 - getStatusType(status) { - switch(status) { - case '正常': - return 'success'; - case '缺货': - return 'warning'; - case '异常': - return 'danger'; - case '已停用': - return 'info'; - default: - return 'info'; - } + handleView(row) { + this.dialogType = "detail"; + this.currentRecord = row; + this.dialogVisible = true; }, - - // 编辑菌种 - handleEdit() { - this.$router.push(`/strain-library/production-cell-library/edit/${this.strainId}`); + handleConfirm(row) { + this.dialogType = "confirm"; + this.currentRecord = row; + this.dialogVisible = true; }, - - // 打印菌种信息 - handlePrint() { - window.print(); + handlePageChange(page) { + this.queryForm.pageNum = page; + // 这里应该调用接口获取对应页码的数据 }, - - // 查看证书 - handleViewCertificate() { - this.certificateDialogVisible = true; + handleTypeChange(type) { + this.currentType = type; }, - - // 下载证书 - handleDownloadCertificate() { - // 实际项目中应处理文件下载逻辑 - window.open(this.strainData.certificateUrl, '_blank'); + handleAddRecord() { + this.addDialogVisible = true; }, - - // 新增使用记录 - handleAddUsage() { - this.$message.info('功能开发中:新增使用记录'); - // 实际项目中应跳转到新增使用记录页面或打开对话框 + handleDialogClose() { + this.currentRecord = {}; + this.dialogVisible = false; }, - - // 查看使用记录详情 - handleViewUsageDetail(row) { - this.$message.info(`查看使用记录: ${row.id}`); - // 实际项目中应跳转到使用记录详情页面或打开对话框 + 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(res.msg); + } + }); }, - - // 编辑使用记录 - handleEditUsage(row) { - this.$message.info(`编辑使用记录: ${row.id}`); - // 实际项目中应跳转到编辑使用记录页面或打开对话框 + handleAddRecordConfirm(record) { + addWarehousing({ ...record, trainLibraryId: this.$route.query.id }).then( + (res) => { + this.$message.success("操作成功"); + this.getRecordList(); + } + ); }, - - // 使用记录分页切换 - handleUsagePageChange(page) { - this.usageCurrentPage = page; - this.fetchUsageRecords(); + goBack() { + this.$router.go(-1); }, - - // 新增测试记录 - handleAddTest() { - this.$message.info('功能开发中:新增测试记录'); - // 实际项目中应跳转到新增测试记录页面或打开对话框 - }, - - // 查看测试记录详情 - handleViewTestDetail(row) { - this.$message.info(`查看测试记录: ${row.id}`); - // 实际项目中应跳转到测试记录详情页面或打开对话框 - }, - - // 编辑测试记录 - handleEditTest(row) { - this.$message.info(`编辑测试记录: ${row.id}`); - // 实际项目中应跳转到编辑测试记录页面或打开对话框 - }, - - // 测试记录分页切换 - handleTestPageChange(page) { - this.testCurrentPage = page; - this.fetchTestRecords(); - } - } -} + }, +}; </script> -<style scoped lang="less"> -.production-cell-record { - padding: 20px; +<style lang="less" scoped> +.record-page { + min-height: 100vh; - .page-header { - display: flex; - justify-content: space-between; - align-items: center; - margin-bottom: 24px; - - .header-left { - display: flex; - align-items: center; - - h2 { - margin: 0 0 0 12px; - font-size: 22px; - font-weight: 500; - } - } - - .header-actions { - display: flex; - gap: 12px; - } - } - - .strain-card { - margin-bottom: 24px; - - .card-header { - display: flex; - justify-content: space-between; - align-items: center; - - .card-title { - font-size: 16px; - font-weight: 500; - } - } - - .strain-info { - display: flex; - flex-wrap: wrap; - - .info-item { - width: 33.33%; - margin-bottom: 16px; - - &.full-width { - width: 100%; + .header-box { + margin-bottom: 20px; + border-radius: 16px; + background: rgba(255, 255, 255, 0.8); + height: 130px; + overflow: hidden; + + .header-content { + color: rgba(0, 0, 0, 0.88); + font-size: 14px; + line-height: 1.5; + + .info-row { + display: flex; + flex-wrap: wrap; + margin-bottom: 8px; + + &:last-child { + margin-bottom: 0; } - - .label { - font-weight: 500; - color: #606266; - } - - .value { - color: #303133; - - &.description { - white-space: pre-line; - line-height: 1.6; - padding: 8px 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; } } } } } - - .record-card { - margin-bottom: 24px; - - .card-header { - display: flex; - justify-content: space-between; - align-items: center; - - .card-title { - font-size: 16px; - font-weight: 500; - } - } - - .pagination-container { - display: flex; - justify-content: flex-end; - margin-top: 20px; - } - } - - .certificate-preview { + + .flex { display: flex; - justify-content: center; align-items: center; - min-height: 500px; - background-color: #f5f7fa; } - - @media print { - .page-header, .header-actions, .record-card { - display: none; + + .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; } - - .strain-card { - border: none; - box-shadow: none; - - .card-header { - background-color: #fff !important; - border-bottom: 1px solid #ddd; + + .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> \ No newline at end of file +</style> diff --git a/culture/src/views/strain-library/production-cell-library/service.js b/culture/src/views/strain-library/production-cell-library/service.js new file mode 100644 index 0000000..08f789d --- /dev/null +++ b/culture/src/views/strain-library/production-cell-library/service.js @@ -0,0 +1,56 @@ +import axios from '@/utils/request'; + +// 列表 +export const getList = (data) => { + return axios.post('/api/t-train-library/pageList', { ...data }) +} + +// 新增 +export const add = (data) => { + return axios.post('/api/t-train-library/add', { ...data }) +} + +// 编辑 +export const edit = (data) => { + return axios.post('/api/t-train-library/update', { ...data }) +} + +// 查看详情 +export const getDetail = (params) => { + return axios.get('/open/t-train-library/getDetailEditById', { params }) +} + +// 批量新增 +export const addBatch = (data) => { + return axios.post('/api/t-train-library/addBatch', data) +} + +// 查看菌种库详情 +export const getDetailById = (data) => { + return axios.post('/open/t-train-library/getDetailById', { ...data }) +} + +// 获取菌种库出入库时间轴列表 +export const timeList = (data) => { + return axios.post('/api/t-train-library/timeList?id='+data.id, { ...data }) +} + +// 新增菌种库出入记录 +export const addWarehousing = (data) => { + return axios.post('/open/t-train-library/addWarehousing', { ...data }) +} + +// 确认出入库 +export const confirmWarehousing = (data) => { + return axios.post('/api/t-train-library/confirm', { ...data }) +} + +// 删除菌种库 +export const deleteStrainLibrary = (params) => { + return axios.delete('/open/t-train-library/deleteById', { params }) +} + +// 删除菌种库出入库记录 +export const deleteWarehousing = (params) => { + return axios.delete('/open/t-train-library/deleteWarehousingById', { params }) +} diff --git a/culture/src/views/strain-library/strain-library-manage/add.vue b/culture/src/views/strain-library/strain-library-manage/add.vue index 5c91b36..0c4dbd1 100644 --- a/culture/src/views/strain-library/strain-library-manage/add.vue +++ b/culture/src/views/strain-library/strain-library-manage/add.vue @@ -106,7 +106,7 @@ rules: { strainCode: [{ validator: (rule, value, callback) => { - if (this.currentAction === 'submit' && !value.trim()) { + if (this.currentAction === 'submit' && !value) { callback(new Error('请输入菌种编号')); } else { callback(); @@ -149,8 +149,13 @@ this.currentAction = 'submit' this.$refs.strainForm.validate((valid) => { if (valid) { - this.signatureVisible = true this.form.isDraft = isDraft + if (isDraft == 1) { + //存草稿 + this.handleSignatureConfirm('') + } else { + this.signatureVisible = true + } } }) }, @@ -193,8 +198,6 @@ requestData.id = this.$route.query.id; await edit(requestData); } else if (this.currentAction === 'batchAdd') { - console.log(requestData); - await addBatch(requestData); } else { await add(requestData); diff --git a/culture/src/views/strain-library/strain-library-manage/components/RecordDetailDialog.vue b/culture/src/views/strain-library/strain-library-manage/components/RecordDetailDialog.vue index 0f8aa15..ffe34f8 100644 --- a/culture/src/views/strain-library/strain-library-manage/components/RecordDetailDialog.vue +++ b/culture/src/views/strain-library/strain-library-manage/components/RecordDetailDialog.vue @@ -1,22 +1,20 @@ <template> - <el-dialog title="出/入库详情" :visible.sync="visible" width="520px" :close-on-click-modal="false" - custom-class="record-detail-dialog" @close="handleClose" @opened="opened"> + <el-dialog :title="type == 'detail' ? '出/入库详情' : '确认出入库'" :visible.sync="visible" width="550px" + :close-on-click-modal="false" custom-class="record-detail-dialog" @close="handleClose" @opened="opened"> <div class="dialog-content"> <el-form :model="formData" label-position="top"> <el-form-item label="出库/入库" required> <div class="type-buttons"> - <el-button :type="formData.type == '1' ? 'primary' : 'default'" - @click="formData.type = '1'">出库</el-button> - <el-button :type="formData.type == '2' ? 'primary' : 'default'" - @click="formData.type = '2'">入库</el-button> + <el-button v-if="formData.type == '1'" type="primary">出库</el-button> + <el-button v-if="formData.type == '2'" type="primary">入库</el-button> </div> </el-form-item> <div class="signature-row"> <el-form-item label="操作人签字" required class="signature-item"> - <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> @@ -24,8 +22,8 @@ </div> </el-form-item> - <el-form-item v-if="formData.operatorSignature" label="出库时间" required class="time-item"> - <div class="time-value">{{ formData.operateTime }}</div> + <el-form-item v-if="formData.handleSignature" label="出库时间" required class="time-item"> + <div class="time-value">{{ formData.boundTime }}</div> </el-form-item> </div> @@ -33,12 +31,12 @@ <el-form-item required class="signature-item"> <template #label> <span>保藏人签字</span> - <el-button type="primary" class="edit-sign-btn" + <el-button v-if="type != 'detail'" type="primary" class="edit-sign-btn" @click="showSignature = true">修改签名</el-button> </template> - <div class="signature-area" :class="{ 'waiting': !formData.reviewerSignature }"> - <template v-if="formData.reviewerSignature"> - <img :src="formData.reviewerSignature" alt="保藏人签字" /> + <div class="signature-area" :class="{ 'waiting': !formData.preserveSignature }"> + <template v-if="formData.preserveSignature"> + <img :src="formData.preserveSignature" alt="保藏人签字" /> </template> <template v-else> <span class="waiting-text">等待确认</span> @@ -46,11 +44,15 @@ </div> </el-form-item> - <el-form-item v-if="formData.reviewerSignature" label="确认时间" required class="time-item"> + <el-form-item v-if="formData.preserveSignature && type == 'detail'" label="确认时间" required + class="time-item"> <div class="time-value">{{ formData.confirmTime }}</div> </el-form-item> </div> </el-form> + <div class="confirm-btn" v-if="type != 'detail'" style="text-align: center;margin-top: 20px;"> + <el-button type="primary" style="width: 80px;" @click="handleOutbound">确认</el-button> + </div> </div> <signature-canvas :visible.sync="showSignature" @confirm="handleSignatureConfirm" @cancel="showSignature = false" /> @@ -70,6 +72,10 @@ recordData: { type: Object, default: () => ({}) + }, + type: { + type: String, + default: 'detail' } }, data() { @@ -96,7 +102,7 @@ this.$emit('close') }, handleOutbound() { - if (!this.formData.operatorSignature || !this.formData.reviewerSignature) { + if (!this.formData.preserveSignature) { this.$message.warning('请等待所有签字确认后再进行出库操作') return } @@ -104,7 +110,7 @@ this.handleClose() }, handleSignatureConfirm(dataUrl) { - this.formData.reviewerSignature = dataUrl + this.formData.preserveSignature = dataUrl this.showSignature = false // 可选:this.formData.confirmTime = new Date().toLocaleString() } diff --git a/culture/src/views/strain-library/strain-library-manage/components/StrainDetail.vue b/culture/src/views/strain-library/strain-library-manage/components/StrainDetail.vue index eaa73e3..342fec3 100644 --- a/culture/src/views/strain-library/strain-library-manage/components/StrainDetail.vue +++ b/culture/src/views/strain-library/strain-library-manage/components/StrainDetail.vue @@ -1,231 +1,286 @@ <template> - <div> - <el-dialog title="原始细胞库详情" :visible.sync="visible" width="70%" :close-on-click-modal="false" - custom-class="strain-detail-dialog" @close="$emit('update:visible', false)" @opened="fetchDetail"> - <div class="strain-info"> - <!-- 第一行信息 --> - <div class="info-row"> - <div class="info-item left-column"> - <span class="label">菌种编号:</span> - <span class="value">{{ detail.strainCode }}</span> - </div> - <div class="info-item flex-column"> - <span class="label">鉴定方法:</span> - <span class="value">{{ detail.appraisalMethod }}</span> - </div> - <div class="info-item flex-column"> - <span class="label">保藏位置:</span> - <span class="value">{{ detail.saveLocation }}</span> - </div> - </div> + <div> + <el-dialog + title="原始细胞库详情" + :visible.sync="visible" + width="70%" + :close-on-click-modal="false" + custom-class="strain-detail-dialog" + @close="$emit('update:visible', false)" + @opened="fetchDetail" + > + <div class="strain-info"> + <!-- 第一行信息 --> + <div class="info-row"> + <div class="info-item left-column"> + <span class="label">菌种编号:</span> + <span class="value">{{ detail.strainCode }}</span> + </div> + <div class="info-item flex-column"> + <span class="label">鉴定方法:</span> + <span class="value">{{ detail.appraisalMethod }}</span> + </div> + <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 left-column"> - <span class="label">菌种名称:</span> - <span class="value">{{ detail.strainName }}</span> - </div> - <div class="info-item flex-column full-width"> - <span class="label">特性描述:</span> - <span class="value">{{ detail.features }}</span> - </div> - </div> + <!-- 第二行信息 --> + <div class="info-row"> + <div class="info-item left-column"> + <span class="label">菌种名称:</span> + <span class="value">{{ detail.strainName }}</span> + </div> + <div class="info-item flex-column full-width"> + <span class="label">特性描述:</span> + <span class="value">{{ detail.features }}</span> + </div> + </div> - <!-- 第三行信息 --> - <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 class="info-item flex-column"> - <span class="label">出入库状态:</span> - <span class="value">{{ { - 1: '已出库', - 2: '出库待确认', - 3: '已入库', - 4: '入库待确认' - }[detail.status] || '' }}</span> - </div> - </div> - </div> + <!-- 第三行信息 --> + <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 class="info-item flex-column"> + <span class="label">出入库状态:</span> + <span class="value">{{ + { + 1: "已出库", + 2: "出库待确认", + 3: "已入库", + 4: "入库待确认", + }[detail.status] || "" + }}</span> + </div> + </div> + </div> - <div class="record-table"> - <div class="table-title">原始细胞库出/入库记录</div> - <el-table :data="detail.records" style="width: 100%"> - <el-table-column label="出库/入库"> - <template #default="{ row }"> - {{ { 1: '出库', 2: '入库' }[row.type] || '' }} - </template> - </el-table-column> - <el-table-column prop="boundTime" label="操作时间" /> - <el-table-column prop="handleName" label="操作人姓名" /> - <el-table-column prop="preserveName" label="签核确认人姓名" /> - <el-table-column label="状态"> - <template #default="{ row }"> - <el-tag :type="row.confirmTime ? 'success' : 'warning'"> - {{ row.confirmTime ? '已确认' : '待确认' }} - </el-tag> - </template> - </el-table-column> - <el-table-column label="操作" width="100"> - <template #default="{ row }"> - <el-button type="text" @click="handleView(row)">确认</el-button> - <el-button style="margin-left: 10px;" type="text" @click="handleView(row)">详情</el-button> - </template> - </el-table-column> - </el-table> - <div class="pagination"> - <el-pagination :current-page.sync="currentPage" :page-size="10" layout="total, prev, pager, next" - :total="total" @current-change="handlePageChange" /> - </div> - </div> - </el-dialog> - <RecordDetailDialog :visible="visibleRecordDetailDialog" :recordData="recordData" @close="visibleRecordDetailDialog = false" /> - </div> - + <div class="record-table"> + <div class="table-title">原始细胞库出/入库记录</div> + <el-table :data="detail.records" style="width: 100%"> + <el-table-column label="出库/入库"> + <template #default="{ row }"> + {{ { 1: "出库", 2: "入库" }[row.type] || "" }} + </template> + </el-table-column> + <el-table-column prop="boundTime" label="操作时间" /> + <el-table-column prop="handleName" label="操作人姓名" /> + <el-table-column prop="preserveName" label="签核确认人姓名" /> + <el-table-column label="状态"> + <template #default="{ row }"> + <el-tag :type="row.confirmTime ? 'success' : 'warning'"> + {{ row.confirmTime ? "已确认" : "待确认" }} + </el-tag> + </template> + </el-table-column> + <el-table-column label="操作" width="100"> + <template #default="{ row }"> + <el-button v-if="!row.confirmTime && roleType == 3" style="margin-right: 10px" type="text" @click="handleConfirm(row)">确认</el-button> + <el-button + type="text" + @click="handleView(row)" + >详情</el-button + > + </template> + </el-table-column> + </el-table> + <div class="pagination"> + <el-pagination + :current-page.sync="currentPage" + :page-size="10" + layout="total, prev, pager, next" + :total="total" + @current-change="handlePageChange" + /> + </div> + </div> + </el-dialog> + <RecordDetailDialog + :visible="visibleRecordDetailDialog" + :recordData="recordData" + @close="handleDialogClose" + @confirm="handleOutbound" + :type="dialogType" + /> + + </div> </template> <script> -import { getDetailById } from '../service' -import RecordDetailDialog from './RecordDetailDialog.vue' +import { getDetailById,confirmWarehousing } from "../service"; +import RecordDetailDialog from "./RecordDetailDialog.vue"; export default { - components: { RecordDetailDialog }, - name: 'StrainDetail', - props: { - visible: { - type: Boolean, - default: false - }, - detail: { - type: Object, - default: () => ({}) - } + components: { RecordDetailDialog }, + name: "StrainDetail", + props: { + visible: { + type: Boolean, + default: false, }, - data() { - return { - visibleRecordDetailDialog: false, - recordData: {}, - currentPage: 1, - total: 0, - query: { - endTime: '', - id: '', - pageNum: 1, - pageSize: 10, - startTime: '' - } - } + detail: { + type: Object, + default: () => ({}), }, - methods: { - fetchDetail() { - this.query.id = this.detail.id - getDetailById(this.query).then(res => { - this.detail.records = res.warehousingList?.records || []; - this.total = res.warehousingList?.total || 0; - this.currentPage = res.warehousingList?.current || 1; - }) - }, - handleView(row) { - console.log('View record:', row) - this.visibleRecordDetailDialog = true; - this.recordData = row; - }, - handlePageChange(page) { - this.currentPage = page - this.$emit('page-change', page) + }, + data() { + return { + visibleRecordDetailDialog: false, + recordData: {}, + currentPage: 1, + total: 0, + dialogType: "", + query: { + endTime: "", + id: "", + pageNum: 1, + pageSize: 10, + startTime: "", + roleType: "", + }, + }; + }, + + methods: { + handleDialogClose() { + this.recordData = {}; + this.visibleRecordDetailDialog = false; + }, + fetchDetail() { + this.roleType = JSON.parse(sessionStorage.getItem("userInfo")).roleType; + this.query.id = this.detail.id; + getDetailById(this.query).then((res) => { + this.detail.records = res.warehousingList?.records || []; + this.total = res.warehousingList?.total || 0; + this.currentPage = res.warehousingList?.current || 1; + this.$forceUpdate(); + }); + }, + handleView(row) { + this.dialogType = "detail"; + this.recordData = row; + this.visibleRecordDetailDialog = true; + + }, + handleOutbound(data) { + // 这里调用出库API + confirmWarehousing({ + id: this.recordData.id, + preserveSignature: data.preserveSignature, + }).then((res) => { + if (res.code == 200) { + this.$message.success("操作成功"); + this.visibleRecordDetailDialog = false; + // 刷新列表 + this.fetchDetail(); + } else { + this.$message.error(res.msg); } - } -} + }); + }, + handleConfirm(row) { + this.dialogType = "confirm"; + this.recordData = row; + this.visibleRecordDetailDialog = true; + }, + handlePageChange(page) { + this.currentPage = page; + this.$emit("page-change", page); + }, + }, +}; </script> <style lang="less" scoped> .strain-detail-dialog { - :deep(.el-dialog__header) { - padding: 20px; - border-bottom: 1px solid #EBEEF5; - margin-right: 0; + :deep(.el-dialog__header) { + padding: 20px; + border-bottom: 1px solid #ebeef5; + margin-right: 0; - .el-dialog__title { - font-size: 18px; - font-weight: bold; - color: #303133; - } + .el-dialog__title { + font-size: 18px; + font-weight: bold; + color: #303133; } + } - :deep(.el-dialog__body) { - padding: 20px; - } + :deep(.el-dialog__body) { + padding: 20px; + } } .strain-info { - background: #F5F7FA; - border-radius: 4px; - padding: 20px; - margin-bottom: 20px; + background: #f5f7fa; + border-radius: 4px; + padding: 20px; + margin-bottom: 20px; - .info-row { - display: flex; - flex-wrap: wrap; - margin-bottom: 16px; + .info-row { + display: flex; + flex-wrap: wrap; + margin-bottom: 16px; - &:last-child { - margin-bottom: 0; - } - - .info-item { - display: flex; - align-items: flex-start; - margin-right: 24px; - margin-bottom: 8px; - - &.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; - } - } + &:last-child { + margin-bottom: 0; } + + .info-item { + display: flex; + align-items: flex-start; + margin-right: 24px; + margin-bottom: 8px; + + &.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; + } + } + } } .record-table { - .table-title { - font-size: 16px; - font-weight: bold; - color: #303133; - margin-bottom: 16px; - padding-left: 8px; - border-left: 4px solid #049C9A; - } + .table-title { + font-size: 16px; + font-weight: bold; + color: #303133; + margin-bottom: 16px; + padding-left: 8px; + border-left: 4px solid #049c9a; + } - .pagination { - margin-top: 20px; - display: flex; - justify-content: center; - } + .pagination { + margin-top: 20px; + display: flex; + justify-content: center; + } } -</style> \ No newline at end of file +</style> diff --git a/culture/src/views/strain-library/strain-library-manage/index.vue b/culture/src/views/strain-library/strain-library-manage/index.vue index dc579a6..9917b97 100644 --- a/culture/src/views/strain-library/strain-library-manage/index.vue +++ b/culture/src/views/strain-library/strain-library-manage/index.vue @@ -1,395 +1,489 @@ <template> - <div class="list"> - <el-card class="header-box"> + <div class="list"> + <el-card class="header-box"> + <div class="box-title"> + <img src="@/assets/public/notice.png" class="header-icon" /> + <span>菌种源保藏出/入细胞库登记表说明</span> + <el-button type="text" class="view-more" @click="handleViewMore" + >查看全部 >></el-button + > + </div> + <div class="header-content" :class="{ collapsed: true }"> + <p> + 1. 菌种全部集中登记在【菌种源保藏出/入细胞库登记表】,菌种来源有 3 + 条路径。1.1 是沙土管或甘油管的源头菌种;入原始细胞库(祖代-O)。1.2 + 是斜面的源头菌种;接种入主细胞库(祖代-O)。经过育种、验证后,菌种保藏为甘油管或沙土管的,入原始细胞库(祖代-0)1.3 + 是含菌物质自己分离后获得的斜面源头菌种,接种入主细胞库;经生产验证后,保藏为沙土管或甘油管,入原始细胞库(祖代-O)。 + 2. + 菌种细胞库,分类入三库,进行传代运行管理。三类库存空间进行区分,保藏菌种。2.1 + 原始细胞库(祖代-O)、2.2 主细胞库(母代-M)、2.3 + 生产细胞库(子代-S)、(孙代-G)3. 细胞库编码规则3.1 + 细胞库编码规则:DD-M-240919-01-(O-0109-01)DD:代表项目组。M:“O”代表祖代原始细胞库,”M“代表母代主细胞库,”S“代表子代生产细胞库,“G”代表孙代生产细胞库。240919:代表在 + 24 年 9 月 19 + 接种批次的菌种;或收到外来菌种时间的入库批次。01:代表两位序列号。(O-0109-01):代表传代菌种的编号3.1.1 + 传代编码方式演例:祖代:DD-O-240919-01 + 传母代:DD-M-241017-01-(O-091901)DD-M-241017-02-(O-091901)DD-M-241017-03-(O-091901)子代:DD-S-241019-01-(M-1017-02)版权归奥利元生物所有,禁止外传。DD-S-241019-02-(M-1017-03)孙代:DD-G-241109-01-(S-1019-02)3.1.2 + 编码规则实现了编码唯一,编码可溯源,编码直观、方便。3.2 + 细胞库说明:3.2.1 + 直接购买、自行从(土壤、相关物料、商品)等分离出来菌株进入原始细胞库。3.2.2 + 从原始细胞库中选取出来再次纯化、改造、提高性能的菌株进入主细胞库。3.2.3 + 主细胞库中选取出稳定,生产性能良好的菌株扩培后保种进入生产细胞库。4. + 菌种选育-保藏过程编号说明4.1 菌种选育时,培养皿的编号可使用 a-01、a-02 + 等用于清晰形态观察记录;菌落编号使用序号 1/2/3等。4.2 + 接种斜面菌种编码(-O)使用原始细胞库编码;斜面转菌种保藏使用与斜面一致的编码(-O);斜面传代入主细胞库的传代菌种,按编码器规则编码(-M)。 + </p> + </div> - <div class="box-title"> - <img src="@/assets/public/notice.png" class="header-icon"> - <span>菌种源保藏出/入细胞库登记表说明</span> - <el-button type="text" class="view-more" @click="handleViewMore">查看全部 >></el-button> + <!-- 查看全部弹窗 --> + <el-dialog + title="菌种源保藏出/入细胞库登记表说明" + :visible.sync="dialogVisible" + width="50%" + class="view-all-dialog" + > + <div class="dialog-content"> + <p> + 1. 菌种全部集中登记在【菌种源保藏出/入细胞库登记表】,菌种来源有 3 + 条路径。1.1 是沙土管或甘油管的源头菌种;入原始细胞库(祖代-O)。1.2 + 是斜面的源头菌种;接种入主细胞库(祖代-O)。经过育种、验证后,菌种保藏为甘油管或沙土管的,入原始细胞库(祖代-0)1.3 + 是含菌物质自己分离后获得的斜面源头菌种,接种入主细胞库;经生产验证后,保藏为沙土管或甘油管,入原始细胞库(祖代-O)。2. + 菌种细胞库,分类入三库,进行传代运行管理。三类库存空间进行区分,保藏菌种。2.1 + 原始细胞库(祖代-O)、2.2 主细胞库(母代-M)、2.3 + 生产细胞库(子代-S)、(孙代-G)3. 细胞库编码规则3.1 + 细胞库编码规则:DD-M-240919-01-(O-0109-01)DD:代表项目组。M:“O”代表祖代原始细胞库,”M“代表母代主细胞库,”S“代表子代生产细胞库,“G”代表孙代生产细胞库。240919:代表在 + 24 年 9 月 19 + 接种批次的菌种;或收到外来菌种时间的入库批次。01:代表两位序列号。(O-0109-01):代表传代菌种的编号3.1.1 + 传代编码方式演例:祖代:DD-O-240919-01 + 传母代:DD-M-241017-01-(O-091901)DD-M-241017-02-(O-091901)DD-M-241017-03-(O-091901)子代:DD-S-241019-01-(M-1017-02)版权归奥利元生物所有,禁止外传。DD-S-241019-02-(M-1017-03)孙代:DD-G-241109-01-(S-1019-02)3.1.2 + 编码规则实现了编码唯一,编码可溯源,编码直观、方便。3.2 + 细胞库说明:3.2.1 + 直接购买、自行从(土壤、相关物料、商品)等分离出来菌株进入原始细胞库。3.2.2 + 从原始细胞库中选取出来再次纯化、改造、提高性能的菌株进入主细胞库。3.2.3 + 主细胞库中选取出稳定,生产性能良好的菌株扩培后保种进入生产细胞库。4. + 菌种选育-保藏过程编号说明4.1 菌种选育时,培养皿的编号可使用 + a-01、a-02 等用于清晰形态观察记录;菌落编号使用序号 1/2/3等。4.2 + 接种斜面菌种编码(-O)使用原始细胞库编码;斜面转菌种保藏使用与斜面一致的编码(-O);斜面传代入主细胞库的传代菌种,按编码器规则编码(-M)。 + </p> + </div> + </el-dialog> + </el-card> + + <!-- Table --> + <TableCustom + :queryForm="queryForm" + :tableData="tableData" + :total="total" + @currentChange="handleCurrentChange" + @sizeChange="handleSizeChange" + > + <template #search> + <el-form :model="form" label-width="auto" inline> + <el-form-item label="菌种编号:"> + <el-input v-model="form.strainNo" 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 v-if="roleType == 4" label="状态:"> + <el-select v-model="form.status" placeholder="请选择"> + <el-option label="全部" value=""></el-option> + <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-form-item class="search-btn-box"> + <el-button type="default" @click="resetForm">重置</el-button> + <el-button type="primary" @click="searchData">查询</el-button> + </el-form-item> + </el-form> + </template> + + <template #setting> + <div class="tableTitle"> + <div class="flex a-center"> + <div + class="title" + :class="{ active: currentType === 'list' }" + @click="handleTypeChange('list')" + > + 原始细胞列表 </div> - <div class="header-content" :class="{ 'collapsed': true }"> - <p>1、菌种全部集中登记在【菌种源保藏出/入细胞库登记表】,请将来源有3 类菌经。</p> - <p>1.1 原净土管理日油性的源头菌种:入细胞细胞库(现代-O)。</p> - <p>1.2 是到菌的源头菌种:接种入主细胞库(现代-O),经过百种、验证后,菌种被保存日油管理沙土菌种,入细胞细胞库(现代-O)。</p> - <p>1.3 是否菌种能自己分离后获得的源头菌种,接种入主细胞库:经过产验证后,保藏为少土管理日油管,入细胞细胞库(现代-O)。</p> + <div + class="drafts" + :class="{ active: currentType === 'draft' }" + @click="handleTypeChange('draft')" + > + 草稿箱 </div> + </div> + <div v-if="roleType == 4" class="flex a-center"> + <el-button + @click="handleNewStrain" + class="el-icon-plus" + type="primary" + style="margin-right: 12px" + >新增原始细胞</el-button + > + <el-button + @click="handleNewStrain" + class="el-icon-plus" + type="primary" + >批量新增</el-button + > + </div> + </div> + </template> - <!-- 查看全部弹窗 --> - <el-dialog title="菌种源保藏出/入细胞库登记表说明" :visible.sync="dialogVisible" width="50%" class="view-all-dialog"> - <div class="dialog-content"> - <p>1、菌种全部集中登记在【菌种源保藏出/入细胞库登记表】,请将来源有3 类菌经。</p> - <p>1.1 原净土管理日油性的源头菌种:入细胞细胞库(现代-O)。</p> - <p>1.2 是到菌的源头菌种:接种入主细胞库(现代-O),经过百种、验证后,菌种被保存日油管理沙土菌种,入细胞细胞库(现代-O)。</p> - <p>1.3 是否菌种能自己分离后获得的源头菌种,接种入主细胞库:经过产验证后,保藏为少土管理日油管,入细胞细胞库(现代-O)。</p> - </div> - </el-dialog> - </el-card> - - <!-- Table --> - <TableCustom :queryForm="queryForm" :tableData="tableData" :total="total" @currentChange="handleCurrentChange" - @sizeChange="handleSizeChange"> - <template #search> - <el-form :model="form" label-width="auto" inline> - <el-form-item label="菌种编号:"> - <el-input v-model="form.strainNo" 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-select v-model="form.status" placeholder="请选择"> - <el-option label="全部" value=""></el-option> - <el-option label="已入库" value="1"></el-option> - <el-option label="已出库" value="2"></el-option> - <el-option label="入库待确认" value="3"></el-option> - </el-select> - </el-form-item> - <el-form-item class="search-btn-box"> - <el-button type="default" @click="resetForm">重置</el-button> - <el-button type="primary" @click="searchData">查询</el-button> - </el-form-item> - </el-form> - </template> - - <template #setting> - <div class="tableTitle"> - <div class="flex a-center"> - <div class="title" :class="{ active: currentType === 'list' }" - @click="handleTypeChange('list')"> - 原始细胞列表</div> - <div class="drafts" :class="{ active: currentType === 'draft' }" - @click="handleTypeChange('draft')"> - 草稿箱</div> - </div> - <div class="flex a-center"> - <el-button @click="handleNewStrain" class="el-icon-plus" type="primary" - style="margin-right: 12px;">新增原始细胞</el-button> - <el-button @click="handleNewStrain" class="el-icon-plus" type="primary">批量新增</el-button> - </div> - </div> - </template> - - <template #table> - <el-table-column prop="strainCode" label="菌种编号" /> - <el-table-column prop="strainName" label="菌种名称" /> - <el-table-column prop="strainSource" label="菌种来源" /> - <el-table-column prop="appraisalMethod" label="鉴定方法" /> - <el-table-column prop="features" label="特征描述" /> - <el-table-column prop="saveMethod" label="菌种保存方法" /> - <el-table-column prop="saveLocation" label="保藏位置" /> - <el-table-column prop="stock" label="库存余量" /> - <el-table-column prop="remark" label="备注" /> - <el-table-column v-if="currentType === 'list'" prop="status" label="当前状态"> - <template #default="{ row }"> - <el-tag :type="getStatusType(row.status)">{{ getStatusText(row.status) }}</el-tag> - </template> - </el-table-column> - <el-table-column label="操作" width="200"> - <template #default="{ row }"> - <el-button type="text" @click="handleDetail(row)">详情</el-button> - <el-button type="text" @click="handleEdit(row)">编辑</el-button> - <el-button v-if="currentType === 'list'" type="text" @click="handleRecord(row)">出入库记录</el-button> - </template> - </el-table-column> - </template> - </TableCustom> - <StrainDetail :visible.sync="detailVisible" :detail="currentDetail" /> - </div> + <template #table> + <el-table-column prop="strainCode" label="菌种编号" /> + <el-table-column prop="strainName" label="菌种名称" /> + <el-table-column prop="strainSource" label="菌种来源" /> + <el-table-column prop="appraisalMethod" label="鉴定方法" /> + <el-table-column prop="features" label="特征描述" /> + <el-table-column prop="saveMethod" label="菌种保存方法" /> + <el-table-column prop="saveLocation" label="保藏位置" /> + <el-table-column prop="stock" label="库存余量" /> + <el-table-column prop="remark" label="备注" /> + <el-table-column + v-if="currentType === 'list'" + prop="status" + label="当前状态" + > + <template #default="{ row }"> + <el-tag :type="getStatusType(row.status)">{{ + getStatusText(row.status) + }}</el-tag> + </template> + </el-table-column> + <el-table-column label="操作" width="200"> + <template #default="{ row }"> + <el-button type="text" @click="handleDetail(row)">详情</el-button> + <el-button v-if="row.status == 2 || row.status == 4" type="text" @click="handleEdit(row)">编辑</el-button> + <el-button + v-if="currentType === 'list'" + type="text" + @click="handleRecord(row)" + >出入库记录</el-button + > + <el-button v-if="roleType == 1" type="text" @click="handleDelete(row)">删除</el-button> + </template> + </el-table-column> + </template> + </TableCustom> + <StrainDetail :visible.sync="detailVisible" :detail="currentDetail" /> + </div> </template> <script> -import StrainDetail from './components/StrainDetail.vue' -import { getList } from './service' +import StrainDetail from "./components/StrainDetail.vue"; +import { getList, deleteStrainLibrary } from "./service"; export default { - name: 'StrainLibraryManage', - components: { - StrainDetail, - }, - data() { - return { - dialogVisible: false, - currentType: 'list', - detailVisible: false, - currentDetail: {}, - form: { - strainNo: '', - strainName: '', - status: '', - type: 1 - }, - queryForm: { - pageSize: 10, - pageNum: 1, - }, - total: 800, - tableData: [] - } - }, - activated() { - this.searchData() - }, - methods: { - handleRecord(row) { - this.$router.push({ path: `/strain-library/strain-library-manage/record?id=${row.id}` }) - }, - handleNewStrain() { - this.$router.push({ path: '/strain-library/strain-library-manage/add' }) - }, - handleEdit(row) { - this.$router.push({ path: `/strain-library/strain-library-manage/add?id=${row.id}` }) - }, - handleDetail(row) { - this.currentDetail = row; - this.detailVisible = true; - }, - handleViewMore() { - this.dialogVisible = true; - }, - resetForm() { - this.form = { - strainNo: '', - strainName: '', - status: '' - } - this.searchData() - }, - searchData() { - const params = { - pageNum: this.queryForm.pageNum, - pageSize: this.queryForm.pageSize, - strainCode: this.form.strainNo, - strainName: this.form.strainName, - isDraft: this.currentType === 'draft' ? 1 : 0, - status: { - '1': 3, - '2': 1, - '3': 4 - }[this.form.status] || '' - }; - getList(params).then(res => { - if (res.code === 200) { - this.tableData = res.data.records; - this.total = res.data.total; - } - }).catch(err => { - this.$message.error('数据加载失败'); - }); - }, - handleCurrentChange(page) { - this.queryForm.pageNum = page; + name: "StrainLibraryManage", + components: { + StrainDetail, + }, + data() { + return { + dialogVisible: false, + currentType: "list", + detailVisible: false, + currentDetail: {}, + form: { + strainNo: "", + strainName: "", + status: "", + }, + queryForm: { + pageSize: 10, + pageNum: 1, + }, + total: 800, + tableData: [], + roleType: "", + }; + }, + activated() { + this.searchData(); + // 角色类型 1=超级管理员 2=审批人 3=工程师 4=实验员 + this.roleType = JSON.parse(sessionStorage.getItem("userInfo")).roleType; + }, + methods: { + handleDelete(row) { + this.$confirm("确定删除该数据吗?", "提示", { + confirmButtonText: "确定", + cancelButtonText: "取消", + type: "warning", + }).then(() => { + deleteStrainLibrary({ id: row.id }).then((res) => { + this.$message.success("删除成功"); this.searchData(); - }, - handleSizeChange(size) { - this.queryForm.pageSize = size; - this.searchData(); - }, - handleTypeChange(type) { - this.currentType = type; - this.searchData() - }, - getStatusType(status) { - const types = { - 1: 'warning', - 2: 'warning', - 3: 'success', - 4: 'success' - } - return types[status] || 'info' - }, - getStatusText(status) { - const texts = { - 1: '已出库', - 2: '出库待确认', - 3: '已入库', - 4: '入库待确认' - } - return texts[status] || '未知状态' - } - } -} + }); + }); + }, + handleRecord(row) { + this.$router.push({ + path: `/strain-library/strain-library-manage/record?id=${row.id}`, + }); + }, + handleNewStrain() { + this.$router.push({ path: "/strain-library/strain-library-manage/add" }); + }, + handleEdit(row) { + this.$router.push({ + path: `/strain-library/strain-library-manage/add?id=${row.id}`, + }); + }, + handleDetail(row) { + this.currentDetail = row; + this.detailVisible = true; + }, + handleViewMore() { + this.dialogVisible = true; + }, + resetForm() { + this.form = { + strainNo: "", + strainName: "", + status: "", + }; + this.searchData(); + }, + searchData() { + const params = { + pageNum: this.queryForm.pageNum, + pageSize: this.queryForm.pageSize, + strainCode: this.form.strainNo, + strainName: this.form.strainName, + isDraft: this.currentType === "draft" ? 1 : 0, + status: this.form.status, + type: 1, + }; + getList(params) + .then((res) => { + if (res.code === 200) { + this.tableData = res.data.records; + this.total = res.data.total; + } + }) + .catch((err) => { + this.$message.error("数据加载失败"); + }); + }, + handleCurrentChange(page) { + this.queryForm.pageNum = page; + this.searchData(); + }, + handleSizeChange(size) { + this.queryForm.pageSize = size; + this.searchData(); + }, + handleTypeChange(type) { + this.currentType = type; + this.searchData(); + }, + getStatusType(status) { + const types = { + 1: "warning", + 2: "warning", + 3: "success", + 4: "success", + }; + return types[status] || "info"; + }, + getStatusText(status) { + const texts = { + 1: "已出库", + 2: "出库待确认", + 3: "已入库", + 4: "入库待确认", + }; + return texts[status] || "未知状态"; + }, + }, +}; </script> <style scoped lang="less"> .list { - padding: 20px; + padding: 20px; } .header-box { - margin-bottom: 20px; - border-radius: 16px; - background: rgba(255, 255, 255, 0.8); + margin-bottom: 20px; + border-radius: 16px; + background: rgba(255, 255, 255, 0.8); - .box-title { - display: flex; - align-items: center; - font-size: 18px; - font-weight: bold; - margin-bottom: 15px; - position: relative; + .box-title { + display: flex; + align-items: center; + font-size: 18px; + font-weight: bold; + margin-bottom: 15px; + position: relative; - .header-icon { - width: 20px; - height: 20px; - margin-right: 10px; - } - - .view-more { - position: absolute; - right: 0; - color: #049C9A; - } + .header-icon { + width: 20px; + height: 20px; + margin-right: 10px; } - .header-content { - color: rgba(0, 0, 0, 0.88); - font-size: 14px; - line-height: 1.8; - margin-left: 30px; - transition: max-height 0.3s ease-in-out; - overflow: hidden; - - &.collapsed { - max-height: 48px; - overflow: hidden; - } - - p { - margin: 5px 0; - } + .view-more { + position: absolute; + right: 0; + color: #049c9a; } + } + + .header-content { + color: rgba(0, 0, 0, 0.88); + font-size: 14px; + line-height: 1.8; + margin-left: 30px; + transition: max-height 0.3s ease-in-out; + overflow: hidden; + + &.collapsed { + max-height: 48px; + overflow: hidden; + } + + p { + margin: 5px 0; + } + } } .search-form { - margin-bottom: 20px; - background: #F5F7FA; - padding: 24px; - border-radius: 8px; + margin-bottom: 20px; + background: #f5f7fa; + padding: 24px; + border-radius: 8px; - .el-form-item { - margin-right: 20px; - margin-bottom: 0; - } + .el-form-item { + margin-right: 20px; + margin-bottom: 0; + } - .el-button { - margin-left: 10px; - } + .el-button { + margin-left: 10px; + } } .action-buttons { - margin-bottom: 20px; + margin-bottom: 20px; - .el-button { - margin-right: 10px; - } + .el-button { + margin-right: 10px; + } } .tab-container { - display: flex; - margin-bottom: 20px; + display: flex; + margin-bottom: 20px; - .tab { - padding: 10px 30px; - border: 1px solid #DCDFE6; - border-bottom: none; - border-radius: 8px 8px 0 0; - cursor: pointer; - margin-right: 10px; - background: #F5F7FA; + .tab { + padding: 10px 30px; + border: 1px solid #dcdfe6; + border-bottom: none; + border-radius: 8px 8px 0 0; + cursor: pointer; + margin-right: 10px; + background: #f5f7fa; - &.active { - background: #fff; - border-color: #049C9A; - color: #049C9A; - font-weight: bold; - } + &.active { + background: #fff; + border-color: #049c9a; + color: #049c9a; + font-weight: bold; } + } } .flex { - display: flex; - align-items: center; + display: flex; + align-items: center; } .tableTitle { - display: flex; - padding-bottom: 20px; - justify-content: space-between; - align-items: center; + 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; - width: unset; - cursor: pointer; - height: 50px; - line-height: 50px; - width: 166px; - text-align: center; + .title { + background: #fafafc; + border-radius: 8px 8px 0px 0px; + border: 1px solid #dcdfe6; + font-weight: bold; + font-size: 18px; + color: #606266; + width: unset; + cursor: pointer; + height: 50px; + line-height: 50px; + width: 166px; + 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: 166px; + 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: 166px; - text-align: center; - } - - .active { - color: #049c9a; - background: #ffffff; - border-radius: 8px 8px 0px 0px; - border: 1px solid #049c9a; - - } + .active { + color: #049c9a; + background: #ffffff; + border-radius: 8px 8px 0px 0px; + border: 1px solid #049c9a; + } } .view-all-dialog { - :deep(.el-dialog__header) { - padding: 20px; - border-bottom: 1px solid #EBEEF5; - margin-right: 0; + :deep(.el-dialog__header) { + padding: 20px; + border-bottom: 1px solid #ebeef5; + margin-right: 0; - .el-dialog__title { - font-size: 18px; - font-weight: bold; - color: #303133; - } + .el-dialog__title { + font-size: 18px; + font-weight: bold; + color: #303133; } + } - :deep(.el-dialog__body) { - padding: 20px; + :deep(.el-dialog__body) { + padding: 20px; - .dialog-content { - font-size: 14px; - line-height: 1.8; - color: #606266; + .dialog-content { + font-size: 14px; + line-height: 1.8; + color: #606266; - p { - margin: 12px 0; + p { + margin: 12px 0; - &:first-child { - margin-top: 0; - } - - &:last-child { - margin-bottom: 0; - } - } + &:first-child { + margin-top: 0; } + + &:last-child { + margin-bottom: 0; + } + } } + } } -</style> \ No newline at end of file +</style> diff --git a/culture/src/views/strain-library/strain-library-manage/record.vue b/culture/src/views/strain-library/strain-library-manage/record.vue index 0db0f96..03eab17 100644 --- a/culture/src/views/strain-library/strain-library-manage/record.vue +++ b/culture/src/views/strain-library/strain-library-manage/record.vue @@ -1,413 +1,438 @@ <template> - <div class="record-page"> - <!-- 基本信息展示区域 --> - <el-card class="header-box"> - <div class="header-content"> - <!-- 第一行 --> - <div class="info-row"> - <div class="info-item left-column"> - <span class="label">菌种编号:</span> - <span class="value">{{ detail.strainCode }}</span> - </div> - <div class="info-item flex-column"> - <span class="label">鉴定方法:</span> - <span class="value">{{ detail.appraisalMethod }}</span> - </div> - <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 left-column"> - <span class="label">菌种名称:</span> - <span class="value">{{ detail.strainName }}</span> - </div> - <div class="info-item flex-column full-width"> - <span class="label">特性描述:</span> - <span class="value">{{ detail.features }}</span> - </div> - </div> - - <!-- 第三行 --> - <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 class="record-page"> + <!-- 基本信息展示区域 --> + <el-card class="header-box"> + <div class="header-content"> + <!-- 第一行 --> + <div class="info-row"> + <div class="info-item left-column"> + <span class="label">菌种编号:</span> + <span class="value">{{ detail.strainCode }}</span> + </div> + <div class="info-item flex-column"> + <span class="label">鉴定方法:</span> + <span class="value">{{ detail.appraisalMethod }}</span> + </div> + <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 left-column"> + <span class="label">菌种名称:</span> + <span class="value">{{ detail.strainName }}</span> + </div> + <div class="info-item flex-column full-width"> + <span class="label">特性描述:</span> + <span class="value">{{ detail.features }}</span> + </div> + </div> + + <!-- 第三行 --> + <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> + + <!-- 出入库记录表格 --> + <TableCustom + :queryForm="queryForm" + :tableData="recordList" + :total="total" + @currentChange="handlePageChange" + > + <template #setting> + <div class="tableTitle"> + <div class="flex a-center"> + <div + class="title" + :class="{ active: currentType === 'table' }" + @click="handleTypeChange('table')" + > + 原始细胞保藏出/入库登记表 </div> - </el-card> + <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> - <!-- 出入库记录表格 --> - <TableCustom :queryForm="queryForm" :tableData="recordList" :total="total" @currentChange="handlePageChange"> - <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 @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> - <template #table v-if="currentType === 'table'"> - <el-table-column prop="type" label="出库/入库" /> - <el-table-column prop="operateTime" label="操作时间" /> - <el-table-column prop="operator" label="操作人签字" /> - <el-table-column prop="reviewer" label="菌种保藏人签字" /> - <el-table-column prop="status" label="状态"> - <template #default="{ row }"> - <el-tag :type="row.status === '已确认' ? 'success' : 'warning'"> - {{ row.status }} - </el-tag> - </template> - </el-table-column> - <el-table-column label="操作" width="180"> - <template #default="{ row }"> - <el-button type="text" class="operation-btn" @click="handleView(row)">详情</el-button> - <el-button type="text" class="operation-btn" @click="handleEdit(row)">编辑</el-button> - <el-button 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" - /> - - <add-record-dialog - :visible.sync="addDialogVisible" - @confirm="handleAddRecordConfirm" - /> - </div> + <!-- 详情弹窗 --> + <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 RecordDetailDialog from './components/RecordDetailDialog.vue' -import AddRecordDialog from './components/AddRecordDialog.vue' -import RecordTimeline from './components/RecordTimeline.vue' -import { timeList,getDetail,addWarehousing } from './service' +import RecordDetailDialog from "./components/RecordDetailDialog.vue"; +import AddRecordDialog from "./components/AddRecordDialog.vue"; +import RecordTimeline from "./components/RecordTimeline.vue"; +import { + timeList, + getDetail, + addWarehousing, + getDetailById, + confirmWarehousing, +} from "./service"; export default { - name: 'StrainRecord', - components: { - RecordDetailDialog, - AddRecordDialog, - RecordTimeline - }, - data() { - return { - currentType: 'table', - detail: {}, - currentPage: 1, - pageSize: 10, - total: 0, - queryForm: { - pageSize: 10, - pageNum: 1 - }, - recordList: [ - { - type: '入库', - operateTime: '2025-1-21 15:46:50', - operator: '张三', - reviewer: '李四', - status: '已确认' - }, - { - type: '出库', - operateTime: '2025-1-21 15:46:50', - operator: '张三', - reviewer: '李四', - status: '已确认' - }, - { - type: '入库', - operateTime: '2025-1-21 15:46:50', - operator: '张三', - reviewer: '李四', - status: '已确认' - }, - { - type: '出库', - operateTime: '2025-1-21 15:46:50', - operator: '张三', - reviewer: '李四', - status: '已确认' - }, - { - type: '入库', - operateTime: '2025-1-21 15:46:50', - operator: '李四', - reviewer: '李四', - status: '已确认' - } - ], - timelineList: [], - dialogVisible: false, - currentRecord: {}, - addDialogVisible: false - } - }, - computed: { - // timelineList() { - // // 可根据需要处理数据格式,这里直接用 recordList - // return this.recordList.map(item => ({ - // ...item, - // confirmTime: item.confirmTime || item.operateTime // 若无确认时间则用操作时间 - // })) - // } - }, - created() { - // 获取路由参数中的菌种信息 - const strainId = this.$route.query.id - this.queryForm.id = strainId - if (strainId) { - this.getStrainDetail(strainId) - this.getRecordList() - } - }, - methods: { - getStrainDetail(id) { - // 这里应该调用接口获取菌种详情 - getDetail({id}).then(res => { - this.detail = res - }) - }, - getRecordList() { - // 这里应该调用接口获取出入库记录 - timeList(this.queryForm).then(res => { - this.timelineList = res.data - }) - this.total = this.recordList.length - }, - handleView(row) { - this.currentRecord = { - ...row, - operatorSignature: 'https://zos.alipayobjects.com/rmsportal/jkjgkEfvpUPVyRjUImniVslZfWPnJuuZ.png', // 模拟操作人签字图片 - reviewerSignature: 'https://zos.alipayobjects.com/rmsportal/jkjgkEfvpUPVyRjUImniVslZfWPnJuuZ.png', // 模拟保藏人签字图片 - operateTime: '2025-1-22 13:49:51', - confirmTime: '2025-1-22 14:30:00' - } - this.dialogVisible = true - }, - handlePageChange(page) { - this.queryForm.pageNum = page - // 这里应该调用接口获取对应页码的数据 - }, - handleTypeChange(type) { - this.currentType = type - }, - handleAddRecord() { - this.addDialogVisible = true - }, - handleEdit(row) { - console.log('编辑记录:', row) - // 实现编辑记录逻辑 - }, - handleDelete(row) { - this.$confirm('确认删除该记录吗?', '提示', { - confirmButtonText: '确定', - cancelButtonText: '取消', - type: 'warning' - }).then(() => { - console.log('删除记录:', row) - // 实际项目中这里应该调用删除API - this.$message({ - type: 'success', - message: '删除成功!' - }) - }).catch(() => { - this.$message({ - type: 'info', - message: '已取消删除' - }) - }) - }, - handleDialogClose() { - this.currentRecord = {} - this.dialogVisible = false - }, - handleOutbound(data) { - // 这里调用出库API - console.log('出库操作:', data) - this.$message.success('出库成功') - this.dialogVisible = false - // 刷新列表 - this.getRecordList() - }, - handleAddRecordConfirm(record) { - addWarehousing({...record,trainLibraryId: this.$route.query.id}).then(res => { - this.$message.success('操作成功') - this.getRecordList() - }) - }, - goBack() { - this.$router.go(-1) - } + name: "StrainRecord", + components: { + RecordDetailDialog, + AddRecordDialog, + RecordTimeline, + }, + data() { + return { + currentType: "table", + detail: {}, + currentPage: 1, + pageSize: 10, + total: 0, + queryForm: { + pageSize: 10, + pageNum: 1, + }, + recordList: [], + timelineList: [], + dialogVisible: false, + currentRecord: {}, + addDialogVisible: false, + dialogType: "detail", + roleType: "", + }; + }, + created() { + 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: { + 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.addDialogVisible = true; + }, + 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(res.msg); + } + }); + }, + handleAddRecordConfirm(record) { + addWarehousing({ ...record, trainLibraryId: this.$route.query.id }).then( + (res) => { + this.$message.success("操作成功"); + this.getRecordList(); + } + ); + }, + goBack() { + this.$router.go(-1); + }, + }, +}; </script> <style lang="less" scoped> .record-page { - min-height: 100vh; + min-height: 100vh; - .header-box { - margin-bottom: 20px; - border-radius: 16px; - background: rgba(255, 255, 255, 0.8); - height: 130px; - overflow: hidden; + .header-box { + margin-bottom: 20px; + border-radius: 16px; + background: rgba(255, 255, 255, 0.8); + height: 130px; + overflow: hidden; - .header-content { - color: rgba(0, 0, 0, 0.88); - font-size: 14px; - line-height: 1.5; + .header-content { + color: rgba(0, 0, 0, 0.88); + font-size: 14px; + line-height: 1.5; - .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; - } - } - } - } - } - - .flex { + .info-row { display: flex; - align-items: center; - } + flex-wrap: wrap; + margin-bottom: 8px; - .tableTitle { - display: flex; - padding-bottom: 20px; - justify-content: space-between; - align-items: center; + &:last-child { + margin-bottom: 0; + } - .title { - background: #fafafc; - border-radius: 8px 8px 0px 0px; - border: 1px solid #dcdfe6; - font-weight: bold; - font-size: 18px; + .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; - cursor: pointer; - height: 50px; - line-height: 50px; - width: 280px; - text-align: center; - } + margin-right: 8px; + white-space: nowrap; + } - .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; + .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; + } } + } + } + } - .active { - color: #049c9a; - background: #ffffff; - border-radius: 8px 8px 0px 0px; - border: 1px solid #049c9a; - } + .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; } - .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; - } - } + .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; } - .operation-btn { - margin-right: 12px; + .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> \ No newline at end of file +</style> diff --git a/culture/src/views/strain-library/strain-library-manage/service.js b/culture/src/views/strain-library/strain-library-manage/service.js index 4ebf2fb..08f789d 100644 --- a/culture/src/views/strain-library/strain-library-manage/service.js +++ b/culture/src/views/strain-library/strain-library-manage/service.js @@ -39,3 +39,18 @@ export const addWarehousing = (data) => { return axios.post('/open/t-train-library/addWarehousing', { ...data }) } + +// 确认出入库 +export const confirmWarehousing = (data) => { + return axios.post('/api/t-train-library/confirm', { ...data }) +} + +// 删除菌种库 +export const deleteStrainLibrary = (params) => { + return axios.delete('/open/t-train-library/deleteById', { params }) +} + +// 删除菌种库出入库记录 +export const deleteWarehousing = (params) => { + return axios.delete('/open/t-train-library/deleteWarehousingById', { params }) +} -- Gitblit v1.7.1