| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293 |
- import 'dart:async';
- import 'package:flutter/material.dart';
- import 'package:flutter_riverpod/flutter_riverpod.dart';
- import '../../../core/utils/common_utils.dart';
- import '../../../core/utils/toast_utils.dart';
- import '../../../core/utils/logger.dart';
- import '../../../l10n/app_localizations.dart';
- import '../../../module/common_input_field.dart';
- import '../../auth/domain/login_service.dart';
- import '../data/change_password_provider.dart';
- import '../domain/change_password_service.dart';
- class ChangePasswordPage extends ConsumerStatefulWidget {
- const ChangePasswordPage({super.key});
- @override
- ConsumerState<ChangePasswordPage> createState() => _ChangePasswordPageState();
- }
- class _ChangePasswordPageState extends ConsumerState<ChangePasswordPage> {
- Timer? _smsTimer;
- // 验证密码一致性(回车键/Tab键触发)
- void _validatePasswordMatch() {
- final password = ref.read(changePwdPasswordProvider);
- final confirmPassword = ref.read(changePwdConfirmPasswordProvider);
- ref.read(changePwdShowPasswordErrorProvider.notifier).state =
- password.isNotEmpty &&
- confirmPassword.isNotEmpty &&
- password != confirmPassword;
- }
- void startSmsCountdown() {
- ref.read(changePwdSmsCountdownProvider.notifier).state = 60;
- _smsTimer?.cancel();
- _smsTimer = Timer.periodic(const Duration(seconds: 1), (timer) {
- final value = ref.read(changePwdSmsCountdownProvider);
- if (value <= 1) {
- timer.cancel();
- ref.read(changePwdSmsCountdownProvider.notifier).state = 0;
- } else {
- ref.read(changePwdSmsCountdownProvider.notifier).state = value - 1;
- }
- });
- }
- void submit(BuildContext context) async {
- final l10n = AppLocalizations.of(context)!;
- final phone = ref.read(changePwdPhoneProvider);
- final smsCode = ref.read(changePwdServerSmsCodeProvider);
- final password = ref.read(changePwdPasswordProvider);
- final confirmPassword = ref.read(changePwdConfirmPasswordProvider);
- final error = validateForm(
- context: context,
- phone: phone,
- code: smsCode,
- password: password,
- confirmPassword: confirmPassword,
- );
- if (error != null) {
- ToastUtils.showError(error);
- return;
- }
- // 调用重置密码接口
- try {
- final response = await ChangePasswordService.changePassword(
- mobile: phone,
- oldPassword: password,
- newPassword: confirmPassword,
- smsCode: smsCode,
- );
- if (response.success && response.code == 20000) {
- AppLogger.d('修改密码成功');
- ToastUtils.showSuccess(l10n.changePasswordSuccess);
- if (mounted) {
- Navigator.of(context).pop();
- }
- } else {
- final errorMsg = response.msg ?? l10n.changePasswordFailed;
- AppLogger.e(errorMsg);
- ToastUtils.showError(errorMsg);
- }
- } catch (e) {
- AppLogger.e('修改密码错误', e);
- ToastUtils.showError(l10n.changePasswordFailed);
- }
- }
- @override
- void dispose() {
- _smsTimer?.cancel();
- super.dispose();
- }
- @override
- Widget build(BuildContext context) {
- final phone = ref.watch(changePwdPhoneProvider);
- final smsCode = ref.watch(changePwdServerSmsCodeProvider);
- final password = ref.watch(changePwdPasswordProvider);
- final confirmPassword = ref.watch(changePwdConfirmPasswordProvider);
- final pwdVisible = ref.watch(changePwdPasswordVisibleProvider);
- final confirmPwdVisible = ref.watch(changePwdConfirmPasswordVisibleProvider);
- final countdown = ref.watch(changePwdSmsCountdownProvider);
- final smsHasSent = ref.watch(changePwdSmsHasSentProvider);
- final showPasswordError = ref.watch(changePwdShowPasswordErrorProvider);
- final canSubmit = validateForm(
- context: context,
- phone: phone,
- code: smsCode,
- password: password,
- confirmPassword: confirmPassword) == null;
- final l10n = AppLocalizations.of(context)!;
- return Dialog(
- insetPadding: const EdgeInsets.all(24),
- shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(8)),
- child: Padding(
- padding: const EdgeInsets.all(20),
- child: Column(
- mainAxisSize: MainAxisSize.min,
- children: [
- /// 标题
- Row(
- mainAxisAlignment: MainAxisAlignment.spaceBetween,
- children: [
- Text(
- l10n.forgotPassword,
- style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold),
- ),
- IconButton(
- icon: const Icon(Icons.close),
- onPressed: () => Navigator.of(context).pop(),
- )
- ],
- ),
- const SizedBox(height: 16),
- /// 手机号
- CommonInputField(
- label: l10n.phoneNumber,
- required: true,
- value: phone,
- hintText: l10n.phoneNumberRequired,
- keyboardType: TextInputType.phone,
- onChanged: (v) =>
- ref.read(changePwdPhoneProvider.notifier).state = v,
- ),
- const SizedBox(height: 16),
- /// 验证码
- CommonInputField(
- label: l10n.smsCode,
- required: true,
- value: smsCode,
- hintText: l10n.smsCodeRequired,
- keyboardType: TextInputType.number,
- onChanged: (v) =>
- ref.read(changePwdServerSmsCodeProvider.notifier).state = v,
- suffix: TextButton(
- onPressed: countdown > 0
- ? null
- : () async {
- // 校验手机号
- if (phone.isEmpty) {
- ToastUtils.showError(l10n.phoneNumberRequired);
- return;
- }
- if (!RegExp(r'^1\d{10}$').hasMatch(phone)) {
- ToastUtils.showError(l10n.phoneNumberInvalid);
- return;
- }
-
- // 发送验证码
- try {
- AppLogger.d('修改密码 - 发送验证码,手机号: $phone');
- final smsCode = await LoginService.sendSmsCode(
- mobile: phone,
- scope: 'yun-his-forget-password-sms-send',
- );
- // 保存服务器返回的验证码
- ref.read(changePwdServerSmsCodeProvider.notifier).state = smsCode;
- AppLogger.d('验证码已保存: $smsCode');
- ToastUtils.showSuccess(l10n.smsCodeSent);
- ref.read(changePwdSmsHasSentProvider.notifier).state = true;
- startSmsCountdown();
- } catch (e) {
- AppLogger.e('发送验证码失败', e);
- ToastUtils.showError(e.toString().replaceAll('Exception: ', ''));
- }
- },
- child: Text(
- countdown > 0
- ? '${countdown}s'
- : (smsHasSent ? l10n.resendSmsCode : l10n.getSmsCode),
- ),
- ),
- ),
- const SizedBox(height: 16),
- /// 新密码
- CommonInputField(
- label: l10n.newPassword,
- required: true,
- value: password,
- hintText: l10n.newPasswordRequired,
- obscureText: !pwdVisible,
- onChanged: (v) =>
- ref.read(changePwdPasswordProvider.notifier).state = v,
- suffix: IconButton(
- icon: Icon(
- pwdVisible ? Icons.visibility : Icons.visibility_off),
- onPressed: () => ref
- .read(changePwdPasswordVisibleProvider.notifier)
- .state = !pwdVisible,
- ),
- ),
- const SizedBox(height: 6),
- Align(
- alignment: Alignment.centerLeft,
- child: Text(
- l10n.passwordFormatError,
- style: TextStyle(fontSize: 12, color: Colors.grey),
- ),
- ),
- const SizedBox(height: 16),
- /// 确认密码
- CommonInputField(
- label: l10n.confirmPassword,
- required: true,
- value: confirmPassword,
- hintText: l10n.confirmPasswordRequired,
- obscureText: !confirmPwdVisible,
- onChanged: (v) {
- ref.read(changePwdConfirmPasswordProvider.notifier).state = v;
- // 用户正在输入,清除错误提示
- ref.read(changePwdShowPasswordErrorProvider.notifier).state = false;
- },
- onFieldSubmitted: (v) {
- // 回车键或 Tab 键时验证密码一致性
- _validatePasswordMatch();
- },
- suffix: IconButton(
- icon: Icon(confirmPwdVisible
- ? Icons.visibility
- : Icons.visibility_off),
- onPressed: () => ref
- .read(changePwdConfirmPasswordVisibleProvider.notifier)
- .state = !confirmPwdVisible,
- ),
- ),
- // 显示密码不一致错误提示(仅在回车/Tab时显示)
- if (showPasswordError)
- Padding(
- padding: const EdgeInsets.only(top: 8.0, left: 4.0),
- child: Text(
- l10n.confirmPasswordError,
- style: const TextStyle(
- color: Colors.red,
- fontSize: 12,
- ),
- ),
- ),
- const SizedBox(height: 24),
- /// 按钮
- Row(
- mainAxisAlignment: MainAxisAlignment.end,
- children: [
- OutlinedButton(
- onPressed: () => Navigator.of(context).pop(),
- child: Text(l10n.cancel),
- ),
- const SizedBox(width: 12),
- ElevatedButton(
- onPressed: canSubmit ? () => submit(context) : null,
- child: Text(l10n.confirm),
- ),
- ],
- )
- ],
- ),
- ),
- );
- }
- }
|