feat: 彻底重构后台 UI 与对话上下文引用模块,全面优化移动端适配 (v1.0.4) #6

Merged
bbb-lsy07 merged 11 commits from main into main 2026-06-21 12:20:25 +08:00
2 changed files with 62 additions and 13 deletions
Showing only changes of commit 957df1a539 - Show all commits
@@ -291,26 +291,51 @@ public class CommentAiAutopilotEndpoint implements CustomEndpoint {
var commentTime = String.valueOf(comment.getMetadata().getCreationTimestamp());
var isCommentAi = isAiOwner(comment.getSpec().getOwner());
// 首条评论没有引用对象
var commentMsg = new ConversationMessage(
"comment", commentOwner, commentContent, commentTime, isCommentAi
"comment", commentOwner, commentContent, commentTime, isCommentAi, null, null
);
return client.list(Reply.class,
reply -> commentName.equals(reply.getSpec().getCommentName()),
null)
.sort(Comparator.comparing(r -> r.getMetadata().getCreationTimestamp()))
.map(reply -> {
.collectList() // 收集为List以便统一处理引用映射
.map(replyList -> {
List<ConversationMessage> messages = new ArrayList<>();
messages.add(commentMsg);
// 构建 Reply 的映射字典,方便查找引用关系
Map<String, Reply> replyMap = new HashMap<>();
for (Reply r : replyList) {
replyMap.put(r.getMetadata().getName(), r);
}
for (Reply reply : replyList) {
var replyOwner = extractOwnerName(reply.getSpec().getOwner());
var replyContent = extractContent(reply.getSpec().getRaw(), reply.getSpec().getContent());
var replyTime = String.valueOf(reply.getMetadata().getCreationTimestamp());
var isAi = isAiOwner(reply.getSpec().getOwner());
return new ConversationMessage("reply", replyOwner, replyContent, replyTime, isAi);
})
.collectList()
.map(replyList -> {
List<ConversationMessage> messages = new ArrayList<>();
messages.add(commentMsg);
messages.addAll(replyList);
String quoteOwner = null;
String quoteContent = null;
// 获取引用的 Reply 名称 (Halo中如果为空,代表直接回复顶级 Comment)
String quoteReplyName = reply.getSpec().getQuoteReply();
if (quoteReplyName != null && !quoteReplyName.isBlank()) {
Reply quotedReply = replyMap.get(quoteReplyName);
if (quotedReply != null) {
quoteOwner = extractOwnerName(quotedReply.getSpec().getOwner());
quoteContent = extractContent(quotedReply.getSpec().getRaw(), quotedReply.getSpec().getContent());
}
} else {
// 没有 quoteReply 表示直接回复首条评论
quoteOwner = commentOwner;
quoteContent = commentContent;
}
messages.add(new ConversationMessage("reply", replyOwner, replyContent, replyTime, isAi, quoteOwner, quoteContent));
}
return messages;
});
})
@@ -712,7 +737,9 @@ public class CommentAiAutopilotEndpoint implements CustomEndpoint {
String owner,
String content,
String time,
boolean isAi
boolean isAi,
String quoteOwner,
String quoteContent
) {}
public record CommenterInfo(
+24 -2
View File
@@ -299,8 +299,19 @@
:class="msg.isAi
? 'bg-blue-50 text-gray-800 rounded-tl-md'
: 'bg-gray-100 text-gray-800 rounded-tr-md'"
v-html="renderContent(msg.content)"
></div>
>
<!-- 引用摘要模块仅当存在引用时显示 -->
<div
v-if="msg.quoteOwner && msg.quoteContent"
class="mb-2 px-3 py-1.5 bg-black/5 rounded-lg border-l-2 border-gray-300 text-xs text-gray-500"
>
<span class="font-medium text-gray-600">@{{ msg.quoteOwner }}</span>:
{{ truncateQuote(msg.quoteContent, 30) }}
</div>
<!-- 实际回复内容 -->
<div v-html="renderContent(msg.content)"></div>
</div>
<!-- Time -->
<div class="text-[10px] text-gray-300 mt-1" :class="msg.isAi ? 'text-left' : 'text-right'">
{{ formatDate(msg.time) }}
@@ -358,6 +369,8 @@ interface ConversationMessage {
content: string
time: string
isAi: boolean
quoteOwner?: string
quoteContent?: string
}
const replies = ref<AiCommentReplyItem[]>([])
@@ -639,6 +652,15 @@ const stripHtml = (html: string) => {
.trim()
}
/**
* Truncate quote content for preview
*/
const truncateQuote = (content: string, length = 30) => {
if (!content) return ""
const plain = stripHtml(content)
return plain.length > length ? plain.substring(0, length) + "..." : plain
}
/**
* Sanitize and render HTML content for conversation bubbles.
* Only allows safe inline tags, strips dangerous elements.