wangeditor富文本

623 阅读1分钟

先看下配置的效果图:

Snipaste_2023-06-19_15-34-38.png vue2组件 使用的版本是:

"@wangeditor/editor": "^5.1.23"

"@wangeditor/editor-for-vue": "^1.0.2"

<template>
  <div>
    <div style="border: 1px solid #dcdfe6; margin-top: 10px">
      <!-- 工具栏 -->
      <Toolbar style="border-bottom: 1px solid #dcdfe6"
        :editor="editor"
        :defaultConfig="toolbarConfig"
        :mode="mode" />
      <!-- 编辑器 -->
      <Editor style="height: 400px; overflow-y: hidden"
        :defaultConfig="editorConfig"
        v-model="valueHtml"
        @onChange="onChange"
        @onCreated="onCreated"
        :mode="mode" />
    </div>
  </div>
</template>

<script>
import { Editor, Toolbar } from '@wangeditor/editor-for-vue'
export default {
  name: 'MyEditor',
  components: { Editor, Toolbar },
  props: {
    content: {
      type: String,
      default: ''
    }
  },
  data() {
    const that = this
    return {
      editor: null,
      mode: 'default',
      valueHtml: '',
      toolbarConfig: {
        // toolbarKeys: [ /* 显示哪些菜单,如何排序、分组 */ ],
        // 隐藏菜单
        excludeKeys: [
          'blockquote',
          'group-video',
          'insertImage',
          //   'emotion',
          'codeBlock',
          'fullScreen',
          'fontFamily',
          //   'lineHeight',
          '|',
          'numberedList'
        ]
      },
      exitIds: [],
      latestError: '',
      editorConfig: {
        placeholder: '请输入内容...',
        // autoFocus: false,

        // 所有的菜单配置,都要在 MENU_CONF 属性下
        MENU_CONF: {
          uploadImage: {
            server: `${process.env.VUE_APP_SHOP_API}/shop/products/uploadShopImg`,
            fieldName: 'file',
            headers: {
              Authorization: this.$store.getters.token
            },
            // maxFileSize: 1 * 1024 * 1024,
            maxNumberOfFiles: 20,
            allowedFileTypes: ['image/png,image/jpeg,image/jpg'],
            withCredentials: true,
            timeout: 5 * 1000,
            onBeforeUpload(file) {
              const fileList = Object.values(file)
              const typeList = ['image/jpg', 'image/jpeg', 'image/png']
              for (const i of fileList) {
                // 不符合上传条件,未抛出过异常,继续抛异常信息 return false
                if (!typeList.includes(i.type) && !that.exitIds.includes(i.id)) {
                  that.exitIds.push(i.id)
                  that.$message.error('图片只能是 JPG、JPEG、PNG 格式!')
                  return false
                } else if (i.size / 1024 / 1024 > 1 && !that.exitIds.includes(i.id)) {
                  that.exitIds.push(i.id)
                  that.$message.error('图片大小不能超过1M! 请重新上传!')
                  return false
                }
              }
              for (const j in file) {
                const obj = {}
                if (typeList.includes(file[j].type) && file[j].size / 1024 / 1024 <= 1) {
                  obj[j] = file[j]
                  return obj
                }
              }
            },
            onProgress() {
            // console.log('progress', progress)
            },
            onSuccess(file, res) {
            //   console.log(`${file.name} 上传成功`, res)
            },
            // 自定义插入
            customInsert(res, insertFn) {
              that.exitIds = []
              insertFn(res.data, '')
            },
            onError(file, res) {
              const fileType = file.type
              const fileSize = file.size
              const typeList = ['image/jpg', 'image/jpeg', 'image/png']
              if (!typeList.includes(fileType)) {
                return that.$message.error('图片只能是 JPG、JPEG、PNG 格式!')
              } else if (fileSize / 1024 / 1024 > 1) {
                return that.$message.error('图片大小不能超过1M! 请重新上传')
              }
            }
          }
        }
      }
    }
  },
  watch: {
    content: {
      handler(newVal, oldVal) {
        this.valueHtml = newVal
      },
      immediate: true
    },
    valueHtml: {
      handler(newVal, oldVal) {
        if (newVal) {
          this.$emit('update-modelValue', newVal)
        }
      },
      deep: true
    }
  },
  methods: {
    onCreated(editor) {
      this.editor = Object.seal(editor) // 【注意】一定要用 Object.seal() 否则会报错
    },
    // onChange 时获取编辑器最新内容
    onChange(editor) {
      this.valueHtml = editor.getHtml()
    }
  },
  mounted() {
    // 模拟 ajax 请求,异步渲染编辑器
    setTimeout(() => {
      this.valueHtml = this._props.content
      this.editor.blur()
    }, 200)
  },
  beforeDestroy() {
    const editor = this.editor
    if (editor === null) return
    editor.destroy() // 组件销毁时,及时销毁 editor ,重要!!!
  }
}
</script>

<style src="@wangeditor/editor/dist/css/style.css"></style>

父组件中的使用

 <textEditor :content="postForm.text" @update-modelValue="changeContent"></textEditor>

vue3组件

<template>
  <div style="border: 1px solid #ccc">
    <Toolbar
      id="editor"
      style="border-bottom: 1px solid #ccc"
      :editor="editorRef"
      :defaultConfig="toolbarConfig"
      :mode="mode"
    />
    <Editor
      style="height: 500px; overflow-y: hidden"
      v-model="valueHtml"
      :defaultConfig="editorConfig"
      :mode="mode"
      @onCreated="handleCreated"
      @on-change="handleChange"
    />
  </div>
</template>
<script lang="ts">
import '@wangeditor/editor/dist/css/style.css' // 引入 css
import {
  defineComponent,
  onBeforeUnmount,
  shallowRef,
  onMounted,
  ref,
  reactive,
  toRefs,
  watch,
} from 'vue'
import { Editor, Toolbar } from '@wangeditor/editor-for-vue'
import message from '@/utils/message'
import useStore from '@/store'

export default defineComponent({
  emits: ['update-modelValue'],
  components: { Editor, Toolbar },
  props: {
    modelValue: {
      type: String,
      default: '',
    },
    disabled: {
      type: Boolean,
      default: false,
    },
  },
  setup(props, context) {
    const editorRef = shallowRef()
    const valueHtml = ref(props.modelValue)
    watch(
      () => valueHtml.value,
      newValue => {
        context.emit('update-modelValue', newValue)
      }
    )
    watch(
      () => props.modelValue,
      newValue => {
        valueHtml.value = newValue
      }
    )

    watch(
      () => props.disabled,
      newValue => {
        if (newValue) {
          editorRef.value.disable()
        } else {
          editorRef.value.enable()
        }
      }
    )
    onMounted(() => {})
    const token: any = sessionStorage.getItem('token')
    //工具栏设置
    const toolbarConfig = {
      excludeKeys: [
        'group-video',
        'insertImage',
        'insertLink',
        'emotion',
        'codeBlock',
        'fullScreen',
      ],
    }

    //编辑器设置
    const editorConfig = {
      placeholder: '请输入内容...',
      MENU_CONF: {
        uploadImage: {
          server: `${
            import.meta.env.VITE_APP_BASE_API
          }/file/fileManager/getImportFileUrl`,
          fieldName: 'file',
          headers: {
            Authorization: `${JSON.parse(token)}`,
          },
          maxFileSize: 5 * 1024 * 1024,
          maxNumberOfFiles: 20,
          allowedFileTypes: ['image/png,image/jpeg,image/jpg'],
          withCredentials: true,
          timeout: 5 * 1000,
          onBeforeUpload(file: object) {
            let fileObj = Object.values(file)[0].data
            const isJPG =
              fileObj.type == 'image/jpg' ||
              fileObj.type == 'image/jpeg' ||
              fileObj.type == 'image/png'
            if (!isJPG) {
              message.error('图片只能是 JPG、GIF、PNG 格式!')
            }

            // 判断图片大小
            let isLt = fileObj.size / 1024 / 1024 < 5 // 判断是否小于5M
            if (!isLt) {
              message.error('图片大小不能超过5M! 请重新上传')
            }
            if (!isJPG) {
              return false
            } else if (!isLt) {
              return false
            } else {
              return file
            }
          },
          onProgress(progress: any) {
            //console.log('progress', progress)
          },
          onSuccess(file: any, res: any) {
            //console.log(`${file.name} 上传成功`, res)
          },
          customInsert(res: any, insertFn: any) {
            insertFn(res.message, '123', res.message)
          },
          onError(file: any, err: any, res: any) {
            //console.log(`${file.name} 上传出错`, err, res)
          },
        },
        uploadVideo: {
          server: `${
            import.meta.env.VITE_APP_BASE_API
          }/file/fileManager/importFile`,
          fieldName: 'file',
          maxFileSize: 5 * 1024 * 1024, // 5M
          maxNumberOfFiles: 3,
          allowedFileTypes: ['video/mp4'],
          metaWithUrl: false,
          withCredentials: true,
          timeout: 15 * 1000,
          customInsert(res: any, insertFn: any) {
            insertFn(res.data.url)
          },
          onBeforeUpload(file: any) {
            return file
          },
          onProgress(progress: any) {
            //console.log('progress', progress)
          },
          onSuccess(file: any, res: any) {
            //console.log(`${file.name} 上传成功`, res)
          },
          onFailed(file: any, res: any) {
            //console.log(`${file.name} 上传失败`, res)
          },
          onError(file: any, err: any, res: any) {
            //console.log(`${file.name} 上传出错`, err, res)
          },
        },
      },
    }
    // 组件销毁时,也及时销毁编辑器
    onBeforeUnmount(() => {
      console.log(onBeforeUnmount, 'onBeforeUnmount')
      const editor = editorRef.value
      if (editor == null) return
      editor.destroy()
    })

    const handleCreated = (editor: any) => {
        editorRef.value = editor
        if (props.disabled) {
          editor.disable()
        } else {
          editor.enable()
        }
    }

    const handleChange = (editor: any) => {
        //  
    }
    return {
      editorRef,
      valueHtml,
      mode: 'default',
      toolbarConfig,
      editorConfig,
      handleCreated,
      handleChange,
    }
  },
})
</script>