feat: 个人用户聚合视图 — 客户管理双Tab+四模块数据聚合
- customers 表新增 customer_type 列 (unit/individual) - 客户管理页:单位客户 | 个人用户 Tab 切换 - 个人用户 Tab: 直接内嵌4 Tab 显示所有个人用户的聚合数据 - 个人用户通过业务模块快速新建产生 (customer_type=individual) - 4个业务API新增 customer_type 筛选参数(JOIN customers) - 客户列表排序: 个人用户置顶 Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
Vendored
+2
@@ -31,6 +31,8 @@ declare module 'vue' {
|
||||
ElOption: typeof import('element-plus/es')['ElOption']
|
||||
ElPagination: typeof import('element-plus/es')['ElPagination']
|
||||
ElProgress: typeof import('element-plus/es')['ElProgress']
|
||||
ElRadioButton: typeof import('element-plus/es')['ElRadioButton']
|
||||
ElRadioGroup: typeof import('element-plus/es')['ElRadioGroup']
|
||||
ElRow: typeof import('element-plus/es')['ElRow']
|
||||
ElSelect: typeof import('element-plus/es')['ElSelect']
|
||||
ElSwitch: typeof import('element-plus/es')['ElSwitch']
|
||||
|
||||
@@ -15,7 +15,16 @@ const search = ref('')
|
||||
const filterIndustry = ref('')
|
||||
const filterService = ref('')
|
||||
const filterManagerId = ref('')
|
||||
const customerType = ref('unit')
|
||||
const customers = ref<any[]>([])
|
||||
|
||||
// Individual view tab data
|
||||
const indivTab = ref('visits')
|
||||
const indivVisits = ref<any[]>([])
|
||||
const indivPlans = ref<any[]>([])
|
||||
const indivMini = ref<any[]>([])
|
||||
const indivKeyVisits = ref<any[]>([])
|
||||
const indivLoading = ref(false)
|
||||
const currentPage = ref(1)
|
||||
const pageSize = ref(25)
|
||||
const total = ref(0)
|
||||
@@ -79,6 +88,32 @@ function onPageChange(page: number) { currentPage.value = page; loadCustomers()
|
||||
function onPageSizeChange(size: number) { pageSize.value = size; currentPage.value = 1; loadCustomers() }
|
||||
function onFilterChange() { currentPage.value = 1; loadCustomers() }
|
||||
|
||||
function switchCustomerType(type: string) {
|
||||
customerType.value = type
|
||||
if (type === 'unit') {
|
||||
onFilterChange()
|
||||
} else {
|
||||
loadIndividualData()
|
||||
}
|
||||
}
|
||||
|
||||
async function loadIndividualData() {
|
||||
indivLoading.value = true
|
||||
try {
|
||||
const [visits, plans, mini, keyVisits] = await Promise.all([
|
||||
api.get('/visits/', { params: { customer_type: 'individual' } }),
|
||||
api.get('/work-plans/', { params: { customer_type: 'individual' } }),
|
||||
api.get('/mini-business/', { params: { customer_type: 'individual' } }),
|
||||
api.get('/key-visits/', { params: { customer_type: 'individual' } }),
|
||||
])
|
||||
indivVisits.value = Array.isArray(visits.data) ? visits.data : (visits.data.items || [])
|
||||
indivPlans.value = Array.isArray(plans.data) ? plans.data : (plans.data.items || [])
|
||||
indivMini.value = Array.isArray(mini.data) ? mini.data : (mini.data.items || [])
|
||||
indivKeyVisits.value = Array.isArray(keyVisits.data) ? keyVisits.data : (keyVisits.data.items || [])
|
||||
} catch (_) {}
|
||||
finally { indivLoading.value = false }
|
||||
}
|
||||
|
||||
function buildMonthlyFee(): string {
|
||||
const amt = form.value.fee_amount.trim()
|
||||
if (!amt) return ''
|
||||
@@ -329,6 +364,60 @@ async function handleImport() {
|
||||
<div class="page-rule"></div>
|
||||
</div>
|
||||
|
||||
<!-- Type Toggle -->
|
||||
<div style="margin-bottom:14px">
|
||||
<el-radio-group v-model="customerType" @change="switchCustomerType">
|
||||
<el-radio-button value="unit">单位客户</el-radio-button>
|
||||
<el-radio-button value="individual">个人用户</el-radio-button>
|
||||
</el-radio-group>
|
||||
</div>
|
||||
|
||||
<!-- Individual View: 4 Tabs -->
|
||||
<div v-if="customerType === 'individual'" v-loading="indivLoading">
|
||||
<el-tabs v-model="indivTab">
|
||||
<el-tab-pane label="拜访记录" name="visits">
|
||||
<el-table :data="indivVisits" size="small" max-height="400" v-if="indivVisits.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="250" show-overflow-tooltip />
|
||||
<el-table-column prop="customer_name" label="客户" width="120" />
|
||||
</el-table>
|
||||
<div v-else class="empty-tab">暂无拜访记录</div>
|
||||
</el-tab-pane>
|
||||
<el-tab-pane label="工作计划" name="plans">
|
||||
<el-table :data="indivPlans" size="small" max-height="400" v-if="indivPlans.length">
|
||||
<el-table-column prop="plan_date" label="计划时间" width="110" />
|
||||
<el-table-column prop="plan_content" label="工作计划" min-width="250" show-overflow-tooltip />
|
||||
<el-table-column prop="status" label="状态" width="80" />
|
||||
<el-table-column prop="customer_name" label="客户" width="120" />
|
||||
</el-table>
|
||||
<div v-else class="empty-tab">暂无工作计划</div>
|
||||
</el-tab-pane>
|
||||
<el-tab-pane label="商机跟单" name="mini">
|
||||
<el-table :data="indivMini" size="small" max-height="400" v-if="indivMini.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="200" show-overflow-tooltip />
|
||||
<el-table-column prop="customer_name" label="客户" width="120" />
|
||||
</el-table>
|
||||
<div v-else class="empty-tab">暂无商机记录</div>
|
||||
</el-tab-pane>
|
||||
<el-tab-pane label="要客拜访" name="keyVisits">
|
||||
<el-table :data="indivKeyVisits" size="small" max-height="400" v-if="indivKeyVisits.length">
|
||||
<el-table-column prop="planned_date" label="计划时间" width="110" />
|
||||
<el-table-column prop="description" label="内容" min-width="250" show-overflow-tooltip />
|
||||
<el-table-column prop="urgency_level" label="紧急度" width="80" />
|
||||
<el-table-column prop="customer_name" label="客户" width="120" />
|
||||
</el-table>
|
||||
<div v-else class="empty-tab">暂不要客记录</div>
|
||||
</el-tab-pane>
|
||||
</el-tabs>
|
||||
</div>
|
||||
|
||||
<!-- Unit View: Search + Table -->
|
||||
<template v-if="customerType === 'unit'">
|
||||
|
||||
<!-- Search & Filter -->
|
||||
<el-card style="margin-bottom: 16px;">
|
||||
<el-row :gutter="12" align="middle">
|
||||
@@ -534,6 +623,7 @@ async function handleImport() {
|
||||
<el-button type="primary" :loading="importLoading" @click="handleImport" :disabled="!importFile">确认导入</el-button>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</template>
|
||||
|
||||
<!-- Merge confirmation dialog -->
|
||||
<el-dialog v-model="mergeDialogVisible" title="合并客户" width="520px" :close-on-click-modal="false">
|
||||
|
||||
Reference in New Issue
Block a user