9 changed files with 249 additions and 246 deletions
@ -1,15 +1,16 @@
@@ -1,15 +1,16 @@
|
||||
// models/editor_tab.dart |
||||
enum EditorTabType { |
||||
blank, // 空白 |
||||
template, // 模板解析 |
||||
fileEditor, // 文件编辑器 |
||||
// 可以添加更多类型 |
||||
} |
||||
|
||||
class EditorTab { |
||||
final String id; |
||||
final String title; |
||||
final String path; |
||||
String content; |
||||
final String fileType; |
||||
final EditorTabType type; |
||||
final dynamic content; // 可存储不同类型的内容 |
||||
|
||||
EditorTab({ |
||||
required this.id, |
||||
required this.title, |
||||
required this.path, |
||||
required this.content, |
||||
required this.fileType, |
||||
}); |
||||
EditorTab({required this.id, required this.title, required this.type, this.content}); |
||||
} |
||||
|
@ -1,97 +1,40 @@
@@ -1,97 +1,40 @@
|
||||
import 'package:flutter/material.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 { |
||||
final List<EditorTab> _openTabs = []; |
||||
int _activeTabIndex = 0; |
||||
int _currentLayout = 0; // 0=平铺, 1=层叠, 2=单页 |
||||
List<EditorTab> _tabs = []; |
||||
String? _activeTabId; |
||||
|
||||
List<EditorTab> get openTabs => _openTabs; |
||||
int get activeTabIndex => _activeTabIndex; |
||||
int get currentLayout => _currentLayout; |
||||
List<EditorTab> get tabs => _tabs; |
||||
String? get activeTabId => _activeTabId; |
||||
|
||||
Future<void> openFile(String filePath) async { |
||||
try { |
||||
// 检查是否已经打开 |
||||
final existingIndex = _openTabs.indexWhere((tab) => tab.path == filePath); |
||||
if (existingIndex != -1) { |
||||
_activeTabIndex = existingIndex; |
||||
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 addTab(EditorTab tab) { |
||||
_tabs.add(tab); |
||||
_activeTabId = tab.id; |
||||
notifyListeners(); |
||||
} |
||||
|
||||
void closeTab(String tabId) { |
||||
final index = _openTabs.indexWhere((tab) => tab.id == tabId); |
||||
if (index != -1) { |
||||
_openTabs.removeAt(index); |
||||
if (_activeTabIndex >= index && _activeTabIndex > 0) { |
||||
_activeTabIndex--; |
||||
} |
||||
notifyListeners(); |
||||
_tabs.removeWhere((tab) => tab.id == tabId); |
||||
if (_activeTabId == tabId) { |
||||
_activeTabId = _tabs.isNotEmpty ? _tabs.last.id : null; |
||||
} |
||||
notifyListeners(); |
||||
} |
||||
|
||||
void setActiveTab(int index) { |
||||
if (index >= 0 && index < _openTabs.length) { |
||||
_activeTabIndex = index; |
||||
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); |
||||
// 可以添加保存成功的提示 |
||||
} |
||||
void setActiveTab(String tabId) { |
||||
_activeTabId = tabId; |
||||
notifyListeners(); |
||||
} |
||||
|
||||
Future<void> copyToClipboard(String tabId) async { |
||||
final index = _openTabs.indexWhere((tab) => tab.id == tabId); |
||||
if (index != -1) { |
||||
final tab = _openTabs[index]; |
||||
// 这里需要实现复制到剪贴板的逻辑 |
||||
// 可以使用 clipboard package |
||||
} |
||||
} |
||||
|
||||
void changeLayout(int layout) { |
||||
if (layout >= 0 && layout <= 2) { |
||||
_currentLayout = layout; |
||||
notifyListeners(); |
||||
} |
||||
} |
||||
|
||||
void updateTabContent(String tabId, String newContent) { |
||||
final index = _openTabs.indexWhere((tab) => tab.id == tabId); |
||||
if (index != -1) { |
||||
_openTabs[index].content = newContent; |
||||
notifyListeners(); |
||||
} |
||||
// 添加模板解析标签 |
||||
void addTemplateParserTab() { |
||||
final tab = EditorTab( |
||||
id: 'template-${DateTime.now().millisecondsSinceEpoch}', |
||||
title: '模板解析 ${_tabs.where((t) => t.type == EditorTabType.template).length + 1}', |
||||
type: EditorTabType.template, |
||||
); |
||||
addTab(tab); |
||||
} |
||||
} |
||||
|
@ -1,83 +0,0 @@
@@ -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 @@
@@ -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