WangEditor5 自定义按钮 图片处理并转为Base64 的实现

1,605 阅读3分钟

需求

默认上传图片功能需要通过服务器,我的需求是获取图片并转换为base64展示到富文本里,此文的由来就是如此。

实现

index.ts 转base64功能实现

/**
 * 将文件转换为经过压缩和等比裁切的 base64 图片
 * @param file 待转换的图片文件
 * @returns 经过压缩和等比裁切后的 base64 图片
 */
export const innerImg = async (file: Blob) => {
  if (!validateFile(file)) return // 检查文件是否符合要求
  let image = await transformImage(file) // 获取文件对应的图片
  image = compressImage(image) // 进行等比裁切操作
  image = base64Image(image) // 将裁切后的图片转换为 base64 格式
  return image
}
/**
 * 验证文件是否合法,文件大小不超过10MB,扩展名为.jpg、.jpeg或.png
 * @param file 待验证的文件
 * @returns 如果文件合法返回true,否则返回false
 */
export const validateFile = (file: any) => {
  const sizeLimit = 10 * 1024 * 1024 // 设置文件大小上限为10MB
  const legalExts = [".jpg", ".jpeg", ".png"] // 合法文件扩展名数组
  if (file.size > sizeLimit) {
    // 如果文件大小超过上限,提示错误并返回false
    ElMessage.error("文件过大")
    return false
  }
  const name = file.name.toLowerCase() // 获取文件名并转换为小写
  if (!legalExts.some((ext) => name.endsWith(ext))) {
    // 如果文件扩展名不合法,提示错误并返回false
    ElMessage.error("文件类型错误")
    return false
  }
  return true // 文件合法,返回true
}

/**
 * 转换图片文件为图片对象并获取图片宽度和高度
 * @param file 待转换的图片文件
 * @returns 包含图片对象、宽度和高度的Promise对象
 */
export const transformImage = (file: Blob) => {
  return new Promise(function (resolve) {
    const oFReader = new FileReader() // 创建FileReader对象
    oFReader.readAsDataURL(file) // 以Data URL形式读取文件
    oFReader.onloadend = function (oFRevent: any) {
      const src = oFRevent.target.result // 获取文件内容
      const image = new Image() // 创建Image对象
      image.src = src // 设置Image对象的src属性
      image.onload = function () {
        // 图片加载完成后
        resolve({
          // 返回包含图片对象、宽度和高度的对象
          width: image.width,
          height: image.height,
          image
        })
      }
    }
  })
}

/**
 * 压缩图片比例并返回压缩后的宽高和图片对象
 * @param file 包含图片宽度、高度和图片对象的对象
 * @returns 压缩后的宽高和图片对象
 */
export const compressImage = (file: { width: any; height: any; image: any } | any) => {
  let compressFlag = false // 图片是否需要压缩的标志
  let sizeRatio = 0 // 压缩宽高比例
  let maxWidth = 750 // 图片最大宽度
  let maxHeight = 600 // 图片最大高度
  const { width, height, image } = file

  // 如果图片高度大于最大高度且宽度大于最大宽度
  if (height > maxHeight && width > maxWidth) {
    compressFlag = true
    sizeRatio = height / maxHeight // 计算宽高比例
    maxWidth = width / sizeRatio // 根据比例重新计算宽度
  } else if (width > maxWidth) {
    // 如果图片宽度大于最大宽度
    compressFlag = true
    sizeRatio = width / maxWidth // 计算宽高比例
    maxHeight = height / sizeRatio // 根据比例重新计算高度
  } else if (height > maxHeight) {
    // 如果图片高度大于最大高度
    compressFlag = true
    sizeRatio = height / maxHeight // 计算宽高比例
    maxWidth = width / sizeRatio // 根据比例重新计算宽度
  }
  // 如果不需要压缩
  if (!compressFlag) {
    maxWidth = width
    maxHeight = height
  }
  return {
    width: maxWidth,
    height: maxHeight,
    image
  }
}


/**
 * 将图片对象转换为 base64 编码的字符串
 * @param file 包含图片宽度、高度和图片对象的对象
 * @returns base64 编码的图片字符串、宽度和高度
 */
export const base64Image = (file: { width: number; height: number; image: any } | any) => {
  const { width, height, image } = file
  const COMPRESS_RATIO = 1 // 获取压缩比例(0-1)
  const canvas = document.createElement("canvas") // 创建 canvas 元素
  const ctx = canvas.getContext("2d") // 获取绘制上下文
  canvas.setAttribute("id", "canvas")
  canvas.width = width
  canvas.height = height
  ctx?.clearRect(0, 0, width, height) // 清除画布内所有像素
  ctx?.drawImage(image, 0, 0, width, height) // 绘制图片到画布上
  const compressedImage = canvas.toDataURL("image/jpeg", COMPRESS_RATIO) // 将画布内容转换为 base64 编码的字符串

  return {
    src: compressedImage,
    width,
    height
  }
}

App.vue 注册按钮 在这里注册不会在切换页面时报错

import { Boot } from "@wangeditor/editor"
import type { IDomEditor } from "@wangeditor/editor"
import { getCurrentInstance } from "vue"

const { proxy }: any = getCurrentInstance()
class NewInnerimgBtn extends InnerimgBtn { // 这里集成了默认的类
  exec(editor: IDomEditor, value: any) {
    if (this.isDisabled(editor)) return
    proxy.$bus.emit("innerImgClick")
  }
}
const inImgBtn = {
  key: "inImgBtn",
  factory() {
    return new NewInnerimgBtn()
  }
}
Boot.registerMenu(inImgBtn)

使用

test.ts

import { getCurrentInstance } from "vue"
const { proxy }: any = getCurrentInstance()
// 全局事件
proxy.$bus.on("innerImgClick", (row: any) => {
  innerImgClick()
})
// 上传图片操作
const fileRef = ref() // 创建上传文件的引用
const innerImgClick = async () => {
  // 点击上传图片按钮
  fileRef.value.click()
  // 上传图片回调
  fileRef.value.onchange = async function () {
    if (this.files.length === 0) return // 没有选择文件
    const file = this.files[0] // 获取文件
    let image: any = await innerImg(file) // 将文件转换为经过压缩和等比裁切的 base64 图片
    editorRef.value.dangerouslyInsertHtml(
      // 将图片插入到编辑器中
      `<p style="text-align: center;">
        <img style="width: ${image.width}px;height:${image.height}px" src="${image.src}"></img>
      </p>`
    )
  }
}