Files
nj/app/lib/widgets/empty_state_widget.dart
iven f64355946c
Some checks failed
Main Merge / backend (push) Has been cancelled
Main Merge / frontend (push) Has been cancelled
feat(app): 共享 UI 组件 + 4 个关键 UX bug 修复
Phase 0 — 共享组件:
- EmptyStateWidget: 统一空状态 (icon + title + subtitle + CTA)
- ErrorStateWidget: 统一错误状态 (message + retry)
- SkeletonBox + SkeletonList: 统一骨架屏加载 (shimmer 动画)

Phase 1 — Bug 修复:
- 班级评论按 journalId 过滤,避免显示在错误日记卡片下
- moodCellColors key 修正: love/tired → angry/thinking
- 日历非 CalendarLoaded 状态改为加载指示器 (不再 SizedBox.shrink)
- 贴纸数统计改为 '--' 占位 (之前错误显示日记总数)
2026-06-07 13:36:10 +08:00

96 lines
2.9 KiB
Dart

// 共享空状态组件 — 统一所有页面的空数据展示
//
// 使用: EmptyStateWidget(icon: Icons.xxx, title: '暂无数据', subtitle: '下拉刷新试试')
// 样式参考: home_page._EmptyJournalState — 大图标淡化 + 标题 + 副标题 + 暖色按钮
import 'package:flutter/material.dart';
import '../core/constants/design_tokens.dart';
import '../core/theme/app_colors.dart';
import '../core/theme/app_radius.dart';
/// 统一空状态组件 — 图标 + 标题 + 可选副标题 + 可选操作按钮
class EmptyStateWidget extends StatelessWidget {
const EmptyStateWidget({
super.key,
required this.icon,
required this.title,
this.subtitle,
this.actionLabel,
this.onAction,
this.iconSize = 64,
});
/// 主图标
final IconData icon;
/// 图标大小
final double iconSize;
/// 标题文字
final String title;
/// 副标题(灰色小字)
final String? subtitle;
/// 操作按钮文字(不传则不显示按钮)
final String? actionLabel;
/// 操作按钮回调
final VoidCallback? onAction;
@override
Widget build(BuildContext context) {
final theme = Theme.of(context);
return Center(
child: Padding(
padding: const EdgeInsets.symmetric(vertical: DesignTokens.spacing40),
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
Icon(
icon,
size: iconSize,
color: theme.colorScheme.onSurface.withValues(alpha: 0.2),
),
const SizedBox(height: DesignTokens.spacing16),
Text(
title,
style: theme.textTheme.bodyLarge?.copyWith(
color: theme.colorScheme.onSurfaceVariant,
),
textAlign: TextAlign.center,
),
if (subtitle != null) ...[
const SizedBox(height: DesignTokens.spacing8),
Text(
subtitle!,
style: theme.textTheme.bodyMedium?.copyWith(
color: theme.colorScheme.onSurfaceVariant.withValues(alpha: 0.6),
),
textAlign: TextAlign.center,
),
],
if (actionLabel != null && onAction != null) ...[
const SizedBox(height: DesignTokens.spacing24),
FilledButton.icon(
onPressed: onAction,
icon: const Icon(Icons.add_rounded),
label: Text(actionLabel!),
style: FilledButton.styleFrom(
backgroundColor: AppColors.accent,
foregroundColor: AppColors.bgLight,
shape: RoundedRectangleBorder(
borderRadius: AppRadius.pillBorder,
),
padding: const EdgeInsets.symmetric(horizontal: 24, vertical: 12),
),
),
],
],
),
),
);
}
}