feat: 优化仪表盘界面样式和交互细节
This commit is contained in:
@@ -1,7 +1,7 @@
|
||||
<template>
|
||||
<div class="dashboard-container">
|
||||
<div class="relative p-6">
|
||||
<!-- github 角标 -->
|
||||
<github-corner class="github-corner" />
|
||||
<github-corner class="absolute top-0 right-0 z-1 border-0" />
|
||||
|
||||
<el-card shadow="never" class="mt-2">
|
||||
<div class="flex flex-wrap">
|
||||
@@ -15,7 +15,9 @@
|
||||
/>
|
||||
</div>
|
||||
<div class="ml-5">
|
||||
<p>{{ greetings }}</p>
|
||||
<p class="text-base font-semibold text-[--el-text-color-primary] leading-tight">
|
||||
{{ greetings }}
|
||||
</p>
|
||||
<p class="text-sm text-gray">今日天气晴朗,气温在15℃至25℃之间,东南风。</p>
|
||||
</div>
|
||||
</div>
|
||||
@@ -121,34 +123,45 @@
|
||||
<el-row :gutter="10" class="mt-5">
|
||||
<!-- 在线用户数量 -->
|
||||
<el-col :span="8" :xs="24" class="mb-xs-3">
|
||||
<el-card shadow="never" class="h-full flex flex-col">
|
||||
<el-card
|
||||
shadow="never"
|
||||
class="h-full transition-all duration-200 hover:-translate-y-0.5 hover:shadow-lg"
|
||||
>
|
||||
<template #header>
|
||||
<div class="flex-x-between">
|
||||
<span class="text-gray">在线用户</span>
|
||||
<el-tag type="danger" size="small">实时</el-tag>
|
||||
<span class="text-xs font-medium text-[--el-text-color-secondary]">在线用户</span>
|
||||
<div class="flex items-center gap-2">
|
||||
<span
|
||||
class="inline-flex items-center gap-1.5 px-2.5 py-0.5 text-xs leading-5 rounded-full border select-none"
|
||||
:class="wsStatusClass"
|
||||
>
|
||||
<el-icon class="text-sm">
|
||||
<Loading
|
||||
v-if="
|
||||
!isConnected &&
|
||||
(connectionState === 'CONNECTING' || connectionState === 'RECONNECTING')
|
||||
"
|
||||
/>
|
||||
<CircleCheck v-else-if="isConnected" />
|
||||
<CircleClose v-else />
|
||||
</el-icon>
|
||||
<span class="text-[--el-text-color-secondary]">WebSocket</span>
|
||||
<span class="font-medium">{{ wsStatusText }}</span>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<div class="flex-x-between mt-2 flex-1">
|
||||
<div class="flex-y-center">
|
||||
<span class="text-lg transition-all duration-300 hover:scale-110">
|
||||
{{ onlineUserCount }}
|
||||
</span>
|
||||
<span v-if="isConnected" class="ml-2 text-xs text-[#67c23a]">
|
||||
<el-icon><Connection /></el-icon>
|
||||
已连接
|
||||
</span>
|
||||
<span v-else class="ml-2 text-xs text-[#f56c6c]">
|
||||
<el-icon><Failed /></el-icon>
|
||||
未连接
|
||||
</span>
|
||||
<div class="mt-2 flex-1 flex items-end">
|
||||
<div class="flex items-baseline gap-1.5">
|
||||
<span class="text-xl font-semibold tracking-wide">{{ onlineUserCount }}</span>
|
||||
<span class="text-xs text-[--el-text-color-secondary]">人</span>
|
||||
</div>
|
||||
<div class="i-svg:people w-8 h-8 animate-[pulse_2s_infinite]" />
|
||||
</div>
|
||||
|
||||
<div class="flex-x-between mt-2 text-sm text-gray">
|
||||
<span>更新时间</span>
|
||||
<span>{{ formattedTime }}</span>
|
||||
<div class="mt-2 flex justify-between items-center">
|
||||
<span class="text-sm text-gray">更新时间</span>
|
||||
<span class="text-sm">{{ formattedTime }}</span>
|
||||
</div>
|
||||
</el-card>
|
||||
</el-col>
|
||||
@@ -176,37 +189,45 @@
|
||||
</el-card>
|
||||
</template>
|
||||
<template v-if="!visitStatsLoading">
|
||||
<el-card shadow="never" class="h-full flex flex-col">
|
||||
<el-card
|
||||
shadow="never"
|
||||
class="h-full transition-all duration-200 hover:-translate-y-0.5 hover:shadow-lg"
|
||||
>
|
||||
<template #header>
|
||||
<div class="flex-x-between">
|
||||
<span class="text-gray">访客数(UV)</span>
|
||||
<span class="text-xs font-medium text-[--el-text-color-secondary]">
|
||||
访客数 UV
|
||||
</span>
|
||||
<el-tag type="success" size="small">日</el-tag>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<div class="flex-x-between mt-2 flex-1">
|
||||
<div class="flex-y-center">
|
||||
<span class="text-lg">{{ displayTransitionUvCount }}</span>
|
||||
<div class="mt-2 flex-1 flex items-end">
|
||||
<div class="flex items-baseline gap-1.5">
|
||||
<span class="text-xl font-semibold tracking-wide">
|
||||
{{ displayTransitionUvCount }}
|
||||
</span>
|
||||
<span
|
||||
:class="[
|
||||
'text-xs',
|
||||
'ml-2',
|
||||
computeGrowthRateClass(visitStatsData.uvGrowthRate),
|
||||
]"
|
||||
v-if="uvGrowthText !== null"
|
||||
:class="['text-xs', computeGrowthRateClass(visitStatsData.uvGrowthRate)]"
|
||||
>
|
||||
<el-icon>
|
||||
<el-icon
|
||||
v-if="
|
||||
visitStatsData.uvGrowthRate !== undefined &&
|
||||
visitStatsData.uvGrowthRate !== null
|
||||
"
|
||||
>
|
||||
<Top v-if="visitStatsData.uvGrowthRate > 0" />
|
||||
<Bottom v-else-if="visitStatsData.uvGrowthRate < 0" />
|
||||
</el-icon>
|
||||
{{ formatGrowthRate(visitStatsData.uvGrowthRate) }}
|
||||
{{ uvGrowthText }}
|
||||
</span>
|
||||
</div>
|
||||
<div class="i-svg:visitor w-8 h-8" />
|
||||
</div>
|
||||
|
||||
<div class="flex-x-between mt-2 text-sm text-gray">
|
||||
<span>总访客数</span>
|
||||
<span>{{ displayTransitionTotalUvCount }}</span>
|
||||
<div class="mt-2 flex justify-between items-center">
|
||||
<span class="text-sm text-gray">总访客数</span>
|
||||
<span class="text-sm">{{ displayTransitionTotalUvCount }}</span>
|
||||
</div>
|
||||
</el-card>
|
||||
</template>
|
||||
@@ -236,37 +257,45 @@
|
||||
</el-card>
|
||||
</template>
|
||||
<template v-if="!visitStatsLoading">
|
||||
<el-card shadow="never" class="h-full flex flex-col">
|
||||
<el-card
|
||||
shadow="never"
|
||||
class="h-full transition-all duration-200 hover:-translate-y-0.5 hover:shadow-lg"
|
||||
>
|
||||
<template #header>
|
||||
<div class="flex-x-between">
|
||||
<span class="text-gray">浏览量(PV)</span>
|
||||
<span class="text-xs font-medium text-[--el-text-color-secondary]">
|
||||
浏览量 PV
|
||||
</span>
|
||||
<el-tag type="primary" size="small">日</el-tag>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<div class="flex-x-between mt-2 flex-1">
|
||||
<div class="flex-y-center">
|
||||
<span class="text-lg">{{ displayTransitionPvCount }}</span>
|
||||
<div class="mt-2 flex-1 flex items-end">
|
||||
<div class="flex items-baseline gap-1.5">
|
||||
<span class="text-xl font-semibold tracking-wide">
|
||||
{{ displayTransitionPvCount }}
|
||||
</span>
|
||||
<span
|
||||
:class="[
|
||||
'text-xs',
|
||||
'ml-2',
|
||||
computeGrowthRateClass(visitStatsData.pvGrowthRate),
|
||||
]"
|
||||
v-if="pvGrowthText !== null"
|
||||
:class="['text-xs', computeGrowthRateClass(visitStatsData.pvGrowthRate)]"
|
||||
>
|
||||
<el-icon>
|
||||
<el-icon
|
||||
v-if="
|
||||
visitStatsData.pvGrowthRate !== undefined &&
|
||||
visitStatsData.pvGrowthRate !== null
|
||||
"
|
||||
>
|
||||
<Top v-if="visitStatsData.pvGrowthRate > 0" />
|
||||
<Bottom v-else-if="visitStatsData.pvGrowthRate < 0" />
|
||||
</el-icon>
|
||||
{{ formatGrowthRate(visitStatsData.pvGrowthRate) }}
|
||||
{{ pvGrowthText }}
|
||||
</span>
|
||||
</div>
|
||||
<div class="i-svg:browser w-8 h-8" />
|
||||
</div>
|
||||
|
||||
<div class="flex-x-between mt-2 text-sm text-gray">
|
||||
<span>总浏览量</span>
|
||||
<span>{{ displayTransitionTotalPvCount }}</span>
|
||||
<div class="mt-2 flex justify-between items-center">
|
||||
<span class="text-sm text-gray">总浏览量</span>
|
||||
<span class="text-sm">{{ displayTransitionTotalPvCount }}</span>
|
||||
</div>
|
||||
</el-card>
|
||||
</template>
|
||||
@@ -295,7 +324,7 @@
|
||||
<el-card>
|
||||
<template #header>
|
||||
<div class="flex-x-between">
|
||||
<span class="header-title">最新动态</span>
|
||||
<span class="font-semibold">最新动态</span>
|
||||
<el-link
|
||||
type="primary"
|
||||
underline="never"
|
||||
@@ -303,7 +332,7 @@
|
||||
target="_blank"
|
||||
>
|
||||
完整记录
|
||||
<el-icon class="link-icon"><TopRight /></el-icon>
|
||||
<el-icon class="ml-0.5"><TopRight /></el-icon>
|
||||
</el-link>
|
||||
</div>
|
||||
</template>
|
||||
@@ -319,15 +348,23 @@
|
||||
:hollow="index !== 0"
|
||||
size="large"
|
||||
>
|
||||
<div class="version-item" :class="{ 'latest-item': index === 0 }">
|
||||
<div>
|
||||
<div
|
||||
class="p-4 mb-3 bg-[--el-fill-color-lighter] rounded-lg transition-all duration-200 hover:translate-x-1"
|
||||
:class="{
|
||||
'bg-[--el-color-primary-light-9]! border border-[--el-color-primary-light-5]':
|
||||
index === 0,
|
||||
}"
|
||||
>
|
||||
<div class="flex items-center gap-2">
|
||||
<el-text tag="strong">{{ item.title }}</el-text>
|
||||
<el-tag v-if="item.tag" :type="index === 0 ? 'success' : 'info'" size="small">
|
||||
{{ item.tag }}
|
||||
</el-tag>
|
||||
</div>
|
||||
|
||||
<el-text class="version-content">{{ item.content }}</el-text>
|
||||
<el-text class="mb-3 text-xs leading-relaxed text-[--el-text-color-secondary]">
|
||||
{{ item.content }}
|
||||
</el-text>
|
||||
|
||||
<div v-if="item.link">
|
||||
<el-link
|
||||
@@ -337,7 +374,7 @@
|
||||
underline="never"
|
||||
>
|
||||
详情
|
||||
<el-icon class="link-icon"><TopRight /></el-icon>
|
||||
<el-icon class="ml-0.5"><TopRight /></el-icon>
|
||||
</el-link>
|
||||
</div>
|
||||
</div>
|
||||
@@ -363,21 +400,11 @@ import type { VisitStatsDetail, VisitTrendDetail } from "@/types/api";
|
||||
import { useUserStore } from "@/store/modules/user";
|
||||
import { formatGrowthRate } from "@/utils";
|
||||
import { useTransition, useDateFormat } from "@vueuse/core";
|
||||
import { Connection, Failed } from "@element-plus/icons-vue";
|
||||
import { CircleCheck, CircleClose, Loading } from "@element-plus/icons-vue";
|
||||
import { useOnlineCount } from "@/composables";
|
||||
|
||||
// 在线用户数量组件相关
|
||||
const { onlineUserCount, lastUpdateTime, isConnected } = useOnlineCount();
|
||||
|
||||
// 记录上一次的用户数量用于计算趋势
|
||||
const previousCount = ref(0);
|
||||
|
||||
// 监听用户数量变化,计算趋势
|
||||
watch(onlineUserCount, (newCount, oldCount) => {
|
||||
if (oldCount > 0) {
|
||||
previousCount.value = oldCount;
|
||||
}
|
||||
});
|
||||
const { onlineUserCount, lastUpdateTime, isConnected, connectionState } = useOnlineCount();
|
||||
|
||||
// 格式化时间戳
|
||||
const formattedTime = computed(() => {
|
||||
@@ -385,6 +412,23 @@ const formattedTime = computed(() => {
|
||||
return useDateFormat(lastUpdateTime, "HH:mm:ss").value;
|
||||
});
|
||||
|
||||
const wsStatusText = computed(() => {
|
||||
if (!isConnected.value) {
|
||||
return connectionState.value === "CONNECTING" || connectionState.value === "RECONNECTING"
|
||||
? "连接中"
|
||||
: "未连接";
|
||||
}
|
||||
return "已连接";
|
||||
});
|
||||
|
||||
const wsStatusClass = computed(() => {
|
||||
if (isConnected.value)
|
||||
return "text-[--el-color-success] bg-[--el-color-success-light-9] border-[--el-color-success-light-7]";
|
||||
return connectionState.value === "CONNECTING" || connectionState.value === "RECONNECTING"
|
||||
? "text-[--el-color-warning] bg-[--el-color-warning-light-9] border-[--el-color-warning-light-7]"
|
||||
: "text-[--el-color-danger] bg-[--el-color-danger-light-9] border-[--el-color-danger-light-7]";
|
||||
});
|
||||
|
||||
interface VersionItem {
|
||||
id: string;
|
||||
title: string; // 版本标题(如:v2.4.0)
|
||||
@@ -456,6 +500,26 @@ const visitStatsData = ref<VisitStatsDetail>({
|
||||
totalPvCount: 0,
|
||||
});
|
||||
|
||||
const uvGrowthText = computed(() => {
|
||||
if (
|
||||
visitStatsData.value.uvGrowthRate === undefined ||
|
||||
visitStatsData.value.uvGrowthRate === null
|
||||
) {
|
||||
return "--";
|
||||
}
|
||||
return formatGrowthRate(visitStatsData.value.uvGrowthRate);
|
||||
});
|
||||
|
||||
const pvGrowthText = computed(() => {
|
||||
if (
|
||||
visitStatsData.value.pvGrowthRate === undefined ||
|
||||
visitStatsData.value.pvGrowthRate === null
|
||||
) {
|
||||
return "--";
|
||||
}
|
||||
return formatGrowthRate(visitStatsData.value.pvGrowthRate);
|
||||
});
|
||||
|
||||
// 数字过渡动画
|
||||
const transitionUvCount = useTransition(
|
||||
computed(() => visitStatsData.value.todayUvCount),
|
||||
@@ -640,38 +704,5 @@ onMounted(() => {
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.dashboard-container {
|
||||
position: relative;
|
||||
padding: 24px;
|
||||
|
||||
.github-corner {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
right: 0;
|
||||
z-index: 1;
|
||||
border: 0;
|
||||
}
|
||||
|
||||
.version-item {
|
||||
padding: 16px;
|
||||
margin-bottom: 12px;
|
||||
background: var(--el-fill-color-lighter);
|
||||
border-radius: 8px;
|
||||
transition: all 0.2s;
|
||||
|
||||
&.latest-item {
|
||||
background: var(--el-color-primary-light-9);
|
||||
border: 1px solid var(--el-color-primary-light-5);
|
||||
}
|
||||
&:hover {
|
||||
transform: translateX(5px);
|
||||
}
|
||||
.version-content {
|
||||
margin-bottom: 12px;
|
||||
font-size: 13px;
|
||||
line-height: 1.5;
|
||||
color: var(--el-text-color-secondary);
|
||||
}
|
||||
}
|
||||
}
|
||||
// 暂无自定义样式
|
||||
</style>
|
||||
|
||||
Reference in New Issue
Block a user