Commit bbbffded by tanghuan

增加扫码登录功能

1 parent d5e2e159
import 'package:appframe/config/constant.dart';
import 'package:appframe/config/locator.dart'; import 'package:appframe/config/locator.dart';
import 'package:appframe/config/routes.dart'; import 'package:appframe/config/routes.dart';
import 'package:appframe/data/repositories/wechat_auth_repository.dart'; import 'package:appframe/data/repositories/wechat_auth_repository.dart';
...@@ -45,7 +44,7 @@ class LoginMainCubit extends Cubit<LoginMainState> { ...@@ -45,7 +44,7 @@ class LoginMainCubit extends Cubit<LoginMainState> {
LoginMainCubit(super.initialState) { LoginMainCubit(super.initialState) {
_fluwx = getIt.get<Fluwx>(); _fluwx = getIt.get<Fluwx>();
_fluwx.addSubscriber(_responseListener); _fluwx.addSubscriber(_responseListener);
_wechatAuthRepository = getIt<WechatAuthRepository>(); _wechatAuthRepository = getIt.get<WechatAuthRepository>();
} }
void toggleAgreed(bool value) { void toggleAgreed(bool value) {
...@@ -83,6 +82,10 @@ class LoginMainCubit extends Cubit<LoginMainState> { ...@@ -83,6 +82,10 @@ class LoginMainCubit extends Cubit<LoginMainState> {
router.go('/loginPhone'); router.go('/loginPhone');
} }
void goLoginQr() {
router.go('/loginQr');
}
void _responseListener(WeChatResponse response) async { void _responseListener(WeChatResponse response) async {
if (response is WeChatAuthResponse) { if (response is WeChatAuthResponse) {
if (response.code == null || response.code == '') { if (response.code == null || response.code == '') {
...@@ -91,6 +94,10 @@ class LoginMainCubit extends Cubit<LoginMainState> { ...@@ -91,6 +94,10 @@ class LoginMainCubit extends Cubit<LoginMainState> {
} }
dynamic resultData = await _wechatAuthRepository.codeToSk(response.code!); dynamic resultData = await _wechatAuthRepository.codeToSk(response.code!);
// 后续添加错误处理
if (resultData['resultCode'] != '001') {
return;
}
var data = resultData['data']; var data = resultData['data'];
var role = data['roles'][0]; var role = data['roles'][0];
...@@ -123,7 +130,7 @@ class LoginMainCubit extends Cubit<LoginMainState> { ...@@ -123,7 +130,7 @@ class LoginMainCubit extends Cubit<LoginMainState> {
@override @override
Future<void> close() { Future<void> close() {
_fluwx.removeSubscriber(_responseListener); _fluwx.clearSubscribers();
return super.close(); return super.close();
} }
} }
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&timestamp=$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();
}
}
...@@ -222,6 +222,7 @@ class WebCubit extends Cubit<WebState> { ...@@ -222,6 +222,7 @@ class WebCubit extends Cubit<WebState> {
} }
} }
} catch (e) { } catch (e) {
emit(state.copyWith(isUpgrading: false));
print('升级检测处理失败'); print('升级检测处理失败');
print(e); print(e);
} }
......
...@@ -36,5 +36,5 @@ class Constant { ...@@ -36,5 +36,5 @@ class Constant {
static const String imClientSecure = 'kM4yqbehB3io9UiLvH6eHvM7xAhfYxoyyaO1tLoHgKltcaI7MZXkUbpFaWdeQIqe'; static const String imClientSecure = 'kM4yqbehB3io9UiLvH6eHvM7xAhfYxoyyaO1tLoHgKltcaI7MZXkUbpFaWdeQIqe';
/// 测试阶段使用 /// 测试阶段使用
static const bool needIM = true; static const bool needIM = false;
} }
...@@ -3,6 +3,7 @@ import 'package:appframe/ui/pages/im_page.dart'; ...@@ -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/link_page.dart';
import 'package:appframe/ui/pages/login_main_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_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/reload_page.dart';
import 'package:appframe/ui/pages/scan_code_page.dart'; import 'package:appframe/ui/pages/scan_code_page.dart';
import 'package:appframe/ui/pages/web_page.dart'; import 'package:appframe/ui/pages/web_page.dart';
...@@ -43,6 +44,12 @@ final GoRouter router = GoRouter( ...@@ -43,6 +44,12 @@ final GoRouter router = GoRouter(
}, },
), ),
GoRoute( GoRoute(
path: '/loginQr',
builder: (BuildContext context, GoRouterState state) {
return const LoginQrPage();
},
),
GoRoute(
path: '/adv', path: '/adv',
builder: (BuildContext context, GoRouterState state) { builder: (BuildContext context, GoRouterState state) {
return const AdvPage(); return const AdvPage();
......
...@@ -22,4 +22,18 @@ class WechatAuthRepository { ...@@ -22,4 +22,18 @@ class WechatAuthRepository {
return resp.data; 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;
}
} }
...@@ -22,33 +22,35 @@ class LoginMainPage extends StatelessWidget { ...@@ -22,33 +22,35 @@ class LoginMainPage extends StatelessWidget {
body: SafeArea( body: SafeArea(
top: false, top: false,
child: SingleChildScrollView( child: SingleChildScrollView(
child: Column( child: Column(
children: [ crossAxisAlignment: CrossAxisAlignment.stretch,
LoginPageImageWidget(), children: [
Transform.translate( LoginPageImageWidget(),
offset: Offset(0, -40), // 向上移动40像素 Transform.translate(
child: Container( offset: Offset(0, -40), // 向上移动40像素
decoration: BoxDecoration( child: Container(
borderRadius: BorderRadius.vertical(top: Radius.circular(20)), decoration: BoxDecoration(
color: Colors.white, // 设置背景色 borderRadius: BorderRadius.vertical(top: Radius.circular(20)),
), color: Colors.white, // 设置背景色
padding: const EdgeInsets.symmetric(horizontal: 24.0), ),
child: Column( padding: const EdgeInsets.symmetric(horizontal: 24.0),
mainAxisAlignment: MainAxisAlignment.start, child: Column(
crossAxisAlignment: CrossAxisAlignment.start, mainAxisAlignment: MainAxisAlignment.start,
children: [ crossAxisAlignment: CrossAxisAlignment.start,
SizedBox(height: 30), children: [
LoginPageHeaderWidget(), SizedBox(height: 30),
SizedBox(height: 25), LoginPageHeaderWidget(),
_buildLoginButtons(context, loginMainCubit, state.agreed), SizedBox(height: 25),
SizedBox(height: 20), _buildLoginButtons(context, loginMainCubit, state.agreed),
_buildAgreement(context, loginMainCubit, state.agreed), SizedBox(height: 20),
], _buildAgreement(context, loginMainCubit, state.agreed),
],
),
), ),
), ),
), ],
], ),
)), ),
), ),
), ),
state.loading state.loading
...@@ -61,7 +63,6 @@ class LoginMainPage extends StatelessWidget { ...@@ -61,7 +63,6 @@ class LoginMainPage extends StatelessWidget {
children: [ children: [
CircularProgressIndicator( CircularProgressIndicator(
color: Color(0xFF7691FA), color: Color(0xFF7691FA),
), ),
], ],
), ),
...@@ -112,6 +113,32 @@ class LoginMainPage extends StatelessWidget { ...@@ -112,6 +113,32 @@ class LoginMainPage extends StatelessWidget {
height: 47, height: 47,
child: ElevatedButton( child: ElevatedButton(
onPressed: () { 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(); loginMainCubit.goLoginPhone();
}, },
style: ElevatedButton.styleFrom( style: ElevatedButton.styleFrom(
......
...@@ -22,35 +22,39 @@ class LoginPhonePage extends StatelessWidget { ...@@ -22,35 +22,39 @@ class LoginPhonePage extends StatelessWidget {
body: SafeArea( body: SafeArea(
top: false, top: false,
child: SingleChildScrollView( child: SingleChildScrollView(
child: Column(children: [ child: Column(
LoginPageImageWidget(), crossAxisAlignment: CrossAxisAlignment.stretch,
Transform.translate( children: [
offset: Offset(0, -40), // 向上移动40像素 LoginPageImageWidget(),
child: Container( Transform.translate(
decoration: BoxDecoration( offset: Offset(0, -40), // 向上移动40像素
borderRadius: BorderRadius.vertical(top: Radius.circular(20)), child: Container(
color: Colors.white, // 设置背景色 decoration: BoxDecoration(
), borderRadius: BorderRadius.vertical(top: Radius.circular(20)),
padding: const EdgeInsets.symmetric(horizontal: 24.0), color: Colors.white, // 设置背景色
child: Column( ),
mainAxisAlignment: MainAxisAlignment.start, padding: const EdgeInsets.symmetric(horizontal: 24.0),
crossAxisAlignment: CrossAxisAlignment.start, child: Column(
children: [ mainAxisAlignment: MainAxisAlignment.start,
SizedBox(height: 30), crossAxisAlignment: CrossAxisAlignment.start,
LoginPageHeaderWidget(), children: [
SizedBox(height: 25), SizedBox(height: 30),
_buildInputFields(loginPhoneCubit, state), LoginPageHeaderWidget(),
SizedBox(height: 20), SizedBox(height: 25),
_buildLoginButton(), _buildInputFields(loginPhoneCubit, state),
SizedBox(height: 30), SizedBox(height: 20),
_buildWechatLogin(loginPhoneCubit), _buildLoginButton(),
SizedBox(height: 24.5), SizedBox(height: 30),
_buildAgreement(context, loginPhoneCubit, state.agreed), _buildAgreement(context, loginPhoneCubit, state.agreed),
], SizedBox(height: 24.5),
_buildWechatLogin(loginPhoneCubit),
],
),
),
), ),
), ],
), ),
])), ),
), ),
); );
}, },
...@@ -231,7 +235,7 @@ class LoginPhonePage extends StatelessWidget { ...@@ -231,7 +235,7 @@ class LoginPhonePage extends StatelessWidget {
Image.asset('assets/images/login/wechat.png'), Image.asset('assets/images/login/wechat.png'),
SizedBox(height: 4), SizedBox(height: 4),
Text( Text(
'微信登录', '返回微信登录',
style: TextStyle( style: TextStyle(
fontSize: 14, fontSize: 14,
color: Color(0xFF000000), color: Color(0xFF000000),
......
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),
),
),
],
),
),
],
),
);
}
}
Styling with Markdown is supported
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!