vue中-视频上传完整封装复用组件

小鱼儿心语:当人生让你碰壁头破血流时,别害怕,没有这些挫折,怎能练就一身钢筋铁骨,当生活给你一百个理由哭泣时,别沮丧,你就拿出一千个理由笑给它看。
视频的上传.png

视频的列表展示.png

视频的修改回显内容展示.png

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

视频上传uploadvideo.vue组件(直接复制即可):
<template>
  <div class="component-upload-image">
    <el-upload
      multiple
      :action="uploadImgUrl"
      :on-success="handleUploadSuccess"
      :before-upload="handleBeforeUpload"
      :limit="limit"
      :on-error="handleUploadError"
      :on-exceed="handleExceed"
      :on-progress="uploadVideoProcess"
      ref="imageUpload"
      :before-remove="handleDelete"
      :show-file-list="false"
      :headers="headers"
      :file-list="videoForm.showVideoPath"
      :on-preview="handlePictureCardPreview"
      :class="{ hide: videoForm.showVideoPath.length >= limit }"
      :disabled="isdisabled"
    >
    <el-button type="primary">上传视频</el-button>
      <!-- <video v-if="videoForm.showVideoPath !='' && !videoFlag"
      v-for="(item,index) in videoForm.showVideoPath"
      :key="index"
              v-bind:src="item"
              class="avatar video-avatar"
              controls="controls"
              style="width: 80%; height: 80%;">
          您的浏览器不支持视频播放
      </video> -->
      <!-- <el-icon class="avatar-uploader-icon"><plus /></el-icon> -->
      <el-progress v-if="videoFlag == true"
                                 type="circle"
                                 v-bind:percentage="videoUploadPercent"
                                 style="margin-top:7px;"></el-progress>

      
    </el-upload>
    <!-- 自定义上传文件列表 -->
    <div v-if="videoForm.showVideoPath.length > 0" style="width: 1300px;display: flex;flex-wrap: wrap;margin-top: 3%;">
      <div v-for="(item, index) in videoForm.showVideoPath">
        
        <!-- <img src="·······/视频路径" class="delete_icon" alt="" @click="deleteImg(item)"> -->
        <video v-if="videoForm.showVideoPath !='' && !videoFlag"
                v-bind:src="item.url"
                class="avatar video-avatar"
                controls="controls"
                style="width: 70%;">
            您的浏览器不支持视频播放
        </video>
        <p style="width: 100%;height: 20px;cursor: pointer;" :style="{background:isbackground}" v-on:mouseover="handleMouseOver" v-on:mouseout="headleMouseOut">
          <span style="float: left;margin-top: -7px;">{{ item.name }}</span>
          <!-- <el-icon class="Close" style="float: right;"></el-icon> -->
          <el-icon :size="16" style="float: right;" @click="deleteImg(item,index)">
            <Close />
          </el-icon>
        </p>
      </div>
      </div>
    <!-- 上传提示 -->
    <div class="el-upload__tip" v-if="showTip">
      请上传
      <template v-if="fileSize">
        大小不超过 <b style="color: #f56c6c">{{ fileSize }}MB</b>
      </template>
      <template v-if="fileType">
        格式为 <b style="color: #f56c6c">{{ fileType.join("/") }}</b>
      </template>
      的文件
    </div>

    <el-dialog
      v-model="dialogVisible"
      title="预览"
      width="800px"
      append-to-body
    >
      <img
        :src="dialogImageUrl"
        style="display: block; max-width: 100%; margin: 0 auto"
      />
    </el-dialog>
  </div>
</template>

<script setup>
import { getToken } from "@/utils/auth";

const props = defineProps({
  modelValue: [String, Object, Array],
  isdisabled: {
    type: Boolean,
    default: false
  },
  // 图片数量限制
  limit: {
    type: Number,
    default: 5,
  },
  // 大小限制(MB)
  fileSize: {
    type: Number,
    default: 5,
  },
  // 文件类型, 例如['png', 'jpg', 'jpeg']
  fileType: {
    type: Array,
    default: () => ['video/mp4', 'video/ogg', 'video/flv', 'video/avi', 'video/wmv', 'video/rmvb', 'video/mov'],
  },
  // 是否显示提示
  isShowTip: {
    type: Boolean,
    default: true
  },
});

const { proxy } = getCurrentInstance();
const emit = defineEmits();
const number = ref(0);
const uploadList = ref([]);
const dialogImageUrl = ref("");
const dialogVisible = ref(false);
const baseUrl = import.meta.env.VITE_APP_BASE_API;
const uploadImgUrl = ref(import.meta.env.VITE_APP_BASE_API + "/common/upload"); // 上传的图片服务器地址
const headers = ref({ Authorization: "Bearer " + getToken() });
const fileList = ref([]);
const showTip = computed(
  () => props.isShowTip && (props.fileType || props.fileSize)
);


const videoFlag = ref(false);
//是否显示进度条
const videoUploadPercent = ref("");
//进度条的进度
const isShowUploadVideo = ref(false);
//显示上传按钮
const videoForm = ref({showVideoPath: []});

const isbackground = ref("");

watch(() => props.modelValue, val => {
  if (val) {
    // 首先将值转为数组
    const list = Array.isArray(val) ? val : props.modelValue.split(",");
    // 然后将数组转为对象数组
    videoForm.value.showVideoPath = list.map(item => {
      const filename = item.split('/') 
      const name = filename[filename.length-1]
      if (typeof item === "string") {
        if (item.indexOf(baseUrl) === -1) {
          item = { name: name, url: baseUrl + item };
        } else {
          item = { name: name, url: item };
        }
      }
      return item;
    });
  } else {
    videoForm.value.showVideoPath = [];
    return [];
  }
},{ deep: true, immediate: true });

function handleMouseOver(){
  isbackground.value = '#ede1e1'
}
function headleMouseOut(){
  isbackground.value = ''
}
function deleteImg(row,index){
  // videoForm.value.showVideoPath.forEach((item,index) => {
    videoForm.value.showVideoPath.splice(index, 1);
  // })
  
  // videoForm.value.showVideoPath = videoForm.value.showVideoPath.fileter(item => item.name == row.name)
}
// 上传前loading加载
function handleBeforeUpload(file) {
  // let isImg = false;
  // if (props.fileType.length) {
  //   let fileExtension = "";
  //   if (file.name.lastIndexOf(".") > -1) {
  //     fileExtension = file.name.slice(file.name.lastIndexOf(".") + 1);
  //   }
  //   isImg = props.fileType.some(type => {
  //     if (file.type.indexOf(type) > -1) return true;
  //     if (fileExtension && fileExtension.indexOf(type) > -1) return true;
  //     return false;
  //   });
  // } else {
  //   isImg = file.type.indexOf("image") > -1;
  // }

  var fileSize = file.size / 1024 / 1024 < props.fileSize;
  if (['video/mp4', 'video/ogg', 'video/flv', 'video/avi', 'video/wmv', 'video/rmvb', 'video/mov'].indexOf(file.type) == -1) {
    proxy.$modal.msgError("请上传正确的视频格式");
      return false;
  }
  if (!fileSize) {
    proxy.$modal.msgError("视频大小不能超过${props.fileSize}MB");
      return false;
  }
  isShowUploadVideo.value = false;
  // if (!isImg) {
  //   proxy.$modal.msgError(
  //     `文件格式不正确, 请上传${props.fileType.join("/")}图片格式文件!`
  //   );
  //   return false;
  // }
  // if (props.fileSize) {
  //   const isLt = file.size / 1024 / 1024 < props.fileSize;
  //   if (!isLt) {
  //     proxy.$modal.msgError(`上传头像图片大小不能超过 ${props.fileSize} MB!`);
  //     return false;
  //   }
  // }
  // proxy.$modal.loading("正在上传图片,请稍候...");
  number.value++;
}
function uploadVideoProcess(event, file, fileList) {
    videoFlag.value = true;
    videoUploadPercent.value = file.percentage.toFixed(0) * 1;
}

// 文件个数超出
function handleExceed() {
  proxy.$modal.msgError(`上传文件数量不能超过 ${props.limit} 个!`);
}

// 上传成功回调
function handleUploadSuccess(res, file) {
  if (res.code === 200) {
    console.log(151,res)
    isShowUploadVideo.value = true;
    videoFlag.value = false;
    videoUploadPercent.value = 0;
    // uploadList.value.push({ name: res.fileName, url: res.fileName });
    // fileList.value.push({ name: res.fileName, url: res.fileName });
    videoForm.value.showVideoPath.push({ name: res.originalFilename, url: import.meta.env.VITE_APP_BASE_API + res.fileName });
    emit("file", listToString(videoForm.value.showVideoPath));
    uploadedSuccessfully();
  } else {
    number.value--;
    proxy.$modal.closeLoading();
    proxy.$modal.msgError(res.msg);
    proxy.$refs.imageUpload.handleRemove(file);
    uploadedSuccessfully();
  }
}

// 删除图片
function handleDelete(file) {
  console.log(166,fileList.value.map(f => f.name).toString(),file)
  // const findex = fileList.value.map(f => f.name).toString().indexOf(file.name);
  // console.log(168,findex)
  // if (findex > -1 && fileList.value.length == number.value) {
    if(fileList.value.length>0){
      fileList.value.forEach((item,index) => {
        console.log(172,item)
        if(item.url.indexOf(file.response.fileName)!=-1){
          fileList.value.splice(index, 1);
          console.log(170,fileList.value.length)
          emit("update:modelValue", listToString(fileList.value));
          emit("file", listToString(fileList.value));
          return false;
        }
      })
    }
    
  // }
}

// 上传结束处理
function uploadedSuccessfully() {
  if (number.value > 0 && videoForm.value.showVideoPath.length === number.value) {
    // fileList.value = fileList.value.filter(f => f.url !== undefined).concat(uploadList.value);
    // fileList.value = uploadList.value
    // console.log(175,fileList.value,uploadList.value)
    // uploadList.value = [];
    // number.value = 0;
    emit("update:modelValue", listToString(videoForm.value.showVideoPath));
    emit("file", listToString(videoForm.value.showVideoPath));
    // proxy.$modal.closeLoading();
  }
}

// 上传失败
function handleUploadError() {
  proxy.$modal.msgError("上传图片失败");
  proxy.$modal.closeLoading();
}

// 预览
function handlePictureCardPreview(file) {
  dialogImageUrl.value = file.url;
  dialogVisible.value = true;
}

// 对象转成指定字符串分隔
function listToString(list, separator) {
  let strs = "";
  separator = separator || ",";
  for (let i in list) {
    if (undefined !== list[i].url && list[i].url.indexOf("blob:") !== 0) {
      strs += list[i].url.replace(baseUrl, "") + separator;
    }
  }
  return strs != "" ? strs.substr(0, strs.length - 1) : "";
}
</script>

<style scoped lang="scss">
// .el-upload--picture-card 控制加号部分
:deep(.hide .el-upload--picture-card) {
    display: none;
}
</style>
父组件引用**完整流程**
<template>
  <div class="app-container">
    // 表格中视频的展示
    <el-table v-loading="loading" :data="standardList" @selection-change="handleSelectionChange">
      <el-table-column label="视频" align="center" prop="unsafeJzsp" width="100">
        <template #default="scope">
          <video :src="scope.row.unsafeJzsp"
              class="avatar video-avatar"
              controls="controls"
              style="width: 100%;">
           </video>
         </template>
      </el-table-column>
    </el-table>
    // 弹框中视频上传的组件引用
    // 添加或修改不安全行为标准对话框
    <el-dialog :title="title" v-model="open" width="50%" append-to-body>
      <el-form ref="standardRef" :model="form" :rules="rules" label-width="120px" inline>
        <el-form-item label="矫正视频" prop="unsafeJzsp">
          // `modelValue`是修改时用于回显视频的属性
          <uploadvideo v-model="form.unsafeJzsp" style="width: 970px;" @file="getvideo" :modelValue="form.unsafeJzsp"></uploadvideo>
        </el-form-item>
      </el-form>
      <template #footer>
        <div class="dialog-footer">
          <el-button type="primary" @click="submitForm">确 定</el-button>
          <el-button @click="cancel">取 消</el-button>
        </div>
      </template>
    </el-dialog>
  </div>
</template>

<script setup>
  // 接口为公共配置,引用即可,也可直接使用接口地址
  import { listStandard, getStandard, delStandard, addStandard, updateStandard } from "@/api/unsafe/standard";
  const { proxy } = getCurrentInstance();
  const standardList = ref([]);
  const data = reactive({
    form: {}
  });
  const { form } = toRefs(data);
  // 上传视频后视频组件传递过来的方法,获取上传的视频文件名
  function getvideo(e){
    form.value.unsafeJzsp = e
  }
  // 查询不安全行为标准列表 
  function getList() {
    loading.value = true;
    listStandard().then(response => {
      standardList.value = response.rows;
      // 拼接视频地址,import.meta.env.VITE_APP_BASE_API 为接口请求的ip地址,根据实际情况获取~
      standardList.value.forEach(item => {
        item.unsafeJzsp = import.meta.env.VITE_APP_BASE_API + item.unsafeJzsp
      })
      loading.value = false;
    });
  }
  /** 修改按钮操作 */
  function handleUpdate(row) {
    const _unsafeBzId = row.unsafeBzId || ids.value
    // 获取详情数据
    getStandard(_unsafeBzId).then(response => {
      form.value = response.data;
      // 拼接视频地址,使提交的视频正?;叵哉故?      form.value.unsafeJzsp = import.meta.env.VITE_APP_BASE_API + form.value.unsafeJzsp
      open.value = true;
      title.value = "修改不安全行为标准";
    });
  }
</script>

接下来给大家总结了video的标签,属性,方法,事件的使用方法:

<video> 标签属性:

src:视频的URL
poster:视频封面,没有播放时显示的图片
preload:预加载
autoplay:自动播放
loop:循环播放
controls:浏览器自带的控制条
width:视频宽度
height:视频高度

Html代码:

<video ref=”media” src=”http://www.abc.com/test.mp4″ controls width=”400px” heigt=”400px”></video>  
// 获取标签
const media= this.$refs.media;      ----vue2.0写法
`或者`
const { proxy } = getCurrentInstance();
const media= proxy.$refs.media;     ----vue3.0写法

Js代码:
// 错误状态

Media.error; //null:正常
Media.error.code; //1.用户终止 2.网络错误 3.解码错误 4.URL无效

// 网络状态

Media.currentSrc; //返回当前资源的URL
Media.src = value; //返回或设置当前资源的URL
Media.canPlayType(type); //是否能播放某种格式的资源
Media.networkState; //0.此元素未初始化 1.正常但没有使用网络 2.正在下载数据 3.没有找到资源
Media.load(); //重新加载src指定的资源
Media.buffered; //返回已缓冲区域,TimeRanges
Media.preload; //none:不预载 metadata:预载资源信息 auto:

// 回放状态

Media.currentTime = value; //当前播放的位置,赋值可改变位置
Media.startTime; //一般为0,如果为流媒体或者不从0开始的资源,则不为0
Media.duration; //当前资源长度 流返回无限
Media.paused; //是否暂停
Media.defaultPlaybackRate = value;//默认的回放速度,可以设置
Media.playbackRate = value;//当前播放速度,设置后马上改变
Media.played; //返回已经播放的区域,TimeRanges,关于此对象见下文
Media.seekable; //返回可以seek的区域 TimeRanges
Media.ended; //是否结束
Media.autoPlay; //是否自动播放
Media.loop; //是否循环播放
Media.play(); //播放
Media.pause(); //暂停

// 控制

Media.controls;//是否有默认控制条
Media.volume = value; //音量
Media.muted = value; //静音

事件:

eventTester = function(e){  
    Media.addEventListener(e,function(){  
        console.log((new Date()).getTime(),e);  
    });  
}  
  
eventTester(“loadstart”);   //客户端开始请求数据  
eventTester(“progress”);    //客户端正在请求数据  
eventTester(“suspend”);     //延迟下载  
eventTester(“abort”);       //客户端主动终止下载(不是因为错误引起),  
eventTester(“error”);       //请求数据时遇到错误  
eventTester(“stalled”);     //网速失速  
eventTester(“play”);        //play()和autoplay开始播放时触发  
eventTester(“pause”);       //pause()触发  
eventTester(“loadedmetadata”);  //成功获取资源长度  
eventTester(“loadeddata”);  //  
eventTester(“waiting”);     //等待数据,并非错误  
eventTester(“playing”);     //开始回放  
eventTester(“canplay”);     //可以播放,但中途可能因为加载而暂停  
eventTester(“canplaythrough”); //可以播放,歌曲全部加载完毕  
eventTester(“seeking”);     //寻找中  
eventTester(“seeked”);      //寻找完毕  
eventTester(“timeupdate”);  //播放时间改变  
eventTester(“ended”);       //播放结束  
eventTester(“ratechange”);  //播放速率改变  
eventTester(“durationchange”);  //资源长度改变  
eventTester(“volumechange”);    //音量改变 

属性列表:

属性列表.png

媒介属性: 一般用于js操作

媒介属性.png

媒介属性1.png

媒介属性2.png
最后编辑于
?著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 214,128评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,316评论 3 388
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事?!?“怎么了?”我有些...
    开封第一讲书人阅读 159,737评论 0 349
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,283评论 1 287
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,384评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,458评论 1 292
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,467评论 3 412
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,251评论 0 269
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,688评论 1 306
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,980评论 2 328
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,155评论 1 342
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,818评论 4 337
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,492评论 3 322
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,142评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,382评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,020评论 2 365
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,044评论 2 352

推荐阅读更多精彩内容