refactor: ♻️ 控制台精简重构(访问统计调整和添加项目相关信息)

This commit is contained in:
Ray.Hao
2024-12-08 18:57:52 +08:00
parent e3e0bec22e
commit a7c1c4e14a
20 changed files with 287 additions and 283 deletions

View File

@@ -1,47 +1,91 @@
<template>
<div class="dashboard-container">
<!-- github 角标 -->
<github-corner class="github-corner" />
<el-card shadow="never">
<el-row justify="space-between">
<el-row class="h-80px">
<el-col :span="18" :xs="24">
<div class="flex h-full items-center">
<div class="flex-x-start">
<img
class="w-20 h-20 mr-5 rounded-full"
class="wh-80px rounded-full"
:src="userStore.userInfo.avatar + '?imageView2/1/w/80/h/80'"
/>
<div>
<div class="ml-5">
<p>{{ greetings }}</p>
<p class="text-sm text-gray">今日天气晴朗气温在15至25之间东南风</p>
</div>
</div>
</el-col>
<el-col :span="6" :xs="24">
<el-row class="h-80px flex-y-center" :gutter="10">
<el-col :span="10">
<div class="font-bold color-#ff9a2e text-sm flex-y-center">
<el-icon class="mr-2px"><Folder /></el-icon>
仓库
</div>
<div class="mt-3">
<el-link href="https://gitee.com/youlaiorg/vue3-element-admin" target="_blank">
<SvgIcon icon-class="gitee" class="text-lg color-#f76560" />
</el-link>
<el-divider direction="vertical" />
<el-link href="https://github.com/youlaitech/vue3-element-admin" target="_blank">
<SvgIcon icon-class="github" class="text-lg color-#4080ff" />
</el-link>
<el-divider direction="vertical" />
<el-link href="https://gitcode.com/youlai/vue3-element-admin" target="_blank">
<SvgIcon icon-class="gitcode" class="text-lg color-#ff9a2e" />
</el-link>
</div>
</el-col>
<el-col :span="10">
<div class="font-bold color-#4080ff text-sm flex-y-center">
<el-icon class="mr-2px"><Document /></el-icon>
文档
</div>
<div class="mt-3">
<el-link href="https://juejin.cn/post/7228990409909108793" target="_blank">
<SvgIcon icon-class="juejin" class="text-lg" />
</el-link>
<el-divider direction="vertical" />
<el-link
href="https://youlai.blog.csdn.net/article/details/130191394"
target="_blank"
>
<SvgIcon icon-class="csdn" class="text-lg" />
</el-link>
<el-divider direction="vertical" />
<el-link href="https://www.cnblogs.com/haoxianrui/p/17331952.html" target="_blank">
<SvgIcon icon-class="cnblogs" class="text-lg" />
</el-link>
</div>
</el-col>
<el-col :span="4">
<div class="font-bold color-#f76560 text-sm flex-y-center">
<el-icon class="mr-2px"><VideoCamera /></el-icon>
视频
</div>
<div class="mt-3">
<el-link
href="https://space.bilibili.com/1731537706/channel/seriesdetail?sid=4459672"
target="_blank"
>
<SvgIcon icon-class="bilibili" class="text-lg" />
</el-link>
</div>
</el-col>
</el-row>
</el-col>
</el-row>
</el-card>
<!-- 数据卡片 -->
<!-- 数据统计 -->
<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">{{ onlineUserCount }}</span>
<svg-icon icon-class="user" 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 v-for="(item, index) in visitStatsList" :key="index" :xs="24" :sm="12" :lg="6">
<!-- 访客数(UV) -->
<el-col :span="12">
<el-skeleton :loading="visitStatsLoading" :rows="5" animated>
<template #template>
<el-card>
@@ -66,32 +110,86 @@
<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="item.tagType" size="small">
{{ item.granularity }}
</el-tag>
<span class="text-gray">访客数(UV)</span>
<el-tag type="success" size="small"></el-tag>
</div>
</template>
<div class="flex-x-between mt-2">
<div class="flex-y-center">
<span class="text-lg">{{ item.todayCount }}</span>
<span :class="['text-xs', 'ml-2', getGrowthRateClass(item.growthRate)]">
<span class="text-lg">{{ visitStatsData.todayUvCount }}</span>
<span
:class="['text-xs', 'ml-2', getGrowthRateClass(visitStatsData.uvGrowthRate)]"
>
<el-icon>
<Top v-if="item.growthRate > 0" />
<Bottom v-else-if="item.growthRate < 0" />
<Top v-if="visitStatsData.uvGrowthRate > 0" />
<Bottom v-else-if="visitStatsData.uvGrowthRate < 0" />
</el-icon>
{{ formatGrowthRate(item.growthRate) }}
{{ formatGrowthRate(visitStatsData.uvGrowthRate) }}
</span>
</div>
<svg-icon :icon-class="item.icon" size="2em" />
<svg-icon icon-class="visitor" size="2em" />
</div>
<div class="flex-x-between mt-2 text-sm text-[var(--el-text-color-secondary)]">
<span>{{ item.title }}</span>
<span>{{ item.totalCount }}</span>
<div class="flex-x-between mt-2 text-sm text-gray">
<span>访客数</span>
<span>{{ visitStatsData.totalUvCount }}</span>
</div>
</el-card>
</template>
</el-skeleton>
</el-col>
<!-- 浏览量(PV) -->
<el-col :span="12">
<el-skeleton :loading="visitStatsLoading" :rows="5" animated>
<template #template>
<el-card>
<template #header>
<div>
<el-skeleton-item variant="h3" style="width: 40%" />
<el-skeleton-item variant="rect" style="float: right; width: 1em; height: 1em" />
</div>
</template>
<div class="flex-x-between">
<el-skeleton-item variant="text" style="width: 30%" />
<el-skeleton-item variant="circle" style="width: 2em; height: 2em" />
</div>
<div class="mt-5 flex-x-between">
<el-skeleton-item variant="text" style="width: 50%" />
<el-skeleton-item variant="text" style="width: 1em" />
</div>
</el-card>
</template>
<template v-if="!visitStatsLoading">
<el-card shadow="never">
<template #header>
<div class="flex-x-between">
<span class="text-gray">浏览量(PV)</span>
<el-tag type="primary" size="small"></el-tag>
</div>
</template>
<div class="flex-x-between mt-2">
<div class="flex-y-center">
<span class="text-lg">{{ visitStatsData.todayPvCount }}</span>
<span
:class="['text-xs', 'ml-2', getGrowthRateClass(visitStatsData.pvGrowthRate)]"
>
<el-icon>
<Top v-if="visitStatsData.pvGrowthRate > 0" />
<Bottom v-else-if="visitStatsData.pvGrowthRate < 0" />
</el-icon>
{{ formatGrowthRate(visitStatsData.pvGrowthRate) }}
</span>
</div>
<svg-icon icon-class="browser" size="2em" />
</div>
<div class="flex-x-between mt-2 text-sm text-gray">
<span>总浏览量</span>
<span>{{ visitStatsData.totalPvCount }}</span>
</div>
</el-card>
</template>
@@ -100,32 +198,30 @@
</el-row>
<el-row :gutter="10" class="mt-5">
<!-- 访问趋势统计图 -->
<el-col :xs="24" :span="16">
<!-- 访问趋势统计图 -->
<VisitTrend id="VisitTrend" width="100%" height="400px" />
</el-col>
<!-- 通知公告 -->
<el-col :xs="24" :span="8">
<el-card>
<template #header>
<div class="flex-x-between">
<div class="flex-y-center">通知公告</div>
<el-link type="primary">
<span class="text-xs" @click="viewMoreNotice">查看更多</span>
<span class="text-xs" @click="handleViewMoreNotice">查看更多</span>
<el-icon class="text-xs"><ArrowRight /></el-icon>
</el-link>
</div>
</template>
<el-scrollbar height="400px">
<div v-for="(item, index) in notices" :key="index" class="flex-y-center py-3">
<div v-for="(item, index) in notices" :key="index" class="flex-y-center py-4">
<DictLabel v-model="item.type" code="notice_type" size="small" />
<el-text
truncated
class="!mx-2 flex-1 !text-xs !text-[var(--el-text-color-secondary)]"
>
<el-text truncated class="!mx-2 flex-1 !text-xs !text-gray">
{{ item.title }}
</el-text>
<el-link @click="viewNoticeDetail(item.id)">
<el-link @click="handleOpenNoticeDetail(item.id)">
<el-icon class="text-sm"><View /></el-icon>
</el-link>
</div>
@@ -144,29 +240,20 @@ defineOptions({
inheritAttrs: false,
});
import VisitTrend from "./components/VisitTrend.vue";
import VisitTrend from "./components/visit-trend.vue";
import WebSocketManager from "@/utils/websocket";
import router from "@/router";
import { useUserStore } from "@/store/modules/user";
import StatsAPI, { VisitStatsVO } from "@/api/system/log";
import NoticeAPI, { NoticePageVO } from "@/api/system/notice";
interface VisitStats {
title: string;
icon: string;
tagType: "primary" | "success" | "warning";
growthRate: number;
// 粒度
granularity: string;
// 今日数量
todayCount: number;
totalCount: number;
}
import { useUserStore } from "@/store/modules/user";
import { formatGrowthRate } from "@/utils";
const noticeDetailRef = ref();
const notices = ref<NoticePageVO[]>([]);
const userStore = useUserStore();
const date: Date = new Date();
const greetings = computed(() => {
@@ -184,45 +271,33 @@ const greetings = computed(() => {
}
});
const onlineUserCount = ref(0);
const visitStatsLoading = ref(true);
const visitStatsList = ref<VisitStats[] | null>(Array(3).fill({}));
const visitStatsData = ref<VisitStatsVO>({
todayUvCount: 0,
uvGrowthRate: 0,
totalUvCount: 0,
todayPvCount: 0,
pvGrowthRate: 0,
totalPvCount: 0,
});
// 加载访问统计数据
const loadVisitStatsData = async () => {
const list: VisitStatsVO[] = await StatsAPI.getVisitStats();
if (list) {
const tagTypes: ("primary" | "success" | "warning")[] = ["primary", "success", "warning"];
const transformedList: VisitStats[] = list.map((item, index) => ({
title: item.title,
icon: getVisitStatsIcon(item.type),
tagType: tagTypes[index % tagTypes.length],
growthRate: item.growthRate,
granularity: "日",
todayCount: item.todayCount,
totalCount: item.totalCount,
}));
visitStatsList.value = transformedList;
visitStatsLoading.value = false;
}
StatsAPI.getVisitStats()
.then((data) => {
visitStatsData.value = data;
})
.finally(() => {
visitStatsLoading.value = false;
});
};
// 格式化增长率
const formatGrowthRate = (growthRate: number): string => {
if (growthRate === 0) {
return "-";
// 根据增长率获取样式
const getGrowthRateClass = (growthRate?: number): string => {
if (!growthRate) {
return "color-[--el-color-info]";
}
const formattedRate = Math.abs(growthRate * 100)
.toFixed(2)
.replace(/\.?0+$/, "");
return formattedRate + "%";
};
/** 获取增长率文本颜色类 */
const getGrowthRateClass = (growthRate: number): string => {
if (growthRate > 0) {
return "color-[--el-color-danger]";
} else if (growthRate < 0) {
@@ -232,44 +307,25 @@ const getGrowthRateClass = (growthRate: number): string => {
}
};
/** 获取访问统计图标 */
const getVisitStatsIcon = (type: string) => {
switch (type) {
case "pv":
return "pv";
case "uv":
return "uv";
case "ip":
return "ip";
default:
return "pv";
}
const loadMyNotice = () => {
NoticeAPI.getMyNoticePage({ pageNum: 1, pageSize: 10 }).then((data) => {
notices.value = data.list;
});
};
const notices = ref<NoticePageVO[]>([]);
// 查看更多
function viewMoreNotice() {
function handleViewMoreNotice() {
router.push({ path: "/myNotice" });
}
// 阅读通知公告
function viewNoticeDetail(id: string) {
// 打开通知公告
function handleOpenNoticeDetail(id: string) {
noticeDetailRef.value.openNotice(id);
}
onMounted(() => {
loadVisitStatsData();
// 获取我的通知公告
NoticeAPI.getMyNoticePage({ pageNum: 1, pageSize: 10 }).then((data) => {
notices.value = data.list;
});
WebSocketManager.subscribeToTopic("/topic/onlineUserCount", (data) => {
console.log("收到在线用户数量:", data);
onlineUserCount.value = JSON.parse(data);
});
loadMyNotice();
});
</script>

View File

@@ -1,22 +0,0 @@
<!-- 接口文档 -->
<template>
<div class="app-container">
<iframe src="http://vapi.youlai.tech/doc.html" width="100%" height="100%" frameborder="0" />
</div>
</template>
<style lang="scss" scoped>
/** 关闭tag标签 */
.app-container {
/* 50px = navbar = 50px */
height: calc(100vh - 50px);
}
/** 开启tag标签 */
.hasTagsView {
.app-container {
/* 84px = navbar + tags-view = 50px + 34px */
height: calc(100vh - 84px);
}
}
</style>

View File

@@ -1,27 +0,0 @@
<!-- 接口文档 -->
<template>
<div class="app-container">
<iframe
src="http://vapi.youlai.tech/swagger-ui.html"
width="100%"
height="100%"
frameborder="0"
/>
</div>
</template>
<style lang="scss" scoped>
/** 关闭tag标签 */
.app-container {
/* 50px = navbar = 50px */
height: calc(100vh - 50px);
}
/** 开启tag标签 */
.hasTagsView {
.app-container {
/* 84px = navbar + tags-view = 50px + 34px */
height: calc(100vh - 84px);
}
}
</style>

View File

@@ -1,16 +0,0 @@
<template>
<div>路由参数{{ query }}</div>
</template>
<script setup lang="ts">
defineOptions({
name: "Other",
inheritAttrs: false,
});
import { useRoute } from "vue-router";
// 获取query参数
const query = useRoute().query.type as string;
</script>
<style lang="scss" scoped></style>