vue3中通过vditor插件实现自定义上传图片、录入echarts、脑图、markdown语法的编辑器

1、下载Vditor插件

npm i vditor

我的vditor版本是3.10.2,大家可以自行选择下载最新版本

官网:Vditor 一款浏览器端的 Markdown 编辑器,支持所见即所得(富文本)、即时渲染(类似 Typora)和分屏 - 链滴

示例:

Vditor 示例 - 链滴

2、引入vue文件

import Vditor from "vditor";
import "vditor/dist/index.css";

 3、封装vditor自定义组件

<template>
  <div>
    <div
      :id="vidtorId"
      class="vditor"
      :class="{ 'vditor-hidden': showPreview }"
    ></div>
  </div>
</template>
<script setup>
import Vditor from "vditor";
import "vditor/dist/index.css";
import { getToken } from "@/utils/auth";
const { proxy } = getCurrentInstance();
const props = defineProps({
  height: {
    type: [Number, String],
    default: "inherit",
  },
  modelValue: {
    type: String,
    default: "",
  },
  knwlgId: {
    type: String,
    default: "",
  },
  showPreview: {
    type: Boolean,
    default: false,
  },
  disabled: {
    type: Boolean,
    default: false,
  },
  // 额外的formData参数
  extraData: {
    type: Object,
    default: () => ({}),
  },
  uploadURL: {
    type: String,
    default: "",
  },
  previewURL: {
    type: String,
    default: "/ekms/images/v1/preview/",
  },
  placeholder: {
    type: String,
    default: "",
  },
});

const vidtorId = ref(
  "vidtor-" + +new Date() + ((Math.random() * 1000).toFixed(0) + "")
);

// 图片上传地址判断,这里大家可以根据自己需求写死或通过props传入
const imgUploadUrl = computed(() => {
  return !props.uploadURL
    ? `/ekms/images/v1/upload?knwlgId=${props.knwlgId}`
    : props.uploadURL;
});

watch(
  () => props.showPreview,
  (newValue, oldValue) => {
    let previewDom =
      contentEditor.value.vditor.toolbar.elements.preview.firstChild;
    let isPreview = previewDom.className.indexOf("vditor-menu--current") > -1;
    emits("update:showPreview", isPreview ? false : true);
    previewDom.click();
  }
);

const emits = defineEmits([
  "update:modelValue",
  "update:showPreview",
  "setHtml",
]);
const contentEditor = ref(null);

onMounted(() => {
  contentEditor.value = new Vditor(vidtorId.value, {
    height: props.height,
    mode: "wysiwyg", //所见即所得(wysiwyg)、即时渲染(ir)、分屏预览(sv)
    value: props.modelValue,
    cdn: import.meta.env.VITE_APP_VDITOR_API,
    placeholder: props.placeholder,
    toolbarConfig: {
      pin: true,
      // hide: true,
    },
    // outline: {
    //   enable: true, //展示大纲,position默认left
    // },
    cache: {
      enable: false, // 是否使用 localStorage 进行缓存
    },
    preview: {
      mode: "both", //显示模式
      delay: 10,
      actions: [],
      theme: {
        path: `${import.meta.env.VITE_APP_VDITOR_API}/dist/css/content-theme`,
      },
    },
    toolbar: [
      "emoji",
      "headings",
      "bold",
      "italic",
      "strike",
      "link",
      "|",
      "list",
      "ordered-list",
      "check",
      "outdent",
      "indent",
      "|",
      "quote",
      "line",
      "code",
      "inline-code",
      "insert-before",
      "insert-after",
      "|",
      {
        //自定义上传
        hotkey: "",
        name: "upload",
        tip: "上传图片",
        className: "right",
      },

      "table",
      "|",
      "undo",
      "redo",
      "|",
      "code-theme",
      "content-theme",
      "preview",
       // 自定义清空内容的菜单
      {
        hotkey: "⇧⌘S",
        name: "clearAll",
        tipPosition: "n",
        tip: "清空内容",
        className: "right",
        icon: '<svg t="1696926237451" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="2371" width="180" height="180"><path d="M512 838.858c10.89 0 19.732-9.158 19.732-20.43v-490.275c0-11.273-8.842-20.43-19.732-20.43s-19.755 9.157-19.755 20.43v490.275c0 11.272 8.842 20.43 19.755 20.43M629.877 838.813c10.935 0.428 20.138-8.37 20.475-19.688l28.665-489.69c0.427-11.272-8.077-20.745-18.99-21.195-10.935-0.405-20.137 8.415-20.475 19.688l-28.665 489.713c-0.405 11.317 8.1 20.767 18.99 21.172M848.038 185.142h-197.708v-81.653c0-22.545-17.685-40.882-39.51-40.882h-197.64c-21.87 0-39.532 18.338-39.532 40.882v81.653h-197.685c-10.913 0-19.755 9.158-19.755 20.475 0 11.272 8.843 20.407 19.755 20.407h39.577l39.488 653.67c6.367 44.73 35.415 81.72 79.065 81.72h355.793c43.65 0 71.573-37.44 79.088-81.72l39.488-653.67h39.578c10.867 0 19.755-9.135 19.755-20.408 0-11.317-8.888-20.475-19.755-20.475M413.157 103.49h197.64v81.653h-197.64v-81.653zM729.418 879.695c-2.655 21.555-17.73 40.86-39.533 40.86h-355.793c-21.87 0-36.54-19.057-39.532-40.86l-39.532-653.67h513.945l-39.555 653.67zM394.145 838.858c10.89-0.473 19.373-9.9 18.99-21.195l-29.070-489.712c-0.427-11.273-9.585-20.070-20.475-19.665-10.913 0.428-19.463 9.9-19.013 21.173l29.093 489.712c0.36 11.295 9.54 20.070 20.475 19.688z" p-id="2372"></path></svg>',
        click() {
          contentEditor.value.setValue("");
        },
      },
    ],
    // vditor结构渲染完成后
    after: () => {
      emits("setHtml", getValue(), getHTML());
      // 这里根据传入的disabled来设置vditor是否可输入内容
      props.disabled && contentEditor.value.disabled();
    },
    input: () => {
      // 变更事件回调
      emits("setHtml", getValue(), getHTML());
    },
    // 上传图片
    upload: {
      accept: "image/*", // 图片类型
      fieldName: "file",
      url: import.meta.env.VITE_APP_BASE_API + imgUploadUrl.value, // 图片上传的接口路径
      extraData: props.extraData, // 要携带的额外的formData参数
      headers: {
        Authorization: "Bearer " + getToken(), // 用户身份信息
      },
      filename(name) {  // 过滤特殊字符
        return name
          .replace(/[^(a-zA-Z0-9\u4e00-\u9fa5\.)]/g, "")
          .replace(/[\?\\/:|<>\*\[\]\(\)\$%\{\}@~]/g, "")
          .replace("/\\s/g", "");
      },
      // 粘贴图片的情况下上传的接口路径
      linkToImgUrl: import.meta.env.VITE_APP_BASE_API + imgUploadUrl.value,
      max: 20 * 1024 * 1024,
      multiple: false,
      withCredentials: false,
      // 图片上传校验,可以校验图片大小及类型
      validate(files) {
        const isLt2M = files[0].size / 1024 / 1024 < 20;
        if (!isLt2M) {
          proxy.$modal.msgError("上传图片大小不能超过 20MB!");
        }
        if (!files[0].type.includes("image")) {
          return proxy.$modal.msgError("仅支持上传图片!");
        }
      },
      //粘贴图片回显处理,如果有图片加了防盗链,则让后台代理替换成自己的图片
      linkToImgFormat(responseText) {
        let res = JSON.parse(responseText);
        if (!res.data?.imgId) return;
        let end = JSON.stringify({
          msg: "",
          code: 0,
          data: {
            originalURL:
              import.meta.env.VITE_APP_BASE_API +
              props.previewURL +
              res.data?.imgId, //图片原始地址记录到本地文件中,可以防止跨站点调用。
            url:
              import.meta.env.VITE_APP_BASE_API +
              props.previewURL +
              res.data?.imgId, //图片链接记录到本地文件中,可以防止跨站点调用。
          },
        });
        return end;
      },
      format(files, responseText) {
        var res = JSON.parse(responseText);
        if (!res.data?.imgId) return;
        //图片回显
        nextTick(() => {
          emits("setHtml", getValue(), getHTML());
        });
        return JSON.stringify({
          msg: "",
          code: 0,
          data: {
            errFiles: [],
            succMap: {
              [res.data.imgPath]:
                import.meta.env.VITE_APP_BASE_API +
                props.previewURL +
                res.data?.imgId,
            },
          },
        });
      },
    },
  });
});

function getValue() {
  return contentEditor.value.getValue(); //获取 Markdown 内容
}

// 获取html结构
function getHTML() {
  let dom = document.querySelector(
    ".vditor-content .vditor-wysiwyg .vditor-reset"
  )?.innerHTML;
  return dom;
}

function setValue(value) {
  return contentEditor.value.setValue(value); //设置 Markdown 内容
}

defineExpose({
  getHTML,
  setValue,
});
</script>

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/750971.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

软件必须要进行跨浏览器测试吗?包括哪些内容和注意事项?

随着互联网的普及和发展&#xff0c;用户对软件的要求越来越高。无论是在台式机、笔记本还是移动设备上&#xff0c;用户都希望能够以最好的体验来使用软件。然而&#xff0c;不同的浏览器在解析网页的方式、支持的技术标准等方面存在差异&#xff0c;这就导致了同一个网页在不…

使用J-Link Commander查找STM32死机问题

接口:PA13,PA14&#xff0c;请勿连接复位引脚。 输入usb命令这里我已经连接过了STM32F407VET6了。 再输入connect命令这里我已经默认选择了SWD接口&#xff0c;4000K速率。 可以输入speed 4000命令选择4000K速率: 写一段崩溃代码进行测试: void CashCode(void){*((volatil…

(2024,RNN,梯度消失和爆炸,记忆诅咒,重参数化和动态学习率,权重矩阵对角化,复值 RNN)梯度消失和爆炸并不是故事的结局

Recurrent neural networks: vanishing and exploding gradients are not the end of the story 公和众与号&#xff1a;EDPJ&#xff08;进 Q 交流群&#xff1a;922230617 或加 VX&#xff1a;CV_EDPJ 进 V 交流群&#xff09; 目录 0. 摘要 1. 梯度消失和梯度爆炸 2. 记…

PyCharm2024 for mac Python编辑开发

Mac分享吧 文章目录 效果一、下载软件二、开始安装1、双击运行软件&#xff08;适合自己的M芯片版或Intel芯片版&#xff09;&#xff0c;将其从左侧拖入右侧文件夹中&#xff0c;等待安装完毕2、应用程序显示软件图标&#xff0c;表示安装成功3、打开访达&#xff0c;点击【文…

深入解读一下`android.os.CountDownTimer`

简介 在 Android 开发中&#xff0c;CountDownTimer 是一个非常有用的类&#xff0c;它可以用于倒计时任务&#xff0c;比如倒计时器、限时活动等。CountDownTimer 提供了一个简单的方式来实现定时操作&#xff0c;无需我们手动管理线程和计时器。 本文将深入解析 CountDownT…

大数据------JavaWeb------Maven(完整知识点汇总)

额外知识点 IDE IDE是集成开发环境的缩写&#xff0c;它是一种软件应用程序&#xff0c;提供了编码、调试和部署软件的一站式解决方案。这些功能集成在一起&#xff0c;使开发人员能够在一个环境中完成整个软件开发过程&#xff0c;从编写代码到调试和测试&#xff0c;直到最终…

中霖教育:税务师考试通过率怎么样?

税务师考试的通过率通常在20%至30%的范围内&#xff0c;涵盖五个科目&#xff1a;《税法一》、《税法二》、《财务与会计》、《涉税服务实务》和《涉税服务相关法律》&#xff0c;成绩有效期为五年。 针对税务师备考&#xff0c;有效的学习策略至关重要。 1、熟悉各科的题型和…

什么是 SSH(安全外壳协议)以及如何工作

安全外壳协议&#xff08;Secure Shell&#xff0c;简称SSH&#xff09;&#xff0c;旨在取代未加密的协议&#xff08;如 Telnet 和 RSH&#xff09;和未受保护的文件传输协议&#xff08;如 FTP 和 RCP&#xff09;&#xff0c;在两个设备之间提供安全的加密连接。 安全外壳…

数字社交的领航者:解析Facebook的引领作用

在当今数字化社会中&#xff0c;社交网络已经成为了人们日常生活不可或缺的一部分。而在众多社交平台中&#xff0c;Facebook凭借其巨大的用户基础和创新的技术应用&#xff0c;被公认为数字社交领域的领航者之一。本文将深入解析Facebook在数字社交中的引领作用&#xff0c;探…

免费可视化工具如何提升工作效率?

免费可视化工具能为我们的工作带来什么好处&#xff1f;在如今数据密集的工作环境中&#xff0c;如何高效地处理和展示数据成为了每个行业的重要任务。传统的工具如Excel虽然强大&#xff0c;但在处理复杂数据和创建高级图表时往往显得力不从心。而免费可视化工具的出现&#x…

让DroidVNC-NG支持中文输入

DroidVNC-NG支持控制端输入内容&#xff0c;但是仅支持英文字符&#xff0c;如果需要控制输入法软键盘输入中文的话就没办法了&#xff0c;经过摸索找到了解决办法。 这个解决办法有个条件就是让DroidVNC-NG成为系统级应用&#xff08;这个条件比较苛刻&#xff09;&#xff…

HarmonyOS Next开发学习手册——层叠布局 (Stack)

概述 层叠布局&#xff08;StackLayout&#xff09;用于在屏幕上预留一块区域来显示组件中的元素&#xff0c;提供元素可以重叠的布局。层叠布局通过 Stack 容器组件实现位置的固定定位与层叠&#xff0c;容器中的子元素依次入栈&#xff0c;后一个子元素覆盖前一个子元素&…

谈谈Flink消费kafka的偏移量

offset配置: flinkKafkaConsumer.setStartFromEarliest():从topic的最早offset位置开始处理数据&#xff0c;如果kafka中保存有消费者组的消费位置将被忽略。 flinkKafkaConsumer.setStartFromLatest():从topic的最新offset位置开始处理数据&#xff0c;如果kafka中保存有消费…

全自动封箱机技术革新:效率优化新篇章

在日新月异的物流行业中&#xff0c;全自动封箱机以其高效、精准的特性&#xff0c;成为了不可或缺的关键设备。然而&#xff0c;随着市场竞争的加剧和客户需求的不断升级&#xff0c;如何进一步优化全自动封箱机的效率&#xff0c;成为了行业内外关注的焦点。 一、全自动封箱机…

俯视LLM的灵魂:一文搞懂稀疏自动编码器

实时了解业内动态&#xff0c;论文是最好的桥梁&#xff0c;专栏精选论文重点解读热点论文&#xff0c;围绕着行业实践和工程量产。若在某个环节出现卡点&#xff0c;可以回到大模型必备腔调或者LLM背后的基础模型重新阅读。而最新科技&#xff08;Mamba,xLSTM,KAN&#xff09;…

鸿蒙开发HarmonyOS NEXT (一) 入门

最近总听见大家讨论鸿蒙&#xff0c;前端转型的好方向&#xff1f;先入门学习下 目前官方版本和文档持续更新中 一、开发环境 提示&#xff1a;要占用的空间比较多&#xff0c;建议安装在剩余空间多的盘 1、下载&#xff1a;官网最新工具 - 下载中心 - 华为开发者联盟 (huaw…

滑动窗口1

1. 长度最小的子数组&#xff08;209&#xff09; 题目描述: 算法原理&#xff1a; 这题使用滑动窗口的方法来进行解决&#xff0c;而滑动窗口实际上就是通过定义同向双指针的方式来实现的&#xff0c;两个指针就是窗口的左右边界&#xff0c;然后结合这样的思想来给出这题的…

华为---配置基本的访问控制列表(ACL)

11、访问控制列表&#xff08;ACL&#xff09; 11.1 配置基本的访问控制列表 11.1.1 原理概述 访问控制列表ACL(Access Control List)是由permit或deny语句组成的一系列有顺序的规则集合&#xff0c;这些规则根据数据包的源地址、目的地址、源端口、目的端口等信息来描述。A…

MySQL数据库简介和安装

文章目录 一、数据库原理目前情况数据库的发展史RDBMS关系型数据库关系型数据库理论 二、MySQL历史发展历程关系型数据库和非关系型数据库 三、安装mysql及优化yum安装编译安装mysql二进制安装优化操作 四、 安装mycli插件客户端工具 一、数据库原理 目前情况 我们正处于一个…

Servlet中请求转发【 Forward】与重定向【Redirection】的区别

在Servlet中&#xff0c;请求转发&#xff08;Request Forwarding&#xff09;和重定向&#xff08;Redirection&#xff09;是用于控制请求流程的两种不同机制。它们的主要区别如下&#xff1a; 一、请求转发 服务器内部操作&#xff1a;转发是在服务器内部进行的操作&#xf…