93 lines
2.3 KiB
TypeScript
93 lines
2.3 KiB
TypeScript
'use client';
|
||
|
||
import dynamic from 'next/dynamic';
|
||
import { useMemo } from 'react';
|
||
import { cn } from '@/lib/utils';
|
||
|
||
// quill 主题样式(client 组件可直接 import)
|
||
import 'react-quill/dist/quill.snow.css';
|
||
|
||
// react-quill 仅支持客户端渲染
|
||
const ReactQuill = dynamic(() => import('react-quill'), {
|
||
ssr: false,
|
||
loading: () => (
|
||
<div className="flex h-64 items-center justify-center rounded bg-gray-50 text-xs text-gray-400">
|
||
编辑器加载中…
|
||
</div>
|
||
),
|
||
});
|
||
|
||
export interface RichEditorProps {
|
||
value?: string;
|
||
onChange: (html: string) => void;
|
||
placeholder?: string;
|
||
className?: string;
|
||
}
|
||
|
||
const TOOLBAR = [
|
||
[{ header: [1, 2, 3, 4, 5, 6, false] }],
|
||
['bold', 'italic', 'underline', 'strike'],
|
||
[{ color: [] }, { background: [] }],
|
||
[{ align: [] }, { list: 'ordered' }, { list: 'bullet' }],
|
||
['blockquote', 'code-block'],
|
||
['link', 'image'],
|
||
['clean'],
|
||
];
|
||
|
||
// 图片插入:转 base64 内联(如需对接后端上传,可在此处替换实现)
|
||
// quill 的 toolbar handler this 指向 toolbar 实例,需要 any
|
||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||
function imageHandler(this: any): void {
|
||
const quill = this?.quill;
|
||
if (!quill) return;
|
||
const input = document.createElement('input');
|
||
input.setAttribute('type', 'file');
|
||
input.setAttribute('accept', 'image/*');
|
||
input.addEventListener('change', () => {
|
||
const file = input.files?.[0];
|
||
if (!file) return;
|
||
const reader = new FileReader();
|
||
reader.onload = () => {
|
||
const range = quill.getSelection();
|
||
const idx = range ? range.index : 0;
|
||
quill.insertEmbed(idx, 'image', String(reader.result));
|
||
};
|
||
reader.readAsDataURL(file);
|
||
});
|
||
input.click();
|
||
}
|
||
|
||
export function RichEditor({
|
||
value,
|
||
onChange,
|
||
placeholder = '请输入内容…',
|
||
className,
|
||
}: RichEditorProps) {
|
||
const modules = useMemo(
|
||
() => ({
|
||
toolbar: {
|
||
container: TOOLBAR,
|
||
handlers: { image: imageHandler },
|
||
},
|
||
}),
|
||
[],
|
||
);
|
||
|
||
return (
|
||
<div
|
||
className={cn(
|
||
'overflow-hidden rounded-md border border-gray-300 bg-white',
|
||
className,
|
||
)}
|
||
>
|
||
<ReactQuill
|
||
theme="snow"
|
||
value={value ?? ''}
|
||
onChange={onChange}
|
||
modules={modules}
|
||
placeholder={placeholder}
|
||
/>
|
||
</div>
|
||
);
|
||
}
|