likes
comments
collection
share

kendo-vue-editor使用及踩坑记录

作者站长头像
站长
· 阅读数 18

背景

公司买了kendoUI的license,项目中刚好需要使用富文本编辑器,大佬们决定使用kendo-vue-editor,(之前我接触比较多的是Quill)

kendo-vue-editor使用及踩坑记录

使用方式

首先需要引入比较多的依赖:

"@progress/kendo-drawing": "^1.17.2"
"@progress/kendo-licensing": "^1.2.2",
"@progress/kendo-theme-default": "^5.12.0",
"@progress/kendo-vue-buttons": "^3.7.3",
"@progress/kendo-vue-dialogs": "^3.7.3",
"@progress/kendo-vue-dropdowns": "^3.7.3",
"@progress/kendo-vue-editor": "^3.7.3",
"@progress/kendo-vue-inputs": "^3.7.3",
"@progress/kendo-vue-intl": "^3.7.3",
"@progress/kendo-vue-layout": "^3.7.3",
"@progress/kendo-vue-pdf": "^3.7.3",
"@progress/kendo-vue-popup": "^3.7.3",
"@progress/kendo-vue-progressbars": "^3.7.3"

组件的基本引入

<template>
  <Editor
    :tools="tools"
    default-edit-mode="div"
    resizable
    :default-content="content"
    :content-style="{ height: '320px' }"
    @change="onChange"
  />
</template>
<script setup lang="ts">
import { Editor } from '@progress/kendo-vue-editor'
import '@progress/kendo-theme-default/dist/all.css'

interface IEditorProps {
  modelValue: string
}

const tools = [
  ['Undo', 'Redo'],
  ['FormatBlock', 'AlignLeft', 'AlignCenter', 'AlignRight'],
  ['Bold', 'Italic', 'Underline', 'Strikethrough', 'BackColor', 'ForeColor'],
  ['FontSize', 'FontName'],
  ['Subscript', 'Superscript'],
  ['Link', 'Unlink', 'InsertImage'],
  ['OrderedList', 'UnorderedList', 'Indent', 'Outdent'],
  [
    'InsertTable',
    'MergeCells',
    'SplitCell',
    'AddRowBefore',
    'AddRowAfter',
    'AddColumnBefore',
    'AddColumnAfter',
    'DeleteRow',
    'DeleteColumn',
    'DeleteTable'
  ],
  ['ViewHtml']
]

const $props = defineProps<IEditorProps>()
const $emit = defineEmits(['update:modelValue'])

const content = ref('')
</script>

这样就可以得到1个基本的可以使用的富文本组件了

kendo-vue-editor使用及踩坑记录

存在的问题

但是自测过程发现组件本身存在一些小bug,项目进度又不能耽误,自行解决这些bug,后面再提pr

  • view html的弹窗右上角的×不触发弹窗关闭
    • 解决方案:手动监听点击事件关闭
const onBtnClick = (e: any) => {
  const classList = [...(e.target?.classList || [])]
  if (classList.includes('k-i-x')) {
    const cancelBtn = document.querySelector('.k-dialog-wrapper .k-actions-stretched .k-button-solid-base') as any
    cancelBtn?.click()
  }
}
onMounted(() => {
  document.addEventListener('click', onBtnClick, true)
})

onUnmounted(() => {
  document.removeEventListener('click', onBtnClick)
})
  • 字体颜色和背景颜色tool在光标位置变化时跟着变化,始终是上次选择的颜色,并且无法支持随意选择颜色,只能选择预设的颜色
    • 解决方案:用kendo自带的colorPicker实现自定义的colorPicker组件
const BgColorButton = {
  render: () =>
    h(ColorPicker, {
      ...EditorToolsSettings.backColor.colorPickerProps,
      icon: 'background',
      view: 'combo',
      showPreview: false,
      showClearButton: false,
      value: bgcolorVal.value,
      onChange: ({ value }: any) => {
        setColor('background-color', 'BackColor', value)
      }
    })
}

const ForeColorButton = {
  render: () =>
    h(ColorPicker, {
      ...EditorToolsSettings.foreColor.colorPickerProps,
      icon: 'foreground-color',
      view: 'combo',
      showPreview: false,
      showClearButton: false,
      value: colorVal.value,
      onChange: ({ value }: any) => {
        setColor('color', 'ForeColor', value)
      }
    })
}

const tools = [
   ...
  ['Bold', 'Italic', 'Underline', 'Strikethrough', BgColorButton, ForeColorButton],
   ...
]

const colorVal = ref('')
const bgcolorVal = ref('')

同时需要在每次光标发生变化做一些处理,这里监听execute事件,再对color做下处理

<template>
  <Editor ref="editorRef" :tools="tools" default-edit-mode="div" resizable :default-content="content"
    :content-style="{ height: '320px' }" :extend-view="extendView" @change="onChange" @execute="onExecute" />
</template>
<script setup lang="ts">
    ...
    const onExecute = (event: any) => {
      checkColor()
    }
    const checkColor = () => {
      // 内部state是异步的,为了获取到最新的state,这里也异步获取下state
      window.setTimeout(() => {
        const editorView = editorRef.value?.view
        const colorList = getInlineStyles(editorView?.state, {
          name: 'color',
          value: /^.+$/
        })
        const bgcolorList = getInlineStyles(editorView?.state, {
          name: 'background-color',
          value: /^.+$/
        })
        // 仅在被选中的文字都使用同1个颜色时才设置colorPicker的回显,否则重置颜色
        colorVal.value = colorList.length === 1 ? colorList[0] : ''
        bgcolorVal.value = bgcolorList.length === 1 ? bgcolorList[0] : ''
      }, 0)
    }
...
</script>

顺便一说,editor自带的color tool只能选择预置的颜色 kendo-vue-editor使用及踩坑记录

要使用这种自定义颜色的话需要自定义color tools,如上文,即colorPicker组件 kendo-vue-editor使用及踩坑记录 但这个组件仍有它自带的bug,点击cancel按钮不会关闭这个弹窗,仍是暂时手动监听事件来解决

const onBtnClick = (e: any) => {
  if (e.target?.closest('.k-coloreditor-cancel')) {
    const popupContainer = e.target?.closest('.k-animation-container')
    popupContainer && (popupContainer.style.display = 'none')
  }
}

onMounted(() => {
  document.addEventListener('click', onBtnClick, true)
})

onUnmounted(() => {
  document.removeEventListener('click', onBtnClick)
})

扩展

自定义图片上传

关于自定义图片上传可能需要回显一些值,这里可以使用extendView

<template>
  <Editor ref="editorRef" :tools="tools" default-edit-mode="div" resizable :default-content="content"
    :content-style="{ height: '320px' }" :extend-view="extendView" @change="onChange" @execute="onExecute" />
</template>
<script setup lang="ts">
    ...
    const extendView = (event: any) => {
      const { viewProps } = event;
      const { plugins, schema } = viewProps.state;
      const image = { ...schema.spec.nodes.get("image") };
      image.attrs['data-id'] = { default: '' }
      image.attrs['data-style'] = { default: '' }
      image.attrs['data-value'] = { default: '' }
      image.attrs['data-filename'] = { default: '' }
      image.attrs['data-url'] = { default: '' }
      const nodes = schema.spec.nodes.update("image", image);
      const marks = schema.spec.marks
      const mySchema = new Schema({
        nodes,
        marks
      })
      const doc = EditorUtils.createDocument(mySchema, content.value);
      return new EditorView(
        {
          mount: event.dom,
        },
        {
          ...event.viewProps,
          state: EditorState.create({
            doc: doc,
            plugins,
          }),
        }
      )
    }
    // 插入图片
    const onSaveImage = (setting: IImageSetting) => {
      if (!setting.url && !setting.media) {
        return
      }
      const editorView = editorRef.value?.view
      if (editorView) {
        const node = editorView?.state?.schema?.nodes.image?.createAndFill({
          src: setting.url || setting.media?.value,
          style: getStyleAttribute(setting.style),
          alt: setting.alt || '',
          'data-id': setting.media?.id || '',
          'data-style': setting.style || '',
          'data-value': setting.media?.value || '',
          'data-filename': setting.media?.fileName || '',
          'data-url': setting.url || '',
        })
        const tr = editorView?.state.tr.replaceSelectionWith(node)
        editorView?.dispatch(tr.setMeta('commandName', 'InsertImage'))
      }
    }
    const onExecute = (event: any) => {
      imageSetting.value = '{}'
      const { doc, selection } = event.transaction;
      // 图片的话selection.empty一定为false
      if (!selection.empty) {
        const node = doc.cut(selection.from, selection.to);
        const selectionHtml = EditorUtils.getHtml({
          doc: node,
          schema: node.type.schema,
        });
        checkImg(selectionHtml)
      }
    }
    // 判断当前光标所在位置是图片
    const checkImg = (htmlString: string) => {
      if (!htmlString) {
        return
      }
      const isImg = /<img.*?src=[\"|\']?(.*?)[\"|\']?\s.*?>/i.test(htmlString)
      if (!isImg) {
        return
      }
      try {
        const domParser = new DOMParser();
        const dom = domParser.parseFromString(htmlString, 'text/html')
        const imgNode = dom.querySelector('img')
        if (imgNode) {
          const style = imgNode.getAttribute('data-style')
          const id = imgNode.getAttribute('data-id')
          const value = imgNode.getAttribute('data-value')
          const filename = imgNode.getAttribute('data-filename')
          const url = imgNode.getAttribute('data-url')
          const alt = imgNode.getAttribute('alt')
          imageSetting.value = JSON.stringify({
            style,
            url,
            alt,
            media: {
              id,
              value,
              filename
            }
          })
        }
      } catch (error) {
        console.log('domParser error', error)
      }
    }
...
</script>

建议

至此一些奇怪bug算是解决掉了~ 如果只想使用富文本的话,我觉得其实有其他更好的选择,比如tiptaptinymce(12.5k stars),以及近几年国产开源的wangEditor)。

转载自:https://juejin.cn/post/7194676713811345468
评论
请登录