添加文章细节显示

This commit is contained in:
2025-11-04 11:52:04 +08:00
parent 06f0215a29
commit 0bc22e9d49

View File

@@ -0,0 +1,448 @@
<template>
<div class="detail-page">
<!-- 加载状态 -->
<div class="loading" v-if="isLoading">
<el-icon class="loading-icon"><Loading /></el-icon>
<p>正在加载详情...</p>
</div>
<!-- 错误状态 -->
<div class="error" v-else-if="errorMsg">
<el-icon class="error-icon"><WarningFilled /></el-icon>
<p>{{ errorMsg }}</p>
<el-button type="text" @click="fetchDetail()">重试</el-button>
</div>
<!-- 案例详情type=case -->
<div class="content case-content" v-else-if="detailData && currentType === 'case'">
<h1 class="title">{{ detailData.title }}</h1>
<div class="meta">
<div class="meta-item">
<span class="meta-label">指导教师</span>
<span class="meta-value">{{ detailData.tutor_name }}{{ detailData.tutor_title }}</span>
</div>
<div class="meta-item">
<span class="meta-label">参与学生</span>
<span class="meta-value">{{ detailData.student_names }}</span>
</div>
<div class="meta-item">
<span class="meta-label">更新时间</span>
<span class="meta-value">{{ detailData.update_time }}</span>
</div>
</div>
<div class="cover" v-if="detailData.cover_url">
<img :src="detailData.cover_url" alt="封面图" class="cover-img">
</div>
<div class="body" v-html="detailData.content"></div>
</div>
<!-- 社区服务详情type=service -->
<div class="content service-content" v-else-if="detailData && currentType === 'service'">
<h1 class="title">{{ detailData.title }}</h1>
<p class="subtitle">{{ detailData.subtitle }}</p>
<div class="meta">
<div class="meta-item">
<span class="meta-label">发布时间</span>
<span class="meta-value">{{ detailData.publish_time }}</span>
</div>
<div class="meta-item">
<span class="meta-label">更新时间</span>
<span class="meta-value">{{ detailData.update_time }}</span>
</div>
<div class="meta-item" v-if="detailData.chief_editor">
<span class="meta-label">主编</span>
<span class="meta-value">{{ detailData.chief_editor }}</span>
</div>
</div>
<div class="cover" v-if="detailData.cover_url">
<img :src="detailData.cover_url" alt="封面图" class="cover-img">
</div>
<div class="intro" v-if="detailData.intro">
<h3 class="section-title">简介</h3>
<p>{{ detailData.intro }}</p>
</div>
<div class="body" v-html="detailData.content"></div>
<!-- 编辑信息如有 -->
<div class="editors" v-if="hasEditorInfo">
<h3 class="section-title">编辑信息</h3>
<div class="editor-item">
<span class="editor-label">图文编辑</span>
<span class="editor-value">{{ detailData.image_editors || '无' }}</span>
</div>
<div class="editor-item">
<span class="editor-label">文字编辑</span>
<span class="editor-value">{{ detailData.text_editors || '无' }}</span>
</div>
<div class="editor-item">
<span class="editor-label">校对</span>
<span class="editor-value">{{ detailData.proofreaders || '无' }}</span>
</div>
<div class="editor-item">
<span class="editor-label">审核</span>
<span class="editor-value">{{ detailData.reviewers || '无' }}</span>
</div>
</div>
</div>
<!-- 新闻详情type=news -->
<div class="content news-content" v-else-if="detailData && currentType === 'news'">
<h1 class="title">{{ detailData.title }}</h1>
<div class="meta">
<div class="meta-item">
<span class="meta-label">专题</span>
<span class="meta-value">{{ detailData.topic || '未分类' }}</span>
</div>
<div class="meta-item">
<span class="meta-label">发布时间</span>
<span class="meta-value">{{ detailData.create_at }}</span>
</div>
<div class="meta-item">
<span class="meta-label">最后更新</span>
<span class="meta-value">{{ detailData.update_at }}</span>
</div>
</div>
<div class="cover" v-if="detailData.cover">
<img :src="detailData.cover" alt="新闻封面" class="cover-img">
</div>
<!-- 摘要如有 -->
<div class="excerpt" v-if="detailData.excerpt">
<h3 class="section-title">摘要</h3>
<div v-html="detailData.excerpt" class="excerpt-content"></div>
</div>
<div class="body" v-html="detailData.content"></div>
</div>
</div>
</template>
<script setup lang="ts">
import { onMounted, ref, computed } from 'vue';
import { useRoute } from 'vue-router';
import axios from 'axios';
import { ElIcon, ElButton } from 'element-plus';
import { Loading, WarningFilled } from '@element-plus/icons-vue';
// 状态管理
const route = useRoute();
const isLoading = ref(false); // 加载状态
const errorMsg = ref(''); // 错误信息
const detailData = ref<any>(null); // 详情数据
const currentType = ref<string>(''); // 当前类型case/service/news
// 判断是否显示编辑信息service类型专用
const hasEditorInfo = computed(() => {
if (!detailData.value) return false;
const { image_editors, text_editors, proofreaders, reviewers } = detailData.value;
return !!image_editors || !!text_editors || !!proofreaders || !!reviewers;
});
// 通用请求函数根据type请求不同接口
const fetchDetail = async () => {
isLoading.value = true;
errorMsg.value = '';
detailData.value = null;
currentType.value = '';
try {
// 1. 提取URL参数
const id = route.query.id;
const type = route.query.type;
// 2. 验证参数支持case/service/news
if (!id || !['case', 'service', 'news'].includes(type as string)) {
throw new Error('参数错误缺少ID或类型不支持仅支持case/service/news');
}
currentType.value = type as string;
// 3. 根据类型请求对应接口
let apiUrl = '';
switch (type) {
case 'case':
apiUrl = `http://localhost:8080/api/teaching-cases/${id}`;
break;
case 'service':
apiUrl = `http://localhost:8080/api/social-service/${id}`;
break;
case 'news':
apiUrl = `http://localhost:8080/api/articles/${id}`;
break;
}
const response = await axios.get(apiUrl, {
withCredentials: true // 修正使用axios正确的跨域凭证配置
});
// 4. 处理不同接口的返回结构
switch (type) {
case 'case':
// case接口数据直接在根节点
if (response.data && response.data.id) {
detailData.value = response.data;
} else {
throw new Error('案例数据格式异常');
}
break;
case 'service':
// service接口数据在data字段且code=0
if (response.data && response.data.code === 0 && response.data.data) {
detailData.value = response.data.data;
} else {
throw new Error(`服务数据获取失败:${response.data?.message || '未知错误'}`);
}
break;
case 'news':
// news接口数据在article字段且success=true
if (response.data && response.data.success && response.data.article) {
detailData.value = response.data.article;
} else {
throw new Error(`新闻数据获取失败:${response.data?.message || '接口返回异常'}`);
}
break;
}
} catch (err) {
errorMsg.value = err instanceof Error ? err.message : '加载失败,请稍后重试';
console.error('详情请求失败:', err);
} finally {
isLoading.value = false;
}
};
// 页面挂载时加载数据
onMounted(() => {
fetchDetail();
});
</script>
<style scoped>
/* 基础样式 */
.detail-page {
max-width: 1200px;
margin: 0 auto;
padding: 30px 20px;
box-sizing: border-box;
}
/* 加载状态 */
.loading {
text-align: center;
padding: 80px 0;
color: #666;
}
.loading .loading-icon {
font-size: 36px;
margin-bottom: 16px;
animation: spin 1.5s linear infinite;
}
.loading p {
font-size: 16px;
}
/* 错误状态 */
.error {
text-align: center;
padding: 80px 0;
color: #ff4444;
}
.error .error-icon {
font-size: 36px;
margin-bottom: 16px;
}
.error p {
font-size: 16px;
margin-bottom: 20px;
}
.error .el-button {
color: #165dff;
}
/* 通用内容样式 */
.content {
line-height: 1.8;
color: #333;
}
.title {
font-size: 28px;
font-weight: 600;
margin-bottom: 16px;
color: #1d2129;
text-align: center;
}
.subtitle {
font-size: 18px;
color: #666;
text-align: center;
margin-bottom: 24px;
font-style: italic;
}
.meta {
display: flex;
flex-wrap: wrap;
gap: 20px;
padding: 16px 0;
border-top: 1px solid #eee;
border-bottom: 1px solid #eee;
margin-bottom: 24px;
font-size: 14px;
color: #666;
}
.meta-item .meta-label {
font-weight: 500;
color: #333;
margin-right: 4px;
}
.cover {
margin-bottom: 32px;
border-radius: 8px;
overflow: hidden;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.08);
}
.cover-img {
width: 100%;
height: auto;
max-height: 600px;
object-fit: cover;
}
.body {
font-size: 16px;
color: #333;
margin-bottom: 40px;
}
.body p {
margin-bottom: 16px;
}
.body img {
max-width: 100%;
height: auto;
border-radius: 8px;
margin: 20px auto;
display: block;
box-shadow: 0 2px 12px rgba(0, 0, 0, 0.1);
}
.body a {
color: #165dff;
text-decoration: underline;
}
.body a:hover {
color: #0e4bd8;
}
.section-title {
font-size: 20px;
color: #1d2129;
margin: 32px 0 16px;
padding-bottom: 8px;
border-bottom: 2px solid #f0f0f0;
}
/* service类型专用样式 */
.intro {
padding: 16px;
background-color: #f9f9f9;
border-radius: 8px;
margin-bottom: 32px;
font-size: 16px;
}
.editors {
padding: 20px;
background-color: #f9f9f9;
border-radius: 8px;
margin-top: 40px;
}
.editor-item {
margin-bottom: 12px;
font-size: 14px;
}
.editor-label {
display: inline-block;
width: 80px;
font-weight: 500;
color: #333;
}
/* news类型专用样式 */
.excerpt {
padding: 16px;
background-color: #f9f9f9;
border-left: 4px solid #165dff;
border-radius: 0 8px 8px 0;
margin-bottom: 32px;
}
.excerpt-content {
font-size: 16px;
color: #555;
}
/* 响应式适配 */
@media (max-width: 768px) {
.detail-page {
padding: 20px 12px;
}
.title {
font-size: 24px;
}
.subtitle {
font-size: 16px;
}
.meta {
flex-direction: column;
gap: 8px;
}
.body {
font-size: 15px;
}
.section-title {
font-size: 18px;
}
.editor-label {
width: 70px;
}
}
/* 加载动画 */
@keyframes spin {
from {
transform: rotate(0deg);
}
to {
transform: rotate(360deg);
}
}
</style>