微信小程序开发记录


微信小程序开发记录


正文

微信小程序开发,如果有后端服务,就牵扯到session认证、用户信息微信跨服务注册(小程序、公众号)。

用户信息微信跨服务注册,就是微信中只有一个用户标识,通过这一个标识,不论是在小程序、还是公众号,都知道你是你,而不会产生两个账号的问题, 这个标识就是unionid,需要保存在后端数据库。OpenID则是微信中不同入口的唯一标示,微信帮我们生成了这个应用的唯一id。为了将来打通不同应用, 最好把这两个字段都记录下来。

不过小程序并不能直接获取到openid,需要调用 wx.login() 获取 临时登录凭证code ,并回传到开发者服务器。 开发者服务器再调用 code2Session 接口,换取 用户唯一标识 OpenID 和 会话密钥 session_key。

看一下实际过程:

//app.js
App({
    onLaunch: function () {
        // 登录
        wx.login({
            success: res => {
                console.log(res.code);
                // 发送 res.code 到后台换取 openId, sessionKey, unionId
                wx.request({
                    url: 'http://localhost/mi/getopenID.php',
                    data:{code: res.code},
                    success:(res)=>{
                        console.log(res.data.openid);
                        this.globalData.openID = res.data.openid;
                    }
                })
            }
        })
    }
})

后端处理:

// 声明CODE,获取小程序传过来的CODE
$code = $_GET["code"];

// 配置appid 此处填写你的appid
$appid = "";

//配置appscret 此处填写你的appsecret
$secret = "";

//api接口
$api = "https://api.weixin.qq.com/sns/jscode2session?appid={$appid}&secret={$secret}&js_code={$code}&grant_type=authorization_code";

//获取GET请求
function httpGet($url){
    $curl = curl_init();
    curl_setopt($curl, CURLOPT_RETURNTRANSFER, 2);
    curl_setopt($curl, CURLOPT_TIMEOUT, 500);
    curl_setopt($curl, CURLOPT_SSL_VERIFYPEER, 2);
    curl_setopt($curl, CURLOPT_SSL_VERIFYHOST, 2);
    curl_setopt($curl, CURLOPT_URL, $url);
    
    $res = curl_exec($curl);
    curl_close($curl);
    
    return $res;
}

//发送
$str = httpGet($api);

// 然后就可以看到返回的openid了,在一个json字符串中
echo $str;

wx.getSetting(Object object)

获取用户的当前设置。返回值中只会出现小程序已经向用户请求过的权限。

// 获取用户信息
wx.getSetting({
  success: res => {
    if (res.authSetting['scope.userInfo']) {
      // 已经授权,可以直接调用 getUserInfo 获取头像昵称,不会弹框
      wx.getUserInfo({
        success: res => {
          // 可以将 res 发送给后台解码出 unionId
          this.globalData.userInfo = res.userInfo

          // 由于 getUserInfo 是网络请求,可能会在 Page.onLoad 之后才返回
          // 所以此处加入 callback 以防止这种情况
          if (this.userInfoReadyCallback) {
            this.userInfoReadyCallback(res)
          }
        }
      })
    }
  }
})

微信小程序不支持session,除了变相使用cookie实现session认证外,我们还可以用JWT实现前后端认证。

注意点

有时会碰到这个报错信息:

Cannot read property ‘globalData’ of undefined;at App onLaunch function

这是因为this具有作用域的概念,在函数的一开始就定义一个变量指向this:

var that = this;

需要使用的时候调用这个新定义的变量就可以了。

提示框

wx.showToast({
    title: '刷新中',
    icon: 'loading',
    duration: 2000
})
wx.showToast({
    title: '刷新成功',
    icon: 'success',
    duration: 1000
})
wx.showToast({
    title: '服务异常',
    icon: 'warn',
    duration: 1000
})

异步处理

微信小程序的wx.request是异步请求,非阻塞,代码会继续向后执行,不等待wx.request请求返回。

如:

Page({
  data: {
    myData: ''
  },
  // loadMyData函数用于打印myData的值
  loadMyData () {
    console.log('获取到的数据为:' + this.data.myData)
  },
  // 生命周期函数onload用于监听页面加载
  onload: function () {
    wx.request({
      url: 'https://api',  // 某个api接口地址
      success: res => {
        console.log(res.data)
        this.setData({
          myData: res.data
        })
        console.log(this.data.myData)
      }
    })
    // 调用之前的函数
    this.loadMyData()
  }
})

我们希望的是后面的代码在wx.request请求返回后再向下执行,这里就需要考虑异步处理的方式了。

方法一-嵌套

...
onload: function () {
  wx.request({
    url: 'https://api',  // 某个api接口地址
    success: res => {
      console.log(res.data)
      this.setData({
        myData: res.data
      })
      console.log(this.data.myData)
      // 把使用数据的函数写在回调函数success中
      this.loadMyData()
    }
  })
}

但是如果逻辑复杂,需要多层异步操作,会出现怎么样的情况呢?

asyncFn1(function(){
  //...
  asyncFn2(function(){
    //...
    asyncFn3(function(){
      //...
      asyncFn4(function(){
        //...
        asyncFn5(function(){
           //...
        });
      });
    });
  });
});

这就是恐怖的“回调地狱”(Callback Hell)。

方法二-回调

方法三-Promise

Promise是javascript中的原生函数,可见这里

Promise这东西简单说来就是,它可以将异步的执行逻辑和结果处理分离,摒弃了一层又一层的回调嵌套,使得处理逻辑更加清晰。

现在我们就用Promise包装一下wx.request:

/**
 * requestPromise用于将wx.request改写成Promise方式
 * @param:{string} myUrl 接口地址
 * @return: Promise实例对象
 */
const requestPromise = myUrl => {
  // 返回一个Promise实例对象
  return new Promise((resolve, reject) => {
    wx.request({
      url: myUrl,
      success: res => resolve(res)
    })
  })
}
// 我把这个函数放在了utils.js中,这样在需要时可以直接引入
module.exports = requestPromise

现在再使用试试:

// 引用模块
const utilApi = require('../../utils/util.js')
Page({
  ...
  // 生命周期函数onload用于监听页面加载
  onLoad: function () {
    utilApi.requestPromise("https://www.bilibili.com/index/ding.json")
    // 使用.then处理结果
    .then(res => {
      console.log(res.data)
      this.setData({
        myData: res.data
      })
      console.log(this.data.myData)
      this.loadMyData()
    })
  }
})

结果和使用回调函数一致。当有多个异步请求时,直接不断地.then(fn)去处理即可,逻辑清晰。

方法四-第三方封装

示例demo

<!--index.wxml-->
<view class="container">
  <view class="userinfo">
    <button wx:if="" open-type="getUserInfo" bindgetuserinfo="getUserInfo"> 获取头像昵称 </button>
    <block wx:else>
      <image bindtap="bindViewTap" class="userinfo-avatar" src="" mode="cover"></image>
      <text class="userinfo-nickname"></text>
    </block>
  </view>
  <view class="usermotto">
    <text class="user-motto"></text>
  </view>
</view>
// index.js
//获取应用实例
const app = getApp()

Page({
  data: {
    motto: 'Hello World',
    userInfo: {},
    hasUserInfo: false,
    canIUse: wx.canIUse('button.open-type.getUserInfo')
  },
  //事件处理函数
  bindViewTap: function() {
    wx.navigateTo({
      url: '../logs/logs'
    })
  },
  onLoad: function () {
    if (app.globalData.userInfo) {
      this.setData({
        userInfo: app.globalData.userInfo,
        hasUserInfo: true
      })
    } else if (this.data.canIUse){
      // 由于 getUserInfo 是网络请求,可能会在 Page.onLoad 之后才返回
      // 所以此处加入 callback 以防止这种情况
      app.userInfoReadyCallback = res => {
        this.setData({
          userInfo: res.userInfo,
          hasUserInfo: true
        })
      }
    } else {
      // 在没有 open-type=getUserInfo 版本的兼容处理
      wx.getUserInfo({
        success: res => {
          app.globalData.userInfo = res.userInfo
          this.setData({
            userInfo: res.userInfo,
            hasUserInfo: true
          })
        }
      })
    }
  },
  getUserInfo: function(e) {
    console.log(e)
    app.globalData.userInfo = e.detail.userInfo
    this.setData({
      userInfo: e.detail.userInfo,
      hasUserInfo: true
    })
  }
})
<!--logs.wxml-->
<view class="container log-list">
  <block wx:for="" wx:for-item="log">
    <text class="log-item">. </text>
  </block>
</view>
// logs.js
const util = require('../../utils/util.js')

Page({
  data: {
    logs: []
  },
  onLoad: function () {
    this.setData({
      logs: (wx.getStorageSync('logs') || []).map(log => {
        return util.formatTime(new Date(log))
      })
    })
  }
})
// utils/util.js
const formatTime = date => {
  const year = date.getFullYear()
  const month = date.getMonth() + 1
  const day = date.getDate()
  const hour = date.getHours()
  const minute = date.getMinutes()
  const second = date.getSeconds()

  return [year, month, day].map(formatNumber).join('/') + ' ' + [hour, minute, second].map(formatNumber).join(':')
}

const formatNumber = n => {
  n = n.toString()
  return n[1] ? n : '0' + n
}

module.exports = {
  formatTime: formatTime
}

内层页面比app应用层加载的快

回调定义

首先要说明在小程序中,app.js和index.js是异步执行的,也就说明可能当你app.js中onlaunch的登录、通过code获取open_id之类的网络请求在执行的时候, index.js中的onload已经执行完了,如果你在index的onload中有需要用到openid的话,那就会无法执行调用了。

这个时候就需要通过用 请求的callback方法了,app.js的获取用户信息中举了例子:

// 由于 getUserInfo 是网络请求,可能会在 Page.onLoad 之后才返回
// 所以此处加入 callback 以防止这种情况
if (this.userInfoReadyCallback) {
    this.userInfoReadyCallback(res)
}

app.userInfoReadyCallback,在index.js中定义。

index.js先判断app中用户id是否存在,如果存在,说明app.js已加载完成,则不定义app.userInfoReadyCallback; 如果不存在,说明app.js还没有加载完成,则定义app.userInfoReadyCallback,等app.js执行到时再去执行userInfoReadyCallback, userInfoReadyCallback中定义的this是index页面,我们在userInfoReadyCallback中先去获取用户信息,然后再去执行index当前页的加载逻辑。

如果 this.userInfoReadyCallback 未定义,说明app.js比index.js加载的快,用户id信息已经在app.js中赋值过了; 如果 this.userInfoReadyCallback 定义了,说明index.js比app.js加载的快,用户id信息在app.js中还没有赋值,则执行userInfoReadyCallback。

代码详情:

app.js

onLaunch: function() {
    wx.login({
      success: res => {
        // 发送 res.code 到后台换取 openId, sessionKey, unionId
        console.log(res)
        wx.request({
          url: that.globalData.url + 'code_to_openid',
          method:'post',
          header: { 'Content-Type': 'application/x-www-form-urlencoded' },
          data:{
            code:res.code
          },
          success(res){
            this.globalData.user_id = res.data.data.id
            this.globalData.unionid = res.data.data.unionid
            this.globalData.user_info = res.data.data
            // callback,如果 userInfoReadyCallback 定义了就执行,否则跳过,非阻塞
            if (this.userInfoReadyCallback) {
              this.userInfoReadyCallback(res)
            }
          }
        })
      }
    })
}

index.js

onLoad: function() {
    let that = this
    //查看是否有userid
    if (!app.globalData.user_id) {
      app.userInfoReadyCallback = res => {
        //判断是否阅读过引导页
        wx.request({
          url: app.globalData.url + 'checkfirst',
          method: 'POST',
          header: { "Content-Type": "application/x-www-form-urlencoded" },
          data: {
            user_id: app.globalData.user_id
          }, success(res) {
            console.log(res)
            if (res.data.resultCode == 0 || res.data.resultCode == ''){
              wx.hideTabBar()
              that.setData({
                guide_show:true
              });
              that.getLists();
            }
          }
        })
      }
    }
}

promise

还有种方式就是使用 promise ,我们在app.js中定义promise,然后在index.js中执行promise,成功后再执行当前页逻辑。

如:

app.js

var http = require('service/http.js')
App({
  onLaunch: function() {
    //调用API从本地缓存中获取数据
    // var that = this;
  },
  getAuthKey: function () {
    var that = this;
    return new Promise(function (resolve, reject) {
        // 调用登录接口
        wx.login({
          success: function (res) {
            if (res.code) {
              that.globalData.code = res.code;
              //调用登录接口
              wx.getUserInfo({
                withCredentials: true,
                success: function (res) {
                  that.globalData.UserRes = res;
                  that.globalData.userInfo = res.userInfo;
                  that.func.postReq('/api/v1/image/oauth', {
                    code: that.globalData.code,
                    signature: that.globalData.UserRes.signature,
                    encryptedData: that.globalData.UserRes.encryptedData,
                    rawData: that.globalData.UserRes.rawData,
                    iv: that.globalData.UserRes.iv
                  }, function (res) {
                    wx.setStorage({
                      key: "auth_key",
                      data: res.data.auth_key
                    })
                    var res = {
                      status: 200,
                      data: res.data.auth_key
                    }
                    resolve(res);
                    
                  })
                }
              })
            } else {
              console.log('获取用户登录态失败!' + res.errMsg);
              var res = {
                status: 300,
                data: '错误'
              }
              reject('error');
            }  
          }
        })
    });
  },
})

index.js

onLoad: function () {
    app.getAuthKey().then(function (res) {
      console.log(res);
      if (res.status == 200){
        var auth_key = res.data;
        app.func.req('/api/v1/image/theme-list', {
          page: 1,
          auth_key: auth_key
        }, function (res) {
          var page = that.data.pageValue + 1;
          that.setData({
            images: res.data,
            pageValue: page
          });
        });
      }else{
        console.log(res.data);
    }
});

二维码

目前有 2 种类型的二维码:

  1. 临时二维码,是有过期时间的,最长可以设置为在二维码生成后的 30天后过期,但能够生成较多数量。 临时二维码主要用于帐号绑定等不要求二维码永久保存的业务场景
  2. 永久二维码,是无过期时间的,但数量较少(目前为最多10万个)。永久二维码主要用于适用于帐号绑定、用户来源统计等场景。

可以借用EasyWeChat快速生成,参见 https://easywechat.com/5.x/basic-services/qrcode.html

实例:

<?php
namespace app\common\service;

use EasyWeChat\Factory;
use fast\Random;
use think\Env;

/**
 * Class WechatService
 * @package app\common\service
 */
class WechatService
{
    public static $instance;

    /**
     * \EasyWeChat\OfficialAccount\Application|null
     */
    protected $app = null;

    /**
     * WechatService constructor.
     */
    private function __construct()
    {
        $config = [
            'app_id' => Env::get('wx.appId'),
            'secret' => Env::get('wx.secret'),
            'token' => Env::get('wx.token'),
            'response_type' => 'array',
        ];

        $this->app = Factory::officialAccount($config);
    }

    /**
     * @param array $options
     * @return static
     */
    public static function instance($options = [])
    {
        if (is_null(self::$instance)) {
            self::$instance = new static($options);
        }

        return self::$instance;
    }

    /**
     * 获取关注用户列表
     * @param string $nextOpenId
     * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string
     */
    public function getUserList($nextOpenId = '')
    {
        $userList = [];
        try {
            $userList = $this->app->user->list($nextOpenId);
        } catch (\Exception $exception) {
            $error = [
                'msg' => $exception->getMessage(),
                'file' => $exception->getFile(),
                'line' => $exception->getLine()
            ];
            
           write_logs($error, 'wechat_h5', 'get_user_list');
        }

        return $userList;
    }

    /**
     * 获取用户基本信息-单个/批量
     * @param array $openIds
     * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string
     */
    public function getUserInfo($openIds = [])
    {
        $userInfo = [];
        try {
            $userInfo = $this->app->user->select($openIds);
        } catch (\Throwable $exception) {
            $error = [
                'msg' => $exception->getMessage(),
                'file' => $exception->getFile(),
                'line' => $exception->getLine()
            ];

            write_logs($error, 'wechat_h5', 'get_user_info');
        }
        
        return $userInfo;
    }

    /**
     * 创建永久二维码
     * 最多创建10W个二维码
     */
    public function createForever($scene = '')
    {
        $result = $this->app->qrcode->forever($scene);
        if (!key_exists('ticket', $result)) {
            return '';
        }

        $destDir = ROOT_PATH . 'public';
        $path = '/uploads/qrcode/wx/forever';

        $saveDir = $destDir . $path;
        if (!is_dir($saveDir)) {
            mkdir($saveDir, 0755, true);
        }

        $ticket = $result['ticket'];
        $url = $this->app->qrcode->url($ticket);

        $fileName = Random::build('md5') . '.jpg';

        $destPath = $saveDir . '/' . $fileName;

        $content = file_get_contents($url); // 得到二进制图片内容

        file_put_contents($destPath, $content); // 写入文件

        return $path . '/' . $fileName;
    }

    /**
     * 创建临时二维码
     * 最多30天
     */
    public function createTemporary($scene = '', $seconds = 30 * 24 * 3600)
    {
        $result = $this->app->qrcode->temporary($scene, $seconds);
        if (!key_exists('ticket', $result)) {
            return '';
        }

        $destDir = ROOT_PATH . 'public';
        $path = '/uploads/qrcode/wx/temporary';

        $saveDir = $destDir . $path;
        if (!is_dir($saveDir)) {
            mkdir($saveDir, 0755, true);
        }

        $ticket = $result['ticket'];
        $url = $this->app->qrcode->url($ticket);

        $fileName = Random::build('md5') . '.jpg';

        $destPath = $saveDir . '/' . $fileName;

        $content = file_get_contents($url); // 得到二进制图片内容

        file_put_contents($destPath, $content); // 写入文件

        return $path . '/' . $fileName;
    }
}

FastAdmin自带的随机数生成类fast\Random

<?php

namespace fast;

/**
 * 随机生成类
 */
class Random
{

    /**
     * 生成数字和字母
     *
     * @param int $len 长度
     * @return string
     */
    public static function alnum($len = 6)
    {
        return self::build('alnum', $len);
    }

    /**
     * 仅生成字符
     *
     * @param int $len 长度
     * @return string
     */
    public static function alpha($len = 6)
    {
        return self::build('alpha', $len);
    }

    /**
     * 生成指定长度的随机数字
     *
     * @param int $len 长度
     * @return string
     */
    public static function numeric($len = 4)
    {
        return self::build('numeric', $len);
    }

    /**
     * 生成指定长度的无0随机数字
     *
     * @param int $len 长度
     * @return string
     */
    public static function nozero($len = 4)
    {
        return self::build('nozero', $len);
    }

    /**
     * 能用的随机数生成
     * @param string $type 类型 alpha/alnum/numeric/nozero/unique/md5/encrypt/sha1
     * @param int    $len  长度
     * @return string
     */
    public static function build($type = 'alnum', $len = 8)
    {
        switch ($type) {
            case 'alpha':
            case 'alnum':
            case 'numeric':
            case 'nozero':
                switch ($type) {
                    case 'alpha':
                        $pool = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ';
                        break;
                    case 'alnum':
                        $pool = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ';
                        break;
                    case 'numeric':
                        $pool = '0123456789';
                        break;
                    case 'nozero':
                        $pool = '123456789';
                        break;
                }
                return substr(str_shuffle(str_repeat($pool, ceil($len / strlen($pool)))), 0, $len);
            case 'unique':
            case 'md5':
                return md5(uniqid(mt_rand()));
            case 'encrypt':
            case 'sha1':
                return sha1(uniqid(mt_rand(), true));
        }
    }

    /**
     * 根据数组元素的概率获得键名
     *
     * @param array $ps     array('p1'=>20, 'p2'=>30, 'p3'=>50);
     * @param int   $num    默认为1,即随机出来的数量
     * @param bool  $unique 默认为true,即当num>1时,随机出的数量是否唯一
     * @return mixed 当num为1时返回键名,反之返回一维数组
     */
    public static function lottery($ps, $num = 1, $unique = true)
    {
        if (!$ps) {
            return $num == 1 ? '' : [];
        }
        if ($num >= count($ps) && $unique) {
            $res = array_keys($ps);
            return $num == 1 ? $res[0] : $res;
        }
        $max_exp = 0;
        $res = [];
        foreach ($ps as $key => $value) {
            $value = substr($value, 0, stripos($value, ".") + 6);
            $exp = strlen(strchr($value, '.')) - 1;
            if ($exp > $max_exp) {
                $max_exp = $exp;
            }
        }
        $pow_exp = pow(10, $max_exp);
        if ($pow_exp > 1) {
            reset($ps);
            foreach ($ps as $key => $value) {
                $ps[$key] = $value * $pow_exp;
            }
        }
        $pro_sum = array_sum($ps);
        if ($pro_sum < 1) {
            return $num == 1 ? '' : [];
        }
        for ($i = 0; $i < $num; $i++) {
            $rand_num = mt_rand(1, $pro_sum);
            reset($ps);
            foreach ($ps as $key => $value) {
                if ($rand_num <= $value) {
                    break;
                } else {
                    $rand_num -= $value;
                }
            }
            if ($num == 1) {
                $res = $key;
                break;
            } else {
                $res[$i] = $key;
            }
            if ($unique) {
                $pro_sum -= $value;
                unset($ps[$key]);
            }
        }
        return $res;
    }

    /**
     * 获取全球唯一标识
     * @return string
     */
    public static function uuid()
    {
        return sprintf(
            '%04x%04x-%04x-%04x-%04x-%04x%04x%04x',
            mt_rand(0, 0xffff),
            mt_rand(0, 0xffff),
            mt_rand(0, 0xffff),
            mt_rand(0, 0x0fff) | 0x4000,
            mt_rand(0, 0x3fff) | 0x8000,
            mt_rand(0, 0xffff),
            mt_rand(0, 0xffff),
            mt_rand(0, 0xffff)
        );
    }
}

下面两个可以看一下:

扫描普通二维码,进入微信小程序 https://blog.csdn.net/xiasohuai/article/details/124121258

小程序如何生成带参数二维码 https://cloud.tencent.com/developer/article/1173253

用PHP生成二维码可以使用类库:PHP QR Code,官网 https://phpqrcode.sourceforge.net

问题

pad横屏变大问题

微信一次版本升级后,微信小程序横屏,页面会变大,原本的rpx单位就不再适用。 微信小程序横屏后,把宽度固定为750rpx了,所以出现了这个问题。

解决办法是用 CSS3的单位 vmax,vmin。 具体使用,参见 https://www.jianshu.com/p/a655d63f8183






参考资料

wx.login 获取 用户的openid https://www.jianshu.com/p/1b35561dc322

auth.code2Session https://developers.weixin.qq.com/miniprogram/dev/api-backend/open-api/login/auth.code2Session.html#%E8%AF%B7%E6%B1%82%E5%9C%B0%E5%9D%80

小程序登录 https://developers.weixin.qq.com/miniprogram/dev/framework/open-ability/login.html

UnionID 机制说明 https://developers.weixin.qq.com/miniprogram/dev/framework/open-ability/union-id.html

微信的OpenID究竟是什么 https://zhuanlan.zhihu.com/p/111355529

wx.getSetting(Object object)

用户信息 wx.getUserInfo(Object object) https://developers.weixin.qq.com/miniprogram/dev/api/open-api/user-info/wx.getUserInfo.html

boolean wx.canIUse(string schema) https://developers.weixin.qq.com/miniprogram/dev/api/base/wx.canIUse.html

微信小程序登录对接Django后端实现JWT方式验证登录 https://blog.csdn.net/weixin_33894640/article/details/93283473

小程序本地测试环境 https://blog.csdn.net/qq_35960620/article/details/83995765

小程序分页加载onReachBottom上拉触底加载–小程序走过的坑 https://blog.csdn.net/weixin_45938332/article/details/105968506?utm_medium=distribute.pc_relevant_t0.none-task-blog-BlogCommendFromMachineLearnPai2-1.compare&depth_1-utm_source=distribute.pc_relevant_t0.none-task-blog-BlogCommendFromMachineLearnPai2-1.compare

从wx.request谈谈异步处理 http://www.okeydown.com/html/2018/10-09/885.html

微信小程序异步处理 https://www.cnblogs.com/xjwy/p/7813859.html

Promise 对象 https://es6.ruanyifeng.com/#docs/promise

微信小程序-逻辑层 https://www.jianshu.com/p/56d742b6175c

App里的onLaunch,后面再执行页面Page里的onLoad执行顺序 https://www.debug8.com/javascript/t_7443.html

微信小程序-解决index页面onload比app.js onlaunch请求快 https://www.jianshu.com/p/2fce7f926427

小程序 解决App.js和index.js异步问题(Promise解决) https://blog.csdn.net/weixin_43789195/article/details/107698549

JavaScript Promise https://www.runoob.com/js/js-promise.html


返回