引言:为什么是电商小程序?

电商是小程序最成熟、最广泛的应用场景之一。它涵盖了:

  • 复杂状态管理:购物车、用户信息、订单
  • 高性能要求:商品列表、图片加载
  • 支付等原生能力:调用支付 SDK
  • 多端适配:微信、支付宝、H5、App

通过一个电商项目,我们可以将前面 12 篇文章的知识融会贯通

本文将带你从零开始,手把手搭建一个功能完整、性能优良的电商小程序


一、项目初始化与架构设计

1.1 创建项目

使用 HBuilderX 创建新项目:

  • 项目类型:uni-app
  • 模板:默认模板
  • 项目名称:uni-ecommerce
1.2 目录结构

基于 第十篇《架构设计》,我们构建清晰的目录:

src/
├── api/                    # 接口
│   ├── modules/
│   │   ├── product.js      # 商品
│   │   ├── cart.js         # 购物车
│   │   ├── order.js        # 订单
│   │   └── user.js         # 用户
│   └── index.js
├── assets/
│   ├── images/
│   └── styles/
│       ├── variables.scss
│       └── mixins.scss
├── components/
│   ├── base/
│   │   ├── Button.vue
│   │   └── Input.vue
│   ├── business/
│   │   ├── ProductCard.vue
│   │   └── CartItem.vue
│   └── layout/
│       └── TabBar.vue
├── composables/
│   ├── useCart.js
│   └── useProduct.js
├── pages/
│   ├── index/              # 首页
│   │   └── index.vue
│   ├── category/           # 分类
│   │   └── index.vue
│   ├── cart/               # 购物车
│   │   └── index.vue
│   ├── product/            # 商品详情
│   │   └── detail.vue
│   └── user/               # 用户中心
│       └── index.vue
├── plugins/
│   └── pinia.js
├── stores/                 # 状态管理
│   ├── cart.js
│   ├── product.js
│   └── index.js
├── types/
│   ├── product.d.ts
│   └── cart.d.ts
├── utils/
│   ├── request.js
│   └── auth.js
├── App.vue
├── main.js
└── manifest.json
1.3 技术栈
  • 框架:uni-app + Vue 3 (Composition API)
  • 状态管理:Pinia
  • 路由:uni-app 原生路由
  • 样式:Sass + Flexbox
  • HTTP:封装 uni.request
  • TypeScript:可选,推荐

二、接口层与数据模拟

2.1 封装请求
// utils/request.js
const BASE_URL = 'https://api.example.com'

export const request = (options) => {
  uni.showLoading({ title: '加载中...' })
  return uni.request({
    url: BASE_URL + options.url,
    method: options.method || 'GET',
    data: options.data,
    header: {
      'Authorization': uni.getStorageSync('token') || ''
    }
  }).finally(() => {
    uni.hideLoading()
  })
}
2.2 定义 API
// api/modules/product.js
import { request } from '@/utils/request'

export default {
  // 获取商品列表
  getList: (params) => request({ url: '/products', method: 'GET', data: params }),
  // 获取商品详情
  getDetail: (id) => request({ url: `/products/${id}` }),
  // 搜索商品
  search: (keyword) => request({ url: '/products/search', data: { keyword } })
}
// api/index.js
import productApi from './modules/product'
import cartApi from './modules/cart'

export const $api = {
  product: productApi,
  cart: cartApi
}
2.3 数据模拟(开发阶段)
// mock/product.js
export const mockProducts = [
  { id: 1, name: 'iPhone 15', price: 5999, image: '/static/iphone.jpg', desc: '最新款苹果手机' },
  { id: 2, name: 'MacBook Pro', price: 12999, image: '/static/mac.jpg', desc: '专业笔记本电脑' }
]

main.js 中挂载 $api


三、首页与商品列表

3.1 使用虚拟列表优化长列表
<!-- pages/index/index.vue -->
<template>
  <view class="container">
    <!-- 搜索框 -->
    <view class="search-bar">
      <input v-model="keyword" placeholder="搜索商品" @confirm="onSearch" />
    </view>

    <!-- 商品列表(虚拟列表) -->
    <VirtualList 
      :items="products" 
      :item-height="120"
      :container-height="listHeight"
    >
      <template #default="{ item }">
        <ProductCard :product="item" @add-to-cart="onAddToCart" />
      </template>
    </VirtualList>
  </view>
</template>

<script setup>
import { ref, onMounted, computed } from 'vue'
import VirtualList from '@/components/business/VirtualList.vue'
import ProductCard from '@/components/business/ProductCard.vue'
import { $api } from '@/api'

const keyword = ref('')
const products = ref([])
const listHeight = ref(uni.getSystemInfoSync().windowHeight - 50) // 减去搜索框高度

const loadProducts = async () => {
  const res = await $api.product.getList({ keyword: keyword.value })
  products.value = res.data
}

onMounted(() => {
  loadProducts()
})

const onSearch = () => {
  loadProducts()
}

const onAddToCart = (product) => {
  // 添加到购物车逻辑
  console.log('Add to cart:', product)
}
</script>

四、商品详情页

4.1 实现详情页
<!-- pages/product/detail.vue -->
<template>
  <view class="detail-container">
    <!-- 轮播图 -->
    <swiper class="image-swiper" :indicator-dots="true">
      <swiper-item v-for="image in product.images" :key="image">
        <image :src="image" mode="aspectFill" />
      </swiper-item>
    </swiper>

    <!-- 商品信息 -->
    <view class="info">
      <text class="title">{{ product.name }}</text>
      <text class="price">¥{{ product.price }}</text>
      <text class="desc">{{ product.desc }}</text>
    </view>

    <!-- 购买按钮 -->
    <view class="footer">
      <button class="add-btn" @click="addToCart">加入购物车</button>
      <button class="buy-btn" @click="buyNow">立即购买</button>
    </view>
  </view>
</template>

<script setup>
import { ref, onLoad } from 'vue'
import { $api } from '@/api'

const product = ref({})

// 页面加载时获取商品 ID 并请求数据
onLoad(async (options) => {
  const { id } = options
  if (id) {
    const res = await $api.product.getDetail(id)
    product.value = res.data
  }
})

const addToCart = () => {
  // 调用组合式函数或 store
  console.log('Add to cart:', product.value)
}

const buyNow = () => {
  // 跳转到订单页
  uni.navigateTo({ url: '/pages/order/confirm' })
}
</script>

五、购物车功能

5.1 状态管理 (Pinia)
// stores/cart.js
import { defineStore } from 'pinia'

export const useCartStore = defineStore('cart', {
  state: () => ({
    items: []
  }),
  getters: {
    totalCount: (state) => state.items.reduce((sum, item) => sum + item.quantity, 0),
    totalPrice: (state) => state.items.reduce((sum, item) => sum + item.price * item.quantity, 0)
  },
  actions: {
    addItem(product) {
      const existing = this.items.find(item => item.id === product.id)
      if (existing) {
        existing.quantity += 1
      } else {
        this.items.push({ ...product, quantity: 1 })
      }
      // 持久化
      uni.setStorageSync('cart', this.items)
    },
    removeItem(productId) {
      this.items = this.items.filter(item => item.id !== productId)
      uni.setStorageSync('cart', this.items)
    },
    loadFromStorage() {
      const saved = uni.getStorageSync('cart')
      if (saved) {
        this.items = saved
      }
    }
  }
})
5.2 购物车页面
<!-- pages/cart/index.vue -->
<template>
  <view class="cart-container">
    <view v-if="cartItems.length === 0" class="empty">
      <text>购物车为空</text>
    </view>
    
    <view v-else>
      <CartItem 
        v-for="item in cartItems" 
        :key="item.id" 
        :item="item"
        @remove="removeItem"
      />
      
      <view class="checkout">
        <text>总计: ¥{{ totalPrice }}</text>
        <button @click="checkout">去结算</button>
      </view>
    </view>
  </view>
</template>

<script setup>
import { computed } from 'vue'
import { useCartStore } from '@/stores/cart'
import CartItem from '@/components/business/CartItem.vue'

const cartStore = useCartStore()

// 从 store 中获取数据
const cartItems = computed(() => cartStore.items)
const totalPrice = computed(() => cartStore.totalPrice)

// 页面加载时从本地存储恢复数据
onLoad(() => {
  cartStore.loadFromStorage()
})

const removeItem = (id) => {
  cartStore.removeItem(id)
}

const checkout = () => {
  uni.navigateTo({ url: '/pages/order/confirm' })
}
</script>

六、支付功能(原生插件)

6.1 集成支付插件

假设我们有一个名为 PaymentPlugin 的原生插件。

// utils/payment.js
export const pay = (order) => {
  return new Promise((resolve, reject) => {
    const plugin = uni.requireNativePlugin('PaymentPlugin')
    plugin.pay(order, (res) => {
      resolve(res)
    }, (err) => {
      reject(err)
    })
  })
}
6.2 订单确认页调用支付
// pages/order/confirm.vue
const handlePay = async () => {
  try {
    const result = await pay(order)
    if (result.success) {
      uni.showToast({ title: '支付成功' })
      uni.redirectTo({ url: '/pages/order/success' })
    }
  } catch (error) {
    uni.showToast({ title: '支付失败', icon: 'none' })
  }
}

七、性能与体验优化

7.1 图片懒加载
<image :src="product.image" mode="aspectFill" lazy-load />
7.2 骨架屏

在数据加载时显示骨架屏,提升用户体验。

7.3 防抖搜索
// 防抖处理搜索
const onSearch = debounce(() => {
  loadProducts()
}, 300)
7.4 代码分包

pages.json 中配置分包,减少首屏加载体积。

{
  "subPackages": [
    {
      "root": "pages/cart/",
      "pages": [
        "index"
      ]
    },
    {
      "root": "pages/user/",
      "pages": [
        "index"
      ]
    }
  ]
}

八、总结

我们成功搭建了一个功能完整的电商小程序:

项目架构:模块化、可维护
核心功能:商品列表、详情、购物车、订单、支付
性能优化:虚拟列表、懒加载、分包
技术整合:Pinia、TypeScript、原生插件

这个项目可以作为你后续开发的模板,根据业务需求进行扩展。


下一篇预告

《uni-app 安全与监控:构建稳定可靠的应用》

我们将探讨:

  • 前端安全:XSS、CSRF 防护
  • 数据安全:敏感信息加密存储
  • 错误监控:集成 Sentry 或自建监控系统
  • 性能监控:关键指标采集
  • 日志上报与分析

确保你的应用不仅功能强大,而且安全、稳定、可靠

敬请期待!

Logo

电商企业物流数字化转型必备!快递鸟 API 接口,72 小时快速完成物流系统集成。全流程实战1V1指导,营造开放的API技术生态圈。

更多推荐