Vibe Coding开发微信小程序实战案例

在AI编程时代,人人都能编程。我以"个人记账助手"小程序为例,详细演示整个开发过程。

项目概述

项目名称 :个人记账助手
功能描述 :用户可以记录日常收支,查看统计图表,管理账单分类
技术栈 :微信小程序原生开发 + 云开发
开发方式 :Vibe Coding + AI辅助

第一阶段:需求分析与架构设计

与AI对话进行需求梳理

开发者描述

复制代码
我想开发一个微信小程序,功能是个人记账。用户可以:
1. 添加收入和支出记录
2. 选择消费分类(餐饮、交通、购物等)
3. 查看月度和年度统计
4. 支持图表展示
5. 数据云端存储

请帮我设计整体架构和页面结构。

AI助手回应

复制代码
基于您的需求,我为您设计以下架构:

页面结构:
├── pages/
│   ├── index/           # 首页(记录列表)
│   ├── add/            # 添加记录页面
│   ├── statistics/     # 统计页面
│   └── profile/        # 个人中心

功能模块:
1. 数据管理模块(CRUD操作)
2. 分类管理模块
3. 统计计算模块
4. 图表展示模块
5. 云存储模块

技术选型建议:
- 使用微信云开发作为后端
- ECharts小程序版本用于图表
- WeUI组件库优化界面

项目初始化

需求描述

复制代码
帮我生成微信小程序的基础配置文件,包括app.json、app.js、app.wxss,
设置好页面路由和基础样式。

AI生成的配置文件

app.json

json 复制代码
{
  "pages": [
    "pages/index/index",
    "pages/add/add",
    "pages/statistics/statistics",
    "pages/profile/profile"
  ],
  "window": {
    "backgroundTextStyle": "light",
    "navigationBarBackgroundColor": "#1AAD19",
    "navigationBarTitleText": "记账助手",
    "navigationBarTextStyle": "white",
    "backgroundColor": "#f8f8f8"
  },
  "tabBar": {
    "color": "#666",
    "selectedColor": "#1AAD19",
    "backgroundColor": "#fff",
    "borderStyle": "white",
    "list": [
      {
        "pagePath": "pages/index/index",
        "text": "记录",
        "iconPath": "images/record.png",
        "selectedIconPath": "images/record-active.png"
      },
      {
        "pagePath": "pages/statistics/statistics",
        "text": "统计",
        "iconPath": "images/chart.png",
        "selectedIconPath": "images/chart-active.png"
      },
      {
        "pagePath": "pages/profile/profile",
        "text": "我的",
        "iconPath": "images/profile.png",
        "selectedIconPath": "images/profile-active.png"
      }
    ]
  },
  "cloud": true
}

第二阶段:核心功能开发

首页记录列表

需求描述

复制代码
创建首页,显示最近的收支记录列表,包括:
- 顶部显示当月收支总额
- 记录列表显示日期、分类、金额、备注
- 支持下拉刷新和上拉加载更多
- 右下角添加按钮跳转到添加页面

AI生成的首页代码

pages/index/index.wxml

xml 复制代码
<view class="container">
  <!-- 月度统计卡片 -->
  <view class="summary-card">
    <view class="summary-item">
      <text class="summary-label">本月收入</text>
      <text class="summary-amount income">+{{monthIncome}}</text>
    </view>
    <view class="summary-item">
      <text class="summary-label">本月支出</text>
      <text class="summary-amount expense">-{{monthExpense}}</text>
    </view>
    <view class="summary-item">
      <text class="summary-label">结余</text>
      <text class="summary-amount {{monthBalance >= 0 ? 'income' : 'expense'}}">
        {{monthBalance >= 0 ? '+' : ''}}{{monthBalance}}
      </text>
    </view>
  </view>

  <!-- 记录列表 -->
  <scroll-view class="record-list" 
               scroll-y="true" 
               bindscrolltolower="loadMore"
               refresher-enabled="true"
               bindrefresherrefresh="onRefresh">
    <view wx:for="{{records}}" wx:key="id" class="record-item">
      <view class="record-left">
        <image class="category-icon" src="{{item.categoryIcon}}"></image>
        <view class="record-info">
          <text class="record-category">{{item.categoryName}}</text>
          <text class="record-note">{{item.note || '无备注'}}</text>
        </view>
      </view>
      <view class="record-right">
        <text class="record-amount {{item.type === 'income' ? 'income' : 'expense'}}">
          {{item.type === 'income' ? '+' : '-'}}{{item.amount}}
        </text>
        <text class="record-date">{{item.date}}</text>
      </view>
    </view>
  </scroll-view>

  <!-- 添加按钮 -->
  <view class="add-btn" bindtap="goToAdd">
    <image src="/images/add.png"></image>
  </view>
</view>

pages/index/index.js

javascript 复制代码
Page({
  data: {
    records: [],
    monthIncome: 0,
    monthExpense: 0,
    monthBalance: 0,
    page: 1,
    hasMore: true
  },

  onLoad() {
    this.initCloud();
    this.loadRecords();
    this.calculateMonthSummary();
  },

  // 初始化云开发
  initCloud() {
    if (!wx.cloud) {
      console.error('请使用 2.2.3 或以上的基础库以使用云能力');
    } else {
      wx.cloud.init({
        traceUser: true,
      });
    }
  },

  // 加载记录列表
  async loadRecords() {
    wx.showLoading({ title: '加载中...' });
    
    try {
      const db = wx.cloud.database();
      const result = await db.collection('records')
        .orderBy('createTime', 'desc')
        .limit(20)
        .skip((this.data.page - 1) * 20)
        .get();

      const newRecords = result.data.map(item => ({
        ...item,
        date: this.formatDate(item.createTime)
      }));

      this.setData({
        records: this.data.page === 1 ? newRecords : [...this.data.records, ...newRecords],
        hasMore: result.data.length === 20
      });
    } catch (error) {
      wx.showToast({ title: '加载失败', icon: 'error' });
    }
    
    wx.hideLoading();
  },

  // 计算月度统计
  async calculateMonthSummary() {
    const now = new Date();
    const startOfMonth = new Date(now.getFullYear(), now.getMonth(), 1);
    const endOfMonth = new Date(now.getFullYear(), now.getMonth() + 1, 0);

    try {
      const db = wx.cloud.database();
      const result = await db.collection('records')
        .where({
          createTime: db.command.gte(startOfMonth).and(db.command.lte(endOfMonth))
        })
        .get();

      let income = 0, expense = 0;
      result.data.forEach(record => {
        if (record.type === 'income') {
          income += record.amount;
        } else {
          expense += record.amount;
        }
      });

      this.setData({
        monthIncome: income.toFixed(2),
        monthExpense: expense.toFixed(2),
        monthBalance: (income - expense).toFixed(2)
      });
    } catch (error) {
      console.error('统计计算失败', error);
    }
  },

  // 格式化日期
  formatDate(date) {
    const d = new Date(date);
    return `${d.getMonth() + 1}-${d.getDate()}`;
  },

  // 跳转到添加页面
  goToAdd() {
    wx.navigateTo({
      url: '/pages/add/add'
    });
  },

  // 下拉刷新
  onRefresh() {
    this.setData({ page: 1 });
    this.loadRecords();
    this.calculateMonthSummary();
  },

  // 上拉加载更多
  loadMore() {
    if (this.data.hasMore) {
      this.setData({ page: this.data.page + 1 });
      this.loadRecords();
    }
  }
});

添加记录页面

需求描述

复制代码
创建添加记录页面,包括:
- 收入/支出切换选项
- 金额输入(数字键盘)
- 分类选择(网格布局)
- 日期选择器
- 备注输入
- 提交按钮

AI生成的添加页面代码

pages/add/add.wxml

xml 复制代码
<view class="container">
  <!-- 类型切换 -->
  <view class="type-switch">
    <view class="switch-item {{recordType === 'expense' ? 'active' : ''}}" 
          bindtap="switchType" data-type="expense">
      支出
    </view>
    <view class="switch-item {{recordType === 'income' ? 'active' : ''}}" 
          bindtap="switchType" data-type="income">
      收入
    </view>
  </view>

  <!-- 金额输入 -->
  <view class="amount-section">
    <text class="currency">¥</text>
    <input class="amount-input" 
           type="digit" 
           placeholder="0.00" 
           value="{{amount}}"
           bindinput="onAmountInput" />
  </view>

  <!-- 分类选择 -->
  <view class="category-section">
    <text class="section-title">选择分类</text>
    <view class="category-grid">
      <view wx:for="{{categories[recordType]}}" 
            wx:key="id" 
            class="category-item {{selectedCategory.id === item.id ? 'selected' : ''}}"
            bindtap="selectCategory" 
            data-category="{{item}}">
        <image class="category-icon" src="{{item.icon}}"></image>
        <text class="category-name">{{item.name}}</text>
      </view>
    </view>
  </view>

  <!-- 日期选择 -->
  <view class="date-section">
    <text class="section-title">日期</text>
    <picker mode="date" 
            value="{{date}}" 
            bindchange="onDateChange">
      <view class="date-picker">
        <text>{{date}}</text>
        <image class="arrow-icon" src="/images/arrow-right.png"></image>
      </view>
    </picker>
  </view>

  <!-- 备注输入 -->
  <view class="note-section">
    <text class="section-title">备注</text>
    <textarea class="note-input" 
              placeholder="添加备注信息(可选)"
              value="{{note}}"
              bindinput="onNoteInput"
              maxlength="100">
    </textarea>
  </view>

  <!-- 提交按钮 -->
  <button class="submit-btn" bindtap="submitRecord">保存记录</button>
</view>

pages/add/add.js

javascript 复制代码
Page({
  data: {
    recordType: 'expense',
    amount: '',
    selectedCategory: {},
    date: '',
    note: '',
    categories: {
      expense: [
        { id: 1, name: '餐饮', icon: '/images/food.png' },
        { id: 2, name: '交通', icon: '/images/transport.png' },
        { id: 3, name: '购物', icon: '/images/shopping.png' },
        { id: 4, name: '娱乐', icon: '/images/entertainment.png' },
        { id: 5, name: '医疗', icon: '/images/medical.png' },
        { id: 6, name: '教育', icon: '/images/education.png' }
      ],
      income: [
        { id: 7, name: '工资', icon: '/images/salary.png' },
        { id: 8, name: '奖金', icon: '/images/bonus.png' },
        { id: 9, name: '投资', icon: '/images/investment.png' },
        { id: 10, name: '其他', icon: '/images/other.png' }
      ]
    }
  },

  onLoad() {
    // 设置默认日期为今天
    const today = new Date();
    const dateStr = `${today.getFullYear()}-${String(today.getMonth() + 1).padStart(2, '0')}-${String(today.getDate()).padStart(2, '0')}`;
    this.setData({ date: dateStr });
  },

  // 切换收支类型
  switchType(e) {
    const type = e.currentTarget.dataset.type;
    this.setData({
      recordType: type,
      selectedCategory: {} // 重置分类选择
    });
  },

  // 金额输入
  onAmountInput(e) {
    let value = e.detail.value;
    // 限制小数点后两位
    if (value.includes('.')) {
      const parts = value.split('.');
      if (parts[1] && parts[1].length > 2) {
        value = parts[0] + '.' + parts[1].substring(0, 2);
      }
    }
    this.setData({ amount: value });
  },

  // 选择分类
  selectCategory(e) {
    const category = e.currentTarget.dataset.category;
    this.setData({ selectedCategory: category });
  },

  // 日期选择
  onDateChange(e) {
    this.setData({ date: e.detail.value });
  },

  // 备注输入
  onNoteInput(e) {
    this.setData({ note: e.detail.value });
  },

  // 提交记录
  async submitRecord() {
    // 数据验证
    if (!this.data.amount || parseFloat(this.data.amount) <= 0) {
      wx.showToast({ title: '请输入有效金额', icon: 'error' });
      return;
    }

    if (!this.data.selectedCategory.id) {
      wx.showToast({ title: '请选择分类', icon: 'error' });
      return;
    }

    wx.showLoading({ title: '保存中...' });

    try {
      const db = wx.cloud.database();
      await db.collection('records').add({
        data: {
          type: this.data.recordType,
          amount: parseFloat(this.data.amount),
          categoryId: this.data.selectedCategory.id,
          categoryName: this.data.selectedCategory.name,
          categoryIcon: this.data.selectedCategory.icon,
          date: this.data.date,
          note: this.data.note,
          createTime: new Date()
        }
      });

      wx.showToast({ title: '保存成功', icon: 'success' });
      
      // 返回首页并刷新
      setTimeout(() => {
        wx.navigateBack();
      }, 1500);

    } catch (error) {
      wx.showToast({ title: '保存失败', icon: 'error' });
      console.error('保存记录失败', error);
    }

    wx.hideLoading();
  }
});

第三阶段:数据可视化

统计页面开发

需求描述

复制代码
创建统计页面,包括:
- 月度/年度切换
- 收支趋势图表(折线图)
- 支出分类占比(饼图)
- 详细数据列表
使用ECharts实现图表功能

AI生成统计页面代码

pages/statistics/statistics.js

javascript 复制代码
import * as echarts from '../../libs/ec-canvas/echarts';

Page({
  data: {
    ec: {
      onInit: null
    },
    pieChart: {
      onInit: null
    },
    periodType: 'month', // month/year
    currentPeriod: '',
    totalIncome: 0,
    totalExpense: 0,
    categoryStats: []
  },

  onLoad() {
    this.initCharts();
    this.loadStatistics();
  },

  // 初始化图表
  initCharts() {
    this.setData({
      'ec.onInit': this.initTrendChart,
      'pieChart.onInit': this.initPieChart
    });
  },

  // 初始化趋势图
  initTrendChart(canvas, width, height, dpr) {
    const chart = echarts.init(canvas, null, {
      width: width,
      height: height,
      devicePixelRatio: dpr
    });
    
    const option = {
      title: {
        text: '收支趋势',
        left: 'center',
        textStyle: { fontSize: 16 }
      },
      legend: {
        data: ['收入', '支出'],
        bottom: 10
      },
      grid: {
        left: 40,
        right: 40,
        top: 60,
        bottom: 60
      },
      xAxis: {
        type: 'category',
        data: []
      },
      yAxis: {
        type: 'value'
      },
      series: [{
        name: '收入',
        type: 'line',
        data: [],
        itemStyle: { color: '#1AAD19' }
      }, {
        name: '支出',
        type: 'line',
        data: [],
        itemStyle: { color: '#E64340' }
      }]
    };

    chart.setOption(option);
    this.trendChart = chart;
    return chart;
  },

  // 初始化饼图
  initPieChart(canvas, width, height, dpr) {
    const chart = echarts.init(canvas, null, {
      width: width,
      height: height,
      devicePixelRatio: dpr
    });

    const option = {
      title: {
        text: '支出分类',
        left: 'center',
        textStyle: { fontSize: 16 }
      },
      series: [{
        type: 'pie',
        radius: ['30%', '70%'],
        center: ['50%', '60%'],
        data: [],
        label: {
          formatter: '{b}: {d}%'
        }
      }]
    };

    chart.setOption(option);
    this.pieChart = chart;
    return chart;
  },

  // 加载统计数据
  async loadStatistics() {
    wx.showLoading({ title: '加载中...' });

    try {
      const db = wx.cloud.database();
      const now = new Date();
      let startDate, endDate;

      if (this.data.periodType === 'month') {
        startDate = new Date(now.getFullYear(), now.getMonth(), 1);
        endDate = new Date(now.getFullYear(), now.getMonth() + 1, 0);
        this.setData({ currentPeriod: `${now.getFullYear()}年${now.getMonth() + 1}月` });
      } else {
        startDate = new Date(now.getFullYear(), 0, 1);
        endDate = new Date(now.getFullYear(), 11, 31);
        this.setData({ currentPeriod: `${now.getFullYear()}年` });
      }

      const result = await db.collection('records')
        .where({
          createTime: db.command.gte(startDate).and(db.command.lte(endDate))
        })
        .get();

      this.processStatistics(result.data);
    } catch (error) {
      wx.showToast({ title: '加载失败', icon: 'error' });
    }

    wx.hideLoading();
  },

  // 处理统计数据
  processStatistics(records) {
    let totalIncome = 0;
    let totalExpense = 0;
    const categoryMap = new Map();
    const dailyStats = new Map();

    records.forEach(record => {
      if (record.type === 'income') {
        totalIncome += record.amount;
      } else {
        totalExpense += record.amount;
        
        // 分类统计
        const categoryName = record.categoryName;
        if (categoryMap.has(categoryName)) {
          categoryMap.set(categoryName, categoryMap.get(categoryName) + record.amount);
        } else {
          categoryMap.set(categoryName, record.amount);
        }
      }

      // 日期统计
      const dateKey = record.date;
      if (!dailyStats.has(dateKey)) {
        dailyStats.set(dateKey, { income: 0, expense: 0 });
      }
      
      if (record.type === 'income') {
        dailyStats.get(dateKey).income += record.amount;
      } else {
        dailyStats.get(dateKey).expense += record.amount;
      }
    });

    this.setData({
      totalIncome: totalIncome.toFixed(2),
      totalExpense: totalExpense.toFixed(2)
    });

    this.updateCharts(dailyStats, categoryMap);
  },

  // 更新图表
  updateCharts(dailyStats, categoryMap) {
    // 更新趋势图
    const dates = Array.from(dailyStats.keys()).sort();
    const incomeData = dates.map(date => dailyStats.get(date).income);
    const expenseData = dates.map(date => dailyStats.get(date).expense);

    if (this.trendChart) {
      this.trendChart.setOption({
        xAxis: { data: dates },
        series: [{
          data: incomeData
        }, {
          data: expenseData
        }]
      });
    }

    // 更新饼图
    const pieData = Array.from(categoryMap.entries()).map(([name, value]) => ({
      name,
      value: value.toFixed(2)
    }));

    if (this.pieChart) {
      this.pieChart.setOption({
        series: [{
          data: pieData
        }]
      });
    }
  }
});

第四阶段:优化与部署

性能优化

需求描述

复制代码
对小程序进行性能优化,包括:
- 图片懒加载
- 数据缓存机制
- 分页加载优化
- 代码分包

AI提供的优化方案

javascript 复制代码
// 数据缓存管理
class CacheManager {
  static cache = new Map();
  
  static set(key, data, expireTime = 5 * 60 * 1000) {
    this.cache.set(key, {
      data,
      expire: Date.now() + expireTime
    });
  }
  
  static get(key) {
    const item = this.cache.get(key);
    if (!item) return null;
    
    if (Date.now() > item.expire) {
      this.cache.delete(key);
      return null;
    }
    
    return item.data;
  }
}

// 图片懒加载组件
Component({
  properties: {
    src: String,
    defaultSrc: String
  },
  
  data: {
    loaded: false,
    currentSrc: ''
  },
  
  lifetimes: {
    attached() {
      this.setData({
        currentSrc: this.data.defaultSrc || '/images/placeholder.png'
      });
      
      this.createIntersectionObserver()
        .relativeToViewport()
        .observe('.lazy-image', (res) => {
          if (res.intersectionRatio > 0 && !this.data.loaded) {
            this.loadImage();
          }
        });
    }
  },
  
  methods: {
    loadImage() {
      wx.previewImage({
        urls: [this.data.src],
        success: () => {
          this.setData({
            currentSrc: this.data.src,
            loaded: true
          });
        }
      });
    }
  }
});

云开发配置

需求描述

复制代码
配置微信云开发环境,包括:
- 数据库集合设计
- 云函数部署
- 安全规则设置

AI生成的云开发配置

javascript 复制代码
// 云函数:统计数据聚合
exports.main = async (event, context) => {
  const wxContext = cloud.getWXContext();
  const db = cloud.database();
  
  try {
    const { type, startDate, endDate } = event;
    
    // 使用聚合查询提高性能
    const result = await db.collection('records')
      .where({
        _openid: wxContext.OPENID,
        createTime: db.command.gte(new Date(startDate))
          .and(db.command.lte(new Date(endDate)))
      })
      .aggregate()
      .group({
        _id: '$categoryName',
        totalAmount: db.command.sum('$amount'),
        count: db.command.sum(1)
      })
      .end();
    
    return {
      success: true,
      data: result.list
    };
  } catch (error) {
    return {
      success: false,
      error: error.message
    };
  }
};

总结与展望

通过这个完整的Vibe Coding开发案例,我们可以看到AI辅助编程在微信小程序开发中的强大威力:

开发效率提升

  • 需求到代码:直接描述需求即可生成代码框架
  • 快速迭代:修改需求后快速调整实现
  • 最佳实践:AI自动应用行业最佳实践

学习成本降低

  • 语法学习:无需深入学习复杂语法
  • 框架理解:通过实例快速理解框架特性
  • 问题解决:AI提供调试和优化建议

代码质量保证

  • 结构规范:生成的代码结构清晰规范
  • 性能优化:自动应用性能优化技巧
  • 安全考虑:内置安全最佳实践

这种开发方式特别适合:

  • 快速原型验证
  • 个人项目开发
  • 学习新技术栈
  • 提升开发效率

随着AI技术的不断发展,Vibe Coding将成为软件开发的重要趋势,让更多人能够参与到软件创造中来。

相关推荐
CC同学呀6 小时前
从0到100:单位订餐统计小程序开发日记2025
小程序
666HZ66611 小时前
微信小程序中scss、ts、wxml
微信小程序·小程序·scss
二十十十十十12 小时前
微信点餐小程序—美食物
微信·小程序
向明天乄12 小时前
在小程序中实现实时聊天:WebSocket最佳实践
websocket·网络协议·小程序
h1853859224412 小时前
租车小程序电动车租赁小程序php方案
小程序
海的诗篇_12 小时前
前端开发面试题总结-原生小程序部分
前端·javascript·面试·小程序·vue·html
说私域12 小时前
基于开源AI智能客服、AI智能名片与S2B2C商城小程序的微商服务质量提升路径研究
人工智能·小程序·开源
精灵vector13 小时前
构建专家级SQL Agent交互
python·aigc·ai编程
牧杉-惊蛰13 小时前
uniapp微信小程序css中background-image失效问题
css·微信小程序·uni-app
莫大h14 小时前
Gemini-cli安装避坑指南
aigc·ai编程