vue3同个页面,多次加载wangeditor富文本编辑器实例

一、需求背景

因为业务需要,在同一个页面中,需要加载多个富文本编辑器。如下图所示。

                                        图1

而且是富文本编辑器是动态生成,即点击按钮就生成一个新的编辑器,如下图2所示

开始尝试了tinymce富文本,出现的问题是,当加载多次后,后面的富文本编辑器就失效了,变成了普通的文本输入框。后来还是用wangEditor,可以根据需要多次加载,也正常使用。

二、使用wangEditor富文本编辑器(以下简称wangEditor编辑器)

因为同个页面需要多次加载,并多个页面重复使用,所以把这个富文本编辑器进行了封装。封装的过程参考一下链接。需要的自己去看。

vue3项目使用tinymce_vue3 使用tinymce-CSDN博客

我的封装代码如下所示,部分改动,为了简化界面。关键代码如下:

/**
  initValue: 父组件传递过来的富文本框初始值,这里会实时监听,更新初始值,放置在弹窗中使用,没有钩子函数的更新。
  getEditorContent() 方法,父组件通过这个方法获取富文本编辑器的内容,包括数组格式的和html格式的内容
*/
<template>
  <!-- <div v-html="valueHtml"></div> -->
  <div style="border: 1px solid #ccc">
    <Toolbar
      style="border-bottom: 1px solid #ccc"
      :editor="editorRef"
      :defaultConfig="toolbarConfig"
      :mode="mode"
    />
    <Editor
      style="min-height: 300px;min-width: 600px; overflow-y: hidden; text-align: left;"
      v-model="valueHtml"
      :defaultConfig="editorConfig"
      :mode="mode"
      @onCreated="handleCreated"
      @onChange="handleChange"
    />
  </div>
</template>
<script setup>
import '@wangeditor/editor/dist/css/style.css' // 引入 css
import { onBeforeUnmount, ref, shallowRef, onMounted, nextTick, watch } from 'vue'
import { Editor, Toolbar } from '@wangeditor/editor-for-vue'
import axios from 'axios'
import { localGet, uploadImgServer } from '@/utils'
// 初始值
const props = defineProps({
  index:Number,
  initValue: String,
  father_method:Function,
})
const emits = defineEmits(['getEditorContent'])
// const emits = defineEmits([''])
let mode = ref('default')
// 编辑器实例,必须用 shallowRef
const editorRef = shallowRef()
// 内容 HTML
const valueHtml = ref('')
// 模拟 ajax 异步获取内容
onMounted(() => {
  nextTick(() => { // 界面节点更新完以后再修改值。
    valueHtml.value = props.initValue
  })
})
// 工具栏配置
const toolbarConfig = {
  toolbarKeys: [
    // 菜单 key
    // 'headerSelect',
    // 'bold', // 加粗
    // 'italic', // 斜体
    // 'through', // 删除线
    // 'underline', // 下划线
    // 'bulletedList', // 无序列表
    // 'numberedList', // 有序列表
    // 'color', // 文字颜色
    // 'insertLink', // 插入链接
    // 'fontSize', // 字体大小
    // 'lineHeight', // 行高
    'uploadImage', // 上传图片
    // 'delIndent', // 缩进
    // 'indent', // 增进
    // 'deleteImage',//删除图片
    // 'divider', // 分割线
    // 'insertTable', // 插入表格
    // 'justifyCenter', // 居中对齐
    // 'justifyJustify', // 两端对齐
    // 'justifyLeft', // 左对齐
    // 'justifyRight', // 右对齐
    'undo', // 撤销
    'redo', // 重做
    // 'clearStyle', // 清除格式
    'fullScreen' // 全屏
  ]
}
const editorConfig = {
  placeholder: '请输入内容...', // 配置默认提示
  // readOnly: true,
  MENU_CONF: {                // 配置上传服务器地址
    uploadImage: {
      // 小于该值就插入 base64 格式(而不上传),默认为 0
      base64LimitSize: 5 * 1024, // 5kb
      // 单个文件的最大体积限制,默认为 2M
      // maxFileSize: 1 * 1024 * 1024, // 1M
      // // 最多可上传几个文件,默认为 100
      // maxNumberOfFiles: 5,
      // 选择文件时的类型限制,默认为 ['image/*'] 。如不想限制,则设置为 []
      allowedFileTypes: ['image/*'],
      // 自定义上传
      async customUpload(file, insertFn) { // 文件上传
        const formData = new FormData();
        formData.set('images', file)
        // 这里根据自己项目封装情况,设置上传地址
        axios.defaults.headers['token'] = localGet('token') || ''
        // axios.defaults.headers.common['Authorization'] = localGet('token')
        axios.defaults.headers.post['Content-Type'] = 'multipart/form-data'
        let result = await axios.post(uploadImgServer, formData)
        console.log('图上传结果',result)
        // 插入图片后,不插入到富文本编辑器中,而是还给父组件,通过father_method方法
        if(props.father_method){
          props.father_method(result.data.results)
        }
        
         // 插入到富文本编辑器中,主意这里的三个参数都是必填的,要不然控制台报错:typeError: Cannot read properties of undefined (reading 'replace')
        // insertFn(result.data.data.url, result.data.data.name, result.data.data.name)
      }
    }
  }
}
// 组件销毁时,也及时销毁编辑器
onBeforeUnmount(() => {
    const editor = editorRef.value
    if (editor == null) return
    editor.destroy()
})
const handleCreated = (editor) => {
  // editorRef.value = editor // 创建富文本编辑器
}
const handleChange = (info) => {
  // info.children 数组包含了数据类型和内容,valueHtml.value内容的html格式
  console.log("变化后",info.children)
  if(info.children[0].type=='paragraph'){
    emits('getEditorContent', info.children[0].text, valueHtml.value)
  }
  
}
watch(()=>props.initValue, (value) => {  // 父组件获取初始值
  // valueHtml.value = value
})
</script>

其中最后的代码是初始化编辑器的内容。我是把它注释掉了因为这也是本文的关键处,如果不注释掉,多个编辑器之间的输入,会互相干扰。

我也不知道为什么,如果有大神知道,跪求留言。如果是页面加载单个编辑器实例,这行代码不需要注释。

附上我的父组件关键代码:

  <p>【题干】</p>
<WangEditor ref="refContentGenerate(index)"  v-model="item.content_front"  @getEditorContent="onEditorChange" :father_method="appendImageToContentFromEditor" /> 
<el-image v-for="url in item.images" :key="url" :src="url" style="width: 150px;" />  
      
<p>【答案】</p>
<WangEditor2 ref="refAnswerGenerate(index)" v-model="item.answer_front" @getEditorContent="onEditorAnswerChange" :father_method="appendImageToAnswerFromEditor" />
 <el-image v-for="url in item.images_answer" :key="url" :src="url" style="width: 150px;" />  
        
<p>【解析】</p>
<WangEditor3 ref="refExplainGenerate(index)" v-model="item.explain_front"  @getEditorContent="onEditorExplainChange" :father_method="appendImageToExplainFromEditor" />
        
<el-image v-for="url in item.images_explain" :key="url" :src="url" style="width: 150px;" />
中间代码省略

// 当富文本编辑器的题干内容发生变化
  const onEditorChange = (array, html) => {
    console.log("选中的数组项",state.currentSelectQuestionIndex)
    console.log("html:",html)
    state.paperQuestionList[state.currentSelectQuestionIndex].content_front=html
    console.log('修改内容后数组',state.paperQuestionList)
  }
  // 当富文本编辑器的题干内容发生变化
  const onEditorAnswerChange = (array, html) => {
    state.paperQuestionList[state.currentSelectQuestionIndex].answer_front=html
  }
    // 当富文本编辑器的题干内容发生变化
  const onEditorExplainChange = (array, html) => {
    state.paperQuestionList[state.currentSelectQuestionIndex].explain_front=html
    console.log('修改解析后数组',state.paperQuestionList)
  }

【解释】:我的业务需求是,不在编辑器内展示图片,而是另外单独显示,并把图片保存到数组的images、images_answer和images_explain中。

三、根据业务需求修改封装代码

我的业务需求是去掉上传图片后把图片插入编辑器的功能。所以相应的封装代码也有修改如下:

async customUpload(file, insertFn) { // 文件上传
        const formData = new FormData();
        formData.set('images', file)
        // 这里根据自己项目封装情况,设置上传地址
        axios.defaults.headers['token'] = localGet('token') || ''
        // axios.defaults.headers.common['Authorization'] = localGet('token')
        axios.defaults.headers.post['Content-Type'] = 'multipart/form-data'
        let result = await axios.post(uploadImgServer, formData)
        console.log('图上传结果',result)
        // 插入图片后,不插入到富文本编辑器中,而是还给父组件,通过father_method方法
        if(props.father_method){
          props.father_method(result.data.results)
        }
        
         // 插入到富文本编辑器中,主意这里的三个参数都是必填的,要不然控制台报错:typeError: Cannot read properties of undefined (reading 'replace')
        // insertFn(result.data.data.url, result.data.data.name, result.data.data.name)
      }

我把代码:

insertFn(result.data.data.url, result.data.data.name, result.data.data.name)

注释掉了。这行代码是把后台收到的图片url插入到编辑器。跟我的业务需求相反。

而是改成了 

props.father_method(result.data.results)。

即当编辑器上传图片后,后台返回的图片url反馈给父组件的father_method执行,让父组件保存图片url

  • 3
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值