17 changed files with 318 additions and 263 deletions
@ -1,104 +0,0 @@
@@ -1,104 +0,0 @@
|
||||
import 'package:flutter/material.dart'; |
||||
import 'package:provider/provider.dart'; |
||||
import 'package:win_text_editor/app/menus/app_menu.dart'; |
||||
import 'package:win_text_editor/app/providers/tab_provider.dart'; |
||||
import 'package:win_text_editor/app/providers/file_provider.dart'; |
||||
import 'package:win_text_editor/app/widgets/editor_pane.dart'; |
||||
import 'package:win_text_editor/app/widgets/file_explorer.dart'; |
||||
import 'package:win_text_editor/app/widgets/console_panel.dart'; // 新增导入 |
||||
|
||||
class AppScaffold extends StatelessWidget { |
||||
const AppScaffold({super.key}); |
||||
|
||||
@override |
||||
Widget build(BuildContext context) { |
||||
return MultiProvider( |
||||
providers: [ |
||||
ChangeNotifierProvider(create: (_) => FileProvider()), |
||||
ChangeNotifierProvider(create: (_) => TabProvider()), |
||||
], |
||||
child: const Scaffold( |
||||
body: Column( |
||||
children: [ |
||||
// 菜单栏 |
||||
AppMenu(), |
||||
// 主内容区域 |
||||
Expanded(child: _ResizablePanel()), |
||||
], |
||||
), |
||||
), |
||||
); |
||||
} |
||||
} |
||||
|
||||
class _ResizablePanel extends StatefulWidget { |
||||
const _ResizablePanel(); |
||||
|
||||
@override |
||||
State<_ResizablePanel> createState() => _ResizablePanelState(); |
||||
} |
||||
|
||||
class _ResizablePanelState extends State<_ResizablePanel> { |
||||
double _leftWidth = 0.2; // 初始宽度20% |
||||
final double _minWidth = 100; // 最小宽度 |
||||
final double _maxWidth = 400; // 最大宽度 |
||||
|
||||
@override |
||||
Widget build(BuildContext context) { |
||||
final screenWidth = MediaQuery.of(context).size.width; |
||||
|
||||
return LayoutBuilder( |
||||
builder: (context, constraints) { |
||||
// 计算实际宽度 |
||||
final leftPanelWidth = (_leftWidth * screenWidth).clamp(_minWidth, _maxWidth); |
||||
|
||||
return Column( |
||||
children: [ |
||||
Expanded( |
||||
child: Row( |
||||
children: [ |
||||
// 左侧文件资源管理器 - 添加Material小部件包裹 |
||||
Material( |
||||
elevation: 1, // 轻微阴影分隔 |
||||
child: SizedBox( |
||||
width: leftPanelWidth, |
||||
child: const ClipRect( |
||||
// 确保内容被裁剪 |
||||
child: FileExplorer(), |
||||
), |
||||
), |
||||
), |
||||
// 拖拽手柄 |
||||
GestureDetector( |
||||
behavior: HitTestBehavior.translucent, |
||||
onPanUpdate: (details) { |
||||
setState(() { |
||||
_leftWidth = ((leftPanelWidth + details.delta.dx) / screenWidth).clamp( |
||||
_minWidth / screenWidth, |
||||
_maxWidth / screenWidth, |
||||
); |
||||
}); |
||||
}, |
||||
child: MouseRegion( |
||||
cursor: SystemMouseCursors.resizeLeftRight, |
||||
child: Container(width: 4, color: Colors.grey[300]), |
||||
), |
||||
), |
||||
// 右侧编辑器区域 - 添加Material背景 |
||||
const Expanded( |
||||
child: Material( |
||||
color: Colors.white, |
||||
child: Column(children: [Expanded(child: EditorPane())]), |
||||
), |
||||
), |
||||
], |
||||
), |
||||
), |
||||
// 控制台面板 |
||||
const ConsolePanel(), |
||||
], |
||||
); |
||||
}, |
||||
); |
||||
} |
||||
} |
@ -0,0 +1,46 @@
@@ -0,0 +1,46 @@
|
||||
import 'package:flutter/material.dart'; |
||||
import 'package:provider/provider.dart'; |
||||
import 'package:win_text_editor/app/core/file_explorer_pane.dart'; |
||||
import 'package:win_text_editor/app/core/tab_manager.dart'; |
||||
import 'package:win_text_editor/app/core/tab_view.dart'; |
||||
import 'package:win_text_editor/app/menus/app_menu.dart'; |
||||
import 'package:win_text_editor/app/providers/file_provider.dart'; |
||||
import 'package:win_text_editor/app/core/console_panel.dart'; |
||||
|
||||
class AppScaffold extends StatelessWidget { |
||||
const AppScaffold({super.key}); |
||||
|
||||
@override |
||||
Widget build(BuildContext context) { |
||||
return MultiProvider( |
||||
providers: [ |
||||
ChangeNotifierProvider(create: (_) => FileProvider()), |
||||
ChangeNotifierProvider(create: (_) => TabManager()), // 改名为TabManager |
||||
], |
||||
child: Scaffold( |
||||
body: Column( |
||||
children: [ |
||||
const AppMenu(), // 顶部菜单 |
||||
Expanded( |
||||
child: Row( |
||||
children: [ |
||||
// 左侧文件树 |
||||
const FileExplorerPane(), |
||||
// 主内容区 |
||||
Expanded( |
||||
child: Consumer<TabManager>( |
||||
builder: |
||||
(_, manager, __) => |
||||
TabView(tabs: manager.tabs, currentTabId: manager.activeTabId), |
||||
), |
||||
), |
||||
], |
||||
), |
||||
), |
||||
const ConsolePanel(), // 底部状态栏 |
||||
], |
||||
), |
||||
), |
||||
); |
||||
} |
||||
} |
@ -0,0 +1,67 @@
@@ -0,0 +1,67 @@
|
||||
import 'package:file_picker/file_picker.dart'; |
||||
import 'package:flutter/material.dart'; |
||||
import 'package:provider/provider.dart'; |
||||
import 'package:win_text_editor/app/providers/file_provider.dart'; |
||||
import 'package:win_text_editor/app/widgets/file_explorer.dart'; |
||||
|
||||
class FileExplorerPane extends StatelessWidget { |
||||
const FileExplorerPane({super.key}); |
||||
|
||||
@override |
||||
Widget build(BuildContext context) { |
||||
return Consumer<FileProvider>( |
||||
builder: (context, fileProvider, child) { |
||||
return Material( |
||||
elevation: 1, |
||||
child: SizedBox( |
||||
width: _calculateWidth(context, fileProvider), |
||||
child: Column( |
||||
children: [ |
||||
// 标题栏和操作按钮 |
||||
_buildHeader(context), |
||||
// 文件树内容 |
||||
const Expanded(child: FileExplorer()), |
||||
], |
||||
), |
||||
), |
||||
); |
||||
}, |
||||
); |
||||
} |
||||
|
||||
Widget _buildHeader(BuildContext context) { |
||||
return Container( |
||||
height: 40, |
||||
padding: const EdgeInsets.symmetric(horizontal: 8), |
||||
decoration: BoxDecoration( |
||||
color: Colors.grey[100], |
||||
border: const Border(bottom: BorderSide(color: Colors.grey)), |
||||
), |
||||
child: Row( |
||||
children: [ |
||||
const Text('文件资源管理器', style: TextStyle(fontWeight: FontWeight.bold)), |
||||
const Spacer(), |
||||
IconButton( |
||||
icon: const Icon(Icons.folder_open, size: 20), |
||||
tooltip: '打开文件夹', |
||||
onPressed: () => _openDirectory(context), |
||||
), |
||||
], |
||||
), |
||||
); |
||||
} |
||||
|
||||
double _calculateWidth(BuildContext context, FileProvider fileProvider) { |
||||
final screenWidth = MediaQuery.of(context).size.width; |
||||
final defaultWidth = screenWidth * 0.2; // 默认20%宽度 |
||||
return defaultWidth.clamp(200, 400); // 限制最小200,最大400 |
||||
} |
||||
|
||||
Future<void> _openDirectory(BuildContext context) async { |
||||
final fileProvider = Provider.of<FileProvider>(context, listen: false); |
||||
final String? selectedDirectory = await FilePicker.platform.getDirectoryPath(); |
||||
if (selectedDirectory != null) { |
||||
await fileProvider.setRootPath(selectedDirectory); |
||||
} |
||||
} |
||||
} |
@ -0,0 +1,105 @@
@@ -0,0 +1,105 @@
|
||||
import 'package:flutter/material.dart'; |
||||
import 'package:win_text_editor/app/components/text_editor.dart'; |
||||
import 'package:win_text_editor/app/core/tab_manager.dart'; |
||||
import 'package:win_text_editor/app/models/tab_model.dart'; |
||||
import 'package:win_text_editor/app/modules/template_parser/template_parser_view.dart'; |
||||
import 'package:provider/provider.dart'; |
||||
|
||||
class TabView extends StatelessWidget { |
||||
final List<AppTab> tabs; |
||||
final String? currentTabId; |
||||
|
||||
const TabView({super.key, required this.tabs, required this.currentTabId}); |
||||
|
||||
@override |
||||
Widget build(BuildContext context) { |
||||
final activeTab = tabs.firstWhere( |
||||
(tab) => tab.id == currentTabId, |
||||
orElse: () => AppTab(id: '', title: ''), // 提供默认值 |
||||
); |
||||
|
||||
return Column( |
||||
children: [ |
||||
// 选项卡标签栏 |
||||
_buildTabBar(context), |
||||
// 选项卡内容区 |
||||
Expanded( |
||||
child: |
||||
activeTab.id.isNotEmpty |
||||
? _buildTabContent(activeTab) |
||||
: const Center(child: Text('无活动标签页')), |
||||
), |
||||
], |
||||
); |
||||
} |
||||
|
||||
Widget _buildTabBar(BuildContext context) { |
||||
return SizedBox( |
||||
height: 40, |
||||
child: ListView.builder( |
||||
scrollDirection: Axis.horizontal, |
||||
itemCount: tabs.length, |
||||
itemBuilder: (ctx, index) { |
||||
final tab = tabs[index]; |
||||
return _TabItem( |
||||
tab: tab, |
||||
isActive: tab.id == currentTabId, |
||||
onClose: () => context.read<TabManager>().closeTab(tab.id), |
||||
onTap: () => context.read<TabManager>().setActiveTab(tab.id), |
||||
); |
||||
}, |
||||
), |
||||
); |
||||
} |
||||
|
||||
Widget _buildTabContent(AppTab tab) { |
||||
switch (tab.type) { |
||||
case 'template_parser': |
||||
return TemplateParserView(tabId: tab.id); // 使用选项卡的ID而不是新生成 |
||||
case 'content_search': |
||||
// return const ContentSearchView(); |
||||
return Container(); // 临时占位 |
||||
default: |
||||
return TextEditor(tabId: tab.id, initialContent: tab.content); |
||||
} |
||||
} |
||||
} |
||||
|
||||
class _TabItem extends StatelessWidget { |
||||
final AppTab tab; |
||||
final bool isActive; |
||||
final VoidCallback onClose; |
||||
final VoidCallback onTap; |
||||
|
||||
const _TabItem({ |
||||
required this.tab, |
||||
required this.isActive, |
||||
required this.onClose, |
||||
required this.onTap, |
||||
}); |
||||
|
||||
@override |
||||
Widget build(BuildContext context) { |
||||
return GestureDetector( |
||||
onTap: onTap, |
||||
child: Container( |
||||
padding: const EdgeInsets.symmetric(horizontal: 16), |
||||
decoration: BoxDecoration( |
||||
color: isActive ? Colors.blue[100] : Colors.grey[200], |
||||
border: Border( |
||||
bottom: BorderSide(color: isActive ? Colors.blue : Colors.transparent, width: 2), |
||||
), |
||||
), |
||||
child: Row( |
||||
children: [ |
||||
if (tab.icon != null) Icon(tab.icon, size: 16), |
||||
if (tab.icon != null) const SizedBox(width: 4), |
||||
Text(tab.title), |
||||
const SizedBox(width: 8), |
||||
IconButton(icon: const Icon(Icons.close, size: 16), onPressed: onClose), |
||||
], |
||||
), |
||||
), |
||||
); |
||||
} |
||||
} |
@ -0,0 +1,33 @@
@@ -0,0 +1,33 @@
|
||||
import 'package:flutter/material.dart'; |
||||
import 'package:win_text_editor/app/modules/template_parser/template_parser_service.dart'; |
||||
|
||||
class TemplateParserController with ChangeNotifier { |
||||
final TemplateParserService _service = TemplateParserService(); |
||||
|
||||
String _sourceContent = ''; |
||||
String _parsedContent = ''; |
||||
String _statusMessage = '准备就绪'; |
||||
|
||||
String get sourceContent => _sourceContent; |
||||
String get parsedContent => _parsedContent; |
||||
String get statusMessage => _statusMessage; |
||||
|
||||
set sourceContent(String value) { |
||||
_sourceContent = value; |
||||
notifyListeners(); |
||||
} |
||||
|
||||
Future<void> parseTemplates() async { |
||||
try { |
||||
_statusMessage = '解析中...'; |
||||
notifyListeners(); |
||||
|
||||
_parsedContent = await _service.parse(_sourceContent); |
||||
_statusMessage = '解析完成'; |
||||
} catch (e) { |
||||
_statusMessage = '解析失败: $e'; |
||||
} finally { |
||||
notifyListeners(); |
||||
} |
||||
} |
||||
} |
@ -0,0 +1,7 @@
@@ -0,0 +1,7 @@
|
||||
class TemplateParserService { |
||||
Future<String> parse(String source) async { |
||||
// 实际的模板解析逻辑 |
||||
await Future.delayed(const Duration(seconds: 1)); // 模拟耗时操作 |
||||
return 'Parssed: $source'; |
||||
} |
||||
} |
@ -1,84 +0,0 @@
@@ -1,84 +0,0 @@
|
||||
import 'package:flutter/material.dart'; |
||||
import 'package:provider/provider.dart'; |
||||
import 'package:win_text_editor/app/providers/tab_provider.dart'; |
||||
import 'template_parser_tab.dart'; |
||||
|
||||
class EditorPane extends StatelessWidget { |
||||
const EditorPane({super.key}); |
||||
|
||||
@override |
||||
Widget build(BuildContext context) { |
||||
final provider = Provider.of<TabProvider>(context); |
||||
|
||||
return Column( |
||||
children: [ |
||||
// 标签栏 |
||||
SizedBox( |
||||
height: 40, |
||||
child: ListView.builder( |
||||
scrollDirection: Axis.horizontal, |
||||
itemCount: provider.tabs.length, |
||||
itemBuilder: (ctx, index) { |
||||
final tab = provider.tabs[index]; |
||||
return _TabItem( |
||||
title: tab.title, |
||||
icon: tab.icon, // 添加图标支持 |
||||
isActive: tab.id == provider.activeTabId, |
||||
onClose: () => provider.closeTab(tab.id), |
||||
onTap: () => provider.setActiveTab(tab.id), |
||||
); |
||||
}, |
||||
), |
||||
), |
||||
// 内容区 |
||||
Expanded( |
||||
child: |
||||
provider.activeTabId != null && provider.tabs.any((t) => t.id == provider.activeTabId) |
||||
? TemplateParserTab(tabId: provider.activeTabId!) |
||||
: const Center(child: Text('无活动标签页')), |
||||
), |
||||
], |
||||
); |
||||
} |
||||
} |
||||
|
||||
class _TabItem extends StatelessWidget { |
||||
final String title; |
||||
final IconData? icon; // 添加图标 |
||||
final bool isActive; |
||||
final VoidCallback onClose; |
||||
final VoidCallback onTap; |
||||
|
||||
const _TabItem({ |
||||
required this.title, |
||||
this.icon, |
||||
required this.isActive, |
||||
required this.onClose, |
||||
required this.onTap, |
||||
}); |
||||
|
||||
@override |
||||
Widget build(BuildContext context) { |
||||
return GestureDetector( |
||||
onTap: onTap, |
||||
child: Container( |
||||
padding: const EdgeInsets.symmetric(horizontal: 16), |
||||
decoration: BoxDecoration( |
||||
color: isActive ? Colors.blue[100] : Colors.grey[200], |
||||
border: Border( |
||||
bottom: BorderSide(color: isActive ? Colors.blue : Colors.transparent, width: 2), |
||||
), |
||||
), |
||||
child: Row( |
||||
children: [ |
||||
if (icon != null) Icon(icon, size: 16), |
||||
if (icon != null) const SizedBox(width: 4), |
||||
Text(title), |
||||
const SizedBox(width: 8), |
||||
IconButton(icon: const Icon(Icons.close, size: 16), onPressed: onClose), |
||||
], |
||||
), |
||||
), |
||||
); |
||||
} |
||||
} |
Loading…
Reference in new issue