uni-App使用H5+实现一个全局的消息提示,不需要使用组件

需求:

实现一个全局的消息提示,比如在网络状态发生变化时,提示用户网络状态。

其实实现这个功能的方式很多,比如在监听网络状态时把网络的状态存储在store里面,然后再页面里面去监听store里的字段变化。emmm···想了一下实现倒是很就简单,但每个页面都需要去做这个监听或者都需要引入一个组件,还是比较麻烦。然后查看一下H5+的官方文档

效果:

可以点击右侧的关闭按钮或者上滑关闭提示。

实现:

先看代码

首先为了可扩展性,把这个写成了一个类,如下:

javascript 复制代码
// 这是消息的图标
import img1 from '../static/icons/not-wifi.png'
// 这是关闭按钮的图标
import img2 from '../static/icons/close-icon2.png'

const {
	statusBarHeight,
	windowWidth
} = uni.getSystemInfoSync();
const viewWidth = uni.upx2px(800)

class NativeMsg {
	// 整个区域的宽高
	viewStyle = {
		backgroundColor: "rgba(255, 255, 255, 0)",
		top: "0rpx",
		left: `${windowWidth / 2 - viewWidth / 2}`,
		width: `${viewWidth}px`,
		// 取图片的高度(带阴影的尺寸)
		height: `${uni.upx2px(80)}px`
	};
	constructor(item, cb) {
		// 记录内容信息,以供回调使用
		this.item = item;
		// 弹出、消失动画要用
		this.offsetTop = -statusBarHeight - uni.upx2px(159);
		// 上边界
		this.startTop = -statusBarHeight - uni.upx2px(159);
		// 下边界
		this.endTop = statusBarHeight;
		// 上滑关闭要用
		this.clientY = 0;
		// nativeObj.View 实例
		this.view = null;
		// 回调函数
		this.cb = cb || null;
		// 隐藏过程flag,防止重复执行
		this.hiding = false;
		// 标记当前弹窗状态
		this.status = "active";
		this.create();
	}
	// 创建区域以及背景
	create() {
		let _view = null;
		// 创建 View区域
		_view = new plus.nativeObj.View(`alarmMsg-${this.item.alarmId || "ins"}`, this.viewStyle);

		// 拦截触摸事件: 开启后 区域内的触摸事件不会透传到下面
		_view.interceptTouchEvent(true);
		// 设置监听点击事件的区域为关闭图片的区域
		_view.setTouchEventRect({
			top: `${uni.upx2px(18)}px`,
			left: '92%',
			width: '22px',
			height: '22px'
		})
		// 增加点击事件监听
		_view.addEventListener("click", (e) => {
			if (this.hiding) return;
			this.hiding = true;
			this.cb && this.cb({ type: "click", result: this.item });
			this.animationHide();
		});
		// 触摸事件监听
		_view.addEventListener("touchstart", res => {
			this.clientY = res.clientY;
		});
		// 触摸事件监听
		_view.addEventListener("touchmove", res => {
			const {
				clientY
			} = res;
			let offsetY = this.clientY - clientY;
			if (offsetY > 25 && !this.hiding) {
				this.hiding = true;
				this.cb && this.cb({
					type: "move",
					result: this.item
				});
				this.animationHide();
			}
		});
		// 保存
		this.view = _view;
		// 画内容
		this.drawInfo();
		// // 显示
		// this.animationShow();
	}
	// 画内容
	drawInfo() {
		const {
			message
		} = this.item;
		this.view.draw([{
				tag: "rect",
				id: "rectBox",
				position: {
					top: '0px',
					left: `0px`,
					width: `100%`,
					height: `100%`
				},
				rectStyles: {
					color: 'rgba(241, 58, 14, .6)',
					radius: '8px',
				}
			},
			{
				tag: 'img',
				id: 'imgBox1',
				src: img1,
				position: {
					top: `${uni.upx2px(22)}px`,
					left: '10px',
					width: '26px',
					height: '22px'
				}
			},
			{
				tag: 'img',
				id: 'imgBox2',
				src: img2,
				position: {
					top: `${uni.upx2px(18)}px`,
					left: '92%',
					width: '22px',
					height: '22px'
				}
			},
			{
				tag: "font",
				id: "mainFont",
				text: message,
				textStyles: {
					size: `${uni.upx2px(28)}px`,
					color: "#feebeb",
					align: "left"
				},
				position: {
					top: `${uni.upx2px(28)}px`,
					left: `10%`,
					height: "wrap_content"
				}
			},
		]);
	}
	// 简易向下出现动画
	animationShow() {
		this.view.show();
		this.view.setStyle({
			...this.viewStyle,
			top: `${this.offsetTop++}px`
		});
		if (this.offsetTop >= this.endTop) {
			this.status = "active";
			return;
		}
		setTimeout(() => {
			this.animationShow();
		}, 0);
	}
	// 简易向上消失动画
	animationHide() {
		this.view.setStyle({
			...this.viewStyle,
			top: `${this.offsetTop--}px`
		});
		if (this.offsetTop <= this.startTop) {
			this.view.hide();
			this.hiding = false;
			this.status = "close";
			return;
		}
		setTimeout(() => {
			this.animationHide();
		}, 0);
	}
	// 获取当前状态
	getStatus() {
		return this.status;
	}
	// 不用动画,直接消失
	hide() {
		this.view.hide();
	}
	close() {
		this.view.close();
	}
}

// 对外暴露一个创建实例的方法
export function createAlarm(item, cb) {
	return new NativeMsg(item, cb);
}

方法封装好了,大概解释一下

1. 设置位置

因为这里的效果是类似于element-ui中message提示组件,是从屏幕的中间位置弹出的,所以这里会先去指定位置信息。这里的样式是整个盒子的样式,后面所有的内容都是写在这个盒子里面的。这个位置的属性需要参照H5+ 里面的文档来进行设置,就不过多解释了。

javascript 复制代码
// 整个区域的宽高
viewStyle = {
    backgroundColor: "rgba(255, 255, 255, 0)",
    top: "0rpx",
    left: `${windowWidth / 2 - viewWidth / 2}`,
    width: `${viewWidth}px`,
    // 取图片的高度(带阴影的尺寸)
    height: `${uni.upx2px(80)}px`
};

2. 创建控件

new plus.nativeObj.View(id, styles, tags) 这个方法就是创建一个原生控件对象的方法,有三个参数:

  • id

View控件对象的id,这个id按逻辑上来讲是全局唯一的,后面有需要是可以通过 plus.nativeObj.View.getViewById() 方法来获取这个对象,就相当于我们设置divid 是一样的道理

  • styles

View控件的样式,设置控件的位置、大小等信息。可以设置这些样式:

  • tags

View控件初始绘制内容,可设置绘制图片、矩形区域、文本等内容。

3. 创建内容

create 方法是去创建了控件并给控件添加了点击事件和触摸事件。最后保存实例。

javascript 复制代码
// 创建区域以及背景
create() {
        let _view = null;
        // 创建 View区域
        _view = new plus.nativeObj.View(`alarmMsg-${this.item.alarmId || "ins"}`, this.viewStyle);

        // 拦截触摸事件: 开启后 区域内的触摸事件不会透传到下面
        _view.interceptTouchEvent(true);
        // 设置监听点击事件的区域为关闭图片的区域
        _view.setTouchEventRect({
                top: `${uni.upx2px(18)}px`,
                left: '92%',
                width: '22px',
                height: '22px'
        })
        // 增加点击事件监听
        _view.addEventListener("click", (e) => {
                if (this.hiding) return;
                this.hiding = true;
                this.cb && this.cb({ type: "click", result: this.item });
                this.animationHide();
        });
        // 触摸事件监听
        _view.addEventListener("touchstart", res => {
                this.clientY = res.clientY;
        });
        // 触摸事件监听
        _view.addEventListener("touchmove", res => {
                const {
                        clientY
                } = res;
                let offsetY = this.clientY - clientY;
                if (offsetY > 25 && !this.hiding) {
                        this.hiding = true;
                        this.cb && this.cb({
                                type: "move",
                                result: this.item
                        });
                        this.animationHide();
                }
        });
        // 保存
        this.view = _view;
        // 画内容
        this.drawInfo();
        // // 显示
        // this.animationShow();
}

然后封装drawInfo方法:

javascript 复制代码
// 画内容
drawInfo() {
    const {
            message
    } = this.item;
    this.view.draw([{
                tag: "rect",
                id: "rectBox",
                position: {
                    top: '0px',
                    left: `0px`,
                    width: `100%`,
                    height: `100%`
                },
                rectStyles: {
                    color: 'rgba(241, 58, 14, .6)',
                    radius: '8px',
                }
            },
            {
                tag: 'img',
                id: 'imgBox1',
                src: img1,
                position: {
                    top: `${uni.upx2px(22)}px`,
                    left: '10px',
                    width: '26px',
                    height: '22px'
                }
            },
            {
                tag: 'img',
                id: 'imgBox2',
                src: img2,
                position: {
                    top: `${uni.upx2px(18)}px`,
                    left: '92%',
                    width: '22px',
                    height: '22px'
                }
            },
            {
                tag: "font",
                id: "mainFont",
                text: message,
                textStyles: {
                    size: `${uni.upx2px(28)}px`,
                    color: "#feebeb",
                    align: "left"
                },
                position: {
                    top: `${uni.upx2px(28)}px`,
                    left: `10%`,
                    height: "wrap_content"
                }
            },
    ]);
}

这里的this.view 就是刚刚创建的控件实例,draw 是原生控件对象的方法,详情请查看官方文档

里面就把我们需要的内容都添加到数组里面就行了,比较麻烦的是这个位置,只能使用绝对定位的方式去控制,所以调试比较费时,其他的看文档就可以了。

4. 使用示例

app.vue文件

vue 复制代码
<script>
import { createAlarm } from '@/common/nativeObj.js';

export default {

    onLaunch() {
        // 先创建一个消息的实例
        this._netAlertMsgIns = createAlarm({
            alarmId: new Date().getTime(),
            message: '当前无法连接网络,可检查网络设置是否正常'
        });
        
        // 监听网络状态
        uni.onNetworkStatusChange(this.netWorkCallback);

        // 获取网络状态
        uni.getNetworkType({
            success: (res) => {
                let result = res.networkType !== 'none';
                if (result) {
                    this._netAlertMsgIns.hide();
                } else {
                    // 没网
                    this._netAlertMsgIns.animationShow();
                }
            },
            fail: () => {
                this._netAlertMsgIns.animationShow();
            }
        });
    },
    onUnload: function () {
        uni.offNetworkStatusChange(this.netWorkCallback);
    },
    methods: {
        netWorkCallback(res) {
            if (res.isConnected) {
                this._netAlertMsgIns.animationHide();
            } else {
                // 当网络断开后的逻辑
                this._netAlertMsgIns.animationShow();
            }
        }
    }
}
</script>

现在就可以很便捷的实现全局的一个网络状态改变时的消息提示了,不需要在页面去重复引入组件之类的操作,只需在软件的入口实现这个方法就可以了。

拓展

其实还可以通过配置来实现和element-uimessage提示组件一样的效果,后面看有时间去做成一个插件吧。

下班!

相关推荐
Lysun001几秒前
[less] Operation on an invalid type
前端·vue·less·sass·scss
J总裁的小芒果16 分钟前
Vue3 el-table 默认选中 传入的数组
前端·javascript·elementui·typescript
Lei_zhen9619 分钟前
记录一次electron-builder报错ENOENT: no such file or directory, rename xxxx的问题
前端·javascript·electron
咖喱鱼蛋21 分钟前
Electron一些概念理解
前端·javascript·electron
yqcoder22 分钟前
Vue3 + Vite + Electron + TS 项目构建
前端·javascript·vue.js
Dnelic-32 分钟前
【单元测试】【Android】JUnit 4 和 JUnit 5 的差异记录
android·junit·单元测试·android studio·自学笔记
鑫宝Code40 分钟前
【React】React Router:深入理解前端路由的工作原理
前端·react.js·前端框架
Mr_Xuhhh2 小时前
重生之我在学环境变量
linux·运维·服务器·前端·chrome·算法
永乐春秋3 小时前
WEB攻防-通用漏洞&文件上传&js验证&mime&user.ini&语言特性
前端
鸽鸽程序猿3 小时前
【前端】CSS
前端·css