feat(mining-app): add mining records and planting records pages
- Add mining records page showing distribution history with share amounts - Add planting records page with adoption summary and detailed records - Remove 推广奖励 and 收益明细 from profile page - Add planting-ledger API endpoint and data models Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
parent
b81ae634a6
commit
546c0060da
|
|
@ -55,4 +55,8 @@ class ApiEndpoints {
|
|||
'/api/v2/contribution/accounts/$accountSequence';
|
||||
static String contributionRecords(String accountSequence) =>
|
||||
'/api/v2/contribution/accounts/$accountSequence/records';
|
||||
|
||||
// User Service 2.0 (Kong路由: /api/v2/users)
|
||||
static String plantingLedger(String accountSequence) =>
|
||||
'/api/v2/users/$accountSequence/planting-ledger';
|
||||
}
|
||||
|
|
|
|||
|
|
@ -11,6 +11,8 @@ import '../../presentation/pages/contribution/contribution_records_page.dart';
|
|||
import '../../presentation/pages/trading/trading_page.dart';
|
||||
import '../../presentation/pages/asset/asset_page.dart';
|
||||
import '../../presentation/pages/profile/profile_page.dart';
|
||||
import '../../presentation/pages/profile/mining_records_page.dart';
|
||||
import '../../presentation/pages/profile/planting_records_page.dart';
|
||||
import '../../presentation/widgets/main_shell.dart';
|
||||
import '../../presentation/providers/user_providers.dart';
|
||||
import 'routes.dart';
|
||||
|
|
@ -102,6 +104,14 @@ final appRouterProvider = Provider<GoRouter>((ref) {
|
|||
path: Routes.contributionRecords,
|
||||
builder: (context, state) => const ContributionRecordsListPage(),
|
||||
),
|
||||
GoRoute(
|
||||
path: Routes.miningRecords,
|
||||
builder: (context, state) => const MiningRecordsPage(),
|
||||
),
|
||||
GoRoute(
|
||||
path: Routes.plantingRecords,
|
||||
builder: (context, state) => const PlantingRecordsPage(),
|
||||
),
|
||||
ShellRoute(
|
||||
builder: (context, state, child) => MainShell(child: child),
|
||||
routes: [
|
||||
|
|
|
|||
|
|
@ -10,5 +10,6 @@ class Routes {
|
|||
static const String profile = '/profile';
|
||||
static const String miningRecords = '/mining-records';
|
||||
static const String contributionRecords = '/contribution-records';
|
||||
static const String plantingRecords = '/planting-records';
|
||||
static const String orders = '/orders';
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,18 +1,25 @@
|
|||
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 '../../../core/network/api_client.dart';
|
||||
import '../../../core/network/api_endpoints.dart';
|
||||
import '../../../core/error/exceptions.dart';
|
||||
import '../../../domain/repositories/mining_repository.dart';
|
||||
|
||||
abstract class MiningRemoteDataSource {
|
||||
Future<ShareAccountModel> getShareAccount(String accountSequence);
|
||||
Future<List<MiningRecordModel>> getMiningRecords(
|
||||
Future<MiningRecordsPage> getMiningRecords(
|
||||
String accountSequence, {
|
||||
int page = 1,
|
||||
int limit = 20,
|
||||
int pageSize = 20,
|
||||
});
|
||||
Future<GlobalStateModel> getGlobalState();
|
||||
Future<PlantingLedgerPageModel> getPlantingLedger(
|
||||
String accountSequence, {
|
||||
int page = 1,
|
||||
int pageSize = 10,
|
||||
});
|
||||
}
|
||||
|
||||
class MiningRemoteDataSourceImpl implements MiningRemoteDataSource {
|
||||
|
|
@ -31,18 +38,28 @@ class MiningRemoteDataSourceImpl implements MiningRemoteDataSource {
|
|||
}
|
||||
|
||||
@override
|
||||
Future<List<MiningRecordModel>> getMiningRecords(
|
||||
Future<MiningRecordsPage> getMiningRecords(
|
||||
String accountSequence, {
|
||||
int page = 1,
|
||||
int limit = 20,
|
||||
int pageSize = 20,
|
||||
}) async {
|
||||
try {
|
||||
final response = await client.get(
|
||||
ApiEndpoints.miningRecords(accountSequence),
|
||||
queryParameters: {'page': page, 'pageSize': limit},
|
||||
queryParameters: {'page': page, 'pageSize': pageSize},
|
||||
);
|
||||
final data = response.data;
|
||||
final items = (data['items'] as List? ?? data['records'] as List? ?? [])
|
||||
.map((json) => MiningRecordModel.fromJson(json))
|
||||
.toList();
|
||||
final pagination = data['pagination'] ?? {};
|
||||
return MiningRecordsPage(
|
||||
items: items,
|
||||
total: pagination['total'] ?? items.length,
|
||||
page: pagination['page'] ?? page,
|
||||
pageSize: pagination['pageSize'] ?? pageSize,
|
||||
totalPages: pagination['totalPages'] ?? 1,
|
||||
);
|
||||
final items = response.data['items'] as List? ?? [];
|
||||
return items.map((json) => MiningRecordModel.fromJson(json)).toList();
|
||||
} catch (e) {
|
||||
throw ServerException(e.toString());
|
||||
}
|
||||
|
|
@ -57,4 +74,21 @@ class MiningRemoteDataSourceImpl implements MiningRemoteDataSource {
|
|||
throw ServerException(e.toString());
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Future<PlantingLedgerPageModel> getPlantingLedger(
|
||||
String accountSequence, {
|
||||
int page = 1,
|
||||
int pageSize = 10,
|
||||
}) async {
|
||||
try {
|
||||
final response = await client.get(
|
||||
ApiEndpoints.plantingLedger(accountSequence),
|
||||
queryParameters: {'page': page, 'pageSize': pageSize},
|
||||
);
|
||||
return PlantingLedgerPageModel.fromJson(response.data);
|
||||
} catch (e) {
|
||||
throw ServerException(e.toString());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,101 @@
|
|||
import '../../domain/entities/planting_record.dart';
|
||||
|
||||
class PlantingRecordModel extends PlantingRecord {
|
||||
const PlantingRecordModel({
|
||||
required super.orderId,
|
||||
required super.orderNo,
|
||||
super.originalAdoptionId,
|
||||
required super.treeCount,
|
||||
required super.contributionPerTree,
|
||||
required super.totalContribution,
|
||||
required super.totalAmount,
|
||||
required super.status,
|
||||
super.adoptionDate,
|
||||
required super.createdAt,
|
||||
});
|
||||
|
||||
factory PlantingRecordModel.fromJson(Map<String, dynamic> json) {
|
||||
return PlantingRecordModel(
|
||||
orderId: json['orderId'] ?? json['id'] ?? '',
|
||||
orderNo: json['orderNo'] ?? '',
|
||||
originalAdoptionId: json['originalAdoptionId'],
|
||||
treeCount: json['treeCount'] ?? 0,
|
||||
contributionPerTree: json['contributionPerTree']?.toString() ?? '0',
|
||||
totalContribution: json['totalContribution']?.toString() ?? json['totalAmount']?.toString() ?? '0',
|
||||
totalAmount: json['totalAmount']?.toString() ?? '0',
|
||||
status: _parseStatus(json['status']),
|
||||
adoptionDate: json['adoptionDate'] != null ? DateTime.tryParse(json['adoptionDate']) : null,
|
||||
createdAt: json['createdAt'] != null
|
||||
? DateTime.parse(json['createdAt'])
|
||||
: DateTime.now(),
|
||||
);
|
||||
}
|
||||
|
||||
static PlantingStatus _parseStatus(String? status) {
|
||||
switch (status?.toUpperCase()) {
|
||||
case 'CREATED':
|
||||
return PlantingStatus.created;
|
||||
case 'PAID':
|
||||
return PlantingStatus.paid;
|
||||
case 'FUND_ALLOCATED':
|
||||
return PlantingStatus.fundAllocated;
|
||||
case 'MINING_ENABLED':
|
||||
return PlantingStatus.miningEnabled;
|
||||
case 'CANCELLED':
|
||||
return PlantingStatus.cancelled;
|
||||
case 'EXPIRED':
|
||||
return PlantingStatus.expired;
|
||||
default:
|
||||
return PlantingStatus.created;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class PlantingSummaryModel extends PlantingSummary {
|
||||
const PlantingSummaryModel({
|
||||
required super.totalOrders,
|
||||
required super.totalTreeCount,
|
||||
required super.totalAmount,
|
||||
required super.effectiveTreeCount,
|
||||
super.firstPlantingAt,
|
||||
super.lastPlantingAt,
|
||||
});
|
||||
|
||||
factory PlantingSummaryModel.fromJson(Map<String, dynamic> json) {
|
||||
return PlantingSummaryModel(
|
||||
totalOrders: json['totalOrders'] ?? 0,
|
||||
totalTreeCount: json['totalTreeCount'] ?? 0,
|
||||
totalAmount: json['totalAmount']?.toString() ?? '0',
|
||||
effectiveTreeCount: json['effectiveTreeCount'] ?? 0,
|
||||
firstPlantingAt: json['firstPlantingAt'] != null
|
||||
? DateTime.tryParse(json['firstPlantingAt'])
|
||||
: null,
|
||||
lastPlantingAt: json['lastPlantingAt'] != null
|
||||
? DateTime.tryParse(json['lastPlantingAt'])
|
||||
: null,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class PlantingLedgerPageModel extends PlantingLedgerPage {
|
||||
const PlantingLedgerPageModel({
|
||||
required super.summary,
|
||||
required super.items,
|
||||
required super.total,
|
||||
required super.page,
|
||||
required super.pageSize,
|
||||
required super.totalPages,
|
||||
});
|
||||
|
||||
factory PlantingLedgerPageModel.fromJson(Map<String, dynamic> json) {
|
||||
final itemsList = json['items'] as List? ?? [];
|
||||
return PlantingLedgerPageModel(
|
||||
summary: PlantingSummaryModel.fromJson(json['summary'] ?? {}),
|
||||
items: itemsList.map((e) => PlantingRecordModel.fromJson(e)).toList(),
|
||||
total: json['total'] ?? 0,
|
||||
page: json['page'] ?? 1,
|
||||
pageSize: json['pageSize'] ?? 10,
|
||||
totalPages: json['totalPages'] ?? 0,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
@ -1,7 +1,7 @@
|
|||
import 'package:dartz/dartz.dart';
|
||||
import '../../domain/entities/share_account.dart';
|
||||
import '../../domain/entities/mining_record.dart';
|
||||
import '../../domain/entities/global_state.dart';
|
||||
import '../../domain/entities/planting_record.dart';
|
||||
import '../../domain/repositories/mining_repository.dart';
|
||||
import '../../core/error/exceptions.dart';
|
||||
import '../../core/error/failures.dart';
|
||||
|
|
@ -30,16 +30,16 @@ class MiningRepositoryImpl implements MiningRepository {
|
|||
}
|
||||
|
||||
@override
|
||||
Future<Either<Failure, List<MiningRecord>>> getMiningRecords(
|
||||
Future<Either<Failure, MiningRecordsPage>> getMiningRecords(
|
||||
String accountSequence, {
|
||||
int page = 1,
|
||||
int limit = 20,
|
||||
int pageSize = 20,
|
||||
}) async {
|
||||
try {
|
||||
final result = await remoteDataSource.getMiningRecords(
|
||||
accountSequence,
|
||||
page: page,
|
||||
limit: limit,
|
||||
pageSize: pageSize,
|
||||
);
|
||||
return Right(result);
|
||||
} on ServerException catch (e) {
|
||||
|
|
@ -70,4 +70,24 @@ class MiningRepositoryImpl implements MiningRepository {
|
|||
return Left(const NetworkFailure());
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Future<Either<Failure, PlantingLedgerPage>> getPlantingLedger(
|
||||
String accountSequence, {
|
||||
int page = 1,
|
||||
int pageSize = 10,
|
||||
}) async {
|
||||
try {
|
||||
final result = await remoteDataSource.getPlantingLedger(
|
||||
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,132 @@
|
|||
import 'package:equatable/equatable.dart';
|
||||
|
||||
/// 认种状态枚举
|
||||
enum PlantingStatus {
|
||||
created,
|
||||
paid,
|
||||
fundAllocated,
|
||||
miningEnabled,
|
||||
cancelled,
|
||||
expired,
|
||||
}
|
||||
|
||||
/// 认种记录
|
||||
class PlantingRecord extends Equatable {
|
||||
/// 订单ID
|
||||
final String orderId;
|
||||
/// 订单号
|
||||
final String orderNo;
|
||||
/// 原始认种ID
|
||||
final String? originalAdoptionId;
|
||||
/// 认种数量
|
||||
final int treeCount;
|
||||
/// 单棵算力
|
||||
final String contributionPerTree;
|
||||
/// 总算力
|
||||
final String totalContribution;
|
||||
/// 认种金额
|
||||
final String totalAmount;
|
||||
/// 状态
|
||||
final PlantingStatus status;
|
||||
/// 认种日期
|
||||
final DateTime? adoptionDate;
|
||||
/// 创建时间
|
||||
final DateTime createdAt;
|
||||
|
||||
const PlantingRecord({
|
||||
required this.orderId,
|
||||
required this.orderNo,
|
||||
this.originalAdoptionId,
|
||||
required this.treeCount,
|
||||
required this.contributionPerTree,
|
||||
required this.totalContribution,
|
||||
required this.totalAmount,
|
||||
required this.status,
|
||||
this.adoptionDate,
|
||||
required this.createdAt,
|
||||
});
|
||||
|
||||
/// 状态显示文本
|
||||
String get statusText {
|
||||
switch (status) {
|
||||
case PlantingStatus.created:
|
||||
return '已创建';
|
||||
case PlantingStatus.paid:
|
||||
return '已支付';
|
||||
case PlantingStatus.fundAllocated:
|
||||
return '资金已分配';
|
||||
case PlantingStatus.miningEnabled:
|
||||
return '已开始挖矿';
|
||||
case PlantingStatus.cancelled:
|
||||
return '已取消';
|
||||
case PlantingStatus.expired:
|
||||
return '已过期';
|
||||
}
|
||||
}
|
||||
|
||||
/// 是否为有效状态
|
||||
bool get isActive =>
|
||||
status == PlantingStatus.miningEnabled ||
|
||||
status == PlantingStatus.paid ||
|
||||
status == PlantingStatus.fundAllocated;
|
||||
|
||||
@override
|
||||
List<Object?> get props => [orderId, orderNo, treeCount, status];
|
||||
}
|
||||
|
||||
/// 认种汇总
|
||||
class PlantingSummary extends Equatable {
|
||||
/// 总订单数
|
||||
final int totalOrders;
|
||||
/// 总认种量
|
||||
final int totalTreeCount;
|
||||
/// 总金额
|
||||
final String totalAmount;
|
||||
/// 有效认种量
|
||||
final int effectiveTreeCount;
|
||||
/// 首次认种时间
|
||||
final DateTime? firstPlantingAt;
|
||||
/// 最近认种时间
|
||||
final DateTime? lastPlantingAt;
|
||||
|
||||
const PlantingSummary({
|
||||
required this.totalOrders,
|
||||
required this.totalTreeCount,
|
||||
required this.totalAmount,
|
||||
required this.effectiveTreeCount,
|
||||
this.firstPlantingAt,
|
||||
this.lastPlantingAt,
|
||||
});
|
||||
|
||||
@override
|
||||
List<Object?> get props => [
|
||||
totalOrders,
|
||||
totalTreeCount,
|
||||
totalAmount,
|
||||
effectiveTreeCount,
|
||||
firstPlantingAt,
|
||||
lastPlantingAt,
|
||||
];
|
||||
}
|
||||
|
||||
/// 认种分类账分页数据
|
||||
class PlantingLedgerPage extends Equatable {
|
||||
final PlantingSummary summary;
|
||||
final List<PlantingRecord> items;
|
||||
final int total;
|
||||
final int page;
|
||||
final int pageSize;
|
||||
final int totalPages;
|
||||
|
||||
const PlantingLedgerPage({
|
||||
required this.summary,
|
||||
required this.items,
|
||||
required this.total,
|
||||
required this.page,
|
||||
required this.pageSize,
|
||||
required this.totalPages,
|
||||
});
|
||||
|
||||
@override
|
||||
List<Object?> get props => [summary, items, total, page, pageSize, totalPages];
|
||||
}
|
||||
|
|
@ -3,15 +3,39 @@ import '../../core/error/failures.dart';
|
|||
import '../entities/share_account.dart';
|
||||
import '../entities/mining_record.dart';
|
||||
import '../entities/global_state.dart';
|
||||
import '../entities/planting_record.dart';
|
||||
|
||||
/// 挖矿记录分页数据
|
||||
class MiningRecordsPage {
|
||||
final List<MiningRecord> items;
|
||||
final int total;
|
||||
final int page;
|
||||
final int pageSize;
|
||||
final int totalPages;
|
||||
|
||||
const MiningRecordsPage({
|
||||
required this.items,
|
||||
required this.total,
|
||||
required this.page,
|
||||
required this.pageSize,
|
||||
required this.totalPages,
|
||||
});
|
||||
}
|
||||
|
||||
abstract class MiningRepository {
|
||||
Future<Either<Failure, ShareAccount>> getShareAccount(String accountSequence);
|
||||
|
||||
Future<Either<Failure, List<MiningRecord>>> getMiningRecords(
|
||||
Future<Either<Failure, MiningRecordsPage>> getMiningRecords(
|
||||
String accountSequence, {
|
||||
int page = 1,
|
||||
int limit = 20,
|
||||
int pageSize = 20,
|
||||
});
|
||||
|
||||
Future<Either<Failure, GlobalState>> getGlobalState();
|
||||
|
||||
Future<Either<Failure, PlantingLedgerPage>> getPlantingLedger(
|
||||
String accountSequence, {
|
||||
int page = 1,
|
||||
int pageSize = 10,
|
||||
});
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,346 @@
|
|||
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/mining_record.dart';
|
||||
import '../../../domain/repositories/mining_repository.dart';
|
||||
import '../../providers/user_providers.dart';
|
||||
import '../../providers/mining_providers.dart';
|
||||
|
||||
/// 分配记录页面(挖矿记录)
|
||||
class MiningRecordsPage extends ConsumerStatefulWidget {
|
||||
const MiningRecordsPage({super.key});
|
||||
|
||||
@override
|
||||
ConsumerState<MiningRecordsPage> createState() => _MiningRecordsPageState();
|
||||
}
|
||||
|
||||
class _MiningRecordsPageState extends ConsumerState<MiningRecordsPage> {
|
||||
static const Color _orange = Color(0xFFFF6B00);
|
||||
static const Color _green = Color(0xFF22C55E);
|
||||
static const Color _grayText = Color(0xFF6B7280);
|
||||
static const Color _darkText = Color(0xFF1F2937);
|
||||
static const Color _bgGray = Color(0xFFF3F4F6);
|
||||
|
||||
int _currentPage = 1;
|
||||
static const int _pageSize = 20;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final user = ref.watch(userNotifierProvider);
|
||||
final accountSequence = user.accountSequence ?? '';
|
||||
|
||||
final recordsParams = MiningRecordsParams(
|
||||
accountSequence: accountSequence,
|
||||
page: _currentPage,
|
||||
pageSize: _pageSize,
|
||||
);
|
||||
final recordsAsync = ref.watch(miningRecordsProvider(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(miningRecordsProvider(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, MiningRecordsParams 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(miningRecordsProvider(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),
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
Text(
|
||||
'认种榴莲树后将开始产生收益',
|
||||
style: TextStyle(fontSize: 14, color: _grayText.withOpacity(0.7)),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildRecordsList(MiningRecordsPage 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(MiningRecord 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: [
|
||||
Text(
|
||||
record.distributionMinute,
|
||||
style: const TextStyle(
|
||||
fontSize: 14,
|
||||
fontWeight: FontWeight.w500,
|
||||
color: _darkText,
|
||||
fontFamily: 'monospace',
|
||||
),
|
||||
),
|
||||
Text(
|
||||
'+${formatAmount(record.shareAmount)}',
|
||||
style: const TextStyle(
|
||||
fontSize: 18,
|
||||
fontWeight: FontWeight.bold,
|
||||
color: _green,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 12),
|
||||
|
||||
// 第二行:算力占比 + 价格快照
|
||||
Row(
|
||||
children: [
|
||||
_buildInfoItem('算力占比', _formatPercent(record.contributionRatio)),
|
||||
const SizedBox(width: 24),
|
||||
_buildInfoItem('价格快照', _formatPrice(record.priceSnapshot)),
|
||||
],
|
||||
),
|
||||
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)),
|
||||
),
|
||||
Text(
|
||||
value,
|
||||
style: const TextStyle(
|
||||
fontSize: 12,
|
||||
color: _darkText,
|
||||
fontWeight: FontWeight.w500,
|
||||
fontFamily: 'monospace',
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
String _formatPercent(String ratio) {
|
||||
try {
|
||||
final value = double.parse(ratio);
|
||||
return '${(value * 100).toStringAsFixed(6)}%';
|
||||
} catch (e) {
|
||||
return ratio;
|
||||
}
|
||||
}
|
||||
|
||||
String _formatPrice(String price) {
|
||||
try {
|
||||
final value = double.parse(price);
|
||||
return value.toStringAsFixed(8);
|
||||
} catch (e) {
|
||||
return price;
|
||||
}
|
||||
}
|
||||
|
||||
Widget _buildPaginationInfo(MiningRecordsPage 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),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,496 @@
|
|||
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/planting_record.dart';
|
||||
import '../../providers/user_providers.dart';
|
||||
import '../../providers/mining_providers.dart';
|
||||
|
||||
/// 认种记录页面
|
||||
class PlantingRecordsPage extends ConsumerStatefulWidget {
|
||||
const PlantingRecordsPage({super.key});
|
||||
|
||||
@override
|
||||
ConsumerState<PlantingRecordsPage> createState() => _PlantingRecordsPageState();
|
||||
}
|
||||
|
||||
class _PlantingRecordsPageState extends ConsumerState<PlantingRecordsPage> {
|
||||
static const Color _orange = Color(0xFFFF6B00);
|
||||
static const Color _green = Color(0xFF22C55E);
|
||||
static const Color _blue = Color(0xFF3B82F6);
|
||||
static const Color _grayText = Color(0xFF6B7280);
|
||||
static const Color _darkText = Color(0xFF1F2937);
|
||||
static const Color _bgGray = Color(0xFFF3F4F6);
|
||||
|
||||
int _currentPage = 1;
|
||||
static const int _pageSize = 10;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final user = ref.watch(userNotifierProvider);
|
||||
final accountSequence = user.accountSequence ?? '';
|
||||
|
||||
final recordsParams = PlantingRecordsParams(
|
||||
accountSequence: accountSequence,
|
||||
page: _currentPage,
|
||||
pageSize: _pageSize,
|
||||
);
|
||||
final recordsAsync = ref.watch(plantingRecordsProvider(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(plantingRecordsProvider(recordsParams));
|
||||
},
|
||||
child: recordsAsync.when(
|
||||
loading: () => _buildLoadingView(),
|
||||
error: (error, stack) => _buildErrorView(error.toString(), recordsParams),
|
||||
data: (ledgerPage) {
|
||||
if (ledgerPage == null || ledgerPage.items.isEmpty) {
|
||||
return _buildEmptyView();
|
||||
}
|
||||
return _buildContent(ledgerPage);
|
||||
},
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildLoadingView() {
|
||||
return SingleChildScrollView(
|
||||
physics: const AlwaysScrollableScrollPhysics(),
|
||||
child: Column(
|
||||
children: [
|
||||
// 汇总卡片骨架
|
||||
Container(
|
||||
margin: const EdgeInsets.all(16),
|
||||
padding: const EdgeInsets.all(16),
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.white,
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
),
|
||||
child: Column(
|
||||
children: [
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceAround,
|
||||
children: List.generate(3, (index) => _buildShimmerStat()),
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceAround,
|
||||
children: List.generate(3, (index) => _buildShimmerStat()),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
// 列表骨架
|
||||
...List.generate(5, (index) => _buildShimmerCard()),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildShimmerStat() {
|
||||
return Column(
|
||||
children: [
|
||||
Container(
|
||||
width: 60,
|
||||
height: 24,
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.grey[300],
|
||||
borderRadius: BorderRadius.circular(4),
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 4),
|
||||
Container(
|
||||
width: 40,
|
||||
height: 12,
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.grey[300],
|
||||
borderRadius: BorderRadius.circular(4),
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildShimmerCard() {
|
||||
return Container(
|
||||
margin: const EdgeInsets.fromLTRB(16, 0, 16, 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: 100, height: 16, color: Colors.grey[300]),
|
||||
Container(width: 60, height: 20, color: Colors.grey[300]),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 12),
|
||||
Row(
|
||||
children: [
|
||||
Container(width: 80, height: 12, color: Colors.grey[300]),
|
||||
const SizedBox(width: 16),
|
||||
Container(width: 80, height: 12, color: Colors.grey[300]),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildErrorView(String error, PlantingRecordsParams 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(plantingRecordsProvider(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.eco_outlined, size: 64, color: _grayText.withOpacity(0.5)),
|
||||
const SizedBox(height: 16),
|
||||
Text(
|
||||
'暂无认种记录',
|
||||
style: TextStyle(fontSize: 16, color: _grayText),
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
Text(
|
||||
'认种榴莲树后将显示记录',
|
||||
style: TextStyle(fontSize: 14, color: _grayText.withOpacity(0.7)),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildContent(PlantingLedgerPage ledgerPage) {
|
||||
return SingleChildScrollView(
|
||||
physics: const AlwaysScrollableScrollPhysics(),
|
||||
child: Column(
|
||||
children: [
|
||||
// 汇总卡片
|
||||
_buildSummaryCard(ledgerPage.summary),
|
||||
// 记录列表
|
||||
...ledgerPage.items.map((record) => _buildRecordCard(record)),
|
||||
// 分页信息
|
||||
_buildPaginationInfo(ledgerPage),
|
||||
const SizedBox(height: 24),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildSummaryCard(PlantingSummary summary) {
|
||||
return Container(
|
||||
margin: const EdgeInsets.all(16),
|
||||
padding: const EdgeInsets.all(16),
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.white,
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Row(
|
||||
children: [
|
||||
Icon(Icons.eco, size: 20, color: _green),
|
||||
const SizedBox(width: 8),
|
||||
const Text(
|
||||
'认种汇总',
|
||||
style: TextStyle(
|
||||
fontSize: 16,
|
||||
fontWeight: FontWeight.bold,
|
||||
color: _darkText,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceAround,
|
||||
children: [
|
||||
_buildSummaryItem('总订单数', summary.totalOrders.toString()),
|
||||
_buildSummaryItem('总认种量', summary.totalTreeCount.toString(), color: _green),
|
||||
_buildSummaryItem('总金额', formatAmount(summary.totalAmount), color: _orange),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceAround,
|
||||
children: [
|
||||
_buildSummaryItem('有效认种', summary.effectiveTreeCount.toString(), color: _blue),
|
||||
_buildSummaryItem(
|
||||
'首次认种',
|
||||
summary.firstPlantingAt != null
|
||||
? DateFormat('MM-dd').format(summary.firstPlantingAt!)
|
||||
: '-',
|
||||
),
|
||||
_buildSummaryItem(
|
||||
'最近认种',
|
||||
summary.lastPlantingAt != null
|
||||
? DateFormat('MM-dd').format(summary.lastPlantingAt!)
|
||||
: '-',
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildSummaryItem(String label, String value, {Color? color}) {
|
||||
return Column(
|
||||
children: [
|
||||
Text(
|
||||
value,
|
||||
style: TextStyle(
|
||||
fontSize: 20,
|
||||
fontWeight: FontWeight.bold,
|
||||
color: color ?? _darkText,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 4),
|
||||
Text(
|
||||
label,
|
||||
style: TextStyle(fontSize: 12, color: _grayText),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildRecordCard(PlantingRecord record) {
|
||||
return Container(
|
||||
margin: const EdgeInsets.fromLTRB(16, 0, 16, 12),
|
||||
padding: const EdgeInsets.all(16),
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.white,
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
border: !record.isActive
|
||||
? Border.all(color: Colors.grey.withOpacity(0.3))
|
||||
: null,
|
||||
),
|
||||
child: Opacity(
|
||||
opacity: record.isActive ? 1.0 : 0.6,
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
// 第一行:订单号 + 状态
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Expanded(
|
||||
child: Text(
|
||||
record.originalAdoptionId ?? record.orderNo,
|
||||
style: const TextStyle(
|
||||
fontSize: 13,
|
||||
color: _darkText,
|
||||
fontFamily: 'monospace',
|
||||
),
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 8),
|
||||
_buildStatusBadge(record.status),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 12),
|
||||
|
||||
// 第二行:认种数量 + 单棵算力 + 总算力
|
||||
Row(
|
||||
children: [
|
||||
_buildInfoItem('认种数量', '${record.treeCount}棵'),
|
||||
const SizedBox(width: 16),
|
||||
_buildInfoItem('单棵算力', formatAmount(record.contributionPerTree)),
|
||||
const SizedBox(width: 16),
|
||||
_buildInfoItem('总算力', formatAmount(record.totalContribution), isHighlight: true),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
|
||||
// 第三行:认种日期
|
||||
Row(
|
||||
children: [
|
||||
Icon(Icons.calendar_today_outlined, size: 12, color: _grayText.withOpacity(0.7)),
|
||||
const SizedBox(width: 4),
|
||||
Text(
|
||||
record.adoptionDate != null
|
||||
? DateFormat('yyyy-MM-dd HH:mm').format(record.adoptionDate!)
|
||||
: DateFormat('yyyy-MM-dd HH:mm').format(record.createdAt),
|
||||
style: TextStyle(fontSize: 11, color: _grayText.withOpacity(0.7)),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildStatusBadge(PlantingStatus status) {
|
||||
Color bgColor;
|
||||
Color textColor;
|
||||
|
||||
switch (status) {
|
||||
case PlantingStatus.miningEnabled:
|
||||
bgColor = _green.withOpacity(0.1);
|
||||
textColor = _green;
|
||||
break;
|
||||
case PlantingStatus.paid:
|
||||
case PlantingStatus.fundAllocated:
|
||||
bgColor = _blue.withOpacity(0.1);
|
||||
textColor = _blue;
|
||||
break;
|
||||
case PlantingStatus.created:
|
||||
bgColor = _orange.withOpacity(0.1);
|
||||
textColor = _orange;
|
||||
break;
|
||||
case PlantingStatus.cancelled:
|
||||
case PlantingStatus.expired:
|
||||
bgColor = Colors.red.withOpacity(0.1);
|
||||
textColor = Colors.red;
|
||||
break;
|
||||
}
|
||||
|
||||
return Container(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 4),
|
||||
decoration: BoxDecoration(
|
||||
color: bgColor,
|
||||
borderRadius: BorderRadius.circular(4),
|
||||
),
|
||||
child: Text(
|
||||
status.toString().split('.').last == 'miningEnabled' ? '已开始挖矿' : _getStatusText(status),
|
||||
style: TextStyle(fontSize: 11, fontWeight: FontWeight.w500, color: textColor),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
String _getStatusText(PlantingStatus status) {
|
||||
switch (status) {
|
||||
case PlantingStatus.created:
|
||||
return '已创建';
|
||||
case PlantingStatus.paid:
|
||||
return '已支付';
|
||||
case PlantingStatus.fundAllocated:
|
||||
return '资金已分配';
|
||||
case PlantingStatus.miningEnabled:
|
||||
return '已开始挖矿';
|
||||
case PlantingStatus.cancelled:
|
||||
return '已取消';
|
||||
case PlantingStatus.expired:
|
||||
return '已过期';
|
||||
}
|
||||
}
|
||||
|
||||
Widget _buildInfoItem(String label, String value, {bool isHighlight = false}) {
|
||||
return Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Text(
|
||||
'$label: ',
|
||||
style: TextStyle(fontSize: 12, color: _grayText.withOpacity(0.7)),
|
||||
),
|
||||
Text(
|
||||
value,
|
||||
style: TextStyle(
|
||||
fontSize: 12,
|
||||
color: isHighlight ? _green : _darkText,
|
||||
fontWeight: isHighlight ? FontWeight.w600 : FontWeight.w500,
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildPaginationInfo(PlantingLedgerPage ledgerPage) {
|
||||
final totalPages = ledgerPage.totalPages > 0
|
||||
? ledgerPage.totalPages
|
||||
: (ledgerPage.total / _pageSize).ceil();
|
||||
|
||||
if (totalPages <= 1) {
|
||||
return const SizedBox.shrink();
|
||||
}
|
||||
|
||||
return Container(
|
||||
padding: const EdgeInsets.symmetric(vertical: 16),
|
||||
child: Column(
|
||||
children: [
|
||||
Text(
|
||||
'共 ${ledgerPage.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),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
@ -410,12 +410,12 @@ class ProfilePage extends ConsumerWidget {
|
|||
_buildRecordIcon(
|
||||
icon: Icons.eco,
|
||||
label: '认种记录',
|
||||
onTap: () {},
|
||||
onTap: () => context.push(Routes.plantingRecords),
|
||||
),
|
||||
_buildRecordIcon(
|
||||
icon: Icons.assignment,
|
||||
label: '分配记录',
|
||||
onTap: () {},
|
||||
onTap: () => context.push(Routes.miningRecords),
|
||||
),
|
||||
_buildRecordIcon(
|
||||
icon: Icons.receipt_long,
|
||||
|
|
@ -491,16 +491,6 @@ class ProfilePage extends ConsumerWidget {
|
|||
icon: Icons.people,
|
||||
label: '我的团队',
|
||||
onTap: () {},
|
||||
),
|
||||
_buildSettingItem(
|
||||
icon: Icons.trending_up,
|
||||
label: '收益明细',
|
||||
onTap: () {},
|
||||
),
|
||||
_buildSettingItem(
|
||||
icon: Icons.card_giftcard,
|
||||
label: '推广奖励',
|
||||
onTap: () {},
|
||||
showDivider: false,
|
||||
),
|
||||
],
|
||||
|
|
|
|||
|
|
@ -2,10 +2,16 @@ import 'dart:async';
|
|||
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/repositories/mining_repository.dart';
|
||||
import '../../domain/usecases/mining/get_share_account.dart';
|
||||
import '../../domain/usecases/mining/get_global_state.dart';
|
||||
import '../../core/di/injection.dart';
|
||||
|
||||
final miningRepositoryProvider = Provider<MiningRepository>((ref) {
|
||||
return getIt<MiningRepository>();
|
||||
});
|
||||
|
||||
// Use Cases Providers
|
||||
final getShareAccountUseCaseProvider = Provider<GetShareAccount>((ref) {
|
||||
return getIt<GetShareAccount>();
|
||||
|
|
@ -67,3 +73,107 @@ final currentPriceProvider = Provider<String>((ref) {
|
|||
error: (_, __) => '0',
|
||||
);
|
||||
});
|
||||
|
||||
/// 挖矿记录请求参数
|
||||
class MiningRecordsParams {
|
||||
final String accountSequence;
|
||||
final int page;
|
||||
final int pageSize;
|
||||
|
||||
const MiningRecordsParams({
|
||||
required this.accountSequence,
|
||||
this.page = 1,
|
||||
this.pageSize = 10,
|
||||
});
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) =>
|
||||
identical(this, other) ||
|
||||
other is MiningRecordsParams &&
|
||||
runtimeType == other.runtimeType &&
|
||||
accountSequence == other.accountSequence &&
|
||||
page == other.page &&
|
||||
pageSize == other.pageSize;
|
||||
|
||||
@override
|
||||
int get hashCode => accountSequence.hashCode ^ page.hashCode ^ pageSize.hashCode;
|
||||
}
|
||||
|
||||
/// 挖矿记录 Provider
|
||||
final miningRecordsProvider = FutureProvider.family<MiningRecordsPage?, MiningRecordsParams>(
|
||||
(ref, params) async {
|
||||
if (params.accountSequence.isEmpty) {
|
||||
return null;
|
||||
}
|
||||
|
||||
final repository = ref.watch(miningRepositoryProvider);
|
||||
final result = await repository.getMiningRecords(
|
||||
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,
|
||||
);
|
||||
},
|
||||
);
|
||||
|
||||
/// 认种记录请求参数
|
||||
class PlantingRecordsParams {
|
||||
final String accountSequence;
|
||||
final int page;
|
||||
final int pageSize;
|
||||
|
||||
const PlantingRecordsParams({
|
||||
required this.accountSequence,
|
||||
this.page = 1,
|
||||
this.pageSize = 10,
|
||||
});
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) =>
|
||||
identical(this, other) ||
|
||||
other is PlantingRecordsParams &&
|
||||
runtimeType == other.runtimeType &&
|
||||
accountSequence == other.accountSequence &&
|
||||
page == other.page &&
|
||||
pageSize == other.pageSize;
|
||||
|
||||
@override
|
||||
int get hashCode => accountSequence.hashCode ^ page.hashCode ^ pageSize.hashCode;
|
||||
}
|
||||
|
||||
/// 认种记录 Provider
|
||||
final plantingRecordsProvider = FutureProvider.family<PlantingLedgerPage?, PlantingRecordsParams>(
|
||||
(ref, params) async {
|
||||
if (params.accountSequence.isEmpty) {
|
||||
return null;
|
||||
}
|
||||
|
||||
final repository = ref.watch(miningRepositoryProvider);
|
||||
final result = await repository.getPlantingLedger(
|
||||
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),
|
||||
(ledger) => ledger,
|
||||
);
|
||||
},
|
||||
);
|
||||
|
|
|
|||
Loading…
Reference in New Issue