Skip to content

组件用法

vue
<template>
  <VueEditor
    ref="editorRef"
    v-model="content"
    locale="zh-CN"
    :editable="editable"
    :toolbar="toolbar"
    :toolbar-permissions="toolbarPermissions"
    :toolbar-theme="toolbarTheme"
    :upload-image="uploadImage"
    :sanitize="true"
    :paste-whitelist-tags="pasteWhitelistTags"
    :paste-whitelist-attrs="pasteWhitelistAttrs"
    :allowed-link-protocols="['https', 'mailto']"
    :link-domain-whitelist="['example.com', 'openai.com']"
    :validate-link="validateLink"
    :min-height="320"
    placeholder="请输入正文"
    @ready="onReady"
    @image-upload-progress="onImageUploadProgress"
    @image-upload-error="onImageUploadError"
  />

  <button @click="editorRef?.focus()">聚焦</button>
  <button @click="editorRef?.clear()">清空</button>
  <button @click="editorRef?.retryLastImageUpload()">重试最近一次上传</button>

  <pre>{{ content }}</pre>
</template>

<script setup>
import { ref } from "vue";
import { VueEditor } from "vue-editor-prose-kit";

const editorRef = ref(null);
const editable = ref(true);
const content = ref("<p>欢迎使用富文本编辑器</p>");

const toolbar = [
  ["blockType"],
  ["bold", "italic", "underline", "highlight"],
  ["bulletList", "orderedList", "taskList"],
  ["table"],
  ["link", "image"],
  ["undo", "redo"],
];

const toolbarPermissions = {
  clearFormatting: false,
  link: "disabled",
};

const toolbarTheme = "slate";

const pasteWhitelistTags = ["a", "blockquote", "br", "code", "col", "colgroup", "em", "h1", "h2", "h3", "hr", "img", "input", "label", "li", "ol", "p", "pre", "strong", "table", "tbody", "td", "th", "thead", "tr", "u", "ul"];
const pasteWhitelistAttrs = ["alt", "checked", "class", "colspan", "data-checked", "data-float", "data-type", "data-width", "href", "rel", "rowspan", "src", "target", "title", "type"];

async function uploadImage(file, context) {
  const formData = new FormData();
  formData.append("file", file);

  const xhr = new XMLHttpRequest();

  return await new Promise((resolve, reject) => {
    xhr.open("POST", "/api/upload");

    xhr.upload.onprogress = (event) => {
      if (!event.lengthComputable) {
        return;
      }

      context.onProgress(Math.round((event.loaded / event.total) * 100));
    };

    xhr.onload = () => {
      if (xhr.status >= 200 && xhr.status < 300) {
        const response = JSON.parse(xhr.responseText);
        resolve(response.url);
        return;
      }

      reject(new Error("upload failed"));
    };

    xhr.onerror = () => reject(new Error("network error"));
    xhr.send(formData);
  });
}

function validateLink({ normalizedUrl, hostname }) {
  if (normalizedUrl.includes("debug=true")) {
    return "链接中不允许携带 debug 参数";
  }

  return hostname !== "blocked.example.com";
}

function onReady(editor) {
  console.log("editor ready", editor);
}

function onImageUploadProgress(progress) {
  console.log("upload progress", progress);
}

function onImageUploadError(error) {
  console.error("upload error", error);
}
</script>