vue3使用 @wangeditor/editor 及遇到问题的记录

wangeditor使用及文件下载问题

wangEditor 官网。 本文讲解 wangEditor 在 vue3 中的如何使用以及发现的问题处理记录。

Q: 发现上传文件后无法下载,看下组件中的代码悉知。。。

1. 安装

npm install @wangeditor/editor @wangeditor/editor-for-vue @wangeditor/plugin-upload-attachment
# or
yarn add @wangeditor/editor @wangeditor/editor-for-vue @wangeditor/plugin-upload-attachment

2. 项目使用

在 main.ts 中引入

import { Boot } from "@wangeditor/editor";
import attachmentModule from "@wangeditor/plugin-upload-attachment";
// 富文本编辑器
Boot.registerModule(attachmentModule);

创建子组件的目录
在这里插入图片描述

index.vue 代码
<template>
  <div :class="['editor-box', self_disabled ? 'editor-disabled' : '']">
    <Toolbar v-if="!hideToolBar" class="editor-toolbar" :editor="editorRef" :default-config="toolbarConfig" :mode="mode" />
    <Editor
      v-model="valueHtml"
      class="editor-content"
      :style="{ height }"
      :mode="mode"
      :default-config="editorConfig"
      @on-created="handleCreated"
      @on-blur="handleBlur"
    />
  </div>
</template>

<script setup lang="ts" name="WangEditor">
import { nextTick, computed, inject, shallowRef, onBeforeUnmount, watch } from "vue";
import { formContextKey, formItemContextKey } from "element-plus";
import { IToolbarConfig, IEditorConfig } from "@wangeditor/editor";
import { Editor, Toolbar } from "@wangeditor/editor-for-vue";
import { uploadFile1 } from "@/api/modules/upload";
import "@wangeditor/editor/dist/css/style.css";

// 富文本 DOM 元素
const editorRef = shallowRef();

// 实列化编辑器
const handleCreated = (editor: any) => {
  editorRef.value = editor;
  if (props.disabled) {
    editorRef.value.disable();
  }
  // *******************回显后的双击下载处理*******开始****************************
  const dom = editorRef.value.getEditableContainer();
  let regex = /<a\s+(?:[^>=]|='[^']*'|="[^"]*"|\S)*?data-w-e-type="attachment"[\s\S]*?>([\s\S]+?)<\/a>/g;
  let matches: any = [];
  let match;
  while ((match = regex.exec(props.value))) {
    let href = match[0]; // 完整的<a>标签及其内部内容
    let text = match[1].trim(); // 提取href属性值
    matches.push({ text: text, href: href });
  }
  const spans = dom.querySelectorAll('span[contenteditable="false"]');
  if (spans && spans.length) {
    for (let i = 0; i < spans.length; i++) {
      const element = spans[i].innerText;
      if (matches && matches.length) {
        for (let j = 0; j < matches.length; j++) {
          const aObj = matches[j];
          if (element === aObj.text) {
            matches.splice(j, 1); // 删除当前
            spans[i].setAttribute("title", aObj.href.match('<a.+?href="(.+?)".*>')[1]);
            spans[i].addEventListener("dblclick", (e: any) => {
              if (e.target?.title) {
                window.open(e.target?.title, "_blank");
              }
            });
          }
        }
      }
    }
  }
  // *******************回显后的双击下载处理*******结束****************************
};

// 接收父组件参数,并设置默认值
interface RichEditorProps {
  value: string; // 富文本值 ==> 必传
  toolbarConfig?: Partial<IToolbarConfig>; // 工具栏配置 ==> 非必传(默认为空)
  editorConfig?: Partial<IEditorConfig>; // 编辑器配置 ==> 非必传(默认为空)
  height?: string; // 富文本高度 ==> 非必传(默认为 500px)
  mode?: "default" | "simple"; // 富文本模式 ==> 非必传(默认为 default)
  hideToolBar?: boolean; // 是否隐藏工具栏 ==> 非必传(默认为false)
  disabled?: boolean; // 是否禁用编辑器 ==> 非必传(默认为false)
  upload: string; // 上传的路径 ==> 必传
}

const props = withDefaults(defineProps<RichEditorProps>(), {
  toolbarConfig: () => {
    return {
      excludeKeys: [],
      insertKeys: {
        index: 22, // 自定义插件在工具栏显示的位置
        keys: ["uploadAttachment"] // 查看名称
      }
    };
  },
  editorConfig: () => {
    return {
      placeholder: "请输入内容...",
      MENU_CONF: {}
    };
  },
  height: "500px",
  mode: "default",
  hideToolBar: false,
  disabled: false,
  upload: "/devices"
});

// 获取 el-form 组件上下文
const formContext = inject(formContextKey, void 0);
// 获取 el-form-item 组件上下文
const formItemContext = inject(formItemContextKey, void 0);
// 判断是否禁用上传和删除
const self_disabled = computed(() => {
  return props.disabled || formContext?.disabled;
});
// 判断当前富文本编辑器是否禁用
if (self_disabled.value) nextTick(() => editorRef.value.disable());

// 富文本的内容监听,触发父组件改变,实现双向数据绑定
const emit = defineEmits<{
  "update:value": [value: string];
  "check-validate": [];
}>();
const valueHtml = computed({
  get() {
    return props.value;
  },
  set(val: string) {
    // 防止富文本内容为空时,校验失败
    if (editorRef.value.isEmpty()) val = "";
    emit("update:value", val);
  }
});
// 图片上传前判断
const uploadImgValidate = (file: File): boolean => {
  console.log(file);
  return true;
};

/**
 * @description 文件自定义上传
 * @param file 上传的文件
 * @param insertFn 上传成功后的回调函数(插入到富文本编辑器中)
 * */
type InsertFnTypeFile = (name: string, href: string) => void;
props.editorConfig.MENU_CONF!["uploadAttachment"] = {
  async customUpload(file: File, insertFn: InsertFnTypeFile) {
    if (!uploadImgValidate(file)) return;
    let formData = new FormData();
    console.log(file, "file===");
    formData.append("file", file);
    try {
      const { result, code }: { [string: string]: any } = await uploadFile1(props.upload, formData);
      if (+code === 200) {
        insertFn(file.name, result);
      }
    } catch (error) {
      console.log(error);
    }
  },
  // 自定义选择,并上传附件
  // customBrowseAndUpload(insertFn) {
  //   console.log("insertFn", insertFn);
  // },
  // 插入到编辑器后的回调
  async onInsertedAttachment(elem) {
    await nextTick();
    // *******************上传后的双击下载处理*******开始****************************
    const dom = editorRef.value.getEditableContainer();
    const spans = dom.querySelectorAll('span[data-slate-inline="true"][data-slate-node="element"][data-slate-void="true"]');
    let num: number = 0;
    for (let i = 0; i < spans.length; i++) {
      const parts = spans[i].getAttribute("id")!.split("-");
      const domLast = +parts[parts.length - 1];
      num = Math.max(num, domLast);
    }
    const idDom = document.getElementById("w-e-element-" + num);
    idDom!.children[0].setAttribute("title", elem.link);
    idDom!.children[0].addEventListener("dblclick", (e: any) => {
      if (e.target?.title) {
        window.open(e.target?.title, "_blank");
      }
    });
    // *******************上传后的双击下载处理*******结束****************************
  }
};
/**
 * @description 图片自定义上传
 * @param file 上传的文件
 * @param insertFn 上传成功后的回调函数(插入到富文本编辑器中)
 * */
type InsertFnTypeImg = (url: string, alt?: string, href?: string) => void;
props.editorConfig.MENU_CONF!["uploadImage"] = {
  async customUpload(file: File, insertFn: InsertFnTypeImg) {
    if (!uploadImgValidate(file)) return;
    let formData = new FormData();
    formData.append("file", file);
    try {
      const { result, code }: { [string: string]: any } = await uploadFile1(props.upload, formData);
      if (+code === 200) {
        insertFn(result, file.name, result);
      }
    } catch (error) {
      console.log(error);
    }
  }
};
/**
 * @description 视频自定义上传
 * @param file 上传的文件
 * @param insertFn 上传成功后的回调函数(插入到富文本编辑器中)
 * */
type InsertFnTypeVideo = (url: string, poster?: string) => void;
props.editorConfig.MENU_CONF!["uploadVideo"] = {
  async customUpload(file: File, insertFn: InsertFnTypeVideo) {
    if (!uploadVideoValidate(file)) return;
    let formData = new FormData();
    formData.append("file", file);
    try {
      const { result, code }: { [string: string]: any } = await uploadFile1(props.upload, formData);
      if (+code === 200) {
        insertFn(result);
      }
    } catch (error) {
      console.log(error);
    }
  }
};

// 视频上传前判断
const uploadVideoValidate = (file: File): boolean => {
  console.log(file);
  return true;
};

// 编辑框失去焦点时触发
const handleBlur = () => {
  console.log("触发handleBlur");
  formItemContext?.prop && formContext?.validateField([formItemContext.prop as string]);
};

// 组件销毁时,也及时销毁编辑器
onBeforeUnmount(() => {
  if (!editorRef.value) return;
  editorRef.value.destroy();
});

defineExpose({
  editor: editorRef
});
</script>

<style scoped lang="scss">
@import "./index.scss";
</style>


index.scss 代码
/* 富文本组件校验失败样式 */
.is-error {
  .editor-box {
    border-color: var(--el-color-danger);
    .editor-toolbar {
      border-bottom-color: var(--el-color-danger);
    }
  }
}

/* 富文本组件禁用样式 */
.editor-disabled {
  cursor: not-allowed !important;
}

/* 富文本组件样式 */
.editor-box {
  /* 防止富文本编辑器全屏时 tabs组件 在其层级之上 */
  z-index: 2;
  width: 100%;
  border: 1px solid var(--el-border-color-darker);
  .editor-toolbar {
    border-bottom: 1px solid var(--el-border-color-darker);
  }
  .editor-content {
    overflow-y: hidden;
    :deep(.w-e-text-placeholder) {
      top: 10px;
    }
    :deep(.w-e-scroll > div) {
      height: 100%;
    }
  }
}
子组件引用
// 引入
import WangEditor from "@/components/WangEditor/index.vue";
// dom 使用
<el-form-item label="xxx" prop="content">
   <WangEditor v-model:value="form.content" :disabled="dialogProps.isView" :hide-tool-bar="dialogProps.isView" upload="/api" height="300px" />
</el-form-item>

文档还有不足之处,欢迎广大老板积极批评指正!!!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值