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