From 6bdcd0a5656e3737f8953b6fe13a5282b254d101 Mon Sep 17 00:00:00 2001 From: zlwu <704541015@qq.com> Date: Tue, 26 May 2026 18:17:15 +0800 Subject: [PATCH] feat(production): add outbound work order tab, columns, and detail dialog with pilot application and infusion schedule sections --- .../production-work-order/index.vue | 409 ++++++++++++++---- 1 file changed, 322 insertions(+), 87 deletions(-) diff --git a/web-admin/src/views/production/production-work-order/index.vue b/web-admin/src/views/production/production-work-order/index.vue index 843507dba..aa29aa561 100644 --- a/web-admin/src/views/production/production-work-order/index.vue +++ b/web-admin/src/views/production/production-work-order/index.vue @@ -4,7 +4,9 @@

生产工单

-

管理入库与临时生产工单;入库工单关联样本接收,临时工单可独立创建并派发任务。

+

管理入库与临时生产工单;入库工单关联样本接收,临时工单可独立创建并派发任务。

@@ -12,9 +14,14 @@

工单列表

- + + @@ -22,11 +29,7 @@
- + + @@ -66,10 +70,11 @@
-
+
- {{ activeTypeTab === 'temporary' ? '新增临时工单' : '新增生产工单' }} + {{ activeTypeTab === 'temporary' ? '新增临时工单' : '新增生产工单' }}
@@ -91,30 +96,121 @@ :index="(i) => (pagination.page - 1) * pagination.pageSize + i + 1" /> - + - + + + + + + + + + + + + + + + + + - + - + - + - + - + - + @@ -143,6 +257,7 @@ 完成工单 - 删除 + 删除 @@ -302,7 +422,6 @@
- -

临时工单不关联样本与协议,创建后请派发任务。

+

临时工单不关联样本与协议,创建后请派发任务。

- +
@@ -325,7 +452,11 @@ - + @@ -334,7 +465,13 @@ - +
@@ -392,16 +529,20 @@ protocolStatusLabel, protocolStatusTagType, donationStatusLabel, - donationStatusTagType, + donationStatusTagType } from '@/utils/status-tag-registry' import type { TagType } from '@/utils/status-tag-registry' /** 根据来源类型返回协议状态标签 */ function protocolStatusLabelBySource(status: string, source: string): string { - return source === 'research_donation' ? donationStatusLabel(status) : protocolStatusLabel(status) + return source === 'research_donation' + ? donationStatusLabel(status) + : protocolStatusLabel(status) } function protocolStatusTagTypeBySource(status: string, source: string): TagType { - return source === 'research_donation' ? donationStatusTagType(status) : protocolStatusTagType(status) + return source === 'research_donation' + ? donationStatusTagType(status) + : protocolStatusTagType(status) } /** 临时工单无关联协议/样本时,空字段展示为 — */ @@ -418,20 +559,21 @@ import DispatchTaskDialog from './DispatchTaskDialog.vue' import EditTaskInfoDialog from './EditTaskInfoDialog.vue' import WorkOrderRemarkDialog from '@/components/business/work-order-remark-dialog/index.vue' - import { - productionConclusionLabel, - productionConclusionTagType - } from '@/utils/production-labels' - import { - fetchProductionTaskList, - fetchDeleteProductionTask - } from '@/api/production-task' + import { productionConclusionLabel, productionConclusionTagType } from '@/utils/production-labels' + import { fetchProductionTaskList, fetchDeleteProductionTask } from '@/api/production-task' defineOptions({ name: 'ProductionProductionWorkOrder' }) - type WorkOrderTypeTab = 'inbound' | 'temporary' | 'all' + type WorkOrderTypeTab = 'inbound' | 'temporary' | 'outbound' | 'all' const activeTypeTab = ref('inbound') - const showReceptionCols = computed(() => activeTypeTab.value !== 'temporary') + const showReceptionCols = computed( + () => activeTypeTab.value !== 'temporary' && activeTypeTab.value !== 'outbound' + ) + const searchPlaceholder = computed(() => { + if (activeTypeTab.value === 'temporary') return '工单号' + if (activeTypeTab.value === 'outbound') return '工单号/申请单号' + return '工单号/协议号/样本编码' + }) const addMode = ref<'inbound' | 'temporary'>('inbound') // ── 新增工单 ────────────────────────────────────────── @@ -485,7 +627,8 @@ const remarkEditLoading = ref(false) const remarkEditRow = ref<{ id: string; workOrderNo?: string; remark?: string } | null>(null) const remarkEditTitle = computed( - () => `编辑备注${remarkEditRow.value?.workOrderNo ? ` — ${remarkEditRow.value.workOrderNo}` : ''}` + () => + `编辑备注${remarkEditRow.value?.workOrderNo ? ` — ${remarkEditRow.value.workOrderNo}` : ''}` ) const remarkEditInitialRemark = computed(() => remarkEditRow.value?.remark || '') @@ -511,7 +654,8 @@ const map: Record = { dpz: { label: '待配置', type: 'info' }, jxz: { label: '进行中', type: 'warning' }, - ywc: { label: '已完成', type: 'success' } + ywc: { label: '已完成', type: 'success' }, + yzf: { label: '已作废', type: 'danger' } } return map[status] || { label: status, type: 'info' } } @@ -530,6 +674,14 @@ } return map[source] || { label: source, type: 'info' } } + const woTypeTag = (type: string) => { + const map: Record = { + inbound: { label: '入库', type: 'success' }, + temporary: { label: '临时', type: 'warning' }, + outbound: { label: '出库', type: 'primary' } + } + return map[type] || { label: type, type: 'info' } + } function formatLocalDate(d: Date): string { const y = d.getFullYear() @@ -641,7 +793,122 @@ if (!detail.value) return [] const d = detail.value const isTemp = d.workOrderType === 'temporary' + const isOutbound = d.workOrderType === 'outbound' const r = d.reception || {} + + // ── 基本信息 fields ── + const baseFields: ViewSection['fields'] = [ + { label: '工单号', value: d.workOrderNo }, + { + label: '工单类型', + value: isTemp ? '临时' : isOutbound ? '出库' : '入库', + tagType: isTemp ? 'warning' : isOutbound ? 'primary' : 'success' + } + ] + + // outbound 显示业务类型 + if (isOutbound) { + baseFields.push({ + label: '业务类型', + value: + d.bizTypeLabel || + (d.bizType === 'pilot_service' + ? '中试服务' + : d.bizType === 'clinical_application' + ? '临床应用' + : '—'), + tagType: 'primary' as const + }) + } + + // inbound 显示协议/样本字段 + if (!isTemp && !isOutbound) { + baseFields.push( + { label: '协议号', value: d.protocolNo }, + { + label: '协议状态', + value: protocolStatusLabelBySource( + String(d.protocolStatus || ''), + d.receptionSource || 'cell_storage' + ), + tagType: protocolStatusTagTypeBySource( + String(d.protocolStatus || ''), + d.receptionSource || 'cell_storage' + ) + }, + { label: '样本编码', value: d.sampleCode }, + { + label: '来源类型', + value: + r.sourceTypeLabel || + d.receptionSourceLabel || + receptionSourceLabel(d.receptionSource || ''), + tagType: 'primary' as const + }, + { label: '样本类型', value: r.sampleTypeName || d.sampleTypeName || '—' } + ) + } + + baseFields.push( + { + label: '工单状态', + value: woStatusTag(d.workOrderStatus).label, + tagType: woStatusTag(d.workOrderStatus).type as any + }, + { label: '备注', value: d.remark || '—', span: true }, + { label: '创建时间', value: d.createTime || '—' } + ) + + const sections: ViewSection[] = [{ title: '基本信息', fields: baseFields }] + + // outbound: 申请单 + 输注计划 + if (isOutbound) { + const pa = d.pilotApplication || {} + const sched = d.infusionSchedule || {} + const appStatusLabel = (s: string) => { + const m: Record = { + dks: '待开始', + jxz: '进行中', + ywc: '已完成', + yzf: '已作废' + } + return m[s] || s || '—' + } + const appStatusTagType = (s: string) => { + const m: Record = { + 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 + } + + // inbound: 运输 + 样本接收 const transportFields: ViewSection['fields'] = [ { label: '运输方式', value: transportTypeLabel(String(r.transportType || '')) } ] @@ -661,62 +928,29 @@ transportFields.length > 1 || r.transportType ? [{ title: '运输信息', fields: transportFields }] : [] + return [ - { - title: '基本信息', - fields: [ - { label: '工单号', value: d.workOrderNo }, - { - label: '工单类型', - value: isTemp ? '临时' : '入库', - tagType: isTemp ? 'warning' : 'success' - }, - ...(isTemp - ? [] - : [ - { label: '协议号', value: d.protocolNo }, - { - label: '协议状态', - value: protocolStatusLabelBySource(String(d.protocolStatus || ''), d.receptionSource || 'cell_storage'), - tagType: protocolStatusTagTypeBySource(String(d.protocolStatus || ''), d.receptionSource || 'cell_storage') - }, - { label: '样本编码', value: d.sampleCode }, - { - label: '来源类型', - value: - r.sourceTypeLabel || - d.receptionSourceLabel || - receptionSourceLabel(d.receptionSource || ''), - tagType: 'primary' as const - }, - { label: '样本类型', value: r.sampleTypeName || d.sampleTypeName || '—' } - ]), - { - label: '工单状态', - value: woStatusTag(d.workOrderStatus).label, - tagType: woStatusTag(d.workOrderStatus).type as any - }, - { label: '备注', value: d.remark || '—', span: true }, - { label: '创建时间', value: d.createTime || '—' } - ] - }, - ...(isTemp ? [] : transportSection), - ...(isTemp - ? [] - : [ + { title: '基本信息', fields: baseFields }, + ...transportSection, + ...(!isTemp + ? [ { title: '样本接收', slot: 'reception-extra', fields: [ { label: '接收时间', value: r.receptionTime || d.receptionTime || '—' }, { label: '接收经手人', value: r.handlerName || d.handlerName || '—' }, - { label: '接收温度(℃)', value: r.receiveTemperature ? `${r.receiveTemperature}℃` : '—' }, + { + 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 || '')), + r.acceptConclusionLabel || + acceptConclusionLabel(String(r.acceptConclusion || '')), tagType: r.acceptConclusion === 'js' ? 'success' @@ -728,7 +962,8 @@ { label: '备注', value: r.remark || '—', span: true } ] } - ]) + ] + : []) ] })