167 lines
4.2 KiB
Dart
167 lines
4.2 KiB
Dart
|
|
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<String, dynamic> 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<String, dynamic> 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<AccessLog> recentAccess;
|
||
|
|
|
||
|
|
InviteStats({
|
||
|
|
required this.code,
|
||
|
|
this.link,
|
||
|
|
this.createdAt,
|
||
|
|
this.expiresAt,
|
||
|
|
required this.stats,
|
||
|
|
this.recentAccess = const [],
|
||
|
|
});
|
||
|
|
|
||
|
|
factory InviteStats.fromJson(Map<String, dynamic> 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<String, dynamic> 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<String, dynamic> 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'],
|
||
|
|
);
|
||
|
|
}
|
||
|
|
}
|