# 小程序笔记
# 基础课程部分
# 微信公众平台
https://mp.weixin.qq.com/ (opens new window)
注册时可选择类型:订阅号、服务号、小程序、企业微信
每个邮箱仅能注册一个小程序。
个人类型小程序:无法使用微信支付、无法使用卡包功能
# 小程序文档API
# 微信开放社区
# 目录说明
默认目录
pages-----------------------页面相关
index ----------------- 首页文件夹
index.js ------------首页js
index.json---------首页配置
index.wxml-------首页html
index.wxss--------首页css
utils------------------------工具相关
app.js ----------------------项目总js
app.json-------------------全局配置( 页面路由以及头部、底部导航的配置等)
app.wxss -----------------项目总样式css
project.config.json ----项目配置
代码构成
.json :配置文件,以json格式存储配置
项目中有三种配置:项目配置(project.config.json)、全局配置(app.json)、页面配置(index.json)
.wxml: 相当于html文件
.wxss: 相当于css
.js : 就是js
# 文件说明
project.config.json项目配置 部分代码说明
setting:{
urlCheck //是否检测安全的域名
es6 //是否把es6转es5
postcss //是否把css样式自动补全
minified //是否压缩
}
2
3
4
5
6
7
8
9
app.json 全局配置
wxml 相关介绍
<view>{{motto}}</view>
循环渲染
<view wx:for="{{list}}" wx:key="{{index}}">
{{index}} {{item}}
</view>
改变for循环item和index的名称
<block wx:for="{{list}}" wx:for-item="data" wx:for-index="inx">
{{inx}} {{data}}
</block>
条件渲染 (类似vue的v-if、v-else)
<view wx:if="{{isLogin}}">已登录</view>
<view wx:else>请登录</view>
条件显示(类似vue的v-show)
<view hidden="{{isLogin}}">显示内容</view>
绑定点击事件
<button bindtap=“tapName”>按钮</button>
Page({
tapName: function(event) {
console.log(event)
}
})
...
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
wxss 相关介绍
尺寸单位:rpx,根据屏幕宽度自适应。
引入外部 wxss:@import ’...‘
js相关介绍
WXS(WeiXin Script)是小程序的一套脚本语言
绑定点击事件
<button bindtap=“onTapHandler”>点我+1</button>
<view>{{count}}</view>
2
Page({
data: {
count: 0
},
onTapHandler: function() {
this.setData({
count: this.data.count++
})
}
})
2
3
4
5
6
7
8
9
10
# 阻止事件冒泡
把绑定方式 bindtap 换成 catchtap 即可。
# 第三方库
WeUI
weUI是一套同微信原生视觉体验一致的基础样式库
iView Weapp
一套高质量的微信小程序UI组件库
Vant Weapp
轻量、可靠的小程序UI组件库
# 云开发
小程序传统开发模式
客户端 -----> 服务端(后端代码、数据库)------> 运维(DB维护、文件存储、内容加速、网络防护、容器服务、负载均衡、安全加固等...)
小程序云开发模式
客户端 -----> 云开发(云函数、云数据库、云存储)
传统开发 VS 云开发
开发效率低 Serverless(无服务)
运维成本高 开发者更关注业务逻辑
无服务(Serverless)开发是未来的发展趋势
# 云开发三大基础能力
# 云函数
(相当于传统开发中的后台接口)
获取appid、获取openid、生成分享图、调用腾讯云SDK ...
# 云数据库
数据的增、删、改、查 ...
# 云存储
管理文件、上传文件、下载文件、分享文件 ...
每个小程序账号可免费创建两个环境,建议:开发环境、生成环境
# 云数据库能力
云开发提供了一个json数据库,提供2GB免费存储空间。
# 数据类型
String 字符串
Number 数字
Object 对象
Array 数组
Boolean 布尔值
GeoPoint 地理位置点
Date 时间 (精确到毫秒ms,客户端时间)
Null 空
# 操作云数据库
小程序控制(读写数据库受权限限制)
云函数控制(拥有所有读写数据库的权限)
控制台控制(拥有所有读写数据库的权限)
# 云数据库权限管理
仅创建者可写,所有人可读 (适合于文章)
仅创建者可读写 (适用于私密内容)
仅管理端可写,所有人可读(适用于商品信息)
仅管理端可读写(适用于后台敏感数据)
# 操作云数据库
//初始化数据库
const db = wx.cloud.database() // 小程序端初始化数据库,如果在云函数端不需要加wx
//切换环境(开发环境/生产环境)
const testDB = wx.cloud.database({
env: 'test'
})
2
3
4
5
6
7
# 实战课程部分
# serverless(无服务)
概念:函数即服务,当需要后端服务的时候,不需要关心后端的IP地址、域名,只需要像调用普通函数一样既可以实现调用。
# 云开发优势
快速上线、专注核心业务、独立开发一个完整的微信小程序、不需要学习新的语言,只需要会javascript、无需运维, 节约成本、数据安全、
# 云开发提供能力
云函数:在云端运行的代码,微信私有协议天然鉴权 (理解:相当于后端部分)
云数据库:一个既可以在小程序端操作又可以在云函数中操作的JSON数据库
云存储:在云端存储文件,可以在云端控制台可视化管理
云调用:基于云函数免鉴权使用小程序开放接口的能力(比如说给用户推送消息等)
HTTP API:使用HTTP API开发者可在已有服务器上访问云资源,实现与云开发的互通(作用:对原有传统模式下开发的小程序,可以与云开发进行互通)
# appID
每个小程序唯一的id
# 云开发项目默认目录结构
cloudfunctions ----------------------------云函数
callback ---------------------------------- 回调函数
config.json ---------------------------
index.js --------------------------------
package.json ------------------------
echo ----------------------------------------
login ----------------------------------------
openapi -----------------------------------
miniprogram ------------------------------- 小程序
images ------------------------------------- 图片
pages --------------------------------------- 页面
style ----------------------------------------- 样式
app.js --------------------------------------- 项目js
app.json ----------------------------------- 全局配置
app.wxss ---------------------------------- 项目样式
sitemap.json ----------------------------- (小程序SEO相关)
project.config.json ----------------------- 项目配置
# 云开发环境
云开发可创建两个环境,建议一个为开发环境,一个为生产环境
# 开发前的准备
开发工具 > 右上角详情 > 本地设置 > 调试基础库 设置为最新版本
app.js > wx.cloud.init > env 设置环境ID
# project.config.json 文件说明
miniprogramRoot 小程序前端代码目录
cloudfunctionRoot 云函数代码目录
# app.json
pages 设置页面 ,设置后会自动在pages目录下生成相应的目录和文件
设置底部导航按钮:
"tabBar": {
"color": "#474747", // 文字颜色
"selectedColor": "#d43c43", // 文字选中颜色
"list": [{ // 按钮列表,2-5项
"pagePath": "pages/playlist/playlist", // 按钮对应页面
"text": "音乐", // 文字
"iconPath": "images/music.png", // 图标路径
"selectedIconPath": "images/music-actived.png" // 选中图标的路径
},
{
"pagePath": "pages/blog/blog",
"text": "发现",
"iconPath": "images/blog.png",
"selectedIconPath": "images/blog-actived.png"
},
{
"pagePath": "pages/profile/profile",
"text": "我的",
"iconPath": "images/profile.png",
"selectedIconPath": "images/profile-actived.png"
}]
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
图标来自于 https://www.iconfont.cn (opens new window)
阿里巴巴图标库,包含矢量图标、字体图标、字体等
# 代码规范
很多公司借鉴的代码规范:https://github.com/airbnb/javascript (opens new window)
# 《音乐》页面开发
<!-- 轮播图组件 参数:indicator-dots 小圆点,autoplay 自动播放, interval 间隔时间,duration 动画时长 -->
<swiper indicator-dots="true" circular="true" interval="3000" duration="500">
<block wx:for="{{swiperImgUrls}}" wx:key="{{index}}"> <!-- 空节点 -->
<swiper-item>
<image src="{{item.url}}" mode="widthFix" class="img"></image>
</swiper-item>
</block>
</swiper>
2
3
4
5
6
7
8
9
# 自定义组件
创建组件
创建目录 components > 组件目录名称 > 右键 新建Component
# 引入组件
在page的json文件中:
{
"usingComponents": {
"x-playlist":"/components/playlist/playlist"
}
}
2
3
4
5
在page的wxml中:
<x-playlist> </x-playlist>
页面引入组件以及组件内部在引用子组件的方法是一样的,同样需要设置json文件。
# 组件传值
父组件中:在引入组件的时候自定义属性名称,并把数据传入子组件
<!-- 参数:playlist 自定义名称,传入组件的数据 -->
<x-playlist playlist="{{传入的数据}}"></x-playlist>
2
子组件中: 子组件的js文件:
/**
* 组件的属性列表
*/
properties: {
playlist:{ // 接收父组件传输的数据
type: Object // 数据类型
}
},
//子组件的wxml文件可直接引入数据{{playlist}}
2
3
4
5
6
7
8
9
10
# wx:key 的使用
key的值不建议使用index,因为当数据发生变化会dom结构产生变化时,使用index的地方不会随之变化。
可以使用数据内部每项不一样的一个数值,如id
<block wx:for="{{swiperImgUrls}}" wx:key="url"> 这里url不需要双大括号,如使用index则需要{{}}
<view>
<image src="{{item.url}}" mode="widthFix" class="img"></image>
</view>
</block>
<view class="playlist-container">
<block wx:for="{{playlist}}" wx:key="_id">
<!-- 参数:playlist 自定义名称,传入组件的数据 -->
<x-playlist playlist="{{item}}"></x-playlist>
</block>
</view>
2
3
4
5
6
7
8
9
10
11
12
13
# async/await 语法
目前,在云函数里,由于 Node 版本最低是 8.9,因此是天然支持 async/await 语法的。而在小程序端则不然。在微信开发者工具里,以及 Android 端手机(浏览器内核是 QQ浏览器的 X5),async/await是天然支持的,但 iOS 端手机在较低版本则不支持,因此需要引入额外的 文件。
可把这个 runtime.js (opens new window) 文件引用到有使用 async/await 的文件当中。
// 注意,必须命名为 regeneratorRuntime
import regeneratorRuntime from '../../utils/runtime.js'
2
# 云函数的使用
cloudfunctions目录 右键 新建 Node.js 云函数
> 输入目录名 getPlaylist
在云函数中向第三方服务器发送请求要依赖第三方库
安装依赖包
云函数目录 getPlaylist
右键 在终端打开
打开命令行 输入命令:
npm install --save request
npm install --save request-promise
2
github request-promise:https://github.com/request/request-promise (opens new window)
然后写相应代码
// 云函数入口文件
const cloud = require('wx-server-sdk')
cloud.init()
const rp = require('request-promise') // 需安装依赖包
const URL = 'http://musicapi.xiecheng.live/personalized'
// 云函数入口函数
exports.main = async (event, context) => {
const playlist = await rp(URL).then((res) => {
return JSON.parse(res).result
})
console.log(playlist)
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
写完代码,云函数目录 getPlaylist
右键 上传并部署:云端安装依赖(不上传node_modules)
进行上传部署代码到云端,等待上传成功,打开云开发控制台即可看到已经上传的云函数,并可对云函数进行测试。
# 数据库操作
数据库> 创建集合 > playlist
// 云函数入口文件
const cloud = require('wx-server-sdk')
cloud.init()
const db = cloud.database() // 初始化数据库,如果在小程序端初始化数据库需要在前面加wx.
const rp = require('request-promise') // 需安装依赖包
const URL = 'http://musicapi.xiecheng.live/personalized' // 第三方服务器地址(老师从网易云获取的数据部署在其服务器,每天的数据会更新)
const playlistCollection = db.collection('playlist') // 获取到数据库playlist集合
const MAX_LIMIT = 10 // 定义常量,获取数据库条数最大的限制
// 云函数入口函数
exports.main = async (event, context) => {
/**
* 注:
* - 关于数据库的操作都是异步操作,都需添加await关键字
* - console.log 打印在云开发控制台 云函数测试内查看
* - 单次获取数据库数据有条数限制,云函数端最多获取100条,小程序端最多获取20条
*/
// const list = await playlistCollection.get() // 获取数据库集合的数据 (因为有条数限制,不直接用此方法)
// 突破条数限制 (为了读取到全部数据然后与第三方服务器获取的数据进行对比去重)
const countResult = await playlistCollection.count() // 获取数据总条数 返回为对象
const total = countResult.total // 取得总条数
const batchTimes = Math.ceil(total / MAX_LIMIT)
const tasks = []
for(let i = 0; i < batchTimes; i++) {
let promise = playlistCollection.skip(i * MAX_LIMIT).limit(MAX_LIMIT).get() // 从第 skip 条开始取,最多取 limit 条数据
tasks.push(promise)
}
let list = {
data: []
}
if (tasks.length > 0) {
list = (await Promise.all(tasks)).reduce((acc, cur) => { // reduce数组方法 累积拼接
return {
data: acc.data.concat(cur.data)
}
})
}
// 获取第三方服务器端数据
const playlist = await rp(URL).then((res) => {
return JSON.parse(res).result
})
// 数据库与服务器数据对比去重(数据已存在数据库的无需再重复添加)
const newData = []
for(let i = 0, len1 = playlist.length; i < len1; i++) {
let flag = true
for(let j = 0, len2 = list.data.length; j < len2; j++) {
if(playlist[i].id === list.data[j].id){
flag = false
break
}
}
if(flag){
newData.push(playlist[i])
}
}
// 把数据插入数据库,需要单条插入
for (let i = 0, len = newData.length; i < len; i++) {
await playlistCollection.add({ // 给数据库集合添加数据
data: {
...newData[i],
createTime: db.serverDate(), // db.serverDate() 获取服务器时间
}
}).then((res) => { // 数据添加成功
console.log('数据添加成功')
}).catch((err) => { // 失败
console.error(err)
})
}
return newData.length // 插入多少条数据
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
# 查询数据库
//云函数中查询数据库的例子:
// 云函数入口文件
const cloud = require('wx-server-sdk')
cloud.init()
const TcbRouter = require('tcb-router')
const db = cloud.database() // 初始化数据库
const blogCollection = db.collection('blog') // 博客的数据库集合
// 云函数入口函数
exports.main = async (event, context) => {
const app = new TcbRouter({ event }) // 初始化TcbRouter
app.router('list', async (ctx, next) => {
// skip 从第几条开始查,limit 查几条数据,orderBy(排序字段,排序方式) 排序,排序方式desc降序/asc升序
ctx.body = await blogCollection.skip(event.start).limit(event.count)
.orderBy('createTime', 'desc').get().then((res) => {
return res.data
})
})
return app.serve() // 必需返回
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
# 云函数调试
云控制台中可会云函数进行云端测试
在小程序调用云函数后,可查看云函数日志
# 定时触发云函数
如果云函数需要定时 / 定期执行,也就是定时触发,我们可以使用云函数定时触发器。配置了定时触发器的云函数,会在相应时间点被自动触发,函数的返回结果不会返回给调用方
云函数目录下新建 config.json
{
"triggers": [
{
"name": "myTriggers",
"type": "timer",
"config":"0 0 10,14,16,20 * * * *" //表示每天的10点、14点、16点、20点触发一次
}
]
}
2
3
4
5
6
7
8
9
编辑好触发器之后,要在云函数目录 > 右键 > 上传触发器
# 配置云函数超时时间
当云函数比较复杂的时候,默认的超时时间3秒可能不能够满足需求,可以适当的设置更为合理的时间
云开发控制台 > 云函数 > 配置 > 超时时间
# 上拉加载与下拉刷新
page页面json中:
"enablePullDownRefresh": true
page页面js中有这两个函数:
/**
* 页面相关事件处理函数--监听用户下拉动作
*/
onPullDownRefresh: function() {
this.setData({
playlist: []
})
this._getPlaylist()
},
/**
* 页面上拉触底事件的处理函数
*/
onReachBottom: function() {
this._getPlaylist()
},
下拉刷新请求完数据后
wx.stopPullDownRefresh() // 停止下拉刷新动画
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
# 云函数路由优化tcb-router
一个用户在一个云环境只能创建50个云函数
假如小程序非常复杂,50个云函数不能够满足业务需求怎么办?
相似的请求归类到同一个云函数处理
tcb-router是一个koa风格的云函数路由库
通俗理解就是可以把很多个接口归类到同一个云函数内。
github-tcb-router: https://github.com/TencentCloudBase/tcb-router (opens new window)
koa洋葱模型...
安装:
在使用到tcb-router的云函数目录下打开命令行,输入命令进行安装
npm install --save tcb-router
2
// 云函数的 index.js
const TcbRouter = require('router'); // 必需
exports.main = (event, context) => {
const app = new TcbRouter({ event });// 必需
// app.use 表示该中间件会适用于所有的路由(全局中间件) 非必需
app.use(async (ctx, next) => { // 这个中间件表示所有路由都会调用到,而路由中间件为单独调用
ctx.data = {}; // 获取要传给小程序端的数据
ctx.data.openId = event.userInfo.openId // 这里获取到的openId将分布到所有路由
await next(); // 执行下一中间件
});
// 路由为数组表示,该中间件适用于 user 和 timer 两个路由
app.router(['user', 'timer'], async (ctx, next) => {
ctx.data.company = 'Tencent'; // 这里获取到的数据将分布到 user 和 timer 两个路由
await next(); // 执行下一中间件
});
// 路由为字符串,该中间件只适用于 user 路由
app.router('user', async (ctx, next) => {
ctx.data.name = 'heyli'; // 获取要传给小程序端的数据
await next(); // 执行下一中间件
}, async (ctx, next) => {
ctx.data.sex = 'male'; // 获取要传给小程序端的数据
await next(); // 执行下一中间件
}, async (ctx) => {
ctx.data.city = 'Foshan'; // 获取要传给小程序端的数据
// ctx.body 返回数据到小程序端
ctx.body = { code: 0, data: ctx.data}; // 要传给小程序端的数据
});
// 路由为字符串,该中间件只适用于 timer 路由
app.router('timer', async (ctx, next) => {
ctx.data.name = 'flytam';
await next(); // 执行下一中间件
}, async (ctx, next) => {
ctx.data.sex = await new Promise(resolve => {
// 等待500ms,再执行下一中间件
setTimeout(() => {
resolve('male');
}, 500);
});
await next(); // 执行下一中间件
}, async (ctx)=> {
ctx.data.city = 'Taishan';
// ctx.body 返回数据到小程序端
ctx.body = { code: 0, data: ctx.data };
});
return app.serve(); // 必需
}
小程序端:
// 调用名为 router 的云函数,路由名为 user
wx.cloud.callFunction({
// 要调用的云函数名称
name: "router",
// 传递给云函数的参数
data: {
$url: "user", // 要调用的路由的路径,传入准确路径或者通配符*
other: "xxx"
}
}).then((res) => {
console.log(res)
})
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
上面tcb-router代码会按照洋葱模型执行,即先从上往下逐个进入中间件,再从下往上逐个退出中间件。
# 本地存储(缓存)
// 存储:
wx.setStorageSync(key, data) // 同步存储(存储成功再继续下一步操作)
wx.setStorage(key, data) // 异步存储(即使存储没成功也会执行下一步代码)、
// 读取:
wx.getStorageSync(key) // 同步 (读取到数据在进行下一步操作)
wx.setStorage(key) // 异步
2
3
4
5
6
7
8
# api设置title
wx.setNavigationBarTitle({
title: '',
})
2
3
# 背景播放音
BackgroundAudioManager (opens new window) 全局唯一的背景音频管理器
// 需要在app.json配置,才能使用后台音乐播放的能力
"requiredBackgroundModes": ["audio", "location"]
2
3
// 获取全局唯一的背景音频管理器
const backgroundAudioManager = wx.getBackgroundAudioManager()
backgroundAudioManager.src = 音频链接
backgroundAudioManager.title = 音频标题
2
3
4
5
6
7
# createSelectorQuery查询节点信息
createSelectorQuery (opens new window) 小程序的方法,用于查询节点等操作
const query = wx.createSelectorQuery()
query.select('#the-id').boundingClientRect() // 节点的布局信息
query.selectViewport().scrollOffset()
query.exec(function(res){
res[0].top // #the-id节点的上边界坐标
res[1].scrollTop // 显示区域的竖直滚动位置
})
2
3
4
5
6
7
# 组件内的方法
Component(Object object) (opens new window)
# 组件生命周期
// 生命周期
lifetimes: {
ready() { // 在组件在视图层布局完成后执行
...
}
},
2
3
4
5
6
# 组件所在页面的生命周期
Component({
pageLifetimes: {
show: function() {
// 页面被展示
},
hide: function() {
// 页面被隐藏
},
resize: function(size) {
// 页面尺寸变化
}
}
})
2
3
4
5
6
7
8
9
10
11
12
13
# 组件对数据的监听
observers: { // 对数据的监听(数据初次加载完成也会执行)
监听的数据对象(newData){
console.log(newData)
}
},
2
3
4
5
# 子组件自定义事件传递给父组件
子组件js:
// 触发自定义事件 向父组件传值, 参数x(可选,传递给父组件的参数,可以是对象或其他)
this.triggerEvent('自定义事件名', 参数x)
父组件wxml:
<子组件标签 bind:自定义事件名="执行的事件" />
父组件js:
执行的事件(event) {
console.log(event.detil.参数)
}
2
3
4
5
6
7
8
9
10
11
12
# 父组件自定义事件传递给子组件
父组件wxml:
<子组件标签 class="子组件类名">
父组件JS:
// 选择组件,并传入事件和参数
this.selectComponent('.子组件类名').自定义事件名(传入参数)
子组件js:
methods: {
自定义事件名(参数x){
console.log(参数x)
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
# 兄弟组件间传递事件和传值
例子:子组件1向子组件2传递参数
父组件wxml中:
<子组件标签1 bind:自定义事件名1="执行的事件">
<子组件标签2 class="子组件2类名">
父组件js:
执行的事件(event) {
this.selectComponent('.子组件2类名').自定义事件名2(event.detil.参数x) // 向子组件2传值
}
子组件1js:
// 触发自定义事件 向父组件传值, 参数x(可选,传递给父组件的参数,可以是对象或其他)
this.triggerEvent('自定义事件名1', 参数x)
子组件2js:
methods: {
自定义事件名2(参数x){
console.log(参数x) // 接收父组件传入的值
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
# 获取手机信息
wx.getSystemInfo(Object object) (opens new window)
wx.getSystemInfo({
success(res){
console.log(res) //手机信息
}
})
2
3
4
5
# 滚动组件
scroll-view (opens new window)
<scroll-view scroll-y scroll-top="{{scrollTop}}" scroll-with-animation="true">
</scroll-view>
2
# 全局属性、方法(类似vuex)
在app.js中:
onLaunch: function () {
this.globalData = {// 设置全局属性、方法
test: 0
}
},
setGlobalData(dataItem, val) { // 设置全局属性
this.globalData[dataItem] = val
},
getGlobalData(dataItem) { // 获取全局属性
return this.globalData[dataItem]
}
在需要调用的页面js中:
const app = getApp() // 在最顶部先调用app方法
// 设置全局属性
app.setGlobalData('test', 1)
// 获取全局属性
app.getGlobalData('test')
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
# 消息提示框
wx.showToast({
title: '成功',
icon: 'success', //图标: success 成功、loading 加载中、none 无
duration: 2000
})
2
3
4
5
# 《发现》页面
# 调用组件外部的样式
components内部的组件无法直接调用外部的样式。可通过以下方式调用组件外部样式:
方法一:
父组件wxml:
<!-- iconfont 和 icon-sousuo 是传入组件内部的样式名称,iconfont(自定义名称)="iconfont(外部样式文件中定义的样式名)" -->
<x-search iconfont="iconfont" icon-sousuo="icon-sousuo"/>
子组件js:
// 组件外部样式
externalClasses: [
'iconfont', // 对应的是上面等号前面的名称
'icon-sousuo'
],
子组件wxml: 即可实现调用组件外的样式
<i class="iconfont icon-sousuo" />
注意:如果想在组件内部再次修改样式,不能够引用外部传进来的class名称进行修改,可以另起一个class名称进行修改。
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
方法二:
组件内:
Component({
options: {
styleIsolation: 'apply-shared'
}
})
2
3
4
5
6
# 组件插槽slot
单个插槽
父组件调用传入插槽内容:
<组件标签>
<view>
<view>插槽内容</view>
<view>插槽内容</view>
</view>
</组件标签>
组件内部定义slot标签:
<view>
<!-- slot插槽 -->
<slot></slot>
</view>
2
3
4
5
6
7
8
9
10
11
12
13
如果需要实现多个插槽
父组件调用传入插槽内容:
<组件标签>
<view slot="slot2">
<view>插槽1内容</view>
<view>插槽1内容</view>
</view>
<view slot="slot1">
<view>插槽2内容</view>
<view>插槽2内容</view>
</view>
</组件标签>
组件js :
options: {// 设置
multipleSlots: true // 打开多个插槽功能
},
组件内部定义slot标签:
<view>
<!-- slot插槽 具名插槽-->
<slot name="slot1"></slot>
<slot name="slot2"></slot>
</view>
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
# 判断用户授权
// 判断用户是否授权
wx.getSetting({
success: (res) => { // 这里使用箭头函数可改变内部this指向为外部的this
console.log(res)
if (res.authSetting['scope.userInfo']) { // 已授权
wx.getUserInfo({ // 获取用户信息
success(res) {
console.log(res)
}
})
} else { // 未授权
}
}
})
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# button的开发能力(获取用户信息)1
<button class="login"
open-type="getUserInfo"
bindgetuserinfo="onGetUserInfo" // bindgetuserinfo 为固定的
>
获取微信授权信息
</button>
bindgetuserinfo 事件会询问用户是否同意授权
js中:
onGetUserInfo(event) { // 获取用户信息
const userInfo = event.detail.userInfo
if (userInfo) { // 用户允许授权
this.setData({
modalShow: false
})
this.triggerEvent('loginSuccess', userInfo) // 给父组件传用户数据
} else { // 用户拒绝授权
this.triggerEvent('loginFail')
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
# 原生组件
auto-focus 自动获取焦点
<textarea
class="content"
placeholder="分享新鲜事..."
maxlength="140"
auto-focus
bindinput="onInput"
bindfocus="onFocus"
bindblur="onBlur"
></textarea>
2
3
4
5
6
7
8
9
10
11
12
# 选择上传图片
let max = 9 - this.data.images.length // 还能再选几张图片
wx.chooseImage({
count: max, // 还能再选几张图片
sizeType: ['original', 'compressed'], // 初始值 and 压缩过的
sourceType: ['album', 'camera'], // 手机相册选择 and 拍照选择
success: (res) => { // 箭头函数改变this指向
console.log(res)
},
})
2
3
4
5
6
7
8
9
# 图片裁剪
<!-- mode 图片裁剪 aspectFill 保证短边完整显示 -->
<image class="image" src="{{item}}" mode="aspectFill"></image>
2
# 获取标签自定义属性data-* (删除图片的实现)
<!-- 显示图片 -->
<block wx:for="{{images}}" wx:key="*this">
<view class="image-wrap">
<!-- mode 图片裁剪 aspectFill 保证短边完整显示 -->
<image class="image" src="{{item}}" mode="aspectFill"></image>
<icon class="iconfont icon-shanchu" bindtap="onDelImage" data-index="{{index}}"></icon>
</view>
</block>
// 删除图片
onDelImage(event) {
// event.target.dataset.index 获取标签属性data-index的值
this.data.images.splice(event.target.dataset.index, 1) // splice会改变原有数组
this.setData({
images: this.data.images
})
},
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
# 全屏预览图片(点击图片放大预览)
// 全屏预览图片
onPreviewImage(event) {
wx.previewImage({
urls: this.data.images, // 图片地址列表
current: event.target.dataset.imgsrc // 当前预览图片地址
})
},
2
3
4
5
6
7
# 文件上传云存储(发布博客例子)
// 结合'发布'的例子:
send() {
// 验证是否输入内容
if (content.trim() === '') { // trim() 去掉字符串空格
wx.showToast({
title: '请输入内容',
icon: 'none'
})
return
}
wx.showLoading({
title: '发布中',
})
/**
* 实现思路及步骤:
* 1、图片 -> 上传 云存储 -> 生成 图片fineID(云文件ID)
* 2、数据 -> 录入 云数据库
* 数据包括:文字内容、图片fineID、昵称、头像、发布时间、openId(用户唯一标识,在插入数据库是系统会自动添加_openId字段,不需要另外插入)
*/
let promiseArr = []
let fileIds = []
// 图片上传云存储
this.data.images.forEach((item) => {
let p = new Promise((resolve, reject) => {
let suffix = /\.\w+$/.exec(item)[0] // 文件扩展名(文件后缀)
wx.cloud.uploadFile({ // 每次只能上传一个文件
/**
* cloudPath 云路径。如果路径相同,后上传的文件会覆盖原文件
* 路径:blog/云存储中的文件夹 + Date.now()时间戳 + Math.random()*1000000随机数 + 文件后缀
*/
cloudPath: 'blog/' + Date.now() + '-' + Math.random() * 1000000 + suffix,
filePath: item, // 文件本地临时路径
success: (res) => {
fileIds.push(res.fileID)
resolve()
},
fail: (err) => {
console.error(err)
reject()
}
})
})
promiseArr.push(p)
})
// 存入云数据库
Promise.all(promiseArr).then((res) => {
db.collection('blog').add({
data: {
...userInfo, // 昵称、头像
content, // 内容
img: fileIds, // 图片fileID列表
createTime: db.serverDate() // 创建时间,取服务端时间
}
}).then((res) => {
wx.hideLoading()
wx.showToast({
title: '发布成功',
})
// 返回博客页面,并刷新
wx.navigateBack()
})
}).catch((err) => {
wx.hideLoading()
wx.showToast({
title: '抱歉,发布失败',
icon: 'none'
})
})
},
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
# js模块化 (时间格式化)
在目录utils 中新建formatTime.js文件
// 时间格式化 模块封装
module.exports = (date) => { // date 数据格式为 date
let fmt = 'yyyy-MM-dd hh:mm:ss' // 预定格式
const o = {
// + 正则中的1个或多个
'M+': date.getMonth() + 1,
'd+': date.getDate(),
'h+': date.getHours(),
'm+': date.getMinutes(),
's+': date.getSeconds()
}
if (/(y+)/.test(fmt)) {
// $1 表示正则中的第一个,即(y+)
fmt = fmt.replace(RegExp.$1, date.getFullYear()) // replace 替换
}
for (let k in o) {
if (new RegExp('('+ k +')').test(fmt)) {
fmt = fmt.replace(RegExp.$1, o[k].toString().length === 1 ? '0' + o[k] : o[k])
}
}
return fmt
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
在组件引入js模块
import formatTime from '../../utils/formatTime.js'
使用:
formatTime(new Date('Wed Aug 28 2019 16:23:06 GMT+0800 (中国标准时间)'))
2
3
4
# 阻止事件冒泡
bind
和 catch
都可以绑定事件,它们的区别是 bind
有事件冒泡,而 catch
没有
# 返回上一个页面并执行方法
// 返回博客页面,并刷新
wx.navigateBack()
const pages = getCurrentPages() // 获取当前页面栈
const prevPage = pages[pages.length - 2] // 取到上一个页面
prevPage.onPullDownRefresh() // 执行上一个页面的方法 onPullDownRefresh
2
3
4
5
# 图片懒加载
给image标签设置 lazy-load 为 true
<image class="img" src="{{item}}" lazy-load="true"></image>
.img {
background: #eee;
}
2
3
4
5
6
懒加载占位图可以给image设置背景图或背景色
# 模糊查询
// 获取博客列表
app.router('blogList', async (ctx, next) => {
const keyword = event.keyword // 搜索关键字 调用接口时传递来的数据
let w = {}
if (keyword.trim() != '') {
w = {
content: db.RegExp({ // 正则
regexp: keyword,
options: 'i' // i表示忽略大小写
})
}
}
// where查询条件 skip 从第几条开始查,limit 查几条数据,orderBy(排序字段,排序方式) 排序,排序方式desc降序/asc升序
ctx.body = await blogCollection.where(w).skip(event.start).limit(event.count)
.orderBy('createTime', 'desc').get().then((res) => {
return res.data
})
})
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
# 提升模糊查询的效率 (添加索引,对数据量大的查询效果明显)
云开发控制台 > 数据库相应的集合 > 索引管理 > 添加索引 > 输入自定义索引名称、该字段的值是否唯一、被查询的字段名、升序/降序 > ok
# 小程序端调用云数据库
一般调用云数据库的操作都写在云函数内,其实小程序端也可以对数据库进行操作。
小程序端一次最多只能查询20条数据,云函数端最多可查询100条数据,可使用多次查询拼接的方式突破限制。
// 小程序端调用云数据库示例
const db = wx.cloud.database() // 初始化数据库
db.collection('blog').orderBy('createTime','deac').get().then((res) => {
console.log(res)
})
2
3
4
5
# 云数据库权限管理
注意:云控制台和服务端(云函数)始终有所有数据读写权限,
但权限的管理仅对小程序端发起的请求有效。
仅创建者可写,所有人可读 (适合于文章)
仅创建者可读写 (适用于私密内容)
仅管理端可写,所有人可读(适用于商品信息)
仅管理端可读写(适用于后台敏感数据)
# 数据库中1对N关系的三种设计方式
# 第一种:N的数量较少 几十个以内
1 条记录存储 N 个子数据
如一条博客中,最多有9张图片,这9张图片可和其他数据放在一个记录中。
[
{
id:...
img:[
'...', '...', '...', '...', '...', '...', '...', '...', '...'
]
}
]
2
3
4
5
6
7
8
# 第二种:N的数量较多 几十到几百个
1 存储 每个N的 id
可分两个数据库集合,
一个为 '目录' 集合,存放 '详情' 集合下的每条数据的 id 目录
一个为 '详情' 集合,每条数据对应一个单独的 id 和 详细数据
目录集合:
[
{
'id':"11",
'name': '产品1',
'xqs': ['111','222','333', ... ] // 存放 详情集合 中的每条数据 id
}
]
详情集合:
[
{'id':"111",name:'零件1',title:'...' ...},
{'id':"222",name:'零件2',title:'...' ...},
{'id':"333",name:'零件3',title:'...' ...},
...
]
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
如歌单列表,与歌曲详情的数据组合设计。
# 第三种:N的数量巨大 几百成千上万个
每个 N 都存储 1 的 id
如新浪博客中的一条博客下面有几千条评论
一条新浪博客:
[{
'id':'11',
'content':'博客内容'
...
}]
上千条评价:
[
{
'id':'111111'
'blogId':'11', // 这个id对应的是那一条博客的id
'content': '评价内容1'
},
{
'id':'222222'
'blogId':'11', // 这个id对应的是那一条博客的id
'content': '评价内容2'
},
{
'id':'33333'
'blogId':'11', // 这个id对应的是那一条博客的id
'content': '评价内容3'
},
...
]
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
# 云调用
通过云函数调用服务端的开发接口
这些接口如:模板消息推送、生成小程序码...
# 模板消息推送
1、使用from表单才能触发消息推送,并设置report-submit="true"
<form slot="modal-content" report-submit="true" bind:submit="onSend">
<textarea name="content" class="comment-content" placeholder="写评论" value="{{content}}" fixed="true"></textarea>
<button class="send" form-type="submit">发送</button>
</form>
2
3
4
5
2、需要到微信公众平台做相应的设置:
微信公众平台 > 功能 > 模板消息 > 添加模板 > 选择相应的模板> 添加成功后会有一个模板ID
3、新建一个云函数,用于云调用。在该云函数下新建配置文件:config.json ,用于配置权限
config.json :
{
"permissions": {
"openapi": [
"templateMessage.send"
]
}
}
2
3
4
5
6
7
云函数设置消息推送:
// 云函数入口函数
exports.main = async (event, context) => {
// 获取openid
const { OPENID } = cloud.getWXContext()
// 模板推送消息
const result = await cloud.openapi.templateMessage.send({
touser: OPENID,
page: `/pages/blog-comment/blog-comment?blogId=${event.blogId}`, // 用户点击推送消息打开的页面
data: { // 模板的内容,keyword为在公众平台设置模板时对应的字段
keyword1: { // 评价内容
value: event.context
},
keyword2: { // 评价时间
value: event.time
}
},
templateId: 'LNwKMcYwlz-0HabgBhmZi6CWZrlNSBiNJ2h0SMorcxQ', // 模板id,到公众平台模板消息上获取
formId: event.formId // 触发消息推送的form表单的id
})
return result
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
4、在提交表单事件完成后调用消息推送云函数
wx.cloud.callFunction({
name: 'sendMessage',
data: {
content,
formId,
blogId: this.properties.blogId
}
}).then((res) => {
console.log(res)
})
2
3
4
5
6
7
8
9
10
# 云函数多集合查询数据库
// 博客详情(博客内容、评论)
app.router('blogDetail', async(ctx, next) => {
let blogId = event.blogId
// 博客内容
let detail = await blogCollection.where({
_id: blogId
}).get().then((res) => {
return res.data
})
// 评论查询
const countResult = await blogCollection.count()
const total = countResult.total
let commentList = {
data: []
}
if (total > 0) {
// 突破100条限制
const batchTimes = Math.ceil(total / MAX_LIMIT)
const tasks = []
for (let i = 0; i < batchTimes; i++) {
let promise = db.collection('blog-comment').skip(i * MAX_LIMIT)
.limit(MAX_LIMIT).where({
blogId
}).orderBy('createTime', 'desc').get()
tasks.push(promise)
}
if (tasks.length > 0) {
commentList = (await Promise.all(tasks)).reduce((acc, cur) => {
return {
data: acc.data.concat(cur.data)
}
})
}
}
ctx.body = {
detail,
commentList
}
})
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
# 分享功能
分享功能需要button标签,设置open-type="share"
<button open-type="share" data-blogid="{{blogId}}" data-blog="{{blog}}" class="share-btn" hover-class="share-hover">
<i class="iconfont icon-fenxiang icon"></i>
<text>分享</text>
</button>
2
3
4
5
在js中有onShareAppMessage方法,点击button会自动执行此方法
onShareAppMessage: function (event) {
console.log(event)
// 对分享卡片的设置
let blogObj = event.target.dataset.blog
return {
title: blogObj.content,
path: `/pages/blog-comment/blog-comment?blogId=${blogObj._id}`,
// imageUrl: '' // 自定义图片,不支持云存储的图片
}
}
2
3
4
5
6
7
8
9
10
11
# 不同场景获取用户信息的方式
# 场景一:只想在界面上显示自己的昵称和头像
以组件的方式:根据type类型获取不同用户数据
该方式不需要授权,只能用于在wxml显示自己的信息
<open-data type="userAvatarUrl"></open-data>
<open-data type="userNickName"></open-data>
...
2
3
# 场景二:在JS中获取用户信息
该方式要在用户授权以后才能获取用户信息
wx.getUserInfo (opens new window)
wx.getUserInfo({
success: (res) => {
console.log(res)
}
})
2
3
4
5
在未授权的情况下需要用户先授权:
// 判断用户是否授权
wx.getSetting({
success: (res) => { // 这里使用箭头函数可改变内部this指向为外部的this
if (res.authSetting['scope.userInfo']) { // 已授权
wx.getUserInfo({ // 获取用户信息
success: (res) => { // 这里使用箭头函数可改变内部this指向为外部的this
app.setGlobalData('userInfo', res.userInfo) // 设置app全局属性
this.onLoginSuccess({
detail: res.userInfo
})
}
})
} else { // 未授权
this.setData({ // 打开弹出层,显示获取用户信息按钮
modalShow: true
})
}
}
})
授权按钮
<button class="login" open-type="getUserInfo" bindgetuserinfo="onGetUserInfo">获取微信授权信息</button>
onGetUserInfo(event) { // 获取用户信息
const userInfo = event.detail.userInfo
if (userInfo) { // 用户允许授权
this.setData({
modalShow: false
})
this.triggerEvent('loginSuccess', userInfo) // 给父组件传用户数据
} else { // 用户拒绝授权
this.triggerEvent('loginFail')
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
注意:上面这种方式没有获取到openId
# 场景三:获取openId
获取openId不需要用户授权
1、传统开发方式获取openId,后台服务器由自己开发,没使用云开发
小程序端 微信服务器 后端服务器
步骤:
小程序端 调用 wx.login 向微信服务器 获取code
小程序端 调用 wx.request 将 code 传递给 后端服务器
后端服务器 使用code 向微信服务器 换取openid和session_key
后端服务器 将openid 发送给 小程序端
2、云开发方式获取openId
云函数login中
// 获取 WX Context (微信调用上下文),包括 OPENID、APPID、及 UNIONID(需满足 UNIONID 获取条件)
const wxContext = cloud.getWXContext()
return {
event,
openid: wxContext.OPENID,
appid: wxContext.APPID,
unionid: wxContext.UNIONID,
}
2
3
4
5
6
7
8
9
普通按钮
<button bindtap="getOpenid">获取openid</button>
getOpenid() {
wx.cloud.callFunction({
name: 'login'
}).then((res) => {
console.log(res)
})
}
2
3
4
5
6
7
8
9
10
openid 在小程序和公众号下是不一样的
unionid 在小程序和公众号下都是一样的
# 《我的》页面
json文件
"navigationBarTitleText": "我的",
"disableScroll": true // 使页面无法滚动
2
# 导航页面链接跳转
# 背景图片
wxss背景图片不支持本地相对路径的图片,只支持网络图片和base64图片
建议使用base64图片,图片文件最好不要太大。
# 每个页面都有的page标签
page {
background-color: #f1f1f1;
}
2
3
# 播放历史与本地存储
方案一:播放历史存储在数据库当中,这样在不同设备访问都可查看播放历史。读取速度相对较慢
方案二:播放历史存储在本地,仅当前设备可查看播放历史。读取速度较快
本项目采用本地存储:
使用openid作为本地存储的key,播放历史存入value
在app.js中获取openid,即打开小程序就获取openid。
// app.js
onLaunch: function () {
this.getOpenid() // 获取openid并存储
},
getOpenid() { // 获取openid并存储
wx.cloud.callFunction({
name: 'login'
}).then((res) => {
const openid = res.result.openid
this.globalData.openid = openid // 保存到全局变量
if (wx.getStorageSync(openid) == '') { // 该用户从未打开过小程序,未存储过openid在本地
wx.setStorageSync(openid, []) // 存储openid到本地
}
})
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
歌曲播放时
// 保存播放历史到本地存储
savePlayHistory() {
const currentSong = musiclist[nowPlayingIndex] // 当前播放歌曲
const openid = app.globalData.openid // 从全局属性获取openid
const playHistory = wx.getStorageSync(openid) // 从本地存储获取播放历史数组
for (let i = 0, len = playHistory.length; i < len; i++) {
if (playHistory[i].id === currentSong.id) { // 当前播放歌曲已存在播放历史中
playHistory.splice(i, 1) // 删除原纪录
break
}
}
playHistory.unshift(currentSong) // 在数组开头插入
wx.setStorage({ // 存入本地
key: openid,
data: playHistory
})
},
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
播放历史页面获取
onLoad: function (options) {
const openid = app.globalData.openid //从全局属性获取openid
const playHistory = wx.getStorageSync(openid) // 读取本地播放历史数据
if (playHistory.length !== 0) { // 有播放历史
this.setData({
playHistory
})
wx.setStorage({ // storage里把musiclist(播放列表)的内容换成播放历史的列表
key: 'musiclist',
data: playHistory,
})
}
},
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# 我的发现
代码分别演示了从云函数和小程序端获取数据,从小程序端获取数据享有权限管理的能力,不需要传openid。
# 小程序码
本项目演示使用接口 B:适用于需要的码数量极多的业务场景 (opens new window) 云调用
的方式。
步骤:
创建云函数 gteQRCode
gteQRCode云函数下创建config.json配置权限,代码如下:
{
"permissions":{
"openapi":[
"wxacode.getUnlimited"
]
}
}
2
3
4
5
6
7
// 云函数入口函数
exports.main = async (event, context) => {
const wxContext = cloud.getWXContext()
const result = await cloud.openapi.wxacode.getUnlimited({
scene: wxContext.OPENID, // 链接参数 不一定传openid,可传其他任意数据,然后通过此数据,在别人扫码进入时就可用于判断
// page: "pages/blog/blog" // 默认进入主页
// lineColor: { // 线条颜色
// 'r': 211,
// 'g': 60,
// 'b': 57
// },
// isHyaline: true // 是否透明
})
// result为二进制数据, 先上传到云存储
// 上传云存储
const upload = await cloud.uploadFile({
cloudPath: 'qrcode/qrcode' + Date.now() + Math.random() + '.png',
fileContent: result.buffer
})
return upload.fileID
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
# 判断是从扫码小程序码进入,以及参数获取
// 在从小程序码进入的页面js,onLoad方法中,
onLoad: function (options) {
console.log(options.scene) // 获取到小程序码进入的参数
}
2
3
4
5
# 版本更新检测
// app.js
onLaunch: function(options) {
this.checkUpate()
},
checkUpate(){
const updateManager = wx.getUpdateManager()
// 检测版本更新
updateManager.onCheckForUpdate((res)=>{
if (res.hasUpdate){
updateManager.onUpdateReady(()=>{
wx.showModal({
title: '更新提示',
content: '新版本已经准备好,是否重启应用',
success(res){
if(res.confirm){
updateManager.applyUpdate()
}
}
})
})
}
})
},
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
# 性能优化
使用开发者工具的调试器,Audits进行评分,然后根据提示针对项目进行优化。
# 场景值scene的作用与应用场景
场景值用来描述用户进入小程序的路径。完整场景值的含义请查看场景值列表 (opens new window)。
可根据不同场景进入实现不同业务处理,比如一个点餐小程序,店家内贴了小程序码,用户通过扫码进入,可立即进入点餐页面,等等
在app.js中的onLaunch(options) 、onShow(options),options包含scene场景值
开发者工具中,切后台,可模拟进入场景。
# 小程序的"SEO"---页面收录sitemap
在app.js的同级目录下有sitemap.json文件,用于配置收录规则
作用:
使小程序搜索可根据小程序的内容进行搜索到
使用方法:
1、在微信公众平台,小程序信息 > 页面收录设置 > 打开 (默认是已开启)
2、打开sitemap.json文件,配置收录规则
{
"desc": "关于本文件的更多信息,请参考文档 https://developers.weixin.qq.com/miniprogram/dev/framework/sitemap.html",
"rules": [{ // 收录规则
"action": "allow",// 是否被收录,allow允许被收录,disallow不允许
"page": "*" // *星号表示所有页面都被收录
}]
}
2
3
4
5
6
7
{
"desc": "关于本文件的更多信息,请参考文档 https://developers.weixin.qq.com/miniprogram/dev/framework/sitemap.html",
"rules": [{ // 收录规则,可添加多条
"action": "allow", // 是否被收录
"page":"pages/player/player", // 页面
"params": ["musicId","index"], // 链接的动态参数
"matching":'exact' // 表示params的参数是否要准确的匹配
},{
"action": "disallow", // 是否被收录
"page":"*", /
}]
}
// 上面配置规则表示除了player页面被收录外,其他页面不被收录
2
3
4
5
6
7
8
9
10
11
12
13
14
# 小程序上线审核流程
微信公众平台,版本管理>把小程序上传为体验版》提交审核》上线
# 后台管理系统
# 架构示意图
前端 后台 小程序云开发
vue-admin-template <---通过ajax--> 基于Koa2;HTTP API 或 tcb-admin-node ---->云函数、云数据库、云存储
# vue-admin-template构建管理系统前端
vue-element-admin (opens new window) 基于element的后台管理系统模板
vue-admin-template (opens new window) 是 vue-element-admin (opens new window)的简化版
使用方法查看官方文档。
# Koa2构建管理系统后端
官网: https://koa.bootcss.com/ (opens new window)
新建空文件夹wx-music-admin-backend,打开终端:
# 生成package.json文件,-y 表示默认的配置
npm init -y
# 安装koa
npm install koa
# 新建app.js文件 (win10系统命令),作为项目入口文件
type nul > app.js
2
3
4
5
6
7
8
9
app.js:
const Koa = require('koa')
const chalk = require('chalk') // 使console.log打印文字有颜色的插件,需: npm i chalk
const app = new Koa()
app.use(async (ctx) => {
ctx.body = 'Hello Wolrd'
})
const port = 3000
app.listen(port, () => { // 端口号,开启服务后的回调函数
console.log(chalk.green(`> 服务已开启,访问:http://localhost:${port}`))
})
2
3
4
5
6
7
8
9
10
11
终端:
# node启动项目
node app.js
# 访问:http://localhost:3000
2
3
4
# 接口调用凭证 access_token 的缓存与更新
access_token,微信的接口调用凭证,详情:https://developers.weixin.qq.com/miniprogram/dev/api-backend/open-api/access-token/auth.getAccessToken.html (opens new window)
回到项目wx-music-admin-backend,打开终端:
# HTTP 请求 插件
npm i request
npm i request-promise
2
3
/**
* 获取微信接口调用凭证
* 详情:https://developers.weixin.qq.com/miniprogram/dev/api-backend/open-api/access-token/auth.getAccessToken.html
*/
const rp = require('request-promise') // node发送http请求的插件
const fs = require('fs') // node文件模块
const path = require('path') // node 路径模块
//fileName = __dirname 当前文件所在目录的绝对路径, 加上 './access_token.json'
const fileName = path.resolve(__dirname, './access_token.json')
// 这两个参数的获取:微信公众平台>开发>开发设置
const APPID = 'wxc4e0b2d98063b103'
const APPSECRET = 'xxx' //小程序密钥,注意保密!
// 微信 access_token 请求地址
const URL = `https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=${APPID}&secret=${APPSECRET}`
// 发送请求获取AccessToken
const updateAccessToken = async () => {
const resStr = await rp(URL)
const res = JSON.parse(resStr)
if (res.access_token) {
// node写文件,参数:1 文件路径,2 文件内容, 首次写文件为新建,往后为覆盖
fs.writeFileSync(fileName, JSON.stringify({
access_token: res.access_token,
createTime: new Date()
}))
} else { // 如获取不到,再次获取
await updateAccessToken()
}
}
// 读取access_token
const getAccessToken = async () => {
try {
// node读取文件,参数:1 读取的文件,2 字符集
const readRes = fs.readFileSync(fileName, 'utf8')
const readObj = JSON.parse(readRes)
// 如果服务器宕机导致setInterval无法定时更新,这里需要再次判断access_token的有效性
const createTime = new Date(readObj.createTime).getTime()
const nowTime = new Date().getTime()
if((nowTime - createTime) / 1000 / 60 / 60 >= 2) {
await updateAccessToken()
await getAccessToken()
return
}
return readObj.access_token
} catch (error) { //捕获异常,在未创建文件时,先创建文件
await updateAccessToken()
await getAccessToken()
}
}
// access_token有效期为2个小时,定时更新
setInterval(async () => {
await updateAccessToken()
}, (7200 - 300) * 1000)
module.exports = getAccessToken
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
# 后端代码通过HTTP API 触发云函数获取数据
HTTP API 触发云函数 (opens new window)
# 产生跨域和后端解决跨域问题
管理系统前端向管理系统后端请求数据,产生了跨域问题
// 产生跨域的几种情况
// http://www.a.com https://www.a.com 协议不同
// http://www.a.com http://www.b.com 域名不同
// http://www.a.com http://news.a.com 主域与子域不同
// http://www.a.com:8080 http://www.a.com:3000 端口不同
// 解决跨域的几种方法
// jsonp
// iframe
// postMessage跨域
// 跨域资源共享(CORS)
2
3
4
5
6
7
8
9
10
11
管理系统后端,安装
// 解决跨域问题的koa包
npm i koa2-cors
2
app.js
//处理跨域
app.use(cors({
origin: ['http://localhost:9528'], // 允许访问本服务的域
credentials: true
}))
2
3
4
5
# 云数据库的增删改查接口
# 后端获取前端post请求传来的数据
get请求可以直接通过ctx.request.query获取,但是post请求需要安装koa-body
npm i koa-body
app.js
const koaBody = require('koa-body') // 对post请求前端传来的数据的获取,需要此依赖
// 接收post参数解析
app.use(koaBody({
multipart: true
}))
2
3
4
5
6
7
接口.js
router.post('/updatePlaylist', async (ctx, next) => {
const params = ctx.request.body // post请求获取前端传来的数据,需安装和配置koa-body
})
2
3
4
5
# 后端获取云存储图片
云存储中上传图片,云数据库中新建图片的集合,并添加数据字段,字段包含云文件的fileid。
后端项目通过调用云数据库的方式获取数据
router.get('/list', async (ctx, next) => {
// 接口中读取数据库默认最多10条数据
const query = `db.collection('swiper').get()`
const res = await callCloudDB(ctx, 'databasequery', query)
console.log(res)
})
2
3
4
5
6
7
但获取到的数据为fileid,并不能用于显示图片,需要通过微信HTTP API获取云存储的接口来获取图片地址