class Invite { final String id; final String code; final String salesId; final String groupId; final String? link; final String? qrCodeUrl; final DateTime? createdAt; final DateTime? expiresAt; final int clickCount; final int scanCount; final int joinCount; final String status; Invite({ required this.id, required this.code, required this.salesId, required this.groupId, this.link, this.qrCodeUrl, this.createdAt, this.expiresAt, this.clickCount = 0, this.scanCount = 0, this.joinCount = 0, this.status = 'active', }); factory Invite.fromJson(Map json) { return Invite( id: (json['_id'] ?? json['id'] ?? '').toString(), code: json['code'] ?? '', salesId: (json['salesId'] ?? '').toString(), groupId: (json['groupId'] ?? '').toString(), link: json['link'], qrCodeUrl: json['qrCodeUrl'], createdAt: json['createdAt'] != null ? DateTime.parse(json['createdAt'].toString()) : null, expiresAt: json['expiresAt'] != null ? DateTime.parse(json['expiresAt'].toString()) : null, clickCount: json['clickCount'] ?? 0, scanCount: json['scanCount'] ?? 0, joinCount: json['joinCount'] ?? 0, status: json['status'] ?? 'active', ); } Map toJson() { return { '_id': id, 'code': code, 'salesId': salesId, 'groupId': groupId, 'link': link, 'qrCodeUrl': qrCodeUrl, 'createdAt': createdAt?.toIso8601String(), 'expiresAt': expiresAt?.toIso8601String(), 'clickCount': clickCount, 'scanCount': scanCount, 'joinCount': joinCount, 'status': status, }; } bool get isExpired => expiresAt != null && DateTime.now().isAfter(expiresAt!); bool get isActive => status == 'active' && !isExpired; bool get isValid => isActive; } /// 单个邀请的统计数据,由 invite.getStats 返回 class InviteStats { final String code; final String? link; final DateTime? createdAt; final DateTime? expiresAt; final InviteStatsDetail stats; final List recentAccess; InviteStats({ required this.code, this.link, this.createdAt, this.expiresAt, required this.stats, this.recentAccess = const [], }); factory InviteStats.fromJson(Map json) { return InviteStats( code: json['code'] ?? '', link: json['link'], createdAt: json['createdAt'] != null ? DateTime.parse(json['createdAt'].toString()) : null, expiresAt: json['expiresAt'] != null ? DateTime.parse(json['expiresAt'].toString()) : null, stats: InviteStatsDetail.fromJson(json['stats'] ?? {}), recentAccess: (json['recentAccess'] as List?) ?.map((e) => AccessLog.fromJson(e)) .toList() ?? [], ); } } class InviteStatsDetail { final int clicks; final int scans; final int joins; final String conversionRate; InviteStatsDetail({ this.clicks = 0, this.scans = 0, this.joins = 0, this.conversionRate = '0', }); factory InviteStatsDetail.fromJson(Map json) { return InviteStatsDetail( clicks: json['clicks'] ?? 0, scans: json['scans'] ?? 0, joins: json['joins'] ?? 0, conversionRate: json['conversionRate']?.toString() ?? '0', ); } double get conversionRateDouble => double.tryParse(conversionRate) ?? 0.0; } /// 来自后端的访问日志条目 class AccessLog { final String? id; final String inviteCode; final String? visitorId; final String accessType; final DateTime? timestamp; final String? ipAddress; AccessLog({ this.id, required this.inviteCode, this.visitorId, required this.accessType, this.timestamp, this.ipAddress, }); factory AccessLog.fromJson(Map json) { return AccessLog( id: (json['_id'] ?? json['id'])?.toString(), inviteCode: json['inviteCode'] ?? '', visitorId: json['visitorId']?.toString(), accessType: json['accessType'] ?? '', timestamp: json['timestamp'] != null ? DateTime.parse(json['timestamp'].toString()) : null, ipAddress: json['ipAddress'], ); } }