文章目录
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
展示效果

入库数据示例
<color=#e60000><size=36>亲爱的国王大人:
您上赛季有没有使用的竞技券,已经回收转为对等<color=#9933ff><size=28>12345678
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 = {
'&': '&',
'<': '<',
'>': '>',
'"': '"',
"'": '''
}
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>
© 版权声明
文章版权归作者所有,未经允许请勿转载。
相关文章
暂无评论...