富文字編輯器 vue-quill-editor使用(新增,展示,修改,新增附件相關)

2021-03-25 12:01:15

上一篇文章連結
富文字編輯器 vue-quill-editor入門(使用,顯示,表格支援)

之前由於自己也只是粗淺的使用,以為沒有太多人和我一樣用到它
有一些地方,就沒有展開講
沒想到大家用到的還挺多的,於是再展開講解一下
先看一下效果

NewsAdd.vue 新增頁面,可以新增附件
在這裡插入圖片描述

NewsDetail.vue 展示頁面
在這裡插入圖片描述

NewsUpdate.vue 修改頁面 獲取富文字內容,然後展示,以供修改
在這裡插入圖片描述

專案程式碼放github不太合適, 就直接粘一下了
簡單講解一下邏輯

  • editor.vue 富文字編輯器元件
    1.activated元件的時候,會請求介面,載入富文字內容this.quill.root.innerHTML = this.content2,展示相應的內容。
    2.在mounted中使用this.quill.on(‘text-change’, () => {this.$emit(‘contentData’, this.quill.root.innerHTML, this.enclosureIDs)})來監聽修改,將html格式的富文字,傳遞給父元件。
    3.使用el-upload實現附件上傳,自定義了它的httpRequest

  • NewsAdd.vue 新增頁面
    1.使用@contentData="change(arguments,userDefined)"獲取子元件傳來的內容
    2.請求介面,儲存資料

  • NewsUpdate 修改頁面
    1.使用@contentData=「change($event)」 獲取子元件傳來的,修改後的資料
    2.請求介面,修改資料

  • NewsDetail 展示頁面
    1.無需引入editor元件,只需要從介面中取出富文字的html,放在

<el-col :span="20">
          <div class="ql-container ql-snow">
            <div class="ql-editor" v-html="form.content"></div>
          </div>
          <!-- <editor v-html="form.content" style="background-color:white"></editor> -->
        </el-col>

即可顯示內容。

以下為程式碼,大家可以取出自己需要的部分

editor.vue

<template>
  <keep-alive>
    <div>
      <div class="editor"></div>
      <el-upload
        class="upload-demo"
        action="#"
        :http-request="httpRequest"
        :on-preview="handlePreview"
        :on-remove="handleRemove"
        :before-remove="beforeRemove"
        multiple
        :limit="50"
        :on-exceed="handleExceed"
        :ref="upload"
        :file-list="fileList">
        <el-button
          size="small"
          type="primary"
          @click="uploadBtn"
          class="my-upload"
          v-show="false"
          style="height:0px">點選上傳</el-button>
        <div slot="tip" class="el-upload__tip" style="margin-top:0px">已選擇附件:</div>
      </el-upload>
    </div>
  </keep-alive>
</template>

<script>
import api from '@/api/index'
import Quill from 'quill'
import 'quill/dist/quill.snow.css'
const titleConfig = {
  'ql-bold': '加粗',
  'ql-color': '顏色',
  'ql-font': '字型',
  'ql-code': '插入程式碼',
  'ql-italic': '斜體',
  'ql-link': '新增連結',
  'ql-background': '顏色',
  'ql-size': '字型大小',
  'ql-strike': '刪除線',
  'ql-script': '上標/下標',
  'ql-underline': '下劃線',
  'ql-blockquote': '參照',
  'ql-header': '標題',
  'ql-indent': '縮排',
  'ql-list': '列表',
  'ql-align': '文字對齊',
  'ql-direction': '文字方向',
  'ql-code-block': '程式碼塊',
  'ql-formula': '公式',
  'ql-image': '圖片',
  'ql-video': '視訊',
  'ql-clean': '清除字型樣式',
  'ql-upload': '檔案',
  'ql-table': '插入表格',
  'ql-table-insert-row': '插入行',
  'ql-table-insert-column': '插入列',
  'ql-table-delete-row': '刪除行',
  'ql-table-delete-column': '刪除列'
}
export default {
  name: 'Editor',
  props: {
  },
  data () {
    return {
      upload: '',
      enclosureIDs: [],
      fileReader: null,
      fileList: [],
      content2: '',
      loadData: (date = this.date) => {
            return this.$http
            .get(api.NewsDetail, {
                params: { nid: this.$route.query.nid }
            })
            .then(res => {
                var resData = res.result.data[0]
                if (resData) {
                this.content2 = resData.content
                this.quill.root.innerHTML = this.content2
                } else {
                this.content2 = ''
                }
            })
        },
      quill: null,
      uploadAPI: api.Enclosure,
      options: {
        theme: 'snow',
        modules: {
          toolbar: {
            container: [
              ['bold', 'italic', 'underline', 'strike'],
              [{ header: 1 }, { header: 2 }],
              [{ list: 'ordered' }, { list: 'bullet' }],
              [{ indent: '-1' }, { indent: '+1' }],
              [{ color: [] }, { background: [] }],
              [{ font: [] }],
              [{ align: [] }],
              ['clean'],
              [
                { table: 'TD' },
                { 'table-insert-row': 'TIR' },
                { 'table-insert-column': 'TIC' },
                { 'table-delete-row': 'TDR' },
                { 'table-delete-column': 'TDC' }
              ],
              ['sourceEditor']
            ],
            handlers: {
              table: function (val) {
                this.quill.getModule('table').insertTable(2, 3)
              },
              'table-insert-row': function () {
                this.quill.getModule('table').insertRowBelow()
              },
              'table-insert-column': function () {
                this.quill.getModule('table').insertColumnRight()
              },
              'table-delete-row': function () {
                this.quill.getModule('table').deleteRow()
              },
              'table-delete-column': function () {
                this.quill.getModule('table').deleteColumn()
              },
              sourceEditor: function () { // 新增工具方法
                  document.getElementsByClassName('my-upload')[0].click()
              }

            }
          },
          table: true
        },
        placeholder: ''
      }
    }
  },
  methods: {

    httpRequest (options) {
          var file = options.file
          var filename = file.name
          const reader = new FileReader()
          reader.readAsDataURL(file)
          const that = this
          reader.onload = function () {
            var base64Str = this.result
            if (base64Str === 'data:') {
              // 為空txt時下載的時候會顯示網路錯誤,這裡進行處理轉換為有一個空格的txt
               base64Str = 'data:text/plain;base64,IA=='
            }
            that.$http
            .post(api.Enclosure, {
                params: { base64Str: base64Str, name: filename, nid: that.$route.query.nid }
              })
            .then(res => {
              that.enclosureIDs.push({ 'en_id': res.enclosure_id, 'name': filename })
              that.$emit('contentData', that.quill.root.innerHTML, that.enclosureIDs)
            })
          }
    },
    uploadBtn () {
    },
    handleRemove (file, fileList) {
        var deleteId = 0
        if (file.id) {
          for (var i = 0; i < this.fileList.length; i++) {
            if (this.fileList[i].name === file.name) {
              this.fileList.splice(i, 1)
                deleteId = file.id
                break
            }
          }
        } else if (file.uid) {
          for (var j = 0; j < this.enclosureIDs.length; j++) {
            if (this.enclosureIDs[j].name === file.name) {
                deleteId = this.enclosureIDs[j]['en_id']
                this.enclosureIDs.splice(j, 1)
                break
            }
          }
        }
        // for (var i = this.fileList.length - 1; i > 0; i--) {
        //   console.log(this.fileList[i])
        //   }
        // this.$emit('contentData', this.quill.root.innerHTML, this.enclosureIDs)
        // // 請求刪除附件介面
        this.$http
          .delete(api.Enclosure, {
            params: { eid: deleteId }
          })
          .then(res => {
            this.$emit('contentData', this.quill.root.innerHTML, this.enclosureIDs)
            this.$message({
            type: 'success',
            message: '刪除成功!'
          })
          })
      },
    handlePreview (file) {
    },
    handleExceed (files, fileList) {
      this.$message.warning(`當前限制選擇 3 個檔案,本次選擇了 ${files.length} 個檔案,共選擇了 ${files.length + fileList.length} 個檔案`)
    },
    beforeRemove (file, fileList) {
      return this.$confirm(`確定移除 ${ file.name }?`)
    },
    addQuillTitle () {
      const oToolBar = document.querySelector('.ql-toolbar')
      const aButton = oToolBar.querySelectorAll('button')
      const aSelect = oToolBar.querySelectorAll('select')
      aButton.forEach(function (item) {
        if (item.className === 'ql-script') {
          item.value === 'sub' ? (item.title = '下標') : (item.title = '上標')
        } else if (item.className === 'ql-indent') {
          item.value === '+1' ? (item.title = '向右縮排') : (item.title = '向左縮排')
        } else {
          item.title = titleConfig[item.classList[0]]
        }
      })
      aSelect.forEach(function (item) {
        item.parentNode.title = titleConfig[item.classList[0]]
      })
    },
    getContentData () {
      return this.quill.getContents()
    }
  },
  mounted () {
    this.fileReader = new FileReader()
    const dom = this.$el.querySelector('.editor')
    this.quill = new Quill(dom, this.options)
    this.myEl = this.$el.querySelector('.ql-sourceEditor')
    // this.quill.setContents(this.value)
    this.quill.on('text-change', () => {
    //   console.log(this.quill.getContents())
    //   this.$emit('contentData', this.quill.getContents())
    //   console.log(this.quill.root.innerHTML)
      this.$emit('contentData', this.quill.root.innerHTML, this.enclosureIDs)
    })
    this.$el.querySelector(
      '.ql-table-insert-row'
    ).innerHTML = `<svg t="1591862376726" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="6306" width="18" height="200"><path d="M500.8 604.779L267.307 371.392l-45.227 45.27 278.741 278.613L779.307 416.66l-45.248-45.248z" p-id="6307"></path></svg>`
    this.$el.querySelector(
      '.ql-table-insert-column'
    ).innerHTML = `<svg t="1591862238963" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="6509" width="18" height="200"><path d="M593.450667 512.128L360.064 278.613333l45.290667-45.226666 278.613333 278.762666L405.333333 790.613333l-45.226666-45.269333z" p-id="6510"></path></svg>`
    this.$el.querySelector(
      '.ql-table-delete-row'
    ).innerHTML = `<svg t="1591862253524" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="6632" width="18" height="200"><path d="M500.8 461.909333L267.306667 695.296l-45.226667-45.269333 278.741333-278.613334L779.306667 650.026667l-45.248 45.226666z" p-id="6633"></path></svg>`
    this.$el.querySelector(
      '.ql-table-delete-column'
    ).innerHTML = `<svg t="1591862261059" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="6755" width="18" height="200"><path d="M641.28 278.613333l-45.226667-45.226666-278.634666 278.762666 278.613333 278.485334 45.248-45.269334-233.365333-233.237333z" p-id="6756"></path></svg>`
    this.addQuillTitle()
    this.$el.querySelector(
      '.ql-sourceEditor'
    ).innerHTML = `<svg t="1592278063482" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="6358" width="18" height="200"><path d="M704 341.333333h64a64 64 0 0 1 64 64v362.666667a64 64 0 0 1-64 64H256a64 64 0 0 1-64-64V405.333333a64 64 0 0 1 64-64h64v64h-64v362.666667h512V405.333333h-64v-64zM517.653333 124.629333l150.826667 150.826667-45.226667 45.269333-74.026666-74.005333v304.768h-64V247.616l-73.173334 73.130667-45.248-45.248 150.826667-150.848z" p-id="6359"></path></svg>`
  },
  activated () {
    if (this.$route.query.nid) {
      this.loadData()
    } else {
      this.quill.root.innerHTML = ''
    }
  }
}
</script>
<style>
.el-upload-list__item {
      transition: none !important;
    }
</style>

NewsAdd.vue

<template>
  <keep-alive>
    <div :style="widthStyle">
      <el-row :gutter="24">
        <el-col :span="24">
          <el-form ref="form" :model="form" label-width="80px">
            <el-form-item label>
              <h1 style="font-size:30px">釋出訊息</h1>
            </el-form-item>
            <el-form-item label="型別">
              <el-select v-model="select_value" filterable placeholder="型別" @change="selectChanged">
                <el-option
                  v-for="item in SelectOptions"
                  :key="item.id"
                  :label="item.name"
                  :value="item.id"
                ></el-option>
              </el-select>
            </el-form-item>
            <el-form-item label="標題">
              <el-input placeholder="標題" v-model="title"></el-input>
            </el-form-item>
            <el-form-item label="內容">
              <editor @contentData="change(arguments,userDefined)" ref="son"></editor>
            </el-form-item>
            <el-form-item>
              <el-button type="primary" :disabled="isAble" @click="onSubmit">立即建立</el-button>
            </el-form-item>
            <el-form-item>
            </el-form-item>
          </el-form>
        </el-col>
      </el-row>
      <!-- <el-row :gutter="24">
        <el-col :span="18">
          <div class="my-upload">
            <el-upload
              multiple
              ref="upload"
              action=""
              accept=".pdf"
              :before-remove="handleBeforeRemove"
              :file-list="fileList"></el-upload>
          </div>
        </el-col>
      </el-row> -->
    </div>
  </keep-alive>
</template>
<script>
import api from '@/api/index'
import editor from '@/views/news/editor.vue'
export default {
  components: {
    editor
  },
  name: 'Editor',
  computed: {
      widthStyle: function () {
        // console.log(document.body.clientWidth)
        var result = ''
        var screenWidth = document.body.clientWidth
        if (screenWidth <= 1280) {
          result = '100%'
        } else if (screenWidth > 1280 && screenWidth <= 1440) {
          result = '90%'
        } else {
          result = '80%'
        }
        return {
          width: result
        }
      }
    },
  data: function () {
    return {
      screenWidth: document.body.clientWidth,
      userDefined: '',
      enclosureIDs: [],
        loadSelectData: (date = this.date) => {
        return this.$http
          .get(api.NewsType, {
            params: { 'select': true }
          })
          .then(res => {
            // this.SelectOptions = res.result.data
            // var result = []currentPage
            // res.result.data.forEach((item) => {
            //     result.push({ 'id': item.tid, 'name': item.news_type })
            // }
            // )
            // console.log(result)
            this.SelectOptions = res.result.data
          })
      },
      bg: {},
      plan: {},
      content: {},
      contentHtml: '',
      SelectOptions: [],
      select_value: '',
      value: '<h1>123</h1>',
      title: '釋出訊息',
      isAble: false,
      form: {
        name: '',
        region: '',
        date1: '',
        date2: '',
        delivery: false,
        type: [],
        resource: '',
        desc: '',
        SelectOptions: []
      },
      quill: null
    }
  },
  methods: {
    change (data) {
      this.contentHtml = data[0]
      this.enclosureIDs = data[1]
    },
    onSubmit () {
        this.isAble = true
        // console.log(this.$refs.childMethod.getContentData())
        this.$http
          .post(api.NewsDetail, {
            params: { type_id: this.select_value, title: this.title, content: this.contentHtml, enclosure_ids: this.enclosureIDs }
          })
          .then(res => {
            this.isAble = false
            this.$refs.son.fileList = []
            this.$refs.son.enclosureIDs = []
            this.SelectOptions = res.result.data
            this.$router.push({
                  path: '/news/'
              })
          })
    },
    selectChanged (value) {}
  },
  created: function () {},
  mounted () {
    this.loadSelectData()
  },
  activated () {
    this.title = ''
    this.content = {}
    this.contentHtml = ''
    this.loadSelectData()
  }
}
</script>
<style>
.ql-editor {
  min-height:400px; height:auto!important; height:400px;
}
</style>

NewsUpdate.vue

<template>
  <keep-alive>
    <el-row :gutter="24">
      <el-col :span="18">
        <el-form label-width="80px">
          <el-form-item label>
            <h1 style="font-size:30px">修改</h1>
          </el-form-item>
          <el-form-item label="標題">
            <el-input placeholder="標題" v-model="title"></el-input>
          </el-form-item>
          <el-form-item label="內容">
            <editor @contentData="change($event)" ref="son"></editor>
          </el-form-item>
          <el-form-item>
            <el-button type="primary" @click="onSubmit" >確認修改</el-button>
          </el-form-item>
        </el-form>
      </el-col>
    </el-row>
  </keep-alive>
</template>
<script>
import api from '@/api/index'
import editor from '@/views/news/editor.vue'
export default {
  components: {
    editor
  },
  name: 'Editor',
  data: function () {
    return {
        aaa: 'aaa',
        loadData: (date = this.date) => {
            return this.$http
            .get(api.NewsDetail, {
                params: { nid: this.$route.query.nid }
            })
            .then(res => {
                var resData = res.result.data[0]
                if (resData) {
                this.title = resData.title
                this.content = resData.content
                // this.$refs.son.fileList = [{ name: '空.txt', url: '#' }, { name: 'food2.jpeg', url: '#' }]
                // this.quill.root.innerHTML = resData.content
                } else {
                this.title = ''
                this.content = ''
                }
            })
        },
        loadEnclosureData: () => {
        return this.$http
          .get(api.Enclosure, {
            params: { nid: this.$route.query.nid }
          })
          .then(res => {
            this.$refs.son.fileList = res
          })
      },
        bg: {},
        plan: {},
        content: '',
        contentHtml: '',
        SelectOptions: [],
        select_value: '',
        value: '<h1>123</h1>',
        title: '',
        quill: null
    }
  },
  methods: {
    change (data) {
      this.contentHtml = data
    },
    onSubmit () {
    //   console.log(this.$refs.childMethod.getContentData())
      this.$http
        .patch(api.NewsDetail, {
          params: { nid: this.$route.query.nid, title: this.title, content: this.contentHtml }
        })
        .then(res => {
            this.$message({
            type: 'success',
            message: '修改成功!'
          })
          this.$router.push({
                path: '/news/detail/',
                query: {
                nid: this.$route.query.nid
                }
            })
        })
        }
    },
    created: function () {
        // this.loadData()
    },
    mounted: function () {
        // this.loadData()
        // this.loadEnclosureData()
        },
    activated () {
        this.loadData()
        this.loadEnclosureData()
    }
}
</script>
<style>
.ql-editor {
  min-height:400px; height:auto!important; height:400px;
}
</style>

NewsDetail.vue

<template>
  <keep-alive>
    <div>
      <el-row :gutter="24" type="flex" justify="center">
        <el-col :span="10" >
          <div>
            <h3 style="font-size:30px">{{ form.news_type }} - {{ form.title }}</h3>
          </div>
        </el-col>
      </el-row>
      <el-row :gutter="24" type="flex" justify="space-between">
        <el-col :span="6">
          <div>
            <h3>{{ form.department }} - {{ form.nickname }}</h3>
          </div>
        </el-col>
        <el-col :offset="8" :span="6">
          <div>
            <h3>{{ form.date }}</h3>
          </div>
        </el-col>
      </el-row>
      <el-row v-show="EnclosureList.length">
        <el-col :span="10">
          <h3>附件:</h3>
          <div v-for="en in EnclosureList" :key="en.id">
            <a :href="en.content" :download="en.name">{{ en.name }}</a>
            <!-- <a :href="en.content">{{ en.name }}</a> -->
            <a style="margin-left:20px" @click="preView(en.content)">預覽</a>
          </div>
        </el-col>
      </el-row>
      <el-row :gutter="24">
        <el-col :span="20">
          <div class="ql-container ql-snow">
            <div class="ql-editor" v-html="form.content"></div>
          </div>
          <!-- <editor v-html="form.content" style="background-color:white"></editor> -->
        </el-col>
      </el-row>
      <el-row>
        <el-col :span="10">
          <el-button type="primary" plain style="margin-bottom:20px" @click="updateNews" v-show="showUpdateBtn">修改</el-button>
          <el-button type="danger" plain style="margin-bottom:20px" @click="deleteNews" v-show="showDeleteBtn">刪除</el-button>
        </el-col>
      </el-row>
      <el-dialog
        title="提示"
        :visible.sync="dialogVisible"
        width="30%"
        :before-close="handleClose">
        <span>確定要刪除該訊息麼 ?</span>
        <span slot="footer" class="dialog-footer">
          <el-button @click="dialogVisible = false">取 消</el-button>
          <el-button type="primary" @click="confirmDelete">確 定</el-button>
        </span>
      </el-dialog>
    </div>
  </keep-alive>

</template>
<script>
import api from '@/api/index'
import editor from '@/views/news/editor.vue'

export default {
  components: {
    editor
  },
  name: 'Detail',
  data: function () {
    return {
      dialogVisible: false,
      showDeleteBtn: false,
      showUpdateBtn: false,
      EnclosureList: [],
      content: {},
      loadData: (date = this.date) => {
        return this.$http
          .get(api.NewsDetail, {
            params: { nid: this.$route.query.nid, tid: this.$route.query.tid }
          })
          .then(res => {
            var resData = res.result.data[0]
            if (resData) {
              this.form.news_id = resData.id
              this.form.news_type = resData.news_type
              this.form.department = resData.department
              this.form.nickname = resData.nickname
              this.form.date = resData.date
              this.form.title = resData.title
              this.form.content = resData.content
              if (resData.deleteable) {
                  this.showDeleteBtn = true
              }
              if (resData.updateable) {
                  this.showUpdateBtn = true
              }
            } else {
              this.form.news_id = ''
              this.form.news_type = ''
              this.form.department = ''
              this.form.nickname = ''
              this.form.date = ''
              this.form.title = ''
              this.form.content = ''
            }
          })
      },
      loadEnclosureData: () => {
        return this.$http
          .get(api.Enclosure, {
            params: { nid: this.$route.query.nid }
          })
          .then(res => {
            this.EnclosureList = res
          })
      },
      form: {
        news_id: '',
        news_type: '',
        department: '',
        nickname: '',
        date: '',
        title: '',
        content: {}
      }
    }
  },
  methods: {
    preView (content) {
      var string = content
      var iframe = "<iframe width='100%' height='100%' src='" + string + "'></iframe>"
      var x = window.open()
      x.document.open()
      x.document.write(iframe)
      x.document.close()
    },
    confirmDelete () {
        this.$http
          .delete(api.NewsDetail, {
            params: { nid: this.$route.query.nid }
          })
          .then(res => {
            this.$message({
            type: 'success',
            message: '刪除成功!'
          })
            this.$router.push({
            path: '/news/',
            query: {}
            })
          })
      this.dialogVisible = false
    },
    handleClose (done) {
          done()
      },
    onSubmit () {},
    updateNews () {
          this.$router.push({
          path: '/news/update/',
          query: { nid: this.$route.query.nid }
          })
      },
    deleteNews () {
      this.dialogVisible = true
    }
    },
    created: function () {},
    mounted: function () {
        this.loadData()
        this.loadEnclosureData()
    },
    activated () {
        this.showUpdateBtn = false
        this.showDeleteBtn = false
        this.loadData()
        this.loadEnclosureData()
    }
}
</script>
<style>
    .ql-editor {
    min-height:400px; height:auto!important; height:400px;
    }
 .el-row {
    margin-bottom: 20px;
  }
  .el-col {
    border-radius: 4px;
  }
  .bg-purple-dark {
    background: #99a9bf;
  }
  .bg-purple {
    background: #d3dce6;
  }
  .bg-purple-light {
    background: #e5e9f2;
  }
  .grid-content {
    border-radius: 4px;
    min-height: 36px;
  }
  .row-bg {
    padding: 10px 0;
    background-color: #f9fafc;
  }
</style>