小程序入门

196 阅读17分钟

小程序入门

初始化操作

1.小程序的文件配置

选择 js 模板 进入

选择在文件夹中删除 除 了sitemap project config的 文件

2 添加 app.js app.json app.wxss 等文件

app.json 用来引入组件 和 上边框的样式

app.wxss 用来设置共用样式 如

page{

height: 100%;

}

3.添加文件夹

static 文件夹 : 用于存放静态的图片

page 文件夹:用于存放组件

index 文件:存放index 组件

logs 文件用放 logs的组件

直接在各个文件夹中 加 page 就行了

js 文件里面放 逻辑代码 data 里面放一些数据 钩子函数里面放一些事件

数据绑定

单向绑定 : 在 index.js 里面的 data 里面声明数据 在wxml 里面声明 {{数据变量}}

双向绑定: 在 index.js 的钩子函数里面定义 this.setData({ msg:'修改之后的数据',

})

路由跳转

index.js page 下面声明一个 事件 函数 wx.navigateTo({ url:' /pages/xx/xxx' })

redirectTo(重定向跳转) relaunch 只开一个

在页面里面也可以设置 子组件 和 navigate 设置

生命周期

渲染完成之前 : onload() onshow() --会执行多次表示初始化显示 OnReady--(mounted) 全部显示完毕

onhide 切到后台触发

组件的使用

获取用户授权信息

(调整基础库设置为 2.14)

button getUserInfo 属性

wxml :

获得用户信息并且设置回调

<button bindgetuserinfo="handleGetUserInfo"  open-type="getUserInfo">获取用户信息</button>

js :

 handleGetUserInfo(res){
    console.log(res)
    if(res.detail.userInfo){
      this.setData({
        userInfo:res.detail.userInfo
      })
    }
  },

在onload 钩子里 wx.getUserInfo 可以解决重复授权的问题

wx.getUserInfo({

  success:(res)=>{

  console.log("成功"+res);

  userInfo:res.userInfo

  },fail:(err)=>{

   console.log(err)

  }

 })

使用 IDE 进行开发

在 vscode 里面安装插件 wxml 在里面进行打开初始化好的文件夹

初始文件设置

删除pages 页面下的 页面文件

新建一个index 文件并且在里面建立page

建立static 文件夹在里面放 images 和 iconfont 样式

app.json 文件进行

{
  "pages":[
    "pages/index/index"
  ],
  "style": "v2",
  "sitemapLocation": "sitemap.json",
  "window": {
    "navigationBarBackgroundColor": "#d43c33",
    "navigationBarTextStyle": "white",
    "navigationBarTitleText": "志云乐"
  }

}

app.js 清空

App({
  onLaunch() {
  }
})

轮播图

轮播图可以直接采用 微信自带的组件

index.wxml

  <!-- 轮播图区域 -->
  <swiper class="banners" indicator-dots indicator-color="ivory"  indicator-active-color="#d43c33" >
    <swiper-item>
      <image src="/static/images/nvsheng.jpg"></image>
    </swiper-item>
    <swiper-item>
      <image src="/static/images/nvsheng.jpg"></image>
    </swiper-item>
    <swiper-item>
      <image src="/static/images/nvsheng.jpg"></image>
    </swiper-item>
  </swiper>

index.wxss

/* pages/index/index.wxss */
.banners{
  width: 100%;
  height: 300rpx;
}
.banners image{
  width: 100%;
  height: 100%;
}

导航按钮

在 阿里矢量图标库 选择好图标后 生产css 代码 放到 static/iconfont 里面

index.wxml

  <!-- 图标导航区域 -->
   <view class="navContainer">
    <view class="navItem">
        <text class="iconfont icon-tuijian1"></text>
        <text>每日推荐</text>
    </view>
    <view class="navItem">
        <text class="iconfont icon-gedan"></text>
        <text>歌单</text>
    </view>
    <view class="navItem">
        <text class="iconfont icon-paihangbang"></text>
        <text>排行榜</text>
    </view>
    <view class="navItem">
        <text class="iconfont icon-diantai"></text>
        <text>电台</text>
    </view>
    <view class="navItem">
        <text class="iconfont icon-zhibo"></text>
        <text>直播</text>
    </view>
   </view>

index.wxss

.navContainer{
  display: flex;
}
.navItem{
  display: flex;
  flex-direction: column;
  align-items: center;
  width: 20%;
}
.navItem .iconfont{
  width: 100rpx;
  height: 100rpx;
  border-radius: 50%;
  text-align: center;
  line-height: 100rpx;
  background-color: #f40;
  font-size: 50rpx;
  color: white;
  margin: 20rpx 0;
}
.navItem text{
  font-size: 26rpx;
}

滚动列表显示

滚动显示

将显示的部分进行滚动呈现,

<scroll-view class="recommendScroll" enable-flex scroll-x>
    <view class="srcollItem">
      <image  src="/static/images/nvsheng.jpg"/>
      <text>表白,表白表白,表白,表白 表白,表白表白,表白,表白</text>
    </view>
     <view class="srcollItem">
      <image  src="/static/images/nvsheng.jpg"/>
      <text>表白,表白表白,表白表白表白</text>
    </view>
     <view class="srcollItem">
      <image  src="/static/images/nvsheng.jpg"/>
      <text>表白,表白表白,表白表白,表白</text>
    </view>
     <view class="srcollItem">
      <image  src="/static/images/nvsheng.jpg"/>
      <text>表白,表白,表白表白,表白</text>
    </view>

enable-flex 用于将显示部分变为流动布局 scroll-x 变为的横向滚动

.recommendScroll{
 display: flex;
}

.srcollItem{
  width: 200rpx;
  margin-right: 20rpx;
}
.srcollItem text{
  display: block;
  font-size: 24rpx;
  /* 单行溢出隐藏 */
  /* white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis; */
  /* 多行文本溢出 */
  overflow: hidden;
  text-overflow: ellipsis;
  display: -webkit-box;
  /* 设置对齐模式 */
  -webkit-box-orient: vertical; 
  -webkit-line-clamp: 2;  /*设置显示行数*/
}
.srcollItem image{
  
  width: 200rpx;
  height: 200rpx;
  border-radius: 10rpx;
}

前后端同步

request 的请求 单独在 utils 文件夹里面建立一个 request.js 请求 index.js 调用该请求就可以了 ,记得在开发工具里设置忽略 https

request.js

import config from "./config";
export default (url,data={},method='GET')=>{
    return new Promise((resolve,reject)=>{
        wx.request({
            url:config.host+url,
            data,
            method,
            success:(res) => {
              console.log('请求成功',res);
              resolve(res);
            },
            fail:(err)=>{
             console.log('请求失败',err);
            }
          })
    })
  
}

config.js

// 配置服务器对象
export default{
    host: 'http://localhost:3000'
}

index.js

  onLoad: async function (options) {
  let result=await request('/banner',{type:2})
    console.log( '请求结果 ', result);
},

排行榜的实现

建立全局组件 navheader (用于存放模块的标题)

建立一个全局文件夹 components 下面的 Navheader 文件夹 创建component

NavHeader.wxml

<view class="header">
      <text class="title">
       {{title}}
      </text>
      <view>
        <text class="more">
        查看更多
        </text>
      </view>   
</view>

NavHeader.wxss

/* components/NavHeader/NavHeader.wxss */
.header .title{
    font-size: 32rpx;
    line-height: 32rpx;
    color:#666;
  }
.header {
    margin-bottom: 40rpx;
  }
.header .more{
    float: right;
    border:  1rpx solid #333;
    padding: 10rpx 20rpx;
    font-size: 24rpx;
    line-height: 7rpx;
    border-radius: 30rpx;
  }

NavHeader.js 用于创建外部导入的数据

properties: {
     title:{
      type:String,
      value:"t默认值"
     },
  },

index.json

"usingComponents": {
    "NavHeader":"/components/NavHeader/NavHeader"
  }

建立 request.js 中的请求

这里的query 参数需要使用多个加入到

data: {
   bannerLists:[],
   recommendList:[],
   topList:[]

  },

  /**
   * 生命周期函数--监听页面加载
   */
  onLoad: async function (options) {
    let bannerList=await request('/banner',{type:2})
    this.setData({
      bannerLists: bannerList.data.banners
    })

    let  recommendListData=await request('/personalized',{limit:10});
    this.setData({
      recommendList:recommendListData.data.result
    })
    let indx=0;
    let topListArr=[];
    while(indx<5){
      let topListData=await request('/top/list',{idx:indx++})
      let topListItem={name:topListData.data.playlist.name,tracks:topListData.data.playlist.tracks.slice(0,3)}
      topListArr.push(topListItem);
      this.setData({
        topList:topListArr
      })
    }
   
  },

创建滚动 排行榜(使用swiper item实现)

index.wxml

 <!-- 排行榜 -->
   <view class="topList" >
    <NavHeader title="排行榜"></NavHeader>
    <!-- 排行榜内容区域 -->
    <swiper class="topListSwiper" circular next-margin="50px" previous-margin="50px">
      <swiper-item wx:for="{{topList}}" wx:key="name">
        <view class="swiperItem">
        <view class="title">{{item.name}}</view>
        <view class="musicItem" wx:for="{{item.tracks}}" wx:key="id" wx:for-item="musicItem">
          <image src="{{musicItem.al.picUrl}}"></image>
          <text class="count">{{index+1}}</text>
          <text class="musicName">{{musicItem.name}}</text>
        </view>
        </view>
       </swiper-item>
   </swiper>
   </view>

index.wxss

.topList{
  padding: 10rpx;
}

.swiperItem .title{
  font-size: 30rpx;
  line-height: 60rpx;
}
.topListSwiper{
  height: 400rpx;
}
.swiperItem{
  width: 96%;
  background: #fdfdfd;
}
.musicItem{
  display: flex;
  margin-bottom: 20rpx;
}
.musicItem .count{
  width: 100rpx;
  height: 100rpx;
  text-align: center;
  line-height: 100rpx;
}
.musicItem image{
  width: 100rpx;
  height: 100rpx;
  border-radius: 2rpx;
}
.musicItem .musicName{
  width: 100rpx;
  height: 100rpx;
  text-align: center;
  line-height: 100rpx;
  font-size: 24rpx;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

tabbar 的创建

在 app.json 里面创建

list 里面主要写上图标和文字,background tabbar 的背景颜色 ,selectedColor 文字选择颜色

"tabBar": {
    "color": "#333",
    "selectedColor": "#d43c33", 
    "backgroundColor": "#fff",
    "list": [
      {
      "pagePath": "pages/index/index",
      "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/tab-video.png",
      "selectedIconPath": "/static/images/tabs/tab-video-current.png"
    },
    {
      "pagePath": "pages/Personal/Personal",
      "text": "个人中心",
      "iconPath": "/static/images/tabs/tab-personal.png",
      "selectedIconPath": "/static/images/tabs/tab-personal-current.png"
    }
  ]

实现手指运动距离的测量

startY=event.touches[0].clientY 一个手指

实现个人中心页面

复制相应的静态的页面到 Personal 文件中

personal .wxml

<!--pages/personal/personal.wxml-->
<view class="personalContainer">
  <view class="user-section">
    <image class="bg" src="/static/images/personal/bgImg2.jpg"></image>
    <view class="user-info-box" bindtap="toLogin">
      <view class="portrait-box">
        <image class="portrait" src='{{userInfo.avatarUrl ? userInfo.avatarUrl : "/static/images/personal/missing-face.png"}}'></image>
      </view>
      <view class="info-box">
        <text class="username">{{userInfo.nickname ? userInfo.nickname : "游客"}}</text>
      </view>
    </view>

    <view class="vip-card-box">
      <image class="card-bg" src="/static/images/personal/vip-card-bg.png" mode=""></image>
      <view class="b-btn">
        立即开通
      </view>
      <view class="tit">
        <!-- 会员图标-->
        <text class="iconfont icon-huiyuan"></text>
        会员
      </view>
      <text class="e-m">manster union</text>
      <text class="e-b">开通会员听歌, 撸代码</text>
    </view>
  </view>


  <view
    class="cover-container"
    bindtouchstart="handleTouchStart"
    bindtouchmove="handleTouchMove"
    bindtouchend="handleTouchEnd"
    style="transform: {{coverTransform}};transition: {{coverTransition}}"
  >
    <image class="arc" src="/static/images/personal/arc.png"></image>
    <!-- 个人中心导航 -->
    <view class="nav-section">
      <view class="nav-item"  hover-class="common-hover"  hover-stay-time="50">
        <text class="iconfont icon-xiaoxi"></text>
        <text>我的消息</text>
      </view>
      <view class="nav-item"   hover-class="common-hover" hover-stay-time="50">
        <text class="iconfont icon-haoyou"></text>
        <text>我的好友</text>
      </view>
      <view class="nav-item"  hover-class="common-hover"  hover-stay-time="50">
        <text class="iconfont icon-geren"></text>
        <text>个人主页</text>
      </view>
      <view class="nav-item" hover-class="common-hover"  hover-stay-time="50">
        <text class="iconfont icon-gexingzhuangban"></text>
        <text>个性装扮</text>
      </view>
    </view>

    <!-- 个人中心列表 -->
    <view class="personalContent">
      <view class="recentPlayContainer">
        <text class="title">最近播放</text>
        <!-- 最近播放记录 -->
        <scroll-view wx:if="{{recentPlayList.length}}" scroll-x="true" class="recentScroll" enable-flex="true">
          <view class="recentItem" wx:for="{{recentPlayList}}" wx:key="id">
            <image bindtap="toSongDetail" id="{{item.song.id}}" src="{{item.song.al.picUrl}}"></image>
          </view>
        </scroll-view>
        <view wx:else>暂无播放记录</view>
      </view>

      <view class="cardList">
        <view class="card-item">
          <text class="title">我的音乐</text>
          <text class="more"> > </text>
        </view>
        <view class="card-item">
          <text class="title">我的收藏</text>
          <text class="more"> > </text>
        </view>
        <view class="card-item">
          <text class="title">我的电台</text>
          <text class="more"> > </text>
        </view>
      </view>
    </view>
  </view>

  <!-- 退出登录按钮 -->
  <view hidden="{{!isLogin}}">
    <button class="logout-btn" bindtap="logout">退出登录</button>
  </view>
</view>

Personal.wxss

/* pages/personal/personal.wxss */
/* pages/personal/personal.wxss */
.personalContainer {
  width: 100%;
  height: 100%;

}

.personalContainer .user-section {
  height: 520rpx;
  position: relative;
  padding: 100rpx 30rpx 0;
}
.user-section .bg {
  position: absolute;
  left: 0;
  top: 0;
  width: 100%;
  height: 100%;
  opacity: 0.7;
  filter: blur(1px);
}


.user-info-box{
  height: 180rpx;
  display:flex;
  align-items:center;
  position:relative;
  z-index: 1;
}

.user-info-box .portrait{
  width: 130rpx;
  height: 130rpx;
  border:5rpx solid #fff;
  border-radius: 50%;
}
.user-info-box .username{
  font-size: 24;
  color: #303133;
  margin-left: 20rpx;
}

/* vip-box */
.vip-card-box {
  position: relative;
  display: flex;
  flex-direction: column;
  background: rgba(0, 0, 0, .7);
  height: 240rpx;
  color: #f7d680;
  border-radius: 16rpx 16rpx 0 0;
  padding: 20rpx 24rpx;
}


.vip-card-box .card-bg{
  position:absolute;
  top: 20rpx;
  right: 0;
  width: 380rpx;
  height: 260rpx;
}

.vip-card-box .b-btn{
  position: absolute;
  right: 20rpx;
  top: 16rpx;
  width: 132rpx;
  height: 40rpx;
  text-align: center;
  line-height: 40rpx;
  font-size: 22rpx;
  color: #36343c;
  border-radius: 20px;
  background: #f9e6af;
  z-index: 1;
}

.vip-card-box .b-btn{
  position: absolute;
  right: 20rpx;
  top: 16rpx;
  width: 132rpx;
  height: 40rpx;
  text-align: center;
  line-height: 40rpx;
  font-size: 22rpx;
  color: #36343c;
  border-radius: 20px;
  /*background: linear-gradient(left, #f9e6af, #ffd465);*/ /*渐变不生效*/
  background: #f9e6af;
  z-index: 1;
}

.vip-card-box .tit {
  font-size: 22rpx;
  color: #f7d680;
  margin-bottom: 28rpx;
}
.vip-card-box .tit .iconfont{
  color: #f6e5a3;
  margin-right: 16rpx;
}




.vip-card-box .e-m{
  font-size: 34rpx;
  margin-top: 10rpx;
}
.vip-card-box .e-b{
  font-size: 24rpx;
  color: #d8cba9;
  margin-top: 10rpx;
}


.cover-container{
  margin-top: -150rpx;
  padding: 0 30rpx;
  position:relative;
  background: #f5f5f5;
  padding-bottom: 20rpx;
}

.cover-container .arc{
  position:absolute;
  left: 0;
  top: -34rpx;
  width: 100%;
  height: 36rpx;
}


/* 导航部分 */
.cover-container .nav-section {
  display: flex;
  background: #fff;
  padding: 20rpx 0;
  border-radius: 15rpx;
}


.nav-section .nav-item {
  width: 25%;
  box-sizing: border-box;
  display: flex;
  flex-direction: column;
  align-items: center;
}

.nav-section .nav-item .iconfont {
  font-size: 50rpx;
  color: #d43c33;
  line-height: 70rpx;
}

.nav-section .nav-item text:last-child {
  font-size: 22rpx;

}


/* 个人中心列表 */
.personalContent {
  background: #fff;
  margin-top: 20rpx;
}

/* 最近播放 */
.personalContent .scrollView {
  display: flex;
  height: 160rpx;
}
.personalContent .recentPlay {
  display: flex;
}

.recentPlayContainer .title {
  padding-left: 20rpx;
  font-size: 26rpx;
  color: #333;
  line-height: 80rpx;
}

.personalContent .recentPlay image {
  width: 160rpx;
  height: 160rpx;
  margin-left: 20rpx;
  border-radius: 20rpx;
}


.cardList {
  margin-top: 20rpx;

}
.cardList .card-item{
  border-top: 1rpx solid #eee;
  height: 80rpx;
  line-height: 80rpx;
  padding: 10rpx;
  font-size: 26rpx;
  color: #333;
}
.cardList .card-item .more {
  float: right;
}

/*最近播放记录*/
.recentScroll {
  display: flex;
  height: 200rpx;
}
.recentItem {
  margin: 0 20rpx;
}
.recentItem image{
  width: 200rpx;
  height: 200rpx;
  border-radius: 10rpx;
}

.logout-btn {
  margin-top: 5rpx;
}

实现滑动特效

使用 wx的触摸函数

先设置需要使用的静态数据

data: {
    coverTransform:'translateY(0)', //移动距离
    coverTransition:'', // 过渡效果
  },
//触摸开始事件
let startY=0; // 手指起始坐标
let moveY=0; //手指移动的坐标
let moveDistance=0; // 手指移动距离
handleTouchStart(event){
// 获取起始坐标
startY=event.touches[0].clientY;
// 清空过渡效果
this.setData({
      coverTransition:''
    })
},
//  触摸移动的事件处理
  handleTouchMove(event){
  //  获取移动坐标
     moveY=event.touches[0].clientY;
     moveDistance=moveY-startY;
     if(moveDistance>80){
      moveDistance=80;
     }
     if(moveDistance<=0){
        return;
     }
     this.setData({
      coverTransform: `translateY(${moveDistance}rpx)` //设置移动距离
     })
  },
 // 触摸结束的事件,手指松开回弹
  handleTouchEnd(){
    this.setData({
      coverTransform: `translateY(0rpx)`,
      coverTransition: 'transform 0.5s linear'
     })
  },

在包裹的盒子上面绑定style
 style="transform: {{coverTransform}};transition: {{coverTransition}}"

登录页面的实现

在page 页面里面创建一个 login (为了调试可以在app.json 中调整到最前面申明)

个人中心页面的跳转函数

toLogin(){
     wx.navigateTo({
       url:'/pages/login/login'
     })
  },
   <view class="user-info-box" bindtap="toLogin">   //绑定事件

在login.json 里面设置 标题

"navigationBarTitleText":"登录页面"

登录页面的主体

login.wxml

<!--pages/login/login.wxml-->
<view class="container">
  <view class="wrapper">
    <view class="left-top-sign">Login</view>
    <view class="welcome">
      欢迎回来!
    </view>
    <view class="input-content">
      <view class="input-item">
        <text class="tit">手机号码:</text>
        <input type="text" placeholder="请输入手机号码" data-test="abc" data-type="phone" id="phone" bindinput="handleInput"/>
      </view>
      <view class="input-item">
        <text class="tit">密码:</text>
        <input type="password"  placeholder="请输入密码" data-test="abc" data-type="password" id="password" bindinput="handleInput"/>
      </view>
    </view>
    <button class="confirm-btn" bindtap="login">登录</button>
    <view class="forget-section">
      忘记密码?
    </view>
  </view>
  <view class="register-section">
    还没有账号?
    <text >马上注册</text>
  </view>
</view>


login.wxss

/* pages/login/login.wxss */
.wrapper{
    position:relative;
    z-index: 90;
    padding-bottom: 40rpx;
  }
  
  .left-top-sign{
    font-size: 120rpx;
    color: #f8f8f8;
    position:relative;
    left: -16rpx;
  }
  
  .welcome{
    position:relative;
    left: 50rpx;
    top: -90rpx;
    font-size: 46rpx;
    color: #555;
  }
  
  
  .input-content{
    padding: 0 60rpx;
  }
  .input-item{
    display:flex;
    align-items:flex-start;
    justify-content: center;
    padding: 0 30rpx;
    background:#f8f6fc;
    height: 80rpx;
    border-radius: 4px;
    margin-bottom: 40rpx;
  
  }
  
  .input-item:last-child{
    margin-bottom: 0;
  }
  .input-item .tit{
    margin-top: 15rpx;
    height: 50rpx;
    line-height: 50rpx;
    font-size: 35rpx;
    color: #606266;
    width: 28%;
  }
  .input-item input{
    margin-top: 10rpx;
    margin-left: 20rpx;
    height: 60rpx;
    font-size: 35rpx;
    color: #303133;
    width: 72%;
  }
  .confirm-btn{
    width: 630rpx!important;
    height: 76rpx;
    line-height: 76rpx;
    border-radius: 50rpx;
    margin-top: 70rpx;
    background: #d43c33;
    color: #fff;
    font-size: 32rpx;
    padding: 0;
  }
  .confirm-btn2:after{
    border-radius: 100px;
  }
  
  .forget-section{
    font-size: 28rpx;
    color: #4399fc;
    text-align: center;
    margin-top: 40rpx;
  }
  
  .register-section{
    position:absolute;
    left: 0;
    bottom: 50rpx;
    width: 100%;
    font-size: 28rpx;
    color: #606266;
    text-align: center;
  
  }
  .register-section text{
    color: #4399fc;
    margin-left: 10rpx;
  }
  

输入标签的事件绑定

login.js

handleInput(event){
   let type =event.currentTarget.id; //用id标记绑定的对象
  // let type =event.currentTarget.dataset.type; // 从自定义标记里面 data-key=value
   this.setData({
     [type]: event.detail.value
   })
 },
<view class="input-item">
        <text class="tit">手机号码:</text>
        <input type="text" placeholder="请输入手机号码" data-test="abc" data-type="phone" id="phone" bindinput="handleInput"/>
      </view>

表单验证

 <button class="confirm-btn" bindtap="login">登录</button>
async 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;
  }
   // 验证通过就发请求
   let result =await request('/login/cellphone',{phone,password})
   if(result.code===200 ){
    wx.showToast({
      title: '登录成功',
      icon:'none'
     })
   }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'
    })
    wx.reLaunch({
      url: '/pages/Personal/Personal',
    })
   }
 },

wx.reLaunch 是关掉 其他路由页面的方式路由跳转

播放记录动态显示

personal.wxml

<view class="user-info-box" bindtap="toLogin">
      <view class="portrait-box">
        <image class="portrait" src='{{userInfo.avatarUrl ? userInfo.avatarUrl : "/static/images/personal/missing-face.png"}}'></image>
      </view>
      <view class="info-box">
        <text class="username">{{userInfo.nickname ? userInfo.nickname : "游客"}}</text>
      </view>
    </view> 
<scroll-view wx:if="{{recentPlayList.length}}" scroll-x="true" class="recentScroll" enable-flex="true">
          <view class="recentItem" wx:for="{{recentPlayList}}" wx:key="id">
            <image bindtap="toSongDetail" id="{{item.song.id}}" src="{{item.song.al.picUrl}}"></image>
          </view>
        </scroll-view>

personal.js api 的设置

import request from "../../utils/request";

//data 添加
 recentPlayList:[]

async getUserRecentPlay(){
    let index=0;
   let recentPlayListData =await request('/user/record',{uid:2075939212,type:0})
    this.setData({
      recentPlayList:recentPlayListData.data.allData.splice(0,10).map(item=>{
        item.id=index++;
        return item;
      })
    })
  },
      
  在onload 里面使用上述函数

video 页面

nav 导航栏

静态页面

scroll-into-view 移动到 scroll-item id="{{'scroll'+item.id}}" scroll 是为了必须保证 scroll id不是数字开头

scroll-with-animation="{{true}}" 是scroll 的

多个类的选择 class="navContent {{navid==item.id?'active':''}}"

点击绑定事件

//video.wxml
bindtap="changeTarget"  id="{{item.id}}">
 
 //video.js
 changeTarget(event){
 
 let targetid=event.currentTarget.id;
 this.setData({
    navid:targetid*1,
    videoList:[]
 })
 wx.showLoading({
  title:'正在加载'
 })
 this.getVideoList(this.data.navid)
},

video.wxml

<!--pages/video/video.wxml-->
<view class="videoContianer">
    <!-- 头部 -->
<view class="header">
    <image src="/static/images/video/video.jpg"/>
     <text class="search">点击搜索</text>
     <image src="/static/images/logo.png"/>
</view>
    <!-- nav头部 -->
<scroll-view class="navScroll" scroll-x scroll-into-view="{{'scroll'+navid}}" enable-flex scroll-with-animation="{{true}}">
    <view id="{{'scroll'+item.id}}" class="navItem" wx:for="{{videoGroupList}}" wx:key="id">
        <view  class="navContent {{navid==item.id?'active':''}}" bindtap="changeTarget"  id="{{item.id}}">
        {{item.name}}
        </view> 
    </view>
</scroll-view>

<scroll-view  class="videoScroll"  scroll-y>
    <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-xihuan"></text>
            <text class="count">{{item.data.praisedCount}}</text>
          </text>
          <text class="item">
            <text class="iconfont icon-comment"></text>
            <text class="count">{{item.data.commentCount}}</text>
          </text>
          <button open-type="share" class="item btn">
            <text class="iconfont icon-xuanxiang"></text>
          </button>
        </view>
      </view>
    </view>

   
</scroll-view>
</view>

video.wxss

/* pages/video/video.wxss */
.videoContianer .header{
 display: flex;
 padding: 10rpx;
}

.videoContianer .header image{
   width: 60rpx;
   height: 60rpx;
}
.videoContianer .header .search{
    border:1rpx solid #eee;
    flex:1;
    margin: 0 20rpx;
    font-size: 26rpx;
    text-align: center;
    line-height: 60rpx;
    color: #f40
}
/* 导航区域 */

.navScroll{
    display: flex;
    white-space: nowrap;
    height: 60rpx;
}
.navScroll .navItem{
    padding: 0 30rpx;
    font-size: 28rpx;
    height: 60rpx;
    line-height: 60rpx;
}
.navScroll .navContent{
    height: 60rpx;
    box-sizing: border-box;
}
.navItem .active{
    border-bottom: 3rpx solid #f40;
}

/* videoItem的结构 */
.videoItem {
    padding: 2% 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;
  }

video.js

// pages/video/video.js
import request from "../../utils/request"
Page({
  /**
   * 页面的初始数据
   */
  data: {
    videoGroupList:[],
    navid:'',
    videoList:[]
  },
//  获取nav数据
async getVideoData(){
let videoGroupListData= await request('/video/group/list')
this.setData({
  navid:videoGroupListData.data.data[8].id,
  videoGroupList:videoGroupListData.data.data.slice(8,18)
})
this.getVideoList(this.data.navid)
},
// 点击切换nav对象,更新id
changeTarget(event){
 
 let targetid=event.currentTarget.id;
 this.setData({
    navid:targetid*1,
    videoList:[]
 })
 wx.showLoading({
  title:'正在加载'
 })
 this.getVideoList(this.data.navid)
},
// 更新视频列表数据
async getVideoList(navId){
   let index= 0;
   let videoListData =await request('/video/group',{id:navId*1})
  //  添加数组的id 便于遍历
   let videoList=videoListData.data.datas.map(item=>{item.id=index++;return item;});
   this.setData({
      videoList
    })
     // 关闭loading
  wx.hideLoading();
  },
  /**
   * 生命周期函数--监听页面加载
   */
  onLoad(options) {
   this.getVideoData();
   this.getVideoList(this.data.navid)
  },

  /**
   * 生命周期函数--监听页面初次渲染完成
   */
  onReady() {

  },

  /**
   * 生命周期函数--监听页面显示
   */
  onShow() {

  },

  /**
   * 生命周期函数--监听页面隐藏
   */
  onHide() {

  },

  /**
   * 生命周期函数--监听页面卸载
   */
  onUnload() {

  },

  /**
   * 页面相关事件处理函数--监听用户下拉动作
   */
  onPullDownRefresh() {

  },

  /**
   * 页面上拉触底事件的处理函数
   */
  onReachBottom() {

  },

  /**
   * 用户点击右上角分享
   */
  onShareAppMessage() {

  }
})

视频显示区

video.wxml

  <!-- 视频区 -->
  <scroll-view 
    scroll-y="true" 
    class="videoScroll"
    refresher-enabled="true"
    bindrefresherrefresh="handleRefresher"
    refresher-triggered="{{isTriggered}}"
  >
    <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}}"
        autoplay="true"
        object-fit="fill"
        bindtimeupdate="handleTimeUpdate"
        bindended="handleEnded"
      ></video>
       
      <!-- 优化image图片代替video标签 -->
      <image wx:else bindtap="handlePlay" id="{{item.data.vid}}" class="common" src="{{item.data.coverUrl}}"></image>

      <!-- 底部 -->
      <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-xihuan"></text>
            <text class="count">{{item.data.praisedCount}}</text>
          </text>
          <text class="item">
            <text class="iconfont icon-comment"></text>
            <text class="count">{{item.data.commentCount}}</text>
          </text>
          <button open-type="share" class="item btn">
            <text class="iconfont icon-xuanxiang"></text>
          </button>
        </view>
      </view>
    </view>
  </scroll-view>

video.wxss

/* 视频 */
.videoScroll{
  margin-top: 10rpx;
  /* 动态计算css宽高 1vh表示1%的高度 */
  height: calc(100vh - 71rpx);
}
.videoItem {
  padding: 0 3%;
}
.videoItem video {
  width: 100%;  
  height: 360rpx;
  border-radius: 10rpx;
}
.videoItem .common {
  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;
}

refresher-enabled="true" 下拉刷新的必备

bindrefresherrefresh="handleRefresher" 向下拉动触发,刷新事件

refresher-triggered="{{isTriggered}}" 是否刷新的标记 (绑定具体数据)

bindplay="handlePlay" 播放的设置 id="{{item.data.vid}}" 绑定item对象 poster="{{item.data.coverUrl}}" 封面图 class="common" 封面和video 共同的样式 wx:if="{{videoId === item.data.vid}}" 是否显示video

autoplay="true" 自动播放 object-fit="fill" 视频直接填充满 bindtimeupdate="handleTimeUpdate" 时间进行更新 bindended="handleEnded" 视频播放完的事件

bindscrolltolower="handleToLower" 下拉到底的事件

事件介绍

video.js

  data: {
    videoGroupList: [],//导航标签
    navId: '',//导航标识
    videoList: [],//视频
    videoId: '',//视频id标识
    videoUpdateTime: [],//记录播放时长
    isTriggered: false,//表示下拉刷新
  },
  
   //获取导航数据
  async getVideoGroupListData(){
    let videoGroupListData = await request('/video/group/list');
    this.setData({
      videoGroupList: videoGroupListData.data.slice(0,20),
      navId: videoGroupListData.data[0].id
    })
     // 重新获取视频列表
    this.getVideoList(this.data.navId);
  },
      
   //点击切换导航的回调
//点击切换导航的回调
  changeNav(event){
    let navId = event.currentTarget.id;//通过id向event事件传参,传的数字会转为string
    this.setData({
      navId: navId>>>0,
      // 清空已有的视频列表
      videoList: []
    })

    //显示正在加载
    wx.showLoading({
      title: '正在加载',
    })
    //动态获取当前导航的动态数据
    this.getVideoList(this.data.navId);
  },
      
   
   //获取视频列表数据
  async getVideoList(navId){
      // 没有navId 直接结束返回
    if(!navId){
      return;
    }
    let VideoListData = await request('/video/group',{id: navId});
      // 如果是没有数据就会进行提示
    if(VideoListData.datas.length === 0){
      wx.showToast({
        title: '暂无推荐视频',
        icon: 'none'
      })
      return;
    }
    //关闭加载提示
    wx.hideLoading();
    // 给videoList 添加id
    let index = 0;
    let videoList = VideoListData.datas.map(item => {
      item.id = index++;
      return item;
    })
    this.setData({
      videoList: videoList,
      //关闭下拉刷新
      isTriggered: false
    })
  },
   // 播放事件回调
    handlePlay(event){
    //播放新视频之前找到上一个正在播放的视频并关闭
    let vid = event.currentTarget.id;
    //怎样关闭上一个视频?找到上一个视频的实例
    //如何确认点击播放的视频与正在播放的视频不是同一个(通过vid的对比)
    //this.vid !==vid && this.videoContext && this.videoContext.stop();
    
    //this.vid = vid;

    this.setData({
      videoId: vid
    })

    //创建控制video的实例对象
    this.videoContext = wx.createVideoContext(vid);
    //判断当前的视频是否播放过,有播放记录,有则跳转到之上次播放的位置
    let {videoUpdateTime} = this.data;
     // 找出与当前vid一致的
    let videoItem = videoUpdateTime.find(item => item.vid === vid);
    if(videoItem){
      this.videoContext.seek(videoItem.currentTime);
    }

    //this.videoContext.play();
  },
      
    //监听视频播放进度
  handleTimeUpdate(event){
    let videoTimeObj = {vid: event.currentTarget.id, currentTime: event.detail.currentTime};
    let {videoUpdateTime} = this.data;

    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){
    //移除播放时长数组中以结束的视频
    let {videoUpdateTime} = this.data;
    
    videoUpdateTime.splice(videoUpdateTime.findIndex(item => item.vid === event.currentTarget.id),1);
    this.setData({
      videoUpdateTime: videoUpdateTime
    })
  },
  //自定义下拉刷新
  handleRefresher(){
    this.getVideoList(this.data.navId);
  },  

视频推荐

创建一个 recommendsong page

Header 部分的构建

recommendsong.wxml

<view class="recommendContianer">
   <view class="header">
    <image src="/static/images/recommendSong/recommendSong.jpg" ></image>
    <view class="date">
        <text class="day">{{day}} / </text>
        <text class="month">{{month}}</text>
    </view>
    </view>
    <view class="ListContainer">
        <view class="ListHeader">
        <text>播放全部</text>
        <text class="changeMore">多选</text>
        </view>
   </view>

recommendsong.wxss

.recommendContianer .header{
    position: relative;
    width: 100%;
    height: 300rpx;
}

.recommendContianer .header image{
    width: 100%;
    height: 100%;
}
.recommendContianer .header .date{
   position:absolute;
   margin-left: -130rpx;
   margin-top: -200rpx;
   left: 50%;
   right: 50%;
   color:#fff;
    width: 300rpx;
    height: 100rpx;
    line-height: 100rpx;
    text-align: center;
}

recommendsong.js

更新日期

data: {
     day:'',
     month:'',
    recommendList:[]
  },
  
  onLoad(options) {
    this.setData({
      day: new Date().getDate(),
      month:new Date().getMonth()+1
    })
    this.getrecommendList();
  },

推荐列表

recommendsong.wxml

 <!-- 推荐列表 -->
        <scroll-view scroll-y class="listscroll">
            <view class="scrollItem" wx:for="{{recommendList}}" wx:key="id" bindtap="tosongDetail" data-song="{{item}}">
                <image src="{{item.album.picUrl}}" />
                <view class="musicInfo">
                <text class="musicName">{{item.name}}</text>
                <text class="author">{{item.artists[0].name}}</text>
                </view>
                <text class="iconfont icon-more"></text>
            </view>
        </scroll-view>

recommendsong.wxss

.ListContainer{
    position: relative;
    top:-20rpx;
    padding: 0 20rpx;
    border-radius: 30rpx;
    background: #fff;
}
.ListHeader{
    height: 80rpx;
    line-height: 80rpx;
}
.ListHeader .changeMore{
    float:right;
}
.scrollItem{
    display: flex;
    margin-bottom: 20rpx;
}
.scrollItem image{
    width: 80rpx;
    height: 80rpx;
    border-radius: 8rpx;
}
.musicInfo{
    display: flex;
    flex-direction: column;
    margin-left: 20rpx;
}

.musicInfo text{
    height: 40rpx;
    line-height: 40rpx;
    font-size: 25rpx;
    max-width: 400rpx;
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}
.scrollItem .iconfont{
    position: absolute;
    width: 80rpx;
    height: 80rpx;
    line-height: 80rpx;
    right: 0;
    text-align: right;
}

recommendsong.js

  onLoad(options) {
    this.setData({
      day: new Date().getDate(),
      month:new Date().getMonth()+1
    })
    this.getrecommendList();
  },
async getrecommendList(){
   let recommendListData= await request('/recommend/songs');
   this.setData({
    recommendList:recommendListData.data.recommend
   }
   )
  },
  tosongDetail(event){
    let song =event.currentTarget.dataset.song;
    wx.navigateTo({
      url: '/pages/songDetail/songDetail?musicId='+song.id,
    })
  },

getrecommendList() 获得视频列表数据

tosongDetail 路由跳转,并且传递song id 参数给子组件

播放详情页

重新创建一个songDetail 页

songDetail.wxml

<view class="songDetailContainer">
  <view class="auther">{{musicMsg.ar[0].name}}</view>
  <view class="circle"></view>
  <image class="needle {{isPlay?'needleDown':''}}" src="/static/images/song/needle.png"></image>
  <view class="discContainer {{isPlay && 'discAnimation'}}" >
    <image class="disc" src="/static/images/song/disc.png"></image>
    <image class="musicImg" src="{{musicMsg.al.picUrl}}"  />
  </view>
  <!-- 底部播放区域 -->
  <view class="musicControl">
    <text class="iconfont icon-suijibofang"></text>
    <text class="iconfont icon-shangyishoushangyige"></text>
    <text class="iconfont {{isPlay ? 'icon-zanting':'icon-bofang'}} big" bindtap="changePlay"></text>
    <text class="iconfont icon-xiayigexiayishou"></text>
    <text class="iconfont icon-biaodan"></text>
  </view>
</view>

songDetail.wxss

/* pages/songDetail/songDetail.wxss */
.songDetailContainer{
    height: 100%;
    background: rgba(0, 0, 0, 0.5);
    display: flex;
    flex-direction: column;
    align-items: center;
}
.circle{
    position: relative;
    z-index: 100;
    width: 60rpx;
    height: 60rpx;
    border-radius: 50%;
    background: #fff;
    margin: 10rpx 0;
}
.needle{
    position: relative;
    z-index: 99;
    top:-40rpx;
    left: 60rpx;
    width: 192rpx;
    height: 274rpx;
    transform-origin: 40rpx 0;
    transform: rotate(-20deg);
    transition: transform 1s;
}
.needleDown{
    transform: rotate(0deg);
}
.discContainer{
    position: relative;
    top:-170rpx;
    width: 598rpx;
    height: 598rpx;
}
.discAnimation{
    animation: disc 4s linear infinite;
    animation-delay: 1s;
}
@keyframes disc{
from{
    transform: rotate(0deg);
}
to{
    transform: rotate(360deg);
}
}
.disc{
    width: 598rpx;
    height: 598rpx;
}
.musicImg{
    position: absolute;
    top:0;
    right:0;
    left:0;
    bottom: 0;
    width: 370rpx;
    height: 370rpx;
    margin: auto ;
    border-radius: 50%;
}
.musicControl {
   position: absolute;
   bottom: 40rpx;
   left:0;
   border-top: 1rpx solid #fff;
   width: 100%;
   display: flex;
}
.musicControl text{
    width: 20%;
    height: 120rpx;
    line-height: 120rpx;
    text-align: center;
    color:#fff;
    font-size: 50rpx;
}
.musicControl .big{
    font-size: 80rpx;
}

详情页主要是实现播放逻辑和播放监听机制

播放动画的实现

顶针放下

通过 isPlay 来判断是否放下的类

.needle{
    position: relative;
    z-index: 99;
    top:-40rpx;
    left: 60rpx;
    width: 192rpx;
    height: 274rpx;
    transform-origin: 40rpx 0;
    transform: rotate(-20deg);
    transition: transform 1s;
}
.needleDown{
    transform: rotate(0deg);
}

通过是isPlay 否进行旋转 添加相应的类

.discContainer{
    position: relative;
    top:-170rpx;
    width: 598rpx;
    height: 598rpx;
}
.discAnimation{
    animation: disc 4s linear infinite;
    animation-delay: 1s;
}
@keyframes disc{
from{
    transform: rotate(0deg);
}
to{
    transform: rotate(360deg);
}
}
.disc{
    width: 598rpx;
    height: 598rpx;
}
.musicImg{
    position: absolute;
    top:0;
    right:0;
    left:0;
    bottom: 0;
    width: 370rpx;
    height: 370rpx;
    margin: auto ;
    border-radius: 50%;
}
<view class="auther">{{musicMsg.ar[0].name}}</view>
  <view class="circle"></view>
  <image class="needle {{isPlay?'needleDown':''}}" src="/static/images/song/needle.png"></image>
  <view class="discContainer {{isPlay && 'discAnimation'}}" >
    <image class="disc" src="/static/images/song/disc.png"></image>
    <image class="musicImg" src="{{musicMsg.al.picUrl}}"  />
.musicControl {
   position: absolute;
   bottom: 40rpx;
   left:0;
   border-top: 1rpx solid #fff;
   width: 100%;
   display: flex;
}
.musicControl text{
    width: 20%;
    height: 120rpx;
    line-height: 120rpx;
    text-align: center;
    color:#fff;
    font-size: 50rpx;
}
.musicControl .big{
    font-size: 80rpx;
}

底部按键用iconfont

app.js

全局变量

globalData:{
   isMusicPlay:false,
   musicId:''
  },

songDetail.js

import request from "../../utils/request"
const appInstance=getApp();   //全局变量 musicId 和 isPlay (退出songDetail依然记录播放状态)

data: {
      isPlay:false,
      musicMsg:{},
      musicId:''
  },
  
      
  // 播放与暂停切换
  changePlay(){
   this.setData({
    isPlay:!this.data.isPlay
   })
   this.musicControl(this.data.isPlay);
   
  },
  // 控制音乐的播放与暂停
 async musicControl(isPlay){
    if(isPlay){
     // 创建音乐实例
     let songData= await request('/song/url',{id:this.data.musicId});
   // 添加链接和名字就可以了
   this.backgroundAudioManager.src=songData.data.data[0].url;
   this.backgroundAudioManager.title=this.data.musicMsg.name; //后台管理播放的名字
  }else{
    //暂停
    this.backgroundAudioManager.pause();
    }
  },

  /**
   * 生命周期函数--监听页面加载 optons可以获取路由上的数据
   */
  onLoad(options) {
    // 获得musicId 和全局数据
   
   let musicId=options.musicId;
   this.setData({
    musicId
   })
   this.getDetailsong(musicId);
   //与全局变量对比
   if(appInstance.globalData.isMusicPlay && appInstance.globalData.musicId===this.data.musicId){
      this.setData({
        isPlay:true
      })
   }
   this.backgroundAudioManager =wx.getBackgroundAudioManager();
  //  监听播放和暂停 结束实现同步
   this.backgroundAudioManager.onPlay(()=>{
       this.setData({
        isPlay:true
       })
       //修改全局变量
       appInstance.globalData.isMusicPlay=true;
       appInstance.globalData.musicId=this.data.musicId;
   });
      //监听暂停
   this.backgroundAudioManager.onPause(()=>{
    this.setData({
      isPlay:false
    })
    appInstance.globalData.isMusicPlay=false;
   });
      //监听结束
   this.backgroundAudioManager.onEnded(()=>{
    this.setData({
      isPlay:false
    })
    appInstance.globalData.isMusicPlay=false;
});
  },
  // 获取歌曲信息
  async getDetailsong(musicId){
   let songData= await request("/song/detail",{ids:musicId})
   this.setData({
    musicMsg:songData.data.songs[0]
   })
   //动态修改标题栏的数据 改为歌曲名字
   wx.setNavigationBarTitle({
     title: this.data.musicMsg.name,
   })
  },

上下切换的逻辑

使用pubsub 插件进行组件之间的通讯

本地设置:点击 js 译为es5 的选项

在小程序里面根目录下

初始包

npm init -y

npm install pubsub-js

小程序 开发工具 -》工具 -》构建npm (有新的包就要更新)

在接收方和发布方导入

import PubSub from "pubsub-js";

绑定事件的一方:订阅方

PubSub.subscribe ( 消息处理,事件的回调)

传递实际参数的一方:发布方

PubSub.publish()

上下歌曲的切换就是通过推荐歌单传递 musicId 数据实现 和切换类型实现

songDetail.js 在详情页可以进行 type的传递

绑定 id

<text class="iconfont icon-shangyishoushangyige" id="pre" bindtap="handleSwitch"></text>
<text class="iconfont icon-xiayigexiayishou" id="next" bindtap="handleSwitch"></text>
data: {
      isPlay:false,
      musicMsg:{},
      musicId:'',
      musicLink:'',
      currentTime:'00:00',//播放时间
      durationTime:'00:00',//总时间
      currentWidth: 0
  },
 handleSwitch(event){
  // type 是 绑定id
 let type =event.currentTarget.id;
 //先停止这首歌
 this.backgroundAudioManager.stop();
  // 清空歌曲链接
 this.setData({
   musicLink:''
 })
 
 PubSub.publish('switchType',type);
  // 从recommendSong获取 musicid
 PubSub.subscribe('musicId', (msg,musicId)=>{
     this.setData({
      musicId
     })
     // 获得歌曲数据
    this.getDetailsong(musicId);
     //  播放歌曲
    this.musicControl(true,this.data.musicLink);
     // 取消订阅,防止数据反复输出
    PubSub.unsubscribe('musicId')
 })
  },

recommendSong.js

 data: {
     day:'',
     month:'',
     recommendList:[],
     index:0
  },

onLoad(options) {
    this.setData({
      day: new Date().getDate(),
      month:new Date().getMonth()+1
    })
    this.getrecommendList();
    // 订阅消息
    PubSub.subscribe('switchType',(msg,type)=>{
      let {recommendList,index}=this.data;
      if(type=='pre'){
        (index===0) && (index = recommendList.length)
        index-=1;
      }else{
        (index== recommendList.length-1)&&(index=-1)
       index+=1;
      }
      // 更新数据
      this.setData({
        index
      })
      let musicId=recommendList[index].id;
      // 发布数据musicId 给详情页
      PubSub.publish('musicId',musicId);

    })
  },
   切换路由的时候修改id
    tosongDetail(event){
    let {song,index} =event.currentTarget.dataset;
    this.setData({
      index
    })
    wx.navigateTo({
      url: '/pages/songDetail/songDetail?musicId='+song.id,
    })
  },

recommendSong.wxml

data-song="{{item}}" data-index="{{index}}"

进度条实现

songDetail.wxml

 <!-- 进度条显示 -->
  <view class="progressControl">
    <text>{{currentTime}}</text>
    <view class="barcontrol">
      <view class="audioBar" style="width:{{currentWidth+'rpx'}}">
        <!-- 小圆圈 -->
        <view class="barCircle" style="left:{{currentWidth+'rpx'}}">
        </view>
      </view>
    </view>
    <text>{{durationTime}}</text>
  </view>

songDetail.wxss

/* 进度条样式 */
.progressControl{
    position: absolute;
    bottom: 200rpx;
    width: 640rpx;
    height: 80rpx;
    line-height: 80rpx;
    display: flex;
}
.barcontrol{
    position: relative;
    width: 450rpx;
    height: 4rpx;
    background: rgba(0, 0, 0, 0.4);
    margin: auto;
}
.audioBar{
    position: absolute;
    top:0;
    left: 0;
    z-index: 1;
    height: 4rpx;
    background: #f40;
}
/* 进度条的圆球 */
.barCircle{
 width: 12rpx;
 height: 12rpx;
 position:absolute;
 border-radius: 50%;
 background: #fff;
 top:-4rpx;
}

使用内联样式实现进度条的变长,和小球的移动

 <view class="audioBar" style="width:{{currentWidth+'rpx'}}">
  <view class="barCircle" style="left:{{currentWidth+'rpx'}}">

使用 moment插件改变播放时间和进度条的长度

先下载相关依赖 npm install moment -》 构建npm

import moment from "moment";
onLoad(options) {
    // 获得musicId 和全局数据
   
   let musicId=options.musicId;
   this.setData({
    musicId
   })
   this.getDetailsong(musicId);
   //与全局变量对比
   if(appInstance.globalData.isMusicPlay && appInstance.globalData.musicId===this.data.musicId){
      this.setData({
        isPlay:true
      })
   }
   this.backgroundAudioManager =wx.getBackgroundAudioManager();
  //  监听播放和暂停 结束实现同步
   this.backgroundAudioManager.onPlay(()=>{
       this.setData({
        isPlay:true
       })
       //修改全局变量
       appInstance.globalData.isMusicPlay=true;
       appInstance.globalData.musicId=this.data.musicId;
   });
   this.backgroundAudioManager.onPause(()=>{
    this.setData({
      isPlay:false
    })
    appInstance.globalData.isMusicPlay=false;
   });
   this.backgroundAudioManager.onStop(()=>{
    this.setData({
      isPlay:false
    });
    appInstance.globalData.isMusicPlay=false;
  })
   this.backgroundAudioManager.onEnded(()=>{
    this.setData({
      isPlay:false,
      currentWidth:0,
      currentTime:'00:00'
    });
    
    appInstance.globalData.isMusicPlay=false;
  });
  // 监听播放进度时间
    this.backgroundAudioManager.onTimeUpdate(()=>{
      let currentTime=moment(this.backgroundAudioManager.currentTime *1000).format('mm:ss');
      let currentWidth=this.backgroundAudioManager.currentTime/this.backgroundAudioManager.duration*450;
      this.setData({
        currentTime,
        currentWidth
       })
    })
  },
      
 // 获取歌曲信息同时修改总时长
  async getDetailsong(musicId){
  
   let songData= await request("/song/detail",{ids:musicId})
  // 转化时间
   let  durationTime=moment(songData.data.songs[0].dt).format('mm:ss')
   this.setData({
    musicMsg:songData.data.songs[0],
    durationTime
   })
   //动态修改标题栏的数据 改为歌曲名字
   wx.setNavigationBarTitle({
     title: this.data.musicMsg.name,
   })
  },