2025-08-12 14:39:25 +08:00
|
|
|
<script setup lang="ts">
|
2025-08-15 15:56:13 +08:00
|
|
|
import { ref, onMounted } from "vue";
|
|
|
|
import { majorRanking, highSchoolRanking } from "@/api";
|
|
|
|
import top1Img from "@/assets/img/zheke/top1.png";
|
|
|
|
import top2Img from "@/assets/img/zheke/top2.png";
|
|
|
|
import top3Img from "@/assets/img/zheke/top3.png";
|
|
|
|
import top4Img from "@/assets/img/zheke/top4.png";
|
2025-08-12 14:39:25 +08:00
|
|
|
|
|
|
|
interface RankingItem {
|
2025-08-15 15:56:13 +08:00
|
|
|
rank: number;
|
|
|
|
name: string;
|
|
|
|
count: number;
|
|
|
|
}
|
|
|
|
|
|
|
|
interface MajorRankingItem {
|
2025-08-12 14:39:25 +08:00
|
|
|
rank: number;
|
|
|
|
major: string;
|
|
|
|
count: number;
|
|
|
|
}
|
|
|
|
|
2025-08-15 15:56:13 +08:00
|
|
|
interface SchoolRankingItem {
|
|
|
|
rank: number;
|
|
|
|
school: string;
|
|
|
|
count: number;
|
|
|
|
}
|
|
|
|
|
2025-08-12 14:39:25 +08:00
|
|
|
// 排行数据
|
|
|
|
const rankingData = ref<RankingItem[]>([]);
|
|
|
|
|
2025-08-15 15:56:13 +08:00
|
|
|
// 排行类型
|
|
|
|
const rankingTypes = [
|
|
|
|
{ label: "用户意向专业排行", value: "major" },
|
|
|
|
{ label: "用户生源高中排行", value: "school" },
|
|
|
|
];
|
|
|
|
const selectedRankingType = ref("major");
|
|
|
|
|
2025-08-12 14:39:25 +08:00
|
|
|
// 获取排行数据
|
|
|
|
const fetchRankingData = () => {
|
2025-08-15 15:56:13 +08:00
|
|
|
if (selectedRankingType.value === "major") {
|
|
|
|
majorRanking().then((res) => {
|
|
|
|
if (res.success) {
|
|
|
|
// 转换数据格式
|
|
|
|
rankingData.value = res.data.map((item: MajorRankingItem) => ({
|
|
|
|
rank: item.rank,
|
|
|
|
name: item.major,
|
|
|
|
count: item.count,
|
|
|
|
}));
|
|
|
|
}
|
|
|
|
});
|
|
|
|
} else {
|
|
|
|
highSchoolRanking().then((res) => {
|
|
|
|
if (res.success) {
|
|
|
|
// 转换数据格式
|
|
|
|
rankingData.value = res.data.map((item: SchoolRankingItem) => ({
|
|
|
|
rank: item.rank,
|
|
|
|
name: item.school,
|
|
|
|
count: item.count,
|
|
|
|
}));
|
|
|
|
}
|
|
|
|
});
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
// 处理排行类型变化
|
|
|
|
const handleRankingTypeChange = () => {
|
|
|
|
fetchRankingData();
|
|
|
|
};
|
|
|
|
|
|
|
|
// 计算最大用户数,用于进度条百分比计算
|
|
|
|
const maxCount = ref(0);
|
|
|
|
const getProgressWidth = (count: number) => {
|
|
|
|
if (maxCount.value === 0) return "0%";
|
|
|
|
return `${(count / maxCount.value) * 100}%`;
|
|
|
|
};
|
|
|
|
|
|
|
|
// 监听数据变化,计算最大值
|
|
|
|
const updateMaxCount = () => {
|
|
|
|
if (rankingData.value.length > 0) {
|
|
|
|
maxCount.value = Math.max(...rankingData.value.map((item) => item.count));
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
// 模拟数据
|
|
|
|
const initMockData = () => {
|
|
|
|
const mockData = [];
|
|
|
|
for (let i = 1; i <= 10; i++) {
|
|
|
|
mockData.push({
|
|
|
|
rank: i,
|
|
|
|
name:
|
|
|
|
selectedRankingType.value === "major" ? "视觉传达设计" : "视觉传达设计",
|
|
|
|
count: 3954,
|
|
|
|
});
|
|
|
|
}
|
|
|
|
rankingData.value = mockData;
|
|
|
|
updateMaxCount();
|
|
|
|
};
|
|
|
|
|
|
|
|
// 年份筛选选项
|
|
|
|
const yearOptions = ref([{ label: "年份筛选", value: "all" }]);
|
|
|
|
|
|
|
|
// 学院筛选
|
|
|
|
const selectedYear = ref("all");
|
|
|
|
|
|
|
|
// 处理年份筛选变化
|
|
|
|
const handleYearChange = () => {
|
|
|
|
console.log("年份筛选");
|
|
|
|
};
|
|
|
|
|
|
|
|
// 获取排名图片
|
|
|
|
const getRankImage = (rank: number) => {
|
|
|
|
switch (rank) {
|
|
|
|
case 1: return top1Img;
|
|
|
|
case 2: return top2Img;
|
|
|
|
case 3: return top3Img;
|
|
|
|
default: return top4Img;
|
|
|
|
}
|
2025-08-12 14:39:25 +08:00
|
|
|
};
|
|
|
|
|
|
|
|
onMounted(() => {
|
2025-08-15 15:56:13 +08:00
|
|
|
// 获取真实数据
|
2025-08-12 14:39:25 +08:00
|
|
|
fetchRankingData();
|
2025-08-15 15:56:13 +08:00
|
|
|
|
|
|
|
// 如果没有真实数据,使用模拟数据
|
|
|
|
setTimeout(() => {
|
|
|
|
if (rankingData.value.length === 0) {
|
|
|
|
initMockData();
|
|
|
|
} else {
|
|
|
|
updateMaxCount();
|
|
|
|
}
|
|
|
|
}, 500);
|
2025-08-12 14:39:25 +08:00
|
|
|
});
|
|
|
|
</script>
|
|
|
|
|
|
|
|
<template>
|
2025-08-15 15:56:13 +08:00
|
|
|
<div class="ranking-container">
|
|
|
|
<!-- 排行类型切换 -->
|
|
|
|
<div class="ranking-tabs">
|
|
|
|
<div
|
|
|
|
v-for="type in rankingTypes"
|
|
|
|
:key="type.value"
|
|
|
|
class="tab-item"
|
|
|
|
:class="{ active: selectedRankingType === type.value }"
|
|
|
|
@click="
|
|
|
|
selectedRankingType = type.value;
|
|
|
|
handleRankingTypeChange();
|
|
|
|
"
|
|
|
|
>
|
|
|
|
{{ type.label }}
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
|
|
|
|
<!-- 排行榜内容 -->
|
2025-08-12 14:39:25 +08:00
|
|
|
<div class="ranking-list">
|
2025-08-15 15:56:13 +08:00
|
|
|
<!-- 标题装饰 -->
|
|
|
|
<div class="ranking-title">
|
|
|
|
<img class="title-bg" src="@/assets/img/zheke/title-bg.png" alt="" />
|
|
|
|
<img
|
|
|
|
class="title-line"
|
|
|
|
src="@/assets/img/zheke/title-line.png"
|
|
|
|
alt=""
|
|
|
|
/>
|
|
|
|
<div class="title-inner">
|
|
|
|
{{
|
|
|
|
selectedRankingType === "major"
|
|
|
|
? "用户意向专业排行"
|
|
|
|
: "用户生源高中排行"
|
|
|
|
}}
|
|
|
|
</div>
|
|
|
|
<!-- 学院筛选 -->
|
|
|
|
<div class="filter-item">
|
|
|
|
<el-select
|
|
|
|
v-model="selectedYear"
|
|
|
|
size="small"
|
|
|
|
class="filter-select"
|
|
|
|
@change="handleYearChange"
|
|
|
|
>
|
|
|
|
<el-option
|
|
|
|
v-for="item in yearOptions"
|
|
|
|
:key="item.value"
|
|
|
|
:label="item.label"
|
|
|
|
:value="item.value"
|
|
|
|
/>
|
|
|
|
</el-select>
|
|
|
|
</div>
|
2025-08-12 14:39:25 +08:00
|
|
|
</div>
|
2025-08-15 15:56:13 +08:00
|
|
|
<div
|
|
|
|
v-for="(item, index) in rankingData"
|
|
|
|
:key="index"
|
|
|
|
class="ranking-item"
|
|
|
|
>
|
|
|
|
<div class="item-content">
|
|
|
|
<div
|
|
|
|
class="rank-badge"
|
|
|
|
:class="`topItem-${item.rank <= 3 ? item.rank : 'other'}`"
|
|
|
|
>
|
|
|
|
<img
|
|
|
|
:src="getRankImage(item.rank)"
|
|
|
|
:alt="`top${item.rank}`"
|
|
|
|
class="rank-bg"
|
|
|
|
/>
|
|
|
|
<span class="rank-text">TOP {{ item.rank }}</span>
|
2025-08-12 14:39:25 +08:00
|
|
|
</div>
|
2025-08-15 15:56:13 +08:00
|
|
|
<div class="item-name">{{ item.name }}</div>
|
|
|
|
<div class="item-count">{{ item.count.toLocaleString() }}人</div>
|
|
|
|
</div>
|
|
|
|
<div class="progress-bar">
|
|
|
|
<div
|
|
|
|
class="progress-inner"
|
|
|
|
:style="{ width: getProgressWidth(item.count) }"
|
|
|
|
></div>
|
2025-08-12 14:39:25 +08:00
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
</template>
|
|
|
|
|
|
|
|
<style scoped lang="scss">
|
2025-08-15 15:56:13 +08:00
|
|
|
.ranking-container {
|
2025-08-12 14:39:25 +08:00
|
|
|
width: 100%;
|
|
|
|
height: 100%;
|
2025-08-15 15:56:13 +08:00
|
|
|
display: flex;
|
|
|
|
flex-direction: column;
|
2025-08-12 14:39:25 +08:00
|
|
|
}
|
|
|
|
|
2025-08-15 15:56:13 +08:00
|
|
|
.ranking-title {
|
|
|
|
height: 33px;
|
|
|
|
line-height: 33px;
|
2025-08-12 14:39:25 +08:00
|
|
|
width: 100%;
|
2025-08-15 15:56:13 +08:00
|
|
|
color: #333;
|
|
|
|
text-align: left;
|
|
|
|
position: relative;
|
2025-08-12 14:39:25 +08:00
|
|
|
display: flex;
|
2025-08-15 15:56:13 +08:00
|
|
|
align-items: center;
|
|
|
|
padding-left: 41px;
|
|
|
|
background: transparent;
|
|
|
|
font-weight: bold;
|
|
|
|
font-size: 20px;
|
|
|
|
margin-bottom: 20px;
|
|
|
|
|
|
|
|
.title-bg {
|
|
|
|
height: 33px;
|
|
|
|
position: absolute;
|
|
|
|
left: 0;
|
|
|
|
top: 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
.title-line {
|
|
|
|
height: 2px;
|
|
|
|
position: absolute;
|
|
|
|
left: 0;
|
|
|
|
bottom: 2px;
|
|
|
|
}
|
|
|
|
|
|
|
|
.title-inner {
|
|
|
|
font-weight: 600;
|
|
|
|
color: #393d44;
|
|
|
|
}
|
|
|
|
|
|
|
|
.filter-item {
|
|
|
|
position: absolute;
|
|
|
|
right: 0;
|
|
|
|
bottom: 5px;
|
|
|
|
font-weight: normal;
|
|
|
|
.filter-select {
|
|
|
|
width: 100px;
|
|
|
|
}
|
|
|
|
}
|
2025-08-12 14:39:25 +08:00
|
|
|
}
|
|
|
|
|
2025-08-15 15:56:13 +08:00
|
|
|
.ranking-tabs {
|
2025-08-12 14:39:25 +08:00
|
|
|
display: flex;
|
2025-08-15 15:56:13 +08:00
|
|
|
border-bottom: 1px solid #e6e9f0;
|
|
|
|
margin-bottom: 10px;
|
|
|
|
|
|
|
|
.tab-item {
|
|
|
|
padding: 10px 15px;
|
|
|
|
font-size: 14px;
|
|
|
|
color: #666;
|
|
|
|
cursor: pointer;
|
|
|
|
position: relative;
|
|
|
|
|
|
|
|
&.active {
|
|
|
|
color: #4b96ff;
|
|
|
|
font-weight: 500;
|
|
|
|
|
|
|
|
&::after {
|
|
|
|
content: "";
|
|
|
|
position: absolute;
|
|
|
|
bottom: -1px;
|
|
|
|
left: 0;
|
|
|
|
width: 100%;
|
|
|
|
height: 2px;
|
|
|
|
background-color: #4b96ff;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2025-08-12 14:39:25 +08:00
|
|
|
}
|
|
|
|
|
2025-08-15 15:56:13 +08:00
|
|
|
.ranking-list {
|
2025-08-12 14:39:25 +08:00
|
|
|
flex: 1;
|
|
|
|
overflow-y: auto;
|
2025-08-15 15:56:13 +08:00
|
|
|
padding: 5px 0;
|
|
|
|
|
|
|
|
/* 隐藏滚动条 */
|
|
|
|
&::-webkit-scrollbar {
|
|
|
|
display: none;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Firefox */
|
|
|
|
scrollbar-width: none;
|
|
|
|
|
|
|
|
/* IE */
|
|
|
|
-ms-overflow-style: none;
|
2025-08-12 14:39:25 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
.ranking-item {
|
|
|
|
display: flex;
|
2025-08-15 15:56:13 +08:00
|
|
|
flex-direction: column;
|
|
|
|
margin-bottom: 23px;
|
|
|
|
// border-bottom: 1px solid #f0f2f5;
|
|
|
|
|
2025-08-12 14:39:25 +08:00
|
|
|
&:last-child {
|
|
|
|
border-bottom: none;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2025-08-15 15:56:13 +08:00
|
|
|
.rank-badge {
|
|
|
|
width: 60px;
|
|
|
|
height: 30px;
|
2025-08-12 14:39:25 +08:00
|
|
|
display: flex;
|
|
|
|
align-items: center;
|
|
|
|
justify-content: center;
|
2025-08-15 15:56:13 +08:00
|
|
|
font-size: 16px;
|
|
|
|
font-weight: bold;
|
|
|
|
position: relative;
|
2025-08-12 14:39:25 +08:00
|
|
|
|
2025-08-15 15:56:13 +08:00
|
|
|
.rank-bg {
|
|
|
|
position: absolute;
|
|
|
|
top: 0;
|
|
|
|
left: 0;
|
|
|
|
width: 100%;
|
|
|
|
height: 100%;
|
|
|
|
border-radius: 2px;
|
|
|
|
object-fit: cover;
|
|
|
|
}
|
2025-08-12 14:39:25 +08:00
|
|
|
|
2025-08-15 15:56:13 +08:00
|
|
|
.rank-text {
|
|
|
|
position: relative; // 确保文字在图片上方
|
|
|
|
z-index: 1;
|
|
|
|
color: white;
|
2025-08-12 14:39:25 +08:00
|
|
|
}
|
2025-08-15 15:56:13 +08:00
|
|
|
|
|
|
|
&.topItem-other {
|
|
|
|
.rank-text {
|
|
|
|
color: #545966;
|
2025-08-12 14:39:25 +08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2025-08-15 15:56:13 +08:00
|
|
|
.item-content {
|
2025-08-12 14:39:25 +08:00
|
|
|
flex: 1;
|
2025-08-15 15:56:13 +08:00
|
|
|
display: flex;
|
|
|
|
align-items: center;
|
|
|
|
margin-bottom: 10px; // 添加固定的底部间距
|
|
|
|
}
|
|
|
|
|
|
|
|
.item-name {
|
|
|
|
font-size: 16px;
|
2025-08-12 14:39:25 +08:00
|
|
|
color: #333;
|
|
|
|
white-space: nowrap;
|
|
|
|
overflow: hidden;
|
|
|
|
text-overflow: ellipsis;
|
2025-08-15 15:56:13 +08:00
|
|
|
margin-left: 27px;
|
2025-08-12 14:39:25 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
.item-count {
|
2025-08-15 15:56:13 +08:00
|
|
|
font-size: 16px;
|
|
|
|
color: #2D2E30;
|
|
|
|
font-weight: 400;
|
|
|
|
margin-left: auto;
|
|
|
|
}
|
|
|
|
|
|
|
|
.progress-bar {
|
|
|
|
height: 6px;
|
|
|
|
background-color: #f0f2f5;
|
|
|
|
border-radius: 3px;
|
|
|
|
overflow: hidden;
|
|
|
|
|
|
|
|
.progress-inner {
|
|
|
|
height: 100%;
|
|
|
|
background-color: #4b96ff;
|
|
|
|
border-radius: 3px;
|
|
|
|
}
|
2025-08-12 14:39:25 +08:00
|
|
|
}
|
2025-08-15 15:56:13 +08:00
|
|
|
</style>
|