原创作者:前端架构师 | 发布日期:2023年11月
UniApp开发环境搭建
环境准备与工具配置
// 安装HBuilderX
// 下载地址:https://www.dcloud.io/hbuilderx.html
// 创建UniApp项目
文件 → 新建 → 项目 → UniApp → 选择模板
// 项目基础配置 manifest.json
{
"name": "电商APP",
"appid": "__UNI__XXXXXX",
"description": "企业级电商解决方案",
"versionName": "1.0.0",
"versionCode": "100",
"transformPx": false,
"app-plus": {
"usingComponents": true,
"nvueStyleCompiler": "uni-app",
"compilerVersion": 3,
"splashscreen": {
"alwaysShowBeforeRender": true,
"waiting": true,
"autoclose": true,
"delay": 0
}
}
}
目录结构规范
ecommerce-app/
├── pages/ // 页面文件
│ ├── index/
│ ├── category/
│ ├── cart/
│ └── user/
├── static/ // 静态资源
│ ├── images/
│ ├── icons/
│ └── fonts/
├── components/ // 公共组件
├── store/ // 状态管理
├── utils/ // 工具函数
├── api/ // 接口管理
├── styles/ // 样式文件
└── uni_modules/ // 插件模块
项目架构设计
路由配置与页面管理
// pages.json 路由配置
{
"pages": [
{
"path": "pages/index/index",
"style": {
"navigationBarTitleText": "首页",
"enablePullDownRefresh": true,
"onReachBottomDistance": 50
}
},
{
"path": "pages/category/category",
"style": {
"navigationBarTitleText": "分类",
"navigationBarBackgroundColor": "#FF6A00"
}
}
],
"tabBar": {
"color": "#7A7E83",
"selectedColor": "#FF6A00",
"borderStyle": "black",
"backgroundColor": "#ffffff",
"list": [
{
"pagePath": "pages/index/index",
"iconPath": "static/tabbar/home.png",
"selectedIconPath": "static/tabbar/home-active.png",
"text": "首页"
},
{
"pagePath": "pages/category/category",
"iconPath": "static/tabbar/category.png",
"selectedIconPath": "static/tabbar/category-active.png",
"text": "分类"
}
]
},
"globalStyle": {
"navigationBarTextStyle": "black",
"navigationBarTitleText": "电商APP",
"navigationBarBackgroundColor": "#FFFFFF",
"backgroundColor": "#F8F8F8"
}
}
自定义导航栏组件
<template>
<view class="custom-navbar" :style="{ height: navbarHeight + 'px' }">
<view class="navbar-content" :style="{ height: statusBarHeight + 'px' }"></view>
<view class="navbar-main" :style="{ height: (navbarHeight - statusBarHeight) + 'px' }">
<view class="navbar-left" @click="handleBack" v-if="showBack">
<image src="/static/icons/back.png" mode="aspectFit"></image>
</view>
<view class="navbar-title">{{ title }}</view>
<view class="navbar-right">
<slot name="right"></slot>
</view>
</view>
</view>
</template>
<script>
export default {
name: 'CustomNavbar',
props: {
title: {
type: String,
default: ''
},
showBack: {
type: Boolean,
default: true
}
},
data() {
return {
statusBarHeight: 0,
navbarHeight: 0
}
},
mounted() {
this.getSystemInfo()
},
methods: {
getSystemInfo() {
const systemInfo = uni.getSystemInfoSync()
this.statusBarHeight = systemInfo.statusBarHeight
// 小程序导航栏高度为44px,App为动态计算
this.navbarHeight = systemInfo.platform === 'ios' ? 44 : 48
},
handleBack() {
uni.navigateBack()
}
}
}
</script>
核心模块实现
商品列表组件
<template>
<view class="product-list">
<view class="product-grid">
<view
class="product-item"
v-for="product in productList"
:key="product.id"
@click="handleProductClick(product)"
>
<image
class="product-image"
:src="product.image"
mode="aspectFill"
lazy-load
></image>
<view class="product-info">
<text class="product-name">{{ product.name }}</text>
<text class="product-desc">{{ product.description }}</text>
<view class="product-price">
<text class="current-price">¥{{ product.price }}</text>
<text class="original-price" v-if="product.originalPrice">
¥{{ product.originalPrice }}
</text>
</view>
<view class="product-actions">
<text class="sales">已售{{ product.sales }}件</text>
<view class="add-cart-btn" @click.stop="addToCart(product)">
<text class="iconfont icon-cart"></text>
</view>
</view>
</view>
</view>
</view>
<load-more :status="loadingStatus"></load-more>
</view>
</template>
<script>
export default {
name: 'ProductList',
props: {
products: {
type: Array,
default: () => []
}
},
data() {
return {
productList: [],
loadingStatus: 'loading',
page: 1,
pageSize: 10
}
},
watch: {
products: {
handler(newVal) {
this.productList = newVal
},
immediate: true
}
},
methods: {
async loadMore() {
if (this.loadingStatus === 'nomore') return
this.loadingStatus = 'loading'
try {
const newProducts = await this.$api.product.getList({
page: this.page,
pageSize: this.pageSize
})
if (newProducts.length > 0) {
this.productList = [...this.productList, ...newProducts]
this.page++
this.loadingStatus = newProducts.length < this.pageSize ? 'nomore' : 'more'
} else {
this.loadingStatus = 'nomore'
}
} catch (error) {
this.loadingStatus = 'more'
console.error('加载商品失败:', error)
}
},
handleProductClick(product) {
uni.navigateTo({
url: `/pages/product/detail?id=${product.id}`
})
},
addToCart(product) {
this.$store.dispatch('cart/addToCart', product)
uni.showToast({
title: '添加成功',
icon: 'success'
})
}
}
}
</script>
购物车管理模块
// store/modules/cart.js
export default {
namespaced: true,
state: {
cartItems: [],
selectedItems: []
},
getters: {
totalCount: state => {
return state.cartItems.reduce((total, item) => total + item.quantity, 0)
},
totalPrice: state => {
return state.selectedItems.reduce((total, item) => {
return total + (item.price * item.quantity)
}, 0)
},
isAllSelected: state => {
return state.cartItems.length > 0 &&
state.selectedItems.length === state.cartItems.length
}
},
mutations: {
SET_CART_ITEMS(state, items) {
state.cartItems = items
},
ADD_TO_CART(state, product) {
const existingItem = state.cartItems.find(item => item.id === product.id)
if (existingItem) {
existingItem.quantity++
} else {
state.cartItems.push({
...product,
quantity: 1,
selected: false
})
}
// 持久化存储
uni.setStorageSync('cart_items', state.cartItems)
},
UPDATE_QUANTITY(state, { id, quantity }) {
const item = state.cartItems.find(item => item.id === id)
if (item) {
item.quantity = Math.max(1, quantity)
uni.setStorageSync('cart_items', state.cartItems)
}
},
TOGGLE_SELECT(state, id) {
const item = state.cartItems.find(item => item.id === id)
if (item) {
item.selected = !item.selected
state.selectedItems = state.cartItems.filter(item => item.selected)
}
},
TOGGLE_ALL_SELECT(state) {
const allSelected = state.cartItems.every(item => item.selected)
state.cartItems.forEach(item => {
item.selected = !allSelected
})
state.selectedItems = allSelected ? [] : [...state.cartItems]
},
REMOVE_ITEMS(state, ids) {
state.cartItems = state.cartItems.filter(item => !ids.includes(item.id))
state.selectedItems = state.selectedItems.filter(item => !ids.includes(item.id))
uni.setStorageSync('cart_items', state.cartItems)
}
},
actions: {
async addToCart({ commit }, product) {
commit('ADD_TO_CART', product)
},
async updateQuantity({ commit }, payload) {
commit('UPDATE_QUANTITY', payload)
},
async toggleSelect({ commit }, id) {
commit('TOGGLE_SELECT', id)
},
async toggleAllSelect({ commit }) {
commit('TOGGLE_ALL_SELECT')
},
async removeItems({ commit }, ids) {
commit('REMOVE_ITEMS', ids)
},
loadCartFromStorage({ commit }) {
const cartItems = uni.getStorageSync('cart_items') || []
commit('SET_CART_ITEMS', cartItems)
}
}
}
状态管理方案
Vuex Store配置
// store/index.js
import Vue from 'vue'
import Vuex from 'vuex'
import cart from './modules/cart'
import user from './modules/user'
import products from './modules/products'
Vue.use(Vuex)
const store = new Vuex.Store({
modules: {
cart,
user,
products
},
state: {
// 全局状态
networkStatus: 'online',
appTheme: 'light'
},
mutations: {
SET_NETWORK_STATUS(state, status) {
state.networkStatus = status
},
SET_APP_THEME(state, theme) {
state.appTheme = theme
uni.setStorageSync('app_theme', theme)
}
},
actions: {
initializeApp({ commit, dispatch }) {
// 加载持久化数据
const savedTheme = uni.getStorageSync('app_theme') || 'light'
commit('SET_APP_THEME', savedTheme)
// 加载购物车数据
dispatch('cart/loadCartFromStorage')
// 监听网络状态
uni.onNetworkStatusChange((res) => {
commit('SET_NETWORK_STATUS', res.isConnected ? 'online' : 'offline')
})
}
}
})
export default store
用户模块实现
// store/modules/user.js
export default {
namespaced: true,
state: {
userInfo: null,
token: '',
loginStatus: false
},
mutations: {
SET_USER_INFO(state, userInfo) {
state.userInfo = userInfo
},
SET_TOKEN(state, token) {
state.token = token
if (token) {
uni.setStorageSync('user_token', token)
} else {
uni.removeStorageSync('user_token')
}
},
SET_LOGIN_STATUS(state, status) {
state.loginStatus = status
}
},
actions: {
async login({ commit }, { username, password }) {
try {
const result = await uni.request({
url: '/api/user/login',
method: 'POST',
data: { username, password }
})
if (result.data.success) {
commit('SET_USER_INFO', result.data.userInfo)
commit('SET_TOKEN', result.data.token)
commit('SET_LOGIN_STATUS', true)
return Promise.resolve(result.data)
} else {
return Promise.reject(new Error(result.data.message))
}
} catch (error) {
return Promise.reject(error)
}
},
async logout({ commit }) {
commit('SET_USER_INFO', null)
commit('SET_TOKEN', '')
commit('SET_LOGIN_STATUS', false)
uni.removeStorageSync('user_token')
},
async checkLoginStatus({ commit, state }) {
const token = uni.getStorageSync('user_token')
if (token && !state.loginStatus) {
try {
const userInfo = await this.$api.user.getUserInfo()
commit('SET_USER_INFO', userInfo)
commit('SET_TOKEN', token)
commit('SET_LOGIN_STATUS', true)
} catch (error) {
// token失效,清除登录状态
commit('SET_TOKEN', '')
uni.removeStorageSync('user_token')
}
}
}
}
}
性能优化策略
图片懒加载优化
// utils/lazy-load.js
export const lazyLoad = {
install(Vue) {
Vue.directive('lazy', {
inserted(el, binding) {
const io = new IntersectionObserver((entries) => {
entries.forEach(entry => {
if (entry.isIntersecting) {
const img = entry.target
img.src = binding.value
io.unobserve(img)
}
})
})
io.observe(el)
}
})
}
}
// 在main.js中使用
import { lazyLoad } from '@/utils/lazy-load'
Vue.use(lazyLoad)
// 在组件中使用
<image v-lazy="product.image" mode="aspectFill"></image>
请求缓存与防抖
// utils/request.js
class RequestCache {
constructor() {
this.cache = new Map()
this.pending = new Map()
}
async request(config) {
const key = this.generateKey(config)
// 检查缓存
if (this.cache.has(key)) {
return Promise.resolve(this.cache.get(key))
}
// 检查是否已有相同请求在进行中
if (this.pending.has(key)) {
return this.pending.get(key)
}
const requestPromise = uni.request(config).then(response => {
// 缓存成功响应
if (response.statusCode === 200) {
this.cache.set(key, response.data)
}
this.pending.delete(key)
return response.data
}).catch(error => {
this.pending.delete(key)
throw error
})
this.pending.set(key, requestPromise)
return requestPromise
}
generateKey(config) {
return `${config.method}_${config.url}_${JSON.stringify(config.data)}`
}
clear() {
this.cache.clear()
this.pending.clear()
}
}
export const requestCache = new RequestCache()
分包加载配置
// manifest.json 分包配置
{
"app-plus": {
"optimization": {
"subPackages": true
}
},
"subPackages": [
{
"root": "pagesA",
"pages": [
{
"path": "product/list",
"style": { ... }
},
{
"path": "product/detail",
"style": { ... }
}
]
},
{
"root": "pagesB",
"pages": [
{
"path": "user/order",
"style": { ... }
},
{
"path": "user/address",
"style": { ... }
}
]
}
],
"preloadRule": {
"pages/index/index": {
"network": "all",
"packages": ["pagesA"]
}
}
}
多端发布部署
条件编译处理多端差异
// utils/platform.js
export const getPlatform = () => {
// #ifdef APP-PLUS
return 'app'
// #endif
// #ifdef MP-WEIXIN
return 'weixin'
// #endif
// #ifdef H5
return 'h5'
// #endif
}
export const adaptStyle = (styles) => {
const platform = getPlatform()
// #ifdef APP-PLUS
return {
...styles,
paddingTop: '44px' // App导航栏高度
}
// #endif
// #ifdef MP-WEIXIN
return {
...styles,
paddingTop: '0px' // 小程序使用自定义导航栏
}
// #endif
return styles
}
// 在组件中使用条件编译
<template>
<view>
<!-- App端特有功能 -->
// #ifdef APP-PLUS
<view class="app-special-feature">
App特有功能
</view>
// #endif
<!-- 微信小程序特有功能 -->
// #ifdef MP-WEIXIN
<view class="mp-special-feature">
小程序特有功能
</view>
// #endif
</view>
</template>
发布脚本配置
// package.json 发布脚本
{
"scripts": {
"build:h5": "uni-build --platform h5",
"build:mp-weixin": "uni-build --platform mp-weixin",
"build:app": "uni-build --platform app",
"dev:h5": "uni --platform h5",
"dev:mp-weixin": "uni --platform mp-weixin",
"dev:app": "uni --platform app"
}
}
// 自定义发布配置
// vue.config.js
module.exports = {
configureWebpack: {
plugins: [
// 自定义插件
]
},
chainWebpack: (config) => {
// 修改webpack配置
config.plugin('define').tap(args => {
args[0]['process.env'].APP_VERSION = JSON.stringify(process.env.npm_package_version)
return args
})
},
// H5特定配置
devServer: {
proxy: {
'/api': {
target: 'https://api.example.com',
changeOrigin: true
}
}
}
}
多端调试技巧
// utils/debug.js
class AppDebugger {
constructor() {
this.isDebug = process.env.NODE_ENV === 'development'
}
log(...args) {
if (this.isDebug) {
console.log('[APP_DEBUG]', ...args)
}
}
error(...args) {
if (this.isDebug) {
console.error('[APP_ERROR]', ...args)
}
// 生产环境错误上报
// #ifndef H5
uni.reportAnalytics('error', {
message: args.join(' ')
})
// #endif
}
performance(name, fn) {
if (this.isDebug) {
const start = Date.now()
const result = fn()
const end = Date.now()
console.log(`[PERFORMANCE] ${name}: ${end - start}ms`)
return result
}
return fn()
}
}
export const debugger = new AppDebugger()
项目总结
通过本教程,我们完整构建了一个企业级的电商APP项目,涵盖了:
- UniApp开发环境搭建和项目初始化
- 复杂的组件架构设计和状态管理方案
- 购物车、用户管理等核心业务模块实现
- 性能优化和多端适配的最佳实践
- 完整的发布部署流程
UniApp作为跨平台开发解决方案,能够显著提升开发效率,一套代码多端发布。掌握这些技术要点,可以帮助开发者快速构建高质量的移动应用。
进一步学习建议
- 深入研究UniApp插件开发
- 学习原生插件集成
- 掌握更复杂的动画和交互效果
- 了解小程序云开发和Serverless架构

