quill 富文本编辑器实现 cocos creator richText

内容分享7天前发布
1 0 0

文章目录

quill 富文本编辑器实现 cocos creator richText实现版本展示效果入库数据示例cocos creator richText实现简化工具栏quill富文本转cocos creator richText回显cocos creator richText 转quill富文本完整代码组件使用

quill 富文本编辑器实现 cocos creator richText

最近一直在使用若依的vue分离版本后台管理,由于项目中需要展示文本支持cocos creator richText ,导致现有的quill富文本不满足需求。经思考可用两种方案:一、自己实现 二、利用quill,quill工具栏减少。经过思考还是决定用quill,于是便有了下面这篇记录。

实现版本

vue:2.6.11
element-ui: 2.15.6
vue-quill-editor: ^3.0.6

展示效果

quill 富文本编辑器实现 cocos creator richText

入库数据示例

<color=#e60000><size=36>亲爱的国王大人:
您上赛季有没有使用的竞技券,已经回收转为对等<color=#9933ff><size=28>12345678

cocos creator richText

quill 富文本编辑器实现 cocos creator richText

实现

简化工具栏


 editorOption: {
        modules: {
          toolbar: [
            ['bold', 'italic'],
            [{ 'color': [] }],
            [{ 'size': ['small', false, 'large', 'huge'] }],
            ['clean'] // 清除字体样式
          ]
        },
        placeholder: '请输入内容...',
        theme: 'snow'
      }

quill富文本转cocos creator richText


	deltaToCocosRichText(delta) {
      let result = ''
      if (!delta.ops) return ''

      delta.ops.forEach(op => {
        const text = op.insert || ''
        if (typeof text !== 'string') {
          // Quill 中插入图片/视频等非文本内容(通常为 object)
          // Cocos RichText 仅支持 <img src="xxx">,且需提前注册图集
          // 此处可忽略或特殊处理
          console.warn('Non-text insert ignored:', text)
          return
        }

        const attrs = op.attributes || {}

        // 开始标签
        let openTags = ''
        let closeTags = ''

        // 处理颜色
        if (attrs.color) {
          openTags += `<color=${attrs.color}>`
          closeTags = `</color>${closeTags}`
        }

        // 处理背景色(Cocos 不支持,可忽略或用其他方式模拟)
        // if (attrs.background) { ... }

        // italic
        if (attrs.italic) {
          openTags += `<i>`
          closeTags = `</i>${closeTags}`
        }
        // 处理粗体(Cocos 无 <b>,可用加大字号或忽略)
        if (attrs.bold) {
          openTags += `<b>`
          closeTags = `</b>${closeTags}`
        }

        // 处理斜体(Cocos 不支持,通常忽略)
        // if (attrs.italic) { ... }

        // 处理字号
        if (attrs.size) {
          let fontSize = 20
          if (attrs.size === 'small') fontSize = 16
          else if (attrs.size === 'large') fontSize = 28
          else if (attrs.size === 'huge') fontSize = 36
          // 注意:false 或 undefined 表示 normal(20)
          openTags += `<size=${fontSize}>`
          closeTags = `</size>${closeTags}`
        }
        // 拼接
        result += openTags + this.escapeHtml(text) + closeTags
      })

      return result
    }

回显cocos creator richText 转quill富文本


cocosRichTextToDelta(cocosText) {
      if (!cocosText || typeof cocosText !== 'string') {
        return { ops: [{ insert: '
' }] }
      }

      const ops = []
      let currentText = ''
      const currentAttributes = {}

      // 解析标签栈
      const tagStack = []
      let i = 0

      while (i < cocosText.length) {
        if (cocosText[i] === '<') {
          // 处理当前累积的文本
          if (currentText) {
            ops.push({
              insert: currentText,
              attributes: { ...currentAttributes }
            })
            currentText = ''
          }

          const tagEnd = cocosText.indexOf('>', i)
          if (tagEnd === -1) break

          const fullTag = cocosText.substring(i, tagEnd + 1)
          const isClosingTag = fullTag.startsWith('</')
          const tagNameMatch = fullTag.match(/</*(w+)/)

          if (tagNameMatch) {
            const tagName = tagNameMatch[1]

            if (!isClosingTag) {
              // 处理开始标签
              if (tagName === 'color') {
                const colorMatch = fullTag.match(/color=([^>]+)/)
                if (colorMatch) {
                  currentAttributes.color = colorMatch[1].replace(/['"]/g, '')
                }
              } else if (tagName === 'size') {
                const sizeMatch = fullTag.match(/size=([^>]+)/)
                if (sizeMatch) {
                  const sizeValue = parseInt(sizeMatch[1].replace(/['"]/g, '')) || 20
                  let sizeName = false // normal
                  if (sizeValue <= 16) sizeName = 'small'
                  else if (sizeValue <= 28) sizeName = 'large'
                  else if (sizeValue > 28) sizeName = 'huge'
                  currentAttributes.size = sizeName
                }
              } else if (tagName === 'b') {
                currentAttributes.bold = true
              } else if (tagName === 'i') {
                currentAttributes.italic = true
              }
              tagStack.push(tagName)
            } else {
              // 处理结束标签
              while (tagStack.length > 0) {
                const poppedTag = tagStack.pop()
                if (poppedTag === tagName) break
              }

              // 清除对应属性
              if (tagName === 'color') {
                delete currentAttributes.color
              } else if (tagName === 'size') {
                delete currentAttributes.size
              } else if (tagName === 'b') {
                delete currentAttributes.bold
              } else if (tagName === 'i') {
                delete currentAttributes.italic
              }
            }
          }

          i = tagEnd + 1
        } else {
          // 累积文本内容
          currentText += cocosText[i]
          i++
        }
      }

      // 处理最后剩余的文本
      if (currentText) {
        ops.push({
          insert: currentText,
          attributes: { ...currentAttributes }
        })
      }

      return { ops }
    }

完整代码


<template>
  <div>
    <quill-editor
      ref="quillEditor"
      :value="content"
      :options="editorOption"
      @input="onEditorInput"
    />
  </div>
</template>

<script>
import { quillEditor } from 'vue-quill-editor'
import 'quill/dist/quill.core.css' // import styles
import 'quill/dist/quill.snow.css' // for snow theme
import 'quill/dist/quill.bubble.css' // for bubble theme

export default {
  name: 'CocosRichTextEditor',
  components: { quillEditor },
  props: {
    value: {
      type: String,
      default: ''
    }
  },
  data() {
    return {
      content: '',
      editorOption: {
        modules: {
          toolbar: [
            ['bold', 'italic'],
            [{ 'color': [] }],
            [{ 'size': ['small', false, 'large', 'huge'] }],
            ['clean'] // 清除字体样式
          ]
        },
        placeholder: '请输入内容...',
        theme: 'snow'
      }
    }
  },
  watch: {
    value: {
      immediate: true,
      handler(newVal) {
        // 等待 DOM 和 Quill 实例就绪
        this.$nextTick(() => {
          const quill = this.$refs.quillEditor?.quill
          if (quill) {
            const delta = this.cocosRichTextToDelta(newVal || '')
            quill.setContents(delta, 'silent') // silent 避免触发 input
          }
        })
      }
    }
  },
  mounted() {
    // 双重保险:确保初始内容加载
    this.$nextTick(() => {
      const quill = this.$refs.quillEditor?.quill
      if (quill && quill.getLength() <= 1) {
        const delta = this.cocosRichTextToDelta(this.value || '')
        quill.setContents(delta, 'silent')
      }
    })
  },
  methods: {
    onEditorInput(html) {
      // 注意:这里 html 是 HTML 字符串,但我们不要它!
      // 必须从 Quill 实例取 Delta
      const quill = this.$refs.quillEditor.quill
      // 保存当前光标位置
      const range = quill.getSelection()

      const delta = quill.getContents()
      const cocosStr = this.deltaToCocosRichText(delta)
      this.$emit('input', cocosStr)

      // 恢复光标位置
      if (range) {
        setTimeout(() => {
          quill.setSelection(range.index, range.length)
        }, 0)
      }
    },
    deltaToCocosRichText(delta) {
      let result = ''
      if (!delta.ops) return ''

      delta.ops.forEach(op => {
        const text = op.insert || ''
        if (typeof text !== 'string') {
          // Quill 中插入图片/视频等非文本内容(通常为 object)
          // Cocos RichText 仅支持 <img src="xxx">,且需提前注册图集
          // 此处可忽略或特殊处理
          console.warn('Non-text insert ignored:', text)
          return
        }

        const attrs = op.attributes || {}

        // 开始标签
        let openTags = ''
        let closeTags = ''

        // 处理颜色
        if (attrs.color) {
          openTags += `<color=${attrs.color}>`
          closeTags = `</color>${closeTags}`
        }

        // 处理背景色(Cocos 不支持,可忽略或用其他方式模拟)
        // if (attrs.background) { ... }

        // italic
        if (attrs.italic) {
          openTags += `<i>`
          closeTags = `</i>${closeTags}`
        }
        // 处理粗体(Cocos 无 <b>,可用加大字号或忽略)
        if (attrs.bold) {
          openTags += `<b>`
          closeTags = `</b>${closeTags}`
        }

        // 处理斜体(Cocos 不支持,通常忽略)
        // if (attrs.italic) { ... }

        // 处理字号
        if (attrs.size) {
          let fontSize = 20
          if (attrs.size === 'small') fontSize = 16
          else if (attrs.size === 'large') fontSize = 28
          else if (attrs.size === 'huge') fontSize = 36
          // 注意:false 或 undefined 表示 normal(20)
          openTags += `<size=${fontSize}>`
          closeTags = `</size>${closeTags}`
        }
        // 拼接
        result += openTags + this.escapeHtml(text) + closeTags
      })

      return result
    },

    // 转义特殊字符,防止破坏标签结构
    escapeHtml(text) {
      const map = {
        '&': '&amp;',
        '<': '&lt;',
        '>': '&gt;',
        '"': '&quot;',
        "'": '''
      }
      return text.replace(/[&<>"']/g, (s) => map[s])
    },
    // 添加 Cocos RichText 转 Delta 方法
    cocosRichTextToDelta(cocosText) {
      if (!cocosText || typeof cocosText !== 'string') {
        return { ops: [{ insert: '
' }] }
      }

      const ops = []
      let currentText = ''
      const currentAttributes = {}

      // 解析标签栈
      const tagStack = []
      let i = 0

      while (i < cocosText.length) {
        if (cocosText[i] === '<') {
          // 处理当前累积的文本
          if (currentText) {
            ops.push({
              insert: currentText,
              attributes: { ...currentAttributes }
            })
            currentText = ''
          }

          const tagEnd = cocosText.indexOf('>', i)
          if (tagEnd === -1) break

          const fullTag = cocosText.substring(i, tagEnd + 1)
          const isClosingTag = fullTag.startsWith('</')
          const tagNameMatch = fullTag.match(/</*(w+)/)

          if (tagNameMatch) {
            const tagName = tagNameMatch[1]

            if (!isClosingTag) {
              // 处理开始标签
              if (tagName === 'color') {
                const colorMatch = fullTag.match(/color=([^>]+)/)
                if (colorMatch) {
                  currentAttributes.color = colorMatch[1].replace(/['"]/g, '')
                }
              } else if (tagName === 'size') {
                const sizeMatch = fullTag.match(/size=([^>]+)/)
                if (sizeMatch) {
                  const sizeValue = parseInt(sizeMatch[1].replace(/['"]/g, '')) || 20
                  let sizeName = false // normal
                  if (sizeValue <= 16) sizeName = 'small'
                  else if (sizeValue <= 28) sizeName = 'large'
                  else if (sizeValue > 28) sizeName = 'huge'
                  currentAttributes.size = sizeName
                }
              } else if (tagName === 'b') {
                currentAttributes.bold = true
              } else if (tagName === 'i') {
                currentAttributes.italic = true
              }
              tagStack.push(tagName)
            } else {
              // 处理结束标签
              while (tagStack.length > 0) {
                const poppedTag = tagStack.pop()
                if (poppedTag === tagName) break
              }

              // 清除对应属性
              if (tagName === 'color') {
                delete currentAttributes.color
              } else if (tagName === 'size') {
                delete currentAttributes.size
              } else if (tagName === 'b') {
                delete currentAttributes.bold
              } else if (tagName === 'i') {
                delete currentAttributes.italic
              }
            }
          }

          i = tagEnd + 1
        } else {
          // 累积文本内容
          currentText += cocosText[i]
          i++
        }
      }

      // 处理最后剩余的文本
      if (currentText) {
        ops.push({
          insert: currentText,
          attributes: { ...currentAttributes }
        })
      }

      return { ops }
    }
  }
}
</script>


组件使用


 <el-form-item label="内容" prop="content" class="quill-wrapper">
    <cocos-rich-text-editor v-model="form.content" />
 </el-form-item>
© 版权声明

相关文章

暂无评论

您必须登录才能参与评论!
立即登录
none
暂无评论...