feat(web-admin): show related family and protocols on customer detail
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
1 parent
af5c6e725d
commit
818b809614
4 files changed
+296
-8
No files matched your search
@@ -931,6 +931,12 @@ export function fetchDeleteCustomer(id: string) {
|
||||
return request.del({ url: `/web/admin/customers/${id}` })
|
||||
}
|
||||
|
||||
export function fetchCustomerRelations(id: string) {
|
||||
return request.get<Api.BizCustomer.CustomerRelations>({
|
||||
url: `/web/admin/customers/${id}/relations`
|
||||
})
|
||||
}
|
||||
|
||||
export function fetchCellSampleTypeList(params?: {
|
||||
page?: number
|
||||
pageSize?: number
|
||||
|
||||
@@ -156,7 +156,9 @@
|
||||
</aside>
|
||||
</div>
|
||||
</div>
|
||||
<slot name="append" />
|
||||
<div v-if="$slots.append" class="view-append">
|
||||
<slot name="append" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<template #footer>
|
||||
@@ -311,6 +313,11 @@
|
||||
border-bottom: 1px solid #fecaca;
|
||||
}
|
||||
|
||||
.view-append {
|
||||
padding: 16px 22px 20px;
|
||||
border-top: 1px solid #e2e8f0;
|
||||
}
|
||||
|
||||
.view-hero-icon {
|
||||
width: 72px;
|
||||
height: 72px;
|
||||
|
||||
Vendored
+31
@@ -510,6 +510,37 @@ declare namespace Api {
|
||||
createTime: string
|
||||
updateTime: string
|
||||
}
|
||||
type PartyRole = 'mother' | 'father' | 'newborn'
|
||||
type RelationBizType = 'cell_storage' | 'research_donation'
|
||||
interface RelationMember {
|
||||
role: PartyRole
|
||||
customerId: string
|
||||
name: string
|
||||
phone: string
|
||||
idCardNo: string
|
||||
}
|
||||
interface RelationGroup {
|
||||
bizType: RelationBizType
|
||||
protocolId: string
|
||||
protocolNo: string
|
||||
protocolStatus: string
|
||||
registerDate: string
|
||||
myRole: PartyRole
|
||||
members: RelationMember[]
|
||||
}
|
||||
interface ProtocolSummary {
|
||||
bizType: RelationBizType
|
||||
protocolId: string
|
||||
protocolNo: string
|
||||
protocolStatus: string
|
||||
registerDate: string
|
||||
myRole: PartyRole
|
||||
}
|
||||
interface CustomerRelations {
|
||||
customer: CustomerItem
|
||||
relatedGroups: RelationGroup[]
|
||||
protocols: ProtocolSummary[]
|
||||
}
|
||||
}
|
||||
|
||||
namespace BizCellSampleType {
|
||||
|
||||
@@ -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>
|
||||
Reference in new issue
Block a user