血泪合集,uniapp超长实践精华总结~
前言
在經歷了多個uniapp小程序項目開發后,我將我所有的踩坑實戰經驗復盤總結,以下內容僅針對uniapp開發微信小程序的場景,其中除了uniapp外還有一些小程序開發的注意點,希望這篇文章可以幫助大家避坑。以下全文干貨(干到口渴那種)
uniapp簡介
先放官方介紹,如果熟悉已經了解uniapp的同學可以跳過。
uni-app 是一個使用 Vue.js 開發所有前端應用的框架,開發者編寫一套代碼,可發布到iOS、Android、Web(響應式)、以及各種小程序(微信/支付寶/百度/頭條/QQ/快手/釘釘/淘寶)、快應用等多個平臺。
使用uniapp的話我們可以使用 vue.js 的語法來寫 小程序 ,簡而言之就是將常規vue.js開發中 template 模板從 html 替換成 wxml 就可以開發小程序,那么這么做有什么好處呢!
uniapp開發準備
ide工具安裝
HBuilder官網鏈接
工欲善其事,必先利其器。開發uniapp的第一步是安裝一個官方的ide工具 HbuilderX ,對于開發uni-app來說我只推薦這一個ide工具,確實非常方便,可以幫助我們快速生成頁面模板和組件模板,可視化的頁面配置,優秀的代碼提示,代碼補全能力 等等。
HbuilderX 相比 vscode 還是有一些不足之處,一些插件生態不是很健全。但是開發uni-app就是非 HbuilderX 莫屬了(畢竟是官方的東西)
HbuilderX 的安裝非常方便,直接在官網下載安裝就OK了。下載完成后我們需要安裝一些插件,打開HbuilderX ,在頂部欄選擇工具->插件安裝,如下圖
打開后我們可以看到當前已安裝的插件和一些官方插件,這里由于年代久遠我不記得最初的時候哪幾個沒安裝了,但是下面紅框圈起來的是一定要安裝的,一個是用于git版本管理的插件,一個是用于編譯sass的插件。
我們還可以設置我們的編輯器主題和字符大小,代碼縮進等,如果你是有其他編輯器使用習慣的可以適當調整。我原本是使用vscode進行開發的,所以我切換成了 雅藍 的主題,實際頁面效果和vscode one dark pro 的 編輯器風格 和 代碼顏色 一模一樣!
以上步驟我認為都是必要的,舒服美觀的開發環境可以極大的提升你的開發興趣!
項目目錄結構分析
新建項目
HbuilderX 安裝配置完畢后,我們就可以開始開發了,首先我們需要創建一個項目,我們點擊ide左上角
文件-> 新建 ->項目 ,然后我們選擇 uniapp 項目 ,模板選擇 默認模板
創建完成后,我們可以看到左側文件樹中新增了一個以項目名命名的文件,其中是hbuilder為我們內置的項目模板
以下是uniapp給我們的項目框架介紹,有一些文件夾是沒有在模板中內置的,因此我們需要自己手動創建以下,例如最外層的components,用來放置我們的一些全局通用組件
┌─components 符合vue組件規范的uni-app組件目錄 │ └─comp-a.vue 可復用的a組件 ├─pages 業務頁面文件存放的目錄 │ ├─index │ │ └─index.vue index頁面 │ └─list │ └─list.vue list頁面 ├─static 存放應用引用的本地靜態資源(如圖片、視頻等)的目錄,注意: 靜態資源只能存放于此 ├─uni_modules 存放[uni_module](/uni_modules)規范的插件。 ├─wxcomponents 存放小程序組件的目錄,詳見 ├─main.js Vue初始化入口文件 ├─App.vue 應用配置,用來配置App全局樣式以及監聽 應用生命周期 ├─manifest.json 配置應用名稱、appid、logo、版本等打包信息,詳見 └─pages.json 配置頁面路由、導航條、選項卡等頁面類信息,詳見如下圖,在開發的過程中,我依據vue項目的開發習慣,在pages中依照業務功能來創建功能分類文件夾,最常見的是按照首頁的tabbar來區分功能模塊,每一個文件夾中存放該功能涉及的所有頁面,且每一個功能文件夾中還有一個單獨的components文件夾用于放置該僅功能文件夾中的頁面依賴的組件。
新建頁面
我們需要創建新頁面的時候可以通過hbuilder內置的頁面模板來快速創建,右鍵點擊左側文件樹中當前的項目,選擇 新建頁面 ,輸入頁面名稱以及選擇模板就可以創建了,一般我選擇的是scss的模板,創建完成后會自動幫你在page.json中注冊該頁面。
通用插件封裝
既然uniapp選擇使用vue.js作為開發框架,那么我們一定要利用上vue中的一些優秀特性,例如插件(plugin),關于vue插件的介紹大家可以直接去官網看。
vue 插件官方介紹鏈接
通過引入插件,我們可以極大的提升我們的開發效率,當然如果是第一次使用uniapp進行開發可能不清楚哪個功能適合封裝成插件引入,下面我就介紹一下一些我在實際開發中封裝的一些通用插件
在封裝前我們需要寫一個 config 文件,方便我們快速自定義一些顏色和請求路徑等。
//congif.js const config = {baseUrl:'https://example.cn',//請求的基本路徑modalColor:'#5271FF', //彈窗顏色 }module.exports = config彈窗插件
在小程序中,如果我們沒有自定義彈窗和擬態框組件的話一般都是使用官方的showModal或者showToast api來進行一些用戶交互。這種非常頻繁使用到的操作非常適合封裝起來快速調用。具體代碼如下
插件代碼
const config = require('../config.js')var message = {toast(title, type = 'text') {if (title.length > 15) {console.error('toast長度超過15個字符,當前長度為' + title.length)return}var icon = 'none'if (type) {switch (type) {case 'text':icon = 'none'breakcase 'suc':icon = 'success'breakcase 'err':icon = 'error'break}}uni.showToast({title,icon})},confirm(title, confirmColor) {return new Promise((res, rej) => {uni.showModal({title,cancelColor: '#b6b6b6',confirmColor: confirmColor || config.modalColor,success: (result) => {if (result.cancel) {rej(result)} else if (result.confirm) {res(result)}}})})},async message(content, confrimText) {return new Promise((res) => {uni.showModal({title: '提示',content,showCancel: false,confirmColor: config.modalColor,success: (result) => {res(result)}})})} } module.exports = message示例調用
this.message.toast('回答已刪除')請求插件
在vue中我們常用的請求庫是axios,幾乎每一個開發者都會將axios進行一個二次封裝,以減少調用時的代碼量以及進行一些全局配置。在uni-app中我們無須引入第三方的請求庫,直接使用官方提供的 uni.request(OBJECT) 這個api來進行請求的調用,當然我們也是要做一個二次封裝的。具體代碼如下
插件代碼
//http.js const config = require('../config.js') const message = require('./message.js') var http = {post(path, params, contentType = 'form', otherUrl, ) {return new Promise((resolve, reject) => {var url = (otherUrl || config.baseUrl) + pathif (!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) + pathif (!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) + pathif (!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) + pathif (!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) + pathif (!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示例調用
async getAnswer() {const res = await this.http.get('/applet/answerList', {qId: this.question.id,})if (res.code === 200) {return res.data} else {this.message.toast('請求失敗')return false} }在上面的代碼中我們可以看到,我們引入兩個依賴文件分別是config和message 這個在下面會說的,在http對象中有五個方法,分別是post,get,update,delete對應著 restful api 的增刪查改操作,還有一個upload 方法用來上傳文件。在小程序中請求我們不需要考慮跨域的問題,我們的默認請求路徑是從 config這個依賴文件中獲取的。而這時候我們還可以利用第一個 彈窗插件 快速的進行消息提示。
存儲插件
在使用vue的時候我們常常會使用vuex + vuex-persistedstate來使我們的數據可以全局使用以及持久化,在小程序中我們有兩種全局數據的存儲方式 一種是globalData,通過在App.vue中添加globalData這個值來進行數據全局化,例如我們可以用來保存我們的小程序版本。
export default {globalData: {version: '1.0.0'} }但是globalData的數據不是持久化的,當我們退出小程序再進入的時候就重新初始化了,所以我們還有一種全局數據存儲方式是storage,類比我們web端的localstroge,使用方式也幾乎一樣。通過官方提供的api實現本地存儲的效果
在小程序中往往沒有web端那么復雜的數據結構,雖然uniapp小程序也可以使用vuex作為一個全局數據管理的庫,但我自己還是做了一個簡化版的方便自己使用。這個插件是在很久以前寫的了,只是非常簡單的實現,如果想要省略setData操作可以用 proxy 劫持存儲的值去存在本地。
插件代碼
var store = {_init(){if(uni.getStorageSync('store')==''){uni.setStorageSync('store',{})return}const data = uni.getStorageSync('store')for(let i in data){if(!this[i]){this[i] = data[i]}}},setData(key,value){if(key == '_init'||key=='setData'){console.error('store:非法key值',key)return}this[key] = valueuni.setStorageSync('store',this)console.log(uni.getStorageSync('store'))} }module.exports = store示例使用
this.store.setData('user',{name:'oil'}) // 賦值 console.log(this.store.user) // 讀值表單驗證插件
表單驗證是使用的是一個很輕量開源的表單驗證庫JSValidate,可以直接點擊鏈接在倉庫看源碼,下面就不放代碼了,講一下如何使用。
JSValidate gitee鏈接
示例調用
// validate.js const validate = {userForm:{intro: '@個人簡介|require',college: '@學校|require!請選擇你的學校',nickname:'@昵稱|require'} }module.exports = validate在表單中寫入一個表單驗證方法,如果條件不符合的話就使用彈窗進行提示
//form.js validateForm() {// 表單驗證方法const validate = require("./validate")const validator = new this.validator()var result = validator.check(validate.userForm, this.form, false)if (result !== true) {this.message.message(result)return false}return true }時間處理插件
關于時間處理插件我在項目中使用的也是一個開源的輕量級js庫 day.js,具體的使用方式參考官方文檔,day.js幾乎包含了所有時間處理相關的操作,例如時間對比,時間轉換等。
day.js官方文檔鏈接
day.js其中有一項功能特別常用,就是一個相對時間轉換的功能,相對時間轉換就是將一個時間字符串例如年月日時分秒轉換成幾秒前,十分鐘前,幾個月前等等。使用的方式如下代碼,我們使用vue的computed來進行時間字符串的處理,通過 閉包 向computed方法內傳入時間字符串參數,然后通過day.js的formNow方法返回一個中文的相對時間。date就是我封裝后引入的插件了。
示例代碼
computed: {relativeDate() {return (date) => {return this.date(date).fromNow()}} }當我們擁有了很多個插件之后(如下圖),我們的插件之間可能會共享一些配置,例如上文中的config.js,接下來我們可以寫一個入口文件index.js用來將我們寫的插件引入,然后在main.js中安裝我們的插件。
示例代碼
// index.js const http = require('./lib/http') const message = require('./lib/message') const router = require('./lib/router') const validator = require('./lib/validator') const date = require('./lib/date') const store = require('./lib/store') const to = require('./lib/to') const oil = {message,http,router,validator,date,store,to,install(Vue) {this.store._init()for (let i in this) {if (i == 'install') {continue}Vue.prototype[i] = this[i]}delete this.install} }export default oil // main.js import Vue from 'vue' import oilUni from './oil-uni/index.js' Vue.use(oilUni) app.$mount()我們在入門文件中定義了一個對象叫做oil,oil上除了我們寫好的方法還有一個特殊的方法就是install ,install 的作用就是在我們使用Vue.use()的時候會將Vue的構造器傳入install方法作為第一個參數,然后我們將所有方法都綁定在Vue的原型鏈上。
安裝后,我們只需要在頁面或組件的js中使用 this. + xx插件 就可以訪問到封裝的插件了。
常用組件封裝
插件的作用是將我們的代碼進行一個模塊化,以便捷的復用一些常用的操作。而組件化則是幫助我們進行一個ui上的封裝和復用。由于小程序的場景一般都不是很復雜,所以非常通用的組件也不多,下面只說說我在業務中常用的一些組件庫和自己封裝的組件
第三方組件庫引入
colorUI
首先推薦的就是我幾乎在所有小程序項目上都有用的 colorUI,與其說 colorUI 是一個組件庫,不如說它是一個樣式庫。
colorUI github鏈接
在colorUI中封裝了大量的行內樣式,我們不需要頻繁的在template和style間切換,大大的減少了我們的心智負擔。可以類比 tailwindcss 這個樣式庫,但是在HBuilderX中,有著非常健全的colorUI樣式的代碼提示。其實不止是colorUI。你自己寫的樣式或者引用的文件在HBuilderX中都會獲得很完整的一個代碼提示。如下圖
colorUI中并沒有為我們提供功能上封裝好的組件,但是在它的示例源碼中,大部分場景都可以直接將樣式和代碼復制出來改造一下直接使用,例如圖片上傳和表單組件等。
colorUI示例頁面
colorUI的引入也非常簡單,將colorUI的包放在項目根目錄下,然后在App.vue的style中引用就OK了,colorUI包含了三個文件,一個是main.css,里面是colorUI所有的樣式代碼,icon 是colorUI的圖標,animation 是colorUI的動畫,三個包的功能都是通過在 行內class 中寫入指定代碼來使用。
//App.vue <style>@import "colorui/main.css";@import "colorui/icon.css";@import "colorui/animation.css"; </style>vant-weapp
vant幾乎是vue生態在移動端個人認為最好用的組件庫了,幾乎所有場景的組件都有涵蓋到,而vant有一個小程序專用的 weapp 版本。 vant weapp 官方文檔
但 vant weapp 是用原生小程序的語法寫的,和uniapp中的vue語法有些出入,所以引入的時候需要做一些特殊操作,下面說一下 vant weapp 如何引入。
首先需要下載vant weapp,將vant文件夾放在項目根目錄下的wxcomponent文件夾里,沒有就新建一個。
在App.vue中引入一下樣式文件,
要注意vant weapp官方文檔中的語法是小程序的,自己轉換成vue的就OK了
滾動容器組件
在引入以上兩個組件庫后,咱們開發時的大部分場景都可以在其中找到對應組件,頂多是進行一個二次封裝以適配業務。
常見業務場景
登錄頁面
在小程序中,登錄往往不像web端需要輸入賬號密碼或者手機驗證碼登錄,而是使用各個平臺的快速鑒權功能,例如微信小程序就是可以通過向微信官方的api發送請求來獲取用戶信息。
獲取用戶信息
因此我們在設計頁面的時候往往只需要非常簡單的一個按鈕來獲取用戶信息,下面我將用一個項目中的登錄功能來給大家展示uniapp實現微信小程序登錄獲取用戶信息的方法
<template><button class="cu-btn bg-blue shadow-blur round lg" @click="login">立即登錄</button> </template> <script> export default {methods: {async login() {uni.showLoading({mask: true,title: '登錄中'})const res = await uni.getUserProfile({desc: '用于存儲用戶數據'})uni.hideLoading()if (res[0]) {this.message.toast('獲取失敗', 'text')return}this.getUser(res[1].userInfo)},async getUser(userInfo) {uni.showLoading({mask: true,title: '登錄中'})const loginRes = await uni.login()if (loginRes[0]) {uni.hideLoading()this.message.toast('獲取失敗')return}const res = await this.http.post('/applet/weChatLogin', {code: loginRes[1].code}, 'form')uni.hideLoading()if (!res.data) {this.router.push('/pages/form/userForm', {userInfo,handle: 'add'})return}this.store.setData('user', res.data)},}我們來分析一下上面的代碼,首先我們使用 uni.getUserProfile api來獲取用戶的基本信息,例如頭像,昵稱,地區等。接下來我們調用 uni.login 獲取code值。 code值是用來傳遞給后端獲取微信給用戶的 唯一標識 openid 的。
我將code碼傳遞給后端,后端解析出openid并在數據庫中查看當前用戶是否已有用戶信息,沒有的話則為第一次登錄。第一次登錄我們就跳轉至表單頁面 完善個人信息 和 獲取用戶手機號碼。
后端的具體代碼在這里不詳細展開說,只說整體實現過程。
獲取用戶手機號碼
獲取用戶號碼有一個必要的條件就是點擊open-type為 getPhoneNumber 的 button 標簽觸發,且需要使用 getphonenumber 綁定一個事件。點擊這個按鈕時,微信會彈出一個授權獲取手機號碼的框框,用戶選擇了同意的話就會觸發我們的 getphonenumber 方法。
getphonenumber 方法的參數e.detail中有兩個屬性 encryptedData(加密數據) 和 iv(初始化矢量) 這兩個參數是用來傳遞給后端進行手機號碼的解析的,后端的具體流程比較復雜,下次單出一篇文章講。
還有一點非常非常重要的,如果你是一個社區型的應用,用戶可能發表一些評論和文章,那么咱們保存的用戶頭像就必須一直有效,如果我們使用微信官方提供的默認頭像鏈接直接存儲在數據庫,那么當用戶換頭像之后,原先的鏈接就會失效!
所以我們需要做的是使用 uni.downloadFile api將用戶的頭像下載為本地文件,再將本地文件上傳至服務器,用戶頭像就會一直有效了!
具體代碼如下,已經將業務抽離了,只剩核心代碼。
<button class="cu-btn bg-blue shadow-blur round lg" open-type="getPhoneNumber"@getphonenumber="getTel" @click="getCode">確定提交 </button> async uploadAvatar() {// 上傳用戶頭像if (this.updateImg) {var uploadRes = await this.http.upload('/applet/file/uploadFile', [this.form.avatarUrl])}else if (this.userInfo) {var downloadRes = await uni.downloadFile({url: this.userInfo.avatarUrl})if (downloadRes[0]) {uni.hideLoading()this.message.toast('登錄失敗')return}var uploadRes = await this.http.upload('/applet/file/uploadFile', [downloadRes[1].tempFilePath])}else {return}this.form.avatarUrl = uploadRes[0] }, async getTel(e) {// 首次登錄,獲取用戶手機號碼if (!e.detail.encryptedData || !e.detail.iv) {this.message.toast('登錄失敗')return}uni.showLoading({mask: true,title: '登錄中'})await this.uploadAvatar()this.form.tags = this.tag.arr.map(item => item.name).join(',')const res = await this.http.post('/applet/authorization', {code: this.code,iv: e.detail.iv,encryptedData: e.detail.encryptedData,userInfo: this.form}, 'json')uni.hideLoading() },列表頁面
下面講講咱們非常非常常見的一個業務場景,那就是 列表頁面,列表頁面看似簡單,實則暗藏玄機。例如下拉刷新,上滑加載,沒有更多內容的提示,關鍵字搜索,標簽欄匹配,空白頁面,列表項組件的復用等等。在一開始就設計完整會對開發效率和整體的代碼健壯性有很大幫助!
我將用我實際開發中的代碼示例為大家演示一個列表的功能實現。還是一樣,我抽離了業務代碼只剩核心代碼!
<!-- 頁面代碼 --> <scroll-view:scroll-y="true"style="height: 100vh":refresher-enabled="true":refresher-triggered="list.flag"@refresherrefresh="refresh"@scrolltolower="getAnswer":enable-back-to-top="true":scroll-into-view="list.scrollId":scroll-with-animation="true" ><view style="position: relative" class="bg-white"><view v-for="(item,index) in list.data" :key="index" ><answer-item:data="item"></answer-item></view></view><viewclass="cu-load bg-gray text-main loading"style="height: 40px"v-if="!list.nomore"></view> </scroll-view> // js代碼 export default { components: {AnswerItem, }, data() {return {list: {data: [],flag: false, //是否展示刷新limit: 10,total: 0,nomore: false, //是否顯示到底empty: false, //是否顯示為空,error: false, //是否請求錯誤,}}; }, async onLoad(e) {this.getQuestion()await this.getAnswer()async getAnswer() {}, methods: {async getAnswer() {const listHandle = require("../../utils/js/listHandle")const id = this.list.data.length ? this.list.data[this.list.data.length - 1].id : ''const res = await this.http.get('/applet/answerList', {qId: this.question.id,limit: this.list.limit,userId: this.store.user ? this.store.user.id : '',id})if (res.code === 200) {const list = listHandle.add(res.data, this.list.data, this.list.limit)Object.assign(this.list, list)return res.data} else {this.list.error = truethis.message.toast('請求失敗')return false} }, async refresh() {// 刷新方法const listHandle = require("../../utils/js/listHandle")this.list.flag = truethis.list.nomore = falsethis.list.empty = falsethis.list.error = falsethis.list.loadNum = 0const res = await this.http.get('/applet/answerList', {qId: this.question.id,limit: this.list.limit,userId: this.store.user ? this.store.user.id : '',id: ''})this.list.flag = falseif (res.code === 200) {const list = listHandle.update(res.data, this.list.limit)Object.assign(this.list, list)} else {this.list.err = truethis.message.toast('請求失敗')} }, } } // listHandle.js 列表項處理的方法 const listHandle = {add(resData, listData, limit) {var ret = {nomore: false,empty: false,data: []}if (resData) {if (resData.length < limit) {// 獲取數據條數小于頁碼數,顯示已到底ret.nomore = true}ret.data = listData.concat(resData)} else {ret.data = listDataret.nomore = trueif (!listData.length) {// 請求已無返回數據且當前列表無數據,顯示為空ret.empty = true}}return ret},update(resData, limit) {var ret = {nomore: false,empty: false,data: []}if (resData) {if (resData.length < limit) {// 獲取數據條數小于頁碼數,顯示已到底ret.nomore = true}ret.data = resData} else {ret.nomore = true// 請求已無返回數據且,顯示為空ret.empty = true}return ret} }module.exports = listHandle注意點
在手機端上滑加載的時候,如果你是按照時間的倒序來排序,傳統web底部分頁器那樣傳一個頁碼數和單頁條數給后端查詢的話。如果在你上滑的過程中有用戶發布新的內容,那么你的列表中就會有 重復的項。
舉個例子:你最初取了十條數據,當你上滑到底部加載時傳了一個 page=2 和 limit=10給后端,意思是將所有數據十條作為一頁,我要拿第二頁的內容。但是這時候有用戶添加了一條新的數據,你第一頁的最后一條就被擠到第二頁去了,此時你拿到的數據第一條會和上一次拿到的最后一條一模一樣!
想要解決這個問題也很簡單,我們在向后端傳遞數據的時候不要按頁碼數去傳值。我們將當前數據的最后一條的id傳給后端,讓后端取 id比這個值更小的十條,此時不論有多少用戶插入新的數據都不會對你的結果產生影響。當然要實現這種功能的前提是你的數據表id是遞增的,如果不是你也可以用數據的創建時間的那個字段來傳遞。
對應這個功能的是下面這行代碼。
const id = this.list.data.length ? this.list.data[this.list.data.length - 1].id : ''
跨頁面方法調用
在uniapp小程序的開發中,我們可能會遇到一種場景,就是我需要在當前頁面調用上一個頁面的方法。
例如:我要實現一個搜索功能,當我點進搜索詳情輸入關鍵詞后,我要返回列表頁面并觸發一次搜索方法,如下圖
我的實現方式如下
searchFunc(){let pages = getCurrentPages()let page = pages[pages.length - 2]page.$vm.searchText = this.search.textpage.$vm.refresh()this.router.back() }getCurrentPages() 是一個全局函數,用于獲取當前頁面棧的實例,以數組形式按棧的順序給出。我們當前頁面就是數組最后一項,那么上一個頁面就是pages[pages.length - 2]。當然我們還要記得加上$vm屬性,因為在uniapp中我們的數據和方法是掛載這個實例上的。我們可以通過這個示例訪問到對應頁面的所有數據和方法!
踩坑注意點
以下列出的問題不僅僅有uni-app的,更多的是小程序本身的一些問題
scoll-view scroll-into-view跨組件
在小程序的scroll-view標簽上有一個scroll-into-view屬性,這個屬性值可以傳入一個id,當我們更改這個值時我們就會滾動到指定id的容器位置。
但是如果我們的scroll-view被封裝在組件中使用時,我們在slot
scroll-view無法下拉刷新
原因
應該是scroll-view元素的下拉位置在元素渲染時就確定了,而我們賦予scroll-view的高度更改了下拉位置,導致下拉的時候沒法拉到位
解決方法
在scroll-view的高度變量確定后再渲染scroll-view元素,具體的做法如下代碼
<scroll-view scroll-y="true" :style="'height:'+height" v-if="height" :refresher-enabled='true':refresher-triggered='list.flag' @refresherrefresh='refresh' @scrolltolower='loadMore'> </scroll-view>scroll-view sticky樣式問題
問題描述
scroll-view 是小程序常常會用到的一個標簽,在滾動窗口內我們可能會有一個頂部標簽欄,如果我們不想通過計算高度去固定在頂部的話我們可以使用 position:sticky 加一個邊界條件例如top:0屬性實現一個粘性布局,在容器滾動的時候,如果我們的頂部標簽欄觸碰到了頂部就不會再滾動了,而是固定在頂部。
但是在小程序中如果你在scroll-view元素中直接為子元素使用sticky屬性,你給予sticky的元素在到達父元素的底部時會失效,如果你遇到過一定會非常印象深刻😭。
解決方法
在scroll-view元素中,再增加一層view元素,然后在再將使用了sticky屬性的子元素放入view中,就可以實現粘貼在某個位置的效果了
ios固定輸入框字體移動bug
問題描述
在一個固定于頁面中間的滾動容器內放了一個表單,在安卓端測試功能完好,在IOS端有一個bug。當輸入框在輸入后沒有點擊其他位置使輸入框失焦的話,如果滾動窗口內部的字體也會跟著滾動下面使解決方法
//原本的輸入框 <input></input>解決方法
使用這個方法更改后,不僅布局的樣式不會改變,而且字體隨著固定的滾動窗口一起滾動的bug也會解決
// 更改后的輸入框 <textarea fixed="true" auto-height="true" ></textarea>真機時間錯誤BUG
問題描述
在小程序中使用new Date().toLocaleDateString() api獲取時間的時候,在開發工具中顯示為當前時間,而在真機中顯示為其他地區的時間
BUG產生原因
toLocaleDateString()方法依賴于底層操作系統在格式化日期上。
例如,在美國,月份出現在日期(06/22/2018)之前,而在印度,日期出現在月份(22/06/2018)之前。
解決方案
使用new Date()構造函數來獲取年月日后拼接
如果沒有輸入任何參數,則Date的構造器會依據系統設置的當前時間來創建一個Date對象。
Date和toLocaleDateString()的區別在于一個是獲取系統當前設置的時間,一個則是底層操作系統來格式化時間
//具體代碼如下 let date = new Date() date = date.getFullYear() + '/' + (date.getMonth() + 1) + '/' + date.getDate() date = date.split('/') if (date[1] < 10) {date[1] = '0' + date[1]} if (date[2] < 10) {date[2] = '0' + date[2]} date = date.join('-')自動全局引入組件
功能介紹
如果大家有用過vue2開發項目,就知道在vue2中可以通過webpack的api進行自動全局組件引入,不需要在頁面中一個一個手動引入,如下代碼
const requireComponent = require.context("@/components", true, /\.vue$/); // 通過webpack獲取conponents目錄下所有組件 const global = {install(app) {const components = requireComponent.keys(); // 獲得組件數組for (let component of components) {let componentName = component.replace(/(.*\/)*([^.]+).*/gi, "$2"); // 獲得組件名稱app.component(componentName, requireComponent(component).default); // 將組件掛載到全局}}, }; export default global;將global.js放在components的同級目錄,然后在 main.js 中引入并使用 Vue.use 來使用插件就OK了,這個方法就可以幫我們自動將組件掛載到全局。
注意點
在uniapp中這個上面這個方法是不能使用的,我們在上面的代碼中可以看到這一行
app.component(componentName, requireComponent(component).default);
在uniapp中 app.component 這個方法是不能傳入變量作為組件名的,只能直接傳入字符串,因此我們就沒法使用它來自動引入組件啦。
總結
這篇文章陸陸續續寫了兩個星期,雖然蠻長了,但是還有很多內容我沒有講到的。雖然理解產出倒逼輸入的道理,但是寫文章還是希望能得到更多的正面反饋!點贊大大滴有,更新大大滴快。
我是 前端新人oil歐呦,歡迎關注共同成長!
總結
以上是生活随笔為你收集整理的血泪合集,uniapp超长实践精华总结~的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 软件工作的薪水是全国平均水平的两倍
- 下一篇: 经典算法-并查集、快速排序、字典序算法、