Commit 7bce65f2 by Administrator

Merge branch 'feature-2511-opt-update-test' of http://120.24.213.56/ethan/appfra…

…me into feature-2511-opt-update-test
2 parents 401282c4 1932b376
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();
}
}
......@@ -12,7 +12,6 @@ import 'package:appframe/services/im_service.dart';
import 'package:appframe/services/local_server_service.dart';
import 'package:appframe/services/player_service.dart';
import 'package:appframe/services/recorder_service.dart';
import 'package:appframe/utils/zip_util.dart';
import 'package:device_info_plus/device_info_plus.dart';
import 'package:dio/dio.dart';
import 'package:equatable/equatable.dart';
......@@ -87,8 +86,8 @@ class WebState extends Equatable {
this.chooseImageCmdMessage = '',
this.chooseVideoCmdFlag = false,
this.chooseVideoCmdMessage = '',
String? h5Version,
}) : this.h5Version = h5Version ?? getIt.get<SharedPreferences>().getString("h5_version") ?? Constant.h5Version;
this.h5Version = '',
});
WebState copyWith({
int? selectedIndex,
......@@ -193,35 +192,39 @@ class WebCubit extends Cubit<WebState> {
}
Future<void> _init() async {
// 当前使用的H5版本
var curVersion = getIt.get<SharedPreferences>().getString(Constant.h5VersionKey) ?? Constant.h5Version;
try {
// 获取版本信息
var versionConfig = await _getVersionConfig();
var correctVersion = versionConfig['version'] as String;
var configVersion = versionConfig['version'] as String;
var downloadUrl = versionConfig['zip'] as String;
var force = versionConfig['force'] as String;
// 当前使用的H5版本
var curVersion = getIt.get<SharedPreferences>().getString('h5_version') ?? Constant.h5Version;
// 版本不一致则需要升级
if (curVersion != correctVersion) {
// 需要强制升级时,一直等待下载完成
// 不需要强制升级时,异步下载,下载完后弹框提示用户进行确认操作
if (curVersion != configVersion) {
if (force == "1") {
// 一直等待升级完成
// 遮罩界面
emit(state.copyWith(isUpgrading: true));
await _upgrade(correctVersion, downloadUrl);
// 升级完成后取消遮罩,继续初始化其它数据
await _downloadH5Zip(configVersion, downloadUrl);
_setH5Version(configVersion);
// 下载完成后取消遮罩,继续初始化其它数据
emit(state.copyWith(isUpgrading: false));
} else {
// 后台下载,完成后提示用户
_upgrade(correctVersion, downloadUrl).then(
_downloadH5Zip(configVersion, downloadUrl).then(
(value) {
_setH5Version(configVersion);
emit(state.copyWith(suggestUpgrade: true));
},
);
}
}
} catch (e) {
emit(state.copyWith(isUpgrading: false));
print('升级检测处理失败');
print(e);
}
......@@ -238,6 +241,9 @@ class WebCubit extends Cubit<WebState> {
// 加载H5页面
_loadHtml();
// 读取 h5 版本号
_readH5ShowVersion();
// 登录IM
_loginIM();
......@@ -264,6 +270,7 @@ class WebCubit extends Cubit<WebState> {
String zip = response.data['zip'] as String;
return {
'version': version,
// 'force': "0",
'force': force,
// 'zip': 'http://192.168.2.177/1.0.0.zip',
'zip': '$zip$version.zip',
......@@ -273,7 +280,7 @@ class WebCubit extends Cubit<WebState> {
}
}
Future<void> _upgrade(String version, String zipUrl) async {
Future<void> _downloadH5Zip(String version, String zipUrl) async {
Dio dio = Dio();
try {
// 下载zip文件
......@@ -301,20 +308,15 @@ class WebCubit extends Cubit<WebState> {
// 删除临时文件
await tempZipFile.delete();
// 解压zip文件
String targetDir = '$httpDirPath/$version';
var result = await ZipUtil.extractZipFile(saveZipFilePath, targetDir);
if (!result) {
throw Exception('文件解压失败');
}
var sharedPreferences = await SharedPreferences.getInstance();
await sharedPreferences.setString('h5_version', version);
} finally {
dio.close(force: true);
}
}
void _setH5Version(String version) {
getIt.get<SharedPreferences>().setString(Constant.h5VersionKey, version);
}
Future<void> _startLocalServer() async {
// 启动本地服务器
_server = await getIt.get<LocalServerService>().startLocalServer();
......@@ -359,6 +361,11 @@ class WebCubit extends Cubit<WebState> {
_controller.loadRequest(Uri.parse(serverUrl));
}
void _readH5ShowVersion() {
var h5Version = getIt.get<SharedPreferences>().getString(Constant.h5ShowVersionKey) ?? 'unknown';
emit(state.copyWith(h5Version: h5Version));
}
Future<void> _loginIM() async {
if (Constant.needIM) {
var imService = getIt.get<ImService>();
......@@ -485,7 +492,7 @@ class WebCubit extends Cubit<WebState> {
// 1 清理非 h5_version 的缓存
var sharedPreferences = getIt.get<SharedPreferences>();
sharedPreferences.getKeys().forEach((key) async {
if (!key.startsWith('h5_version')) {
if (!key.startsWith('h5')) {
await sharedPreferences.remove(key);
}
});
......@@ -494,7 +501,7 @@ class WebCubit extends Cubit<WebState> {
var dir = await getApplicationSupportDirectory();
var httpDir = Directory('${dir.path}/${Constant.h5DistDir}');
if (httpDir.existsSync()) {
var version = sharedPreferences.getString('h5_version') ?? Constant.h5Version;
var version = sharedPreferences.getString(Constant.h5VersionKey) ?? Constant.h5Version;
await for (final FileSystemEntity entity in httpDir.list()) {
if (entity is Directory) {
......@@ -554,9 +561,9 @@ class WebCubit extends Cubit<WebState> {
///
/// 升级提示
///
void suggestUpgrade(BuildContext context) {
void suggestUpgrade(BuildContext ctx) {
showDialog(
context: context,
context: ctx,
barrierDismissible: false,
builder: (BuildContext context) {
return AlertDialog(
......@@ -566,18 +573,16 @@ class WebCubit extends Cubit<WebState> {
TextButton(
child: Text('取消'),
onPressed: () {
emit(state.copyWith(suggestUpgrade: false));
Navigator.of(context).pop();
emit(state.copyWith(suggestUpgrade: false));
},
),
TextButton(
child: Text('确定'),
onPressed: () {
emit(state.copyWith(suggestUpgrade: false));
getIt.get<LocalServerService>().resetHttpDirectory();
_controller.reload();
// _loadHtml();
Navigator.of(context).pop();
emit(state.copyWith(suggestUpgrade: false));
router.go('/reload');
},
),
],
......@@ -628,6 +633,14 @@ class WebCubit extends Cubit<WebState> {
}
void _chooseImageFromAlbum(BuildContext context, int count, String unique, String cmd) async {
// 检查是否已被永久拒绝,此时需要对用户进行引导
if (await _checkGalleryPermanentlyDenied()) {
var resp = {'unique': unique, 'cmd': cmd, 'data': null, 'errMsg': 'no auth'};
_sendResponse(resp);
_permissionLead(context, '相册权限');
return;
}
final List<AssetEntity>? result;
try {
result = await AssetPicker.pickAssets(
......@@ -643,12 +656,6 @@ class WebCubit extends Cubit<WebState> {
} catch (e) {
var resp = {'unique': unique, 'cmd': cmd, 'data': null, 'errMsg': 'no auth'};
_sendResponse(resp);
// 权限异常之后,检查是否已被永久拒绝,此时需要对用户进行引导
if (await _checkGalleryPermanentlyDenied()) {
_permissionLead(context, '相册权限');
}
return;
}
......@@ -675,17 +682,20 @@ class WebCubit extends Cubit<WebState> {
}
void _chooseImageFromCamera(BuildContext context, String unique, String cmd) async {
// 检查是否已被永久拒绝,此时需要对用户进行引导
if (await _checkCameraPermanentlyDenied()) {
var resp = {'unique': unique, 'cmd': cmd, 'data': null, 'errMsg': 'no auth'};
_sendResponse(resp);
_permissionLead(context, '相机权限');
return;
}
AssetEntity? asset;
try {
asset = await CameraPicker.pickFromCamera(context, pickerConfig: const CameraPickerConfig());
} catch (e) {
var resp = {'unique': unique, 'cmd': cmd, 'data': null, 'errMsg': 'no auth'};
_sendResponse(resp);
if (await _checkCameraPermanentlyDenied()) {
_permissionLead(context, '相机权限');
}
return;
}
......@@ -831,8 +841,15 @@ class WebCubit extends Cubit<WebState> {
}
void _chooseVideoFromAlbum(BuildContext context, int count, String unique, String cmd) async {
List<AssetEntity>? result;
// 检查是否已被永久拒绝,此时需要对用户进行引导
if (await _checkGalleryPermanentlyDenied()) {
var resp = {'unique': unique, 'cmd': cmd, 'data': null, 'errMsg': 'no auth'};
_sendResponse(resp);
_permissionLead(context, '相册权限');
return;
}
List<AssetEntity>? result;
try {
result = await AssetPicker.pickAssets(
context,
......@@ -845,12 +862,6 @@ class WebCubit extends Cubit<WebState> {
} catch (e) {
var resp = {'unique': unique, 'cmd': cmd, 'data': null, 'errMsg': 'no auth'};
_sendResponse(resp);
// 权限异常之后,检查是否已被永久拒绝,此时需要对用户进行引导
if (await _checkGalleryPermanentlyDenied()) {
_permissionLead(context, '相册权限');
}
return;
}
......@@ -877,6 +888,14 @@ class WebCubit extends Cubit<WebState> {
}
void _chooseVideoFromCamera(BuildContext context, int maxDuration, String unique, String cmd) async {
// 检查是否已被永久拒绝,此时需要对用户进行引导
if (await _checkCameraPermanentlyDenied()) {
var resp = {'unique': unique, 'cmd': cmd, 'data': null, 'errMsg': 'no auth'};
_sendResponse(resp);
_permissionLead(context, '相机权限');
return;
}
AssetEntity? asset;
try {
asset = await CameraPicker.pickFromCamera(
......@@ -891,11 +910,6 @@ class WebCubit extends Cubit<WebState> {
} catch (e) {
var resp = {'unique': unique, 'cmd': cmd, 'data': null, 'errMsg': 'no auth'};
_sendResponse(resp);
if (await _checkCameraPermanentlyDenied()) {
_permissionLead(context, '相机权限');
}
return;
}
......
import 'package:appframe/config/evn_config.dart';
class Constant {
/// 应用内部 http 服务
static const int localServerPort = 35982;
......@@ -19,11 +21,20 @@ class Constant {
/// app 版本号规则
static const String appVersion = '1.0.2512031';
// h5的起始终最低版本号规则
/// H5的起始终最低版本号规则
static const String h5Version = '1.0.0';
/// H5的版本号存储的key
static const String h5VersionKey = 'h5_version';
/// 用于显示的H5版本号存储的key
static const String h5ShowVersionKey = 'h5_show_version';
/// H5版本号配置文件地址
static const String configUrl = 'https://bxe-obs.banxiaoer.com/conf/xeapp_conf_dev.json';
static const String configUrl = EnvConfig.env == 'dev'
? 'https://bxe-obs.banxiaoer.com/conf/xeapp_conf_dev.json'
// ? 'http://192.168.2.177/xeapp_conf_dev.json'
: 'https://bxe-obs.banxiaoer.com/conf/xeapp_conf_pro.json';
/// 内部 H5 dist 目录
static const String h5DistDir = 'http_dist_assets';
......@@ -33,5 +44,5 @@ class Constant {
static const String imClientSecure = 'kM4yqbehB3io9UiLvH6eHvM7xAhfYxoyyaO1tLoHgKltcaI7MZXkUbpFaWdeQIqe';
/// 测试阶段使用
static const bool needIM = true;
static const bool needIM = false;
}
class EnvConfig {
static const String env = String.fromEnvironment('env', defaultValue: 'dev');
static bool isDev() {
return env == 'dev';
}
}
......@@ -3,6 +3,8 @@ 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';
import 'package:flutter/material.dart';
......@@ -42,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();
......@@ -53,5 +61,11 @@ final GoRouter router = GoRouter(
return const ImPage();
},
),
GoRoute(
path: '/reload',
builder: (BuildContext context, GoRouterState state) {
return const ReloadPage();
},
),
],
);
import 'package:appframe/config/constant.dart';
import 'package:appframe/config/locator.dart';
import 'package:appframe/services/dispatcher.dart';
import 'package:shared_preferences/shared_preferences.dart';
......@@ -59,7 +60,7 @@ class ClearStorageHandler extends MessageHandler {
Future<dynamic> handleMessage(dynamic params) async {
var sharedPreferences = getIt.get<SharedPreferences>();
sharedPreferences.getKeys().forEach((key) async {
if (!key.startsWith('h5_version')) {
if (!key.startsWith('h5_')) {
await sharedPreferences.remove(key);
}
});
......
......@@ -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;
}
}
......@@ -44,10 +44,6 @@ class LocalServerService {
return server;
}
void resetHttpDirectory() {
_httpDirectory = null;
}
// 目录下的文件
Future<void> _serveTempFile(HttpRequest request, String requestPath) async {
try {
......@@ -149,7 +145,7 @@ class LocalServerService {
}
Future<void> _initHttpDirectory() async {
var version = getIt.get<SharedPreferences>().getString('h5_version') ?? Constant.h5Version;
var version = getIt.get<SharedPreferences>().getString(Constant.h5VersionKey) ?? Constant.h5Version;
var direct = await getApplicationSupportDirectory();
_httpDirectory = '${direct.path}/${Constant.h5DistDir}/$version';
}
......@@ -163,12 +159,26 @@ class LocalServerService {
// }
// 判断H5打包文件是否存在,不存在则从assets中解压
var version = getIt.get<SharedPreferences>().getString('h5_version') ?? Constant.h5Version;
var version = getIt.get<SharedPreferences>().getString(Constant.h5VersionKey) ?? Constant.h5Version;
var dir = await getApplicationSupportDirectory();
var distFilePath = '${dir.path}/${Constant.h5DistDir}/$version.zip';
if (!File(distFilePath).existsSync()) {
distFilePath = 'assets/dist.zip';
}
// 解压
await ZipUtil.extractZipFile(distFilePath, outputDirectory);
// 用于显示的版本号
await _getAndSetShowVersion(outputDirectory);
}
// 读取和设置用于显示的版本号
Future<void> _getAndSetShowVersion(String outputDirectory) async {
var versionFile = File('$outputDirectory/version.txt');
if (await versionFile.exists()) {
var content = (await versionFile.readAsString()).trim();
getIt.get<SharedPreferences>().setString(Constant.h5ShowVersionKey, content);
} else {
getIt.get<SharedPreferences>().setString(Constant.h5ShowVersionKey, 'undefined');
}
}
}
......@@ -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),
),
),
],
),
),
],
),
);
}
}
import 'package:appframe/config/routes.dart';
import 'package:flutter/material.dart';
///
/// 用于重新加载的中间路由
///
class ReloadPage extends StatefulWidget {
const ReloadPage({super.key});
@override
State<ReloadPage> createState() => _ReloadPageState();
}
class _ReloadPageState extends State<ReloadPage> {
@override
void initState() {
super.initState();
WidgetsBinding.instance.addPostFrameCallback((_) {
// 界面显示完成后执行的操作
_performPostDisplayOperations();
});
}
void _performPostDisplayOperations() {
router.go('/web');
}
@override
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
const CircularProgressIndicator(),
const SizedBox(height: 16),
Text('加载中...'),
],
),
),
);
}
}
......@@ -269,8 +269,9 @@ class WebPage extends StatelessWidget {
child: Text('确认'),
onPressed: () {
Navigator.of(context).pop();
ctx.read<WebCubit>().clearStorage();
ctx.read<WebCubit>().goLogin();
var webCubit = ctx.read<WebCubit>();
webCubit.clearStorage();
webCubit.goLogin();
},
),
],
......
......@@ -26,7 +26,8 @@ dependencies:
path_provider: ^2.1.5
shared_preferences: ^2.5.3
uuid: ^4.5.1
crypto: ^3.0.7
# --- 路由与权限 ---
go_router: ^16.2.1
permission_handler: ^12.0.1
......@@ -95,4 +96,4 @@ flutter:
# 建议:不要直接引用 assets/ 根目录,这可能会导致打包进不必要的文件
# 最好精确到子文件夹
# - assets/dist.zip <-- 确认 zip 文件是否必须在运行时解压,这会增加包体积
- assets/images/login/
- assets/images/login/
\ 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!