Quill2官网 quilljs.com/docs/quicks…
官网上有用react的例子
基于例子进行代码改造
先写一个Quill组件
import Quill from 'quill';
import { v4 as uuidV4 } from 'uuid';
import ossService from '@/services/oss';
import { message } from 'antd';
import { useEffect } from 'react';
import styles from './index.less';
type QuillComponentProps = {
defaultValue: string;
onChange: (value: string) => void;
};
let quill: any = null;
const QuillComponent = (props: QuillComponentProps) => {
const { defaultValue, onChange } = props;
const toolbarOptions = {
container: [
['bold', 'italic', 'underline', 'strike'], // 加粗,斜体,下划线,删除线
['blockquote', 'code-block'], // 引用,代码块
[{ header: 1 }, { header: 2 }], // 标题,键值对的形式;1、2表示字体大小
[{ list: 'ordered' }, { list: 'bullet' }], // 列表
[{ direction: 'rtl' }], // 文本方向
[{ header: [1, 2, 3, 4, 5, 6, false] }], // 几级标题
[{ color: [] }, { background: [] }], // 字体颜色,字体背景颜色
[{ font: [] }], // 字体
[{ align: [] }], // 对齐方式
['clean'], // 清除字体样式
['image', 'video', 'link'],
], // 上传图片、上传视频 // remove formatting button
};
//富文本配置
const options = {
modules: {
toolbar: toolbarOptions,
},
placeholder: '请输入...',
theme: 'snow',
};
useEffect(() => {
quill = new Quill('#editor', options);
}, []);
useEffect(() => {
if (quill) {
if (defaultValue === '<p><br></p>') {
quill.setContents([{ insert: '' }]);
return;
}
quill.setContents(JSON.parse(defaultValue));
quill.on('text-change', (delta, oldDelta, source) => {
// 在这里处理富文本内容的变化
const content = quill.getContents();
onChange(JSON.stringify(content));
});
}
}, [quill, defaultValue]);
return (
<div className={styles.content}>
<div id="editor" />
</div>
);
};
export default QuillComponent;
自定义配置
自定义配置上传图片,视频,链接等,Quill也有详细说明 quilljs.com/docs/guides…
简单改造一下,改成ts文件
import Quill from 'quill';
const BlockEmbed = Quill.import('blots/block/embed');
class VideoBlot extends BlockEmbed {
static blotName = 'video';
static tagName = 'video';
static create(url) {
let node = super.create();
node.setAttribute('src', url);
node.setAttribute('controls', 'true');
return node;
}
static value(node: HTMLElement) {
return node.getAttribute('src');
}
}
export default VideoBlot;
上面这个是自定义视频上传,写成VideoBlot之后,需要在Quill里注册一下
Quill.register(VideoBlot);
然后在toolbarOptions里配置下handlers
handlers: {
video: () => {
//你想实现的自定义上传视频
}
}
实现文档
上传视频之后要插入文档,就要用到
注意!!
我查看Quill2官网没找到有html字符串可以变成Delta格式的方法(如果有,并且我这段话有误请指出) 只有Delta格式变成html字符串的方法 quill.root.innerHTML 然而在Quill内部运行的只支持Deltas
所以我存数据会直接存Delta
// 在这里处理富文本内容的变化
const content = quill.getContents();
onChange(JSON.stringify(content));
赋值的时候
quill.setContents(JSON.parse(defaultValue));
最后
完整代码
QuillComponent.tsx
import Quill from 'quill';
import { v4 as uuidV4 } from 'uuid';
import ossService from '@/services/oss';
import { message } from 'antd';
import { useEffect } from 'react';
import VideoBlot from '@/pages/system/guideQuill/VideoBlot';
import styles from './index.less';
type QuillComponentProps = {
defaultValue: string;
onChange: (value: string) => void;
};
let quill: any = null;
Quill.register(VideoBlot);
const QuillComponent = (props: QuillComponentProps) => {
const { defaultValue, onChange } = props;
const toolbarOptions = {
container: [
['bold', 'italic', 'underline', 'strike'], // 加粗,斜体,下划线,删除线
['blockquote', 'code-block'], // 引用,代码块
[{ header: 1 }, { header: 2 }], // 标题,键值对的形式;1、2表示字体大小
[{ list: 'ordered' }, { list: 'bullet' }], // 列表
[{ direction: 'rtl' }], // 文本方向
[{ header: [1, 2, 3, 4, 5, 6, false] }], // 几级标题
[{ color: [] }, { background: [] }], // 字体颜色,字体背景颜色
[{ font: [] }], // 字体
[{ align: [] }], // 对齐方式
['clean'], // 清除字体样式
['image', 'video', 'link'],
], // 上传图片、上传视频 // remove formatting button
handlers: {
video: () => {
let range = quill.getSelection(true);
quill.insertText(range.index, '\n', Quill.sources.USER);
//上传视频
const fileInput = document.createElement('input');
fileInput.setAttribute('type', 'file');
fileInput.setAttribute('accept', 'video/*'); // 只接受视频文件
fileInput.style.display = 'none'; // 隐藏文件输入
// 将<input>添加到文档中
document.body.appendChild(fileInput);
// 触发<input>的click事件,这会打开文件选择对话框
fileInput.click();
// 当用户选择文件后,你可以监听<input>的change事件来处理文件
fileInput.addEventListener('change', (event) => {
const file = event.target?.files[0];
if (file) {
// 这里处理选择的文件
const filePath =
'documentAttachment/wms/' + uuidV4() + file?.name.slice(file.name.indexOf('.'));
ossService.upload({ bucketACL: 'PUBLIC_READ', key: filePath }).then((uploadRes) => {
if (uploadRes?.status?.success && uploadRes?.body?.preSignedUrl) {
try {
fetch(uploadRes.body.preSignedUrl, {
method: 'PUT',
headers: {
'Content-Type': 'application/octet-stream',
},
body: file,
}).then((res) => {
if (res.status === 200) {
quill.insertEmbed(
range.index + 1,
'video',
uploadRes?.body?.preSignedUrl.split('?')[0],
Quill.sources.USER,
);
quill.formatText(range.index + 1, 1);
quill.setSelection(range.index + 2, Quill.sources.SILENT);
fileInput.remove();
}
});
} catch (e) {
message.warn('上传失败');
fileInput.remove();
}
}
});
} else {
console.log('No file selected.');
}
});
console.log('视频');
},
},
};
//富文本配置
const options = {
modules: {
toolbar: toolbarOptions,
},
placeholder: '请输入...',
theme: 'snow',
};
useEffect(() => {
quill = new Quill('#editor', options);
}, []);
useEffect(() => {
if (quill) {
if (defaultValue === '<p><br></p>') {
quill.setContents([{ insert: '' }]);
return;
}
quill.setContents(JSON.parse(defaultValue));
quill.on('text-change', (delta, oldDelta, source) => {
// 在这里处理富文本内容的变化
const content = quill.getContents();
onChange(JSON.stringify(content))
//直接得到html字符串
console.log('Content changed:', quill.root.innerHTML);
});
}
}, [quill, defaultValue]);
return (
<div className={styles.content}>
<div id="editor" />
</div>
);
};
export default QuillComponent;
VideoBlot.ts
import Quill from 'quill';
const BlockEmbed = Quill.import('blots/block/embed');
class VideoBlot extends BlockEmbed {
static blotName = 'video';
static tagName = 'video';
static create(url) {
let node = super.create();
node.setAttribute('src', url);
node.setAttribute('controls', 'true');
return node;
}
static value(node: HTMLElement) {
return node.getAttribute('src');
}
}
export default VideoBlot;
index.tsx
import 'quill/dist/quill.snow.css';
import QuillComponent from './QuillComponent';
import { useEffect, useState } from 'react';
const RichText = () => {
const [detailInfo, setDetailInfo] = useState<any>();
const [currentHtml, setCurrentHtml] = useState('');
useEffect(() => {
//通过接口拿到数据
setDetailInfo({ content: '' });
}, []);
return (
<div>
<QuillComponent
defaultValue={detailInfo?.content || '123'}
onChange={(val) => {
setCurrentHtml(val);
}}
/>
</div>
);
};
export default RichText;
index.less 做了个操作栏的固定
.content {
:global {
.ql-toolbar .ql-snow {
position: fixed !important;
}
}
}
本文皆原创,如需转发请告知一声喔! over...