Files
schoolNews/schoolNewsWeb/src/views/user/home/HomeView.vue
2025-12-02 10:58:15 +08:00

321 lines
7.8 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<template>
<div class="home-view">
<!-- 轮播横幅区域 -->
<div class="banner-section">
<!-- 加载状态 -->
<div v-if="loading" class="banner-loading">
<div class="loading-spinner"></div>
<p>加载中...</p>
</div>
<!-- 轮播图 -->
<Carousel
v-else-if="banners.length > 0"
:items="banners"
:interval="5000"
:active-icon="dangIcon"
indicator-position="bottom-right"
>
<template #default="{ item }">
<BannerCard :banner="item" />
</template>
</Carousel>
<!-- 空状态 -->
<div v-else class="banner-empty">
<p>暂无轮播内容</p>
</div>
</div>
<!-- 热门资源推荐 -->
<div class="section">
<div class="section-header">
<h2 class="section-title">热门资源推荐</h2>
<div class="more-link" @click="handleMoreClick('hot')">
<span>查看更多</span>
<el-icon><ArrowRight /></el-icon>
</div>
</div>
<div v-if="hotResourcesLoading" class="loading-container">
<div class="loading-spinner"></div>
<p>加载中...</p>
</div>
<div v-else-if="hotResources.length > 0" class="article-grid">
<HotArticleCard
v-for="resource in hotResources"
:key="resource.id || resource.resourceID"
:resource="resource"
/>
</div>
<div v-else class="empty-container">
<p>暂无热门资源</p>
</div>
</div>
<!-- 思政新闻概览 -->
<div class="section">
<div class="section-header">
<h2 class="section-title">思政新闻概览</h2>
<div class="more-link" @click="handleMoreClick('ideological')">
<span>查看更多</span>
<el-icon><ArrowRight /></el-icon>
</div>
</div>
<div v-if="ideologicalResourcesLoading" class="loading-container">
<div class="loading-spinner"></div>
<p>加载中...</p>
</div>
<div v-else-if="ideologicalResources.length > 0" class="article-grid">
<IdeologicalArticleCard
v-for="resource in ideologicalResources"
:key="resource.id || resource.resourceID"
:resource="resource"
/>
</div>
<div v-else class="empty-container">
<p>暂无思政资源</p>
</div>
</div>
<!-- 我的学习数据 -->
<div class="section">
<div class="section-header">
<h2 class="section-title">我的学习数据</h2>
<div class="more-link" @click="handleMoreClick('learning')">
<span>查看更多</span>
<el-icon><ArrowRight /></el-icon>
</div>
</div>
<LearningProgress />
</div>
</div>
</template>
<script setup lang="ts">
import { ref, onMounted } from 'vue';
import { BannerCard, LearningProgress } from '@/views/public/';
import { HotArticleCard, IdeologicalArticleCard } from '@/views/public/article';
import { Carousel } from '@/components/base';
import { ArrowRight } from '@element-plus/icons-vue';
import { bannerApi } from '@/apis/resource/banner';
import { recommendApi } from '@/apis/homepage';
import { ElMessage } from 'element-plus';
import type { Banner, ResourceRecommendVO } from '@/types';
import dangIcon from '@/assets/imgs/dang.svg';
import { useRouter } from 'vue-router';
const router = useRouter();
// 轮播数据
const banners = ref<Banner[]>([]);
const loading = ref(false);
// 热门资源数据
const hotResources = ref<ResourceRecommendVO[]>([]);
const hotResourcesLoading = ref(false);
// 思政资源数据
const ideologicalResources = ref<ResourceRecommendVO[]>([]);
const ideologicalResourcesLoading = ref(false);
// 加载轮播图数据
async function loadBanners() {
try {
loading.value = true;
const result = await bannerApi.getHomeBannerList();
if (result.code === 200 && result.dataList) {
// 只显示启用状态的banner按排序号排序
banners.value = result.dataList
.filter((banner: Banner) => banner.status === 1)
.sort((a: Banner, b: Banner) => (a.orderNum || 0) - (b.orderNum || 0));
}
} catch (error) {
console.error('加载轮播图失败:', error);
ElMessage.error('加载轮播图失败');
} finally {
loading.value = false;
}
}
// 加载热门资源数据
async function loadHotResources() {
try {
hotResourcesLoading.value = true;
const result = await recommendApi.getHotResources(3);
if (result.code === 200 && result.dataList) {
hotResources.value = result.dataList;
}
} catch (error) {
console.error('加载热门资源失败:', error);
ElMessage.error('加载热门资源失败');
} finally {
hotResourcesLoading.value = false;
}
}
// 加载思政资源数据
async function loadIdeologicalResources() {
try {
ideologicalResourcesLoading.value = true;
const result = await recommendApi.getIdeologicalResources(3);
if (result.code === 200 && result.dataList) {
ideologicalResources.value = result.dataList;
}
} catch (error) {
console.error('加载思政资源失败:', error);
ElMessage.error('加载思政资源失败');
} finally {
ideologicalResourcesLoading.value = false;
}
}
function handleMoreClick(type: string) {
if (type === 'hot') {
router.push('/resource-hot');
} else if (type === 'ideological') {
router.push('/resource-center');
} else if (type === 'learning') {
router.push('/learning-center');
}
}
// 组件挂载时加载数据
onMounted(() => {
loadBanners();
loadHotResources();
loadIdeologicalResources();
});
</script>
<style lang="scss" scoped>
.home-view {
background-color: #F9F9F9;
min-height: 100vh;
padding-bottom: 60px;
}
.banner-section {
width: 100%;
height: 30vh;
position: relative;
}
.banner-loading,
.banner-empty {
width: 100%;
height: 100%;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
background: #f5f7fa;
p {
margin-top: 16px;
color: #909399;
font-size: 14px;
}
}
.loading-spinner {
width: 40px;
height: 40px;
border: 3px solid #e4e7ed;
border-top-color: #E7000B;
border-radius: 50%;
animation: spin 0.8s linear infinite;
}
@keyframes spin {
to { transform: rotate(360deg); }
}
.section {
// max-width: 1440px;
margin: 0 auto;
padding: 0 120px;
margin-top: 60px;
.section-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 24px;
.section-title {
font-family: 'Source Han Sans SC', sans-serif;
font-weight: 600;
font-size: 28px;
line-height: 38px;
color: #141F38;
margin: 0;
}
.more-link {
display: flex;
align-items: center;
gap: 2px;
cursor: pointer;
transition: color 0.3s;
span {
font-family: 'Source Han Sans SC', sans-serif;
font-weight: 400;
font-size: 16px;
line-height: 24px;
color: rgba(20, 31, 56, 0.3);
}
.el-icon {
width: 17px;
height: 17px;
color: rgba(20, 31, 56, 0.3);
}
&:hover {
span, .el-icon {
color: rgba(20, 31, 56, 0.6);
}
}
}
}
.article-grid {
display: grid;
grid-template-columns: repeat(3, 1fr);
gap: 24px;
}
.loading-container,
.empty-container {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
padding: 60px 0;
min-height: 200px;
p {
margin-top: 16px;
color: #909399;
font-size: 14px;
}
}
.loading-spinner {
width: 40px;
height: 40px;
border: 3px solid #e4e7ed;
border-top-color: #E7000B;
border-radius: 50%;
animation: spin 0.8s linear infinite;
}
}
@keyframes spin {
to { transform: rotate(360deg); }
}
</style>