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>