使用videjs+vue2+elementui自定义播放器控件

news2024/11/17 5:40:42

一、安装项目所需依赖

videojs依赖:

npm install --save-dev video.js

elementui依赖(这个图方便就不按需引入了):

npm i element-ui -S

二、main.js修改

增加以下几行:

import videojs from 'video.js'
import elemenui from 'element-ui'
import 'element-ui/lib/theme-chalk/index.css';
Vue.prototype.$videoJs = videojs;
Vue.use(elemenui)

三、准备html结构

1、准备两个组件

在components文件夹下创建两个组件videoComponent和videoPlayer——

videoComponent挂载到App组件上

videoPlayer挂载到videoComponent上

2、各组件中的html结构

先把两个组件最基本的结构搭好

videoComponent:

<template>
  <div class="container">
    <video-player :options="videoOptions" class="video-css"></video-player>
  </div>
</template>

<script>
  import videoPlayer from './videoPlayer.vue'
  export default {
    name: 'videoComponent',
    components:{videoPlayer},
    data(){
      return{
        videoOptions:{
            //一些视频的配置项,见下面补充...        
        }
      }
   
  }
</script>

<style scoped>
  .container{
    width: 100%;
    height: 100vh;
    display: flex;
    justify-content: center;
    align-items: center;
  }
  /* 视频宽高将由此样式调整 */
  .video-css{
    width: 800px;
    height: auto;
  }
</style>

videoPlayer:

<template>
  <div class="videoBox">
    <video ref="videoPlayer" class="video-js"></video>
  </div>
</template>

<script>
    import 'video.js/dist/video-js.css';
    export default {
        name:'videoPlayer',
    }
</script>

<style scoped>
    .videoBox{
        box-sizing: border-box;
        position: relative;
        width: 800px;
        height:500px;
    }
</style>

App:

<template>
  <div id="app">
    <video-component></video-component>
  </div>
</template>

<script>
  import videoComponent from './components/videoComponent.vue'
  export default {
    name: 'App',
    components: {
      videoComponent
    }
  }
</script>
<style>
  *{
    padding: 0;
    margin: 0;
  }
</style>

3、在videoPlayer组件里实例化播放器

<script>
    import 'video.js/dist/video-js.css';
    export default {
        name:'videoPlayer',
        //接收外部组件来的参数,可实现videoPlayer组件的复用
        props:{
            options: {
                type: Object
            }
        },
        data(){
            return{
                player:null
            }
        },
        methods:{
            //定义好一个实例化播放器的方法
            createVideoPlayer(){
                this.player = this.$videoJS(this.$refs.videoPlayer,this.options)
            }
        },
        //在组件挂载时调用播放器实例化方法
        mounted(){
            this.createVideoPlayer()
        },
        //组件销毁前销毁播放器
        beforeDestroy(){
            if(this.player){
                this.player.dispose()
            }
        }
    }
</script>

4、videoComponent传递视频参数

还有很多配置项,有需要自己查

<script> 
  import videoPlayer from './videoPlayer.vue'
  export default {
    name: 'videoComponent',
    components:{videoPlayer},
    data(){
      return{
        videoOptions:{
          //controls配置项决定是否显示默认控件
          controls:true,
          //fluid配置项根据外层css样式大小,自动填充宽高
          fluid:true,
          //sources配置项配置视频播放源
          sources:[
            {
              //播放源
              src:require('@/assets/kp.mp4'),
              //视频类型
              type:"video/mp4"
            }
          ]
          
        }
      }
    },
   
  }
</script>

在assets文件夹下事先放好一个视频,在配置项sources里面改一下视频路径。

走到这一步,运行项目一般可以正常播放视频了:

 接下来就可以实现播放器自定义控件

5、自定义控件的html结构

确定视频可以正常播放后就可以着手自定义控件了,控件写在videoPlayer这个组件里。先上结构,这里图标用的都是elementui的icon,音量没找到合适的就凑合着用了其他图标:

<template>
  <div class="videoBox">
    <video 
        ref="videoPlayer"
        class="video-js"
    >
    </video>
    <!-- 自定义控件 -->
    <div class="controlBar">
        <div class="progressBar"  @mousedown="isDraging = true" @mouseup="isDraging = false">
            <el-slider    
                v-model="currentTimeVal"
                :max="totalTimeVal"
                :format-tooltip="timeFormat"
                @change="progressUpdate"
            >
            </el-slider>
        </div>
        <div class="controlBtnBox">
            <div class="left">
                <i class="el-icon-video-play icon-size"></i>
                <span>00:00:00 / 00:00:00</span>
            </div>
            <div class="right">
                <i class="el-icon-d-arrow-left icon-size" @click="back(player.currentTime())"></i>
                <i class="el-icon-d-arrow-right icon-size" @click="forward(player.currentTime())"></i>
                <i class="el-icon-bell icon-size" @click="toShowVolumeBar"></i>
                <div id="volumeBar">
                    <el-slider
                        v-model="volume"
                        vertical
                        height="100px">
                    </el-slider>
                </div>
            </div>
            <div class="rateBox">
                <span @click="toShowOptions">{{ratedisplay}}</span>
                <div class="rateOptions" v-show="isShowRateOptions">
                    <span 
                        v-for="r,index in rateOptions" 
                        :key="index" 
                        @click="setPlayRate(r)"
                    >
                        {{r}}x
                    </span>
                </div>
            </div>
        </div>
    </div>
  </div>
</template>

结构效果图最终长这样,这里样式不多废话,跟完整代码放在文章最后面。

 6、准备数据

 结构搞定之后,在data里准备好后续需要用到的数据

        data(){
            return{
                player:null,
                //当前播放速率
                rate:1.0,
                //播放速率
                rateOptions:[2.0,1.75,1.5,1.0,0.75,0.5],
                //显示速率选项
                isShowRateOptions:false,
                //音量
                volume:30,
                //是否暂停
                isPaused:true,
                //当前播放时间点和视频总时长
                currentTime:'00:00:00',
                totalTime:'00:00:00',
                //进度条的当前值,必须为number类型
                currentTimeVal:0,
                //进度条最大值,必须为number类型
                totalTimeVal:0,
                //是否在拖到进度条
                isDraging:false
            }
        },

rate和rateOptions用在哪上面的代码已经写了,isShowRateOptions后面用来隐藏倍速选项那个框的这里不重要先不去理。主要是volume,它绑定在el-slider上是该滑块的默认值,后面改音量会用到;currentTime和 totalTime用于动态显示当前视频播放的具体时间,以及视频的总时长; currentTimeVal和totalTimeVal是改变进度条、实现时长跳转的主要数据;isPaused用来决定自定义控件的图标是暂停还是播放。

7、播放暂停、进度条实时跟进,拉到进度条实现跳转

首先从简单的暂停和播放做起。在html结构中,我们应该根据isPaused来决定显示哪个图标,同时将前面显示时长的假数据换成data里的currentTime和totalTime:

<i :class="[isPaused ? 'el-icon-video-play' : 'el-icon-video-pause']" class=" icon-size" v-show="isPaused"></i>
<span>{{currentTime}} / {{totalTime}}</span>

然后给图标绑定上一个togglePlay的函数,来响应点击后实现播放或暂停:

            //控制视频的播放与暂停
            togglePlay(){
                this.isPaused = !this.isPaused
                if(!this.isPaused){
                    this.player.play()
                }else{
                    this.player.pause()
                }
            },

时间格式化:

//视频时长格式化
timeFormat(time){
    let hour = Math.floor(time / 3600),
        minute = Math.floor((time % 3600) / 60),
        second = Math.floor(time % 60);
    hour = hour < 10 ? "0" + hour : hour;
    minute = minute < 10 ? "0" + minute : minute;
    second = second < 10 ? "0" + second : second;
    return `${hour}:${minute}:${second}`;
},

获取视频总时长:

//获取视频的总时长和进度条最大值
getTotalTime(){
    this.totalTime = this.timeFormat(this.player.duration())
    this.totalTimeVal = Math.floor(this.player.duration())
},

更新视频当前播放时间、进度条实时跟进:

进度条的实现原理其实就是video的timeUpdate事件可以监测到视频的播放进度,在这个事件中可以一直获取到视频当前的播放时间,然后将这个值赋给滑块绑定的currentTimeVal,这样就能在播放过程中跟着改变滑块的位置了。

//更新视频当前播放时间
timeUpdate(){
    //如果当前正在拉到进度条,先停止更新当前播放时间,直接return结束这个函数
    //没有这一句会出现拉动进度条跳转失败的bug
    if(this.isDraging) return

    this.currentTime = this.timeFormat(this.player.currentTime())
    this.currentTimeVal = this.player.currentTime()              
    //当前时间更新到等于总时长时,要改变视频的播放状态按钮
    if(this.currentTime === this.totalTime){
        this.isPaused = true
    }               
},

拉到进度条跳转到指定位置播放:

这一功能的实现就是el-slider有一个change事件,在拖拽滑块松开鼠标后触发,这时只要在鼠标松开后,改变播放器的currentTime属性的值,

这里要稍微注意一下:因为我们在拉动进度条的时候,视频还处于播放状态,那么意味着上一步我们更新进度条时长的那个函数获取到的currentTime值也会改变el-slider的值,所以在上一步的函数中,我们需要监测进度条是否在拉动,如果是,我们应该停止执行那个函数。监听只需要在进度条外层的div上绑定一个mouseon和mousedown事件,鼠标按住时让isDragging等于false,然后在timeUpdate函数中通过isDragging来判断进度条是否处于拖拽的状态。

//进度条拉动时更新进度条值并从拉到的位置播放
progressUpdate(val){
    this.player.currentTime(val) 
    // 虽然mouseup已经可以改变isDraging的值,但下面这句不能少,不然视频播放结束再点击播放时,进度条不会回到最开始位置  
    this.isDraging = false           
},

8、更新速率、改变音量

//改变速率
setPlayRate(rate){
    this.rate = rate;
    this.player.playbackRate(rate);
    this.isShowRateOptions = false;
},
//改变音量
changeVolume(val){
    this.volume = val
    //由于h5规定volum的值在0-1之间,所以这里要对获取到的val做一个处理(滑块的val是从0-100)
    this.player.volume(val / 100)
},
//快进
forward(ct){
    this.progressUpdate(ct + 10)
},
//后退
back(ct){
    this.progressUpdate(ct - 10)
}

9、完整代码

VideoComponent:

<template>
  <div class="container">
    <video-player
      :options="videoOptions"    
      class="video-css"
    >
    </video-player>
  </div>
</template>
<script> 
  import videoPlayer from './videoPlayer.vue'
  export default {
    name: 'videoComponent',
    components:{videoPlayer},
    data(){
      return{
        videoOptions:{
          //controls配置项决定是否显示默认控件,因为这里要做自定义的控件,就不显示了
          controls:false,
          //fluid配置项根据外层css样式大小,自动填充宽高
          fluid:true,
          //sources配置项配置视频播放源
          sources:[
            {
              //播放源
              src:require('@/assets/kp.mp4'),
              //视频类型
              type:"video/mp4"
            }
          ],
          
        }
      }
    },
   
  }
</script>

<style scoped>
  .container{
    width: 800px;
    height: 100vh;
    display: flex;
    justify-content: center;
    align-items: center;
  }
  /* 视频宽高由此样式调整 */
  .video-css{
    width: 800px;
    height:auto;
  }
</style>

VideoPlayer:

<template>
  <div class="videoBox">
    <video 
        ref="videoPlayer"
        class="video-js"
        @canplay="getTotalTime"
        @timeupdate="timeUpdate"
    >
    </video>
    <!-- 自定义控件 -->
    <div class="controlBar">
        <div class="progressBar" @mousedown="isDraging = true" @mouseup="isDraging = false">
            <el-slider    
                v-model="currentTimeVal"
                :max="totalTimeVal"
                :format-tooltip="timeFormat"
                @change="progressUpdate"
            >
            </el-slider>
        </div>
        <div class="controlBtnBox">
            <div class="left">
                <i 
                    :class="[isPaused ? 'el-icon-video-play' : 'el-icon-video-pause']" 
                    class=" icon-size" 
                    @click="togglePlay()"
                >
                </i>
                <span>{{currentTime}}/{{totalTime}}</span>
            </div>
            <div class="right">
                <i class="el-icon-d-arrow-left icon-size" @click="back(player.currentTime())></i>
                <i class="el-icon-d-arrow-right icon-size" @click="forward(player.currentTime())></i>
                <i class="el-icon-bell icon-size"  @click="toShowVolumeBar"></i>
                <div id="volumeBar" v-show="isShowVolumeBar">
                    <el-slider
                        v-model="volume"
                        vertical
                        height="100px"
                        @input="changeVolume"
                    >
                    </el-slider>
                </div>
            </div>
            <div class="rateBox">
                <span @click="toShowOptions">{{ratedisplay}}</span>
                <div class="rateOptions" v-show="isShowRateOptions">
                    <span 
                        v-for="r,index in rateOptions" 
                        :key="index" 
                        @click="setPlayRate(r)"
                    >
                        {{r}}x
                    </span>
                </div>
            </div>
        </div>
    </div>
  </div>
</template>

<script>
    import 'video.js/dist/video-js.css';
    export default {
        name:'videoPlayer',
        //接收来自父组件videoComponent的video的具体配置信息,这样可以实现对VideoPlayer组件的复用
        props:{
            options: {
                type: Object
            }
        },
        //用计算属性来实现当速率为1时,显示“倍速”
        computed:{
            ratedisplay(){
                if(this.rate == 1){
                    return '倍速'
                }else{
                    return this.rate + 'x'
                }
            }
        },
        data(){
            return{
                
                player:null,
                //当前播放速率
                rate:1.0,
                //播放速率
                rateOptions:[2.0,1.75,1.5,1.0,0.75,0.5],
                //显示速率选项和音量选项
                isShowRateOptions:false,
                isShowVolumeBar:false,
                //音量
                volume:30,
                //是否暂停
                isPaused:true,
                //当前播放时间点和视频总时长
                currentTime:'00:00:00',
                totalTime:'00:00:00',
                //进度条的当前值,必须为number类型
                currentTimeVal:0,
                //进度条最大值,必须为number类型
                totalTimeVal:0,
                //是否在拖到进度条
                isDraging:false
            }
        },
        methods:{
            createVideoPlayer(){
                this.player = this.$videoJS(this.$refs.videoPlayer,this.options)
            },
            //显示速率选项
            toShowOptions(){
                this.isShowRateOptions = !this.isShowRateOptions
            },
            toShowVolumeBar(){
                this.isShowVolumeBar = !this.isShowVolumeBar
            },
            //视频时长格式化
            timeFormat(time){
                let hour = Math.floor(time / 3600),
                    minute = Math.floor((time % 3600) / 60),
                    second = Math.floor(time % 60);
                hour = hour < 10 ? "0" + hour : hour;
                minute = minute < 10 ? "0" + minute : minute;
                second = second < 10 ? "0" + second : second;
                return `${hour}:${minute}:${second}`;
            },
            //获取视频的总时长和进度条最大值
            getTotalTime(){
                this.totalTime = this.timeFormat(this.player.duration())
                this.totalTimeVal = Math.floor(this.player.duration())
            },
            //改变速率
            setPlayRate(rate){
                this.rate = rate;
                this.player.playbackRate(rate);
                this.isShowRateOptions = false;
            },
            //控制视频的播放与暂停
            togglePlay(){
                console.log()
                this.isPaused = !this.isPaused
                if(!this.isPaused){
                    this.player.play()
                }else{
                    this.player.pause()
                }
            },
             //更新视频当前播放时间
            timeUpdate(){
                //如果当前正在拉到进度条,先停止更新当前播放时间,直接return结束这个函数
                //没有这一句会出现拉动进度条跳转失败的bug
                if(this.isDraging) return

                this.currentTime = this.timeFormat(this.player.currentTime())
                this.currentTimeVal = this.player.currentTime()              
                //当前时间更新到等于总时长时,要改变视频的播放状态按钮
                if(this.currentTime === this.totalTime){
                    
                    this.isPaused = true
                }               
            },
            //进度条拉动时更新进度条值并从拉到的位置播放
            progressUpdate(val){
                this.player.currentTime(val) 
                // 虽然mouseup已经可以改变isDraging的值,但下面这句不能少,不然视频播放结束再点击播放时,进度条不会回到最开始位置  
                this.isDraging = false           
            },
            //改变音量
            changeVolume(val){
                this.volume = val
                //由于h5规定volum的值在0-1之间,所以这里要对获取到的val做一个处理(滑块的val是从0-100)
                this.player.volume(val / 100)
            },
            //快进
            forward(ct){
                this.progressUpdate(ct + 10)
            },
            //后退
            back(ct){
                this.progressUpdate(ct - 10)
            }
            
        },
        mounted(){
            this.createVideoPlayer()
        },
        beforeDestroy(){
            if(this.player){
                this.player.dispose()
            }
        }
    }
</script>

<style scoped>
    .videoBox{
        box-sizing: border-box;
        position: relative;
        width: 800px;
        height:500px;
        background-color: rgb(73, 156, 128);
    }
    .controlBar{
        width: 90%;
        height: 55px;
        position:absolute;
        bottom: 20px;
        left: 5%;
        background-color:#817f7f5a;
        box-sizing: border-box;
        color: rgb(233, 231, 231);
    }
    .progressBar{
        box-sizing: border-box;
        position: relative;
        width: 100%;
        padding: 10px;
        height: 10%;
        /* background-color: aliceblue; */
    }
    .controlBtnBox{
        box-sizing: border-box;
        width: 100%;
        height:60%;
        display: flex;
        justify-content: space-between;
        align-items: center;
     
    }
    /* 以下强行修改了el-slider样式 */
    .progressBar /deep/ .el-slider__bar{
        height: 3px;
        background-color: #409EFF;
        border-top-left-radius: 3px;
        border-bottom-left-radius: 3px;
        position: absolute;
    }
    .progressBar /deep/ .el-slider__button{
        height: 8px;
        width: 8px;
    }
    .progressBar /deep/ .el-slider__runway{
        margin-top:1px;
        margin-bottom: 1px;
        height: 3px;
    }
    .progressBar /deep/.el-slider__button-wrapper{
        width: 28px;
        height: 33px;
    }
     .icon-size{
       font-size: 25px;
       cursor: pointer;
    }
    .left{
        padding-left:10px ;
        width: 50%;
        display: flex;
        align-items: center;
    }
    .left span{       
        margin-left: 20px;
    }
    .right{
        width: 15%;
        display: flex;
        justify-content: space-around;
       position: relative;
    }
    .right i{
        display: block;
    }
    #volumeBar{
        width: 30px;
        height: 120px;
        background-color: #817f7f5a;
        position: absolute;
        top:-150px;
        right: 4px;
        display: flex;
        justify-content: center;
        align-items: center;
    }
    .rateBox{
        width: 15%;
        cursor: pointer;
    }
    .rateOptions{
        width: 80px;
        height: 180px;
        background-color: #817f7f5a;
        position: absolute;
        top:-185px;
        right: 50px;
        display: flex;
        flex-wrap: wrap;
        align-content: center;
    }
    .rateOptions span{
        display: block;
        width: 100%;
        height: 30px;
        text-align: center;
        line-height: 30px;
    }
    .rateOptions span:hover{
       background-color: #cec9c95a;
       color: #409EFF;
    }
</style>

App:

<template>
  <div id="app">
    <video-component></video-component>
  </div>
</template>

<script>
  import videoComponent from './components/videoComponent.vue'
  export default {
    name: 'App',
    components: {
      videoComponent
    }
  }
</script>
<style>
  *{
    padding: 0;
    margin: 0;
  }
  #app{
    display: flex;
    justify-content: center;
    width: 100%;
  }
</style>

结束,暂时还没发现什么bug。

但测试用的是的视频是放在本地的,没有涉及到资源加载,如果视频资源是走网络请求的话还得再改。。

参考链接:

以下是实现的时候参考的几篇文章

vue中自定义视频:

vue 中 自定义视频video_粥粥_的博客-CSDN博客

video.js使用教程:

Video.js使用教程一(详解)_lucky-peach的博客-CSDN博客

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/410315.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

成功解决:下载的谷歌浏览器,打开却是“2345浏览器”,方法亲测有效

今天打开谷歌浏览器使用&#xff0c;浏览器界面显示的2345浏览器&#xff0c;难道谷歌把2345收购了&#xff1f;应该不能&#xff0c;上网查找问题原因才发现&#xff0c;原来的谷歌首页是被劫持了。&#xff08;如果迫切解决问题&#xff0c;直接拉到底找方法&#xff09; 试了…

前端必备的谷歌浏览器JSON可视化插件:JSON-Handle

功能简介: 日常开发过程中,对接后台返回的数据接口时,数据格式常常是各种json格式字符串,在netWork里面查看十分不便,需要在网上找一个json格式化的工具再查看,然后再取数据字段,然后绑定到页面上,十分不便,推荐这么一款前端开发的浏览器插件工具给大家使用。 返回数…

【React-Hooks进阶】useState回调函数的参数 / useEffect发送网络请求/ useRef / useContext

前言 博主主页&#x1f449;&#x1f3fb;蜡笔雏田学代码 专栏链接&#x1f449;&#x1f3fb;React专栏 上篇文章初步学习了Hooks的基础知识 今天来深入学习Hooks的一些扩展知识 感兴趣的小伙伴一起来看看吧~&#x1f91e; 文章目录useState -回调函数的参数使用场景语法语法规…

el-switch

目录 在element ui中el-switch开关组件具有先改变开关值再传值的特点。&#xff08;先改后传&#xff09; 1、触发change事件时 2、绑定disabled属性写三元表达式时 3、解决办法 在element ui中el-switch开关组件具有先改变开关值再传值的特点。&#xff08;先改后传&#xf…

cron表达式 详解

corn表达式是&#xff1a;由若干数字、空格、符号按一定的规则&#xff0c;组成的一组字符串&#xff0c;从而表达时间的信息。 好像和正则表达式有点类似哈&#xff0c;都是一个字符串表示一些信息。Cron 表达式生成器&#xff1a; https://www.smart-tools.cn/cron简介Cron 表…

基于Java+MySQL 实现(Web)日程管理系统【100010222】

基于Java的日程管理系统开发 摘要 日程管理在日常生活中是十分普通的一件事情&#xff0c;人们无论在生活中还是工作中都会有大大小小、各种各样的事情安排&#xff0c;如果仅仅靠纸张或者自己记录这些事情&#xff0c;往往会遗忘。针对这样的痛点&#xff0c;本文提供了日程…

HTML小图标的使用(无需下载图标源码)

我们在浏览一个网页中&#xff0c;会遇到很多有趣的小图标&#xff0c;这些小图标与访问者形成了友好的互动&#xff0c;所以我们在开发中都会适当插入一些生动有趣的图标来吸引访问者。插入图标的网站有很多&#xff0c;我这里以阿里巴巴图标库&#xff08;iconfont-阿里巴巴矢…

Div标签里放img和span标签实现垂直水平居中

正常默认布局 代码实现&#xff1a; <div style"width: 400px; height:400px; background-color:blueviolet"><img style"width: 80px; height: 80px;" src"./picture.png"><span style"color:white;">我是span标…

Vue3 使用MD5加密(清晰明了)

概述 最近在想做个cloud项目,gitee上找了个模板项目&#xff0c;前端使用到vue3 typeScript、Element Plus、Vue Router、Pinia、Axios、i18n、Vite等技术&#xff0c;最近使用到vue3 MD5加密&#xff0c;顺便学习一下&#xff0c;在此总结一下&#xff0c;若有不足之处&…

《EDA前端软件开发工程师面试指南》

2023届EDA领域校招总结&#xff0c;完结撒花&#xff01;&#xff01;&#xff01; 目录 前言 一、EDA公司介绍 二、项目面试 1.自我介绍 2.项目深入 3.专业经验 4.成果和技能 5.对面试官有什么问题 三、C面试 1、高频考点 2、其他知识点 3、算法题 四、逻辑综合面…

【前端代码实例】使用HTML5+CSS3+JavaScript制作一个响应式的后台管理系统~带侧边导航栏仪表盘功能

bilibili在线视频演示地址: 【前端代码实例】使用HTML5+CSS3+JavaScript制作一个响应式的后台管理系统~带侧边导航栏仪表盘功能 效果图: 完整代码: <!DOCTYPE html> <html lang="en"> <head><meta charset="UTF-8"><meta…

前端已死?后端已亡?弯弯绕绕,几分真几分假

前段时间&#xff0c;我在掘金分享了一篇GPT-4 性能文章&#xff0c;也许是过于强大带来的威胁性&#xff0c;引来评论区的排队哀嚎&#xff08;如下图&#xff09;&#xff0c;所以“前端已死&#xff0c;后端已亡”这个概念真的成立吗&#xff1f;本文着重探讨前端。 前端和后…

Seurat | 强烈建议收藏的单细胞分析标准流程(基础质控与过滤)(一)

1. 写在前面 作为现在最火的scRNAseq分析包&#xff0c;Seurat当之无愧。&#x1f618; 本期开始我们介绍一下Seurat包的用法&#xff0c;先从基础质控和过滤开始吧。&#x1f973; 2.用到的包 rm(list ls()) library(Seurat) library(tidyverse) library(SingleR) library…

钉钉消息防撤回功能研究与实现-可查看历史消息[文件/图文/管理员/链接 撤回拦截]

研究背景 由于在某个大学进行上课的时候,遇到的某个老师,总是习惯发过的消息,到第二天的时候撤回,我们用聊天工具的其中一个原因,不就是因为可以随时去查看发过的消息吗&#xff0c;&#xff0c;而这位老师的操作,也让包括我在内的很多人感到痛不欲生。 想一想,当自己想要去看下…

Error in mounted hook: TypeError: Cannot read properties of undefined (reading isHiddenDay ) found

Error in mounted hook: TypeError: Cannot read properties of undefined (reading isHiddenDay ) found 无法读取未定义的属性‘isHiddenDay’. 在vue中使用fullcalendar在mounted钩子中渲染报错 背景 我在一个小demo中实现还是好好的&#xff0c;并且用的依赖都是6.x版本的…

初入了解——什么是VUE

个人简介&#xff1a;云计算网络运维专业人员&#xff0c;了解运维知识&#xff0c;掌握TCP/IP协议&#xff0c;每天分享网络运维知识与技能。座右铭&#xff1a;海不辞水&#xff0c;故能成其大&#xff1b;山不辞石&#xff0c;故能成其高。个人主页&#xff1a;小李会科技的…

vue3+ts项目里如何使用状态管理pinia以及数据持久化

我们都知道在vue2项目里搭配状态管理vuex3XX使用&#xff0c;效果极好的。 虽然在vue3项目里&#xff0c;vuex4XX仍能发挥余热&#xff0c;但由于缺乏对于ts的支持&#xff0c;使得类型推断陷入僵局。 所以在vue3ts的项目里&#xff0c;vuex渐被舍弃&#xff0c;pinia取而代之…

微信小程序入门教程 --(保姆级)

一、小程序注册 1、首先&#xff0c;到小程序官网注册自己的小程序账号&#xff0c;以下附有地址和教程&#xff1a; 小程序官网 进入这个地址之后&#xff0c;会看到这样的页面&#xff0c;点击立即注册按钮 2、在接下来的页面&#xff0c;选择小程序 3、然后根据提示完成三…

Web基础 HTML标签 六种超链接标签的使用方式

超链接标签&#xff08;重点&#xff09; 1、链接的语法格式&#xff1a; <a href"跳转目标链接" target"目标窗口的弹出方式"> 文本或图像 </a><a>标签里的a是单词anchor的的缩写&#xff0c;意为:锚。 两个属性的作用如下&#xff…

【前端灵魂脚本语言JavaScript⑤】——JS中数组的使用

&#x1f41a; 作者: 阿伟 &#x1f482; 个人主页: Flyme awei &#x1f40b; 希望大家多多支持&#x1f618;一起进步呀&#xff01; &#x1f4ac; 文章对你有帮助&#x1f449;关注✨点赞&#x1f44d;收藏&#x1f4c2; JavaScript数组的使用 文章目录JavaScript数组的使用…