引言:跨平台开发的新范式
在移动互联网时代,多端适配成为开发者的重要挑战。UniApp基于Vue.js生态,实现了”一次开发,多端部署”的愿景。本文将深入探讨如何基于UniApp构建企业级电商应用,涵盖架构设计、性能优化、原生能力集成等核心内容。
一、UniApp架构设计与工程化实践
1.1 项目架构分层设计
企业级应用需要清晰的架构分层,确保代码的可维护性和可扩展性:
project-root/
├── src/
│ ├── components/ # 公共组件
│ ├── pages/ # 页面组件
│ ├── static/ # 静态资源
│ ├── store/ # 状态管理
│ ├── api/ # 接口服务
│ ├── utils/ # 工具函数
│ └── config/ # 配置文件
├── platforms/ # 平台特定代码
├── uni_modules/ # 第三方模块
└── manifest.json # 应用配置
1.2 多环境配置管理
// config/env.js
const envConfig = {
development: {
baseURL: 'https://dev-api.example.com',
appId: 'dev_app_id',
debug: true
},
production: {
baseURL: 'https://api.example.com',
appId: 'prod_app_id',
debug: false
}
}
const currentEnv = process.env.NODE_ENV || 'development'
export default envConfig[currentEnv]
// main.js
import envConfig from '@/config/env'
Vue.prototype.$config = envConfig
二、实战案例:企业级电商APP开发
2.1 核心页面架构设计
电商应用包含首页、商品列表、商品详情、购物车、订单、个人中心等核心模块。
2.2 首页组件化实现
<template>
<view class="home-container">
<!-- 自定义导航栏 -->
<custom-nav-bar title="商城首页" :show-search="true" @search="onSearch"></custom-nav-bar>
<!-- 轮播图 -->
<swiper class="banner-swiper" :indicator-dots="true" :autoplay="true" :interval="3000">
<swiper-item v-for="(banner, index) in bannerList" :key="index">
<image :src="banner.image" mode="aspectFill" @click="onBannerClick(banner)"></image>
</swiper-item>
</swiper>
<!-- 分类导航 -->
<scroll-view class="category-scroll" scroll-x="true">
<view class="category-item" v-for="category in categories" :key="category.id"
@click="onCategoryClick(category)">
<image :src="category.icon" class="category-icon"></image>
<text class="category-name">{{ category.name }}</text>
</view>
</scroll-view>
<!-- 商品列表 -->
<product-grid :products="productList" @item-click="onProductClick"></product-grid>
<!-- 加载更多 -->
<load-more :status="loadStatus" @load-more="loadMoreProducts"></load-more>
</view>
</template>
<script>
import { mapState, mapActions } from 'vuex'
export default {
data() {
return {
bannerList: [],
categories: [],
productList: [],
page: 1,
loadStatus: 'more'
}
},
computed: {
...mapState(['userInfo'])
},
onLoad() {
this.initHomeData()
},
onPullDownRefresh() {
this.refreshHomeData().finally(() => {
uni.stopPullDownRefresh()
})
},
onReachBottom() {
if (this.loadStatus === 'more') {
this.loadMoreProducts()
}
},
methods: {
...mapActions(['addToCart']),
async initHomeData() {
try {
await Promise.all([
this.loadBanners(),
this.loadCategories(),
this.loadProducts()
])
} catch (error) {
this.$utils.showToast('数据加载失败')
}
},
async refreshHomeData() {
this.page = 1
this.productList = []
await this.initHomeData()
},
async loadBanners() {
const { data } = await this.$api.home.getBanners()
this.bannerList = data
},
async loadCategories() {
const { data } = await this.$api.home.getCategories()
this.categories = data
},
async loadProducts() {
const { data } = await this.$api.product.getList({
page: this.page,
pageSize: 10
})
if (data.list.length > 0) {
this.productList = this.productList.concat(data.list)
this.page++
this.loadStatus = data.hasMore ? 'more' : 'noMore'
} else {
this.loadStatus = 'noMore'
}
},
onBannerClick(banner) {
if (banner.linkType === 'product') {
uni.navigateTo({
url: `/pages/product/detail?id=${banner.linkId}`
})
}
},
onCategoryClick(category) {
uni.navigateTo({
url: `/pages/product/list?categoryId=${category.id}`
})
},
onProductClick(product) {
uni.navigateTo({
url: `/pages/product/detail?id=${product.id}`
})
},
onSearch(keyword) {
uni.navigateTo({
url: `/pages/search/result?keyword=${keyword}`
})
}
}
}
</script>
2.3 商品详情页实现
<template>
<view class="product-detail">
<scroll-view class="detail-scroll" scroll-y="true"
:refresher-enabled="true" :refresher-triggered="refreshing"
@refresherrefresh="onRefresh">
<!-- 商品图片 -->
<product-gallery :images="product.images" :video="product.video"></product-gallery>
<!-- 商品信息 -->
<view class="product-info">
<view class="price-section">
<text class="current-price">¥{{ product.price }}</text>
<text class="original-price" v-if="product.originalPrice">
¥{{ product.originalPrice }}
</text>
<text class="discount" v-if="product.discount">
{{ product.discount }}折
</text>
</view>
<view class="title-section">
<text class="product-title">{{ product.title }}</text>
<text class="product-subtitle">{{ product.subtitle }}</text>
</view>
<view class="tags-section">
<text class="tag" v-for="tag in product.tags" :key="tag">
{{ tag }}
</text>
</view>
</view>
<!-- SKU选择 -->
<sku-selector :product="product" :skus="skus"
@sku-change="onSkuChange"></sku-selector>
<!-- 商品详情 -->
<rich-text class="product-content" :nodes="product.content"></rich-text>
</scroll-view>
<!-- 底部操作栏 -->
<view class="action-bar">
<view class="action-icons">
<view class="action-item" @click="onServiceClick">
<image src="/static/icons/service.png"></image>
<text>客服</text>
</view>
<view class="action-item" @click="onCartClick">
<image src="/static/icons/cart.png"></image>
<text>购物车</text>
<view class="cart-badge" v-if="cartCount > 0">
{{ cartCount }}
</view>
</view>
<view class="action-item" @click="onFavoriteClick">
<image :src="isFavorite ? '/static/icons/favorite-active.png' : '/static/icons/favorite.png'"></image>
<text>收藏</text>
</view>
</view>
<view class="action-buttons">
<button class="btn add-cart" @click="addToCart">
加入购物车
</button>
<button class="btn buy-now" @click="buyNow">
立即购买
</button>
</view>
</view>
</view>
</template>
<script>
export default {
data() {
return {
product: {},
skus: [],
selectedSku: null,
isFavorite: false,
refreshing: false
}
},
computed: {
cartCount() {
return this.$store.getters.cartCount
}
},
onLoad(options) {
this.productId = options.id
this.loadProductDetail()
},
methods: {
async loadProductDetail() {
try {
const [productRes, skuRes, favoriteRes] = await Promise.all([
this.$api.product.getDetail(this.productId),
this.$api.product.getSkus(this.productId),
this.$api.user.checkFavorite(this.productId)
])
this.product = productRes.data
this.skus = skuRes.data
this.isFavorite = favoriteRes.data.isFavorite
// 设置页面标题
uni.setNavigationBarTitle({
title: this.product.title
})
} catch (error) {
this.$utils.showToast('商品信息加载失败')
}
},
onSkuChange(sku) {
this.selectedSku = sku
},
async addToCart() {
if (!this.selectedSku) {
this.$utils.showToast('请选择商品规格')
return
}
try {
await this.$store.dispatch('addToCart', {
productId: this.productId,
skuId: this.selectedSku.id,
quantity: 1
})
this.$utils.showToast('添加成功', 'success')
} catch (error) {
this.$utils.showToast('添加失败')
}
},
buyNow() {
if (!this.selectedSku) {
this.$utils.showToast('请选择商品规格')
return
}
uni.navigateTo({
url: `/pages/order/confirm?skuId=${this.selectedSku.id}&quantity=1`
})
},
onRefresh() {
this.refreshing = true
this.loadProductDetail().finally(() => {
this.refreshing = false
})
}
}
}
</script>
三、状态管理与数据持久化
3.1 Vuex状态管理配置
// store/index.js
import Vue from 'vue'
import Vuex from 'vuex'
import user from './modules/user'
import cart from './modules/cart'
import product from './modules/product'
Vue.use(Vuex)
const store = new Vuex.Store({
modules: {
user,
cart,
product
},
state: {
// 全局状态
appLoaded: false,
networkStatus: 'online'
},
mutations: {
SET_APP_LOADED(state, loaded) {
state.appLoaded = loaded
},
SET_NETWORK_STATUS(state, status) {
state.networkStatus = status
}
},
actions: {
async initializeApp({ commit, dispatch }) {
try {
// 初始化必要数据
await Promise.all([
dispatch('user/checkLoginStatus'),
dispatch('cart/loadCart')
])
commit('SET_APP_LOADED', true)
} catch (error) {
console.error('App initialization failed:', error)
}
}
}
})
export default store
// store/modules/cart.js
const cart = {
state: {
items: [],
total: 0
},
getters: {
cartCount: state => {
return state.items.reduce((count, item) => count + item.quantity, 0)
},
cartTotal: state => {
return state.items.reduce((total, item) => {
return total + (item.price * item.quantity)
}, 0)
}
},
mutations: {
SET_CART_ITEMS(state, items) {
state.items = items
},
ADD_CART_ITEM(state, item) {
const existingItem = state.items.find(i =>
i.productId === item.productId && i.skuId === item.skuId
)
if (existingItem) {
existingItem.quantity += item.quantity
} else {
state.items.push(item)
}
// 持久化到本地存储
uni.setStorageSync('cart_items', state.items)
},
UPDATE_CART_ITEM(state, { index, quantity }) {
if (quantity <= 0) {
state.items.splice(index, 1)
} else {
state.items[index].quantity = quantity
}
uni.setStorageSync('cart_items', state.items)
}
},
actions: {
async loadCart({ commit }) {
try {
// 从本地存储加载
const localItems = uni.getStorageSync('cart_items') || []
commit('SET_CART_ITEMS', localItems)
// 同步服务端数据
const { data } = await this.$api.cart.getList()
commit('SET_CART_ITEMS', data)
} catch (error) {
console.error('Load cart failed:', error)
}
},
async addToCart({ commit }, item) {
try {
// 调用接口添加到服务端
await this.$api.cart.add(item)
// 更新本地状态
commit('ADD_CART_ITEM', item)
return Promise.resolve()
} catch (error) {
return Promise.reject(error)
}
}
}
}
export default cart
四、性能优化与最佳实践
4.1 图片懒加载与优化
// components/lazy-image.vue
<template>
<image :src="finalSrc" :mode="mode" :lazy-load="true"
@load="onImageLoad" @error="onImageError"
:class="['lazy-image', { loaded: isLoaded }]">
</image>
</template>
<script>
export default {
props: {
src: String,
mode: {
type: String,
default: 'aspectFill'
},
placeholder: {
type: String,
default: '/static/images/placeholder.png'
}
},
data() {
return {
isLoaded: false,
loadError: false
}
},
computed: {
finalSrc() {
if (this.loadError || !this.src) {
return this.placeholder
}
return this.isLoaded ? this.src : this.placeholder
}
},
methods: {
onImageLoad() {
this.isLoaded = true
this.$emit('load')
},
onImageError() {
this.loadError = true
this.$emit('error')
}
}
}
</script>
// 使用示例
<lazy-image :src="product.image" mode="aspectFill"></lazy-image>
4.2 请求封装与缓存策略
// utils/request.js
class Request {
constructor() {
this.baseURL = this.getBaseURL()
this.timeout = 10000
this.interceptors = {
request: [],
response: []
}
}
getBaseURL() {
// 根据平台返回不同的baseURL
#ifdef H5
return '/api'
#endif
#ifdef MP-WEIXIN
return 'https://api.example.com/miniapp'
#endif
#ifdef APP-PLUS
return 'https://api.example.com/app'
#endif
}
async request(options) {
// 请求拦截
for (let interceptor of this.interceptors.request) {
options = await interceptor(options)
}
return new Promise((resolve, reject) => {
uni.request({
url: this.baseURL + options.url,
method: options.method || 'GET',
data: options.data,
header: {
'Content-Type': 'application/json',
'Authorization': this.getToken(),
...options.header
},
timeout: this.timeout,
success: (res) => {
// 响应拦截
for (let interceptor of this.interceptors.response) {
res = interceptor(res)
}
if (res.statusCode === 200) {
resolve(res.data)
} else {
reject(this.handleError(res))
}
},
fail: (error) => {
reject(this.handleError(error))
}
})
})
}
getToken() {
return uni.getStorageSync('token') || ''
}
handleError(error) {
// 统一错误处理
const errorMap = {
401: '未授权,请重新登录',
403: '禁止访问',
404: '请求地址不存在',
500: '服务器内部错误',
502: '网关错误',
503: '服务不可用'
}
return {
code: error.statusCode || -1,
message: errorMap[error.statusCode] || '网络请求失败',
data: error.data
}
}
// 缓存请求
async cachedRequest(options, cacheKey, expireTime = 300000) { // 5分钟
const cache = uni.getStorageSync(cacheKey)
const now = Date.now()
if (cache && (now - cache.timestamp {
if (options.showLoading !== false) {
uni.showLoading({
title: '加载中...',
mask: true
})
}
return options
})
// 响应拦截器 - 隐藏loading
request.interceptors.response.push((res) => {
uni.hideLoading()
return res
})
export default request
五、多端适配与原生能力集成
5.1 条件编译与平台差异处理
// utils/platform.js
export const getPlatform = () => {
#ifdef H5
return 'h5'
#endif
#ifdef MP-WEIXIN
return 'weixin'
#endif
#ifdef APP-PLUS
return 'app'
#endif
}
export const isWeixin = getPlatform() === 'weixin'
export const isApp = getPlatform() === 'app'
export const isH5 = getPlatform() === 'h5'
// 平台特定的分享功能
export const shareToFriend = (options) => {
#ifdef MP-WEIXIN
wx.shareAppMessage(options)
#endif
#ifdef APP-PLUS
plus.share.sendWithSystem(options)
#endif
#ifdef H5
// H5分享处理
if (navigator.share) {
navigator.share(options)
}
#endif
}
六、部署与发布流程
6.1 自动化构建配置
// package.json 构建脚本
{
"scripts": {
"build:h5": "cross-env NODE_ENV=production uni-build --platform h5",
"build:mp-weixin": "cross-env NODE_ENV=production uni-build --platform mp-weixin",
"build:app": "cross-env NODE_ENV=production uni-build --platform app",
"dev:h5": "cross-env NODE_ENV=development uni-build --platform h5",
"dev:mp-weixin": "cross-env NODE_ENV=development uni-build --platform mp-weixin"
}
}
// GitHub Actions 自动化部署
// .github/workflows/deploy.yml
name: Deploy UniApp
on:
push:
branches: [ main ]
jobs:
build-and-deploy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: Setup Node.js
uses: actions/setup-node@v2
with:
node-version: '14'
- name: Install dependencies
run: npm install
- name: Build for H5
run: npm run build:h5
- name: Build for Weixin Mini Program
run: npm run build:mp-weixin
- name: Deploy to CDN
uses: peaceiris/actions-gh-pages@v3
with:
github_token: ${{ secrets.GITHUB_TOKEN }}
publish_dir: ./dist/build/h5
结语
UniApp为跨平台开发提供了强大的解决方案,通过合理的架构设计和性能优化,可以构建出媲美原生体验的企业级应用。本文提供的电商应用案例涵盖了从基础架构到高级优化的完整流程,为实际项目开发提供了可靠的技术参考。
本文详细介绍了UniApp在企业级电商应用开发中的完整实践方案,所有代码示例均为原创实现,可直接用于项目开发和架构设计。