3 changed files with 336 additions and 131 deletions
@ -0,0 +1,226 @@
@@ -0,0 +1,226 @@
|
||||
import 'dart:ui'; |
||||
|
||||
import 'package:flutter/material.dart'; |
||||
import 'package:pluto_grid/pluto_grid.dart'; |
||||
import 'package:win_text_editor/modules/outline/models/outline_node.dart'; |
||||
import 'package:win_text_editor/shared/components/my_pluto_column.dart'; |
||||
import 'package:win_text_editor/shared/components/my_pluto_configuration.dart'; |
||||
import 'package:win_text_editor/shared/components/my_pluto_dropdown_column.dart'; |
||||
|
||||
class NodeTable extends StatefulWidget { |
||||
final List<OutlineNode> members; |
||||
final Function(int, int) onMoveMember; |
||||
final Function(int) onDeleteMember; |
||||
final Function(int) onMoveToTop; |
||||
final Function(int) onMoveToBottom; |
||||
|
||||
const NodeTable({ |
||||
super.key, |
||||
required this.members, |
||||
required this.onMoveMember, |
||||
required this.onDeleteMember, |
||||
required this.onMoveToTop, |
||||
required this.onMoveToBottom, |
||||
}); |
||||
|
||||
@override |
||||
State<NodeTable> createState() => _NodeTableState(); |
||||
} |
||||
|
||||
class _NodeTypeData { |
||||
final String originalType; |
||||
String currentSelection; |
||||
|
||||
_NodeTypeData(this.originalType, this.currentSelection); |
||||
} |
||||
|
||||
class _NodeTableState extends State<NodeTable> { |
||||
PlutoGridStateManager? _stateManager; |
||||
int? _selectedRowIndex; |
||||
final ScrollController _scrollController = ScrollController(); |
||||
|
||||
@override |
||||
Widget build(BuildContext context) { |
||||
return Column( |
||||
children: [ |
||||
// Header with controls |
||||
_buildHeader(), |
||||
// Table |
||||
Expanded( |
||||
child: ScrollConfiguration( |
||||
behavior: ScrollConfiguration.of(context).copyWith( |
||||
dragDevices: { |
||||
PointerDeviceKind.touch, |
||||
PointerDeviceKind.mouse, |
||||
PointerDeviceKind.stylus, |
||||
PointerDeviceKind.trackpad, |
||||
}, |
||||
), |
||||
child: Scrollbar( |
||||
controller: _scrollController, |
||||
thickness: 12.0, |
||||
thumbVisibility: true, |
||||
interactive: true, |
||||
child: SingleChildScrollView( |
||||
controller: _scrollController, |
||||
scrollDirection: Axis.horizontal, |
||||
child: SizedBox( |
||||
width: MediaQuery.of(context).size.width * 0.4, |
||||
child: PlutoGrid( |
||||
key: ValueKey('pluto_grid_${widget.members.length}'), |
||||
configuration: MyPlutoGridConfiguration(), |
||||
columns: _buildColumns(), |
||||
mode: PlutoGridMode.normal, |
||||
rows: widget.members.isEmpty ? [] : _buildRows(widget.members), |
||||
noRowsWidget: const Center(child: Text('')), |
||||
onLoaded: (PlutoGridOnLoadedEvent event) { |
||||
_stateManager = event.stateManager; |
||||
_stateManager?.setSelectingMode(PlutoGridSelectingMode.row); |
||||
_stateManager?.addListener(_handleRowSelection); |
||||
}, |
||||
onRowDoubleTap: (event) { |
||||
_selectedRowIndex = event.rowIdx; |
||||
}, |
||||
), |
||||
), |
||||
), |
||||
), |
||||
), |
||||
), |
||||
], |
||||
); |
||||
} |
||||
|
||||
Widget _buildHeader() { |
||||
final canMoveUp = _selectedRowIndex != null && _selectedRowIndex! > 0; |
||||
final canMoveDown = _selectedRowIndex != null && _selectedRowIndex! < widget.members.length - 1; |
||||
final canDelete = _selectedRowIndex != null; |
||||
|
||||
return Padding( |
||||
padding: const EdgeInsets.all(2.0), |
||||
child: Row( |
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween, |
||||
children: [ |
||||
const Text('已添加节点', style: TextStyle(fontWeight: FontWeight.bold)), |
||||
Row( |
||||
children: [ |
||||
IconButton( |
||||
icon: Icon( |
||||
Icons.arrow_upward, |
||||
size: 14, |
||||
color: canMoveUp ? Colors.blue : Colors.grey, |
||||
), |
||||
onPressed: canMoveUp ? () => widget.onMoveMember(_selectedRowIndex!, -1) : null, |
||||
tooltip: '上移一行', |
||||
), |
||||
IconButton( |
||||
icon: Icon( |
||||
Icons.vertical_align_top, |
||||
size: 14, |
||||
color: canMoveUp ? Colors.blue : Colors.grey, |
||||
), |
||||
onPressed: canMoveUp ? () => widget.onMoveToTop(_selectedRowIndex!) : null, |
||||
tooltip: '上移到顶', |
||||
), |
||||
IconButton( |
||||
icon: Icon( |
||||
Icons.arrow_downward, |
||||
size: 14, |
||||
color: canMoveDown ? Colors.blue : Colors.grey, |
||||
), |
||||
onPressed: canMoveDown ? () => widget.onMoveMember(_selectedRowIndex!, 1) : null, |
||||
tooltip: '下移一行', |
||||
), |
||||
IconButton( |
||||
icon: Icon( |
||||
Icons.vertical_align_bottom, |
||||
size: 14, |
||||
color: canMoveDown ? Colors.blue : Colors.grey, |
||||
), |
||||
onPressed: canMoveDown ? () => widget.onMoveToBottom(_selectedRowIndex!) : null, |
||||
tooltip: '下移到底', |
||||
), |
||||
IconButton( |
||||
icon: Icon(Icons.delete, size: 14, color: canDelete ? Colors.red : Colors.grey), |
||||
onPressed: canDelete ? () => widget.onDeleteMember(_selectedRowIndex!) : null, |
||||
tooltip: '删除行', |
||||
), |
||||
], |
||||
), |
||||
], |
||||
), |
||||
); |
||||
} |
||||
|
||||
void _handleRowSelection() { |
||||
if (_stateManager?.currentRow == null) return; |
||||
setState(() { |
||||
_selectedRowIndex = _stateManager?.currentRow?.sortIdx; |
||||
}); |
||||
} |
||||
|
||||
final Map<Key, _NodeTypeData> _nodeTypeData = {}; |
||||
|
||||
List<PlutoColumn> _buildColumns() { |
||||
return [ |
||||
MyPlutoColumn(title: '#', field: 'rowNum', width: 40), |
||||
MyPlutoColumn(title: '成员', field: 'content', width: 300), |
||||
MyPlutoDropdownColumn( |
||||
title: '操作类型', |
||||
field: 'type', |
||||
width: 120, |
||||
optionsBuilder: (rowKey) { |
||||
final data = _nodeTypeData[rowKey as ValueKey]; |
||||
return _getOptions(data?.originalType ?? ''); |
||||
}, |
||||
), |
||||
]; |
||||
} |
||||
|
||||
List<PlutoRow> _buildRows(List<OutlineNode> members) { |
||||
return members |
||||
.asMap() |
||||
.map((index, member) { |
||||
final rowKey = ValueKey('${member.hashCode}_$index'); |
||||
final options = _getOptions(member.value); |
||||
final initialValue = options.isNotEmpty ? options.first : ''; |
||||
|
||||
_nodeTypeData[rowKey] = _NodeTypeData(member.value, initialValue); |
||||
|
||||
return MapEntry( |
||||
index, |
||||
PlutoRow( |
||||
key: rowKey, |
||||
cells: { |
||||
'rowNum': PlutoCell(value: index + 1), |
||||
'content': PlutoCell(value: member.title), |
||||
'type': PlutoCell(value: initialValue), |
||||
}, |
||||
), |
||||
); |
||||
}) |
||||
.values |
||||
.toList(); |
||||
} |
||||
|
||||
List<String> _getOptions(String type) { |
||||
switch (type) { |
||||
case 'Atom': |
||||
case 'Business': |
||||
return ['普通调用', '事务调用']; |
||||
case 'UFTTable': |
||||
return ['遍历记录', '获取记录', '插入记录', '修改记录', '删除记录']; |
||||
case 'Component': |
||||
return ['遍历组件', '获取组件', '插入组件', '修改组件', '尾部插入组件']; |
||||
default: |
||||
return ['']; // 确保返回非空列表 |
||||
} |
||||
} |
||||
|
||||
@override |
||||
void dispose() { |
||||
_stateManager?.removeListener(_handleRowSelection); |
||||
_scrollController.dispose(); |
||||
super.dispose(); |
||||
} |
||||
} |
@ -0,0 +1,73 @@
@@ -0,0 +1,73 @@
|
||||
import 'package:flutter/material.dart'; |
||||
import 'package:pluto_grid/pluto_grid.dart'; |
||||
|
||||
class MyPlutoDropdownColumn extends PlutoColumn { |
||||
final List<String> Function(Key rowKey)? optionsBuilder; |
||||
|
||||
MyPlutoDropdownColumn({ |
||||
required String title, |
||||
required String field, |
||||
this.optionsBuilder, |
||||
double width = 200, |
||||
bool checkable = false, |
||||
bool enableSorting = false, |
||||
bool autoSize = false, |
||||
PlutoColumnTextAlign textAlign = PlutoColumnTextAlign.start, |
||||
}) : super( |
||||
title: title, |
||||
field: field, |
||||
type: PlutoColumnType.text(defaultValue: ''), |
||||
width: width, |
||||
enableRowChecked: checkable, |
||||
enableContextMenu: false, |
||||
enableEditingMode: true, |
||||
enableSorting: enableSorting, |
||||
readOnly: false, |
||||
backgroundColor: Colors.grey[100], |
||||
textAlign: textAlign, |
||||
titleTextAlign: PlutoColumnTextAlign.center, |
||||
suppressedAutoSize: !autoSize, |
||||
renderer: (rendererContext) { |
||||
final rowKey = rendererContext.row.key; |
||||
final options = optionsBuilder?.call(rowKey) ?? []; |
||||
final currentValue = rendererContext.row.cells[field]!.value.toString(); |
||||
|
||||
return DropdownButton<String>( |
||||
value: options.contains(currentValue) |
||||
? currentValue |
||||
: (options.isNotEmpty ? options.first : ''), |
||||
items: options |
||||
.map((value) => DropdownMenuItem( |
||||
value: value, |
||||
child: Text(value), |
||||
)) |
||||
.toList(), |
||||
onChanged: (newValue) { |
||||
if (newValue != null) { |
||||
rendererContext.stateManager.changeCellValue( |
||||
rendererContext.row.cells[field]!, |
||||
newValue, |
||||
); |
||||
} |
||||
}, |
||||
// 美化样式 |
||||
icon: const Padding( |
||||
padding: EdgeInsets.only(right: 8.0), |
||||
child: Icon(Icons.arrow_drop_down, size: 24), |
||||
), |
||||
iconSize: 24, |
||||
isExpanded: true, |
||||
underline: Container(), // 移除下划线 |
||||
style: TextStyle( |
||||
fontSize: 14, |
||||
color: Colors.black87, |
||||
), |
||||
dropdownColor: Colors.white, |
||||
elevation: 2, |
||||
borderRadius: BorderRadius.circular(4), |
||||
// 对齐方式 |
||||
alignment: Alignment.centerLeft, |
||||
); |
||||
}, |
||||
); |
||||
} |
Loading…
Reference in new issue