news 2026/10/25 1:43:37

Day 17 | uni-app 小程序:前端从零搭建

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Day 17 | uni-app 小程序:前端从零搭建

摘要:后端 API 全部就绪了(Day01-16),但用户总不能抱着 Postman 购物吧?今天把前端搞起来——用 uni-app + Vue3 从零搭建商城小程序。涵盖项目初始化、五大核心页面(首页/商品详情/购物车/订单/个人中心)、组件化开发、Pinia 全局状态管理。成本?依然是零。


后端 API 写好了,然后呢?

过去 16 天,我们把商城的后端搭得明明白白:

  • Django + DRF 框架稳稳当当
  • 商品、订单、用户、支付、客服……接口全齐了
  • 统一的响应格式{code: 0, message: "success", data: ...}

但问题来了——用户怎么用?

你不会真的让用户打开 Postman,填个 POST 请求来买东西吧?那大概是客户流失最快的电商了。

我们需要一个前端。一个能在手机上跑的、界面好看的、交互流畅的前端。

为什么选 uni-app?

  • 一套代码,多端运行:微信小程序、支付宝小程序、H5、App……写一次,到处跑
  • Vue3 生态:Composition API、Pinia 状态管理
  • 国内生态好:插件市场丰富,踩坑有人帮

今天就从零开始,把商城的小程序前端搭起来。


uni-app 和 Vue3 的组合拳

uni-app 是什么

一句话:用 Vue 的语法写跨平台应用。

你的 Vue3 代码 │ ├── 编译 → 微信小程序(WXML + WXSS + JS) ├── 编译 → 支付宝小程序 ├── 编译 → H5(网页版) └── 编译 → App(iOS / Android)

底层是编译器 + 运行时。你写的是标准 Vue3 代码,uni-app 帮你翻译成各平台的原生代码。

Vue3 + Composition API

Vue3 最核心的变化是 Composition API。相比 Options API 那种 data/methods/computed 分家的写法,Composition API 让你按功能组织代码:

// Options API:按类型分家exportdefault{data(){return{count:0}},methods:{increment(){this.count++}},computed:{double(){returnthis.count*2}}}// Composition API:按逻辑聚合import{ref,computed}from'vue'constcount=ref(0)constincrement=()=>count.value++constdouble=computed(()=>count.value*2)

Pinia:Vue3 的官方状态管理

Vuex 已经是过去式了。Pinia 的优势很明显:

  • API 更简洁:没有 mutations,直接改 state
  • TypeScript 友好:类型推导完美
  • 体积更小:核心代码不到 1KB
  • 支持 Composition API:setup语法无缝集成

购物车数据、用户登录状态……这些跨页面共享的数据,用 Pinia 管理再合适不过。


动手实战

项目初始化

# 拉取 uni-app + Vue3 模板(vite-ts 自带 TypeScript)npx degit dcloudio/uni-preset-vue#vite-ts mei-ya-da-appcdmei-ya-da-appnpminstall# 安装 Pinia + 持久化插件(刷新/杀掉小程序不丢数据)npminstallpinianpminstallpinia-plugin-persisted-uniapp

初始化完成后,目录按功能分层:

mei-ya-da-app/src/ ├── api/ # 接口请求封装(request.ts 基础封装 + 按模块分的接口文件) ├── components/ # 通用组件(ProductCard、NavBar、CartModal、EmptyState) ├── composables/ # 组合式函数(useAuth、useCart) ├── pages/ # 页面(index 首页 / product 详情 / cart 购物车 / order 订单 / mine 我的) ├── stores/ # Pinia 状态(cart 购物车 / user 用户) ├── static/ # 静态资源 └── styles/ # 全局样式

这个结构看着复杂,其实就是按功能分层。api 管网络、components 放组件、pages 放页面、stores 放状态。找代码一目了然。

请求封装(api/request.ts)

所有接口请求的底层封装,统一处理 Base URL、Token 注入、错误拦截。核心逻辑三段:

exportfunctionrequest<T>(options):Promise<T>{returnnewPromise((resolve,reject)=>{// ① 从本地存储取 Token,注入请求头 Authorizationconsttoken=uni.getStorageSync('token')||''uni.request({url:BASE_URL+options.url,method:options.method||'GET',header:{'Content-Type':'application/json','Authorization':token?`Bearer${token}`:''},success:(res)=>{constdata=res.data// ② 统一响应格式 {code:0, message, data} —— code 0 成功,取 data.data// ③ code 401 说明 Token 过期 → 清 Token、提示登录、跳转// 其他 code → 弹出后端返回的错误信息},})})}

这段代码做了三件事:① 自动在请求头加 Token;② 自动解析后端统一响应格式;③ 401 自动跳登录。前端所有接口调用都走这个封装。

页面路由配置(pages.json)

底部导航栏四个 Tab:首页、购物车、订单、我的。商品详情页不在 TabBar 里,通过跳转进入。

{"pages":[{"path":"pages/index/index","style":{"navigationBarTitleText":"商城","enablePullDownRefresh":true}},{"path":"pages/product/detail","style":{"navigationBarTitleText":"商品详情"}},{"path":"pages/cart/cart","style":{"navigationBarTitleText":"购物车"}},{"path":"pages/order/order","style":{"navigationBarTitleText":"我的订单"}},{"path":"pages/mine/mine","style":{"navigationBarTitleText":"个人中心","navigationStyle":"custom"}}],"tabBar":{"color":"#999999","selectedColor":"#FF6034","list":[{"pagePath":"pages/index/index","text":"首页"},{"pagePath":"pages/cart/cart","text":"购物车"},{"pagePath":"pages/order/order","text":"订单"},{"pagePath":"pages/mine/mine","text":"我的"}]}}

五个核心页面

首页(pages/index/index.vue)

页面布局:顶部搜索栏(灰色圆角输入框)→轮播 Banner(约 180rpx 高,自动播放)→分类导航(一行四个图标)→商品瀑布流(两列卡片:图/名称/价格/销量)。

关键逻辑:

<template> <!-- 搜索栏 / 轮播 Banner / 分类导航 / 两列商品网格 --> <view class="product-grid"> <ProductCard v-for="p in productList" :key="p.id" :product="p" @click="goDetail(p.id)" /> </view> </template> <script setup lang="ts"> // ① onMounted 并行加载:轮播 + 分类 + 商品列表(Promise.all) // ② loadMore 分页:page+1 追加到列表尾部,hasMore 控制是否还能加载 // ③ 下拉刷新 onMounted 重新拉取首页数据 </script>
商品详情页(pages/product/detail.vue)

页面布局:商品大图轮播→价格区(红色大字 + 原价删除线 + 自营/包邮标签)→商品标题描述→规格选择入口(点开 SKU 弹窗)→图文详情(rich-text)→底部固定操作栏(收藏/客服/加入购物车/立即购买)。

关键逻辑:

<script setup lang="ts"> // ① loadProduct:从路由参数拿 id → getProductDetail(id) 加载详情 // ② addToCart:没选规格先弹 SKU 窗;选了规格调 cartStore.addItem 加购 // ③ buyNow:跳过购物车,带 product_id + sku 参数直接跳确认订单页 // ④ 图片预览 uni.previewImage / 收藏 toggleFavorite / 客服占位 </script>
购物车页(pages/cart/cart.vue)

页面布局:商品行(勾选框 + 缩略图 + 名称规格 + 价格 + 数量步进器)→底部结算栏(全选 + 合计金额 + 结算按钮带件数)。空车显示空状态插画 + “去逛逛”。

关键逻辑:

<script setup lang="ts"> // ① toggleCheck / toggleSelectAll:改 checked 后调 store.updateChecked() // ② changeQty:数量减到 0 时弹窗确认删除 // ③ goCheckout:只取勾选商品,编码后跳确认订单页 </script>
订单页(pages/order/order.vue)

页面布局:顶部 Tab 栏(全部/待付款/待发货/待收货/已完成)→订单卡片(订单号+状态标签 / 商品缩略图+名称+规格+价格×数量 / 底部合计+操作按钮)。

关键逻辑:

<script setup lang="ts"> // ① switchTab:切换状态 → 重置 page=1 → 重新拉取订单列表 // ② 操作按钮按 status 条件渲染:待付款显示"去支付/取消订单",待收货显示"确认收货" // ③ cancelOrder / confirmReceive:先弹确认框,确认后调对应 API 并刷新列表 </script>
个人中心(pages/mine/mine.vue)

页面布局:渐变头部(圆形头像 + 昵称 + 会员标签)→订单快捷入口(四宫格图标)→功能列表(地址/优惠券/客服/关于/设置)→退出登录按钮。

关键逻辑:

<script setup lang="ts"> // ① 未登录时显示"点击登录",跳登录页 // ② 各菜单项目前是占位 toast,后续接真实功能 // ③ logout:弹确认框 → userStore.logout() 清 Token </script>

五个页面的样式(SCSS)篇幅长但全是通用布局代码,这里不逐行贴——照着"页面由哪几块组成"的描述写就行,布局思路比样式代码更重要。

组件化开发

商品卡片组件(ProductCard.vue)被首页、分类页、搜索结果页复用——defineProps接收商品数据,defineEmits抛出点击事件,父组件决定跳哪:

<script setup lang="ts"> const props = defineProps<{ product: { id: number; name: string; image: string; price: number } }>() const emit = defineEmits<{ (e: 'click'): void }>() </script>

空状态组件(EmptyState.vue)——购物车空了、订单空了、搜索没结果都用它:一个图标 + 一句话 + 可选按钮,通过 props 传文案、emit 触发按钮点击。

Pinia 状态管理

购物车 Store(stores/cart.ts)

购物车状态需要在首页(加购)、购物车页(管理)、结算页(读取)之间共享。核心逻辑:

exportconstuseCartStore=defineStore('cart',()=>{constitems=ref<CartItem[]>([])// Getters:isAllChecked 全选 / checkedCount 选中件数 / checkedTotal 选中总金额 / totalCount 角标数// Actions:functionaddItem(newItem){// 关键:同商品 + 同规格合并数量,否则新增一条constexisting=items.value.find(item=>item.product_id===newItem.product_id&&JSON.stringify(item.spec)===JSON.stringify(newItem.spec))existing?existing.quantity+=newItem.quantity:items.value.push({...newItem,checked:false})updateBadge()// 更新 TabBar 角标}functionremoveItem(productId){...}// 过滤掉指定商品 + 更新角标functionclearCart(){...}// 清空 + 更新角标},{persist:{key:'meiyada-cart'}})// 持久化:杀掉小程序购物车还在
用户 Store(stores/user.ts)

管理登录状态、Token、个人信息:

exportconstuseUserStore=defineStore('user',()=>{consttoken=ref('');constnickname=ref('');constavatar=ref('')constisLoggedIn=computed(()=>!!token.value)asyncfunctionlogin(username,password){// 调后端登录接口拿 token → 存 state + uni.setStorageSync('token')(request.ts 自动读)}functionlogout(){// 清空 state + uni.removeStorageSync('token')}},{persist:{key:'meiyada-user'}})
main.ts 注册 Pinia
import{createPinia}from'pinia'importpiniaPluginPersisteduniappfrom'pinia-plugin-persisted-uniapp'constpinia=createPinia()pinia.use(piniaPluginPersisteduniapp)// 挂持久化插件app.use(pinia)

两个 Store 都用了持久化插件。购物车、Token 存在本地,小程序杀掉再打开,购物车的东西还在,用户也不用重新登录。


AI 加速:让 AI 帮你写页面

写前端页面最烦的是重复劳动——每个页面的结构都差不多,就是数据和样式不一样。这种活让 AI 来:

“基于 uni-app + Vue3 + TypeScript,生成一个电商商品列表页面。要求:两列瀑布流布局,每个卡片包含商品图片、名称、标签、价格、销量。使用 Composition API,样式用 SCSS,适配微信小程序。”

AI 能给你生成 80% 的代码,剩下 20% 是调整细节、对接接口。

小程序的样式调试很痛苦——没有 Chrome DevTools 那么好用。遇到样式问题直接截图丢给 AI:"这个商品卡片的价格和标题重叠了,帮我看看哪里出了问题。"比自己一个个属性排查快多了。

后端 API 文档给到 AI,让它直接生成前端的接口调用代码,也省得自己对着文档一行行翻译。


学完来练练手

  1. 🟢 入门:给商品卡片组件加一个"限时折扣"标签,当商品有discount字段时显示倒计时
  2. 🟡 进阶:给购物车页面加上"左滑删除"功能(touchstart/touchmove/touchend监听手势 +transform: translateX()实现滑动效果)
  3. 🔴 挑战:实现一个完整的搜索功能——搜索框输入 → 调用后端搜索接口 → 展示结果 → 支持历史记录

本系列为梅雅达编程笔记原创,首发于 CSDN。有问题?评论区见

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/25 1:40:15

Oracle生成随机数

一、随机函数指可以产生随机数的函数&#xff0c;它是可以产生随机的数字或者字符串&#xff1b;1.语法格式&#xff1a;①RAND()可以产生0到1之间的随机数&#xff1b;②dbms_random.value(数1(小),数2(大))生成数1-数2之间的随机数,默认生成小数&#xff1b; ③dbms_random.s…

作者头像 李华
网站建设 2026/10/25 1:37:24

倒排索引(Inverted Index)

倒排索引&#xff08;Inverted Index&#xff09;倒排索引&#xff08;Inverted Index&#xff09;一、先从正排索引说起二、什么是倒排索引三、两者对比四、倒排索引的组成1. 词项字典&#xff08;Term Dictionary&#xff09;2. 倒排列表&#xff08;Posting List&#xff09…

作者头像 李华
网站建设 2026/10/25 1:31:20

深入剖析 MySQL 原理级别的面试题

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/10/25 1:25:20

Spring Boot集成Tesseract实现生产级OCR服务

简介&#xff1a;本资源是一份面向Java后端开发者与OCR技术实践者的Spring Boot集成实战指南&#xff0c;聚焦于使用Tesseract OCR引擎实现高精度中文图片文字识别。内容涵盖Tesseract 5.0新版LSTM识别原理、chi_sim简体中文模型配置、tess4j依赖集成、Spring Boot自动装配与服…

作者头像 李华