Laravel GrapesJS中如何为编辑器元素创建并使用唯一custom_id
你之前的实现思路走了弯路:完全不需要修改、编译vendor发布的editor.js文件,不管是浏览器端动态改写源码还是存数据库做正则替换,都是不必要的。jd-dotlogics/laravel-grapesjs基于GrapesJS开发,后者本身提供了完整的扩展API,所有自定义需求都可以通过官方预留的配置脚本实现,不需要碰3.7万行的核心源码。
1. 清理无效配置
首先把config/laravel-grapesjs.php里scripts配置项中你加的fs.promises CDN、editor_control.js、modified_editor.js全部删除,只保留包自带的脚本和你自己的editor-config.js即可,包本身已经处理了脚本加载顺序,会保证editor-config.js在编辑器核心加载完成后执行,不存在时序问题。
2. 实现自定义工具栏按钮+弹窗+提交逻辑
所有逻辑直接写在editor-config.js中,通过GrapesJS官方API实现,不需要修改核心文件:
// 编辑器加载完成后执行 editor.on('load', () => { // 新增工具栏按钮 editor.Panels.addButton('options', [{ id: 'custom-bind-url', className: 'fa fa-link', // 复用编辑器自带的Font Awesome图标 command: 'triggerUrlBind', attributes: { title: '绑定自定义URL' } }]) // 注册按钮点击命令 editor.Commands.add('triggerUrlBind', { run(editor) { const selectedEl = editor.getSelected() if (!selectedEl) { alert('请先选中要绑定的元素') return } const customId = selectedEl.get('custom_id') const inputVal = prompt('请输入要绑定的外部URL:') if (!inputVal?.trim()) return // 提交到后端存储 fetch('/admin/grapesjs/save-bind', { method: 'POST', headers: { 'Content-Type': 'application/json', 'X-CSRF-TOKEN': document.querySelector('meta[name="csrf-token"]').content }, body: JSON.stringify({ page_id: window.currentEditPageId, // 自行传入当前编辑的页面ID,用于区分不同页面的绑定 custom_id: customId, bind_url: inputVal.trim() }) }).then(res => res.json()).then(res => { if (res.code === 0) { selectedEl.set('custom_bind_url', inputVal.trim()) } }) } }) })
3. 全局唯一custom_id生成
不要用正则替换createElement的方式注入ID,监听GrapesJS的组件创建钩子,按照你需要的层级路径规则生成唯一ID即可:
// 每个组件创建时自动生成custom_id editor.on('component:create', (component) => { // 已有ID则跳过,避免重复生成 if (component.get('custom_id')) return // 按 父类型=>父类型=>当前类型 的规则拼接层级路径,同级同类型加索引保证唯一 const pathSegs = [] let currentNode = component while (currentNode) { const parent = currentNode.parent const sameTypeSiblings = parent ? parent.components().filter(item => item.get('type') === currentNode.get('type')) : [currentNode] const index = sameTypeSiblings.findIndex(item => item.cid === currentNode.cid) pathSegs.unshift(`${currentNode.get('type')}${index}`) currentNode = parent } component.set('custom_id', pathSegs.join('=>')) })
- 如果需要随机唯一ID,直接用浏览器原生API
crypto.randomUUID()即可,现代浏览器全支持,不需要引入uuid等第三方依赖。
4. 历史绑定数据自动回填
编辑器加载完成、页面内容渲染完毕后,请求后端拉取当前页面的历史绑定数据,通过custom_id匹配到对应元素赋值:
editor.on('loaded', () => { // 拉取当前页面的所有绑定数据 fetch(`/admin/grapesjs/get-binds?page_id=${window.currentEditPageId}`) .then(res => res.json()) .then(res => { if (res.code !== 0) return const bindMap = res.data // 后端返回格式:{custom_id: bind_url} // 遍历画布内所有元素,匹配绑定值 const allComponents = editor.DomComponents.getWrapper().find('*') allComponents.forEach(comp => { const cid = comp.get('custom_id') if (cid && bindMap[cid]) { comp.set('custom_bind_url', bindMap[cid]) // 此处可加自定义逻辑,比如给元素绑定点击跳转事件、加标记样式等 } }) }) })
5. 保存时全量提交映射关系
如果需要在保存页面时全量同步绑定关系,监听编辑器的存储事件,把映射关系加到保存参数中即可:
editor.on('storage:start', (storageObj) => { const allComponents = editor.DomComponents.getWrapper().find('*') const bindMap = {} allComponents.forEach(comp => { const cid = comp.get('custom_id') const url = comp.get('custom_bind_url') if (cid && url) bindMap[cid] = url }) // 将映射关系追加到保存参数中 storageObj.params.custom_bind_map = JSON.stringify(bindMap) })
6. 后端实现说明
- 新建绑定关系表,核心字段:
id、page_id、custom_id、bind_url、created_at、updated_at - 实现两个接口:单条绑定保存接口(按钮点击时调用)、当前页面绑定列表查询接口(加载时调用),也可以直接在原有页面保存接口中接收
custom_bind_map参数做批量增删改 - 不需要把JS源码存入数据库做正则替换,该方案性能差、容易因特殊字符转义问题触发语法错误。
关于Blade模板向JS传递大段字符串的问题:如果确实需要传大段含特殊字符的内容到JS,不要直接用
{{ }}输出,使用Blade原生的@json($variable)指令即可,框架会自动处理所有引号、换行、反斜杠的转义,不会出现语法错误。
内容的提问来源于stack exchange,提问作者mstdmstd

