From e9c0196d68bbda0494bd15994967aecdafc17f26 Mon Sep 17 00:00:00 2001 From: hailin Date: Tue, 6 Jan 2026 22:36:31 -0800 Subject: [PATCH] =?UTF-8?q?feat(admin-web):=20=E6=B7=BB=E5=8A=A0=E8=BF=87?= =?UTF-8?q?=E6=9C=9F=E6=94=B6=E7=9B=8A=E6=98=8E=E7=BB=86=E6=9F=A5=E8=AF=A2?= =?UTF-8?q?=E5=8A=9F=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - reward-service: 添加 getExpiredRewardsEntries API 查询过期收益明细 - reporting-service: 添加过期收益明细转发接口和类型定义 - admin-web: 过期收益统计区域新增"查看明细"按钮 - 支持分页浏览过期收益记录 - 支持按权益类型筛选 - 显示过期时间、用户ID、账户、权益类型、金额、订单号 回滚方式:删除各服务中标注 [2026-01-07] 的代码块 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 --- .../system-account-report.controller.ts | 22 +++ ...stem-account-report-application.service.ts | 16 +- .../reward-service/reward-service.client.ts | 57 +++++++ .../api/controllers/internal.controller.ts | 28 ++++ .../services/reward-application.service.ts | 76 +++++++++ .../SystemAccountsTab.module.scss | 34 ++++ .../SystemAccountsTab.tsx | 148 +++++++++++++++++- .../src/infrastructure/api/endpoints.ts | 2 + .../services/systemAccountReportService.ts | 13 ++ .../src/types/system-account.types.ts | 28 ++++ 10 files changed, 421 insertions(+), 3 deletions(-) diff --git a/backend/services/reporting-service/src/api/controllers/system-account-report.controller.ts b/backend/services/reporting-service/src/api/controllers/system-account-report.controller.ts index 68d749a2..93803f64 100644 --- a/backend/services/reporting-service/src/api/controllers/system-account-report.controller.ts +++ b/backend/services/reporting-service/src/api/controllers/system-account-report.controller.ts @@ -79,6 +79,28 @@ export class SystemAccountReportController { return this.systemAccountReportService.getExpiredRewardsSummary({ startDate, endDate }); } + // [2026-01-07] 新增:过期收益明细列表 + // 用于系统账户报表中展示过期收益的具体记录 + // 回滚方式:删除此方法 + @Get('expired-rewards-entries') + @ApiOperation({ summary: '获取过期收益明细列表' }) + @ApiQuery({ name: 'page', required: false, description: '页码,默认1' }) + @ApiQuery({ name: 'pageSize', required: false, description: '每页条数,默认50' }) + @ApiQuery({ name: 'rightType', required: false, description: '权益类型筛选(可选)' }) + @ApiResponse({ status: 200, description: '过期收益明细列表' }) + async getExpiredRewardsEntries( + @Query('page') page?: string, + @Query('pageSize') pageSize?: string, + @Query('rightType') rightType?: string, + ) { + this.logger.log(`[getExpiredRewardsEntries] 请求过期收益明细, page=${page}, pageSize=${pageSize}, rightType=${rightType || 'all'}`); + return this.systemAccountReportService.getExpiredRewardsEntries({ + page: page ? parseInt(page, 10) : undefined, + pageSize: pageSize ? parseInt(pageSize, 10) : undefined, + rightType, + }); + } + // [2026-01-07] 新增:面对面结算明细列表 // 回滚方式:删除此方法 @Get('offline-settlement-entries') diff --git a/backend/services/reporting-service/src/application/services/system-account-report-application.service.ts b/backend/services/reporting-service/src/application/services/system-account-report-application.service.ts index 00a54812..4a16576b 100644 --- a/backend/services/reporting-service/src/application/services/system-account-report-application.service.ts +++ b/backend/services/reporting-service/src/application/services/system-account-report-application.service.ts @@ -7,7 +7,7 @@ */ import { Injectable, Logger } from '@nestjs/common'; import { WalletServiceClient, OfflineSettlementSummary, AllSystemAccountsResponse, AllSystemAccountsLedgerResponse, FeeCollectionSummaryResponse, FeeCollectionEntriesResponse, OfflineSettlementEntriesResponse } from '../../infrastructure/external/wallet-service/wallet-service.client'; -import { RewardServiceClient, ExpiredRewardsSummary, AllRewardTypeSummaries, RewardEntriesResponse } from '../../infrastructure/external/reward-service/reward-service.client'; +import { RewardServiceClient, ExpiredRewardsSummary, AllRewardTypeSummaries, RewardEntriesResponse, ExpiredRewardsEntriesResponse } from '../../infrastructure/external/reward-service/reward-service.client'; /** * 固定系统账户信息 @@ -185,6 +185,20 @@ export class SystemAccountReportApplicationService { return this.rewardServiceClient.getExpiredRewardsSummary(params); } + // [2026-01-07] 新增:获取过期收益明细列表 + // 回滚方式:删除此方法 + /** + * 获取过期收益明细列表 + */ + async getExpiredRewardsEntries(params?: { + page?: number; + pageSize?: number; + rightType?: string; + }): Promise { + this.logger.log('[getExpiredRewardsEntries] 获取过期收益明细列表'); + return this.rewardServiceClient.getExpiredRewardsEntries(params); + } + // [2026-01-07] 新增:获取面对面结算明细列表 // 回滚方式:删除此方法 /** diff --git a/backend/services/reporting-service/src/infrastructure/external/reward-service/reward-service.client.ts b/backend/services/reporting-service/src/infrastructure/external/reward-service/reward-service.client.ts index 1b0facd9..73565245 100644 --- a/backend/services/reporting-service/src/infrastructure/external/reward-service/reward-service.client.ts +++ b/backend/services/reporting-service/src/infrastructure/external/reward-service/reward-service.client.ts @@ -83,6 +83,28 @@ export interface RewardEntryDTO { expiredAt: string | null; } +// [2026-01-07] 新增:过期收益明细条目 +export interface ExpiredRewardEntryDTO { + id: string; + accountSequence: string; + userId: number; + sourceOrderId: string; + rightType: string; + usdtAmount: number; + hashpowerAmount: number; + createdAt: string; + expiredAt: string | null; +} + +// [2026-01-07] 新增:过期收益明细响应 +export interface ExpiredRewardsEntriesResponse { + entries: ExpiredRewardEntryDTO[]; + total: number; + page: number; + pageSize: number; + totalPages: number; +} + // [2026-01-06] 新增:收益记录列表响应 export interface RewardEntriesResponse { entries: RewardEntryDTO[]; @@ -251,4 +273,39 @@ export class RewardServiceClient { }; } } + + // [2026-01-07] 新增:获取过期收益明细列表 + /** + * 获取过期收益明细列表 + */ + async getExpiredRewardsEntries(params?: { + page?: number; + pageSize?: number; + rightType?: string; + }): Promise { + try { + const queryParams = new URLSearchParams(); + if (params?.page) queryParams.append('page', params.page.toString()); + if (params?.pageSize) queryParams.append('pageSize', params.pageSize.toString()); + if (params?.rightType) queryParams.append('rightType', params.rightType); + + const url = `${this.baseUrl}/api/v1/internal/statistics/expired-rewards-entries?${queryParams.toString()}`; + this.logger.debug(`[getExpiredRewardsEntries] 请求: ${url}`); + + const response = await firstValueFrom( + this.httpService.get(url), + ); + + return response.data; + } catch (error) { + this.logger.error(`[getExpiredRewardsEntries] 失败: ${error.message}`); + return { + entries: [], + total: 0, + page: 1, + pageSize: params?.pageSize ?? 50, + totalPages: 0, + }; + } + } } diff --git a/backend/services/reward-service/src/api/controllers/internal.controller.ts b/backend/services/reward-service/src/api/controllers/internal.controller.ts index 87d5ce89..08c4e156 100644 --- a/backend/services/reward-service/src/api/controllers/internal.controller.ts +++ b/backend/services/reward-service/src/api/controllers/internal.controller.ts @@ -167,4 +167,32 @@ export class InternalController { this.logger.log(`手续费详细记录查询结果: total=${result.total}, page=${result.page}, pageSize=${result.pageSize}`); return result; } + + // [2026-01-07] 新增:获取过期收益明细列表接口 + // 用于系统账户报表中展示过期收益的具体记录 + // 回滚方式:删除以下 API 方法即可 + + @Get('statistics/expired-rewards-entries') + @ApiOperation({ summary: '获取过期收益明细列表(内部接口)- 用于系统账户报表' }) + @ApiQuery({ name: 'page', required: false, description: '页码,默认1' }) + @ApiQuery({ name: 'pageSize', required: false, description: '每页条数,默认50' }) + @ApiQuery({ name: 'rightType', required: false, description: '权益类型筛选(可选)' }) + @ApiResponse({ status: 200, description: '过期收益明细列表' }) + async getExpiredRewardsEntries( + @Query('page') page?: string, + @Query('pageSize') pageSize?: string, + @Query('rightType') rightType?: string, + ) { + this.logger.log(`========== statistics/expired-rewards-entries 请求 ==========`); + this.logger.log(`page=${page}, pageSize=${pageSize}, rightType=${rightType || 'all'}`); + + const result = await this.rewardService.getExpiredRewardsEntries({ + page: page ? parseInt(page, 10) : undefined, + pageSize: pageSize ? parseInt(pageSize, 10) : undefined, + rightType: rightType || undefined, + }); + + this.logger.log(`过期收益明细查询结果: total=${result.total}, page=${result.page}, pageSize=${result.pageSize}`); + return result; + } } diff --git a/backend/services/reward-service/src/application/services/reward-application.service.ts b/backend/services/reward-service/src/application/services/reward-application.service.ts index fd84edb7..b2ee46be 100644 --- a/backend/services/reward-service/src/application/services/reward-application.service.ts +++ b/backend/services/reward-service/src/application/services/reward-application.service.ts @@ -1468,4 +1468,80 @@ export class RewardApplicationService { totalPages: Math.ceil(total / pageSize), }; } + + // [2026-01-07] 新增:获取过期收益明细列表 + // 用于系统账户报表中展示过期收益的具体记录 + // 回滚方式:删除以下方法即可 + /** + * 获取过期收益明细列表 + * @param page 页码 + * @param pageSize 每页条数 + * @param rightType 可选的权益类型筛选 + */ + async getExpiredRewardsEntries(params: { + page?: number; + pageSize?: number; + rightType?: string; + }): Promise<{ + entries: Array<{ + id: string; + accountSequence: string; + userId: number; + sourceOrderId: string; + rightType: string; + usdtAmount: number; + hashpowerAmount: number; + createdAt: string; + expiredAt: string | null; + }>; + total: number; + page: number; + pageSize: number; + totalPages: number; + }> { + const page = params.page ?? 1; + const pageSize = params.pageSize ?? 50; + const skip = (page - 1) * pageSize; + + this.logger.log(`[getExpiredRewardsEntries] 查询过期收益明细, page=${page}, pageSize=${pageSize}, rightType=${params.rightType || 'all'}`); + + // 构建查询条件 + const whereClause: any = { + rewardStatus: 'EXPIRED', + }; + if (params.rightType) { + whereClause.rightType = params.rightType; + } + + // 查询总数 + const total = await this.prisma.rewardLedgerEntry.count({ + where: whereClause, + }); + + // 查询数据 + const entries = await this.prisma.rewardLedgerEntry.findMany({ + where: whereClause, + orderBy: { expiredAt: 'desc' }, + skip, + take: pageSize, + }); + + return { + entries: entries.map(entry => ({ + id: entry.id.toString(), + accountSequence: entry.accountSequence, + userId: entry.userId, + sourceOrderId: entry.sourceOrderNo, + rightType: entry.rightType, + usdtAmount: Number(entry.usdtAmount), + hashpowerAmount: Number(entry.hashpowerAmount), + createdAt: entry.createdAt.toISOString(), + expiredAt: entry.expiredAt?.toISOString() ?? null, + })), + total, + page, + pageSize, + totalPages: Math.ceil(total / pageSize), + }; + } } diff --git a/frontend/admin-web/src/components/features/system-account-report/SystemAccountsTab.module.scss b/frontend/admin-web/src/components/features/system-account-report/SystemAccountsTab.module.scss index ceeea931..9250d34f 100644 --- a/frontend/admin-web/src/components/features/system-account-report/SystemAccountsTab.module.scss +++ b/frontend/admin-web/src/components/features/system-account-report/SystemAccountsTab.module.scss @@ -340,6 +340,40 @@ border-top: 1px solid #e5e7eb; } +.entriesHeader { + display: flex; + justify-content: space-between; + align-items: center; + margin-bottom: 12px; +} + +.filterGroup { + display: flex; + align-items: center; + gap: 8px; + font-size: 14px; + color: #374151; + + label { + font-weight: 500; + } +} + +.filterSelect { + padding: 6px 12px; + border: 1px solid #d1d5db; + border-radius: 6px; + font-size: 14px; + background-color: #fff; + cursor: pointer; + + &:focus { + outline: none; + border-color: #3b82f6; + box-shadow: 0 0 0 2px rgba(59, 130, 246, 0.1); + } +} + .paginationInfo { text-align: center; padding: 12px; diff --git a/frontend/admin-web/src/components/features/system-account-report/SystemAccountsTab.tsx b/frontend/admin-web/src/components/features/system-account-report/SystemAccountsTab.tsx index 66724e90..3cbdf589 100644 --- a/frontend/admin-web/src/components/features/system-account-report/SystemAccountsTab.tsx +++ b/frontend/admin-web/src/components/features/system-account-report/SystemAccountsTab.tsx @@ -25,6 +25,8 @@ import type { FeeCollectionEntriesResponse, OfflineSettlementEntriesResponse, OfflineSettlementEntryDTO, + ExpiredRewardsEntriesResponse, + ExpiredRewardEntryDTO, } from '@/types'; import { ENTRY_TYPE_LABELS, ACCOUNT_TYPE_LABELS, FEE_TYPE_LABELS, REWARD_RIGHT_TYPE_LABELS, REWARD_STATUS_LABELS, FEE_COLLECTION_TYPE_LABELS, getAccountDisplayName, SYSTEM_ACCOUNT_NAMES, PROVINCE_CODE_NAMES } from '@/types'; import styles from './SystemAccountsTab.module.scss'; @@ -549,8 +551,58 @@ function OfflineSettlementSection({ data }: { data: SystemAccountReportResponse[ /** * 过期收益统计区域 * [2026-01-05] 更新:USDT改为绿积分,添加空值检查 + * [2026-01-07] 更新:添加明细列表显示功能 */ function ExpiredRewardsSection({ data }: { data: SystemAccountReportResponse['expiredRewards'] | null | undefined }) { + const [showEntries, setShowEntries] = useState(false); + const [entriesData, setEntriesData] = useState(null); + const [entriesLoading, setEntriesLoading] = useState(false); + const [currentPage, setCurrentPage] = useState(1); + const [selectedRightType, setSelectedRightType] = useState(''); + const pageSize = 20; + + // 加载明细数据 + const loadEntries = useCallback(async (page: number, rightType?: string) => { + setEntriesLoading(true); + try { + const response = await systemAccountReportService.getExpiredRewardsEntries({ + page, + pageSize, + rightType: rightType || undefined, + }); + if (response.success && response.data) { + setEntriesData(response.data); + } + } catch (error) { + console.error('加载过期收益明细失败:', error); + } finally { + setEntriesLoading(false); + } + }, [pageSize]); + + // 切换显示明细 + const toggleEntries = () => { + if (!showEntries && !entriesData) { + loadEntries(1, selectedRightType); + } + setShowEntries(!showEntries); + }; + + // 切换权益类型筛选 + const handleRightTypeChange = (rightType: string) => { + setSelectedRightType(rightType); + setCurrentPage(1); + if (showEntries) { + loadEntries(1, rightType); + } + }; + + // 分页 + const handlePageChange = (page: number) => { + setCurrentPage(page); + loadEntries(page, selectedRightType); + }; + if (!data) { return (
@@ -562,7 +614,15 @@ function ExpiredRewardsSection({ data }: { data: SystemAccountReportResponse['ex return (
-

过期收益统计

+
+

过期收益统计

+ +
{/* 汇总卡片 */}
@@ -630,7 +690,91 @@ function ExpiredRewardsSection({ data }: { data: SystemAccountReportResponse['ex )} - {(!data.byRightType || data.byRightType.length === 0) && (!data.byMonth || data.byMonth.length === 0) && ( + {/* [2026-01-07] 新增:明细列表 */} + {showEntries && ( +
+
+

过期收益明细

+
+ + +
+
+ + {entriesLoading ? ( +
+
+ 加载明细中... +
+ ) : entriesData && entriesData.entries.length > 0 ? ( + <> +
+ + + + + + + + + + + + + {entriesData.entries.map((entry) => ( + + + + + + + + + ))} + +
过期时间用户ID账户权益类型金额 (绿积分)订单号
{entry.expiredAt ? new Date(entry.expiredAt).toLocaleString('zh-CN') : '-'}{entry.userId}{getAccountDisplayName(entry.accountSequence)}{getRightTypeName(entry.rightType)}{formatAmount(entry.usdtAmount)}{entry.sourceOrderId}
+
+ {/* 分页 */} + {entriesData.totalPages > 1 && ( +
+ + + 第 {currentPage} 页 / 共 {entriesData.totalPages} 页 (共 {entriesData.total} 条) + + +
+ )} + + ) : ( +
暂无过期收益明细数据
+ )} +
+ )} + + {(!data.byRightType || data.byRightType.length === 0) && (!data.byMonth || data.byMonth.length === 0) && !showEntries && (
暂无过期收益数据
)}
diff --git a/frontend/admin-web/src/infrastructure/api/endpoints.ts b/frontend/admin-web/src/infrastructure/api/endpoints.ts index e71392f2..57d4b281 100644 --- a/frontend/admin-web/src/infrastructure/api/endpoints.ts +++ b/frontend/admin-web/src/infrastructure/api/endpoints.ts @@ -240,5 +240,7 @@ export const API_ENDPOINTS = { FEE_COLLECTION_ENTRIES: '/v1/system-account-reports/fee-collection-entries', // [2026-01-07] 新增:面对面结算明细列表 OFFLINE_SETTLEMENT_ENTRIES: '/v1/system-account-reports/offline-settlement-entries', + // [2026-01-07] 新增:过期收益明细列表 + EXPIRED_REWARDS_ENTRIES: '/v1/system-account-reports/expired-rewards-entries', }, } as const; diff --git a/frontend/admin-web/src/services/systemAccountReportService.ts b/frontend/admin-web/src/services/systemAccountReportService.ts index f97da5d5..8e7f58e6 100644 --- a/frontend/admin-web/src/services/systemAccountReportService.ts +++ b/frontend/admin-web/src/services/systemAccountReportService.ts @@ -20,6 +20,7 @@ import type { FeeCollectionSummaryResponse, FeeCollectionEntriesResponse, OfflineSettlementEntriesResponse, + ExpiredRewardsEntriesResponse, } from '@/types'; /** @@ -89,6 +90,18 @@ export const systemAccountReportService = { return apiClient.get(API_ENDPOINTS.SYSTEM_ACCOUNT_REPORTS.EXPIRED_REWARDS, { params }); }, + // [2026-01-07] 新增:获取过期收益明细列表 + /** + * 获取过期收益明细列表 + */ + async getExpiredRewardsEntries(params?: { + page?: number; + pageSize?: number; + rightType?: string; + }): Promise> { + return apiClient.get(API_ENDPOINTS.SYSTEM_ACCOUNT_REPORTS.EXPIRED_REWARDS_ENTRIES, { params }); + }, + // [2026-01-05] 新增:获取所有系统账户分类账明细 /** * 获取所有系统账户的分类账明细 diff --git a/frontend/admin-web/src/types/system-account.types.ts b/frontend/admin-web/src/types/system-account.types.ts index cbe1bb89..5c30ee68 100644 --- a/frontend/admin-web/src/types/system-account.types.ts +++ b/frontend/admin-web/src/types/system-account.types.ts @@ -97,6 +97,34 @@ export interface ExpiredRewardsSummary { }>; } +// [2026-01-07] 新增:过期收益明细条目 +/** + * 过期收益明细条目 + */ +export interface ExpiredRewardEntryDTO { + id: string; + accountSequence: string; + userId: number; + sourceOrderId: string; + rightType: string; + usdtAmount: number; + hashpowerAmount: number; + createdAt: string; + expiredAt: string | null; +} + +// [2026-01-07] 新增:过期收益明细响应 +/** + * 过期收益明细响应 + */ +export interface ExpiredRewardsEntriesResponse { + entries: ExpiredRewardEntryDTO[]; + total: number; + page: number; + pageSize: number; + totalPages: number; +} + /** * 固定系统账户 */