1. 介紹
uni-app
是一個使用 Vue.js 開發所有前端應用的框架,開發者編寫一套代碼,可發布到iOS、Android、Web(響應式)、以及各種小程序(微信/支付寶/百度/頭條/飛書/QQ/快手/釘釘/淘寶)、快應用等多個平臺。
uni-app
在手,做啥都不愁。即使不跨端,uni-app
也是更好的小程序開發框架(詳見)、更好的App跨平臺框架、更方便的H5開發框架。不管領導安排什么樣的項目,你都可以快速交付,不需要轉換開發思維、不需要更改開發習慣。
2. 環境搭建
2.1 安裝編輯器HBuilder X (APP開發版)
下載地址 通用前端開發工具,為uniapp做了特別強化
插件安裝(工具 -> 插件安裝 -> 選擇插件下載
)
sass
git
-
npm
image.png
2.2 安裝微信開發者工具
3. 使用HBuilderX初始化項目
文件 -> 新建 -> uniapp項目
微信開發者工具端口開放 設置 -> 安全設置 -> 端口開啟
4. 項目目錄及作用
┌─components 符合vue組件規范的uni-app組件目錄
│ └─comp-a.vue 可復用的組件
├─pages 業務頁面文件存放的目錄
│ ├─index
│ │ └─index.vue index頁面
│ └─list
│ └─list.vue list頁面
├─static 存放應用引用的本地靜態資源(如圖片、視頻等)的目錄,注意: 靜態資源只能存放于此
├─uni_modules 存放uni_module規范的插件
├─wxcomponents 存放小程序組件的目錄
├─main.js Vue初始化入口文件
├─App.vue 應用配置,用來配置App全局樣式以及監聽 應用生命周期
├─manifest.json 配置應用名稱、appid、logo、版本等打包信息
└─pages.json 配置頁面路由、導航條、選項卡等頁面類信息
pages: 頁面存放目錄
static: 靜態資源目錄
unpackage: 打包輸出目錄
components: 組件存放目錄
App.vue: 根組件,所有頁面都在App.vue下進行切換,是頁面入口文件,可以調用應用的生命周期函數。
main.js: 項目入口文件,初始化vue實例并使用需要的插件。
manifest.json: 應用的配置文件,用于指定應用的名稱、圖標、權限等。
pages.json: 對uni-app進行全局配置,決定頁面文件的路徑、窗口樣式、原生的導航欄以及底部tabbar等。
uni.scss: 預置了scss變量預置,方便整體控制應用的風格,比如按鈕顏色、邊框風格
開發規范:
頁面文件遵循Vue單文件組件(SFC)規范
組件標簽靠近小程序規范
接口能力靠近小程序規范
數據綁定及事件處理同Vue.js規范,同時補充了APP及頁面規范生命周期
為兼容多端運行,建議使用flex布局進行開發
3.4 代碼運行
-
運行到瀏覽器
運行 -> 運行到瀏覽器 -> chrome
-
運行到小程序
運行 -> 運行到小程序模擬器 -> 微信開發者工具
image.png
第一次使用,需要先配置小程序ide的相關路徑,才能運行成功。
`工具 -> 設置 -> 運行配置`

若HBuilderX不能正常啟動微信開發者工具,需要開發者手動啟動,然后將uniapp生成小程序工程的路徑拷貝到微信開發者工具里面,在HBuilderX里面開發,在微信開發者工具里面就可看到實時的效果。
5. 全局配置和頁面配置
pages.json 配置手冊
globelStyle: 全局配置
pages: 頁面配置 通過 pages 節點配置應用由哪些頁面組成,pages 節點接收一個數組,數組每個項都是一個對象,其屬性值如下:
tabbar: 導航欄配置 在App和小程序端提升性能。在這兩個平臺,底層原生引擎在啟動時無需等待js引擎初始化,即可直接讀取 pages.json 中配置的 tabBar 信息,渲染原生tab。
6. condition
啟動模式配置,僅開發期間生效,用于模擬直達頁面的場景,如:用戶點擊所打開的頁面7. uni-app組件
搭建頁面基礎結構
-
視圖組件 view
image.png -
基礎組件 text
image.png -
表單組件 button
image.png -
媒體組件 image
image.png
8. 樣式設置
9. 數據綁定和事件綁定
同vue: v-bind\v-on
<pre spellcheck="false" class="md-fences md-end-block ty-contain-cm modeLoaded" lang="" cid="n99" mdtype="fences" style="box-sizing: border-box; overflow: visible; font-family: var(--monospace); --select-text-bg-color: #36284e; --select-text-font-color: #fff; font-size: 0.9rem; line-height: 1.71429em; display: block; break-inside: avoid; text-align: left; white-space: normal; background-image: inherit; background-position: inherit; background-size: inherit; background-repeat: inherit; background-attachment: inherit; background-origin: inherit; background-clip: inherit; background-color: rgb(218, 218, 218); position: relative !important; margin-bottom: 3em; margin-left: 2em; padding-left: 1ch; padding-right: 1ch; width: inherit; color: rgb(31, 9, 9); font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-style: initial; text-decoration-color: initial;"><template>
<view class="content">
<image class="logo" src="/static/logo.png"></image>
<view class="text-area">
<text class="title">{{title}}</text>
<input v-model="inputValue" @input="onChangeInput" />
</view>
<button @click="onClick"></button>
</view>
</template>
<script>
export default {
data() {
return {
title: 'Hi',
inputValue: '。。'
}
},
onLoad() {
},
methods: {
onClick(){
console.log('click')
},
onChangeInput(){
console.log(this.inputValue)
}
}
}
</script></pre>
10. 生命周期
-
應用生命周期 僅可在
App.vue
中監聽,在頁面監聽無效。image.png -
頁面生命周期 image.png
11. 下拉刷新
11.1 開啟下拉刷新
在pages.json
中加入下拉刷新屬性,globalStyle為全局添加,page中為單頁面添加
11.2 在頁面中進行uni.startPullDownRefresh(OBJECT)
調用
官方文檔查看 開始下拉刷新,調用后觸發下拉刷新動畫,效果與用戶手動下拉刷新一致。
11.3 監聽下拉刷新
在頁面生命周期中加入監聽事件。當處理完數據刷新后,uni.stopPullDownRefresh
可以停止當前頁面的下拉刷新。
<pre spellcheck="false" class="md-fences md-end-block ty-contain-cm modeLoaded" lang="" cid="n114" mdtype="fences" style="box-sizing: border-box; overflow: visible; font-family: var(--monospace); --select-text-bg-color: #36284e; --select-text-font-color: #fff; font-size: 0.9rem; line-height: 1.71429em; display: block; break-inside: avoid; text-align: left; white-space: normal; background-image: inherit; background-position: inherit; background-size: inherit; background-repeat: inherit; background-attachment: inherit; background-origin: inherit; background-clip: inherit; background-color: rgb(218, 218, 218); position: relative !important; margin-bottom: 3em; margin-left: 2em; padding-left: 1ch; padding-right: 1ch; width: inherit; color: rgb(31, 9, 9); font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-style: initial; text-decoration-color: initial;">onPullDownRefresh() {
console.log('刷新了');
uni.stopPullDownRefresh();
}</pre>
12. 網絡請求
uni.request(Object)
13. 數據存儲
-
異步存儲
uni.setStorage(OBJECT)
將數據存儲在本地緩存中指定的 key 中,會覆蓋掉原來該 key 對應的內容image.pnguni.getStorage(OBJECT)
uni.removeStorage(OBJECT)
同步存儲
uni.setStorageSync(KEY, DATA)
將 data 存儲在本地緩存中指定的 key 中,會覆蓋掉原來該 key 對應的內容
<pre spellcheck="false" class="md-fences md-end-block ty-contain-cm modeLoaded" lang="" cid="n124" mdtype="fences" style="box-sizing: border-box; overflow: visible; font-family: var(--monospace); --select-text-bg-color: #36284e; --select-text-font-color: #fff; font-size: 0.9rem; line-height: 1.71429em; display: block; break-inside: avoid; text-align: left; white-space: normal; background-image: inherit; background-position: inherit; background-size: inherit; background-repeat: inherit; background-attachment: inherit; background-origin: inherit; background-clip: inherit; background-color: rgb(218, 218, 218); position: relative !important; margin-bottom: 3em; margin-left: 2em; padding-left: 1ch; padding-right: 1ch; width: inherit; color: rgb(31, 9, 9); font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-style: initial; text-decoration-color: initial;">try {
uni.removeStorageSync('storage_key');
} catch (e) {
// error
}</pre>
uni.getStorageSync(KEY)
uni.removeStorageSync(KEY)
14. 圖片上傳預覽
uni.chooseImage(OBJECT)
從本地相冊選擇圖片或使用相機拍照。
<pre spellcheck="false" class="md-fences md-end-block ty-contain-cm modeLoaded" lang="" cid="n128" mdtype="fences" style="box-sizing: border-box; overflow: visible; font-family: var(--monospace); --select-text-bg-color: #36284e; --select-text-font-color: #fff; font-size: 0.9rem; line-height: 1.71429em; display: block; break-inside: avoid; text-align: left; white-space: normal; background-image: inherit; background-position: inherit; background-size: inherit; background-repeat: inherit; background-attachment: inherit; background-origin: inherit; background-clip: inherit; background-color: rgb(218, 218, 218); position: relative !important; margin-bottom: 3em; margin-left: 2em; padding-left: 1ch; padding-right: 1ch; width: inherit; color: rgb(31, 9, 9); font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-style: initial; text-decoration-color: initial;">uni.chooseImage({
count: 6, //默認9
sizeType: ['original', 'compressed'], //可以指定是原圖還是壓縮圖,默認二者都有
sourceType: ['album'], //從相冊選擇
success: function (res) {
console.log(JSON.stringify(res.tempFilePaths));
}
});</pre>
uni.previewImage(OBJECT)
預覽圖片。
15. 條件編譯跨端兼容
注釋: #ifdef <端名>... #endif16. 導航跳轉和傳參
在起始頁面跳轉到test.vue頁面并傳遞參數
<pre spellcheck="false" class="md-fences md-end-block ty-contain-cm modeLoaded" lang="" cid="n135" mdtype="fences" style="box-sizing: border-box; overflow: visible; font-family: var(--monospace); --select-text-bg-color: #36284e; --select-text-font-color: #fff; font-size: 0.9rem; line-height: 1.71429em; display: block; break-inside: avoid; text-align: left; white-space: normal; background-image: inherit; background-position: inherit; background-size: inherit; background-repeat: inherit; background-attachment: inherit; background-origin: inherit; background-clip: inherit; background-color: rgb(218, 218, 218); position: relative !important; margin-bottom: 3em; margin-left: 2em; padding-left: 1ch; padding-right: 1ch; width: inherit; color: rgb(31, 9, 9); font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-style: initial; text-decoration-color: initial;">uni.navigateTo({
url: 'test?id=1&name=uniapp'
});</pre>
在頁面接受參數
<pre spellcheck="false" class="md-fences md-end-block ty-contain-cm modeLoaded" lang="" cid="n137" mdtype="fences" style="box-sizing: border-box; overflow: visible; font-family: var(--monospace); --select-text-bg-color: #36284e; --select-text-font-color: #fff; font-size: 0.9rem; line-height: 1.71429em; display: block; break-inside: avoid; text-align: left; white-space: normal; background-image: inherit; background-position: inherit; background-size: inherit; background-repeat: inherit; background-attachment: inherit; background-origin: inherit; background-clip: inherit; background-color: rgb(218, 218, 218); position: relative !important; margin-bottom: 3em; margin-left: 2em; padding-left: 1ch; padding-right: 1ch; width: inherit; color: rgb(31, 9, 9); font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-style: initial; text-decoration-color: initial;">export default {
onLoad: function (option) { //option為object類型,會序列化上個頁面傳遞的參數
console.log(option.id); //打印出上個頁面傳遞的參數。
console.log(option.name); //打印出上個頁面傳遞的參數。
}
}</pre>
17. 組件創建、生命周期與父子組件傳值
(同vue)
18. request封裝(引用參考文章2)
<pre spellcheck="false" class="md-fences md-end-block ty-contain-cm modeLoaded" lang="" cid="n142" mdtype="fences" style="box-sizing: border-box; overflow: visible; font-family: var(--monospace); --select-text-bg-color: #36284e; --select-text-font-color: #fff; font-size: 0.9rem; line-height: 1.71429em; display: block; break-inside: avoid; text-align: left; white-space: normal; background-image: inherit; background-position: inherit; background-size: inherit; background-repeat: inherit; background-attachment: inherit; background-origin: inherit; background-clip: inherit; background-color: rgb(218, 218, 218); position: relative !important; margin-bottom: 3em; margin-left: 2em; padding-left: 1ch; padding-right: 1ch; width: inherit; color: rgb(31, 9, 9); font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-style: initial; text-decoration-color: initial;">var http = {
post(path, params, contentType = 'form', otherUrl, ) {
return new Promise((resolve, reject) => {
var url = (otherUrl || config.baseUrl) + path
if (!checkUrl(url)) {
rej('請求失敗')
}
uni.request({
method: 'POST',
url,
header: {
"Content-Type": contentType === 'json' ? "application/json" :
"application/x-www-form-urlencoded"
},
data: params,
success: (res) => {
console.log('request:POST請求' + config.baseUrl + path + ' 成功', res.data)
resolve(res.data)
},
fail: (err) => {
message.toast('請求失敗', 'err')
console.error('request:請求' + config.baseUrl + path + ' 失敗', err)
reject('請求失敗')
}
})
})
},
put(path, params, contentType = 'form', otherUrl, ) {
return new Promise((resolve, reject) => {
var url = (otherUrl || config.baseUrl) + path
if (!checkUrl(url)) {
rej('請求失敗')
}
uni.request({
method: 'PUT',
url,
header: {
"Content-Type": contentType === 'json' ? "application/json" :
"application/x-www-form-urlencoded"
},
data: params,
success: (res) => {
console.log('request:PUT請求' + config.baseUrl + path + ' 成功', res.data)
resolve(res.data)
},
fail: (err) => {
message.toast('請求失敗', 'err')
console.error('request:PUT請求' + config.baseUrl + path + ' 失敗', err)
reject('請求失敗')
}
})
})
},
get(path, params, otherUrl) {
return new Promise((resolve, reject) => {
var url = (otherUrl || config.baseUrl) + path
if (!checkUrl(url)) {
return
}
uni.request({
url,
data: params,
success: (res) => {
console.log('request:GET請求' + config.baseUrl + path + ' 成功', res.data)
resolve(res.data)
},
fail: (err) => {
message.toast('請求失敗', 'err')
console.error('request:GET請求' + config.baseUrl + path + ' 失敗', err)
reject(err)
}
})
})
},
delete(path, params, otherUrl) {
return new Promise((resolve, reject) => {
var url = (otherUrl || config.baseUrl) + path
if (!checkUrl(url)) {
return
}
uni.request({
url,
data: params,
method: "DELETE",
success: (res) => {
console.log('request:DELETE請求' + config.baseUrl + path + ' 成功', res.data)
resolve(res.data)
},
fail: (err) => {
message.toast('請求失敗', 'err')
console.error('request:DELETE請求' + config.baseUrl + path + ' 失敗', err)
reject(err)
}
})
})
},
async upload(path, fileArray, otherUrl) {
if (typeof fileArray !== 'object') {
console.error('request:參數錯誤,請傳入文件數組')
return
}
var url = (otherUrl || config.baseUrl) + path
if (!checkUrl(url)) {
return
}
var arr = []
for (let i in fileArray) {
const res = await uni.uploadFile({
url: otherUrl || config.baseUrl + path,
filePath: fileArray[i],
name: 'file'
})
console.log(res)
if (res[0]) {
console.error('request:上傳失敗', res[0])
return
}
arr.push(JSON.parse(res[1].data).data)
}
return arr
},
}
function checkUrl(url) {
var urlReg = /((ht|f)tps?)://[\w-]+(.[\w-]+)+([\w-.,@?=%&:/+#]*[\w-@?^=%&/+#])?$/;
if (!urlReg.test(url)) {
console.error('request:請求路徑錯誤' + url)
return false
}
return true
}
module.exports = http</pre>