This commit is contained in:
zlwu committed 2026-06-16 07:38:27 +08:00
1 parent a1c2511eb7
commit eaf7d490ec
382 files changed
+44146 -34245

No files matched your search

@@ -7,20 +7,36 @@
<dt>申请单号</dt>
<dd>{{ dash(detail?.applicationNo) }}</dd>
</div>
<div class="view-kv">
<dt>患者姓名</dt>
<dd>{{ dash(detail?.patientName) }}</dd>
</div>
<div class="view-kv">
<dt>渠道名称</dt>
<dd>{{ dash(detail?.channelName) }}</dd>
</div>
<template v-if="isBasicExperiment">
<div class="view-kv">
<dt>申请单名称</dt>
<dd>{{ dash(detail?.applicationName) }}</dd>
</div>
<div class="view-kv">
<dt>课题名称</dt>
<dd>{{ dash(detail?.projectTitle) }}</dd>
</div>
<div class="view-kv">
<dt>客户名称</dt>
<dd>{{ dash(detail?.customerName) }}</dd>
</div>
</template>
<template v-else>
<div class="view-kv">
<dt>患者姓名</dt>
<dd>{{ dash(detail?.patientName) }}</dd>
</div>
<div class="view-kv">
<dt>渠道名称</dt>
<dd>{{ dash(detail?.channelName) }}</dd>
</div>
</template>
<div class="view-kv">
<dt>细胞类型/规格</dt>
<dd>{{ dash(detail?.cellTypeSpec) }}</dd>
</div>
<div class="view-kv">
<dt>输注时间</dt>
<dt>{{ pickupTimeLabel }}</dt>
<dd>{{ dash(detail?.infusionTime) }}</dd>
</div>
<div v-if="showPayStatus" class="view-kv">
@@ -85,6 +101,7 @@
import ProtocolAttachmentReadonly from '@/views/cell-storage/protocol/ProtocolAttachmentReadonly.vue'
import { formatMoneyDisplay } from '@/utils/money-format'
import { payStatusLabel, payStatusTagType } from '@/utils/status-tag-registry'
import { getPilotBizConfig } from '@/config/pilot-biz-type'
const props = withDefaults(
defineProps<{
@@ -125,6 +142,14 @@
.filter(Boolean) as Array<Record<string, unknown>>
}
const isBasicExperiment = computed(
() => String(props.detail?.bizType || '') === 'basic_experiment'
)
const pickupTimeLabel = computed(() =>
getPilotBizConfig(String(props.detail?.bizType || '')).pickupTimeLabel
)
const receiptAttachmentFiles = computed(() => urlsToFiles(props.detail?.transferAttachments))
const refundAttachmentFiles = computed(() => urlsToFiles(props.detail?.refundAttachments))
@@ -308,6 +308,7 @@
width="1200px"
class="dialog-form-layout"
align-center
modal-class="protocol-dialog-mask"
destroy-on-close
append-to-body
:close-on-click-modal="false"
@@ -1,5 +1,6 @@
<template>
<el-dialog
append-to-body
:model-value="modelValue"
title="查看商品"
width="900px"
@@ -96,6 +96,7 @@
fetchQcTaskMaterialUsages,
type QcTaskMaterialUsageItem
} from '@/api/qc-task-result'
import { SHOW_QC_TASK_MATERIAL_USAGE } from '@/utils/qc-material-ui'
import {
taskTypeLabel,
taskStatusLabel,
@@ -172,7 +173,7 @@
;(detail as QcTaskDetail).results = []
}
viewMaterialUsages.value = []
if (detail.taskType !== 'dsfjc') {
if (SHOW_QC_TASK_MATERIAL_USAGE && detail.taskType !== 'dsfjc') {
try {
const savedUsages = await fetchQcTaskMaterialUsages(id).catch(
() => [] as QcTaskMaterialUsageItem[]
@@ -337,7 +338,7 @@
: [{ title: '检测参数', fields: [] }])
]
const tailSections: ViewSection[] = []
if (d.taskType !== 'dsfjc') {
if (SHOW_QC_TASK_MATERIAL_USAGE && d.taskType !== 'dsfjc') {
tailSections.push({ title: '物料使用', fields: [], slot: 'material-usages' })
}
if (d.attachmentFiles?.length) {
@@ -1,7 +1,10 @@
<!-- 样本查看 — 质检信息卡片(协议单/捐赠单查看弹窗复用) -->
<template>
<div class="sample-card__section sample-card__section--qc">
<div class="sample-card__section-label">质检信息</div>
<div
class="sample-card__section sample-card__section--qc"
:class="{ 'sample-card__section--embedded': embedded }"
>
<div v-if="!embedded" class="sample-card__section-label">{{ sectionLabel || '质检信息' }}</div>
<div v-if="!qcInfo" class="sample-qc-empty">{{ emptyHint }}</div>
@@ -128,6 +131,11 @@
workOrderStatusLabel?: string
qcBizType?: string
qcBizTypeLabel?: string
triggerSource?: string
triggerSourceLabel?: string
cellProductCode?: string
cellProductName?: string
productionWorkOrderNo?: string
configuredTime?: string
hasLab?: boolean
hasThirdParty?: boolean
@@ -145,12 +153,20 @@
acceptConclusion?: string
}
const props = defineProps<{
qcInfo?: SampleQcInfo | null
sampleReception?: SampleReceptionBrief | null
/** 任务详情弹窗不展示样本信息、样本接收(出库放行等场景) */
hideSampleContext?: boolean
}>()
const props = withDefaults(
defineProps<{
qcInfo?: SampleQcInfo | null
sampleReception?: SampleReceptionBrief | null
/** 任务详情弹窗不展示样本信息、样本接收(出库放行等场景) */
hideSampleContext?: boolean
/** 嵌入分组面板时隐藏区块标题与顶部分隔 */
embedded?: boolean
sectionLabel?: string
}>(),
{
embedded: false
}
)
const taskViewVisible = ref(false)
const taskViewId = ref('')
@@ -181,6 +197,12 @@
margin-top: 4px;
padding-top: 14px;
border-top: 1px dashed #e2e8f0;
&.sample-card__section--embedded {
margin-top: 0;
padding-top: 0;
border-top: none;
}
}
.sample-card__section-label {
@@ -0,0 +1,189 @@
<!-- 样本查看 — 质检信息分组(门禁 + 生产过程,入库放行/协议/捐赠查看复用) -->
<template>
<div
class="sample-qc-grouped-panel-root"
:class="{ 'sample-qc-grouped-panel-root--section': showSectionLabel }"
>
<div v-if="showSectionLabel" class="sample-card__section-label">质检信息</div>
<div class="sample-qc-grouped-panel">
<section class="sample-qc-grouped-panel__block">
<h4 class="sample-qc-grouped-panel__subtitle">样本接收质检(入库门禁)</h4>
<SampleQcInfoCards
embedded
:qc-info="gateQcInfo"
:sample-reception="sampleReception"
:hide-sample-context="hideSampleContext"
/>
</section>
<section class="sample-qc-grouped-panel__block">
<h4 class="sample-qc-grouped-panel__subtitle">生产过程质检</h4>
<p class="sample-qc-grouped-panel__hint">过程参考,不参与入库放行判定</p>
<div v-if="!processQcInfoList?.length" class="sample-qc-grouped-panel__empty">
暂无生产过程质检工单
</div>
<div v-else class="sample-qc-grouped-panel__process-list">
<article
v-for="(item, idx) in processQcInfoList"
:key="item.workOrderNo || String(idx)"
class="sample-qc-grouped-panel__process-item"
>
<div class="process-qc-meta">
<el-tag type="success" size="small" class="process-qc-meta__tag">生产过程</el-tag>
<div class="qc-kv">
<span class="qc-kv__label">质检对象</span>
<span class="qc-kv__value">{{ formatQcTarget(item) }}</span>
</div>
<div v-if="item.productionWorkOrderNo" class="qc-kv">
<span class="qc-kv__label">生产工单</span>
<span class="qc-kv__value qc-kv__value--code">{{
item.productionWorkOrderNo
}}</span>
</div>
</div>
<SampleQcInfoCards embedded :qc-info="item" :hide-sample-context="hideSampleContext" />
</article>
</div>
</section>
</div>
</div>
</template>
<script setup lang="ts">
import SampleQcInfoCards, {
type SampleQcInfo,
type SampleReceptionBrief
} from '@/components/business/sample-qc-info-cards/index.vue'
defineOptions({ name: 'SampleQcInfoGroupedPanel' })
withDefaults(
defineProps<{
gateQcInfo?: SampleQcInfo | null
processQcInfoList?: SampleQcInfo[]
sampleReception?: SampleReceptionBrief | null
hideSampleContext?: boolean
/** 协议/捐赠样本卡内展示外层「质检信息」标题 */
showSectionLabel?: boolean
}>(),
{
processQcInfoList: () => [],
showSectionLabel: true
}
)
function formatQcTarget(item: SampleQcInfo) {
const parts = [item.cellProductCode, item.cellProductName].filter(Boolean)
return parts.length ? parts.join(' · ') : '—'
}
</script>
<style scoped lang="scss">
.sample-qc-grouped-panel-root--section {
margin-top: 4px;
padding-top: 14px;
border-top: 1px dashed #e2e8f0;
}
.sample-card__section-label {
font-size: 11px;
font-weight: 600;
color: #94a3b8;
margin-bottom: 10px;
letter-spacing: 0.3px;
}
.sample-qc-grouped-panel {
display: flex;
flex-direction: column;
gap: 16px;
}
.sample-qc-grouped-panel__block {
display: flex;
flex-direction: column;
gap: 8px;
min-width: 0;
}
.sample-qc-grouped-panel__subtitle {
margin: 0;
padding-left: 10px;
font-size: 12px;
font-weight: 600;
color: #334155;
line-height: 1.4;
border-left: 3px solid #10b981;
}
.sample-qc-grouped-panel__hint {
margin: 0;
padding-left: 13px;
font-size: 12px;
color: #94a3b8;
line-height: 1.4;
}
.sample-qc-grouped-panel__empty {
padding: 4px 0 4px 13px;
font-size: 13px;
color: #94a3b8;
}
.sample-qc-grouped-panel__process-list {
display: flex;
flex-direction: column;
gap: 12px;
padding-left: 13px;
}
.sample-qc-grouped-panel__process-item {
padding: 10px 12px;
border: 1px solid #e2e8f0;
border-radius: 8px;
background: #f8fafc;
}
.process-qc-meta {
display: flex;
flex-wrap: wrap;
align-items: flex-start;
gap: 8px 16px;
margin-bottom: 8px;
}
.process-qc-meta__tag {
flex-shrink: 0;
}
.process-qc-meta .qc-kv {
flex: 1;
min-width: 140px;
}
.qc-kv {
display: flex;
align-items: flex-start;
gap: 8px;
font-size: 12px;
}
.qc-kv__label {
color: #94a3b8;
flex-shrink: 0;
min-width: 56px;
}
.qc-kv__value {
color: #1e293b;
font-weight: 500;
word-break: break-word;
&--code {
font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace;
font-size: 12px;
}
}
</style>
@@ -127,6 +127,7 @@
width="840px"
class="dialog-form-layout"
align-center
modal-class="protocol-dialog-mask"
destroy-on-close
append-to-body
:close-on-click-modal="false"
@@ -265,6 +266,7 @@
width="1200px"
class="dialog-form-layout"
align-center
modal-class="protocol-dialog-mask"
destroy-on-close
append-to-body
:close-on-click-modal="false"
@@ -6,6 +6,8 @@
width="560px"
class="dialog-form-layout"
align-center
append-to-body
modal-class="protocol-dialog-mask"
destroy-on-close
:close-on-click-modal="false"
@close="handleClose"
@@ -6,6 +6,8 @@
width="480px"
class="dialog-form-layout"
align-center
append-to-body
modal-class="protocol-dialog-mask"
destroy-on-close
:close-on-click-modal="false"
@close="handleClose"
@@ -6,6 +6,8 @@
width="560px"
class="dialog-form-layout"
align-center
append-to-body
modal-class="protocol-dialog-mask"
destroy-on-close
:close-on-click-modal="false"
@close="handleClose"