| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220 |
- import React, { useState, useRef, useEffect } from 'react';
- import ReactQuill from 'react-quill';
- import 'react-quill/dist/quill.snow.css';
- import { Form } from 'antd';
- import { EyeOutlined, CloseOutlined } from '@ant-design/icons';
- // 提取纯文本工具函数
- const extractPlainText = (html: string): string => {
- if (!html) return '';
- const tempDiv = document.createElement('div');
- tempDiv.innerHTML = html;
- return tempDiv.textContent || '';
- };
- interface LinkData {
- id: string;
- originalText: string;
- url: string;
- isDeleted: boolean;
- }
- const PersistentImagePreviewEditor = () => {
- const [form] = Form.useForm();
- const [editorHtml, setEditorHtml] = useState<string>(`
- <p>系统工作人员联系方式</p>
- <p>系统工作人员联系方式表:
- <a href="http://xia0miduo.gicp.net:9000/papbtest//pdf/a2965738189226381312/a2966033634305507328/【示意图序号_a2966033634305507328_3】.jpg" target="_blank">【示意图序号_a2966033634305507328_2】</a>,
- 第二个标签 <a href="https://jkeckms.ryuiso.com/chat-bg.jpg" target="_blank">【示意图序号_a2966033634305507328_2】</a>,
- 第三个标签 <a href="https://jkeckms.ryuiso.com/chat-bg.jpg" target="_blank">【示意图序号_a2966033634305507328_2】</a>
- </p>
- `);
- const [plainText, setPlainText] = useState<string>(extractPlainText(editorHtml));
- const [linkDataList, setLinkDataList] = useState<LinkData[]>([]);
- const [activeImageUrl, setActiveImageUrl] = useState<string | null>(null);
-
- // 跟踪鼠标是否在链接或预览区上
- const [isHoveringLink, setIsHoveringLink] = useState(false);
- const [isHoveringPreview, setIsHoveringPreview] = useState(false);
-
- const editorContainerRef = useRef<HTMLDivElement>(null);
- const previewRef = useRef<HTMLDivElement>(null);
- // 初始化链接数据
- useEffect(() => {
- const parser = new DOMParser();
- const doc = parser.parseFromString(editorHtml, 'text/html');
- const links = doc.getElementsByTagName('a');
- const linksData: LinkData[] = [];
- Array.from(links).forEach((link, index) => {
- linksData.push({
- id: `link-${index}-${Date.now()}`,
- originalText: link.textContent || '',
- url: link.href,
- isDeleted: false
- });
- });
- setLinkDataList(linksData);
- }, []);
- // 处理内容变化
- const handleEditorChange = (html: string) => {
- setEditorHtml(html);
- const text = extractPlainText(html);
- setPlainText(text);
- form.setFieldsValue({ slice_text: text });
- if (linkDataList.length > 0) {
- const updatedLinks = [...linkDataList];
- updatedLinks.forEach(link => {
- const feature = link.originalText.slice(0, 5);
- link.isDeleted = !text.includes(feature);
- });
- setLinkDataList(updatedLinks);
- }
- };
- // 处理鼠标悬停链接事件
- useEffect(() => {
- if (!editorContainerRef.current) return;
- const handleMouseOverLink = (e: MouseEvent) => {
- const target = e.target as HTMLAnchorElement;
- if (target.tagName === 'A') {
- const url = target.href;
- const isKnownLink = linkDataList.some(link =>
- !link.isDeleted && url.includes(link.url)
- );
- if (isKnownLink) {
- console.log('图片地址:', url);
- setActiveImageUrl(url);
- setIsHoveringLink(true);
- }
- }
- };
- const handleMouseOutLink = () => {
- setIsHoveringLink(false);
- };
- const container = editorContainerRef.current;
- container.addEventListener('mouseover', handleMouseOverLink);
- container.addEventListener('mouseout', handleMouseOutLink);
- return () => {
- container.removeEventListener('mouseover', handleMouseOverLink);
- container.removeEventListener('mouseout', handleMouseOutLink);
- };
- }, [linkDataList]);
- // 控制预览区显示/隐藏的核心逻辑
- useEffect(() => {
- // 当鼠标既不在链接上也不在预览区上时,才隐藏预览
- if (!isHoveringLink && !isHoveringPreview) {
- setActiveImageUrl(null);
- }
- }, [isHoveringLink, isHoveringPreview]);
- // 配置编辑器(无工具栏)
- const modules = {
- toolbar: false
- };
- return (
- <Form form={form} initialValues={{ slice_text: plainText }}>
- <Form.Item
- name="slice_text"
- rules={[{ required: true, message: '内容不能为空' }]}
- >
- {/* 容器:编辑器 + 右侧图片预览区 */}
- <div style={{
- display: 'flex',
- gap: '16px',
- alignItems: 'flex-start'
- }}>
- {/* 500x500的编辑器 */}
- <div
- ref={editorContainerRef}
- style={{
- width: '500px',
- height: '500px',
- border: '1px solid #d9d9d9',
- borderRadius: '4px',
- overflow: 'hidden'
- }}
- >
- <ReactQuill
- value={editorHtml}
- onChange={handleEditorChange}
- modules={modules}
- style={{ height: '100%' }}
- placeholder="请输入内容..."
- />
- </div>
- {/* 右侧图片预览区(固定位置) */}
- {activeImageUrl && (
- <div
- ref={previewRef}
- style={{
- width: '300px',
- height: '500px',
- backgroundColor: 'white',
- borderRadius: '8px',
- boxShadow: '0 4px 16px rgba(0, 0, 0, 0.15)',
- padding: '12px',
- border: '1px solid #eee',
- display: 'flex',
- flexDirection: 'column'
- }}
- onMouseOver={() => setIsHoveringPreview(true)}
- onMouseOut={() => setIsHoveringPreview(false)}
- >
- <div style={{
- display: 'flex',
- justifyContent: 'space-between',
- alignItems: 'center',
- marginBottom: '12px',
- paddingBottom: '8px',
- borderBottom: '1px solid #f0f0f0'
- }}>
- <div style={{ display: 'flex', alignItems: 'center' }}>
- <EyeOutlined style={{ color: '#1890ff', marginRight: '8px' }} />
- <span style={{ fontSize: '14px', color: '#333' }}>图片预览</span>
- </div>
- <CloseOutlined
- style={{ color: '#999', cursor: 'pointer', fontSize: '16px' }}
- onClick={() => setActiveImageUrl(null)}
- />
- </div>
-
- <div style={{
- flex: 1,
- display: 'flex',
- alignItems: 'center',
- justifyContent: 'center',
- overflow: 'hidden'
- }}>
- <img
- src={activeImageUrl}
- alt="示意图预览"
- style={{
- maxWidth: '100%',
- maxHeight: '100%',
- objectFit: 'contain'
- }}
- onError={() => setActiveImageUrl(null)}
- />
- </div>
- </div>
- )}
- </div>
- </Form.Item>
- </Form>
- );
- };
- export default PersistentImagePreviewEditor;
|