ss
jiangping
2025-06-19 6f2abed09bb02b22f73477642c21a333fe741207
admin/src/components/common/RichEditor.vue
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,322 @@
<template>
  <div :style="styleEditor">
    <Toolbar style="border-bottom: 1px solid #ccc" :editor="editor" :defaultConfig="toolbarConfig" :mode="mode" />
    <Editor :style="style" class="declass"  v-model="html" :defaultConfig="editorConfig" :mode="mode"
            @onCreated="onCreated" @onChange="onChange" />
  </div>
</template>
<style src="@wangeditor/editor/dist/css/style.css"></style>
<script>
import Vue from 'vue'
import { Editor, Toolbar } from '@wangeditor/editor-for-vue'
import { Loading } from 'element-ui'
let loadingInstance = null
export default Vue.extend({
  props: {
    richData: { // çˆ¶ç»„件传递的数据
      type: String,
      default: ''
    },
    styleEditor: '',
    style:'',
    readonly: false // æ˜¯å¦å¯ä»¥è¾“å…¥
  },
  name: 'RichEditor',
  components: { Editor, Toolbar },
  data () {
    return {
      editor: null,
      html: '',
      toolbarConfig: { // å·¥å…·æ é…ç½®
        toolbarKeys: this.readonly ? ['fullScreen'] : [ // æ˜¾ç¤ºæŒ‡å®šçš„菜单项
          'bold', // ç²—体
          'underline', // ä¸‹åˆ’线
          'italic', // æ–œä½“
          'through', // åˆ é™¤çº¿
          'code', // è¡Œå†…代码
          'sub', // ä¸‹æ ‡
          'sup', // ä¸Šæ ‡
          'clearStyle', // æ¸…除格式
          'color', // å­—体颜色
          'bgColor', // èƒŒæ™¯è‰²
          'fontSize', // å­—号
          'fontFamily', // å­—体
          'indent', // å¢žåŠ ç¼©è¿›
          'delIndent', // å‡å°‘缩进
          'justifyLeft', // å·¦å¯¹é½
          'justifyRight', // å³å¯¹é½
          'justifyCenter', // å±…中对齐
          'justifyJustify', // ä¸¤ç«¯å¯¹é½
          'lineHeight', // è¡Œé«˜
          // "viewImageLink", // æŸ¥çœ‹é“¾æŽ¥
          'divider', // åˆ†å‰²çº¿
          'emotion', // è¡¨æƒ…
          'insertLink', // æ’入链接
          // "editLink", // ä¿®æ”¹é“¾æŽ¥
          // "unLink", // å–消链接
          // "viewLink", // æŸ¥çœ‹é“¾æŽ¥
          'codeBlock', // ä»£ç å—
          'blockquote', // å¼•用
          'headerSelect', // æ ‡é¢˜
          // "header1", // æ ‡é¢˜1
          // "header2", // æ ‡é¢˜2
          // "header3", // æ ‡é¢˜3
          // "header4", // æ ‡é¢˜4
          // "header5", // æ ‡é¢˜5
          // "todo", // å¾…办
          'redo', // é‡åš
          'undo', // æ’¤é”€
          // "enter", // å›žè½¦
          // "bulletedList", // æ— åºåˆ—表
          // "numberedList", // æœ‰åºåˆ—表
          // "codeSelectLang" // é€‰æ‹©è¯­è¨€
          // è¡¨æ ¼åŠŸèƒ½åˆ†ç»„
          /* {
             key: 'table-style', // å¿…填,要以 group å¼€å¤´
             title: '表格', // å¿…å¡«
             // iconSvg: '<svg>....</svg>', // å¯é€‰
             menuKeys: [
               "insertTable", // æ’入表格
               "deleteTable", // åˆ é™¤è¡¨æ ¼
               "insertTableRow", // æ’入行
               "deleteTableRow", // åˆ é™¤è¡Œ
               "insertTableCol", // æ’入列
               "deleteTableCol", // åˆ é™¤åˆ—
               "tableHeader", // è¡¨å¤´
               "tableFullWidth", // å®½åº¦è‡ªé€‚应
             ] // ä¸‹çº§èœå• key ï¼Œå¿…å¡«
           }, */
          // ä¸Šä¼ å›¾ç‰‡åˆ†ç»„
          {
            key: 'img-style', // å¿…填,要以 group å¼€å¤´
            title: '图片', // å¿…å¡«
            // iconSvg: '<svg>....</svg>', // å¯é€‰
            menuKeys: [
              'uploadImage', // ä¸Šä¼ å›¾ç‰‡
              'insertImage', // ç½‘络图片
              'deleteImage', // åˆ é™¤å›¾ç‰‡
              'editImage', // ç¼–辑图片
              'imageWidth30', // å›¾ç‰‡å®½åº¦ç›¸å¯¹äºŽç¼–辑器宽度的百分比30
              'imageWidth50', // å›¾ç‰‡å®½åº¦ç›¸å¯¹äºŽç¼–辑器宽度的百分比50
              'imageWidth100' // å›¾ç‰‡å®½åº¦ç›¸å¯¹äºŽç¼–辑器宽度的百分比100
            ] // ä¸‹çº§èœå• key ï¼Œå¿…å¡«
          },
          // è§†é¢‘分组
          {
            key: 'video-style', // å¿…填,要以 group å¼€å¤´
            title: '视频', // å¿…å¡«
            // iconSvg: '<svg>....</svg>', // å¯é€‰
            menuKeys: [
              'insertVideo', // æ’入网络视频
              'uploadVideo', // ä¸Šä¼ è§†é¢‘
              'editVideoSize' // ä¿®æ”¹è§†é¢‘尺寸
            ] // ä¸‹çº§èœå• key ï¼Œå¿…å¡«
          },
          'fullScreen' // å…¨å±
        ],
        excludeKeys: [ // éšè—æŒ‡å®šçš„菜单项
          // 'headerSelect',
          // 'video-style'
          // æŽ’除菜单组,写菜单组 key çš„值即可
        ]
      },
      editorConfig: { // ç¼–辑器配置
        placeholder: '请输入内容...',
        readOnly: this.readonly, // æ˜¯å¦åªè¯»ï¼Œé»˜è®¤false
        autoFocus: false, // æ˜¯å¦è‡ªåЍfocus,默认为true
        scroll: true, // é…ç½®ç¼–辑器是否支持滚动,默认为 true ã€‚注意,此时不要固定 editor-container çš„高度,设置一个 min-height å³å¯ã€‚
        maxLength: 20000, // æœ€å¤§é™åˆ¶ï¼Œé¿å…å†…容过多卡顿
        MENU_CONF: {
          // å›¾ç‰‡ä¸Šä¼ 
          uploadImage: {
            server: process.env.VUE_APP_API_PREFIX + '/web/public/uploadLocal?folder=',
            fieldName: 'file',
            // å•个文件的最大体积限制,默认为 2M
            maxFileSize: 20 * 1024 * 1024, // 10M
            // æœ€å¤šå¯ä¸Šä¼ å‡ ä¸ªæ–‡ä»¶ï¼Œé»˜è®¤ä¸º 100
            maxNumberOfFiles: 10,
            // é€‰æ‹©æ–‡ä»¶æ—¶çš„类型限制,默认为 ['image/*'] ã€‚如不想限制,则设置为 []
            allowedFileTypes: ['image/*'],
            // è‡ªå®šä¹‰ä¸Šä¼ å‚数,例如传递验证的 token ç­‰ã€‚参数会被添加到 formData ä¸­ï¼Œä¸€èµ·ä¸Šä¼ åˆ°æœåŠ¡ç«¯ã€‚
            meta: {
            },
            // å°† meta æ‹¼æŽ¥åˆ° url å‚数中,默认 false
            metaWithUrl: false,
            // è‡ªå®šä¹‰å¢žåŠ  http  header
            // headers: { Authorization: "Bearer " + getToken() },
            // è·¨åŸŸæ˜¯å¦ä¼ é€’ cookie ï¼Œé»˜è®¤ä¸º false
            withCredentials: true,
            // è¶…时时间,默认为 10 ç§’
            timeout: 10 * 1000, // 10 ç§’
            // ä¸Šä¼ å‰
            onBeforeUpload (files) {
              loadingInstance = Loading.service({
                lock: true,
                text: '上传中...',
                spinner: 'el-icon-loading',
                background: 'rgba(0, 0, 0, 0.7)'
              })
              return files
            },
            // è‡ªå®šä¹‰æ’入图片
            customInsert (res, insertFn) {
              console.log(res)
              // å› ä¸ºè‡ªå®šä¹‰æ’入导致onSuccess与onFailed回调函数不起作用,自己手动处理
              // å…ˆå…³é—­ç­‰å¾…çš„Message
              loadingInstance = Loading.service({
                lock: true,
                text: '上传中...',
                spinner: 'el-icon-loading',
                background: 'rgba(0, 0, 0, 0.7)'
              }).close()
              if (res.code === 200) {
                // Message.success({
                //     message: `${res.data.originalName} ä¸Šä¼ æˆåŠŸ`
                // });
              } else {
                // Message.error({
                //     message: `${res.data.originalName} ä¸Šä¼ å¤±è´¥ï¼Œè¯·é‡æ–°å°è¯•`
                // });
              }
              insertFn(res.data.url, res.data.originname, res.data.imgname)
            },
            // å•个文件上传成功之后
            onSuccess (file, res) {
              console.log(`${file.originalFilename} ä¸Šä¼ æˆåŠŸ`, res)
            },
            // å•个文件上传失败
            onFailed (file, res) {
              console.log(`${file.originalFilename} ä¸Šä¼ å¤±è´¥`, res)
              loadingInstance.close()
            },
            // ä¸Šä¼ è¿›åº¦çš„回调函数
            onProgress (progress) {
              console.log('progress', progress)
              // progress æ˜¯ 0-100 çš„æ•°å­—
            },
            // ä¸Šä¼ é”™è¯¯ï¼Œæˆ–者触发 timeout è¶…æ—¶
            onError (file, err, res) {
              loadingInstance.close()
              console.log(`${file.originalFilename} ä¸Šä¼ å‡ºé”™`, err, res)
            }
          },
          // è§†é¢‘上传
          uploadVideo: {
            fieldName: 'file',
            server: process.env.VUE_APP_API_PREFIX + '/public/upload?folder=richeditor',
            // å•个文件的最大体积限制,默认为 10M
            maxFileSize: 50 * 1024 * 1024, // 50M
            // æœ€å¤šå¯ä¸Šä¼ å‡ ä¸ªæ–‡ä»¶ï¼Œé»˜è®¤ä¸º 5
            maxNumberOfFiles: 3,
            // é€‰æ‹©æ–‡ä»¶æ—¶çš„类型限制,默认为 ['video/*'] ã€‚如不想限制,则设置为 []
            allowedFileTypes: ['video/*'],
            // è‡ªå®šä¹‰ä¸Šä¼ å‚数,例如传递验证的 token ç­‰ã€‚参数会被添加到 formData ä¸­ï¼Œä¸€èµ·ä¸Šä¼ åˆ°æœåŠ¡ç«¯ã€‚
            meta: {
              // token: 'xxx',
              // otherKey: 'yyy'
            },
            // å°† meta æ‹¼æŽ¥åˆ° url å‚数中,默认 false
            metaWithUrl: false,
            // è‡ªå®šä¹‰å¢žåŠ  http  header
            headers: {
              // Authorization: "Bearer " + getToken()
              // otherKey: 'xxx'
            },
            // è·¨åŸŸæ˜¯å¦ä¼ é€’ cookie ï¼Œé»˜è®¤ä¸º false
            withCredentials: true,
            // è¶…时时间,默认为 30 ç§’
            timeout: 1000 * 1000, // 1000 ç§’,
            // ä¸Šä¼ ä¹‹å‰è§¦å‘
            onBeforeUpload (file) {
              return file
            },
            // è‡ªå®šä¹‰æ’入视频
            customInsert (res, insertFn) {
              // å› ä¸ºè‡ªå®šä¹‰æ’入导致onSuccess与onFailed回调函数不起作用,自己手动处理
              // å…ˆå…³é—­ç­‰å¾…çš„Message
              // Message.closeAll();
              if (res.code === 200) {
                // Message.success({
                //     message: `${res.data.originalName} ä¸Šä¼ æˆåŠŸ`
                // });
              } else {
                // Message.error({
                //     message: `${res.data.originalName} ä¸Šä¼ å¤±è´¥ï¼Œè¯·é‡æ–°å°è¯•`
                // });
              }
              insertFn(res.data.url, res.data.url)
            },
            // ä¸Šä¼ è¿›åº¦çš„回调函数
            onProgress (progress) {
              console.log(progress)
              // onProgress(progress) {       // JS è¯­æ³•
              // progress æ˜¯ 0-100 çš„æ•°å­—
            },
            // // å•个文件上传成功之后
            // onSuccess(file, res) {
            //   console.log(`${file.name} ä¸Šä¼ æˆåŠŸ`, res);
            //   this.successMsg(file);
            // },
            // // å•个文件上传失败
            // onFailed(file, res) {
            //   console.log(`${file.name} ä¸Šä¼ å¤±è´¥`, res);
            //   this.errorMsg(file);
            // },
            // ä¸Šä¼ é”™è¯¯ï¼Œæˆ–者触发 timeout è¶…æ—¶
            onError (file, err, res) {
              console.log(`${file.name} ä¸Šä¼ å‡ºé”™`, err, res)
              // Notification.error({
              //     title: '错误',
              //     message: `${file.name} ä¸Šä¼ å¤±è´¥ï¼Œè¯·é‡æ–°å°è¯•`
              // });
            }
          }
        }
      },
      mode: 'default' // or 'simple'
    }
  },
  watch: {
    richData: function (value) {
      this.html = value
    },
    readonly: function (value) {
      this.readonly = value
    },
    styleEditor: function (value) {
      this.styleEditor = value
    }
  },
  mounted () {
    // éœ€è¦åœ¨ç¼–辑器创建完毕后在赋值
    this.$nextTick(() => {
      this.html = this.richData
    })
  },
  methods: {
    // ç¼–辑器创建完毕时的回调函数
    onCreated (editor) {
      this.editor = Object.seal(editor) // ä¸€å®šè¦ç”¨ Object.seal() ï¼Œå¦åˆ™ä¼šæŠ¥é”™
    },
    // ç¼–辑器内容、选区变化时的回调函数
    onChange (editor) {
      this.$emit('getWangedditor', editor.getHtml())
      console.log('onChange', editor.getHtml()) // onChange æ—¶èŽ·å–ç¼–è¾‘å™¨æœ€æ–°å†…å®¹
    }
  },
  beforeDestroy () {
    // ç¼–辑器销毁时的回调函数。调用 editor.destroy() å³å¯é”€æ¯ç¼–辑器
    const editor = this.editor
    if (editor == null) return
    editor.destroy() // ç»„件销毁时,及时销毁编辑器
  }
})
</script>
<style lang="scss">
.declass{
  min-height: 80px; overflow-y: hidden;
}
</style>>