refactor: ♻️ 访问统计对接后台临时提交

This commit is contained in:
haoxianrui
2024-07-06 05:07:50 +08:00
parent 7345b4dd66
commit d852698499
6 changed files with 176 additions and 185 deletions

View File

@@ -15,6 +15,33 @@ class LogAPI {
params: queryParams,
});
}
/**
* 获取访问趋势
*
* @param queryParams
* @returns
*/
static getVisitTrend(queryParams: VisitTrendQuery) {
return request<any, VisitTrendVO>({
url: `${LOG_BASE_URL}/visit-trend`,
method: "get",
params: queryParams,
});
}
/**
* 获取访问趋势
*
* @param queryParams
* @returns
*/
static getVisitStats() {
return request<any, VisitStatsVO[]>({
url: `${LOG_BASE_URL}/visit-stats`,
method: "get",
});
}
}
export default LogAPI;
@@ -54,3 +81,40 @@ export interface LogPageVO {
/** 操作人 */
operator: string;
}
/** 访问趋势视图对象 */
export interface VisitTrendVO {
/** 日期列表 */
dates: string[];
/** 浏览量(PV) */
pvList: number[];
/** 访客数(UV) */
uvList: number[];
/** IP数 */
ipList: number[];
}
/** 访问趋势查询参数 */
export interface VisitTrendQuery {
/** 开始日期 */
startDate: string;
/** 结束日期 */
endDate: string;
}
/** 访问统计 */
export interface VisitStatsVO {
/** 标题 */
title: string;
/** 类型 */
type: "pv" | "uv" | "ip";
/** 今日访问量 */
todayCount: number;
/** 总访问量 */
totalCount: number;
/** 同比增长率(相对于昨天同一时间段的增长率) */
growthRate: number;
totalCountOutput: number;
}

View File

@@ -1,35 +0,0 @@
import request from "@/utils/request";
const STATS_BASE_URL = "/api/v1/stats";
class StatsAPI {
static getVisitTrend(queryParams: VisitTrendQuery) {
return request<any, VisitTrendVO>({
url: `${STATS_BASE_URL}/visit-trend`,
method: "get",
params: queryParams,
});
}
}
export default StatsAPI;
/** 访问趋势视图对象 */
export interface VisitTrendVO {
/** 日期列表 */
dates: string[];
/** 浏览量(PV) */
pvList: number[];
/** 访客数(UV) */
uvList: number[];
/** IP数 */
ipList: number[];
}
/** 访问趋势查询参数 */
export interface VisitTrendQuery {
/** 开始日期 */
startDate: string;
/** 结束日期 */
endDate: string;
}

View File

Before

Width:  |  Height:  |  Size: 890 B

After

Width:  |  Height:  |  Size: 890 B

View File

@@ -19,6 +19,12 @@
<!-- 语言选择 -->
<lang-select class="setting-item" />
<el-dropdown class="setting-item" trigger="click">
<el-badge is-dot class="mt-[16px]">
<i-ep-bell />
</el-badge>
</el-dropdown>
</template>
<!-- 用户头像 -->

View File

@@ -30,7 +30,7 @@
<script setup lang="ts">
import * as echarts from "echarts";
import StatsAPI, { VisitTrendVO, VisitTrendQuery } from "@/api/stats";
import StatsAPI, { VisitTrendVO, VisitTrendQuery } from "@/api/log";
const dataRange = ref(1);
const chart: Ref<echarts.ECharts | null> = ref(null);

View File

@@ -42,51 +42,93 @@
<!-- 数据卡片 -->
<el-row :gutter="10" class="mt-5">
<el-col :xs="24" :sm="12" :lg="6">
<el-card shadow="never">
<template #header>
<div class="flex-x-between">
<span class="text-[var(--el-text-color-secondary)]"
>在线用户</span
>
<el-tag type="success" size="small">-</el-tag>
</div>
</template>
<div class="flex-x-between mt-2">
<span class="text-lg"> 1</span>
<svg-icon icon-class="item.iconClass" size="2em" />
</div>
<div
class="flex-x-between mt-2 text-sm text-[var(--el-text-color-secondary)]"
>
<span> 总用户数 </span>
<span>5 </span>
</div>
</el-card>
</el-col>
<el-col
:xs="24"
:sm="12"
:lg="6"
v-for="(item, index) in cardData"
v-for="(item, index) in visitStatsList"
:key="index"
>
<el-card shadow="never">
<template #header>
<div class="flex items-center justify-between">
<span class="text-[var(--el-text-color-secondary)]">{{
item.title
}}</span>
<el-tag v-if="item.tagText" :type="item.tagType" size="small">
{{ item.tagText }}
</el-tag>
<el-skeleton :loading="loading" animated>
<template #template>
<div>
<el-skeleton-item variant="text" style="width: 60%" />
<div class="mt-2">
<el-skeleton-item variant="text" style="width: 40%" />
<el-skeleton-item
variant="rect"
style="float: right; width: 2em; height: 2em"
/>
</div>
<div class="mt-2">
<el-skeleton-item variant="text" style="width: 100%" />
<el-skeleton-item variant="text" style="width: 80%" />
</div>
</div>
</template>
<template v-if="!loading">
<el-card shadow="never">
<template #header>
<div class="flex-x-between">
<span class="text-[var(--el-text-color-secondary)]">{{
item.title
}}</span>
<el-tag type="primary" size="small"> </el-tag>
</div>
</template>
<div class="flex items-center justify-between mt-2">
<div class="flex-y-center">
<span class="text-lg"> {{ Math.round(item.count) }}</span>
<span
v-if="item.growthRate"
:class="[
'text-xs',
'ml-2',
item.growthRate > 0 ? 'color-red' : 'color-green',
]"
><i-ep-top v-if="item.growthRate > 0" /><i-ep-bottom
v-else-if="item.growthRate < 0"
/>
{{ Math.abs(item.growthRate * 100) }}%
</span>
</div>
<svg-icon :icon-class="item.iconClass" size="2em" />
</div>
<div class="flex-x-between mt-2">
<div class="flex-y-center">
<span class="text-lg"> {{ item.todayCount }}</span>
<span
v-if="item.growthRate"
:class="[
'text-xs',
'ml-2',
item.growthRate > 0 ? 'color-red' : 'color-green',
]"
>
<i-ep-top v-if="item.growthRate > 0" />
<i-ep-bottom v-else-if="item.growthRate < 0" />
{{ Math.abs(item.growthRate * 100).toFixed(2) }}%
</span>
</div>
<svg-icon :icon-class="item.type" size="2em" />
</div>
<div
class="flex items-center justify-between mt-2 text-sm text-[var(--el-text-color-secondary)]"
>
<span> {{ item.dataDesc }} </span>
<span> {{ item.totalCount }} </span>
</div>
</el-card>
<div
class="flex-x-between mt-2 text-sm text-[var(--el-text-color-secondary)]"
>
<span>{{ item.title }} </span>
<span> {{ item.totalCountOutput }} </span>
</div>
</el-card>
</template>
</el-skeleton>
</el-col>
</el-row>
@@ -115,18 +157,20 @@
</template>
<script setup lang="ts">
import type { EpPropMergeType } from "element-plus/es/utils/vue/props/types";
defineOptions({
name: "Dashboard",
inheritAttrs: false,
});
import type { EpPropMergeType } from "element-plus/es/utils/vue/props/types";
import { useUserStore } from "@/store/modules/user";
import { useTransition, TransitionPresets } from "@vueuse/core";
import StatsAPI, { VisitStatsVO } from "@/api/log";
const userStore = useUserStore();
const date: Date = new Date();
const date: Date = new Date();
const greetings = computed(() => {
const hours = date.getHours();
if (hours >= 6 && hours < 8) {
@@ -142,40 +186,6 @@ const greetings = computed(() => {
}
});
const duration = 5000;
// 在线用户数
const onlineUserCount = ref(0);
const onlineUserCountOutput = useTransition(onlineUserCount, {
duration: duration,
transition: TransitionPresets.easeOutExpo,
});
onlineUserCount.value = 1;
// 浏览量
const pvCount = ref(0);
const pvCountOutput = useTransition(pvCount, {
duration: duration,
transition: TransitionPresets.easeOutExpo,
});
pvCount.value = 2000;
// 访客数
const uvCount = ref(0);
const uvCountOutput = useTransition(uvCount, {
duration: duration,
transition: TransitionPresets.easeOutExpo,
});
uvCount.value = 2000;
// IP数
const ipCount = ref(0);
const ipCountOutput = useTransition(ipCount, {
duration: duration,
transition: TransitionPresets.easeOutExpo,
});
ipCount.value = 2000;
// 右上角数量
const statisticData = ref([
{
@@ -199,63 +209,6 @@ const statisticData = ref([
},
]);
interface CardProp {
title: string;
tagType: EpPropMergeType<
StringConstructor,
"primary" | "success" | "info" | "warning" | "danger",
unknown
>;
tagText: string;
count: any;
totalCount: any;
dataDesc: string;
iconClass: string;
growthRate?: number;
}
// 卡片数量
const cardData = ref<CardProp[]>([
{
title: "在线用户",
tagType: "success",
tagText: "-",
count: onlineUserCountOutput,
totalCount: "3",
dataDesc: "总用户数",
iconClass: "visit",
},
{
title: "浏览量(PV)",
tagType: "primary",
tagText: "日",
count: pvCountOutput,
totalCount: 3000,
dataDesc: "总浏览量",
iconClass: "pv",
growthRate: 0.5,
},
{
title: "访客数(UV)",
tagType: "danger",
tagText: "日",
count: uvCountOutput,
totalCount: 3000,
dataDesc: "总访客数",
iconClass: "uv",
growthRate: -0.1,
},
{
title: "IP数",
tagType: "success",
tagText: "日",
count: ipCountOutput,
totalCount: 3000,
dataDesc: "总IP数",
iconClass: "ip",
growthRate: 0.2,
},
]);
const notices = ref([
{
title: "v2.12.0",
@@ -282,6 +235,30 @@ const notices = ref([
description: "修复了一些问题,优化了一些代码。",
},
]);
const loading = ref(true);
const visitStatsList = ref<VisitStatsVO[] | null>(Array(3).fill({}));
const loadVisitStatsData = async () => {
const list = await StatsAPI.getVisitStats();
if (list) {
visitStatsList.value = list;
// 初始化动画输出
list.forEach((item) => {
item.totalCountOutput = useTransition(item.totalCount, {
duration: 1000,
transition: TransitionPresets.easeOutExpo,
}).value;
});
loading.value = false;
}
};
onMounted(() => {
loadVisitStatsData();
});
</script>
<style lang="scss" scoped>
@@ -289,12 +266,6 @@ const notices = ref([
position: relative;
padding: 24px;
.user-avatar {
width: 40px;
height: 40px;
border-radius: 50%;
}
.github-corner {
position: absolute;
top: 0;
@@ -302,20 +273,5 @@ const notices = ref([
z-index: 1;
border: 0;
}
.data-box {
display: flex;
justify-content: space-between;
padding: 20px;
font-weight: bold;
color: var(--el-text-color-regular);
background: var(--el-bg-color-overlay);
border-color: var(--el-border-color);
box-shadow: var(--el-box-shadow-dark);
}
.svg-icon {
fill: currentcolor !important;
}
}
</style>