feat: 新增勘察明细管理与修改密码功能

This commit is contained in:
andyesfly committed 2026-10-17 10:10:09 +08:00
1 parent 56830e94ec
commit 774403785c
12 files changed
+1553 -18

No files matched your search

@@ -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,
),
),
),
),
],
);
}
}