现在基于上次的中加入图片、表格功能、选中高亮、以及使用v-model绑定富文本组件
安装图片和表格模块
yarn add @tiptap/extension-highlight @tiptap/extension-image
@tiptap/extension-table @tiptap/extension-table-cell
@tiptap/extension-table-header @tiptap/extension-table-row
修改MenuBar.vue中的 items
export default {
components: {
MenuItem
},
props: {
editor: {
type: Object,
required: true
}
},
setup(props) {
const items = reactive([
{
icon: 'bold',
title: '加粗',
action: () => props.editor.chain().focus().toggleBold().run(),
isActive: () => props.editor.isActive('bold')
},
{
icon: 'italic',
title: '斜体',
action: () => props.editor.chain().focus().toggleItalic().run(),
isActive: () => props.editor.isActive('italic')
},
{
icon: 'strikethrough',
title: '文本线',
action: () => props.editor.chain().focus().toggleStrike().run(),
isActive: () => props.editor.isActive('strike')
},
{
icon: 'code-view',
title: '代码',
action: () => props.editor.chain().focus().toggleCode().run(),
isActive: () => props.editor.isActive('code')
},
{
icon: 'mark-pen-line',
title: '高亮',
action: () => props.editor.chain().focus().toggleHighlight().run(),
isActive: () => props.editor.isActive('highlight')
},
{
type: 'divider'
},
{
icon: 'h-1',
title: '标题1',
action: () => props.editor.chain().focus().toggleHeading({ level: 1 }).run(),
isActive: () => props.editor.isActive('heading', { level: 1 })
},
{
icon: 'h-2',
title: '标题2',
action: () => props.editor.chain().focus().toggleHeading({ level: 2 }).run(),
isActive: () => props.editor.isActive('heading', { level: 2 })
},
{
icon: 'h-3',
title: '标题3',
action: () => props.editor.chain().focus().toggleHeading({ level: 3 }).run(),
isActive: () => props.editor.isActive('heading', { level: 3 })
},
{
icon: 'h-4',
title: '标题4',
action: () => props.editor.chain().focus().toggleHeading({ level: 4 }).run(),
isActive: () => props.editor.isActive('heading', { level: 4 })
},
{
icon: 'h-5',
title: '标题5',
action: () => props.editor.chain().focus().toggleHeading({ level: 5 }).run(),
isActive: () => props.editor.isActive('heading', { level: 5 })
},
{
icon: 'h-6',
title: '标题6',
action: () => props.editor.chain().focus().toggleHeading({ level: 6 }).run(),
isActive: () => props.editor.isActive('heading', { level: 6 })
},
{
icon: 'paragraph',
title: '段落',
action: () => props.editor.chain().focus().setParagraph().run(),
isActive: () => props.editor.isActive('paragraph')
},
{
icon: 'list-unordered',
title: '无须列表',
action: () => props.editor.chain().focus().toggleBulletList().run(),
isActive: () => props.editor.isActive('bulletList')
},
{
icon: 'list-ordered',
title: '有须列表',
action: () => props.editor.chain().focus().toggleOrderedList().run(),
isActive: () => props.editor.isActive('orderedList')
},
{
type: 'divider'
},
{
icon: 'double-quotes-l',
title: '块',
action: () => props.editor.chain().focus().toggleBlockquote().run(),
isActive: () => props.editor.isActive('blockquote')
},
{
icon: 'separator',
title: '横线',
action: () => props.editor.chain().focus().setHorizontalRule().run()
},
{
type: 'divider'
},
{
icon: 'format-clear',
title: '清除样式',
action: () => props.editor.chain()
.focus()
.clearNodes()
.unsetAllMarks()
.run()
},
{
type: 'divider'
},
{
icon: 'image-line',
title: '插入图片',
action: () => {
const url = window.prompt('URL')
if (url) {
props.editor.chain().focus().setImage({ src: url }).run()
}
}
},
{
type: 'divider'
},
{
icon: 'arrow-go-back-line',
title: '撤销',
action: () => props.editor.chain().focus().undo().run()
},
{
icon: 'arrow-go-forward-line',
title: '取消撤销',
action: () => props.editor.chain().focus().redo().run()
},
{
type: 'divider'
},
{
icon: 'table-2',
title: '插入表格',
action: () => props.editor.chain().focus()
.insertTable({ rows: 3, cols: 3, withHeaderRow: true }).run()
},
{
icon: 'delete-bin-6-line',
title: '删除表格',
action: () => props.editor.chain().focus().deleteTable().run()
},
{
icon: 'merge-cells-horizontal',
title: '合并拆分单元格',
action: () => props.editor.chain().focus().mergeOrSplit().run()
},
{
icon: 'insert-row-top',
title: '上面添加一行',
action: () => props.editor.chain().focus().addRowBefore().run()
},
{
icon: 'insert-row-bottom',
title: '下面添加一行',
action: () => props.editor.chain().focus().addRowAfter().run()
},
{
icon: 'delete-row',
title: '删除行',
action: () => props.editor.chain().focus().deleteRow().run()
},
{
icon: 'insert-column-left',
title: '左边添加一列',
action: () => props.editor.chain().focus().addColumnBefore().run()
},
{
icon: 'insert-column-right',
title: '右边添加一列',
action: () => props.editor.chain().focus().addColumnAfter().run()
},
{
icon: 'delete-column',
title: '删除行',
action: () => props.editor.chain().focus().deleteColumn().run()
},
{
icon: 'sip-line',
title: '单元格背景色',
action: () => props.editor.chain().focus().toggleHeaderCell().run()
},
{
type: 'divider'
}
])
return {
items
}
}
}
修改Editor.vue
import { useEditor, EditorContent } from '@tiptap/vue-3'
import StarterKit from '@tiptap/starter-kit'
import { defineComponent, onBeforeUnmount } from 'vue'
import MenuBar from './MenuBar.vue'
import Highlight from '@tiptap/extension-highlight'
import Image from '@tiptap/extension-image'
import Table from '@tiptap/extension-table'
import TableRow from '@tiptap/extension-table-row'
import TableCell from '@tiptap/extension-table-cell'
import TableHeader from '@tiptap/extension-table-header'
const CustomTableCell = TableCell.extend({
addAttributes() {
return {
// 展开现有属性,?.是可选链操作符,可以自行百度(懂的大佬当我没说)
...this.parent?.(),
// 添加新的属性
backgroundColor: {
default: null,
parseHTML: (element) => element.getAttribute('data-background-color'),
renderHTML: (attributes) => ({
'data-background-color': attributes.backgroundColor,
style: `background-color: ${attributes.backgroundColor}`
})
}
}
}
})
export default defineComponent({
components: {
EditorContent,
MenuBar
},
props: {
width: {
type: String,
default: '800px'
},
height: {
type: String,
default: '300px'
}
},
setup(props, { emit }) {
const editor = useEditor({
content: "<p>tiptap editor demo.🎉</p>",
extensions: [
StarterKit,
Image,
Highlight.configure({ multicolor: true }),
Table.configure({
resizable: true
}),
TableRow,
TableHeader,
CustomTableCell
],
})
return {
editor
}
}
})
使用的方法非常简单,就是调用editor的api。现在我们就加上了选中高亮、图片、表格的功能。高亮的背景色是根据mark元素的css来的
效果
要注意最好用css手动给图片设置一个宽度会好看点,不然大的图片会直接铺满整个富文本框(我这个效果是设置了宽度)
接下来就是如何讲富文本的数据发送给后端了,修改Editor.vue
export default defineComponent({
components: {
EditorContent,
MenuBar
},
props: {
html: {
type: String,
default: ''
},
/*json: {
type: Object,
default: () => ({
type: 'doc',
content: [
// …
]
})
},*/
width: {
type: String,
default: '800px'
},
height: {
type: String,
default: '300px'
}
},
setup(props, { emit }) {
const editor = useEditor({
content: props.html,
extensions: [
StarterKit,
Image,
Highlight.configure({ multicolor: true }),
Table.configure({
resizable: true
}),
TableRow,
TableHeader,
CustomTableCell
],
onUpdate: () => {
emit('update:html', editor.value.getHTML())
//emit('update:json', editor.value.getJSON())
}
})
return {
editor
}
}
})
使用
<script setup>
import Editor from "./Editor.vue"
const editorHtml = ref('<p>tiptap editor demo.🎉</p>')
</script>
<Editor v-model:html="editorHtml" />
也可以是JSON格式的数据,取消上面代码json的注释并注释html(也可以尝试两种都保留,没测试过,感觉可行) 注意使用JSON格式是默认值必须是{type: 'doc',content: []},不然会报错(没有指定类型)