vue3移动端可同时上传照片和视频的组件

uni-app中的uni-file-picker可单独上传照片或视频,但不支持同时上传照片和视频。本篇博客使用image标签和video标签实现移动端(H5+app+小程序)中照片和视频的同时上传。

本篇博客采用的是照片和视频的单独上传,但可同时展示,支持照片和视频的删除,其中照片和视频均是调用的后端的接口,上传至服务器中,大家可根据个人需要灵活存储照片和视频。

实现效果:

实现代码 :

将照片和视频区分上传,并单独处理,照片采用image标签,视频采用video标签。

复制代码
<template>
  <!-- 上传视频或者图片 -->
  <view class="up-page">
    <!--图片-->
    <view class="show-box" v-for="(item, index) in imageList" :key="index">
      <image
        class="full"
        :src="item"
        :data-src="image"
        @tap="previewImage(item)"
      >
      </image>
      <view class="delect-icon" @tap="delect(index)">
        <image class="full" :src="clearIcon" mode=""></image>
      </view>
    </view>
    <!--视频-->
    <view class="show-box" v-for="(item1, index1) in videoList" :key="index1">
      <video class="full" :src="item1"></video>
      <view class="delect-icon" @tap="delectVideo(index1)">
        <image class="full" :src="clearIcon" mode=""></image>
      </view>
    </view>
    <view v-if="VideoOfImagesShow" @tap="chooseVideoImage" class="box-mode">
      <image class="full" :src="selectfile" mode=""></image>
    </view>
  </view>
</template>

对照片和视频分别进行上传、删除等操作,避免index值出现错乱,出现错删和误删。

配置项可进行父组件的传值灵活设置。

此处处理的是将后端上传后的照片或视频id传递出去,此处可根据个人需求灵活设置。但需注意的是,imageList和videoList不要轻易改变,此处是专门使用了对应的id数组进行了重新定义。

复制代码
<script setup>
import { uploadImg, uploadVideo } from '@/api/upload'

const emit = defineEmits(['getImgUploadIds', 'getVideoUploadIds'])

var sourceType = [['camera'], ['album'], ['camera', 'album']]

const clearIcon = ref(
  'data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMjAiIGhlaWdodD0iMjAiIHZpZXdCb3g9IjAgMCAyMCAyMCIgZmlsbD0ibm9uZSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj48cGF0aCBkPSJNMCAwaDE2YTQgNCAwIDAgMSA0IDR2MTZINGE0IDQgMCAwIDEtNC00VjB6IiBmaWxsPSJ1cmwoI3BhaW50MF9saW5lYXIpIiBmaWxsLW9wYWNpdHk9Ii45OCIgZmlsdGVyPSJ1cmwoI2ZpbHRlcjBfYikiLz48cGF0aCBkPSJNMTAuOTQgOS45OTlsMi44NjMtMi44NTdhLjY2OS42NjkgMCAxIDAtLjk0Ni0uOTQ2TDEwIDkuMDYgNy4xNDMgNi4xOTZhLjY2OS42NjkgMCAwIDAtLjk0Ni45NDZsMi44NjQgMi44NTctMi44NjQgMi44NTdhLjY2Ni42NjYgMCAwIDAgLjIxNyAxLjA5Mi42NjQuNjY0IDAgMCAwIC43MjktLjE0NkwxMCAxMC45MzhsMi44NTcgMi44NjRhLjY2Ny42NjcgMCAwIDAgMS4wOTItLjIxNy42NjYuNjY2IDAgMCAwLS4xNDYtLjcyOUwxMC45MzkgMTB6IiBmaWxsPSIjZmZmIi8+PGRlZnM+PGZpbHRlciBpZD0iZmlsdGVyMF9iIiB4PSItNCIgeT0iLTQiIHdpZHRoPSIyOCIgaGVpZ2h0PSIyOCIgZmlsdGVyVW5pdHM9InVzZXJTcGFjZU9uVXNlIiBjb2xvci1pbnRlcnBvbGF0aW9uLWZpbHRlcnM9InNSR0IiPjxmZUZsb29kIGZsb29kLW9wYWNpdHk9IjAiIHJlc3VsdD0iQmFja2dyb3VuZEltYWdlRml4Ii8+PGZlR2F1c3NpYW5CbHVyIGluPSJCYWNrZ3JvdW5kSW1hZ2UiIHN0ZERldmlhdGlvbj0iMiIvPjxmZUNvbXBvc2l0ZSBpbjI9IlNvdXJjZUFscGhhIiBvcGVyYXRvcj0iaW4iIHJlc3VsdD0iZWZmZWN0MV9iYWNrZ3JvdW5kQmx1ciIvPjxmZUJsZW5kIGluPSJTb3VyY2VHcmFwaGljIiBpbjI9ImVmZmVjdDFfYmFja2dyb3VuZEJsdXIiIHJlc3VsdD0ic2hhcGUiLz48L2ZpbHRlcj48bGluZWFyR3JhZGllbnQgaWQ9InBhaW50MF9saW5lYXIiIHgxPSIyMCIgeDI9IjE1LjU4NiIgeTI9IjIyLjk0IiBncmFkaWVudFVuaXRzPSJ1c2VyU3BhY2VPblVzZSI+PHN0b3Agc3RvcC1jb2xvcj0iIzBEMUUyOCIgc3RvcC1vcGFjaXR5PSIuOCIvPjxzdG9wIG9mZnNldD0iMSIgc3RvcC1jb2xvcj0iIzA1MEUxMiIgc3RvcC1vcGFjaXR5PSIuNjUiLz48L2xpbmVhckdyYWRpZW50PjwvZGVmcz48L3N2Zz4='
)
const selectfile = ref(
  'data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iNjQiIGhlaWdodD0iNjQiIHZpZXdCb3g9IjAgMCA2NCA2NCIgZmlsbD0ibm9uZSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj48cmVjdCB4PSIuMjUiIHk9Ii4yNSIgd2lkdGg9IjYzLjUiIGhlaWdodD0iNjMuNSIgcng9IjMuNzUiIGZpbGw9IiNGMkYyRjIiIHN0cm9rZT0iI0YyRjJGMiIgc3Ryb2tlLXdpZHRoPSIuNSIvPjxyZWN0IHg9IjE2IiB5PSIzMSIgd2lkdGg9IjMyIiBoZWlnaHQ9IjIiIHJ4PSIxIiBmaWxsPSIjQkZCRkJGIi8+PHJlY3QgeD0iMzMiIHk9IjE2IiB3aWR0aD0iMzIiIGhlaWdodD0iMiIgcng9IjEiIHRyYW5zZm9ybT0icm90YXRlKDkwIDMzIDE2KSIgZmlsbD0iI0JGQkZCRiIvPjwvc3ZnPg=='
)
const VideoOfImagesShow = ref(true) // 页面图片或视频数量超出后,拍照按钮隐藏
const imageList = ref([]) //存放图片的地址
const imageListIds = ref([]) //存放图片id的地址
const videoList = ref([]) //视频存放的地址
const videoListIds = ref([]) //存放视频id的地址
// const sourceType = ref(['拍摄', '相册', '拍摄或相册'])
const sourceTypeIndex = ref(2)
const cameraList = ref([
  {
    value: 'back',
    name: '后置摄像头',
    checked: 'true'
  },
  {
    value: 'front',
    name: '前置摄像头'
  }
])
const cameraIndex = ref(0) // 上传视频时的数量
const props = defineProps({
  // 上传配置项
  uploadConfig: {
    type: Object,
    required: true,
    default: () => {
      return {
        //    maxCount: 3,
        //     exParam: 'imgArr'
      }
    }
  },
  imgUrlStr: {
    type: [String, Array],
    required: true,
    default: () => {
      return []
    }
  }
})
// onUnload((val) => {
//   ;(imageList.value = []),
//     (sourceTypeIndex.value = 2),
//     (sourceType.value = ['拍摄', '相册', '拍摄或相册'])
// }),
// 点击上传图片或视频
function chooseVideoImage() {
  uni.showActionSheet({
    title: '选择上传类型',
    itemList: ['图片', '视频'],
    success: (res) => {
      if (res.tapIndex == 0) {
        chooseImages()
      } else {
        chooseVideo()
      }
    }
  })
}
//上传图片
function chooseImages() {
  uni.chooseImage({
    count: props.uploadConfig.maxCount, // 允许选择的数量
    sizeType: ['original', 'compressed'], // 可以指定是原图还是压缩图,默认二者都有
    sourceType: ['album', 'camera'], // 从相册选择
    success: (e) => {
        uni.showLoading({
        mask: true,
        title: '上传中,请稍侯'
      })
      uploadImg(e, props.uploadConfig.exParam).then((res) => {
        const ids = getImgUrl(res)
        imageListIds.value.push(ids)
        emit('getImgUploadIds', imageListIds.value)
        imageList.value = imageList.value.concat(e.tempFilePaths)
        uni.hideLoading()
      })

      if (
        imageList.value.length + videoList.value.length ==
        props.uploadConfig.maxCount
      ) {
        VideoOfImagesShow.value = false //图片上传数量和count一样时,让点击拍照按钮消失
      }
    }
  })
}
//上传视频
function chooseVideo(index) {
  uni.chooseVideo({
    maxDuration: 60, //拍摄视频最长拍摄时间,单位秒。最长支持 60 秒
    count: props.uploadConfig.maxCount,
    camera: cameraList.value[cameraIndex.value].value, //'front'、'back',默认'back'
    sourceType: sourceType[sourceTypeIndex.value],

    success: (e) => {
      uni.showLoading({
        mask: true,
        title: '上传中,请稍侯'
      })
      uploadVideo(e, props.uploadConfig.exParam).then((res) => {
        const ids = getImgUrl(res)
        videoListIds.value.push(ids)
        emit('getVideoUploadIds', videoListIds.value)
        videoList.value = videoList.value.concat(e.tempFilePath)
        uni.hideLoading()
      })
      if (
        imageList.value.length + videoList.value.length ==
        props.uploadConfig.maxCount
      ) {
        VideoOfImagesShow.value = false
      }
    }
  })
}
//预览图片
function previewImage(e) {
  uni.previewImage({
    current: e,
    urls: imageList.value
  })
}
// 删除图片
function delect(index) {
  uni.showModal({
    title: '提示',
    content: '是否要删除该图片',
    success: (res) => {
      if (res.confirm) {
        imageList.value.splice(index, 1)
        imageListIds.value.splice(index, 1)
        emit('getImgUploadIds', imageListIds.value)
      }
      if (
        imageList.value.length + videoList.value.length ==
        props.uploadConfig.maxCount
      ) {
        VideoOfImagesShow.value = false
      } else {
        VideoOfImagesShow.value = true
      }
    }
  })
}
// 删除视频
function delectVideo(index) {
  uni.showModal({
    title: '提示',
    content: '是否要删除此视频',
    success: (res) => {
      if (res.confirm) {
        videoList.value.splice(index, 1)
        videoListIds.value.splice(index, 1)
        emit('getVideoUploadIds', videoListIds.value)
      }
      if (
        imageList.value.length + videoList.value.length ==
        props.uploadConfig.maxCount
      ) {
        VideoOfImagesShow.value = false
      } else {
        VideoOfImagesShow.value = true
      }
    }
  })
}
// 处理图片
function getImgUrl(arr) {
  const idArr = []
  let idStr = ''
  arr.forEach((item) => {
    idArr.push(item.data.id)
  })
  idStr = idArr.join(',')
  return idStr
}
</script>

对视频和照片的尺寸进行定义。可灵活根据个人需求进行设置。

复制代码
<style lang="scss">
/* 统一上传后显示的盒子宽高比 */
.box-mode {
  width: 20vw;
  height: 20vw;

  border-radius: 8rpx;
  overflow: hidden;
}

.full {
  width: 100%;
  height: 100%;
}

.up-page {
  display: flex;
  flex-wrap: wrap;
  display: flex;
  width: 100%;
  .show-box:nth-child(3n) {
    margin-right: 0;
  }
  .show-box {
    position: relative;
    margin-bottom: 4vw;
    margin-right: 4vw;
    @extend .box-mode;

    .delect-icon {
      height: 40rpx;
      width: 40rpx;
      position: absolute;
      right: 0rpx;
      top: 0rpx;
      z-index: 1000;
    }
  }
}
</style>

以上三部分可组合为一个完整的vue组件,可在父组件中使用。父组件的使用实例

复制代码
          <FileUpload :uploadConfig="uploadConfig" @getImgUploadIds="uploadImgIds($event)" @getVideoUploadIds="uploadVideoIds($event)"/>

const uploadConfig = ref({
   maxCount: 3,
   exParam: 'imgArr'
})

// 获取图片的ids
function uploadImgIds(val) {
  console.log(val)
}
// 获取视频的ids
function uploadVideoIds(val) {
  console.log(val)
}
相关推荐
腾讯TNTWeb前端团队1 小时前
helux v5 发布了,像pinia一样优雅地管理你的react状态吧
前端·javascript·react.js
范文杰5 小时前
AI 时代如何更高效开发前端组件?21st.dev 给了一种答案
前端·ai编程
拉不动的猪5 小时前
刷刷题50(常见的js数据通信与渲染问题)
前端·javascript·面试
拉不动的猪5 小时前
JS多线程Webworks中的几种实战场景演示
前端·javascript·面试
FreeCultureBoy6 小时前
macOS 命令行 原生挂载 webdav 方法
前端
uhakadotcom6 小时前
Astro 框架:快速构建内容驱动型网站的利器
前端·javascript·面试
uhakadotcom6 小时前
了解Nest.js和Next.js:如何选择合适的框架
前端·javascript·面试
uhakadotcom7 小时前
React与Next.js:基础知识及应用场景
前端·面试·github
uhakadotcom7 小时前
Remix 框架:性能与易用性的完美结合
前端·javascript·面试
uhakadotcom7 小时前
Node.js 包管理器:npm vs pnpm
前端·javascript·面试