QinGang_interested/lib/pages/user/login_page.dart

625 lines
26 KiB
Dart
Raw Normal View History

2025-12-12 09:11:30 +08:00
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 {
2025-12-24 16:07:53 +08:00
try{
final response = await AuthApi.getUserCaptcha();
if (response['success']) {
setState(() {
_captchaImageBase64 = response['data']['img'];
_captchaIdentifier = response['data']['captchaKey'];
});
}
}catch(e){
print(e);
2025-12-12 09:11:30 +08:00
}
2025-12-24 16:07:53 +08:00
2025-12-12 09:11:30 +08:00
}
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(
2025-12-24 16:07:53 +08:00
"assets/images/g_logo.png",
2025-12-12 09:11:30 +08:00
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(
2025-12-24 16:07:53 +08:00
horizontal: 10,
2025-12-12 09:11:30 +08:00
),
child: _buildCaptchaImage(),
),
],
),
),
2025-12-24 16:07:53 +08:00
const Divider(),
2025-12-12 09:11:30 +08:00
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');
}
}
}