Skip to content

MarkdownEditor Markdown 编辑器

基于 marked 的 Markdown 编辑器组件,支持实时预览和语法高亮。

基础用法

vue
<script setup lang="ts">
import { ref } from 'vue'
import { MarkdownEditor } from '@vben/common-ui'

const content = ref('')
</script>

<template>
  <MarkdownEditor v-model="content" />
</template>

自定义高度

vue
<script setup lang="ts">
import { ref } from 'vue'
import { MarkdownEditor } from '@vben/common-ui'

const content = ref('')
</script>

<template>
  <MarkdownEditor 
    v-model="content" 
    height="600px"
  />
</template>

分屏模式

vue
<script setup lang="ts">
import { ref } from 'vue'
import { MarkdownEditor } from '@vben/common-ui'

const content = ref('')
</script>

<template>
  <MarkdownEditor 
    v-model="content"
    mode="split"
  />
</template>

只读模式

vue
<script setup lang="ts">
import { ref } from 'vue'
import { MarkdownEditor } from '@vben/common-ui'

const content = ref(`# 标题

这是一段 **Markdown** 内容。

- 列表项 1
- 列表项 2
- 列表项 3
`)
</script>

<template>
  <MarkdownEditor 
    v-model="content"
    readonly
  />
</template>

API

Props

参数说明类型默认值
modelValue绑定值(Markdown 字符串)string''
height编辑器高度string'500px'
placeholder占位文本string'请输入 Markdown 内容...'
readonly是否只读booleanfalse
disabled是否禁用booleanfalse
mode显示模式'edit' | 'preview' | 'split''split'
theme代码高亮主题string'github'
uploadImage自定义图片上传(file: File) => Promise<string>-
toolbar工具栏配置array默认工具栏

Events

事件名说明回调参数
update:modelValue内容变化时触发(value: string) => void
change内容变化时触发(value: string) => void
focus获得焦点时触发() => void
blur失去焦点时触发() => void

Methods

方法名说明参数
getMarkdown获取 Markdown 内容-
getHTML获取 HTML 内容-
clear清空内容-
focus聚焦编辑器-
insertText插入文本(text: string) => void

完整示例

vue
<script setup lang="ts">
import { ref } from 'vue'
import { message } from 'ant-design-vue'
import { MarkdownEditor } from '@vben/common-ui'
import axios from 'axios'

const content = ref(`# Markdown 编辑器

## 功能特性

- 实时预览
- 语法高亮
- 图片上传
- 代码块支持

## 代码示例

\`\`\`javascript
function hello() {
  console.log('Hello, World!')
}
\`\`\`

## 表格

| 列1 | 列2 | 列3 |
|-----|-----|-----|
| 值1 | 值2 | 值3 |
`)

const editorRef = ref()
const mode = ref<'edit' | 'preview' | 'split'>('split')

// 自定义图片上传
const handleUploadImage = async (file: File) => {
  // 检查文件大小
  if (file.size > 2 * 1024 * 1024) {
    message.error('图片大小不能超过 2MB')
    throw new Error('图片过大')
  }
  
  // 上传图片
  const formData = new FormData()
  formData.append('file', file)
  
  try {
    const { data } = await axios.post('/api/upload/image', formData)
    return data.url
  } catch (error) {
    message.error('图片上传失败')
    throw error
  }
}

const handleChange = (value: string) => {
  console.log('内容变化:', value)
}

const handleSave = () => {
  const markdown = editorRef.value?.getMarkdown()
  const html = editorRef.value?.getHTML()
  
  console.log('Markdown:', markdown)
  console.log('HTML:', html)
  
  message.success('保存成功')
}

const handleClear = () => {
  editorRef.value?.clear()
}

const handleInsertCode = () => {
  const code = `\`\`\`javascript
function example() {
  console.log('示例代码')
}
\`\`\``
  editorRef.value?.insertText(code)
}
</script>

<template>
  <div class="editor-container">
    <div class="editor-toolbar">
      <a-space>
        <a-radio-group v-model:value="mode" button-style="solid">
          <a-radio-button value="edit">编辑</a-radio-button>
          <a-radio-button value="split">分屏</a-radio-button>
          <a-radio-button value="preview">预览</a-radio-button>
        </a-radio-group>
        
        <a-button @click="handleInsertCode">插入代码</a-button>
        <a-button type="primary" @click="handleSave">保存</a-button>
        <a-button @click="handleClear">清空</a-button>
      </a-space>
    </div>
    
    <MarkdownEditor
      ref="editorRef"
      v-model="content"
      :mode="mode"
      height="600px"
      :upload-image="handleUploadImage"
      @change="handleChange"
    />
  </div>
</template>

<style scoped>
.editor-container {
  border: 1px solid #d9d9d9;
  border-radius: 4px;
  overflow: hidden;
}

.editor-toolbar {
  padding: 12px 16px;
  background: #fafafa;
  border-bottom: 1px solid #d9d9d9;
}
</style>

Markdown 语法

标题

markdown
# 一级标题
## 二级标题
### 三级标题
#### 四级标题
##### 五级标题
###### 六级标题

文本样式

markdown
**粗体文本**
*斜体文本*
~~删除线~~
`行内代码`

列表

markdown
# 无序列表
- 项目 1
- 项目 2
- 项目 3

# 有序列表
1. 第一项
2. 第二项
3. 第三项

链接和图片

markdown
[链接文本](https://example.com)
![图片描述](https://example.com/image.jpg)

代码块

markdown
```javascript
function hello() {
  console.log('Hello, World!')
}
```

引用

markdown
> 这是一段引用文本
> 可以有多行

表格

markdown
| 列1 | 列2 | 列3 |
|-----|-----|-----|
| 值1 | 值2 | 值3 |
| 值4 | 值5 | 值6 |

分隔线

markdown
---
***
___

工具栏配置

默认工具栏

javascript
const defaultToolbar = [
  'bold',           // 粗体
  'italic',         // 斜体
  'strikethrough',  // 删除线
  'heading',        // 标题
  'code',           // 行内代码
  'quote',          // 引用
  'unordered-list', // 无序列表
  'ordered-list',   // 有序列表
  'link',           // 链接
  'image',          // 图片
  'table',          // 表格
  'horizontal-rule',// 分隔线
  'preview',        // 预览
  'fullscreen',     // 全屏
  'guide'           // 帮助
]

自定义工具栏

vue
<script setup lang="ts">
const toolbar = [
  'bold',
  'italic',
  'heading',
  'code',
  'link',
  'image',
  'preview'
]
</script>

<template>
  <MarkdownEditor 
    v-model="content"
    :toolbar="toolbar"
  />
</template>

主题定制

代码高亮主题

vue
<script setup lang="ts">
import { ref } from 'vue'
import { MarkdownEditor } from '@vben/common-ui'

const content = ref('')
const theme = ref('github')

const themes = [
  'github',
  'monokai',
  'dracula',
  'atom-one-dark',
  'vs2015'
]
</script>

<template>
  <div>
    <a-select v-model:value="theme" style="width: 200px; margin-bottom: 16px">
      <a-select-option v-for="t in themes" :key="t" :value="t">
        {{ t }}
      </a-select-option>
    </a-select>
    
    <MarkdownEditor 
      v-model="content"
      :theme="theme"
    />
  </div>
</template>

图片处理

图片上传

typescript
const handleUploadImage = async (file: File) => {
  // 检查文件类型
  if (!['image/jpeg', 'image/png', 'image/gif'].includes(file.type)) {
    message.error('只支持 JPG、PNG、GIF 格式')
    throw new Error('格式不支持')
  }
  
  // 检查文件大小
  if (file.size > 2 * 1024 * 1024) {
    message.error('图片大小不能超过 2MB')
    throw new Error('图片过大')
  }
  
  // 上传图片
  const formData = new FormData()
  formData.append('file', file)
  
  const { data } = await axios.post('/api/upload/image', formData)
  return data.url
}

粘贴图片

vue
<script setup lang="ts">
import { ref } from 'vue'
import { MarkdownEditor } from '@vben/common-ui'

const content = ref('')

const handlePaste = async (event: ClipboardEvent) => {
  const items = event.clipboardData?.items
  if (!items) return
  
  for (const item of items) {
    if (item.type.indexOf('image') !== -1) {
      const file = item.getAsFile()
      if (file) {
        // 上传图片
        const url = await handleUploadImage(file)
        // 插入图片
        editorRef.value?.insertText(`![image](${url})`)
      }
    }
  }
}
</script>

<template>
  <MarkdownEditor 
    v-model="content"
    @paste="handlePaste"
  />
</template>

导出功能

导出 Markdown

typescript
const handleExportMarkdown = () => {
  const markdown = editorRef.value?.getMarkdown()
  const blob = new Blob([markdown], { type: 'text/markdown' })
  const url = URL.createObjectURL(blob)
  const link = document.createElement('a')
  link.href = url
  link.download = 'document.md'
  link.click()
  URL.revokeObjectURL(url)
}

导出 HTML

typescript
const handleExportHTML = () => {
  const html = editorRef.value?.getHTML()
  const blob = new Blob([html], { type: 'text/html' })
  const url = URL.createObjectURL(blob)
  const link = document.createElement('a')
  link.href = url
  link.download = 'document.html'
  link.click()
  URL.revokeObjectURL(url)
}

导出 PDF

typescript
import html2pdf from 'html2pdf.js'

const handleExportPDF = () => {
  const html = editorRef.value?.getHTML()
  const element = document.createElement('div')
  element.innerHTML = html
  
  html2pdf()
    .from(element)
    .save('document.pdf')
}

快捷键

快捷键功能
Ctrl/Cmd + B粗体
Ctrl/Cmd + I斜体
Ctrl/Cmd + K插入链接
Ctrl/Cmd + Shift + I插入图片
Ctrl/Cmd + Shift + C插入代码块
Ctrl/Cmd + Shift + Q插入引用
Ctrl/Cmd + Shift + L插入无序列表
Ctrl/Cmd + Shift + O插入有序列表
Ctrl/Cmd + Shift + T插入表格
Ctrl/Cmd + Shift + H插入分隔线
Ctrl/Cmd + Shift + P切换预览
F11全屏

注意事项

注意

  1. Markdown 内容会转换为 HTML,需要注意 XSS 防护
  2. 图片上传需要配置服务器地址
  3. 建议限制图片大小,避免影响性能
  4. 代码高亮需要引入对应的主题样式

提示

  • 使用 uploadImage 自定义图片上传逻辑
  • 可以通过 mode 属性切换显示模式
  • 使用 getMarkdown() 获取 Markdown 内容
  • 使用 getHTML() 获取 HTML 内容
  • 支持拖拽上传图片

相关链接

MIT License