5-1 实战项目:移动端商品小应用
恭喜你走到这里!这一章把前面学的全部串起来,做一个真实的 H5 移动端商品应用:商品列表 → 商品详情 → 购物车,带下拉刷新、上拉加载、底部导航、状态持久化。
配套完整项目:
05-实战项目/shop-app/,装好依赖即可运行。
1. 项目用到了哪些知识
| 功能 | 用到的知识(章节) |
|---|---|
单文件组件 + <script setup> | 2-1 |
| 页面跳转(列表→详情→购物车) | Vue Router(2-2) |
| 购物车跨页共享 + 持久化 | Pinia(2-3) |
| 取商品数据 | api 封装思路(2-4,这里用本地 mock 便于离线运行) |
| 移动端适配 | vw + postcss 插件(3-1) |
| UI 组件(NavBar/Card/Tabbar/Stepper...) | Vant(3-2) |
| 下拉刷新 + 上拉加载 | van-pull-refresh / van-list(3-3) |
为了离线也能跑,商品数据用本地 mock(
src/api/)模拟接口(返回 Promise,带延迟,像真实请求)。换成真后端时,把 mock 换成 2-4 的 axios 调用即可,组件代码不用动——这正是把接口收拢到api/层的好处。
2. 怎么跑起来
bash
cd 05-实战项目/shop-app
npm install
npm run dev浏览器打开后,按 F12 → 点设备工具栏图标,切到手机视图(如 iPhone SE / 12),体验最佳。
3. 项目结构与数据流
shop-app/src/
├── main.js ← 入口:装 Pinia / Router / Vant
├── App.vue ← 根:<router-view> + 底部 Tabbar
├── router/index.js ← 路由:列表 / 详情 / 购物车 / 我的
├── stores/cart.js ← 购物车 Pinia store(含 localStorage 持久化)
├── api/
│ ├── mock.js ← 模拟商品数据
│ └── product.js ← getProductPage / getProduct(返回 Promise)
└── views/
├── ProductList.vue ← 商品列表(下拉刷新 + 上拉分页加载)
├── ProductDetail.vue← 商品详情(加入购物车)
├── Cart.vue ← 购物车(改数量 / 删除 / 结算)
└── Mine.vue ← 我的(展示购物车统计)数据流核心:购物车数据放在 Pinia 的 cart store 里。
列表页/详情页 ──cart.add(商品)──► cart store(全局)
│
底部 Tabbar 角标 ◄──totalCount──┤
购物车页 ◄────items / 增删改─────┘三个页面都操作同一个 store,互不直接通信——这就是 2-3 讲的全局状态管理在实战里的样子。
4. 关键代码讲解
(1)购物车 store(stores/cart.js)
js
export const useCartStore = defineStore('cart', () => {
// 初始化时从 localStorage 读,实现刷新不丢(2-3 提到的持久化)
const items = ref(JSON.parse(localStorage.getItem('cart') || '[]'))
const totalCount = computed(() => items.value.reduce((s, i) => s + i.qty, 0))
const totalPrice = computed(() => items.value.reduce((s, i) => s + i.price * i.qty, 0))
function add(p) {
const found = items.value.find(i => i.id === p.id)
if (found) found.qty++ // 已有就加数量
else items.value.push({ id: p.id, name: p.name, price: p.price, emoji: p.emoji, qty: 1 })
}
function setQty(id, qty) { const f = items.value.find(i => i.id === id); if (f) f.qty = qty }
function remove(id) { items.value = items.value.filter(i => i.id !== id) }
function clear() { items.value = [] }
// 监听变化,自动存回 localStorage(watch 的副作用,1-3)
watch(items, (v) => localStorage.setItem('cart', JSON.stringify(v)), { deep: true })
return { items, totalCount, totalPrice, add, setQty, remove, clear }
})把 1-3(computed/watch)、2-3(Pinia)、持久化全用上了。
(2)列表页:下拉刷新 + 上拉加载(ProductList.vue)
vue
<van-pull-refresh v-model="refreshing" @refresh="onRefresh">
<van-list v-model:loading="loading" :finished="finished"
finished-text="没有更多了" @load="onLoad">
<!-- 商品卡片 v-for ... -->
</van-list>
</van-pull-refresh>js
let page = 1
async function onLoad() { // 滚到底自动触发
const res = await getProductPage(page)
if (refreshing.value) refreshing.value = false
list.value.push(...res.items) // 追加
loading.value = false
if (res.hasMore) page++
else finished.value = true
}
function onRefresh() { // 下拉触发
finished.value = false; page = 1; list.value = []
loading.value = true; onLoad() // 从头加载
}这就是 3-3 那套「分页 + 三状态」的真实落地。
(3)路由与底部导航
App.vue 用 <van-tabbar route> 配合 vue-router:点 Tab 自动切路由,角标读 Pinia 的 totalCount。详情页通过 meta.hideTab 隐藏底部栏(沉浸式)。
5. 建议的学习路径(动手!)
别只看,一定要动手改。推荐按顺序做这几个练习,做完你就真的掌握了:
- 加商品:在
api/mock.js加一个新商品(照已有格式),刷新看列表多一个。 - 改详情:在
ProductDetail.vue给详情页加一个「库存:xx」显示(mock 里加stock字段)。 - 购物车数量:在
Cart.vue体会van-stepper改数量,看合计金额实时变(computed)。 - 持久化验证:加几件商品到购物车,刷新页面,发现购物车还在(localStorage 持久化)。
- 接真后端(进阶):把
api/product.js里的 mock 换成 2-4 的 axios 写法,指向你们后端的商品接口——你会发现页面组件几乎不用改。 - 加搜索(进阶):列表页顶部加
van-search,根据关键字过滤商品(用 computed 过滤)。
6. 你已经具备的能力
走完这套教程,你现在能:
- ✅ 看懂并写 Vue 3 组件(响应式、指令、组件通信、生命周期)
- ✅ 用 Vite 工程化开发,配路由、状态管理、调接口
- ✅ 做 H5 移动端适配,用 Vant 快速搭界面
- ✅ 实现列表、详情、购物车这类真实业务页面
- ✅ 看懂公司项目的代码结构,参与实际开发
7. 接下来往哪走
- 多读公司项目代码:对照本教程的结构(router/stores/api/views/components),很快能上手。
- 遇到不会的组件查 Vant 文档,遇到不会的语法查 Vue 官方文档。
- 补 TypeScript(第 4 部分):如果公司项目用 TS,把组件逐步加上类型。
- 进阶主题(用到再学):权限路由守卫、请求 loading 全局拦截、组件库主题定制、性能优化、单元测试。
后端转前端最难的是「思维转变」——从「掌控流程」到「维护状态、响应事件」。你已经迈过这道坎了。剩下的就是在真实项目里多写多练。加油!🚀
🎉 全部教程完成! 回到 总目录 README 可随时复习任意章节。