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 { final TextEditingController _phoneController = TextEditingController(); final TextEditingController _passwordController = TextEditingController(); final TextEditingController _codeController = TextEditingController(); final GlobalKey _formKey = GlobalKey(); 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 _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 _getData() async { final prefs = await SharedPreferences.getInstance(); setState(() { _phoneController.text = prefs.getString('savePhone') ?? ''; _passwordController.text = prefs.getString('savePass') ?? ''; }); } Future _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 _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'); } } }