提交
This commit is contained in:
1 parent
1ca2668112
commit
5b3edb1700
278 files changed
+53761
-36931
No files matched your search
@@ -34,7 +34,9 @@
|
||||
show-overflow-tooltip
|
||||
/>
|
||||
<el-table-column prop="productTypeName" label="产品类型" min-width="100" />
|
||||
<el-table-column prop="specName" label="规格" min-width="90" />
|
||||
<el-table-column label="规格" min-width="90">
|
||||
<template #default="{ row }">{{ resolveCellProductSpecDisplay(row) || '—' }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="passage" label="代次" min-width="70" align="center" />
|
||||
<el-table-column
|
||||
prop="storageSlotName"
|
||||
@@ -355,7 +357,9 @@
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="batchNo" label="批次号" min-width="110" show-overflow-tooltip />
|
||||
<el-table-column prop="specName" label="规格" min-width="90" show-overflow-tooltip />
|
||||
<el-table-column label="规格" min-width="90" show-overflow-tooltip>
|
||||
<template #default="{ row }">{{ resolveCellProductSpecDisplay(row) || '—' }}</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
</div>
|
||||
<template #footer>
|
||||
@@ -410,6 +414,7 @@
|
||||
import type { OutboundReleaseCellRow } from '@/api/outbound-release-review'
|
||||
import { downloadAttachmentFile } from '@/utils/download-attachment'
|
||||
import { cellProductStatusLabel, cellProductStatusTagType } from '@/utils/status-tag-registry'
|
||||
import { resolveCellProductSpecDisplay } from '@/utils/cell-product-spec'
|
||||
|
||||
const props = withDefaults(
|
||||
defineProps<{
|
||||
@@ -728,7 +733,7 @@
|
||||
productCode: row.productCode || '',
|
||||
productName: row.productName || '',
|
||||
productTypeName: row.productTypeName || '',
|
||||
specName: row.specName || '',
|
||||
specName: resolveCellProductSpecDisplay(row),
|
||||
passage: row.passage || '',
|
||||
batchNo: row.batchNo || '',
|
||||
storageSlotName: row.storageSlotName || '',
|
||||
|
||||
@@ -37,7 +37,9 @@
|
||||
min-width="90"
|
||||
show-overflow-tooltip
|
||||
/>
|
||||
<el-table-column prop="specName" label="规格" min-width="90" show-overflow-tooltip />
|
||||
<el-table-column label="规格" min-width="90" show-overflow-tooltip>
|
||||
<template #default="{ row }">{{ resolveCellProductSpecDisplay(row) || '—' }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column
|
||||
prop="storageSlotName"
|
||||
label="储物格"
|
||||
@@ -224,6 +226,7 @@
|
||||
outboundMaterialCellStatusTagType
|
||||
} from '@/utils/status-tag-registry'
|
||||
import { downloadAttachmentFile } from '@/utils/download-attachment'
|
||||
import { resolveCellProductSpecDisplay } from '@/utils/cell-product-spec'
|
||||
|
||||
defineOptions({ name: 'OutboundMaterialReadonlyPanel' })
|
||||
|
||||
|
||||
@@ -49,9 +49,10 @@
|
||||
<template #default="{ row }">{{ row.productTypeName || '—' }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="规格" min-width="80" align="center" header-align="center">
|
||||
<template #default="{ row }">{{ row.specName || '—' }}</template>
|
||||
<template #default="{ row }">{{ resolveCellProductSpecDisplay(row) || '—' }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column
|
||||
v-if="viewOutputsNeedPassage"
|
||||
prop="passage"
|
||||
label="代次"
|
||||
min-width="70"
|
||||
@@ -70,12 +71,14 @@
|
||||
<template #default="{ row }">{{ row.storageSlotName || '—' }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column
|
||||
label="预计细胞数量(亿)"
|
||||
:label="EXPECTED_CELL_COUNT_LABEL"
|
||||
min-width="130"
|
||||
align="center"
|
||||
header-align="center"
|
||||
>
|
||||
<template #default="{ row }">{{ row.expectedCellCount ?? '—' }}</template>
|
||||
<template #default="{ row }">{{
|
||||
formatExpectedCellCountDisplay(row.expectedCellCount)
|
||||
}}</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="活率(%)" min-width="80" align="center" header-align="center">
|
||||
<template #default="{ row }">{{
|
||||
@@ -120,6 +123,12 @@
|
||||
import { fetchProductionTaskDetail } from '@/api/production-task'
|
||||
import { productionConclusionLabel, productionConclusionTagType } from '@/utils/production-labels'
|
||||
import { cellProductStatusLabel, cellProductStatusTagType } from '@/utils/status-tag-registry'
|
||||
import {
|
||||
EXPECTED_CELL_COUNT_LABEL,
|
||||
formatExpectedCellCountDisplay
|
||||
} from '@/utils/expected-cell-count'
|
||||
import { resolveCellProductSpecDisplay } from '@/utils/cell-product-spec'
|
||||
import { cellProductTypeNeedsPassage } from '@/utils/cell-product-type'
|
||||
|
||||
const props = defineProps<{
|
||||
visible: boolean
|
||||
@@ -201,10 +210,17 @@
|
||||
return rows.map((row) => ({
|
||||
...row,
|
||||
productCode: row.productCode || row.outputBarcode || '',
|
||||
productName: row.productName || row.outputName || ''
|
||||
productName: row.productName || row.outputName || '',
|
||||
specName: resolveCellProductSpecDisplay(row)
|
||||
}))
|
||||
})
|
||||
|
||||
const viewOutputsNeedPassage = computed(() =>
|
||||
viewOutputs.value.some((row) =>
|
||||
cellProductTypeNeedsPassage(row.productTypeName, row.productTypeCode)
|
||||
)
|
||||
)
|
||||
|
||||
const viewSections = computed<ViewSection[]>(() => {
|
||||
const d = viewDetail.value
|
||||
if (!d) return []
|
||||
|
||||
@@ -101,7 +101,7 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { computed } from 'vue'
|
||||
import { computed, onMounted } from 'vue'
|
||||
import ProtocolAttachmentReadonly from '@/views/cell-storage/protocol/ProtocolAttachmentReadonly.vue'
|
||||
import { maskIdCard } from '@/utils/mask-sensitive'
|
||||
import {
|
||||
@@ -111,6 +111,7 @@
|
||||
payStatusLabel,
|
||||
payStatusTagType,
|
||||
protocolStatusLabel,
|
||||
loadTransferTypeDict,
|
||||
transferTypeLabel
|
||||
} from '@/utils/protocol-payment-labels'
|
||||
|
||||
@@ -184,6 +185,10 @@
|
||||
refundAttachmentFiles.value.length > 0
|
||||
)
|
||||
})
|
||||
|
||||
onMounted(() => {
|
||||
void loadTransferTypeDict()
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
|
||||
@@ -121,6 +121,12 @@
|
||||
qcPreviewAttachment,
|
||||
qcDownloadAttachment
|
||||
} from '@/views/quality-inspection/qc-work-order/composables/useQcViewAttachmentHelpers'
|
||||
import { resolveCellProductSpecDisplay } from '@/utils/cell-product-spec'
|
||||
import {
|
||||
buildProductionReceptionViewSection,
|
||||
buildQcReceptionViewFields
|
||||
} from '@/utils/sample-reception-view-sections'
|
||||
import type { ProductionReceptionRecord } from '@/utils/sample-production-reception-fields'
|
||||
|
||||
const props = defineProps<{
|
||||
visible: boolean
|
||||
@@ -338,7 +344,7 @@
|
||||
{ label: '产品名称', value: d.cellProductName || '—' },
|
||||
{ label: '产品类型', value: d.cellProductTypeName || '—' },
|
||||
{ label: '代次', value: d.cellProductPassage || '—' },
|
||||
{ label: '规格', value: d.cellProductSpecName || '—' }
|
||||
{ label: '规格', value: resolveCellProductSpecDisplay(d) || '—' }
|
||||
]
|
||||
}
|
||||
]
|
||||
@@ -366,34 +372,19 @@
|
||||
? [{ title: '运输信息', fields: transportFields }]
|
||||
: []),
|
||||
{
|
||||
title: '样本接收',
|
||||
title: '质检接收',
|
||||
slot: 'reception-extra',
|
||||
fields: [
|
||||
{ label: '接收时间', value: r.receptionTime || d.receptionTime || '—' },
|
||||
{ label: '接收经手人', value: r.handlerName || d.handlerName || '—' },
|
||||
...(String(r.acceptConclusion || '') === 'js'
|
||||
? [
|
||||
{
|
||||
label: '实验室入库编码',
|
||||
value: r.labStorageCode || d.labStorageCode || '—'
|
||||
}
|
||||
]
|
||||
: []),
|
||||
{
|
||||
label: '接收温度(℃)',
|
||||
value: r.receiveTemperature ? `${r.receiveTemperature}℃` : '—'
|
||||
},
|
||||
{ label: '体积(ml)', value: formatMetric(r.volumeMl) },
|
||||
{ label: '净重(g)', value: formatMetric(r.netWeightG) },
|
||||
{
|
||||
label: '结论',
|
||||
value:
|
||||
r.acceptConclusionLabel || acceptConclusionLabel(String(r.acceptConclusion || ''))
|
||||
},
|
||||
{ label: '检查项', checkItems: r.checkResults ?? [], span: true },
|
||||
{ label: '备注', value: r.remark || '—', span: true }
|
||||
]
|
||||
}
|
||||
fields: buildQcReceptionViewFields(
|
||||
r as Record<string, unknown>,
|
||||
d as unknown as Record<string, unknown>
|
||||
)
|
||||
},
|
||||
buildProductionReceptionViewSection(
|
||||
(r.productionReception as ProductionReceptionRecord) ||
|
||||
(d as { productionReception?: ProductionReceptionRecord }).productionReception,
|
||||
String(r.sourceType || d.receptionSource || 'cell_storage'),
|
||||
String(r.sampleTypeName || d.sampleTypeName || '')
|
||||
)
|
||||
]
|
||||
const taskResultSummaryFields: ViewSection['fields'] = [
|
||||
{
|
||||
|
||||
@@ -0,0 +1,259 @@
|
||||
<!-- 质检 / 生产样本接收信息只读双区块 -->
|
||||
<template>
|
||||
<div class="sample-reception-dual-readonly">
|
||||
<div v-if="showQc" class="sample-reception-dual-readonly__block">
|
||||
<h4 v-if="qcTitle" class="detail-section-title">{{ qcTitle }}</h4>
|
||||
<div v-if="!qcReception" class="sample-reception-dual-readonly__empty">{{ qcEmptyHint }}</div>
|
||||
<template v-else>
|
||||
<div class="readonly-grid form-grid-2">
|
||||
<div class="readonly-item">
|
||||
<span class="label">接收时间</span>{{ qcReception.receptionTime || '—' }}
|
||||
</div>
|
||||
<div class="readonly-item">
|
||||
<span class="label">经手人</span>{{ qcReception.handlerName || '—' }}
|
||||
</div>
|
||||
<div class="readonly-item">
|
||||
<span class="label">接收温度(℃)</span
|
||||
>{{
|
||||
qcReception.receiveTemperature
|
||||
? `${qcReception.receiveTemperature}℃`
|
||||
: '—'
|
||||
}}
|
||||
</div>
|
||||
<div class="readonly-item">
|
||||
<span class="label">结论</span>
|
||||
<el-tag :type="acceptTagType(qcReception.acceptConclusion)" size="small">
|
||||
{{ acceptLabel(qcReception) }}
|
||||
</el-tag>
|
||||
</div>
|
||||
<div
|
||||
v-if="String(qcReception.acceptConclusion || '') === 'js'"
|
||||
class="readonly-item"
|
||||
>
|
||||
<span class="label">实验室入库编码</span>{{ qcReception.labStorageCode || '—' }}
|
||||
</div>
|
||||
<div class="readonly-item form-col-span-2 check-items-readonly">
|
||||
<span class="label">检查项</span>
|
||||
<div v-if="qcReception.checkResults?.length" class="check-list check-list-view">
|
||||
<el-checkbox
|
||||
v-for="chk in qcReception.checkResults"
|
||||
:key="String(chk.itemCode || chk.itemName)"
|
||||
:model-value="Boolean(chk.checked)"
|
||||
disabled
|
||||
>
|
||||
{{ chk.itemName }}
|
||||
</el-checkbox>
|
||||
</div>
|
||||
<span v-else>—</span>
|
||||
</div>
|
||||
<div class="readonly-item form-col-span-2">
|
||||
<span class="label">备注</span>{{ qcReception.remark || '—' }}
|
||||
</div>
|
||||
<div class="readonly-item form-col-span-2">
|
||||
<span class="label">接收附件</span>
|
||||
<ProtocolAttachmentReadonly
|
||||
:files="qcAttachmentFiles"
|
||||
@preview="handleAttachmentPreview"
|
||||
@download="handleAttachmentDownload"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
|
||||
<div v-if="showProduction" class="sample-reception-dual-readonly__block">
|
||||
<h4 v-if="productionTitle" class="detail-section-title">{{ productionTitle }}</h4>
|
||||
<div v-if="!productionReception" class="sample-reception-dual-readonly__empty">
|
||||
{{ productionEmptyHint }}
|
||||
</div>
|
||||
<div
|
||||
v-else
|
||||
class="readonly-grid"
|
||||
:class="productionGridCols === 4 ? 'form-grid-4' : 'form-grid-2'"
|
||||
>
|
||||
<div class="readonly-item">
|
||||
<span class="label">处理时间</span>{{ productionReception.processTime || '—' }}
|
||||
</div>
|
||||
<div class="readonly-item">
|
||||
<span class="label">处理人</span>{{ productionReception.handlerName || '—' }}
|
||||
</div>
|
||||
<div
|
||||
v-for="field in productionFieldDefs"
|
||||
:key="field.key"
|
||||
class="readonly-item"
|
||||
>
|
||||
<span class="label">{{ field.label }}</span
|
||||
>{{ formatProductionFieldValue(field, productionReception) }}
|
||||
</div>
|
||||
<div class="readonly-item form-col-span-2">
|
||||
<span class="label">备注</span>{{ productionReception.remark || '—' }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { computed } from 'vue'
|
||||
import ProtocolAttachmentReadonly from '@/views/cell-storage/protocol/ProtocolAttachmentReadonly.vue'
|
||||
import { acceptConclusionLabel } from '@/utils/qc-labels'
|
||||
import { downloadAttachmentFile } from '@/utils/download-attachment'
|
||||
import {
|
||||
formatProductionFieldValue,
|
||||
resolveProductionReceptionFields,
|
||||
type ProductionReceptionRecord,
|
||||
type QcReceptionRecord
|
||||
} from '@/utils/sample-production-reception-fields'
|
||||
|
||||
defineOptions({ name: 'SampleReceptionDualReadonlyPanel' })
|
||||
|
||||
const props = withDefaults(
|
||||
defineProps<{
|
||||
qcReception?: QcReceptionRecord | null
|
||||
productionReception?: ProductionReceptionRecord | null
|
||||
sourceType?: string
|
||||
sampleTypeName?: string
|
||||
showQc?: boolean
|
||||
showProduction?: boolean
|
||||
qcTitle?: string
|
||||
productionTitle?: string
|
||||
productionGridCols?: 2 | 4
|
||||
qcEmptyHint?: string
|
||||
productionEmptyHint?: string
|
||||
}>(),
|
||||
{
|
||||
showQc: true,
|
||||
showProduction: true,
|
||||
qcTitle: '质检接收信息',
|
||||
productionTitle: '生产接收信息',
|
||||
productionGridCols: 2,
|
||||
qcEmptyHint: '暂未接收',
|
||||
productionEmptyHint: '生产尚未登记'
|
||||
}
|
||||
)
|
||||
|
||||
const productionFieldDefs = computed(() =>
|
||||
resolveProductionReceptionFields(
|
||||
props.sourceType || 'cell_storage',
|
||||
props.sampleTypeName || ''
|
||||
)
|
||||
)
|
||||
|
||||
function acceptLabel(rec: QcReceptionRecord) {
|
||||
return (
|
||||
rec.acceptConclusionLabel ||
|
||||
acceptConclusionLabel(String(rec.acceptConclusion || '')) ||
|
||||
'—'
|
||||
)
|
||||
}
|
||||
|
||||
function acceptTagType(conclusion?: string) {
|
||||
if (conclusion === 'js') return 'success'
|
||||
if (conclusion === 'bj') return 'danger'
|
||||
return 'warning'
|
||||
}
|
||||
|
||||
const minioBaseUrl = (import.meta.env.VITE_MINIO_BASE_URL || '').replace(/\/$/, '')
|
||||
|
||||
const qcAttachmentFiles = computed(() => {
|
||||
const rec = props.qcReception
|
||||
if (!rec) return []
|
||||
if (Array.isArray(rec.receiveAttachmentFiles)) {
|
||||
return rec.receiveAttachmentFiles as Array<Record<string, unknown>>
|
||||
}
|
||||
return []
|
||||
})
|
||||
|
||||
function fullFileUrl(url: string) {
|
||||
if (!url) return ''
|
||||
if (/^https?:\/\//i.test(url)) return url
|
||||
return minioBaseUrl ? `${minioBaseUrl}/${url.replace(/^\//, '')}` : url
|
||||
}
|
||||
|
||||
function handleAttachmentPreview(file: Record<string, unknown>) {
|
||||
const url = String(file.fileUrl || '')
|
||||
if (!url) return
|
||||
window.open(fullFileUrl(url), '_blank')
|
||||
}
|
||||
|
||||
function handleAttachmentDownload(file: Record<string, unknown>) {
|
||||
downloadAttachmentFile(file, { fullFileUrl })
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.sample-reception-dual-readonly__block + .sample-reception-dual-readonly__block {
|
||||
margin-top: 16px;
|
||||
padding-top: 16px;
|
||||
border-top: 1px dashed var(--el-border-color-lighter, #e2e8f0);
|
||||
}
|
||||
|
||||
.sample-reception-dual-readonly__empty {
|
||||
color: var(--mint-text-muted, #64748b);
|
||||
font-size: 13px;
|
||||
padding: 4px 0 8px;
|
||||
}
|
||||
|
||||
.readonly-item {
|
||||
font-size: 14px;
|
||||
line-height: 1.5;
|
||||
|
||||
.label {
|
||||
display: inline-block;
|
||||
min-width: 72px;
|
||||
margin-right: 0;
|
||||
color: var(--mint-text-muted, #64748b);
|
||||
|
||||
&::after {
|
||||
content: ': ';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.check-items-readonly {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 8px;
|
||||
|
||||
.label {
|
||||
margin-right: 0;
|
||||
}
|
||||
|
||||
.check-list {
|
||||
padding-left: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.check-list-view {
|
||||
pointer-events: none;
|
||||
|
||||
:deep(.el-checkbox.is-disabled) {
|
||||
opacity: 1;
|
||||
cursor: default;
|
||||
|
||||
.el-checkbox__label {
|
||||
color: var(--el-text-color-regular);
|
||||
}
|
||||
|
||||
.el-checkbox__input.is-disabled {
|
||||
opacity: 1;
|
||||
cursor: default;
|
||||
|
||||
.el-checkbox__inner {
|
||||
background-color: var(--el-checkbox-bg-color);
|
||||
border-color: var(--el-checkbox-input-border-color);
|
||||
cursor: default;
|
||||
}
|
||||
|
||||
&.is-checked .el-checkbox__inner {
|
||||
background-color: var(--el-checkbox-checked-bg-color);
|
||||
border-color: var(--el-checkbox-checked-input-border-color);
|
||||
|
||||
&::after {
|
||||
border-color: var(--el-checkbox-checked-icon-color);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
Reference in new issue
Block a user