feat(web-admin): add shared storage-cert QC fill/view dialog.
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
1 parent
28b10663ee
commit
df827e4344
1 file changed
+354
@@ -0,0 +1,354 @@
|
||||
<template>
|
||||
<el-dialog
|
||||
:model-value="visible"
|
||||
:title="dialogTitle"
|
||||
width="920px"
|
||||
class="dialog-form-layout storage-cert-qc-dialog"
|
||||
align-center
|
||||
append-to-body
|
||||
destroy-on-close
|
||||
modal-class="protocol-dialog-mask"
|
||||
@update:model-value="emit('update:visible', $event)"
|
||||
>
|
||||
<div v-loading="loading" class="storage-cert-qc-dialog__body">
|
||||
<div class="ctx-row">
|
||||
<div class="ctx-item">
|
||||
<label>实验室流转编码</label>
|
||||
<div class="val">{{ identity.labFlowCode || '—' }}</div>
|
||||
</div>
|
||||
<div class="ctx-item">
|
||||
<label>协议编码</label>
|
||||
<div class="val">{{ identity.protocolCode || '—' }}</div>
|
||||
</div>
|
||||
<div class="ctx-item">
|
||||
<label>母亲姓名</label>
|
||||
<div class="val">{{ identity.motherName || '—' }}</div>
|
||||
</div>
|
||||
<div class="ctx-item">
|
||||
<label>样本类型</label>
|
||||
<div class="val">{{ identity.sampleTypeName || sampleTypeName || '—' }}</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<el-empty
|
||||
v-if="mode === 'view' && !filledFlag && !loading"
|
||||
description="质检尚未填写"
|
||||
/>
|
||||
|
||||
<template v-else-if="typeKey && groups.length">
|
||||
<div
|
||||
v-for="(group, gi) in groups"
|
||||
:key="gi"
|
||||
class="field-group"
|
||||
:class="`field-group--${group.variant || 'primary'}`"
|
||||
>
|
||||
<div class="field-group__title">{{ group.title }}</div>
|
||||
<el-form label-position="top" class="biz-dialog-form field-group__form" @submit.prevent>
|
||||
<div class="form-grid-3">
|
||||
<el-form-item
|
||||
v-for="field in group.fields"
|
||||
:key="field.key"
|
||||
:label="field.label"
|
||||
:class="{
|
||||
'form-col-span-2': field.span === 2,
|
||||
'form-col-span-3': field.span === 3
|
||||
}"
|
||||
>
|
||||
<template v-if="mode === 'view'">
|
||||
<div class="readonly-box">{{ displayValue(field.key) }}</div>
|
||||
</template>
|
||||
<el-select
|
||||
v-else-if="field.control === 'select_yny'"
|
||||
v-model="form[field.key]"
|
||||
clearable
|
||||
placeholder="请选择"
|
||||
style="width: 100%"
|
||||
>
|
||||
<el-option
|
||||
v-for="opt in YNY_OPTIONS"
|
||||
:key="opt.value"
|
||||
:label="opt.label"
|
||||
:value="opt.value"
|
||||
/>
|
||||
</el-select>
|
||||
<el-input
|
||||
v-else-if="field.control === 'textarea'"
|
||||
v-model="form[field.key]"
|
||||
type="textarea"
|
||||
:rows="4"
|
||||
placeholder="请输入"
|
||||
/>
|
||||
<el-input v-else v-model="form[field.key]" clearable placeholder="请输入" />
|
||||
</el-form-item>
|
||||
</div>
|
||||
</el-form>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<el-alert
|
||||
v-else-if="!loading && !typeKey"
|
||||
type="warning"
|
||||
:closable="false"
|
||||
title="该样本类型不支持储存证质检信息"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<template #footer>
|
||||
<template v-if="mode === 'edit'">
|
||||
<el-button @click="emit('update:visible', false)">
|
||||
<el-icon><Close /></el-icon>
|
||||
取消
|
||||
</el-button>
|
||||
<el-button type="primary" :loading="saving" @click="handleSave">
|
||||
<el-icon><Check /></el-icon>
|
||||
确定
|
||||
</el-button>
|
||||
</template>
|
||||
<el-button v-else type="primary" @click="emit('update:visible', false)">
|
||||
<el-icon><Check /></el-icon>
|
||||
关闭
|
||||
</el-button>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { Check, Close } from '@element-plus/icons-vue'
|
||||
import { ElMessage } from 'element-plus'
|
||||
import {
|
||||
fetchSampleStorageCertQc,
|
||||
saveSampleStorageCertQc,
|
||||
type SampleStorageCertQcDetail
|
||||
} from '@/api/sample-storage-cert-qc'
|
||||
import {
|
||||
STORAGE_CERT_QC_GROUPS,
|
||||
YNY_OPTIONS,
|
||||
resolveStorageCertQcTypeKey,
|
||||
type StorageCertQcTypeKey
|
||||
} from '@/utils/storage-cert-qc-fields'
|
||||
|
||||
const props = defineProps<{
|
||||
visible: boolean
|
||||
mode: 'edit' | 'view'
|
||||
sampleCode: string
|
||||
sampleTypeName?: string
|
||||
qcWorkOrderId?: string
|
||||
}>()
|
||||
|
||||
const emit = defineEmits<{
|
||||
(e: 'update:visible', v: boolean): void
|
||||
(e: 'saved'): void
|
||||
}>()
|
||||
|
||||
const loading = ref(false)
|
||||
const saving = ref(false)
|
||||
const filledFlag = ref(false)
|
||||
const typeKey = ref<StorageCertQcTypeKey | null>(null)
|
||||
const form = reactive<Record<string, string>>({})
|
||||
const identity = reactive({
|
||||
labFlowCode: '',
|
||||
protocolCode: '',
|
||||
motherName: '',
|
||||
sampleTypeName: ''
|
||||
})
|
||||
|
||||
const dialogTitle = computed(() =>
|
||||
props.mode === 'edit' ? '填写储存证质检信息' : '查看储存证的质检信息'
|
||||
)
|
||||
|
||||
const groups = computed(() => (typeKey.value ? STORAGE_CERT_QC_GROUPS[typeKey.value] || [] : []))
|
||||
|
||||
function displayValue(key: string) {
|
||||
const v = form[key]
|
||||
if (v == null || String(v).trim() === '') return '—'
|
||||
return String(v)
|
||||
}
|
||||
|
||||
function resetFormKeys(key: StorageCertQcTypeKey | null, payload: Record<string, unknown>) {
|
||||
Object.keys(form).forEach((k) => delete form[k])
|
||||
if (!key) return
|
||||
for (const group of STORAGE_CERT_QC_GROUPS[key] || []) {
|
||||
for (const field of group.fields) {
|
||||
const raw = payload[field.key]
|
||||
form[field.key] = raw == null ? '' : String(raw)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function applyDetail(detail: SampleStorageCertQcDetail) {
|
||||
identity.labFlowCode = detail.labFlowCode || detail.sampleCode || ''
|
||||
identity.protocolCode = detail.protocolCode || ''
|
||||
identity.motherName = detail.motherName || ''
|
||||
identity.sampleTypeName = detail.sampleTypeName || props.sampleTypeName || ''
|
||||
filledFlag.value = !!detail.filledFlag
|
||||
const resolved =
|
||||
(detail.sampleTypeKey as StorageCertQcTypeKey | null) ||
|
||||
resolveStorageCertQcTypeKey(identity.sampleTypeName)
|
||||
typeKey.value = resolved
|
||||
resetFormKeys(resolved, (detail.payload || {}) as Record<string, unknown>)
|
||||
}
|
||||
|
||||
async function loadDetail() {
|
||||
const code = String(props.sampleCode || '').trim()
|
||||
if (!code) return
|
||||
loading.value = true
|
||||
try {
|
||||
const detail = (await fetchSampleStorageCertQc(code)) as SampleStorageCertQcDetail
|
||||
applyDetail(detail)
|
||||
} catch {
|
||||
typeKey.value = resolveStorageCertQcTypeKey(props.sampleTypeName)
|
||||
filledFlag.value = false
|
||||
resetFormKeys(typeKey.value, {})
|
||||
identity.labFlowCode = code
|
||||
identity.sampleTypeName = props.sampleTypeName || ''
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
async function handleSave() {
|
||||
const code = String(props.sampleCode || '').trim()
|
||||
if (!code || !typeKey.value) {
|
||||
ElMessage.warning('无法保存:样本类型不支持或缺少样本编码')
|
||||
return
|
||||
}
|
||||
const payload: Record<string, unknown> = {}
|
||||
for (const group of groups.value) {
|
||||
for (const field of group.fields) {
|
||||
const v = form[field.key]
|
||||
payload[field.key] = v == null ? '' : v
|
||||
}
|
||||
}
|
||||
saving.value = true
|
||||
try {
|
||||
await saveSampleStorageCertQc(code, {
|
||||
payload,
|
||||
qcWorkOrderId: props.qcWorkOrderId || undefined
|
||||
})
|
||||
ElMessage.success('保存成功')
|
||||
emit('saved')
|
||||
emit('update:visible', false)
|
||||
} finally {
|
||||
saving.value = false
|
||||
}
|
||||
}
|
||||
|
||||
watch(
|
||||
() => [props.visible, props.sampleCode] as const,
|
||||
([vis]) => {
|
||||
if (vis) loadDetail()
|
||||
}
|
||||
)
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.storage-cert-qc-dialog__body {
|
||||
min-height: 120px;
|
||||
}
|
||||
|
||||
.ctx-row {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(4, 1fr);
|
||||
gap: 10px;
|
||||
margin-bottom: 16px;
|
||||
padding: 12px;
|
||||
background: #f8fafc;
|
||||
border: 1px solid var(--el-border-color-lighter, #e2e8f0);
|
||||
border-radius: 8px;
|
||||
}
|
||||
|
||||
.ctx-item label {
|
||||
display: block;
|
||||
margin-bottom: 2px;
|
||||
font-size: 11px;
|
||||
color: var(--el-text-color-secondary, #64748b);
|
||||
}
|
||||
|
||||
.ctx-item .val {
|
||||
font-size: 13px;
|
||||
font-weight: 500;
|
||||
color: var(--el-text-color-primary, #0f172a);
|
||||
}
|
||||
|
||||
.field-group {
|
||||
margin-bottom: 14px;
|
||||
overflow: hidden;
|
||||
border: 1px solid var(--el-border-color-lighter, #e2e8f0);
|
||||
border-radius: 10px;
|
||||
}
|
||||
|
||||
.field-group__title {
|
||||
padding: 10px 14px;
|
||||
font-size: 13px;
|
||||
font-weight: 600;
|
||||
color: var(--el-color-primary-dark-2, #059669);
|
||||
background: var(--el-color-primary-light-9, #ecfdf5);
|
||||
border-bottom: 1px solid #a7f3d0;
|
||||
}
|
||||
|
||||
.field-group--alt .field-group__title {
|
||||
color: var(--el-text-color-regular, #334155);
|
||||
background: #f1f5f9;
|
||||
border-bottom-color: var(--el-border-color-lighter, #e2e8f0);
|
||||
}
|
||||
|
||||
.field-group--meta .field-group__title {
|
||||
color: #92400e;
|
||||
background: #fffbeb;
|
||||
border-bottom-color: #fde68a;
|
||||
}
|
||||
|
||||
.field-group__form {
|
||||
padding: 14px;
|
||||
}
|
||||
|
||||
.form-grid-3 {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(3, 1fr);
|
||||
gap: 12px 14px;
|
||||
|
||||
:deep(.el-form-item) {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.form-col-span-2 {
|
||||
grid-column: span 2;
|
||||
}
|
||||
|
||||
.form-col-span-3 {
|
||||
grid-column: 1 / -1;
|
||||
}
|
||||
|
||||
.readonly-box {
|
||||
min-height: 36px;
|
||||
padding: 8px 10px;
|
||||
font-size: 13px;
|
||||
color: var(--el-text-color-regular, #334155);
|
||||
white-space: pre-wrap;
|
||||
background: #f8fafc;
|
||||
border: 1px solid var(--el-border-color-lighter, #e2e8f0);
|
||||
border-radius: 8px;
|
||||
}
|
||||
|
||||
@media (max-width: 900px) {
|
||||
.form-grid-3 {
|
||||
grid-template-columns: repeat(2, 1fr);
|
||||
}
|
||||
|
||||
.ctx-row {
|
||||
grid-template-columns: 1fr 1fr;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 560px) {
|
||||
.form-grid-3 {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
|
||||
.form-col-span-2,
|
||||
.form-col-span-3 {
|
||||
grid-column: 1 / -1;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
Reference in new issue
Block a user