【学习】微信小程序开发指南:从零开始的完整实践

前言

微信小程序作为腾讯推出的轻量级应用平台,自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. 微信认证(可选)

3. 获取AppID

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

(二)下载开发工具

1. 微信开发者工具下载

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

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

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

2. 安装开发工具

# Windows
双击下载的.exe文件,按照提示安装

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

# Linux
sudo dpkg -i wechat_devtools_xxx_linux_x64.deb

3. 登录开发工具

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

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

1. 新建项目

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

2. 项目结构说明

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

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. 基本标签

<!-- 视图容器 -->
<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. 数据绑定

<!-- 文本绑定 -->
<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. 事件绑定

<!-- 点击事件 -->
<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. 基本语法

/* 全局样式 */
.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. 尺寸单位

/* rpx:响应式像素,根据屏幕宽度自适应 */
.box {
  width: 750rpx;  /* 在iPhone6上等于375px */
  height: 200rpx;
}

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

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

3. 样式导入

/* 导入外部样式 */
@import "common.wxss";

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

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

(三)JavaScript逻辑

1. 页面生命周期

// 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. 数据绑定和更新

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调用

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 中配置页面路径:

{
  "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. 页面跳转方法

// 保留当前页面,跳转到应用内的某个页面
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. 页面参数传递

// 发送页面
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. 本地存储

// 同步存储
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. 数据管理工具类

// 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. 基础网络请求

// 发起网络请求
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. 封装网络请求工具

// 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接口管理

// 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/

// 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
      })
    }
  }
})
<!-- 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>
/* 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;
}
{
  "component": true,
  "usingComponents": {}
}

2. 使用自定义组件

在页面中使用组件:

// pages/index/index.json
{
  "usingComponents": {
    "custom-button": "/components/custom-button/custom-button"
  }
}
<!-- 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>
// 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. 任务筛选:按状态筛选任务

(二)项目结构设计

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. 数据模型设计

// 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. 任务列表页面

<!-- 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>
// 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. 任务项组件

<!-- 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>
// 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. 添加任务页面

<!-- 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>
// 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()
    }
  }
})

(四)样式设计

/* 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;
}
/* 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. 代码质量检查

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

// 检查清单
const codeReviewChecklist = {
  // 功能完整性
  functionality: [
    '所有功能按需求正常工作',
    '异常情况处理完善',
    '用户体验流畅'
  ],
  
  // 性能优化
  performance: [
    '图片资源已压缩',
    '代码已压缩混淆',
    '网络请求已优化',
    '内存使用合理'
  ],
  
  // 兼容性
  compatibility: [
    '不同机型测试通过',
    '不同版本微信测试通过',
    'iOS和Android测试通过'
  ],
  
  // 安全性
  security: [
    '敏感信息已加密',
    '用户数据保护完善',
    '网络传输安全'
  ]
}

2. 真机测试

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

(二)版本管理

1. 版本号规范

// 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. 提交代码

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

(三)上传代码

1. 开发版本上传

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

2. 体验版本设置

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

(四)提交审核

1. 审核前准备

// 审核准备清单
const auditPreparation = {
  // 基本信息
  basicInfo: [
    '小程序名称符合规范',
    '小程序简介准确描述功能',
    '小程序头像清晰合规',
    '服务类目选择正确'
  ],
  
  // 功能完整性
  functionality: [
    '核心功能完整可用',
    '页面跳转正常',
    '数据加载正常',
    '异常处理完善'
  ],
  
  // 用户体验
  userExperience: [
    '界面美观易用',
    '操作流程顺畅',
    '加载速度合理',
    '错误提示友好'
  ],
  
  // 合规性
  compliance: [
    '内容健康合规',
    '无违规功能',
    '隐私政策完善',
    '用户协议清晰'
  ]
}

2. 提交审核流程

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

3. 审核注意事项

## 常见审核问题及解决方案

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

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

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

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

(五)发布上线

1. 审核通过后发布

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

2. 版本管理

// 版本发布记录
const versionHistory = {
  'v1.0.0': {
    releaseDate: '2024-01-15',
    features: [
      '基础的待办事项管理功能',
      '任务的增删改查',
      '任务状态筛选',
      '数据本地存储'
    ],
    bugFixes: [],
    notes: '初始版本发布'
  },
  
  'v1.1.0': {
    releaseDate: '2024-02-01',
    features: [
      '添加任务提醒功能',
      '支持任务分类',
      '优化界面设计'
    ],
    bugFixes: [
      '修复任务删除后数据不同步问题',
      '修复长文本显示异常'
    ],
    notes: '功能增强版本'
  }
}

七、性能优化与最佳实践

(一)性能优化策略

1. 代码优化

// 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. 图片优化

// 图片优化工具类
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. 网络请求优化

// 请求缓存管理
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. 使用模块化开发
// 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. 错误处理

// 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. 状态管理

// 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周:WXML和WXSS
- [ ] 学习WXML基础语法
- [ ] 掌握数据绑定
- [ ] 学习WXSS样式编写
- [ ] 了解rpx单位使用

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

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

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

## 第二阶段:核心功能开发

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

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

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

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

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

## 第三阶段:深入优化和扩展

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

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

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

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

(二)实践项目推荐

1. 入门项目

// 项目1:个人名片小程序
const businessCardProject = {
  description: '制作个人或企业名片展示',
  features: [
    '个人信息展示',
    '联系方式展示',
    '二维码生成',
    '一键保存联系人'
  ],
  techStack: ['基础组件', '数据绑定', 'API调用'],
  difficulty: '⭐',
  timeEstimate: '1-2周'
}

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

2. 进阶项目

// 项目3:在线商城小程序
const ecommerceProject = {
  description: '完整的电商购物平台',
  features: [
    '商品展示和搜索',
    '购物车管理',
    '订单处理',
    '用户中心',
    '支付集成'
  ],
  techStack: [
    '组件化开发',
    '状态管理',
    '支付API',
    '用户授权'
  ],
  difficulty: '⭐⭐⭐⭐',
  timeEstimate: '2-3个月'
}

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

(三)学习资源推荐

1. 官方资源

## 官方文档和工具

### 核心文档
- [微信小程序官方文档](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. 社区资源

## 开源项目和社区

### 优秀开源项目
- [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. 进阶学习

// 技术栈扩展建议
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. 发布上线:了解审核规范、版本管理、发布流程等运营知识

持续学习建议

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

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

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