vue3+ts
需求:在编辑器插入图片和视频时下方会有一个输入框填写描述,上传word功能
wangeditor文档wangEditor开源 Web 富文本编辑器,开箱即用,配置简朴 https://www.wangeditor.com/
安装:npm install @wangeditor/editor --save
1、自定义按钮部分 index.ts,参考了文档
2、editorComponents.vue代码,在editor组件中引入index.ts和renderviedoEle/index和renderimgEle/index
- <script setup lang="ts">
- import {
- onBeforeUnmount,
- ref,
- reactive,
- shallowRef,
- defineEmits,
- defineProps,
- } from "vue";
- import "@wangeditor/editor/dist/css/style.css";
- import {
- Editor,
- Toolbar,
- IDomEditor,
- } from "@wangeditor/editor-for-vue";
- import {
- Boot,
- DomEditor,
- } from "@wangeditor/editor";
- import type { UploadInstance } from "element-plus";
- import mammoth from "mammoth";
- import customvideo from "@/utils/renderviedoEle/index";
- import customimage from "@/utils/renderimgEle/index";
- import {
- menu1Conf,
- menu2Conf,
- menu3Conf,
- } from "@/utils/menus/index";
- defineOptions({
- name: "editUpload"
- });
- const emit = defineEmits([
- "changevalue",
- ]);
- const mode = "default";
- const props = defineProps({
- editvalue: {
- type: String,
- default: ""
- },
- });
- const localeditvalue = ref(props.editvalue);
- const txtplace = reactive({
- findContent: "",
- replaceContent: ""
- });
- const textReplaceShow = ref(false);
- const replaceTextInHTML = function (html, searchText, replaceText) {
- // 定义全局匹配的正则表达式,匹配除了HTML标签之外的所有内容
- const regex = />([^<]*)</g;
- // 使用replace方法替换匹配到的文本内容
- const replacedHtml = html.replace(regex, (match, text) => {
- // 判断文本内容是否包含需要替换的搜索文本
- if (text.includes(searchText)) {
- // 替换文本内容
- const replacedText = text.replace(
- new RegExp(searchText, "g"),
- replaceText
- );
- return `>${replacedText}<`;
- } else {
- // 不需要替换,返回原内容
- return match;
- }
- });
- return replacedHtml;
- };
- const handleSubmit = () => {//替换文本提交
- const html = editorRef.value.getHtml();
- const newHtml = replaceTextInHTML(
- html,
- txtplace.findContent,
- txtplace.replaceContent
- );
- editorRef.value.setHtml(newHtml);
- };
- const insertVideo = val => {//插入视频
- editorRef.value.restoreSelection();// 恢复选区
- setTimeout(() => {
- editorRef.value.insertNode({
- type: "customvideo",
- src: val.videoUrl,
- poster: val.coverUrl,
- videoId: val.videoID,
- altDes: "",
- children: [
- {
- text: ""
- }
- ]
- });
- }, 500);
- }
- const sendeluploads = ref<UploadInstance>();
- // 编辑器实例,必须用 shallowRef
- const editorRef = shallowRef();
- const toolbarConfig: any = {//这里把不想要的菜单排除掉
- excludeKeys: [
- "insertImage",
- "insertVideo",
- "uploadVideo",
- "editvideomenu",
- "group-video"
- ]
- };
- const editorConfig = {
- placeholder: "请输入内容...",
- MENU_CONF: {}
- };
- // 在工具栏插入自定义的按钮
- toolbarConfig.insertKeys = {
- index: 19, // 插入的位置,基于当前的 toolbarKeys
- keys: [
- "videomenu",
- "wordmenu",
- "textReplace"
- ]
- };
- //注意:这个要再外面注入,不然会报错
- Boot.registerModule(customvideo);
- Boot.registerModule(customimage);
- const handleCreated = (editor: IDomEditor) => {
- editorRef.value = editor;
- // 判断已插入过就不要重复插入按钮
- if (
- !editor
- .getAllMenuKeys()
- ?.includes(
- "videomenu",
- "wordmenu",
- "textReplace"
- )
- ) {
- Boot.registerMenu(menu1Conf);
- Boot.registerMenu(menu2Conf);
- Boot.registerMenu(menu3Conf);
- }
- editor.on("uploadvideo", val => {
- // 处理上传视频的逻辑,上传完直接插入视频 insertVideo()
- // ........
- });
- editor.on("uploadword", () => {
- // 点击上传word按钮模拟上传事件clik
- sendeluploads.value.$.vnode.el.querySelector("input").click();
- });
- editor.on("toggleModal", (modalName, show) => {
- // 显示替换的弹框
- textReplaceShow.value = show;
- });
- };
- const onChange = editor => {//编辑器的值改变
- emit("changevalue", editor.getHtml());
- };
- // 组件销毁时,也及时销毁编辑器
- onBeforeUnmount(() => {
- const editor = editorRef.value;
- if (editor == null) return;
- editor.destroy();
- });
- // 图片上传阿里云服务器
- editorConfig.MENU_CONF["uploadImage"] = {
- // 自定义上传
- async customUpload(file: File, insertFn) {
- aliyunApi(file).then((res: any) => {
- // 上传到服务器后插入自定义图片节点
- editorRef.value.insertNode({
- type: "customimage",
- src: res.url,
- alt: res.name,
- href: res.url,
- children: [
- {
- text: ""
- }
- ]
- });
- });
- }
- };
- const handleSuccess = val => {};
- const beforeUpload = val => {};
- const handleUpload = val => {//上传完word文档后的处理,此处用到了mammoth.js,查看地址:https://github.com/mwilliamson/mammoth.js
- // word文档转换插入到富文本
- const file = val.file;
- var reader = new FileReader();
- reader.onload = function (loadEvent) {
- var arrayBuffer = loadEvent.target?.result;
- mammoth
- .convertToHtml(
- { arrayBuffer: arrayBuffer as ArrayBuffer },
- { convertImage: convertImage }//将base64图片转换上传到阿里云服务器
- )
- .then(
- function (result) {
- // 没能修改插入图片的源码,这里自己做了下修改,加了customimage的div,让图片渲染走自己定义的节点
- // 如果没有这一步,会默认插入原先img的那个节点
- const parser = new DOMParser();
- const doc = parser.parseFromString(result.value, "text/html");
- const images = doc.getElementsByTagName("img");
- for (let i = images.length - 1; i >= 0; i--) {
- const img = images[i];
- const div = doc.createElement("div");
- div.setAttribute("data-w-e-type", "customimage");
- div.setAttribute("data-w-e-is-void", "");
- div.setAttribute("data-w-e-is-inline", "");
- if (img.parentNode) {
- img.parentNode.replaceChild(div, img);
- }
- div.appendChild(img);
- }
- const processedHtml = doc.body.innerHTML;
- editorRef.value.dangerouslyInsertHtml(processedHtml);
- },
- function (error) {
- console.error(error);
- }
- );
- };
- reader.readAsArrayBuffer(file);
- };
- // word图片转换
- const convertImage = mammoth.images.imgElement(image => {
- return image.read("base64").then(async imageBuffer => {
- const result = await uploadBase64Image(imageBuffer, image.contentType);
- return { src: result };
- });
- });
- const uploadBase64Image = async (base64Image, mime) => {
- const _file = base64ToBlob(base64Image, mime);
- let data: any = await aliyunApi(_file);
- return data.url;
- };
- const base64ToBlob = (base64, mime) => {
- mime = mime || "";
- const sliceSize = 1024;
- const byteChars = window.atob(base64);
- const byteArrays = [];
- for (
- let offset = 0, len = byteChars.length;
- offset < len;
- offset += sliceSize
- ) {
- const slice = byteChars.slice(offset, offset + sliceSize);
- const byteNumbers = new Array(slice.length);
- for (let i = 0; i < slice.length; i++) {
- byteNumbers[i] = slice.charCodeAt(i);
- }
- const byteArray = new Uint8Array(byteNumbers);
- byteArrays.push(byteArray);
- }
- return new Blob(byteArrays, { type: mime });
- };
- </script>
- <template>
- <div
- class="wangeditor"
- >
- <Toolbar :editor="editorRef" :defaultConfig="toolbarConfig" :mode="mode" />
- <Editor
- id="editor-container"
- v-model="localeditvalue"
- :defaultConfig="editorConfig"
- :mode="mode"
- style="height: 500px; overflow-y: hidden; border: 1px solid #ccc"
- @onCreated="handleCreated"
- @onChange="onChange"
- />
- <el-upload
- v-show="false"
- ref="sendeluploads"
- action="#"
- :show-file-list="false"
- accept=".docx"
- :on-success="handleSuccess"
- :before-upload="beforeUpload"
- :http-request="handleUpload"
- />
- <el-dialog
- v-model="textReplaceShow"
- title="文本替换"
- width="30%"
- class="replacedialog"
- >
- <el-form
- v-model="txtplace"
- label-width="auto"
- >
- <el-form-item label="查找文本">
- <el-input v-model="txtplace.findContent" />
- </el-form-item>
- <el-form-item label="替换文本">
- <el-input v-model="txtplace.replaceContent" />
- </el-form-item>
- <el-form-item>
- <el-button type="primary" @click="handleSubmit">替换</el-button>
- </el-form-item>
- </el-form>
- </el-dialog>
- </div>
- </template>
- <style scoped lang="scss">
- .replacedialog {
- .el-form {
- .el-form-item {
- margin-bottom: 20px;
- label {
- font-weight: bold;
- color: #333;
- }
- .el-input {
- input {
- color: #333;
- }
- }
- }
- }
- }
- </style>
- <style lang="scss">
- .w-e-image-container {
- border: 2px solid transparent;
- }
- .w-e-text-container [data-slate-editor] .w-e-selected-image-container {
- border: 2px solid rgb(180 213 255);
- }
- .w-e-text-container [data-slate-editor] img {
- display: block !important;
- margin: 0 auto;
- }
- .w-e-text-container [data-slate-editor] .w-e-image-container {
- display: block;
- }
- .w-e-text-container [data-slate-editor] .w-e-image-container:hover {
- box-shadow: none;
- }
- .txt-input {
- .el-textarea__inner {
- height: 300px;
- }
- }
- .w-e-text-container [data-slate-editor] p {
- margin: 5px 0;
- }
- .w-e-textarea-video-container video {
- width: 30%;
- }
- .w-e-textarea-video-container {
- background: none;
- }
- .w-e-text-container
- [data-slate-editor]
- .w-e-selected-image-container
- .left-top {
- display: none;
- }
- .w-e-text-container
- [data-slate-editor]
- .w-e-selected-image-container
- .right-top {
- display: none;
- }
- .w-e-text-container
- [data-slate-editor]
- .w-e-selected-image-container
- .left-bottom {
- display: none;
- }
- .w-e-text-container
- [data-slate-editor]
- .w-e-selected-image-container
- .right-bottom {
- display: none;
- }
- </style>
复制代码 3、在页面中引用editor组件
复制代码 4.自定义节点的部分renderviedoEle/index,renderimgEle/index 放在了githubhttps://github.com/srttina/wangeditor-customsalte/tree/master
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。 |