企业级命令式弹窗方案:三行代码适配已有 Dialog

28 阅读5分钟

vue-layerx 1.0.1 正式发布。

文档:xuyimingwork.github.io/vue-layerx/
仓库:github.com/xuyimingwor…

业务页弹窗一多,模板里往往会出现这种风景:

  • 一堆 xxxVisible / v-model 样板代码
  • Dialog / Drawer 和表单绑死在同一个文件,内容复用很麻烦
  • 想命令式 open(),不想换掉现有组件库、又不想写 JSX

如果你也踩过这些坑,这篇可以当作一次「能落地」的解法介绍。

先看结果:三行代码打开弹窗

const useDialog = createLayer(ElDialog)
const dialog = useDialog(HelloWorld)
dialog.open()

拆开读就是三件事:

  1. 适配:用项目里已有的 Dialog / Drawer / Popup 生成 useDialog
  2. 绑定:传入普通业务内容组件
  3. 打开:命令式调用,页面上不必再堆弹层模板和 visible

vue-layerx 的立场很明确:

不碰的组件库,只提供命令式 API,把「弹层壳」和「业务内容」拆开编排。

核心模型:容器 × 内容

一次弹层只有两个角色:

职责例子
容器(container)显隐、遮罩、标题栏等壳ElDialogElDrawer、项目内 BaseDialog
内容(content)普通业务组件,可页内复用UserForm、详情面板

对应 API 流水线:

  • createLayer(容器) => 配容器
  • useDialog(内容) => 配内容
  • dialog.open() => 单次打开

这种拆法带来新的治理空间:

  • 容器组件可以在基建层统一(宽度、遮罩、关闭策略)
  • 内容组件可以在业务层复用(弹窗内、页面内共用同一份组件)
  • 调用页只关心「打开谁、传什么参」

从适配到打开:最小可用路径

1. 适配已有容器

// dialog.ts
import { createLayer } from 'vue-layerx'
import { ElDialog } from 'element-plus'

export const useDialog = createLayer(ElDialog, {
  props: {
    // 配置一些默认属性
    width: '480px',
    destroyOnClose: true,
    appendToBody: true,
  }
})

Element Plus、Ant Design Vue、Vant、项目内 BaseDialog 都可以——只要是靠 v-model(或同类显隐 prop)控制显示隐藏的组件。

2. 内容就是普通组件

<!-- HelloWorld.vue -->
<template>
  <p>Hello World</p>
</template>

3. 使用时命令式打开

<script setup lang="ts">
import HelloWorld from './HelloWorld.vue'
import { useDialog } from './dialog'

const dialog = useDialog(HelloWorld)
</script>

<template>
  <button @click="dialog.open({ props: {
    // 这里可以向 content 传递参数
    message: `你好`
  } })">打开弹层</button>
</template>

页面模板里不再出现 Dialog 样板;visible 变量也不用维护。

插槽继续用模板

命令式组件的问题是,将组件交给工具渲染后,模板上没有组件了,也没有地方插入插槽了。

大部分方案让你传 JSX 节点,但是这会带来项目复杂度上升。

在 vue-layerx 中,你可以利用 LayerTemplate

<script setup lang="ts">
import { LayerTemplate } from 'vue-layerx'
import HelloWorld from './HelloWorld.vue'
import { useDialog } from './dialog'

const dialog = useDialog(HelloWorld)
</script>

<template>
  <button @click="dialog.open()">打开弹层</button>
  <LayerTemplate :to="dialog" name="footer" container>
    我会出现在 dialog 的 footer slot 里
  </LayerTemplate>
</template>

不强制 JSX,团队学习成本更低。

内容组件里的也能传递模板

每次都让调用方去传递 footer 内容有点麻烦,而且业务逻辑没有自包含。

vue-layerx 还提供了 defineLayer,让内容组件也可以向容器传递配置或插槽模板。

<!-- HelloWorld.vue -->
<script setup lang="ts">
import { defineLayer, LayerTemplate } from 'vue-layerx'

const layer = defineLayer({
  // 从内容组件配置 dialog 容器
  props: { width: '380px' }
})
</script>
<template>
  <p>Hello World</p>
  <LayerTemplate :to="layer" name="footer">
    我会出现在 dialog 的 footer slot 里
  </LayerTemplate>
</template>

问:这样写和直接写 HelloWorldDialog.vue 有什么区别?

答:区别是 HelloWorld.vueElDialog.vue 拆开了。HelloWorld.vue 可以作为普通组件随便用在某个页面上,同时可以和 ElDrawer.vue 等其它容器组件随意组合。

对于项目中已有的 HelloWorldDialog.vue,vue-layerx 提供了渐进式接入方案! 见:容器与内容未拆分

企业向:配置可叠,也能「最后钉死」

真实项目里,同一项配置会出现在多处:

  • createLayer:基建默认
  • defineLayer:内容契约
  • useDialog / open:调用方覆盖

vue-layerx 允许它们叠加,并约定越靠后的优先级越高。

但有时项目要的不是「默认值」,而是「合并完仍然必须如此」——例如全站禁止点遮罩关闭。

这时用 adapter:等各处配置合并完,再改一次结果。

import type { LayerAdapter } from 'vue-layerx'
import { createLayer } from 'vue-layerx'
import { ElDialog } from 'element-plus'

const enforceMaskNotClosable: LayerAdapter = (config) => ({
  ...config,
  container: {
    ...config.container,
    props: {
      ...config.container?.props,
      closeOnClickModal: false,
    },
  },
})

export const useDialog = createLayer(ElDialog, {
  adapter: enforceMaskNotClosable,
})

是的,vue-layerx 提供了 aop 能力

其它能力

  • 提供等待弹窗结果的能力:await dialog.confirm()
  • 响应式配置的能力:useDialog(HelloWorld, () => ({ props: { id: props.id } }))
  • 命令式调用下 Provide / Inject 的上下文断裂与修复
  • LayerTemplate 跨实例插槽传送时的响应式依赖追踪
  • 配置合并(基建/内容/调用方/AOP)在高并发场景下的优先级冲突
  • vue devtools 内容展示

适合谁 / 不适合谁

更适合:

  • Vue 3 业务中台,弹窗 / 抽屉很多
  • 已有 Element / Ant / Vant / 自研壳,不想换库
  • 希望表单组件既能页内用,又能命令式打开
  • 需要基建层统一弹层策略(关闭规则、宽度、字段映射)

暂时不必:

  • 全项目只有一两个简单 Dialog,样板还能接受
  • 团队已经深度绑定某套「自研 Modal 运行时」,迁移成本大于收益

质量保证

  • 全量 TypeScript
  • 零运行时外部依赖(peer 只要求 Vue)
  • 语句 / 分支 / 函数 / 行测试覆盖 100%,测试用例 300+
  • 支持 SSR(可在 Nuxt / Vite SSR 中安全引入)
  • 文档站 + Playground,可直接点着试

我还是 vue-asyncx 的作者

vue-asyncx 解决的是异步样板代码。

这次的 vue-layerx 解决的是弹层样板——同样是组合式、少侵入、尽量复用你已有的代码。

现在就可以试

pnpm add vue-layerx

文档网站里有许多可以直接点击的案例哟,欢迎体验