|
|
@@ -3,10 +3,6 @@
|
|
|
<template>
|
|
|
<div class="page-container">
|
|
|
<section class="panel">
|
|
|
- <div class="panel-top">
|
|
|
- <h2>客户列表</h2>
|
|
|
- </div>
|
|
|
-
|
|
|
<div class="filter-block">
|
|
|
<el-form :model="searchForm" class="filter-form" @submit.prevent>
|
|
|
<div class="filter-grid">
|
|
|
@@ -57,6 +53,7 @@
|
|
|
:disabled="selectedRows.length === 0"
|
|
|
@click="handleBatchDelete"
|
|
|
>
|
|
|
+ <el-icon><Delete /></el-icon>
|
|
|
批量删除
|
|
|
</el-button>
|
|
|
</div>
|
|
|
@@ -253,6 +250,108 @@
|
|
|
:icon="User"
|
|
|
:meta="`身份证:${viewRow?.idCardNo || '—'}${viewRow?.createTime ? ' · ' + viewRow.createTime : ''}`"
|
|
|
:sections="viewSections"
|
|
|
+ :loading="relationsLoading"
|
|
|
+ width="820px"
|
|
|
+ >
|
|
|
+ <template #append>
|
|
|
+ <div class="customer-relations" v-loading="relationsLoading">
|
|
|
+ <h3 class="rel-section-title">关联客户</h3>
|
|
|
+ <div v-if="!(relations?.relatedGroups || []).length" class="rel-empty">暂无关联协议</div>
|
|
|
+ <div
|
|
|
+ v-for="group in relations?.relatedGroups || []"
|
|
|
+ :key="`${group.bizType}-${group.protocolId}`"
|
|
|
+ class="rel-group"
|
|
|
+ >
|
|
|
+ <div class="rel-group-head">
|
|
|
+ <el-tag size="small" :type="group.bizType === 'cell_storage' ? 'primary' : 'success'">
|
|
|
+ {{ bizTypeLabel(group.bizType) }}
|
|
|
+ </el-tag>
|
|
|
+ <button type="button" class="rel-link" @click="openProtocol(group)">
|
|
|
+ {{ group.protocolNo || '—' }}
|
|
|
+ </button>
|
|
|
+ <span class="rel-muted">本客户角色:{{ roleLabel(group.myRole) }}</span>
|
|
|
+ </div>
|
|
|
+ <el-table :data="group.members" size="small" class="rel-member-table" stripe>
|
|
|
+ <el-table-column label="角色" min-width="90" align="center" header-align="center">
|
|
|
+ <template #default="{ row }">
|
|
|
+ {{ roleLabel(row.role) }}
|
|
|
+ <el-tag v-if="row.customerId && row.customerId === viewRow?.id" size="small" type="info">
|
|
|
+ 本人
|
|
|
+ </el-tag>
|
|
|
+ </template>
|
|
|
+ </el-table-column>
|
|
|
+ <el-table-column label="姓名" min-width="110" align="center" header-align="center">
|
|
|
+ <template #default="{ row }">
|
|
|
+ <button
|
|
|
+ v-if="row.customerId && row.customerId !== viewRow?.id"
|
|
|
+ type="button"
|
|
|
+ class="rel-link"
|
|
|
+ @click="openRelatedCustomer(row)"
|
|
|
+ >
|
|
|
+ {{ row.name || '—' }}
|
|
|
+ </button>
|
|
|
+ <span v-else>{{ row.name || '—' }}</span>
|
|
|
+ </template>
|
|
|
+ </el-table-column>
|
|
|
+ <el-table-column prop="phone" label="电话" min-width="120" align="center" header-align="center">
|
|
|
+ <template #default="{ row }">{{ row.phone || '—' }}</template>
|
|
|
+ </el-table-column>
|
|
|
+ <el-table-column prop="idCardNo" label="身份证" min-width="160" align="center" header-align="center">
|
|
|
+ <template #default="{ row }">{{ row.idCardNo || '—' }}</template>
|
|
|
+ </el-table-column>
|
|
|
+ </el-table>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <h3 class="rel-section-title rel-section-title--gap">关联协议</h3>
|
|
|
+ <div v-if="!(relations?.protocols || []).length" class="rel-empty">暂无关联协议</div>
|
|
|
+ <el-table
|
|
|
+ v-else
|
|
|
+ :data="relations?.protocols || []"
|
|
|
+ size="small"
|
|
|
+ class="rel-protocol-table"
|
|
|
+ stripe
|
|
|
+ >
|
|
|
+ <el-table-column label="协议号" min-width="140" align="center" header-align="center">
|
|
|
+ <template #default="{ row }">
|
|
|
+ <button type="button" class="rel-link" @click="openProtocol(row)">
|
|
|
+ {{ row.protocolNo || '—' }}
|
|
|
+ </button>
|
|
|
+ </template>
|
|
|
+ </el-table-column>
|
|
|
+ <el-table-column label="类型" min-width="110" align="center" header-align="center">
|
|
|
+ <template #default="{ row }">
|
|
|
+ <el-tag size="small" :type="row.bizType === 'cell_storage' ? 'primary' : 'success'">
|
|
|
+ {{ bizTypeLabel(row.bizType) }}
|
|
|
+ </el-tag>
|
|
|
+ </template>
|
|
|
+ </el-table-column>
|
|
|
+ <el-table-column label="状态" min-width="100" align="center" header-align="center">
|
|
|
+ <template #default="{ row }">
|
|
|
+ <el-tag size="small" :type="protocolTagType(row)">
|
|
|
+ {{ protocolLabel(row) }}
|
|
|
+ </el-tag>
|
|
|
+ </template>
|
|
|
+ </el-table-column>
|
|
|
+ <el-table-column label="日期" min-width="110" align="center" header-align="center">
|
|
|
+ <template #default="{ row }">{{ formatRegisterDate(row.registerDate) }}</template>
|
|
|
+ </el-table-column>
|
|
|
+ <el-table-column label="本客户角色" min-width="100" align="center" header-align="center">
|
|
|
+ <template #default="{ row }">{{ roleLabel(row.myRole) }}</template>
|
|
|
+ </el-table-column>
|
|
|
+ </el-table>
|
|
|
+ </div>
|
|
|
+ </template>
|
|
|
+ </SystemViewDialog>
|
|
|
+
|
|
|
+ <ProtocolViewDialog
|
|
|
+ v-model="protocolViewVisible"
|
|
|
+ :protocol-id="viewProtocolId"
|
|
|
+ show-audit-history
|
|
|
+ />
|
|
|
+ <DonationViewDialog
|
|
|
+ v-model="donationViewVisible"
|
|
|
+ :donation-id="viewDonationId"
|
|
|
+ show-audit-history
|
|
|
/>
|
|
|
</section>
|
|
|
</div>
|
|
|
@@ -268,10 +367,19 @@
|
|
|
fetchCustomerList,
|
|
|
fetchCreateCustomer,
|
|
|
fetchUpdateCustomer,
|
|
|
- fetchDeleteCustomer
|
|
|
+ fetchDeleteCustomer,
|
|
|
+ fetchCustomerRelations
|
|
|
} from '@/api/admin'
|
|
|
import SystemViewDialog from '@/components/business/system-view-dialog/index.vue'
|
|
|
- import { sexLabel } from '@/utils/status-tag-registry'
|
|
|
+ import ProtocolViewDialog from '@/views/cell-storage/protocol/ProtocolViewDialog.vue'
|
|
|
+ import DonationViewDialog from '@/views/research-donation/list/DonationViewDialog.vue'
|
|
|
+ import {
|
|
|
+ sexLabel,
|
|
|
+ protocolStatusDisplayLabel,
|
|
|
+ protocolStatusTagType,
|
|
|
+ donationStatusDisplayLabel,
|
|
|
+ donationStatusTagType
|
|
|
+ } from '@/utils/status-tag-registry'
|
|
|
|
|
|
defineOptions({ name: 'BizCustomer' })
|
|
|
|
|
|
@@ -291,6 +399,13 @@
|
|
|
// ---- 查看弹窗 ----
|
|
|
const viewVisible = ref(false)
|
|
|
const viewRow = ref<Api.BizCustomer.CustomerItem | null>(null)
|
|
|
+ const relationsLoading = ref(false)
|
|
|
+ const relations = ref<Api.BizCustomer.CustomerRelations | null>(null)
|
|
|
+ const protocolViewVisible = ref(false)
|
|
|
+ const viewProtocolId = ref<string | null>(null)
|
|
|
+ const donationViewVisible = ref(false)
|
|
|
+ const viewDonationId = ref<string | null>(null)
|
|
|
+
|
|
|
const viewSections = computed(() => {
|
|
|
const row = viewRow.value
|
|
|
if (!row) return []
|
|
|
@@ -310,9 +425,81 @@
|
|
|
}
|
|
|
]
|
|
|
})
|
|
|
- function handleView(row: Api.BizCustomer.CustomerItem) {
|
|
|
+
|
|
|
+ function roleLabel(role: string) {
|
|
|
+ if (role === 'mother') return '母亲'
|
|
|
+ if (role === 'father') return '父亲'
|
|
|
+ if (role === 'newborn') return '子女'
|
|
|
+ return role || '—'
|
|
|
+ }
|
|
|
+
|
|
|
+ function bizTypeLabel(bizType: string) {
|
|
|
+ return bizType === 'research_donation' ? '科研捐赠' : '细胞存储'
|
|
|
+ }
|
|
|
+
|
|
|
+ function protocolLabel(row: Api.BizCustomer.ProtocolSummary) {
|
|
|
+ return row.bizType === 'research_donation'
|
|
|
+ ? donationStatusDisplayLabel(row.protocolStatus)
|
|
|
+ : protocolStatusDisplayLabel(row.protocolStatus)
|
|
|
+ }
|
|
|
+
|
|
|
+ function protocolTagType(row: Api.BizCustomer.ProtocolSummary) {
|
|
|
+ return row.bizType === 'research_donation'
|
|
|
+ ? donationStatusTagType(row.protocolStatus)
|
|
|
+ : protocolStatusTagType(row.protocolStatus)
|
|
|
+ }
|
|
|
+
|
|
|
+ function formatRegisterDate(val: string) {
|
|
|
+ const s = (val || '').trim()
|
|
|
+ if (/^\d{8}$/.test(s)) return `${s.slice(0, 4)}-${s.slice(4, 6)}-${s.slice(6, 8)}`
|
|
|
+ return formatDisplayOrDash(s)
|
|
|
+ }
|
|
|
+
|
|
|
+ async function handleView(row: Api.BizCustomer.CustomerItem) {
|
|
|
viewRow.value = row
|
|
|
viewVisible.value = true
|
|
|
+ relationsLoading.value = true
|
|
|
+ relations.value = null
|
|
|
+ try {
|
|
|
+ const data = await fetchCustomerRelations(row.id)
|
|
|
+ relations.value = data
|
|
|
+ if (data?.customer) viewRow.value = data.customer
|
|
|
+ } catch {
|
|
|
+ /* 全局处理 */
|
|
|
+ } finally {
|
|
|
+ relationsLoading.value = false
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ function openRelatedCustomer(member: Api.BizCustomer.RelationMember) {
|
|
|
+ if (!member.customerId) return
|
|
|
+ void handleView({
|
|
|
+ id: member.customerId,
|
|
|
+ customerName: member.name || '',
|
|
|
+ sex: '2',
|
|
|
+ idCardNo: member.idCardNo || '',
|
|
|
+ age: null,
|
|
|
+ phoneNo: member.phone || '',
|
|
|
+ customerTags: '',
|
|
|
+ tenantNo: '',
|
|
|
+ orderNum: 0,
|
|
|
+ remark: '',
|
|
|
+ createTime: '',
|
|
|
+ updateTime: ''
|
|
|
+ })
|
|
|
+ }
|
|
|
+
|
|
|
+ function openProtocol(
|
|
|
+ row: Pick<Api.BizCustomer.ProtocolSummary, 'bizType' | 'protocolId'>
|
|
|
+ ) {
|
|
|
+ if (!row.protocolId) return
|
|
|
+ if (row.bizType === 'research_donation') {
|
|
|
+ viewDonationId.value = row.protocolId
|
|
|
+ donationViewVisible.value = true
|
|
|
+ return
|
|
|
+ }
|
|
|
+ viewProtocolId.value = row.protocolId
|
|
|
+ protocolViewVisible.value = true
|
|
|
}
|
|
|
|
|
|
const formData = reactive({
|
|
|
@@ -478,4 +665,61 @@
|
|
|
margin-bottom: 0;
|
|
|
}
|
|
|
}
|
|
|
+
|
|
|
+ .customer-relations {
|
|
|
+ .rel-section-title {
|
|
|
+ margin: 0 0 10px;
|
|
|
+ font-size: 14px;
|
|
|
+ font-weight: 600;
|
|
|
+ color: #0f172a;
|
|
|
+ }
|
|
|
+
|
|
|
+ .rel-section-title--gap {
|
|
|
+ margin-top: 18px;
|
|
|
+ }
|
|
|
+
|
|
|
+ .rel-empty {
|
|
|
+ padding: 8px 0 4px;
|
|
|
+ color: var(--mint-text-muted, #64748b);
|
|
|
+ font-size: 13px;
|
|
|
+ }
|
|
|
+
|
|
|
+ .rel-group {
|
|
|
+ margin-bottom: 14px;
|
|
|
+ padding: 10px 12px;
|
|
|
+ border: 1px solid #e2e8f0;
|
|
|
+ border-radius: 8px;
|
|
|
+ background: #f8fafc;
|
|
|
+ }
|
|
|
+
|
|
|
+ .rel-group-head {
|
|
|
+ display: flex;
|
|
|
+ flex-wrap: wrap;
|
|
|
+ align-items: center;
|
|
|
+ gap: 8px;
|
|
|
+ margin-bottom: 8px;
|
|
|
+ }
|
|
|
+
|
|
|
+ .rel-muted {
|
|
|
+ color: var(--mint-text-muted, #64748b);
|
|
|
+ font-size: 12px;
|
|
|
+ }
|
|
|
+
|
|
|
+ .rel-link {
|
|
|
+ padding: 0;
|
|
|
+ border: 0;
|
|
|
+ background: transparent;
|
|
|
+ color: var(--primary, #10b981);
|
|
|
+ cursor: pointer;
|
|
|
+ font: inherit;
|
|
|
+ text-decoration: underline;
|
|
|
+ text-underline-offset: 2px;
|
|
|
+ }
|
|
|
+
|
|
|
+ .rel-member-table,
|
|
|
+ .rel-protocol-table {
|
|
|
+ width: 100%;
|
|
|
+ background: #fff;
|
|
|
+ }
|
|
|
+ }
|
|
|
</style>
|