微信小程序高级教程 — 实战进阶

前置要求:小程序基础、Vue.js基础
学习时长:约3-5天
适用场景:电商小程序、工具小程序、内容小程序

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

1.1 项目结构

todo-miniapp/
├── pages/
│   ├── index/              # 首页(待办列表)
│   ├── add/                # 添加待办
│   └── detail/             # 待办详情
├── components/
│   ├── todo-item/          # 待办项组件
│   └── empty/              # 空状态组件
├── utils/
│   ├── request.js          # 请求封装
│   ├── storage.js          # 本地存储
│   └── util.js             # 工具函数
├── store/
│   └── index.js            # 状态管理
├── app.js
├── app.json
└── app.wxss

1.2 页面实现

<!-- pages/index/index.wxml -->
<view class="container">
  <view class="header">
    <text class="title">我的待办</text>
    <text class="count">{{todos.length}} 项</text>
  </view>
  
  <!-- 添加输入框 -->
  <view class="input-box">
    <input 
      placeholder="添加新待办..." 
      value="{{inputValue}}"
      bindinput="onInput"
      bindconfirm="addTodo"
    />
    <button bindtap="addTodo" size="mini">添加</button>
  </view>
  
  <!-- 待办列表 -->
  <view class="todo-list" wx:if="{{todos.length > 0}}">
    <view 
      wx:for="{{todos}}" 
      wx:key="id"
      class="todo-item {{item.completed ? 'completed' : ''}}"
    >
      <checkbox 
        checked="{{item.completed}}" 
        bindchange="toggleTodo"
        data-id="{{item.id}}"
      />
      <text 
        class="todo-text"
        bindtap="goDetail"
        data-id="{{item.id}}"
      >{{item.text}}</text>
      <text 
        class="delete-btn"
        bindtap="deleteTodo"
        data-id="{{item.id}}"
      >×</text>
    </view>
  </view>
  
  <!-- 空状态 -->
  <view class="empty" wx:else>
    <text>暂无待办事项</text>
  </view>
  
  <!-- 底部统计 -->
  <view class="footer" wx:if="{{todos.length > 0}}">
    <text>已完成 {{completedCount}} / {{todos.length}}</text>
    <text bindtap="clearCompleted" class="clear-btn">清除已完成</text>
  </view>
</view>

// pages/index/index.js
Page({
  data: {
    todos: [],
    inputValue: '',
    completedCount: 0
  },
  
  onLoad() {
    this.loadTodos()
  },
  
  onShow() {
    this.loadTodos()
  },
  
  // 加载待办
  loadTodos() {
    const todos = wx.getStorageSync('todos') || []
    const completedCount = todos.filter(t => t.completed).length
    this.setData({ todos, completedCount })
  },
  
  // 保存待办
  saveTodos() {
    wx.setStorageSync('todos', this.data.todos)
  },
  
  // 输入事件
  onInput(e) {
    this.setData({ inputValue: e.detail.value })
  },
  
  // 添加待办
  addTodo() {
    const text = this.data.inputValue.trim()
    if (!text) {
      wx.showToast({ title: '请输入内容', icon: 'error' })
      return
    }
    
    const todo = {
      id: Date.now(),
      text,
      completed: false,
      createdAt: new Date().toISOString()
    }
    
    const todos = [todo, ...this.data.todos]
    this.setData({
      todos,
      inputValue: '',
      completedCount: todos.filter(t => t.completed).length
    })
    this.saveTodos()
    
    wx.showToast({ title: '添加成功', icon: 'success' })
  },
  
  // 切换完成状态
  toggleTodo(e) {
    const id = e.currentTarget.dataset.id
    const todos = this.data.todos.map(t => {
      if (t.id === id) {
        return { ...t, completed: !t.completed }
      }
      return t
    })
    
    this.setData({
      todos,
      completedCount: todos.filter(t => t.completed).length
    })
    this.saveTodos()
  },
  
  // 删除待办
  deleteTodo(e) {
    const id = e.currentTarget.dataset.id
    wx.showModal({
      title: '确认删除',
      content: '确定删除这个待办吗?',
      success: (res) => {
        if (res.confirm) {
          const todos = this.data.todos.filter(t => t.id !== id)
          this.setData({
            todos,
            completedCount: todos.filter(t => t.completed).length
          })
          this.saveTodos()
        }
      }
    })
  },
  
  // 跳转详情
  goDetail(e) {
    const id = e.currentTarget.dataset.id
    wx.navigateTo({ url: `/pages/detail/index?id=${id}` })
  },
  
  // 清除已完成
  clearCompleted() {
    const todos = this.data.todos.filter(t => !t.completed)
    this.setData({ todos, completedCount: 0 })
    this.saveTodos()
  }
})

/* pages/index/index.wxss */
.container {
  padding: 30rpx;
  background: #f5f5f5;
  min-height: 100vh;
}

.header {
  display: flex;
  justify-content: space-between;
  align-items: center;
  margin-bottom: 30rpx;
}

.title {
  font-size: 44rpx;
  font-weight: bold;
  color: #333;
}

.count {
  font-size: 28rpx;
  color: #999;
}

.input-box {
  display: flex;
  gap: 20rpx;
  margin-bottom: 30rpx;
}

.input-box input {
  flex: 1;
  background: #fff;
  padding: 20rpx 30rpx;
  border-radius: 10rpx;
  font-size: 28rpx;
}

.todo-list {
  background: #fff;
  border-radius: 10rpx;
  overflow: hidden;
}

.todo-item {
  display: flex;
  align-items: center;
  padding: 30rpx;
  border-bottom: 1rpx solid #f0f0f0;
}

.todo-item.completed .todo-text {
  text-decoration: line-through;
  color: #999;
}

.todo-text {
  flex: 1;
  margin: 0 20rpx;
  font-size: 30rpx;
  color: #333;
}

.delete-btn {
  font-size: 40rpx;
  color: #999;
  padding: 0 10rpx;
}

.empty {
  text-align: center;
  padding: 100rpx 0;
  color: #999;
}

.footer {
  display: flex;
  justify-content: space-between;
  margin-top: 30rpx;
  font-size: 24rpx;
  color: #999;
}

.clear-btn {
  color: #ff4d4f;
}


二、常用功能实现

2.1 网络请求封装

// utils/request.js
const BASE_URL = 'https://api.example.com'

const request = (options) => {
  return new Promise((resolve, reject) => {
    const token = wx.getStorageSync('token')
    
    wx.request({
      url: BASE_URL + options.url,
      method: options.method || 'GET',
      data: options.data || {},
      header: {
        'Content-Type': 'application/json',
        'Authorization': token ? `Bearer ${token}` : ''
      },
      success: (res) => {
        if (res.statusCode === 200) {
          resolve(res.data)
        } else if (res.statusCode === 401) {
          wx.navigateTo({ url: '/pages/login/index' })
          reject(new Error('未登录'))
        } else {
          wx.showToast({ title: res.data.message || '请求失败', icon: 'error' })
          reject(res.data)
        }
      },
      fail: (err) => {
        wx.showToast({ title: '网络错误', icon: 'error' })
        reject(err)
      }
    })
  })
}

// API方法
export const api = {
  // 用户
  login: (data) => request({ url: '/auth/login', method: 'POST', data }),
  getUserInfo: () => request({ url: '/user/info' }),
  
  // 待办
  getTodos: () => request({ url: '/todos' }),
  addTodo: (data) => request({ url: '/todos', method: 'POST', data }),
  updateTodo: (id, data) => request({ url: `/todos/${id}`, method: 'PUT', data }),
  deleteTodo: (id) => request({ url: `/todos/${id}`, method: 'DELETE' })
}

export default request

2.2 登录授权

// 微信登录
const login = async () => {
  try {
    // 1. 获取code
    const { code } = await wx.login()
    
    // 2. 发送code到后端
    const res = await api.login({ code })
    
    // 3. 保存token
    wx.setStorageSync('token', res.token)
    wx.setStorageSync('userInfo', res.user)
    
    // 4. 跳转首页
    wx.switchTab({ url: '/pages/index/index' })
  } catch (err) {
    console.error('登录失败:', err)
  }
}

// 获取用户信息(需要用户授权)
const getUserProfile = () => {
  wx.getUserProfile({
    desc: '用于完善用户资料',
    success: (res) => {
      const userInfo = res.userInfo
      // 发送userInfo到后端
    }
  })
}

2.3 分享功能

// 页面分享
Page({
  onShareAppMessage() {
    return {
      title: '我的待办事项',
      path: '/pages/index/index',
      imageUrl: '/images/share.png'
    }
  },
  
  onShareTimeline() {
    return {
      title: '我的待办事项',
      imageUrl: '/images/share.png'
    }
  }
})

2.4 图片上传

const chooseAndUpload = () => {
  wx.chooseImage({
    count: 1,
    sizeType: ['compressed'],
    sourceType: ['album', 'camera'],
    success: (res) => {
      const filePath = res.tempFilePaths[0]
      
      wx.uploadFile({
        url: 'https://api.example.com/upload',
        filePath,
        name: 'file',
        header: {
          'Authorization': `Bearer ${wx.getStorageSync('token')}`
        },
        success: (res) => {
          const data = JSON.parse(res.data)
          console.log('上传成功:', data.url)
        }
      })
    }
  })
}


三、自定义组件

3.1 待办项组件

// components/todo-item/index.json
{
  "component": true
}

<!-- components/todo-item/index.wxml -->
<view class="todo-item {{completed ? 'completed' : ''}}" bindtap="onTap">
  <checkbox checked="{{completed}}" bindchange="onToggle" />
  <text class="text">{{text}}</text>
  <text class="time">{{createTime}}</text>
</view>

// components/todo-item/index.js
Component({
  properties: {
    id: Number,
    text: String,
    completed: Boolean,
    createdAt: String
  },
  
  computed: {
    createTime() {
      // 格式化时间
      return this.properties.createdAt?.slice(0, 10)
    }
  },
  
  methods: {
    onTap() {
      this.triggerEvent('tap', { id: this.properties.id })
    },
    
    onToggle(e) {
      this.triggerEvent('toggle', { 
        id: this.properties.id,
        completed: !this.properties.completed
      })
    }
  }
})

/* components/todo-item/index.wxss */
.todo-item {
  display: flex;
  align-items: center;
  padding: 30rpx;
  background: #fff;
  border-bottom: 1rpx solid #f0f0f0;
}

.todo-item.completed .text {
  text-decoration: line-through;
  color: #999;
}

.text {
  flex: 1;
  margin: 0 20rpx;
  font-size: 30rpx;
}

.time {
  font-size: 24rpx;
  color: #999;
}


四、云开发(可选)

// 云函数示例
// cloudfunctions/getTodos/index.js
const cloud = require('wx-server-sdk')
cloud.init()
const db = cloud.database()

exports.main = async (event, context) => {
  const { OPENID } = cloud.getWXContext()
  
  const result = await db.collection('todos')
    .where({ openid: OPENID })
    .orderBy('createdAt', 'desc')
    .get()
  
  return {
    code: 200,
    data: result.data
  }
}

// 前端调用
const res = await wx.cloud.callFunction({
  name: 'getTodos'
})
console.log(res.result.data)


五、性能优化

✅ 使用分包加载
✅ 图片压缩和懒加载
✅ 长列表使用虚拟列表
✅ 减少setData的数据量
✅ 使用WXS处理视图层逻辑
✅ 避免频繁的setData调用
✅ 使用缓存减少请求
✅ 预加载下一页数据


学习建议

  1. 先做一个完整的小项目,如待办事项
  2. 掌握网络请求封装,这是前后端交互的基础
  3. 学习自定义组件,提高代码复用性
  4. 了解云开发,简化后端开发
  5. 关注性能优化,提升用户体验

下一步学习

返回首页