vue + elementUI upload组件,前端上传视频到oss视频点播

该博客详细介绍了如何利用阿里云视频点播服务,结合ElementUI组件,实现前端视频上传、获取视频时长、截取视频帧等功能。通过后端接口获取上传凭证和地址,实现视频的分片上传,并展示了上传成功后的处理流程。同时,还提供了上传组件的封装和在表单中的应用实例。
摘要由CSDN通过智能技术生成

 改了需求,视频由原来上传到服务器直接上传到阿里云的视频点播,使用官方推荐的获取地址和视频凭证的方式。结合框架使用的elementUI 库,记录一下。

上传到视频点播大致流程如下:使用elementUI 的上传组件添加视频,通过自定义验证后,调用后端的接口,返回videoI等必要参数,然后调用视频点播的方法。刷新视频凭证也是如此,调用后端接口,返回必要数据。

功能主要有,获取视频时长、获取视频的本地url地址、截取视频帧,上传视频、获取上传进度

因为代码过多不好维护,这部分的代码封装了一个组件,然后引入到表单页面中(父组件)

AddVideo.vue 是子组件

releasevideo.vue 是父组件

AddVideo.vue的源码

<template>
  <el-upload class="videoboxUpload" :action="uploadApi" :show-file-list="false" :auto-upload="false" accept=".mp4,.flv,.mov"
    :on-change="videoChange">
    <div class="upload_div">
      <div style="float: left;">
        <video v-if="videoDataUrl !='' && videoFlag == true" :src="videoDataUrl" class="avatar" controls="controls">您的浏览器不支持视频播放</video>
        <i v-else-if="videoDataUrl =='' && videoFlag == false" class="el-icon-plus avatar-uploader-icon"></i>
        <el-progress v-else-if="videoDataUrl != '' && videoFlag == false" type="circle" :width="110" :percentage="authProgress"></el-progress>
      </div>
      <div class="upload_txt">
        <el-button type="primary" size="mini" class="el-icon-upload el-icon--right">
          <span v-if="videoFlag == true"> 重新上传</span>
          <span v-else-if="videoFlag == false"> 上传视频</span>
        </el-button>
        <br>
        <span>支持视频的类型 mp4</span>
        <br>
        <span v-if="videoFlag == true"> <i class="el-icon-success" style="color:#52C41A;margin-right:10px;">上传成功</i> </span>
        <span v-if="videoFlag == true">时长:{{ totalTime }}</span>
      </div>
      <video style="display: none;" :src="videoDataUrl" class="avatar" controls="controls" autoplay name="media" ref="divVideo"
        crossOrigin='*' @loadeddata="loadedListener">
        <source>
      </video>
    <!-- <span v-for="(item, index) in fileImgs">
        <img :src="item" :id="index" @click="getId($event)" width="200px" height="150px" />
      </span> -->
    </div>

  </el-upload>

</template>
<script>
  import axios from 'axios'
  export default {
    props:["parentThis","setVideoImg"], //传入父组件的方法、属性
    data() {
      return {
        videoFlag: false,
        authProgress: 0,
        uploadApi: '',
        videoDataUrl:'',
        vUrl:'',
        fileImgs:[],
        totalTime: '',
        cover: '',
        uploader: '',
        videoDataForm: {
          userId: '1811802489331712',
          region: '',
          partSize: 1048576, //分片大小
          parallel: 5, //并行上传片数
          retryCount: 3, //网络失败后重新上传次数
          retryDuration: 2, //网络失败后重新上传时间
        },
        videoId: ''
      }
    },
    methods: {
      // 视频文件钩子改变
      videoChange(file, fileList) {
        this.authProgress = 0
        this.videoFlag = false
        console.log(file, '视频改变的钩子')
        // 视频文件上传前的验证
        // console.log(file.size / 1024 / 1024)
        const isLt1G = file.raw.size / 1024 / 1024 < 1024;
        // if (['video/mp4', 'video/ogg', 'video/flv', 'video/avi', 'video/wmv', 'video/rmvb'].indexOf(file.raw.type) == -
        //   1) {
        //   this.$message.error('请上传正确的视频格式');
        //   return false;
        // }
        if (!isLt1G) {
            this.$message.error('上传视频大小不能超过1G哦!');
            return false;
        }
        this.fileImgs = [],
        this.setVideoImg()
        this.videoDataUrl = "",
        this.videoFlag = false


        // 获取上传视频的本地localUrl
         var localUrl = null;
         if (window.createObjectURL != undefined) {
           // basic
           localUrl = window.createObjectURL(file.raw);
         } else if (window.URL != undefined) {
           // mozilla(firefox)
           localUrl = window.URL.createObjectURL(file.raw);
         } else if (window.webkitURL != undefined) {
           // webkit or chrome
           localUrl = window.webkitURL.createObjectURL(file.raw);
         }
         // 转换后的地址为 blob:http://xxx/7bf54338-74bb-47b9-9a7f-7a7093c716b5
        console.log(localUrl)
        this.videoDataUrl = localUrl
        // this.localUrl = localUrl


        //获取视频时长
        let url = URL.createObjectURL(file.raw);
        let audioElement = new Audio(url);
        let duration;
        audioElement.addEventListener("loadedmetadata", function(_event) {
          duration = audioElement.duration; //时长为秒,小数,182.36
          // console.log(duration / 60, (duration / 60).toFixed(2));
         console.log(duration)
        });
        //异步更新data视图
        setTimeout(() => {
          console.log(duration)
          this.totalTime = this.secondsFormat(duration)
          // this.videoDataUrl = videoDataUrl
        }, 100)

        let that = this
        let userData = '{"Vod":{}}'
        if (this.uploader) {
          this.uploader.stopUpload()
        }
        this.uploader = this.createUplader(this)
        this.uploader.addFile(file.raw, null, null, null, userData)
        this.toSaveVideo ()
      },
      // 上传视频按钮
      toSaveVideo() {
        console.log("开始上传")
        this.uploader.startUpload()
      },
      // createUploader
      createUplader(that) {
        console.log("that", that)
        let uploader = new AliyunUpload.Vod({
          userId: that.videoDataForm.userId,
          partSize: that.videoDataForm.partSize,
          parallel: that.videoDataForm.parallel,
          retryCount: that.videoDataForm.retryCount,
          retryDuration: that.videoDataForm.retryDuration,
          //是否上报上传日志到点播,默认为true
          enableUploadProgress: true,
          // 开始上传
          'onUploadstarted': function(uploadInfo) {
            console.log(uploadInfo)
            if (uploadInfo.videoId) {
              // 如果 uploadInfo.videoId 存在, 调用 刷新视频上传凭证接口(https://help.aliyun.com/document_detail/55408.html)
              //这里主要是前端请求后台刷新上传凭证
              let refreshUrl = 'http://47.94.228.6:8060/video/refreshVoucher?videoId=' + uploadInfo.videoId
              axios.post(refreshUrl).then(({
                data
              }) => {
                if (data.result.code !== 1) {
                  return that.$message.error("上传失败")
                }
                let uploadAuth = data.result.uploadAuth
                let uploadAddress = data.result.uploadAddress
                let videoId = data.result.videoId
                that.videoId = data.result.videoId
                uploader.setUploadAuthAndAddress(uploadInfo, uploadAuth, uploadAddress, videoId)
              })
            } else {
              // 如果 uploadInfo.videoId 不存在,调用 获取视频上传地址和凭证接口(https://help.aliyun.com/document_detail/55407.html)
              //这里主要是前端请求后台获取上传凭证
              let title = uploadInfo.file.name.substr(0, uploadInfo.file.name.lastIndexOf('.'))
              let createUrl = 'http://47.94.228.6:8060/video/getVoucher?title=' + title + '&FileName=' +
                uploadInfo.file.name
              console.log(createUrl)
              axios.post(createUrl).then(({
                data
              }) => {
                console.log(data)
                if (data.code !== 1) {
                  return that.$message.error("uploadInfo.videoId 不存在")
                }
                let uploadAuth = data.result.uploadAuth
                let uploadAddress = data.result.uploadAddress
                let videoId = data.result.videoId
                that.videoId = data.result.videoId
                uploader.setUploadAuthAndAddress(uploadInfo, uploadAuth, uploadAddress, videoId)
              })
            }
          },
          // 文件上传成功
          'onUploadSucceed': function(uploadInfo) {
            console.log("点播上传成功------",uploadInfo)
            that.$message.success("上传成功")
            let vUrl = uploadInfo.object
             that.vUrl = "http://hou.k-playchina.com/" + vUrl
             // console.log(that.vUrl)
             that.videoFlag = true
          },
          // 文件上传失败
          'onUploadFailed': function(uploadInfo, code, message) {
            console.log(uploadInfo)
            that.$message.error('上传失败')
            // that.videoLoading.close()
          },
          // 文件上传进度,单位:字节
          'onUploadProgress': function(uploadInfo, totalSize, loadedPercent) {
            // console.log(uploadInfo)
            // console.log(totalSize)
            // console.log(loadedPercent)
            that.authProgress = Math.ceil(loadedPercent * 100)
          },
          // 上传凭证超时
          'onUploadTokenExpired': function(uploadInfo) {
            console.log("OnUploadToken已过期");
            //实现时,根据uploadInfo.videoId调用刷新视频上传凭证接口重新获取UploadAuth
            //https://help.aliyun.com/document_detail/55408.html
            //从点播服务刷新的uploadAuth,设置到SDK里
            that.$message.success('上传文件超时,正在重新上传')
            uploader.resumeUploadWithAuth(uploadAuth);
          },
          //全部文件上传结束
          'onUploadEnd': function(uploadInfo) {
            // console.log("onUploadEnd: uploaded all the files");
            // that.$message.success('上传结束')
            console.log("上传结束")
          }
        });
        return uploader
      },
      // 转换视频时长的函数
      secondsFormat( s ) {
          var hour = Math.floor( s / 3600);
          var minute = Math.floor( (s - hour*3600) /60 );
          var second = Math.floor(s - hour*3600 - minute*60);
          return  hour + ":" + minute + ":" + second;
      },
      //视频截图
      loadedListener() {
        if (this.videoDataUrl && this.$refs.divVideo && this.$refs.divVideo.readyState === 4) {
          // 获取到的视频时长(s)
          console.log(parseInt(this.$refs.divVideo.duration));
          // console.log(this.$refs.divVideo)
          let _this = this;

          // 截取视频帧,video标签内使用了autoplay让视频自动播放,这里需要截4张,设置了定时器
            var canvas = document.createElement("canvas");
            console.log(canvas)
            canvas.width = _this.$refs.divVideo.videoWidth * 0.8;
            canvas.height = _this.$refs.divVideo.videoHeight * 0.8;
            canvas.getContext('2d').drawImage(_this.$refs.divVideo, 0, 0, canvas.width, canvas
              .height);
            // 生成图片(base64)
            let aaa =setInterval(()=> {
              const dataUrl = canvas.toDataURL('image/png')
              _this.fileImgs.push(dataUrl)
              // _this.$emit('setVideoImg');
              _this.setVideoImg()
            }, 2500)

            setTimeout(()=>{
              clearInterval(aaa)
              // 截图完毕,停止视频
              _this.$refs.divVideo.pause()
            },10000)
        }
      },
      editMethod(){
        this.totalTime = this.parentThis.totalTime
        this.videoFlag = this.parentThis.videoFlag
        this.videoDataUrl = this.parentThis.vUrl
        console.log(this.editVideo)
      }

    }

  }
</script>

<style scoped="scoped">
  /deep/ .el-upload {
    cursor: auto;
    margin-top: 5px;
  }

  .avatar-uploader-icon {
    font-size: 28px;
    color: #8c939d;
    width: 167px;
    height: 93px;
    line-height: 93px;
    text-align: center;
    background: #ddd;
    cursor: pointer;
  }

  .avatar {
    display: inline-block;
    width: 167px;
    height: 93px;
  }

  .img-box img {
    width: 167px;
    height: 93px;
    margin-right: 15px;
  }

  .imgstyle {
    box-shadow: 0 0 7px 2px #1890FF;
  }

  .upload_txt {
    float: left;
    text-align: left;
    margin-left: 20px;
    height: 93px;
    line-height: 30px;
    color: rgba(0, 0, 0, 0.6);
  }

  .upload_txt button {
    margin-bottom: 10px;
  }
</style>

releasevideo.vue 的源码(只有视频的干净版)

<template>
  <div class="video-container">
    <el-form :model="ruleForm" :rules="rules" ref="ruleForm" label-position="top" label-width="100px" class="demo-ruleForm">

      <el-form-item label="上传视频:" prop="videoIntroduce" style="">
        <AddVideo  ref="setVideoImg"  :setVideoImg="setVideoImg"  :parentThis="this" />
      </el-form-item>

      <el-form-item label="选取视频帧" required>
        <div class="img-box" v-model="ruleForm.fileImg">
          <span v-for="(item, index) in fileImgs">
            <img :src="item" :id="index" @click="getId($event)" :class="{imgstyle:index == current}" />
          </span>
        </div>
      </el-form-item>
      <el-form-item class="footer-btn">
        <el-button type="primary" size="small" @click="submitForm('ruleForm')">
          <span v-if="btnFlag">立即创建</span>
          <span v-else-if="!btnFlag">立即修改</span>
        </el-button>
        <el-button size="small" @click="$router.push({name:'VideoList'})">取消</el-button>
      </el-form-item>
    </el-form>
  </div>
</template>
  import { AddVideo } from './components'

  export default {
    components: {
      AddVideo, //导入添加视频组件
    },

    data() {
      return {
        btnFlag: true,
        vUrl: '',
        totalTime: '',
        fileImgs:[],
        formData: '',
        ruleForm: {
          fileImg: '',
        }
      }
    },
    methods: {
    
      //获取子组件的视频帧
      setVideoImg(){
        this.fileImgs = this.$refs.setVideoImg.fileImgs
        this.ruleForm.fileImg = this.fileImgs[0] //默认选择第一个
      }
     
    }
   

  }
</script>
<style scoped="scoped">
  .video-container {
    padding: 20px;
    padding-top: 5px;
  }

  .el-input {
    width: 400px;
  }

  /deep/ .el-input__inner {
    height: 32px;
    line-height: 32px;
  }

  /deep/ .el-upload {
    cursor: auto;
  }



  .img-box img {
    width: 167px;
    height: 93px;
    margin-right: 15px;
  }

  .imgstyle {
    box-shadow: 0 0 7px 2px #1890FF;
  }

  /deep/ .el-form-item {
    margin-bottom: 5px;
  }

  /deep/ .el-form--label-top .el-form-item__label {
    padding: 0;
  }

  /deep/ .el-form-item__error {
    position: absolute;
    top: 8px;
    left: 410px;
  }

  .footer-btn {
    margin-top: 50px;
  }

  .footer-btn button {
    width: 100px;
    margin-right: 20px;
  }

  .footer-btn button:nth-child(2) {
    width: auto;
  }
</style>

 

releasevideo.vue 的源码完整版

<template>
  <div class="video-container">
    <el-form :model="ruleForm" :rules="rules" ref="ruleForm" label-position="top" label-width="100px" class="demo-ruleForm">

      <el-form-item label="视频标题" prop="videoTitle">
        <el-input v-model="ruleForm.videoTitle" placeholder="请输入视频标题"></el-input>
      </el-form-item>
      <el-form-item label="视频分类" required >
        <el-select v-model="ruleForm.bigName" value-key="bigId" @focus="getBigName" @change="getKeyId(ruleForm.bigName)"
          placeholder="请选择一级分类">
          <el-option v-for="item in bigNames" :keyId="item.bigId" :label="item.bigName" :value="item.bigName">
          </el-option>
        </el-select>
        <el-select v-model="ruleForm.classifyName" placeholder="请选择二级分类" @change="getCid(ruleForm.classifyName)">
          <el-option v-for="item in classifyNames" :label="item.classifyName" :value="item.classifyName"> </el-option>
        </el-select>
      </el-form-item>

      <el-form-item label="集数">
        <el-input v-model="ruleForm.videoDiver" style="width: 70px;margin-right: 10px;"></el-input>集
        <div class="video-title" style="line-height: 40px; color: rgba(0, 0, 0, 0.45);">不选集数默认为单视频</div>
      </el-form-item>

      <el-form-item label="上传视频:" prop="videoIntroduce" style="">
        <AddVideo  ref="setVideoImg"  :setVideoImg="setVideoImg"  :parentThis="this" />
      </el-form-item>

      <el-form-item label="选取视频帧" required>
        <div class="img-box" v-model="ruleForm.fileImg">
          <span v-for="(item, index) in fileImgs">
            <img :src="item" :id="index" @click="getId($event)" :class="{imgstyle:index == current}" />
          </span>
        </div>
      </el-form-item>
      <el-form-item class="footer-btn">
        <el-button type="primary" size="small" @click="submitForm('ruleForm')">
          <span v-if="btnFlag">立即创建</span>
          <span v-else-if="!btnFlag">立即修改</span>
        </el-button>
        <el-button size="small" @click="$router.push({name:'VideoList'})">取消</el-button>
      </el-form-item>
    </el-form>
  </div>
</template>


  import { AddVideo } from './components'
  import {
    getBigAndClassify,
    getBigId,
    uploadVideo,
    findId,
    putVideoUrl,
    addVideoInput
  } from '@/api/video'


  export default {
    components: {
      AddVideo, //导入添加视频组件
    },

    data() {
      return {

        actionURL: '',
        videoFlag: true,
        btnFlag: true,
        bigNames: [],
        keyId: '',
        classifyNames: [],
        fileImgs: [],
        bigId: '',
        vUrl: '',
        localUrl:'',
        totalTime: '',
        current: 0,
        formData: '',
        ruleForm: {
          videoTitle: '',
          bigName: '',
          classifyName: '',
          videoDiver: '',
          cid: '',
          file: '',
          fileImg: '',
          id: '' //原视频视频的id -修改页面
        },
        dialogImageUrl: '',
        dialogVisible: false,
        rules: {
          videoTitle: [{
              required: true,
              message: '请输入视频名称',
              trigger: 'blur'
            },
            {
              min: 1,
              max: 30,
              message: '长度最好控制在20个字符左右',
              trigger: 'blur'
            }
          ],
          bigName: [{
            required: true,
            message: '请选择视频分类',
            trigger: 'change'
          }],
          classifyName: [{
            required: true,
            message: '请选择视频分类',
            trigger: 'change'
          }]
        },
        imageUrl: '',
        progressPercent: 0
      }
    },
    methods: {
      //获取一级分类
      getBigName() {
        getBigAndClassify().then(response => {
          // console.log(response)
          this.bigNames = response.result
        }).catch(error => {
          reject(error)
        })
      },
      getKeyId(val) {
        // console.log(val)
        let obj = {};
        obj = this.bigNames.find((item) => { //遍历bigNames的数据
          return item.bigName === val; //筛选出匹配数据
        });
        this.keyId = obj.bigId;
        this.bigId = "bigId" + "=" + this.keyId;
        // 获取二级分类
        getBigId(this.bigId).then(response => {
          // console.log(response)
          this.classifyNames = response.result
          this.ruleForm.classifyName = ''
        }).catch(error => {
          reject(error)
        })
      },
      //获取二级分类的cid
      getCid(val) {
        // console.log(val)
        let obj = {};
        obj = this.classifyNames.find((item) => { //遍历bigNames的数据
          return item.classifyName === val; //筛选出匹配数据
        });
        this.ruleForm.cid = obj.cid
        // console.log(this.ruleForm.cid)
        // console.log(this.ruleForm.classifyName)
      },
      //获取子组件的视频帧
      setVideoImg(){
        this.fileImgs = this.$refs.setVideoImg.fileImgs
        this.ruleForm.fileImg = this.fileImgs[0] //默认选择第一个
      },
      //清除数据
      clearFrom() {
        this.fileImgs = [];
        this.ruleForm.videoDiver = ""
        this.ruleForm.bigName = ""
        this.ruleForm.classifyName = ""
        this.imageUrl = ""
        this.videoFlag == false
        this.vUrl = ''
      },
      //提交
      submitForm(formName) {
        this.totalTime = this.$refs.setVideoImg.totalTime
        this.vUrl = this.$refs.setVideoImg.vUrl
        this.$refs[formName].validate((valid) => {
          if (valid) {
            var formData = new FormData();
            formData.append('videoImg', this.ruleForm.fileImg.substring (22));
            formData.append('vUrl', this.vUrl);
            formData.append('videoTitle', this.ruleForm.videoTitle);
            formData.append('bigName', this.ruleForm.bigName);
            formData.append('classifyName', this.ruleForm.classifyName);
            formData.append('videoDiver', this.ruleForm.videoDiver);
            formData.append('totalTime', this.totalTime);
            formData.append('vId', this.ruleForm.cid);
            // formData1.append('aId', this.aId);
            formData.append('aId', 1);
            // console.log(formData1.totalTime)
            //判断是添加还是编辑
            if (this.$route.query.id) {
              formData.append('id', this.ruleForm.id);
              putVideoUrl(formData).then(response => {
                console.log(response)
                if (response.code == "1") {
                  this.$message.success("修改成功");
                  this.$router.push({
                    name: 'VideoList'
                  })
                  // this.$refs[formName].resetFields(); //重置表单
                  // this.clearFrom()
                }
              }).catch(error => {
                 return reject(error)
              })
            } else {
              addVideoInput(formData).then(response => {
                if (response.code == "1") {
                  this.$message.success("添加成功");
                  this.$refs[formName].resetFields(); //重置表单
                  this.$router.push({name:'VideoList'})
                  // this.clearFrom()

                }
              }).catch(error => {
                reject(error)
              })
            }
          } else {
            console.log('error submit!!');
            return false;
          }
        });
      },

      getId(e) {
        this.current = "";
        console.log(e.currentTarget); // 当前元素
        console.log(e); // 获取 v-bind:id=值 中绑定的 id 的值,此 id 为其标签中的属性,且不会与非绑定的 id 冲突
        console.log(e.currentTarget.src); // 获取 v-bind:id=值 中绑定的 id 的值,此 id 为其标签中的属性,且不会与非绑定的 id 冲突
        this.current = e.currentTarget.id
        this.ruleForm.fileImg = e.currentTarget.src
      },
      resetForm(formName) {
        this.$refs[formName].resetFields();
      }
    },
    mounted() {
      console.log(this.$route.query.id)
      this.btnFlag = this.$route.query.id === undefined
        //判断是添加还是编辑页面
      if (this.$route.query.id) {
        findId(this.$route.query.id).then(response => {
          console.log(response)
          this.ruleForm.videoTitle = response.result.findId.videoTitle;
          this.ruleForm.videoDiver = response.result.findId.videoDiver;
          this.ruleForm.cid = response.result.findId.vid
          this.ruleForm.bigName = this.$route.query.bigName
          this.ruleForm.classifyName = this.$route.query.classifyName
          this.fileImgs.push(response.result.findId.videoImg);
          this.ruleForm.fileImg = response.result.findId.videoImg;
          this.totalTime = response.result.findId.totalTime
          this.ruleForm.id = this.$route.query.id
          this.totalTime = response.result.findId.totalTime
          this.vUrl = response.result.findId.vurl;
          // 调用子组件的赋值
          this.$refs.setVideoImg.editMethod()
        })
      }
    }

  }
</script>
<style scoped="scoped">
  .video-container {
    padding: 20px;
    padding-top: 5px;
  }

  .el-input {
    width: 400px;
  }

  /deep/ .el-input__inner {
    height: 32px;
    line-height: 32px;
  }

  /deep/ .el-upload {
    cursor: auto;
  }

  .avatar-uploader-icon {
    font-size: 28px;
    color: #8c939d;
    width: 167px;
    height: 93px;
    line-height: 93px;
    text-align: center;
    background: #ddd;
    cursor: pointer;
  }

  .avatar {
    display: inline-block;
    width: 167px;
    height: 93px;
  }

  .img-box img {
    width: 167px;
    height: 93px;
    margin-right: 15px;
  }

  .imgstyle {
    box-shadow: 0 0 7px 2px #1890FF;
  }

  /deep/ .el-form-item {
    margin-bottom: 5px;
  }

  /deep/ .el-form--label-top .el-form-item__label {
    padding: 0;
  }

  /deep/ .el-form-item__error {
    position: absolute;
    top: 8px;
    left: 410px;
  }

  .footer-btn {
    margin-top: 50px;
  }

  .footer-btn button {
    width: 100px;
    margin-right: 20px;
  }

  .footer-btn button:nth-child(2) {
    width: auto;
  }
</style>

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值