从“静态尴尬”到“真实交互”:GrapesJS Drawer 组件的创新性设计

在前端开发中,GrapesJS 作为一款可视化页面搭建工具,可以说是非常"务实"的存在:界面拖拽、实时预览、源码编辑等功能都很完备,极大地降低了开发和维护静态页面的成本。然而,GrapesJS 在处理 动态内容 时却往往让人陷入困境:用它做静态页面得心应手,可一旦遇到需要根据交互逻辑动态维护内容的需求,转而就需要大量二次开发,才能让动态部分和静态页面完美结合。

今天分享的这篇文章,就来"现身说法"------如何在 GrapesJS 中开发一个动态渲染的 Drawer 弹框组件 。我们会通过一个多页面的设计思路,讲解如何巧妙分离"静态设计"与"动态交互"这两个互相矛盾又息息相关的需求。希望可以帮助更多的小伙伴快速上手。

一、为什么需要动态 Drawer ?

1.1 项目背景和痛点

想象一个实际业务场景:我们需要在页面中加入一个右侧弹出的信息填写弹窗(Drawer),用于展示或编辑一份动态获取的数据表单。对于前端和 UI 设计师来说,大多喜欢直接在页面设计中预览这个 Drawer 的大致样式,最好还能在拖拽组件时就能看到它的最终效果。但现实是,Drawer 中的内容是要根据后端接口返回的信息进行动态渲染的,GrapesJS 并不直接支持这种"动态可视化"。

那怎么办?假如我在已有的 GrapesJS 设计器中强行塞入这个 Drawer,要么 我得提前写好占位的静态内容,要么在视觉设计时完全展示不出来------都很别扭,也很容易导致后续联调或维护上的混乱。

1.2 多页面模式的思路

正如开始所说,前端可视化搭建往往离不开额外的配置或二次开发,来处理那些"静态 + 动态"的场景冲突。我们在项目中找出的一个巧妙思路 是:将这个 Drawer 的静态结构和需要动态渲染的表单视图------拆分到不同的页面进行开发

  1. PageA:主页面(GrapesJS 设计完的正常功能页)。在这个页面中,我们只需要留出一个按钮或者链接,告诉系统:"当我点击这个按钮时,会呼出一个 Drawer"。
  2. PageB:Drawer 组件所在的页面。在这个页面上,设计师可以独立使用 GrapesJS(或其他工具)进行表单、文本、样式的可视化设计。因为这一块内容是像一个完整的"小页面",就更容易在 GrapesJS 里独立制作。在实际运行时再通过 Node.js 渲染成 HTML,再动态插入到应用中。

如此,通过"多页面"的方式,就让主页面和弹窗页面各司其职:主页面只负责静态排版和调用逻辑,而弹窗页面则专注于动态样式和内容的可视化。最终再通过一段加载逻辑,将弹窗与主页面串联起来。

二、核心思路 + 代码原理

下面是我们项目中的关键代码片段,给大家做参考。该组件基于 Vue3 + Ant Design Vue 构建,将 Drawer 组件的调用和渲染拆分到两个部分:

  • 入口函数showDrawer,负责将我们真正的 Drawer 组件(AndDrawer.vue)动态挂载到页面上。
  • Drawer 组件AndDrawer.vue,内部会根据后端返回的 HTML 和 CSS,在运行时进行插入渲染。

2.1 入口:showDrawer(opts: DrawerProps)

const showDrawer = (opts: DrawerProps) => {
  Promise.all([
    import('@/views/system/function/editor/components/AndDrawer.vue'),
  ]).then(([vueModule]) => {

    let app: App | null = null;
    let mountNode: HTMLDivElement | null = null;

    const drawerProps = reactive({
      ...opts,
      onClose: () => {
        app?.unmount();       // 通过可选链安全访问
        mountNode?.remove();  // 更安全的 DOM 移除方式
      }
    });

    app = createApp({
      render() {
        return h(ConfigProvider, { locale: zhCN }, () =>
          h(vueModule.default, { drawerProps, onClose: drawerProps.onClose })
        );
      },
    });

    mountNode = document.createElement('div');
    document.body.appendChild(mountNode);

    app.use(Antd);
    app.mount(mountNode);
  }).catch((err) => {
    console.error('Failed to mount Iterator component:', err);
  });
};

从代码可以看出,这段逻辑在需要的时候,才会异步请求并挂载我们真正的 AndDrawer.vue 组件(这也是为什么可以把它放在另外一个"PageB"里,或独立的模块中)。这样,在主页面中只需在合适的时机调用 showDrawer(),就能瞬时展示一个右侧弹窗。

2.1.1 为什么要 Promise + 动态 import?

因为我们期望 Drawer 组件只有在真正需要时才被加载,而不是主页面一加载就把所有依赖都打包进来。也方便后续独立维护和版本管理。

2.2 动态 Drawer 组件:AndDrawer.vue

<template>
  <a-drawer v-model:open="open" :title="drawerProps.title" placement="right" :width="drawerProps.width">
    <div ref="container"></div>
  </a-drawer>
</template>

<script lang="ts" setup>
import { onMounted, ref } from 'vue';
import { type DrawerProps } from '@/views/system/function/common/CommonTypes';
import { defHttp } from '/@/utils/http/axios';
import { appendHTMLToFirstElementByClass } from '@/views/system/function/utils/Base';

const open = ref<boolean>(true);

// 组件属性
const props = withDefaults(defineProps<{
  drawerProps: DrawerProps;
}>(), {
  drawerProps: () => (
    { width: 600, title: '默认标题', placement: 'right' }
  )
});

const container = ref<HTMLElement>();

onMounted(() => {
  query();
});

const query = () => {
  let params = {
    module_name: props.drawerProps.moduleName,
    comp_id: props.drawerProps.compId,
    clone_num: 1
  }

  // 请求后端 Node.js 的生成接口
  defHttp.post({ url: `/generate_ui/ui/generate_html`, params }, { isTransformResponse: false })
    .then(r => {
      if (r.success) {
        appendElement(r);
      }
    })
    .catch(e => {
      console.log(e);
    })
    .finally(() => {
    });
};

const appendElement = (r) => {
  let containerElement = container.value;
  appendHTMLToFirstElementByClass(
    containerElement as HTMLElement, 
    r.html, 
    r.css, 
    { preserveExistingContent: false }
  )
};
</script>

<style scoped></style>

简要来看,这个组件所做的事情就是:

  1. 先借助 props.drawerProps 中的参数,来决定 Drawer 的基本属性(宽度、标题、placement 等)。
  2. onMounted 阶段调用 query() 接口,去 Node.js 后端 生成或获取该弹窗要动态渲染的 HTML 片段和对应的 CSS。
  3. 把后端返回的内容插进 div ref="container" 里,实现 "页面设计" + "实时数据" 的融合。

至于 generate_html 接口背后是如何实现的,就不在本文过多展开。大致来说:这是一个在 Node.js 端"无头"环境里,将 PageB 通过类似 puppeteer / ejs / handleBars 等模板技术进行渲染处理,然后把结果打包成 HTML + CSS,由 Vue 接管并注入到页面中。

三、解决了哪些问题?

3.1 前后端标准分离 + 动态渲染

痛点:如果把所有对动态内容的处理都写死在 GrapesJS 中,后续维护非常困难,而且 GrapesJS 也并不适合做复杂的数据交互逻辑。

解决:通过"多页面"+"动态挂载"的思路,我们把 Drawer 的业务逻辑、可视化布局、以及动态接口调用都分散到更合适的地方。让 GrapesJS 只负责静态部分的设计,实现高度解耦。

3.2 静态可视化 & 动态展示 "两全其美"

痛点:在 GrapesJS 上直接预览动态数据是不现实的,或者需要值班开发手动写 mock。 这样维护量大,还不一定跟真正的后端接口对得上。

解决:把需要动态加载的区域放到另一个可视化页面设计中,分别各自专注于"静态"和"动态"部分。需要展示时,通过"动态 drawer"拉过来即可,避免前期搭建时的臃肿,也让"设计师"或"运营"只需要在对应页面专心做可视化设计。

3.3 模块化 & 复用

痛点:很多时候我们需要的 Drawer 组件不仅是一个页面用,还可能多个业务场景公用,或者在不同项目里复用。

解决 :一个"动态挂载"的通用 Drawer 组件,只需传入对应的参数,比如大小、标题、内容生成 API 地址......其余都由我们统一封装。这样就像"装配线"一样,无论在哪个页面,只要调用了 showDrawer,就能快速拉起同样的 Drawer 结构。

四、背后的挑战 & 实践心得

不得不说,在最初做这套方案时,也遇到过不少痛点和"坑":

  1. 动态 import 兼容性:早期某些低版本浏览器或打包工具对动态 import 不太友好,需要我们配置 Babel 或 Vite/webpack 的相应设置。
  2. Node.js 模板渲染:如何在 Node.js 里无头地渲染出我们想要的页面片段,需要一定的插件或技术栈,比如 puppeteer、ejs 或其他 SSR 方案。要保证渲染出的 HTML/CSS 能在前端畅通无阻地插入。
  3. 样式冲突:在把样式插入到主页面时,可能会遇到命名冲突或层级冲突问题,需要借助类似 CSS Modules、Scoped CSS 或者最简单的 BEM 命名规范来避免。
  4. 通信与交互 :如果 Drawer 内部还牵涉到一些业务交互,比如数据回调到主页面,需要借助事件总线、或者把通信事件都封装到 DrawerProps 里,避免耦合。

但解决完这一系列的问题后,随之而来的,是模块化可复用解耦程度都有了大幅的提升。尤其是当项目发展到一定规模时,这种"多页面 + 动态弹窗合并"的模式变得很有价值。

五、总结 & 展望

这篇文章从一个比较具体的场景出发,介绍了如何让 GrapesJS + Vue3 也能轻松处理"动态 Drawer"需求的思路。通过拆分到多页面的方式,借助 Node.js 在后台对页面进行无头渲染,然后在需要的场景中再用一段通用的挂载脚本去"拉起"这个动态 Drawer。看似复杂,实则为后续的功能扩展提供了相对清晰的思路与实施路径。

回顾一下,这样做的核心好处就是

  • 将静态与动态部分分开,降低对 GrapesJS 的侵入;
  • 让主页面的设计过程保持简单;
  • Drawer 部分的动态更新逻辑也能专门维护,升级更自由。

如果你也在使用 GrapesJS 或者其它低代码工具,并苦于如何处理动态渲染、不想写很多 mocked 占位,那么不妨试试这种"多页面 + 动态挂载"的玩法。既能不破坏已有的可视化体验,又能让"真实"逻辑"后期注入",十分灵活。

希望这次的分享能帮助到你 ,让开发者与设计师在同一个环境里更愉快地合作,也让你的前端项目在面对动静结合的场景时游刃有余。

如果你也有其他关于 GrapesJS 动态内容渲染或二次开发的经验,欢迎留言、交流。让我们一起在可视化搭建领域持续探索与进步!

相关推荐
西猫雷婶23 分钟前
神经网络|(十四)|霍普菲尔德神经网络-Hebbian训练
人工智能·深度学习·神经网络
张拭心1 小时前
2024 总结,我的停滞与觉醒
android·前端
念九_ysl1 小时前
深入解析Vue3单文件组件:原理、场景与实战
前端·javascript·vue.js
Jenna的海糖1 小时前
vue3如何配置环境和打包
前端·javascript·vue.js
梦丶晓羽1 小时前
自然语言处理:文本分类
人工智能·python·自然语言处理·文本分类·朴素贝叶斯·逻辑斯谛回归
SuperCreators1 小时前
DeepSeek与浏览器自动化AI Agent构建指南
人工智能·自动化
美狐美颜sdk2 小时前
什么是美颜SDK?从几何变换到深度学习驱动的美颜算法详解
人工智能·深度学习·算法·美颜sdk·第三方美颜sdk·视频美颜sdk·美颜api
星之卡比*2 小时前
前端知识点---库和包的概念
前端·harmonyos·鸿蒙
灵感__idea2 小时前
Vuejs技术内幕:数据响应式之3.x版
前端·vue.js·源码阅读
訾博ZiBo2 小时前
AI日报 - 2025年3月10日
人工智能