基于 Vue3 动态组件的弹框流程管理:命令模式事件
一、背景:从“弹框切换”到“弹框流程”
在中大型前端项目中,弹框往往不再只是简单的显示与隐藏,而是承载着创建、编辑、确认等一整套业务流程。如果仍然通过多个el-dialog或大量v-if来控制,很容易出现状态分散、切换逻辑混乱、扩展成本高等问题。
本文基于 Vue3,通过动态组件 + 配置驱动 + 轻量命令模式的方式,实现:
页面中只存在一个
el-dialog弹框内容可自由切换
弹框之间具备明确的 next / back 流程关系
流程中产生的数据可以稳定地在组件之间传递
二、核心设计思路
整体拆分为四个角色:
dialogConfig:描述“弹框长什么样”
dialogFlow:描述“弹框怎么走流程”
dialogCommand:对外暴露命令(open / next / back / close)
DialogContainer:唯一的
el-dialog容器
业务层只负责“发出指令”,不直接操作弹框状态。
三、弹框配置(dialogConfig)
import StepOne from './dialogs/StepOne.vue' import StepTwo from './dialogs/StepTwo.vue' import ConfirmDialog from './dialogs/ConfirmDialog.vue' export const dialogConfig = { stepOne: { component: StepOne, title: '第一步', width: '600px' }, stepTwo: { component: StepTwo, title: '第二步', width: '600px' }, confirm: { component: ConfirmDialog, title: '确认信息', width: '500px' } } as const export type DialogType = keyof typeof dialogConfig四、弹框流程配置(dialogFlow)
import type { DialogType } from './dialogConfig' export const dialogFlow: Record<DialogType, { next?: DialogType; back?: DialogType }> = { stepOne: { next: 'stepTwo' }, stepTwo: { back: 'stepOne', next: 'confirm' }, confirm: { back: 'stepTwo' } }流程关系完全由配置决定,组件内部不需要知道下一步是谁。
五、弹框状态与流程上下文
5.1 弹框状态
import { ref } from 'vue' import type { DialogType } from './dialogConfig' export const dialogVisible = ref(false) export const currentDialog = ref<DialogType | null>(null)5.2 流程上下文(用于组件之间传递数据)
import { reactive } from 'vue' export const dialogContext = reactive<Record<string, any>>({})dialogContext用于存放整个弹框流程中产生的数据,它的生命周期与流程一致。
六、命令层实现(核心)
6.1 打开弹框
export function openDialog(type: DialogType, data: Record<string, any> = {}) { currentDialog.value = type Object.assign(dialogContext, data) dialogVisible.value = true }6.2 下一步(携带数据)
import { dialogFlow } from './dialogFlow' export function nextDialog(payload: Record<string, any> = {}) { const current = currentDialog.value if (!current) return Object.assign(dialogContext, payload) const next = dialogFlow[current]?.next if (next) { openDialog(next, dialogContext) } }6.3 返回上一步
export function backDialog() { const current = currentDialog.value if (!current) return const back = dialogFlow[current]?.back if (back) { openDialog(back, dialogContext) } }6.4 关闭弹框并清理数据
export function closeDialog() { dialogVisible.value = false currentDialog.value = null Object.keys(dialogContext).forEach(key => delete dialogContext[key]) }七、统一弹框容器
<template> <el-dialog v-model="dialogVisible" :title="currentConfig?.title" :width="currentConfig?.width" destroy-on-close> <component :is="currentConfig?.component" v-bind="dialogContext" @next="nextDialog" @back="backDialog" @close="closeDialog" /> </el-dialog> </template> <script setup lang="ts"> import { computed } from 'vue' import { dialogConfig } from './dialogConfig' import { dialogVisible, currentDialog } from './useDialog' import { dialogContext } from './dialogContext' import { nextDialog, backDialog, closeDialog } from './dialogCommand' const currentConfig = computed(() => { return currentDialog.value ? dialogConfig[currentDialog.value] : null }) </script>八、组件如何产生与消费数据
8.1 上一步组件提交数据
<script setup lang="ts"> const emit = defineEmits(['next']) const handleNext = () => { emit('next', { name: 'Tom', age: 18 }) } </script>8.2 下一步组件直接使用数据
<script setup lang="ts"> const props = defineProps<{ name?: string; age?: number }>() </script>组件不需要知道数据来自哪一步,只关心当前要展示什么。
九、业务侧如何启动流程
import { openDialog } from '@/dialog/dialogCommand' const startFlow = () => { openDialog('stepOne') }十、组件之间的数据传递方式总结
在这套设计中,不推荐使用keep-alive来保存上一步组件状态,而是采用流程上下文的方式:
数据不存放在组件内部
所有步骤的数据统一进入
dialogContext命令层负责数据合并与流转
这样做的好处是:
组件之间完全解耦
数据流向清晰、可追踪
不受动态组件销毁影响
更适合有流程概念的弹框场景
结语
当弹框开始具备“流程”属性时,问题的本质就不再是 UI,而是控制权与数据归属。
通过动态组件承载 UI,通过配置描述流程,通过命令驱动行为,通过上下文管理数据,可以让弹框体系长期保持清晰、稳定、可扩展。这是一种非常贴近真实业务的 Vue3 弹框工程化实践。
