This is the 26th day of my participation in the Novembermore Challenge.The final text challenge in 2021
Hello, Hello, I am gray little ape, a super bug writing program ape!
During the National Day, I made a personal blog website based on springboot+ VUE. Today, I will share the development process with you and teach you how to build a personal blog.
The full source code is now available on Gitee.
Remember βstarβ!
Friends a key three β concern! The grey Ape takes you to the highway πππ**! **
The last article and we talked about the background implementation, this article will continue to talk about building personal blog system interface is how to build up it?
β‘ Project directory β‘
3. Vue front-end page development
(1) Install element-UI
(2) Install AxiOS
(3) Configure page routing
(4) Login page
(5) Blog list
(6) Blog editor
(7) Blog details page
(8) Permission route interception
Fourth, write at the end & project summary
3. Vue front-end page development
The front-end page development is based on Vue and Element-UI, involving axios sending requests, the introduction of markdown editor, login verification, cross-domain requests and other issues.
The blog’s home page looks like this:
β
Next, I would like to share with you the development process of the front-end page.
(1) Install element-UI
Element-ui is a component library for front-end development. There are various developed components available for us to use.
Element – The world’s most popular Vue UI framework
To use this component library we first need to import. In the root directory of vue, enter the following command:
Switch to the project root directory
cd vueblog-vue
# installation element – the UI
npm install element-ui –save
Then open the main.js file in your project’s SRC directory and introduce the element-UI dependency.
import Element from 'element-ui'
import "element-ui/lib/theme-chalk/index.css"
Vue.use(Element)
Copy the code
At this point, components in the component library are free to use as they please.
(2) Install AxiOS
Axios is a PROMISE based HTTP library that can be used to make our development more efficient when working on our front and back end projects. [Axios website]
The Axios installation command is as follows:
cnpm install axios –save
We also need to introduce axios globally in main.js,
import axios from 'axios'
Vue.prototype.$axios = axios
Copy the code
Then we can initiate our request with this.$axios.get()!
(3) Configure page routing
The next step is to define the page route. The purpose of defining the page route is that when we visit the corresponding path, we can determine the page we are going to visit according to the route.
The pages in the Views folder are:
- Blogdetail.vue (Blog details page)
- Blogedit.vue (Edit blog)
- Blogs. Vue
- Login.vue (Login page)
The page routing is set in index.js under the router file. The configuration is as follows:
/** * import Vue from 'Vue' import VueRouter from 'vuue -router' // import Login from '.. /views/Login.vue' import Blogs from '.. /views/Blogs.vue' import BlogEdit from '.. /views/BlogEdit.vue' import BlogDetail from '.. /views/BlogDetail.vue' Vue.use(VueRouter) const routes = [ { path: '/', name: 'index', redirect: {name: "Blogs"} // page redirection}, {path: '/ Blogs', name: 'Blogs', Component: Blogs}, {path: '/login', name: 'login', component: Login}, {path: '/blog/add', name: 'BlogAdd', Component: BlogEdit, True}}, {path: '/blog/:blogId/edit', name: 'BlogEdit', Component: BlogEdit, // Add permission meta: { requireAuth: true } }, { path: '/blog/:blogId', name: 'BlogDetail', component: BlogDetail }, ] const router = new VueRouter({ mode: 'history', base: process.env.BASE_URL, routes }) export default routerCopy the code
Meta: requireAuth: true specifies a restricted resource that requires login to access. ** We’ll talk about this later in route permission interception.
(4) Login page
Login page we login by user name and password, component I use element- UI component, so login verification function is directly available in the login.
ββ
Login authentication
After clicking the login button here, there will be a process to verify the login.
After we initiate the login request, we get the returned request and check whether there is the JWTToken we need in the request. If there is, we share the obtained token and user information with our browser, and then jump to the home page. If not, pop up and do nothing.
The code is as follows:
methods: $refs[formName].validate((valid) => {if (valid) {// alert('submit! '); // Get the current this object const _this = this; this.$axios.post("/login", this.ruleForm).then(res => { console.log(res.data) const jwt = res.headers["authorization"] if (jwt === null){ This.$alert(' wrong username or password!! ', 'prompt', {confirmButtonText: 'sure, callback: action = > {/ / _this. $router. Push ("/blogs ")}}); }else {const userInfo = res.data.data console.log(JWT) console.log(userInfo) _this.$store.com MIT ("SET_TOKEN", jwt); _this.$store.commit("SET_USERINFO", userInfo); $this.$router. Push ("/blogs")}}); } else { console.log('error submit!! '); return false; }}); }, resetForm(formName) { this.$refs[formName].resetFields(); }}Copy the code
Token Status Synchronization
In the above code, we use $store to synchronize the token and user information, so how to do this synchronization is actually wrapped and set in the index.js file of store.
To store tokens, we use localStorage and to store user information, we use sessionStorage. After all, we do not need to keep the user information for a long time. After saving the token information, we can initialize the user information at any time.
The code in index.js looks like this:
import Vue from 'vue' import Vuex from 'vuex' Vue.use(Vuex) export default new Vuex.Store({ state: { // token: GetItem ("token"), // deserialize userInfo: JSON. Parse (sessionstorage.getitem ("userInfo"))}, mutations: {return the value SET_TOKEN: (state, token) => { state.token = token; SetItem ("token", token); }, SET_USERINFO: (state, userinfo) => {state. userinfo = userinfo; // Session is cleared every time the browser closes and regenerates after logging in // Because sessionStorage cannot store objects, So store it as a string sessionStorage.setitem ("userInfo", json.stringify (userInfo)); }, // Remove user info REMOVE_INFO: (state) => {// Empty all user info when removing user info state.token = ""; state.userInfo = {}; localStorage.setItem("token", ""); sessionStorage.setItem("userInfo", JSON.stringify("")); GetUser: state => {return state.userInfo; } }, actions: {}, modules: {} })Copy the code
Define the global AXIOS interceptor
Although we do not need to do any operation, we still need to perform popup prompt sometimes, so that we can carry out unified encapsulation and setting for the popup of such error message. So I set up an interceptor for Axios, both pre-interceptor and post-interceptor, to pop up a message if the code or status of the data we’re returning is not normal.
To do this, create a file axios.js (the same as main.js) in the SRC directory and define an axios intercept:
import axios from "axios" import Element from "element-ui" import router from ".. /router" import store from ".. /store"; / / set up unified request path axios. Defaults. BaseURL = "/ API" / / front intercept axios. Interceptors. Request. Use (config = > {return config}) / * * * To request the return of data filtering * / axios. Interceptors. Response. Use (response = > {let res = response. The data; The console. The log (" = = = = = = = = = = = = = = = = = ") the console. The log (res) console. The log (" = = = = = = = = = = = = = = = = = ") / / if the status code is 200, If (res.code === 200) {return response} else {// // Popup prompt! Element.message. error(' Wrong username or password! ', {duration: Return promise.reject (response.data.msg)}}, // If there is no password error, If (error.response.data){error.message = error.response.data.msg; } // If the status code is 401, If (error.response.status === 401){store.com MIT ("REMOVE_INFO") router.push("/login")} // An error message is displayed Element.Message.error(error.message, {duration: 3 * 1000}) return Promise.reject(error) })Copy the code
Then don’t forget to import the axios JS file in your main js file.
Import './axios.js' // request interceptionCopy the code
A brief description of what these intercepts do:
** Pre-request interception: ** Pre-request interception, in which the header token information is assembled for all requests requiring permissions, so that it is not reconfigured at the time of use.
** Postfix interception: ** Interception after a request has been returned, allowing processing and validation of the returned data after the request has been made,
(5) Blog list
After our login, we will enter the main page of the blog, which mainly displays the blog information currently entered into the system. The interface is as follows:
ββ
The blog’s display is in accordance with the time line, the final release of blog will appear in the first, at the same time, you will find in the blog home page head will show us some basic information, including personal information, and the function of the edit and exit, the header information will always appear in our page, so in order to implement code reuse, reduce the usage of the code, We extracted all the Header information and put it in header. vue.
<template> <div class="m_content"> <h3> < {user.username}} </h3> <div class="block"> < el-Avatar :size="50" :src="user.avatar"></el-avatar> <div>{{user.username}}</div> </div> <div class="maction"> <span><el-link type="primary" Href ="/blogs"> home </el-link></span> < el-Link type="success" </ el-Divider >< span><el-link type="success" A href = "/ blog/add" > blog < / el - link > < / span > < span v - show = "!" haslogin"> <el-divider direction="vertical"></el-divider> <span><el-link type="warning" Href ="/login"> login </el-link></span> </span> <span v-show="haslogin"> < el-Divider v-show="haslogin" Direction ="vertical"></ el-Divider >< span> <el-link type="danger" @click="logout"> Exit </el-link></span> </span> </div> </div> </template> <script> export default {name: "Header", data() {return {user: {username: "please login ", avatar: "Https://cube.elemecdn.com/3/7c/3ea6beec64369c2642b92c6726f1epng.png"}, haslogin: false}}, / / write method the methods: {// Exit logout() {const _this = this; $this.$axios.get("/logout", {headers: {"authorization": $this. Localstorage.getitem ("token")}}). Then (res => {// Clear data information _this.$store.com MIT ("REMOVE_INFO") // Return to the login page _this. $router. Push ("/login ")})},}, / / perform an initialization operation created () {/ / if the user name, Came back to show the if (this. $store. Getters. GetUser. Username) {this. User. The username = this. $store. The getters. GetUser. The username this.user.avatar = this.$store.getters.getUser.avatar this.haslogin = true } } } </script> <style scoped> .m_content { max-width: 960px; margin: 0 auto; text-align: center; } .maction { margin: 10px 0px; } </style>Copy the code
If the Header information is needed on another page, you only need to reference the Header page to the page and then write the Header information to the content. As follows:
import Header from "@/components/Header";
data() {
components: {Header}
}
Copy the code
The component is then invoked in the template
<Header></Header>
Copy the code
The main page of this blog includes the blog pagination, and the blog arrangement, because we use the pagination component, so the information returned directly with the page information, we can use it directly.
<template> <div> <Header></Header> <div class="block"> <el-timeline> <el-timeline-item :timestamp="blog.created" placement="top" v-for="blog in blogs"> <el-card> <router-link :to="{name:'BlogDetail',params:{blogId:blog.id}}"> <h4>{{blog.title}}</h4> </router-link> <p>{{blog.description}}</p> </el-card> </el-timeline-item> </el-timeline> </div> <el-pagination class="mpage" background layout="prev, pager, next" :current-page="currentPage" :page-size="pageSize" :total="total" @current-change=page > </el-pagination> </div> </template> <script> // import public Header import Header from ".. /components/Header"; Export default {name: "Blogs", // Register Header components: {Header}, // Return data {Blogs: {}, currentPage: }}, Mounted () {}, methods: CurrentPage () {const _this = this; _this.$axios.get("/blogs? currentPage=" + currentPage,{ headers: { "authorization": Localstorage.getitem ("token")}}).then(res => {console.log(res) _this.blogs = res.data.data.records) _this.currentPage = res.data.data.current _this.total = res.data.data.total _this.pageSize = res.data.data.size }) } }, created() { this.page(1) } } </script> <style scoped> .mpage { margin: 0 auto; text-align: center; } </style>Copy the code
Data () directly defines blogs, a list of blogs, as well as some paging information. Methods () defines page (currentPage). The parameter is the page number currentPage that needs to be adjusted, and the value can be directly assigned after the result is obtained. Mounted () then calls the first page this.page(1) in the mounted() method.
(6) Blog editor
In the blog edit page, we can edit existing blogs and publish new blogs, but this function is only available when logged in. In the blog edit page, we have introduced the Markdown editor, which has support for VUE. We can just import the dependencies and use them,
Markdown editor introduced
Step 1: Enter the plug-in
A useful plug-in for the Markdown editor is mavon-Editor, which we need to install first.
cnpm install mavon-editor –save
Step 2: Global registration
If you want to use it, you need to register it globally in the main.js file.
/ / global registered import Vue from 'Vue' import mavonEditor from 'mavon - editor' import 'mavon - editor/dist/CSS/index. The CSS' / / use Vue.use(mavonEditor)Copy the code
Step 3: Define to the page
The use of the Markdown editor, after being registered in the global page, only requires us to introduce the following code in the page.
<mavon-editor v-model="editForm.content"/>
Copy the code
That’s how Vue introduced the Markdown editor,
Also attached is the code for the blog edit page:
<template> <div> <Header></Header> <div class="m_content"> <el-form :model="ruleForm" :rules="rules" ref="ruleForm" Label-width ="100px" class="demo-ruleForm"> <el-form-item label=" title ="title"> <el-input V-model =" ruleform. title"></ el-form-item> </el-form-item> <el-form-item label=" Abstract "prop="description"> <el-input Type ="textarea" V-model =" ruleform. description"></el-input> </el-form-item> <el-form-item label=" content" prop="content"> <mavon-editor v-model="ruleForm.content"></mavon-editor> </el-form-item> <el-form-item> <el-button type="primary" </el-button @click="resetForm('ruleForm')"> </el-button> </el-form-item> </el-form> </div> </div> </template> <script> import Header from ".. /components/Header"; export default { name: "BlogEdit", components: {Header}, data() { return { ruleForm: { id: "", title: '', description: Rules: {title: [{required: true, message: 'Please enter the title ', trigger: 'blur'}, {min: 5, Max: 100, message: 'Length between 5 and 100 characters ', trigger: 'blur'}], Description: [{required: true, message:' Please enter summary ', trigger: 'blur'}], content: [{required: true, message: 'Please input content ', trigger: 'blur'}],}}; }, mounted() { console.log(localStorage.getItem("token")) }, methods: { submitForm(formName) { this.$refs[formName].validate((valid) => { if (valid) { const _this = this $axios.post("/blog/edit", this.ruleForm, {// Add token headers to request headers: {"authorization": localstorage.getitem ("token")}}). Then (res => {console.log(res) this. ', 'confirmButtonText ', {confirmButtonText:' confirmButtonText ', callback: action => {_this.$router. Push ("/blogs")}}); }) } else { console.log('error submit!! '); return false; }}); }, resetForm(formName) { this.$refs[formName].resetFields(); }}, // create () { The user's information can be displayed on the edit page when the user sends "/blog/{id}/edit" type request. The output will be displayed when the user sends "/blog/add" to add a blog. $route.params.blogid console.log(blogId) const _this = this; if (blogId){ _this.$axios.get("/blog/" + blogId).then(res=>{ const blog = res.data.data; _this.ruleForm.id = blog.id _this.ruleForm.title = blog.title _this.ruleForm.description = blog.description _this.ruleForm.content = blog.content }) } } } </script> <style scoped> .m_content { margin: 0 auto; } </style>Copy the code
The effect is as follows:
β
(7) Blog details page
In the blog details page, we need to echo our blog information, but when we publish the blog, we use the MarkDown editor, so when we echo the content with the Markdown tag, how should we echo our official edited text?
Here you need to use a plug-in markdown-it, which parses the MD document and then imports Github-Markdown-it, using the MD style.
Parse MD documents
cnpm install markdown-it –save
# md style
cnpm install github-markdown-css
This is used where you need to write blog text:
<div class="content markdown-body" v-html="blog.content"></div>
Copy the code
There are also some MD related renderings that you can see in the code,
** The logic goes like this; ** Initialize the create() method by calling getBlog() and requesting the blog details interface. The returned blog details content is rendered by markdown-it tool.
Import styles again:
import 'github-markdown.css'
Copy the code
Then add class markdown-body to the content div
<template> <div> <Header></Header> <div class="mblog"> <h2>{{blog.title}}</h2> <div> <el-link icon="el-icon-edit" v-if="ownBlog"> <! <router-link :to="{name:'BlogEdit',params:{blogId: Blog.id}}"> Edit </router-link> </el-link> < el-Divider direction="vertical"></ el-Divider >< el-link type="danger" @click="messageHandel" V-if ="ownBlog"> Delete </el-link> </div> < el-Divider ></ el-Divider >< div class="markdown-body" v-html="blog.content"></div> </div> <el-dialog :title="title" :visible.sync="centerDialogVisible" width="30%" center> <span slot="footer" class="dialog-footer"> <el-button @click="centerDialogVisible = false" Type ="primary" @click="deleteBlog"> </el-button> </span> </el-dialog> </div> </template> <script Header from ".. /components/Header"; Import "github-markdown- CSS /github-markdown. CSS "import messageModel from ".. /components/messageModel"; export default { name: "BlogDetail", components: {Header, messageModel}, data() { return { centerDialogVisible: False, title:' Are you sure you want to delete this blog post? ', blog: { id: "", title: "", content: "" }, ownBlog: false } }, methods:{ messageHandel(){ this.centerDialogVisible = true }, deleteBlog(){ const _this = this const blogid = this.$route.params.blogId _this.$axios.post("/blog/delete/" + blogid,{ headers:{ "authorization":localStorage.getItem("token") } }).then(res=>{ console.log(res) const code = res.data.code if (code === 200){this.centerDialogVisible = false _this. route. push("/blogs")}else {this.centerDialogVisible (' Delete failed! ', 'prompt ', {confirmButtonText:' confirmButtonText ',}); }}}}), Created () {const blogId = this.$route.params.blogid console.log(blogId) const _this = this $axios.get("/blog/" + blogId). Then (res => {const blog = res.data.data; _this.blog.id = blog.id _this.blog.title = blog.title _this.blog.description = blog.description /** * Convert the markdown text to normal text Var markDownIT = require(" markDownIT ") var md = new markDownIT( Result = md.render(blog.content) // Render markdown text into HTML text _this.blog.content = result // Assign normal text to the page display // Only if the current blog user ID matches the current logged-in user ID. _this.ownblog = (blog.userId === _this.$store.getters.getuser.id)})} </script> <style scoped>.mblog { Box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.1); width: 100%; min-height: 760px; margin: 10px 15px; } /deep/.el-dialog__body{ padding: 0 ! important } </style>Copy the code
The result is as follows. The blog post I posted on CSDN is basically the same.
β
(8) Permission route interception
Because we also mentioned at the beginning that some pages need to be logged in to access, so how to intercept in the foreground? Actually thinking is very simple, main is to give each page request to add a parameter, mark whether it is need in the condition of the login to access, at the same time filter to intercept every request, if the request is need to log in to access, then from the browser access token, if you can get enough, release, or jump to the login page.
I summed up a separate blog about this knowledge point, welcome friends to learn! Two steps to setting up login authentication interception in Vue! * * * *
Fourth, write at the end & project summary
Even if the development of the whole project is completed, I have also referred to the video explanation of the leader of STATION B, and I have learned a lot. The whole project is very friendly to beginners who learn the separation of front and background. Shiro + JWT security authentication, unified result encapsulation, routing Settings in the foreground page, storing and obtaining tokens, cross-domain solution, login authentication and so on are all worth learning.
The project refers to teacher MarkerHub’s “4 hours to develop a SpringBoot+ Vue front-end and back-end separation blog project”. The explanation in this article is my personal development summary and ideas, if you have any questions, welcome to criticize and correct! Link to a video of teacher MarkerHub.
Project source code I put in gitee, [source link], friends don’t forget βstarβ yo!
** a key three even add attention! Grey Ape is taking you to the highway! * * β¨ β¨ β¨
I’m Grey Ape, and I’ll see you next time!
β
β