feat: 客户详情弹窗新增拜访/计划/商机/要客四个关联数据Tab

点击客户名 → 800px 弹窗,顶部显示基本信息,下方 el-tabs 切换:
- 拜访记录 / 工作计划 / 商机跟单 / 要客拜访
- 各 Tab 并行加载对应 customer_id 筛选数据

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
2026-07-12 21:03:10 +08:00
parent 9354752b89
commit 05d33d518f
+100 -19
View File
@@ -36,6 +36,12 @@ const feeCustomUnit = ref('')
const detailVisible = ref(false)
const detailCustomer = ref<any>(null)
const detailActiveTab = ref('visits')
const detailVisits = ref<any[]>([])
const detailPlans = ref<any[]>([])
const detailMini = ref<any[]>([])
const detailKeyVisits = ref<any[]>([])
const detailLoading = ref(false)
const managers = ref<any[]>([])
const selectedIds = ref<string[]>([])
@@ -205,7 +211,30 @@ function mgrTextColor(name: string) {
}
async function openDetail(customer: any) {
try { const res = await customersApi.get(customer.id); detailCustomer.value = res.data; detailVisible.value = true } catch (_) {}
try {
const res = await customersApi.get(customer.id)
detailCustomer.value = res.data
detailActiveTab.value = 'visits'
detailVisible.value = true
loadDetailData(customer.id)
} catch (_) {}
}
async function loadDetailData(customerId: string) {
detailLoading.value = true
try {
const [visits, plans, mini, keyVisits] = await Promise.all([
api.get('/visits/', { params: { customer_id: customerId } }),
api.get('/work-plans/', { params: { customer_id: customerId } }),
api.get('/mini-business/', { params: { customer_id: customerId } }),
api.get('/key-visits/', { params: { customer_id: customerId } }),
])
detailVisits.value = Array.isArray(visits.data) ? visits.data : (visits.data.items || [])
detailPlans.value = Array.isArray(plans.data) ? plans.data : (plans.data.items || [])
detailMini.value = Array.isArray(mini.data) ? mini.data : (mini.data.items || [])
detailKeyVisits.value = Array.isArray(keyVisits.data) ? keyVisits.data : (keyVisits.data.items || [])
} catch (_) {}
finally { detailLoading.value = false }
}
async function handleDelete(customer: any) {
@@ -403,25 +432,73 @@ async function handleImport() {
</el-dialog>
<!-- Detail Dialog -->
<el-dialog v-model="detailVisible" title="客户详情" width="500px">
<template v-if="detailCustomer">
<el-descriptions :column="1" border>
<el-descriptions-item label="单位名称">{{ detailCustomer.name }}</el-descriptions-item>
<el-descriptions-item label="行业">{{ detailCustomer.industry || '-' }}</el-descriptions-item>
<el-descriptions-item label="地址">{{ detailCustomer.address || '-' }}</el-descriptions-item>
<el-descriptions-item label="在用业务">{{ detailCustomer.in_use_services || '-' }}</el-descriptions-item>
<el-descriptions-item label="收支费用">{{ detailCustomer.monthly_fee || '-' }}</el-descriptions-item>
<el-descriptions-item label="备注">{{ detailCustomer.remarks || '-' }}</el-descriptions-item>
<el-descriptions-item label="客户经理">{{ detailCustomer.primary_manager_name || '-' }}</el-descriptions-item>
</el-descriptions>
<h4 style="margin-top:16px; font-family: var(--font-heading); color: var(--ink)">联系人</h4>
<div v-if="detailCustomer.contacts?.length">
<el-tag v-for="c in detailCustomer.contacts" :key="c.id" style="margin:4px">
{{ c.name }}{{ c.phone ? ' · '+c.phone : '' }}{{ c.role_desc ? ' ('+c.role_desc+')' : '' }}
</el-tag>
<el-dialog v-model="detailVisible" :title="detailCustomer?.name || '客户详情'" width="800px" v-if="detailCustomer">
<!-- Basic Info -->
<div class="cust-info-bar">
<span class="cust-info-item">行业{{ detailCustomer.industry || '-' }}</span>
<span class="cust-info-sep">|</span>
<span class="cust-info-item">地址{{ detailCustomer.address || '-' }}</span>
<span class="cust-info-sep">|</span>
<span class="cust-info-item">费用{{ detailCustomer.monthly_fee || '-' }}</span>
<span class="cust-info-sep">|</span>
<span class="cust-info-item">客户经理{{ detailCustomer.primary_manager_name || '-' }}</span>
</div>
<div v-else style="color: var(--c-text-muted); text-align:center; padding:12px">暂无联系人</div>
</template>
<div v-if="detailCustomer.in_use_services" class="cust-info-bar" style="margin-top:4px">
<span class="cust-info-item">在用业务{{ detailCustomer.in_use_services }}</span>
</div>
<div v-if="detailCustomer.contacts?.length" class="cust-info-bar" style="margin-top:4px">
<span class="cust-info-item">联系人{{ detailCustomer.contacts.map((c:any) => c.name + (c.phone ? ' '+c.phone : '')).join('、') }}</span>
</div>
<!-- Tabs -->
<el-tabs v-model="detailActiveTab" style="margin-top:16px">
<el-tab-pane label="拜访记录" name="visits">
<div v-loading="detailLoading">
<el-table :data="detailVisits" size="small" max-height="300" v-if="detailVisits.length">
<el-table-column prop="visit_date" label="日期" width="110" />
<el-table-column prop="visit_method" label="方式" width="80" />
<el-table-column prop="communication_content" label="沟通内容" min-width="200" show-overflow-tooltip />
<el-table-column prop="manager_name" label="客户经理" width="90" />
</el-table>
<div v-else class="empty-tab">暂无拜访记录</div>
</div>
</el-tab-pane>
<el-tab-pane label="工作计划" name="plans">
<div v-loading="detailLoading">
<el-table :data="detailPlans" size="small" max-height="300" v-if="detailPlans.length">
<el-table-column prop="plan_date" label="计划时间" width="110" />
<el-table-column prop="plan_content" label="工作计划" min-width="220" show-overflow-tooltip />
<el-table-column prop="status" label="状态" width="90" />
<el-table-column prop="manager_name" label="客户经理" width="90" />
</el-table>
<div v-else class="empty-tab">暂无工作计划</div>
</div>
</el-tab-pane>
<el-tab-pane label="商机跟单" name="mini">
<div v-loading="detailLoading">
<el-table :data="detailMini" size="small" max-height="300" v-if="detailMini.length">
<el-table-column prop="product_type" label="产品类型" width="110" />
<el-table-column prop="amount" label="金额" width="100" />
<el-table-column prop="status" label="状态" width="80" />
<el-table-column prop="follow_up_detail" label="跟进内容" min-width="180" show-overflow-tooltip />
<el-table-column prop="manager_name" label="客户经理" width="90" />
</el-table>
<div v-else class="empty-tab">暂无商机记录</div>
</div>
</el-tab-pane>
<el-tab-pane label="要客拜访" name="keyVisits">
<div v-loading="detailLoading">
<el-table :data="detailKeyVisits" size="small" max-height="300" v-if="detailKeyVisits.length">
<el-table-column prop="planned_date" label="计划时间" width="110" />
<el-table-column prop="description" label="内容" min-width="200" show-overflow-tooltip />
<el-table-column prop="urgency_level" label="紧急度" width="80" />
<el-table-column prop="manager_name" label="客户经理" width="90" />
</el-table>
<div v-else class="empty-tab">暂不要客记录</div>
</div>
</el-tab-pane>
</el-tabs>
<template #footer>
<el-button v-if="!auth.isLeader" type="primary" @click="detailVisible=false; openEdit(detailCustomer)">编辑</el-button>
<el-button v-if="auth.isDirector" type="danger" @click="detailVisible=false; handleDelete(detailCustomer)">删除</el-button>
@@ -499,4 +576,8 @@ async function handleImport() {
}
.page-rule { width: 32px; height: 3px; background: var(--gold); margin-top: 8px; }
.header-btns { display: flex; gap: 8px; }
.cust-info-bar { display: flex; align-items: center; gap: 4px; font-size: 13px; color: var(--warm-gray); }
.cust-info-item { white-space: nowrap; }
.cust-info-sep { color: var(--gold); margin: 0 4px; }
.empty-tab { text-align: center; color: var(--c-text-muted); padding: 24px 0 12px; font-size: 13px; }
</style>