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: '是否豁免占比考核' })
exemptFromPercentageCheck: boolean
@ApiProperty({ description: '初始考核目标社区10市100省500' })
initialTargetTreeCount: number
@ApiProperty({ description: '当前团队认种数量' })
currentTreeCount: number
@ApiProperty({ description: '月度考核目标' })
monthlyTargetTreeCount: number
@ApiProperty({ description: '创建时间' })
createdAt: Date

View File

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

View File

@ -322,7 +322,11 @@ export class AuthorizationApplicationService {
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),
)
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
}
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 {
authorizationId: auth.authorizationId.value,
userId: auth.userId.value,
@ -439,6 +459,10 @@ export class AuthorizationApplicationService {
currentMonthIndex: auth.currentMonthIndex,
requireLocalPercentage: auth.requireLocalPercentage,
exemptFromPercentageCheck: auth.exemptFromPercentageCheck,
// 考核进度字段
initialTargetTreeCount: auth.getInitialTarget(),
currentTreeCount,
monthlyTargetTreeCount,
createdAt: auth.createdAt,
updatedAt: auth.updatedAt,
}

View File

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

View File

@ -50,10 +50,13 @@ class _ProfilePageState extends ConsumerState<ProfilePage> {
// referral-service
List<Map<String, dynamic>> _referrals = [];
//
final int _communityLevel = 3;
final int _currentPlanting = 12;
final int _requiredPlanting = 50;
// authorization-service
bool _hasCommunityAuth = false; //
bool _communityBenefitActive = false; //
int _communityCurrentTreeCount = 0; //
int _communityInitialTarget = 10; // 10
int _communityMonthlyTarget = 10; //
int _communityMonthIndex = 0; //
// wallet-service
double _pendingUsdt = 0.0;
@ -283,9 +286,15 @@ class _ProfilePageState extends ConsumerState<ProfilePage> {
/// (from authorization-service)
Future<void> _loadAuthorizationData() async {
try {
debugPrint('[ProfilePage] 开始加载授权数据...');
final authorizationService = ref.read(authorizationServiceProvider);
final summary = await authorizationService.getMyAuthorizationSummary();
debugPrint('[ProfilePage] 授权数据加载成功:');
debugPrint('[ProfilePage] 社区授权: ${summary.community != null}');
debugPrint('[ProfilePage] 市公司授权: ${summary.cityCompany != null}');
debugPrint('[ProfilePage] 省公司授权: ${summary.provinceCompany != null}');
if (mounted) {
setState(() {
_community = summary.communityName ?? '--';
@ -293,10 +302,29 @@ class _ProfilePageState extends ConsumerState<ProfilePage> {
_provinceCompany = summary.provinceCompanyName ?? '--';
// 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] 堆栈: $stackTrace');
//
}
}
@ -1591,6 +1619,94 @@ class _ProfilePageState extends ConsumerState<ProfilePage> {
///
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(
padding: const EdgeInsets.all(16),
decoration: BoxDecoration(
@ -1604,51 +1720,75 @@ class _ProfilePageState extends ConsumerState<ProfilePage> {
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const Text(
'社区权益考核',
style: TextStyle(
fontSize: 18,
fontFamily: 'Inter',
fontWeight: FontWeight.w700,
height: 1.56,
color: Color(0xFF5D4037),
),
),
const SizedBox(height: 15),
//
//
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
const Text(
'社区等级',
'社区权益考核',
style: TextStyle(
fontSize: 14,
fontFamily: 'Inter',
fontWeight: FontWeight.w500,
height: 1.43,
color: Color(0xCC5D4037),
),
),
Text(
'Level $_communityLevel',
style: const TextStyle(
fontSize: 14,
fontSize: 18,
fontFamily: 'Inter',
fontWeight: FontWeight.w700,
height: 1.43,
height: 1.56,
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(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
const Text(
'种植数量 / 等级要求',
style: TextStyle(
Text(
_communityBenefitActive ? '团队认种 / 月度目标' : '团队认种 / 激活目标',
style: const TextStyle(
fontSize: 14,
fontFamily: 'Inter',
fontWeight: FontWeight.w500,
@ -1657,7 +1797,7 @@ class _ProfilePageState extends ConsumerState<ProfilePage> {
),
),
Text(
'$_currentPlanting / $_requiredPlanting',
'$_communityCurrentTreeCount / ${_communityBenefitActive ? _communityMonthlyTarget : _communityInitialTarget}',
style: const TextStyle(
fontSize: 14,
fontFamily: 'Inter',
@ -1678,7 +1818,7 @@ class _ProfilePageState extends ConsumerState<ProfilePage> {
),
child: FractionallySizedBox(
alignment: Alignment.centerLeft,
widthFactor: _currentPlanting / _requiredPlanting,
widthFactor: progressRatio,
child: Container(
decoration: BoxDecoration(
color: const Color(0xFFD4AF37),
@ -1690,8 +1830,8 @@ class _ProfilePageState extends ConsumerState<ProfilePage> {
const SizedBox(height: 16),
//
Row(
children: const [
Text(
children: [
const Text(
'社区贡献奖励',
style: TextStyle(
fontSize: 14,
@ -1701,16 +1841,20 @@ class _ProfilePageState extends ConsumerState<ProfilePage> {
color: Color(0xCC5D4037),
),
),
SizedBox(width: 39),
const SizedBox(width: 39),
Expanded(
child: Text(
'有资格获得 3% 的社区福利',
_communityBenefitActive
? '有资格获得 3% 的社区福利'
: '需团队认种达到 $_communityInitialTarget 棵激活',
style: TextStyle(
fontSize: 14,
fontFamily: 'Inter',
fontWeight: FontWeight.w500,
height: 1.43,
color: Color(0xFFD4AF37),
color: _communityBenefitActive
? const Color(0xFFD4AF37)
: const Color(0x995D4037),
),
),
),