feat(planting): 认种成功后检查实名认证状态

当CONTRACT_SIGNING_ENABLED=true时,认种成功后检查用户是否已完成实名认证:
- 如果未完成实名认证,显示提示弹窗引导用户去认证
- 如果已完成或功能未启用,按原有流程返回个人中心

新增:
- KycRequiredDialog 实名认证提示弹窗组件
- ContractSigningConfig 配置类和getConfig()方法
- kycServiceProvider 依赖注入

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
hailin 2025-12-24 21:10:55 -08:00
parent bc34907a84
commit 59e9cddf5b
4 changed files with 279 additions and 1 deletions

View File

@ -14,6 +14,7 @@ import '../services/notification_service.dart';
import '../services/system_config_service.dart';
import '../services/contract_signing_service.dart';
import '../services/contract_check_service.dart';
import '../../features/kyc/data/kyc_service.dart';
// Storage Providers
final secureStorageProvider = Provider<SecureStorage>((ref) {
@ -107,6 +108,12 @@ final contractCheckServiceProvider = Provider<ContractCheckService>((ref) {
return ContractCheckService(contractSigningService: contractSigningService);
});
// KYC Service Provider ( identity-service)
final kycServiceProvider = Provider<KycService>((ref) {
final apiClient = ref.watch(apiClientProvider);
return KycService(apiClient: apiClient);
});
// Override provider with initialized instance
ProviderContainer createProviderContainer(LocalStorage localStorage) {
return ProviderContainer(

View File

@ -153,6 +153,30 @@ class ContractSigningService {
ContractSigningService({required ApiClient apiClient}) : _apiClient = apiClient;
///
///
Future<ContractSigningConfig> getConfig() async {
try {
debugPrint('[ContractSigningService] 获取合同签署配置');
final response = await _apiClient.get('/planting/contract-signing/config');
if (response.statusCode == 200) {
final data = response.data as Map<String, dynamic>;
if (data['success'] == true && data['data'] != null) {
return ContractSigningConfig.fromJson(data['data']);
}
}
//
return ContractSigningConfig(contractSigningEnabled: true);
} catch (e) {
debugPrint('[ContractSigningService] 获取配置失败: $e');
//
return ContractSigningConfig(contractSigningEnabled: true);
}
}
///
Future<List<ContractSigningTask>> getPendingTasks() async {
try {

View File

@ -3,7 +3,9 @@ import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:go_router/go_router.dart';
import 'package:city_pickers/city_pickers.dart';
import '../widgets/planting_confirm_dialog.dart';
import '../widgets/kyc_required_dialog.dart';
import '../../../../core/di/injection_container.dart';
import '../../../../routes/route_paths.dart';
///
class PlantingLocationParams {
@ -208,6 +210,37 @@ class _PlantingLocationPageState extends ConsumerState<PlantingLocationPage> {
);
}
///
///
Future<bool> _checkKycRequirement() async {
try {
// 1.
final contractSigningService = ref.read(contractSigningServiceProvider);
final config = await contractSigningService.getConfig();
//
if (!config.contractSigningEnabled) {
debugPrint('[PlantingLocationPage] 合同签署功能未启用,跳过实名认证检查');
return false;
}
// 2.
final kycService = ref.read(kycServiceProvider);
final kycStatus = await kycService.getKycStatus();
// 1
final isVerified = kycStatus.level1.verified;
debugPrint('[PlantingLocationPage] 合同签署功能启用,实名认证状态: $isVerified');
//
return !isVerified;
} catch (e) {
debugPrint('[PlantingLocationPage] 检查实名认证状态失败: $e');
//
return false;
}
}
///
Future<void> _submitPlanting() async {
setState(() => _isSubmitting = true);
@ -243,8 +276,22 @@ class _PlantingLocationPageState extends ConsumerState<PlantingLocationPage> {
),
);
// 5.
final needsKyc = await _checkKycRequirement();
if (needsKyc && mounted) {
//
final goToKyc = await KycRequiredDialog.show(context: context);
if (goToKyc == true && mounted) {
//
context.push(RoutePaths.kycEntry);
return;
}
}
//
context.go('/profile');
if (mounted) {
context.go('/profile');
}
}
} catch (e) {
debugPrint('认种失败: $e');

View File

@ -0,0 +1,200 @@
import 'package:flutter/material.dart';
///
///
class KycRequiredDialog extends StatelessWidget {
const KycRequiredDialog({super.key});
///
/// true false
static Future<bool?> show({required BuildContext context}) {
return showDialog<bool>(
context: context,
barrierDismissible: true,
barrierColor: const Color(0x80000000),
builder: (context) => const KycRequiredDialog(),
);
}
@override
Widget build(BuildContext context) {
return Dialog(
backgroundColor: Colors.transparent,
insetPadding: const EdgeInsets.symmetric(horizontal: 24),
child: Container(
width: double.infinity,
constraints: const BoxConstraints(maxWidth: 360),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(16),
boxShadow: const [
BoxShadow(
color: Color(0x40000000),
blurRadius: 50,
offset: Offset(0, 25),
spreadRadius: -12,
),
],
),
child: Stack(
children: [
//
Padding(
padding: const EdgeInsets.fromLTRB(24, 32, 24, 24),
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
//
_buildIcon(),
const SizedBox(height: 16),
//
_buildTitle(),
const SizedBox(height: 12),
//
_buildDescription(),
const SizedBox(height: 24),
//
_buildButtons(context),
],
),
),
//
Positioned(
top: 8,
right: 8,
child: GestureDetector(
onTap: () => Navigator.pop(context, false),
child: Container(
width: 32,
height: 32,
decoration: BoxDecoration(
color: const Color(0x0D000000),
borderRadius: BorderRadius.circular(16),
),
child: const Icon(
Icons.close,
size: 18,
color: Color(0xFF8B5A2B),
),
),
),
),
],
),
),
);
}
///
Widget _buildIcon() {
return Container(
width: 64,
height: 64,
decoration: BoxDecoration(
color: const Color(0xFFFFF7E6),
borderRadius: BorderRadius.circular(32),
),
child: const Center(
child: Icon(
Icons.verified_user_outlined,
color: Color(0xFFD4AF37),
size: 32,
),
),
);
}
///
Widget _buildTitle() {
return const Text(
'需要先完成实名认证',
style: TextStyle(
fontSize: 20,
fontFamily: 'Inter',
fontWeight: FontWeight.w700,
height: 1.3,
color: Color(0xFF5D4037),
),
textAlign: TextAlign.center,
);
}
///
Widget _buildDescription() {
return const Text(
'为了保障您的权益,认种前需要完成实名认证。\n完成认证后即可继续认种。',
style: TextStyle(
fontSize: 14,
fontFamily: 'Inter',
height: 1.5,
color: Color(0xFF745D43),
),
textAlign: TextAlign.center,
);
}
///
Widget _buildButtons(BuildContext context) {
return Column(
children: [
//
SizedBox(
width: double.infinity,
child: GestureDetector(
onTap: () => Navigator.pop(context, true),
child: Container(
height: 48,
decoration: BoxDecoration(
color: const Color(0xFFD4AF37),
borderRadius: BorderRadius.circular(8),
),
child: const Center(
child: Text(
'去实名认证',
style: TextStyle(
fontSize: 16,
fontFamily: 'Inter',
fontWeight: FontWeight.w600,
height: 1.5,
color: Colors.white,
),
),
),
),
),
),
const SizedBox(height: 12),
//
SizedBox(
width: double.infinity,
child: GestureDetector(
onTap: () => Navigator.pop(context, false),
child: Container(
height: 48,
decoration: BoxDecoration(
color: Colors.transparent,
borderRadius: BorderRadius.circular(8),
border: Border.all(
color: const Color(0xFFD4AF37),
width: 1,
),
),
child: const Center(
child: Text(
'稍后再说',
style: TextStyle(
fontSize: 16,
fontFamily: 'Inter',
fontWeight: FontWeight.w600,
height: 1.5,
color: Color(0xFFD4AF37),
),
),
),
),
),
),
],
);
}
}