一、用户登录
1. 绑定事件 和 获取数据
bindinput :当输入框有变化时,触发事件。
handleInput(event){
// let type=event.currentTarget.id; // 1. 通过设置的id 传值
let type=event.currentTarget.dataset.type; // 2. 通过data-type 传值
this.setData({
[type]:event.detail.value; // [type]: value
})
},
结果:
2. 前端验证
wx.showToast(Object object) 提示信息
<button class="confirm-btn" bindtap="login">登录</button>
关键代码:
// 正则表达式验证号码是否正确
let {phone,password}=this.data;
let phoneReg=/^1(3|4|5|6|7|8|9)\d{9}$/;
if(!phoneReg.test(phone)){
// 提醒用户
wx.showToast({
title:"手机号格式错误!!",
icon:"none"
});
return;
}
全部代码:
//登录的回调
login(){
// 1. 收集表单项的数据
let {phone,password}=this.data;
// 2. 前端验证 : 不能为空,号码正确
if(!phone){
// 提醒用户
wx.showToast({
title:"用户名不能为空!",
icon:"none"
});
return;
}
// 正则表达式:号码正确
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;
}
// 提醒用户
wx.showToast({
title:"前端验证通过!",
icon:"none"
});
},
3. 后端验证
首先还是先启动本地的服务器!
// 3. 后端验证
let result = await request("/login/cellphone",{phone,password});
if(result.code === 200){ // 成功
wx.showToast({
title: "登录成功!"
});
}
else if(result.code === 400){
wx.showToast({
title: "手机号错误 !",
icon: "none"
});
}
else if(result.code === 502) {
wx.showToast({
title: "密码错误 !",
icon: "none"
});
}
else{
wx.showToast({
title: "登录失败,请重新登陆 !",
icon: "none"
});
}
二、本地存储(个人中心与登录界面的交互)
页面跳转 :
页面跳转使用 reLaunch(login --> personal页面)
在页面跳转的时候,数据很难交互,这里使用本地缓存
!
展示本地数据:
获取存储数据:
// 个人中心页面跳转 登录界面
toLogin(){
wx.navigateTo({
url:"/pages/login/login"
}),
onLoad: function (options) {
// 读取本地存储的用户信息
let userInfo = wx.getStorageSync("userInfo");
if(userInfo){
this.setData({
userInfo:JSON.parse(userInfo)
});
}
},
存储数据:
// 3. 后端验证
let result = await request("/login/cellphone",{phone,password});
if(result.code === 200){ // 登录成功
wx.showToast({
title: "登录成功!"
});
// 将用户信息存储到本地
// * JSON.stringify()的作用是将 JavaScript 对象转换为 JSON 字符串,而JSON.parse()可以将JSON字符串转为一个对象。
wx.setStorageSync("userInfo",JSON.stringify(result.profile));
// 跳转个人中心
wx.reLaunch({
url:"/pages/personal/personal"
});
}
三、获取用户的播放纪录
服务器接口:
页面:
<view class="recentPlayContainer">
<text class="title">最近播放</text>
<!-- 最近播放记录 -->
<scroll-view wx:if="{{recentPlayList.length}}" scroll-x class="recentScroll" enable-flex>
<view class="recentItem" wx:for="{{recentPlayList}}" wx:key="id">
<image src="{{item.song.al.picUrl}}"></image>
</view>
</scroll-view>
<view wx:else>暂无播放记录</view>
</view>
样式:
/* 最近播放纪录的样式*/
.recentScroll{
display: flex;
height: 200rpx;
}
.recentItem{
margin-right: 20rpx;
}
.recentScroll image{
width: 200rpx;
height: 200rpx;
border-radius: 10rpx;
}
获取播放记录的js代码:
// 获取用户的播放纪录
async getUserRecentPalyList(userId) {
let recentPalyListData = await request("/user/record", {uid: userId, type: 0});
// 由于每一项没有一个key 可以标识,这里使用map进行加工一下
let index = 0;
let recentPlayList = recentPalyListData.allData.slice(0,10).map(item=>{ // recentPlayList 就是加工后的数据
item.id=index++;
return item;
})
this.setData({
recentPlayList:recentPlayList
})
},
四、视频播放
1. 导航数据动态显示
知识点:1.标签动态添加 class属性值
<view class="navContent {{navId === item.id ? 'active':''}}" bindtap="changeNav" id="{{item.id}}">
界面:
<!-- 导航区域 -->
<scroll-view scroll-x class="navScroll" enable-flex>
<view class="navItem" wx:for="{{videoGroupList}}" wx:key="id">
<view class="navContent {{navId === item.id ? 'active':''}}" bindtap="changeNav" id="{{item.id}}">
{{item.name}}
</view>
</view>
</scroll-view>
js代码:
data: {
videoGroupList:[], // 导航标签数据
navId:"", // 导航的标识,标记哪个被选中
},
// 获取导航数据
async getVideoGroupListData() {
let videoGroupListData = await request("/video/group/list");
this.setData({
videoGroupList: videoGroupListData.data.slice(0, 14),
navId:videoGroupListData.data[0].id
})
},
//点击切换导航的回调
changeNav(event){
let navId = event.currentTarget.id;
this.setData({
navId:navId * 1 // 这里乘以一个 1 为了将字符串转换成 int类型
})
},
2. 通过 cookie 获取视频数据
2.1 存储cookie:
下面对request进行了修改,由于获取视频数据需要携带 cookie,登录时添加了isLogin参数来标识是否是登录操作,如果是登录操作:成功后需要将cookie存储到本地。
// 登录::::
let result = await request("/login/cellphone",{phone,password,isLogin:true});
存储 cookie
根据cookie是否有值来来设置是否携带cookie(使用三元表达式)
header:{
// 这里使用三元操作 ,为了保证 cookie有值,不然会报错
cookie:wx.getStorageSync("cookies") ? wx.getStorageSync("cookies").find(item => item.indexOf("MUSIC_U") !== -1) : ""
},
export default(url , data={}, method="GET")=>{
return new Promise((resolve,reject)=>{
// 1. new Promise 初始化promise 实例的状态pending
wx.request({
url:config.host + url,
data,
method,
header:{
// 这里使用三元操作 ,为了保证 cookie有值,不然会报错
cookie:wx.getStorageSync("cookies") ? wx.getStorageSync("cookies").find(item => item.indexOf("MUSIC_U") !== -1) : ""
},
success:(res)=>{
// 获取后台数据成功
if(data.isLogin){ // 如果是登录请求,需要将cookie存入本地
wx.setStorage({
key:"cookies",
data:res.cookies
})
}
// console.log("获取后台数据成功",res);
resolve(res.data); // resolve 修改promise的状体为成功状态 resolved
},
fail:(err)=>{
// 获取后台数据失败
// console.log("获取后台数据失败",err);
reject(err); //reject 修改promise的状态为失败状态 为 rejected
}
})
})
}
2.2 请求获取视频数据
// 获取导航数据
async getVideoGroupListData() {
let videoGroupListData = await request("/video/group/list");
this.setData({
videoGroupList: videoGroupListData.data.slice(0, 14),
navId:videoGroupListData.data[0].id
});
this.getVideoList(this.data.navId); // 获取视频列表数据 navId 是当前选择的 标签ID
},
// 获取视频列表数据
async getVideoList(navId) {
let videoListData = await request("/video/group", {id: navId});
// 为了wx:key 有唯一值,所以这里加工一下
let index =0 ;
let videoList = videoListData.datas.map(item =>{
item.id = index++;
return item;
})
this.setData({
videoList:videoList
})
},
3. 视频展示
页面:
<!-- 视频的列表区域 -->
<scroll-view scroll-y class="videoScroll">
<view class="videoItem" wx:for="{{videoList}}" wx:key="id">
<video src="{{item.data.urlInfo.url}}"></video>
<view class="content">{{item.data.title}}</view>
<view class="footer">
<image class="avatar" src="{{item.data.creator.avatarUrl}}"></image>
<text class="nickName">{{item.data.creator.nickname}}</text>
<view class="comments_praised">
<text class="item">
<text class="iconfont icon-buoumaotubiao15"></text>
<text class="count">{{item.data.praisedCount}}</text>
</text>
<text class="item">
<text class="iconfont icon-pinglun1"></text>
<text class="count">{{item.data.commentCount}}</text>
</text>
<button open-type="share" class="item btn">
<text class="iconfont icon-gengduo"></text>
</button>
</view>
</view>
</view>
</scroll-view>
样式:
/* 视频列表的样式 */
.videoScroll{
padding-top: 10rpx;
}
.videoItem{
padding: 0 3%;
}
.videoItem video{
width: 100%;
height: 360rpx;
border-radius: 10rpx;
}
.videoItem .content {
font-size: 26rpx;
height:80rpx;
line-height: 80rpx;
max-width: 500rpx;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
/* footer */
.footer {
border-top: 1rpx solid #eee;
padding: 20rpx 0;
}
.footer .avatar {
width: 60rpx;
height: 60rpx;
border-radius: 50%;
vertical-align: middle;
}
.footer .nickName {
font-size: 26rpx;
vertical-align: middle;
margin-left: 20rpx;
}
.footer .comments_praised {
float: right;
}
.comments_praised .btn {
display: inline;
padding: 0;
background-color: transparent;
border-color: transparent;
}
.comments_praised .btn:after {
border: none;
}
.comments_praised .item {
margin-left: 50rpx;
position: relative;
}
.comments_praised .item .count {
position: absolute;
top: -20rpx;
font-size: 20rpx;
}
4. 导航切换视频
通过导航栏切换视频时,获取服务器的数据要消耗一定的时间,为了给用户比较好的体验,当加载数据的时候显示加载提示,使用wx.showLoading函数。
显示 loading 提示框。需主动调用 wx.hideLoading 才能关闭提示框
点击导航时,通过navId来获取不同的视频数据。
this.getVideoList(this.data.navId);
注意点::
- 加载新的视频页时,清除旧的视频数据;
- 在得到数据前显示“正在加载”提示;
- 获取数据成功后,关闭提示;
// 获取视频列表数据
async getVideoList(navId) {
this.setData({
videoList:[], // 加载新的视频页时 将原来的视频数据清空
});
wx.showLoading({
title:"正在加载"
});
let videoListData = await request("/video/group", {id: navId});
wx.hideLoading(); // 关闭提示框
// 为了wx:key 有唯一值,所以这里加工一下
let index =0 ;
let videoList = videoListData.datas.map(item =>{
item.id = index++;
return item;
})
this.setData({
videoList:videoList
})
},
设置点击导航,当前的项滚动到第一位置!
<!-- 导航区域 -->
<scroll-view scroll-into-view="{{'nzs'+navId}}" scroll-with-animation="true" scroll-x class="navScroll" enable-flex>
<view id="{{'nzs'+item.id}}" class="navItem" wx:for="{{videoGroupList}}" wx:key="id">
<view class="navContent {{navId === item.id ? 'active':''}}" bindtap="changeNav" id="{{item.id}}">
{{item.name}}
</view>
</view>
</scroll-view>
5. 决解同时播放多个视频问题
当点击新的视频的时候,需要关闭上次点击的视频!
思想:单用例,好比,一个瓶子只能装一个球,需要将旧的球先倒出,然后将新的球放入!(关闭上一次的视频,播放新的视频)
<video id="{{item.data.vid}}" bindplay="handlePlay" src="{{item.data.urlInfo.url}}"></video>
// 点击播放| 继续播放 触发事件
handlePlay(event){
/* 问题:决解多个视频同时播放的问题
需求:
1. 在点击播放的事件中需要找到上一个播放的视频
2. 在播放新的视频之前需要关闭上一个正在播放的视频
关键:
1. 如何获取上一个视频的实例对象?
2. 如风确认点击播放的视频和正在播放的视频不是同一个视频
单例模式:
1. 需要创建多个对象的场景下,通过一个变量接收,始终保持只有一个对象,
2. 好处:节约内存空间
* */
let vid = event.currentTarget.id;
this.vid!==vid && this.videoContext && this.videoContext.stop(); // 关闭上一个播放的视频
this.vid = vid;
// 1. 创建一个可以控制 video标签的实例对象
this.videoContext = wx.createVideoContext(vid);
},
6. image 代替 video 性能优化
<!-- 视频的列表区域 -->
<scroll-view scroll-y class="videoScroll">
<view class="videoItem" wx:for="{{videoList}}" wx:key="id">
<video
id="{{item.data.vid}}"
bindplay="handlePlay"
src="{{item.data.urlInfo.url}}"
poster="{{item.data.coverUrl}}"
wx:if="{{videoId === item.data.vid}}"
object-fit="cover"
>
</video>
<!-- 性能优化:使用iamge标签代替video标签 -->
<image wx:else bindtap="handlePlay" id="{{item.data.vid}}" src="{{item.data.coverUrl}}"></image>
</scroll-view>
// 点击播放| 继续播放 触发事件
handlePlay(event){
let vid = event.currentTarget.id;
// 更新data中的videoId的状态数据
this.setData({
videoId:vid // 当前播放的videoID
})
// 1. 创建一个可以控制 video标签的实例对象
this.videoContext = wx.createVideoContext(vid);
this.videoContext.play()
},
7. 实现视频播放跳转☞指定的位置
1.播放进度变化时事件,实现跳转:
2.视频播放结束,需要将纪录在videoUpdateTime数组里面删除纪录:
<video
id="{{item.data.vid}}"
bindplay="handlePlay"
src="{{item.data.urlInfo.url}}"
poster="{{item.data.coverUrl}}"
wx:if="{{videoId === item.data.vid}}"
object-fit="cover"
bindtimeupdate="handleTimeUadate"
bindended="handleEnded"
>
</video>
保存纪录:
// 监听视频播放的事件
handleTimeUadate(event){
let videoTimeObj = {vid:event.currentTarget.id,currentTime:event.detail.currentTime}; // 整合一个对象,标记视频的播放时长
let {videoUpdateTime} = this.data;
// 判断纪录播放时长的videoUatateTime数组中是否存在当前的视频的播放记录
let videoItem = videoUpdateTime.find(item => item.vid === videoTimeObj.vid);
if(videoItem){ // 已存在
videoItem.currentTime = videoTimeObj.currentTime;
} else{ // 数组里面的没有纪录
videoUpdateTime.push(videoTimeObj);
}
this.setData({
videoUpdateTime:videoUpdateTime
})
},
删除纪录:
// 视频播放结束的回调函数
handleEnded(event){
console.log("播放结束");
// 移除纪录时长数组中当前视频的纪录
let {videoUpdateTime} = this.data;
videoUpdateTime.splice(videoUpdateTime.findIndex(item => item.vid === event.currentTarget.id),1);
this.setData({
videoUpdateTime:videoUpdateTime
})
},
再次播放跳转到上次离开的位置纪录:
// 1. 创建一个可以控制 video标签的实例对象
this.videoContext = wx.createVideoContext(vid);
// 判断当前的视频是否有播放纪录:如果有,跳转☞指定的位置
let {videoUpdateTime} = this.data;
let videoItem = videoUpdateTime.find(item => item.vid === vid);
if(videoItem){ // 跳转
this.videoContext.seek(videoItem.currentTime);
}
this.videoContext.play()
五、列表滑动功能
从上面一个效果演示可以看出,当界面下滑动时,上面的搜索框和标签栏都会跟着动,为了将其固定,实现列表滑动的效果,我们需要将 scrool-view 组件的高度设置一下,使其占据除了搜索框和标签栏和tabBar以外的高度。
/* 视频列表的样式 */
.videoScroll{
padding-top: 10rpx;
/* calc:可以动态计算css的宽高 注意:计算数的两边必须加空格,不然计算失效 */
/* 视口单位:vh vw 1vh = 1% 的视口高度, 1vw = 1% 的视口宽度 */
height: calc(100vh - 152rpx);
}
六、 Scroll-view 下拉刷新、上拉加载
下拉刷新:
上拉加载:
<!-- 视频的列表区域 -->
<scroll-view
scroll-y
class="videoScroll"
refresher-enabled="true"
bindrefresherrefresh="handleRefresh"
refresher-triggered="{{isTriggered}}"
bindscrolltolower="handleToLower"
>
// 1.自定义下拉刷新的回调 :scroll-view
handleRefresh(event){
// 1.发请求,获取最新的视频数据
// 2. 关闭下拉,在getVideoGroupListData函数中进行
this.getVideoList(this.data.navId);
},
// 收回下拉状态
this.setData({
isTriggered:false
})
// 2.自定义上拉触底你的回调:scrool-view
handleToLower(event){
console.log("下拉加载新的数据")
// 数据分页:1. 后端分页 2. 前端分页
// 模拟数据
let newVideoList = this.data.videoList;
let videoList = this.data.videoList;
// 更新数据
videoList.push(...newVideoList);
this.setData({
videoList:videoList
});
},
注意:Page也有下拉和上拉的刷新事件