diff --git a/web-admin/src/views/cell-storage/protocol-audit/ProtocolAuditTab.vue b/web-admin/src/views/cell-storage/protocol-audit/ProtocolAuditTab.vue index 4a522d67..8dd3ee1b 100644 --- a/web-admin/src/views/cell-storage/protocol-audit/ProtocolAuditTab.vue +++ b/web-admin/src/views/cell-storage/protocol-audit/ProtocolAuditTab.vue @@ -1,390 +1,359 @@ - + @@ -398,10 +367,16 @@ Delete, Search, RefreshLeft, - CircleCheck + CircleCheck, + CircleClose, + Select, + Switch, + Timer, + Coin } from '@element-plus/icons-vue' import ProtocolFormDialog from '../protocol/ProtocolFormDialog.vue' import ProtocolViewDialog from '../protocol/ProtocolViewDialog.vue' + import ProtocolRefundRecordDialog from '../protocol/ProtocolRefundRecordDialog.vue' import ProtocolAuditDialog, { type ProtocolAuditTarget } from '../protocol/ProtocolAuditDialog.vue' @@ -409,9 +384,6 @@ type StatusChangeAction, type StatusChangeTarget } from '../protocol/ProtocolStatusChangeDialog.vue' - import QcWorkOrderProtocolPaymentPanel from '@/views/quality-inspection/qc-work-order/QcWorkOrderProtocolPaymentPanel.vue' - import SampleCodeWithSpecialDisplay from '@/components/business/sample-code-with-special-display/index.vue' - import type { QcProtocolPaymentSummary } from '@/api/qc-work-order' import { withFullscreenLoading, fullscreenLoadingText } from '@/utils/fullscreen-loading' import { fetchCollectionProtocolList, @@ -423,121 +395,20 @@ formatProtocolDocBatchDeleteConfirm } from '@/utils/protocol-doc-label' import { - protocolStatusDisplayLabel, - protocolStatusTagType, - newbornSexLabel, - sampleStatusLabel, - sampleStatusTagType, - transportTypeLabel, - acceptConclusionLabel, - acceptConclusionTagType, - qcWorkOrderConclusionLabel, - qcWorkOrderConclusionTagType + payModeLabel, + payModeTagType, + transportTypeLabel } from '@/utils/status-tag-registry' - import { maskIdCard } from '@/utils/mask-sensitive' import { loadVoidTypeDict } from '@/utils/void-type-dict' + import { + flattenProtocolAuditRows, + uniqueProtocolsFromFlatRows, + firstPayTimeLines, + type ProtocolAuditFlatRow + } from '@/utils/protocol-audit-flatten' defineOptions({ name: 'CellStorageProtocolAuditTab' }) - type ProtocolSampleTransport = { - transportType?: string - destSiteName?: string - expressCompanyName?: string - waybillNo?: string - carrierName?: string - carrierPhone?: string - etaTime?: string - } - - type ProtocolSampleItem = { - sampleTypeName: string - sampleCode: string - sampleStatus: string - labStorageCode: string - isSpecial?: boolean - specialRemark?: string - transport?: ProtocolSampleTransport | null - receptionAcceptConclusion?: string - receptionRemark?: string - qcConclusion?: string - qcConclusionLabel?: string - } - - function getPaymentSummary(row: CollectionProtocolRow): QcProtocolPaymentSummary | null { - const summary = row.paymentSummary - if (!summary || typeof summary !== 'object') return null - return summary as QcProtocolPaymentSummary - } - - function formatTransportSummary(transport?: ProtocolSampleTransport | null): string { - if (!transport?.transportType) return '—' - const parts: string[] = [transportTypeLabel(String(transport.transportType))] - const dest = String(transport.destSiteName || '').trim() - if (dest) parts.push(dest) - if (transport.transportType === 'kdys') { - const expressName = String(transport.expressCompanyName || '').trim() - if (expressName) parts.push(expressName) - const waybill = String(transport.waybillNo || '').trim() - if (waybill) parts.push(waybill) - } else if (transport.transportType === 'ryxd') { - const carrier = String(transport.carrierName || '').trim() - if (carrier) parts.push(carrier) - const phone = String(transport.carrierPhone || '').trim() - if (phone) parts.push(phone) - } - const eta = String(transport.etaTime || '').trim() - if (eta) parts.push(`预计 ${eta}`) - return parts.join(' · ') - } - - function getSampleItems(row: CollectionProtocolRow): ProtocolSampleItem[] { - const rawItems = row.sampleItems - if (Array.isArray(rawItems) && rawItems.length) { - return rawItems.map((item) => { - const rec = item as Record - const transportRaw = rec.transport - const transport = - transportRaw && typeof transportRaw === 'object' - ? (transportRaw as ProtocolSampleTransport) - : null - return { - sampleTypeName: String(rec.sampleTypeName || '').trim(), - sampleCode: String(rec.sampleCode || '').trim(), - sampleStatus: String(rec.sampleStatus || '').trim(), - labStorageCode: String(rec.labStorageCode || '').trim(), - isSpecial: !!rec.isSpecial, - specialRemark: String(rec.specialRemark || '').trim(), - transport, - receptionAcceptConclusion: String(rec.receptionAcceptConclusion || '').trim(), - receptionRemark: String(rec.receptionRemark || '').trim(), - qcConclusion: String(rec.qcConclusion || '').trim(), - qcConclusionLabel: String(rec.qcConclusionLabel || '').trim() - } - }) - } - const types = String(row.sampleTypeNames || '') - .split(',') - .map((s) => s.trim()) - .filter(Boolean) - const codes = String(row.sampleCodes || '') - .split(',') - .map((s) => s.trim()) - .filter(Boolean) - const labCodes = String(row.labStorageCodes || '') - .split(',') - .map((s) => s.trim()) - const len = Math.max(types.length, codes.length) - if (!len) return [] - return Array.from({ length: len }, (_, i) => ({ - sampleTypeName: types[i] || '', - sampleCode: codes[i] || '', - sampleStatus: '', - labStorageCode: labCodes[i] || '', - transport: null - })) - } - - /* ====== 列表 ====== */ const searchForm = reactive<{ keyword: string; protocolStatus: string }>({ keyword: '', protocolStatus: '' @@ -545,19 +416,29 @@ const createTimeRange = ref<[string, string] | null>(null) const sampleCollectTimeRange = ref<[string, string] | null>(null) const tableData = ref([]) - const selectedRows = ref([]) + const selectedFlatRows = ref([]) const loading = ref(false) const pagination = reactive({ page: 1, pageSize: 10, total: 0 }) - const isRowSelected = (id: string) => selectedRows.value.some((r) => String(r.id) === id) + const flatRows = computed(() => flattenProtocolAuditRows(tableData.value)) + const selectedProtocolCount = computed( + () => uniqueProtocolsFromFlatRows(selectedFlatRows.value).length + ) - const toggleRowSelection = (row: CollectionProtocolRow, checked: boolean) => { - const id = String(row.id) - if (checked) { - if (!isRowSelected(id)) selectedRows.value = [...selectedRows.value, row] - return - } - selectedRows.value = selectedRows.value.filter((r) => String(r.id) !== id) + function selectedProtocols(): CollectionProtocolRow[] { + return uniqueProtocolsFromFlatRows(selectedFlatRows.value) + } + + function handleSelectionChange(rows: ProtocolAuditFlatRow[]) { + selectedFlatRows.value = rows || [] + } + + function handleRowDblClick(row: ProtocolAuditFlatRow) { + handleView(row.protocol) + } + + function payLines(row: ProtocolAuditFlatRow) { + return firstPayTimeLines(row.paymentSummary) } async function loadData() { @@ -576,7 +457,7 @@ tableData.value = res.list || [] pagination.total = res.total ?? 0 const validIds = new Set(tableData.value.map((r) => String(r.id))) - selectedRows.value = selectedRows.value.filter((r) => validIds.has(String(r.id))) + selectedFlatRows.value = selectedFlatRows.value.filter((r) => validIds.has(r.protocolId)) } finally { loading.value = false } @@ -596,7 +477,6 @@ loadData() } - /* ====== 查看弹窗 ====== */ const viewVisible = ref(false) const viewProtocolId = ref(null) const viewRow = ref(null) @@ -607,7 +487,6 @@ viewVisible.value = true } - /* ====== 新增/编辑弹窗 ====== */ const formDialogVisible = ref(false) const formDialogEditId = ref(null) const formDialogRow = ref(null) @@ -619,7 +498,7 @@ } function handleEdit(row: CollectionProtocolRow) { - formDialogEditId.value = row.id + formDialogEditId.value = row.id as string formDialogRow.value = row formDialogVisible.value = true } @@ -628,12 +507,29 @@ loadData() } - /* ====== 审核 ====== */ + const refundDialogVisible = ref(false) + const refundProtocolId = ref('') + const refundProtocolLabel = ref('') + + function openRefundDialog() { + const rows = selectedProtocols() + if (rows.length !== 1) { + ElMessage.warning('请先勾选一条协议单再登记退费') + return + } + const row = rows[0] + refundProtocolId.value = String(row.id || '') + const name = String(row.motherName || '').trim() + const no = String(row.protocolNo || '').trim() + refundProtocolLabel.value = [name, no].filter(Boolean).join(' / ') + refundDialogVisible.value = true + } + const auditDialogVisible = ref(false) const auditTargets = ref([]) function openAuditDialog(row?: CollectionProtocolRow) { - const rows = row ? [row] : selectedRows.value + const rows = row ? [row] : selectedProtocols() if (!rows.length) { ElMessage.warning('请先选择要审核的协议单') return @@ -646,25 +542,18 @@ } function onAuditSubmitted() { - selectedRows.value = [] + selectedFlatRows.value = [] loadData() } - /* ====== 状态变更(生效/过期/作废/转科研) ====== */ const statusChangeDialogVisible = ref(false) const statusChangeAction = ref('activate') const statusChangeTargets = ref([]) function openStatusChangeDialog(action: StatusChangeAction, row?: CollectionProtocolRow) { - const rows = row ? [row] : selectedRows.value + const rows = row ? [row] : selectedProtocols() if (!rows.length) { - const labels: Record = { - activate: '协议生效', - expire: '协议过期', - void: '协议作废', - toResearch: '协议转科研' - } - ElMessage.warning(`请先选择要操作的协议单`) + ElMessage.warning('请先选择要操作的协议单') return } statusChangeAction.value = action @@ -676,11 +565,10 @@ } function onStatusChangeSubmitted() { - selectedRows.value = [] + selectedFlatRows.value = [] loadData() } - /* ====== 删除 ====== */ function handleDelete(row: CollectionProtocolRow) { ElMessageBox.confirm(formatProtocolDocDeleteConfirm('cell_storage', row), '删除确认', { confirmButtonText: '确定', @@ -695,9 +583,10 @@ } async function handleBatchDelete() { - if (!selectedRows.value.length) return + const rows = selectedProtocols() + if (!rows.length) return await ElMessageBox.confirm( - formatProtocolDocBatchDeleteConfirm('cell_storage', selectedRows.value.length), + formatProtocolDocBatchDeleteConfirm('cell_storage', rows.length), '批量删除', { confirmButtonText: '确定', @@ -705,17 +594,16 @@ type: 'warning' } ) - const rows = [...selectedRows.value] await withFullscreenLoading(async () => { for (const row of rows) { await fetchDeleteCollectionProtocol(String(row.id)) } ElMessage.success('批量删除成功') - selectedRows.value = [] + selectedFlatRows.value = [] loadData() }, fullscreenLoadingText.batchDelete) } - // 输入检索条件时自动搜索(防抖 300ms) + watchDebounced( [searchForm, createTimeRange, sampleCollectTimeRange], () => { @@ -732,375 +620,26 @@