refactor: ♻️ 访问统计对接后台临时提交
This commit is contained in:
@@ -15,6 +15,33 @@ class LogAPI {
|
|||||||
params: queryParams,
|
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;
|
export default LogAPI;
|
||||||
@@ -54,3 +81,40 @@ export interface LogPageVO {
|
|||||||
/** 操作人 */
|
/** 操作人 */
|
||||||
operator: string;
|
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;
|
||||||
|
}
|
||||||
|
|||||||
@@ -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;
|
|
||||||
}
|
|
||||||
|
Before Width: | Height: | Size: 890 B After Width: | Height: | Size: 890 B |
@@ -19,6 +19,12 @@
|
|||||||
|
|
||||||
<!-- 语言选择 -->
|
<!-- 语言选择 -->
|
||||||
<lang-select class="setting-item" />
|
<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>
|
</template>
|
||||||
|
|
||||||
<!-- 用户头像 -->
|
<!-- 用户头像 -->
|
||||||
|
|||||||
@@ -30,7 +30,7 @@
|
|||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import * as echarts from "echarts";
|
import * as echarts from "echarts";
|
||||||
import StatsAPI, { VisitTrendVO, VisitTrendQuery } from "@/api/stats";
|
import StatsAPI, { VisitTrendVO, VisitTrendQuery } from "@/api/log";
|
||||||
|
|
||||||
const dataRange = ref(1);
|
const dataRange = ref(1);
|
||||||
const chart: Ref<echarts.ECharts | null> = ref(null);
|
const chart: Ref<echarts.ECharts | null> = ref(null);
|
||||||
|
|||||||
@@ -42,51 +42,93 @@
|
|||||||
|
|
||||||
<!-- 数据卡片 -->
|
<!-- 数据卡片 -->
|
||||||
<el-row :gutter="10" class="mt-5">
|
<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
|
<el-col
|
||||||
:xs="24"
|
:xs="24"
|
||||||
:sm="12"
|
:sm="12"
|
||||||
:lg="6"
|
:lg="6"
|
||||||
v-for="(item, index) in cardData"
|
v-for="(item, index) in visitStatsList"
|
||||||
:key="index"
|
:key="index"
|
||||||
>
|
>
|
||||||
<el-card shadow="never">
|
<el-skeleton :loading="loading" animated>
|
||||||
<template #header>
|
<template #template>
|
||||||
<div class="flex items-center justify-between">
|
<div>
|
||||||
<span class="text-[var(--el-text-color-secondary)]">{{
|
<el-skeleton-item variant="text" style="width: 60%" />
|
||||||
item.title
|
<div class="mt-2">
|
||||||
}}</span>
|
<el-skeleton-item variant="text" style="width: 40%" />
|
||||||
<el-tag v-if="item.tagText" :type="item.tagType" size="small">
|
<el-skeleton-item
|
||||||
{{ item.tagText }}
|
variant="rect"
|
||||||
</el-tag>
|
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>
|
</div>
|
||||||
</template>
|
</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-x-between mt-2">
|
||||||
<div class="flex-y-center">
|
<div class="flex-y-center">
|
||||||
<span class="text-lg"> {{ Math.round(item.count) }}</span>
|
<span class="text-lg"> {{ item.todayCount }}</span>
|
||||||
<span
|
<span
|
||||||
v-if="item.growthRate"
|
v-if="item.growthRate"
|
||||||
:class="[
|
:class="[
|
||||||
'text-xs',
|
'text-xs',
|
||||||
'ml-2',
|
'ml-2',
|
||||||
item.growthRate > 0 ? 'color-red' : 'color-green',
|
item.growthRate > 0 ? 'color-red' : 'color-green',
|
||||||
]"
|
]"
|
||||||
><i-ep-top v-if="item.growthRate > 0" /><i-ep-bottom
|
>
|
||||||
v-else-if="item.growthRate < 0"
|
<i-ep-top v-if="item.growthRate > 0" />
|
||||||
/>
|
<i-ep-bottom v-else-if="item.growthRate < 0" />
|
||||||
{{ Math.abs(item.growthRate * 100) }}%
|
{{ Math.abs(item.growthRate * 100).toFixed(2) }}%
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<svg-icon :icon-class="item.iconClass" size="2em" />
|
<svg-icon :icon-class="item.type" size="2em" />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div
|
<div
|
||||||
class="flex items-center justify-between mt-2 text-sm text-[var(--el-text-color-secondary)]"
|
class="flex-x-between mt-2 text-sm text-[var(--el-text-color-secondary)]"
|
||||||
>
|
>
|
||||||
<span> {{ item.dataDesc }} </span>
|
<span>总{{ item.title }} </span>
|
||||||
<span> {{ item.totalCount }} </span>
|
<span> {{ item.totalCountOutput }} </span>
|
||||||
</div>
|
</div>
|
||||||
</el-card>
|
</el-card>
|
||||||
|
</template>
|
||||||
|
</el-skeleton>
|
||||||
</el-col>
|
</el-col>
|
||||||
</el-row>
|
</el-row>
|
||||||
|
|
||||||
@@ -115,18 +157,20 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import type { EpPropMergeType } from "element-plus/es/utils/vue/props/types";
|
|
||||||
defineOptions({
|
defineOptions({
|
||||||
name: "Dashboard",
|
name: "Dashboard",
|
||||||
inheritAttrs: false,
|
inheritAttrs: false,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
import type { EpPropMergeType } from "element-plus/es/utils/vue/props/types";
|
||||||
|
|
||||||
import { useUserStore } from "@/store/modules/user";
|
import { useUserStore } from "@/store/modules/user";
|
||||||
import { useTransition, TransitionPresets } from "@vueuse/core";
|
import { useTransition, TransitionPresets } from "@vueuse/core";
|
||||||
|
|
||||||
|
import StatsAPI, { VisitStatsVO } from "@/api/log";
|
||||||
const userStore = useUserStore();
|
const userStore = useUserStore();
|
||||||
const date: Date = new Date();
|
|
||||||
|
|
||||||
|
const date: Date = new Date();
|
||||||
const greetings = computed(() => {
|
const greetings = computed(() => {
|
||||||
const hours = date.getHours();
|
const hours = date.getHours();
|
||||||
if (hours >= 6 && hours < 8) {
|
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([
|
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([
|
const notices = ref([
|
||||||
{
|
{
|
||||||
title: "v2.12.0",
|
title: "v2.12.0",
|
||||||
@@ -282,6 +235,30 @@ const notices = ref([
|
|||||||
description: "修复了一些问题,优化了一些代码。",
|
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>
|
</script>
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
@@ -289,12 +266,6 @@ const notices = ref([
|
|||||||
position: relative;
|
position: relative;
|
||||||
padding: 24px;
|
padding: 24px;
|
||||||
|
|
||||||
.user-avatar {
|
|
||||||
width: 40px;
|
|
||||||
height: 40px;
|
|
||||||
border-radius: 50%;
|
|
||||||
}
|
|
||||||
|
|
||||||
.github-corner {
|
.github-corner {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
top: 0;
|
top: 0;
|
||||||
@@ -302,20 +273,5 @@ const notices = ref([
|
|||||||
z-index: 1;
|
z-index: 1;
|
||||||
border: 0;
|
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>
|
</style>
|
||||||
|
|||||||
Reference in New Issue
Block a user