add
This commit is contained in:
@@ -0,0 +1,109 @@
|
||||
/// 评论模型(《03-接口文档》6.3/6.4/6.5,对应服务端 interaction_service 输出)
|
||||
class CommentItem {
|
||||
CommentItem({
|
||||
required this.commentId,
|
||||
required this.userId,
|
||||
this.nickname = '',
|
||||
this.avatarUrl = '',
|
||||
this.content = '',
|
||||
this.likeCount = 0,
|
||||
this.replyCount = 0,
|
||||
this.createdAt = '',
|
||||
});
|
||||
|
||||
final int commentId;
|
||||
final int userId;
|
||||
final String nickname;
|
||||
final String avatarUrl;
|
||||
final String content;
|
||||
final int likeCount;
|
||||
|
||||
/// 一级评论的回复数(二级回复列表项恒为 0)
|
||||
final int replyCount;
|
||||
final String createdAt;
|
||||
|
||||
CommentItem copyWith({int? replyCount}) => CommentItem(
|
||||
commentId: commentId,
|
||||
userId: userId,
|
||||
nickname: nickname,
|
||||
avatarUrl: avatarUrl,
|
||||
content: content,
|
||||
likeCount: likeCount,
|
||||
replyCount: replyCount ?? this.replyCount,
|
||||
createdAt: createdAt,
|
||||
);
|
||||
|
||||
factory CommentItem.fromJson(Map<String, dynamic> json) {
|
||||
final user = (json['user'] as Map<String, dynamic>?) ?? const {};
|
||||
return CommentItem(
|
||||
commentId: (json['comment_id'] as num?)?.toInt() ?? 0,
|
||||
userId: (user['user_id'] as num?)?.toInt() ?? 0,
|
||||
nickname: (user['nickname'] as String?) ?? '',
|
||||
avatarUrl: (user['avatar_url'] as String?) ?? '',
|
||||
content: (json['content'] as String?) ?? '',
|
||||
likeCount: (json['like_count'] as num?)?.toInt() ?? 0,
|
||||
replyCount: (json['reply_count'] as num?)?.toInt() ?? 0,
|
||||
createdAt: (json['created_at'] as String?) ?? '',
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// 评论分页(普通 page/page_size 分页,《03-接口文档》1.4)
|
||||
class CommentListPage {
|
||||
CommentListPage({required this.total, required this.items});
|
||||
|
||||
final int total;
|
||||
final List<CommentItem> items;
|
||||
|
||||
factory CommentListPage.fromJson(Map<String, dynamic> json) =>
|
||||
CommentListPage(
|
||||
total: (json['total'] as num?)?.toInt() ?? 0,
|
||||
items: ((json['list'] as List?) ?? const [])
|
||||
.whereType<Map<String, dynamic>>()
|
||||
.map(CommentItem.fromJson)
|
||||
.toList(),
|
||||
);
|
||||
}
|
||||
|
||||
/// 评论积分奖励进度(发表/删除评论响应中的 reward_progress)
|
||||
class RewardProgress {
|
||||
RewardProgress({
|
||||
required this.current,
|
||||
required this.target,
|
||||
this.rewardedThisTime = false,
|
||||
this.rewardPoints = 0,
|
||||
});
|
||||
|
||||
/// 当前进度(满 target 自动发奖并清零)
|
||||
final int current;
|
||||
final int target;
|
||||
|
||||
/// 本次是否触发发奖
|
||||
final bool rewardedThisTime;
|
||||
|
||||
/// 本次发放积分数(未发奖为 0)
|
||||
final int rewardPoints;
|
||||
|
||||
factory RewardProgress.fromJson(Map<String, dynamic> json) => RewardProgress(
|
||||
current: (json['current'] as num?)?.toInt() ?? 0,
|
||||
target: (json['target'] as num?)?.toInt() ?? 10,
|
||||
rewardedThisTime: (json['rewarded_this_time'] as bool?) ?? false,
|
||||
rewardPoints: (json['reward_points'] as num?)?.toInt() ?? 0,
|
||||
);
|
||||
}
|
||||
|
||||
/// 发表评论响应(《03-接口文档》6.4)
|
||||
class CommentCreateResult {
|
||||
CommentCreateResult({required this.commentId, required this.rewardProgress});
|
||||
|
||||
final int commentId;
|
||||
final RewardProgress rewardProgress;
|
||||
|
||||
factory CommentCreateResult.fromJson(Map<String, dynamic> json) =>
|
||||
CommentCreateResult(
|
||||
commentId: (json['comment_id'] as num?)?.toInt() ?? 0,
|
||||
rewardProgress: RewardProgress.fromJson(
|
||||
(json['reward_progress'] as Map<String, dynamic>?) ?? const {},
|
||||
),
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,112 @@
|
||||
/// 会员套餐(《03-接口文档》7.1,价格来自服务端配置)
|
||||
class MembershipPlan {
|
||||
const MembershipPlan({
|
||||
required this.planCode,
|
||||
required this.name,
|
||||
required this.days,
|
||||
required this.points,
|
||||
this.enabled = true,
|
||||
this.desc = '',
|
||||
});
|
||||
|
||||
final String planCode;
|
||||
final String name;
|
||||
|
||||
/// 有效天数;0 = 终身
|
||||
final int days;
|
||||
final int points;
|
||||
final bool enabled;
|
||||
final String desc;
|
||||
|
||||
factory MembershipPlan.fromJson(Map<String, dynamic> json) =>
|
||||
MembershipPlan(
|
||||
planCode: json['plan_code'] as String,
|
||||
name: json['name'] as String,
|
||||
days: (json['days'] as num).toInt(),
|
||||
points: (json['points'] as num).toInt(),
|
||||
enabled: json['enabled'] as bool? ?? true,
|
||||
desc: (json['desc'] as String?) ?? '',
|
||||
);
|
||||
}
|
||||
|
||||
/// 套餐列表响应
|
||||
class PlansResult {
|
||||
const PlansResult({required this.plans, this.benefits = const []});
|
||||
|
||||
final List<MembershipPlan> plans;
|
||||
final List<String> benefits;
|
||||
|
||||
factory PlansResult.fromJson(Map<String, dynamic> json) => PlansResult(
|
||||
plans: ((json['plans'] as List?) ?? const [])
|
||||
.whereType<Map<String, dynamic>>()
|
||||
.map(MembershipPlan.fromJson)
|
||||
.toList(),
|
||||
benefits:
|
||||
((json['benefits'] as List?) ?? const []).cast<String>(),
|
||||
);
|
||||
}
|
||||
|
||||
/// 我的会员状态(《03-接口文档》7.2)
|
||||
class MyMemberState {
|
||||
const MyMemberState({
|
||||
this.isMember = false,
|
||||
this.isLifetime = false,
|
||||
this.currentPlan,
|
||||
this.planName,
|
||||
this.expireAt,
|
||||
this.remainingDays = 0,
|
||||
this.history = const [],
|
||||
});
|
||||
|
||||
final bool isMember;
|
||||
final bool isLifetime;
|
||||
final String? currentPlan;
|
||||
final String? planName;
|
||||
final String? expireAt;
|
||||
final int remainingDays;
|
||||
final List<MemberHistoryItem> history;
|
||||
|
||||
factory MyMemberState.fromJson(Map<String, dynamic> json) => MyMemberState(
|
||||
isMember: json['is_member'] as bool? ?? false,
|
||||
isLifetime: json['is_lifetime'] as bool? ?? false,
|
||||
currentPlan: json['current_plan'] as String?,
|
||||
planName: json['plan_name'] as String?,
|
||||
expireAt: json['expire_at'] as String?,
|
||||
remainingDays: (json['remaining_days'] as num?)?.toInt() ?? 0,
|
||||
history: ((json['history'] as List?) ?? const [])
|
||||
.whereType<Map<String, dynamic>>()
|
||||
.map(MemberHistoryItem.fromJson)
|
||||
.toList(),
|
||||
);
|
||||
}
|
||||
|
||||
/// 会员开通历史
|
||||
class MemberHistoryItem {
|
||||
const MemberHistoryItem({
|
||||
required this.orderNo,
|
||||
required this.planName,
|
||||
required this.pointsCost,
|
||||
this.startAt = '',
|
||||
this.endAt = '',
|
||||
this.source = 0,
|
||||
});
|
||||
|
||||
final String orderNo;
|
||||
final String planName;
|
||||
final int pointsCost;
|
||||
final String startAt;
|
||||
final String endAt;
|
||||
final int source; // 1 积分开通 2 活动赠送
|
||||
|
||||
String get sourceLabel => source == 2 ? '活动赠送' : '积分开通';
|
||||
|
||||
factory MemberHistoryItem.fromJson(Map<String, dynamic> json) =>
|
||||
MemberHistoryItem(
|
||||
orderNo: json['order_no'] as String,
|
||||
planName: json['plan_name'] as String,
|
||||
pointsCost: (json['points_cost'] as num).toInt(),
|
||||
startAt: (json['start_at'] as String?) ?? '',
|
||||
endAt: (json['end_at'] as String?) ?? '',
|
||||
source: (json['source'] as num?)?.toInt() ?? 1,
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
/// 用户模型(登录/注册响应的 user 字段与 GET /users/me 的超集,字段可空按需取)
|
||||
class UserModel {
|
||||
UserModel({
|
||||
required this.userId,
|
||||
this.phone,
|
||||
this.email,
|
||||
this.nickname = '',
|
||||
this.avatarUrl = '',
|
||||
this.bio = '',
|
||||
this.gender = 0,
|
||||
this.isMember = false,
|
||||
this.memberPlan,
|
||||
this.memberExpireAt,
|
||||
this.points = 0,
|
||||
});
|
||||
|
||||
final int userId;
|
||||
final String? phone;
|
||||
final String? email;
|
||||
final String nickname;
|
||||
final String avatarUrl;
|
||||
final String bio;
|
||||
final int gender; // 0未知 1男 2女
|
||||
final bool isMember;
|
||||
final String? memberPlan; // monthly/yearly/lifetime/trial...
|
||||
final String? memberExpireAt; // yyyy-MM-dd HH:mm:ss
|
||||
final int points;
|
||||
|
||||
factory UserModel.fromJson(Map<String, dynamic> json) => UserModel(
|
||||
userId: (json['user_id'] ?? json['id']) as int,
|
||||
phone: json['phone'] as String?,
|
||||
email: json['email'] as String?,
|
||||
nickname: (json['nickname'] as String?) ?? '',
|
||||
avatarUrl: (json['avatar_url'] as String?) ?? '',
|
||||
bio: (json['bio'] as String?) ?? '',
|
||||
gender: (json['gender'] as num?)?.toInt() ?? 0,
|
||||
isMember: (json['is_member'] as bool?) ?? false,
|
||||
memberPlan: json['member_plan'] as String?,
|
||||
memberExpireAt: json['member_expire_at'] as String?,
|
||||
points: (json['points'] as num?)?.toInt() ?? 0,
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,64 @@
|
||||
import 'video_item.dart';
|
||||
|
||||
/// 用户公开主页(《03-接口文档》4.7)
|
||||
class UserPublicProfile {
|
||||
UserPublicProfile({
|
||||
required this.userId,
|
||||
this.nickname = '',
|
||||
this.avatarUrl = '',
|
||||
this.bio = '',
|
||||
this.videoCount = 0,
|
||||
this.totalLikes = 0,
|
||||
});
|
||||
|
||||
final int userId;
|
||||
final String nickname;
|
||||
final String avatarUrl;
|
||||
final String bio;
|
||||
|
||||
/// 已发布作品数
|
||||
final int videoCount;
|
||||
|
||||
/// 累计获赞
|
||||
final int totalLikes;
|
||||
|
||||
factory UserPublicProfile.fromJson(Map<String, dynamic> json) =>
|
||||
UserPublicProfile(
|
||||
userId: (json['user_id'] as num?)?.toInt() ?? 0,
|
||||
nickname: (json['nickname'] as String?) ?? '',
|
||||
avatarUrl: (json['avatar_url'] as String?) ?? '',
|
||||
bio: (json['bio'] as String?) ?? '',
|
||||
videoCount: (json['video_count'] as num?)?.toInt() ?? 0,
|
||||
totalLikes: (json['total_likes'] as num?)?.toInt() ?? 0,
|
||||
);
|
||||
}
|
||||
|
||||
/// 观看历史项(《03-接口文档》6.7:video + watched_at + cost_type)
|
||||
class HistoryItem {
|
||||
HistoryItem({
|
||||
required this.video,
|
||||
this.watchedAt = '',
|
||||
this.costType = 0,
|
||||
});
|
||||
|
||||
final VideoItem video;
|
||||
final String watchedAt;
|
||||
|
||||
/// 计费类型:0 免费额度 / 1 积分 / 2 会员 / 3 重复或本人
|
||||
final int costType;
|
||||
|
||||
String get costLabel => switch (costType) {
|
||||
1 => '积分',
|
||||
2 => '会员',
|
||||
3 => '免费',
|
||||
_ => '免费',
|
||||
};
|
||||
|
||||
factory HistoryItem.fromJson(Map<String, dynamic> json) => HistoryItem(
|
||||
video: VideoItem.fromJson(
|
||||
(json['video'] as Map<String, dynamic>?) ?? const {},
|
||||
),
|
||||
watchedAt: (json['watched_at'] as String?) ?? '',
|
||||
costType: (json['cost_type'] as num?)?.toInt() ?? 0,
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,117 @@
|
||||
/// Feed 视频项(GET /videos/feed 列表项,对应服务端 video_service 输出)
|
||||
class VideoItem {
|
||||
VideoItem({
|
||||
required this.videoId,
|
||||
this.title = '',
|
||||
this.coverUrl = '',
|
||||
this.videoUrl = '',
|
||||
this.duration = 0,
|
||||
required this.author,
|
||||
this.categoryId = 0,
|
||||
this.viewCount = 0,
|
||||
this.likeCount = 0,
|
||||
this.commentCount = 0,
|
||||
this.favoriteCount = 0,
|
||||
this.tags = const [],
|
||||
this.isLiked = false,
|
||||
this.isFavorited = false,
|
||||
this.description = '',
|
||||
this.status = -1,
|
||||
this.statusText = '',
|
||||
this.rejectReason = '',
|
||||
});
|
||||
|
||||
final int videoId;
|
||||
final String title;
|
||||
final String coverUrl;
|
||||
final String videoUrl; // m3u8
|
||||
final int duration; // 秒
|
||||
final VideoAuthor author;
|
||||
final int categoryId;
|
||||
final int viewCount;
|
||||
final int likeCount;
|
||||
final int commentCount;
|
||||
final int favoriteCount;
|
||||
final List<String> tags;
|
||||
final bool isLiked;
|
||||
final bool isFavorited;
|
||||
final String description;
|
||||
|
||||
/// 作品状态(仅「我的作品」接口返回):-1 未提供;0转码中 1待审核 2已发布 3拒绝 4下架 5转码失败
|
||||
final int status;
|
||||
final String statusText;
|
||||
final String rejectReason;
|
||||
|
||||
/// 拷贝更新(点赞/收藏乐观更新用)
|
||||
VideoItem copyWith({
|
||||
bool? isLiked,
|
||||
bool? isFavorited,
|
||||
int? likeCount,
|
||||
int? favoriteCount,
|
||||
int? commentCount,
|
||||
}) =>
|
||||
VideoItem(
|
||||
videoId: videoId,
|
||||
title: title,
|
||||
coverUrl: coverUrl,
|
||||
videoUrl: videoUrl,
|
||||
duration: duration,
|
||||
author: author,
|
||||
categoryId: categoryId,
|
||||
viewCount: viewCount,
|
||||
likeCount: likeCount ?? this.likeCount,
|
||||
commentCount: commentCount ?? this.commentCount,
|
||||
favoriteCount: favoriteCount ?? this.favoriteCount,
|
||||
tags: tags,
|
||||
isLiked: isLiked ?? this.isLiked,
|
||||
isFavorited: isFavorited ?? this.isFavorited,
|
||||
description: description,
|
||||
status: status,
|
||||
statusText: statusText,
|
||||
rejectReason: rejectReason,
|
||||
);
|
||||
|
||||
factory VideoItem.fromJson(Map<String, dynamic> json) => VideoItem(
|
||||
videoId: json['video_id'] as int,
|
||||
title: (json['title'] as String?) ?? '',
|
||||
coverUrl: (json['cover_url'] as String?) ?? '',
|
||||
videoUrl: (json['video_url'] as String?) ?? '',
|
||||
duration: (json['duration'] as num?)?.toInt() ?? 0,
|
||||
categoryId: (json['category_id'] as num?)?.toInt() ?? 0,
|
||||
author: VideoAuthor.fromJson(
|
||||
(json['author'] as Map<String, dynamic>?) ?? const {},
|
||||
),
|
||||
viewCount: (json['view_count'] as num?)?.toInt() ?? 0,
|
||||
likeCount: (json['like_count'] as num?)?.toInt() ?? 0,
|
||||
commentCount: (json['comment_count'] as num?)?.toInt() ?? 0,
|
||||
favoriteCount: (json['favorite_count'] as num?)?.toInt() ?? 0,
|
||||
tags: ((json['tags'] as List?) ?? const [])
|
||||
.map((e) => e.toString())
|
||||
.toList(),
|
||||
isLiked: (json['is_liked'] as bool?) ?? false,
|
||||
isFavorited: (json['is_favorited'] as bool?) ?? false,
|
||||
description: (json['description'] as String?) ?? '',
|
||||
status: (json['status'] as num?)?.toInt() ?? -1,
|
||||
statusText: (json['status_text'] as String?) ?? '',
|
||||
rejectReason: (json['reject_reason'] as String?) ?? '',
|
||||
);
|
||||
}
|
||||
|
||||
/// 作者简要信息
|
||||
class VideoAuthor {
|
||||
VideoAuthor({
|
||||
required this.userId,
|
||||
this.nickname = '',
|
||||
this.avatarUrl = '',
|
||||
});
|
||||
|
||||
final int userId;
|
||||
final String nickname;
|
||||
final String avatarUrl;
|
||||
|
||||
factory VideoAuthor.fromJson(Map<String, dynamic> json) => VideoAuthor(
|
||||
userId: (json['user_id'] as num?)?.toInt() ?? 0,
|
||||
nickname: (json['nickname'] as String?) ?? '',
|
||||
avatarUrl: (json['avatar_url'] as String?) ?? '',
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
/// 观看上报(计费)响应模型(《03-接口文档》5.3)
|
||||
class WatchResult {
|
||||
WatchResult({
|
||||
required this.costType,
|
||||
required this.costPoints,
|
||||
required this.remainingPoints,
|
||||
required this.freeRemainingToday,
|
||||
});
|
||||
|
||||
/// 计费类型:0 占用免费额度 / 1 积分扣减 / 2 会员免费 / 3 重复观看或本人作品
|
||||
final int costType;
|
||||
|
||||
/// 本次扣除积分(cost_type=1 时为 1,其余为 0)
|
||||
final int costPoints;
|
||||
|
||||
/// 当前剩余积分
|
||||
final int remainingPoints;
|
||||
|
||||
/// 今日免费观看剩余次数
|
||||
final int freeRemainingToday;
|
||||
|
||||
bool get isPointsCost => costType == 1;
|
||||
|
||||
factory WatchResult.fromJson(Map<String, dynamic> json) => WatchResult(
|
||||
costType: (json['cost_type'] as num?)?.toInt() ?? 0,
|
||||
costPoints: (json['cost_points'] as num?)?.toInt() ?? 0,
|
||||
remainingPoints: (json['remaining_points'] as num?)?.toInt() ?? 0,
|
||||
freeRemainingToday: (json['free_remaining_today'] as num?)?.toInt() ?? 0,
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user