7171 </el-card >
7272 </div >
7373 </div >
74+
75+ <!-- 薪资排行榜 -->
76+ <div class =" ranking-section" >
77+ <div class =" section-header" >
78+ <h2 class =" section-title" >薪资排行榜</h2 >
79+ <div class =" section-subtitle" >群雄逐鹿 · 谁主沉浮</div >
80+ </div >
81+
82+ <div v-loading =" rankingLoading" class =" ranking-content" >
83+ <div v-if =" rankingList.length === 0 && !rankingLoading" class =" no-ranking" >
84+ <el-empty description =" 暂无排行榜数据" />
85+ </div >
86+
87+ <div v-else class =" ranking-grid" >
88+ <div
89+ v-for =" (column, columnIndex) in rankingColumns"
90+ :key =" columnIndex"
91+ class =" ranking-column"
92+ >
93+ <div class =" column-header" >
94+ <span class =" column-title" >第{{ columnIndex * 10 + 1 }}-{{ Math.min((columnIndex + 1) * 10, rankingList.length) }}名</span >
95+ </div >
96+
97+ <div class =" ranking-items" >
98+ <div
99+ v-for =" user in column"
100+ :key =" user.id"
101+ class =" ranking-item"
102+ :class =" getRankingClass(user.rank)"
103+ >
104+ <div class =" ranking-number" >{{ user.rank }}</div >
105+ <el-avatar
106+ :src =" user.avatar"
107+ :size =" 40"
108+ class =" ranking-avatar"
109+ >
110+ <el-icon ><User /></el-icon >
111+ </el-avatar >
112+ <div class =" ranking-info" >
113+ <div class =" ranking-name" >{{ user.nickname }}</div >
114+ <div class =" ranking-salary" >¥{{ user.salary?.toLocaleString() || 0 }}/月</div >
115+ </div >
116+ <div v-if =" user.rank <= 3" class =" ranking-medal" >
117+ <el-icon v-if =" user.rank === 1" class =" medal gold" ><Trophy /></el-icon >
118+ <el-icon v-else-if =" user.rank === 2" class =" medal silver" ><Trophy /></el-icon >
119+ <el-icon v-else-if =" user.rank === 3" class =" medal bronze" ><Trophy /></el-icon >
120+ </div >
121+ </div >
122+ </div >
123+ </div >
124+ </div >
125+ </div >
126+ </div >
74127 </div >
75128 </div >
76129</template >
77130
78131<script setup>
79- import { computed } from ' vue'
132+ import { computed , ref , onMounted } from ' vue'
80133import { useRouter } from ' vue-router'
81134import { useUserStore } from ' ../stores/user'
135+ import { getSalaryRanking } from ' ../api/ranking'
136+ import { ElMessage } from ' element-plus'
82137import {
83138 KnifeFork ,
84139 Coin ,
85140 MagicStick ,
86141 TrendCharts ,
87- Guide
142+ Guide ,
143+ User ,
144+ Trophy
88145} from ' @element-plus/icons-vue'
89146import bannerImage from ' ../assets/banner.png'
90147import GlobalNavbar from ' ../components/GlobalNavbar.vue'
@@ -95,6 +152,45 @@ const userStore = useUserStore()
95152const isLoggedIn = computed (() => userStore .isLoggedIn )
96153const user = computed (() => userStore .user )
97154
155+ // 排行榜相关数据
156+ const rankingList = ref ([])
157+ const rankingLoading = ref (false )
158+
159+ // 计算排行榜分列显示
160+ const rankingColumns = computed (() => {
161+ const columns = []
162+ for (let i = 0 ; i < 3 ; i++ ) {
163+ const start = i * 10
164+ const end = Math .min (start + 10 , rankingList .value .length )
165+ if (start < rankingList .value .length ) {
166+ columns .push (rankingList .value .slice (start, end))
167+ }
168+ }
169+ return columns
170+ })
171+
172+ // 获取排名样式类
173+ const getRankingClass = (rank ) => {
174+ if (rank === 1 ) return ' rank-first'
175+ if (rank === 2 ) return ' rank-second'
176+ if (rank === 3 ) return ' rank-third'
177+ return ' '
178+ }
179+
180+ // 加载排行榜数据
181+ const loadRanking = async () => {
182+ rankingLoading .value = true
183+ try {
184+ const data = await getSalaryRanking (30 )
185+ rankingList .value = data || []
186+ } catch (error) {
187+ console .error (' 获取排行榜失败:' , error)
188+ ElMessage .error (' 获取排行榜失败' )
189+ } finally {
190+ rankingLoading .value = false
191+ }
192+ }
193+
98194const handleLogout = async () => {
99195 await userStore .logoutUser ()
100196 router .push (' /' )
@@ -113,6 +209,11 @@ const handleChallengeClick = () => {
113209 router .push (' /login' )
114210 }
115211}
212+
213+ // 组件挂载时加载排行榜
214+ onMounted (() => {
215+ loadRanking ()
216+ })
116217 </script >
117218
118219<style scoped>
@@ -380,6 +481,214 @@ const handleChallengeClick = () => {
380481 opacity : 0.2 ;
381482}
382483
484+ /* 排行榜样式 */
485+ .ranking-section {
486+ margin-top : 80px ;
487+ }
488+
489+ .ranking-content {
490+ min-height : 300px ;
491+ }
492+
493+ .no-ranking {
494+ display : flex ;
495+ justify-content : center ;
496+ align-items : center ;
497+ height : 300px ;
498+ }
499+
500+ .ranking-grid {
501+ display : grid ;
502+ grid-template-columns : repeat (auto-fit , minmax (350px , 1fr ));
503+ gap : 30px ;
504+ margin-top : 40px ;
505+ }
506+
507+ .ranking-column {
508+ background : var (--bg-card );
509+ border : 2px solid var (--border-light );
510+ border-radius : 16px ;
511+ overflow : hidden ;
512+ box-shadow : 0 8px 24px var (--shadow-medium );
513+ transition : all 0.3s ease ;
514+ }
515+
516+ .ranking-column :hover {
517+ transform : translateY (-4px );
518+ box-shadow : 0 12px 32px var (--shadow-heavy );
519+ border-color : var (--accent-gold );
520+ }
521+
522+ .column-header {
523+ background : linear-gradient (135deg , var (--primary-brown ) 0% , var (--secondary-brown ) 100% );
524+ color : var (--bg-card );
525+ padding : 16px 20px ;
526+ text-align : center ;
527+ position : relative ;
528+ }
529+
530+ .column-header ::after {
531+ content : ' ' ;
532+ position : absolute ;
533+ bottom : 0 ;
534+ left : 0 ;
535+ right : 0 ;
536+ height : 3px ;
537+ background : linear-gradient (90deg , var (--accent-gold ) 0% , var (--accent-copper ) 100% );
538+ }
539+
540+ .column-title {
541+ font-size : 16px ;
542+ font-weight : 600 ;
543+ letter-spacing : 1px ;
544+ }
545+
546+ .ranking-items {
547+ padding : 20px ;
548+ }
549+
550+ .ranking-item {
551+ display : flex ;
552+ align-items : center ;
553+ padding : 12px 16px ;
554+ margin-bottom : 12px ;
555+ background : var (--bg-secondary );
556+ border : 1px solid var (--border-light );
557+ border-radius : 12px ;
558+ transition : all 0.3s ease ;
559+ position : relative ;
560+ overflow : hidden ;
561+ }
562+
563+ .ranking-item ::before {
564+ content : ' ' ;
565+ position : absolute ;
566+ top : 0 ;
567+ left : -100% ;
568+ width : 100% ;
569+ height : 100% ;
570+ background : linear-gradient (90deg , transparent , rgba (218 , 165 , 32 , 0.1 ), transparent );
571+ transition : left 0.6s ease ;
572+ }
573+
574+ .ranking-item :hover ::before {
575+ left : 100% ;
576+ }
577+
578+ .ranking-item :hover {
579+ transform : translateX (4px );
580+ border-color : var (--accent-gold );
581+ box-shadow : 0 4px 12px var (--shadow-light );
582+ }
583+
584+ .ranking-item :last-child {
585+ margin-bottom : 0 ;
586+ }
587+
588+ .ranking-item.rank-first {
589+ background : linear-gradient (135deg , rgba (255 , 215 , 0 , 0.1 ) 0% , rgba (255 , 193 , 7 , 0.05 ) 100% );
590+ border-color : #FFD700 ;
591+ }
592+
593+ .ranking-item.rank-second {
594+ background : linear-gradient (135deg , rgba (192 , 192 , 192 , 0.1 ) 0% , rgba (169 , 169 , 169 , 0.05 ) 100% );
595+ border-color : #C0C0C0 ;
596+ }
597+
598+ .ranking-item.rank-third {
599+ background : linear-gradient (135deg , rgba (205 , 127 , 50 , 0.1 ) 0% , rgba (184 , 115 , 51 , 0.05 ) 100% );
600+ border-color : #CD7F32 ;
601+ }
602+
603+ .ranking-number {
604+ width : 32px ;
605+ height : 32px ;
606+ background : var (--primary-brown );
607+ color : var (--bg-card );
608+ border-radius : 50% ;
609+ display : flex ;
610+ align-items : center ;
611+ justify-content : center ;
612+ font-weight : 600 ;
613+ font-size : 14px ;
614+ margin-right : 12px ;
615+ flex-shrink : 0 ;
616+ }
617+
618+ .rank-first .ranking-number {
619+ background : linear-gradient (135deg , #FFD700 0% , #FFA500 100% );
620+ color : #333 ;
621+ }
622+
623+ .rank-second .ranking-number {
624+ background : linear-gradient (135deg , #C0C0C0 0% , #A9A9A9 100% );
625+ color : #333 ;
626+ }
627+
628+ .rank-third .ranking-number {
629+ background : linear-gradient (135deg , #CD7F32 0% , #B8733B 100% );
630+ color : #fff ;
631+ }
632+
633+ .ranking-avatar {
634+ margin-right : 12px ;
635+ border : 2px solid var (--border-light );
636+ flex-shrink : 0 ;
637+ }
638+
639+ .rank-first .ranking-avatar {
640+ border-color : #FFD700 ;
641+ }
642+
643+ .rank-second .ranking-avatar {
644+ border-color : #C0C0C0 ;
645+ }
646+
647+ .rank-third .ranking-avatar {
648+ border-color : #CD7F32 ;
649+ }
650+
651+ .ranking-info {
652+ flex : 1 ;
653+ min-width : 0 ;
654+ }
655+
656+ .ranking-name {
657+ font-weight : 600 ;
658+ color : var (--text-primary );
659+ margin-bottom : 4px ;
660+ overflow : hidden ;
661+ text-overflow : ellipsis ;
662+ white-space : nowrap ;
663+ }
664+
665+ .ranking-salary {
666+ color : var (--accent-gold );
667+ font-weight : 600 ;
668+ font-size : 14px ;
669+ }
670+
671+ .ranking-medal {
672+ margin-left : 8px ;
673+ flex-shrink : 0 ;
674+ }
675+
676+ .medal {
677+ font-size : 20px ;
678+ }
679+
680+ .medal.gold {
681+ color : #FFD700 ;
682+ }
683+
684+ .medal.silver {
685+ color : #C0C0C0 ;
686+ }
687+
688+ .medal.bronze {
689+ color : #CD7F32 ;
690+ }
691+
383692/* 响应式设计 */
384693@media (max-width : 768px ) {
385694
@@ -396,6 +705,32 @@ const handleChallengeClick = () => {
396705 gap : 30px ;
397706 }
398707
708+ .ranking-grid {
709+ grid-template-columns : 1fr ;
710+ gap : 20px ;
711+ }
712+
713+ .ranking-column {
714+ margin-bottom : 20px ;
715+ }
716+
717+ .ranking-item {
718+ padding : 10px 12px ;
719+ }
720+
721+ .ranking-avatar {
722+ width : 36px !important ;
723+ height : 36px !important ;
724+ }
725+
726+ .ranking-name {
727+ font-size : 14px ;
728+ }
729+
730+ .ranking-salary {
731+ font-size : 13px ;
732+ }
733+
399734 .main-content {
400735 padding : 60px 20px ;
401736 }
0 commit comments