feat: 添加消息中心及顶栏未读角标功能
新增消息中心页面,实现未读消息轮询与展示 为各 Tab 页面顶栏添加通知图标及未读数角标 新增通知 API 接口与未读数状态管理 实现消息标记已读、全部已读及下拉刷新功能
This commit is contained in:
1 parent
66013ee069
commit
7ad604ff8b
9 files changed
+575
-5
No files matched your search
@@ -0,0 +1,132 @@
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
|
||||
import '../../../core/network/api_client.dart';
|
||||
import '../../../core/network/api_response.dart';
|
||||
import '../../../core/providers.dart';
|
||||
import '../../../core/utils/id_util.dart';
|
||||
|
||||
final noticeApiProvider = Provider<NoticeApi>(
|
||||
(ref) => NoticeApi(ref.watch(apiClientProvider)),
|
||||
);
|
||||
|
||||
/// 站内通知,与后端 `sys_notice` / `/api/notices` 契约对齐。
|
||||
///
|
||||
/// 管理端接口固定 `receiver_type='user'`,字段序列化为驼峰(主键 BIGINT 为字符串)。
|
||||
class Notice {
|
||||
const Notice({
|
||||
required this.id,
|
||||
required this.noticeType,
|
||||
required this.title,
|
||||
required this.content,
|
||||
required this.bizType,
|
||||
required this.bizId,
|
||||
required this.isRead,
|
||||
required this.receiverType,
|
||||
required this.ruleId,
|
||||
required this.batchId,
|
||||
required this.createdAt,
|
||||
});
|
||||
|
||||
final int id;
|
||||
final String noticeType;
|
||||
final String title;
|
||||
final String content;
|
||||
final String bizType;
|
||||
final int? bizId;
|
||||
|
||||
/// 0 未读 / 1 已读
|
||||
final int isRead;
|
||||
final String receiverType;
|
||||
final int? ruleId;
|
||||
final int? batchId;
|
||||
final String? createdAt;
|
||||
|
||||
bool get unread => isRead == 0;
|
||||
|
||||
factory Notice.fromJson(Map<String, dynamic> json) => Notice(
|
||||
id: toIntOr(json['id'], 0),
|
||||
noticeType: json['noticeType'] as String? ?? '',
|
||||
title: json['title'] as String? ?? '',
|
||||
content: json['content'] as String? ?? '',
|
||||
bizType: json['bizType'] as String? ?? '',
|
||||
bizId: toIntOrNull(json['bizId']),
|
||||
isRead: toIntOr(json['isRead'], 0),
|
||||
receiverType: json['receiverType'] as String? ?? 'user',
|
||||
ruleId: toIntOrNull(json['ruleId']),
|
||||
batchId: toIntOrNull(json['batchId']),
|
||||
createdAt: (json['created_at'] ?? json['createdAt']) as String?,
|
||||
);
|
||||
|
||||
Notice copyWith({int? isRead}) => Notice(
|
||||
id: id,
|
||||
noticeType: noticeType,
|
||||
title: title,
|
||||
content: content,
|
||||
bizType: bizType,
|
||||
bizId: bizId,
|
||||
isRead: isRead ?? this.isRead,
|
||||
receiverType: receiverType,
|
||||
ruleId: ruleId,
|
||||
batchId: batchId,
|
||||
createdAt: createdAt,
|
||||
);
|
||||
}
|
||||
|
||||
/// `/notices` 分页结果。
|
||||
class NoticePage {
|
||||
const NoticePage({
|
||||
required this.list,
|
||||
required this.total,
|
||||
required this.page,
|
||||
required this.pageSize,
|
||||
});
|
||||
|
||||
final List<Notice> list;
|
||||
final int total;
|
||||
final int page;
|
||||
final int pageSize;
|
||||
|
||||
factory NoticePage.fromJson(Map<String, dynamic> json, int fallbackPageSize) =>
|
||||
NoticePage(
|
||||
list: (json['list'] as List? ?? [])
|
||||
.map((e) => Notice.fromJson((e as Map).cast<String, dynamic>()))
|
||||
.toList(),
|
||||
total: toIntOr(json['total'], 0),
|
||||
page: toIntOr(json['page'], 1),
|
||||
pageSize: toIntOr(json['pageSize'], fallbackPageSize),
|
||||
);
|
||||
}
|
||||
|
||||
/// 站内消息接口,对应后端 `notice.controller.ts`(管理端)。
|
||||
class NoticeApi {
|
||||
NoticeApi(this._client);
|
||||
|
||||
final ApiClient _client;
|
||||
|
||||
Future<ApiResponse<NoticePage>> list({
|
||||
int page = 1,
|
||||
int pageSize = 15,
|
||||
int? isRead,
|
||||
}) => _client.get<NoticePage>(
|
||||
'/notices',
|
||||
query: {'page': page, 'pageSize': pageSize, 'isRead': isRead},
|
||||
parse: (d) =>
|
||||
NoticePage.fromJson((d as Map).cast<String, dynamic>(), pageSize),
|
||||
);
|
||||
|
||||
/// 未读数量(顶栏红点)。
|
||||
Future<ApiResponse<int>> unreadCount() => _client.get<int>(
|
||||
'/notices/unread-count',
|
||||
parse: (d) => toIntOr((d as Map)['count'], 0),
|
||||
);
|
||||
|
||||
/// 单条标记已读。
|
||||
Future<ApiResponse<void>> markRead(int id) =>
|
||||
_client.put<void>('/notices/$id/read');
|
||||
|
||||
/// 全部标记已读,返回本次更新条数。
|
||||
Future<ApiResponse<int>> markAllRead() => _client.put<int>(
|
||||
'/notices/read-all',
|
||||
parse: (d) => toIntOr((d as Map)['updated'], 0),
|
||||
);
|
||||
}
|
||||
Reference in new issue
Block a user