10 changed files with 627 additions and 209 deletions
@ -0,0 +1,181 @@ |
|||||||
|
// filter_controller.dart |
||||||
|
import 'package:flutter/foundation.dart'; |
||||||
|
import 'package:win_text_editor/modules/template_parser/models/template_node.dart'; |
||||||
|
import 'template_notifier.dart'; |
||||||
|
|
||||||
|
class FilterController extends TemplateNotifier { |
||||||
|
String? _selectedFilterField; |
||||||
|
String? _selectedFilterOperator; |
||||||
|
String _filterValue = ''; |
||||||
|
bool _isFilterValid = false; |
||||||
|
bool _isFilterApplied = false; |
||||||
|
List<String> _availableFields = []; |
||||||
|
|
||||||
|
// 仅按钮需要全局通知的Notifier |
||||||
|
final ValueNotifier<bool> _filterActionNotifier = ValueNotifier(false); |
||||||
|
|
||||||
|
String? get selectedFilterField => _selectedFilterField; |
||||||
|
String? get selectedFilterOperator => _selectedFilterOperator; |
||||||
|
String get filterValue => _filterValue; |
||||||
|
bool get isFilterValid => _isFilterValid; |
||||||
|
bool get isFilterApplied => _isFilterApplied; |
||||||
|
List<String> get availableFields => _availableFields; |
||||||
|
|
||||||
|
ValueNotifier<bool> get filterActionNotifier => _filterActionNotifier; |
||||||
|
|
||||||
|
set selectedFilterOperator(String? value) { |
||||||
|
_selectedFilterOperator = value; |
||||||
|
_updateFilterValidity(); |
||||||
|
// 仅通知本地监听器(下拉框变化) |
||||||
|
safeNotify(); |
||||||
|
} |
||||||
|
|
||||||
|
set filterValue(String value) { |
||||||
|
_filterValue = value; |
||||||
|
_updateFilterValidity(); |
||||||
|
// 仅通知本地监听器(输入框变化) |
||||||
|
safeNotify(); |
||||||
|
} |
||||||
|
|
||||||
|
set selectedFilterField(String? value) { |
||||||
|
// 当设置的值不在可用字段中时自动清空 |
||||||
|
if (value != null && !_availableFields.contains(value)) { |
||||||
|
_selectedFilterField = null; |
||||||
|
} else { |
||||||
|
_selectedFilterField = value; |
||||||
|
} |
||||||
|
_updateFilterValidity(); |
||||||
|
safeNotify(); |
||||||
|
} |
||||||
|
|
||||||
|
void updateAvailableFields(List<String> newFields) { |
||||||
|
_availableFields = newFields; |
||||||
|
// 检查当前选中值是否仍然有效 |
||||||
|
if (_selectedFilterField != null && !newFields.contains(_selectedFilterField)) { |
||||||
|
_selectedFilterField = null; |
||||||
|
} |
||||||
|
_updateFilterValidity(); |
||||||
|
safeNotify(); |
||||||
|
} |
||||||
|
|
||||||
|
void _updateFilterValidity() { |
||||||
|
_isFilterValid = _checkFilterValidity(); |
||||||
|
} |
||||||
|
|
||||||
|
bool _checkFilterValidity() { |
||||||
|
if (_selectedFilterField == null || _selectedFilterOperator == null || _filterValue.isEmpty) { |
||||||
|
return false; |
||||||
|
} |
||||||
|
|
||||||
|
final operator = _selectedFilterOperator!; |
||||||
|
final value = _filterValue; |
||||||
|
|
||||||
|
if (operator == 'between' || operator == 'in') { |
||||||
|
final parts = value.split(',').map((e) => e.trim()).where((e) => e.isNotEmpty).toList(); |
||||||
|
if (parts.length < 2) { |
||||||
|
return false; |
||||||
|
} |
||||||
|
|
||||||
|
if (operator == 'between') { |
||||||
|
for (var part in parts) { |
||||||
|
if (double.tryParse(part) == null) { |
||||||
|
return false; |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
} else if (operator == '>' || operator == '<') { |
||||||
|
return double.tryParse(value) != null; |
||||||
|
} |
||||||
|
|
||||||
|
return true; |
||||||
|
} |
||||||
|
|
||||||
|
//进行过滤处理,供外部获取调用,提供原始列表,获取过滤结果 |
||||||
|
List<TemplateItem> doFilter(List<TemplateItem> templateItems) { |
||||||
|
if (selectedFilterField == null || selectedFilterOperator == null) { |
||||||
|
return templateItems; |
||||||
|
} |
||||||
|
|
||||||
|
List<TemplateItem> filteredItems = []; |
||||||
|
|
||||||
|
// 1. 找出所有匹配的主项 |
||||||
|
final primaryMatches = templateItems.where((item) { |
||||||
|
final nodeName = item.xPath.split('/').last; |
||||||
|
return nodeName == selectedFilterField && _matchesCondition(item.value); |
||||||
|
}); |
||||||
|
|
||||||
|
// 2. 收集这些主项的ID(同实例的所有项共享相同ID) |
||||||
|
final matchedIds = primaryMatches.map((item) => item.id).toSet(); |
||||||
|
|
||||||
|
// 3. 过滤出同实例的所有项 |
||||||
|
filteredItems.cast(); |
||||||
|
|
||||||
|
filteredItems.addAll( |
||||||
|
filteredItems.where((item) { |
||||||
|
return matchedIds.contains(item.id); |
||||||
|
}).toList(), |
||||||
|
); |
||||||
|
|
||||||
|
_isFilterApplied = true; |
||||||
|
return filteredItems; |
||||||
|
} |
||||||
|
|
||||||
|
// 检查值是否符合条件 |
||||||
|
bool _matchesCondition(String value) { |
||||||
|
if (selectedFilterOperator == null || filterValue.isEmpty) { |
||||||
|
return false; |
||||||
|
} |
||||||
|
|
||||||
|
switch (selectedFilterOperator!) { |
||||||
|
case '==': |
||||||
|
return value == filterValue; |
||||||
|
|
||||||
|
case '>': |
||||||
|
final numValue = double.tryParse(value); |
||||||
|
final filterNum = double.tryParse(filterValue); |
||||||
|
return numValue != null && filterNum != null && numValue > filterNum; |
||||||
|
|
||||||
|
case '<': |
||||||
|
final numValue = double.tryParse(value); |
||||||
|
final filterNum = double.tryParse(filterValue); |
||||||
|
return numValue != null && filterNum != null && numValue < filterNum; |
||||||
|
|
||||||
|
case 'between': |
||||||
|
final parts = |
||||||
|
filterValue.split(',').map((e) => e.trim()).where((e) => e.isNotEmpty).toList(); |
||||||
|
|
||||||
|
if (parts.length < 2) return false; |
||||||
|
|
||||||
|
final lower = double.tryParse(parts[0]); |
||||||
|
final upper = double.tryParse(parts[1]); |
||||||
|
final numValue = double.tryParse(value); |
||||||
|
|
||||||
|
return numValue != null && |
||||||
|
lower != null && |
||||||
|
upper != null && |
||||||
|
numValue >= lower && |
||||||
|
numValue <= upper; |
||||||
|
|
||||||
|
case 'in': |
||||||
|
final filterValues = |
||||||
|
filterValue.split(',').map((e) => e.trim()).where((e) => e.isNotEmpty).toList(); |
||||||
|
|
||||||
|
return filterValues.contains(value); |
||||||
|
|
||||||
|
default: |
||||||
|
return false; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
//执行过滤 |
||||||
|
void applyFilter() { |
||||||
|
_isFilterApplied = true; |
||||||
|
_filterActionNotifier.value = !_filterActionNotifier.value; |
||||||
|
} |
||||||
|
|
||||||
|
// 取消过滤 |
||||||
|
void clearFilter() { |
||||||
|
_isFilterApplied = false; |
||||||
|
_filterActionNotifier.value = !_filterActionNotifier.value; |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,53 @@ |
|||||||
|
// grid_view_controller.dart |
||||||
|
import 'package:win_text_editor/modules/template_parser/models/template_node.dart'; |
||||||
|
import 'template_notifier.dart'; |
||||||
|
|
||||||
|
class GridViewController extends TemplateNotifier { |
||||||
|
List<TemplateItem> _templateItems = []; |
||||||
|
List<TemplateItem> _filteredItems = []; |
||||||
|
bool _isFilterApplied = false; |
||||||
|
|
||||||
|
List<TemplateItem> get displayedItems => _isFilterApplied ? _filteredItems : _templateItems; |
||||||
|
bool get isFilterApplied => _isFilterApplied; |
||||||
|
List<TemplateItem> get templateItems => _templateItems; |
||||||
|
|
||||||
|
// 新增方法:更新节点引用 |
||||||
|
List<TemplateNode>? _currentTreeNodes; |
||||||
|
void updateTreeNodesRef(List<TemplateNode> nodes) { |
||||||
|
_currentTreeNodes = nodes; |
||||||
|
safeNotify(); |
||||||
|
} |
||||||
|
|
||||||
|
List<TemplateNode> getSelectedNodes() { |
||||||
|
if (_currentTreeNodes == null) return []; |
||||||
|
|
||||||
|
List<TemplateNode> selectedNodes = []; |
||||||
|
void traverse(TemplateNode node) { |
||||||
|
if (node.isChecked) selectedNodes.add(node); |
||||||
|
for (var child in node.children) { |
||||||
|
traverse(child); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
for (var node in _currentTreeNodes!) { |
||||||
|
traverse(node); |
||||||
|
} |
||||||
|
return selectedNodes; |
||||||
|
} |
||||||
|
|
||||||
|
void updateTemplateItems(List<TemplateItem> items) { |
||||||
|
_templateItems = items; |
||||||
|
safeNotify(); |
||||||
|
} |
||||||
|
|
||||||
|
void applyFilter(List<TemplateItem> filteredItems) { |
||||||
|
_filteredItems = filteredItems; |
||||||
|
_isFilterApplied = true; |
||||||
|
safeNotify(); |
||||||
|
} |
||||||
|
|
||||||
|
void clearFilter() { |
||||||
|
_isFilterApplied = false; |
||||||
|
safeNotify(); |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,9 @@ |
|||||||
|
// template_notifier.dart |
||||||
|
import 'package:flutter/foundation.dart'; |
||||||
|
|
||||||
|
abstract class TemplateNotifier extends ChangeNotifier { |
||||||
|
@protected |
||||||
|
void safeNotify() { |
||||||
|
if (hasListeners) notifyListeners(); |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,68 @@ |
|||||||
|
// tree_view_controller.dart |
||||||
|
|
||||||
|
import 'package:win_text_editor/modules/template_parser/models/template_node.dart'; |
||||||
|
import 'template_notifier.dart'; |
||||||
|
|
||||||
|
class TreeViewController extends TemplateNotifier { |
||||||
|
//根节点 |
||||||
|
List<TemplateNode> _treeNodes = []; |
||||||
|
TemplateNode? _selectedNode; |
||||||
|
String? _currentParentPath; |
||||||
|
|
||||||
|
List<TemplateNode> get treeNodes => _treeNodes; |
||||||
|
TemplateNode? get selectedNode => _selectedNode; |
||||||
|
|
||||||
|
// 加载树视图,当文件路径改变时调用 |
||||||
|
void updateTreeNodes(List<TemplateNode> nodes) { |
||||||
|
_treeNodes = nodes; |
||||||
|
safeNotify(); |
||||||
|
} |
||||||
|
|
||||||
|
void selectTreeNode(TemplateNode node) { |
||||||
|
_selectedNode = node; |
||||||
|
safeNotify(); |
||||||
|
} |
||||||
|
|
||||||
|
// 选择节点,多选时仅可选中同一层级的节点 |
||||||
|
void toggleNodeCheck(TemplateNode node) { |
||||||
|
final parentPath = node.path.substring(0, node.path.lastIndexOf('/')); |
||||||
|
if (_currentParentPath != null && _currentParentPath != parentPath) { |
||||||
|
clearAllChecked(); |
||||||
|
} |
||||||
|
node.isChecked = !node.isChecked; |
||||||
|
_currentParentPath = parentPath; |
||||||
|
safeNotify(); |
||||||
|
} |
||||||
|
|
||||||
|
void clearAllChecked() { |
||||||
|
void traverse(TemplateNode node) { |
||||||
|
node.isChecked = false; |
||||||
|
for (var child in node.children) { |
||||||
|
traverse(child); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
for (var node in _treeNodes) { |
||||||
|
traverse(node); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
List<String> get selectedNodeNames { |
||||||
|
List<String> selectedNodeNames = []; |
||||||
|
|
||||||
|
void traverse(TemplateNode node) { |
||||||
|
if (node.isChecked) { |
||||||
|
selectedNodeNames.add(node.name); |
||||||
|
} |
||||||
|
for (var child in node.children) { |
||||||
|
traverse(child); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
for (var node in _treeNodes) { |
||||||
|
traverse(node); |
||||||
|
} |
||||||
|
|
||||||
|
return selectedNodeNames; |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,140 @@ |
|||||||
|
import 'package:flutter/material.dart'; |
||||||
|
import 'package:provider/provider.dart'; |
||||||
|
import 'package:win_text_editor/modules/template_parser/controllers/filter_controller.dart'; |
||||||
|
|
||||||
|
class TemplateFilterPanel extends StatefulWidget { |
||||||
|
final FilterController controller; |
||||||
|
|
||||||
|
const TemplateFilterPanel({super.key, required this.controller}); |
||||||
|
|
||||||
|
@override |
||||||
|
State<TemplateFilterPanel> createState() => _TemplateFilterPanelState(); |
||||||
|
} |
||||||
|
|
||||||
|
class _TemplateFilterPanelState extends State<TemplateFilterPanel> { |
||||||
|
late TextEditingController _textController; |
||||||
|
|
||||||
|
@override |
||||||
|
void initState() { |
||||||
|
super.initState(); |
||||||
|
_textController = TextEditingController(text: widget.controller.filterValue); |
||||||
|
} |
||||||
|
|
||||||
|
@override |
||||||
|
void didUpdateWidget(covariant TemplateFilterPanel oldWidget) { |
||||||
|
super.didUpdateWidget(oldWidget); |
||||||
|
if (widget.controller.filterValue != _textController.text) { |
||||||
|
_textController.text = widget.controller.filterValue; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
@override |
||||||
|
void dispose() { |
||||||
|
_textController.dispose(); |
||||||
|
super.dispose(); |
||||||
|
} |
||||||
|
|
||||||
|
@override |
||||||
|
Widget build(BuildContext context) { |
||||||
|
return Consumer<FilterController>( |
||||||
|
builder: (context, controller, _) { |
||||||
|
return Column( |
||||||
|
crossAxisAlignment: CrossAxisAlignment.start, |
||||||
|
children: [ |
||||||
|
const SizedBox(height: 16), |
||||||
|
Padding( |
||||||
|
padding: const EdgeInsets.symmetric(horizontal: 8.0), |
||||||
|
child: Row( |
||||||
|
children: [ |
||||||
|
Expanded( |
||||||
|
child: DropdownButtonFormField<String>( |
||||||
|
decoration: const InputDecoration( |
||||||
|
labelText: '过滤字段', |
||||||
|
border: OutlineInputBorder(), |
||||||
|
), |
||||||
|
items: |
||||||
|
controller.availableFields.map((String value) { |
||||||
|
return DropdownMenuItem<String>(value: value, child: Text(value)); |
||||||
|
}).toList(), |
||||||
|
onChanged: (value) { |
||||||
|
controller.selectedFilterField = value; |
||||||
|
}, |
||||||
|
value: controller.selectedFilterField, |
||||||
|
), |
||||||
|
), |
||||||
|
const SizedBox(width: 16), |
||||||
|
SizedBox( |
||||||
|
width: 120, |
||||||
|
child: DropdownButtonFormField<String>( |
||||||
|
decoration: const InputDecoration( |
||||||
|
labelText: '匹配方式', |
||||||
|
border: OutlineInputBorder(), |
||||||
|
), |
||||||
|
items: const [ |
||||||
|
DropdownMenuItem(value: '==', child: Text('==')), |
||||||
|
DropdownMenuItem(value: '>', child: Text('>')), |
||||||
|
DropdownMenuItem(value: '<', child: Text('<')), |
||||||
|
DropdownMenuItem(value: 'between', child: Text('between')), |
||||||
|
DropdownMenuItem(value: 'in', child: Text('in')), |
||||||
|
], |
||||||
|
onChanged: (value) { |
||||||
|
controller.selectedFilterOperator = value; |
||||||
|
}, |
||||||
|
value: controller.selectedFilterOperator, |
||||||
|
), |
||||||
|
), |
||||||
|
], |
||||||
|
), |
||||||
|
), |
||||||
|
const SizedBox(height: 16), |
||||||
|
Padding( |
||||||
|
padding: const EdgeInsets.symmetric(horizontal: 8.0), |
||||||
|
child: SizedBox( |
||||||
|
height: 190, |
||||||
|
child: TextField( |
||||||
|
maxLines: null, |
||||||
|
expands: true, |
||||||
|
decoration: const InputDecoration( |
||||||
|
labelText: '过滤条件值', |
||||||
|
border: OutlineInputBorder(), |
||||||
|
alignLabelWithHint: true, |
||||||
|
), |
||||||
|
controller: _textController, |
||||||
|
onChanged: (value) { |
||||||
|
widget.controller.filterValue = value; |
||||||
|
}, |
||||||
|
), |
||||||
|
), |
||||||
|
), |
||||||
|
const SizedBox(height: 16), |
||||||
|
// 修改按钮部分代码 |
||||||
|
Padding( |
||||||
|
padding: const EdgeInsets.symmetric(horizontal: 8.0), |
||||||
|
child: Row( |
||||||
|
mainAxisAlignment: MainAxisAlignment.end, |
||||||
|
children: [ |
||||||
|
ElevatedButton( |
||||||
|
onPressed: |
||||||
|
controller.selectedFilterField != null && |
||||||
|
controller.selectedFilterOperator != null && |
||||||
|
controller.isFilterValid |
||||||
|
? () => controller.applyFilter() |
||||||
|
: null, |
||||||
|
child: const Text('过滤'), |
||||||
|
), |
||||||
|
const SizedBox(width: 16), |
||||||
|
ElevatedButton( |
||||||
|
onPressed: () { |
||||||
|
controller.applyFilter(); |
||||||
|
}, |
||||||
|
child: const Text('刷新'), |
||||||
|
), |
||||||
|
], |
||||||
|
), |
||||||
|
), |
||||||
|
], |
||||||
|
); |
||||||
|
}, |
||||||
|
); |
||||||
|
} |
||||||
|
} |
Loading…
Reference in new issue