Files
web_huangguo2_h5/app/static/js/pages/playComments.js
T
谢宇宁andClaude Opus 5.5 0fbdba5ccf Initial commit
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-29 11:47:47 +07:00

600 lines
20 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
(function () {
var root = document.querySelector("[data-play-composer]");
if (!root) return;
var commentsRoot = document.querySelector("[data-play-comments]") || root.closest(".play-comments");
var input = root.querySelector("[data-comment-input]");
var countEl = root.querySelector("[data-comment-count]");
var sendBtn = root.querySelector("[data-comment-send]");
var listEl = document.querySelector("[data-comment-list]");
var totalEl = document.querySelector("[data-comment-total-label]");
var moreBtn = document.querySelector("[data-comment-more]");
var sending = false;
var inlineSending = false;
var loadingMore = false;
var MAX_LEN = 200;
var PAGE_SIZE = 4;
var replyTarget = null;
function toast(message, type) {
if (typeof window.showToast === "function") {
window.showToast(message, type);
return;
}
if (window.AuthAPI && typeof window.AuthAPI.toast === "function") {
window.AuthAPI.toast(message, type || "info");
return;
}
}
function toInt(v, fallback) {
var n = parseInt(v, 10);
return isNaN(n) ? fallback || 0 : n;
}
function escapeHtml(s) {
return String(s == null ? "" : s)
.replace(/&/g, "&amp;")
.replace(/</g, "&lt;")
.replace(/>/g, "&gt;")
.replace(/"/g, "&quot;");
}
function getPageSize() {
return Math.max(
1,
toInt(commentsRoot && commentsRoot.getAttribute("data-comment-size"), PAGE_SIZE)
);
}
function getPage() {
return Math.max(
1,
toInt(commentsRoot && commentsRoot.getAttribute("data-comment-page"), 1)
);
}
function getLoaded() {
if (!listEl) return 0;
return listEl.querySelectorAll(".play-comment:not(.play-comment--empty)").length;
}
function setPagerState(page, loaded, total) {
if (!commentsRoot) return;
if (page != null) commentsRoot.setAttribute("data-comment-page", String(page));
if (loaded != null) commentsRoot.setAttribute("data-comment-loaded", String(loaded));
if (total != null) commentsRoot.setAttribute("data-comment-total", String(total));
syncMoreButton(total != null ? total : toInt(commentsRoot.getAttribute("data-comment-total"), 0));
}
function syncMoreButton(total) {
if (!moreBtn) return;
var loaded = getLoaded();
var hasMore = total > loaded;
if (hasMore) {
moreBtn.hidden = false;
moreBtn.removeAttribute("hidden");
} else {
moreBtn.hidden = true;
moreBtn.setAttribute("hidden", "");
}
}
function syncCount() {
if (!input || !countEl) return;
var len = Array.from(input.value || "").length;
countEl.textContent = len + " / " + MAX_LEN;
}
function formatTime(raw) {
var s = String(raw || "").trim();
if (!s) return "刚刚";
var d = new Date(s);
if (isNaN(d.getTime())) return s;
return (
d.getFullYear() +
"/" +
(d.getMonth() + 1) +
"/" +
d.getDate() +
" " +
String(d.getHours()).padStart(2, "0") +
":" +
String(d.getMinutes()).padStart(2, "0")
);
}
function renderCommentItem(item) {
item = item || {};
var avatar = String(item.userAvatar || "").trim();
var name = escapeHtml(item.userName || "匿名用户");
var text = escapeHtml(item.text || "");
var time = escapeHtml(formatTime(item.createdAt));
var like = toInt(item.countLike, 0);
var liked = Number(item.isLike) === 1;
var avatarHtml = avatar
? '<img class="play-comment__avatar lazy" src="/static/images/placeholder.png?v=2" data-src="' +
escapeHtml(avatar) +
'" alt="" loading="lazy" decoding="async">'
: '<img class="play-comment__avatar" src="/static/images/placeholder.png?v=2" data-src="/static/images/profile/avatar.png" alt="" loading="lazy" decoding="async">';
var children = Array.isArray(item.children) ? item.children : [];
var replies = children
.map(function (child) {
var cName = escapeHtml(child.userName || "用户");
var replyTo = child.replyUserName
? " 回复 " + escapeHtml(child.replyUserName)
: "";
return (
'<div class="play-comment__reply">' +
cName +
replyTo +
":" +
escapeHtml(child.text || "") +
"</div>"
);
})
.join("");
var commentId = escapeHtml(item.id || "");
var rawName = String(item.userName || "匿名用户");
return (
'<li class="play-comment" data-comment-id="' +
commentId +
'">' +
avatarHtml +
'<div class="play-comment__body">' +
'<div class="play-comment__meta">' +
'<span class="play-comment__name">' +
name +
"</span>" +
'<span class="play-comment__time">' +
time +
"</span>" +
"</div>" +
'<p class="play-comment__text">' +
text +
"</p>" +
replies +
'<div class="play-comment__actions">' +
'<button class="play-comment__action' +
(liked ? " is-active" : "") +
'" type="button" aria-label="点赞" data-comment-like="' +
commentId +
'" data-liked="' +
(liked ? "1" : "2") +
'" data-like-raw="' +
escapeHtml(like) +
'">' +
'<img class="icon" src="/static/images/placeholder.png?v=2" data-src="/static/images/heart.svg" alt="" loading="lazy" decoding="async">' +
'<span data-like-count>' +
escapeHtml(like) +
"</span>" +
"</button>" +
'<button class="play-comment__action" type="button" data-comment-reply data-comment-id="' +
commentId +
'" data-parents-id="' +
commentId +
'" data-reply-name="' +
escapeHtml(rawName) +
'">回复</button>' +
"</div></div></li>"
);
}
function getLoadedIds() {
var ids = Object.create(null);
if (!listEl) return ids;
listEl.querySelectorAll(".play-comment[data-comment-id]").forEach(function (el) {
var id = String(el.getAttribute("data-comment-id") || "").trim();
if (id) ids[id] = true;
});
return ids;
}
function updateTotal(n) {
if (!totalEl) return;
totalEl.textContent = n > 0 ? n + " 条" : "暂无评论";
}
function renderList(list, totalCount) {
if (!listEl) return;
list = Array.isArray(list) ? list : [];
clearReplyTarget();
if (!list.length) {
listEl.innerHTML =
'<li class="play-comment play-comment--empty"><p class="play-comment__text">暂无评论</p></li>';
} else {
listEl.innerHTML = list.map(renderCommentItem).join("");
if (typeof window.initLazyImages === "function") {
window.initLazyImages(listEl);
}
}
var total = typeof totalCount === "number" ? totalCount : toInt(commentsRoot && commentsRoot.getAttribute("data-comment-total"), 0);
if (typeof totalCount === "number") {
updateTotal(totalCount);
}
setPagerState(1, list.length, total);
}
// 下一页追加到现有列表底部,不替换已展示评论。
function appendList(list, totalCount) {
if (!listEl) return;
list = Array.isArray(list) ? list : [];
var total =
typeof totalCount === "number"
? totalCount
: toInt(commentsRoot && commentsRoot.getAttribute("data-comment-total"), 0);
if (typeof totalCount === "number") {
updateTotal(totalCount);
}
if (!list.length) {
setPagerState(getPage(), getLoaded(), total);
return;
}
var empty = listEl.querySelector(".play-comment--empty");
if (empty) empty.remove();
var existing = getLoadedIds();
var fresh = list.filter(function (item) {
var id = String((item && item.id) || "").trim();
return id && !existing[id];
});
if (!fresh.length) {
setPagerState(getPage(), getLoaded(), total);
return;
}
var wrap = document.createElement("ul");
wrap.innerHTML = fresh.map(renderCommentItem).join("");
var nodes = [];
while (wrap.firstChild) {
nodes.push(wrap.firstChild);
listEl.appendChild(wrap.firstChild);
}
if (typeof window.initLazyImages === "function") {
nodes.forEach(function (node) {
if (node && node.nodeType === 1) window.initLazyImages(node);
});
window.initLazyImages(listEl);
}
setPagerState(getPage(), getLoaded(), total);
}
function fetchCommentPage(pageNum) {
if (!window.HttpRequest || typeof window.HttpRequest.get !== "function") {
return Promise.reject(new Error("评论服务未就绪"));
}
var objectId = toInt(root.getAttribute("data-object-id"), 0);
var objectType = toInt(root.getAttribute("data-object-type"), 10);
if (objectId <= 0) return Promise.reject(new Error("缺少评论对象"));
var pageSize = getPageSize();
return window.HttpRequest.get("/api/comment/list", {
params: {
objectId: objectId,
objectType: objectType,
pageNum: pageNum,
pageSize: pageSize,
sortOrder: 1,
},
}).then(function (res) {
if (!res || Number(res.code) !== 200 || !res.data) {
throw new Error((res && (res.tip || res.msg)) || "加载失败");
}
return res.data;
});
}
function refreshList() {
return fetchCommentPage(1)
.then(function (data) {
renderList(data.list || [], toInt(data.totalCount, 0));
})
.catch(function () {});
}
function loadMoreComments() {
if (loadingMore || !moreBtn) return;
var nextPage = getPage() + 1;
loadingMore = true;
moreBtn.disabled = true;
fetchCommentPage(nextPage)
.then(function (data) {
var list = data.list || [];
var total = toInt(data.totalCount, 0);
if (commentsRoot) {
commentsRoot.setAttribute("data-comment-page", String(nextPage));
}
// 只追加,绝不 renderList 整表替换
appendList(list, total);
})
.catch(function (err) {
toast((err && err.message) || "加载失败,请稍后重试", "error");
})
.finally(function () {
loadingMore = false;
if (moreBtn) moreBtn.disabled = false;
});
}
function removeInlineComposer() {
if (!listEl) return;
listEl.querySelectorAll("[data-comment-inline-composer]").forEach(function (el) {
el.remove();
});
}
function clearReplyTarget() {
replyTarget = null;
removeInlineComposer();
}
function applyLikeUI(btn, isLike, countLike) {
if (!btn) return;
var liked = Number(isLike) === 1;
btn.classList.toggle("is-active", liked);
btn.setAttribute("data-liked", liked ? "1" : "2");
if (countLike != null) {
btn.setAttribute("data-like-raw", String(countLike));
var countEl = btn.querySelector("[data-like-count]");
if (countEl) countEl.textContent = String(countLike);
}
}
function toggleCommentLike(btn) {
if (!btn || btn.getAttribute("data-liking") === "1") return;
if (root.getAttribute("data-logged-in") !== "1") {
var loginBtn = document.querySelector("[data-login-open]");
if (loginBtn) loginBtn.click();
return;
}
if (!window.HttpRequest || typeof window.HttpRequest.post !== "function") {
toast("点赞服务未就绪", "error");
return;
}
var commentId = toInt(btn.getAttribute("data-comment-like"), 0);
if (commentId <= 0) return;
var likedNow = Number(btn.getAttribute("data-liked") || 2) === 1;
var nextLike = !likedNow;
var prevCount = toInt(btn.getAttribute("data-like-raw"), 0);
var nextCount = Math.max(0, prevCount + (nextLike ? 1 : -1));
btn.setAttribute("data-liking", "1");
btn.disabled = true;
applyLikeUI(btn, nextLike ? 1 : 2, nextCount);
var payload = { commentId: commentId, isLike: nextLike };
window.HttpRequest.post("/api/comment/like/set", payload)
.then(function (res) {
if (!res || Number(res.code) !== 200) {
throw new Error((res && (res.tip || res.msg)) || "操作失败");
}
var data = res.data || {};
applyLikeUI(
btn,
data.isLike != null ? data.isLike : nextLike ? 1 : 2,
data.countLike != null ? data.countLike : nextCount
);
})
.catch(function (err) {
applyLikeUI(btn, likedNow ? 1 : 2, prevCount);
toast((err && err.message) || "操作失败,请稍后重试", "error");
})
.finally(function () {
btn.removeAttribute("data-liking");
btn.disabled = false;
});
}
function openInlineComposer(commentEl, target) {
if (!commentEl || !target) return;
removeInlineComposer();
replyTarget = target;
var body = commentEl.querySelector(".play-comment__body") || commentEl;
var actions = body.querySelector(".play-comment__actions");
var box = document.createElement("div");
box.className = "play-comment__inline";
box.setAttribute("data-comment-inline-composer", "");
var name = target.replyName || "用户";
box.innerHTML =
'<div class="play-comment__inline-hint">' +
"<span>回复 @" +
escapeHtml(name) +
"</span>" +
'<button type="button" data-comment-reply-cancel>取消</button>' +
"</div>" +
'<div class="play-comment__inline-row">' +
'<input class="play-comment__inline-input" type="text" maxlength="200" placeholder="回复 @' +
escapeHtml(name) +
'…" data-comment-inline-input autocomplete="off" />' +
'<button class="play-comment__inline-send" type="button" data-comment-inline-send>发送</button>' +
"</div>";
if (actions && actions.parentNode === body) {
if (actions.nextSibling) body.insertBefore(box, actions.nextSibling);
else body.appendChild(box);
} else {
body.appendChild(box);
}
var inlineInput = box.querySelector("[data-comment-inline-input]");
if (inlineInput) inlineInput.focus();
}
function setSending(on) {
sending = !!on;
if (sendBtn) sendBtn.disabled = !!on;
if (input) input.disabled = !!on;
}
function submitTopComment() {
if (sending) return;
if (root.getAttribute("data-logged-in") !== "1") {
var loginBtn = document.querySelector("[data-login-open]");
if (loginBtn) loginBtn.click();
return;
}
if (!window.HttpRequest || typeof window.HttpRequest.post !== "function") {
toast("评论服务未就绪", "error");
return;
}
var text = String((input && input.value) || "").trim();
if (!text) {
toast("请输入评论内容", "error");
return;
}
if (Array.from(text).length > MAX_LEN) {
toast("评论最多 200 字", "error");
return;
}
var objectId = toInt(root.getAttribute("data-object-id"), 0);
var objectType = toInt(root.getAttribute("data-object-type"), 10);
if (objectId <= 0) {
toast("缺少评论对象", "error");
return;
}
var payload = {
objectId: objectId,
objectType: objectType,
text: text,
parentsId: 0,
replyCommentId: 0,
};
setSending(true);
window.HttpRequest.post("/api/comment/send", payload)
.then(function (res) {
if (!res || Number(res.code) !== 200) {
throw new Error((res && (res.tip || res.msg)) || "发表失败");
}
if (input) input.value = "";
syncCount();
clearReplyTarget();
toast("发布成功,评论审核后才会展示", "success");
return refreshList();
})
.catch(function (err) {
toast((err && err.message) || "发表失败,请稍后重试", "error");
})
.finally(function () {
setSending(false);
});
}
function sendInlineReply(box) {
if (!box || inlineSending || !replyTarget) return;
if (!window.HttpRequest || typeof window.HttpRequest.post !== "function") {
toast("评论服务未就绪", "error");
return;
}
var inlineInput = box.querySelector("[data-comment-inline-input]");
var btn = box.querySelector("[data-comment-inline-send]");
if (!inlineInput) return;
var text = String(inlineInput.value || "").trim();
if (!text) {
toast("请输入评论内容", "error");
return;
}
if (Array.from(text).length > MAX_LEN) {
toast("评论最多 200 字", "error");
return;
}
var objectId = toInt(root.getAttribute("data-object-id"), 0);
var objectType = toInt(root.getAttribute("data-object-type"), 10);
if (objectId <= 0) {
toast("缺少评论对象", "error");
return;
}
inlineSending = true;
if (btn) btn.disabled = true;
inlineInput.disabled = true;
var payload = {
objectId: objectId,
objectType: objectType,
text: text,
parentsId: toInt(replyTarget.parentsId, 0),
replyCommentId: toInt(
replyTarget.replyCommentId || replyTarget.parentsId,
0
),
};
window.HttpRequest.post("/api/comment/send", payload)
.then(function (res) {
if (!res || Number(res.code) !== 200) {
throw new Error((res && (res.tip || res.msg)) || "发表失败");
}
clearReplyTarget();
toast("发布成功,评论审核后才会展示", "success");
return refreshList();
})
.catch(function (err) {
toast((err && err.message) || "发表失败,请稍后重试", "error");
})
.finally(function () {
inlineSending = false;
if (btn) btn.disabled = false;
if (inlineInput) inlineInput.disabled = false;
});
}
if (input) {
input.addEventListener("input", syncCount);
input.addEventListener("keydown", function (event) {
if (event.key === "Enter" && !event.shiftKey) {
event.preventDefault();
submitTopComment();
}
});
syncCount();
}
if (sendBtn) {
sendBtn.addEventListener("click", submitTopComment);
}
if (moreBtn) {
moreBtn.addEventListener("click", function (event) {
event.preventDefault();
loadMoreComments();
});
}
if (listEl) {
listEl.addEventListener("click", function (event) {
var likeBtn = window.domClosest(event.target, "[data-comment-like]");
if (likeBtn && listEl.contains(likeBtn)) {
event.preventDefault();
toggleCommentLike(likeBtn);
return;
}
var cancelBtn = window.domClosest(event.target, "[data-comment-reply-cancel]");
if (cancelBtn && listEl.contains(cancelBtn)) {
clearReplyTarget();
return;
}
var sendInlineBtn = window.domClosest(event.target, "[data-comment-inline-send]");
if (sendInlineBtn && listEl.contains(sendInlineBtn)) {
var box = sendInlineBtn.closest("[data-comment-inline-composer]");
sendInlineReply(box);
return;
}
var replyBtn = window.domClosest(event.target, "[data-comment-reply]");
if (!replyBtn || !listEl.contains(replyBtn)) return;
if (root.getAttribute("data-logged-in") !== "1") {
var loginBtn = document.querySelector("[data-login-open]");
if (loginBtn) loginBtn.click();
return;
}
var parentsId = toInt(replyBtn.getAttribute("data-parents-id"), 0);
var commentId = toInt(replyBtn.getAttribute("data-comment-id"), parentsId);
if (parentsId <= 0) return;
var commentEl = replyBtn.closest(".play-comment");
openInlineComposer(commentEl, {
parentsId: parentsId,
replyCommentId: commentId,
replyName: replyBtn.getAttribute("data-reply-name") || "用户",
});
});
listEl.addEventListener("keydown", function (event) {
if (event.key !== "Enter" || event.shiftKey) return;
var inlineInput = window.domClosest(event.target, "[data-comment-inline-input]");
if (!inlineInput || !listEl.contains(inlineInput)) return;
event.preventDefault();
var box = inlineInput.closest("[data-comment-inline-composer]");
sendInlineReply(box);
});
}
})();