文章目录
1. 基本组件的使用
uni-app提供了丰富的基础组件给开发者,开发者可以像搭建积木一样,组合各种组件拼接成自己的应用 uni-app中的组件,就像html中的div,p,span等标签的作用一样,用于搭建页面的基础结构 https://uniapp.dcloud.net.cn/component
1. text文本组件的使用
属性 | 类型 | 默认值 | 必填 | 说明 |
---|---|---|---|---|
selectabel | boolean | false | 否 | 文本是否可选 |
space | string | 否 | 显示连续空格,可选参数: ensp,emsp,nbsp ensp:中文字符空格一半大小 emsp:中文字符空格大小 nbsp:根据字体设置的空格大小 | |
decode | boolean | false | 否 | 是否解码,支持:5+APP,H5,微信小程序 |
•text组件相当于行内标签,在同一行显示(相当于span标签)
•除了文本节点以外的其他节点都无法长按选中
•
<text>text组件</text>
<!-- text 组件使用 -->
<text>
<text>嵌套</text>
</text>
<view>
<!-- 长按可选中文本 -->
<text selectable>详情页呀详情页</text>
</view>
<view>
<!-- 显示连续空格 -->
<view>
<!-- ensp 两个个空格=一个字符大小 -->
<text space="ensp">详情页呀 详情页</text>
</view>
<view>
<!-- emsp 一个空格=一个字符大小 -->
<text space="emsp">详情页呀 详情页</text>
</view>
<view>
<!-- nbsp 三个空格=一个字符大小 -->
<text space="nbsp">详情页呀 详情页</text>
</view>
</view>
<view>
<!-- 解码 -->
<text decode>&</text>
<text decode selectable>λ</text>
</view>
2. view视图容器组件的使用
类似于HTML的div
组件属性:
属性 | 类型 | 默认值 | 必填 | 说明 |
---|---|---|---|---|
hover-class | string | none | 否 | 指定按下去的样式类,当hover-class=“none”,没有点击效果 |
hover-stop-propagation | blooean | false | 否 | 指定是否阻止本阶段的祖先阶段出现点击态 |
hover-start-time | number | 50 | 否 | 按住后多久出现点击态,单位毫秒 |
hover-stay-time | number | 400 | 否 | 手指松开后点击态保留事件,单位毫秒 |
<!-- view 组件使用 -->
<view class="f-content" hover-class="f-active">
<view class="content" hover-class="content-active" :hover-stop-propagation="true" :hover-stay-time="1000">
呀哈哈
</view>
</view>
3. button按钮组件的使用
组件属性:
属性 | 类型 | 默认值 | 说明 |
---|---|---|---|
size | string | default | 按钮的大小,可选:default,mini |
type | String | default | 按钮的样式类型,可选:primary,default,warn |
plain | Boolean | false | 按钮式否镂空,背景色透明 |
disabled | Boolean | false | 按钮是否可点击 |
loading | Boolean | false | 按钮名称是否显示loading图标状态 |
type: - primary: 微信小程序、360小程序为绿色,App、H5、百度小程序、支付宝小程序、飞书小程序、快应用为蓝色,字节跳动小程序为红色,QQ小程序为浅蓝色。如想在多端统一颜色,请改用default,然后自行写样式 - default: 白色 - warn: 红色
<view>
<text>button组件</text>
<button>呀哈哈</button>
<button size="mini" type="warn">呀哈哈</button>
<button type="primary">呀哈哈</button>
<button type="primary" plain>呀哈哈</button>
<button type="primary" disabled>呀哈哈</button>
<button type="primary" plain loading>呀哈哈</button>
</view>
4. image组件的使用
组件属性:
属性 | 类型 | 默认值 | 说明 | 平台差异说明 |
---|---|---|---|---|
src | string | 图片资源地址仅支持相对路径、绝对路径,支持 base64 码; | ||
mode | String | scaleToFill | 图片裁剪、缩放的模式 | |
lazy-load | Boolean | false | 图片懒加载。只针对page与scroll-view下的image有效 | 微信小程序、百度小程序、字节跳动小程序、飞书小程序 |
fade-show | Boolean | true | 图片显示动画效果 | 仅App-nvue 2.3.4+ Android有效 |
webp | Boolean | false | 在系统不支持webp的情况下是否单独启用webp。默认false,只支持网络资源。 | 微信小程序2.9.0 |
show-menu-by-longpress | Boolean | false | 开启长按图片显示识别小程序码菜单 | 微信小程序2.7.0 |
draggable | Boolean | true | 是否能拖动图片 | H5 3.1.1+、App(iOS15+) |
@error | HandleEvent | 当错误发生时,发布到 AppService 的事件名,事件对象event.detail = {errMsg: ‘something wrong’} | ||
@load | HandleEvent | 当图片载入完毕时,发布到 AppService 的事件名,事件对象event.detail = {height:‘图片高度px’, width:‘图片宽度px’} |
mode常用缩放 :
1、缩放: aspectFit: 保持纵横比缩放图片,使图片的长边能完全显示出来。也就是说,可以完整地将图片显示出来。
2、缩放: aspectFill: 保持纵横比缩放图片,只保证图片的短边能完全显示出来。也就是说,图片通常只在水平或垂直方向是完整的,另一个方向将会发生截取。
3、缩放:scaleToFill: 不保持纵横比缩放图片,使图片的宽高完全拉伸至填满 image 元素
4、缩放: widthFix: 宽度不变,高度自动变化,保持原图宽高比不变
5、缩放: heightFix: 高度不变,宽度自动变化,保持原图宽高比不变 App 和 H5 平台 HBuilderX 2.9.3+ 支持、微信小程序需要基础库 2.10.3
6、裁剪: top: 不缩放图片,只显示图片的顶部区域
7、裁剪: bottom: 不缩放图片,只显示图片的底部区域
8、裁剪: center: 不缩放图片,只显示图片的中间区域 - 裁剪: left 不缩放图片,只显示图片的左边区域 - 裁剪: right 不缩放图片,只显示图片的右边区域
9、裁剪: top left: 不缩放图片,只显示图片的左上边区域 - 裁剪: top right 不缩放图片,只显示图片的右上边区域 - 裁剪: bottom left 不缩放图片,只显示图片的左下边区域
10、裁剪: bottom right: 不缩放图片,只显示图片的右下边区域
<view>
<text>image组件</text>
<!-- 缩放:aspectFit 保持纵横比缩放图片,使图片的长边能完全显示出来。也就是说,可以完整地将图片显示出来。 -->
<!-- 缩放: aspectFill 保持纵横比缩放图片,只保证图片的短边能完全显示出来。也就是说,图片通常只在水平或垂直方向是完整的,另一个方向将会发生截取。 -->
<image src="http://destiny001.gitee.io/image/cxk.gif"></image>
<image src="http://destiny001.gitee.io/image/cxk.gif" mode="aspectFit"></image>
<image src="http://destiny001.gitee.io/image/cxk.gif" mode="aspectFill"></image>
</view>
5. map组件
参考【uni-app】申请高德地图key,封装map.js,实现H5、iOS、Android通过getlocation获取地图定位信息
2. uni-app中的样式
rpx 即响应式px, 一种根据平面宽度自适应的动态单位。以750宽的平面为基准,750rpx恰好为屏幕宽度。屏幕变宽,rpx实际显示效果会等比放大
使用@import语句可以导入外联样式表,@import后跟需要导入的外联样式表的相对路径,用;表示语句结束
支持基本常用的选择器class,id,element第
在uni-app中不支持使用*选择器
page相当于body节点
定义在APP.vue中的样式为全局样式,作用于每一个页面。在page目录下的vue文件中定义的样式为局部样式,只作用在对应的页面,并会覆盖APP.vue中相同的选择器
uni-app支持使用字体图标,使用方式与普通web项目相同,需要注意:
字体文件小于40kb,uni-app会自动将其转化为base64格式
字体文件大于等于40kb, 需开发者自己转换,否则不生效
字体文件的引用路径推荐使用~@开通的绝对路径
@font-face {
font-family: test-icon;
src:url('~@/static/iconfont.ttf');
}
- 如何使用scss或者less
<view class="box">
呀哈哈
<text class="iconfont icon-tupian icon"></text>
</view>
<style lang="scss">
@import url('./style.css');
.box{
width: 375rpx;
height: 375rpx;
background-color: gray;
font-size: 16rpx;
color: #fff;
.icon{
color: red;
}
}
</style>
<!-- App.vue 全局样式 -->
<style>
/*每个页面公共css */
@import url('./static/fonts/iconfont.css');
</style>
1. uni-app:px2rpx计算
rpx和px的区分和转换:
开发者可以通过设计稿基准宽度计算页面元素 rpx 值,设计稿 1px 与框架样式 1rpx 转换公式如下:
设计稿 1px / 设计稿基准宽度 = 框架样式 1rpx / 750rpx
换言之,页面元素宽度在 uni-app 中的宽度计算公式: 750 * 元素在设计稿中的宽度 / 设计稿基准宽度
举例说明:
若设计稿宽度为 750px,元素 A 在设计稿上的宽度为 100px,那么元素 A 在 uni-app 里面的宽度应该设为:750 * 100 / 750,结果为:100rpx。
若设计稿宽度为 640px,元素 A 在设计稿上的宽度为 100px,那么元素 A 在 uni-app 里面的宽度应该设为:750 * 100 / 640,结果为:117rpx。
若设计稿宽度为 375px,元素 B 在设计稿上的宽度为 200px,那么元素 B 在 uni-app 里面的宽度应该设为:750 * 200 / 375,结果为:400rpx。
注意:
- rpx 是和宽度相关的单位,屏幕越宽,该值实际像素越大。如不想根据屏幕宽度缩放,则应该使用 px 单位。
- 如果开发者在字体或高度中也使用了 rpx ,那么需注意这样的写法意味着随着屏幕变宽,字体会变大、高度会变大。
- 如果你需要固定高度,则应该使用px - rpx不支持动态横竖屏切换计算,使用rpx建议锁定屏幕方向 - 设计师可以用 iPhone6 作为视觉稿的标准
3. uni-app的数据绑定
1. 基本的数据绑定
基本的数据绑定和vue一致
<template>
<view>
{{"数据绑定"}}
<view>{{name}}</view>
<view>{{"hello word"}}</view>
<view>{{2+3}}</view>
<view>{{flag?"哦嚯嚯":"咦嘻嘻"}}</view>
<!-- v-bind -->
<image v-bind:src="url"></image>
<!-- <image :src="url"></image> -->
<!-- v-for -->
<view v-for="(item, index) in arr" :key="item.id">
姓名:{{item.name}} --- 年龄:{{item.age}}
</view>
<!-- v-on -->
<view @click="handleFClick" style="width: 375rpx;height: 375rpx;background-color: darkcyan;padding: 10rpx;">
<button type="primary" size="mini" v-on:click.stop="handleClick($event,20)">按钮</button>
<!-- <button type="primary" size="mini" @click="handleClick($event, 20)">按钮</button> -->
</view>
</view>
</template>
<script>
export default {
data() {
return {
flag:false,
name:"呀哈哈",
}
},
</script>
2. v-bind,v-for,v-on
<template>
<view>
{{"数据绑定"}}
<view>{{name}}</view>
<view>{{"hello word"}}</view>
<view>{{2+3}}</view>
<view>{{flag?"哦嚯嚯":"咦嘻嘻"}}</view>
<!-- v-bind -->
<image v-bind:src="url"></image>
<!-- <image :src="url"></image> -->
<!-- v-for -->
<view v-for="(item, index) in arr" :key="item.id">
姓名:{{item.name}} --- 年龄:{{item.age}}
</view>
<!-- v-on -->
<view @click="handleFClick" style="width: 375rpx;height: 375rpx;background-color: darkcyan;padding: 10rpx;">
<button type="primary" size="mini" v-on:click.stop="handleClick($event,20)">按钮</button>
<!-- <button type="primary" size="mini" @click="handleClick($event, 20)">按钮</button> -->
</view>
</view>
</template>
<script>
export default {
data() {
return {
flag:false,
name:"呀哈哈",
url:"http://destiny001.gitee.io/image/cxk.gif",
arr:[
{
id:1,
age:10,
name:"呀哈哈"
},
{
id:2,
age:12,
name:"哟嚯嚯"
},
{
id:3,
age:18,
name:"哦呵呵"
}
]
}
},
methods: {
handleClick(e, num){
console.log("子-按钮点击",e, num);
},
handleFClick(){
console.log("父-冒泡点击");
}
}
}
</script>
<style lang="scss">
</style>
4. uni-app的生命周期
1. uni-app 应用的生命周期
生命周期:一个对象从创建、运行、销毁的整个过程被称为生命周期 生命周期函数:在生命周期宏每个阶段都会伴随着每一个函数的触发,这些函数被称为生命周期函数
uni-app支持如下应用生命周期函数
函数名 | 说明 |
---|---|
onLaunch | 当uni-app初始化完成时触发(全局只触发一次) |
onShow | 当uni-app启动,或从后台进入前台显示 |
onHide | 当uni-app从前台进入后台 |
onError | 当uni-app报错时触发 |
应用的生命周期写在App.vue中
<script>
export default {
onLaunch: function() {
console.log('App Launch')
},
onShow: function() {
console.log('App Show')
},
onHide: function() {
console.log('App Hide')
},
onError: function(err) {
console.log(err);
}
}
</script>
2. 页面的生命周期
函数名 | 说明 |
---|---|
onLoad | 监听页面加载,其参数为上个页面传递的数据,参数类型为object(用于页面传参),触发一次 |
onShow | 监听页面显示。页面每次出现在屏幕上都触发,包括从下级页面点返回当前页面, 可触发多次 |
onReady | 监听页面初次渲染完成 |
onHide | 监听页面隐藏, 可触发多次 |
onUnLoad | 监听页面卸载 |
onPullDownRefresh | 监听用户下拉动作,一般用于下拉刷新 |
onReachBottom | 页面滚动到底部的事件(不是 scroll-view滚到底),常用于下拉下一页数据 |
onTabItemTap | 点击 tab 时触发,参数为Object, 微信小程序、QQ小程序、支付宝小程序、百度小程序、H5、App、快手小程序、京东小程序 |
执行顺序:onLoad 页面加载 => onShow 页面显示 => onReady 页面渲染完成
onTabItemTap返回的json对象说明:
- index: Number, 被点击tabItem的序号,从0开始
- pagePath: String, 被点击tabItem的页面路径
- text: String, 被点击tabItem的按钮文字
3. onPullDownRefresh: 监听下拉刷新
- onPullDownRefresh: 监听用户下拉动作,一般用于下拉刷新
在 js 中定义 onPullDownRefresh 处理函数(和onLoad等生命周期函数同级),监听该页面用户下拉刷新事件。
页面开启下拉刷新方法: - 需要在 pages.json 里,找到的当前页面的pages节点,并在 style 选项中开启 enablePullDownRefresh。 当处理完数据刷新后, uni.stopPullDownRefresh:关闭下拉刷新 ,可以停止当前页面的下拉刷新。
pages.json:
{
"path" : "pages/message/message",
"style": {
"navigationBarTitleText": "呀哈哈",
"enablePullDownRefresh": true
}
},
- uni.startPullDownRefresh(Object): 开启下拉刷新 ,调用后触发下拉刷新动画,效果与用户手动下拉刷新一致。
4. onReachBottom: 上拉加载
onReachBottom: 页面滚动到底部的事件(不是 scroll-view滚到底),常用于下拉下一页数据
<template>
<view
@touchstart="start"
@touchend="end"
@touchmove="move">
uni-app 生命周期
<button @click="handlePullDown">下拉刷新</button>
<view v-for="(item,index) in list" :key="index">
<text>name: {{item}} --- index: {{index}}</text>
</view>
<u-button v-if="showTab">展示切换tab</u-button>
</view>
</template>
<script>
export default {
data() {
return {
showTab:true,
startData: {
clientX: '',
clientY: '',
},
list:new Array(10).fill('').map((item,index)=>item='msg_'+index),
}
},
onPullDownRefresh() {
console.log("触发下拉刷新了。");
setTimeout(()=>{
let addArr = Array(10).fill("").map((item,index)=>item = "msg_"+(index+this.list.length));
this.list = [...this.list,...addArr];
uni.stopPullDownRefresh();
console.log("下拉刷新结束。");
},1000)
},
onReachBottom() { // 页面上拉触底事件触发时距页面底部距离默认 50px
console.log("页面滚动到底部了");
let addArr = Array(10).fill("").map((item,index)=>item = "msg_"+(index+this.list.length));
this.list = [...this.list,...addArr];
},
onLoad(options){
console.log("onLoad -- 生命周期页面加载了。");
},
onShow(){
console.log("onShow -- 生命周期页面显示了。");
},
onReady(){
console.log("onReady -- 生命周期页面初次渲染完成了。");
},
onHide(){
console.log("onHide -- 生命周期页面隐藏了。");
},
methods: {
// 下拉刷新
handlePullDown(){
uni.startPullDownRefresh();
},
// 触摸touch事件
start(e){ //@touchstart 触摸开始
this.startData.pageX = e.changedTouches[0].pageX; //手指按下时的X坐标
this.startData.pageY = e.changedTouches[0].pageY; //手指按下时的Y坐标
console.log('this.startData', this.startData)
},
end(){
// this.showTab = true;
},
move(event) { //@touchmove触摸移动
let touch = event.touches[0]; //滑动过程中,手指滑动的坐标信息 返回的是Objcet对象
console.log(touch.pageY)
if(touch.pageY < this.startData.pageY) {
console.log('向下移动')
this.showTab = false;
}else{
console.log('向上移动')
this.showTab = true;
}
},
}
}
</script>
5. 网络请求
在uni中可以调 uni.request方法进行网络请求 在小程序网络相关的api在使用前需要配置域名白名单(本地环境不需要,发布上线时再修改) uni.request(Object)部分参数:uni.request
属性 | 类型 | 默认值 | 必填 | 说明 |
---|---|---|---|---|
url | String | 是 | 开发者服务器接口地址 | |
data | Object/String/ArrayBuffer | 否 | 请求的参数 | |
header | Object | 否 | 设置请求的header,header中不能设置Referer | |
method | String | GET | 否 | 有效值:GET, POST,PUT, DELETE, CONNECT, HEAD, POTIONS, TRACE |
timeout | Number | 60000 | 否 | 超时事件,单位ms |
dataTyoe | String | json | 否 | 如果设为json, 会阐释对返回的数据做一次JSON.parse |
responseType | String | text | 否 | 设置响应的数据类型,合法值:text、arraybuffer |
success | Function | 否 | 收到开发者服务器成功返回的回调函数 | |
fail | Function | 否 | 接口调用失败的回调函数 | |
complete | Function | 否 | 接口调用结束的回调函数(调用成功、失败都会执行) |
1. 发送请求: uni-app 原生api
<button @click="handleGetData">GET请求</button>
<script>
methods:{
handleGetData(){
uni.request({
url:"http://localhost:8082/api/getlunbo",
success(res) {
console.log(res);
},
fail() {
},
complete() {
}
})
}
}
</script>
注:小程序如发生失败可在:详情=>本地设置=>勾选不校验合法域名、web-view(业务域名)、TLS版本以及HTTPS证书
2. 项目封装http.js 发送请求
文件路径:lib/http/index.js
import request from './http.js'
// import file from './file.js' //todo 跟http合并为一个
import Config from '.env'
// 接口默认超时时间毫秒
const Timeout = Config.service.timeout.default
const TimeoutObject = {
errorNo: '408',
errorInfo: '请求超时,请检查网络连接'
}
const NotFoundObject = {
errorNo: '404',
errorInfo: '无效的请求地址'
}
const http = {
/**
* Form表单请求接口
* @param {String} url 请求地址
* @param {Object} option 请求参数
* @param {Number} timeout 请求超时时间,单位:毫秒
*/
formRequest: (url, option, timeout) => {
return Promise.race([
new Promise((resolve, reject) => {
if (!url) {
return reject(NotFoundObject)
}
let config = {
valString: false,
timeout: timeout
}
request.post(url, option, config).then(r => resolve(r)).catch(e => reject(e))
}),
new Promise((resolve, reject) => {
setTimeout(() => reject(TimeoutObject), timeout)
})
])
},
/**
* JSON请求接口
* @param {String} url 请求地址
* @param {Object} option 请求参数
* @param {Number} timeout 请求超时时间,单位:毫秒
*/
jsonRequest: (url, option, timeout = Timeout) => {
return Promise.race([
new Promise((resolve, reject) => {
if (!url) {
return reject(NotFoundObject)
}
let config = {
timeout: timeout,
valString: option.valString == undefined ? false : option.valString,
isConvert: option.isConvert == undefined ? true : option.isConvert,
header: { // JSON格式
'Content-Type': 'application/json'
},
transformRequest: [
data => { // JSON序列化
// 去除空数组,防止IAR报错
data = JSON.stringify(data, (_key, val) => (
val instanceof Array && val.length === 0) ?
undefined : val)
return data
}
]
}
// 发送异步请求
request.post(url, option, config).then(r => resolve(r)).catch(e => reject(e))
}),
new Promise((resolve, reject) => {
setTimeout(() => reject(TimeoutObject), timeout)
})
])
},
/**
* JSON请求接口
* @param {String} url 请求地址
* @param {Object} option 请求参数
* @param {Number} timeout 请求超时时间,单位:毫秒
*/
jsonGetRequest: (url, option, timeout = Timeout) => {
return Promise.race([
new Promise((resolve, reject) => {
if (!url) {
return reject(NotFoundObject)
}
let config = {
timeout: timeout,
valString: option.valString == undefined ? false : option.valString,
isConvert: option.isConvert == undefined ? true : option.isConvert,
header: { // JSON格式
'Content-Type': 'application/json'
},
transformRequest: [
data => { // JSON序列化
// 去除空数组,防止IAR报错
data = JSON.stringify(data, (_key, val) => (
val instanceof Array && val.length === 0) ?
undefined : val)
return data
}
]
}
// 发送异步请求
request.get(url, option, config).then(r => resolve(r)).catch(e => reject(e))
}).catch(e=>console.log(e)),
new Promise((resolve, reject) => {
setTimeout(() => reject(TimeoutObject), timeout)
})
])
},
/**
* 文件上传接口
* @param {String} url 请求地址
* @param {Object} option 请求参数
* @param {Number} timeout 请求超时时间,单位:毫秒
*/
fileRequest: (url, option, timeout = Timeout) => {
return Promise.race([
new Promise((resolve, reject) => {
if (!url) {
return reject(NotFoundObject)
}
let config = {
timeout: timeout,
header: {
'Access-Type': 'register'
}
}
// 用独立的实例发送异步请求
request.post(url, option, config).then(r => resolve(r)).catch(e => reject(e))
}),
new Promise((resolve, reject) => {
setTimeout(() => reject(TimeoutObject), timeout)
})
])
}
}
export default http;
lib/http.js
import Request from './request.js'
import Config from '.env'
// import LS from '../storage'
// import { isJsonString } from '../utils'
// import { showAlert } from '../alert'
// 是否开启字段转换
const isEnableConvert = Config.service.isConvert
// 创建http实例
const httpFetch = new Request();
// request拦截器
httpFetch.interceptor.request(async config => {
// // 获取用户会话
// let r = await LS.getAccessToken()
// if (r && r.ok && r.data.token) {
// config.data['curr_session_id'] = r.data.token
// }
// // 设置客户端IP地址
// r = await LS.getClientIp()
// if (r && r.ok && r.data.clientIp) {
// config.data['curr_ip'] = r.data.clientIp
// }
// // 设置市场编码 todo
// if (Config.market) {
// config.data['market_id'] = Config.market
// }
// if (isEnableConvert && config.data) {
// let item = {},
// valString = config.valString == undefined ? true : config.valString,
// isConvert = config.isConvert == undefined ? true : config.isConvert
// delete config.isConvert
// delete config.valString
// convert(config.data, item, isConvert, valString)
// config.data = item
// }
return config
})
// response拦截器
httpFetch.interceptor.response(rsp => {
if (!rsp.data) return rsp
// ie中rsp.data返回为string类型,chorme为obj类型
if (typeof rsp.data === 'string' && isJsonString(rsp.data)) {
rsp.data = JSON.parse(rsp.data)
}
// 兼容IAR模式追加data模式
if (rsp.data.data) {
rsp.data = rsp.data.data.length === 1 ? rsp.data.data[0] : rsp.data.data
}
// 获取错误码
let errInfo = ''
const errCode = getErrorCode(rsp)
/**
* 9008错误码处理
* 对不起,您缺少访问权限
*/
if (errCode === '9008' || errCode === 9008) {
errInfo = '对不起,您缺少访问权限'
doLogout(errInfo, () => {})
return new Promise((resolve, reject) => {})
}
/**
* 9004错误码处理
* 此账号在异地登录
*/
if (errCode === '9004' || errCode === 9004) {
errInfo = getErrorInfo(rsp)
doLogout(errInfo)
return new Promise((resolve, reject) => {})
}
/**
* 9007错误码处理
* 会话已失效,请重新登录
*/
if (errCode === '9007' || errCode === 9007) {
errInfo = '会话已失效,请重新登录'
doLogout(errInfo)
return new Promise((resolve, reject) => {})
}
// 删除多余的字段
cleanInvalidAttr(rsp)
// 获取最终的返回结果
let result = rsp.data
// 参数名称格式转换
if (canConvert(rsp, result)) {
let tempItem = {}
if (Array.isArray(result)) {
tempItem = result.map(obj => {
let innerItem = {}
// convert(obj, innerItem, false, false)
return innerItem
})
} else {
// convert(result, tempItem, false, false)
}
result = tempItem
}
// 如果有错误统一按照错误处理
if (errCode !== '' && errCode !== '0' && errCode!==undefined && errCode!==null) {
return Promise.reject(result)
}
return result
})
/**
* 退出登录处理
* @param {*} errInfo
* @param {*} callback
*/
function doLogout(errInfo, callback) {
// const func = callback ? callback : () => {
// // 清空本地缓存
// LS.clearNativeData()
// // 跳转到登录页面同时清除历史记录防止后退
// uni.reLaunch({
// url: 'user/login',
// });
// }
// return showAlert({
// title: '提示',
// message: errInfo
// }, func)
}
// 获取错误编号
function getErrorCode(rsp) {
const r = rsp.data
return r.error_code || r.errorCode || r.error_no || r.errorNo
}
// 获取错误信息
function getErrorInfo(rsp) {
const r = rsp.data
return r.error_info || r.errorInfo
}
// 清理多余的字段
function cleanInvalidAttr(rsp) {
// 删除多余的字段
const r = rsp.data
if (r.data !== undefined && r.data == null) {
delete rsp.data.data
}
if (r.dataInfo !== undefined && r.dataInfo == null) {
delete rsp.data.datainfo
}
if (r.data_info !== undefined && r.data_info == null) {
delete rsp.data.data_info
}
if (r.error_extinfo !== undefined && r.error_extinfo == null) {
delete rsp.data.error_extinfo
}
}
// 是否需要参数名称格式转换
function canConvert(rsp, result) {
// let b = hasConvert(rsp.config.url)
return isEnableConvert && (null != result && typeof result === 'object') && rsp.config.url
}
/**
* 网络异常处理
* @param {*} err
*/
function networkError(err) {
const {
code,
message
} = err
return {
errorCode: code,
errorInfo: message,
success: false
}
}
/**
* 公有方法:判断是否是json格式
* @param {*} str
*/
function isJsonString(str) {
if (typeof str === 'string') {
try {
var obj = JSON.parse(str)
if (typeof obj === 'object' && obj) {
return true
}
return false
} catch (e) {
return false
}
}
}
function showAlert(option, handler) {
uni.showModal({
title: '提示',
content: option.message,
showCancel: false,
success: handler
});
}
export default httpFetch;
lib/request.js
import Config from '.env'
export default class Request {
config = {
baseUrl: Config.service.baseUrl,
header: {
'Content-Type': 'application/json;charset=UTF-8'
},
method: 'GET',
dataType: 'json',
responseType: 'text',
timeout: Config.service.timeout.default || 60000,
success() {},
fail() {},
complete() {}
}
static posUrl(url) {
/* 判断url是否为绝对路径 */
return /(http|https):\/\/([\w.]+\/?)\S*/.test(url)
}
interceptor = {
request(f) {
if (f) {
Request.requestBeforeFun = f
}
},
response(f) {
if (f) {
Request.requestComFun = f
}
}
}
static requestBeforeFun(config) {
return config
}
static requestComFun(response) {
return response
}
// 更改基础请求路径
setConfig(f) {
this.config = f(this.config)
}
request(options = {}) {
options.baseUrl = options.baseUrl || this.config.baseUrl
options.dataType = options.dataType || this.config.dataType
options.url = Request.posUrl(options.url) ? options.url : (options.baseUrl + options.url)
options.data = options.data || {}
options.header = options.header || this.config.header
options.method = options.method || this.config.method
return new Promise(async (resolve, reject) => {
let next = true
let _config = null
options.complete = (response) => {
let statusCode = response.statusCode
response.config = _config
response = Request.requestComFun(response)
if (statusCode === 200) { // 成功
resolve(response)
} else {
if (response){
reject(response)
}else{
reject(null);
}
}
}
let cancel = (t = 'handle cancel') => {
let err = {
errMsg: t,
config: afC
}
reject(err)
next = false
}
let afC = {
...this.config,
...options
}
_config = {
...afC,
...await Request.requestBeforeFun(afC, cancel)
}
if (!next) return
uni.request(_config)
})
.catch(e=>console.log(e));
}
// 设置 get 和 post 请求 需要url请求地址 data所需参数
get(url, data, options = {}) {
options.url = url
options.data = data
options.method = 'GET'
// 使用 this.request 发起请求,传入参数,获取数据
return this.request(options)
}
post(url, data, options = {}) {
options.url = url
options.data = data
options.method = 'POST'
return this.request(options)
}
}
6. 数据缓存
1. 异步uni.setStorage,同步setStorageSync
uni.setStorage(object):将数据存储在本地缓存中指定的key中,会覆盖掉原来key对应的内容,是一个异步接口。
参数:
属性 | 类型 | 必填 | 说明 |
---|---|---|---|
key | string | 是 | 本地缓存中的指定的 key |
data | Any | default | 按钮的样式类型,可选:primary,default,warn |
plain | Boolean | 是 | 需要存储的内容,只支持原生类型、及能够通过 JSON.stringify 序列化的对象 |
success | Function | 否 | 接口调用成功的回调函数 |
fail | Function | 否 | 接口调用失败的回调函数 |
complete | Function | 否 | 接口调用结束的回调函数(调用成功、失败都会执行) |
同步存储 :uni.setStorageSync
2. 异步uni.getStorage,同步getStorageSync
uni.getStorage(Object): 异步从本地缓存中异步获取指定 key 对应的内容。 参数:
属性 | 类型 | 必填 | 说明 |
---|---|---|---|
key | string | 是 | 本地缓存中的指定的 key |
success | Function | 否 | 接口调用的回调函数,res = {data: key对应的内容} |
fail | Function | 否 | 接口调用失败的回调函数 |
complete | Function | 否 | 接口调用结束的回调函数(调用成功、失败都会执行) |
同步获取 :uni.getStorageSync
3. 异步uni.removeStorage,同步removeStorageSync
uni.removeStorage(Object): 异步从本地缓存中异步移除指定 key。
属性 | 类型 | 必填 | 说明 |
---|---|---|---|
key | string | 是 | 本地缓存中的指定的 key |
success | Function | 否 | 接口调用的回调函数 |
fail | Function | 否 | 接口调用失败的回调函数 |
complete | Function | 否 | 接口调用结束的回调函数(调用成功、失败都会执行) |
同步删除 :uni.removeStorageSync
清理本地数据缓存:uni.clearStorage(),uni.clearStorageSync()
<script>
export default {
data() {
return {
item:{msg:"信息描述"}
}
},
onLoad() {
console.log("onLoad -- 生命周期页面加载了。");
},
onUnload() {
console.log("导航页面卸载。");
},
methods:{
goDetail(){
uni.navigateTo({
url:"/pages/detail-component/detail?id='1'&name='yhh'"
})
},
goMessage(){
uni.setStorageSync('name', 'yhh');
uni.switchTab({
url:"/pages/message/message"
})
},
redirectDetail(){
uni.redirectTo({
url:"/pages/detail-component/detail?id='1'"
})
},
urlDetail(){
uni.navigateTo({
url:"/pages/detail-component/detail?item="+encodeURIComponent(JSON.stringify(this.item))
})
}
}
}
</script>
7. 图片的上传和预览
1. uni.chooseImage(object): 图片上传
1、uni.chooseImage(object) 方法从本地相册选择图片或使用相机拍照
2、App端如需要更丰富的相机拍照API(如直接调用前置摄像头),可使用组件https://uniapp.dcloud.net.cn/component/camera.html ,
uniApp开发调用摄像头拍照, 想要设置默认为前置摄像头: 使用uni.chooseImage 是无法设置这一默认值的,只有chooseVideo(即拍摄视频)支持
- 如果对兼容性没有要求,只是在小程序用,可使用组件 camera,通过参数 device-position控制, 但不支持H5,App端
2. uni.previewImage(object): 图片预览
3. camera组件
页面内嵌的区域相机组件。注意这不是点击后全屏打开的相机。https://uniapp.dcloud.net.cn/component/camera.html
支持:微信小程序,百度小程序,QQ小程序,快应用,快手小程序,京东小程序 ,在 App 和 H5 端,可以使用API方式来调用全屏摄像头,而不是组件内嵌方式,详见:uni.chooseImage 和 uni.chooseVideo
相关api: https://uniapp.dcloud.net.cn/api/media/camera-context.html#:
创建并返回 camera 组件的上下文 cameraContext 对象。
<template>
<view>
图片上传
<u-button @click="chooseImg">上传图片</u-button>
<image v-for="item in imgArr" :src="item" :key="item" @click="viewImg"></image>
<br />
<text>camera组件,仅支持小程序</text>
<u-button @click="getCamera">拍照</u-button>
<!-- 相机拍照 -->
<view class="" v-if="ifPhoto">
<!-- 相机 -->
<camera :device-position="convert" flash="off" @error="error" class="camera"> </camera>
<!-- 操作 -->
<view class="padding bottom_code flex align-center justify-between">
<!-- 返回 -->
<view class="code_button" @click="back">
<image src="../../static/tabs/icon_return.png" mode="aspectFill"></image>
</view>
<!-- 拍照 -->
<view class="code_button" @click="takePhoto">
<image src="../../static/tabs/camera.png" mode="aspectFill"></image>
</view>
<!-- 切换摄像头 -->
<view class="code_button" @click="showConvert">
<image src="../../static/tabs/camera_switch.png" mode="aspectFill"></image>
</view>
</view>
</view>
<!-- 照片查看 -->
<view class="" v-if="src">
<view>
<image :src="src" mode="aspectFit"></image>
</view>
<!-- 操作 -->
<view style="display: flex;justify-content: space-around;">
<button size="mini" type="default" @click="anew">重新拍摄</button>
<button size="mini" type="primary" @click="uploading">上传</button>
</view>
</view>
</view>
</template>
<script>
export default {
data() {
return {
imgArr:[],
//true 拍照 false 查看
ifPhoto: false,
//照片
src: null,
oldSrc: null,
//前置或后置摄像头,值为front, back
convert: 'front'
}
},
methods: {
chooseImg(){
uni.chooseImage({
count:5, // 默认9
sizeType: ['original', 'compressed'], // 可以指定是原图还是压缩图,默认二者都有
// sourceType: ['camera'], // 拍照
success: (res) => {
console.log(res,"===>图片");
this.imgArr = res.tempFilePaths;
}
})
},
viewImg(){
// 预览图片
uni.previewImage({
urls: this.imgArr,
loop:true,
indicator:'number',
longPressActions: {
itemList: ['发送给朋友', '保存图片', '收藏'],
success: function(data) {
console.log('选中了第' + (data.tapIndex + 1) + '个按钮,第' + (data.index + 1) + '张图片');
},
fail: function(err) {
console.log(err.errMsg);
},
}
});
},
getCamera(){
this.ifPhoto = true;
},
//拍照
takePhoto() {
const ctx = uni.createCameraContext();
ctx.takePhoto({
quality: 'high',
success: (res) => {
console.log(res);
this.src = res.tempImagePath
if (this.src != null) {
this.ifPhoto = false
}
}
});
},
//摄像头启动失败
error(e) {
console.log(e.detail);
},
//切换摄像头
showConvert() {
if (this.convert == 'front') {
// 后置
this.convert = 'back'
} else {
// 前置
this.convert = 'front'
}
},
//返回
back() {
// uni.navigateBack({
// delta: 1
// })
this.ifPhoto = false;
this.src = this.oldSrc;
},
//重新
anew() {
this.oldSrc = this.src;
this.src = '';
this.ifPhoto = true;
},
//上传
uploading() {
console.log('上传成功');
}
}
}
</script>
<style lang="scss">
// 相机
.camera {
width: 100%;
height: 50vh;
}
//操作
.bottom_code {
position: fixed;
bottom: 10rpx;
left: 0;
width: 100%;
height: 120rpx;
// background-color: #1CA6EC;
display: flex;
justify-content: space-around;
.code_button {
width: 90rpx;
height: 90rpx;
// border-radius: 50%;
image {
width: 100%;
height: 100%;
}
}
}
.img_code {
width: 100%;
height: 80vh;
padding-top: 180rpx;
image {
width: 100%;
height: 100%;
}
}
</style>
8. 条件编译跨端兼容
https://uniapp.dcloud.net.cn/tutorial/platform.html
uni-app已将常用的组件、js api封装到框架中,开发者按照uni-app规范开发即可保证多平台兼容,大部分业务均可直接满足 但每个平台都有自己的一些特性,因此会存在一些无法跨平台的情况。 * 大量写if else , 会造成代码执行性能低下和管理混乱 * 编译到不同的工程后二次修改,会让后续升级变得很麻烦
在 C 语言中,通过 #ifdef、#ifndef 的方式,为 windows、mac 等不同 os 编译不同的代码。 uni-app 参考这个思路,为 uni-app 提供了条件编译手段,在一个工程里优雅的完成了平台个性化实现。
以 #ifdef 或 #ifndef 加 %PLATFORM% 开头,以 #endif 结尾。
- ifdef:if defined 仅在某平台存在
- ifndef:if not defined 除了某平台均存在
- %PLATFORM%:平台名称
// 仅出现在 App 平台下的代码
#ifdef APP-PLUS
需条件编译的代码
#endif
// 除了 H5 平台,其它平台均存在的代码
#ifndef H5
需条件编译的代码
#endif
// 在 H5 平台或微信小程序平台存在的代码(这里只有||,不可能出现&&,因为没有交集)
#ifdef H5 || MP-WEIXIN
需条件编译的代码
#endif
条件编译是利用注释实现的,在不同语法里注释写法不一样,js使用 // 注释、css 使用 /* 注释 */、vue/nvue 模板里使用 <!-- 注释 -->;```
```html
<template>
<!-- #ifdef H5 -->
<view>这段文字仅在h5页面展示</view>
<!-- #endif -->
<!-- #ifdef MP-WEIXIN -->
<view>这段文字仅在微信小程序页面展示</view>
<!-- #endif -->
</view>
</template>
<script>
export default {
data() {
return {
}
},
methods: {
onLoad() {
// #ifdef H5
console.log("仅在h5页面打印出来\(^o^)/~");
// #endif
// #ifdef MP-WEIXIN
console.log("仅在微信小程序页面打印出来\(^o^)/~");
// #endif
},
}
</script>
<style>
/* #ifdef H5 */
view{
background-color: aquamarine;
}
/* #endif */
/* #ifdef MP-WEIXIN */
view{
background-color: coral;
}
/* #endif */
</style>
%PLATFORM% 可取值如下:
- APP-PLUS : App
- APP-PLUS-NVUE或APP-NVUE : App nvue 页面
- APP-ANDROID : App Android 平台 仅限 uts文件
- APP-IOS : App iOS 平台 仅限 uts文件
- H5 : H5
- MP-WEIXIN : 微信小程序
- MP-ALIPAY : 支付宝小程序
- MP-BAIDU : 百度小程序
- MP-TOUTIAO : 字节跳动小程序
- MP-LARK : 飞书小程序
- MP-QQ : QQ小程序
- MP-KUAISHOU : 快手小程序
- MP-JD : 京东小程序
- MP-360 : 360小程序
- MP : 微信小程序/支付宝小程序/百度小程序/字节跳动小程序/飞书小程序/QQ小程序/360小程序
- QUICKAPP-WEBVIEW : 快应用通用(包含联盟、华为)
- QUICKAPP-WEBVIEW-UNION : 快应用联盟
- QUICKAPP-WEBVIEW-HUAWEI : 快应用华为
static 目录的条件编译
在不同平台,引用的静态资源可能也存在差异,通过 static 的条件编译可以解决此问题,static 目录下新建不同平台的专有目录,专有目录下的静态资源只有在特定平台才会编译进去。
9. uni中导航的跳转和传参
1. 使用navigator组件进行跳转
https://uniapp.dcloud.net.cn/component/navigator.html
1、该组件类似HTML中的组件,但只能跳转本地页面。目标页面必须在pages.json中注册。
2、url有长度限制,太长的字符串会传递失败,可使用窗体通信、全局变量,或encodeURIComponent等多种方式解决
3、tabbar不可通过url进行传参,可以通过在main.js定义全局变量,或使用本地存储
2. 使用编程式导航进行跳转
https://uniapp.dcloud.net.cn/api/router.html#navigateto
•uni.navigateTo(OBJECT):保留当前页面,跳转到应用内的某个页面,使用uni.navigateBack可以返回到原页面。
•uni.redirectTo(OBJECT):关闭当前页面,跳转到应用内的某个页面。
•uni.switchTab(OBJECT):跳转到 tabBar 页面,并关闭其他所有非 tabBar 页面。
•uni.navigateBack(OBJECT):关闭当前页面,返回上一页面或多级页面。可通过 getCurrentPages() 获取当前的页面栈,决定需要返回几层。
•uni.preloadPage(OBJECT):预加载页面,是一种性能优化技术。被预载的页面,在打开时速度更快。支持:H5,App-nvue
注:由于生命周期onLoad只执行一次,当点击返回按钮返回上一级页面时,并不会触发上一页页面的onLoad以到达传递参数的目的。因为navigateTo是保留当前页面,跳转到下一级页面。所以如果需要再返回上一级页面传递参数时,可以使用:页面通讯uni.on(),localStorage等。
<template>
<view>
<view>navigator导航跳转</view>
<!-- 1. 跳转到普通页面 -->
<navigator url="/pages/detail-component/detail?id='1'&name='yhh'">
<button size="mini" type="default">跳转至详情页1</button>
</navigator>
<!-- 2. 跳转到tabbar页面:open-type="switchTab",
tabbar不可通过url进行传参,可以通过在main.js定义全局变量,或使用本地存储
-->
<navigator url="/pages/message/message?id='1'" open-type="switchTab">
<button size="mini" type="default">跳转至信息页</button>
</navigator>
<!-- 3. 在当前页面打开,并关闭当前页面:open-type="redirect" -->
<navigator url="/pages/detail-component/detail?id='1'" open-type="redirect">
<button size="mini" type="default">跳转至详情页2</button>
</navigator>
<!-- 4. url长度限制:encodeURIComponent(str) 编码 -->
<navigator :url="'/pages/detail-component/detail?item='+encodeURIComponent(JSON.stringify(item))">
<button size="mini" type="default">跳转至详情页3</button>
</navigator>
<br />
<view>编程式导航跳转</view>
<button size="mini" type="default" @click="goDetail">跳转至详情页1</button>
<button size="mini" type="default" @click="goMessage">跳转至信息页</button>
<button size="mini" type="default" @click="redirectDetail">跳转至详情页2</button>
<button size="mini" type="default" @click="urlDetail">跳转至详情页3</button>
</view>
</template>
<script>
export default {
data() {
return {
item:{msg:"信息描述"}
}
},
onLoad() {
console.log("onLoad -- 生命周期页面加载了。");
},
onUnload() {
console.log("导航页面卸载。");
},
methods:{
goDetail(){
uni.navigateTo({
url:"/pages/detail-component/detail?id='1'&name='yhh'"
})
},
goMessage(){
uni.setStorageSync('name', 'yhh');
uni.switchTab({
url:"/pages/message/message"
})
},
redirectDetail(){
uni.redirectTo({
url:"/pages/detail-component/detail?id='1'"
})
},
urlDetail(){
uni.navigateTo({
url:"/pages/detail-component/detail?item="+encodeURIComponent(JSON.stringify(this.item))
})
}
}
}
</script>
<style>
3. 项目封装的 $navigate 进行跳转
main.js 封装:
// 扩展路由导航
Vue.prototype.$navigate = (route, params = {}, option = {}) => {
try {
if (route.includes('?')) {
let p = route.split('?')[1]
route = route.split('?')[0]
p.split("&").map(it => {
params[it.split('=')[0]] = it.split('=')[1]
})
}
let paramsData = ""
let history = (option.history === false || option.history === 'false') && option.history.toString()
let switchTab = params.switchTab && params.switchTab.toString() || 'false';
let navigateFun = history === 'false' ? uni.redirectTo : switchTab == 'true' ? uni.reLaunch : uni
.navigateTo;
route = route.startsWith('/') ? route : '/' + route
route = route.startsWith('/pages') ? route : '/pages' + route
Object.keys(params).forEach(function(key) {
paramsData += key + '=' + encodeURIComponent(params[key]) + '&'
})
paramsData = paramsData == "" ? "" : '?' + paramsData.substring(0, paramsData.length - 1)
return navigateFun({
url: route + paramsData,
});
} catch (e) {
console.log(e)
}
}
// 使用:
confirmPick(item){
this.jumpByDefault('/receipt/pick/pickDetail', { detailId: item.id, otpType:'insert', title:'新增仓单提货' });
},
jumpByDefault (link, opt = {}) {
this.$navigate(link, opt, {})
},
// url有长度限制,太长的字符串会传递失败,使用 encodeURIComponent进行传参:
handleBook(item){
this.$navigate('/pages/ctl/custody/custodyDetail', { detail: encodeURIComponent(JSON.stringify(item)) });
},
// 接受参数:
onLoad(params){
console.log( JSON.parse(decodeURIComponent(params.detail)) )
},
10. 组件的创建和生命周期
组件的生命周期
• beforeCreated: 在实例初始化之后被调用
• created: 在实例创建完成后被立即调用
• beforeMount: 在挂载开始前被调用
• mounted: 挂载到实例之后被调用。此处并不能确定子组件被全部挂载,如需要子组件完成挂载之后再执行操作可以使用$nextTick
• beforeUpdate: 数据更新时调用,发生在虚拟DOM达不到之前,仅支持H5
• updated: 由于数据更新导致的虚拟DOM重新渲染和达不到,在这一行调用,仅支持H5
• beforeDestory: 实例销毁之前调用,在这异步实例仍然可以使用
• destoryed:
实例销毁后调用,调用后,vue实例指示的所有东西都会解绑,所有事件监听器都会被移除,所有的子实例也会被销毁。
因为生命周期属于组件,所以当虚拟DOM树对比完后,只会对此组件的虚拟DOM树要更新的部分做更新。 而子组件在虚拟DOM树中,只知道子组件的构造器(Ctor),传入的数据(data)和子项(children)有没有变化,不关心子组件内部的虚拟DOM树。 因此, 只能保证这个组件要去渲染它的子组件,却无法保证它的子组件内部是如何渲染的。
当使用nextTick时,将在微任务(不支持微任务的浏览器将回退至宏任务)堆栈中入栈你写的回调。如果所有子组件都已经下载完毕,并在Vue中定义,
则从根组件往后渲染时,因为用的都是同步方法,微任务将在这些同步方法后被执行,所以大多数时候可以通过nextTick获取子组件渲染后的DOM节点。
但是如果实例化组件的时候,有其他微任务入栈,就要看微任务队列的执行顺序了,所以无法完全保证。
对于异步组件,由于网络原因,import微任务总在$nextTick之后,所以总是拿不到子组件的DOM。 而在setTimeout一定时间之后,由于setTimeout是宏任务,所以一般会在渲染后执行。 但对于异步组件,如果由于网络延迟,导致微任务在宏任务之后入栈,则在setTimeout的回调中仍无法获取子组件的DOM。
简单的来说, 由于有异步执行顺序的影响,只能保证单独组件内部的执行顺序,无法保证其他组件的解析注册执行顺序。
11. 组件通讯
• 父子组件之间通讯:props
• 同层级组件之间通讯:https://uniapp.dcloud.net.cn/api/window/communication.html
12、小程序运行含未引入组件报错
微信开发者工具从 1.05.2201210 版本开始,对小程序项目新增了无依赖文件过滤能力。
如果某个 js 文件被静态分析显示是无依赖文件,在实际运行时又被其他 js 文件 require 引用了,则会在工具模拟器中报错这个错误。
解决方式: 关闭过滤无依赖文件:project.config.json 中 settings 选项添加 ignoreDevUnusedFiles: false , ignoreUploadUnusedFiles: false,清除缓存,然后再重新编译就好了