17 changed files with 318 additions and 263 deletions
@ -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 @@ |
|||||||
|
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 @@ |
|||||||
|
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 @@ |
|||||||
|
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 @@ |
|||||||
|
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 @@ |
|||||||
|
class TemplateParserService { |
||||||
|
Future<String> parse(String source) async { |
||||||
|
// 实际的模板解析逻辑 |
||||||
|
await Future.delayed(const Duration(seconds: 1)); // 模拟耗时操作 |
||||||
|
return 'Parssed: $source'; |
||||||
|
} |
||||||
|
} |
@ -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