This commit is contained in:
zlwu committed 2026-05-26 21:27:48 +08:00
1 parent edf88dc016
commit 3f122da803
55 files changed
+13486 -8504

No files matched your search

@@ -47,9 +47,7 @@
</el-form-item>
</div>
<div class="filter-actions-row">
<span class="filter-hint"
>默认展示近 30 天内已开始的任务;清空日期可查看尚未开始的待配置任务。</span
>
<span class="filter-hint">可按任务开始时间筛选;不选日期则展示全部任务。</span>
<div class="filter-actions-end">
<el-button class="btn-ghost" @click="handleReset"
><el-icon><RefreshLeft /></el-icon>重置</el-button
@@ -81,8 +79,8 @@
<el-table-column prop="workOrderNo" label="所属工单号" min-width="140" align="center" />
<el-table-column prop="orderSource" label="来源" min-width="100" align="center">
<template #default="{ row }">
<el-tag :type="row.orderSource === 'temporary' ? 'warning' : 'success'" size="small">
{{ row.orderSource === 'temporary' ? '临时' : '入库' }}
<el-tag :type="productionOrderSourceTag(row.orderSource).type" size="small">
{{ productionOrderSourceTag(row.orderSource).label }}
</el-tag>
</template>
</el-table-column>
@@ -118,9 +116,9 @@
</el-table-column>
<el-table-column prop="taskType" label="任务类型" min-width="120" align="center">
<template #default="{ row }">
<el-tag :type="taskTypeTag(row.taskType, row.orderSource).type" size="small">{{
taskTypeTag(row.taskType, row.orderSource).label
}}</el-tag>
<el-tag :type="productionTaskTypeTag(row.taskType, row.orderSource).type" size="small">
{{ productionTaskTypeTag(row.taskType, row.orderSource).label }}
</el-tag>
</template>
</el-table-column>
<el-table-column
@@ -325,7 +323,12 @@
import TaskExecutionDialog from '../production-work-order/TaskExecutionDialog.vue'
import DispatchTaskDialog from '../production-work-order/DispatchTaskDialog.vue'
import EditTaskInfoDialog from '../production-work-order/EditTaskInfoDialog.vue'
import { productionConclusionLabel, productionConclusionTagType } from '@/utils/production-labels'
import {
productionConclusionLabel,
productionConclusionTagType,
productionOrderSourceTag,
productionTaskTypeTag
} from '@/utils/production-labels'
/** 根据来源类型返回协议状态标签 */
function protocolStatusLabelBySource(status: string, source: string): string {
@@ -407,36 +410,11 @@
return map[status] || { label: status, type: 'info' }
}
function taskTypeTag(taskType: string, orderSource?: string) {
if (orderSource === 'temporary') {
return { label: '临时生产单', type: 'warning' }
}
const map: Record<string, { label: string; type: string }> = {
cell_storage: { label: '入库生产单', type: 'primary' },
outbound: { label: '出库生产单', type: 'warning' }
}
return map[taskType] || { label: taskType, type: 'info' }
}
// ---------------------------------------------------------------------------
// 列表视图
// ---------------------------------------------------------------------------
function formatLocalDate(d: Date): string {
const y = d.getFullYear()
const m = String(d.getMonth() + 1).padStart(2, '0')
const day = String(d.getDate()).padStart(2, '0')
return `${y}-${m}-${day}`
}
function getDefaultLast30DaysRange(): [string, string] {
const end = new Date()
const start = new Date()
start.setDate(start.getDate() - 30)
return [formatLocalDate(start), formatLocalDate(end)]
}
const searchForm = reactive({ keyword: '', taskStatus: '' })
const startedTimeRange = ref<[string, string] | null>(getDefaultLast30DaysRange())
const startedTimeRange = ref<[string, string] | null>(null)
const tableData = ref<Record<string, any>[]>([])
const loading = ref(false)
const pagination = reactive({ page: 1, pageSize: 10, total: 0 })
@@ -474,7 +452,7 @@
function handleReset() {
searchForm.keyword = ''
searchForm.taskStatus = ''
startedTimeRange.value = getDefaultLast30DaysRange()
startedTimeRange.value = null
pagination.page = 1
loadData()
}
@@ -199,6 +199,7 @@
import { Rank, Plus, CopyDocument, Delete } from '@element-plus/icons-vue'
import { VueDraggable as draggable } from 'vue-draggable-plus'
import { fetchCreateProductionTask } from '@/api/production-task'
import { productionTaskTypeForWorkOrder } from '@/utils/production-labels'
import { fetchProcessRouteList } from '@/api/production'
import type { FormInstance, FormRules } from 'element-plus'
import { useTaskStepEditor } from './useTaskStepEditor'
@@ -264,7 +265,7 @@
try {
const task = await fetchCreateProductionTask({
work_order_id: props.workOrderRow.id,
task_type: 'cell_storage',
task_type: productionTaskTypeForWorkOrder(props.workOrderRow.workOrderType),
task_name: form.taskName,
route_id: form.routeId,
description: form.description || undefined,
@@ -145,24 +145,6 @@
>
<template #default="{ row }">{{ row.applicationNo || '—' }}</template>
</el-table-column>
<el-table-column
v-if="activeTypeTab === 'outbound'"
prop="cellType"
label="细胞类型"
min-width="110"
align="center"
>
<template #default="{ row }">{{ row.cellType || '—' }}</template>
</el-table-column>
<el-table-column
v-if="activeTypeTab === 'outbound'"
prop="specText"
label="规格"
min-width="110"
align="center"
>
<template #default="{ row }">{{ row.specText || '—' }}</template>
</el-table-column>
<el-table-column
v-if="activeTypeTab === 'outbound'"
prop="infusionTime"
@@ -332,10 +314,72 @@
<SystemViewDialog
v-model:visible="viewVisible"
title="工单详情"
:width="viewDialogWidth"
:grid-columns="viewDialogGridColumns"
:compact="viewDialogCompact"
:icon="Document"
:meta="detail?.workOrderNo || ''"
:sections="viewSections"
>
<template #diagnosis-attachments>
<ProtocolAttachmentReadonly
:files="viewDiagnosisAttachmentFiles"
@preview="handleReceptionAttachmentPreview"
@download="handleReceptionAttachmentDownload"
/>
</template>
<template #product-table>
<div v-if="detail?.products?.length" class="view-subtable-wrap">
<el-table
:data="detail?.products || []"
stripe
size="small"
class="list-table view-subtable"
>
<el-table-column type="index" label="序号" min-width="60" align="center" />
<el-table-column
prop="projectCode"
label="项目编码"
min-width="120"
align="center"
show-overflow-tooltip
/>
<el-table-column
prop="projectName"
label="项目名称"
min-width="120"
align="center"
show-overflow-tooltip
/>
<el-table-column
prop="cellTypeName"
label="细胞类型"
min-width="100"
align="center"
show-overflow-tooltip
/>
<el-table-column
prop="specText"
label="规格"
min-width="100"
align="center"
show-overflow-tooltip
/>
<el-table-column prop="quantity" label="数量" min-width="70" align="center" />
<el-table-column label="产品状态" min-width="100" align="center">
<template #default="{ row }">
<el-tag
:type="productStatusTagType(row.productStatus || '') as any"
size="small"
>
{{ productStatusLabel(row.productStatus || '') }}
</el-tag>
</template>
</el-table-column>
</el-table>
</div>
<p v-else class="view-empty-hint">暂无产品明细</p>
</template>
<template #reception-extra>
<div class="view-reception-attachments">
<div class="view-kv view-kv-span-2">
@@ -548,8 +592,12 @@
protocolStatusLabel,
protocolStatusTagType,
donationStatusLabel,
donationStatusTagType
donationStatusTagType,
productStatusLabel,
productStatusTagType
} from '@/utils/status-tag-registry'
import { buildOutboundPilotContextSections } from '@/utils/outbound-qc-pilot-view-sections'
import type { OutboundQcDetail } from '@/api/qc-work-order'
import type { TagType } from '@/utils/status-tag-registry'
/** 根据来源类型返回协议状态标签 */
@@ -767,6 +815,15 @@
const viewVisible = ref(false)
const detail = ref<ProductionWorkOrderDetail | null>(null)
const isOutboundPilotService = computed(
() =>
detail.value?.workOrderType === 'outbound' && detail.value?.bizType === 'pilot_service'
)
const viewDialogWidth = computed(() => (isOutboundPilotService.value ? '1200px' : '720px'))
const viewDialogGridColumns = computed<2 | 3 | 4>(() =>
isOutboundPilotService.value ? 4 : 2
)
const viewDialogCompact = computed(() => isOutboundPilotService.value)
const minioBaseUrl = (import.meta.env.VITE_MINIO_BASE_URL || '').replace(/\/$/, '')
const viewReceptionAttachmentFiles = computed(() => {
@@ -799,6 +856,21 @@
function handleReceptionAttachmentDownload(file: Record<string, unknown>) {
downloadAttachmentFile(file, { fullFileUrl: receptionAttachmentFullUrl })
}
const viewDiagnosisAttachmentFiles = computed(() => {
const urls = detail.value?.patient?.diagnosisAttachments || ''
if (!urls.trim()) return []
return urls
.split(',')
.map((u, i) => {
const url = u.trim()
if (!url) return null
const name = url.includes('/') ? url.split('/').pop() || `附件${i + 1}` : url
return { id: `url_${i}`, fileUrl: receptionAttachmentFullUrl(url), fileName: name }
})
.filter(Boolean) as Array<Record<string, unknown>>
})
function formatMetric(v: unknown) {
if (v === null || v === undefined || v === '') return '—'
return String(v)
@@ -880,50 +952,62 @@
const sections: ViewSection[] = [{ title: '基本信息', fields: baseFields }]
// outbound: 申请单 + 输注计划
// outbound: 中试服务 — 与出库质检工单查看弹窗一致;临床应用 — 保留简要申请/输注信息
if (isOutbound) {
const pa = d.pilotApplication || {}
const sched = d.infusionSchedule || {}
const appStatusLabel = (s: string) => {
const m: Record<string, string> = {
dks: '待开始',
jxz: '进行中',
ywc: '已完成',
yzf: '已作废'
if (d.bizType === 'pilot_service') {
const pilotDetail: OutboundQcDetail = {
workOrder: {} as OutboundQcDetail['workOrder'],
application: d.application,
patient: d.patient,
infusionPlan: d.infusionPlan,
infusionSchedule: d.infusionSchedule,
products: d.products
}
return m[s] || s || '—'
}
const appStatusTagType = (s: string) => {
const m: Record<string, string> = {
dks: 'info',
jxz: 'warning',
ywc: 'success',
yzf: 'danger'
}
return m[s] || 'info'
}
sections.push({
title: '中试申请信息',
fields: [
{ label: '申请单号', value: pa.applicationNo || '—' },
{ label: '申请名称', value: pa.applicationName || '—' },
{
label: '申请状态',
value: appStatusLabel(pa.applicationStatus),
tagType: appStatusTagType(pa.applicationStatus) as any
sections.push(...buildOutboundPilotContextSections(pilotDetail))
} else {
const pa = d.pilotApplication || {}
const sched = d.infusionSchedule || {}
const appStatusLabel = (s: string) => {
const m: Record<string, string> = {
dks: '待开始',
jxz: '进行中',
ywc: '已完成',
yzf: '已作废'
}
]
})
sections.push({
title: '输注计划信息',
fields: [
{ label: '细胞类型', value: sched.cellType || '—' },
{ label: '规格', value: sched.specText || '—' },
{ label: '输注时间', value: sched.infusionTime || '—' },
{ label: '输注地点', value: sched.infusionLocation || '—' },
{ label: '输注方式', value: sched.infusionMethod || '—' }
]
})
return m[s] || s || '—'
}
const appStatusTagType = (s: string) => {
const m: Record<string, string> = {
dks: 'info',
jxz: 'warning',
ywc: 'success',
yzf: 'danger'
}
return m[s] || 'info'
}
sections.push({
title: '中试申请信息',
fields: [
{ label: '申请单号', value: pa.applicationNo || '—' },
{ label: '申请名称', value: pa.applicationName || '—' },
{
label: '申请状态',
value: appStatusLabel(pa.applicationStatus || ''),
tagType: appStatusTagType(pa.applicationStatus || '') as any
}
]
})
sections.push({
title: '输注计划信息',
fields: [
{ label: '细胞类型', value: sched.cellType || '—' },
{ label: '规格', value: sched.specText || '—' },
{ label: '输注时间', value: sched.infusionTime || '—' },
{ label: '输注地点', value: sched.infusionLocation || '—' },
{ label: '输注方式', value: sched.infusionMethod || '—' }
]
})
}
return sections
}
@@ -99,8 +99,23 @@
<el-table-column prop="workOrderNo" label="工单号" min-width="140" align="center" />
<el-table-column prop="orderSource" label="来源" min-width="100" align="center">
<template #default="{ row }">
<el-tag :type="row.orderSource === 'temporary' ? 'warning' : 'success'" size="small">
{{ row.orderSource === 'temporary' ? '临时' : '入库' }}
<el-tag
:type="
row.orderSource === 'temporary'
? 'warning'
: row.orderSource === 'outbound'
? 'primary'
: 'success'
"
size="small"
>
{{
row.orderSource === 'temporary'
? '临时'
: row.orderSource === 'outbound'
? '出库'
: '入库'
}}
</el-tag>
</template>
</el-table-column>
@@ -390,12 +405,73 @@
<SystemViewDialog
v-model:visible="viewVisible"
title="任务详情"
width="960px"
:grid-columns="3"
:width="viewDialogWidth"
:grid-columns="viewDialogGridColumns"
:compact="viewDialogCompact"
:loading="viewLoading"
:icon="Tickets"
:meta="viewDetail?.taskNo || ''"
:sections="viewSections"
>
<template #diagnosis-attachments>
<ProtocolAttachmentReadonly
:files="viewDiagnosisAttachmentFiles"
@preview="handleViewAttachmentPreview"
@download="handleViewAttachmentDownload"
/>
</template>
<template #product-table>
<div v-if="viewOutboundDetail?.products?.length" class="view-subtable-wrap">
<el-table
:data="viewOutboundDetail?.products || []"
stripe
size="small"
class="list-table view-subtable"
>
<el-table-column type="index" label="序号" min-width="60" align="center" />
<el-table-column
prop="projectCode"
label="项目编码"
min-width="120"
align="center"
show-overflow-tooltip
/>
<el-table-column
prop="projectName"
label="项目名称"
min-width="120"
align="center"
show-overflow-tooltip
/>
<el-table-column
prop="cellTypeName"
label="细胞类型"
min-width="100"
align="center"
show-overflow-tooltip
/>
<el-table-column
prop="specText"
label="规格"
min-width="100"
align="center"
show-overflow-tooltip
/>
<el-table-column prop="quantity" label="数量" min-width="70" align="center" />
<el-table-column label="产品状态" min-width="100" align="center">
<template #default="{ row }">
<el-tag
:type="productStatusTagType(row.productStatus || '') as any"
size="small"
>
{{ productStatusLabel(row.productStatus || '') }}
</el-tag>
</template>
</el-table-column>
</el-table>
</div>
<p v-else class="view-empty-hint">暂无产品明细</p>
</template>
<template #reception-extra>
<div class="view-reception-attachments">
<div class="view-kv view-kv-span-2">
@@ -496,6 +572,7 @@
type QcTask,
type QcTaskDetail
} from '@/api/qc-task'
import { fetchOutboundQcDetail, type OutboundQcDetail } from '@/api/qc-work-order'
import {
fetchQcTaskResults,
fetchQcTaskMaterialUsages,
@@ -519,8 +596,14 @@
protocolStatusLabel,
protocolStatusTagType,
donationStatusLabel,
donationStatusTagType
donationStatusTagType,
productStatusLabel,
productStatusTagType
} from '@/utils/status-tag-registry'
import {
buildOutboundPilotContextSections,
isOutboundPilotServiceQcTask
} from '@/utils/outbound-qc-pilot-view-sections'
import type { TagType } from '@/utils/status-tag-registry'
/** 根据来源类型返回协议状态标签 */
@@ -762,8 +845,17 @@
}
const viewVisible = ref(false)
const viewLoading = ref(false)
const viewDetail = ref<QcTaskDetail | null>(null)
const viewOutboundDetail = ref<OutboundQcDetail | null>(null)
const viewMaterialUsages = ref<MaterialUsageRow[]>([])
const viewDialogWidth = computed(() =>
isOutboundPilotServiceQcTask(viewDetail.value) ? '1200px' : '960px'
)
const viewDialogGridColumns = computed<2 | 3 | 4>(() =>
isOutboundPilotServiceQcTask(viewDetail.value) ? 4 : 3
)
const viewDialogCompact = computed(() => isOutboundPilotServiceQcTask(viewDetail.value))
const minioBaseUrl = (import.meta.env.VITE_MINIO_BASE_URL || '').replace(/\/$/, '')
function apiFilesToFinanceItems(
@@ -794,6 +886,20 @@
downloadAttachmentFile(file, { fullFileUrl: attachmentFullUrl })
}
const viewDiagnosisAttachmentFiles = computed(() => {
const urls = viewOutboundDetail.value?.patient?.diagnosisAttachments || ''
if (!urls.trim()) return []
return urls
.split(',')
.map((u, i) => {
const url = u.trim()
if (!url) return null
const name = url.includes('/') ? url.split('/').pop() || `附件${i + 1}` : url
return { id: `url_${i}`, fileUrl: attachmentFullUrl(url), fileName: name }
})
.filter(Boolean) as Array<Record<string, unknown>>
})
const viewReceptionAttachmentFiles = computed(() => {
const r = viewDetail.value?.reception
if (!r) return []
@@ -857,6 +963,7 @@
const viewSections = computed<ViewSection[]>(() => {
const d = viewDetail.value
if (!d) return []
const isOutboundPilot = isOutboundPilotServiceQcTask(d)
const r = d.reception || {}
const isTempQc = d.qcBizType === 'temporary'
const transportFields: ViewSection['fields'] = [
@@ -890,7 +997,11 @@
}
]
: []
const sampleSections: ViewSection[] = isTempQc
const outboundPilotSections: ViewSection[] =
isOutboundPilot && viewOutboundDetail.value
? buildOutboundPilotContextSections(viewOutboundDetail.value)
: []
const sampleSections: ViewSection[] = isTempQc || isOutboundPilot
? []
: [
{
@@ -1018,6 +1129,7 @@
]
},
...cellProductSection,
...outboundPilotSections,
...sampleSections,
...resultSection,
...tailSections
@@ -1069,29 +1181,43 @@
}
async function handleView(row: QcTask) {
const detail = await fetchQcTaskDetail(row.id)
try {
const results = await fetchQcTaskResults(row.id)
;(detail as any).results = results || []
} catch {
;(detail as any).results = []
}
viewMaterialUsages.value = []
if (row.taskType !== 'dsfjc') {
try {
const [savedUsages, opts] = await Promise.all([
fetchQcTaskMaterialUsages(row.id).catch(() => [] as QcTaskMaterialUsageItem[]),
fetchQcMaterialOptions().catch(() => [] as QcMaterialOption[])
])
const rows = materialRowsFromApi(savedUsages || [])
enrichMaterialUsageRows(rows, opts || [])
viewMaterialUsages.value = rows
} catch {
viewMaterialUsages.value = []
}
}
viewDetail.value = detail
viewVisible.value = true
viewLoading.value = true
viewDetail.value = null
viewOutboundDetail.value = null
viewMaterialUsages.value = []
try {
const detail = await fetchQcTaskDetail(row.id)
try {
const results = await fetchQcTaskResults(row.id)
;(detail as any).results = results || []
} catch {
;(detail as any).results = []
}
if (row.taskType !== 'dsfjc') {
try {
const [savedUsages, opts] = await Promise.all([
fetchQcTaskMaterialUsages(row.id).catch(() => [] as QcTaskMaterialUsageItem[]),
fetchQcMaterialOptions().catch(() => [] as QcMaterialOption[])
])
const rows = materialRowsFromApi(savedUsages || [])
enrichMaterialUsageRows(rows, opts || [])
viewMaterialUsages.value = rows
} catch {
viewMaterialUsages.value = []
}
}
if (isOutboundPilotServiceQcTask(detail) && row.workOrderId) {
try {
viewOutboundDetail.value = await fetchOutboundQcDetail(row.workOrderId)
} catch {
viewOutboundDetail.value = null
}
}
viewDetail.value = detail
} finally {
viewLoading.value = false
}
}
async function openResult(row: QcTask) {
@@ -19,6 +19,23 @@
@download="handleDownload"
/>
</template>
<template #product-table>
<el-table :data="detail?.products || []" stripe size="small" class="outbound-product-table">
<el-table-column type="index" label="序号" min-width="60" align="center" />
<el-table-column prop="projectCode" label="项目编码" min-width="120" align="center" show-overflow-tooltip />
<el-table-column prop="projectName" label="项目名称" min-width="120" align="center" show-overflow-tooltip />
<el-table-column prop="cellTypeName" label="细胞类型" min-width="100" align="center" show-overflow-tooltip />
<el-table-column prop="specText" label="规格" min-width="100" align="center" show-overflow-tooltip />
<el-table-column prop="quantity" label="数量" min-width="70" align="center" />
<el-table-column label="产品状态" min-width="100" align="center">
<template #default="{ row }">
<el-tag :type="productStatusTagType(row.productStatus || '') as any" size="small">
{{ productStatusLabel(row.productStatus || '') }}
</el-tag>
</template>
</el-table-column>
</el-table>
</template>
<template v-for="task in tasksWithAttachments" #[taskAttachmentSlotName(task.id)]>
<div :key="task.id" class="outbound-qc-task-attachments">
<ProtocolAttachmentReadonly
@@ -45,25 +62,12 @@
taskStatusLabel,
qcTaskConclusionLabel
} from '@/utils/qc-labels'
import {
scheduleStatusLabel,
scheduleStatusTagType,
infusionStatusLabel,
infusionStatusTagType,
qcStatusLabel,
qcStatusTagType,
applicationStatusLabel,
applicationStatusTagType,
bizTypeLabel,
bizTypeTagType,
productStatusLabel,
productStatusTagType
} from '@/utils/status-tag-registry'
import { bizTypeLabel, bizTypeTagType, productStatusLabel, productStatusTagType } from '@/utils/status-tag-registry'
import { buildOutboundPilotContextSections } from '@/utils/outbound-qc-pilot-view-sections'
import SystemViewDialog from '@/components/business/system-view-dialog/index.vue'
import type { ViewField, ViewSection } from '@/components/business/system-view-dialog/index.vue'
import ProtocolAttachmentReadonly from '@/views/cell-storage/protocol/ProtocolAttachmentReadonly.vue'
import { downloadAttachmentFile } from '@/utils/download-attachment'
import { formatMoneyDisplay } from '@/utils/money-format'
defineOptions({ name: 'OutboundQcViewDialog' })
@@ -282,12 +286,6 @@
if (!detail.value) return []
const d = detail.value
const wo: Partial<QcWorkOrderDetail> = d.workOrder || {}
const app = d.application
const patient = d.patient
const plan = d.infusionPlan
const schedule = d.infusionSchedule
const products = d.products || []
const result: ViewSection[] = []
// 1. 工单基本信息
@@ -308,121 +306,15 @@
},
{ label: '检测方式', value: formatDetectMethods(wo) },
{ label: '配置时间', value: wo.configuredTime || '—' },
{ label: '配置人', value: wo.configuredBy || '—' },
{ label: '配置人', value: wo.configuredByName || wo.configuredBy || '—' },
{ label: '备注', value: wo.remark || '—', span: true },
{ label: '创建时间', value: wo.createTime || '—' }
]
})
// 2. 中试服务申请单信息
if (app) {
result.push({
title: '中试服务申请单信息',
fields: [
{ label: '申请单号', value: app.applicationNo || '—' },
{ label: '申请单名称', value: app.applicationName || '—' },
{
label: '申请单状态',
value: applicationStatusLabel(app.applicationStatus || ''),
tagType: applicationStatusTagType(app.applicationStatus || '') as ViewField['tagType']
},
{ label: '渠道名称', value: app.channelName || '—' }
]
})
}
result.push(...buildOutboundPilotContextSections(d))
// 3. 患者信息
if (patient) {
const patientFields: ViewField[] = [
{ label: '患者姓名', value: patient.patientName || '—' },
{ label: '性别', value: patient.sex || '—' },
{ label: '年龄', value: patient.age || '—' },
{ label: '联系电话', value: patient.phoneNo || '—' },
{ label: '身份证号', value: patient.idCardNo || '—' },
{ label: '紧急联系人', value: patient.emergencyContact || '—' },
{ label: '紧急联系电话', value: patient.emergencyPhone || '—' },
{ label: '临床诊断', value: patient.clinicalDiagnosis || '—', span: true }
]
result.push({
title: '患者信息',
fields: patientFields,
slotTitle: '诊断附件',
slotTitleStyle: 'field',
slot: 'diagnosis-attachments'
})
}
// 4. 输注方案信息
if (plan) {
result.push({
title: '输注方案信息',
fields: [
{ label: '细胞类型', value: plan.cellType || '—' },
{ label: '规格', value: plan.specText || '—' },
{ label: '总疗程数', value: plan.totalCourseCount != null ? String(plan.totalCourseCount) : '—' },
{ label: '疗程间隔', value: plan.courseInterval || '—' },
{ label: '输注途径', value: plan.infusionRoute || '—' },
{ label: '输注开始日期', value: plan.infusionStartDate || '—' }
]
})
}
// 5. 本次输注计划信息
if (schedule) {
const scheduleFields: ViewField[] = [
{ label: '输注时间', value: schedule.infusionTime || '—' },
{ label: '输注地点', value: schedule.infusionLocation || '—' },
{ label: '输注方式', value: schedule.infusionMethod || '—' },
{
label: '计划状态',
value: scheduleStatusLabel(schedule.scheduleStatus || ''),
tagType: scheduleStatusTagType(schedule.scheduleStatus || '') as ViewField['tagType']
},
{
label: '输注状态',
value: infusionStatusLabel(schedule.infusionStatus || ''),
tagType: infusionStatusTagType(schedule.infusionStatus || '') as ViewField['tagType']
},
{
label: '质检状态',
value: qcStatusLabel(schedule.qcStatus || ''),
tagType: qcStatusTagType(schedule.qcStatus || '') as ViewField['tagType']
}
]
if (schedule.cancelReason) {
scheduleFields.push({ label: '取消原因', value: schedule.cancelReason, span: true })
}
result.push({ title: '本次输注计划信息', fields: scheduleFields })
}
// 6. 产品明细
if (products.length) {
const productItems: string[] = products.map(
(p) =>
`${p.projectCode || '—'} / ${p.projectName || '—'} / ${p.cellTypeName || '—'} / ${p.specText || '—'} / 协议价:¥${p.agreementPrice != null ? formatMoneyDisplay(p.agreementPrice) : '—'} / 实际价:¥${p.actualPrice != null ? formatMoneyDisplay(p.actualPrice) : '—'} / 数量:${p.quantity ?? '—'} / ${productStatusLabel(p.productStatus || '')}`
)
result.push({
title: '产品明细',
fields: [{ label: '产品列表', value: '', span: true }],
slot: 'product-table'
})
// Override: we'll use items instead of a separate slot — use span field with items list
result[result.length - 1] = {
title: '产品明细',
fields: [
{
label: '产品列表',
span: true,
items: products.map(
(p) =>
`${p.projectCode || '—'} ${p.projectName || '—'} | ${p.cellTypeName || '—'} | ${p.specText || '—'} | 协议价 ¥${p.agreementPrice != null ? formatMoneyDisplay(p.agreementPrice) : '—'} | 实际价 ¥${p.actualPrice != null ? formatMoneyDisplay(p.actualPrice) : '—'} | 数量 ${p.quantity ?? '—'}`
)
}
]
}
}
// 7. 检测结果 — lab tasks
// 检测结果 — lab tasks
const labSections = buildTaskTypeSections(tasksWithResults.value, 'sysz', '实验室项目')
result.push(...labSections)
@@ -438,4 +330,8 @@
.outbound-qc-task-attachments {
margin-top: 0;
}
.outbound-product-table {
margin-top: 4px;
}
</style>
@@ -295,7 +295,7 @@
<template #default="{ row }">{{ formatTempEmptyField(row, row.sampleCode) }}</template>
</el-table-column>
<el-table-column
v-if="showReceptionCols"
v-if="showReceptionCols || activeTypeTab === 'outbound'"
prop="receptionSourceLabel"
label="来源类型"
min-width="100"
@@ -722,8 +722,8 @@
type WorkOrderTypeTab = 'inbound' | 'temporary' | 'outbound' | 'all'
const activeTypeTab = ref<WorkOrderTypeTab>('inbound')
const showReceptionCols = computed(() => activeTypeTab.value !== 'temporary')
const showTempProductCols = computed(() => activeTypeTab.value !== 'inbound')
const showReceptionCols = computed(() => activeTypeTab.value !== 'temporary' && activeTypeTab.value !== 'outbound')
const showTempProductCols = computed(() => activeTypeTab.value !== 'inbound' && activeTypeTab.value !== 'outbound')
const showOutboundCols = computed(() => activeTypeTab.value === 'outbound')
const addMode = ref<'inbound' | 'temporary'>('inbound')
const editingId = ref('')