From 998eb716dcdcd7a4fdbab9a2d5db82d10d709cd4 Mon Sep 17 00:00:00 2001 From: wangjia <809946525@qq.com> Date: Tue, 14 Apr 2026 00:38:56 +0800 Subject: [PATCH] =?UTF-8?q?fix(client):=20=E4=BF=AE=E5=A4=8D=E8=A1=A8?= =?UTF-8?q?=E6=A0=BC=20hover=20=E6=95=88=E6=9E=9C=E4=B8=8D=E7=94=9F?= =?UTF-8?q?=E6=95=88?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit DataTable 只有设了 onSelectChanged 才追踪鼠标状态,但加回调会 出现复选框。改用 Table + IntrinsicColumnWidth 保持列宽自动计算, 每行通过 MouseRegion + ValueNotifier 共享悬停状态,真正实现 行级 hover 高亮(#F5F7FF)。 Co-Authored-By: Claude Sonnet 4.6 --- client/lib/widgets/data_table_card.dart | 123 +++++++++++++++++------- 1 file changed, 89 insertions(+), 34 deletions(-) diff --git a/client/lib/widgets/data_table_card.dart b/client/lib/widgets/data_table_card.dart index 7419dc9..4cb1caf 100644 --- a/client/lib/widgets/data_table_card.dart +++ b/client/lib/widgets/data_table_card.dart @@ -1,7 +1,7 @@ import 'package:flutter/material.dart'; import '../core/theme/app_theme.dart'; -class DataTableCard extends StatelessWidget { +class DataTableCard extends StatefulWidget { final List columns; final List rows; final Widget? toolbar; @@ -21,48 +21,51 @@ class DataTableCard extends StatelessWidget { this.onPageChanged, }); + @override + State createState() => _DataTableCardState(); +} + +class _DataTableCardState extends State { + // 当前悬停行的索引(-1 表示无悬停) + final _hoveredRow = ValueNotifier(-1); + + @override + void dispose() { + _hoveredRow.dispose(); + super.dispose(); + } + @override Widget build(BuildContext context) { return Column( children: [ - if (toolbar != null) + if (widget.toolbar != null) Container( color: AppTheme.surface, padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 6), - child: toolbar!, + child: widget.toolbar!, ), - if (toolbar != null) const Divider(height: 1), + if (widget.toolbar != null) const Divider(height: 1), Expanded( child: LayoutBuilder( builder: (context, constraints) => SingleChildScrollView( child: SingleChildScrollView( scrollDirection: Axis.horizontal, child: ConstrainedBox( - constraints: - BoxConstraints(minWidth: constraints.maxWidth), - child: DataTable( - headingRowColor: WidgetStateProperty.all( - const Color(0xFFF0F4FF)), - dataRowColor: WidgetStateProperty.resolveWith((states) { - if (states.contains(WidgetState.hovered)) { - return const Color(0xFFF5F7FF); - } - return null; - }), - headingTextStyle: const TextStyle( - fontSize: 13, - fontWeight: FontWeight.w600, - color: AppTheme.primaryDark, + constraints: BoxConstraints(minWidth: constraints.maxWidth), + child: Table( + defaultColumnWidth: const IntrinsicColumnWidth(), + border: TableBorder( + horizontalInside: BorderSide( + color: Colors.grey.shade200, + width: 0.5, + ), ), - dataTextStyle: const TextStyle( - fontSize: 13, color: AppTheme.textPrimary), - columnSpacing: 24, - horizontalMargin: 16, - dataRowMinHeight: 40, - dataRowMaxHeight: 56, - dividerThickness: 0.5, - columns: columns, - rows: rows, + children: [ + _buildHeaderRow(), + for (int i = 0; i < widget.rows.length; i++) + _buildDataRow(i, widget.rows[i]), + ], ), ), ), @@ -70,7 +73,7 @@ class DataTableCard extends StatelessWidget { ), ), // Pagination - if (totalCount != null) + if (widget.totalCount != null) Container( height: 48, color: AppTheme.surface, @@ -78,16 +81,16 @@ class DataTableCard extends StatelessWidget { child: Row( children: [ Text( - '共 $totalCount 条记录', + '共 ${widget.totalCount} 条记录', style: const TextStyle( fontSize: 13, color: AppTheme.textSecondary), ), const Spacer(), _PaginationBar( - page: page, - total: totalCount!, - pageSize: pageSize, - onPageChanged: onPageChanged, + page: widget.page, + total: widget.totalCount!, + pageSize: widget.pageSize, + onPageChanged: widget.onPageChanged, ), ], ), @@ -95,6 +98,58 @@ class DataTableCard extends StatelessWidget { ], ); } + + TableRow _buildHeaderRow() { + return TableRow( + decoration: const BoxDecoration(color: Color(0xFFF0F4FF)), + children: widget.columns.map((col) { + return Padding( + padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 12), + child: DefaultTextStyle( + style: const TextStyle( + fontSize: 13, + fontWeight: FontWeight.w600, + color: AppTheme.primaryDark, + ), + child: col.numeric + ? Align(alignment: Alignment.centerRight, child: col.label) + : col.label, + ), + ); + }).toList(), + ); + } + + TableRow _buildDataRow(int rowIndex, DataRow row) { + return TableRow( + children: List.generate(row.cells.length, (colIndex) { + final cell = row.cells[colIndex]; + final col = widget.columns[colIndex]; + return MouseRegion( + onEnter: (_) => _hoveredRow.value = rowIndex, + onExit: (_) { + if (_hoveredRow.value == rowIndex) _hoveredRow.value = -1; + }, + child: ValueListenableBuilder( + valueListenable: _hoveredRow, + builder: (context, hovered, _) => Container( + constraints: const BoxConstraints(minHeight: 48), + alignment: + col.numeric ? Alignment.centerRight : Alignment.centerLeft, + color: hovered == rowIndex ? const Color(0xFFF5F7FF) : null, + padding: + const EdgeInsets.symmetric(horizontal: 16, vertical: 10), + child: DefaultTextStyle( + style: const TextStyle( + fontSize: 13, color: AppTheme.textPrimary), + child: cell.child, + ), + ), + ), + ); + }), + ); + } } class _PaginationBar extends StatelessWidget {