From a1d7e81859f554f3a53680cc35f0f49bf1f77098 Mon Sep 17 00:00:00 2001
From: wwf <1971391498@qq.com>
Date: 星期四, 14 五月 2026 14:37:02 +0800
Subject: [PATCH] 导入项目
---
src/views/mall/statistics/member/index.vue | 313 ++++++++++++++++++++++++++++++++++++++++++++++++++++
1 files changed, 313 insertions(+), 0 deletions(-)
diff --git a/src/views/mall/statistics/member/index.vue b/src/views/mall/statistics/member/index.vue
new file mode 100644
index 0000000..0e1bbaf
--- /dev/null
+++ b/src/views/mall/statistics/member/index.vue
@@ -0,0 +1,313 @@
+<template>
+ <doc-alert title="銆愮粺璁°�戜細鍛樸�佸晢鍝併�佷氦鏄撶粺璁�" url="https://doc.iocoder.cn/mall/statistics/" />
+
+ <div class="flex flex-col">
+ <el-row :gutter="16" class="summary">
+ <el-col v-loading="loading" :sm="6" :xs="12">
+ <SummaryCard
+ :value="summary?.userCount || 0"
+ icon="fa-solid:users"
+ icon-bg-color="text-blue-500"
+ icon-color="bg-blue-100"
+ title="绱浼氬憳鏁�"
+ />
+ </el-col>
+ <el-col v-loading="loading" :sm="6" :xs="12">
+ <SummaryCard
+ :value="summary?.rechargeUserCount || 0"
+ icon="fa-solid:user"
+ icon-bg-color="text-purple-500"
+ icon-color="bg-purple-100"
+ title="绱鍏呭�间汉鏁�"
+ />
+ </el-col>
+ <el-col v-loading="loading" :sm="6" :xs="12">
+ <SummaryCard
+ :decimals="2"
+ :value="fenToYuan(summary?.rechargePrice || 0)"
+ icon="fa-solid:money-check-alt"
+ icon-bg-color="text-yellow-500"
+ icon-color="bg-yellow-100"
+ prefix="锟�"
+ title="绱鍏呭�奸噾棰�"
+ />
+ </el-col>
+ <el-col v-loading="loading" :sm="6" :xs="12">
+ <SummaryCard
+ :decimals="2"
+ :value="fenToYuan(summary?.expensePrice || 0)"
+ icon="fa-solid:yen-sign"
+ icon-bg-color="text-green-500"
+ icon-color="bg-green-100"
+ prefix="锟�"
+ title="绱娑堣垂閲戦"
+ />
+ </el-col>
+ </el-row>
+ <el-row :gutter="16" class="mb-4">
+ <el-col :md="18" :sm="24">
+ <!-- 浼氬憳姒傝 -->
+ <MemberFunnelCard />
+ </el-col>
+ <el-col :md="6" :sm="24">
+ <!-- 浼氬憳缁堢 -->
+ <MemberTerminalCard />
+ </el-col>
+ </el-row>
+ <el-row :gutter="16">
+ <el-col :md="18" :sm="24">
+ <el-card shadow="never">
+ <template #header>
+ <CardTitle title="浼氬憳鍦板煙鍒嗗竷" />
+ </template>
+ <el-row v-loading="loading">
+ <el-col :span="10">
+ <Echart :height="300" :options="areaChartOptions" />
+ </el-col>
+ <el-col :span="14">
+ <el-table :data="areaStatisticsList" :height="300">
+ <el-table-column
+ :sort-method="(obj1, obj2) => obj1.areaName.localeCompare(obj2.areaName, 'zh-CN')"
+ align="center"
+ label="鐪佷唤"
+ min-width="80"
+ prop="areaName"
+ show-overflow-tooltip
+ sortable
+ />
+ <el-table-column
+ align="center"
+ label="浼氬憳鏁伴噺"
+ min-width="105"
+ prop="userCount"
+ sortable
+ />
+ <el-table-column
+ align="center"
+ label="璁㈠崟鍒涘缓鏁伴噺"
+ min-width="135"
+ prop="orderCreateUserCount"
+ sortable
+ />
+ <el-table-column
+ align="center"
+ label="璁㈠崟鏀粯鏁伴噺"
+ min-width="135"
+ prop="orderPayUserCount"
+ sortable
+ />
+ <el-table-column
+ :formatter="fenToYuanFormat"
+ align="center"
+ label="璁㈠崟鏀粯閲戦"
+ min-width="135"
+ prop="orderPayPrice"
+ sortable
+ />
+ </el-table>
+ </el-col>
+ </el-row>
+ </el-card>
+ </el-col>
+ <el-col :md="6" :sm="24">
+ <el-card v-loading="loading" shadow="never">
+ <template #header>
+ <CardTitle title="浼氬憳鎬у埆姣斾緥" />
+ </template>
+ <Echart :height="300" :options="sexChartOptions" />
+ </el-card>
+ </el-col>
+ </el-row>
+ </div>
+</template>
+<script lang="ts" setup>
+import * as MemberStatisticsApi from '@/api/mall/statistics/member'
+import {
+ MemberAreaStatisticsRespVO,
+ MemberSexStatisticsRespVO,
+ MemberSummaryRespVO,
+ MemberTerminalStatisticsRespVO
+} from '@/api/mall/statistics/member'
+import SummaryCard from '@/components/SummaryCard/index.vue'
+import { EChartsOption } from 'echarts'
+import china from '@/assets/map/json/china.json'
+import { areaReplace, fenToYuan } from '@/utils'
+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 summary = ref<MemberSummaryRespVO>() // 浼氬憳缁熻鏁版嵁
+const areaStatisticsList = shallowRef<MemberAreaStatisticsRespVO[]>() // 鐪佷唤浼氬憳缁熻
+
+// 娉ㄥ唽鍦板浘
+echarts?.registerMap('china', china as any)
+
+/** 浼氬憳缁堢缁熻鍥鹃厤缃� */
+const terminalChartOptions = reactive<EChartsOption>({
+ tooltip: {
+ trigger: 'item',
+ confine: true,
+ formatter: '{a} <br/>{b} : {c} ({d}%)'
+ },
+ legend: {
+ orient: 'vertical',
+ left: 'right'
+ },
+ roseType: 'area',
+ series: [
+ {
+ name: '浼氬憳缁堢',
+ type: 'pie',
+ label: {
+ show: false
+ },
+ labelLine: {
+ show: false
+ },
+ data: []
+ }
+ ]
+}) as EChartsOption
+
+/** 浼氬憳鎬у埆缁熻鍥鹃厤缃� */
+const sexChartOptions = reactive<EChartsOption>({
+ tooltip: {
+ trigger: 'item',
+ confine: true,
+ formatter: '{a} <br/>{b} : {c} ({d}%)'
+ },
+ legend: {
+ orient: 'vertical',
+ left: 'right'
+ },
+ roseType: 'area',
+ series: [
+ {
+ name: '浼氬憳鎬у埆',
+ type: 'pie',
+ label: {
+ show: false
+ },
+ labelLine: {
+ show: false
+ },
+ data: []
+ }
+ ]
+}) as EChartsOption
+
+const areaChartOptions = reactive<EChartsOption>({
+ tooltip: {
+ trigger: 'item',
+ formatter: (params: any) => {
+ return `${params?.data?.areaName || params?.name}<br/>
+浼氬憳鏁伴噺锛�${params?.data?.userCount || 0}<br/>
+璁㈠崟鍒涘缓鏁伴噺锛�${params?.data?.orderCreateUserCount || 0}<br/>
+璁㈠崟鏀粯鏁伴噺锛�${params?.data?.orderPayUserCount || 0}<br/>
+璁㈠崟鏀粯閲戦锛�${fenToYuan(params?.data?.orderPayPrice || 0)}`
+ }
+ },
+ visualMap: {
+ text: ['楂�', '浣�'],
+ realtime: false,
+ calculable: true,
+ top: 'middle',
+ inRange: {
+ color: ['#fff', '#3b82f6']
+ }
+ },
+ series: [
+ {
+ name: '浼氬憳鍦板煙鍒嗗竷',
+ type: 'map',
+ map: 'china',
+ roam: false,
+ selectedMode: false,
+ data: []
+ }
+ ]
+}) as EChartsOption
+
+/** 鏌ヨ浼氬憳缁熻 */
+const getMemberSummary = async () => {
+ summary.value = await MemberStatisticsApi.getMemberSummary()
+}
+
+/** 鎸夌収鐪佷唤锛屾煡璇細鍛樼粺璁″垪琛� */
+const getMemberAreaStatisticsList = async () => {
+ const list = await MemberStatisticsApi.getMemberAreaStatisticsList()
+ areaStatisticsList.value = list.map((item: MemberAreaStatisticsRespVO) => {
+ return {
+ ...item,
+ areaName: areaReplace(item.areaName)
+ }
+ })
+ let min = 0
+ let max = 0
+ areaChartOptions.series![0].data = areaStatisticsList.value.map((item) => {
+ min = Math.min(min, item.orderPayUserCount || 0)
+ max = Math.max(max, item.orderPayUserCount || 0)
+ return { ...item, name: item.areaName, value: item.orderPayUserCount || 0 }
+ })
+ areaChartOptions.visualMap!['min'] = min
+ areaChartOptions.visualMap!['max'] = max
+}
+
+/** 鎸夌収鎬у埆锛屾煡璇細鍛樼粺璁″垪琛� */
+const getMemberSexStatisticsList = async () => {
+ const list = await MemberStatisticsApi.getMemberSexStatisticsList()
+ const dictDataList = getIntDictOptions(DICT_TYPE.SYSTEM_USER_SEX)
+ dictDataList.push({ label: '鏈煡', value: null } as any)
+ sexChartOptions.series![0].data = dictDataList.map((dictData: DictDataType) => {
+ const userCount = list.find(
+ (item: MemberSexStatisticsRespVO) => item.sex === dictData.value
+ )?.userCount
+ return {
+ name: dictData.label,
+ value: userCount || 0
+ }
+ })
+}
+
+/** 鎸夌収缁堢锛屾煡璇細鍛樼粺璁″垪琛� */
+const getMemberTerminalStatisticsList = async () => {
+ const list = await MemberStatisticsApi.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
+ )?.userCount
+ return {
+ name: dictData.label,
+ value: userCount || 0
+ }
+ })
+}
+
+/** 鍒濆鍖� **/
+onMounted(async () => {
+ loading.value = true
+ await Promise.all([
+ getMemberSummary(),
+ getMemberTerminalStatisticsList(),
+ getMemberAreaStatisticsList(),
+ getMemberSexStatisticsList()
+ ])
+ loading.value = false
+})
+</script>
+<style lang="scss" scoped>
+.summary {
+ .el-col {
+ margin-bottom: 1rem;
+ }
+}
+</style>
--
Gitblit v1.8.0