针对于vue3,如果你也是遇到了这些问题,直接使用下面的方法可以完美解决
问题1:Cannot read properties of null (reading 'emitsOptions')
问题2:使用 wangeditor 时报错 Uncaught (in promise) TypeError: Cannot read properties of null (reading 'emitsOptions') 和 Uncaught (in promise) Error: Cannot find a descendant at path [2] in node: {"children":[{"type":"paragraph","children":
问题3:自定义按钮报错,这种是重复注册的问题
Duplicated key 'customInsertImage' in menu items


进入正题直接可以使用下面的代码和方法,
vue3
"@wangeditor/editor": "^5.1.23",
"@wangeditor/editor-for-vue": "^5.1.12",
首先自建个组件,wangEditorCom.vue,可直接使用下面代码
<template>
<div>
<!-- 工具栏 -->
<Toolbar
:editor="editorRef"
:defaultConfig="toolbarConfig"
:mode="'default'"
style="border: 1px solid #ccc"
/>
<!-- 编辑器 -->
<Editor
v-model="valueHtml"
:defaultConfig="editorConfig"
:mode="'default'"
style="height: 300px; border: 1px solid #ccc; overflow-y: auto;"
@onCreated="handleCreated"
/>
<!--这个是弹窗,可以改成自己的 -->
<!-- <ResourceSelector
v-model="resourceStore.showResourceDialog"
title="从资源库选择图片"
:multiple="false"
:accept="['image']"
ref="resourceSelectorRef"
@confirm="handleResourceConfirm"
/>-->
</div>
</template>
<script setup>
import '@wangeditor/editor/dist/css/style.css'
import { ref, onBeforeUnmount } from 'vue'
import { Editor, Toolbar } from '@wangeditor/editor-for-vue'
//弹窗
//import ResourceSelector from '../components/ResourceSelector.vue
//这里使用的是pinia,可以改成自己的
import { useResourceStore } from '../stores/resource'
const resourceStore = useResourceStore()
// end 这里使用的是pinia
const resourceSelectorRef = ref(null)
// 2. 编辑器数据
const valueHtml = ref('<p>这里是</p>')
const editorRef = ref(null)
const toolbarConfig = {
insertKeys: {
index: 0, // 插到最前
keys: ['|', 'customInsertImage']
}
}
const editorConfig = {
placeholder: '请输入内容...',
MENU_CONF: {
insertImage: {
onInsertedImage(imageNode) {
console.log('已插入网络图片', imageNode)
},
checkImage(src) {
return /^https?:\/\//.test(src) // 只允许 http/https
}
},
uploadImage: {
//图片服务地址,可以改成自己的
server: '/api/upload',
fieldName: 'file',
maxFileSize: 5 * 1024 * 1024,
allowedFileTypes: ['image/*']
}
}
}
const handleCreated = (editor) => {
//一定要这样Object.seal加这个,不然会报错
editorRef.value = Object.seal(editor)
}
//这是弹窗的回调事件,resource,
const handleResourceConfirm = (resource) => {
if (resource) {
//插入图片,全局的一个editor,是在main里面注册的
if (window.editor) {
window.editor.dangerouslyInsertHtml(`<img src="${resource[0].url}" alt="" />`)
}
}
}
onBeforeUnmount(() => {
//一定要销毁
if (editorRef.value) {
editorRef.value.destroy()
}
})
//这里是父页面给子页面赋值用的
defineExpose({
valueHtml,
})
</script>
然后建立一个editorMenus.js,自定义toobar按钮,不需要自定义按钮的话,直接跳到最后使用
// editorMenus.js
import { Boot } from '@wangeditor/editor'
import { useResourceStore } from '../stores/resource'
// 自定义菜单配置
export const InsertImgMenuConf = {
//key值要和,wangEditCom.vue里面的 toolbarConfig配置的对应
key: 'customInsertImage',
factory() {
return {
title: '插入图片',
iconSvg:
'<svg viewBox="0 0 1024 1024" width="16" height="16"><path d="M896 160H128c-35.2 0-64 28.8-64 64v576c0 35.2 28.8 64 64 64h768c35.2 0 64-28.8 64-64V224c0-35.2-28.8-64-64-64zM128 800V224h768v288l-160-160-256 256-128-128-224 224z"/></svg>',
tag: 'button',
getValue() { return '' },
isActive() { return false },
isDisabled(editor) { return editor.isDisabled() },
exec(editor) {
if (this.isDisabled(editor)) return
//全局保存editor实例
window.editor = editor
//使用pinia打开资源库弹窗
const resourceStore = useResourceStore()
resourceStore.showResourceDialog = true
},
}
},
}
// 全局只注册一次
if (!window.__hasRegisteredCustomInsertImage) {
Boot.registerMenu(InsertImgMenuConf)
window.__hasRegisteredCustomInsertImage = true
}
再然后pinia,
//这里是弹窗关闭和显示
import { defineStore } from 'pinia'
export const useResourceStore = defineStore('resource', {
state: () => ({
showResourceDialog: false
})
})
再然后main.js
直接引用 自己放置的目录 import ../utils/editorMenus.js
最后使用如下,因为我的是在form表单里面,下面例子可以直接使用
<template>
<el-button type="success" @click="handleAdd">新增动态</el-button>
<el-table :data="newsList" style="width: 100%">
<el-table-column type="index" width="80" />
<el-table-column prop="title" label="标题" width="80" />
<el-table-column label="操作" width="200">
<template #default="scope">
<el-button size="small" type="primary" @click="handleEdit(scope.row)">编辑</el-button>
</template>
</el-table-column>
</el-table>
<!--destroy-on-close 这个一定要加,这是弹窗销毁事件,不加会报错的-->
<el-dialog
v-model="dialogVisible"
:title="dialogTitle"
width="60%"
@close="resetForm"
destroy-on-close
>
<el-form :model="form" ref="formRef" label-width="100px">
<el-form-item label="标题" prop="title">
<el-input v-model="form.title" placeholder="请输入标题" />
</el-form-item>
<el-form-item label="内容" prop="description">
<wangEditcom ref="wangEditcomRef" />
</el-form-item>
<template #footer>
<span class="dialog-footer">
<el-button @click="dialogVisible = false">取消</el-button>
<el-button type="primary" @click="handleSubmit">确定</el-button>
</span>
</template>
</el-dialog>
</<template>
<script setup>
import { ref, reactive, onMounted, onBeforeUnmount, shallowRef, nextTick } from 'vue'
import wangEditcom from '../views/wangEditcom.vue'
const wangEditcomRef = ref(null)
const formRef= ref(null)
const newsList=ref({
[id:1,title:"晚上嗯"]
})
const form=ref(null)
// 对话框相关
const dialogVisible = ref(false)
const dialogTitle = ref('新增')
const handleAdd = () => {
dialogTitle.value = '新增'
dialogVisible.value = true
}
const handleEdit = (row) => {
dialogTitle.value = '编辑'
Object.assign(form, row)
dialogVisible.value = true
nextTick(() => {
if (wangEditcomRef.value) {
wangEditcomRef.value.valueHtml = row.description || '<p>请输入内容...</p>'
}
})
}
const handleSubmit=()=>{
newsList.push(...form.value)
}
</script>