feat(client): 筛选项移入列头,hover 显示
新增 FilterableColumnHeader widget:鼠标悬停列头时显示漏斗图标, 有活跃筛选时图标常驻并变为主色调,点击打开多选弹窗,活跃时右侧 显示一键清除按钮。 入库单/出库单列表:仓库、供应商/客户列头换用 FilterableColumnHeader, 工具栏从两行合并为一行(移除独立筛选按钮行,显示字段按钮移至右侧)。 Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -143,6 +143,83 @@ class _MultiSelectDialogState extends State<_MultiSelectDialog> {
|
||||
}
|
||||
}
|
||||
|
||||
/// Column header widget that shows a filter icon on hover.
|
||||
/// Clicking the icon opens the same multi-select dialog as [MultiSelectDropdown].
|
||||
/// Use as [DataColumn.label] for filterable columns.
|
||||
class FilterableColumnHeader extends StatefulWidget {
|
||||
final String text;
|
||||
final List<String> options;
|
||||
final Set<String> selected;
|
||||
final ValueChanged<Set<String>> onChanged;
|
||||
|
||||
const FilterableColumnHeader({
|
||||
super.key,
|
||||
required this.text,
|
||||
required this.options,
|
||||
required this.selected,
|
||||
required this.onChanged,
|
||||
});
|
||||
|
||||
@override
|
||||
State<FilterableColumnHeader> createState() => _FilterableColumnHeaderState();
|
||||
}
|
||||
|
||||
class _FilterableColumnHeaderState extends State<FilterableColumnHeader> {
|
||||
bool _hovered = false;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final active = widget.selected.isNotEmpty;
|
||||
final showIcon = _hovered || active;
|
||||
|
||||
return MouseRegion(
|
||||
onEnter: (_) => setState(() => _hovered = true),
|
||||
onExit: (_) => setState(() => _hovered = false),
|
||||
child: Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Text(widget.text),
|
||||
AnimatedOpacity(
|
||||
opacity: showIcon ? 1.0 : 0.0,
|
||||
duration: const Duration(milliseconds: 120),
|
||||
child: InkWell(
|
||||
onTap: widget.options.isEmpty ? null : () => _show(context),
|
||||
borderRadius: BorderRadius.circular(4),
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 2),
|
||||
child: Icon(
|
||||
active ? Icons.filter_alt : Icons.filter_alt_outlined,
|
||||
size: 13,
|
||||
color: active ? AppTheme.primary : AppTheme.textSecondary,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
if (active)
|
||||
InkWell(
|
||||
onTap: () => widget.onChanged({}),
|
||||
borderRadius: BorderRadius.circular(4),
|
||||
child: const Icon(Icons.cancel, size: 12, color: AppTheme.primary),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
void _show(BuildContext context) {
|
||||
showDialog(
|
||||
context: context,
|
||||
barrierColor: Colors.black12,
|
||||
builder: (_) => _MultiSelectDialog(
|
||||
label: widget.text,
|
||||
options: widget.options,
|
||||
initial: widget.selected,
|
||||
onApply: widget.onChanged,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// Column definition for column visibility toggle
|
||||
class ColDef {
|
||||
final String key;
|
||||
|
||||
Reference in New Issue
Block a user