3 changed files with 336 additions and 131 deletions
@ -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 @@ |
|||||||
|
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