feat(admin-web): 仪表板改用 planting-service 源数据

统计卡片和趋势图不再使用 reporting-service,直接使用 planting-service 的源数据:

- 统计卡片:总认种量、总订单数、今日认种、本月认种
- 趋势图:支持 7天/30天/90天 切换
- 新增 usePlantingTrendForDashboard hook

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
hailin 2026-01-06 10:47:44 -08:00
parent b947fe8205
commit 305514b246
4 changed files with 120 additions and 43 deletions

View File

@ -601,7 +601,33 @@
"Bash(git commit -m \"$\\(cat <<''EOF''\nfeat\\(wallet-service\\): 实现 Unit of Work 模式保证 settleToBalance 事务原子性\n\n- 新增 UnitOfWork 接口和实现,使用 Prisma Interactive Transaction\n- 修改 IWalletAccountRepository 和 ILedgerEntryRepository 接口支持可选事务参数\n- 修改仓库实现,支持在事务中执行数据库操作\n- 修改 settleToBalance 方法使用 UnitOfWork确保钱包更新和流水记录原子性\n- 注册 UnitOfWorkService 到 InfrastructureModule\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(ls -la \"c:\\\\Users\\\\dong\\\\Desktop\\\\rwadurian\\\\backend\\\\services\\\\wallet-service\\\\prisma\\\\migrations\"\" 2>/dev/null || dir \"c:UsersdongDesktoprwadurianbackendserviceswallet-serviceprismamigrations \")",
"Bash(git commit -m \"$\\(cat <<''EOF''\nfeat\\(admin-web\\): 添加系统账户收益类型汇总统计功能\n\n在数据统计-系统账户中新增5个统计Tab\n- 手续费账户汇总统计成本费、运营费、总部社区基础费、RWAD底池注入\n- 省团队收益汇总:统计省团队权益收益\n- 市团队收益汇总:统计市团队权益收益\n- 分享引荐收益汇总:统计分享权益收益\n- 社区收益汇总:统计社区权益收益\n\n后端变更\n- reward-service: 添加 getRewardsSummaryByType、getAllRewardTypeSummaries 方法\n- reporting-service: 聚合收益类型汇总统计接口\n\n前端变更\n- 添加 RewardTypeSummary、FeeAccountSummary 类型定义\n- 添加 getRewardTypeSummaries API 方法\n- 添加 FeeAccountSection、RewardTypeSummarySection 组件\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\\(wallet-service\\): 实现手续费归集账户功能\n\n- 新增系统账户 S0000000006 \\(user_id=-6\\) 用于归集提现手续费\n- 新增 FEE_COLLECTION 流水类型记录手续费归集\n- 区块链提现完成时使用 UnitOfWork 事务归集手续费\n- 法币提现完成时在事务中归集手续费\n- WithdrawalOrderRepository 添加事务支持\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''\nfeat\\(wallet-service\\): 实现手续费归集账户功能\n\n- 新增系统账户 S0000000006 \\(user_id=-6\\) 用于归集提现手续费\n- 新增 FEE_COLLECTION 流水类型记录手续费归集\n- 区块链提现完成时使用 UnitOfWork 事务归集手续费\n- 法币提现完成时在事务中归集手续费\n- WithdrawalOrderRepository 添加事务支持\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(backend/services/blockchain-service/src/application/application.module.ts )",
"Bash(backend/services/blockchain-service/src/application/event-handlers/system-withdrawal-requested.handler.ts )",
"Bash(backend/services/blockchain-service/src/infrastructure/kafka/withdrawal-event-consumer.service.ts )",
"Bash(backend/services/wallet-service/src/api/api.module.ts )",
"Bash(backend/services/wallet-service/src/api/controllers/index.ts )",
"Bash(backend/services/wallet-service/src/api/controllers/system-withdrawal.controller.ts )",
"Bash(backend/services/wallet-service/src/application/services/index.ts )",
"Bash(backend/services/wallet-service/src/application/services/system-withdrawal-application.service.ts )",
"Bash(backend/services/wallet-service/src/application/event-handlers/system-withdrawal-status.handler.ts )",
"Bash(backend/services/wallet-service/src/infrastructure/external/identity/identity-client.service.ts )",
"Bash(backend/services/wallet-service/src/infrastructure/kafka/withdrawal-event-consumer.service.ts)",
"Bash(backend/services/planting-service/src/api/controllers/planting-stats.controller.ts )",
"Bash(backend/services/planting-service/src/api/dto/response/planting-stats.response.ts )",
"Bash(backend/services/planting-service/src/domain/repositories/planting-order.repository.interface.ts )",
"Bash(backend/services/planting-service/src/infrastructure/persistence/repositories/planting-order.repository.impl.ts )",
"Bash(frontend/admin-web/src/app/\\\\\\(dashboard\\\\\\)/statistics/page.tsx )",
"Bash(frontend/admin-web/src/app/\\\\\\(dashboard\\\\\\)/statistics/statistics.module.scss )",
"Bash(frontend/admin-web/src/services/dashboardService.ts )",
"Bash(frontend/admin-web/src/types/dashboard.types.ts)",
"Bash(git commit -m \"$\\(cat <<''EOF''\nfeat\\(wallet/blockchain/identity\\): implement system account withdrawal feature\n\n- Add SystemWithdrawalApplicationService to handle system account transfers\n- Add SystemWithdrawalController with endpoints for request, query, and account listing\n- Add SystemWithdrawalStatusHandler to process blockchain confirmation/failure events\n- Add SystemWithdrawalRequestedHandler in blockchain-service to execute ERC20 transfers\n- Add getUserByAccountSequence endpoint in identity-service for user lookup\n- Support dynamic memo generation based on actual source account name\n- Dual-sided ledger entries for system account transfers\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(frontend/admin-web/src/hooks/index.ts )",
"Bash(frontend/admin-web/src/hooks/useSystemWithdrawal.ts )",
"Bash(frontend/admin-web/src/services/systemWithdrawalService.ts )",
"Bash(frontend/admin-web/src/types/system-withdrawal.types.ts )",
"Bash(\"frontend/admin-web/src/app/\\(dashboard\\)/system-transfer/\")",
"Bash(git commit -m \"$\\(cat <<''EOF''\nfeat\\(admin-web\\): add system account transfer management page\n\n- Add system-transfer page with transfer form and order history\n- Add SystemWithdrawalService for API calls\n- Add useSystemWithdrawal hooks for React Query integration\n- Add system-withdrawal types definitions\n- Add navigation menu item for system transfer\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": []

View File

@ -152,7 +152,7 @@ async function main() {
log('DEBUG', '转出方流水:', {
id: transferOutEntry.id,
amount: transferOutEntry.amount.toString(),
balanceAfter: transferOutEntry.balanceAfter.toString(),
balanceAfter: transferOutEntry.balanceAfter?.toString() ?? 'null',
createdAt: transferOutEntry.createdAt.toISOString(),
});
}
@ -172,7 +172,7 @@ async function main() {
log('ERROR', '流水详情:', {
id: existingTransferIn.id,
amount: existingTransferIn.amount.toString(),
balanceAfter: existingTransferIn.balanceAfter.toString(),
balanceAfter: existingTransferIn.balanceAfter?.toString() ?? 'null',
createdAt: existingTransferIn.createdAt.toISOString(),
});
log('ERROR', '');

View File

@ -1,5 +1,10 @@
'use client';
/**
*
* [2026-01-06] planting-service
*/
import { useState } from 'react';
import { Button } from '@/components/common';
import { PageContainer } from '@/components/layout';
@ -8,11 +13,10 @@ import { TrendChart } from '@/components/features/dashboard/TrendChart';
import { RegionDistribution } from '@/components/features/dashboard/RegionDistribution';
import { RecentActivity } from '@/components/features/dashboard/RecentActivity';
import {
useDashboardStats,
useDashboardTrend,
useDashboardRegion,
useDashboardActivities,
usePlantingStats,
usePlantingTrendForDashboard,
} from '@/hooks';
import type { DashboardPeriod } from '@/types';
import styles from './dashboard.module.scss';
@ -45,23 +49,34 @@ const EmptyData = ({ message }: { message: string }) => (
</div>
);
/** 将 DashboardPeriod 转换为天数 */
function periodToDays(period: DashboardPeriod): 7 | 30 | 90 {
switch (period) {
case '7d': return 7;
case '30d': return 30;
case '90d': return 90;
default: return 7;
}
}
export default function DashboardPage() {
const [trendPeriod, setTrendPeriod] = useState<DashboardPeriod>('7d');
// 使用 React Query hooks 获取数据
// [2026-01-06] 直接使用 planting-service 数据(源数据)
const {
data: statsData,
data: plantingStatsData,
isLoading: statsLoading,
error: statsError,
refetch: refetchStats,
} = useDashboardStats();
} = usePlantingStats();
// [2026-01-06] 趋势数据也使用 planting-service
const {
data: trendData,
isLoading: trendLoading,
error: trendError,
refetch: refetchTrend,
} = useDashboardTrend(trendPeriod);
} = usePlantingTrendForDashboard(periodToDays(trendPeriod));
const {
data: regionData,
@ -77,28 +92,37 @@ export default function DashboardPage() {
refetch: refetchActivities,
} = useDashboardActivities(5);
// 从 planting-service 获取可靠的认种统计数据
const {
data: plantingStatsData,
isLoading: plantingStatsLoading,
error: plantingStatsError,
refetch: refetchPlantingStats,
} = usePlantingStats();
// 合并统计数据:总认种量使用 planting-service 的数据
const mergedStatsData = statsData?.map((stat) => {
if (stat.title === '总认种量' && plantingStatsData) {
return {
...stat,
value: plantingStatsData.totalTreeCount,
};
}
return stat;
});
// 判断是否正在加载统计数据
const isStatsLoading = statsLoading || plantingStatsLoading;
const hasStatsError = statsError || plantingStatsError;
// [2026-01-06] 基于 planting-service 数据构建统计卡片数据
const statsCards = plantingStatsData ? [
{
title: '总认种量',
value: plantingStatsData.totalTreeCount,
suffix: '棵',
change: { value: plantingStatsData.todayStats?.treeCount ?? 0, trend: 'up' as const },
color: '#10b981',
},
{
title: '总订单数',
value: plantingStatsData.totalOrderCount,
suffix: '单',
change: { value: plantingStatsData.todayStats?.orderCount ?? 0, trend: 'up' as const },
color: '#3b82f6',
},
{
title: '今日认种',
value: plantingStatsData.todayStats?.treeCount ?? 0,
suffix: '棵',
change: { value: 0, trend: 'up' as const },
color: '#f59e0b',
},
{
title: '本月认种',
value: plantingStatsData.monthStats?.treeCount ?? 0,
suffix: '棵',
change: { value: 0, trend: 'up' as const },
color: '#8b5cf6',
},
] : [];
const headerActions = (
<>
@ -121,29 +145,26 @@ export default function DashboardPage() {
actions={headerActions}
>
<div className={styles.dashboard}>
{/* 统计卡片区 */}
{/* 统计卡片区 - [2026-01-06] 使用 planting-service 数据 */}
<div className={styles.dashboard__stats}>
{isStatsLoading ? (
{statsLoading ? (
// 加载状态显示骨架屏
<>
{[1, 2, 3, 4].map((i) => (
<StatCardSkeleton key={i} />
))}
</>
) : hasStatsError ? (
) : statsError ? (
// 错误状态
<div className={styles.dashboard__statsError}>
<ErrorMessage
message="加载统计数据失败"
onRetry={() => {
refetchStats();
refetchPlantingStats();
}}
onRetry={() => refetchStats()}
/>
</div>
) : mergedStatsData && mergedStatsData.length > 0 ? (
// 正常显示数据(总认种量使用 planting-service 的可靠数据)
mergedStatsData.map((stat, index) => (
) : statsCards.length > 0 ? (
// 正常显示数据(直接使用 planting-service 源数据)
statsCards.map((stat, index) => (
<StatCard key={index} {...stat} />
))
) : (
@ -152,7 +173,7 @@ export default function DashboardPage() {
)}
</div>
{/* 图表区 */}
{/* 图表区 - [2026-01-06] 使用 planting-service 数据 */}
<div className={styles.dashboard__charts}>
<div className={styles.dashboard__mainChart}>
{trendError ? (
@ -163,7 +184,7 @@ export default function DashboardPage() {
) : (
<TrendChart
title="认种趋势"
data={trendData?.data ?? []}
data={trendData ?? []}
period={trendPeriod}
onPeriodChange={setTrendPeriod}
loading={trendLoading}

View File

@ -16,6 +16,8 @@ export const dashboardKeys = {
region: () => [...dashboardKeys.all, 'region'] as const,
activities: (limit: number) => [...dashboardKeys.all, 'activities', limit] as const,
plantingStats: () => [...dashboardKeys.all, 'plantingStats'] as const,
// [2026-01-06] 新增planting-service 趋势数据(用于仪表板)
plantingTrend: (days: number) => [...dashboardKeys.all, 'plantingTrend', days] as const,
};
/**
@ -114,3 +116,31 @@ export function usePlantingStats() {
gcTime: 5 * 60 * 1000,
});
}
/**
*
* [2026-01-06] 使 planting-service
* @param days 7, 30, 90
*/
export function usePlantingTrendForDashboard(days: 7 | 30 | 90 = 7) {
return useQuery({
queryKey: dashboardKeys.plantingTrend(days),
queryFn: async () => {
// 使用 day 维度获取趋势数据
const response = await dashboardService.getPlantingTrendData('day');
if (!response?.data) return [];
// 根据 days 参数截取最近的数据
const allData = response.data;
const slicedData = allData.slice(-days);
// 转换为 TrendChart 组件需要的格式
return slicedData.map(item => ({
date: item.label.slice(5), // 2026-01-06 -> 01-06
value: item.treeCount,
}));
},
staleTime: 60 * 1000,
gcTime: 5 * 60 * 1000,
});
}