提交 f07ee38f authored 作者: 王鹏飞's avatar 王鹏飞

chore: 更新首页显示内容

上级 2d05d23e
......@@ -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>
......
......@@ -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
<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 到此讨论。请谨慎行事。
请先完成此评论的编辑!
注册 或者 后发表评论