# miusic_project_0425 **Repository Path**: newsegmentfault/miusic_project_0425 ## Basic Information - **Project Name**: miusic_project_0425 - **Description**: 云音乐小程序qweqwe - **Primary Language**: Unknown - **License**: Not specified - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 0 - **Forks**: 0 - **Created**: 2022-10-28 - **Last Updated**: 2022-11-01 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README # day01 ### 介绍了课件的目录 如何跑起来 miusic 项目,先启动node服务,在启动微信小程序 ### 小程序简介 是什么? 为什么? 怎么玩? 小程序:依托于微信环境的一个小应用,不需要下载(需要下载,2M以内) 为什么要有小程序? 功能生活中必须有,但是不常用,下载APP不方便,此时最适合小程序 怎么玩? 在微信开发者工具中写小程序的代码 ### 小程序账号注册 和 IDE安装 一个邮箱对应一个小程序(如果注册错了需要重新找一个邮箱),注册小程序账号的时候,选择个人(不要选择企业),注册小程序账号的目的是为了 AppId,每一个小程序都会有一个 AppId 微信开发者工具 安装路径不要包含特殊字符 ### 复习flex布局 分为 `容器` 属性和 `项目` (每个成员)的属性 容器的属性: ```css display: flex; flex-direction: row; // 主轴方向 flex-wrap: nowrap; // nowrap 不换行 justify-content: center; // 主轴的对其方式 align-items: center; // 侧轴的对其方式 align-content: center; // 多行主轴的对其方式 ``` 项目的属性: ```css order: 1; // 排序,数值越大越靠后(靠后指的是靠主轴的结束位置) flex-grow: 1; // 放大比例 flex-shrink: 1; // 缩小比例 flex-basis: auto; // 分配剩余空间之前定宽 (auto就是0%) flex: 1; // flex-grow: 1; flex-shrink: 1; flex-basis: 0%; align-self: center; // 当前项目的对其方式 ``` ### IDE介绍 模拟器、编辑器、调试器至少有一个 编译、预览、真机调试、清除缓存 上传、版本管理、详情 > 注意: 详情中本地调试里可以配置小程序的版本 ### 配置文件 一般项目中.json文件都是配置文件 project.config.json 项目的基础配置文件 project.private.config.json 当前项目私有的配置文件,如果这个文件中有和 基础配置文件 相同的配置项的时候,会覆盖 基础配置文件 的配置项 sitemap.json 配置小程序和页面是否可以被微信搜索到 ### 应用相关文件介绍 * app.js 应用的入口文件,调用了 App() 函数,创建应用,小程序提供的方法 * app.json 应用的配置项 ```js { "pages": [ "pages/index/index" ], "window": { "navigationBarBackgroundColor": "#fff", // 只能16进制颜色 "navigationBarTitleText": "胡歌", // 随便写 "navigationBarTextStyle": "black" // 只能白色和黑色 } } ``` pages 配置页面 window 配置窗口 > json是配置文件,字符串需要用双引号 * app.wxss - 全局css设置 书写一个页面,在 pages/index/ 路径下有4个文件,每次创建page自动会啊在 app.json 的 `pages` 选项中自动创建 4个文件分别是 .js文件、.json文件、.wxml 文件、.wxss文件 .js 写js内容 - 包含数据和方法 .json文件 - 当前页面的配置项 .wxml - 微信组件模板,使用 view 和 text 组件 .wxss - css样式 > 注意:数据如果在模板中使用,直接使用插值语法,包括标签里面和标签的**属性**都是 ### count案例 给元素绑定事件 `bindtap`,书写回调,回调直接卸载配置项当中 在方法中获取数据`this.data.xxxx` 在方法中修改数据 `this.setData({ xxx: xxxx })` ### 获取用户信息 ```js wx.getUserProfile({ desc: '获取个人信息,是否同意?', success: (arg) => { console.log(arg) // this.data.nickName = arg.userInfo.nickName // this.data.avatarUrl = arg.userInfo.avatarUrl this.setData({ nickName: arg.userInfo.nickName, avatarUrl: arg.userInfo.avatarUrl }) } }) ``` #### 跳转页面 ```js // navigateTo 跳转页面 - 有历史记录 - 不会销毁页面 // 配置项url的时候,一定要携带根路径 wx.navigateTo({ url: '/pages/logs/logs', }) // redirectTo 重定向页面 - 没有历史记录,相当于刷新页面了 - 会销毁页面 wx.redirectTo({ url: '/pages/logs/logs', }) ``` > 注意:navigateTo跳转页面的时候,之前隐藏的页面会缓存起来,不会销毁,**小程序最多可以缓存10页面** #### 隐藏按钮 wx:if="{{ !nickName }}" ```html ``` #### 钩子函数 分为 应用级钩子 和 页面级钩子 应用级钩子 * onLaunch - 应用初始化的时候只会执行一次 * onShow - 应用展示的时候 * onHide - 应用隐藏的时候 * onError - 不常用 页面级钩子 * onLoad - 页面加载 * onReady - 页面渲染 * onShow - 页面展示 * onHide - 页面隐藏 * onUnload - 页面卸载 * onPullDownRefresh - 下拉刷新 * onReachBottom - 上拉加载 * onShareAppMessage - 分享 #### 微信弹框 ```js wx.showToast({ title: '成功' }) ``` # day02 ### 搭建项目架子 ### 首页 * 静态页面 * 初始化数据展示 #### 静态页面 * 轮播静态搭建 小程序swiper组件,swiper 标签中只能放 swiper-item ,关于swiper组件的属性参考文档 * 每日推荐静态搭建 使用 flex 布局 * 推荐歌曲静态搭建 - 需要封装组件 recommend * 精心推荐 - 滚动区域 使用 `scroll-view` 组件,必须有高度,需要在属性中配置横向滚动 `scroll-x` 和开启 flex 布局 `enable-flex="true"` * 排行榜静态搭建 - 需要封装组件 recommend * 榜单 swiper 搭建 > 注意:next-margin="120rpx" 下一个 swiper-item 的偏移量 #### recommend 组件(component)封装 * 定义 把静态结构和样式拿竟来,在.js文件中使用 Componet() 声明组件 * 注册 哪个页面(page)使用该组件,在该页面的 .json 文件中 "usingComponents" 配置项配置组件 * 使用 在页面中使用即可 * 传参 父组件使用的时候在标签上把属性传进去,子组件在.js中使用 properties 属性接收在标签上的数据,即可在页面中使用 #### 判断当前网络 ```js wx.getNetworkType({ success: (result) => { console.log(result) if (result.networkType === 'wifi') { wx.showToast({ icon: 'none', title: '当前网络是wifi环境', }) } else { wx.showToast({ icon: 'none', title: '当前网络是非wifi环境,注意流量消耗', }) } }, }) ``` 接下来该发请求拿数据,需要request #### 封装 utils/request.js 文件 ```js export default function request({ url, data = {}, method = 'GET', timeout = 60000, dataType = 'json' }) { return new Promise((resolve, reject) => { wx.request({ url: `http://localhost:3000${ url }`, data, method, timeout, dataType, success: (res) => { resolve(res.data) }, fail: (err) => { reject(err) } }) }) } ``` > 注意:promise 封装异步请求 > > 之前在没有promise的时候,使用的回调解决异步封装问题 #### 调接口,拿数据 * swiper 接口数据 * 滚动区域 接口数据 * 榜单接口数据 > 循环拿到各个榜单数据,需要自己组装一下 # day03 ### tabBar 在 app.json 文件中配置,最少2个,最多5个 ```js "tabBar": { "list": [ { "pagePath": "pages/home/home", "text": "首页", "iconPath": "static/images/tabs/tab-home.png", "selectedIconPath": "static/images/tabs/tab-home-current.png" }, { "pagePath": "pages/video/video", "text": "视频", "iconPath": "static/images/tabs/select.png", "selectedIconPath": "static/images/tabs/selected.png" }, { "pagePath": "pages/center/center", "text": "个人中心", "iconPath": "static/images/tabs/tab-my.png", "selectedIconPath": "static/images/tabs/tab-my-current.png" } ] } ``` ### 个人中心 搭建静态页面,复制粘贴过来的,把所有的事件干掉,自己写 交互 * 下拉过渡效果 * 点击头像跳转登录页面,进行登录 * 最近播放列表展示 #### 下拉过渡效果 * bindtouchstart 当手指按下的时候获取起始位置,并记录到全局 * bindtouchmove 获取到手指的实时位置,减去起始位置,得到元素移动的差值 `moveY` ,然后保存到data中 页面中整个底部要跟着动,使用 `transform: translateY({{moveY * 2}}rpx)` * bindtouchend 当手指松开的时候,差值 `moveY` 重新赋值为0,让底部元素过渡回去 需要添加过渡效果 当手指重新开始点击屏幕的时候需要重置过渡 > 注意:只有手指松开的时候又过渡效果,下拉的时候没有 #### 登录 点击头像,跳转登录页面 * 收集表单数据 * 校验表单数据 * 发请求登录,但是由于接口不能用,模拟了接口返回的数据 > 注意:返回的数据中必须有 userId,拿着 userId 获取最近播放列表 * 当完成登录的时候需要干两件事 * 存储个人数据,将数据存储到 storage 中 `wx.setStorageSync('USERINFO', JSON.stringify(data))` > 注意:storage 中key能存1M,整体能存 10M * 跳转到个人中 * 这里不能用 wx.redirectTo 和 navigateTo ---> 页面跳转,但是不能跳转tabBar页面 * 需要使用 wx.switchTab(有问题) 和 wx.reLaunch --> 页面跳转,用来跳转到tabBar页面 #### 个人中心 登录跳转回来之后,在 onLoad 钩子中通过 storage 拿到个人信息的数据,进行展示 调用接口,获取个人最近播放的列表进行展示 > 调用接口的时候需要 userId 参数 ### 视频页面 * 静态页面搭建 * 数据展示 和 交互 #### 静态页面搭建 * 顶部假的 header - 用于搜索的 * nav滚动区域 * 视频列表的滚动区域 > 注意: > > 视频列表的滚动区域,用户不是 position: fixed; 需要一个定高,由于设备高度的不确定,定高是需要计算的 > > 使用 css3 中的计算的属性 calc() > > `height: calc(100vh - 200rpx)` > > 使用 calc 注意事项: > > 加减的时候,运算符两侧必有有空格,运算的两侧必须都带有单位 > > 乘除的时候,运算符两侧可以没有空格,运算的两侧必须有一个带有单位 > > 拓展 - 面试题: > > 左侧固定,右侧自适应 > > 1. flex: 1 > 2. calc(100vw - 200px) * nav 滚动区域拿数据展示 和 交互 在 onLoad 中直接调用接口拿数据,存到data中,在页面中进行展示 交互: * 点击某一个 item, 下方的红线需要变 使用参考值思想,点击哪一个记录点击的那一个item的id `navId`,当记录的id `navId` 和循环展示出来的id相同的时候,此时给 active 类名,加上底部红线 * 点击 item 需要跳转到当前的 item,让当前的item展示在第一个位置 scroll-view 中 croll-into-view="{{ 'scroll' + navId }}" ---> 滚动到的位置 scroll-with-animation="true" ---> 滚动的过渡效果 ```html ----- 这里item的id不能以数字开头 ``` * 获取视频列表的数据 需要根据当前的 `navId` 去拿视频列表的数据 > 注意: > > 发送这个请求的时候需要携带 cookie , 由于登录接口无法使用 (cellPhone 这个接口不能用),所以拿不到 cookie,需要自己手动在 x63云音乐 登录,在cellPhone接口中拿响应报文中的 cookie,响应的 cookie很多,拿以 `MUSIC_U_xxxx` 开头的cookie即可 > > 拿到cookie之后,放到请求头当中,发请求就可以拿到数据了 > > 问:[如果]登录接口好使怎么操作? > > 响应的cookie(字符串)会存到当前的 cookie(容器) 中,发请求的时候,去cookie(容器)中拿到这个cookie(字符串),携带到请求头当中即可 拿到数据之后,需要组装数据,把我们需要的数据组装好即可 * 视频列表的展示 # day04 ### 视频页面 前一天获取到可视频列表的数据,拿着数据进行展示即可 #### 点击视频播放 点击视频的时候需要创建 视频播放上下文(实例) `this.player = wx.createVideoContext('id')` 创建实例中传的id是 video 标签的id 拿到实例之后进行播放 `this.plery.play()` 记录播放视频的id ```js this.setData({ currnetId: vid, // 视频的id }) ``` 记录播放的状态,两种修改方式: 1. 走setData 2. 不走setData 1. 走setData 修改的是数组中某一个成员(对象)的某个属性,需要准备数据路径 ```js // 获取当前播放video的数据的下标 let videoIndex = this.data.videoList.findIndex(item => item.vid == vid); // 需要修改的数据的 数据路径 let videoPath = `videoList[${ videoIndex }].playStatus` this.setData({ [videoPath]: true // 播放 }) ``` 数据路径举例: ```js this.setData({ 'list[0].status': true // 这里的属性名取的数组中下标为0位置的status属性 }) ``` 2. 不走 setData ```js let videoData = this.data.videoList.find(item => item.vid == vid); videoData.playStatus = true; ``` > 什么情况下走setData? > > 涉及到页面中展示的数据 走 setData 如何知道点击的是不是同一个视频,拿着上一次记录的 currentId 和 当前点击video的vid进行对比,如果相同的点击的就是同一个,如果不同就点击的不是同一个 ##### 二次点击同一个视频 修改当前播放状态,判断当前的播放状态 ```js let videoData = this.data.videoList.find(item => item.vid == vid); if (videoData.playStatus) { // 播放状态,需要暂停 this.player.pause() } else { this.player.play() } videoData.playStatus = !videoData.playStatus; // 点击的是同一个视频不需要改变 currentId 的记录 ``` ##### 二次点击不同的视频 ```js // 先把上一个暂停掉 this.player.pause() // 修改上一个播放状态, currnetId 是记录上一个播放的id this.videoList.find(item => item.vid == this.data.currnetId).playStatus = false; // 创建当前播放的上下文(实例), vid 是当前点击视频的id this.player = wx.createVideoContext(vid); // 播放 this.player.play(); // 修改状态 videoData.playStatus = true; // 记录当前id this.setData({ currentId: vid }) ``` > 注意: > > 页面中始终只能有一个播放器,不能让多个视频重复播放 > > 第一次点击的时候,走创建player的逻辑,第二次点击的时候,就应该走二次点击的逻辑 ### 视频黑边 和 图片切换问题 video标签上有一个属性 `object-fit="fill"` 可以让视频填充,解决黑边问题 图片切换使用参考值思想,默认显示图片列表,当点击图片的时候,记录currentId,然后开始播放,只有currentId 和 wx:for 出来的视频的 vid 相同才显示播放器 > 图片在切换的时候,发现有bug,需要真机测试 ### 真机测试 使用内网穿透,让公网可以访问到本地启动的服务,并且可以挂上 https 和 域名 详情参见 - 拓展 ### 切换视频回到之前的播放状态 需要记录每一个视频的播放时间,把这个时间绑定在数据中,在视频实时播放的时候,可以通过 `bindtimeupdate="updateHandler"` 事件拿到视频实时播放的数据,在回调中把时间实时存储即可 当切换视频播放状态的时候,走二次点击的不同视频逻辑,在播放的时候,需要判断数据中是否有记录的时长,如果有需要设置 ```js // 当播放的时候,判断一下有没有已经播放的时长(之前就播放过) if (videoData.currentTime) { // 设置当前播放要跳转的位置 this.player.seek(videoData.currentTime) } ``` ### 视频播放结束时候,需要清空记录的时长 ```js endHandler(e) { // 找到数据 let vid = e.currentTarget.id; let videoData = this.data.videoList.find(item => item.vid == vid); // 修改时长 videoData.currnetTime = 0; // 修改播放状态 videoData.playStatus = false; } ``` ### 分享 设置分享的时候,需要一个钩子,只要有钩子,分享功能就能使用,如果需要自定义分享内容,把钩子的返回值配置即可 右上角按钮分享 - 自动触发分享钩子 页面按钮分享 - 需要在按钮上配置才能触发分享的钩子 `` ### 下拉刷新 `scroll-view` 标签设置三个内容 refresher-enabled="true" 开启下拉,允许下拉刷新 refresher-triggered="{{ flag }}" 控制下拉开始或结束,是一个布尔值 bindrefresherrefresh="refresherHandler" 触发下拉事件的回调 进入回调之后,直接重新获取数据展示即可 ### 上拉加载 bindscrolltolower="lowerHandler" 滑动到底部触发这个回调,在回调中拿数据,拼接到已有数据之后即可 ## 推荐歌曲页 静态搭建页面 > 注意: 需要滚动的区域是 `scroll-view` 的区域,需要设置高度,使用 calc 计算得到高度即可 onLoad 钩子中发请求,拿数据,展示 # day05 点击推荐中歌曲跳转到播放界面(详情页) ### 详情页 #### 搭建静态 根据结构搭建静态,需要注意的点: * 唱针 唱针需要旋转,用到的属性: ```css transform: rotate(0deg); /* 设置旋转的角度 */ transform-origin: 50rpx 50rpx; /* 设置旋转的圆心,支持'rpx' '百分比' 'center' 'left' */ ``` * 唱片 用到动画 ```css move 关键帧 4s 持续时间 linear 动画匀速直线(动画速率) infinite 动画无限循环 1s 延时1秒开始动画 animation: move 4s linear infinite 1s; ``` #### 拿数据 需要拿歌曲的详细信息,和歌曲的播放地址,需要发两个请求,发请求需要携带当前播放歌曲的id,涉及到了页面的传参 总结: 页面间通信方式: 1. 通过storage可以通信 2. 通过 事件通道 进行通信(A页面 -> B页面) wx.navigateTo 页面通信 在 wx.navigateTo 的 success 回调中,传输数据的地方,拿到通道,触发事件,传递参数 ```js wx.navigateTo({ url: 'xxxxxxx', success: (res) => { // res.eventChannel 事件通道 res.eventChannel.emit('musicId', { musicId }) } }) ``` 在接收数据的地方 ```js const eventChannel = this.getOpenerEventChannel(); eventChannel.on('musicId', (arg) => {}) ``` 获取通道,绑定事件,留下回调,接收参数 3. query 传参 -> 跳转路径 问号 之后的参数 (A页面 -> B页面) 在跳转的url上以query的形式记录参数,在下一个页面的onLoad的参数中可以获取到query参数 4. 使用第三方插件 pubsub-js 进行通信 使用第三方包步骤: 1. 初始化npm包 npm init 2. 安装依赖 npm i pubsub-js 3. 点击编辑器 "工具" -> 构建npm 完成第三方包的小程序构建 注意: 第三步需要每次构建吗?什么时候点击构建? 每次下载新的第三方包的时候构建一次 pubsub使用步骤 1. 安装引入 2. 订阅事件,留下回调,接收参数 3. 发布事件,传递参数 详情页在onLoad中获取到歌曲列表页传过来的id,拿着id发请求拿数据 详情的数据直接拿过来展示 歌曲的播放地址,拿到之后,需要创建播放器实例(背景音乐播放器实例),用来播放歌曲, ```js if (!this.player) { this.player = wx.getBackgroundAudioManager(); // 创建实例 } // 设置url - 设置url自动播放 this.player.src = this.data.songUrl; // 设置标题 - 必须设置 this.player.title = this.data.songDetail.songName; ``` #### 交互 - 点击按钮播放、暂停 涉及到两件事:1. 页面动画的改变 2. 歌曲页需要暂停,使用一个数据 `play` 来控制播放和暂停,true代表播放,false代表暂停 点击播放/暂停按钮,需要修改记录的数据 `this.setData({ play: !this.data.play })` ,还需要让播放器暂停,`this.player.play()` 、`this.player.pause()` #### 系统播放暂停改变数据状态 创建player的时候加播放、暂停的事件监听,一旦播放、暂停就可以进入回调 ```js // 绑定播放和暂停的回调 this.player.onPlay(() => { this.setData({ play: true }) }) this.player.onPause(() => { this.setData({ play: false }) }) ``` #### 上一首和下一首 涉及到已存在两个页面之间的通信,使用 Pubsub 进行通信,关于pubsub的笔记,写在上面的通信中 当点击上一首、下一首的时候,把当前播放歌曲的id传回上一个页面,同时携带上操作,上一个页面根据操作来寻找下一首、上一首(注意边界条件,最后一首歌曲下一首的时候需要切到第一首,第一首上一首的时候需要切换到最后一首),获取到即将要播放歌曲的id,再传回到详情页,通过 Pubsub 再传一次 #### 歌曲播放完毕自动下一首 创建player的时候绑定歌曲结束的事件监听 ```js this.player.onEnded(() => { Pubsub.publish('operateType', { musicId: this.data.musicId, operate: 'next' }) }) ``` #### 进度条 搭建静态样式 ```html {{ currentTime }} {{ duration }} ``` 数据 在播放音乐的时候,有事件可以实时触发更新数据,在这个回调中拿时间,拿到时间使用 moment.js 将时间转成我们需要的格式,放到数据当中,实时更新页面 安装moment,需要构建 ```js this.player.onTimeUpdate(() => { // 这里时间获取到的单位是秒 // console.log(this.player.currentTime, this.player.duration) let currentTime = moment(this.player.currentTime * 1000).format('mm:ss'); let duration = moment(this.player.duration * 1000).format('mm:ss'); let w = this.player.currentTime / this.player.duration * 400; this.setData({ currentTime, duration, w }) }) ``` # day06 第三方插件使用 1. 登录小程序后台,找到设置,第三方设置,前往"服务市场" "服务市场"需要注册一下账号,找到导航"开发者资源" -> 侧边栏"插件" 找一个地图相关的插件(0元),例如:"路线规划",点击进入,有"接入文档",按照接入文档操作即可 2. 文档内容 2.1 需要小程序后台添加 当前插件 2.2 需要粘贴插件到 app.json 2.3 需要让小程序获取位置权限设置 app.json 2.4 粘贴插件代码 3. 发现粘贴代码需要开发者的唯一key,在文档下方的说明中找到"申请Key",点击"申请key" 进入位置服务中心 1. ![](static/001.png) ![](static/002.png) ![](static/003.png) ## 拓展 ### 内网穿透 让公网可以访问自己本机起的服务,将本机的服务+端口映射成一个公网的域名 需要使用第三放软件 花生壳: 1. 注册 花生壳 账号 在管理平台页面,创建一个映射,可以把本地的服务映射到公网的某个域名下 2. 启动本机的花生壳应用,登录之后,才能让外网访问