part of 'studio_app.dart'; class StudioHistoryPage extends StatefulWidget { const StudioHistoryPage({ super.key, required this.tab, required this.runtime, required this.onAction, }); final StudioTab tab; final StudioRuntime runtime; final void Function(String title, String detail) onAction; @override State createState() => _StudioHistoryPageState(); } class _StudioHistoryPageState extends State { bool _loading = true; String? _errorMessage; List> _rows = >[]; @override void initState() { super.initState(); _reload(); } Future _reload() async { setState(() { _loading = true; _errorMessage = null; }); try { final rows = await widget.runtime.client.fetchUpgradeLogs( session: widget.runtime.session, ); if (!mounted) { return; } setState(() { _rows = rows; }); } catch (error, stackTrace) { _printError('加载历史失败', error, stackTrace); if (!mounted) { return; } setState(() { _errorMessage = _normalizeError(error); }); } finally { if (mounted) { setState(() { _loading = false; }); } } } void _printError(String title, Object error, StackTrace stackTrace) { debugPrint('========== $title =========='); debugPrint('error: $error'); debugPrint('stackTrace:\n$stackTrace'); debugPrint('========== $title end =========='); } String _normalizeError(Object error) { final text = error.toString(); const marker = 'Bad state: '; if (text.startsWith(marker)) { return text.substring(marker.length); } return text; } String _value(Map row, List keys, [String fallback = '']) { for (final key in keys) { final value = row[key]; if (value != null && value.toString().trim().isNotEmpty) { return value.toString(); } } return fallback; } @override Widget build(BuildContext context) { return SingleChildScrollView( child: Card( child: Padding( padding: const EdgeInsets.all(12), child: Column( crossAxisAlignment: CrossAxisAlignment.stretch, children: [ Row( children: [ Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( widget.tab.title, style: Theme.of(context).textTheme.titleMedium ?.copyWith(fontWeight: FontWeight.w700), ), const SizedBox(height: 4), Text( '从后端读取发布与修改历史。', style: Theme.of(context).textTheme.bodySmall ?.copyWith(color: _mutedColor(context)), ), ], ), ), FilledButton.tonalIcon( onPressed: _loading ? null : _reload, icon: _loading ? const SizedBox( width: 16, height: 16, child: CircularProgressIndicator(strokeWidth: 2), ) : const Icon(Icons.refresh_rounded), label: const Text('刷新'), ), ], ), const SizedBox(height: 8), if (_errorMessage != null) ...[ SelectableText( _errorMessage!, style: Theme.of(context).textTheme.bodyMedium?.copyWith( color: Theme.of(context).colorScheme.error, ), ), const SizedBox(height: 12), ], if (_loading) const Padding( padding: EdgeInsets.symmetric(vertical: 16), child: Center(child: CircularProgressIndicator()), ) else SizedBox( width: double.infinity, child: DataTable( columns: const [ DataColumn(label: Text('版本')), DataColumn(label: Text('操作者')), DataColumn(label: Text('时间')), DataColumn(label: Text('状态')), DataColumn(label: Text('说明')), ], rows: _rows .map( (row) => DataRow( cells: [ DataCell(Text(_value(row, const ['version', 'tag', 'name'], ''))), DataCell(Text(_value(row, const ['user', 'operator', 'creator'], ''))), DataCell(Text(_value(row, const ['time', 'createTime', 'date'], ''))), DataCell(Text(_value(row, const ['status', 'state'], ''))), DataCell( SelectableText( _value( row, const ['note', 'msg', 'message', 'summary', 'desc'], row.toString(), ), ), ), ], ), ) .toList(), ), ), const SizedBox(height: 8), Text( '共 ${_rows.length} 条历史记录', style: Theme.of(context).textTheme.bodySmall ?.copyWith(color: _mutedColor(context, 0.62)), ), ], ), ), ), ); } }