第十三篇:uni-app 实战:从零搭建一个电商小程序
本文介绍了从零开始搭建一个功能完整的电商小程序的完整流程。项目采用uni-app+Vue3框架,使用Pinia进行状态管理,包含商品列表、详情页、购物车、支付等核心功能模块。文章详细讲解了项目初始化、架构设计、接口封装、状态管理、性能优化等关键环节,并提供了虚拟列表、骨架屏、图片懒加载等优化方案。通过这个项目,开发者可以学习如何构建一个模块化、可维护的电商小程序,并掌握uni-app开发的核心技术
·
引言:为什么是电商小程序?
电商是小程序最成熟、最广泛的应用场景之一。它涵盖了:
- 复杂状态管理:购物车、用户信息、订单
- 高性能要求:商品列表、图片加载
- 支付等原生能力:调用支付 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 或自建监控系统
- 性能监控:关键指标采集
- 日志上报与分析
确保你的应用不仅功能强大,而且安全、稳定、可靠。
敬请期待!
更多推荐

所有评论(0)