<template>
|
<div>
|
<el-dialog v-model="visible" :title="dialogTitle"
|
width="90%" :close-on-click-modal="false" @close="closeDia">
|
<div class="form-body">
|
<table class="verify-table">
|
<colgroup>
|
<col style="width: 16%"/>
|
<col style="width: 36%"/>
|
<col style="width: 16%"/>
|
<col style="width: 32%"/>
|
</colgroup>
|
<tbody>
|
<tr>
|
<th>标准名称</th>
|
<td>
|
<el-input v-model="form.standardName"/>
|
</td>
|
<th>标准编号</th>
|
<td>
|
<el-input v-model="form.standardCode"/>
|
</td>
|
</tr>
|
<tr>
|
<th>验证项目</th>
|
<td colspan="3">
|
<el-input v-model="form.verifyItem"/>
|
</td>
|
</tr>
|
</tbody>
|
</table>
|
<table class="verify-table content-table">
|
<colgroup>
|
<col style="width: 16%"/>
|
<col style="width: 36%"/>
|
<col style="width: 36%"/>
|
<col style="width: 12%"/>
|
</colgroup>
|
<thead>
|
<tr>
|
<th>验证内容</th>
|
<th>方法要求</th>
|
<th>实验室能力</th>
|
<th>是否满足</th>
|
</tr>
|
</thead>
|
<tbody>
|
<tr v-for="(item, index) in content.checks" :key="item.key">
|
<th>{{ index < 7 ? `${index + 1}. ${item.label}` : item.label }}</th>
|
<td>
|
<el-input v-model="item.requirement" type="textarea" :rows="3"/>
|
</td>
|
<td>
|
<el-input v-model="item.capability" type="textarea" :rows="3"/>
|
<div v-if="item.key === 'personnel'" class="row-extra">
|
<span>检测人员</span>
|
<el-select v-model="form.confirmUser" multiple :multiple-limit="5" placeholder="请选择检测人员">
|
<el-option v-for="user in userList" :key="user.id" :label="user.name" :value="user.id"/>
|
</el-select>
|
<el-link v-if="operationType === 'edit'" type="primary" @click="viewWorkPermit">查看上岗证</el-link>
|
</div>
|
<div v-if="item.key === 'equipment'" class="row-extra">
|
<el-link type="primary" @click="viewDevice">查看设备</el-link>
|
<el-link v-if="operationType === 'edit'" type="primary" @click="viewCalibrationsFileDia">查看校准证书
|
</el-link>
|
</div>
|
</td>
|
<td class="choice-cell">
|
<el-radio-group v-model="item.satisfied">
|
<el-radio :value="1">是</el-radio>
|
<el-radio :value="0">否</el-radio>
|
</el-radio-group>
|
</td>
|
</tr>
|
<tr>
|
<th>验证结论</th>
|
<td colspan="3">
|
<el-input v-model="content.conclusion" type="textarea" :rows="4"/>
|
</td>
|
</tr>
|
<tr v-for="decision in decisions" :key="decision.key">
|
<th>{{ decision.label }}</th>
|
<td colspan="3">
|
<div class="decision-row">
|
<el-radio-group v-model="content[decision.key]">
|
<el-radio value="同意使用">同意使用</el-radio>
|
<el-radio value="不能使用">不能使用</el-radio>
|
<el-radio value="限制使用">限制使用</el-radio>
|
</el-radio-group>
|
<span>{{ decision.personLabel }}</span>
|
<el-input v-model="content[decision.personKey]" class="person-input"/>
|
<span>日期</span>
|
<el-date-picker v-model="content[decision.dateKey]" type="date" value-format="YYYY-MM-DD"
|
placeholder="选择日期"/>
|
</div>
|
</td>
|
</tr>
|
</tbody>
|
</table>
|
<div v-if="operationType === 'edit'" class="attachments">
|
<span>附件1 原始记录、附件2 验证结果</span>
|
<el-button size="small" @click="viewTestRecord">查看及上传附件</el-button>
|
</div>
|
</div>
|
<template #footer>
|
<el-button @click="closeDia">取消</el-button>
|
<el-button v-if="operationType !== 'view'" :loading="loading" type="primary" @click="submit">提交</el-button>
|
</template>
|
</el-dialog>
|
<ViewDeviceDialog v-if="deviceVisible" ref="device" @closDeviceDia="deviceVisible = false"
|
@handleDeviceInfo="setDevices"/>
|
<ViewTestRecord v-if="recordVisible" ref="record"/>
|
<ViewWorkPermitDia v-if="permitVisible" ref="permit"/>
|
<CalibrationsFileDia v-if="calibrationVisible" ref="calibration"/>
|
</div>
|
</template>
|
|
<script>
|
import ViewDeviceDialog from './ViewDeviceDialog.vue'
|
import ViewTestRecord from './ViewTestRecord.vue'
|
import ViewWorkPermitDia from './viewWorkPermitDia.vue'
|
import CalibrationsFileDia from './calibrationsFileDia.vue'
|
import {addMethodVerify, getMethodVerifyOne, updateMethodVerify} from '@/api/cnas/process/method/methodVerification'
|
import {selectUserCondition} from '@/api/cnas/process/method/methodVerification'
|
|
const labels = [
|
['personnel', '人员能力'], ['equipment', '检测设备'], ['reagents', '试剂要求'],
|
['conditions', '试验条件'], ['sample', '样品处置'], ['steps', '试验步骤'],
|
['processing', '数据处理'], ['qualityControl', '质量控制方法'], ['result', '方法验证结果']
|
]
|
const decisions = [
|
{key: 'reviewDecision', label: '评审意见', personKey: 'reviewer', personLabel: '评审人员', dateKey: 'reviewDate'},
|
{
|
key: 'approvalDecision',
|
label: '审批意见',
|
personKey: 'approver',
|
personLabel: '技术负责人',
|
dateKey: 'approvalDate'
|
}
|
]
|
|
function emptyContent() {
|
return {
|
checks: labels.map(([key, label]) => ({key, label, requirement: '', capability: '', satisfied: null})),
|
conclusion: '',
|
reviewDecision: '',
|
reviewer: '',
|
reviewDate: '',
|
approvalDecision: '',
|
approver: '',
|
approvalDate: ''
|
}
|
}
|
|
export default {
|
name: 'MethodVerificationWordForm',
|
components: {ViewDeviceDialog, ViewTestRecord, ViewWorkPermitDia, CalibrationsFileDia},
|
props: {operationType: {type: String, default: 'add'}},
|
computed: {
|
dialogTitle() {
|
if (this.operationType === 'view') return '查看方法验证'
|
return this.operationType === 'edit' ? '编辑方法验证' : '新增方法验证'
|
}
|
},
|
data() {
|
return {
|
visible: false, loading: false, decisions,
|
form: {standardName: '', standardCode: '', verifyItem: '', confirmUser: [], machineAttachmentList: []},
|
content: emptyContent(), info: {}, userList: [],
|
deviceVisible: false, recordVisible: false, permitVisible: false, calibrationVisible: false
|
}
|
},
|
methods: {
|
openDia(row = {}) {
|
this.visible = true
|
this.info = row || {}
|
selectUserCondition().then(res => {
|
if (res.code === 200) this.userList = res.data || []
|
})
|
if (this.operationType !== 'add') this.load(row)
|
},
|
load(row) {
|
getMethodVerifyOne({methodVerifyId: row.methodVerifyId}).then(res => {
|
if (res.code !== 200) return
|
this.form = {...res.data, confirmUser: res.data.confirmUser ? res.data.confirmUser.split(',').map(Number) : []}
|
try {
|
const saved = JSON.parse(res.data.verificationContent || '{}')
|
const defaults = emptyContent()
|
this.content = {
|
...defaults, ...saved, checks: defaults.checks.map(item => ({
|
...item, ...(saved.checks || []).find(check => check.key === item.key)
|
}))
|
}
|
} catch (_) {
|
this.content = emptyContent()
|
this.$message.error('验证内容格式错误,请联系管理员')
|
}
|
})
|
},
|
submit() {
|
if (!this.form.standardName?.trim() || !this.form.standardCode?.trim() || !this.form.verifyItem?.trim()) {
|
this.$message.warning('请填写标准名称、标准编号和验证项目')
|
return
|
}
|
this.loading = true
|
const payload = {
|
...this.form, operationType: 1,
|
confirmUser: (this.form.confirmUser || []).join(','),
|
verificationContent: JSON.stringify(this.content)
|
}
|
const request = this.operationType === 'edit' ? updateMethodVerify : addMethodVerify
|
request(payload).then(res => {
|
if (res.code === 200) {
|
this.$message.success('操作成功');
|
this.closeDia()
|
}
|
}).finally(() => {
|
this.loading = false
|
})
|
},
|
openChild(flag, refName, data) {
|
this[flag] = true
|
this.$nextTick(() => this.$refs[refName]?.openDia(data))
|
},
|
viewWorkPermit() {
|
if (!this.form.confirmUser?.length) {
|
this.$message.warning('请先选择检测人员')
|
return
|
}
|
this.openChild('permitVisible', 'permit', this.form.confirmUser)
|
},
|
viewDevice() {
|
this.openChild('deviceVisible', 'device', this.form)
|
},
|
viewCalibrationsFileDia() {
|
this.openChild('calibrationVisible', 'calibration', this.form)
|
},
|
viewTestRecord() {
|
this.openChild('recordVisible', 'record', this.info)
|
},
|
setDevices(list) {
|
this.deviceVisible = false;
|
this.form.machineAttachmentList = list
|
},
|
closeDia() {
|
this.visible = false;
|
this.$emit('closeDia')
|
}
|
}
|
}
|
</script>
|
|
<style scoped>
|
.form-body {
|
max-height: 70vh;
|
overflow: auto;
|
}
|
|
.verify-table {
|
width: 100%;
|
border-collapse: collapse;
|
margin-bottom: 14px;
|
table-layout: fixed;
|
}
|
|
.verify-table th, .verify-table td {
|
border: 1px solid #c5cbd0;
|
padding: 10px;
|
vertical-align: middle;
|
}
|
|
.verify-table th {
|
background: #f5f7f8;
|
font-weight: 500;
|
text-align: center;
|
}
|
|
.choice-cell {
|
text-align: center;
|
}
|
|
.row-extra, .decision-row, .attachments {
|
display: flex;
|
align-items: center;
|
gap: 10px;
|
flex-wrap: wrap;
|
margin-top: 8px;
|
}
|
|
.person-input {
|
width: 140px;
|
}
|
|
.attachments {
|
margin: 16px 0;
|
}
|
</style>
|