feat:用户意向专业排行
This commit is contained in:
parent
1e347af4f6
commit
c1f5e00dd5
Binary file not shown.
After Width: | Height: | Size: 1.1 KiB |
Binary file not shown.
After Width: | Height: | Size: 1.1 KiB |
Binary file not shown.
After Width: | Height: | Size: 1.1 KiB |
Binary file not shown.
After Width: | Height: | Size: 1.4 KiB |
|
@ -81,7 +81,7 @@ $item_title_content-height: calc(100% - 33px - 20px);
|
|||
|
||||
.item_title_content_def {
|
||||
width: 100%;
|
||||
height: 1px;
|
||||
height: 100%;
|
||||
background: #fff;
|
||||
}
|
||||
</style>
|
||||
|
|
|
@ -1,155 +1,221 @@
|
|||
<script setup lang="ts">
|
||||
import { ref, onMounted } from 'vue';
|
||||
import { highSchoolRanking } from "@/api";
|
||||
import * as echarts from 'echarts';
|
||||
import { studentDistribution } from "@/api";
|
||||
|
||||
interface RankingItem {
|
||||
rank: number;
|
||||
school: string;
|
||||
count: number;
|
||||
}
|
||||
const chartRef = ref<HTMLElement | null>(null);
|
||||
let chart: echarts.ECharts | null = null;
|
||||
|
||||
// 排行数据
|
||||
const rankingData = ref<RankingItem[]>([]);
|
||||
// 年份筛选选项
|
||||
const yearOptions = ref([
|
||||
{ label: '年份筛选', value: 'all' }
|
||||
]);
|
||||
const selectedYear = ref('all');
|
||||
|
||||
// 获取排行数据
|
||||
const fetchRankingData = () => {
|
||||
highSchoolRanking().then(res => {
|
||||
// 数据
|
||||
const chartData = ref({
|
||||
years: ['2022', '2023', '2024', '2025', '2026', '2027'],
|
||||
values: [50, 200, 550, 850, 750, 900, 200]
|
||||
});
|
||||
|
||||
// 获取数据
|
||||
const getChartData = () => {
|
||||
studentDistribution().then(res => {
|
||||
if (res.success) {
|
||||
rankingData.value = res.data;
|
||||
// 实际项目中应该使用API返回的数据
|
||||
// chartData.value = res.data;
|
||||
initChart();
|
||||
} else {
|
||||
// 如果API调用失败,使用模拟数据
|
||||
initChart();
|
||||
}
|
||||
}).catch(() => {
|
||||
// 如果发生错误,使用模拟数据
|
||||
initChart();
|
||||
});
|
||||
};
|
||||
|
||||
// 处理年份筛选变化
|
||||
const handleYearChange = () => {
|
||||
getChartData();
|
||||
};
|
||||
|
||||
const initChart = () => {
|
||||
if (!chartRef.value) return;
|
||||
|
||||
chart = echarts.init(chartRef.value);
|
||||
|
||||
const option = {
|
||||
grid: {
|
||||
top: '15%',
|
||||
left: '0',
|
||||
right: '5%',
|
||||
bottom: '0',
|
||||
containLabel: true
|
||||
},
|
||||
xAxis: {
|
||||
type: 'category',
|
||||
data: chartData.value.years,
|
||||
axisLine: {
|
||||
lineStyle: {
|
||||
color: '#E0E0E0'
|
||||
}
|
||||
},
|
||||
axisLabel: {
|
||||
color: '#99AABF',
|
||||
fontSize: 12
|
||||
}
|
||||
},
|
||||
yAxis: {
|
||||
type: 'value',
|
||||
name: '',
|
||||
max: 900,
|
||||
interval: 300,
|
||||
axisLabel: {
|
||||
color: '#99AABF',
|
||||
fontSize: 12
|
||||
},
|
||||
splitLine: {
|
||||
lineStyle: {
|
||||
type: 'dashed',
|
||||
color: '#E0E0E0'
|
||||
}
|
||||
}
|
||||
},
|
||||
series: [
|
||||
{
|
||||
data: chartData.value.values,
|
||||
type: 'line',
|
||||
smooth: true,
|
||||
symbol: 'circle',
|
||||
symbolSize: 8,
|
||||
itemStyle: {
|
||||
color: '#fff',
|
||||
borderColor: '#4B96FF',
|
||||
borderWidth: 2
|
||||
},
|
||||
lineStyle: {
|
||||
color: '#4B96FF',
|
||||
width: 3
|
||||
},
|
||||
areaStyle: {
|
||||
color: {
|
||||
type: 'linear',
|
||||
x: 0,
|
||||
y: 0,
|
||||
x2: 0,
|
||||
y2: 1,
|
||||
colorStops: [
|
||||
{
|
||||
offset: 0,
|
||||
color: 'rgba(75, 150, 255, 0.3)'
|
||||
},
|
||||
{
|
||||
offset: 1,
|
||||
color: 'rgba(75, 150, 255, 0.1)'
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
markPoint: {
|
||||
symbol: 'circle',
|
||||
symbolSize: 10,
|
||||
itemStyle: {
|
||||
color: '#4B96FF'
|
||||
},
|
||||
data: [
|
||||
{ coord: ['2025', 850] }
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
||||
};
|
||||
|
||||
chart.setOption(option);
|
||||
};
|
||||
|
||||
// 监听窗口大小变化,重绘图表
|
||||
const handleResize = () => {
|
||||
if (chart) {
|
||||
chart.resize();
|
||||
}
|
||||
};
|
||||
|
||||
onMounted(() => {
|
||||
fetchRankingData();
|
||||
getChartData();
|
||||
window.addEventListener('resize', handleResize);
|
||||
});
|
||||
|
||||
// 组件卸载时移除事件监听
|
||||
const onUnmounted = () => {
|
||||
window.removeEventListener('resize', handleResize);
|
||||
if (chart) {
|
||||
chart.dispose();
|
||||
chart = null;
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="high-school-ranking">
|
||||
<div class="ranking-list">
|
||||
<div class="ranking-header">
|
||||
<div class="header-item rank">排名</div>
|
||||
<div class="header-item school">学校名称</div>
|
||||
<div class="header-item count">用户数</div>
|
||||
</div>
|
||||
<div class="ranking-body">
|
||||
<div v-for="(item, index) in rankingData" :key="index" class="ranking-item">
|
||||
<div class="item-rank" :class="{ 'top-rank': item.rank <= 3 }">
|
||||
<span class="rank-number">{{ item.rank }}</span>
|
||||
</div>
|
||||
<div class="item-school">{{ item.school }}</div>
|
||||
<div class="item-count">{{ item.count }}人</div>
|
||||
<div class="student-distribution">
|
||||
<div class="chart-header">
|
||||
<div class="filters">
|
||||
<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>
|
||||
</div>
|
||||
</div>
|
||||
<div class="chart-container" ref="chartRef"></div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.high-school-ranking {
|
||||
.student-distribution {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
padding: 10px 0;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.ranking-list {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.ranking-header {
|
||||
display: flex;
|
||||
padding: 10px 0;
|
||||
border-bottom: 1px solid #eee;
|
||||
font-weight: bold;
|
||||
color: #333;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.ranking-body {
|
||||
flex: 1;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.ranking-item {
|
||||
display: flex;
|
||||
padding: 12px 0;
|
||||
border-bottom: 1px solid #f5f5f5;
|
||||
|
||||
&:last-child {
|
||||
border-bottom: none;
|
||||
}
|
||||
}
|
||||
|
||||
.header-item, .item-rank, .item-school, .item-count {
|
||||
.chart-header {
|
||||
position: absolute;
|
||||
right: 0;
|
||||
top: -35px;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.rank {
|
||||
width: 60px;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.school {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.count {
|
||||
width: 80px;
|
||||
justify-content: flex-end;
|
||||
}
|
||||
|
||||
.item-rank {
|
||||
width: 60px;
|
||||
justify-content: center;
|
||||
margin-bottom: 15px;
|
||||
|
||||
.rank-number {
|
||||
.filters {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
border-radius: 50%;
|
||||
background-color: #f5f5f5;
|
||||
font-size: 14px;
|
||||
color: #666;
|
||||
}
|
||||
|
||||
&.top-rank {
|
||||
.rank-number {
|
||||
background-color: #4B96FF;
|
||||
color: #fff;
|
||||
}
|
||||
gap: 10px;
|
||||
|
||||
&:nth-child(1) .rank-number {
|
||||
background-color: #f5222d;
|
||||
}
|
||||
|
||||
&:nth-child(2) .rank-number {
|
||||
background-color: #fa8c16;
|
||||
}
|
||||
|
||||
&:nth-child(3) .rank-number {
|
||||
background-color: #52c41a;
|
||||
.filter-item {
|
||||
.filter-select {
|
||||
width: 100px;
|
||||
|
||||
:deep(.el-input__wrapper) {
|
||||
background-color: #f5f7fa;
|
||||
box-shadow: none;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
:deep(.el-input__inner) {
|
||||
font-size: 12px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.item-school {
|
||||
flex: 1;
|
||||
font-size: 14px;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.item-count {
|
||||
width: 80px;
|
||||
justify-content: flex-end;
|
||||
font-size: 14px;
|
||||
color: #4B96FF;
|
||||
font-weight: bold;
|
||||
.chart-container {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
</style>
|
|
@ -1,45 +1,213 @@
|
|||
<script setup lang="ts">
|
||||
import { ref, onMounted } from 'vue';
|
||||
import { majorRanking } from "@/api";
|
||||
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";
|
||||
|
||||
interface RankingItem {
|
||||
rank: number;
|
||||
name: string;
|
||||
count: number;
|
||||
}
|
||||
|
||||
interface MajorRankingItem {
|
||||
rank: number;
|
||||
major: string;
|
||||
count: number;
|
||||
}
|
||||
|
||||
interface SchoolRankingItem {
|
||||
rank: number;
|
||||
school: string;
|
||||
count: number;
|
||||
}
|
||||
|
||||
// 排行数据
|
||||
const rankingData = ref<RankingItem[]>([]);
|
||||
|
||||
// 排行类型
|
||||
const rankingTypes = [
|
||||
{ label: "用户意向专业排行", value: "major" },
|
||||
{ label: "用户生源高中排行", value: "school" },
|
||||
];
|
||||
const selectedRankingType = ref("major");
|
||||
|
||||
// 获取排行数据
|
||||
const fetchRankingData = () => {
|
||||
majorRanking().then(res => {
|
||||
if (res.success) {
|
||||
rankingData.value = res.data;
|
||||
}
|
||||
});
|
||||
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;
|
||||
}
|
||||
};
|
||||
|
||||
onMounted(() => {
|
||||
// 获取真实数据
|
||||
fetchRankingData();
|
||||
|
||||
// 如果没有真实数据,使用模拟数据
|
||||
setTimeout(() => {
|
||||
if (rankingData.value.length === 0) {
|
||||
initMockData();
|
||||
} else {
|
||||
updateMaxCount();
|
||||
}
|
||||
}, 500);
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="major-ranking">
|
||||
<div class="ranking-list">
|
||||
<div class="ranking-header">
|
||||
<div class="header-item rank">排名</div>
|
||||
<div class="header-item major">专业名称</div>
|
||||
<div class="header-item count">用户数</div>
|
||||
<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 class="ranking-body">
|
||||
<div v-for="(item, index) in rankingData" :key="index" class="ranking-item">
|
||||
<div class="item-rank" :class="{ 'top-rank': item.rank <= 3 }">
|
||||
<span class="rank-number">{{ item.rank }}</span>
|
||||
</div>
|
||||
|
||||
<!-- 排行榜内容 -->
|
||||
<div class="ranking-list">
|
||||
<!-- 标题装饰 -->
|
||||
<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>
|
||||
</div>
|
||||
<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>
|
||||
</div>
|
||||
<div class="item-major">{{ item.major }}</div>
|
||||
<div class="item-count">{{ item.count }}人</div>
|
||||
<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>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -47,112 +215,181 @@ onMounted(() => {
|
|||
</template>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.major-ranking {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
padding: 10px 0;
|
||||
}
|
||||
|
||||
.ranking-list {
|
||||
.ranking-container {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.ranking-header {
|
||||
display: flex;
|
||||
padding: 10px 0;
|
||||
border-bottom: 1px solid #eee;
|
||||
font-weight: bold;
|
||||
.ranking-title {
|
||||
height: 33px;
|
||||
line-height: 33px;
|
||||
width: 100%;
|
||||
color: #333;
|
||||
font-size: 14px;
|
||||
text-align: left;
|
||||
position: relative;
|
||||
display: flex;
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.ranking-body {
|
||||
.ranking-tabs {
|
||||
display: flex;
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.ranking-list {
|
||||
flex: 1;
|
||||
overflow-y: auto;
|
||||
padding: 5px 0;
|
||||
|
||||
/* 隐藏滚动条 */
|
||||
&::-webkit-scrollbar {
|
||||
display: none;
|
||||
}
|
||||
|
||||
/* Firefox */
|
||||
scrollbar-width: none;
|
||||
|
||||
/* IE */
|
||||
-ms-overflow-style: none;
|
||||
}
|
||||
|
||||
.ranking-item {
|
||||
display: flex;
|
||||
padding: 12px 0;
|
||||
border-bottom: 1px solid #f5f5f5;
|
||||
|
||||
flex-direction: column;
|
||||
margin-bottom: 23px;
|
||||
// border-bottom: 1px solid #f0f2f5;
|
||||
|
||||
&:last-child {
|
||||
border-bottom: none;
|
||||
}
|
||||
}
|
||||
|
||||
.header-item, .item-rank, .item-major, .item-count {
|
||||
.rank-badge {
|
||||
width: 60px;
|
||||
height: 30px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.rank {
|
||||
width: 60px;
|
||||
justify-content: center;
|
||||
}
|
||||
font-size: 16px;
|
||||
font-weight: bold;
|
||||
position: relative;
|
||||
|
||||
.major {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.count {
|
||||
width: 80px;
|
||||
justify-content: flex-end;
|
||||
}
|
||||
|
||||
.item-rank {
|
||||
width: 60px;
|
||||
justify-content: center;
|
||||
|
||||
.rank-number {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
border-radius: 50%;
|
||||
background-color: #f5f5f5;
|
||||
font-size: 14px;
|
||||
color: #666;
|
||||
.rank-bg {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
border-radius: 2px;
|
||||
object-fit: cover;
|
||||
}
|
||||
|
||||
&.top-rank {
|
||||
.rank-number {
|
||||
background-color: #4B96FF;
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
&:nth-child(1) .rank-number {
|
||||
background-color: #f5222d;
|
||||
}
|
||||
|
||||
&:nth-child(2) .rank-number {
|
||||
background-color: #fa8c16;
|
||||
}
|
||||
|
||||
&:nth-child(3) .rank-number {
|
||||
background-color: #52c41a;
|
||||
|
||||
.rank-text {
|
||||
position: relative; // 确保文字在图片上方
|
||||
z-index: 1;
|
||||
color: white;
|
||||
}
|
||||
|
||||
&.topItem-other {
|
||||
.rank-text {
|
||||
color: #545966;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.item-major {
|
||||
.item-content {
|
||||
flex: 1;
|
||||
font-size: 14px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin-bottom: 10px; // 添加固定的底部间距
|
||||
}
|
||||
|
||||
.item-name {
|
||||
font-size: 16px;
|
||||
color: #333;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
margin-left: 27px;
|
||||
}
|
||||
|
||||
.item-count {
|
||||
width: 80px;
|
||||
justify-content: flex-end;
|
||||
font-size: 14px;
|
||||
color: #4B96FF;
|
||||
font-weight: bold;
|
||||
font-size: 16px;
|
||||
color: #2D2E30;
|
||||
font-weight: 400;
|
||||
margin-left: auto;
|
||||
}
|
||||
</style>
|
||||
|
||||
.progress-bar {
|
||||
height: 6px;
|
||||
background-color: #f0f2f5;
|
||||
border-radius: 3px;
|
||||
overflow: hidden;
|
||||
|
||||
.progress-inner {
|
||||
height: 100%;
|
||||
background-color: #4b96ff;
|
||||
border-radius: 3px;
|
||||
}
|
||||
}
|
||||
</style>
|
|
@ -159,13 +159,13 @@ onUnmounted(() => {
|
|||
</div>
|
||||
<div class="filter-item">
|
||||
<el-select v-model="selectedTimeFilter" size="small" class="filter-select" @change="handleFilterChange">
|
||||
<el-option
|
||||
<el-option
|
||||
v-for="item in timeFilters"
|
||||
:key="item.value"
|
||||
:label="item.label"
|
||||
:value="item.value"
|
||||
/>
|
||||
</el-select>
|
||||
:key="item.value"
|
||||
:label="item.label"
|
||||
:value="item.value"
|
||||
/>
|
||||
</el-select>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -196,13 +196,13 @@ onUnmounted(() => {
|
|||
.filter-item {
|
||||
.filter-select {
|
||||
width: 120px;
|
||||
|
||||
:deep(.el-input__wrapper) {
|
||||
background-color: #f5f7fa;
|
||||
box-shadow: none;
|
||||
|
||||
:deep(.el-input__wrapper) {
|
||||
background-color: #f5f7fa;
|
||||
box-shadow: none;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
:deep(.el-input__inner) {
|
||||
font-size: 12px;
|
||||
}
|
||||
|
|
|
@ -1,155 +0,0 @@
|
|||
<script setup lang="ts">
|
||||
import { ref, onMounted } from 'vue';
|
||||
import { highSchoolRanking } from "@/api";
|
||||
|
||||
interface RankingItem {
|
||||
rank: number;
|
||||
school: string;
|
||||
count: number;
|
||||
}
|
||||
|
||||
// 排行数据
|
||||
const rankingData = ref<RankingItem[]>([]);
|
||||
|
||||
// 获取排行数据
|
||||
const fetchRankingData = () => {
|
||||
highSchoolRanking().then(res => {
|
||||
if (res.success) {
|
||||
rankingData.value = res.data;
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
onMounted(() => {
|
||||
fetchRankingData();
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="high-school-ranking">
|
||||
<div class="ranking-list">
|
||||
<div class="ranking-header">
|
||||
<div class="header-item rank">排名</div>
|
||||
<div class="header-item school">学校名称</div>
|
||||
<div class="header-item count">用户数</div>
|
||||
</div>
|
||||
<div class="ranking-body">
|
||||
<div v-for="(item, index) in rankingData" :key="index" class="ranking-item">
|
||||
<div class="item-rank" :class="{ 'top-rank': item.rank <= 3 }">
|
||||
<span class="rank-number">{{ item.rank }}</span>
|
||||
</div>
|
||||
<div class="item-school">{{ item.school }}</div>
|
||||
<div class="item-count">{{ item.count }}人</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.high-school-ranking {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
padding: 10px 0;
|
||||
}
|
||||
|
||||
.ranking-list {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.ranking-header {
|
||||
display: flex;
|
||||
padding: 10px 0;
|
||||
border-bottom: 1px solid #eee;
|
||||
font-weight: bold;
|
||||
color: #333;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.ranking-body {
|
||||
flex: 1;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.ranking-item {
|
||||
display: flex;
|
||||
padding: 12px 0;
|
||||
border-bottom: 1px solid #f5f5f5;
|
||||
|
||||
&:last-child {
|
||||
border-bottom: none;
|
||||
}
|
||||
}
|
||||
|
||||
.header-item, .item-rank, .item-school, .item-count {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.rank {
|
||||
width: 60px;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.school {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.count {
|
||||
width: 80px;
|
||||
justify-content: flex-end;
|
||||
}
|
||||
|
||||
.item-rank {
|
||||
width: 60px;
|
||||
justify-content: center;
|
||||
|
||||
.rank-number {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
border-radius: 50%;
|
||||
background-color: #f5f5f5;
|
||||
font-size: 14px;
|
||||
color: #666;
|
||||
}
|
||||
|
||||
&.top-rank {
|
||||
.rank-number {
|
||||
background-color: #4B96FF;
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
&:nth-child(1) .rank-number {
|
||||
background-color: #f5222d;
|
||||
}
|
||||
|
||||
&:nth-child(2) .rank-number {
|
||||
background-color: #fa8c16;
|
||||
}
|
||||
|
||||
&:nth-child(3) .rank-number {
|
||||
background-color: #52c41a;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.item-school {
|
||||
flex: 1;
|
||||
font-size: 14px;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.item-count {
|
||||
width: 80px;
|
||||
justify-content: flex-end;
|
||||
font-size: 14px;
|
||||
color: #4B96FF;
|
||||
font-weight: bold;
|
||||
}
|
||||
</style>
|
|
@ -1,119 +0,0 @@
|
|||
<script setup lang="ts">
|
||||
import { ref, onMounted } from 'vue';
|
||||
import { commonQuestions } from "@/api";
|
||||
|
||||
interface QuestionItem {
|
||||
id: number;
|
||||
question: string;
|
||||
count: number;
|
||||
}
|
||||
|
||||
// 问题数据
|
||||
const questionData = ref<QuestionItem[]>([]);
|
||||
|
||||
// 获取问题数据
|
||||
const fetchQuestionData = () => {
|
||||
commonQuestions().then(res => {
|
||||
if (res.success) {
|
||||
questionData.value = res.data;
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
onMounted(() => {
|
||||
fetchQuestionData();
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="common-questions">
|
||||
<div class="question-list">
|
||||
<div v-for="(item, index) in questionData" :key="index" class="question-item">
|
||||
<div class="question-rank">{{ index + 1 }}</div>
|
||||
<div class="question-content">
|
||||
<div class="question-text">{{ item.question }}</div>
|
||||
<div class="question-count">
|
||||
<span class="count-value">{{ item.count }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.common-questions {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
padding: 10px 0;
|
||||
}
|
||||
|
||||
.question-list {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.question-item {
|
||||
display: flex;
|
||||
padding: 15px 0;
|
||||
border-bottom: 1px solid #f5f5f5;
|
||||
|
||||
&:last-child {
|
||||
border-bottom: none;
|
||||
}
|
||||
}
|
||||
|
||||
.question-rank {
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
background-color: #f5f5f5;
|
||||
border-radius: 50%;
|
||||
margin-right: 15px;
|
||||
font-size: 14px;
|
||||
color: #666;
|
||||
flex-shrink: 0;
|
||||
|
||||
// 前三名使用不同颜色
|
||||
.question-item:nth-child(1) & {
|
||||
background-color: #f5222d;
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.question-item:nth-child(2) & {
|
||||
background-color: #fa8c16;
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.question-item:nth-child(3) & {
|
||||
background-color: #52c41a;
|
||||
color: #fff;
|
||||
}
|
||||
}
|
||||
|
||||
.question-content {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.question-text {
|
||||
font-size: 14px;
|
||||
color: #333;
|
||||
line-height: 1.5;
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
|
||||
.question-count {
|
||||
font-size: 12px;
|
||||
color: #999;
|
||||
|
||||
.count-value {
|
||||
color: #4B96FF;
|
||||
font-weight: bold;
|
||||
}
|
||||
}
|
||||
</style>
|
|
@ -1,158 +0,0 @@
|
|||
<script setup lang="ts">
|
||||
import { ref, onMounted } from 'vue';
|
||||
import { majorRanking } from "@/api";
|
||||
|
||||
interface RankingItem {
|
||||
rank: number;
|
||||
major: string;
|
||||
count: number;
|
||||
}
|
||||
|
||||
// 排行数据
|
||||
const rankingData = ref<RankingItem[]>([]);
|
||||
|
||||
// 获取排行数据
|
||||
const fetchRankingData = () => {
|
||||
majorRanking().then(res => {
|
||||
if (res.success) {
|
||||
rankingData.value = res.data;
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
onMounted(() => {
|
||||
fetchRankingData();
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="major-ranking">
|
||||
<div class="ranking-list">
|
||||
<div class="ranking-header">
|
||||
<div class="header-item rank">排名</div>
|
||||
<div class="header-item major">专业名称</div>
|
||||
<div class="header-item count">用户数</div>
|
||||
</div>
|
||||
<div class="ranking-body">
|
||||
<div v-for="(item, index) in rankingData" :key="index" class="ranking-item">
|
||||
<div class="item-rank" :class="{ 'top-rank': item.rank <= 3 }">
|
||||
<span class="rank-number">{{ item.rank }}</span>
|
||||
</div>
|
||||
<div class="item-major">{{ item.major }}</div>
|
||||
<div class="item-count">{{ item.count }}人</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.major-ranking {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
padding: 10px 0;
|
||||
}
|
||||
|
||||
.ranking-list {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.ranking-header {
|
||||
display: flex;
|
||||
padding: 10px 0;
|
||||
border-bottom: 1px solid #eee;
|
||||
font-weight: bold;
|
||||
color: #333;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.ranking-body {
|
||||
flex: 1;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.ranking-item {
|
||||
display: flex;
|
||||
padding: 12px 0;
|
||||
border-bottom: 1px solid #f5f5f5;
|
||||
|
||||
&:last-child {
|
||||
border-bottom: none;
|
||||
}
|
||||
}
|
||||
|
||||
.header-item, .item-rank, .item-major, .item-count {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.rank {
|
||||
width: 60px;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.major {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.count {
|
||||
width: 80px;
|
||||
justify-content: flex-end;
|
||||
}
|
||||
|
||||
.item-rank {
|
||||
width: 60px;
|
||||
justify-content: center;
|
||||
|
||||
.rank-number {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
border-radius: 50%;
|
||||
background-color: #f5f5f5;
|
||||
font-size: 14px;
|
||||
color: #666;
|
||||
}
|
||||
|
||||
&.top-rank {
|
||||
.rank-number {
|
||||
background-color: #4B96FF;
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
&:nth-child(1) .rank-number {
|
||||
background-color: #f5222d;
|
||||
}
|
||||
|
||||
&:nth-child(2) .rank-number {
|
||||
background-color: #fa8c16;
|
||||
}
|
||||
|
||||
&:nth-child(3) .rank-number {
|
||||
background-color: #52c41a;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.item-major {
|
||||
flex: 1;
|
||||
font-size: 14px;
|
||||
color: #333;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
.item-count {
|
||||
width: 80px;
|
||||
justify-content: flex-end;
|
||||
font-size: 14px;
|
||||
color: #4B96FF;
|
||||
font-weight: bold;
|
||||
}
|
||||
</style>
|
|
@ -1,178 +0,0 @@
|
|||
<script setup lang="ts">
|
||||
import { ref, onMounted } from 'vue';
|
||||
import * as echarts from 'echarts';
|
||||
import { averageDuration } from "@/api";
|
||||
|
||||
const chartRef = ref<HTMLElement | null>(null);
|
||||
let chart: echarts.ECharts | null = null;
|
||||
|
||||
// 过滤选项
|
||||
const filterOptions = [
|
||||
{ value: 'all', label: '全部来源' },
|
||||
{ value: 'web', label: '网页端' },
|
||||
{ value: 'mobile', label: '移动端' }
|
||||
];
|
||||
const selectedFilter = ref(filterOptions[0].value);
|
||||
|
||||
// 图表数据
|
||||
const chartData = ref({
|
||||
days: [] as string[],
|
||||
durations: [] as number[]
|
||||
});
|
||||
|
||||
// 获取图表数据
|
||||
const getChartData = () => {
|
||||
averageDuration().then(res => {
|
||||
if (res.success) {
|
||||
chartData.value = res.data;
|
||||
initChart();
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
const initChart = () => {
|
||||
if (!chartRef.value) return;
|
||||
|
||||
chart = echarts.init(chartRef.value);
|
||||
|
||||
const option = {
|
||||
grid: {
|
||||
top: '10%',
|
||||
left: '3%',
|
||||
right: '4%',
|
||||
bottom: '15%',
|
||||
containLabel: true
|
||||
},
|
||||
tooltip: {
|
||||
trigger: 'axis',
|
||||
axisPointer: {
|
||||
type: 'shadow'
|
||||
}
|
||||
},
|
||||
xAxis: {
|
||||
type: 'category',
|
||||
data: chartData.value.days,
|
||||
axisLine: {
|
||||
lineStyle: {
|
||||
color: '#E0E0E0'
|
||||
}
|
||||
},
|
||||
axisLabel: {
|
||||
color: '#999999'
|
||||
}
|
||||
},
|
||||
yAxis: {
|
||||
type: 'value',
|
||||
name: '分钟',
|
||||
nameTextStyle: {
|
||||
color: '#999999'
|
||||
},
|
||||
min: 0,
|
||||
max: 100,
|
||||
interval: 20,
|
||||
splitLine: {
|
||||
lineStyle: {
|
||||
type: 'dashed',
|
||||
color: '#E0E0E0'
|
||||
}
|
||||
},
|
||||
axisLabel: {
|
||||
color: '#999999'
|
||||
}
|
||||
},
|
||||
series: [
|
||||
{
|
||||
name: '平均时长',
|
||||
type: 'bar',
|
||||
barWidth: '40%',
|
||||
data: chartData.value.durations,
|
||||
itemStyle: {
|
||||
color: '#4B96FF'
|
||||
}
|
||||
}
|
||||
]
|
||||
};
|
||||
|
||||
chart.setOption(option);
|
||||
};
|
||||
|
||||
// 监听窗口大小变化,重绘图表
|
||||
const handleResize = () => {
|
||||
if (chart) {
|
||||
chart.resize();
|
||||
}
|
||||
};
|
||||
|
||||
// 处理过滤器变化
|
||||
const handleFilterChange = () => {
|
||||
// 这里可以根据选择的过滤条件重新获取数据并更新图表
|
||||
console.log('Filter changed to:', selectedFilter.value);
|
||||
getChartData(); // 简单实现,实际应该传入过滤参数
|
||||
};
|
||||
|
||||
onMounted(() => {
|
||||
getChartData();
|
||||
window.addEventListener('resize', handleResize);
|
||||
});
|
||||
|
||||
// 组件卸载时移除事件监听
|
||||
const onUnmounted = () => {
|
||||
window.removeEventListener('resize', handleResize);
|
||||
if (chart) {
|
||||
chart.dispose();
|
||||
chart = null;
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="average-duration">
|
||||
<div class="filter-container">
|
||||
<div class="filter-label">数据来源:</div>
|
||||
<el-select v-model="selectedFilter" placeholder="选择来源" size="small" @change="handleFilterChange">
|
||||
<el-option
|
||||
v-for="item in filterOptions"
|
||||
:key="item.value"
|
||||
:label="item.label"
|
||||
:value="item.value"
|
||||
/>
|
||||
</el-select>
|
||||
</div>
|
||||
<div class="chart-container" ref="chartRef"></div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.average-duration {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.filter-container {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 10px 0;
|
||||
|
||||
.filter-label {
|
||||
font-size: 14px;
|
||||
color: #666;
|
||||
margin-right: 10px;
|
||||
}
|
||||
|
||||
:deep(.el-input__wrapper) {
|
||||
background-color: #f5f7fa;
|
||||
box-shadow: none;
|
||||
}
|
||||
|
||||
:deep(.el-select .el-input) {
|
||||
width: 120px;
|
||||
}
|
||||
}
|
||||
|
||||
.chart-container {
|
||||
flex: 1;
|
||||
width: 100%;
|
||||
}
|
||||
</style>
|
|
@ -6,17 +6,15 @@ const ContentHeader = defineAsyncComponent(() => import("./content-header.vue"))
|
|||
// 导入左侧组件
|
||||
const LeftTop = defineAsyncComponent(() => import("./LeftTop.vue"));
|
||||
const LeftCenter = defineAsyncComponent(() => import("./LeftCenter.vue"));
|
||||
const AverageDuration = defineAsyncComponent(() => import("./average-duration.vue"));
|
||||
const LeftBottom = defineAsyncComponent(() => import("./components/LeftBottom.vue"));
|
||||
const LeftBottom = defineAsyncComponent(() => import("./LeftBottom.vue"));
|
||||
|
||||
|
||||
// 导入中间组件
|
||||
const CenterTop = defineAsyncComponent(() => import("./components/CenterTop.vue"));
|
||||
const CenterBottom = defineAsyncComponent(() => import("./components/CenterBottom.vue"));
|
||||
|
||||
// 导入右侧组件
|
||||
const RightTop = defineAsyncComponent(() => import("./components/RightTop.vue"));
|
||||
const RightCenter = defineAsyncComponent(() => import("./components/RightCenter.vue"));
|
||||
const RightBottom = defineAsyncComponent(() => import("./components/RightBottom.vue"));
|
||||
const RightCenter = defineAsyncComponent(() => import("./RightCenter.vue"));
|
||||
</script>
|
||||
|
||||
<template>
|
||||
|
@ -33,35 +31,24 @@ const RightBottom = defineAsyncComponent(() => import("./components/RightBottom.
|
|||
<LeftCenter />
|
||||
</ItemWrap>
|
||||
|
||||
<!-- <ItemWrap title="学生用户高考年份统计" class="left-contetn-bottom module-item">
|
||||
<ItemWrap title="学生用户高考年份统计" class="left-contetn-bottom module-item">
|
||||
<LeftBottom />
|
||||
</ItemWrap> -->
|
||||
</ItemWrap>
|
||||
</div>
|
||||
|
||||
<!-- 中间内容区域 -->
|
||||
<div class="center-content">
|
||||
<ItemWrap class="module-item map-container">
|
||||
<CenterTop />
|
||||
</ItemWrap>
|
||||
|
||||
<ItemWrap title="高频咨询问题" class="module-item">
|
||||
<CenterTop />
|
||||
<!-- <ItemWrap title="高频咨询问题" class="module-item">
|
||||
<CenterBottom />
|
||||
</ItemWrap>
|
||||
</ItemWrap> -->
|
||||
</div>
|
||||
|
||||
<!-- 右侧内容区域 -->
|
||||
<div class="right-content">
|
||||
<ItemWrap title="平均时长趋势" class="module-item">
|
||||
<RightTop />
|
||||
</ItemWrap>
|
||||
|
||||
<ItemWrap title="用户量属专业排行" class="module-item">
|
||||
<ItemWrap class="right-content-top module-item">
|
||||
<RightCenter />
|
||||
</ItemWrap>
|
||||
|
||||
<ItemWrap title="常见咨询问题" class="module-item">
|
||||
<RightBottom />
|
||||
</ItemWrap>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -87,7 +74,7 @@ const RightBottom = defineAsyncComponent(() => import("./components/RightBottom.
|
|||
.left-content, .right-content {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
width: 25%;
|
||||
width: 500px;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
|
@ -110,6 +97,10 @@ const RightBottom = defineAsyncComponent(() => import("./components/RightBottom.
|
|||
height: 240px;
|
||||
}
|
||||
|
||||
.right-content-top {
|
||||
height: 710px;
|
||||
}
|
||||
|
||||
// .module-item {
|
||||
// height: 100%;
|
||||
// flex: 1;
|
||||
|
|
Loading…
Reference in New Issue