完整社会服务web

This commit is contained in:
2025-11-04 11:50:57 +08:00
parent 0b03178feb
commit e614a6895d

View File

@@ -11,7 +11,6 @@
}" }"
> >
<div class="banner-container"> <div class="banner-container">
</div> </div>
</div> </div>
@@ -75,7 +74,7 @@
> >
<!-- 封面图添加默认占位图 --> <!-- 封面图添加默认占位图 -->
<img <img
:src="item.cover_url || 'https://picsum.photos/id/237/120/120'" :src="item.cover_image || 'https://picsum.photos/id/237/120/120'"
:alt="item.title" :alt="item.title"
class="item-img" class="item-img"
:onerror="defaultImg" :onerror="defaultImg"
@@ -99,13 +98,13 @@
background background
layout="total, sizes, prev, pager, next, jumper" layout="total, sizes, prev, pager, next, jumper"
:total="total" :total="total"
v-model:current-page="pagination[currentTab].currentPage" :current-page="pagination[currentTab].currentPage"
v-model:page-size="pagination[currentTab].pageSize" :page-size="pagination[currentTab].pageSize"
:page-sizes="[10, 20, 50]" :page-sizes="[10, 20, 50]"
@size-change="handlePageSizeChange" @size-change="handlePageSizeChange"
@current-change="handleCurrentPageChange" @current-change="handleCurrentPageChange"
:disabled="loading" :disabled="loading"
></el-pagination> />
</div> </div>
</div> </div>
@@ -117,7 +116,7 @@
<!-- 封面图 --> <!-- 封面图 -->
<div class="article-cover"> <div class="article-cover">
<img <img
:src="currentArticle.cover_url || 'https://picsum.photos/id/237/1200/600'" :src="currentArticle.cover_image || 'https://picsum.photos/id/237/1200/600'"
:alt="currentArticle.title" :alt="currentArticle.title"
class="cover-img" class="cover-img"
:onerror="defaultImg" :onerror="defaultImg"
@@ -150,7 +149,7 @@
@click="selectMainArticle(item)" @click="selectMainArticle(item)"
> >
<img <img
:src="item.cover_url || 'https://picsum.photos/id/237/300/180'" :src="item.cover_image || 'https://picsum.photos/id/237/300/180'"
:alt="item.title" :alt="item.title"
class="card-img" class="card-img"
:onerror="defaultImg" :onerror="defaultImg"
@@ -171,21 +170,41 @@
</div> </div>
</main> </main>
<!-- 页脚 -->
</div> </div>
</el-config-provider> </el-config-provider>
</template> </template>
<script> <script lang="ts">
import axios from 'axios'; import axios from 'axios';
// 导入Element Plus组件和中文语言包
import { ElMessage, ElPagination, ElConfigProvider } from 'element-plus'; import { ElMessage, ElPagination, ElConfigProvider } from 'element-plus';
import zhCn from 'element-plus/es/locale/lang/zh-cn'; import zhCn from 'element-plus/es/locale/lang/zh-cn';
// 基础API地址
const base_url = 'http://localhost:8080/api'; const base_url = 'http://localhost:8080/api';
// 定义图片项类型
interface ImageItem {
image_url: string;
sort: number;
}
// 文章类型(与后端字段一致)
type Article = {
id: number;
title: string;
cover_image?: string;
subtitle?: string; // 新增字段
content_summary?: string;
publish_time: string;
content?: string;
};
interface PaginationState {
currentPage: number;
pageSize: number;
}
type TabKey = 'schoolEnterprise' | 'researchInternship' | 'ruralGovernment';
export default { export default {
name: 'WorkstationPage', name: 'WorkstationPage',
components: { components: {
@@ -194,75 +213,63 @@ export default {
}, },
data() { data() {
return { return {
zhCn, // 中文语言包 zhCn,
currentTab: 'schoolEnterprise', currentTab: 'schoolEnterprise' as TabKey,
// 存储各分类的完整数据
articles: { articles: {
schoolEnterprise: [], schoolEnterprise: [] as Article[],
researchInternship: [], researchInternship: [] as Article[],
ruralGovernment: [] ruralGovernment: [] as Article[]
}, },
currentArticle: null, currentArticle: null as Article | null,
relatedArticles: [], relatedArticles: [] as Article[],
allArticles: [], // 分页当前页数据 allArticles: [] as Article[],
total: 0, // 分页总条数 total: 0 as number,
loading: false, loading: false as boolean,
showAllList: false, showAllList: false as boolean,
// 每个分类独立的分页状态
pagination: { pagination: {
schoolEnterprise: { currentPage: 1, pageSize: 10 }, schoolEnterprise: { currentPage: 1, pageSize: 10 } as PaginationState,
researchInternship: { currentPage: 1, pageSize: 10 }, researchInternship: { currentPage: 1, pageSize: 10 } as PaginationState,
ruralGovernment: { currentPage: 1, pageSize: 10 } ruralGovernment: { currentPage: 1, pageSize: 10 } as PaginationState
}, } as Record<TabKey, PaginationState>,
// 封面图加载失败默认图 defaultImg: "this.src='https://picsum.photos/id/237/120/120'" as string,
defaultImg: "this.src='https://picsum.photos/id/237/120/120'", bannerUrl: '' as string
// 横幅图片地址
bannerUrl: ''
}; };
}, },
computed: { computed: {
currentArticles() { currentArticles(): Article[] {
return this.articles[this.currentTab] || []; return this.articles[this.currentTab] || [];
}, },
// 当前分类的分页配置 currentPagination(): PaginationState {
currentPagination() {
return this.pagination[this.currentTab]; return this.pagination[this.currentTab];
} }
}, },
watch: { watch: {
currentTab: { currentTab: {
immediate: true, immediate: true,
handler(newTab) { handler(newTab: TabKey) {
// 切换分类时,加载首页数据
this.fetchHomeData(newTab); this.fetchHomeData(newTab);
this.showAllList = false; this.showAllList = false;
}, },
}, },
}, },
created() { created() {
// 组件创建时获取横幅图片
this.fetchBannerImage(); this.fetchBannerImage();
}, },
methods: { methods: {
switchTab(tab) { switchTab(tab: TabKey) {
this.currentTab = tab; this.currentTab = tab;
}, },
/**
* 获取横幅图片使用POST请求
*/
async fetchBannerImage() { async fetchBannerImage() {
try { try {
const response = await axios.post( const response = await axios.post(
`${base_url}/page-image/get`, `${base_url}/page-image/get`,
{ page: 'SocialService' }, // 请求参数 { page: 'SocialService' },
{ headers: { 'Content-Type': 'application/json' } } { headers: { 'Content-Type': 'application/json' } }
); );
// 处理返回结果
if (response.data && Array.isArray(response.data.images) && response.data.images.length > 0) { if (response.data && Array.isArray(response.data.images) && response.data.images.length > 0) {
// 查找排序为1的图片如果有多个 const targetImage: ImageItem = response.data.images.find((img: ImageItem) => img.sort === 1) || response.data.images[0];
const targetImage = response.data.images.find(img => img.sort === 1) || response.data.images[0];
this.bannerUrl = targetImage.image_url; this.bannerUrl = targetImage.image_url;
} else { } else {
ElMessage.warning('未找到横幅图片数据'); ElMessage.warning('未找到横幅图片数据');
@@ -273,17 +280,14 @@ export default {
} }
}, },
// 首页数据加载只加载第一页取前4条用于首页展示 async fetchHomeData(category: TabKey) {
async fetchHomeData(category) {
this.loading = true; this.loading = true;
try { try {
const url = this.getCategoryUrl(category); const url = this.getCategoryUrl(category);
const payload = { page: 1, page_size: 4 }; // 首页只查4条1条主文章+3条相关 const payload = { page: 1, page_size: 4 };
const response = await axios.post(url, payload, { const response = await axios.post(url, payload, {
headers: { 'Content-Type': 'application/json' }, headers: { 'Content-Type': 'application/json' },
}); });
this.handleResponseData(response, category, true); this.handleResponseData(response, category, true);
} catch (error) { } catch (error) {
this.handleFetchError(error, '首页数据'); this.handleFetchError(error, '首页数据');
@@ -292,17 +296,14 @@ export default {
} }
}, },
// 分页数据加载(全部项目页用) async fetchPagedData(category: TabKey, page = 1, pageSize = 10) {
async fetchPagedData(category, page = 1, pageSize = 10) {
this.loading = true; this.loading = true;
try { try {
const url = this.getCategoryUrl(category); const url = this.getCategoryUrl(category);
const payload = { page, page_size: pageSize }; const payload = { page, page_size: pageSize };
const response = await axios.post(url, payload, { const response = await axios.post(url, payload, {
headers: { 'Content-Type': 'application/json' }, headers: { 'Content-Type': 'application/json' },
}); });
this.handleResponseData(response, category, false); this.handleResponseData(response, category, false);
} catch (error) { } catch (error) {
this.handleFetchError(error, '分页数据'); this.handleFetchError(error, '分页数据');
@@ -311,35 +312,29 @@ export default {
} }
}, },
// 处理接口响应数据 handleResponseData(response: any, category: TabKey, isHome = true) {
handleResponseData(response, category, isHome = true) {
const resData = response.data || {}; const resData = response.data || {};
if (resData.code !== 0) throw new Error(resData.message || '接口返回错误'); if (resData.code !== 0) throw new Error(resData.message || '接口返回错误');
const rawList = resData.data?.list || []; const rawList = resData.data?.list || [];
if (!Array.isArray(rawList)) throw new Error('list 不是数组'); if (!Array.isArray(rawList)) throw new Error('list 不是数组');
// 按发布时间倒序排序 const sortedList = rawList.sort((a: Article, b: Article) =>
const sortedList = rawList.sort((a, b) => new Date(b.publish_time).getTime() - new Date(a.publish_time).getTime()
new Date(b.publish_time) - new Date(a.publish_time)
); );
// 存储总条数(分页用)
this.total = resData.data?.total || 0; this.total = resData.data?.total || 0;
if (isHome) { if (isHome) {
// 首页逻辑:存储完整数据,设置主文章和相关文章
this.articles[category] = sortedList; this.articles[category] = sortedList;
this.currentArticle = sortedList[0] || null; this.currentArticle = sortedList[0] || null;
this.relatedArticles = sortedList.slice(1, 4) || []; this.relatedArticles = sortedList.slice(1, 4) || [];
} else { } else {
// 分页逻辑:存储当前页数据
this.allArticles = sortedList; this.allArticles = sortedList;
} }
}, },
// 获取分类对应的接口URL getCategoryUrl(category: TabKey): string {
getCategoryUrl(category) {
switch (category) { switch (category) {
case 'schoolEnterprise': case 'schoolEnterprise':
return `${base_url}/social-service/list`; return `${base_url}/social-service/list`;
@@ -352,13 +347,11 @@ export default {
} }
}, },
// 错误处理 handleFetchError(error: any, type: string) {
handleFetchError(error, type) {
console.error(`获取${type}失败:`, error); console.error(`获取${type}失败:`, error);
const errMsg = error.message || '网络异常'; const errMsg = error.message || '网络异常';
ElMessage?.error?.(`加载${type}失败:${errMsg}`) || alert(`加载失败:${errMsg}`); ElMessage?.error?.(`加载${type}失败:${errMsg}`) || alert(`加载失败:${errMsg}`);
// 重置数据状态
this.allArticles = []; this.allArticles = [];
this.total = 0; this.total = 0;
if (type === '首页数据') { if (type === '首页数据') {
@@ -368,12 +361,9 @@ export default {
} }
}, },
// 查看全部:切换到分页页并加载第一页数据
handleViewAll() { handleViewAll() {
this.showAllList = true; this.showAllList = true;
// 重置当前分类的分页状态为第一页
this.currentPagination.currentPage = 1; this.currentPagination.currentPage = 1;
// 加载分页第一页数据
this.fetchPagedData( this.fetchPagedData(
this.currentTab, this.currentTab,
this.currentPagination.currentPage, this.currentPagination.currentPage,
@@ -381,39 +371,28 @@ export default {
); );
}, },
// 页码切换 handleCurrentPageChange(val: number) {
handleCurrentPageChange(val) {
this.currentPagination.currentPage = val; this.currentPagination.currentPage = val;
this.fetchPagedData( this.fetchPagedData(this.currentTab, val, this.currentPagination.pageSize);
this.currentTab,
val,
this.currentPagination.pageSize
);
}, },
// 每页条数切换 handlePageSizeChange(val: number) {
handlePageSizeChange(val) {
this.currentPagination.pageSize = val; this.currentPagination.pageSize = val;
this.currentPagination.currentPage = 1; // 切换条数时重置为第一页 this.currentPagination.currentPage = 1;
this.fetchPagedData( this.fetchPagedData(this.currentTab, 1, val);
this.currentTab,
1,
val
);
}, },
selectMainArticle(article) { selectMainArticle(article: Article) {
this.currentArticle = article; this.currentArticle = article;
// 重新筛选相关文章排除当前选中项取前3条
this.relatedArticles = this.currentArticles this.relatedArticles = this.currentArticles
.filter(a => a.id !== article.id) .filter(a => a.id !== article.id)
.sort((a, b) => new Date(b.publish_time) - new Date(a.publish_time)) .sort((a: Article, b: Article) => new Date(b.publish_time).getTime() - new Date(a.publish_time).getTime())
.slice(0, 3); .slice(0, 3);
this.showAllList = false; this.showAllList = false;
window.scrollTo({ top: 0, behavior: 'smooth' }); window.scrollTo({ top: 0, behavior: 'smooth' });
}, },
formatDate(dateStr) { formatDate(dateStr: string): string {
if (!dateStr) return ''; if (!dateStr) return '';
const date = new Date(dateStr); const date = new Date(dateStr);
return date.toLocaleDateString('zh-CN', { return date.toLocaleDateString('zh-CN', {
@@ -423,8 +402,8 @@ export default {
}); });
}, },
getTabName(tab) { getTabName(tab: TabKey): string {
const map = { const map: Record<TabKey, string> = {
schoolEnterprise: '校企合作', schoolEnterprise: '校企合作',
researchInternship: '研究实习项目', researchInternship: '研究实习项目',
ruralGovernment: '乡村政府项目', ruralGovernment: '乡村政府项目',
@@ -453,7 +432,7 @@ export default {
.cover-img { width: 100%; height: auto; max-height: 500px; object-fit: cover; transition: transform 0.6s ease; } .cover-img { width: 100%; height: auto; max-height: 500px; object-fit: cover; transition: transform 0.6s ease; }
.article-cover:hover .cover-img { transform: scale(1.02); } .article-cover:hover .cover-img { transform: scale(1.02); }
.article-body { max-width: 800px; margin: 0 auto; } .article-body { max-width: 800px; margin: 0 auto; }
.article-title { font-family: 'Source Han Serif CN', '思源宋体 CN';font-size: 2.2rem; color: #1D2129; margin-bottom: 1rem; font-weight: 700; line-height: 1.4; } .article-title { font-family: 'Source Han Serif CN', '思源宋体 CN'; font-size: 2.2rem; color: #1D2129; margin-bottom: 1rem; font-weight: 700; line-height: 1.4; }
.article-subtitle { color: #4B5563; font-size: 1.3rem; margin-bottom: 1rem; font-weight: 500; font-family: 'Source Han Serif CN', '思源宋体 CN'; } .article-subtitle { color: #4B5563; font-size: 1.3rem; margin-bottom: 1rem; font-weight: 500; font-family: 'Source Han Serif CN', '思源宋体 CN'; }
.article-date { color: #9CA3AF; font-size: 1rem; margin-bottom: 2.5rem; font-weight: 500; } .article-date { color: #9CA3AF; font-size: 1rem; margin-bottom: 2.5rem; font-weight: 500; }
.article-content { color: #4B5563; line-height: 1.95; font-size: 1.1rem; text-align: left; margin: 0 auto; } .article-content { color: #4B5563; line-height: 1.95; font-size: 1.1rem; text-align: left; margin: 0 auto; }
@@ -476,9 +455,6 @@ export default {
.empty-state { text-align: center; padding: 4rem 0; color: #9CA3AF; font-size: 1.1rem; } .empty-state { text-align: center; padding: 4rem 0; color: #9CA3AF; font-size: 1.1rem; }
.footer { background-color: #F7F8FA; padding: 2.5rem 0; margin-top: 5rem; border-top: 1px solid #E5E6EB; }
.copyright { text-align: center; color: #86909C; font-size: 0.95rem; }
.all-articles-page { max-width: 1000px; margin: 0 auto; } .all-articles-page { max-width: 1000px; margin: 0 auto; }
.breadcrumbs { margin-bottom: 2rem; font-size: 0.95rem; color: #6B7280; } .breadcrumbs { margin-bottom: 2rem; font-size: 0.95rem; color: #6B7280; }
.breadcrumbs a { color: #165DFF; text-decoration: none; margin-right: 0.5rem; } .breadcrumbs a { color: #165DFF; text-decoration: none; margin-right: 0.5rem; }
@@ -496,14 +472,11 @@ export default {
.item-subtitle { color: #4B5563; font-size: 1rem; margin: 0 0 0.75rem; font-style: italic; display: -webkit-box; -webkit-line-clamp: 1; -webkit-box-orient: vertical; overflow: hidden; } .item-subtitle { color: #4B5563; font-size: 1rem; margin: 0 0 0.75rem; font-style: italic; display: -webkit-box; -webkit-line-clamp: 1; -webkit-box-orient: vertical; overflow: hidden; }
.item-date { color: #9CA3AF; font-size: 0.9rem; } .item-date { color: #9CA3AF; font-size: 0.9rem; }
/* 分页容器样式 */
.pagination-container { margin-top: 3rem; display: flex; justify-content: center; padding-bottom: 2rem; } .pagination-container { margin-top: 3rem; display: flex; justify-content: center; padding-bottom: 2rem; }
/* 适配Element Plus分页组件样式 */
::v-deep .el-pagination { --el-pagination-color: #6B7280; --el-pagination-hover-color: #165DFF; --el-pagination-active-color: #fff; --el-pagination-active-bg-color: #165DFF; } ::v-deep .el-pagination { --el-pagination-color: #6B7280; --el-pagination-hover-color: #165DFF; --el-pagination-active-color: #fff; --el-pagination-active-bg-color: #165DFF; }
::v-deep .el-pagination__sizes .el-input__wrapper { width: 100px; } ::v-deep .el-pagination__sizes .el-input__wrapper { width: 100px; }
/* 顶部横幅样式 */
.top-banner { .top-banner {
width: 100%; width: 100%;
background-position: center center; background-position: center center;
@@ -532,38 +505,14 @@ export default {
margin: 0 auto; margin: 0 auto;
padding: 0 20px; padding: 0 20px;
} }
.banner-content h3 {
font-size: 1.8rem;
margin-bottom: 1rem;
font-weight: 700;
letter-spacing: 0.5px;
}
/* 响应式适配 */
@media (max-width: 768px) {
.top-banner {
padding: 2.5rem 0;
}
.banner-content h3 {
font-size: 1.5rem;
}
}
@media (max-width: 480px) {
.top-banner {
padding: 2rem 0;
}
.banner-content h3 {
font-size: 1.3rem;
}
}
@media (max-width: 768px) { @media (max-width: 768px) {
.top-banner { padding: 2.5rem 0; }
.related-title { flex-direction: column; gap: 1rem; } .related-title { flex-direction: column; gap: 1rem; }
.related-articles { grid-template-columns: 1fr; } .related-articles { grid-template-columns: 1fr; }
.all-articles-header { flex-direction: column; gap: 1.5rem; align-items: flex-start; } .all-articles-header { flex-direction: column; gap: 1.5rem; align-items: flex-start; }
.article-item { flex-direction: column; } .article-item { flex-direction: column; }
.item-img { width: 100%; height: 180px; } .item-img { width: 100%; height: 180px; }
.pagination-container { padding-bottom: 1rem; }
} }
@media (max-width: 480px) { @media (max-width: 480px) {
.article-title { font-size: 1.8rem; } .article-title { font-size: 1.8rem; }