- 业务需求:点击商品菜单,弹出选择商品弹窗,选择好商品后弹窗关闭,文章中插入商品卡片
- 需求拆解:新增商品菜单--注册自定义菜单;控制弹窗显隐;插入商品--自定义新元素
下面就记录一下这些步骤中一一踩过的坑和解决方式吧
新增商品菜单
这一步就可以完全按照官网的步骤
1、编写insertGoods.js文件
class MyMenu {
constructor (newsEditor) {
this.title = '插入商品'
this.iconSvg = '<svg t="1690187176682" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="4413" width="200" height="200"><path d="M256.727757 323.584c16.9984 0 30.72-13.7216 30.72-30.72 0-125.952 102.4-228.352 228.352-228.352s228.352 102.4 228.352 228.352c0 16.9984 13.7216 30.72 30.72 30.72s30.72-13.7216 30.72-30.72c0-159.744-130.048-289.792-289.792-289.792S226.007757 133.12 226.007757 292.864c0 16.9984 13.7216 30.72 30.72 30.72zM895.089357 358.4H129.137357C51.108557 358.4-9.102643 431.104 1.137357 512.8192l37.888 303.104C53.975757 934.912 150.026957 1024 263.690957 1024h496.8448c113.664 0 209.5104-89.088 224.4608-208.0768l37.888-303.104c10.24-81.7152-49.7664-154.4192-127.7952-154.4192zM279.665357 692.224c-41.3696 0-74.752-33.3824-74.752-74.752s33.3824-74.752 74.752-74.752 74.752 33.3824 74.752 74.752-33.3824 74.752-74.752 74.752z m495.616 0c-41.3696 0-74.752-33.3824-74.752-74.752s33.3824-74.752 74.752-74.752 74.752 33.3824 74.752 74.752-33.3824 74.752-74.752 74.752z" p-id="4414"></path></svg>'
this.tag = 'button'
this.newsEditor = newsEditor
}
getValue (editor) {
return ''
}
isActive (editor) {
return false // or true
}
isDisabled (editor) {
return false
}
exec (editor) {
if (this.isDisabled(editor)) return
editor.emit('selectGoodsModalShow')
}
}
const myMenuConf = {
key: 'insertGoods',
factory: function () {
return new MyMenu()
}
}
export default myMenuConf
2、编辑器页面引入并注册菜单
import myMenuConf from './insertGoods.js'
Boot.registerMenu(myMenuConf)
这里注意,一定要在顶部注册,只能注册一次
控制弹窗显隐
这里踩了一点坑,一开始没发现emit这个函数,想着在new的时候传递编辑器页面的this过去,这样就得在onCreated函数里注册菜单,这样就存在重复注册的问题,然后又想了一个办法,给菜单名字+时间戳,就可以解决重复的问题,于是代码就写成了这样(你就说能不能用吧🙈)
const that = this
const menuKey = 'insertGoods' + moment(new Date()).valueOf()
const myMenuConf = {
key: menuKey,
factory: function () {
return new MyMenuClass(that)
}
}
Boot.registerMenu(myMenuConf)
this.$nextTick(() => {
const toolbar = DomEditor.getToolbar(this.editor)
const toolbarConfig = toolbar.getConfig()
const toolbarKeys = toolbarConfig.toolbarKeys
toolbarConfig.insertKeys = {
index: toolbarKeys.length - 1, // 插入的位置,基于当前的 toolbarKeys
keys: menuKey
}
})
后来发现了emit,这就舒服了
文章中插入商品
这里是重头戏了,一开始使用了文档中的dangerouslyInsertHtml方法,并不行,后来仔细阅读文档,开来是要自己开发插件了
- 首先按照官网的说明定义的我们数据结构并使用insertNode插入(在选好商品后做一步)
const { img, title, price, id } = goodsInfo
this.editor.insertNode({
type: 'goodscard',
img,
title,
price,
id,
children: [{ text: '' }]
})
- 参照官网的renderElem 编写编译器页面的dom结构渲染
代码较长,可参考官网的renderElem,是一样的
- 参照官网的elemToHtml 定义拿到的html结构
代码较长,可参考官网的elemToHtml,是一样的
- 参照官网的parseAttachmentHtml 处理编辑文章时候的数据解析
代码较长,可参考官网的parseAttachmentHtml,是一样的
到这里,文章中插入商品卡片,拿到字符串html,编辑解析html,按道理讲应该都都已经可以了,但是我一插入,就会报错,应该很多小伙伴都遇到了,因为我在git的issue上看到了很多🙈,在问题复现的时候我发现有时候是正常不报错的,跟之前注册新菜单时候有点类似,于是想到是不是也是注册的问题,然后,把这些注册都放到了最外面,于是,问题解决了🙉
这时候又出现了一个问题,商品卡片后面的内容都不能编辑,官网上说这时候需要编写一个plugin.js 来重新定义你的节点数据类型
- 按照官网的插入附件案例,因为我的卡片是块级元素,所以就只定义了isVoid,但是发现咋还是不行呢🪂(要跳崖了)
newEditor.isVoid = elem => {
const type = DomEditor.getNodeType(elem)
if (type === 'goodscard') return true // 针对 type: goodscard ,设置为 void
return isVoid(elem)
}
这时候发现官网有一个插件【链接卡片】和我的卡片很类似,于是去翻翻源码,发现他的plugin竟然真的不太一样多了一部分的处理,我反手就是一个control C + control V github.com/wangeditor-…
// 重新 normalize
newEditor.normalizeNode = ([node, path]) => {
const type = DomEditor.getNodeType(node)
if (type !== 'link-card') {
// 未命中 link-card ,执行默认的 normalizeNode
return normalizeNode([node, path])
}
// editor 顶级 node
const topLevelNodes = newEditor.children || []
// --------------------- link-card 后面必须跟一个 p header blockquote(否则后面无法继续输入文字) ---------------------
const nextNode = topLevelNodes[path[0] + 1] || {}
const nextNodeType = DomEditor.getNodeType(nextNode)
if (
nextNodeType !== 'paragraph' &&
nextNodeType !== 'blockquote' &&
!nextNodeType.startsWith('header')
) {
// link-card node 后面不是 p 或 header ,则插入一个空 p
const p = { type: 'paragraph', children: [{ text: '' }] }
const insertPath = [path[0] + 1]
SlateTransforms.insertNodes(newEditor, p, {
at: insertPath, // 在 link-card 后面插入
})
}
}
好了,大功告成,到这里编译器插入卡片,解析html,编辑,编译器操作都能正常使用了;
但是又发现一个问题,因为我定义的是block类型的数据,使用insertNode插入dom时候,不管当前行是否有内容都会新开一行进行插入,像这样
这里的问题,找了很久都没有找到解决方案,在issue上也发现很多人遇到了这个问题,但都没有解决,尝试了很久,想着先判断当前行是否是空内容,如果是,便先对当前节点进行删除处理,感觉思路是对的,于是又去翻源码,发现了SlateTransforms.removeNodes 这个方法可以删除节点,试了一下是可行的,所以,这样就完美解决了这个问题。
到此插入商品卡片的功能就完全实现了,真是踩了不少的坑,最后总结一下,开发中真的要多点耐心,多尝试,也可以找找源码,可以发现不一样的灵感💃