uni-app開發(fā)小程序:項目架構(gòu)以及經(jīng)驗分享

uni-app開發(fā)小程序:項目架構(gòu)以及經(jīng)驗分享

2022年的時候,公司為了快速完成產(chǎn)品并上線,所以選用微信小程序為載體;由于后期還是打算開發(fā)App;雖然公司有iosAndroid踪少,但是如果能一套代碼打包多端,一定程度上可以解決成本糠涛。前端技術(shù)棧也是vue援奢,在考察選擇了uni-app。后來多個小程序項目都采用了uni-app開發(fā)忍捡,積累了一定的經(jīng)驗以及封裝了較多業(yè)務(wù)組件集漾,這里就分享一下uni-app項目的整體架構(gòu)、方法封裝組件庫選擇以及注意事項砸脊。全文代碼都會放到github具篇,先贊后看,月入百萬凌埂!

創(chuàng)建項目

uni-app提供了兩種創(chuàng)建項目的方式:

  • 1.通過HBuilderX可視化工具創(chuàng)建
  • 2.通過vue-cli命令創(chuàng)建

??需要注意的是驱显,一定要根據(jù)項目需求來選擇項目的創(chuàng)建方式;如果只是單獨的開發(fā)小程序App,且開發(fā)環(huán)境單一埃疫,可以使用HBuilderX可視化工具創(chuàng)建伏恐。如果多端開發(fā),以及同一套代碼可能會打包生成多個小程序建議使用vue-cli進行創(chuàng)建熔恢,不然后期想搞自動化構(gòu)建以及按指定條件進行編譯比較痛苦脐湾。關(guān)于按條件編譯臭笆,文章后面會有詳細說明叙淌。

使用vue-cli安裝和運行:

1.全局安裝 vue-cli

npm install -g @vue/cli

2.創(chuàng)建uni-app

vue create -p dcloudio/uni-preset-vue 項目名稱

3.進入項目文件夾

cd 項目名稱

4.運行項目,如果是已微信小程序為主愁铺,可以在package.json中的命令改為:

"scripts": {
    "serve": "npm run dev:mp-weixin"
}

然后執(zhí)行

npm run serve

使用cli創(chuàng)建項目默認不帶css預(yù)編譯鹰霍,需要手動安裝一下,這里已sass為例:

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

整體項目架構(gòu)

通過HBuilderX或者vue-cli創(chuàng)建的項目茵乱,目錄結(jié)構(gòu)有稍許不同茂洒,但基本沒什么差異,這里就按vue-cli創(chuàng)建的項目為例瓶竭,整體架構(gòu)配置如下:

    ├──dist 編譯后的文件路徑
    ├──package.json 配置項
    ├──src 核心內(nèi)容
        ├──api 項目接口
        ├──components 全局公共組件
        ├──config 項目配置文件
        ├──pages 主包
        ├──static 全局靜態(tài)資源
        ├──store vuex
        ├──mixins 全局混入
        ├──utils 公共方法
        ├──App.vue 應(yīng)用配置督勺,配置App全局樣式以及監(jiān)聽
        ├──main.js Vue初始化入口文件
        ├──manifest.json 配置應(yīng)用名稱、appid等打包信息
        ├──pages.json 配置頁面路由斤贰、導(dǎo)航條智哀、選項卡等頁面類信息
        └──uni.scss 全局樣式

封裝方法

工欲善其事,必先利其器荧恍。在開發(fā)之前瓷叫,我們可以把一些全局通用的方法進行封裝,以及把uni-app提供的api進行二次封裝送巡,方便使用摹菠。全局的公共方法我們都會放到/src/utils文件夾下。

封裝常用方法

下面這些方法都放在/src/utils/utils.js中骗爆,文章末尾會提供github鏈接方便查看次氨。如果項目較大,建議把方法根據(jù)功能定義不同的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
        })
    }
}

緩存操作(設(shè)置/獲取/刪除/清空)

/**
 * 緩存操作
 * @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()
}

頁面跳轉(zhuǎn)

/**
 * 頁面跳轉(zhuǎn)
 * @param {'navigateTo' | 'redirectTo' | 'reLaunch' | 'switchTab' | 'navigateBack' | number } url  轉(zhuǎn)跳路徑
 * @param {String} params 跳轉(zhuǎn)時攜帶的參數(shù)
 * @param {String} type 轉(zhuǎn)跳方式
 **/
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)
    }
}

圖片預(yù)覽

/**
 * 預(yù)覽圖片
 * @param {Array} urls 圖片鏈接
 */
export function previewImage(urls, itemList = ['發(fā)送給朋友', '保存圖片', '收藏']) {
    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: '需要您授權(quán)保存相冊',
                    showCancel: false,
                    success: (modalSuccess) => {
                        uni.openSetting({
                            success(settingdata) {
                                if (settingdata.authSetting['scope.writePhotosAlbum']) {
                                    uni.showModal({
                                        title: '提示',
                                        content: '獲取權(quán)限成功,再次點擊圖片即可保存',
                                        showCancel: false
                                    })
                                } else {
                                    uni.showModal({
                                        title: '提示',
                                        content: '獲取權(quán)限失敗糟需,將無法保存到相冊哦~',
                                        showCancel: false
                                    })
                                }
                            },
                            fail(failData) {
                                console.log('failData', failData)
                            }
                        })
                    }
                })
            }
        }
    })
}

更多函數(shù)就不在文章中展示了,已經(jīng)放到/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){
                    // 這里根據(jù)自己的業(yè)務(wù)邏輯去調(diào)整
                    if(res.resultCode == 'PA-G998'){
                        clearStorageSync()
                        useRouter('/pages/login/index', 'reLaunch')
                    }else{
                        reslove(res.data)
                    }
                }else{
                    toast('網(wǎng)絡(luò)連接失敗,請稍后重試')
                    reject(res)
                }
            },
            fail: (msg) => {
                uni.hideLoading()
                toast('網(wǎng)絡(luò)連接失敗,請稍后重試')
                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文件夾下按業(yè)務(wù)模塊建立對應(yīng)的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方案并不能滿足產(chǎn)品需求/ui要求,官方也提供了自定義tabBar的方案尽楔,但此方案有很多弊端投储,比如:切換時候會tabBar會有明顯的閃動±觯可以參考之前寫的文章小程序自定義TabBar 如何實現(xiàn)“keep-alive”玛荞,文章中是原生小程序,但思路在uni-app中同樣適用呕寝,如果感興趣勋眯,可以評論區(qū)提問。

版本切換

很多場景下下梢,需要根據(jù)不同的環(huán)境去切換不同的請求域名客蹋、APPID等字段,這時候就需要通過環(huán)境變量來進行區(qū)分孽江。下面案例我們就分為三個環(huán)境:開發(fā)環(huán)境(dev)讶坯、測試環(huán)境(test)、生產(chǎn)環(huán)境(prod)竟坛。

建立env文件

在項目根目錄建立下面三個文件并寫入內(nèi)容(常量名要以VUE開頭命名):

.env.dev(開發(fā)環(huán)境)

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

.env.test(測試環(huán)境)

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

.env.prod(生產(chǎn)環(huán)境)

VUE_APP_MODE=wxbb53ae105735a06d
VUE_APP_ID=prod
VUE_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"
},

然后執(zhí)行

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')
},

此時輸出結(jié)果就是

// dev ====VUE_APP_BASE
// https://www.baidu.dev.com ====VUE_APP_BASE

動態(tài)修改appid

如果同一套代碼担汤,需要打包生成多個小程序涎跨,就需要動態(tài)修改appid了;文章開頭說過appid在/src/manifest.json文件中配置崭歧,但json文件又不能直接寫變量隅很,這時候就可以參考官方 提出的解決方案:建立vue.config.js文件,具體操作如下率碾。

在根目錄下建立vue.config.js文件寫入以下內(nèi)容:

// 讀取 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')
}
// 讀取環(huán)境變量內(nèi)容
replaceManifest('mp-weixin.appid', `"${process.env.VUE_APP_ID}"`)

fs.writeFileSync(manifestPath, Manifest, {
  flag: 'w'
})

如果是通過HBuilderX可視化工具創(chuàng)建的項目,則無法去自動根據(jù)環(huán)境去修改appid所宰,只能去手動修改绒尊。

組件庫

uni-app最受歡迎的可能就是插件市場了,插件市場提供了很多優(yōu)秀的插件/組件庫供我們選擇仔粥,比較火的就是自家的uni-ui以及uView UI婴谱,大部分組件還是比較好用的蟹但,如果做中大型項目以及UI要求較高的情況下,還是比較推薦自己搭一套組件庫谭羔,方便擴展以及維護华糖。

結(jié)尾

關(guān)于uni-app項目的起步工作就到這里了,后面有機會寫一套完整的uni搭建電商小程序項目瘟裸,記得關(guān)注客叉。代碼已經(jīng)提交到github,如果對你有幫助话告,記得點個star!

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末兼搏,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子超棺,更是在濱河造成了極大的恐慌向族,老刑警劉巖呵燕,帶你破解...
    沈念sama閱讀 218,122評論 6 505
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件棠绘,死亡現(xiàn)場離奇詭異,居然都是意外死亡再扭,警方通過查閱死者的電腦和手機氧苍,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,070評論 3 395
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來泛范,“玉大人让虐,你說我怎么就攤上這事“盏矗” “怎么了赡突?”我有些...
    開封第一講書人閱讀 164,491評論 0 354
  • 文/不壞的土叔 我叫張陵,是天一觀的道長区赵。 經(jīng)常有香客問我惭缰,道長,這世上最難降的妖魔是什么笼才? 我笑而不...
    開封第一講書人閱讀 58,636評論 1 293
  • 正文 為了忘掉前任漱受,我火速辦了婚禮,結(jié)果婚禮上骡送,老公的妹妹穿的比我還像新娘昂羡。我一直安慰自己,他們只是感情好摔踱,可當(dāng)我...
    茶點故事閱讀 67,676評論 6 392
  • 文/花漫 我一把揭開白布虐先。 她就那樣靜靜地躺著,像睡著了一般派敷。 火紅的嫁衣襯著肌膚如雪蛹批。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,541評論 1 305
  • 那天,我揣著相機與錄音般眉,去河邊找鬼了赵。 笑死,一個胖子當(dāng)著我的面吹牛甸赃,可吹牛的內(nèi)容都是我干的柿汛。 我是一名探鬼主播,決...
    沈念sama閱讀 40,292評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼埠对,長吁一口氣:“原來是場噩夢啊……” “哼络断!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起项玛,我...
    開封第一講書人閱讀 39,211評論 0 276
  • 序言:老撾萬榮一對情侶失蹤貌笨,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后襟沮,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體锥惋,經(jīng)...
    沈念sama閱讀 45,655評論 1 314
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,846評論 3 336
  • 正文 我和宋清朗相戀三年开伏,在試婚紗的時候發(fā)現(xiàn)自己被綠了膀跌。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 39,965評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡固灵,死狀恐怖捅伤,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情巫玻,我是刑警寧澤丛忆,帶...
    沈念sama閱讀 35,684評論 5 347
  • 正文 年R本政府宣布,位于F島的核電站仍秤,受9級特大地震影響熄诡,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜徒扶,卻給世界環(huán)境...
    茶點故事閱讀 41,295評論 3 329
  • 文/蒙蒙 一粮彤、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧姜骡,春花似錦导坟、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,894評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至康栈,卻和暖如春递递,著一層夾襖步出監(jiān)牢的瞬間喷橙,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,012評論 1 269
  • 我被黑心中介騙來泰國打工登舞, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留贰逾,地道東北人。 一個月前我還...
    沈念sama閱讀 48,126評論 3 370
  • 正文 我出身青樓菠秒,卻偏偏與公主長得像疙剑,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子践叠,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 44,914評論 2 355

推薦閱讀更多精彩內(nèi)容