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} 不存在`);
|
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.getAuthorizationRoles(accountSequence),
|
||||||
this.userDetailRepository.getMonthlyAssessments(accountSequence),
|
this.userDetailRepository.getMonthlyAssessments(accountSequence),
|
||||||
|
this.userDetailRepository.getBenefitAssessments(accountSequence),
|
||||||
this.userDetailRepository.getSystemAccountLedger(accountSequence),
|
this.userDetailRepository.getSystemAccountLedger(accountSequence),
|
||||||
]);
|
]);
|
||||||
|
|
||||||
|
|
@ -385,6 +386,28 @@ export class UserDetailController {
|
||||||
completedAt: assessment.completedAt?.toISOString() || null,
|
completedAt: assessment.completedAt?.toISOString() || null,
|
||||||
assessedAt: assessment.assessedAt?.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) => ({
|
systemAccountLedger: systemLedger.map((ledger) => ({
|
||||||
ledgerId: ledger.ledgerId.toString(),
|
ledgerId: ledger.ledgerId.toString(),
|
||||||
accountId: ledger.accountId.toString(),
|
accountId: ledger.accountId.toString(),
|
||||||
|
|
|
||||||
|
|
@ -256,11 +256,38 @@ export class SystemAccountLedgerItemDto {
|
||||||
createdAt!: string;
|
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 {
|
export class AuthorizationDetailResponseDto {
|
||||||
roles!: AuthorizationRoleDto[];
|
roles!: AuthorizationRoleDto[];
|
||||||
assessments!: MonthlyAssessmentDto[];
|
assessments!: MonthlyAssessmentDto[];
|
||||||
|
benefitAssessments!: BenefitAssessmentDto[]; // [2026-01-08] 新增:权益考核记录
|
||||||
systemAccountLedger!: SystemAccountLedgerItemDto[];
|
systemAccountLedger!: SystemAccountLedgerItemDto[];
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -173,6 +173,29 @@ export interface SystemAccountLedger {
|
||||||
createdAt: Date;
|
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[]>;
|
getSystemAccountLedger(accountSequence: string): Promise<SystemAccountLedger[]>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* [2026-01-08] 获取权益考核记录
|
||||||
|
* 独立于火柴人排名(MonthlyAssessment),专门记录权益有效性考核历史
|
||||||
|
*/
|
||||||
|
getBenefitAssessments(accountSequence: string): Promise<BenefitAssessment[]>;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 获取用户个人认种量(有效树数)
|
* 获取用户个人认种量(有效树数)
|
||||||
*/
|
*/
|
||||||
|
|
|
||||||
|
|
@ -15,6 +15,7 @@ import {
|
||||||
AuthorizationRole,
|
AuthorizationRole,
|
||||||
MonthlyAssessment,
|
MonthlyAssessment,
|
||||||
SystemAccountLedger,
|
SystemAccountLedger,
|
||||||
|
BenefitAssessment,
|
||||||
} from '../../../domain/repositories/user-detail-query.repository';
|
} from '../../../domain/repositories/user-detail-query.repository';
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
|
|
@ -478,6 +479,36 @@ export class UserDetailQueryRepositoryImpl implements IUserDetailQueryRepository
|
||||||
return [];
|
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> {
|
async getPersonalAdoptionCount(accountSequence: string): Promise<number> {
|
||||||
// 统计用户的认种订单数量(状态为 MINING_ENABLED)
|
// 统计用户的认种订单数量(状态为 MINING_ENABLED)
|
||||||
const count = await this.prisma.plantingOrderQueryView.count({
|
const count = await this.prisma.plantingOrderQueryView.count({
|
||||||
|
|
|
||||||
|
|
@ -84,10 +84,10 @@ const plantingStatusLabels: Record<string, string> = {
|
||||||
const roleTypeLabels: Record<string, string> = {
|
const roleTypeLabels: Record<string, string> = {
|
||||||
COMMUNITY: '社区权益',
|
COMMUNITY: '社区权益',
|
||||||
COMMUNITY_PARTNER: '社区权益',
|
COMMUNITY_PARTNER: '社区权益',
|
||||||
AUTH_PROVINCE_COMPANY: '省区域',
|
AUTH_PROVINCE_COMPANY: '省团队', // 授权省公司 = 省团队权益
|
||||||
PROVINCE_COMPANY: '省团队',
|
PROVINCE_COMPANY: '省区域', // 正式省公司 = 省区域权益
|
||||||
AUTH_CITY_COMPANY: '市区域',
|
AUTH_CITY_COMPANY: '市团队', // 授权市公司 = 市团队权益(40U)
|
||||||
CITY_COMPANY: '市团队',
|
CITY_COMPANY: '市区域', // 正式市公司 = 市区域权益(35U+2%算力)
|
||||||
};
|
};
|
||||||
|
|
||||||
const authStatusLabels: Record<string, string> = {
|
const authStatusLabels: Record<string, string> = {
|
||||||
|
|
@ -104,6 +104,14 @@ const assessmentResultLabels: Record<string, string> = {
|
||||||
BYPASSED: '豁免',
|
BYPASSED: '豁免',
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// 权益操作类型标签
|
||||||
|
const benefitActionLabels: Record<string, string> = {
|
||||||
|
ACTIVATED: '已激活',
|
||||||
|
RENEWED: '已续期',
|
||||||
|
DEACTIVATED: '已停用',
|
||||||
|
NO_CHANGE: '无变化',
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 将区域代码转换为名称
|
* 将区域代码转换为名称
|
||||||
* @param code 区域代码,如 450000(省)或 451200(市)
|
* @param code 区域代码,如 450000(省)或 451200(市)
|
||||||
|
|
@ -777,6 +785,80 @@ export default function UserDetailPage() {
|
||||||
})()}
|
})()}
|
||||||
</div>
|
</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 && (
|
{authData.systemAccountLedger.length > 0 && (
|
||||||
<div className={styles.authTab__systemLedger}>
|
<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;
|
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 {
|
export interface AuthorizationDetailResponse {
|
||||||
roles: AuthorizationRole[];
|
roles: AuthorizationRole[];
|
||||||
assessments: MonthlyAssessment[];
|
assessments: MonthlyAssessment[];
|
||||||
|
benefitAssessments: BenefitAssessment[]; // [2026-01-08] 新增
|
||||||
systemAccountLedger: SystemAccountLedgerItem[];
|
systemAccountLedger: SystemAccountLedgerItem[];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -320,3 +344,11 @@ export const ASSESSMENT_RESULT_LABELS: Record<string, string> = {
|
||||||
FAILED: '未通过',
|
FAILED: '未通过',
|
||||||
BYPASSED: '豁免',
|
BYPASSED: '豁免',
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// [2026-01-08] 新增:权益操作类型标签
|
||||||
|
export const BENEFIT_ACTION_LABELS: Record<string, string> = {
|
||||||
|
ACTIVATED: '已激活',
|
||||||
|
RENEWED: '已续期',
|
||||||
|
DEACTIVATED: '已停用',
|
||||||
|
NO_CHANGE: '无变化',
|
||||||
|
};
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue