尚硅谷谷粒学院-在线教育项目上传视频前端功能分析

 1.总代码

<template>

  <div class="app-container">

    <h2 style="text-align: center;">发布新课程</h2>

    <el-steps :active="2" process-status="wait" align-center style="margin-bottom: 40px;">
      <el-step title="填写课程基本信息"/>
      <el-step title="创建课程大纲"/>
      <el-step title="最终发布"/>
    </el-steps>

<el-button type="text" @click="openChapterDialog()">添加章节</el-button>

    <!-- 章节 -->
    <ul class="chanpterList">
        <li
            v-for="chapter in chapterVideoList"
            :key="chapter.id">
            <p>
                {{ chapter.title }}

                <span class="acts">
                    <el-button style="" type="text" @click="openVideo(chapter.id)">添加小节</el-button>
                    <el-button style="" type="text" @click="openEditChatper(chapter.id)">编辑</el-button>
                    <el-button type="text" @click="removeChapter(chapter.id)">删除</el-button>
                </span>
            </p>

            <!-- 视频 -->
            <ul class="chanpterList videoList">
                <li
                    v-for="video in chapter.children"
                    :key="video.id">
                    <p>{{ video.title }}

                <span class="acts">
                    
                    <el-button style="" type="text" @click="openEditVideo(video.id)">编辑</el-button>
                    <el-button type="text" @click="removeVideo(video.id)">删除</el-button>
                </span>
                    </p>
                </li>
            </ul>
        </li>
    </ul>
    <div>
        <el-button @click="previous">上一步</el-button>
        <el-button :disabled="saveBtnDisabled" type="primary" @click="next">下一步</el-button>
    </div>

    <!-- 添加和修改章节表单 -->
    <el-dialog :visible.sync="dialogChapterFormVisible" title="添加章节">
        <el-form :model="chapter" label-width="120px">
            <el-form-item label="章节标题">
                <el-input v-model="chapter.title"/>
            </el-form-item>
            <el-form-item label="章节排序">
                <el-input-number v-model="chapter.sort" :min="0" controls-position="right"/>
            </el-form-item>
        </el-form>
        <div slot="footer" class="dialog-footer">
            <el-button @click="dialogChapterFormVisible = false">取 消</el-button>
            <el-button type="primary" @click="saveOrUpdate">确 定</el-button>
        </div>
    </el-dialog>

    <!-- 添加和修改课时表单 -->
<el-dialog :visible.sync="dialogVideoFormVisible" title="添加课时" @close='closeDialog'>
  <el-form :model="video" label-width="120px">
    <el-form-item label="课时标题">
      <el-input v-model="video.title"/>
    </el-form-item>
    <el-form-item label="课时排序">
      <el-input-number v-model="video.sort" :min="0" controls-position="right"/>
    </el-form-item>
    <el-form-item label="是否免费">
      <el-radio-group v-model="video.isFree">
        <el-radio :label="1" >免费</el-radio>
        <el-radio :label="0">默认</el-radio>
      </el-radio-group>
    </el-form-item>

    <el-form-item label="上传视频">
        <el-upload
            :on-success="handleVodUploadSuccess"
            :on-remove="handleVodRemove"
            :before-remove="beforeVodRemove"
            :on-exceed="handleUploadExceed"
            :file-list="fileList"
            :action="BASE_API+'/eduvod/video/uploadAlyVideo'"
            :limit="1"
            class="upload-demo">
        <el-button size="small" type="primary">上传视频</el-button>
        <el-tooltip placement="right-end">
            <div slot="content">最大支持1G,<br>
                支持3GP、ASF、AVI、DAT、DV、FLV、F4V、<br>
                GIF、M2T、M4V、MJ2、MJPEG、MKV、MOV、MP4、<br>
                MPE、MPG、MPEG、MTS、OGG、QT、RM、RMVB、<br>
                SWF、TS、VOB、WMV、WEBM 等视频格式上传</div>
            <i class="el-icon-question"/>
        </el-tooltip>
        </el-upload>
    </el-form-item>
    
  </el-form>
  <div slot="footer" class="dialog-footer">
    <el-button @click="dialogVideoFormVisible = false">取 消</el-button>
    <el-button :disabled="saveVideoBtnDisabled" type="primary" @click="saveOrUpdateVideo">确 定</el-button>
  </div>
</el-dialog>

  </div>
</template>
<script>
import chapter from '@/api/edu/chapter'
import video from '@/api/edu/video'

export default {
    data() {
        return {
            saveBtnDisabled:false,
            courseId:'',//课程id
            chapterVideoList:[],
            chapter:{ //封装章节数据
                title: '',
                sort: 0
            },
            video: {
                title: '',
                sort: 0,
                isFree: 0,
                videoSourceId: ''
            },
            videoSourceIdOld: '',//用来存储老的视频id
            videoOriginalNameOld: '',//用于存储老的视频名称
            dialogChapterFormVisible:false,//章节弹框
            dialogVideoFormVisible:false, //小节弹框
            fileList: [],//上传文件列表
            BASE_API: process.env.BASE_API // 接口API地址
        }
    },
    created() {
        //获取路由的id值
        if(this.$route.params && this.$route.params.id) {
            this.courseId = this.$route.params.id
            //根据课程id查询章节和小节
            this.getChapterVideo()
        }
    },
    methods:{
        //=================解决阿里云无效视频问题=================
        //@close方法,经测试无论点击取消,或是确认或是叉号都会执行该方法
        closeDialog(){
            console.log("兜底进入")
            console.log(this.fileList.length)
            //每次我们打开这个弹窗都清空一下filelist,这样如果执行到这里filelist不为空说明上传视频了,如果为空,则说明没上传视频,或者上传之后进行了其他操作清空了
            //这里是个兜底操作,用来删除上传但是未保存到数据库的视频
            //判断视频列表是否为空,为空说明未上传视频,则不需要操作
            //若不为空说明上传视频,但是此时我们可能是点击叉号、确认、取消来进行的操作
            if(this.video.videoSourceId){
                console.log("兜底我执行了")
                console.log(this.video.videoSourceId)
                //我们在确认按钮触发事件中进行旧视频删除操作,删除成功后将视频id置为空,我们这里只需要判断id是否为空即可作为是否删除视频的依据
                //因为如果到这里视频不为空就说明是上传了视频,但是并没有保存数据,所以这个视频就是多余的,直接删除即可
                //调用接口的删除视频的方法
            video.deleteAlyvod(this.video.videoSourceId)
                .then(response => {
                    //提示信息
                    this.$message({
                        type: 'success',
                        message: '删除未使用视频成功!'
                    });
                    /* //判断是否是修改
                    if(this.video.id){
                        //如果小节的id存在说明是修改,那么删除视频之后就直接调用更新,来保证数据库中视频信息的清除
                        this.updateVideo()
                    } */
                    //把文件列表清空
                    this.fileList = []
                    //上传视频id赋值
                    this.video.videoSourceId = ''
                    //上传视频名称赋值
                    this.video.videoOriginalName = ''
                })
                

            }
            
        },
        dialogVideoFormUnVisible(){
            console.log('"hi"')
            this.dialogVideoFormVisible = false
        },
        //点击确定调用的方法
        handleVodRemove() {
            //调用接口的删除视频的方法
            video.deleteAlyvod(this.video.videoSourceId)
                .then(response => {
                    //提示信息
                    this.$message({
                        type: 'success',
                        message: '删除视频成功!'
                    });
                    /* //判断是否是修改
                    if(this.video.id){
                        //如果小节的id存在说明是修改,那么删除视频之后就直接调用更新,来保证数据库中视频信息的清除
                        this.updateVideo()
                    } */
                    //把文件列表清空
                    this.fileList = []
                    //把video视频id和视频名称值清空,否则后续仍然会把视频id传到后台,然后插入数据库,
                    //上传视频id赋值
                    this.video.videoSourceId = ''
                    //上传视频名称赋值
                    this.video.videoOriginalName = ''
                })
        },
        //点击×调用这个方法
        beforeVodRemove(file,fileList) {
            //弹窗询问是否删除
            return this.$confirm(`确定移除 ${ file.name }?`);
        },
        //上传视频成功调用的方法
        handleVodUploadSuccess(response, file, fileList) {
            //上传视频id赋值
            this.video.videoSourceId = response.data.videoId
            //上传视频名称赋值
            this.video.videoOriginalName = file.name
        },
        handleUploadExceed() {
            this.$message.warning('想要重新上传视频,请先删除已上传的视频')
        },
//==============================小节操作====================================
        //删除小节
        removeVideo(id) {
            this.$confirm('此操作将删除小节, 是否继续?', '提示', {
                confirmButtonText: '确定',
                cancelButtonText: '取消',
                type: 'warning'
            }).then(() => {  //点击确定,执行then方法
                //调用删除的方法
                video.deleteVideo(id)
                    .then(response =>{//删除成功
                    //提示信息
                    this.$message({
                        type: 'success',
                        message: '删除小节成功!'
                    });
                    //刷新页面
                    this.getChapterVideo()
                })
            }) //点击取消,执行catch方法
        },
        //添加小节弹框的方法
        openVideo(chapterId) {
            //弹框
            this.dialogVideoFormVisible = true
            //清空video数据
            this.video = {}
            //设置章节id
            this.video.chapterId = chapterId
        },
        //添加小节
        addVideo() {
            //设置课程id
            this.video.courseId = this.courseId
            video.addVideo(this.video)
                .then(response => {
                    //关闭弹框
                    this.dialogVideoFormVisible = false
                    //提示
                    this.$message({
                        type: 'success',
                        message: '添加小节成功!'
                    });
                    //刷新页面
                    this.getChapterVideo()
                    this.fileList = []
                })
        },
        //修改小节
        updateVideo(){
            //更新小节前判断视频id是否有值或者判断filelist是否为空都可以,如果为空说明没上传新视频
            //那么直接将老视频的id以及名称传回数据库即可
            if(!this.video.videoSourceId){
                console.log("编辑但未上传新视频执行了")
                console.log(this.video.videoSourceId)
                //将保存的老值返回
                this.video.videoSourceId = this.videoSourceIdOld
                this.video.videoOriginalName = this.videoOriginalNameOld
                //然后保存即可
                video.updateVideo(this.video)
            .then(response =>{
                //关闭弹框
                    this.dialogVideoFormVisible = false
                    //提示
                    this.$message({
                        type: 'success',
                        message: '修改小节成功!'
                    });
                    //刷新页面
                    this.getChapterVideo()
                    //设置为空则兜底操作不执行就不会删除新视频
                    this.fileList = []
                    //清空老值
                    this.videoSourceIdOld = ''
                    this.videoOriginalNameOld = ''
                    //清空视频id防止后面兜底操作清除我们的老视频
                    this.video.videoSourceId = ''
                    this.video.videoOriginalName = ''
            })
                
            }else{
                console.log("编辑并上传了新视频执行了")
                //如果不为空说明上传了新的视频,那么就需要将新视频的id和名字传到数据库,并且删除老视频
                //由于上传视频操作会给id和名称赋值,所以直接上传即可
                video.updateVideo(this.video)
            .then(response =>{
                //关闭弹框
                    this.dialogVideoFormVisible = false
                    //提示
                    this.$message({
                        type: 'success',
                        message: '修改小节成功!'
                    });
                    //刷新页面
                    this.getChapterVideo()
                    //设置为空则兜底操作不执行就不会删除新视频
                    this.fileList = []
                    //清空视频id防止后面兜底操作重复清除
                    this.video.videoSourceId = ''
                    this.video.videoOriginalName = ''
            })
            //由于上传了新值并点击了确定因此需要清除阿里云中老视频
                    video.deleteAlyvod(this.videoSourceIdOld)
                .then(response => {
                    //提示信息
                    this.$message({
                        type: 'success',
                        message: '更换视频成功!'
                    });
                    })
                    //清空老值
                    this.videoSourceIdOld = ''
                    this.videoOriginalNameOld = ''
            }
                
            
            
        },
        openEditVideo(videoId){
            //弹窗
            this.dialogVideoFormVisible = true

            //数据回显
            video.getVideo(videoId)
            .then(response =>{
                //先清空文件列表,假设前面打开过别的小节编辑,但是没有点确定,那么名字会留在这里,导致显示不属于自己的文件列表
                this.fileList = []
                this.video = response.data.video
                //保存老的视频id
                this.videoSourceIdOld = this.video.videoSourceId
                //保存老名称
                this.videoOriginalNameOld = this.video.videoOriginalName
                //查询完将视频id清空防止关闭弹窗统一删除未使用视频时误删有用视频
                this.video.videoSourceId = ''
                console.log("打开编辑时")
                console.log(this.video.videoSourceId)
                //判断查到的文件名称是否是空值,如果是空值,那么就不用给当前页的文件名称赋值了,不然会显示出没有名称的文件列表
                /* if(this.video.videoOriginalName!=""){
                    //如果不是空值,就把文件名放入文件列表供显示
                    this.fileList = [{name:this.video.videoOriginalName}]
                } */
                
            
            })
        },
        saveOrUpdateVideo() {
            if(!this.video.id){
                this.addVideo()
            }else
            {
                this.updateVideo()
            }
            
        },

//==============================章节操作====================================
        //删除章节
        removeChapter(chapterId) {
            this.$confirm('此操作将删除章节, 是否继续?', '提示', {
                confirmButtonText: '确定',
                cancelButtonText: '取消',
                type: 'warning'
            }).then(() => {  //点击确定,执行then方法
                //调用删除的方法
                chapter.deleteChapter(chapterId)
                    .then(response =>{//删除成功
                    //提示信息
                    this.$message({
                        type: 'success',
                        message: '删除成功!'
                    });
                    //刷新页面
                    this.getChapterVideo()
                })
            }) //点击取消,执行catch方法
        },
        //修改章节弹框数据回显
        openEditChatper(chapterId) {
            //弹框
            this.dialogChapterFormVisible = true
            //调用接口
            chapter.getChapter(chapterId)   
                .then(response => {
                    this.chapter = response.data.chapter
                })
        },
        //弹出添加章节页面
        openChapterDialog() {
            //弹框
            this.dialogChapterFormVisible = true
            //表单数据清空
            this.chapter.id = ''
            this.chapter.title = ''
            this.chapter.sort = 0
        },
        //添加章节
        addChapter() {
            //设置课程id到chapter对象里面
            this.chapter.courseId = this.courseId
            chapter.addChapter(this.chapter)
                .then(response => {
                    //关闭弹框
                    this.dialogChapterFormVisible = false
                    //提示
                    this.$message({
                        type: 'success',
                        message: '添加章节成功!'
                    });
                    //刷新页面
                    this.getChapterVideo()
                })
        },
        //修改章节的方法
        updateChapter() {
            chapter.updateChapter(this.chapter)
                .then(response =>  {
                    //关闭弹框
                    this.dialogChapterFormVisible = false
                    //提示
                    this.$message({
                        type: 'success',
                        message: '修改章节成功!'
                    });
                    //刷新页面
                    this.getChapterVideo()
                })
        },
        saveOrUpdate() {
            if(!this.chapter.id) {
                this.addChapter()
            } else {
                this.updateChapter()
            }
        },
        //根据课程id查询章节和小节
        getChapterVideo() {
            chapter.getAllChapterVideo(this.courseId)
                .then(response => {
                    this.chapterVideoList = response.data.allChapterVideo
                })
        },
        previous() {
            this.$router.push({path:'/course/info/'+this.courseId})
        },
        next() {
            //跳转到第二步
            this.$router.push({path:'/course/publish/'+this.courseId})
        }
    }
}
</script>
<style scoped>
.chanpterList{
    position: relative;
    list-style: none;
    margin: 0;
    padding: 0;
}
.chanpterList li{
  position: relative;
}
.chanpterList p{
  float: left;
  font-size: 20px;
  margin: 10px 0;
  padding: 10px;
  height: 70px;
  line-height: 50px;
  width: 100%;
  border: 1px solid #DDD;
}
.chanpterList .acts {
    float: right;
    font-size: 14px;
}

.videoList{
  padding-left: 50px;
}
.videoList p{
  float: left;
  font-size: 14px;
  margin: 10px 0;
  padding: 10px;
  height: 50px;
  line-height: 30px;
  width: 100%;
  border: 1px dotted #DDD;
}

</style>

 2.问题分析:

问题一:当点击上传文件,文件上传到阿里云,但是我们并没有点确认的情况

会出现的问题分析:此时由于我们文件已经上传到了阿里云,但是由于我们修改数据库的操作是在点击确认之后才会进行的,而我们没有进行点击,那么我们的这个视频就是多余的,需要重新删除,所有我们要在关闭事件中去删除这个视频,由于上传视频功能会设置视频id值,因此我们直接删除调用删除方法删除id值即可 

关闭事件如下

 事件代码:

//=================解决阿里云无效视频问题=================
        //@close方法,经测试无论点击取消,或是确认或是叉号都会执行该方法
        closeDialog(){
            console.log("兜底进入")
            console.log(this.fileList.length)
            //每次我们打开这个弹窗都清空一下filelist,这样如果执行到这里filelist不为空说明上传视频了,如果为空,则说明没上传视频,或者上传之后进行了其他操作清空了
            //这里是个兜底操作,用来删除上传但是未保存到数据库的视频
            //判断视频列表是否为空,为空说明未上传视频,则不需要操作
            //若不为空说明上传视频,但是此时我们可能是点击叉号、确认、取消来进行的操作
            if(this.video.videoSourceId){
                console.log("兜底我执行了")
                console.log(this.video.videoSourceId)
                //我们在确认按钮触发事件中进行旧视频删除操作,删除成功后将视频id置为空,我们这里只需要判断id是否为空即可作为是否删除视频的依据
                //因为如果到这里视频不为空就说明是上传了视频,但是并没有保存数据,所以这个视频就是多余的,直接删除即可
                //调用接口的删除视频的方法
            video.deleteAlyvod(this.video.videoSourceId)
                .then(response => {
                    //提示信息
                    this.$message({
                        type: 'success',
                        message: '删除未使用视频成功!'
                    });
                    /* //判断是否是修改
                    if(this.video.id){
                        //如果小节的id存在说明是修改,那么删除视频之后就直接调用更新,来保证数据库中视频信息的清除
                        this.updateVideo()
                    } */
                    //把文件列表清空
                    this.fileList = []
                    //上传视频id赋值
                    this.video.videoSourceId = ''
                    //上传视频名称赋值
                    this.video.videoOriginalName = ''
                })
                

            }
            
        },

问题二:ID可能不是上传的,而是数据库查到的,这时候如果删除,那么数据库里的ID对应的视频文件不就相当于误删?

OK,如果我们点击编辑按钮,那么回显数据也会查询数据库,拿到数据库中已有的视频id值,这样id不也有值了,那么我们删除时怎么知道这个ID是有用的还是没用的呢,很简单,我们在回显之后手动清空ID值即可,方法如下

openEditVideo(videoId){
            //弹窗
            this.dialogVideoFormVisible = true

            //数据回显
            video.getVideo(videoId)
            .then(response =>{
                //先清空文件列表,假设前面打开过别的小节编辑,但是没有点确定,那么名字会留在这里,导致显示不属于自己的文件列表
                this.fileList = []
                this.video = response.data.video
                //保存老的视频id
                this.videoSourceIdOld = this.video.videoSourceId
                //保存老名称
                this.videoOriginalNameOld = this.video.videoOriginalName
                //查询完将视频id清空防止关闭弹窗统一删除未使用视频时误删有用视频
                this.video.videoSourceId = ''
                console.log("打开编辑时")
                console.log(this.video.videoSourceId)
                //判断查到的文件名称是否是空值,如果是空值,那么就不用给当前页的文件名称赋值了,不然会显示出没有名称的文件列表
                /* if(this.video.videoOriginalName!=""){
                    //如果不是空值,就把文件名放入文件列表供显示
                    this.fileList = [{name:this.video.videoOriginalName}]
                } */
                
            
            })
        },

在这里我清空了视频ID值,并且使用了两个新的值去保存视频ID和视频名称,后面有用处,这样由于ID清空,那么我们的删除方法就不会删掉我们这个有用的视频了,因为删除方法的判断条件是是否有视频ID,而我们清空了,所以就不会删除

问题三:那如果我们上传了新视频,并点击了上传,旧视频怎么办,又或者没上传新视频,正常保存,但是ID已经清空了怎么办

是的,我们清空了旧视频ID,但是别忘了我们使用了两个新变量保存了他们,所有只需要在点击确认按钮时将其值设置回来就可以了,而如果上传了新视频,那么上传过程自然会给视频ID赋值,我们直接保存即可将新视频信息存入数据库了,完美替换

但是别忘了新视频传完我们还要删除旧视频呢,不然你的流量费要上涨咯,依然是利用我们之前额外保存的旧视频ID来进行删除视频操作

代码如下

//修改小节
        updateVideo(){
            //更新小节前判断视频id是否有值或者判断filelist是否为空都可以,如果为空说明没上传新视频
            //那么直接将老视频的id以及名称传回数据库即可
            if(!this.video.videoSourceId){
                console.log("编辑但未上传新视频执行了")
                console.log(this.video.videoSourceId)
                //将保存的老值返回
                this.video.videoSourceId = this.videoSourceIdOld
                this.video.videoOriginalName = this.videoOriginalNameOld
                //然后保存即可
                video.updateVideo(this.video)
            .then(response =>{
                //关闭弹框
                    this.dialogVideoFormVisible = false
                    //提示
                    this.$message({
                        type: 'success',
                        message: '修改小节成功!'
                    });
                    //刷新页面
                    this.getChapterVideo()
                    //设置为空则兜底操作不执行就不会删除新视频
                    this.fileList = []
                    //清空老值
                    this.videoSourceIdOld = ''
                    this.videoOriginalNameOld = ''
                    //清空视频id防止后面兜底操作清除我们的老视频
                    this.video.videoSourceId = ''
                    this.video.videoOriginalName = ''
            })
                
            }else{
                console.log("编辑并上传了新视频执行了")
                //如果不为空说明上传了新的视频,那么就需要将新视频的id和名字传到数据库,并且删除老视频
                //由于上传视频操作会给id和名称赋值,所以直接上传即可
                video.updateVideo(this.video)
            .then(response =>{
                //关闭弹框
                    this.dialogVideoFormVisible = false
                    //提示
                    this.$message({
                        type: 'success',
                        message: '修改小节成功!'
                    });
                    //刷新页面
                    this.getChapterVideo()
                    //设置为空则兜底操作不执行就不会删除新视频
                    this.fileList = []
                    //清空视频id防止后面兜底操作重复清除
                    this.video.videoSourceId = ''
                    this.video.videoOriginalName = ''
            })
            //由于上传了新值并点击了确定因此需要清除阿里云中老视频
                    video.deleteAlyvod(this.videoSourceIdOld)
                .then(response => {
                    //提示信息
                    this.$message({
                        type: 'success',
                        message: '更换视频成功!'
                    });
                    })
                    //清空老值
                    this.videoSourceIdOld = ''
                    this.videoOriginalNameOld = ''
            }
                
            
            
        },

但是别忘了执行完这一系列操作之后还是要清空视频ID信息,因为我通过打印控制台信息发现,关闭方法不只是点击叉号或者取消会调用,就连点击确认按钮也会调用,应该是只要弹窗消失就会调用吧,我没学过前端,不太了解,所有我们要清空,这样最后的关闭方法就不会把我们新上传的视频或者从数据库中查到的视频给删除了。

问题四:我们上传了视频又手动删除会影响吗?

不会,因为手动删除视频的方法会将ID清空,所以不会导致后续重复删除导致报错,而且我们前面保存的旧ID也有效防止了由于一系列操作ID丢失的问题

总结:

总结就是:1.上传未使用的视频要删除(通过判断视频ID为空实现,只要不为空就删除,所有前面步骤一定要保证视频ID里不能保存有用的视频ID,并且还有确保没用的视频ID会在里面)

                  2.上传新视频并保存时要删除旧视频(由于旧视频ID放在视频ID熟悉中会被删,而我们旧视频不是一定要删除的,比如未上传新视频或者上传新视频未保存的情况下都是要保留旧视频的,因此额外保存,最后根据条件决定是否重新写入数据库)

                  3.上传新视频但是未保存时旧视频不能丢失(由于未保存,所有数据库不受影响,而最后的关闭方法会删除新上传的视频)

                  4.不能误删旧视频(本质就是不让旧视频ID出现在视频ID属性中,防止被最后兜底的给删掉,所有我们一直存在一个其他变量中,这样如果上传了新视频并保存时,我们也可以在保存事件中根据旧ID主动删除,但是谨记,由于这个新视频是成功保存的,所有我们要清空ID,不然后面兜底的会无情的把你的这个新视频删掉)

由于博客是凌晨五点多写的,所以可能脑子不太清晰,也没打草稿,所有可能会有遗漏,但是我自己测试我能想到的情况应该是都解决了,当然网络中断等一些别的问题引起的乱七八糟那我就不知道了,如有遗漏,欢迎补充

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值