vue使用wangEditor自定义上传音频的新功能


image.png
wangEditor官网 开源 Web 富文本编辑器,开箱即用,配置简单

要实现一个完整的富文本编辑器功能,你可能还需要以下功能:

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: '请输入内容...' },
            mode: 'default', // or 'simple'
        }
    },
    methods: {
        onCreated(editor) {
            this.editor = Object.seal(editor) // 一定要用 Object.seal() ,否则会报错
        },
    },
    mounted() {
        // 模拟 ajax 请求,异步渲染编辑器
        setTimeout(() => {
            this.html = '<p>模拟 Ajax 异步设置内容 HTML</p>'
        }, 1500)
    },
    beforeDestroy() {
        const editor = this.editor
        if (editor == null) return
        editor.destroy() // 组件销毁时,及时销毁编辑器
    }
})
</script>

TIP:
赋值 this.editor 时要用 Object.seal()
组件销毁时,要及时销毁编辑器

wangEditor富文本编辑框中,有上传图片和上传视频的功能,但是没有上传音频的功能,所以就需要使用wangEditor的自动以扩展新功能来实现上传音频的功能

自定义扩展新功能

文件目录为:


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;
  }

  // 获取菜单执行时的 value ,用不到则返回空 字符串或 false
  // getValue(editor: IDomEditor): string | boolean {
  // TS 语法
  getValue(editor) {
    // JS 语法
    return "";
  }

  // 菜单是否需要禁用(如选中 H1 ,“引用”菜单被禁用),用不到则返回 false
  // isDisabled(editor: IDomEditor): boolean {
  // TS 语法
  isDisabled(editor) {
    // JS 语法
    return false;
  }

  // 点击菜单时触发的函数
  // exec(editor: IDomEditor, value: string | boolean) {
  // TS 语法
  exec(editor, value) {
    // JS 语法
    // DropPanel menu ,这个函数不用写,空着即可
    if (this.isDisabled(editor)) {
      return;
    }
    editor.emit("AudioMenuClick");
  }
}


// export const menu1Conf = {
//   key: "uploadAudio", // 定义 menu key :要保证唯一、不重复(重要)
//   factory() {
//     return new AudioMenu(); // 把 `YourMenuClass` 替换为你菜单的 class
//   }
// };

2. 注册菜单到wangEditor,并插入菜单到工具栏 --- index.vue

onCreated(editor) {
      this.editorRef = Object.seal(editor); // 一定要用 Object.seal() ,否则会报错
      this.toolbarConfig.insertKeys = {
        index: 24, // 插入的位置,基于当前的 toolbarKeys
        keys: ["menu1"]
      };
      // 注册菜单
      Boot.registerMenu(menu1Conf);
      module();

      // 事件监听
      const initMediaMenuEvent = () => {
        const editor = this.editorRef;
        // 在点击事件中,根据具体菜单,可以触发响应的功能,这里可以为每个事件创建一个el-dialog弹窗。我们就可以完全按照自己的需求开发后续功能
        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. 定义节点数据结构 --- 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 最后一个节点,需要后面插入 p --------------
      const isLast = DomEditor.isLastNode(newEditor, node);
      if (isLast) {
        Transforms.insertNodes(newEditor, DomEditor.genEmptyParagraph(), {
          at: [path[0] + 1]
        });
      }
    }

    // 执行默认的 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标签
    {
      props: {
        src: src,
        contentEditable: false,
        controls: true
      },
      style: {
        width: width + "px",
        height: height + "px",
        "max-width": "100%" // 这里之所以要写死,是为了实现宽度自适应的。如果直接设置width:100%,会触发报错。所以想要实现width:100%效果,需要先设置max-width,然后在给width设置一个离谱的值,比如说100000.
      }
    }
  );
  const vnode = h(
    "div",
    {
      props: {
        className: "w-e-textarea-video-container", // 这里直接复用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. 把新元素转换为 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数据,存放一些必要的信息,到时候通过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自定义扩展存放的地方,可以根据需要自行扩展
  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中导入并使用

import module from '../../../plugins/module'


onCreated(editor) {
  module()
}

以上就是今天的全部内容啦。遇到问题就留言。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,271评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,275评论 2 380
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,151评论 0 336
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,550评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,553评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,559评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,924评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,580评论 0 257
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,826评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,578评论 2 320
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,661评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,363评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,940评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,926评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,156评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,872评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,391评论 2 342

推荐阅读更多精彩内容