AI 接入 V1.0
UI更新
This commit is contained in:
+44
-143
@@ -10,8 +10,13 @@ import '../../providers/auth_provider.dart';
|
||||
import '../../utils/crypto.dart';
|
||||
import '../../utils/toast.dart';
|
||||
import '../shell/main_shell.dart';
|
||||
import '../../widgets/cute_icon.dart';
|
||||
|
||||
/// 登录/注册页(复刻 uniApp pages/login/login.vue)
|
||||
/// 登录页(复刻 uniApp pages/login/login.vue)
|
||||
///
|
||||
/// **注册功能已下线**(2026-09-24):这是单人应用,开放注册等于任何人都能建号。
|
||||
/// App 端的入口和界面都去掉了;后端 `/api/auth/register` 按用户要求保留着,
|
||||
/// 真要加人时把入口加回来即可。
|
||||
class LoginPage extends ConsumerStatefulWidget {
|
||||
const LoginPage({super.key});
|
||||
|
||||
@@ -21,15 +26,12 @@ class LoginPage extends ConsumerStatefulWidget {
|
||||
|
||||
class _LoginPageState extends ConsumerState<LoginPage>
|
||||
with SingleTickerProviderStateMixin {
|
||||
bool _isRegister = false;
|
||||
bool _showPassword = false;
|
||||
bool _loading = false;
|
||||
bool _agreeProtocol = false;
|
||||
bool _rememberMe = false;
|
||||
|
||||
final _usernameController = TextEditingController();
|
||||
final _passwordController = TextEditingController();
|
||||
final _nicknameController = TextEditingController();
|
||||
final _formKey = GlobalKey<FormState>();
|
||||
|
||||
late final AnimationController _floatController = AnimationController(
|
||||
@@ -53,7 +55,6 @@ class _LoginPageState extends ConsumerState<LoginPage>
|
||||
_floatController.dispose();
|
||||
_usernameController.dispose();
|
||||
_passwordController.dispose();
|
||||
_nicknameController.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@@ -66,43 +67,24 @@ class _LoginPageState extends ConsumerState<LoginPage>
|
||||
showToast(context, '请输入用户名和密码', error: true);
|
||||
return;
|
||||
}
|
||||
if (_isRegister) {
|
||||
if (password.length < 6) {
|
||||
showToast(context, '密码至少6位', error: true);
|
||||
return;
|
||||
}
|
||||
if (!_agreeProtocol) {
|
||||
showToast(context, '请先阅读并同意用户协议');
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
setState(() => _loading = true);
|
||||
try {
|
||||
final api = ref.read(authApiProvider);
|
||||
final response = _isRegister
|
||||
? await api.register(
|
||||
username,
|
||||
password,
|
||||
_nicknameController.text.trim().isEmpty
|
||||
? username
|
||||
: _nicknameController.text.trim(),
|
||||
)
|
||||
: await api.login(username, password);
|
||||
final response = await ref.read(authApiProvider).login(username, password);
|
||||
|
||||
// 记住密码
|
||||
if (!_isRegister && _rememberMe) {
|
||||
if (_rememberMe) {
|
||||
await LocalStorage.instance.saveRememberedPassword(
|
||||
username,
|
||||
encryptPassword(password),
|
||||
);
|
||||
} else if (!_isRegister && !_rememberMe) {
|
||||
} else {
|
||||
await LocalStorage.instance.clearRememberedPassword();
|
||||
}
|
||||
|
||||
await ref.read(authProvider.notifier).loginSuccess(response);
|
||||
if (!mounted) return;
|
||||
showToast(context, _isRegister ? '🎉 注册成功' : '🚀 登录成功');
|
||||
showToast(context, '登录成功', icon: 'check');
|
||||
Navigator.of(context).pushAndRemoveUntil(
|
||||
MaterialPageRoute(builder: (_) => const MainShell()),
|
||||
(route) => false,
|
||||
@@ -308,90 +290,55 @@ class _LoginPageState extends ConsumerState<LoginPage>
|
||||
key: _formKey,
|
||||
child: Column(
|
||||
children: [
|
||||
_buildSegmentTabs(),
|
||||
const SizedBox(height: 20),
|
||||
_inputField(
|
||||
icon: '👤',
|
||||
icon: 'user',
|
||||
controller: _usernameController,
|
||||
hint: '请输入用户名',
|
||||
maxLength: 20,
|
||||
),
|
||||
const SizedBox(height: 14),
|
||||
_inputField(
|
||||
icon: '🔒',
|
||||
icon: 'lock',
|
||||
controller: _passwordController,
|
||||
hint: '请输入密码',
|
||||
maxLength: 20,
|
||||
obscure: !_showPassword,
|
||||
suffix: IconButton(
|
||||
icon: Text(
|
||||
_showPassword ? '👁️' : '👁️🗨️',
|
||||
style: const TextStyle(fontSize: 16),
|
||||
// 一个图标用颜色表示状态:显示中=品牌色,隐藏中=浅灰
|
||||
icon: CuteIcon(
|
||||
'eye',
|
||||
size: 18,
|
||||
tint: _showPassword
|
||||
? const Color(0xFFC8956E)
|
||||
: const Color(0xFFBCAAA4),
|
||||
),
|
||||
onPressed: () =>
|
||||
setState(() => _showPassword = !_showPassword),
|
||||
),
|
||||
),
|
||||
if (_isRegister) ...[
|
||||
const SizedBox(height: 14),
|
||||
_inputField(
|
||||
icon: '✨',
|
||||
controller: _nicknameController,
|
||||
hint: '请输入昵称(可选)',
|
||||
maxLength: 20,
|
||||
),
|
||||
],
|
||||
if (!_isRegister) ...[
|
||||
const SizedBox(height: 8),
|
||||
Row(
|
||||
children: [
|
||||
SizedBox(
|
||||
width: 22,
|
||||
height: 22,
|
||||
child: Checkbox(
|
||||
value: _rememberMe,
|
||||
activeColor: const Color(0xFFC8956E),
|
||||
onChanged: (v) =>
|
||||
setState(() => _rememberMe = v ?? false),
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
Row(
|
||||
children: [
|
||||
SizedBox(
|
||||
width: 22,
|
||||
height: 22,
|
||||
child: Checkbox(
|
||||
value: _rememberMe,
|
||||
activeColor: const Color(0xFFC8956E),
|
||||
onChanged: (v) =>
|
||||
setState(() => _rememberMe = v ?? false),
|
||||
),
|
||||
const SizedBox(width: 6),
|
||||
const Text(
|
||||
'记住密码',
|
||||
style: TextStyle(
|
||||
fontSize: 13,
|
||||
color: Color(0xFF8D6E63),
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 6),
|
||||
const Text(
|
||||
'记住密码',
|
||||
style: TextStyle(
|
||||
fontSize: 13,
|
||||
color: Color(0xFF8D6E63),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
if (_isRegister) ...[
|
||||
const SizedBox(height: 8),
|
||||
Row(
|
||||
children: [
|
||||
SizedBox(
|
||||
width: 22,
|
||||
height: 22,
|
||||
child: Checkbox(
|
||||
value: _agreeProtocol,
|
||||
activeColor: const Color(0xFFC8956E),
|
||||
onChanged: (v) =>
|
||||
setState(() => _agreeProtocol = v ?? false),
|
||||
),
|
||||
),
|
||||
const Expanded(
|
||||
child: Text(
|
||||
'我已阅读并同意用户协议',
|
||||
style: TextStyle(
|
||||
fontSize: 13,
|
||||
color: Color(0xFF8D6E63),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 24),
|
||||
SizedBox(
|
||||
width: double.infinity,
|
||||
@@ -414,9 +361,9 @@ class _LoginPageState extends ConsumerState<LoginPage>
|
||||
color: Colors.white,
|
||||
),
|
||||
)
|
||||
: Text(
|
||||
_isRegister ? '注 册' : '登 录',
|
||||
style: const TextStyle(
|
||||
: const Text(
|
||||
'登 录',
|
||||
style: TextStyle(
|
||||
fontSize: 17,
|
||||
fontWeight: FontWeight.bold,
|
||||
letterSpacing: 4,
|
||||
@@ -437,52 +384,6 @@ class _LoginPageState extends ConsumerState<LoginPage>
|
||||
sigmaY: 10,
|
||||
);
|
||||
|
||||
// ---- 登录/注册切换 ----
|
||||
Widget _buildSegmentTabs() {
|
||||
return Container(
|
||||
decoration: BoxDecoration(
|
||||
color: const Color(0x33C8956E),
|
||||
borderRadius: BorderRadius.circular(20),
|
||||
),
|
||||
padding: const EdgeInsets.all(4),
|
||||
child: Row(
|
||||
children: [
|
||||
_segment(
|
||||
'登录',
|
||||
!_isRegister,
|
||||
() => setState(() => _isRegister = false),
|
||||
),
|
||||
_segment('注册', _isRegister, () => setState(() => _isRegister = true)),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _segment(String text, bool active, VoidCallback onTap) {
|
||||
return Expanded(
|
||||
child: GestureDetector(
|
||||
onTap: onTap,
|
||||
child: AnimatedContainer(
|
||||
duration: const Duration(milliseconds: 200),
|
||||
padding: const EdgeInsets.symmetric(vertical: 10),
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: BorderRadius.circular(16),
|
||||
color: active ? const Color(0xFFC8956E) : Colors.transparent,
|
||||
),
|
||||
child: Center(
|
||||
child: Text(
|
||||
text,
|
||||
style: TextStyle(
|
||||
fontSize: 15,
|
||||
fontWeight: FontWeight.w600,
|
||||
color: active ? Colors.white : const Color(0xFF8D6E63),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _inputField({
|
||||
required String icon,
|
||||
@@ -501,7 +402,7 @@ class _LoginPageState extends ConsumerState<LoginPage>
|
||||
),
|
||||
child: Row(
|
||||
children: [
|
||||
Text(icon, style: const TextStyle(fontSize: 18)),
|
||||
CuteIcon(icon, size: 20),
|
||||
const SizedBox(width: 10),
|
||||
Expanded(
|
||||
child: TextField(
|
||||
|
||||
Reference in New Issue
Block a user