Skip to content
Toggle navigation
P
Projects
G
Groups
S
Snippets
Help
phsl
/
admin
This project
Loading...
Sign in
Toggle navigation
Go to a project
Project
Repository
Issues
0
Merge Requests
0
Pipelines
Wiki
Snippets
Members
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Commit
3c49ed57
authored
Apr 14, 2024
by
puhui999
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
CRM: 完善销售漏斗分析
parent
2d7bab8a
Hide whitespace changes
Inline
Side-by-side
Showing
4 changed files
with
305 additions
and
6 deletions
+305
-6
src/api/crm/statistics/funnel.ts
+25
-0
src/utils/dict.ts
+7
-6
src/views/crm/statistics/funnel/components/FunnelBusiness.vue
+135
-0
src/views/crm/statistics/funnel/index.vue
+138
-0
No files found.
src/api/crm/statistics/funnel.ts
0 → 100644
View file @
3c49ed57
import
request
from
'@/config/axios'
export
interface
CrmStatisticFunnelRespVO
{
customerCount
:
number
// 客户数
businessCount
:
number
// 商机数
winCount
:
number
// 赢单数
}
// 客户分析 API
export
const
StatisticFunnelApi
=
{
// 1. 获取销售漏斗统计数据
getFunnelSummary
:
(
params
:
any
)
=>
{
return
request
.
get
({
url
:
'/crm/statistics-funnel/get-funnel-summary'
,
params
})
},
// 2. 获取商机结束状态统计
getBusinessEndStatusSummary
:
(
params
:
any
)
=>
{
return
request
.
get
({
url
:
'/crm/statistics-funnel/get-business-end-status-summary'
,
params
})
}
}
src/utils/dict.ts
View file @
3c49ed57
...
@@ -197,14 +197,15 @@ export enum DICT_TYPE {
...
@@ -197,14 +197,15 @@ export enum DICT_TYPE {
// ========== CRM - 客户管理模块 ==========
// ========== CRM - 客户管理模块 ==========
CRM_AUDIT_STATUS
=
'crm_audit_status'
,
// CRM 审批状态
CRM_AUDIT_STATUS
=
'crm_audit_status'
,
// CRM 审批状态
CRM_BIZ_TYPE
=
'crm_biz_type'
,
// CRM 业务类型
CRM_BIZ_TYPE
=
'crm_biz_type'
,
// CRM 业务类型
CRM_BUSINESS_END_STATUS_TYPE
=
'crm_business_end_status_type'
,
// CRM 商机结束状态类型
CRM_RECEIVABLE_RETURN_TYPE
=
'crm_receivable_return_type'
,
// CRM 回款的还款方式
CRM_RECEIVABLE_RETURN_TYPE
=
'crm_receivable_return_type'
,
// CRM 回款的还款方式
CRM_CUSTOMER_INDUSTRY
=
'crm_customer_industry'
,
CRM_CUSTOMER_INDUSTRY
=
'crm_customer_industry'
,
// CRM 客户所属行业
CRM_CUSTOMER_LEVEL
=
'crm_customer_level'
,
CRM_CUSTOMER_LEVEL
=
'crm_customer_level'
,
// CRM 客户级别
CRM_CUSTOMER_SOURCE
=
'crm_customer_source'
,
CRM_CUSTOMER_SOURCE
=
'crm_customer_source'
,
// CRM 客户来源
CRM_PRODUCT_STATUS
=
'crm_product_status'
,
CRM_PRODUCT_STATUS
=
'crm_product_status'
,
// CRM 商品状态
CRM_PERMISSION_LEVEL
=
'crm_permission_level'
,
// CRM 数据权限的级别
CRM_PERMISSION_LEVEL
=
'crm_permission_level'
,
// CRM 数据权限的级别
CRM_PRODUCT_UNIT
=
'crm_product_unit'
,
// 产品单位
CRM_PRODUCT_UNIT
=
'crm_product_unit'
,
//
CRM
产品单位
CRM_FOLLOW_UP_TYPE
=
'crm_follow_up_type'
,
// 跟进方式
CRM_FOLLOW_UP_TYPE
=
'crm_follow_up_type'
,
//
CRM
跟进方式
// ========== ERP - 企业资源计划模块 ==========
// ========== ERP - 企业资源计划模块 ==========
ERP_AUDIT_STATUS
=
'erp_audit_status'
,
// ERP 审批状态
ERP_AUDIT_STATUS
=
'erp_audit_status'
,
// ERP 审批状态
...
...
src/views/crm/statistics/funnel/components/FunnelBusiness.vue
0 → 100644
View file @
3c49ed57
<!-- 销售漏斗分析 -->
<
template
>
<!-- Echarts图 -->
<el-card
shadow=
"never"
>
<el-row>
<el-col
:span=
"24"
>
<el-skeleton
:loading=
"loading"
animated
>
<Echart
:height=
"500"
:options=
"echartsOption"
/>
</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=
"endStatus"
width=
"200"
>
<template
#
default=
"scope"
>
<dict-tag
:type=
"DICT_TYPE.CRM_BUSINESS_END_STATUS_TYPE"
:value=
"scope.row.endStatus"
/>
</
template
>
</el-table-column>
<el-table-column
align=
"center"
label=
"商机数"
min-width=
"200"
prop=
"businessCount"
/>
<el-table-column
align=
"center"
label=
"商机总金额(元)"
min-width=
"200"
prop=
"totalPrice"
/>
</el-table>
</el-card>
</template>
<
script
lang=
"ts"
setup
>
import
{
CrmStatisticFunnelRespVO
,
StatisticFunnelApi
}
from
'@/api/crm/statistics/funnel'
import
{
EChartsOption
}
from
'echarts'
import
{
DICT_TYPE
}
from
'@/utils/dict'
import
echarts
from
'@/plugins/echarts'
import
{
FunnelChart
}
from
'echarts/charts'
echarts
?.
use
([
FunnelChart
])
defineOptions
({
name
:
'FunnelBusiness'
})
const
props
=
defineProps
<
{
queryParams
:
any
}
>
()
// 搜索参数
const
loading
=
ref
(
false
)
// 加载中
const
list
=
ref
<
CrmStatisticFunnelRespVO
[]
>
([])
// 列表的数据
/** 销售漏斗 */
const
echartsOption
=
reactive
<
EChartsOption
>
({
title
:
{
text
:
'销售漏斗'
},
tooltip
:
{
trigger
:
'item'
,
formatter
:
'
{
a
}
<
br
/>
{
b
}
'
},
toolbox: {
feature: {
dataView: { readOnly: false },
restore: {},
saveAsImage: {}
}
},
legend: {
data: ['
客户
', '
商机
', '
赢单
']
},
series: [
{
name: '
销售漏斗
',
type: '
funnel
',
left: '
10
%
',
top: 60,
bottom: 60,
width: '
80
%
',
min: 0,
max: 100,
minSize: '
0
%
',
maxSize: '
100
%
',
sort: '
descending
',
gap: 2,
label: {
show: true,
position: '
inside
'
},
labelLine: {
length: 10,
lineStyle: {
width: 1,
type: '
solid
'
}
},
itemStyle: {
borderColor: '
#
fff
',
borderWidth: 1
},
emphasis: {
label: {
fontSize: 20
}
},
data: [
{ value: 60, name: '
客户
-
0
个
' },
{ value: 40, name: '
商机
-
0
个
' },
{ value: 20, name: '
赢单
-
0
个
' }
]
}
]
}) as EChartsOption
/** 获取统计数据 */
const loadData = async () => {
loading.value = true
// 1. 加载漏斗数据
const data = (await StatisticFunnelApi.getFunnelSummary(
props.queryParams
)) as CrmStatisticFunnelRespVO
// 2.1 更新 Echarts 数据
if (
!!data &&
echartsOption.series &&
echartsOption.series[0] &&
echartsOption.series[0]['
data
']
) {
// tips:写死 value 值是为了保持漏斗顺序不变
const list: { value: number; name: string }[] = []
list.push({ value: 60, name: `客户-${data.customerCount || 0}个` })
list.push({ value: 40, name: `商机-${data.businessCount || 0}个` })
list.push({ value: 20, name: `赢单-${data.winCount || 0}个` })
echartsOption.series[0]['
data
'
]
=
list
}
// 2.2 获取商机结束状态统计
list
.
value
=
await
StatisticFunnelApi
.
getBusinessEndStatusSummary
(
props
.
queryParams
)
loading
.
value
=
false
}
defineExpose
({
loadData
})
/** 初始化 */
onMounted
(()
=>
{
loadData
()
})
</
script
>
src/views/crm/statistics/funnel/index.vue
0 → 100644
View file @
3c49ed57
<!-- 数据统计 - 客户画像 -->
<
template
>
<ContentWrap>
<!-- 搜索工作栏 -->
<el-form
ref=
"queryFormRef"
:inline=
"true"
:model=
"queryParams"
class=
"-mb-15px"
label-width=
"68px"
>
<el-form-item
label=
"时间范围"
prop=
"orderDate"
>
<el-date-picker
v-model=
"queryParams.times"
:default-time=
"[new Date('1 00:00:00'), new Date('1 23:59:59')]"
:shortcuts=
"defaultShortcuts"
class=
"!w-240px"
end-placeholder=
"结束日期"
start-placeholder=
"开始日期"
type=
"daterange"
value-format=
"YYYY-MM-DD HH:mm:ss"
/>
</el-form-item>
<el-form-item
label=
"归属部门"
prop=
"deptId"
>
<el-tree-select
v-model=
"queryParams.deptId"
:data=
"deptList"
:props=
"defaultProps"
check-strictly
class=
"!w-240px"
node-key=
"id"
placeholder=
"请选择归属部门"
@
change=
"queryParams.userId = undefined"
/>
</el-form-item>
<el-form-item
label=
"员工"
prop=
"userId"
>
<el-select
v-model=
"queryParams.userId"
class=
"!w-240px"
clearable
placeholder=
"员工"
>
<el-option
v-for=
"(user, index) in userListByDeptId"
:key=
"index"
:label=
"user.nickname"
:value=
"user.id"
/>
</el-select>
</el-form-item>
<el-form-item>
<el-button
@
click=
"handleQuery"
>
<Icon
class=
"mr-5px"
icon=
"ep:search"
/>
搜索
</el-button>
<el-button
@
click=
"resetQuery"
>
<Icon
class=
"mr-5px"
icon=
"ep:refresh"
/>
重置
</el-button>
</el-form-item>
</el-form>
</ContentWrap>
<!-- 客户统计 -->
<el-col>
<el-tabs
v-model=
"activeTab"
>
<el-tab-pane
label=
"销售漏斗分析"
lazy
name=
"funnelRef"
>
<FunnelBusiness
ref=
"funnelRef"
:query-params=
"queryParams"
/>
</el-tab-pane>
<el-tab-pane
label=
"新增商机分析"
lazy
name=
"levelRef"
/>
<el-tab-pane
label=
"商机转化率分析"
lazy
name=
"sourceRef"
/>
</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
FunnelBusiness
from
'./components/FunnelBusiness.vue'
defineOptions
({
name
:
'CrmStatisticsFunnel'
})
const
queryParams
=
reactive
({
deptId
:
useUserStore
().
getUser
.
deptId
,
userId
:
undefined
,
times
:
[
// 默认显示最近一周的数据
formatDate
(
beginOfDay
(
new
Date
(
new
Date
().
getTime
()
-
3600
*
1000
*
24
*
7
))),
formatDate
(
endOfDay
(
new
Date
(
new
Date
().
getTime
()
-
3600
*
1000
*
24
)))
]
})
const
queryFormRef
=
ref
()
// 搜索的表单
const
deptList
=
ref
<
Tree
[]
>
([])
// 部门树形结构
const
userList
=
ref
<
UserApi
.
UserVO
[]
>
([])
// 全量用户清单
/** 根据选择的部门筛选员工清单 */
const
userListByDeptId
=
computed
(()
=>
queryParams
.
deptId
?
userList
.
value
.
filter
((
u
:
UserApi
.
UserVO
)
=>
u
.
deptId
===
queryParams
.
deptId
)
:
[]
)
const
activeTab
=
ref
(
'funnelRef'
)
// 活跃标签
const
funnelRef
=
ref
()
// 客户地区分布
const
levelRef
=
ref
()
// 客户级别
const
sourceRef
=
ref
()
// 客户来源
/** 搜索按钮操作 */
const
handleQuery
=
()
=>
{
switch
(
activeTab
.
value
)
{
case
'funnelRef'
:
funnelRef
.
value
?.
loadData
?.()
break
case
'levelRef'
:
levelRef
.
value
?.
loadData
?.()
break
case
'sourceRef'
:
sourceRef
.
value
?.
loadData
?.()
break
}
}
/** 当 activeTab 改变时,刷新当前活动的 tab */
watch
(
activeTab
,
()
=>
{
handleQuery
()
})
/** 重置按钮操作 */
const
resetQuery
=
()
=>
{
queryFormRef
.
value
.
resetFields
()
handleQuery
()
}
/** 初始化 */
onMounted
(
async
()
=>
{
deptList
.
value
=
handleTree
(
await
DeptApi
.
getSimpleDeptList
())
userList
.
value
=
handleTree
(
await
UserApi
.
getSimpleUserList
())
})
</
script
>
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment