jjsos_autosos_flutter/lib/pages/login/bind_phone_page.dart

263 lines
7.4 KiB
Dart

import 'package:autosos_flutter/api/user_api.dart';
import 'package:autosos_flutter/provider/providers.dart';
import 'package:autosos_flutter/router/app_router.dart';
import 'package:autosos_flutter/service/push_service.dart';
import 'package:autosos_flutter/util/toast.dart';
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:go_router/go_router.dart';
/// 登录页(升级版:手机号 + 验证码 + 密码 + 微信登录)
class BindPhonePage extends ConsumerStatefulWidget {
const BindPhonePage({super.key});
@override
ConsumerState<BindPhonePage> createState() => _BindPhonePageState();
}
class _BindPhonePageState extends ConsumerState<BindPhonePage> with SingleTickerProviderStateMixin {
late TabController _tab;
final _phoneCtrl = TextEditingController();
final _codeCtrl = TextEditingController();
final _pwdCtrl = TextEditingController();
bool _sending = false;
int _countdown = 0;
@override
void initState() {
super.initState();
_tab = TabController(length: 2, vsync: this);
_phoneCtrl.text = '14725803690';
_pwdCtrl.text = '123456';
}
@override
void dispose() {
_tab.dispose();
_phoneCtrl.dispose();
_codeCtrl.dispose();
_pwdCtrl.dispose();
super.dispose();
}
Future<void> _sendCode() async {
final phone = _phoneCtrl.text.trim();
if (phone.length != 11) {
Toast.show('请输入正确的手机号');
return;
}
setState(() => _sending = true);
try {
final r = await UserApi.instance.sendSms(phone);
if (r.isSuccess) {
Toast.show('验证码已发送');
_startCountdown(r.data?.cooldown ?? 60);
} else {
Toast.show(r.message ?? '发送失败');
}
} catch (e) {
Toast.show('网络异常');
} finally {
if (mounted) setState(() => _sending = false);
}
}
void _startCountdown(int seconds) {
setState(() => _countdown = seconds);
Future.doWhile(() async {
await Future.delayed(const Duration(seconds: 1));
if (!mounted) return false;
setState(() => _countdown = _countdown - 1);
return _countdown > 0;
});
}
Future<void> _loginByPassword() async {
final phone = _phoneCtrl.text.trim();
final pwd = _pwdCtrl.text;
if (phone.length != 11 || pwd.isEmpty) {
Toast.show('请输入手机号和密码');
return;
}
try {
final cid = PushService.instance.clientId;
final r = await UserApi.instance.login(
username: phone, password: pwd, cid: cid,
);
if (r.isSuccess && r.data != null) {
await _onLoginSuccess(r.data!.accessToken);
} else {
Toast.show(r.message ?? '登录失败');
}
} catch (e) {
Toast.show('网络异常');
}
}
Future<void> _loginBySms() async {
final phone = _phoneCtrl.text.trim();
final code = _codeCtrl.text.trim();
if (phone.length != 11 || code.isEmpty) {
Toast.show('请输入手机号和验证码');
return;
}
try {
final cid = PushService.instance.clientId;
final r = await UserApi.instance.loginBySms(mobile: phone, code: code, cid: cid);
if (r.isSuccess && r.data != null) {
await _onLoginSuccess(r.data!.accessToken);
} else {
Toast.show(r.message ?? '登录失败');
}
} catch (e) {
Toast.show('网络异常');
}
}
Future<void> _onLoginSuccess(String token) async {
// 写入 SP
await Future.wait([
// 持久化 token
]);
ref.read(accessTokenProvider.notifier).state = token;
Toast.show('登录成功');
if (!mounted) return;
context.go(Routes.home);
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('登录')),
body: SingleChildScrollView(
padding: const EdgeInsets.all(24),
child: Column(
children: [
Image.asset('images/1.5x/ic_launcher.png', width: 72, height: 72),
const SizedBox(height: 12),
const Text('欢迎登录啾啾救援',
style: TextStyle(fontSize: 22, fontWeight: FontWeight.bold)),
const Text('宁波易到互联科技有限公司',
style: TextStyle(fontSize: 13, color: Colors.grey)),
const SizedBox(height: 24),
TabBar(
controller: _tab,
tabs: const [Tab(text: '账号密码'), Tab(text: '短信登录')],
),
const SizedBox(height: 16),
SizedBox(
height: 220,
child: TabBarView(
controller: _tab,
children: [
_buildPasswordForm(),
_buildSmsForm(),
],
),
),
const SizedBox(height: 12),
Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
const Text('其他登录方式:'),
IconButton(
icon: const Icon(Icons.wechat, color: Colors.green, size: 32),
onPressed: () => context.push(Routes.bindWx),
),
],
),
],
),
),
);
}
Widget _buildPhoneField() {
return Row(
children: [
const Text('+86', style: TextStyle(fontSize: 16)),
const SizedBox(width: 12),
Container(width: 1, height: 24, color: Colors.grey),
const SizedBox(width: 12),
Expanded(
child: TextField(
controller: _phoneCtrl,
keyboardType: TextInputType.phone,
decoration: const InputDecoration(
hintText: '请输入手机号',
border: InputBorder.none,
),
),
),
],
);
}
Widget _buildPasswordForm() {
return Column(
children: [
_buildPhoneField(),
const Divider(),
TextField(
controller: _pwdCtrl,
obscureText: true,
decoration: const InputDecoration(
hintText: '请输入密码',
border: InputBorder.none,
),
),
const Divider(),
const SizedBox(height: 24),
SizedBox(
width: double.infinity,
height: 48,
child: ElevatedButton(
onPressed: _loginByPassword,
child: const Text('登录'),
),
),
],
);
}
Widget _buildSmsForm() {
return Column(
children: [
_buildPhoneField(),
const Divider(),
Row(
children: [
Expanded(
child: TextField(
controller: _codeCtrl,
keyboardType: TextInputType.number,
decoration: const InputDecoration(
hintText: '请输入验证码',
border: InputBorder.none,
),
),
),
TextButton(
onPressed: (_sending || _countdown > 0) ? null : _sendCode,
child: Text(
_countdown > 0 ? '$_countdown秒后重试' : '获取验证码',
style: const TextStyle(fontSize: 14),
),
),
],
),
const Divider(),
const SizedBox(height: 24),
SizedBox(
width: double.infinity,
height: 48,
child: ElevatedButton(
onPressed: _loginBySms,
child: const Text('登录'),
),
),
],
);
}
}