feat: crm summary

pull/155/head
xingyu4j 2025-06-21 19:45:31 +08:00
parent f15be6eade
commit 223c3e7a8a
3 changed files with 322 additions and 35 deletions

View File

@ -1,5 +1,3 @@
import type { PageParam } from '@vben/request';
import { requestClient } from '#/api/request';
export namespace CrmStatisticsCustomerApi {
@ -93,10 +91,19 @@ export namespace CrmStatisticsCustomerApi {
customerDealCycle: number;
customerDealCount: number;
}
export interface CustomerSummaryParams {
times: string[];
interval: number;
deptId: number;
userId: number;
}
}
/** 客户总量分析(按日期) */
export function getCustomerSummaryByDate(params: PageParam) {
export function getCustomerSummaryByDate(
params: CrmStatisticsCustomerApi.CustomerSummaryParams,
) {
return requestClient.get<CrmStatisticsCustomerApi.CustomerSummaryByDate[]>(
'/crm/statistics-customer/get-customer-summary-by-date',
{ params },
@ -104,7 +111,9 @@ export function getCustomerSummaryByDate(params: PageParam) {
}
/** 客户总量分析(按用户) */
export function getCustomerSummaryByUser(params: PageParam) {
export function getCustomerSummaryByUser(
params: CrmStatisticsCustomerApi.CustomerSummaryParams,
) {
return requestClient.get<CrmStatisticsCustomerApi.CustomerSummaryByUser[]>(
'/crm/statistics-customer/get-customer-summary-by-user',
{ params },
@ -112,7 +121,9 @@ export function getCustomerSummaryByUser(params: PageParam) {
}
/** 客户跟进次数分析(按日期) */
export function getFollowUpSummaryByDate(params: PageParam) {
export function getFollowUpSummaryByDate(
params: CrmStatisticsCustomerApi.CustomerSummaryParams,
) {
return requestClient.get<CrmStatisticsCustomerApi.FollowUpSummaryByDate[]>(
'/crm/statistics-customer/get-follow-up-summary-by-date',
{ params },
@ -120,7 +131,9 @@ export function getFollowUpSummaryByDate(params: PageParam) {
}
/** 客户跟进次数分析(按用户) */
export function getFollowUpSummaryByUser(params: PageParam) {
export function getFollowUpSummaryByUser(
params: CrmStatisticsCustomerApi.CustomerSummaryParams,
) {
return requestClient.get<CrmStatisticsCustomerApi.FollowUpSummaryByUser[]>(
'/crm/statistics-customer/get-follow-up-summary-by-user',
{ params },
@ -128,7 +141,9 @@ export function getFollowUpSummaryByUser(params: PageParam) {
}
/** 获取客户跟进方式统计数 */
export function getFollowUpSummaryByType(params: PageParam) {
export function getFollowUpSummaryByType(
params: CrmStatisticsCustomerApi.CustomerSummaryParams,
) {
return requestClient.get<CrmStatisticsCustomerApi.FollowUpSummaryByType[]>(
'/crm/statistics-customer/get-follow-up-summary-by-type',
{ params },
@ -136,7 +151,9 @@ export function getFollowUpSummaryByType(params: PageParam) {
}
/** 合同摘要信息(客户转化率页面) */
export function getContractSummary(params: PageParam) {
export function getContractSummary(
params: CrmStatisticsCustomerApi.CustomerSummaryParams,
) {
return requestClient.get<CrmStatisticsCustomerApi.CustomerContractSummary[]>(
'/crm/statistics-customer/get-contract-summary',
{ params },
@ -144,7 +161,9 @@ export function getContractSummary(params: PageParam) {
}
/** 获取客户公海分析(按日期) */
export function getPoolSummaryByDate(params: PageParam) {
export function getPoolSummaryByDate(
params: CrmStatisticsCustomerApi.CustomerSummaryParams,
) {
return requestClient.get<CrmStatisticsCustomerApi.PoolSummaryByDate[]>(
'/crm/statistics-customer/get-pool-summary-by-date',
{ params },
@ -152,7 +171,9 @@ export function getPoolSummaryByDate(params: PageParam) {
}
/** 获取客户公海分析(按用户) */
export function getPoolSummaryByUser(params: PageParam) {
export function getPoolSummaryByUser(
params: CrmStatisticsCustomerApi.CustomerSummaryParams,
) {
return requestClient.get<CrmStatisticsCustomerApi.PoolSummaryByUser[]>(
'/crm/statistics-customer/get-pool-summary-by-user',
{ params },
@ -160,7 +181,9 @@ export function getPoolSummaryByUser(params: PageParam) {
}
/** 获取客户成交周期(按日期) */
export function getCustomerDealCycleByDate(params: PageParam) {
export function getCustomerDealCycleByDate(
params: CrmStatisticsCustomerApi.CustomerSummaryParams,
) {
return requestClient.get<CrmStatisticsCustomerApi.CustomerDealCycleByDate[]>(
'/crm/statistics-customer/get-customer-deal-cycle-by-date',
{ params },
@ -168,7 +191,9 @@ export function getCustomerDealCycleByDate(params: PageParam) {
}
/** 获取客户成交周期(按用户) */
export function getCustomerDealCycleByUser(params: PageParam) {
export function getCustomerDealCycleByUser(
params: CrmStatisticsCustomerApi.CustomerSummaryParams,
) {
return requestClient.get<CrmStatisticsCustomerApi.CustomerDealCycleByUser[]>(
'/crm/statistics-customer/get-customer-deal-cycle-by-user',
{ params },
@ -176,7 +201,9 @@ export function getCustomerDealCycleByUser(params: PageParam) {
}
/** 获取客户成交周期(按地区) */
export function getCustomerDealCycleByArea(params: PageParam) {
export function getCustomerDealCycleByArea(
params: CrmStatisticsCustomerApi.CustomerSummaryParams,
) {
return requestClient.get<CrmStatisticsCustomerApi.CustomerDealCycleByArea[]>(
'/crm/statistics-customer/get-customer-deal-cycle-by-area',
{ params },
@ -184,7 +211,9 @@ export function getCustomerDealCycleByArea(params: PageParam) {
}
/** 获取客户成交周期(按产品) */
export function getCustomerDealCycleByProduct(params: PageParam) {
export function getCustomerDealCycleByProduct(
params: CrmStatisticsCustomerApi.CustomerSummaryParams,
) {
return requestClient.get<
CrmStatisticsCustomerApi.CustomerDealCycleByProduct[]
>('/crm/statistics-customer/get-customer-deal-cycle-by-product', { params });

View File

@ -0,0 +1,135 @@
import type { VbenFormSchema } from '#/adapter/form';
import type { VxeTableGridOptions } from '#/adapter/vxe-table';
import { useUserStore } from '@vben/stores';
import {
beginOfDay,
endOfDay,
erpCalculatePercentage,
formatDateTime,
handleTree,
} from '@vben/utils';
import { getSimpleDeptList } from '#/api/system/dept';
import { getSimpleUserList } from '#/api/system/user';
import { DICT_TYPE, getDictOptions, getRangePickerDefaultProps } from '#/utils';
const userStore = useUserStore();
/** 列表的搜索表单 */
export function useGridFormSchema(): VbenFormSchema[] {
return [
{
fieldName: 'times',
label: '时间范围',
component: 'RangePicker',
componentProps: {
...getRangePickerDefaultProps(),
},
defaultValue: [
formatDateTime(beginOfDay(new Date(Date.now() - 3600 * 1000 * 24 * 7))),
formatDateTime(endOfDay(new Date(Date.now() - 3600 * 1000 * 24))),
] as [Date, Date],
},
{
fieldName: 'interval',
label: '时间间隔',
component: 'Select',
componentProps: {
allowClear: true,
options: getDictOptions(DICT_TYPE.DATE_INTERVAL, 'number'),
},
defaultValue: 2,
},
{
fieldName: 'deptId',
label: '归属部门',
component: 'ApiTreeSelect',
componentProps: {
api: async () => {
const data = await getSimpleDeptList();
return handleTree(data);
},
labelField: 'name',
valueField: 'id',
childrenField: 'children',
treeDefaultExpandAll: true,
},
defaultValue: userStore.userInfo?.deptId,
},
{
fieldName: 'userId',
label: '员工',
component: 'ApiSelect',
componentProps: {
api: getSimpleUserList,
allowClear: true,
labelField: 'nickname',
valueField: 'id',
},
},
];
}
/** 列表的字段 */
export function useSummaryGridColumns(): VxeTableGridOptions['columns'] {
return [
{
type: 'seq',
title: '序号',
},
{
field: 'ownerUserName',
title: '员工姓名',
minWidth: 100,
},
{
field: 'customerCreateCount',
title: '新增客户数',
minWidth: 200,
},
{
field: 'customerDealCount',
title: '成交客户数',
minWidth: 200,
},
{
field: 'customerDealRate',
title: '客户成交率(%)',
minWidth: 200,
formatter: ({ row }) => {
return erpCalculatePercentage(
row.customerDealCount,
row.customerCreateCount,
);
},
},
{
field: 'contractPrice',
title: '合同总金额',
minWidth: 200,
formatter: 'formatAmount2',
},
{
field: 'receivablePrice',
title: '回款金额',
minWidth: 200,
formatter: 'formatAmount2',
},
{
field: 'creceivablePrice',
title: '未回款金额',
minWidth: 200,
formatter: ({ row }) => {
return erpCalculatePercentage(row.receivablePrice, row.contractPrice);
},
},
{
field: 'ccreceivablePrice',
title: '回款完成率(%)',
formatter: ({ row }) => {
return erpCalculatePercentage(row.receivablePrice, row.contractPrice);
},
},
];
}

View File

@ -1,28 +1,151 @@
<script lang="ts" setup>
import { Page } from '@vben/common-ui';
import type { EchartsUIType } from '@vben/plugins/echarts';
import { Button } from 'ant-design-vue';
import type { VxeTableGridOptions } from '#/adapter/vxe-table';
import type { CrmStatisticsCustomerApi } from '#/api/crm/statistics/customer';
import { ref } from 'vue';
import { Page } from '@vben/common-ui';
import { EchartsUI, useEcharts } from '@vben/plugins/echarts';
import { Tabs } from 'ant-design-vue';
import { useVbenVxeGrid } from '#/adapter/vxe-table';
import {
getCustomerSummaryByDate,
getCustomerSummaryByUser,
} from '#/api/crm/statistics/customer';
import { useGridFormSchema, useSummaryGridColumns } from './data';
const activeTabName = ref('customerSummary');
const chartRef = ref<EchartsUIType>();
const { renderEcharts } = useEcharts(chartRef);
async function setChartData(res: any) {
renderEcharts({
grid: {
bottom: '5%',
containLabel: true,
left: '5%',
right: '5%',
top: '5 %',
},
legend: {},
series: [
{
name: '新增客户数',
type: 'bar',
yAxisIndex: 0,
data: res.map((item: any) => item.customerCreateCount),
},
{
name: '成交客户数',
type: 'bar',
yAxisIndex: 1,
data: res.map((item: any) => item.customerDealCount),
},
],
toolbox: {
feature: {
dataZoom: {
xAxisIndex: false, // Y
},
brush: {
type: ['lineX', 'clear'], //
},
saveAsImage: { show: true, name: '客户总量分析' }, //
},
},
tooltip: {
trigger: 'axis',
axisPointer: {
type: 'shadow',
},
},
yAxis: [
{
type: 'value',
name: '新增客户数',
min: 0,
minInterval: 1, //
},
{
type: 'value',
name: '成交客户数',
min: 0,
minInterval: 1, //
splitLine: {
lineStyle: {
type: 'dotted', // 线,
opacity: 0.7,
},
},
},
],
xAxis: {
type: 'category',
name: '日期',
data: res.map((item: any) => item.time),
},
});
}
const [Grid, gridApi] = useVbenVxeGrid({
formOptions: {
schema: useGridFormSchema(),
},
gridOptions: {
columns: useSummaryGridColumns(),
height: 'auto',
keepSource: true,
pagerConfig: {
enabled: false,
},
proxyConfig: {
ajax: {
query: async (_, formValues) => {
const res = await getCustomerSummaryByDate(formValues);
setChartData(res);
return await getCustomerSummaryByUser(formValues);
},
},
},
rowConfig: {
keyField: 'id',
isHover: true,
},
toolbarConfig: {
enabled: false,
},
} as VxeTableGridOptions<CrmStatisticsCustomerApi.CustomerSummaryByUser>,
});
function handleTabChange(key: any) {
activeTabName.value = key;
gridApi.query();
}
</script>
<template>
<Page>
<Button
danger
type="link"
target="_blank"
href="https://github.com/yudaocode/yudao-ui-admin-vue3"
>
该功能支持 Vue3 + element-plus 版本
</Button>
<br />
<Button
type="link"
target="_blank"
href="https://github.com/yudaocode/yudao-ui-admin-vue3/blob/master/src/views/crm/statistics/customer/index.vue"
>
可参考
https://github.com/yudaocode/yudao-ui-admin-vue3/blob/master/src/views/crm/statistics/customer/index.vue
代码pull request 贡献给我们
</Button>
<Page auto-content-height>
<Grid>
<template #top>
<Tabs v-model:active-key="activeTabName" @change="handleTabChange">
<Tabs.TabPane
tab="客户总量分析"
key="customerSummary"
:force-render="true"
/>
<Tabs.TabPane
tab="客户跟进次数分析"
key="followUpSummary"
:force-render="true"
/>
</Tabs>
<EchartsUI class="mb-20 h-full w-full" ref="chartRef" />
</template>
</Grid>
</Page>
</template>