346 lines
12 KiB
Vue
346 lines
12 KiB
Vue
<template>
|
||
<div class="dashboard-container">
|
||
<!-- github 角标 -->
|
||
<github-corner class="github-corner" />
|
||
|
||
<el-card shadow="never">
|
||
<el-row class="h-80px">
|
||
<el-col :span="18" :xs="24">
|
||
<div class="flex-x-start">
|
||
<img
|
||
class="wh-80px rounded-full"
|
||
:src="userStore.userInfo.avatar + '?imageView2/1/w/80/h/80'"
|
||
/>
|
||
<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">
|
||
<!-- 访客数(UV) -->
|
||
<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">访客数(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">{{ visitStatsData.todayUvCount }}</span>
|
||
<span
|
||
:class="['text-xs', 'ml-2', getGrowthRateClass(visitStatsData.uvGrowthRate)]"
|
||
>
|
||
<el-icon>
|
||
<Top v-if="visitStatsData.uvGrowthRate > 0" />
|
||
<Bottom v-else-if="visitStatsData.uvGrowthRate < 0" />
|
||
</el-icon>
|
||
{{ formatGrowthRate(visitStatsData.uvGrowthRate) }}
|
||
</span>
|
||
</div>
|
||
<svg-icon icon-class="visitor" size="2em" />
|
||
</div>
|
||
|
||
<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>
|
||
</el-skeleton>
|
||
</el-col>
|
||
</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="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-4">
|
||
<DictLabel v-model="item.type" code="notice_type" size="small" />
|
||
<el-text truncated class="!mx-2 flex-1 !text-xs !text-gray">
|
||
{{ item.title }}
|
||
</el-text>
|
||
<el-link @click="handleOpenNoticeDetail(item.id)">
|
||
<el-icon class="text-sm"><View /></el-icon>
|
||
</el-link>
|
||
</div>
|
||
</el-scrollbar>
|
||
</el-card>
|
||
</el-col>
|
||
</el-row>
|
||
|
||
<NoticeDetail ref="noticeDetailRef" />
|
||
</div>
|
||
</template>
|
||
|
||
<script setup lang="ts">
|
||
defineOptions({
|
||
name: "Dashboard",
|
||
inheritAttrs: false,
|
||
});
|
||
|
||
import VisitTrend from "./components/visit-trend.vue";
|
||
|
||
import router from "@/router";
|
||
|
||
import LogAPI, { VisitStatsVO } from "@/api/system/log";
|
||
import NoticeAPI, { NoticePageVO } from "@/api/system/notice";
|
||
|
||
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(() => {
|
||
const hours = date.getHours();
|
||
if (hours >= 6 && hours < 8) {
|
||
return "晨起披衣出草堂,轩窗已自喜微凉🌅!";
|
||
} else if (hours >= 8 && hours < 12) {
|
||
return "上午好," + userStore.userInfo.nickname + "!";
|
||
} else if (hours >= 12 && hours < 18) {
|
||
return "下午好," + userStore.userInfo.nickname + "!";
|
||
} else if (hours >= 18 && hours < 24) {
|
||
return "晚上好," + userStore.userInfo.nickname + "!";
|
||
} else {
|
||
return "偷偷向银河要了一把碎星,只等你闭上眼睛撒入你的梦中,晚安🌛!";
|
||
}
|
||
});
|
||
|
||
const visitStatsLoading = ref(true);
|
||
const visitStatsData = ref<VisitStatsVO>({
|
||
todayUvCount: 0,
|
||
uvGrowthRate: 0,
|
||
totalUvCount: 0,
|
||
todayPvCount: 0,
|
||
pvGrowthRate: 0,
|
||
totalPvCount: 0,
|
||
});
|
||
|
||
// 加载访问统计数据
|
||
const loadVisitStatsData = async () => {
|
||
LogAPI.getVisitStats()
|
||
.then((data) => {
|
||
visitStatsData.value = data;
|
||
})
|
||
.finally(() => {
|
||
visitStatsLoading.value = false;
|
||
});
|
||
};
|
||
|
||
// 根据增长率获取样式
|
||
const getGrowthRateClass = (growthRate?: number): string => {
|
||
if (!growthRate) {
|
||
return "color-[--el-color-info]";
|
||
}
|
||
|
||
if (growthRate > 0) {
|
||
return "color-[--el-color-danger]";
|
||
} else if (growthRate < 0) {
|
||
return "color-[--el-color-success]";
|
||
} else {
|
||
return "color-[--el-color-info]";
|
||
}
|
||
};
|
||
|
||
const loadMyNotice = () => {
|
||
NoticeAPI.getMyNoticePage({ pageNum: 1, pageSize: 10 }).then((data) => {
|
||
notices.value = data.list;
|
||
});
|
||
};
|
||
|
||
// 查看更多
|
||
function handleViewMoreNotice() {
|
||
router.push({ path: "/myNotice" });
|
||
}
|
||
|
||
// 打开通知公告
|
||
function handleOpenNoticeDetail(id: string) {
|
||
noticeDetailRef.value.openNotice(id);
|
||
}
|
||
|
||
onMounted(() => {
|
||
loadVisitStatsData();
|
||
loadMyNotice();
|
||
});
|
||
</script>
|
||
|
||
<style lang="scss" scoped>
|
||
.dashboard-container {
|
||
position: relative;
|
||
padding: 24px;
|
||
|
||
.github-corner {
|
||
position: absolute;
|
||
top: 0;
|
||
right: 0;
|
||
z-index: 1;
|
||
border: 0;
|
||
}
|
||
}
|
||
</style>
|