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 bd25deab
authored
2026-06-18 09:22:05 +0800
by
tanghuan
Browse Files
Options
Browse Files
Tag
Download
Email Patches
Plain Diff
游客登录、微信绑定、二维码扫码绑定
1 parent
6304208d
Hide whitespace changes
Inline
Side-by-side
Showing
16 changed files
with
990 additions
and
421 deletions
lib/bloc/login_main_cubit.dart
lib/bloc/login_phone_cubit.dart
lib/bloc/login_qr_cubit.dart
lib/bloc/web_cubit.dart
lib/config/constant.dart
lib/config/locator.dart
lib/data/repositories/message/wechat_bind_handler.dart
lib/data/repositories/message/wechat_qr_bind_handler.dart
lib/data/repositories/phone_auth_repository.dart
lib/data/repositories/user_auth_repository.dart
lib/services/dispatcher.dart
lib/ui/pages/login_main_page.dart
lib/ui/pages/login_v3/login_main_page_v3.dart
lib/ui/pages/web_page.dart
lib/ui/widgets/wechat_qr_bind_dialog.dart
lib/utils/login_util.dart
lib/bloc/login_main_cubit.dart
View file @
bd25dea
...
...
@@ -6,6 +6,7 @@ import 'package:appframe/config/locator.dart';
import
'package:appframe/config/routes.dart'
;
import
'package:appframe/data/repositories/user_auth_repository.dart'
;
import
'package:appframe/data/repositories/wechat_auth_repository.dart'
;
import
'package:appframe/utils/login_util.dart'
;
import
'package:equatable/equatable.dart'
;
import
'package:flutter/material.dart'
;
import
'package:flutter_bloc/flutter_bloc.dart'
;
...
...
@@ -181,37 +182,44 @@ class LoginMainCubit extends Cubit<LoginMainState> with WidgetsBindingObserver {
var
data
=
resultData
[
'data'
]
as
Map
<
String
,
dynamic
>;
int
binding
=
resultData
[
'binding'
];
if
(
binding
==
1
)
{
_handleLoginSuccess
(
data
);
}
else
{
// 未绑定时,也会返回 sessionCode 和 userCode
if
(
state
.
wechatInstalled
)
{
// 已安装微信APP,直接拉起微信授权,不使用 sessionCode 和 userCode
// 设置 appleUserIdentifier 状态,通知用户需要授权微信认证
emit
(
state
.
copyWith
(
appleUserIdentifier:
data
[
'appleUid'
]!,
showNeedWechatForApple:
true
));
}
else
{
// 未安装微信APP,使用 sessionCode 和 userCode
_handleLoginSuccess
(
data
);
}
var
visitor
=
binding
==
1
?
0
:
1
;
if
(
visitor
==
1
)
{
var
sharedPreferences
=
getIt
.
get
<
SharedPreferences
>();
sharedPreferences
.
setString
(
'auth_visitor_type'
,
'apple'
);
sharedPreferences
.
setString
(
'auth_visitor_id'
,
credential
.
userIdentifier
!);
}
LoginUtil
.
handleLoginSuccess
(
data
,
visitor
,
'router'
);
// if (binding == 1) {
// _handleLoginSuccess(data, 0);
// } else {
// // 未绑定时,也会返回 sessionCode 和 userCode
// if (state.wechatInstalled) {
// // 已安装微信APP,直接拉起微信授权,不使用 sessionCode 和 userCode
// // 设置 appleUserIdentifier 状态,通知用户需要授权微信认证
// emit(state.copyWith(appleUserIdentifier: data['appleUid']!, showNeedWechatForApple: true));
// } else {
// // 未安装微信APP,使用 sessionCode 和 userCode
// _handleLoginSuccess(data, 1);
// }
// }
}
Future
<
void
>
wechatAuthForApple
()
async
{
emit
(
state
.
copyWith
(
showNeedWechatForApple:
false
,
loginType:
2
));
var
authResult
=
await
_fluwx
.
authBy
(
which:
NormalAuth
(
scope:
'snsapi_userinfo'
,
state:
'wechat_sdk_test'
),
);
if
(!
authResult
)
{
Fluttertoast
.
showToast
(
msg:
'微信授权处理失败'
,
gravity:
ToastGravity
.
TOP
,
backgroundColor:
Colors
.
red
);
return
;
}
// 控制显示加载框,并启动等待微信授权回调的兜底机制
emit
(
state
.
copyWith
(
loading:
true
));
_startWechatAuthWaiting
();
}
//
Future<void> wechatAuthForApple() async {
//
emit(state.copyWith(showNeedWechatForApple: false, loginType: 2));
//
//
var authResult = await _fluwx.authBy(
//
which: NormalAuth(scope: 'snsapi_userinfo', state: 'wechat_sdk_test'),
//
);
//
//
if (!authResult) {
//
Fluttertoast.showToast(msg: '微信授权处理失败', gravity: ToastGravity.TOP, backgroundColor: Colors.red);
//
return;
//
}
//
//
// 控制显示加载框,并启动等待微信授权回调的兜底机制
//
emit(state.copyWith(loading: true));
//
_startWechatAuthWaiting();
//
}
Future
<
void
>
wechatAuth
()
async
{
emit
(
state
.
copyWith
(
loginType:
1
));
...
...
@@ -267,104 +275,10 @@ class LoginMainCubit extends Cubit<LoginMainState> with WidgetsBindingObserver {
}
var
data
=
resultData
[
'data'
]
as
Map
<
String
,
dynamic
>;
_handleLoginSuccess
(
data
);
LoginUtil
.
handleLoginSuccess
(
data
,
0
,
'router'
);
}
}
void
_handleLoginSuccess
(
Map
<
String
,
dynamic
>
data
)
{
var
roles
=
data
[
'roles'
];
// 过滤出家长角色的数据
if
(
roles
?.
isNotEmpty
??
false
)
{
roles
.
removeWhere
((
element
)
=>
element
[
'userType'
]
!=
2
);
}
else
{
roles
=
[];
}
var
sessionCode
=
data
[
'sessionCode'
];
var
userCode
=
data
[
'userCode'
];
var
classCode
=
''
;
var
userType
=
0
;
var
stuId
=
''
;
var
className
=
''
;
var
stuName
=
''
;
var
relation
=
''
;
var
sharedPreferences
=
getIt
.
get
<
SharedPreferences
>();
if
(
roles
.
isNotEmpty
)
{
var
role
=
roles
[
0
];
classCode
=
role
[
'classCode'
];
userType
=
role
[
'userType'
];
stuId
=
role
[
'stuId'
];
className
=
role
[
'className'
];
stuName
=
role
[
'stuName'
];
relation
=
role
[
'relation'
]
??
''
;
List
<
String
>
classIdList
=
[];
for
(
var
role
in
roles
)
{
classIdList
.
add
(
role
[
'classCode'
]
as
String
);
}
debugPrint
(
'classCodeIds:--------------
$classIdList
'
);
sharedPreferences
.
setStringList
(
Constant
.
classIdSetKey
,
classIdList
);
}
else
{
sharedPreferences
.
setStringList
(
Constant
.
classIdSetKey
,
[]);
}
var
preUserCode
=
sharedPreferences
.
getString
(
'pre_userCode'
)
??
''
;
if
(
userCode
!=
preUserCode
)
{
sharedPreferences
.
setString
(
'pre_userCode'
,
userCode
);
sharedPreferences
.
setString
(
'pre_classCode'
,
classCode
);
sharedPreferences
.
setInt
(
'pre_userType'
,
userType
);
sharedPreferences
.
setString
(
'pre_stuId'
,
stuId
);
}
else
{
var
preClassCode
=
sharedPreferences
.
getString
(
'pre_classCode'
)
??
''
;
var
preUserType
=
sharedPreferences
.
getInt
(
'pre_userType'
)
??
0
;
var
preStuId
=
sharedPreferences
.
getString
(
'pre_stuId'
)
??
''
;
if
(
preClassCode
!=
''
&&
roles
.
any
((
element
)
=>
element
[
'classCode'
]
==
preClassCode
&&
element
[
'userType'
]
==
preUserType
&&
element
[
'stuId'
]
==
preStuId
))
{
classCode
=
preClassCode
;
userType
=
preUserType
;
stuId
=
preStuId
;
}
else
{
sharedPreferences
.
setString
(
'pre_userCode'
,
userCode
);
sharedPreferences
.
setString
(
'pre_classCode'
,
classCode
);
sharedPreferences
.
setInt
(
'pre_userType'
,
userType
);
sharedPreferences
.
setString
(
'pre_stuId'
,
stuId
);
}
}
sharedPreferences
.
setString
(
'auth_sessionCode'
,
sessionCode
);
sharedPreferences
.
setString
(
'auth_userCode'
,
userCode
);
sharedPreferences
.
setString
(
'auth_classCode'
,
classCode
);
sharedPreferences
.
setInt
(
'auth_userType'
,
userType
);
sharedPreferences
.
setString
(
'auth_stuId'
,
stuId
);
sharedPreferences
.
setString
(
'auth_className'
,
className
);
sharedPreferences
.
setString
(
'auth_stuName'
,
stuName
);
sharedPreferences
.
setString
(
'auth_relation'
,
relation
);
debugPrint
(
'loginType:
${state.loginType}
appleUid:
${state.appleUserIdentifier}
'
);
// 针对 Apple 登录
if
(
state
.
loginType
==
2
&&
state
.
appleUserIdentifier
.
isNotEmpty
)
{
// appleUserIdentifier未绑定,则进行绑定
_userAuthRepository
.
newBinding
(
state
.
appleUserIdentifier
,
userCode
);
}
router
.
go
(
'/web'
,
extra:
{
'sessionCode'
:
sessionCode
,
'userCode'
:
userCode
,
'classCode'
:
classCode
,
'userType'
:
userType
,
'stuId'
:
stuId
,
},
);
}
@override
void
didChangeAppLifecycleState
(
AppLifecycleState
appState
)
{
// App 回到前台时,如果仍在等待微信授权回调,给真实回调一个短窗口;
...
...
lib/bloc/login_phone_cubit.dart
View file @
bd25dea
import
'dart:async'
;
import
'package:appframe/config/constant.dart'
;
import
'package:appframe/config/locator.dart'
;
import
'package:appframe/config/routes.dart'
;
import
'package:appframe/data/repositories/phone_auth_repository.dart'
;
import
'package:appframe/utils/login_util.dart'
;
import
'package:equatable/equatable.dart'
;
import
'package:flutter/material.dart'
;
import
'package:flutter_bloc/flutter_bloc.dart'
;
...
...
@@ -97,7 +97,7 @@ class LoginPhoneCubit extends Cubit<LoginPhoneState> {
}
// 发送验证码
var
result
=
await
_phoneAuthRepository
.
verifyCode
(
phone
,
0
);
var
result
=
await
_phoneAuthRepository
.
verifyCode
(
phone
,
1
);
if
(
result
[
'code'
]
!=
0
)
{
Fluttertoast
.
showToast
(
msg:
result
[
'error'
],
gravity:
ToastGravity
.
TOP
,
backgroundColor:
Colors
.
red
);
return
;
...
...
@@ -140,97 +140,15 @@ class LoginPhoneCubit extends Cubit<LoginPhoneState> {
}
var
data
=
resultData
[
'data'
]
as
Map
<
String
,
dynamic
>;
_handleLoginSuccess
(
data
);
}
void
_handleLoginSuccess
(
Map
<
String
,
dynamic
>
data
)
{
var
roles
=
data
[
'roles'
];
// 过滤出家长角色的数据
if
(
roles
?.
isNotEmpty
??
false
)
{
roles
.
removeWhere
((
element
)
=>
element
[
'userType'
]
!=
2
);
}
else
{
roles
=
[];
}
var
sessionCode
=
data
[
'sessionCode'
];
var
userCode
=
data
[
'userCode'
];
var
classCode
=
''
;
var
userType
=
0
;
var
stuId
=
''
;
var
className
=
''
;
var
stuName
=
''
;
var
relation
=
''
;
var
sharedPreferences
=
getIt
.
get
<
SharedPreferences
>();
if
(
roles
.
isNotEmpty
)
{
var
role
=
roles
[
0
];
classCode
=
role
[
'classCode'
];
userType
=
role
[
'userType'
];
stuId
=
role
[
'stuId'
];
className
=
role
[
'className'
];
stuName
=
role
[
'stuName'
];
relation
=
role
[
'relation'
]
??
''
;
// 将角色中的班级数据处理后,进行缓存
List
<
String
>
classIdList
=
[];
for
(
var
role
in
roles
)
{
classIdList
.
add
(
role
[
'classCode'
]
as
String
);
}
debugPrint
(
'classCodeIds:--------------
$classIdList
'
);
sharedPreferences
.
setStringList
(
Constant
.
classIdSetKey
,
classIdList
);
}
else
{
sharedPreferences
.
setStringList
(
Constant
.
classIdSetKey
,
[]);
int
binding
=
resultData
[
'binding'
];
// binding=1 代表已绑定,不是游客
var
visitor
=
binding
==
1
?
0
:
1
;
if
(
visitor
==
1
)
{
var
sharedPreferences
=
getIt
.
get
<
SharedPreferences
>();
sharedPreferences
.
setString
(
'auth_visitor_type'
,
'phone'
);
sharedPreferences
.
setString
(
'auth_visitor_id'
,
phone
);
}
var
preUserCode
=
sharedPreferences
.
getString
(
'pre_userCode'
)
??
''
;
if
(
userCode
!=
preUserCode
)
{
// 新用户登录
sharedPreferences
.
setString
(
'pre_userCode'
,
userCode
);
sharedPreferences
.
setString
(
'pre_classCode'
,
classCode
);
sharedPreferences
.
setInt
(
'pre_userType'
,
userType
);
sharedPreferences
.
setString
(
'pre_stuId'
,
stuId
);
}
else
{
// 前一个登录用户重新登录
var
preClassCode
=
sharedPreferences
.
getString
(
'pre_classCode'
)
??
''
;
var
preUserType
=
sharedPreferences
.
getInt
(
'pre_userType'
)
??
0
;
var
preStuId
=
sharedPreferences
.
getString
(
'pre_stuId'
)
??
''
;
if
(
preClassCode
!=
''
&&
roles
.
any
((
element
)
=>
element
[
'classCode'
]
==
preClassCode
&&
element
[
'userType'
]
==
preUserType
&&
element
[
'stuId'
]
==
preStuId
))
{
classCode
=
preClassCode
;
userType
=
preUserType
;
stuId
=
preStuId
;
}
else
{
sharedPreferences
.
setString
(
'pre_userCode'
,
userCode
);
sharedPreferences
.
setString
(
'pre_classCode'
,
classCode
);
sharedPreferences
.
setInt
(
'pre_userType'
,
userType
);
sharedPreferences
.
setString
(
'pre_stuId'
,
stuId
);
}
}
sharedPreferences
.
setString
(
'auth_sessionCode'
,
sessionCode
);
sharedPreferences
.
setString
(
'auth_userCode'
,
userCode
);
sharedPreferences
.
setString
(
'auth_classCode'
,
classCode
);
sharedPreferences
.
setInt
(
'auth_userType'
,
userType
);
sharedPreferences
.
setString
(
'auth_stuId'
,
stuId
);
sharedPreferences
.
setString
(
'auth_className'
,
className
);
sharedPreferences
.
setString
(
'auth_stuName'
,
stuName
);
sharedPreferences
.
setString
(
'auth_relation'
,
relation
);
router
.
go
(
'/web'
,
extra:
{
'sessionCode'
:
sessionCode
,
'userCode'
:
userCode
,
'classCode'
:
classCode
,
'userType'
:
userType
,
'stuId'
:
stuId
,
},
);
LoginUtil
.
handleLoginSuccess
(
data
,
visitor
,
'router'
);
}
void
toggleAgreed
(
bool
value
)
{
...
...
lib/bloc/login_qr_cubit.dart
View file @
bd25dea
...
...
@@ -4,6 +4,7 @@ import 'package:appframe/config/constant.dart';
import
'package:appframe/config/locator.dart'
;
import
'package:appframe/config/routes.dart'
;
import
'package:appframe/data/repositories/wechat_auth_repository.dart'
;
import
'package:appframe/utils/login_util.dart'
;
import
'package:crypto/crypto.dart'
;
import
'package:equatable/equatable.dart'
;
import
'package:flutter/foundation.dart'
;
...
...
@@ -11,7 +12,6 @@ import 'package:flutter/material.dart';
import
'package:flutter_bloc/flutter_bloc.dart'
;
import
'package:fluttertoast/fluttertoast.dart'
;
import
'package:fluwx/fluwx.dart'
;
import
'package:shared_preferences/shared_preferences.dart'
;
class
LoginQrState
extends
Equatable
{
final
int
status
;
...
...
@@ -157,97 +157,7 @@ class LoginQrCubit extends Cubit<LoginQrState> {
}
var
data
=
resultData
[
'data'
]
as
Map
<
String
,
dynamic
>;
_handleLoginSuccess
(
data
);
}
void
_handleLoginSuccess
(
Map
<
String
,
dynamic
>
data
)
{
var
roles
=
data
[
'roles'
];
// 过滤出家长角色的数据
if
(
roles
?.
isNotEmpty
??
false
)
{
roles
.
removeWhere
((
element
)
=>
element
[
'userType'
]
!=
2
);
}
else
{
roles
=
[];
}
var
sessionCode
=
data
[
'sessionCode'
];
var
userCode
=
data
[
'userCode'
];
var
classCode
=
''
;
var
userType
=
0
;
var
stuId
=
''
;
var
className
=
''
;
var
stuName
=
''
;
var
relation
=
''
;
var
sharedPreferences
=
getIt
.
get
<
SharedPreferences
>();
if
(
roles
.
isNotEmpty
)
{
var
role
=
roles
[
0
];
classCode
=
role
[
'classCode'
];
userType
=
role
[
'userType'
];
stuId
=
role
[
'stuId'
];
className
=
role
[
'className'
];
stuName
=
role
[
'stuName'
];
relation
=
role
[
'relation'
];
// 将角色中的班级数据处理后,进行缓存
List
<
String
>
classIdList
=
[];
for
(
var
role
in
roles
)
{
classIdList
.
add
(
role
[
'classCode'
]
as
String
);
}
debugPrint
(
'classCodeIds:--------------
$classIdList
'
);
sharedPreferences
.
setStringList
(
Constant
.
classIdSetKey
,
classIdList
);
}
else
{
sharedPreferences
.
setStringList
(
Constant
.
classIdSetKey
,
[]);
}
var
preUserCode
=
sharedPreferences
.
getString
(
'pre_userCode'
)
??
''
;
if
(
userCode
!=
preUserCode
)
{
// 新用户登录
sharedPreferences
.
setString
(
'pre_userCode'
,
userCode
);
sharedPreferences
.
setString
(
'pre_classCode'
,
classCode
);
sharedPreferences
.
setInt
(
'pre_userType'
,
userType
);
sharedPreferences
.
setString
(
'pre_stuId'
,
stuId
);
}
else
{
// 前一个登录用户重新登录
var
preClassCode
=
sharedPreferences
.
getString
(
'pre_classCode'
)
??
''
;
var
preUserType
=
sharedPreferences
.
getInt
(
'pre_userType'
)
??
0
;
var
preStuId
=
sharedPreferences
.
getString
(
'pre_stuId'
)
??
''
;
if
(
preClassCode
!=
''
&&
roles
.
any
((
element
)
=>
element
[
'classCode'
]
==
preClassCode
&&
element
[
'userType'
]
==
preUserType
&&
element
[
'stuId'
]
==
preStuId
))
{
classCode
=
preClassCode
;
userType
=
preUserType
;
stuId
=
preStuId
;
}
else
{
sharedPreferences
.
setString
(
'pre_userCode'
,
userCode
);
sharedPreferences
.
setString
(
'pre_classCode'
,
classCode
);
sharedPreferences
.
setInt
(
'pre_userType'
,
userType
);
sharedPreferences
.
setString
(
'pre_stuId'
,
stuId
);
}
}
sharedPreferences
.
setString
(
'auth_sessionCode'
,
sessionCode
);
sharedPreferences
.
setString
(
'auth_userCode'
,
userCode
);
sharedPreferences
.
setString
(
'auth_classCode'
,
classCode
);
sharedPreferences
.
setInt
(
'auth_userType'
,
userType
);
sharedPreferences
.
setString
(
'auth_stuId'
,
stuId
);
sharedPreferences
.
setString
(
'auth_className'
,
className
);
sharedPreferences
.
setString
(
'auth_stuName'
,
stuName
);
sharedPreferences
.
setString
(
'auth_relation'
,
relation
);
router
.
go
(
'/web'
,
extra:
{
'sessionCode'
:
sessionCode
,
'userCode'
:
userCode
,
'classCode'
:
classCode
,
'userType'
:
userType
,
'stuId'
:
stuId
,
},
);
LoginUtil
.
handleLoginSuccess
(
data
,
0
,
'router'
);
}
void
goLoginMain
()
{
...
...
lib/bloc/web_cubit.dart
View file @
bd25dea
...
...
@@ -6,6 +6,7 @@ import 'package:app_settings/app_settings.dart';
import
'package:appframe/config/constant.dart'
;
import
'package:appframe/config/locator.dart'
;
import
'package:appframe/config/routes.dart'
;
import
'package:appframe/ui/widgets/wechat_qr_bind_dialog.dart'
;
import
'package:appframe/data/models/message/h5_message.dart'
;
import
'package:appframe/services/dispatcher.dart'
;
import
'package:appframe/services/im_service.dart'
;
...
...
@@ -18,7 +19,6 @@ import 'package:equatable/equatable.dart';
import
'package:flutter/material.dart'
;
import
'package:flutter_bloc/flutter_bloc.dart'
;
import
'package:fluttertoast/fluttertoast.dart'
;
import
'package:fluwx/fluwx.dart'
;
import
'package:path_provider/path_provider.dart'
;
import
'package:permission_handler/permission_handler.dart'
;
import
'package:shared_preferences/shared_preferences.dart'
;
...
...
@@ -47,6 +47,8 @@ class WebState extends Equatable {
// 用来控制是否需要判断处理加群、退群等逻辑
final
bool
loginOpFlag
;
// 没有对应身份的游客
final
int
?
visitor
;
final
String
?
sessionCode
;
final
String
?
userCode
;
final
String
?
classCode
;
...
...
@@ -69,6 +71,9 @@ class WebState extends Equatable {
final
bool
chooseVideoCmdFlag
;
final
String
chooseVideoCmdMessage
;
/// wechatQrBindCmd
final
bool
wechatQrBindCmdFlag
;
/// 用于测试监测问题
final
String
testMsg
;
...
...
@@ -85,6 +90,7 @@ class WebState extends Equatable {
this
.
showAppBar
=
true
,
this
.
showBottomNavBar
=
false
,
this
.
loginOpFlag
=
false
,
this
.
visitor
,
this
.
sessionCode
,
this
.
userCode
,
this
.
classCode
,
...
...
@@ -98,6 +104,7 @@ class WebState extends Equatable {
this
.
chooseImageCmdMessage
=
''
,
this
.
chooseVideoCmdFlag
=
false
,
this
.
chooseVideoCmdMessage
=
''
,
this
.
wechatQrBindCmdFlag
=
false
,
this
.
testMsg
=
''
,
});
...
...
@@ -114,6 +121,7 @@ class WebState extends Equatable {
bool
?
showAppBar
,
bool
?
showBottomNavBar
,
bool
?
loginOpFlag
,
int
?
visitor
,
String
?
sessionCode
,
String
?
userCode
,
String
?
classCode
,
...
...
@@ -127,6 +135,7 @@ class WebState extends Equatable {
String
?
chooseImageCmdMessage
,
bool
?
chooseVideoCmdFlag
,
String
?
chooseVideoCmdMessage
,
bool
?
wechatQrBindCmdFlag
,
String
?
testMsg
,
})
{
return
WebState
(
...
...
@@ -142,6 +151,7 @@ class WebState extends Equatable {
showAppBar:
showAppBar
??
this
.
showAppBar
,
showBottomNavBar:
showBottomNavBar
??
this
.
showBottomNavBar
,
loginOpFlag:
loginOpFlag
??
this
.
loginOpFlag
,
visitor:
visitor
??
this
.
visitor
,
sessionCode:
sessionCode
??
this
.
sessionCode
,
userCode:
userCode
??
this
.
userCode
,
classCode:
classCode
??
this
.
classCode
,
...
...
@@ -155,6 +165,7 @@ class WebState extends Equatable {
chooseImageCmdMessage:
chooseImageCmdMessage
??
this
.
chooseImageCmdMessage
,
chooseVideoCmdFlag:
chooseVideoCmdFlag
??
this
.
chooseVideoCmdFlag
,
chooseVideoCmdMessage:
chooseVideoCmdMessage
??
this
.
chooseVideoCmdMessage
,
wechatQrBindCmdFlag:
wechatQrBindCmdFlag
??
this
.
wechatQrBindCmdFlag
,
testMsg:
testMsg
??
this
.
testMsg
,
);
}
...
...
@@ -172,6 +183,7 @@ class WebState extends Equatable {
setting
,
showAppBar
,
showBottomNavBar
,
visitor
,
sessionCode
,
userCode
,
classCode
,
...
...
@@ -185,6 +197,7 @@ class WebState extends Equatable {
chooseImageCmdMessage
,
chooseVideoCmdFlag
,
chooseVideoCmdMessage
,
wechatQrBindCmdFlag
,
testMsg
,
];
}
...
...
@@ -370,6 +383,7 @@ class WebCubit extends Cubit<WebState> with WidgetsBindingObserver {
// 构造函数中已拦截判断未登录的情况进行了处理,所以这里不再处理未登录的情况
final
String
serverUrl
=
'
${Constant.localServerUrl}
/index.html'
'#/h5/login/pages/applogin?'
'visitor=
${state.visitor}
&'
'sessionCode=
${state.sessionCode}
&'
'userCode=
${state.userCode}
&'
'classCode=
${state.classCode}
&'
...
...
@@ -381,7 +395,38 @@ class WebCubit extends Cubit<WebState> with WidgetsBindingObserver {
_controller
.
loadRequest
(
Uri
.
parse
(
serverUrl
));
}
/// 微信绑定成功后,更新身份信息并重新加载 H5 页面
/// 避免销毁重建整个 WebPage,保留本地服务器和 WebView 控制器
void
reloadWithIdentity
({
required
int
visitor
,
required
String
sessionCode
,
required
String
userCode
,
required
String
classCode
,
required
int
userType
,
required
String
stuId
,
})
{
emit
(
state
.
copyWith
(
loginOpFlag:
true
,
visitor:
visitor
,
sessionCode:
sessionCode
,
userCode:
userCode
,
classCode:
classCode
,
userType:
userType
,
stuId:
stuId
,
));
// 重新加载 H5 页面(_loadHtml 读取的是 state 中的值)
_loadHtml
();
// 用新身份重新登录 IM
_loginIM
();
}
Future
<
void
>
_loginIM
()
async
{
// 游客身份不处理IM登录
if
(
state
.
visitor
==
1
)
{
return
;
}
if
(
Constant
.
needIM
)
{
var
imService
=
getIt
.
get
<
ImService
>();
var
loginResult
=
await
imService
.
login
(
state
.
userCode
!);
...
...
@@ -1070,6 +1115,21 @@ class WebCubit extends Cubit<WebState> with WidgetsBindingObserver {
_sendResponse
(
resp
);
}
void
setWechatQrBindCmdFlag
(
bool
wechatQrBindCmdFlag
)
{
emit
(
state
.
copyWith
(
wechatQrBindCmdFlag:
wechatQrBindCmdFlag
));
}
void
showWechatQrBindDialog
(
BuildContext
context
)
{
setWechatQrBindCmdFlag
(
false
);
showDialog
(
context:
context
,
barrierDismissible:
false
,
builder:
(
BuildContext
dialogContext
)
{
return
WechatQrBindDialog
();
},
);
}
void
setWindowInfoCmdFlag
(
bool
windowInfoCmdFlag
,
String
windowInfoCmdMessage
)
{
emit
(
state
.
copyWith
(
windowInfoCmdFlag:
windowInfoCmdFlag
,
windowInfoCmdMessage:
windowInfoCmdMessage
));
}
...
...
lib/config/constant.dart
View file @
bd25dea
...
...
@@ -116,6 +116,6 @@ class Constant {
static
const
String
hasShownPrivacyFirstTimeKey
=
'has_shown_privacy_first_time'
;
/// 测试阶段使用
static
const
bool
needIM
=
tru
e
;
static
const
bool
needIM
=
fals
e
;
static
const
bool
needUpgrade
=
true
;
}
lib/config/locator.dart
View file @
bd25dea
...
...
@@ -36,6 +36,8 @@ import 'package:appframe/data/repositories/message/upload_cancel_handler.dart';
import
'package:appframe/data/repositories/message/upload_file.dart'
;
import
'package:appframe/data/repositories/message/upload_start_handler.dart'
;
import
'package:appframe/data/repositories/message/vibrate_short_handler.dart'
;
import
'package:appframe/data/repositories/message/wechat_bind_handler.dart'
;
import
'package:appframe/data/repositories/message/wechat_qr_bind_handler.dart'
;
import
'package:appframe/data/repositories/message/video_info_handler.dart'
;
import
'package:appframe/data/repositories/message/wifi_info_handler.dart'
;
import
'package:appframe/data/repositories/message/window_info_handler.dart'
;
...
...
@@ -211,6 +213,12 @@ Future<void> setupLocator() async {
/// 设置用户角色信息
getIt
.
registerLazySingleton
<
MessageHandler
>(()
=>
RoleInfoHandler
(),
instanceName:
'setRoleInfo'
);
/// 微信绑定
getIt
.
registerLazySingleton
<
MessageHandler
>(()
=>
WechatBindHandler
(),
instanceName:
'wechatBind'
);
/// 微信二维码绑定
getIt
.
registerLazySingleton
<
MessageHandler
>(()
=>
WechatQrBindHandler
(),
instanceName:
'wechatQrBind'
);
/// 设置屏幕模式
getIt
.
registerLazySingleton
<
MessageHandler
>(()
=>
ScreenHandler
(),
instanceName:
'setScreen'
);
...
...
lib/data/repositories/message/wechat_bind_handler.dart
0 → 100644
View file @
bd25dea
import
'dart:async'
;
import
'package:appframe/config/locator.dart'
;
import
'package:appframe/data/repositories/user_auth_repository.dart'
;
import
'package:appframe/data/repositories/wechat_auth_repository.dart'
;
import
'package:appframe/services/dispatcher.dart'
;
import
'package:appframe/utils/login_util.dart'
;
import
'package:flutter/foundation.dart'
;
import
'package:flutter/material.dart'
;
import
'package:fluttertoast/fluttertoast.dart'
;
import
'package:fluwx/fluwx.dart'
;
import
'package:shared_preferences/shared_preferences.dart'
;
class
WechatBindHandler
extends
MessageHandler
{
late
final
Fluwx
_fluwx
;
late
final
FluwxCancelable
_fluwxCancelable
;
late
final
WechatAuthRepository
_wechatAuthRepository
;
late
final
UserAuthRepository
_userAuthRepository
;
// 是否正在等待微信授权回调
bool
_waitingWechatAuth
=
false
;
// 总超时定时器
Timer
?
_wechatAuthTimeoutTimer
;
// App 回到前台后的短延时定时器
Timer
?
_wechatAuthResumeTimer
;
WechatBindHandler
()
{
_fluwx
=
getIt
.
get
<
Fluwx
>();
_wechatAuthRepository
=
getIt
.
get
<
WechatAuthRepository
>();
_userAuthRepository
=
getIt
.
get
<
UserAuthRepository
>();
}
@override
Future
<
dynamic
>
handleMessage
(
params
)
async
{
if
(!
await
_fluwx
.
isWeChatInstalled
)
{
// throw Exception('设备上未安装微信App,不支持微信绑定');
Fluttertoast
.
showToast
(
msg:
'设备上未安装微信App,不支持微信绑定'
,
gravity:
ToastGravity
.
TOP
,
backgroundColor:
Colors
.
red
);
return
null
;
}
_fluwxCancelable
=
_fluwx
.
addSubscriber
(
_responseListener
);
var
authResult
=
await
_fluwx
.
authBy
(
which:
NormalAuth
(
scope:
'snsapi_userinfo'
,
state:
'wechat_bind'
),
);
if
(!
authResult
)
{
_fluwxCancelable
.
cancel
();
// throw Exception('微信授权处理失败');
Fluttertoast
.
showToast
(
msg:
'微信授权处理失败'
,
gravity:
ToastGravity
.
TOP
,
backgroundColor:
Colors
.
red
);
return
null
;
}
// 启动等待微信授权回调的兜底机制
_startWechatAuthWaiting
();
// 微信授权为异步回调,不返回结果给H5
return
null
;
}
void
_responseListener
(
WeChatResponse
response
)
async
{
if
(
response
is
WeChatAuthResponse
)
{
// 收到正式回调,结束等待状态
_finishWechatAuthWaiting
();
if
(
response
.
code
==
null
||
response
.
code
==
''
)
{
Fluttertoast
.
showToast
(
msg:
'微信授权取消'
,
gravity:
ToastGravity
.
TOP
);
_cleanup
();
return
;
}
try
{
// 1. 用code换取会话信息
var
resultData
=
await
_wechatAuthRepository
.
codeToSk
(
response
.
code
!)
as
Map
<
String
,
dynamic
>?;
// 请求接口异常
if
(
resultData
==
null
)
{
Fluttertoast
.
showToast
(
msg:
'绑定请求处理失败'
,
gravity:
ToastGravity
.
TOP
);
_cleanup
();
return
;
}
// 状态码错误
if
(
resultData
[
'resultCode'
]
!=
'001'
)
{
Fluttertoast
.
showToast
(
msg:
'绑定请求状态失败'
,
gravity:
ToastGravity
.
TOP
);
_cleanup
();
return
;
}
var
data
=
resultData
[
'data'
]
as
Map
<
String
,
dynamic
>;
var
wechatUserCode
=
data
[
'userCode'
]
as
String
;
// 2. 从SharedPreferences获取当前用户信息
var
sharedPreferences
=
getIt
.
get
<
SharedPreferences
>();
var
visitorId
=
sharedPreferences
.
getString
(
'auth_visitor_id'
)
??
''
;
var
visitorType
=
sharedPreferences
.
getString
(
'auth_visitor_type'
)
??
''
;
if
(
visitorId
.
isEmpty
||
visitorType
.
isEmpty
)
{
Fluttertoast
.
showToast
(
msg:
'用户信息获取失败,请重新登录'
,
gravity:
ToastGravity
.
TOP
);
_cleanup
();
return
;
}
if
(
visitorType
!=
'apple'
&&
visitorType
!=
'phone'
)
{
Fluttertoast
.
showToast
(
msg:
'用户信息错误,请重新登录'
,
gravity:
ToastGravity
.
TOP
);
_cleanup
();
return
;
}
// 3. 调用绑定接口
var
bindResult
=
await
_userAuthRepository
.
newBinding
(
visitorId
,
wechatUserCode
,
visitorType
)
as
Map
<
String
,
dynamic
>?;
if
(
bindResult
!=
null
&&
bindResult
[
'code'
]
==
0
)
{
// Fluttertoast.showToast(msg: '微信绑定成功', gravity: ToastGravity.TOP);
LoginUtil
.
handleLoginSuccess
(
data
,
0
,
'reload'
);
}
else
{
var
errorMsg
=
bindResult
?[
'error'
]
??
'微信绑定失败'
;
Fluttertoast
.
showToast
(
msg:
errorMsg
,
gravity:
ToastGravity
.
TOP
);
}
}
catch
(
e
)
{
debugPrint
(
'wechatBind error:
$e
'
);
Fluttertoast
.
showToast
(
msg:
'微信绑定异常'
,
gravity:
ToastGravity
.
TOP
);
}
finally
{
_cleanup
();
}
}
}
// 标记进入"等待微信授权回调"状态,并启动总超时
void
_startWechatAuthWaiting
()
{
_waitingWechatAuth
=
true
;
_wechatAuthTimeoutTimer
?.
cancel
();
_wechatAuthTimeoutTimer
=
Timer
(
const
Duration
(
seconds:
30
),
()
{
if
(
_waitingWechatAuth
)
{
_finishWechatAuthWaiting
(
reason:
_WechatBindFinishReason
.
timeout
);
}
});
}
// 结束等待,按场景给出提示
void
_finishWechatAuthWaiting
({
_WechatBindFinishReason
reason
=
_WechatBindFinishReason
.
response
,
})
{
if
(!
_waitingWechatAuth
)
return
;
_waitingWechatAuth
=
false
;
_wechatAuthTimeoutTimer
?.
cancel
();
_wechatAuthResumeTimer
?.
cancel
();
_wechatAuthTimeoutTimer
=
null
;
_wechatAuthResumeTimer
=
null
;
switch
(
reason
)
{
case
_WechatBindFinishReason
.
cancel
:
Fluttertoast
.
showToast
(
msg:
'已取消微信授权'
,
gravity:
ToastGravity
.
TOP
);
_cleanup
();
break
;
case
_WechatBindFinishReason
.
timeout
:
Fluttertoast
.
showToast
(
msg:
'微信授权超时,请重试'
,
gravity:
ToastGravity
.
TOP
);
_cleanup
();
break
;
case
_WechatBindFinishReason
.
response
:
// 正常收到回调时不弹提示
break
;
}
}
// 清理资源
void
_cleanup
()
{
_fluwxCancelable
.
cancel
();
_wechatAuthTimeoutTimer
?.
cancel
();
_wechatAuthResumeTimer
?.
cancel
();
}
}
enum
_WechatBindFinishReason
{
// 收到了正式的 WeChatAuthResponse
response
,
// App 已回到前台但仍无回调,判定为取消
cancel
,
// 总超时
timeout
,
}
lib/data/repositories/message/wechat_qr_bind_handler.dart
0 → 100644
View file @
bd25dea
import
'package:appframe/bloc/web_cubit.dart'
;
import
'package:appframe/services/dispatcher.dart'
;
class
WechatQrBindHandler
extends
MessageHandler
{
late
WebCubit
?
_webCubit
;
@override
void
setCubit
(
WebCubit
cubit
)
{
_webCubit
=
cubit
;
}
void
_unfollowCubit
()
{
_webCubit
=
null
;
}
@override
Future
<
dynamic
>
handleMessage
(
params
)
async
{
try
{
_webCubit
!.
setWechatQrBindCmdFlag
(
true
);
}
finally
{
_unfollowCubit
();
}
// 二维码绑定流程为异步,不返回结果给H5
return
null
;
}
}
lib/data/repositories/phone_auth_repository.dart
View file @
bd25dea
...
...
@@ -78,7 +78,7 @@ class PhoneAuthRepository {
///
Future
<
dynamic
>
login
(
String
phone
,
String
verifyCode
)
async
{
Response
resp
=
await
_appService
.
post
(
'/api/v1/comm/phone/login'
,
'/api/v1/comm/phone/login
v2
'
,
{
"phone"
:
phone
,
"verifyCode"
:
verifyCode
,
...
...
lib/data/repositories/user_auth_repository.dart
View file @
bd25dea
...
...
@@ -64,13 +64,13 @@ class UserAuthRepository {
/// "error": "",
/// "code": 0,
/// }
Future
<
dynamic
>
newBinding
(
String
userid
,
String
bxeUserId
)
async
{
Future
<
dynamic
>
newBinding
(
String
userid
,
String
bxeUserId
,
String
type
)
async
{
Response
resp
=
await
_appService
.
post
(
'/api/v1/comm/user/newbinding'
,
{
"userId"
:
userid
,
"bxeUserId"
:
bxeUserId
,
"type"
:
"apple"
,
"type"
:
type
,
},
);
return
resp
.
statusCode
==
HttpStatus
.
ok
?
resp
.
data
:
null
;
...
...
lib/services/dispatcher.dart
View file @
bd25dea
...
...
@@ -64,7 +64,8 @@ class MessageDispatcher {
h5Message
.
cmd
.
startsWith
(
"setTitlebar"
)
||
h5Message
.
cmd
==
"audioPlay"
||
h5Message
.
cmd
==
"openLink"
||
h5Message
.
cmd
==
"uploadStart"
)
{
h5Message
.
cmd
==
"uploadStart"
||
h5Message
.
cmd
==
"wechatQrBind"
)
{
handler
.
setCubit
(
webCubit
!);
handler
.
setMessage
(
message
);
}
...
...
lib/ui/pages/login_main_page.dart
View file @
bd25dea
...
...
@@ -161,7 +161,7 @@ class LoginMainPage extends StatelessWidget {
if
(
state
.
showAgreed
)
{
_showAgreementDialog
(
context
,
context
.
read
<
LoginMainCubit
>());
}
else
if
(
state
.
showNeedWechatForApple
)
{
_showNeedWechatDialogForApple
(
context
,
context
.
read
<
LoginMainCubit
>());
//
_showNeedWechatDialogForApple(context, context.read<LoginMainCubit>());
}
else
if
(
state
.
showPrivacyFirstTime
)
{
_showPrivacyFirstTimeDialog
(
context
,
context
.
read
<
LoginMainCubit
>());
}
...
...
@@ -381,59 +381,59 @@ class LoginMainPage extends StatelessWidget {
);
}
void
_showNeedWechatDialogForApple
(
BuildContext
context
,
LoginMainCubit
loginMainCubit
)
{
showDialog
(
context:
context
,
barrierDismissible:
false
,
builder:
(
BuildContext
ctx
)
{
return
PopScope
(
canPop:
false
,
child:
AlertDialog
(
shape:
RoundedRectangleBorder
(
borderRadius:
BorderRadius
.
all
(
Radius
.
circular
(
5
),
),
),
title:
Text
(
'温馨提示'
,
style:
TextStyle
(
fontSize:
17
,
color:
Color
(
0xFF000000
),
// fontWeight: FontWeight.bold,
),
textAlign:
TextAlign
.
center
,
),
content:
Text
.
rich
(
TextSpan
(
text:
'为了避免您之前在微信小程序的使用数据不丢失,必须绑定微信才可以继续!'
,
style:
TextStyle
(
color:
Color
(
0xFF666666
),
fontSize:
14
),
),
),
actions:
[
Center
(
child:
TextButton
(
onPressed:
()
{
Navigator
.
of
(
ctx
).
pop
(
'OK'
);
loginMainCubit
.
wechatAuthForApple
();
},
style:
TextButton
.
styleFrom
(
foregroundColor:
Color
(
0xFF7691FA
),
textStyle:
TextStyle
(
fontSize:
17
),
minimumSize:
Size
.
fromHeight
(
40
),
padding:
EdgeInsets
.
zero
,
shape:
RoundedRectangleBorder
(
borderRadius:
BorderRadius
.
zero
,
),
),
child:
Text
(
'绑定微信'
),
),
),
],
),
);
},
);
}
//
void _showNeedWechatDialogForApple(BuildContext context, LoginMainCubit loginMainCubit) {
//
showDialog(
//
context: context,
//
barrierDismissible: false,
//
builder: (BuildContext ctx) {
//
return PopScope(
//
canPop: false,
//
child: AlertDialog(
//
shape: RoundedRectangleBorder(
//
borderRadius: BorderRadius.all(
//
Radius.circular(5),
//
),
//
),
//
title: Text(
//
'温馨提示',
//
style: TextStyle(
//
fontSize: 17,
//
color: Color(0xFF000000),
//
// fontWeight: FontWeight.bold,
//
),
//
textAlign: TextAlign.center,
//
),
//
content: Text.rich(
//
TextSpan(
//
text: '为了避免您之前在微信小程序的使用数据不丢失,必须绑定微信才可以继续!',
//
style: TextStyle(color: Color(0xFF666666), fontSize: 14),
//
),
//
),
//
actions: [
//
Center(
//
child: TextButton(
//
onPressed: () {
//
Navigator.of(ctx).pop('OK');
//
loginMainCubit.wechatAuthForApple();
//
},
//
style: TextButton.styleFrom(
//
foregroundColor: Color(0xFF7691FA),
//
textStyle: TextStyle(fontSize: 17),
//
minimumSize: Size.fromHeight(40),
//
padding: EdgeInsets.zero,
//
shape: RoundedRectangleBorder(
//
borderRadius: BorderRadius.zero,
//
),
//
),
//
child: Text('绑定微信'),
//
),
//
),
//
],
//
),
//
);
//
},
//
);
//
}
// 首次打开显示个人信息收集提示弹窗
Future
<
void
>
_showPrivacyFirstTimeDialog
(
BuildContext
context
,
LoginMainCubit
loginMainCubit
)
async
{
...
...
lib/ui/pages/login_v3/login_main_page_v3.dart
View file @
bd25dea
...
...
@@ -231,7 +231,7 @@ class LoginMainPageV3 extends StatelessWidget {
if
(
state
.
showAgreed
)
{
_showAgreementDialog
(
context
,
context
.
read
<
LoginMainCubit
>());
}
else
if
(
state
.
showNeedWechatForApple
)
{
_showNeedWechatDialogForApple
(
context
,
context
.
read
<
LoginMainCubit
>());
//
_showNeedWechatDialogForApple(context, context.read<LoginMainCubit>());
}
else
if
(
state
.
showPrivacyFirstTime
)
{
_showPrivacyFirstTimeDialog
(
context
,
context
.
read
<
LoginMainCubit
>());
}
...
...
@@ -450,59 +450,59 @@ class LoginMainPageV3 extends StatelessWidget {
);
}
void
_showNeedWechatDialogForApple
(
BuildContext
context
,
LoginMainCubit
loginMainCubit
)
{
showDialog
(
context:
context
,
barrierDismissible:
false
,
builder:
(
BuildContext
ctx
)
{
return
PopScope
(
canPop:
false
,
child:
AlertDialog
(
shape:
RoundedRectangleBorder
(
borderRadius:
BorderRadius
.
all
(
Radius
.
circular
(
5
),
),
),
title:
Text
(
'温馨提示'
,
style:
TextStyle
(
fontSize:
17
,
color:
Color
(
0xFF000000
),
// fontWeight: FontWeight.bold,
),
textAlign:
TextAlign
.
center
,
),
content:
Text
.
rich
(
TextSpan
(
text:
'为了避免您之前在微信小程序的使用数据不丢失,必须绑定微信才可以继续!'
,
style:
TextStyle
(
color:
Color
(
0xFF666666
),
fontSize:
14
),
),
),
actions:
[
Center
(
child:
TextButton
(
onPressed:
()
{
Navigator
.
of
(
ctx
).
pop
(
'OK'
);
loginMainCubit
.
wechatAuthForApple
();
},
style:
TextButton
.
styleFrom
(
foregroundColor:
Color
(
0xFF7691FA
),
textStyle:
TextStyle
(
fontSize:
17
),
minimumSize:
Size
.
fromHeight
(
40
),
padding:
EdgeInsets
.
zero
,
shape:
RoundedRectangleBorder
(
borderRadius:
BorderRadius
.
zero
,
),
),
child:
Text
(
'绑定微信'
),
),
),
],
),
);
},
);
}
//
void _showNeedWechatDialogForApple(BuildContext context, LoginMainCubit loginMainCubit) {
//
showDialog(
//
context: context,
//
barrierDismissible: false,
//
builder: (BuildContext ctx) {
//
return PopScope(
//
canPop: false,
//
child: AlertDialog(
//
shape: RoundedRectangleBorder(
//
borderRadius: BorderRadius.all(
//
Radius.circular(5),
//
),
//
),
//
title: Text(
//
'温馨提示',
//
style: TextStyle(
//
fontSize: 17,
//
color: Color(0xFF000000),
//
// fontWeight: FontWeight.bold,
//
),
//
textAlign: TextAlign.center,
//
),
//
content: Text.rich(
//
TextSpan(
//
text: '为了避免您之前在微信小程序的使用数据不丢失,必须绑定微信才可以继续!',
//
style: TextStyle(color: Color(0xFF666666), fontSize: 14),
//
),
//
),
//
actions: [
//
Center(
//
child: TextButton(
//
onPressed: () {
//
Navigator.of(ctx).pop('OK');
//
loginMainCubit.wechatAuthForApple();
//
},
//
style: TextButton.styleFrom(
//
foregroundColor: Color(0xFF7691FA),
//
textStyle: TextStyle(fontSize: 17),
//
minimumSize: Size.fromHeight(40),
//
padding: EdgeInsets.zero,
//
shape: RoundedRectangleBorder(
//
borderRadius: BorderRadius.zero,
//
),
//
),
//
child: Text('绑定微信'),
//
),
//
),
//
],
//
),
//
);
//
},
//
);
//
}
/// 首次打开显示个人信息收集提示弹窗
/// 值针对iOS,Android用原生界面显示
...
...
lib/ui/pages/web_page.dart
View file @
bd25dea
...
...
@@ -19,6 +19,7 @@ class WebPage extends StatelessWidget {
var
loginOpFlag
=
true
;
var
visitor
=
extraData
?[
'visitor'
];
var
sessionCode
=
extraData
?[
'sessionCode'
];
var
userCode
=
extraData
?[
'userCode'
];
var
classCode
=
extraData
?[
'classCode'
];
...
...
@@ -29,6 +30,7 @@ class WebPage extends StatelessWidget {
loginOpFlag
=
false
;
var
sharedPreferences
=
getIt
.
get
<
SharedPreferences
>();
visitor
=
sharedPreferences
.
getInt
(
'auth_visitor'
);
sessionCode
=
sharedPreferences
.
getString
(
'auth_sessionCode'
);
userCode
=
sharedPreferences
.
getString
(
'auth_userCode'
);
classCode
=
sharedPreferences
.
getString
(
'auth_classCode'
);
...
...
@@ -41,6 +43,7 @@ class WebPage extends StatelessWidget {
final
webCubit
=
WebCubit
(
WebState
(
loginOpFlag:
loginOpFlag
,
visitor:
visitor
,
sessionCode:
sessionCode
,
userCode:
userCode
,
classCode:
classCode
,
...
...
@@ -171,6 +174,8 @@ class WebPage extends StatelessWidget {
context
.
read
<
WebCubit
>().
chooseImage
(
context
);
}
else
if
(
state
.
chooseVideoCmdFlag
)
{
context
.
read
<
WebCubit
>().
chooseVideo
(
context
);
}
else
if
(
state
.
wechatQrBindCmdFlag
)
{
context
.
read
<
WebCubit
>().
showWechatQrBindDialog
(
context
);
}
},
),
...
...
lib/ui/widgets/wechat_qr_bind_dialog.dart
0 → 100644
View file @
bd25dea
import
'dart:convert'
;
import
'dart:typed_data'
;
import
'package:appframe/config/constant.dart'
;
import
'package:appframe/config/locator.dart'
;
import
'package:appframe/data/repositories/user_auth_repository.dart'
;
import
'package:appframe/data/repositories/wechat_auth_repository.dart'
;
import
'package:appframe/utils/login_util.dart'
;
import
'package:crypto/crypto.dart'
;
import
'package:flutter/material.dart'
;
import
'package:fluwx/fluwx.dart'
;
import
'package:shared_preferences/shared_preferences.dart'
;
///
/// 微信二维码绑定弹窗
/// 内部自管理二维码生成、扫码回调、绑定请求等完整流程
///
class
WechatQrBindDialog
extends
StatefulWidget
{
@override
State
<
WechatQrBindDialog
>
createState
()
=>
_WechatQrBindDialogState
();
}
class
_WechatQrBindDialogState
extends
State
<
WechatQrBindDialog
>
{
// 0: 加载中 1: 显示二维码 2: 已扫码待确认 3: 用户取消 4: 过期/错误 5: 绑定成功
int
_status
=
0
;
String
_tip
=
'正在生成二维码...'
;
Uint8List
?
_qrImage
;
late
final
Fluwx
_fluwx
;
late
final
FluwxCancelable
_fluwxCancelable
;
late
final
WechatAuthRepository
_wechatAuthRepository
;
late
final
UserAuthRepository
_userAuthRepository
;
@override
void
initState
()
{
super
.
initState
();
_fluwx
=
getIt
.
get
<
Fluwx
>();
_wechatAuthRepository
=
getIt
.
get
<
WechatAuthRepository
>();
_userAuthRepository
=
getIt
.
get
<
UserAuthRepository
>();
_fluwxCancelable
=
_fluwx
.
addSubscriber
(
_responseListener
);
_initQrCode
();
}
Future
<
void
>
_initQrCode
()
async
{
try
{
var
resultData
=
await
_wechatAuthRepository
.
getTicket
()
as
Map
<
String
,
dynamic
>?;
if
(
resultData
==
null
)
{
_setError
(
'生成二维码失败'
);
return
;
}
if
(
resultData
[
'resultCode'
]
!=
'001'
)
{
_setError
(
'生成二维码状态错误'
);
return
;
}
var
sdkTicket
=
resultData
[
'data'
];
var
timestamp
=
DateTime
.
now
().
millisecondsSinceEpoch
;
var
signature
=
_sig
(
Constant
.
wxAppId
,
'
$timestamp
'
,
sdkTicket
,
'
$timestamp
'
);
var
authResult
=
await
_fluwx
.
authBy
(
which:
QRCode
(
appId:
Constant
.
wxAppId
,
scope:
'snsapi_userinfo'
,
nonceStr:
'
$timestamp
'
,
timestamp:
'
$timestamp
'
,
signature:
signature
,
),
);
if
(!
authResult
)
{
_setError
(
'请求微信失败'
);
}
}
catch
(
e
)
{
debugPrint
(
'wechatQrBind initQrCode error:
$e
'
);
_setError
(
'生成二维码异常'
);
}
}
void
_responseListener
(
WeChatResponse
response
)
async
{
if
(
response
is
WeChatAuthGotQRCodeResponse
)
{
int
?
errCode
=
response
.
errCode
;
if
(
errCode
!=
null
&&
errCode
==
0
)
{
if
(!
mounted
)
return
;
setState
(()
{
_status
=
1
;
_qrImage
=
response
.
qrCode
;
_tip
=
'打开微信,扫描二维码绑定'
;
});
}
else
{
_setError
(
'错误
$errCode
'
);
}
}
else
if
(
response
is
WeChatQRCodeScannedResponse
)
{
int
?
errCode
=
response
.
errCode
;
if
(
errCode
!=
null
&&
errCode
==
0
)
{
if
(!
mounted
)
return
;
setState
(()
{
_status
=
2
;
_tip
=
'在微信中轻触允许即可绑定'
;
});
}
else
{
_setError
(
'错误
$errCode
'
);
}
}
else
if
(
response
is
WeChatAuthByQRCodeFinishedResponse
)
{
int
?
errCode
=
response
.
errCode
;
if
(
errCode
!=
null
&&
errCode
==
0
)
{
await
_doBind
(
response
.
authCode
!);
}
else
if
(
errCode
==
-
4
)
{
if
(!
mounted
)
return
;
setState
(()
{
_status
=
3
;
_tip
=
'你已取消此次绑定'
;
});
}
else
{
if
(!
mounted
)
return
;
setState
(()
{
_status
=
4
;
_tip
=
'请刷新二维码,重新绑定'
;
});
}
}
}
Future
<
void
>
_doBind
(
String
code
)
async
{
try
{
var
resultData
=
await
_wechatAuthRepository
.
codeToSk
(
code
)
as
Map
<
String
,
dynamic
>?;
if
(
resultData
==
null
)
{
_setError
(
'绑定请求处理失败'
);
return
;
}
if
(
resultData
[
'resultCode'
]
!=
'001'
)
{
_setError
(
'绑定请求状态失败'
);
return
;
}
var
data
=
resultData
[
'data'
]
as
Map
<
String
,
dynamic
>;
var
wechatUserCode
=
data
[
'userCode'
]
as
String
;
var
sharedPreferences
=
getIt
.
get
<
SharedPreferences
>();
var
visitorId
=
sharedPreferences
.
getString
(
'auth_visitor_id'
)
??
''
;
var
visitorType
=
sharedPreferences
.
getString
(
'auth_visitor_type'
)
??
''
;
if
(
visitorId
.
isEmpty
||
visitorType
.
isEmpty
)
{
_setError
(
'用户信息获取失败,请重新登录'
);
return
;
}
if
(
visitorType
!=
'apple'
&&
visitorType
!=
'phone'
)
{
_setError
(
'用户信息错误,请重新登录'
);
return
;
}
var
bindResult
=
await
_userAuthRepository
.
newBinding
(
visitorId
,
wechatUserCode
,
visitorType
,
)
as
Map
<
String
,
dynamic
>?;
if
(!
mounted
)
return
;
if
(
bindResult
!=
null
&&
bindResult
[
'code'
]
==
0
)
{
setState
(()
{
_status
=
5
;
_tip
=
'微信绑定成功'
;
});
// 延迟关闭弹窗
Future
.
delayed
(
const
Duration
(
milliseconds:
1200
),
()
{
if
(
mounted
)
Navigator
.
of
(
context
).
pop
();
});
LoginUtil
.
handleLoginSuccess
(
data
,
0
,
'reload'
);
}
else
{
var
errorMsg
=
bindResult
?[
'error'
]
??
'微信绑定失败'
;
_setError
(
errorMsg
);
}
}
catch
(
e
)
{
debugPrint
(
'wechatQrBind doBind error:
$e
'
);
_setError
(
'微信绑定异常'
);
}
}
void
_setError
(
String
tip
)
{
if
(!
mounted
)
return
;
setState
(()
{
_status
=
4
;
_tip
=
tip
;
});
}
Future
<
void
>
_refresh
()
async
{
setState
(()
{
_status
=
0
;
_tip
=
'正在重新生成二维码...'
;
_qrImage
=
null
;
});
try
{
_fluwx
.
stopAuthByQRCode
();
}
catch
(
e
)
{
debugPrint
(
'stopAuthByQRCode error:
$e
'
);
}
await
_initQrCode
();
}
String
_sig
(
String
appId
,
String
nonceStr
,
String
sdkTicket
,
String
timestamp
)
{
String
str
=
'appid=
$appId
&noncestr=
$nonceStr
&sdk_ticket=
$sdkTicket
×tamp=
$timestamp
'
;
return
sha1
.
convert
(
utf8
.
encode
(
str
)).
toString
();
}
@override
void
dispose
()
{
_fluwxCancelable
.
cancel
();
try
{
_fluwx
.
stopAuthByQRCode
();
}
catch
(
e
)
{
debugPrint
(
'stopAuthByQRCode error:
$e
'
);
}
super
.
dispose
();
}
@override
Widget
build
(
BuildContext
context
)
{
return
Dialog
(
shape:
RoundedRectangleBorder
(
borderRadius:
BorderRadius
.
circular
(
16
),
),
child:
Container
(
width:
300
,
padding:
const
EdgeInsets
.
symmetric
(
horizontal:
24
,
vertical:
28
),
child:
Column
(
mainAxisSize:
MainAxisSize
.
min
,
children:
[
Text
(
'微信扫码绑定'
,
style:
TextStyle
(
fontSize:
17
,
fontWeight:
FontWeight
.
bold
,
color:
Color
(
0xFF333333
),
),
),
const
SizedBox
(
height:
20
),
SizedBox
(
height:
230
,
child:
_buildContent
(),
),
const
SizedBox
(
height:
16
),
// 底部按钮区域
_buildBottomActions
(),
],
),
),
);
}
Widget
_buildContent
()
{
if
(
_status
==
0
)
{
// 加载中
return
Column
(
mainAxisAlignment:
MainAxisAlignment
.
center
,
children:
[
const
SizedBox
(
width:
48
,
height:
48
,
child:
CircularProgressIndicator
(
color:
Color
(
0xFF7691FA
),
strokeWidth:
3
,
),
),
const
SizedBox
(
height:
16
),
Text
(
_tip
,
style:
TextStyle
(
fontSize:
14
,
color:
Color
(
0xFF666666
)),
textAlign:
TextAlign
.
center
,
),
],
);
}
else
if
(
_status
==
1
)
{
// 显示二维码
return
Column
(
children:
[
Container
(
width:
190
,
height:
190
,
decoration:
BoxDecoration
(
border:
Border
.
all
(
color:
Color
(
0x29265ECF
),
width:
0.5
),
borderRadius:
BorderRadius
.
circular
(
12
),
),
child:
ClipRRect
(
borderRadius:
BorderRadius
.
circular
(
12
),
child:
Image
.
memory
(
_qrImage
!,
width:
190
,
height:
190
),
),
),
const
SizedBox
(
height:
8
),
Text
(
_tip
,
style:
TextStyle
(
fontSize:
14
,
color:
Color
(
0xFF333333
)),
textAlign:
TextAlign
.
center
,
),
],
);
}
else
if
(
_status
==
2
)
{
// 已扫码,等待确认
return
Column
(
mainAxisAlignment:
MainAxisAlignment
.
center
,
children:
[
Icon
(
Icons
.
check_circle
,
color:
Color
(
0xFF07C160
),
size:
64
),
const
SizedBox
(
height:
12
),
Text
(
'已扫码'
,
style:
TextStyle
(
fontSize:
16
,
fontWeight:
FontWeight
.
bold
,
color:
Color
(
0xFF333333
),
),
),
const
SizedBox
(
height:
8
),
Text
(
_tip
,
style:
TextStyle
(
fontSize:
14
,
color:
Color
(
0xFF666666
)),
textAlign:
TextAlign
.
center
,
),
],
);
}
else
if
(
_status
==
3
)
{
// 用户取消
return
Column
(
mainAxisAlignment:
MainAxisAlignment
.
center
,
children:
[
Icon
(
Icons
.
cancel
,
color:
Color
(
0xFFE64340
),
size:
64
),
const
SizedBox
(
height:
12
),
Text
(
'已取消绑定'
,
style:
TextStyle
(
fontSize:
16
,
fontWeight:
FontWeight
.
bold
,
color:
Color
(
0xFF333333
),
),
),
const
SizedBox
(
height:
8
),
Text
(
_tip
,
style:
TextStyle
(
fontSize:
14
,
color:
Color
(
0xFF666666
)),
textAlign:
TextAlign
.
center
,
),
],
);
}
else
if
(
_status
==
4
)
{
// 错误/过期
return
Column
(
mainAxisAlignment:
MainAxisAlignment
.
center
,
children:
[
Icon
(
Icons
.
error_outline
,
color:
Color
(
0xFFE64340
),
size:
64
),
const
SizedBox
(
height:
12
),
Text
(
'绑定失败'
,
style:
TextStyle
(
fontSize:
16
,
fontWeight:
FontWeight
.
bold
,
color:
Color
(
0xFF333333
),
),
),
const
SizedBox
(
height:
8
),
Text
(
_tip
,
style:
TextStyle
(
fontSize:
14
,
color:
Color
(
0xFF666666
)),
textAlign:
TextAlign
.
center
,
),
],
);
}
else
if
(
_status
==
5
)
{
// 绑定成功
return
Column
(
mainAxisAlignment:
MainAxisAlignment
.
center
,
children:
[
Icon
(
Icons
.
check_circle
,
color:
Color
(
0xFF07C160
),
size:
64
),
const
SizedBox
(
height:
12
),
Text
(
_tip
,
style:
TextStyle
(
fontSize:
16
,
fontWeight:
FontWeight
.
bold
,
color:
Color
(
0xFF07C160
),
),
),
],
);
}
return
SizedBox
.
shrink
();
}
Widget
_buildBottomActions
()
{
if
(
_status
==
1
||
_status
==
2
)
{
// 二维码显示中,提供关闭按钮
return
TextButton
(
onPressed:
()
=>
Navigator
.
of
(
context
).
pop
(),
child:
Text
(
'取消'
,
style:
TextStyle
(
fontSize:
15
,
color:
Color
(
0xFF999999
)),
),
);
}
else
if
(
_status
==
3
||
_status
==
4
)
{
// 取消或错误,提供重试和关闭按钮
return
Row
(
mainAxisAlignment:
MainAxisAlignment
.
center
,
children:
[
TextButton
(
onPressed:
_refresh
,
child:
Text
(
'重试'
,
style:
TextStyle
(
fontSize:
15
,
color:
Color
(
0xFF7691FA
)),
),
),
const
SizedBox
(
width:
16
),
TextButton
(
onPressed:
()
=>
Navigator
.
of
(
context
).
pop
(),
child:
Text
(
'关闭'
,
style:
TextStyle
(
fontSize:
15
,
color:
Color
(
0xFF999999
)),
),
),
],
);
}
return
SizedBox
.
shrink
();
}
}
lib/utils/login_util.dart
0 → 100644
View file @
bd25dea
import
'package:appframe/config/constant.dart'
;
import
'package:appframe/config/locator.dart'
;
import
'package:appframe/config/routes.dart'
;
import
'package:flutter/material.dart'
;
import
'package:shared_preferences/shared_preferences.dart'
;
/// 登录成功后的公共处理逻辑
class
LoginUtil
{
/// 处理登录成功后的数据解析、缓存持久化和路由跳转
///
/// [data] 登录接口返回的 data 字段
/// [visitor] 是否为游客(0=非游客,1=游客)
/// [loginType] 登录类型,router=跳转路由,reload=重新加载
static
void
handleLoginSuccess
(
Map
<
String
,
dynamic
>
data
,
int
visitor
,
String
loginType
)
{
var
roles
=
data
[
'roles'
];
// 过滤出家长角色的数据
if
(
roles
?.
isNotEmpty
??
false
)
{
roles
.
removeWhere
((
element
)
=>
element
[
'userType'
]
!=
2
);
}
else
{
roles
=
[];
}
var
sessionCode
=
data
[
'sessionCode'
];
var
userCode
=
data
[
'userCode'
];
var
classCode
=
''
;
var
userType
=
0
;
var
stuId
=
''
;
var
className
=
''
;
var
stuName
=
''
;
var
relation
=
''
;
var
sharedPreferences
=
getIt
.
get
<
SharedPreferences
>();
if
(
roles
.
isNotEmpty
)
{
var
role
=
roles
[
0
];
classCode
=
role
[
'classCode'
];
userType
=
role
[
'userType'
];
stuId
=
role
[
'stuId'
];
className
=
role
[
'className'
];
stuName
=
role
[
'stuName'
];
relation
=
role
[
'relation'
]
??
''
;
// 将角色中的班级数据处理后,进行缓存
List
<
String
>
classIdList
=
[];
for
(
var
role
in
roles
)
{
classIdList
.
add
(
role
[
'classCode'
]
as
String
);
}
debugPrint
(
'classCodeIds:--------------
$classIdList
'
);
sharedPreferences
.
setStringList
(
Constant
.
classIdSetKey
,
classIdList
);
}
else
{
sharedPreferences
.
setStringList
(
Constant
.
classIdSetKey
,
[]);
}
var
preUserCode
=
sharedPreferences
.
getString
(
'pre_userCode'
)
??
''
;
if
(
userCode
!=
preUserCode
)
{
// 新用户登录
sharedPreferences
.
setString
(
'pre_userCode'
,
userCode
);
sharedPreferences
.
setString
(
'pre_classCode'
,
classCode
);
sharedPreferences
.
setInt
(
'pre_userType'
,
userType
);
sharedPreferences
.
setString
(
'pre_stuId'
,
stuId
);
}
else
{
// 前一个登录用户重新登录
var
preClassCode
=
sharedPreferences
.
getString
(
'pre_classCode'
)
??
''
;
var
preUserType
=
sharedPreferences
.
getInt
(
'pre_userType'
)
??
0
;
var
preStuId
=
sharedPreferences
.
getString
(
'pre_stuId'
)
??
''
;
if
(
preClassCode
!=
''
&&
roles
.
any
((
element
)
=>
element
[
'classCode'
]
==
preClassCode
&&
element
[
'userType'
]
==
preUserType
&&
element
[
'stuId'
]
==
preStuId
))
{
classCode
=
preClassCode
;
userType
=
preUserType
;
stuId
=
preStuId
;
}
else
{
sharedPreferences
.
setString
(
'pre_userCode'
,
userCode
);
sharedPreferences
.
setString
(
'pre_classCode'
,
classCode
);
sharedPreferences
.
setInt
(
'pre_userType'
,
userType
);
sharedPreferences
.
setString
(
'pre_stuId'
,
stuId
);
}
}
sharedPreferences
.
setInt
(
'auth_visitor'
,
visitor
);
sharedPreferences
.
setString
(
'auth_sessionCode'
,
sessionCode
);
sharedPreferences
.
setString
(
'auth_userCode'
,
userCode
);
sharedPreferences
.
setString
(
'auth_classCode'
,
classCode
);
sharedPreferences
.
setInt
(
'auth_userType'
,
userType
);
sharedPreferences
.
setString
(
'auth_stuId'
,
stuId
);
sharedPreferences
.
setString
(
'auth_className'
,
className
);
sharedPreferences
.
setString
(
'auth_stuName'
,
stuName
);
sharedPreferences
.
setString
(
'auth_relation'
,
relation
);
if
(
loginType
==
'router'
)
{
router
.
go
(
'/web'
,
extra:
{
'visitor'
:
visitor
,
'sessionCode'
:
sessionCode
,
'userCode'
:
userCode
,
'classCode'
:
classCode
,
'userType'
:
userType
,
'stuId'
:
stuId
,
},
);
}
else
if
(
loginType
==
'reload'
)
{
// 通知当前 WebCubit 更新身份信息并重新加载 H5,避免销毁重建整个页面
WebCubitHolder
.
instance
?.
reloadWithIdentity
(
visitor:
0
,
sessionCode:
sessionCode
,
userCode:
userCode
,
classCode:
classCode
,
userType:
userType
,
stuId:
stuId
,
);
}
}
}
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