Commit bbbffded by tanghuan

增加扫码登录功能

1 parent d5e2e159
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();
}
}
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> {
}
}
} catch (e) {
emit(state.copyWith(isUpgrading: false));
print('升级检测处理失败');
print(e);
}
......
......@@ -36,5 +36,5 @@ class Constant {
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';
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();
......
......@@ -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;
}
}
......@@ -22,33 +22,35 @@ class LoginMainPage extends StatelessWidget {
body: SafeArea(
top: false,
child: SingleChildScrollView(
child: Column(
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),
_buildLoginButtons(context, loginMainCubit, state.agreed),
SizedBox(height: 20),
_buildAgreement(context, loginMainCubit, state.agreed),
],
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),
_buildLoginButtons(context, loginMainCubit, state.agreed),
SizedBox(height: 20),
_buildAgreement(context, loginMainCubit, state.agreed),
],
),
),
),
),
],
)),
],
),
),
),
),
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(
......
......@@ -22,35 +22,39 @@ class LoginPhonePage extends StatelessWidget {
body: SafeArea(
top: false,
child: SingleChildScrollView(
child: Column(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),
_buildInputFields(loginPhoneCubit, state),
SizedBox(height: 20),
_buildLoginButton(),
SizedBox(height: 30),
_buildWechatLogin(loginPhoneCubit),
SizedBox(height: 24.5),
_buildAgreement(context, loginPhoneCubit, state.agreed),
],
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),
_buildInputFields(loginPhoneCubit, state),
SizedBox(height: 20),
_buildLoginButton(),
SizedBox(height: 30),
_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),
......
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!