|
|
@@ -0,0 +1,224 @@
|
|
|
+import 'package:flutter/material.dart';
|
|
|
+import 'package:fl_chart/fl_chart.dart';
|
|
|
+import '../../core/theme/app_colors.dart';
|
|
|
+
|
|
|
+class ExpenseApplyDetailReportPage extends StatelessWidget {
|
|
|
+ const ExpenseApplyDetailReportPage({super.key});
|
|
|
+
|
|
|
+ @override
|
|
|
+ Widget build(BuildContext context) {
|
|
|
+ return Scaffold(
|
|
|
+ appBar: AppBar(title: const Text('报销申请明细表')),
|
|
|
+ body: SingleChildScrollView(
|
|
|
+ padding: const EdgeInsets.all(12),
|
|
|
+ child: Column(
|
|
|
+ crossAxisAlignment: CrossAxisAlignment.start,
|
|
|
+ children: [
|
|
|
+ _buildSummaryRow(),
|
|
|
+ const SizedBox(height: 16),
|
|
|
+ const Text('月度趋势',
|
|
|
+ style: TextStyle(
|
|
|
+ fontSize: 15,
|
|
|
+ fontWeight: FontWeight.w600,
|
|
|
+ color: AppColors.textPrimary)),
|
|
|
+ const SizedBox(height: 12),
|
|
|
+ SizedBox(
|
|
|
+ height: 220,
|
|
|
+ child: _buildBarChart(),
|
|
|
+ ),
|
|
|
+ const SizedBox(height: 16),
|
|
|
+ const Text('明细列表',
|
|
|
+ style: TextStyle(
|
|
|
+ fontSize: 15,
|
|
|
+ fontWeight: FontWeight.w600,
|
|
|
+ color: AppColors.textPrimary)),
|
|
|
+ const SizedBox(height: 8),
|
|
|
+ _buildDetailList(),
|
|
|
+ ],
|
|
|
+ ),
|
|
|
+ ),
|
|
|
+ );
|
|
|
+ }
|
|
|
+
|
|
|
+ Widget _buildSummaryRow() {
|
|
|
+ return Row(
|
|
|
+ children: [
|
|
|
+ Expanded(
|
|
|
+ child: _summaryCard('申请总金额', '¥15,800.00', AppColors.primary),
|
|
|
+ ),
|
|
|
+ const SizedBox(width: 8),
|
|
|
+ Expanded(
|
|
|
+ child: _summaryCard('申请笔数', '18 笔', AppColors.success),
|
|
|
+ ),
|
|
|
+ const SizedBox(width: 8),
|
|
|
+ Expanded(
|
|
|
+ child: _summaryCard('待审批', '5 笔', AppColors.warning),
|
|
|
+ ),
|
|
|
+ ],
|
|
|
+ );
|
|
|
+ }
|
|
|
+
|
|
|
+ Widget _summaryCard(String label, String value, Color color) {
|
|
|
+ return Container(
|
|
|
+ padding: const EdgeInsets.all(12),
|
|
|
+ decoration: BoxDecoration(
|
|
|
+ color: color.withValues(alpha: 0.08),
|
|
|
+ borderRadius: BorderRadius.circular(10),
|
|
|
+ ),
|
|
|
+ child: Column(
|
|
|
+ crossAxisAlignment: CrossAxisAlignment.start,
|
|
|
+ children: [
|
|
|
+ Text(label,
|
|
|
+ style: const TextStyle(
|
|
|
+ fontSize: 11, color: AppColors.textSecondary)),
|
|
|
+ const SizedBox(height: 4),
|
|
|
+ Text(value,
|
|
|
+ style: TextStyle(
|
|
|
+ fontSize: 16,
|
|
|
+ fontWeight: FontWeight.bold,
|
|
|
+ color: color)),
|
|
|
+ ],
|
|
|
+ ),
|
|
|
+ );
|
|
|
+ }
|
|
|
+
|
|
|
+ Widget _buildBarChart() {
|
|
|
+ final data = [
|
|
|
+ ('1月', 3200.0),
|
|
|
+ ('2月', 1800.0),
|
|
|
+ ('3月', 4500.0),
|
|
|
+ ('4月', 3800.0),
|
|
|
+ ('5月', 2500.0),
|
|
|
+ ('6月', 0.0),
|
|
|
+ ];
|
|
|
+
|
|
|
+ return BarChart(
|
|
|
+ BarChartData(
|
|
|
+ alignment: BarChartAlignment.spaceAround,
|
|
|
+ maxY: 5000,
|
|
|
+ barGroups: data.asMap().entries.map((entry) {
|
|
|
+ final i = entry.key;
|
|
|
+ final (_, value) = entry.value;
|
|
|
+ return BarChartGroupData(
|
|
|
+ x: i,
|
|
|
+ barRods: [
|
|
|
+ BarChartRodData(
|
|
|
+ toY: value,
|
|
|
+ color: AppColors.success,
|
|
|
+ width: 20,
|
|
|
+ borderRadius: const BorderRadius.only(
|
|
|
+ topLeft: Radius.circular(4),
|
|
|
+ topRight: Radius.circular(4)),
|
|
|
+ ),
|
|
|
+ ],
|
|
|
+ );
|
|
|
+ }).toList(),
|
|
|
+ titlesData: FlTitlesData(
|
|
|
+ leftTitles: AxisTitles(
|
|
|
+ sideTitles: SideTitles(
|
|
|
+ showTitles: true,
|
|
|
+ reservedSize: 36,
|
|
|
+ getTitlesWidget: (value, meta) => Text(
|
|
|
+ '${(value / 1000).toInt()}k',
|
|
|
+ style: const TextStyle(
|
|
|
+ fontSize: 10, color: AppColors.textHint),
|
|
|
+ ),
|
|
|
+ ),
|
|
|
+ ),
|
|
|
+ bottomTitles: AxisTitles(
|
|
|
+ sideTitles: SideTitles(
|
|
|
+ showTitles: true,
|
|
|
+ getTitlesWidget: (value, meta) {
|
|
|
+ final idx = value.toInt();
|
|
|
+ if (idx < 0 || idx >= data.length) return const SizedBox();
|
|
|
+ return Padding(
|
|
|
+ padding: const EdgeInsets.only(top: 4),
|
|
|
+ child: Text(data[idx].$1,
|
|
|
+ style: const TextStyle(
|
|
|
+ fontSize: 10, color: AppColors.textHint)),
|
|
|
+ );
|
|
|
+ },
|
|
|
+ ),
|
|
|
+ ),
|
|
|
+ topTitles: const AxisTitles(
|
|
|
+ sideTitles: SideTitles(showTitles: false)),
|
|
|
+ rightTitles: const AxisTitles(
|
|
|
+ sideTitles: SideTitles(showTitles: false)),
|
|
|
+ ),
|
|
|
+ gridData: FlGridData(
|
|
|
+ show: true,
|
|
|
+ drawVerticalLine: false,
|
|
|
+ horizontalInterval: 1000,
|
|
|
+ getDrawingHorizontalLine: (value) => FlLine(
|
|
|
+ color: AppColors.divider,
|
|
|
+ strokeWidth: 0.5,
|
|
|
+ ),
|
|
|
+ ),
|
|
|
+ borderData: FlBorderData(show: false),
|
|
|
+ ),
|
|
|
+ );
|
|
|
+ }
|
|
|
+
|
|
|
+ Widget _buildDetailList() {
|
|
|
+ final items = [
|
|
|
+ {'no': 'BXSQ-20240501-001', 'applicant': '张三', 'amount': '¥3,500.00', 'status': '待审批'},
|
|
|
+ {'no': 'BXSQ-20240428-002', 'applicant': '李四', 'amount': '¥1,560.00', 'status': '已通过'},
|
|
|
+ {'no': 'BXSQ-20240425-003', 'applicant': '王五', 'amount': '¥2,800.00', 'status': '已拒绝'},
|
|
|
+ {'no': 'BXSQ-20240420-004', 'applicant': '赵六', 'amount': '¥890.00', 'status': '已通过'},
|
|
|
+ ];
|
|
|
+ return Column(
|
|
|
+ children: items
|
|
|
+ .map((item) => Container(
|
|
|
+ margin: const EdgeInsets.only(bottom: 6),
|
|
|
+ padding: const EdgeInsets.all(12),
|
|
|
+ decoration: BoxDecoration(
|
|
|
+ color: Colors.white,
|
|
|
+ borderRadius: BorderRadius.circular(8),
|
|
|
+ boxShadow: [
|
|
|
+ BoxShadow(
|
|
|
+ color: Colors.black.withValues(alpha: 0.04),
|
|
|
+ blurRadius: 4,
|
|
|
+ offset: const Offset(0, 1)),
|
|
|
+ ],
|
|
|
+ ),
|
|
|
+ child: Row(
|
|
|
+ mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
|
|
+ children: [
|
|
|
+ Column(
|
|
|
+ crossAxisAlignment: CrossAxisAlignment.start,
|
|
|
+ children: [
|
|
|
+ Text(item['no']!,
|
|
|
+ style: const TextStyle(
|
|
|
+ fontSize: 13,
|
|
|
+ color: AppColors.textPrimary)),
|
|
|
+ Text(item['applicant']!,
|
|
|
+ style: const TextStyle(
|
|
|
+ fontSize: 10,
|
|
|
+ color: AppColors.textHint)),
|
|
|
+ ],
|
|
|
+ ),
|
|
|
+ Column(
|
|
|
+ crossAxisAlignment: CrossAxisAlignment.end,
|
|
|
+ children: [
|
|
|
+ Text(item['amount']!,
|
|
|
+ style: const TextStyle(
|
|
|
+ fontSize: 14,
|
|
|
+ fontWeight: FontWeight.w600,
|
|
|
+ color: AppColors.primary)),
|
|
|
+ Text(item['status']!,
|
|
|
+ style: TextStyle(
|
|
|
+ fontSize: 10,
|
|
|
+ color: item['status'] == '待审批'
|
|
|
+ ? AppColors.warning
|
|
|
+ : item['status'] == '已通过'
|
|
|
+ ? AppColors.success
|
|
|
+ : AppColors.error)),
|
|
|
+ ],
|
|
|
+ ),
|
|
|
+ ],
|
|
|
+ ),
|
|
|
+ ))
|
|
|
+ .toList(),
|
|
|
+ );
|
|
|
+ }
|
|
|
+}
|