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 bbbffded
authored
2025-12-04 15:00:23 +0800
by
tanghuan
Browse Files
Options
Browse Files
Tag
Download
Email Patches
Plain Diff
增加扫码登录功能
1 parent
d5e2e159
Show whitespace changes
Inline
Side-by-side
Showing
9 changed files
with
396 additions
and
11 deletions
lib/bloc/login_main_cubit.dart
lib/bloc/login_qr_cubit.dart
lib/bloc/web_cubit.dart
lib/config/constant.dart
lib/config/routes.dart
lib/data/repositories/wechat_auth_repository.dart
lib/ui/pages/login_main_page.dart
lib/ui/pages/login_phone_page.dart
lib/ui/pages/login_qr_page.dart
lib/bloc/login_main_cubit.dart
View file @
bbbffde
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'
;
...
...
@@ -45,7 +44,7 @@ class LoginMainCubit extends Cubit<LoginMainState> {
LoginMainCubit
(
super
.
initialState
)
{
_fluwx
=
getIt
.
get
<
Fluwx
>();
_fluwx
.
addSubscriber
(
_responseListener
);
_wechatAuthRepository
=
getIt
<
WechatAuthRepository
>();
_wechatAuthRepository
=
getIt
.
get
<
WechatAuthRepository
>();
}
void
toggleAgreed
(
bool
value
)
{
...
...
@@ -83,6 +82,10 @@ class LoginMainCubit extends Cubit<LoginMainState> {
router
.
go
(
'/loginPhone'
);
}
void
goLoginQr
()
{
router
.
go
(
'/loginQr'
);
}
void
_responseListener
(
WeChatResponse
response
)
async
{
if
(
response
is
WeChatAuthResponse
)
{
if
(
response
.
code
==
null
||
response
.
code
==
''
)
{
...
...
@@ -91,6 +94,10 @@ class LoginMainCubit extends Cubit<LoginMainState> {
}
dynamic
resultData
=
await
_wechatAuthRepository
.
codeToSk
(
response
.
code
!);
// 后续添加错误处理
if
(
resultData
[
'resultCode'
]
!=
'001'
)
{
return
;
}
var
data
=
resultData
[
'data'
];
var
role
=
data
[
'roles'
][
0
];
...
...
@@ -123,7 +130,7 @@ class LoginMainCubit extends Cubit<LoginMainState> {
@override
Future
<
void
>
close
()
{
_fluwx
.
removeSubscriber
(
_responseListener
);
_fluwx
.
clearSubscribers
(
);
return
super
.
close
();
}
}
lib/bloc/login_qr_cubit.dart
0 → 100644
View file @
bbbffde
import
'dart:convert'
;
import
'dart:typed_data'
;
import
'package:appframe/config/locator.dart'
;
import
'package:appframe/config/routes.dart'
;
import
'package:appframe/data/repositories/wechat_auth_repository.dart'
;
import
'package:crypto/crypto.dart'
;
import
'package:equatable/equatable.dart'
;
import
'package:flutter_bloc/flutter_bloc.dart'
;
import
'package:fluwx/fluwx.dart'
;
import
'package:shared_preferences/shared_preferences.dart'
;
class
LoginQrState
extends
Equatable
{
final
int
status
;
final
Uint8List
?
image
;
final
String
tip
;
const
LoginQrState
({
this
.
status
=
0
,
this
.
image
,
this
.
tip
=
''
,
});
@override
List
<
Object
?>
get
props
=>
[
status
,
image
,
tip
,
];
LoginQrState
copyWith
({
int
?
status
,
Uint8List
?
image
,
String
?
tip
,
})
{
return
LoginQrState
(
status:
status
??
this
.
status
,
image:
image
??
this
.
image
,
tip:
tip
??
this
.
tip
,
);
}
}
class
LoginQrCubit
extends
Cubit
<
LoginQrState
>
{
late
final
Fluwx
_fluwx
;
late
final
WechatAuthRepository
_wechatAuthRepository
;
LoginQrCubit
(
super
.
initialState
)
{
_fluwx
=
getIt
.
get
<
Fluwx
>();
_wechatAuthRepository
=
getIt
.
get
<
WechatAuthRepository
>();
init
();
}
Future
<
void
>
init
()
async
{
// sdk_ticket
var
result
=
await
_wechatAuthRepository
.
getTicket
();
// 后续添加错误处理
if
(
result
[
'resultCode'
]
!=
'001'
)
{
print
(
"获取 sdk_ticket 失败"
);
return
;
}
print
(
'获取 sdk_ticket 成功'
);
var
sdkTicket
=
result
[
'data'
];
// 当前时间戳
var
timestamp
=
DateTime
.
now
().
millisecondsSinceEpoch
;
var
signature
=
_sig
(
'wx8c32ea248f0c7765'
,
'
$timestamp
'
,
sdkTicket
,
'
$timestamp
'
);
print
(
'开始处理二维码登录'
);
_fluwx
.
addSubscriber
(
_responseListener
);
var
authResult
=
await
_fluwx
.
authBy
(
which:
QRCode
(
appId:
'wx8c32ea248f0c7765'
,
scope:
'snsapi_userinfo'
,
nonceStr:
'
$timestamp
'
,
timestamp:
'
$timestamp
'
,
signature:
signature
,
),
);
print
(
'AuthResult
$authResult
'
);
print
(
'结束处理二维码'
);
}
void
_responseListener
(
WeChatResponse
response
)
async
{
print
(
'回调。。。。。。。。。'
);
if
(
response
is
WeChatAuthGotQRCodeResponse
)
{
print
(
'收到二维码。。。'
);
int
?
errCode
=
response
.
errCode
;
if
(
errCode
!=
null
&&
errCode
==
0
)
{
emit
(
state
.
copyWith
(
status:
1
,
image:
response
.
qrCode
,
tip:
'打开微信,扫描二维码登录'
));
}
else
{
emit
(
state
.
copyWith
(
tip:
'错误
$errCode
'
));
}
}
else
if
(
response
is
WeChatQRCodeScannedResponse
)
{
print
(
'已扫描二维码。。。'
);
int
?
errCode
=
response
.
errCode
;
if
(
errCode
!=
null
&&
errCode
==
0
)
{
emit
(
state
.
copyWith
(
status:
2
,
tip:
'在微信中轻触允许即可登录'
));
}
else
{
emit
(
state
.
copyWith
(
tip:
'错误
$errCode
'
));
}
}
else
if
(
response
is
WeChatAuthByQRCodeFinishedResponse
)
{
print
(
'确认二维码。。。'
);
int
?
errCode
=
response
.
errCode
;
if
(
errCode
!=
null
&&
errCode
==
0
)
{
_doLogin
(
response
.
authCode
!);
}
else
{
emit
(
state
.
copyWith
(
status:
3
,
tip:
'你已取消此次登录'
));
}
}
}
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
();
}
Future
<
void
>
_doLogin
(
String
code
)
async
{
dynamic
resultData
=
await
_wechatAuthRepository
.
codeToSk
(
code
);
// 后续添加错误处理
if
(
resultData
[
'resultCode'
]
!=
'001'
)
{
return
;
}
var
data
=
resultData
[
'data'
];
var
role
=
data
[
'roles'
][
0
];
final
sessionCode
=
data
[
'sessionCode'
];
final
userCode
=
data
[
'userCode'
];
final
classCode
=
role
[
'classCode'
];
final
userType
=
role
[
'userType'
];
final
stuId
=
role
[
'stuId'
];
var
sharedPreferences
=
getIt
.
get
<
SharedPreferences
>();
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
??
''
);
router
.
go
(
'/web'
,
extra:
{
'sessionCode'
:
sessionCode
,
'userCode'
:
userCode
,
'classCode'
:
classCode
,
'userType'
:
userType
,
'stuId'
:
stuId
,
},
);
}
void
goLoginMain
()
{
router
.
go
(
'/loginMain'
);
}
@override
Future
<
void
>
close
()
{
_fluwx
.
stopAuthByQRCode
();
_fluwx
.
clearSubscribers
();
return
super
.
close
();
}
}
lib/bloc/web_cubit.dart
View file @
bbbffde
...
...
@@ -222,6 +222,7 @@ class WebCubit extends Cubit<WebState> {
}
}
}
catch
(
e
)
{
emit
(
state
.
copyWith
(
isUpgrading:
false
));
print
(
'升级检测处理失败'
);
print
(
e
);
}
...
...
lib/config/constant.dart
View file @
bbbffde
...
...
@@ -36,5 +36,5 @@ class Constant {
static
const
String
imClientSecure
=
'kM4yqbehB3io9UiLvH6eHvM7xAhfYxoyyaO1tLoHgKltcaI7MZXkUbpFaWdeQIqe'
;
/// 测试阶段使用
static
const
bool
needIM
=
tru
e
;
static
const
bool
needIM
=
fals
e
;
}
lib/config/routes.dart
View file @
bbbffde
...
...
@@ -3,6 +3,7 @@ import 'package:appframe/ui/pages/im_page.dart';
import
'package:appframe/ui/pages/link_page.dart'
;
import
'package:appframe/ui/pages/login_main_page.dart'
;
import
'package:appframe/ui/pages/login_phone_page.dart'
;
import
'package:appframe/ui/pages/login_qr_page.dart'
;
import
'package:appframe/ui/pages/reload_page.dart'
;
import
'package:appframe/ui/pages/scan_code_page.dart'
;
import
'package:appframe/ui/pages/web_page.dart'
;
...
...
@@ -43,6 +44,12 @@ final GoRouter router = GoRouter(
},
),
GoRoute
(
path:
'/loginQr'
,
builder:
(
BuildContext
context
,
GoRouterState
state
)
{
return
const
LoginQrPage
();
},
),
GoRoute
(
path:
'/adv'
,
builder:
(
BuildContext
context
,
GoRouterState
state
)
{
return
const
AdvPage
();
...
...
lib/data/repositories/wechat_auth_repository.dart
View file @
bbbffde
...
...
@@ -22,4 +22,18 @@ class WechatAuthRepository {
return
resp
.
data
;
}
Future
<
dynamic
>
getTicket
()
async
{
Response
resp
=
await
_apiService
.
post
(
'/login/applet/wkbxe/getTicketByApp?version=1.0.0'
,
{
"appCode"
:
"bxeapp"
,
"params"
:
{},
},
);
print
(
'获取ticket:
$resp
'
);
return
resp
.
data
;
}
}
lib/ui/pages/login_main_page.dart
View file @
bbbffde
...
...
@@ -23,6 +23,7 @@ class LoginMainPage extends StatelessWidget {
top:
false
,
child:
SingleChildScrollView
(
child:
Column
(
crossAxisAlignment:
CrossAxisAlignment
.
stretch
,
children:
[
LoginPageImageWidget
(),
Transform
.
translate
(
...
...
@@ -48,7 +49,8 @@ class LoginMainPage extends StatelessWidget {
),
),
],
)),
),
),
),
),
state
.
loading
...
...
@@ -61,7 +63,6 @@ class LoginMainPage extends StatelessWidget {
children:
[
CircularProgressIndicator
(
color:
Color
(
0xFF7691FA
),
),
],
),
...
...
@@ -112,6 +113,32 @@ class LoginMainPage extends StatelessWidget {
height:
47
,
child:
ElevatedButton
(
onPressed:
()
{
loginMainCubit
.
goLoginQr
();
},
style:
ElevatedButton
.
styleFrom
(
backgroundColor:
Color
(
0xFF00CB60
),
foregroundColor:
Colors
.
white
,
textStyle:
TextStyle
(
fontSize:
19
),
shape:
RoundedRectangleBorder
(
borderRadius:
BorderRadius
.
circular
(
23.5
),
),
),
child:
Row
(
mainAxisAlignment:
MainAxisAlignment
.
center
,
children:
[
Image
.
asset
(
'assets/images/login/wechat_white_icon.png'
),
SizedBox
(
width:
6
),
Text
(
'扫码登录'
),
],
),
),
),
SizedBox
(
height:
15
),
SizedBox
(
width:
double
.
infinity
,
height:
47
,
child:
ElevatedButton
(
onPressed:
()
{
loginMainCubit
.
goLoginPhone
();
},
style:
ElevatedButton
.
styleFrom
(
...
...
lib/ui/pages/login_phone_page.dart
View file @
bbbffde
...
...
@@ -22,7 +22,9 @@ class LoginPhonePage extends StatelessWidget {
body:
SafeArea
(
top:
false
,
child:
SingleChildScrollView
(
child:
Column
(
children:
[
child:
Column
(
crossAxisAlignment:
CrossAxisAlignment
.
stretch
,
children:
[
LoginPageImageWidget
(),
Transform
.
translate
(
offset:
Offset
(
0
,
-
40
),
// 向上移动40像素
...
...
@@ -43,14 +45,16 @@ class LoginPhonePage extends StatelessWidget {
SizedBox
(
height:
20
),
_buildLoginButton
(),
SizedBox
(
height:
30
),
_buildWechatLogin
(
loginPhoneCubit
),
SizedBox
(
height:
24.5
),
_buildAgreement
(
context
,
loginPhoneCubit
,
state
.
agreed
),
SizedBox
(
height:
24.5
),
_buildWechatLogin
(
loginPhoneCubit
),
],
),
),
),
])),
],
),
),
),
);
},
...
...
@@ -231,7 +235,7 @@ class LoginPhonePage extends StatelessWidget {
Image
.
asset
(
'assets/images/login/wechat.png'
),
SizedBox
(
height:
4
),
Text
(
'微信登录'
,
'
返回
微信登录'
,
style:
TextStyle
(
fontSize:
14
,
color:
Color
(
0xFF000000
),
...
...
lib/ui/pages/login_qr_page.dart
0 → 100644
View file @
bbbffde
import
'package:appframe/bloc/login_qr_cubit.dart'
;
import
'package:appframe/ui/widgets/login/login_page_header_widget.dart'
;
import
'package:appframe/ui/widgets/login/login_page_image_widget.dart'
;
import
'package:flutter/material.dart'
;
import
'package:flutter_bloc/flutter_bloc.dart'
;
class
LoginQrPage
extends
StatelessWidget
{
const
LoginQrPage
({
super
.
key
});
@override
Widget
build
(
BuildContext
context
)
{
return
BlocProvider
(
create:
(
BuildContext
context
)
=>
LoginQrCubit
(
LoginQrState
()),
child:
BlocConsumer
<
LoginQrCubit
,
LoginQrState
>(
builder:
(
context
,
state
)
{
var
loginQrCubit
=
context
.
read
<
LoginQrCubit
>();
return
Scaffold
(
resizeToAvoidBottomInset:
true
,
backgroundColor:
Colors
.
white
,
body:
SafeArea
(
top:
false
,
child:
SingleChildScrollView
(
child:
Column
(
crossAxisAlignment:
CrossAxisAlignment
.
stretch
,
children:
[
LoginPageImageWidget
(),
Transform
.
translate
(
offset:
Offset
(
0
,
-
40
),
// 向上移动40像素
child:
Container
(
decoration:
BoxDecoration
(
borderRadius:
BorderRadius
.
vertical
(
top:
Radius
.
circular
(
20
)),
color:
Colors
.
white
,
// 设置背景色
),
padding:
const
EdgeInsets
.
symmetric
(
horizontal:
24.0
),
child:
Column
(
mainAxisAlignment:
MainAxisAlignment
.
start
,
crossAxisAlignment:
CrossAxisAlignment
.
start
,
children:
[
SizedBox
(
height:
30
),
LoginPageHeaderWidget
(),
SizedBox
(
height:
25
),
Center
(
child:
_buildQrCode
(
loginQrCubit
,
state
),
),
SizedBox
(
height:
30
),
_buildWechatLogin
(
loginQrCubit
),
],
),
),
),
],
),
),
),
);
},
listener:
(
context
,
state
)
{},
));
}
Column
?
_buildQrCode
(
LoginQrCubit
loginQrCubit
,
LoginQrState
state
)
{
if
(
state
.
status
==
0
)
{
// 等待二维码数据
return
Column
(
children:
[
Column
(
children:
[
SizedBox
(
height:
40
),
CircularProgressIndicator
(),
SizedBox
(
height:
12
),
Text
(
'正在生成二维码...'
,
),
SizedBox
(
height:
40
),
],
),
SizedBox
(
height:
12
),
Text
(
state
.
tip
,
),
],
);
}
else
if
(
state
.
status
==
1
)
{
// 等待扫码
return
Column
(
children:
[
Image
.
memory
(
state
.
image
!,
width:
200
,
height:
200
,
),
SizedBox
(
height:
12
),
Text
(
state
.
tip
,
),
],
);
}
else
if
(
state
.
status
==
2
)
{
// 已扫码,等待确认
return
Column
(
children:
[
// Image.asset("assets/qr_suc.png"),
Text
(
"已扫码"
,
),
SizedBox
(
height:
12
),
Text
(
state
.
tip
,
),
],
);
}
else
if
(
state
.
status
==
3
)
{
// 拒绝
return
Column
(
children:
[
// Image.asset("assets/qr_fail.png"),
Text
(
"拒绝登录"
,
),
SizedBox
(
height:
12
),
Text
(
state
.
tip
,
),
],
);
}
return
null
;
}
Widget
_buildWechatLogin
(
LoginQrCubit
loginQrCubit
)
{
return
SizedBox
(
width:
double
.
infinity
,
child:
Column
(
mainAxisAlignment:
MainAxisAlignment
.
center
,
children:
[
GestureDetector
(
onTap:
()
{
loginQrCubit
.
goLoginMain
();
},
child:
Column
(
children:
[
Image
.
asset
(
'assets/images/login/wechat.png'
),
SizedBox
(
height:
4
),
Text
(
'返回微信登录'
,
style:
TextStyle
(
fontSize:
14
,
color:
Color
(
0xFF000000
),
),
),
],
),
),
],
),
);
}
}
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