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 文档)粘贴了一篇文章,里面包含图片、文本 等复杂场景,要如何处理呢?

相关推荐
陈陈CHENCHEN14 分钟前
【Linux】服务器根目录磁盘扩容操作记录
linux·运维·服务器
见闻小天地2 小时前
从晶片工艺到可靠性验证:晶威特TF-3215型RTC晶振全面解析
运维·业界资讯
沸速存储2 小时前
AI 机器人靠哪些内存与存储驱动整机运行?
服务器·科技·嵌入式硬件·电脑
名字还没想好☜3 小时前
Docker 网络实战:bridge/host/none/自定义网络、容器互通与端口映射踩坑
运维·docker·kubernetes
fengyehongWorld3 小时前
Linux squid搭建基础代理服务器
linux·运维·服务器
木白CPP3 小时前
Linux DMA驱动详解(二)-----DMA的使用者
java·linux·运维
赵民勇3 小时前
systemd-socket-activate命令详解
linux·运维
跨境小彭4 小时前
Temu运营避坑:制造地点信息填写规范、后果及批量实操教程
大数据·运维·自动化·跨境电商·temu
Cx330❀4 小时前
【Linux网络】网络层 IP 协议:从网段划分到内核源码解析
大数据·linux·服务器·网络·tcp/ip·性能优化·langchain
Dachui_11225 小时前
ZeroNews LFS 使用教程:NAS 文件发布为公网 HTTPS 文件空间,不传网盘、按人授权、免登录分享
运维·网络安全·文件共享·团队协作·nas