CRM: 完善用户画像数据统计

This commit is contained in:
puhui999
2024-04-03 12:19:09 +08:00
parent d16bed4ad7
commit 36385a74ba
9 changed files with 339 additions and 137 deletions

View File

@ -0,0 +1,150 @@
<!-- 客户城市分布 -->
<template>
<!-- Echarts图 -->
<el-card shadow="never">
<el-row :gutter="20">
<el-col :span="12">
<el-skeleton :loading="loading" animated>
<Echart :height="500" :options="echartsOption" />
</el-skeleton>
</el-col>
<el-col :span="12">
<el-skeleton :loading="loading" animated>
<Echart :height="500" :options="echartsOption2" />
</el-skeleton>
</el-col>
</el-row>
</el-card>
</template>
<script lang="ts" setup>
import { EChartsOption } from 'echarts'
import china from '@/assets/map/json/china.json'
import echarts from '@/plugins/echarts'
import {
CrmStatisticCustomerAreaRespVO,
StatisticsPortraitApi
} from '@/api/crm/statistics/portrait'
defineOptions({ name: 'CustomerAddress' })
const props = defineProps<{ queryParams: any }>() // 搜索参数
// 注册地图
echarts?.registerMap('china', china as any)
const loading = ref(false) // 加载中
const areaStatisticsList = ref<CrmStatisticCustomerAreaRespVO[]>([]) // 列表的数据
/** 地图配置 */
const echartsOption = reactive<EChartsOption>({
title: {
text: '全部客户',
left: 'center'
},
tooltip: {
trigger: 'item',
showDelay: 0,
transitionDuration: 0.2
},
visualMap: {
text: ['高', '低'],
realtime: false,
calculable: true,
top: 'middle',
inRange: {
color: ['#fff', '#3b82f6']
}
},
series: [
{
name: '客户地域分布',
type: 'map',
map: 'china',
roam: false,
selectedMode: false,
data: []
}
]
}) as EChartsOption
/** 地图配置 */
const echartsOption2 = reactive<EChartsOption>({
title: {
text: '成交客户',
left: 'center'
},
tooltip: {
trigger: 'item',
showDelay: 0,
transitionDuration: 0.2
},
visualMap: {
text: ['高', '低'],
realtime: false,
calculable: true,
top: 'middle',
inRange: {
color: ['#fff', '#3b82f6']
}
},
series: [
{
name: '客户地域分布',
type: 'map',
map: 'china',
roam: false,
selectedMode: false,
data: []
}
]
}) as EChartsOption
/** 获取统计数据 */
const loadData = async () => {
// 1. 加载统计数据
loading.value = true
const areaList = await StatisticsPortraitApi.getCustomerArea(props.queryParams)
areaStatisticsList.value = areaList.map((item: CrmStatisticCustomerAreaRespVO) => {
return {
...item,
areaName: item.areaName
.replace('维吾尔自治区', '')
.replace('壮族自治区', '')
.replace('回族自治区', '')
.replace('自治区', '')
.replace('省', '')
}
})
builderLeftMap()
builderRightMap()
loading.value = false
}
defineExpose({ loadData })
const builderLeftMap = () => {
let min = 0
let max = 0
echartsOption.series![0].data = areaStatisticsList.value.map((item) => {
min = Math.min(min, item.customerCount || 0)
max = Math.max(max, item.customerCount || 0)
return { ...item, name: item.areaName, value: item.customerCount || 0 }
})
echartsOption.visualMap!['min'] = min
echartsOption.visualMap!['max'] = max
}
const builderRightMap = () => {
let min = 0
let max = 0
echartsOption2.series![0].data = areaStatisticsList.value.map((item) => {
min = Math.min(min, item.dealCount || 0)
max = Math.max(max, item.dealCount || 0)
return { ...item, name: item.areaName, value: item.dealCount || 0 }
})
echartsOption2.visualMap!['min'] = min
echartsOption2.visualMap!['max'] = max
}
/** 初始化 */
onMounted(() => {
loadData()
})
</script>

View File

@ -0,0 +1,197 @@
<!-- 客户行业分析 -->
<template>
<!-- Echarts图 -->
<el-card shadow="never">
<el-row :gutter="20">
<el-col :span="12">
<el-skeleton :loading="loading" animated>
<Echart :height="500" :options="echartsOption" />
</el-skeleton>
</el-col>
<el-col :span="12">
<el-skeleton :loading="loading" animated>
<Echart :height="500" :options="echartsOption2" />
</el-skeleton>
</el-col>
</el-row>
</el-card>
<!-- 统计列表 -->
<el-card class="mt-16px" shadow="never">
<el-table v-loading="loading" :data="list">
<el-table-column align="center" label="序号" type="index" width="80" />
<el-table-column align="center" label="客户行业" prop="industryId" width="100">
<template #default="scope">
<dict-tag :type="DICT_TYPE.CRM_CUSTOMER_INDUSTRY" :value="scope.row.industryId" />
</template>
</el-table-column>
<el-table-column align="center" label="客户个数" min-width="200" prop="customerCount" />
<el-table-column align="center" label="成交个数" min-width="200" prop="dealCount" />
<el-table-column align="center" label="行业占比(%)" min-width="200" prop="industryPortion" />
<el-table-column align="center" label="成交占比(%)" min-width="200" prop="dealPortion" />
</el-table>
</el-card>
</template>
<script lang="ts" setup>
import {
CrmStatisticCustomerIndustryRespVO,
StatisticsPortraitApi
} from '@/api/crm/statistics/portrait'
import { EChartsOption } from 'echarts'
import { DICT_TYPE, getDictLabel } from '@/utils/dict'
import { getSumValue } from '@/utils'
import { isEmpty } from '@/utils/is'
defineOptions({ name: 'CustomerIndustry' })
const props = defineProps<{ queryParams: any }>() // 搜索参数
const loading = ref(false) // 加载中
const list = ref<CrmStatisticCustomerIndustryRespVO[]>([]) // 列表的数据
/** 饼图配置 */
const echartsOption = reactive<EChartsOption>({
title: {
text: '全部客户',
left: 'center'
},
tooltip: {
trigger: 'item'
},
legend: {
orient: 'vertical',
left: 'left'
},
toolbox: {
feature: {
saveAsImage: { show: true, name: '全部客户' } // 保存为图片
}
},
series: [
{
name: '全部客户',
type: 'pie',
radius: ['40%', '70%'],
avoidLabelOverlap: false,
itemStyle: {
borderRadius: 10,
borderColor: '#fff',
borderWidth: 2
},
label: {
show: false,
position: 'center'
},
emphasis: {
label: {
show: true,
fontSize: 40,
fontWeight: 'bold'
}
},
labelLine: {
show: false
},
data: []
}
]
}) as EChartsOption
/** 饼图配置 */
const echartsOption2 = reactive<EChartsOption>({
title: {
text: '成交客户',
left: 'center'
},
tooltip: {
trigger: 'item'
},
legend: {
orient: 'vertical',
left: 'left'
},
toolbox: {
feature: {
saveAsImage: { show: true, name: '成交客户' } // 保存为图片
}
},
series: [
{
name: '成交客户',
type: 'pie',
radius: ['40%', '70%'],
avoidLabelOverlap: false,
itemStyle: {
borderRadius: 10,
borderColor: '#fff',
borderWidth: 2
},
label: {
show: false,
position: 'center'
},
emphasis: {
label: {
show: true,
fontSize: 40,
fontWeight: 'bold'
}
},
labelLine: {
show: false
},
data: []
}
]
}) as EChartsOption
/** 获取统计数据 */
const loadData = async () => {
// 1. 加载统计数据
loading.value = true
const industryList = await StatisticsPortraitApi.getCustomerIndustry(props.queryParams)
// 2.1 更新 Echarts 数据
if (echartsOption.series && echartsOption.series[0] && echartsOption.series[0]['data']) {
echartsOption.series[0]['data'] = industryList.map((r: CrmStatisticCustomerIndustryRespVO) => {
return {
name: getDictLabel(DICT_TYPE.CRM_CUSTOMER_INDUSTRY, r.industryId),
value: r.customerCount
}
})
}
// 2.2 更新 Echarts2 数据
if (echartsOption2.series && echartsOption2.series[0] && echartsOption2.series[0]['data']) {
echartsOption2.series[0]['data'] = industryList.map((r: CrmStatisticCustomerIndustryRespVO) => {
return {
name: getDictLabel(DICT_TYPE.CRM_CUSTOMER_INDUSTRY, r.industryId),
value: r.dealCount
}
})
}
// 3. 计算比例
calculateProportion(industryList)
list.value = industryList
loading.value = false
}
defineExpose({ loadData })
/**
* 计算比例
*/
const calculateProportion = (sourceList: CrmStatisticCustomerIndustryRespVO[]) => {
if (isEmpty(sourceList)) {
return
}
// 这里类型丢失了所以重新搞个变量
const list = sourceList as unknown as CrmStatisticCustomerIndustryRespVO[]
const sumCustomerCount = getSumValue(list.map((item) => item.customerCount))
const sumDealCount = getSumValue(list.map((item) => item.dealCount))
list.forEach((item) => {
item.industryPortion =
item.customerCount === 0 ? 0 : ((item.customerCount / sumCustomerCount) * 100).toFixed(2)
item.dealPortion = item.dealCount === 0 ? 0 : ((item.dealCount / sumDealCount) * 100).toFixed(2)
})
}
/** 初始化 */
onMounted(() => {
loadData()
})
</script>

View File

@ -0,0 +1,197 @@
<!-- 客户来源分析 -->
<template>
<!-- Echarts图 -->
<el-card shadow="never">
<el-row :gutter="20">
<el-col :span="12">
<el-skeleton :loading="loading" animated>
<Echart :height="500" :options="echartsOption" />
</el-skeleton>
</el-col>
<el-col :span="12">
<el-skeleton :loading="loading" animated>
<Echart :height="500" :options="echartsOption2" />
</el-skeleton>
</el-col>
</el-row>
</el-card>
<!-- 统计列表 -->
<el-card class="mt-16px" shadow="never">
<el-table v-loading="loading" :data="list">
<el-table-column align="center" label="序号" type="index" width="80" />
<el-table-column align="center" label="客户级别" prop="level" width="200">
<template #default="scope">
<dict-tag :type="DICT_TYPE.CRM_CUSTOMER_LEVEL" :value="scope.row.level" />
</template>
</el-table-column>
<el-table-column align="center" label="客户个数" min-width="200" prop="customerCount" />
<el-table-column align="center" label="成交个数" min-width="200" prop="dealCount" />
<el-table-column align="center" label="级别占比(%)" min-width="200" prop="levelPortion" />
<el-table-column align="center" label="成交占比(%)" min-width="200" prop="dealPortion" />
</el-table>
</el-card>
</template>
<script lang="ts" setup>
import {
CrmStatisticCustomerLevelRespVO,
StatisticsPortraitApi
} from '@/api/crm/statistics/portrait'
import { EChartsOption } from 'echarts'
import { DICT_TYPE, getDictLabel } from '@/utils/dict'
import { getSumValue } from '@/utils'
import { isEmpty } from '@/utils/is'
defineOptions({ name: 'CustomerSource' })
const props = defineProps<{ queryParams: any }>() // 搜索参数
const loading = ref(false) // 加载中
const list = ref<CrmStatisticCustomerLevelRespVO[]>([]) // 列表的数据
/** 饼图配置 */
const echartsOption = reactive<EChartsOption>({
title: {
text: '全部客户',
left: 'center'
},
tooltip: {
trigger: 'item'
},
legend: {
orient: 'vertical',
left: 'left'
},
toolbox: {
feature: {
saveAsImage: { show: true, name: '全部客户' } // 保存为图片
}
},
series: [
{
name: '全部客户',
type: 'pie',
radius: ['40%', '70%'],
avoidLabelOverlap: false,
itemStyle: {
borderRadius: 10,
borderColor: '#fff',
borderWidth: 2
},
label: {
show: false,
position: 'center'
},
emphasis: {
label: {
show: true,
fontSize: 40,
fontWeight: 'bold'
}
},
labelLine: {
show: false
},
data: []
}
]
}) as EChartsOption
/** 饼图配置 */
const echartsOption2 = reactive<EChartsOption>({
title: {
text: '成交客户',
left: 'center'
},
tooltip: {
trigger: 'item'
},
legend: {
orient: 'vertical',
left: 'left'
},
toolbox: {
feature: {
saveAsImage: { show: true, name: '成交客户' } // 保存为图片
}
},
series: [
{
name: '成交客户',
type: 'pie',
radius: ['40%', '70%'],
avoidLabelOverlap: false,
itemStyle: {
borderRadius: 10,
borderColor: '#fff',
borderWidth: 2
},
label: {
show: false,
position: 'center'
},
emphasis: {
label: {
show: true,
fontSize: 40,
fontWeight: 'bold'
}
},
labelLine: {
show: false
},
data: []
}
]
}) as EChartsOption
/** 获取统计数据 */
const loadData = async () => {
// 1. 加载统计数据
loading.value = true
const levelList = await StatisticsPortraitApi.getCustomerLevel(props.queryParams)
// 2.1 更新 Echarts 数据
if (echartsOption.series && echartsOption.series[0] && echartsOption.series[0]['data']) {
echartsOption.series[0]['data'] = levelList.map((r: CrmStatisticCustomerLevelRespVO) => {
return {
name: getDictLabel(DICT_TYPE.CRM_CUSTOMER_LEVEL, r.level),
value: r.customerCount
}
})
}
// 2.2 更新 Echarts2 数据
if (echartsOption2.series && echartsOption2.series[0] && echartsOption2.series[0]['data']) {
echartsOption2.series[0]['data'] = levelList.map((r: CrmStatisticCustomerLevelRespVO) => {
return {
name: getDictLabel(DICT_TYPE.CRM_CUSTOMER_LEVEL, r.level),
value: r.dealCount
}
})
}
// 3. 计算比例
calculateProportion(levelList)
list.value = levelList
loading.value = false
}
defineExpose({ loadData })
/**
* 计算比例
*/
const calculateProportion = (levelList: CrmStatisticCustomerLevelRespVO[]) => {
if (isEmpty(levelList)) {
return
}
// 这里类型丢失了所以重新搞个变量
const list = levelList as unknown as CrmStatisticCustomerLevelRespVO[]
const sumCustomerCount = getSumValue(list.map((item) => item.customerCount))
const sumDealCount = getSumValue(list.map((item) => item.dealCount))
list.forEach((item) => {
item.levelPortion =
item.customerCount === 0 ? 0 : ((item.customerCount / sumCustomerCount) * 100).toFixed(2)
item.dealPortion = item.dealCount === 0 ? 0 : ((item.dealCount / sumDealCount) * 100).toFixed(2)
})
}
/** 初始化 */
onMounted(() => {
loadData()
})
</script>

View File

@ -0,0 +1,198 @@
<!-- 客户来源分析 -->
<template>
<!-- Echarts图 -->
<el-card shadow="never">
<el-row :gutter="20">
<el-col :span="12">
<el-skeleton :loading="loading" animated>
<Echart :height="500" :options="echartsOption" />
</el-skeleton>
</el-col>
<el-col :span="12">
<el-skeleton :loading="loading" animated>
<Echart :height="500" :options="echartsOption2" />
</el-skeleton>
</el-col>
</el-row>
</el-card>
<!-- 统计列表 -->
<el-card class="mt-16px" shadow="never">
<el-table v-loading="loading" :data="list">
<el-table-column align="center" label="序号" type="index" width="80" />
<el-table-column align="center" label="客户来源" prop="source" width="100">
<template #default="scope">
<dict-tag :type="DICT_TYPE.CRM_CUSTOMER_SOURCE" :value="scope.row.source" />
</template>
</el-table-column>
<el-table-column align="center" label="客户个数" min-width="200" prop="customerCount" />
<el-table-column align="center" label="成交个数" min-width="200" prop="dealCount" />
<el-table-column align="center" label="来源占比(%)" min-width="200" prop="sourcePortion" />
<el-table-column align="center" label="成交占比(%)" min-width="200" prop="dealPortion" />
</el-table>
</el-card>
</template>
<script lang="ts" setup>
import {
CrmStatisticCustomerSourceRespVO,
StatisticsPortraitApi
} from '@/api/crm/statistics/portrait'
import { EChartsOption } from 'echarts'
import { DICT_TYPE, getDictLabel } from '@/utils/dict'
import { isEmpty } from '@/utils/is'
import { getSumValue } from '@/utils'
defineOptions({ name: 'CustomerSource' })
const props = defineProps<{ queryParams: any }>() // 搜索参数
const loading = ref(false) // 加载中
const list = ref<CrmStatisticCustomerSourceRespVO[]>([]) // 列表的数据
/** 饼图配置 */
const echartsOption = reactive<EChartsOption>({
title: {
text: '全部客户',
left: 'center'
},
tooltip: {
trigger: 'item'
},
legend: {
orient: 'vertical',
left: 'left'
},
toolbox: {
feature: {
saveAsImage: { show: true, name: '全部客户' } // 保存为图片
}
},
series: [
{
name: '全部客户',
type: 'pie',
radius: ['40%', '70%'],
avoidLabelOverlap: false,
itemStyle: {
borderRadius: 10,
borderColor: '#fff',
borderWidth: 2
},
label: {
show: false,
position: 'center'
},
emphasis: {
label: {
show: true,
fontSize: 40,
fontWeight: 'bold'
}
},
labelLine: {
show: false
},
data: []
}
]
}) as EChartsOption
/** 饼图配置 */
const echartsOption2 = reactive<EChartsOption>({
title: {
text: '成交客户',
left: 'center'
},
tooltip: {
trigger: 'item'
},
legend: {
orient: 'vertical',
left: 'left'
},
toolbox: {
feature: {
saveAsImage: { show: true, name: '成交客户' } // 保存为图片
}
},
series: [
{
name: '成交客户',
type: 'pie',
radius: ['40%', '70%'],
avoidLabelOverlap: false,
itemStyle: {
borderRadius: 10,
borderColor: '#fff',
borderWidth: 2
},
label: {
show: false,
position: 'center'
},
emphasis: {
label: {
show: true,
fontSize: 40,
fontWeight: 'bold'
}
},
labelLine: {
show: false
},
data: []
}
]
}) as EChartsOption
/** 获取统计数据 */
const loadData = async () => {
// 1. 加载统计数据
loading.value = true
const sourceList = await StatisticsPortraitApi.getCustomerSource(props.queryParams)
// 2.1 更新 Echarts 数据
if (echartsOption.series && echartsOption.series[0] && echartsOption.series[0]['data']) {
echartsOption.series[0]['data'] = sourceList.map((r: CrmStatisticCustomerSourceRespVO) => {
return {
name: getDictLabel(DICT_TYPE.CRM_CUSTOMER_SOURCE, r.source),
value: r.customerCount
}
})
}
// 2.2 更新 Echarts2 数据
if (echartsOption2.series && echartsOption2.series[0] && echartsOption2.series[0]['data']) {
echartsOption2.series[0]['data'] = sourceList.map((r: CrmStatisticCustomerSourceRespVO) => {
return {
name: getDictLabel(DICT_TYPE.CRM_CUSTOMER_SOURCE, r.source),
value: r.dealCount
}
})
}
// 3. 计算比例
calculateProportion(sourceList)
list.value = sourceList
loading.value = false
}
defineExpose({ loadData })
/**
* 计算比例
*/
const calculateProportion = (sourceList: CrmStatisticCustomerSourceRespVO[]) => {
if (isEmpty(sourceList)) {
return
}
// 这里类型丢失了所以重新搞个变量
const list = sourceList as unknown as CrmStatisticCustomerSourceRespVO[]
const sumCustomerCount = getSumValue(list.map((item) => item.customerCount))
const sumDealCount = getSumValue(list.map((item) => item.dealCount))
list.forEach((item) => {
item.sourcePortion =
item.customerCount === 0 ? 0 : ((item.customerCount / sumCustomerCount) * 100).toFixed(2)
item.dealPortion = item.dealCount === 0 ? 0 : ((item.dealCount / sumDealCount) * 100).toFixed(2)
})
}
/** 初始化 */
onMounted(() => {
loadData()
})
</script>