fix(web-admin): 样本接收展示协议知情同意书等附件
质检样本接收列表与弹窗需查看采集协议侧附件。后端待接收/已接收列表补齐协议附件字段;前端列表增加知情同意书缩略图,接收弹窗右侧改为上运输、下协议附件分组展示。 关联 #43 Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
1 parent
9cf1110495
commit
c40feab76a
6 files changed
+362
-34
No files matched your search
@@ -0,0 +1,62 @@
|
||||
import { describe, it } from 'node:test'
|
||||
import assert from 'node:assert/strict'
|
||||
import {
|
||||
getConsentProtocolAttachment,
|
||||
getSourceProtocolAttachment,
|
||||
getSourceProtocolAttachmentGroups
|
||||
} from './protocol-source-attachment'
|
||||
|
||||
describe('protocol-source-attachment', () => {
|
||||
it('returns health survey as primary for cell storage', () => {
|
||||
const meta = getSourceProtocolAttachment({
|
||||
sourceType: 'cell_storage',
|
||||
healthSurveyAttachmentFiles: [{ id: '1', fileUrl: '/a.png' }]
|
||||
})
|
||||
assert.equal(meta.label, '孕妇健康调查表')
|
||||
assert.equal(meta.files.length, 1)
|
||||
})
|
||||
|
||||
it('returns infectious disease report as primary for research donation', () => {
|
||||
const meta = getSourceProtocolAttachment({
|
||||
sourceType: 'research_donation',
|
||||
infectiousDiseaseReportAttachmentFiles: [{ id: '2', fileUrl: '/b.png' }]
|
||||
})
|
||||
assert.equal(meta.label, '传染病检测报告')
|
||||
assert.equal(meta.files.length, 1)
|
||||
})
|
||||
|
||||
it('returns consent attachment from shared field', () => {
|
||||
const meta = getConsentProtocolAttachment({
|
||||
consentAttachmentFiles: [{ id: '3', fileUrl: '/c.png' }]
|
||||
})
|
||||
assert.equal(meta.label, '知情同意书')
|
||||
assert.equal(meta.files.length, 1)
|
||||
})
|
||||
|
||||
it('groups cell storage attachments for reception dialog', () => {
|
||||
const groups = getSourceProtocolAttachmentGroups({
|
||||
sourceType: 'cell_storage',
|
||||
protocolAttachmentFiles: [{ id: 'p' }],
|
||||
consentAttachmentFiles: [{ id: 'c' }],
|
||||
healthSurveyAttachmentFiles: [{ id: 'h' }]
|
||||
})
|
||||
assert.deepEqual(
|
||||
groups.map((g) => g.label),
|
||||
['协议附件', '知情同意书', '孕妇健康调查表']
|
||||
)
|
||||
assert.ok(groups.every((g) => g.files.length === 1))
|
||||
})
|
||||
|
||||
it('groups research donation attachments for reception dialog', () => {
|
||||
const groups = getSourceProtocolAttachmentGroups({
|
||||
sourceType: 'research_donation',
|
||||
consentAttachmentFiles: [{ id: 'c' }],
|
||||
infectiousDiseaseReportAttachmentFiles: [{ id: 'i' }],
|
||||
attachmentFiles: [{ id: 'o' }]
|
||||
})
|
||||
assert.deepEqual(
|
||||
groups.map((g) => g.label),
|
||||
['知情同意书', '传染病检测报告', '其他附件']
|
||||
)
|
||||
})
|
||||
})
|
||||
@@ -8,7 +8,7 @@ function normalizeAttachmentFiles(raw: unknown): Array<Record<string, unknown>>
|
||||
return raw.filter((item) => item && typeof item === 'object') as Array<Record<string, unknown>>
|
||||
}
|
||||
|
||||
/** 按来源返回协议级附件标签与文件列表(细胞存储=孕妇健康调查表,科研捐赠=传染病检测报告) */
|
||||
/** 按来源返回协议级主附件标签与文件列表(细胞存储=孕妇健康调查表,科研捐赠=传染病检测报告) */
|
||||
export function getSourceProtocolAttachment(
|
||||
row: Record<string, unknown>
|
||||
): ProtocolSourceAttachmentMeta {
|
||||
@@ -24,3 +24,54 @@ export function getSourceProtocolAttachment(
|
||||
files: normalizeAttachmentFiles(row.healthSurveyAttachmentFiles)
|
||||
}
|
||||
}
|
||||
|
||||
/** 知情同意书(细胞存储与科研捐赠共用字段) */
|
||||
export function getConsentProtocolAttachment(
|
||||
row: Record<string, unknown>
|
||||
): ProtocolSourceAttachmentMeta {
|
||||
return {
|
||||
label: '知情同意书',
|
||||
files: normalizeAttachmentFiles(row.consentAttachmentFiles)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 样本接收弹窗:按来源返回质检需查看的协议附件分组。
|
||||
* 细胞存储:协议附件 / 知情同意书 / 孕妇健康调查表
|
||||
* 科研捐赠:知情同意书 / 传染病检测报告 / 其他附件
|
||||
*/
|
||||
export function getSourceProtocolAttachmentGroups(
|
||||
row: Record<string, unknown>
|
||||
): ProtocolSourceAttachmentMeta[] {
|
||||
const source = String(row.sourceType || 'cell_storage').trim()
|
||||
if (source === 'research_donation') {
|
||||
return [
|
||||
{
|
||||
label: '知情同意书',
|
||||
files: normalizeAttachmentFiles(row.consentAttachmentFiles)
|
||||
},
|
||||
{
|
||||
label: '传染病检测报告',
|
||||
files: normalizeAttachmentFiles(row.infectiousDiseaseReportAttachmentFiles)
|
||||
},
|
||||
{
|
||||
label: '其他附件',
|
||||
files: normalizeAttachmentFiles(row.attachmentFiles)
|
||||
}
|
||||
]
|
||||
}
|
||||
return [
|
||||
{
|
||||
label: '协议附件',
|
||||
files: normalizeAttachmentFiles(row.protocolAttachmentFiles)
|
||||
},
|
||||
{
|
||||
label: '知情同意书',
|
||||
files: normalizeAttachmentFiles(row.consentAttachmentFiles)
|
||||
},
|
||||
{
|
||||
label: '孕妇健康调查表',
|
||||
files: normalizeAttachmentFiles(row.healthSurveyAttachmentFiles)
|
||||
}
|
||||
]
|
||||
}
|
||||
+18
-1
@@ -80,6 +80,16 @@
|
||||
/>
|
||||
</dd>
|
||||
</div>
|
||||
<div class="sample-reception-card__meta-item sample-reception-card__meta-item--attachment">
|
||||
<dt>{{ consentAttachmentMeta(row).label }}</dt>
|
||||
<dd>
|
||||
<ProtocolAttachmentMiniReadonly
|
||||
:files="consentAttachmentMeta(row).files"
|
||||
@preview="handleAttachmentPreview"
|
||||
@download="handleAttachmentDownload"
|
||||
/>
|
||||
</dd>
|
||||
</div>
|
||||
<div v-if="activeTab === 'received'" class="sample-reception-card__meta-item">
|
||||
<dt>实验室入库编码</dt>
|
||||
<dd>
|
||||
@@ -242,7 +252,10 @@
|
||||
import SampleCodeWithSpecialDisplay from '@/components/business/sample-code-with-special-display/index.vue'
|
||||
import QcWorkOrderProtocolPaymentPanel from '@/views/quality-inspection/qc-work-order/QcWorkOrderProtocolPaymentPanel.vue'
|
||||
import ProtocolAttachmentMiniReadonly from '@/components/business/protocol-attachment-mini-readonly/index.vue'
|
||||
import { getSourceProtocolAttachment } from '@/utils/protocol-source-attachment'
|
||||
import {
|
||||
getConsentProtocolAttachment,
|
||||
getSourceProtocolAttachment
|
||||
} from '@/utils/protocol-source-attachment'
|
||||
import { useAttachmentActions } from '@/composables/useAttachmentActions'
|
||||
|
||||
defineOptions({ name: 'SampleReceptionCardList' })
|
||||
@@ -284,6 +297,10 @@
|
||||
return getSourceProtocolAttachment(row)
|
||||
}
|
||||
|
||||
function consentAttachmentMeta(row: Record<string, unknown>) {
|
||||
return getConsentProtocolAttachment(row)
|
||||
}
|
||||
|
||||
function protocolStatusLabelBySource(status: string, source: string, voidType?: string): string {
|
||||
return source === 'research_donation'
|
||||
? donationStatusDisplayLabel(status, voidType)
|
||||
|
||||
@@ -204,41 +204,62 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div id="form-section-transport" class="sample-transport-col">
|
||||
<div class="section-title section-title--compact">
|
||||
<div class="section-title-left"><h3>运输信息</h3></div>
|
||||
<div id="form-section-transport" class="sample-transport-col sample-transport-col--stack">
|
||||
<div class="sample-transport-stack-block">
|
||||
<div class="section-title section-title--compact">
|
||||
<div class="section-title-left"><h3>运输信息</h3></div>
|
||||
</div>
|
||||
<div class="data-grid data-grid--2">
|
||||
<div class="data-item">
|
||||
<div class="data-label">运输方式</div>
|
||||
<div class="data-value">
|
||||
{{ transportTypeLabel(String(currentRow.transportType || '')) }}
|
||||
</div>
|
||||
</div>
|
||||
<div class="data-item">
|
||||
<div class="data-label">预计到达</div>
|
||||
<div class="data-value">{{ formatDisplayOrDash(currentRow.etaTime) }}</div>
|
||||
</div>
|
||||
<template v-if="currentRow.transportType === 'kdys'">
|
||||
<div class="data-item">
|
||||
<div class="data-label">快递公司</div>
|
||||
<div class="data-value">{{ currentRow.expressCompanyName || '-' }}</div>
|
||||
</div>
|
||||
<div class="data-item">
|
||||
<div class="data-label">快递单号</div>
|
||||
<div class="data-value">{{ currentRow.waybillNo || '-' }}</div>
|
||||
</div>
|
||||
</template>
|
||||
<template v-else-if="currentRow.transportType === 'ryxd'">
|
||||
<div class="data-item">
|
||||
<div class="data-label">携带人</div>
|
||||
<div class="data-value">{{ currentRow.carrierName || '-' }}</div>
|
||||
</div>
|
||||
<div class="data-item">
|
||||
<div class="data-label">携带人电话</div>
|
||||
<div class="data-value">{{ currentRow.carrierPhone || '-' }}</div>
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
</div>
|
||||
<div class="data-grid data-grid--2">
|
||||
<div class="data-item">
|
||||
<div class="data-label">运输方式</div>
|
||||
<div class="data-value">
|
||||
{{ transportTypeLabel(String(currentRow.transportType || '')) }}
|
||||
<div class="sample-transport-stack-block sample-protocol-attachments">
|
||||
<div class="section-title section-title--compact">
|
||||
<div class="section-title-left"><h3>协议附件</h3></div>
|
||||
</div>
|
||||
<div class="sample-protocol-attachments__list">
|
||||
<div
|
||||
v-for="group in receiveProtocolAttachmentGroups"
|
||||
:key="group.label"
|
||||
class="sample-protocol-attachments__item"
|
||||
>
|
||||
<div class="data-label">{{ group.label }}</div>
|
||||
<ProtocolAttachmentReadonly
|
||||
:files="group.files"
|
||||
@preview="handleAttachmentPreview"
|
||||
@download="handleAttachmentDownload"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="data-item">
|
||||
<div class="data-label">预计到达</div>
|
||||
<div class="data-value">{{ formatDisplayOrDash(currentRow.etaTime) }}</div>
|
||||
</div>
|
||||
<template v-if="currentRow.transportType === 'kdys'">
|
||||
<div class="data-item">
|
||||
<div class="data-label">快递公司</div>
|
||||
<div class="data-value">{{ currentRow.expressCompanyName || '-' }}</div>
|
||||
</div>
|
||||
<div class="data-item">
|
||||
<div class="data-label">快递单号</div>
|
||||
<div class="data-value">{{ currentRow.waybillNo || '-' }}</div>
|
||||
</div>
|
||||
</template>
|
||||
<template v-else-if="currentRow.transportType === 'ryxd'">
|
||||
<div class="data-item">
|
||||
<div class="data-label">携带人</div>
|
||||
<div class="data-value">{{ currentRow.carrierName || '-' }}</div>
|
||||
</div>
|
||||
<div class="data-item">
|
||||
<div class="data-label">携带人电话</div>
|
||||
<div class="data-value">{{ currentRow.carrierPhone || '-' }}</div>
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -745,6 +766,7 @@
|
||||
buildPrefilledLabStorageCode,
|
||||
parseLabStorageReferenceDate
|
||||
} from '@/utils/sample-reception-lab-storage-code'
|
||||
import { getSourceProtocolAttachmentGroups } from '@/utils/protocol-source-attachment'
|
||||
import type { ProductionReceptionRecord } from '@/utils/sample-production-reception-fields'
|
||||
import {
|
||||
formatProductionFieldValue,
|
||||
@@ -955,6 +977,12 @@
|
||||
.filter(Boolean) as Array<Record<string, unknown>>
|
||||
})
|
||||
|
||||
const receiveProtocolAttachmentGroups = computed(() => {
|
||||
const row = currentRow.value
|
||||
if (!row) return []
|
||||
return getSourceProtocolAttachmentGroups(row)
|
||||
})
|
||||
|
||||
function formatMetric(v: unknown) {
|
||||
if (v === null || v === undefined || v === '') return '-'
|
||||
const n = Number(v)
|
||||
@@ -1653,6 +1681,35 @@
|
||||
padding-left: 20px;
|
||||
border-left: 1px solid #e2e8f0;
|
||||
}
|
||||
|
||||
&--stack {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 14px;
|
||||
}
|
||||
}
|
||||
|
||||
.sample-transport-stack-block {
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.sample-protocol-attachments {
|
||||
background: #ecfdf5;
|
||||
border-radius: 8px;
|
||||
padding: 12px 14px;
|
||||
border: 1px solid #a7f3d0;
|
||||
}
|
||||
|
||||
.sample-protocol-attachments__list {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.sample-protocol-attachments__item {
|
||||
.data-label {
|
||||
margin-bottom: 6px;
|
||||
}
|
||||
}
|
||||
|
||||
.section-title {
|
||||
|
||||
Reference in new issue
Block a user