wangEditor 图文混合粘贴 word

现状描述: 目前直接复制一篇图文混合的word内容,只能粘贴进来文本, 图片需要一张一张的复制,工作效率很低。

需求:  1. 支持word文本的图文混合粘贴

           2.支持直接复制微信公众号内容和图片混合粘贴(经调研由于微信图片的防盗链问题以及当前业务场景不做实现, 防盗链问题可参考另外一篇如何解决”此图片来自微信公众平台,未经允许不可引用“问题?

1. 实现思路:

    1.1 手动通过工具栏上传的图片配置 uploadImage 自定义上传图片实现

    1.2 图文混合粘贴的图片, 通过自定义粘贴事件来实现

  •  获取所有粘贴的html
  • 拿到所有的rtf数据
  • 从html从匹配出所有的imag标签, 并从rtf中找到对应的图片数据
  • 请求上传图片的接口,拿到服务器端的地址
  • 返回图片地址给编辑器

具体代码实现如下

<!--
 * @Description: 模块名称
 * @Author: ym
 * @Date: 2023-04-25 20:55:24
 * @LastEditTime: 2023-12-25 17:26:05
-->
<template>
  <div style="border: 1px solid #ccc">
    <Toolbar
      style="border-bottom: 1px solid #ccc"
      :editor="editorRef"
      :defaultConfig="toolbarConfig"
      mode="default"
    />
    <Editor
      style="height: 500px; overflow-y: hidden;"
      v-model="valueHtml"
      mode="default"
      :defaultConfig="editorConfig"
      :uploadImgServer="'/announce/file/new/upload'"
      @onCreated="handleCreated"
      @customPaste="onCustomPaste"
    />
  </div>
</template>
<script setup>
import '@wangeditor/editor/dist/css/style.css' // 引入 css
import { useFormItem } from 'element-plus'
import { uniqueId } from 'lodash-es'
import {map as BMap} from 'bluebird'

import {
  uploadFile,
} from '@/assets/api/index'

import { nextTick, onBeforeUnmount, computed, shallowRef, watch} from 'vue'
import { Editor, Toolbar } from '@wangeditor/editor-for-vue'

const props = defineProps({
  modelValue: {type: String},
  disabled: {type: Boolean},
})
const editorRef = shallowRef()
const emits = defineEmits(['update:modelValue'])
const { formItem } = useFormItem()
const valueHtml = computed({
  get: () => props.modelValue,
  set: val => {
    formItem?.validate && formItem?.validate('blur')
    emits('update:modelValue', val)
  }
})
const token = sessionStorage.getItem('authorization')
const toolbarConfig = {excludeKeys:['insertImage', 'group-video', 'codeBlock']}
const editorConfig = {
  placeholder: '复制文字至编辑器后,请调整字体为宋体,字号为normal,小标题等可适当加粗', readOnly: props.disabled, MENU_CONF: {
    uploadImage: {
      showLinkImg: false,
      base64LimitSize: 5 * 1024,
      allowedFileTypes: ['image/*'],
      uploadFileName: 'file',
      uploadImgMaxLength: 20,
      uploadImgMaxSize: 200 * 1024 * 1024,
      customUpload (file, insertFn) {
        console.log('*****图片上传事件', file);
        let formData = new FormData()
        formData.append('file', file)
        uploadFile(formData).then(res => {
          console.log(res)
          insertFn('https://yto-announce.oss-cn-shanghai.aliyuncs.com/' + res.filePath, file.name, '')
        })
      }
     
    }
  }
}
const findAllImgSrcsFromHtml = (htmlData) => {
  const imgReg = /<img.*?(?:>|\/>)/gi; //匹配图片中的img标签
  const srcReg = /src=[\'\"]?([^\'\"]*)[\'\"]?/i; // 匹配图片中的src

  const arr = htmlData.match(imgReg); //筛选出所有的img
  if (!arr || (Array.isArray(arr) && !arr.length)) {
    return false;
  }
  const srcArr = arr.map(e => e.match(srcReg)).filter(el => el)
  return srcArr
}
const extractImageDataFromRtf = (rtfData) => {
    const regexPictureHeader = /{\\pict[\s\S]+?({\\\*\\blipuid\s?[\da-fA-F]+)[\s}]*/
    const regexPicture = new RegExp('(?:(' + regexPictureHeader.source + '))([\\da-fA-F\\s]+)\\}', 'g');
    const images = rtfData.match(regexPicture);
    const result = [];
    if (images) {
        for (const image of images) {
            let imageType = false;

            if (image.includes('\\pngblip')) {
                imageType = 'image/png';
            } else if (image.includes('\\jpegblip')) {
                imageType = 'image/jpeg';
            }

            if (imageType) {
                result.push({
                    hex: image.replace(regexPictureHeader, '').replace(/[^\da-fA-F]/g, ''),
                    type: imageType
                });
            }
        }
    }
    return result;
}

const hexToFile = (obj) => {
  console.log('****obj', obj);
    // 将十六进制字符串转成字节数组
    const bytes = obj.hex.match(/.{1,2}/g).map(byte => parseInt(byte, 16))
    
    // 从字节数组创建一个Blob对象
    const blob = new Blob([new Uint8Array(bytes)], { type: obj.type })
    
    // 从Blob创建一个File对象
    const file = new File([blob], uniqueId('imgage'), { type: obj.type })
    let formData = new FormData()
    formData.append('file', file)
    return formData
}

const onCustomPaste = async (editor, event) => {
  let html = event.clipboardData.getData('text/html') // 获取粘贴的 html
  const rtf = event.clipboardData.getData('text/rtf') // 获取 rtf 数据(如从 word wsp 复制粘贴)
  //  word wsp 复制粘贴
  if (html && rtf) {
    event.preventDefault()
    const imgSrcs = findAllImgSrcsFromHtml(html) // 从html中查找所有的图片
    if (imgSrcs && imgSrcs.length) {
      const rtfImageData = extractImageDataFromRtf(rtf) // 从rtf 中查找图片数据
      await BMap(rtfImageData, async (e, i) => {
        try {
          const formData = hexToFile(e)
          const res = await uploadFile(formData)
          const imgUrl = 'https://yto-announce.oss-cn-shanghai.aliyuncs.com/' + res.filePath
          html = html.replace( imgSrcs[i][1], imgUrl)
        } catch (error) {
          console.error('图片上传出错了,手动上传!', error);
        }
      })
      editor.dangerouslyInsertHtml(html)
    } else {
      return true
    }
  } else {
    return true
  }
}
// 组件销毁时,也及时销毁编辑器
onBeforeUnmount(() => {
    const editor = editorRef.value
    if (editor == null) return
    editor.destroy()
})

const handleCreated = (editor) => {
  editorRef.value = editor // 记录 editor 实例,重要!
}

watch(() => props.disabled, (val) => {
  console.log('****',val)
  nextTick(() => {
    if (val && editorRef.value) {
      editorRef.value.disable()
    }
  })
}, {immediate: true})
</script>  
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值