feat(mining-app): 添加补发记录功能
- 添加批量补发记录 API endpoint - 创建 BatchMiningRecord 实体和模型 - 添加批量补发记录 provider 和页面 - 在"我的"页面动态显示补发记录入口(仅当用户有记录时显示) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
parent
134e45e0bf
commit
d5dc248a16
|
|
@ -18,6 +18,8 @@ class ApiEndpoints {
|
|||
'/api/v2/mining/accounts/$accountSequence';
|
||||
static String miningRecords(String accountSequence) =>
|
||||
'/api/v2/mining/accounts/$accountSequence/records';
|
||||
static String batchMiningRecords(String accountSequence) =>
|
||||
'/api/v2/mining/batch-mining/records/$accountSequence';
|
||||
static const String globalState = '/api/v2/mining/global-state';
|
||||
static String realtimeEarning(String accountSequence) =>
|
||||
'/api/v2/mining/accounts/$accountSequence/realtime';
|
||||
|
|
|
|||
|
|
@ -13,6 +13,7 @@ import '../../presentation/pages/asset/asset_page.dart';
|
|||
import '../../presentation/pages/profile/profile_page.dart';
|
||||
import '../../presentation/pages/profile/edit_profile_page.dart';
|
||||
import '../../presentation/pages/profile/mining_records_page.dart';
|
||||
import '../../presentation/pages/profile/batch_mining_records_page.dart';
|
||||
import '../../presentation/pages/profile/planting_records_page.dart';
|
||||
import '../../presentation/pages/asset/send_shares_page.dart';
|
||||
import '../../presentation/pages/asset/receive_shares_page.dart';
|
||||
|
|
@ -119,6 +120,10 @@ final appRouterProvider = Provider<GoRouter>((ref) {
|
|||
path: Routes.miningRecords,
|
||||
builder: (context, state) => const MiningRecordsListPage(),
|
||||
),
|
||||
GoRoute(
|
||||
path: Routes.batchMiningRecords,
|
||||
builder: (context, state) => const BatchMiningRecordsPage(),
|
||||
),
|
||||
GoRoute(
|
||||
path: Routes.plantingRecords,
|
||||
builder: (context, state) => const PlantingRecordsPage(),
|
||||
|
|
|
|||
|
|
@ -10,6 +10,7 @@ class Routes {
|
|||
static const String profile = '/profile';
|
||||
static const String editProfile = '/edit-profile';
|
||||
static const String miningRecords = '/mining-records';
|
||||
static const String batchMiningRecords = '/batch-mining-records';
|
||||
static const String contributionRecords = '/contribution-records';
|
||||
static const String plantingRecords = '/planting-records';
|
||||
static const String orders = '/orders';
|
||||
|
|
|
|||
|
|
@ -2,10 +2,12 @@ import '../../models/share_account_model.dart';
|
|||
import '../../models/mining_record_model.dart';
|
||||
import '../../models/global_state_model.dart';
|
||||
import '../../models/planting_record_model.dart';
|
||||
import '../../models/batch_mining_record_model.dart';
|
||||
import '../../../core/network/api_client.dart';
|
||||
import '../../../core/network/api_endpoints.dart';
|
||||
import '../../../core/error/exceptions.dart';
|
||||
import '../../../domain/repositories/mining_repository.dart';
|
||||
import '../../../domain/entities/batch_mining_record.dart';
|
||||
|
||||
abstract class MiningRemoteDataSource {
|
||||
Future<ShareAccountModel> getShareAccount(String accountSequence);
|
||||
|
|
@ -20,6 +22,11 @@ abstract class MiningRemoteDataSource {
|
|||
int page = 1,
|
||||
int pageSize = 10,
|
||||
});
|
||||
Future<BatchMiningRecordsPage> getBatchMiningRecords(
|
||||
String accountSequence, {
|
||||
int page = 1,
|
||||
int pageSize = 20,
|
||||
});
|
||||
}
|
||||
|
||||
class MiningRemoteDataSourceImpl implements MiningRemoteDataSource {
|
||||
|
|
@ -99,4 +106,21 @@ class MiningRemoteDataSourceImpl implements MiningRemoteDataSource {
|
|||
throw ServerException(e.toString());
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Future<BatchMiningRecordsPage> getBatchMiningRecords(
|
||||
String accountSequence, {
|
||||
int page = 1,
|
||||
int pageSize = 20,
|
||||
}) async {
|
||||
try {
|
||||
final response = await client.get(
|
||||
ApiEndpoints.batchMiningRecords(accountSequence),
|
||||
queryParameters: {'page': page, 'pageSize': pageSize},
|
||||
);
|
||||
return BatchMiningRecordsPageModel.fromJson(response.data, page, pageSize);
|
||||
} catch (e) {
|
||||
throw ServerException(e.toString());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,62 @@
|
|||
import '../../domain/entities/batch_mining_record.dart';
|
||||
|
||||
class BatchMiningRecordModel extends BatchMiningRecord {
|
||||
const BatchMiningRecordModel({
|
||||
required super.id,
|
||||
required super.accountSequence,
|
||||
required super.batch,
|
||||
required super.phase,
|
||||
required super.treeCount,
|
||||
required super.daysInPhase,
|
||||
required super.preMineDays,
|
||||
required super.amount,
|
||||
super.remark,
|
||||
required super.createdAt,
|
||||
});
|
||||
|
||||
factory BatchMiningRecordModel.fromJson(Map<String, dynamic> json) {
|
||||
return BatchMiningRecordModel(
|
||||
id: json['id']?.toString() ?? '',
|
||||
accountSequence: json['accountSequence']?.toString() ?? '',
|
||||
batch: json['batch'] as int? ?? 0,
|
||||
phase: json['phase'] as int? ?? 0,
|
||||
treeCount: json['treeCount'] as int? ?? 0,
|
||||
daysInPhase: json['daysInPhase'] as int? ?? 0,
|
||||
preMineDays: json['preMineDays'] as int? ?? 0,
|
||||
amount: json['amount']?.toString() ?? '0',
|
||||
remark: json['remark']?.toString(),
|
||||
createdAt: json['createdAt'] != null
|
||||
? DateTime.parse(json['createdAt'].toString())
|
||||
: DateTime.now(),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class BatchMiningRecordsPageModel extends BatchMiningRecordsPage {
|
||||
const BatchMiningRecordsPageModel({
|
||||
required super.items,
|
||||
required super.total,
|
||||
required super.page,
|
||||
required super.pageSize,
|
||||
required super.totalPages,
|
||||
});
|
||||
|
||||
factory BatchMiningRecordsPageModel.fromJson(
|
||||
Map<String, dynamic> json,
|
||||
int page,
|
||||
int pageSize,
|
||||
) {
|
||||
final rawItems = json['data'] as List? ?? json['items'] as List? ?? [];
|
||||
final items = rawItems
|
||||
.map((item) => BatchMiningRecordModel.fromJson(item as Map<String, dynamic>))
|
||||
.toList();
|
||||
final total = json['total'] as int? ?? items.length;
|
||||
return BatchMiningRecordsPageModel(
|
||||
items: items,
|
||||
total: total,
|
||||
page: page,
|
||||
pageSize: pageSize,
|
||||
totalPages: (total / pageSize).ceil(),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
@ -2,6 +2,7 @@ import 'package:dartz/dartz.dart';
|
|||
import '../../domain/entities/share_account.dart';
|
||||
import '../../domain/entities/global_state.dart';
|
||||
import '../../domain/entities/planting_record.dart';
|
||||
import '../../domain/entities/batch_mining_record.dart';
|
||||
import '../../domain/repositories/mining_repository.dart';
|
||||
import '../../core/error/exceptions.dart';
|
||||
import '../../core/error/failures.dart';
|
||||
|
|
@ -90,4 +91,24 @@ class MiningRepositoryImpl implements MiningRepository {
|
|||
return Left(const NetworkFailure());
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Future<Either<Failure, BatchMiningRecordsPage>> getBatchMiningRecords(
|
||||
String accountSequence, {
|
||||
int page = 1,
|
||||
int pageSize = 20,
|
||||
}) async {
|
||||
try {
|
||||
final result = await remoteDataSource.getBatchMiningRecords(
|
||||
accountSequence,
|
||||
page: page,
|
||||
pageSize: pageSize,
|
||||
);
|
||||
return Right(result);
|
||||
} on ServerException catch (e) {
|
||||
return Left(ServerFailure(e.message));
|
||||
} on NetworkException {
|
||||
return Left(const NetworkFailure());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,59 @@
|
|||
import 'package:equatable/equatable.dart';
|
||||
|
||||
/// 批量补发记录
|
||||
class BatchMiningRecord extends Equatable {
|
||||
final String id;
|
||||
final String accountSequence;
|
||||
final int batch;
|
||||
final int phase;
|
||||
final int treeCount;
|
||||
final int daysInPhase;
|
||||
final int preMineDays;
|
||||
final String amount;
|
||||
final String? remark;
|
||||
final DateTime createdAt;
|
||||
|
||||
const BatchMiningRecord({
|
||||
required this.id,
|
||||
required this.accountSequence,
|
||||
required this.batch,
|
||||
required this.phase,
|
||||
required this.treeCount,
|
||||
required this.daysInPhase,
|
||||
required this.preMineDays,
|
||||
required this.amount,
|
||||
this.remark,
|
||||
required this.createdAt,
|
||||
});
|
||||
|
||||
@override
|
||||
List<Object?> get props => [
|
||||
id,
|
||||
accountSequence,
|
||||
batch,
|
||||
phase,
|
||||
treeCount,
|
||||
daysInPhase,
|
||||
preMineDays,
|
||||
amount,
|
||||
remark,
|
||||
createdAt,
|
||||
];
|
||||
}
|
||||
|
||||
/// 批量补发记录分页数据
|
||||
class BatchMiningRecordsPage {
|
||||
final List<BatchMiningRecord> items;
|
||||
final int total;
|
||||
final int page;
|
||||
final int pageSize;
|
||||
final int totalPages;
|
||||
|
||||
const BatchMiningRecordsPage({
|
||||
required this.items,
|
||||
required this.total,
|
||||
required this.page,
|
||||
required this.pageSize,
|
||||
required this.totalPages,
|
||||
});
|
||||
}
|
||||
|
|
@ -4,6 +4,7 @@ import '../entities/share_account.dart';
|
|||
import '../entities/mining_record.dart';
|
||||
import '../entities/global_state.dart';
|
||||
import '../entities/planting_record.dart';
|
||||
import '../entities/batch_mining_record.dart';
|
||||
|
||||
/// 挖矿记录分页数据
|
||||
class MiningRecordsPage {
|
||||
|
|
@ -38,4 +39,10 @@ abstract class MiningRepository {
|
|||
int page = 1,
|
||||
int pageSize = 10,
|
||||
});
|
||||
|
||||
Future<Either<Failure, BatchMiningRecordsPage>> getBatchMiningRecords(
|
||||
String accountSequence, {
|
||||
int page = 1,
|
||||
int pageSize = 20,
|
||||
});
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,383 @@
|
|||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:intl/intl.dart';
|
||||
import '../../../core/constants/app_colors.dart';
|
||||
import '../../../core/utils/format_utils.dart';
|
||||
import '../../../domain/entities/batch_mining_record.dart';
|
||||
import '../../providers/user_providers.dart';
|
||||
import '../../providers/mining_providers.dart';
|
||||
|
||||
/// 补发记录页面
|
||||
class BatchMiningRecordsPage extends ConsumerStatefulWidget {
|
||||
const BatchMiningRecordsPage({super.key});
|
||||
|
||||
@override
|
||||
ConsumerState<BatchMiningRecordsPage> createState() => _BatchMiningRecordsPageState();
|
||||
}
|
||||
|
||||
class _BatchMiningRecordsPageState extends ConsumerState<BatchMiningRecordsPage> {
|
||||
static const Color _orange = Color(0xFFFF6B00);
|
||||
static const Color _green = Color(0xFF22C55E);
|
||||
static const Color _grayText = Color(0xFF6B7280);
|
||||
static const Color _darkText = Color(0xFF1F2937);
|
||||
|
||||
int _currentPage = 1;
|
||||
static const int _pageSize = 20;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final user = ref.watch(userNotifierProvider);
|
||||
final accountSequence = user.accountSequence ?? '';
|
||||
|
||||
final recordsParams = BatchMiningRecordsParams(
|
||||
accountSequence: accountSequence,
|
||||
page: _currentPage,
|
||||
pageSize: _pageSize,
|
||||
);
|
||||
final recordsAsync = ref.watch(batchMiningRecordsProvider(recordsParams));
|
||||
|
||||
return Scaffold(
|
||||
backgroundColor: const Color(0xFFF5F5F5),
|
||||
appBar: AppBar(
|
||||
backgroundColor: Colors.white,
|
||||
elevation: 0,
|
||||
leading: IconButton(
|
||||
icon: const Icon(Icons.arrow_back_ios, color: _darkText, size: 20),
|
||||
onPressed: () => Navigator.of(context).pop(),
|
||||
),
|
||||
title: const Text(
|
||||
'补发记录',
|
||||
style: TextStyle(
|
||||
fontSize: 18,
|
||||
fontWeight: FontWeight.w600,
|
||||
color: _darkText,
|
||||
),
|
||||
),
|
||||
centerTitle: true,
|
||||
),
|
||||
body: RefreshIndicator(
|
||||
onRefresh: () async {
|
||||
setState(() => _currentPage = 1);
|
||||
ref.invalidate(batchMiningRecordsProvider(recordsParams));
|
||||
},
|
||||
child: recordsAsync.when(
|
||||
loading: () => _buildLoadingList(),
|
||||
error: (error, stack) => _buildErrorView(error.toString(), recordsParams),
|
||||
data: (recordsPage) {
|
||||
if (recordsPage == null || recordsPage.items.isEmpty) {
|
||||
return _buildEmptyView();
|
||||
}
|
||||
return _buildRecordsList(recordsPage);
|
||||
},
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildLoadingList() {
|
||||
return ListView.builder(
|
||||
padding: const EdgeInsets.all(16),
|
||||
itemCount: 10,
|
||||
itemBuilder: (context, index) => _buildShimmerCard(),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildShimmerCard() {
|
||||
return Container(
|
||||
margin: const EdgeInsets.only(bottom: 12),
|
||||
padding: const EdgeInsets.all(16),
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.white,
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Container(
|
||||
width: 120,
|
||||
height: 16,
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.grey[300],
|
||||
borderRadius: BorderRadius.circular(4),
|
||||
),
|
||||
),
|
||||
Container(
|
||||
width: 80,
|
||||
height: 20,
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.grey[300],
|
||||
borderRadius: BorderRadius.circular(4),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 12),
|
||||
Row(
|
||||
children: [
|
||||
Container(
|
||||
width: 60,
|
||||
height: 12,
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.grey[300],
|
||||
borderRadius: BorderRadius.circular(4),
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 16),
|
||||
Container(
|
||||
width: 80,
|
||||
height: 12,
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.grey[300],
|
||||
borderRadius: BorderRadius.circular(4),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildErrorView(String error, BatchMiningRecordsParams params) {
|
||||
return Center(
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
const Icon(Icons.error_outline, size: 48, color: AppColors.error),
|
||||
const SizedBox(height: 16),
|
||||
Text('加载失败', style: TextStyle(fontSize: 16, color: _grayText)),
|
||||
const SizedBox(height: 8),
|
||||
Text(error, style: TextStyle(fontSize: 12, color: _grayText)),
|
||||
const SizedBox(height: 16),
|
||||
ElevatedButton(
|
||||
onPressed: () {
|
||||
ref.invalidate(batchMiningRecordsProvider(params));
|
||||
},
|
||||
style: ElevatedButton.styleFrom(backgroundColor: _orange),
|
||||
child: const Text('重试', style: TextStyle(color: Colors.white)),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildEmptyView() {
|
||||
return Center(
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
Icon(Icons.inbox_outlined, size: 64, color: _grayText.withOpacity(0.5)),
|
||||
const SizedBox(height: 16),
|
||||
Text(
|
||||
'暂无补发记录',
|
||||
style: TextStyle(fontSize: 16, color: _grayText),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildRecordsList(BatchMiningRecordsPage recordsPage) {
|
||||
return ListView.builder(
|
||||
padding: const EdgeInsets.all(16),
|
||||
itemCount: recordsPage.items.length + 1,
|
||||
itemBuilder: (context, index) {
|
||||
if (index == recordsPage.items.length) {
|
||||
return _buildPaginationInfo(recordsPage);
|
||||
}
|
||||
return _buildRecordCard(recordsPage.items[index]);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildRecordCard(BatchMiningRecord record) {
|
||||
return Container(
|
||||
margin: const EdgeInsets.only(bottom: 12),
|
||||
padding: const EdgeInsets.all(16),
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.white,
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
// 第一行:批次/阶段信息 + 补发金额
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Row(
|
||||
children: [
|
||||
Container(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 4),
|
||||
decoration: BoxDecoration(
|
||||
color: _orange.withOpacity(0.1),
|
||||
borderRadius: BorderRadius.circular(4),
|
||||
),
|
||||
child: Text(
|
||||
'第${record.batch}批',
|
||||
style: const TextStyle(
|
||||
fontSize: 12,
|
||||
fontWeight: FontWeight.w500,
|
||||
color: _orange,
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 8),
|
||||
Container(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 4),
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.blue.withOpacity(0.1),
|
||||
borderRadius: BorderRadius.circular(4),
|
||||
),
|
||||
child: Text(
|
||||
'阶段${record.phase}',
|
||||
style: const TextStyle(
|
||||
fontSize: 12,
|
||||
fontWeight: FontWeight.w500,
|
||||
color: Colors.blue,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
Text(
|
||||
'+${formatAmount(record.amount)}',
|
||||
style: const TextStyle(
|
||||
fontSize: 18,
|
||||
fontWeight: FontWeight.bold,
|
||||
color: _green,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 12),
|
||||
|
||||
// 第二行:认种量 + 阶段天数
|
||||
Row(
|
||||
children: [
|
||||
Expanded(
|
||||
child: _buildInfoItem('认种量', '${record.treeCount} 棵'),
|
||||
),
|
||||
const SizedBox(width: 16),
|
||||
Expanded(
|
||||
child: _buildInfoItem('阶段天数', '${record.daysInPhase} 天'),
|
||||
),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
|
||||
// 第三行:提前天数
|
||||
Row(
|
||||
children: [
|
||||
Expanded(
|
||||
child: _buildInfoItem('提前天数', '${record.preMineDays} 天'),
|
||||
),
|
||||
const SizedBox(width: 16),
|
||||
const Expanded(child: SizedBox()),
|
||||
],
|
||||
),
|
||||
|
||||
// 备注(如果有)
|
||||
if (record.remark != null && record.remark!.isNotEmpty) ...[
|
||||
const SizedBox(height: 8),
|
||||
Row(
|
||||
children: [
|
||||
Icon(Icons.note_outlined, size: 12, color: _grayText.withOpacity(0.7)),
|
||||
const SizedBox(width: 4),
|
||||
Expanded(
|
||||
child: Text(
|
||||
record.remark!,
|
||||
style: TextStyle(fontSize: 11, color: _grayText.withOpacity(0.7)),
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
|
||||
const SizedBox(height: 8),
|
||||
// 记录时间
|
||||
Row(
|
||||
children: [
|
||||
Icon(Icons.access_time, size: 12, color: _grayText.withOpacity(0.7)),
|
||||
const SizedBox(width: 4),
|
||||
Text(
|
||||
DateFormat('yyyy-MM-dd HH:mm:ss').format(record.createdAt),
|
||||
style: TextStyle(fontSize: 11, color: _grayText.withOpacity(0.7)),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildInfoItem(String label, String value) {
|
||||
return Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Text(
|
||||
'$label: ',
|
||||
style: TextStyle(fontSize: 12, color: _grayText.withOpacity(0.7)),
|
||||
),
|
||||
Flexible(
|
||||
child: Text(
|
||||
value,
|
||||
style: const TextStyle(
|
||||
fontSize: 12,
|
||||
color: _darkText,
|
||||
fontWeight: FontWeight.w500,
|
||||
),
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildPaginationInfo(BatchMiningRecordsPage recordsPage) {
|
||||
final totalPages = recordsPage.totalPages > 0
|
||||
? recordsPage.totalPages
|
||||
: (recordsPage.total / _pageSize).ceil();
|
||||
|
||||
return Container(
|
||||
padding: const EdgeInsets.symmetric(vertical: 16),
|
||||
child: Column(
|
||||
children: [
|
||||
Text(
|
||||
'共 ${recordsPage.total} 条记录,第 $_currentPage / $totalPages 页',
|
||||
style: TextStyle(fontSize: 12, color: _grayText),
|
||||
),
|
||||
const SizedBox(height: 12),
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
if (_currentPage > 1)
|
||||
TextButton.icon(
|
||||
onPressed: () {
|
||||
setState(() => _currentPage--);
|
||||
},
|
||||
icon: const Icon(Icons.chevron_left, size: 18),
|
||||
label: const Text('上一页'),
|
||||
style: TextButton.styleFrom(foregroundColor: _orange),
|
||||
),
|
||||
const SizedBox(width: 16),
|
||||
if (_currentPage < totalPages)
|
||||
TextButton.icon(
|
||||
onPressed: () {
|
||||
setState(() => _currentPage++);
|
||||
},
|
||||
icon: const Text('下一页'),
|
||||
label: const Icon(Icons.chevron_right, size: 18),
|
||||
style: TextButton.styleFrom(foregroundColor: _orange),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
@ -5,6 +5,7 @@ import '../../../core/router/routes.dart';
|
|||
import '../../providers/user_providers.dart';
|
||||
import '../../providers/profile_providers.dart';
|
||||
import '../../providers/settings_providers.dart';
|
||||
import '../../providers/mining_providers.dart';
|
||||
import '../../widgets/shimmer_loading.dart';
|
||||
|
||||
class ProfilePage extends ConsumerWidget {
|
||||
|
|
@ -89,7 +90,7 @@ class ProfilePage extends ConsumerWidget {
|
|||
// const SizedBox(height: 16),
|
||||
|
||||
// 记录入口
|
||||
_buildRecordsSection(context),
|
||||
_buildRecordsSection(context, ref, user.accountSequence ?? ''),
|
||||
|
||||
const SizedBox(height: 16),
|
||||
|
||||
|
|
@ -380,7 +381,45 @@ class ProfilePage extends ConsumerWidget {
|
|||
);
|
||||
}
|
||||
|
||||
Widget _buildRecordsSection(BuildContext context) {
|
||||
Widget _buildRecordsSection(BuildContext context, WidgetRef ref, String accountSequence) {
|
||||
// 检查是否有批量补发记录
|
||||
final hasBatchRecordsAsync = ref.watch(hasBatchMiningRecordsProvider(accountSequence));
|
||||
final hasBatchRecords = hasBatchRecordsAsync.valueOrNull ?? false;
|
||||
|
||||
// 构建记录图标列表
|
||||
final recordIcons = <Widget>[
|
||||
_buildRecordIcon(
|
||||
context: context,
|
||||
icon: Icons.eco,
|
||||
label: '参与记录',
|
||||
onTap: () => context.push(Routes.plantingRecords),
|
||||
),
|
||||
_buildRecordIcon(
|
||||
context: context,
|
||||
icon: Icons.assignment,
|
||||
label: '分配记录',
|
||||
onTap: () => context.push(Routes.miningRecords),
|
||||
),
|
||||
_buildRecordIcon(
|
||||
context: context,
|
||||
icon: Icons.receipt_long,
|
||||
label: '交易记录',
|
||||
onTap: () => context.push(Routes.tradingRecords),
|
||||
),
|
||||
];
|
||||
|
||||
// 如果有批量补发记录,添加补发记录入口
|
||||
if (hasBatchRecords) {
|
||||
recordIcons.add(
|
||||
_buildRecordIcon(
|
||||
context: context,
|
||||
icon: Icons.card_giftcard,
|
||||
label: '补发记录',
|
||||
onTap: () => context.push(Routes.batchMiningRecords),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
return Container(
|
||||
margin: const EdgeInsets.symmetric(horizontal: 16),
|
||||
padding: const EdgeInsets.all(16),
|
||||
|
|
@ -400,29 +439,19 @@ class ProfilePage extends ConsumerWidget {
|
|||
),
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceAround,
|
||||
children: [
|
||||
_buildRecordIcon(
|
||||
context: context,
|
||||
icon: Icons.eco,
|
||||
label: '参与记录',
|
||||
onTap: () => context.push(Routes.plantingRecords),
|
||||
),
|
||||
_buildRecordIcon(
|
||||
context: context,
|
||||
icon: Icons.assignment,
|
||||
label: '分配记录',
|
||||
onTap: () => context.push(Routes.miningRecords),
|
||||
),
|
||||
_buildRecordIcon(
|
||||
context: context,
|
||||
icon: Icons.receipt_long,
|
||||
label: '交易记录',
|
||||
onTap: () => context.push(Routes.tradingRecords),
|
||||
),
|
||||
],
|
||||
),
|
||||
// 使用 Wrap 或根据数量动态布局
|
||||
if (recordIcons.length <= 4)
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceAround,
|
||||
children: recordIcons,
|
||||
)
|
||||
else
|
||||
Wrap(
|
||||
alignment: WrapAlignment.spaceAround,
|
||||
spacing: 16,
|
||||
runSpacing: 16,
|
||||
children: recordIcons,
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@ import 'package:flutter_riverpod/flutter_riverpod.dart';
|
|||
import '../../domain/entities/share_account.dart';
|
||||
import '../../domain/entities/global_state.dart';
|
||||
import '../../domain/entities/planting_record.dart';
|
||||
import '../../domain/entities/batch_mining_record.dart';
|
||||
import '../../domain/repositories/mining_repository.dart';
|
||||
import '../../domain/usecases/mining/get_share_account.dart';
|
||||
import '../../domain/usecases/mining/get_global_state.dart';
|
||||
|
|
@ -177,3 +178,82 @@ final plantingRecordsProvider = FutureProvider.family<PlantingLedgerPage?, Plant
|
|||
);
|
||||
},
|
||||
);
|
||||
|
||||
/// 批量补发记录请求参数
|
||||
class BatchMiningRecordsParams {
|
||||
final String accountSequence;
|
||||
final int page;
|
||||
final int pageSize;
|
||||
|
||||
const BatchMiningRecordsParams({
|
||||
required this.accountSequence,
|
||||
this.page = 1,
|
||||
this.pageSize = 20,
|
||||
});
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) =>
|
||||
identical(this, other) ||
|
||||
other is BatchMiningRecordsParams &&
|
||||
runtimeType == other.runtimeType &&
|
||||
accountSequence == other.accountSequence &&
|
||||
page == other.page &&
|
||||
pageSize == other.pageSize;
|
||||
|
||||
@override
|
||||
int get hashCode => accountSequence.hashCode ^ page.hashCode ^ pageSize.hashCode;
|
||||
}
|
||||
|
||||
/// 批量补发记录 Provider
|
||||
final batchMiningRecordsProvider = FutureProvider.family<BatchMiningRecordsPage?, BatchMiningRecordsParams>(
|
||||
(ref, params) async {
|
||||
if (params.accountSequence.isEmpty) {
|
||||
return null;
|
||||
}
|
||||
|
||||
final repository = ref.watch(miningRepositoryProvider);
|
||||
final result = await repository.getBatchMiningRecords(
|
||||
params.accountSequence,
|
||||
page: params.page,
|
||||
pageSize: params.pageSize,
|
||||
);
|
||||
|
||||
ref.keepAlive();
|
||||
final timer = Timer(const Duration(minutes: 5), () {
|
||||
ref.invalidateSelf();
|
||||
});
|
||||
ref.onDispose(() => timer.cancel());
|
||||
|
||||
return result.fold(
|
||||
(failure) => throw Exception(failure.message),
|
||||
(records) => records,
|
||||
);
|
||||
},
|
||||
);
|
||||
|
||||
/// 检查用户是否有批量补发记录的 Provider
|
||||
final hasBatchMiningRecordsProvider = FutureProvider.family<bool, String>(
|
||||
(ref, accountSequence) async {
|
||||
if (accountSequence.isEmpty) {
|
||||
return false;
|
||||
}
|
||||
|
||||
final repository = ref.watch(miningRepositoryProvider);
|
||||
final result = await repository.getBatchMiningRecords(
|
||||
accountSequence,
|
||||
page: 1,
|
||||
pageSize: 1,
|
||||
);
|
||||
|
||||
ref.keepAlive();
|
||||
final timer = Timer(const Duration(minutes: 10), () {
|
||||
ref.invalidateSelf();
|
||||
});
|
||||
ref.onDispose(() => timer.cancel());
|
||||
|
||||
return result.fold(
|
||||
(failure) => false,
|
||||
(records) => records.total > 0,
|
||||
);
|
||||
},
|
||||
);
|
||||
|
|
|
|||
Loading…
Reference in New Issue