UniApp跨平台开发实战:从零构建企业级电商APP完整指南 | 原创技术教程

2025-11-03 0 984

原创作者:前端架构师 | 发布日期: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架构

UniApp跨平台开发实战:从零构建企业级电商APP完整指南 | 原创技术教程
收藏 (0) 打赏

感谢您的支持,我会继续努力的!

打开微信/支付宝扫一扫,即可进行扫码打赏哦,分享从这里开始,精彩与您同在
点赞 (0)

淘吗网 uniapp UniApp跨平台开发实战:从零构建企业级电商APP完整指南 | 原创技术教程 https://www.taomawang.com/web/uniapp/1366.html

下一篇:

已经没有下一篇了!

常见问题

相关文章

发表评论
暂无评论
官方客服团队

为您解决烦忧 - 24小时在线 专业服务