631 lines
20 KiB
Dart
631 lines
20 KiB
Dart
import 'package:flutter/material.dart';
|
|
import 'package:flutter/services.dart';
|
|
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
|
import '../../../../core/theme/app_colors.dart';
|
|
import '../providers/referral_providers.dart';
|
|
import '../../domain/models/referral_info.dart';
|
|
|
|
class ReferralScreen extends ConsumerWidget {
|
|
const ReferralScreen({super.key});
|
|
|
|
@override
|
|
Widget build(BuildContext context, WidgetRef ref) {
|
|
final infoAsync = ref.watch(referralInfoProvider);
|
|
final isDark = Theme.of(context).brightness == Brightness.dark;
|
|
final cardColor = isDark ? AppColors.surface : Colors.white;
|
|
|
|
return Scaffold(
|
|
backgroundColor: AppColors.background,
|
|
appBar: AppBar(
|
|
backgroundColor: AppColors.background,
|
|
title: const Text('邀请有礼'),
|
|
actions: [
|
|
IconButton(
|
|
icon: const Icon(Icons.refresh),
|
|
onPressed: () {
|
|
ref.invalidate(referralInfoProvider);
|
|
ref.invalidate(referralListProvider);
|
|
ref.invalidate(pendingRewardsProvider);
|
|
},
|
|
),
|
|
],
|
|
),
|
|
body: infoAsync.when(
|
|
loading: () => const Center(child: CircularProgressIndicator()),
|
|
error: (e, _) => Center(child: Text('加载失败: $e')),
|
|
data: (info) => _ReferralBody(info: info, cardColor: cardColor),
|
|
),
|
|
);
|
|
}
|
|
}
|
|
|
|
class _ReferralBody extends ConsumerWidget {
|
|
final ReferralInfo info;
|
|
final Color cardColor;
|
|
|
|
const _ReferralBody({required this.info, required this.cardColor});
|
|
|
|
@override
|
|
Widget build(BuildContext context, WidgetRef ref) {
|
|
return ListView(
|
|
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8),
|
|
children: [
|
|
// ── Referral Code Card ─────────────────────────────────────
|
|
_ReferralCodeCard(info: info, cardColor: cardColor),
|
|
const SizedBox(height: 16),
|
|
|
|
// ── Stats Row ─────────────────────────────────────────────
|
|
_StatsRow(info: info, cardColor: cardColor),
|
|
const SizedBox(height: 20),
|
|
|
|
// ── Reward Rules ──────────────────────────────────────────
|
|
_RewardRulesCard(cardColor: cardColor),
|
|
const SizedBox(height: 20),
|
|
|
|
// ── Referral List ─────────────────────────────────────────
|
|
Row(
|
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
|
children: [
|
|
const Text(
|
|
'推荐记录',
|
|
style: TextStyle(fontWeight: FontWeight.w600, fontSize: 16),
|
|
),
|
|
TextButton(
|
|
onPressed: () => _showReferralList(context),
|
|
child: const Text('查看全部 >'),
|
|
),
|
|
],
|
|
),
|
|
_ReferralPreviewList(cardColor: cardColor),
|
|
const SizedBox(height: 20),
|
|
|
|
// ── Reward List ───────────────────────────────────────────
|
|
Row(
|
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
|
children: [
|
|
const Text(
|
|
'待领积分',
|
|
style: TextStyle(fontWeight: FontWeight.w600, fontSize: 16),
|
|
),
|
|
TextButton(
|
|
onPressed: () => _showRewardList(context),
|
|
child: const Text('查看全部 >'),
|
|
),
|
|
],
|
|
),
|
|
_RewardPreviewList(cardColor: cardColor),
|
|
const SizedBox(height: 40),
|
|
],
|
|
);
|
|
}
|
|
|
|
void _showReferralList(BuildContext context) {
|
|
Navigator.push(
|
|
context,
|
|
MaterialPageRoute(builder: (_) => const _ReferralListPage()),
|
|
);
|
|
}
|
|
|
|
void _showRewardList(BuildContext context) {
|
|
Navigator.push(
|
|
context,
|
|
MaterialPageRoute(builder: (_) => const _RewardListPage()),
|
|
);
|
|
}
|
|
}
|
|
|
|
// ── Referral Code Card ────────────────────────────────────────────────────────
|
|
|
|
class _ReferralCodeCard extends StatelessWidget {
|
|
final ReferralInfo info;
|
|
final Color cardColor;
|
|
|
|
const _ReferralCodeCard({required this.info, required this.cardColor});
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
return Card(
|
|
color: cardColor,
|
|
elevation: 0,
|
|
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(16)),
|
|
child: Padding(
|
|
padding: const EdgeInsets.all(20),
|
|
child: Column(
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: [
|
|
const Text(
|
|
'你的推荐码',
|
|
style: TextStyle(color: Colors.grey, fontSize: 13),
|
|
),
|
|
const SizedBox(height: 8),
|
|
Row(
|
|
children: [
|
|
Expanded(
|
|
child: Text(
|
|
info.referralCode,
|
|
style: const TextStyle(
|
|
fontSize: 28,
|
|
fontWeight: FontWeight.bold,
|
|
letterSpacing: 2,
|
|
color: AppColors.primary,
|
|
),
|
|
),
|
|
),
|
|
IconButton(
|
|
icon: const Icon(Icons.copy, color: AppColors.primary),
|
|
tooltip: '复制推荐码',
|
|
onPressed: () => _copy(context, info.referralCode),
|
|
),
|
|
],
|
|
),
|
|
const SizedBox(height: 12),
|
|
Row(
|
|
children: [
|
|
Expanded(
|
|
child: OutlinedButton.icon(
|
|
icon: const Icon(Icons.link, size: 18),
|
|
label: const Text('复制邀请链接'),
|
|
onPressed: () => _copy(context, info.shareUrl),
|
|
style: OutlinedButton.styleFrom(
|
|
foregroundColor: AppColors.primary,
|
|
side: const BorderSide(color: AppColors.primary),
|
|
shape: RoundedRectangleBorder(
|
|
borderRadius: BorderRadius.circular(10)),
|
|
),
|
|
),
|
|
),
|
|
const SizedBox(width: 8),
|
|
Expanded(
|
|
child: FilledButton.icon(
|
|
icon: const Icon(Icons.share, size: 18),
|
|
label: const Text('分享'),
|
|
onPressed: () => _share(context, info),
|
|
style: FilledButton.styleFrom(
|
|
backgroundColor: AppColors.primary,
|
|
shape: RoundedRectangleBorder(
|
|
borderRadius: BorderRadius.circular(10)),
|
|
),
|
|
),
|
|
),
|
|
],
|
|
),
|
|
],
|
|
),
|
|
),
|
|
);
|
|
}
|
|
|
|
void _copy(BuildContext context, String text) {
|
|
Clipboard.setData(ClipboardData(text: text));
|
|
ScaffoldMessenger.of(context).showSnackBar(
|
|
const SnackBar(content: Text('已复制到剪贴板'), duration: Duration(seconds: 2)),
|
|
);
|
|
}
|
|
|
|
void _share(BuildContext context, ReferralInfo info) {
|
|
// For a full implementation, use share_plus package
|
|
// For now, copy the share text
|
|
final text = '邀请你使用 IT0 智能运维平台,注册即可获得积分奖励!\n推荐码:${info.referralCode}\n链接:${info.shareUrl}';
|
|
_copy(context, text);
|
|
}
|
|
}
|
|
|
|
// ── Stats Row ─────────────────────────────────────────────────────────────────
|
|
|
|
class _StatsRow extends StatelessWidget {
|
|
final ReferralInfo info;
|
|
final Color cardColor;
|
|
|
|
const _StatsRow({required this.info, required this.cardColor});
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
return Row(
|
|
children: [
|
|
_StatCard(
|
|
cardColor: cardColor,
|
|
label: '已推荐',
|
|
value: '${info.directCount}',
|
|
unit: '人',
|
|
color: const Color(0xFF6366F1),
|
|
),
|
|
const SizedBox(width: 10),
|
|
_StatCard(
|
|
cardColor: cardColor,
|
|
label: '已激活',
|
|
value: '${info.activeCount}',
|
|
unit: '人',
|
|
color: const Color(0xFF10B981),
|
|
),
|
|
const SizedBox(width: 10),
|
|
_StatCard(
|
|
cardColor: cardColor,
|
|
label: '待领积分',
|
|
value: info.pendingCreditFormatted,
|
|
unit: '',
|
|
color: const Color(0xFFF59E0B),
|
|
),
|
|
],
|
|
);
|
|
}
|
|
}
|
|
|
|
class _StatCard extends StatelessWidget {
|
|
final Color cardColor;
|
|
final String label;
|
|
final String value;
|
|
final String unit;
|
|
final Color color;
|
|
|
|
const _StatCard({
|
|
required this.cardColor,
|
|
required this.label,
|
|
required this.value,
|
|
required this.unit,
|
|
required this.color,
|
|
});
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
return Expanded(
|
|
child: Card(
|
|
color: cardColor,
|
|
elevation: 0,
|
|
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)),
|
|
child: Padding(
|
|
padding: const EdgeInsets.symmetric(vertical: 16, horizontal: 12),
|
|
child: Column(
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: [
|
|
Text(label,
|
|
style: const TextStyle(fontSize: 12, color: Colors.grey)),
|
|
const SizedBox(height: 4),
|
|
RichText(
|
|
text: TextSpan(
|
|
children: [
|
|
TextSpan(
|
|
text: value,
|
|
style: TextStyle(
|
|
fontSize: 22,
|
|
fontWeight: FontWeight.bold,
|
|
color: color,
|
|
),
|
|
),
|
|
if (unit.isNotEmpty)
|
|
TextSpan(
|
|
text: unit,
|
|
style: TextStyle(
|
|
fontSize: 13, color: color.withAlpha(180)),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
),
|
|
);
|
|
}
|
|
}
|
|
|
|
// ── Reward Rules Card ─────────────────────────────────────────────────────────
|
|
|
|
class _RewardRulesCard extends StatelessWidget {
|
|
final Color cardColor;
|
|
|
|
const _RewardRulesCard({required this.cardColor});
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
return Card(
|
|
color: cardColor,
|
|
elevation: 0,
|
|
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(16)),
|
|
child: Padding(
|
|
padding: const EdgeInsets.all(16),
|
|
child: Column(
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: [
|
|
const Row(
|
|
children: [
|
|
Icon(Icons.card_giftcard, color: Color(0xFFF59E0B), size: 20),
|
|
SizedBox(width: 6),
|
|
Text(
|
|
'奖励规则',
|
|
style: TextStyle(fontWeight: FontWeight.w600, fontSize: 15),
|
|
),
|
|
],
|
|
),
|
|
const SizedBox(height: 12),
|
|
_RuleItem(
|
|
icon: Icons.star_rounded,
|
|
color: const Color(0xFF6366F1),
|
|
text: '推荐 Pro 套餐:你获得 \$15 积分,对方获得 \$5 积分',
|
|
),
|
|
const SizedBox(height: 8),
|
|
_RuleItem(
|
|
icon: Icons.star_rounded,
|
|
color: const Color(0xFF7C3AED),
|
|
text: '推荐 Enterprise 套餐:你获得 \$50 积分,对方获得 \$20 积分',
|
|
),
|
|
const SizedBox(height: 8),
|
|
_RuleItem(
|
|
icon: Icons.repeat_rounded,
|
|
color: const Color(0xFF10B981),
|
|
text: '对方续订后,你持续获得每月付款额 10% 的积分,最长 12 个月',
|
|
),
|
|
const SizedBox(height: 8),
|
|
_RuleItem(
|
|
icon: Icons.account_balance_wallet_outlined,
|
|
color: const Color(0xFFF59E0B),
|
|
text: '积分自动抵扣你的下期账单',
|
|
),
|
|
],
|
|
),
|
|
),
|
|
);
|
|
}
|
|
}
|
|
|
|
class _RuleItem extends StatelessWidget {
|
|
final IconData icon;
|
|
final Color color;
|
|
final String text;
|
|
|
|
const _RuleItem({required this.icon, required this.color, required this.text});
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
return Row(
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: [
|
|
Icon(icon, color: color, size: 16),
|
|
const SizedBox(width: 8),
|
|
Expanded(
|
|
child: Text(text, style: const TextStyle(fontSize: 13, height: 1.4)),
|
|
),
|
|
],
|
|
);
|
|
}
|
|
}
|
|
|
|
// ── Preview Lists ─────────────────────────────────────────────────────────────
|
|
|
|
class _ReferralPreviewList extends ConsumerWidget {
|
|
final Color cardColor;
|
|
const _ReferralPreviewList({required this.cardColor});
|
|
|
|
@override
|
|
Widget build(BuildContext context, WidgetRef ref) {
|
|
final async = ref.watch(referralListProvider);
|
|
return async.when(
|
|
loading: () => const SizedBox(
|
|
height: 60,
|
|
child: Center(child: CircularProgressIndicator()),
|
|
),
|
|
error: (_, __) => const SizedBox.shrink(),
|
|
data: (result) {
|
|
if (result.items.isEmpty) {
|
|
return Card(
|
|
color: cardColor,
|
|
elevation: 0,
|
|
shape:
|
|
RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)),
|
|
child: const Padding(
|
|
padding: EdgeInsets.all(20),
|
|
child: Center(
|
|
child: Text(
|
|
'暂无推荐记录,分享推荐码邀请好友吧',
|
|
style: TextStyle(color: Colors.grey, fontSize: 13),
|
|
),
|
|
),
|
|
),
|
|
);
|
|
}
|
|
final preview = result.items.take(3).toList();
|
|
return Card(
|
|
color: cardColor,
|
|
elevation: 0,
|
|
shape:
|
|
RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)),
|
|
clipBehavior: Clip.antiAlias,
|
|
child: Column(
|
|
children: preview.map((item) => _ReferralTile(item: item)).toList(),
|
|
),
|
|
);
|
|
},
|
|
);
|
|
}
|
|
}
|
|
|
|
class _ReferralTile extends StatelessWidget {
|
|
final ReferralItem item;
|
|
const _ReferralTile({required this.item});
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
final statusColor = item.isActive
|
|
? const Color(0xFF10B981)
|
|
: item.status == 'EXPIRED'
|
|
? Colors.grey
|
|
: const Color(0xFFF59E0B);
|
|
final statusLabel = switch (item.status) {
|
|
'PENDING' => '待付款',
|
|
'ACTIVE' => '已激活',
|
|
'REWARDED' => '已奖励',
|
|
'EXPIRED' => '已过期',
|
|
_ => item.status,
|
|
};
|
|
|
|
return ListTile(
|
|
leading: CircleAvatar(
|
|
backgroundColor: statusColor.withAlpha(30),
|
|
child: Icon(
|
|
item.isActive ? Icons.check_circle : Icons.pending,
|
|
color: statusColor,
|
|
size: 20,
|
|
),
|
|
),
|
|
title: Text(
|
|
item.referredTenantId.length > 8
|
|
? '${item.referredTenantId.substring(0, 8)}...'
|
|
: item.referredTenantId,
|
|
style: const TextStyle(fontSize: 14, fontWeight: FontWeight.w500),
|
|
),
|
|
subtitle: Text(
|
|
'注册于 ${_formatDate(item.registeredAt)}',
|
|
style: const TextStyle(fontSize: 12),
|
|
),
|
|
trailing: Container(
|
|
padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 4),
|
|
decoration: BoxDecoration(
|
|
color: statusColor.withAlpha(20),
|
|
borderRadius: BorderRadius.circular(20),
|
|
),
|
|
child: Text(
|
|
statusLabel,
|
|
style: TextStyle(
|
|
fontSize: 12, color: statusColor, fontWeight: FontWeight.w500),
|
|
),
|
|
),
|
|
);
|
|
}
|
|
|
|
String _formatDate(DateTime dt) =>
|
|
'${dt.year}-${dt.month.toString().padLeft(2, '0')}-${dt.day.toString().padLeft(2, '0')}';
|
|
}
|
|
|
|
class _RewardPreviewList extends ConsumerWidget {
|
|
final Color cardColor;
|
|
const _RewardPreviewList({required this.cardColor});
|
|
|
|
@override
|
|
Widget build(BuildContext context, WidgetRef ref) {
|
|
final async = ref.watch(pendingRewardsProvider);
|
|
return async.when(
|
|
loading: () => const SizedBox(
|
|
height: 60, child: Center(child: CircularProgressIndicator())),
|
|
error: (_, __) => const SizedBox.shrink(),
|
|
data: (result) {
|
|
if (result.items.isEmpty) {
|
|
return Card(
|
|
color: cardColor,
|
|
elevation: 0,
|
|
shape:
|
|
RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)),
|
|
child: const Padding(
|
|
padding: EdgeInsets.all(20),
|
|
child: Center(
|
|
child: Text(
|
|
'暂无待领积分',
|
|
style: TextStyle(color: Colors.grey, fontSize: 13),
|
|
),
|
|
),
|
|
),
|
|
);
|
|
}
|
|
final preview = result.items.take(3).toList();
|
|
return Card(
|
|
color: cardColor,
|
|
elevation: 0,
|
|
shape:
|
|
RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)),
|
|
clipBehavior: Clip.antiAlias,
|
|
child: Column(
|
|
children: preview
|
|
.map((item) => _RewardTile(item: item))
|
|
.toList(),
|
|
),
|
|
);
|
|
},
|
|
);
|
|
}
|
|
}
|
|
|
|
class _RewardTile extends StatelessWidget {
|
|
final RewardItem item;
|
|
const _RewardTile({required this.item});
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
return ListTile(
|
|
leading: CircleAvatar(
|
|
backgroundColor: const Color(0xFFF59E0B).withAlpha(30),
|
|
child: const Icon(Icons.attach_money,
|
|
color: Color(0xFFF59E0B), size: 20),
|
|
),
|
|
title: Text(
|
|
item.amountFormatted,
|
|
style: const TextStyle(
|
|
fontSize: 16,
|
|
fontWeight: FontWeight.bold,
|
|
color: Color(0xFF10B981)),
|
|
),
|
|
subtitle: Text(item.triggerLabel,
|
|
style: const TextStyle(fontSize: 12)),
|
|
trailing: Container(
|
|
padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 4),
|
|
decoration: BoxDecoration(
|
|
color: const Color(0xFFF59E0B).withAlpha(20),
|
|
borderRadius: BorderRadius.circular(20),
|
|
),
|
|
child: const Text(
|
|
'待抵扣',
|
|
style: TextStyle(
|
|
fontSize: 12,
|
|
color: Color(0xFFF59E0B),
|
|
fontWeight: FontWeight.w500),
|
|
),
|
|
),
|
|
);
|
|
}
|
|
}
|
|
|
|
// ── Full list pages ───────────────────────────────────────────────────────────
|
|
|
|
class _ReferralListPage extends ConsumerWidget {
|
|
const _ReferralListPage();
|
|
|
|
@override
|
|
Widget build(BuildContext context, WidgetRef ref) {
|
|
final async = ref.watch(referralListProvider);
|
|
return Scaffold(
|
|
appBar: AppBar(title: const Text('推荐记录')),
|
|
body: async.when(
|
|
loading: () => const Center(child: CircularProgressIndicator()),
|
|
error: (e, _) => Center(child: Text('加载失败: $e')),
|
|
data: (result) => result.items.isEmpty
|
|
? const Center(child: Text('暂无推荐记录'))
|
|
: ListView.separated(
|
|
itemCount: result.items.length,
|
|
separatorBuilder: (_, __) => const Divider(height: 1),
|
|
itemBuilder: (_, i) => _ReferralTile(item: result.items[i]),
|
|
),
|
|
),
|
|
);
|
|
}
|
|
}
|
|
|
|
class _RewardListPage extends ConsumerWidget {
|
|
const _RewardListPage();
|
|
|
|
@override
|
|
Widget build(BuildContext context, WidgetRef ref) {
|
|
final async = ref.watch(allRewardsProvider);
|
|
return Scaffold(
|
|
appBar: AppBar(title: const Text('奖励历史')),
|
|
body: async.when(
|
|
loading: () => const Center(child: CircularProgressIndicator()),
|
|
error: (e, _) => Center(child: Text('加载失败: $e')),
|
|
data: (result) => result.items.isEmpty
|
|
? const Center(child: Text('暂无奖励记录'))
|
|
: ListView.separated(
|
|
itemCount: result.items.length,
|
|
separatorBuilder: (_, __) => const Divider(height: 1),
|
|
itemBuilder: (_, i) => _RewardTile(item: result.items[i]),
|
|
),
|
|
),
|
|
);
|
|
}
|
|
}
|