QinGang_interested/lib/pages/user/login_page.dart

625 lines
26 KiB
Dart
Raw Blame History

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

import 'dart:convert';
import 'dart:io';
import 'package:qhd_prevention/customWidget/custom_alert_dialog.dart';
import 'package:flutter/gestures.dart';
import 'package:flutter/material.dart';
import 'package:fluttertoast/fluttertoast.dart';
import 'package:qhd_prevention/customWidget/custom_button.dart';
import 'package:qhd_prevention/customWidget/toast_util.dart';
import 'package:qhd_prevention/http/ApiService.dart';
import 'package:qhd_prevention/pages/mine/forgot_pwd_page.dart';
import 'package:qhd_prevention/pages/mine/mine_set_pwd_page.dart';
import 'package:qhd_prevention/pages/mine/webViewPage.dart';
import 'package:qhd_prevention/pages/user/CustomInput.dart';
import 'package:qhd_prevention/pages/user/choose_userFirm_page.dart';
import 'package:qhd_prevention/pages/user/full_userinfo_page.dart';
import 'package:qhd_prevention/pages/user/register_page.dart';
import 'package:qhd_prevention/services/auth_service.dart';
import 'package:qhd_prevention/tools/tools.dart';
import 'package:shared_preferences/shared_preferences.dart';
import 'package:qhd_prevention/pages/main_tab.dart';
import 'package:qhd_prevention/services/SessionService.dart';
class LoginPage extends StatefulWidget {
const LoginPage({super.key});
@override
_LoginPageState createState() => _LoginPageState();
}
class _LoginPageState extends State<LoginPage> {
final TextEditingController _phoneController = TextEditingController();
final TextEditingController _passwordController = TextEditingController();
final TextEditingController _codeController = TextEditingController();
final GlobalKey<FormState> _formKey = GlobalKey<FormState>();
final FocusNode _phoneFocusNode = FocusNode();
final FocusNode _passwordFocusNode = FocusNode();
final FocusNode _codeFocusNode = FocusNode();
String _errorMessage = '';
bool _isLoading = false;
bool _obscurePassword = true;
bool _agreed = false;
String _captchaImageBase64 = '';
String _captchaIdentifier = '';
bool _rememberPassword = true;
@override
void initState() {
super.initState();
_phoneController.addListener(_onTextChanged);
_getData();
_getCaptcha();
_phoneController.text = SessionService.instance.loginPhone ?? "";
_passwordController.text = SessionService.instance.loginPass ?? "";
}
@override
void dispose() {
_phoneController.removeListener(_onTextChanged);
_phoneController.dispose();
_passwordController.dispose();
_codeController.dispose();
_phoneFocusNode.dispose();
_passwordFocusNode.dispose();
_codeFocusNode.dispose();
super.dispose();
}
Future<void> _getCaptcha() async {
try{
final response = await AuthApi.getUserCaptcha();
if (response['success']) {
setState(() {
_captchaImageBase64 = response['data']['img'];
_captchaIdentifier = response['data']['captchaKey'];
});
}
}catch(e){
print(e);
}
}
Future<void> _getData() async {
final prefs = await SharedPreferences.getInstance();
setState(() {
_phoneController.text = prefs.getString('savePhone') ?? '';
_passwordController.text = prefs.getString('savePass') ?? '';
});
}
Future<void> _saveData(String phone, String pass) async {
final prefs = await SharedPreferences.getInstance();
await prefs.setString("savePhone", phone);
await prefs.setString("savePass", pass);
}
void _onTextChanged() {
setState(() {});
}
@override
Widget build(BuildContext context) {
final screenHeight = MediaQuery.of(context).size.height;
double height = 230.0;
return Scaffold(
backgroundColor: Colors.white,
resizeToAvoidBottomInset: true,
body: Stack(
children: [
// 背景图:铺满屏幕
Positioned(
left: 0,
top: 0,
right: 0,
child: Image.asset(
'assets/images/loginbg.png',
fit: BoxFit.fitWidth,
),
),
Positioned(
top: 0,
left: 20,
right: 0,
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const SizedBox(height: 70), // 顶部间距
Image.asset(
"assets/images/g_logo.png",
width: 40,
height: 40,
),
const SizedBox(height: 10),
const Text(
"欢迎使用",
style: TextStyle(
color: Colors.white,
fontSize: 23,
fontWeight: FontWeight.w500,
),
),
const Text(
"秦港-相关方安全管理平台",
style: TextStyle(
color: Colors.white,
fontSize: 23,
fontWeight: FontWeight.w500,
),
),
const SizedBox(height: 20),
],
),
),
Positioned(
bottom: 0,
left: 0,
right: 0,
top: height,
child: GestureDetector(
onTap: () => FocusScope.of(context).unfocus(),
child: SafeArea(
child: Form(
key: _formKey,
child: SingleChildScrollView(
// 让内容至少占满屏高,并且内容可以滚动
child: ConstrainedBox(
constraints: BoxConstraints(minHeight: screenHeight-height-50),
child: IntrinsicHeight(
child: Container(
padding: const EdgeInsets.symmetric(horizontal: 16),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
// 中间可滚动表单区域(左右内边距)
Container(
// color: Colors.white,
child: Padding(
padding: const EdgeInsets.symmetric(
horizontal: 20,
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.center,
children: [
// const SizedBox(height: 60),
CustomInput.buildInput(
_phoneController,
title: "手机号",
hint: "请输入您的手机号",
keyboardType: TextInputType.phone,
suffix:
_phoneController.text.isEmpty
? SizedBox()
: IconButton(
icon: const Icon(
Icons.cancel,
size: 20,
color: Colors.grey,
),
onPressed:
() => setState(
() =>
_phoneController
.clear(),
),
),
validator: (v) {
if (v == null || v.isEmpty)
return '请输入您的手机号';
return null;
},
),
const SizedBox(height: 20),
CustomInput.buildInput(
title: "密码",
_passwordController,
hint: "请输入您的密码",
obscure: _obscurePassword,
suffix: IconButton(
icon: Icon(
_obscurePassword
? Icons.visibility_off
: Icons.visibility,
color: Colors.grey,
),
onPressed:
() => setState(
() =>
_obscurePassword =
!_obscurePassword,
),
),
validator: (v) {
if (v == null || v.isEmpty)
return '请输入密码';
return null;
},
),
const SizedBox(height: 20),
Row(
mainAxisAlignment:
MainAxisAlignment.spaceBetween,
children: [
Text(
'验证码',
style: TextStyle(
fontSize: 17,
fontWeight: FontWeight.bold,
),
),
const SizedBox(),
],
),
// 验证码行
SizedBox(
height: 60,
child: Row(
children: [
Expanded(
child: Padding(
padding: const EdgeInsets.only(
left: 0,
right: 10,
),
child: TextFormField(
controller: _codeController,
focusNode: _codeFocusNode,
keyboardType:
TextInputType.number,
decoration: const InputDecoration(
hintText: '请输入验证码',
hintStyle: TextStyle(
color: Colors.black26,
),
border: InputBorder.none,
contentPadding: EdgeInsets.zero,
),
style: const TextStyle(
color: Colors.black,
),
),
),
),
Padding(
padding: const EdgeInsets.symmetric(
horizontal: 10,
),
child: _buildCaptchaImage(),
),
],
),
),
const Divider(),
const SizedBox(height: 10),
if (_errorMessage.isNotEmpty)
Padding(
padding: const EdgeInsets.symmetric(
horizontal: 25,
),
child: Text(
_errorMessage,
style: const TextStyle(
color: Colors.red,
),
),
),
_remenbemberPWDAndRegister(),
const SizedBox(height: 10),
CustomButton(
text: '登录',
backgroundColor: const Color(0xFF2A75F8),
height: 50,
textStyle: const TextStyle(
fontSize: 16,
fontWeight: FontWeight.w500,
),
borderRadius: 25,
onPressed: _handleLogin,
),
const SizedBox(height: 10),
CustomButton(
text: '注册',
height: 50,
textColor: Colors.black87,
textStyle: const TextStyle(
fontSize: 16,
fontWeight: FontWeight.w500,
),
backgroundColor: Color(0xFFF3F4F8),
borderRadius: 25,
onPressed: () {
pushPage(RegisterPage(), context);
},
),
const SizedBox(height: 20),
],
),
),
),
// 底部协议:固定在页面底部(不会被背景覆盖,因为在上层)
Padding(
padding: const EdgeInsets.symmetric(
horizontal: 10,
vertical: 10,
),
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
Checkbox(
value: _agreed,
activeColor: Colors.blue,
checkColor: Colors.white,
side: const BorderSide(color: Colors.grey),
onChanged: (value) {
setState(() {
_agreed = value ?? false;
});
},
),
Flexible(
child: RichText(
text: TextSpan(
children: [
const TextSpan(
text: '我已阅读并同意',
style: TextStyle(
color: Colors.black,
fontSize: 12,
),
),
TextSpan(
text: '《服务协议》',
style: const TextStyle(
color: Colors.blue,
fontSize: 12,
),
// 如果你用 recognizer请替换为你之前的 recognizer 变量
recognizer:
TapGestureRecognizer()
..onTap = () {
pushPage(
const WebViewPage(
name: "用户服务协议",
url:
'http://47.92.102.56:7811/file/xieyi/zsyhxy.htm',
),
context,
);
},
),
const TextSpan(
text: '',
style: TextStyle(
color: Colors.black,
fontSize: 12,
),
),
TextSpan(
text: '《隐私政策》',
style: const TextStyle(
color: Colors.blue,
fontSize: 12,
),
recognizer:
TapGestureRecognizer()
..onTap = () {
pushPage(
const WebViewPage(
name: "隐私政策",
url:
'http://47.92.102.56:7811/file/xieyi/zsysq.htm',
),
context,
);
},
),
],
),
),
),
],
),
),
const SizedBox(height: 10),
],
),
),
),
),
),
),
),
),
),
// 交互层:放在背景之上
],
),
);
}
Widget _remenbemberPWDAndRegister() {
return Padding(
padding: const EdgeInsets.symmetric(horizontal: 0),
child: Row(
children: [
// 左侧:记住密码
Expanded(
child: InkWell(
onTap:
() => setState(() => _rememberPassword = !_rememberPassword),
child: Row(
mainAxisSize: MainAxisSize.min,
children: [
Checkbox(
value: _rememberPassword,
onChanged:
(v) => setState(() => _rememberPassword = v ?? false),
activeColor: const Color(0xFF2A75F8),
side: const BorderSide(color: Colors.grey),
materialTapTargetSize: MaterialTapTargetSize.shrinkWrap,
),
const Text(
'记住密码',
style: TextStyle(fontSize: 14, color: Colors.black38),
),
],
),
),
),
TextButton(
onPressed: () {
pushPage(ForgotPwdPage('0'), context);
},
child: const Text(
'忘记密码?',
style: TextStyle(fontSize: 14, color: Colors.black38),
),
),
],
),
);
}
// 修改验证码图片构建方法
Widget _buildCaptchaImage() {
if (_captchaImageBase64.isEmpty) {
return Container(
width: 100,
height: 40,
decoration: BoxDecoration(
color: Colors.black26,
borderRadius: BorderRadius.circular(4),
),
child: const Center(
child: Text(
'加载中...',
style: TextStyle(color: Colors.grey, fontSize: 12),
),
),
);
}
return GestureDetector(
onTap: _getCaptcha,
child: Container(
width: 100,
height: 40,
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(4),
),
child: ClipRRect(
borderRadius: BorderRadius.circular(4),
child: Image.memory(
base64.decode(_captchaImageBase64),
fit: BoxFit.cover,
errorBuilder: (context, error, stackTrace) {
return const Center(
child: Text(
'加载失败',
style: TextStyle(color: Colors.grey, fontSize: 12),
),
);
},
),
),
),
);
}
Future<void> _handleLogin() async {
if (_isLoading) {
return;
}
if (!(_formKey.currentState?.validate() ?? false)) return;
if (_codeController.text.isEmpty) {
ToastUtil.showNormal(context, "请输入验证码");
return;
}
if (!_agreed) {
ToastUtil.showNormal(context, "请先阅读并同意《服务协议》和《隐私政策》");
return;
}
// _phoneController.text='18700000002';
// _passwordController.text='Aa@12345678';
// _phoneController.text='卓云企业1';
// _phoneController.text='ceshi36-220';
// _passwordController.text='Aa12345678';
final userName = _phoneController.text.trim();
final userPwd = _passwordController.text.trim();
_saveData(userName, userPwd);
setState(() => _isLoading = true);
Map params = {
'captchaCode': _codeController.text,
'captchaKey' : _captchaIdentifier,
};
LoadingDialogHelper.show();
try {
final data = await AuthService.login(params, userName, userPwd);
LoadingDialogHelper.hide();
_getCaptcha();
setState(() => _isLoading = false);
if (FormUtils.hasValue(data, 'success') &&
data['success']) { // 登录成功直接进入主页
Navigator.pushReplacement(
context,
MaterialPageRoute(
builder: (_) => const MainPage(isChooseFirm: true,)),
);
} else {
if (FormUtils.hasValue(data, 'isInfoComplete') &&
data['isInfoComplete'] == false) { // 如果还没有用户信息。需要先完善
Navigator.pushReplacement(
context,
MaterialPageRoute(
builder: (_) => const FullUserinfoPage(isEidt: true, isChooseFirm: false,)),
);
} else if (FormUtils.hasValue(data, 'isChooseFirm') &&
data['isChooseFirm'] == false) { // 多个企业,跳转选择
{ // 先不进行底座登录,选择企业
List firmList = data['firmList'];
if (firmList.isEmpty) {
Navigator.pushReplacement(
context,
MaterialPageRoute(
builder: (_) => const MainPage(isChooseFirm: false,)),
);
return;
}else{ // 跳转选择企业
Navigator.pushReplacement(
context,
MaterialPageRoute(
builder: (_) => ChooseUserfirmPage(firms: firmList, userName: data['userName'], password: data['password'],)),
);
}
}
}
if (data.isEmpty) {
return;
}
}
}catch (e) {
setState(() => _isLoading = false);
Fluttertoast.showToast(msg: '登录失败: $e');
}
}
}