Evan's blog Evan's blog
首页
  • 前端文章

    • JavaScript
  • 学习笔记

    • 《JavaScript教程》
    • 《JavaScript高级程序设计》
    • 《ES6 教程》
    • 《Vue》
    • 《React》
    • 《TypeScript 从零实现 axios》
    • 《Git》
    • TypeScript
    • JS设计模式总结
  • HTML
  • CSS
  • 技术文档
  • GitHub技巧
  • Nodejs
  • 博客搭建
  • 学习
  • 面试
  • 心情杂货
  • 实用技巧
  • 友情链接
关于
收藏
  • 分类
  • 标签
  • 归档
GitHub (opens new window)

Evan Xu

前端界的小学生
首页
  • 前端文章

    • JavaScript
  • 学习笔记

    • 《JavaScript教程》
    • 《JavaScript高级程序设计》
    • 《ES6 教程》
    • 《Vue》
    • 《React》
    • 《TypeScript 从零实现 axios》
    • 《Git》
    • TypeScript
    • JS设计模式总结
  • HTML
  • CSS
  • 技术文档
  • GitHub技巧
  • Nodejs
  • 博客搭建
  • 学习
  • 面试
  • 心情杂货
  • 实用技巧
  • 友情链接
关于
收藏
  • 分类
  • 标签
  • 归档
GitHub (opens new window)
  • JavaScript文章

  • 学习笔记

    • 《JavaScript教程》笔记
    • 《JavaScript高级程序设计》笔记
    • 《ES6 教程》笔记
    • 《Vue》笔记
    • 《React》笔记
    • 《TypeScript 从零实现 axios》
    • 《Git》学习笔记
    • TypeScript笔记
    • 小程序笔记
      • 基础课程部分
        • 微信公众平台
        • 小程序文档API
        • 微信开放社区
        • 目录说明
        • 文件说明
        • 阻止事件冒泡
        • 第三方库
        • 云开发
        • 云开发三大基础能力
        • 云函数
        • 云数据库
        • 云存储
        • 云数据库能力
        • 数据类型
        • 操作云数据库
        • 云数据库权限管理
        • 操作云数据库
      • 实战课程部分
        • serverless(无服务)
        • 云开发优势
        • 云开发提供能力
        • appID
        • 云开发项目默认目录结构
        • 云开发环境
        • 开发前的准备
        • project.config.json 文件说明
        • app.json
        • 代码规范
        • 《音乐》页面开发
        • 自定义组件
        • 引入组件
        • 组件传值
        • wx:key 的使用
        • async/await 语法
        • 云函数的使用
        • 数据库操作
        • 查询数据库
        • 云函数调试
        • 定时触发云函数
        • 配置云函数超时时间
        • 上拉加载与下拉刷新
        • 云函数路由优化tcb-router
        • 本地存储(缓存)
        • api设置title
        • 背景播放音
        • createSelectorQuery查询节点信息
        • 组件内的方法
        • 组件生命周期
        • 组件所在页面的生命周期
        • 组件对数据的监听
        • 子组件自定义事件传递给父组件
        • 父组件自定义事件传递给子组件
        • 兄弟组件间传递事件和传值
        • 获取手机信息
        • 滚动组件
        • 全局属性、方法(类似vuex)
        • 消息提示框
      • 《发现》页面
        • 调用组件外部的样式
        • 组件插槽slot
        • 判断用户授权
        • button的开发能力(获取用户信息)1
        • 原生组件
        • 选择上传图片
        • 图片裁剪
        • 获取标签自定义属性data-* (删除图片的实现)
        • 全屏预览图片(点击图片放大预览)
        • 文件上传云存储(发布博客例子)
        • js模块化 (时间格式化)
        • 阻止事件冒泡
        • 返回上一个页面并执行方法
        • 图片懒加载
        • 模糊查询
        • 提升模糊查询的效率 (添加索引,对数据量大的查询效果明显)
        • 小程序端调用云数据库
        • 云数据库权限管理
        • 数据库中1对N关系的三种设计方式
        • 第一种:N的数量较少 几十个以内
        • 第二种:N的数量较多 几十到几百个
        • 第三种:N的数量巨大 几百成千上万个
        • 云调用
        • 模板消息推送
        • 云函数多集合查询数据库
        • 分享功能
        • 不同场景获取用户信息的方式
        • 场景一:只想在界面上显示自己的昵称和头像
        • 场景二:在JS中获取用户信息
        • 场景三:获取openId
      • 《我的》页面
        • 导航页面链接跳转
        • 背景图片
        • 每个页面都有的page标签
        • 播放历史与本地存储
        • 我的发现
        • 小程序码
        • 判断是从扫码小程序码进入,以及参数获取
        • 版本更新检测
        • 性能优化
        • 场景值scene的作用与应用场景
        • 小程序的"SEO"---页面收录sitemap
        • 小程序上线审核流程
      • 后台管理系统
        • 架构示意图
        • vue-admin-template构建管理系统前端
        • Koa2构建管理系统后端
        • 接口调用凭证 access_token 的缓存与更新
        • 后端代码通过HTTP API 触发云函数获取数据
        • 产生跨域和后端解决跨域问题
        • 云数据库的增删改查接口
        • 后端获取前端post请求传来的数据
        • 后端获取云存储图片
        • 后端上传图片到云存储
    • JS设计模式总结笔记
  • 前端
  • 学习笔记
xugaoyi
2019-12-25
目录
基础课程部分
微信公众平台
小程序文档API
微信开放社区
目录说明
文件说明
阻止事件冒泡
第三方库
云开发
云开发三大基础能力
云函数
云数据库
云存储
云数据库能力
数据类型
操作云数据库
云数据库权限管理
操作云数据库
实战课程部分
serverless(无服务)
云开发优势
云开发提供能力
appID
云开发项目默认目录结构
云开发环境
开发前的准备
project.config.json 文件说明
app.json
代码规范
《音乐》页面开发
自定义组件
引入组件
组件传值
wx:key 的使用
async/await 语法
云函数的使用
数据库操作
查询数据库
云函数调试
定时触发云函数
配置云函数超时时间
上拉加载与下拉刷新
云函数路由优化tcb-router
本地存储(缓存)
api设置title
背景播放音
createSelectorQuery查询节点信息
组件内的方法
组件生命周期
组件所在页面的生命周期
组件对数据的监听
子组件自定义事件传递给父组件
父组件自定义事件传递给子组件
兄弟组件间传递事件和传值
获取手机信息
滚动组件
全局属性、方法(类似vuex)
消息提示框
《发现》页面
调用组件外部的样式
组件插槽slot
判断用户授权
button的开发能力(获取用户信息)1
原生组件
选择上传图片
图片裁剪
获取标签自定义属性data-* (删除图片的实现)
全屏预览图片(点击图片放大预览)
文件上传云存储(发布博客例子)
js模块化 (时间格式化)
阻止事件冒泡
返回上一个页面并执行方法
图片懒加载
模糊查询
提升模糊查询的效率 (添加索引,对数据量大的查询效果明显)
小程序端调用云数据库
云数据库权限管理
数据库中1对N关系的三种设计方式
第一种:N的数量较少 几十个以内
第二种:N的数量较多 几十到几百个
第三种:N的数量巨大 几百成千上万个
云调用
模板消息推送
云函数多集合查询数据库
分享功能
不同场景获取用户信息的方式
场景一:只想在界面上显示自己的昵称和头像
场景二:在JS中获取用户信息
场景三:获取openId
《我的》页面
导航页面链接跳转
背景图片
每个页面都有的page标签
播放历史与本地存储
我的发现
小程序码
判断是从扫码小程序码进入,以及参数获取
版本更新检测
性能优化
场景值scene的作用与应用场景
小程序的"SEO"---页面收录sitemap
小程序上线审核流程
后台管理系统
架构示意图
vue-admin-template构建管理系统前端
Koa2构建管理系统后端
接口调用凭证 access_token 的缓存与更新
后端代码通过HTTP API 触发云函数获取数据
产生跨域和后端解决跨域问题
云数据库的增删改查接口
后端获取前端post请求传来的数据
后端获取云存储图片
后端上传图片到云存储

小程序笔记

# 小程序笔记

# 基础课程部分

# 微信公众平台

https://mp.weixin.qq.com/ (opens new window)

注册时可选择类型:订阅号、服务号、小程序、企业微信

每个邮箱仅能注册一个小程序。

个人类型小程序:无法使用微信支付、无法使用卡包功能

# 小程序文档API

小程序开发文档 (opens new window)

# 微信开放社区

微信开发社区 (opens new window)

# 目录说明

默认目录

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 是否压缩

}

app.json 全局配置

全局配置API (opens new window)

wxml 相关介绍

wxmlAPI (opens new window)

<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)
  }
})
...

1
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 相关介绍

wxssAPI (opens new window)

尺寸单位:rpx,根据屏幕宽度自适应。

引入外部wxss:@import ’...‘

js相关介绍

WXS(WeiXin Script)是小程序的一套脚本语言

wxsAPI (opens new window)

绑定点击事件

<button bindtap=“onTapHandler”>点我+1</button>
<view>{{count}}</view>
1
2
Page({
  data: {
    count: 0
  },
  onTapHandler: function() {
  	this.setData({
  		count: this.data.count++
  	})
  }
})
1
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'
})
1
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"
    }]
  }
1
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>

1
2
3
4
5
6
7
8
9

# 自定义组件

创建组件

创建目录 components > 组件目录名称 > 右键 新建Component

# 引入组件

在page的json文件中:

{
  "usingComponents": {
    "x-playlist":"/components/playlist/playlist"
  }
}
1
2
3
4
5

在page的wxml中:

<x-playlist> </x-playlist>
1

页面引入组件以及组件内部在引用子组件的方法是一样的,同样需要设置json文件。

# 组件传值

父组件中:在引入组件的时候自定义属性名称,并把数据传入子组件

<!-- 参数:playlist 自定义名称,传入组件的数据 -->
<x-playlist playlist="{{传入的数据}}"></x-playlist>
1
2

子组件中: 子组件的js文件:

  /**
   * 组件的属性列表
   */
  properties: {
    playlist:{ // 接收父组件传输的数据
      type: Object // 数据类型
    }
   },

 //子组件的wxml文件可直接引入数据{{playlist}}
1
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>

1
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'
1
2

# 云函数的使用

cloudfunctions目录 右键 新建 Node.js 云函数 > 输入目录名 getPlaylist

在云函数中向第三方服务器发送请求要依赖第三方库

安装依赖包

云函数目录 getPlaylist 右键 在终端打开 打开命令行 输入命令:

npm install --save request
npm install --save request-promise
1
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)
}
1
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 // 插入多少条数据
}
1
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() // 必需返回
}
1
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

API (opens new window)

{
  "triggers": [
    {
      "name": "myTriggers",
      "type": "timer",
      "config":"0 0 10,14,16,20 * * * *" //表示每天的10点、14点、16点、20点触发一次
    }
  ]
}
1
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() // 停止下拉刷新动画
1
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
1
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)
})
1
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) // 异步

1
2
3
4
5
6
7
8

# api设置title

wx.setNavigationBarTitle({
      title: '',
})
1
2
3

# 背景播放音

BackgroundAudioManager (opens new window) 全局唯一的背景音频管理器

// 需要在app.json配置,才能使用后台音乐播放的能力

"requiredBackgroundModes": ["audio", "location"]
1
2
3
// 获取全局唯一的背景音频管理器
const backgroundAudioManager = wx.getBackgroundAudioManager()


backgroundAudioManager.src = 音频链接
backgroundAudioManager.title = 音频标题

1
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 // 显示区域的竖直滚动位置
})
1
2
3
4
5
6
7

# 组件内的方法

Component(Object object) (opens new window)

# 组件生命周期

lifetimes (opens new window)

// 生命周期
lifetimes: {
    ready() { // 在组件在视图层布局完成后执行
   	 ...
    }
},
1
2
3
4
5
6
# 组件所在页面的生命周期
Component({
  pageLifetimes: {
    show: function() {
      // 页面被展示
    },
    hide: function() {
      // 页面被隐藏
    },
    resize: function(size) {
      // 页面尺寸变化
    }
  }
})
1
2
3
4
5
6
7
8
9
10
11
12
13

# 组件对数据的监听

observers (opens new window)

observers: { // 对数据的监听(数据初次加载完成也会执行)
    监听的数据对象(newData){
      console.log(newData)
    }
},
1
2
3
4
5

# 子组件自定义事件传递给父组件

子组件js:
// 触发自定义事件 向父组件传值, 参数x(可选,传递给父组件的参数,可以是对象或其他)
this.triggerEvent('自定义事件名', 参数x)


父组件wxml:
<子组件标签 bind:自定义事件名="执行的事件" />

父组件js:
执行的事件(event) {
	console.log(event.detil.参数)
}
1
2
3
4
5
6
7
8
9
10
11
12

# 父组件自定义事件传递给子组件

父组件wxml:
<子组件标签 class="子组件类名">

父组件JS:
// 选择组件,并传入事件和参数
this.selectComponent('.子组件类名').自定义事件名(传入参数)

子组件js:
methods: {
	自定义事件名(参数x){
		console.log(参数x)
	}
}

1
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)  // 接收父组件传入的值
	}
}

1
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) //手机信息
	}
})
1
2
3
4
5

# 滚动组件

scroll-view (opens new window)

<scroll-view scroll-y scroll-top="{{scrollTop}}" scroll-with-animation="true">
</scroll-view>
1
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')
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23

# 消息提示框

showToast (opens new window)

wx.showToast({
  title: '成功',
  icon: 'success', //图标: success 成功、loading 加载中、none 无
  duration: 2000
})
1
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名称进行修改。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17

方法二:

消除样式隔离 (opens new window)

组件内:
Component({
  options: {
    styleIsolation: 'apply-shared'
  }
})
1
2
3
4
5
6

# 组件插槽slot

单个插槽

父组件调用传入插槽内容:
<组件标签>
    <view>
      <view>插槽内容</view>
      <view>插槽内容</view>
    </view>
</组件标签>

 组件内部定义slot标签:
 <view>
    <!-- slot插槽 -->
    <slot></slot>
</view>
1
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>
1
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

# 判断用户授权

授权 (opens new window)

// 判断用户是否授权
    wx.getSetting({
      success: (res) => { // 这里使用箭头函数可改变内部this指向为外部的this
        console.log(res)
        if (res.authSetting['scope.userInfo']) { // 已授权
         wx.getUserInfo({ // 获取用户信息
           success(res) {
             console.log(res)
           }
         })
        } else { // 未授权

        }
      }
    })
1
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')
      }
    }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23

# 原生组件

原生组件 (opens new window)


auto-focus 自动获取焦点

<textarea
    class="content"
    placeholder="分享新鲜事..."
    maxlength="140"
    auto-focus
    bindinput="onInput"
    bindfocus="onFocus"
    bindblur="onBlur"
  ></textarea>
1
2
3
4
5
6
7
8
9
10
11
12

# 选择上传图片

上传图片 (opens new window)

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)
      },
    })
1
2
3
4
5
6
7
8
9

# 图片裁剪

图片裁剪 (opens new window)

<!-- mode 图片裁剪 aspectFill 保证短边完整显示 -->
<image class="image" src="{{item}}" mode="aspectFill"></image>
1
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
    })
  },

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19

# 全屏预览图片(点击图片放大预览)

全屏预览图片 (opens new window)

// 全屏预览图片
  onPreviewImage(event) {
    wx.previewImage({
      urls: this.data.images, // 图片地址列表
      current: event.target.dataset.imgsrc // 当前预览图片地址
    })
  },
1
2
3
4
5
6
7

# 文件上传云存储(发布博客例子)

文件上传云存储 (opens new window)

 //  结合'发布'的例子:
  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'
      })
    })
  },
1
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
}
1
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 (中国标准时间)'))
1
2
3
4

# 阻止事件冒泡

bind 和 catch 都可以绑定事件,它们的区别是 bind 有事件冒泡,而 catch 没有

# 返回上一个页面并执行方法

API (opens new window)

 // 返回博客页面,并刷新
 wx.navigateBack()
 const pages = getCurrentPages() // 获取当前页面栈
 const prevPage = pages[pages.length - 2]  // 取到上一个页面
 prevPage.onPullDownRefresh() // 执行上一个页面的方法 onPullDownRefresh
1
2
3
4
5

# 图片懒加载

API (opens new window)

给image标签设置 lazy-load 为 true
<image class="img" src="{{item}}" lazy-load="true"></image>

.img {
  background: #eee;
}
1
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
    })

  })
1
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)
    })
1
2
3
4
5

# 云数据库权限管理

注意:云控制台和服务端(云函数)始终有所有数据读写权限,

但权限的管理仅对小程序端发起的请求有效。

  • 仅创建者可写,所有人可读 (适合于文章)

  • 仅创建者可读写 (适用于私密内容)

  • 仅管理端可写,所有人可读(适用于商品信息)

  • 仅管理端可读写(适用于后台敏感数据)

# 数据库中1对N关系的三种设计方式

# 第一种:N的数量较少 几十个以内

1 条记录存储 N 个子数据

​ 如一条博客中,最多有9张图片,这9张图片可和其他数据放在一个记录中。

[
	{
		id:...
		img:[
		'...', '...', '...', '...', '...', '...', '...', '...', '...'
		]
	}
]
1
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:'...' ...},
...
]


1
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'
},
...
]

1
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>
1
2
3
4
5

2、需要到微信公众平台做相应的设置:

微信公众平台 > 功能 > 模板消息 > 添加模板 > 选择相应的模板> 添加成功后会有一个模板ID

3、新建一个云函数,用于云调用。在该云函数下新建配置文件:config.json ,用于配置权限

config.json :

{
  "permissions": {
    "openapi": [
      "templateMessage.send"
    ]
  }
}
1
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
}
1
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)
        })
1
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
    }
  })
1
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>
1
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: '' // 自定义图片,不支持云存储的图片
    }
  }
1
2
3
4
5
6
7
8
9
10
11

# 不同场景获取用户信息的方式

# 场景一:只想在界面上显示自己的昵称和头像

以组件的方式:根据type类型获取不同用户数据

该方式不需要授权,只能用于在wxml显示自己的信息

open-data (opens new window)

<open-data type="userAvatarUrl"></open-data>
<open-data type="userNickName"></open-data>
...
1
2
3
# 场景二:在JS中获取用户信息

该方式要在用户授权以后才能获取用户信息

wx.getUserInfo (opens new window)

wx.getUserInfo({
      success: (res) => {
        console.log(res)
      }
    })
1
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')
      }
    }

1
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,
  }
1
2
3
4
5
6
7
8
9
普通按钮
<button bindtap="getOpenid">获取openid</button>

getOpenid() {
	wx.cloud.callFunction({
		name: 'login'
	}).then((res) => {
		console.log(res)
	})
}
1
2
3
4
5
6
7
8
9
10

openid 在小程序和公众号下是不一样的

unionid 在小程序和公众号下都是一样的

# 《我的》页面

json文件

"navigationBarTitleText": "我的",
  "disableScroll": true  // 使页面无法滚动
1
2

# 导航页面链接跳转

navigator (opens new window)

# 背景图片

wxss背景图片不支持本地相对路径的图片,只支持网络图片和base64图片

建议使用base64图片,图片文件最好不要太大。

# 每个页面都有的page标签

page {
  background-color: #f1f1f1;
}
1
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到本地
      }
    })
  }
1
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
    })

  },
1
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,
      })
    }

  },
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

# 我的发现

代码分别演示了从云函数和小程序端获取数据,从小程序端获取数据享有权限管理的能力,不需要传openid。

# 小程序码

获取小程序码 (opens new window)

本项目演示使用接口 B:适用于需要的码数量极多的业务场景 (opens new window) 云调用 的方式。

步骤:

  • 创建云函数 gteQRCode

  • gteQRCode云函数下创建config.json配置权限,代码如下:

{
  "permissions":{
    "openapi":[
      "wxacode.getUnlimited"
    ]
  }
}
1
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
}
1
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) // 获取到小程序码进入的参数
}
1
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()
              }
            }
          })
        })
      }
    })
  },
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23

# 性能优化

官网文档优化建议 (opens new window)

使用开发者工具的调试器,Audits进行评分,然后根据提示针对项目进行优化。

# 场景值scene的作用与应用场景

场景值 (opens new window)

场景值用来描述用户进入小程序的路径。完整场景值的含义请查看场景值列表 (opens new window)。

可根据不同场景进入实现不同业务处理,比如一个点餐小程序,店家内贴了小程序码,用户通过扫码进入,可立即进入点餐页面,等等

在app.js中的onLaunch(options) 、onShow(options),options包含scene场景值

开发者工具中,切后台,可模拟进入场景。

# 小程序的"SEO"---页面收录sitemap

在app.js的同级目录下有sitemap.json文件,用于配置收录规则

stiemap配置 (opens new window)

作用:

使小程序搜索可根据小程序的内容进行搜索到

使用方法:

1、在微信公众平台,小程序信息 > 页面收录设置 > 打开 (默认是已开启)

2、打开sitemap.json文件,配置收录规则

{
  "desc": "关于本文件的更多信息,请参考文档 https://developers.weixin.qq.com/miniprogram/dev/framework/sitemap.html",
  "rules": [{ // 收录规则
  "action": "allow",// 是否被收录,allow允许被收录,disallow不允许
  "page": "*" // *星号表示所有页面都被收录
  }]
}
1
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页面被收录外,其他页面不被收录
1
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

1
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}`))
})
1
2
3
4
5
6
7
8
9
10
11

终端:

# node启动项目
node app.js

# 访问:http://localhost:3000
1
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
1
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
1
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)
1
2
3
4
5
6
7
8
9
10
11

管理系统后端,安装

// 解决跨域问题的koa包
npm i koa2-cors
1
2

app.js

//处理跨域
app.use(cors({
  origin: ['http://localhost:9528'], // 允许访问本服务的域
  credentials: true
}))
1
2
3
4
5

# 云数据库的增删改查接口

数据库查询记录 (opens new window)

# 后端获取前端post请求传来的数据

get请求可以直接通过ctx.request.query获取,但是post请求需要安装koa-body

npm i koa-body
1

app.js

const koaBody = require('koa-body') // 对post请求前端传来的数据的获取,需要此依赖

// 接收post参数解析
app.use(koaBody({
  multipart: true
}))

1
2
3
4
5
6
7

接口.js

router.post('/updatePlaylist', async (ctx, next) => {

  const params = ctx.request.body // post请求获取前端传来的数据,需安装和配置koa-body

})
1
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)

})
1
2
3
4
5
6
7

但获取到的数据为fileid,并不能用于显示图片,需要通过微信HTTP API获取云存储的接口来获取图片地址

获取云存储 (opens new window)

# 后端上传图片到云存储

文件上传 (opens new window)

编辑 (opens new window)
#小程序
上次更新: 2022/08/18, 06:19:14
TypeScript笔记
JS设计模式总结笔记

← TypeScript笔记 JS设计模式总结笔记→

最近更新
01
Git修改分支名
08-11
02
CSS给table的tbody添加滚动条
06-29
03
我做了一个手写春联小网页,祝大家虎年暴富 原创
01-28
更多文章>

Gitalking ...

Theme by Vdoing | Copyright © 2019-2025 Evan Xu | MIT License
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式