|
|
@@ -245,12 +245,11 @@ class _ExpenseDetailReportPageState
|
|
|
);
|
|
|
}
|
|
|
|
|
|
- // ── 4 张统计卡片 ──
|
|
|
+ // ── 5 张统计卡片(2+2+1)──
|
|
|
Widget _buildStatCards() {
|
|
|
final colors = Theme.of(context).extension<AppColorsExtension>()!;
|
|
|
final l10n = AppLocalizations.of(context);
|
|
|
final summary = _data?.summary ?? const ReportSummary();
|
|
|
- final pendingCount = summary.totalCount - summary.approvedCount;
|
|
|
return Padding(
|
|
|
padding: const EdgeInsets.fromLTRB(12, 12, 12, 0),
|
|
|
child: Column(
|
|
|
@@ -259,16 +258,16 @@ class _ExpenseDetailReportPageState
|
|
|
children: [
|
|
|
Expanded(
|
|
|
child: _statCard(
|
|
|
- l10n.get('statTotalApproved'),
|
|
|
- '¥${summary.totalAmount.toStringAsFixed(2)}',
|
|
|
- colors.amountPrimary,
|
|
|
+ l10n.get('statExpenseTotalCount'),
|
|
|
+ '${summary.totalCount} 笔',
|
|
|
+ colors.textPrimary,
|
|
|
),
|
|
|
),
|
|
|
const SizedBox(width: 8),
|
|
|
Expanded(
|
|
|
child: _statCard(
|
|
|
- l10n.get('statMonthCount'),
|
|
|
- '${summary.totalCount} 笔',
|
|
|
+ l10n.get('statTransferredToPmt'),
|
|
|
+ '${summary.transferredCount} 笔',
|
|
|
colors.textPrimary,
|
|
|
),
|
|
|
),
|
|
|
@@ -279,17 +278,29 @@ class _ExpenseDetailReportPageState
|
|
|
children: [
|
|
|
Expanded(
|
|
|
child: _statCard(
|
|
|
- l10n.get('statPendingApprove'),
|
|
|
- '$pendingCount 笔',
|
|
|
- colors.textPrimary,
|
|
|
+ l10n.get('statExpenseTotal'),
|
|
|
+ '¥${summary.totalAmount.toStringAsFixed(2)}',
|
|
|
+ colors.amountPrimary,
|
|
|
),
|
|
|
),
|
|
|
const SizedBox(width: 8),
|
|
|
Expanded(
|
|
|
child: _statCard(
|
|
|
- l10n.get('statPendingPayment'),
|
|
|
- '0 笔',
|
|
|
- colors.textPrimary,
|
|
|
+ l10n.get('statTransferredToPmtAmount'),
|
|
|
+ '¥${summary.transferredAmount.toStringAsFixed(2)}',
|
|
|
+ colors.amountPrimary,
|
|
|
+ ),
|
|
|
+ ),
|
|
|
+ ],
|
|
|
+ ),
|
|
|
+ const SizedBox(height: 8),
|
|
|
+ Row(
|
|
|
+ children: [
|
|
|
+ Expanded(
|
|
|
+ child: _statCard(
|
|
|
+ l10n.get('statApprovedAmount'),
|
|
|
+ '¥${summary.approvedAmount.toStringAsFixed(2)}',
|
|
|
+ colors.amountPrimary,
|
|
|
),
|
|
|
),
|
|
|
],
|
|
|
@@ -370,11 +381,13 @@ class _ExpenseDetailReportPageState
|
|
|
children: [
|
|
|
_legendDot(colors.primary, l10n.get('expenseAmount')),
|
|
|
const SizedBox(width: 16),
|
|
|
- _legendDot(colors.success, l10n.get('approvedAmount')),
|
|
|
+ _legendDot(colors.warning, l10n.get('approvedAmountLabel')),
|
|
|
+ const SizedBox(width: 16),
|
|
|
+ _legendDot(colors.success, l10n.get('transferredAmount')),
|
|
|
],
|
|
|
),
|
|
|
const SizedBox(height: 12),
|
|
|
- SizedBox(height: 200, child: _buildDualLineChart()),
|
|
|
+ SizedBox(height: 200, child: _buildTripleLineChart()),
|
|
|
],
|
|
|
),
|
|
|
),
|
|
|
@@ -400,51 +413,37 @@ class _ExpenseDetailReportPageState
|
|
|
);
|
|
|
}
|
|
|
|
|
|
- Widget _buildDualLineChart() {
|
|
|
+ // 三折线图
|
|
|
+ Widget _buildTripleLineChart() {
|
|
|
final colors = Theme.of(context).extension<AppColorsExtension>()!;
|
|
|
- final monthly = _data?.monthly ?? [];
|
|
|
- final months = [
|
|
|
- '1月',
|
|
|
- '2月',
|
|
|
- '3月',
|
|
|
- '4月',
|
|
|
- '5月',
|
|
|
- '6月',
|
|
|
- '7月',
|
|
|
- '8月',
|
|
|
- '9月',
|
|
|
- '10月',
|
|
|
- '11月',
|
|
|
- '12月',
|
|
|
- ];
|
|
|
|
|
|
- // 构建 12 个月的点,API 未返回的月份填 0
|
|
|
- final amountSpots = List.generate(12, (i) {
|
|
|
- final idx = monthly.indexWhere((m) => m.month == i + 1);
|
|
|
- return FlSpot(i.toDouble(), idx >= 0 ? monthly[idx].amount : 0);
|
|
|
+ // 从 monthly 构建 12 个月的数据,无数据的月份默认为 0
|
|
|
+ final monthlyMap = <int, ReportMonthlyItem>{};
|
|
|
+ for (final item in (_data?.monthly ?? [])) {
|
|
|
+ monthlyMap[item.month] = item;
|
|
|
+ }
|
|
|
+ final monthLabels = List.generate(12, (i) => '${i + 1}月');
|
|
|
+ final amountData = List.generate(12, (i) {
|
|
|
+ return monthlyMap[i + 1]?.amount ?? 0;
|
|
|
+ });
|
|
|
+ final approvedData = List.generate(12, (i) {
|
|
|
+ return monthlyMap[i + 1]?.approvedAmount ?? 0;
|
|
|
});
|
|
|
- final approvedSpots = List.generate(12, (i) {
|
|
|
- final idx = monthly.indexWhere((m) => m.month == i + 1);
|
|
|
- return FlSpot(i.toDouble(), idx >= 0 ? monthly[idx].approvedAmount : 0);
|
|
|
+ final transferredData = List.generate(12, (i) {
|
|
|
+ return monthlyMap[i + 1]?.transferredAmount ?? 0;
|
|
|
});
|
|
|
|
|
|
- // 计算合理的 Y 轴最大值
|
|
|
- final allValues = <double>[];
|
|
|
- for (final m in monthly) {
|
|
|
- allValues.add(m.amount);
|
|
|
- allValues.add(m.approvedAmount);
|
|
|
- }
|
|
|
- final maxVal = allValues.isEmpty
|
|
|
- ? 100.0
|
|
|
- : allValues.reduce((a, b) => a > b ? a : b);
|
|
|
- final maxY = maxVal * 1.2;
|
|
|
+ // 计算 Y 轴最大值
|
|
|
+ final allValues = [...amountData, ...approvedData, ...transferredData];
|
|
|
+ final maxVal = allValues.reduce((a, b) => a > b ? a : b);
|
|
|
+ final maxY = (maxVal > 0 ? maxVal * 1.2 : 100).toDouble();
|
|
|
|
|
|
return LineChart(
|
|
|
LineChartData(
|
|
|
gridData: FlGridData(
|
|
|
show: true,
|
|
|
drawVerticalLine: false,
|
|
|
- horizontalInterval: maxY > 0 ? maxY / 5 : 20,
|
|
|
+ horizontalInterval: maxY / 5,
|
|
|
getDrawingHorizontalLine: (v) =>
|
|
|
FlLine(color: colors.border, strokeWidth: 0.5),
|
|
|
),
|
|
|
@@ -452,14 +451,11 @@ class _ExpenseDetailReportPageState
|
|
|
leftTitles: AxisTitles(
|
|
|
sideTitles: SideTitles(
|
|
|
showTitles: true,
|
|
|
- reservedSize: 50,
|
|
|
- getTitlesWidget: (v, meta) {
|
|
|
- final val = v.toInt();
|
|
|
- return Text(
|
|
|
- '¥$val',
|
|
|
- style: TextStyle(fontSize: 10, color: colors.textPlaceholder),
|
|
|
- );
|
|
|
- },
|
|
|
+ reservedSize: 48,
|
|
|
+ getTitlesWidget: (v, meta) => Text(
|
|
|
+ '¥${v.toInt()}',
|
|
|
+ style: TextStyle(fontSize: 10, color: colors.textPlaceholder),
|
|
|
+ ),
|
|
|
),
|
|
|
),
|
|
|
bottomTitles: AxisTitles(
|
|
|
@@ -469,15 +465,12 @@ class _ExpenseDetailReportPageState
|
|
|
reservedSize: 26,
|
|
|
getTitlesWidget: (v, meta) {
|
|
|
final i = v.toInt();
|
|
|
- if (i < 0 || i >= months.length) return const SizedBox();
|
|
|
+ if (i < 0 || i >= monthLabels.length) return const SizedBox();
|
|
|
return Padding(
|
|
|
padding: const EdgeInsets.only(top: 4),
|
|
|
child: Text(
|
|
|
- months[i],
|
|
|
- style: TextStyle(
|
|
|
- fontSize: 9,
|
|
|
- color: colors.textPlaceholder,
|
|
|
- ),
|
|
|
+ monthLabels[i],
|
|
|
+ style: TextStyle(fontSize: 9, color: colors.textPlaceholder),
|
|
|
),
|
|
|
);
|
|
|
},
|
|
|
@@ -492,14 +485,13 @@ class _ExpenseDetailReportPageState
|
|
|
),
|
|
|
borderData: FlBorderData(show: false),
|
|
|
minY: 0,
|
|
|
- maxY: maxY > 0 ? maxY : 100,
|
|
|
+ maxY: maxY,
|
|
|
lineTouchData: LineTouchData(
|
|
|
enabled: true,
|
|
|
touchTooltipData: LineTouchTooltipData(
|
|
|
getTooltipItems: (spots) => spots.map((s) {
|
|
|
- final mi = s.spotIndex;
|
|
|
return LineTooltipItem(
|
|
|
- '${months[mi]}\n¥${s.y.toStringAsFixed(0)}',
|
|
|
+ '${monthLabels[s.spotIndex]}\n¥${s.y.toStringAsFixed(0)}',
|
|
|
const TextStyle(
|
|
|
color: Colors.white,
|
|
|
fontSize: 12,
|
|
|
@@ -510,8 +502,12 @@ class _ExpenseDetailReportPageState
|
|
|
),
|
|
|
),
|
|
|
lineBarsData: [
|
|
|
+ // 报销金额 - primary/蓝
|
|
|
LineChartBarData(
|
|
|
- spots: amountSpots,
|
|
|
+ spots: List.generate(
|
|
|
+ 12,
|
|
|
+ (i) => FlSpot(i.toDouble(), amountData[i]),
|
|
|
+ ),
|
|
|
isCurved: true,
|
|
|
color: colors.primary,
|
|
|
barWidth: 2.5,
|
|
|
@@ -529,8 +525,35 @@ class _ExpenseDetailReportPageState
|
|
|
color: colors.primary.withValues(alpha: 0.08),
|
|
|
),
|
|
|
),
|
|
|
+ // 核准金额 - warning/橙
|
|
|
LineChartBarData(
|
|
|
- spots: approvedSpots,
|
|
|
+ spots: List.generate(
|
|
|
+ 12,
|
|
|
+ (i) => FlSpot(i.toDouble(), approvedData[i]),
|
|
|
+ ),
|
|
|
+ isCurved: true,
|
|
|
+ color: colors.warning,
|
|
|
+ barWidth: 2.5,
|
|
|
+ dotData: FlDotData(
|
|
|
+ show: true,
|
|
|
+ getDotPainter: (spot, percent, barData, index) =>
|
|
|
+ FlDotCirclePainter(
|
|
|
+ radius: 3,
|
|
|
+ color: colors.warning,
|
|
|
+ strokeWidth: 0,
|
|
|
+ ),
|
|
|
+ ),
|
|
|
+ belowBarData: BarAreaData(
|
|
|
+ show: true,
|
|
|
+ color: colors.warning.withValues(alpha: 0.08),
|
|
|
+ ),
|
|
|
+ ),
|
|
|
+ // 已转收支单金额 - success/绿
|
|
|
+ LineChartBarData(
|
|
|
+ spots: List.generate(
|
|
|
+ 12,
|
|
|
+ (i) => FlSpot(i.toDouble(), transferredData[i]),
|
|
|
+ ),
|
|
|
isCurved: true,
|
|
|
color: colors.success,
|
|
|
barWidth: 2.5,
|
|
|
@@ -633,6 +656,7 @@ class _ExpenseDetailReportPageState
|
|
|
);
|
|
|
}
|
|
|
|
|
|
+ // ── 明细列表(TDTable)──
|
|
|
Widget _buildDetailList() {
|
|
|
final l10n = AppLocalizations.of(context);
|
|
|
final colors = Theme.of(context).extension<AppColorsExtension>()!;
|
|
|
@@ -673,17 +697,20 @@ class _ExpenseDetailReportPageState
|
|
|
),
|
|
|
);
|
|
|
}
|
|
|
- final headerStyle = TextStyle(
|
|
|
- fontSize: 12,
|
|
|
- fontWeight: FontWeight.w600,
|
|
|
- color: colors.textSecondary,
|
|
|
- );
|
|
|
- final rowStyle = TextStyle(fontSize: 13, color: colors.textPrimary);
|
|
|
- final amountStyle = TextStyle(
|
|
|
- fontSize: 13,
|
|
|
- color: colors.amountPrimary,
|
|
|
- fontWeight: FontWeight.w600,
|
|
|
- );
|
|
|
+
|
|
|
+ final tableData = _details.map((d) => {
|
|
|
+ 'billNo': d.billNo,
|
|
|
+ 'billDate': d.billDate != null && d.billDate!.length >= 10
|
|
|
+ ? d.billDate!.substring(0, 10)
|
|
|
+ : '-',
|
|
|
+ 'amount': d.amount,
|
|
|
+ 'approvedAmount': d.approvedAmount,
|
|
|
+ 'reason': d.reason,
|
|
|
+ 'isTransferred': d.isTransferred,
|
|
|
+ }).toList();
|
|
|
+
|
|
|
+ final totalPages = (_detailTotal / 20).ceil();
|
|
|
+
|
|
|
return Padding(
|
|
|
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 8),
|
|
|
child: Container(
|
|
|
@@ -709,184 +736,169 @@ class _ExpenseDetailReportPageState
|
|
|
],
|
|
|
),
|
|
|
),
|
|
|
- Container(
|
|
|
- padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8),
|
|
|
- color: colors.bgDisabled,
|
|
|
- child: Row(
|
|
|
- children: [
|
|
|
- Expanded(
|
|
|
- flex: 3,
|
|
|
- child: Text(l10n.get('expenseNo'), style: headerStyle),
|
|
|
- ),
|
|
|
- const SizedBox(width: 8),
|
|
|
- SizedBox(
|
|
|
- width: 80,
|
|
|
- child: Text(l10n.get('date'), style: headerStyle),
|
|
|
- ),
|
|
|
- const SizedBox(width: 8),
|
|
|
- SizedBox(
|
|
|
- width: 80,
|
|
|
- child: Text(
|
|
|
- l10n.get('expenseAmount'),
|
|
|
- textAlign: TextAlign.right,
|
|
|
- style: headerStyle,
|
|
|
- ),
|
|
|
- ),
|
|
|
- const SizedBox(width: 8),
|
|
|
- SizedBox(
|
|
|
- width: 60,
|
|
|
- child: Text(
|
|
|
- l10n.get('transferStatus'),
|
|
|
- textAlign: TextAlign.center,
|
|
|
- style: headerStyle,
|
|
|
- ),
|
|
|
- ),
|
|
|
- ],
|
|
|
- ),
|
|
|
- ),
|
|
|
- ..._details.map((d) {
|
|
|
- final transferred = d.isTransferred;
|
|
|
- return Padding(
|
|
|
- padding: const EdgeInsets.symmetric(
|
|
|
- horizontal: 16,
|
|
|
- vertical: 8,
|
|
|
+ TDTable(
|
|
|
+ bordered: true,
|
|
|
+ stripe: true,
|
|
|
+ columns: [
|
|
|
+ TDTableCol(
|
|
|
+ title: l10n.get('expenseNo'),
|
|
|
+ colKey: 'billNo',
|
|
|
+ width: 120,
|
|
|
+ ellipsis: true,
|
|
|
),
|
|
|
- child: Row(
|
|
|
- crossAxisAlignment: CrossAxisAlignment.center,
|
|
|
- children: [
|
|
|
- Expanded(
|
|
|
- flex: 3,
|
|
|
- child: Text(
|
|
|
- d.billNo,
|
|
|
- style: rowStyle,
|
|
|
- maxLines: 2,
|
|
|
- overflow: TextOverflow.ellipsis,
|
|
|
- ),
|
|
|
- ),
|
|
|
- const SizedBox(width: 8),
|
|
|
- SizedBox(
|
|
|
- width: 80,
|
|
|
- child: Text(
|
|
|
- d.billDate != null && d.billDate!.length >= 10
|
|
|
- ? d.billDate!.substring(0, 10)
|
|
|
- : '-',
|
|
|
- style: rowStyle,
|
|
|
- maxLines: 2,
|
|
|
+ TDTableCol(
|
|
|
+ title: l10n.get('date'),
|
|
|
+ colKey: 'billDate',
|
|
|
+ width: 85,
|
|
|
+ ),
|
|
|
+ TDTableCol(
|
|
|
+ title: l10n.get('expenseAmount'),
|
|
|
+ colKey: 'amount',
|
|
|
+ width: 85,
|
|
|
+ align: TDTableColAlign.right,
|
|
|
+ cellBuilder: (context, rowIndex) {
|
|
|
+ final amount = tableData[rowIndex]['amount'] as double;
|
|
|
+ return Text(
|
|
|
+ '¥${amount.toStringAsFixed(2)}',
|
|
|
+ style: TextStyle(
|
|
|
+ fontSize: 13,
|
|
|
+ color: colors.amountPrimary,
|
|
|
+ fontWeight: FontWeight.w600,
|
|
|
),
|
|
|
- ),
|
|
|
- const SizedBox(width: 8),
|
|
|
- SizedBox(
|
|
|
- width: 80,
|
|
|
- child: Text(
|
|
|
- '¥${d.amount.toStringAsFixed(2)}',
|
|
|
- textAlign: TextAlign.right,
|
|
|
- style: amountStyle,
|
|
|
- maxLines: 1,
|
|
|
- overflow: TextOverflow.ellipsis,
|
|
|
+ );
|
|
|
+ },
|
|
|
+ ),
|
|
|
+ TDTableCol(
|
|
|
+ title: l10n.get('approvedAmountLabel'),
|
|
|
+ colKey: 'approvedAmount',
|
|
|
+ width: 85,
|
|
|
+ align: TDTableColAlign.right,
|
|
|
+ cellBuilder: (context, rowIndex) {
|
|
|
+ final amount = tableData[rowIndex]['approvedAmount'] as double;
|
|
|
+ return Text(
|
|
|
+ '¥${amount.toStringAsFixed(2)}',
|
|
|
+ style: TextStyle(
|
|
|
+ fontSize: 13,
|
|
|
+ color: colors.amountPrimary,
|
|
|
+ fontWeight: FontWeight.w600,
|
|
|
),
|
|
|
- ),
|
|
|
- const SizedBox(width: 8),
|
|
|
- SizedBox(
|
|
|
- width: 60,
|
|
|
- child: transferred
|
|
|
- ? Container(
|
|
|
- padding: const EdgeInsets.symmetric(
|
|
|
- horizontal: 6,
|
|
|
- vertical: 2,
|
|
|
- ),
|
|
|
- decoration: BoxDecoration(
|
|
|
- color: colors.success.withValues(alpha: 0.1),
|
|
|
- borderRadius: BorderRadius.circular(10),
|
|
|
- border: Border.all(
|
|
|
- color: colors.success, width: 0.5),
|
|
|
- ),
|
|
|
- child: Text(
|
|
|
- l10n.get('statusTransferred'),
|
|
|
- textAlign: TextAlign.center,
|
|
|
- style: TextStyle(
|
|
|
- fontSize: 11,
|
|
|
- color: colors.success,
|
|
|
- fontWeight: FontWeight.w500,
|
|
|
- ),
|
|
|
- ),
|
|
|
- )
|
|
|
- : const SizedBox.shrink(),
|
|
|
- ),
|
|
|
- ],
|
|
|
+ );
|
|
|
+ },
|
|
|
),
|
|
|
- );
|
|
|
- }),
|
|
|
- // 分页栏
|
|
|
- if (_detailTotal > 0)
|
|
|
- Container(
|
|
|
- padding: const EdgeInsets.symmetric(
|
|
|
- horizontal: 16,
|
|
|
- vertical: 10,
|
|
|
+ TDTableCol(
|
|
|
+ title: l10n.get('expenseApplyReason'),
|
|
|
+ colKey: 'reason',
|
|
|
+ width: 110,
|
|
|
+ ellipsis: true,
|
|
|
),
|
|
|
- decoration: BoxDecoration(
|
|
|
- border: Border(
|
|
|
- top: BorderSide(color: colors.border, width: 0.5),
|
|
|
- ),
|
|
|
+ TDTableCol(
|
|
|
+ title: l10n.get('transferStatus'),
|
|
|
+ colKey: 'isTransferred',
|
|
|
+ width: 70,
|
|
|
+ align: TDTableColAlign.center,
|
|
|
+ cellBuilder: (context, rowIndex) {
|
|
|
+ final transferred = tableData[rowIndex]['isTransferred'] as bool;
|
|
|
+ return transferred
|
|
|
+ ? Container(
|
|
|
+ padding: const EdgeInsets.symmetric(
|
|
|
+ horizontal: 6,
|
|
|
+ vertical: 2,
|
|
|
+ ),
|
|
|
+ decoration: BoxDecoration(
|
|
|
+ color: colors.success.withValues(alpha: 0.1),
|
|
|
+ borderRadius: BorderRadius.circular(10),
|
|
|
+ border: Border.all(
|
|
|
+ color: colors.success, width: 0.5),
|
|
|
+ ),
|
|
|
+ child: Text(
|
|
|
+ l10n.get('statusTransferred'),
|
|
|
+ style: TextStyle(
|
|
|
+ fontSize: 11,
|
|
|
+ color: colors.success,
|
|
|
+ fontWeight: FontWeight.w500,
|
|
|
+ ),
|
|
|
+ ),
|
|
|
+ )
|
|
|
+ : const SizedBox.shrink();
|
|
|
+ },
|
|
|
),
|
|
|
- child: Row(
|
|
|
- mainAxisAlignment: MainAxisAlignment.center,
|
|
|
- children: [
|
|
|
- GestureDetector(
|
|
|
- onTap: _detailPage > 1
|
|
|
- ? () {
|
|
|
- _detailPage--;
|
|
|
- _loadDetails();
|
|
|
- }
|
|
|
- : null,
|
|
|
- child: Icon(
|
|
|
- Icons.chevron_left,
|
|
|
- size: 20,
|
|
|
- color: _detailPage > 1
|
|
|
- ? colors.textPrimary
|
|
|
- : colors.textPlaceholder,
|
|
|
- ),
|
|
|
- ),
|
|
|
- const SizedBox(width: 16),
|
|
|
- Text(
|
|
|
- '$_detailPage',
|
|
|
- style: TextStyle(fontSize: 13, color: colors.textPrimary),
|
|
|
- ),
|
|
|
- const SizedBox(width: 4),
|
|
|
- Text(
|
|
|
- '/ ${(_detailTotal / 20).ceil()}',
|
|
|
- style: TextStyle(
|
|
|
- fontSize: 13,
|
|
|
- color: colors.textSecondary,
|
|
|
+ ],
|
|
|
+ data: tableData,
|
|
|
+ empty: TDTableEmpty(text: l10n.get('noDetailData')),
|
|
|
+ footerWidget: _detailTotal > 0
|
|
|
+ ? Container(
|
|
|
+ padding: const EdgeInsets.symmetric(
|
|
|
+ horizontal: 16,
|
|
|
+ vertical: 10,
|
|
|
),
|
|
|
- ),
|
|
|
- const SizedBox(width: 16),
|
|
|
- GestureDetector(
|
|
|
- onTap: _detailPage * 20 < _detailTotal
|
|
|
- ? () {
|
|
|
- _detailPage++;
|
|
|
- _loadDetails();
|
|
|
- }
|
|
|
- : null,
|
|
|
- child: Icon(
|
|
|
- Icons.chevron_right,
|
|
|
- size: 20,
|
|
|
- color: _detailPage * 20 < _detailTotal
|
|
|
- ? colors.textPrimary
|
|
|
- : colors.textPlaceholder,
|
|
|
+ decoration: BoxDecoration(
|
|
|
+ border: Border(
|
|
|
+ top: BorderSide(color: colors.border, width: 0.5),
|
|
|
+ ),
|
|
|
),
|
|
|
- ),
|
|
|
- const SizedBox(width: 16),
|
|
|
- Text(
|
|
|
- '${l10n.get('total')} $_detailTotal ${l10n.get('items')}',
|
|
|
- style: TextStyle(
|
|
|
- fontSize: 12,
|
|
|
- color: colors.textSecondary,
|
|
|
+ child: Row(
|
|
|
+ mainAxisAlignment: MainAxisAlignment.center,
|
|
|
+ children: [
|
|
|
+ GestureDetector(
|
|
|
+ onTap: _detailPage > 1
|
|
|
+ ? () {
|
|
|
+ _detailPage--;
|
|
|
+ _loadDetails();
|
|
|
+ }
|
|
|
+ : null,
|
|
|
+ child: Icon(
|
|
|
+ Icons.chevron_left,
|
|
|
+ size: 20,
|
|
|
+ color: _detailPage > 1
|
|
|
+ ? colors.textPrimary
|
|
|
+ : colors.textPlaceholder,
|
|
|
+ ),
|
|
|
+ ),
|
|
|
+ const SizedBox(width: 16),
|
|
|
+ Text(
|
|
|
+ '$_detailPage',
|
|
|
+ style: TextStyle(
|
|
|
+ fontSize: 13,
|
|
|
+ color: colors.textPrimary,
|
|
|
+ ),
|
|
|
+ ),
|
|
|
+ const SizedBox(width: 4),
|
|
|
+ Text(
|
|
|
+ '/ $totalPages',
|
|
|
+ style: TextStyle(
|
|
|
+ fontSize: 13,
|
|
|
+ color: colors.textSecondary,
|
|
|
+ ),
|
|
|
+ ),
|
|
|
+ const SizedBox(width: 16),
|
|
|
+ GestureDetector(
|
|
|
+ onTap: _detailPage < totalPages
|
|
|
+ ? () {
|
|
|
+ _detailPage++;
|
|
|
+ _loadDetails();
|
|
|
+ }
|
|
|
+ : null,
|
|
|
+ child: Icon(
|
|
|
+ Icons.chevron_right,
|
|
|
+ size: 20,
|
|
|
+ color: _detailPage < totalPages
|
|
|
+ ? colors.textPrimary
|
|
|
+ : colors.textPlaceholder,
|
|
|
+ ),
|
|
|
+ ),
|
|
|
+ const SizedBox(width: 16),
|
|
|
+ Text(
|
|
|
+ '${l10n.get('total')} $_detailTotal ${l10n.get('items')}',
|
|
|
+ style: TextStyle(
|
|
|
+ fontSize: 12,
|
|
|
+ color: colors.textSecondary,
|
|
|
+ ),
|
|
|
+ ),
|
|
|
+ ],
|
|
|
),
|
|
|
- ),
|
|
|
- ],
|
|
|
- ),
|
|
|
- ),
|
|
|
- const SizedBox(height: 8),
|
|
|
+ )
|
|
|
+ : null,
|
|
|
+ ),
|
|
|
],
|
|
|
),
|
|
|
),
|