微信小程序开发入门教程
本教程面向零基础开发者,从概念到实战,手把手带你掌握微信小程序开发。
目录
- 什么是微信小程序
- 开发环境搭建
- 项目结构详解
- WXML 模板语法
- WXSS 样式开发
- JS 逻辑层开发
- 组件化开发
- API 调用基础
- 网络请求
- 数据缓存
- 用户授权与登录
- 微信支付功能
- 实战项目:电商小程序
- 发布与审核
- 常见问题与调试技巧
1. 什么是微信小程序
微信小程序是一种不需要下载安装即可使用的应用,用户通过微信扫一扫或搜索即可打开。它实现了"触手可及"的应用体验。
小程序的核心特点
| 特点 | 说明 |
|---|---|
| 轻量级 | 无需安装,即开即用,用完即走 |
| 跨平台 | 一套代码同时运行在 iOS 和 Android |
| 强社交 | 依托微信生态,易于分享和传播 |
| 丰富能力 | 可调用微信支付、摄像头、地理位置等原生能力 |
| 开发成本低 | 相比原生 App,开发周期短、成本低 |
小程序与其他技术的对比
原生 App:功能最强,但开发成本高、需要下载安装
H5 网页: 开发简单,但体验较差、能力受限
小程序: 介于两者之间,体验接近原生,开发成本较低
2. 开发环境搭建
2.1 注册小程序账号
- 访问 微信公众平台
- 点击"立即注册",选择"小程序"类型
- 填写邮箱、密码等信息完成注册
- 登录后在"开发管理" → "开发设置"中获取 AppID
💡 提示:个人开发者也可以注册,但部分能力(如微信支付)需要企业资质。
2.2 安装微信开发者工具
微信开发者工具是官方提供的集成开发环境(IDE),支持代码编辑、调试、预览和上传。
下载与安装:
- 访问 微信开发者工具下载页
- 根据操作系统(Windows / macOS)下载对应版本
- 安装后用微信扫码登录
2.3 创建第一个项目
- 打开开发者工具,点击"+"号创建新项目
- 填写项目名称,例如
my-first-miniprogram - 填入你的 AppID(或选择"测试号")
- 选择"不使用云开发"(我们先从基础学起)
- 选择"小程序模板"作为起始模板
- 点击"确定"即可创建项目
创建成功后,你会看到开发者工具的主界面,左侧是模拟器预览,右侧是代码编辑器。
3. 项目结构详解
一个标准的小程序项目结构如下:
my-miniprogram/
├── pages/ # 页面目录
│ ├── index/ # 首页
│ │ ├── index.js # 页面逻辑
│ │ ├── index.json # 页面配置
│ │ ├── index.wxml # 页面模板(类似 HTML)
│ │ └── index.wxss # 页面样式(类似 CSS)
│ └── logs/
│ ├── logs.js
│ ├── logs.json
│ ├── logs.wxml
│ └── logs.wxss
├── utils/ # 工具函数
│ └── util.js
├── app.js # 应用逻辑(入口文件)
├── app.json # 应用配置(全局)
├── app.wxss # 应用样式(全局)
└── project.config.json # 项目配置
3.1 核心文件说明
app.json — 全局配置文件
这是小程序的"大脑",定义了页面路径、窗口表现、底部 TabBar 等:
{
"pages": [
"pages/index/index",
"pages/logs/logs"
],
"window": {
"navigationBarBackgroundColor": "#ffffff",
"navigationBarTitleText": "我的小程序",
"navigationBarTextStyle": "black",
"backgroundColor": "#f5f5f5"
},
"tabBar": {
"color": "#999999",
"selectedColor": "#1296db",
"list": [
{
"pagePath": "pages/index/index",
"text": "首页",
"iconPath": "images/home.png",
"selectedIconPath": "images/home-active.png"
},
{
"pagePath": "pages/logs/logs",
"text": "日志",
"iconPath": "images/log.png",
"selectedIconPath": "images/log-active.png"
}
]
}
}
app.js — 应用入口文件
App({
onLaunch() {
// 小程序初始化时触发(全局只触发一次)
console.log('小程序启动了!')
// 检查登录态
wx.checkSession({
success() {
console.log('session 未过期')
},
fail() {
console.log('session 已过期,需要重新登录')
}
})
},
onShow(options) {
// 小程序显示时触发(从后台切到前台)
console.log('小程序显示', options)
},
onHide() {
// 小程序隐藏时触发(从前台切到后台)
console.log('小程序隐藏')
},
// 全局数据,所有页面都可以通过 getApp() 访问
globalData: {
userInfo: null,
baseUrl: 'https://api.example.com'
}
})
3.2 页面文件的四种类型
每个页面由四个文件组成,各司其职:
| 文件类型 | 作用 | 类比 Web |
|---|---|---|
.wxml |
页面结构(模板) | HTML |
.wxss |
页面样式 | CSS |
.js |
页面逻辑 | JavaScript |
.json |
页面配置 | — |
4. WXML 模板语法
WXML(WeiXin Markup Language)是微信小程序的模板语言,类似于 HTML,但有一些独特的语法。
4.1 数据绑定
使用双花括号 {{}} 将 JS 中的数据绑定到模板中:
<!-- pages/index/index.wxml -->
<view class="container">
<text>用户名:{{username}}</text>
<text>年龄:{{age}}</text>
<text>是否成人:{{age >= 18 ? '是' : '否'}}</text>
<!-- 属性绑定 -->
<image src="{{avatarUrl}}" mode="aspectFill"></image>
<!-- 运算表达式 -->
<view>{{a + b}} 的结果是 {{a + b}}</view>
</view>
对应的 JS:
// pages/index/index.js
Page({
data: {
username: '张三',
age: 25,
avatarUrl: '/images/avatar.png',
a: 1,
b: 2
}
})
4.2 条件渲染
<!-- wx:if / wx:elif / wx:else -->
<view wx:if="{{score >= 90}}">优秀</view>
<view wx:elif="{{score >= 60}}">及格</view>
<view wx:else>不及格</view>
<!-- hidden 属性(适合频繁切换的场景) -->
<view hidden="{{!isLoggedIn}}">欢迎回来!</view>
<view hidden="{{isLoggedIn}}">请先登录</view>
💡
wx:ifvshidden:wx:if会销毁和重建组件,适合条件不常变化的场景;hidden只是隐藏组件,适合频繁切换显示/隐藏的场景。
4.3 列表渲染
使用 wx:for 遍历数组:
<!-- 基础用法 -->
<view wx:for="{{fruits}}" wx:key="index">
索引:{{index}},名称:{{item}}
</view>
<!-- 使用 wx:key 提高性能 -->
<view wx:for="{{students}}" wx:key="id">
<text>姓名:{{item.name}}</text>
<text>年龄:{{item.age}}</text>
</view>
<!-- 自定义变量名 -->
<view wx:for="{{students}}" wx:for-item="student" wx:for-index="idx" wx:key="id">
第 {{idx}} 个学生:{{student.name}}
</view>
对应的 JS:
Page({
data: {
fruits: ['苹果', '香蕉', '橙子'],
students: [
{ id: 1, name: '小明', age: 20 },
{ id: 2, name: '小红', age: 21 },
{ id: 3, name: '小刚', age: 19 }
]
}
})
4.4 事件绑定
<!-- 绑定点击事件 -->
<button bindtap="onTap">点击我</button>
<!-- 带参数的事件 -->
<button bindtap="onTapWithArgs" data-id="{{item.id}}" data-name="{{item.name}}">
删除
</button>
<!-- 表单输入 -->
<input bindinput="onInput" placeholder="请输入内容" value="{{inputValue}}"></input>
<!-- 阻止事件冒泡 -->
<view catchtap="onViewTap">
<button bindtap="onBtnTap">按钮</button>
</view>
Page({
data: {
inputValue: ''
},
onTap() {
console.log('按钮被点击了')
wx.showToast({ title: '点击成功', icon: 'success' })
},
onTapWithArgs(e) {
// 通过 e.currentTarget.dataset 获取传入的参数
const { id, name } = e.currentTarget.dataset
console.log(`删除:ID=${id}, Name=${name}`)
},
onInput(e) {
// 实时获取输入框的值
this.setData({ inputValue: e.detail.value })
}
})
4.5 模板(template)
可以定义可复用的模板片段:
<!-- 定义模板 -->
<template name="userCard">
<view class="card">
<image src="{{avatar}}" class="avatar"></image>
<view class="info">
<text class="name">{{name}}</text>
<text class="desc">{{desc}}</text>
</view>
</view>
</template>
<!-- 使用模板 -->
<template is="userCard" data="{{...userInfo}}"></template>
<template is="userCard" data="{{name: '小明', avatar: '/img/1.png', desc: '前端开发者'}}"></template>
5. WXSS 样式开发
WXSS(WeiXin Style Sheet)类似于 CSS,但有一些扩展特性。
5.1 尺寸单位 rpx
rpx(responsive pixel)是小程序特有的响应式单位,750rpx 恒等于屏幕宽度。
/* 在 iPhone 6(375px 宽)上,1rpx = 0.5px */
/* 在 iPhone 6 Plus(414px 宽)上,1rpx ≈ 0.552px */
.container {
width: 750rpx; /* 占满屏幕宽度 */
padding: 30rpx; /* 约 15px */
font-size: 28rpx; /* 约 14px */
}
.box {
width: 345rpx; /* 约屏幕的一半 */
height: 200rpx;
margin: 20rpx;
}
💡 建议:设计稿通常以 750px 宽度为基准,直接将设计稿上的 px 值写成 rpx 即可自动适配不同屏幕。
5.2 样式导入
使用 @import 导入外部样式文件:
/* common.wxss - 公共样式 */
.text-primary {
color: #1296db;
}
.text-gray {
color: #999;
}
/* pages/index/index.wxss */
@import '../../common.wxss';
.page-container {
background: #f5f5f5;
min-height: 100vh;
}
5.3 常用布局示例
Flex 布局(推荐):
/* 水平居中 */
.center-h {
display: flex;
justify-content: center;
}
/* 垂直居中 */
.center-v {
display: flex;
align-items: center;
}
/* 水平 + 垂直居中 */
.center-all {
display: flex;
justify-content: center;
align-items: center;
}
/* 两端对齐 */
.space-between {
display: flex;
justify-content: space-between;
align-items: center;
}
/* 等分布局 */
.equal-split {
display: flex;
}
.equal-split > view {
flex: 1;
}
商品卡片布局示例:
.product-card {
display: flex;
padding: 20rpx;
background: #fff;
border-radius: 16rpx;
margin-bottom: 20rpx;
box-shadow: 0 2rpx 12rpx rgba(0, 0, 0, 0.08);
}
.product-card .product-img {
width: 200rpx;
height: 200rpx;
border-radius: 12rpx;
flex-shrink: 0;
}
.product-card .product-info {
flex: 1;
margin-left: 20rpx;
display: flex;
flex-direction: column;
justify-content: space-between;
}
.product-card .product-name {
font-size: 28rpx;
color: #333;
font-weight: bold;
}
.product-card .product-price {
font-size: 32rpx;
color: #e74c3c;
font-weight: bold;
}
6. JS 逻辑层开发
6.1 Page 构造器
每个页面的 JS 文件通过 Page() 注册页面,包含数据和事件处理函数:
Page({
// 页面数据
data: {
count: 0,
message: 'Hello World',
list: []
},
// 生命周期函数:页面加载
onLoad(options) {
console.log('页面加载', options)
// options 可以获取页面跳转时传递的参数
},
// 生命周期函数:页面显示
onShow() {
console.log('页面显示')
},
// 生命周期函数:页面初次渲染完成
onReady() {
console.log('页面初次渲染完成')
},
// 生命周期函数:页面隐藏
onHide() {
console.log('页面隐藏')
},
// 生命周期函数:页面卸载
onUnload() {
console.log('页面卸载')
},
// 下拉刷新
onPullDownRefresh() {
console.log('触发下拉刷新')
this.loadData().then(() => {
wx.stopPullDownRefresh() // 停止下拉刷新
})
},
// 上拉加载更多
onReachBottom() {
console.log('触底了,加载更多数据')
this.loadMore()
},
// 页面滚动
onPageScroll(e) {
// e.scrollTop 为页面滚动距离
},
// 自定义方法
loadData() {
return new Promise((resolve) => {
setTimeout(() => {
this.setData({ list: ['数据1', '数据2', '数据3'] })
resolve()
}, 1000)
})
},
loadMore() {
// 加载更多逻辑
},
// 事件处理函数
onTapButton() {
// 使用 setData 更新数据(这是触发视图更新的唯一方式)
this.setData({
count: this.data.count + 1
})
}
})
6.2 setData 详解
setData 是小程序中更新视图的核心方法,它将数据从逻辑层发送到视图层:
// 更新单个字段
this.setData({
count: 10
})
// 更新嵌套字段(使用路径)
this.setData({
'userInfo.name': '新名字',
'userInfo.address.city': '北京'
})
// 更新数组中的某一项
this.setData({
'list[0].name': '修改后的名字'
})
// 在回调中执行后续操作
this.setData({ count: this.data.count + 1 }, () => {
console.log('视图已更新')
})
⚠️ 注意事项:
setData是同步执行的,但视图更新是异步的- 避免一次性设置过大的数据,会影响性能
- 不要在
setData中传递函数或不可序列化的数据
6.3 页面间通信
通过 URL 参数传参:
// 跳转并传参
wx.navigateTo({
url: '/pages/detail/detail?id=123&type=book'
})
// 在目标页面接收参数
Page({
onLoad(options) {
const { id, type } = options
console.log(id, type) // 123, book
}
})
通过全局数据传参:
// 页面 A:设置全局数据
const app = getApp()
app.globalData.sharedData = { key: 'value' }
// 页面 B:读取全局数据
const app = getApp()
const data = app.globalData.sharedData
7. 组件化开发
小程序提供了丰富的内置组件,同时也支持自定义组件。
7.1 常用内置组件
<!-- 视图容器 -->
<view class="box">这是一个块级容器</view>
<scroll-view scroll-y style="height: 400rpx;">
<view wx:for="{{list}}" wx:key="index">{{item}}</view>
</scroll-view>
<swiper autoplay circular>
<swiper-item wx:for="{{banners}}" wx:key="index">
<image src="{{item.url}}" mode="aspectFill"></image>
</swiper-item>
</swiper>
<!-- 基础内容 -->
<text>文本内容(支持长按复制)</text>
<rich-text nodes="{{htmlContent}}"></rich-text>
<progress percent="60" stroke-width="6"></progress>
<!-- 表单组件 -->
<input placeholder="请输入" bindinput="onInput"></input>
<textarea placeholder="多行输入" bindinput="onTextareaInput"></textarea>
<switch checked="{{isEnabled}}" bindchange="onSwitchChange"></switch>
<picker mode="date" bindchange="onDateChange">
<view>选择日期:{{date}}</view>
</picker>
<!-- 媒体组件 -->
<image src="{{imgUrl}}" mode="aspectFill" bindtap="previewImage"></image>
<video src="{{videoUrl}}" controls></video>
7.2 自定义组件
创建自定义组件的步骤:
第一步:创建组件文件
components/
└── product-card/
├── product-card.js
├── product-card.json
├── product-card.wxml
└── product-card.wxss
第二步:编写组件代码
// components/product-card/product-card.js
Component({
// 组件属性:接收外部传入的数据
properties: {
product: {
type: Object,
value: {}
},
showPrice: {
type: Boolean,
value: true
}
},
// 组件内部数据
data: {
isCollected: false
},
// 组件方法
methods: {
onTapCard() {
// 触发自定义事件,通知父组件
this.triggerEvent('tapcard', {
id: this.properties.product.id
})
},
onTapCollect() {
this.setData({ isCollected: !this.data.isCollected })
this.triggerEvent('collect', {
id: this.properties.product.id,
collected: this.data.isCollected
})
}
}
})
<!-- components/product-card/product-card.wxml -->
<view class="card" bindtap="onTapCard">
<image src="{{product.image}}" class="card-img" mode="aspectFill"></image>
<view class="card-body">
<text class="card-title">{{product.name}}</text>
<text class="card-desc">{{product.desc}}</text>
<view class="card-footer" wx:if="{{showPrice}}">
<text class="price">¥{{product.price}}</text>
<view class="collect-btn" catchtap="onTapCollect">
<text>{{isCollected ? '❤️' : '🤍'}}</text>
</view>
</view>
</view>
</view>
/* components/product-card/product-card.wxss */
.card {
background: #fff;
border-radius: 16rpx;
overflow: hidden;
box-shadow: 0 4rpx 16rpx rgba(0, 0, 0, 0.06);
}
.card-img {
width: 100%;
height: 360rpx;
}
.card-body {
padding: 20rpx;
}
.card-title {
font-size: 28rpx;
color: #333;
font-weight: bold;
display: -webkit-box;
-webkit-line-clamp: 2;
-webkit-box-orient: vertical;
overflow: hidden;
}
.card-desc {
font-size: 24rpx;
color: #999;
margin-top: 8rpx;
}
.card-footer {
display: flex;
justify-content: space-between;
align-items: center;
margin-top: 16rpx;
}
.price {
font-size: 36rpx;
color: #e74c3c;
font-weight: bold;
}
第三步:在页面中使用组件
// pages/index/index.json
{
"usingComponents": {
"product-card": "/components/product-card/product-card"
}
}
<!-- pages/index/index.wxml -->
<product-card
wx:for="{{products}}"
wx:key="id"
product="{{item}}"
showPrice="{{true}}"
bindtapcard="onProductTap"
bindcollect="onProductCollect"
/>
// pages/index/index.js
Page({
data: {
products: [
{
id: 1,
name: '无线蓝牙耳机',
desc: '高品质音效,超长续航',
price: 199,
image: '/images/earphone.png'
}
]
},
onProductTap(e) {
wx.navigateTo({
url: `/pages/detail/detail?id=${e.detail.id}`
})
},
onProductCollect(e) {
console.log('收藏状态变更:', e.detail)
}
})
8. API 调用基础
微信小程序提供了丰富的原生 API,以 wx. 开头。
8.1 交互反馈
// 消息提示框
wx.showToast({
title: '操作成功',
icon: 'success',
duration: 2000
})
// 加载提示框
wx.showLoading({ title: '加载中...' })
setTimeout(() => wx.hideLoading(), 2000)
// 模态对话框
wx.showModal({
title: '确认',
content: '确定要删除吗?',
success(res) {
if (res.confirm) {
console.log('用户点击确定')
} else if (res.cancel) {
console.log('用户点击取消')
}
}
})
// 操作菜单(从底部弹出)
wx.showActionSheet({
itemList: ['拍照', '从相册选择', '取消'],
success(res) {
console.log('选择了第', res.tapIndex + 1, '项')
}
})
8.2 路由与页面跳转
// 保留当前页面,跳转到新页面(可返回)
wx.navigateTo({
url: '/pages/detail/detail?id=123'
})
// 关闭当前页面,跳转到新页面(不可返回)
wx.redirectTo({
url: '/pages/result/result'
})
// 跳转到 TabBar 页面(必须用 switchTab)
wx.switchTab({
url: '/pages/index/index'
})
// 关闭所有页面,打开新页面
wx.reLaunch({
url: '/pages/index/index'
})
// 返回上一页
wx.navigateBack({
delta: 1 // 返回的页面层数
})
8.3 设备能力
// 获取网络状态
wx.getNetworkType({
success(res) {
console.log('网络类型:', res.networkType) // wifi, 2g, 3g, 4g, 5g, none
}
})
// 获取系统信息
wx.getSystemInfo({
success(res) {
console.log('手机型号:', res.model)
console.log('系统版本:', res.system)
console.log('屏幕宽度:', res.screenWidth)
console.log('屏幕高度:', res.screenHeight)
console.log('状态栏高度:', res.statusBarHeight)
}
})
// 扫描二维码
wx.scanCode({
success(res) {
console.log('扫描结果:', res.result)
}
})
// 拨打电话
wx.makePhoneCall({
phoneNumber: '10086'
})
8.4 地图与位置
// 获取当前地理位置
wx.getLocation({
type: 'gcj02',
success(res) {
console.log('经度:', res.longitude)
console.log('纬度:', res.latitude)
}
})
// 使用微信内置地图选择位置
wx.chooseLocation({
success(res) {
console.log('选中位置:', res.name, res.address)
console.log('经度:', res.longitude, '纬度:', res.latitude)
}
})
9. 网络请求
9.1 基础请求
小程序通过 wx.request 发起 HTTP 请求:
// GET 请求
wx.request({
url: 'https://api.example.com/products',
method: 'GET',
data: {
page: 1,
limit: 10
},
success(res) {
console.log('响应数据:', res.data)
},
fail(err) {
console.error('请求失败:', err)
}
})
// POST 请求
wx.request({
url: 'https://api.example.com/orders',
method: 'POST',
data: {
productId: 123,
quantity: 2
},
header: {
'Content-Type': 'application/json',
'Authorization': 'Bearer token_xxx'
},
success(res) {
if (res.statusCode === 200) {
console.log('订单创建成功:', res.data)
}
}
})
9.2 封装请求工具
为了统一管理请求,建议封装一个请求工具:
// utils/request.js
const app = getApp()
const request = (options) => {
return new Promise((resolve, reject) => {
const { url, method = 'GET', data, header = {} } = options
// 自动拼接 baseUrl
const fullUrl = url.startsWith('http') ? url : `${app.globalData.baseUrl}${url}`
// 自动添加 token
const token = wx.getStorageSync('token')
if (token) {
header['Authorization'] = `Bearer ${token}`
}
wx.request({
url: fullUrl,
method,
data,
header: {
'Content-Type': 'application/json',
...header
},
success(res) {
if (res.statusCode === 200) {
if (res.data.code === 0) {
resolve(res.data)
} else {
wx.showToast({ title: res.data.message || '请求失败', icon: 'none' })
reject(res.data)
}
} else if (res.statusCode === 401) {
// Token 过期,跳转登录
wx.redirectTo({ url: '/pages/login/login' })
reject(new Error('未授权'))
} else {
wx.showToast({ title: '服务器错误', icon: 'none' })
reject(new Error(`HTTP ${res.statusCode}`))
}
},
fail(err) {
wx.showToast({ title: '网络错误', icon: 'none' })
reject(err)
}
})
})
}
// 便捷方法
const get = (url, data) => request({ url, method: 'GET', data })
const post = (url, data) => request({ url, method: 'POST', data })
module.exports = { request, get, post }
使用封装后的请求:
const { get, post } = require('../../utils/request')
Page({
async onLoad() {
try {
// 获取商品列表
const res = await get('/products', { page: 1, limit: 10 })
this.setData({ products: res.data.list })
// 创建订单
const order = await post('/orders', {
productId: 123,
quantity: 1
})
console.log('订单号:', order.data.orderId)
} catch (err) {
console.error(err)
}
}
})
10. 数据缓存
小程序提供了本地存储能力,可以将数据缓存到用户设备上。
10.1 同步 API
// 存储数据
wx.setStorageSync('token', 'abc123')
wx.setStorageSync('userInfo', { name: '张三', age: 25 })
// 读取数据
const token = wx.getStorageSync('token')
const userInfo = wx.getStorageSync('userInfo')
console.log(token, userInfo)
// 删除数据
wx.removeStorageSync('token')
// 清空所有缓存
wx.clearStorageSync()
// 获取缓存信息
const info = wx.getStorageInfoSync()
console.log('缓存的 keys:', info.keys)
console.log('已用大小:', info.currentSize, 'KB')
10.2 异步 API
// 异步存储
wx.setStorage({
key: 'cartList',
data: [{ id: 1, name: '商品A', count: 2 }],
success() {
console.log('存储成功')
}
})
// 异步读取
wx.getStorage({
key: 'cartList',
success(res) {
console.log('购物车数据:', res.data)
}
})
10.3 实战:购物车缓存
// 购物车管理工具
const CartManager = {
CART_KEY: 'shopping_cart',
// 获取购物车
getCart() {
return wx.getStorageSync(this.CART_KEY) || []
},
// 添加商品到购物车
addItem(product) {
const cart = this.getCart()
const existIndex = cart.findIndex(item => item.id === product.id)
if (existIndex > -1) {
cart[existIndex].count += 1
} else {
cart.push({ ...product, count: 1 })
}
wx.setStorageSync(this.CART_KEY, cart)
return cart
},
// 移除商品
removeItem(productId) {
let cart = this.getCart()
cart = cart.filter(item => item.id !== productId)
wx.setStorageSync(this.CART_KEY, cart)
return cart
},
// 修改数量
updateCount(productId, count) {
const cart = this.getCart()
const item = cart.find(item => item.id === productId)
if (item) {
item.count = Math.max(0, count)
if (item.count === 0) {
return this.removeItem(productId)
}
}
wx.setStorageSync(this.CART_KEY, cart)
return cart
},
// 获取总数
getTotalCount() {
return this.getCart().reduce((sum, item) => sum + item.count, 0)
},
// 获取总价
getTotalPrice() {
return this.getCart().reduce((sum, item) => sum + item.price * item.count, 0)
},
// 清空购物车
clearCart() {
wx.removeStorageSync(this.CART_KEY)
}
}
module.exports = CartManager
11. 用户授权与登录
11.1 微信登录流程
微信小程序的登录流程涉及三方:小程序、微信服务器、你的业务服务器。
┌──────────┐ ┌──────────┐ ┌──────────┐
│ 小程序 │────>│ 微信服务器 │────>│ 业务服务器 │
│ │<────│ │<────│ │
└──────────┘ └──────────┘ └──────────┘
1. wx.login() 2. 返回 code 3. 用 code 换 openid
4. 自定义登录态
5. 存储 token <─────────────────────┘
11.2 实现登录
// pages/login/login.js
Page({
data: {
isLoggedIn: false,
userInfo: null
},
// 第一步:调用微信登录获取 code
wxLogin() {
wx.login({
success(res) {
if (res.code) {
console.log('登录 code:', res.code)
// 第二步:将 code 发送到业务服务器
wx.request({
url: 'https://api.example.com/auth/login',
method: 'POST',
data: { code: res.code },
success(response) {
// 第三步:保存服务器返回的自定义登录态
const { token, userInfo } = response.data
wx.setStorageSync('token', token)
wx.setStorageSync('userInfo', userInfo)
// 登录成功,返回上一页或跳转首页
wx.switchTab({ url: '/pages/index/index' })
}
})
}
}
})
},
// 获取用户头像和昵称(需要用户主动点击按钮授权)
onChooseAvatar(e) {
const avatarUrl = e.detail.avatarUrl
this.setData({ 'userInfo.avatarUrl': avatarUrl })
},
onInputNickname(e) {
const nickName = e.detail.value
this.setData({ 'userInfo.nickName': nickName })
},
// 保存用户信息
async saveUserInfo() {
const app = getApp()
const userInfo = this.data.userInfo
try {
wx.request({
url: 'https://api.example.com/user/update',
method: 'POST',
header: {
'Authorization': `Bearer ${wx.getStorageSync('token')}`
},
data: userInfo,
success(res) {
app.globalData.userInfo = userInfo
wx.showToast({ title: '保存成功', icon: 'success' })
}
})
} catch (err) {
wx.showToast({ title: '保存失败', icon: 'none' })
}
}
})
登录页面模板:
<!-- pages/login/login.wxml -->
<view class="login-page">
<view class="logo">
<image src="/images/logo.png" class="logo-img"></image>
<text class="app-name">我的电商小程序</text>
</view>
<view class="login-form">
<!-- 头像选择 -->
<button class="avatar-btn" open-type="chooseAvatar" bindchooseavatar="onChooseAvatar">
<image wx:if="{{userInfo.avatarUrl}}" src="{{userInfo.avatarUrl}}" class="avatar-preview"></image>
<text wx:else>选择头像</text>
</button>
<!-- 昵称输入 -->
<input type="nickname" placeholder="请输入昵称" bindinput="onInputNickname" class="nickname-input"></input>
<!-- 登录按钮 -->
<button type="primary" bindtap="wxLogin" class="login-btn">微信登录</button>
</view>
</view>
11.3 权限管理
某些 API 需要用户授权后才能使用:
// 检查权限
wx.getSetting({
success(res) {
// 检查是否已授权地理位置
if (!res.authSetting['scope.userLocation']) {
// 首次使用,会弹出授权弹窗
wx.authorize({
scope: 'scope.userLocation',
success() {
console.log('已授权地理位置')
},
fail() {
// 用户拒绝授权,引导去设置页
wx.showModal({
title: '提示',
content: '需要地理位置权限,请在设置中开启',
success(res) {
if (res.confirm) {
wx.openSetting({
success(settingRes) {
console.log('设置结果:', settingRes.authSetting)
}
})
}
}
})
}
})
}
}
})
12. 微信支付功能
12.1 支付流程概述
1. 用户在小程序内选择商品,点击"下单"
2. 小程序调用业务服务器创建订单
3. 业务服务器调用微信支付统一下单 API,获取预支付信息
4. 业务服务器将支付参数返回给小程序
5. 小程序调用 wx.requestPayment 发起支付
6. 用户在微信内完成支付
7. 微信服务器通知业务服务器支付结果
12.2 前端实现
// pages/order-confirm/order-confirm.js
const { post } = require('../../utils/request')
Page({
data: {
cartItems: [],
totalPrice: 0,
address: null,
isSubmitting: false
},
onLoad() {
// 加载购物车数据和地址
this.loadCartData()
this.loadAddress()
},
loadCartData() {
const cart = wx.getStorageSync('shopping_cart') || []
const totalPrice = cart.reduce((sum, item) => sum + item.price * item.count, 0)
this.setData({ cartItems: cart, totalPrice })
},
loadAddress() {
const address = wx.getStorageSync('default_address')
this.setData({ address })
},
// 选择地址
chooseAddress() {
wx.chooseAddress({
success(res) {
const address = {
name: res.userName,
phone: res.telNumber,
province: res.provinceName,
city: res.cityName,
district: res.countyName,
detail: res.detailInfo
}
wx.setStorageSync('default_address', address)
this.setData({ address })
}
})
},
// 提交订单并发起支付
async submitOrder() {
if (!this.data.address) {
wx.showToast({ title: '请先选择收货地址', icon: 'none' })
return
}
if (this.data.isSubmitting) return
this.setData({ isSubmitting: true })
try {
// 1. 调用业务服务器创建订单
const orderRes = await post('/orders/create', {
items: this.data.cartItems,
address: this.data.address,
totalPrice: this.data.totalPrice
})
const { orderId, payParams } = orderRes.data
// 2. 调用微信支付
const paymentResult = await this.wxPay(payParams)
// 3. 支付成功
wx.showToast({ title: '支付成功', icon: 'success' })
// 清空购物车
wx.removeStorageSync('shopping_cart')
// 跳转到订单详情页
setTimeout(() => {
wx.redirectTo({
url: `/pages/order-detail/order-detail?id=${orderId}`
})
}, 1500)
} catch (err) {
console.error('下单失败:', err)
wx.showToast({ title: err.message || '下单失败', icon: 'none' })
} finally {
this.setData({ isSubmitting: false })
}
},
// 封装微信支付为 Promise
wxPay(payParams) {
return new Promise((resolve, reject) => {
wx.requestPayment({
timeStamp: payParams.timeStamp,
nonceStr: payParams.nonceStr,
package: payParams.package,
signType: payParams.signType || 'MD5',
paySign: payParams.paySign,
success(res) {
resolve(res)
},
fail(err) {
if (err.errMsg.includes('cancel')) {
reject(new Error('用户取消支付'))
} else {
reject(new Error('支付失败'))
}
}
})
})
}
})
⚠️ 重要提醒:微信支付需要企业资质,并在微信商户平台完成签约。个人开发者无法使用支付功能。
13. 实战项目:电商小程序
下面我们整合前面所学,构建一个简化版的电商小程序。
13.1 项目结构
shop-miniprogram/
├── components/
│ ├── product-card/ # 商品卡片组件
│ ├── cart-item/ # 购物车商品组件
│ └── tab-bar/ # 自定义底部导航
├── pages/
│ ├── index/ # 首页(商品列表)
│ ├── category/ # 分类页
│ ├── cart/ # 购物车页
│ ├── mine/ # 我的页面
│ ├── detail/ # 商品详情页
│ ├── order-confirm/ # 订单确认页
│ └── login/ # 登录页
├── utils/
│ ├── request.js # 网络请求封装
│ └── cart.js # 购物车管理
├── images/ # 图片资源
├── app.js
├── app.json
└── app.wxss
13.2 全局配置
// app.json
{
"pages": [
"pages/index/index",
"pages/category/category",
"pages/cart/cart",
"pages/mine/mine",
"pages/detail/detail",
"pages/order-confirm/order-confirm",
"pages/login/login"
],
"window": {
"navigationBarBackgroundColor": "#ff6b6b",
"navigationBarTitleText": "小商城",
"navigationBarTextStyle": "white",
"backgroundColor": "#f5f5f5"
},
"tabBar": {
"color": "#999",
"selectedColor": "#ff6b6b",
"list": [
{
"pagePath": "pages/index/index",
"text": "首页",
"iconPath": "images/tab/home.png",
"selectedIconPath": "images/tab/home-active.png"
},
{
"pagePath": "pages/category/category",
"text": "分类",
"iconPath": "images/tab/category.png",
"selectedIconPath": "images/tab/category-active.png"
},
{
"pagePath": "pages/cart/cart",
"text": "购物车",
"iconPath": "images/tab/cart.png",
"selectedIconPath": "images/tab/cart-active.png"
},
{
"pagePath": "pages/mine/mine",
"text": "我的",
"iconPath": "images/tab/mine.png",
"selectedIconPath": "images/tab/mine-active.png"
}
]
},
"permission": {
"scope.userLocation": {
"desc": "用于获取您的收货地址"
}
}
}
13.3 全局样式
/* app.wxss */
page {
background: #f5f5f5;
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
font-size: 28rpx;
color: #333;
box-sizing: border-box;
}
/* 通用工具类 */
.container {
padding: 20rpx;
}
.flex {
display: flex;
}
.flex-center {
display: flex;
justify-content: center;
align-items: center;
}
.flex-between {
display: flex;
justify-content: space-between;
align-items: center;
}
.text-ellipsis {
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.btn-primary {
background: linear-gradient(135deg, #ff6b6b, #ee5a24);
color: #fff;
border: none;
border-radius: 50rpx;
font-size: 28rpx;
padding: 20rpx 60rpx;
}
.btn-primary::after {
border: none;
}
13.4 首页实现
// pages/index/index.js
const { get } = require('../../utils/request')
Page({
data: {
banners: [
{ id: 1, image: '/images/banner1.jpg' },
{ id: 2, image: '/images/banner2.jpg' }
],
categories: [
{ id: 1, name: '数码', icon: '📱' },
{ id: 2, name: '服饰', icon: '👕' },
{ id: 3, name: '食品', icon: '🍜' },
{ id: 4, name: '家居', icon: '🏠' }
],
products: [],
page: 1,
hasMore: true,
isLoading: false
},
onLoad() {
this.loadProducts()
},
// 下拉刷新
onPullDownRefresh() {
this.setData({ page: 1, hasMore: true, products: [] })
this.loadProducts().then(() => {
wx.stopPullDownRefresh()
})
},
// 上拉加载更多
onReachBottom() {
if (this.data.hasMore && !this.data.isLoading) {
this.setData({ page: this.data.page + 1 })
this.loadProducts()
}
},
async loadProducts() {
if (this.data.isLoading) return
this.setData({ isLoading: true })
try {
const res = await get('/products', {
page: this.data.page,
limit: 10
})
const newProducts = this.data.products.concat(res.data.list)
this.setData({
products: newProducts,
hasMore: res.data.list.length >= 10
})
} catch (err) {
console.error('加载商品失败:', err)
} finally {
this.setData({ isLoading: false })
}
},
// 点击分类
onCategoryTap(e) {
const { id } = e.currentTarget.dataset
wx.navigateTo({
url: `/pages/category/category?id=${id}`
})
},
// 点击商品
onProductTap(e) {
const { id } = e.currentTarget.dataset
wx.navigateTo({
url: `/pages/detail/detail?id=${id}`
})
},
// 搜索
onSearchTap() {
wx.navigateTo({ url: '/pages/search/search' })
}
})
<!-- pages/index/index.wxml -->
<view class="index-page">
<!-- 搜索栏 -->
<view class="search-bar" bindtap="onSearchTap">
<view class="search-input">
<text class="search-icon">🔍</text>
<text class="search-placeholder">搜索商品</text>
</view>
</view>
<!-- 轮播图 -->
<swiper class="banner" autoplay circular indicator-dots indicator-color="rgba(255,255,255,0.5)" indicator-active-color="#fff">
<swiper-item wx:for="{{banners}}" wx:key="id">
<image src="{{item.image}}" class="banner-img" mode="aspectFill"></image>
</swiper-item>
</swiper>
<!-- 分类入口 -->
<view class="categories">
<view class="category-item" wx:for="{{categories}}" wx:key="id"
bindtap="onCategoryTap" data-id="{{item.id}}">
<text class="category-icon">{{item.icon}}</text>
<text class="category-name">{{item.name}}</text>
</view>
</view>
<!-- 商品列表 -->
<view class="section">
<view class="section-title">热门推荐</view>
<view class="product-grid">
<view class="product-item" wx:for="{{products}}" wx:key="id"
bindtap="onProductTap" data-id="{{item.id}}">
<image src="{{item.image}}" class="product-img" mode="aspectFill"></image>
<view class="product-info">
<text class="product-name text-ellipsis">{{item.name}}</text>
<view class="product-bottom">
<text class="product-price">¥{{item.price}}</text>
<text class="product-sales">已售{{item.sales}}件</text>
</view>
</view>
</view>
</view>
</view>
<!-- 加载状态 -->
<view class="loading-tip" wx:if="{{isLoading}}">加载中...</view>
<view class="loading-tip" wx:elif="{{!hasMore}}">没有更多了</view>
</view>
/* pages/index/index.wxss */
.search-bar {
padding: 16rpx 20rpx;
background: #ff6b6b;
}
.search-input {
display: flex;
align-items: center;
background: rgba(255, 255, 255, 0.9);
border-radius: 32rpx;
padding: 14rpx 24rpx;
}
.search-icon {
margin-right: 12rpx;
}
.search-placeholder {
color: #999;
font-size: 26rpx;
}
.banner {
width: 100%;
height: 360rpx;
}
.banner-img {
width: 100%;
height: 100%;
}
.categories {
display: flex;
justify-content: space-around;
padding: 30rpx 20rpx;
background: #fff;
margin-bottom: 20rpx;
}
.category-item {
display: flex;
flex-direction: column;
align-items: center;
}
.category-icon {
font-size: 48rpx;
margin-bottom: 8rpx;
}
.category-name {
font-size: 24rpx;
color: #666;
}
.section {
padding: 0 20rpx;
}
.section-title {
font-size: 32rpx;
font-weight: bold;
padding: 20rpx 0;
}
.product-grid {
display: flex;
flex-wrap: wrap;
justify-content: space-between;
}
.product-item {
width: 345rpx;
background: #fff;
border-radius: 16rpx;
overflow: hidden;
margin-bottom: 20rpx;
}
.product-img {
width: 345rpx;
height: 345rpx;
}
.product-info {
padding: 16rpx;
}
.product-name {
font-size: 26rpx;
color: #333;
}
.product-bottom {
display: flex;
justify-content: space-between;
align-items: center;
margin-top: 12rpx;
}
.product-price {
font-size: 32rpx;
color: #e74c3c;
font-weight: bold;
}
.product-sales {
font-size: 22rpx;
color: #999;
}
.loading-tip {
text-align: center;
padding: 30rpx;
color: #999;
font-size: 24rpx;
}
13.5 商品详情页
// pages/detail/detail.js
const { get, post } = require('../../utils/request')
const CartManager = require('../../utils/cart')
Page({
data: {
product: null,
selectedSku: null,
quantity: 1,
showSkuPopup: false,
cartCount: 0,
isCollected: false
},
onLoad(options) {
this.loadProductDetail(options.id)
this.updateCartCount()
},
onShow() {
this.updateCartCount()
},
async loadProductDetail(id) {
try {
wx.showLoading({ title: '加载中' })
const res = await get(`/products/${id}`)
this.setData({
product: res.data,
selectedSku: res.data.skus ? res.data.skus[0] : null
})
wx.setNavigationBarTitle({ title: res.data.name })
} catch (err) {
wx.showToast({ title: '加载失败', icon: 'none' })
} finally {
wx.hideLoading()
}
},
// 预览图片
previewImage(e) {
const { current } = e.currentTarget.dataset
wx.previewImage({
current,
urls: this.data.product.images
})
},
// 选择 SKU
selectSku(e) {
const { index } = e.currentTarget.dataset
this.setData({
selectedSku: this.data.product.skus[index]
})
},
// 修改数量
changeQuantity(e) {
const { action } = e.currentTarget.dataset
let quantity = this.data.quantity
if (action === 'minus') {
quantity = Math.max(1, quantity - 1)
} else {
quantity = Math.min(this.data.selectedSku.stock, quantity + 1)
}
this.setData({ quantity })
},
// 显示 SKU 弹窗(用于加入购物车 / 立即购买)
showSku(e) {
const { action } = e.currentTarget.dataset
this.setData({ showSkuPopup: true, buyAction: action })
},
hideSku() {
this.setData({ showSkuPopup: false })
},
// 加入购物车
addToCart() {
const { product, selectedSku, quantity } = this.data
const cartItem = {
id: product.id,
skuId: selectedSku.id,
name: product.name,
image: product.images[0],
price: selectedSku.price,
skuName: selectedSku.name,
count: quantity
}
CartManager.addItem(cartItem)
this.updateCartCount()
this.hideSku()
wx.showToast({ title: '已加入购物车', icon: 'success' })
},
// 立即购买
buyNow() {
const { product, selectedSku, quantity } = this.data
const orderItem = {
id: product.id,
skuId: selectedSku.id,
name: product.name,
image: product.images[0],
price: selectedSku.price,
skuName: selectedSku.name,
count: quantity
}
// 将购买信息存入缓存,跳转到订单确认页
wx.setStorageSync('buy_now_item', orderItem)
this.hideSku()
wx.navigateTo({ url: '/pages/order-confirm/order-confirm?from=buyNow' })
},
// 收藏
toggleCollect() {
this.setData({ isCollected: !this.data.isCollected })
wx.showToast({
title: this.data.isCollected ? '已收藏' : '已取消收藏',
icon: 'none'
})
},
updateCartCount() {
this.setData({ cartCount: CartManager.getTotalCount() })
},
// 跳转购物车
goCart() {
wx.switchTab({ url: '/pages/cart/cart' })
},
// 分享
onShareAppMessage() {
return {
title: this.data.product.name,
path: `/pages/detail/detail?id=${this.data.product.id}`,
imageUrl: this.data.product.images[0]
}
}
})
<!-- pages/detail/detail.wxml -->
<view class="detail-page" wx:if="{{product}}">
<!-- 商品图片轮播 -->
<swiper class="detail-swiper" indicator-dots autoplay>
<swiper-item wx:for="{{product.images}}" wx:key="index">
<image src="{{item}}" mode="aspectFill" class="detail-img"
bindtap="previewImage" data-current="{{item}}"></image>
</swiper-item>
</swiper>
<!-- 价格信息 -->
<view class="price-section">
<view class="current-price">
<text class="price-symbol">¥</text>
<text class="price-value">{{selectedSku.price || product.price}}</text>
<text class="original-price" wx:if="{{selectedSku.originalPrice}}">¥{{selectedSku.originalPrice}}</text>
</view>
<text class="sales-count">已售{{product.sales}}件</text>
</view>
<!-- 商品名称 -->
<view class="name-section">
<text class="product-title">{{product.name}}</text>
</view>
<!-- SKU 选择 -->
<view class="sku-section" bindtap="showSku" data-action="cart">
<text class="sku-label">已选:</text>
<text class="sku-value">{{selectedSku.name}} × {{quantity}}</text>
<text class="sku-arrow">›</text>
</view>
<!-- 商品详情 -->
<view class="detail-section">
<view class="section-title">商品详情</view>
<rich-text nodes="{{product.detail}}"></rich-text>
</view>
<!-- SKU 弹窗 -->
<view class="sku-popup-mask" wx:if="{{showSkuPopup}}" bindtap="hideSku"></view>
<view class="sku-popup {{showSkuPopup ? 'show' : ''}}">
<view class="sku-popup-header">
<image src="{{product.images[0]}}" class="sku-preview-img"></image>
<view class="sku-popup-info">
<text class="sku-popup-price">¥{{selectedSku.price}}</text>
<text class="sku-popup-stock">库存:{{selectedSku.stock}}</text>
</view>
<text class="sku-popup-close" bindtap="hideSku">✕</text>
</view>
<view class="sku-options">
<text class="sku-options-title">规格</text>
<view class="sku-list">
<view class="sku-tag {{selectedSku.id === item.id ? 'active' : ''}}"
wx:for="{{product.skus}}" wx:key="id"
bindtap="selectSku" data-index="{{index}}">
{{item.name}}
</view>
</view>
</view>
<view class="quantity-section">
<text class="quantity-title">数量</text>
<view class="quantity-control">
<view class="qty-btn" bindtap="changeQuantity" data-action="minus">−</view>
<text class="qty-value">{{quantity}}</text>
<view class="qty-btn" bindtap="changeQuantity" data-action="plus">+</view>
</view>
</view>
<view class="sku-popup-footer">
<button class="btn-add-cart" bindtap="addToCart">加入购物车</button>
<button class="btn-buy-now" bindtap="buyNow">立即购买</button>
</view>
</view>
<!-- 底部操作栏 -->
<view class="bottom-bar">
<view class="bottom-icons">
<view class="bottom-icon-item" bindtap="toggleCollect">
<text>{{isCollected ? '❤️' : '🤍'}}</text>
<text class="icon-label">收藏</text>
</view>
<view class="bottom-icon-item" bindtap="goCart">
<view class="cart-badge" wx:if="{{cartCount > 0}}">{{cartCount}}</view>
<text>🛒</text>
<text class="icon-label">购物车</text>
</view>
</view>
<view class="bottom-btns">
<view class="bottom-btn add-cart" bindtap="showSku" data-action="cart">加入购物车</view>
<view class="bottom-btn buy-now" bindtap="showSku" data-action="buy">立即购买</view>
</view>
</view>
</view>
13.6 购物车页面
// pages/cart/cart.js
const CartManager = require('../../utils/cart')
Page({
data: {
cartItems: [],
isAllSelected: false,
totalPrice: 0,
totalCount: 0,
isEditing: false
},
onShow() {
this.refreshCart()
},
refreshCart() {
const cartItems = CartManager.getCart().map(item => ({
...item,
selected: true
}))
this.setData({ cartItems })
this.calcTotal()
},
// 切换选中状态
toggleSelect(e) {
const { index } = e.currentTarget.dataset
const key = `cartItems[${index}].selected`
this.setData({
[key]: !this.data.cartItems[index].selected
})
this.calcTotal()
},
// 全选/取消全选
toggleSelectAll() {
const isAllSelected = !this.data.isAllSelected
const cartItems = this.data.cartItems.map(item => ({
...item,
selected: isAllSelected
}))
this.setData({ cartItems, isAllSelected })
this.calcTotal()
},
// 修改数量
changeQuantity(e) {
const { index, action } = e.currentTarget.dataset
const item = this.data.cartItems[index]
let newCount = item.count
if (action === 'minus') {
newCount = Math.max(1, item.count - 1)
} else {
newCount = item.count + 1
}
CartManager.updateCount(item.id, newCount)
this.setData({ [`cartItems[${index}].count`]: newCount })
this.calcTotal()
},
// 删除商品
deleteItem(e) {
const { index } = e.currentTarget.dataset
const item = this.data.cartItems[index]
wx.showModal({
title: '确认删除',
content: `确定要删除"${item.name}"吗?`,
success: (res) => {
if (res.confirm) {
CartManager.removeItem(item.id)
this.refreshCart()
}
}
})
},
// 计算总价
calcTotal() {
const selectedItems = this.data.cartItems.filter(item => item.selected)
const totalPrice = selectedItems.reduce((sum, item) => sum + item.price * item.count, 0)
const totalCount = selectedItems.reduce((sum, item) => sum + item.count, 0)
this.setData({
totalPrice: totalPrice.toFixed(2),
totalCount,
isAllSelected: selectedItems.length === this.data.cartItems.length && this.data.cartItems.length > 0
})
},
// 切换编辑模式
toggleEdit() {
this.setData({ isEditing: !this.data.isEditing })
},
// 去结算
goCheckout() {
const selectedItems = this.data.cartItems.filter(item => item.selected)
if (selectedItems.length === 0) {
wx.showToast({ title: '请选择商品', icon: 'none' })
return
}
wx.setStorageSync('checkout_items', selectedItems)
wx.navigateTo({ url: '/pages/order-confirm/order-confirm' })
},
// 清空购物车
clearCart() {
wx.showModal({
title: '确认清空',
content: '确定要清空购物车吗?',
success: (res) => {
if (res.confirm) {
CartManager.clearCart()
this.refreshCart()
}
}
})
}
})
14. 发布与审核
14.1 发布前检查清单
✅ 功能完整性:所有功能正常运行,无明显 Bug
✅ 界面适配:在不同屏幕尺寸上显示正常
✅ 性能优化:页面加载速度、图片大小合理
✅ 错误处理:网络异常、空数据等情况有友好提示
✅ 隐私合规:用户数据收集有明确告知和授权
✅ 内容合规:不涉及违规内容
✅ 体验评分:开发者工具中"体验评分"达到 90 分以上
14.2 上传与提交审核
- 在开发者工具中,点击右上角"上传"按钮
- 填写版本号(如
1.0.0)和项目备注 - 登录微信公众平台 → "版本管理"
- 在"开发版本"中找到刚上传的版本
- 点击"提交审核",填写相关信息
- 等待审核通过(通常 1-7 个工作日)
- 审核通过后,点击"发布"即可上线
14.3 版本管理建议
版本号规范:主版本.次版本.修订号(如 1.2.3)
- 主版本:重大功能更新或架构变化
- 次版本:新功能添加
- 修订号:Bug 修复、小改动
15. 常见问题与调试技巧
15.1 常见问题
Q:页面白屏怎么办?
- 检查
app.json中页面路径是否正确 - 检查页面 JS 文件是否有语法错误
- 查看控制台是否有报错信息
Q:setData 后视图没有更新?
- 确认
setData的数据格式正确 - 检查是否在正确的
this上调用 - 确认 WXML 中绑定的字段名是否一致
Q:图片不显示?
- 检查图片路径是否正确(小程序中路径区分大小写)
- 确认图片格式是否支持(jpg、png、gif、webp)
- 检查图片大小是否超出限制
Q:网络请求失败?
- 在微信公众平台配置合法域名(开发管理 → 开发设置 → 服务器域名)
- 勾选开发者工具中的"不校验合法域名"选项(仅开发阶段)
- 检查请求地址是否正确
15.2 调试技巧
- 使用 Console 面板:查看
console.log输出和错误信息 - 使用 Network 面板:查看所有网络请求的状态和数据
- 使用 AppData 面板:实时查看和修改页面数据
- 使用 Wxml 面板:检查页面元素结构和样式
- 使用 Performance 面板:分析页面性能瓶颈
// 性能监控示例
const startTime = Date.now()
// 在页面渲染完成后
Page({
onReady() {
const renderTime = Date.now() - startTime
console.log(`页面渲染耗时:${renderTime}ms`)
if (renderTime > 1000) {
console.warn('页面渲染较慢,建议优化')
}
}
})
15.3 性能优化建议
- 减少
setData调用:合并多次setData为一次 - 精简数据传输:只传递需要更新的数据字段
- 图片优化:使用 WebP 格式、合理压缩、懒加载
- 分包加载:将非核心页面放入分包,减少主包体积
- 合理使用缓存:减少重复的网络请求
// 分包配置示例 app.json
{
"pages": [
"pages/index/index",
"pages/cart/cart"
],
"subpackages": [
{
"root": "packageA",
"name": "shop",
"pages": [
"pages/detail/detail",
"pages/order/order"
]
},
{
"root": "packageB",
"name": "user",
"pages": [
"pages/mine/mine",
"pages/address/address"
]
}
]
}
总结
本教程从零基础出发,涵盖了微信小程序开发的核心知识:
| 知识点 | 重要程度 |
|---|---|
| 项目结构与配置 | ⭐⭐⭐⭐⭐ |
| WXML 模板语法 | ⭐⭐⭐⭐⭐ |
| WXSS 样式(rpx) | ⭐⭐⭐⭐ |
| JS 逻辑与 setData | ⭐⭐⭐⭐⭐ |
| 组件化开发 | ⭐⭐⭐⭐ |
| 网络请求封装 | ⭐⭐⭐⭐ |
| 数据缓存 | ⭐⭐⭐ |
| 用户授权与登录 | ⭐⭐⭐⭐ |
| 微信支付 | ⭐⭐⭐ |
| 性能优化 | ⭐⭐⭐ |
推荐学习路线
入门阶段:项目结构 → WXML → WXSS → 基础 JS → 简单页面
进阶阶段:组件化 → API 调用 → 网络请求 → 数据缓存
实战阶段:完整项目 → 用户登录 → 支付接入 → 性能优化
高级阶段:自定义组件 → 分包加载 → 云开发 → 插件开发
推荐资源
📝 本教程所有代码示例均为原创,可直接复制使用。 建议结合微信开发者工具实际运行,加深理解。祝你开发愉快!