|  |  |  |  | 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;  |