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)
}
相关推荐
码蜂窝编程官方19 分钟前
【含开题报告+文档+PPT+源码】基于SpringBoot+Vue的虎鲸旅游攻略网的设计与实现
java·vue.js·spring boot·后端·spring·旅游
gqkmiss19 分钟前
Chrome 浏览器 131 版本开发者工具(DevTools)更新内容
前端·chrome·浏览器·chrome devtools
Summer不秃25 分钟前
Flutter之使用mqtt进行连接和信息传输的使用案例
前端·flutter
旭日猎鹰29 分钟前
Flutter踩坑记录(二)-- GestureDetector+Expanded点击无效果
前端·javascript·flutter
Viktor_Ye35 分钟前
高效集成易快报与金蝶应付单的方案
java·前端·数据库
hummhumm37 分钟前
第 25 章 - Golang 项目结构
java·开发语言·前端·后端·python·elasticsearch·golang
乐闻x1 小时前
Vue.js 性能优化指南:掌握 keep-alive 的使用技巧
前端·vue.js·性能优化
一条晒干的咸魚1 小时前
【Web前端】创建我的第一个 Web 表单
服务器·前端·javascript·json·对象·表单
花海少爷1 小时前
第十章 JavaScript的应用课后习题
开发语言·javascript·ecmascript
Amd7941 小时前
Nuxt.js 应用中的 webpack:compiled 事件钩子
前端·webpack·开发·编译·nuxt.js·事件·钩子