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
bc95dc29
authored
Apr 14, 2024
by
puhui999
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
CRM: 完善新增商机分析
parent
3c49ed57
Hide whitespace changes
Inline
Side-by-side
Showing
4 changed files
with
355 additions
and
41 deletions
+355
-41
src/api/crm/statistics/funnel.ts
+20
-0
src/views/crm/business/index.vue
+41
-33
src/views/crm/statistics/funnel/components/BusinessSummary.vue
+259
-0
src/views/crm/statistics/funnel/index.vue
+35
-8
No files found.
src/api/crm/statistics/funnel.ts
View file @
bc95dc29
...
...
@@ -6,6 +6,12 @@ export interface CrmStatisticFunnelRespVO {
winCount
:
number
// 赢单数
}
export
interface
CrmStatisticsBusinessSummaryByDateRespVO
{
time
:
string
// 时间
businessCreateCount
:
number
// 商机数
businessDealCount
:
number
// 商机金额
}
// 客户分析 API
export
const
StatisticFunnelApi
=
{
// 1. 获取销售漏斗统计数据
...
...
@@ -21,5 +27,19 @@ export const StatisticFunnelApi = {
url
:
'/crm/statistics-funnel/get-business-end-status-summary'
,
params
})
},
// 3. 获取新增商机分析(按日期)
getBusinessSummaryByDate
:
(
params
:
any
)
=>
{
return
request
.
get
({
url
:
'/crm/statistics-funnel/get-business-summary-by-date'
,
params
})
},
// 4. 获取商机列表(按日期)
getBusinessPageByDate
:
(
params
:
any
)
=>
{
return
request
.
get
({
url
:
'/crm/statistics-funnel/get-business-page-by-date'
,
params
})
}
}
src/views/crm/business/index.vue
View file @
bc95dc29
...
...
@@ -5,35 +5,43 @@
<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=
"name"
>
<el-input
v-model=
"queryParams.name"
placeholder=
"请输入商机名称
"
class=
"!w-240px
"
clearable
placeholder=
"请输入商机名称"
@
keyup
.
enter=
"handleQuery"
class=
"!w-240px"
/>
</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
type=
"primary"
@
click=
"openForm('create')"
v-hasPermi=
"['crm:business:create']"
>
<Icon
icon=
"ep:plus"
class=
"mr-5px"
/>
新增
<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-button
v-hasPermi=
"['crm:business:create']"
type=
"primary"
@
click=
"openForm('create')"
>
<Icon
class=
"mr-5px"
icon=
"ep:plus"
/>
新增
</el-button>
<el-button
type=
"success"
v-hasPermi=
"['crm:business:export']"
:loading=
"exportLoading"
plain
type=
"success"
@
click=
"handleExport"
:loading=
"exportLoading"
v-hasPermi=
"['crm:business:export']"
>
<Icon
icon=
"ep:download"
class=
"mr-5px"
/>
导出
<Icon
class=
"mr-5px"
icon=
"ep:download"
/>
导出
</el-button>
</el-form-item>
</el-form>
...
...
@@ -46,8 +54,8 @@
<el-tab-pane
label=
"我参与的"
name=
"2"
/>
<el-tab-pane
label=
"下属负责的"
name=
"3"
/>
</el-tabs>
<el-table
v-loading=
"loading"
:data=
"list"
:s
tripe=
"true"
:show-overflow-tooltip
=
"true"
>
<el-table-column
align=
"center"
label=
"商机名称"
fixed=
"left
"
prop=
"name"
width=
"160"
>
<el-table
v-loading=
"loading"
:data=
"list"
:s
how-overflow-tooltip=
"true"
:stripe
=
"true"
>
<el-table-column
align=
"center"
fixed=
"left"
label=
"商机名称
"
prop=
"name"
width=
"160"
>
<template
#
default=
"scope"
>
<el-link
:underline=
"false"
type=
"primary"
@
click=
"openDetail(scope.row.id)"
>
{{
scope
.
row
.
name
}}
...
...
@@ -66,17 +74,17 @@
</
template
>
</el-table-column>
<el-table-column
label=
"商机金额(元)
"
:formatter=
"erpPriceTableColumnFormatter
"
align=
"center"
label=
"商机金额(元)"
prop=
"totalPrice"
width=
"140"
:formatter=
"erpPriceTableColumnFormatter"
/>
<el-table-column
label=
"预计成交日期
"
:formatter=
"dateFormatter
"
align=
"center"
label=
"预计成交日期"
prop=
"dealTime"
:formatter=
"dateFormatter"
width=
"180px"
/>
<el-table-column
align=
"center"
label=
"备注"
prop=
"remark"
width=
"200"
/>
...
...
@@ -97,49 +105,49 @@
width=
"180px"
/>
<el-table-column
label=
"更新时间
"
:formatter=
"dateFormatter
"
align=
"center"
label=
"更新时间"
prop=
"updateTime"
:formatter=
"dateFormatter"
width=
"180px"
/>
<el-table-column
label=
"创建时间
"
:formatter=
"dateFormatter
"
align=
"center"
label=
"创建时间"
prop=
"createTime"
:formatter=
"dateFormatter"
width=
"180px"
/>
<el-table-column
align=
"center"
label=
"创建人"
prop=
"creatorName"
width=
"100px"
/>
<el-table-column
label=
"商机状态组"
align=
"center"
prop=
"statusTypeName"
fixed=
"right"
label=
"商机状态组"
prop=
"statusTypeName"
width=
"140"
/>
<el-table-column
label=
"商机阶段"
align=
"center"
prop=
"statusName"
fixed=
"right"
label=
"商机阶段"
prop=
"statusName"
width=
"120"
/>
<el-table-column
label=
"操作"
align=
"center"
fixed=
"right
"
width=
"130px"
>
<el-table-column
align=
"center"
fixed=
"right"
label=
"操作
"
width=
"130px"
>
<
template
#
default=
"scope"
>
<el-button
v-hasPermi=
"['crm:business:update']"
link
type=
"primary"
@
click=
"openForm('update', scope.row.id)"
v-hasPermi=
"['crm:business:update']"
>
编辑
</el-button>
<el-button
v-hasPermi=
"['crm:business:delete']"
link
type=
"danger"
@
click=
"handleDelete(scope.row.id)"
v-hasPermi=
"['crm:business:delete']"
>
删除
</el-button>
...
...
@@ -148,9 +156,9 @@
</el-table>
<!-- 分页 -->
<Pagination
:total=
"total"
v-model:page=
"queryParams.pageNo"
v-model:limit=
"queryParams.pageSize"
v-model:page=
"queryParams.pageNo"
:total=
"total"
@
pagination=
"getList"
/>
</ContentWrap>
...
...
@@ -159,7 +167,7 @@
<BusinessForm
ref=
"formRef"
@
success=
"getList"
/>
</template>
<
script
setup
lang=
"ts"
>
<
script
lang=
"ts"
setup
>
import
{
dateFormatter
}
from
'@/utils/formatTime'
import
download
from
'@/utils/download'
import
*
as
BusinessApi
from
'@/api/crm/business'
...
...
@@ -216,7 +224,7 @@ const handleTabClick = (tab: TabsPaneContext) => {
}
/** 打开客户详情 */
const
{
currentRoute
,
push
}
=
useRouter
()
const
{
push
}
=
useRouter
()
const
openDetail
=
(
id
:
number
)
=>
{
push
({
name
:
'CrmBusinessDetail'
,
params
:
{
id
}
})
}
...
...
src/views/crm/statistics/funnel/components/BusinessSummary.vue
0 → 100644
View file @
bc95dc29
<!-- 客户总量统计 -->
<
template
>
<!-- Echarts图 -->
<el-card
shadow=
"never"
>
<el-skeleton
:loading=
"loading"
animated
>
<Echart
:height=
"500"
:options=
"echartsOption"
/>
</el-skeleton>
</el-card>
<!-- 统计列表 -->
<el-card
class=
"mt-16px"
shadow=
"never"
>
<el-table
v-loading=
"loading"
:data=
"list"
>
<el-table-column
align=
"center"
fixed=
"left"
label=
"序号"
type=
"index"
width=
"80"
/>
<el-table-column
align=
"center"
fixed=
"left"
label=
"商机名称"
prop=
"name"
width=
"160"
>
<template
#
default=
"scope"
>
<el-link
:underline=
"false"
type=
"primary"
@
click=
"openDetail(scope.row.id)"
>
{{
scope
.
row
.
name
}}
</el-link>
</
template
>
</el-table-column>
<el-table-column
align=
"center"
fixed=
"left"
label=
"客户名称"
prop=
"customerName"
width=
"120"
>
<
template
#
default=
"scope"
>
<el-link
:underline=
"false"
type=
"primary"
@
click=
"openCustomerDetail(scope.row.customerId)"
>
{{
scope
.
row
.
customerName
}}
</el-link>
</
template
>
</el-table-column>
<el-table-column
:formatter=
"erpPriceTableColumnFormatter"
align=
"center"
label=
"商机金额(元)"
prop=
"totalPrice"
width=
"140"
/>
<el-table-column
:formatter=
"dateFormatter"
align=
"center"
label=
"预计成交日期"
prop=
"dealTime"
width=
"180px"
/>
<el-table-column
align=
"center"
label=
"备注"
prop=
"remark"
width=
"200"
/>
<el-table-column
:formatter=
"dateFormatter"
align=
"center"
label=
"下次联系时间"
prop=
"contactNextTime"
width=
"180px"
/>
<el-table-column
align=
"center"
label=
"负责人"
prop=
"ownerUserName"
width=
"100px"
/>
<el-table-column
align=
"center"
label=
"所属部门"
prop=
"ownerUserDeptName"
width=
"100px"
/>
<el-table-column
:formatter=
"dateFormatter"
align=
"center"
label=
"最后跟进时间"
prop=
"contactLastTime"
width=
"180px"
/>
<el-table-column
:formatter=
"dateFormatter"
align=
"center"
label=
"更新时间"
prop=
"updateTime"
width=
"180px"
/>
<el-table-column
:formatter=
"dateFormatter"
align=
"center"
label=
"创建时间"
prop=
"createTime"
width=
"180px"
/>
<el-table-column
align=
"center"
label=
"创建人"
prop=
"creatorName"
width=
"100px"
/>
<el-table-column
align=
"center"
fixed=
"right"
label=
"商机状态组"
prop=
"statusTypeName"
width=
"140"
/>
<el-table-column
align=
"center"
fixed=
"right"
label=
"商机阶段"
prop=
"statusName"
width=
"120"
/>
</el-table>
<!-- 分页 -->
<Pagination
v-model:limit=
"queryParams0.pageSize"
v-model:page=
"queryParams0.pageNo"
:total=
"total"
@
pagination=
"getList"
/>
</el-card>
</template>
<
script
lang=
"ts"
setup
>
import
{
CrmStatisticsBusinessSummaryByDateRespVO
,
StatisticFunnelApi
}
from
'@/api/crm/statistics/funnel'
import
{
EChartsOption
}
from
'echarts'
import
{
erpPriceTableColumnFormatter
}
from
'@/utils'
import
{
dateFormatter
}
from
'@/utils/formatTime'
defineOptions
({
name
:
'BusinessSummary'
})
const
props
=
defineProps
<
{
queryParams
:
any
}
>
()
// 搜索参数
const
queryParams0
=
reactive
({
pageNo
:
1
,
pageSize
:
10
})
const
loading
=
ref
(
false
)
// 加载中
const
list
=
ref
([])
// 列表的数据
const
total
=
ref
(
0
)
/** 将传进来的值赋值给 formData */
watch
(
()
=>
props
.
queryParams
,
(
data
)
=>
{
if
(
!
data
)
{
return
}
const
newObj
=
{
...
queryParams0
,
...
data
}
Object
.
assign
(
queryParams0
,
newObj
)
},
{
immediate
:
true
}
)
/** 柱状图配置:纵向 */
const
echartsOption
=
reactive
<
EChartsOption
>
({
grid
:
{
left
:
30
,
right
:
30
,
// 让 X 轴右侧显示完整
bottom
:
20
,
containLabel
:
true
},
legend
:
{},
series
:
[
{
name
:
'新增商机数量'
,
type
:
'bar'
,
yAxisIndex
:
0
,
data
:
[]
},
{
name
:
'新增商机金额'
,
type
:
'bar'
,
yAxisIndex
:
1
,
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
:
'新增商机数量'
,
min
:
0
,
minInterval
:
1
// 显示整数刻度
},
{
type
:
'value'
,
name
:
'新增商机金额'
,
min
:
0
,
minInterval
:
1
,
// 显示整数刻度
splitLine
:
{
lineStyle
:
{
type
:
'dotted'
,
// 右侧网格线虚化, 减少混乱
opacity
:
0.7
}
}
}
],
xAxis
:
{
type
:
'category'
,
name
:
'日期'
,
data
:
[]
}
})
as
EChartsOption
/** 获取数据并填充图表 */
const
fetchAndFill
=
async
()
=>
{
// 1. 加载统计数据
const
businessSummaryByDate
=
await
StatisticFunnelApi
.
getBusinessSummaryByDate
(
props
.
queryParams
)
// 2.1 更新 Echarts 数据
if
(
echartsOption
.
xAxis
&&
echartsOption
.
xAxis
[
'data'
])
{
echartsOption
.
xAxis
[
'data'
]
=
businessSummaryByDate
.
map
(
(
s
:
CrmStatisticsBusinessSummaryByDateRespVO
)
=>
s
.
time
)
}
if
(
echartsOption
.
series
&&
echartsOption
.
series
[
0
]
&&
echartsOption
.
series
[
0
][
'data'
])
{
echartsOption
.
series
[
0
][
'data'
]
=
businessSummaryByDate
.
map
(
(
s
:
CrmStatisticsBusinessSummaryByDateRespVO
)
=>
s
.
businessCreateCount
)
}
if
(
echartsOption
.
series
&&
echartsOption
.
series
[
1
]
&&
echartsOption
.
series
[
1
][
'data'
])
{
echartsOption
.
series
[
1
][
'data'
]
=
businessSummaryByDate
.
map
(
(
s
:
CrmStatisticsBusinessSummaryByDateRespVO
)
=>
s
.
businessDealCount
)
}
// 2.2 更新列表数据
await
getList
()
}
/** 获取商机列表 */
const
getList
=
async
()
=>
{
const
data
=
await
StatisticFunnelApi
.
getBusinessPageByDate
(
props
.
queryParams
)
list
.
value
=
data
.
list
total
.
value
=
data
.
total
}
/** 打开客户详情 */
const
{
push
}
=
useRouter
()
const
openDetail
=
(
id
:
number
)
=>
{
push
({
name
:
'CrmBusinessDetail'
,
params
:
{
id
}
})
}
/** 打开客户详情 */
const
openCustomerDetail
=
(
id
:
number
)
=>
{
push
({
name
:
'CrmCustomerDetail'
,
params
:
{
id
}
})
}
/** 获取统计数据 */
const
loadData
=
async
()
=>
{
loading
.
value
=
true
try
{
await
fetchAndFill
()
}
finally
{
loading
.
value
=
false
}
}
defineExpose
({
loadData
})
/** 初始化 */
onMounted
(()
=>
{
loadData
()
})
</
script
>
src/views/crm/statistics/funnel/index.vue
View file @
bc95dc29
...
...
@@ -19,8 +19,24 @@
start-placeholder=
"开始日期"
type=
"daterange"
value-format=
"YYYY-MM-DD HH:mm:ss"
@
change=
"handleQuery"
/>
</el-form-item>
<el-form-item
label=
"时间间隔"
prop=
"interval"
>
<el-select
v-model=
"queryParams.interval"
class=
"!w-240px"
placeholder=
"间隔类型"
@
change=
"handleQuery"
>
<el-option
v-for=
"dict in getIntDictOptions(DICT_TYPE.DATE_INTERVAL)"
:key=
"dict.value"
:label=
"dict.label"
:value=
"dict.value"
/>
</el-select>
</el-form-item>
<el-form-item
label=
"归属部门"
prop=
"deptId"
>
<el-tree-select
v-model=
"queryParams.deptId"
...
...
@@ -30,11 +46,17 @@
class=
"!w-240px"
node-key=
"id"
placeholder=
"请选择归属部门"
@
change=
"
queryParams.userId = undefined
"
@
change=
"
(queryParams.userId = undefined), handleQuery()
"
/>
</el-form-item>
<el-form-item
label=
"员工"
prop=
"userId"
>
<el-select
v-model=
"queryParams.userId"
class=
"!w-240px"
clearable
placeholder=
"员工"
>
<el-select
v-model=
"queryParams.userId"
class=
"!w-240px"
clearable
placeholder=
"员工"
@
change=
"handleQuery"
>
<el-option
v-for=
"(user, index) in userListByDeptId"
:key=
"index"
...
...
@@ -46,7 +68,7 @@
<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"
/>
...
...
@@ -62,7 +84,9 @@
<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=
"businessSummaryRef"
>
<BusinessSummary
ref=
"businessSummaryRef"
:query-params=
"queryParams"
/>
</el-tab-pane>
<el-tab-pane
label=
"商机转化率分析"
lazy
name=
"sourceRef"
/>
</el-tabs>
</el-col>
...
...
@@ -75,10 +99,13 @@ 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'
import
BusinessSummary
from
'./components/BusinessSummary.vue'
import
{
DICT_TYPE
,
getIntDictOptions
}
from
'@/utils/dict'
defineOptions
({
name
:
'CrmStatisticsFunnel'
})
const
queryParams
=
reactive
({
interval
:
2
,
// WEEK, 周
deptId
:
useUserStore
().
getUser
.
deptId
,
userId
:
undefined
,
times
:
[
...
...
@@ -100,8 +127,8 @@ const userListByDeptId = computed(() =>
)
const
activeTab
=
ref
(
'funnelRef'
)
// 活跃标签
const
funnelRef
=
ref
()
//
客户地区分布
const
levelRef
=
ref
()
// 客户级别
const
funnelRef
=
ref
()
//
销售漏斗
const
businessSummaryRef
=
ref
()
// 新增商机分析
const
sourceRef
=
ref
()
// 客户来源
/** 搜索按钮操作 */
...
...
@@ -110,8 +137,8 @@ const handleQuery = () => {
case
'funnelRef'
:
funnelRef
.
value
?.
loadData
?.()
break
case
'
level
Ref'
:
level
Ref
.
value
?.
loadData
?.()
case
'
businessSummary
Ref'
:
businessSummary
Ref
.
value
?.
loadData
?.()
break
case
'sourceRef'
:
sourceRef
.
value
?.
loadData
?.()
...
...
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