1065 字
5 分钟
8-文章篇-文章详情-Springboot Vue教程
后端实现
文章详情Dto
在dto下创建ArticleDetailDto类,代码如下:
package com.example.demo.dto;
import com.example.demo.entity.Article;import lombok.Data;import lombok.NoArgsConstructor; // 确保有无参构造函数,方便JSON反序列化import lombok.AllArgsConstructor; // 如果你希望有一个包含所有字段的构造函数
import java.time.LocalDateTime;
/** * 文章详情DTO,包含作者用户名 */@Data // 自动生成 Getter, Setter, toString, equals, hashCode 方法@NoArgsConstructor // 自动生成无参构造函数@AllArgsConstructor // 自动生成全参构造函数 (包括 authorUsername)public class ArticleDetailDto { private Integer id; private String title; private String content; private Integer userId; private String authorUsername; // 作者用户名 private LocalDateTime createTime; private LocalDateTime updateTime; private Integer viewCount; private Integer status;
public ArticleDetailDto(Article article, String authorUsername) { this.id = article.getId(); this.title = article.getTitle(); this.content = article.getContent(); this.userId = article.getUserId(); this.createTime = article.getCreateTime(); this.updateTime = article.getUpdateTime(); this.viewCount = article.getViewCount(); this.status = article.getStatus(); this.authorUsername = authorUsername; // 设置作者用户名 }}服务层
在ArticleService中增加如下代码:
/** * 根据文章ID获取文章详情 * @param articleId 文章ID * @return 包含文章详情的 Optional<Article> 对象,如果找不到则为 Optional.empty() */Optional<ArticleDetailDto> getArticleById(Integer articleId);在Impl中的ArticleServiceImpl中增加如下代码:
@Autowiredprivate UserMapper userMapper;
@Overridepublic Optional<ArticleDetailDto> getArticleById(Integer articleId) { Optional<Article> optionalArticle = Optional.ofNullable(baseMapper.selectById(articleId)); if (optionalArticle.isPresent()) { int userId = optionalArticle.get().getUserId(); String username = userMapper.selectById(userId).getUsername(); ArticleDetailDto articleDetailDto = new ArticleDetailDto(optionalArticle.get(), username); return Optional.of(articleDetailDto); } return Optional.empty();}控制层
在ArticleController中添加:
/** * 根据文章ID获取文章详情 * @param id 文章ID,通过路径变量传入 * @return SaResult 包含文章详情或错误信息 */@GetMapping("/{id}") // 定义一个 GET 接口,路径中包含文章IDpublic SaResult getArticleDetail(@PathVariable("id") Integer id) { Optional<ArticleDetailDto> articleOptional = articleService.getArticleById(id);
if (articleOptional.isPresent()) { return SaResult.ok("文章详情获取成功").setData(articleOptional.get()); } else { return SaResult.error("未找到指定ID的文章"); }}前端实现
api对接
在api文件夹中的Api.ts中添加:
/** * 根据文章ID获取文章详情 * @param id 文章ID * @returns Promise<Article> 文章详情 */export const getArticleDetail = (id: number): Promise<Article> => { return api.get(`/article/${id}`); // 调用后端接口};在router文件夹中的index.ts,在const router = createRouter函数中增加:
{ path: '/article/:id', // **新增路由:文章详情页,:id 是动态参数** name: 'articleDetail', component: () => import('@/views/ArticleDetail.vue'), props: true, // 允许组件通过 props 接收路由参数},文章详情接口
在interface中的Article.ts中修改Article接口:
export interface Article { id: number title: string content: string userId: number createTime: string | Date updateTime?: string | Date // 可选 status?: 0 | 1 // 0: 草稿, 1: 已发布 viewCount?: number authorUsername?: string}文章视图
在views下创建ArticleDetail.vue,代码如下:
<template> <div class="article-detail-container"> <el-card class="article-card" v-loading="loading"> <template #header> <div class="card-header"> <h2>{{ article?.title }}</h2> <el-text class="mx-1" type="info" v-if="article?.authorUsername">作者: {{ article?.authorUsername }}</el-text> </div> </template> <div class="article-content" v-html="formattedContent"></div> <el-divider /> <div class="article-meta"> <el-text class="mx-1" type="info" v-if="article?.createTime">发布时间: {{ formatDateTime(article?.createTime) }}</el-text> <el-text class="mx-1" type="info" v-if="article?.updateTime">更新时间: {{ formatDateTime(article?.updateTime) }}</el-text> </div> </el-card>
<el-empty v-if="!article && !loading && !error" description="文章不存在或已删除"></el-empty> <div v-if="error" class="error-message"> <el-alert :title="error" type="error" show-icon /> </div> </div></template>
<script setup lang="ts">import { ref, onMounted, computed } from 'vue'import { useRoute } from 'vue-router'import { getArticleDetail } from '@/api/Api'import type { Article } from '@/interface/Article'import { ElCard, ElText, ElDivider, ElEmpty, ElAlert, ElMessage } from 'element-plus'
const route = useRoute()const articleId = ref<number | null>(null)
const article = ref<Article | null>(null)const loading = ref(true)const error = ref<string | null>(null)
const formatDateTime = (dateString: string | Date | undefined) => { if (!dateString) return '' const date = new Date(dateString) return date.toLocaleString()}
const formattedContent = computed(() => { if (article.value?.content) { // 使用可选链操作符确保 article.value 不是 null return article.value.content.replace(/\\n/g, '<br>') } return ''})
const fetchArticleDetail = async () => { loading.value = true error.value = null // 在每次请求前清除之前的错误
try { const id = Number(route.params.id) if (isNaN(id) || id <= 0) { error.value = '无效的文章ID' loading.value = false return } articleId.value = id
const res = await getArticleDetail(id) article.value = res
} catch (err: any) { console.error('获取文章详情失败:', err) // 修复 2: 确保 error.value 是一个字符串,如果 err.msg 不存在,提供一个默认值 error.value = err.msg ? String(err.msg) : '获取文章详情失败,请稍后再试。' ElMessage.error(error.value) // ElMessage.error 期望一个 string article.value = null // 请求失败时,清空文章数据 } finally { loading.value = false }}
onMounted(() => { fetchArticleDetail()})</script>
<style scoped>.article-detail-container { max-width: 800px; margin: 20px auto; padding: 20px; background-color: #f9f9f9; border-radius: 8px; box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.1);}
.article-card { width: 100%;}
.card-header { display: flex; justify-content: space-between; align-items: center; margin-bottom: 10px;}
.card-header h2 { margin: 0; font-size: 2em; color: #333;}
.article-content { font-size: 1.1em; line-height: 1.8; color: #555; white-space: pre-wrap;}
.article-meta { display: flex; justify-content: flex-end; gap: 15px; margin-top: 20px; font-size: 0.9em; color: #888;}
.error-message { margin-top: 20px; text-align: center;}</style>到此,所有的核心功能差不多已经完成。做到这儿就可以结束了。
分享
如果这篇文章对你有帮助,欢迎分享给更多人!
8-文章篇-文章详情-Springboot Vue教程
https://blog.yumui.top/posts/8-springboot-vue/ 部分信息可能已经过时









