这里封装一个比较全面的message和loading,因为loading和message在uniapp中只能存在一个,所以导致经常会出现提示语一闪而过的情况,所以封装一个函数方法,解决问题,同时也让loading不会出现一闪而过的情况

// utils/message.js

/**
 * 消息提示管理工具
 * 解决 uni-app 中 loading 和 toast 冲突问题
 * 支持多 loading 计数、自动关闭、队列管理等
 */
class MessageManager {
	constructor() {
		this.loadingCount = 0; // 请求计数
		this.loadingMap = new Map() // 🔥 记录每一次 showLoading 的调用栈
		this.realShowTimer = null; // 真正 showLoading 的定时器
		this.minShowTimer = null; // 保证至少显示 1 s 的定时器
		this.startTime = 0; // 第一次 showLoading 的时间戳
		this.hasShown = false; // 本次是否已经真正出现过 loading
		this.toastQueue = [];
		this.isShowingToast = false;
		this.defaultLoadingText = '加载中...';
		this.defaultToastDuration = 3000;

		// 🔥 兜底:单例最长 10 s 必须消失
		this.maxAlive = 10000
		this.maxAliveTimer = null

		// #ifdef H5
		document.addEventListener('visibilitychange', () => {
		  if (document.hidden) this.clearAll()
		})
		// #endif
		
		// #ifndef H5
		uni.onAppHide(() => this.clearAll())
		// #endif
	}

	/* 生成唯一 key,用于精准关闭 */
	genKey() {
		return `${Date.now()}-${Math.random().toString(36).slice(2, 8)}`
	}


	/**
	 * 显示加载
	 * @param {string} title
	 * @param {boolean} mask
	 * @returns {string} key  可用于主动 hide
	 */
	showLoading(title = this.defaultLoadingText, mask = true) {
		const key = this.genKey()
		if (this.loadingCount === 0) {
			this.startTime = Date.now()
			this.hasShown = false
			this.realShowTimer = setTimeout(() => {
				uni.showLoading({
					title,
					mask
				})
				this.hasShown = true
				this.realShowStart = Date.now()
			}, 1000)

			// 🔥 兜底定时器
			this.maxAliveTimer = setTimeout(() => {
				console.error('[MessageManager] loading 存活超过 maxAlive,强制清空', this.loadingMap)
				this.clearAll()
			}, this.maxAlive)
		}

		this.loadingCount++
		// 🔥 记录调用栈
		this.loadingMap.set(key, new Error().stack)
		return key
	}

	/**
	 * 隐藏 loading
	 * @param {boolean} force  强制清零
	 * @param {string} key     如果传了 key,只关闭这一次
	 */
	hideLoading(force = false, key) {
		if (key) {
			if (!this.loadingMap.has(key)) return // 已经关过了
			this.loadingMap.delete(key)
			this.loadingCount = Math.max(0, this.loadingCount - 1)
		} else {
			if (!force && this.loadingCount > 0) this.loadingCount--
			else this.loadingCount = 0
		}

		if (this.loadingCount === 0) {
			clearTimeout(this.realShowTimer)
			clearTimeout(this.maxAliveTimer)
			if (this.hasShown) {
				const alreadyShow = Date.now() - this.realShowStart
				const needDelay = Math.max(0, 1000 - alreadyShow)
				this.minShowTimer = setTimeout(() => {
					uni.hideLoading()
					this.checkToastQueue()
				}, needDelay)
			} else {
				uni.hideLoading()
				this.checkToastQueue()
			}
		}
	}


	/**
	 * 显示轻提示
	 * @param {string} title - 提示文字
	 * @param {object} options - 配置项
	 * @param {string} options.icon - 图标
	 * @param {number} options.duration - 持续时间(ms)
	 * @param {boolean} options.mask - 是否显示透明蒙层
	 */
	toast(title, options = {}) {
		const config = {
			title,
			icon: options.icon || 'none',
			duration: options.duration || this.defaultToastDuration,
			mask: options.mask || false
		};

		// 如果当前有loading显示,先关闭loading并加入队列
		if (this.loadingCount > 0) {
			this.hideLoading(true);
			this.toastQueue.push(config);
			return;
		}

		// 如果当前正在显示toast,加入队列
		if (this.isShowingToast) {
			this.toastQueue.push(config);
			return;
		}

		this.showToast(config);
	}

	/**
	 * 显示toast
	 * @param {object} config - toast配置
	 */
	showToast(config) {
		this.isShowingToast = true;
		uni.showToast({
			...config,
			complete: () => {
				this.isShowingToast = false;
				// toast显示完成后检查队列
				setTimeout(() => {
					this.checkToastQueue();
				}, 400); // 增加一点延迟避免快速切换的问题
			}
		});
	}

	/**
	 * 检查并显示队列中的toast
	 */
	checkToastQueue() {
		if (this.toastQueue.length > 0 && !this.isShowingToast && this.loadingCount === 0) {
			const nextToast = this.toastQueue.shift();
			this.showToast(nextToast);
		}
	}

	/**
	 * 成功提示
	 * @param {string} title - 提示文字
	 * @param {number} duration - 持续时间(ms)
	 */
	success(title, duration = this.defaultToastDuration) {
		this.toast(title, {
			icon: 'success',
			duration
		});
	}

	/**
	 * 错误提示
	 * @param {string} title - 提示文字
	 * @param {number} duration - 持续时间(ms)
	 */
	error(title, duration = this.defaultToastDuration) {
		this.toast(title, {
			icon: 'error',
			duration
		});
	}

	/**
	 * 清空所有提示
	 */
	clearAll() {
		clearTimeout(this.realShowTimer)
		clearTimeout(this.minShowTimer)
		clearTimeout(this.maxAliveTimer)
		this.loadingCount = 0
		this.loadingMap.clear()
		this.toastQueue = []
		this.hasShown = false
		uni.hideLoading()
		uni.hideToast()
		this.isShowingToast = false
	}
}

// 导出单例
export default new MessageManager();
Logo

腾讯云面向开发者汇聚海量精品云计算使用和开发经验,营造开放的云计算技术生态圈。

更多推荐