Skip to content
Toggle navigation
Toggle navigation
This project
Loading...
Sign in
ethan
/
appframe
Go to a project
Toggle navigation
Toggle navigation pinning
Projects
Groups
Snippets
Help
Project
Activity
Repository
Pipelines
Graphs
Issues
0
Merge Requests
0
Wiki
Network
Create a new issue
Jobs
Commits
Issue Boards
Files
Commits
Network
Compare
Branches
Tags
Commit 5460fac6
authored
2026-06-29 13:54:19 +0800
by
tanghuan
Browse Files
Options
Browse Files
Tag
Download
Email Patches
Plain Diff
消息通知推送处理
1 parent
005587c7
Hide whitespace changes
Inline
Side-by-side
Showing
9 changed files
with
336 additions
and
125 deletions
android/app/build.gradle.kts
lib/bloc/web_cubit.dart
lib/config/locator.dart
lib/services/im_service.dart
lib/services/notification_service.dart
lib/ui/pages/reload_page.dart
lib/ui/pages/web_page.dart
lib/utils/login_util.dart
pubspec.yaml
android/app/build.gradle.kts
View file @
5460fac
...
...
@@ -21,6 +21,8 @@ android {
ndkVersion = flutter.ndkVersion
compileOptions {
// flutter_local_notifications 插件要求启用核心库脱糖
isCoreLibraryDesugaringEnabled = true
sourceCompatibility = JavaVersion.VERSION_11
targetCompatibility = JavaVersion.VERSION_11
}
...
...
@@ -91,6 +93,9 @@ flutter {
}
dependencies {
// flutter_local_notifications 插件要求启用核心库脱糖
coreLibraryDesugaring("com.android.tools:desugar_jdk_libs:2.1.4")
// 添加 AppCompat 支持库(PrivacyActivity需要)
implementation("androidx.appcompat:appcompat:1.6.1")
...
...
lib/bloc/web_cubit.dart
View file @
5460fac
...
...
@@ -57,6 +57,9 @@ class WebState extends Equatable {
final
int
?
userType
;
final
String
?
stuId
;
/// 通知点击等场景需要直接打开的 URL
final
String
?
targetUrl
;
/// getOrientationCmd
final
bool
orientationCmdFlag
;
final
String
orientationCmdMessage
;
...
...
@@ -99,6 +102,7 @@ class WebState extends Equatable {
this
.
classCode
,
this
.
userType
,
this
.
stuId
,
this
.
targetUrl
,
this
.
orientationCmdFlag
=
false
,
this
.
orientationCmdMessage
=
''
,
this
.
windowInfoCmdFlag
=
false
,
...
...
@@ -131,6 +135,7 @@ class WebState extends Equatable {
String
?
classCode
,
int
?
userType
,
String
?
stuId
,
String
?
targetUrl
,
bool
?
orientationCmdFlag
,
String
?
orientationCmdMessage
,
bool
?
windowInfoCmdFlag
,
...
...
@@ -162,6 +167,7 @@ class WebState extends Equatable {
classCode:
classCode
??
this
.
classCode
,
userType:
userType
??
this
.
userType
,
stuId:
stuId
??
this
.
stuId
,
targetUrl:
targetUrl
??
this
.
targetUrl
,
orientationCmdFlag:
orientationCmdFlag
??
this
.
orientationCmdFlag
,
orientationCmdMessage:
orientationCmdMessage
??
this
.
orientationCmdMessage
,
windowInfoCmdFlag:
windowInfoCmdFlag
??
this
.
windowInfoCmdFlag
,
...
...
@@ -195,6 +201,7 @@ class WebState extends Equatable {
classCode
,
userType
,
stuId
,
targetUrl
,
orientationCmdFlag
,
orientationCmdMessage
,
windowInfoCmdFlag
,
...
...
@@ -394,6 +401,15 @@ class WebCubit extends Cubit<WebState> with WidgetsBindingObserver {
}
void
_loadHtml
()
{
// 如果通过路由透传了 targetUrl(如通知点击场景),
// 优先在 WebView 中打开该 URL。
final
targetUrl
=
state
.
targetUrl
;
if
(
targetUrl
!=
null
&&
targetUrl
.
isNotEmpty
)
{
final
String
resolvedUrl
=
'
${Constant.localServerUrl}
/index.html#
$targetUrl
'
;
_controller
.
loadRequest
(
Uri
.
parse
(
resolvedUrl
));
return
;
}
var
sharedPreferences
=
getIt
.
get
<
SharedPreferences
>();
var
debug
=
sharedPreferences
.
getInt
(
'debug'
)
??
0
;
// 构造函数中已拦截判断未登录的情况进行了处理,所以这里不再处理未登录的情况
...
...
lib/config/locator.dart
View file @
5460fac
...
...
@@ -49,6 +49,7 @@ import 'package:appframe/services/api_service.dart';
import
'package:appframe/services/dispatcher.dart'
;
import
'package:appframe/services/im_service.dart'
;
import
'package:appframe/services/local_server_service.dart'
;
import
'package:appframe/services/notification_service.dart'
;
import
'package:appframe/services/player_service.dart'
;
import
'package:appframe/services/recorder_service.dart'
;
import
'package:fluwx/fluwx.dart'
;
...
...
@@ -237,6 +238,9 @@ Future<void> setupLocator() async {
instanceName:
"appApiService"
,
);
/// 通知服务
getIt
.
registerSingleton
<
NotificationService
>(
NotificationService
());
/// imService
getIt
.
registerSingleton
<
ImService
>(
ImService
());
...
...
lib/services/im_service.dart
View file @
5460fac
...
...
@@ -2,7 +2,9 @@ import 'package:appframe/config/constant.dart';
import
'package:appframe/config/locator.dart'
;
import
'package:appframe/data/repositories/subs_repository.dart'
;
import
'package:appframe/services/api_service.dart'
;
import
'package:appframe/services/notification_service.dart'
;
import
'package:flutter/material.dart'
;
import
'package:fluttertoast/fluttertoast.dart'
;
import
'package:shared_preferences/shared_preferences.dart'
;
import
'package:tencent_cloud_chat_push/common/tim_push_listener.dart'
;
import
'package:tencent_cloud_chat_push/common/tim_push_message.dart'
;
...
...
@@ -65,120 +67,140 @@ class ImService {
},
onRecvMessageReadReceipts:
(
List
<
V2TimMessageReceipt
>
receiptList
)
{
//群聊已读回调
for
(
var
element
in
receiptList
)
{
element
.
groupID
;
// 群id
element
.
msgID
;
// 已读回执消息 ID
element
.
readCount
;
// 群消息最新已读数
element
.
unreadCount
;
// 群消息最新未读数
element
.
userID
;
// C2C 消息对方 ID
}
//
for (var element in receiptList) {
//
element.groupID; // 群id
//
element.msgID; // 已读回执消息 ID
//
element.readCount; // 群消息最新已读数
//
element.unreadCount; // 群消息最新未读数
//
element.userID; // C2C 消息对方 ID
//
}
},
onRecvMessageRevoked:
(
String
messageid
)
{
// 在本地维护的消息中处理被对方撤回的消息
},
onRecvNewMessage:
(
V2TimMessage
message
)
async
{
// 处理文本消息
if
(
message
.
elemType
==
MessageElemType
.
V2TIM_ELEM_TYPE_TEXT
)
{
// message.textElem?.text;
// 时间戳转换
DateTime
dt
=
DateTime
.
fromMillisecondsSinceEpoch
(
message
.
timestamp
!
*
1000
);
debugPrint
(
"收到IM消息—— 时间:
${dt.year}
-
${dt.month}
-
${dt.day}
${dt.hour}
:
${dt.minute}
:
${dt.second}
发送者:
${message.sender}
内容:
${message.textElem?.text}
"
);
// 目前只会有文本消息,所以其他消息类型暂不处理,直接return
return
;
// 标记消息为已读
if
(
message
.
userID
!=
null
&&
message
.
userID
!.
isNotEmpty
)
{
// C2C消息标记已读
var
res
=
await
TencentImSDKPlugin
.
v2TIMManager
.
getConversationManager
().
cleanConversationUnreadMessageCount
(
conversationID:
'c2c_
${message.userID}
'
,
cleanTimestamp:
0
,
cleanSequence:
0
,
);
debugPrint
(
"[im_service]标记C2C消息已读: userID=
${message.userID}
, result=
${res.code}
"
);
}
else
if
(
message
.
groupID
!=
null
&&
message
.
groupID
!.
isNotEmpty
)
{
// 群消息标记已读
var
res
=
await
TencentImSDKPlugin
.
v2TIMManager
.
getConversationManager
().
cleanConversationUnreadMessageCount
(
conversationID:
'group_
${message.groupID}
'
,
cleanTimestamp:
0
,
cleanSequence:
0
,
);
debugPrint
(
"[im_service]标记群消息已读: groupID=
${message.groupID}
, result=
${res.code}
"
);
}
// 处理文本消息
// if (message.elemType == MessageElemType.V2TIM_ELEM_TYPE_TEXT) {
// // message.textElem?.text;
//
// // 时间戳转换
// DateTime dt = DateTime.fromMillisecondsSinceEpoch(message.timestamp! * 1000);
// debugPrint("[高级消息监听器] onRecvNewMessage 时间:${dt.year}-${dt.month}-${dt.day} ${dt.hour}:${dt.minute}:${dt.second} 发送者:${message.sender} 内容:${message.textElem?.text}");
//
// Fluttertoast.showToast(msg: '[高级消息监听器] onRecvNewMessage ${message.textElem?.text ?? ""}', gravity: ToastGravity.TOP);
//
// // 目前只会有文本消息,所以其他消息类型暂不处理,直接return
// return;
// }
// 使用自定义消息
if
(
message
.
elemType
==
MessageElemType
.
V2TIM_ELEM_TYPE_CUSTOM
)
{
message
.
customElem
?.
data
;
message
.
customElem
?.
desc
;
message
.
customElem
?.
extension
;
}
// 使用图片消息
if
(
message
.
elemType
==
MessageElemType
.
V2TIM_ELEM_TYPE_IMAGE
)
{
message
.
imageElem
?.
path
;
// 图片上传时的路径,消息发送者才会有这个字段,消息发送者可用这个字段将图片预先上屏,优化上屏体验。
message
.
imageElem
?.
imageList
?.
forEach
((
element
)
{
// 遍历大图、原图、缩略图
// 解析图片属性
element
?.
height
;
element
?.
localUrl
;
element
?.
size
;
element
?.
type
;
// 大图 缩略图 原图
element
?.
url
;
element
?.
uuid
;
element
?.
width
;
});
}
// 处理视频消息
if
(
message
.
elemType
==
MessageElemType
.
V2TIM_ELEM_TYPE_VIDEO
)
{
// 解析视频消息属性,封面、播放地址、宽高、大小等。
message
.
videoElem
?.
UUID
;
message
.
videoElem
?.
duration
;
message
.
videoElem
?.
localSnapshotUrl
;
message
.
videoElem
?.
localVideoUrl
;
message
.
videoElem
?.
snapshotHeight
;
message
.
videoElem
?.
snapshotPath
;
// ...
}
// 处理音频消息
if
(
message
.
elemType
==
MessageElemType
.
V2TIM_ELEM_TYPE_SOUND
)
{
// 解析语音消息 播放地址,本地地址,大小,时长等。
message
.
soundElem
?.
UUID
;
message
.
soundElem
?.
dataSize
;
message
.
soundElem
?.
duration
;
message
.
soundElem
?.
localUrl
;
message
.
soundElem
?.
url
;
// ...
}
// 处理文件消息
if
(
message
.
elemType
==
MessageElemType
.
V2TIM_ELEM_TYPE_FILE
)
{
// 解析文件消息 文件名、文件大小、url等
message
.
fileElem
?.
UUID
;
message
.
fileElem
?.
fileName
;
message
.
fileElem
?.
fileSize
;
message
.
fileElem
?.
localUrl
;
message
.
fileElem
?.
path
;
message
.
fileElem
?.
url
;
}
// 处理位置消息
if
(
message
.
elemType
==
MessageElemType
.
V2TIM_ELEM_TYPE_LOCATION
)
{
// 解析地理位置消息,经纬度、描述等
message
.
locationElem
?.
desc
;
message
.
locationElem
?.
latitude
;
message
.
locationElem
?.
longitude
;
}
// 处理表情消息
if
(
message
.
elemType
==
MessageElemType
.
V2TIM_ELEM_TYPE_FACE
)
{
message
.
faceElem
?.
data
;
message
.
faceElem
?.
index
;
}
// 处理群组tips文本消息
if
(
message
.
elemType
==
MessageElemType
.
V2TIM_ELEM_TYPE_GROUP_TIPS
)
{
message
.
groupTipsElem
?.
groupID
;
// 所属群组
message
.
groupTipsElem
?.
type
;
// 群Tips类型
message
.
groupTipsElem
?.
opMember
;
// 操作人资料
message
.
groupTipsElem
?.
memberList
;
// 被操作人资料
message
.
groupTipsElem
?.
groupChangeInfoList
;
// 群信息变更详情
message
.
groupTipsElem
?.
memberChangeInfoList
;
// 群成员变更信息
message
.
groupTipsElem
?.
memberCount
;
// 当前群在线人数
}
// 处理合并消息消息
if
(
message
.
elemType
==
MessageElemType
.
V2TIM_ELEM_TYPE_MERGER
)
{
message
.
mergerElem
?.
abstractList
;
message
.
mergerElem
?.
isLayersOverLimit
;
message
.
mergerElem
?.
title
;
V2TimValueCallback
<
List
<
V2TimMessage
>>
download
=
await
TencentImSDKPlugin
.
v2TIMManager
.
getMessageManager
().
downloadMergerMessage
(
msgID:
message
.
msgID
!,
);
if
(
download
.
code
==
0
)
{
//List<V2TimMessage>? messageList = download.data;
}
}
if
(
message
.
textElem
?.
nextElem
!=
null
)
{
//通过第一个 Elem 对象的 nextElem 方法获取下一个 Elem 对象,如果下一个 Elem 对象存在,会返回 Elem 对象实例,如果不存在,会返回 null。
}
//
if (message.elemType == MessageElemType.V2TIM_ELEM_TYPE_CUSTOM) {
//
message.customElem?.data;
//
message.customElem?.desc;
//
message.customElem?.extension;
//
}
//
//
使用图片消息
//
if (message.elemType == MessageElemType.V2TIM_ELEM_TYPE_IMAGE) {
//
message.imageElem?.path; // 图片上传时的路径,消息发送者才会有这个字段,消息发送者可用这个字段将图片预先上屏,优化上屏体验。
//
message.imageElem?.imageList?.forEach((element) {
//
// 遍历大图、原图、缩略图
//
// 解析图片属性
//
element?.height;
//
element?.localUrl;
//
element?.size;
//
element?.type; // 大图 缩略图 原图
//
element?.url;
//
element?.uuid;
//
element?.width;
//
});
//
}
//
//
处理视频消息
//
if (message.elemType == MessageElemType.V2TIM_ELEM_TYPE_VIDEO) {
//
// 解析视频消息属性,封面、播放地址、宽高、大小等。
//
message.videoElem?.UUID;
//
message.videoElem?.duration;
//
message.videoElem?.localSnapshotUrl;
//
message.videoElem?.localVideoUrl;
//
message.videoElem?.snapshotHeight;
//
message.videoElem?.snapshotPath;
//
// ...
//
}
//
//
处理音频消息
//
if (message.elemType == MessageElemType.V2TIM_ELEM_TYPE_SOUND) {
//
// 解析语音消息 播放地址,本地地址,大小,时长等。
//
message.soundElem?.UUID;
//
message.soundElem?.dataSize;
//
message.soundElem?.duration;
//
message.soundElem?.localUrl;
//
message.soundElem?.url;
//
// ...
//
}
//
//
处理文件消息
//
if (message.elemType == MessageElemType.V2TIM_ELEM_TYPE_FILE) {
//
// 解析文件消息 文件名、文件大小、url等
//
message.fileElem?.UUID;
//
message.fileElem?.fileName;
//
message.fileElem?.fileSize;
//
message.fileElem?.localUrl;
//
message.fileElem?.path;
//
message.fileElem?.url;
//
}
//
//
处理位置消息
//
if (message.elemType == MessageElemType.V2TIM_ELEM_TYPE_LOCATION) {
//
// 解析地理位置消息,经纬度、描述等
//
message.locationElem?.desc;
//
message.locationElem?.latitude;
//
message.locationElem?.longitude;
//
}
//
//
处理表情消息
//
if (message.elemType == MessageElemType.V2TIM_ELEM_TYPE_FACE) {
//
message.faceElem?.data;
//
message.faceElem?.index;
//
}
//
//
处理群组tips文本消息
//
if (message.elemType == MessageElemType.V2TIM_ELEM_TYPE_GROUP_TIPS) {
//
message.groupTipsElem?.groupID; // 所属群组
//
message.groupTipsElem?.type; // 群Tips类型
//
message.groupTipsElem?.opMember; // 操作人资料
//
message.groupTipsElem?.memberList; // 被操作人资料
//
message.groupTipsElem?.groupChangeInfoList; // 群信息变更详情
//
message.groupTipsElem?.memberChangeInfoList; // 群成员变更信息
//
message.groupTipsElem?.memberCount; // 当前群在线人数
//
}
//
//
处理合并消息消息
//
if (message.elemType == MessageElemType.V2TIM_ELEM_TYPE_MERGER) {
//
message.mergerElem?.abstractList;
//
message.mergerElem?.isLayersOverLimit;
//
message.mergerElem?.title;
//
V2TimValueCallback<List<V2TimMessage>> download =
//
await TencentImSDKPlugin.v2TIMManager.getMessageManager().downloadMergerMessage(
//
msgID: message.msgID!,
//
);
//
if (download.code == 0) {
//
//List<V2TimMessage>? messageList = download.data;
//
}
//
}
//
if (message.textElem?.nextElem != null) {
//
//通过第一个 Elem 对象的 nextElem 方法获取下一个 Elem 对象,如果下一个 Elem 对象存在,会返回 Elem 对象实例,如果不存在,会返回 null。
//
}
},
onSendMessageProgress:
(
V2TimMessage
message
,
int
progress
)
{
//文件上传进度回调
...
...
@@ -349,33 +371,33 @@ class ImService {
void
_onNotificationClicked
({
required
String
ext
,
String
?
userID
,
String
?
groupID
})
{
debugPrint
(
"_onNotificationClicked:
$ext
, userID:
$userID
, groupID:
$groupID
"
);
if
(
userID
!=
null
||
groupID
!=
null
)
{
// 根据 userID 或 groupID 跳转至对应 Message 页面.
}
else
{
// 根据 ext 字段, 自己写解析方式, 跳转至对应页面.
}
getIt
.
get
<
NotificationService
>().
handleNotificationClick
(
ext
);
}
TIMPushListener
timPushListener
=
TIMPushListener
(
onRecvPushMessage:
(
TimPushMessage
message
)
{
debugPrint
(
'推送监听器 onRecvPushMessage-------------'
);
String
messageLog
=
message
.
toLogString
();
debugPrint
(
"message:
$messageLog
"
);
// // 手机消息通知
// getIt.get<NotificationService>().showNotification(
// id: DateTime.now().millisecondsSinceEpoch % 1000000,
// title: message.title ?? '',
// body: message.desc ?? '',
// );
// debugPrint('[推送监听器] onRecvPushMessage-------------');
// String messageLog = message.toLogString();
// debugPrint("message: $messageLog");
//
// Fluttertoast.showToast(msg: "[推送监听器] onRecvPushMessage: ${message.toLogString()}", gravity: ToastGravity.TOP);
debugPrint
(
'[im_service][推送监听器] onRecvPushMessage------------- 调起手机通知'
);
// 手机消息通知
getIt
.
get
<
NotificationService
>().
showNotification
(
id:
DateTime
.
now
().
millisecondsSinceEpoch
%
1000000
,
title:
message
.
title
??
''
,
body:
message
.
desc
??
''
,
payload:
message
.
ext
??
''
,
);
},
onRevokePushMessage:
(
String
messageId
)
{
debugPrint
(
'
推送监听器
onRevokePushMessage-------------'
);
debugPrint
(
'
[im_service][推送监听器]
onRevokePushMessage-------------'
);
debugPrint
(
"message:
$messageId
"
);
},
onNotificationClicked:
(
String
ext
)
{
debugPrint
(
'
推送监听器
onNotificationClicked-------------'
);
debugPrint
(
'
[im_service][推送监听器]
onNotificationClicked-------------'
);
debugPrint
(
"ext:
$ext
"
);
Fluttertoast
.
showToast
(
msg:
"[im_service][推送监听器] Notification Clicked:
$ext
"
,
toastLength:
Toast
.
LENGTH_LONG
);
},
);
...
...
lib/services/notification_service.dart
0 → 100644
View file @
5460fac
import
'dart:convert'
;
import
'dart:io'
;
import
'package:appframe/config/locator.dart'
;
import
'package:appframe/config/routes.dart'
;
import
'package:flutter_local_notifications/flutter_local_notifications.dart'
;
import
'package:flutter/material.dart'
;
import
'package:shared_preferences/shared_preferences.dart'
;
class
NotificationService
{
final
FlutterLocalNotificationsPlugin
_plugin
=
FlutterLocalNotificationsPlugin
();
bool
_isInitialized
=
false
;
bool
get
isInitialized
=>
_isInitialized
;
/// 初始化通知插件
Future
<
void
>
initialize
()
async
{
if
(
_isInitialized
)
return
;
const
androidSettings
=
AndroidInitializationSettings
(
'@mipmap/launcher_icon'
);
const
iosSettings
=
DarwinInitializationSettings
(
requestAlertPermission:
true
,
requestBadgePermission:
true
,
requestSoundPermission:
true
,
);
const
settings
=
InitializationSettings
(
android:
androidSettings
,
iOS:
iosSettings
);
final
result
=
await
_plugin
.
initialize
(
settings
,
onDidReceiveNotificationResponse:
_onNotificationResponse
,
);
_isInitialized
=
result
??
false
;
if
(
_isInitialized
)
{
debugPrint
(
'NotificationService 初始化成功'
);
// Android 13+ 需要请求通知权限
if
(
Platform
.
isAndroid
)
{
final
androidPlugin
=
_plugin
.
resolvePlatformSpecificImplementation
<
AndroidFlutterLocalNotificationsPlugin
>();
await
androidPlugin
?.
requestNotificationsPermission
();
}
}
else
{
debugPrint
(
'NotificationService 初始化失败'
);
}
}
/// 处理通知点击(供外部调用,如离线推送点击)
void
handleNotificationClick
(
String
?
payload
)
{
debugPrint
(
'[notification_service] handleNotificationClick payload:
$payload
'
);
// 通过 /reload 中转路由跳转,确保即使当前已在 /web,
// GoRouter 也会完整重建 WebPage 并加载新的 targetUrl。
final
targetUrl
=
_buildTargetUrl
(
payload
);
debugPrint
(
'[notification_service] targetUrl:
$targetUrl
'
);
if
(
targetUrl
!=
null
&&
targetUrl
.
isNotEmpty
)
{
router
.
go
(
'/reload'
,
extra:
{
'targetUrl'
:
targetUrl
},
);
}
else
{
router
.
go
(
'/web'
);
}
}
/// 前台本地通知点击回调处理
void
_onNotificationResponse
(
NotificationResponse
response
)
{
debugPrint
(
'[notification_service] Notification clicked, payload:
${response.payload}
'
);
handleNotificationClick
(
response
.
payload
);
}
/// 解析通知 payload,结合 auth_roles 缓存拼接目标 URL。
/// payload 格式:{"func": "homework", "uniqueCode": "...", "classCode": "..."}
/// 返回示例:/h5/login/pages/applogin?sessionCode=xxx&userCode=xxx&classCode=xxx&userType=2&stuId=xxx&page=homeworkdetail&uniqueCode=xxx
String
?
_buildTargetUrl
(
String
?
payload
)
{
if
(
payload
==
null
||
payload
.
isEmpty
)
return
null
;
try
{
final
data
=
jsonDecode
(
payload
)
as
Map
<
String
,
dynamic
>;
final
uniqueCode
=
data
[
'uniqueCode'
]
as
String
?;
final
classCode
=
data
[
'classCode'
]
as
String
?;
if
(
uniqueCode
==
null
||
uniqueCode
.
isEmpty
||
classCode
==
null
||
classCode
.
isEmpty
)
{
debugPrint
(
'[notification_service] payload 缺少 uniqueCode 或 classCode'
);
return
null
;
}
final
prefs
=
getIt
.
get
<
SharedPreferences
>();
final
rolesJson
=
prefs
.
getString
(
'auth_roles'
)
??
''
;
if
(
rolesJson
.
isEmpty
)
{
debugPrint
(
'[notification_service] auth_roles 缓存为空'
);
return
null
;
}
final
roles
=
jsonDecode
(
rolesJson
)
as
List
<
dynamic
>;
Map
<
String
,
dynamic
>?
matchedRole
;
for
(
final
role
in
roles
)
{
if
(
role
is
Map
<
String
,
dynamic
>
&&
role
[
'classCode'
]?.
toString
()
==
classCode
)
{
matchedRole
=
role
;
break
;
}
}
if
(
matchedRole
==
null
)
{
debugPrint
(
'[notification_service] 未找到与 classCode=
$classCode
匹配的 role'
);
return
null
;
}
final
stuId
=
matchedRole
[
'stuId'
]?.
toString
()
??
''
;
final
sessionCode
=
prefs
.
getString
(
'auth_sessionCode'
)
??
''
;
final
userCode
=
prefs
.
getString
(
'auth_userCode'
)
??
''
;
return
'/h5/login/pages/applogin?'
'sessionCode=
$sessionCode
&'
'userCode=
$userCode
&'
'classCode=
$classCode
&'
'userType=2&'
'stuId=
$stuId
&'
'page=homeworkdetail&'
'uniqueCode=
$uniqueCode
'
;
}
catch
(
e
,
stackTrace
)
{
debugPrint
(
'[notification_service] 解析 payload 失败:
$e
\n
$stackTrace
'
);
return
null
;
}
}
/// 显示手机通知(懒初始化:首次调用自动触发初始化)
Future
<
void
>
showNotification
({
required
int
id
,
required
String
title
,
required
String
body
,
String
?
payload
,
})
async
{
// 懒初始化:首次调用时自动初始化
if
(!
_isInitialized
)
{
await
initialize
();
}
const
androidDetails
=
AndroidNotificationDetails
(
'homework_message_channel'
,
'作业消息通知'
,
channelDescription:
'接收作业消息推送通知'
,
importance:
Importance
.
high
,
priority:
Priority
.
high
,
);
const
iosDetails
=
DarwinNotificationDetails
();
const
details
=
NotificationDetails
(
android:
androidDetails
,
iOS:
iosDetails
);
await
_plugin
.
show
(
id
,
title
,
body
,
details
,
payload:
payload
);
debugPrint
(
'NotificationService showNotification: id=
$id
, title=
$title
, body=
$body
'
);
}
}
lib/ui/pages/reload_page.dart
View file @
5460fac
import
'package:appframe/config/routes.dart'
;
import
'package:flutter/material.dart'
;
import
'package:go_router/go_router.dart'
;
///
/// 用于重新加载的中间路由
/// 通知点击等场景先跳转到此页,再转发 targetUrl 到 /web,
/// 确保 GoRouter 完整重建 WebPage 并加载目标 URL。
///
class
ReloadPage
extends
StatefulWidget
{
const
ReloadPage
({
super
.
key
});
...
...
@@ -23,7 +26,12 @@ class _ReloadPageState extends State<ReloadPage> {
}
void
_performPostDisplayOperations
()
{
router
.
go
(
'/web'
);
final
extra
=
GoRouterState
.
of
(
context
).
extra
;
if
(
extra
is
Map
<
String
,
dynamic
>
&&
extra
[
'targetUrl'
]
!=
null
)
{
router
.
go
(
'/web'
,
extra:
extra
);
}
else
{
router
.
go
(
'/web'
);
}
}
@override
...
...
lib/ui/pages/web_page.dart
View file @
5460fac
...
...
@@ -25,6 +25,7 @@ class WebPage extends StatelessWidget {
var
classCode
=
extraData
?[
'classCode'
];
var
userType
=
extraData
?[
'userType'
];
var
stuId
=
extraData
?[
'stuId'
];
var
targetUrl
=
extraData
?[
'targetUrl'
];
if
(
sessionCode
==
null
||
sessionCode
==
''
)
{
loginOpFlag
=
false
;
...
...
@@ -49,6 +50,7 @@ class WebPage extends StatelessWidget {
classCode:
classCode
,
userType:
userType
,
stuId:
stuId
,
targetUrl:
targetUrl
,
),
);
return
webCubit
;
...
...
lib/utils/login_util.dart
View file @
5460fac
import
'dart:convert'
;
import
'package:appframe/config/constant.dart'
;
import
'package:appframe/config/locator.dart'
;
import
'package:appframe/config/routes.dart'
;
...
...
@@ -89,6 +91,8 @@ class LoginUtil {
sharedPreferences
.
setString
(
'auth_className'
,
className
);
sharedPreferences
.
setString
(
'auth_stuName'
,
stuName
);
sharedPreferences
.
setString
(
'auth_relation'
,
relation
);
// 用于处理点击推送通知时,获取跳转到指定页面的参数
sharedPreferences
.
setString
(
'auth_roles'
,
jsonEncode
(
roles
));
if
(
loginType
==
'router'
)
{
router
.
go
(
...
...
pubspec.yaml
View file @
5460fac
...
...
@@ -95,6 +95,7 @@ dependencies:
cupertino_icons
:
^1.0.8
fluttertoast
:
^9.0.0
flutter_local_notifications
:
^19.5.0
dev_dependencies
:
flutter_test
:
...
...
Write
Preview
Styling with
Markdown
is supported
Attach a file
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to post a comment