10 changed files with 627 additions and 209 deletions
@ -0,0 +1,181 @@
@@ -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 @@
@@ -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 @@
@@ -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 @@
@@ -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 @@
@@ -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