UniApp跨平台开发实战:从零构建企业级多端应用完整指南

2025-08-25 0 807

UniApp在现代跨平台开发中的优势

UniApp是一个使用Vue.js语法开发所有前端应用的框架,开发者编写一套代码,可发布到iOS、Android、Web(响应式)、以及各种小程序(微信/支付宝/百度/头条/QQ/快手/钉钉/淘宝)、快应用等多个平台。它解决了多端开发的痛点,大大提高了开发效率和代码复用率。

环境搭建与项目初始化

1. 开发环境配置

确保系统已安装Node.js(版本≥12),然后安装HBuilderX或使用VSCode+uni-app插件

# 通过vue-cli创建uni-app项目
npm install -g @vue/cli
vue create -p dcloudio/uni-preset-vue my-project

# 选择模板(选择默认模板或自定义)
cd my-project
npm run dev:%PLATFORM%  # 如:npm run dev:mp-weixin

# 或使用HBuilderX可视化创建
# 文件 → 新建 → 项目 → uni-app → 选择模板

2. 项目目录结构解析

my-project/
├── pages/                 # 页面目录
│   ├── index/
│   │   ├── index.vue     # 首页页面
│   │   └── index.json    # 页面配置文件
├── components/           # 组件目录
├── static/              # 静态资源
├── uni_modules/         # uni模块
├── App.vue             # 应用配置
├── main.js             # 入口文件
├── manifest.json       # 应用配置文件
└── pages.json          # 页面路由配置

3. 核心配置文件详解

// pages.json - 全局页面配置
{
    "pages": [
        {
            "path": "pages/index/index",
            "style": {
                "navigationBarTitleText": "首页",
                "enablePullDownRefresh": true
            }
        }
    ],
    "globalStyle": {
        "navigationBarTextStyle": "black",
        "navigationBarTitleText": "uni-app",
        "navigationBarBackgroundColor": "#FFFFFF",
        "backgroundColor": "#F8F8F8"
    },
    "tabBar": {
        "color": "#7A7E83",
        "selectedColor": "#007AFF",
        "list": [{
            "pagePath": "pages/index/index",
            "text": "首页"
        }]
    }
}

// manifest.json - 应用配置
{
    "name": "my-app",
    "appid": "__UNI__XXXXXX",
    "description": "我的uni-app应用",
    "versionName": "1.0.0",
    "mp-weixin": {
        "appid": "微信小程序appid",
        "setting": {
            "urlCheck": false
        }
    }
}

实战案例:企业级电商多端应用

我们将构建一个完整的电商应用,包含首页、商品列表、商品详情、购物车、个人中心等模块。

1. 项目架构设计

// 项目结构设计
src/
├── api/              # 接口管理
│   ├── index.js     # 接口统一导出
│   ├── product.js   # 商品相关接口
│   └── user.js      # 用户相关接口
├── common/          # 公共资源
│   ├── css/         # 公共样式
│   ├── js/          # 工具函数
│   └── images/      # 公共图片
├── components/      # 公共组件
│   ├── product-card.vue    # 商品卡片
│   ├── search-bar.vue      # 搜索栏
│   └── tab-bar.vue         # 自定义tabbar
├── store/           # 状态管理
│   ├── index.js     # store主文件
│   ├── modules/     # 模块化store
│   └── types.js     # 类型定义
├── pages/           # 页面目录
└── utils/           # 工具类
    ├── request.js   # 网络请求封装
    ├── auth.js      # 认证相关
    └── utils.js     # 通用工具函数

2. 网络请求封装

// utils/request.js - 统一请求封装
const BASE_URL = 'https://api.example.com';

class Request {
    constructor() {
        this.interceptors = {
            request: null,
            response: null
        };
    }

    // 设置请求拦截器
    setRequestInterceptor(interceptor) {
        this.interceptors.request = interceptor;
    }

    // 设置响应拦截器
    setResponseInterceptor(interceptor) {
        this.interceptors.response = interceptor;
    }

    async request(url, options = {}) {
        const { method = 'GET', data = {}, header = {} } = options;
        
        // 请求拦截
        let requestConfig = { url: BASE_URL + url, method, data, header };
        if (this.interceptors.request) {
            requestConfig = await this.interceptors.request(requestConfig);
        }

        return new Promise((resolve, reject) => {
            uni.request({
                ...requestConfig,
                success: (response) => {
                    let responseData = response;
                    // 响应拦截
                    if (this.interceptors.response) {
                        responseData = this.interceptors.response(response);
                    }
                    resolve(responseData);
                },
                fail: (error) => {
                    reject(error);
                }
            });
        });
    }

    get(url, data = {}, header = {}) {
        return this.request(url, { method: 'GET', data, header });
    }

    post(url, data = {}, header = {}) {
        header['Content-Type'] = 'application/json';
        return this.request(url, { method: 'POST', data, header });
    }
}

// 创建请求实例
const http = new Request();

// 添加请求拦截器 - 添加token
http.setRequestInterceptor(async (config) => {
    const token = uni.getStorageSync('token');
    if (token) {
        config.header.Authorization = `Bearer ${token}`;
    }
    return config;
});

// 添加响应拦截器 - 统一错误处理
http.setResponseInterceptor((response) => {
    const { statusCode, data } = response;
    if (statusCode === 200) {
        return data;
    } else if (statusCode === 401) {
        // token过期,跳转到登录页
        uni.navigateTo({ url: '/pages/login/login' });
        return Promise.reject(new Error('请重新登录'));
    } else {
        return Promise.reject(new Error(data.message || '请求失败'));
    }
});

export default http;

3. 状态管理(Vuex)配置

// store/index.js - Vuex状态管理
import Vue from 'vue';
import Vuex from 'vuex';
import cart from './modules/cart';
import user from './modules/user';

Vue.use(Vuex);

const store = new Vuex.Store({
    modules: {
        cart,
        user
    },
    state: {
        isLoading: false
    },
    mutations: {
        SET_LOADING(state, isLoading) {
            state.isLoading = isLoading;
        }
    },
    actions: {
        setLoading({ commit }, isLoading) {
            commit('SET_LOADING', isLoading);
        }
    }
});

export default store;

// store/modules/cart.js - 购物车模块
const state = {
    cartItems: [],
    cartTotal: 0
};

const mutations = {
    ADD_TO_CART(state, product) {
        const existingItem = state.cartItems.find(item => item.id === product.id);
        if (existingItem) {
            existingItem.quantity += 1;
        } else {
            state.cartItems.push({ ...product, quantity: 1 });
        }
        state.cartTotal = state.cartItems.reduce((total, item) => total + item.price * item.quantity, 0);
    },
    REMOVE_FROM_CART(state, productId) {
        state.cartItems = state.cartItems.filter(item => item.id !== productId);
        state.cartTotal = state.cartItems.reduce((total, item) => total + item.price * item.quantity, 0);
    },
    CLEAR_CART(state) {
        state.cartItems = [];
        state.cartTotal = 0;
    }
};

const actions = {
    addToCart({ commit }, product) {
        commit('ADD_TO_CART', product);
        uni.showToast({
            title: '添加成功',
            icon: 'success'
        });
    },
    removeFromCart({ commit }, productId) {
        commit('REMOVE_FROM_CART', productId);
    },
    clearCart({ commit }) {
        commit('CLEAR_CART');
    }
};

export default {
    namespaced: true,
    state,
    mutations,
    actions
};

4. 首页组件开发

// pages/index/index.vue - 首页
<template>
    <view class="container">
        
        <search-bar @search="handleSearch" />
        
        
        <swiper class="swiper" indicator-dots autoplay circular>
            <swiper-item v-for="(banner, index) in banners" :key="index">
                <image :src="banner.image" mode="aspectFill" class="banner-image" @click="navigateTo(banner.url)" />
            </swiper-item>
        </swiper>
        
        
        <view class="category-grid">
            <view v-for="category in categories" :key="category.id" class="category-item" @click="navigateToCategory(category.id)">
                <image :src="category.icon" mode="aspectFit" class="category-icon" />
                <text class="category-name">{{ category.name }}</text>
            </view>
        </view>
        
        
        <view class="section">
            <view class="section-header">
                <text class="section-title">热门推荐</text>
                <text class="section-more" @click="navigateTo('/pages/product/list')">查看更多 ></text>
            </view>
            <view class="product-list">
                <product-card 
                    v-for="product in recommendedProducts" 
                    :key="product.id" 
                    :product="product" 
                    @add-to-cart="addToCart"
                />
            </view>
        </view>
    </view>
</template>

<script>
import { mapActions } from 'vuex';
import SearchBar from '@/components/search-bar.vue';
import ProductCard from '@/components/product-card.vue';

export default {
    components: {
        SearchBar,
        ProductCard
    },
    data() {
        return {
            banners: [],
            categories: [],
            recommendedProducts: []
        };
    },
    async onLoad() {
        await this.loadHomeData();
    },
    onPullDownRefresh() {
        this.loadHomeData().finally(() => {
            uni.stopPullDownRefresh();
        });
    },
    methods: {
        ...mapActions('cart', ['addToCart']),
        
        async loadHomeData() {
            try {
                const [bannerRes, categoryRes, productRes] = await Promise.all([
                    this.$http.get('/banners'),
                    this.$http.get('/categories'),
                    this.$http.get('/products/recommended')
                ]);
                
                this.banners = bannerRes.data;
                this.categories = categoryRes.data;
                this.recommendedProducts = productRes.data;
            } catch (error) {
                uni.showToast({
                    title: '加载失败',
                    icon: 'error'
                });
            }
        },
        
        handleSearch(keyword) {
            uni.navigateTo({
                url: `/pages/product/list?keyword=${keyword}`
            });
        },
        
        navigateToCategory(categoryId) {
            uni.navigateTo({
                url: `/pages/product/list?category_id=${categoryId}`
            });
        },
        
        navigateTo(url) {
            if (url.startsWith('http')) {
                // 处理外部链接
                uni.navigateTo({
                    url: `/pages/webview/webview?url=${encodeURIComponent(url)}`
                });
            } else {
                uni.navigateTo({ url });
            }
        }
    }
};
</script>

<style lang="scss">
.container {
    padding: 20rpx;
}

.swiper {
    height: 300rpx;
    margin-bottom: 30rpx;
}

.banner-image {
    width: 100%;
    height: 100%;
    border-radius: 16rpx;
}

.category-grid {
    display: grid;
    grid-template-columns: repeat(5, 1fr);
    gap: 20rpx;
    margin-bottom: 40rpx;
}

.category-item {
    display: flex;
    flex-direction: column;
    align-items: center;
}

.category-icon {
    width: 80rpx;
    height: 80rpx;
    margin-bottom: 10rpx;
}

.category-name {
    font-size: 24rpx;
    color: #666;
}

.section-header {
    display: flex;
    justify-content: space-between;
    align-items: center;
    margin-bottom: 20rpx;
}

.section-title {
    font-size: 32rpx;
    font-weight: bold;
}

.section-more {
    font-size: 24rpx;
    color: #999;
}

.product-list {
    display: grid;
    grid-template-columns: repeat(2, 1fr);
    gap: 20rpx;
}
</style>

5. 商品卡片组件

// components/product-card.vue
<template>
    <view class="product-card" @click="navigateToDetail">
        <image :src="product.image" mode="aspectFill" class="product-image" />
        <view class="product-info">
            <text class="product-name">{{ product.name }}</text>
            <text class="product-desc">{{ product.description }}</text>
            <view class="product-footer">
                <text class="product-price">¥{{ product.price }}</text>
                <view class="action-buttons">
                    <button class="add-cart-btn" @click.stop="handleAddToCart">
                        <text class="iconfont icon-cart"></text>
                    </button>
                </view>
            </view>
        </view>
    </view>
</template>

<script>
export default {
    props: {
        product: {
            type: Object,
            required: true
        }
    },
    methods: {
        navigateToDetail() {
            uni.navigateTo({
                url: `/pages/product/detail?id=${this.product.id}`
            });
        },
        
        handleAddToCart() {
            this.$emit('add-to-cart', this.product);
        }
    }
};
</script>

<style lang="scss">
.product-card {
    background: #fff;
    border-radius: 16rpx;
    overflow: hidden;
    box-shadow: 0 4rpx 16rpx rgba(0, 0, 0, 0.1);
}

.product-image {
    width: 100%;
    height: 300rpx;
}

.product-info {
    padding: 20rpx;
}

.product-name {
    font-size: 28rpx;
    font-weight: bold;
    display: block;
    margin-bottom: 10rpx;
}

.product-desc {
    font-size: 24rpx;
    color: #666;
    display: block;
    margin-bottom: 20rpx;
    overflow: hidden;
    text-overflow: ellipsis;
    display: -webkit-box;
    -webkit-line-clamp: 2;
    -webkit-box-orient: vertical;
}

.product-footer {
    display: flex;
    justify-content: space-between;
    align-items: center;
}

.product-price {
    font-size: 32rpx;
    color: #e64340;
    font-weight: bold;
}

.add-cart-btn {
    background: #e64340;
    width: 60rpx;
    height: 60rpx;
    border-radius: 50%;
    display: flex;
    align-items: center;
    justify-content: center;
    padding: 0;
    
    .iconfont {
        color: #fff;
        font-size: 32rpx;
    }
}
</style>

多端适配与优化策略

1. 条件编译处理平台差异

// 使用条件编译处理不同平台逻辑
export default {
    methods: {
        shareContent() {
            // #ifdef MP-WEIXIN
            wx.shareAppMessage({
                title: '分享标题',
                path: '/pages/index/index'
            });
            // #endif
            
            // #ifdef APP-PLUS
            plus.share.sendWithSystem({
                content: '分享内容',
                href: 'https://example.com'
            });
            // #endif
            
            // #ifdef H5
            if (navigator.share) {
                navigator.share({
                    title: '分享标题',
                    url: window.location.href
                });
            }
            // #endif
        },
        
        // 平台特定的样式处理
        getPlatformStyle() {
            let style = {};
            // #ifdef MP-WEIXIN
            style.paddingTop = '44px'; // 微信小程序胶囊按钮高度
            // #endif
            
            // #ifdef APP-PLUS
            style.paddingTop = 'var(--status-bar-height)';
            // #endif
            
            return style;
        }
    }
};

2. 性能优化策略

// 图片懒加载优化
<image 
    :src="item.image" 
    mode="aspectFill" 
    lazy-load 
    :fade-show="false"
/>

// 列表渲染优化
<view 
    v-for="(item, index) in longList" 
    :key="item.id"
    :render-when="index  {
        this.setData(data);
    }, 100);
}

3. 用户体验优化

// 页面加载状态管理
export default {
    data() {
        return {
            isLoading: true,
            isError: false
        };
    },
    async onLoad() {
        await this.loadData();
    },
    methods: {
        async loadData() {
            this.isLoading = true;
            this.isError = false;
            
            try {
                await this.fetchData();
            } catch (error) {
                this.isError = true;
                console.error('加载失败:', error);
            } finally {
                this.isLoading = false;
            }
        },
        
        // 骨架屏渲染
        renderSkeleton() {
            if (this.isLoading) {
                return (
                    <view class="skeleton">
                        <view class="skeleton-banner"></view>
                        <view class="skeleton-item" v-for="i in 6" :key="i"></view>
                    </view>
                );
            }
            
            if (this.isError) {
                return (
                    <view class="error-view">
                        <text>加载失败</text>
                        <button @click="loadData">重新加载</button>
                    </view>
                );
            }
            
            return this.renderContent();
        }
    }
};

打包发布与部署

1. 多平台打包配置

// package.json 脚本配置
{
    "scripts": {
        "dev:mp-weixin": "cross-env NODE_ENV=development uni-build --platform mp-weixin",
        "build:mp-weixin": "cross-env NODE_ENV=production uni-build --platform mp-weixin",
        "build:app": "cross-env NODE_ENV=production uni-build --platform app-plus",
        "build:h5": "cross-env NODE_ENV=production uni-build --platform h5"
    }
}

// 环境变量配置
const isProduction = process.env.NODE_ENV === 'production';

// API地址配置
export const API_BASE_URL = isProduction 
    ? 'https://api.production.com' 
    : 'https://api.development.com';

// 微信小程序配置
// manifest.json → mp-weixin
{
    "appid": "wx1234567890abcdef",
    "setting": {
        "urlCheck": false,
        "es6": true,
        "postcss": true
    },
    "usingComponents": true
}

2. 自动化部署流程

// GitHub Actions 自动化部署示例
name: Deploy UniApp

on:
  push:
    branches: [ main ]

jobs:
  deploy:
    runs-on: ubuntu-latest
    
    steps:
    - uses: actions/checkout@v2
    
    - name: Setup Node.js
      uses: actions/setup-node@v2
      with:
        node-version: '16'
        
    - name: Install dependencies
      run: npm install
      
    - name: Build for WeChat Mini Program
      run: npm run build:mp-weixin
      
    - name: Deploy to WeChat Mini Program
      uses: wechat-miniprogram/ci-action@v1
      with:
        appid: ${{ secrets.WX_APPID }}
        privateKey: ${{ secrets.WX_PRIVATE_KEY }}
        projectPath: dist/build/mp-weixin
        version: ${{ github.sha }}
        desc: ${{ github.event.head_commit.message }}

总结

UniApp作为跨平台开发的首选框架,通过一套代码实现多端发布,极大地提高了开发效率和代码复用率。本文通过一个完整的电商应用案例,详细介绍了UniApp的核心概念、项目架构、组件开发、状态管理、性能优化和部署发布的全流程。

关键开发实践:

  • 合理的项目结构和代码组织
  • 统一的网络请求封装和错误处理
  • 有效的状态管理方案
  • 组件化开发提高代码复用
  • 多端适配和条件编译
  • 性能优化和用户体验提升
  • 自动化部署流程

掌握UniApp开发技术,能够帮助开发者快速构建高质量的多端应用,适应现代移动开发的多样化需求。

UniApp跨平台开发实战:从零构建企业级多端应用完整指南
收藏 (0) 打赏

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

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

淘吗网 uniapp UniApp跨平台开发实战:从零构建企业级多端应用完整指南 https://www.taomawang.com/web/uniapp/972.html

常见问题

相关文章

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

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