0 项目概述
TinyBlog 是一个博客系统,正如其名 Tiny,它是一个极其微小小小小小小👌的博客系统,定位是一个用于学习各种技术的 Demo 应用😀。
本文主要是使用 Vue + Vuetify 实现前端页面。后台接口部分可以参考之前的文章
TinyBlog 后端开发 SpringBoot+JdbcTemplate 版本
先看下最终实现的效果。
查看所有 Blog 列表。(博客数据是在掘金随机复制的,感兴趣查请搜索查看原文,这里仅作为测试数据演示)
新建 Blog。
编辑 Blog。
1 项目搭建
1.1 Vue CLI 创建工程
打开 Terminal,通过命令 vue create tinyblog-vue-vuetify 创建工程。
1.2 安装 Vuetify
然后我们切换到工程目录 cd tinyblog-vue-vuetify/,通过 vue add vuetify 命令,添加 Vuetify。
1.3 安装 Vue Router
通过命令 npm install vue-router 添加 Vue Router。
1.4 安装 Axios
通过 npm install axios 命令,添加 Axios。
最后,通过执行 code . 使用 VS Code 打开工程。我们可以通过 npm run serve 命令,启动应用。
浏览器访问应用的网址,正常应该可以看到下面的页面。
1.5 技术栈
- Vue, Vuetify
1.6 开发工具
- Vue CLI, VS Code
2 编写代码
2.1 封装后台接口服务
2.1.1 封装 axios
新建文件 src/http.js,封装 axios,定义后台接口地址。
2.1.2 封装后台接口
将我们之前后台提供的接口,封装成对应的方法,方便后续使用。(后台接口参考之前文章:TinyBlog 后端开发 SpringBoot+JdbcTemplate 版本)
| 请求 | URL | 接口说明 |
|---|---|---|
| POST | /api/blog | 创建一篇博客 |
| GET | /api/blog | 获取所有博客 |
| GET | /api/blog/:id | 获取 id 为 :id 的博客 |
| PUT | /api/blog/:id | 更新 id 为 :id 的博客 |
| DELETE | /api/blog/:id | 删除 id 为 :id 的博客 |
创建 src/services/BlogService.js 文件,封装后台接口。
2.2 规划页面及路由
2.2.1 规划页面
一个博客系统最基础的功能至少包括:查看博客列表,新建博客,编辑博客。所以我们至少定义 3 个路由地址:
/blogs:查看所有博客。/edit/${id}:编辑 id 为 ${id} 的博客。
/add:新建一篇博客。
2.2.2 定义路由
新建 src/router.js 文件,定义好我们的路由信息。
import Vue from "vue";
import Router from "vue-router";
Vue.use(Router);
export default new Router({
mode: "history",
routes: [
{
path: "/",
alias: "/blogs",
name: "blogs",
component: () => import("./components/BlogList")
},
{
path: "/edit/:id",
name: "edit",
component: () => import("./components/EditBlog")
},
{
path: "/add",
name: "add",
component: () => import("./components/AddBlog")
}
]
});
2.2.3 引入路由
创建好 router.js 后,还需要在 main.js 中引入路由才能生效。(如果你正在运行,记得把 router.js 中的路由暂时注释掉,不然会报错找不到 Vue 组件)
可以看到 3 个路由对应了 3 个 Vue 的组件,3 个组件目前还没有编写,不过没关系,我们先定义好,后面慢慢实现。
2.3 编写 Vue 组件
根据前面的规划,我们需要 3 个 Vue 组件。但是不要着急编写,因为我们首先需要创建网页的导航栏。
2.3.1 编写导航栏
我们创建导航栏组件 src/components/Navbar.vue。
<template>
<v-app-bar app color="white" class="d-flex justify-center">
<div class="d-flex" style="width: 960px">
<!-- LOGO -->
<v-toolbar-title class="mr-2 headline grey--text text--darken-1">
<span class="font-weight-light">Tiny</span>
<span>Blog</span>
</v-toolbar-title>
<v-spacer></v-spacer>
<!-- 搜索框 -->
<v-text-field
v-model="searchParam"
class="mr-4"
style="max-width: 450px"
dense
flat
hide-no-data
hide-details
label="搜索博客"
append-icon="mdi-magnify"
@click:append="searchByParam"
solo-inverted
></v-text-field>
<!-- 导航链接 -->
<v-btn to="/blogs" text>
<span class="subtitle-1 grey--text text--darken-2">首页</span>
</v-btn>
<v-btn to="/add" text>
<span class="subtitle-1 grey--text text--darken-2">写文章</span>
</v-btn>
<v-btn @click="goToGitHub" text>
<v-icon class="grey--text text--darken-2"> mdi-github </v-icon>
</v-btn>
</div>
</v-app-bar>
</template>
<script>
export default {
name: "Navbar",
data() {
return {
searchParam: "",
};
},
methods: {
searchByParam() {
},
goToGitHub() {
window.location.href ="https://gitee.com/noissues/tinyblog-vue-vuetify.git";
},
},
};
</script>
<style>
.navitem {
width: 960px;
}
</style>
接着,我们要到 App.vue 中添加我们封装的导航栏。(目前它里面应该还是 HelloWorld 的内容)
修改之后,npm run serve 启动后,便可以看到我们修改的内容生效了。
2.3.2 编写 BlogList 组件
新增 src/components/BlogList.vue 组件。
<template>
<v-card class="mx-auto mt-3" style="max-width: 960px" tile>
<v-card-title>博客列表</v-card-title>
<!-- 列表展示 -->
<v-col v-for="(item, i) in blogs" :key="i" cols="12" class="py-1">
<v-card outlined @click="editBlog(item.id)">
<div class="d-flex flex-no-wrap justify-space-between">
<div>
<!-- 标题 -->
<v-card-title
class="subtitle-1 font-weight-black"
v-text="item.title"
></v-card-title>
<!-- 描述 -->
<v-card-subtitle
class="grey--text"
v-text="item.description"
></v-card-subtitle>
</div>
</div>
</v-card>
</v-col>
</v-card>
</template>
<script>
import BlogService from "../services/BlogService";
export default {
props: ["searchParam"],
name: "BlogList",
data() {
return {
blogs: [],
};
},
methods: {
getAllBlogs() {
BlogService.getAllBlogs()
.then((response) => {
this.blogs = response.data.map(this.getDisplayBlog);
console.log(response.data);
})
.catch((e) => {
console.log(e);
});
},
editBlog(id) {
this.$router.push({ path: `/edit/${id}` });
},
// 界面展示转换
getDisplayBlog(blog) {
return {
id: blog.id,
title:
blog.title.length > 30
? blog.title.substr(0, 30) + "..."
: blog.title,
description:
blog.description.length > 30
? blog.description.substr(0, 30) + "..."
: blog.description,
};
},
},
mounted() {
this.getAllBlogs();
},
};
</script>
<style>
.list {
max-width: 960px;
}
</style>
2.3.3 编写 BlogEditor 组件
注意到新增和编辑 Blog 的页面可以复用,我们将其单独拆出成一个组件。
新增 src/components/BlogList.vue 组件。
<template>
<v-form ref="form" lazy-validation>
<v-card class="mx-auto" tile>
<v-card-title v-if="currentBlog">编辑博客</v-card-title>
<v-card-title v-else>新建博客</v-card-title>
<v-card-text>
<v-text-field
outlined
v-model="blog.title"
:rules="[(v) => !!v || '请填写标题']"
label="标题"
required
></v-text-field>
<v-text-field
outlined
v-model="blog.description"
:rules="[(v) => !!v || '请填写简介']"
label="简介"
required
></v-text-field>
<v-textarea
outlined
label="内容"
v-model="blog.content"
:rules="[(v) => !!v || '请填写内容']"
required
></v-textarea>
</v-card-text>
<v-divider class="mt-2"></v-divider>
<v-card-actions>
<div v-if="currentBlog">
<v-btn @click="updateBlog" color="primary" small class="mr-2">
更新
</v-btn>
<v-btn @click="deleteBlog" color="error" small class="mr-2">
删除
</v-btn>
<v-btn small @click="back">取消</v-btn>
</div>
<div v-else>
<v-btn @click="addBlog" color="primary" small class="mr-3">
提交
</v-btn>
</div>
</v-card-actions>
</v-card>
</v-form>
</template>
<script>
import BlogService from "../services/BlogService";
export default {
name: "BlogEditor",
props: ["currentBlog"],
data() {
return {
blog: {
id: null,
title: "",
description: "",
content: "",
},
message: "",
};
},
methods: {
back() {
this.$router.back();
},
updateBlog() {
BlogService.updateBlog(this.blog.id, this.blog)
.then((response) => {
console.log(response.data);
this.$emit("updated", "🎉博客更新成功!");
})
.catch((e) => {
console.log(e);
});
},
addBlog() {
var data = {
title: this.blog.title,
description: this.blog.description,
content: this.blog.content,
};
BlogService.addBlog(data)
.then((response) => {
console.log(response.data);
let data = {};
data.id = response.data.id;
data.message = "🎉博客创建成功!";
this.$emit("added", data);
})
.catch((e) => {
console.log(e);
});
},
deleteBlog() {
BlogService.deleteBlog(this.blog.id)
.then((response) => {
console.log(response.data);
this.$router.push({ name: "blogs" });
})
.catch((e) => {
console.log(e);
});
},
},
mounted() {
if (this.currentBlog) {
this.blog.id = this.currentBlog.id;
this.blog.title = this.currentBlog.title;
this.blog.description = this.currentBlog.description;
this.blog.content = this.currentBlog.content;
}
},
};
</script>
2.3.4 编写 AddBlog 组件
新增 src/components/AddBlog.vue 组件。使用我们刚刚创建的 BlogEditor 组件。
<template>
<div class="submit-form mt-3 mx-auto">
<div v-if="!submitted">
<BlogEditor @added="addBlogHandler" />
</div>
<div v-else>
<v-card class="mx-auto">
<v-card-title> 🎉博客创建成功! </v-card-title>
<v-card-subtitle>
请选择查看已提交的博客还是继续创建博客
</v-card-subtitle>
<v-card-actions>
<v-btn color="primary" @click="editBlog">查看已提交博客</v-btn>
<v-btn color="success" @click="addBlog">继续创建</v-btn>
</v-card-actions>
</v-card>
</div>
</div>
</template>
<script>
import BlogEditor from "./BlogEditor";
export default {
name: "AddBlog",
components: {
BlogEditor,
},
data() {
return {
submitted: false,
};
},
methods: {
addBlogHandler(data) {
this.submitted = true;
this.id = data.id; // 新创建的博客 id
},
addBlog() {
this.submitted = false;
},
editBlog() {
this.$router.push({ path: `/edit/${this.id}` });
},
},
};
</script>
<style>
.submit-form {
max-width: 960px;
margin: auto;
}
</style>
2.3.5 编写 EditBlog 组件
新增 src/components/EditBlog.vue 组件。
<template>
<div v-if="currentBlog" class="edit-form mt-3 mx-auto">
<BlogEditor v-bind:currentBlog="currentBlog" @updated="updateBlogHandler" />
<v-snackbar v-model="snackbar" :timeout="timeout" top>
{{ message }}
<template v-slot:action="{ attrs }">
<v-btn color="white" text v-bind="attrs" @click="snackbar = false">
关闭
</v-btn>
</template>
</v-snackbar>
</div>
</template>
<script>
import BlogService from "../services/BlogService";
import BlogEditor from "./BlogEditor";
export default {
name: "EditBlog",
components: {
BlogEditor,
},
data() {
return {
snackbar: false,
timeout: 2000,
currentBlog: null,
message: "",
};
},
methods: {
updateBlogHandler(message) {
this.snackbar = true;
this.message = message;
},
getBlog(id) {
BlogService.getBlogById(id)
.then((response) => {
this.currentBlog = response.data;
console.log(response.data);
})
.catch((e) => {
console.log(e);
});
},
},
mounted() {
this.getBlog(this.$route.params.id);
},
};
</script>
<style>
.edit-form {
max-width: 960px;
margin: auto;
}
</style>
3 运行测试
首先启动后端服务。
注意在测试前端代码的时候,发现了后端接口的几个 Bug,已经修复,请更新最新代码测试。
然后 npm run serve 启动前台应用。
新建 Blog。
编辑 Blog。
4 项目总结
本文主要介绍了 TinyBlog 的前端开发过程,主要使用了 Vue + Vuetify 实现的。代码已经上传到我的 Gitee 仓库。
因为平时工作主要还是后台开发,TinyBlog 的前端部分也是现学现卖,所以如果大家有什么想法或者建议,欢迎评论区留言。