Skip to content
项目
群组
代码片段
帮助
当前项目
正在载入...
登录 / 注册
切换导航面板
S
saas-dml
项目
项目
详情
活动
周期分析
仓库
仓库
文件
提交
分支
标签
贡献者
图表
比较
统计图
议题
0
议题
0
列表
看板
标记
里程碑
合并请求
0
合并请求
0
CI / CD
CI / CD
流水线
作业
日程
统计图
Wiki
Wiki
代码片段
代码片段
成员
成员
折叠边栏
关闭边栏
活动
图像
聊天
创建新问题
作业
提交
问题看板
Open sidebar
EzijingWeb
saas-dml
Commits
f07ee38f
提交
f07ee38f
authored
12月 11, 2024
作者:
王鹏飞
浏览文件
操作
浏览文件
下载
电子邮件补丁
差异文件
chore: 更新首页显示内容
上级
2d05d23e
隐藏空白字符变更
内嵌
并排
正在显示
3 个修改的文件
包含
358 行增加
和
130 行删除
+358
-130
ChartCard.vue
src/components/ChartCard.vue
+8
-3
api.ts
src/modules/home/api.ts
+11
-0
Index.vue
src/modules/home/views/Index.vue
+339
-127
没有找到文件。
src/components/ChartCard.vue
浏览文件 @
f07ee38f
...
...
@@ -20,10 +20,15 @@ use([
TitleComponent
,
TooltipComponent
,
LegendComponent
,
GridComponent
GridComponent
,
])
const
props
=
defineProps
<
{
title
?:
string
;
options
?:
any
;
loading
?:
boolean
}
>
()
const
props
=
withDefaults
(
defineProps
<
{
title
?:
string
;
options
?:
any
;
loading
?:
boolean
;
hasFullscreen
?:
boolean
}
>
(),
{
hasFullscreen
:
true
,
}
)
const
el
=
ref
<
HTMLElement
|
null
>
(
null
)
const
{
isFullscreen
,
toggle
}
=
useFullscreen
(
el
)
...
...
@@ -42,7 +47,7 @@ const color = ['#af1c40', '#c17933', '#8f0034', '#d45548', '#ab3259', '#dec34c',
</slot>
<div
class=
"tools"
>
<slot
name=
"tools"
></slot>
<el-tooltip
effect=
"dark"
:content=
"isFullscreen ? '退出全屏' : '全屏'"
>
<el-tooltip
effect=
"dark"
:content=
"isFullscreen ? '退出全屏' : '全屏'"
v-if=
"hasFullscreen"
>
<el-icon
class=
"icon-fullscreen"
@
click=
"toggle"
><FullScreen
/></el-icon>
</el-tooltip>
</div>
...
...
src/modules/home/api.ts
浏览文件 @
f07ee38f
...
...
@@ -14,3 +14,13 @@ export function getMembersList() {
export
function
getEventList
(
params
:
{
member_id
:
string
;
page
?:
number
;
'per-page'
?:
number
})
{
return
httpRequest
.
get
(
'/api/lab/v1/experiment/index/events'
,
{
params
})
}
// 获取用户来源
export
function
getMemberConnections
(
params
:
{
sso_id
:
string
})
{
return
httpRequest
.
get
(
'/api/lab/v1/experiment/analyse/connections'
,
{
params
})
}
// 获取热门标签
export
function
getHotTags
(
params
:
{
sso_id
:
string
;
number
?:
number
})
{
return
httpRequest
.
get
(
'/api/lab/v1/experiment/analyse/hot-tags'
,
{
params
})
}
\ No newline at end of file
src/modules/home/views/Index.vue
浏览文件 @
f07ee38f
<
script
setup
lang=
"ts"
>
import
{
getExperimentData
,
getMembersList
,
getEventList
}
from
'../api'
<
script
setup
>
import
{
getExperimentData
,
getMembersList
,
getEventList
,
getMemberConnections
,
getHotTags
}
from
'../api'
import
Icon
from
'@/components/ConnectionIcon.vue'
// import { useMapStore } from '@/stores/map'
import
ChartCard
from
'@/components/ChartCard.vue'
import
{
useMapStore
}
from
'@/stores/map'
import
{
useUserStore
}
from
'@/stores/user'
import
{
getNameByValue
}
from
'@/utils/dictionary'
const
ViewEvent
=
defineAsyncComponent
(()
=>
import
(
'@/components/ViewEvent.vue'
))
const
connectionTypeList
=
useMapStore
().
getMapValuesByKey
(
'experiment_connection_type'
)
const
materialTypeList
=
useMapStore
().
getMapValuesByKey
(
'experiment_marketing_material_type'
)
const
userStore
=
useUserStore
()
// 左边展示数据
let
leftData
=
$ref
<
{
members
:
string
tags
:
string
groups
:
string
files
:
string
itineraries
:
string
connections
:
string
}
>
()
getExperimentData
().
then
(
res
=>
{
let
leftData
=
$ref
()
getExperimentData
().
then
((
res
)
=>
{
leftData
=
res
.
data
})
// 最近活跃客户
let
userList
=
$ref
<
{
name
:
string
;
id
:
string
;
isActive
:
boolean
;
gender
:
string
}[]
>
([])
getMembersList
().
then
(
res
=>
{
userList
=
res
.
data
.
map
((
element
:
any
,
index
:
number
)
=>
{
let
userList
=
$ref
([])
getMembersList
().
then
(
(
res
)
=>
{
userList
=
res
.
data
.
map
((
element
,
index
)
=>
{
element
.
isActive
=
index
===
0
return
element
})
})
const
activeUser
=
computed
(()
=>
{
return
userList
.
find
(
item
=>
item
.
isActive
)
return
userList
.
find
(
(
item
)
=>
item
.
isActive
)
})
// 最近活跃客户的事件
let
eventData
=
$ref
<
{
list
:
Array
<
any
>
;
total
:
number
}
>
({
list
:
[],
total
:
0
})
let
eventData
=
$ref
({
list
:
[],
total
:
0
})
const
eventCurrentPage
=
ref
(
1
)
const
getEvent
=
function
(
id
?:
string
)
{
const
getEvent
=
function
(
id
)
{
id
=
id
||
activeUser
.
value
?.
id
if
(
id
)
{
getEventList
({
member_id
:
id
,
page
:
eventCurrentPage
.
value
,
'per-page'
:
10
}).
then
(
res
=>
{
getEventList
({
member_id
:
id
,
page
:
eventCurrentPage
.
value
,
'per-page'
:
4
}).
then
((
res
)
=>
{
eventData
=
res
.
data
})
}
...
...
@@ -45,14 +45,14 @@ watchEffect(() => {
})
// 切换客户事件
const
handleUser
=
(
item
:
any
)
=>
{
userList
?.
map
(
item
=>
(
item
.
isActive
=
false
&&
item
))
const
handleUser
=
(
item
)
=>
{
userList
?.
map
(
(
item
)
=>
(
item
.
isActive
=
false
&&
item
))
item
.
isActive
=
true
eventCurrentPage
.
value
=
1
}
// 获取上下午
const
getDate
=
function
(
date
:
string
)
{
const
getDate
=
function
(
date
)
{
return
parseInt
(
date
.
slice
(
date
.
indexOf
(
' '
),
date
.
indexOf
(
' '
)
+
3
))
>
12
?
'下午'
:
'上午'
}
...
...
@@ -61,140 +61,353 @@ const getDate = function (date: string) {
// return experimentTypeList.find((item: any) => item.id === id)?.value as string
// }
const
currentUser
=
computed
(()
=>
{
return
userList
?.
find
(
item
=>
item
.
isActive
)
return
userList
?.
find
(
(
item
)
=>
item
.
isActive
)
})
const
viewEventVisible
=
ref
(
false
)
const
currentViewEvent
=
ref
()
function
handleViewEvent
(
item
:
any
)
{
function
handleViewEvent
(
item
)
{
viewEventVisible
.
value
=
true
currentViewEvent
.
value
=
item
}
// 用户数据来源分布
const
connection
=
ref
([])
async
function
fetchConnections
()
{
const
res
=
await
getMemberConnections
({
sso_id
:
userStore
.
user
?.
id
})
connection
.
value
=
res
.
data
.
items
.
map
((
item
)
=>
{
return
{
...
item
,
group_name
:
getNameByValue
(
item
.
group_name
,
connectionTypeList
)
}
})
}
const
connectionOption
=
computed
(()
=>
{
if
(
!
connection
.
value
.
length
)
return
return
{
grid
:
{
left
:
'5%'
,
top
:
'10%'
,
right
:
'5%'
,
bottom
:
'5%'
,
containLabel
:
true
},
tooltip
:
{
trigger
:
'axis'
,
},
xAxis
:
{
type
:
'category'
,
axisLabel
:
{
interval
:
0
},
data
:
connection
.
value
.
map
((
item
)
=>
item
.
group_name
),
},
yAxis
:
{
type
:
'value'
,
},
series
:
[
{
name
:
'数据'
,
type
:
'bar'
,
label
:
{
show
:
true
,
position
:
'top'
},
data
:
connection
.
value
.
map
((
item
)
=>
item
.
total
),
},
],
}
})
// 热门标签
const
labelHot
=
ref
([])
async
function
fetchLabelHot
()
{
const
res
=
await
getHotTags
({
sso_id
:
userStore
.
user
?.
id
})
labelHot
.
value
=
res
.
data
.
items
}
const
labelHotOption
=
computed
(()
=>
{
if
(
!
labelHot
.
value
.
length
)
return
return
{
grid
:
{
left
:
'10%'
,
top
:
'10%'
,
right
:
'10%'
,
bottom
:
'10%'
,
containLabel
:
true
},
tooltip
:
{},
series
:
[
{
type
:
'wordCloud'
,
gridSize
:
15
,
sizeRange
:
[
12
,
50
],
rotationRange
:
[
0
,
0
],
shape
:
'circle'
,
width
:
'100%'
,
height
:
'100%'
,
drawOutOfBound
:
false
,
textStyle
:
{
color
:
function
()
{
return
(
'rgb('
+
[
Math
.
round
(
Math
.
random
()
*
160
),
Math
.
round
(
Math
.
random
()
*
160
),
Math
.
round
(
Math
.
random
()
*
160
)].
join
(
','
)
+
')'
)
},
},
emphasis
:
{
focus
:
'self'
,
textStyle
:
{
textShadowBlur
:
10
,
textShadowColor
:
'#333'
,
},
},
data
:
labelHot
.
value
.
map
((
item
)
=>
{
return
{
name
:
item
.
group_name
,
value
:
item
.
total
}
}),
},
],
}
})
// 营销物料分布
const
materialOptions
=
computed
(()
=>
{
if
(
!
leftData
?.
materials
.
length
)
return
return
{
grid
:
{
left
:
'5%'
,
top
:
'10%'
,
right
:
'5%'
,
bottom
:
'5%'
,
containLabel
:
true
},
tooltip
:
{
trigger
:
'item'
,
formatter
:
'{b}: {c}<br />{d}%'
,
},
series
:
[
{
type
:
'pie'
,
label
:
{
formatter
:
'{b}
\
n{d}%'
},
itemStyle
:
{
borderRadius
:
6
},
radius
:
[
0
,
'70%'
],
data
:
leftData
.
materials
.
map
((
item
)
=>
{
return
{
name
:
getNameByValue
(
item
.
type
,
materialTypeList
),
value
:
item
.
num
}
}),
},
],
}
})
// 直播场次分布
const
liveNumberOptions
=
computed
(()
=>
{
if
(
!
leftData
?.
live_practice_records
.
length
)
return
return
{
grid
:
{
left
:
'5%'
,
top
:
'10%'
,
right
:
'5%'
,
bottom
:
'5%'
,
containLabel
:
true
},
tooltip
:
{
trigger
:
'item'
,
formatter
:
'{b}: {c}<br />{d}%'
,
},
series
:
[
{
type
:
'pie'
,
label
:
{
formatter
:
'{b}
\
n{d}%'
},
itemStyle
:
{
borderRadius
:
6
},
radius
:
[
0
,
'70%'
],
data
:
leftData
.
live_practice_records
.
map
((
item
)
=>
{
return
{
name
:
item
.
live_commodity
,
value
:
item
.
num
}
}),
},
],
}
})
onMounted
(()
=>
{
fetchConnections
()
fetchLabelHot
()
})
</
script
>
<
template
>
<div
class=
"home"
>
<div
class=
"home-left_content"
>
<div
class=
"content-card"
>
<AppCard
class=
"card"
title=
"我的客户:"
>
<p>
{{
leftData
?.
members
}}
</p>
</AppCard>
<AppCard
class=
"card"
title=
"我的标签:"
>
<p>
{{
leftData
?.
tags
}}
</p>
</AppCard>
<AppCard
class=
"card"
title=
"我的群组:"
>
<p>
{{
leftData
?.
groups
}}
</p>
</AppCard>
</div>
<div
class=
"content-card"
>
<AppCard
class=
"card"
title=
"我的营销资料:"
>
<p>
{{
leftData
?.
files
}}
</p>
</AppCard>
<AppCard
class=
"card"
title=
"我的旅程:"
>
<p>
{{
leftData
?.
itineraries
}}
</p>
</AppCard>
<AppCard
class=
"card"
title=
"我的链接:"
>
<p>
{{
leftData
?.
connections
}}
</p>
</AppCard>
</div>
<AppCard
title=
"当前用户旅程模板数量:"
><el-empty
:image-size=
"120"
/></AppCard>
<AppCard
title=
"旅程转化目标分析:"
><el-empty
:image-size=
"120"
/></AppCard>
</div>
<div
class=
"home-right_content"
>
<AppCard
class=
"card"
title=
"最近活跃用户跟踪"
>
<div
class=
"content-user"
>
<div
:class=
"item.isActive ? 'content-user_item active' : 'content-user_item'"
v-for=
"item in userList"
:key=
"item.id"
@
click=
"handleUser(item)"
>
<img
:src=
"item.gender === '1' ? 'https://webapp-pub.ezijing.com/pages/assa/dml_boy.png' : 'https://webapp-pub.ezijing.com/pages/assa/dml_girl.png'"
/>
<div
class=
"name"
>
{{
item
.
name
}}
</div>
</div>
<div
class=
"row"
>
<div
class=
"col"
>
<div
class=
"row"
>
<AppCard
class=
"card"
title=
"链接渠道"
>
<p>
<router-link
to=
"/connect"
>
{{
leftData
?.
connections
}}
</router-link>
</p>
</AppCard>
<AppCard
class=
"card"
title=
"用户属性"
>
<p>
<router-link
to=
"/metadata/user"
>
{{
leftData
?.
user_attributes
}}
</router-link>
</p>
</AppCard>
<AppCard
class=
"card"
title=
"用户行为事件"
>
<p>
<router-link
to=
"/metadata/event"
>
{{
leftData
?.
events
}}
</router-link>
</p>
</AppCard>
<AppCard
class=
"card"
title=
"标签"
>
<p>
<router-link
to=
"/label"
>
{{
leftData
?.
tags
}}
</router-link>
</p>
</AppCard>
<AppCard
class=
"card"
title=
"营销策划报告"
>
<p>
<router-link
to=
"/market/my"
>
{{
leftData
?.
marketing_planning_records
}}
</router-link>
</p>
</AppCard>
</div>
<div
class=
"row"
>
<AppCard
class=
"card"
title=
"用户旅程"
>
<p>
<router-link
to=
"/trip"
>
{{
leftData
?.
itineraries
}}
</router-link>
</p>
</AppCard>
<AppCard
class=
"card"
title=
"用户数"
>
<p>
<router-link
to=
"/user"
>
{{
leftData
?.
members
}}
</router-link>
</p>
</AppCard>
<AppCard
class=
"card"
title=
"用户行为事件数"
>
<p>
{{
leftData
?.
user_events
}}
</p>
</AppCard>
<AppCard
class=
"card"
title=
"用户群组"
>
<p>
<router-link
to=
"/group"
>
{{
leftData
?.
groups
}}
</router-link>
</p>
</AppCard>
<AppCard
class=
"card"
title=
"直播商品"
>
<p>
<router-link
to=
"/live/product/management"
>
{{
leftData
?.
live_commodities
}}
</router-link>
</p>
</AppCard>
</div>
<div
class=
"row"
>
<AppCard
class=
"card"
title=
"文本营销物料"
>
<p>
<router-link
to=
"/material?type=1"
>
{{
leftData
?.
text_materials
}}
</router-link>
</p>
</AppCard>
<AppCard
class=
"card"
title=
"图片营销物料"
>
<p>
<router-link
to=
"/material?type=2"
>
{{
leftData
?.
pic_materials
}}
</router-link>
</p>
</AppCard>
<AppCard
class=
"card"
title=
"视频营销物料"
>
<p>
<router-link
to=
"/material?type=4"
>
{{
leftData
?.
video_materials
}}
</router-link>
</p>
</AppCard>
<AppCard
class=
"card"
title=
"直播话术"
>
<p>
<router-link
to=
"/live/talk"
>
{{
leftData
?.
live_speeches
}}
</router-link>
</p>
</AppCard>
<AppCard
class=
"card"
title=
"直播"
>
<p>
<router-link
to=
"/live"
>
{{
leftData
?.
live_practice
}}
</router-link>
</p>
</AppCard>
</div>
<template
v-if=
"eventData.total"
>
<div
class=
"event-box"
v-for=
"item in eventData.list"
:key=
"item.id"
>
<div
class=
"date"
>
{{
item
.
updated_time
?.
slice
(
0
,
item
.
updated_time
.
indexOf
(
' '
))
}}
</div>
<div
class=
"event-content"
>
<div
class=
"time"
>
{{
item
.
updated_time
?.
slice
(
item
.
updated_time
.
indexOf
(
' '
),
item
.
updated_time
.
length
-
3
)
}}
{{
getDate
(
item
.
updated_time
)
}}
</div>
<div
style=
"width: 340px"
>
<AppCard
class=
"card"
title=
"最近活跃用户跟踪"
>
<div
class=
"content-user"
>
<template
v-for=
"(item, index) in userList"
:key=
"item.id"
>
<div
:class=
"
{ 'content-user_item': true, active: item.isActive }"
@click="handleUser(item)"
v-if="index
<
8
"
>
<img
:src=
"
item.gender === '1'
? 'https://webapp-pub.ezijing.com/pages/assa/dml_boy.png'
: 'https://webapp-pub.ezijing.com/pages/assa/dml_girl.png'
"
/>
<div
class=
"name"
>
{{
item
.
name
}}
</div>
</div>
<!--
<Icon
:name=
"item.connection_type"
w=
"30"
h=
"30"
></Icon>
-->
<div
class=
"event"
>
<Icon
class=
"icon"
:name=
"item.connection_type"
:multiColor=
"true"
w=
"24"
h=
"24"
></Icon>
在
<span>
"
{{
item
.
connection_name
}}
"
</span>
上
<span
style=
"cursor: pointer"
@
click=
"handleViewEvent(item)"
>
"
{{
item
.
event_name
}}
"
</span>
</
template
>
</div>
<
template
v-if=
"eventData.total"
>
<div
class=
"event-box"
v-for=
"item in eventData.list"
:key=
"item.id"
>
<div
class=
"date"
>
{{
item
.
updated_time
?.
slice
(
0
,
item
.
updated_time
.
indexOf
(
' '
))
}}
</div>
<div
class=
"event-content"
>
<div
class=
"time"
>
{{
item
.
updated_time
?.
slice
(
item
.
updated_time
.
indexOf
(
' '
),
item
.
updated_time
.
length
-
3
)
}}
{{
getDate
(
item
.
updated_time
)
}}
</div>
<!--
<Icon
:name=
"item.connection_type"
w=
"30"
h=
"30"
></Icon>
-->
<div
class=
"event"
>
<Icon
class=
"icon"
:name=
"item.connection_type"
:multiColor=
"true"
w=
"24"
h=
"24"
></Icon>
在
<span>
"
{{
item
.
connection_name
}}
"
</span>
上
<span
style=
"cursor: pointer"
@
click=
"handleViewEvent(item)"
>
"
{{
item
.
event_name
}}
"
</span>
</div>
</div>
</div>
</div>
<div
style=
"display: flex; align-items: center; justify-content: center; margin-top: 20px"
>
<el-pagination
layout=
"prev, pager, next"
v-model:current-page=
"eventCurrentPage"
:total=
"eventData.total"
hide-on-single-page
/>
</div>
</
template
>
<el-empty
description=
"暂无数据"
:image-size=
"80"
v-else
/>
</AppCard>
<div
style=
"display: flex; align-items: center; justify-content: center; margin-top: 20px"
>
<el-pagination
layout=
"prev, pager, next"
v-model:current-page=
"eventCurrentPage"
:total=
"eventData.total"
hide-on-single-page
/>
</div>
</
template
>
<el-empty
description=
"暂无数据"
:image-size=
"80"
v-else
/>
</AppCard>
</div>
</div>
<div
class=
"row"
>
<ChartCard
title=
"用户来源渠道分布"
:options=
"connectionOption"
:hasFullscreen=
"false"
></ChartCard>
<ChartCard
title=
"用户标签分布"
:options=
"labelHotOption"
:hasFullscreen=
"false"
></ChartCard>
<ChartCard
title=
"营销物料分布"
:options=
"materialOptions"
:hasFullscreen=
"false"
></ChartCard>
<ChartCard
title=
"直播场次分布"
:options=
"liveNumberOptions"
:hasFullscreen=
"false"
></ChartCard>
</div>
</div>
<!-- 事件详情 -->
<ViewEvent
v-model=
"viewEventVisible"
:event=
"currentViewEvent"
:user=
"currentUser"
v-if=
"viewEventVisible && currentViewEvent"
></ViewEvent>
<ViewEvent
v-model=
"viewEventVisible"
:event=
"currentViewEvent"
:user=
"currentUser"
v-if=
"viewEventVisible && currentViewEvent"
></ViewEvent>
</template>
<
style
lang=
"scss"
scoped
>
.home
{
display
:
flex
;
.home-left_content
{
flex
:
1
;
.content-card
{
.row
{
display
:
flex
;
gap
:
10px
;
+
.row
{
margin-top
:
10px
;
}
.col
{
flex
:
1
;
display
:
flex
;
margin-bottom
:
10px
;
gap
:
10px
;
.card
{
flex-direction
:
column
;
.row
{
flex
:
1
;
margin
:
0
;
p
{
line-height
:
100px
;
text-align
:
center
;
font-size
:
28px
;
color
:
#aa1941
;
}
}
}
}
.home-right_content
{
width
:
40%
;
margin-left
:
10px
;
.card
{
height
:
100%
;
.content-user
{
background
:
#efefef
;
padding
:
20px
;
border-radius
:
5px
;
display
:
flex
;
flex-wrap
:
wrap
;
.content-user_item
{
width
:
60px
;
// background-color: #fff;
padding
:
10px
;
border-radius
:
10px
;
margin-right
:
12px
;
cursor
:
pointer
;
&
.active
{
background-color
:
#fff
;
}
}
img
{
width
:
50px
;
height
:
50px
;
margin
:
0
auto
;
display
:
block
;
}
.name
{
text-align
:
center
;
font-size
:
14px
;
margin-top
:
10px
;
}
flex
:
1
;
margin
:
0
;
min-height
:
100%
;
p
{
line-height
:
100px
;
text-align
:
center
;
font-size
:
28px
;
color
:
#aa1941
;
}
}
}
::v-deep
(
.chart-card
)
{
background-color
:
#fff
;
}
}
.content-user
{
background
:
#efefef
;
padding
:
10px
;
border-radius
:
5px
;
display
:
flex
;
flex-wrap
:
wrap
;
.content-user_item
{
width
:
50px
;
padding
:
10px
;
border-radius
:
10px
;
cursor
:
pointer
;
&
.active
{
background-color
:
#fff
;
}
}
img
{
width
:
40px
;
height
:
40px
;
margin
:
0
auto
;
display
:
block
;
}
.name
{
text-align
:
center
;
font-size
:
14px
;
margin-top
:
10px
;
}
}
.event-box
{
border-bottom
:
1px
solid
#ccc
;
...
...
@@ -207,7 +420,6 @@ function handleViewEvent(item: any) {
display
:
flex
;
font-size
:
12px
;
align-items
:
center
;
// margin-bottom: 20px;
flex-wrap
:
wrap
;
.event
{
display
:
flex
;
...
...
编写
预览
Markdown
格式
0%
重试
或
添加新文件
添加附件
取消
您添加了
0
人
到此讨论。请谨慎行事。
请先完成此评论的编辑!
取消
请
注册
或者
登录
后发表评论