461 lines
		
	
	
		
			17 KiB
		
	
	
	
		
			Dart
		
	
	
			
		
		
	
	
			461 lines
		
	
	
		
			17 KiB
		
	
	
	
		
			Dart
		
	
	
| 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/toast_util.dart';
 | ||
| import 'package:qhd_prevention/pages/mine/mine_set_pwd_page.dart';
 | ||
| import 'package:qhd_prevention/services/auth_service.dart';
 | ||
| import 'package:qhd_prevention/tools/tools.dart';
 | ||
| import 'package:qhd_prevention/tools/update/update_dialogs.dart';
 | ||
| import 'package:shared_preferences/shared_preferences.dart';
 | ||
| import '../tools/tools.dart';
 | ||
| import 'main_tab.dart';
 | ||
| import 'mine/webViewPage.dart';
 | ||
| 
 | ||
| void main() => runApp(const MyApp());
 | ||
| 
 | ||
| class MyApp extends StatelessWidget {
 | ||
|   const MyApp({super.key});
 | ||
| 
 | ||
|   @override
 | ||
|   Widget build(BuildContext context) {
 | ||
|     return MaterialApp(
 | ||
|       title: '登录页面',
 | ||
|       theme: ThemeData(
 | ||
|         primarySwatch: Colors.blue,
 | ||
|         inputDecorationTheme: const InputDecorationTheme(
 | ||
|           border: InputBorder.none,
 | ||
|           contentPadding: EdgeInsets.symmetric(horizontal: 8),
 | ||
|         ),
 | ||
|       ),
 | ||
|       home: const LoginPage(),
 | ||
|       debugShowCheckedModeBanner: false,
 | ||
|     );
 | ||
|   }
 | ||
| }
 | ||
| 
 | ||
| class LoginPage extends StatefulWidget {
 | ||
|   const LoginPage({super.key});
 | ||
| 
 | ||
|   @override
 | ||
|   _LoginPageState createState() => _LoginPageState();
 | ||
| }
 | ||
| 
 | ||
| class _LoginPageState extends State<LoginPage> {
 | ||
|   final TextEditingController _phoneController = TextEditingController(
 | ||
|     // text: '13293211008',
 | ||
|   );
 | ||
|   final TextEditingController _passwordController = TextEditingController(
 | ||
|     // text: 'Zsaq@123456',
 | ||
|   );
 | ||
|   final GlobalKey<FormState> _formKey = GlobalKey<FormState>();
 | ||
|   String _errorMessage = '';
 | ||
|   bool _isLoading = false;
 | ||
|   bool _obscurePassword = true;
 | ||
|   bool _agreed = false;
 | ||
| 
 | ||
|   @override
 | ||
|   void initState() {
 | ||
|     super.initState();
 | ||
|     _phoneController.addListener(_onTextChanged);
 | ||
| 
 | ||
| 
 | ||
|       _getData();
 | ||
|     // _phoneController.text= SessionService.instance.loginPhone ?? "";
 | ||
|     // _passwordController.text= SessionService.instance.loginPass?? "";
 | ||
| 
 | ||
|     _checkUpdata();
 | ||
|   }
 | ||
| 
 | ||
| 
 | ||
|   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 dispose() {
 | ||
|     _phoneController.removeListener(_onTextChanged);
 | ||
|     _phoneController.dispose();
 | ||
|     super.dispose();
 | ||
|   }
 | ||
|   void _onTextChanged() {
 | ||
|     setState(() {}); // 文本变化时刷新UI
 | ||
|   }
 | ||
| 
 | ||
|   Future<void> _checkUpdata() async {
 | ||
|     try{
 | ||
|       final result = await AuthService.checkUpdate();
 | ||
|       if (FormUtils.hasValue(result, 'pd')) {
 | ||
|         Map pd = result['pd'];
 | ||
|         final versionInfo = await getAppVersion();
 | ||
|         bool isWifi = true;
 | ||
|         if (versionInfo.versionName != pd['VERSION']) {
 | ||
|           //有更新 提示更新
 | ||
|           final ok = await CustomAlertDialog.showConfirm(
 | ||
|             context,
 | ||
|               barrierDismissible:false,
 | ||
|             title: '更新通知',
 | ||
|             content: isWifi ? '发现新版本,是否更新?为了更好的体验,请更新到最新版本。' : '发现新版本,检查到您当前使用的是移动网络,是否更新?更新时请注意流量消耗。为了更好的体验,请更新到最新版本。',
 | ||
|             cancelText: pd['ISUPDATE'] == '1' ? '' : '稍后更新',
 | ||
|             confirmText: '立即更新'
 | ||
|           );
 | ||
|           if (ok) {
 | ||
|             if (Platform.isIOS) {
 | ||
|               openAppStore();
 | ||
|             }else{
 | ||
|               final apkUrl = pd['FILEURL'] ?? '';
 | ||
|               await showUpdateConfirm(context, apkUrl: apkUrl);
 | ||
|             }
 | ||
|           }
 | ||
|           return;
 | ||
|         }
 | ||
| 
 | ||
|       }
 | ||
|     }catch(_) {}
 | ||
| 
 | ||
|   }
 | ||
| 
 | ||
|   @override
 | ||
|   Widget build(BuildContext context) {
 | ||
|     return Scaffold(
 | ||
|       resizeToAvoidBottomInset: false,
 | ||
|       body: Container(
 | ||
|         width: double.infinity,
 | ||
|         height: double.infinity,
 | ||
|         decoration: const BoxDecoration(
 | ||
|           image: DecorationImage(
 | ||
|             image: AssetImage('assets/images/bg-login.png'),
 | ||
|             fit: BoxFit.cover,
 | ||
|           ),
 | ||
|         ),
 | ||
|         child: Form(
 | ||
|           key: _formKey,
 | ||
|           child: Stack(
 | ||
|             children: [
 | ||
|               Positioned.fill(
 | ||
|                 child: Image.asset(
 | ||
|                   'assets/images/bg-login.png',
 | ||
|                   fit: BoxFit.cover,
 | ||
|                 ),
 | ||
|               ),
 | ||
|               Padding(
 | ||
|                 padding: const EdgeInsets.symmetric(horizontal: 30),
 | ||
|                 // child: SingleChildScrollView(
 | ||
|                   child: Column(
 | ||
|                     children: [
 | ||
|                       const SizedBox(height: 70),
 | ||
|                       Row(
 | ||
|                         children: [
 | ||
|                           Image.asset('assets/image/logo.png', height: 50),
 | ||
|                           const SizedBox(width: 15),
 | ||
|                           const Expanded(
 | ||
|                             child: Text(
 | ||
|                               '欢迎使用,\n智守安全云平台!',
 | ||
|                               style: TextStyle(
 | ||
|                                 fontSize: 20,
 | ||
|                                 color: Colors.white,
 | ||
|                                 fontWeight: FontWeight.bold,
 | ||
|                               ),
 | ||
|                             ),
 | ||
|                           ),
 | ||
|                         ],
 | ||
|                       ),
 | ||
|                       const SizedBox(height: 50),
 | ||
|                       _buildInputSection(
 | ||
|                         label: "手机号码",
 | ||
|                         controller: _phoneController,
 | ||
|                         hintText: "请输入手机号...",
 | ||
|                         keyboardType: TextInputType.phone,
 | ||
|                         validator: (value) {
 | ||
|                           if (value == null || value.isEmpty) return '请输入手机号';
 | ||
|                           // if (!RegExp(r'^1[3-9]\d{9}\$').hasMatch(value))
 | ||
|                           //   return '请输入有效的手机号';
 | ||
|                           return null;
 | ||
|                         },
 | ||
|                         suffixIcon: _phoneController.text.isEmpty
 | ||
|                             ? const SizedBox(height: 20,)
 | ||
|                             : IconButton(
 | ||
|                           icon: Icon(Icons.cancel, size: 20,color: Colors.white,),
 | ||
|                           onPressed: () => _phoneController.clear(),
 | ||
|                         ),
 | ||
|                       ),
 | ||
| 
 | ||
|                       _buildInputSection(
 | ||
|                         label: "密码",
 | ||
|                         controller: _passwordController,
 | ||
|                         hintText: "请输入密码...",
 | ||
|                         obscureText: _obscurePassword,
 | ||
|                         suffixIcon: IconButton(
 | ||
|                           icon: Icon(
 | ||
|                             _obscurePassword
 | ||
|                                 ? Icons.visibility_off
 | ||
|                                 : Icons.visibility,
 | ||
|                             color: Colors.white,
 | ||
|                           ),
 | ||
|                           onPressed:
 | ||
|                               () => setState(
 | ||
|                                 () => _obscurePassword = !_obscurePassword,
 | ||
|                               ),
 | ||
|                         ),
 | ||
|                         validator: (value) {
 | ||
|                           if (value == null || value.isEmpty) return '请输入密码';
 | ||
|                           return null;
 | ||
|                         },
 | ||
|                       ),
 | ||
| 
 | ||
|                       const SizedBox(height: 30),
 | ||
|                       if (_errorMessage.isNotEmpty)
 | ||
|                         Padding(
 | ||
|                           padding: const EdgeInsets.symmetric(horizontal: 25),
 | ||
|                           child: Text(
 | ||
|                             _errorMessage,
 | ||
|                             style: const TextStyle(color: Colors.blue),
 | ||
|                           ),
 | ||
|                         ),
 | ||
| 
 | ||
| 
 | ||
|                       Padding(
 | ||
|                         padding: const EdgeInsets.symmetric(vertical: 0),
 | ||
|                         child: SizedBox(
 | ||
|                           width: double.infinity,
 | ||
|                           height: 48,
 | ||
|                           child: ElevatedButton(
 | ||
|                             onPressed:
 | ||
|                             _handleLogin,
 | ||
|                                 // (!_isLoading && _agreed) ? _handleLogin : null,
 | ||
|                             style: ElevatedButton.styleFrom(
 | ||
|                               backgroundColor: Colors.blue,
 | ||
|                               shape: RoundedRectangleBorder(
 | ||
|                                 borderRadius: BorderRadius.circular(8),
 | ||
|                               ),
 | ||
|                             ),
 | ||
|                             child: const Text(
 | ||
|                               '登录',
 | ||
|                               style: TextStyle(
 | ||
|                                 fontSize: 18,
 | ||
|                                 color: Colors.white,
 | ||
|                                 fontWeight: FontWeight.bold,
 | ||
|                               ),
 | ||
|                             ),
 | ||
|                           ),
 | ||
|                         ),
 | ||
|                       ),
 | ||
| 
 | ||
|                       Spacer(),
 | ||
| 
 | ||
|                       Padding(
 | ||
|                         padding: const EdgeInsets.only(bottom: 10),
 | ||
|                         child: Center( // 关键:把 Row 放到 Center 中
 | ||
|                           child: Row(
 | ||
|                             mainAxisSize: MainAxisSize.min, // Row 根据子项宽度自适应,不再铺满父宽度
 | ||
|                             crossAxisAlignment: CrossAxisAlignment.center,
 | ||
|                             children: [
 | ||
|                               Checkbox(
 | ||
|                                 value: _agreed,
 | ||
|                                 activeColor: Colors.white,
 | ||
|                                 checkColor: Colors.blueAccent,
 | ||
|                                 side: const BorderSide(color: Colors.white),
 | ||
|                                 onChanged: (value) {
 | ||
|                                   setState(() {
 | ||
|                                     _agreed = value ?? false;
 | ||
|                                   });
 | ||
|                                 },
 | ||
|                               ),
 | ||
| 
 | ||
|                               // 用 Flexible 而非 Expanded,且给 RichText 一个居中对齐
 | ||
|                               Flexible(
 | ||
|                                 fit: FlexFit.loose,
 | ||
|                                 child: RichText(
 | ||
|                                   textAlign: TextAlign.center, // 使文本在它的可用宽度内居中
 | ||
|                                   text: TextSpan(
 | ||
|                                     children: [
 | ||
|                                       const TextSpan(
 | ||
|                                         text: '我已阅读并同意',
 | ||
|                                         style: TextStyle(
 | ||
|                                           color: Colors.white,
 | ||
|                                           fontSize: 12,
 | ||
|                                         ),
 | ||
|                                       ),
 | ||
|                                       TextSpan(
 | ||
|                                         text: '《服务协议》',
 | ||
|                                         style: const TextStyle(
 | ||
|                                           color: Color(0xFF0D1D8C),
 | ||
|                                           fontSize: 12,
 | ||
|                                         ),
 | ||
|                                         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.white,
 | ||
|                                           fontSize: 12,
 | ||
|                                         ),
 | ||
|                                       ),
 | ||
|                                       TextSpan(
 | ||
|                                         text: '《隐私政策》',
 | ||
|                                         style: const TextStyle(
 | ||
|                                           color: Color(0xFF0D1D8C),
 | ||
|                                           fontSize: 12,
 | ||
|                                         ),
 | ||
|                                         recognizer: TapGestureRecognizer()
 | ||
|                                           ..onTap = () {
 | ||
|                                             pushPage(
 | ||
|                                               const WebViewPage(
 | ||
|                                                 name: "隐私政策",
 | ||
|                                                 url: 'http://47.92.102.56:7811/file/xieyi/zsysq.htm',
 | ||
|                                               ),
 | ||
|                                               context,
 | ||
|                                             );
 | ||
|                                           },
 | ||
|                                       ),
 | ||
|                                     ],
 | ||
|                                   ),
 | ||
|                                 ),
 | ||
|                               ),
 | ||
|                             ],
 | ||
|                           ),
 | ||
|                         ),
 | ||
|                       ),
 | ||
| 
 | ||
|                     ],
 | ||
|                   ),
 | ||
|                 // ),
 | ||
|               ),
 | ||
|             ],
 | ||
|           ),
 | ||
|         ),
 | ||
|       ),
 | ||
|     );
 | ||
|   }
 | ||
| 
 | ||
|   Widget _buildInputSection({
 | ||
|     required String label,
 | ||
|     required TextEditingController controller,
 | ||
|     required String hintText,
 | ||
|     bool obscureText = false,
 | ||
|     TextInputType keyboardType = TextInputType.text,
 | ||
|     Widget? suffixIcon,
 | ||
|     String? Function(String?)? validator,
 | ||
|   }) {
 | ||
|     return Padding(
 | ||
|       padding: const EdgeInsets.symmetric(vertical: 12),
 | ||
|       child: Column(
 | ||
|         crossAxisAlignment: CrossAxisAlignment.start,
 | ||
|         children: [
 | ||
|           Text(
 | ||
|             label,
 | ||
|             style: const TextStyle(
 | ||
|               fontSize: 15,
 | ||
|               fontWeight: FontWeight.w500,
 | ||
|               color: Colors.white,
 | ||
|             ),
 | ||
|           ),
 | ||
|           const SizedBox(height: 0),
 | ||
|           TextFormField(
 | ||
|             controller: controller,
 | ||
|             obscureText: obscureText,
 | ||
|             keyboardType: keyboardType,
 | ||
|             validator: validator,
 | ||
|             textAlignVertical: TextAlignVertical.center,
 | ||
|             decoration: InputDecoration(
 | ||
|               hintText: hintText,
 | ||
|               hintStyle: const TextStyle(color: Colors.white70),
 | ||
|               suffixIcon: suffixIcon,
 | ||
|               isDense: true,
 | ||
|               contentPadding: EdgeInsets.zero,
 | ||
|             ),
 | ||
|             style: const TextStyle(color: Colors.white),
 | ||
|           ),
 | ||
|           const Divider(
 | ||
|             height: 1,
 | ||
|             thickness: 1,
 | ||
|             color: Colors.white70,
 | ||
|           ),
 | ||
|         ],
 | ||
|       ),
 | ||
|     );
 | ||
|   }
 | ||
| 
 | ||
|   Future<void> _handleLogin() async {
 | ||
|     if(_isLoading){
 | ||
|       return;
 | ||
|     }
 | ||
|     if (!(_formKey.currentState?.validate() ?? false)) return;
 | ||
| 
 | ||
|     if(!_agreed){
 | ||
|       ToastUtil.showNormal(context, "请先阅读并同意《服务协议》和《隐私政策》");
 | ||
|       return;
 | ||
|     }
 | ||
| 
 | ||
| 
 | ||
|     final userName = _phoneController.text.trim();
 | ||
|     final userPwd = _passwordController.text;
 | ||
| 
 | ||
|     _saveData(userName,userPwd);
 | ||
|     // SessionService.instance.setSavePhone(userName);
 | ||
|     // SessionService.instance.setSavePass(userPwd);
 | ||
| 
 | ||
|     setState(() => _isLoading = true);
 | ||
|     showDialog(
 | ||
|       context: context,
 | ||
|       barrierDismissible: false,
 | ||
|       builder: (_) => const Center(child: CircularProgressIndicator()),
 | ||
|     );
 | ||
| 
 | ||
|     try {
 | ||
|       final data  = await AuthService.login(userName, userPwd);
 | ||
| 
 | ||
|       Navigator.of(context).pop(); // 关loading
 | ||
|       setState(() => _isLoading = false);
 | ||
| 
 | ||
|       if(data.isEmpty){
 | ||
|         return;
 | ||
|       }
 | ||
| 
 | ||
|       if (data['result'] == 'success') {
 | ||
|         if(FormUtils.hasValue(data, 'WEAK_PASSWORD') && data['WEAK_PASSWORD'] == '1'){
 | ||
|           pushPage(const MineSetPwdPage("1"), context);
 | ||
| 
 | ||
|         }else if(FormUtils.hasValue(data, 'LONG_TERM_PASSWORD_NOT_CHANGED') && data['LONG_TERM_PASSWORD_NOT_CHANGED']=='1'){
 | ||
|           pushPage(const MineSetPwdPage("2"), context);
 | ||
| 
 | ||
|         }else{
 | ||
|           Navigator.pushReplacement(
 | ||
|             context,
 | ||
|             MaterialPageRoute(builder: (_) => const MainPage()),
 | ||
|           );
 | ||
|         }
 | ||
| 
 | ||
|       }
 | ||
|     } catch (e) {
 | ||
|       Navigator.of(context).pop();
 | ||
|       setState(() => _isLoading = false);
 | ||
|       Fluttertoast.showToast(msg: '登录失败: $e');
 | ||
|     }
 | ||
|   }
 | ||
| }
 |