341 lines
12 KiB
Dart
341 lines
12 KiB
Dart
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))),
|
||
),
|
||
],
|
||
),
|
||
);
|
||
}
|
||
}
|