feat(mining-admin): 添加用户详情页缺失的 API 端点

- 添加 referral-tree API(返回空推荐关系数据)
- 添加 planting-ledger API(返回空认种数据)
- 添加 wallet-ledger API(返回空钱包流水数据)
- 修复前端 referral-tree 组件空数据处理

注:这些 API 目前返回占位数据,完整数据需要通过 CDC 从各服务同步

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
hailin 2026-01-11 20:53:18 -08:00
parent fc3efe6a27
commit 1a7c73e531
3 changed files with 139 additions and 7 deletions

View File

@ -102,4 +102,43 @@ export class UsersController {
status, status,
}); });
} }
@Get(':accountSequence/referral-tree')
@ApiOperation({ summary: '获取用户引荐关系树' })
@ApiParam({ name: 'accountSequence', type: String })
@ApiQuery({ name: 'direction', required: false, type: String, description: 'up, down, both' })
@ApiQuery({ name: 'depth', required: false, type: Number })
async getReferralTree(
@Param('accountSequence') accountSequence: string,
@Query('direction') direction?: string,
@Query('depth') depth?: number,
) {
return this.usersService.getReferralTree(accountSequence, direction || 'both', depth || 1);
}
@Get(':accountSequence/planting-ledger')
@ApiOperation({ summary: '获取用户认种分类账' })
@ApiParam({ name: 'accountSequence', type: String })
@ApiQuery({ name: 'page', required: false, type: Number })
@ApiQuery({ name: 'pageSize', required: false, type: Number })
async getPlantingLedger(
@Param('accountSequence') accountSequence: string,
@Query('page') page?: number,
@Query('pageSize') pageSize?: number,
) {
return this.usersService.getPlantingLedger(accountSequence, page ?? 1, pageSize ?? 20);
}
@Get(':accountSequence/wallet-ledger')
@ApiOperation({ summary: '获取用户钱包流水' })
@ApiParam({ name: 'accountSequence', type: String })
@ApiQuery({ name: 'page', required: false, type: Number })
@ApiQuery({ name: 'pageSize', required: false, type: Number })
async getWalletLedger(
@Param('accountSequence') accountSequence: string,
@Query('page') page?: number,
@Query('pageSize') pageSize?: number,
) {
return this.usersService.getWalletLedger(accountSequence, page ?? 1, pageSize ?? 20);
}
} }

View File

@ -252,6 +252,99 @@ export class UsersService {
}; };
} }
/**
*
* TODO: identity-service
*/
async getReferralTree(accountSequence: string, direction: string, depth: number) {
const user = await this.prisma.syncedUser.findUnique({
where: { accountSequence },
include: { contributionAccount: true },
});
if (!user) {
throw new NotFoundException(`用户 ${accountSequence} 不存在`);
}
// 返回基础结构,数据需要从 identity-service 同步
return {
currentUser: {
accountSequence: user.accountSequence,
nickname: user.realName || null,
avatar: null,
personalAdoptions: 0,
teamAdoptions: 0,
directReferralCount: user.contributionAccount?.directReferralCount || 0,
},
ancestors: [],
directReferrals: [],
note: '推荐关系数据需要从 identity-service 同步',
};
}
/**
*
* TODO: adoption-service
*/
async getPlantingLedger(accountSequence: string, page: number, pageSize: number) {
const user = await this.prisma.syncedUser.findUnique({
where: { accountSequence },
});
if (!user) {
throw new NotFoundException(`用户 ${accountSequence} 不存在`);
}
// 返回空数据,数据需要从 adoption-service 同步
return {
summary: {
totalOrders: 0,
totalTreeCount: 0,
totalAmount: '0',
effectiveTreeCount: 0,
firstPlantingAt: null,
lastPlantingAt: null,
},
items: [],
total: 0,
page,
pageSize,
totalPages: 0,
note: '认种数据需要从 adoption-service 同步',
};
}
/**
*
* TODO: mining-service
*/
async getWalletLedger(accountSequence: string, page: number, pageSize: number) {
const user = await this.prisma.syncedUser.findUnique({
where: { accountSequence },
include: { miningAccount: true },
});
if (!user) {
throw new NotFoundException(`用户 ${accountSequence} 不存在`);
}
const mining = user.miningAccount;
return {
summary: {
availableBalance: mining?.availableBalance?.toString() || '0',
frozenBalance: mining?.frozenBalance?.toString() || '0',
totalMined: mining?.totalMined?.toString() || '0',
},
items: [],
total: 0,
page,
pageSize,
totalPages: 0,
note: '钱包流水数据需要从 mining-service 同步',
};
}
// =========================================================================== // ===========================================================================
// 辅助方法 // 辅助方法
// =========================================================================== // ===========================================================================

View File

@ -25,7 +25,7 @@ export function ReferralTree({ accountSequence }: ReferralTreeProps) {
// 当 referralTree 数据加载完成后,自动展开当前用户的直推下级 // 当 referralTree 数据加载完成后,自动展开当前用户的直推下级
useEffect(() => { useEffect(() => {
if (referralTree && referralTree.directReferrals.length > 0) { if (referralTree?.currentUser && referralTree.directReferrals?.length > 0) {
setExpandedNodes((prev) => ({ setExpandedNodes((prev) => ({
...prev, ...prev,
[referralTree.currentUser.accountSequence]: referralTree.directReferrals, [referralTree.currentUser.accountSequence]: referralTree.directReferrals,
@ -112,16 +112,16 @@ export function ReferralTree({ accountSequence }: ReferralTreeProps) {
{/* 向上的引荐人链 */} {/* 向上的引荐人链 */}
<div className="space-y-2"> <div className="space-y-2">
<p className="text-sm font-medium text-muted-foreground"> ()</p> <p className="text-sm font-medium text-muted-foreground"> ()</p>
{referralTree.ancestors.length > 0 ? ( {(referralTree.ancestors?.length ?? 0) > 0 ? (
<div className="space-y-2"> <div className="space-y-2">
{referralTree.ancestors.map((ancestor, index) => ( {(referralTree.ancestors || []).map((ancestor, index) => (
<div key={ancestor.accountSequence}> <div key={ancestor.accountSequence}>
<ReferralNodeCard <ReferralNodeCard
node={ancestor} node={ancestor}
onClick={() => handleTreeNodeClick(ancestor)} onClick={() => handleTreeNodeClick(ancestor)}
variant="ancestor" variant="ancestor"
/> />
{index < referralTree.ancestors.length - 1 && ( {index < (referralTree.ancestors?.length ?? 0) - 1 && (
<div className="flex justify-center py-1"> <div className="flex justify-center py-1">
<ChevronDown className="h-4 w-4 text-muted-foreground" /> <ChevronDown className="h-4 w-4 text-muted-foreground" />
</div> </div>
@ -154,13 +154,13 @@ export function ReferralTree({ accountSequence }: ReferralTreeProps) {
</div> </div>
{/* 直推下级列表 */} {/* 直推下级列表 */}
{referralTree.directReferrals.length > 0 && ( {(referralTree.directReferrals?.length ?? 0) > 0 && (
<div className="space-y-2 ml-6 border-l-2 border-muted pl-4"> <div className="space-y-2 ml-6 border-l-2 border-muted pl-4">
<p className="text-sm font-medium text-muted-foreground"> <p className="text-sm font-medium text-muted-foreground">
({referralTree.directReferrals.length}) ({referralTree.directReferrals?.length ?? 0})
</p> </p>
<div className="space-y-2"> <div className="space-y-2">
{referralTree.directReferrals.map((child) => ( {(referralTree.directReferrals || []).map((child) => (
<ReferralNodeCard <ReferralNodeCard
key={child.accountSequence} key={child.accountSequence}
node={child} node={child}