# cloudMusic_vue **Repository Path**: wy-cp3/cloud-music_vue ## Basic Information - **Project Name**: cloudMusic_vue - **Description**: vue+vuex+vue-router+iconfont+html5+less+es6 - **Primary Language**: Unknown - **License**: MulanPSL-2.0 - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 1 - **Forks**: 0 - **Created**: 2022-04-14 - **Last Updated**: 2022-04-14 ## Categories & Tags **Categories**: Uncategorized **Tags**: vue3, vue2 ## README # 项目总结 [TOC] ## 一、vue3网易云音乐设计 ==特别提醒:接口必须一直运行== - 首先在`NeteaseCloudMusicApi-master`文件夹下打开终端,通过`node app.js`运行。 - 然后再`musicapp`文件夹下运行 `npm run serve`,便可以观看运行效果。 - 随后`npm ruun build`打包为上传服务器做准备。 - git上传gitee,利用gitee pages部署 - 修改 api/index.js ![image-20220414214904138](C:\Users\sxy\AppData\Roaming\Typora\typora-user-images\image-20220414214904138.png) - `npm ruun build`打包 - gitee新建仓库并上传 - 点击仓库服务中的Gitee Pages启动 ### 0、Vscode使用方法及其他补充 #### 0.1 Vue3 语法 - `ref` 响应式变量, `toRefs`变成响应式变量 - `reactive`响应式对象 #### 0.2 emmet语法 链接:https://blog.csdn.net/qq_44871797/article/details/111143872 ![image-20220410135844123](C:\Users\sxy\AppData\Roaming\Typora\typora-user-images\image-20220410135844123.png) ![image-20220410135856338](C:\Users\sxy\AppData\Roaming\Typora\typora-user-images\image-20220410135856338.png) ![image-20220410135927613](C:\Users\sxy\AppData\Roaming\Typora\typora-user-images\image-20220410135927613.png) ![image-20220410135934168](C:\Users\sxy\AppData\Roaming\Typora\typora-user-images\image-20220410135934168.png) ### 1、自适应rem格局 ```JavaScript function remSize(){ var deviceWidth = document.documentElement.clientWidth || window.innerWidth; // ?说window.innerWidth是为了应对谷歌浏览器和IE,看官方都支持 if(deviceWidth>=750){ deviceWidth = 750 } if (deviceWidth <= 320){ deviceWidth = 320 } document.documentElement.style.fontSize = (deviceWidth / 7.5) + 'px' document.querySelector('body').style.fontSize = 0.3 +'rem' // 设计稿是750px. // 设置1半的宽度,那么就是375px // 1rem == 100px的设计稿宽度 // 表达一半的宽度就是3.75rem } remSize() window.onresize = function(){ remSize() } ``` #### 1.1 Element.clientWidth 内联元素以及没有 CSS 样式的元素的 `**clientWidth**` 属性值为 0。`**Element.clientWidth**` 属性表示元素的内部宽度,以像素计。该属性包括内边距 padding,但不包括边框 border、外边距 margin 和垂直滚动条(如果有的话)。 ==当在根元素(\元素)上使用clientWidth时(或者在\上,如果文档是在quirks(怪异)模式下),将返回viewport的宽度(不包括任何滚动条). [This is a special case of `clientWidth`](https://www.w3.org/TR/2016/WD-cssom-view-1-20160317/#dom-element-clientwidth).== 该属性值会被四舍五入为一个整数。如果你需要一个小数值,可使用 [`element.getBoundingClientRect()`](https://developer.mozilla.org/zh-CN/docs/Web/API/Element/getBoundingClientRect)。 [语法] ```JavaScript var intElemClientWidth = element.clientWidth; ``` `intElemClientWidth` 是一个整数,表示元素的 `clientWidth`。`clientWidth` 是一个只读属性。 [示例] ![Image:Dimensions-client.png](https://developer.mozilla.org/@api/deki/files/185/=Dimensions-client.png) #### 1.2 @viewport **已废弃:** 该特性已经从 Web 标准中删除,虽然一些浏览器目前仍然支持它,但也许会在未来的某个时间停止支持,请尽量不要使用该特性。 **`@viewport`** 规则让我们可以对文档的大小进行设置 [viewport](https://developer.mozilla.org/zh-CN/docs/Glossary/Viewport) 。这个特性主要被用于移动设备,但是也可以用在支持类似“固定到边缘”等特性的桌面浏览器,如微软的Edge。 ==按百分比计算尺寸的时候,就是参照的**初始视口(viewport)**。初始视口指的是任何用户代理和样式对它进行修改之前的视口。桌面浏览器如果不是全屏模式的话,一般是基于窗口大小==。 在移动设备上(或者桌面浏览器的全屏模式),初始视口通常就是应用程序可以使用的屏幕部分。它可能是全屏或者减去由操作系统或者其它应用程序所占用的部分(例如状态栏)。 ```javascript @viewport { width: 100vw; /*将视口宽度设为与设备宽度相同*/ } ``` Copy to Clipboard [语法] @规则包含一组包含在CSS代码块中的嵌套的 [descriptor](https://developer.mozilla.org/zh-CN/docs/Glossary/Descriptor_(CSS))。 缩放因子`1.0` 或者 `100%` 表示不缩放,大于1表示放大,小于1表示缩小。 [描述符] 目前,大多数浏览器对 `@viewport` 的支持很差,Internet Explorer和Edge对其支持较好。但即使在这些浏览器中,也只有少量的描述符可用。如果浏览器不支持 `@viewport`,浏览器会忽略 `@viewport` 以及任何和其相关的描述符。 - [`min-width`](https://developer.mozilla.org/en-US/docs/Web/CSS/@viewport) 设置viewport的最小宽度 - [`max-width`](https://developer.mozilla.org/en-US/docs/Web/CSS/@viewport) 设置viewport的最大宽度 - [`width`](https://developer.mozilla.org/en-US/docs/Web/CSS/@viewport) 同时设置 `min-width` 和 `max-width` - [`min-height`](https://developer.mozilla.org/en-US/docs/Web/CSS/@viewport) 设置viewport的最小高度 - [`max-height`](https://developer.mozilla.org/en-US/docs/Web/CSS/@viewport) 设置viewport的最大高度 - [`height`](https://developer.mozilla.org/en-US/docs/Web/CSS/@viewport) 同时设置 `min-height` 和 `max-height` - [`zoom`](https://developer.mozilla.org/en-US/docs/Web/CSS/@viewport) 设置初始缩放系数 - [`min-zoom`](https://developer.mozilla.org/en-US/docs/Web/CSS/@viewport) 设置最小缩放系数 - [`max-zoom`](https://developer.mozilla.org/en-US/docs/Web/CSS/@viewport) 设置最大缩放系数 - [`user-zoom`](https://developer.mozilla.org/en-US/docs/Web/CSS/@viewport) 设置用户是能更改缩放系数 - [`orientation`](https://developer.mozilla.org/en-US/docs/Web/CSS/@viewport) 设置文档的方向 - [`viewport-fit` (en-US)](https://developer.mozilla.org/en-US/docs/Web/CSS/@viewport) 控制文档在非矩形显示器上的显示。 #### 1.3 Window.innerWidth ==只读的 [`Window`](https://developer.mozilla.org/zh-CN/docs/Web/API/Window) 属性 `**innerWidth**` 返回以像素为单位的窗口的内部宽度。如果垂直滚动条存在,则这个属性将包括它的宽度。== 更确切地说,`innerWidth` 返回窗口的 [layout viewport (en-US)](https://developer.mozilla.org/en-US/docs/Glossary/Layout_viewport) 的宽度。 窗口的内部高度——布局视口的高度——可以从 [`innerHeight`](https://developer.mozilla.org/zh-CN/docs/Web/API/Window/innerHeight) 属性中获取到。 [语法](https://developer.mozilla.org/zh-CN/docs/Web/API/Window/innerWidth#语法) ```javascript let intViewportWidth = window.innerWidth; ``` [值](https://developer.mozilla.org/zh-CN/docs/Web/API/Window/innerWidth#值) 一个整数型的值表示窗口的布局视口宽度是以像素为单位的。这个属性是只读的,并且没有默认值。 若要更改窗口的宽度,请使用 [`Window`](https://developer.mozilla.org/zh-CN/docs/Web/API/Window) 的方法来调整窗口的大小,例如[`resizeBy()`](https://developer.mozilla.org/zh-CN/docs/Web/API/Window/resizeBy) 或者 [`resizeTo()`](https://developer.mozilla.org/zh-CN/docs/Web/API/Window/resizeTo)。 [使用说明](https://developer.mozilla.org/zh-CN/docs/Web/API/Window/innerWidth#使用说明) ==如果你需要获取除去滚动条和边框的窗口宽度,请使用根元素 [`html`](https://developer.mozilla.org/zh-CN/docs/Web/HTML/Element/html)的[`clientWidth`](https://developer.mozilla.org/zh-CN/docs/Web/API/Element/clientWidth) 属性。== `innerWidth` 属性在任何表现类似于窗口的任何窗口或对象(例如框架或选项卡)上都是可用的。 [示例](https://developer.mozilla.org/zh-CN/docs/Web/API/Window/innerWidth#示例) ```javascript // 返回视口的宽度 var intFrameWidth = window.innerWidth; // 返回一个框架集内的框架的视口宽度 var intFrameWidth = self.innerWidth; // 返回最近的父级框架集的视口宽度 var intFramesetWidth = parent.innerWidth; // 返回最外层框架集的视口宽度 var intOuterFramesetWidth = top.innerWidth; ``` ### 2、阿里图标库iconfont 1. 更新项目库图标时,要将`public`中的链接更新 ```javascript <%= htmlWebpackPlugin.options.title %>
``` 2. SVG图使用fill改变颜色 ```css .icon{ fill: #ccc; } ``` 3. 图标用`width`设置大小,而不是`font-size` ```less ``` ### 3、封装 #### 3.0 组件化编程 ```JavaScript ``` #### 3.1 swiper轮播组件 1. 版本混乱,按照官方给出方法仍然报错,降低版本等等问题;在后续项目中考虑另一种轮播方法 ``` npm install swiper@5 --save ``` 指定版本5,这样避免报错 2. 具体实现 ```JavaScript ``` 3. 弹幕说==考虑使用Vant==引入swiper 官网链接:https://www.wenjiangs.com/doc/vant-quickstart ==vant的Swipe轮播==:https://www.wenjiangs.com/doc/vant-swipe 引入 ```js import { createApp } from 'vue'; import { Swipe, SwipeItem } from 'vant'; const app = createApp(); app.use(Swipe); app.use(SwipeItem); ``` 基础用法 每个 SwipeItem 代表一张轮播卡片,可以通过 `autoplay` 属性设置自动轮播的间隔。 ```html 1 2 3 4 ``` 图片懒加载 当 Swipe 中含有图片时,可以配合 [Lazyload](https://www.wenjiangs.com/doc/vant-lazyload) 组件实现图片懒加载。 ```html import { createApp } from 'vue'; import { Lazyload } from 'vant'; const app = createApp(); app.use(Lazyload); export default { data() { return { images: [ 'https://img.yzcdn.cn/vant/apple-1.jpg', 'https://img.yzcdn.cn/vant/apple-2.jpg', ], }; }, }; ``` 监听 change 事件 ```html 1 2 3 4 import { Toast } from 'vant'; export default { methods: { onChange(index) { Toast('当前 Swipe 索引:' + index); }, }, }; ``` 纵向滚动 设置 `vertical` 属性后滑块会纵向排列,此时需要指定滑块容器的高度。 ```html 1 2 3 4 ``` 自定义滑块大小 滑块默认宽度为 `100%`,可以通过 `width` 属性设置单个滑块的宽度。纵向滚动模式下,可以通过 `height` 属性设置单个滑块的高度。 ```html 1 2 3 4 ``` > 目前不支持在循环滚动模式下自定义滑块大小,因此需要将 loop 设置为 false。 4. Vant的 Lazyload懒加载 引入 `Lazyload` 是 `Vue` 指令,使用前需要对指令进行注册。 ```js import { createApp } from 'vue'; import { Lazyload } from 'vant'; const app = createApp(); app.use(Lazyload); // 注册时可以配置额外的选项 app.use(Lazyload, { lazyComponent: true, }); ``` 代码演示 基础用法 将 `v-lazy` 指令的值设置为你需要懒加载的图片。 ```html export default { data() { return { imageList: [ 'https://img.yzcdn.cn/vant/apple-1.jpg', 'https://img.yzcdn.cn/vant/apple-2.jpg', ], }; }, }; ``` 背景图懒加载 和图片懒加载不同,背景图懒加载需要使用 `v-lazy:background-image`,值设置为背景图片的地址,需要注意的是必须声明容器高度。 ```html
``` 组件懒加载 将需要懒加载的组件放在 `lazy-component` 标签中,即可实现组件懒加载。 ```js // 注册时设置`lazyComponent`选项 app.use(Lazyload, { lazyComponent: true, }); ``` #### 3.2 axios封装、api接口封装 - 此处封装的不好,借鉴第二个项目中的封装。 - 设置 `baseUrl` ```JavaScript import axios from 'axios'; let baseUrl = 'http://localhost:3000' // 获取轮播图的api,type:资源类型,对应以下类型,默认为 0 即PC // 1: android;2: iphone;3: ipad export function getBanner(type=0){ return axios.get(`${baseUrl}/banner?type=${type}`) } // 获取推荐歌单,可选参数 : limit: 取出数量 , 默认为 10 export function getMusicList(limit=10){ return axios.get(`${baseUrl}/personalized?limit=${limit}`) } // 获取歌单的详情 export function getPlaylistDetail(id){ return axios.get(`${baseUrl}/playlist/detail?id=${id}`) } // 获取歌词 export function getLyric(id){ return axios.get(`${baseUrl}/lyric?id=${id}`) } // 搜索歌曲 export function searchMusic(keyword){ return axios.get(`${baseUrl}/search?keywords=${keyword}`) } // 手机登录 export function phoneLogin(phone,password){ return axios.get(`${baseUrl}/login/cellphone?phone=${phone}&password=${password}`) } // 获取用户的详情 export function userDetail(uid){ return axios.get(`${baseUrl}/user/detail?uid=${uid}`) } export default { getBanner,getMusicList,getPlaylistDetail,getLyric,searchMusic,phoneLogin, userDetail } ``` #### 3.3 swiper演示显示多个sliders https://www.swiper.com.cn/demo/index.html ![image-20220410164234440](C:\Users\sxy\AppData\Roaming\Typora\typora-user-images\image-20220410164234440.png) ```javascript
Slide 1
Slide 2
Slide 3
Slide 4
Slide 5
Slide 6
Slide 7
Slide 8
Slide 9
``` #### 3.4 登陆页面 ```JavaScript // router/api/index.js import { createRouter, createWebHistory } from 'vue-router' import Home from '../views/Home.vue' import store from '@/store/index.js' const routes = [ { path: '/login', name: 'login', // route level code-splitting // this generates a separate chunk (about.[hash].js) for this route // which is lazy-loaded when the route is visited. component: () => import(/* webpackChunkName: "about" */ '../views/Login.vue') }, ] const router = createRouter({ history: createWebHistory(process.env.BASE_URL), routes }) export default router // vuex/store/index.js import { createStore } from 'vuex' import api from '@/api/index.js' export default createStore({ state: { user:{ isLogin:false, account:{}, userDetail:{} } }, getters:{}, mutations: { setUser(state,value){ state.user = value } }, actions: { async phoneLogin(content,payload){ // console.log(payload) let result = await api.phoneLogin(payload.phone,payload.password) if(result.data.code==200){ content.state.user.isLogin = true; content.state.user.account = result.data.account let userDetail = await api.userDetail(result.data.account.id) content.state.user.userDetail = userDetail.data; localStorage.userData = JSON.stringify(content.state.user) console.log(userDetail) content.commit('setUser',content.state.user) } console.log(result) return result } }, modules: { } }) // components/Login.vue ``` ### 4、CSS技巧 #### 4.1 less 1. 嵌套式样式 ```less ``` #### 4.2 横轴分布与侧轴居中 ```less //横轴 justify-content: space-between; /* 均匀排列每个元素 首个元素放置于起点,末尾元素放置于终点 */ justify-content: space-around; /* 均匀排列每个元素 每个元素周围分配相同的空间 */ justify-content: space-evenly; /* 均匀排列每个元素 每个元素之间的间隔相等 */ justify-content: stretch; /* 均匀排列每个元素 //侧轴 align-items: center; ``` #### 4.3 全局字体 ```less font-family: '微软雅黑'; ``` #### 4.4 多行文本省略 - 基于行数截断 ```css .name{ height: 0.6rem; width: 100%; font-size: 0.24rem; line-height: 0.3rem; position: relative; -webkit-line-clamp: 2; display: -webkit-box; -webkit-box-orient: vertical; overflow: hidden; text-overflow: ellipsis; } ``` ```JavaScript .description{ font-size: 0.24rem; color: #ccc; overflow: hidden; text-overflow:ellipsis; display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; } ``` #### 4.5 CSS filter模糊度及z-index ![image-20220411203850012](C:\Users\sxy\AppData\Roaming\Typora\typora-user-images\image-20220411203850012.png) ```css .bg{ position: fixed; left: 0; top: 0; width: 7.5rem; height: auto; z-index: -1; //在底层 filter:blur(40px) //模糊度 } ``` #### 4.6 跑马灯 ```css
{{playDetail.al.name}}
marquee{ width: 5rem; flex: 1; } ``` #### 4.7 播放词条的旋转 ```javascript // .needle{ width:2.5rem; height: auto; position: absolute; left: 3.5rem; transform-origin: 0.3rem 0; transform: rotate(-10deg); //初始角度 transition: all 1s; z-index: 10; //为了在上层,让播放词条不被覆盖 } .needle.active{ width:2.5rem; height: auto; position: absolute; left: 3.5rem; transform-origin: 0.3rem 0; transform: rotate(15deg); //旋转后角度 transition: all 1s; z-index: 10; } ``` #### 4.8 calc()动态计算公式 说明: 1、任何长度值都可以使用calc()函数进行计算; 2、calc()函数支持 "+", "-", "*", "/" 运算; 3、calc()函数使用标准的数学运算优先级规则; 4、需要注意的是,运算符前后都需要保留一个空格,例如:width: calc(100% - 10px); image-20220412225556104 ```JavaScript .disc{ width: 5.5rem; height: auto; position: absolute; left: calc(50% - 2.75rem) ; top: 2.5rem; } .playImg{ width: 3.4rem; height: 3.4rem; border-radius: 1.7rem; position: absolute; left: calc(50% - 1.7rem); top: 3.55rem; } ``` #### 4.9 offsetWidth与offset Height、offsetLeft与offsetTop、scrollTop 1. 总览 ![img](https://img-blog.csdn.net/20180814155356917?watermark/2/text/aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzM0MTE4ODE5/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70) ​ 如图所示中间是个div,那么div.offsetTop = 此div border外到父元素border内且都不包括border ​ 那么div.scrollTop = 此div border(包括border)到窗体上边界(如果div比较大,使得外部产生滚动条,那么这个距离将会随着滚动条变化) 2. offsetWidth与offset Height ![Image:Dimensions-offset.png](https://developer.mozilla.org/@api/deki/files/186/=Dimensions-offset.png) 3. offsetTop和scrollTop区别 - 如图所示中间是个div,那么div.offsetTop = 此div border外到父元素border内且都不包括border 那么div.scrollTop = 此div border(包括border)到窗体上边界(如果div比较大,使得外部产生滚动条,那么这个距离将会随着滚动条变化) - scrollTop:当前元素顶端距离窗口顶端距离,鼠标滚轮会影响其数值. ==是这些元素中唯一一个可写可读的==。 下面的图是用微信截图随便画的:D(不小心混入了一个光标。。 ![img](https://img2018.cnblogs.com/blog/670878/201810/670878-20181008162649050-2000307786.png) ![img](https://img2018.cnblogs.com/blog/670878/201810/670878-20181008162350402-98055053.png)![img](https://img2018.cnblogs.com/blog/670878/201810/670878-20181008170134916-81833900.png) 所以当滚动条在最顶端的时候, scrollTop=0 ,当滚动条在最低端的时候, scrollTop=115 这个115怎么来的(滚动条高度是15,我量的),见下图。(实为我主观臆测,不保证准确性。。。_(:з」∠)_ ![img](https://img2018.cnblogs.com/blog/670878/201810/670878-20181008170955794-1723253104.png) scrollTop是一个整数。 如果一个元素不能被滚动,它的`scrollTop`将被设置为`0`。 设置scrollTop的值小于0,`scrollTop` 被设为`0。` 如果设置了超出这个容器可滚动的值, `scrollTop` 会被设为最大值。 4. 汇总 ```JavaScript ``` #### 4.10 flex-wrap & ==flex:1== [CSS](https://developer.mozilla.org/zh-CN/docs/Web/CSS) 的 **`flex-wrap`** 属性指定 flex 元素单行显示还是多行显示。如果允许换行,这个属性允许你控制行的堆叠方向。 ```css flex-wrap: nowrap; /* Default value */ flex-wrap: wrap; flex-wrap: wrap-reverse; /* Global values */ flex-wrap: inherit; flex-wrap: initial; flex-wrap: revert; flex-wrap: unset; ``` - nowrap flex 的元素被摆放到到一行,这可能导致 flex 容器溢出。**cross-start** 会根据 [`flex-direction`](https://developer.mozilla.org/zh-CN/docs/Web/CSS/flex-direction) 的值等价于 **start** 或 **before**。为该属性的默认值。 - wrap flex 元素 被打断到多个行中。**cross-start** 会根据 [`flex-direction`](https://developer.mozilla.org/zh-CN/docs/Web/CSS/flex-direction) 的值等价于 **start** 或**before**。**cross-end** 为确定的 **cross-start** 的另一端。 - wrap-reverse 和 wrap 的行为一样,但是 **cross-start** 和 **cross-end** 互换。 #### 4.11 set去重 - 对于重复的历史搜索进行去重,并控制显示的最大长度 ```JavaScript methods: { saveKeyWord: async function () { this.keywordList.push(this.searchKeyword) this.keywordList = Array.from(new Set(this.keywordList)) if (this.keywordList.length > 10) { this.keywordList = this.keywordList.slice(this.keywordList.length - 10, this.keywordList.length) } localStorage.keywordList = JSON.stringify(this.keywordList) let result = await searchMusic(this.searchKeyword) this.searchSongs = result.data.result.songs console.log(this.searchSongs) }, ``` ### 5、JavaScript技巧或函数 #### 5.1 修改数字 `Number.prototype.toFixed()`确定小数点后位数。 ```JavaScript function changeValue (num){ let res = 0 if(num>=100000000){ res = num/100000000 res = res.toFixed(2) + '亿' }else if(num>10000){ res = num/10000 res = res.toFixed(2) + '万' } return res } ``` #### 5.2 正则分割歌词 - 以换行\n来切割歌词 ```javascript let arr = state.lyric.split(/\n/igs).map((item,i,arr)=>{} ``` #### 5.3 处理歌词样式 - 原始歌词样式 ![image-20220413212839237](C:\Users\sxy\AppData\Roaming\Typora\typora-user-images\image-20220413212839237.png) - JavaScript字符串处理 ```JavaScript // vuex/store/index.js getters:{ lyricList:function(state){ let arr = state.lyric.split(/\n/igs).map((item,i,arr)=>{ let min = parseInt(item.slice(1,3)) ; let sec = parseInt(item.slice(4,6)) ; let mill = parseInt(item.slice(7,10)); return { min,sec,mill, lyric:item.slice(12,item.length), content:item, time:mill+sec*1000+min*60*1000 } }) //components/playMusic.vue

{{item.lyric}}

import {mapState} from 'vuex' computed:{ ...mapState(['lyric','currentTime','playlist','playCurrentIndex']), }, ``` - 更改后效果 ![image-20220413213456401](C:\Users\sxy\AppData\Roaming\Typora\typora-user-images\image-20220413213456401.png) #### 5.4 切换上一首歌词 ```JavaScript // store/index.js setPlayIndex(state,value){ state.playCurrentIndex = value }, // components/playMusic.vue
``` #### 6.3 v-for循环拿取数据 ```html
``` #### 6.4 ==Vuex、store== - 使用技巧 ```JavaScript // commit 使用 同步mutation increment: () => store.commit('increment'), // dispatch 使用 异步action asyncIncrement: () => store.dispatch('asyncIncrement') ``` - 整体引入 ```javascript import { createStore } from 'vuex' import api from '@/api/index.js' export default createStore({ state: { playlist:[{ name: "彩券", id: 1486513704, al:{ id: 96680121, name: "彩券", pic: 109951165386012140, picUrl: "http://p3.music.126.net/NP1Zg57UUbHNzAhw7n-LeA==/109951165386012146.jpg", pic_str: "109951165386012146" }}], playCurrentIndex:0, lyric:'', currentTime:0, intervalId:0, user:{ isLogin:false, account:{}, userDetail:{} } }, getters:{ lyricList:function(state){ let arr = state.lyric.split(/\n/igs).map((item,i,arr)=>{ let min = parseInt(item.slice(1,3)) ; let sec = parseInt(item.slice(4,6)) ; let mill = parseInt(item.slice(7,10)); return { min,sec,mill, lyric:item.slice(12,item.length), content:item, time:mill+sec*1000+min*60*1000 } }) arr.forEach((item,i)=>{ if(i==0){ item.pre = 0; }else{ item.pre = arr[i-1].time } }) return arr } }, mutations: { setPlaylist:function(state,value){ state.playlist = value }, pushPlaylist:function(state,value){ state.playlist.push(value) }, setPlayIndex(state,value){ state.playCurrentIndex = value }, setLyric(state,value){ state.lyric = value }, setCurrentTime(state,value){ state.currentTime = value }, setUser(state,value){ state.user = value } }, actions: { async reqLyric(content,payload){ console.log(payload) let result = await api.getLyric(payload.id) content.commit('setLyric',result.data.lrc.lyric) console.log(result.data.lrc.lyric) }, async phoneLogin(content,payload){ // console.log(payload) let result = await api.phoneLogin(payload.phone,payload.password) if(result.data.code==200){ content.state.user.isLogin = true; content.state.user.account = result.data.account let userDetail = await api.userDetail(result.data.account.id) content.state.user.userDetail = userDetail.data; localStorage.userData = JSON.stringify(content.state.user) console.log(userDetail) content.commit('setUser',content.state.user) } console.log(result) return result } }, modules: { } }) ``` - Vue2在子组件使用 `store` ==先import引入,再…结构== ```javascript import {mapState, mapMutations} from 'vuex' export default { data () { return { paused: true, show: false } }, computed: { ...mapState(['playlist', 'playCurrentIndex']) }, mounted () { console.log([this.$refs.audio]) this.$store.dispatch('reqLyric', { id: this.playlist[this.playCurrentIndex].id }) }, updated () { console.log(this.playlist[this.playCurrentIndex]) this.$store.dispatch('reqLyric', { id: this.playlist[this.playCurrentIndex].id }) }, } ``` - Vue3正规使用Vuex,`useStore` ## 组合式API[#](https://vuex.vuejs.org/zh/guide/composition-api.html#组合式api) 可以通过调用 `useStore` 函数,来在 `setup` 钩子函数中访问 store。这与在组件中使用选项式 API 访问 `this.$store` 是等效的。 ```javascript import { useStore } from 'vuex' export default { setup () { const store = useStore() } } ``` ## 访问 State 和 Getter[#](https://vuex.vuejs.org/zh/guide/composition-api.html#访问-state-和-getter) 为了访问 state 和 getter,需要创建 `computed` 引用以保留响应性,这与在选项式 API 中创建计算属性等效。 ```javascript import { computed } from 'vue' import { useStore } from 'vuex' export default { setup () { const store = useStore() return { // 在 computed 函数中访问 state count: computed(() => store.state.count), // 在 computed 函数中访问 getter double: computed(() => store.getters.double) } } } ``` ## 访问 Mutation 和 Action[#](https://vuex.vuejs.org/zh/guide/composition-api.html#访问-mutation-和-action) 要使用 mutation 和 action 时,只需要在 `setup` 钩子函数中调用 `commit` 和 `dispatch` 函数。 ```javascript import { useStore } from 'vuex' export default { setup () { const store = useStore() return { // 使用 mutation increment: () => store.commit('increment'), // 使用 action asyncIncrement: () => store.dispatch('asyncIncrement') } } } ``` #### 6.5 v-if & v-else & v-show - 播放与暂停图标的切换 ```javascript // export default { data () { return { paused: true, show: false } }, ``` - 通过v-show设置歌词组件的显示 ```javascript
// export default { data () { return { paused: true, show: false } }, ``` - 同一位置上 播放与暂停按钮 的样式切换 ```css // .icon{ fill: #fff; width: 0.5rem; height: 0.5rem; } .play.icon{ width: 1rem; height: 1rem; } ``` #### 6.6 父向子组件传值 - 传递playList,让歌词组件获取歌词数据 ```javascript //父组件 //子组件
export default { props:['playDetail','paused','play'], ``` #### 6.7 子向父组件传值 - 歌词组件向父组件传递返回按钮,来控制子组件(歌词组件)的显示(v-show) ```javascript //子组件
//父组件 ``` #### 6.8 axios(api接口)+vuex(store) - 获取歌词 ```JavaScript // axios/api/index.js import axios from 'axios'; export function getLyric(id){ return axios.get(`${baseUrl}/lyric?id=${id}`) } export default { getBanner,getMusicList,getPlaylistDetail,getLyric,searchMusic,phoneLogin, userDetail } // vuex/store/index.js import { createStore } from 'vuex' import api from '@/api/index.js' export default createStore({ state: {}, mutations: { setLyric(state,value){ state.lyric = value }, }, actions: { async reqLyric(content,payload){ console.log(payload) let result = await api.getLyric(payload.id) // commit 使用 同步mutation // dispatch 使用 异步action content.commit('setLyric',result.data.lrc.lyric) console.log(result.data.lrc.lyric) }, } // ``` #### 6.9 ==watch监听== - watch监听时间,控制歌词滚动和高亮 ```JavaScript // components/playMusic.vue

{{item.lyric}}

import { watch } from 'vue' import {mapState} from 'vuex' export default { props:['playDetail','paused','play'], data:function(){ return { isLyric:true, } }, computed:{ ...mapState(['lyric','currentTime','playlist','playCurrentIndex']), }, watch:{ currentTime:function(newValue){ console.log(newValue) console.log([this.$refs.playLyric]) let p = document.querySelector('p.active') if(p){ let offsetTop = p.offsetTop; this.$refs.playLyric.scrollTop = p.offsetTop; //直接将活跃歌词移到屏幕中央 console.log([p]) } } }, } ``` #### 6.10 vue-router之路由守卫 - beforeEnter在登陆页面进行操作 ```JavaScript import { createRouter, createWebHistory } from 'vue-router' import Home from '../views/Home.vue' import store from '@/store/index.js' const routes = [ { path: '/login', name: 'login', // route level code-splitting // this generates a separate chunk (about.[hash].js) for this route // which is lazy-loaded when the route is visited. component: () => import(/* webpackChunkName: "about" */ '../views/Login.vue') }, { path: '/me', name: 'me', beforeEnter:(to,from,next)=>{ console.log(store.state.user) if(store.state.user.isLogin){ next() }else{ next("/login") } }, // route level code-splitting // this generates a separate chunk (about.[hash].js) for this route // which is lazy-loaded when the route is visited. component: () => import(/* webpackChunkName: "about" */ '../views/Me.vue') } ] ```