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:
parent
178c484f04
commit
f55ac7e9cb
|
|
@ -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(),
|
||||
|
|
|
|||
|
|
@ -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[];
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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[]>;
|
||||
|
||||
/**
|
||||
* 获取用户个人认种量(有效树数)
|
||||
*/
|
||||
|
|
|
|||
|
|
@ -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({
|
||||
|
|
|
|||
|
|
@ -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}>
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// 分页
|
||||
// ============================================================================
|
||||
|
|
|
|||
|
|
@ -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: '无变化',
|
||||
};
|
||||
|
|
|
|||
Loading…
Reference in New Issue