VUE 仿windows文件夹文件

146 篇文章 5 订阅

我在2019年的时候,曾经在一家工厂,做过一个文件管理系统,当时有个界面效果是如下,采用的是jquery来写的,2年后,我又看到了这种,不过这并不是我写的,但不影响我去学习研究并对比,如下是现在VUE响应式的文件夹/文件前端效果

 

Rec 0002

 功能:新增 移动 上传 删除 全选 重命名 查找

所用技术 Vue+element

 所用技术点

       element的面包屑

       element的分页

       vue-simple-uploader文件多线程式上传

       封装了图片组件

        视频播放器vue-video-player

难度等级:中等

技术难度还好,主要是和后端约定好数据的设计,业务步骤,有了这些,再加上上面的插件,基本就没难度了。附上完整代码。

index.vue

<!--多媒体素材首页 -->
<template>
  <div class="video-container">
    <el-card shadow="hover">
      <div class="video-header clearfix">
        <div class="header-top">
          <el-button :loading="uploadeFile_loading" type="primary" size="medium" @click="uploadeFile">
            <i class="el-icon-upload2"></i>
            <!-- 上传 -->
            {{ $t('material.upload') }}
          </el-button>
          <!-- 新建文件夹 -->
          <el-button type="primary" size="medium" @click="addFolder">
            <i class="el-icon-plus"></i>
            {{ $t('material.newFolder') }}
          </el-button>
          <!-- 重命名 -->
          <el-button
            v-show="activeFlag"
            :disabled="activeFlagNotReame"
            style="margin-left:10px"
            type="primary"
            size="medium"
            @click="handleRenameFile"
          >
            <i class="el-icon-edit-outline"></i>
            {{ $t('material.rename') }}
          </el-button>
          <!-- 移动到 -->
          <el-button
            v-show="activeFlag"
            :disabled="activeFlagMoveble"
            style="margin-left:10px"
            type="primary"
            size="medium"
            @click="moveFolder"
          >
            <i class="el-icon-rank"></i>
            {{ $t('material.moveTo') }}
          </el-button>

          <!-- 全选 -->
          <el-button v-show="activeFlag" type="primary" style="margin-left:10px" size="medium" @click="toggleCheckAllSelect">
            <i class="el-icon-check"></i>
            {{ $t('material.selectAll') }}
          </el-button>
          <!-- 删除 -->
          <el-button v-show="activeFlag" style="margin-left:10px" size="medium" @click="handleDeleteFile">
            <i class="el-icon-delete"></i>
            {{ $t('material.delete') }}
          </el-button>
          <div class="fr">
            <el-radio-group v-model="resType" @change="queryMaterialPage">
              <!-- 全部 -->
              <el-radio label="-1">{{ $t('material.all') }}</el-radio>
              <!-- 图片 -->
              <el-radio label="1">{{ $t('material.image') }}</el-radio>
              <!-- 应用 -->
              <el-radio label="98">{{ $t('programModel.apply') }}</el-radio>
            </el-radio-group>
            <el-input
              v-model="keyWord"
              :placeholder="$t('common.name')"
              style="width:150px;margin-left:10px;"
              size="medium"
              clearable
              @clear="resetGetMaterialPage"
            ></el-input>
            <!-- 查询 -->
            <el-button size="medium" type="primary" style="margin-left:2px" icon="el-icon-search" @click="queryMaterialPage">{{ $t('material.search') }}</el-button>
          </div>
        </div>

        <div class="breadcrumb">
          <!-- 面包屑 -->
          <el-breadcrumb separator-class="el-icon-arrow-right">
            <el-breadcrumb-item v-for="(item,index) in breadcrumbList" :key="index">
              <span
                :class="(index === breadcrumbList.length-1) ? 'breadcrumb-link-active': ''"
                class="breadcrumb-link"
                @click="backFileFolder(item)"
              >{{ item.name }}</span>
            </el-breadcrumb-item>
          </el-breadcrumb>
        </div>
      </div>

      <div
        v-loading="loading"
        class="video-main"
      >
        <ul class="list">
          <li
            v-for="(file,index) in files"
            :key="index"
            :class="{active:file.active}"
            class="list-item"
            @dblclick="dbClickOpenFile(file)"
          >
            <div class="inner">
              <el-image
                v-if="file.resType > 0"
                :src="file.thumUrl ? file.thumUrl : defultThumUrl"
                class="icon-thumb"
                fit="contain"
                alt
              ></el-image>
              <i v-else class="icon-folder"></i>
              <div v-show="file.resType > 0" class="hover-cover">
                <span style="color:#fff;">{{ file.size | sizeFilter }}</span>
              </div>
            </div>
            <i class="icon-file-selected" @click="toggleSelect(file,file.id)"></i>
            <div class="file-name">
              <span :title="file.name">{{ file.name }}</span>
            </div>
          </li>
        </ul>
        <!-- 分页 -->
        <el-pagination
          :current-page="pageIndex"
          :hide-on-single-page="true"
          :page-sizes="[100, 200, 400, 600]"
          :page-size="100"
          :total="total"
          layout="total,  prev, pager, next, jumper"
          @current-change="handleCurrentChange"
        ></el-pagination>
      </div>
    </el-card>
    <!-- 重命名弹出框 -->
    <el-dialog :title="$t('common.rename')" :visible.sync="renameDialogVisible" width="30%">
      <el-form ref="renameForm" :model="renameForm" :rules="renameRules" style="margin-top:20px">
        <el-form-item prop="name">
          <el-input v-model="renameForm.name" autocomplete="off"></el-input>
        </el-form-item>
      </el-form>
      <div slot="footer" class="dialog-footer">
        <el-button size="medium" @click="renameDialogVisible = false">{{ $t('common.cancel') }}</el-button>
        <el-button :loading="rename_loading" type="primary" size="medium" @click="handleRenameConfirm">{{ $t('common.confirm') }}</el-button>
      </div>
    </el-dialog>
    <!-- 上传组件 -->
    <uploader-dialog :show.sync="showDialog" :upload-url="upfileUrl" :dir-id="parentId"></uploader-dialog>
    <!-- 移动文件组件 -->
    <move-file-dialog
      :show.sync="moveFileDialog"
      :ids="activeItems"
      :parent-id="parentId"
      @removeActiveItem="handleRemoveActiveItem"
    ></move-file-dialog>
    <!-- 图片弹出放大组件 -->
    <viewer-dialog
      :show.sync="showImageDialog"
      :src="imgSrc"
      :img-title="imgTitle"
      @closeImgDialog="imgDialogClose"
    ></viewer-dialog>
    <!-- 视频播放组件 -->
    <el-dialog :visible.sync="showlPlayVideo" :append-to-body="true" @close="closePlay">
      <player :video-url="videoUrl" :state="state" :poster="poster"></player>
    </el-dialog>
  </div>
</template>

<script>
import Vue from 'vue'
// 引入上传子组件dialog
import uploaderDialog from './components/uploader-dialog' 
// 引入移动文件子组件dialog
import moveFileDialog from './components/movefile-dialog'
// 引入图片子组件dialog
import viewerDialog from './components/viewer-dialog'
// 引入视频播放组件
import player from '@/components/Video-player'
export default {
  name: 'Videomanagement',
  components: {
    uploaderDialog,
    moveFileDialog,
    viewerDialog,
    player
  },
  filters: {
    sizeFilter($bytesize) {
      let $i = 0
      while (Math.abs($bytesize) >= 1024) {
        $bytesize = $bytesize / 1024
        $i++
        if ($i === 4) break
      }
      const $units = ['Bytes', 'KB', 'MB', 'GB', 'TB']
      const $newsize = Math.round($bytesize, 2)
      return $newsize + ' ' + $units[$i]
    }
  },
  data() {
    return {
      uploadeFile_loading: false,
      rename_loading: false,
      defultThumUrl: '../../assets/svg/icon-file-thumbnail.svg', // 默认文件夹的样式图片
      loading: false, // 加载loading样式
      keyWord: '', // 查询关键字
      resType: '-1', // 资源类型查询条件
      id: 0, // 当前目录id
      parentId: 0, // 当前目录的父文件夹id
      upfileUrl: '', // 上传文件服务器的url地址
      showDialog: false, // 是否弹出上传dialog
      moveFileDialog: false, // 是否弹出移动文件dialog
      showImageDialog: false, // 弹出大图浏览dialog
      imgSrc: '', // 弹出大图的图片url地址
      imgTitle: '', // 弹出大图的图片title
      showlPlayVideo: false, // 弹出视频播放
      videoUrl: '', // 视频播放地址
      state: false, // 视频播放状态
      poster: '', // 视频播放的封面
      activeFlag: false, // 用来显示关闭删除,重命名,移动到按钮操作
      activeFlagNotReame: false, // 当激活多个元素的时候,重命名操作灰掉
      activeFlagMoveble: false, // 当激活选中的元素中有文件夹的时候,移动操作不可用,灰掉
      activeItems: [], // 选中激活的文件列表
      files: [], // 文件数据列表
      folderListData: [], // 文件夹列表数据
      breadcrumbList: [], // 面包屑中保存的文件夹数据
      toggleCheckAllSelectFlag: false, // 全选样式切换
      pageIndex: 1, // 分页参数-当前页
      pageSize: 100, // 分页参数-分页大小
      total: 0, // 分页参数-总条数
      storagePercent: 0, // 存储空间容量百分比
      maxStorage: 0, //  存储空间总容量
      useStorage: 0, // 存储空间已用容量
      renameDialogVisible: false,
      renameForm: {
        name: ''
      },
      renameRules: {
        name: { required: true,
          pattern: /^[^/\\\\:\\*\\?\\<\\>\\|\"]{1,255}$/,
          message: this.$t('material.incorrectFileNameTips'), // 请输入正确的文件名称
          trigger: 'blur'
        }
      }
    }
  },
  computed: {

  },
  created() {
    this.initMaterialPage()
    this.getFolderList() // 获取文件夹列表数据
  },
  mounted() {},
  methods: {
    handleCurrentChange(val) {
      this.pageIndex = val
      this.initMaterialPage(this.parentId)
    },
    uploadeFile() {
      this.uploadeFile_loading = true
      // 上传文件
      this.urlmethod(this.url.usermanagement.serverAddr_servers, null)
        .then(res => {
          this.upfileUrl = res.data.uploadUrl
          this.showDialog = true
          this.uploadeFile_loading = false
        })
        .catch(err => {
          this.uploadeFile_loading = false
          console.log('err', err)
          this.loading = false
        })
    },
    moveFolder() {
      // 点击移动文件按钮,弹出移动文件dialog
      this.moveFileDialog = true
    },
    toggleSelect(item, id) {
      // 切换选择文件或文件夹的激活样式
      this.id = id
      if (item.active) {
        Vue.set(item, 'active', false) // 为item添加不存在的属性,需要使用vue提供的Vue.set( object, key, value )方法
        const index = this.activeItems.indexOf(id)
        if (index > -1) {
          this.activeItems.splice(index, 1)
        }
      } else {
        Vue.set(item, 'active', true)
        this.activeItems.push(id)
      }
      if (this.activeItems.length > 0) {
        // 当有文件被激活时,显示重命名,删除,移动操作按钮
        this.activeFlag = true
      } else {
        this.activeFlag = false
      }
      if (this.activeItems.length > 1) {
        // 当有两个以上的文件被选中的时候,重命名,移动操作按钮不可用
        this.activeFlagNotReame = true
      } else {
        this.activeFlagNotReame = false
      }
      this.activeFlagMoveble = true
      this.files.forEach(item => {
        if (item.active) {
          if (item.resType !== 0) {
            // 只要有一个不是文件夹的被选中,就可以移动
            this.activeFlagMoveble = false
          }
        }
      })
    },
    toggleCheckAllSelect() {
      // 全选切换
      this.activeFlagMoveble = !this.activeFlagMoveble // 全选移动操作可用,切换取反
      if (!this.toggleCheckAllSelectFlag) {
        this.files.forEach(item => {
          if (!item.active) {
            Vue.set(item, 'active', true)
          }
        })
        this.toggleCheckAllSelectFlag = true
      } else {
        this.files.forEach(item => {
          if (item.active) {
            Vue.set(item, 'active', false)
          }
        })
        this.toggleCheckAllSelectFlag = false
      }
      this.activeItems = []
      this.files.forEach(item => {
        if (item.active) {
          this.activeItems.push(item.id)
          if (item.resType !== 0) {
            // 只要有一个不是文件夹的被选中,就可以移动
            this.activeFlagMoveble = false
          }
        }
      })
      // 当没有选项被选中的时候
      if (this.activeItems.length === 0) {
        this.activeFlag = false
        this.activeFlagNotReame = false
      }
    },
    initMaterialPage(id = 0) {
      // 初始化获取素材管理分页(可以通过传递文件夹id来获取对应的文件列表,默认值0,获取的是根目录)
      this.loading = true
      const para = {
        data: {
          pageIndex: this.pageIndex,
          pageSize: this.pageSize,
          parentId: id,
          resType: Number(this.resType),
          keyWord: this.keyWord
        }
      }
      this.urlmethod(this.url.material.list_res, para)
        .then(res => {
          this.files = [...res.data.list]
          this.loading = false
          this.total = res.data.recordTotal
          this.activeItems = [] // 激活选项空
          this.activeFlag = false // 可操作按钮隐藏
          this.activeFlagMoveble = false // 恢复能不能移动的初始值
        })
        .catch(err => {
          console.log('err', err)
          this.loading = false
        })
    },
    queryMaterialPage() {
      // 通过条件筛选查询获取文件列表,也需要把当前文件夹的id传递进去
      this.initMaterialPage(this.parentId)
    },
    resetGetMaterialPage() {
      // 点击搜索框清楚按钮,重新获取列表数据
      // 把当前的文件夹id传进去
      this.initMaterialPage(this.parentId)
    },
    getFolderList() {
      // 获取文件夹列表数据
      this.urlmethod(this.url.material.selectDir_res, null)
        .then(res => {
          this.folderListData.push(res.data)
          const temObj = {}
          temObj.id = this.folderListData[0].id
          temObj.name = this.folderListData[0].name
          temObj.parentId = this.folderListData[0].parentId
          this.breadcrumbList.push(temObj) // 这里只保存了根目录文件夹
        })
        .catch(err => {
          console.log('err', err)
          this.loading = false
        })
    },
    backFileFolder(item) {
      // 点击面包屑回退到对应的文件夹级别,并且分页的索引值也需要退回到第一页
      this.pageIndex = 1
      const index = this.breadcrumbList.indexOf(item)
      const length = this.breadcrumbList.length
      if (item.id === this.breadcrumbList[length - 1].id) {
        // 点击的元素id是最后一个文件id,不能删除
      } else {
        this.breadcrumbList.splice(index + 1)
      }
      this.initMaterialPage(item.id) // 点击面包屑中的哪个文件夹就获取对应的文件列表页
      this.parentId = item.id // 更新当前界面的父文件夹id
    },
    addFolder() {
      // 新增文件夹
      this.$prompt(null, this.$t('common.add'), {
        confirmButtonText: this.$t('common.confirm'),
        cancelButtonText: this.$t('common.cancel'),
        inputPlaceholder: this.$t('material.enterFolderNameTips'),
        inputPattern: /^[^/\\\\:\\*\\?\\<\\>\\|\"]{1,255}$/, // 文件名正则
        inputErrorMessage: this.$t('material.incorrectFileNameTips') // 文件名格式不正确
      })
        .then(({ value }) => {
          const para = {
            data: {
              name: value,
              parentId: this.parentId
            }
          }
          this.urlmethod(this.url.material.addDir_res, para)
            .then(() => {
              this.initMaterialPage(this.parentId)
              this.$message({
                type: 'success',
                message: this.$t('material.newFolderNameIs') + value // 新的文件名是
              })
            })
            .catch(err => {
              console.log('err', err)
            })
        })
        .catch(() => {
          this.$message({
            type: 'info',
            message: this.$t('common.canceled')
          })
        })
    },
    // 打开重命名弹窗
    handleRenameFile() {
      // 文件重命名操作
      // 当激活项里只有唯一的一个元素,遍历files,通过id就可以找到该项需要重命名的name
      this.files.forEach(v => {
        if (this.activeItems.indexOf(v.id) === 0) {
          this.renameForm.name = v.name
        }
      })
      this.renameDialogVisible = true
    },
    // 重命名弹窗确定提交
    handleRenameConfirm() {
      this.$refs['renameForm'].validate((valid) => {
        if (valid) {
          this.rename_loading = true
          const para = {
            data: {
              id: this.id,
              name: this.renameForm.name
            }
          }
          this.urlmethod(this.url.material.reName_res, para)
            .then(() => {
              // 重命名成功后重新获取列表
              this.initMaterialPage(this.parentId)
              this.$message({
                type: 'success',
                message: this.$t('material.newFolderNameIs') + this.renameForm.name
              })
              this.renameDialogVisible = false
              this.rename_loading = false
            })
            .catch(err => {
              this.rename_loading = false
              console.log('err', err)
            })
        } else {
          return false
        }
      })
    },
    handleDeleteFile() {
      // 删除文件
      this.$confirm(this.$t('common.tipsBeforeDelete'), this.$t('common.tips'), {
        confirmButtonText: this.$t('common.confirm'),
        cancelButtonText: this.$t('common.cancel'),
        type: 'warning'
      })
        .then(() => {
          this.loading = true
          // 执行删除文件的api
          const para = {
            data: {
              ids: this.activeItems
            }
          }
          this.urlmethod(this.url.material.delete_res, para)
            .then(() => {
              // 删除成功后重新获取列表,注意,这是不是this.id,而是父id,因为点击文件夹了,当前的id已经发生了改变
              this.initMaterialPage(this.parentId)
              this.activeItems = [] // 激活选项空
              this.activeFlag = false // 可操作按钮隐藏
              this.$message({
                type: 'success',
                message: this.$t('common.deleteSuccess')
              })
              this.loading = false
            })
            .catch(err => {
              this.loading = false
              console.log('err', err)
            })
        })
        .catch(() => {
          this.$message({
            type: 'info',
            message: this.$t('common.canceled')
          })
        })
    },
    handleRemoveActiveItem(removeId) {
      // 子组件把文件移动位置成功后触发这个绑定的事件处理函数
      this.activeItems = [] // 激活选项空
      this.activeFlag = false // 可操作按钮隐藏
    },
    dbClickOpenFile(file) {
      // 双击文件操作
      // 双击后记住当前文件的id
      this.id = file.id
      if (file.resType === 0) {
        // 打开的是文件夹
        const temObj = {}
        temObj.id = file.id
        temObj.name = file.name
        temObj.parentId = file.parentId
        this.breadcrumbList.push(temObj) // 双击文件夹把当前文件夹的信息push到面包屑列表中
        this.parentId = file.id // 进入文件夹后,此时当前的父id就应该修改为双击文件夹的那个id
        this.initMaterialPage(this.id)
      } else if (file.resType === 1) {
        // 打开的是图片
        this.showImageDialog = true
        this.imgSrc = file.url
        this.imgTitle = file.name
      } else if (file.resType === 2 || file.resType === 99) {
        // 打开的是视频
        this.showlPlayVideo = true
        this.videoUrl = file.url
        this.state = true
        this.poster = file.thumUrl
      } else {
        // 其他文件的操作
        // 暂不支持
      }
    },
    closePlay() {
      // 关闭视频播放弹窗
      this.state = false
      this.videoUrl = ''
    },
    imgDialogClose() {
      // 子组件发出图片弹窗关闭,这里需要清空传入的图片地址,fix在慢网速的情况下,会先看到上一次的图片。
      this.imgSrc = ''
    }
  }
}
</script>

<style lang="scss" scoped>
.clearfix:after {
  content: '';
  display: block;
  clear: both;
}
.video-container {
  min-width: 630px;
  margin: 10px;
  .video-header {
    padding: 0 0 5px 0;
    border-bottom: 1px solid #dbdbdb;
    .breadcrumb {
      // float: left;
      height: 20px;
      margin-top: 10px;
      .breadcrumb-link {
        cursor: pointer;
        //font-size: 16px;
      }
      .breadcrumb-link:hover {
        color: #409eff;
        text-decoration: underline;
      }
      .breadcrumb-link-active {
        // 面包屑当前激活文件夹的样式
        font-weight: 700;
      }
    }
    .header-top {
      // float: right;
      height: 40px;
      line-height: 40px;
      position: relative;
    }
  }
  .video-main {
    .list-item {
      border: 1px solid #fff;
      box-sizing: border-box;
      position: relative;
      height: 100px;
      width: 80px;
      // background-color: green;
      margin: 5px;
      display: inline-block;
      cursor: pointer;
      .inner {
        height: 60px;
        width: 60px;
        // background-color: red;
        margin: 5px 10px;
        // padding: 10px 15px 10px 15px;
        .icon-folder {
          // 文件夹的样式
          display: inline-block;
          width: 60px;
          height: 60px;
          background-image: url(../../assets/svg/icon-file-close.svg);
          background-size: 100% 100%;
        }
        .icon-thumb {
          // 文件的样式
          width: 60px;
          height: 60px;
        }
      }
      .file-name {
        // 文件夹name样式
        padding-left: 10px;
        overflow: hidden;
        text-overflow: ellipsis;
        white-space: nowrap;
        color: #424e67;
        font-size: 12px;
      }
      .file-name:hover {
        color: #409eff;
      }
    }
    .hover-cover {
      width: 60px;
      height: 60px;
      position: absolute;
      left: 10px;
      top: 5px;
      background-color: rgb(0,0,0);
      opacity: 0;
      text-align: center;
      line-height: 60px;
      font-size: 12px;
    }
    .list-item:hover {
      background-color: #f1f5fa;
      .icon-file-selected {
        opacity: 0.5;
      }
      .hover-cover {
        opacity: 0.6;
      }
    }
    .icon-file-selected {
      // 小圆点默认样式
      position: absolute;
      left: 5px;
      top: 5px;
      display: inline-block;
      width: 20px;
      height: 20px;
      background-size: 100% 100%;
      background-image: url(../../assets/svg/icon-file-selected.svg);
      opacity: 0;
    }
    .icon-file-selected:hover {
      // 小圆点hover
      opacity: 1 !important;
    }
    .active {
      // 选择文件触发激活样式
      border: 1px solid #409eff;
      border-radius: 8px;
      .icon-file-selected {
        position: absolute;
        left: 5px;
        top: 5px;
        display: inline-block;
        width: 20px;
        height: 20px;
        background-size: 100% 100%;
        background-image: url(../../assets/svg/icon-file-selected.svg);
        opacity: 1;
      }
    }
    .active:hover {
      // 激活状态小圆点透明度1
      .icon-file-selected {
        opacity: 1 !important;
      }
    }
    .loadding-message {
      // 加载loading的文字样式
      color: #424e67;
      font-size: 12px;
      text-align: center;
    }
  }
}
</style>

 viewer-dialog.vue

<!-- 图片预览组件dailog形式 -->
<template>
  <div class="viewer-dialog-wrap">
    <el-dialog
      :visible.sync="visible"
      :title="imgTitle"
      :show="show"
      custom-class="dialog-width"
      @close="$emit('update:show', false)"
    >
      <img :src="src" alt />
      <!-- <div slot="footer" class="dialog-footer">{{imgTitle}}</div> -->
    </el-dialog>
  </div>
</template>

<script>
export default {
  name: 'ViewerDialog',
  filters: {},
  props: {
    id: {
      type: String,
      default: ''
    },
    show: {
      type: Boolean,
      default: false
    },
    src: {
      type: String,
      default: ''
    },
    imgTitle: {
      type: String,
      default: ''
    }
  },
  data() {
    return {
      visible: this.show
    }
  },
  computed: {},
  watch: {
    show(val, oldVal) {
      if (val === oldVal) {
        return
      }
      this.visible = val
    },
    // 如果内部有新值变化,更新外部的visible
    visible(val, oldVal) {
      if (val === oldVal) {
        return
      }
      this.$emit('update:visible', val)
      if (val === false) {
        // 当关闭弹窗的时候,向父组件传递清空src的消息
        this.$emit('closeImgDialog')
      }
    }
  },
  created() {},
  mounted() {},
  methods: {
    inited(viewer) {
      this.$viewer = viewer
    }
  }
}
</script>
<style lang="scss">
.viewer-dialog-wrap {
  .dialog-width {
    img {
      width: 100%;
      height: 100%;
    }
  }
}
</style>

uploader-dialog.vue

<!-- 素材管理上传功能弹出页面 -->
<template>
  <div class="uploader-dialog-wrap">
    <el-dialog

      :title="title"
      :visible.sync="visible"
      :show="show"
      :show-close="false"
      :close-on-click-modal="false"
      custom-class="dialog-width"
      @close="$emit('update:show', false)"
    >
      <uploader 
        v-if="visible"
        ref="uploader"
        :options="options"
        :file-status-text="fileStatusText"
        :auto-start="true"
        class="uploader-example"
        @file-added="onFileAdded"
        @file-progress="onFileProgress"
        @file-success="onFileSuccess"
        @file-error="onFileError"
      >
        <uploader-unsupport></uploader-unsupport>
        <uploader-btn ref="uploadBtn" :attrs="attrs" class="global-uploader-btn">{{ $t('material.selectFile') }}</uploader-btn>
        <uploader-list></uploader-list>
        <div class="warning-message"><i class="el-icon-warning" style="color:#E6A23C;font-size:20px;"></i>{{ $t('material.uploadTips') }}</div>
      </uploader>

      <div slot="footer" class="dialog-footer">
        <el-button :title="$t('common.nocompleteWillEmpty')" @click="close">{{ $t('common.close') }}</el-button>
      </div>
    </el-dialog>
  </div>
</template>

<script>
import { getCookie } from '@/prototypeEx/cacheEx'
export default {
  filters: {},
  props: {
    dirId: {
      // 上传文件夹id
      type: Number,
      default: 0
    },
    show: {
      type: Boolean,
      default: false
    },
    uploadUrl: {
      type: String,
      default: '',
      required: true
    }
  },
  data() {
    return {
      selectFile_loading: false,
      visible: this.show,
      title: this.$t('common.upload'),
      dialogLoading: false,
      MineType: [
        'image/png',
        'image/jpg',
        'image/jpeg',
        'image/gif',
        'image/webp',
        'application/x-msdownload',
        'application/vnd.android.package-archive'
      ], // 定义可接受的文件类型
      options: {
        target: '/', //  目标上传 URL
        testChunks: true, // 是否开启服务器分片校验,是否测试每个块是否在服务端已经上传了,主要用来实现秒传、跨浏览器上传等,默认 true
        chunkSize: this.config.uploader_chunk_size, // 分块时按照该值来分10kb
        fileParameterName: 'file', // 上传文件时文件的参数名,默认 file
        maxChunkRetries: 3, // 最大自动失败重试上传次数
        query: {
          token: '', // 从vuex中拿到的token
          dirId: this.dirId // 上传目标文件夹
        },
        checkChunkUploadedByResponse: function(chunk, message) {
          // 服务器分片校验函数,秒传及断点续传基础,用于根据 XHR 响应内容检测每个块是否上传成功了
          // let objMessage = JSON.parse(message);
          // if (objMessage.skipUpload) {
          //   return true;
          // }
          // return (objMessage.uploaded || []).indexOf(chunk.offset + 1) >= 0;
        },
        simultaneousUploads: 3 // 并发上传数,默认 3
        // singleFile: true 是否开启单文件上传
        // headers: {
        //   token: "eddd7f17-f352-4d25-8bd6-541ef72e446e"
        // }
      },
      attrs: {
        accept:
          'image/gif,image/png,image/jpg,image/jpeg,image/webp,application/octet-stream,.apk'
      },
      fileStatusText: {
        success: this.$t('common.success'),
        error: this.$t('common.error'),
        uploading: this.$t('material.uploading'),
        paused: this.$t('common.pause'),
        waiting: this.$t('common.wait')
      }
    }
  },
  computed: {
    uploader() {
      return this.$refs.uploader.uploader // 实例化上传对象
    }
  },
  watch: {
    show(val, oldVal) {
      if (val === oldVal) {
        return
      }
      if (val) {
        this.initData()
      }
      this.visible = val
    },
    // 如果内部有新值变化,更新外部的visible
    visible(val, oldVal) {
      if (val === oldVal) {
        return
      }
      this.$emit('update:visible', val)
    }
  },
  created() {
  },
  mounted() {
    this.options.query.token = getCookie('token') // 加载后把vuex中的token赋值给上传组件的options
  },
  methods: {

    // 初始化上传地址问题
    initData() {
      this.options.target = this.uploadUrl
      this.options.query.dirId = this.dirId
    },
    createUUID(file) {
      // 生成文件的UUID作为唯一的标识符
      // file.pause(); // 文件上传先暂停
      const s = []
      const hexDigits = '0123456789abcdef'
      for (let i = 0; i < 36; i++) {
        s[i] = hexDigits.substr(Math.floor(Math.random() * 0x10), 1)
      }
      s[14] = '4' // bits 12-15 of the time_hi_and_version field to 0010
      s[19] = hexDigits.substr((s[19] & 0x3) | 0x8, 1) // bits 6-7 of the clock_seq_hi_and_reserved to 01
      s[8] = s[13] = s[18] = s[23] = '-'

      const uuid = s.join('')
      file.uniqueIdentifier = uuid
      // file.resume(); // 文件继续上传
    },
    onFileAdded(file) {
      // 选择文件后
      // this.computeMD5(file);
      this.createUUID(file)
      const index = this.MineType.indexOf(file.fileType)
      if (index === -1) {
        // 不在MINE自定义的文件格式类型中,不让上传;
        file.ignored = true
      }
    },
    onFileProgress(rootFile, file, chunk) {
      // 上传中的回调函数
    },
    onFileSuccess(rootFile, file, response, chunk) {
      // 文件上传成功后的回调
      const res = JSON.parse(response)
      // console.log(res);
      if (res.success === 1) {
        const para = {
          data: {
            parentId: this.dirId,
            name: file.name,
            identifier: res.data.identifier,
            thumPic: res.data.t_identifier,
            size: file.size,
            resType: res.data.resType,
            duration: res.data.duration,
            serverCode: res.data.serverCode
          }
        }
        this.urlmethod(this.url.material.add_res, para)
          .then(res => {
          })
          .catch(err => {
            console.log(err)
          })
        // 上传成功
      } else {
        this.$message({
          message: res.errMsg,
          type: 'warning'
        })
      }
      // 服务器自定义的错误(即虽返回200,但是是错误的情况),这种错误是Uploader无法拦截的
      // if (!res.result) {
      //   this.$message({ message: res.message, type: "error" });
      //   //文件状态设为“失败”
      //   this.statusSet(file.id, "failed");
      //   return;
      // }

      // 如果服务端返回需要合并
      // if (res.needMerge) {
      //   // 文件状态设为“合并中”
      //   this.statusSet(file.id, "merging");
      //   api
      //     .mergeSimpleUpload({
      //       tempName: res.tempName,
      //       fileName: file.name,
      //       ...this.params
      //     })
      //     .then(res => {
      //       // 文件合并成功
      //       // Bus.$emit("fileSuccess");
      //       this.statusRemove(file.id);
      //     })
      //     .catch(e => {});
      //   不需要合并
      // } else {
      //   // Bus.$emit("fileSuccess");
      //   console.log("上传成功");
      // }
    },
    onFileError(rootFile, file, response, chunk) {
      // 错误的回调
      console.log(response)
      // this.$message({
      //   message: response,
      //   type: "error"
      // });
    },
    close() {
      // 点击确认关闭上传框
      this.uploader.cancel() // 点击关闭按钮后清空上传对象
      // 这里调用父组件重新获取数据刷新列表的方法,重新获取容量
      this.$parent.initMaterialPage(this.dirId)
      this.visible = false
    }
  }
}
</script>
<style lang="scss" scoped>
.uploader-dialog-wrap {
  .warning-message {
    margin-top: 10px;
    color: #F56C6C;
    vertical-align: middle;
  }
}
</style>
<style lang="scss">
.uploader-dialog-wrap {
  .el-dialog {
    .el-dialog__header {
      // background-color: #42b983;
      .el-dialog__title {
        color: #333;
        font-size: 24px;
      }
      .el-dialog__headerbtn .el-dialog__close {
        color: #333;
      }
    }
  }
  .dialog-width {
    min-width: 45rem !important;
  }
  .uploader-example {
    // 上传组件外壳样式
    // width: 880px;
    padding: 15px;
    // margin: 40px auto 0;
    font-size: 12px;
    // box-shadow: 0 0 10px rgba(0, 0, 0, 0.4);
  }
  .uploader-example .uploader-btn {
    // 上传按钮样式
    background-color: #409eff;
    color: #fff;
    border-color: #409eff;
    padding: 7px 13px;
    border-radius: 3px;
    margin-bottom: 10px;
  }
  .uploader-example .uploader-btn:hover {
    // 按钮hover样式
    background-color: #66b1ff;
    border-color: #66b1ff;
  }
  .el-button--danger {
    background-color: #409EFF;
    border-color: #409EFF;
    &:hover {
      background-color: #66b1ff;
    border-color: #66b1ff;
    }
  }
  .uploader-example .uploader-list {
    // 上传文件列表样式
    max-height: 440px;
    overflow: auto;
    overflow-x: hidden;
    overflow-y: auto;
  }
  // 覆盖掉默认的文件图标样式
  .uploader-file-icon[icon='image']:before {
    content: '';
  }
  .uploader-file-icon[icon='image'] {
    background: url(../../../assets/image-icon.png);
  }
  .uploader-file-icon[icon='document']:before {
    content: '';
  }
  .uploader-file-icon[icon='document'] {
    background: url(../../../assets/text-icon.png);
  }
  .el-dialog__body {
    padding: 5px;
  }
}
</style>

player.vue

<template>
  <div class="video-container-wrap">
    <div class="player">
      <video-player
        ref="videoPlayer"
        :playsinline="false"
        :options="playerOptions"
        class="video-player vjs-custom-skin"
        @play="onPlayerPlay($event)"
        @pause="onPlayerPause($event)"
        @statechanged="playerStateChanged($event)"
      ></video-player>
    </div>
  </div>
</template>
<script>
// 引入样式
import { videoPlayer } from 'vue-video-player'
import 'video.js/dist/video-js.css'
export default {
  components: {
    videoPlayer
  },
  props: {
    videoUrl: { type: String, default: '' },
    state: { type: Boolean, default: false },
    poster: { type: String, default: '' }
  },
  data() {
    return {
      playerOptions: {
        autoplay: false, // 如果true,浏览器准备好时开始回放。
        muted: false, // 默认情况下将会消除任何音频。
        loop: false, // 导致视频一结束就重新开始。
        preload: 'auto', // 建议浏览器在<video>加载元素后是否应该开始下载视频数据。auto浏览器选择最佳行为,立即开始加载视频(如果浏览器支持)
        language: 'en',
        aspectRatio: '16:9', // 将播放器置于流畅模式,并在计算播放器的动态大小时使用该值。值应该代表一个比例 - 用冒号分隔的两个数字(例如"16:9"或"4:3")
        fluid: true, // 当true时,Video.js player将拥有流体大小。换句话说,它将按比例缩放以适应其容器。
        sources: [
          {
            type: 'video/mp4',
            src: this.videoUrl // 你的m3u8地址(必填)
          }
        ],
        poster: this.poster,
        // poster: 'https://surmon-china.github.io/vue-quill-editor/static/images/surmon-3.jpg', // 你的封面地址
        width: document.documentElement.clientWidth,
        notSupportedMessage: this.$t('material.noPlay') // 允许覆盖Video.js无法播放媒体源时显示的默认信息。
      }
    }
  },
  computed: {
    player() {
      return this.$refs.videoPlayer.player
    }
  },
  watch: {
    // 更改视频源 videoUrl从弹出框组件传值
    videoUrl(val, oldVal) {
      if (val === oldVal) {
        return
      }
      if (val !== '') {
        this.playerOptions.sources[0].src = val
      }
    },

    // 弹出框关闭后暂停 否则一直在播放 state从弹出框组件传值
    state(val, oldVal) {
      if (val === oldVal) {
        return
      }
      this.$refs.videoPlayer.player.pause()
    },
    poster(val, oldVal) {
      // 检测它的变化,来改变视频封面
      if (val === oldVal) {
        return
      }
      this.playerOptions.poster = val
    }
  },
  methods: {
    onPlayerPlay(player) {},
    onPlayerPause(player) {},
    playerStateChanged(player) {}
  }
}
</script>
<style lang="scss">
.video-container-wrap {
  .player {
    .video-js .vjs-big-play-button {
      /*
      播放按钮换成圆形
     */
      height: 2em;
      width: 2em;
      line-height: 2em;
      border-radius: 1em;
      top: 50%;
      left: 50%;
      transform: translate(-50%, -50%);
    }
  }
}
</style>

movefile-dialog.vue

<!-- 素材管理移动文件功能弹出页面 -->
<template>
  <div class="movefile-dialog-wrap">
    <el-dialog
      :title="title"
      :visible.sync="visible"
      :show="show"
      custom-class="dialog-width"
      @close="$emit('update:show', false)"
    >
      <el-tree
        ref="tree"
        :data="folderListData"
        :props="defaultProps"
        :check-strictly="true"
        show-checkbox
        node-key="id"
        @check-change="orgCheckChange"
      ></el-tree>
      <div slot="footer" class="dialog-footer">
        <el-button size="medium" @click="handleClose">{{ $t('common.cancel') }}</el-button>
        <el-button :loading="move_loading" type="primary" size="medium" @click="handleMoveFile">{{ $t('common.confirm') }}</el-button>
      </div>
    </el-dialog>
  </div>
</template>

<script>
export default {
  filters: {},
  props: {
    ids: {
      // 当前点击需要移动操作的文件id数组集合
      type: Array,
      default: function() {
        return []
      }
    },
    parentId: {
      // 当前移动操作文件所属的文件夹id
      type: Number,
      default: 0
    },
    show: {
      type: Boolean,
      default: false
    }
  },
  data() {
    return {
      move_loading: false,
      visible: this.show,
      title: this.$t('material.moveTo'),
      dialogLoading: false,
      folderListData: [], // 文件夹列表数据
      defaultProps: {
        children: 'children',
        label: 'name'
      },
      selectOrg: {
        orgsid: []
      }
    }
  },
  computed: {},
  watch: {
    show(val, oldVal) {
      if (val === oldVal) {
        return
      }
      this.visible = val
      this.handleFechtUserFolderList() // 每次打开对话框,都去重新获取文件夹列表接口
    },
    // 如果内部有新值变化,更新外部的visible
    visible(val, oldVal) {
      if (val === oldVal) {
        return
      }
      this.$emit('update:visible', val)
    },
    parentId(val, oldVal) {
      if (val === oldVal) {
        return
      }
      // 监测父组件那边传过来的上传文件夹id的变化, 如果有改变,就更新options中的目标上传id
      this.parentId = val
    }
  },
  created() {
  },
  mounted() {},
  methods: {
    handleFechtUserFolderList() {
      // 处理获取文件夹列表
      this.loading = true
      this.urlmethod(this.url.material.selectDir_res, null)
        .then(res => {
          this.folderListData = []
          this.folderListData.push(res.data)
          this.loading = false
        })
        .catch(err => {
          console.log('err', err)
          this.loading = false
        })
    },
    handleMoveFile() {
      // 执行移动文件的操作
      // this.id就是传递过来的需要操作的文件id
      if (this.selectOrg.orgsid.length === 1) {
        this.move_loading = true
        const para = {
          data: {
            ids: this.ids, // 移动文件的id
            parentId: this.selectOrg.orgsid[0] // 移动到的目标文件夹id
          }
        }
        this.urlmethod(this.url.material.remove_res, para)
          .then(() => {
            // 移动文件操作成功
            this.$message({
              type: 'success',
              message: this.$t('common.moveSuccess')
            })
            this.move_loading = false
            this.visible = false
            this.$emit('removeActiveItem', this.id)
            // 这里调用父组件重新获取数据刷新列表的方法
            this.$parent.initMaterialPage(this.parentId)
          })
          .catch(err => {
            this.move_loading = false
            console.log('err', err)
            this.loading = false
          })
      }
    },
    // check-change
    // 节点选中状态发生变化时的回调
    // 共三个参数,依次为:传递给 data 属性的数组中该节点所对应的对象、节点本身是否被选中、节点的子树中是否有被选中的节点
    orgCheckChange(data, checked, indeterminate) {
      // 获取当前选择的id在数组中的索引
      const indexs = this.selectOrg.orgsid.indexOf(data.id)
      // 如果不存在数组中,并且数组中已经有一个id并且checked为true的时候,代表不能再次选择。
      if (indexs < 0 && this.selectOrg.orgsid.length === 1 && checked) {
        // this.$message({
        //   message: this.$t('esell.onlyOneSelect'),
        //   type: 'warning',
        //   showClose: true
        // })
        // 设置已选择的节点为false 很重要

        this.$refs.tree.setCheckedKeys([])
        this.$refs.tree.setChecked(data, true)
        this.selectOrg.orgsid = []
        this.selectOrg.orgsid.push(data.id)
      } else if (this.selectOrg.orgsid.length === 0 && checked) {
        // 发现数组为空 并且是已选择
        // 防止数组有值,首先清空,再push
        this.selectOrg.orgsid = []
        this.selectOrg.orgsid.push(data.id)
      } else if (
        indexs >= 0 &&
        this.selectOrg.orgsid.length === 1 &&
        !checked
      ) {
        // 再次直接进行赋值为空操作
        this.selectOrg.orgsid = []
      }
    },
    handleClose() {
      // 点击取消按钮
      this.visible = false
    }
  }
}
</script>
<style lang="scss">
.movefile-dialog-wrap {
  .el-dialog {
    border-radius: 6px;
    .el-dialog__header {
      border-bottom: 1px solid #d0dfe7;
      border-top-left-radius: 6px;
      border-top-right-radius: 6px;
    }
  }
  .dialog-width {
    min-width: 45rem !important;
  }
}
</style>

 uploader-dialog.vue

<!-- 上传功能弹出页面 -->
<template>
  <div class="uploader-dialog-wrap">
    <el-dialog
      :title="title"
      :visible.sync="visible"
      :show="show"
      :show-close="false"
      :close-on-click-modal="false"
      custom-class="dialog-width"
      @close="$emit('update:show', false)"
    >
      <uploader
        v-if="visible"
        ref="uploader"
        :options="options"
        :file-status-text="fileStatusText"
        :auto-start="true"
        class="uploader-example"
        @file-added="onFileAdded"
        @file-progress="onFileProgress"
        @file-success="onFileSuccess"
        @file-error="onFileError"
      >
        <uploader-unsupport></uploader-unsupport>
        <uploader-btn ref="uploadBtn" :attrs="attrs" class="global-uploader-btn">{{ $t('admanagement.selectFile') }}</uploader-btn>
        <uploader-list></uploader-list>
        <div v-if="selectType==0" class="warning-message"><i class="el-icon-warning" style="color:#E6A23C;font-size:20px;"></i>&nbsp;&nbsp;&nbsp;{{ $t('admanagement.uploadTips') }}</div>
        <div v-if="selectType==1" class="warning-message"><i class="el-icon-warning" style="color:#E6A23C;font-size:20px;"></i>&nbsp;&nbsp;&nbsp;{{ $t('admanagement.uploadTips3') }}</div>
      </uploader>

      <div slot="footer" class="dialog-footer">
        <el-button :title="$t('admanagement.nocompleteWillEmpty')" @click="close">{{ $t('common.close') }}</el-button>
      </div>
    </el-dialog>
  </div>
</template>

<script>
export default {
  filters: {},
  props: {
    dirId: {
      // 上传文件夹id
      type: Number,
      default: 0
    },
    show: {
      type: Boolean,
      default: false
    },
    bigFiles: {
      type: Number,
      default: 0
    },
    exsitFileNum: {
      type: Number,
      default: 0
    },
    uploadUrl: {
      type: String,
      default: '',
      required: true
    },
    selectType: { // 1代表只能选图片
      type: Number,
      default: 0
    }
  },
  data() {
    return {
      isComing: false,
      upload_loading: true,
      addFiles: 0,
      imageVideoList: [],
      visible: this.show,
      title: this.$t('common.upload'),
      token: this.cacheEx.getCookie('token'),
      dialogLoading: false,
      MineType: [
        'image/png',
        'image/jpg',
        'image/jpeg',
        'image/gif',
        'image/webp',
        'video/mp4',
        'video/x-ms-wmv',
        'video/avi',
        'video/quicktime', // mov格式
        'video/3gpp', // 3gp格式
        'video/x-matroska' // mkv格式
      ], // 定义可接受的文件类型
      options: {
        target: '/', //  目标上传 URL
        testChunks: true, // 是否开启服务器分片校验,是否测试每个块是否在服务端已经上传了,主要用来实现秒传、跨浏览器上传等,默认 true
        chunkSize: this.config.uploader_chunk_size, // 分块时按照该值来分10kb
        fileParameterName: 'file', // 上传文件时文件的参数名,默认 file
        maxChunkRetries: 3, // 最大自动失败重试上传次数
        query: {
          token: '', // 从vuex中拿到的token
          dirId: this.dirId, // 上传目标文件夹
          refCount: 0
        },
        checkChunkUploadedByResponse: function(chunk, message) {
          // 服务器分片校验函数,秒传及断点续传基础,用于根据 XHR 响应内容检测每个块是否上传成功了
          // let objMessage = JSON.parse(message);
          // if (objMessage.skipUpload) {
          //   return true;
          // }
          // return (objMessage.uploaded || []).indexOf(chunk.offset + 1) >= 0;
        },
        simultaneousUploads: 3 // 并发上传数,默认 3
        // singleFile: true 是否开启单文件上传
        // headers: {
        //   token: "eddd7f17-f352-4d25-8bd6-541ef72e446e"
        // }
      },
      attrs: {
        accept:
          'image/gif,image/png,image/jpg,image/jpeg,image/webp,.mp4,.mkv,.wmv,.avi,.mov,.3gp'
      },
      fileStatusText: {
        success: this.$t('accountManage.sucess'),
        error: this.$t('common.error'),
        uploading: this.$t('admanagement.uploading'),
        paused: this.$t('admanagement.pause'),
        waiting: this.$t('admanagement.wait')
      }
    }
  },
  computed: {
    uploader() {
      return this.$refs.uploader.uploader // 实例化上传对象
    }
  },
  watch: {
    show(val, oldVal) {
      if (val === oldVal) {
        return
      }
      if (val) {
        this.addFiles = 0
        if (this.selectType == 1) {
          this.MineType = [
            'image/png',
            'image/jpg',
            'image/jpeg',
            'image/gif',
            'image/webp'
          ]
        }
        this.initData()
      }
      this.visible = val
    },
    // 如果内部有新值变化,更新外部的visible
    visible(val, oldVal) {
      if (val === oldVal) {
        return
      }
      this.$emit('update:visible', val)
    }
  },
  created() {
  },
  mounted() {
    this.options.query.token = this.token // 加载后把vuex中的token赋值给上传组件的options
  },
  methods: {

    // 初始化上传地址问题
    initData() {
      this.imageVideoList = []
      this.options.target = this.uploadUrl
      this.options.query.dirId = this.dirId
    },
    createUUID(file) {
      // 生成文件的UUID作为唯一的标识符
      // file.pause(); // 文件上传先暂停
      const s = []
      const hexDigits = '0123456789abcdef'
      for (let i = 0; i < 36; i++) {
        s[i] = hexDigits.substr(Math.floor(Math.random() * 0x10), 1)
      }
      s[14] = '4' // bits 12-15 of the time_hi_and_version field to 0010
      s[19] = hexDigits.substr((s[19] & 0x3) | 0x8, 1) // bits 6-7 of the clock_seq_hi_and_reserved to 01
      s[8] = s[13] = s[18] = s[23] = '-'

      const uuid = s.join('')
      file.uniqueIdentifier = uuid
      // file.resume(); // 文件继续上传
    },
    onFileAdded(file) {
      if (this.exsitFileNum + (++this.addFiles) > this.bigFiles) {
        file.ignored = true
        if (!this.isComing) {
          this.$message({
            message: this.$t('admanagement.nomorethan5') + (this.bigFiles - this.exsitFileNum) + this.$t('admanagement.per'),
            type: 'warning'
          })
          this.isComing = true
        }
        return
      }
      // 选择文件后
      // this.computeMD5(file);
      this.createUUID(file)
      const index = this.MineType.indexOf(file.fileType)
      if (index === -1) {
        // 不在MINE自定义的文件格式类型中,不让上传;
        file.ignored = true
      }
      if (file.size > 1024 * 1024 * 1024 * 1024) { // 5 * 1024 * 1024 * 1000
        file.ignored = true
        this.$message({
          message: this.$t('admanagement.uploadTips2'),
          type: 'warning'
        })
      }
    },
    onFileProgress(rootFile, file, chunk) {
      // 上传中的回调函数
    },
    onFileSuccess(rootFile, file, response, chunk) {
      // 文件上传成功后的回调
      const res = JSON.parse(response)
      // console.log(res);
      if (res.success === 1) {
        res.data.fileName = file.name
        this.imageVideoList.push(res.data)
        // 上传成功
      } else {
        this.$message({
          message: res.errMsg,
          type: 'warning'
        })
      }
      // 服务器自定义的错误(即虽返回200,但是是错误的情况),这种错误是Uploader无法拦截的
      // if (!res.result) {
      //   this.$message({ message: res.message, type: "error" });
      //   //文件状态设为“失败”
      //   this.statusSet(file.id, "failed");
      //   return;
      // }

      // 如果服务端返回需要合并
      // if (res.needMerge) {
      //   // 文件状态设为“合并中”
      //   this.statusSet(file.id, "merging");
      //   api
      //     .mergeSimpleUpload({
      //       tempName: res.tempName,
      //       fileName: file.name,
      //       ...this.params
      //     })
      //     .then(res => {
      //       // 文件合并成功
      //       // Bus.$emit("fileSuccess");
      //       this.statusRemove(file.id);
      //     })
      //     .catch(e => {});
      //   不需要合并
      // } else {
      //   // Bus.$emit("fileSuccess");
      //   console.log("上传成功");
      // }
    },
    onFileError(rootFile, file, response, chunk) {
      // 错误的回调
      console.log(response)
      // this.$message({
      //   message: response,
      //   type: "error"
      // });
    },
    close() {
      // 点击确认关闭上传框
      this.uploader.cancel() // 点击关闭按钮后清空上传对象
      this.$emit('adPackageImaListChosed', this.imageVideoList)
      this.imageVideoList = []
      this.visible = false
    }
  }
}
</script>
<style lang="scss" scoped>
.uploader-dialog-wrap {
  .warning-message {
    margin-top: 10px;
    color: #F56C6C;
    vertical-align: middle;
  }
}
</style>
<style lang="scss">
.uploader-dialog-wrap {
  .el-dialog {
    .el-dialog__header {
      // background-color: #42b983;
      .el-dialog__title {
        color: #333;
        font-size: 24px;
      }
      .el-dialog__headerbtn .el-dialog__close {
        color: #333;
      }
    }
  }
  .dialog-width {
    min-width: 45rem !important;
  }
  .uploader-example {
    // 上传组件外壳样式
    // width: 880px;
    padding: 15px;
    // margin: 40px auto 0;
    font-size: 12px;
    // box-shadow: 0 0 10px rgba(0, 0, 0, 0.4);
  }
  .uploader-example .uploader-btn {
    // 上传按钮样式
    background-color: #409eff;
    color: #fff;
    border-color: #409eff;
    padding: 7px 13px;
    border-radius: 3px;
    margin-bottom: 10px;
  }
  .uploader-example .uploader-btn:hover {
    // 按钮hover样式
    background-color: #66b1ff;
    border-color: #66b1ff;
  }
  .el-button--danger {
    background-color: #409EFF;
    border-color: #409EFF;
    &:hover {
      background-color: #66b1ff;
    border-color: #66b1ff;
    }
  }
  .uploader-example .uploader-list {
    // 上传文件列表样式
    max-height: 440px;
    overflow: auto;
    overflow-x: hidden;
    overflow-y: auto;
  }
  // 覆盖掉默认的文件图标样式
  .uploader-file-icon[icon='image']:before {
    content: '';
  }
  .uploader-file-icon[icon='image'] {
    background: url(../../assets/image-icon.png);
  }
  .uploader-file-icon[icon='document']:before {
    content: '';
  }
  .uploader-file-icon[icon='document'] {
    background: url(../../assets/text-icon.png);
  }
  .el-dialog__body {
    padding: 5px;
  }
}
</style>

index.vue

<template>
  <div class="video-container-wrap">
    <div class="player" >
      <video-player
        ref="videoPlayer"
        :playsinline="false"
        :options="playerOptions"
        class="video-player vjs-custom-skin"
        @play="onPlayerPlay($event)"
        @pause="onPlayerPause($event)"
        @statechanged="playerStateChanged($event)"
      ></video-player>
    </div>
  </div>
</template>
<script>
import { videoPlayer } from 'vue-video-player'
import 'video.js/dist/video-js.css'
export default {
  components: {
    videoPlayer
  },
  props: {
    videoUrl: { type: String, default: '' },
    state: { type: Boolean, default: false },
    poster: { type: String, default: '' },
    playType: { type: String, default: 'video/mp4' }
  },
  data() {
    return {
      playerOptions: {
        autoplay: false, // 如果true,浏览器准备好时开始回放。
        muted: false, // 默认情况下将会消除任何音频。
        loop: false, // 导致视频一结束就重新开始。
        preload: 'auto', // 建议浏览器在<video>加载元素后是否应该开始下载视频数据。auto浏览器选择最佳行为,立即开始加载视频(如果浏览器支持)
        language: 'en',
        aspectRatio: '16:9', // 将播放器置于流畅模式,并在计算播放器的动态大小时使用该值。值应该代表一个比例 - 用冒号分隔的两个数字(例如"16:9"或"4:3")
        fluid: true, // 当true时,Video.js player将拥有流体大小。换句话说,它将按比例缩放以适应其容器。
        sources: [
          {
            type: this.playType,
            src: this.videoUrl // 你的m3u8地址(必填)
          }
        ],
        poster: this.poster,
        width: document.documentElement.clientWidth,
        notSupportedMessage: this.$t('esell.noPlay') // 允许覆盖Video.js无法播放媒体源时显示的默认信息。
      }
    }
  },
  computed: {
    player() {
      return this.$refs.videoPlayer.player
    }
  },
  watch: {
    // 更改视频源 videoUrl从弹出框组件传值
    videoUrl(val, oldVal) {
      if (val === oldVal) {
        return
      }
      if (val !== '') {
        this.playerOptions.sources[0].src = val
      }
    },

    // 弹出框关闭后暂停 否则一直在播放 state从弹出框组件传值
    state(val, oldVal) {
      if (val === oldVal) {
        return
      }
      this.$refs.videoPlayer.player.pause()
    },
    poster(val, oldVal) {
      // 检测它的变化,来改变视频封面
      if (val === oldVal) {
        return
      }
      this.playerOptions.poster = val
    }
  },
  methods: {
    onPlayerPlay(player) {},
    onPlayerPause(player) {},
    playerStateChanged(player) {}
  }
}
</script>
<style lang="scss">
.video-container-wrap {
  .player {
    .video-js .vjs-big-play-button {
      /*
      播放按钮换成圆形
     */
      height: 2em;
      width: 2em;
      line-height: 2em;
      border-radius: 1em;
      top: 50%;
      left: 50%;
      transform: translate(-50%, -50%);
    }
  }
}
</style>

  • 15
    点赞
  • 76
    收藏
    觉得还不错? 一键收藏
  • 12
    评论
可以通过以下步骤实现: 1. 安装阿里云 Node.js SDK。 在 Node.js 环境下,可以使用阿里云 Node.js SDK 实现阿里云服务的访问。可以使用以下命令安装: ```bash npm install --save @alicloud/pop-core ``` 2. 在阿里云上创建 OSS 存储空间。 可以在阿里云 OSS 控制台中创建一个存储空间,用于存储上传的文件。 3. 在 Vue.js 中使用 Electron 的文件选择器选择文件夹。 可以使用 Electron 的 dialog 模块提供的 showOpenDialog 方法选择文件夹。具体可以参考以下代码: ```javascript const { dialog } = require('electron') dialog.showOpenDialog({ properties: ['openDirectory'] }).then(result => { console.log(result.filePaths) }).catch(err => { console.log(err) }) ``` 4. 遍历文件夹内的文件并上传到阿里云 OSS。 可以使用 Node.js 的 fs 模块遍历文件夹内的文件,并使用阿里云 Node.js SDK 将文件上传到 OSS 中。具体可以参考以下代码: ```javascript const fs = require('fs') const path = require('path') const Core = require('@alicloud/pop-core') const client = new Core({ accessKeyId: '<your-access-key-id>', accessKeySecret: '<your-access-key-secret>', endpoint: '<your-oss-endpoint>', apiVersion: '2013-10-15' }) // 遍历文件夹内的文件并上传到阿里云 OSS function uploadFilesToOSS(folderPath, bucketName) { fs.readdir(folderPath, (err, files) => { if (err) { console.log(err) return } files.forEach(file => { const filePath = path.join(folderPath, file) fs.stat(filePath, (err, stats) => { if (err) { console.log(err) return } if (stats.isFile()) { const fileName = path.basename(filePath) // 上传文件到阿里云 OSS client.request('PutObject', { Bucket: bucketName, Key: fileName, Body: fs.createReadStream(filePath) }, (err, result) => { if (err) { console.log(err) return } console.log(`Uploaded ${fileName} to ${bucketName}`) }) } else if (stats.isDirectory()) { uploadFilesToOSS(filePath, bucketName) } }) }) }) } ``` 在以上代码中,`<your-access-key-id>` 和 `<your-access-key-secret>` 需要替换为你的阿里云访问密钥 ID 和访问密钥 Secret,`<your-oss-endpoint>` 需要替换为你的 OSS 服务的 endpoint,`uploadFilesToOSS` 方法需要传入文件夹路径和存储空间名称。 5. 在 Vue.js 中使用 Electron 的 IPC 模块与主进程通信。 在 Vue.js 中,可以使用 Electron 的 IPC 模块与主进程通信,将文件夹路径和存储空间名称传递给主进程。具体可以参考以下代码: ```javascript const { ipcRenderer } = require('electron') ipcRenderer.send('upload-files-to-oss', folderPath, bucketName) ``` 在以上代码中,`'upload-files-to-oss'` 是自定义的事件名称,`folderPath` 和 `bucketName` 分别是文件夹路径和存储空间名称。 6. 在 Electron 的主进程中监听 IPC 事件并调用上传方法。 在 Electron 的主进程中,可以监听 `'upload-files-to-oss'` 事件,并调用上传方法。具体可以参考以下代码: ```javascript const { ipcMain } = require('electron') ipcMain.on('upload-files-to-oss', (event, folderPath, bucketName) => { uploadFilesToOSS(folderPath, bucketName) }) ``` 至此,通过 electron 和 vueWindows文件夹文件传到阿里云服务器的流程就完成了。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值