feat(mobile-app): 流水明细支持显示权益类型和详情
- 后端 wallet-service: getMyLedger API 返回 allocationType 字段 - 前端流水明细: 显示权益类型名称(分享权益、省/市区域权益等) - 新增权益详情弹窗,点击权益记录可查看详细信息 - 兑换页面: "RMB/CNY提现" 改为 "提现" - 我的团队: "暂无下级成员" 改为 "暂无团队成员" - 自助申请授权: 隐藏团队链占用区域提示 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
parent
f7e2f7f6f2
commit
78d7e0e637
|
|
@ -423,7 +423,8 @@
|
|||
"Bash(git commit -m \"$\\(cat <<''EOF''\nfix\\(mobile-app\\): 使用 appRouterProvider 获取全局路由状态\n\n改用 ref.read\\(appRouterProvider\\) 替代 GoRouter.of\\(context\\),\n确保能正确获取到当前的全局路由路径。\n\n🤖 Generated with [Claude Code]\\(https://claude.com/claude-code\\)\n\nCo-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>\nEOF\n\\)\")",
|
||||
"Bash(git commit -m \"$\\(cat <<''EOF''\nfix\\(mobile-app\\): 首次检查也加入路由判断,避免在KYC页面弹窗\n\n_checkContractsAndKyc\\(\\) 方法之前没有调用 _shouldSkipContractCheck\\(\\),\n导致用户在合同/KYC页面时首次检查仍会弹窗。\n\n🤖 Generated with [Claude Code]\\(https://claude.com/claude-code\\)\n\nCo-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>\nEOF\n\\)\")",
|
||||
"Bash(git commit -m \"$\\(cat <<''EOF''\nfix\\(mobile-app\\): 遍历路由栈检测当前页面,修复push导航检测问题\n\n之前只检查 currentConfiguration.uri.path,对于 push 导航的页面无法正确检测。\n现在遍历整个 matches 路由栈,只要栈中有合同/KYC页面就跳过弹窗。\n\n🤖 Generated with [Claude Code]\\(https://claude.com/claude-code\\)\n\nCo-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>\nEOF\n\\)\")",
|
||||
"Bash(git commit -m \"$\\(cat <<''EOF''\nfix\\(planting-service\\): 修复跨服务调用使用错误标识符导致的500错误\n\n问题根源:\n- getBalance 调用使用 userId.toString\\(\\) \\(纯数字如 \"14\"\\)\n- wallet-service 按 accountSequence 查找钱包失败后尝试创建新钱包\n- 但 userId 已存在,触发唯一约束冲突导致500错误\n\n修复内容:\n1. planting-application.service.ts:\n - createOrder: getBalance\\(userId.toString\\(\\)\\) → getBalance\\(accountSequence\\)\n - payOrder: getBalance\\(userId.toString\\(\\)\\) → getBalance\\(walletIdentifier\\)\n\n2. payment-compensation.service.ts:\n - 注入 IPlantingOrderRepository 获取订单的 accountSequence\n - handleUnfreeze/handleRetryConfirm 添加 accountSequence 参数\n\n3. wallet-service.client.ts:\n - ensureRegionAccounts 接口添加 provinceTeamAccount/cityTeamAccount 字段\n\n🤖 Generated with [Claude Code]\\(https://claude.com/claude-code\\)\n\nCo-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>\nEOF\n\\)\")"
|
||||
"Bash(git commit -m \"$\\(cat <<''EOF''\nfix\\(planting-service\\): 修复跨服务调用使用错误标识符导致的500错误\n\n问题根源:\n- getBalance 调用使用 userId.toString\\(\\) \\(纯数字如 \"14\"\\)\n- wallet-service 按 accountSequence 查找钱包失败后尝试创建新钱包\n- 但 userId 已存在,触发唯一约束冲突导致500错误\n\n修复内容:\n1. planting-application.service.ts:\n - createOrder: getBalance\\(userId.toString\\(\\)\\) → getBalance\\(accountSequence\\)\n - payOrder: getBalance\\(userId.toString\\(\\)\\) → getBalance\\(walletIdentifier\\)\n\n2. payment-compensation.service.ts:\n - 注入 IPlantingOrderRepository 获取订单的 accountSequence\n - handleUnfreeze/handleRetryConfirm 添加 accountSequence 参数\n\n3. wallet-service.client.ts:\n - ensureRegionAccounts 接口添加 provinceTeamAccount/cityTeamAccount 字段\n\n🤖 Generated with [Claude Code]\\(https://claude.com/claude-code\\)\n\nCo-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>\nEOF\n\\)\")",
|
||||
"Bash(git commit -m \"$\\(cat <<''EOF''\nfeat\\(mobile-app\\): 流水明细支持显示权益类型和详情\n\n- 后端 wallet-service: getMyLedger API 返回 allocationType 字段\n- 前端流水明细: 显示权益类型名称(分享权益、省/市区域权益等)\n- 新增权益详情弹窗,点击权益记录可查看详细信息\n- 兑换页面: \"RMB/CNY提现\" 改为 \"提现\"\n- 我的团队: \"暂无下级成员\" 改为 \"暂无团队成员\"\n- 自助申请授权: 隐藏团队链占用区域提示\n\n🤖 Generated with [Claude Code]\\(https://claude.com/claude-code\\)\n\nCo-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>\nEOF\n\\)\")"
|
||||
],
|
||||
"deny": [],
|
||||
"ask": []
|
||||
|
|
|
|||
|
|
@ -25,6 +25,9 @@ export class LedgerEntryResponseDTO {
|
|||
@ApiProperty({ description: '备注', nullable: true })
|
||||
memo: string | null;
|
||||
|
||||
@ApiProperty({ description: '权益分配类型 (SHARE_RIGHT, PROVINCE_AREA_RIGHT, CITY_AREA_RIGHT, COMMUNITY_RIGHT 等)', nullable: true })
|
||||
allocationType: string | null;
|
||||
|
||||
@ApiProperty({ description: '创建时间' })
|
||||
createdAt: string;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -61,6 +61,7 @@ export interface LedgerEntryDTO {
|
|||
refOrderId: string | null;
|
||||
refTxHash: string | null;
|
||||
memo: string | null;
|
||||
allocationType: string | null;
|
||||
createdAt: string;
|
||||
}
|
||||
|
||||
|
|
@ -1730,6 +1731,7 @@ export class WalletApplicationService {
|
|||
refOrderId: entry.refOrderId,
|
||||
refTxHash: entry.refTxHash,
|
||||
memo: entry.memo,
|
||||
allocationType: (entry.payloadJson as Record<string, unknown>)?.allocationType as string ?? null,
|
||||
createdAt: entry.createdAt.toISOString(),
|
||||
})),
|
||||
total: result.total,
|
||||
|
|
|
|||
|
|
@ -747,6 +747,7 @@ class LedgerEntry {
|
|||
final String? refOrderId;
|
||||
final String? refTxHash;
|
||||
final String? memo;
|
||||
final String? allocationType;
|
||||
final DateTime createdAt;
|
||||
|
||||
LedgerEntry({
|
||||
|
|
@ -758,6 +759,7 @@ class LedgerEntry {
|
|||
this.refOrderId,
|
||||
this.refTxHash,
|
||||
this.memo,
|
||||
this.allocationType,
|
||||
required this.createdAt,
|
||||
});
|
||||
|
||||
|
|
@ -771,6 +773,7 @@ class LedgerEntry {
|
|||
refOrderId: json['refOrderId'],
|
||||
refTxHash: json['refTxHash'],
|
||||
memo: json['memo'],
|
||||
allocationType: json['allocationType'],
|
||||
createdAt: json['createdAt'] != null
|
||||
? DateTime.tryParse(json['createdAt']) ?? DateTime.now()
|
||||
: DateTime.now(),
|
||||
|
|
@ -801,6 +804,25 @@ class LedgerEntry {
|
|||
return nameMap[entryType] ?? entryType;
|
||||
}
|
||||
|
||||
/// 获取权益分配类型中文名
|
||||
String? get allocationTypeName {
|
||||
if (allocationType == null) return null;
|
||||
const nameMap = {
|
||||
'SHARE_RIGHT': '分享权益',
|
||||
'PROVINCE_AREA_RIGHT': '省区域权益',
|
||||
'PROVINCE_TEAM_RIGHT': '省团队权益',
|
||||
'CITY_AREA_RIGHT': '市区域权益',
|
||||
'CITY_TEAM_RIGHT': '市团队权益',
|
||||
'COMMUNITY_RIGHT': '社区权益',
|
||||
};
|
||||
return nameMap[allocationType] ?? allocationType;
|
||||
}
|
||||
|
||||
/// 获取显示名称(优先显示权益类型,否则显示流水类型)
|
||||
String get displayName {
|
||||
return allocationTypeName ?? entryTypeName;
|
||||
}
|
||||
|
||||
/// 是否为收入
|
||||
bool get isIncome => amount > 0;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -979,18 +979,18 @@ class _AuthorizationApplyPageState
|
|||
: const Color(0xFF745D43),
|
||||
),
|
||||
),
|
||||
// 显示团队链中已占用区域提示
|
||||
if (occupiedCount > 0 && !isDisabled) ...[
|
||||
const SizedBox(height: 8),
|
||||
Text(
|
||||
'团队链中已有 $occupiedCount 个${type == AuthorizationType.cityTeam ? '城市' : '省份'}被申请,请选择其他区域',
|
||||
style: const TextStyle(
|
||||
fontSize: 12,
|
||||
fontFamily: 'Inter',
|
||||
color: Color(0xFFFF9800),
|
||||
),
|
||||
),
|
||||
],
|
||||
// 显示团队链中已占用区域提示 (暂时隐藏)
|
||||
// if (occupiedCount > 0 && !isDisabled) ...[
|
||||
// const SizedBox(height: 8),
|
||||
// Text(
|
||||
// '团队链中已有 $occupiedCount 个${type == AuthorizationType.cityTeam ? '城市' : '省份'}被申请,请选择其他区域',
|
||||
// style: const TextStyle(
|
||||
// fontSize: 12,
|
||||
// fontFamily: 'Inter',
|
||||
// color: Color(0xFFFF9800),
|
||||
// ),
|
||||
// ),
|
||||
// ],
|
||||
// 显示互斥限制提示
|
||||
if (isMutuallyExcluded) ...[
|
||||
const SizedBox(height: 8),
|
||||
|
|
|
|||
|
|
@ -3402,7 +3402,7 @@ class _ProfilePageState extends ConsumerState<ProfilePage> {
|
|||
child: Padding(
|
||||
padding: EdgeInsets.all(20),
|
||||
child: Text(
|
||||
'暂无下级成员',
|
||||
'暂无团队成员',
|
||||
style: TextStyle(
|
||||
fontSize: 14,
|
||||
fontFamily: 'Inter',
|
||||
|
|
|
|||
|
|
@ -926,11 +926,13 @@ class _LedgerDetailPageState extends ConsumerState<LedgerDetailPage>
|
|||
/// 构建流水项
|
||||
Widget _buildLedgerItem(LedgerEntry entry) {
|
||||
final isIncome = entry.isIncome;
|
||||
// 只有"认种支付"类型可点击查看详情
|
||||
final bool isClickable = entry.entryType == 'PLANT_PAYMENT' && entry.refOrderId != null;
|
||||
// 可点击查看详情的类型:认种支付、权益分配
|
||||
final bool isPlantPayment = entry.entryType == 'PLANT_PAYMENT' && entry.refOrderId != null;
|
||||
final bool isRewardEntry = entry.allocationType != null;
|
||||
final bool isClickable = isPlantPayment || isRewardEntry;
|
||||
|
||||
return GestureDetector(
|
||||
onTap: isClickable ? () => _showTransactionDetail(entry) : null,
|
||||
onTap: isClickable ? () => _showEntryDetail(entry) : null,
|
||||
child: Container(
|
||||
margin: const EdgeInsets.only(bottom: 8),
|
||||
padding: const EdgeInsets.all(16),
|
||||
|
|
@ -970,7 +972,7 @@ class _LedgerDetailPageState extends ConsumerState<LedgerDetailPage>
|
|||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
entry.entryTypeName,
|
||||
entry.displayName,
|
||||
style: const TextStyle(
|
||||
fontSize: 14,
|
||||
fontWeight: FontWeight.w500,
|
||||
|
|
@ -985,18 +987,6 @@ class _LedgerDetailPageState extends ConsumerState<LedgerDetailPage>
|
|||
color: Color(0x995D4037),
|
||||
),
|
||||
),
|
||||
if (entry.memo != null && entry.memo!.isNotEmpty) ...[
|
||||
const SizedBox(height: 2),
|
||||
Text(
|
||||
entry.memo!,
|
||||
style: const TextStyle(
|
||||
fontSize: 10,
|
||||
color: Color(0x995D4037),
|
||||
),
|
||||
maxLines: 1,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
],
|
||||
],
|
||||
),
|
||||
),
|
||||
|
|
@ -1050,6 +1040,17 @@ class _LedgerDetailPageState extends ConsumerState<LedgerDetailPage>
|
|||
);
|
||||
}
|
||||
|
||||
/// 显示流水详情(根据类型显示不同弹窗)
|
||||
void _showEntryDetail(LedgerEntry entry) {
|
||||
if (entry.entryType == 'PLANT_PAYMENT' && entry.refOrderId != null) {
|
||||
// 认种支付 - 显示合同详情
|
||||
_showTransactionDetail(entry);
|
||||
} else if (entry.allocationType != null) {
|
||||
// 权益分配 - 显示权益详情
|
||||
_showRewardDetail(entry);
|
||||
}
|
||||
}
|
||||
|
||||
/// 显示交易详情弹窗(认种支付)
|
||||
Future<void> _showTransactionDetail(LedgerEntry entry) async {
|
||||
if (entry.refOrderId == null) return;
|
||||
|
|
@ -1066,6 +1067,16 @@ class _LedgerDetailPageState extends ConsumerState<LedgerDetailPage>
|
|||
);
|
||||
}
|
||||
|
||||
/// 显示权益详情弹窗
|
||||
void _showRewardDetail(LedgerEntry entry) {
|
||||
showModalBottomSheet(
|
||||
context: context,
|
||||
isScrollControlled: true,
|
||||
backgroundColor: Colors.transparent,
|
||||
builder: (context) => _RewardDetailSheet(entry: entry),
|
||||
);
|
||||
}
|
||||
|
||||
/// 查看合同 PDF
|
||||
Future<void> _viewContractPdf(String orderNo) async {
|
||||
// 显示加载指示器
|
||||
|
|
@ -1391,3 +1402,206 @@ class _ContractPdfViewerPage extends StatelessWidget {
|
|||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// 权益详情底部弹窗
|
||||
class _RewardDetailSheet extends StatelessWidget {
|
||||
final LedgerEntry entry;
|
||||
|
||||
const _RewardDetailSheet({required this.entry});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final isIncome = entry.isIncome;
|
||||
|
||||
// 根据权益类型获取图标
|
||||
IconData getRewardIcon() {
|
||||
switch (entry.allocationType) {
|
||||
case 'SHARE_RIGHT':
|
||||
return Icons.share_outlined;
|
||||
case 'PROVINCE_AREA_RIGHT':
|
||||
case 'PROVINCE_TEAM_RIGHT':
|
||||
return Icons.location_city_outlined;
|
||||
case 'CITY_AREA_RIGHT':
|
||||
case 'CITY_TEAM_RIGHT':
|
||||
return Icons.apartment_outlined;
|
||||
case 'COMMUNITY_RIGHT':
|
||||
return Icons.groups_outlined;
|
||||
default:
|
||||
return Icons.card_giftcard_outlined;
|
||||
}
|
||||
}
|
||||
|
||||
// 根据权益类型获取描述
|
||||
String getRewardDescription() {
|
||||
switch (entry.allocationType) {
|
||||
case 'SHARE_RIGHT':
|
||||
return '通过分享邀请获得的权益收益';
|
||||
case 'PROVINCE_AREA_RIGHT':
|
||||
return '省级区域销售产生的权益收益';
|
||||
case 'PROVINCE_TEAM_RIGHT':
|
||||
return '省级团队业绩产生的权益收益';
|
||||
case 'CITY_AREA_RIGHT':
|
||||
return '市级区域销售产生的权益收益';
|
||||
case 'CITY_TEAM_RIGHT':
|
||||
return '市级团队业绩产生的权益收益';
|
||||
case 'COMMUNITY_RIGHT':
|
||||
return '社区运营产生的权益收益';
|
||||
default:
|
||||
return '权益分配收益';
|
||||
}
|
||||
}
|
||||
|
||||
return Container(
|
||||
decoration: const BoxDecoration(
|
||||
color: Colors.white,
|
||||
borderRadius: BorderRadius.vertical(top: Radius.circular(20)),
|
||||
),
|
||||
child: SafeArea(
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
// 拖拽指示器
|
||||
Container(
|
||||
margin: const EdgeInsets.only(top: 12),
|
||||
width: 40,
|
||||
height: 4,
|
||||
decoration: BoxDecoration(
|
||||
color: const Color(0xFFE0E0E0),
|
||||
borderRadius: BorderRadius.circular(2),
|
||||
),
|
||||
),
|
||||
// 标题区域
|
||||
Padding(
|
||||
padding: const EdgeInsets.all(20),
|
||||
child: Row(
|
||||
children: [
|
||||
Container(
|
||||
width: 48,
|
||||
height: 48,
|
||||
decoration: BoxDecoration(
|
||||
color: isIncome
|
||||
? const Color(0x1A4CAF50)
|
||||
: const Color(0x1AE53935),
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
),
|
||||
child: Icon(
|
||||
getRewardIcon(),
|
||||
size: 24,
|
||||
color: isIncome ? const Color(0xFF4CAF50) : const Color(0xFFE53935),
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 16),
|
||||
Expanded(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
entry.displayName,
|
||||
style: const TextStyle(
|
||||
fontSize: 18,
|
||||
fontWeight: FontWeight.w700,
|
||||
color: Color(0xFF5D4037),
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 4),
|
||||
Text(
|
||||
getRewardDescription(),
|
||||
style: const TextStyle(
|
||||
fontSize: 12,
|
||||
color: Color(0x995D4037),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
// 分隔线
|
||||
Container(
|
||||
height: 1,
|
||||
color: const Color(0x1A8B5A2B),
|
||||
),
|
||||
// 详情列表
|
||||
Padding(
|
||||
padding: const EdgeInsets.all(20),
|
||||
child: Column(
|
||||
children: [
|
||||
_buildDetailRow(
|
||||
'收益金额',
|
||||
'${isIncome ? '+' : ''}${_formatAmount(entry.amount)} 绿积分',
|
||||
color: isIncome ? const Color(0xFF4CAF50) : const Color(0xFFE53935),
|
||||
),
|
||||
_buildDetailRow('权益类型', entry.displayName),
|
||||
_buildDetailRow('流水类型', entry.entryTypeName),
|
||||
if (entry.balanceAfter != null)
|
||||
_buildDetailRow('入账后余额', '${_formatAmount(entry.balanceAfter!)} 绿积分'),
|
||||
_buildDetailRow('入账时间', _formatDateTime(entry.createdAt)),
|
||||
if (entry.refOrderId != null && entry.refOrderId!.isNotEmpty)
|
||||
_buildDetailRow('关联订单', entry.refOrderId!),
|
||||
],
|
||||
),
|
||||
),
|
||||
// 底部关闭按钮
|
||||
Padding(
|
||||
padding: const EdgeInsets.fromLTRB(20, 0, 20, 20),
|
||||
child: SizedBox(
|
||||
width: double.infinity,
|
||||
child: ElevatedButton(
|
||||
onPressed: () => Navigator.of(context).pop(),
|
||||
style: ElevatedButton.styleFrom(
|
||||
backgroundColor: const Color(0xFFD4AF37),
|
||||
foregroundColor: Colors.white,
|
||||
padding: const EdgeInsets.symmetric(vertical: 14),
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
),
|
||||
),
|
||||
child: const Text('关闭'),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildDetailRow(String label, String value, {Color? color}) {
|
||||
return Padding(
|
||||
padding: const EdgeInsets.symmetric(vertical: 8),
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Text(
|
||||
label,
|
||||
style: const TextStyle(
|
||||
fontSize: 14,
|
||||
color: Color(0x995D4037),
|
||||
),
|
||||
),
|
||||
Flexible(
|
||||
child: Text(
|
||||
value,
|
||||
style: TextStyle(
|
||||
fontSize: 14,
|
||||
fontWeight: FontWeight.w600,
|
||||
color: color ?? const Color(0xFF5D4037),
|
||||
),
|
||||
textAlign: TextAlign.right,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
String _formatAmount(double amount) {
|
||||
final formatter = NumberFormat('#,##0.00', 'zh_CN');
|
||||
return formatter.format(amount);
|
||||
}
|
||||
|
||||
String _formatDateTime(DateTime date) {
|
||||
return DateTimeUtils.formatDateTime(date);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -436,7 +436,7 @@ class _TradingPageState extends ConsumerState<TradingPage> {
|
|||
// const SizedBox(width: 8),
|
||||
// _buildCurrencyChip(SettlementCurrency.og, 'OG'),
|
||||
// const SizedBox(width: 8),
|
||||
_buildCurrencyChip(SettlementCurrency.usdt, 'RMB/CNY提现'),
|
||||
_buildCurrencyChip(SettlementCurrency.usdt, '提现'),
|
||||
// const SizedBox(width: 8),
|
||||
// _buildCurrencyChip(SettlementCurrency.dst, 'DST'),
|
||||
],
|
||||
|
|
|
|||
Loading…
Reference in New Issue