Bootstrap

QuillEditor富文本结合vue3使用,可单独抽离成组件,富文本的内容可自定义

实现效果:

上方的粗体、斜体、字号、字体等各种信息支持自定义配置。

实现方式:

下面的介绍为分步骤的详细介绍,完整版纯享代码可参考这篇博客富文本QuillEditor+vue3组件代码纯享版-CSDN博客

1.新建一个新文件--子组件,如命名为QuillEditor.vue

2.安装插件QuillEditor

npm install QuillEditor

3.引入QuillEditor,template中

下面中没有的插件,自行安装即可。

<template>
	<div style="display: block; width: 100%; height: 100%">
		<QuillEditor
			ref="quillRef"
			v-model:content="content"
			:options="myOptions"
			contentType="html"
			@update:content="setValue()"
		/>
	</div>
</template>

<script setup>
import Compressor from 'compressorjs';
import { QuillEditor, Quill } from '@vueup/vue-quill';
import '@vueup/vue-quill/dist/vue-quill.snow.css';
import ImageUploader from 'quill-image-uploader';
import ImageResize from 'quill-image-resize-custom-module';
import { uploadFilesSysFile } from '@/api/api';
import '@/styles/QuillFont.css'; // 字体和字体大小的自定义文件

</script>

4.配置QuillEditor

下面的内容都是配置在script中。

(1)富文本的总体配置项

const props = defineProps({
	// 左侧菜单数据
	modelValue: String,
});
const emit = defineEmits(['update:modelValue']);
const content = ref('');
const quillRef = ref(null);
// 富文本配置项,将模块功能一起写入到配置项内,也可以单独配置Modules
const myOptions = reactive({
	modules: {
		toolbar: {
			container: [
				['bold', 'italic', 'underline', 'strike'], // 加粗、斜体、下划线、删除线
				[{ header: [1, 2, 3, 4, 5, 6, false] }], // 标题,不同级别的标题
				[{ align: [] }], // 对齐方式
				['blockquote', 'code-block'], // 引用,代码块
				[{ script: 'sub' }, { script: 'super' }], // 上标/下标
				[{ list: 'ordered' }, { list: 'bullet' }], // 有序、无序列表
				[{ indent: '-1' }, { indent: '+1' }], // 缩进
				['link', 'image', 'video'], // 超链接 图片 视频
				[{ direction: 'rtl' }], // 文本方向
				[{ color: [] }, { background: [] }], // 字体颜色和背景色
				[{ size: fontSizeStyle.whitelist }], // 字体大小
				[{ font: fontStyle.whitelist }], // 字体
				[{ lineheight: lineHeight }], // 设置行高
				['clean'], // 清除格式
			],
			handlers: {
				lineheight: (value) => {
					const editor = quillRef.value.getQuill();
					if (value) {
						editor.format('lineHeight', value);
					}
				},
			},
		},
		// 上传图片--进行压缩并修改为服务器地址
		imageUploader: {
			upload: async (file) => {
				try {
					const compressedFile = await compressImage(file); // 压缩图片

					// 将Blob格式转化为File格式
					const fileVal = new File([compressedFile], compressedFile.name, {
						type: compressedFile.type,
						lastModified: Date.now(),
					});

					return new Promise((resolve, reject) => {
						const formData = new FormData();
						formData.append('files', fileVal);

						uploadFilesSysFile(formData)
							.then((res) => {
								resolve(window.Global.commonPreviewImage_URL + res.data); // 图片的服务器地址展示
							})
							.catch((err) => {
								// eslint-disable-next-line prefer-promise-reject-errors
								reject('Upload failed');
								console.error('Error:', err);
							});
					});
				} catch (error) {
					console.error('压缩和上传图像时出错:', error);
					return false;
				}
			},
		},
		// 设置图片的大小--可拖拽设置大小
		imageResize: {
			parchment: Quill.import('parchment'),
		},
	},
	placeholder: '请输入内容...',
});


Quill.register(fontSizeStyle, true);
Quill.register(fontStyle, true);
Quill.register('modules/imageUploader', ImageUploader);
Quill.register('modules/imageResize', ImageResize);

const setValue = () => {
	// 用于设置双向绑定值
	const text = toRaw(quillRef.value).getHTML();
	emit('update:modelValue', text);
};

watch(
	() => props.modelValue,
	(val) => {
		if (val) {
			content.value = val; // 用于监听绑定值进行数据回填
		} else {
			// eslint-disable-next-line no-unused-expressions
			toRaw(quillRef.value) && toRaw(quillRef.value).setContents(''); // 可用于弹窗使用富文本框关闭弹窗清除值
		}
	},
	{
		immediate: true, // 组件创建时立即执行一次回调
	},
);


Tips:下面是几项特殊的配置,除上面的配置外,还应多加上下面的这些代码。

(2)配置行高

在子组件中配置行高,同时要新建一个行高的css样式文件,子组件中配置的行高数值,css文件中都应该进行了定义才能被有效引用。

// 设置行高的配置======start
const lineHeight = ['1', '1.25', '1.5', '1.75', '2.5', '3', '5'];
const parchment = Quill.import('parchment');
const lineHeightConfig = {
	scope: parchment.Scope.INLINE,
	whitelist: lineHeight,
};
const lineHeightStyle = new parchment.Attributor.Style(
	'lineHeight',
	'line-height',
	lineHeightConfig,
);
Quill.register({ 'formats/lineHeight': lineHeightStyle }, true);
// 设置行高的配置======end

行高的css文件QuillLineHeight.css

.ql-snow .ql-picker.ql-lineheight .ql-picker-label::before {
	content: '行高';
}
.ql-snow .ql-picker.ql-lineheight .ql-picker-item[data-value='1']::before {
	content: '1';
}
.ql-snow .ql-picker.ql-lineheight .ql-picker-item[data-value='1.5']::before {
	content: '1.5';
}
.ql-snow .ql-picker.ql-lineheight .ql-picker-item[data-value='1.75']::before {
	content: '1.75';
}
.ql-snow .ql-picker.ql-lineheight .ql-picker-item[data-value='2']::before {
	content: '2';
}
.ql-snow .ql-picker.ql-lineheight .ql-picker-item[data-value='3']::before {
	content: '3';
}
.ql-snow .ql-picker.ql-lineheight .ql-picker-item[data-value='4']::before {
	content: '4';
}
.ql-snow .ql-picker.ql-lineheight .ql-picker-item[data-value='5']::before {
	content: '5';
}
.ql-snow .ql-picker.ql-lineheight {
	width: 70px;
}

(3)配置字号

下面的字号会显示在富文本的字号中,但需配置一个总体的字号文件,此处将字号和字体的样式文件放在同一个css中了,命名为QuillFont.css。

// 设置字体大小
const fontSizeStyle = Quill.import('attributors/style/size'); // 引入这个后会把样式写在style上
fontSizeStyle.whitelist = [
	'12px',
	'14px',
	'16px',
	'18px',
	'20px',
	'22px',
	'24px',
	'28px',
	'30px',
];

QuillFont.css文件的样式

.ql-snow .ql-picker.ql-font .ql-picker-label[data-value='SimHei']::before,
.ql-snow .ql-picker.ql-font .ql-picker-item[data-value='SimHei']::before {
	font-family: SimHei;
	content: '黑体';
}
.ql-snow .ql-picker.ql-font .ql-picker-label[data-value='Microsoft-YaHei']::before,
.ql-snow .ql-picker.ql-font .ql-picker-item[data-value='Microsoft-YaHei']::before {
	font-family: 'Microsoft YaHei';
	content: '微软雅黑';
}
.ql-snow .ql-picker.ql-font .ql-picker-label[data-value='KaiTi']::before,
.ql-snow .ql-picker.ql-font .ql-picker-item[data-value='KaiTi']::before {
	font-family: KaiTi;
	content: '楷体';
}
.ql-snow .ql-picker.ql-font .ql-picker-label[data-value='FangSong']::before,
.ql-snow .ql-picker.ql-font .ql-picker-item[data-value='FangSong']::before {
	font-family: FangSong;
	content: '仿宋';
}
.ql-snow .ql-picker.ql-font .ql-picker-label[data-value='SimSun']::before,
.ql-snow .ql-picker.ql-font .ql-picker-item[data-value='SimSun']::before {
	font-family: SimSun;
	content: '宋体';
}
.ql-snow .ql-picker.ql-font .ql-picker-label[data-value='STFANGSO']::before,
.ql-snow .ql-picker.ql-font .ql-picker-item[data-value='STFANGSO']::before {
	font-family: STFANGSO;
	content: '华文仿宋';
}
.ql-snow .ql-picker.ql-font .ql-picker-label[data-value='STKAITI']::before,
.ql-snow .ql-picker.ql-font .ql-picker-item[data-value='STKAITI']::before {
	font-family: STKAITI;
	content: '华文楷体';
}
.ql-snow .ql-picker.ql-font .ql-picker-label[data-value='Arial']::before,
.ql-snow .ql-picker.ql-font .ql-picker-item[data-value='Arial']::before {
	font-family: Arial;
	content: 'Arial';
}
.ql-snow .ql-picker.ql-font .ql-picker-label[data-value='Times-New-Roman']::before,
.ql-snow .ql-picker.ql-font .ql-picker-item[data-value='Times-New-Roman']::before {
	font-family: 'Times New Roman';
	content: 'Times New Roman';
}
.ql-snow .ql-picker.ql-font .ql-picker-label[data-value='sans-serif']::before,
.ql-snow .ql-picker.ql-font .ql-picker-item[data-value='sans-serif']::before {
	font-family: sans-serif;
	content: 'sans-serif';
}
.ql-font-SimSun {
	font-family: SimSun;
}
.ql-font-SimHei {
	font-family: SimHei;
}
.ql-font-Microsoft-YaHei {
	font-family: 'Microsoft YaHei';
}
.ql-font-KaiTi {
	font-family: KaiTi;
}
.ql-font-FangSong {
	font-family: FangSong;
}
.ql-font-Arial {
	font-family: Arial;
}
.ql-font-Times-New-Roman {
	font-family: 'Times New Roman';
}
.ql-font-sans-serif {
	font-family: sans-serif;
}

/* 字号设置 */

/* 默认字号 */
.ql-snow .ql-picker.ql-size .ql-picker-label::before,
.ql-snow .ql-picker.ql-size .ql-picker-item::before {
	content: '字号';
}
.ql-snow .ql-picker.ql-size .ql-picker-label[data-value='12px']::before,
.ql-snow .ql-picker.ql-size .ql-picker-item[data-value='12px']::before {
	font-size: 12px;
	content: '12px';
}
.ql-size-12px {
	font-size: 12px;
}
.ql-snow .ql-picker.ql-size .ql-picker-label[data-value='14px']::before,
.ql-snow .ql-picker.ql-size .ql-picker-item[data-value='14px']::before {
	font-size: 14px;
	content: '14px';
}
.ql-size-14px {
	font-size: 14px;
}
.ql-snow .ql-picker.ql-size .ql-picker-label[data-value='16px']::before,
.ql-snow .ql-picker.ql-size .ql-picker-item[data-value='16px']::before {
	font-size: 16px;
	content: '16px';
}
.ql-size-16px {
	font-size: 16px;
}
.ql-snow .ql-picker.ql-size .ql-picker-label[data-value='18px']::before,
.ql-snow .ql-picker.ql-size .ql-picker-item[data-value='18px']::before {
	font-size: 18px;
	content: '18px';
}
.ql-size-18px {
	font-size: 18px;
}
.ql-snow .ql-picker.ql-size .ql-picker-label[data-value='20px']::before,
.ql-snow .ql-picker.ql-size .ql-picker-item[data-value='20px']::before {
	font-size: 20px;
	content: '20px';
}
.ql-size-20px {
	font-size: 20px;
}
.ql-snow .ql-picker.ql-size .ql-picker-label[data-value='22px']::before,
.ql-snow .ql-picker.ql-size .ql-picker-item[data-value='22px']::before {
	font-size: 22px;
	content: '22px';
}
.ql-size-24px {
	font-size: 24px;
}
.ql-snow .ql-picker.ql-size .ql-picker-label[data-value='24px']::before,
.ql-snow .ql-picker.ql-size .ql-picker-item[data-value='24px']::before {
	font-size: 24px;
	content: '24px';
}
.ql-snow .ql-picker.ql-size .ql-picker-label[data-value='26px']::before,
.ql-snow .ql-picker.ql-size .ql-picker-item[data-value='26px']::before {
	font-size: 26px;
	content: '26px';
}
.ql-size-26px {
	font-size: 26px;
}
.ql-snow .ql-picker.ql-size .ql-picker-label[data-value='28px']::before,
.ql-snow .ql-picker.ql-size .ql-picker-item[data-value='28px']::before {
	font-size: 28px;
	content: '28px';
}
.ql-size-28px {
	font-size: 28px;
}
.ql-snow .ql-picker.ql-size .ql-picker-label[data-value='30px']::before,
.ql-snow .ql-picker.ql-size .ql-picker-item[data-value='30px']::before {
	font-size: 30px;
	content: '30px';
}
.ql-size-30px {
	font-size: 30px;
}
.ql-snow .ql-picker.ql-size .ql-picker-label[data-value='32px']::before,
.ql-snow .ql-picker.ql-size .ql-picker-item[data-value='32px']::before {
	font-size: 32px;
	content: '32px';
}
.ql-size-32px {
	font-size: 32px;
}
.ql-snow .ql-picker.ql-size .ql-picker-label[data-value='36px']::before,
.ql-snow .ql-picker.ql-size .ql-picker-item[data-value='36px']::before {
	font-size: 36px;
	content: '36px';
}
.ql-size-36px {
	font-size: 36px;
}
.ql-snow .ql-picker.ql-size .ql-picker-label[data-value='40px']::before,
.ql-snow .ql-picker.ql-size .ql-picker-item[data-value='40px']::before {
	font-size: 40px;
	content: '40px';
}
.ql-size-40px {
	font-size: 40px;
}

(4)配置字体

// 设置字体
const fontStyle = Quill.import('attributors/style/font'); // 引入这个后会把样式写在style上
fontStyle.whitelist = [
	'SimSun', // 宋体
	'SimHei', // 黑体
	'STFANGSO', // 华文仿宋
	'Microsoft-YaHei', // 微软雅黑
	'KaiTi', // 楷体
	'FangSong', // 仿宋
	'STKAITI', // 华文楷体
	'Arial',
	'Times-New-Roman',
	'sans-serif',
];

(5)图片压缩

有时上传的图片过大,会影响运行速度及性能展示。

// 图片压缩
const compressImage = (file) => {
	return new Promise((resolve, reject) => {
		// eslint-disable-next-line no-new
		new Compressor(file, {
			quality: 0.6, // 设置压缩质量
			success(result) {
				resolve(result);
			},
			error(error) {
				reject(error);
			},
		});
	});
};

5.设置样式

<style>
.ql-container {
	height: calc(100% - 42px);
}
</style>

6.父组件的使用

以上5步为富文本的子组件内容及相关的样式文件,组件准备好之后在相应的页面及父组件中引入使用。

父组件的template中

<QuillEditor v-model="content"  />
 

父组件的script中


import QuillEditor from '@/components/QuillEditor.vue';
 
 
const content = ref('');

;