uniapp 知识点归纳
两个小程序之间相互跳转
- 先在
manifest.json
/app.json
(如果是原生写法) 文件中进行配置
"navigateToMiniProgramAppIdList" : [
"xxx" //需要跳转的小程序appid 小程序B
],
- 在点击事件中写入以下函数进行配置
//跳转到 B
uni.navigateToMiniProgram({
appId: 'xxx',
path: 'pages/index/index',
extraData: {},
envVersion: 'trial',//打开版本 开发版 develop 体验版trial 正式版release
success(res) {
}
})
- 小程序B接收时,直接在
Onload(option)
中进行接收A带来的参数
uniapp 组件库
注意:以下是两个图标组件的图标库
uni-ui 组件源码
Editor 富文本组件
由于该组件按官方文档引入,只有输入框,并没有文档中描述的那些工具栏。经过查找,以下提供两种方式进行使用
方式一
引用封装好的组件
- 先引入组件代码
- 使用组件
vue<uniEditor ref="uniEditor" :editorDetail="params.content" @getContents="getContents"></uniEditor>
jsimport uniEditor from '../components/editor/index.vue' export default { components: { uniEditor //这步不要忘了 }, data() { return { params: { content: null //用于接收输入的富文本 } } }, methods: { // 获取富文本内容 getContents(html){ this.params.content = html }, } }
这一步,是在父组件使用editor时绑定了值
editorDetail
和方法getContents
。一旦编辑器有了变化,就能监听到然后触发getContents
方法,并把最新的值赋给content
- 📖 参考文档 及 封装组件的源码
方式二
自写按钮,把需要用到的editor组件的方法,单独复制出来作为使用。
- 正常引入
editor
组件 - 按需写按钮
- 按钮的点击事件,去
editor
源码里复制。或者参考官方文档的API
- 正常引入
u-parse 富文本内容显示
这里主要是对后端传回的富文本内容做一下处理
<u-parse :content="detailHtml"/>
import {formatRichText} from '@/utils/formatRichText.js'
export default {
data() {
detailHtml: null //格式化后的富文本数据
},
onLoad() {
this.detailHtml = formatRichText('后端传回的富文本参数')
}
}
处理富文本的js文件
/**
* 处理富文本里的图片宽度自适应
* 1.去掉img、video标签里的style、width、height属性
* 2.img标签添加style属性:max-width:100%;height:auto
* 3.修改所有style里的width属性为max-width:100%
* 4.去掉<br/>标签
* @param html
* @returns {void|string|*}
*/
function formatRichText(html){
// let newContent= html.replace(/<img[^>]*>/gi,function(match,capture){
// match = match.replace(/style="[^"]+"/gi, '').replace(/style='[^']+'/gi, '');
// match = match.replace(/width="[^"]+"/gi, '').replace(/width='[^']+'/gi, '');
// match = match.replace(/height="[^"]+"/gi, '').replace(/height='[^']+'/gi, '');
// return match;
// });
// newContent = newContent.replace(/style="[^"]+"/gi,function(match,capture){
// match = match.replace(/width:[^;]+;/gi, 'max-width:100%;').replace(/width:[^;]+;/gi, 'max-width:100%;');
// return match;
// });
// newContent = newContent.replace(/<br[^>]*\/>/gi, '');
// newContent = newContent.replace(/\<img/gi, '<img style="max-width:100%;height:auto;display:block;margin-top:0;margin-bottom:0;"');
// return newContent;
// 去掉img标签里的style、width、height属性
let newContent= html.replace(/<img[^>]*>/gi,function(match,capture){
match = match.replace(/style="[^"]+"/gi, '').replace(/style='[^']+'/gi, '');
match = match.replace(/width="[^"]+"/gi, '').replace(/width='[^']+'/gi, '');
match = match.replace(/height="[^"]+"/gi, '').replace(/height='[^']+'/gi, '');
return match;
});
// 去掉video标签里的style、width、height属性
newContent= html.replace(/<video[^>]*>/gi,function(match,capture){
match = match.replace(/style="[^"]+"/gi, '').replace(/style='[^']+'/gi, '');
match = match.replace(/width="[^"]+"/gi, '').replace(/width='[^']+'/gi, '');
match = match.replace(/height="[^"]+"/gi, '').replace(/height='[^']+'/gi, '');
return match;
});
// 修改所有style里的width属性为max-width:100%
// 去掉所有style里的font-size属性
newContent = newContent.replace(/style="[^"]+"/gi,function(match,capture){
match = match.replace(/width:[^;]+;/gi, 'max-width:100%;').replace(/width:[^;]+;/gi, 'max-width:100%;');
match = match.replace(/font-size:[^;]+;/gi, '');
return match;
});
// 去掉<br/>标签
newContent = newContent.replace(/<br[^>]*\/>/gi, '');
// img标签添加style属性:max-width:100%;height:auto
newContent = newContent.replace(/\<img/gi, '<img style="max-width:100%;height:auto;display:block;margin:0px auto;"');
// video标签添加style属性:max-width:100%;height:auto
newContent = newContent.replace(/\<video/gi, '<video style="max-width:100%;height:auto;display:block;margin:0px auto;"');
return newContent;
}
module.exports = {
formatRichText
}
上传视频文件
此处,参考官方文档
uni.chooseVideo({
maxDuration:60, //视频最大时长60s
count: 1, // 最多可上传文件数
sourceType: ['album','camera'], // album 从相册选视频,camera 使用相机拍摄
success: (res) => {
console.log(res)
let videoFile = res.tempFilePath; // 选定视频的临时文件路径
// 将本地资源上传到开发者服务器
uni.uploadFile({
url: '', // 服务器 url
filePath: videoFile, // 上传文件资源的路径
name: 'file', // 服务器接收的参数名
success: (uploadFileRes) => {
console.log(uploadFileRes.data)
}
})
}
})
日期选择组件
因为有个小程序项目,需要用到日期选择组件,类似酒店民宿预订的日期组件。除了组件的使用以外,还涉及到js计算,如日期的转换
,以及计算日期间隔
。以下做个归纳总结:
- 首先引入组件
uni-datetime-picker
<uni-datetime-picker v-model="dateRange" type="daterange" @change="change">
<view class="date_picker_content">
<view class="date_box">
<!-- 入住时间 -->
<text class="date">{{formatDateRange[0]}}</text>
<text class="tag">入住</text>
</view>
<text class="count_box">
<!-- 统计时间间隔 -->
-<text class="count">{{countDay}}晚</text>-
</text>
<view class="date_box">
<!-- 离店时间 -->
<text class="date">{{formatDateRange[1]}}</text>
<text class="tag">离店</text>
</view>
</view>
</uni-datetime-picker>
export default {
data() {
dateRange: [], // 日期选择范围 组件绑定的值
formatDateRange: [], //格式化后的日期
countDay: 1, // 日期间隔天数,默认1晚
},
}
- 获取当天的日期
getDate(type) {
const date = new Date();
let year = date.getFullYear(); //年
let month = date.getMonth() + 1; //月
let day = date.getDate(); //日
// 如果天数小于9,则加上0
day = day > 9 ? day : '0' + day;
// 起始日期
let startDate = year + '-' + month + '-' + day;
// 截止日期
let endDate = year + '-' + month + '-' + '0' + (Number(day)+1);
// 用于提交表单的数据格式
this.dateRange.push(startDate)
this.dateRange.push(endDate)
// 用于数据渲染的格式
let formate_startDate = month + '月' + day + '日';
let formate_endDate = month + '月' + (Number(day)+1) + '日';
this.formatDateRange.push(formate_startDate);
this.formatDateRange.push(formate_endDate);
},
- 对组件选择的日期,进行格式化
// 组件的点击时间,日期选择获取后存储的值, 并对时间进行格式化
change(e) {
// 由于e获取的值是日期范围,有两个日期,所以用e[0],e[1]来获取不同的值
// 获取入住和离店时间
let s1 = e[0];
let s2 = e[1];
// 对入住和离店时间的月进行截取 格式例如:2023-04-24 只取月份 04
let m1 = s1.substring(5,7);
let m2 = s2.substring(5,7);
// 如果月份小于九月,则去掉前面的0
if(Number(m1) <= 9 && Number(m2) <= 9) {
m1 = s1.substring(6,7);
m2 = s2.substring(6,7);
}
// 对入住和离店时间的日进行截取 格式例如:2023-04-24 只取日期 24
let d1 = s1.substring(8,10);
let d2 = s2.substring(8,10);
// 拼接数据
let arr = [`${m1}月${d1}日`,`${m2}月${d2}日`];
// 赋值渲染(格式为:xx月xx日)
this.formatDateRange = arr;
// 这个变量用于传给后端(格式为:xxxx-xx-xx)
this.dateRange = e;
// 获取日期间隔天数
this.countDay = this.countDayFunc(e[0],e[1]);
},
- 计算时间间隔天数
// 计算天数
countDayFunc(date1,date2) {
//这里的date1、date2为日期的字符串
//将date1,date2转换为Date对象
var _dt1=new Date(date1);
var _dt2=new Date(date2);
var dt1=_dt1.getTime();
var dt2=_dt2.getTime();
return parseInt(Math.abs(dt1- dt2)/1000/60/60/24);//这里是计算天数,如果想获得周数则在该返回值上再除以7
},
拨打电话
用于带该参数(电话号码),跳转到手机拨打电话界面。便于用户直接拨打电话,省去了复制粘贴。
示例
uni.makePhoneCall({
phoneNumber: 'xxxx', // 需要拨打的电话号码
})
动态设置当前页的标题
示例
uni.setNavigationBarTitle({
title: '新的标题'
});
小程序内置地图查看位置
示例
uni.openLocation({
latitude: Number(item.latitude), //维度 必填
longitude: Number(item.longitude), //经度 必填
name: '位置名',
address: '详细地址',
scale: 14 //缩放比例
})
uni-popup 弹出层组件
- 引入组件
<uni-popup ref="popup" type="bottom" background-color="#fff" maskBackgroundColor="rgba(0, 0, 0, 0.4)">
<scroll-view scroll-y>
<view class="popup_box" style="">
...内容
</view>
</scroll-view>
<view class="bottom_btn">..底部固定按钮</view>
</uni-popup>
- 点击事件 从底部弹出此组件
// 打开底部弹窗
handleOpenPopup() {
this.$refs.popup.open('bottom');
},
- 弹出层组件的样式修改
.popup_box {
/* 圆角 */
border-radius: 25rpx 25rpx 0 0;
/* 组件高度 */
height: 85vh;
}
/deep/ .vue-ref {
padding-bottom: 0 !important;
}
/deep/ .uni-popup .uni-popup__wrapper {
background-color: #fff !important;
border-radius: 25upx 25upx 0 0 !important;
}
/* 如果内容有引用轮播图 */
/deep/ .u-swiper {
border-radius: 25rpx 25rpx 0 0 !important;
}
/deep/ .u-swiper__wrapper__item__wrapper__image {
border-radius: 25rpx 25rpx 0 0 !important;
}
标签栏组件
u-tabs
uni-segmented-control
自定义顶部导航栏
涉及到需要自定义顶部样式,如果设置渐变色,标题是图标等。
- 在
page.json
文件中,加入以下参数
"style": {
"navigationStyle": "custom"
}
- 页面代码
示例1
顶部是图标
<view :style="{'height':`${statusBarHeight+navBarHeight}`+ 'px'}" style="z-index: 0; text-align: center;background: linear-gradient(-315deg, #FF526F 25%, #FF0404 49%, #FF5D04 90%);">
<view :style="{'height':`${statusBarHeight}`+ 'px'}"></view>
<view style="display:flex;text-align: center;align-items: center;padding-left: 40rpx;padding-top: 20rpx;" :style="{'height':`${navBarHeight}`+ 'px'}">
<!-- 两个logo -->
<image mode="aspectFit" src=""></image>
<image mode="widthFix" src=""></image>
</view>
<!-- 刘海屏处理 -->
<!-- <image v-if="topFit" style="height:100%;margin-top:3vh" mode="widthFix" src=""></image>
<image v-else style="height:100%;margin-top:3.5vh" mode="widthFix" src=""></image> -->
</view>
示例2
顶部是文字
<view class="header-top" :style="{'height':`${statusBarHeight+navBarHeight}`+ 'px'}">
<view :style="{'height':`${statusBarHeight}`+ 'px'}"></view>
<view :style="{'height':`${navBarHeight}px`,'line-height':`${navBarHeight}px`}">
...文字内容
</view>
</view>
计算顶部高度
export default {
data() {
statusBarHeight: 20, //状态栏高度
navBarHeight: 45, //导航栏高度
//topFit: false,
},
onLoad() {
// 对顶部自定义导航栏高度做一个计算
this.$nextTick(() => {
//获取手机系统信息
const sysInfo = uni.getSystemInfoSync();
this.statusBarHeight = sysInfo.statusBarHeight;
//获取胶囊的位置
const menuButtonInfo = uni.getMenuButtonBoundingClientRect();
// 算法二、三 相对更准确写些
// 算法一
this.navBarHeight = (menuButtonInfo.bottom - sysInfo.statusBarHeight) + (menuButtonInfo.top - sysInfo.statusBarHeight);
// 算法二
this.navBarHeight = menuButtonInfo.top + menuButtonInfo.bottom - sysInfo.statusBarHeight + 4;
// 算法三
this.navBarHeight = (menuButtonInfo.top - sysInfo.statusBarHeight) * 2 + menuButtonInfo.height;
// 算法四 在有刘海屏的情况下
// 直接状态栏高度statusBarHeight + 44
// iphone机型判断 这部分也许会在顶部是logo时,并且对下方内容高度做适配判断
// if(sysInfo.model.indexOf('iPhone X') != -1 || sysInfo.model.indexOf("iPhone XR") != -1 || sysInfo.model.indexOf("iPhone XS Max") != -1){
// this.topFit = true
// }else{
// this.topFit = false;
// }
})
}
}
剪贴板
复制
uni.setClipboardData({
data: '内容',
success: function () {
console.log('success');
}
});
粘贴 (获取剪贴板的内容)
uni.getClipboardData({
success: function (res) {
console.log(res.data);
}
});
文本框
引入 uni-app
的 textarea
组件
实现,点击输入框,自动聚焦,弹起键盘。点击右侧的发表按钮,获取到输入框的内容。
- 💡 关键代码
用 v-model 绑定参数
<textarea
auto-height adjust-position
auto-blur fixed
:show-confirm-bar="false"
placeholder="请输入发表评论"
cursor-spacing="20"
style="padding: 20rpx;width: 90%;"
v-model="commentData"
/>
export default {
data() {
return {
commentData: '' // 评论内容
}
},
methods: {
handleSubmit() {
// 用 V-model绑定,点击事件就能直接获取到输入框的值
console.log(this.commentData)
}
}
}
- 🏷️ 属性说明
属性名 | 说明 |
---|---|
auto-height | 自动增高 |
adjust-position | 键盘弹起时,自动上推页面 |
auto-blur | 键盘收起时,自动失去焦点 |
cursor-spacing | 指定光标与键盘的距离,分离输入框与键盘的距离. 可设置20. |
- 📖 参考文档
图片预览
多图预览
<swiper circular indicator-dots="true" :autoplay="true" interval="5000" duration="500"
indicator-color='rgba(255, 255, 255, .3)' indicator-active-color='#ffffff'>
<block v-if="picData2F.length>0">
<swiper-item v-for="(item,index) in picData2F" :key="index" style="height: 100%;width: 100%;" @click="previewImage(index)">
<img style="width: 700rpx;height:300rpx;" mode="aspectFit "
:src="item | imgOnline" />
</swiper-item>
</block>
</swiper>
previewImage(index) {
let picDataList = this.picData2F.map(item => {
console.log(item)
return `地址完整路径,带上参数`;
});
uni.previewImage({
current: index,
urls: picDataList
});
}
单图预览
previewImg(photoImg) {
let imgsArray = [];
imgsArray[0] = photoImg;
uni.previewImage({
current: 0,
urls: imgsArray
});
}
单页面分享
自定义按钮
<view class="share">
<image style="width: 32rpx;height: 32rpx;"
mode="aspectFit" src="../../static/img/share.png"></image>
<button id="shareBtn" open-type="share" ></button>
</view>
.share {
width: 32rpx;
height: 32rpx;
position: relative;
margin: auto 25rpx;
#shareBtn {
position: absolute;
top: 0;
left: 0;
opacity: 0;
width: 62rpx;
height: 62rpx;
}
}
// 自定义此页面的转发给好友(已经有全局的分享方法,此处会覆盖全局)
onShareAppMessage(res) {
console.log(res)
console.log(this.goodsId)
return {
title: '分享的标题',
path: `/pages/index/goodsDetail?id=${this.goodsId}&type=${this.goodsType}`, // 带参数进入小程序页面
imageUrl: ''
}
},
uni.showToast
- 问题描述
调用这个toast之后,进行页面跳转,但是会出现一闪而过的情况
- 解决
uni.showToast({
title: '已取消支付',
icon: "error",
duration: 2500,
success: () => {
setTimeout(() => {
uni.navigateBack() // 默认返回上一级
}, 2500)
}
})
引入腾讯地图
请移步左侧 腾讯地图
菜单项查看
回到顶部(uni.pageScrollTo)
- 问题描述
由于表单内容过长,导致点击提交验证判断时,还得再滑会顶部。
- 解决
uni.pageScrollTo({
scrollTop:0, // 滚动到页面的目标位置 这个是滚动到顶部, 0
duration:300 // 滚动动画的时长
})
如果验证判断不通过时,直接执行这部分代码,直接回到顶部
TIP
还可以自制一个backTop按钮,滑动某一段时,显示按钮,然后回到顶部
这里前端部分就不写了,详细写一下方法部分
- 先引入监听页面的滚动时间,见下方代码段:
监听页面滚动事件(onPageScroll)
onPageScroll
方法,与 methods
同级
注:isShow
请在 data
中自行定义
onPageScroll(e){
// console.log('e:',e)
if(e.scrollTop>=200){
// 显示
this.isShow=true
} else {
// 隐藏
this.isShow=false
}
}
- 给前端的组件添加点击事件,让其回到顶部
topBack(){
uni.pageScrollTo({
scrollTop:0, // 滚动到页面的目标位置 这个是滚动到顶部, 0
duration:300 // 滚动动画的时长
})
}
- 📖 参考文档
跳转刷新
- 问题描述
小程序点击登录之后,登录成功不跳转
即使跳转成功,但页面不刷新,数据渲染不了
跳转成功,返回的上一页不是跳转前的页面。这里遇到一个问题,当我跳转到一个页面时,由于业务原因,需要先跳转到登录页之后,才能访问该路径。当我登录完之后,返回了三次还是在登录页面。
- 解决
如果是用的微信小程序自带的底部菜单,页面需要用
uni.switchTab
进行跳转跳转成功后,加入以下代码,刷新页面
uni.switchTab({
url: '/pages/my/index/index',
success: (res) => {
let page = getCurrentPages().pop()
if (page == undefined || page == null) return
page.onLoad()
}
})
TIP
这里再补充一下,除了此方法之外,还可以在需要刷新的页面,引用 onShow()
方法,在此方法里去调用后端接口,以此进行重新获取页面数据(刷新页面)
- 判断一下当前页面的路径,并获取上一页的路径。如果非此前上一页路径,则强制跳转到别的页面
// 当前页面
let pages = getCurrentPages();
// 前一个页面
let beforePage = pages[pages.length - 2];
if(beforePage.route == 'pages/login/login') {
uni.navigateTo({
url:'/pages/index/index'
})
} else {
uni.navigateBack({
delta: 1
})
}
TIP
注意,上面的代码段要写在跳转的 点击事件
里面。如果放在 mounted
或者是 onLoad
里面,会一直刷新页面,出现死循环。
拦截器
在 vue
中,对于一个需要登录才能访问的页面,我们会用 路由的前置守卫
进行拦截。但是,在小程序中,我们要怎么去拦截需要登录的页面呢 ?
创建一个 interceptor.js
文件路径:@/utils/interceptor.js
// interceptor.js
// 页面白名单
const whiteList = [
'/',
'/pages/login/login',
'/pages/index/index',
]
// 允许跳转的判断
export function hasPermission (url) {
let tokens = uni.getStorageSync('tokens')
// 在白名单中或有token,直接跳转
if(whiteList.indexOf(url) !== -1 || tokens) {
return true
}
return false
}
// 页面跳转拦截
export function navInterceptor() {
uni.addInterceptor('navigateTo', {
// 页面跳转前进行拦截, invoke根据返回值进行判断是否继续执行跳转
invoke (e) {
//console.log(e,'----e')
let url = e.url;
let index = url.indexOf("?"); //indexOf 获取第一个斜杠的索引,
let result = url.substring(0,index)
//console.log(result,'----111'); //substring 截取,
// 如果被拦截,则跳转到登录页面
if(!hasPermission(result)){
uni.reLaunch({
url: '/pages/login/login'
})
return false
}
return true
},
success (e) {
//console.log(e)
},
fail(e) {
//console.log(e)
}
})
}
// 页面跳转拦截,针对底部 switchTab中的路径
export function switchInterceptor() {
uni.addInterceptor('switchTab', {
// tabbar页面跳转前进行拦截
invoke (e) {
console.log(e)
let url = e.url;
let index = url.indexOf("?"); //indexOf 获取第一个斜杠的索引,
let result = url.substring(0,index)
if(!hasPermission(result)){
uni.reLaunch({
url: '/pages/login/login'
})
return false
}
return true
},
success (e) {
//console.log(e)
},
fail(e) {
//console.log(e)
}
})
}
引入拦截器
在 main.js
中引入写好的拦截器
// main.js
import '@/utils/intercept.js'
别的引入方式
除了上述方法,我们还可以在 app.vue
中,引入拦截器
// app.vue
// 引入拦截器中的拦截方法
import {navInterceptor,switchInterceptor} from '@/utils/intercept.js'
export default {
onLaunch: function() {
console.log('App Launch')
// 在小程序启动时,就开始拦截,引导用户登录才能访问
navInterceptor()
switchInterceptor()
},
onShow: function() {
console.log('App Show')
},
onHide: function() {
console.log('App Hide')
}
}
- 📖 参考文档
获取微信头像
由于获取微信头像,需要使用微信原生的按钮。因此这里主要是想用自定义的图片去代替小程序自带的 button
效果图
<view class="avator">
<image :src="params.image | imgOnlineToken" class="avator_image" mode="aspectFill"></image>
<button type="balanced" open-type="chooseAvatar" @chooseavatar="chooseavatar">
<image src="@/static/user/icon_photo_camera.png"></image>
</button>
</view>
<script>
export default {
data() {
return {
}
},
methods: {
chooseavatar(e) {
// 获取微信头像
let avatarUrl = 'data:image/jpeg;base64,' + uni.getFileSystemManager().readFileSync(e.detail.avatarUrl,'base64')
....
// 后续就是调接口,上传头像到服务器...
},
}
}
</script>
<style lang="scss" scoped>
button {
background-color: red;
width: 14px !important;
height: 14px !important;
padding: 0;
margin: 0;
overflow: unset;
line-height: 0;
background: #6cbd5f;
border-radius: 50%;
padding: 5px;
box-sizing: initial;
position: absolute;
bottom: 0;
right: 0;
border: 2px solid #fff;
image {
width: 100%;
height: 100%;
}
}
button::after {
border: none !important;
box-sizing: initial !important;
border-radius: 50% !important;
width: 14px !important;
height: 14px !important;
}
</style>
跳转返回,不刷新
- 问题描述
项目中,遇到这么一个需求。
一个表单页面,当我把前几行的内容填写完成后,有一个字段填写需要跳转到相应页面进行获取数据,并且带数据返回。
这时候,就会遇到一个问题,如果返回之后,之前填写的表单数据是否又要重新填写?
- 解决方法
利用 acceptDataFromOpenedPage
和 this.getOpenerEventChannel()
方法进行监听数据
核心代码
- 表单页
跳转点击事件
uni.navigateTo({
url: '跳转路径',
events: {
// 为指定事件添加一个监听器,获取被打开页面传送到当前页面的数据
acceptDataFromOpenedPage: (res) => {
// 获取跳转页面传递回来的数据
console.log(res,'----接收传参')
},
}
})
- 跳转页面
点击返回事件
// this.getOpenerEventChannel()是uni自带的一个方法,返回一个对象
const eventChannel = this.getOpenerEventChannel();
// 通过监听上个页面的uni.navigateTo的events中定义的事件传递参数
eventChannel.emit('acceptDataFromOpenedPage', {
//...需要传递的参数
});
// 关闭当前页返回上一页并触发acceptDataFromOpenedPage事件
uni.navigateBack({
delta: 1
});
带对象数组跳转路径
- 问题描述
会有这种需求,需要带 对象
或者 数组
从 页面A 跳转到 页面B
核心函数
// 将对象转成字符串,再用 encodeURIComponent() 函数进行转义
encodeURIComponent(JSON.stringify(obj))
// 将接收的参数转换成对象:
JSON.parse(decodeURIComponent(obj))
页面A
let arr = encodeURIComponent(JSON.stringify('需要传递的对象参数: arr、obj'));
uni.navigateTo({
url:`xxx?arr=${arr}`
})
页面B
onLoad(option) {
let arr = JSON.parse(decodeURIComponent(option.arr))
},
刷新当前页面
情况一
当数据有更新时,需要重新获取该页面的数据值
可以在 methods
中,定义以下方法:
reload() {
// 页面重载
const pages = getCurrentPages()
// 声明一个pages使用getCurrentPages方法
const curPage = pages[pages.length - 1]
// 声明一个当前页面
curPage.onLoad(curPage.options) // 传入参数
curPage.onShow()
// 执行刷新
},
对 onLoad
和 onShow
里的接口进行回调
情况二
当上个页面通过 setStorageSync
存储数据时,回退到上一个页面,页面数据未更新,需要再次刷新页面(或者重新进入)
,才能获取最新存入的缓存数据。即,回退页面时,获取到的缓存数据未更新
(实际是更新的,但还是旧数据)。
首先,在 二级页面 的 beforeDestroy
方法中,写入以下代码:
beforeDestroy() {
let pages = getCurrentPages();
let prevPage = pages[pages.length - 2]; // 上一个页面
// 直接调用上一个页面的setData()方法,把数据存到上一个页面中去
prevPage.setData({
isRefresh: true
})
},
也就是在页面回退的时候,会执行到销毁当前页面的函数。通过 getCurrentPages
获取到当前页面的索引,通过 pages[pages.length - 2]
获取到上一个页面。向上一个页面,传递需要刷新的信号,告知上一个页面,我需要刷新。
然后,在 一级页面 的 onShow
方法中,写入以下代码:
onShow(options) {
// 页面刷新,重新获取缓存数据
let pages = getCurrentPages();
let currPage = pages[pages.length - 1];
if(currPage.__data__.isRefresh) {
// 下方写入需要刷新的数据方法 或者 赋值语句
// 例1:this.status = uni.getStorageSync('status');
// 例2:this.getList();
...
// 每一次需要清除,否则会参数会缓存
currPage.__data__.isRefresh = false;
}
},
单选框
样式扩展
效果图:
<view class="coupon_box">
<!-- 可以选择与取消 -->
<radio-group>
<view class="coupon_card" v-for="(item,index) in couponList" :key="index">
<view class="lt">
<view class="price"><label>¥</label>{{item.price}}</view>
</view>
<view class="line"></view>
<view class="rt flex_al just_bw">
<view class="coupon_info">
<view class="coupon_name">{{item.title}}</view>
<view class="coupon_desc">{{item.desc}}</view>
</view>
<view class="radioBox">
<radio color="#CA6F3A" :value="item.id + ''" :checked="item.id === couponIdCurrent" @click="handleSelected(index)"/>
</view>
</view>
</view>
</radio-group>
<!-- 默认选中状态 -->
<radio-group @change="radioChange">
<view class="methods_item flex_al just_bw" v-for="(item,index) in methedsList" :key='index'
:class="index === radioCurrent?'radiOn':''">
<view class="methods_item_fl flex_al">
<image mode="aspectFit"
src="@/static/img/icon_wechat.png"></image>
<label class="wx">微信支付</label>
</view>
<view class="radioBox">
<radio color="#CA6F3A" :value="item.id + ''" :checked="index === radioCurrent" />
</view>
</view>
</radio-group>
</view>
TIP
具体样式实现,请参考 CSS栏
中的 优惠券样式归纳
选中 与 取消
data() {
return {
couponIdCurrent: null,
}
},
methods: {
handleSelected(index) {
if (this.couponIdCurrent == this.couponList[index].id) {
this.couponIdCurrent = null
} else {
this.couponIdCurrent = this.couponList[index].id
}
this.$emit('couponSelect',this.couponIdCurrent)
}
}
默认选中
data() {
return {
radioCurrent: 0,
methedsList: [
{
imgUrl: '@/static/img/icon_wechat.png',
title: '微信支付',
id: 0
}
],
}
},
radioChange(e) {
this.methedsList.forEach( (item,index) => {
if (item.id === e.target.id) {
this.radioCurrent = index;
}
})
},
- 📖 参考文档
表格(Table)
效果图:
要不是遇到了需求,我已经忘记还有 表格
这个组件了。在我的印象中,涉及移动端的,很少会用到。常见的基本都用在后台管理系统中。
这次项目,我用到的是 DCloud
里的一个组件(在这基础上改了一些东西),uniapp
这个扩展组件库,真的挺好用的,有需要的可以去里面翻一翻
<Table :items="formList" :thList="TableHeader"
:rightBorder="rightBorder" :isClick="true" @onClickTh="handleTableClick"
></Table>
data() {
return {
TableHeader, // 表头数据 (由于太长,就从外部引用了,不写在本页面中)
rightBorder: true, // 组件中,是否显示固定区与滚动区的分隔线
formList: [], // 表格数据
}
},
methods: {
handleTableClick(key,item) {
if(key.text === '报名') {
// 判断按钮显示的那一列,做点击事件,与后端交互
console.log(key,'---key')
console.log(item,'---item')
} else {
// 这里主要是表格数据过长会被隐藏,通过 modal 弹窗显示出来
uni.showModal({
title: key.text,
content: item[key.dataKey],
showCancel: false,
confirmColor: '#CA6F3A',
confirmText: '关闭'
});
}
},
}
表头数据: 可以自定义
fixed: 是否固定在左侧
sortKey: 是否排序
dataKey: 对应的数据属性名
isBtn: 是否显示按钮
btnName: 按钮名称
后两个是我自己加的,主要是业务需要。可以在每一条数据的后面,多加一个按钮,可以实现点击事件
export const TableHeader = [
{
text: '岗位名称',
fixed: true,
sortKey: '',
dataKey: 'position_name',
},
...
{
text: '报名',
fixed: false,
sortKey: '',
dataKey: '',
width: '280rpx',
isBtn: true,
btnName: '点击报名'
},
]
- 📖 组件
- 表格 本次项目所用的组件
- table表格(支持条件格式、排序、格式化、链接、统计等一堆功能) 本来是考虑用这个的,但是组件本身体积有点大,所以就换成了上面那个。这个可以用于学习,功能比较全
wx.openDocument 设置标题
测试给我们反馈了一个问题,线上预览文件的时候,本地打开文件,顶部标题是缓存地址名称,不是原文件的标题,我才知道原来还能这样设置,话不多说上代码
这里我直接附上我封装好的方法
// 预览文件
export function previewFiles(fileUrl) {
// 完整的文件地址
let link = fileUrl;
// 获取文件名称与后缀 xxx.pdf
let fileName = link.substr(link.lastIndexOf('/') + 1);
wx.showLoading({
title: '文件加载中',
})
wx.downloadFile({
url: link,
filePath: wx.env.USER_DATA_PATH + `/${fileName}`, // 用文件名命名,否则显示临时路径名称 (划重点)
success: res => {
if (res.statusCode === 200) {
const filePath = res.filePath;
wx.openDocument({
filePath: filePath,
showMenu: true, //关键点
success: function (res) {
console.log('打开文档成功')
},
complete: function(res) {
wx.hideLoading();
}
})
} else {
wx.hideLoading();
showToast('打开失败')
}
},
fail: error => {
console.log(error,'---error')
wx.hideLoading();
showToast('打开失败')
}
})
}
💡 核心: filePath: wx.env.USER_DATA_PATH + `/$
小程序与H5页面的交互
最近开发遇到一个这样的需求,由于 uniapp
中 map组件 使用的是以腾讯地图为底图的组件,无论引用的是高德,还是哪些第三方的SDK
,只能使用插件的api,不能改变地图。
这也就意味着,腾讯地图本身的卫星图很糊,想引用第三方地图只能引入 H5页面。
H5页面
- 单独引入高德地图组件
- 接收小程序传来的参数,设置当前类型,判断是进行选单点,还是电子围栏数组
- 点击确认返回时,把经纬度数据传回小程序,并关闭当前页面
小程序
- 通过点击事件,带参数进入
web-view
页面 - 由于当前页面是表单页,只是需要进入H5地图页面进行获取数据,所以需要保留当前已填写的表单信息
- 接收H5页面传回来的参数,并赋值
具体实现方式如下:
小程序
总共涉及到两个页面:
- 点击跳转H5页面的表单页
- H5展示的
web-view
页面
- 表单页 - 点击事件
带参数跳转,并接收传回来的数据(保留当前表单页的数据不被清空)
// 选择坐标点
handleChooseLocation() {
uni.navigateTo({
url: `/pages_map/map/map?type=single`,
events: {
// 为指定事件添加一个监听器,获取被打开页面传送到当前页面的数据
acceptDataFromOpenedPage: (res) => {
console.log(res,'----接收传参')
this.shopInfo.location.lat = res.lat;
this.shopInfo.location.lng = res.lng;
},
}
});
},
- H5展示的
web-view
页面
通过给 web-view
添加 @message 绑定事件,获取H5页面传回的数据,并触发事件,将数据传递给表单页。
<template>
<web-view :src="webUrl" @message="getMessage"></web-view>
</template>
export default {
data() {
return {
webUrl: ''
}
},
onLoad(option) {
console.log(option)
this.webUrl = `http://xxxx/?type=${option.type}`
},
methods: {
// 获取网页传回的参数,然后传回给表单页
getMessage(e) {
console.log(e,333)
// this.getOpenerEventChannel()是uni自带的一个方法,返回一个对象
const eventChannel = this.getOpenerEventChannel();
eventChannel.emit('acceptDataFromOpenedPage', e.detail.data[0]);
}
}
}
H5页面
点击返回时,调用 微信SDK 方法,通过 postMessage 函数,向小程序发送参数,并返回
核心代码
二者一定要同时写,否则无效!!!
// 向小程序发送参数 data里携带的就是要带去的参数
wx.miniProgram.postMessage({
data: {
lat: lnglatTransfer[1],
lng: lnglatTransfer[0]
}
});
// 返回小程序
wx.miniProgram.navigateBack({
delta: 1
});
uniapp 打包H5项目
第一次用 uniapp
创建H5项目,结果发现突然不会打包了。然后就去网上搜了一下:
- 点击项目,在顶部栏找到 发行 -> 网站-PC
- 按照弹窗步骤,填入页面标题和域名,等待打包编译即可
然后,这里就开始踩坑了。打包完,本地打开是空白,部署完还是空白!!!
划重点:
在 manifest.json
中,源码视图 里配置下面两个属性即可
"h5" : {
"router" : {
"mode" : "hash"
},
"publicPath" : "./"
}
带参数,生成小程序码
问题描述
项目中,遇到这么一个需求。
需要为每个店铺生成小程序码,并绘制成海报。扫码直接进入店铺详情页面。
假如当前店铺需要获取详细的数据,那就需要店铺id等一些接口必要的参数。那生成小程序码的时候,就带把这些参数带上。
解决思路
- 小程序码,需要带哪些参数?
- 参数如何带中文?
- 扫码之后,如何接收参数?
核心代码
- 小程序码,需要带哪些参数?
首先,生成的小程序码是在后端生成的。前端值需要往后端传 page
和 scene
这两个参数即可。
page:扫码后小程序跳转进入的页面
scene:当前页面所需的参数(比如我要进入店铺详情页面,我的接口就需要店铺id和店铺类型两个参数)
let res = await getQrcode({
page: 'pages_travel/details/details',
scene: `id=${this.currentShopId}&type=${this.currentType}`,
});
- 参数如何带中文?
那么,当scene中,存在参数是中文的情况下,直接传,生成的小程序码就会是无效的。
所以就需要进行转码
let param = "shopId=1077&shopName=霸王茶姬";
let encodedParam = encodeURIComponent(param);
但需要注意的是,scene
的长度不能超过32个字符,所以如果中文太长的话,转码之后的值是绝对会超过32位的。所以,还是尽量避免传入中文。
- 扫码之后,如何接收参数?
扫码之后,接收的参数还是在 onLoad
中获取
如果是扫码进入的,option
中 会携带 scene
属性值,这里面就是我们页面接口中需要的参数,也是我们生成二维码时,所传递的参数。
onLoad(option) {
console.log(option,'---页面接收参数')
// 判断是否有scene 获取店铺id和type
if(option && option.scene) {
// 解析获取扫码进入的 scene 参数
let scene = decodeURIComponent(option.scene);
console.log(scene,'---scene')
let sceneObj = {};
for (let i = 0; i < scene.split('&').length;i++){
let arr = scene.split('&')[i].split('=');
sceneObj[arr[0]] = arr[1];
}
console.log(sceneObj,'---sceneObj')
}
},
将多个文件打包成压缩包上传
问题描述
项目中,遇到这么一个需求。
需要把多个图片打包成zip压缩包,上传到对象存储
中,再把返回的地址提交给后端。
准备工作
- 这里用到两个文件,一个是 fileUtil.js ,以及 zipUtil.js
- 创建压缩包存放目录,一般放在微信的 wx.env.USER_DATA_PATH 目录下
- 确定压缩包路径,以及压缩包名称
核心代码
<script>
{/* 导入对象存储上传的方法 */}
import {
UploadFile
} from '@/utils/upload.js'
{/* 文件压缩用到的主要核心文件 */}
const fileUtil = require('@/utils/zipTools/fileUtil.js');
const zipUtil = require('@/utils/zipTools/zipUtil.js');
{/* 压缩包所在目录 */}
const zipFolder = `${wx.env.USER_DATA_PATH}/ziptest`;
{/* 压缩包路径,包含压缩包后缀名称 */}
const zipPath = `${zipFolder}/attachment.zip`;
export default {
data() {
return {
attachList: [], // 图片数组
attachmentUrl: '', // 附件地址
}
},
methods: {
async upload() {
loadingShow('图片上传中');
this.attachList = [
{ name: `图片1`, path: 'xxxx' },
{ name: '图片2', path: 'xxxx' },
{ name: '图片3', path: 'xxxx' },
];
// 这里主要是给图片文件进行命名,格式: 名称 + 文件后缀
this.attachList.forEach(item => {
// 查找最后一个点的位置
let lastDotIndex = item.path.lastIndexOf('.');
if (lastDotIndex !== -1) {
item.name = item.name + '.' + item.path.substring(lastDotIndex + 1);
}
});
// 先创建对应目录
await fileUtil.mkdir(zipFolder);
// 打包 zip
await zipUtil.zip(this.attachList, zipPath, res => {});
// 上传到对象存储,获取对应的地址
let uploadRes = await UploadFile(zipPath, 'attachment.zip' ,'upload_zip');
this.attachmentUrl = uploadRes.uploadUrl;
loadingHide();
}
}
}
</script>