Commit ee39088f by tanghuan

新的登录UI,以及其他一些调整

1 parent 51118024
Showing 64 changed files with 331 additions and 98 deletions
import 'dart:async';
import 'package:appframe/config/locator.dart';
import 'package:appframe/config/routes.dart';
import 'package:appframe/data/repositories/phone_auth_repository.dart';
import 'package:equatable/equatable.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:shared_preferences/shared_preferences.dart';
class LoginPhoneState extends Equatable {
final bool agreed;
final bool showAgreed;
final String phone;
final String password;
final bool showSnackBar;
final String snackBarMsg;
final bool allowSend;
final int seconds;
const LoginPhoneState(
{this.agreed = false,
const LoginPhoneState({
this.agreed = false,
this.showAgreed = false,
this.phone = '',
this.password = '',
this.showSnackBar = false,
this.snackBarMsg = '',
this.allowSend = true,
this.seconds = 0});
this.seconds = 0,
});
LoginPhoneState copyWith({
bool? agreed,
bool? showAgreed,
String? phone,
String? password,
bool? showSnackBar,
String? snackBarMsg,
bool? allowSend,
int? seconds,
}) {
return LoginPhoneState(
agreed: agreed ?? this.agreed,
showAgreed: showAgreed ?? this.showAgreed,
phone: phone ?? this.phone,
password: password ?? this.password,
showSnackBar: showSnackBar ?? this.showSnackBar,
snackBarMsg: snackBarMsg ?? this.snackBarMsg,
allowSend: allowSend ?? this.allowSend,
seconds: seconds ?? this.seconds,
);
}
@override
List<Object?> get props => [phone, password, agreed, showAgreed, allowSend, seconds];
List<Object?> get props => [
agreed,
showAgreed,
showSnackBar,
snackBarMsg,
allowSend,
seconds,
];
}
class LoginPhoneCubit extends Cubit<LoginPhoneState> {
......@@ -49,6 +61,8 @@ class LoginPhoneCubit extends Cubit<LoginPhoneState> {
Timer? _timer;
int countdown = 60; // 倒计时秒数
late final PhoneAuthRepository _phoneAuthRepository;
TextEditingController get phoneController => _phoneController;
TextEditingController get codeController => _codeController;
......@@ -57,8 +71,10 @@ class LoginPhoneCubit extends Cubit<LoginPhoneState> {
_phoneController = TextEditingController();
_codeController = TextEditingController();
_phoneController.text = state.phone;
_codeController.text = state.password;
_phoneController.text = '';
_codeController.text = '';
_phoneAuthRepository = getIt.get<PhoneAuthRepository>();
}
/// 开始倒计时
......@@ -78,21 +94,107 @@ class LoginPhoneCubit extends Cubit<LoginPhoneState> {
}
/// 发送验证码
void sendVerificationCode() {
Future<void> sendVerificationCode() async {
if (state.allowSend) {
// 实际发送验证码的逻辑
startCountdown(); // 开始倒计时
// 验证手机号码
String phone = _phoneController.text;
if (!RegExp(r'^1[3-9][0-9]{9}$').hasMatch(phone)) {
emit(state.copyWith(showSnackBar: true, snackBarMsg: '请输入正确的手机号码'));
emit(state.copyWith(showSnackBar: false));
return;
}
// 发送验证码
var result = await _phoneAuthRepository.verifyCode(phone);
if (result['code'] != 0) {
emit(state.copyWith(showSnackBar: true, snackBarMsg: result['error']));
emit(state.copyWith(showSnackBar: false));
return;
}
emit(state.copyWith(allowSend: false, seconds: 60));
// 开始倒计时
startCountdown();
}
}
Future<void> phoneAuth() async {
String phone = _phoneController.text;
String verifyCode = _codeController.text;
if (!RegExp(r'^1[3-9][0-9]{9}$').hasMatch(phone)) {
emit(state.copyWith(showSnackBar: true, snackBarMsg: '请输入正确的手机号码'));
emit(state.copyWith(showSnackBar: false));
return;
}
if (!RegExp(r'^\d{4}$').hasMatch(verifyCode)) {
emit(state.copyWith(showSnackBar: true, snackBarMsg: '请输入正确的验证码'));
emit(state.copyWith(showSnackBar: false));
return;
}
if (!state.agreed) {
emit(state.copyWith(showAgreed: true));
return;
}
var result = await _phoneAuthRepository.login(phone, verifyCode);
if (result['code'] != 1) {
emit(state.copyWith(showSnackBar: true, snackBarMsg: result['error']));
return;
}
var data = result['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 toggleAgreed(bool value) {
emit(state.copyWith(agreed: value));
}
void confirmAgreed() {
emit(state.copyWith(agreed: true, showAgreed: false));
phoneAuth();
}
void cancelAgreed() {
emit(state.copyWith(showAgreed: false));
}
void goLoginMain() {
router.go('/loginMain');
}
void goLoginQr() {
router.go('/loginQr');
}
@override
Future<void> close() async {
try {
......
import 'dart:convert';
import 'dart:typed_data';
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';
......@@ -66,11 +67,11 @@ class LoginQrCubit extends Cubit<LoginQrState> {
// 当前时间戳
var timestamp = DateTime.now().millisecondsSinceEpoch;
var signature = _sig('wx8c32ea248f0c7765', '$timestamp', sdkTicket, '$timestamp');
var signature = _sig(Constant.wxAppId, '$timestamp', sdkTicket, '$timestamp');
var authResult = await _fluwx.authBy(
which: QRCode(
appId: 'wx8c32ea248f0c7765',
appId: Constant.wxAppId,
scope: 'snsapi_userinfo',
nonceStr: '$timestamp',
timestamp: '$timestamp',
......@@ -84,14 +85,21 @@ class LoginQrCubit extends Cubit<LoginQrState> {
if (response is WeChatAuthGotQRCodeResponse) {
int? errCode = response.errCode;
if (errCode != null && errCode == 0) {
emit(state.copyWith(status: 1, image: response.qrCode, tip: '打开微信,扫描二维码登录'));
emit(state.copyWith(
status: 1,
image: response.qrCode,
tip: '打开微信,扫描二维码登录',
));
} else {
emit(state.copyWith(tip: '错误 $errCode'));
}
} else if (response is WeChatQRCodeScannedResponse) {
int? errCode = response.errCode;
if (errCode != null && errCode == 0) {
emit(state.copyWith(status: 2, tip: '在微信中轻触允许即可登录'));
emit(state.copyWith(
status: 2,
tip: '在微信中轻触允许即可登录',
));
} else {
emit(state.copyWith(tip: '错误 $errCode'));
}
......@@ -100,7 +108,10 @@ class LoginQrCubit extends Cubit<LoginQrState> {
if (errCode != null && errCode == 0) {
_doLogin(response.authCode!);
} else {
emit(state.copyWith(status: 3, tip: '你已取消此次登录'));
emit(state.copyWith(
status: 3,
tip: '你已取消此次登录',
));
}
}
}
......
......@@ -55,15 +55,24 @@ class Constant {
/// /////////////////////////////////////////////////////////////////////////////////////////////////////////////////
///
static const String bxeBaseUrl = EnvConfig.env == 'dev' ? 'https://dev.banxiaoer.net' : 'https://bxr.banxiaoer.com';
static const String iotAppBaseUrl = EnvConfig.env == 'dev' ? 'https://iotapp-dev.banxiaoer.com/iotapp' : 'https://iotapp.banxiaoer.com/iotapp';
static const String iotAppBaseUrl =
EnvConfig.env == 'dev' ? 'https://iotapp-dev.banxiaoer.com/iotapp' : 'https://iotapp.banxiaoer.com/iotapp';
/// 微信登录相关
/// /////////////////////////////////////////////////////////////////////////////////////////////////////////////////
///
static const String wxAppId = 'wx8c32ea248f0c7765';
static const String universalLink = 'https://dev.banxiaoer.net/path/to/wechat/';
/// IM 相关
/// /////////////////////////////////////////////////////////////////////////////////////////////////////////////////
///
/// IM SDK
static const int imSdkAppId = EnvConfig.env == 'dev' ? 1400310691 : 0;
static const String imClientSecure =
EnvConfig.env == 'dev' ? 'kM4yqbehB3io9UiLvH6eHvM7xAhfYxoyyaO1tLoHgKltcaI7MZXkUbpFaWdeQIqe' : '';
static const int imSdkAppId = EnvConfig.env == 'dev' ? 1400310691 : 1600117207;
static const String imClientSecure = EnvConfig.env == 'dev'
? 'kM4yqbehB3io9UiLvH6eHvM7xAhfYxoyyaO1tLoHgKltcaI7MZXkUbpFaWdeQIqe'
: 'GkMkhAnrCThYrZxApCBdFidcAC8USwVnhoqMGzqmSvmcegRCvETtDR2Te9btarnG';
/// 测试阶段使用
static const bool needIM = false;
......
......@@ -31,6 +31,7 @@ import 'package:appframe/data/repositories/message/vibrate_short_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';
import 'package:appframe/data/repositories/phone_auth_repository.dart';
import 'package:appframe/data/repositories/wechat_auth_repository.dart';
import 'package:appframe/services/api_service.dart';
import 'package:appframe/services/dispatcher.dart';
......@@ -51,8 +52,8 @@ Future<void> setupLocator() async {
Fluwx fluwx = Fluwx();
if (Platform.isAndroid || Platform.isIOS) {
await fluwx.registerApi(
appId: "wx8c32ea248f0c7765",
universalLink: "https://dev.banxiaoer.net/path/to/wechat/",
appId: Constant.wxAppId,
universalLink: Constant.universalLink,
);
}
return fluwx;
......@@ -177,7 +178,7 @@ Future<void> setupLocator() async {
getIt.registerLazySingleton<MessageHandler>(() => TitleBarHandler(), instanceName: 'setTitlebar');
/// 新路由打开链接
getIt.registerLazySingleton<MessageHandler>(() => OpenLinkHandler(), instanceName: 'openlink');
getIt.registerLazySingleton<MessageHandler>(() => OpenLinkHandler(), instanceName: 'openLink');
/// 登录
getIt.registerLazySingleton<MessageHandler>(() => GoLoginHandler(), instanceName: 'goLogin');
......@@ -208,4 +209,5 @@ Future<void> setupLocator() async {
/// repository
getIt.registerLazySingleton<WechatAuthRepository>(() => WechatAuthRepository());
getIt.registerLazySingleton<PhoneAuthRepository>(() => PhoneAuthRepository());
}
import 'package:appframe/config/locator.dart';
import 'package:appframe/services/api_service.dart';
import 'package:dio/dio.dart';
class PhoneAuthRepository {
late final ApiService _appService;
PhoneAuthRepository() {
_appService = getIt<ApiService>(instanceName: 'appApiService');
}
///
/// {
/// "code": 0,
/// "error": "绑定成功"
/// }
///
Future<dynamic> bind(String userid, String phone, String verifyCode) async {
Response resp = await _appService.post(
'/api/v1/comm/phone/bind',
{
"userid": userid,
"phone": phone,
"verifyCode": verifyCode,
},
);
return resp.data;
}
///
/// {
/// "code": 0,
/// "error": "获取成功",
/// }
///
Future<dynamic> verifyCode(String phone) async {
Response resp = await _appService.get(
'/api/v1/comm/phone/verifycode',
queryParameters: {
"phone": phone,
"type": 1,
},
);
return resp.data;
}
///
/// {
/// "code": 0,
/// "error": "登录成功",
/// "userid": "user123"
/// }
///
Future<dynamic> login(String phone, String verifyCode) async {
Response resp = await _appService.post(
'/api/v1/comm/phone/login',
{
"phone": phone,
"verifyCode": verifyCode,
},
);
return resp.data;
}
}
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';
......@@ -15,7 +13,6 @@ class LoginQrPage extends StatelessWidget {
builder: (context, state) {
var loginQrCubit = context.read<LoginQrCubit>();
return Scaffold(
resizeToAvoidBottomInset: true,
backgroundColor: Colors.white,
body: SafeArea(
top: false,
......@@ -23,30 +20,39 @@ class LoginQrPage extends StatelessWidget {
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, // 设置背景色
Image.asset(
'assets/images/login_v2/banner_2.png',
width: MediaQuery.of(context).size.width,
fit: BoxFit.fitWidth,
),
padding: const EdgeInsets.symmetric(horizontal: 24.0),
child: Column(
mainAxisAlignment: MainAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
SizedBox(height: 30),
LoginPageHeaderWidget(),
SizedBox(height: 25),
Center(
SizedBox(
height: 380,
child: _buildQrCode(loginQrCubit, state),
),
SizedBox(height: 30),
_buildWechatLogin(loginQrCubit),
],
SizedBox(height: 20),
Center(
child: Text(
'其他方式登录',
style: TextStyle(
fontSize: 14,
color: Color(0xFF999999),
),
strutStyle: StrutStyle(height: 16 / 14),
),
),
SizedBox(height: 15),
Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
InkWell(
onTap: () {
loginQrCubit.goLoginMain();
},
child: Image.asset(
'assets/images/login_v2/wechat_green_icon.png',
),
),
],
),
],
),
......@@ -65,18 +71,31 @@ class LoginQrPage extends StatelessWidget {
children: [
Column(
children: [
SizedBox(height: 40),
CircularProgressIndicator(),
SizedBox(height: 12),
SizedBox(height: 112),
Image.asset(
'assets/images/login_v2/loading.gif',
width: 72,
height: 72,
),
SizedBox(height: 24),
Text(
'正在生成二维码...',
style: TextStyle(
fontSize: 14,
color: Color(0xFF333333),
),
SizedBox(height: 40),
],
strutStyle: StrutStyle(height: 16 / 14),
),
SizedBox(height: 12),
SizedBox(height: 8),
Text(
state.tip,
style: TextStyle(
fontSize: 14,
color: Color(0xFF333333),
),
strutStyle: StrutStyle(height: 16 / 14),
),
],
),
],
);
......@@ -84,14 +103,31 @@ class LoginQrPage extends StatelessWidget {
// 等待扫码
return Column(
children: [
Image.memory(
Container(
width: 230,
height: 230,
padding: EdgeInsets.all(20),
decoration: BoxDecoration(
border: Border.all(
color: Color(0x29265ECF),
width: 0.5,
),
borderRadius: BorderRadius.circular(20),
),
child: Image.memory(
state.image!,
width: 200,
height: 200,
width: 190,
height: 190,
),
),
SizedBox(height: 12),
Text(
state.tip,
style: TextStyle(
fontSize: 14,
color: Color(0xFF333333),
),
strutStyle: StrutStyle(height: 16 / 14),
),
],
);
......@@ -99,13 +135,31 @@ class LoginQrPage extends StatelessWidget {
// 已扫码,等待确认
return Column(
children: [
// Image.asset("assets/qr_suc.png"),
SizedBox(height: 15.5),
Image.asset(
"assets/images/login_v2/allowed.png",
width: 217,
height: 190,
),
SizedBox(height: 15.5),
Text(
"已扫码",
style: TextStyle(
fontSize: 16,
fontWeight: FontWeight.bold,
fontFamily: '黑体',
color: Color(0xFF333333),
),
SizedBox(height: 12),
strutStyle: StrutStyle(height: 21 / 16),
),
SizedBox(height: 8),
Text(
state.tip,
style: TextStyle(
fontSize: 14,
color: Color(0xFF333333),
),
strutStyle: StrutStyle(height: 16 / 14),
),
],
);
......@@ -113,46 +167,35 @@ class LoginQrPage extends StatelessWidget {
// 拒绝
return Column(
children: [
// Image.asset("assets/qr_fail.png"),
SizedBox(height: 15.5),
Image.asset(
"assets/images/login_v2/refused.png",
width: 217,
height: 190,
),
SizedBox(height: 15.5),
Text(
"拒绝登录",
style: TextStyle(
fontSize: 16,
fontWeight: FontWeight.bold,
fontFamily: '黑体',
color: Color(0xFF333333),
),
SizedBox(height: 12),
Text(
state.tip,
strutStyle: StrutStyle(height: 21 / 16),
),
],
);
}
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),
SizedBox(height: 8),
Text(
'返回微信登录',
state.tip,
style: TextStyle(
fontSize: 14,
color: Color(0xFF000000),
),
),
],
color: Color(0xFF333333),
),
strutStyle: StrutStyle(height: 16 / 14),
),
],
),
);
}
return null;
}
}
......@@ -90,7 +90,7 @@ class WebPage extends StatelessWidget {
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
ListTile(
/*ListTile(
leading: const Icon(Icons.chat_bubble_outline),
title: const Text('消息测试'),
onTap: () {
......@@ -113,7 +113,7 @@ class WebPage extends StatelessWidget {
Navigator.pop(ctx);
ctx.read<WebCubit>().goMiniProgram();
},
),
),*/
ListTile(
leading: const Icon(Icons.refresh),
title: const Text('刷新'),
......
......@@ -10,22 +10,23 @@ class LoginPageAgreedWidget extends StatelessWidget {
children: [
TextSpan(
text: '同意',
style: TextStyle(color: Color(0xFF999999), fontSize: 14),
style: TextStyle(color: Color(0xFF999999), fontSize: 12),
),
TextSpan(
text: '《隐私保障》',
style: TextStyle(color: Color(0xFF7691FA), fontSize: 14),
style: TextStyle(color: Color(0xFF7691FA), fontSize: 12),
),
TextSpan(
text: '和',
style: TextStyle(color: Color(0xFF999999), fontSize: 14),
style: TextStyle(color: Color(0xFF999999), fontSize: 12),
),
TextSpan(
text: '《用户协议》',
style: TextStyle(color: Color(0xFF7691FA), fontSize: 14),
style: TextStyle(color: Color(0xFF7691FA), fontSize: 12),
),
],
),
strutStyle: StrutStyle(height: 13.5/12), // 设置行高为13.5
);
}
}
......@@ -97,3 +97,4 @@ flutter:
# 最好精确到子文件夹
# - assets/dist.zip <-- 确认 zip 文件是否必须在运行时解压,这会增加包体积
- assets/images/login/
- assets/images/login_v2/
\ No newline at end of file
Styling with Markdown is supported
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!