This commit is contained in:
macbook_max
2026-10-15 20:36:59 +08:00
commit 6bf949949c
7923 changed files with 1369582 additions and 0 deletions
+109
View File
@@ -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 {},
),
);
}
+112
View File
@@ -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,
);
}
+42
View File
@@ -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,
);
}
+64
View File
@@ -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,
);
}
+117
View File
@@ -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?) ?? '',
);
}
+30
View File
@@ -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,
);
}