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:
parent
e5e2793337
commit
22c32031af
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
}
|
||||
|
|
|
|||
|
|
@ -98,6 +98,10 @@ class AuthorizationResponse {
|
|||
final int currentMonthIndex;
|
||||
final double requireLocalPercentage;
|
||||
final bool exemptFromPercentageCheck;
|
||||
// 考核进度字段
|
||||
final int initialTargetTreeCount; // 初始考核目标(社区10,市100,省500)
|
||||
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(),
|
||||
|
|
|
|||
|
|
@ -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),
|
||||
),
|
||||
),
|
||||
),
|
||||
|
|
|
|||
Loading…
Reference in New Issue