feat: 新增勘察明细管理与修改密码功能
This commit is contained in:
1 parent
56830e94ec
commit
774403785c
12 files changed
+1553
-18
No files matched your search
@@ -55,4 +55,12 @@ class AuthApi {
|
||||
},
|
||||
parse: (d) => WorkerInfo.fromJson((d as Map).cast<String, dynamic>()),
|
||||
);
|
||||
|
||||
/// 修改登录密码。后端 `/worker-app/profile` 收到 `password` 即更新密码哈希。
|
||||
Future<ApiResponse<WorkerInfo>> changePassword(String password) =>
|
||||
_client.put<WorkerInfo>(
|
||||
'/worker-app/profile',
|
||||
body: {'password': password},
|
||||
parse: (d) => WorkerInfo.fromJson((d as Map).cast<String, dynamic>()),
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,208 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:go_router/go_router.dart';
|
||||
|
||||
import '../../../core/network/api_response.dart';
|
||||
import '../../../core/theme/app_colors.dart';
|
||||
import '../../../core/theme/app_dimens.dart';
|
||||
import '../../../core/theme/app_typography.dart';
|
||||
import '../../../shared/widgets/app_feedback.dart';
|
||||
import '../../../shared/widgets/x_card.dart';
|
||||
import '../../../shared/widgets/x_common.dart';
|
||||
import '../../../shared/widgets/x_top_bar.dart';
|
||||
import '../application/session_controller.dart';
|
||||
|
||||
/// 修改登录密码页。
|
||||
///
|
||||
/// 后端 `/worker-app/profile` 传 `password` 即更新密码哈希,
|
||||
/// 因此本页只校验新密码的长度与两次输入一致,不要求原密码。
|
||||
class ChangePasswordPage extends ConsumerStatefulWidget {
|
||||
const ChangePasswordPage({super.key});
|
||||
|
||||
@override
|
||||
ConsumerState<ChangePasswordPage> createState() => _ChangePasswordPageState();
|
||||
}
|
||||
|
||||
class _ChangePasswordPageState extends ConsumerState<ChangePasswordPage> {
|
||||
final _password = TextEditingController();
|
||||
final _confirm = TextEditingController();
|
||||
|
||||
bool _loading = false;
|
||||
String _error = '';
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_password.addListener(_onChanged);
|
||||
_confirm.addListener(_onChanged);
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_password.dispose();
|
||||
_confirm.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
void _onChanged() => setState(() {});
|
||||
|
||||
bool get _canSubmit =>
|
||||
_password.text.length >= 6 && _password.text == _confirm.text;
|
||||
|
||||
Future<void> _handleSubmit() async {
|
||||
if (!_canSubmit) {
|
||||
setState(() => _error = '新密码至少 6 位,且两次输入需一致');
|
||||
return;
|
||||
}
|
||||
|
||||
setState(() {
|
||||
_loading = true;
|
||||
_error = '';
|
||||
});
|
||||
try {
|
||||
final res = await ref
|
||||
.read(authApiProvider)
|
||||
.changePassword(_password.text);
|
||||
if (!mounted) return;
|
||||
if (!res.isOk) {
|
||||
setState(() => _error = res.message.isEmpty ? '修改失败' : res.message);
|
||||
return;
|
||||
}
|
||||
showAppToast('密码修改成功', success: true);
|
||||
context.pop();
|
||||
} on ApiException catch (e) {
|
||||
if (mounted) setState(() => _error = e.message);
|
||||
} catch (_) {
|
||||
if (mounted) setState(() => _error = '网络异常,请稍后重试');
|
||||
} finally {
|
||||
if (mounted) setState(() => _loading = false);
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
body: XPageShell(
|
||||
topBar: XTopBar.nav(title: '修改密码', onBack: () => context.pop()),
|
||||
child: SingleChildScrollView(
|
||||
padding: const EdgeInsets.fromLTRB(16, 20, 16, 40),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||
children: [
|
||||
XCard(
|
||||
padding: const EdgeInsets.all(20),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||
children: [
|
||||
_Field(
|
||||
label: '新密码',
|
||||
hint: '至少 6 位',
|
||||
controller: _password,
|
||||
required: true,
|
||||
),
|
||||
const SizedBox(height: 18),
|
||||
_Field(
|
||||
label: '确认新密码',
|
||||
hint: '请再次输入新密码',
|
||||
controller: _confirm,
|
||||
required: true,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 20),
|
||||
XPrimaryButton(
|
||||
label: '确认修改',
|
||||
height: 56,
|
||||
radius: AppRadius.xl,
|
||||
loading: _loading,
|
||||
onPressed: _canSubmit ? _handleSubmit : null,
|
||||
shadows: AppShadows.primaryButton,
|
||||
),
|
||||
if (_error.isNotEmpty)
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(top: 14),
|
||||
child: Text(
|
||||
_error,
|
||||
textAlign: TextAlign.center,
|
||||
style: const TextStyle(fontSize: 13, color: AppColors.error),
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
const Text(
|
||||
'如忘记密码,可联系平台管理员重置为手机号后 6 位。',
|
||||
textAlign: TextAlign.center,
|
||||
style: TextStyle(fontSize: 13, color: AppColors.textMuted),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// 表单输入项,与注册页保持同一套视觉语言。
|
||||
class _Field extends StatelessWidget {
|
||||
const _Field({
|
||||
required this.label,
|
||||
required this.hint,
|
||||
required this.controller,
|
||||
this.required = false,
|
||||
});
|
||||
|
||||
final String label;
|
||||
final String hint;
|
||||
final TextEditingController controller;
|
||||
final bool required;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Row(
|
||||
children: [
|
||||
Text(
|
||||
label,
|
||||
style: AppTypography.bodySm.copyWith(
|
||||
color: AppColors.onSurfaceVariant,
|
||||
),
|
||||
),
|
||||
if (required)
|
||||
const Text(
|
||||
' *',
|
||||
style: TextStyle(fontSize: 13, color: AppColors.error),
|
||||
),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
Container(
|
||||
height: 48,
|
||||
padding: const EdgeInsets.symmetric(horizontal: 14),
|
||||
decoration: BoxDecoration(
|
||||
color: AppColors.surfaceBright,
|
||||
border: Border.all(color: AppColors.borderSoft),
|
||||
borderRadius: BorderRadius.circular(AppRadius.lg),
|
||||
),
|
||||
child: TextField(
|
||||
controller: controller,
|
||||
obscureText: true,
|
||||
style: const TextStyle(fontSize: 15, color: AppColors.onSurface),
|
||||
cursorColor: AppColors.primary,
|
||||
decoration: InputDecoration(
|
||||
isDense: true,
|
||||
border: InputBorder.none,
|
||||
counterText: '',
|
||||
hintText: hint,
|
||||
hintStyle: const TextStyle(
|
||||
fontSize: 15,
|
||||
color: AppColors.textPlaceholder,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user