VUE.JS和NODE.JS构建一个简易的前后端分离静态博客系统(三)
Edit.vue
<template>
<div id="edit">
<ClassicHeader>
<template v-slot:left>
<span>编辑随笔</span>
</template>
<template v-slot:right>
<el-button @click="newPost('Management')">存为草稿</el-button>
<el-button @click="newPost()">存为草稿并继续编辑</el-button>
</template>
</ClassicHeader>
<main>
<div id="post_title">
<el-input v-model="post_title" placeholder="标题"></el-input>
</div>
<div id="post_content">
<editor :init="tinymce_init" v-model="post_content" />
</div>
<div id="post_category">
<SubTitle>分类</SubTitle>
<el-checkbox-group v-model="checkList">
<el-checkbox v-for="category in categories" :key="category.id" :label="category.id">{{category.name}}</el-checkbox>
</el-checkbox-group>
</div>
</main>
</div>
</template>
<script>
import axios from 'axios'
import qs from 'qs'
import Editor from '@tinymce/tinymce-vue'
import ClassicHeader from '@/components/ClassicHeader.vue'
import SubTitle from '@/components/SubTitle'
export default {
name: 'Edit',
components: {
'editor': Editor,
ClassicHeader,
SubTitle,
},
data() {
return {
post_title: '',
post_content: '',
checkList: [],
tinymce_init: {
height: 654,
language: 'zh-Hans',
menubar: false,
plugins: 'wordcount table searchreplace save preview media lists link insertdatetime image emoticons codesample code charmap autolink anchor advlist',
toolbar: ['bold italic underline strikethrough | numlist bullist | forecolor backcolor | alignleft aligncenter alignright alignjustify | outdent indent | searchreplace | preview wordcount | print',
'link unlink anchor | removeformat | codesample | code | blocks fontfamily | image media insertdatetime insertfile emoticons charmap | undo redo',
'table tabledelete | tableprops tablerowprops tablecellprops | tableinsertrowbefore tableinsertrowafter tabledeleterow | tableinsertcolbefore tableinsertcolafter tabledeletecol'],
},
categories: [],
}
},
mounted() {
this.reloadCategories()
},
methods: {
reloadCategories() {
axios.get(this.$url_categories)
.then(resp => {
this.categories = resp.data
})
.catch(err => {
console.log(err)
})
},
newPost(jump2 = 'Edit2') {
if (!this.post_title && this.post_title.trim() === '') {
this.$message.error('标题不能为空!');
return
}
console.log(this.checkList)
const DATA = qs.stringify({
'title': this.post_title,
'content': this.post_content,
'category': JSON.stringify(this.checkList),
});
const CONFIG = {
method: 'post',
url: this.$url_posts,
headers: {
'Content-Type': 'application/x-www-form-urlencoded'
},
data : DATA
};
// 把屏幕锁了防止乱点
const LOADING = this.$loading({
lock: true,
// spinner: 'el-icon-loading',
background: 'rgba(0, 0, 0, 0.7)'
});
axios(CONFIG)
.then((resp) => {
let post = resp.data
setTimeout(() => {
// 至少锁1秒才解除
LOADING.close();
this.$message({
type: 'success',
message: '操作成功',
});
if (this.$route.name !== jump2) {
this.$router.push({
name: jump2,
query: {
post_id: post.id,
},
});
}
}, 1000);
})
.catch(function (error) {
console.log(error);
});
}
},
}
</script>
<style scoped>
#edit {
display: flex;
flex-direction: column;
}
main {
display: flex;
flex-direction: column;
padding-bottom: 30px;
}
#post_title {
padding: 10px 10px;
border: 1px solid #dcdfe6;
}
#post_content {
padding: 10px 10px;
border: 1px solid #dcdfe6;
}
#post_category {
padding: 10px 10px;
border: 1px solid #dcdfe6;
}
</style>
这段代码也是一个Vue.js组件,实现了一个新建文章的编辑页面。页面包含一个标题、一个内容编辑器和一个分类复选框,以及两个按钮:存为草稿、存为草稿并继续编辑。
与前一个组件不同的是,该组件的post属性被拆分成了post_title和post_content两个变量,用于分别存储文章的标题和内容。另外,该组件中没有定义post的状态属性。相应地,该组件的方法中也没有定义发布文章的方法,只有一个newPost()方法,用于将用户编辑的文章保存为草稿。
该组件的实现与前一个组件的大部分代码类似,只是去除了一些发布文章相关的逻辑,而增加了一些保存草稿相关的逻辑。在newPost()方法中,用户编辑的文章数据被包装成一个表单数据,通过axios发送到服务器进行保存。保存成功后,根据不同的参数值,分别跳转到编辑页面或文章管理页面。
最后,该组件也定义了一些样式,用于控制页面的布局和样式,其中#edit用于设置页面的显示方式为flex布局,#post_title、#post_content和#post_category用于设置标题、内容编辑器和分类复选框的边框和内边距。这些样式是通过Vue.js的scoped样式功能实现的,只对当前组件的DOM元素生效,不影响全局样式。
Edit2.vue
<template>
<div id="edit2">
<ClassicHeader>
<template v-slot:left>
<span>正在编辑随笔:{{ post_id }}</span>
</template>
<template v-slot:right>
<el-button @click="publish" :disabled="post.state === '已发布'">发布</el-button>
<el-button @click="updatePost()">保存</el-button>
<el-button @click="updateAndExit()">保存并退出编辑模式</el-button>
</template>
</ClassicHeader>
<main>
<div id="post_title">
<el-input v-model="post.title" placeholder="标题"></el-input>
</div>
<div id="post_content">
<editor :init="tinymce_init" v-model="post.content" />
</div>
<div id="post_category">
<SubTitle>分类</SubTitle>
<el-checkbox-group v-model="checkList">
<el-checkbox v-for="category in categories" :key="category.id" :label="category.id">{{category.name}}</el-checkbox>
</el-checkbox-group>
</div>
</main>
</div>
</template>
<script>
import axios from 'axios'
import qs from 'qs'
import Editor from '@tinymce/tinymce-vue'
import ClassicHeader from '@/components/ClassicHeader.vue'
import SubTitle from '@/components/SubTitle'
export default {
name: 'Edit2',
components: {
'editor': Editor,
ClassicHeader,
SubTitle,
},
data() {
return {
post: {
title: '',
content: '',
state: '',
},
categories: [],
checkList: [],
tinymce_init: {
height: 654,
language: 'zh-Hans',
menubar: false,
plugins: 'wordcount table searchreplace save preview media lists link insertdatetime image emoticons codesample code charmap autolink anchor advlist',
toolbar: ['bold italic underline strikethrough | numlist bullist | forecolor backcolor | alignleft aligncenter alignright alignjustify | outdent indent | searchreplace | preview wordcount | print',
'link unlink anchor | removeformat | codesample | code | blocks fontfamily | image media insertdatetime insertfile emoticons charmap | undo redo',
'table tabledelete | tableprops tablerowprops tablecellprops | tableinsertrowbefore tableinsertrowafter tabledeleterow | tableinsertcolbefore tableinsertcolafter tabledeletecol'],
},
}
},
computed: {
post_id: {
get() {
return this.$route.query.post_id
},
}
},
mounted() {
this.reloadCategories()
this.reloadPost()
},
methods: {
reloadPost() {
axios.get(this.$url_posts + `/${ this.post_id }`)
.then(resp => {
this.post = resp.data
// 不懂为啥,对于数组不管是发送还是接收都要手动进行 解析/转化
this.checkList = JSON.parse(this.post.category)
})
.catch(err => {
console.log(err)
})
},
reloadCategories() {
axios.get(this.$url_categories)
.then(resp => {
this.categories = resp.data
})
.catch(err => {
console.log(err)
})
},
publish() {
this.post.state = '已发布'
this.post.pubDate = new Date()
this.updateAndExit()
},
updateAndExit() {
this.updatePost(true)
},
updatePost(jump2 = false) {
if (!this.post.title && this.post.title.trim() === '') {
this.$message.error('标题不能为空!');
return
}
const data = qs.stringify({
'title': this.post.title,
'content': this.post.content,
'state': this.post.state,
'pubDate': this.post.pubDate,
'category': JSON.stringify(this.checkList),
});
const config = {
method: 'post',
url: this.$url_posts + `/${this.post_id}`,
headers: {
'Content-Type': 'application/x-www-form-urlencoded'
},
data : data
};
const LOADING = this.$loading({
lock: true,
background: 'rgba(0, 0, 0, 0.7)'
});
axios(config)
.then((resp) => {
let post = resp.data
console.log(post)
setTimeout(() => {
LOADING.close();
this.$message({
type: 'success',
message: '操作成功',
});
if (jump2) {
if (this.$route.name !== 'Management') {
this.$router.push({
name: 'Management',
});
}
}
}, 1000);
})
.catch(function (error) {
console.log(error);
});
},
},
}
</script>
<style scoped>
#edit2 {
display: flex;
flex-direction: column;
padding-bottom: 30px;
}
#post_title {
padding: 10px 10px;
border: 1px solid #dcdfe6;
}
#post_content {
padding: 10px 10px;
border: 1px solid #dcdfe6;
}
#post_category {
padding: 10px 10px;
border: 1px solid #dcdfe6;
}
</style>
这段代码是一个Vue.js组件,主要实现了一个编辑页面的功能。页面包含一个标题、一个内容编辑器、一个分类复选框和三个按钮:发布、保存、保存并退出编辑模式。
该组件首先引入了一些第三方库和组件,如axios(一个基于Promise的HTTP库)、qs(一个处理URL参数和请求payload的库)、@tinymce/tinymce-vue(一个Vue.js组件,提供了一个所见即所得的富文本编辑器)以及一些自定义组件。接着定义了一些组件内部的data和computed属性,用于存储页面的状态和一些计算属性。其中post属性用于存储用户编辑的文章的标题、内容和状态,categories属性用于存储文章分类列表,checkList属性用于存储用户选择的分类,tinymce_init属性用于配置富文本编辑器的一些参数,post_id属性用于获取路由参数中的post_id参数。
该组件定义了一些方法,用于加载文章和分类、发布文章、保存文章和退出编辑模式。其中reloadPost()和reloadCategories()方法用于加载文章和分类列表,publish()方法用于将文章状态设置为已发布,并调用updateAndExit()方法保存并退出编辑模式,updateAndExit()方法用于保存文章并退出编辑模式,updatePost()方法用于保存文章的具体实现。该方法首先对文章标题进行非空校验,然后将用户编辑的文章数据包装成一个表单数据,通过axios发送到服务器进行保存,并在保存成功后显示一个操作成功的提示消息。
最后,该组件还定义了一些样式,用于控制页面的布局和样式。其中#edit2用于设置页面的显示方式为flex布局,#post_title、#post_content和#post_category用于设置标题、内容编辑器和分类复选框的边框和内边距。这些样式是通过Vue.js的scoped样式功能实现的,只对当前组件的DOM元素生效,不影响全局样式。