文章参考:
https://blog.csdn.net/benlalagang/article/details/127783044
按照教程使用
1、字体字号等自定义的样式不生效,一直是同样的
可以试一下写在scoped外或者加上v-deep使用
我用了后者,使用这个注意看一下类名是否匹配,我写的时候要减少一层外侧类名才生效:
即比如原来是:
.父级 .子级 .再子级 {
}
可能要写成
.子级 .再子级 {
}
2、页面如果要根据编辑器生成的返回值渲染格式的话
如果用vue的话可以使用v-html
编辑器生成值:content = ‘
编辑器生成的html格式的字符串
’ 就可以按照html标签渲染到页面上了**
3、如果只想保留换行格式的编辑器返回值
编辑器提供的change事件的回调函数的第三个参数是只保留了换行格式的文本,返回格式是:“内容\n”,我在页面使用的时候就直接把\n替换成<br>
了
**4 如果想去掉全部格式,只保留文本内容的话
可以使用正则表达式直接将返回值里面的标签删除
正则表达式:
content.replace(/<[^>]*>/g, “”).trim().replace(‘">’, “”)
文章参考:https://blog.csdn.net/qq_47888106/article/details/133771594
**
缺点:对于一些特殊符号,在获取内容的时候会变成html的编码格式,所以统计字数会比看到的多
更好的推荐:vue-quill本身有提供一个getText
方法用于统计内容字数,可以直接用这个
例如:编辑器实例.getText.(0, 10)
文章参考:
https://quilljs.com/docs/api#gettext
**注意:**为空时默认保留一个空行,每一行都会算作一个字符,所以默认空的时候长度计算为1,可以通过trim()方法得到正确的长度,此时空格也会算一个字,如果想去掉空格,可以使用.replace(/\s/g, ‘’)正则替换掉,再计算长度即可
5、对图片的处理(这里是vue3的写法),base64传到后端服务器得到新地址再使用
function convertBase64UrlToFile(base64: string, fileName = '图片'): File {
// 将base64按照 , 进行分割 将前缀 与后续内容分隔开
let data = base64.split(',');
// 利用正则表达式 从前缀中获取图片的类型信息(image/png、image/jpeg、image/webp等)
let type = data[0].match(/:(.*?);/)?.[1];
// 从图片的类型信息中 获取具体的文件格式后缀(png、jpeg、webp)
let suffix = type?.split('/')[1];
// 使用atob()对base64数据进行解码 结果是一个文件数据流 以字符串的格式输出
const bstr = window.atob(data[1]);
// 获取解码结果字符串的长度
let n = bstr.length;
// 根据解码结果字符串的长度创建一个等长的整形数字数组
// 但在创建时 所有元素初始值都为 0
const u8arr = new Uint8Array(n);
// 将整形数组的每个元素填充为解码结果字符串对应位置字符的UTF-16 编码单元
while (n--) {
// charCodeAt():获取给定索引处字符对应的 UTF-16 代码单元
u8arr[n] = bstr.charCodeAt(n);
}
const file = new File([u8arr], `${fileName}.${suffix}`, {
type: type,
});
// 将File文件对象返回给方法的调用者
return file;
}
// 有图片的时候要上传图片 content:富文本编辑器的内容
const handleImgFormat = () => {
// 拿出所有的img
const imgTags = content.value.match(/<img[^>]*>/g) || [];
// 匹配base64的图片,上下两个其实也可以用一个通用正则匹配就好
let imgReg = RegExp(/data:image\/.*;base64,/);
// 拿到图片里还是base64格式的图片
const base64Imgs = imgTags.filter((i) => imgReg.test(i));
if (base64Imgs.length) {
const promises = imgTags.map((item) => {
return new Promise((resolve, reject) => {
const base64Data = item.match(/src="([^"]+)"/)?.[1];
if (base64Data) {
// base64格式转File(这里是我需要用File格式转到后端接口)
const fileData = convertBase64UrlToFile(base64Data);
//
const data =把图片传到后端接口得到新地址的接口调用.then((url) => {
// 替换原来的地址为拿到的新地址
content.value = content.value.replace(base64Data, url || '');
return url;
});
resolve(data);
}
});
});
return Promise.all(promises);
} else {
return new Promise((resolve, reject) => {
resolve('');
});
}
};
PS:此时能想到的做法是在调用传富文本内容前先把图片上传了再调用存储富文本内容的接口
vue3用法
文章参考:vue3的富文本编辑器下载使用方法【vue2的好像是不太适用,所以要换一个】
较完整代码:
import { Quill, QuillEditor } from '@vueup/vue-quill';
import '@vueup/vue-quill/dist/vue-quill.snow.css';
import '@vueup/vue-quill/dist/vue-quill.core.css';
import '@vueup/vue-quill/dist/vue-quill.bubble.css';
const fontSize = ['12px', false, '16px', '18px', '20px', '24px', '28px', '32px', '36px'];
Quill.imports['attributors/style/size'].whitelist = fontSize;
Quill.register(Quill.imports['attributors/style/size']);
const fontFamily = ['SimSun', 'SimHei', 'KaiTi', 'FangSong'];
const Font = Quill.import('attributors/style/font');
Font.whitelist = fontFamily;
Quill.register(Font, true);
const titleConfig = [
{ Choice: '.ql-insertMetric', title: '跳转配置' },
{ Choice: '.ql-bold', title: '加粗' },
{ Choice: '.ql-italic', title: '斜体' },
{ Choice: '.ql-underline', title: '下划线' },
{ Choice: '.ql-header', title: '段落格式' },
{ Choice: '.ql-strike', title: '删除线' },
{ Choice: '.ql-blockquote', title: '块引用' },
{ Choice: '.ql-code', title: '插入代码' },
{ Choice: '.ql-code-block', title: '插入代码段' },
{ Choice: '.ql-font', title: '字体' },
{ Choice: '.ql-size', title: '字体大小' },
{ Choice: '.ql-list[value="ordered"]', title: '编号列表' },
{ Choice: '.ql-list[value="bullet"]', title: '项目列表' },
{ Choice: '.ql-direction', title: '文本方向' },
{ Choice: '.ql-header[value="1"]', title: 'h1' },
{ Choice: '.ql-header[value="2"]', title: 'h2' },
{ Choice: '.ql-align', title: '对齐方式' },
{ Choice: '.ql-color', title: '字体颜色' },
{ Choice: '.ql-background', title: '背景颜色' },
{ Choice: '.ql-image', title: '图像' },
{ Choice: '.ql-video', title: '视频' },
{ Choice: '.ql-link', title: '添加链接' },
{ Choice: '.ql-formula', title: '插入公式' },
{ Choice: '.ql-clean', title: '清除字体格式' },
{ Choice: '.ql-script[value="sub"]', title: '下标' },
{ Choice: '.ql-script[value="super"]', title: '上标' },
{ Choice: '.ql-indent[value="-1"]', title: '向左缩进' },
{ Choice: '.ql-indent[value="+1"]', title: '向右缩进' },
{ Choice: '.ql-header .ql-picker-label', title: '标题大小' },
{ Choice: '.ql-header .ql-picker-item[data-value="1"]', title: '标题一' },
{ Choice: '.ql-header .ql-picker-item[data-value="2"]', title: '标题二' },
{ Choice: '.ql-header .ql-picker-item[data-value="3"]', title: '标题三' },
{ Choice: '.ql-header .ql-picker-item[data-value="4"]', title: '标题四' },
{ Choice: '.ql-header .ql-picker-item[data-value="5"]', title: '标题五' },
{ Choice: '.ql-header .ql-picker-item[data-value="6"]', title: '标题六' },
{ Choice: '.ql-header .ql-picker-item:last-child', title: '标准' },
{ Choice: '.ql-size .ql-picker-item[data-value="small"]', title: '小号' },
{ Choice: '.ql-size .ql-picker-item[data-value="large"]', title: '大号' },
{ Choice: '.ql-size .ql-picker-item[data-value="huge"]', title: '超大号' },
{ Choice: '.ql-size .ql-picker-item:nth-child(2)', title: '标准' },
{ Choice: '.ql-align .ql-picker-item:first-child', title: '居左对齐' },
{ Choice: '.ql-align .ql-picker-item[data-value="center"]', title: '居中对齐' },
{ Choice: '.ql-align .ql-picker-item[data-value="right"]', title: '居右对齐' },
{ Choice: '.ql-align .ql-picker-item[data-value="justify"]', title: '两端对齐' },
];
// 工具栏配置项
const editorOption = {
theme: 'snow',
modules: {
toolbar: [
// 加粗 斜体 下划线 删除线
['bold', 'italic', 'underline', 'strike'],
// 引用 代码块
['blockquote', 'code-block'],
// 1、2 级标题
[{ header: 1 }, { header: 2 }],
// 有序、无序列表
[{ list: 'ordered' }, { list: 'bullet' }],
// 上标/下标
[{ script: 'sub' }, { script: 'super' }],
// 缩进
[{ indent: '-1' }, { indent: '+1' }],
// 图片
['image'],
// 文本方向
[{ direction: 'rtl' }],
// 字体大小
[{ size: fontSize }],
// 字体
[{ font: fontFamily }],
// 标题
[{ header: [1, 2, 3, 4, 5, 6] }],
// 字体颜色、字体背景颜色
[{ color: [] }, { background: [] }],
// 对齐方式
[{ align: [] }],
// 清除文本格式
['clean'],
],
},
placeholder: '请输入正文',
};
const initTitle = () => {
// 这个没生效就没用
// document.getElementsByClassName('ql-editor')[0].dataset.placeholder = '';
for (const item of titleConfig) {
const tip = document.querySelector(`.ql-toolbar ${item.Choice}`);
if (!tip) {
continue;
}
tip.setAttribute('title', item.title);
}
};
onMounted(() => {
initTitle();
});
function convertBase64UrlToFile(见上)
const handleImgFormat(见上)
<template>
// 样式要自己调整一下,这里只是部分好像
<div class="quill-editor-class h-100% border-1 border-#d7d7d7 border-solid">
<QuillEditor v-model:content="content" contentType="html" :options="editorOption" v-bind="attrs" />
</div>
</template>
<style lang="scss" scoped>
// 样式好像不完全,要自己调整一下
.quill-editor-class {
display: flex;
flex-direction: column;
:deep() {
.ql-container {
height: 1px;
flex: 1;
}
.ql-size .ql-picker-label[data-value='12px']::before,
.ql-size .ql-picker-item[data-value='12px']::before {
content: '12px';
}
.ql-size .ql-picker-label[data-value='14px']::before,
.ql-size .ql-picker-item[data-value='14px']::before,
.ql-size .ql-picker-item:not([data-value])::before {
content: '14px';
}
.ql-size .ql-picker-label:not([data-value])::before {
content: '14px';
}
.ql-size .ql-picker-label[data-value='16px']::before,
.ql-size .ql-picker-item[data-value='16px']::before {
content: '16px';
}
.ql-size .ql-picker-label[data-value='18px']::before,
.ql-size .ql-picker-item[data-value='18px']::before {
content: '18px';
}
.ql-size .ql-picker-label[data-value='20px']::before,
.ql-size .ql-picker-item[data-value='20px']::before {
content: '20px';
}
.ql-size .ql-picker-label[data-value='24px']::before,
.ql-size .ql-picker-item[data-value='24px']::before {
content: '24px';
}
.ql-size .ql-picker-label[data-value='28px']::before,
.ql-size .ql-picker-item[data-value='28px']::before {
content: '28px';
}
.ql-size .ql-picker-label[data-value='32px']::before,
.ql-size .ql-picker-item[data-value='32px']::before {
content: '32px';
}
.ql-size .ql-picker-label[data-value='36px']::before,
.ql-size .ql-picker-item[data-value='36px']::before {
content: '36px';
}
.ql-font .ql-picker-label[data-value='SimSun']::before,
.ql-font .ql-picker-item[data-value='SimSun']::before {
content: '宋体';
}
.ql-font .ql-picker-label[data-value='SimHei']::before,
.ql-font .ql-picker-item[data-value='SimHei']::before {
content: '黑体';
}
.ql-font .ql-picker-label[data-value='KaiTi']::before,
.ql-font .ql-picker-item[data-value='KaiTi']::before {
content: '楷体';
}
.ql-font .ql-picker-label[data-value='FangSong']::before,
.ql-font .ql-picker-item[data-value='FangSong']::before {
content: '仿宋';
}
}
}
</style>