前言

微信小程序作为腾讯推出的轻量级应用平台,自2017年正式发布以来,已经成为移动互联网生态中不可或缺的一部分。小程序具有”用完即走”的特点,无需下载安装,通过微信即可使用,为用户提供了便捷的服务体验,也为开发者提供了新的应用分发渠道。

本文将从零开始,详细介绍微信小程序的开发流程,包括环境搭建、基础语法、核心功能实现、发布上线等各个环节,帮助初学者快速掌握小程序开发技能。

一、微信小程序概述

(一)什么是微信小程序

微信小程序是一种不需要下载安装即可使用的应用,它实现了应用”触手可及”的梦想,用户扫一扫或者搜一下即可打开应用。小程序具有以下特点:

  1. 无需安装:通过微信直接使用,不占用手机存储空间
  2. 即用即走:用完即可关闭,不会在后台持续运行
  3. 功能丰富:支持多种API,可实现复杂的业务逻辑
  4. 跨平台:一次开发,同时支持iOS和Android
  5. 生态完善:与微信生态深度融合,支持分享、支付等功能

(二)小程序的技术架构

微信小程序采用双线程架构:

  • 逻辑层(App Service):运行JavaScript代码,处理业务逻辑
  • 视图层(View):由WXML和WXSS组成,负责页面渲染

两个线程通过微信客户端(Native)进行通信,确保了界面渲染的流畅性和逻辑处理的独立性。

(三)开发语言和文件类型

小程序主要使用以下四种文件类型:

  1. WXML:类似HTML的标记语言,用于描述页面结构
  2. WXSS:类似CSS的样式语言,用于描述页面样式
  3. JavaScript:用于处理页面逻辑
  4. JSON:用于配置文件

二、开发环境搭建

(一)注册微信小程序账号

1. 访问微信公众平台

打开浏览器,访问 https://mp.weixin.qq.com/

2. 注册小程序账号

1
2
3
4
5
6
1. 点击"立即注册"
2. 选择"小程序"
3. 填写邮箱和密码
4. 邮箱验证
5. 信息登记(个人或企业)
6. 微信认证(可选)

3. 获取AppID

注册完成后,在小程序管理后台的”开发” -> “开发管理” -> “开发设置”中可以找到AppID,这是小程序的唯一标识。

(二)下载开发工具

1. 微信开发者工具下载

访问 https://developers.weixin.qq.com/miniprogram/dev/devtools/download.html

根据操作系统选择对应版本:

  • Windows 64位
  • Windows 32位
  • macOS
  • Linux

2. 安装开发工具

1
2
3
4
5
6
7
8
# Windows
双击下载的.exe文件,按照提示安装

# macOS
双击下载的.dmg文件,拖拽到Applications文件夹

# Linux
sudo dpkg -i wechat_devtools_xxx_linux_x64.deb

3. 登录开发工具

使用微信扫码登录开发者工具,确保登录的微信号已经绑定到小程序账号。

(三)创建第一个小程序项目

1. 新建项目

1
2
3
4
5
6
7
8
9
1. 打开微信开发者工具
2. 点击"新建项目"
3. 填写项目信息:
- 项目名称:如"我的第一个小程序"
- 目录:选择项目存放路径
- AppID:填入之前获取的AppID
- 开发模式:选择"小程序"
- 后端服务:选择"不使用云服务"
4. 点击"新建"

2. 项目结构说明

创建完成后,项目目录结构如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
my-miniprogram/
├── pages/ # 页面文件夹
│ ├── index/ # 首页
│ │ ├── index.js # 页面逻辑
│ │ ├── index.json # 页面配置
│ │ ├── index.wxml # 页面结构
│ │ └── index.wxss # 页面样式
│ └── logs/ # 日志页面
│ ├── logs.js
│ ├── logs.json
│ ├── logs.wxml
│ └── logs.wxss
├── utils/ # 工具函数
│ └── util.js
├── app.js # 小程序逻辑
├── app.json # 小程序配置
├── app.wxss # 小程序样式
├── project.config.json # 项目配置
└── sitemap.json # 站点地图

三、小程序基础语法

(一)WXML语法

WXML(WeiXin Markup Language)是小程序的标记语言,类似于HTML但有所不同。

1. 基本标签

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
<!-- 视图容器 -->
<view class="container">
<!-- 文本 -->
<text>Hello World</text>

<!-- 图片 -->
<image src="/images/logo.png" mode="aspectFit"></image>

<!-- 按钮 -->
<button type="primary" bindtap="handleClick">点击我</button>

<!-- 输入框 -->
<input placeholder="请输入内容" bindinput="handleInput" />

<!-- 滚动视图 -->
<scroll-view scroll-y="true" style="height: 200px;">
<view>滚动内容1</view>
<view>滚动内容2</view>
<view>滚动内容3</view>
</scroll-view>
</view>

2. 数据绑定

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<!-- 文本绑定 -->
<text>{{message}}</text>

<!-- 属性绑定 -->
<image src="{{imageSrc}}" style="width: {{imageWidth}}px;"></image>

<!-- 条件渲染 -->
<view wx:if="{{isShow}}">显示的内容</view>
<view wx:elif="{{isHide}}">隐藏的内容</view>
<view wx:else>默认内容</view>

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

3. 事件绑定

1
2
3
4
5
6
7
8
9
10
11
12
13
<!-- 点击事件 -->
<button bindtap="handleTap">点击</button>

<!-- 长按事件 -->
<view bindlongpress="handleLongPress">长按我</view>

<!-- 输入事件 -->
<input bindinput="handleInput" bindblur="handleBlur" />

<!-- 事件传参 -->
<button bindtap="handleTap" data-id="{{item.id}}" data-name="{{item.name}}">
{{item.name}}
</button>

(二)WXSS样式

WXSS(WeiXin Style Sheets)是小程序的样式语言,在CSS基础上扩展了尺寸单位和样式导入功能。

1. 基本语法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
/* 全局样式 */
.container {
display: flex;
flex-direction: column;
align-items: center;
padding: 20rpx;
background-color: #f5f5f5;
}

/* 文本样式 */
.title {
font-size: 32rpx;
font-weight: bold;
color: #333;
margin-bottom: 20rpx;
}

/* 按钮样式 */
.btn {
width: 200rpx;
height: 80rpx;
background-color: #007aff;
color: white;
border-radius: 10rpx;
display: flex;
align-items: center;
justify-content: center;
}

/* 响应式设计 */
@media (max-width: 600rpx) {
.container {
padding: 10rpx;
}
}

2. 尺寸单位

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
/* rpx:响应式像素,根据屏幕宽度自适应 */
.box {
width: 750rpx; /* 在iPhone6上等于375px */
height: 200rpx;
}

/* px:物理像素 */
.border {
border: 1px solid #ccc;
}

/* %:百分比 */
.full-width {
width: 100%;
}

3. 样式导入

1
2
3
4
5
6
7
8
9
10
11
12
/* 导入外部样式 */
@import "common.wxss";

/* 使用变量 */
:root {
--main-color: #007aff;
--text-color: #333;
}

.text {
color: var(--text-color);
}

(三)JavaScript逻辑

1. 页面生命周期

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
// pages/index/index.js
Page({
/**
* 页面的初始数据
*/
data: {
message: 'Hello World',
userInfo: {},
hasUserInfo: false
},

/**
* 生命周期函数--监听页面加载
*/
onLoad: function (options) {
console.log('页面加载', options)
// 页面加载时执行
this.initData()
},

/**
* 生命周期函数--监听页面显示
*/
onShow: function () {
console.log('页面显示')
// 页面显示时执行
},

/**
* 生命周期函数--监听页面隐藏
*/
onHide: function () {
console.log('页面隐藏')
// 页面隐藏时执行
},

/**
* 生命周期函数--监听页面卸载
*/
onUnload: function () {
console.log('页面卸载')
// 页面卸载时执行
},

/**
* 页面相关事件处理函数--监听用户下拉动作
*/
onPullDownRefresh: function () {
console.log('下拉刷新')
// 下拉刷新时执行
this.refreshData()
},

/**
* 页面上拉触底事件的处理函数
*/
onReachBottom: function () {
console.log('上拉触底')
// 上拉触底时执行
this.loadMoreData()
},

// 自定义方法
initData: function() {
// 初始化数据
},

refreshData: function() {
// 刷新数据
wx.stopPullDownRefresh() // 停止下拉刷新
},

loadMoreData: function() {
// 加载更多数据
}
})

2. 数据绑定和更新

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
Page({
data: {
count: 0,
list: [],
userInfo: {
name: '张三',
age: 25
}
},

// 更新数据
updateData: function() {
// 更新简单数据
this.setData({
count: this.data.count + 1
})

// 更新数组
this.setData({
list: [...this.data.list, { id: Date.now(), name: '新项目' }]
})

// 更新对象属性
this.setData({
'userInfo.name': '李四',
'userInfo.age': 30
})
},

// 事件处理
handleTap: function(e) {
console.log('点击事件', e)
const { id, name } = e.currentTarget.dataset
console.log('传递的数据', id, name)
},

handleInput: function(e) {
const value = e.detail.value
this.setData({
inputValue: value
})
}
})

3. 小程序API调用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
Page({
// 网络请求
fetchData: function() {
wx.request({
url: 'https://api.example.com/data',
method: 'GET',
data: {
page: 1,
limit: 10
},
header: {
'content-type': 'application/json'
},
success: (res) => {
console.log('请求成功', res.data)
this.setData({
list: res.data.list
})
},
fail: (err) => {
console.error('请求失败', err)
wx.showToast({
title: '网络错误',
icon: 'error'
})
}
})
},

// 显示提示
showToast: function() {
wx.showToast({
title: '操作成功',
icon: 'success',
duration: 2000
})
},

// 显示模态框
showModal: function() {
wx.showModal({
title: '提示',
content: '确定要删除吗?',
success: (res) => {
if (res.confirm) {
console.log('用户点击确定')
this.deleteItem()
} else if (res.cancel) {
console.log('用户点击取消')
}
}
})
},

// 页面跳转
navigateToDetail: function() {
wx.navigateTo({
url: '/pages/detail/detail?id=123&name=test'
})
},

// 获取用户信息
getUserInfo: function() {
wx.getUserProfile({
desc: '用于完善会员资料',
success: (res) => {
console.log('获取用户信息成功', res.userInfo)
this.setData({
userInfo: res.userInfo,
hasUserInfo: true
})
},
fail: (err) => {
console.error('获取用户信息失败', err)
}
})
},

// 选择图片
chooseImage: function() {
wx.chooseMedia({
count: 1,
mediaType: ['image'],
sourceType: ['album', 'camera'],
success: (res) => {
const tempFilePath = res.tempFiles[0].tempFilePath
this.setData({
imageSrc: tempFilePath
})
}
})
}
})

四、核心功能开发

(一)页面导航和路由

1. 配置页面路由

app.json 中配置页面路径:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
{
"pages": [
"pages/index/index",
"pages/detail/detail",
"pages/user/user",
"pages/search/search"
],
"window": {
"backgroundTextStyle": "light",
"navigationBarBackgroundColor": "#fff",
"navigationBarTitleText": "我的小程序",
"navigationBarTextStyle": "black"
},
"tabBar": {
"color": "#7A7E83",
"selectedColor": "#3cc51f",
"borderStyle": "black",
"backgroundColor": "#ffffff",
"list": [
{
"pagePath": "pages/index/index",
"iconPath": "images/icon_home.png",
"selectedIconPath": "images/icon_home_selected.png",
"text": "首页"
},
{
"pagePath": "pages/user/user",
"iconPath": "images/icon_user.png",
"selectedIconPath": "images/icon_user_selected.png",
"text": "我的"
}
]
}
}

2. 页面跳转方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
// 保留当前页面,跳转到应用内的某个页面
wx.navigateTo({
url: '/pages/detail/detail?id=123&name=test'
})

// 关闭当前页面,跳转到应用内的某个页面
wx.redirectTo({
url: '/pages/detail/detail'
})

// 跳转到 tabBar 页面,并关闭其他所有非 tabBar 页面
wx.switchTab({
url: '/pages/index/index'
})

// 关闭所有页面,打开到应用内的某个页面
wx.reLaunch({
url: '/pages/index/index'
})

// 关闭当前页面,返回上一页面或多级页面
wx.navigateBack({
delta: 1 // 返回的页面数,如果 delta 大于现有页面数,则返回到首页
})

3. 页面参数传递

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
// 发送页面
Page({
goToDetail: function() {
const data = {
id: 123,
name: '商品名称',
price: 99.9
}

wx.navigateTo({
url: `/pages/detail/detail?data=${JSON.stringify(data)}`
})
}
})

// 接收页面
Page({
onLoad: function(options) {
console.log('接收到的参数', options)

if (options.data) {
const data = JSON.parse(options.data)
this.setData({
productInfo: data
})
}
}
})

(二)数据存储

1. 本地存储

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
// 同步存储
wx.setStorageSync('userInfo', {
name: '张三',
age: 25,
avatar: 'https://example.com/avatar.jpg'
})

// 同步读取
const userInfo = wx.getStorageSync('userInfo')
console.log('用户信息', userInfo)

// 异步存储
wx.setStorage({
key: 'token',
data: 'abc123def456',
success: function() {
console.log('存储成功')
}
})

// 异步读取
wx.getStorage({
key: 'token',
success: function(res) {
console.log('Token', res.data)
},
fail: function() {
console.log('读取失败')
}
})

// 删除存储
wx.removeStorageSync('userInfo')

// 清空所有存储
wx.clearStorageSync()

2. 数据管理工具类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
// utils/storage.js
class Storage {
// 设置数据
static set(key, value) {
try {
wx.setStorageSync(key, value)
return true
} catch (error) {
console.error('存储失败', error)
return false
}
}

// 获取数据
static get(key, defaultValue = null) {
try {
const value = wx.getStorageSync(key)
return value !== '' ? value : defaultValue
} catch (error) {
console.error('读取失败', error)
return defaultValue
}
}

// 删除数据
static remove(key) {
try {
wx.removeStorageSync(key)
return true
} catch (error) {
console.error('删除失败', error)
return false
}
}

// 清空所有数据
static clear() {
try {
wx.clearStorageSync()
return true
} catch (error) {
console.error('清空失败', error)
return false
}
}

// 检查是否存在
static has(key) {
try {
const value = wx.getStorageSync(key)
return value !== ''
} catch (error) {
return false
}
}
}

module.exports = Storage

(三)网络请求

1. 基础网络请求

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
// 发起网络请求
wx.request({
url: 'https://api.example.com/users',
method: 'GET',
data: {
page: 1,
limit: 10
},
header: {
'content-type': 'application/json',
'Authorization': 'Bearer ' + wx.getStorageSync('token')
},
success: function(res) {
console.log('请求成功', res)
if (res.statusCode === 200) {
// 处理成功响应
console.log('数据', res.data)
} else {
// 处理错误响应
console.error('服务器错误', res.statusCode)
}
},
fail: function(err) {
console.error('请求失败', err)
wx.showToast({
title: '网络错误',
icon: 'error'
})
},
complete: function() {
console.log('请求完成')
// 隐藏加载提示
wx.hideLoading()
}
})

2. 封装网络请求工具

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
// utils/request.js
class Request {
constructor() {
this.baseURL = 'https://api.example.com'
this.timeout = 10000
}

// 请求拦截器
interceptRequest(config) {
// 显示加载提示
wx.showLoading({
title: '加载中...',
mask: true
})

// 添加token
const token = wx.getStorageSync('token')
if (token) {
config.header = {
...config.header,
'Authorization': `Bearer ${token}`
}
}

return config
}

// 响应拦截器
interceptResponse(response) {
wx.hideLoading()

if (response.statusCode === 200) {
const { code, data, message } = response.data

if (code === 0) {
return Promise.resolve(data)
} else if (code === 401) {
// token过期,跳转到登录页
wx.removeStorageSync('token')
wx.redirectTo({
url: '/pages/login/login'
})
return Promise.reject(new Error('登录已过期'))
} else {
wx.showToast({
title: message || '请求失败',
icon: 'error'
})
return Promise.reject(new Error(message))
}
} else {
wx.showToast({
title: '网络错误',
icon: 'error'
})
return Promise.reject(new Error('网络错误'))
}
}

// 通用请求方法
request(config) {
return new Promise((resolve, reject) => {
// 请求拦截
config = this.interceptRequest({
url: this.baseURL + config.url,
method: config.method || 'GET',
data: config.data || {},
header: {
'content-type': 'application/json',
...config.header
},
timeout: this.timeout
})

wx.request({
...config,
success: (response) => {
this.interceptResponse(response)
.then(resolve)
.catch(reject)
},
fail: (error) => {
wx.hideLoading()
wx.showToast({
title: '网络连接失败',
icon: 'error'
})
reject(error)
}
})
})
}

// GET请求
get(url, data = {}) {
return this.request({
url,
method: 'GET',
data
})
}

// POST请求
post(url, data = {}) {
return this.request({
url,
method: 'POST',
data
})
}

// PUT请求
put(url, data = {}) {
return this.request({
url,
method: 'PUT',
data
})
}

// DELETE请求
delete(url, data = {}) {
return this.request({
url,
method: 'DELETE',
data
})
}
}

// 创建实例
const request = new Request()

module.exports = request

3. API接口管理

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
// api/index.js
const request = require('../utils/request')

// 用户相关接口
const userAPI = {
// 用户登录
login(data) {
return request.post('/auth/login', data)
},

// 获取用户信息
getUserInfo() {
return request.get('/user/info')
},

// 更新用户信息
updateUserInfo(data) {
return request.put('/user/info', data)
}
}

// 商品相关接口
const productAPI = {
// 获取商品列表
getProductList(params) {
return request.get('/products', params)
},

// 获取商品详情
getProductDetail(id) {
return request.get(`/products/${id}`)
},

// 搜索商品
searchProducts(keyword) {
return request.get('/products/search', { keyword })
}
}

module.exports = {
userAPI,
productAPI
}

(四)组件开发

1. 自定义组件

创建组件文件夹 components/custom-button/

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
// components/custom-button/custom-button.js
Component({
/**
* 组件的属性列表
*/
properties: {
text: {
type: String,
value: '按钮'
},
type: {
type: String,
value: 'default' // default, primary, warn
},
size: {
type: String,
value: 'normal' // mini, normal, large
},
disabled: {
type: Boolean,
value: false
},
loading: {
type: Boolean,
value: false
}
},

/**
* 组件的初始数据
*/
data: {

},

/**
* 组件的方法列表
*/
methods: {
handleTap: function() {
if (this.data.disabled || this.data.loading) {
return
}

// 触发父组件事件
this.triggerEvent('tap', {
text: this.data.text
})
}
}
})
1
2
3
4
5
6
7
8
9
<!-- components/custom-button/custom-button.wxml -->
<button
class="custom-btn custom-btn-{{type}} custom-btn-{{size}}"
disabled="{{disabled || loading}}"
bindtap="handleTap"
>
<text wx:if="{{loading}}" class="loading">加载中...</text>
<text wx:else>{{text}}</text>
</button>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
/* components/custom-button/custom-button.wxss */
.custom-btn {
border: none;
border-radius: 8rpx;
font-size: 28rpx;
transition: all 0.3s;
}

.custom-btn-default {
background-color: #f5f5f5;
color: #333;
}

.custom-btn-primary {
background-color: #007aff;
color: white;
}

.custom-btn-warn {
background-color: #ff3b30;
color: white;
}

.custom-btn-mini {
padding: 10rpx 20rpx;
font-size: 24rpx;
}

.custom-btn-normal {
padding: 20rpx 40rpx;
font-size: 28rpx;
}

.custom-btn-large {
padding: 30rpx 60rpx;
font-size: 32rpx;
}

.loading {
opacity: 0.7;
}
1
2
3
4
{
"component": true,
"usingComponents": {}
}

2. 使用自定义组件

在页面中使用组件:

1
2
3
4
5
6
// pages/index/index.json
{
"usingComponents": {
"custom-button": "/components/custom-button/custom-button"
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
<!-- pages/index/index.wxml -->
<view class="container">
<custom-button
text="普通按钮"
type="default"
size="normal"
bind:tap="handleButtonTap"
></custom-button>

<custom-button
text="主要按钮"
type="primary"
size="large"
bind:tap="handleButtonTap"
></custom-button>

<custom-button
text="加载按钮"
type="primary"
loading="{{isLoading}}"
bind:tap="handleLoadingTap"
></custom-button>
</view>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
// pages/index/index.js
Page({
data: {
isLoading: false
},

handleButtonTap: function(e) {
console.log('按钮被点击', e.detail)
wx.showToast({
title: `点击了${e.detail.text}`,
icon: 'success'
})
},

handleLoadingTap: function() {
this.setData({
isLoading: true
})

setTimeout(() => {
this.setData({
isLoading: false
})
}, 2000)
}
})

五、实战项目:待办事项小程序

(一)项目需求分析

我们将开发一个简单的待办事项管理小程序,包含以下功能:

  1. 任务列表:显示所有待办任务
  2. 添加任务:创建新的待办任务
  3. 编辑任务:修改任务内容
  4. 完成任务:标记任务为已完成
  5. 删除任务:删除不需要的任务
  6. 任务筛选:按状态筛选任务

(二)项目结构设计

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
todo-miniprogram/
├── pages/
│ ├── index/ # 首页-任务列表
│ ├── add-task/ # 添加任务页面
│ └── edit-task/ # 编辑任务页面
├── components/
│ ├── task-item/ # 任务项组件
│ └── task-filter/ # 任务筛选组件
├── utils/
│ ├── storage.js # 存储工具
│ └── util.js # 通用工具
├── images/ # 图片资源
├── app.js
├── app.json
└── app.wxss

(三)核心功能实现

1. 数据模型设计

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
// utils/task-manager.js
class TaskManager {
constructor() {
this.storageKey = 'todo_tasks'
}

// 获取所有任务
getAllTasks() {
const tasks = wx.getStorageSync(this.storageKey) || []
return tasks.sort((a, b) => new Date(b.createTime) - new Date(a.createTime))
}

// 添加任务
addTask(title, description = '') {
const tasks = this.getAllTasks()
const newTask = {
id: Date.now().toString(),
title: title.trim(),
description: description.trim(),
completed: false,
createTime: new Date().toISOString(),
updateTime: new Date().toISOString()
}

tasks.unshift(newTask)
wx.setStorageSync(this.storageKey, tasks)
return newTask
}

// 更新任务
updateTask(id, updates) {
const tasks = this.getAllTasks()
const index = tasks.findIndex(task => task.id === id)

if (index !== -1) {
tasks[index] = {
...tasks[index],
...updates,
updateTime: new Date().toISOString()
}
wx.setStorageSync(this.storageKey, tasks)
return tasks[index]
}
return null
}

// 删除任务
deleteTask(id) {
const tasks = this.getAllTasks()
const filteredTasks = tasks.filter(task => task.id !== id)
wx.setStorageSync(this.storageKey, filteredTasks)
return true
}

// 切换任务完成状态
toggleTaskComplete(id) {
const tasks = this.getAllTasks()
const task = tasks.find(task => task.id === id)

if (task) {
return this.updateTask(id, { completed: !task.completed })
}
return null
}

// 获取任务统计
getTaskStats() {
const tasks = this.getAllTasks()
return {
total: tasks.length,
completed: tasks.filter(task => task.completed).length,
pending: tasks.filter(task => !task.completed).length
}
}

// 按状态筛选任务
getTasksByStatus(status) {
const tasks = this.getAllTasks()

switch (status) {
case 'completed':
return tasks.filter(task => task.completed)
case 'pending':
return tasks.filter(task => !task.completed)
default:
return tasks
}
}
}

module.exports = new TaskManager()

2. 任务列表页面

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
<!-- pages/index/index.wxml -->
<view class="container">
<!-- 头部统计 -->
<view class="header">
<view class="stats">
<view class="stat-item">
<text class="stat-number">{{stats.total}}</text>
<text class="stat-label">总计</text>
</view>
<view class="stat-item">
<text class="stat-number">{{stats.pending}}</text>
<text class="stat-label">待完成</text>
</view>
<view class="stat-item">
<text class="stat-number">{{stats.completed}}</text>
<text class="stat-label">已完成</text>
</view>
</view>
</view>

<!-- 筛选器 -->
<view class="filter-bar">
<view class="filter-item {{currentFilter === 'all' ? 'active' : ''}}"
bindtap="setFilter" data-filter="all">
全部
</view>
<view class="filter-item {{currentFilter === 'pending' ? 'active' : ''}}"
bindtap="setFilter" data-filter="pending">
待完成
</view>
<view class="filter-item {{currentFilter === 'completed' ? 'active' : ''}}"
bindtap="setFilter" data-filter="completed">
已完成
</view>
</view>

<!-- 任务列表 -->
<scroll-view class="task-list" scroll-y="true">
<view wx:if="{{filteredTasks.length === 0}}" class="empty-state">
<image src="/images/empty.png" class="empty-image"></image>
<text class="empty-text">暂无任务</text>
</view>

<task-item
wx:for="{{filteredTasks}}"
wx:key="id"
task="{{item}}"
bind:toggle="handleToggleTask"
bind:edit="handleEditTask"
bind:delete="handleDeleteTask"
></task-item>
</scroll-view>

<!-- 添加按钮 -->
<view class="add-button" bindtap="goToAddTask">
<text class="add-icon">+</text>
</view>
</view>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
// pages/index/index.js
const TaskManager = require('../../utils/task-manager')

Page({
data: {
tasks: [],
filteredTasks: [],
currentFilter: 'all',
stats: {
total: 0,
pending: 0,
completed: 0
}
},

onLoad: function() {
this.loadTasks()
},

onShow: function() {
this.loadTasks()
},

// 加载任务数据
loadTasks: function() {
const tasks = TaskManager.getAllTasks()
const stats = TaskManager.getTaskStats()

this.setData({
tasks,
stats
})

this.filterTasks()
},

// 筛选任务
filterTasks: function() {
const { currentFilter } = this.data
const filteredTasks = TaskManager.getTasksByStatus(
currentFilter === 'all' ? null : currentFilter
)

this.setData({
filteredTasks
})
},

// 设置筛选条件
setFilter: function(e) {
const filter = e.currentTarget.dataset.filter
this.setData({
currentFilter: filter
})
this.filterTasks()
},

// 切换任务完成状态
handleToggleTask: function(e) {
const { taskId } = e.detail
TaskManager.toggleTaskComplete(taskId)
this.loadTasks()

wx.showToast({
title: '状态已更新',
icon: 'success',
duration: 1000
})
},

// 编辑任务
handleEditTask: function(e) {
const { taskId } = e.detail
wx.navigateTo({
url: `/pages/edit-task/edit-task?id=${taskId}`
})
},

// 删除任务
handleDeleteTask: function(e) {
const { taskId } = e.detail

wx.showModal({
title: '确认删除',
content: '确定要删除这个任务吗?',
success: (res) => {
if (res.confirm) {
TaskManager.deleteTask(taskId)
this.loadTasks()

wx.showToast({
title: '删除成功',
icon: 'success'
})
}
}
})
},

// 跳转到添加任务页面
goToAddTask: function() {
wx.navigateTo({
url: '/pages/add-task/add-task'
})
}
})

3. 任务项组件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
<!-- components/task-item/task-item.wxml -->
<view class="task-item {{task.completed ? 'completed' : ''}}">
<view class="task-content" bindtap="handleToggle">
<view class="checkbox {{task.completed ? 'checked' : ''}}">
<text wx:if="{{task.completed}}" class="check-icon"></text>
</view>

<view class="task-info">
<text class="task-title">{{task.title}}</text>
<text wx:if="{{task.description}}" class="task-description">{{task.description}}</text>
<text class="task-time">{{formatTime(task.createTime)}}</text>
</view>
</view>

<view class="task-actions">
<view class="action-btn edit-btn" bindtap="handleEdit">
<text>编辑</text>
</view>
<view class="action-btn delete-btn" bindtap="handleDelete">
<text>删除</text>
</view>
</view>
</view>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
// components/task-item/task-item.js
Component({
properties: {
task: {
type: Object,
value: {}
}
},

methods: {
// 切换完成状态
handleToggle: function() {
this.triggerEvent('toggle', {
taskId: this.data.task.id
})
},

// 编辑任务
handleEdit: function() {
this.triggerEvent('edit', {
taskId: this.data.task.id
})
},

// 删除任务
handleDelete: function() {
this.triggerEvent('delete', {
taskId: this.data.task.id
})
},

// 格式化时间
formatTime: function(timeString) {
const date = new Date(timeString)
const now = new Date()
const diff = now - date

if (diff < 60000) { // 1分钟内
return '刚刚'
} else if (diff < 3600000) { // 1小时内
return `${Math.floor(diff / 60000)}分钟前`
} else if (diff < 86400000) { // 1天内
return `${Math.floor(diff / 3600000)}小时前`
} else {
return date.toLocaleDateString()
}
}
}
})

4. 添加任务页面

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
<!-- pages/add-task/add-task.wxml -->
<view class="container">
<view class="form">
<view class="form-item">
<text class="label">任务标题 *</text>
<input
class="input"
placeholder="请输入任务标题"
value="{{title}}"
bindinput="handleTitleInput"
maxlength="50"
/>
</view>

<view class="form-item">
<text class="label">任务描述</text>
<textarea
class="textarea"
placeholder="请输入任务描述(可选)"
value="{{description}}"
bindinput="handleDescriptionInput"
maxlength="200"
auto-height
></textarea>
</view>
</view>

<view class="button-group">
<button class="btn cancel-btn" bindtap="handleCancel">取消</button>
<button
class="btn save-btn {{!title.trim() ? 'disabled' : ''}}"
bindtap="handleSave"
disabled="{{!title.trim()}}"
>
保存
</button>
</view>
</view>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
// pages/add-task/add-task.js
const TaskManager = require('../../utils/task-manager')

Page({
data: {
title: '',
description: ''
},

// 输入标题
handleTitleInput: function(e) {
this.setData({
title: e.detail.value
})
},

// 输入描述
handleDescriptionInput: function(e) {
this.setData({
description: e.detail.value
})
},

// 保存任务
handleSave: function() {
const { title, description } = this.data

if (!title.trim()) {
wx.showToast({
title: '请输入任务标题',
icon: 'error'
})
return
}

try {
TaskManager.addTask(title, description)

wx.showToast({
title: '添加成功',
icon: 'success'
})

setTimeout(() => {
wx.navigateBack()
}, 1000)
} catch (error) {
wx.showToast({
title: '添加失败',
icon: 'error'
})
}
},

// 取消
handleCancel: function() {
if (this.data.title.trim() || this.data.description.trim()) {
wx.showModal({
title: '确认取消',
content: '当前有未保存的内容,确定要取消吗?',
success: (res) => {
if (res.confirm) {
wx.navigateBack()
}
}
})
} else {
wx.navigateBack()
}
}
})

(四)样式设计

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
/* app.wxss - 全局样式 */
page {
background-color: #f5f5f5;
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
}

.container {
min-height: 100vh;
padding: 20rpx;
}

/* 通用按钮样式 */
.btn {
border-radius: 12rpx;
font-size: 28rpx;
font-weight: 500;
transition: all 0.3s;
}

.btn.disabled {
opacity: 0.5;
}

/* 表单样式 */
.form {
background: white;
border-radius: 16rpx;
padding: 40rpx;
margin-bottom: 40rpx;
box-shadow: 0 2rpx 16rpx rgba(0, 0, 0, 0.1);
}

.form-item {
margin-bottom: 40rpx;
}

.form-item:last-child {
margin-bottom: 0;
}

.label {
display: block;
font-size: 28rpx;
color: #333;
margin-bottom: 16rpx;
font-weight: 500;
}

.input, .textarea {
width: 100%;
padding: 20rpx;
border: 2rpx solid #e0e0e0;
border-radius: 8rpx;
font-size: 28rpx;
background: #fafafa;
}

.input:focus, .textarea:focus {
border-color: #007aff;
background: white;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
/* pages/index/index.wxss */
.header {
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
border-radius: 16rpx;
padding: 40rpx;
margin-bottom: 30rpx;
color: white;
}

.stats {
display: flex;
justify-content: space-around;
}

.stat-item {
text-align: center;
}

.stat-number {
display: block;
font-size: 48rpx;
font-weight: bold;
margin-bottom: 8rpx;
}

.stat-label {
font-size: 24rpx;
opacity: 0.9;
}

.filter-bar {
display: flex;
background: white;
border-radius: 12rpx;
padding: 8rpx;
margin-bottom: 30rpx;
box-shadow: 0 2rpx 16rpx rgba(0, 0, 0, 0.1);
}

.filter-item {
flex: 1;
text-align: center;
padding: 16rpx;
border-radius: 8rpx;
font-size: 26rpx;
color: #666;
transition: all 0.3s;
}

.filter-item.active {
background: #007aff;
color: white;
}

.task-list {
height: calc(100vh - 400rpx);
}

.empty-state {
text-align: center;
padding: 100rpx 0;
}

.empty-image {
width: 200rpx;
height: 200rpx;
opacity: 0.3;
}

.empty-text {
display: block;
margin-top: 30rpx;
color: #999;
font-size: 28rpx;
}

.add-button {
position: fixed;
right: 40rpx;
bottom: 40rpx;
width: 120rpx;
height: 120rpx;
background: #007aff;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
box-shadow: 0 8rpx 24rpx rgba(0, 122, 255, 0.3);
z-index: 100;
}

.add-icon {
color: white;
font-size: 48rpx;
font-weight: 300;
}

六、小程序发布上线

(一)代码审查和测试

1. 代码质量检查

在发布前,需要进行全面的代码审查:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
// 检查清单
const codeReviewChecklist = {
// 功能完整性
functionality: [
'所有功能按需求正常工作',
'异常情况处理完善',
'用户体验流畅'
],

// 性能优化
performance: [
'图片资源已压缩',
'代码已压缩混淆',
'网络请求已优化',
'内存使用合理'
],

// 兼容性
compatibility: [
'不同机型测试通过',
'不同版本微信测试通过',
'iOS和Android测试通过'
],

// 安全性
security: [
'敏感信息已加密',
'用户数据保护完善',
'网络传输安全'
]
}

2. 真机测试

1
2
3
4
5
6
# 测试步骤
1. 在开发者工具中点击"预览"
2. 使用手机微信扫描二维码
3. 在真机上测试所有功能
4. 测试不同网络环境(WiFi、4G、弱网)
5. 测试不同屏幕尺寸适配

(二)版本管理

1. 版本号规范

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
// project.config.json
{
"projectname": "todo-miniprogram",
"appid": "your-app-id",
"setting": {
"urlCheck": true,
"es6": true,
"enhance": true,
"postcss": true,
"preloadBackgroundData": false,
"minified": true,
"newFeature": false,
"coverView": true,
"nodeModules": false,
"autoAudits": false,
"showShadowRootInWxmlPanel": true,
"scopeDataCheck": false,
"uglifyFileName": false,
"checkInvalidKey": true,
"checkSiteMap": true,
"uploadWithSourceMap": true,
"compileHotReLoad": false,
"lazyloadPlaceholderEnable": false,
"useMultiFrameRuntime": true,
"useApiHook": true,
"useApiHostProcess": true,
"babelSetting": {
"ignore": [],
"disablePlugins": [],
"outputPath": ""
},
"enableEngineNative": false,
"useIsolateContext": false,
"userConfirmedBundleSwitch": false,
"packNpmManually": false,
"packNpmRelationList": [],
"minifyWXSS": true,
"disableUseStrict": false,
"minifyWXML": true,
"showES6CompileOption": false,
"useCompilerPlugins": false
},
"compileType": "miniprogram",
"libVersion": "2.19.4",
"packOptions": {
"ignore": [],
"include": []
},
"condition": {},
"editorSetting": {
"tabIndent": "insertSpaces",
"tabSize": 2
}
}

2. 提交代码

1
2
3
4
5
# 使用Git管理代码版本
git init
git add .
git commit -m "feat: 初始版本 - 待办事项小程序"
git tag v1.0.0

(三)上传代码

1. 开发版本上传

1
2
3
4
5
1. 在微信开发者工具中点击"上传"
2. 填写版本号和项目备注
- 版本号:1.0.0
- 项目备注:初始版本,包含基础的待办事项管理功能
3. 点击"上传"

2. 体验版本设置

1
2
3
4
5
1. 登录微信公众平台
2. 进入"开发" -> "开发版本"
3. 找到刚上传的版本
4. 点击"选为体验版"
5. 设置体验者微信号

(四)提交审核

1. 审核前准备

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
// 审核准备清单
const auditPreparation = {
// 基本信息
basicInfo: [
'小程序名称符合规范',
'小程序简介准确描述功能',
'小程序头像清晰合规',
'服务类目选择正确'
],

// 功能完整性
functionality: [
'核心功能完整可用',
'页面跳转正常',
'数据加载正常',
'异常处理完善'
],

// 用户体验
userExperience: [
'界面美观易用',
'操作流程顺畅',
'加载速度合理',
'错误提示友好'
],

// 合规性
compliance: [
'内容健康合规',
'无违规功能',
'隐私政策完善',
'用户协议清晰'
]
}

2. 提交审核流程

1
2
3
4
5
6
7
1. 在微信公众平台进入"开发" -> "开发版本"
2. 点击"提交审核"
3. 填写审核信息:
- 功能页面:选择需要审核的页面
- 测试帐号:提供测试用的微信号(如需要)
- 补充说明:详细说明小程序功能和使用方法
4. 确认提交

3. 审核注意事项

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
## 常见审核问题及解决方案

### 功能问题
- **问题**:页面空白或加载失败
- **解决**:检查网络请求、数据绑定、页面路径

### 内容问题
- **问题**:内容不符合规范
- **解决**:确保内容健康、无违规信息

### 体验问题
- **问题**:操作不流畅、界面混乱
- **解决**:优化交互设计、统一视觉风格

### 信息问题
- **问题**:小程序信息不准确
- **解决**:完善小程序描述、选择正确类目

(五)发布上线

1. 审核通过后发布

1
2
3
4
1. 审核通过后,在"开发版本"页面会显示"审核通过"
2. 点击"发布"按钮
3. 确认发布信息
4. 点击"确定"完成发布

2. 版本管理

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
// 版本发布记录
const versionHistory = {
'v1.0.0': {
releaseDate: '2024-01-15',
features: [
'基础的待办事项管理功能',
'任务的增删改查',
'任务状态筛选',
'数据本地存储'
],
bugFixes: [],
notes: '初始版本发布'
},

'v1.1.0': {
releaseDate: '2024-02-01',
features: [
'添加任务提醒功能',
'支持任务分类',
'优化界面设计'
],
bugFixes: [
'修复任务删除后数据不同步问题',
'修复长文本显示异常'
],
notes: '功能增强版本'
}
}

七、性能优化与最佳实践

(一)性能优化策略

1. 代码优化

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
// 1. 避免频繁的setData调用
// 不好的做法
for (let i = 0; i < 100; i++) {
this.setData({
[`list[${i}]`]: newValue
})
}

// 好的做法
const updates = {}
for (let i = 0; i < 100; i++) {
updates[`list[${i}]`] = newValue
}
this.setData(updates)

// 2. 减少数据传输量
// 不好的做法
this.setData({
largeObject: {
...this.data.largeObject,
newProperty: value
}
})

// 好的做法
this.setData({
'largeObject.newProperty': value
})

// 3. 使用节流和防抖
const throttle = (func, delay) => {
let timer = null
return function(...args) {
if (!timer) {
timer = setTimeout(() => {
func.apply(this, args)
timer = null
}, delay)
}
}
}

const debounce = (func, delay) => {
let timer = null
return function(...args) {
clearTimeout(timer)
timer = setTimeout(() => {
func.apply(this, args)
}, delay)
}
}

// 使用示例
Page({
data: {
searchKeyword: ''
},

// 搜索输入防抖
onSearchInput: debounce(function(e) {
const keyword = e.detail.value
this.setData({
searchKeyword: keyword
})
this.searchData(keyword)
}, 300),

// 滚动事件节流
onScroll: throttle(function(e) {
const scrollTop = e.detail.scrollTop
// 处理滚动逻辑
}, 100)
})

2. 图片优化

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
// 图片优化工具类
class ImageOptimizer {
// 压缩图片
static compressImage(filePath, quality = 0.8) {
return new Promise((resolve, reject) => {
wx.compressImage({
src: filePath,
quality: quality,
success: resolve,
fail: reject
})
})
}

// 获取图片信息
static getImageInfo(src) {
return new Promise((resolve, reject) => {
wx.getImageInfo({
src: src,
success: resolve,
fail: reject
})
})
}

// 选择并压缩图片
static async chooseAndCompressImage(options = {}) {
try {
const chooseResult = await wx.chooseMedia({
count: 1,
mediaType: ['image'],
sourceType: ['album', 'camera'],
...options
})

const tempFilePath = chooseResult.tempFiles[0].tempFilePath
const compressResult = await this.compressImage(tempFilePath)

return compressResult.tempFilePath
} catch (error) {
throw new Error('图片处理失败')
}
}
}

// 使用示例
Page({
async chooseAvatar() {
try {
wx.showLoading({ title: '处理中...' })

const compressedPath = await ImageOptimizer.chooseAndCompressImage({
quality: 0.7
})

this.setData({
avatarUrl: compressedPath
})

wx.hideLoading()
} catch (error) {
wx.hideLoading()
wx.showToast({
title: '图片处理失败',
icon: 'error'
})
}
}
})

3. 网络请求优化

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
// 请求缓存管理
class RequestCache {
constructor() {
this.cache = new Map()
this.maxCacheSize = 50
this.defaultExpireTime = 5 * 60 * 1000 // 5分钟
}

// 生成缓存key
generateKey(url, data) {
return `${url}_${JSON.stringify(data || {})}`
}

// 设置缓存
set(key, data, expireTime = this.defaultExpireTime) {
// 清理过期缓存
this.clearExpired()

// 限制缓存大小
if (this.cache.size >= this.maxCacheSize) {
const firstKey = this.cache.keys().next().value
this.cache.delete(firstKey)
}

this.cache.set(key, {
data,
expireTime: Date.now() + expireTime
})
}

// 获取缓存
get(key) {
const cached = this.cache.get(key)

if (!cached) {
return null
}

if (Date.now() > cached.expireTime) {
this.cache.delete(key)
return null
}

return cached.data
}

// 清理过期缓存
clearExpired() {
const now = Date.now()
for (const [key, value] of this.cache.entries()) {
if (now > value.expireTime) {
this.cache.delete(key)
}
}
}

// 清空所有缓存
clear() {
this.cache.clear()
}
}

// 增强的请求工具
class EnhancedRequest {
constructor() {
this.cache = new RequestCache()
this.pendingRequests = new Map()
}

async request(config) {
const { url, data, useCache = true, cacheTime } = config
const cacheKey = this.cache.generateKey(url, data)

// 检查缓存
if (useCache) {
const cachedData = this.cache.get(cacheKey)
if (cachedData) {
return Promise.resolve(cachedData)
}
}

// 防止重复请求
if (this.pendingRequests.has(cacheKey)) {
return this.pendingRequests.get(cacheKey)
}

// 发起请求
const requestPromise = new Promise((resolve, reject) => {
wx.request({
...config,
success: (res) => {
this.pendingRequests.delete(cacheKey)

if (res.statusCode === 200) {
// 缓存响应数据
if (useCache) {
this.cache.set(cacheKey, res.data, cacheTime)
}
resolve(res.data)
} else {
reject(new Error(`请求失败: ${res.statusCode}`))
}
},
fail: (error) => {
this.pendingRequests.delete(cacheKey)
reject(error)
}
})
})

this.pendingRequests.set(cacheKey, requestPromise)
return requestPromise
}
}

const enhancedRequest = new EnhancedRequest()
module.exports = enhancedRequest

(二)最佳实践

1. 代码组织

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
// 1. 使用模块化开发
// utils/constants.js - 常量管理
module.exports = {
API_BASE_URL: 'https://api.example.com',
STORAGE_KEYS: {
USER_INFO: 'userInfo',
TOKEN: 'token',
SETTINGS: 'settings'
},
STATUS_CODES: {
SUCCESS: 0,
ERROR: -1,
UNAUTHORIZED: 401
}
}

// utils/validators.js - 验证工具
class Validators {
static isEmail(email) {
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/
return emailRegex.test(email)
}

static isPhone(phone) {
const phoneRegex = /^1[3-9]\d{9}$/
return phoneRegex.test(phone)
}

static isRequired(value) {
return value !== null && value !== undefined && value.toString().trim() !== ''
}

static minLength(value, min) {
return value && value.length >= min
}

static maxLength(value, max) {
return value && value.length <= max
}
}

module.exports = Validators

// utils/formatters.js - 格式化工具
class Formatters {
static formatDate(date, format = 'YYYY-MM-DD') {
const d = new Date(date)
const year = d.getFullYear()
const month = String(d.getMonth() + 1).padStart(2, '0')
const day = String(d.getDate()).padStart(2, '0')
const hour = String(d.getHours()).padStart(2, '0')
const minute = String(d.getMinutes()).padStart(2, '0')
const second = String(d.getSeconds()).padStart(2, '0')

return format
.replace('YYYY', year)
.replace('MM', month)
.replace('DD', day)
.replace('HH', hour)
.replace('mm', minute)
.replace('ss', second)
}

static formatNumber(num, decimals = 2) {
return Number(num).toFixed(decimals)
}

static formatFileSize(bytes) {
if (bytes === 0) return '0 B'

const k = 1024
const sizes = ['B', 'KB', 'MB', 'GB']
const i = Math.floor(Math.log(bytes) / Math.log(k))

return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i]
}
}

module.exports = Formatters

2. 错误处理

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
// utils/error-handler.js
class ErrorHandler {
static handle(error, context = '') {
console.error(`[${context}] 错误:`, error)

let message = '操作失败,请稍后重试'

if (error.message) {
message = error.message
} else if (typeof error === 'string') {
message = error
}

wx.showToast({
title: message,
icon: 'error',
duration: 2000
})

// 上报错误(可选)
this.reportError(error, context)
}

static reportError(error, context) {
// 错误上报逻辑
const errorInfo = {
message: error.message || error,
stack: error.stack,
context,
timestamp: new Date().toISOString(),
userAgent: wx.getSystemInfoSync()
}

// 发送到错误监控服务
wx.request({
url: 'https://api.example.com/errors',
method: 'POST',
data: errorInfo,
fail: () => {
// 静默失败,不影响用户体验
}
})
}

static async withErrorHandling(asyncFn, context = '') {
try {
return await asyncFn()
} catch (error) {
this.handle(error, context)
throw error
}
}
}

module.exports = ErrorHandler

// 使用示例
const ErrorHandler = require('../../utils/error-handler')

Page({
async loadData() {
await ErrorHandler.withErrorHandling(async () => {
const data = await api.getData()
this.setData({ data })
}, 'loadData')
},

handleSubmit() {
try {
// 业务逻辑
this.submitForm()
} catch (error) {
ErrorHandler.handle(error, 'handleSubmit')
}
}
})

3. 状态管理

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
// utils/store.js - 简单的状态管理
class Store {
constructor() {
this.state = {}
this.listeners = []
}

// 设置状态
setState(newState) {
this.state = {
...this.state,
...newState
}

// 通知所有监听者
this.listeners.forEach(listener => {
listener(this.state)
})
}

// 获取状态
getState() {
return this.state
}

// 订阅状态变化
subscribe(listener) {
this.listeners.push(listener)

// 返回取消订阅函数
return () => {
const index = this.listeners.indexOf(listener)
if (index > -1) {
this.listeners.splice(index, 1)
}
}
}

// 清空状态
clear() {
this.state = {}
this.listeners = []
}
}

// 创建全局store实例
const globalStore = new Store()

// 初始化用户状态
globalStore.setState({
user: {
isLogin: false,
userInfo: null,
token: null
},
app: {
loading: false,
networkStatus: 'online'
}
})

module.exports = globalStore

// 在页面中使用
const store = require('../../utils/store')

Page({
data: {
userInfo: null
},

onLoad() {
// 订阅状态变化
this.unsubscribe = store.subscribe((state) => {
this.setData({
userInfo: state.user.userInfo
})
})

// 获取初始状态
const state = store.getState()
this.setData({
userInfo: state.user.userInfo
})
},

onUnload() {
// 取消订阅
if (this.unsubscribe) {
this.unsubscribe()
}
},

login() {
// 更新全局状态
store.setState({
user: {
isLogin: true,
userInfo: { name: '张三', avatar: 'xxx' },
token: 'abc123'
}
})
}
})

八、学习建议与进阶方向

(一)学习路径规划

1. 基础阶段(1-2个月)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
## 第一阶段:环境搭建和基础语法

### 第1周:环境准备
- [ ] 注册小程序账号
- [ ] 下载开发工具
- [ ] 创建第一个项目
- [ ] 熟悉开发工具界面

### 第2周:WXML和WXSS
- [ ] 学习WXML基础语法
- [ ] 掌握数据绑定
- [ ] 学习WXSS样式编写
- [ ] 了解rpx单位使用

### 第3周:JavaScript逻辑
- [ ] 页面生命周期
- [ ] 事件处理
- [ ] 数据更新机制
- [ ] 基础API使用

### 第4周:综合练习
- [ ] 完成一个简单的计算器
- [ ] 实现一个图片查看器
- [ ] 制作一个简单的表单

2. 进阶阶段(2-3个月)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
## 第二阶段:核心功能开发

### 第5-6周:组件开发
- [ ] 自定义组件创建
- [ ] 组件通信机制
- [ ] 组件生命周期
- [ ] 组件库使用

### 第7-8周:网络和存储
- [ ] 网络请求处理
- [ ] 数据缓存策略
- [ ] 本地存储使用
- [ ] 文件上传下载

### 第9-10周:高级功能
- [ ] 微信登录授权
- [ ] 支付功能集成
- [ ] 分享功能实现
- [ ] 地图定位服务

### 第11-12周:项目实战
- [ ] 完成一个完整项目
- [ ] 代码优化重构
- [ ] 性能测试优化
- [ ] 发布上线流程

3. 高级阶段(3-6个月)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
## 第三阶段:深入优化和扩展

### 架构设计
- [ ] 模块化开发
- [ ] 状态管理
- [ ] 路由管理
- [ ] 错误处理机制

### 性能优化
- [ ] 代码分包
- [ ] 懒加载实现
- [ ] 图片优化
- [ ] 网络优化

### 工程化
- [ ] 自动化构建
- [ ] 代码规范
- [ ] 单元测试
- [ ] 持续集成

### 生态扩展
- [ ] 云开发使用
- [ ] 插件开发
- [ ] 第三方库集成
- [ ] 跨平台方案

(二)实践项目推荐

1. 入门项目

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
// 项目1:个人名片小程序
const businessCardProject = {
description: '制作个人或企业名片展示',
features: [
'个人信息展示',
'联系方式展示',
'二维码生成',
'一键保存联系人'
],
techStack: ['基础组件', '数据绑定', 'API调用'],
difficulty: '⭐',
timeEstimate: '1-2周'
}

// 项目2:天气查询小程序
const weatherProject = {
description: '实时天气信息查询',
features: [
'当前天气显示',
'未来天气预报',
'城市搜索',
'定位获取天气'
],
techStack: ['网络请求', '地理定位', '数据处理'],
difficulty: '⭐⭐',
timeEstimate: '2-3周'
}

2. 进阶项目

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
// 项目3:在线商城小程序
const ecommerceProject = {
description: '完整的电商购物平台',
features: [
'商品展示和搜索',
'购物车管理',
'订单处理',
'用户中心',
'支付集成'
],
techStack: [
'组件化开发',
'状态管理',
'支付API',
'用户授权'
],
difficulty: '⭐⭐⭐⭐',
timeEstimate: '2-3个月'
}

// 项目4:社交分享小程序
const socialProject = {
description: '图片分享和社交互动平台',
features: [
'图片上传和编辑',
'动态发布',
'点赞评论',
'用户关注',
'消息通知'
],
techStack: [
'文件上传',
'实时通信',
'云开发',
'图片处理'
],
difficulty: '⭐⭐⭐⭐⭐',
timeEstimate: '3-4个月'
}

(三)学习资源推荐

1. 官方资源

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
## 官方文档和工具

### 核心文档
- [微信小程序官方文档](https://developers.weixin.qq.com/miniprogram/dev/framework/)
- [小程序API参考](https://developers.weixin.qq.com/miniprogram/dev/api/)
- [组件库文档](https://developers.weixin.qq.com/miniprogram/dev/component/)

### 开发工具
- [微信开发者工具](https://developers.weixin.qq.com/miniprogram/dev/devtools/download.html)
- [小程序助手](https://developers.weixin.qq.com/miniprogram/dev/devtools/mydev.html)
- [代码片段工具](https://developers.weixin.qq.com/s/)

### 设计资源
- [微信设计指南](https://developers.weixin.qq.com/miniprogram/design/)
- [WeUI组件库](https://github.com/Tencent/weui-wxss)
- [官方DEMO](https://github.com/wechat-miniprogram/miniprogram-demo)

2. 社区资源

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
## 开源项目和社区

### 优秀开源项目
- [Vant Weapp](https://github.com/youzan/vant-weapp) - 轻量、可靠的小程序UI组件库
- [ColorUI](https://github.com/weilanwl/ColorUI) - 鲜亮的高饱和色彩UI框架
- [MinUI](https://github.com/meili/minui) - 基于微信小程序自定义组件特性开发而成的一套简洁、易用、高效的组件库

### 学习平台
- [微信小程序联盟](https://www.wxapp-union.com/)
- [小程序社区](https://developers.weixin.qq.com/community/minihome)
- [掘金小程序专栏](https://juejin.cn/tag/%E5%B0%8F%E7%A8%8B%E5%BA%8F)

### 技术博客
- 关注微信官方技术团队博客
- 阅读优秀开发者的实践分享
- 参与技术讨论和问答

3. 进阶学习

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
// 技术栈扩展建议
const advancedLearning = {
// 前端技术栈
frontend: [
'TypeScript - 类型安全的JavaScript',
'Sass/Less - CSS预处理器',
'Webpack - 模块打包工具',
'ESLint - 代码质量检查'
],

// 后端技术栈
backend: [
'Node.js - 服务端JavaScript',
'Express/Koa - Web框架',
'MongoDB/MySQL - 数据库',
'Redis - 缓存数据库'
],

// 云服务
cloud: [
'微信云开发 - 一站式后端服务',
'腾讯云 - 云计算服务',
'阿里云 - 云计算服务',
'AWS - 亚马逊云服务'
],

// 工程化工具
engineering: [
'Git - 版本控制',
'CI/CD - 持续集成部署',
'Docker - 容器化',
'Nginx - Web服务器'
]
}

总结

微信小程序作为移动互联网时代的重要应用形态,为开发者提供了便捷的开发平台和丰富的功能接口。通过本文的详细介绍,我们从零开始学习了小程序开发的完整流程:

核心要点回顾

  1. 基础知识:掌握WXML、WXSS、JavaScript和JSON四种文件类型的使用
  2. 开发环境:熟练使用微信开发者工具进行项目创建、调试和发布
  3. 核心功能:实现页面导航、数据存储、网络请求、组件开发等关键功能
  4. 实战项目:通过待办事项小程序的完整开发,理解项目架构和开发流程
  5. 性能优化:学习代码优化、图片处理、网络缓存等性能提升技巧
  6. 发布上线:了解审核规范、版本管理、发布流程等运营知识

持续学习建议

小程序开发是一个不断发展的技术领域,建议开发者:

  • 保持学习:关注官方文档更新,学习新特性和最佳实践
  • 多做项目:通过实际项目积累经验,提升解决问题的能力
  • 参与社区:加入开发者社区,与同行交流经验和技术
  • 关注生态:了解相关技术栈,如云开发、跨平台框架等

希望本文能够帮助你快速入门微信小程序开发,在移动应用开发的道路上取得成功!