251 lines
8.4 KiB
Dart
251 lines
8.4 KiB
Dart
import 'package:flutter/material.dart';
|
|
import '../theme.dart';
|
|
import '../l10n/app_localizations.dart';
|
|
|
|
class ReportPage extends StatelessWidget {
|
|
const ReportPage({super.key});
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
final l10n = AppLocalizations.of(context)!;
|
|
return Scaffold(
|
|
backgroundColor: AppColors.background,
|
|
appBar: AppBar(title: Text(l10n.dataReport)),
|
|
body: ListView(
|
|
padding: const EdgeInsets.all(20),
|
|
children: [
|
|
_buildSummaryCards(context, l10n),
|
|
const SizedBox(height: 20),
|
|
_buildChartCard(context, l10n.orderTrend, [
|
|
_buildBar(context, l10n.monday, 0.6),
|
|
_buildBar(context, l10n.tuesday, 0.8),
|
|
_buildBar(context, l10n.wednesday, 0.5),
|
|
_buildBar(context, l10n.thursday, 0.9),
|
|
_buildBar(context, l10n.friday, 0.7),
|
|
_buildBar(context, l10n.saturday, 1.0),
|
|
_buildBar(context, l10n.sunday, 0.75),
|
|
]),
|
|
const SizedBox(height: 20),
|
|
_buildChartCard(context, l10n.revenueComposition, [
|
|
_buildPieItem(context, l10n.ticket, 0.45, const Color(0xFF1A56DB)),
|
|
_buildPieItem(context, l10n.hotel, 0.30, const Color(0xFF10B981)),
|
|
_buildPieItem(context, l10n.catering, 0.15, const Color(0xFFF59E0B)),
|
|
_buildPieItem(context, l10n.spa, 0.10, const Color(0xFFEC4899)),
|
|
]),
|
|
const SizedBox(height: 20),
|
|
_buildRankingCard(context, l10n),
|
|
],
|
|
),
|
|
);
|
|
}
|
|
|
|
Widget _buildSummaryCards(BuildContext context, AppLocalizations l10n) {
|
|
final items = [
|
|
(l10n.totalRevenue, '¥128,560', '${l10n.vsLastWeek} +12%', const Color(0xFF1A56DB)),
|
|
(l10n.orderCount, '1,286', '${l10n.vsLastWeek} +8%', const Color(0xFF10B981)),
|
|
(l10n.avgOrderValue, '¥168', '${l10n.vsLastWeek} +3%', const Color(0xFFF59E0B)),
|
|
(l10n.verifyRate, '96.5%', '${l10n.vsLastWeek} +2%', const Color(0xFF8B5CF6)),
|
|
];
|
|
|
|
return GridView.count(
|
|
shrinkWrap: true,
|
|
physics: const NeverScrollableScrollPhysics(),
|
|
crossAxisCount: 2,
|
|
crossAxisSpacing: 12,
|
|
mainAxisSpacing: 12,
|
|
childAspectRatio: 1.3,
|
|
children: items.map((item) {
|
|
return Container(
|
|
padding: const EdgeInsets.all(16),
|
|
decoration: BoxDecoration(
|
|
color: AppColors.surface,
|
|
borderRadius: BorderRadius.circular(16),
|
|
boxShadow: [
|
|
BoxShadow(
|
|
color: Colors.black.withValues(alpha: 0.04),
|
|
blurRadius: 10,
|
|
offset: const Offset(0, 2),
|
|
),
|
|
],
|
|
),
|
|
child: Column(
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: [
|
|
Text(
|
|
item.$1,
|
|
style: TextStyle(fontSize: 13, color: AppColors.textSecondary),
|
|
),
|
|
const SizedBox(height: 8),
|
|
Text(
|
|
item.$2,
|
|
style: TextStyle(
|
|
fontSize: 20,
|
|
fontWeight: FontWeight.bold,
|
|
color: item.$4,
|
|
),
|
|
),
|
|
const SizedBox(height: 6),
|
|
Text(
|
|
item.$3,
|
|
style: const TextStyle(fontSize: 12, color: AppColors.success),
|
|
),
|
|
],
|
|
),
|
|
);
|
|
}).toList(),
|
|
);
|
|
}
|
|
|
|
Widget _buildChartCard(BuildContext context, String title, List<Widget> children) {
|
|
return Container(
|
|
padding: const EdgeInsets.all(20),
|
|
decoration: BoxDecoration(
|
|
color: AppColors.surface,
|
|
borderRadius: BorderRadius.circular(16),
|
|
),
|
|
child: Column(
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: [
|
|
Text(
|
|
title,
|
|
style: TextStyle(
|
|
fontSize: 16,
|
|
fontWeight: FontWeight.w600,
|
|
color: AppColors.textPrimary,
|
|
),
|
|
),
|
|
const SizedBox(height: 20),
|
|
...children,
|
|
],
|
|
),
|
|
);
|
|
}
|
|
|
|
Widget _buildBar(BuildContext context, String label, double value) {
|
|
return Padding(
|
|
padding: const EdgeInsets.only(bottom: 12),
|
|
child: Row(
|
|
children: [
|
|
SizedBox(
|
|
width: 40,
|
|
child: Text(label, style: TextStyle(fontSize: 12, color: AppColors.textSecondary)),
|
|
),
|
|
Expanded(
|
|
child: Container(
|
|
height: 8,
|
|
decoration: BoxDecoration(
|
|
color: AppColors.background,
|
|
borderRadius: BorderRadius.circular(4),
|
|
),
|
|
child: FractionallySizedBox(
|
|
alignment: Alignment.centerLeft,
|
|
widthFactor: value,
|
|
child: Container(
|
|
decoration: BoxDecoration(
|
|
gradient: AppGradients.primary,
|
|
borderRadius: BorderRadius.circular(4),
|
|
),
|
|
),
|
|
),
|
|
),
|
|
),
|
|
const SizedBox(width: 10),
|
|
Text('${(value * 100).toInt()}%', style: TextStyle(fontSize: 12, color: AppColors.textSecondary)),
|
|
],
|
|
),
|
|
);
|
|
}
|
|
|
|
Widget _buildPieItem(BuildContext context, String label, double value, Color color) {
|
|
return Padding(
|
|
padding: const EdgeInsets.only(bottom: 10),
|
|
child: Row(
|
|
children: [
|
|
Container(
|
|
width: 12,
|
|
height: 12,
|
|
decoration: BoxDecoration(color: color, borderRadius: BorderRadius.circular(3)),
|
|
),
|
|
const SizedBox(width: 10),
|
|
Expanded(
|
|
child: Text(label, style: TextStyle(fontSize: 14, color: AppColors.textPrimary)),
|
|
),
|
|
Text('${(value * 100).toInt()}%', style: TextStyle(fontSize: 14, fontWeight: FontWeight.w600, color: AppColors.textPrimary)),
|
|
],
|
|
),
|
|
);
|
|
}
|
|
|
|
Widget _buildRankingCard(BuildContext context, AppLocalizations l10n) {
|
|
final products = [
|
|
(l10n.adultTicket, '¥45,280', '352${l10n.orderUnit}'),
|
|
(l10n.luxurySuite, '¥38,560', '43${l10n.orderUnit}'),
|
|
(l10n.familyPackage, '¥28,800', '74${l10n.orderUnit}'),
|
|
(l10n.spaPackage, '¥15,960', '26${l10n.orderUnit}'),
|
|
];
|
|
|
|
return Container(
|
|
padding: const EdgeInsets.all(20),
|
|
decoration: BoxDecoration(
|
|
color: AppColors.surface,
|
|
borderRadius: BorderRadius.circular(16),
|
|
),
|
|
child: Column(
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: [
|
|
Text(
|
|
l10n.topSales,
|
|
style: TextStyle(fontSize: 16, fontWeight: FontWeight.w600, color: AppColors.textPrimary),
|
|
),
|
|
const SizedBox(height: 16),
|
|
...products.asMap().entries.map((entry) {
|
|
final rank = entry.key + 1;
|
|
final product = entry.value;
|
|
return Padding(
|
|
padding: const EdgeInsets.only(bottom: 14),
|
|
child: Row(
|
|
children: [
|
|
Container(
|
|
width: 26,
|
|
height: 26,
|
|
decoration: BoxDecoration(
|
|
color: rank <= 3 ? AppColors.primary.withValues(alpha: 0.1) : AppColors.background,
|
|
borderRadius: BorderRadius.circular(6),
|
|
),
|
|
child: Center(
|
|
child: Text(
|
|
'$rank',
|
|
style: TextStyle(
|
|
fontSize: 13,
|
|
fontWeight: FontWeight.bold,
|
|
color: rank <= 3 ? AppColors.primary : AppColors.textTertiary,
|
|
),
|
|
),
|
|
),
|
|
),
|
|
const SizedBox(width: 12),
|
|
Expanded(
|
|
child: Text(
|
|
product.$1,
|
|
style: TextStyle(fontSize: 14, color: AppColors.textPrimary),
|
|
),
|
|
),
|
|
Text(
|
|
product.$2,
|
|
style: const TextStyle(fontSize: 14, fontWeight: FontWeight.w600, color: AppColors.primary),
|
|
),
|
|
const SizedBox(width: 12),
|
|
Text(
|
|
product.$3,
|
|
style: TextStyle(fontSize: 12, color: AppColors.textTertiary),
|
|
),
|
|
],
|
|
),
|
|
);
|
|
}).toList(),
|
|
],
|
|
),
|
|
);
|
|
}
|
|
}
|