这是在b站尚硅谷的官方视频
代码可以在他的官网找
搜索关键字: 待做 听说 妙 知识点 坑 思路 思考感悟
还有一些就不贴了
<meta name="viewport" content="width=device-width,initial-scale=1.0"
了解一下脚手架是什么
命名 avatarUrl
上面那个箭头量出来的是px
如果dpr=2 代表着1px=2rpx
小程序中动态的数据都要去data中找
this代表当前页面的实参对象
this.setDate({})
vue里面是数据劫持代理
// Vue数据劫持代理 // 模拟Vue中data选项 let data = { username: 'curry', age: 33 } // 模拟组件的实例 let _this = { } // 利用Object.defineProperty() for(let item in data){ // console.log(item, data[item]); Object.defineProperty(_this, item, { // get:用来获取扩展属性值的, 当获取该属性值的时候调用get方法 get(){ console.log('get()'); return data[item] }, // set: 监视扩展属性的, 只要已修改就调用 set(newValue){ console.log('set()', newValue); // _this.username = newValue; 千万不要在set方中修改修改当前扩展属性的值,会出现死循环 data[item] = newValue; } }) } console.log(_this); // 通过Object.defineProperty的get方法添加的扩展属性不能直接对象.属性修改 _this.username = 'wade'; console.log(_this.username);
事件流的三个阶段
1. 捕获: 从外向内
2. 执行目标阶段
3. 冒泡: 从内向外
onl oad: function (options) { var that=this wx.getUserInfo({ success:function(e){ console.log("success被调用") that.setData({ userInfo:e.userInfo }) }, fail:function(e){ } }) },
这时候按钮应该没了 这个该怎么写呢 ? 需要用到wx:if wx:elif wx:else
<view class="container"> <button wx:if="{{!userInfo.nickName}}" open-type="getUserInfo" bindgetuserinfo="getUerInfo">点击获取用户信息</button> <image wx:else src="{{userInfo.avatarUrl}}" /> <text>{{userInfo.nickName}}</text> </view>
onLoad: function (options) { var that=this wx.getUserInfo({ success:function(e){ // console.log(e) that.setData({ userInfo:e.userInfo }) }, fail:function(e){ // console.log(e) } }) }, getUerInfo:function(e){ console.log(e) //可以看到已经获取到信息了 this.setData({ userInfo:e.detail.userInfo }) },
page{ height: 100%; } .container{ height: 50%; display: flex; flex-direction: column; align-items: center; justify-content: space-evenly; } image{ width: 200rpx; height: 200rpx; border-radius: 50%; }
想让他横着滚动怎么办怎么办呢,
| enable-flex | boolean | false | 否 | 启用 flexbox 布局。开启后,当前节点声明了 display: flex
就会成为 flex container,并作用于其孩子节点。 |
单行文本溢出隐藏加省略号三件套:
但是失效了 这是为什么呢 因为这是作用在text上面 而text是内联元素不是块级元素 是靠里面的东西撑开的 没有能力切割 所以要把它变成块级元素
那多行文本溢出怎么做呢
如何拿到后端的数据呢? 先找到那个文件夹 然后看教程 npm start 以后浏览器输入http://localhost:3000/banner就能看到数据了 然后确定能拿到数据以后就可以在小程序里发送request请求了
https://developers.weixin.qq.com/miniprogram/dev/framework/ability/network.html
前后端交互
因为request请求很多都是差不多的 可以封装在utils里面
1. 封装功能函数 * 1. 功能点明确 * 2. 函数内部应该保留固定代码(静态的) * 3. 将动态的数据抽取成形参,由使用者根据自身的情况动态的传入实参 * 4. 一个良好的功能函数应该设置形参的默认值(ES6的形参默认值) //因为数组可以用foreach这些遍历但是如果是空或者是个对象 会报错 所以需要形参默认值 * 2. 封装功能组件 * 1. 功能点明确 * 2. 组件内部保留静态的代码 * 3. 将动态的数据抽取成props参数,由使用者根据自身的情况以标签属性的形式动态传入props数据 * 4. 一个良好的组件应该设置组件的必要性及数据类型 * //和vue相关 * props: { * msg: { * required: true, * default: 默认值, * type: String * } * }
//data默认是一个对象 ; 因为method默认是post 要是传入post的话第三个参数就不用写了 比较简便 如果是get请求就需要写第三个参数 export default(url,data={},method="POST")=>{ //到后面发现我写错了 wx.request({ url: url, //参数 method:method, data:data, success:function(e){ console.log(e); }, fail:function(e){ console.log(e); } }) }
然后index.js里面接收请求
import request from '../../utils/request' onload: request('http://localhost:3000/banner',{type:2})
这样就能输出信息了 但是呢 我们需要把数据放到index.js里面 然后setdata 放到data里面 wxml页面才能引用
onl oad: async function (options) { var result=await request('http://localhost:3000/banner',{type:2}) console.log(result) },
同时,await后面通常返回promise的实例 这时候request.js就变成了这样
export default (url, data={}, method='GET') => { return new Promise((resolve, reject) => { // 1. new Promise初始化promise实例的状态为pending wx.request({ url: url, data, method, success: (res) => { resolve(res.data); // resolve修改promise的状态为成功状态resolved }, fail: (err) => { // console.log('请求失败: ', err); reject(err); // reject修改promise的状态为失败状态 rejected } }) } }
http://localhost:3000
没必要写 那可以这样写wx.request({ url: 'http://localhost:3000'+url, …… }
但是呢 require.js是专门发请求的地方,以后要修改的话不应该到发请求的地方修改 我们应该创建一个文件专门改地址(耦合度)
// config.js: export default{ host:'http://localhost:3000' //host:服务器主机的意思 } //require.js: import config from './config' wx.request({ url:config.host+url }
<navHeader></navHeader>
会发现已经出来了let startY = 0; // 手指起始的坐标 let moveY = 0; // 手指移动的坐标 let moveDistance = 0; // 手指移动的距离 Page({ data: { coverTransform: 'translateY(0)', coveTransition: '', }, handleTouchStart(event){ this.setData({ coveTransition: '' }) // 获取手指起始坐标 startY = event.touches[0].clientY; }, handleTouchMove(event){ moveY = event.touches[0].clientY; moveDistance = moveY - startY; if(moveDistance <= 0){ return; } if(moveDistance >= 80){ moveDistance = 80; } // 动态更新coverTransform的状态值 this.setData({ coverTransform: `translateY(${moveDistance}rpx)` }) }, handleTouchEnd(){ // 动态更新coverTransform的状态值 this.setData({ coverTransform: `translateY(0rpx)`, coveTransition: 'transform 1s linear' }) },
<view class="cover-container" bindtouchstart="handleTouchStart" bindtouchmove="handleTouchMove" bindtouchend="handleTouchEnd" style="transform: {{coverTransform}}; transition: {{coveTransition}}" >
用户输入数据的时候 实时收集内容 (类似vue 的v-model的双向数据绑定效果)
如果password和phone用同一个事件处理函数
区分bindinput和change的区别 如何向这个事件处理函数传参呢? 了解一下里面的id
那如何分辨是哪一个传过来的呢
一个案例:要给ul下面所有的li都绑定事件 可以用循环遍历(效率差)而且如果以后还想再添加li进去 后来的li没有事件
这时候事件委托就派上用场了 就是说在ul上绑定事件,只要绑定一次就好 而且后来添加的元素也能享用
let{phone,password}=this.data
这句话什么意思呢 phone=this.data.phone password=this.data.password//按钮点击 login:function(){ let{phone,password}=this.data // 验证手机号是否为空 if(!phone){ wx.showToast({ title: '请输入手机号', icon:'none' }) return; // 老师说因为保险起见 异步加个return为什么呢? //为啥呢 后面没有必要走了 } // 验证手机号是否合法 // 第一位1 第二位3-9 后9位随意 let phoneReg=/^1(3|4|5|6|7|8|9)\d{9}/ if(!phoneReg.test(phone)){ wx.showToast({ title: '输入正确的手机号', icon:'none' }) return; } if(!password){ wx.showToast({ title: '请输入密码', icon:'none' }) return; } },
//验证手机号密码 login:async function(){ let{phone,password}=this.data //相当于let phone=this.data.phone , password同理 //判断phone是否为空 if(!phone){ wx.showToast({ title: '请输入手机号码', icon:'none' }) return; //这个都没通过后面也没必要执行了 } //接下来判断手机号合不合法 第一位1 第二位3-9 后9位任意 let phoneReg=/^1(3|4|5|6|7|8|9)\d{9}/ if(!phoneReg.test(phone)){ wx.showToast({ title: '请输入正确的手机号', icon:'none' }) return; } //接下来判断密码 这里只判断了是否为空 if(!password){ wx.showToast({ title: '请输入密码', icon:'none' }) return; } result='' let result= await request('/login/cellphone',{phone,password}) console.log(result); if(result.code===200){ wx.showToast({ title: '登录成功', })}else{ wx.showToast({ title: '输入错误 请重新输入', icon:'none' }) } },
https://developers.weixin.qq.com/miniprogram/dev/api/storage/wx.setStorageSync.html
json.stringify
onLoad: function (options) { let userinfo=wx.getStorageSync('userinfo') if(userinfo){ this.setData({userinfo:JSON.parse(userinfo)}) //注意这里json.parse的应用 } },
老师说跳转的时候可以用wx.relaunch(他可以销毁所有页面 这样回到personal里的onload就可以用了)
我试了一下 也可以把上面的代码放到onshow他是监听页面显示的(性能不好)
1. 语法: wx.setStorage() || wx.setStorageSync() || ..... 2. 注意点: 1. 建议存储的数据为json数据 2. 单个 key 允许存储的最大数据长度为 1MB,所有数据存储上限为 10MB 3. 属于永久存储,同H5的localStorage一样
我们的播放记录里面没有唯一标识id 那该怎么办呢 --用map
async getUserRecentPlay(userId){ let recentPlayListData=await request('/user/record',{uid:userId ,type:0}) let index=0; let recentPlayList=recentPlayListData.weekData.splice(0,10).map(item=>{ item.id=index++ return item //千万不要忘记return }) this.setData({ recentPlayList }) },
无论是用户登录了没数据还是没有登录recentPlayList都是空
然后要在页面上如果没有播放记录就显示暂无播放记录 如果有的话就显示图片用到wxif wxelse 判断什么呢 判断recentPlayList.length 不能判断recentPlayList因为这是判断他存不存在的 无论有没有数据它都存在
通过id传参 如果传的是数字 他会转化成字符串 我们可以用>>>0、 *1、 == 或者直接用data-传参
onl oad: function (options) { this.getVideoGroup() this.getVideoList(this.data.navId) },
像这样的 第一个函数执行了setdata 把navId放到了data里面 第二个函数传入了data里面的navid
会出问题 为什么呢 第一个函数没执行完就执行第二个了 第二个就取不到navId 那该怎么办呢 --把第二个函数放到第一个函数里面
是这样的 要拿到视频数据需要cookies
以前封装的函数只是拿到了e.data data和cookies是并列的
这时候也要拿到cookies 要去request.js里面改一下代码
因为要在视频页用 所以可以放缓存里面 不是这个原因
这是要取出来的代码
async getVideoList(navId){ let videoListData= await request('/video/group',{id:navId}) console.log(videoListData); },
但是他要cookies 我们就要在请求的时候拿到cookies 这时候要修改request.js里面的内容 现在变成这样了
export default(url,data={},method="POST")=>{ return new Promise((resolve,reject)=>{ wx.request({ url:config.host+url, method:method, data:data, //这里的header是给视频页获取准备的 因为它需要cookies才能获取到数据 header:{ //这个header的作用是携带数据 cookie:wx.getStorageSync('cookies')?(wx.getStorageSync('cookies').find(item=>item.indexOf('MUSIC_U=6')!==-1)):' ' // 本来是这样的 cookie:wx.getStorageSync('cookies').find(item=>item.index('MUSIC_U=6')):'' //这里为什么要加!==-1呢 因为等于负一他也算对的 -1转换成布尔值也是对的 //注意这里为什么要加三元运算符呢 因为一开始是没有cookies缓存的 这时候在用find可能就会出错 所以加了个三元运算符 让cookies等于空 success:function(e){ //let result= await request('/login/cellphone',{phone,password,isLogin:true}) 这是login里写的 if(data.isLogin){ //如果是在login页面调用的 wx.setStorage({ //把他放缓存里 可以给header里面的cookie用 data: e.cookies, key: 'cookies' }) } resolve(e.data) },
注意里面的元素没有唯一的id需要用map加工一下
showloading 以及点击把当前的页面变白色(清空) 把videoList:[]
changeNav(event){ let navId = event.currentTarget.id; // 通过id向event传参的时候如果传的是number会自动转换成string // let navId = event.currentTarget.dataset.id; this.setData({ navId: navId>>>0, videoList: [] }) // 显示正在加载 wx.showLoading({ title: '正在加载' }) // 动态获取当前导航对应的视频数据 this.getVideoList(this.data.navId); },
因为scroll-into-view不能用数字开头 所以前面加了个scroll字符串(妙啊) 然后navItem的id也要跟着换了
<!-- 导航区域 --> <scroll-view scroll-x enable-flex class="navScroll" scroll-into-view="{{'scroll'+navId}}" > <view class="navItem " wx:for="{{videoGroupList}}" wx:key="id" data-id="{{item.id}}" id="{{'scroll'+item.id}}" > <view class="navContent {{item.id===navId?'active':''}}" bindtap="changeNav" id="{{item.id}}">{{item.name}}</view> </view> </scroll-view>
过渡的太生硬了 有个scroll-with-animation
知识点
handlePlay(event){ console.log(event); let vid=event.currentTarget.id let videoContext=wx.createVideoContext(vid) videoContext.stop() },
handlePlay(event){ // console.log(event); // let vid=event.currentTarget.id this.videoContext this.videoContext=wx.createVideoContext(vid) // videoContext.stop() },
handlePlay(event){ // console.log(event); // let vid=event.currentTarget.id this.videoContext.stop() //会报错的 因为一开始他是没有值的 ,play()当然不行 this.videoContext=wx.createVideoContext(vid) // videoContext.stop() },
handlePlay(event){ // console.log(event); let vid=event.currentTarget.id this.videoContext&&this.videoContext.stop() //注意这种写法 当前面的为true走后面的 this.videoContext=wx.createVideoContext(vid) // videoContext.stop() },
我们要确保当前的视频跟之前的不是同一个视频
handlePlay(event){ // console.log(event); let vid=event.currentTarget.id this.vid!==vid&&this.videoContext&&this.videoContext.stop() //和上面有点像 this.videoContext=wx.createVideoContext(vid) // videoContext.stop() },
性能优化:(使用image图片代替video)https://developers.weixin.qq.com/community/develop/doc/000e4ef22583d8961919efb6b56009
用wxif和wx else决定显示哪个 也可以盖在上面
图片和视频共用一个class 和函数
一点击图片会把videoId更新到data中 然后判断videoId与视频哪一项匹配 哪一项匹配就显示video而不是图片
this.setData({ videoId:vid })
<view class="videoItem" wx:for="{{videoList}}"wx:key="id"> <video src="{{item.data.urlInfo.url}}" bindplay="handlePlay" id="{{item.data.vid}}" poster="{{item.data.coverUrl}}" class="common" wx:if="{{videoId===item.data.vid}}" ></video> <!-- //性能优化 --> <image wx:else class="common" bindtap="handlePlay" id="{{item.data.vid}}" src="{{item.data.coverUrl}}"></image>
//这样的话我们也没有必要写那样一长串了 因为不存在多视频同时播放的情况
但是虽然切换了 我们的视频还是不能自动播放
handlePlay(event){ let vid=event.currentTarget.id // this.vid!==vid&&this.videoContext&&this.videoContext.stop() //注意这种写法 铛前面的为true走后面的x // this.vid=vid this.setData({ videoId:vid }) this.videoContext=wx.createVideoContext(vid) this.videoContext.play() },
有个aspectfit可以解决视频左右黑条条
就是什么呢 先image和video设同一个函数 然后点击image的时候呢 就把vid放到data里 然后video判断vid和他的item.videoList.vid香不相同 如果相同 就显示相应的视频
如何计算出下面tabbar的高度呢 ?
有一个calc 可以计算出动态css的值
算出来我们顶部的rpx值
在视频列表height:calc(100vh-152rpx)
记得运算符左右加空格
我们要知道 视频的id和视频播放到哪儿了
data里创建一个数组 videoUpdateTime:[]
每一项数据:
let videoTimeObj={vid:event.currentTarget.id,currentTime:event.detail.currentTime}
let{videoUpdateTime}=this.data //取出data里面的videoUpdateTime
然后把videoTimeObj给push进去 然后再setdata videoUpdateTime
但是这样是不行的 他会把每一秒的记录都存进去 会有多个重复的vid
我们要判断数组中之前有没有这个对象
如果有 --修改播放时间
没有 – 在数组中添加当前视频的播放对象
let videoItem=videoUpdateTime.find(item=>item.id===videoTimeObj.vid)
如果不相等为undefined
if(videoItem){ //疑惑 难道videoItem里面的 东西改变 videoUpdateTime也会改变吗 videoItem.currentTime=event.detail.currentTime }else{ videoUpdateTime.push(videoTimeObj) }
然后更新
// 监听视频播放进度 handleTimeUpdata:function(event){ console.log(event); let videoTimeObj={vid:event.currentTarget.id,currentTime:event.detail.currentTime} // this.data.videoUpdateTime.push(videoTimeObj) let{videoUpdateTime}=this.data //取出data里面的videoUpdateTime let videoItem=videoUpdateTime.find(item=>item.vid===videoTimeObj.vid) if(videoItem){ videoItem.currentTime=event.detail.currentTime }else{ videoUpdateTime.push(videoTimeObj) } this.setData({ videoUpdateTime }) },
目的是什么呢 就是点击的时候能够更新到当前的进度
点击的事件添加:
这样不断的向videoUpdateTime里堆数据也不好 我们要视频看结束了 就把他删除掉
handleEnded(event){ let{videoUpdateTime}=this.data //找数组下标:findindex videoUpdateTime.splice( videoUpdateTime.findIndex(item=>item.item.vid===event.currentTarget.id) ,1) this.setData({ videoUpdateTime }) }
自定义下拉刷新被触发:bindrefresherrefresh 每次调用都获取一次数据
但是他不能自己关掉 需要用 refresher-triggered这个东西 他是个布尔类型的 我们要先将里面的值变成false 每次获取数据它都变成false
https://developers.weixin.qq.com/miniprogram/dev/component/scroll-view.html
上拉加载 :后端 前端
后端:发一次请求给10条 比如 你发一次请求给你十条 再发一次再给十条 由后端控制给你几次 要携带指定数据
前端的话就是后端一次性给100条数据 前端拿到以后一次显示十条 上拉触底以后再截10-19
但是呢 我们可以造一个假的
三点运算符是什么呢 他有拆包的功能 就是说可以把数组变成对象
handleToLower(){ console.log('scroll-view 上拉触底'); // 数据分页: 1. 后端分页, 2. 前端分页 console.log('发送请求 || 在前端截取最新的数据 追加到视频列表的后方'); console.log('网易云音乐暂时没有提供分页的api'); // 模拟数据 let newVideoList = [ …… ]; let videoList = this.data.videoList; // 将视频最新的数据更新原有视频列表数据中 videoList.push(...newVideoList); this.setData({ videoList }) },
https://developers.weixin.qq.com/miniprogram/dev/reference/api/Page.html#onShareAppMessage-Object-object
onShareAppMessage:function({from}){ console.log(from); if(from=="button"){ return{ title:"来自btn", page:"/pages/video/video" } }else{ return{ title:"来自menu", page:"/pages/video/video" } }
待做:排行给123添加样式
注意:定位的代码最好写在样式块的最上面
想把下面的数字放到图片的中间怎么办呢
用定位的方式
但是数字那个盒子设成top50% height50%了 会发现只是左上角顶点在中间 那该怎么办呢 应该用margintop :-高度的一半 marginleft -宽度的一半 这样把他往左上角拉
本来是播放全部 更多 这样布局的 如果把更多变成float left 就会变成更多 播放全部
听说:图片太多可以用七牛云免费存储
这样是不行的
我们应该把他设为relative 然后往上提20rpx 还要把背景颜色设为白色
注意当一个盒子设为flex以后 他下面的子元素都会变成block(块元素)
如果用display flex 然后 justify-content: space-evenly; 以后如果发现元素定位不对 可以用 margin 来进行微调 就像这样
一个很妙的地方
/* 磁盘 */ .discContainer{ width: 598rpx; height: 598rpx; position: relative; top: -130rpx; } .disc { width: 598rpx; height: 598rpx; border: 1rpx solid red; } /* 唱盘图片 */ .musicImg{ width: 360rpx; height: 360rpx; border-radius: 50%; position: absolute; top: 0; right: 0; bottom: 0; left: 0; margin: auto; }
注意这里top0 right0 bottom0 left0 和margin auto的妙处
直接translate:rotate(-20deg)不行 因为他是在中间旋转的 我们要以左上角为顶点
要用到
transform-origin: top left; transform: rotate(-20deg);
我们先初始化一个isPlay来表示是否播放 从而控制摇杆的样式 needleRotated是在播放时摇杆的样式
<image class="needle {{isPlay?'needleRotated':''}}" src="/static/images/song/needle.png"></image>
一个很妙的地方 {{isPlay?'needleRotated':''}}
=》isPlay&&'needleRotated'
知识点:
@keyframes: 设置动画帧
这两个都是根据判断isPlay为真还是假做的动画 摇杆一开始就播放 但是图片旋转需要等一秒才行(增加真实性)
待做 isPlay为false的时候 图片停了 在继续播放的时候他不能等两秒再旋转 该怎么解决呢
播放暂停两个思路:
思路1
<text wx:if="{{isPlay==true}}" class="iconfont icon-bofang1"></text> <text wx:else class="iconfont icon-bofang"></text>
思路2
<text class="iconfont {{isPlay?'icon-bofang1':'icon-bofang'}} big" ></text>
一个justify-content:space-evenly的坑:因为那个暂停的按钮太小了 我给他放大了一下然后就出现了这种情况 :其他的按钮也动了 其实他本来全体都会往下动的 因为我设了align-item:center 然后给父盒子加了个高度就好了(用height和line-height也可以 因为不会随着其他的动而动)
解决办法
font-size: 60rpx; /* color: #fff; */ width: 20%; text-align: center;
群里听说:
微擎
自己做点小程序搭建好,放那边。有相同需求的直接小改动给他- -
或者直接买微擎商店的小程序
点击播放暂停 点击暂停播放:
// 点击播放或暂停 也可以用if else handleMusicPlay(){ let isPlay=!this.data.isPlay this.setData({ isPlay }) },
如果用if else就长了
handleMusicPlay(){ if(this.data.isPlay==true){ this.setData({ isPlay:false }) } else{ this.setData({ isPlay:true }) } },
现在要携带数据跳转到播放页面
要携带什么数据呢 --携带item
<view class="scrollItem" wx:for="{{recommendList}}" wx:key="id" bindtap="toSongDetail" data-song="{{item}}">
然后js接收song
toSongDetail(event){ let song=event.currentTarget.dataset.song //路由跳转传参 支持query参数 wx.navigateTo({ url: '/pages/songDetail/songDetail?song='+song, }) },
然后该怎么做呢
然后音乐播放页面onload接收
然后发现不行啊
变成了这个{song: “[object Object]”}
一个坑 为什么呢 url地址里面不能有js对象 如果有 他会自动帮你调用tostring去转换成字符串 转换之前是个object他就会变成上面那样有个中括号 然后里面有object
那如何解决呢 在传参的时候 要先给他转换成字符串
url: '/pages/songDetail/songDetail?song='+JSON.stringify(song) ,
如果传的是字符串 他就不会帮忙转换了
然后onload接收的时候再转换过来 (json.parse)
console.log(JSON.parse(options.song));
但是报错了Unexpected end of JSON input
这意味着什么呢 意味着json.parse里面传参传错了 为什么错了呢 因为里面要放的是个json的对象 但是事实上并不是 我们打印出来options.song是什么东西 发现他只打印到picUrl 后面的东西都没了 都被截掉了
这又是为什么呢因为原生小程序中路由传参 对参数长度有限制,如果参数长度过长会自动截取掉
那怎么办啊 我们要传入一个标识告诉页面传的是哪个音乐 有一个接口: /song/detail?ids=347230
我们这时候要传id进去(而不是song对象作为参数传递 长度过长了)
这时候我恍然大悟 为什么军政的那个项目把数据都放到公共的里面去了
思路1:
我是想着这样写的 onl oad接收到参数以后就放到data里面 然后通过函数来发请求然后调用data里面的id
思路2:
老师写的 是在函数里面传参 就不用把id放到data里了 简便了很多 但是如果后面要用到id的话还是需要放到公共区data里面的
我发现老师经常会写什么什么Data(发请求后接收的数据)
//动态修改窗口标题
wx.setNavigationBarTitle({
title: this.data.song.name,
})
if(isPlay==true){ //音乐播放 //获取链接 let link=await request('/song/url',{id:musicId}) console.log("发送了一次请求"); link=link.data[0].url let music=wx.getBackgroundAudioManager() music.src=link music.title= this.data.song.name }else{ music.pause() //这里是不行的 因为let让music只能在isplay=true的时候使用 }
那该怎么办呢 应该把 let music=wx.getBackgroundAudioManager()提到if-else上面
我认为老师上面写的不太好(可能)因为每次播放它都会发一次请求(后面第70集老师讲到了怎么优化)
待做:只用发一次请求
老师原来的代码:
data: { isPlay: false, // 音乐是否播放 song: {}, // 歌曲详情对象 musicId: '', // 音乐id }, /** * 生命周期函数--监听页面加载 */ onl oad: function (options) { // options: 用于接收路由跳转的query参数 // 原生小程序中路由传参,对参数的长度有限制,如果参数长度过长会自动截取掉 // console.log(JSON.parse(options.songPackage)); let musicId = options.musicId; this.setData({ musicId }) // 获取音乐详情 this.getMusicInfo(musicId); this.musicControl(this.data.isPlay, musicId) }, // 获取音乐详情的功能函数 async getMusicInfo(musicId){ let songData = await request('/song/detail', {ids: musicId}); // songData.songs[0].dt 单位ms this.setData({ song: songData.songs[0], }) // 动态修改窗口标题 wx.setNavigationBarTitle({ title: this.data.song.name }) }, // 点击播放/暂停的回调 handleMusicPlay(){ let isPlay = !this.data.isPlay; this.setData({ isPlay }) let {musicId} = this.data; this.musicControl(isPlay, musicId); }, // 控制音乐播放/暂停的功能函数 async musicControl(isPlay, musicId){ var backgroundAudioManager=wx.getBackgroundAudioManager() if(isPlay){ // 音乐播放 // 获取音乐播放链接 let musicLinkData = await request('/song/url', {id: musicId}); let musicLink = musicLinkData.data[0].url; this.setData({ musicLink }) backgroundAudioManager.src = musicLink; backgroundAudioManager.title = this.data.song.name; }else { backgroundAudioManager.pause(); } },
data: { isPlay:false, //标识是否在播放 song:{ //渲染页面用的 title:'', singer:'', coverImg:'' } , musicId:'', //音乐的id musicPlay:{ //播放音乐用的 musicTitle:'', musicUrl:'' } }, music:null, /** * 生命周期函数--监听页面加载 */ onl oad: function (options) { //接收路由跳转的query参数 // console.log(options.musicId); let musicId=options.musicId this.setData({musicId}) this.getMusicInfo(musicId) // this.getMusicDetail(musicId) //这两个是用来获取song和musicPlay里面的东西的 }, //获取音乐详情功能函数 async getMusicInfo(id){ let songData=await request('/song/detail',{ids:id}) console.log(songData); this.setData({ 'song.singer':songData.songs[0].ar[0].name, 'song.title':songData.songs[0].al.name, 'song.coverImg':songData.songs[0].al.picUrl, 'musicPlay.musicTitle':songData.songs[0].al.name }) //动态修改窗口标题 wx.setNavigationBarTitle({ title: this.data.song.name, }) }, // 点击播放或暂停 handleMusicPlay(){ if(this.data.isPlay==true){ this.setData({ isPlay:false }) this.musicPlay() } else{ this.setData({ isPlay:true }) this.musicPlay() } }, //获取音乐src async getMusicDetail(musicId){ //获取链接 let link=await request('/song/url',{id:musicId}) console.log("发送了一次请求"); this.setData({ 'musicPlay.musicUrl':link.data[0].url }) }, // 音乐播放功能 musicPlay:function(){ this.music=wx.getBackgroundAudioManager(), this.music.src=this.data.musicPlay.musicUrl this.music.title=this.data.musicPlay.musicTitle if(this.data.isPlay){ console.log(this.data.isPlay); this.music.play() }else{ this.music.pause() } // this.music.play() // console.log(music); },
补充:老师后来改的
我们需要事先监视播放暂停
this.music=wx.getBackgroundAudioManager(), this.music.onPlay(()=>{ console.log('播放'); }), this.music.onPause(()=>{ console.log('暂停'); })
musicPlay:function(){ this.music=wx.getBackgroundAudioManager(), this.music.onPlay(()=>{ // console.log('播放'); this.setData({ isPlay:true }) }), this.music.onPause(()=>{ // console.log('暂停');}) this.setData({ isPlay:false }) }) this.music.src=this.data.musicPlay.musicUrl this.music.title=this.data.musicPlay.musicTitle if(this.data.isPlay){ console.log(this.data.isPlay); this.music.play() }else{ this.music.pause() } // this.music.onpause() // this.music.play() // console.log(music); }
还有一个点叉叉以后应该暂停的
就要用到这个
this.music.onStop(()=>{ this.setData({ isPlay:false }) })
注意 这时候我们发现代码重合度太高了
// 音乐播放功能 musicPlay:function(){ this.music=wx.getBackgroundAudioManager(), //监听播放暂停停止 this.music.onPlay(()=>{ // console.log('播放'); this.setData({ isPlay:true }) }), this.music.onPause(()=>{ // console.log('暂停');}) this.setData({ isPlay:false }) }) this.music.onStop(()=>{ this.setData({ isPlay:false }) }) this.music.src=this.data.musicPlay.musicUrl this.music.title=this.data.musicPlay.musicTitle if(this.data.isPlay){ console.log(this.data.isPlay); this.music.play() }else{ this.music.pause() } }
我们需要专门写一个修改播放状态的功能函数
changePlayState(isPlay){ this.setData({ isPlay }) }
修改后:
//监听播放暂停停止 this.music.onPlay(()=>{ this.changePlayState(true) }), this.music.onPause(()=>{ this.changePlayState(false) }) this.music.onStop(()=>{ this.changePlayState(false) })
知识点:出去了以后再回来是暂停状态(如何解决页面销毁音乐播放问题)
这里有个知识点 getApp的使用 我们需要一个全局的东西来保存isPlay的状态 以前学了缓存 现在学app.js
https://developers.weixin.qq.com/miniprogram/dev/reference/api/getApp.html
isMusicPlay:false, //是否有音乐在播放 musicId:'' },
// 获取全局实例 const appInstance=getApp
上面这个太冗余了
可以这样 这样就把状态和id保存下来了(妙)
3. 保存下来是为了下次再进来时 判断是不是上次的状态是不是播放
if(appInstance.globalData.isMusicPlay&&appInstance.globalData.musicId==musicId){ // 修改当前页面音乐播放状态为true this.setData({ isPlay:true }) }
解决两个页面通信问题:缓存 appdata
上一曲下一曲
妙 在播放页面切歌只用一个函数就行了 只需要判断按钮传入的id就知道点的哪个
但是播放页面没有推荐页面那些歌曲的信息啊 只有当前的信息
所以我们要把上一首下一首交给推荐页面
思路:点的上一曲还是下一曲我们要传给推荐页面 然后推荐页面把id传回来
知识点 页面通信—npm(我想也可以通过缓存和公共js来解决)
知识点
{ "name": "1", "version": "1.0.0" }
那个MY TOPIC 要用一个消息的名称替换 消息的订阅和发布 相当于vue中的自定义事件
知识点 自定义事件
所以播放页面应该是发布方
推荐页是订阅方
思考:页面之间传参与订阅模式
上面几步完成以后
然后在推荐页面引入他import PubSub from 'pubsub.js'
报错
VM4612 WAService.js:2 Error: module “pages/recommendSong/pubsub.js” is not defined
知识点
我们看到报错信息是在recommendSong里面找不到pubsub
当然找不到了 因为pubsub不是安装在推荐页面的
所以新增第六步 构建npm包
是什么意思呢要把原来的包转化成小程序能使用的包
小程序加载第三方包的时候只会到miniprogram npm里面找
如果没有这个文件夹或者文件夹下没有要找的包 就从当前目录出发去找(就出现上文报错的情况)
先订阅再发布
先在推荐页面订阅
PubSub.subscribe('switchType',(msg,data)=>{ console.log(msg,data); });
然后播放页面
handleChange(event){ let type=event.currentTarget.id console.log(type); // 发送消息给推荐页面 PubSub.publish('switchType',type) }
一个坑 我的这样写总是报错CreateListFromArrayLike called on non-object
但是老师的没有问题 因为老师下的包是pubsub-js而我下的是pubsub.js 排查了好长时间啊
不过我的也能用 不过就是要把type变成数组
然后订阅的那个msg是不能打印的 只能打印data(老师的是都能打印的)
我修改后的代码
播放页面:
handleChange(event) { let type = event.currentTarget.id let index={'haha':'fdsa'} // 发送消息给推荐页面 PubSub.publish('hello/world',[type]); }
推荐页面:onload里面
PubSub.subscribe('hello/world', function(data) { console.log(data); });
因为订阅只要触发一次就够了 所以选择放到onload里面
现在我们需要知道下标方便推荐页面知道上一首歌下一首歌是哪一首
这时候 wxml里面循环遍历的那里应该多加一个data-index={{index}}了 然后setdata一下
方便取到下标
我是这样写的
let song=event.currentTarget.dataset.song console.log(song); let index=event.currentTarget.dataset.index
老师的:
let {index,song}=event.currentTarget.dataset
订阅函数里面也可以简写
let{index,recommendList}=this.data //获取data里面的index和recommendList
注意 本来我在订阅函数里是照着老师写的 但是总是报错 我又把我的pubsub.js换成老师的pubsub-js发现还是报错 说明不是这个的原因 然后我简化了一下找找到底是哪里错了
这时候我才发现箭头函数的好处
这样我们就能获取index了
PubSub.subscribe('hello/world', (mag,data)=>{ let{index}=this.data if(data=='pre'){//上一首 index=index-1 }else{ //下一首 index=index+1 } this.setData({ index }) console.log(index); });
然后就可以取到音乐id
let musicId=recommendList[index].id
但是这个页面没用 我们要把它再传到详情页
详情页有了id就可以通过id播放对应的歌曲
所以我们又要用到订阅发布了
这时候显示播放页订阅 然后推荐页发布
什么时候订阅呢 在上一次发布之前
我试了不行 肯定哪里有问题 一般来说应该就打印一次啊 为什么每次都会多一次呢
我们把订阅消息的回调放到了点击切歌里面 会累加的
他的底层是怎么做的呢?
{musicId:[{第一个回调},{第二个回调},{后面添加的回调}……{}]}
一个坑注意 订阅了多次 会导致数组有多个回调 这时候我们打印出来他会把里面的东西全部打印
这时候就需要取消订阅
// 订阅来自推荐页面的音乐id PubSub.subscribe('musicId',(msg,musicId)=>{ console.log(musicId); PubSub.unsubscribe('musicId') })
主要思路是什么呢 推荐页面接收到了歌曲页面点的是上一曲还是下一曲 然后根据这个让当前的commendList下标+1-1 然后再判断这条数据的id 然后再传给播放页面
然后播放页面再根据传入的id 就知道播放哪一首歌曲了
待做
但是我写的有bug 现在暂时没找到bug在哪里
var PubSub = require('pubsub-js'); // pages/songDetail/songDetail.js import request from '../../utils/request' // 获取全局实例 const appInstance = getApp() Page({ data: { isPlay: false, //标识是否在播放 song: { //渲染页面用的 title: '', singer: '', coverImg: '' }, musicId: '', //音乐的id musicPlay: { //播放音乐用的 musicTitle: '', musicUrl: '' } }, music: null, /** * 生命周期函数--监听页面加载 */ onl oad: function (options) { //接收路由跳转的query参数 // console.log(options.musicId); let musicId = options.musicId this.setData({ musicId }) // 判断之前的音乐是否在播放 if (appInstance.globalData.isMusicPlay && appInstance.globalData.musicId == musicId) { // 修改当前页面音乐播放状态为true this.setData({ isPlay: true }) } this.getMusicInfo(musicId) // this.getMusicDetail(musicId) //这两个是用来获取song和musicPlay里面的东西的 }, //获取音乐详情功能函数 async getMusicInfo(id) { let songData = await request('/song/detail', { ids: id }) console.log(songData); this.setData({ 'song.singer': songData.songs[0].ar[0].name, 'song.title': songData.songs[0].name, 'song.coverImg': songData.songs[0].al.picUrl, 'musicPlay.musicTitle': songData.songs[0].name }) //动态修改窗口标题 wx.setNavigationBarTitle({ title: this.data.song.name, }) }, // 点击播放或暂停 handleMusicPlay() { if (this.data.isPlay == true) { this.setData({ isPlay: false }) this.musicPlay() } else { this.setData({ isPlay: true }) this.musicPlay() } }, //获取音乐src async getMusicDetail(musicId) { //获取链接 let link = await request('/song/url', { id: musicId }) console.log("发送了一次请求"); this.setData({ 'musicPlay.musicUrl': link.data[0].url }) }, // 音乐播放功能 musicPlay: function () { this.music = wx.getBackgroundAudioManager(), //监听播放暂停停止 this.music.onPlay(() => { // console.log('播放'); this.changePlayState(true) //全局 appInstance.globalData.musicId = this.data.musicId }), this.music.onPause(() => { // console.log('暂停');}) this.changePlayState(false) }) this.music.onStop(() => { this.changePlayState(false) }) this.music.src = this.data.musicPlay.musicUrl this.music.title = this.data.musicPlay.musicTitle if(this.data.isPlay){ this.music.play() }else{ this.music.pause() } }, // 修改播放状态 changePlayState(isPlay) { this.setData({ isPlay }) // 全局状态 appInstance.globalData.isMusicPlay = isPlay console.log(appInstance.globalData.isMusicPlay); }, handleChange(event) { // this.changePlayState(false) let type = event.currentTarget.id this.music.stop() // 订阅来自推荐页面的音乐id PubSub.subscribe('musicId',(msg,musicId)=>{ console.log(musicId); this.setData({ musicId }) this.getMusicInfo(musicId) // this.getMusicDetail(musicId) this.musicPlay() PubSub.unsubscribe('musicId') }) // 发送消息给推荐页面 PubSub.publish('switchType',type); } })
我知道了 又是异步的问题
可以这样
群里听说:现在基本上面试,算法+设计模式会占50的比重
最重要的是知道 各个组件的特性 知道什么时候 用什么技术最合适
这里我没有听老师讲 自己写的 因为我看小程序有他自己的进度条组件 更方便点
这里有个错我又查了好长时间
注意
本来我是写的currentTime的 找了好长时间的错误
sliderChange:function(e){ // this.music.pause() console.log(e.detail.value); this.music.seek(e.detail.value*this.data.duration/100) }, formatTime:function(time){ var minute=Math.floor(time/60)%60 var second=Math.floor(time)%60 return ((minute>10?minute:'0'+minute)+":"+(second>10?second:'0'+second)) }
e.detail.value是百分比
这里的seek要传currentTime
this.music.onTimeUpdate(()=>{ this.setData({ 'sliderDetail.duration':this.formatTime(this.music.duration), 'sliderDetail.currentTime':this.formatTime(this.music.currentTime), 'sliderDetail.percent':this.music.currentTime/this.music.duration*100, duration:this.music.duration //这里这样写是因为上面那个要用原始的数据而不是xx:xx }) })
听说:
作文推荐你们看潘赟的
英一鸭的准
妙啊 老师推荐:momentjs:专门处理日期和时间的一个库 我们拿到的数据已经有长度了
在播放页面 点击的时候有一个根据传入的是pre还是next在推荐页面订阅了id
我们也可以拿来用 但是呢 我觉得要把它拿出来放到data里面 因为没有点击的话(自动下一曲没有点击)就订阅不了 所以我就想到放到data里面
到这里就差不多告一段落了
贴出来播放页面的代码吧
var PubSub = require('pubsub-js'); // pages/songDetail/songDetail.js import request from '../../utils/request' // 获取全局实例 const appInstance = getApp() Page({ data: { isPlay: false, //标识是否在播放 song: { //渲染页面用的 title: '', singer: '', coverImg: '' }, musicId: '', //音乐的id musicPlay: { //播放音乐用的 musicTitle: '', musicUrl: '' }, sliderDetail:{ duration:'', currentTime:'', percent:'' } }, music: null, /** * 生命周期函数--监听页面加载 */ onl oad: function (options) { //接收路由跳转的query参数 // console.log(options.musicId); let musicId = options.musicId this.setData({ musicId }) // 判断之前的音乐是否在播放 if (appInstance.globalData.isMusicPlay && appInstance.globalData.musicId == musicId) { // 修改当前页面音乐播放状态为true this.setData({ isPlay: true }) } this.getMusicInfo(musicId) // this.getMusicDetail(musicId) //这两个是用来获取song和musicPlay里面的东西的 // 订阅来自推荐页面的音乐id 因为自动切换到下一曲也要用到订阅 那个只是局限于点击以后的 PubSub.subscribe('musicId',(msg,musicId)=>{ this.setData({ musicId }) this.getMusicInfo(musicId) // this.getMusicDetail(musicId) PubSub.unsubscribe('musicId') }) }, //获取音乐详情功能函数 async getMusicInfo(id) { let songData = await request('/song/detail', { ids: id }) this.setData({ 'song.singer': songData.songs[0].ar[0].name, 'song.title': songData.songs[0].name, 'song.coverImg': songData.songs[0].al.picUrl, 'musicPlay.musicTitle': songData.songs[0].name }) //动态修改窗口标题 wx.setNavigationBarTitle({ title: this.data.song.name, }) }, // 点击播放或暂停 handleMusicPlay() { if (this.data.isPlay == true) { this.setData({ isPlay: false }) this.musicPlay() console.log('handleMusic里面执行musicPlay 状态改为false'); } else { this.setData({ isPlay: true }) this.musicPlay() console.log('handleMusic里面执行musicPlay 状态改为true'); } }, //获取音乐src async getMusicDetail(musicId) { //获取链接 let link = await request('/song/url', { id: musicId }) this.setData({ 'musicPlay.musicUrl': link.data[0].url }) console.log("获取了一次地址"); this.musicPlay() }, // 音乐播放功能 musicPlay: function () { this.music = wx.getBackgroundAudioManager(), //监听播放暂停停止 this.music.onPlay(() => { // console.log('播放'); this.changePlayState(true) //全局 appInstance.globalData.musicId = this.data.musicId console.log("musicPlay里面音乐播放"); }), this.music.onPause(() => { // console.log('暂停');}) this.changePlayState(false) console.log("musicPlay里面音乐暂停"); }) this.music.onStop(() => { this.changePlayState(false) console.log("musicPlay里面音乐停止"); }) this.music.onTimeUpdate(()=>{ this.setData({ 'sliderDetail.duration':this.formatTime(this.music.duration), 'sliderDetail.currentTime':this.formatTime(this.music.currentTime), 'sliderDetail.percent':this.music.currentTime/this.music.duration*100, duration:this.music.duration }) this.music.onEnded(()=>{ console.log("播放完了"); PubSub.publish('switchType','next'); }) }) this.music.src = this.data.musicPlay.musicUrl this.music.title = this.data.musicPlay.musicTitle if(this.data.isPlay){ this.music.play() }else{ this.music.pause() } //slider }, // 修改播放状态 changePlayState(isPlay) { this.setData({ isPlay }) // 全局状态 appInstance.globalData.isMusicPlay = isPlay }, handleChange(event) { // this.changePlayState(false) let type = event.currentTarget.id console.log("点击了"+type); this.music.stop() // 订阅来自推荐页面的音乐id (放在onload里面了) // 发送消息给推荐页面 PubSub.publish('switchType',type); }, sliderChange:function(e){ // this.music.pause() console.log(e.detail.value); this.music.seek(e.detail.value*this.data.duration/100) }, formatTime:function(time){ var minute=Math.floor(time/60)%60 var second=Math.floor(time)%60 return ((minute>10?minute:'0'+minute)+":"+(second>10?second:'0'+second)) } })
随机播放 和 单曲循环
随机思路 1注意下标不要超出 2如果随机到了当前的音乐 要再随机一次
我是真的感受到了有人说的 厉害的代码有很高的可读性,一看就能看懂。像我这种遇到个功能改来改去的把我自己都绕晕了,一段代码一会搬到这儿 过一会加个功能发现好像不能放着儿,又搬到那边 ,虽然最后功能都实现了 但是还是感觉支离破碎的 可能是因为我还没有找到面向对象的感觉吧
听说:liveserver
搜索框 右侧搜索固定 左侧输入框自适应
这个可以用height lineheight 让文字居中然后margin来实现横线的效果
知识点:流式布局
没必要这样写:
可以直接初始化数据请求两个数据
async getInitData(){ let placeholderData = await request('/search/default'); //搜索框 let hotListData=await request("/search/hot/detail") //热歌榜数据 this.setData({ placeholder:placeholderData.data.realkeyword, hotList:hotListData.data }) },
感悟:太强了 原来人家连图标地址都给出来了(热歌,飙升的图标)
看到这样的样式 那个图片稍微再字的上面 可以这样 : 给父盒子设个宽度 然后让字上下居中 然后图片就可以在字的上面了
知识点 :输入框input事件与change事件的区别:change失去焦点 input实时
一个坑
这是什么东西呢 这是对象的意思 输入为text怎么会显示对象呢 因为async返回值是一个promise对象
解决:发请求再封装一个方法,用async 。这样的话searchContent就不会返回对象了
还有一个要注意的是节流的知识点 我们如果输入的很快 他会请求很多次 其实是没有必要的
所以第二种方法 可以设一个定时器 然后在定时器上加async
这样也不够 他还是请求那么多次
妙啊
这样还是不对啊 哪里不对呢
我们看这个 会发现每次它都会置为false 所以每次都会发请求 我们应该放到函数外面
这种也不行 因为第一次发请求也要等300ms
issend:false, //节流用 //监听input输入 handleInputChange(event){ console.log(event.detail.value); this.setData({ searchContent:event.detail.value.trim() }) if(this.issend){ return; } this.issend=true setTimeout(async() => { let searchListData=await request('/search',{keywords:this.data.searchContent,limit:10}) console.log(searchListData); this.issend=false }, 300); },
我们可以把请求提到外面
但是又遇到了那个async的问题
这时候就要单独封装一个请求的函数
//监听input输入 issend:false, //防抖节流用 handleInputChange(event){ console.log(event.detail.value); this.setData({ searchContent:event.detail.value.trim() }) if(this.issend){ return; } this.searchList() this.issend=true setTimeout(async() => { this.issend=false }, 300); //平常做节流一般都是300ms }, //请求搜索数据 async searchList(){ let searchListData=await request('/search',{keywords:this.data.searchContent,limit:10}) console.log(searchListData); },
当时那个搜索图标 我给设成position是absolute了 所以在搜索内容盒子里的图标全都跑到上面去了
因此上面的搜索图标应该给他单独起个样式名字
可以看到还是有问题的
在他为空的时候不应该发请求
然后这两个是互斥的关系
知识点:
两个都加一个block 然后用条件判断决定显示哪一个
block有什么好处呢 他可以帮你圈中多个元素 但是在页面结构中 并没有显示block如图
但是呢 还有一个问题 当显示列表为空的时候,他变不回热搜列表
这时候我们还要改一下js里面的代码 如果输入框没东西那么searchList为空数组(因为判断显示哪个block就是根据searchList是不是空决定的) (其实本来就应该这么做的 要不然输入框为空 数组里还有东西)
听说:angular+nest.js是很好的开发框架 angular以后出去ts无压力
听说:https://www.52pojie.cn/forum.php?mod=viewthread&tid=1301167&extra=page%3D1%26filter%3Dtypeid%26typeid%3D283 用react写网易云
听说:
听说 微信小程序云开发-从0打造云音乐全栈小程序 【最新3-9小节】接口不能用了
https://www.52pojie.cn/thread-1304174-1-1.html
我知道了 coderwhy就是王红元老师 讲的确实很好
历史记录应该跟热搜榜一起显示的 然后搜索的时候两个都隐藏 所以历史记录应该写在wx:else里面
静态搭建的时候 有一个删除的图标一直在最后一行 怎么做呢 可以用绝对定位来做
然后我们要考虑动态的历史记录了
首先一个知识点(unshift方法)https://www.w3school.com.cn/jsref/jsref_unshift.asp
在请求数据里面加
//将搜索的关键字放到历史记录里面 let{searchContent,historyList}=this.data historyList.unshift(searchContent) this.setData({ historyList })
但是一刷新就没有了 所以是不对的 应该存到本地
然后还有一个要注意的是要找一下以前有没有添加过这个记录
可以用find 还有indexof查找存不存在
如果不存在就添加 但是如果存在呢?如果是这种情况[a,b,c] 然后这次搜索的是b 我们就要把b添加进去 然后把后面的b删掉
代码放在请求数据函数里面
//将搜索的关键字放到历史记录里面 let{searchContent,historyList}=this.data if(historyList.indexOf(searchContent) !== -1){ //以前存在 //以前存在就删掉对应的下标对应的信息并添加信息到第一个 historyList.splice(historyList.indexOf(searchContent),1) historyList.unshift(searchContent) }else{ //以前不存在 historyList.unshift(searchContent) } wx.setStorageSync('searchHistory', historyList)
清空搜索内容:x号
那么一个x如何控制表单项的内容呢 我们可以通过value让他和我们搜集的数据关联起来
value={{searchContent}}
思路:我们通过事件处理函数来控制searchContent 为空 然后通过searchContent来控制value的值
clearSearchContent(){ this.setData({ searchContent:'' }) },
但是并没有清空 在函数里随便打印个东西 发现并没有执行
一个天坑 这是为什么呢 其实我们点的是表单项 ×号其实在表单项下面 我们要把它的层级提高一点
可以用这个小箭头测试一下能不能测出来叉号
有的时候点叉号命名输入框都没东西了,搜索出来的数据还是显示在上面
这时候我们还要把searchList置为空数组
听说:语义化是什么
这个的功能怎么做呢 很简单 清空缓存和数组就ok啦
可以再完善一点 再删除之前问用户同不同意
放到模板消息里面
听说:群里小伙伴发的面试题https://blog.csdn.net/qq_45018844/article/details/112008216
还可以再优化一下
当没有记录的时候 不显示记录的那个盒子 这样就没有“记录”这两个字和删除按钮了
判断历史记录数组的长度
还可以再优化一下
在没有内容的时候要隐藏掉那个叉号
也很简单 判断searchContent是不是空就好了
知识点 当然也可以hidden=true还是false 等同于vue里面的v-show
用户频繁输入删除 最好用hidden 性能要高
<text class="clear" bindtap="clearSearchContent" hidden="{{!searchContent}}">X</text>
听说:群友发的
小程序云开发数据库照抄 mogodb 还能抄的这么蠢
loopup 关联的 唯一索引, 关联结果还用数组
mogodb 关联的唯一索引的, 结果直接是单条数据
待做:mogodb是什么
https://developers.weixin.qq.com/miniprogram/dev/reference/wxml/template.html
注意与上次的组件对比
如何向模板内部动态导入数据?
另外一个知识点:es6新特性 三点运算符 用来拆包
听说:进销存什么意思
还有一个群友发的图
知识点 fly库(与axios库类似) 支持的平台多
有一个加密数据知识点:jasonwebtoken (jwt)
听说:学会node 然后mongodb数据库
分为三种:常规分包 独立分包 分包预下载
然后我们会发现很多路径都错了
都要注意修改一下
独立分包
独立分包是小程序中一种特殊类型的分包,可以独立于主包和其他分包运行。从独立分包中页面进入小程序时,不需要下载主包。当用户进入普通分包或主包内页面时,主包才会被下载。
开发者可以按需将某些具有一定功能独立性的页面配置到独立分包中。当小程序从普通的分包页面启动时,需要首先下载主包;而独立分包不依赖主包即可运行,可以很大程度上提升分包页面的启动速度
我们用这个的时候会发现对应分包的iconfont没了 必须在当前的页面再引入
一个坑 比如你独立分包的页面以前受到了app.wxss影响,现在分包了以后很多样式会出问题 需要再把app.wxss里面的内容复制过来