小程序入门
初始化操作
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,
})
},