实用element上传视频,并且自定义封面

vue-core-video-player

视频组件:

<template>
  <div>
    <el-dialog :visible.sync="isShow" title="设置封面" :close-on-click-modal="false" width="750px" class="dialog-dfl" :before-close="beforeClose">
      <div>
        <el-tabs v-model="activeName" class="demo-tabs">
          <el-tab-pane label="封面截取" name="one">
            <div class=" nochose" v-if="!file">
              暂不支持预览选帧
              <div>重新上传本地视频,再截选一帧;或直接<span @click="uploadimg">上传图片</span></div>
            </div>
            <div class="conts" v-else style="height: 403px" v-loading="loading" element-loading-background="#fff" element-loading-text="解析中">
              <div v-show="!loading">
                <div class="look_img">
                  <img :src="cover.ossImgPath + imgForm.ossid" alt="" :key="imgForm.ossid" />
                </div>
                <div class="imgs_list_box">
                  <div class="imgs_list">
                    <div v-for="(item, index) in imgForm.img_list" :key="index" class="imgs_item">
                      <img :src="item" alt="" />
                    </div>
                    <div class="slider-dfl">
                      <span style="color:transparent">{{ imgForm.videoTime }} {{ sliderVal }}</span>
                      <el-slider v-model="sliderVal" :step="0.01" :min="0" :max="imgForm.videoTime" placement="bottom" :format-tooltip="formatTooltip" @change="sliderChange" />
                    </div>
                  </div>
                </div>
              </div>
            </div>
          </el-tab-pane>
          <el-tab-pane label="本地上传" name="two">
            <div v-loading="false" element-loading-background="#fff" element-loading-text="解析中" style="height: 403px" class="cover-upload">
              <div v-if="true" class="conts_right">
                <img class="detailBg" :src="cover.ossImgPath + coverImg.ossid" alt="" v-if="coverImg.ossid" />
                <el-upload :action="cover.coverUploadUrl" class="upload-img" :headers="{ Authorization: `Bearer ${$store.getters.token}` }" :show-file-list="false" :data="cover.uploadData" name="files" ref="coverImg" key="coverImg" :before-upload="beforeAvatarUpload" :on-success="(response, file, fileList) => handlecoverAvatarSuccess(response, file, fileList)">
                  <div class="noimg-upload" v-if="!coverImg.ossid">
                    <img src="@/assets/img/upload.png" alt="" class="upload-icon" />
                    <el-button class="fot-btn upload-btn">
                      上传封面
                    </el-button>
                  </div>
                  <div v-else class="re-upload"><img src="@/assets/img/reupload.png" alt="" class="reupload-icon" />重新上传</div>
                </el-upload>

                <!-- <div class="el-upload__tip">图片上传尺寸建议370*370,文件小于400K,仅支持JPG,PNG</div> -->
              </div>
            </div>
          </el-tab-pane>
        </el-tabs>
      </div>
      <template #footer>
        <div class="dialog-footer">
          <el-button class="calcle-btn fot-btn" @click="beforeClose">
            取消
          </el-button>
          <el-button class="submit-btn fot-btn" @click="confirmCover">
            确认
          </el-button>

          <!-- <div v-show="activeName == 'two'" class="bottom_btn">
            <div class="r">
              <el-button color="#fe3355" @click="confirmCoverTwo">
                确认封面
              </el-button>
            </div>
          </div> -->
        </div>
      </template>
    </el-dialog>
  </div>
</template>
<script>
// import { VueCropper } from 'vue-cropper'
import mixins from './getossid'
export default {
  //   components: { VueCropper },
  props: {
    isShow: {
      type: Boolean,
      default: () => true
    }
    //传过来的视频文件
    // file: {
    //   type: Object,
    //   default: () => ({})
    // }
  },
  mixins: [mixins],
  data() {
    return {
      loading: true,
      file: null,
      activeName: 'one',
      sliderVal: 0,
      sliderVal2: 20,
      cropperRef: {},
      coverImg: {}, //上传封面
      imgForm: {
        url: '', //封面预览地址
        urlTwo: '', //封面预览地址
        blob: {}, //封面blob对象
        img_list: [], //底部预览条图片数组
        videoTime: 0, //视频时长
        ossid: '',
        oldVideoFile: {} //旧的视频文件
      },
      cover: {
        coverUploadUrl: process.env.BASE_API + '/ykb-oss/oss/uploadFdfs',
        ossImgPath: process.env.BASE_API + '/ykb-oss/oss/view?ossId=',

        uploadData: {
          param: JSON.stringify({
            appid: 'oss7wqc3k',
            appsecret: 'ok601gkg',
            project: 'lms',
            opDocType: ''
          })
        }
      },
      option: {
        img: '',
        outputSize: 1, // 裁剪生成图片的质量
        outputType: 'jpeg', // 裁剪生成图片的格式 jpeg, png, webp
        info: true, // 裁剪框的大小信息
        canScale: true, // 图片是否允许滚轮缩放
        autoCrop: true, // 是否默认生成截图框
        autoCropWidth: 200, // 默认生成截图框宽度
        autoCropHeight: 200, // 默认生成截图框高度
        fixedBox: false, // 固定截图框大小 不允许改变
        fixed: false, // 是否开启截图框宽高固定比例
        fixedNumber: [1, 1], // 截图框的宽高比例 [ 宽度 , 高度 ]
        canMove: true, // 上传图片是否可以移动
        canMoveBox: true, // 截图框能否拖动
        original: false, // 上传图片按照原始比例渲染
        centerBox: false, // 截图框是否被限制在图片里面
        infoTrue: true, // true 为展示真实输出图片宽高 false 展示看到的截图框宽高
        full: false, // 是否输出原图比例的截图
        enlarge: '1', // 图片根据截图框输出比例倍数
        mode: 'contain' // 图片默认渲染方式 contain , cover, 100px, 100% auto,
      },
      duration: '',
      url: '',
      cropperForm: {
        imgLookUrl: '', //裁剪实时预览
        bgColor: '#fff', //裁剪图片底色
        loadings: true
      }
    }
  },
  watch: {
    file(newVal, oldVal) {
      // if (this.isShow == true && this.file.type) {
      //   if (this.file.type.includes('video')) {
      //     //通过验证
      //     if (newVal.name != this.imgForm.oldVideoFile.name && newVal.size != this.imgForm.oldVideoFile.size) {
      //       //是否已经选择了这个视频文件 选择了相同的文件就不用初始化了 如果不同就初始化
      //       this.loading = true
      //       this.cropperForm.loadings = true
      //       this.activeName = 'one'
      //       this.init()
      //     }
      //   } else {
      //     //未通过验证
      //     this.$message({
      //       message: '请选择视频格式的文件',
      //       grouping: true,
      //       type: 'error'
      //     })
      //     this.$emit('closeDialog', false)
      //   }
      // } else if (this.isShow == true) {
      //   //未通过验证
      //   this.$message({
      //     message: '请选择视频格式的文件',
      //     grouping: true,
      //     type: 'error'
      //   })
      //   this.$emit('closeDialog', false)
      // }
    },
    isShow(newVal, oldVal) {
      this.sliderVal = 0.1
      this.coverImg = {}
      console.log(this.file)
      // this.sliderChange(0.1)
      //   if (this.isShow == true && this.file.type) {
      //     if (this.file.type.includes('video')) {
      //       //通过验证
      //       if (newVal[0].name != this.imgForm.oldVideoFile.name && newVal[0].size != this.imgForm.oldVideoFile.size) {
      //         //是否已经选择了这个视频文件 选择了相同的文件就不用初始化了 如果不同就初始化
      //         this.loading = true
      //         this.cropperForm.loadings = true
      //         this.activeName = 'one'
      //         this.init()
      //       }
      //     } else {
      //       //未通过验证
      //       this.$message({
      //         message: '请选择视频格式的文件',
      //         grouping: true,
      //         type: 'error'
      //       })
      //       this.$emit('closeDialog', false)
      //     }
      //   } else if (this.isShow == true) {
      //     //未通过验证
      //     this.$message({
      //       message: '请选择视频格式的文件',
      //       grouping: true,
      //       type: 'error'
      //     })
      //     this.$emit('closeDialog', false)
      //   }
    }
  },
  created() {},
  methods: {
    uploadimg() {
      this.activeName = 'two'
      this.$refs['coverImg'].$refs['upload-inner'].handleClick()
    },
    changeVideo() {
      //关闭弹窗并且触发上传本地视频
      this.$emit('closeDialog', false)
    },
    changeFile(newVal, duration, url) {
      this.file = newVal
      this.duration = duration
      this.url = url
      //  {
      //   uid: 1698891870169,
      //   lastModified: 1692350189838,
      //   name: 'b.mp4',
      //   size: 15250255,
      //   type: 'video/mp4',
      //   webkitRelativePath: ''
      // }

      console.log('changeFile', newVal, duration, url)
      if (this.file.type) {
        if (this.file.type.includes('video')) {
          //通过验证
          if (newVal.name != this.imgForm.oldVideoFile.name && newVal.size != this.imgForm.oldVideoFile.size) {
            //是否已经选择了这个视频文件 选择了相同的文件就不用初始化了 如果不同就初始化
            this.loading = true
            this.cropperForm.loadings = true
            this.activeName = 'one'
            this.init()
          }
        } else {
          //未通过验证
          this.$message({
            message: '请选择视频格式的文件',
            grouping: true,
            type: 'error'
          })
          this.$emit('closeDialog', false)
        }
      } else if (this.isShow == true) {
        //未通过验证
        this.$message({
          message: '请选择视频格式的文件',
          grouping: true,
          type: 'error'
        })
        this.$emit('closeDialog', false)
      }
    },
    init() {
      this.imgForm.url = ''
      this.imgForm.ossid = ''
      this.imgForm.blob = {}
      this.imgForm.img_list = []
      this.imgForm.videoTime = 0
      this.imgForm.oldVideoFile = this.file
      let reader = new FileReader()
      let that = this
      //获取视频时长
      reader.onload = function(e) {
        let video = document.createElement('video')
        // @ts-ignore
        video.src = e.target.result || that.url
        video.addEventListener('loadedmetadata', async function() {
          // 这里先看900宽度能放几张图片
          const img_src = await that.captureFrame(that.file, Math.floor(video.duration || that.duration))
          var img_load = document.createElement('img')
          img_load.setAttribute('src', img_src.url)
          img_load.onload = function() {
            var aspectRatio = img_load.naturalWidth / img_load.naturalHeight
            // option.fixedNumber[0] =
            //   parseFloat((img_load.width / img_load.height).toFixed(2)) - 0.2;
            that.option.fixedNumber[0] = img_load.width / img_load.height
            var width = 50 * aspectRatio
            let count = Math.floor(540 / width) // 总宽度为960 看能放几张图片
            let duration = Math.floor(video.duration) //取整
            that.imgForm.videoTime = duration
            var step = Math.floor(duration / (count - 1)) // 步长
            var result = [] // 存储结果的数组
            for (var i = 0; i < count; i++) {
              result.push(i * step)
            }
            if (result[0] == 0) {
              result[0] = 0.1
            }
            result.forEach(async (item, index) => {
              const res = await that.captureFrame(that.file, item)
              if (index == 0) {
                that.imgForm.url = res.url
                that.getImageBase64(res.blob).then(res => {
                  that.getossid(res).then(resid => {
                    that.imgForm.ossid = resid
                  })
                })
                that.imgForm.blob = res.blob
              }
              that.imgForm.img_list.push(res.url)
            })
            that.sliderChange(0.1)
            that.$nextTick(() => {
              setTimeout(() => {
                that.loading = false
              }, 2000)
            })
          }
        })
      }

      // @ts-ignore
      reader.readAsDataURL(this.file)
    },
    //滑块位置改变 更滑上方主封面图
    async sliderChange(val) {
      console.log(val, '-=--===9999')
      let that = this
      const res = await this.captureFrame(this.file, val)
      this.imgForm.url = res.url

      that.getImageBase64(res.blob).then(res1 => {
        that.getossid(res1).then(resid => {
          that.$set(that.imgForm, 'ossid', resid)
        })
      })
      this.imgForm.blob = res.blob
      console.log(this.imgForm)
    },
    // 格式化提示时间
    formatTooltip(val) {
      var timeString = this.convertSeconds(val)
      return timeString
    },
    //关闭模态弹窗
    beforeClose() {
      this.$emit('closeDialog', false)
    },
    //确认封面选择封面
    confirmCover() {
      this.$emit('closeDialog', false)
      if (this.activeName == 'one') {
        this.$emit('confirmImg', {
          url: this.imgForm.url,
          blob: this.imgForm.blob,
          ossid: this.imgForm.ossid
        })
      } else {
        this.$emit('confirmImg', {
          url: this.cover.ossImgPath + this.coverImg.ossid,
          ossid: this.coverImg.ossid
          // blob: this.imgForm.blob
        })
      }
    },
    // 获取视频帧的封面
    captureFrame(videoFile, time = 0) {
      return new Promise(succeed => {
        const video = document.createElement('video')
        video.currentTime = time
        video.muted = true
        video.autoplay = true
        video.oncanplay = async () => {
          const res = await this.drawVideo(video)
          succeed(res)
        }
        video.src = URL.createObjectURL(videoFile)
      })
    },

    // 画视频
    drawVideo(video) {
      return new Promise(res => {
        const cvs = document.createElement('canvas')
        const ctx = cvs.getContext('2d')
        cvs.width = video.videoWidth
        cvs.height = video.videoHeight
        ctx.drawImage(video, 0, 0, cvs.width, cvs.height)
        cvs.toBlob(blob => {
          res({
            blob,
            url: URL.createObjectURL(blob)
          })
        })
      })
    },

    // 秒数换算时间
    convertSeconds(seconds) {
      var hours = parseInt(seconds / 3600)
      var minutes = parseInt(seconds / 60)
      var remainingSeconds = parseInt(seconds % 60) //秒
      var millisecond = Math.floor((seconds % 60) * 10) //毫秒
      var timeString = ''
      if (hours > 0) {
        timeString += hours + ':'
      }
      timeString += minutes + ':' + remainingSeconds
      return timeString
    },
    //封面上传
    beforeAvatarUpload(file) {
      const suffix = file.type === 'image/jpg' || file.type === 'image/png' || file.type === 'image/jpeg'
      const isLt1M = file.size / 1024 / 1024 < 1
      if (!suffix) {
        this.$message.error('只能上传图片!')
        return false
      }
      if (!isLt1M) {
        this.$message.error('上传图片大小不能超过 1MB!')
        return false
      }
    },
    //封面上传
    handlecoverAvatarSuccess(response, file, fileList, index, scope) {
      // console.log(this.$refs['coverImg' + index].uploadFiles, response, file, fileList)
      let data = {
        id: file.id || null,
        fileName: file.name,
        ossid: response.data[0].id,
        fileSize: file.size,
        fileExt: file.ext,
        type: 6,
        duration: file.duration || 0
      }

      this.$set(this, 'coverImg', data)
    },
    //本地封面确定事件
    confirmCoverTwo() {
      // @ts-ignore
      this.$refs.cropperRef.getCropData(data => {
        const image = new Image()
        image.src = data
        image.onload = () => {
          const canvas = document.createElement('canvas')
          const ctx = canvas.getContext('2d')
          canvas.width = image.width
          canvas.height = image.height
          ctx.drawImage(image, 0, 0, canvas.width, canvas.height)
          canvas.toBlob(blob => {
            this.$emit('closeDialog', false)
            this.$emit('confirmImg', {
              url: URL.createObjectURL(blob),
              blob
            })
          })
        }
      })
    }
  }
}
</script>

<style lang="scss" scoped>
.conts {
  height: 403px;
  box-sizing: border-box;
  padding: 0 20px;
  display: flex;
  align-items: center;
  justify-content: center;
  .look_img {
    display: flex;
    justify-content: center;
    margin-bottom: 25px;
    img {
      width: 540px !important;
      height: 309px !important;
      border-radius: 4px;
    }
  }

  .imgs_list_box {
    display: flex;
    align-items: center;
    justify-content: center;
  }
  .imgs_list {
    position: relative;
    width: auto !important;
    height: 91px;
    margin: 0 auto;
    display: flex;
    align-items: center;
    justify-content: center;
    .imgs_item {
      img {
        width: auto !important;
        height: 60px !important;
      }
    }
  }
}
.nochose {
  text-align: center;
  height: 403px;
  display: flex;
  align-items: center;
  justify-content: center;
  flex-direction: column;
  div {
    margin-top: 10px;
  }
  span {
    color: #00aeec;
    cursor: pointer;
  }
}
.slider-dfl {
  position: absolute;
  left: 0;
  top: 0;
  width: 100%;
}
.cover-upload {
  display: flex;
  // align-items: center;
  justify-content: center;
}

.conts_right {
  height: 309px;
  width: 540px;
  box-sizing: border-box;
  display: flex;
  align-items: center;
  justify-content: center;
  background: #f7f7f7ff;
  border: 1px dashed #d9d9d9;
  position: relative;
  .l {
    flex: 1;
    height: 100%;
    border: 1px solid #e4e7ed;
    margin-right: 20px;
    display: flex;
    align-items: center;
    justify-content: center;
    overflow: hidden;
  }
  .r {
    flex: 1;
    height: 100%;
    border: 1px solid #e4e7ed;
  }
  .upload-icon {
    width: 40px;
    height: 40px;
    margin-bottom: 24px;
  }
  .noimg-upload {
    display: flex;
    align-items: center;
    justify-content: center;
    flex-direction: column;
  }
  .upload-btn {
    background: #f5b051ff;
    color: #ffffffff;
    border: 1px solid #f5b051ff;
    &:hover {
      opacity: 0.7;
    }
  }
  .detailBg {
    width: 100%;
    height: 100%;
  }
  .re-upload {
    position: absolute;
    bottom: -30px;
    width: 200px;
    color: #f03e40ff;
    text-align: left;
    right: calc(50% - 20px);
    left: calc(50% - 20px);
    .reupload-icon {
      width: 13px;
      height: 13px;
      position: relative;
      top: 1px;
      margin-right: 4px;
    }
  }
}
.bottom_btn {
  display: flex;
  align-items: center;
  justify-content: space-between;
  .l {
    display: flex;
    align-items: center;
  }
}
#img-file {
  position: absolute;
  top: 0;
  left: 0;
  width: 100%;
  height: 100%;
  opacity: 0;
  cursor: pointer;
}
.dialog-footer {
  text-align: center;
}
.fot-btn {
  width: 110px;
}
.calcle-btn {
  color: #848a90ff;
  border: 1px solid #dcdfe6ff;
  &:hover {
    opacity: 0.7;
    background-color: #f5f7faff;
  }
}
.submit-btn {
  color: #ffffffff;
  background: #f03e40ff;
  border: 1px solid #f03e40ff;
  &:hover {
    opacity: 0.7;
  }
}
/deep/ .el-dialog__headerbtn .el-dialog__close {
  color: #1c1f21ff;
  font-weight: 600;
}
/deep/ button.el-dialog__headerbtn {
  background: #f5f7faff;
  padding: 4px 2px 2px 2px;
}
/deep/ span.el-dialog__title {
  color: #1c1f21ff;
  font-size: 20px;
  font-weight: 400;
}
/deep/ .el-dialog__header {
  border-bottom: 1px solid #f3f5f6ff;
}
/deep/ .dialog-dfl .el-dialog__body {
  padding-top: 24px;
  padding-bottom: 0px;
}
/deep/ .dialog-dfl .el-tabs__nav-wrap.is-top {
  text-align: center;
}
/deep/ .dialog-dfl .el-tabs__nav-scroll {
  display: inline-block;
}
/deep/ .dialog-dfl .el-tabs__item.is-top.is-active {
  color: #f03e40ff;
  margin-bottom: 7px;
}
/deep/ .dialog-dfl .el-tabs__item.is-top {
  font-size: 18px;
  height: 21px;
  line-height: 21px;
  color: #1c1f21;
}

/deep/ .dialog-dfl .el-slider__button {
  position: relative !important;
  width: 18px !important;
  height: 65px !important;
  border: 2px solid #fe3355;
  border-radius: 4px;
  transform: translateY(15%);
}
/deep/ .dialog-dfl .el-tabs__nav-wrap::after {
  background: transparent;
}
/deep/ .dialog-dfl .el-slider__button::after {
  position: absolute;
  left: 5px;
  top: 50%;
  transform: translateY(-50%);
  content: '';
  background-color: #ebebeb;
  border-radius: 1.5px;
  height: 34px;
  width: 3px;
}
/deep/ .dialog-dfl .el-slider__button::before {
  position: absolute;
  right: 5px;
  top: 50%;
  transform: translateY(-50%);
  content: '';
  background-color: #ebebeb;
  border-radius: 1.5px;
  height: 34px;
  width: 3px;
}
/deep/ .dialog-dfl .el-slider__runway {
  background-color: transparent !important;
  height: 66px !important;
  // top: -21px;
  margin: 0px;
  width: 99%;
}
/deep/ .el-slider__bar {
  height: 66px !important;
  // transform: translateY(-48%);
  background-color: transparent;
}
/deep/ .dialog-dfl .el-slider {
  height: 100% !important;
}
/deep/ .dialog-dfl .el-loading-spinner .path {
  stroke: #fe3355 !important;
}
/deep/ .dialog-dfl .el-loading-spinner .el-loading-text {
  color: #c1c1c1 !important;
  margin-top: 10px;
}
/deep/ .el-slider__button {
  height: 74px;
}
</style>

将视频的地址获取后端需要的id,模拟文件上传动作

import axios from 'axios'
import ak from '@/utils/common.js'
export default {
  data() {
    return {}
  },
  methods: {
    async getossid(urlData, fileName = new Date().getTime() + 'cover.jpg') {
      //获取提图片的ossid
      let arr = urlData.split(',')
      let mime = arr[0].match(/:(.*?);/)[1]
      let bytes = atob(arr[1]) // 解码base64
      let n = bytes.length
      let ia = new Uint8Array(n)
      while (n--) {
        ia[n] = bytes.charCodeAt(n)
      }
      let file = new File([ia], fileName, { type: mime })

      const url = process.env.BASE_API + '/ykb-oss/oss/uploadFdfs'
      let formData = new FormData()
      formData.append('files', new File([ia], fileName, { type: mime }))
      formData.append('param', JSON.stringify({ appid: 'oss7wqc3k', appsecret: 'ok601gkg', project: 'lms' }))
      const fileInfo = await axios({
        method: 'post',
        url,
        // url: '/api/ykb-oss/oss/mergeChunkFile',
        headers: {
          Authorization: `Bearer ${this.$store.getters.token}`,
          'Content-Type': 'application/x-www-form-urlencoded'
        },
        data: formData
      })
      // this.firstImage = fileInfo.data.data[0].id
      return fileInfo.data.data[0].id
      // return new File([ia], fileName, { type: mime })
    },
    getImageBase64(blob) {
      //blob转换为base64
      return new Promise((resolve, reject) => {
        const reader = new FileReader()
        reader.readAsDataURL(blob)
        reader.onload = () => {
          const base64 = reader.result
          resolve(base64)
        }
        reader.onerror = error => reject(error)
      })
    },
    // 根据ossid获取文件实际的路径
    async getUrlByOssid(ossId) {
      const res = await axios({
        method: 'post',
        headers: {
          Authorization: 'Bearer ' + localStorage.getItem('token')
        },
        url: process.env.BASE_API + '/ykb-oss/oss/getStoragePath',
        data: { ossId: ossId, type: 'S00000002-200040' }
      })
      if (res.data.code !== 8200) {
        if (res.data.message) {
          ak.error(res.data.message)
          return Promise.reject(res.data.message)
        } else {
          ak.error('通讯错误,请联系管理员')
          return Promise.reject('通讯错误,请联系管理员')
        }
      } else {
        return res.data.data
      }
    }
  }
}

运用组件

  <videoCover ref="videoCover" :file="videoForm.file" :is-show="videoForm.comIsShow" @closeDialog="close" @confirmImg="confirmImg"></videoCover>
<script>
import videoCover from './captur.vue'



    //打开弹窗
    openmodal() {
      this.videoForm.comIsShow = true
      if (Object.keys(this.videoForm.file).length != 0) {
        this.$refs.videoCover.changeFile(this.videoForm.file, this.videoForm.duration, this.videoForm.videoUrl)
      }
    },
</script>

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值