@@ -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 . todayUv Count } } < / span >
< span
: class = "['text-xs', 'ml-2', getGrowthRateClass(visitStatsData.uvGrowthRate)]"
>
< el-icon >
< Top v-if = "item.g rowthRate > 0" / >
< Bottom v-else-if = "item.g rowthRate < 0" / >
< Top v-if = "visitStatsData.uvG rowthRate > 0" / >
< Bottom v-else-if = "visitStatsData.uvG rowthRate < 0" / >
< / el-icon >
{ { formatGrowthRate ( item . g rowthRate) } }
{ { formatGrowthRate ( visitStatsData . uvG rowthRate) } }
< / 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 . totalUv Count } } < / 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 ="v iewMoreNotice" > 查看更多 < / span >
< span class = "text-xs" @click ="handleV iewMoreNotice" > 查看更多 < / 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 ="view NoticeDetail(item.id)" >
< el-link @click ="handleOpen NoticeDetail(item.id)" >
< el -icon class = "text-sm" > < View / > < / el-icon >
< / el-link >
< / div >
@@ -144,29 +240,20 @@ defineOptions({
inheritAttrs : false ,
} ) ;
import VisitTrend from "./components/V isitT rend.vue" ;
import VisitTrend from "./components/v isit-t rend.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 . titl e,
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 = fals e;
} ) ;
} ;
// 格式化增长率
const forma tGrowthRate = ( growthRate : number ) : string => {
if ( growthRate === 0 ) {
return "- " ;
// 根据增长率获取样式
const ge tGrowthRateClass = ( 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 v iewMoreNotice( ) {
function handleV iewMoreNotice( ) {
router . push ( { path : "/myNotice" } ) ;
}
// 阅读 通知公告
function view NoticeDetail( id : string ) {
// 打开 通知公告
function handleOpen NoticeDetail( 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 >