Forráskód Böngészése

feature 用户统计页面接口对接&UI优化

ylong 4 hónapja
szülő
commit
a1888d8208

+ 23 - 0
src/api/customer/stat.js

@@ -0,0 +1,23 @@
+import request from '@/utils/request';
+
+/**
+ * 获取用户统计数据
+ */
+export async function getUserStatistic() {
+    const res = await request.get('/user/userInfo/statistic');
+    if (res.data.code === 200) {
+        return res.data;
+    }
+    return Promise.reject(new Error(res.data.msg));
+}
+
+/**
+ * 获取用户统计曲线数据
+ */
+export async function getUserStatisticCurve(params) {
+    const res = await request.get('/user/userInfo/statisticCurve', { params });
+    if (res.data.code === 200) {
+        return res.data;
+    }
+    return Promise.reject(new Error(res.data.msg));
+} 

+ 13 - 2
src/components/DateSearch/index.vue

@@ -52,13 +52,24 @@
         position: {
             type: String,
             default: 'right'
+        },
+        quickSelect: {
+            type: Number,
+            default: 7
         }
     });
 
     const emit = defineEmits(['update:modelValue', 'search']);
 
-    const dateRange = ref(props.modelValue);
-    const activeQuickSelect = ref(7); // 默认选中7日
+    const end = dayjs().subtract(1, 'day');
+    const start = dayjs().subtract(7, 'day');
+    const newDateRange = [
+        start.format('YYYY-MM-DD'),
+        end.format('YYYY-MM-DD')
+    ];
+
+    const dateRange = ref(props.modelValue.length > 0 ? props.modelValue : newDateRange);
+    const activeQuickSelect = ref(props.quickSelect); // 默认选中7日
 
     // 监听外部值变化
     watch(

+ 66 - 11
src/views/customer/stat/components/user-active-compare.vue

@@ -3,26 +3,27 @@
         <el-row :gutter="16" class="mt-4">
             <el-col :span="8">
                 <div class="statistic-card">
-                    <el-statistic title="今日" :value="98500" suffix="次" />
+                    <el-statistic title="今日" :value="todayActiveNum" suffix="人" />
                     <div class="statistic-footer">
                         <span>对比昨日</span>
-                        <el-text type="success">
-                            24%<el-icon><Top /></el-icon>
+                        <el-text :type="comparisonType">
+                            {{ comparisonValue }}%<el-icon><component :is="comparisonIcon" /></el-icon>
                         </el-text>
                     </div>
                 </div>
             </el-col>
             <el-col :span="8">
-                <el-statistic :value="693700" title="合计" suffix="次" />
+                <el-statistic :value="yesterdayActiveNum" title="昨日" suffix="人" />
             </el-col>
             <el-col :span="8">
-                <el-statistic :value="690" title="均值" suffix="次" />
+                <el-statistic :value="averageActiveNum" title="均值" suffix="人" />
             </el-col>
         </el-row>
         <v-chart ref="saleChartRef" style="height: 280px" :option="options" />
     </div>
 </template>
 <script setup>
+    import { ref, reactive, computed } from 'vue';
     import VChart from 'vue-echarts';
     import { use } from 'echarts/core';
     import { CanvasRenderer } from 'echarts/renderers';
@@ -33,6 +34,7 @@
         LegendComponent,
         ToolboxComponent
     } from 'echarts/components';
+    import { Top, Bottom } from '@element-plus/icons-vue';
 
     // 按需加载echarts
     use([
@@ -43,12 +45,59 @@
         LegendComponent
     ]);
 
-    const options = reactive({
+    const props = defineProps({
+        todayActiveNum: {
+            type: Number,
+            default: 0
+        },
+        yesterdayActiveNum: {
+            type: Number,
+            default: 0
+        },
+        chatAxis: {
+            type: Array,
+            default: () => []
+        },
+        todayActiveNumCharts: {
+            type: Array,
+            default: () => []
+        },
+        yesterdayActiveNumCharts: {
+            type: Array,
+            default: () => []
+        }
+    });
+
+    // 计算均值
+    const averageActiveNum = computed(() => {
+        if (props.todayActiveNumCharts.length === 0) return 0;
+        const sum = props.todayActiveNumCharts.reduce((acc, val) => acc + val, 0);
+        return Math.round(sum / props.todayActiveNumCharts.length);
+    });
+
+    // 计算环比显示(这里简化处理,实际应该从接口获取环比数据)
+    const comparisonType = computed(() => {
+        const comparison = props.todayActiveNum - props.yesterdayActiveNum;
+        return comparison > 0 ? 'success' : 'danger';
+    });
+
+    const comparisonValue = computed(() => {
+        const comparison = props.todayActiveNum - props.yesterdayActiveNum;
+        if (props.yesterdayActiveNum === 0) return 0;
+        return Math.abs(Math.round((comparison / props.yesterdayActiveNum) * 100));
+    });
+
+    const comparisonIcon = computed(() => {
+        const comparison = props.todayActiveNum - props.yesterdayActiveNum;
+        return comparison > 0 ? Top : Bottom;
+    });
+
+    const options = computed(() => ({
         tooltip: {
             trigger: 'axis'
         },
         legend: {
-            data: ['活跃用户数']
+            data: ['今日活跃用户', '昨日活跃用户']
         },
         grid: {
             left: '3%',
@@ -59,18 +108,24 @@
         xAxis: {
             type: 'category',
             boundaryGap: false,
-            data: ['1月', '2月', '3月', '4月', '5月']
+            data: props.chatAxis || []
         },
         yAxis: {
             type: 'value'
         },
         series: [
             {
-                name: '活跃用户',
+                name: '今日活跃用户',
                 type: 'line',
-                data: [120, 132, 234, 134, 90],
+                data: props.todayActiveNumCharts || [],
+                smooth: true
+            },
+            {
+                name: '昨日活跃用户',
+                type: 'line',
+                data: props.yesterdayActiveNumCharts || [],
                 smooth: true
             }
         ]
-    });
+    }));
 </script>

+ 65 - 11
src/views/customer/stat/components/user-add-compare.vue

@@ -3,26 +3,27 @@
         <el-row :gutter="16" class="mt-4">
             <el-col :span="8">
                 <div class="statistic-card">
-                    <el-statistic title="今日" :value="98500" suffix="人" />
+                    <el-statistic title="今日" :value="todayRegNum" suffix="人" />
                     <div class="statistic-footer">
                         <span>对比昨日</span>
-                        <el-text type="success">
-                            24%<el-icon><Top /></el-icon>
+                        <el-text :type="comparisonType">
+                            {{ comparisonValue }}%<el-icon><component :is="comparisonIcon" /></el-icon>
                         </el-text>
                     </div>
                 </div>
             </el-col>
             <el-col :span="8">
-                <el-statistic :value="693700" title="昨日" suffix="人" />
+                <el-statistic :value="yesterdayRegNum" title="昨日" suffix="人" />
             </el-col>
             <el-col :span="8">
-                <el-statistic :value="690" title="均值" suffix="次" />
+                <el-statistic :value="averageRegNum" title="均值" suffix="人" />
             </el-col>
         </el-row>
         <v-chart ref="saleChartRef" style="height: 280px" :option="options" />
     </div>
 </template>
 <script setup>
+    import { ref, reactive, computed } from 'vue';
     import VChart from 'vue-echarts';
     import { use } from 'echarts/core';
     import { CanvasRenderer } from 'echarts/renderers';
@@ -44,12 +45,59 @@
         LegendComponent
     ]);
 
-    const options = reactive({
+    const props = defineProps({
+        todayRegNum: {
+            type: Number,
+            default: 0
+        },
+        yesterdayRegNum: {
+            type: Number,
+            default: 0
+        },
+        todayRegNumComparison: {
+            type: Number,
+            default: 0
+        },
+        chatAxis: {
+            type: Array,
+            default: () => []
+        },
+        todayRegNumCharts: {
+            type: Array,
+            default: () => []
+        },
+        yesterdayRegNumCharts: {
+            type: Array,
+            default: () => []
+        }
+    });
+
+    // 计算均值
+    const averageRegNum = computed(() => {
+        if (props.todayRegNumCharts.length === 0) return 0;
+        const sum = props.todayRegNumCharts.reduce((acc, val) => acc + val, 0);
+        return Math.round(sum / props.todayRegNumCharts.length);
+    });
+
+    // 计算环比显示
+    const comparisonType = computed(() => {
+        return props.todayRegNumComparison > 0 ? 'success' : 'danger';
+    });
+
+    const comparisonValue = computed(() => {
+        return Math.abs(props.todayRegNumComparison);
+    });
+
+    const comparisonIcon = computed(() => {
+        return props.todayRegNumComparison > 0 ? Top : Bottom;
+    });
+
+    const options = computed(() => ({
         tooltip: {
             trigger: 'axis'
         },
         legend: {
-            data: ['新增用户数']
+            data: ['今日新增用户', '昨日新增用户']
         },
         grid: {
             left: '3%',
@@ -60,18 +108,24 @@
         xAxis: {
             type: 'category',
             boundaryGap: false,
-            data: ['1月', '2月', '3月', '4月', '5月']
+            data: props.chatAxis || []
         },
         yAxis: {
             type: 'value'
         },
         series: [
             {
-                name: '新增用户',
+                name: '今日新增用户',
                 type: 'line',
-                data: [120, 132, 234, 134, 90],
+                data: props.todayRegNumCharts || [],
+                smooth: true
+            },
+            {
+                name: '昨日新增用户',
+                type: 'line',
+                data: props.yesterdayRegNumCharts || [],
                 smooth: true
             }
         ]
-    });
+    }));
 </script>

+ 27 - 8
src/views/customer/stat/components/user-data-line.vue

@@ -2,6 +2,7 @@
     <v-chart ref="saleChartRef" style="height: 350px" :option="options" />
 </template>
 <script setup>
+    import { ref, reactive, watch, computed } from 'vue';
     import VChart from 'vue-echarts';
     import { use } from 'echarts/core';
     import { CanvasRenderer } from 'echarts/renderers';
@@ -22,12 +23,24 @@
         LegendComponent
     ]);
 
-    const options = reactive({
+    const props = defineProps({
+        chartData: {
+            type: Object,
+            default: () => ({
+                chatAxis: [],
+                regNumCharts: [],
+                activeNumCharts: [],
+                totalNumCharts: []
+            })
+        }
+    });
+
+    const options = computed(() => ({
         tooltip: {
             trigger: 'axis'
         },
         legend: {
-            data: ['新增用户', '活跃用户']
+            data: ['新增用户', '活跃用户数', '累计用户数']
         },
         grid: {
             left: '3%',
@@ -38,24 +51,30 @@
         xAxis: {
             type: 'category',
             boundaryGap: false,
-            data: ['1月', '2月', '3月', '4月', '5月', '6月', '7日']
+            data: props.chartData.chatAxis || []
         },
         yAxis: {
             type: 'value'
         },
         series: [
             {
-                name: '新增用户',
+                name: '新增用户',
                 type: 'line',
-                data: [120, 132, 234, 134, 90, 230, 210],
+                data: props.chartData.regNumCharts || [],
                 smooth: true
             },
             {
-                name: '活跃用户',
+                name: '活跃用户',
                 type: 'line',
-                data: [220, 182, 101, 134, 90, 120, 310],
+                data: props.chartData.activeNumCharts || [],
+                smooth: true
+            },
+            {
+                name: '累计用户数',
+                type: 'line',
+                data: props.chartData.totalNumCharts || [],
                 smooth: true
             }
         ]
-    });
+    }));
 </script>

+ 235 - 87
src/views/customer/stat/index.vue

@@ -8,84 +8,197 @@
                 >
                     <el-statistic
                         title="今日新增"
-                        :value="562"
+                        :value="userStatData.todayRegNum"
                         value-style="font-size:24px"
                     >
                         <template #suffix>
                             <el-icon
                                 style="vertical-align: -0.125em"
-                                color="#52c41a"
+                                :color="
+                                    comparisonDisplay(
+                                        userStatData.todayRegNumComparison
+                                    ).color
+                                "
                             >
-                                <Top />
+                                <component
+                                    :is="
+                                        comparisonDisplay(
+                                            userStatData.todayRegNumComparison
+                                        ).icon
+                                    "
+                                />
                             </el-icon>
-                            <el-text type="success" size="small">15%</el-text>
+                            <el-text
+                                :type="
+                                    comparisonDisplay(
+                                        userStatData.todayRegNumComparison
+                                    ).type
+                                "
+                                size="small"
+                            >
+                                {{
+                                    comparisonDisplay(
+                                        userStatData.todayRegNumComparison
+                                    ).value
+                                }}
+                            </el-text>
                         </template>
                     </el-statistic>
                     <el-statistic
                         title="昨日新增"
-                        :value="562"
+                        :value="userStatData.yesterdayRegNum"
                         value-style="font-size:24px"
                     >
                         <template #suffix>
                             <el-icon
                                 style="vertical-align: -0.125em"
-                                color="#ff4d4f"
+                                :color="
+                                    comparisonDisplay(
+                                        userStatData.yesterdayRegNumComparison
+                                    ).color
+                                "
                             >
-                                <Bottom />
+                                <component
+                                    :is="
+                                        comparisonDisplay(
+                                            userStatData.yesterdayRegNumComparison
+                                        ).icon
+                                    "
+                                />
                             </el-icon>
-                            <el-text type="danger" size="small">25%</el-text>
+                            <el-text
+                                :type="
+                                    comparisonDisplay(
+                                        userStatData.yesterdayRegNumComparison
+                                    ).type
+                                "
+                                size="small"
+                            >
+                                {{
+                                    comparisonDisplay(
+                                        userStatData.yesterdayRegNumComparison
+                                    ).value
+                                }}
+                            </el-text>
                         </template>
                     </el-statistic>
                     <el-statistic
                         title="7日新增"
-                        :value="562"
+                        :value="userStatData.sevenDayRegNum"
                         value-style="font-size:24px"
                     >
                         <template #suffix>
                             <el-icon
                                 style="vertical-align: -0.125em"
-                                color="#ff4d4f"
+                                :color="
+                                    comparisonDisplay(
+                                        userStatData.sevenDayRegNumComparison
+                                    ).color
+                                "
                             >
-                                <Bottom />
+                                <component
+                                    :is="
+                                        comparisonDisplay(
+                                            userStatData.sevenDayRegNumComparison
+                                        ).icon
+                                    "
+                                />
                             </el-icon>
-                            <el-text type="danger" size="small">25%</el-text>
+                            <el-text
+                                :type="
+                                    comparisonDisplay(
+                                        userStatData.sevenDayRegNumComparison
+                                    ).type
+                                "
+                                size="small"
+                            >
+                                {{
+                                    comparisonDisplay(
+                                        userStatData.sevenDayRegNumComparison
+                                    ).value
+                                }}
+                            </el-text>
                         </template>
                     </el-statistic>
                     <el-statistic
                         title="30日新增"
-                        :value="562"
+                        :value="userStatData.thirtyDayRegNum"
                         value-style="font-size:24px"
                     >
                         <template #suffix>
                             <el-icon
                                 style="vertical-align: -0.125em"
-                                color="#ff4d4f"
+                                :color="
+                                    comparisonDisplay(
+                                        userStatData.thirtyDayRegNumComparison
+                                    ).color
+                                "
                             >
-                                <Bottom />
+                                <component
+                                    :is="
+                                        comparisonDisplay(
+                                            userStatData.thirtyDayRegNumComparison
+                                        ).icon
+                                    "
+                                />
                             </el-icon>
-                            <el-text type="danger" size="small">25%</el-text>
+                            <el-text
+                                :type="
+                                    comparisonDisplay(
+                                        userStatData.thirtyDayRegNumComparison
+                                    ).type
+                                "
+                                size="small"
+                            >
+                                {{
+                                    comparisonDisplay(
+                                        userStatData.thirtyDayRegNumComparison
+                                    ).value
+                                }}
+                            </el-text>
                         </template>
                     </el-statistic>
                 </div>
-                <date-search />
+                <date-search @search="fetchUserCurveData" />
             </div>
 
             <div class="common-card mb-10">
                 <div class="common-title">用户数据</div>
-                <user-data-line />
+                <user-data-line :chart-data="userCurveData" />
             </div>
             <div class="common-card flex gap-6">
                 <div class="flex-1 gray-card">
-                    <div class="common-title"
-                        >今日对比昨日| 分时新增新用户数量</div
-                    >
-                    <user-add-compare />
+                    <div class="common-title">
+                        今日对比昨日| 分时新增新用户数量
+                    </div>
+                    <user-add-compare
+                        :today-reg-num="userStatData.todayRegNum"
+                        :yesterday-reg-num="userStatData.yesterdayRegNum"
+                        :today-reg-num-comparison="
+                            userStatData.todayRegNumComparison
+                        "
+                        :chat-axis="userStatData.chatAxis"
+                        :today-reg-num-charts="userStatData.todayRegNumCharts"
+                        :yesterday-reg-num-charts="
+                            userStatData.yesterdayRegNumCharts
+                        "
+                    />
                 </div>
                 <div class="flex-1 gray-card">
-                    <div class="common-title"
-                        >今日对比昨日| 分时活跃新用户数量</div
-                    >
-                    <user-active-compare />
+                    <div class="common-title">
+                        今日对比昨日| 分时活跃新用户数量
+                    </div>
+                    <user-active-compare
+                        :today-active-num="userStatData.todayActiveNum"
+                        :yesterday-active-num="userStatData.yesterdayActiveNum"
+                        :chat-axis="userStatData.chatAxis"
+                        :today-active-num-charts="
+                            userStatData.todayActiveNumCharts
+                        "
+                        :yesterday-active-num-charts="
+                            userStatData.yesterdayActiveNumCharts
+                        "
+                    />
                 </div>
             </div>
         </ele-card>
@@ -93,75 +206,110 @@
 </template>
 
 <script setup>
-    import { ref, reactive } from 'vue';
-    import { Bottom, Top } from '@element-plus/icons-vue';
-    import dateSearch from '@/components/DateSearch/index.vue';
-    import pageSearch from '@/views/finance/withdrawal/components/page-search.vue';
-    import { useDictData } from '@/utils/use-dict-data';
-    import userDataLine from '@/views/customer/stat/components/user-data-line.vue';
-    import userActiveCompare from '@/views/customer/stat/components/user-active-compare.vue';
-    import userAddCompare from '@/views/customer/stat/components/user-add-compare.vue';
+import { ref, reactive, onMounted } from "vue";
+import { Bottom, Top } from "@element-plus/icons-vue";
+import { EleMessage } from "ele-admin-plus/es";
+import dateSearch from "@/components/DateSearch/index.vue";
+import { getUserStatistic, getUserStatisticCurve } from "@/api/customer/stat";
+import userDataLine from "@/views/customer/stat/components/user-data-line.vue";
+import userActiveCompare from "@/views/customer/stat/components/user-active-compare.vue";
+import userAddCompare from "@/views/customer/stat/components/user-add-compare.vue";
+import request from "@/utils/request";
+import dayjs from "dayjs";
 
-    defineOptions({ name: 'Withdrawal' });
-    const [useStatusDicts] = useDictData(['use_status']);
+defineOptions({ name: "UserStat" });
 
-    const useStatus = ref('1');
-    function handleStatusChange(value) {
-        pageRef.value.reload({ useStatus: value });
-    }
+// 用户统计数据
+const userStatData = ref({
+    todayRegNum: 0,
+    yesterdayRegNum: 0,
+    sevenDayRegNum: 0,
+    thirtyDayRegNum: 0,
+    todayRegNumComparison: 0,
+    yesterdayRegNumComparison: 0,
+    sevenDayRegNumComparison: 0,
+    thirtyDayRegNumComparison: 0,
+    todayActiveNum: 0,
+    yesterdayActiveNum: 0,
+    chatAxis: [],
+    todayRegNumCharts: [],
+    yesterdayRegNumCharts: [],
+    todayActiveNumCharts: [],
+    yesterdayActiveNumCharts: [],
+});
 
-    /** 表格列配置 */
-    const columns = ref([
-        { label: '交易时间', prop: 'createTime', align: 'center', width: 180 },
-        { label: '用户UID', prop: 'uid', align: 'center', minWidth: 140 },
-        {
-            label: '支付单号/流水号',
-            prop: 'paymentCode',
-            align: 'center',
-            minWidth: 160
-        },
-        { label: '对方账户', prop: 'addressDetail', align: 'center' },
-        { label: '结算金额', prop: 'money', align: 'center' },
-        {
-            label: '交易状态',
-            prop: 'useStatus',
-            align: 'center',
-            formatter: (row) =>
-                useStatusDicts.value.find((d) => d.dictValue == row.useStatus)
-                    ?.dictLabel
-        },
-        {
-            label: '交易类型',
-            prop: 'paymentType',
-            align: 'center',
-            formatter: (row) =>
-                useStatusDicts.value.find((d) => d.dictValue == row.useStatus)
-                    ?.dictLabel
-        },
-        { label: '订单编号', prop: 'code', align: 'center' }
-    ]);
+// 用户统计曲线数据
+const userCurveData = ref({
+    chatAxis: [],
+    regNumCharts: [],
+    activeNumCharts: [],
+});
 
-    /** 页面组件实例 */
-    const pageRef = ref(null);
+// 获取用户统计数据
+const fetchUserStatData = async () => {
+    try {
+        const res = await request.get("/user/userInfo/statistic");
+        if (res.data.code===200) {
+            userStatData.value = res.data.data;
+        }
+    } catch (error) {
+        EleMessage.error("获取用户统计数据失败");
+        console.error("Failed to fetch user statistic data:", error);
+    }
+};
 
-    const pageConfig = reactive({
-        pageUrl: '/baseinfo/godown/pagelist',
-        exportUrl: '/baseinfo/godown/export',
-        fileName: '佣金记录',
-        cacheKey: 'commissionTable'
-    });
+// 获取用户统计曲线数据
+const fetchUserCurveData = async (time = []) => {
+    try {
+        const end = dayjs().subtract(1, "day");
+        const start = dayjs().subtract(7, "day");
+        // 默认获取最近7天的数据
+        const params = {
+            startTime: time[0] || start.format("YYYY-MM-DD"),
+            endTime: time[1] || end.format("YYYY-MM-DD"),
+        };
+        const res = await request.get("/user/userInfo/statisticCurve", {
+            params,
+        });
+        if (res.data.code === 200) {
+            userCurveData.value = res.data.data;
+        }
+    } catch (error) {
+        EleMessage.error("获取用户统计曲线数据失败");
+        console.error("Failed to fetch user curve data:", error);
+    }
+};
 
-    //刷新表格
-    function reload(where) {
-        pageRef.value?.reload(where);
+// 计算环比显示
+const comparisonDisplay = (comparison) => {
+    if (comparison > 0) {
+        return {
+            icon: Top,
+            color: "#52c41a",
+            type: "success",
+            value: `${comparison}%`,
+        };
+    } else {
+        return {
+            icon: Bottom,
+            color: "#ff4d4f",
+            type: "danger",
+            value: `${Math.abs(comparison)}%`,
+        };
     }
+};
+
+onMounted(() => {
+    fetchUserStatData();
+    fetchUserCurveData();
+});
 </script>
 
 <style lang="scss">
-    .gray-card {
-        border-radius: 10px;
-        padding: 20px;
-        box-sizing: border-box;
-        background: #f9f8f7;
-    }
+.gray-card {
+    border-radius: 10px;
+    padding: 20px;
+    box-sizing: border-box;
+    background: #f9f8f7;
+}
 </style>