2025-10-27 17:29:25 +08:00
|
|
|
|
<template>
|
2025-10-27 19:05:56 +08:00
|
|
|
|
<div class="home-view">
|
|
|
|
|
|
<!-- 轮播横幅区域 -->
|
|
|
|
|
|
<div class="banner-section">
|
2025-10-28 19:04:35 +08:00
|
|
|
|
<!-- 加载状态 -->
|
|
|
|
|
|
<div v-if="loading" class="banner-loading">
|
|
|
|
|
|
<div class="loading-spinner"></div>
|
|
|
|
|
|
<p>加载中...</p>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
<!-- 轮播图 -->
|
2025-10-27 19:05:56 +08:00
|
|
|
|
<Carousel
|
2025-10-28 19:04:35 +08:00
|
|
|
|
v-else-if="banners.length > 0"
|
2025-10-27 19:05:56 +08:00
|
|
|
|
:items="banners"
|
|
|
|
|
|
:interval="5000"
|
|
|
|
|
|
:active-icon="dangIcon"
|
|
|
|
|
|
indicator-position="bottom-right"
|
|
|
|
|
|
>
|
|
|
|
|
|
<template #default="{ item }">
|
|
|
|
|
|
<BannerCard :banner="item" />
|
|
|
|
|
|
</template>
|
|
|
|
|
|
</Carousel>
|
2025-10-28 19:04:35 +08:00
|
|
|
|
|
|
|
|
|
|
<!-- 空状态 -->
|
|
|
|
|
|
<div v-else class="banner-empty">
|
|
|
|
|
|
<p>暂无轮播内容</p>
|
|
|
|
|
|
</div>
|
2025-10-27 19:05:56 +08:00
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
<!-- 热门资源推荐 -->
|
|
|
|
|
|
<div class="section">
|
|
|
|
|
|
<div class="section-header">
|
|
|
|
|
|
<h2 class="section-title">热门资源推荐</h2>
|
2025-10-31 19:13:21 +08:00
|
|
|
|
<div class="more-link" @click="handleMoreClick('hot')">
|
2025-10-27 19:05:56 +08:00
|
|
|
|
<span>查看更多</span>
|
|
|
|
|
|
<el-icon><ArrowRight /></el-icon>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
2025-10-31 19:13:21 +08:00
|
|
|
|
<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>
|
2025-10-27 19:05:56 +08:00
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
<!-- 思政新闻概览 -->
|
|
|
|
|
|
<div class="section">
|
|
|
|
|
|
<div class="section-header">
|
|
|
|
|
|
<h2 class="section-title">思政新闻概览</h2>
|
2025-10-31 19:13:21 +08:00
|
|
|
|
<div class="more-link" @click="handleMoreClick('ideological')">
|
2025-10-27 19:05:56 +08:00
|
|
|
|
<span>查看更多</span>
|
|
|
|
|
|
<el-icon><ArrowRight /></el-icon>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
2025-10-31 19:13:21 +08:00
|
|
|
|
<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>
|
2025-10-27 19:05:56 +08:00
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
<!-- 我的学习数据 -->
|
|
|
|
|
|
<div class="section">
|
|
|
|
|
|
<div class="section-header">
|
|
|
|
|
|
<h2 class="section-title">我的学习数据</h2>
|
2025-10-31 19:13:21 +08:00
|
|
|
|
<div class="more-link" @click="handleMoreClick('learning')">
|
2025-10-27 19:05:56 +08:00
|
|
|
|
<span>查看更多</span>
|
|
|
|
|
|
<el-icon><ArrowRight /></el-icon>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<LearningProgress />
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
2025-10-27 17:29:25 +08:00
|
|
|
|
</template>
|
2025-10-27 19:05:56 +08:00
|
|
|
|
|
2025-10-27 17:29:25 +08:00
|
|
|
|
<script setup lang="ts">
|
2025-10-28 19:04:35 +08:00
|
|
|
|
import { ref, onMounted } from 'vue';
|
2025-10-27 19:05:56 +08:00
|
|
|
|
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';
|
2025-10-28 19:04:35 +08:00
|
|
|
|
import { bannerApi } from '@/apis/resource/banner';
|
2025-10-31 19:13:21 +08:00
|
|
|
|
import { recommendApi } from '@/apis/homepage';
|
2025-10-28 19:04:35 +08:00
|
|
|
|
import { ElMessage } from 'element-plus';
|
2025-10-31 19:13:21 +08:00
|
|
|
|
import type { Banner, ResourceRecommendVO } from '@/types';
|
2025-10-27 19:05:56 +08:00
|
|
|
|
import dangIcon from '@/assets/imgs/dang.svg';
|
2025-10-31 19:13:21 +08:00
|
|
|
|
import { useRouter } from 'vue-router';
|
|
|
|
|
|
|
|
|
|
|
|
const router = useRouter();
|
2025-10-27 19:05:56 +08:00
|
|
|
|
|
2025-10-28 19:04:35 +08:00
|
|
|
|
// 轮播数据
|
|
|
|
|
|
const banners = ref<Banner[]>([]);
|
|
|
|
|
|
const loading = ref(false);
|
|
|
|
|
|
|
2025-10-31 19:13:21 +08:00
|
|
|
|
// 热门资源数据
|
|
|
|
|
|
const hotResources = ref<ResourceRecommendVO[]>([]);
|
|
|
|
|
|
const hotResourcesLoading = ref(false);
|
|
|
|
|
|
|
|
|
|
|
|
// 思政资源数据
|
|
|
|
|
|
const ideologicalResources = ref<ResourceRecommendVO[]>([]);
|
|
|
|
|
|
const ideologicalResourcesLoading = ref(false);
|
|
|
|
|
|
|
2025-10-28 19:04:35 +08:00
|
|
|
|
// 加载轮播图数据
|
|
|
|
|
|
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;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-10-31 19:13:21 +08:00
|
|
|
|
// 加载热门资源数据
|
|
|
|
|
|
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');
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-10-28 19:04:35 +08:00
|
|
|
|
// 组件挂载时加载数据
|
|
|
|
|
|
onMounted(() => {
|
|
|
|
|
|
loadBanners();
|
2025-10-31 19:13:21 +08:00
|
|
|
|
loadHotResources();
|
|
|
|
|
|
loadIdeologicalResources();
|
2025-10-28 19:04:35 +08:00
|
|
|
|
});
|
2025-10-27 17:29:25 +08:00
|
|
|
|
</script>
|
2025-10-27 19:05:56 +08:00
|
|
|
|
|
2025-10-27 17:29:25 +08:00
|
|
|
|
<style lang="scss" scoped>
|
2025-10-27 19:05:56 +08:00
|
|
|
|
.home-view {
|
|
|
|
|
|
background-color: #F9F9F9;
|
|
|
|
|
|
min-height: 100vh;
|
|
|
|
|
|
padding-bottom: 60px;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.banner-section {
|
|
|
|
|
|
width: 100%;
|
|
|
|
|
|
height: 30vh;
|
2025-10-28 19:04:35 +08:00
|
|
|
|
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); }
|
2025-10-27 19:05:56 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.section {
|
2025-12-02 10:58:15 +08:00
|
|
|
|
// max-width: 1440px;
|
2025-10-27 19:05:56 +08:00
|
|
|
|
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 {
|
2025-12-01 18:37:43 +08:00
|
|
|
|
font-family: 'Source Han Sans SC', sans-serif;
|
2025-10-27 19:05:56 +08:00
|
|
|
|
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 {
|
2025-12-01 18:37:43 +08:00
|
|
|
|
font-family: 'Source Han Sans SC', sans-serif;
|
2025-10-27 19:05:56 +08:00
|
|
|
|
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;
|
|
|
|
|
|
}
|
2025-10-31 19:13:21 +08:00
|
|
|
|
|
|
|
|
|
|
.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); }
|
2025-10-27 19:05:56 +08:00
|
|
|
|
}
|
2025-10-27 17:29:25 +08:00
|
|
|
|
</style>
|