4 changed files with 212 additions and 106 deletions
@ -1,113 +1,38 @@ |
|||||||
import 'package:flutter/material.dart'; |
import 'package:flutter/material.dart'; |
||||||
|
// import 'text_editor.dart'; |
||||||
|
|
||||||
class TemplateParser extends StatelessWidget { |
class TemplateParser extends StatefulWidget { |
||||||
const TemplateParser({Key? key}) : super(key: key); |
final String? initialContent; |
||||||
|
|
||||||
|
const TemplateParser({Key? key, this.initialContent}) : super(key: key); |
||||||
|
|
||||||
@override |
@override |
||||||
Widget build(BuildContext context) { |
State<TemplateParser> createState() => _TemplateParserState(); |
||||||
return Card( |
} |
||||||
margin: EdgeInsets.zero, // 移除默认边距以填满整个区域 |
|
||||||
color: Colors.grey[100], |
|
||||||
child: Column( |
|
||||||
children: [ |
|
||||||
// 四个编辑框区域 |
|
||||||
Expanded( |
|
||||||
child: GridView.builder( |
|
||||||
padding: const EdgeInsets.all(8.0), |
|
||||||
gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount( |
|
||||||
crossAxisCount: 2, |
|
||||||
childAspectRatio: 1.0, |
|
||||||
crossAxisSpacing: 8.0, |
|
||||||
mainAxisSpacing: 8.0, |
|
||||||
), |
|
||||||
itemCount: 4, |
|
||||||
itemBuilder: (context, index) { |
|
||||||
return _buildEditorPanel(context, index); |
|
||||||
}, |
|
||||||
), |
|
||||||
), |
|
||||||
], |
|
||||||
), |
|
||||||
); |
|
||||||
} |
|
||||||
|
|
||||||
Widget _buildEditorPanel(BuildContext context, int index) { |
class _TemplateParserState extends State<TemplateParser> { |
||||||
// 定义每个面板的标题 |
late String _currentContent; |
||||||
final titles = ['输入内容', '输出内容', '参考文件', '模板']; |
|
||||||
|
|
||||||
return Column( |
@override |
||||||
crossAxisAlignment: CrossAxisAlignment.stretch, |
void initState() { |
||||||
children: [ |
super.initState(); |
||||||
// 工具条 |
_currentContent = widget.initialContent ?? '这里是模板解析器的初始内容'; |
||||||
Container( |
} |
||||||
padding: const EdgeInsets.symmetric(horizontal: 8.0, vertical: 4.0), |
|
||||||
decoration: BoxDecoration( |
|
||||||
color: Colors.grey[100], |
|
||||||
borderRadius: const BorderRadius.vertical(top: Radius.circular(4.0)), |
|
||||||
), |
|
||||||
child: Row( |
|
||||||
mainAxisAlignment: MainAxisAlignment.spaceBetween, |
|
||||||
children: [ |
|
||||||
// 左侧标题 |
|
||||||
Text( |
|
||||||
titles[index], |
|
||||||
style: const TextStyle(fontWeight: FontWeight.bold, fontSize: 14), |
|
||||||
), |
|
||||||
// 右侧工具按钮 |
|
||||||
Row( |
|
||||||
mainAxisSize: MainAxisSize.min, |
|
||||||
children: [ |
|
||||||
IconButton( |
|
||||||
icon: const Icon(Icons.folder_open, size: 18), |
|
||||||
onPressed: () {}, |
|
||||||
padding: EdgeInsets.zero, |
|
||||||
constraints: const BoxConstraints(), |
|
||||||
), |
|
||||||
IconButton( |
|
||||||
icon: const Icon(Icons.content_copy, size: 18), |
|
||||||
onPressed: () {}, |
|
||||||
padding: EdgeInsets.zero, |
|
||||||
constraints: const BoxConstraints(), |
|
||||||
), |
|
||||||
IconButton( |
|
||||||
icon: const Icon(Icons.save, size: 18), |
|
||||||
onPressed: () {}, |
|
||||||
padding: EdgeInsets.zero, |
|
||||||
constraints: const BoxConstraints(), |
|
||||||
), |
|
||||||
IconButton( |
|
||||||
icon: const Icon(Icons.close, size: 18), |
|
||||||
onPressed: () {}, |
|
||||||
padding: EdgeInsets.zero, |
|
||||||
constraints: const BoxConstraints(), |
|
||||||
), |
|
||||||
], |
|
||||||
), |
|
||||||
], |
|
||||||
), |
|
||||||
), |
|
||||||
|
|
||||||
// 编辑框 |
@override |
||||||
Expanded( |
Widget build(BuildContext context) { |
||||||
child: Container( |
return Card( |
||||||
decoration: BoxDecoration( |
margin: EdgeInsets.zero, |
||||||
border: Border.all(color: Colors.grey), |
color: Colors.grey[100], |
||||||
borderRadius: const BorderRadius.vertical(bottom: Radius.circular(4.0)), |
// child: TextEditor( |
||||||
), |
// editorTitle: '模板解析器', |
||||||
child: const TextField( |
// initialContent: _currentContent, |
||||||
maxLines: null, |
// onContentChanged: (newContent) { |
||||||
expands: true, |
// setState(() { |
||||||
decoration: InputDecoration( |
// _currentContent = newContent; |
||||||
fillColor: Colors.white, |
// }); |
||||||
filled: true, |
// }, |
||||||
border: InputBorder.none, |
// ), |
||||||
contentPadding: EdgeInsets.all(8.0), |
|
||||||
hintText: '输入内容...', |
|
||||||
), |
|
||||||
), |
|
||||||
), |
|
||||||
), |
|
||||||
], |
|
||||||
); |
); |
||||||
} |
} |
||||||
} |
} |
||||||
|
@ -0,0 +1,181 @@ |
|||||||
|
import 'package:flutter/material.dart'; |
||||||
|
import 'package:flutter/services.dart'; |
||||||
|
|
||||||
|
class TextEditor extends StatefulWidget { |
||||||
|
final String? initialContent; |
||||||
|
final String editorTitle; |
||||||
|
final bool showToolbar; |
||||||
|
final ValueChanged<String>? onContentChanged; |
||||||
|
|
||||||
|
const TextEditor({ |
||||||
|
Key? key, |
||||||
|
this.initialContent, |
||||||
|
this.editorTitle = '源文本', |
||||||
|
this.showToolbar = true, |
||||||
|
this.onContentChanged, |
||||||
|
}) : super(key: key); |
||||||
|
|
||||||
|
@override |
||||||
|
State<TextEditor> createState() => _TextEditorState(); |
||||||
|
} |
||||||
|
|
||||||
|
class _TextEditorState extends State<TextEditor> { |
||||||
|
late final TextEditingController _textController; |
||||||
|
late String _currentFileName; |
||||||
|
final ScrollController _scrollController = ScrollController(); |
||||||
|
|
||||||
|
@override |
||||||
|
void initState() { |
||||||
|
super.initState(); |
||||||
|
_textController = TextEditingController(text: widget.initialContent); |
||||||
|
_currentFileName = "未命名文件"; |
||||||
|
_textController.addListener(_handleTextChanged); |
||||||
|
} |
||||||
|
|
||||||
|
void _handleTextChanged() { |
||||||
|
widget.onContentChanged?.call(_textController.text); |
||||||
|
} |
||||||
|
|
||||||
|
@override |
||||||
|
void didUpdateWidget(TextEditor oldWidget) { |
||||||
|
super.didUpdateWidget(oldWidget); |
||||||
|
if (widget.initialContent != oldWidget.initialContent && |
||||||
|
widget.initialContent != _textController.text) { |
||||||
|
_textController.text = widget.initialContent ?? ''; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
@override |
||||||
|
void dispose() { |
||||||
|
_textController.removeListener(_handleTextChanged); |
||||||
|
_textController.dispose(); |
||||||
|
_scrollController.dispose(); |
||||||
|
super.dispose(); |
||||||
|
} |
||||||
|
|
||||||
|
Future<void> _openFile() async { |
||||||
|
const String mockFileContent = """这是从文件加载的示例文本: |
||||||
|
第1行内容 |
||||||
|
第2行内容 |
||||||
|
第3行内容"""; |
||||||
|
|
||||||
|
setState(() { |
||||||
|
_textController.text = mockFileContent; |
||||||
|
_currentFileName = "示例文件.txt"; |
||||||
|
}); |
||||||
|
} |
||||||
|
|
||||||
|
Future<void> _copyToClipboard() async { |
||||||
|
if (_textController.text.isNotEmpty) { |
||||||
|
await Clipboard.setData(ClipboardData(text: _textController.text)); |
||||||
|
if (mounted) { |
||||||
|
ScaffoldMessenger.of(context).showSnackBar(const SnackBar(content: Text('已复制全部文本到剪贴板'))); |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
Future<void> _saveFile() async { |
||||||
|
if (_textController.text.isEmpty) return; |
||||||
|
|
||||||
|
if (mounted) { |
||||||
|
ScaffoldMessenger.of(context).showSnackBar( |
||||||
|
SnackBar( |
||||||
|
content: Text('文件 "$_currentFileName" 保存成功'), |
||||||
|
duration: const Duration(seconds: 2), |
||||||
|
), |
||||||
|
); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
@override |
||||||
|
Widget build(BuildContext context) { |
||||||
|
return Column( |
||||||
|
children: [ |
||||||
|
if (widget.showToolbar) ...[ |
||||||
|
Container( |
||||||
|
padding: const EdgeInsets.symmetric(horizontal: 16.0, vertical: 8.0), |
||||||
|
decoration: BoxDecoration( |
||||||
|
color: Colors.blue[50], |
||||||
|
border: Border(bottom: BorderSide(color: Colors.grey[300]!)), |
||||||
|
), |
||||||
|
child: Row( |
||||||
|
mainAxisAlignment: MainAxisAlignment.spaceBetween, |
||||||
|
children: [ |
||||||
|
Text( |
||||||
|
widget.editorTitle, |
||||||
|
style: const TextStyle(fontSize: 16, fontWeight: FontWeight.bold), |
||||||
|
), |
||||||
|
Row( |
||||||
|
children: [ |
||||||
|
IconButton( |
||||||
|
icon: const Icon(Icons.folder_open, size: 20), |
||||||
|
tooltip: '打开文件', |
||||||
|
onPressed: _openFile, |
||||||
|
), |
||||||
|
IconButton( |
||||||
|
icon: const Icon(Icons.content_copy, size: 20), |
||||||
|
tooltip: '复制全部', |
||||||
|
onPressed: _copyToClipboard, |
||||||
|
), |
||||||
|
IconButton( |
||||||
|
icon: const Icon(Icons.save, size: 20), |
||||||
|
tooltip: '保存文件', |
||||||
|
onPressed: _saveFile, |
||||||
|
), |
||||||
|
], |
||||||
|
), |
||||||
|
], |
||||||
|
), |
||||||
|
), |
||||||
|
], |
||||||
|
Expanded(child: _buildEditorField()), |
||||||
|
], |
||||||
|
); |
||||||
|
} |
||||||
|
|
||||||
|
Widget _buildEditorField() { |
||||||
|
return ScrollConfiguration( |
||||||
|
behavior: _NoScrollBehavior(), |
||||||
|
child: SingleChildScrollView( |
||||||
|
controller: _scrollController, |
||||||
|
physics: const ClampingScrollPhysics(), |
||||||
|
child: ConstrainedBox( |
||||||
|
constraints: BoxConstraints(minHeight: MediaQuery.of(context).size.height), |
||||||
|
child: Padding( |
||||||
|
padding: const EdgeInsets.all(8.0), |
||||||
|
child: TextField( |
||||||
|
controller: _textController, |
||||||
|
maxLines: null, |
||||||
|
scrollPhysics: const NeverScrollableScrollPhysics(), |
||||||
|
decoration: InputDecoration( |
||||||
|
hintText: '在此输入文本...', |
||||||
|
border: InputBorder.none, |
||||||
|
filled: true, |
||||||
|
fillColor: Colors.white, |
||||||
|
contentPadding: const EdgeInsets.all(12.0), |
||||||
|
), |
||||||
|
style: const TextStyle(fontSize: 14), |
||||||
|
), |
||||||
|
), |
||||||
|
), |
||||||
|
), |
||||||
|
); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
class _NoScrollBehavior extends ScrollBehavior { |
||||||
|
@override |
||||||
|
Widget buildScrollbar(BuildContext context, Widget child, ScrollableDetails details) { |
||||||
|
return child; |
||||||
|
} |
||||||
|
|
||||||
|
@override |
||||||
|
Widget buildOverscrollIndicator(BuildContext context, Widget child, ScrollableDetails details) { |
||||||
|
return child; |
||||||
|
} |
||||||
|
|
||||||
|
@override |
||||||
|
ScrollPhysics getScrollPhysics(BuildContext context) { |
||||||
|
return const ClampingScrollPhysics(); |
||||||
|
} |
||||||
|
} |
Loading…
Reference in new issue