修改首页内容

This commit is contained in:
2025-11-04 11:51:55 +08:00
parent 0c23f55cb1
commit 06f0215a29

View File

@@ -8,32 +8,30 @@
arrow="always" arrow="always"
v-if="carouselImages.length > 0" v-if="carouselImages.length > 0"
> >
<!-- 动态循环生成轮播项 -->
<el-carousel-item v-for="(item, index) in carouselImages" :key="item.id || index"> <el-carousel-item v-for="(item, index) in carouselImages" :key="item.id || index">
<div class="carousel-item"> <div class="carousel-item">
<!-- 绑定接口返回的图片URL -->
<img :src="item.image_url" :alt="item.alt || `轮播图${index+1}`" class="carousel-img"> <img :src="item.image_url" :alt="item.alt || `轮播图${index+1}`" class="carousel-img">
<div class="carousel-caption"> <div class="carousel-caption">
<!-- 标题和描述可后续扩展为接口返回字段当前先用默认值 -->
<h3>{{ item.title || defaultTitles[index] }}</h3> <h3>{{ item.title || defaultTitles[index] }}</h3>
<p>{{ item.desc || defaultDescs[index] }}</p> <p>{{ item.desc || defaultDescs[index] }}</p>
</div> </div>
</div> </div>
</el-carousel-item> </el-carousel-item>
</el-carousel> </el-carousel>
<!-- 新闻动态 --> <!-- 新闻动态 -->
<div class="news-section"> <div class="news-section">
<div class="section-header"> <div class="section-header">
<h2 style="font-family: 'Source Han Serif CN', '思源宋体 CN', serif; font-size: 55px; margin: 0; padding: 0;"> <h2 style="font-family: 'Source Han Serif CN', '思源宋体 CN', serif; font-size: 55px; margin: 0; padding: 0;">
新闻动态<span class="en-title" style="font-family: 'Source Han Serif CN', '思源宋体 CN', serif;">News</span> 新闻动态<span class="en-title" style="font-family: 'Source Han Serif CN', '思源宋体 CN', serif;">News</span>
</h2> </h2>
<el-link class="more-btn" :to="{ path: '/news-more' }">More</el-link> <router-link to="/news-more" class="more-btn">More</router-link>
</div> </div>
<div class="news-cards"> <div class="news-cards">
<el-card <el-card
class="news-card" class="news-card"
v-for="(news, index) in newsList" v-for="(news, index) in newsList.slice(0, 3)"
:key="index" :key="news.id"
shadow="hover" shadow="hover"
:body-style="{ padding: '0px' }" :body-style="{ padding: '0px' }"
> >
@@ -41,37 +39,63 @@
<div class="card-content"> <div class="card-content">
<h3 class="card-title">{{ news.title }}</h3> <h3 class="card-title">{{ news.title }}</h3>
<p class="card-desc">{{ news.desc }}</p> <p class="card-desc">{{ news.desc }}</p>
<el-button type="text" class="detail-btn"> <div class="detail-btn" @click="handleDetailClick(news, 'news')">
<el-icon><ArrowRight /></el-icon> 详情 <span class="arrow-circle">
</el-button> <el-icon><ArrowRight /></el-icon>
</span>
<span class="detail-text">详情</span>
</div>
</div> </div>
</el-card> </el-card>
</div> </div>
<div class="featured-news" v-if="featuredNews">
<div class="featured-img-container">
<img :src="featuredNews.imageUrl" :alt="featuredNews.title" class="featured-img">
</div>
<div class="featured-content">
<div class="featured-header">
<h3 class="featured-title">
<span class="prefix">最新成果</span>
{{ featuredNews.title }}
</h3>
<el-link class="featured-more" @click="handleDetailClick(featuredNews, 'news')">More</el-link>
</div>
<div class="featured-date">{{ featuredNews.date }}</div>
<div class="featured-desc">{{ featuredNews.fullDesc }}</div>
</div>
</div>
</div> </div>
<!-- 案例资源 --> <!-- 案例资源 -->
<div class="news-section"> <div class="news-section">
<div class="section-header"> <div class="section-header">
<h2 style="font-family: 'Source Han Serif CN', '思源宋体 CN', serif; font-size: 55px; margin: 0; padding: 0;"> <h2 style="font-family: 'Source Han Serif CN', '思源宋体 CN', serif; font-size: 55px; margin: 0; padding: 0;">
案例资源<span class="en-title" style="font-family: 'Source Han Serif CN', '思源宋体 CN', serif;">News</span> 案例资源<span class="en-title" style="font-family: 'Source Han Serif CN', '思源宋体 CN', serif;">Cases</span>
</h2> </h2>
<el-link class="more-btn" :to="{ path: '/resource-more' }">More</el-link> <router-link to="/case-resources" class="more-btn">More</router-link>
</div> </div>
<div class="news-cards"> <div class="news-cards">
<el-card <el-card
class="news-card" class="news-card"
v-for="(resource, index) in resourceList" v-for="(resource, index) in resourceList"
:key="index" :key="resource.id"
shadow="hover" shadow="hover"
:body-style="{ padding: '0px' }" :body-style="{ padding: '0px' }"
> >
<img :src="resource.imageUrl" alt="案例资源图片" class="card-img"> <img :src="resource.imageUrl" alt="案例资源图片" class="card-img">
<div class="card-content"> <div class="card-content">
<h3 class="card-title">{{ resource.title }}</h3> <h3 class="card-title">{{ resource.title }}</h3>
<p class="card-desc">{{ resource.desc }}</p> <p class="card-desc">
<el-button type="text" class="detail-btn">
<el-icon><ArrowRight /></el-icon> 详情 <br>
</el-button> {{ resource.desc }}
</p>
<div class="detail-btn" @click="handleDetailClick(resource, 'case')">
<span class="arrow-circle">
<el-icon><ArrowRight /></el-icon>
</span>
<span class="detail-text">详情</span>
</div>
</div> </div>
</el-card> </el-card>
</div> </div>
@@ -81,25 +105,29 @@
<div class="news-section"> <div class="news-section">
<div class="section-header"> <div class="section-header">
<h2 style="font-family: 'Source Han Serif CN', '思源宋体 CN', serif; font-size: 55px; margin: 0; padding: 0;"> <h2 style="font-family: 'Source Han Serif CN', '思源宋体 CN', serif; font-size: 55px; margin: 0; padding: 0;">
服务<span class="en-title" style="font-family: 'Source Han Serif CN', '思源宋体 CN', serif;">News</span> 服务<span class="en-title" style="font-family: 'Source Han Serif CN', '思源宋体 CN', serif;">Services</span>
</h2> </h2>
<el-link class="more-btn" :to="{ path: '/community-more' }">More</el-link> <router-link to="/social-service" class="more-btn">More</router-link>
</div> </div>
<div class="news-cards"> <div class="news-cards">
<el-card <el-card
class="news-card" class="news-card"
v-for="(service, index) in communityList" v-for="(service, index) in communityList"
:key="index" :key="service.id"
shadow="hover" shadow="hover"
:body-style="{ padding: '0px' }" :body-style="{ padding: '0px' }"
> >
<img :src="service.imageUrl" alt="社区服务图片" class="card-img"> <img :src="service.imageUrl" alt="社区服务图片" class="card-img">
<div class="card-content"> <div class="card-content">
<h3 class="card-title">{{ service.title }}</h3> <h3 class="card-title">{{ service.title }}</h3>
<p class="card-subtitle">{{ service.subtitle }}</p>
<p class="card-desc">{{ service.desc }}</p> <p class="card-desc">{{ service.desc }}</p>
<el-button type="text" class="detail-btn"> <div class="detail-btn" @click="handleDetailClick(service, 'service')">
<el-icon><ArrowRight /></el-icon> 详情 <span class="arrow-circle">
</el-button> <el-icon><ArrowRight /></el-icon>
</span>
<span class="detail-text">详情</span>
</div>
</div> </div>
</el-card> </el-card>
</div> </div>
@@ -109,15 +137,21 @@
<script lang="ts" setup> <script lang="ts" setup>
import { ref, onMounted } from 'vue'; import { ref, onMounted } from 'vue';
import { ElCarousel, ElCarouselItem, ElLink, ElCard, ElButton, ElIcon } from 'element-plus'; import { ElCarousel, ElCarouselItem, ElLink, ElCard, ElIcon, ElLoading, ElMessage } from 'element-plus';
import { ArrowRight } from '@element-plus/icons-vue'; import { ArrowRight } from '@element-plus/icons-vue';
import axios from 'axios'; import axios from 'axios';
import { useRouter } from 'vue-router';
// 数据结构 // 数据结构
interface NewsItem { interface NewsItem {
id?: number;
imageUrl: string; imageUrl: string;
title: string; title: string;
desc: string; subtitle?: string;
desc: string; // 必需属性
fullDesc?: string;
date?: string;
tutorName?: string;
} }
interface Article { interface Article {
@@ -125,12 +159,66 @@ interface Article {
title: string; title: string;
cover: string; cover: string;
excerpt: string; excerpt: string;
publish_date?: string;
content?: string;
}
interface TeachingCase {
id: number;
title: string;
tutor_name: string;
tutor_title: string;
student_names: string;
content: string;
cover_url: string;
create_time: string;
}
interface CaseResponse {
total: number;
list: TeachingCase[];
}
interface SocialServiceItem {
id: number;
title: string;
subtitle: string;
cover_url: string;
intro: string;
content: string;
publish_time: string;
}
interface SocialServiceResponse {
code: number;
message: string;
data: {
total: number;
list: SocialServiceItem[];
};
} }
// 响应式数据 // 响应式数据
const newsList = ref<NewsItem[]>([]); const newsList = ref<NewsItem[]>([]);
const resourceList = ref<NewsItem[]>([]); const resourceList = ref<NewsItem[]>([]);
const communityList = ref<NewsItem[]>([]); const communityList = ref<NewsItem[]>([]);
const featuredNews = ref<NewsItem | null>(null);
const router = useRouter();
// 详情点击事件新增type参数区分内容类型
const handleDetailClick = (item: NewsItem, type: 'news' | 'case' | 'service') => {
if (item.id) {
router.push({
path: '/detail',
query: {
id: item.id,
type: type
}
});
} else {
ElMessage.warning('该内容暂无详情');
}
};
// HTML转纯文本工具函数 // HTML转纯文本工具函数
function stripHtml(htmlStr: string): string { function stripHtml(htmlStr: string): string {
@@ -139,48 +227,123 @@ function stripHtml(htmlStr: string): string {
return doc.body.textContent || ""; return doc.body.textContent || "";
} }
// 通用请求函数 // 格式化日期
function formatDate(dateStr?: string): string {
if (!dateStr) return '';
const date = new Date(dateStr);
return `${date.getFullYear()}${date.getMonth() + 1}${date.getDate()}`;
}
// 新闻数据请求
const fetchArticlesByTopic = async (topic: string): Promise<NewsItem[]> => { const fetchArticlesByTopic = async (topic: string): Promise<NewsItem[]> => {
const loading = ElLoading.service({ text: '加载中...' });
try { try {
const response = await fetch('http://localhost:8080/api/articles/getarticle', { const response = await fetch('http://localhost:8080/api/articles/getarticle', {
method: 'POST', method: 'POST',
headers: { 'Content-Type': 'application/json' }, headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ page: 1, size: 3, topic }) body: JSON.stringify({ page: 1, size: 4, topic })
}); });
if (!response.ok) throw new Error(`HTTP error! status: ${response.status}`); if (!response.ok) throw new Error(`HTTP error! status: ${response.status}`);
const data = await response.json(); const data = await response.json();
if (data && Array.isArray(data.Article_list)) { if (data && Array.isArray(data.Article_list)) {
return data.Article_list.map((article: Article) => { return data.Article_list.map((article: Article) => ({
const plainTextDesc = stripHtml(article.excerpt); id: article.id,
const truncatedDesc = plainTextDesc.length > 100 ? `${plainTextDesc.substring(0, 100)}...` : plainTextDesc;
return {
imageUrl: article.cover, imageUrl: article.cover,
title: article.title, title: article.title,
desc: truncatedDesc, desc: stripHtml(article.excerpt).slice(0, 100) + '...',
}; fullDesc: stripHtml(article.content || ''),
}); date: article.publish_date ? formatDate(article.publish_date) : ''
}));
} }
return []; return [];
} catch (error) { } catch (error) {
console.error(`Failed to fetch ${topic}:`, error); console.error(`Failed to fetch ${topic}:`, error);
ElMessage.error('新闻数据加载失败');
return []; return [];
} finally {
loading.close();
}
};
// 案例资源请求
const fetchTeachingCases = async (): Promise<NewsItem[]> => {
const loading = ElLoading.service({ text: '加载中...' });
try {
const response = await axios.post<CaseResponse>(
'http://localhost:8080/api/teaching-cases/list',
{ page: 1, size: 20, keyword: "", sort: 0 }
);
return response.data.list
.sort((a, b) => new Date(b.create_time).getTime() - new Date(a.create_time).getTime())
.slice(0, 3)
.map(caseItem => ({
id: caseItem.id,
imageUrl: caseItem.cover_url,
title: caseItem.title,
desc: stripHtml(caseItem.content).slice(0, 80) + '...',
tutorName: caseItem.tutor_name
}));
} catch (error) {
console.error('获取案例资源失败:', error);
ElMessage.error('案例资源加载失败');
return [];
} finally {
loading.close();
}
};
// 社区服务请求
const fetchSocialServices = async (): Promise<NewsItem[]> => {
const loading = ElLoading.service({ text: '加载中...' });
try {
const response = await axios.post<SocialServiceResponse>(
'http://localhost:8080/api/social-service/list',
{ page: 1, page_size: 20 }
);
if (response.data.code !== 0) throw new Error(response.data.message);
return response.data.data.list
.sort((a, b) => new Date(b.publish_time).getTime() - new Date(a.publish_time).getTime())
.slice(0, 3)
.map(service => ({
id: service.id,
imageUrl: service.cover_url,
title: service.title,
subtitle: service.subtitle,
desc: (service.intro || stripHtml(service.content)).slice(0, 100) + '...'
}));
} catch (error) {
console.error('获取社区服务失败:', error);
ElMessage.error('社区服务加载失败');
return [];
} finally {
loading.close();
} }
}; };
// 挂载时请求数据 // 挂载时请求数据
onMounted(async () => { onMounted(async () => {
const [newsData, resourceData, communityData] = await Promise.all([ const newsData = await fetchArticlesByTopic('新闻');
fetchArticlesByTopic('新闻'), newsList.value = newsData.slice(0, 3);
fetchArticlesByTopic('案例资源'), // 修复:补充必需的 desc 属性,与 NewsItem 接口一致
fetchArticlesByTopic('社区服务') featuredNews.value = newsData[3] || {
]); id: 4,
newsList.value = newsData; imageUrl: 'https://p11-flow-imagex-download-sign.byteimg.com/tos-cn-i-a9rns2rl98/5d52aa74480f44788c82993e586cb6e7.png~tplv-a9rns2rl98-resize-jpeg-v1.png',
resourceList.value = resourceData; title: '"中国网谷杯"乡村文创案例',
communityList.value = communityData; desc: '8月22日2025年武汉轻工大学创新创业大赛乡村文创赛道决赛落幕...', // 新增必需属性
fullDesc: '8月22日2025年武汉轻工大学创新创业大赛乡村文创赛道决赛落幕我校"中国网谷杯"乡村文创案例凭借创新设计理念和实用价值荣获金奖,相关成果将应用于乡村振兴实践项目。',
date: '2025年8月22日'
};
resourceList.value = await fetchTeachingCases();
communityList.value = await fetchSocialServices();
}); });
// 轮播图数据
const defaultTitles = [ const defaultTitles = [
'健康生活环境设计研究成果展', '健康生活环境设计研究成果展',
'2025健康设计国际学术研讨会', '2025健康设计国际学术研讨会',
@@ -192,39 +355,27 @@ const defaultDescs = [
'以设计赋能社区,提升居民生活品质' '以设计赋能社区,提升居民生活品质'
]; ];
interface CarouselImage { interface CarouselImage {
id?: number; // 可选数据库返回的ID id?: number;
image_url: string; // 图片URL必选 image_url: string;
sort?: number; // 可选,排序字段 sort?: number;
// 其他可选字段如title、desc等
title?: string; title?: string;
desc?: string; desc?: string;
alt?: string; alt?: string;
} }
// 2. 显式指定泛型类型为 CarouselImage[]
const carouselImages = ref<CarouselImage[]>([]); const carouselImages = ref<CarouselImage[]>([]);
onMounted(async () => { onMounted(async () => {
try { try {
// 发送 POST 请求JSON 体传参
const response = await axios.post( const response = await axios.post(
'http://localhost:8080/api/page-image/get', 'http://localhost:8080/api/page-image/get',
{ page: 'home' } // JSON 格式传递 page 参数 { page: 'home' }
); );
console.log("首页图片查询成功",response.data.images);
// 检查响应是否成功
if (response.data.message === '查询成功') { if (response.data.message === '查询成功') {
carouselImages.value = response.data.images;
// 按 sort 排序(处理可能的 undefined
carouselImages.value = response.data.images
console.log("赋值后:",carouselImages.value);
} }
} catch (error) { } catch (error) {
console.error('查询失败:', error); console.error('查询轮播图失败:', error);
// 兜底数据
carouselImages.value = [ carouselImages.value = [
{ image_url: 'https://picsum.photos/1920/400?random=1' }, { image_url: 'https://picsum.photos/1920/400?random=1' },
{ image_url: 'https://picsum.photos/1920/400?random=2' }, { image_url: 'https://picsum.photos/1920/400?random=2' },
@@ -232,8 +383,6 @@ onMounted(async () => {
]; ];
} }
}); });
</script> </script>
<style scoped> <style scoped>
@@ -271,7 +420,7 @@ onMounted(async () => {
font-size: 18px; font-size: 18px;
} }
/* 新闻列表样式 */ /* 公共区块样式 */
.news-section { .news-section {
max-width: 1200px; max-width: 1200px;
margin: 50px auto; margin: 50px auto;
@@ -299,23 +448,26 @@ onMounted(async () => {
.more-btn { .more-btn {
color: #999; color: #999;
font-size: 14px; font-size: 14px;
cursor: pointer;
} }
.more-btn:hover { .more-btn:hover {
color: #b50009; color: #b50009;
} }
/* 卡片样式 */ /* 卡片通用样式 */
.news-cards { .news-cards {
display: grid; display: grid;
grid-template-columns: repeat(3, 1fr); grid-template-columns: repeat(3, 1fr);
gap: 25px; gap: 25px;
margin-bottom: 30px;
} }
.news-card { .news-card {
border: 1px solid #e0e0e0; border: 1px solid #e0e0e0;
transition: all 0.3s ease; transition: all 0.3s ease;
display: flex; display: flex;
flex-direction: column; flex-direction: column;
height: 458px; height: 480px;
cursor: pointer;
} }
.news-card:hover { .news-card:hover {
transform: translateY(-5px); transform: translateY(-5px);
@@ -339,6 +491,13 @@ onMounted(async () => {
font-weight: 500; font-weight: 500;
color: #333; color: #333;
} }
.card-subtitle {
font-size: 14px;
color: #666;
margin: 0 0 10px 0;
font-style: italic;
line-height: 1.5;
}
.card-desc { .card-desc {
font-family: 'Source Han Sans CN', '思源黑体 CN', sans-serif; font-family: 'Source Han Sans CN', '思源黑体 CN', sans-serif;
font-size: 14px; font-size: 14px;
@@ -347,14 +506,114 @@ onMounted(async () => {
flex-grow: 1; flex-grow: 1;
margin-bottom: 15px; margin-bottom: 15px;
font-weight: bold; font-weight: bold;
overflow: hidden;
text-overflow: ellipsis;
display: -webkit-box;
-webkit-line-clamp: 4;
-webkit-box-orient: vertical;
} }
/* 详情按钮样式 */
.detail-btn { .detail-btn {
color: #b50009 !important;
display: flex; display: flex;
align-items: center; align-items: center;
gap: 5px; gap: 8px;
padding: 0 !important; cursor: pointer;
align-self: flex-start; align-self: flex-start;
transition: all 0.2s ease;
padding: 5px 0;
}
.detail-btn:hover {
opacity: 0.9;
transform: translateX(3px);
}
.arrow-circle {
width: 30px;
height: 30px;
border-radius: 50%;
background-color: #b50009;
color: #fff;
display: flex;
align-items: center;
justify-content: center;
font-size: 16px;
}
.detail-text {
color: #333;
font-size: 14px;
font-weight: 500;
}
/* 特色新闻样式 */
.featured-news {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 20px;
border: 1px solid #e0e0e0;
overflow: hidden;
transition: all 0.3s ease;
cursor: pointer;
}
.featured-news:hover {
box-shadow: 0 4px 12px rgba(0,0,0,0.1);
}
.featured-img-container {
height: 100%;
min-height: 280px;
}
.featured-img {
width: 100%;
height: 100%;
object-fit: cover;
display: block;
}
.featured-content {
padding: 30px;
display: flex;
flex-direction: column;
justify-content: center;
}
.featured-header {
display: flex;
justify-content: space-between;
align-items: flex-start;
margin-bottom: 15px;
}
.featured-title {
font-size: 22px;
font-weight: 600;
color: #333;
line-height: 1.4;
margin: 0;
}
.featured-title .prefix {
color: #666;
font-weight: 400;
}
.featured-more {
color: #b50009;
font-size: 14px;
align-self: flex-start;
margin-top: 5px;
cursor: pointer;
}
.featured-date {
color: #999;
font-size: 14px;
margin-bottom: 15px;
padding-bottom: 10px;
border-bottom: 1px dashed #e0e0e0;
}
.featured-desc {
font-size: 15px;
color: #666;
line-height: 1.8;
margin: 0;
}
/* 案例资源导师信息样式 */
.tutor-info {
font-weight: 500;
} }
/* 响应式调整 */ /* 响应式调整 */
@@ -362,6 +621,12 @@ onMounted(async () => {
.news-cards { .news-cards {
grid-template-columns: repeat(2, 1fr); grid-template-columns: repeat(2, 1fr);
} }
.featured-news {
grid-template-columns: 1fr;
}
.featured-img-container {
min-height: 220px;
}
} }
@media (max-width: 768px) { @media (max-width: 768px) {
.carousel-caption h3 { .carousel-caption h3 {
@@ -373,5 +638,14 @@ onMounted(async () => {
.news-cards { .news-cards {
grid-template-columns: 1fr; grid-template-columns: 1fr;
} }
.card-title {
font-size: 22px;
}
.featured-content {
padding: 20px;
}
.featured-title {
font-size: 20px;
}
} }
</style> </style>