Files
feature-tenant/lib/features/auth/presentation/pages/login_page.dart
2026-08-18 08:52:33 +08:00

323 lines
13 KiB
Dart
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import 'package:cc_ui_kit/cc_ui_kit.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:go_router/go_router.dart';
import 'package:maibu_satabot_v2/core/app/app_user_cubit.dart';
import 'package:maibu_satabot_v2/core/di/injection.dart';
import 'package:maibu_satabot_v2/core/theme/AppTheme.dart';
import 'package:maibu_satabot_v2/features/devices/presentation/bloc/devices_cubit.dart';
import '../../../../core/router/route_paths.dart';
import '../bloc/login_cubit.dart';
import '../bloc/login_state.dart';
class LoginPage extends StatefulWidget {
const LoginPage({super.key});
@override
State<LoginPage> createState() => _LoginPageState();
}
class _LoginPageState extends State<LoginPage> {
final _userCtrl = TextEditingController();
final _pwdCtrl = TextEditingController();
bool _isAgreed = false; // 协议勾选状态
bool _obscurePwd = true; // 密码可见性
@override
void dispose() {
_userCtrl.dispose();
_pwdCtrl.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: context.appColors.pageBackground,
// 保持 AppBar 简洁或直接去掉
appBar: AppBar(backgroundColor: context.appColors.pageBackground, elevation: 0),
body: BlocListener<LoginCubit, LoginState>(
listener: (context, state) {
debugPrint('🔍 [LOGIN] 登录状态变化: $state');
if (state is LoginSuccess) {
debugPrint('✅ [LOGIN] 登录成功,准备跳转到首页');
context.go(RoutePaths.home);
debugPrint('✅ [LOGIN] 已调用 context.go(RoutePaths.home)');
} else if (state is LoginFailure) {
// 可以在这里弹出简洁的黑白提示框
ScaffoldMessenger.of(context).showSnackBar(SnackBar(content: Text('登录失败,${state.message}')));
}
},
child: LayoutBuilder(
builder: (context, constraints) {
return SingleChildScrollView(
padding: const EdgeInsets.symmetric(horizontal: 20.0),
child: ConstrainedBox(
constraints: BoxConstraints(
minHeight: constraints.maxHeight,
),
child: IntrinsicHeight(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
// 顶部区域:图片 + 认证信息 + 标题(压缩空间)
const SizedBox(height: 10),
// 顶部居中图片和认证信息
Center(
child: Column(
children: [
Image.asset(
'assets/images/huawei.png',
width: 80,
height: 80,
fit: BoxFit.contain,
),
const SizedBox(height: 6),
Padding(
padding: const EdgeInsets.symmetric(horizontal: 20.0),
child: Text(
"通过华为云生态技术认证,请放心使用,证书编号C202605301",
textAlign: TextAlign.center,
style: TextStyle(
fontSize: 10,
color: context.appColors.textTertiary,
height: 1.3,
),
),
),
],
),
),
const SizedBox(height: 16),
Text(
"账号登录v1.1.5",
style: TextStyle(fontSize: 28, fontWeight: FontWeight.bold, color: context.appColors.textPrimary),
),
const SizedBox(height: 8),
Text("请填写以下信息以验证身份", style: TextStyle(fontSize: 15, color: context.appColors.textTertiary)),
const SizedBox(height: 24),
// 表单区域
// 账号输入框
_buildInputLabel("账号"),
TextField(
controller: _userCtrl,
style: TextStyle(fontSize: 14, color: context.appColors.textPrimary),
cursorColor: context.appColors.textPrimary,
decoration: _inputDecoration(hint: "请输入用户名"),
),
const SizedBox(height: 20),
// 密码输入框
_buildInputLabel("密码"),
TextField(
controller: _pwdCtrl,
obscureText: _obscurePwd,
style: TextStyle(fontSize: 14, color: context.appColors.textPrimary),
cursorColor: context.appColors.textPrimary,
decoration: _inputDecoration(
hint: "请输入密码",
suffixIcon: IconButton(
icon: Icon(_obscurePwd ? Icons.visibility_off_outlined : Icons.visibility_outlined, color: context.appColors.textTertiary, size: 20),
onPressed: () => setState(() => _obscurePwd = !_obscurePwd),
),
),
),
const SizedBox(height: 16),
// 协议勾选区域
_buildProtocolSection(),
const SizedBox(height: 30),
// 底部区域:登录按钮 + 注册链接
SizedBox(
width: double.infinity,
child: BlocBuilder<LoginCubit, LoginState>(
builder: (context, state) {
bool isLoading = state is LoginLoading;
return CCPrimaryButton(
onPressed: isLoading
? null
: () {
if (!_isAgreed) {
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(
content: Text('请先阅读并同意用户协议'),
duration: Duration(seconds: 1),
),
);
return;
}
_handleLogin();
},
text: isLoading ? '登录中...' : '登 录',
);
},
),
),
const SizedBox(height: 16),
Center(
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text(
"没有账号吗?",
style: TextStyle(fontSize: 14, color: context.appColors.textTertiary),
),
InkWell(
onTap: () {
debugPrint(' 点击了立即注册');
debugPrint('👉 跳转路径: ${RoutePaths.register}');
context.go(RoutePaths.register);
},
borderRadius: BorderRadius.circular(4),
child: Padding(
padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 4),
child: Text(
"立即注册",
style: TextStyle(
fontSize: 14,
color: context.appColors.textPrimary,
fontWeight: FontWeight.bold,
),
),
),
),
],
),
),
const SizedBox(height: 20),
],
),
),
),
);
},
),
),
);
}
// 执行登录逻辑
void _handleLogin() {
context.read<LoginCubit>().login(_userCtrl.text, _pwdCtrl.text, 2);
//final user = sl<AppUserCubit>().state.user;
//final devicesCubit = sl<DevicesCubit>();
//if (user != null) {
// devicesCubit.fetchAllDevices(user.username);
//}
}
// 辅助组件:输入框标签
Widget _buildInputLabel(String label) {
return Padding(
padding: const EdgeInsets.only(bottom: 8.0),
child: Text(label, style: TextStyle(fontSize: 14, fontWeight: FontWeight.w600, color: context.appColors.textSecondary)),
);
}
// 辅助组件:输入框装饰
InputDecoration _inputDecoration({required String hint, Widget? suffixIcon}) {
return InputDecoration(
hintText: hint,
hintStyle: TextStyle(color: context.appColors.textTertiary, fontSize: 14),
filled: true,
fillColor: context.appColors.fillBackground,
contentPadding: const EdgeInsets.symmetric(horizontal: 16, vertical: 16),
enabledBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(8),
borderSide: BorderSide(color: context.appColors.divider),
),
focusedBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(8),
borderSide: BorderSide(color: context.appColors.primary, width: 1),
),
suffixIcon: suffixIcon,
);
}
// 辅助组件:协议勾选
Widget _buildProtocolSection() {
return Row(
crossAxisAlignment: CrossAxisAlignment.center,
children: [
SizedBox(
height: 24,
width: 24,
child: Checkbox(
value: _isAgreed,
activeColor: context.appColors.primary,
checkColor: Colors.white,
side: BorderSide(color: context.appColors.divider),
onChanged: (val) => setState(() => _isAgreed = val!),
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(4)),
),
),
const SizedBox(width: 8),
Expanded(
child: Wrap(
children: [
Text("我已阅读并同意 ", style: TextStyle(fontSize: 13, color: context.appColors.textTertiary)),
_protocolText("《用户协议》", () => _showProtocolDetail("用户协议")),
Text(" 和 ", style: TextStyle(fontSize: 13, color: context.appColors.textTertiary)),
_protocolText("《隐私政策》", () => _showProtocolDetail("隐私政策")),
],
),
),
],
);
}
Widget _protocolText(String text, VoidCallback onTap) {
return GestureDetector(
onTap: onTap,
child: Text(
text,
style: TextStyle(fontSize: 13, color: context.appColors.textPrimary, fontWeight: FontWeight.bold),
),
);
}
void _showProtocolDetail(String title) {
showModalBottomSheet(
context: context,
isScrollControlled: true,
backgroundColor: context.appColors.cardBackground,
shape: const RoundedRectangleBorder(borderRadius: BorderRadius.vertical(top: Radius.circular(16))),
builder: (context) => Container(
padding: const EdgeInsets.all(24),
height: MediaQuery.of(context).size.height * 0.7,
child: Column(
children: [
Text(title, style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold, color: context.appColors.textPrimary)),
Divider(height: 32, color: context.appColors.divider),
Expanded(
child: SingleChildScrollView(
child: Text("此处放置您的${title}详细内容...\n" * 20, style: TextStyle(color: context.appColors.textSecondary, height: 1.5)),
),
),
const SizedBox(height: 16),
SizedBox(
width: double.infinity,
child: BlocBuilder<LoginCubit, LoginState>(
builder: (context, state) {
return CCPrimaryButton(onPressed: () => Navigator.pop(context), text: "我已了解");
},
),
),
],
),
),
);
}
}