Vue3 + Element Plus实现用户粘贴图片上传功能的组件

前言

本文将介绍如何使用 Vue 3 和 Element Plus 创建一个支持图片粘贴上传的组件,并提供详细的代码实现和说明。

组件功能概述

我们的目标是创建一个图片上传组件,具备以下功能:

  1. 支持用户通过拖拽或点击按钮选择图片上传。
  2. 支持用户直接粘贴图片到组件中进行上传。
  3. 显示上传图片的预览,并允许用户删除已上传的图片。
  4. 提供对上传文件的类型和大小检查。

代码实现

以下是实现上述功能的 Vue 3 组件代码:

<template>
  <div @paste="handlePaste">
    <el-upload
      v-model:file-list="fileList"
      action=""
      list-type="picture-card"
      :on-preview="handlePictureCardPreview"
      :before-remove="handleBeforeRemove"
      :before-upload="handleBeforeUpload"
      :http-request="handleUpload"
    >
      <el-icon><Plus /></el-icon>
    </el-upload>
    <el-dialog v-model:visible="dialogVisible">
      <img w-full :src="dialogImageUrl" alt="Preview Image" />
    </el-dialog>
    <span>
      Please note that the size of each image should not exceed 3MB, and we support files in JPG and PNG formats.
    </span>
  </div>
</template>

<script setup lang="ts">
import { Plus } from '@element-plus/icons-vue';
import type { UploadRawFile, UploadRequestOptions } from 'element-plus';
import { ElLoading } from 'element-plus';
import adminApi from '@/api/adminApi';
import showMessage from '~/utils/showMessage';

const fileList = ref<UploadRawFile[]>([]);
const fullPathList = ref<string[]>([]); // Full paths
const pathList = ref<string[]>([]); // Paths without domain

const dialogImageUrl = ref('');
const dialogVisible = ref(false);

const handleUpload = async (options: UploadRequestOptions): Promise<void> => {
  const { file } = options;
  const formData = new FormData();
  formData.append('file', file);

  const uploadLoading = ElLoading.service({
    lock: true,
    text: 'uploading...',
    background: 'rgba(0, 0, 0, 0.18)',
  });

  try {
    const { data } = await adminApi.uploadFile(formData);
    if (data) {
      fullPathList.value.push(data.url);
      pathList.value.push(data.path);
      emit('update:file-list', pathList.value);
      emit('update:full-path-list', fullPathList.value);
    }
  } catch (error) {
    showMessage({ message: 'Upload failed', type: 'error' });
  } finally {
    uploadLoading.close();
  }
};

const handleBeforeUpload = (file: File): boolean => {
  const isLt3M = file.size / 1024 / 1024 < 3;
  const isAcceptableType = ['image/jpeg', 'image/png'].includes(file.type);
  if (!isLt3M) {
    showMessage({ message: 'The size of each image should not exceed 3MB', type: 'error' });
    return false;
  }
  if (!isAcceptableType) {
    showMessage({ message: 'Only PNG and JPG formats are supported', type: 'error' });
    return false;
  }
  return true;
};

const handleBeforeRemove = (file: UploadRawFile): boolean => {
  const index = fileList.value.findIndex((item) => item.uid === file.uid);
  if (index !== -1) {
    fileList.value.splice(index, 1);
    pathList.value.splice(index, 1);
    fullPathList.value.splice(index, 1);
    emit('update:file-list', pathList.value);
    emit('update:full-path-list', fullPathList.value);
  }
  return true;
};

const handlePictureCardPreview = (file: UploadRawFile): void => {
  dialogImageUrl.value = file.url!;
  dialogVisible.value = true;
};

const handlePaste = (event: ClipboardEvent) => {
  const items = event.clipboardData?.items;
  if (!items) return;

  for (let i = 0; i < items.length; i++) {
    const item = items[i];
    if (item.kind === 'file' && item.type.startsWith('image/')) {
      const file = item.getAsFile();
      if (file) {
        const uploadFile: UploadRawFile = {
          ...file,
          uid: Date.now(), // 使用当前时间戳生成唯一的 uid
          status: 'success',
          url: URL.createObjectURL(file),
        };

        fileList.value.push(uploadFile);

        const formData = new FormData();
        formData.append('file', file);

        const uploadOptions: UploadRequestOptions = {
          file,
          onProgress: () => {},
          onSuccess: () => {},
          onError: () => {},
        };

        handleUpload(uploadOptions);
      }
    }
  }
};

const emit = defineEmits(['update:file-list', 'update:full-path-list']);

const clearFiles = () => {
  fileList.value = [];
  pathList.value = [];
  fullPathList.value = [];
  emit('update:file-list', []);
  emit('update:full-path-list', []);
};

defineExpose({ clearFiles });
</script>

<style scoped>
span {
  line-height: 20px;
  margin-top: 12px;
}
</style>

 

代码说明

  1. 组件结构

    • el-upload 组件用于处理图片的上传,支持图片预览和删除。
    • el-dialog 组件用于展示图片的详细预览。
  2. handleUpload 方法

    • 处理上传文件的逻辑,使用 FormData 将文件提交到服务器,并处理加载状态。
  3. handleBeforeUpload 方法

    • 在上传之前检查文件的大小和类型,确保符合要求。
  4. handleBeforeRemove 方法

    • 处理文件删除时的逻辑,更新文件列表。
  5. handlePictureCardPreview 方法

    • 显示图片的详细预览。
  6. handlePaste 方法

    • 处理粘贴事件,从剪贴板中提取图片文件并触发上传。
  7. 样式

    • 简单的样式调整,使组件界面更加美观。

使用 

<template>
      <el-form
        ref="handleFormRef"
        :model="handleForm"
        :rules="validateRules"
        label-width="100px"
        label-position="right"
      >
        <!-- 表单内容 -->
        <el-form-item prop="ImgPathList" label="" required>
          <FileUpload v-model:file-list="handleForm.ImgPathList" v-model:full-path-list="handleForm.imgFullPathList" ref="fileUploadRef" />
        </el-form-item>
      </el-form>
</template>

<script setup lang="ts">
  import FileUpload from '@/components/FileUpload/index.vue';
</script>

总结

通过以上代码,我们实现了一个功能完整的图片上传组件,支持用户通过点击、拖拽和粘贴方式上传图片。该组件能够处理图片的上传、预览、删除及类型和大小检查,为用户提供了更好的体验。希望这个示例能帮助你在项目中实现类似功能。

如果你有任何问题或需要进一步的帮助,欢迎在评论区留言!

### Vue实现通过粘贴图片 URL 来上传图片的功能 要在 Vue实现通过粘贴图片 URL 的方式来上传图片,可以按照以下思路设计解决方案: #### 功能概述 当用户在输入框中粘贴一张图片的 URL 时,前端会自动抓取该图片并将其转换为文件对象。随后,可以通过 `FormData` 将其发送到后端接口完成上传操作。 --- #### 技术细节与实现步骤 1. **监听粘贴事件** 使用 VueElement UI 提供的组件属性绑定机制,在 `<el-input>` 上绑定 `@paste.native` 方法捕获用户粘贴行为[^2]。 2. **解析粘贴内容中的图片链接** 当检测到粘贴的内容是一个有效的图片 URL 时,提取该 URL 并验证其合法性。 3. **下载远程图片资源** 利用 JavaScript 的 `fetch` 或者 `axios` 请求库从服务器获取图片数据流,并将其转化为 Blob 对象。 4. **创建 File 对象** 将 Blob 数据封装成标准的 File 对象以便后续处理。 5. **构建 FormData 并提交至后端** 创建一个 `FormData` 实例并将 File 添加进去,最后通过 AJAX 调用 `/multipartUpload` 接口完成上传过程[^1]。 以下是完整的代码示例: ```javascript <template> <div> <!-- 输入区域 --> <el-input type="textarea" @paste.native="handlePaste" v-model="inputText" placeholder="请输入..."></el-input> <!-- 已上传图片展示区 --> <span v-for="(file, index) in uploadedFiles" :key="index" style="margin-left: 10px;"> <img :src="file.previewUrl" alt="Uploaded Image" style="width: 100px;" /> <i class="el-icon-close" @click="removeFile(index)"></i> </span> </div> </template> <script> export default { data() { return { inputText: "", // 用户输入的文字内容 uploadedFiles: [] // 存储已上传的文件信息 }; }, methods: { async handlePaste(event) { const clipboardData = event.clipboardData || window.clipboardData; let pastedContent = clipboardData.getData("text/plain"); try { if (this.isImageUrl(pastedContent)) { // 验证是否为合法图片URL const blob = await this.fetchImageAsBlob(pastedContent); // 下载图片作为Blob const file = new File([blob], "pasted-image.png", { type: "image/png" }); // 构建File对象 const formData = new FormData(); formData.append("file", file); // 发送请求给后端 const response = await fetch("/api/multipartUpload", { method: "POST", body: formData, }); if (!response.ok) throw new Error("Failed to upload image."); const result = await response.json(); // 更新状态显示成功上传的结果 this.uploadedFiles.push({ previewUrl: `${result.baseUrl}${result.fileName}`, fileName: result.fileName, }); } } catch (error) { console.error(error); } }, isImageUrl(urlString) { const imgPattern = /\.(jpeg|jpg|gif|png)$/i; // 正则匹配常见图片扩展名 return urlString && urlString.match(imgPattern); }, async fetchImageAsBlob(imageUrl) { const response = await fetch(imageUrl); if (!response.ok) throw new Error(`Could not load image at ${imageUrl}`); return await response.blob(); // 返回Blob形式的数据 }, removeFile(index) { this.uploadedFiles.splice(index, 1); // 移除指定索引处的文件记录 } } }; </script> ``` --- #### 关键点说明 - **判断粘贴内容是否为有效图片 URL** 可以利用正则表达式校验字符串是否符合常见的图片格式规则。 - **跨域问题** 如果目标图片托管于其他域名下,则需要注意浏览器的安全策略可能阻止直接访问这些外部资源。此时需联系对方服务提供 CORS 支持或者借助代理解决此限制。 - **错误处理** 整个流程涉及多个异步环节(比如网络请求),因此建议加入全面的异常捕捉逻辑以免程序崩溃影响用户体验。 ---
评论 12
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值