uni-app开发小程序:项目架构以及经验分享

Viewed 0

uni-app开发小程序:项目架构以及经验分享

在2022年,为了快速开发并上线产品,公司选择了微信小程序作为载体。考虑到未来可能开发App,且前端技术栈基于Vue,我们考察后选择了uni-app,以实现一套代码多端打包。多个uni-app项目积累的经验促使我们分享其整体架构、方法封装、组件库选择及注意事项。

创建项目

uni-app提供了两种创建项目的方式:

  1. 通过HBuilderX可视化工具创建。
  2. 通过vue-cli命令创建。

需要注意的是,应根据项目需求选择创建方式。如果仅开发单一端(如小程序或App)且开发环境简单,可使用HBuilderX。如需多端开发或同一套代码打包多个小程序,建议使用vue-cli,以便于自动化构建和条件编译。

使用vue-cli安装和运行步骤如下:

  1. 全局安装vue-cli:
    npm install -g @vue/cli
    
  2. 创建uni-app项目:
    vue create -p dcloudio/uni-preset-vue 项目名称
    
  3. 进入项目文件夹:
    cd 项目名称
    
  4. 运行项目:若以微信小程序为主,可在package.json中修改命令为:
    "scripts": {
        "serve": "npm run dev:mp-weixin"
    }
    
    然后执行:
    npm run serve
    

使用cli创建的项目默认不带CSS预编译,需手动安装。以sass为例:

npm i sass --save-dev
npm i sass-loader --save-dev

整体项目架构

通过HBuilderX或vue-cli创建的项目目录结构略有差异,但基本一致。以vue-cli创建的项目为例,整体架构配置如下:

├──dist 编译后的文件路径
├──package.json 配置项
├──src 核心内容
    ├──api 项目接口
    ├──components 全局公共组件
    ├──config 项目配置文件
    ├──pages 主包
    ├──static 全局静态资源
    ├──store vuex
    ├──mixins 全局混入
    ├──utils 公共方法
    ├──App.vue 应用配置,配置App全局样式以及监听
    ├──main.js Vue初始化入口文件
    ├──manifest.json 配置应用名称、appid等打包信息
    ├──pages.json 配置页面路由、导航条、选项卡等页面类信息
    └──uni.scss 全局样式

封装方法

在开发前,建议封装全局通用方法,并对uni-app的API进行二次封装,以提高效率。公共方法通常放在/src/utils文件夹下。

封装常用方法

以下方法放在/src/utils/utils.js中。若项目较大,可按功能分文件定义。

小程序Toast提示:

/**
 * 提示方法
 * @param {String} title 提示文字
 * @param {String}  icon icon图片
 * @param {Number}  duration 提示时间
 */
export function toast(title, icon = 'none', duration = 1500) {
    if(title) {
        uni.showToast({
            title,
            icon,
            duration
        })
    }
}

缓存操作(设置/获取/删除/清空):

export function setStorageSync(key, data) {
    uni.setStorageSync(key, data)
}

export function getStorageSync(key) {
    return uni.getStorageSync(key)
}

export function removeStorageSync(key) {
    return uni.removeStorageSync(key)
}

export function clearStorageSync() {
    return uni.clearStorageSync()
}

页面跳转:

/**
 * 页面跳转
 * @param {'navigateTo' | 'redirectTo' | 'reLaunch' | 'switchTab' | 'navigateBack' | number } url  转跳路径
 * @param {String} params 跳转时携带的参数
 * @param {String} type 转跳方式
 **/
export function useRouter(url, params = {}, type = 'navigateTo') {
    try {
        if (Object.keys(params).length) url = `${url}?data=${encodeURIComponent(JSON.stringify(params))}`
        if (type === 'navigateBack') {
            uni[type]({ delta: url })
        } else {
            uni[type]({ url })
        }
    } catch (error) {
        console.error(error)
    }
}

图片预览:

/**
 * 预览图片
 * @param {Array} urls 图片链接
 */
export function previewImage(urls, itemList = ['发送给朋友', '保存图片', '收藏']) {
    uni.previewImage({
        urls,
        longPressActions: {
            itemList,
            fail: function (error) {
                console.error(error,'===previewImage')
            }
        }
    })
}

图片下载:

/**
 * 保存图片到本地
 * @param {String} filePath 图片临时路径
 **/
export function saveImage(filePath) {
    if (!filePath) return false
    uni.saveImageToPhotosAlbum({
        filePath,
        success: (res) => {
            toast('图片保存成功', 'success')
        },
        fail: (err) => {
            if (err.errMsg === 'saveImageToPhotosAlbum:fail:auth denied' || err.errMsg === 'saveImageToPhotosAlbum:fail auth deny') {
                uni.showModal({
                    title: '提示',
                    content: '需要您授权保存相册',
                    showCancel: false,
                    success: (modalSuccess) => {
                        uni.openSetting({
                            success(settingdata) {
                                if (settingdata.authSetting['scope.writePhotosAlbum']) {
                                    uni.showModal({
                                        title: '提示',
                                        content: '获取权限成功,再次点击图片即可保存',
                                        showCancel: false
                                    })
                                } else {
                                    uni.showModal({
                                        title: '提示',
                                        content: '获取权限失败,将无法保存到相册哦~',
                                        showCancel: false
                                    })
                                }
                            },
                            fail(failData) {
                                console.log('failData', failData)
                            }
                        })
                    }
                })
            }
        }
    })
}

更多函数可在工具文件中查看,不再逐一展示。

请求封装

为简化页面中的请求代码,需对uni-app的请求进行二次封装。在/src/utils下建立request.js,代码如下:

import {toast, clearStorageSync, getStorageSync, useRouter} from './utils'
import {BASE_URL} from '@/config/index'

const baseRequest = async (url, method, data, loading = true) =>{
    let header = {}
    header.token = getStorageSync('token') || ''
    return new Promise((resolve, reject) => {
        loading && uni.showLoading({title: 'loading'})
        uni.request({
            url: BASE_URL + url,
            method: method || 'GET',
            header: header,
            timeout: 10000,
            data: data || {},
            success: (successData) => {
                const res = successData.data
                uni.hideLoading()
                if(successData.statusCode == 200){
                    // 根据业务逻辑调整
                    if(res.resultCode == 'PA-G998'){
                        clearStorageSync()
                        useRouter('/pages/login/index', 'reLaunch')
                    }else{
                        resolve(res.data)
                    }
                }else{
                    toast('网络连接失败,请稍后重试')
                    reject(res)
                }
            },
            fail: (msg) => {
                uni.hideLoading()
                toast('网络连接失败,请稍后重试')
                reject(msg)
            }
        })
    })
}

const request = {};
['options', 'get', 'post', 'put', 'head', 'delete', 'trace', 'connect'].forEach((method) => {
    request[method] = (api, data, loading) => baseRequest(api, method, data, loading)
})

export default request

封装后,在/src/api文件夹下按业务模块建立API文件。例如,获取用户信息接口:在/src/api下建立user.js,引入request:

import request from '@/utils/request'

//个人信息
export const info = data => request.post('/v1/api/info', data)

在页面中使用:

import {info} from '@/api/user.js'

export default {
    methods: {
        async getUserinfo() {
            let userInfo = await info()
            console.log('用户信息==', userInfo)
        }
    }
}

自定义tabBar

官方提供的tabBar可能无法满足UI需求,uni-app支持自定义tabBar,但切换时可能出现闪动。可参考相关思路实现,例如通过页面管理和状态控制来优化体验。

版本切换

根据环境切换请求域名、APPID等字段,需通过环境变量区分。通常分为开发环境(dev)、测试环境(test)和生产环境(prod)。

建立env文件

在项目根目录建立以下文件:

.env.dev(开发环境):

VUE_APP_MODE=dev
VUE_APP_ID=wxbb53ae105735a06b
VUE_APP_BASE=https://www.baidu.dev.com

.env.test(测试环境):

VUE_APP_MODE=test
VUE_APP_ID=wxbb53ae105735a06c
VUE_APP_BASE=https://www.baidu.test.com

.env.prod(生产环境):

VUE_APP_MODE=prod
VUE_APP_ID=wxbb53ae105735a06d
VUE_APP_BASE=https://www.baidu.prod.com

修改package.json文件

在scripts中添加模式参数:

"scripts": {
    "dev:mp-weixin": "cross-env UNI_PLATFORM=mp-weixin vue-cli-service uni-build --watch --mode dev",
    "build:mp-weixin": "cross-env UNI_PLATFORM=mp-weixin vue-cli-service uni-build --mode prod"
}

执行npm run dev:mp-weixin后,在页面中可通过process.env.VUE_APP_MODEprocess.env.VUE_APP_BASE获取环境变量。

动态修改appid

若同一套代码需打包多个小程序,需动态修改appid。appid在/src/manifest.json中配置,但JSON文件不支持变量,可通过建立vue.config.js文件实现。

在根目录下建立vue.config.js,写入以下内容:

const fs = require('fs')
const manifestPath = './src/manifest.json'
let Manifest = fs.readFileSync(manifestPath, { encoding: 'utf-8' })

function replaceManifest(path, value) {
    const arr = path.split('.')
    const len = arr.length
    const lastItem = arr[len - 1]
    let i = 0
    let ManifestArr = Manifest.split(/\n/)
    for (let index = 0; index < ManifestArr.length; index++) {
        const item = ManifestArr[index]
        if (new RegExp(`"${arr[i]}"`).test(item)) ++i
        if (i === len) {
            const hasComma = /,/.test(item)
            ManifestArr[index] = item.replace(
                new RegExp(`"${lastItem}"[\\s\\S]*:[\\s\\S]*`),
                `"${lastItem}": ${value}${hasComma ? ',' : ''}`
            )
            break
        }
    }
    Manifest = ManifestArr.join('\n')
}

replaceManifest('mp-weixin.appid', `"${process.env.VUE_APP_ID}"`)
fs.writeFileSync(manifestPath, Manifest, { flag: 'w' })

通过HBuilderX创建的项目需手动修改appid。

组件库

uni-app插件市场提供了丰富组件,如uni-uiuView UI。对于中大型项目或UI要求较高的情况,建议自建组件库以便扩展和维护。

结尾

以上分享了uni-app项目的起步工作,包括项目创建、架构设计、方法封装、环境配置等。这些实践有助于提升开发效率和项目可维护性。未来可进一步探索完整项目搭建。

0 Answers