为了账号安全,请及时绑定邮箱和手机立即绑定

【九月打卡】第8天 完成上传组件-支持拖拽上传和代码重构

标签:
Vue.js

课程名称web前端架构师

课程章节:第11周 第五章

主讲老师:张轩

课程内容:完成上传组件-支持拖拽上传 代码重构

拖拽上传

分析

拖拽区域触发事件:

  • 拖拽元素进入区域时触发dragenter;
  • 拖拽元素进入区域后悬停触发dragover;
  • 拖拽元素离开区域时触发dragleave;
  • 拖拽元素放置时触发 drop, 此时我们可以发送 上传文件请求

在拖拽元素进入区域悬停时,我们可以给拖拽区域元素一个 class is-draging ,当离开拖拽区域时,就移除 is-draging 这个class。

下面编写触发拖拽事件的代码

// 拖拽元素进入区域或离开区域时触发。
// over为true 为进入区域悬停,false 时为离开拖拽区域
function handleDrag (e: DragEvent, over:boolean) {
  e.preventDefault()
  e.stopPropagation()
  dragOver.value = over
}

// 拽元素放置时触发
function handleDrop (e: DragEvent) {
  e.preventDefault()
  e.stopPropagation()
  // is
  if (e.dataTransfer) {
    // 发送请求 
    uploadFile(e.dataTransfer.files)
  }
  dragOver.value = false
}

编写代码实现

首先我们在使用组件时,需要用户传递一个参数 drag ,是否需要拖拽上传

interface Props{
  beforeUpload?: (file: File) => boolean | Promise<boolean>
  drap?: boolean
  name?: string
}

事件处理, 如果需要 拖拽上传,添加拖拽事件

let events: {[key: string]:(e: any) => void} = {
  click: triggerUpload
}
if (props.isDrag) {
  events = {
    ...events,
    dragover: (e: DragEvent) => { handleDrag(e, true) },
    dragleave: (e: DragEvent) => { handleDrag(e, false) },
    drop: handleDrop
  }
}

给元素绑定事件和class, 事件绑定通过 v-on 绑定多个事件

<div
  class="choose-flle"
  :class="{ 'is-draging': drag && dragOver }"
  v-on="events"
>
...
</div>

编写测试代码

it('test drag upload', async () => {
  const wrapper = mount(UploadFile, {
    props: {
      drag: true
    }
  })
  const uploadFileEl = wrapper.get('.choose-file')
  await uploadFileEl.trigger('dragover')
  expect(uploadFileEl.classes()).toContain('is-draging')
  await uploadFileEl.trigger('dragleave')
  expect(uploadFileEl.classes()).not.toContain('is-draging')
  await uploadFileEl.trigger('drop', {
    dataTransfer: {
      files: [testFile]
    }
  })
  request.mockResolvedValueOnce({ status: 'success' })
  expect(request).toHaveBeenCalled()
  await flushPromises()
  expect(wrapper.findAll('li').length).toBe(1)
})

这里需要注意过是触发 drop 事件. https://test-utils.vuejs.org/api/#trigger

trigger 方法第一个参数是事件名称,第二个参数需要传递的参数

查看下我们需要传递的参数格式

function handleDrop (e: DragEvent) {
  e.preventDefault()
  e.stopPropagation()
  // is
  if (e.dataTransfer) {
    uploadFile(e.dataTransfer.files)
  }
  dragOver.value = false
}

知道需要传递的参数格式就可以触发 drop 事件了

const testFile = new File(['foo'], 'foo.txt', {
  type: 'text/plain'
})
await uploadFileEl.trigger('drop', {
  dataTransfer: {
    files: [testFile]
  }
})

代码重构

有时候我们不需要选择完图片后自动上传,这个就需要将我们之前上传文件给提出来

之前上传文件的代码

async function uploadFile (files: FileList) {
  const uploadFile = files[0]
  const formData = new FormData()
  formData.append(props.name, uploadFile)
  const fileObj = reactive<FileItem>({
    id: '' + Date.now(),
    name: uploadFile.name,
    size: uploadFile.size,
    status: 'loading',
    raw: uploadFile
  })
  try {
    fileObj.status = 'loading'
    loadStatus.value = 'loading'
    if (props.beforeUpload) {
      const res = await props.beforeUpload(uploadFile)
      if (!res) {
        return
      }
    }
    uploadFiles.push(fileObj)
    const res = await request<{code: number}>(formData)
    fileObj.status = 'success'
    loadStatus.value = 'success'
    if (res.code === 0) {
      console.log('上传成功')
    }
  } catch (e) {
    fileObj.status = 'fail'
    loadStatus.value = 'fail'
  } finally {
    (fileRef.value as HTMLInputElement).value = ''
  }
}

上传文件的步骤

  • 准备
    • 执行 beforeUpload 钩子,是否需要往下执行
  • 将文件添加到 fileList 中
  • 判断是否需要自动上传
    • 需要就执行文件上传
    • 不需要什么也不用做
  • 给外部暴露一个手动上传文件的方法
<script setup lang="ts">
import { reactive, ref, computed } from 'vue'
import request from '@/utils/request'
type UploadStatus = 'ready' | 'loading' | 'success' | 'fail'
interface FileItem {
  id: string
  name: string
  size: number
  status:UploadStatus
  raw: File
  res?: any
}
interface Props{
  beforeUpload?: (file: File) => boolean | Promise<boolean>
  drag?: boolean
  name?: string
  autoUpload?: boolean
}
const fileRef = ref<HTMLInputElement | null>(null)
const dragOver = ref(false)
const props = withDefaults(defineProps<Props>(), {
  drag: false,
  beforeUpload: () => true,
  name: 'file',
  autoUpload: false
})
// const uploadStatus = ref<UploadStatus>('ready')
const uploadFileList = reactive<FileItem[]>([])
const isUploading = computed(() => uploadFileList.some(file => file.status === 'loading'))
const loadStatus = ref<UploadStatus>('ready')

async function handleChangeFile (e: Event) {
  const target = e.target as HTMLInputElement
  const files = target.files
  if (files) {
    uploadReady(files[0])
  }
}
function addFileToList (file: File) {
  if (!file) return
  const fileObj = reactive<FileItem>({
    id: '' + Date.now(),
    name: file.name,
    size: file.size,
    status: 'ready',
    raw: file
  })
  uploadFileList.push(fileObj)
  if (props.autoUpload) {
    uploadFile(file, fileObj)
  }
}
async function uploadReady (file: File) {
  if (props.beforeUpload) {
    const res = await props.beforeUpload(file)
    if (!res) {
      return false
    }
  }
  addFileToList(file)
}
// 给外部暴露一个手动上传文件的方法
function uploadFiles () {
  uploadFileList.filter(file => file.status !== 'success').forEach(file => uploadFile(file.raw, file))
}
defineExpose({
  uploadFiles
})
async function uploadFile (file: File, fileObj: FileItem) {
  const formData = new FormData()
  formData.append(props.name, file)
  try {
    loadStatus.value = fileObj.status = 'loading'
    const res = await request<{code: number}>(formData)
    fileObj.status = loadStatus.value = 'success'
    if (res.code === 0) {
      console.log('上传成功')
    }
  } catch (e) {
    fileObj.status = loadStatus.value = 'fail'
  } finally {
    (fileRef.value as HTMLInputElement).value = ''
  }
}

function handleDrag (e: DragEvent, over:boolean) {
  e.preventDefault()
  e.stopPropagation()
  dragOver.value = over
}
function handleDrop (e: DragEvent) {
  e.preventDefault()
  e.stopPropagation()
  if (e.dataTransfer) {
    uploadReady(e.dataTransfer.files[0])
  }
  dragOver.value = false
}
let events: {[key: string]:(e: any) => void} = {
  click: triggerUpload
}
if (props.drag) {
  events = {
    ...events,
    dragover: (e: DragEvent) => { handleDrag(e, true) },
    dragleave: (e: DragEvent) => { handleDrag(e, false) },
    drop: handleDrop
  }
}
function triggerUpload () {
  fileRef.value?.click()
}
function delFile (index: number) {
  uploadFileList.splice(index, 1)
}
</script>

<template>
  <div
    class="choose-file"
    :class="{ 'is-draging': drag && dragOver }"
    v-on="events"
  >
    <input
      type="file"
      name="file"
      ref="fileRef"
      :style="{display: 'none'}"
      @change="handleChangeFile"
    >
    <div>
      <slot
        v-if="isUploading"
        name="loading"
      >
        <button>正在上传</button>
      </slot>
      <slot
        v-else-if="loadStatus==='success'"
        name="uploaded"
        :uploaded-data="{status: loadStatus}"
      >
        <button>点击上传</button>
      </slot>
      <slot v-else>
        <button>点击上传</button>
      </slot>
    </div>
  </div>
  <ul>
    <li
      v-for="(file,index) in uploadFileList"
      :key="file.id"
      :class="'upload-'+ file.status"
    >
      <span class="filename">{{ file.name }}</span>
      <button
        class="delete-icon"
        @click="delFile(index)"
      >
        del
      </button>
    </li>
  </ul>
</template>

图片描述

点击查看更多内容
TA 点赞

若觉得本文不错,就分享一下吧!

评论

作者其他优质文章

正在加载中
Python工程师
手记
粉丝
2
获赞与收藏
1

关注作者,订阅最新文章

阅读免费教程

  • 推荐
  • 评论
  • 收藏
  • 共同学习,写下你的评论
感谢您的支持,我会继续努力的~
扫码打赏,你说多少就多少
赞赏金额会直接到老师账户
支付方式
打开微信扫一扫,即可进行扫码打赏哦
今天注册有机会得

100积分直接送

付费专栏免费学

大额优惠券免费领

立即参与 放弃机会
意见反馈 帮助中心 APP下载
官方微信

举报

0/150
提交
取消