Skip to content

Vue 3 示例

本页面展示如何在 Vue 3 项目中使用 Elegant Editor。

基础使用

安装

bash
pnpm add @frame-flex/vue @frame-flex/exporters

最简单的编辑器

vue
<template>
  <RichTextEditor v-model="content" />
</template>

<script setup lang="ts">
import { ref } from 'vue';
import { RichTextEditor } from '@frame-flex/vue';
import '@frame-flex/vue/styles';

const content = ref({
  type: 'doc',
  content: []
});
</script>

完整示例

带工具栏和导出功能

vue
<template>
  <div class="editor-container">
    <!-- 编辑器 -->
    <RichTextEditor
      v-model="content"
      :editable="editable"
      @update="handleUpdate"
    />

    <!-- 工具栏 -->
    <div class="toolbar">
      <button @click="toggleEditable">
        {{ editable ? '锁定' : '解锁' }}
      </button>
      <button @click="clearContent">清空内容</button>
    </div>

    <!-- 导出按钮 -->
    <div class="export-actions">
      <button @click="exportHTML">导出 HTML</button>
      <button @click="exportDOCX">导出 DOCX</button>
      <button @click="exportJSON">导出 JSON</button>
    </div>

    <!-- 统计信息 -->
    <div class="stats">
      <div>字符数: {{ stats.characters }}</div>
      <div>单词数: {{ stats.words }}</div>
      <div>段落数: {{ stats.paragraphs }}</div>
    </div>
  </div>
</template>

<script setup lang="ts">
import { ref, computed } from 'vue';
import { RichTextEditor, useEditor } from '@frame-flex/vue';
import { downloadHTML, downloadDOCX, downloadJSON } from '@frame-flex/exporters';
import '@frame-flex/vue/styles';

const content = ref({
  type: 'doc',
  content: [
    {
      type: 'heading',
      attrs: { level: 1 },
      content: [{ type: 'text', text: '欢迎使用 Elegant Editor' }]
    },
    {
      type: 'paragraph',
      content: [
        { type: 'text', text: '这是一个功能强大的' },
        { type: 'text', text: '富文本编辑器', marks: [{ type: 'bold' }] },
        { type: 'text', text: '。' }
      ]
    }
  ]
});

const editable = ref(true);
const { editor } = useEditor();

const stats = computed(() => {
  if (!editor.value) {
    return { characters: 0, words: 0, paragraphs: 0 };
  }

  const text = editor.value.getText();
  const html = editor.value.getHTML();

  return {
    characters: text.length,
    words: text.trim().split(/\s+/).filter(Boolean).length,
    paragraphs: (html.match(/<p>/g) || []).length
  };
});

function handleUpdate(newContent: any) {
  console.log('Content updated:', newContent);
}

function toggleEditable() {
  editable.value = !editable.value;
}

function clearContent() {
  if (confirm('确定要清空内容吗?')) {
    content.value = { type: 'doc', content: [] };
  }
}

function exportHTML() {
  if (editor.value) {
    downloadHTML(editor.value, 'document.html', {
      includeStyles: true,
      includeScripts: true,
      title: 'Elegant Editor 文档'
    });
  }
}

async function exportDOCX() {
  if (editor.value) {
    await downloadDOCX(editor.value, 'document.docx');
  }
}

function exportJSON() {
  if (editor.value) {
    downloadJSON(editor.value, 'document.json');
  }
}
</script>

<style scoped>
.editor-container {
  max-width: 900px;
  margin: 0 auto;
  padding: 20px;
}

.toolbar {
  display: flex;
  gap: 10px;
  margin: 20px 0;
}

.export-actions {
  display: flex;
  gap: 10px;
  margin: 20px 0;
}

button {
  padding: 10px 20px;
  background: #3b82f6;
  color: white;
  border: none;
  border-radius: 6px;
  cursor: pointer;
  font-size: 14px;
  transition: background 0.2s;
}

button:hover {
  background: #2563eb;
}

.stats {
  display: flex;
  gap: 20px;
  padding: 15px;
  background: #f8fafc;
  border-radius: 6px;
  margin-top: 20px;
}

.stats div {
  font-size: 14px;
  color: #64748b;
}
</style>

自定义配置

只读模式

vue
<template>
  <RichTextEditor
    v-model="content"
    :editable="false"
  />
</template>

初始内容

vue
<script setup lang="ts">
const content = ref({
  type: 'doc',
  content: [
    {
      type: 'heading',
      attrs: { level: 1 },
      content: [{ type: 'text', text: '标题' }]
    },
    {
      type: 'paragraph',
      content: [{ type: 'text', text: '段落内容' }]
    }
  ]
});
</script>

监听事件

vue
<template>
  <RichTextEditor
    v-model="content"
    @update="onUpdate"
    @focus="onFocus"
    @blur="onBlur"
  />
</template>

<script setup lang="ts">
function onUpdate(content: any) {
  console.log('内容更新:', content);
}

function onFocus() {
  console.log('编辑器获得焦点');
}

function onBlur() {
  console.log('编辑器失去焦点');
}
</script>

使用 Composable

useEditor

vue
<script setup lang="ts">
import { onMounted } from 'vue';
import { useEditor } from '@frame-flex/vue';

const { editor, createEditor, destroyEditor } = useEditor();

onMounted(() => {
  createEditor({
    content: '<p>Hello World</p>',
    editable: true,
    onUpdate: (content) => {
      console.log(content);
    }
  });
});

// 获取内容
const getContent = () => {
  console.log(editor.value?.getHTML());
  console.log(editor.value?.getJSON());
};

// 设置内容
const setContent = () => {
  editor.value?.setContent('<h1>New Content</h1>');
};

// 切换可编辑状态
const toggleEditable = () => {
  const current = editor.value?.isEditable;
  editor.value?.setEditable(!current);
};
</script>

与表单集成

在表单中使用

vue
<template>
  <form @submit.prevent="handleSubmit">
    <div class="form-group">
      <label>标题</label>
      <input v-model="form.title" type="text" />
    </div>

    <div class="form-group">
      <label>内容</label>
      <RichTextEditor v-model="form.content" />
    </div>

    <button type="submit">提交</button>
  </form>
</template>

<script setup lang="ts">
import { ref } from 'vue';
import { RichTextEditor } from '@frame-flex/vue';

const form = ref({
  title: '',
  content: { type: 'doc', content: [] }
});

function handleSubmit() {
  console.log('提交表单:', form.value);
  // 发送到服务器...
}
</script>

TypeScript 支持

Elegant Editor 提供完整的类型定义:

typescript
import type { JSONContent } from '@frame-flex/core';
import type { Editor } from '@tiptap/core';

const content: JSONContent = {
  type: 'doc',
  content: []
};

const editor: Editor | null = useEditor().editor.value;

性能优化

懒加载

vue
<script setup lang="ts">
import { defineAsyncComponent } from 'vue';

const RichTextEditor = defineAsyncComponent(() =>
  import('@frame-flex/vue').then(m => m.RichTextEditor)
);
</script>

防抖更新

vue
<script setup lang="ts">
import { ref } from 'vue';
import { useDebounceFn } from '@vueuse/core';

const content = ref({ type: 'doc', content: [] });

const debouncedUpdate = useDebounceFn((newContent) => {
  // 保存到服务器...
  console.log('保存内容:', newContent);
}, 1000);

function handleUpdate(newContent: any) {
  content.value = newContent;
  debouncedUpdate(newContent);
}
</script>

常见问题

Q: 如何获取纯文本?

typescript
const text = editor.value?.getText();

Q: 如何设置初始焦点?

vue
<script setup lang="ts">
onMounted(() => {
  editor.value?.commands.focus('end');
});
</script>

Q: 如何添加自定义样式?

vue
<style>
/* 覆盖编辑器样式 */
:deep(.ProseMirror) {
  min-height: 300px;
  padding: 20px;
}

:deep(.ProseMirror h1) {
  color: #3b82f6;
}
</style>

下一步

Released under the MIT License.