当前位置: 首页 > 后端技术 > Node.js

2019Electron+Vue+AntDesignVue仿网易云音乐windows客户端实战分享

时间:2023-04-03 12:34:05 Node.js

特色视频播放器拖拽播放桌面歌词迷你模式自定义托盘右键菜单任务栏缩略图,歌曲操作音频可视化自动/手动查看更新Nedb数据库持久化自定义安装路径,安装界面美化在浏览器中启动客户端TravisCL,AppVeyor自动构建皮肤,下载,本地歌曲匹配,网络更改桌面通知,分享歌曲/歌单/MV/视频等QQ空间登录,私密Fm,歌单、专辑、歌手、排行榜、MV、视频、评论、搜索、用户、新闻、粉丝、关注、云盘、收藏……心跳模式、歌词微调、下一曲播放、追加播放、单曲循环、随机播放、列表循环路由引导、局部刷新、首页栏目调整和持久化……下载&&运行项目地址点击下载应用。macOS用户请下载dmg文件,Windows用户请下载exe文件,linux用户请下载AppImage文件。项目目前依赖网易CloudMusicApi,感谢网易CloudMusicApi作者。目前只适配windows。基于draggabilly封装一个可拖拽的对话框拖拽对话框的身影在项目中比较常见,比如首页的栏目调整对话框,喜欢的歌曲列表等,但是AntDesignVue提供的对话框组件并没有提供了一个拖拽功能,但是这个功能在项目中是必不可少的,只好自己动手了。封装一个drop-modal主要分为三步:让drop-modal有一个API,用a-modal在drop-modal上实现v-modelmodal第一次显示后,实例化Draggabilly$attrs,$slots,$listeners实现前两步的目的是保持写drop-modal和a-modal的语法基本一致。第一步相对简单。创建一个新的下拉模式。模板如下:实现v-model通常我们绑定v-model给a-modal设置一个值,通过修改该值来控制对话框的显示和隐藏,像这样

contents

所以我们也应该在Implementv-modelondrop-modal实现上。如果理解自定义组件的v-model是:value和@input的语法糖,实现起来并不难。首先定义一个道具值。为了保持单向数据流,定义一个计算属性currentValue,在其get方法中返回值,在set方法中触发自定义事件,最后将currentValue绑定到a-modal上。核心代码如下:...computed:{currentValue:{get(){returnthis.value},set(val){this.$emit('input',val)}}}实例化Draggabilly最后也是最重要的一步是通过watch监听value,当value为true时,实例化一个Draggabilly,使modal可拖动。这一步需要注意4点:确保在nextTick中实例化Draggabilly只在第一次显示时实例化Draggabilly确定可拖动dommodal的嵌套情况至此,封装好的drop-modal满足了所有的需求目前的项目,当然也有不足之处。总结封装drop-modal涉及的vue核心知识点——$attrs、$slots、$listeners、自定义组件v-model的还原、保持数据单向的计算属性、$nextTick。最终代码drop-modal**Vue优雅地“操作”dom调整列的顺序动态组件的核心思想是:动态组件,通过操作控制列的顺序数组导航的元素位置。navs中每个对象的key是componentName,hideMore控制是否在标题右侧显示更多链接。navs:[{name:'独家播放',key:'privateContent',hideMore:true},{name:'最新音乐',key:'新歌'},{name:'推荐播放列表',key:'播放列表'},{name:'推荐MV',key:'mv'},{name:'主播',key:'dj'}]
h5的拖拽api下一个问题就是如何操作数组navs~通过h5拖拽api改变元素的位置并将新的位置newNavs持久化,并使用当页面初始化时,newNavs可以渲染列组件。另外,结合了transition-group组件,让columnorder的变化有了过渡效果,这个过渡效果也很好的诠释了动画的意义——“解释刚刚发生了什么”核心代码如下:{{nav.name}}
data(){return{oldNav:0,newNav:0,}}methods:{dragstart(nav){this.oldNav=nav},dragenter(nav){this.newNav=navif(this.oldNav.name!==this.newNav.name){letoldIndex=this.navs.findIndex(nav=>nav.name==this.oldNav.name)letnewIndex=this.navs.findIndex(nav=>nav.name==this.newNav.name)letnewItems=[...this.navs]newItems.splice(oldIndex,1)newItems.splice(newIndex,0,this.oldNav)this.navs=[...newItems]window.localStorage.setItem('nav',JSON.stringify(this.navs))}}}最终效果如下:在其他项目中,还是有很多地方可以优雅的操作dom,原理也差不多,就是进度条等数据驱动的组件
通过操作变量bufferedOffsetWidth来控制缓冲条的宽度。又如私人调频点歌卡的切换。篇幅有限不想过多介绍。详情请到fm源码查看音频可视化。凉爽的!!!项目结合两者实现了如下效果:射线和动态粒子,不同的是我的射线更细更短更密(当然这些都是可控的),粒子是向内波动的。音频可视化的要点是使用canvas绘制基于AudioContext得到的频谱数据。首先获取频谱数据//getAPIletcontext=newAudioContext;//加载音频,它可以是dom或Audio的实例letaudio=newAudio("1.mp3");//创建节点letsource=context.createMediaElementSource(audio);让分析器=context.createAnalyser();//连接:源→分析器→目标source.connect(analyser);analyzer.connect(context.destination);//创建数据letoutput=newUint8Array(460);//获取频域数据analyzer.getByteFrequencyData(output)并打印输出,是这样的:使用canvas绘图先绘制静态外层光线,注意观察每条光线const{width,height}=document.getElementById('canvas')constdu=3//圆心到两条射线的距离所成的角,即射线之间的间隙constpotInt={x:width/2,y:height/2}//起始坐标,即画布的中心constR=150//半径constW=4//射线的宽度constL=32//射线的长度圆角:cxt.lineCap='round'渐变:cxt.createLinearGradient(x1,y1,x2,y2)起点:(Math.sin(((i*du)/180)*Math.PI)*R+potInt.y,-Math.cos(((i*du)/180)*Math.PI)*R+potInt.x)终点:(Math.sin(((i*du)/180)*Math.PI)*(R+L)+potInt.是的,-Math.cos(((i*du)/180)*Math.PI)*(R+L)+potInt.x)其中i是循环的索引360度数决定了起点和终点每个光线点,决定了渐变的起点和终点。通过moveTo、lineTo绘制然后扩大半径R让Rv=R+value,先hardcode1然后绘制叠加在渐变层上的纯色层。然后在requestAnimationFrame的执行函数中,可以根据光谱数据动态改变值来实现动画效果,但是需要注意的是,渐变层的光线总是要长于光线L的长度纯色层。Canvas动画当然少不了cxt.clearRect(0,0,width,height)和requestAnimationFrame!动画和粒子内波动的实现请参考musicView源码。另外还实现了一个类似熔岩喷发的效果,也很nice。渲染进程间即时通信项目的一个关键难点是如何在各个窗口中实时共享商店中的数据,例如歌词和播放状态。本来想通过主进程做中转,主进程笑着婉言谢绝:“渲染进程能搞定的事情就别烦我了,我很忙!”。最后,我把目光投向了localstorage。//触发更新statewindow.addEventListener('storage',()=>{initState()})//订阅mutation改变storagestore.subscribe((mutation,state)=>{localStorage.setItem(STOREKEY,JSON.stringify(getState(state)))})原理是订阅mutation改变storage,监听storage触发updatestate,写一个vuex插件实现这个功能。详见keep-state.jsusage:在store入口文件keep-state中引入,keep-state插件是在需要监控的模块mudules的执行函数中传入的一个函数,执行该函数的结果在初始化stroe时提供给插件。importpersistStatePluginfrom'./plugins/keep-state'constmyPlugin=persistStatePlugin(['User','play','Localsong','Setting','Update'])conststore=newVuex.Store({...plugins:[myPlugin]})electron实战桌面歌词实现桌面歌词需要注意以下几点:透明窗口可以锁定在其他窗口之上(锁定后忽略窗口内所有鼠标事件)以及如何出现在屏幕上确保通过设置transparent:true,alwaysOnTop:true,窗体可以透明和窗体在上面,分别。在透明表单中,需要注意的是html、body、#app等不能设置非透明背景色。通过setignoremouseeventsignoreapi切换锁定表单。至于窗体的初始位置,默认是屏幕中央。我希望它在任务栏上方水平和垂直居中,所以我需要获取屏幕的高度来做一些事情const{height}=electron.screen.getPrimaryDisplay().workAreaSize。最终表单初始化的核心代码如下:constoptions={frame:false,x:0,y:height-150,fullscreenable:false,minimizable:false,maximizable:false,transparent:true,alwaysOnTop:true,skipTaskbar:true,//任务栏不显示窗口面板closable:false}constwinURL=process.env.NODE_ENV==='development'?`http://localhost:9080/#desktop-lyric`:`file://${__dirname}/index.html#desktop-lyric`letlyricWindow=newBrowserWindow(options)lyricWindow.loadURL(winURL)的迷你模式electron实战mini模式主要分为两部分:主面板,当前播放列表面板,主面板分为两部分三个面板:歌曲缩略图,按住拖动歌曲信息和工具栏相关操作面板。实现要点是隐藏主窗口,显示小窗口(320*50)。通过win.setBounds(),在切换下拉列表时动态改变表单的大小?像这样:答案之一是通过一种形式来模拟它。菜单的显示和隐藏可以通过监听托盘的右键事件来切换,需要实时计算每个菜单出现的位置和边界条件。electron实战中自定义任务栏的缩略图工具栏taskbar工具栏?看起来是这样的,包括标题缩略图和歌曲的相关操作。好在electron提供了相关的API来实现这个功能ThumbnailToolbarElectron实战拖拽播放介绍拖拽播放有3种方式:拖拽文件到主窗口播放文件和拖拽到桌面快捷图标打开客户端播放客户端已经打开,将文件拖到桌面快捷方式图标上播放(不打开第二个实例)禁用默认行为请看一下默认拖文件到客户端之前实现了什么发生?是的,默认和拖拽文件到Chrome一样,像这样……你猜对了……!所以我们的第一步是禁用这些默认行为:实现播放我们通过监听window文件操作的drop事件来实现我们的打开。这只实现了拖放播放中的第一种情况。window.ondrop=openFilesOndrop另外两种情况需要在windows平台上操作process.argv。将文件拖到桌面快捷图标,打开客户端播放。首先我们来说说第二种情况。在主进程的就绪事件回调中,将process.argv赋值给全局变量globalglobal.argv=process.argv,在渲染过程中,通过electron的remote模块的getGlobal方法获取argv。process.argv初始化如下:["E:\electron-vue-cloud-music\网易云音乐.exe"]为客户端可执行文件路径。所以在执行handleWillOpenFiles方法之前判断数组的长度。在handleWillOpenFiles方法中过滤掉.mp3文件进行相关的解析和播放操作。移动到createdInitimport{remote}from'electron'conststartArgv=remote.getGlobal('argv')if(startArgv.length>1){handleWillOpenFiles(startArgv)}客户端已经打开,将文件拖到桌面快捷方式模式图标实现播放至于第三种情况,与第二种情况类似,区别在于argv参数的获取以及渲染进程如何获取argv。对于argv的获取,是在主进程app的二次监听回调中获取,通过自定义事件分发,渲染进程监听自定义事件接受。//主进程app.on('second-instance',(event,argv,workingDirectory)=>{if(mainWindow){mainWindow.webContents.send('open-files',{argv})}})//渲染过程import{ipcRenderer}from'electron'ipcRenderer.on('open-files',async(event,args)=>{let{argv}=argshandleWillOpenFiles(argv)})电子战斗自动/手动检查更新自动更新已被删除。我简单说一下如何手动检查更新。具体流程如下:开发,提交npm版本补丁&&gitpushoriginmaster&&gitpushoriginmaster&&gitpushorigin--tagsTravisCL,AppVeyor检测master变化自动在github上构建编辑发布远程版本用户/客户端触发检查更新客户端调用githubAPI获取最新的远程版本号和本地版本号对比例如需要更新,显示更新表单,引导下载安装下载完成后,关闭表单,打开下载的文件进行更新安装Electron实战Nedb数据库持久化Nedb数据库主要用于存储下载的歌单和歌词。被盗官网的介绍是:EmbeddedpersistentorinmemorydatabaseforNode.js,nw.js,Electronandbrowsers,100%JavaScript,nobinarydependency.API是MongoDB的一个子集,而且速度非常快。我的四级小白话翻译为Electron而生,无依赖,速度快,使用起来和mongoDb几乎一样。Electron实际打包自定义安装路径,安装界面美化自定义安装路径比较简单。在package.json中找到build字段,在"nsis"中添加如下代码:{"oneClick":false,//是否一键安装"allowToChangeInstallationDirectory":true//是否允许修改安装路径}的自定义安装界面可以通过一些开源工具快速实现,比如NSIS-UI,已经简单实现了,效果还可以:electron实战的自定义协议,在浏览器中实现客户端。通过app.setAsDefaultProtocolClient,可以实现自定义协议在浏览器中唤起客户端。如果你已经安装了,你可以尝试打开电子云音乐。Electron实战离线/在线检测和桌面通知可以通过窗口的在线和离线监控网络状态。通过navigator.onLine可以判断当前网络状态。桌面通知可以通过h5的Notification实现。window平台使用时,请务必设置appIdTravisCL,AppVeyor会自动构建并分享阮一峰的一篇文章,继续集成。测试窗口平台。至此电子云音乐的实战分享就基本结束了。项目中还有很多有趣的东西,但篇幅有限,不能面面俱到。本来想说说那些可敬的css,但是不玩lol的衰减游戏,我就失去峡谷宗师了!不排除会有第二集。。第一次写文章,感谢各位看官的阅读,谢谢。最后一句唠叨:“我觉得还不错,给我点个赞吧~”其他界面预览(多图警告)