feat(admin-web): 用户详情页显示权益考核记录

- 后端 admin-service 添加 getBenefitAssessments 查询方法
- 更新 AuthorizationDetailResponseDto 包含 benefitAssessments
- 前端用户详情页授权信息 Tab 新增权益考核记录表格
- 显示字段:考核月份、角色、区域、完成/需求、权益操作、
  权益状态变化、有效期至、结果
- 添加权益操作类型样式:已激活(绿)、已续期(蓝)、已停用(红)、无变化(灰)

🤖 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-08 04:42:51 -08:00
parent 178c484f04
commit f55ac7e9cb
7 changed files with 258 additions and 5 deletions

View File

@ -343,9 +343,10 @@ export class UserDetailController {
throw new NotFoundException(`用户 ${accountSequence} 不存在`);
}
const [roles, assessments, systemLedger] = await Promise.all([
const [roles, assessments, benefitAssessments, systemLedger] = await Promise.all([
this.userDetailRepository.getAuthorizationRoles(accountSequence),
this.userDetailRepository.getMonthlyAssessments(accountSequence),
this.userDetailRepository.getBenefitAssessments(accountSequence),
this.userDetailRepository.getSystemAccountLedger(accountSequence),
]);
@ -385,6 +386,28 @@ export class UserDetailController {
completedAt: assessment.completedAt?.toISOString() || null,
assessedAt: assessment.assessedAt?.toISOString() || null,
})),
// [2026-01-08] 新增:权益考核记录
benefitAssessments: benefitAssessments.map((ba) => ({
id: ba.id,
authorizationId: ba.authorizationId,
roleType: ba.roleType,
regionCode: ba.regionCode,
regionName: ba.regionName,
assessmentMonth: ba.assessmentMonth,
monthIndex: ba.monthIndex,
monthlyTarget: ba.monthlyTarget,
cumulativeTarget: ba.cumulativeTarget,
treesCompleted: ba.treesCompleted,
treesRequired: ba.treesRequired,
benefitActionTaken: ba.benefitActionTaken,
previousBenefitStatus: ba.previousBenefitStatus,
newBenefitStatus: ba.newBenefitStatus,
newValidUntil: ba.newValidUntil?.toISOString() || null,
result: ba.result,
remarks: ba.remarks,
assessedAt: ba.assessedAt.toISOString(),
createdAt: ba.createdAt.toISOString(),
})),
systemAccountLedger: systemLedger.map((ledger) => ({
ledgerId: ledger.ledgerId.toString(),
accountId: ledger.accountId.toString(),

View File

@ -256,11 +256,38 @@ export class SystemAccountLedgerItemDto {
createdAt!: string;
}
/**
* [2026-01-08]
* MonthlyAssessment
*/
export class BenefitAssessmentDto {
id!: string;
authorizationId!: string;
roleType!: string;
regionCode!: string;
regionName!: string;
assessmentMonth!: string;
monthIndex!: number;
monthlyTarget!: number;
cumulativeTarget!: number;
treesCompleted!: number;
treesRequired!: number;
benefitActionTaken!: string; // ACTIVATED, RENEWED, DEACTIVATED, NO_CHANGE
previousBenefitStatus!: boolean;
newBenefitStatus!: boolean;
newValidUntil!: string | null;
result!: string;
remarks!: string | null;
assessedAt!: string;
createdAt!: string;
}
/**
*
*/
export class AuthorizationDetailResponseDto {
roles!: AuthorizationRoleDto[];
assessments!: MonthlyAssessmentDto[];
benefitAssessments!: BenefitAssessmentDto[]; // [2026-01-08] 新增:权益考核记录
systemAccountLedger!: SystemAccountLedgerItemDto[];
}

View File

@ -173,6 +173,29 @@ export interface SystemAccountLedger {
createdAt: Date;
}
// [2026-01-08] 新增:权益考核记录,独立于火柴人排名
export interface BenefitAssessment {
id: string;
authorizationId: string;
roleType: string;
regionCode: string;
regionName: string;
assessmentMonth: string;
monthIndex: number;
monthlyTarget: number;
cumulativeTarget: number;
treesCompleted: number;
treesRequired: number;
benefitActionTaken: string; // ACTIVATED, RENEWED, DEACTIVATED, NO_CHANGE
previousBenefitStatus: boolean;
newBenefitStatus: boolean;
newValidUntil: Date | null;
result: string;
remarks: string | null;
assessedAt: Date;
createdAt: Date;
}
// ============================================================================
// 仓储接口
// ============================================================================
@ -246,6 +269,12 @@ export interface IUserDetailQueryRepository {
*/
getSystemAccountLedger(accountSequence: string): Promise<SystemAccountLedger[]>;
/**
* [2026-01-08]
* MonthlyAssessment
*/
getBenefitAssessments(accountSequence: string): Promise<BenefitAssessment[]>;
/**
*
*/

View File

@ -15,6 +15,7 @@ import {
AuthorizationRole,
MonthlyAssessment,
SystemAccountLedger,
BenefitAssessment,
} from '../../../domain/repositories/user-detail-query.repository';
@Injectable()
@ -478,6 +479,36 @@ export class UserDetailQueryRepositoryImpl implements IUserDetailQueryRepository
return [];
}
// [2026-01-08] 新增:获取权益考核记录
async getBenefitAssessments(accountSequence: string): Promise<BenefitAssessment[]> {
const assessments = await this.prisma.benefitAssessmentQueryView.findMany({
where: { accountSequence },
orderBy: [{ assessmentMonth: 'desc' }, { createdAt: 'desc' }],
});
return assessments.map((assessment) => ({
id: assessment.id,
authorizationId: assessment.authorizationId,
roleType: assessment.roleType,
regionCode: assessment.regionCode,
regionName: assessment.regionName,
assessmentMonth: assessment.assessmentMonth,
monthIndex: assessment.monthIndex,
monthlyTarget: assessment.monthlyTarget,
cumulativeTarget: assessment.cumulativeTarget,
treesCompleted: assessment.treesCompleted,
treesRequired: assessment.treesRequired,
benefitActionTaken: assessment.benefitActionTaken,
previousBenefitStatus: assessment.previousBenefitStatus,
newBenefitStatus: assessment.newBenefitStatus,
newValidUntil: assessment.newValidUntil,
result: assessment.result,
remarks: assessment.remarks,
assessedAt: assessment.assessedAt,
createdAt: assessment.createdAt,
}));
}
async getPersonalAdoptionCount(accountSequence: string): Promise<number> {
// 统计用户的认种订单数量(状态为 MINING_ENABLED
const count = await this.prisma.plantingOrderQueryView.count({

View File

@ -84,10 +84,10 @@ const plantingStatusLabels: Record<string, string> = {
const roleTypeLabels: Record<string, string> = {
COMMUNITY: '社区权益',
COMMUNITY_PARTNER: '社区权益',
AUTH_PROVINCE_COMPANY: '省区域',
PROVINCE_COMPANY: '省团队',
AUTH_CITY_COMPANY: '市区域',
CITY_COMPANY: '市团队',
AUTH_PROVINCE_COMPANY: '省团队', // 授权省公司 = 省团队权益
PROVINCE_COMPANY: '省区域', // 正式省公司 = 省区域权益
AUTH_CITY_COMPANY: '市团队', // 授权市公司 = 市团队权益(40U)
CITY_COMPANY: '市区域', // 正式市公司 = 市区域权益(35U+2%算力)
};
const authStatusLabels: Record<string, string> = {
@ -104,6 +104,14 @@ const assessmentResultLabels: Record<string, string> = {
BYPASSED: '豁免',
};
// 权益操作类型标签
const benefitActionLabels: Record<string, string> = {
ACTIVATED: '已激活',
RENEWED: '已续期',
DEACTIVATED: '已停用',
NO_CHANGE: '无变化',
};
/**
*
* @param code 450000 451200
@ -777,6 +785,80 @@ export default function UserDetailPage() {
})()}
</div>
{/* 权益考核记录 */}
<div className={styles.authTab__assessments}>
<h3></h3>
{(() => {
// 只显示用户实际拥有且未撤销角色的权益考核记录
const activeRoleIds = new Set(
authData.roles
.filter(r => r.status !== 'REVOKED')
.map(r => r.id)
);
const filteredBenefitAssessments = (authData.benefitAssessments || []).filter(
a => activeRoleIds.has(a.authorizationId)
);
// 创建角色ID到区域名称的映射
const roleIdToRegion = new Map(
authData.roles.map(r => [r.id, r.regionName])
);
return filteredBenefitAssessments.length === 0 ? (
<div className={styles.authTab__empty}></div>
) : (
<div className={styles.ledgerTable}>
<div className={styles.ledgerTable__header}>
<div className={styles.ledgerTable__cell}></div>
<div className={styles.ledgerTable__cell}></div>
<div className={styles.ledgerTable__cell}></div>
<div className={styles.ledgerTable__cell}>/</div>
<div className={styles.ledgerTable__cell}></div>
<div className={styles.ledgerTable__cell}></div>
<div className={styles.ledgerTable__cell}></div>
<div className={styles.ledgerTable__cell}></div>
</div>
{filteredBenefitAssessments.map((assessment) => (
<div key={assessment.id} className={styles.ledgerTable__row}>
<div className={styles.ledgerTable__cell}>{assessment.assessmentMonth}</div>
<div className={styles.ledgerTable__cell}>
{roleTypeLabels[assessment.roleType] || assessment.roleType}
</div>
<div className={styles.ledgerTable__cell}>
{roleIdToRegion.get(assessment.authorizationId) || assessment.regionName || getRegionName(assessment.regionCode)}
</div>
<div className={styles.ledgerTable__cell}>
{formatNumber(assessment.treesCompleted)} / {formatNumber(assessment.treesRequired)}
</div>
<div className={styles.ledgerTable__cell}>
<span className={cn(
styles.ledgerTable__benefitAction,
styles[`ledgerTable__benefitAction--${assessment.benefitActionTaken.toLowerCase()}`]
)}>
{benefitActionLabels[assessment.benefitActionTaken] || assessment.benefitActionTaken}
</span>
</div>
<div className={styles.ledgerTable__cell}>
{assessment.previousBenefitStatus ? '有效' : '无效'} {assessment.newBenefitStatus ? '有效' : '无效'}
</div>
<div className={styles.ledgerTable__cell}>
{assessment.newValidUntil ? formatDate(assessment.newValidUntil) : '-'}
</div>
<div className={styles.ledgerTable__cell}>
<span className={cn(
styles.ledgerTable__result,
styles[`ledgerTable__result--${assessment.result.toLowerCase()}`]
)}>
{assessmentResultLabels[assessment.result] || assessment.result}
</span>
</div>
</div>
))}
</div>
);
})()}
</div>
{/* 系统账户流水(如果有) */}
{authData.systemAccountLedger.length > 0 && (
<div className={styles.authTab__systemLedger}>

View File

@ -847,6 +847,35 @@
}
}
// 权益操作类型标签
.ledgerTable__benefitAction {
display: inline-block;
padding: $spacing-xs $spacing-sm;
border-radius: $border-radius-sm;
font-size: $font-size-xs;
font-weight: $font-weight-medium;
&--activated {
background-color: rgba($success-color, 0.1);
color: $success-color;
}
&--renewed {
background-color: rgba($primary-color, 0.1);
color: $primary-color;
}
&--deactivated {
background-color: rgba($error-color, 0.1);
color: $error-color;
}
&--no_change {
background-color: rgba($text-disabled, 0.2);
color: $text-secondary;
}
}
// ============================================================================
// 分页
// ============================================================================

View File

@ -227,9 +227,33 @@ export interface SystemAccountLedgerItem {
createdAt: string;
}
// [2026-01-08] 新增:权益考核记录
export interface BenefitAssessment {
id: string;
authorizationId: string;
roleType: string;
regionCode: string;
regionName: string;
assessmentMonth: string;
monthIndex: number;
monthlyTarget: number;
cumulativeTarget: number;
treesCompleted: number;
treesRequired: number;
benefitActionTaken: string; // ACTIVATED, RENEWED, DEACTIVATED, NO_CHANGE
previousBenefitStatus: boolean;
newBenefitStatus: boolean;
newValidUntil: string | null;
result: string;
remarks: string | null;
assessedAt: string;
createdAt: string;
}
export interface AuthorizationDetailResponse {
roles: AuthorizationRole[];
assessments: MonthlyAssessment[];
benefitAssessments: BenefitAssessment[]; // [2026-01-08] 新增
systemAccountLedger: SystemAccountLedgerItem[];
}
@ -320,3 +344,11 @@ export const ASSESSMENT_RESULT_LABELS: Record<string, string> = {
FAILED: '未通过',
BYPASSED: '豁免',
};
// [2026-01-08] 新增:权益操作类型标签
export const BENEFIT_ACTION_LABELS: Record<string, string> = {
ACTIVATED: '已激活',
RENEWED: '已续期',
DEACTIVATED: '已停用',
NO_CHANGE: '无变化',
};