diff --git a/src/api/crm/statistics/customer.ts b/src/api/crm/statistics/customer.ts index f048d174..2c801bb6 100644 --- a/src/api/crm/statistics/customer.ts +++ b/src/api/crm/statistics/customer.ts @@ -1,53 +1,116 @@ import request from '@/config/axios' -export interface StatisticsCustomerRespVO { - count: number - cycle: number - category: string +export interface CrmStatisticsCustomerSummaryByDateRespVO { + time: string + customerCreateCount: number + customerDealCount: number } -// 客户总量分析 API -export const StatisticsCustomerApi = { - // 客户总量(新建) - getTotalCustomerCount: (params: any) => { - return request.get({ - url: '/crm/statistics-customer/get-total-customer-count', - params - }) - }, - // 客户总量(成交) - getDealTotalCustomerCount: (params: any) => { - return request.get({ - url: '/crm/statistics-customer/get-deal-total-customer-count', - params - }) - }, - // 获取客户跟进次数 - getRecordCount: (params: any) => { - return request.get({ - url: '/crm/statistics-customer/get-record-count', - params - }) - }, - // 获取客户跟进次数 - getDistinctRecordCount: (params: any) => { - return request.get({ - url: '/crm/statistics-customer/get-distinct-record-count', - params - }) - }, - // 获取客户跟进方式统计数 - getRecordTypeCount: (params: any) => { - return request.get({ - url: '/crm/statistics-customer/get-record-type-count', - params - }) - }, - // 获取客户成交周期 - getCustomerCycle: (params: any) => { - return request.get({ - url: '/crm/statistics-customer/get-customer-cycle', - params - }) - }, +export interface CrmStatisticsCustomerSummaryByUserRespVO { + ownerUserName: string + customerCreateCount: number + customerDealCount: number + contractPrice: number + receivablePrice: number +} + +export interface CrmStatisticsFollowupSummaryByDateRespVO { + time: string + followupRecordCount: number + followupCustomerCount: number +} + +export interface CrmStatisticsFollowupSummaryByUserRespVO { + ownerUserName: string + followupRecordCount: number + followupCustomerCount: number +} + +export interface CrmStatisticsFollowupSummaryByTypeRespVO { + followupType: string + followupRecordCount: number +} + +export interface CrmStatisticsCustomerContractSummaryRespVO { + customerName: string + contractName: string + totalPrice: number + receivablePrice: number + customerType: string + customerSource: string + ownerUserName: string + creatorUserName: string + createTime: Date + orderDate: Date +} + +export interface CrmStatisticsCustomerDealCycleByDateRespVO { + time: string + customerDealCycle: number +} + +export interface CrmStatisticsCustomerDealCycleByUserRespVO { + ownerUserName: string + customerDealCycle: number + customerDealCount: number +} + +// 客户分析 API +export const StatisticsCustomerApi = { + // 1.1 客户总量分析(按日期) + getCustomerSummaryByDate: (params: any) => { + return request.get({ + url: '/crm/statistics-customer/get-customer-summary-by-date', + params + }) + }, + // 1.2 客户总量分析(按用户) + getCustomerSummaryByUser: (params: any) => { + return request.get({ + url: '/crm/statistics-customer/get-customer-summary-by-user', + params + }) + }, + // 2.1 客户跟进次数分析(按日期) + getFollowupSummaryByDate: (params: any) => { + return request.get({ + url: '/crm/statistics-customer/get-followup-summary-by-date', + params + }) + }, + // 2.2 客户跟进次数分析(按用户) + getFollowupSummaryByUser: (params: any) => { + return request.get({ + url: '/crm/statistics-customer/get-followup-summary-by-user', + params + }) + }, + // 3.1 获取客户跟进方式统计数 + getFollowupSummaryByType: (params: any) => { + return request.get({ + url: '/crm/statistics-customer/get-followup-summary-by-type', + params + }) + }, + // 4.1 合同摘要信息(客户转化率页面) + getContractSummary: (params: any) => { + return request.get({ + url: '/crm/statistics-customer/get-followup-summary-by-type', + params + }) + }, + // 5.1 获取客户成交周期(按日期) + getCustomerDealCycleByDate: (params: any) => { + return request.get({ + url: '/crm/statistics-customer/get-customer-deal-cycle-by-date', + params + }) + }, + // 5.2 获取客户成交周期(按用户) + getCustomerDealCycleByUser: (params: any) => { + return request.get({ + url: '/crm/statistics-customer/get-customer-deal-cycle-by-user', + params + }) + } } diff --git a/src/views/crm/statistics/customer/components/ConversionRate.vue b/src/views/crm/statistics/customer/components/ConversionRate.vue deleted file mode 100644 index 5f3ad591..00000000 --- a/src/views/crm/statistics/customer/components/ConversionRate.vue +++ /dev/null @@ -1,112 +0,0 @@ -<!-- 客户转化率分析 --> -<template> - <!-- Echarts图 --> - <el-card shadow="never"> - <el-skeleton :loading="loading" animated> - <Echart :height="500" :options="echartsOption" /> - </el-skeleton> - </el-card> - - <!-- 统计列表 --> - <el-card shadow="never" class="mt-16px"> - <el-table v-loading="loading" :data="list"> - <el-table-column label="序号" align="center" type="index" width="80" /> - <el-table-column label="日期" align="center" prop="category" min-width="200" /> - <el-table-column label="新增客户数" align="center" prop="customerCount" min-width="200" /> - <el-table-column label="成交客户数" align="center" prop="dealCustomerCount" min-width="200" /> - <el-table-column label="转化率(%)" align="center" prop="conversionRate" min-width="200" /> - </el-table> - </el-card> -</template> -<script setup lang="ts"> -import { StatisticsCustomerApi, StatisticsCustomerRespVO } from '@/api/crm/statistics/customer' -import { EChartsOption } from 'echarts' -import { round } from 'lodash-es'; - -defineOptions({ name: 'ConversionRate' }) -const props = defineProps<{ queryParams: any }>() // 搜索参数 - -const loading = ref(false) // 加载中 -const list = ref<StatisticsCustomerRespVO[]>([]) // 列表的数据 - -/** 柱状图配置:纵向 */ -const echartsOption = reactive<EChartsOption>({ - grid: { - left: 20, - right: 20, - bottom: 20, - containLabel: true - }, - legend: { }, - series: [ - { - name: '客户转化率', - type: 'line', - data: [] - }, - ], - toolbox: { - feature: { - dataZoom: { - xAxisIndex: false // 数据区域缩放:Y 轴不缩放 - }, - brush: { - type: ['lineX', 'clear'] // 区域缩放按钮、还原按钮 - }, - saveAsImage: { show: true, name: '客户转化率分析' } // 保存为图片 - } - }, - tooltip: { - trigger: 'axis', - axisPointer: { - type: 'shadow' - } - }, - yAxis: { - type: 'value', - name: '转化率(%)' - }, - xAxis: { - type: 'category', - name: '日期', - data: [] - } -}) as EChartsOption - -/** 获取统计数据 */ -const loadData = async () => { - // 1. 加载统计数据 - loading.value = true - const customerCount = await StatisticsCustomerApi.getTotalCustomerCount(props.queryParams) - const dealCustomerCount = await StatisticsCustomerApi.getDealTotalCustomerCount(props.queryParams) - // 2.1 更新 Echarts 数据 - if (echartsOption.xAxis && echartsOption.xAxis['data']) { - echartsOption.xAxis['data'] = customerCount.map((s: StatisticsCustomerRespVO) => s.category) - } - if (echartsOption.series && echartsOption.series[0] && echartsOption.series[0]['data']) { - echartsOption.series[0]['data'] = customerCount.map((item: StatisticsCustomerRespVO, index: number) => { - return { - name: item.category, - value: item.count ? round(dealCustomerCount[index].count / item.count * 100, 2) : 0, - } - }) - } - // 2.2 更新列表数据 - const tableData = customerCount.map((item: StatisticsCustomerRespVO, index: number) => { - return { - category: item.category, - dealCustomerCount: dealCustomerCount[index].count, - customerCount: item.count, - conversionRate: item.count ? round(dealCustomerCount[index].count / item.count * 100, 2) : 0, - } - }) - list.value = tableData - loading.value = false -} -defineExpose({ loadData }) - -/** 初始化 */ -onMounted(() => { - loadData() -}) -</script> diff --git a/src/views/crm/statistics/customer/components/CustomerConversionStat.vue b/src/views/crm/statistics/customer/components/CustomerConversionStat.vue new file mode 100644 index 00000000..fbef26b8 --- /dev/null +++ b/src/views/crm/statistics/customer/components/CustomerConversionStat.vue @@ -0,0 +1,130 @@ +<!-- 客户转化率分析 --> +<template> + <!-- Echarts图 --> + <el-card shadow="never"> + <el-skeleton :loading="loading" animated> + <Echart :height="500" :options="echartsOption" /> + </el-skeleton> + </el-card> + + <!-- 统计列表 --> + <el-card shadow="never" class="mt-16px"> + <el-table v-loading="loading" :data="list"> + <el-table-column label="序号" align="center" type="index" width="80" /> + <el-table-column label="客户名称" align="center" prop="customerName" min-width="200" /> + <el-table-column label="合同名称" align="center" prop="contractName" min-width="200" /> + <el-table-column label="合同总金额" align="center" prop="totalPrice" min-width="200" /> + <el-table-column label="回款金额" align="center" prop="receivablePrice" min-width="200" /> + <el-table-column label="负责人" align="center" prop="ownerUserName" min-width="200" /> + <el-table-column label="创建人" align="center" prop="creatorUserName" min-width="200" /> + <el-table-column + label="创建时间" + align="center" + prop="createTime" + :formatter="dateFormatter" + min-width="200" + /> + <el-table-column + label="下单日期" + align="center" + prop="orderDate" + :formatter="dateFormatter2" + min-width="200" + /> + </el-table> + </el-card> +</template> +<script setup lang="ts"> +import { + StatisticsCustomerApi, + CrmStatisticsCustomerSummaryByDateRespVO +} from '@/api/crm/statistics/customer' +import { EChartsOption } from 'echarts' +import { round } from 'lodash-es' +import { dateFormatter, dateFormatter2 } from '@/utils/formatTime' + +defineOptions({ name: 'CustomerConversionStat' }) +const props = defineProps<{ queryParams: any }>() // 搜索参数 + +const loading = ref(false) // 加载中 +const list = ref<CrmStatisticsCustomerSummaryByDateRespVO[]>([]) // 列表的数据 + +/** 柱状图配置:纵向 */ +const echartsOption = reactive<EChartsOption>({ + grid: { + left: 20, + right: 20, + bottom: 20, + containLabel: true + }, + legend: {}, + series: [ + { + name: '客户转化率', + type: 'line', + data: [] + } + ], + toolbox: { + feature: { + dataZoom: { + xAxisIndex: false // 数据区域缩放:Y 轴不缩放 + }, + brush: { + type: ['lineX', 'clear'] // 区域缩放按钮、还原按钮 + }, + saveAsImage: { show: true, name: '客户转化率分析' } // 保存为图片 + } + }, + tooltip: { + trigger: 'axis', + axisPointer: { + type: 'shadow' + } + }, + yAxis: { + type: 'value', + name: '转化率(%)' + }, + xAxis: { + type: 'category', + name: '日期', + data: [] + } +}) as EChartsOption + +/** 获取统计数据 */ +const loadData = async () => { + // 1. 加载统计数据 + loading.value = true + const customerCount = await StatisticsCustomerApi.getCustomerSummaryByDate(props.queryParams) + const contractSummary = await StatisticsCustomerApi.getContractSummary(props.queryParams) + // 2.1 更新 Echarts 数据 + if (echartsOption.xAxis && echartsOption.xAxis['data']) { + echartsOption.xAxis['data'] = customerCount.map( + (s: CrmStatisticsCustomerSummaryByDateRespVO) => s.time + ) + } + if (echartsOption.series && echartsOption.series[0] && echartsOption.series[0]['data']) { + echartsOption.series[0]['data'] = customerCount.map( + (item: CrmStatisticsCustomerSummaryByDateRespVO) => { + return { + name: item.time, + value: item.customerCreateCount + ? round((item.customerDealCount / item.customerCreateCount) * 100, 2) + : 0 + } + } + ) + } + // 2.2 更新列表数据 + list.value = contractSummary + loading.value = false +} +defineExpose({ loadData }) + +/** 初始化 */ +onMounted(() => { + loadData() +}) +</script> diff --git a/src/views/crm/statistics/customer/components/CustomerCycle.vue b/src/views/crm/statistics/customer/components/CustomerDealCycle.vue similarity index 60% rename from src/views/crm/statistics/customer/components/CustomerCycle.vue rename to src/views/crm/statistics/customer/components/CustomerDealCycle.vue index d28f345d..9243e6a3 100644 --- a/src/views/crm/statistics/customer/components/CustomerCycle.vue +++ b/src/views/crm/statistics/customer/components/CustomerDealCycle.vue @@ -11,21 +11,30 @@ <el-card shadow="never" class="mt-16px"> <el-table v-loading="loading" :data="list"> <el-table-column label="序号" align="center" type="index" width="80" /> - <el-table-column label="日期" align="center" prop="category" min-width="200" /> - <el-table-column label="成交周期(天)" align="center" prop="customerCycle" min-width="200" /> - <el-table-column label="成交客户数" align="center" prop="dealCustomerCount" min-width="200" /> + <el-table-column label="日期" align="center" prop="ownerUserName" min-width="200" /> + <el-table-column + label="成交周期(天)" + align="center" + prop="customerDealCycle" + min-width="200" + /> + <el-table-column label="成交客户数" align="center" prop="customerDealCount" min-width="200" /> </el-table> </el-card> </template> <script setup lang="ts"> -import { StatisticsCustomerApi, StatisticsCustomerRespVO } from '@/api/crm/statistics/customer' +import { + StatisticsCustomerApi, + CrmStatisticsCustomerDealCycleByDateRespVO, + CrmStatisticsCustomerSummaryByDateRespVO, +} from '@/api/crm/statistics/customer' import { EChartsOption } from 'echarts' -defineOptions({ name: 'TotalCustomerCount' }) +defineOptions({ name: 'CustomerDealCycle' }) const props = defineProps<{ queryParams: any }>() // 搜索参数 const loading = ref(false) // 加载中 -const list = ref<StatisticsCustomerRespVO[]>([]) // 列表的数据 +const list = ref<CrmStatisticsCustomerDealCycleByDateRespVO[]>([]) // 列表的数据 /** 柱状图配置:纵向 */ const echartsOption = reactive<EChartsOption>({ @@ -35,7 +44,7 @@ const echartsOption = reactive<EChartsOption>({ bottom: 20, containLabel: true }, - legend: { }, + legend: {}, series: [ { name: '成交周期(天)', @@ -46,7 +55,7 @@ const echartsOption = reactive<EChartsOption>({ name: '成交客户数', type: 'bar', data: [] - }, + } ], toolbox: { feature: { @@ -80,27 +89,33 @@ const echartsOption = reactive<EChartsOption>({ const loadData = async () => { // 1. 加载统计数据 loading.value = true - const customerCycle = await StatisticsCustomerApi.getCustomerCycle(props.queryParams) - const dealCustomerCount = await StatisticsCustomerApi.getDealTotalCustomerCount(props.queryParams) + const customerDealCycleByDate = await StatisticsCustomerApi.getCustomerDealCycleByDate( + props.queryParams + ) + const customerSummaryByDate = await StatisticsCustomerApi.getCustomerSummaryByDate( + props.queryParams + ) + const customerDealCycleByUser = await StatisticsCustomerApi.getCustomerDealCycleByUser( + props.queryParams + ) // 2.1 更新 Echarts 数据 if (echartsOption.xAxis && echartsOption.xAxis['data']) { - echartsOption.xAxis['data'] = customerCycle.map((s: StatisticsCustomerRespVO) => s.category) + echartsOption.xAxis['data'] = customerDealCycleByDate.map( + (s: CrmStatisticsCustomerDealCycleByDateRespVO) => s.time + ) } if (echartsOption.series && echartsOption.series[0] && echartsOption.series[0]['data']) { - echartsOption.series[0]['data'] = customerCycle.map((s: StatisticsCustomerRespVO) => s['cycle']) + echartsOption.series[0]['data'] = customerDealCycleByDate.map( + (s: CrmStatisticsCustomerDealCycleByDateRespVO) => s.customerDealCycle + ) } if (echartsOption.series && echartsOption.series[1] && echartsOption.series[1]['data']) { - echartsOption.series[1]['data'] = dealCustomerCount.map((s: StatisticsCustomerRespVO) => s.count) + echartsOption.series[1]['data'] = customerSummaryByDate.map( + (s: CrmStatisticsCustomerSummaryByDateRespVO) => s.customerDealCount + ) } // 2.2 更新列表数据 - const tableData = customerCycle.map((item: StatisticsCustomerRespVO, index: number) => { - return { - category: item.category, - customerCycle: item.cycle, - dealCustomerCount: dealCustomerCount[index].count, - } - }) - list.value = tableData + list.value = customerDealCycleByUser loading.value = false } defineExpose({ loadData }) diff --git a/src/views/crm/statistics/customer/components/FollowupCount.vue b/src/views/crm/statistics/customer/components/CustomerFollowupSummary.vue similarity index 60% rename from src/views/crm/statistics/customer/components/FollowupCount.vue rename to src/views/crm/statistics/customer/components/CustomerFollowupSummary.vue index e0ae1274..cfb025f5 100644 --- a/src/views/crm/statistics/customer/components/FollowupCount.vue +++ b/src/views/crm/statistics/customer/components/CustomerFollowupSummary.vue @@ -11,21 +11,30 @@ <el-card shadow="never" class="mt-16px"> <el-table v-loading="loading" :data="list"> <el-table-column label="序号" align="center" type="index" width="80" /> - <el-table-column label="日期" align="center" prop="category" min-width="200" /> - <el-table-column label="跟进客户数" align="center" prop="distinctRecordCount" min-width="200" /> - <el-table-column label="跟进次数" align="center" prop="recordCount" min-width="200" /> + <el-table-column label="员工姓名" align="center" prop="ownerUserName" min-width="200" /> + <el-table-column label="跟进次数" align="right" prop="followupRecordCount" min-width="200" /> + <el-table-column + label="跟进客户数" + align="right" + prop="followupCustomerCount" + min-width="200" + /> </el-table> </el-card> </template> <script setup lang="ts"> -import { StatisticsCustomerApi, StatisticsCustomerRespVO } from '@/api/crm/statistics/customer' +import { + StatisticsCustomerApi, + CrmStatisticsFollowupSummaryByDateRespVO, + CrmStatisticsFollowupSummaryByUserRespVO +} from '@/api/crm/statistics/customer' import { EChartsOption } from 'echarts' -defineOptions({ name: 'FollowupCount' }) +defineOptions({ name: 'CustomerFollowupSummary' }) const props = defineProps<{ queryParams: any }>() // 搜索参数 const loading = ref(false) // 加载中 -const list = ref<StatisticsCustomerRespVO[]>([]) // 列表的数据 +const list = ref<CrmStatisticsFollowupSummaryByUserRespVO[]>([]) // 列表的数据 /** 柱状图配置:纵向 */ const echartsOption = reactive<EChartsOption>({ @@ -35,7 +44,7 @@ const echartsOption = reactive<EChartsOption>({ bottom: 20, containLabel: true }, - legend: { }, + legend: {}, series: [ { name: '跟进客户数', @@ -46,7 +55,7 @@ const echartsOption = reactive<EChartsOption>({ name: '跟进次数', type: 'bar', data: [] - }, + } ], toolbox: { feature: { @@ -80,27 +89,30 @@ const echartsOption = reactive<EChartsOption>({ const loadData = async () => { // 1. 加载统计数据 loading.value = true - const recordCount = await StatisticsCustomerApi.getRecordCount(props.queryParams) - const distinctRecordCount = await StatisticsCustomerApi.getDistinctRecordCount(props.queryParams) + const followupSummaryByDate = await StatisticsCustomerApi.getFollowupSummaryByDate( + props.queryParams + ) + const followupSummaryByUser = await StatisticsCustomerApi.getFollowupSummaryByUser( + props.queryParams + ) // 2.1 更新 Echarts 数据 if (echartsOption.xAxis && echartsOption.xAxis['data']) { - echartsOption.xAxis['data'] = recordCount.map((s: StatisticsCustomerRespVO) => s.category) + echartsOption.xAxis['data'] = followupSummaryByDate.map( + (s: CrmStatisticsFollowupSummaryByDateRespVO) => s.time + ) } if (echartsOption.series && echartsOption.series[0] && echartsOption.series[0]['data']) { - echartsOption.series[0]['data'] = distinctRecordCount.map((s: StatisticsCustomerRespVO) => s.count) + echartsOption.series[0]['data'] = followupSummaryByDate.map( + (s: CrmStatisticsFollowupSummaryByDateRespVO) => s.followupCustomerCount + ) } if (echartsOption.series && echartsOption.series[1] && echartsOption.series[1]['data']) { - echartsOption.series[1]['data'] = recordCount.map((s: StatisticsCustomerRespVO) => s.count) + echartsOption.series[1]['data'] = followupSummaryByDate.map( + (s: CrmStatisticsFollowupSummaryByDateRespVO) => s.followupRecordCount + ) } // 2.2 更新列表数据 - const tableData = recordCount.map((item: StatisticsCustomerRespVO, index: number) => { - return { - category: item.category, - recordCount: item.count, - distinctRecordCount: distinctRecordCount[index].count, - } - }) - list.value = tableData + list.value = followupSummaryByUser loading.value = false } defineExpose({ loadData }) diff --git a/src/views/crm/statistics/customer/components/FollowupType.vue b/src/views/crm/statistics/customer/components/CustomerFollowupType.vue similarity index 64% rename from src/views/crm/statistics/customer/components/FollowupType.vue rename to src/views/crm/statistics/customer/components/CustomerFollowupType.vue index 2debcb1c..87510054 100644 --- a/src/views/crm/statistics/customer/components/FollowupType.vue +++ b/src/views/crm/statistics/customer/components/CustomerFollowupType.vue @@ -11,22 +11,25 @@ <el-card shadow="never" class="mt-16px"> <el-table v-loading="loading" :data="list"> <el-table-column label="序号" align="center" type="index" width="80" /> - <el-table-column label="跟进方式" align="center" prop="category" min-width="200" /> - <el-table-column label="个数" align="center" prop="count" min-width="200" /> + <el-table-column label="跟进方式" align="center" prop="followupType" min-width="200" /> + <el-table-column label="个数" align="center" prop="followupRecordCount" min-width="200" /> <el-table-column label="占比(%)" align="center" prop="portion" min-width="200" /> </el-table> </el-card> </template> <script setup lang="ts"> -import { StatisticsCustomerApi, StatisticsCustomerRespVO } from '@/api/crm/statistics/customer' +import { + StatisticsCustomerApi, + CrmStatisticsFollowupSummaryByTypeRespVO +} from '@/api/crm/statistics/customer' import { EChartsOption } from 'echarts' import { round, sumBy } from 'lodash-es' -defineOptions({ name: 'FollowupType' }) +defineOptions({ name: 'CustomerFollowupType' }) const props = defineProps<{ queryParams: any }>() // 搜索参数 const loading = ref(false) // 加载中 -const list = ref<StatisticsCustomerRespVO[]>([]) // 列表的数据 +const list = ref<CrmStatisticsFollowupSummaryByTypeRespVO[]>([]) // 列表的数据 /** 饼图配置 */ const echartsOption = reactive<EChartsOption>({ @@ -68,23 +71,27 @@ const echartsOption = reactive<EChartsOption>({ const loadData = async () => { // 1. 加载统计数据 loading.value = true - const recordTypeCount = await StatisticsCustomerApi.getRecordTypeCount(props.queryParams) + const followupSummaryByType = await StatisticsCustomerApi.getFollowupSummaryByType( + props.queryParams + ) // 2.1 更新 Echarts 数据 if (echartsOption.series && echartsOption.series[0] && echartsOption.series[0]['data']) { - echartsOption.series[0]['data'] = recordTypeCount.map((r: StatisticsCustomerRespVO) => { - return { - name: r.category, - value: r.count + echartsOption.series[0]['data'] = followupSummaryByType.map( + (r: CrmStatisticsFollowupSummaryByTypeRespVO) => { + return { + name: r.followupType, + value: r.followupRecordCount + } } - }) + ) } // 2.2 更新列表数据 - const totalCount = sumBy(recordTypeCount, 'count') - list.value = recordTypeCount.map((r: StatisticsCustomerRespVO) => { + const totalCount = sumBy(followupSummaryByType, 'followupRecordCount') + list.value = followupSummaryByType.map((r: CrmStatisticsFollowupSummaryByTypeRespVO) => { return { - category: r.category, - count: r.count, - portion: round((r.count / totalCount) * 100, 2) + followupType: r.followupType, + followupRecordCount: r.followupRecordCount, + portion: round((r.followupRecordCount / totalCount) * 100, 2) } }) loading.value = false diff --git a/src/views/crm/statistics/customer/components/CustomerSummary.vue b/src/views/crm/statistics/customer/components/CustomerSummary.vue new file mode 100644 index 00000000..da50b30b --- /dev/null +++ b/src/views/crm/statistics/customer/components/CustomerSummary.vue @@ -0,0 +1,150 @@ +<!-- 客户统计 --> +<template> + <!-- Echarts图 --> + <el-card shadow="never"> + <el-skeleton :loading="loading" animated> + <Echart :height="500" :options="echartsOption" /> + </el-skeleton> + </el-card> + + <!-- 统计列表 --> + <el-card shadow="never" class="mt-16px"> + <el-table v-loading="loading" :data="list"> + <el-table-column label="序号" align="center" type="index" width="80" /> + <el-table-column label="员工姓名" prop="ownerUserName" min-width="100" /> + <el-table-column + label="新增客户数" + align="right" + prop="customerCreateCount" + min-width="200" + /> + <el-table-column label="成交客户数" align="right" prop="customerDealCount" min-width="200" /> + <el-table-column label="客户成交率(%)" align="right" min-width="200"> + <template #default="scope"> + {{ + scope.row.customerCreateCount !== 0 + ? round((scope.row.customerDealCount / scope.row.customerCreateCount) * 100, 2) + : 0 + }} + </template> + </el-table-column> + <el-table-column label="合同总金额" align="right" prop="contractPrice" min-width="200" /> + <el-table-column label="回款金额" align="right" prop="receivablePrice" min-width="200" /> + <el-table-column label="未回款金额" align="right" min-width="200"> + <template #default="scope"> + {{ round(scope.row.contractPrice - scope.row.receivablePrice, 2) }} + </template> + </el-table-column> + <el-table-column label="回款完成率(%)" align="right" min-width="200"> + <template #default="scope"> + {{ + scope.row.contractPrice !== 0 + ? round((scope.row.receivablePrice / scope.row.contractPrice) * 100, 2) + : 0 + }} + </template> + </el-table-column> + </el-table> + </el-card> +</template> +<script setup lang="ts"> +import { + StatisticsCustomerApi, + CrmStatisticsCustomerSummaryByDateRespVO, + CrmStatisticsCustomerSummaryByUserRespVO +} from '@/api/crm/statistics/customer' +import { EChartsOption } from 'echarts' +import { round } from 'lodash-es' + +defineOptions({ name: 'CustomerSummary' }) +const props = defineProps<{ queryParams: any }>() // 搜索参数 + +const loading = ref(false) // 加载中 +const list = ref<CrmStatisticsCustomerSummaryByUserRespVO[]>([]) // 列表的数据 + +/** 柱状图配置:纵向 */ +const echartsOption = reactive<EChartsOption>({ + grid: { + left: 20, + right: 20, + bottom: 20, + containLabel: true + }, + legend: {}, + series: [ + { + name: '新增客户数', + type: 'bar', + data: [] + }, + { + name: '成交客户数', + type: 'bar', + data: [] + } + ], + toolbox: { + feature: { + dataZoom: { + xAxisIndex: false // 数据区域缩放:Y 轴不缩放 + }, + brush: { + type: ['lineX', 'clear'] // 区域缩放按钮、还原按钮 + }, + saveAsImage: { show: true, name: '客户总量分析' } // 保存为图片 + } + }, + tooltip: { + trigger: 'axis', + axisPointer: { + type: 'shadow' + } + }, + yAxis: { + type: 'value', + name: '数量(个)' + }, + xAxis: { + type: 'category', + name: '日期', + data: [] + } +}) as EChartsOption + +/** 获取统计数据 */ +const loadData = async () => { + // 1. 加载统计数据 + loading.value = true + const customerSummaryByDate = await StatisticsCustomerApi.getCustomerSummaryByDate( + props.queryParams + ) + const customerSummaryByUser = await StatisticsCustomerApi.getCustomerSummaryByUser( + props.queryParams + ) + // 2.1 更新 Echarts 数据 + if (echartsOption.xAxis && echartsOption.xAxis['data']) { + echartsOption.xAxis['data'] = customerSummaryByDate.map( + (s: CrmStatisticsCustomerSummaryByDateRespVO) => s.time + ) + } + if (echartsOption.series && echartsOption.series[0] && echartsOption.series[0]['data']) { + echartsOption.series[0]['data'] = customerSummaryByDate.map( + (s: CrmStatisticsCustomerSummaryByDateRespVO) => s.customerCreateCount + ) + } + if (echartsOption.series && echartsOption.series[1] && echartsOption.series[1]['data']) { + echartsOption.series[1]['data'] = customerSummaryByDate.map( + (s: CrmStatisticsCustomerSummaryByDateRespVO) => s.customerDealCount + ) + } + // 2.2 更新列表数据 + list.value = customerSummaryByUser + loading.value = false +} +defineExpose({ loadData }) + +/** 初始化 */ +onMounted(() => { + loadData() +}) +</script> diff --git a/src/views/crm/statistics/customer/components/TotalCustomerCount.vue b/src/views/crm/statistics/customer/components/TotalCustomerCount.vue deleted file mode 100644 index 84c50d04..00000000 --- a/src/views/crm/statistics/customer/components/TotalCustomerCount.vue +++ /dev/null @@ -1,112 +0,0 @@ -<!-- 客户总量分析 --> -<template> - <!-- Echarts图 --> - <el-card shadow="never"> - <el-skeleton :loading="loading" animated> - <Echart :height="500" :options="echartsOption" /> - </el-skeleton> - </el-card> - - <!-- 统计列表 --> - <el-card shadow="never" class="mt-16px"> - <el-table v-loading="loading" :data="list"> - <el-table-column label="序号" align="center" type="index" width="80" /> - <el-table-column label="日期" align="center" prop="category" min-width="200" /> - <el-table-column label="新增客户数" align="center" prop="customerCount" min-width="200" /> - <el-table-column label="成交客户数" align="center" prop="dealCustomerCount" min-width="200" /> - </el-table> - </el-card> -</template> -<script setup lang="ts"> -import { StatisticsCustomerApi, StatisticsCustomerRespVO } from '@/api/crm/statistics/customer' -import { EChartsOption } from 'echarts' - -defineOptions({ name: 'TotalCustomerCount' }) -const props = defineProps<{ queryParams: any }>() // 搜索参数 - -const loading = ref(false) // 加载中 -const list = ref<StatisticsCustomerRespVO[]>([]) // 列表的数据 - -/** 柱状图配置:纵向 */ -const echartsOption = reactive<EChartsOption>({ - grid: { - left: 20, - right: 20, - bottom: 20, - containLabel: true - }, - legend: { }, - series: [ - { - name: '新增客户数', - type: 'bar', - data: [] - }, - { - name: '成交客户数', - type: 'bar', - data: [] - }, - ], - toolbox: { - feature: { - dataZoom: { - xAxisIndex: false // 数据区域缩放:Y 轴不缩放 - }, - brush: { - type: ['lineX', 'clear'] // 区域缩放按钮、还原按钮 - }, - saveAsImage: { show: true, name: '客户总量分析' } // 保存为图片 - } - }, - tooltip: { - trigger: 'axis', - axisPointer: { - type: 'shadow' - } - }, - yAxis: { - type: 'value', - name: '数量(个)' - }, - xAxis: { - type: 'category', - name: '日期', - data: [] - } -}) as EChartsOption - -/** 获取统计数据 */ -const loadData = async () => { - // 1. 加载统计数据 - loading.value = true - const customerCount = await StatisticsCustomerApi.getTotalCustomerCount(props.queryParams) - const dealCustomerCount = await StatisticsCustomerApi.getDealTotalCustomerCount(props.queryParams) - // 2.1 更新 Echarts 数据 - if (echartsOption.xAxis && echartsOption.xAxis['data']) { - echartsOption.xAxis['data'] = customerCount.map((s: StatisticsCustomerRespVO) => s.category) - } - if (echartsOption.series && echartsOption.series[0] && echartsOption.series[0]['data']) { - echartsOption.series[0]['data'] = customerCount.map((s: StatisticsCustomerRespVO) => s.count) - } - if (echartsOption.series && echartsOption.series[1] && echartsOption.series[1]['data']) { - echartsOption.series[1]['data'] = dealCustomerCount.map((s: StatisticsCustomerRespVO) => s.count) - } - // 2.2 更新列表数据 - const tableData = customerCount.map((item: StatisticsCustomerRespVO, index: number) => { - return { - category: item.category, - customerCount: item.count, - dealCustomerCount: dealCustomerCount[index].count, - } - }) - list.value = tableData - loading.value = false -} -defineExpose({ loadData }) - -/** 初始化 */ -onMounted(() => { - loadData() -}) -</script> diff --git a/src/views/crm/statistics/customer/index.vue b/src/views/crm/statistics/customer/index.vue index 89e2d009..8096814f 100644 --- a/src/views/crm/statistics/customer/index.vue +++ b/src/views/crm/statistics/customer/index.vue @@ -49,44 +49,44 @@ </el-form> </ContentWrap> - <!-- 排行数据 --> + <!-- 客户统计 --> <el-col> <el-tabs v-model="activeTab"> <!-- 客户总量分析 --> - <el-tab-pane label="客户总量分析" name="totalCustomerCount" lazy> - <TotalCustomerCount :query-params="queryParams" ref="totalCustomerCountRef" /> + <el-tab-pane label="客户总量分析" name="customerSummary" lazy> + <CustomerSummary :query-params="queryParams" ref="customerSummaryRef" /> </el-tab-pane> <!-- 客户跟进次数分析 --> - <el-tab-pane label="客户跟进次数分析" name="followupCount" lazy> - <FollowupCount :query-params="queryParams" ref="followupCountRef" /> + <el-tab-pane label="客户跟进次数分析" name="followupSummary" lazy> + <CustomerFollowupSummary :query-params="queryParams" ref="followupSummaryRef" /> </el-tab-pane> <!-- 客户跟进方式分析 --> <el-tab-pane label="客户跟进方式分析" name="followupType" lazy> - <FollowupType :query-params="queryParams" ref="followupTypeRef" /> + <CustomerFollowupType :query-params="queryParams" ref="followupTypeRef" /> </el-tab-pane> <!-- 客户转化率分析 --> - <el-tab-pane label="客户转化率分析" name="conversionRate" lazy> - <ConversionRate :query-params="queryParams" ref="conversionRateRef" /> + <el-tab-pane label="客户转化率分析" name="conversionStat" lazy> + <CustomerConversionStat :query-params="queryParams" ref="conversionStatRef" /> </el-tab-pane> <!-- 成交周期分析 --> - <el-tab-pane label="成交周期分析" name="customerCycle" lazy> - <CustomerCycle :query-params="queryParams" ref="customerCycleRef" /> + <el-tab-pane label="成交周期分析" name="dealCycle" lazy> + <CustomerDealCycle :query-params="queryParams" ref="dealCycleRef" /> </el-tab-pane> </el-tabs> </el-col> </template> + <script lang="ts" setup> import * as DeptApi from '@/api/system/dept' import * as UserApi from '@/api/system/user' import { useUserStore } from '@/store/modules/user' import { beginOfDay, defaultShortcuts, endOfDay, formatDate } from '@/utils/formatTime' import { defaultProps, handleTree } from '@/utils/tree' -import TotalCustomerCount from './components/TotalCustomerCount.vue' -import FollowupCount from './components/FollowupCount.vue' -import FollowupType from './components/FollowupType.vue' -import ConversionRate from './components/ConversionRate.vue' -import CustomerCycle from './components/CustomerCycle.vue' - +import CustomerSummary from './components/CustomerSummary.vue' +import CustomerFollowupSummary from './components/CustomerFollowupSummary.vue' +import CustomerFollowupType from './components/CustomerFollowupType.vue' +import CustomerConversionStat from './components/CustomerConversionStat.vue' +import CustomerDealCycle from './components/CustomerDealCycle.vue' defineOptions({ name: 'CrmStatisticsCustomer' }) @@ -105,41 +105,43 @@ const deptList = ref<Tree[]>([]) // 部门树形结构 const userList = ref<UserApi.UserVO[]>([]) // 全量用户清单 // 根据选择的部门筛选员工清单 const userListByDeptId = computed(() => - queryParams.deptId ? userList.value.filter((u: UserApi.UserVO) => u.deptId === queryParams.deptId) : [] + queryParams.deptId + ? userList.value.filter((u: UserApi.UserVO) => u.deptId === queryParams.deptId) + : [] ) // 活跃标签 -const activeTab = ref('totalCustomerCount') +const activeTab = ref('customerSummary') // 1.客户总量分析 -const totalCustomerCountRef = ref() +const customerSummaryRef = ref() // 2.客户跟进次数分析 -const followupCountRef = ref() +const followupSummaryRef = ref() // 3.客户跟进方式分析 const followupTypeRef = ref() // 4.客户转化率分析 -const conversionRateRef = ref() +const conversionStatRef = ref() // 5.公海客户分析 // 缺 crm_owner_record 表 // 6.成交周期分析 -const customerCycleRef = ref() +const dealCycleRef = ref() /** 搜索按钮操作 */ const handleQuery = () => { switch (activeTab.value) { - case 'totalCustomerCount': - totalCustomerCountRef.value?.loadData?.() + case 'customerSummary': + customerSummaryRef.value?.loadData?.() break - case 'followupCount': - followupCountRef.value?.loadData?.() + case 'followupSummary': + followupSummaryRef.value?.loadData?.() break case 'followupType': followupTypeRef.value?.loadData?.() break - case 'conversionRate': - conversionRateRef.value?.loadData?.() + case 'conversionStat': + conversionStatRef.value?.loadData?.() break - case 'customerCycle': - customerCycleRef.value?.loadData?.() + case 'dealCycle': + dealCycleRef.value?.loadData?.() break } }