写点什么

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

作者:陇锦
  • 2023-07-25
    北京
  • 本文字数:5411 字

    阅读完需:约 18 分钟

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

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

2022年的时候,公司为了快速完成产品并上线,所以选用微信小程序为载体;由于后期还是打算开发App;虽然公司有iosAndroid,但是如果能一套代码打包多端,一定程度上可以解决成本。前端技术栈也是vue,在考察选择了uni-app。后来多个小程序项目都采用了uni-app开发,积累了一定的经验以及封装了较多业务组件,这里就分享一下uni-app项目的整体架构、方法封装组件库选择以及注意事项。全文代码都会放到github,先赞后看,月入百万!

创建项目

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



⚠️需要注意的是,一定要根据项目需求来选择项目的创建方式;如果只是单独的开发小程序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-devnpm 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中,文章末尾会提供github链接方便查看。如果项目较大,建议把方法根据功能定义不同的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    })  }}
复制代码


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


/** * 缓存操作 * @param {String} val */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)                            }                        })                    }                })            }        }    })}
复制代码


更多函数就不在文章中展示了,已经放到/src/utils/utils,js里面,具体可以到 github 查看。

请求封装

为了减少在页面中的请求代码,所以我们要对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) =>{ header.token = getStorageSync('token') || '' return new Promise((reslove, 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{ reslove(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.js


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 info = await info() console.log('用户信息==', info) } }}
复制代码

自定义tabBar

uni-app或者小程序基本避不开这个话题了,很多情况下,官方提供的tabBar方案并不能满足产品需求/ui 要求,官方也提供了自定义tabBar的方案,但此方案有很多弊端,比如:切换时候会tabBar会有明显的闪动。可以参考之前写的文章小程序自定义TabBar 如何实现“keep-alive”,文章中是原生小程序,但思路在uni-app中同样适用,如果感兴趣,可以评论区提问。

版本切换

很多场景下,需要根据不同的环境去切换不同的请求域名、APPID等字段,这时候就需要通过环境变量来进行区分。下面案例我们就分为三个环境:开发环境(dev)、测试环境(test)、生产环境(prod)。

建立env文件

在项目根目录建立下面三个文件并写入内容(常量名要以 VUE 开头命名):


.env.dev(开发环境)


VUE_APP_MODE=devVUE_APP_ID=wxbb53ae105735a06bVUE_APP_BASE=https://www.baidu.dev.com
复制代码


.env.test(测试环境)


VUE_APP_MODE=testVUE_APP_ID=wxbb53ae105735a06cVUE_APP_BASE=https://www.baidu.test.com
复制代码


.env.prod(生产环境)


VUE_APP_MODE=wxbb53ae105735a06dVUE_APP_ID=prodVUE_APP_BASE=https://www.baidu.prod.com
复制代码

修改package.json文件

"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 --watch --mode prod"},
复制代码


然后执行


npm run dev:mp-weixin
复制代码


/src/pages/index/index.vue下,打印:


onLoad() {    console.log(process.env.VUE_APP_MODE, '====VUE_APP_BASE')     console.log(process.env.VUE_APP_BASE, '====VUE_APP_BASE')},
复制代码


此时输出结果就是


// dev ====VUE_APP_BASE// https://www.baidu.dev.com ====VUE_APP_BASE
复制代码

动态修改 appid

如果同一套代码,需要打包生成多个小程序,就需要动态修改appid了;文章开头说过 appid 在/src/manifest.json文件中配置,但json文件又不能直接写变量,这时候就可以参考官方 提出的解决方案:建立vue.config.js文件,具体操作如下。


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


// 读取 manifest.json ,修改后重新写入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-ui以及uView UI,大部分组件还是比较好用的,如果做中大型项目以及 UI 要求较高的情况下,还是比较推荐自己搭一套组件库,方便扩展以及维护。

结尾

关于uni-app项目的起步工作就到这里了,后面有机会写一套完整的uni搭建电商小程序项目,记得关注。代码已经提交到github,如果对你有帮助,记得点个star!

发布于: 23 小时前阅读数: 16
用户头像

陇锦

关注

还未添加个人签名 2019-11-11 加入

还未添加个人简介

评论

发布
暂无评论
uni-app开发小程序:项目架构以及经验分享_小程序_陇锦_InfoQ写作社区