浏览代码

通知列表的逻辑

liukai 1 周之前
父节点
当前提交
dfc722e29a

+ 22 - 13
packages/cpt_main/lib/modules/notification/item_notification.dart

@@ -1,5 +1,6 @@
 import 'package:cs_resources/generated/assets.dart';
 import 'package:cs_resources/theme/app_colors_theme.dart';
+import 'package:domain/entity/notification_page_entity.dart';
 import 'package:flutter/material.dart';
 import 'package:widgets/ext/ex_widget.dart';
 import 'package:widgets/my_load_image.dart';
@@ -7,7 +8,7 @@ import 'package:widgets/my_text_view.dart';
 import 'package:widgets/utils/dark_theme_util.dart';
 
 class NotificationItem extends StatelessWidget {
-  final String item;
+  final NotificationPageList item;
   final int index;
 
   const NotificationItem({
@@ -22,22 +23,28 @@ class NotificationItem extends StatelessWidget {
       color: context.appColors.whiteBG,
       padding: const EdgeInsets.only(left: 11, right: 15, top: 10.5),
       child: Column(
+        crossAxisAlignment: CrossAxisAlignment.start,
         children: [
           Row(
+            crossAxisAlignment: CrossAxisAlignment.start,
             children: [
-            const  MyAssetImage(
+              const MyAssetImage(
                 Assets.mainNotificationItemIcon,
                 width: 42.5,
                 height: 42.5,
               ).marginOnly(right: 16),
+
               Column(
                 children: [
                   //标题
                   Row(
                     crossAxisAlignment: CrossAxisAlignment.center,
                     children: [
+
                       //未读的小圆点
-                      Container(
+                      Visibility(
+                        visible: !item.read,
+                        child: Container(
                         width: 6,
                         height: 6,
                         margin: const EdgeInsets.only(right: 8.5),
@@ -45,11 +52,12 @@ class NotificationItem extends StatelessWidget {
                           shape: BoxShape.circle,
                           color: context.appColors.tabBgSelectedPrimary,
                         ),
-                      ),
+                      ),),
+
 
                       //消息标题
                       MyTextView(
-                        "NEW ANNOUNCEMENT",
+                        item.title??"",
                         fontSize: 16,
                         isFontBold: true,
                         textColor: context.appColors.textBlack,
@@ -58,8 +66,9 @@ class NotificationItem extends StatelessWidget {
 
                       //消息时间
                       MyTextView(
-                        "10:19 AM",
+                        item.createdAt!.split(" at ")[1],
                         fontSize: 13,
+                        marginLeft: 10,
                         isFontRegular: true,
                         textColor: context.appColors.textLightPurple,
                       ),
@@ -71,19 +80,19 @@ class NotificationItem extends StatelessWidget {
                     children: [
                       //文本内容
                       MyTextView(
-                        "Standard operating procedure for replacement vehicles andover night parking vehicles.",
+                        item.body??"",
                         fontSize: 15,
                         marginRight: 15,
                         isFontRegular: true,
                         textColor: context.appColors.textBlack,
                       ).expanded(),
 
-                      //图片
-                      MyLoadImage(
-                        "https://img1.baidu.com/it/u=1656098746,3560654086&fm=253&fmt=auto&app=120&f=JPEG?w=800&h=800",
-                        width: 87.5,
-                        height: 60.5,
-                      ),
+                      // //图片
+                      // MyLoadImage(
+                      //   "https://img1.baidu.com/it/u=1656098746,3560654086&fm=253&fmt=auto&app=120&f=JPEG?w=800&h=800",
+                      //   width: 87.5,
+                      //   height: 60.5,
+                      // ),
                     ],
                   ).marginOnly(top: 9),
                 ],

+ 6 - 1
packages/cpt_main/lib/modules/notification/notification_group_data.dart

@@ -1,6 +1,11 @@
 
 //对应分组的数据,需要在ViewModel中处理每一个组的数据
+import 'package:domain/entity/notification_page_entity.dart';
+
 class NotificationGroupData{
   String? groupId;
-  List<String>? groupDatas=[];
+  List<NotificationPageList>? groupDatas= [];
+
+  NotificationGroupData(this.groupId, this.groupDatas);
+
 }

+ 10 - 9
packages/cpt_main/lib/modules/notification/notification_page.dart

@@ -1,12 +1,16 @@
+import 'package:cpt_main/modules/feedback/create/feedback_create_page.dart';
 import 'package:cpt_main/modules/notification/item_notification_header.dart';
 import 'package:cpt_main/router/page/main_page_router.dart';
 import 'package:cs_resources/generated/l10n.dart';
 import 'package:cs_resources/theme/app_colors_theme.dart';
+import 'package:domain/entity/notification_page_entity.dart';
 import 'package:flutter/material.dart';
 import 'package:auto_route/auto_route.dart';
 import 'package:flutter_hooks/flutter_hooks.dart';
 import 'package:hooks_riverpod/hooks_riverpod.dart';
+import 'package:plugin_platform/engine/toast/toast_engine.dart';
 import 'package:router/ext/auto_router_extensions.dart';
+import 'package:widgets/ext/ex_widget.dart';
 import 'package:widgets/load_state_layout.dart';
 import 'package:widgets/my_appbar.dart';
 import 'package:widgets/my_text_view.dart';
@@ -76,23 +80,20 @@ class NotificationPage extends HookConsumerWidget {
                   return StickyHeader(
                     header: NotificationItemHeader(state.datas[index].groupId),
                     content: Column(
-                      children: _buildGroup(state.datas[index].groupDatas!, index),
+                      children: state.datas[index].groupDatas!.map((item) {
+                        return NotificationItem(item: item, index: index).onTap(() {
+                          viewModel.setReadStatus(item);
+                        });
+                      }).toList(),
                     ),
                   );
                 },
                 childCount: state.datas.length,
-              ))
+              )),
             ],
           ),
         ),
       ),
     );
   }
-
-  //当前组内的子数据
-  _buildGroup(List<String> list, int index) {
-    return list.map((item) {
-      return NotificationItem(item: item, index: index);
-    }).toList();
-  }
 }

+ 169 - 88
packages/cpt_main/lib/modules/notification/notification_view_model.dart

@@ -1,6 +1,16 @@
 import 'package:cpt_main/modules/notification/notification_group_data.dart';
+import 'package:cs_resources/generated/l10n.dart';
+import 'package:domain/entity/notification_page_entity.dart';
+import 'package:domain/repository/main_repository.dart';
+import 'package:plugin_platform/engine/dialog/dialog_engine.dart';
+import 'package:plugin_platform/engine/notify/notify_engine.dart';
 import 'package:plugin_platform/engine/toast/toast_engine.dart';
+import 'package:plugin_platform/http/dio/dio_cancelable_mixin.dart';
 import 'package:riverpod_annotation/riverpod_annotation.dart';
+import 'package:router/componentRouter/component_service_manager.dart';
+import 'package:shared/utils/date_time_utils.dart';
+import 'package:shared/utils/log_utils.dart';
+import 'package:widgets/dialog/app_default_dialog.dart';
 import 'package:widgets/load_state_layout.dart';
 import 'package:widgets/widget_export.dart';
 
@@ -9,9 +19,12 @@ import 'notification_state.dart';
 part 'notification_view_model.g.dart';
 
 @riverpod
-class NotificationViewModel extends _$NotificationViewModel {
+class NotificationViewModel extends _$NotificationViewModel with DioCancelableMixin {
+  late final MainRepository _mainRepository;
+
   @override
   NotificationState build() {
+    _mainRepository = ref.read(mainRepositoryProvider);
     return NotificationState(datas: []);
   }
 
@@ -54,107 +67,175 @@ class NotificationViewModel extends _$NotificationViewModel {
       changeLoadingState(LoadState.State_Loading, null);
     }
 
-    // 获取 Applied 列表
-    // var listResult = await _jobRepository.fetchJobAppliedList(
-    //   state.jobId,
-    //   state.selectedStatusId,
-    //   state.keyword,
-    //   curPage: _curPage,
-    //   cancelToken: cancelToken,
-    // );
-    //
-    // // 处理数据
-    // if (listResult.isSuccess) {
-    //   handleList(listResult.data?.rows);
-    // } else {
-    //   errorMessage = listResult.errorMsg;
-    //   changeLoadingState(LoadState.State_Error);
-    // }
-
-    await Future.delayed(const Duration(milliseconds: 1500));
-
-    List<NotificationGroupData> list = [];
-    if (_curPage > 1) {
-
-      //这里只加载一页吧
-    } else {
+    // 获取列表
+    var listResult = await _mainRepository.fetchNotificationList(
+      curPage: _curPage,
+      cancelToken: cancelToken,
+    );
 
-      list.add(NotificationGroupData()
-        ..groupId = "Toady"
-        ..groupDatas = ["1", "2", "3", "4"]);
+    // 处理数据
+    if (listResult.isSuccess) {
+      handleList(listResult.data?.list);
+    } else {
+      changeLoadingState(LoadState.State_Error, listResult.errorMsg);
+    }
 
-      list.add(NotificationGroupData()
-        ..groupId = "Friday 11 oct 2024"
-        ..groupDatas = ["1", "2", "3"]);
+    // 最后赋值
+    _needShowPlaceholder = false;
+  }
 
-      list.add(NotificationGroupData()
-        ..groupId = "Thursday 10 oct 2024"
-        ..groupDatas = ["1", "2"]);
+// 处理数据与展示的逻辑
+  void handleList(List<NotificationPageList>? list) {
+    if (list != null && list.isNotEmpty) {
+      //有数据,处理转换数据
+
+      List<NotificationGroupData> newDatas;
+      //先从当的数据中找到 groupId 比对获取到的list 是否有匹配的 groupId
+      if (_curPage == 1) {
+        newDatas = []; // 重新加载数据
+      } else {
+        newDatas = List.from(state.datas); // 获取当前状态的数据的副本去添加数据
+      }
+
+      for (var item in list) {
+        String? createAt = item.createdAt!.split(" at ")[0];
+        //处理 groupId 的显示字段  21 May 2025
+        DateTime? dateTime = DateTimeUtils.parseCustomDate(createAt.trim(), format: 'dd MMM yyyy');
+        String itemGroupId;
+        if (dateTime != null) {
+          if (DateTimeUtils.isTodayByDateTime(dateTime)) {
+            itemGroupId = S.current.today;
+          } else {
+            itemGroupId = "${DateTimeUtils.getWeekday(dateTime, languageCode: 'en')} ${DateTimeUtils.formatDate(dateTime, format: 'dd MMM yyyy')}";
+          }
+        } else {
+          itemGroupId = createAt;
+        }
+
+        // 尝试在当前分组中找到匹配的 createdAt
+        var curGroup = newDatas.firstWhere((element) => element.groupId == itemGroupId, orElse: () => NotificationGroupData(null, []));
+
+        // 如果有匹配的 createdAt,则添加进去
+        if (curGroup.groupId != null) {
+          curGroup.groupDatas!.add(item); // 添加通知到现有组
+        } else {
+          // 如果没有匹配的 createdAt,则创建新的分组
+          newDatas.add(NotificationGroupData(itemGroupId, [item])); // 创建新组
+        }
+      }
+
+      if (_curPage == 1) {
+        //刷新的方式
+        state = state.copyWith(datas: newDatas);
+        refreshController.finishRefresh();
+
+        //更新展示的状态
+        changeLoadingState(LoadState.State_Success, null);
+      } else {
+        //加载更多
+        state.datas.addAll(newDatas);
+        refreshController.finishLoad();
+      }
+    } else {
+      if (_curPage == 1) {
+        //展示无数据的布局
+        state = state.copyWith(datas: []);
+        changeLoadingState(LoadState.State_Empty, null);
+        refreshController.finishRefresh();
+      } else {
+        //展示加载完成,没有更多数据了
+        refreshController.finishLoad(IndicatorResult.noMore);
+      }
+    }
+  }
 
-      list.add(NotificationGroupData()
-        ..groupId = "Wednesday 9 oct 2024"
-        ..groupDatas = ["1"]);
+  /// 点击标记全部
+  void markAll() {
+    DialogEngine.show(
+        widget: AppDefaultDialog(
+      message: S.current.read_all_msg,
+      confirmAction: () {
+        _requestReadAll();
+      },
+    ));
+  }
 
+  ///设置单个已读
+  void setReadStatus(NotificationPageList item) async {
+    // 创建新的数据列表
+    List<NotificationGroupData> newDatas = List.from(state.datas); // 复制一份当前数据
+
+    // 找到对应的组并更新状态
+    for (var group in newDatas) {
+      for (var notification in group.groupDatas!) {
+        if (notification.id == item.id) {
+          // 假设 item 有一个唯一的 id 字段
+          notification.read = true; // 更新状态为已读
+          break; // 找到并更新后可以退出循环
+        }
+      }
     }
 
+    // 更新状态,通知 UI 刷新
+    state = state.copyWith(datas: newDatas);
 
-    if (_curPage == 1) {
-      //刷新的方式
-      state = state.copyWith(datas: list);
-      refreshController.finishRefresh();
-
-      //更新展示的状态
-      changeLoadingState(LoadState.State_Success, null);
-    } else {
-      //加载更多
-      final allList = state.datas;
-      allList.addAll(list);
-      state.datas.addAll(list);
+    //转转详情
+    _godoDetailPage(item);
 
-      // refreshController.finishLoad();
-      refreshController.finishLoad(IndicatorResult.noMore);
+    // 发送请求设置服务器已读
+    final result = await _mainRepository.setNotificationRead(id: item.id);
 
-      state = state.copyWith(datas: allList);
+    if (result.isSuccess) {
+      Log.d("设置 ${item.id} 的消息已读成功!");
+    } else {
+      ToastEngine.show(result.errorMsg ?? "UnKnow Error");
     }
+  }
 
-    // 最后赋值
-    _needShowPlaceholder = false;
+  ///跳转到详情页
+  void _godoDetailPage(NotificationPageList item) {
+    NotificationPageListData? data = item.data;
+    if (data == null) return;
+    // 1、加入unit成功  ApprovedJoinUnitNotification
+    // 2、加入unit失败  RejectedJoinUnitNotification
+    // 3、online form批准了  ApprovedApplyOnlineFormNotification
+    // 4、online form拒绝了  RejectedApplyOnlineFormNotification
+    // 5、facility 预定成功了(不需要,因为支付了就是预定成功了)
+    // 6、facility 预定失败了(长时间未支付,被系统取消了)  FacilityBookingNotPaidCancelNotification
+    // 7、每月物业费账单生成了
+    // 8、每月停车费账单生成了
+    // 9、服务订单长时间未支付  PaidServiceOrderNotPaidCancelNotification
+
+    String? type = data.type;
+    if (type == 'ApprovedApplyOnlineFormNotification' || type == 'RejectedApplyOnlineFormNotification') {
+      //去 Form 详情
+      ComponentServiceManager().formService.startFormDetailPage(data.estateOnlineFormId ?? "", data.id ?? "", data.onlineFormTypeId ?? "");
+    }
   }
 
-// 处理数据与展示的逻辑
-// void handleList(List<JobAppliedListSGRows>? list) {
-//   if (list != null && list.isNotEmpty) {
-//     //有数据,判断是刷新还是加载更多的数据
-//     if (_curPage == 1) {
-//       //刷新的方式
-//       state.datas.clear();
-//       state.datas.addAll(list);
-//       refreshController.finishRefresh();
-//
-//       //更新展示的状态
-//       changeLoadingState(LoadState.State_Success);
-//     } else {
-//       //加载更多
-//       state.datas.addAll(list);
-//       refreshController.finishLoad();
-//       update();
-//     }
-//   } else {
-//     if (_curPage == 1) {
-//       //展示无数据的布局
-//       state.datas.clear();
-//       changeLoadingState(LoadState.State_Empty);
-//       refreshController.finishRefresh();
-//     } else {
-//       //展示加载完成,没有更多数据了
-//       refreshController.finishLoad(IndicatorResult.noMore);
-//     }
-//   }
-// }
+  /// 请求设置全部已读
+  void _requestReadAll() async {
+    // 发送请求设置服务器已读
+    final result = await _mainRepository.setNotificationRead();
 
-  /// 点击标记全部
-  void markAll() {
-    ToastEngine.show("点击标记全部");
-  }
+    if (result.isSuccess) {
+      Log.d("设置全部的消息已读成功!");
+      NotifyEngine.showSuccess(S.current.successful);
+
+      // 创建新的数据列表
+      List<NotificationGroupData> newDatas = List.from(state.datas); // 复制一份当前数据
+
+      // 找到对应的组并更新状态
+      for (var group in newDatas) {
+        for (var notification in group.groupDatas!) {
+          notification.read = true; // 更新状态为已读
+        }
+      }
 
+      // 更新状态,通知 UI 刷新
+      state = state.copyWith(datas: newDatas);
+    } else {
+      ToastEngine.show(result.errorMsg ?? "UnKnow Error");
+    }
+  }
 }

+ 6 - 0
packages/cs_domain/lib/constants/api_constants.dart

@@ -71,6 +71,12 @@ class ApiConstants {
   //首页-管理员详情
   static const apiManageGuideDetail = "/api/v1/user/strata-management/guide/detail";
 
+  //通知列表
+  static const apiNotificationList = "/api/v1/user/me/notification/index";
+
+  //消息设置已读
+  static const apiNotificationRead = "/api/v1/user/me/notification/read";
+
   // =========================== Profile ↓=========================================
 
   //用户Me页面详情

+ 71 - 0
packages/cs_domain/lib/entity/notification_page_entity.dart

@@ -0,0 +1,71 @@
+import 'package:domain/generated/json/base/json_field.dart';
+import 'package:domain/generated/json/notification_page_entity.g.dart';
+import 'dart:convert';
+export 'package:domain/generated/json/notification_page_entity.g.dart';
+
+@JsonSerializable()
+class NotificationPageEntity {
+	int count = 0;
+	int page = 0;
+	int limit = 0;
+	@JSONField(name: "count_page")
+	int countPage = 0;
+	List<NotificationPageList>? list = [];
+
+	NotificationPageEntity();
+
+	factory NotificationPageEntity.fromJson(Map<String, dynamic> json) => $NotificationPageEntityFromJson(json);
+
+	Map<String, dynamic> toJson() => $NotificationPageEntityToJson(this);
+
+	@override
+	String toString() {
+		return jsonEncode(this);
+	}
+}
+
+@JsonSerializable()
+class NotificationPageList {
+	String? id;
+	@JSONField(name: "estate_id")
+	String? estateId;
+	String? type;
+	String? title;
+	String? body;
+	NotificationPageListData? data;
+	bool read = false;
+	@JSONField(name: "created_at")
+	String? createdAt;
+
+	NotificationPageList();
+
+	factory NotificationPageList.fromJson(Map<String, dynamic> json) => $NotificationPageListFromJson(json);
+
+	Map<String, dynamic> toJson() => $NotificationPageListToJson(this);
+
+	@override
+	String toString() {
+		return jsonEncode(this);
+	}
+}
+
+@JsonSerializable()
+class NotificationPageListData {
+	String? id;
+	@JSONField(name: "estate_online_form_id")
+	String? estateOnlineFormId;
+	@JSONField(name: "online_form_type_id")
+	String? onlineFormTypeId;
+	String? type;
+
+	NotificationPageListData();
+
+	factory NotificationPageListData.fromJson(Map<String, dynamic> json) => $NotificationPageListDataFromJson(json);
+
+	Map<String, dynamic> toJson() => $NotificationPageListDataToJson(this);
+
+	@override
+	String toString() {
+		return jsonEncode(this);
+	}
+}

+ 13 - 0
packages/cs_domain/lib/generated/json/base/json_convert_content.dart

@@ -39,6 +39,7 @@ import 'package:domain/entity/notice_board_announ_entity.dart';
 import 'package:domain/entity/notice_board_documents_entity.dart';
 import 'package:domain/entity/notice_board_event_detail_entity.dart';
 import 'package:domain/entity/notice_board_event_entity.dart';
+import 'package:domain/entity/notification_page_entity.dart';
 import 'package:domain/entity/paid_service_detail_entity.dart';
 import 'package:domain/entity/paid_service_entity.dart';
 import 'package:domain/entity/paid_service_pay_success_info_entity.dart';
@@ -429,6 +430,15 @@ class JsonConvert {
     if (<NoticeBoardEventList>[] is M) {
       return data.map<NoticeBoardEventList>((Map<String, dynamic> e) => NoticeBoardEventList.fromJson(e)).toList() as M;
     }
+    if (<NotificationPageEntity>[] is M) {
+      return data.map<NotificationPageEntity>((Map<String, dynamic> e) => NotificationPageEntity.fromJson(e)).toList() as M;
+    }
+    if (<NotificationPageList>[] is M) {
+      return data.map<NotificationPageList>((Map<String, dynamic> e) => NotificationPageList.fromJson(e)).toList() as M;
+    }
+    if (<NotificationPageListData>[] is M) {
+      return data.map<NotificationPageListData>((Map<String, dynamic> e) => NotificationPageListData.fromJson(e)).toList() as M;
+    }
     if (<PaidServiceDetailEntity>[] is M) {
       return data.map<PaidServiceDetailEntity>((Map<String, dynamic> e) => PaidServiceDetailEntity.fromJson(e)).toList() as M;
     }
@@ -809,6 +819,9 @@ class JsonConvertClassCollection {
     (NoticeBoardEventDetailEntity).toString(): NoticeBoardEventDetailEntity.fromJson,
     (NoticeBoardEventEntity).toString(): NoticeBoardEventEntity.fromJson,
     (NoticeBoardEventList).toString(): NoticeBoardEventList.fromJson,
+    (NotificationPageEntity).toString(): NotificationPageEntity.fromJson,
+    (NotificationPageList).toString(): NotificationPageList.fromJson,
+    (NotificationPageListData).toString(): NotificationPageListData.fromJson,
     (PaidServiceDetailEntity).toString(): PaidServiceDetailEntity.fromJson,
     (PaidServiceDetailMerchant).toString(): PaidServiceDetailMerchant.fromJson,
     (PaidServiceDetailCategory).toString(): PaidServiceDetailCategory.fromJson,

+ 173 - 0
packages/cs_domain/lib/generated/json/notification_page_entity.g.dart

@@ -0,0 +1,173 @@
+import 'package:domain/generated/json/base/json_convert_content.dart';
+import 'package:domain/entity/notification_page_entity.dart';
+
+NotificationPageEntity $NotificationPageEntityFromJson(Map<String, dynamic> json) {
+  final NotificationPageEntity notificationPageEntity = NotificationPageEntity();
+  final int? count = jsonConvert.convert<int>(json['count']);
+  if (count != null) {
+    notificationPageEntity.count = count;
+  }
+  final int? page = jsonConvert.convert<int>(json['page']);
+  if (page != null) {
+    notificationPageEntity.page = page;
+  }
+  final int? limit = jsonConvert.convert<int>(json['limit']);
+  if (limit != null) {
+    notificationPageEntity.limit = limit;
+  }
+  final int? countPage = jsonConvert.convert<int>(json['count_page']);
+  if (countPage != null) {
+    notificationPageEntity.countPage = countPage;
+  }
+  final List<NotificationPageList>? list = (json['list'] as List<dynamic>?)?.map(
+          (e) => jsonConvert.convert<NotificationPageList>(e) as NotificationPageList).toList();
+  if (list != null) {
+    notificationPageEntity.list = list;
+  }
+  return notificationPageEntity;
+}
+
+Map<String, dynamic> $NotificationPageEntityToJson(NotificationPageEntity entity) {
+  final Map<String, dynamic> data = <String, dynamic>{};
+  data['count'] = entity.count;
+  data['page'] = entity.page;
+  data['limit'] = entity.limit;
+  data['count_page'] = entity.countPage;
+  data['list'] = entity.list?.map((v) => v.toJson()).toList();
+  return data;
+}
+
+extension NotificationPageEntityExtension on NotificationPageEntity {
+  NotificationPageEntity copyWith({
+    int? count,
+    int? page,
+    int? limit,
+    int? countPage,
+    List<NotificationPageList>? list,
+  }) {
+    return NotificationPageEntity()
+      ..count = count ?? this.count
+      ..page = page ?? this.page
+      ..limit = limit ?? this.limit
+      ..countPage = countPage ?? this.countPage
+      ..list = list ?? this.list;
+  }
+}
+
+NotificationPageList $NotificationPageListFromJson(Map<String, dynamic> json) {
+  final NotificationPageList notificationPageList = NotificationPageList();
+  final String? id = jsonConvert.convert<String>(json['id']);
+  if (id != null) {
+    notificationPageList.id = id;
+  }
+  final String? estateId = jsonConvert.convert<String>(json['estate_id']);
+  if (estateId != null) {
+    notificationPageList.estateId = estateId;
+  }
+  final String? type = jsonConvert.convert<String>(json['type']);
+  if (type != null) {
+    notificationPageList.type = type;
+  }
+  final String? title = jsonConvert.convert<String>(json['title']);
+  if (title != null) {
+    notificationPageList.title = title;
+  }
+  final String? body = jsonConvert.convert<String>(json['body']);
+  if (body != null) {
+    notificationPageList.body = body;
+  }
+  final NotificationPageListData? data = jsonConvert.convert<NotificationPageListData>(json['data']);
+  if (data != null) {
+    notificationPageList.data = data;
+  }
+  final bool? read = jsonConvert.convert<bool>(json['read']);
+  if (read != null) {
+    notificationPageList.read = read;
+  }
+  final String? createdAt = jsonConvert.convert<String>(json['created_at']);
+  if (createdAt != null) {
+    notificationPageList.createdAt = createdAt;
+  }
+  return notificationPageList;
+}
+
+Map<String, dynamic> $NotificationPageListToJson(NotificationPageList entity) {
+  final Map<String, dynamic> data = <String, dynamic>{};
+  data['id'] = entity.id;
+  data['estate_id'] = entity.estateId;
+  data['type'] = entity.type;
+  data['title'] = entity.title;
+  data['body'] = entity.body;
+  data['data'] = entity.data?.toJson();
+  data['read'] = entity.read;
+  data['created_at'] = entity.createdAt;
+  return data;
+}
+
+extension NotificationPageListExtension on NotificationPageList {
+  NotificationPageList copyWith({
+    String? id,
+    String? estateId,
+    String? type,
+    String? title,
+    String? body,
+    NotificationPageListData? data,
+    bool? read,
+    String? createdAt,
+  }) {
+    return NotificationPageList()
+      ..id = id ?? this.id
+      ..estateId = estateId ?? this.estateId
+      ..type = type ?? this.type
+      ..title = title ?? this.title
+      ..body = body ?? this.body
+      ..data = data ?? this.data
+      ..read = read ?? this.read
+      ..createdAt = createdAt ?? this.createdAt;
+  }
+}
+
+NotificationPageListData $NotificationPageListDataFromJson(Map<String, dynamic> json) {
+  final NotificationPageListData notificationPageListData = NotificationPageListData();
+  final String? id = jsonConvert.convert<String>(json['id']);
+  if (id != null) {
+    notificationPageListData.id = id;
+  }
+  final String? estateOnlineFormId = jsonConvert.convert<String>(json['estate_online_form_id']);
+  if (estateOnlineFormId != null) {
+    notificationPageListData.estateOnlineFormId = estateOnlineFormId;
+  }
+  final String? onlineFormTypeId = jsonConvert.convert<String>(json['online_form_type_id']);
+  if (onlineFormTypeId != null) {
+    notificationPageListData.onlineFormTypeId = onlineFormTypeId;
+  }
+  final String? type = jsonConvert.convert<String>(json['type']);
+  if (type != null) {
+    notificationPageListData.type = type;
+  }
+  return notificationPageListData;
+}
+
+Map<String, dynamic> $NotificationPageListDataToJson(NotificationPageListData entity) {
+  final Map<String, dynamic> data = <String, dynamic>{};
+  data['id'] = entity.id;
+  data['estate_online_form_id'] = entity.estateOnlineFormId;
+  data['online_form_type_id'] = entity.onlineFormTypeId;
+  data['type'] = entity.type;
+  return data;
+}
+
+extension NotificationPageListDataExtension on NotificationPageListData {
+  NotificationPageListData copyWith({
+    String? id,
+    String? estateOnlineFormId,
+    String? onlineFormTypeId,
+    String? type,
+  }) {
+    return NotificationPageListData()
+      ..id = id ?? this.id
+      ..estateOnlineFormId = estateOnlineFormId ?? this.estateOnlineFormId
+      ..onlineFormTypeId = onlineFormTypeId ?? this.onlineFormTypeId
+      ..type = type ?? this.type;
+  }
+}

+ 49 - 0
packages/cs_domain/lib/repository/main_repository.dart

@@ -2,6 +2,7 @@ import 'package:domain/entity/feedback_detail_entity.dart';
 import 'package:domain/entity/home_list_entity.dart';
 import 'package:domain/entity/latest_news_detail_entity.dart';
 import 'package:domain/entity/latest_news_page_entity.dart';
+import 'package:domain/entity/notification_page_entity.dart';
 import 'package:domain/entity/visitor_page_entity.dart';
 import 'package:plugin_platform/platform_export.dart';
 import 'package:plugin_platform/http/dio_engine.dart';
@@ -363,4 +364,52 @@ class MainRepository {
     return result.convert();
   }
 
+  /// 通知列表
+  Future<HttpResult<NotificationPageEntity>> fetchNotificationList({
+    required int curPage,
+    CancelToken? cancelToken,
+  }) async {
+    Map<String, String> params = {};
+    params['page'] = curPage.toString();
+    params['limit'] = "10";
+
+    final result = await dioEngine.requestNetResult(
+      ApiConstants.apiNotificationList,
+      params: params,
+      method: HttpMethod.GET,
+      cancelToken: cancelToken,
+    );
+
+    if (result.isSuccess) {
+      final json = result.getDataJson();
+      var data = NotificationPageEntity.fromJson(json!);
+      return result.convert<NotificationPageEntity>(data: data);
+    }
+    return result.convert();
+  }
+
+  /// 设置通知已读
+  Future<HttpResult> setNotificationRead({
+    String? id,
+    CancelToken? cancelToken,
+  }) async {
+    Map<String, String> params = {};
+    if (Utils.isNotEmpty(id)) {
+      params['id'] = id!;
+    }
+
+    final result = await dioEngine.requestNetResult(
+      ApiConstants.apiNotificationRead,
+      params: params,
+      method: HttpMethod.POST,
+      isShowLoadingDialog: true,
+      networkDebounce: true,
+      cancelToken: cancelToken,
+    );
+
+    if (result.isSuccess) {
+      return result.convert();
+    }
+    return result.convert();
+  }
 }

+ 1 - 3
packages/cs_plugin_basic/lib/fcm/fcm_utils.dart

@@ -39,8 +39,6 @@ class FcmUtils {
       onToken?.call(newToken); // 将刷新后的 Token 通过回调传递
     });
 
-    Log.d("FCM -> Token:$fCMToken"); // 这个就是设备token,后端通过这token给设备发送信息
-
     //拿到 FCM Token 之后初始化一些监听
     _initPushNotifications();
     _initLocalNotifications();
@@ -108,6 +106,7 @@ class FcmUtils {
   // 处理收到的消息,比如跳转页面或者其他处理
   void handleMessage(RemoteMessage message) {
     final dataJson = message.data;
+    Log.d("点击消息的处理 handleMessage -> data:$dataJson");
 
     // 1、加入unit成功  ApprovedJoinUnitNotification
     // 2、加入unit失败  RejectedJoinUnitNotification
@@ -125,7 +124,6 @@ class FcmUtils {
       ComponentServiceManager().formService.startFormDetailPage(dataJson['estate_online_form_id'], dataJson['id'], dataJson['online_form_type_id']);
     }
 
-    Log.d("点击消息的处理 handleMessage -> data:$dataJson");
   }
 
 // static Future<void> handleBackgroundMessage(RemoteMessage message) async {

+ 4 - 9
packages/cs_resources/lib/generated/intl/messages_all.dart

@@ -43,10 +43,8 @@ MessageLookupByLibrary? _findExact(String localeName) {
 /// User programs should call this before using [localeName] for messages.
 Future<bool> initializeMessages(String localeName) {
   var availableLocale = Intl.verifiedLocale(
-    localeName,
-    (locale) => _deferredLibraries[locale] != null,
-    onFailure: (_) => null,
-  );
+      localeName, (locale) => _deferredLibraries[locale] != null,
+      onFailure: (_) => null);
   if (availableLocale == null) {
     return new SynchronousFuture(false);
   }
@@ -66,11 +64,8 @@ bool _messagesExistFor(String locale) {
 }
 
 MessageLookupByLibrary? _findGeneratedMessagesFor(String locale) {
-  var actualLocale = Intl.verifiedLocale(
-    locale,
-    _messagesExistFor,
-    onFailure: (_) => null,
-  );
+  var actualLocale =
+      Intl.verifiedLocale(locale, _messagesExistFor, onFailure: (_) => null);
   if (actualLocale == null) return null;
   return _findExact(actualLocale);
 }

文件差异内容过多而无法显示
+ 674 - 819
packages/cs_resources/lib/generated/intl/messages_en.dart


文件差异内容过多而无法显示
+ 523 - 552
packages/cs_resources/lib/generated/intl/messages_zh_CN.dart


文件差异内容过多而无法显示
+ 508 - 536
packages/cs_resources/lib/generated/intl/messages_zh_HK.dart


文件差异内容过多而无法显示
+ 1101 - 191
packages/cs_resources/lib/generated/l10n.dart


+ 2 - 0
packages/cs_resources/lib/l10n/intl_en.arb

@@ -451,5 +451,7 @@
   "unpaid": "Unpaid",
   "refunded": "Refunded",
   "paid_on_sometime": "Paid on {time}",
+  "read_all_msg": "Are you sure you want to mark all notifications?",
+  "today": "Today",
   "other": "Other"
 }

+ 2 - 0
packages/cs_resources/lib/l10n/intl_zh_CN.arb

@@ -448,5 +448,7 @@
   "unpaid": "待支付",
   "refunded": "已退款",
   "paid_on_sometime": "付款时间 {time}",
+  "read_all_msg": "您确定要标记所有的消息通知为已读吗?",
+  "today": "今天",
   "other": "其他"
 }

+ 2 - 0
packages/cs_resources/lib/l10n/intl_zh_HK.arb

@@ -434,5 +434,7 @@
   "unpaid": "待支付",
   "refunded": "已退款",
   "paid_on_sometime": "付款时间 {time}",
+  "read_all_msg": "您确定要标记所有的消息通知为已读吗?",
+  "today": "今天",
   "other": "其他"
 }

+ 91 - 39
packages/cs_shared/lib/utils/date_time_utils.dart

@@ -1,3 +1,7 @@
+import 'package:intl/intl.dart';
+
+import 'log_utils.dart';
+
 /// month->days.
 Map<int, int> MONTH_DAY = {
   1: 31,
@@ -17,7 +21,7 @@ Map<int, int> MONTH_DAY = {
 /// ISO 8601 标准的时间格式字符串
 /// 时间与日期的格式化工具类(自定义基于Dart原生Api-DateTime实现)
 class DateTimeUtils {
-  static String full = 'yyyy-MM-dd HH:mm:ss';  //如 2023-05-25 12:05:03
+  static String full = 'yyyy-MM-dd HH:mm:ss'; //如 2023-05-25 12:05:03
 
   // ===================================== 反格式化时间 ↓ =====================================
 
@@ -49,6 +53,17 @@ class DateTimeUtils {
     return dateTime;
   }
 
+  //格式化自定义的时间格式
+  static DateTime? parseCustomDate(String dateStr, {String format = 'yyyy-MM-dd'}) {
+    try {
+      // 使用 DateFormat 来解析您的日期字符串
+      DateFormat dateFormat = DateFormat(format, 'en_US');
+      return dateFormat.parse(dateStr);
+    } catch (e) {
+      Log.e("Error parsing date: $e");
+      return null; // 如果解析失败,返回 null
+    }
+  }
 
   /*
      根据指定的毫秒时间戳得到原生的DateTime对象
@@ -61,7 +76,6 @@ class DateTimeUtils {
     return DateTime.fromMillisecondsSinceEpoch(ms, isUtc: isUtc);
   }
 
-
   /*
      根据日期格式得到对应的毫秒值
 
@@ -80,7 +94,6 @@ class DateTimeUtils {
     return dateTime?.millisecondsSinceEpoch;
   }
 
-
   /*
      获取当前系统日期的毫秒值
 
@@ -94,7 +107,6 @@ class DateTimeUtils {
 
   // ===================================== 反格式化时间 ↑ =====================================
 
-
   // ===================================== 格式化时间 ↓ =====================================
 
   /*
@@ -133,13 +145,10 @@ class DateTimeUtils {
      final date = DateTimeUtils.formatDateMs(1682304352000,format: 'dd MMM yyyy HH:mm');
      打印结果为:24 Apr 2023 10:45
    */
-  static String formatDateMs(int ms,
-      {bool isUtc = false, String? format, bool isMonthEn = true, bool isMonthEnShort = true}) {
-    return formatDate(getDateTimeByMs(ms, isUtc: isUtc),
-        format: format, isMonthEn: isMonthEn, isMonthEnShort: isMonthEnShort);
+  static String formatDateMs(int ms, {bool isUtc = false, String? format, bool isMonthEn = true, bool isMonthEnShort = true}) {
+    return formatDate(getDateTimeByMs(ms, isUtc: isUtc), format: format, isMonthEn: isMonthEn, isMonthEnShort: isMonthEnShort);
   }
 
-
   /*
      根据指定的日期字符串格式化时间值。默认格式为 yyyy-MM-dd HH:mm:ss
 
@@ -155,13 +164,10 @@ class DateTimeUtils {
      final date = DateTimeUtils.formatDateStr('2023/10/21 12:08:55',format: 'yyyy-MM-dd');
      这样就不行,因为 2023/10/21 12:08:55 不是标准格式无法转换为 DateTime 对象
    */
-  static String formatDateStr(String dateStr,
-      {bool? isUtc, String? format, bool isMonthEn = true, bool isMonthEnShort = true}) {
-    return formatDate(getDateTime(dateStr, isUtc: isUtc),
-        format: format, isMonthEn: isMonthEn, isMonthEnShort: isMonthEnShort);
+  static String formatDateStr(String dateStr, {bool? isUtc, String? format, bool isMonthEn = true, bool isMonthEnShort = true}) {
+    return formatDate(getDateTime(dateStr, isUtc: isUtc), format: format, isMonthEn: isMonthEn, isMonthEnShort: isMonthEnShort);
   }
 
-
   /*
      总方法 - 根据标准的 DateTime 来格式化时间。默认格式为 yyyy-MM-dd HH:mm:ss
 
@@ -233,40 +239,88 @@ class DateTimeUtils {
     String monthStr = month.toString();
     switch (month) {
       case 1:
-        monthStr = !isMonthEn ? month.toString() : isMonthEnShort ? "Jan" : "January";
+        monthStr = !isMonthEn
+            ? month.toString()
+            : isMonthEnShort
+                ? "Jan"
+                : "January";
         break;
       case 2:
-        monthStr = !isMonthEn ? month.toString() : isMonthEnShort ? "Feb" : "February";
+        monthStr = !isMonthEn
+            ? month.toString()
+            : isMonthEnShort
+                ? "Feb"
+                : "February";
         break;
       case 3:
-        monthStr = !isMonthEn ? month.toString() : isMonthEnShort ? "Mar" : "March";
+        monthStr = !isMonthEn
+            ? month.toString()
+            : isMonthEnShort
+                ? "Mar"
+                : "March";
         break;
       case 4:
-        monthStr = !isMonthEn ? month.toString() : isMonthEnShort ? "Apr" : "April";
+        monthStr = !isMonthEn
+            ? month.toString()
+            : isMonthEnShort
+                ? "Apr"
+                : "April";
         break;
       case 5:
-        monthStr = !isMonthEn ? month.toString() : isMonthEnShort ? "May" : "May";
+        monthStr = !isMonthEn
+            ? month.toString()
+            : isMonthEnShort
+                ? "May"
+                : "May";
         break;
       case 6:
-        monthStr = !isMonthEn ? month.toString() : isMonthEnShort ? "Jun" : "June";
+        monthStr = !isMonthEn
+            ? month.toString()
+            : isMonthEnShort
+                ? "Jun"
+                : "June";
         break;
       case 7:
-        monthStr = !isMonthEn ? month.toString() : isMonthEnShort ? "Jul" : "July";
+        monthStr = !isMonthEn
+            ? month.toString()
+            : isMonthEnShort
+                ? "Jul"
+                : "July";
         break;
       case 8:
-        monthStr = !isMonthEn ? month.toString() : isMonthEnShort ? "Aug" : "August";
+        monthStr = !isMonthEn
+            ? month.toString()
+            : isMonthEnShort
+                ? "Aug"
+                : "August";
         break;
       case 9:
-        monthStr = !isMonthEn ? month.toString() : isMonthEnShort ? "Sep" : "September";
+        monthStr = !isMonthEn
+            ? month.toString()
+            : isMonthEnShort
+                ? "Sep"
+                : "September";
         break;
       case 10:
-        monthStr = !isMonthEn ? month.toString() : isMonthEnShort ? "Oct" : "October";
+        monthStr = !isMonthEn
+            ? month.toString()
+            : isMonthEnShort
+                ? "Oct"
+                : "October";
         break;
       case 11:
-        monthStr = !isMonthEn ? month.toString() : isMonthEnShort ? "Nov" : "November";
+        monthStr = !isMonthEn
+            ? month.toString()
+            : isMonthEnShort
+                ? "Nov"
+                : "November";
         break;
       case 12:
-        monthStr = !isMonthEn ? month.toString() : isMonthEnShort ? "Dec" : "December";
+        monthStr = !isMonthEn
+            ? month.toString()
+            : isMonthEnShort
+                ? "Dec"
+                : "December";
         break;
     }
     return monthStr;
@@ -309,13 +363,9 @@ class DateTimeUtils {
       default:
         break;
     }
-    return languageCode == 'zh'
-        ? (short ? weekday.replaceAll('星期', '周') : weekday)
-        : weekday.substring(0, short ? 3 : weekday.length);
+    return languageCode == 'zh' ? (short ? weekday.replaceAll('星期', '周') : weekday) : weekday.substring(0, short ? 3 : weekday.length);
   }
 
-
-
   /// 在今年的第几天.
   static int getDayOfYear(DateTime dateTime) {
     int year = dateTime.year;
@@ -348,17 +398,21 @@ class DateTimeUtils {
     return old.year == now.year && old.month == now.month && old.day == now.day;
   }
 
+  /// 根据 DateTime 判断是否是当天
+  static bool isTodayByDateTime(DateTime target) {
+    final now = DateTime.now();
+    return target.year == now.year &&
+        target.month == now.month &&
+        target.day == now.day;
+  }
+
   /// 是否是昨天.
   static bool isYesterday(DateTime dateTime, DateTime locDateTime) {
     if (yearIsEqual(dateTime, locDateTime)) {
       int spDay = getDayOfYear(locDateTime) - getDayOfYear(dateTime);
       return spDay == 1;
     } else {
-      return ((locDateTime.year - dateTime.year == 1) &&
-          dateTime.month == 12 &&
-          locDateTime.month == 1 &&
-          dateTime.day == 31 &&
-          locDateTime.day == 1);
+      return ((locDateTime.year - dateTime.year == 1) && dateTime.month == 12 && locDateTime.month == 1 && dateTime.day == 31 && locDateTime.day == 1);
     }
   }
 
@@ -382,8 +436,7 @@ class DateTimeUtils {
 
     DateTime old = _now.millisecondsSinceEpoch > _old.millisecondsSinceEpoch ? _old : _now;
     DateTime now = _now.millisecondsSinceEpoch > _old.millisecondsSinceEpoch ? _now : _old;
-    return (now.weekday >= old.weekday) &&
-        (now.millisecondsSinceEpoch - old.millisecondsSinceEpoch <= 7 * 24 * 60 * 60 * 1000);
+    return (now.weekday >= old.weekday) && (now.millisecondsSinceEpoch - old.millisecondsSinceEpoch <= 7 * 24 * 60 * 60 * 1000);
   }
 
   /// 是否同年.
@@ -417,5 +470,4 @@ class DateTimeUtils {
     Duration difference = DateTime.now().difference(date);
     return difference.inDays;
   }
-
 }