doum
2025-09-26 9057e04efad1b7d61c77a72e5c37a504d0aee935
admin/src/components/common/RichEditor.vue
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,335 @@
<template>
  <div :style="styleEditor">
    <Toolbar style="border-bottom: 1px solid #ccc" :editor="editor" :defaultConfig="toolbarConfig" :mode="mode" />
    <Editor style="min-height: 80px; overflow-y: hidden;" 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: '',
    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: 10 * 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 + '/web/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
      console.log("onChange123", value); // onChange æ—¶èŽ·å–ç¼–è¾‘å™¨æœ€æ–°å†…å®¹
      this.$nextTick(()=>{
        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) {
      var that =this
      this.editor = Object.seal(editor) // ä¸€å®šè¦ç”¨ Object.seal() ï¼Œå¦åˆ™ä¼šæŠ¥é”™
      setTimeout(function (){
        that.html = that.richData
        // alert(that.html)
      },15000)
    },
    // ç¼–辑器内容、选区变化时的回调函数
    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>
::v-deep .w-e-text-container {
  height: 420px !important;
}
.w-e-text-container .w-e-scroll {
  height: 500px !important;
  -webkit-overflow-scrolling: touch;
}
</style>