| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359 |
- import 'package:flutter/material.dart';
- import 'package:flutter_riverpod/flutter_riverpod.dart';
- import 'package:go_router/go_router.dart';
- import 'package:sino_med_cloud/l10n/app_localizations.dart';
- class LoginPage extends ConsumerStatefulWidget {
- const LoginPage({super.key});
- @override
- ConsumerState<LoginPage> createState() => _LoginPageState();
- }
- class _LoginPageState extends ConsumerState<LoginPage>
- with SingleTickerProviderStateMixin {
- late TabController _tabController;
- final _passwordFormKey = GlobalKey<FormState>();
- final _smsFormKey = GlobalKey<FormState>();
- // 密码登录表单
- final _phoneController = TextEditingController();
- final _passwordController = TextEditingController();
- bool _obscurePassword = true;
- // 验证码登录表单
- final _phoneSmsController = TextEditingController();
- final _smsCodeController = TextEditingController();
- int _countdown = 0;
- @override
- void initState() {
- super.initState();
- _tabController = TabController(length: 2, vsync: this);
- }
- @override
- void dispose() {
- _tabController.dispose();
- _phoneController.dispose();
- _passwordController.dispose();
- _phoneSmsController.dispose();
- _smsCodeController.dispose();
- super.dispose();
- }
- // 发送验证码
- void _sendSmsCode() {
- final l10n = AppLocalizations.of(context)!;
- if (_phoneSmsController.text.isEmpty) {
- ScaffoldMessenger.of(context).showSnackBar(
- SnackBar(content: Text(l10n.phoneNumberRequiredForSms)),
- );
- return;
- }
- // TODO: 调用发送验证码接口
- setState(() {
- _countdown = 60;
- });
- // 倒计时
- Future.doWhile(() async {
- await Future.delayed(const Duration(seconds: 1));
- if (mounted) {
- setState(() {
- _countdown--;
- });
- }
- return _countdown > 0;
- });
- }
- // 密码登录
- void _handlePasswordLogin() {
- final l10n = AppLocalizations.of(context)!;
- if (_passwordFormKey.currentState!.validate()) {
- // TODO: 调用登录接口
- // ScaffoldMessenger.of(context).showSnackBar(
- // SnackBar(content: Text(l10n.loginNotImplemented)),
- // );
- context.go('/mainTab');
- }
- }
- // 验证码登录
- void _handleSmsLogin() {
- final l10n = AppLocalizations.of(context)!;
- if (_smsFormKey.currentState!.validate()) {
- // TODO: 调用登录接口
- // ScaffoldMessenger.of(context).showSnackBar(
- // SnackBar(content: Text(l10n.loginNotImplemented)),
- // );
- context.go('/mainTab');
- }
- }
- @override
- Widget build(BuildContext context) {
- final l10n = AppLocalizations.of(context)!;
- return Scaffold(
- body: GestureDetector(
- onTap: () {
- // 点击空白区域时收起键盘并移除焦点
- FocusScope.of(context).unfocus();
- },
- behavior: HitTestBehavior.opaque,
- child: SafeArea(
- child: SingleChildScrollView(
- padding: const EdgeInsets.all(24),
- child: Column(
- crossAxisAlignment: CrossAxisAlignment.stretch,
- children: [
- const SizedBox(height: 40),
- // Logo 或标题
- Text(
- l10n.appName,
- style: const TextStyle(
- fontSize: 32,
- fontWeight: FontWeight.bold,
- color: Color(0xFF1F2937),
- ),
- textAlign: TextAlign.center,
- ),
- const SizedBox(height: 8),
- Text(
- l10n.appSubtitle,
- style: const TextStyle(
- fontSize: 16,
- color: Color(0xFF6B7280),
- ),
- textAlign: TextAlign.center,
- ),
- const SizedBox(height: 48),
- // Tab 切换
- Container(
- decoration: BoxDecoration(
- color: Colors.white,
- borderRadius: BorderRadius.circular(12),
- ),
- child: TabBar(
- controller: _tabController,
- indicator: BoxDecoration(
- borderRadius: BorderRadius.circular(12),
- color: const Color(0xFF00BFA5),
- ),
- indicatorSize: TabBarIndicatorSize.tab,
- dividerColor: Colors.transparent,
- labelColor: Colors.white,
- unselectedLabelColor: const Color(0xFF6B7280),
- labelStyle: const TextStyle(
- fontSize: 16,
- fontWeight: FontWeight.w600,
- ),
- unselectedLabelStyle: const TextStyle(
- fontSize: 16,
- fontWeight: FontWeight.w500,
- ),
- tabs: [
- Tab(text: l10n.passwordLogin),
- Tab(text: l10n.smsLogin),
- ],
- ),
- ),
- const SizedBox(height: 24),
- // Tab 内容
- SizedBox(
- height: 400,
- child: TabBarView(
- controller: _tabController,
- children: [
- _buildPasswordLoginForm(),
- _buildSmsLoginForm(),
- ],
- ),
- ),
- ],
- ),
- ),
- ),
- ),
- );
- }
- // 密码登录表单
- Widget _buildPasswordLoginForm() {
- final l10n = AppLocalizations.of(context)!;
- return Form(
- key: _passwordFormKey,
- child: Column(
- crossAxisAlignment: CrossAxisAlignment.stretch,
- children: [
- const SizedBox(height: 4),
- // 手机号输入
- TextFormField(
- controller: _phoneController,
- keyboardType: TextInputType.phone,
- decoration: InputDecoration(
- labelText: l10n.phoneNumber,
- hintText: l10n.phoneNumberHint,
- prefixIcon: const Icon(Icons.phone_outlined),
- ),
- validator: (value) {
- if (value == null || value.isEmpty) {
- return l10n.phoneNumberRequired;
- }
- if (!RegExp(r'^1[3-9]\d{9}$').hasMatch(value)) {
- return l10n.phoneNumberInvalid;
- }
- return null;
- },
- ),
- const SizedBox(height: 16),
- // 密码输入
- TextFormField(
- controller: _passwordController,
- obscureText: _obscurePassword,
- decoration: InputDecoration(
- labelText: l10n.password,
- hintText: l10n.passwordHint,
- prefixIcon: const Icon(Icons.lock_outline),
- suffixIcon: IconButton(
- icon: Icon(
- _obscurePassword ? Icons.visibility_outlined : Icons.visibility_off_outlined,
- ),
- onPressed: () {
- setState(() {
- _obscurePassword = !_obscurePassword;
- });
- },
- ),
- ),
- validator: (value) {
- if (value == null || value.isEmpty) {
- return l10n.passwordRequired;
- }
- if (value.length < 6) {
- return l10n.passwordMinLength;
- }
- return null;
- },
- ),
- const SizedBox(height: 8),
- // 忘记密码
- Align(
- alignment: Alignment.centerRight,
- child: TextButton(
- onPressed: () {
- // TODO: 跳转到忘记密码页面
- ScaffoldMessenger.of(context).showSnackBar(
- SnackBar(content: Text(l10n.forgotPasswordNotImplemented)),
- );
- },
- child: Text(l10n.forgotPassword),
- ),
- ),
- const SizedBox(height: 24),
- // 登录按钮
- ElevatedButton(
- onPressed: _handlePasswordLogin,
- style: ElevatedButton.styleFrom(
- padding: const EdgeInsets.symmetric(vertical: 16),
- ),
- child: Text(l10n.login),
- ),
- ],
- ),
- );
- }
- // 验证码登录表单
- Widget _buildSmsLoginForm() {
- final l10n = AppLocalizations.of(context)!;
- return Form(
- key: _smsFormKey,
- child: Column(
- crossAxisAlignment: CrossAxisAlignment.stretch,
- children: [
- const SizedBox(height: 4),
- // 手机号输入
- TextFormField(
- controller: _phoneSmsController,
- keyboardType: TextInputType.phone,
- decoration: InputDecoration(
- labelText: l10n.phoneNumber,
- hintText: l10n.phoneNumberHint,
- prefixIcon: const Icon(Icons.phone_outlined),
- ),
- validator: (value) {
- if (value == null || value.isEmpty) {
- return l10n.phoneNumberRequired;
- }
- if (!RegExp(r'^1[3-9]\d{9}$').hasMatch(value)) {
- return l10n.phoneNumberInvalid;
- }
- return null;
- },
- ),
- const SizedBox(height: 16),
- // 验证码输入
- Row(
- children: [
- Expanded(
- child: TextFormField(
- controller: _smsCodeController,
- keyboardType: TextInputType.number,
- decoration: InputDecoration(
- labelText: l10n.smsCode,
- hintText: l10n.smsCodeHint,
- prefixIcon: const Icon(Icons.sms_outlined),
- ),
- validator: (value) {
- if (value == null || value.isEmpty) {
- return l10n.smsCodeRequired;
- }
- if (value.length != 6) {
- return l10n.smsCodeLength;
- }
- return null;
- },
- ),
- ),
- const SizedBox(width: 12),
- SizedBox(
- width: 100,
- child: ElevatedButton(
- onPressed: _countdown > 0 ? null : _sendSmsCode,
- style: ElevatedButton.styleFrom(
- padding: const EdgeInsets.symmetric(vertical: 16),
- backgroundColor: _countdown > 0
- ? const Color(0xFFE5E7EB)
- : const Color(0xFF00BFA5),
- ),
- child: Text(
- _countdown > 0
- ? l10n.smsCodeCountdown(_countdown)
- : l10n.getSmsCode,
- style: TextStyle(
- color: _countdown > 0
- ? const Color(0xFF6B7280)
- : Colors.white,
- ),
- ),
- ),
- ),
- ],
- ),
- const SizedBox(height: 24),
- // 登录按钮
- ElevatedButton(
- onPressed: _handleSmsLogin,
- style: ElevatedButton.styleFrom(
- padding: const EdgeInsets.symmetric(vertical: 16),
- ),
- child: Text(l10n.login),
- ),
- ],
- ),
- );
- }
- }
|