|
|
import React, { useCallback, useEffect, useRef } from 'react';
|
|
|
import { Editor } from '@tinymce/tinymce-react';
|
|
|
import { defaultConfig, wordPasteProcessor } from './config';
|
|
|
import { message } from 'antd';
|
|
|
|
|
|
interface SimpleImagePasteEditorProps {
|
|
|
value?: string;
|
|
|
onChange?: (value: string) => void;
|
|
|
height?: number;
|
|
|
placeholder?: string;
|
|
|
disabled?: boolean;
|
|
|
className?: string;
|
|
|
// 图片上传配置
|
|
|
imageUploadUrl?: string;
|
|
|
imageUploadHandler?: (blobInfo: any, progress: any, failure: any) => Promise<string>;
|
|
|
// 是否启用图片粘贴自动上传
|
|
|
enableImagePaste?: boolean;
|
|
|
// 图片上传进度回调
|
|
|
onImageUploadProgress?: (progress: number) => void;
|
|
|
// 图片上传完成回调
|
|
|
onImageUploadComplete?: (url: string) => void;
|
|
|
// 图片上传失败回调
|
|
|
onImageUploadError?: (error: string) => void;
|
|
|
}
|
|
|
|
|
|
const SimpleImagePasteEditor: React.FC<SimpleImagePasteEditorProps> = ({
|
|
|
value = '',
|
|
|
onChange,
|
|
|
height = 400,
|
|
|
placeholder = '请输入内容...',
|
|
|
disabled = false,
|
|
|
className = '',
|
|
|
imageUploadUrl,
|
|
|
imageUploadHandler,
|
|
|
enableImagePaste = true,
|
|
|
onImageUploadProgress,
|
|
|
onImageUploadComplete,
|
|
|
onImageUploadError,
|
|
|
}) => {
|
|
|
const editorRef = useRef<any>(null);
|
|
|
|
|
|
const handleEditorChange = useCallback(
|
|
|
(content: string) => {
|
|
|
if (onChange) {
|
|
|
onChange(content);
|
|
|
}
|
|
|
},
|
|
|
[onChange],
|
|
|
);
|
|
|
|
|
|
const handleEditorInit = useCallback((evt: any, editor: any) => {
|
|
|
editorRef.current = editor;
|
|
|
}, []);
|
|
|
|
|
|
useEffect(() => {
|
|
|
if (editorRef.current && value !== editorRef.current.getContent()) {
|
|
|
editorRef.current.setContent(value);
|
|
|
}
|
|
|
}, [value]);
|
|
|
|
|
|
// 自定义图片上传处理函数
|
|
|
const customImageUploadHandler = useCallback((blobInfo: any, progress: any, failure: any): Promise<string> => {
|
|
|
if (imageUploadHandler) {
|
|
|
return imageUploadHandler(blobInfo, progress, failure);
|
|
|
}
|
|
|
return new Promise((resolve, reject) => {
|
|
|
const xhr = new XMLHttpRequest();
|
|
|
const fd = new FormData();
|
|
|
xhr.upload.addEventListener('progress', (e) => {
|
|
|
const progressPercent = (e.loaded / e.total) * 100;
|
|
|
progress(progressPercent);
|
|
|
onImageUploadProgress?.(progressPercent);
|
|
|
});
|
|
|
xhr.addEventListener('load', () => {
|
|
|
if (xhr.status === 200) {
|
|
|
try {
|
|
|
const response = JSON.parse(xhr.responseText);
|
|
|
if (response.retcode) {
|
|
|
onImageUploadComplete?.(response.data);
|
|
|
resolve(response.data);
|
|
|
} else {
|
|
|
const errorMsg = '上传失败';
|
|
|
onImageUploadError?.(errorMsg);
|
|
|
reject(errorMsg);
|
|
|
}
|
|
|
} catch (e) {
|
|
|
const errorMsg = '响应解析失败';
|
|
|
onImageUploadError?.(errorMsg);
|
|
|
reject(errorMsg);
|
|
|
}
|
|
|
} else {
|
|
|
const errorMsg = '上传失败';
|
|
|
onImageUploadError?.(errorMsg);
|
|
|
reject(errorMsg);
|
|
|
}
|
|
|
});
|
|
|
xhr.addEventListener('error', () => {
|
|
|
const errorMsg = '网络错误';
|
|
|
onImageUploadError?.(errorMsg);
|
|
|
reject(errorMsg);
|
|
|
});
|
|
|
fd.append('file', blobInfo.blob(), blobInfo.filename());
|
|
|
xhr.open('POST', imageUploadUrl || process.env.BASE_URL + '/oss/imgUpload');
|
|
|
xhr.send(fd);
|
|
|
});
|
|
|
}, [imageUploadHandler, imageUploadUrl, onImageUploadProgress, onImageUploadComplete, onImageUploadError]);
|
|
|
|
|
|
// 处理粘贴事件,自动上传图片
|
|
|
const handlePastePreProcess = useCallback((e: any) => {
|
|
|
// 先处理Word粘贴
|
|
|
e.content = wordPasteProcessor(e.content);
|
|
|
if (!enableImagePaste) return;
|
|
|
// 检查并移除本地图片链接
|
|
|
// const localImgRegex = /<img[^>]*(src|data-image-src)=["']file:\/\/\/[^"']+["'][^>]*>/gi;
|
|
|
// if (localImgRegex.test(e.content)) {
|
|
|
// e.content = e.content.replace(localImgRegex, '');
|
|
|
// setTimeout(() => {
|
|
|
// message.warning('检测到本地图片链接,网页无法读取本地图片。请直接复制图片文件或截图粘贴。');
|
|
|
// }, 0);
|
|
|
// }
|
|
|
// 检查base64图片并上传(原有逻辑)
|
|
|
const imgRegex = /<img[^>]*src="data:image\/[^"]*"[^>]*>/gi;
|
|
|
const hasDataImages = imgRegex.test(e.content);
|
|
|
if (hasDataImages) {
|
|
|
const imgMatches = e.content.match(/<img[^>]*src="(data:image\/[^"]*)"[^>]*>/gi);
|
|
|
if (imgMatches) {
|
|
|
const uploadPromises = imgMatches.map(async (imgTag: string) => {
|
|
|
const srcMatch = imgTag.match(/src="(data:image\/[^"]*)"/);
|
|
|
if (srcMatch) {
|
|
|
const dataUrl = srcMatch[1];
|
|
|
try {
|
|
|
const response = await fetch(dataUrl);
|
|
|
const blob = await response.blob();
|
|
|
const blobInfo = {
|
|
|
blob: () => blob,
|
|
|
filename: () => `pasted-image-${Date.now()}.png`,
|
|
|
};
|
|
|
const uploadedUrl = await customImageUploadHandler(
|
|
|
blobInfo,
|
|
|
(progress: number) => { console.log('图片上传进度:', progress); },
|
|
|
(error: string) => { console.error('图片上传失败:', error); }
|
|
|
);
|
|
|
return e.content.replace(dataUrl, uploadedUrl);
|
|
|
} catch (error) {
|
|
|
console.error('图片处理失败:', error);
|
|
|
return e.content;
|
|
|
}
|
|
|
}
|
|
|
return e.content;
|
|
|
});
|
|
|
Promise.all(uploadPromises).then((results) => {
|
|
|
if (results.length > 0) {
|
|
|
e.content = results[0];
|
|
|
}
|
|
|
});
|
|
|
}
|
|
|
}
|
|
|
}, [enableImagePaste, customImageUploadHandler]);
|
|
|
|
|
|
// setup增强,优先处理剪贴板图片文件
|
|
|
const initConfig = {
|
|
|
...defaultConfig,
|
|
|
height,
|
|
|
placeholder,
|
|
|
setup: (editor: any) => {
|
|
|
editor.on('PastePreProcess', handlePastePreProcess);
|
|
|
// 监听原生paste事件,优先处理clipboardData图片
|
|
|
editor.on('paste', async (event: ClipboardEvent) => {
|
|
|
if (!enableImagePaste) return;
|
|
|
if (!event.clipboardData) return;
|
|
|
const items = event.clipboardData.items;
|
|
|
console.log(items)
|
|
|
for (let i = 0; i < items.length; i++) {
|
|
|
const item = items[i];
|
|
|
console.log(item)
|
|
|
if (item.kind === 'file' && item.type.startsWith('image/')) {
|
|
|
event.preventDefault();
|
|
|
const file = item.getAsFile();
|
|
|
if (file) {
|
|
|
const blobInfo = {
|
|
|
blob: () => file,
|
|
|
filename: () => `clipboard-image-${Date.now()}.${file.type.split('/')[1] || 'png'}`,
|
|
|
};
|
|
|
try {
|
|
|
const uploadedUrl = await customImageUploadHandler(
|
|
|
blobInfo,
|
|
|
(progress: number) => { console.log('图片上传进度:', progress); },
|
|
|
(error: string) => { console.error('图片上传失败:', error); }
|
|
|
);
|
|
|
editor.insertContent(`<img src="${uploadedUrl}" />`);
|
|
|
message.success('图片粘贴上传成功');
|
|
|
} catch (err) {
|
|
|
message.error('图片粘贴上传失败');
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
})
|
|
|
},
|
|
|
images_upload_handler: customImageUploadHandler,
|
|
|
paste_data_images: true,
|
|
|
images_file_types: 'jpeg,jpg,png,gif,webp',
|
|
|
images_upload_max_size: 5 * 1024 * 1024,
|
|
|
};
|
|
|
|
|
|
return (
|
|
|
<div className={className}>
|
|
|
<Editor
|
|
|
apiKey="acps5w8zgbvzyh1vn9y63oh6wa89n43ujdvswrz1ckjx8pi6"
|
|
|
onInit={handleEditorInit}
|
|
|
value={value}
|
|
|
onEditorChange={handleEditorChange}
|
|
|
disabled={disabled}
|
|
|
init={initConfig}
|
|
|
/>
|
|
|
</div>
|
|
|
);
|
|
|
};
|
|
|
|
|
|
export default SimpleImagePasteEditor;
|