TinyEditor 篇3:拖拽图片到编辑器并同步上传至服务器

上一篇,我们实现了剪贴板粘贴图片并上传至服务器的功能,在本篇,我们实现从外部拖拽图片到编辑器中,也可以将图片上传到服务器。

功能实现:

新增一个 handleDrop 方法,在这个方法中处理拖拽及上传方法的触发。

需要注意的是,我们需要 阻止浏览器默认拖拽行为(否则可能打开图片):

javascript 复制代码
editorRef.value.addEventListener('dragover', (e) => {
    e.preventDefault()
})

下面直接来看完整代码:

javascript 复制代码
<template>
  <div class="editor-container">
    <div ref="editorRef" class="editor-inside"></div>
  </div>
</template>

<script setup>
import { onMounted, ref } from 'vue'
import '@opentiny/fluent-editor/style.css'
import FluentEditor from '@opentiny/fluent-editor'
import axios from 'axios'
import { uploadFile } from '@/model/editor.js'

const editorRef = ref(null)
let editorInstance = null

const TOOLBAR_CONFIG = [
  [{ header: [] }],
  ['bold', 'italic', 'underline', 'link'],
  [{ list: 'ordered' }, { list: 'bullet' }],
  ['clean'],
  ['image']
]

const selectImage = () => {
  const input = document.createElement('input')
  input.type = 'file'
  input.accept = 'image/*'
  input.click()

  input.onchange = async () => {
    const file = input.files[0]
    if (!file) return

    try {
      const url = await uploadImage(file)

      const range = editorInstance.getSelection()
      editorInstance.insertEmbed(range.index, 'image', url)

    } catch (err) {
      console.error('上传失败', err)
    }
  }
}

const uploadImage = async (file) => {
  const ext = file.name.split('.').pop()

  const res = await uploadFile({
    ext
  })

  const resData = res.data.data
  const uploadUrl = resData.upload_url
  const fileUrl = resData.public_url

  await axios.put(uploadUrl, file, {
    headers: {
      'Content-Type': file.type
    }
  })

  return fileUrl
}

const handlePaste = async (e) => {
  const items = e.clipboardData?.items
  if (!items) return

  for (let i = 0; i < items.length; i++) {
    const item = items[i]

    if (item.type.indexOf('image') !== -1) {
      e.preventDefault()
      e.stopPropagation()

      const file = item.getAsFile()

      try {
        const url = await uploadImage(file)

        const range = editorInstance.getSelection(true)
        editorInstance.insertEmbed(range.index, 'image', url)
      } catch (err) {
        console.error('粘贴上传失败', err)
      }

      break
    }
  }
}

const handleDrop = async (e) => {
  const files = e.dataTransfer?.files
  if (!files || files.length === 0) return
  
  const imageFiles = Array.from(files).filter(f => f.type.startsWith('image/'))
  if (imageFiles.length === 0) return

  e.preventDefault()
  e.stopPropagation()

  let index = 0
  try {
    const range = editorInstance.getSelection(true)
    index = range ? range.index : 0
  } catch {
    index = 0
  }

  for (const file of imageFiles) {
    try {
      const url = await uploadImage(file)
      editorInstance.insertEmbed(index, 'image', url)
      index += 1
    } catch (err) {
      console.error('拖拽上传失败', err)
    }
  }
}

onMounted(() => {
  if (!editorRef.value) return

  editorInstance = new FluentEditor(editorRef.value, {
    theme: 'snow',
    modules: {
      toolbar: {
        container: TOOLBAR_CONFIG,
        handlers: {
          image: selectImage
        }
      }
    }
  })

  editorRef.value.addEventListener('paste', handlePaste, true)

  editorRef.value.addEventListener('drop', handleDrop, true)
  
  editorRef.value.addEventListener('dragover', (e) => {
    e.preventDefault()
  })
})
</script>

<style scoped>
.editor-container {
  width: 50%;
  display: flex;
  justify-content: center;
  padding-top: 20px;
}

.editor-inside {
  height: 350px;
}

.editor-inside :deep(.ql-container) {
  height: 350px;
}

.editor-inside :deep(.ql-editor) {
  height: 310px;
}
</style>

关于编辑器中直接上传图片的处理场景已经介绍的差不多了,下一篇我们来看看假如我从外部(网页、Word 文档)粘贴了一篇文章,里面包含图片、文本 等复杂场景,要如何处理呢?

相关推荐
神の愛6 分钟前
VSCode报错了??
ide·vscode·编辑器
猩猩—点灯1 小时前
部署远程利器-RustDesk
运维·服务器·网络
biubiubiu07061 小时前
Linux 中 `source` 和 `systemctl daemon-reload` 的区别与踩坑点
linux·运维·服务器
ringking1231 小时前
Linux 主机通过 Wi-Fi 上网,并将网络通过网口共享给交换机下游设备
linux·服务器·网络
不愿透露姓名的大鹏1 小时前
华为存储新增LUN存储到VMware集群
运维·服务器·华为·vmware·存储
Tattoo_Welkin2 小时前
Docker 入门
运维·docker·容器
xingyuzhisuan2 小时前
4090部署DeepSeek-V3:CPU卸载层数实测指南
运维·深度学习·gpu算力
一目Leizi2 小时前
Burp Suite实战:利用不同响应进行用户名枚举与密码爆破
运维·服务器·安全
从零点2 小时前
第三节linux,编译linux源码
linux·运维·服务器
左手厨刀右手茼蒿3 小时前
Flutter 三方库 firebase_admin 跨云边管线企业级鸿蒙管控底座适配风云:无障碍贯穿服务器授权防火墙打通底层生态授权域并构建海量设备推送集结-适配鸿蒙 HarmonyOS ohos
服务器·flutter·harmonyos