uniapp的主要项目技术以及遇到的问题处理
一、uniapp的主要技术分享
1、 uniapp的目录
-
pages.json 文件用来对 uni-app 进行全局配置,决定页面文件的路径、窗口样式、原生的导航栏、底部的原生tabbar 等
-
manifest.json 文件是应用的配置文件,用于指定应用的名称、图标、权限等。
-
App.vue 是我们的跟组件,所有页面都是在App.vue下进行切换的,是页面入口文件,可以调用应用的生命周期函数。
-
main.js 是我们的项目入口文件,主要作用是初始化vue实例并使用需要的插件。
-
uni.scss 文件的用途是为了方便整体控制应用的风格。比如按钮颜色、边框风格,uni.scss文件里预置了一批scss变量预置。
-
unpackage 就是打包目录,在这里有各个平台的打包文件
-
pages 所有的页面存放目录
-
static 静态资源目录,例如图片等
-
components 组件存放目录
2、 uniapp中的生命周期
(1)应用生命周期
函数名 | 说明 |
---|---|
onLaunch | 当uni-app 初始化完成时触发(全局只触发一次) |
onShow | 当 uni-app 启动,或从后台进入前台显示,通俗的说就是打开应用时,就会显示 |
onHide | 当 uni-app 从前台进入后台,就是将该应用置于后台 |
onError | 当 uni-app 报错时触发 |
(2)页面生命周期
函数名 | 说明 |
---|---|
onLoad | (不会多次触发 监听页面加载,其参数为上个页面传递的数据,参数类型为Object |
onShow | 监听页面显示。页面每次出现在屏幕上都触发,包括从下级页面点返回露出当前页面 |
onReady | (不会触发多次) 监听页面初次渲染完成。 |
onHide | 监听页面隐藏 |
onUnload | 监听页面卸载 |
(3)组件生命周期同Vue生命周期
注意: Uniapp组件只有组件周期
3、下拉刷新
(1)开启下拉刷新
在uni-app中有两种方式开启下拉刷新
- 需要在 pages.json 里,找到的当前页面的pages节点,并在 style 选项中开启 enablePullDownRefresh【不建议全局开启刷新】
- 通过调用uni.startPullDownRefresh方法来开启下拉刷新
- 关闭加载的事件uni.stopPullDownRefresh()
(2)监听下拉刷新
通过onPullDownRefresh可以监听到下拉刷新的动作
export default {
data () {
return {
arr: ['uniapp','vue']
}
},
methods: {
startPull () {
uni.startPullDownRefresh()
}
},
onPullDownRefresh () {
console.log('触发下拉刷新了')
}
}
注意: 监听刷新onPullDownRefresh与onload同级
4、上拉加载
通过在pages.json文件中找到当前页面的pages节点下style中配置onReachBottomDistance可以设置距离底部开启加载的距离,默认为50px
通过onReachBottom监听到触底的行为
5、条件注释实现跨段兼容
条件编译是用特殊的注释作为标记,在编译时根据这些特殊的注释,将注释里面的代码编译到不同平台。
写法: 以 #ifdef 加平台标识 开头,以 #endif 结尾。
<!-- #ifdef H5 -->
<view>
h5页面会显示
</view>
<!-- #endif -->
<!-- #ifdef MP-WEIXIN -->
<view>
微信小程序会显示
</view>
<!-- #endif -->
<!-- #ifdef APP-PLUS -->
<view>
app会显示
</view>
<!-- #endif -->
6、uni中的导航跳转
(1)利用navigator进行跳转
保留当前页面,跳转到应用内的某个页面,使用uni.navigateBack可以返回到原页面。
- open-type=“navigate” 跳转到普通页面(默认跳转到单页面上,保留当前页面,打开新的页面后,还可以返回到上一级页面)
<navigator url="/pages/about/about" hover-class="navigator-hover">
<button type="default">跳转到关于页面</button>
</navigator>
- open-type=“switchTab” 跳转到tabbar页面(如果不加open-type=“switchTab”,就跳转不过来,卸载非tabbar页面,打开新的页面)卸载所有非tabbar页面
<navigator url="/pages/message/message" open-type="switchTab">
<button type="default">跳转到message页面</button>
</navigator>
- open-type=“redirect” 跳转到单个页面(卸载当前页面,打开新的页面,不可以返回到上一级页面)卸载当前页面
<navigator url="/pages/message/message" open-type="redirect">
<button type="default">跳转到message页面</button>
</navigator>
(2)利用编程式导航进行跳转
-
redirectTo进行跳转
关闭当前页面,跳转到应用内的某个页面 -
navigateTo进行导航跳转
保留当前页面,跳转到应用内的某个页面,使用uni.navigateBack可以返回到原页面。 -
switchTab跳转到tabbar页面
跳转到 tabBar 页面,并关闭其他所有非 tabBar 页面。 -
uni.navigateBack(OBJECT)
关闭当前页面,返回上一页面或多级页面。可通过 getCurrentPages() 获取当前的页面栈,决定需要返回几层。
(3)导航跳转传递参数
在导航进行跳转到下一个页面的同时,可以给下一个页面传递相应的参数,接收参数的页面可以通过onLoad生命周期进行接收
// 传递参数的页面
goAbout () {
uni.navigateTo({
url: '/pages/about/about?id=80&name='小王'',
});
}
// 接收参数的页面
<script>
export default {
onLoad (options) {
console.log(options)
}
}
</script>
二、uniapp的典型问题分享
1、实时录音转写遇到的问题
问题: 录音的后的音频需要处理,才能传输进行语音转文字的实时的转写。处理音频数据需要用到window对象,而Uniapp是没有Window对象的,所以没办法处理处理音频文件。
解决: 使用renderjs来解决不能使用window对象的问题
官方: renderjs是一个运行在视图层的js。它只支持app-vue和web。
官方不建议在uni-app里操作dom,但如果想使用一些操作了dom、window的库,就可以使用renderjs来解决。
为什么renderjs可以操作?
因为在app-vue环境下,视图层由webview渲染,而renderjs运行在视图层,就可以操作dom和window、document等这些全局变量。(在app-vue的service层没有这些)
<template>
<text class="aaa">
<!--这里属于渲染线程,可称为渲染线程的视图层,简称视图层。绑定的showCursor、text1、text2、show,属于逻辑线程(称为逻辑层)-->
<text class="bbb" @tap="showCursor">
{{text1}}<text class="cursor" v-if="show === false">光标</text>{{text2}}
</text>
</text>
</template>
<script module="text" lang="renderjs">
//这里的整个script块及代码,全部属于渲染线程,是渲染线程的逻辑层,也就是renderjs层。
//它与上面视图层的关系,是同一线程(渲染线程)内的视图层和逻辑层之间的关系,因此它与视图层共享内存,在APP-VUE和Web端可以直接调用视图层的$el、innerHTML等属性和window、document、navigator等浏览器的js API。
//它与下面逻辑线程的关系,是两个不同线程之间的关系。
//由于渲染线程和逻辑线程二者并未共享内存,所以当前代码块(属于渲染线程的私有逻辑层,即renderjs层)不能直接调用属于另一个线程(逻辑线程)的showCursor、text1、text2、show等数据。
</script>
<script>
//这里的整个<script>块、代码,以及上面模板中绑定的showCursor、text1、text2、show等,全部隶属于逻辑线程,可称为逻辑层(逻辑线程只有逻辑层)
//在本模块中,可以随意访问showCursor、text1、text2、show等属性或方法,但是显然不能直接访问另一线程中的视图层和逻辑层数据
//也就是,既不能直接访问html模板中的$el、innerHTML等属性,也不能直接访问renderjs、wxs、SJS模块中的数据。而只能通过执行当前模块中的showCursor方法,或更改当前模块中的text1、text2、show属性,间接地、异步地影响视图层的渲染效果。
</script>
如何使用renderjs
<template>
<view>
<text>renderjs区域</text>
<!-- 页面 view 中监听变量msg-->
<!-- 监听变量msg 调用renderjs的receiveMsg方法-->
<view @click="renderjs.emitData" :msg="msg" :change:msg="renderjs.receiveMsg" class="renderjs" id="renderjs-view">
</view>
<!-- 调用renderjs的click方法-->
<button @click="changeMsg" class="app-view">app-view</button>
</view>
</template>
<script>
export default {
data() {
return {
msg: ''
};
},
methods: {
// 触发逻辑层出入renderjs数据改变
changeMsg() {
this.msg = 'hello renderjs' + Math.random() * 10;
},
// 接收renderjs发回的数据
receiveRenderData(val) {
console.log('receiveRenderData-->', val);
}
}
};
</script>
<script module="renderjs" lang="renderjs">
export default {
data() {
return {
name: 'render-vm'
}
},
mounted() {
const view = document.getElementById('renderjs-view')
const button = document.createElement('button')
button.innerText = '一个按钮'
view.appendChild(button)
},
methods: {
// 接收逻辑层发送的数据
receiveMsg(newValue, oldValue, ownerVm, vm) {
//数据变化
console.log('newValue', newValue)
console.log('oldValue', oldValue)
console.log('ownerVm', ownerVm) //当前视图层的Vue实例
console.log('vm', vm)//当前的视图层实例
},
// 发送数据到逻辑层
emitData(e, ownerVm) {
ownerVm.callMethod('receiveRenderData', this.name)
}
}
};
</script>
2、自定义菜单遇到的问题
问题: 自己写的菜单自定义会出现错乱的情况,后拿到别的公司给的组件进行自定义不能实现拖拽切换位置顺序的功能。
解决: 在网上找到了别人写的可以拖转交换顺序的代码(使用movable-area,movable-view实现),进行结合修改为我们需求要的功能形式。拖拽的板块需要自适应,需要动态计算手机的尺寸做适配手机尺寸,以及平板的适配尺寸。
怎么实现?
总体分为三部分,触摸前、触摸移动时、触摸结束后,是基于触摸移动事件的元素位置计算、排序和状态更新的功能。
- 第一部分代码是在触摸开始时,通过 uni.createSelectorQuery().in(this) 创建查询器,并使用 query.select(‘#drag’).boundingClientRect() 来获取元素 #drag 的位置信息。然后,通过计算触摸点相对于元素位置的偏移值,并将结果保存在对应的变量中。
touchstart(e) {
// 计算 x y 轴点击位置
var query = uni.createSelectorQuery().in(this)
query.select('#drag').boundingClientRect()
query.exec((res) => {
this.topY = res[0].top
this.topX = res[0].left
//计算触摸点相对于元素的偏移值
let touchY = e.mp.touches[0].clientY - res[0].top
let touchX = e.mp.touches[0].clientX - res[0].left
//触摸点的偏移值取模
this.deviationY = touchY % this.height
this.deviationX = touchX % (this.windowWidth * 0.2)
this.active = Number(e.currentTarget.dataset.index)
this.index = Number(e.currentTarget.dataset.index)
})
},
- 第二部分代码是在触摸移动时,根据触摸点和元素位置的偏移值进行元素排序的操作。通过判断偏移值是否需要重新排序元素,并更新其他元素的排序号。
touchmove(e) {
if (this.active < 0) return
// 元素的偏移值
let temY = e.mp.touches[0].clientY - this.topY
let temX = e.mp.touches[0].clientX - this.topX
let touchY = temY - 30
let touchX = temX - this.windowWidth * 0.1
this.currentList[this.active].y = touchY
this.currentList[this.active].x = touchX
this.currentList[this.active].animation = false
this.currentList.every((res, index) => {
//相对于元素位置的绝对偏移值
let absX = Math.abs(touchX - res.x)
let absY = Math.abs(touchY - res.y)
// 设置元素定点距离多少进行重排
if (res.isShow == 0){
return true
}
if (0 < absX && absX <= 20 && absY > 0 && absY <= 40 && this.active != index) {
let temNumber = this.currentList[index].SortNumber
this.currentList.every((_res, _index) => {
// 判断从大像小移还是从小向大移
if (this.currentList[this.active].SortNumber < this.currentList[index].SortNumber) {
// 移动元素比目标元素所在位置小,则将位于活动元素和目标元素之间的元素排序号减一排序
if (this.currentList[_index].SortNumber > this.currentList[this.active].SortNumber && this.currentList[
_index].SortNumber <= this.currentList[index].SortNumber) {
_res.SortNumber--
}
} else {
// 反之++
if (this.currentList[_index].SortNumber < this.currentList[this.active].SortNumber && this.currentList[
_index].SortNumber >= this.currentList[index].SortNumber) {
_res.SortNumber++
}
}
return true
}, this)
this.currentList[this.active].SortNumber = temNumber
this.moveUpdateCurrentList(temNumber)
return false
} else {
return true
}
}, this)
},
- 第三部分代码是在触摸结束时,进行一些收尾工作。将活动元素的索引设置为当前触摸元素的索引,并更新相关变量的值。
touchend(e) {
if (this.currentList[this.active]) {
this.currentList[this.active].animation = true
}
this.moveUpdateCurrentList(-1)
this.active = -1
},
3、上传本地的文件、图片、音频等的问题
官方提供的API:uni.chooseFile(OBJECT)只能选择媒体文件(相册内) App端如需选择非媒体文件,需要再插件市场搜索文件选择,其中Android端可以使用Native.js,无需原生插件,而iOS端需要原生插件。
从插件库中符合要求的文件上传组件是调用打开了手机的文件管理器。
问题: 打开文件管理器很难找到自己需要的文件,实用性几乎为0
解决: 找到了一个获取到本地所有文件的所有的文件管理器(分类过的文件管理器、相册、本地安装的文档类软件等)功能比较强大,结合我们的业务做了相应的调整。
注意: 无法手动控制展示的文件管理器。