import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:flutter_lucide/flutter_lucide.dart'; import '../../models/element.dart'; import '../../models/annotation.dart'; import '../../theme/app_colors.dart'; /// 增强的文本编辑器组件 class EnhancedTextEditor extends StatefulWidget { final String text; final List elements; final List annotations; final Function(String selectedText)? onTextSelected; final Function(Annotation annotation)? onAnnotationAction; const EnhancedTextEditor({ Key? key, required this.text, required this.elements, this.annotations = const [], this.onTextSelected, this.onAnnotationAction, }) : super(key: key); @override State createState() => _EnhancedTextEditorState(); } class _EnhancedTextEditorState extends State { String? _selectedText; String _defaultSelectedText() { final t = widget.text.trim(); if (t.isEmpty) return ''; String firstLine = t.split('\n').first; // 切第一句 if (firstLine.contains('。')) { firstLine = firstLine.split('。').first; } if (firstLine.length > 40) { firstLine = firstLine.substring(0, 40); } return firstLine; } @override Widget build(BuildContext context) { final String displaySelected = (_selectedText != null && _selectedText!.isNotEmpty) ? _selectedText! : _defaultSelectedText(); return Column( mainAxisSize: MainAxisSize.min, crossAxisAlignment: CrossAxisAlignment.stretch, children: [ // 固定显示的操作工具栏(无选择时使用默认文本) Container( margin: const EdgeInsets.only(bottom: 12), padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 8), decoration: BoxDecoration( color: AppColors.primary.withOpacity(0.05), borderRadius: BorderRadius.circular(8), border: Border.all(color: AppColors.primary.withOpacity(0.2)), ), child: Row( children: [ Expanded( child: Text( '选中: "$displaySelected"', style: TextStyle( fontSize: 13, color: AppColors.textPrimary, fontWeight: FontWeight.w500, ), maxLines: 1, overflow: TextOverflow.ellipsis, ), ), const SizedBox(width: 8), _buildToolbarButton( icon: LucideIcons.highlighter, label: '标记为要素', onTap: () { if (displaySelected.isNotEmpty) { widget.onTextSelected?.call(displaySelected); } }, ), const SizedBox(width: 4), _buildToolbarButton( icon: LucideIcons.spell_check, label: '检查拼写', onTap: () {}, ), const SizedBox(width: 4), _buildToolbarButton( icon: LucideIcons.sparkles, label: 'AI润色', onTap: () {}, ), const SizedBox(width: 4), _buildToolbarButton( icon: LucideIcons.copy, label: '复制', onTap: () { if (displaySelected.isNotEmpty) { Clipboard.setData(ClipboardData(text: displaySelected)); } }, ), ], ), ), // 文本内容 GestureDetector( onTap: () {}, child: SingleChildScrollView( child: SelectableText.rich( TextSpan( children: _buildTextSpans(), style: const TextStyle( fontSize: 15, height: 1.8, color: AppColors.textPrimary, ), ), onSelectionChanged: (selection, cause) { if (selection.isValid && !selection.isCollapsed) { final text = widget.text.substring( selection.start, selection.end, ); setState(() { _selectedText = text; }); } }, ), ), ), ], ); } Widget _buildToolbarButton({ required IconData icon, required String label, required VoidCallback onTap, }) { return Material( color: Colors.transparent, child: InkWell( onTap: onTap, borderRadius: BorderRadius.circular(6), child: Container( padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 6), child: Row( mainAxisSize: MainAxisSize.min, children: [ Icon(icon, size: 14, color: AppColors.primary), const SizedBox(width: 4), Text( label, style: TextStyle( fontSize: 12, color: AppColors.primary, fontWeight: FontWeight.w500, ), ), ], ), ), ), ); } List _buildTextSpans() { final spans = []; final text = widget.text; int lastIndex = 0; // 合并要素和批注,按位置排序 final allMarkers = <_TextMarker>[]; // 添加要素标记 for (final element in widget.elements) { final index = text.indexOf(element.value, lastIndex); if (index != -1) { allMarkers.add(_TextMarker( start: index, end: index + element.value.length, type: _MarkerType.element, element: element, )); } } // 添加批注标记 for (final annotation in widget.annotations) { final index = text.indexOf(annotation.text); if (index != -1) { allMarkers.add(_TextMarker( start: index, end: index + annotation.text.length, type: _MarkerType.annotation, annotation: annotation, )); } } // 按位置排序 allMarkers.sort((a, b) => a.start.compareTo(b.start)); // 构建文本片段 for (final marker in allMarkers) { // 添加标记前的文本 if (marker.start > lastIndex) { spans.add(TextSpan(text: text.substring(lastIndex, marker.start))); } // 添加标记文本 if (marker.type == _MarkerType.element) { final element = marker.element!; final color = _getElementColor(element.type); spans.add( TextSpan( children: [ WidgetSpan( alignment: PlaceholderAlignment.middle, child: Container( margin: const EdgeInsets.only(right: 4), padding: const EdgeInsets.symmetric(horizontal: 6, vertical: 2), decoration: BoxDecoration( color: color.withOpacity(0.15), borderRadius: BorderRadius.circular(4), border: Border.all(color: color.withOpacity(0.4)), ), child: Text( element.label, style: TextStyle( fontSize: 10, fontWeight: FontWeight.w600, color: color, ), ), ), ), TextSpan( text: element.value, style: TextStyle( backgroundColor: color.withOpacity(0.2), color: color, fontWeight: FontWeight.w600, decoration: TextDecoration.underline, decorationColor: color, ), mouseCursor: SystemMouseCursors.click, ), ], ), ); } else if (marker.type == _MarkerType.annotation) { final annotation = marker.annotation!; spans.add(_buildAnnotationSpan(annotation)); } lastIndex = marker.end; } // 添加剩余文本 if (lastIndex < text.length) { spans.add(TextSpan(text: text.substring(lastIndex))); } return spans.isEmpty ? [TextSpan(text: text)] : spans; } TextSpan _buildAnnotationSpan(Annotation annotation) { switch (annotation.type) { case AnnotationType.highlight: // 高亮显示 return TextSpan( text: annotation.text, style: TextStyle( backgroundColor: AppColors.primary.withOpacity(0.2), color: AppColors.primary, fontWeight: FontWeight.w500, ), ); case AnnotationType.strikethrough: // 错别字:红色删除线 + 正确词提示 return TextSpan( text: annotation.text, style: const TextStyle( color: AppColors.error, decoration: TextDecoration.lineThrough, decorationColor: AppColors.error, decorationThickness: 2, ), children: annotation.suggestion != null ? [ const TextSpan(text: ' '), TextSpan( text: annotation.suggestion!, style: TextStyle( color: AppColors.success, backgroundColor: AppColors.success.withOpacity(0.1), fontWeight: FontWeight.w600, ), ), ] : null, ); case AnnotationType.suggestion: // AI润色建议:浅蓝色背景 return TextSpan( text: annotation.text, style: TextStyle( backgroundColor: AppColors.info.withOpacity(0.15), color: AppColors.info, fontStyle: FontStyle.italic, ), children: annotation.suggestion != null ? [ const TextSpan(text: ' '), TextSpan( text: '💡 ${annotation.suggestion!}', style: TextStyle( fontSize: 12, color: AppColors.info, fontWeight: FontWeight.w500, backgroundColor: AppColors.info.withOpacity(0.2), ), ), ] : null, ); } } Color _getElementColor(ElementType type) { switch (type) { case ElementType.amount: return AppColors.amount; case ElementType.company: return AppColors.company; case ElementType.person: return AppColors.person; case ElementType.location: return AppColors.location; case ElementType.date: return AppColors.date; case ElementType.other: return AppColors.other; } } } class _TextMarker { final int start; final int end; final _MarkerType type; final DocumentElement? element; final Annotation? annotation; _TextMarker({ required this.start, required this.end, required this.type, this.element, this.annotation, }); } enum _MarkerType { element, annotation, }