微信小程序开发完全指南

微信小程序开发完全指南


目录

  1. 项目目录结构
  2. 基础语法
  3. 生命周期
  4. 常用组件
  5. 常用 API
  6. 授权体系
  7. 登录态维持
  8. 分包加载
  9. 云开发
  10. 服务端常用 API

1. 项目目录结构

1.1 标准主包结构

miniprogram/
├── app.js                  # 应用入口
├── app.json                # 全局配置
├── app.wxss                # 全局样式
├── pages/                  # 页面目录(每个页面一个子目录)
│   ├── index/              # 首页
│   │   ├── index.js
│   │   ├── index.wxml
│   │   ├── index.wxss
│   │   └── index.json
│   └── logs/               # 日志页
│       ├── logs.js
│       ├── logs.wxml
│       ├── logs.wxss
│       └── logs.json
├── components/             # 公共组件
│   └── my-header/
│       ├── my-header.js
│       ├── my-header.wxml
│       ├── my-header.wxss
│       └── my-header.json
├── utils/                  # 工具函数
│   └── util.js
├── images/                 # 本地图片(主包限 2MB)
├── vendor/                 # 第三方 SDK
└── sitemap.json            # SEO 配置

1.2 app.json 示例

json
{
  "pages": [
    "pages/index/index",
    "pages/logs/logs",
    "pages/user/index"
  ],
  "window": {
    "navigationBarBackgroundColor": "#ffffff",
    "navigationBarTitleText": "我的小程序",
    "navigationBarTextStyle": "black",
    "backgroundColor": "#f8f8f8",
    "enablePullDownRefresh": false
  },
  "tabBar": {
    "color": "#999999",
    "selectedColor": "#1890ff",
    "backgroundColor": "#ffffff",
    "borderStyle": "black",
    "list": [
      { "pagePath": "pages/index/index", "text": "首页", "iconPath": "images/home.png", "selectedIconPath": "images/home-active.png" },
      { "pagePath": "pages/user/index", "text": "我的", "iconPath": "images/user.png", "selectedIconPath": "images/user-active.png" }
    ]
  },
  "style": "v2",
  "sitemapLocation": "sitemap.json",
  "lazyCodeLoading": "requiredComponents"
}

1.3 分包结构

miniprogram/
├── app.js
├── app.json
├── pages/                  # 主包页面
│   └── index/
├── subpkg/                 # 分包 A(games)
│   ├── pages/
│   │   └── game/
│   └── app.json
├── subpkg2/                # 分包 B(profile)
│   ├── pages/
│   │   └── settings/
│   └── app.json
└── components/            # 主包组件

对应根目录 app.json

json
{
  "pages": [ "pages/index/index" ],
  "subPackages": [
    {
      "root": "subpkg/games",
      "name": "games",
      "pages": [
        { "path": "pages/game/index", "lazyLoad": true }
      ]
    },
    {
      "root": "subpkg/profile",
      "name": "profile",
      "pages": [
        { "path": "pages/settings/index" }
      ]
    }
  ],
  "preloadRule": {
    "pages/index/index": {
      "network": "all",
      "packages": ["subpkg/games"]
    }
  }
}

2. 基础语法

2.1 WXML 模板

数据绑定

html
<!-- 基础绑定 -->
<view>{{ message }}</view>

<!-- 运算表达式 -->
<view>{{ a + b }}</view>
<view>{{ flag ? 'yes' : 'no' }}</view>

<!-- 列表渲染(wx:for) -->
<view wx:for="{{ list }}" wx:key="id">
  {{ index }}: {{ item.name }}
</view>

<!-- 条件渲染(wx:if / wx:elif / wx:else) -->
<view wx:if="{{ score >= 90 }}">优秀</view>
<view wx:elif="{{ score >= 60 }}">及格</view>
<view wx:else>不及格</view>

<!-- 模板引用 -->
<import src="../templates/header.wxml" />
<template is="tpl_name" data="{{ ...tplData }}" />

<!-- 组件引用 -->
<my-component propA="{{ value }}" bindmyevent="onMyEvent" />

事件绑定

html
<!-- 冒泡事件:bind + 事件名 -->
<view bindtap="onTap">点击我</view>

<!-- 非冒泡事件:catch + 事件名 -->
<view catchtap="onCatchTap">阻止冒泡</view>

<!-- 事件传参:data-* 属性 -->
<view data-id="123" data-name="Alice" bindtap="onTap">传参</view>

<!-- input / textarea 双向绑定 -->
<input value="{{ inputValue }}" bindinput="onInput" />
javascript
// 对应 JS
onTap(e) {
  const { id, name } = e.currentTarget.dataset;
  console.log(id, name); // 123, Alice
},
onCatchTap(e) {
  // 不会触发父级冒泡
}

指令速查

指令 作用
wx:if 条件渲染(不渲染 DOM)
wx:elif 条件分支
wx:else 条件兜底
hidden 条件隐藏(渲染 DOM,display:none)
wx:for 列表渲染
wx:key 列表 key(建议用唯一字段或 *this
wx:model 双向绑定(2.9.x+,基础库需 2.9+)

2.2 WXSS 样式

选择器(仅支持部分)

css
/* 支持的选择器 */
.selector {}              /* 类选择器 */
#id {}                    /* id 选择器 */
view {}                   /* 标签选择器 */
view, .class {}           /* 并集 */
view .child {}            /* 后代 */
view > .child {}          /* 子选择器 */

/* 不支持:通配符 *、属性选择器 [attr]、伪类 */

rpx 响应式单位

css
/* iPhone 6: 1px = 2rpx;750 设计稿宽度等于 375rpx */
.box {
  width: 750rpx;     /* 全屏宽度 */
  height: 200rpx;
  padding: 20rpx;
}

样式导入 & 全局样式

css
/* 在 app.wxss 中定义全局样式 */
page {
  --primary-color: #1890ff;
  background-color: #f5f5f5;
  font-size: 28rpx;
}

/* 局部页面引用 */
@import "./template.wxss";

基础样式示例

css
/* flex 布局 */
.flex-row {
  display: flex;
  flex-direction: row;
  align-items: center;
  justify-content: space-between;
}

/* 单行省略 */
.ellipsis {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

/* 多行省略 */
.line-clamp-2 {
  display: -webkit-box;
  -webkit-box-orient: vertical;
  -webkit-line-clamp: 2;
  overflow: hidden;
}

2.3 JavaScript(与浏览器的差异)

App() / Page() / Component() 注册

javascript
// app.js
App({
  globalData: {
    userInfo: null,
    baseUrl: 'https://api.example.com'
  },
  onLaunch() {
    console.log('小程序启动');
  }
});

// page.js
Page({
  data: {
    name: '微信小程序',
    count: 0
  },
  onLoad(options) {
    // 页面加载,options 为路由参数
    console.log(options.id);
  },
  onShow() { /* 页面显示 */ },
  onReady() { /* 页面首次渲染完成 */ },
  onHide() { /* 页面隐藏 */ },
  onUnload() { /* 页面卸载 */ },
  onPullDownRefresh() { /* 下拉刷新 */ },
  onReachBottom() { /* 上拉触底 */ },
  methods: {
    addCount() {
      this.setData({ count: this.data.count + 1 });
    }
  }
});

setData 的正确姿势

javascript
// ❌ 错误:每次都传整个列表
this.setData({ list: newList });

// ✅ 正确:按路径更新(减少数据传输量)
this.setData({ 'list[0].name': '新名字' });

// ✅ 正确:合并更新
this.setData({
  count: this.data.count + 1,
  'user.name': 'Alice',
  'arr[0]': itemData
});

// ✅ 正确:预先合并再 setData(一次合并,一次更新)
const update = {};
update['list[' + index + '].read'] = true;
this.setData(update);

Component 构造器

javascript
Component({
  properties: {
    title: { type: String, value: '' },
    items: { type: Array, value: [] }
  },
  data: {},
  lifetimes: {
    created() { /* 组件实例创建 */ },
    attached() { /* 组件进入页面节点树 */ },
    ready() { /* 组件布局完成 */ },
    moved() { /* 组件被移动 */ },
    detached() { /* 组件离开页面节点树 */ }
  },
  pageLifetimes: {
    show() { /* 组件所在页面显示 */ },
    hide() { /* 组件所在页面隐藏 */ }
  },
  methods: {
    onTap() { this.triggerEvent('customevent', { value: 123 }); }
  }
});

使用 behaviors 复用代码

javascript
// behaviors/my_behavior.js
module.exports = Behavior({
  data: { shared: 'shared data' },
  methods: { sharedMethod() { console.log('shared'); } }
});

// 组件中使用
Component({
  behaviors: [require('../behaviors/my_behavior.js')]
});

ES6+ 语法支持

javascript
// async/await(需基础库 2.8+)
async fetchData() {
  try {
    const res = await wx.cloud.callContainer({ path: '/api/list' });
    this.setData({ list: res.data });
  } catch (err) {
    console.error(err);
  }
}

// 解构
const { id, name } = e.currentTarget.dataset;

⚠️ 注意:不支持 windowdocumentDOM API。不支持 ES2022 以外的特性请确认兼容性。


3. 生命周期

3.1 应用生命周期(app.js)

javascript
App({
  onLaunch(options) {
    // 小程序初始化(冷启动)
    // options.scene, options.query, options.path
  },
  onShow(options) {
    // 小程序启动或从后台切回前台
  },
  onHide() {
    // 小程序进入后台(home 键、切换应用)
  },
  onError(error) {
    // 线上错误监听
    console.error(error);
  },
  onPageNotFound(res) {
    // 页面不存在时触发
    wx.redirectTo({ url: 'pages/index/index' });
  },
  onUnhandledRejection(res) {
    // 未处理的 Promise 拒绝
  }
});

3.2 页面生命周期

javascript
Page({
  data: { content: '' },

  onLoad(query) {
    // 页面加载时调用(只触发一次)
    // query: 路由参数
    this.loadData(query.id);
  },

  onShow() {
    // 页面显示(每次切回页面都触发)
  },

  onReady() {
    // 页面初次渲染完成(只触发一次)
  },

  onHide() {
    // 页面隐藏(navigateTo/redirectTo/tab 切换)
  },

  onUnload() {
    // 页面卸载(navigateBack/redirectTo/close)
  },

  onPullDownRefresh() {
    // 下拉刷新(需在 app.json window.enablePullDownRefresh: true)
    this.fetchData().then(() => wx.stopPullDownRefresh());
  },

  onReachBottom() {
    // 上拉触底
    this.loadMore();
  },

  onPageScroll(Object) {
    // 页面滚动(慎用,避免在 scroll-view 中使用)
  },

  onShareAppMessage() {
    // 分享配置
    return { title: '分享标题', path: '/pages/index/index?id=123' };
  },

  onResize() { /* 屏幕旋转 */ },
  onTabItemTap(item) { /* tabBar 点击 */ }
});

3.3 组件生命周期

javascript
Component({
  lifetimes: {
    created() { /* 实例创建,可设置 data */ },
    attached() { /* 节点树挂载完毕 */ },
    ready() { /* 渲染完成 */ },
    moved() { /* 组件被移动 */ },
    detached() { /* 离开节点树 */ }
  },
  // 所在页面生命周期(可选)
  pageLifetimes: {
    show() { /* 页面显示 */ },
    hide() { /* 页面隐藏 */ },
    resize(size) { /* 页面尺寸变化 */ }
  }
});

3.4 生命周期时序图

用户打开小程序
    │
    ▼
app.onLaunch()
    │
    ▼
page.onLoad() ──────────────────────────────────→ 用户切后台
    │                                              │
    ▼                                              ▼
page.onShow()                                  app.onHide()
    │
    ▼
page.onReady()
    │
    ▼
用户交互(可随时触发)
    │
    ├─→ page.onHide() (navigateTo/tab切换)
    │
    ├─→ page.onUnload() (navigateBack/redirectTo)
    │
    ├─→ page.onPullDownRefresh()
    │
    └─→ page.onReachBottom()

4. 常用组件

4.1 视图容器

view — 替代 div

html
<view class="container">
  <view class="card">内容卡片</view>
</view>
属性 类型 说明
hover Boolean 是否开启点击态
hover-class String 点击态类名
hover-start-time Number 按住多久(ms)触发
hover-stay-time Number 离开后保留时间

scroll-view — 滚动容器

html
<scroll-view
  scroll-y
  scroll-top="{{ scrollTop }}"
  scroll-into-view="{{ toView }}"
  bindscroll="onScroll"
  lower-threshold="100"
  bindscrolltolower="onReachBottom"
>
  <view id="item-100">锚点内容</view>
</scroll-view>
属性 说明
scroll-x/y 开启横向/纵向滚动
scroll-top 滚动到 Y 位置
scroll-into-view 滚动到指定 id 元素
refresher-enabled 开启下拉刷新
bindscrolltolower/upper 触底/触顶事件

swiper — 轮播

html
<swiper
  indicator-dots
  autoplay="{{ true }}"
  interval="3000"
  circular
  bindchange="onSwiperChange"
>
  <swiper-item wx:for="{{ banners }}">
    <image src="{{ item.imageUrl }}" mode="aspectFill" />
  </swiper-item>
</swiper>

movable-area / movable-view — 移动

html
<movable-area style="width: 300px; height: 300px;">
  <movable-view x="{{ x }}" y="{{ y }}" direction="all" bindchange="onMove">
    可拖动内容
  </movable-view>
</movable-area>

4.2 表单组件

button — 按钮

html
<!-- 开放能力按钮 -->
<button
  open-type="share"
  size="default"
  type="primary"
  plain="{{ false }}"
  disabled="{{ false }}"
  loading="{{ false }}"
  bindgetuserinfo="onGetUserInfo"
>分享</button>

<!-- 微信开放能力列表 -->
<!--
  contact            → 客服消息
  share              → 分享
  getPhoneNumber     → 获取手机号
  getUserInfo        → 获取用户信息
  launchApp          → 打开 App
  openSetting        → 打开设置页
  feedback           → 反馈
  chooseAvatar       → 选择头像
-->

input — 输入框

html
<input
  type="text"
  password="{{ false }}"
  placeholder="请输入"
  value="{{ inputValue }}"
  bindinput="onInput"
  bindfocus="onFocus"
  bindblur="onBlur"
  bindconfirm="onConfirm"
  maxlength="140"
  cursor-spacing="10"
/>

<!-- type 可选:text/number/idcard/digit/safe-password -->

textarea — 多行文本

html
<textarea
  value="{{ content }}"
  placeholder="请输入内容"
  maxlength="500"
  auto-height
  bindinput="onTextareaInput"
  style="min-height: 200rpx;"
/>

picker — 选择器

html
<!-- 普通选择器 -->
<picker mode="selector" range="{{ array }}" bindchange="onChange">
  <view>{{ array[index] }}</view>
</picker>

<!-- 时间选择器 -->
<picker mode="time" value="{{ time }}" start="09:00" end="18:00" bindchange="onTimeChange">
  <view>选择时间: {{ time }}</view>
</picker>

<!-- 日期选择器 -->
<picker mode="date" value="{{ date }}" start="2020-01-01" end="2030-12-31" fields="day" bindchange="onDateChange">
  <view>选择日期: {{ date }}</view>
</picker>

<!-- 多列选择器 -->
<picker mode="multiSelector" range="{{ multiArray }}" bindchange="onMultiChange">
  <view>{{ multiArray[0][multiIndex[0]] }} - {{ multiArray[1][multiIndex[1]] }}</view>
</picker>

checkbox-group / radio-group — 多选/单选

html
<checkbox-group bindchange="onCheckboxChange">
  <label wx:for="{{ options }}" wx:key="id">
    <checkbox value="{{ item.id }}" checked="{{ item.checked }}" />
    {{ item.name }}
  </label>
</checkbox-group>

<radio-group bindchange="onRadioChange">
  <label wx:for="{{ options }}" wx:key="id">
    <radio value="{{ item.id }}" />
    {{ item.name }}
  </label>
</radio-group>

switch / slider — 开关/滑动条

html
<switch checked="{{ switchChecked }}" bindchange="onSwitchChange" />
<slider min="0" max="100" value="{{ sliderVal }}" show-value bindchange="onSliderChange" />

form — 表单提交

html
<form bindsubmit="onFormSubmit" bindreset="onFormReset">
  <input name="username" placeholder="用户名" />
  <input name="password" password placeholder="密码" />
  <button form-type="submit">提交</button>
  <button form-type="reset">重置</button>
</form>
javascript
onFormSubmit(e) {
  console.log(e.detail.value); // { username: '...', password: '...' }
}

4.3 媒体组件

image — 图片

html
<image
  src="{{ imgUrl }}"
  mode="aspectFill"
  lazy-load="{{ true }}"
  show-menu-by-longpress
  binderror="onImgError"
  bindload="onImgLoad"
/>

mode 常用值:scaleToFill | aspectFit | aspectFill | widthFix | heightFix | top/center/...

video — 视频

html
<video
  src="{{ videoUrl }}"
  poster="{{ posterUrl }}"
  controls
  autoplay="{{ false }}"
  loop="{{ false }}"
  muted="{{ false }}"
  enable-danmu
  danmu-list="{{ danmuList }}"
  bindtimeupdate="onTimeUpdate"
  bindended="onEnded"
  enable-play-gesture="{{ true }}"
  show-fullscreen-btn
>
</video>

camera — 相机

html
<camera
  device-position="back"
  flash="auto"
  binderror="onCameraError"
  style="width: 100%; height: 400rpx;"
/>
<button bindtap="takePhoto">拍照</button>
javascript
const ctx = wx.createCameraContext();
ctx.takePhoto({
  quality: 'high',
  success: (res) => this.setData({ tempPhotoPath: res.tempImagePath })
});

editor — 富文本编辑(基础库 2.7+)

html
<editor
  id="editor"
  placeholder="输入内容..."
  bindinput="onEditorInput"
  read-only="{{ false }}"
/>

4.4 导航与路由展示

navigator — 页面链接

html
<navigator
  url="/pages/detail/index?id={{ id }}"
  open-type="navigateTo"
  hover-class="none"
>
  跳转详情
</navigator>
open-type 行为
navigate 跳转到 tabBar 页面
redirect 关闭当前页,重定向
switchTab 跳转到 tabBar 页面
navigateBack 返回上一页
reLaunch 关闭所有页面,打开到应用内页
exit 退出小程序(需配置 downloadFileDomain)

functional-page-navigator — 跳转功能页

html
<functional-page-navigator
  name="chooseAddress"
  version="develop"
  bindsuccess="onChooseAddressSuccess"
  bindfail="onChooseAddressFail"
>
  选择收货地址
</functional-page-navigator>

4.5 地图组件(map)

html
<map
  latitude="{{ latitude }}"
  longitude="{{ longitude }}"
  scale="14"
  markers="{{ markers }}"
  polyline="{{ polylines }}"
  circles="{{ circles }}"
  controls="{{ controls }}"
  show-location
  include-points="{{ includePoints }}"
  bindmarkertap="onMarkerTap"
  bindtap="onMapTap"
  style="width: 100%; height: 500rpx;"
/>
javascript
// markers 格式
markers: [{
  id: 1,
  latitude: 31.230416,
  longitude: 121.473701,
  title: '上海',
  iconPath: '/images/marker.png',
  width: 30,
  height: 30,
  callout: {
    content: '上海',
    color: '#333',
    fontSize: 14,
    borderRadius: 4,
    padding: 8,
    display: 'ALWAYS',
    bgColor: '#fff'
  }
}]

4.6 其他常用组件

icon — 图标

html
<icon type="success" size="40" color="#07c160" />
<!-- type: success/success_no_circle/info/warn/waiting/cancel/search/clear -->

progress — 进度条

html
<progress percent="60" show-info stroke-width="6" color="#07c160" />

rich-text — 富文本渲染

html
<rich-text nodes="{{ htmlString }}" />
javascript
// 支持 HTML string 或 node 数组
this.setData({
  htmlString: '<p style="color:#999">内容</p>'
});

open-data — 展示用户信息(无需授权)

html
<open-data type="userNickName" lang="zh_CN" />
<open-data type="userAvatarUrl" />
<open-data type="userGender" />
<open-data type="userCity" />

web-view — 嵌入网页(需配置业务域名)

html
<!-- 需在 app.json 添加 web-view 域名白名单 -->
<web-view src="https://example.com/page.html" bindmessage="onWebViewMessage" />

5. 常用 API

5.1 路由跳转

javascript
// 保留当前页面,跳转(非 tabBar)
wx.navigateTo({ url: '/pages/detail/index?id=123' });

// 关闭当前页面,跳转(非 tabBar)
wx.redirectTo({ url: '/pages/detail/index' });

// 跳转 tabBar 页面
wx.switchTab({ url: '/pages/index/index' });

// 关闭所有页面,打开
wx.reLaunch({ url: '/pages/index/index' });

// 返回上一页
wx.navigateBack({ delta: 1 });

// 获取当前页面栈
const pages = getCurrentPages();
const currentPage = pages[pages.length - 1];

⚠️ 页面栈最多 10 层,navigateTo 超过 10 层会失败,建议用 reLaunch 或手动 navigateBack 控制。


5.2 数据存储

javascript
// 同步存储
wx.setStorageSync('token', 'abc123');
wx.setStorageSync('user', { name: 'Alice', age: 18 });

// 异步存储
wx.setStorage({ key: 'token', data: 'abc123' });

// 同步读取
const token = wx.getStorageSync('token');
const user = wx.getStorageSync('user');

// 异步读取
wx.getStorage({ key: 'token', success: (res) => console.log(res.data) });

// 同步/异步删除
wx.removeStorageSync('token');
wx.removeStorage({ key: 'token' });

// 清空
wx.clearStorageSync();
wx.clearStorage();

// 获取存储信息
wx.getStorageInfo({
  success: (res) => {
    console.log(res.keys);      // ['token', 'user']
    console.log(res.currentSize); // 当前大小(KB)
    console.log(res.limitSize);    // 限制大小(KB)
  }
});

⚠️ storage 容量 10MB,同步 API 在数据量大时会阻塞主线程,建议用异步 API。


5.3 网络请求

javascript
// ⚠️ 必须在 app.json 配置合法域名(开发阶段可勾选"不校验...")
wx.request({
  url: 'https://api.example.com/user/profile',
  method: 'GET',
  data: { id: 123 },
  header: {
    'content-type': 'application/json',
    'Authorization': 'Bearer ' + wx.getStorageSync('token')
  },
  success: (res) => {
    if (res.statusCode === 200) {
      this.setData({ profile: res.data });
    } else if (res.statusCode === 401) {
      // token 过期,跳转登录
      wx.redirectTo({ url: '/pages/login/index' });
    }
  },
  fail: (err) => {
    wx.showToast({ title: '网络请求失败', icon: 'none' });
    console.error(err);
  },
  complete: () => {
    this.setData({ loading: false });
  }
});

// ⚠️ request 最大并发 10 个,超过会等待
// ⚠️ 超时时间默认 60s,最长 120s

封装请求工具(推荐)

javascript
// utils/request.js
const BASE_URL = 'https://api.example.com';

function request({ url, data, method = 'GET', needAuth = true }) {
  return new Promise((resolve, reject) => {
    const header = { 'content-type': 'application/json' };
    if (needAuth) {
      const token = wx.getStorageSync('token');
      if (!token) return reject({ errMsg: 'no_token' });
      header['Authorization'] = `Bearer ${token}`;
    }

    wx.showLoading({ title: '加载中...' });
    wx.request({
      url: BASE_URL + url,
      data,
      method,
      header,
      success: (res) => {
        if (res.statusCode === 200) {
          resolve(res.data);
        } else if (res.statusCode === 401) {
          wx.removeStorageSync('token');
          wx.redirectTo({ url: '/pages/login/index' });
          reject(res);
        } else {
          wx.showToast({ title: res.data.message || '请求失败', icon: 'none' });
          reject(res);
        }
      },
      fail: reject,
      complete: () => wx.hideLoading()
    });
  });
}

module.exports = { request };

5.4 设备相关

javascript
// 获取系统信息
wx.getSystemInfo({
  success: (res) => {
    console.log(res.brand, res.model, res.system);
    console.log(res.statusBarHeight, res.screenHeight);
    console.log(res.windowWidth, res.windowHeight);
    // iPhone X: statusBarHeight=44, 底部安全区需注意
  }
});

// 获取设备信息(更快,不依赖网络)
wx.getDeviceInfo?.(); // 基础库 2.25+

// 剪贴板
wx.setClipboardData({ data: '复制内容' });
wx.getClipboardData({
  success: (res) => console.log(res.data)
});

// 扫码
wx.scanCode({
  onlyFromCamera: false,
  success: (res) => console.log(res.result)
});

// 振动
wx.vibrateShort({ type: 'light' }); // 短振动
wx.vibrateLong(); // 长振动(400ms)

// 屏幕亮度
wx.setScreenBrightness({ value: 0.5 });
wx.getScreenBrightness({
  success: (res) => console.log(res.value)
});

// 保持屏幕常亮
wx.setKeepScreenOn({ keepScreenOn: true });

// 打电话
wx.makePhoneCall({ phoneNumber: '400-xxx-xxxx' });

// 发送邮件(需 App 配合)
wx.env && wx.env.openEmail?.();

5.5 位置服务

javascript
// 获取当前位置
wx.getLocation({
  type: 'wgs84',       // wgs84 | gcj02(国测局坐标,地图默认)
  altitude: false,     // 是否返回海拔(基础库 1.2+)
  isHighAccuracy: true, // 开启高精度定位(基础库 2.9+)
  success: (res) => {
    console.log(res.latitude, res.longitude);
    console.log(res.speed, res.accuracy);
  },
  fail: (err) => {
    if (err.errMsg.includes('auth deny')) {
      wx.showModal({ title: '提示', content: '需要位置权限,请在设置中开启' });
    }
  }
});

// 打开地图选择位置(带交互)
wx.chooseLocation({
  success: (res) => {
    console.log(res.name, res.address, res.latitude, res.longitude);
  }
});

// 打开内置地图查看位置
wx.openLocation({
  latitude: 31.230416,
  longitude: 121.473701,
  name: '上海',
  address: '上海市黄浦区',
  scale: 15
});

5.6 图片与文件

javascript
// 选择图片
wx.chooseImage({
  count: 9,           // 默认 9
  sizeType: ['original', 'compressed'], // 原图/压缩
  sourceType: ['album', 'camera'],      // 相册/相机
  success: (res) => {
    const paths = res.tempFilePaths;
    console.log(paths); // 临时文件路径(本地有效)
  }
});

// 预览图片
wx.previewImage({
  current: urls[0],
  urls: urls  // 需要是 https 图片
});

// 保存图片到相册
wx.saveImageToPhotosAlbum({
  filePath: tempFilePath,
  success: () => wx.showToast({ title: '保存成功' })
});

// 保存文件到本地
wx.saveFile({
  tempFilePath: res.tempFilePath,
  success: (res) => {
    console.log(res.savedFilePath);
  }
});

// 获取文件信息
wx.getFileInfo({
  filePath: 'http://...', // 本地或 https 路径
  success: (res) => console.log(res.size, res.digest)
});

5.7 交互反馈

javascript
// toast(自动消失)
wx.showToast({ title: '操作成功', icon: 'success', duration: 2000 });
wx.showToast({ title: '网络异常', icon: 'error', duration: 2000 });
wx.showToast({ title: '提示信息', icon: 'none', duration: 2000 });

// modal 对话框
wx.showModal({
  title: '确认删除',
  content: '确定要删除这条记录吗?',
  confirmColor: '#07c160',
  success: (res) => {
    if (res.confirm) console.log('用户点击确定');
    if (res.cancel) console.log('用户点击取消');
  }
});

// loading 遮罩
wx.showLoading({ title: '加载中...', mask: true });
setTimeout(() => wx.hideLoading(), 3000);

// actionSheet 底部菜单
wx.showActionSheet({
  itemList: ['选项1', '选项2', '选项3'],
  success: (res) => console.log(res.tapIndex)
});

// 导航栏加载状态
wx.showNavigationBarLoading();
wx.hideNavigationBarLoading();

// 页面滚动
wx.pageScrollTo({ scrollTop: 0, duration: 300 });

5.8 动画

javascript
// ⚠️ 方式一:WXS 响应式动画(推荐,脱离主线程,更流畅)
// ⚠️ 方式二:Animation API(注意与 Skyline 不兼容)

// Animation 方式
const animation = wx.createAnimation({
  transformOrigin: '50% 50%',
  duration: 300,
  timingFunction: 'ease'
});

this.animation = animation;

// 连续动画链
animation.opacity(0).scale(0.8).step();
animation.opacity(1).scale(1).step();
this.setData({ animation: animation.export() });

// 单帧设置
this.setData({
  animationData: animation.rotate(180).step().export()
});

6. 授权体系

6.1 scope 权限列表

scope 说明 申请方式
scope.userInfo 用户信息 button open-type="getUserInfo"
scope.userLocation 地理位置 wx.getLocation
scope.userLocationBackground 后台定位 需通过审核申请
scope.werun 微信运动步数 wx.getWeRunData
scope.writePhotosAlbum 保存到相册 wx.saveImageToPhotosAlbum
scope.camera 摄像头 <camera> 组件
scope.record 麦克风 wx.getRecorderManager
scope.address 通讯地址 wx.chooseAddress
scope.invoiceTitle 发票抬头 wx.chooseInvoiceTitle
scope.invoice 获取发票 wx.chooseInvoice
scope.vehicleNumber 车牌号 wx.chooseVehicleInfo

6.2 授权流程

javascript
// 方式一:button 触发(推荐)
// wxml
<button open-type="getUserInfo" bindgetuserinfo="onGetUserInfo">获取用户信息</button>

// js
onGetUserInfo(e) {
  if (e.detail.errMsg.includes('ok')) {
    const userInfo = e.detail.userInfo;
    const encryptedData = e.detail.encryptedData;
    const iv = e.detail.iv;
    // 发送给后端解密
    this.loginWithBackend(userInfo, encryptedData, iv);
  } else {
    wx.showToast({ title: '需要授权才能继续', icon: 'none' });
  }
}

// 方式二:主动检查 + 请求(wx.getSetting)
wx.getSetting({
  success: (res) => {
    if (!res.authSetting['scope.userLocation']) {
      wx.authorize({
        scope: 'scope.userLocation',
        success: () => { /* 已授权 */ },
        fail: () => {
          // 用户拒绝 → 引导打开设置
          wx.showModal({
            title: '提示',
            content: '需要位置权限,请在设置中打开',
            success: (modalRes) => {
              if (modalRes.confirm) {
                wx.openSetting();
              }
            }
          });
        }
      });
    } else {
      // 已授权,直接获取
      this.getLocation();
    }
  }
});

6.3 获取手机号(需企业认证)

javascript
// wxml
<button open-type="getPhoneNumber" bindgetphonenumber="onGetPhoneNumber">绑定手机号</button>

// js
onGetPhoneNumber(e) {
  if (e.detail.errMsg === 'getPhoneNumber:ok') {
    wx.request({
      url: 'https://api.example.com/auth/decodePhone',
      method: 'POST',
      data: {
        code: e.detail.code  // 服务端用 code 换取手机号
      }
    });
  }
}

6.4 头像选择(基础库 2.21+)

html
<button open-type="chooseAvatar" bindchooseavatar="onChooseAvatar">选择头像</button>
javascript
onChooseAvatar(e) {
  const { avatarUrl } = e.detail;
  // 上传头像到服务器
  wx.uploadFile({
    url: 'https://api.example.com/upload',
    filePath: avatarUrl,
    name: 'avatar',
    success: (res) => {
      console.log(JSON.parse(res.data).url);
    }
  });
}

7. 登录态维持

7.1 微信登录完整流程

┌─────────────┐         ┌─────────────┐         ┌─────────────┐
│   小程序     │         │   微信平台   │         │   后端服务器  │
└──────┬──────┘         └──────┬──────┘         └──────┬──────┘
       │                       │                       │
       │  1. wx.login()        │                       │
       │───────────────────────►│                       │
       │  拿到 code            │                       │
       │◄───────────────────────│                       │
       │                       │                       │
       │  2. 发送 code + iv + encryptedData            │
       │──────────────────────────────────────────────►│
       │                       │         3. 用 code 换  │
       │                       │      openid + session │
       │                       │◄──────────────────────│
       │                       │         4. 解密获取    │
       │                       │      unionid/userInfo  │
       │  5. 返回 openid/session_key/ticket             │
       │◄──────────────────────────────────────────────│
       │                       │                       │
       │  6. 存储 session,标记登录态                   │
       │                       │                       │

7.2 前端实现代码

javascript
// utils/auth.js

/**
 * 微信小程序登录
 * @returns {Promise<{token: string, userInfo: object}>}
 */
function wxLogin() {
  return new Promise((resolve, reject) => {
    // 1. 获取 code
    wx.login({
      success: (loginRes) => {
        if (!loginRes.code) return reject(new Error('未获取到 code'));

        // 2. 获取用户信息(可选,button 触发更合规)
        wx.getUserProfile({
          desc: '用于完善用户资料',
          success: (userRes) => {
            // 3. 发送到后端
            wx.request({
              url: `${BASE_URL}/auth/wx-login`,
              method: 'POST',
              data: {
                code: loginRes.code,
                encryptedData: userRes.encryptedData,
                iv: userRes.iv,
                rawData: userRes.rawData,
                signature: userRes.signature
              },
              success: (res) => {
                if (res.statusCode === 200 && res.data.token) {
                  // 4. 存储 token 和用户信息
                  wx.setStorageSync('token', res.data.token);
                  wx.setStorageSync('userInfo', res.data.userInfo);
                  resolve(res.data);
                } else {
                  reject(new Error(res.data.message || '登录失败'));
                }
              },
              fail: reject
            });
          },
          fail: (err) => {
            wx.showToast({ title: '需要授权才能继续', icon: 'none' });
            reject(err);
          }
        });
      },
      fail: reject
    });
  });
}

/**
 * 检查登录态(带自动登录)
 */
function checkSession() {
  return new Promise((resolve, reject) => {
    wx.checkSession({
      success: () => resolve(true),
      fail: () => {
        // session 过期,重新登录
        wxLogin().then(resolve).catch(reject);
      }
    });
  });
}

/**
 * 获取当前 token(带自动续期检查)
 */
function getToken() {
  const token = wx.getStorageSync('token');
  if (!token) return null;
  checkSession().catch(() => {
    wx.removeStorageSync('token');
    wx.removeStorageSync('userInfo');
  });
  return token;
}

/**
 * 登出
 */
function logout() {
  wx.removeStorageSync('token');
  wx.removeStorageSync('userInfo');
  wx.removeStorageSync('openid');
}

module.exports = { wxLogin, checkSession, getToken, logout };

7.3 在业务页面中使用

javascript
// pages/index/index.js
const { getToken, checkSession } = require('../../utils/auth');

Page({
  data: { userInfo: null, isLogin: false },

  onLoad() {
    this.checkLogin();
  },

  async checkLogin() {
    const userInfo = wx.getStorageSync('userInfo');
    if (!userInfo) return;

    try {
      await checkSession();
      this.setData({ userInfo, isLogin: true });
    } catch (err) {
      this.setData({ userInfo: null, isLogin: false });
    }
  },

  async onLogin() {
    try {
      const { wxLogin } = require('../../utils/auth');
      const result = await wxLogin();
      this.setData({ userInfo: result.userInfo, isLogin: true });
      wx.showToast({ title: '登录成功' });
    } catch (err) {
      wx.showToast({ title: '登录失败', icon: 'none' });
    }
  }
});

7.4 无感刷新 token(请求拦截器)

javascript
// utils/interceptor.js

function requestWithAuth(options) {
  const token = wx.getStorageSync('token');
  if (token) {
    options.header = options.header || {};
    options.header['Authorization'] = `Bearer ${token}`;
  }

  return new Promise((resolve, reject) => {
    wx.request({
      ...options,
      fail: reject
    });
  });
}

// app.js - 全局拦截
App({
  onLaunch() {
    // 全局 request 封装或使用第三方库如 Fly.js / Taro-request
  }
});

8. 分包加载

8.1 为什么需要分包

  • 单个主包不能超过 2MB
  • 分包可降低首次下载时间
  • 分包总量不超过 20MB(主包+所有分包)
  • 单个分包不超过 2MB

8.2 分包配置

根目录 app.json

json
{
  "pages": [
    "pages/index/index",
    "pages/user/index"
  ],
  "subPackages": [
    {
      "root": "subpkg/games",
      "name": "games",
      "pages": [
        { "path": "pages/index/index", "lazyLoad": true },
        { "path": "pages/detail/index" }
      ]
    },
    {
      "root": "subpkg/shop",
      "pages": [
        { "path": "pages/cart/index" },
        { "path": "pages/order/index" }
      ]
    }
  ]
}

每个分包需要有独立的 app.json(可省略,仅当有独立样式/window 配置时需要)

8.3 分包页面跳转

javascript
// ❌ 无法直接跳转到分包页面(分包 root 外层需要用相对路径)
wx.navigateTo({ url: '/pages/index/index' }); // 主包

// ✅ 分包内跳转
wx.navigateTo({ url: 'pages/detail/index' }); // 相对路径

// ✅ 从主包跳转到分包页面(可以用绝对路径)
wx.navigateTo({ url: '/subpkg/games/pages/index/index' });

// ✅ 分包内跳转到其他分包(需要用绝对路径)
wx.navigateTo({ url: '/subpkg/shop/pages/cart/index' });

// ✅ 分包内 switchTab(主包 tabBar 页面)
wx.switchTab({ url: '/pages/index/index' });

8.4 分包预加载规则

json
// app.json
{
  "preloadRule": {
    "pages/index/index": {
      "network": "all",      // all | wifi
      "packages": ["subpkg/games"]
    },
    "pages/user/index": {
      "network": "wifi",
      "packages": ["subpkg/shop"]
    }
  }
}

预加载规则:在指定主包页面加载时,提前下载分包。

8.5 分包间共享

共享内容 是否支持
主包 components/ ✅ 支持
主包 utils/ ✅ 支持(通过 require('../../utils/...')
主包 app.wxss ✅ 样式自动共享
分包间 components ❌ 不支持(需各自在分包内定义)
分包间共享 ❌ 不支持(需通过主包中转或本地存储)

8.6 分包独立分包

json
{
  "root": "subpkg/independent",
  "independent": true
}

独立分包:可独立运行,不依赖主包。可用于广告落地页、分享页等场景。


9. 云开发

9.1 环境初始化

javascript
// cloudfunctions/common/utils.js
const cloud = require('wx-server-sdk');
cloud.init({ env: cloud.DYNAMIC_CURRENT_ENV }); // 自动使用当前环境

// 云函数入口
const db = cloud.database();
const _ = db.command;
const $ = _.aggregate;

9.2 云数据库

javascript
// 前端 - 增删改查
wx.cloud.init(); // 在 app.js 中初始化一次

// 插入
wx.cloud.database().collection('users').add({
  data: { name: 'Alice', age: 18, createTime: db.serverDate() }
}).then(res => console.log(res._id));

// 查询
wx.cloud.database().collection('users')
  .where({ name: 'Alice' })
  .field({ name: true, age: true })
  .orderBy('createTime', 'desc')
  .skip(0)
  .limit(20)
  .get()
  .then(res => this.setData({ list: res.data }));

// 更新(字段更新)
wx.cloud.database().collection('users')
  .doc(id)
  .update({ data: { age: _.inc(1) } }); // 原子递增

// 删除
wx.cloud.database().collection('users').doc(id).remove();

9.3 云存储

javascript
// 上传文件
wx.cloud.uploadFile({
  cloudPath: 'avatars/' + Date.now() + '.png',
  filePath: tempFilePath, // 临时文件路径
  success: (res) => console.log(res.fileID)
});

// 下载文件获取临时链接
wx.cloud.getTempFileURL({
  fileList: [fileID1, fileID2],
  success: (res) => {
    res.fileList.forEach(item => {
      if (item.status === 0) {
        console.log(item.tempFileURL); // https URL
      }
    });
  }
});

// 删除文件
wx.cloud.deleteFile({ fileList: [fileID] });

9.4 云函数

javascript
// cloudfunctions/login/index.js
const cloud = require('wx-server-sdk');
cloud.init({ env: cloud.DYNAMIC_CURRENT_ENV });

exports.main = async (event, context) => {
  const wxContext = cloud.getWXContext();
  const { code } = event;

  // 调用微信接口获取 openid
  const res = await cloud.cloudbase.callContainer({
    service: 'auth',
    path: '/code2session',
    method: 'GET',
    data: { appid: wxContext.APPID, code, grant_type: 'authorization_code' }
  });

  return {
    openid: res.openid,
    sessionKey: res.session_key,
    appid: wxContext.APPID
  };
};

10. 服务端常用 API

所有服务端接口域名:https://api.weixin.qq.com 除获取 access_token 外,所有接口均需在 URL 中携带 ?access_token=ACCESS_TOKEN

10.1 获取 access_token

所有服务端 API 的前置步骤,token 有效期 7200 秒(2 小时),生产环境必须全局缓存,不可每次请求都重新获取。

GET https://api.weixin.qq.com/cgi-bin/token
    ?grant_type=client_credential
    &appid=APPID
    &secret=APPSECRET

返回示例

json
{
  "access_token": "ACCESS_TOKEN",
  "expires_in": 7200
}

Node.js 封装示例

javascript
// utils/wxToken.js
const axios = require('axios');
const { appid, secret } = require('../config');

let cachedToken = null;
let tokenExpireTime = 0;

async function getAccessToken() {
  const now = Date.now();
  // 提前 5 分钟刷新
  if (cachedToken && now < tokenExpireTime - 5 * 60 * 1000) {
    return cachedToken;
  }

  const url = `https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=${appid}&secret=${secret}`;
  const res = await axios.get(url);

  if (res.data.access_token) {
    cachedToken = res.data.access_token;
    tokenExpireTime = now + res.data.expires_in * 1000;
    return cachedToken;
  }
  throw new Error('获取 access_token 失败: ' + JSON.stringify(res.data));
}

module.exports = { getAccessToken };

10.2 登录与用户

code2Session — 用 code 换取 openid / session_key

GET https://api.weixin.qq.com/sns/jscode2session
    ?appid=APPID
    &secret=SECRET
    &js_code=CODE
    &grant_type=authorization_code

返回示例

json
{
  "openid": "oUpF8uMuAJO_M2pxb1Q9zNjWeS6o",
  "session_key": "tiihtNczf5jlfXJpVJ...",
  "unionid": "o6_bmasdasdsad6_2sgVt7hMZOPfL",   // 满足条件才返回
  "errcode": 0,
  "errmsg": "ok"
}

⚠️ unionid 返回条件:1. 小程序绑定到微信开放平台;2. 用户曾经授权过同主体下的其他应用。

Node.js 示例

javascript
// services/wxAuth.js
const axios = require('axios');
const { appid, secret } = require('../config');

async function code2Session(code) {
  const url = `https://api.weixin.qq.com/sns/jscode2session?appid=${appid}&secret=${secret}&js_code=${code}&grant_type=authorization_code`;
  const res = await axios.get(url);
  if (res.data.errcode) {
    throw new Error(`code2Session 失败: ${res.data.errmsg}`);
  }
  return res.data; // { openid, session_key, unionid? }
}

/**
 * 解密 getUserInfo 返回的 encryptedData
 * 依赖:npm install crypto-js
 */
const CryptoJS = require('crypto-js');

function decryptUserInfo(encryptedData, iv, sessionKey) {
  const key = CryptoJS.enc.Base64.parse(sessionKey);
  const ivBytes = CryptoJS.enc.Base64.parse(iv);
  const encrypted = CryptoJS.enc.Base64.parse(encryptedData);

  const decrypted = CryptoJS.AES.decrypt(
    { ciphertext: encrypted },
    key,
    { iv: ivBytes, mode: CryptoJS.mode.CBC, padding: CryptoJS.pad.Pkcs7 }
  );

  const jsonStr = CryptoJS.enc.Utf8.stringify(decrypted);
  return JSON.parse(jsonStr);
}

module.exports = { code2Session, decryptUserInfo };

getPhoneNumber — 获取用户手机号(新接口,基础库 2.21.2+)

POST https://api.weixin.qq.com/wxa/business/getuserphonenumber?access_token=ACCESS_TOKEN
Content-Type: application/json

{ "code": "PHONE_CODE" }

code 来自前端 <button open-type="getPhoneNumber" bindgetphonenumber="onGetPhoneNumber"> 回调中的 e.detail.code

返回示例

json
{
  "errcode": 0,
  "errmsg": "ok",
  "phone_info": {
    "phoneNumber": "18888888888",
    "purePhoneNumber": "18888888888",
    "countryCode": "86",
    "watermark": { "appid": "APPID", "timestamp": 1234567890 }
  }
}

10.3 微信支付(小程序支付 JSAPI)

支付流程概览

小程序前端              商户后端               微信支付平台
    │                     │                      │
    │  点击支付            │                      │
    │─────────────────────►│                      │
    │                     │  1.统一下单            │
    │                     │──────────────────────►│
    │                     │  prepay_id            │
    │                     │◄──────────────────────│
    │  2.返回支付参数      │                      │
    │◄────────────────────│                      │
    │  3.wx.requestPayment│                      │
    │─────────────────────────────────────────────►│
    │                     │                      │ 4.支付结果通知
    │                     │◄──────────────────────│
    │  5.展示结果          │                      │

统一下单(V3 接口)

POST https://api.mch.weixin.qq.com/v3/pay/transactions/jsapi

请求头(V3 签名)

Authorization: WECHATPAY2-SHA256-RSA2048 mchid=\"xxx\",nonce_str=\"xxx\",signature=\"xxx\",timestamp=\"xxx\",serial_no=\"xxx\"
Content-Type: application/json

请求体

json
{
  "appid": "wx1234567890abcdef",
  "mchid": "1230000109",
  "description": "商品描述",
  "out_trade_no": "order_20240101_001",
  "notify_url": "https://api.example.com/wxpay/notify",
  "amount": {
    "total": 1,
    "currency": "CNY"
  },
  "payer": {
    "openid": "oUpF8uMuAJO_M2pxb1Q9zNjWeS6o"
  }
}

返回示例

json
{
  "prepay_id": "wx201410272009395522657a690389489100"
}

前端调起支付

javascript
// 后端返回签名后的支付参数
wx.requestPayment({
  timeStamp: '1414561699',
  nonceStr: '5K8264ILTKCH16CQ2502SI8ZNMTM67VS',
  package: 'prepay_id=wx201410272009395522657a690389489100',
  signType: 'RSA',          // V3 用 RSA,V2 用 MD5
  paySign: 'signature...',
  success: (res) => { wx.showToast({ title: '支付成功' }); },
  fail: (err) => { wx.showToast({ title: '支付失败', icon: 'none' }); }
});

支付通知(回调)处理

javascript
// routes/wxpay.js
const express = require('express');
const app = express();

app.post('/wxpay/notify', express.raw({ type: 'application/json' }), (req, res) => {
  const { event_type, resource } = req.body;
  // 1. 验签(使用微信支付平台证书)
  // 2. 解密 resource.ciphertext(AES-256-GCM)
  // 3. 处理业务逻辑(更新订单状态)
  // 4. 返回成功响应

  res.json({ code: 'SUCCESS', message: '成功' });
});

查询订单

GET https://api.mch.weixin.qq.com/v3/pay/transactions/out-trade-no/{out_trade_no}?mchid={mchid}

申请退款

POST https://api.mch.weixin.qq.com/v3/refund/domestic/refunds
json
{
  "out_trade_no": "order_20240101_001",
  "out_refund_no": "refund_001",
  "reason": "商品质量问题",
  "amount": {
    "refund": 1,
    "total": 1,
    "currency": "CNY"
  }
}

10.4 订阅消息

用户在小程序内订阅后,服务端可主动推送消息(无需用户在线)。

发送订阅消息

POST https://api.weixin.qq.com/cgi-bin/message/subscribe/send?access_token=ACCESS_TOKEN

请求体

json
{
  "touser": "oUpF8uMuAJO_M2pxb1Q9zNjWeS6o",
  "template_id": "TEMPLATE_ID",
  "page": "pages/order/index?id=123",
  "data": {
    "thing1": { "value": "您购买的课程已上线" },
    "date2": { "value": "2024-01-01 14:00" },
    "phrase3": { "value": "已发货" }
  },
  "miniprogram_state": "formal"   // developer | trial | formal
}

⚠️ data 中的字段名(thing1date2 等)必须与模板配置完全一致,值的长度也有限制。

Node.js 示例

javascript
const { getAccessToken } = require('../utils/wxToken');

async function sendSubscribeMessage(openid, templateId, data, page = '') {
  const token = await getAccessToken();
  const url = `https://api.weixin.qq.com/cgi-bin/message/subscribe/send?access_token=${token}`;

  const res = await axios.post(url, {
    touser: openid,
    template_id: templateId,
    page: page,
    data: data,
    miniprogram_state: 'formal'
  });

  if (res.data.errcode !== 0) {
    console.error('订阅消息发送失败:', res.data);
  }
  return res.data;
}

10.5 内容安全(违规内容检测)

文本安全检测(msgSecCheck)

POST https://api.weixin.qq.com/wxa/msg_sec_check?access_token=ACCESS_TOKEN
json
{
  "content": "要检测的文本",
  "version": 2,          // 使用 V2 接口(支持开放内容)
  "scene": 1             // 1:资料 2:评论 3:论坛 4:社交日志
}

返回示例

json
{
  "errcode": 0,          // 0 = 正常,87014 = 疑似违规
  "errmsg": "ok",
  "result": {
    "suggest": "pass",   // pass | review(需人工) | block(违规)
    "label": 200          // 200:正常 100:低俗 20001:涉政 ...
  }
}

图片安全检测(imgSecCheck)

POST https://api.weixin.qq.com/wxa/img_sec_check?access_token=ACCESS_TOKEN
Content-Type: multipart/form-data
javascript
const FormData = require('form-data');
const fs = require('fs');

async function checkImage(filePath) {
  const token = await getAccessToken();
  const form = new FormData();
  form.append('media', fs.createReadStream(filePath));

  const res = await axios.post(
    `https://api.weixin.qq.com/wxa/img_sec_check?access_token=${token}`,
    form,
    { headers: form.getHeaders() }
  );
  return res.data.errcode === 0; // true = 安全
}

异步音视频检测(mediaCheckAsync)

POST https://api.weixin.qq.com/wxa/media_check_async?access_token=ACCESS_TOKEN
json
{
  "media_url": "https://example.com/audio.mp3",
  "media_type": 2,     // 1:音频 2:图片
  "version": 2
}

10.6 小程序码与二维码

获取无限制小程序码(推荐,数量不限)

POST https://api.weixin.qq.com/wxa/getwxacodeunlimit?access_token=ACCESS_TOKEN
json
{
  "scene": "uid=123&ref=share",   // 最多 32 个字符
  "page": "pages/index/index",     // 必须是已发布的页面路径
  "width": 430,
  "auto_color": false,
  "line_color": { "r": 0, "g": 0, "b": 0 },
  "is_hyaline": false              // 是否为透明底色
}

返回的是二进制图片流(Buffer),需直接写入文件;scene 参数在目标页 onLoad(query) 中通过 query.scene 获取。

Node.js 保存示例

javascript
const axios = require('axios');
const fs = require('fs');
const { getAccessToken } = require('../utils/wxToken');

async function generateQRCode(scene, page) {
  const token = await getAccessToken();
  const res = await axios.post(
    `https://api.weixin.qq.com/wxa/getwxacodeunlimit?access_token=${token}`,
    { scene, page, width: 430 },
    { responseType: 'arraybuffer' }
  );

  const filePath = `./qrcodes/${scene}.png`;
  fs.writeFileSync(filePath, res.data);
  return filePath;
}

获取小程序二维码(数量有限,10 万次)

POST https://api.weixin.qq.com/cgi-bin/wxaapp/createwxaqrcode?access_token=ACCESS_TOKEN
json
{
  "path": "pages/index/index?id=123",   // 带参数路径
  "width": 430
}

10.7 近期新增重要接口(2024+)

发货信息录入(交易组件,基础库 2.18+)

小程序交易组件订单,商家发货后需主动上报微信,否则会影响评分。

POST https://api.weixin.qq.com/shop/delivery/send?access_token=ACCESS_TOKEN
json
{
  "order_id": 123456789,
  "delivery_id": "DELIVERY_ID",
  "waybill_id": "1234567890",
  "delivery_status": 1          // 1:已发货
}

小程序交易保障(用户确认收货)

POST https://api.weixin.qq.com/shop/order/confirm_receive?access_token=ACCESS_TOKEN

10.8 服务端 API 速查表

分类 接口 请求方式 路径
基础 获取 access_token GET /cgi-bin/token
登录 code2Session GET /sns/jscode2session
用户 获取手机号(新) POST /wxa/business/getuserphonenumber
支付 统一下单(V3) POST /v3/pay/transactions/jsapi
支付 查询订单(V3) GET /v3/pay/transactions/out-trade-no/{out_trade_no}
支付 申请退款(V3) POST /v3/refund/domestic/refunds
消息 发送订阅消息 POST /cgi-bin/message/subscribe/send
安全 文本检测 POST /wxa/msg_sec_check
安全 图片检测 POST /wxa/img_sec_check
安全 异步音视频检测 POST /wxa/media_check_async
无限制小程序码 POST /wxa/getwxacodeunlimit
小程序二维码 POST /cgi-bin/wxaapp/createwxaqrcode
数据分析 访问趋势 POST /datacube/getweanalysisappidvisitortrend
交易 发货信息录入 POST /shop/delivery/send

📌 完整接口列表见官方文档:https://developers.weixin.qq.com/miniprogram/dev/OpenApiDoc/


附录:常见问题速查

问题 解决方案
setData 性能差 按路径更新、减少调用频率、避免传大数组
分包图片无法加载 本地图片放分包目录,CDN 图片直接填 https
页面栈超过 10 层 reLaunch 或手动控制 navigateBack
canvas 层级过高遮挡 使用 cover-view 覆盖层,或 canvas-id 配合 z-index
tabBar 图标不显示 图标尺寸 81×81px,建议 2x/3x 多份
微信登录 40029 code 无效 code 只能使用一次,wx.login() 后立即使用
请求报 ERR_SSL_PROTOCOL_ERROR 检查是否用了 http://,小程序只支持 https://
storage 存不了大对象 分片存储或存到云存储,用 fileID 引用
分包页面空白 检查 app.json 路径是否正确,区分相对路径和绝对路径
open-type="getUserInfo" 失效 基础库 1.3.0+ 废弃,改用 wx.getUserProfile
后台定位申请被拒 需说明实际业务场景,通过「服务平台」申请开通

建议配合微信官方文档 https://developers.weixin.qq.com/miniprogram/dev/framework/ 一起使用

评论 (0)

发表评论