142 lines
4.7 KiB
Dart
142 lines
4.7 KiB
Dart
import 'package:flutter/material.dart';
|
|
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
|
|
|
import '../../../models/budget.dart';
|
|
import '../../../utils/amount_util.dart';
|
|
import '../../../widgets/donut_chart.dart';
|
|
import '../../../widgets/state_views.dart';
|
|
|
|
/// 本月预算卡:环形图 + 预算 / 剩余 / 日均可用
|
|
class BudgetCard extends StatelessWidget {
|
|
const BudgetCard({super.key, required this.budget});
|
|
|
|
final AsyncValue<BudgetInfo> budget;
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
return Container(
|
|
padding: const EdgeInsets.all(16),
|
|
decoration: BoxDecoration(
|
|
color: Colors.white,
|
|
borderRadius: BorderRadius.circular(16),
|
|
),
|
|
child: budget.when(
|
|
loading: () => const LoadingView(tip: '预算加载中...', padding: EdgeInsets.all(20)),
|
|
error: (e, _) => ErrorView(message: '预算加载失败'),
|
|
data: (info) => info.hasBudget ? _content(info) : _noBudget(),
|
|
),
|
|
);
|
|
}
|
|
|
|
Widget _noBudget() {
|
|
return const EmptyView(
|
|
icon: '🎯',
|
|
title: '本月未设置预算',
|
|
tip: '可在「我的 → 预算设置」中设置',
|
|
padding: EdgeInsets.symmetric(vertical: 20),
|
|
);
|
|
}
|
|
|
|
Widget _content(BudgetInfo info) {
|
|
final over = info.isOverBudget;
|
|
final ringColor = over ? const Color(0xFFE53935) : const Color(0xFFC8956E);
|
|
final usedRatio = info.usedRatio;
|
|
|
|
return Column(
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: [
|
|
const Padding(
|
|
padding: EdgeInsets.only(left: 4, bottom: 12),
|
|
child: Text(
|
|
'本月预算',
|
|
style: TextStyle(fontSize: 14, fontWeight: FontWeight.w600, color: Color(0xFF5D4037)),
|
|
),
|
|
),
|
|
Row(
|
|
children: [
|
|
DonutChart(
|
|
size: 116,
|
|
centerSpaceRadius: 38,
|
|
slices: [
|
|
DonutSlice(value: usedRatio, color: ringColor),
|
|
DonutSlice(value: 1 - usedRatio, color: const Color(0xFFF0E6D8)),
|
|
],
|
|
center: Column(
|
|
mainAxisSize: MainAxisSize.min,
|
|
children: [
|
|
Text(
|
|
over ? '超支' : '${(usedRatio * 100).round()}%',
|
|
style: TextStyle(
|
|
fontSize: over ? 14 : 17,
|
|
fontWeight: FontWeight.bold,
|
|
color: over ? const Color(0xFFE53935) : const Color(0xFF5D4037),
|
|
),
|
|
),
|
|
if (!over)
|
|
const Text('已用', style: TextStyle(fontSize: 10, color: Color(0xFF8D6E63))),
|
|
],
|
|
),
|
|
),
|
|
const SizedBox(width: 16),
|
|
Expanded(
|
|
child: Column(
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: [
|
|
_amountRow('预算总额', '¥${formatAmount(info.amount)}', const Color(0xFF5D4037)),
|
|
const SizedBox(height: 8),
|
|
_amountRow('已用', '¥${formatAmount(info.usedAmount)}', ringColor),
|
|
const SizedBox(height: 8),
|
|
_amountRow(
|
|
'剩余',
|
|
'¥${formatAmount(info.remainingAmount)}',
|
|
info.remainingAmount < 0 ? const Color(0xFFE53935) : const Color(0xFF19BE6B),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
],
|
|
),
|
|
const SizedBox(height: 12),
|
|
Container(
|
|
width: double.infinity,
|
|
padding: const EdgeInsets.symmetric(vertical: 10),
|
|
decoration: BoxDecoration(
|
|
gradient: const LinearGradient(
|
|
colors: [Color(0x33C8956E), Color(0x33FFD700)],
|
|
),
|
|
borderRadius: BorderRadius.circular(12),
|
|
border: Border.all(color: const Color(0xFFC8956E)),
|
|
),
|
|
child: Row(
|
|
mainAxisAlignment: MainAxisAlignment.center,
|
|
children: [
|
|
const Text('💰 日均可用 ', style: TextStyle(fontSize: 13, color: Color(0xFF6D4C41))),
|
|
Text(
|
|
'¥${formatAmount(info.remainingDaily)}',
|
|
style: const TextStyle(
|
|
fontSize: 15,
|
|
fontWeight: FontWeight.bold,
|
|
color: Color(0xFF5D4037),
|
|
),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
],
|
|
);
|
|
}
|
|
|
|
Widget _amountRow(String label, String value, Color valueColor) {
|
|
return Row(
|
|
children: [
|
|
Text(label, style: const TextStyle(fontSize: 12.5, color: Color(0xFF8D6E63))),
|
|
const Spacer(),
|
|
Text(
|
|
value,
|
|
style: TextStyle(fontSize: 14, fontWeight: FontWeight.bold, color: valueColor),
|
|
),
|
|
],
|
|
);
|
|
}
|
|
}
|