Skip to content
当前页

vditor

安装

pnpm i vditor

二次封装

针对vditor进行封装,扩展了mermaid相关的示例,不影响直接使用vditor

二次封装代码位置

import { useVditor } from '/@/views/demo/hooks/web/useVditor';

功能:

demo.png

效果:

echarts.pngecahrts-pie.png

使用说明

vditor官方给的文档非常详细,也有中文说明,目前做了部分测试,未发现阻断性问题,后边会列出测试过程中遇到的一些问题以及注意事项

测试代码

vue
<template>
  <div id="vditor"></div>
  <button @click="test">2222</button>
</template>

<script setup lang="ts">
import { ref, onMounted } from 'vue';
import Vditor from 'vditor';
import 'vditor/dist/index.css';
import { useVditor } from '/@/views/demo/hooks/web/useVditor';

const vditorss = ref<Vditor | null>(null);

function test() {
  // console.log(vditor.value.vditor.options.toolbar)
  vditorss.value!.setValue(
          'Vue Composition API + Vditor + TypeScript Minimal Example 11111',
  );
}

interface MyResponse {
  code: number;
  message: string;
  result: any;
}

interface NeedResult {
  code: number;
  msg: string;
  data: any;
}

onMounted(() => {
  const { vditor } = useVditor('vditor', {
    // cdn: 'https://cdn.jsdelivr.net/npm/vditor@3.8.12',
    cdn: '/resource',
    height: 600,
    toolbarConfig: {
      pin: true, // 页面吸顶效果,类似于菜单始终处于顶部
      hide: false, // 是否隐藏工具栏
    },
    preview: {
      // 预览状态下 的操作按钮["desktop", "tablet", "mobile", "mp-wechat", "zhihu"],
      // 可以拷贝针对不同平台格式的文档
      actions: [],
    },
    cache: {
      enable: false,
    },
    toolbar: [
      'mermaid', // 自定义  使用useVditor可以使用
      // 'emoji',
      // 'headings',
      // 'bold',
      // 'italic',
      // 'strike',
      // '|',
      // 'line',
      // 'quote',
      // 'list',
      // 'ordered-list',
      // 'check',
      // 'outdent',
      // 'indent',
      // 'code',
      // 'inline-code',
      // 'insert-after',
      // 'insert-before',
      // 'undo',
      // 'redo',
      // 'upload',
      // 'link',
      // 'table',
      // 'record',
      'edit-mode',
      // 'both',
      'preview',
      // 'fullscreen',
      // 'outline',
      // 'code-theme',
      // 'content-theme',
      // 'export',
      // 'devtools',
      // 'info',
      // 'help',
      // 'br',
    ],
    after: () => {
      // vditor.value is a instance of Vditor now and thus can be safely used here
      vditor.value!.setValue(
              'Vue Composition API + Vditor + TypeScript Minimal Example',
      );
    },
    upload: {
      // accept: 'image/*,.mp3, .wav, .rar', //上传文件类型
      // token: 'xxxx',
      // filename: function (name) {
      //   console.log('filename:' + name);
      //   return name
      //     .replace(/[^(a-zA-Z0-9\u4e00-\u9fa5\.)]/g, '')
      //     .replace(/[\?\\/:|<>\*\[\]\(\)\$%\{\}@~]/g, '')
      //     .replace('/\\s/g', '');
      // }, // 替换文件名称
      // fieldName: 'files', // 上传字段名称
      url: '/api/markdown-upload', // 本地文件上传地址,后续会给出后端接口写法
      format(files: File[], responseText: string): string {
        // 对返回体进行处理, 返回值为 string
        // files文件信息
        // responseText返回体字符串
        // 需求结构体
        /**
         * {
         *  "msg": "",
         *  "code": 0,
         *  "data": {
         *  "errFiles": ['filename', 'filename2'],
         *  "succMap": {
         *    "filename3": "filepath3",
         *    "filename3": "filepath3"
         *    }
         *  }
         * }
         */
        console.log(files, responseText);
        let result: NeedResult = {} as NeedResult;
        const response: MyResponse = JSON.parse(responseText);
        result.code = 0; // 注意、注意、注意,结构体要求此处为0
        result.msg = response.message;
        result.data = response.result;
        /**
         * 后端返回值,需要处理成固定结构
         * {
         *     "success":true,
         *     "message":"OK",
         *     "result":{
         *         "succMap":{
         *             "image.png":"/midway-upload-files/upload_1667184793903.0.3117785344185593.0.png"
         *         }
         *     }
         * }
         */
        return JSON.stringify(result);
      },
      linkToImgUrl: '/api/url', // 从互联网复制文章,存在外网图片,上传到后端的接口,后续会给出后端接口写法
      linkToImgFormat(responseText: string): string {
        let result: NeedResult = {} as NeedResult;
        const response: MyResponse = JSON.parse(responseText);
        result.code = 0; // 注意、注意、注意,结构体要求此处为0,否则替换失败
        result.msg = response.message;
        result.data = response.result;
        console.log(result);
        return JSON.stringify(result);
      },
      linkToImgCallback(responseText: string) {
        console.log(responseText);
      },
    },
  });
  vditorss.value = vditor.value;
});
</script>

<style></style>

图片上传后端参考

  • 本地文件上传接口(如复制粘贴图片到编辑器中)

    参数file是一个文件对象upload.png

  • 互联网文章图片上传

    参数中的url为互联网图片地址,需要服务器端进行图片下载internet-img.png

注意事项

1、关于CDN的问题

  • 遇到的问题:

    由于官方使用的CDN不太容易访问,出现引用了vditor,但是界面却没有显示编辑器

  • 解决方案:

    • 按照官方示例搭建CDN(未测试)
    • 将vditor源码打包后将dist包放到public目录下进行引入(本次测试使用的该方式) cdn.png

2、关于vite与vditor兼容的问题

  • 遇到的问题:

    出现Lute is not define错误

  • 解决方案:

    • vite.config.ts文件中有process.xx相关的配置 process.png

3、复制互联网文章内图片上传服务器遇到的问题,相关功能 upload.linkToImgFormat

  • 遇到的问题:

    upload.linkToImgFormat按返回值结构处理后,连接未替换成站内url

  • 注意事项:

js
// POST data
xhr.send(JSON.stringify({url: src})); // src 为站外图片地址
// return data
{
 msg: '',
 code: 0,   // 注意-注意-注意,此处必须为 0 
 data : {
   originalURL: '',  // 原url
   url: ''           // 站内url
 }
}