el-form与el-upload结合上传带附件的表单数据(前端篇)

1.写在之前

本文前端采用Vue + element-plus技术栈,前端项目参考yudao-ui-admin-vue3项目与
Geeker-Admin项目。

这篇文章是el-form与el-upload结合上传带附件的表单数据(后端篇)-CSDN博客姐妹篇,后端篇文章主要讲的是后端的实现逻辑,前端篇稍微简单一些,其实最主要的就是封装el-upload组件,供具体的表单组件调用。

2.封装el-upload组件

废话不多说,直接上代码。

<template>
  <div class="upload-file">
    <el-upload
      :multiple="props.limit > 1"
      name="file"
      v-model:file-list="_fileList"
      :show-file-list="true"
      :auto-upload="autoUpload"
      :action="updateUrl"
      :headers="uploadHeaders"
      :limit="props.limit"
      :drag="drag"
      :before-upload="beforeUpload"
      :on-exceed="handleExceed"
      :on-success="handleFileSuccess"
      :on-error="excelUploadError"
      :on-remove="handleRemove"
      :on-preview="handlePreview"
      :accept="fileType.join(',')"
      :data="{ bucket: props.bucket }"
      :disabled="disabled"
      class="upload-file-uploader"
    >
      <el-button v-if="!disabled" type="primary"
        ><Icon icon="ep:upload-filled" />选取文件</el-button
      >
    </el-upload>
  </div>
</template>
<script lang="ts" setup>
import { propTypes } from '@/utils/propTypes'
import { getAccessToken } from '@/utils/auth'
import type { UploadUserFile, UploadProps, UploadRawFile, UploadFile } from 'element-plus'
import { downloadFile } from '@/api/infra/file'
import download from '@/utils/download'

defineOptions({ name: 'UploadFile' })

const message = useMessage() // 消息弹窗
const emit = defineEmits(['update:fileList'])

const props = defineProps({
  fileList: propTypes.array.def([]),
  title: propTypes.string.def('文件上传'),
  updateUrl: propTypes.string.def(import.meta.env.VITE_UPLOAD_URL),
  fileType: propTypes.array.def([]), // 文件类型, 例如['png', 'jpg', 'jpeg']
  fileSize: propTypes.number.def(500), // 大小限制(MB)
  limit: propTypes.number.def(5), // 数量限制
  autoUpload: propTypes.bool.def(true), // 自动上传
  drag: propTypes.bool.def(false), // 拖拽上传
  isShowTip: propTypes.bool.def(true), // 是否显示提示
  bucket: propTypes.string.def('operation'), //默认存储到operation bucket中
  disabled: propTypes.bool.def(false)
})
// ========== 上传相关 ==========
const uploadList = ref<UploadUserFile[]>([])
const _fileList = ref<UploadUserFile[]>(props.fileList)
const uploadHeaders = ref({
  Authorization: 'Bearer ' + getAccessToken()
})

// 监听 props.fileList 列表默认值改变
watch(
  () => props.fileList,
  (n: UploadUserFile[]) => {
    _fileList.value = n
  }
)

// 文件上传之前判断
const beforeUpload: UploadProps['beforeUpload'] = (file: UploadRawFile) => {
  if (_fileList.value.length >= props.limit) {
    message.error(`上传文件数量不能超过${props.limit}个!`)
    return false
  }
  let fileExtension = ''
  if (file.name.lastIndexOf('.') > -1) {
    fileExtension = file.name.slice(file.name.lastIndexOf('.'))
  }
  const isImg = props.fileType.some((type: string) => {
    if (file.type.indexOf(type) > -1) return true
    return !!(fileExtension && fileExtension.indexOf(type) > -1)
  })
  const isLimit = file.size < props.fileSize * 1024 * 1024
  if (!isImg) {
    message.error(`文件格式不正确, 请上传${props.fileType.join('/')}格式!`)
    return false
  }
  if (!isLimit) {
    message.error(`上传文件大小不能超过${props.fileSize}MB!`)
    return false
  }
}
const handleFileSuccess: UploadProps['onSuccess'] = (res: any): void => {
  // 因为后端返回的res经过统一处理拦截 此处需要根据res返回的code判断是否真的上传成功
  let code: number = res.code
  let data = res.data
  if (code == 200) {
    let name = data.name //后端生成的文件唯一标识
    //前端存储中 如果有response相同的文件,说明有相同内容的文件 需要清楚所有 只保存一个 即本次上传的文件
    _fileList.value = _fileList.value.filter((item) => item.name !== name)
    uploadList.value.push({ name: data.name, url: data.url, response: data.response })
    _fileList.value = _fileList.value.concat(uploadList.value)
    _fileList.value.sort(sortFileList('name'))
    uploadList.value = []
    message.success('文件上传成功')
    emitUpdateModelValue()
  } else {
    message.error('文件上传失败')
  }
}
// 文件数超出提示
const handleExceed: UploadProps['onExceed'] = (): void => {
  message.error(`上传文件数量不能超过${props.limit}个!`)
}
// 上传错误提示
const excelUploadError: UploadProps['onError'] = (): void => {
  message.error('导入数据失败,请您重新上传!')
}
// 删除上传文件
const handleRemove = (file) => {
  const findex = _fileList.value.map((f) => f.name).indexOf(file.name)
  if (findex > -1) {
    _fileList.value.splice(findex, 1)
    emitUpdateModelValue()
  }
}
const handlePreview: UploadProps['onPreview'] = async (uploadFile: UploadFile) => {
  console.log(123)
  const res = await downloadFile(uploadFile.response)
  console.log(res)
  download.commonFile(res, uploadFile.name)
}

const emitUpdateModelValue = () => {
  emit('update:fileList', _fileList.value)
}

//数组排序 按照名字升序
const sortFileList = (name) => {
  const rev = 1
  return function (a, b) {
    a = a[name]
    b = b[name]
    if (a < b) {
      return rev * -1
    }
    if (a > b) {
      return rev
    }
    return 0
  }
}
</script>
<style scoped lang="scss">
.upload-file-uploader {
  margin-bottom: 5px;
}

:deep(.el-upload-list) {
  width: 400px;
}

:deep(.upload-file-list .el-upload-list__item) {
  position: relative;
  margin-bottom: 10px;
  line-height: 2;
  border: 1px solid #e4e7ed;
}

:deep(.el-upload-list__item-file-name) {
  max-width: 500px;
}

:deep(.upload-file-list .ele-upload-list__item-content) {
  display: flex;
  justify-content: space-between;
  align-items: center;
  color: inherit;
}

:deep(.ele-upload-list__item-content-action .el-link) {
  margin-right: 10px;
}
</style>

代码组件个人理解没有什么好讲的。开启自动上传,上传成功拿到后端的数据返回,构造数据,如果有名称相同的文件,全部删除,只使用最新的上传文件数据。上传成功后,更新表单绑定的文件数据。

3.关于文件的下载

这里想要说一下文件的下载,前期看了很多实现,有使用a标签用文件的URL实现下载的,有直接使用window.open(URL)实现的,我在实际下载中,遇到两个问题,第一是遇到浏览器能处理的文件,例如MP4的视频文件,pdf的文本文件,会直接打开,不会下载,第二个问题是下载的名称不能自己指定,按照网上查找的方法指定也不起作用,最后我选择的第二节中代码方法,先获取文件内容,后下载文件。

commonFile: (data: Blob, fileName: string) => {
    download0(data, fileName, 'application/octet-stream')
  }

const download0 = (data: Blob, fileName: string, mineType: string) => {
  // 创建 blob
  const blob = new Blob([data], { type: mineType })
  // 创建 href 超链接,点击进行下载
  window.URL = window.URL || window.webkitURL
  const href = URL.createObjectURL(blob)
  const downA = document.createElement('a')
  downA.href = href
  downA.download = fileName
  downA.click()
  // 销毁超连接
  window.URL.revokeObjectURL(href)
}

4.表单使用

先上一段代码,上传的文件类型为file-type规定的文件类型。

<tr>
          <td><span :class="{ required: required }">附件</span></td>
          <td colspan="3">
            <el-form-item prop="files">
              <UploadFile
                v-model:file-list="form.files"
                :file-type="['.jpg', '.png', '.docx', '.pdf', '.mp4']"
                :disabled="!required"
              />
            </el-form-item>
            <el-form-item>
              <span style="color: red; font-size: 12px">
                这是一些注意消息,比如上传的文件个数,上传的文件类型,上传的文件大小,上传的文件注意事项
              </span>
            </el-form-item>
          </td>
        </tr>

5.实际效果展示

6.写在最后

其实感觉前端只要有第二节封装组件的代码,代码一看就一目了然,就是在文件下载时候,多花了一点小心思。本篇文章只是简单笼统的介绍了一下前端实现传文件,具体的表单设计其实有很多立方需要讲,后期的话,如果有时间,会录一个实际效果展示视频,敬请期待。如果有不对的地方,还请看到本篇文章的您不吝赐教。

  • 7
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值