微信小程序【网易云音乐实战】(第四篇 用户登录、本地存储、视频播放、上拉下拉刷新)

一、用户登录

官网事件使用使用

在这里插入图片描述
在这里插入图片描述

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页面)
在这里插入图片描述

在页面跳转的时候,数据很难交互,这里使用本地缓存

数据存储 官方API

展示本地数据:
在这里插入图片描述
获取存储数据:

  // 个人中心页面跳转 登录界面
  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函数。

wx.showLoading(Object object)

显示 loading 提示框。需主动调用 wx.hideLoading 才能关闭提示框

点击导航时,通过navId来获取不同的视频数据。

this.getVideoList(this.data.navId);

注意点::

  1. 加载新的视频页时,清除旧的视频数据;
  2. 在得到数据前显示“正在加载”提示;
  3. 获取数据成功后,关闭提示;
// 获取视频列表数据
  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-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组件

在这里插入图片描述
VideoContext API
在这里插入图片描述

<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. 实现视频播放跳转☞指定的位置

VideoContext.seek()
在这里插入图片描述

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()

五、列表滑动功能

scroll-view

在这里插入图片描述

从上面一个效果演示可以看出,当界面下滑动时,上面的搜索框和标签栏都会跟着动,为了将其固定,实现列表滑动的效果,我们需要将 scrool-view 组件的高度设置一下,使其占据除了搜索框和标签栏和tabBar以外的高度。

/*  视频列表的样式  */
.videoScroll{
    
    
    padding-top: 10rpx;
    /*  calc:可以动态计算css的宽高 注意:计算数的两边必须加空格,不然计算失效 */
    /*  视口单位:vh vw 1vh = 1% 的视口高度, 1vw = 1% 的视口宽度  */
    height: calc(100vh - 152rpx);
}

六、 Scroll-view 下拉刷新、上拉加载

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也有下拉和上拉的刷新事件
在这里插入图片描述
在这里插入图片描述

猜你喜欢

转载自blog.csdn.net/qq_45021180/article/details/113059746