Files
slogan/app/lib/features/auth/login_page.dart
T
2026-08-04 17:04:18 +08:00

172 lines
6.1 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:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:go_router/go_router.dart';
import '../../core/auth/auth_provider.dart';
import '../../core/config/app_config.dart';
import '../../shared/app_toast.dart';
class LoginPage extends ConsumerStatefulWidget {
const LoginPage({super.key});
@override
ConsumerState<LoginPage> createState() => _LoginPageState();
}
class _LoginPageState extends ConsumerState<LoginPage> {
final _accountCtrl = TextEditingController();
final _passwordCtrl = TextEditingController();
@override
void dispose() {
_accountCtrl.dispose();
_passwordCtrl.dispose();
super.dispose();
}
Future<void> _submit([String? acc, String? pwd]) async {
final account = (acc ?? _accountCtrl.text).trim();
final password = pwd ?? _passwordCtrl.text;
if (account.isEmpty || password.isEmpty) {
showToast('请输入账号和密码');
return;
}
await ref.read(authProvider.notifier).login(account, password);
final state = ref.read(authProvider);
if (state.hasError) {
showToast(state.error.toString().replaceFirst('Exception: ', ''));
return;
}
if (state.value?.authenticated == true && mounted) {
context.go('/home');
}
}
Future<void> _showRegisterDialog() async {
final account = TextEditingController();
final password = TextEditingController();
final name = TextEditingController();
final result = await showDialog<bool>(
context: context,
builder: (ctx) => AlertDialog(
title: const Text('注册新账号'),
content: Column(
mainAxisSize: MainAxisSize.min,
children: [
TextField(
controller: account,
decoration: const InputDecoration(labelText: '账号')),
TextField(
controller: password,
obscureText: true,
decoration: const InputDecoration(labelText: '密码(至少6位)')),
TextField(
controller: name,
decoration: const InputDecoration(labelText: '昵称')),
],
),
actions: [
TextButton(
onPressed: () => Navigator.pop(ctx, false),
child: const Text('取消')),
FilledButton(
onPressed: () => Navigator.pop(ctx, true),
child: const Text('注册')),
],
),
);
if (result != true) return;
try {
final api = ref.read(apiClientProvider);
await api.post<Map<String, dynamic>>('/user/register', {
'account': account.text.trim(),
'password': password.text,
'name': name.text.trim(),
});
showToast('注册成功,正在登录...');
_accountCtrl.text = account.text.trim();
_passwordCtrl.text = password.text;
await _submit();
} catch (e) {
showToast('注册失败:${e.toString().replaceFirst('Exception: ', '')}');
}
}
@override
Widget build(BuildContext context) {
final auth = ref.watch(authProvider);
final loading = auth.isLoading;
return Scaffold(
body: SafeArea(
child: Center(
child: SingleChildScrollView(
padding: const EdgeInsets.symmetric(horizontal: 32),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
Icon(Icons.face_retouching_natural,
size: 72, color: Theme.of(context).colorScheme.primary),
const SizedBox(height: 12),
const Text('我的形象穿搭',
textAlign: TextAlign.center,
style:
TextStyle(fontSize: 24, fontWeight: FontWeight.bold)),
const Text('拍出你的个人形象,生成专属穿搭方案',
textAlign: TextAlign.center,
style: TextStyle(color: Colors.grey, fontSize: 13)),
const SizedBox(height: 40),
TextField(
controller: _accountCtrl,
decoration: const InputDecoration(
labelText: '账号',
prefixIcon: Icon(Icons.person_outline),
border: OutlineInputBorder()),
),
const SizedBox(height: 16),
TextField(
controller: _passwordCtrl,
obscureText: true,
decoration: const InputDecoration(
labelText: '密码',
prefixIcon: Icon(Icons.lock_outline),
border: OutlineInputBorder()),
onSubmitted: (_) => _submit(),
),
const SizedBox(height: 32),
FilledButton(
onPressed: loading ? null : _submit,
style: FilledButton.styleFrom(
padding: const EdgeInsets.symmetric(vertical: 14)),
child: loading
? const SizedBox(
width: 20,
height: 20,
child: CircularProgressIndicator(strokeWidth: 2))
: const Text('登录'),
),
if (AppConfig.testAccount.isNotEmpty) ...[
const SizedBox(height: 8),
OutlinedButton.icon(
onPressed: loading
? null
: () => _submit(
AppConfig.testAccount, AppConfig.testPassword),
icon: const Icon(Icons.science_outlined, size: 18),
label: Text('测试账号一键登录(${AppConfig.testAccount}'),
),
],
const SizedBox(height: 8),
TextButton(
onPressed: loading ? null : _showRegisterDialog,
child: const Text('还没有账号?注册新账号'),
),
],
),
),
),
),
);
}
}