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