简单的仿网易云音乐小程序(一)
前言
今天我们开始学习简单的仿网易云音乐小程序,该小程序由Github提供,并具有网络请求和音乐播放等经典基础功能。
思维图

在上述的思维图中,每当点击歌曲列表中的歌曲时,均会跳转到音乐播放界面。
注意事项
由于该小程序使用到了网络请求功能,故需要在开发者工具中进行设置。如果不设置,会出现以下情况。

虚拟机调试
如果仅仅只是想在虚拟机上进行网络请求功能的调试,要解决上述问题很简单。
- 打开开发者工具中右上角的详情页面。

- 将本地设置中的红箭头指视处打勾

真机调试
当你想使用真机进行调试时,上述办法就行不通了,需要到微信小程序的官网上进行设置。
- 登陆小程序官网,进入到管理页面,并点击开发选项卡。

- 在开发页面中点开开发设置,找到服务器域名模块,添加上网络请求的地址即可。

主页面
搜索框
搜索框位于页面的顶部,当用户点击时,自动跳转到等待搜索页面。故很容易想到这是一个导航跳转功能。
<navigator class='topsearchLeft' url='../search/search'>
<view class='iconfont icon-sousuo sousuo'></view>
<text>搜索</text>
</navigator>
上述代码中,设置了一个导航组件,当点击该组件时,跳转到url对应的页面中。
歌单列表
歌单列表用到了我们之前尚未学习过的新只是,即导入wxml的模版文件。使用模版文件也很好理解,因为歌单列表会重复的使用,当将该页面独立为一个模版组件后,调用起来将会非常的方便,也省略了很多冗余的代码。
<!-- 从文件中导入模版wxml -->
<import src="./template/boutiqueItem" />
<view class='BoutiqueList'>
<!-- block 并不是一个组件,它仅仅是一个包装元素,不会在页面中做任何渲染,只接受控制属性。 -->
<!-- for循环遍历音乐数据 -->
<block wx:for="{{musicList}}" wx:key="index">
<!-- 将每一项数据输送给模版进行页面的渲染 -->
<!-- 使用名字为ListItem的模版 -->
<template is="ListItem" data="{{item}}" />
</block>
</view>
这个模版在后续代码中均有使用,故我们在这进行学习
歌单模版
本小程序使用的歌单模版为一个单个的wxml文件,其他的wxml可以使用相对路径的方法对其进行调用。

模版文件存放在template文件夹下,WXML提供模板template,可以在模板中定义代码片段,然后在不同的地方调用。该模版内容没有特别新奇的地方,故没有特别添加上注释
<wxs src="../../../utils/filter.wxs" module="filter" />
<template name="ListItem">
<!-- 这里的item是调用该template输出的data值 -->
<view class='musicItem' bindtap='getdetail' data-id='{{item.id}}'>
<view class='musicItem_left'>
<image class='musicItemImg' src='{{item.coverImgUrl}}' alt='' lazy-load="true" mode="scaleToFill"></image>
<view class='playCount_box'>
<view class='iconfont icon-ttpod playCount_icon'></view>
<!-- 使用wxs中的内容 -->
<text class='playCount'>{{filter.over10000(item.playCount)}}</text>
</view>
</view>
<view class='musicItem_right'>
<text class='musicItem_name'>{{item.name}}</text>
<text class='musicItem_nickname'>by {{item.creator.nickname}}</text>
<view class='musicItem_tag'>
<text class='musicItem_tag_b'>{{item.tags[0]}}</text>
<text class='musicItem_copywriter'>{{item.copywriter}}</text>
</view>
</view>
</view>
</template>
唯一值得留意的是上述代码的第一行。WXS(WeiXin Script)是小程序的一套脚本语言,结合 WXML,可以构建出页面的结构。其中的module为该标签的模块名。此外,getdetail为点击每个item的响应函数
// 跳转到musiclist页面
// 在musiclist页面中的load函数,可以获取到该跳转传入的id
getdetail: function(options) {
wx.navigateTo({
url: '../../../../musicList/musicList?musicListID=' + options.currentTarget.dataset.id
})
}
wxs filter
接下来我们看看这个wxs这个被命名为filter的组件在做些什么(直观上来看,它是作为一个数据的过滤器)
// 超出1万和1亿的过滤器
var over10000 = function (num) {
if (num < 10000) {
return num;
} else if (num >= 100000000) {
return parseInt(num / 100000000) + "亿";
} else {
return parseInt(num / 10000) + "万";
}
}
module.exports = {
over10000: over10000,
}
我们贴出了上述代码中仅使用到的内容,即通过num来返回不通格式的数据,以免数据太多,显示太难看。
页面逻辑
最后我们来看看主页面的页面逻辑。
- 导入网络请求的api
- 初始化网络请求的函数
- 当页面加载时调用网络请求的函数
- 当滑动到页面底部时,再次发动请求
// 1
import api from '../../utils/api.js';
// 2
getBoutiqueList() {
// 调用api中的函数
api.getBoutiqueList({
// 配置每次请求数据的条数
data: {
limit: this.data.limit
},
// 请求成功,返回音乐列表
success: resp => {
this.setData({
musicList: resp.data.playlists
})
}
})
},
// 3
// 生命周期函数--监听页面加载
onLoad(e) {
this.getBoutiqueList()
},
// 4
// 页面上拉触底事件的处理函数
onReachBottom: function() {
this.setData({
limit: this.data.limit + 10
})
this.getBoutiqueList()
},
等待搜索页面
搜索框
页面顶部的搜索框的页面代码如下
<view class='topSearch'>
<!-- 构建一个表单组件,reset绑定的响应函数为clearValue -->
<form bindreset="clearValue" class='inputform'>
<!-- 构建输入控件
placeholder-class: 指定 placeholder 的样式类
confirm-type: 设置键盘右下角按钮的文字,仅在type='text'时生效
bindconfirm: 点击按钮后的响应函数
bindinput: 输入时触发 -->
<input class='inputB' placeholder-class="holderInput" placeholder="输入您想要搜索的内容" auto-focus confirm-type="search" bindconfirm="startSearch" bindinput="inputC" value="{{keywords}}"></input>
<view class='iconfont icon-sousuo sousuo'></view>
<!-- 小叉叉按钮,用来清楚输入框的值 -->
<!-- 有值就显示,无值就不显示 -->
<button class='iconfont icon-shanchu clearBtn' formType="reset" wx:if="{{haveValue}}"></button>
</form>
<!-- 取消按钮,响应函数为back -->
<view class='cansBtn' bindtap='back'>取消</view>
</view>
其实搜索框就是一个添加了多个样式和功能的输入框。接下来,我们依次看看响应函数。
clearValue
// 清除vlaue
clearValue() {
this.setData({
haveValue: false,
showHotsearch: true
})
},
该响应函数很简单,就是将haveValue标志为设置为false,表示输入框没有值;将showHotsearch设置为true,表示不显示搜索内容,只显示热门搜索。
startSearch
首先给出响应函数
// 搜索框输入后按搜索按钮时的搜索,参数都应是默认值
startSearch(opt) {
let keywords = '';
// 如果输入框没有值
if (!opt.detail.value){
keywords = 'hello word'//opt.currentTarget.dataset.value
}else{
keywords = opt.detail.value
}
// 搜索前先清空每个数组
this.setData({
haveValue: true,
keywords: keywords,
songArr: [], //歌曲
musicListArr: [], //歌单
singerArr: [], //歌手
videoArr: [], //视频
})
// 执行搜索函数
this.search();
},
我们看到,这个响应函数首先是对数据做了一个清洗和出事话,然后在执行搜索函数
// 实际进行搜索的函数
search() {
// 调用api中对函数
api.searchStart({
// 需要对参数为,
// 关键字,返回内容对条数,搜索的类型
data: {
keywords: this.data.keywords,
limit: this.data.limit,
type: this.data.searchType,
},
// 搜索到结果后的回调
success: resp => {
// 使用搜索的类型来判断内容
switch (this.data.searchType) {
// 歌曲列表
case 1:
this.setData({
songArr: resp.data.result.songs,
showHotsearch: false
})
break;
// 歌单列表
case 1000:
this.setData({
musicListArr: resp.data.result.playlists,
showHotsearch: false
})
break;
// 歌手列表
case 100:
this.setData({
singerArr: resp.data.result.artists,
showHotsearch: false
})
break;
// 视频列表
case 1004:
this.setData({
videoArr: resp.data.result.mvs,
showHotsearch: false
})
break;
}
}
})
},
基本没什么好说的,就是调用网络请求,然后通过类型类判断更新的数据。当数据得到更新后,页面自然也就发生了变化
inputC
就是一个在输入过程的监控
// 输入中
inputC(opt) {
// 判断输入的数据长度是否大于0
// 并用此来设置标志位
if (opt.detail.value.length) {
this.setData({
haveValue: true
})
} else {
this.setData({
haveValue: false,
showHotsearch: true
})
}
},
back
这个就更简单了,返回上一级
// 返回上一页
back() {
wx.navigateBack()
},
热门搜索标签
当未发生搜索行为,即showHotsearch为true时,页面显示的是热门搜索标签
<!-- 用showHotsearch来判断当前该展示搜索结构还是展示热门搜素标签 -->
<view class='searchBottom' wx:if="{{showHotsearch}}">
<view class='hotsearchtitle'>热门搜索</view>
<!-- 遍历热门搜索数据,并渲染到页面 -->
<block wx:for="{{hotsearch}}" wx:key="index">
<text class='hotsearchitem' bindtap='startSearch' data-value='{{item.name}}'>{{item.name}}</text>
</block>
</view>
当搜索行为完成后,将会显示单曲,歌单,歌手或视频的页面,这四个界面可通过点击来进行切换。
定义切换组件
<view class='searchNav'>
<!-- 页面内容上端,搜索框下端的内容切换组件 -->
<!-- 响应函数均为changeNav -->
<text bindtap='changeNav' data-type='1'>单曲</text>
<text bindtap='changeNav' data-type='1000'>歌单</text>
<text bindtap='changeNav' data-type='100'>歌手</text>
<text bindtap='changeNav' data-type='1004'>视频</text>
<view class='navLine' style='transform:translateX({{navLinePosition}})'></view>
</view>
切换组件的响应函数,因为大都一样,故只给出一小段
// nav点击
changeNav(opt) {
// 根据点击到的组件的类型进行数据的更新
switch (opt.currentTarget.dataset.type) {
case '1':
// 主要就是更新下划线
// 搜索类型
// 下划线的位置
this.setData({
navLinePosition: '53rpx',
searchType: 1,
positionLeft: 0
})
// 重新执行搜索
if (!this.data.songArr.length) {
this.search();
}
break;
使用数据渲染不同页面
这里就是简单的遍历不同的数据,然后对页面进行渲染。
<view class='songBox centeritem' style='height:{{innerHeight}}rpx'>
<block wx:for="{{songArr}}" wx:key="index">
<template is="songList" data="{{item}}" />
</block>
</view>
可以看到,这里也适用了模版。具体来说,单曲,歌手,歌曲列表,视频列表都用了不同的模版,但是都大同小异。值得一看的是模版中的点击响应函数,在此用点击播放歌曲为例
//跳转到播放页
playSong(opt) {
// 获取歌曲细节信息
api.getSongDetai({
data: {
ids: opt.currentTarget.dataset.itemid
},
success: resp => {
app.globalData.musicListArr = resp.data.songs;
// 跳转到歌曲播放界面
wx.navigateTo({
url: '../audioPage/audioPage?musicindex=0&duration=' + opt.currentTarget.dataset.duration,
})
}
})
},
小结
这篇博文中,主要对主页面和搜索界面进行了学习。其中最值得注意的就是网络请求的使用和模版的使用。接下来,我们将学习其他页面,并学习这篇文章中跳过的api的源码部分。
7036



被折叠的 条评论
为什么被折叠?



