市面上有诸多富文本编辑器,工作中有用到wangeditor/editor和tinymce
对比两款富文本编辑器
先说结论 最后使用的是tinymce
原因: wangeditor不支持这种有序和无序列表嵌套情况,当这种情况出现的时候,会丢数据 !!!
而
tinymce编辑器 支持这种情况
wangeditor配置方法
文档地址: 快速开始 | wangEditor
import { Editor, Toolbar } from '@wangeditor/editor-for-vue'
import { IDomEditor, IEditorConfig } from '@wangeditor/editor'
// 编辑器实例,必须用 shallowRef
const editorRef = shallowRef<IDomEditor>()
const props = defineProps({
editorId: propTypes.string.def('wangeEditor-1'),
height: propTypes.oneOfType([Number, String]).def('500px'),
editorConfig: {
type: Object as PropType<IEditorConfig>,
default: () => undefined
},
modelValue: propTypes.string.def('')
})
// 编辑器配置
const editorConfig = computed((): IEditorConfig => {
return Object.assign(
{
readOnly: false,
customAlert: (s: string, t: string) => {
switch (t) {
case 'success':
ElMessage.success(s)
break
case 'info':
ElMessage.info(s)
break
case 'warning':
ElMessage.warning(s)
break
case 'error':
ElMessage.error(s)
break
default:
ElMessage.info(s)
break
}
},
autoFocus: false,
scroll: true,
uploadImgShowBase64: true
},
props.editorConfig || {}
)
})
const handleCreated = (editor: IDomEditor) => {
editorRef.value = editor
valueHtml.value = props.modelValue
}
// 组件销毁时,及时销毁编辑器
onBeforeUnmount(() => {
const editor = unref(editorRef.value)
// 销毁,并移除 editor
editor?.destroy()
})
const getEditorRef = async (): Promise<IDomEditor> => {
await nextTick()
return unref(editorRef.value) as IDomEditor
}
defineExpose({
getEditorRef
})
<Toolbar
:editor="editorRef"
:editorId="editorId"
/>
<!-- 编辑器 -->
<Editor
v-model="valueHtml"
:editorId="editorId"
:defaultConfig="editorConfig"
:style="editorStyle"
@on-created="handleCreated"
/>
tinymce配置方法
文档地址:TinyMCE 7 Documentation | TinyMCE Documentation
github 从github上下载需要的版本然后解压放到自己的项目的public结构下,其中包含语言国际化
//这些必须都导入到项目中
import tinymce from 'tinymce/tinymce'
import 'tinymce/themes/mobile/theme'
import 'tinymce/themes/silver/theme'
import 'tinymce/icons/default/icons'
import 'tinymce/plugins/advlist'
import 'tinymce/plugins/anchor'
import 'tinymce/plugins/autolink'
import 'tinymce/plugins/autoresize'
import 'tinymce/plugins/autosave'
import 'tinymce/plugins/charmap'
import 'tinymce/plugins/code'
import 'tinymce/plugins/codesample'
import 'tinymce/plugins/directionality'
import 'tinymce/plugins/emoticons'
import 'tinymce/plugins/fullpage'
import 'tinymce/plugins/fullscreen'
import 'tinymce/plugins/help'
import 'tinymce/plugins/hr'
import 'tinymce/plugins/image'
import 'tinymce/plugins/importcss'
import 'tinymce/plugins/insertdatetime'
import 'tinymce/plugins/link'
import 'tinymce/plugins/lists'
import 'tinymce/plugins/media'
import 'tinymce/plugins/nonbreaking'
import 'tinymce/plugins/pagebreak'
import 'tinymce/plugins/paste'
import 'tinymce/plugins/preview'
import 'tinymce/plugins/print'
import 'tinymce/plugins/quickbars'
import 'tinymce/plugins/save'
import 'tinymce/plugins/searchreplace'
import 'tinymce/plugins/tabfocus'
import 'tinymce/plugins/table'
import 'tinymce/plugins/template'
import 'tinymce/plugins/textcolor'
import 'tinymce/plugins/textpattern'
import 'tinymce/plugins/toc'
import 'tinymce/plugins/visualblocks'
import 'tinymce/plugins/visualchars'
import 'tinymce/plugins/wordcount'
//初始化方法
onMounted(async () => {
loading.value = true
await nextTick()
tinymce.init({
selector: '#rich-text-editor',//容器
width: '100%',
height: height,
skin_url: '/tinymce/skins/ui/oxide', // Ensure the correct path
content_css: '/tinymce/skins/content/default/content.css',
emoticons_database_url: '/tinymce/emoticons/js/emojis.js',
min_height: min_height,
max_height: max_height,
branding: false, // Hide technical support in the bottom right corner
elementpath: true, // Disable the status bar at the bottom of the editor
menubar: false,
statusbar: false, // Hide the status bar at the bottom of the editor
paste_data_images: true, // Allow pasting images
resize: false, // resize editor tool
/* enable automatic uploads of images represented by blob or data URIs*/
automatic_uploads: true,
// URL of our upload handler (for more details check: https://www.tiny.cloud/docs/configure/file-image-upload/#images_upload_url)
images_upload_url: '/api/project/v1/uploadImage',
// here we add custom filepicker only to Image dialog
file_picker_types: 'image',
/* and here's our custom image picker*/
file_picker_callback: async (cb) => {
const input = document.createElement('input')
input.setAttribute('type', 'file')
input.setAttribute('accept', 'image/*')
input.addEventListener('change', async function (event: any) {
const file = event.target!.files[0]
const formData = new FormData()
formData.append('file', file)
try {
const data = (await uploadImage(formData)) as ApiResponseData
cb(data.message, { title: file.name })
} catch (e) {
console.error(e)
}
})
input.click()
},
//根据自己需求添加 依赖 toolbar功能 选择需要加载的插件
plugins:
'print preview searchreplace autolink directionality visualblocks visualchars fullscreen link image media template code codesample table charmap hr pagebreak nonbreaking anchor insertdatetime advlist lists wordcount textpattern help emoticons autosave',
toolbar: `formatselect bold italic underline numlist bullist`,
contextmenu: false,
// content_style: 'body { font-family:Helvetica,Arial,sans-serif; font-size:32rpx }',
// extended_valid_elements: 'head,style,body,head[*],style[*],body[*],[*]',
// cleanup: false,
valid_children: '+div[style]'
})
const content = await processHtmlString(summaryData.value)
const repairedContent = content + captureHeaderInner(content)
loading.value = false
tinymce.get('rich-text-editor').setContent(repairedContent)
})
function captureHeaderInner(htmlString: string) {
const parser = new DOMParser()
const doc = parser.parseFromString(htmlString, 'text/html')
return doc.head.innerHTML
}
function getContent() {
return {
jobHtml: tinymce.get('rich-text-editor').getContent(),
templateDescription: tinymce
.get('rich-text-editor')
.getContent({ format: 'text' })
.replace(/[\s\n]/g, '')
.substring(0, 50)
}
}
function processHtmlString(htmlString: string) {
if (!htmlString) return Promise.resolve('')
const parser = new DOMParser()
const doc = parser.parseFromString(htmlString, 'text/html')
const uploadPromises: any[] = []
return Promise.all(uploadPromises).then(() => {
return doc.documentElement.innerHTML
})
}
<div id="rich-text-editor" style="overflow-y: auto"></div>