@ -44,20 +44,118 @@
< / el-row >
< el-row :gutter = "16" class = "mb-4" >
< el-col :md = "18" :sm = "24" >
<!-- 会员概览 -- >
< M emberFunnelCard / >
< el-card shadow = "never" >
< t emplate # header >
< div class = "flex flex-row items-center justify-between" >
< span > 会员概览 < / span >
<!-- 查询条件 -- >
< div class = "my--2 flex flex-row items-center gap-2" >
< el-radio-group v-model = "shortcutDays" @change="handleDateTypeChange" >
< el -radio -button :label = "1" > 昨天 < / el-radio-button >
< el-radio-button :label = "7" > 最近7天 < / el-radio-button >
< el-radio-button :label = "30" > 最近30天 < / el-radio-button >
< / el-radio-group >
< el-date-picker
v-model = "queryParams.times"
value -format = " YYYY -MM -DD HH : mm : ss "
type = "daterange"
start -placeholder = " 开始日期 "
end -placeholder = " 结束日期 "
: default -time = " [ new Date ( ' 1 00 : 00 : 00 ' ) , new Date ( ' 1 23 : 59 : 59 ' ) ] "
:shortcuts = "shortcuts"
class = "!w-240px"
@change ="getMemberAnalyse"
/ >
< / div >
< / div >
< / template >
< div class = "min-w-225 py-1.75" v-loading = "analyseLoading" >
< div class = "relative h-24 flex" >
< div class = "h-full w-75% bg-blue-50 <lg:w-35% <xl:w-55%" >
< div class = "ml-15 h-full flex flex-col justify-center" >
< div class = "font-bold" >
注册用户数量 : { { analyseData ? . comparison ? . value ? . userCount || 0 } }
< / div >
< div class = "mt-2 text-3.5" >
环比增长率 : { {
calculateRelativeRate (
analyseData ? . comparison ? . value ? . userCount ,
analyseData ? . comparison ? . reference ? . userCount
)
} } %
< / div >
< / div >
< / div >
< div
class = "trapezoid1 ml--38.5 mt-1.5 h-full w-77 flex flex-col items-center justify-center bg-blue-5 text-3.5 text-white"
>
< span class = "text-6 font-bold" > { { analyseData ? . visitorCount || 0 } } < / span >
< span > 访客 < / span >
< / div >
< / div >
< div class = "relative h-24 flex" >
< div class = "h-full w-75% flex bg-cyan-50 <lg:w-35% <xl:w-55%" >
< div class = "ml-15 h-full flex flex-col justify-center" >
< div class = "font-bold" >
活跃用户数量 : { { analyseData ? . comparison ? . value ? . activeUserCount || 0 } }
< / div >
< div class = "mt-2 text-3.5" >
环比增长率 : { {
calculateRelativeRate (
analyseData ? . comparison ? . value ? . activeUserCount ,
analyseData ? . comparison ? . reference ? . activeUserCount
)
} } %
< / div >
< / div >
< / div >
< div
class = "trapezoid2 ml--28 mt-1.7 h-25 w-56 flex flex-col items-center justify-center bg-cyan-5 text-3.5 text-white"
>
< span class = "text-6 font-bold" > { { analyseData ? . orderUserCount || 0 } } < / span >
< span > 下单 < / span >
< / div >
< / div >
< div class = "relative h-24 flex" >
< div class = "w-75% flex bg-slate-50 <lg:w-35% <xl:w-55%" >
< div class = "ml-15 h-full flex flex-row gap-x-16" >
< div class = "flex flex-col justify-center" >
< div class = "font-bold" >
充值用户数量 : { { analyseData ? . comparison ? . value ? . rechargeUserCount || 0 } }
< / div >
< div class = "mt-2 text-3.5" >
环比增长率 : { {
calculateRelativeRate (
analyseData ? . comparison ? . value ? . rechargeUserCount ,
analyseData ? . comparison ? . reference ? . rechargeUserCount
)
} } %
< / div >
< / div >
< div class = "flex flex-col justify-center" >
< div class = "font-bold" > 客单价 : { { fenToYuan ( analyseData ? . atv || 0 ) } } < / div >
< / div >
< / div >
< / div >
< div
class = "trapezoid3 ml--18 mt-3.25 h-23 w-36 flex flex-col items-center justify-center bg-slate-5 text-3.5 text-white"
>
< span class = "text-6 font-bold" > { { analyseData ? . payUserCount || 0 } } < / span >
< span > 成交用户 < / span >
< / div >
< / div >
< / div >
< / el-card >
< / el-col >
< el-col :md = "6" :sm = "24" >
<!-- 会员终端 -- >
< MemberT erminalCard / >
< el-card shadow = "never" header = " 会员终端" v-loading = "loading" >
< Echart :height = "300" :options = "t erminalCh artOptions" / >
< / el-card >
< / el-col >
< / el-row >
< el-row :gutter = "16" >
< el-col :md = "18" :sm = "24" >
< el-card shadow = "never" >
< template # header >
< CardTitle title = "会员地域分布" / >
< / template >
< el-card shadow = "never" header = "会员地域分布" >
< el-row v-loading = "loading" >
< el -col :span = "10" >
< Echart :height = "300" :options = "areaChartOptions" / >
@ -82,14 +180,14 @@
/>
<el-table-column
label=" 订单创建数量 "
prop=" orderCreateUser Count "
prop=" orderCreateCount "
align=" center "
min-width=" 135 "
sortable
/>
<el-table-column
label=" 订单支付数量 "
prop=" orderPayUser Count "
prop=" orderPayCount "
align=" center "
min-width=" 135 "
sortable
@ -108,10 +206,7 @@
</el-card>
</el-col>
<el-col :md=" 6 " :sm=" 24 ">
<el-card shadow=" never " v-loading=" loading ">
<template #header>
<CardTitle title=" 会员性别比例 " />
</template>
<el-card shadow=" never " header=" 会员性别比例 " v-loading=" loading ">
<Echart :height=" 300 " :options=" sexChartOptions " />
</el-card>
</el-col>
@ -119,33 +214,62 @@
</div>
</template>
<script lang=" ts " setup>
import * as MemberStatistics Api from '@/api/mall/statistics/member'
import * as Trade MemberApi from '@/api/mall/statistics/member'
import TradeTrendValue from '../trade/components/TradeTrendValue.vue'
import { EChartsOption } from 'echarts'
import china from '@/assets/map/json/china.json'
import dayjs from 'dayjs'
import { fenToYuan } from '@/utils'
import * as DateUtil from '@/utils/formatTime'
import {
MemberAnalyseRespVO,
MemberAreaStatisticsRespVO,
MemberSexStatisticsRespVO,
MemberAnalyseReqVO,
MemberSummaryRespVO,
MemberTerminalStatisticsRespVO
} from '@/api/mall/statistics/member'
import { DICT_TYPE, DictDataType, getIntDictOptions } from '@/utils/dict'
import echarts from '@/plugins/echarts'
import { fenToYuanFormat } from '@/utils/formatter'
import MemberFunnelCard from './components/MemberFunnelCard.vue'
import MemberTerminalCard from './components/MemberTerminalCard.vue'
import { CardTitle } from '@/components/Card'
/** 会员统计 */
defineOptions({ name: 'MemberStatistics' })
const loading = ref(true) // 加载中
const analyseLoading = ref(true) // 会员概览加载中
const queryParams = reactive<MemberAnalyseReqVO>({ times: ['', ''] }) // 会员概览查询参数
const shortcutDays = ref(7) // 日期快捷天数(单选按钮组), 默认7天
const summary = ref<MemberSummaryRespVO>() // 会员统计数据
const analyseData = ref<MemberAnalyseRespVO>() // 会员分析数据
const areaStatisticsList = shallowRef<MemberAreaStatisticsRespVO[]>() // 省份会员统计
// 注册地图
echarts?.registerMap('china', china as any )
echarts?.registerMap('china', china! )
/** 日期快捷选择 */
const shortcuts = [
{
text: '昨天',
value: () => DateUtil.getDayRange(new Date(), -1)
},
{
text: '最近7天',
value: () => DateUtil.getLast7Days()
},
{
text: '本月',
value: () => [dayjs().startOf('M'), dayjs().subtract(1, 'd')]
},
{
text: '最近30天',
value: () => DateUtil.getLast30Days()
},
{
text: '最近1年',
value: () => DateUtil.getLast1Year()
}
]
/** 会员终端统计图配置 */
const terminalChartOptions = reactive<EChartsOption>({
@ -207,8 +331,8 @@ const areaChartOptions = reactive<EChartsOption>({
formatter: (params: any) => {
return `${params?.data?.areaName || params?.name}<br/>
会员数量:${params?.data?.userCount || 0}<br/>
订单创建数量:${params?.data?.orderCreateUser Count || 0}<br/>
订单支付数量:${params?.data?.orderPayUser Count || 0}<br/>
订单创建数量:${params?.data?.orderCreateCount || 0}<br/>
订单支付数量:${params?.data?.orderPayCount || 0}<br/>
订单支付金额:${fenToYuan(params?.data?.orderPayPrice || 0)}`
}
},
@ -233,14 +357,37 @@ const areaChartOptions = reactive<EChartsOption>({
]
}) as EChartsOption
/** 计算环比 */
const calculateRelativeRate = (value?: number, reference?: number) => {
// 防止除0
if (!reference) return 0
return ((100 * ((value || 0) - reference)) / reference).toFixed(0)
}
/** 设置时间范围 */
function setTimes() {
const beginDate = dayjs().subtract(shortcutDays.value, 'd')
const yesterday = dayjs().subtract(1, 'd')
queryParams.times = DateUtil.getDateRange(beginDate, yesterday)
}
/** 处理会员概览查询(日期单选按钮组选择后) */
const handleDateTypeChange = async () => {
// 设置时间范围
setTimes()
// 查询数据
await getMemberAnalyse()
}
/** 查询会员统计 */
const getMemberSummary = async () => {
summary.value = await MemberStatistics Api.getMemberSummary()
summary.value = await Trade MemberApi.getMemberSummary()
}
/** 按照省份,查询会员统计列表 */
const getMemberAreaStatisticsList = async () => {
const list = await MemberStatistics Api.getMemberAreaStatisticsList()
const list = await Trade MemberApi.getMemberAreaStatisticsList()
areaStatisticsList.value = list.map((item: MemberAreaStatisticsRespVO) => {
return {
...item,
@ -254,21 +401,20 @@ const getMemberAreaStatisticsList = async () => {
})
let min = 0
let max = 0
areaChartOptions.series! [0].data = areaStatisticsList.value.map((item) => {
min = Math.min(min, item.orderPayUser Count || 0 )
max = Math.max(max, item.orderPayUser Count || 0 )
return { ...item, name: item.areaName, value: item.orderPayUser Count || 0 }
areaChartOptions.series[0].data = areaStatisticsList.value.map((item) => {
min = Math.min(min, item.orderPayCount)
max = Math.max(max, item.orderPayCount)
return { ...item, name: item.areaName, value: item.orderPayCount || 0 }
})
areaChartOptions.visualMap![' min'] = min
areaChartOptions.visualMap![' max'] = max
areaChartOptions.visualMap. min = min
areaChartOptions.visualMap. max = max
}
/** 按照性别,查询会员统计列表 */
const getMemberSexStatisticsList = async () => {
const list = await MemberStatistics Api.getMemberSexStatisticsList()
const list = await Trade MemberApi.getMemberSexStatisticsList()
const dictDataList = getIntDictOptions(DICT_TYPE.SYSTEM_USER_SEX)
dictDataList.push({ label: '未知', value: null } as any)
sexChartOptions.series![0].data = dictDataList.map((dictData: DictDataType) => {
sexChartOptions.series[0].data = dictDataList.map((dictData: DictDataType) => {
const userCount = list.find((item: MemberSexStatisticsRespVO) => item.sex === dictData.value)
?.userCount
return {
@ -280,9 +426,8 @@ const getMemberSexStatisticsList = async () => {
/** 按照终端,查询会员统计列表 */
const getMemberTerminalStatisticsList = async () => {
const list = await MemberStatistics Api.getMemberTerminalStatisticsList()
const list = await Trade MemberApi.getMemberTerminalStatisticsList()
const dictDataList = getIntDictOptions(DICT_TYPE.TERMINAL)
dictDataList.push({ label: '未知', value: null } as any)
terminalChartOptions.series![0].data = dictDataList.map((dictData: DictDataType) => {
const userCount = list.find(
(item: MemberTerminalStatisticsRespVO) => item.terminal === dictData.value
@ -294,6 +439,20 @@ const getMemberTerminalStatisticsList = async () => {
})
}
/** 查询会员概览数据列表 */
const getMemberAnalyse = async () => {
analyseLoading.value = true
const times = queryParams.times
// 开始与截止在同一天的, 环比出不来, 需要延长一天
if (DateUtil.isSameDay(times[0], times[1])) {
// 前天
times[0] = DateUtil.formatDate(dayjs(times[0]).subtract(1, 'd'))
}
// 查询数据
analyseData.value = await TradeMemberApi.getMemberAnalyse({ times })
analyseLoading.value = false
}
/** 初始化 **/
onMounted(async () => {
loading.value = true
@ -301,7 +460,8 @@ onMounted(async () => {
getMemberSummary(),
getMemberTerminalStatisticsList(),
getMemberAreaStatisticsList(),
getMemberSexStatisticsList()
getMemberSexStatisticsList(),
handleDateTypeChange()
])
loading.value = false
})
@ -312,4 +472,16 @@ onMounted(async () => {
margin - bottom : 1 rem ;
}
}
. trapezoid1 {
transform : perspective ( 5 em ) rotateX ( - 11 deg ) ;
}
. trapezoid2 {
transform : perspective ( 7 em ) rotateX ( - 20 deg ) ;
}
. trapezoid3 {
transform : perspective ( 3 em ) rotateX ( - 13 deg ) ;
}
< / style >