gcx/frontend/admin-app/lib/features/credit/presentation/pages/credit_page.dart

341 lines
12 KiB
Dart
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import 'package:flutter/material.dart';
import '../../../../app/theme/app_colors.dart';
import '../../../../app/i18n/app_localizations.dart';
import '../../../../core/services/issuer_credit_service.dart';
/// 信用评级页面
///
/// 四因子信用评分核销率35% + (1-Breakage率)25% + 市场存续20% + 用户满意度20%
/// AI建议列表信用提升建议
class CreditPage extends StatefulWidget {
const CreditPage({super.key});
@override
State<CreditPage> createState() => _CreditPageState();
}
class _CreditPageState extends State<CreditPage> {
final _creditService = IssuerCreditService();
bool _isLoading = true;
String? _error;
CreditModel? _credit;
@override
void initState() {
super.initState();
_loadCredit();
}
Future<void> _loadCredit() async {
setState(() {
_isLoading = true;
_error = null;
});
try {
final credit = await _creditService.getCredit();
if (!mounted) return;
setState(() {
_credit = credit;
_isLoading = false;
});
} catch (e) {
debugPrint('[CreditPage] loadCredit error: $e');
if (!mounted) return;
setState(() {
_isLoading = false;
_error = e.toString();
});
}
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text(context.t('credit_title'))),
body: _isLoading
? const Center(child: CircularProgressIndicator())
: _error != null
? Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
const Icon(Icons.error_outline, size: 48, color: AppColors.textTertiary),
const SizedBox(height: 16),
Text(_error!, style: const TextStyle(color: AppColors.textSecondary)),
const SizedBox(height: 16),
ElevatedButton(onPressed: _loadCredit, child: Text(context.t('retry'))),
],
),
)
: RefreshIndicator(
onRefresh: _loadCredit,
child: SingleChildScrollView(
physics: const AlwaysScrollableScrollPhysics(),
padding: const EdgeInsets.all(20),
child: Column(
children: [
// Score Gauge
_buildScoreGauge(context),
const SizedBox(height: 24),
// Four Factors
_buildFactorsCard(context),
const SizedBox(height: 20),
// Tier Progress
_buildTierProgress(context),
const SizedBox(height: 20),
// AI Suggestions
_buildAiSuggestions(context),
const SizedBox(height: 20),
// Credit History
_buildCreditHistory(context),
],
),
),
),
);
}
Widget _buildScoreGauge(BuildContext context) {
final credit = _credit!;
return Container(
padding: const EdgeInsets.all(24),
decoration: BoxDecoration(
color: AppColors.surface,
borderRadius: BorderRadius.circular(16),
border: Border.all(color: AppColors.borderLight),
),
child: Column(
children: [
Container(
width: 120,
height: 120,
decoration: BoxDecoration(
shape: BoxShape.circle,
gradient: LinearGradient(
begin: Alignment.topCenter,
end: Alignment.bottomCenter,
colors: [AppColors.creditAA, AppColors.creditAA.withValues(alpha: 0.3)],
),
),
child: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text(credit.grade, style: const TextStyle(fontSize: 32, fontWeight: FontWeight.w700, color: Colors.white)),
Text('${credit.score}${context.t('credit_score_unit')}', style: const TextStyle(fontSize: 14, color: Colors.white70)),
],
),
),
),
const SizedBox(height: 16),
Text(context.t('credit_score_label'), style: const TextStyle(fontSize: 18, fontWeight: FontWeight.w600)),
const SizedBox(height: 4),
Text(context.t('credit_gap_label'), style: const TextStyle(fontSize: 13, color: AppColors.textSecondary)),
],
),
);
}
Widget _buildFactorsCard(BuildContext context) {
final factors = _credit!.factors;
final factorColors = [AppColors.success, AppColors.info, AppColors.primary, AppColors.warning];
return Container(
padding: const EdgeInsets.all(16),
decoration: BoxDecoration(
color: AppColors.surface,
borderRadius: BorderRadius.circular(12),
border: Border.all(color: AppColors.borderLight),
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(context.t('credit_factors'), style: const TextStyle(fontSize: 15, fontWeight: FontWeight.w600)),
const SizedBox(height: 16),
...factors.asMap().entries.map((entry) {
final f = entry.value;
final color = factorColors[entry.key % factorColors.length];
final score = f.score / 100;
return Padding(
padding: const EdgeInsets.only(bottom: 16),
child: Column(
children: [
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(f.name, style: const TextStyle(fontSize: 13)),
Text(
'${f.score.toInt()}${context.t('credit_score_unit')} (${(f.weight * 100).toInt()}%)',
style: const TextStyle(fontSize: 12, color: AppColors.textSecondary),
),
],
),
const SizedBox(height: 6),
ClipRRect(
borderRadius: BorderRadius.circular(4),
child: LinearProgressIndicator(
value: score.clamp(0.0, 1.0),
backgroundColor: AppColors.gray100,
valueColor: AlwaysStoppedAnimation(color),
minHeight: 8,
),
),
],
),
);
}),
if (factors.isEmpty)
const Padding(
padding: EdgeInsets.all(16),
child: Center(child: Text(context.t('credit_no_factors'), style: const TextStyle(color: AppColors.textTertiary))),
),
],
),
);
}
Widget _buildTierProgress(BuildContext context) {
final credit = _credit!;
final tierColors = [AppColors.tierSilver, AppColors.tierGold, AppColors.tierPlatinum, AppColors.tierDiamond];
final tiers = credit.tiers;
return Container(
padding: const EdgeInsets.all(16),
decoration: BoxDecoration(
color: AppColors.surface,
borderRadius: BorderRadius.circular(12),
border: Border.all(color: AppColors.borderLight),
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(context.t('credit_tier_title'), style: const TextStyle(fontSize: 15, fontWeight: FontWeight.w600)),
const SizedBox(height: 16),
Row(
mainAxisAlignment: MainAxisAlignment.spaceAround,
children: tiers.asMap().entries.map((entry) {
final index = entry.key;
final name = entry.value;
final isReached = index <= credit.currentTierIndex;
final color = tierColors[index % tierColors.length];
return Column(
children: [
Container(
width: 44,
height: 44,
decoration: BoxDecoration(
color: isReached ? color.withValues(alpha: 0.15) : AppColors.gray100,
shape: BoxShape.circle,
border: isReached ? Border.all(color: color, width: 2) : null,
),
child: Icon(
Icons.star_rounded,
color: isReached ? color : AppColors.textTertiary,
size: 22,
),
),
const SizedBox(height: 6),
Text(
name,
style: TextStyle(
fontSize: 12,
color: isReached ? color : AppColors.textTertiary,
fontWeight: isReached ? FontWeight.w600 : FontWeight.w400,
),
),
],
);
}).toList(),
),
const SizedBox(height: 12),
Text(
context.t('credit_tier_progress'),
style: const TextStyle(fontSize: 12, color: AppColors.textSecondary),
),
],
),
);
}
Widget _buildAiSuggestions(BuildContext context) {
final suggestions = _credit!.suggestions;
final icons = [Icons.trending_up_rounded, Icons.notification_important_rounded, Icons.rate_review_rounded];
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
children: [
const Icon(Icons.auto_awesome_rounded, color: AppColors.primary, size: 20),
const SizedBox(width: 8),
Text(context.t('credit_ai_title'), style: const TextStyle(fontSize: 15, fontWeight: FontWeight.w600)),
],
),
const SizedBox(height: 12),
if (suggestions.isEmpty)
Container(
padding: const EdgeInsets.all(14),
decoration: BoxDecoration(
color: AppColors.primarySurface,
borderRadius: BorderRadius.circular(10),
),
child: Center(
child: Text(context.t('credit_no_suggestions'), style: const TextStyle(color: AppColors.textSecondary)),
),
)
else
...suggestions.asMap().entries.map((entry) {
final icon = icons[entry.key % icons.length];
final text = entry.value;
return Container(
margin: const EdgeInsets.only(bottom: 10),
padding: const EdgeInsets.all(14),
decoration: BoxDecoration(
color: AppColors.primarySurface,
borderRadius: BorderRadius.circular(10),
),
child: Row(
children: [
Icon(icon, color: AppColors.primary, size: 20),
const SizedBox(width: 12),
Expanded(
child: Text(text, style: const TextStyle(fontSize: 13, color: AppColors.textSecondary)),
),
],
),
);
}),
],
);
}
Widget _buildCreditHistory(BuildContext context) {
// Credit history not yet provided by API, keeping placeholder
return Container(
padding: const EdgeInsets.all(16),
decoration: BoxDecoration(
color: AppColors.surface,
borderRadius: BorderRadius.circular(12),
border: Border.all(color: AppColors.borderLight),
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(context.t('credit_history_title'), style: const TextStyle(fontSize: 15, fontWeight: FontWeight.w600)),
const SizedBox(height: 12),
const Padding(
padding: EdgeInsets.all(16),
child: Center(child: Text('--', style: TextStyle(color: AppColors.textTertiary))),
),
],
),
);
}
}