fix:按照shadcn_ui对着demo_shadcn对齐

This commit is contained in:
jingyun
2026-06-05 15:04:39 +08:00
parent 9727b906c6
commit c5288f397d
29 changed files with 1425 additions and 642 deletions
+173 -131
View File
@@ -1,17 +1,14 @@
import 'package:flutter/material.dart';
import 'package:flutter_hooks/flutter_hooks.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:shadcn_ui/shadcn_ui.dart';
import '../../data/api/report_data_source.dart';
import '../../data/content_providers.dart';
import '../../data/models/models.dart';
import '../../routing/app_routes.dart';
import '../../theme/app_icons.dart';
import '../../theme/yanting_text.dart';
import '../../theme/yanting_tokens.dart';
import '../../theme/wise_tokens.dart';
import '../../widgets/app_buttons.dart';
import '../../widgets/badges.dart';
import '../../widgets/mini_player.dart';
import '../../widgets/page_header.dart';
import '../../widgets/states.dart';
@@ -45,6 +42,8 @@ class ReportsPage extends HookConsumerWidget {
@override
Widget build(BuildContext context, WidgetRef ref) {
final theme = ShadTheme.of(context);
final searchController = useTextEditingController();
final query = useState('');
final topic = useState('');
final hasAudio = useState(false);
@@ -67,104 +66,127 @@ class ReportsPage extends HookConsumerWidget {
hasAudio: currentHasAudio,
);
return ListView(
return SingleChildScrollView(
padding: const EdgeInsets.fromLTRB(
WiseSpacing.x4,
YantingSpacing.screenX,
4,
WiseSpacing.x4,
YantingSpacing.screenX,
16,
),
children: [
const PageHeader(title: '研报', subtitle: '全部已发布研报解读'),
TextField(
decoration: InputDecoration(
hintText: '搜索标题、机构或主题',
prefixIcon: const Icon(AppIcons.search),
suffixIcon: currentQuery.isEmpty
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
const PageHeader(title: '研报', subtitle: '全部已发布研报解读'),
ShadInput(
controller: searchController,
placeholder: const Text('搜索标题、机构或主题'),
leading: const Padding(
padding: EdgeInsets.only(right: 8),
child: Icon(LucideIcons.search, size: 16),
),
trailing: currentQuery.isEmpty
? null
: IconButton(
onPressed: () => query.value = '',
icon: const Icon(Icons.close),
),
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(YantingRadius.md),
borderSide: const BorderSide(color: YantingColors.input),
),
),
onChanged: (value) => query.value = value.trim(),
),
const SizedBox(height: WiseSpacing.x3),
Row(
children: [
AppButton(
label: '筛选',
icon: AppIcons.filter,
kind: AppButtonKind.ghost,
onPressed: items.isEmpty
? null
: () => _openFilterSheet(
context,
items: items,
topic: topic,
: Padding(
padding: const EdgeInsets.only(left: 8),
child: ShadButton.ghost(
size: ShadButtonSize.sm,
onPressed: () {
searchController.clear();
query.value = '';
},
child: const Icon(LucideIcons.x, size: 16),
),
),
const SizedBox(width: WiseSpacing.x2),
AppButton(
label: '最新',
icon: AppIcons.sort,
kind: AppButtonKind.ghost,
onPressed: () {},
),
const SizedBox(width: WiseSpacing.x2),
AppChip(
label: '音频',
selected: currentHasAudio,
onTap: () => hasAudio.value = !currentHasAudio,
),
const Spacer(),
Text('${filtered.length}', style: YantingText.meta),
],
),
const SizedBox(height: WiseSpacing.x3),
if (filtered.isEmpty)
EmptyState(
title: currentQuery.isNotEmpty ? '未找到相关研报' : '当前筛选下暂无研报',
message: currentQuery.isNotEmpty ? '换个关键词试试' : '调整筛选条件后再试',
actionLabel: '清除筛选',
onAction: () {
query.value = '';
topic.value = '';
hasAudio.value = false;
},
)
else
for (final report in filtered) ...[
ReportCardWidget(
report: report,
onTap: () => openReportDetail(
context,
dataSource,
report,
player: player,
onStartAudio: onStartModuleAudio,
onToggleAudio: onToggleAudio,
onSeekAudio: onSeekAudio,
onSpeed: onSpeed,
),
onChanged: (value) => query.value = value.trim(),
),
const SizedBox(height: YantingSpacing.x3),
Wrap(
spacing: YantingSpacing.x2,
runSpacing: YantingSpacing.x2,
children: [
ShadButton.outline(
onPressed: items.isEmpty
? null
: () => _openFilterSheet(
context,
items: items,
topic: topic,
),
leading: const Icon(
LucideIcons.slidersHorizontal,
size: 16,
),
child: const Text('筛选'),
),
onPlayTap: () => onPlay(
AudioItem(
audioId: 'local_${report.id}',
reportId: report.id,
titleCn: report.titleCn,
reportTitleCn: report.titleCn,
durationSec: 180,
institution: report.institution,
ShadButton.outline(
onPressed: () {},
leading: const Icon(LucideIcons.arrowUpDown, size: 16),
child: const Text('最新'),
),
ShadBadge.secondary(
onPressed: () => hasAudio.value = !currentHasAudio,
backgroundColor: currentHasAudio
? theme.colorScheme.foreground
: theme.colorScheme.secondary,
foregroundColor: currentHasAudio
? theme.colorScheme.background
: theme.colorScheme.secondaryForeground,
hoverBackgroundColor: currentHasAudio
? theme.colorScheme.foreground.withValues(alpha: 0.9)
: theme.colorScheme.border,
child: const Text('音频'),
),
],
),
const SizedBox(height: 8),
Align(
alignment: Alignment.centerRight,
child: Text('${filtered.length}', style: YantingText.meta),
),
const SizedBox(height: YantingSpacing.x3),
const ShadSeparator.horizontal(),
const SizedBox(height: YantingSpacing.x3),
if (filtered.isEmpty)
EmptyState(
title: currentQuery.isNotEmpty ? '未找到相关研报' : '当前筛选下暂无研报',
message: currentQuery.isNotEmpty ? '换个关键词试试' : '调整筛选条件后再试',
actionLabel: '清除筛选',
onAction: () {
searchController.clear();
query.value = '';
topic.value = '';
hasAudio.value = false;
},
)
else
for (final report in filtered) ...[
ReportCardWidget(
report: report,
onTap: () => openReportDetail(
context,
dataSource,
report,
player: player,
onStartAudio: onStartModuleAudio,
onToggleAudio: onToggleAudio,
onSeekAudio: onSeekAudio,
onSpeed: onSpeed,
),
onPlayTap: () => onPlay(
AudioItem(
audioId: 'local_${report.id}',
reportId: report.id,
titleCn: report.titleCn,
reportTitleCn: report.titleCn,
durationSec: 180,
institution: report.institution,
),
),
),
),
const SizedBox(height: WiseSpacing.x3),
],
],
const SizedBox(height: YantingSpacing.x3),
],
],
),
);
},
);
@@ -194,45 +216,65 @@ void _openFilterSheet(
required ValueNotifier<String> topic,
}) {
final topics = {for (final item in items) ...item.topics}.toList();
showModalBottomSheet<void>(
showShadSheet<void>(
context: context,
showDragHandle: true,
shape: const RoundedRectangleBorder(
borderRadius: BorderRadius.vertical(top: Radius.circular(WiseRadius.lg)),
),
builder: (context) => Padding(
padding: const EdgeInsets.fromLTRB(20, 4, 20, 28),
child: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text('筛选研报', style: Theme.of(context).textTheme.titleLarge),
const SizedBox(height: WiseSpacing.x3),
Wrap(
spacing: WiseSpacing.x2,
runSpacing: WiseSpacing.x2,
children: [
AppChip(
label: '全部主题',
selected: topic.value.isEmpty,
onTap: () => topic.value = '',
),
for (final t in topics)
AppChip(
label: t,
selected: topic.value == t,
onTap: () => topic.value = t,
side: ShadSheetSide.bottom,
builder: (context) {
final theme = ShadTheme.of(context);
final selectedBackground = theme.colorScheme.foreground;
final selectedForeground = theme.colorScheme.background;
final unselectedBackground = theme.colorScheme.secondary;
final unselectedForeground = theme.colorScheme.secondaryForeground;
return ShadSheet(
title: const Text('筛选研报'),
description: const Text('按主题快速收窄列表。'),
child: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Wrap(
spacing: YantingSpacing.x2,
runSpacing: YantingSpacing.x2,
children: [
ShadBadge.secondary(
onPressed: () => topic.value = '',
backgroundColor: topic.value.isEmpty
? selectedBackground
: unselectedBackground,
foregroundColor: topic.value.isEmpty
? selectedForeground
: unselectedForeground,
hoverBackgroundColor: topic.value.isEmpty
? selectedBackground.withValues(alpha: 0.9)
: theme.colorScheme.border,
child: const Text('全部主题'),
),
],
),
const SizedBox(height: WiseSpacing.x4),
AppButton(
label: '完成',
expand: true,
onPressed: () => Navigator.pop(context),
),
],
),
),
for (final t in topics)
ShadBadge.secondary(
onPressed: () => topic.value = t,
backgroundColor: topic.value == t
? selectedBackground
: unselectedBackground,
foregroundColor: topic.value == t
? selectedForeground
: unselectedForeground,
hoverBackgroundColor: topic.value == t
? selectedBackground.withValues(alpha: 0.9)
: theme.colorScheme.border,
child: Text(t),
),
],
),
const SizedBox(height: 12),
ShadButton(
width: double.infinity,
onPressed: () => Navigator.pop(context),
child: const Text('完成'),
),
],
),
);
},
);
}