
image.png
wangEditor官網(wǎng) 開源 Web 富文本編輯器,開箱即用,配置簡單
要實現(xiàn)一個完整的富文本編輯器功能,你可能還需要以下功能:
- 內(nèi)容處理 - 獲取內(nèi)容,設(shè)置內(nèi)容,展示內(nèi)容
- 工具欄配置 - 插入新菜單,屏蔽某個菜單等
- 編輯器配置 - 兼聽各個生命周期,自定義粘貼
- 菜單配置 - 配置顏色、字體、字號、鏈接校驗、上傳圖片、上傳視頻等
- 編輯器 API - 控制編輯器內(nèi)容和選區(qū)
- 擴(kuò)展新功能 - 擴(kuò)展菜單、元素、插件等
Vue2
安裝
npm install @wangeditor/editor --save
# 或者 yarn add @wangeditor/editor
npm install @wangeditor/editor-for-vue --save
# 或者 yarn add @wangeditor/editor-for-vue
使用
<template>
<div style="border: 1px solid #ccc;">
<Toolbar
style="border-bottom: 1px solid #ccc"
:editor="editor"
:defaultConfig="toolbarConfig"
:mode="mode"
/>
<Editor
style="height: 500px; overflow-y: hidden;"
v-model="html"
:defaultConfig="editorConfig"
:mode="mode"
@onCreated="onCreated"
/>
</div>
</template>
<script>
import Vue from 'vue'
import { Editor, Toolbar } from '@wangeditor/editor-for-vue'
export default Vue.extend({
components: { Editor, Toolbar },
data() {
return {
editor: null,
html: '<p>hello</p>',
toolbarConfig: { },
editorConfig: { placeholder: '請輸入內(nèi)容...' },
mode: 'default', // or 'simple'
}
},
methods: {
onCreated(editor) {
this.editor = Object.seal(editor) // 一定要用 Object.seal() ,否則會報錯
},
},
mounted() {
// 模擬 ajax 請求,異步渲染編輯器
setTimeout(() => {
this.html = '<p>模擬 Ajax 異步設(shè)置內(nèi)容 HTML</p>'
}, 1500)
},
beforeDestroy() {
const editor = this.editor
if (editor == null) return
editor.destroy() // 組件銷毀時,及時銷毀編輯器
}
})
</script>
TIP:
賦值 this.editor 時要用 Object.seal()
組件銷毀時,要及時銷毀編輯器
wangEditor富文本編輯框中,有上傳圖片和上傳視頻的功能,但是沒有上傳音頻的功能,所以就需要使用wangEditor的自動以擴(kuò)展新功能來實現(xiàn)上傳音頻的功能
自定義擴(kuò)展新功能
文件目錄為:

image.png
1. 注冊新菜單
import { IDomEditor, IDropPanelMenu } from "@wangeditor/editor";
// class MyDropPanelMenu implements IDropPanelMenu {
// TS 語法
export default class AudioMenu {
// JS 語法
constructor() {
this.title = "上傳音頻";
this.iconSvg =
'<svg class="icon" width="200px" height="200.00px" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg"><path fill="#FF7F7F" d="M849.408 6.656L411.648 140.8c-53.248 15.36-95.744 70.656-95.744 123.392v461.312S284.16 704 213.504 714.24C109.568 729.088 25.6 808.448 25.6 891.904s83.968 134.656 187.904 119.808c103.936-14.848 179.712-91.648 179.712-175.104v-445.44c0-36.864 44.544-52.736 44.544-52.736l387.072-121.344s43.008-14.336 43.008 25.088v367.616s-39.424-22.528-110.08-14.336c-103.936 12.8-187.904 90.624-187.904 174.08S653.824 905.728 757.76 893.44c103.936-12.8 187.904-90.624 187.904-174.08V74.752c-0.512-52.224-43.52-82.944-96.256-68.096z" /></svg>';
this.tag = "button";
this.showDropPanel = true;
}
// 菜單是否需要激活(如選中加粗文本,“加粗”菜單會激活),用不到則返回 false
// isActive(editor: IDomEditor): boolean {
// TS 語法
isActive(editor) {
// JS 語法
return false;
}
// 獲取菜單執(zhí)行時的 value ,用不到則返回空 字符串或 false
// getValue(editor: IDomEditor): string | boolean {
// TS 語法
getValue(editor) {
// JS 語法
return "";
}
// 菜單是否需要禁用(如選中 H1 ,“引用”菜單被禁用),用不到則返回 false
// isDisabled(editor: IDomEditor): boolean {
// TS 語法
isDisabled(editor) {
// JS 語法
return false;
}
// 點擊菜單時觸發(fā)的函數(shù)
// exec(editor: IDomEditor, value: string | boolean) {
// TS 語法
exec(editor, value) {
// JS 語法
// DropPanel menu ,這個函數(shù)不用寫,空著即可
if (this.isDisabled(editor)) {
return;
}
editor.emit("AudioMenuClick");
}
}
// export const menu1Conf = {
// key: "uploadAudio", // 定義 menu key :要保證唯一、不重復(fù)(重要)
// factory() {
// return new AudioMenu(); // 把 `YourMenuClass` 替換為你菜單的 class
// }
// };
2. 注冊菜單到wangEditor,并插入菜單到工具欄 --- index.vue
onCreated(editor) {
this.editorRef = Object.seal(editor); // 一定要用 Object.seal() ,否則會報錯
this.toolbarConfig.insertKeys = {
index: 24, // 插入的位置,基于當(dāng)前的 toolbarKeys
keys: ["menu1"]
};
// 注冊菜單
Boot.registerMenu(menu1Conf);
module();
// 事件監(jiān)聽
const initMediaMenuEvent = () => {
const editor = this.editorRef;
// 在點擊事件中,根據(jù)具體菜單,可以觸發(fā)響應(yīng)的功能,這里可以為每個事件創(chuàng)建一個el-dialog彈窗。我們就可以完全按照自己的需求開發(fā)后續(xù)功能
editor.on("AudioMenuClick", () => {
// 你點擊了音頻菜單
console.log("123");
editor.insertNode({
type: "audio",
src: "http://music.163.com/song/media/outer/url?id=1908673805.mp3",
children: [{ text: "aaa" }]
});
});
};
initMediaMenuEvent(); // 注冊自定義菜單點擊事件
}
3. 定義節(jié)點數(shù)據(jù)結(jié)構(gòu) --- plugin.js
import { DomEditor, IDomEditor } from "@wangeditor/editor";
import { Transforms } from "slate";
function withAudio(editor) {
const { isVoid, normalizeNode } = editor;
const newEditor = editor;
// 重寫 isVoid
// @ts-ignore
newEditor.isVoid = elem => {
const { type } = elem;
if (type === "audio") {
return true;
}
return isVoid(elem);
};
// 重寫 normalizeNode
newEditor.normalizeNode = ([node, path]) => {
const type = DomEditor.getNodeType(node);
// ----------------- audio 后面必須跟一個 p header blockquote -----------------
if (type === "audio") {
// -------------- audio 是 editor 最后一個節(jié)點,需要后面插入 p --------------
const isLast = DomEditor.isLastNode(newEditor, node);
if (isLast) {
Transforms.insertNodes(newEditor, DomEditor.genEmptyParagraph(), {
at: [path[0] + 1]
});
}
}
// 執(zhí)行默認(rèn)的 normalizeNode ,重要?。?!
return normalizeNode([node, path]);
};
// 返回 editor ,重要!
return newEditor;
}
export default withAudio;
4. 在編輯器中渲染新元素 --- render-elem.js
必須安裝 snabbdom.js
yarn add snabbdom --peer
## 安裝到 package.json 的 peerDependencies 中即可
import { DomEditor, IDomEditor, SlateElement } from "@wangeditor/editor";
import { h, VNode } from "snabbdom";
function renderAudioElement(elemNode, children, editor) {
const { src = "", width = "300", height = "54" } = elemNode;
const selected = DomEditor.isNodeSelected(editor, elemNode);
const audioVnode = h(
"audio", // html標(biāo)簽
{
props: {
src: src,
contentEditable: false,
controls: true
},
style: {
width: width + "px",
height: height + "px",
"max-width": "100%" // 這里之所以要寫死,是為了實現(xiàn)寬度自適應(yīng)的。如果直接設(shè)置width:100%,會觸發(fā)報錯。所以想要實現(xiàn)width:100%效果,需要先設(shè)置max-width,然后在給width設(shè)置一個離譜的值,比如說100000.
}
}
);
const vnode = h(
"div",
{
props: {
className: "w-e-textarea-video-container", // 這里直接復(fù)用video的效果
"data-selected": selected ? "true" : ""
}
},
audioVnode
);
const containerVnode = h(
"div",
{
props: {
contentEditable: false
},
on: {
mousedown: e => e.preventDefault()
}
},
vnode
);
return containerVnode;
}
const renderAudioConf = {
type: "audio", // 新元素 type ,重要?。?!即custom-type中定義的type
renderElem: renderAudioElement
};
export { renderAudioConf };
5. 把新元素轉(zhuǎn)換為 HTML --- elem-to-html.js
import { SlateElement } from "@wangeditor/editor";
function audioElemtToHtml(elem, childrenHtml) {
const { src, width = 300, height = 54 } = elem;
// 通過data-w-e開頭的data數(shù)據(jù),存放一些必要的信息,到時候通過setHtml將富文本信息還原回編輯器的時候,才能使編輯器正常識別
const html = `<div data-w-e-type="audio" data-w-e-is-void data-w-e-type="audio" data-w-e-width="${width}" data-w-e-height="${height}" data-src="${src}" data-width="${width}" data-height="${height}">
<audio poster="" controls style="width:${width};height:${height};max-width:100%" src="${src}"><source src="${src}" type="audio/mpeg"/></audio>
</div>`;
return html;
}
const audioToHtmlConf = {
type: "audio",
elemToHtml: audioElemtToHtml
};
export { audioToHtmlConf };
6. 解析新元素 HTML 到編輯器 --- parse-elem-html.js
import { IDomEditor, SlateDescendant, SlateElement } from "@wangeditor/editor";
function parseAudioElementHtml(domElem,children,editor) {
const src = domElem.getAttribute("data-src"); // 這些就是elem-html.ts自定義擴(kuò)展存放的地方,可以根據(jù)需要自行擴(kuò)展
const height = domElem.getAttribute("data-height");
const width = domElem.getAttribute("data-width");
const myAudio = {
// 這里的信息要和custom-types.ts一致
type: "audio",
src,
width,
height,
children: [{ text: "" }]
};
return myAudio;
}
const parseAudioHtmlConf = {
selector: 'div[data-w-e-type="audio"]', // 這個就是elem-html.ts中第一個div里包含的信息
parseElemHtml: parseAudioElementHtml
};
export { parseAudioHtmlConf };
7. 注冊插件到 wangEditor --- index.js
import { IModuleConf } from "@wangeditor/editor";
import { Boot } from "@wangeditor/editor";
import { renderAudioConf } from "./render-elem";
import { audioToHtmlConf } from "./elem-to-html";
import { parseAudioHtmlConf } from "./parse-elem-html";
import withAudio from "./plugin";
function module() {
Boot.registerRenderElem(renderAudioConf);
Boot.registerElemToHtml(audioToHtmlConf);
Boot.registerParseElemHtml(parseAudioHtmlConf);
Boot.registerPlugin(withAudio);
}
export default module;
8. 在index.vue中導(dǎo)入并使用
import module from '../../../plugins/module'
onCreated(editor) {
module()
}
以上就是今天的全部內(nèi)容啦。遇到問題就留言。