9 changed files with 249 additions and 246 deletions
@ -1,15 +1,16 @@ |
|||||||
|
// models/editor_tab.dart |
||||||
|
enum EditorTabType { |
||||||
|
blank, // 空白 |
||||||
|
template, // 模板解析 |
||||||
|
fileEditor, // 文件编辑器 |
||||||
|
// 可以添加更多类型 |
||||||
|
} |
||||||
|
|
||||||
class EditorTab { |
class EditorTab { |
||||||
final String id; |
final String id; |
||||||
final String title; |
final String title; |
||||||
final String path; |
final EditorTabType type; |
||||||
String content; |
final dynamic content; // 可存储不同类型的内容 |
||||||
final String fileType; |
|
||||||
|
|
||||||
EditorTab({ |
EditorTab({required this.id, required this.title, required this.type, this.content}); |
||||||
required this.id, |
|
||||||
required this.title, |
|
||||||
required this.path, |
|
||||||
required this.content, |
|
||||||
required this.fileType, |
|
||||||
}); |
|
||||||
} |
} |
||||||
|
@ -1,97 +1,40 @@ |
|||||||
import 'package:flutter/material.dart'; |
import 'package:flutter/material.dart'; |
||||||
import 'package:win_text_editor/app/models/editor_tab.dart'; |
import 'package:win_text_editor/app/models/editor_tab.dart'; |
||||||
import 'package:win_text_editor/app/services/file_service.dart'; |
|
||||||
import 'package:win_text_editor/app/services/syntax_service.dart'; |
|
||||||
|
|
||||||
|
// providers/editor_provider.dart |
||||||
class EditorProvider with ChangeNotifier { |
class EditorProvider with ChangeNotifier { |
||||||
final List<EditorTab> _openTabs = []; |
List<EditorTab> _tabs = []; |
||||||
int _activeTabIndex = 0; |
String? _activeTabId; |
||||||
int _currentLayout = 0; // 0=平铺, 1=层叠, 2=单页 |
|
||||||
|
|
||||||
List<EditorTab> get openTabs => _openTabs; |
List<EditorTab> get tabs => _tabs; |
||||||
int get activeTabIndex => _activeTabIndex; |
String? get activeTabId => _activeTabId; |
||||||
int get currentLayout => _currentLayout; |
|
||||||
|
|
||||||
Future<void> openFile(String filePath) async { |
void addTab(EditorTab tab) { |
||||||
try { |
_tabs.add(tab); |
||||||
// 检查是否已经打开 |
_activeTabId = tab.id; |
||||||
final existingIndex = _openTabs.indexWhere((tab) => tab.path == filePath); |
|
||||||
if (existingIndex != -1) { |
|
||||||
_activeTabIndex = existingIndex; |
|
||||||
notifyListeners(); |
notifyListeners(); |
||||||
return; |
|
||||||
} |
|
||||||
|
|
||||||
// 读取文件内容 |
|
||||||
final content = await FileService.readFile(filePath); |
|
||||||
final fileName = filePath.split('/').last; |
|
||||||
final fileType = SyntaxService.detectFileType(fileName); |
|
||||||
|
|
||||||
// 创建新标签页 |
|
||||||
final newTab = EditorTab( |
|
||||||
id: DateTime.now().millisecondsSinceEpoch.toString(), |
|
||||||
title: fileName, |
|
||||||
path: filePath, |
|
||||||
content: content, |
|
||||||
fileType: fileType, |
|
||||||
); |
|
||||||
|
|
||||||
_openTabs.add(newTab); |
|
||||||
_activeTabIndex = _openTabs.length - 1; |
|
||||||
notifyListeners(); |
|
||||||
} catch (e) { |
|
||||||
debugPrint('Error opening file: $e'); |
|
||||||
} |
|
||||||
} |
} |
||||||
|
|
||||||
void closeTab(String tabId) { |
void closeTab(String tabId) { |
||||||
final index = _openTabs.indexWhere((tab) => tab.id == tabId); |
_tabs.removeWhere((tab) => tab.id == tabId); |
||||||
if (index != -1) { |
if (_activeTabId == tabId) { |
||||||
_openTabs.removeAt(index); |
_activeTabId = _tabs.isNotEmpty ? _tabs.last.id : null; |
||||||
if (_activeTabIndex >= index && _activeTabIndex > 0) { |
|
||||||
_activeTabIndex--; |
|
||||||
} |
|
||||||
notifyListeners(); |
|
||||||
} |
} |
||||||
} |
|
||||||
|
|
||||||
void setActiveTab(int index) { |
|
||||||
if (index >= 0 && index < _openTabs.length) { |
|
||||||
_activeTabIndex = index; |
|
||||||
notifyListeners(); |
notifyListeners(); |
||||||
} |
} |
||||||
} |
|
||||||
|
|
||||||
Future<void> saveFile(String tabId) async { |
|
||||||
final index = _openTabs.indexWhere((tab) => tab.id == tabId); |
|
||||||
if (index != -1) { |
|
||||||
final tab = _openTabs[index]; |
|
||||||
await FileService.writeFile(tab.path, tab.content); |
|
||||||
// 可以添加保存成功的提示 |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
Future<void> copyToClipboard(String tabId) async { |
void setActiveTab(String tabId) { |
||||||
final index = _openTabs.indexWhere((tab) => tab.id == tabId); |
_activeTabId = tabId; |
||||||
if (index != -1) { |
|
||||||
final tab = _openTabs[index]; |
|
||||||
// 这里需要实现复制到剪贴板的逻辑 |
|
||||||
// 可以使用 clipboard package |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
void changeLayout(int layout) { |
|
||||||
if (layout >= 0 && layout <= 2) { |
|
||||||
_currentLayout = layout; |
|
||||||
notifyListeners(); |
notifyListeners(); |
||||||
} |
} |
||||||
} |
|
||||||
|
|
||||||
void updateTabContent(String tabId, String newContent) { |
// 添加模板解析标签 |
||||||
final index = _openTabs.indexWhere((tab) => tab.id == tabId); |
void addTemplateParserTab() { |
||||||
if (index != -1) { |
final tab = EditorTab( |
||||||
_openTabs[index].content = newContent; |
id: 'template-${DateTime.now().millisecondsSinceEpoch}', |
||||||
notifyListeners(); |
title: '模板解析 ${_tabs.where((t) => t.type == EditorTabType.template).length + 1}', |
||||||
} |
type: EditorTabType.template, |
||||||
|
); |
||||||
|
addTab(tab); |
||||||
} |
} |
||||||
} |
} |
||||||
|
@ -1,83 +0,0 @@ |
|||||||
import 'package:flutter/material.dart'; |
|
||||||
import 'package:provider/provider.dart'; |
|
||||||
import 'package:win_text_editor/app/providers/editor_provider.dart'; |
|
||||||
|
|
||||||
import '../providers/file_provider.dart'; |
|
||||||
|
|
||||||
class EditorTabBar extends StatelessWidget { |
|
||||||
const EditorTabBar({super.key}); |
|
||||||
|
|
||||||
@override |
|
||||||
Widget build(BuildContext context) { |
|
||||||
final editorProvider = Provider.of<EditorProvider>(context); |
|
||||||
|
|
||||||
return Container( |
|
||||||
height: 40, |
|
||||||
color: Colors.grey[300], |
|
||||||
child: Row( |
|
||||||
children: [ |
|
||||||
// 布局切换按钮 |
|
||||||
PopupMenuButton<int>( |
|
||||||
icon: const Icon(Icons.grid_view), |
|
||||||
itemBuilder: |
|
||||||
(context) => [ |
|
||||||
const PopupMenuItem(value: 0, child: Text('平铺布局')), |
|
||||||
const PopupMenuItem(value: 1, child: Text('层叠布局')), |
|
||||||
const PopupMenuItem(value: 2, child: Text('单页布局')), |
|
||||||
], |
|
||||||
onSelected: (value) => editorProvider.changeLayout(value), |
|
||||||
), |
|
||||||
// 标签页 |
|
||||||
Expanded( |
|
||||||
child: ListView.builder( |
|
||||||
scrollDirection: Axis.horizontal, |
|
||||||
itemCount: editorProvider.openTabs.length, |
|
||||||
itemBuilder: (context, index) { |
|
||||||
final tab = editorProvider.openTabs[index]; |
|
||||||
return InkWell( |
|
||||||
onTap: () => editorProvider.setActiveTab(index), |
|
||||||
child: Container( |
|
||||||
padding: const EdgeInsets.symmetric(horizontal: 16.0), |
|
||||||
decoration: BoxDecoration( |
|
||||||
color: |
|
||||||
editorProvider.activeTabIndex == index ? Colors.white : Colors.grey[200], |
|
||||||
border: Border( |
|
||||||
bottom: BorderSide( |
|
||||||
color: |
|
||||||
editorProvider.activeTabIndex == index |
|
||||||
? Colors.blue |
|
||||||
: Colors.transparent, |
|
||||||
width: 2.0, |
|
||||||
), |
|
||||||
), |
|
||||||
), |
|
||||||
child: Center( |
|
||||||
child: Row( |
|
||||||
children: [ |
|
||||||
Text(tab.title), |
|
||||||
const SizedBox(width: 8), |
|
||||||
IconButton( |
|
||||||
icon: const Icon(Icons.close, size: 16), |
|
||||||
onPressed: () => editorProvider.closeTab(tab.id), |
|
||||||
), |
|
||||||
], |
|
||||||
), |
|
||||||
), |
|
||||||
), |
|
||||||
); |
|
||||||
}, |
|
||||||
), |
|
||||||
), |
|
||||||
// 打开文件按钮 |
|
||||||
IconButton( |
|
||||||
icon: const Icon(Icons.add), |
|
||||||
onPressed: () async { |
|
||||||
final fileProvider = Provider.of<FileProvider>(context, listen: false); |
|
||||||
await fileProvider.pickAndOpenFile(); |
|
||||||
}, |
|
||||||
), |
|
||||||
], |
|
||||||
), |
|
||||||
); |
|
||||||
} |
|
||||||
} |
|
@ -0,0 +1,113 @@ |
|||||||
|
import 'package:flutter/material.dart'; |
||||||
|
|
||||||
|
class TemplateParser extends StatelessWidget { |
||||||
|
const TemplateParser({Key? key}) : 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); |
||||||
|
}, |
||||||
|
), |
||||||
|
), |
||||||
|
], |
||||||
|
), |
||||||
|
); |
||||||
|
} |
||||||
|
|
||||||
|
Widget _buildEditorPanel(BuildContext context, int index) { |
||||||
|
// 定义每个面板的标题 |
||||||
|
final titles = ['输入内容', '输出内容', '参考文件', '模板']; |
||||||
|
|
||||||
|
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(), |
||||||
|
), |
||||||
|
], |
||||||
|
), |
||||||
|
], |
||||||
|
), |
||||||
|
), |
||||||
|
|
||||||
|
// 编辑框 |
||||||
|
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: '输入内容...', |
||||||
|
), |
||||||
|
), |
||||||
|
), |
||||||
|
), |
||||||
|
], |
||||||
|
); |
||||||
|
} |
||||||
|
} |
Loading…
Reference in new issue