一、从0构建项目
打开微信公众平台 -> 注册账号 -> 下载开发工具 -> 创建项目
1. 创建项目
2. 开通云开发
项目创建完成之后,在调试器里会报一个错误:云开发初始化错误
- 点击云开发按钮,不断下一步
- 输入环境信息,点击确定(当我们创建环境的时候,每个环境会对应一套云开发资源,每个环境是独立的)
- 等待 10 - 30min 之后,重启开发工具
3. 代码结构初始化
-
基础结构
-
app.js
文件配置云开发环境,填写环境 id
wx.cloud.init({
// env 参数说明:
// env 参数决定接下来小程序发起的云开发调用(wx.cloud.xxx)会默认请求到哪个云环境的资源
// 此处请填入环境 ID, 环境 ID 可打开云控制台查看
// 如不填则使用默认环境(第一个创建的环境)
env: 'dev-f2y9s',
// 是否在云开发控制台中记录访问过我们小程序的用户
traceUser: true,
})
- 配置页面 & 配置
tabBar
"pages": [
"pages/playlist/playlist",
"pages/blog/blog",
"pages/profile/profile"
],
"tabBar": {
"color": "#8a8a8a",
"selectedColor": "#d43c43",
"list": [
{
"pagePath": "pages/playlist/playlist",
"text": "音乐",
"iconPath": "./images/playlist.png",
"selectedIconPath": "./images/playlist_active.png"
},
{
"pagePath": "pages/blog/blog",
"text": "发现",
"iconPath": "./images/blog.png",
"selectedIconPath": "./images/blog_active.png"
},
{
"pagePath": "pages/profile/profile",
"text": "我的",
"iconPath": "./images/profile.png",
"selectedIconPath": "./images/profile_active.png"
}
]
},
4. 代码规范
借鉴的规范:https://github.com/airbnb/javascript
5. 调用云函数
- 首先看一下当前拥有的云函数
- 由于当前云函数还未上传,直接使用的话会报如下错误:
- 上传云函数
右键云函数 -> 创建并部署:云端安装依赖(不上传 node_modules)
- 使用云函数
onLoad: function (options) {
wx.cloud.callFunction({
name: 'login'
}).then((res) => {
console.log(res)
})
},
二、 播放列表功能实现
1. 轮播图组件 Swiper
资源地址:
https://github.com/xiecheng328/miniprogram/tree/master/assets
1)数据
资源地址:https://github.com/xiecheng328/miniprogram/tree/master/assets
data: {
swiperImages: [{
url: 'http://p1.music.126.net/oeH9rlBAj3UNkhOmfog8Hw==/109951164169407335.jpg',
},
{
url: 'http://p1.music.126.net/xhWAaHI-SIYP8ZMzL9NOqg==/109951164167032995.jpg',
},
{
url: 'http://p1.music.126.net/Yo-FjrJTQ9clkDkuUCTtUg==/109951164169441928.jpg',
}
]
},
2)结构
在这里,循环写到了
<block>
标签内;在DOM中<block>
不会渲染出来
<!-- 轮播图 -->
<swiper class="swiper" indicator-dots="{{ true }}" autoplay="{{ true }}" interval="{{ 2000 }}" circular="{{ true }}">
<block wx:for="{{swiperImages}}" wx:key="{{ index }}">
<swiper-item>
<image class="bannerImg" src="{{item.url}}" mode="widthFix"></image>
</swiper-item>
</block>
</swiper>
2. 组件化开发
新建组件
组件位置:miniprogram/components
新建组件:新建目录 -> 新建component
使用组件
注册组件
<!--
"usingComponents": {
"组件名": "组件路径"
}
-->
{
"usingComponents": {
"gx-playlist": "/components/playlist/playlist"
}
}
使用组件
<gx-playlist></gx-playlist>
3. 自定义歌单组件 playlist
1)使用组件的流程
初始化组件
新建组件 - 注册组件 - 初始化数据
给组件传值
<gx-playlist playItem="{{ item }}"></gx-playlist>
组件中 - 接收父级传入的数据
/**
* 组件的属性列表
*/
properties: {
playItem: {
type: Object
}
},
使用父级传入的数据
<view class="playlist-container">
<image class="playlist-image" src="{{ playItem.picUrl }}"></image>
<text class="playlist-count">{{ playItem.playCount }}</text>
<view class="playlist-name">{{ playItem.name }}</view>
</view>
2)组件化开发中的数据监听器
组件化开发中的数据监听器(格式化播放数量)
observers
- 数据监听器支持监听属性或内部数据的变化,可以同时监听多个。一次 setData 最多触发每个监听器一次。
同时,监听器可以监听子数据字段
Component({
observers: {
'some.subfield': function(subfield) {
// 使用 setData 设置 this.data.some.subfield 时触发
// (除此以外,使用 setData 设置 this.data.some 也会触发)
subfield === this.data.some.subfield
},
'arr[12]': function(arr12) {
// 使用 setData 设置 this.data.arr[12] 时触发
// (除此以外,使用 setData 设置 this.data.arr 也会触发)
arr12 === this.data.arr[12]
},
}
})
- 如果需要监听所有子数据字段的变化,可以使用通配符 ** 。
Component({
observers: {
'some.field.**': function(field) {
// 使用 setData 设置 this.data.some.field 本身或其下任何子数据字段时触发
// (除此以外,使用 setData 设置 this.data.some 也会触发)
field === this.data.some.field
},
},
})
- 特别地,仅使用通配符 ** 可以监听全部 setData 。
Component({
observers: {
'**': function() {
// 每次 setData 都触发
},
},
})
- 好了,基础说完了,接下来看一下怎样格式化播放数量
// 数据监听器
observers: {
['playItem.playCount'](count) {
this.setData({
_count: this._filterCount(count, 2)
})
}
},
// 组件的初始数据
data: {
_count: ''
},
methods: {
/**
* 格式化逻辑部分
* num: 待转化的数字
* point:保留几位小数
*/
_filterCount(num, point) {
let numStr = num.toString().split('.')[0] // 舍弃小数点,并转成String
if (numStr.length < 6) {
return numStr
} else if (numStr.length >= 6 && numStr.length <= 8) {
return (num/10000).toFixed(point) + '万'
} else if(numStr.length > 8) {
return (num/100000000).toFixed(point) + '亿'
}
},
},
3)小程序的列表渲染
在组件上使用 wx:for 控制属性绑定一个数组,即可使用数组中各项的数据重复渲染该组件。
默认数组的当前项的下标变量名默认为 index,数组当前项的变量名默认为 item
<view wx:for="{{array}}">
{{index}}: {{item.message}}
</view>
Page({
data: {
array: [{
message: 'foo',
}, {
message: 'bar'
}]
}
})
使用 wx:for-item 可以指定数组当前元素的变量名,
使用 wx:for-index 可以指定数组当前下标的变量名:
<view wx:for="{{array}}" wx:for-index="idx" wx:for-item="itemName">
{{idx}}: {{itemName.message}}
</view>
注意
- 在小程序中的背景图片是不能使用网络图片的,可以使用本地图片或者是 base64格式
- 如果当前文字是白色,背景颜色不确定,可以添加上适当的阴影,来保证不管背景颜色是淡是深,都尽可能的有一个比较好的展示效果
- 传入组件的值,在组件内不能改变,同vue;
4. 通过 base64 的方式,使用 svg
图片
-
搜索:
在线制作base64
-
随便找一个,打开,上传
svg
图片,生成 base64 字符串 -
使用 将生成的字符串放到
background: url()
里
background-image: url(data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAyMiAyMCI+PGcgb3BhY2l0eT0iLjE1Ij48cGF0aCBmaWxsLXJ1bGU9ImV2ZW5vZGQiIGZpbGw9IiMwNDAwMDAiIGQ9Im0yMiAxNi43NzdjMCAxLjIzMy0xLjEyMSAyLjIzMy0yLjUwNiAyLjIzMy0xLjM4NCAwLTIuNTA2LTEtMi41MDYtMi4yMzN2LTIuNTUzYzAtMS4yMzQgMS4xMjItMi4yMzMgMi41MDYtMi4yMzMuMTc0IDAgLjM0My4wMTcuNTA2LjA0NnYtMS4zN2gtLjAzM2MuMDE3LS4yMi4wMzMtLjQ0MS4wMzMtLjY2NiAwLTQuNDE4LTMuNTgyLTgtOC04LTQuNDE4IDAtOCAzLjU4Mi04IDggMCAuMjI1LjAxNi40NDYuMDM0LjY2NmgtLjAzNHYxLjM3Yy4xNjMtLjAyOS4zMzMtLjA0Ni41MDUtLjA0NiAxLjM4NCAwIDIuNTA2Ljk5OSAyLjUwNiAyLjIzM3YyLjU1M2MwIDEuMjMzLTEuMTIyIDIuMjMzLTIuNTA2IDIuMjMzcy0yLjUwNS0uOTk5LTIuNTA1LTIuMjMzdi0yLjU1M2MwLS4yNTguMDU5LS41MDEuMTQ4LS43My0uMDg1LS4xNDgtLjE0OC0uMzEtLjE0OC0uNDkzdi0yLjY2N2MwLS4wMjMuMDEyLS4wNDMuMDEzLS4wNjctLjAwNC0uMDg4LS4wMTMtLjE3Ni0uMDEzLS4yNjYgMC01LjUyMyA0LjQ3Ny0xMCAxMC0xMCA1LjUyMyAwIDEwIDQuNDc3IDEwIDEwIDAgLjA5LS4wMDkuMTc4LS4wMTQuMjY2LjAwMi4wMjQuMDE0LjA0NC4wMTQuMDY3djJjMCAuMzA2LS4xNDUuNTY5LS4zNi43NTMuMjI0LjMzNC4zNi43Mi4zNiAxLjEzOHYyLjU1MiIvPjwvZz48cGF0aCBmaWxsLXJ1bGU9ImV2ZW5vZGQiIGZpbGw9IiNmZmYiIGQ9Im0yMCAxNi43NzdjMCAxLjIzMy0xLjEyMSAyLjIzMy0yLjUwNiAyLjIzMy0xLjM4NCAwLTIuNTA2LTEtMi41MDYtMi4yMzN2LTIuNTUzYzAtMS4yMzQgMS4xMjItMi4yMzMgMi41MDYtMi4yMzMuMTc0IDAgLjM0My4wMTcuNTA2LjA0NnYtMS4zN2gtLjAzM2MuMDE3LS4yMi4wMzMtLjQ0MS4wMzMtLjY2NiAwLTQuNDE4LTMuNTgyLTgtOC04LTQuNDE4IDAtOCAzLjU4Mi04IDggMCAuMjI1LjAxNi40NDYuMDM0LjY2NmgtLjAzNHYxLjM3Yy4xNjMtLjAyOS4zMzMtLjA0Ni41MDUtLjA0NiAxLjM4NCAwIDIuNTA2Ljk5OSAyLjUwNiAyLjIzM3YyLjU1M2MwIDEuMjMzLTEuMTIyIDIuMjMzLTIuNTA2IDIuMjMzcy0yLjUwNS0uOTk5LTIuNTA1LTIuMjMzdi0yLjU1M2MwLS4yNTguMDU5LS41MDEuMTQ4LS43My0uMDg1LS4xNDgtLjE0OC0uMzEtLjE0OC0uNDkzdi0yLjY2N2MwLS4wMjMuMDEyLS4wNDMuMDEzLS4wNjctLjAwNC0uMDg4LS4wMTMtLjE3Ni0uMDEzLS4yNjYgMC01LjUyMyA0LjQ3Ny0xMCAxMC0xMCA1LjUyMyAwIDEwIDQuNDc3IDEwIDEwIDAgLjA5LS4wMDkuMTc4LS4wMTQuMjY2LjAwMi4wMjQuMDE0LjA0NC4wMTQuMDY3djJjMCAuMzA2LS4xNDUuNTY5LS4zNi43NTMuMjI0LjMzNC4zNi43Mi4zNiAxLjEzOHYyLjU1MiIvPjwvc3ZnPg==);
5. wx:key
- 如果数组是单纯的字符串数组,如:
arr: ['wxml', 'js', 'wxss', 'json']
; 则wx:key
建议使用wx:key="*this"
- 如果是数组对象,直接使用对象每一项的
id
就好;使用每一项的id的时候,如果字段也是id
,不用写成wx:key="item.id"
,直接写成wx:key="id"
就好; - 注意:不要用
index
6. async / await
说明:
async / await
是 Es7 的语法,如果在云函数中使用,云函数默认是支持的,因为云函数要求,node版本最低是 8.9,所以在云函数端async / await
是默认支持的;但是在小程序端,默认是不支持async / await
的,所以需要引入额外的文件,才能使小程序端支持;
说明:当我写这篇博客的时候,小程序已经支持
async / await
语法了
下面还是记录一下,如果不支持,我们该怎么处理;
1. 下载文件
地址:https://github.com/xiecheng328/miniprogram/tree/master/regenerator
将tuntime.js
文件,放到/miniprogram/utils
中
2. 引入文件
// 注意 regeneratorRuntime 不要变
import regeneratorRuntime from '../../utils/runtime.js'
然后就可以使用 async / await
语法了;
7. 读取歌单数据并插入云数据库
音乐数据是从第三方获取到的,每天都会有新的推荐的歌单,如果我只是一次获取的话,那我的歌单就只是那些数据,所以说,我们要给函数设置一个定时触发器,每天会有固定的时间去服务器上取数据,这样就能保证我每天取到的都是最新的数据;
取数据的时候,要和旧数据做一下比较,把数据库中没有的数据才插入到数据库中;
1. 创建云函数
右键 cloudfunctions
->
新建 Node.js 云函数
->
getPlayList
2. 发送网络请求
工具
借助第三方工具,使用的是axios
yarn add axios
地址
http://musicapi.leanapp.cn/
推荐歌单 (get)
说明 : 调用此接口 , 可获取推荐歌单
可选参数 : limit: 取出数量 , 默认为 30 (不支持 offset)
接口地址 : /personalized
调用例子 : /personalized?limit=1
请求到数据
// 云函数入口文件
const cloud = require('wx-server-sdk')
// axios
const axios = require('axios')
const url = 'http://musicapi.leanapp.cn/personalized?limit=20'
cloud.init()
// 云函数入口函数
exports.main = async (event, context) => {
const res = await axios.get(url)
console.log(res.data.result)
}
3. 将请求到的数据插入数据库
1)创建数据库集合
2)插入数据
https://developers.weixin.qq.com/miniprogram/dev/wxcloud/guide/database/init.html
云数据库插入的时候只能单条插入,所以要循环遍历当前数组
// 云函数入口文件
const cloud = require('wx-server-sdk')
// axios
const axios = require('axios')
const url = 'http://musicapi.leanapp.cn/personalized?limit=20'
cloud.init()
// 数据库 - 要在初始化后面,因为有cloud,所以就不用wx.cloud了
const db = cloud.database()
// 云函数入口函数
exports.main = async (event, context) => {
const res = await axios.get(url)
if (res.data.result.length > 0) {
for (let i = 0; i < res.data.result.length; i++) {
await db.collection('playlist').add({
data: {
...res.data.result[i],
createTime: db.serverDate()
}
}).then((res) => {
console.log('Success', res)
}).catch((err) => {
console.log('Error', err);
})
}
}
}
注意:
- 获取数据库的引用,要在
cloud.init()
之后,因为能直接拿到cloud
,所以就不用wx.cloud.database()
引用了; const db = cloud.database()
–db.serverDate()
能获取到当前服务器的时间,在写入数据库数据的时候,添加上时间,方便做按时间排序操作- 因为把数据插入数据库也是异步操作,需要插入一条后再插入下一条数据,所以要加上
await
8. 歌单数据去重
当我再次调用 请求歌单数据并插入数据库 这个函数的时候,假设我读取到的还是这些数据,同样插入到数据库中,这样我对应的歌单信息就会有重复的数据,所以需要去重;
// 云函数入口函数
exports.main = async (event, context) => {
// 读取数据库中的歌单数据
const list = await db.collection('playlist').get();
// 读取服务器的歌单数据
const res = await axios.get(url)
const playList = res.data.result;
// 数组去重
const newData = []
for (let i = 0; i < playList.length; i++) {
let flag = true;
for (let j = 0; j < list.data.length; j++) {
if (playList[i].id === list.data[j].id) {
flag = false
break
}
}
if (flag) {
newData.push(playList[i])
}
}
// 循环数据并插入数据库
if (newData.length > 0) {
for (let i = 0; i < newData.length; i++) {
await db.collection('playlist').add({
data: {
...newData[i],
createTime: db.serverDate()
}
}).then((res) => {
console.log('Success', res)
}).catch((err) => {
console.log('Error', err);
})
}
}
}
9. 突破获取条数的限制
当我们去获取云数据库中某一个集合的所有数据的时候,其实是有对应的条数限制的,如果我们在云函数中获取,那么每次最多获取100条,如果是在小程序端获取,每次最多获取20条数据;
随着时间推移,歌单的数量会不断增多,所以100条数据很明显不会满足需求;这里就说一下如何突破100条的条数限制;
需要优化的,就是这一行代码
// 读取数据库中的歌单数据
const list = await db.collection('playlist').get();
思路:假设当前我的数据库中有210条数据,如果每次只能取100条的话,我取三次就能把这210条数据取完,然后把这三次的数据拼装成一个数组,这个数组的数据就是全部的数据;
// 每次能获取的最大数量
const MAX_COUNT = 10;
// 获取歌单条数
const countResult = await db.collection('playlist').count();
const total = countResult.total
// 要获取的次数(向上取整)
const batchTimes = Math.ceil(total / MAX_COUNT)
// 所有的歌单数据数组
const list = []
for (let i = 0; i < batchTimes; i++) {
const arr = await db.collection('playlist').skip(i * MAX_COUNT).limit(MAX_COUNT).get()
list.push(...arr.data);
}