import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'dart:async'; /// 元件库面板组件 /// /// 网格/列表双视图切换 /// 搜索与筛选(按类别、封装、厂商) /// 拖拽元件到画布 class ComponentLibraryPanel extends ConsumerStatefulWidget { /// 视图模式:网格或列表 final LibraryViewMode initialViewMode; /// 元件选择回调 final Function(ComponentLibraryItem)? onComponentSelected; /// 元件拖拽开始回调 final Function(ComponentLibraryItem)? onDragStarted; /// 筛选条件变更回调 final Function(FilterOptions)? onFilterChanged; const ComponentLibraryPanel({ super.key, this.initialViewMode = LibraryViewMode.grid, this.onComponentSelected, this.onDragStarted, this.onFilterChanged, }); @override ConsumerState createState() => _ComponentLibraryPanelState(); } class _ComponentLibraryPanelState extends ConsumerState with SingleTickerProviderStateMixin { LibraryViewMode _viewMode = LibraryViewMode.grid; String _searchQuery = ''; FilterOptions _filterOptions = FilterOptions(); // 筛选面板是否展开 bool _isFilterExpanded = false; // 搜索防抖 Timer? _searchDebounce; // 元件数据(模拟) late List _allComponents; late List _filteredComponents; // 当前选中的类别 String? _selectedCategory; // 当前选中的封装 String? _selectedFootprint; @override void initState() { super.initState(); _viewMode = widget.initialViewMode; _loadComponents(); } @override void dispose() { _searchDebounce?.cancel(); super.dispose(); } void _loadComponents() { // 加载元件数据(实际应从数据源加载) _allComponents = _getMockComponents(); _applyFilters(); } void _applyFilters() { setState(() { _filteredComponents = _allComponents.where((component) { // 搜索过滤 if (_searchQuery.isNotEmpty) { final query = _searchQuery.toLowerCase(); final nameMatch = component.name.toLowerCase().contains(query); final descMatch = component.description?.toLowerCase().contains(query) ?? false; if (!nameMatch && !descMatch) return false; } // 类别过滤 if (_selectedCategory != null && component.category != _selectedCategory) { return false; } // 封装过滤 if (_selectedFootprint != null && component.footprint != _selectedFootprint) { return false; } return true; }).toList(); }); widget.onFilterChanged?.call(_filterOptions); } void _onSearchChanged(String value) { _searchDebounce?.cancel(); _searchDebounce = Timer(const Duration(milliseconds: 300), () { setState(() { _searchQuery = value; }); _applyFilters(); }); } void _toggleViewMode() { setState(() { _viewMode = _viewMode == LibraryViewMode.grid ? LibraryViewMode.list : LibraryViewMode.grid; }); } void _toggleFilter() { setState(() { _isFilterExpanded = !_isFilterExpanded; }); } void _onCategorySelected(String? category) { setState(() { _selectedCategory = category; }); _applyFilters(); } void _onFootprintSelected(String? footprint) { setState(() { _selectedFootprint = footprint; }); _applyFilters(); } void _resetFilters() { setState(() { _selectedCategory = null; _selectedFootprint = null; _searchQuery = ''; }); _applyFilters(); } @override Widget build(BuildContext context) { return Container( color: Colors.white, child: Column( children: [ // 顶部工具栏 _buildToolbar(), // 搜索栏 _buildSearchBar(), // 筛选器 if (_isFilterExpanded) _buildFilterPanel(), const Divider(height: 1), // 元件列表/网格 Expanded( child: _filteredComponents.isEmpty ? _buildEmptyState() : _buildComponentGrid(), ), ], ), ); } Widget _buildToolbar() { return Container( padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 4), child: Row( children: [ // 标题 const Text( '元件库', style: TextStyle( fontSize: 16, fontWeight: FontWeight.bold, ), ), const Spacer(), // 筛选按钮 IconButton( icon: Icon( Icons.filter_list, color: _isFilterExpanded ? Theme.of(context).primaryColor : null, ), onPressed: _toggleFilter, tooltip: '筛选', ), // 视图切换按钮 IconButton( icon: Icon( _viewMode == LibraryViewMode.grid ? Icons.view_list : Icons.grid_view, ), onPressed: _toggleViewMode, tooltip: _viewMode == LibraryViewMode.grid ? '列表视图' : '网格视图', ), ], ), ); } Widget _buildSearchBar() { return Padding( padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 4), child: TextField( decoration: InputDecoration( hintText: '搜索元件...', prefixIcon: const Icon(Icons.search), suffixIcon: _searchQuery.isNotEmpty ? IconButton( icon: const Icon(Icons.clear), onPressed: () { setState(() { _searchQuery = ''; }); _applyFilters(); }, ) : null, border: OutlineInputBorder( borderRadius: BorderRadius.circular(8), ), contentPadding: const EdgeInsets.symmetric(horizontal: 12, vertical: 8), ), onChanged: _onSearchChanged, ), ); } Widget _buildFilterPanel() { return Container( padding: const EdgeInsets.all(12), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Row( children: [ const Text( '筛选条件', style: TextStyle( fontSize: 14, fontWeight: FontWeight.bold, ), ), const Spacer(), TextButton( onPressed: _resetFilters, child: const Text('重置'), ), ], ), const SizedBox(height: 8), // 类别筛选 Row( children: [ const Text('类别:', style: TextStyle(fontSize: 12)), const SizedBox(width: 8), Expanded( child: SingleChildScrollView( scrollDirection: Axis.horizontal, child: Row( children: _getCategories().map((category) { final isSelected = _selectedCategory == category; return Padding( padding: const EdgeInsets.only(right: 8), child: FilterChip( label: Text(category), selected: isSelected, onSelected: (selected) { _onCategorySelected(selected ? category : null); }, ), ); }).toList(), ), ), ), ], ), const SizedBox(height: 8), // 封装筛选 Row( children: [ const Text('封装:', style: TextStyle(fontSize: 12)), const SizedBox(width: 8), Expanded( child: SingleChildScrollView( scrollDirection: Axis.horizontal, child: Row( children: _getFootprints().map((footprint) { final isSelected = _selectedFootprint == footprint; return Padding( padding: const EdgeInsets.only(right: 8), child: FilterChip( label: Text(footprint), selected: isSelected, onSelected: (selected) { _onFootprintSelected(selected ? footprint : null); }, ), ); }).toList(), ), ), ), ], ), ], ), ); } Widget _buildEmptyState() { return Center( child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ Icon( Icons.search_off, size: 64, color: Colors.grey[400], ), const SizedBox(height: 16), Text( _searchQuery.isNotEmpty ? '未找到匹配的元件' : '暂无元件', style: TextStyle( fontSize: 16, color: Colors.grey[600], ), ), if (_searchQuery.isNotEmpty) TextButton( onPressed: _resetFilters, child: const Text('清除筛选'), ), ], ), ); } Widget _buildComponentGrid() { if (_viewMode == LibraryViewMode.grid) { return GridView.builder( padding: const EdgeInsets.all(8), gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount( crossAxisCount: 2, crossAxisSpacing: 8, mainAxisSpacing: 8, childAspectRatio: 1.2, ), itemCount: _filteredComponents.length, itemBuilder: (context, index) { final component = _filteredComponents[index]; return _buildGridItem(component); }, ); } else { return ListView.builder( itemCount: _filteredComponents.length, itemBuilder: (context, index) { final component = _filteredComponents[index]; return _buildListItem(component); }, ); } } Widget _buildGridItem(ComponentLibraryItem component) { return Draggable( data: component, feedback: Material( elevation: 4, borderRadius: BorderRadius.circular(8), child: Container( width: 150, decoration: BoxDecoration( color: Colors.white, borderRadius: BorderRadius.circular(8), border: Border.all(color: Theme.of(context).primaryColor, width: 2), ), child: Column( mainAxisSize: MainAxisSize.min, children: [ const SizedBox(height: 40), Text(component.name, style: const TextStyle(fontWeight: FontWeight.bold)), ], ), ), ), childWhenDragging: Opacity( opacity: 0.5, child: _buildGridItemContent(component), ), child: _buildGridItemContent(component), ); } Widget _buildGridItemContent(ComponentLibraryItem component) { return GestureDetector( onTap: () => widget.onComponentSelected?.call(component), child: Container( decoration: BoxDecoration( color: Colors.grey[50], borderRadius: BorderRadius.circular(8), border: Border.all(color: Colors.grey[300]!), ), child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ // 元件图标 Icon( _getComponentIcon(component.category), size: 40, color: Theme.of(context).primaryColor, ), const SizedBox(height: 8), // 元件名称 Text( component.name, style: const TextStyle( fontWeight: FontWeight.bold, fontSize: 14, ), textAlign: TextAlign.center, maxLines: 1, overflow: TextOverflow.ellipsis, ), const SizedBox(height: 4), // 封装 Text( component.footprint, style: TextStyle( fontSize: 12, color: Colors.grey[600], ), ), const SizedBox(height: 4), // 类别标签 Container( padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 2), decoration: BoxDecoration( color: Theme.of(context).primaryColor.withOpacity(0.1), borderRadius: BorderRadius.circular(4), ), child: Text( component.category, style: TextStyle( fontSize: 10, color: Theme.of(context).primaryColor, ), ), ), ], ), ), ); } Widget _buildListItem(ComponentLibraryItem component) { return Draggable( data: component, feedback: Material( elevation: 4, child: Container( padding: const EdgeInsets.all(12), decoration: BoxDecoration( color: Colors.white, border: Border.all(color: Theme.of(context).primaryColor, width: 2), ), child: Row( children: [ Icon(_getComponentIcon(component.category)), const SizedBox(width: 12), Text(component.name, style: const TextStyle(fontWeight: FontWeight.bold)), ], ), ), ), childWhenDragging: Opacity( opacity: 0.5, child: _buildListItemContent(component), ), child: _buildListItemContent(component), ); } Widget _buildListItemContent(ComponentLibraryItem component) { return ListTile( leading: Icon( _getComponentIcon(component.category), color: Theme.of(context).primaryColor, ), title: Text(component.name), subtitle: Text('${component.footprint} • ${component.category}'), trailing: component.description != null ? Text( component.description!, style: TextStyle(fontSize: 12, color: Colors.grey[600]), maxLines: 1, overflow: TextOverflow.ellipsis, ) : null, onTap: () => widget.onComponentSelected?.call(component), onLongPress: () => _showComponentDetails(component), ); } void _showComponentDetails(ComponentLibraryItem component) { showModalBottomSheet( context: context, builder: (context) => Container( padding: const EdgeInsets.all(24), child: Column( mainAxisSize: MainAxisSize.min, crossAxisAlignment: CrossAxisAlignment.start, children: [ Row( children: [ Icon( _getComponentIcon(component.category), size: 48, color: Theme.of(context).primaryColor, ), const SizedBox(width: 16), Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( component.name, style: const TextStyle( fontSize: 20, fontWeight: FontWeight.bold, ), ), Text( component.footprint, style: TextStyle( fontSize: 14, color: Colors.grey[600], ), ), ], ), ), ], ), const SizedBox(height: 16), if (component.description != null) ...[ const Text( '描述', style: TextStyle(fontWeight: FontWeight.bold), ), const SizedBox(height: 4), Text(component.description!), const SizedBox(height: 16), ], ElevatedButton.icon( onPressed: () { Navigator.pop(context); widget.onComponentSelected?.call(component); }, icon: const Icon(Icons.add), label: const Text('添加到原理图'), style: ElevatedButton.styleFrom( minimumSize: const Size(double.infinity, 48), ), ), ], ), ), ); } IconData _getComponentIcon(String category) { switch (category) { case '电源': return Icons.battery_full; case '被动元件': return Icons.rectangle_outlined; case '半导体': return Icons.memory; case '连接器': return Icons.usb; case '光电器件': return Icons.lightbulb; case '集成电路': return Icons.dns; default: return Icons.category; } } List _getCategories() { return ['电源', '被动元件', '半导体', '连接器', '光电器件', '集成电路']; } List _getFootprints() { return ['0402', '0603', '0805', '1206', 'SOT23', 'SOT223', 'SOIC8', 'DIP8', 'QFN16']; } List _getMockComponents() { return [ ComponentLibraryItem( name: '电阻', category: '被动元件', footprint: '0805', description: '10kΩ ±1%', ), ComponentLibraryItem( name: '电容', category: '被动元件', footprint: '0603', description: '100nF ±10%', ), ComponentLibraryItem( name: 'LED', category: '光电器件', footprint: 'LED0603', description: '红色发光二极管', ), ComponentLibraryItem( name: '三极管', category: '半导体', footprint: 'SOT23', description: 'NPN 型', ), ComponentLibraryItem( name: 'MOSFET', category: '半导体', footprint: 'SOT23', description: 'N 沟道增强型', ), ComponentLibraryItem( name: '运放', category: '集成电路', footprint: 'SOIC8', description: '通用运算放大器', ), ComponentLibraryItem( name: '稳压器', category: '电源', footprint: 'SOT223', description: '3.3V LDO', ), ComponentLibraryItem( name: '排针', category: '连接器', footprint: 'HDR1X2', description: '2.54mm 间距', ), ComponentLibraryItem( name: 'USB 接口', category: '连接器', footprint: 'USB_MICRO_B', description: 'Micro USB B 型', ), ComponentLibraryItem( name: '晶振', category: '被动元件', footprint: 'HC49S', description: '16MHz', ), ]; } } /// 视图模式枚举 enum LibraryViewMode { grid, // 网格视图 list, // 列表视图 } /// 元件库项目 class ComponentLibraryItem { final String name; final String category; final String footprint; final String? description; final String? manufacturer; final String? symbolData; ComponentLibraryItem({ required this.name, required this.category, required this.footprint, this.description, this.manufacturer, this.symbolData, }); } /// 筛选选项 class FilterOptions { final String? category; final String? footprint; final String? manufacturer; final String? searchQuery; FilterOptions({ this.category, this.footprint, this.manufacturer, this.searchQuery, }); FilterOptions copyWith({ String? category, String? footprint, String? manufacturer, String? searchQuery, }) { return FilterOptions( category: category ?? this.category, footprint: footprint ?? this.footprint, manufacturer: manufacturer ?? this.manufacturer, searchQuery: searchQuery ?? this.searchQuery, ); } } /// 显示元件库面板的辅助函数(作为侧边抽屉) Future showComponentLibraryDrawer( BuildContext context, { LibraryViewMode initialViewMode = LibraryViewMode.grid, Function(ComponentLibraryItem)? onComponentSelected, }) { return showModalBottomSheet( context: context, isScrollControlled: true, builder: (context) => DraggableScrollableSheet( initialChildSize: 0.7, minChildSize: 0.3, maxChildSize: 0.95, expand: false, builder: (context, scrollController) => Container( decoration: const BoxDecoration( color: Colors.white, borderRadius: BorderRadius.vertical(top: Radius.circular(16)), ), child: Column( children: [ // 拖动手柄 Container( margin: const EdgeInsets.only(top: 8), width: 40, height: 4, decoration: BoxDecoration( color: Colors.grey[300], borderRadius: BorderRadius.circular(2), ), ), Expanded( child: ComponentLibraryPanel( initialViewMode: initialViewMode, onComponentSelected: onComponentSelected, ), ), ], ), ), ), ); }