announcement_list_page.dart 6.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208
  1. import 'package:flutter/material.dart';
  2. import 'package:flutter_riverpod/flutter_riverpod.dart';
  3. import 'package:go_router/go_router.dart';
  4. import '../../core/theme/app_colors.dart';
  5. import '../shell/nav_bar_config.dart';
  6. import '../../core/utils/date_utils.dart' as du;
  7. import '../../core/utils/responsive.dart';
  8. import '../../shared/widgets/filter_tabs.dart';
  9. import '../../shared/widgets/empty_state.dart';
  10. import '../../shared/widgets/loading_widget.dart';
  11. import '../../core/i18n/app_localizations.dart';
  12. import 'announcement_list_controller.dart';
  13. import 'announcement_model.dart';
  14. class AnnouncementListPage extends ConsumerStatefulWidget {
  15. const AnnouncementListPage({super.key});
  16. @override
  17. ConsumerState<AnnouncementListPage> createState() =>
  18. _AnnouncementListPageState();
  19. }
  20. class _AnnouncementListPageState extends ConsumerState<AnnouncementListPage> {
  21. int _tabIndex = 0;
  22. final _tabs = ['通知公告', '人事与制度', '放假与活动', '草稿'];
  23. final _searchCtrl = TextEditingController();
  24. @override
  25. void dispose() {
  26. _searchCtrl.dispose();
  27. super.dispose();
  28. }
  29. @override
  30. Widget build(BuildContext context) {
  31. final itemsAsync = ref.watch(announcementListProvider);
  32. final r = ResponsiveHelper.of(context);
  33. final l10n = AppLocalizations.of(context);
  34. ref
  35. .read(navBarConfigProvider.notifier)
  36. .update(
  37. NavBarConfig(
  38. title: l10n.get('announcementList'),
  39. showBack: true,
  40. showRight: true,
  41. rightWidget: IconButton(
  42. icon: const Icon(
  43. Icons.add,
  44. size: 22,
  45. color: AppColors.textSecondary,
  46. ),
  47. onPressed: () => context.push('/announcement/create'),
  48. ),
  49. onBack: () => context.pop(),
  50. ),
  51. );
  52. return Center(
  53. child: ConstrainedBox(
  54. constraints: BoxConstraints(maxWidth: r.listMaxWidth),
  55. child: Column(
  56. children: [
  57. FilterTabs(
  58. tabs: _tabs,
  59. selectedIndex: _tabIndex,
  60. onChanged: (i) => setState(() => _tabIndex = i),
  61. ),
  62. Expanded(
  63. child: itemsAsync.when(
  64. loading: () => const LoadingWidget(),
  65. error: (_, __) => const EmptyState(message: '加载失败'),
  66. data: (items) => items.isEmpty
  67. ? const EmptyState(message: '暂无公告')
  68. : RefreshIndicator(
  69. onRefresh: () async {
  70. ref.invalidate(announcementListProvider);
  71. },
  72. child: ListView.builder(
  73. padding: const EdgeInsets.symmetric(vertical: 8),
  74. itemCount: items.length,
  75. itemBuilder: (_, i) =>
  76. _buildAnnouncementCard(items[i]),
  77. ),
  78. ),
  79. ),
  80. ),
  81. ],
  82. ),
  83. ),
  84. );
  85. }
  86. Widget _buildAnnouncementCard(AnnouncementModel item) {
  87. return Padding(
  88. padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 4),
  89. child: GestureDetector(
  90. onTap: () => context.push('/announcement/detail/${item.id}'),
  91. child: Container(
  92. padding: const EdgeInsets.all(12),
  93. decoration: BoxDecoration(
  94. color: AppColors.bgCard,
  95. borderRadius: BorderRadius.circular(8),
  96. ),
  97. child: Column(
  98. crossAxisAlignment: CrossAxisAlignment.start,
  99. children: [
  100. // 标题行
  101. Row(
  102. children: [
  103. if (item.isTop)
  104. Container(
  105. margin: const EdgeInsets.only(right: 8),
  106. padding: const EdgeInsets.symmetric(
  107. horizontal: 4,
  108. vertical: 1,
  109. ),
  110. decoration: BoxDecoration(
  111. color: AppColors.danger,
  112. borderRadius: BorderRadius.circular(2),
  113. ),
  114. child: const Text(
  115. '置顶',
  116. style: TextStyle(fontSize: 10, color: Colors.white),
  117. ),
  118. ),
  119. Expanded(
  120. child: Text(
  121. item.title,
  122. maxLines: 1,
  123. overflow: TextOverflow.ellipsis,
  124. style: const TextStyle(
  125. fontSize: 15,
  126. fontWeight: FontWeight.w600,
  127. color: AppColors.textPrimary,
  128. ),
  129. ),
  130. ),
  131. if (item.readCount > 0)
  132. Container(
  133. width: 8,
  134. height: 8,
  135. decoration: const BoxDecoration(
  136. color: AppColors.danger,
  137. shape: BoxShape.circle,
  138. ),
  139. ),
  140. ],
  141. ),
  142. const SizedBox(height: 8),
  143. // 元信息行
  144. Row(
  145. mainAxisAlignment: MainAxisAlignment.spaceBetween,
  146. children: [
  147. Row(
  148. children: [
  149. _buildTypeTag(item.type),
  150. const SizedBox(width: 8),
  151. Text(
  152. item.publisherName,
  153. style: const TextStyle(
  154. fontSize: 12,
  155. color: AppColors.textSecondary,
  156. ),
  157. ),
  158. ],
  159. ),
  160. Text(
  161. du.DateUtils.formatDateTime(item.publishTime),
  162. style: const TextStyle(
  163. fontSize: 12,
  164. color: AppColors.textPlaceholder,
  165. ),
  166. ),
  167. ],
  168. ),
  169. ],
  170. ),
  171. ),
  172. ),
  173. );
  174. }
  175. Widget _buildTypeTag(String type) {
  176. Color bgColor;
  177. Color textColor;
  178. switch (type) {
  179. case '人事与制度':
  180. bgColor = AppColors.successBg;
  181. textColor = AppColors.success;
  182. case '放假与活动':
  183. bgColor = AppColors.warningBg;
  184. textColor = AppColors.warning;
  185. case '系统公告':
  186. bgColor = AppColors.warningBg;
  187. textColor = AppColors.warning;
  188. default:
  189. bgColor = AppColors.primaryLight;
  190. textColor = AppColors.primary;
  191. }
  192. return Container(
  193. padding: const EdgeInsets.symmetric(horizontal: 6, vertical: 2),
  194. decoration: BoxDecoration(
  195. color: bgColor,
  196. borderRadius: BorderRadius.circular(3),
  197. ),
  198. child: Text(type, style: TextStyle(fontSize: 11, color: textColor)),
  199. );
  200. }
  201. }