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; // 是否启用图片粘贴自动上传 enableImagePaste?: boolean; // 图片上传进度回调 onImageUploadProgress?: (progress: number) => void; // 图片上传完成回调 onImageUploadComplete?: (url: string) => void; // 图片上传失败回调 onImageUploadError?: (error: string) => void; } const SimpleImagePasteEditor: React.FC = ({ value = '', onChange, height = 400, placeholder = '请输入内容...', disabled = false, className = '', imageUploadUrl, imageUploadHandler, enableImagePaste = true, onImageUploadProgress, onImageUploadComplete, onImageUploadError, }) => { const editorRef = useRef(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 => { 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 = /]*(src|data-image-src)=["']file:\/\/\/[^"']+["'][^>]*>/gi; // if (localImgRegex.test(e.content)) { // e.content = e.content.replace(localImgRegex, ''); // setTimeout(() => { // message.warning('检测到本地图片链接,网页无法读取本地图片。请直接复制图片文件或截图粘贴。'); // }, 0); // } // 检查base64图片并上传(原有逻辑) const imgRegex = /]*src="data:image\/[^"]*"[^>]*>/gi; const hasDataImages = imgRegex.test(e.content); if (hasDataImages) { const imgMatches = e.content.match(/]*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(``); 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 (
); }; export default SimpleImagePasteEditor;