Skip to content
On this page
🎨 作者:Jacinda 📔 阅读量:

uniapp 知识点归纳

两个小程序之间相互跳转

  1. 先在 manifest.json / app.json(如果是原生写法) 文件中进行配置
js
"navigateToMiniProgramAppIdList" : [ 
    "xxx"  //需要跳转的小程序appid 小程序B
],
  1. 在点击事件中写入以下函数进行配置
js
//跳转到 B
uni.navigateToMiniProgram({
    appId: 'xxx',
    path: 'pages/index/index',
    extraData: {},
    envVersion: 'trial',//打开版本  开发版 develop 体验版trial 正式版release
    success(res) {
        
    }
})
  1. 小程序B接收时,直接在 Onload(option) 中进行接收A带来的参数

uniapp 组件库

注意:以下是两个图标组件的图标库

uni-ui 组件源码

Editor 富文本组件

由于该组件按官方文档引入,只有输入框,并没有文档中描述的那些工具栏。经过查找,以下提供两种方式进行使用

  • 方式一

    引用封装好的组件

    1. 先引入组件代码
    2. 使用组件
    vue
    <uniEditor ref="uniEditor" :editorDetail="params.content"  @getContents="getContents"></uniEditor>
    js
    import 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组件的方法,单独复制出来作为使用。

    1. 正常引入 editor 组件
    2. 按需写按钮
    3. 按钮的点击事件,去 editor 源码里复制。或者参考官方文档的API

u-parse 富文本内容显示

这里主要是对后端传回的富文本内容做一下处理

vue
<u-parse :content="detailHtml"/>
js
import {formatRichText} from '@/utils/formatRichText.js'
export default {
    data() {
        detailHtml: null //格式化后的富文本数据
    },
    onLoad() {
        this.detailHtml = formatRichText('后端传回的富文本参数')
    }
}

处理富文本的js文件

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
}

上传视频文件

此处,参考官方文档

js
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计算,如日期的转换,以及计算日期间隔。以下做个归纳总结:

  1. 首先引入组件 uni-datetime-picker
html
<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>
js
export default {
    data() {
        dateRange: [], // 日期选择范围  组件绑定的值
        formatDateRange: [], //格式化后的日期
        countDay: 1, // 日期间隔天数,默认1晚
    },
}
  1. 获取当天的日期
js
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);
},
  1. 对组件选择的日期,进行格式化
js
// 组件的点击时间,日期选择获取后存储的值, 并对时间进行格式化
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]);
},
  1. 计算时间间隔天数
js
// 计算天数
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
},

拨打电话

用于带该参数(电话号码),跳转到手机拨打电话界面。便于用户直接拨打电话,省去了复制粘贴。

示例

js
uni.makePhoneCall({
    phoneNumber: 'xxxx', // 需要拨打的电话号码
})

动态设置当前页的标题

示例

js
uni.setNavigationBarTitle({
    title: '新的标题'
});

小程序内置地图查看位置

示例

js
uni.openLocation({
    latitude: Number(item.latitude),	//维度  必填
    longitude: Number(item.longitude), //经度 必填
    name: '位置名',
    address: '详细地址',
    scale: 14 //缩放比例
})

uni-popup 弹出层组件

  1. 引入组件
html
<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>
  1. 点击事件 从底部弹出此组件
js
// 打开底部弹窗
handleOpenPopup() {
    this.$refs.popup.open('bottom');
},
  1. 弹出层组件的样式修改
css
.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

自定义顶部导航栏

涉及到需要自定义顶部样式,如果设置渐变色,标题是图标等。

  1. page.json 文件中,加入以下参数
js
"style": {
    "navigationStyle": "custom"
}
  1. 页面代码

示例1

顶部是图标

html
<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

顶部是文字

html
<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>

计算顶部高度

js
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;
            // }
        })
    }
}

剪贴板

复制

javascript
uni.setClipboardData({
	data: '内容',
	success: function () {
		console.log('success');
	}
});

粘贴 (获取剪贴板的内容)

javascript
uni.getClipboardData({
	success: function (res) {
		console.log(res.data);
	}
});

文本框

引入 uni-apptextarea组件

实现,点击输入框,自动聚焦,弹起键盘。点击右侧的发表按钮,获取到输入框的内容。

  • 💡 关键代码

用 v-model 绑定参数

javascript
<textarea 
    auto-height adjust-position 
    auto-blur fixed
    :show-confirm-bar="false"
    placeholder="请输入发表评论"
    cursor-spacing="20"
    style="padding: 20rpx;width: 90%;"
    v-model="commentData"
/>
javascript
export default {
    data() {
        return {
            commentData: '' // 评论内容
        }
    },
    methods: {
        handleSubmit() {
            // 用 V-model绑定,点击事件就能直接获取到输入框的值
            console.log(this.commentData)
        }
    }
}
  • 🏷️ 属性说明
属性名说明
auto-height自动增高
adjust-position键盘弹起时,自动上推页面
auto-blur键盘收起时,自动失去焦点
cursor-spacing指定光标与键盘的距离,分离输入框与键盘的距离. 可设置20.

图片预览

多图预览

html
<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>
javascript
previewImage(index) {
    let picDataList = this.picData2F.map(item => {
        console.log(item)
        return `地址完整路径,带上参数`;
    });
    uni.previewImage({
        current: index,
        urls: picDataList
    });
}

单图预览

javascript
previewImg(photoImg) {
	let imgsArray = [];
	imgsArray[0] = photoImg;
	uni.previewImage({
		current: 0,
		urls: imgsArray
	});
}

单页面分享

自定义按钮

html
<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>
css
.share {
    width: 32rpx;
    height: 32rpx;
    position: relative;
    margin: auto 25rpx;
    #shareBtn {
        position: absolute;
        top: 0;
        left: 0;
        opacity: 0;
        width: 62rpx;
        height: 62rpx;
    }
}
javascript
// 自定义此页面的转发给好友(已经有全局的分享方法,此处会覆盖全局)
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之后,进行页面跳转,但是会出现一闪而过的情况

  • 解决
javascript
uni.showToast({
    title: '已取消支付',
    icon: "error",
    duration: 2500,
    success: () => {
        setTimeout(() => {
            uni.navigateBack() // 默认返回上一级
        }, 2500)
    }
})

引入腾讯地图

请移步左侧 腾讯地图 菜单项查看

回到顶部(uni.pageScrollTo)

  • 问题描述

由于表单内容过长,导致点击提交验证判断时,还得再滑会顶部。

  • 解决
javascript
uni.pageScrollTo({
    scrollTop:0,   // 滚动到页面的目标位置  这个是滚动到顶部, 0 
    duration:300  // 滚动动画的时长
})

如果验证判断不通过时,直接执行这部分代码,直接回到顶部

TIP

还可以自制一个backTop按钮,滑动某一段时,显示按钮,然后回到顶部

这里前端部分就不写了,详细写一下方法部分

  1. 先引入监听页面的滚动时间,见下方代码段:

监听页面滚动事件(onPageScroll)

onPageScroll方法,与 methods 同级

注:isShow 请在 data 中自行定义

javascript
onPageScroll(e){
    // console.log('e:',e)
    if(e.scrollTop>=200){
        // 显示
        this.isShow=true
    } else {
	    // 隐藏
	    this.isShow=false
    }
}
  1. 给前端的组件添加点击事件,让其回到顶部
javascript
topBack(){
    uni.pageScrollTo({
        scrollTop:0,   // 滚动到页面的目标位置  这个是滚动到顶部, 0 
        duration:300  // 滚动动画的时长
    })
}

跳转刷新

  • 问题描述
  1. 小程序点击登录之后,登录成功不跳转

  2. 即使跳转成功,但页面不刷新,数据渲染不了

  3. 跳转成功,返回的上一页不是跳转前的页面。这里遇到一个问题,当我跳转到一个页面时,由于业务原因,需要先跳转到登录页之后,才能访问该路径。当我登录完之后,返回了三次还是在登录页面。

  • 解决
  1. 如果是用的微信小程序自带的底部菜单,页面需要用 uni.switchTab 进行跳转

  2. 跳转成功后,加入以下代码,刷新页面

javascript
uni.switchTab({
    url: '/pages/my/index/index',
    success: (res) => {
        let page = getCurrentPages().pop()
        if (page == undefined || page == null) return
        page.onLoad()
    }
})

TIP

这里再补充一下,除了此方法之外,还可以在需要刷新的页面,引用 onShow() 方法,在此方法里去调用后端接口,以此进行重新获取页面数据(刷新页面)

  1. 判断一下当前页面的路径,并获取上一页的路径。如果非此前上一页路径,则强制跳转到别的页面
javascript
// 当前页面
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

javascript
// 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 中引入写好的拦截器

javascript
// main.js

import '@/utils/intercept.js'

别的引入方式

除了上述方法,我们还可以在 app.vue 中,引入拦截器

javascript
// 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

效果图

图 0

html
<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>

跳转返回,不刷新

  • 问题描述

项目中,遇到这么一个需求。

一个表单页面,当我把前几行的内容填写完成后,有一个字段填写需要跳转到相应页面进行获取数据,并且带数据返回。

这时候,就会遇到一个问题,如果返回之后,之前填写的表单数据是否又要重新填写?

  • 解决方法

利用 acceptDataFromOpenedPagethis.getOpenerEventChannel() 方法进行监听数据

核心代码

  • 表单页

跳转点击事件

javascript
uni.navigateTo({
    url: '跳转路径',
    events: {
        // 为指定事件添加一个监听器,获取被打开页面传送到当前页面的数据
        acceptDataFromOpenedPage: (res) => {
            // 获取跳转页面传递回来的数据
            console.log(res,'----接收传参')
        },
    }
})
  • 跳转页面

点击返回事件

javascript
// this.getOpenerEventChannel()是uni自带的一个方法,返回一个对象
const eventChannel = this.getOpenerEventChannel();
// 通过监听上个页面的uni.navigateTo的events中定义的事件传递参数
eventChannel.emit('acceptDataFromOpenedPage', {
    //...需要传递的参数
});
// 关闭当前页返回上一页并触发acceptDataFromOpenedPage事件
uni.navigateBack({
    delta: 1
});

带对象数组跳转路径

  • 问题描述

会有这种需求,需要带 对象 或者 数组页面A 跳转到 页面B

核心函数

javascript
// 将对象转成字符串,再用 encodeURIComponent() 函数进行转义
encodeURIComponent(JSON.stringify(obj))

// 将接收的参数转换成对象:
JSON.parse(decodeURIComponent(obj))

页面A

javascript
let arr = encodeURIComponent(JSON.stringify('需要传递的对象参数: arr、obj'));

uni.navigateTo({
	url:`xxx?arr=${arr}`
})

页面B

javascript
onLoad(option) {
	let arr = JSON.parse(decodeURIComponent(option.arr))
},

刷新当前页面

情况一

当数据有更新时,需要重新获取该页面的数据值

可以在 methods 中,定义以下方法:

javascript
reload() {
    // 页面重载
    const pages = getCurrentPages()
    // 声明一个pages使用getCurrentPages方法
    const curPage = pages[pages.length - 1]
    // 声明一个当前页面
    curPage.onLoad(curPage.options) // 传入参数
    curPage.onShow()
    // 执行刷新
},

onLoadonShow 里的接口进行回调

情况二

当上个页面通过 setStorageSync 存储数据时,回退到上一个页面,页面数据未更新,需要再次刷新页面(或者重新进入),才能获取最新存入的缓存数据。即,回退页面时,获取到的缓存数据未更新(实际是更新的,但还是旧数据)。

首先,在 二级页面beforeDestroy 方法中,写入以下代码:

javascript
beforeDestroy() {
    let pages = getCurrentPages();
    let prevPage = pages[pages.length - 2]; // 上一个页面
    // 直接调用上一个页面的setData()方法,把数据存到上一个页面中去
    prevPage.setData({
        isRefresh: true 
    })
},

也就是在页面回退的时候,会执行到销毁当前页面的函数。通过 getCurrentPages 获取到当前页面的索引,通过 pages[pages.length - 2] 获取到上一个页面。向上一个页面,传递需要刷新的信号,告知上一个页面,我需要刷新

然后,在 一级页面onShow 方法中,写入以下代码:

javascript
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;
    }
},

单选框

样式扩展

效果图:

图 1

图 2

html
<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栏 中的 优惠券样式归纳

选中 与 取消

javascript
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)
    }
}

默认选中

javascript
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)

效果图:

图 3

要不是遇到了需求,我已经忘记还有 表格 这个组件了。在我的印象中,涉及移动端的,很少会用到。常见的基本都用在后台管理系统中。

这次项目,我用到的是 DCloud 里的一个组件(在这基础上改了一些东西),uniapp 这个扩展组件库,真的挺好用的,有需要的可以去里面翻一翻

html
<Table :items="formList" :thList="TableHeader"
    :rightBorder="rightBorder" :isClick="true" @onClickTh="handleTableClick"
></Table>
javascript
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: 按钮名称

后两个是我自己加的,主要是业务需要。可以在每一条数据的后面,多加一个按钮,可以实现点击事件

javascript
export const TableHeader = [
	{
		text: '岗位名称',
		fixed: true,        
		sortKey: '',    
		dataKey: 'position_name',
	},
    ...
    {
		text: '报名',
		fixed: false,        
		sortKey: '',    
		dataKey: '',
		width: '280rpx',
		isBtn: true,
		btnName: '点击报名'
	},
]

wx.openDocument 设置标题

测试给我们反馈了一个问题,线上预览文件的时候,本地打开文件,顶部标题是缓存地址名称,不是原文件的标题,我才知道原来还能这样设置,话不多说上代码

这里我直接附上我封装好的方法

javascript
// 预览文件
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页面的交互

最近开发遇到一个这样的需求,由于 uniappmap组件 使用的是以腾讯地图为底图的组件,无论引用的是高德,还是哪些第三方的SDK,只能使用插件的api,不能改变地图。

这也就意味着,腾讯地图本身的卫星图很糊,想引用第三方地图只能引入 H5页面

H5页面

  1. 单独引入高德地图组件
  2. 接收小程序传来的参数,设置当前类型,判断是进行选单点,还是电子围栏数组
  3. 点击确认返回时,把经纬度数据传回小程序,并关闭当前页面

小程序

  1. 通过点击事件,带参数进入 web-view 页面
  2. 由于当前页面是表单页,只是需要进入H5地图页面进行获取数据,所以需要保留当前已填写的表单信息
  3. 接收H5页面传回来的参数,并赋值

具体实现方式如下:

小程序

总共涉及到两个页面:

  1. 点击跳转H5页面的表单页
  2. H5展示的 web-view 页面
  • 表单页 - 点击事件

带参数跳转,并接收传回来的数据(保留当前表单页的数据不被清空)

javascript
// 选择坐标点
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页面传回的数据,并触发事件,将数据传递给表单页。

html
<template>
	<web-view :src="webUrl" @message="getMessage"></web-view>
</template>
javascript
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 函数,向小程序发送参数,并返回

核心代码

二者一定要同时写,否则无效!!!

javascript
// 向小程序发送参数 data里携带的就是要带去的参数
wx.miniProgram.postMessage({
    data: {
        lat: lnglatTransfer[1],
        lng: lnglatTransfer[0]
    }
});
// 返回小程序
wx.miniProgram.navigateBack({
    delta: 1
});

uniapp 打包H5项目

第一次用 uniapp 创建H5项目,结果发现突然不会打包了。然后就去网上搜了一下:

  1. 点击项目,在顶部栏找到 发行 -> 网站-PC
  2. 按照弹窗步骤,填入页面标题和域名,等待打包编译即可

然后,这里就开始踩坑了。打包完,本地打开是空白,部署完还是空白!!!

划重点

manifest.json 中,源码视图 里配置下面两个属性即可

json
"h5" : {
    "router" : {
        "mode" : "hash"
    },
    "publicPath" : "./"
}