全局交互革命:统一弹窗体系
在现代移动应用中,弹窗(Modal)是最基础也是最常用的交互组件。然而,传统的弹窗实现往往存在耦合度高、调用繁琐、扩展性差等问题。
本文将详细介绍本项目如何通过 Pinia Store + 全局挂载 + Hook 封装 的组合拳,打造一个既支持基础 Alert/Confirm,又支持多层级、可翻页公告的强大全局弹窗体系。
一、 传统痛点 vs 创新方案
1. 传统痛点
- 组件地狱:需要在每个页面重复引入
<modal>组件,模板代码冗余。 - 调用受限:只能在 Vue 组件内部通过
this.$refs.modal.open()调用,无法在纯 JS 文件(如 Axios 拦截器、Router 守卫)中直接唤起。 - 功能单一:通常只支持简单的标题+内容,面对复杂的“多条公告翻页”、“富文本展示”等需求无能为力。
2. 创新方案:HjModal
我们构建了一个全局单例的弹窗系统,它像操作系统的系统级弹窗一样,可以在任何时间、任何地点被唤起。
二、 核心架构设计
1. 视图层:全局挂载
在 App.ku.vue 或 Layout 根节点中,我们只放置唯一一个 <hj-modal-global> 组件。
<!-- src/components/hjModal/index.vue -->
<template>
<hj-modal v-model="store.show" v-bind="store.state" @confirm="store.handleConfirm" @cancel="store.handleCancel">
<slot />
</hj-modal>
</template>
<script setup lang="ts">
import { useModalStore } from '@/store/hjModal/index';
// 这个组件直接与 Pinia Store 绑定
const store = useModalStore();
</script>
2. 逻辑层:Store 驱动
整个弹窗的状态(显示/隐藏、标题、内容、回调函数)完全由 Pinia Store 管理。这使得我们可以通过操作 Store 来驱动视图。
核心 Store 实现 (src/store/hjModal/index.ts):
export const useModalStore = defineStore('modal', () => {
// 弹窗队列(支持连续弹出)
const queue = ref<ModalOptions[]>([]);
const show = ref(false);
// 扁平化状态,方便直接 v-bind 到组件
const state = reactive({
title: '提示',
content: '',
typeCode: 'alert', // 支持 alert, confirm, notice
// ...
});
// 核心方法:打开弹窗
const openModal = (options: ModalOptions) => {
queue.value.push(options);
processQueue(); // 触发队列处理
};
// 支持 Promise 调用的 Confirm
const handleConfirm = async () => {
if (onConfirmCallback.value) {
await onConfirmCallback.value();
}
closeModal();
};
return { openModal, state, show, ... };
});
3. 能力层:Hook 封装
为了应对复杂的业务逻辑(如公告的翻页、接口请求),我们将核心交互逻辑抽离到了 Hook 中。
src/components/hjModal/useHjModal.ts:
export function useHjModal(props, emit) {
// 内部维护公告列表
const internalNoticeList = ref([]);
// 自动加载远程公告
const initNoticeList = async () => {
if (props.noticeApi) {
const res = await request(props.noticeApi, ...);
internalNoticeList.value = res.list;
}
};
// 翻页逻辑
const nextNotice = () => { ... };
return { internalNoticeList, nextNotice, ... };
}
三、 实战场景
场景 1:最简单的命令式调用
在任何 JS 文件中(例如拦截器捕获到 401 错误):
import { useModalStore } from '@/store/hjModal';
// 无需引入组件,直接调用
useModalStore().openModal({
title: '登录过期',
content: '您的会话已失效,请重新登录',
onConfirm: () => {
router.push('/login');
}
});
场景 2:多层级公告(复杂交互)
系统需要弹出一个包含多条更新日志的公告,支持翻页查看,甚至在点击某条公告详情时叠加新的弹窗层级。
useModalStore().openModal({
typeCode: 'notice', // 切换为公告模式
title: '系统更新',
noticeApi: '/api/system/notices', // 告诉弹窗去哪里拉取数据
noticeParams: { version: '1.0.0' }
});
此时,全局弹窗组件会自动:
- 显示 Loading 状态。
- 调用
noticeApi获取数据(支持多条数据)。 - 渲染出带有“上一页/下一页”按钮的富文本内容区域,实现翻页效果。
- 多层级支持:如果当前已有弹窗,新的
openModal调用会将新弹窗推入queue队列或根据策略直接叠加覆盖,实现层级递进体验。 - 用户看完点击关闭后,自动销毁当前层,展示下一层或彻底关闭。
四、 方案优势
- 解耦:视图与逻辑完全分离,业务代码中不再混杂弹窗的 HTML 结构。
- 全域调用:打破了组件层级的限制,任何模块(Store、Router、Utils)皆可唤起 UI 交互。
- 高扩展性:通过
typeCode和 Hook 机制,我们可以轻松扩展出“输入框弹窗”、“图片广告弹窗”等多种形态,而无需修改调用方的代码。 - 队列管理:内置队列机制,确保多个弹窗指令同时发出时,能按顺序依次展示,不会重叠冲突。
这套体系将弹窗从一个“UI 组件”升级为了一种“系统服务”,极大地提升了交互开发的灵活性和效率。