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

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