市场人员需要把四张列表按采集大表对应工作表导出为 Excel 文本。补预签约列表的地区、医院、样本类型和转账时间,并沿用现有工具栏导出。 Co-authored-by: Cursor <cursoragent@cursor.com>
816 lines
33 KiB
Vue
816 lines
33 KiB
Vue
<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>
|