625 lines
26 KiB
Dart
625 lines
26 KiB
Dart
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');
|
||
}
|
||
}
|
||
}
|