news 2026/4/29 8:23:55

手把手教你用 Spring Boot + Vue 搭建个人博客系统(前端篇)

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
手把手教你用 Spring Boot + Vue 搭建个人博客系统(前端篇)

视频看了几百小时还迷糊?关注我,几分钟让你秒懂!


一、为什么需要前端篇?

在上一篇《后端篇》中,我们已经用Spring Boot + MyBatis-Plus搭建好了博客系统的 RESTful API。现在,是时候让这些接口“活”起来——通过Vue 3 + Vite + Axios构建一个清爽、响应式的博客前台页面。

即使你是 Vue 小白,只要会 HTML 和一点 JavaScript,也能跟着一步步完成!


二、需求场景回顾

我们的博客前端要实现以下功能:

  1. 首页:展示所有文章列表(标题 + 分类 + 发布时间);
  2. 文章详情页:点击某篇文章,查看完整内容;
  3. 简洁美观:不需要复杂后台,但要有良好的阅读体验;
  4. 与后端无缝对接:调用上一篇写好的/api/posts接口。

⚠️ 注意:本篇聚焦前台展示(读者视角),不包含后台管理(如写文章、登录等),后续可扩展。


三、技术选型(前端)

技术作用
Vue 3 (Composition API)响应式框架,构建用户界面
Vite新一代前端构建工具,启动快、热更新快
Axios发送 HTTP 请求,调用后端 API
Vue Router实现页面路由跳转(首页 ↔ 详情页)
Tailwind CSS(可选)快速美化 UI(本文用原生 CSS 简化)

四、项目初始化

1. 创建 Vue 3 项目(使用 Vite)

npm create vue@latest blog-frontend

按提示选择:

  • ✔ Project name:blog-frontend
  • ✔ Add Vue Router for Single Page Application development?Yes
  • 其他选 No(简化)

进入目录并安装依赖:

cd blog-frontend npm install npm install axios npm run dev

默认访问http://localhost:5173


五、核心代码实现

1. 配置 Axios(统一 API 调用)

创建src/api/index.js

// src/api/index.js import axios from 'axios'; const api = axios.create({ baseURL: 'http://localhost:8080/api', // 后端地址(Spring Boot 默认端口) timeout: 5000, }); export default api;

💡 注意:如果你的 Spring Boot 运行在 8080,而 Vue 在 5173,会遇到跨域问题
临时解决方案:在 Spring Boot 的 Controller 上加@CrossOrigin(开发阶段可用)。

@RestController @RequestMapping("/api/posts") @CrossOrigin(origins = "http://localhost:5173") // ← 加这一行 public class PostController { // ... }

2. 定义数据类型(TypeScript 可选,这里用 JS 注释说明)

我们假设后端返回的文章结构如下:

{ "id": 1, "title": "Spring Boot 入门", "content": "详细内容...", "categoryName": "Java", "createTime": "2026-01-05T10:00:00" }

3. 首页:文章列表(HomeView.vue)

修改src/views/HomeView.vue

<template> <div class="container"> <h1>我的技术博客</h1> <div v-if="loading">加载中...</div> <div v-else-if="error" class="error">{{ error }}</div> <div v-else class="posts"> <div v-for="post in posts" :key="post.id" class="post-item" @click="goToDetail(post.id)" > <h2>{{ post.title }}</h2> <p class="meta"> <span class="category">{{ post.categoryName }}</span> <span class="time">{{ formatDate(post.createTime) }}</span> </p> </div> </div> </div> </template> <script setup> import { ref, onMounted } from 'vue'; import { useRouter } from 'vue-router'; import api from '@/api'; const posts = ref([]); const loading = ref(true); const error = ref(null); const router = useRouter(); onMounted(async () => { try { const response = await api.get('/posts'); posts.value = response.data; } catch (err) { error.value = '加载文章失败,请检查后端是否运行'; console.error(err); } finally { loading.value = false; } }); function goToDetail(id) { router.push(`/post/${id}`); } function formatDate(dateString) { const date = new Date(dateString); return date.toLocaleDateString('zh-CN'); } </script> <style scoped> .container { max-width: 800px; margin: 0 auto; padding: 20px; } .post-item { border-bottom: 1px solid #eee; padding: 15px 0; cursor: pointer; } .post-item:hover { background-color: #f9f9f9; } .meta { color: #666; font-size: 0.9em; } .category { background: #e0f7fa; padding: 2px 6px; border-radius: 4px; margin-right: 10px; } </style>

4. 详情页:文章内容(PostView.vue)

创建src/views/PostView.vue

<template> <div class="container"> <button @click="goBack" class="back-btn">← 返回首页</button> <div v-if="loading">加载中...</div> <div v-else-if="error" class="error">{{ error }}</div> <article v-else> <h1>{{ post.title }}</h1> <div class="meta"> <span class="category">{{ post.categoryName }}</span> <span class="time">{{ formatDate(post.createTime) }}</span> </div> <div class="content" v-html="post.content"></div> </article> </div> </template> <script setup> import { ref, onMounted } from 'vue'; import { useRoute, useRouter } from 'vue-router'; import api from '@/api'; const route = useRoute(); const router = useRouter(); const post = ref(null); const loading = ref(true); const error = ref(null); onMounted(async () => { const id = route.params.id; try { const response = await api.get(`/posts/${id}`); post.value = response.data; } catch (err) { error.value = '文章不存在或加载失败'; } finally { loading.value = false; } }); function goBack() { router.go(-1); } function formatDate(dateString) { const date = new Date(dateString); return date.toLocaleDateString('zh-CN'); } </script> <style scoped> .container { max-width: 800px; margin: 0 auto; padding: 20px; } .back-btn { margin-bottom: 20px; background: #007bff; color: white; border: none; padding: 8px 16px; border-radius: 4px; cursor: pointer; } .content { line-height: 1.6; margin-top: 20px; } </style>

5. 配置路由(router/index.js)

确保src/router/index.js包含详情页路由:

import { createRouter, createWebHistory } from 'vue-router' import HomeView from '../views/HomeView.vue' import PostView from '../views/PostView.vue' const routes = [ { path: '/', name: 'home', component: HomeView }, { path: '/post/:id', name: 'post', component: PostView, props: true } ] const router = createRouter({ history: createWebHistory(import.meta.env.BASE_URL), routes }) export default router

六、反例 & 常见坑点

❌ 反例1:直接在模板里写v-html不过滤(XSS 风险)

<!-- 危险!如果 content 来自用户输入,可能注入恶意脚本 --> <div v-html="post.content"></div>

✅ 正确做法:

  • 如果内容是你自己写的(可信),可接受;
  • 如果支持用户评论/投稿,必须用 DOMPurify 等库过滤 HTML

❌ 反例2:硬编码 API 地址

// 不好维护! axios.get('http://localhost:8080/api/posts')

✅ 正确做法:统一配置baseURL(如我们做的api.js)。


❌ 反例3:忽略加载状态和错误处理

用户看到空白页面会以为“网站坏了”。

✅ 正确做法:显示 loading / error 提示,提升用户体验。


七、注意事项

  1. 跨域问题:开发时前后端端口不同(8080 vs 5173),务必处理 CORS;
  2. 后端必须运行:启动 Spring Boot 项目后再运行 Vue;
  3. 内容安全:若未来支持富文本编辑,务必对 HTML 进行消毒;
  4. 部署建议:生产环境可将 Vue 打包后放入 Spring Boot 的static目录,或用 Nginx 代理。

八、效果预览

  1. 启动后端:./mvnw spring-boot:run
  2. 启动前端:npm run dev
  3. 访问http://localhost:5173
  4. 点击文章 → 跳转详情页 ✅

九、下一步扩展方向

  • 添加Markdown 渲染(用marked库);
  • 增加分页无限滚动
  • 实现搜索分类筛选
  • 搭建后台管理页面(需登录 + JWT 鉴权)。

视频看了几百小时还迷糊?关注我,几分钟让你秒懂!

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/4/15 13:13:07

计算机深度学习毕设实战-基于python的CNN卷积神经网络对辣椒类别识别

博主介绍&#xff1a;✌️码农一枚 &#xff0c;专注于大学生项目实战开发、讲解和毕业&#x1f6a2;文撰写修改等。全栈领域优质创作者&#xff0c;博客之星、掘金/华为云/阿里云/InfoQ等平台优质作者、专注于Java、小程序技术领域和毕业项目实战 ✌️技术范围&#xff1a;&am…

作者头像 李华
网站建设 2026/4/19 12:43:33

Android Profiler实战宝典:揪出CPU耗时元凶与内存泄露小偷

前言 作为Android开发者&#xff0c;我们总能遇到这样的“灵魂拷问”&#xff1a; “APP首页怎么滑着就卡了&#xff1f;”“为什么用了几分钟就报内存溢出&#xff1f;”“明明代码没改多少&#xff0c;怎么性能差了这么多&#xff1f;” 这时候&#xff0c;Android Studio自带…

作者头像 李华
网站建设 2026/4/20 9:39:29

智谱AI再发力:GLM-4.6V-Flash-WEB推动视觉大模型平民化

智谱AI再发力&#xff1a;GLM-4.6V-Flash-WEB推动视觉大模型平民化 在今天的AI应用现场&#xff0c;越来越多的产品团队正面临一个尴尬的现实&#xff1a;明明有多模态大模型的技术突破&#xff0c;却依然“用不起”——不是性能不够&#xff0c;而是跑不快、部署难、成本高。尤…

作者头像 李华