1
This commit is contained in:
@@ -14,8 +14,11 @@
|
||||
+ (NSDictionary *)mj_replacedKeyFromPropertyName {
|
||||
return @{
|
||||
@"commentId" : @"id",
|
||||
@"userName" : @[ @"userName", @"nickname", @"name" ],
|
||||
@"avatarUrl" : @[ @"avatarUrl", @"avatar" ],
|
||||
@"userId" : @"userId",
|
||||
@"userName" : @"userName",
|
||||
@"avatarUrl" : @"userAvatar",
|
||||
@"createTime" : @"createdAt",
|
||||
@"totalReplyCount" : @"replyCount",
|
||||
};
|
||||
}
|
||||
|
||||
@@ -23,6 +26,24 @@
|
||||
return @{@"replies" : [KBAIReplyModel class]};
|
||||
}
|
||||
|
||||
- (void)setLiked:(NSInteger)liked {
|
||||
// 后端返回的是 NSInteger (0/1),转换为 BOOL
|
||||
_isLiked = (liked == 1);
|
||||
}
|
||||
|
||||
- (void)setCreatedAt:(NSString *)createdAt {
|
||||
// 后端返回的是字符串时间,转换为时间戳
|
||||
if (createdAt && createdAt.length > 0) {
|
||||
NSDateFormatter *formatter = [[NSDateFormatter alloc] init];
|
||||
formatter.dateFormat = @"yyyy-MM-dd HH:mm:ss";
|
||||
formatter.timeZone = [NSTimeZone timeZoneWithName:@"Asia/Shanghai"];
|
||||
NSDate *date = [formatter dateFromString:createdAt];
|
||||
if (date) {
|
||||
_createTime = [date timeIntervalSince1970];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
- (instancetype)init {
|
||||
self = [super init];
|
||||
if (self) {
|
||||
|
||||
@@ -7,17 +7,39 @@
|
||||
|
||||
#import "KBAIReplyModel.h"
|
||||
#import <MJExtension/MJExtension.h>
|
||||
#import "KBAIReplyModel.h"
|
||||
|
||||
|
||||
@implementation KBAIReplyModel
|
||||
|
||||
+ (NSDictionary *)mj_replacedKeyFromPropertyName {
|
||||
return @{
|
||||
@"replyId" : @"id",
|
||||
@"userName" : @[ @"userName", @"nickname", @"name" ],
|
||||
@"avatarUrl" : @[ @"avatarUrl", @"avatar" ],
|
||||
@"userId" : @"userId",
|
||||
@"userName" : @"userName",
|
||||
@"avatarUrl" : @"userAvatar",
|
||||
@"createTime" : @"createdAt",
|
||||
};
|
||||
}
|
||||
|
||||
- (void)setLiked:(NSInteger)liked {
|
||||
// 后端返回的是 NSInteger (0/1),转换为 BOOL
|
||||
_isLiked = (liked == 1);
|
||||
}
|
||||
|
||||
- (void)setCreatedAt:(NSString *)createdAt {
|
||||
// 后端返回的是字符串时间,转换为时间戳
|
||||
if (createdAt && createdAt.length > 0) {
|
||||
NSDateFormatter *formatter = [[NSDateFormatter alloc] init];
|
||||
formatter.dateFormat = @"yyyy-MM-dd HH:mm:ss";
|
||||
formatter.timeZone = [NSTimeZone timeZoneWithName:@"Asia/Shanghai"];
|
||||
NSDate *date = [formatter dateFromString:createdAt];
|
||||
if (date) {
|
||||
_createTime = [date timeIntervalSince1970];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
- (NSString *)formattedTime {
|
||||
NSDate *date = [NSDate dateWithTimeIntervalSince1970:self.createTime];
|
||||
NSTimeInterval interval = [[NSDate date] timeIntervalSinceDate:date];
|
||||
|
||||
@@ -6,17 +6,23 @@
|
||||
//
|
||||
|
||||
#import <UIKit/UIKit.h>
|
||||
#import <LSTPopView/LSTPopView.h>
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
/// 抖音风格评论视图
|
||||
@interface KBAICommentView : UIView
|
||||
|
||||
/// 加载评论数据(从本地 JSON 文件)
|
||||
/// AI 陪聊角色 ID
|
||||
@property(nonatomic, assign) NSInteger companionId;
|
||||
|
||||
/// 加载评论数据(从网络)
|
||||
- (void)loadComments;
|
||||
|
||||
/// 评论总数
|
||||
@property(nonatomic, readonly) NSInteger totalCommentCount;
|
||||
@property(nonatomic, weak) LSTPopView *popView
|
||||
;
|
||||
|
||||
@end
|
||||
|
||||
|
||||
@@ -12,6 +12,8 @@
|
||||
#import "KBAICommentModel.h"
|
||||
#import "KBAIReplyCell.h"
|
||||
#import "KBAIReplyModel.h"
|
||||
#import "KBCommentModel.h"
|
||||
#import "AiVM.h"
|
||||
#import <MJExtension/MJExtension.h>
|
||||
#import <Masonry/Masonry.h>
|
||||
|
||||
@@ -40,6 +42,9 @@ static NSString *const kCommentFooterIdentifier = @"CommentFooter";
|
||||
/// 当前回复的目标(二级评论)
|
||||
@property(nonatomic, weak) KBAIReplyModel *replyToReply;
|
||||
|
||||
/// AiVM 实例
|
||||
@property(nonatomic, strong) AiVM *aiVM;
|
||||
|
||||
@end
|
||||
|
||||
@implementation KBAICommentView
|
||||
@@ -52,7 +57,6 @@ static NSString *const kCommentFooterIdentifier = @"CommentFooter";
|
||||
self.comments = [NSMutableArray array];
|
||||
[self setupUI];
|
||||
[self setupKeyboardObservers];
|
||||
[self loadComments];
|
||||
}
|
||||
return self;
|
||||
}
|
||||
@@ -162,52 +166,66 @@ static NSString *const kCommentFooterIdentifier = @"CommentFooter";
|
||||
#pragma mark - Data Loading
|
||||
|
||||
- (void)loadComments {
|
||||
NSString *filePath = [[NSBundle mainBundle] pathForResource:@"comments_mock"
|
||||
ofType:@"json"];
|
||||
if (!filePath) {
|
||||
NSLog(@"[KBAICommentView] comments_mock.json not found");
|
||||
if (self.companionId <= 0) {
|
||||
NSLog(@"[KBAICommentView] companionId 未设置,无法加载评论");
|
||||
return;
|
||||
}
|
||||
|
||||
__weak typeof(self) weakSelf = self;
|
||||
[self.aiVM fetchCommentsWithCompanionId:self.companionId
|
||||
pageNum:1
|
||||
pageSize:20
|
||||
completion:^(KBCommentPageModel *pageModel, NSError *error) {
|
||||
__strong typeof(weakSelf) strongSelf = weakSelf;
|
||||
if (!strongSelf) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (error) {
|
||||
NSLog(@"[KBAICommentView] 加载评论失败:%@", error.localizedDescription);
|
||||
return;
|
||||
}
|
||||
|
||||
dispatch_async(dispatch_get_main_queue(), ^{
|
||||
[strongSelf updateCommentsWithPageModel:pageModel];
|
||||
});
|
||||
}];
|
||||
}
|
||||
|
||||
NSData *data = [NSData dataWithContentsOfFile:filePath];
|
||||
if (!data) {
|
||||
NSLog(@"[KBAICommentView] Failed to read comments_mock.json");
|
||||
/// 更新评论数据(从后端返回的 KBCommentPageModel 转换为 UI 层的 KBAICommentModel)
|
||||
- (void)updateCommentsWithPageModel:(KBCommentPageModel *)pageModel {
|
||||
if (!pageModel) {
|
||||
NSLog(@"[KBAICommentView] pageModel 为空");
|
||||
return;
|
||||
}
|
||||
|
||||
NSError *error;
|
||||
NSDictionary *json = [NSJSONSerialization JSONObjectWithData:data
|
||||
options:0
|
||||
error:&error];
|
||||
if (error) {
|
||||
NSLog(@"[KBAICommentView] JSON parse error: %@", error);
|
||||
return;
|
||||
}
|
||||
|
||||
self.totalCommentCount = [json[@"totalCount"] integerValue];
|
||||
NSArray *commentsArray = json[@"comments"];
|
||||
|
||||
|
||||
self.totalCommentCount = pageModel.total;
|
||||
|
||||
[self.comments removeAllObjects];
|
||||
|
||||
|
||||
// 获取 tableView 宽度用于计算高度
|
||||
CGFloat tableWidth = self.tableView.bounds.size.width;
|
||||
if (tableWidth <= 0) {
|
||||
tableWidth = [UIScreen mainScreen].bounds.size.width;
|
||||
}
|
||||
|
||||
for (NSDictionary *dict in commentsArray) {
|
||||
KBAICommentModel *comment = [KBAICommentModel mj_objectWithKeyValues:dict];
|
||||
|
||||
NSLog(@"[KBAICommentView] 加载到 %ld 条评论,共 %ld 条", (long)pageModel.records.count, (long)pageModel.total);
|
||||
|
||||
for (KBCommentItem *item in pageModel.records) {
|
||||
// 转换为 KBAICommentModel(使用 MJExtension)
|
||||
KBAICommentModel *comment = [KBAICommentModel mj_objectWithKeyValues:[item mj_keyValues]];
|
||||
|
||||
// 预先计算并缓存 Header 高度
|
||||
comment.cachedHeaderHeight =
|
||||
[comment calculateHeaderHeightWithMaxWidth:tableWidth];
|
||||
comment.cachedHeaderHeight = [comment calculateHeaderHeightWithMaxWidth:tableWidth];
|
||||
|
||||
// 预先计算并缓存所有 Reply 高度
|
||||
for (KBAIReplyModel *reply in comment.replies) {
|
||||
reply.cachedCellHeight =
|
||||
[reply calculateCellHeightWithMaxWidth:tableWidth];
|
||||
reply.cachedCellHeight = [reply calculateCellHeightWithMaxWidth:tableWidth];
|
||||
}
|
||||
|
||||
[self.comments addObject:comment];
|
||||
}
|
||||
|
||||
|
||||
[self updateTitle];
|
||||
[self.tableView reloadData];
|
||||
}
|
||||
@@ -422,10 +440,11 @@ static NSInteger const kRepliesLoadCount = 5;
|
||||
#pragma mark - Actions
|
||||
|
||||
- (void)closeButtonTapped {
|
||||
[self.popView dismiss];
|
||||
// 关闭评论视图(由外部处理)
|
||||
[[NSNotificationCenter defaultCenter]
|
||||
postNotificationName:@"KBAICommentViewCloseNotification"
|
||||
object:nil];
|
||||
// [[NSNotificationCenter defaultCenter]
|
||||
// postNotificationName:@"KBAICommentViewCloseNotification"
|
||||
// object:nil];
|
||||
}
|
||||
|
||||
#pragma mark - Lazy Loading
|
||||
@@ -555,100 +574,102 @@ static NSInteger const kRepliesLoadCount = 5;
|
||||
}
|
||||
|
||||
- (void)sendNewCommentWithText:(NSString *)text tableWidth:(CGFloat)tableWidth {
|
||||
// 创建新一级评论
|
||||
KBAICommentModel *newComment = [[KBAICommentModel alloc] init];
|
||||
newComment.commentId = [NSUUID UUID].UUIDString;
|
||||
newComment.userId = @"current_user";
|
||||
newComment.userName = @"我";
|
||||
newComment.avatarUrl = @"";
|
||||
newComment.content = text;
|
||||
newComment.likeCount = 0;
|
||||
newComment.isLiked = NO;
|
||||
newComment.createTime = [[NSDate date] timeIntervalSince1970];
|
||||
newComment.replies = @[];
|
||||
|
||||
// 计算高度缓存
|
||||
newComment.cachedHeaderHeight =
|
||||
[newComment calculateHeaderHeightWithMaxWidth:tableWidth];
|
||||
|
||||
// 插入到数组第一个
|
||||
[self.comments insertObject:newComment atIndex:0];
|
||||
self.totalCommentCount++;
|
||||
[self updateTitle];
|
||||
|
||||
// 插入新 section
|
||||
[self.tableView insertSections:[NSIndexSet indexSetWithIndex:0]
|
||||
withRowAnimation:UITableViewRowAnimationAutomatic];
|
||||
|
||||
// 滚动到顶部
|
||||
[self.tableView setContentOffset:CGPointZero animated:YES];
|
||||
// TODO: 调用网络接口发送一级评论
|
||||
NSLog(@"[KBAICommentView] 发送一级评论:%@", text);
|
||||
|
||||
// 示例代码:
|
||||
// [self.aiVM sendCommentWithCompanionId:self.companionId
|
||||
// content:text
|
||||
// completion:^(KBCommentItem *newItem, NSError *error) {
|
||||
// if (error) {
|
||||
// NSLog(@"[KBAICommentView] 发送评论失败:%@", error.localizedDescription);
|
||||
// return;
|
||||
// }
|
||||
//
|
||||
// // 转换为 KBAICommentModel
|
||||
// KBAICommentModel *comment = [KBAICommentModel mj_objectWithKeyValues:[newItem mj_keyValues]];
|
||||
// comment.cachedHeaderHeight = [comment calculateHeaderHeightWithMaxWidth:tableWidth];
|
||||
//
|
||||
// // 插入到数组第一个
|
||||
// [self.comments insertObject:comment atIndex:0];
|
||||
// self.totalCommentCount++;
|
||||
// [self updateTitle];
|
||||
//
|
||||
// // 插入新 section
|
||||
// [self.tableView insertSections:[NSIndexSet indexSetWithIndex:0]
|
||||
// withRowAnimation:UITableViewRowAnimationAutomatic];
|
||||
//
|
||||
// // 滚动到顶部
|
||||
// [self.tableView setContentOffset:CGPointZero animated:YES];
|
||||
// }];
|
||||
}
|
||||
|
||||
- (void)sendReplyWithText:(NSString *)text tableWidth:(CGFloat)tableWidth {
|
||||
KBAICommentModel *comment = self.replyToComment;
|
||||
if (!comment)
|
||||
return;
|
||||
|
||||
// TODO: 调用网络接口发送二级评论(回复)
|
||||
NSLog(@"[KBAICommentView] 回复评论 %@:%@", comment.commentId, text);
|
||||
|
||||
// 示例代码:
|
||||
// NSInteger parentId = [comment.commentId integerValue];
|
||||
// [self.aiVM replyCommentWithParentId:parentId
|
||||
// content:text
|
||||
// completion:^(KBCommentItem *newItem, NSError *error) {
|
||||
// if (error) {
|
||||
// NSLog(@"[KBAICommentView] 回复评论失败:%@", error.localizedDescription);
|
||||
// return;
|
||||
// }
|
||||
//
|
||||
// // 转换为 KBAIReplyModel
|
||||
// KBAIReplyModel *newReply = [KBAIReplyModel mj_objectWithKeyValues:[newItem mj_keyValues]];
|
||||
// newReply.cachedCellHeight = [newReply calculateCellHeightWithMaxWidth:tableWidth];
|
||||
//
|
||||
// // 添加到 replies 数组
|
||||
// NSMutableArray *newReplies = [NSMutableArray arrayWithArray:comment.replies];
|
||||
// [newReplies addObject:newReply];
|
||||
// comment.replies = newReplies;
|
||||
// comment.totalReplyCount = newReplies.count;
|
||||
//
|
||||
// // 找到该评论的 section
|
||||
// NSInteger section = [self.comments indexOfObject:comment];
|
||||
// if (section == NSNotFound) return;
|
||||
//
|
||||
// // 如果已展开,添加到 displayedReplies 并插入行
|
||||
// if (comment.isRepliesExpanded) {
|
||||
// NSInteger newRowIndex = comment.displayedReplies.count;
|
||||
// [comment.displayedReplies addObject:newReply];
|
||||
//
|
||||
// NSIndexPath *indexPath = [NSIndexPath indexPathForRow:newRowIndex inSection:section];
|
||||
// [self.tableView insertRowsAtIndexPaths:@[indexPath]
|
||||
// withRowAnimation:UITableViewRowAnimationAutomatic];
|
||||
//
|
||||
// // 刷新 Footer
|
||||
// KBAICommentFooterView *footerView = (KBAICommentFooterView *)[self.tableView footerViewForSection:section];
|
||||
// if (footerView) {
|
||||
// [footerView configureWithComment:comment];
|
||||
// }
|
||||
//
|
||||
// // 滚动到新回复
|
||||
// [self.tableView scrollToRowAtIndexPath:indexPath
|
||||
// atScrollPosition:UITableViewScrollPositionBottom
|
||||
// animated:YES];
|
||||
// } else {
|
||||
// // 未展开,刷新 Footer 显示新的回复数
|
||||
// KBAICommentFooterView *footerView = (KBAICommentFooterView *)[self.tableView footerViewForSection:section];
|
||||
// if (footerView) {
|
||||
// [footerView configureWithComment:comment];
|
||||
// }
|
||||
// }
|
||||
// }];
|
||||
}
|
||||
|
||||
// 创建新二级评论
|
||||
KBAIReplyModel *newReply = [[KBAIReplyModel alloc] init];
|
||||
newReply.replyId = [NSUUID UUID].UUIDString;
|
||||
newReply.userId = @"current_user";
|
||||
newReply.userName = @"我";
|
||||
newReply.avatarUrl = @"";
|
||||
newReply.content = text;
|
||||
newReply.likeCount = 0;
|
||||
newReply.isLiked = NO;
|
||||
newReply.createTime = [[NSDate date] timeIntervalSince1970];
|
||||
|
||||
// 如果是回复二级评论,设置被回复用户
|
||||
if (self.replyToReply) {
|
||||
newReply.replyToUserName = self.replyToReply.userName;
|
||||
}
|
||||
|
||||
// 计算高度缓存
|
||||
newReply.cachedCellHeight =
|
||||
[newReply calculateCellHeightWithMaxWidth:tableWidth];
|
||||
|
||||
// 添加到 replies 数组
|
||||
NSMutableArray *newReplies = [NSMutableArray arrayWithArray:comment.replies];
|
||||
[newReplies addObject:newReply];
|
||||
comment.replies = newReplies;
|
||||
comment.totalReplyCount = newReplies.count;
|
||||
|
||||
// 找到该评论的 section
|
||||
NSInteger section = [self.comments indexOfObject:comment];
|
||||
if (section == NSNotFound)
|
||||
return;
|
||||
|
||||
// 如果已展开,添加到 displayedReplies 并插入行
|
||||
if (comment.isRepliesExpanded) {
|
||||
NSInteger newRowIndex = comment.displayedReplies.count;
|
||||
[comment.displayedReplies addObject:newReply];
|
||||
|
||||
NSIndexPath *indexPath = [NSIndexPath indexPathForRow:newRowIndex
|
||||
inSection:section];
|
||||
[self.tableView insertRowsAtIndexPaths:@[ indexPath ]
|
||||
withRowAnimation:UITableViewRowAnimationAutomatic];
|
||||
|
||||
// 刷新 Footer
|
||||
KBAICommentFooterView *footerView =
|
||||
(KBAICommentFooterView *)[self.tableView footerViewForSection:section];
|
||||
if (footerView) {
|
||||
[footerView configureWithComment:comment];
|
||||
}
|
||||
|
||||
// 滚动到新回复
|
||||
[self.tableView scrollToRowAtIndexPath:indexPath
|
||||
atScrollPosition:UITableViewScrollPositionBottom
|
||||
animated:YES];
|
||||
} else {
|
||||
// 未展开,刷新 Footer 显示新的回复数
|
||||
KBAICommentFooterView *footerView =
|
||||
(KBAICommentFooterView *)[self.tableView footerViewForSection:section];
|
||||
if (footerView) {
|
||||
[footerView configureWithComment:comment];
|
||||
}
|
||||
- (AiVM *)aiVM {
|
||||
if (!_aiVM) {
|
||||
_aiVM = [[AiVM alloc] init];
|
||||
}
|
||||
return _aiVM;
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@@ -531,12 +531,16 @@
|
||||
initWithFrame:CGRectMake(0, 0, KB_SCREEN_WIDTH, customViewHeight)];
|
||||
|
||||
// 设置评论视图的人设 ID
|
||||
// customView.companionId = self.persona.personaId;
|
||||
customView.companionId = self.persona.personaId;
|
||||
|
||||
// 加载评论数据
|
||||
[customView loadComments];
|
||||
|
||||
LSTPopView *popView = [LSTPopView initWithCustomView:customView
|
||||
parentView:nil
|
||||
popStyle:LSTPopStyleSmoothFromBottom
|
||||
dismissStyle:LSTDismissStyleSmoothToBottom];
|
||||
customView.popView = popView;
|
||||
self.popView = popView;
|
||||
popView.priority = 1000;
|
||||
popView.isAvoidKeyboard = NO;
|
||||
|
||||
Reference in New Issue
Block a user