🏆 Taro 从零到一(十):实战项目 — 从零搭建一个完整小程序
系列导读:系列完结篇!综合前 9 篇全部知识,搭建一个带有 首页、商品列表、购物车、我的的完整电商小程序。
📊 项目功能清单
| 功能 | 涉及知识点 |
|---|---|
| 🏠 首页 | 轮播图 / 分类宫格 / 商品推荐 + NutUI |
| 📜 商品列表 | ScrollView / 分页加载 / 搜索 |
| 📖 商品详情 | 路由参数 / 图片预览 / 分享 |
| 🛒 购物车 | Zustand 全局状态 / 数量增减 / 金额计算 |
| 👤 我的 | 登录 / 用户信息 / 设置列表 |
🏗 1. 项目结构
src/
├── app.ts # 应用入口
├── app.config.ts # 路由 + TabBar 配置
├── app.scss # 全局样式
├── api/ # API 接口层
│ ├── request.ts # 请求封装
│ ├── product.ts # 商品接口
│ └── auth.ts # 认证接口
├── store/ # 状态管理
│ ├── useAuthStore.ts # 登录态
│ └── useCartStore.ts # 购物车
├── hooks/ # 自定义 Hook
│ └── useRequest.ts # 请求 Hook
├── utils/ # 工具函数
│ ├── platform.ts # 平台判断
│ └── storage.ts # 存储封装
├── components/ # 公共组件
│ ├── ProductCard/
│ └── Empty/
├── pages/
│ ├── index/ # 🏠 首页
│ ├── list/ # 📜 商品列表
│ ├── detail/ # 📖 商品详情
│ ├── cart/ # 🛒 购物车
│ └── mine/ # 👤 我的
└── assets/ # 静态资源
├── tab/ # TabBar 图标
└── images/
⚙️ 2. 全局配置
// src/app.config.ts
export default defineAppConfig({
pages: [
'pages/index/index',
'pages/list/index',
'pages/cart/index',
'pages/mine/index',
'pages/detail/index',
'pages/login/index',
],
window: {
backgroundTextStyle: 'light',
navigationBarBackgroundColor: '#ffffff',
navigationBarTitleText: '好物商城',
navigationBarTextStyle: 'black',
},
tabBar: {
color: '#999',
selectedColor: '#6366F1',
backgroundColor: '#fff',
borderStyle: 'white',
list: [
{ pagePath: 'pages/index/index', text: '首页',
iconPath: 'assets/tab/home.png', selectedIconPath: 'assets/tab/home-active.png' },
{ pagePath: 'pages/list/index', text: '分类',
iconPath: 'assets/tab/category.png', selectedIconPath: 'assets/tab/category-active.png' },
{ pagePath: 'pages/cart/index', text: '购物车',
iconPath: 'assets/tab/cart.png', selectedIconPath: 'assets/tab/cart-active.png' },
{ pagePath: 'pages/mine/index', text: '我的',
iconPath: 'assets/tab/mine.png', selectedIconPath: 'assets/tab/mine-active.png' },
],
},
})
🏠 3. 首页
// src/pages/index/index.tsx
import { View, Text, Swiper, SwiperItem, Image } from '@tarojs/components'
import { useDidShow } from '@tarojs/taro'
import { Grid, GridItem, NoticeBar, SearchBar } from '@nutui/nutui-react-taro'
import ProductCard from '@/components/ProductCard'
import { useRequest } from '@/hooks/useRequest'
import { productApi } from '@/api/product'
import './index.scss'
export default function IndexPage() {
const { data: products, refresh } = useRequest(() =>
productApi.getList({ page: 1, pageSize: 10 })
)
// 每次回到首页刷新数据
useDidShow(() => { refresh() })
const banners = [
{ id: 1, image: 'https://picsum.photos/750/320?1' },
{ id: 2, image: 'https://picsum.photos/750/320?2' },
{ id: 3, image: 'https://picsum.photos/750/320?3' },
]
const categories = [
{ id: 1, name: '手机', icon: '📱' },
{ id: 2, name: '电脑', icon: '💻' },
{ id: 3, name: '耳机', icon: '🎧' },
{ id: 4, name: '手表', icon: '⌚' },
{ id: 5, name: '更多', icon: '📦' },
]
return (
<View className="index-page">
<SearchBar
placeholder="搜索你想要的商品"
shape="round"
disabled
onClickInput={() => Taro.navigateTo({ url: '/pages/list/index?focus=1' })}
/>
<Swiper className="banner" autoplay circular indicatorDots>
{banners.map(b => (
<SwiperItem key={b.id}>
<Image src={b.image} mode="aspectFill" className="banner-img" />
</SwiperItem>
))}
</Swiper>
<NoticeBar content="🎉 新用户专享:首单立减10元" scrollable />
<Grid columns={5} className="categories">
{categories.map(cat => (
<GridItem
key={cat.id}
text={cat.name}
onClick={() => Taro.switchTab({ url: '/pages/list/index' })}
>
<Text style={{ fontSize: '40px' }}>{cat.icon}</Text>
</GridItem>
))}
</Grid>
<View className="section">
<Text className="section-title">🔥 热门推荐</Text>
<View className="product-grid">
{products?.list.map(p => (
<ProductCard
key={p.id}
product={p}
onTap={() => Taro.navigateTo({ url: `/pages/detail/index?id=${p.id}` })}
/>
))}
</View>
</View>
</View>
)
}
🛒 4. 购物车页
// src/pages/cart/index.tsx
import { View, Text, Image } from '@tarojs/components'
import { Button, Empty, Checkbox, InputNumber, SwipeCell } from '@nutui/nutui-react-taro'
import useCartStore from '@/store/useCartStore'
import './index.scss'
export default function CartPage() {
const { items, updateQuantity, removeItem, totalPrice } = useCartStore()
if (items.length === 0) {
return (
<View className="cart-empty">
<Empty description="购物车空空如也">
<Button
type="primary"
size="small"
onClick={() => Taro.switchTab({ url: '/pages/list/index' })}
>
去逛逛
</Button>
</Empty>
</View>
)
}
return (
<View className="cart-page">
<View className="cart-list">
{items.map(item => (
<SwipeCell
key={item.id}
rightAction={
<Button type="danger" onClick={() => removeItem(item.id)}>删除</Button>
}
>
<View className="cart-item">
<Image src={item.image} className="item-img" mode="aspectFill" />
<View className="item-info">
<Text className="item-name">{item.name}</Text>
<View className="item-bottom">
<Text className="item-price">¥{item.price}</Text>
<InputNumber
min={1}
max={99}
value={item.quantity}
onChangeFuc={(v) => updateQuantity(item.id, v)}
/>
</View>
</View>
</View>
</SwipeCell>
))}
</View>
<View className="cart-footer">
<View className="total-info">
<Text>合计:</Text>
<Text className="total-price">¥{totalPrice().toFixed(2)}</Text>
</View>
<Button type="primary" className="checkout-btn">
去结算({items.reduce((s, i) => s + i.quantity, 0)})
</Button>
</View>
</View>
)
}
👤 5. 我的页面
// src/pages/mine/index.tsx
import { View, Text, Image } from '@tarojs/components'
import { Cell, Avatar, Tag, Button } from '@nutui/nutui-react-taro'
import useAuthStore from '@/store/useAuthStore'
import './index.scss'
export default function MinePage() {
const { user, isLoggedIn, logout } = useAuthStore()
return (
<View className="mine-page">
{/* 用户信息区 */}
<View className="user-header">
{isLoggedIn ? (
<View className="user-info">
<Avatar size="large" src={user?.avatar} />
<View className="user-detail">
<Text className="user-name">{user?.name}</Text>
<Tag type="primary" size="small">VIP 会员</Tag>
</View>
</View>
) : (
<View className="login-prompt" onClick={() => Taro.navigateTo({ url: '/pages/login/index' })}>
<Avatar size="large" />
<Text className="login-text">点击登录</Text>
</View>
)}
</View>
{/* 订单区 */}
<View className="order-section">
<Cell title="我的订单" extra="查看全部" />
<View className="order-grid">
{[
{ icon: '💳', label: '待付款', count: 2 },
{ icon: '📦', label: '待发货', count: 0 },
{ icon: '🚚', label: '待收货', count: 1 },
{ icon: '⭐', label: '待评价', count: 3 },
].map(item => (
<View key={item.label} className="order-item">
<Text className="order-icon">{item.icon}</Text>
<Text className="order-label">{item.label}</Text>
{item.count > 0 && <Text className="order-badge">{item.count}</Text>}
</View>
))}
</View>
</View>
{/* 功能列表 */}
<View className="func-list">
<Cell title="收货地址" onClick={() => {}} />
<Cell title="优惠券" extra="3 张可用" onClick={() => {}} />
<Cell title="收藏夹" onClick={() => {}} />
<Cell title="帮助中心" onClick={() => {}} />
<Cell title="关于我们" onClick={() => {}} />
</View>
{isLoggedIn && (
<View className="logout">
<Button block type="default" onClick={() => {
logout()
Taro.showToast({ title: '已退出登录', icon: 'none' })
}}>
退出登录
</Button>
</View>
)}
</View>
)
}
🚀 6. 构建与发布
# 开发调试
npm run dev:weapp # 微信开发者工具实时预览
# 生产构建
npm run build:weapp # 构建微信小程序
npm run build:h5 # 构建 H5
npm run build:alipay # 构建支付宝小程序
# 上传发布
# 微信:开发者工具 → 上传 → 填写版本号 → 提交审核
# H5:将 dist/h5 部署到 CDN / Vercel / Nginx
✅ 全系列学习 Checklist
基础篇(第 1-3 篇)
- 安装 Taro CLI 并创建项目
- 掌握 React + TypeScript 基础
- 熟悉 Taro 内置组件和页面生命周期
核心篇(第 4-7 篇)
- 掌握路由导航和参数传递
- 会用 NutUI 组件库搭建界面
- 用 Zustand 管理全局状态
- 封装统一网络请求层
进阶篇(第 8-9 篇)
- 掌握多端条件编译
- 会调用小程序原生能力(登录/上传/定位/分享)
实战篇(第 10 篇)
- 搭建完整电商小程序
- 构建并发布到微信/H5
🎉 恭喜你完成了「Taro 从零到一」全部 10 篇系列!
你已经掌握了用 Taro 开发多端小程序的完整技能。 下一步:打开微信开发者工具,开始写你自己的小程序吧!
本文是「Taro 从零到一」系列第 10 篇(完结篇),共 10 篇。