微信小程序开发完全指南
目录
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 示例
{
"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:
{
"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 模板
数据绑定
<!-- 基础绑定 -->
<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" />
事件绑定
<!-- 冒泡事件: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" />
// 对应 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 样式
选择器(仅支持部分)
/* 支持的选择器 */
.selector {} /* 类选择器 */
#id {} /* id 选择器 */
view {} /* 标签选择器 */
view, .class {} /* 并集 */
view .child {} /* 后代 */
view > .child {} /* 子选择器 */
/* 不支持:通配符 *、属性选择器 [attr]、伪类 */
rpx 响应式单位
/* iPhone 6: 1px = 2rpx;750 设计稿宽度等于 375rpx */
.box {
width: 750rpx; /* 全屏宽度 */
height: 200rpx;
padding: 20rpx;
}
样式导入 & 全局样式
/* 在 app.wxss 中定义全局样式 */
page {
--primary-color: #1890ff;
background-color: #f5f5f5;
font-size: 28rpx;
}
/* 局部页面引用 */
@import "./template.wxss";
基础样式示例
/* 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() 注册
// 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 的正确姿势
// ❌ 错误:每次都传整个列表
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 构造器
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 复用代码
// behaviors/my_behavior.js
module.exports = Behavior({
data: { shared: 'shared data' },
methods: { sharedMethod() { console.log('shared'); } }
});
// 组件中使用
Component({
behaviors: [require('../behaviors/my_behavior.js')]
});
ES6+ 语法支持
// 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;
⚠️ 注意:不支持
window、document、DOM API。不支持 ES2022 以外的特性请确认兼容性。
3. 生命周期
3.1 应用生命周期(app.js)
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 页面生命周期
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 组件生命周期
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
<view class="container">
<view class="card">内容卡片</view>
</view>
| 属性 | 类型 | 说明 |
|---|---|---|
hover |
Boolean | 是否开启点击态 |
hover-class |
String | 点击态类名 |
hover-start-time |
Number | 按住多久(ms)触发 |
hover-stay-time |
Number | 离开后保留时间 |
scroll-view — 滚动容器
<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 — 轮播
<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 — 移动
<movable-area style="width: 300px; height: 300px;">
<movable-view x="{{ x }}" y="{{ y }}" direction="all" bindchange="onMove">
可拖动内容
</movable-view>
</movable-area>
4.2 表单组件
button — 按钮
<!-- 开放能力按钮 -->
<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 — 输入框
<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 — 多行文本
<textarea
value="{{ content }}"
placeholder="请输入内容"
maxlength="500"
auto-height
bindinput="onTextareaInput"
style="min-height: 200rpx;"
/>
picker — 选择器
<!-- 普通选择器 -->
<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 — 多选/单选
<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 — 开关/滑动条
<switch checked="{{ switchChecked }}" bindchange="onSwitchChange" />
<slider min="0" max="100" value="{{ sliderVal }}" show-value bindchange="onSliderChange" />
form — 表单提交
<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>
onFormSubmit(e) {
console.log(e.detail.value); // { username: '...', password: '...' }
}
4.3 媒体组件
image — 图片
<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 — 视频
<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 — 相机
<camera
device-position="back"
flash="auto"
binderror="onCameraError"
style="width: 100%; height: 400rpx;"
/>
<button bindtap="takePhoto">拍照</button>
const ctx = wx.createCameraContext();
ctx.takePhoto({
quality: 'high',
success: (res) => this.setData({ tempPhotoPath: res.tempImagePath })
});
editor — 富文本编辑(基础库 2.7+)
<editor
id="editor"
placeholder="输入内容..."
bindinput="onEditorInput"
read-only="{{ false }}"
/>
4.4 导航与路由展示
navigator — 页面链接
<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 — 跳转功能页
<functional-page-navigator
name="chooseAddress"
version="develop"
bindsuccess="onChooseAddressSuccess"
bindfail="onChooseAddressFail"
>
选择收货地址
</functional-page-navigator>
4.5 地图组件(map)
<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;"
/>
// 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 — 图标
<icon type="success" size="40" color="#07c160" />
<!-- type: success/success_no_circle/info/warn/waiting/cancel/search/clear -->
progress — 进度条
<progress percent="60" show-info stroke-width="6" color="#07c160" />
rich-text — 富文本渲染
<rich-text nodes="{{ htmlString }}" />
// 支持 HTML string 或 node 数组
this.setData({
htmlString: '<p style="color:#999">内容</p>'
});
open-data — 展示用户信息(无需授权)
<open-data type="userNickName" lang="zh_CN" />
<open-data type="userAvatarUrl" />
<open-data type="userGender" />
<open-data type="userCity" />
web-view — 嵌入网页(需配置业务域名)
<!-- 需在 app.json 添加 web-view 域名白名单 -->
<web-view src="https://example.com/page.html" bindmessage="onWebViewMessage" />
5. 常用 API
5.1 路由跳转
// 保留当前页面,跳转(非 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 数据存储
// 同步存储
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 网络请求
// ⚠️ 必须在 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
封装请求工具(推荐)
// 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 设备相关
// 获取系统信息
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 位置服务
// 获取当前位置
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 图片与文件
// 选择图片
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 交互反馈
// 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 动画
// ⚠️ 方式一: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 授权流程
// 方式一: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 获取手机号(需企业认证)
// 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+)
<button open-type="chooseAvatar" bindchooseavatar="onChooseAvatar">选择头像</button>
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 前端实现代码
// 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 在业务页面中使用
// 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(请求拦截器)
// 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
{
"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 分包页面跳转
// ❌ 无法直接跳转到分包页面(分包 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 分包预加载规则
// 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 分包独立分包
{
"root": "subpkg/independent",
"independent": true
}
独立分包:可独立运行,不依赖主包。可用于广告落地页、分享页等场景。
9. 云开发
9.1 环境初始化
// 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 云数据库
// 前端 - 增删改查
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 云存储
// 上传文件
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 云函数
// 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
返回示例
{
"access_token": "ACCESS_TOKEN",
"expires_in": 7200
}
Node.js 封装示例
// 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
返回示例
{
"openid": "oUpF8uMuAJO_M2pxb1Q9zNjWeS6o",
"session_key": "tiihtNczf5jlfXJpVJ...",
"unionid": "o6_bmasdasdsad6_2sgVt7hMZOPfL", // 满足条件才返回
"errcode": 0,
"errmsg": "ok"
}
⚠️
unionid返回条件:1. 小程序绑定到微信开放平台;2. 用户曾经授权过同主体下的其他应用。
Node.js 示例
// 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
返回示例
{
"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
请求体
{
"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"
}
}
返回示例
{
"prepay_id": "wx201410272009395522657a690389489100"
}
前端调起支付
// 后端返回签名后的支付参数
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' }); }
});
支付通知(回调)处理
// 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
{
"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
请求体
{
"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中的字段名(thing1、date2等)必须与模板配置完全一致,值的长度也有限制。
Node.js 示例
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
{
"content": "要检测的文本",
"version": 2, // 使用 V2 接口(支持开放内容)
"scene": 1 // 1:资料 2:评论 3:论坛 4:社交日志
}
返回示例
{
"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
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
{
"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
{
"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 保存示例
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
{
"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
{
"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)
发表评论