基于ReactJS实现类Dialogflow的文本选中标注弹窗功能
React实现文本选中标注关联实体功能(仿Dialogflow效果)
核心需求拆解
实现类似Dialogflow的文本标注功能:选中句子中的目标文本片段后,在选中区域附近弹出弹窗,选择对应实体(如sys.date、sys.time)后,该文本会被标记为关联实体。
实现步骤与代码示例
1. 文本选中监听与区域定位
首先监听用户的文本选中操作,获取选中内容和位置,用于弹窗定位:
import { useState, useEffect, useRef } from 'react'; const TextAnnotator = () => { const textRef = useRef(null); const [selectedText, setSelectedText] = useState(''); const [popupPosition, setPopupPosition] = useState({ top: 0, left: 0, show: false }); const [annotations, setAnnotations] = useState([]); // 监听文本选中事件 useEffect(() => { const handleSelection = () => { const selection = window.getSelection(); if (!selection || selection.isCollapsed) { setPopupPosition(prev => ({ ...prev, show: false })); return; } const selectedContent = selection.toString().trim(); if (!selectedContent) return; // 获取选中区域的位置信息 const range = selection.getRangeAt(0); const rect = range.getBoundingClientRect(); setSelectedText(selectedContent); setPopupPosition({ top: rect.bottom + window.scrollY + 8, left: rect.left + window.scrollX, show: true }); }; window.addEventListener('selectionchange', handleSelection); return () => window.removeEventListener('selectionchange', handleSelection); }, []);
2. 弹窗渲染与实体关联逻辑
弹窗展示可选实体列表,点击实体后将选中文本与实体关联,更新标注状态:
// 关联实体的处理函数 const handleEntitySelect = (entity) => { // 避免重复标注相同文本片段 const existingAnnotation = annotations.find(anno => anno.text === selectedText); if (existingAnnotation) return; setAnnotations(prev => [...prev, { text: selectedText, entity }]); setPopupPosition(prev => ({ ...prev, show: false })); window.getSelection().removeAllRanges(); }; // 渲染带标注的文本 const renderAnnotatedText = () => { let text = "I will arrive tomorrow at 5pm"; annotations.forEach(anno => { const highlighted = `<span style="background: #ffeb3b; padding: 2px 4px; border-radius: 3px;" title="${anno.entity}">${anno.text}</span>`; text = text.replace(new RegExp(anno.text, 'g'), highlighted); }); return <div ref={textRef} dangerouslySetInnerHTML={{ __html: text }} />; };
3. 完整组件渲染
将文本区域和弹窗整合到组件中:
return ( <div style={{ padding: '20px', position: 'relative' }}> <h3>待标注文本</h3> {renderAnnotatedText()} {/* 实体选择弹窗 */} {popupPosition.show && ( <div style={{ position: 'absolute', top: popupPosition.top, left: popupPosition.left, background: '#fff', border: '1px solid #ddd', borderRadius: '4px', boxShadow: '0 2px 8px rgba(0,0,0,0.15)', padding: '8px 0', minWidth: '150px' }} > <div style={{ padding: '6px 12px', cursor: 'pointer' }} onClick={() => handleEntitySelect('sys.date')}> @sys.date </div> <div style={{ padding: '6px 12px', cursor: 'pointer' }} onClick={() => handleEntitySelect('sys.time')}> @sys.time </div> </div> )} </div> ); }; export default TextAnnotator;
关键细节说明
- 文本渲染:使用
dangerouslySetInnerHTML处理带标注的文本,实际项目中可考虑用更安全的文本拆分拼接方式,避免XSS风险。 - 选区处理:监听
selectionchange事件覆盖全局选中操作,若仅需监听特定文本区域,可在事件中判断选区是否属于目标元素。 - 弹窗定位:通过
getBoundingClientRect()获取选中区域的绝对位置,结合window.scrollY/X处理滚动偏移。
内容的提问来源于stack exchange,提问作者Bhavika Patel
相关产品推荐
相关产品推荐

