VDone Demo VDone Demo
Home
  • Articles

    • JavaScript
  • Study Notes

    • JavaScript Tutorial
    • Professional JavaScript
    • ES6 Tutorial
    • Vue
    • React
    • TypeScript: Build Axios from Scratch
    • Git
    • TypeScript
    • JS Design Patterns
  • HTML
  • CSS
  • Technical Docs
  • GitHub Tips
  • Node.js
  • Blog Setup
  • Learning
  • Interviews
  • Miscellaneous
  • Practical Tips
  • Friends
About
Bookmarks
  • Categories
  • Tags
  • Archives
GitHub (opens new window)

Nikolay Tuzov

Backend Developer
Home
  • Articles

    • JavaScript
  • Study Notes

    • JavaScript Tutorial
    • Professional JavaScript
    • ES6 Tutorial
    • Vue
    • React
    • TypeScript: Build Axios from Scratch
    • Git
    • TypeScript
    • JS Design Patterns
  • HTML
  • CSS
  • Technical Docs
  • GitHub Tips
  • Node.js
  • Blog Setup
  • Learning
  • Interviews
  • Miscellaneous
  • Practical Tips
  • Friends
About
Bookmarks
  • Categories
  • Tags
  • Archives
GitHub (opens new window)
  • JavaScript文章

  • 学习笔记

    • JavaScript Tutorial - Notes
    • Professional JavaScript for Web Developers - Notes
    • ES6 Tutorial - Notes
    • Vue - Notes
    • React - Notes
    • TypeScript - Implementing axios from Scratch
    • Git - Study Notes
    • TypeScript Notes
    • Mini Program Notes
      • Fundamentals Course
        • WeChat Official Platform
        • Mini Program API Documentation
        • WeChat Open Community
        • Directory Structure
        • File Details
        • Preventing Event Bubbling
        • Third-Party Libraries
        • Cloud Development
        • Three Core Capabilities of Cloud Development
        • Cloud Functions
        • Cloud Database
        • Cloud Storage
        • Cloud Database Capabilities
        • Data Types
        • Operating the Cloud Database
        • Cloud Database Permission Management
        • Operating the Cloud Database
      • Practical Course
        • Serverless
        • Advantages of Cloud Development
        • Cloud Development Capabilities
        • appID
        • Default Cloud Development Project Directory Structure
        • Cloud Development Environments
        • Pre-Development Setup
        • project.config.json File Details
        • app.json
        • Code Standards
        • "Music" Page Development
        • Custom Components
        • Importing a Component
        • Component Data Passing
        • wx:key Usage
        • async/await Syntax
        • Using Cloud Functions
        • Database Operations
        • Querying the Database
        • Cloud Function Debugging
        • Scheduled Cloud Function Triggers
        • Configuring Cloud Function Timeout
        • Pull-Up Loading and Pull-Down Refresh
        • Cloud Function Routing with tcb-router
        • Local Storage (Cache)
        • API to Set Title
        • Background Audio
        • createSelectorQuery for Querying Node Information
        • Component Methods
        • Component Lifecycle
        • Page Lifecycle of a Component's Host Page
        • Component Data Observers
        • Child Component Custom Event to Parent Component
        • Parent Component Custom Event to Child Component
        • Sibling Component Event and Data Passing
        • Getting Device Information
        • Scroll Component
        • Global Properties and Methods (similar to Vuex)
        • Toast Message
      • "Discover" Page
        • Using External Styles in Components
        • Component Slots
        • Checking User Authorization
        • Button's Open Capability (Getting User Info) 1
        • Native Components
        • Selecting and Uploading Images
        • Image Cropping
        • Getting Custom data-* Attributes (Deleting Images)
        • Full-Screen Image Preview (Click to Enlarge)
        • Uploading Files to Cloud Storage (Blog Publishing Example)
        • JS Modularization (Date Formatting)
        • Preventing Event Bubbling
        • Navigate Back and Execute a Method
        • Image Lazy Loading
        • Fuzzy Search
        • Improving Fuzzy Search Performance (Adding Indexes, significant for large datasets)
        • Mini Program Client-Side Database Calls
        • Cloud Database Permission Management
        • Three Design Approaches for 1-to-N Relationships in Databases
        • Approach 1: N is a small number (within a few dozen)
        • Approach 2: N is a moderate number (dozens to hundreds)
        • Approach 3: N is very large (hundreds, thousands, or more)
        • Cloud Calls
        • Template Message Push
        • Cloud Function Multi-Collection Database Query
        • Share Functionality
        • Different Ways to Get User Info in Different Scenarios
        • Scenario 1: Just want to display your own nickname and avatar on the UI
        • Scenario 2: Getting user info in JavaScript
        • Scenario 3: Getting the openId
      • "Profile" Page
        • Navigation Page Link
        • Background Images
        • The page Tag Present on Every Page
        • Play History and Local Storage
        • My Posts
        • Mini Program QR Code
        • Detecting QR Code Entry and Getting Parameters
        • Version Update Detection
        • Performance Optimization
        • Scene Values and Their Applications
        • Mini Program "SEO" -- Page Indexing with sitemap
        • Mini Program Launch Review Process
      • Admin Panel
        • Architecture Diagram
        • Building the Admin Frontend with vue-admin-template
        • Building the Admin Backend with Koa2
        • Caching and Refreshing the access_token
        • Triggering Cloud Functions via HTTP API from the Backend
        • CORS Issues and Backend Solutions
        • Cloud Database CRUD APIs
        • Receiving POST Request Data on the Backend
        • Getting Cloud Storage Images from the Backend
        • Uploading Images to Cloud Storage from the Backend
    • JS Design Patterns Summary Notes
  • 前端
  • 学习笔记
xugaoyi
2019-12-25
Contents

Mini Program Notes

# Mini Program Notes

# Fundamentals Course

# WeChat Official Platform

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

When registering, you can choose from these types: Subscription Account, Service Account, Mini Program, Enterprise WeChat

Each email can only register one mini program.

Personal-type mini programs: cannot use WeChat Pay or card & coupon features

# Mini Program API Documentation

Mini Program Development Docs (opens new window)

# WeChat Open Community

WeChat Developer Community (opens new window)

# Directory Structure

Default Directory

pages-----------------------Pages

index  ----------------- Home page folder

	index.js ------------Home page JS

	index.json---------Home page config

	index.wxml-------Home page HTML

	index.wxss--------Home page CSS

utils------------------------Utilities

app.js ----------------------Main project JS

app.json-------------------Global config (page routes, header/footer navigation config, etc.)

app.wxss -----------------Main project styles CSS

project.config.json ----Project config

Code Structure

.json : Config files, stored in JSON format

		 There are three types of config in a project: project config (project.config.json), global config (app.json), page config (index.json)

.wxml: Equivalent to HTML files

.wxss: Equivalent to CSS

.js : JavaScript

# File Details

project.config.json project config -- key fields

setting:{

urlCheck -- whether to check for secure domains

es6 -- whether to transpile ES6 to ES5

postcss -- whether to auto-prefix CSS styles

minified -- whether to minify

}

app.json global config

Global Config API (opens new window)

wxml introduction

wxml API (opens new window)

<view>{{motto}}</view>

Loop rendering
<view wx:for="{{list}}" wx:key="{{index}}">
      {{index}} {{item}}
</view>

Renaming for-loop item and index
<block wx:for="{{list}}" wx:for-item="data" wx:for-index="inx">
	{{inx}} {{data}}
</block>

Conditional rendering (similar to Vue's v-if / v-else)
<view wx:if="{{isLogin}}">Logged in</view>
<view wx:else>Please log in</view>

Conditional display (similar to Vue's v-show)
<view hidden="{{isLogin}}">Display content</view>

Binding click events
<button bindtap="tapName">Button</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 introduction

wxss API (opens new window)

Size unit: rpx -- adapts automatically to screen width.

Importing external wxss: @import '...'

JS introduction

WXS (WeiXin Script) is a scripting language for mini programs

wxs API (opens new window)

Binding click events

<button bindtap="onTapHandler">Click me +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
# Preventing Event Bubbling

Change the binding method from bindtap to catchtap.

# Third-Party Libraries

WeUI

WeUI is a set of base style libraries consistent with WeChat's native visual experience

iView Weapp

A high-quality UI component library for WeChat mini programs

Vant Weapp

A lightweight, reliable mini program UI component library

# Cloud Development

Traditional mini program development model

Client -----> Server (backend code, database) ------> Operations (DB maintenance, file storage, content acceleration, network protection, container services, load balancing, security hardening, etc.)

Mini program cloud development model

Client -----> Cloud Development (cloud functions, cloud database, cloud storage)

Traditional Development vs Cloud Development

Low development efficiency Serverless

High operation costs Developers focus more on business logic

Serverless development is the future trend

# Three Core Capabilities of Cloud Development
# Cloud Functions

(Equivalent to backend APIs in traditional development)

Get appid, get openid, generate share images, call Tencent Cloud SDK ...

# Cloud Database

CRUD operations on data ...

# Cloud Storage

Manage files, upload files, download files, share files ...

Each mini program account can create two environments for free. Recommendation: development environment and production environment

# Cloud Database Capabilities

Cloud development provides a JSON database with 2GB of free storage.

# Data Types

String

Number

Object

Array

Boolean

GeoPoint

Date (precise to milliseconds, client-side time)

Null

# Operating the Cloud Database

Mini program control (read/write subject to permission restrictions)

Cloud function control (has full read/write database permissions)

Console control (has full read/write database permissions)

# Cloud Database Permission Management

Only the creator can write, everyone can read (suitable for articles)

Only the creator can read/write (suitable for private content)

Only admins can write, everyone can read (suitable for product information)

Only admins can read/write (suitable for sensitive backend data)

# Operating the Cloud Database

// Initialize the database
const db = wx.cloud.database() // Initialize database on the mini program side; in cloud functions, no need for the wx. prefix

// Switch environments (development/production)
const testDB = wx.cloud.database({
	env: 'test'
})
1
2
3
4
5
6
7

# Practical Course

# Serverless

Concept: Functions as a Service -- when you need backend services, you don't need to worry about IP addresses or domain names; you just call a function like any ordinary function.

# Advantages of Cloud Development

Rapid deployment, focus on core business, independently develop a complete WeChat mini program, no need to learn new languages -- just JavaScript, no operations needed -- saves costs, data security

# Cloud Development Capabilities

Cloud Functions: code that runs in the cloud, with built-in authentication via WeChat's proprietary protocol (Think: equivalent to the backend)

Cloud Database: a JSON database that can be operated from both the mini program client and cloud functions

Cloud Storage: store files in the cloud, with visual management in the cloud console

Cloud Calls: use mini program open APIs from cloud functions without authentication (e.g., push messages to users)

HTTP API: developers can access cloud resources from existing servers via HTTP API, enabling interoperability with cloud development (Purpose: for mini programs originally developed in the traditional model, enabling interoperability with cloud development)

# appID

A unique ID for each mini program

# Default Cloud Development Project Directory Structure

cloudfunctions ----------------------------Cloud Functions

callback ---------------------------------- Callback functions

	config.json ---------------------------

	index.js --------------------------------

	package.json ------------------------

echo ----------------------------------------

login ----------------------------------------

openapi -----------------------------------

miniprogram ------------------------------- Mini Program

images ------------------------------------- Images

pages --------------------------------------- Pages

style ----------------------------------------- Styles

app.js --------------------------------------- Main project JS

app.json ----------------------------------- Global config

app.wxss ---------------------------------- Main project styles

sitemap.json -----------------------------  (Mini program SEO related)

project.config.json ----------------------- Project config

# Cloud Development Environments

Cloud development allows creating two environments. Recommendation: one for development, one for production

# Pre-Development Setup

Developer Tools > Details (top right) > Local Settings > Debug Base Library -- set to the latest version

app.js > wx.cloud.init > env -- set the environment ID

# project.config.json File Details

miniprogramRoot -- mini program frontend code directory

cloudfunctionRoot -- cloud function code directory

# app.json

pages -- set up pages; after setting, the corresponding directories and files will be auto-generated in the pages directory

Setting up bottom tab bar buttons:

"tabBar": {
    "color": "#474747", // Text color
    "selectedColor": "#d43c43", // Selected text color
    "list": [{ // Button list, 2-5 items
      "pagePath": "pages/playlist/playlist", // Page associated with the button
      "text": "Music", // Text
      "iconPath": "images/music.png", // Icon path
      "selectedIconPath": "images/music-actived.png" // Selected icon path
    },
    {
      "pagePath": "pages/blog/blog",
      "text": "Discover",
      "iconPath": "images/blog.png",
      "selectedIconPath": "images/blog-actived.png"
    },
    {
      "pagePath": "pages/profile/profile",
      "text": "Profile",
      "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

Icons from https://www.iconfont.cn (opens new window)

Alibaba's icon library, including vector icons, icon fonts, fonts, etc.

# Code Standards

Many companies follow this code style guide: https://github.com/airbnb/javascript (opens new window)

# "Music" Page Development

 <!-- Carousel component. Parameters: indicator-dots (dots), autoplay, interval, duration (animation duration) -->
<swiper indicator-dots="true" circular="true" interval="3000" duration="500">
  <block wx:for="{{swiperImgUrls}}" wx:key="{{index}}"> <!-- Empty node -->
    <swiper-item>
      <image src="{{item.url}}" mode="widthFix" class="img"></image>
    </swiper-item>
  </block>
</swiper>

1
2
3
4
5
6
7
8
9

# Custom Components

Creating a Component

Create directory: components > component folder name > right-click New Component

# Importing a Component

In the page's JSON file:

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

In the page's WXML:

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

Importing components in a page and importing sub-components within a component use the same method -- both require setting the JSON file.

# Component Data Passing

Parent component: when importing a component, define a custom attribute name and pass data into the child component

<!-- Parameter: playlist is a custom name, the data passed to the component -->
<x-playlist playlist="{{dataToPass}}"></x-playlist>
1
2

Child component: Child component's JS file:

  /**
   * Component property list
   */
  properties: {
    playlist:{ // Receive data passed from the parent component
      type: Object // Data type
    }
   },

 // The child component's WXML can directly use the data: {{playlist}}
1
2
3
4
5
6
7
8
9
10

# wx:key Usage

The key value should not use index, because when data changes cause DOM structure changes, index-based references won't update accordingly.

Use a unique value within each data item, such as an id

<block wx:for="{{swiperImgUrls}}" wx:key="url"> Here url doesn't need double braces; index would require {{}}
    <view>
      <image src="{{item.url}}" mode="widthFix" class="img"></image>
    </view>
</block>

<view class="playlist-container">
  <block wx:for="{{playlist}}" wx:key="_id">
    <!-- Parameter: playlist is a custom name, the data passed to the component -->
    <x-playlist playlist="{{item}}"></x-playlist>
  </block>
</view>

1
2
3
4
5
6
7
8
9
10
11
12
13

# async/await Syntax

Currently, in cloud functions, since the minimum Node version is 8.9, async/await syntax is natively supported. However, on the mini program client side, it's different. In the WeChat developer tools and on Android devices (QQ browser's X5 kernel), async/await is natively supported, but on iOS devices with older versions, it's not. An additional file needs to be imported.

You can import this runtime.js (opens new window) file into files that use async/await.

// Note: it must be named regeneratorRuntime
import regeneratorRuntime from '../../utils/runtime.js'
1
2

# Using Cloud Functions

In the cloudfunctions directory, right-click New Node.js Cloud Function > enter directory name getPlaylist

To make requests from cloud functions to third-party servers, you need a third-party library

Installing dependencies

Right-click on the cloud function directory getPlaylist > Open in Terminal > enter commands:

npm install --save request
npm install --save request-promise
1
2

GitHub request-promise: https://github.com/request/request-promise (opens new window)

Then write the corresponding code

// Cloud function entry file
const cloud = require('wx-server-sdk')

cloud.init()

const rp = require('request-promise') // Requires installing dependencies

const URL = 'http://musicapi.xiecheng.live/personalized'

// Cloud function entry function
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

After writing the code, right-click on the cloud function directory getPlaylist > Upload and Deploy: Install Dependencies in Cloud (don't upload node_modules) to upload and deploy the code to the cloud. Wait for the upload to succeed, then open the Cloud Development Console to see the uploaded cloud function, where you can also test it.

# Database Operations

Database > Create Collection > playlist

// Cloud function entry file
const cloud = require('wx-server-sdk')

cloud.init()

const db = cloud.database() // Initialize database; on the mini program side, add wx. prefix

const rp = require('request-promise') // Requires installing dependencies

const URL = 'http://musicapi.xiecheng.live/personalized' // Third-party server URL (the instructor deployed data from NetEase Cloud Music on his server, updated daily)

const playlistCollection = db.collection('playlist') // Get the playlist collection from the database

const MAX_LIMIT = 10 // Define a constant for the maximum number of records per query

// Cloud function entry function
exports.main = async (event, context) => {
  /**
   * Notes:
   * - All database operations are asynchronous and require the await keyword
   * - console.log output can be viewed in the Cloud Console under Cloud Function Testing
   * - There's a per-query limit: cloud functions can fetch up to 100 records, mini program client up to 20
   */

  // const list = await playlistCollection.get() // Get data from the collection (not used directly due to record limits)

  // Breaking through the record limit (to read all data for comparison with third-party server data for deduplication)
  const countResult = await playlistCollection.count() // Get total record count, returns an object
  const total = countResult.total // Get the total count
  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 n records, fetch up to limit records
    tasks.push(promise)
  }
  let list = {
    data: []
  }
  if (tasks.length > 0) {
    list = (await Promise.all(tasks)).reduce((acc, cur) => { // reduce array method for cumulative concatenation
      return {
        data: acc.data.concat(cur.data)
      }
    })
  }

  // Get data from the third-party server
  const playlist = await rp(URL).then((res) => {
    return JSON.parse(res).result
  })

  // Compare database and server data for deduplication (don't re-add data that already exists in the database)
  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])
    }
  }

  // Insert data into the database, one record at a time
  for (let i = 0, len = newData.length; i < len; i++) {
    await playlistCollection.add({ // Add data to the database collection
      data: {
        ...newData[i],
        createTime: db.serverDate(), // db.serverDate() gets the server time
      }
    }).then((res) => { // Data added successfully
      console.log('Data added successfully')
    }).catch((err) => { // Failed
      console.error(err)
    })
  }
  return newData.length // Number of records inserted
}
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
# Querying the Database
// Example of querying the database in a cloud function:

// Cloud function entry file
const cloud = require('wx-server-sdk')

cloud.init()

const TcbRouter = require('tcb-router')
const db = cloud.database() // Initialize database
const blogCollection = db.collection('blog') // Blog database collection

// Cloud function entry function
exports.main = async (event, context) => {
  const app = new TcbRouter({ event }) // Initialize TcbRouter

  app.router('list', async (ctx, next) => {
    // skip: start from which record, limit: how many records to fetch, orderBy(sortField, sortOrder): desc/asc
    ctx.body =  await blogCollection.skip(event.start).limit(event.count)
    .orderBy('createTime', 'desc').get().then((res) => {
      return res.data
    })

  })


  return app.serve() // Must return
}
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
# Cloud Function Debugging

Cloud functions can be tested in the cloud console

After calling a cloud function from the mini program, you can view the cloud function logs

# Scheduled Cloud Function Triggers

If a cloud function needs to be executed on a schedule, i.e., timed triggers, you can use cloud function timed triggers. A cloud function configured with a timed trigger will be automatically triggered at the corresponding time. The function's return value will not be returned to the caller.

Create a config.json file in the cloud function directory

API (opens new window)

{
  "triggers": [
    {
      "name": "myTriggers",
      "type": "timer",
      "config":"0 0 10,14,16,20 * * * *" // Trigger once daily at 10, 14, 16, and 20 o'clock
    }
  ]
}
1
2
3
4
5
6
7
8
9

After editing the trigger, right-click on the cloud function directory > Upload Trigger

# Configuring Cloud Function Timeout

When a cloud function is complex, the default 3-second timeout may not be enough. You can set a more reasonable timeout.

Cloud Development Console > Cloud Functions > Configuration > Timeout

# Pull-Up Loading and Pull-Down Refresh

In the page's JSON file:
"enablePullDownRefresh": true


In the page's JS file, these two functions exist:

 /**
   * Page event handler -- listen for user pull-down action
   */
  onPullDownRefresh: function() {
	 this.setData({
      playlist: []
    })
    this._getPlaylist()
  },

  /**
   * Handler for page pull-up reaching bottom
   */
  onReachBottom: function() {
    this._getPlaylist()
  },



 After pull-down refresh data request completes:
 wx.stopPullDownRefresh() // Stop the pull-down refresh animation
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

# Cloud Function Routing with tcb-router

Each user can only create 50 cloud functions per cloud environment

What if the mini program is very complex and 50 cloud functions aren't enough?

Group similar requests into the same cloud function

tcb-router is a Koa-style cloud function routing library

In simple terms, it lets you group many API endpoints into a single cloud function.

GitHub tcb-router: https://github.com/TencentCloudBase/tcb-router (opens new window)

Koa onion model...

Installation:

Open a terminal in the cloud function directory that uses tcb-router, then run:
npm install --save tcb-router
1
2
// Cloud function index.js
const TcbRouter = require('router'); // Required

exports.main = (event, context) => {
    const app = new TcbRouter({ event }); // Required

    // app.use means this middleware applies to all routes (global middleware). Optional
    app.use(async (ctx, next) => { // This middleware is called by all routes, while route middleware is called individually
        ctx.data = {}; // Data to pass to the mini program client
        ctx.data.openId = event.userInfo.openId // The openId obtained here is distributed to all routes
        await next(); // Execute next middleware
    });

    // Array route: this middleware applies to both user and timer routes
    app.router(['user', 'timer'], async (ctx, next) => {
        ctx.data.company = 'Tencent'; // Data obtained here is distributed to both user and timer routes
        await next(); // Execute next middleware
    });

    // String route: this middleware only applies to the user route
    app.router('user', async (ctx, next) => {
        ctx.data.name = 'heyli';  // Data to pass to the mini program client
        await next(); // Execute next middleware
    }, async (ctx, next) => {
        ctx.data.sex = 'male'; // Data to pass to the mini program client
        await next(); // Execute next middleware
    }, async (ctx) => {
        ctx.data.city = 'Foshan'; // Data to pass to the mini program client
        // ctx.body returns data to the mini program client
        ctx.body = { code: 0, data: ctx.data};  // Data to pass to the mini program client
    });

    // String route: this middleware only applies to the timer route
    app.router('timer', async (ctx, next) => {
        ctx.data.name = 'flytam';
        await next(); // Execute next middleware
    }, async (ctx, next) => {
        ctx.data.sex = await new Promise(resolve => {
        // Wait 500ms, then execute next middleware
        setTimeout(() => {
            resolve('male');
        }, 500);
        });
        await next(); // Execute next middleware
    }, async (ctx)=>  {
        ctx.data.city = 'Taishan';

        // ctx.body returns data to the mini program client
        ctx.body = { code: 0, data: ctx.data };
    });

    return app.serve(); // Required

}


Mini program client:

// Call the cloud function named router, with the route name user
wx.cloud.callFunction({
    // Cloud function name to call
    name: "router",
    // Parameters passed to the cloud function
    data: {
        $url: "user", // Route path to call, pass exact path or wildcard *
        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

The tcb-router code above follows the onion model: entering each middleware from top to bottom, then exiting each middleware from bottom to top.

# Local Storage (Cache)

// Store:
wx.setStorageSync(key, data) // Synchronous storage (wait for storage to complete before continuing)
wx.setStorage(key, data) // Asynchronous storage (continues to next code even if storage hasn't completed)

// Read:
wx.getStorageSync(key) // Synchronous (wait for data to be read before continuing)
wx.setStorage(key) // Asynchronous

1
2
3
4
5
6
7
8

# API to Set Title

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

# Background Audio

BackgroundAudioManager (opens new window) -- the globally unique background audio manager

// Must be configured in app.json to enable background music playback

"requiredBackgroundModes": ["audio", "location"]
1
2
3
// Get the globally unique background audio manager
const backgroundAudioManager = wx.getBackgroundAudioManager()


backgroundAudioManager.src = audioURL
backgroundAudioManager.title = audioTitle

1
2
3
4
5
6
7

# createSelectorQuery for Querying Node Information

createSelectorQuery (opens new window) -- mini program method for querying nodes and other operations

const query = wx.createSelectorQuery()
query.select('#the-id').boundingClientRect() // Node layout information
query.selectViewport().scrollOffset()
query.exec(function(res){
  res[0].top       // Top boundary coordinate of #the-id node
  res[1].scrollTop // Vertical scroll position of the viewport
})
1
2
3
4
5
6
7

# Component Methods

Component(Object object) (opens new window)

# Component Lifecycle

lifetimes (opens new window)

// Lifecycle
lifetimes: {
    ready() { // Executes after the component layout is complete in the view layer
   	 ...
    }
},
1
2
3
4
5
6
# Page Lifecycle of a Component's Host Page
Component({
  pageLifetimes: {
    show: function() {
      // Page is shown
    },
    hide: function() {
      // Page is hidden
    },
    resize: function(size) {
      // Page size changed
    }
  }
})
1
2
3
4
5
6
7
8
9
10
11
12
13

# Component Data Observers

observers (opens new window)

observers: { // Data observers (also fires on initial data load)
    observedData(newData){
      console.log(newData)
    }
},
1
2
3
4
5

# Child Component Custom Event to Parent Component

Child component JS:
// Trigger a custom event to pass data to the parent. Parameter x (optional, data passed to the parent -- can be an object or other type)
this.triggerEvent('customEventName', parameterX)


Parent component WXML:
<child-component-tag bind:customEventName="handler" />

Parent component JS:
handler(event) {
	console.log(event.detail.parameter)
}
1
2
3
4
5
6
7
8
9
10
11
12

# Parent Component Custom Event to Child Component

Parent component WXML:
<child-component-tag class="child-class-name">

Parent component JS:
// Select the component and pass event and parameters
this.selectComponent('.child-class-name').customEventName(parameters)

Child component JS:
methods: {
	customEventName(parameterX){
		console.log(parameterX)
	}
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14

# Sibling Component Event and Data Passing

Example: Child component 1 passes parameters to Child component 2

Parent component WXML:
<child-tag-1 bind:customEvent1="handler">
<child-tag-2 class="child2-class-name">

Parent component JS:
handler(event) {
	this.selectComponent('.child2-class-name').customEvent2(event.detail.parameterX) // Pass value to child component 2
}

Child component 1 JS:
// Trigger custom event to pass data to parent. Parameter x (optional)
this.triggerEvent('customEvent1', parameterX)



Child component 2 JS:
methods: {
	customEvent2(parameterX){
		console.log(parameterX)  // Receive value from parent
	}
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24

# Getting Device Information

wx.getSystemInfo(Object object) (opens new window)

wx.getSystemInfo({
	success(res){
		console.log(res) // Device information
	}
})
1
2
3
4
5

# Scroll Component

scroll-view (opens new window)

<scroll-view scroll-y scroll-top="{{scrollTop}}" scroll-with-animation="true">
</scroll-view>
1
2

# Global Properties and Methods (similar to Vuex)

In app.js:

onLaunch: function () {
	this.globalData = { // Set global properties and methods
		test: 0
	}
},
setGlobalData(dataItem, val) { // Set global property
	this.globalData[dataItem] = val
},
getGlobalData(dataItem) { // Get global property
	return this.globalData[dataItem]
}


In the page JS where you need to use it:
const app = getApp() // Call the app method at the top

// Set global property
app.setGlobalData('test', 1)

// Get global property
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

# Toast Message

showToast (opens new window)

wx.showToast({
  title: 'Success',
  icon: 'success', // Icon: success, loading, none
  duration: 2000
})
1
2
3
4
5

# "Discover" Page

# Using External Styles in Components

Components inside the components directory cannot directly use external styles. You can use the following methods:

Method 1:

Parent component WXML:
<!-- iconfont and icon-sousuo are style names passed into the component. iconfont(custom name)="iconfont(style name defined in external stylesheet)" -->
<x-search iconfont="iconfont" icon-sousuo="icon-sousuo"/>


Child component JS:
// External component styles
  externalClasses: [
    'iconfont', // Corresponds to the name before the = sign above
    'icon-sousuo'
  ],

 Child component WXML: now external styles are available
 <i class="iconfont icon-sousuo" />


  Note: if you want to further modify the style within the component, do not reference the external class name. Instead, use a different class name for modifications.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17

Method 2:

Removing style isolation (opens new window)

Inside the component:
Component({
  options: {
    styleIsolation: 'apply-shared'
  }
})
1
2
3
4
5
6

# Component Slots

Single Slot

Parent component passes slot content:
<component-tag>
    <view>
      <view>Slot content</view>
      <view>Slot content</view>
    </view>
</component-tag>

 Component defines the slot tag:
 <view>
    <!-- slot -->
    <slot></slot>
</view>
1
2
3
4
5
6
7
8
9
10
11
12
13

Multiple Slots


Parent component passes slot content:
<component-tag>
    <view slot="slot2">
      <view>Slot 1 content</view>
      <view>Slot 1 content</view>
    </view>

     <view slot="slot1">
      <view>Slot 2 content</view>
      <view>Slot 2 content</view>
    </view>
</component-tag>

Component JS:
options: { // Settings
    multipleSlots: true // Enable multiple slots
},


Component defines named slot tags:
<view>
    <!-- Named slots -->
    <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

# Checking User Authorization

Authorization (opens new window)

// Check if user is authorized
    wx.getSetting({
      success: (res) => { // Arrow function changes inner this to outer this
        console.log(res)
        if (res.authSetting['scope.userInfo']) { // Authorized
         wx.getUserInfo({ // Get user info
           success(res) {
             console.log(res)
           }
         })
        } else { // Not authorized

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

# Button's Open Capability (Getting User Info) 1

<button class="login"
    open-type="getUserInfo"
    bindgetuserinfo="onGetUserInfo"     // bindgetuserinfo is fixed
>
	Get WeChat Authorization Info
</button>


The bindgetuserinfo event will ask the user whether to authorize


JS:
    onGetUserInfo(event) { // Get user info
      const userInfo = event.detail.userInfo
      if (userInfo) { // User allowed authorization
        this.setData({
          modalShow: false
        })
        this.triggerEvent('loginSuccess', userInfo) // Pass user data to parent
      } else { // User denied authorization
        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

# Native Components

Native Components (opens new window)


auto-focus: automatically get focus

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

# Selecting and Uploading Images

Choose Image (opens new window)

let max = 9 - this.data.images.length // How many more images can be selected
wx.chooseImage({
      count: max, // How many more images can be selected
      sizeType: ['original', 'compressed'], // Original and compressed
      sourceType: ['album', 'camera'], // From album or camera
      success: (res) => { // Arrow function changes this reference
        console.log(res)
      },
    })
1
2
3
4
5
6
7
8
9

# Image Cropping

Image Cropping (opens new window)

<!-- mode: image cropping. aspectFill ensures the short edge is fully displayed -->
<image class="image" src="{{item}}" mode="aspectFill"></image>
1
2

# Getting Custom data-* Attributes (Deleting Images)

<!-- Display images -->
    <block wx:for="{{images}}" wx:key="*this">
      <view class="image-wrap">
        <!-- mode: image cropping. aspectFill ensures the short edge is fully displayed -->
        <image class="image" src="{{item}}" mode="aspectFill"></image>
        <icon class="iconfont icon-shanchu" bindtap="onDelImage" data-index="{{index}}"></icon>
      </view>
    </block>


    // Delete image
  onDelImage(event) {
  	// event.target.dataset.index gets the value of the data-index attribute
    this.data.images.splice(event.target.dataset.index, 1) // splice modifies the original array
    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

# Full-Screen Image Preview (Click to Enlarge)

Full-Screen Image Preview (opens new window)

// Full-screen image preview
  onPreviewImage(event) {
    wx.previewImage({
      urls: this.data.images, // Image URL list
      current: event.target.dataset.imgsrc // Currently previewed image URL
    })
  },
1
2
3
4
5
6
7

# Uploading Files to Cloud Storage (Blog Publishing Example)

Upload File to Cloud Storage (opens new window)

 // Combined with the "publish" example:
  send() {
    // Validate input
    if (content.trim() === '') { // trim() removes whitespace
      wx.showToast({
        title: 'Please enter content',
        icon: 'none'
      })
      return
    }
    wx.showLoading({
      title: 'Publishing',
    })
    /**
     * Implementation approach and steps:
     * 1. Images -> Upload to cloud storage -> Generate image fileIDs (cloud file IDs)
     * 2. Data -> Insert into cloud database
     *    Data includes: text content, image fileIDs, nickname, avatar, publish time, openId (user unique identifier; the system auto-adds _openId field when inserting into the database)
     */
    let promiseArr = []
    let fileIds = []
    // Upload images to cloud storage
    this.data.images.forEach((item) => {
      let p = new Promise((resolve, reject) => {
        let suffix = /\.\w+$/.exec(item)[0] // File extension
        wx.cloud.uploadFile({ // Only one file can be uploaded at a time
          /**
           * cloudPath: cloud path. If the path is the same, later uploads overwrite earlier ones
           * Path: blog/ folder in cloud storage + Date.now() timestamp + Math.random()*1000000 random number + file extension
           */
          cloudPath: 'blog/' + Date.now() + '-' + Math.random() * 1000000 + suffix,
          filePath: item, // Local temporary file path
          success: (res) => {
            fileIds.push(res.fileID)
            resolve()
          },
          fail: (err) => {
            console.error(err)
            reject()
          }
        })
      })
      promiseArr.push(p)
    })

    // Save to cloud database
    Promise.all(promiseArr).then((res) => {
      db.collection('blog').add({
        data: {
          ...userInfo, // Nickname, avatar
          content, // Content
          img: fileIds, // Image fileID list
          createTime: db.serverDate() // Creation time, using server time
        }
      }).then((res) => {
        wx.hideLoading()
        wx.showToast({
          title: 'Published successfully',
        })
        // Return to blog page and refresh
        wx.navigateBack()

      })
    }).catch((err) => {
      wx.hideLoading()
      wx.showToast({
        title: 'Sorry, publishing failed',
        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 Modularization (Date Formatting)

Create a formatTime.js file in the utils directory

// Date formatting module
module.exports = (date) => { // date parameter must be a Date type
  let fmt = 'yyyy-MM-dd hh:mm:ss' // Predefined format
  const o = {
    // + means one or more in regex
    'M+': date.getMonth() + 1,
    'd+': date.getDate(),
    'h+': date.getHours(),
    'm+': date.getMinutes(),
    's+': date.getSeconds()
  }

  if (/(y+)/.test(fmt)) {
    // $1 refers to the first group in the regex, i.e., (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

Import the JS module in a component

import formatTime from '../../utils/formatTime.js'

Usage:
formatTime(new Date('Wed Aug 28 2019 16:23:06 GMT+0800 (China Standard Time)'))
1
2
3
4

# Preventing Event Bubbling

Both bind and catch can bindEvents. The difference is that bind allows event bubbling, while catch does not.

# Navigate Back and Execute a Method

API (opens new window)

 // Return to the blog page and refresh
 wx.navigateBack()
 const pages = getCurrentPages() // Get the current page stack
 const prevPage = pages[pages.length - 2]  // Get the previous page
 prevPage.onPullDownRefresh() // Execute the previous page's onPullDownRefresh method
1
2
3
4
5

# Image Lazy Loading

API (opens new window)

Set lazy-load to true on the image tag
<image class="img" src="{{item}}" lazy-load="true"></image>

.img {
  background: #eee;
}
1
2
3
4
5
6

A background image or background color on the image element can serve as a lazy-loading placeholder

# Fuzzy Search

// Get blog list
  app.router('blogList', async (ctx, next) => {
    const keyword = event.keyword // Search keyword passed from the API call
    let w = {}
    if (keyword.trim() != '') {
      w = {
        content: db.RegExp({ // Regex
          regexp: keyword,
          options: 'i' // i means case-insensitive
        })
      }
    }

    // where: query condition, skip: start from which record, limit: how many records, orderBy(field, order): 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

# Improving Fuzzy Search Performance (Adding Indexes, significant for large datasets)

Cloud Development Console > Database > corresponding collection > Index Management > Add Index > enter custom index name, whether the field value is unique, field name to query, ascending/descending > OK

# Mini Program Client-Side Database Calls

Database operations are typically written in cloud functions, but the mini program client can also operate on the database directly.

The mini program client can query up to 20 records at a time; cloud functions can query up to 100. You can use multiple queries with concatenation to break through the limit.

// Mini program client-side database call example
    const db = wx.cloud.database() // Initialize database
    db.collection('blog').orderBy('createTime','deac').get().then((res) => {
      console.log(res)
    })
1
2
3
4
5

# Cloud Database Permission Management

Note: The cloud console and server-side (cloud functions) always have full read/write permissions.

Permission management only applies to requests initiated from the mini program client.

  • Only the creator can write, everyone can read (suitable for articles)

  • Only the creator can read/write (suitable for private content)

  • Only admins can write, everyone can read (suitable for product information)

  • Only admins can read/write (suitable for sensitive backend data)

# Three Design Approaches for 1-to-N Relationships in Databases

# Approach 1: N is a small number (within a few dozen)

Store N sub-items within 1 record

For example, a blog post has at most 9 images; these 9 images can be stored alongside other data in a single record.
[
	{
		id:...
		img:[
		'...', '...', '...', '...', '...', '...', '...', '...', '...'
		]
	}
]
1
2
3
4
5
6
7
8
# Approach 2: N is a moderate number (dozens to hundreds)

Store IDs of each N item in the 1 record

Use two database collections:

One is a "catalog" collection, storing IDs from the "details" collection

One is a "details" collection, where each record has its own unique ID and detailed data

Catalog collection:
[
	{
		'id':"11",
		'name': 'Product 1',
		'xqs': ['111','222','333', ... ]  // Store IDs from the details collection
	}
]



Details collection:
[
{'id':"111",name:'Part 1',title:'...' ...},
{'id':"222",name:'Part 2',title:'...' ...},
{'id':"333",name:'Part 3',title:'...' ...},
...
]


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

Like the relationship between a playlist and song details.

# Approach 3: N is very large (hundreds, thousands, or more)

Each N item stores the ID of the 1 record

For example, a Sina blog post with thousands of comments

A blog post:
[{
	'id':'11',
	'content':'Blog content'
	...
}]


Thousands of comments:
[
{
	'id':'111111'
	'blogId':'11', // This ID corresponds to the blog post's ID
	'content': 'Comment 1'
},
{
	'id':'222222'
	'blogId':'11', // This ID corresponds to the blog post's ID
	'content': 'Comment 2'
},
{
	'id':'33333'
	'blogId':'11', // This ID corresponds to the blog post's ID
	'content': 'Comment 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

# Cloud Calls

Call server-side open APIs through cloud functions

These APIs include: template message push, mini program QR code generation, etc.

# Template Message Push

1. A form element is required to trigger message push, with report-submit="true" set


<form slot="modal-content" report-submit="true" bind:submit="onSend">
    <textarea name="content" class="comment-content" placeholder="Write a comment" value="{{content}}" fixed="true"></textarea>
    <button class="send" form-type="submit">Send</button>
  </form>
1
2
3
4
5

2. Configuration is needed on the WeChat Official Platform:

WeChat Official Platform > Features > Template Messages > Add Template > Choose the appropriate template > After adding, you'll get a template ID

3. Create a new cloud function for the cloud call. Create a config.json configuration file in that cloud function directory for permissions

config.json:

{
  "permissions": {
    "openapi": [
      "templateMessage.send"
    ]
  }
}
1
2
3
4
5
6
7

Cloud function for message push:

// Cloud function entry function
exports.main = async (event, context) => {
  // Get openid
  const { OPENID } = cloud.getWXContext()

  // Template push message
  const result = await cloud.openapi.templateMessage.send({
    touser: OPENID,
    page: `/pages/blog-comment/blog-comment?blogId=${event.blogId}`, // Page opened when user taps the push notification
    data: { // Template content; keyword fields correspond to fields set in the template on the official platform
      keyword1: { // Comment content
        value: event.context
      },
      keyword2: { // Comment time
        value: event.time
      }
    },
    templateId: 'LNwKMcYwlz-0HabgBhmZi6CWZrlNSBiNJ2h0SMorcxQ', // Template ID, obtained from the official platform
    formId: event.formId // Form ID that triggers the push
  })

  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. Call the message push cloud function after form submission completes

 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

# Cloud Function Multi-Collection Database Query

// Blog detail (blog content + comments)
  app.router('blogDetail', async(ctx, next) => {
    let blogId = event.blogId

    // Blog content
    let detail = await blogCollection.where({
      _id: blogId
    }).get().then((res) => {
      return res.data
    })

    // Comment query
    const countResult = await blogCollection.count()
    const total = countResult.total
    let commentList = {
      data: []
    }
    if (total > 0) {
      // Break through the 100-record limit
      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

# Share Functionality

The share feature requires a button tag with 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>Share</text>
    </button>
1
2
3
4
5

In JS, the onShareAppMessage method is automatically called when the button is tapped

onShareAppMessage: function (event) {
    console.log(event)

    // Customize the share card
    let blogObj = event.target.dataset.blog
    return {
      title: blogObj.content,
      path: `/pages/blog-comment/blog-comment?blogId=${blogObj._id}`,
      // imageUrl: '' // Custom image, cloud storage images not supported
    }
  }
1
2
3
4
5
6
7
8
9
10
11

# Different Ways to Get User Info in Different Scenarios

# Scenario 1: Just want to display your own nickname and avatar on the UI

Using a component approach: get different user data based on the type

This approach does not require authorization; can only be used to display your own info in WXML

open-data (opens new window)

<open-data type="userAvatarUrl"></open-data>
<open-data type="userNickName"></open-data>
...
1
2
3
# Scenario 2: Getting user info in JavaScript

This approach requires user authorization before getting user info

wx.getUserInfo (opens new window)

wx.getUserInfo({
      success: (res) => {
        console.log(res)
      }
    })
1
2
3
4
5

Authorization is needed first if not yet authorized:

// Check if user is authorized
      wx.getSetting({
        success: (res) => { // Arrow function changes inner this to outer this
          if (res.authSetting['scope.userInfo']) { // Authorized
            wx.getUserInfo({ // Get user info
              success: (res) => { // Arrow function changes inner this to outer this

                app.setGlobalData('userInfo', res.userInfo) // Set global property

                this.onLoginSuccess({
                  detail: res.userInfo
                })
              }
            })
          } else { // Not authorized
            this.setData({ // Show modal with user info button
              modalShow: true
            })
          }
        }
      })


  Authorization button:
 <button class="login" open-type="getUserInfo" bindgetuserinfo="onGetUserInfo">Get WeChat Authorization Info</button>


    onGetUserInfo(event) { // Get user info
      const userInfo = event.detail.userInfo
      if (userInfo) { // User allowed authorization
        this.setData({
          modalShow: false
        })
        this.triggerEvent('loginSuccess', userInfo) // Pass user data to parent
      } else { // User denied authorization
        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

Note: this approach does not obtain the openId

# Scenario 3: Getting the openId

Getting the openId does not require user authorization

  1. Traditional development approach: the backend server is self-developed, no cloud development

Mini program client WeChat server Backend server

Steps:

Mini program client calls wx.login to get a code from WeChat server

Mini program client calls wx.request to pass the code to the backend server

Backend server uses the code to exchange for openid and session_key from WeChat server

Backend server sends the openid to the mini program client

  1. Cloud development approach

In the login cloud function:

// Get WX Context (WeChat call context), including OPENID, APPID, and UNIONID (if conditions are met)
  const wxContext = cloud.getWXContext()

  return {
    event,
    openid: wxContext.OPENID,
    appid: wxContext.APPID,
    unionid: wxContext.UNIONID,
  }
1
2
3
4
5
6
7
8
9
Regular button:
<button bindtap="getOpenid">Get openid</button>

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

openid is different between mini programs and official accounts

unionid is the same between mini programs and official accounts

# "Profile" Page

JSON file

"navigationBarTitleText": "Profile",
  "disableScroll": true  // Disable page scrolling
1
2

# Navigation Page Link

navigator (opens new window)

# Background Images

WXSS background images don't support local relative paths, only network images and base64 images

Base64 images are recommended; keep file sizes small.

# The page Tag Present on Every Page

page {
  background-color: #f1f1f1;
}
1
2
3

# Play History and Local Storage

Option 1: Store play history in the database, accessible across devices. Slower read speed.

Option 2: Store play history locally, accessible only on the current device. Faster read speed.

This project uses local storage:

Use openid as the local storage key, with play history as the value

Get openid in app.js, i.e., obtain openid when the mini program opens.

// app.js
onLaunch: function () {
	this.getOpenid() // Get and store openid
},
getOpenid() { // Get and store openid
    wx.cloud.callFunction({
      name: 'login'
    }).then((res) => {
      const openid = res.result.openid
      this.globalData.openid = openid // Save to global variable
      if (wx.getStorageSync(openid) == '') { // User has never opened the mini program, no openid stored locally
        wx.setStorageSync(openid, []) // Store openid locally
      }
    })
  }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

During song playback:

// Save play history to local storage
  savePlayHistory() {
    const currentSong = musiclist[nowPlayingIndex] // Currently playing song
    const openid = app.globalData.openid // Get openid from global property
    const playHistory = wx.getStorageSync(openid) // Get play history array from local storage

    for (let i = 0, len = playHistory.length; i < len; i++) {
      if (playHistory[i].id === currentSong.id) { // Current song already exists in play history
        playHistory.splice(i, 1) // Remove the existing record
        break
      }
    }

    playHistory.unshift(currentSong) // Insert at the beginning
    wx.setStorage({ // Save locally
      key: openid,
      data: playHistory
    })

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

Play history page retrieval:

onLoad: function (options) {

    const openid = app.globalData.openid // Get openid from global property
    const playHistory = wx.getStorageSync(openid) // Read local play history data

    if (playHistory.length !== 0) { // Has play history
      this.setData({
        playHistory
      })
      wx.setStorage({ // Replace the musiclist (playlist) in storage with the play history list
        key: 'musiclist',
        data: playHistory,
      })
    }

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

# My Posts

The code demonstrates fetching data from both cloud functions and the mini program client. Fetching from the mini program client has permission management capabilities and doesn't require passing openid.

# Mini Program QR Code

Get Mini Program QR Code (opens new window)

This project demonstrates using API B: suitable for scenarios requiring a very large number of codes (opens new window) via cloud call.

Steps:

  • Create cloud function getQRCode

  • Create config.json in the getQRCode cloud function directory for permission configuration:

    {
      "permissions":{
        "openapi":[
          "wxacode.getUnlimited"
        ]
      }
    }
    
    1
    2
    3
    4
    5
    6
    7
// Cloud function entry function
exports.main = async (event, context) => {
  const wxContext = cloud.getWXContext()

  const result = await cloud.openapi.wxacode.getUnlimited({
    scene: wxContext.OPENID, // Link parameter; doesn't have to be openid, can be any data used to identify who scanned the code
    // page: "pages/blog/blog" // Defaults to the main page
    // lineColor: { // Line color
    //   'r': 211,
    //   'g': 60,
    //   'b': 57
    // },
    // isHyaline: true // Whether transparent
  })

  // result is binary data; upload to cloud storage first

  // Upload to cloud storage
  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
# Detecting QR Code Entry and Getting Parameters
// In the onLoad method of the page entered via QR code:

onLoad: function (options) {
	console.log(options.scene) // Get the QR code entry parameter
}
1
2
3
4
5

# Version Update Detection

// app.js
onLaunch: function(options) {
    this.checkUpate()
},
checkUpate(){
    const updateManager = wx.getUpdateManager()
    // Check for version updates
    updateManager.onCheckForUpdate((res)=>{
      if (res.hasUpdate){
        updateManager.onUpdateReady(()=>{
          wx.showModal({
            title: 'Update Available',
            content: 'A new version is ready. Restart the app?',
            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

# Performance Optimization

Official Performance Tips (opens new window)

Use the developer tools' debugger and Audits for scoring, then optimize the project based on the suggestions.

# Scene Values and Their Applications

Scene Values (opens new window)

Scene values describe the path through which a user enters the mini program. See the Scene Value List (opens new window) for complete details.

Different business logic can be implemented based on different entry scenes. For example, a food ordering mini program with QR codes posted in the restaurant -- users scanning the code can go directly to the ordering page.

In app.js, onLaunch(options) and onShow(options), options contains the scene value.

In the developer tools, use "Switch Background" to simulate different entry scenes.

# Mini Program "SEO" -- Page Indexing with sitemap

The sitemap.json file is in the same directory as app.js, used to configure indexing rules.

sitemap Configuration (opens new window)

Purpose:

Enable mini program search to find results based on mini program content.

How to use:

  1. On the WeChat Official Platform: Mini Program Info > Page Indexing Settings > Enable (enabled by default)

  2. Open sitemap.json and configure indexing rules

{
  "desc": "For more info, see https://developers.weixin.qq.com/miniprogram/dev/framework/sitemap.html",
  "rules": [{ // Indexing rules
  "action": "allow",// Whether to be indexed: allow or disallow
  "page": "*" // * means all pages are indexed
  }]
}
1
2
3
4
5
6
7
{
  "desc": "For more info, see https://developers.weixin.qq.com/miniprogram/dev/framework/sitemap.html",
  "rules": [{ // Indexing rules, can add multiple
  "action": "allow", // Whether to be indexed
  "page":"pages/player/player",  // Page
  "params": ["musicId","index"], // Dynamic link parameters
  "matching":'exact' // Whether params must match exactly
  },{
  "action": "disallow", // Whether to be indexed
  "page":"*",  /
  }]
}

// The above rules mean: only the player page is indexed, all other pages are not
1
2
3
4
5
6
7
8
9
10
11
12
13
14

# Mini Program Launch Review Process

WeChat Official Platform > Version Management > Upload as beta version > Submit for review > Launch

# Admin Panel

# Architecture Diagram

			Frontend                                                           Backend                                                         Mini Program Cloud Dev

vue-admin-template <---via ajax--> Koa2-based; HTTP API or tcb-admin-node ---->Cloud Functions, Cloud DB, Cloud Storage

​

# Building the Admin Frontend with vue-admin-template

vue-element-admin (opens new window) -- admin system template based on Element

vue-admin-template (opens new window) -- simplified version of vue-element-admin (opens new window)

See the official documentation for usage.

# Building the Admin Backend with Koa2

Official site: https://koa.bootcss.com/ (opens new window)

Create a new empty folder wx-music-admin-backend, open terminal:

# Generate package.json file, -y means default configuration
npm init -y

# Install Koa
npm install koa

# Create app.js file (Windows 10 command), as the project entry file
type nul > app.js

1
2
3
4
5
6
7
8
9

app.js:

const Koa = require('koa')
const chalk = require('chalk') // Plugin for colored console.log output, requires: npm i chalk
const app = new Koa()

app.use(async (ctx) => {
  ctx.body = 'Hello Wolrd'
})
const port = 3000
app.listen(port, () => { // Port, callback after starting the service
  console.log(chalk.green(`> Service started, visit: http://localhost:${port}`))
})
1
2
3
4
5
6
7
8
9
10
11

Terminal:

# Start the project with Node
node app.js

# Visit: http://localhost:3000
1
2
3
4

# Caching and Refreshing the access_token

access_token, the WeChat API call credential. Details: https://developers.weixin.qq.com/miniprogram/dev/api-backend/open-api/access-token/auth.getAccessToken.html (opens new window)

Back to the project wx-music-admin-backend, open terminal:

# HTTP request plugin
npm i request
npm i request-promise
1
2
3
/**
 * Get WeChat API call credential
 * Details: https://developers.weixin.qq.com/miniprogram/dev/api-backend/open-api/access-token/auth.getAccessToken.html
 */


const rp = require('request-promise') // Node HTTP request plugin
const fs = require('fs') // Node file module
const path = require('path') // Node path module

// fileName = __dirname (absolute path of the current file's directory) + './access_token.json'
const fileName = path.resolve(__dirname, './access_token.json')

// Get these parameters from: WeChat Official Platform > Development > Development Settings
const APPID = 'wxc4e0b2d98063b103'
const APPSECRET = 'xxx' // Mini program secret key, keep it confidential!

// WeChat access_token request URL
const URL = `https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=${APPID}&secret=${APPSECRET}`

// Send request to get AccessToken
const updateAccessToken = async () => {
  const resStr = await rp(URL)
  const res = JSON.parse(resStr)

  if (res.access_token) {
    // Node writeFile: param 1: file path, param 2: file content. First write creates file, subsequent writes overwrite
    fs.writeFileSync(fileName, JSON.stringify({
      access_token: res.access_token,
      createTime: new Date()
    }))
  } else { // If not obtained, try again
    await updateAccessToken()
  }
}

// Read access_token
const getAccessToken = async () => {
  try {
     // Node readFile: param 1: file to read, param 2: character set
    const readRes = fs.readFileSync(fileName, 'utf8')
    const readObj = JSON.parse(readRes)

    // If the server was down and setInterval couldn't refresh on time, check access_token validity here
    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) { // Catch exception: file doesn't exist yet, create it first
    await updateAccessToken()
    await getAccessToken()
  }
}

// access_token is valid for 2 hours, refresh on schedule
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

# Triggering Cloud Functions via HTTP API from the Backend

HTTP API to Trigger Cloud Functions (opens new window)

# CORS Issues and Backend Solutions

CORS issues arise when the admin frontend requests data from the admin backend

  // Situations that cause CORS issues:
  // http://www.a.com  https://www.a.com -- different protocols
  // http://www.a.com  http://www.b.com -- different domains
  // http://www.a.com  http://news.a.com -- different subdomains
  // http://www.a.com:8080  http://www.a.com:3000 -- different ports

// Methods to solve CORS:
// JSONP
// iframe
// postMessage cross-origin
// Cross-Origin Resource Sharing (CORS)
1
2
3
4
5
6
7
8
9
10
11

Admin backend, install:

// Koa package for solving CORS issues
npm i koa2-cors
1
2

app.js

// Handle CORS
app.use(cors({
  origin: ['http://localhost:9528'], // Allowed domain to access this service
  credentials: true
}))
1
2
3
4
5

# Cloud Database CRUD APIs

Database Query Records (opens new window)

# Receiving POST Request Data on the Backend

GET request data can be accessed directly via ctx.request.query, but POST requests require koa-body

npm i koa-body
1

app.js

const koaBody = require('koa-body') // Required for accessing POST request data from the frontend

// Parse POST request body
app.use(koaBody({
  multipart: true
}))

1
2
3
4
5
6
7

API route file:

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

  const params = ctx.request.body // Get POST request data from the frontend; requires koa-body installed and configured

})
1
2
3
4
5

# Getting Cloud Storage Images from the Backend

Upload images to cloud storage; create an image collection in the cloud database and add data fields including the cloud file's fileID.

The backend project retrieves data by calling the cloud database

router.get('/list', async (ctx, next) => {
  // The API reads up to 10 records from the database by default
  const query = `db.collection('swiper').get()`
  const res = await callCloudDB(ctx, 'databasequery', query)
  console.log(res)

})
1
2
3
4
5
6
7

However, the retrieved data contains fileIDs, which cannot directly display images. You need to use the WeChat HTTP API for cloud storage to get the image URLs.

Get Cloud Storage Files (opens new window)

# Uploading Images to Cloud Storage from the Backend

File Upload (opens new window)

Edit (opens new window)
#Mini Program
Last Updated: 2026/03/21, 12:14:36
TypeScript Notes
JS Design Patterns Summary Notes

← TypeScript Notes JS Design Patterns Summary Notes→

Recent Updates
01
How I Discovered Disposable Email — A True Story
06-12
02
Animations in Grid Layout
09-15
03
Renaming a Git Branch
08-11
More Articles >
Theme by VDone | Copyright © 2026-2026 Nikolay Tuzov | MIT License | Telegram
  • Auto
  • Light Mode
  • Dark Mode
  • Reading Mode