ITPub博客

首页 > 移动开发 > 微信开发 > 从零搭建商城小程序:完整源码解析 + 前后端交互实战

从零搭建商城小程序:完整源码解析 + 前后端交互实战

原创 微信开发 作者:lilinhua 时间:2026-01-13 14:21:44 0 删除 编辑

   项目概述与准备工作

  在移动互联网时代,小程序以其“无需下载、即用即走”的特性,成为电商领域的重要入口。今天,我将带大家从零开始搭建一个完整的商城小程序,深入解析前后端架构设计与交互实现。

  1.1技术栈选择

  • 源码及演示:xcxyms.top

  •前端:微信小程序原生开发(WXML、WXSS、JavaScript)

  •后端:Node.js+Express+MySQL

  •开发工具:微信开发者工具、Visual Studio Code

  1.2项目结构规划

miniprogram/
  ├── pages/           # 页面文件
  │   ├── index/       # 首页
  │   ├── category/    # 分类页
  │   ├── cart/        # 购物车
  │   └── mine/        # 个人中心
  ├── components/      # 自定义组件
  ├── utils/           # 工具函数
  ├── config/          # 配置文件
  └── app.js          # 小程序入口
backend/
  ├── routes/         # 路由文件
  ├── controllers/    # 控制器
  ├── models/         # 数据模型
  └── app.js         # 服务入口

   小程序前端架构实现

  1.应用初始化配置

  在app.js中进行全局配置:

// app.js
App({
  globalData: {
    userInfo: null,
    baseURL: 'https://your-domain.com/api'
  },
  
  onLaunch() {
    // 检查更新
    this.checkUpdate()
    
    // 获取用户信息
    this.getUserInfo()
  },
  
  // 封装网络请求
  request(options) {
    return new Promise((resolve, reject) => {
      wx.request({
        url: this.globalData.baseURL + options.url,
        data: options.data || {},
        header: {
          'content-type': 'application/json',
          'token': wx.getStorageSync('token')
        },
        method: options.method || 'GET',
        success: (res) => {
          if (res.data.code === 200) {
            resolve(res.data.data)
          } else {
            reject(res.data.message)
          }
        },
        fail: reject
      })
    })
  }
})

  2.首页商品展示实现

  首页是小程序的门面,需要展示轮播图、商品分类和推荐商品。

<!-- pages/index/index.wxml -->
<view class="container">
  <!-- 搜索框 -->
  <view class="search-bar">
    <input placeholder="搜索商品" bindinput="onSearchInput" />
  </view>
  
  <!-- 轮播图 -->
  <swiper indicator-dots autoplay interval="3000">
    <swiper-item wx:for="{{banners}}" wx:key="id">
      <image src="{{item.image}}" mode="aspectFill" />
    </swiper-item>
  </swiper>
  
  <!-- 商品分类 -->
  <view class="categories">
    <view 
      class="category-item" 
      wx:for="{{categories}}" 
      wx:key="id"
      bindtap="goCategory"
      data-id="{{item.id}}"
    >
      <image src="{{item.icon}}" />
      <text>{{item.name}}</text>
    </view>
  </view>
  
  <!-- 推荐商品 -->
  <view class="recommend">
    <view class="section-title">热门推荐</view>
    <view class="product-list">
      <view 
        class="product-item" 
        wx:for="{{products}}" 
        wx:key="id"
        bindtap="goProductDetail"
        data-id="{{item.id}}"
      >
        <image src="{{item.cover}}" />
        <view class="product-info">
          <view class="product-name">{{item.name}}</view>
          <view class="product-price">¥{{item.price}}</view>
        </view>
      </view>
    </view>
  </view>
</view>

  对应的JavaScript逻辑:

// pages/index/index.js
Page({
  data: {
    banners: [],
    categories: [],
    products: [],
    page: 1,
    loading: false
  },
  
  onLoad() {
    this.loadHomeData()
  },
  
  async loadHomeData() {
    try {
      const app = getApp()
      
      // 并行请求数据
      const [banners, categories, products] = await Promise.all([
        app.request({ url: '/banner/list' }),
        app.request({ url: '/category/list' }),
        app.request({ url: '/product/list', data: { page: 1, limit: 10 } })
      ])
      
      this.setData({
        banners,
        categories: categories.slice(0, 8), // 只显示前8个分类
        products
      })
    } catch (error) {
      wx.showToast({ title: '加载失败', icon: 'error' })
    }
  },
  
  // 上拉加载更多
  onReachBottom() {
    if (this.data.loading) return
    
    this.setData({ loading: true })
    this.loadMoreProducts()
  },
  
  async loadMoreProducts() {
    const app = getApp()
    const nextPage = this.data.page + 1
    
    try {
      const newProducts = await app.request({
        url: '/product/list',
        data: { page: nextPage, limit: 10 }
      })
      
      if (newProducts.length > 0) {
        this.setData({
          products: [...this.data.products, ...newProducts],
          page: nextPage
        })
      } else {
        wx.showToast({ title: '没有更多了', icon: 'none' })
      }
    } finally {
      this.setData({ loading: false })
    }
  }
})

   后端服务架构设计

       1.Express服务搭建

// backend/app.js
const express = require('express')
const cors = require('cors')
const mysql = require('mysql2/promise')
const app = express()
// 中间件
app.use(cors())
app.use(express.json())
// 数据库连接池
const pool = mysql.createPool({
  host: 'localhost',
  user: 'root',
  password: 'password',
  database: 'mall',
  waitForConnections: true,
  connectionLimit: 10,
  queueLimit: 0
})
// 路由挂载
app.use('/api/auth', require('./routes/auth'))
app.use('/api/product', require('./routes/product'))
app.use('/api/cart', require('./routes/cart'))
app.use('/api/order', require('./routes/order'))
// 错误处理中间件
app.use((err, req, res, next) => {
  console.error(err.stack)
  res.status(500).json({
    code: 500,
    message: '服务器内部错误',
    data: null
  })
})
const PORT = 3000
app.listen(PORT, () => {
  console.log(`服务器运行在 http://localhost:${PORT}`)
})

  2.商品管理接口实现

// backend/routes/product.js
const express = require('express')
const router = express.Router()
const productController = require('../controllers/productController')
// 商品列表
router.get('/list', async (req, res) => {
  try {
    const { page = 1, limit = 10, categoryId } = req.query
    const offset = (page - 1) * limit
    
    const [products, total] = await productController.getProducts({
      offset,
      limit: parseInt(limit),
      categoryId
    })
    
    res.json({
      code: 200,
      message: 'success',
      data: {
        list: products,
        total,
        page: parseInt(page),
        totalPages: Math.ceil(total / limit)
      }
    })
  } catch (error) {
    res.status(500).json({
      code: 500,
      message: error.message,
      data: null
    })
  }
})
// 商品详情
router.get('/:id', async (req, res) => {
  try {
    const product = await productController.getProductById(req.params.id)
    
    if (!product) {
      return res.status(404).json({
        code: 404,
        message: '商品不存在',
        data: null
      })
    }
    
    res.json({
      code: 200,
      message: 'success',
      data: product
    })
  } catch (error) {
    res.status(500).json({
      code: 500,
      message: error.message,
      data: null
    })
  }
})
module.exports = router

  对应的控制器和数据模型:

// backend/controllers/productController.js
const productModel = require('../models/productModel')
class ProductController {
  async getProducts({ offset, limit, categoryId }) {
    const where = {}
    if (categoryId) {
      where.category_id = categoryId
    }
    
    const products = await productModel.find(where, offset, limit)
    const total = await productModel.count(where)
    
    return [products, total]
  }
  
  async getProductById(id) {
    return await productModel.findById(id)
  }
}
module.exports = new ProductController()

   购物车功能完整实现

  购物车是电商核心功能,需要处理商品增删改查和状态同步。

// pages/cart/cart.js
Page({
  data: {
    cartItems: [],
    selectedAll: false,
    totalPrice: 0,
    totalCount: 0
  },
  
  onShow() {
    this.loadCartData()
  },
  
  async loadCartData() {
    try {
      const app = getApp()
      const cartItems = await app.request({ url: '/cart/list' })
      
      this.setData({ cartItems }, () => {
        this.calculateTotal()
      })
    } catch (error) {
      console.error('加载购物车失败:', error)
    }
  },
  
  // 选择商品
  selectItem(e) {
    const { id } = e.currentTarget.dataset
    const { cartItems } = this.data
    
    const index = cartItems.findIndex(item => item.id === id)
    if (index !== -1) {
      cartItems[index].selected = !cartItems[index].selected
      
      this.setData({ cartItems }, () => {
        this.calculateTotal()
        this.checkSelectAll()
      })
    }
  },
  
  // 修改商品数量
  async changeQuantity(e) {
    const { id, type } = e.currentTarget.dataset
    const { cartItems } = this.data
    const index = cartItems.findIndex(item => item.id === id)
    
    if (index === -1) return
    
    const newQuantity = type === 'add' 
      ? cartItems[index].quantity + 1
      : Math.max(1, cartItems[index].quantity - 1)
    
    try {
      const app = getApp()
      await app.request({
        url: '/cart/update',
        method: 'POST',
        data: { productId: id, quantity: newQuantity }
      })
      
      cartItems[index].quantity = newQuantity
      this.setData({ cartItems }, () => {
        this.calculateTotal()
      })
    } catch (error) {
      wx.showToast({ title: '更新失败', icon: 'error' })
    }
  },
  
  // 计算总价和数量
  calculateTotal() {
    const { cartItems } = this.data
    let totalPrice = 0
    let totalCount = 0
    
    cartItems.forEach(item => {
      if (item.selected) {
        totalPrice += item.price * item.quantity
        totalCount += item.quantity
      }
    })
    
    this.setData({
      totalPrice: totalPrice.toFixed(2),
      totalCount
    })
  },
  
  // 全选/取消全选
  toggleSelectAll() {
    const { cartItems, selectedAll } = this.data
    const newSelected = !selectedAll
    
    cartItems.forEach(item => {
      item.selected = newSelected
    })
    
    this.setData({
      cartItems,
      selectedAll: newSelected
    }, () => {
      this.calculateTotal()
    })
  },
  
  // 去结算
  async checkout() {
    const { cartItems, totalPrice } = this.data
    const selectedItems = cartItems.filter(item => item.selected)
    
    if (selectedItems.length === 0) {
      wx.showToast({ title: '请选择商品', icon: 'none' })
      return
    }
    
    // 创建订单
    try {
      const app = getApp()
      const order = await app.request({
        url: '/order/create',
        method: 'POST',
        data: {
          items: selectedItems,
          totalPrice
        }
      })
      
      // 跳转到订单确认页
      wx.navigateTo({
        url: `/pages/order/confirm?id=${order.id}`
      })
    } catch (error) {
      wx.showToast({ title: '创建订单失败', icon: 'error' })
    }
  }
})

   订单系统设计与实现

  订单状态机设计

// backend/models/orderModel.js
class OrderModel {
  // 订单状态枚举
  static STATUS = {
    PENDING: 0,      // 待支付
    PAID: 1,         // 已支付
    SHIPPED: 2,      // 已发货
    DELIVERED: 3,    // 已收货
    CANCELLED: 4,    // 已取消
    REFUNDED: 5      // 已退款
  }
  
  // 创建订单
  async createOrder(userId, orderData) {
    const connection = await pool.getConnection()
    
    try {
      await connection.beginTransaction()
      
      // 1. 创建订单主记录
      const [orderResult] = await connection.execute(
        `INSERT INTO orders (user_id, order_no, total_amount, status, address) 
         VALUES (?, ?, ?, ?, ?)`,
        [
          userId,
          this.generateOrderNo(),
          orderData.totalPrice,
          this.STATUS.PENDING,
          JSON.stringify(orderData.address)
        ]
      )
      
      const orderId = orderResult.insertId
      
      // 2. 创建订单商品明细
      for (const item of orderData.items) {
        await connection.execute(
          `INSERT INTO order_items 
           (order_id, product_id, product_name, price, quantity, total_price)
           VALUES (?, ?, ?, ?, ?, ?)`,
          [
            orderId,
            item.productId,
            item.productName,
            item.price,
            item.quantity,
            item.price * item.quantity
          ]
        )
        
        // 3. 扣减库存
        await connection.execute(
          'UPDATE products SET stock = stock - ? WHERE id = ?',
          [item.quantity, item.productId]
        )
      }
      
      // 4. 清空购物车中已结算商品
      await connection.execute(
        'DELETE FROM cart WHERE user_id = ? AND product_id IN (?)',
        [userId, orderData.items.map(item => item.productId)]
      )
      
      await connection.commit()
      
      return { orderId, orderNo: orderResult.order_no }
    } catch (error) {
      await connection.rollback()
      throw error
    } finally {
      connection.release()
    }
  }
  
  // 生成订单号
  generateOrderNo() {
    const date = new Date()
    const year = date.getFullYear()
    const month = (date.getMonth() + 1).toString().padStart(2, '0')
    const day = date.getDate().toString().padStart(2, '0')
    const random = Math.floor(Math.random() * 10000).toString().padStart(4, '0')
    return `${year}${month}${day}${random}`
  }
}

   用户认证与支付集成

       1.微信登录集成

// utils/auth.js
const login = () => {
  return new Promise((resolve, reject) => {
    wx.login({
      success: async (res) => {
        if (res.code) {
          try {
            const app = getApp()
            const result = await app.request({
              url: '/auth/wxlogin',
              method: 'POST',
              data: { code: res.code }
            })
            
            // 存储token
            wx.setStorageSync('token', result.token)
            wx.setStorageSync('userInfo', result.userInfo)
            
            resolve(result.userInfo)
          } catch (error) {
            reject(error)
          }
        } else {
          reject(new Error('登录失败'))
        }
      },
      fail: reject
    })
  })
}
// 检查登录状态
const checkLogin = () => {
  return new Promise((resolve) => {
    const token = wx.getStorageSync('token')
    
    if (!token) {
      // 跳转到登录页
      wx.navigateTo({ url: '/pages/auth/login' })
      resolve(false)
    } else {
      // 验证token有效性
      wx.checkSession({
        success: () => resolve(true),
        fail: () => {
          wx.removeStorageSync('token')
          wx.navigateTo({ url: '/pages/auth/login' })
          resolve(false)
        }
      })
    }
  })
}

  2.微信支付集成

// utils/auth.js
const login = () => {
  return new Promise((resolve, reject) => {
    wx.login({
      success: async (res) => {
        if (res.code) {
          try {
            const app = getApp()
            const result = await app.request({
              url: '/auth/wxlogin',
              method: 'POST',
              data: { code: res.code }
            })
            
            // 存储token
            wx.setStorageSync('token', result.token)
            wx.setStorageSync('userInfo', result.userInfo)
            
            resolve(result.userInfo)
          } catch (error) {
            reject(error)
          }
        } else {
          reject(new Error('登录失败'))
        }
      },
      fail: reject
    })
  })
}
// 检查登录状态
const checkLogin = () => {
  return new Promise((resolve) => {
    const token = wx.getStorageSync('token')
    
    if (!token) {
      // 跳转到登录页
      wx.navigateTo({ url: '/pages/auth/login' })
      resolve(false)
    } else {
      // 验证token有效性
      wx.checkSession({
        success: () => resolve(true),
        fail: () => {
          wx.removeStorageSync('token')
          wx.navigateTo({ url: '/pages/auth/login' })
          resolve(false)
        }
      })
    }
  })
}

   性能优化与最佳实践

      1.图片懒加载优化

// 在页面配置中启用图片懒加载
// pages/index/index.json
{
  "usingComponents": {},
  "enablePullDownRefresh": true,
  "onReachBottomDistance": 50,
  "lazyCodeLoading": "requiredComponents"
}
// 使用IntersectionObserver实现自定义懒加载
const initLazyLoad = () => {
  const observer = wx.createIntersectionObserver(this)
  
  observer.relativeToViewport({ bottom: 100 }).observe('.lazy-image', (res) => {
    if (res.intersectionRatio > 0) {
      // 加载图片
      this.loadImage(res.dataset.src, res.id)
    }
  })
}

  2.数据缓存策略

// utils/cache.js
class CacheManager {
  constructor() {
    this.prefix = 'mall_'
  }
  
  set(key, data, expire = 3600) {
    const cacheData = {
      data,
      expire: Date.now() + expire * 1000
    }
    
    try {
      wx.setStorageSync(this.prefix + key, cacheData)
    } catch (error) {
      console.error('缓存设置失败:', error)
    }
  }
  
  get(key) {
    try {
      const cacheData = wx.getStorageSync(this.prefix + key)
      
      if (!cacheData) return null
      
      if (Date.now() > cacheData.expire) {
        this.remove(key)
        return null
      }
      
      return cacheData.data
    } catch (error) {
      console.error('缓存获取失败:', error)
      return null
    }
  }
  
  // 首页数据缓存示例
  async getHomeData(forceRefresh = false) {
    const cacheKey = 'home_data'
    
    if (!forceRefresh) {
      const cached = this.get(cacheKey)
      if (cached) return cached
    }
    
    const app = getApp()
    const data = await app.request({ url: '/home/data' })
    
    // 缓存1小时
    this.set(cacheKey, data, 3600)
    
    return data
  }
}

   总结


  通过本文的详细拆解,我们完成了从零搭建商城小程序的完整探索。从基础框架搭建到前后端交互实现,从购物车逻辑到微信支付集成,每个模块都展示了实战开发的核心要点。这个过程中,我们不仅掌握了小程序开发的工具链和技术栈,更重要的是理解了电商系统的业务逻辑与数据流转。在快速迭代的互联网时代,掌握小程序开发技能已不仅仅是加分项,而是许多场景下的必备能力。希望本项目的完整实现,能为您提供可复用的开发模式和清晰的架构思路。

来自 “ ITPUB博客 ” ,链接:https://blog.itpub.net/70049276/viewspace-3107787/ ,如需转载,请注明出处,否则将追究法律责任。

上一篇: 没有了~
下一篇: 没有了~
请登录后发表评论 登录
全部评论

注册时间:2026-01-13

  • 博文量
    1
  • 访问量
    1332