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元素生效,不影响全局样式。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章