feat(authorization): add community assessment progress to profile page

Backend:
- Add initialTargetTreeCount, currentTreeCount, monthlyTargetTreeCount
  fields to AuthorizationDTO and AuthorizationResponse
- Query TeamStatistics to populate current tree count in getUserAuthorizations

Frontend:
- Update AuthorizationResponse to parse new progress fields
- Replace hardcoded community assessment values with real API data
- Show authorization status: red (unauthorized), orange (pending), green (active)
- Display progress bar and target requirements based on benefit status

🤖 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 2025-12-10 06:44:13 -08:00
parent e5e2793337
commit 22c32031af
5 changed files with 236 additions and 45 deletions

View File

@ -35,6 +35,15 @@ export class AuthorizationResponse {
@ApiProperty({ description: '是否豁免占比考核' }) @ApiProperty({ description: '是否豁免占比考核' })
exemptFromPercentageCheck: boolean exemptFromPercentageCheck: boolean
@ApiProperty({ description: '初始考核目标社区10市100省500' })
initialTargetTreeCount: number
@ApiProperty({ description: '当前团队认种数量' })
currentTreeCount: number
@ApiProperty({ description: '月度考核目标' })
monthlyTargetTreeCount: number
@ApiProperty({ description: '创建时间' }) @ApiProperty({ description: '创建时间' })
createdAt: Date createdAt: Date

View File

@ -12,6 +12,10 @@ export interface AuthorizationDTO {
currentMonthIndex: number currentMonthIndex: number
requireLocalPercentage: number requireLocalPercentage: number
exemptFromPercentageCheck: boolean exemptFromPercentageCheck: boolean
// 考核进度字段
initialTargetTreeCount: number // 初始考核目标社区10市100省500
currentTreeCount: number // 当前团队认种数量
monthlyTargetTreeCount: number // 月度考核目标社区固定10
createdAt: Date createdAt: Date
updatedAt: Date updatedAt: Date
} }

View File

@ -322,7 +322,11 @@ export class AuthorizationApplicationService {
UserId.create(userId), UserId.create(userId),
) )
return authorizations.map((auth) => this.toAuthorizationDTO(auth)) // 查询用户团队统计数据
const teamStats = await this.statsRepository.findByUserId(UserId.create(userId).value)
const currentTreeCount = teamStats?.totalTeamPlantingCount || 0
return authorizations.map((auth) => this.toAuthorizationDTO(auth, currentTreeCount))
} }
/** /**
@ -333,7 +337,13 @@ export class AuthorizationApplicationService {
AuthorizationId.create(authorizationId), AuthorizationId.create(authorizationId),
) )
return authorization ? this.toAuthorizationDTO(authorization) : null if (!authorization) return null
// 查询用户团队统计数据
const teamStats = await this.statsRepository.findByUserId(authorization.userId.value)
const currentTreeCount = teamStats?.totalTeamPlantingCount || 0
return this.toAuthorizationDTO(authorization, currentTreeCount)
} }
/** /**
@ -426,7 +436,17 @@ export class AuthorizationApplicationService {
return 0 return 0
} }
private toAuthorizationDTO(auth: AuthorizationRole): AuthorizationDTO { private toAuthorizationDTO(auth: AuthorizationRole, currentTreeCount: number): AuthorizationDTO {
// 获取月度考核目标社区固定10其他类型根据阶梯规则
let monthlyTargetTreeCount = 0
if (auth.roleType === RoleType.COMMUNITY) {
monthlyTargetTreeCount = 10 // 社区固定每月新增10棵
} else if (auth.benefitActive && auth.currentMonthIndex > 0) {
// 省/市公司使用阶梯目标
const target = LadderTargetRule.getTarget(auth.roleType, auth.currentMonthIndex)
monthlyTargetTreeCount = target.monthlyTarget
}
return { return {
authorizationId: auth.authorizationId.value, authorizationId: auth.authorizationId.value,
userId: auth.userId.value, userId: auth.userId.value,
@ -439,6 +459,10 @@ export class AuthorizationApplicationService {
currentMonthIndex: auth.currentMonthIndex, currentMonthIndex: auth.currentMonthIndex,
requireLocalPercentage: auth.requireLocalPercentage, requireLocalPercentage: auth.requireLocalPercentage,
exemptFromPercentageCheck: auth.exemptFromPercentageCheck, exemptFromPercentageCheck: auth.exemptFromPercentageCheck,
// 考核进度字段
initialTargetTreeCount: auth.getInitialTarget(),
currentTreeCount,
monthlyTargetTreeCount,
createdAt: auth.createdAt, createdAt: auth.createdAt,
updatedAt: auth.updatedAt, updatedAt: auth.updatedAt,
} }

View File

@ -98,6 +98,10 @@ class AuthorizationResponse {
final int currentMonthIndex; final int currentMonthIndex;
final double requireLocalPercentage; final double requireLocalPercentage;
final bool exemptFromPercentageCheck; final bool exemptFromPercentageCheck;
//
final int initialTargetTreeCount; // 10100500
final int currentTreeCount; //
final int monthlyTargetTreeCount; //
final DateTime createdAt; final DateTime createdAt;
final DateTime updatedAt; final DateTime updatedAt;
@ -113,6 +117,9 @@ class AuthorizationResponse {
required this.currentMonthIndex, required this.currentMonthIndex,
required this.requireLocalPercentage, required this.requireLocalPercentage,
required this.exemptFromPercentageCheck, required this.exemptFromPercentageCheck,
required this.initialTargetTreeCount,
required this.currentTreeCount,
required this.monthlyTargetTreeCount,
required this.createdAt, required this.createdAt,
required this.updatedAt, required this.updatedAt,
}); });
@ -130,6 +137,9 @@ class AuthorizationResponse {
currentMonthIndex: json['currentMonthIndex'] ?? 0, currentMonthIndex: json['currentMonthIndex'] ?? 0,
requireLocalPercentage: (json['requireLocalPercentage'] ?? 0).toDouble(), requireLocalPercentage: (json['requireLocalPercentage'] ?? 0).toDouble(),
exemptFromPercentageCheck: json['exemptFromPercentageCheck'] ?? false, exemptFromPercentageCheck: json['exemptFromPercentageCheck'] ?? false,
initialTargetTreeCount: json['initialTargetTreeCount'] ?? 0,
currentTreeCount: json['currentTreeCount'] ?? 0,
monthlyTargetTreeCount: json['monthlyTargetTreeCount'] ?? 0,
createdAt: json['createdAt'] != null createdAt: json['createdAt'] != null
? DateTime.parse(json['createdAt']) ? DateTime.parse(json['createdAt'])
: DateTime.now(), : DateTime.now(),

View File

@ -50,10 +50,13 @@ class _ProfilePageState extends ConsumerState<ProfilePage> {
// referral-service // referral-service
List<Map<String, dynamic>> _referrals = []; List<Map<String, dynamic>> _referrals = [];
// // authorization-service
final int _communityLevel = 3; bool _hasCommunityAuth = false; //
final int _currentPlanting = 12; bool _communityBenefitActive = false; //
final int _requiredPlanting = 50; int _communityCurrentTreeCount = 0; //
int _communityInitialTarget = 10; // 10
int _communityMonthlyTarget = 10; //
int _communityMonthIndex = 0; //
// wallet-service // wallet-service
double _pendingUsdt = 0.0; double _pendingUsdt = 0.0;
@ -283,9 +286,15 @@ class _ProfilePageState extends ConsumerState<ProfilePage> {
/// (from authorization-service) /// (from authorization-service)
Future<void> _loadAuthorizationData() async { Future<void> _loadAuthorizationData() async {
try { try {
debugPrint('[ProfilePage] 开始加载授权数据...');
final authorizationService = ref.read(authorizationServiceProvider); final authorizationService = ref.read(authorizationServiceProvider);
final summary = await authorizationService.getMyAuthorizationSummary(); final summary = await authorizationService.getMyAuthorizationSummary();
debugPrint('[ProfilePage] 授权数据加载成功:');
debugPrint('[ProfilePage] 社区授权: ${summary.community != null}');
debugPrint('[ProfilePage] 市公司授权: ${summary.cityCompany != null}');
debugPrint('[ProfilePage] 省公司授权: ${summary.provinceCompany != null}');
if (mounted) { if (mounted) {
setState(() { setState(() {
_community = summary.communityName ?? '--'; _community = summary.communityName ?? '--';
@ -293,10 +302,29 @@ class _ProfilePageState extends ConsumerState<ProfilePage> {
_provinceCompany = summary.provinceCompanyName ?? '--'; _provinceCompany = summary.provinceCompanyName ?? '--';
// API获取 // API获取
// API或从推荐链中计算 // API或从推荐链中计算
//
if (summary.community != null) {
_hasCommunityAuth = true;
_communityBenefitActive = summary.community!.benefitActive;
_communityCurrentTreeCount = summary.community!.currentTreeCount;
_communityInitialTarget = summary.community!.initialTargetTreeCount;
_communityMonthlyTarget = summary.community!.monthlyTargetTreeCount;
_communityMonthIndex = summary.community!.currentMonthIndex;
debugPrint('[ProfilePage] 社区考核数据:');
debugPrint('[ProfilePage] 权益激活: $_communityBenefitActive');
debugPrint('[ProfilePage] 当前数量: $_communityCurrentTreeCount');
debugPrint('[ProfilePage] 初始目标: $_communityInitialTarget');
debugPrint('[ProfilePage] 月度目标: $_communityMonthlyTarget');
debugPrint('[ProfilePage] 考核月份: $_communityMonthIndex');
} else {
_hasCommunityAuth = false;
}
}); });
} }
} catch (e) { } catch (e, stackTrace) {
debugPrint('[ProfilePage] 加载授权数据失败: $e'); debugPrint('[ProfilePage] 加载授权数据失败: $e');
debugPrint('[ProfilePage] 堆栈: $stackTrace');
// //
} }
} }
@ -1591,6 +1619,94 @@ class _ProfilePageState extends ConsumerState<ProfilePage> {
/// ///
Widget _buildCommunityAssessment() { Widget _buildCommunityAssessment() {
//
if (!_hasCommunityAuth) {
return Container(
padding: const EdgeInsets.all(16),
decoration: BoxDecoration(
color: const Color(0x15F44336), //
borderRadius: BorderRadius.circular(12),
border: Border.all(
color: const Color(0x33F44336), //
width: 1,
),
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
const Text(
'社区权益考核',
style: TextStyle(
fontSize: 18,
fontFamily: 'Inter',
fontWeight: FontWeight.w700,
height: 1.56,
color: Color(0xFF5D4037),
),
),
Container(
padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 2),
decoration: BoxDecoration(
color: const Color(0x20F44336),
borderRadius: BorderRadius.circular(4),
),
child: const Text(
'未授权',
style: TextStyle(
fontSize: 12,
fontFamily: 'Inter',
fontWeight: FontWeight.w600,
color: Color(0xFFF44336), //
),
),
),
],
),
const SizedBox(height: 15),
const Text(
'暂无社区授权',
style: TextStyle(
fontSize: 14,
fontFamily: 'Inter',
fontWeight: FontWeight.w500,
height: 1.43,
color: Color(0xFFF44336), //
),
),
const SizedBox(height: 8),
const Text(
'请联系管理员申请社区授权',
style: TextStyle(
fontSize: 12,
fontFamily: 'Inter',
height: 1.5,
color: Color(0x99F44336), //
),
),
],
),
);
}
//
final progressRatio = _communityInitialTarget > 0
? (_communityCurrentTreeCount / _communityInitialTarget).clamp(0.0, 1.0)
: 0.0;
//
final String statusText;
final Color statusColor;
if (_communityBenefitActive) {
statusText = '已激活';
statusColor = const Color(0xFF4CAF50); // 绿
} else {
statusText = '待激活';
statusColor = const Color(0xFFFF9800); //
}
return Container( return Container(
padding: const EdgeInsets.all(16), padding: const EdgeInsets.all(16),
decoration: BoxDecoration( decoration: BoxDecoration(
@ -1604,51 +1720,75 @@ class _ProfilePageState extends ConsumerState<ProfilePage> {
child: Column( child: Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
const Text( //
'社区权益考核',
style: TextStyle(
fontSize: 18,
fontFamily: 'Inter',
fontWeight: FontWeight.w700,
height: 1.56,
color: Color(0xFF5D4037),
),
),
const SizedBox(height: 15),
//
Row( Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween, mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [ children: [
const Text( const Text(
'社区等级', '社区权益考核',
style: TextStyle( style: TextStyle(
fontSize: 14, fontSize: 18,
fontFamily: 'Inter',
fontWeight: FontWeight.w500,
height: 1.43,
color: Color(0xCC5D4037),
),
),
Text(
'Level $_communityLevel',
style: const TextStyle(
fontSize: 14,
fontFamily: 'Inter', fontFamily: 'Inter',
fontWeight: FontWeight.w700, fontWeight: FontWeight.w700,
height: 1.43, height: 1.56,
color: Color(0xFF5D4037), color: Color(0xFF5D4037),
), ),
), ),
Container(
padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 2),
decoration: BoxDecoration(
color: statusColor.withOpacity(0.15),
borderRadius: BorderRadius.circular(4),
),
child: Text(
statusText,
style: TextStyle(
fontSize: 12,
fontFamily: 'Inter',
fontWeight: FontWeight.w600,
color: statusColor,
),
),
),
], ],
), ),
const SizedBox(height: 16), const SizedBox(height: 15),
// //
if (_communityBenefitActive && _communityMonthIndex > 0) ...[
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
const Text(
'当前考核月份',
style: TextStyle(
fontSize: 14,
fontFamily: 'Inter',
fontWeight: FontWeight.w500,
height: 1.43,
color: Color(0xCC5D4037),
),
),
Text(
'$_communityMonthIndex',
style: const TextStyle(
fontSize: 14,
fontFamily: 'Inter',
fontWeight: FontWeight.w700,
height: 1.43,
color: Color(0xFF5D4037),
),
),
],
),
const SizedBox(height: 16),
],
// /
Row( Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween, mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [ children: [
const Text( Text(
'种植数量 / 等级要求', _communityBenefitActive ? '团队认种 / 月度目标' : '团队认种 / 激活目标',
style: TextStyle( style: const TextStyle(
fontSize: 14, fontSize: 14,
fontFamily: 'Inter', fontFamily: 'Inter',
fontWeight: FontWeight.w500, fontWeight: FontWeight.w500,
@ -1657,7 +1797,7 @@ class _ProfilePageState extends ConsumerState<ProfilePage> {
), ),
), ),
Text( Text(
'$_currentPlanting / $_requiredPlanting', '$_communityCurrentTreeCount / ${_communityBenefitActive ? _communityMonthlyTarget : _communityInitialTarget}',
style: const TextStyle( style: const TextStyle(
fontSize: 14, fontSize: 14,
fontFamily: 'Inter', fontFamily: 'Inter',
@ -1678,7 +1818,7 @@ class _ProfilePageState extends ConsumerState<ProfilePage> {
), ),
child: FractionallySizedBox( child: FractionallySizedBox(
alignment: Alignment.centerLeft, alignment: Alignment.centerLeft,
widthFactor: _currentPlanting / _requiredPlanting, widthFactor: progressRatio,
child: Container( child: Container(
decoration: BoxDecoration( decoration: BoxDecoration(
color: const Color(0xFFD4AF37), color: const Color(0xFFD4AF37),
@ -1690,8 +1830,8 @@ class _ProfilePageState extends ConsumerState<ProfilePage> {
const SizedBox(height: 16), const SizedBox(height: 16),
// //
Row( Row(
children: const [ children: [
Text( const Text(
'社区贡献奖励', '社区贡献奖励',
style: TextStyle( style: TextStyle(
fontSize: 14, fontSize: 14,
@ -1701,16 +1841,20 @@ class _ProfilePageState extends ConsumerState<ProfilePage> {
color: Color(0xCC5D4037), color: Color(0xCC5D4037),
), ),
), ),
SizedBox(width: 39), const SizedBox(width: 39),
Expanded( Expanded(
child: Text( child: Text(
'有资格获得 3% 的社区福利', _communityBenefitActive
? '有资格获得 3% 的社区福利'
: '需团队认种达到 $_communityInitialTarget 棵激活',
style: TextStyle( style: TextStyle(
fontSize: 14, fontSize: 14,
fontFamily: 'Inter', fontFamily: 'Inter',
fontWeight: FontWeight.w500, fontWeight: FontWeight.w500,
height: 1.43, height: 1.43,
color: Color(0xFFD4AF37), color: _communityBenefitActive
? const Color(0xFFD4AF37)
: const Color(0x995D4037),
), ),
), ),
), ),