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
36385a74
authored
Apr 03, 2024
by
puhui999
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
CRM: 完善用户画像数据统计
parent
d16bed4a
Hide whitespace changes
Inline
Side-by-side
Showing
9 changed files
with
339 additions
and
137 deletions
+339
-137
src/api/crm/statistics/customer.ts
+0
-59
src/api/crm/statistics/portrait.ts
+60
-0
src/utils/dict.ts
+1
-1
src/views/crm/statistics/customer/index.vue
+22
-56
src/views/crm/statistics/portrait/components/CustomerAddress.vue
+3
-3
src/views/crm/statistics/portrait/components/CustomerIndustry.vue
+32
-6
src/views/crm/statistics/portrait/components/CustomerLevel.vue
+32
-6
src/views/crm/statistics/portrait/components/CustomerSource.vue
+33
-6
src/views/crm/statistics/portrait/index.vue
+156
-0
No files found.
src/api/crm/statistics/customer.ts
View file @
36385a74
...
...
@@ -49,36 +49,6 @@ export interface CrmStatisticsCustomerDealCycleByDateRespVO {
customerDealCycle
:
number
}
export
interface
CrmStatisticCustomerBaseRespVO
{
customerCount
:
number
dealCount
:
number
dealPortion
:
number
}
export
interface
CrmStatisticCustomerIndustryRespVO
extends
CrmStatisticCustomerBaseRespVO
{
industryId
:
number
industryName
:
string
industryPortion
:
number
}
export
interface
CrmStatisticCustomerSourceRespVO
extends
CrmStatisticCustomerBaseRespVO
{
source
:
number
sourceName
:
string
sourcePortion
:
number
}
export
interface
CrmStatisticCustomerLevelRespVO
extends
CrmStatisticCustomerBaseRespVO
{
level
:
number
levelName
:
string
levelPortion
:
number
}
export
interface
CrmStatisticCustomerAreaRespVO
extends
CrmStatisticCustomerBaseRespVO
{
areaId
:
number
areaName
:
string
areaPortion
:
number
}
export
interface
CrmStatisticsCustomerDealCycleByUserRespVO
{
ownerUserName
:
string
customerDealCycle
:
number
...
...
@@ -142,34 +112,5 @@ export const StatisticsCustomerApi = {
url
:
'/crm/statistics-customer/get-customer-deal-cycle-by-user'
,
params
})
},
// TODO @puhui999:下面这些拆出去哈;
// 6.1 获取客户行业统计数据
getCustomerIndustry
:
(
params
:
any
)
=>
{
return
request
.
get
({
url
:
'/crm/statistics-portrait/get-customer-industry-summary'
,
params
})
},
// 6.1 获取客户来源统计数据
getCustomerSource
:
(
params
:
any
)
=>
{
return
request
.
get
({
url
:
'/crm/statistics-portrait/get-customer-source-summary'
,
params
})
},
// 6.1 获取客户行业统计数据
getCustomerLevel
:
(
params
:
any
)
=>
{
return
request
.
get
({
url
:
'/crm/statistics-portrait/get-customer-level-summary'
,
params
})
},
// 6.1 获取客户行业统计数据
getCustomerArea
:
(
params
:
any
)
=>
{
return
request
.
get
({
url
:
'/crm/statistics-portrait/get-customer-area-summary'
,
params
})
}
}
src/api/crm/statistics/portrait.ts
0 → 100644
View file @
36385a74
import
request
from
'@/config/axios'
export
interface
CrmStatisticCustomerBaseRespVO
{
customerCount
:
number
dealCount
:
number
dealPortion
:
string
|
number
}
export
interface
CrmStatisticCustomerIndustryRespVO
extends
CrmStatisticCustomerBaseRespVO
{
industryId
:
number
industryPortion
:
string
|
number
}
export
interface
CrmStatisticCustomerSourceRespVO
extends
CrmStatisticCustomerBaseRespVO
{
source
:
number
sourcePortion
:
string
|
number
}
export
interface
CrmStatisticCustomerLevelRespVO
extends
CrmStatisticCustomerBaseRespVO
{
level
:
number
levelPortion
:
string
|
number
}
export
interface
CrmStatisticCustomerAreaRespVO
extends
CrmStatisticCustomerBaseRespVO
{
areaId
:
number
areaName
:
string
areaPortion
:
string
|
number
}
// 客户分析 API
export
const
StatisticsPortraitApi
=
{
// 1. 获取客户行业统计数据
getCustomerIndustry
:
(
params
:
any
)
=>
{
return
request
.
get
({
url
:
'/crm/statistics-portrait/get-customer-industry-summary'
,
params
})
},
// 2. 获取客户来源统计数据
getCustomerSource
:
(
params
:
any
)
=>
{
return
request
.
get
({
url
:
'/crm/statistics-portrait/get-customer-source-summary'
,
params
})
},
// 3. 获取客户级别统计数据
getCustomerLevel
:
(
params
:
any
)
=>
{
return
request
.
get
({
url
:
'/crm/statistics-portrait/get-customer-level-summary'
,
params
})
},
// 4. 获取客户地区统计数据
getCustomerArea
:
(
params
:
any
)
=>
{
return
request
.
get
({
url
:
'/crm/statistics-portrait/get-customer-area-summary'
,
params
})
}
}
src/utils/dict.ts
View file @
36385a74
...
...
@@ -137,7 +137,7 @@ export enum DICT_TYPE {
INFRA_FILE_STORAGE
=
'infra_file_storage'
,
// ========== BPM 模块 ==========
BPM_MODEL_FORM_TYPE
=
'bpm_model_
category
'
,
BPM_MODEL_FORM_TYPE
=
'bpm_model_
form_type
'
,
BPM_TASK_CANDIDATE_STRATEGY
=
'bpm_task_candidate_strategy'
,
BPM_PROCESS_INSTANCE_STATUS
=
'bpm_process_instance_status'
,
BPM_TASK_STATUS
=
'bpm_task_status'
,
...
...
src/views/crm/statistics/customer/index.vue
View file @
36385a74
...
...
@@ -3,22 +3,22 @@
<ContentWrap>
<!-- 搜索工作栏 -->
<el-form
class=
"-mb-15px"
:model=
"queryParams"
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"
:default-time=
"[new Date('1 00:00:00'), new Date('1 23:59:59')]"
/>
</el-form-item>
<el-form-item
label=
"时间间隔"
prop=
"interval"
>
...
...
@@ -34,28 +34,34 @@
<el-form-item
label=
"归属部门"
prop=
"deptId"
>
<el-tree-select
v-model=
"queryParams.deptId"
class=
"!w-240px"
: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"
placeholder=
"员工"
clearable
>
<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"
:key=
"index"
/>
</el-select>
</el-form-item>
<el-form-item>
<el-button
@
click=
"handleQuery"
>
<Icon
icon=
"ep:search"
class=
"mr-5px"
/>
搜索
</el-button>
<el-button
@
click=
"resetQuery"
>
<Icon
icon=
"ep:refresh"
class=
"mr-5px"
/>
重置
</el-button>
<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>
...
...
@@ -64,41 +70,25 @@
<el-col>
<el-tabs
v-model=
"activeTab"
>
<!-- 客户总量分析 -->
<el-tab-pane
label=
"客户总量分析"
name=
"customerSummary"
lazy
>
<CustomerSummary
:query-params=
"queryParams"
ref=
"customerSummaryRef
"
/>
<el-tab-pane
label=
"客户总量分析"
lazy
name=
"customerSummary"
>
<CustomerSummary
ref=
"customerSummaryRef"
:query-params=
"queryParams
"
/>
</el-tab-pane>
<!-- 客户跟进次数分析 -->
<el-tab-pane
label=
"客户跟进次数分析"
name=
"followUpSummary"
lazy
>
<CustomerFollowUpSummary
:query-params=
"queryParams"
ref=
"followUpSummaryRef
"
/>
<el-tab-pane
label=
"客户跟进次数分析"
lazy
name=
"followUpSummary"
>
<CustomerFollowUpSummary
ref=
"followUpSummaryRef"
:query-params=
"queryParams
"
/>
</el-tab-pane>
<!-- 客户跟进方式分析 -->
<el-tab-pane
label=
"客户跟进方式分析"
name=
"followUpType"
lazy
>
<CustomerFollowUpType
:query-params=
"queryParams"
ref=
"followUpTypeRef
"
/>
<el-tab-pane
label=
"客户跟进方式分析"
lazy
name=
"followUpType"
>
<CustomerFollowUpType
ref=
"followUpTypeRef"
:query-params=
"queryParams
"
/>
</el-tab-pane>
<!-- 客户转化率分析 -->
<el-tab-pane
label=
"客户转化率分析"
name=
"conversionStat"
lazy
>
<CustomerConversionStat
:query-params=
"queryParams"
ref=
"conversionStatRef
"
/>
<el-tab-pane
label=
"客户转化率分析"
lazy
name=
"conversionStat"
>
<CustomerConversionStat
ref=
"conversionStatRef"
:query-params=
"queryParams
"
/>
</el-tab-pane>
<!-- 成交周期分析 -->
<el-tab-pane
label=
"成交周期分析"
lazy
name=
"dealCycle"
>
<CustomerDealCycle
ref=
"dealCycleRef"
:query-params=
"queryParams"
/>
</el-tab-pane>
<!-- 城市分布分析 -->
<el-tab-pane
label=
"城市分布分析"
lazy
name=
"addressRef"
>
<CustomerAddress
ref=
"addressRef"
:query-params=
"queryParams"
/>
</el-tab-pane>
<!-- 客户级别分析 -->
<el-tab-pane
label=
"客户级别分析"
lazy
name=
"levelRef"
>
<CustomerLevel
ref=
"levelRef"
:query-params=
"queryParams"
/>
</el-tab-pane>
<!-- 客户来源分析 -->
<el-tab-pane
label=
"客户来源分析"
lazy
name=
"sourceRef"
>
<CustomerSource
ref=
"sourceRef"
:query-params=
"queryParams"
/>
</el-tab-pane>
<!-- 客户行业分析 -->
<el-tab-pane
label=
"客户行业分析"
lazy
name=
"industryRef"
>
<CustomerIndustry
ref=
"industryRef"
:query-params=
"queryParams"
/>
</el-tab-pane>
</el-tabs>
</el-col>
</
template
>
...
...
@@ -115,10 +105,6 @@ import CustomerFollowUpType from './components/CustomerFollowUpType.vue'
import
CustomerConversionStat
from
'./components/CustomerConversionStat.vue'
import
CustomerDealCycle
from
'./components/CustomerDealCycle.vue'
import
{
DICT_TYPE
,
getIntDictOptions
}
from
'@/utils/dict'
import
CustomerAddress
from
'./components/CustomerAddress.vue'
import
CustomerIndustry
from
'./components/CustomerIndustry.vue'
import
CustomerSource
from
'./components/CustomerSource.vue'
import
CustomerLevel
from
'./components/CustomerLevel.vue'
defineOptions
({
name
:
'CrmStatisticsCustomer'
})
...
...
@@ -152,13 +138,6 @@ const conversionStatRef = ref() // 4. 客户转化率分析
// 5. TODO 公海客户分析
// 缺 crm_owner_record 表 TODO @dhb52:可以先做界面 + 接口,接口数据直接写死返回,相当于 mock 出来
const
dealCycleRef
=
ref
()
// 6. 成交周期分析
const
addressRef
=
ref
()
// 客户级别
const
levelRef
=
ref
()
// 客户来源
const
sourceRef
=
ref
()
// 客户行业
const
industryRef
=
ref
()
/** 搜索按钮操作 */
const
handleQuery
=
()
=>
{
...
...
@@ -178,19 +157,6 @@ const handleQuery = () => {
case
'dealCycle'
:
// 成交周期分析
dealCycleRef
.
value
?.
loadData
?.()
break
// TODO @puhui999:这 4 个拆出去哈;独立一个【客户画像】菜单
case
'addressRef'
:
addressRef
.
value
?.
loadData
?.()
break
case
'levelRef'
:
levelRef
.
value
?.
loadData
?.()
break
case
'sourceRef'
:
sourceRef
.
value
?.
loadData
?.()
break
case
'industryRef'
:
industryRef
.
value
?.
loadData
?.()
break
}
}
...
...
src/views/crm/statistics/
customer
/components/CustomerAddress.vue
→
src/views/crm/statistics/
portrait
/components/CustomerAddress.vue
View file @
36385a74
...
...
@@ -22,8 +22,8 @@ import china from '@/assets/map/json/china.json'
import
echarts
from
'@/plugins/echarts'
import
{
CrmStatisticCustomerAreaRespVO
,
Statistics
Customer
Api
}
from
'@/api/crm/statistics/
customer
'
Statistics
Portrait
Api
}
from
'@/api/crm/statistics/
portrait
'
defineOptions
({
name
:
'CustomerAddress'
})
const
props
=
defineProps
<
{
queryParams
:
any
}
>
()
// 搜索参数
...
...
@@ -102,7 +102,7 @@ const echartsOption2 = reactive<EChartsOption>({
const
loadData
=
async
()
=>
{
// 1. 加载统计数据
loading
.
value
=
true
const
areaList
=
await
Statistics
Customer
Api
.
getCustomerArea
(
props
.
queryParams
)
const
areaList
=
await
Statistics
Portrait
Api
.
getCustomerArea
(
props
.
queryParams
)
areaStatisticsList
.
value
=
areaList
.
map
((
item
:
CrmStatisticCustomerAreaRespVO
)
=>
{
return
{
...
item
,
...
...
src/views/crm/statistics/
customer
/components/CustomerIndustry.vue
→
src/views/crm/statistics/
portrait
/components/CustomerIndustry.vue
View file @
36385a74
...
...
@@ -20,7 +20,11 @@
<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=
"客户行业"
min-width=
"200"
prop=
"industryName"
/>
<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"
/>
...
...
@@ -31,9 +35,12 @@
<
script
lang=
"ts"
setup
>
import
{
CrmStatisticCustomerIndustryRespVO
,
Statistics
Customer
Api
}
from
'@/api/crm/statistics/
customer
'
Statistics
Portrait
Api
}
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
}
>
()
// 搜索参数
...
...
@@ -140,12 +147,12 @@ const echartsOption2 = reactive<EChartsOption>({
const
loadData
=
async
()
=>
{
// 1. 加载统计数据
loading
.
value
=
true
const
industryList
=
await
Statistics
Customer
Api
.
getCustomerIndustry
(
props
.
queryParams
)
const
industryList
=
await
Statistics
Portrait
Api
.
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
:
r
.
industryName
,
name
:
getDictLabel
(
DICT_TYPE
.
CRM_CUSTOMER_INDUSTRY
,
r
.
industryId
)
,
value
:
r
.
customerCount
}
})
...
...
@@ -154,15 +161,34 @@ const loadData = async () => {
if
(
echartsOption2
.
series
&&
echartsOption2
.
series
[
0
]
&&
echartsOption2
.
series
[
0
][
'data'
])
{
echartsOption2
.
series
[
0
][
'data'
]
=
industryList
.
map
((
r
:
CrmStatisticCustomerIndustryRespVO
)
=>
{
return
{
name
:
r
.
industryName
,
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
(()
=>
{
...
...
src/views/crm/statistics/
customer
/components/CustomerLevel.vue
→
src/views/crm/statistics/
portrait
/components/CustomerLevel.vue
View file @
36385a74
...
...
@@ -20,7 +20,11 @@
<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=
"客户来源"
min-width=
"200"
prop=
"levelName"
/>
<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"
/>
...
...
@@ -31,9 +35,12 @@
<
script
lang=
"ts"
setup
>
import
{
CrmStatisticCustomerLevelRespVO
,
Statistics
Customer
Api
}
from
'@/api/crm/statistics/
customer
'
Statistics
Portrait
Api
}
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
}
>
()
// 搜索参数
...
...
@@ -140,12 +147,12 @@ const echartsOption2 = reactive<EChartsOption>({
const
loadData
=
async
()
=>
{
// 1. 加载统计数据
loading
.
value
=
true
const
levelList
=
await
Statistics
Customer
Api
.
getCustomerLevel
(
props
.
queryParams
)
const
levelList
=
await
Statistics
Portrait
Api
.
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
:
r
.
levelName
,
name
:
getDictLabel
(
DICT_TYPE
.
CRM_CUSTOMER_LEVEL
,
r
.
level
)
,
value
:
r
.
customerCount
}
})
...
...
@@ -154,15 +161,34 @@ const loadData = async () => {
if
(
echartsOption2
.
series
&&
echartsOption2
.
series
[
0
]
&&
echartsOption2
.
series
[
0
][
'data'
])
{
echartsOption2
.
series
[
0
][
'data'
]
=
levelList
.
map
((
r
:
CrmStatisticCustomerLevelRespVO
)
=>
{
return
{
name
:
r
.
levelName
,
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
(()
=>
{
...
...
src/views/crm/statistics/
customer
/components/CustomerSource.vue
→
src/views/crm/statistics/
portrait
/components/CustomerSource.vue
View file @
36385a74
...
...
@@ -20,7 +20,11 @@
<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=
"客户来源"
min-width=
"200"
prop=
"sourceName"
/>
<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"
/>
...
...
@@ -31,9 +35,12 @@
<
script
lang=
"ts"
setup
>
import
{
CrmStatisticCustomerSourceRespVO
,
Statistics
Customer
Api
}
from
'@/api/crm/statistics/
customer
'
Statistics
Portrait
Api
}
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
}
>
()
// 搜索参数
...
...
@@ -140,12 +147,12 @@ const echartsOption2 = reactive<EChartsOption>({
const
loadData
=
async
()
=>
{
// 1. 加载统计数据
loading
.
value
=
true
const
sourceList
=
await
Statistics
Customer
Api
.
getCustomerSource
(
props
.
queryParams
)
const
sourceList
=
await
Statistics
Portrait
Api
.
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
:
r
.
sourceName
,
name
:
getDictLabel
(
DICT_TYPE
.
CRM_CUSTOMER_SOURCE
,
r
.
source
)
,
value
:
r
.
customerCount
}
})
...
...
@@ -154,16 +161,36 @@ const loadData = async () => {
if
(
echartsOption2
.
series
&&
echartsOption2
.
series
[
0
]
&&
echartsOption2
.
series
[
0
][
'data'
])
{
echartsOption2
.
series
[
0
][
'data'
]
=
sourceList
.
map
((
r
:
CrmStatisticCustomerSourceRespVO
)
=>
{
return
{
name
:
r
.
sourceName
,
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
()
...
...
src/views/crm/statistics/portrait/index.vue
0 → 100644
View file @
36385a74
<!-- 数据统计 - 客户画像 -->
<
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=
"addressRef"
>
<CustomerAddress
ref=
"addressRef"
:query-params=
"queryParams"
/>
</el-tab-pane>
<!-- 客户级别分析 -->
<el-tab-pane
label=
"客户级别分析"
lazy
name=
"levelRef"
>
<CustomerLevel
ref=
"levelRef"
:query-params=
"queryParams"
/>
</el-tab-pane>
<!-- 客户来源分析 -->
<el-tab-pane
label=
"客户来源分析"
lazy
name=
"sourceRef"
>
<CustomerSource
ref=
"sourceRef"
:query-params=
"queryParams"
/>
</el-tab-pane>
<!-- 客户行业分析 -->
<el-tab-pane
label=
"客户行业分析"
lazy
name=
"industryRef"
>
<CustomerIndustry
ref=
"industryRef"
:query-params=
"queryParams"
/>
</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
CustomerAddress
from
'./components/CustomerAddress.vue'
import
CustomerIndustry
from
'./components/CustomerIndustry.vue'
import
CustomerSource
from
'./components/CustomerSource.vue'
import
CustomerLevel
from
'./components/CustomerLevel.vue'
defineOptions
({
name
:
'CrmStatisticsPortrait'
})
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
(
'addressRef'
)
// 活跃标签
const
addressRef
=
ref
()
// 客户地区分布
const
levelRef
=
ref
()
// 客户级别
const
sourceRef
=
ref
()
// 客户来源
const
industryRef
=
ref
()
// 客户行业
/** 搜索按钮操作 */
const
handleQuery
=
()
=>
{
switch
(
activeTab
.
value
)
{
case
'addressRef'
:
addressRef
.
value
?.
loadData
?.()
break
case
'levelRef'
:
levelRef
.
value
?.
loadData
?.()
break
case
'sourceRef'
:
sourceRef
.
value
?.
loadData
?.()
break
case
'industryRef'
:
industryRef
.
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