264 lines
8.5 KiB
Dart
264 lines
8.5 KiB
Dart
import 'package:flutter/material.dart';
|
|
import '../../../../app/theme/app_colors.dart';
|
|
|
|
/// 核销管理页面
|
|
///
|
|
/// 扫码核销 + 手动输入券码 + 批量核销 + 核销记录
|
|
class RedemptionPage extends StatelessWidget {
|
|
const RedemptionPage({super.key});
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
return DefaultTabController(
|
|
length: 2,
|
|
child: Scaffold(
|
|
appBar: AppBar(
|
|
title: const Text('核销管理'),
|
|
bottom: const TabBar(
|
|
tabs: [
|
|
Tab(text: '扫码核销'),
|
|
Tab(text: '核销记录'),
|
|
],
|
|
),
|
|
),
|
|
body: const TabBarView(
|
|
children: [
|
|
_ScanRedeemTab(),
|
|
_RedeemHistoryTab(),
|
|
],
|
|
),
|
|
),
|
|
);
|
|
}
|
|
}
|
|
|
|
class _ScanRedeemTab extends StatelessWidget {
|
|
const _ScanRedeemTab();
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
return SingleChildScrollView(
|
|
padding: const EdgeInsets.all(20),
|
|
child: Column(
|
|
children: [
|
|
// Scan Area
|
|
Container(
|
|
height: 260,
|
|
decoration: BoxDecoration(
|
|
color: AppColors.gray900,
|
|
borderRadius: BorderRadius.circular(16),
|
|
),
|
|
child: Center(
|
|
child: Column(
|
|
mainAxisAlignment: MainAxisAlignment.center,
|
|
children: [
|
|
Container(
|
|
width: 160,
|
|
height: 160,
|
|
decoration: BoxDecoration(
|
|
border: Border.all(color: AppColors.primary, width: 2),
|
|
borderRadius: BorderRadius.circular(12),
|
|
),
|
|
child: const Icon(Icons.qr_code_scanner_rounded, color: AppColors.primary, size: 64),
|
|
),
|
|
const SizedBox(height: 16),
|
|
const Text('将券码对准扫描框', style: TextStyle(color: Colors.white70, fontSize: 14)),
|
|
],
|
|
),
|
|
),
|
|
),
|
|
const SizedBox(height: 20),
|
|
|
|
// Manual Input
|
|
Row(
|
|
children: [
|
|
Expanded(
|
|
child: TextField(
|
|
decoration: const InputDecoration(
|
|
hintText: '手动输入券码',
|
|
prefixIcon: Icon(Icons.keyboard_rounded),
|
|
),
|
|
),
|
|
),
|
|
const SizedBox(width: 12),
|
|
SizedBox(
|
|
height: 52,
|
|
child: ElevatedButton(
|
|
onPressed: () => _showRedeemConfirm(context),
|
|
child: const Text('核销'),
|
|
),
|
|
),
|
|
],
|
|
),
|
|
const SizedBox(height: 20),
|
|
|
|
// Batch Redeem
|
|
OutlinedButton.icon(
|
|
onPressed: () => _showBatchRedeem(context),
|
|
icon: const Icon(Icons.list_alt_rounded),
|
|
label: const Text('批量核销'),
|
|
style: OutlinedButton.styleFrom(
|
|
minimumSize: const Size(double.infinity, 48),
|
|
),
|
|
),
|
|
const SizedBox(height: 24),
|
|
|
|
// Today Stats
|
|
Container(
|
|
padding: const EdgeInsets.all(16),
|
|
decoration: BoxDecoration(
|
|
color: AppColors.surface,
|
|
borderRadius: BorderRadius.circular(12),
|
|
border: Border.all(color: AppColors.borderLight),
|
|
),
|
|
child: const Column(
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: [
|
|
Text('今日核销', style: TextStyle(fontSize: 15, fontWeight: FontWeight.w600)),
|
|
SizedBox(height: 12),
|
|
Row(
|
|
mainAxisAlignment: MainAxisAlignment.spaceAround,
|
|
children: [
|
|
_StatItem(label: '核销次数', value: '45'),
|
|
_StatItem(label: '核销金额', value: '\$1,125'),
|
|
_StatItem(label: '门店数', value: '3'),
|
|
],
|
|
),
|
|
],
|
|
),
|
|
),
|
|
],
|
|
),
|
|
);
|
|
}
|
|
|
|
void _showRedeemConfirm(BuildContext context) {
|
|
showModalBottomSheet(
|
|
context: context,
|
|
builder: (ctx) => Container(
|
|
padding: const EdgeInsets.all(24),
|
|
child: Column(
|
|
mainAxisSize: MainAxisSize.min,
|
|
children: [
|
|
const Icon(Icons.check_circle_rounded, color: AppColors.success, size: 56),
|
|
const SizedBox(height: 16),
|
|
const Text('核销确认', style: TextStyle(fontSize: 20, fontWeight: FontWeight.w600)),
|
|
const SizedBox(height: 8),
|
|
const Text('¥25 星巴克礼品卡', style: TextStyle(color: AppColors.textSecondary)),
|
|
const SizedBox(height: 24),
|
|
SizedBox(
|
|
width: double.infinity,
|
|
child: ElevatedButton(
|
|
onPressed: () => Navigator.pop(ctx),
|
|
child: const Text('确认核销'),
|
|
),
|
|
),
|
|
const SizedBox(height: 12),
|
|
],
|
|
),
|
|
),
|
|
);
|
|
}
|
|
|
|
void _showBatchRedeem(BuildContext context) {
|
|
showModalBottomSheet(
|
|
context: context,
|
|
isScrollControlled: true,
|
|
builder: (ctx) => DraggableScrollableSheet(
|
|
expand: false,
|
|
initialChildSize: 0.6,
|
|
builder: (_, controller) => Padding(
|
|
padding: const EdgeInsets.all(24),
|
|
child: Column(
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: [
|
|
const Text('批量核销', style: TextStyle(fontSize: 20, fontWeight: FontWeight.w600)),
|
|
const SizedBox(height: 8),
|
|
const Text('输入多个券码,每行一个', style: TextStyle(color: AppColors.textSecondary)),
|
|
const SizedBox(height: 16),
|
|
Expanded(
|
|
child: TextField(
|
|
maxLines: null,
|
|
expands: true,
|
|
textAlignVertical: TextAlignVertical.top,
|
|
decoration: const InputDecoration(
|
|
hintText: '粘贴券码列表...',
|
|
border: OutlineInputBorder(),
|
|
),
|
|
),
|
|
),
|
|
const SizedBox(height: 16),
|
|
SizedBox(
|
|
width: double.infinity,
|
|
child: ElevatedButton(
|
|
onPressed: () => Navigator.pop(ctx),
|
|
child: const Text('批量核销'),
|
|
),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
),
|
|
);
|
|
}
|
|
}
|
|
|
|
class _RedeemHistoryTab extends StatelessWidget {
|
|
const _RedeemHistoryTab();
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
final records = [
|
|
('¥25 礼品卡', '门店A · 收银员张三', '10分钟前', true),
|
|
('¥100 购物券', '门店B · 收银员李四', '25分钟前', true),
|
|
('¥50 生活券', '手动输入', '1小时前', true),
|
|
('¥25 礼品卡', '门店A · 扫码', '2小时前', false),
|
|
];
|
|
|
|
return ListView.separated(
|
|
padding: const EdgeInsets.all(20),
|
|
itemCount: records.length,
|
|
separatorBuilder: (_, __) => const Divider(height: 1),
|
|
itemBuilder: (context, index) {
|
|
final (name, source, time, success) = records[index];
|
|
return ListTile(
|
|
contentPadding: const EdgeInsets.symmetric(vertical: 8),
|
|
leading: Container(
|
|
width: 40,
|
|
height: 40,
|
|
decoration: BoxDecoration(
|
|
color: success ? AppColors.successLight : AppColors.errorLight,
|
|
borderRadius: BorderRadius.circular(10),
|
|
),
|
|
child: Icon(
|
|
success ? Icons.check_rounded : Icons.close_rounded,
|
|
color: success ? AppColors.success : AppColors.error,
|
|
size: 20,
|
|
),
|
|
),
|
|
title: Text(name, style: const TextStyle(fontWeight: FontWeight.w500)),
|
|
subtitle: Text(source, style: const TextStyle(fontSize: 12, color: AppColors.textSecondary)),
|
|
trailing: Text(time, style: const TextStyle(fontSize: 11, color: AppColors.textTertiary)),
|
|
);
|
|
},
|
|
);
|
|
}
|
|
}
|
|
|
|
class _StatItem extends StatelessWidget {
|
|
final String label;
|
|
final String value;
|
|
const _StatItem({required this.label, required this.value});
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
return Column(
|
|
children: [
|
|
Text(value, style: const TextStyle(fontSize: 20, fontWeight: FontWeight.w700, color: AppColors.primary)),
|
|
const SizedBox(height: 4),
|
|
Text(label, style: const TextStyle(fontSize: 12, color: AppColors.textTertiary)),
|
|
],
|
|
);
|
|
}
|
|
}
|