前言
微信小程序作为腾讯推出的轻量级应用平台,自2017年正式发布以来,已经成为移动互联网生态中不可或缺的一部分。小程序具有”用完即走”的特点,无需下载安装,通过微信即可使用,为用户提供了便捷的服务体验,也为开发者提供了新的应用分发渠道。
本文将从零开始,详细介绍微信小程序的开发流程,包括环境搭建、基础语法、核心功能实现、发布上线等各个环节,帮助初学者快速掌握小程序开发技能。
一、微信小程序概述
(一)什么是微信小程序
微信小程序是一种不需要下载安装即可使用的应用,它实现了应用”触手可及”的梦想,用户扫一扫或者搜一下即可打开应用。小程序具有以下特点:
- 无需安装:通过微信直接使用,不占用手机存储空间
- 即用即走:用完即可关闭,不会在后台持续运行
- 功能丰富:支持多种API,可实现复杂的业务逻辑
- 跨平台:一次开发,同时支持iOS和Android
- 生态完善:与微信生态深度融合,支持分享、支付等功能
(二)小程序的技术架构
微信小程序采用双线程架构:
- 逻辑层(App Service):运行JavaScript代码,处理业务逻辑
- 视图层(View):由WXML和WXSS组成,负责页面渲染
两个线程通过微信客户端(Native)进行通信,确保了界面渲染的流畅性和逻辑处理的独立性。
(三)开发语言和文件类型
小程序主要使用以下四种文件类型:
- WXML:类似HTML的标记语言,用于描述页面结构
- WXSS:类似CSS的样式语言,用于描述页面样式
- JavaScript:用于处理页面逻辑
- 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
| .box { width: 750rpx; height: 200rpx; }
.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
| 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' })
wx.switchTab({ url: '/pages/index/index' })
wx.reLaunch({ url: '/pages/index/index' })
wx.navigateBack({ delta: 1 })
|
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
| 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
| class Request { constructor() { this.baseURL = 'https://api.example.com' this.timeout = 10000 }
interceptRequest(config) { wx.showLoading({ title: '加载中...', mask: true })
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) { 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(url, data = {}) { return this.request({ url, method: 'GET', data }) }
post(url, data = {}) { return this.request({ url, method: 'POST', data }) }
put(url, data = {}) { return this.request({ url, method: 'PUT', data }) }
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
| 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
| Component({
properties: { text: { type: String, value: '按钮' }, type: { type: String, value: 'default' }, size: { type: String, value: 'normal' }, 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
| <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
| .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
| { "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
| <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
| 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 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
| 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
| <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
| 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
| <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
| 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) { return '刚刚' } else if (diff < 3600000) { return `${Math.floor(diff / 60000)}分钟前` } else if (diff < 86400000) { 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
| <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
| 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
| 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
| .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
| { "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
|
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)
this.setData({ largeObject: { ...this.data.largeObject, newProperty: value } })
this.setData({ 'largeObject.newProperty': value })
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 } 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
|
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 } }
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
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
| 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
| 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 = [] } }
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
| const businessCardProject = { description: '制作个人或企业名片展示', features: [ '个人信息展示', '联系方式展示', '二维码生成', '一键保存联系人' ], techStack: ['基础组件', '数据绑定', 'API调用'], difficulty: '⭐', timeEstimate: '1-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
| const ecommerceProject = { description: '完整的电商购物平台', features: [ '商品展示和搜索', '购物车管理', '订单处理', '用户中心', '支付集成' ], techStack: [ '组件化开发', '状态管理', '支付API', '用户授权' ], difficulty: '⭐⭐⭐⭐', timeEstimate: '2-3个月' }
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服务器' ] }
|
总结
微信小程序作为移动互联网时代的重要应用形态,为开发者提供了便捷的开发平台和丰富的功能接口。通过本文的详细介绍,我们从零开始学习了小程序开发的完整流程:
核心要点回顾
- 基础知识:掌握WXML、WXSS、JavaScript和JSON四种文件类型的使用
- 开发环境:熟练使用微信开发者工具进行项目创建、调试和发布
- 核心功能:实现页面导航、数据存储、网络请求、组件开发等关键功能
- 实战项目:通过待办事项小程序的完整开发,理解项目架构和开发流程
- 性能优化:学习代码优化、图片处理、网络缓存等性能提升技巧
- 发布上线:了解审核规范、版本管理、发布流程等运营知识
持续学习建议
小程序开发是一个不断发展的技术领域,建议开发者:
- 保持学习:关注官方文档更新,学习新特性和最佳实践
- 多做项目:通过实际项目积累经验,提升解决问题的能力
- 参与社区:加入开发者社区,与同行交流经验和技术
- 关注生态:了解相关技术栈,如云开发、跨平台框架等
希望本文能够帮助你快速入门微信小程序开发,在移动应用开发的道路上取得成功!