4 changed files with 212 additions and 106 deletions
@ -1,113 +1,38 @@
@@ -1,113 +1,38 @@
|
||||
import 'package:flutter/material.dart'; |
||||
// import 'text_editor.dart'; |
||||
|
||||
class TemplateParser extends StatelessWidget { |
||||
const TemplateParser({Key? key}) : super(key: key); |
||||
class TemplateParser extends StatefulWidget { |
||||
final String? initialContent; |
||||
|
||||
const TemplateParser({Key? key, this.initialContent}) : super(key: key); |
||||
|
||||
@override |
||||
Widget build(BuildContext context) { |
||||
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); |
||||
}, |
||||
), |
||||
), |
||||
], |
||||
), |
||||
); |
||||
State<TemplateParser> createState() => _TemplateParserState(); |
||||
} |
||||
|
||||
Widget _buildEditorPanel(BuildContext context, int index) { |
||||
// 定义每个面板的标题 |
||||
final titles = ['输入内容', '输出内容', '参考文件', '模板']; |
||||
class _TemplateParserState extends State<TemplateParser> { |
||||
late String _currentContent; |
||||
|
||||
return Column( |
||||
crossAxisAlignment: CrossAxisAlignment.stretch, |
||||
children: [ |
||||
// 工具条 |
||||
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 |
||||
void initState() { |
||||
super.initState(); |
||||
_currentContent = widget.initialContent ?? '这里是模板解析器的初始内容'; |
||||
} |
||||
|
||||
// 编辑框 |
||||
Expanded( |
||||
child: Container( |
||||
decoration: BoxDecoration( |
||||
border: Border.all(color: Colors.grey), |
||||
borderRadius: const BorderRadius.vertical(bottom: Radius.circular(4.0)), |
||||
), |
||||
child: const TextField( |
||||
maxLines: null, |
||||
expands: true, |
||||
decoration: InputDecoration( |
||||
fillColor: Colors.white, |
||||
filled: true, |
||||
border: InputBorder.none, |
||||
contentPadding: EdgeInsets.all(8.0), |
||||
hintText: '输入内容...', |
||||
), |
||||
), |
||||
), |
||||
), |
||||
], |
||||
@override |
||||
Widget build(BuildContext context) { |
||||
return Card( |
||||
margin: EdgeInsets.zero, |
||||
color: Colors.grey[100], |
||||
// child: TextEditor( |
||||
// editorTitle: '模板解析器', |
||||
// initialContent: _currentContent, |
||||
// onContentChanged: (newContent) { |
||||
// setState(() { |
||||
// _currentContent = newContent; |
||||
// }); |
||||
// }, |
||||
// ), |
||||
); |
||||
} |
||||
} |
||||
|
@ -0,0 +1,181 @@
@@ -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