Files
zkxw/web-admin/src/views/cell-storage/sample-info/UnifiedSampleInfoPage.vue
T
zlwuandCursor a214a35f4f feat(web-admin): 按采集大表导出预签约、自存、科研和客服表
市场人员需要把四张列表按采集大表对应工作表导出为 Excel 文本。补预签约列表的地区、医院、样本类型和转账时间,并沿用现有工具栏导出。

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-11-06 10:05:11 +08:00

816 lines
33 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<template>
<div class="unified-sample-info-page">
<div class="filter-block">
<el-form
:model="searchForm"
class="filter-form sample-info-filter-form"
label-width="auto"
@submit.prevent
>
<div
class="filter-grid sample-info-filter-grid"
:class="{ 'is-cell-storage': isCellStorage }"
>
<el-form-item label="关键词">
<el-input v-model="searchForm.keyword" placeholder="姓名/样本编码/入库编码" clearable />
</el-form-item>
<el-form-item label="采集">
<el-select v-model="searchForm.collectionFlag" placeholder="全部" clearable style="width: 100%">
<el-option label="未采集" value="uncollected" />
<el-option label="已采集" value="collected" />
<el-option label="已作废" value="voided" />
</el-select>
</el-form-item>
<el-form-item label="接收">
<el-select v-model="searchForm.receptionFlag" placeholder="全部" clearable style="width: 100%">
<el-option label="未接收" value="none" />
<el-option label="已接收" value="js" />
<el-option label="拒收" value="bj" />
</el-select>
</el-form-item>
<el-form-item label="质检">
<el-select v-model="searchForm.qcFlag" placeholder="全部" clearable style="width: 100%">
<el-option label="未完成" value="incomplete" />
<el-option label="已完成" value="complete" />
<el-option label="已作废" value="voided" />
</el-select>
</el-form-item>
<el-form-item label="入库">
<el-select v-model="searchForm.storageFlag" placeholder="全部" clearable style="width: 100%">
<el-option label="未入库" value="none" />
<el-option label="已入库" value="yrk" />
<el-option label="拒绝入库" value="rejected" />
</el-select>
</el-form-item>
<el-form-item v-if="isCellStorage" label="储存证">
<el-select
v-model="searchForm.storageCertHandled"
placeholder="全部"
clearable
style="width: 100%"
>
<el-option label="已办理" value="1" />
<el-option label="未办理" value="0" />
</el-select>
</el-form-item>
<el-form-item v-if="isCellStorage" label="保险">
<el-select
v-model="searchForm.insuranceHandled"
placeholder="全部"
clearable
style="width: 100%"
>
<el-option label="已办理" value="1" />
<el-option label="未办理" value="0" />
</el-select>
</el-form-item>
</div>
<div class="filter-actions-row">
<div class="filter-actions-end">
<el-button class="btn-ghost" @click="handleReset">
<el-icon><RefreshLeft /></el-icon>重置
</el-button>
<el-button class="btn-main" type="primary" @click="handleSearch">
<el-icon><Search /></el-icon>搜索
</el-button>
</div>
</div>
</el-form>
</div>
<div class="list-toolbar">
<div class="list-toolbar-row">
<div class="list-toolbar-selected">
已选 <span class="sel-num">{{ selectedRows.length }}</span> 项
</div>
<el-button
type="primary"
class="btn-main"
:disabled="!selectedRows.length"
@click="openQuickRegister('storage')"
>
<el-icon><Box /></el-icon>
登记入库
</el-button>
<el-button
v-if="isCellStorage"
class="btn-ghost"
:loading="exporting"
@click="handleExport"
>
<el-icon><Download /></el-icon>
导出 Excel
</el-button>
<el-button
v-if="isCellStorage"
class="btn-ghost"
:disabled="!selectedRows.length"
@click="openQuickRegister('storage_cert')"
>
<el-icon><Ticket /></el-icon>
登记储存证
</el-button>
<el-button
v-if="isCellStorage"
class="btn-ghost"
:disabled="!selectedRows.length"
@click="openQuickRegister('insurance')"
>
<el-icon><Collection /></el-icon>
登记保险
</el-button>
<el-button
v-if="isCellStorage"
class="btn-ghost"
:disabled="!selectedRows.length"
@click="openQuickRegister('sms')"
>
<el-icon><ChatDotRound /></el-icon>
登记短信
</el-button>
<el-button
v-if="isCellStorage"
class="btn-ghost"
:disabled="!selectedRows.length"
@click="openQuickRegister('followup')"
>
<el-icon><Phone /></el-icon>
登记回访
</el-button>
<el-button
v-if="isCellStorage"
class="btn-ghost"
:disabled="!selectedRows.length"
@click="openQuickRegister('fulfillment')"
>
<el-icon><Location /></el-icon>
登记履约
</el-button>
<el-button
v-if="isCellStorage"
class="btn-ghost"
:disabled="!selectedRows.length"
@click="openQuickRegister('all')"
>
<el-icon><Files /></el-icon>
登记全部
</el-button>
</div>
</div>
<div class="list-body list-wrap">
<el-table
ref="tableRef"
class="list-table"
:data="tableData"
v-loading="loading"
stripe
row-key="rowKey"
@selection-change="handleSelectionChange"
@row-dblclick="handleView"
>
<el-table-column type="selection" min-width="55" align="center" header-align="center" fixed />
<el-table-column
type="index"
label="序号"
min-width="68"
align="center"
header-align="center"
:index="(i) => (pagination.page - 1) * pagination.pageSize + i + 1"
/>
<!-- 细胞存储:对齐 Excel 客户服务宽表 -->
<template v-if="isCellStorage">
<el-table-column prop="labStorageCode" label="实验室流转编码" min-width="140" align="center" show-overflow-tooltip>
<template #default="{ row }">{{ row.labStorageCode || '—' }}</template>
</el-table-column>
<el-table-column label="储存状态" min-width="100" align="center">
<template #default="{ row }">
<el-tag :type="statusTagType(row)" size="small">{{ statusLabel(row) }}</el-tag>
</template>
</el-table-column>
<el-table-column prop="smsSendDate" label="发送日期" min-width="120" align="center">
<template #default="{ row }">{{ row.smsSendDate || '—' }}</template>
</el-table-column>
<el-table-column prop="smsSendStatus" label="短信发送情况" min-width="110" align="center">
<template #default="{ row }">{{ row.smsSendStatus || '—' }}</template>
</el-table-column>
<el-table-column prop="sampleCode" label="样本编码" min-width="160" align="center">
<template #default="{ row }">
<SampleCodeWithSpecialDisplay
class="sample-code-col"
:sample-code="row.sampleCode"
:is-special="!!row.isSpecial"
:special-remark="row.specialRemark"
layout="block"
monospace
/>
</template>
</el-table-column>
<el-table-column prop="motherName" label="母亲姓名" min-width="100" align="center" />
<el-table-column prop="motherPhone" label="母亲电话" min-width="120" align="center">
<template #default="{ row }">{{ row.motherPhone || '—' }}</template>
</el-table-column>
<el-table-column prop="motherIdCard" label="母亲身份证" min-width="160" align="center" show-overflow-tooltip>
<template #default="{ row }">{{ row.motherIdCard || '—' }}</template>
</el-table-column>
<el-table-column prop="fatherName" label="父亲姓名" min-width="100" align="center">
<template #default="{ row }">{{ row.fatherName || '—' }}</template>
</el-table-column>
<el-table-column prop="fatherPhone" label="父亲电话" min-width="120" align="center">
<template #default="{ row }">{{ row.fatherPhone || '—' }}</template>
</el-table-column>
<el-table-column prop="fatherIdCard" label="父亲身份证" min-width="160" align="center" show-overflow-tooltip>
<template #default="{ row }">{{ row.fatherIdCard || '—' }}</template>
</el-table-column>
<el-table-column prop="microbeFollowupDate" label="微生物回访日期" min-width="130" align="center">
<template #default="{ row }">{{ row.microbeFollowupDate || '—' }}</template>
</el-table-column>
<el-table-column prop="microbeFollowupStatus" label="微生物回访情况" min-width="120" align="center">
<template #default="{ row }">{{ row.microbeFollowupStatus || '—' }}</template>
</el-table-column>
<el-table-column prop="fullmoonFollowupDate" label="满月回访时间" min-width="120" align="center">
<template #default="{ row }">{{ row.fullmoonFollowupDate || '—' }}</template>
</el-table-column>
<el-table-column prop="fullmoonFollowupStatus" label="满月回访情况" min-width="120" align="center">
<template #default="{ row }">{{ row.fullmoonFollowupStatus || '—' }}</template>
</el-table-column>
<el-table-column prop="householdRegistered" label="是否上户口" min-width="100" align="center">
<template #default="{ row }">{{ row.householdRegistered || '—' }}</template>
</el-table-column>
<el-table-column prop="newbornName" label="宝宝姓名" min-width="100" align="center" show-overflow-tooltip />
<el-table-column prop="newbornIdCard" label="宝宝身份证" min-width="160" align="center" show-overflow-tooltip>
<template #default="{ row }">{{ row.newbornIdCard || '—' }}</template>
</el-table-column>
<el-table-column prop="newbornSex" label="性别" min-width="70" align="center">
<template #default="{ row }">{{ sexLabel(row.newbornSex) }}</template>
</el-table-column>
<el-table-column prop="newbornDeliveryTime" label="分娩时间" min-width="160" align="center" show-overflow-tooltip>
<template #default="{ row }">{{ row.newbornDeliveryTime || '—' }}</template>
</el-table-column>
<el-table-column prop="purchaseInsuranceTime" label="购买保险时间" min-width="160" align="center">
<template #default="{ row }">{{ row.purchaseInsuranceTime || '—' }}</template>
</el-table-column>
<el-table-column prop="policySendTime" label="保单发送时间" min-width="160" align="center">
<template #default="{ row }">{{ row.policySendTime || '—' }}</template>
</el-table-column>
<el-table-column prop="einvoiceSendTime" label="电子发票发送时间" min-width="160" align="center">
<template #default="{ row }">{{ row.einvoiceSendTime || '—' }}</template>
</el-table-column>
<el-table-column prop="mailCertTime" label="邮寄储存证时间" min-width="160" align="center">
<template #default="{ row }">{{ row.mailCertTime || '—' }}</template>
</el-table-column>
<el-table-column prop="effectiveAddress" label="有效地址" min-width="180" align="center" show-overflow-tooltip>
<template #default="{ row }">{{ row.effectiveAddress || '—' }}</template>
</el-table-column>
<el-table-column prop="marketSpecialistName" label="市场专员" min-width="100" align="center">
<template #default="{ row }">{{ row.marketSpecialistName || '—' }}</template>
</el-table-column>
<el-table-column prop="payModeLabel" label="缴费方式" min-width="90" align="center">
<template #default="{ row }">{{ row.payModeLabel || '—' }}</template>
</el-table-column>
<el-table-column prop="protocolRemark" label="备注" min-width="140" align="center" show-overflow-tooltip>
<template #default="{ row }">{{ row.protocolRemark || '—' }}</template>
</el-table-column>
<el-table-column label="协议书" min-width="80" align="center">
<template #default="{ row }">
<el-tag :type="row.protocolAttachmentPresent ? 'success' : 'info'" size="small">
{{ row.protocolAttachmentPresent ? '有' : '无' }}
</el-tag>
</template>
</el-table-column>
<el-table-column label="保险单" min-width="80" align="center">
<template #default="{ row }">
<el-tag :type="row.insuranceDocPresent ? 'success' : 'info'" size="small">
{{ row.insuranceDocPresent ? '有' : '无' }}
</el-tag>
</template>
</el-table-column>
<el-table-column label="知情同意书" min-width="100" align="center">
<template #default="{ row }">
<el-tag :type="row.informedConsentPresent ? 'success' : 'info'" size="small">
{{ row.informedConsentPresent ? '有' : '无' }}
</el-tag>
</template>
</el-table-column>
<el-table-column label="健康调查表" min-width="100" align="center">
<template #default="{ row }">
<el-tag :type="row.healthSurveyPresent ? 'success' : 'info'" size="small">
{{ row.healthSurveyPresent ? '有' : '无' }}
</el-tag>
</template>
</el-table-column>
<el-table-column prop="expectedDeliveryDate" label="预产期" min-width="120" align="center" />
<el-table-column prop="sampleTypeName" label="样本类型" min-width="110" align="center" />
<el-table-column label="采集" min-width="90" align="center">
<template #default="{ row }">
<el-tag :type="sampleCollectionFlagTagType(String(row.collectionFlag || ''))" size="small">
{{ row.collectionFlagLabel || sampleCollectionFlagLabel(String(row.collectionFlag || '')) }}
</el-tag>
</template>
</el-table-column>
<el-table-column label="接收" min-width="90" align="center">
<template #default="{ row }">
<el-tag :type="sampleReceptionFlagTagType(String(row.receptionFlag || ''))" size="small">
{{ row.receptionFlagLabel || sampleReceptionFlagLabel(String(row.receptionFlag || '')) }}
</el-tag>
</template>
</el-table-column>
<el-table-column label="质检" min-width="90" align="center">
<template #default="{ row }">
<el-tag :type="sampleQcFlagTagType(String(row.qcFlag || ''))" size="small">
{{ row.qcFlagLabel || sampleQcFlagLabel(String(row.qcFlag || '')) }}
</el-tag>
</template>
</el-table-column>
<el-table-column label="入库" min-width="100" align="center">
<template #default="{ row }">
<el-tag :type="sampleStorageFlagTagType(String(row.storageFlag || ''))" size="small">
{{ row.storageFlagLabel || sampleStorageFlagLabel(String(row.storageFlag || '')) }}
</el-tag>
</template>
</el-table-column>
<el-table-column label="储存证" min-width="90" align="center">
<template #default="{ row }">
<el-tag :type="handleDoneFlagTagType(row.storageCertHandled ? '1' : '0')" size="small">
{{ handleDoneFlagLabel(row.storageCertHandled ? '1' : '0') }}
</el-tag>
</template>
</el-table-column>
<el-table-column label="保险" min-width="90" align="center">
<template #default="{ row }">
<el-tag :type="handleDoneFlagTagType(row.insuranceHandled ? '1' : '0')" size="small">
{{ handleDoneFlagLabel(row.insuranceHandled ? '1' : '0') }}
</el-tag>
</template>
</el-table-column>
</template>
<!-- 科研捐赠:保持精简列 -->
<template v-else>
<el-table-column label="捐赠状态" min-width="100" align="center">
<template #default="{ row }">
<el-tag :type="statusTagType(row)" size="small">{{ statusLabel(row) }}</el-tag>
</template>
</el-table-column>
<el-table-column prop="expectedDeliveryDate" label="预产期" min-width="120" align="center" />
<el-table-column prop="motherName" label="母亲姓名" min-width="100" align="center" />
<el-table-column prop="newbornName" label="新生儿姓名" min-width="110" align="center" show-overflow-tooltip />
<el-table-column prop="sampleCode" label="样本编码" min-width="160" align="center">
<template #default="{ row }">
<SampleCodeWithSpecialDisplay
class="sample-code-col"
:sample-code="row.sampleCode"
:is-special="!!row.isSpecial"
:special-remark="row.specialRemark"
layout="block"
monospace
/>
</template>
</el-table-column>
<el-table-column prop="labStorageCode" label="实验室入库编码" min-width="150" align="center">
<template #default="{ row }">{{ row.labStorageCode || '—' }}</template>
</el-table-column>
<el-table-column prop="sampleTypeName" label="样本类型" min-width="110" align="center" />
<el-table-column label="采集" min-width="90" align="center">
<template #default="{ row }">
<el-tag :type="sampleCollectionFlagTagType(String(row.collectionFlag || ''))" size="small">
{{ row.collectionFlagLabel || sampleCollectionFlagLabel(String(row.collectionFlag || '')) }}
</el-tag>
</template>
</el-table-column>
<el-table-column label="接收" min-width="90" align="center">
<template #default="{ row }">
<el-tag :type="sampleReceptionFlagTagType(String(row.receptionFlag || ''))" size="small">
{{ row.receptionFlagLabel || sampleReceptionFlagLabel(String(row.receptionFlag || '')) }}
</el-tag>
</template>
</el-table-column>
<el-table-column label="质检" min-width="90" align="center">
<template #default="{ row }">
<el-tag :type="sampleQcFlagTagType(String(row.qcFlag || ''))" size="small">
{{ row.qcFlagLabel || sampleQcFlagLabel(String(row.qcFlag || '')) }}
</el-tag>
</template>
</el-table-column>
<el-table-column label="入库" min-width="100" align="center">
<template #default="{ row }">
<el-tag :type="sampleStorageFlagTagType(String(row.storageFlag || ''))" size="small">
{{ row.storageFlagLabel || sampleStorageFlagLabel(String(row.storageFlag || '')) }}
</el-tag>
</template>
</el-table-column>
</template>
<el-table-column label="操作" min-width="360" fixed="right" align="center" header-align="center">
<template #default="{ row }">
<el-button class="btn-op-item primary" @click="handleView(row)">
<el-icon><View /></el-icon>查看
</el-button>
<el-button
class="btn-op-item"
:disabled="!row.sampleCode"
@click="openCodeCorrection(row)"
>
<el-icon><EditPen /></el-icon>更正编码
</el-button>
<el-button
v-if="isCellStorage && supportsStorageCertQc(row.sampleTypeName)"
class="btn-op-item"
@click="openStorageCertQcEdit(row)"
>
<el-icon><Document /></el-icon>填写储存证质检信息
</el-button>
</template>
</el-table-column>
</el-table>
<div class="pagination list-pager-bar">
<el-pagination
:current-page="pagination.page"
:page-size="pagination.pageSize"
:total="pagination.total"
:page-sizes="[10, 20, 50]"
layout="total, sizes, prev, pager, next"
@size-change="(val) => ((pagination.page = 1), (pagination.pageSize = val), loadData())"
@current-change="(val) => ((pagination.page = val), loadData())"
/>
</div>
</div>
<ProtocolViewDialog
v-if="isCellStorage"
v-model="protocolViewVisible"
:protocol-id="viewProtocolId"
:list-row="viewProtocolRow"
:highlight-sample-code="highlightSampleCode"
show-audit-history
show-handle-panels
/>
<DonationViewDialog
v-else
v-model="donationViewVisible"
:donation-id="viewDonationId"
:list-row="viewDonationRow"
:highlight-sample-code="highlightSampleCode"
show-audit-history
/>
<SampleInfoQuickRegisterDialog
v-model="quickRegisterVisible"
:kind="quickRegisterKind"
:reception-source="activeSource"
:rows="quickRegisterRows"
@saved="onQuickRegisterSaved"
/>
<StorageCertQcDialog
v-model:visible="storageCertQcVisible"
mode="edit"
:sample-code="storageCertQcSampleCode"
:sample-type-name="storageCertQcSampleTypeName"
/>
<SampleCodeCorrectionDialog
v-model="codeCorrectionVisible"
:reception-source="activeSource"
:old-sample-code="codeCorrectionOldCode"
@success="loadData"
/>
</div>
</template>
<script setup lang="ts">
import {
Box,
ChatDotRound,
Collection,
Document,
Download,
EditPen,
Files,
Location,
Phone,
RefreshLeft,
Search,
Ticket,
View
} from '@element-plus/icons-vue'
import { ElMessage } from 'element-plus'
import StorageCertQcDialog from '@/components/business/storage-cert-qc-dialog/index.vue'
import { supportsStorageCertQc } from '@/utils/storage-cert-qc-fields'
import { fetchProtocolSampleCollectionList } from '@/api/protocol-sample-collection'
import { fetchResearchDonationSampleCollectionList } from '@/api/research-donation-sample-collection'
import type { QuickRegisterKind } from '@/api/sample-info-handle'
import type { CollectionProtocolRow } from '@/api/collection-protocol'
import type { ResearchDonationRow } from '@/api/research-donation'
import ProtocolViewDialog from '@/views/cell-storage/protocol/ProtocolViewDialog.vue'
import DonationViewDialog from '@/views/research-donation/list/DonationViewDialog.vue'
import SampleCodeWithSpecialDisplay from '@/components/business/sample-code-with-special-display/index.vue'
import SampleInfoQuickRegisterDialog from './SampleInfoQuickRegisterDialog.vue'
import SampleCodeCorrectionDialog from './SampleCodeCorrectionDialog.vue'
import {
donationStatusDisplayLabel,
donationStatusTagType,
handleDoneFlagLabel,
handleDoneFlagTagType,
protocolStatusDisplayLabel,
protocolStatusTagType,
sampleCollectionFlagLabel,
sampleCollectionFlagTagType,
sampleQcFlagLabel,
sampleQcFlagTagType,
sampleReceptionFlagLabel,
sampleReceptionFlagTagType,
sampleStorageFlagLabel,
sampleStorageFlagTagType
} from '@/utils/status-tag-registry'
import {
CUSTOMER_SERVICE_HEADERS,
buildCustomerServiceLedgerRows,
fetchAllPages,
saveCollectionLedgerWorkbook
} from '@/utils/collection-ledger-export'
defineOptions({ name: 'UnifiedSampleInfoPage' })
const props = defineProps<{
defaultSource: 'cell_storage' | 'research_donation'
}>()
/** 客户服务管理仅细胞存储;科研捐赠隐藏路由仍可通过 defaultSource 进入 */
const activeSource = computed(() => props.defaultSource)
const isCellStorage = computed(() => activeSource.value === 'cell_storage')
const searchForm = reactive({
keyword: '',
collectionFlag: '',
receptionFlag: '',
qcFlag: '',
storageFlag: '',
storageCertHandled: '',
insuranceHandled: ''
})
const tableData = ref<Record<string, unknown>[]>([])
const loading = ref(false)
const exporting = ref(false)
const pagination = reactive({ page: 1, pageSize: 10, total: 0 })
const selectedRows = ref<Record<string, unknown>[]>([])
const tableRef = ref<{ clearSelection?: () => void } | null>(null)
const quickRegisterVisible = ref(false)
const quickRegisterKind = ref<QuickRegisterKind>('storage')
const quickRegisterRows = ref<Record<string, unknown>[]>([])
const protocolViewVisible = ref(false)
const donationViewVisible = ref(false)
const viewProtocolId = ref<string | null>(null)
const viewDonationId = ref<string | null>(null)
const viewProtocolRow = ref<CollectionProtocolRow | null>(null)
const viewDonationRow = ref<ResearchDonationRow | null>(null)
const highlightSampleCode = ref('')
const storageCertQcVisible = ref(false)
const storageCertQcSampleCode = ref('')
const storageCertQcSampleTypeName = ref('')
const codeCorrectionVisible = ref(false)
const codeCorrectionOldCode = ref('')
function sexLabel(sex: unknown) {
const s = String(sex || '').trim()
if (s === '1' || s === '男') return '男'
if (s === '2' || s === '女') return '女'
return s || '—'
}
function openStorageCertQcEdit(row: Record<string, unknown>) {
storageCertQcSampleCode.value = String(row.sampleCode || '')
storageCertQcSampleTypeName.value = String(row.sampleTypeName || '')
if (!storageCertQcSampleCode.value) {
ElMessage.warning('缺少样本编码,无法填写')
return
}
storageCertQcVisible.value = true
}
function openCodeCorrection(row: Record<string, unknown>) {
const code = String(row.sampleCode || '').trim()
if (!code) {
ElMessage.warning('缺少样本编码,无法更正')
return
}
codeCorrectionOldCode.value = code
codeCorrectionVisible.value = true
}
function statusLabel(row: Record<string, unknown>) {
if (isCellStorage.value) {
return protocolStatusDisplayLabel(String(row.protocolStatus || ''), String(row.voidType || ''))
}
return donationStatusDisplayLabel(String(row.donationStatus || ''), String(row.voidType || ''))
}
function statusTagType(row: Record<string, unknown>) {
if (isCellStorage.value) {
return protocolStatusTagType(String(row.protocolStatus || ''))
}
return donationStatusTagType(String(row.donationStatus || ''))
}
async function loadData() {
loading.value = true
try {
const params: Record<string, unknown> = {
page: pagination.page,
pageSize: pagination.pageSize,
keyword: searchForm.keyword || undefined,
collectionFlag: searchForm.collectionFlag || undefined,
receptionFlag: searchForm.receptionFlag || undefined,
qcFlag: searchForm.qcFlag || undefined,
storageFlag: searchForm.storageFlag || undefined
}
if (isCellStorage.value) {
params.storageCertHandled = searchForm.storageCertHandled || undefined
params.insuranceHandled = searchForm.insuranceHandled || undefined
const res = (await fetchProtocolSampleCollectionList(params as any)) as Record<string, unknown>
const list = ((res.list as Record<string, unknown>[]) || []).map((row) => ({
...row,
rowKey: String(row.protocolSampleId || row.sampleCode || Math.random())
}))
tableData.value = list
pagination.total = Number(res.total) || 0
} else {
const res = (await fetchResearchDonationSampleCollectionList(
params as any
)) as Record<string, unknown>
const list = ((res.list as Record<string, unknown>[]) || []).map((row) => ({
...row,
rowKey: String(row.donationSampleId || row.sampleCode || Math.random())
}))
tableData.value = list
pagination.total = Number(res.total) || 0
}
} finally {
loading.value = false
}
}
async function handleExport() {
if (exporting.value || !isCellStorage.value) return
exporting.value = true
try {
const rows = selectedRows.value.length
? selectedRows.value
: await fetchAllPages((page, pageSize) =>
fetchProtocolSampleCollectionList({
page,
pageSize,
keyword: searchForm.keyword || undefined,
collectionFlag: searchForm.collectionFlag || undefined,
receptionFlag: searchForm.receptionFlag || undefined,
qcFlag: searchForm.qcFlag || undefined,
storageFlag: searchForm.storageFlag || undefined,
storageCertHandled: searchForm.storageCertHandled || undefined,
insuranceHandled: searchForm.insuranceHandled || undefined
})
)
if (!rows.length) {
ElMessage.warning('没有可导出的数据')
return
}
await saveCollectionLedgerWorkbook(
'客服表',
CUSTOMER_SERVICE_HEADERS,
buildCustomerServiceLedgerRows(rows),
'客服表'
)
ElMessage.success('导出成功')
} catch {
ElMessage.error('导出失败,请重试')
} finally {
exporting.value = false
}
}
function handleSearch() {
pagination.page = 1
loadData()
}
function handleReset() {
Object.assign(searchForm, {
keyword: '',
collectionFlag: '',
receptionFlag: '',
qcFlag: '',
storageFlag: '',
storageCertHandled: '',
insuranceHandled: ''
})
handleSearch()
}
function handleSelectionChange(rows: Record<string, unknown>[]) {
selectedRows.value = rows
}
function openQuickRegister(kind: QuickRegisterKind) {
if (!selectedRows.value.length) {
ElMessage.warning('请先选择样本')
return
}
if ((kind === 'storage_cert' || kind === 'insurance') && !isCellStorage.value) {
ElMessage.warning('储存证与保险仅支持细胞存储样本')
return
}
quickRegisterKind.value = kind
quickRegisterRows.value = [...selectedRows.value]
quickRegisterVisible.value = true
}
function onQuickRegisterSaved() {
tableRef.value?.clearSelection?.()
selectedRows.value = []
loadData()
}
function handleView(row: Record<string, unknown>) {
highlightSampleCode.value = String(row.sampleCode || '')
if (isCellStorage.value) {
const pid = String(row.collectionProtocolId || '')
if (!pid) {
ElMessage.warning('缺少关联协议单,无法查看')
return
}
viewProtocolId.value = pid
viewProtocolRow.value = {
id: pid,
motherName: row.motherName,
newbornName: row.newbornName
} as CollectionProtocolRow
protocolViewVisible.value = true
return
}
const did = String(row.researchDonationId || '')
if (!did) {
ElMessage.warning('缺少关联捐赠协议单,无法查看')
return
}
viewDonationId.value = did
viewDonationRow.value = {
id: did,
motherName: row.motherName,
newbornName: row.newbornName
} as ResearchDonationRow
donationViewVisible.value = true
}
watchDebounced(
searchForm,
() => {
pagination.page = 1
loadData()
},
{ debounce: 300, deep: true }
)
onMounted(() => {
loadData()
})
</script>
<style scoped lang="scss">
.sample-info-filter-form {
min-width: 0;
}
.unified-sample-info-page .filter-grid.sample-info-filter-grid {
margin-bottom: 0;
align-items: center;
grid-template-columns: minmax(0, 1.5fr) repeat(4, minmax(0, 1fr));
}
.unified-sample-info-page .filter-grid.sample-info-filter-grid.is-cell-storage {
grid-template-columns: minmax(0, 1.5fr) repeat(6, minmax(0, 1fr));
}
.sample-info-filter-form :deep(.el-form-item) {
margin-bottom: 0;
min-width: 0;
}
.sample-info-filter-form :deep(.el-select),
.sample-info-filter-form :deep(.el-input) {
min-width: 0;
}
/* 样本编码上、特殊样本 Tag 下,列内居中(与入库放行审核列表一致) */
.sample-code-col {
display: block;
text-align: center;
:deep(.special-sample-display--block) {
margin-top: 4px;
text-align: center;
.el-tag {
text-align: center;
}
}
}
</style>