Files
web_huangguo_web/app/static/js/pages/detail.js
T
fish03andClaude Opus 5.5 bef9f8b4ed Initial commit
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-09-23 14:43:16 +07:00

1576 lines
63 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 section = document.querySelector(".detail-episodes");
if (!section) return;
var ranges = section.querySelectorAll(".detail-episode-range");
var episodes = section.querySelectorAll(".detail-episode-list .detail-episode");
if (!episodes.length) return;
if (!ranges.length) return;
function applyRange(start, end) {
episodes.forEach(function (el) {
var n = Number(el.getAttribute("data-ep"));
var inRange = !Number.isNaN(n) && n >= start && n <= end;
el.classList.toggle("is-range-hidden", !inRange);
});
}
function activate(btn) {
ranges.forEach(function (item) {
item.classList.toggle("detail-episode-range--active", item === btn);
});
applyRange(Number(btn.dataset.rangeStart), Number(btn.dataset.rangeEnd));
}
ranges.forEach(function (btn) {
btn.addEventListener("click", function () {
activate(btn);
});
});
var active = section.querySelector(".detail-episode-range--active") || ranges[0];
activate(active);
})();
(function () {
var shareBtns = document.querySelectorAll("[data-detail-share]");
if (!shareBtns.length) return;
function toast(message, type) {
if (typeof window.showToast === "function") {
window.showToast(message, type);
return;
}
window.alert(message);
}
function resolvePlayletId(btn) {
var id = Number(btn.getAttribute("data-playlet-id") || 0);
if (id > 0) return id;
var host =
btn.closest("[data-playlet-id]") ||
document.querySelector("[data-play-player][data-playlet-id]") ||
document.querySelector("[data-playlet-id]");
if (host) {
id = Number(host.getAttribute("data-playlet-id") || 0);
}
return id > 0 ? id : 0;
}
function reportForwardHeat(playletId) {
if (!playletId || !window.HttpRequest) return;
window.HttpRequest.post("/api/playlet/forward/heat", { id: playletId }).catch(
function () {
/* 加热失败不影响分享体验 */
}
);
}
function fallbackCopy(text) {
var area = document.createElement("textarea");
area.value = text;
area.setAttribute("readonly", "");
area.style.cssText = "position:fixed;left:-9999px;top:0;opacity:0;";
document.body.appendChild(area);
area.focus();
area.select();
area.setSelectionRange(0, area.value.length);
var ok = false;
try {
ok = document.execCommand("copy");
} catch (e) {
ok = false;
}
document.body.removeChild(area);
return ok;
}
function copyPageUrl() {
var url = window.location.href;
if (navigator.clipboard && navigator.clipboard.writeText && window.isSecureContext) {
return navigator.clipboard.writeText(url).then(
function () {
return true;
},
function () {
return fallbackCopy(url);
}
);
}
return Promise.resolve(fallbackCopy(url));
}
shareBtns.forEach(function (btn) {
btn.addEventListener("click", function () {
var playletId = resolvePlayletId(btn);
copyPageUrl()
.then(function (ok) {
if (ok) {
toast("链接已复制", "success");
reportForwardHeat(playletId);
} else {
toast("复制失败,请手动复制链接", "error");
}
})
.catch(function () {
toast("复制失败,请手动复制链接", "error");
});
});
});
})();
(function () {
// detail.js 同步执行早于 defer 的 request.js,需等 HttpRequest 就绪后再绑定
function toast(message, type) {
if (typeof window.showToast === "function") {
window.showToast(message, type);
return;
}
window.alert(message);
}
function bindLikeActions() {
var likeBtns = document.querySelectorAll("[data-detail-like]");
if (!likeBtns.length) return;
if (!window.HttpRequest || typeof window.HttpRequest.post !== "function") {
if (!bindLikeActions._retried) {
bindLikeActions._retried = true;
window.addEventListener("load", bindLikeActions, { once: true });
}
return;
}
if (bindLikeActions._bound) return;
bindLikeActions._bound = true;
function applyLikeUI(isLike, countLike) {
var liked = Number(isLike) === 1;
likeBtns.forEach(function (btn) {
btn.classList.toggle("detail-action--active", liked);
btn.classList.toggle("detail-h5-top-btn--active", liked);
btn.classList.toggle("detail-h5-like--active", liked);
btn.classList.toggle("play-h5-side-item--active", liked);
btn.setAttribute("data-liked", liked ? "1" : "2");
btn.setAttribute("aria-pressed", liked ? "true" : "false");
if (countLike != null) {
btn.setAttribute("data-count-like", String(countLike));
}
var label = btn.querySelector("[data-detail-like-label]");
if (label) {
if (btn.getAttribute("data-like-label-mode") === "state") {
label.textContent = liked ? "已点赞" : "点赞";
} else {
var n = countLike != null ? countLike : btn.getAttribute("data-count-like");
label.textContent = String(n == null || n === "" ? 0 : n);
}
}
var h5Icon = btn.querySelector("[data-h5-like-icon]");
if (h5Icon) {
h5Icon.src = liked
? "/static/images/h5_liked.svg"
: "/static/images/like.svg";
}
});
}
likeBtns.forEach(function (btn) {
btn.addEventListener("click", function () {
if (btn.getAttribute("data-liking") === "1") return;
var playletId = Number(btn.getAttribute("data-playlet-id") || 0);
if (!playletId) return;
var likedNow = Number(btn.getAttribute("data-liked") || 2) === 1;
var countNow = Number(btn.getAttribute("data-count-like") || 0);
if (Number.isNaN(countNow)) countNow = 0;
var nextLike = !likedNow;
var nextCount = Math.max(0, countNow + (nextLike ? 1 : -1));
likeBtns.forEach(function (item) {
item.setAttribute("data-liking", "1");
item.disabled = true;
});
applyLikeUI(nextLike ? 1 : 2, nextCount);
window.HttpRequest.post("/api/playlet/like/set", {
id: playletId,
isLike: nextLike,
})
.then(function (res) {
if (!res || res.code !== 200) {
if (res && res.code === 401) {
var loginOpen = document.querySelector(
"[data-login-dialog-open]"
);
if (loginOpen) loginOpen.click();
}
throw new Error((res && (res.tip || res.msg)) || "操作失败");
}
var data = res.data || {};
applyLikeUI(
data.isLike != null ? data.isLike : nextLike ? 1 : 2,
data.countLike != null ? data.countLike : nextCount
);
})
.catch(function (err) {
applyLikeUI(likedNow ? 1 : 2, countNow);
toast((err && err.message) || "操作失败,请稍后重试", "error");
})
.finally(function () {
likeBtns.forEach(function (item) {
item.removeAttribute("data-liking");
item.disabled = false;
});
});
});
});
}
if (document.readyState === "loading") {
document.addEventListener("DOMContentLoaded", bindLikeActions, { once: true });
} else {
bindLikeActions();
}
})();
(function () {
function toast(message, type) {
if (typeof window.showToast === "function") {
window.showToast(message, type);
return;
}
window.alert(message);
}
function bindCollectActions() {
var collectBtns = document.querySelectorAll("[data-detail-collect]");
if (!collectBtns.length) return;
if (!window.HttpRequest || typeof window.HttpRequest.post !== "function") {
if (!bindCollectActions._retried) {
bindCollectActions._retried = true;
window.addEventListener("load", bindCollectActions, { once: true });
}
return;
}
if (bindCollectActions._bound) return;
bindCollectActions._bound = true;
function applyCollectUI(isCollect, countCollect) {
var collected = Number(isCollect) === 1;
collectBtns.forEach(function (btn) {
btn.classList.toggle("detail-action--active", collected);
btn.classList.toggle("detail-h5-collect--active", collected);
btn.classList.toggle("detail-h5-top-btn--active", collected);
btn.classList.toggle("play-h5-side-item--active", collected);
btn.classList.toggle("is-collected", collected);
btn.setAttribute("data-collected", collected ? "1" : "2");
btn.setAttribute("aria-pressed", collected ? "true" : "false");
if (countCollect != null) {
btn.setAttribute("data-count-collect", String(countCollect));
}
var label = btn.querySelector("[data-detail-collect-label]");
if (label) {
if (btn.classList.contains("detail-h5-collect")) {
var n = countCollect != null
? countCollect
: btn.getAttribute("data-count-collect");
label.textContent = String(n == null || n === "" ? 0 : n);
} else {
label.textContent = collected ? "已收藏" : "收藏";
}
}
var h5Icon = btn.querySelector("[data-h5-collect-icon]");
if (h5Icon) {
if (btn.classList.contains("detail-h5-top-btn")) {
h5Icon.src = collected
? "/static/images/h5_collected.svg?v=2"
: "/static/images/collect.svg";
} else if (btn.classList.contains("play-h5-side-item")) {
h5Icon.src = collected
? "/static/images/h5_collected.svg?v=2"
: "/static/images/collect.svg";
} else {
h5Icon.src = collected
? "/static/images/h5_detail_collected.svg"
: "/static/images/h5_detail_collect.svg";
}
}
});
}
collectBtns.forEach(function (btn) {
btn.addEventListener("click", function () {
if (btn.getAttribute("data-collecting") === "1") return;
var playletId = Number(btn.getAttribute("data-playlet-id") || 0);
if (!playletId) return;
var collectedNow = Number(btn.getAttribute("data-collected") || 2) === 1;
var countNow = Number(btn.getAttribute("data-count-collect") || 0);
if (Number.isNaN(countNow)) countNow = 0;
var nextCollect = !collectedNow;
var nextCount = Math.max(0, countNow + (nextCollect ? 1 : -1));
collectBtns.forEach(function (item) {
item.setAttribute("data-collecting", "1");
item.disabled = true;
});
applyCollectUI(nextCollect ? 1 : 2, nextCount);
window.HttpRequest.post("/api/playlet/collect/set", {
id: playletId,
isCollect: nextCollect,
})
.then(function (res) {
if (!res || res.code !== 200) {
if (res && res.code === 401) {
var loginOpen = document.querySelector(
"[data-login-dialog-open]"
);
if (loginOpen) loginOpen.click();
}
throw new Error((res && (res.tip || res.msg)) || "操作失败");
}
var data = res.data || {};
applyCollectUI(
data.isCollect != null ? data.isCollect : nextCollect ? 1 : 2,
data.countCollect != null ? data.countCollect : nextCount
);
})
.catch(function (err) {
applyCollectUI(collectedNow ? 1 : 2, countNow);
toast((err && err.message) || "操作失败,请稍后重试", "error");
})
.finally(function () {
collectBtns.forEach(function (item) {
item.removeAttribute("data-collecting");
item.disabled = false;
});
});
});
});
}
if (document.readyState === "loading") {
document.addEventListener("DOMContentLoaded", bindCollectActions, { once: true });
} else {
bindCollectActions();
}
})();
(function () {
var grid = document.querySelector("[data-related-grid]");
var btn = document.querySelector("[data-related-refresh]");
if (!grid || !btn) return;
var section = document.querySelector("[data-related-section]");
var mobileMedia = window.matchMedia && window.matchMedia("(max-width: 767px)");
var isMobileDetail =
document.body.classList.contains("is-detail-page") &&
mobileMedia &&
mobileMedia.matches;
if (isMobileDetail) {
var PLACEHOLDER = "/static/images/placeholder.png?v=2";
var guessLikeData = [];
var guessOffset = 0;
var pageSize = 4;
grid.textContent = "";
btn.hidden = true;
if (section) section.hidden = true;
function detailHref(item) {
var slug = String((item && item.slug) || "").trim();
var id = Number(item && item.id);
var key = slug || (id > 0 ? String(id) : "");
return key ? "/detail/" + encodeURIComponent(key) + "/" : "#";
}
function firstCardTag(item) {
var tags = item && Array.isArray(item.tagNames) ? item.tagNames : [];
var themes = item && Array.isArray(item.themeNames) ? item.themeNames : [];
return String(tags[0] || themes[0] || "热门").trim();
}
function setCardCover(image, rawURL, title) {
var cover = String(rawURL || "").trim();
image.className = "poster-cover-img";
image.alt = title;
image.title = title;
image.addEventListener("error", function () {
image.classList.remove("lazy");
image.removeAttribute("data-src");
image.src = PLACEHOLDER;
}, { once: true });
if (
cover &&
typeof window.shouldDecryptImage === "function" &&
window.shouldDecryptImage(cover)
) {
image.classList.add("lazy");
image.src = PLACEHOLDER;
image.setAttribute("data-src", cover);
return;
}
image.src = cover || PLACEHOLDER;
}
function createGuessCard(item) {
var card = document.createElement("a");
var cover = document.createElement("div");
var image = document.createElement("img");
var episode = document.createElement("div");
var episodeText = document.createElement("div");
var body = document.createElement("div");
var title = document.createElement("div");
var subtitle = document.createElement("div");
var meta = document.createElement("div");
var tag = document.createElement("span");
var titleText = String((item && item.title) || "精彩短剧");
var totalEpisode = Math.max(0, Number(item && item.totalEpisode) || 0);
card.className = "poster-card";
card.href = detailHref(item);
card.setAttribute("aria-label", titleText);
cover.className = "poster-cover";
episode.className = "episode-badge";
episodeText.className = "text-white-xs";
body.className = "poster-card-body";
title.className = "poster-card-title";
subtitle.className = "poster-card-subtitle";
meta.className = "poster-card-meta";
tag.className = "poster-card-tag";
setCardCover(image, (item && (item.cover || item.banner)) || "", titleText);
episodeText.textContent = totalEpisode ? totalEpisode + "集" : "更新中";
title.textContent = titleText;
subtitle.textContent = String((item && item.desc) || "精彩内容等你来看");
tag.textContent = firstCardTag(item);
episode.appendChild(episodeText);
cover.appendChild(image);
cover.appendChild(episode);
meta.appendChild(tag);
body.appendChild(title);
body.appendChild(subtitle);
body.appendChild(meta);
card.appendChild(cover);
card.appendChild(body);
return card;
}
function renderMobileGuessLike() {
var count = Math.min(pageSize, guessLikeData.length);
grid.textContent = "";
if (section) section.hidden = count === 0;
btn.hidden = guessLikeData.length <= pageSize;
for (var index = 0; index < count; index += 1) {
grid.appendChild(
createGuessCard(guessLikeData[(guessOffset + index) % guessLikeData.length])
);
}
if (count && typeof window.initLazyImages === "function") {
window.initLazyImages(grid);
}
}
btn.addEventListener("click", function () {
if (guessLikeData.length > pageSize) {
guessOffset = (guessOffset + pageSize) % guessLikeData.length;
}
renderMobileGuessLike();
});
function loadMobileGuessLike() {
if (!window.HttpRequest || typeof window.HttpRequest.get !== "function") {
renderMobileGuessLike();
return;
}
window.HttpRequest.get("/api/playlet/hot-like")
.then(function (response) {
var source = response && response.data && response.data.guessLike;
guessLikeData = Array.isArray(source)
? source.filter(function (item) {
return item && (item.id || item.slug);
})
: [];
guessOffset = 0;
renderMobileGuessLike();
})
.catch(function () {
guessLikeData = [];
renderMobileGuessLike();
});
}
if (document.readyState === "loading") {
document.addEventListener("DOMContentLoaded", loadMobileGuessLike, { once: true });
} else {
loadMobileGuessLike();
}
return;
}
var cards = Array.prototype.slice.call(grid.querySelectorAll(".poster-card"));
if (cards.length <= 6) {
btn.hidden = true;
return;
}
var pageSize = 6;
var page = 0;
function render() {
var start = (page * pageSize) % cards.length;
cards.forEach(function (card) {
card.style.display = "none";
});
for (var i = 0; i < pageSize; i++) {
var card = cards[(start + i) % cards.length];
card.style.display = "";
grid.appendChild(card);
}
}
render();
btn.addEventListener("click", function () {
page += 1;
render();
});
})();
(function () {
// 注意:本文件在页面底部同步加载,早于 header 中 defer 的 request.js。
// 不能在此用 `if (!HttpRequest) return`,否则评论初始化会整段跳过。
// 实际请求在 DOMContentLoaded(HttpRequest 已就绪)后再发起。
var DEFAULT_AVATAR = "/static/images/avatar.png";
var PLACEHOLDER = "/static/images/placeholder.png?v=2";
function toast(message, type) {
if (typeof window.showToast === "function") {
window.showToast(message, type);
return;
}
window.alert(message);
}
function escapeHtml(s) {
return String(s == null ? "" : s)
.replace(/&/g, "&amp;")
.replace(/</g, "&lt;")
.replace(/>/g, "&gt;")
.replace(/"/g, "&quot;");
}
function formatCommentTime(raw) {
if (!raw) return "";
var d = new Date(raw);
if (Number.isNaN(d.getTime())) return String(raw);
return (
d.getFullYear() +
"/" +
(d.getMonth() + 1) +
"/" +
d.getDate() +
" " +
String(d.getHours()).padStart(2, "0") +
":" +
String(d.getMinutes()).padStart(2, "0")
);
}
function formatRelativeTime(raw) {
if (!raw) return "";
var d = new Date(raw);
if (Number.isNaN(d.getTime())) return String(raw);
var diff = Date.now() - d.getTime();
if (diff < 0) diff = 0;
var minutes = Math.floor(diff / 60000);
if (minutes < 1) return "刚刚";
if (minutes < 60) return minutes + "分钟前";
var hours = Math.floor(minutes / 60);
if (hours < 24) return hours + "小时前";
var days = Math.floor(hours / 24);
if (days < 30) return days + "天前";
return formatCommentTime(raw);
}
function formatCompactCount(n) {
n = Number(n);
if (!n || n < 0 || Number.isNaN(n)) return "0";
if (n < 10000) return String(n);
var v = n / 10000;
if (v >= 10) return Math.floor(v) + "w";
return (Math.round(v * 10) / 10) + "w";
}
function avatarURL(url) {
url = String(url || "").trim();
return url || DEFAULT_AVATAR;
}
function replyToggleLabel(expanded, count) {
count = Number(count || 0);
if (expanded) return "收起回复";
return "查看全部 " + count + " 条回复 ›";
}
function renderDetailComment(item, playPcMode) {
var liked = Number(item.isLike) === 1;
var avatar = avatarURL(item.userAvatar);
var commentId = item.id;
var parentsId = Number(item.parentsId || 0) || Number(commentId);
var avatarHtml = item.userAvatar
? '<img class="detail-comment-avatar lazy" src="' +
PLACEHOLDER +
'" data-src="' +
escapeHtml(avatar) +
'" alt="' +
escapeHtml((item.userName || "匿名用户") + "的头像") +
'" />'
: '<img class="detail-comment-avatar" src="' + DEFAULT_AVATAR + '" alt="匿名用户头像" />';
var likeIcon = liked
? "/static/images/like_solid.svg"
: "/static/images/like_hollow.svg";
var textHtml = escapeHtml(item.text || "");
if (item.replyUserName) {
textHtml =
'<span class="detail-comment-reply-to">回复 @' +
escapeHtml(item.replyUserName) +
"</span> " +
textHtml;
}
var children = Array.isArray(item.children) ? item.children : [];
var replyCount = Number(item.commentCount || children.length || 0);
var repliesHtml = "";
if (children.length) {
var collapsed = children.length > 1;
repliesHtml =
'<div class="detail-comment-replies">' +
children
.map(function (child, index) {
var childText = escapeHtml(child.text || "");
if (child.replyUserName) {
childText =
'<span class="detail-comment-reply-to">@' +
escapeHtml(child.replyUserName) +
"</span> " +
childText;
}
return (
'<div class="detail-comment-reply-item" data-comment-id="' +
escapeHtml(child.id) +
'"' +
(collapsed && index > 0
? ' data-reply-collapsed-item hidden'
: "") +
">" +
'<span class="detail-comment-reply-name">' +
escapeHtml(child.userName || "匿名用户") +
":</span>" +
'<span class="detail-comment-reply-text">' +
childText +
"</span>" +
"</div>"
);
})
.join("") +
(replyCount > 1
? '<button class="detail-comment-replies-toggle" type="button" data-comment-replies-toggle data-reply-count="' +
escapeHtml(replyCount) +
'" aria-expanded="false">' +
replyToggleLabel(false, replyCount) +
"</button>"
: "") +
"</div>";
} else if (playPcMode && replyCount > 1) {
repliesHtml =
'<div class="detail-comment-replies"><button class="detail-comment-replies-toggle" type="button" data-comment-replies-toggle data-reply-count="' +
escapeHtml(replyCount) +
'" aria-expanded="false">' +
replyToggleLabel(false, replyCount) +
"</button></div>";
}
return (
'<div class="detail-comment" data-comment-id="' +
escapeHtml(commentId) +
'" data-parents-id="' +
escapeHtml(parentsId) +
'" data-user-name="' +
escapeHtml(item.userName || "匿名用户") +
'">' +
avatarHtml +
'<div class="detail-comment-body">' +
'<div class="detail-comment-name">' +
escapeHtml(item.userName || "匿名用户") +
"</div>" +
'<div class="detail-comment-text">' +
textHtml +
"</div>" +
'<div class="detail-comment-meta">' +
'<span class="detail-comment-time">' +
escapeHtml(formatCommentTime(item.createdAt)) +
"</span>" +
'<button class="detail-comment-like' +
(liked ? " detail-comment-like--active" : "") +
'" type="button" aria-label="点赞" data-comment-like="' +
escapeHtml(commentId) +
'" data-liked="' +
(liked ? "1" : "2") +
'">' +
'<img src="' +
likeIcon +
'" alt="点赞" />' +
'<span data-like-count>' +
escapeHtml(item.countLike == null ? 0 : item.countLike) +
"</span>" +
"</button>" +
'<button class="detail-comment-reply-label" type="button" data-comment-reply="' +
escapeHtml(commentId) +
'" data-parents-id="' +
escapeHtml(parentsId) +
'" data-reply-name="' +
escapeHtml(item.userName || "匿名用户") +
'">回复</button>' +
"</div>" +
repliesHtml +
"</div></div>"
);
}
function renderPlayComment(item) {
var liked = Number(item.isLike) === 1;
var avatar = avatarURL(item.userAvatar);
var commentId = item.id;
var parentsId = Number(item.parentsId || 0) || Number(commentId);
var children = Array.isArray(item.children) ? item.children : [];
var replyCount = Number(item.commentCount || children.length || 0);
var avatarHtml = item.userAvatar
? '<img class="play-comment-avatar lazy" src="' +
PLACEHOLDER +
'" data-src="' +
escapeHtml(avatar) +
'" alt="' +
escapeHtml((item.userName || "匿名用户") + "的头像") +
'" />'
: '<img class="play-comment-avatar" src="' + DEFAULT_AVATAR + '" alt="匿名用户头像" />';
var likeIcon = liked
? "/static/images/h5_liked.svg"
: "/static/images/like_hollow.svg";
var textHtml = escapeHtml(item.text || "");
if (item.replyUserName) {
textHtml =
'<span class="play-comment-reply-to">回复 @' +
escapeHtml(item.replyUserName) +
"</span> " +
textHtml;
}
var repliesHtml = "";
if (children.length) {
repliesHtml =
'<div class="play-comment-replies">' +
children
.map(function (child) {
return (
'<div class="play-comment-reply-line" data-comment-id="' +
escapeHtml(child.id) +
'">' +
'<span class="play-comment-reply-name">' +
escapeHtml(child.userName || "匿名用户") +
"</span>" +
(child.replyUserName
? '<span class="play-comment-reply-badge">回复</span><span class="play-comment-reply-name">' +
escapeHtml(child.replyUserName) +
"</span>"
: "") +
'<span class="play-comment-reply-text">' +
escapeHtml(child.text || "") +
"</span>" +
"</div>"
);
})
.join("") +
(replyCount > children.length
? '<button class="play-comment-replies-all" type="button" data-comment-replies="' +
escapeHtml(commentId) +
'">全部' +
escapeHtml(replyCount) +
"条评论</button>"
: "") +
"</div>";
} else if (replyCount > 0) {
repliesHtml =
'<button class="play-comment-more-replies" type="button" data-comment-replies="' +
escapeHtml(commentId) +
'">全部' +
escapeHtml(replyCount) +
'条评论<img src="/static/images/arrow_right.svg" alt="查看回复" /></button>';
}
return (
'<div class="play-comment-item" data-comment-id="' +
escapeHtml(commentId) +
'" data-parents-id="' +
escapeHtml(parentsId) +
'" data-user-name="' +
escapeHtml(item.userName || "匿名用户") +
'">' +
avatarHtml +
'<div class="play-comment-body">' +
'<div class="play-comment-name">' +
escapeHtml(item.userName || "匿名用户") +
"</div>" +
'<div class="play-comment-text">' +
textHtml +
"</div>" +
'<div class="play-comment-meta">' +
'<div class="play-comment-meta-left">' +
'<span class="play-comment-time">' +
escapeHtml(formatRelativeTime(item.createdAt)) +
"</span>" +
'<button class="play-comment-reply-action" type="button" data-comment-reply="' +
escapeHtml(commentId) +
'" data-parents-id="' +
escapeHtml(parentsId) +
'" data-reply-name="' +
escapeHtml(item.userName || "匿名用户") +
'">回复</button>' +
"</div>" +
'<button class="play-comment-like' +
(liked ? " play-comment-like--active" : "") +
'" type="button" aria-label="点赞" data-comment-like="' +
escapeHtml(commentId) +
'" data-liked="' +
(liked ? "1" : "2") +
'" data-like-raw="' +
escapeHtml(item.countLike == null ? 0 : item.countLike) +
'">' +
'<img src="' +
likeIcon +
'" alt="点赞" />' +
'<span data-like-count>' +
escapeHtml(formatCompactCount(item.countLike)) +
"</span>" +
"</button>" +
"</div>" +
repliesHtml +
"</div></div>"
);
}
function renderComment(item, sheetMode, playPcMode) {
return sheetMode
? renderPlayComment(item)
: renderDetailComment(item, playPcMode);
}
function triggerLazy(root) {
if (typeof window.initLazyImages === "function") {
window.initLazyImages(root || document);
return;
}
if (typeof window.__lazyLoadImages === "function") {
window.__lazyLoadImages();
return;
}
if (window.LazyLoad && typeof window.LazyLoad.update === "function") {
window.LazyLoad.update();
}
}
function initCommentSection(section) {
// PC 播放页评论区仅桌面初始化;H5 sheet 始终初始化(避免宽屏加载后缩到手机仍卡在「加载中」)
if (section.classList.contains("play-comments")) {
if (window.matchMedia("(max-width: 767px)").matches) return;
}
var sheetRoot = section.closest("[data-play-comment-sheet]");
var sheetMode = !!sheetRoot || section.classList.contains("play-comment-sheet-body");
var playPcMode = section.classList.contains("play-comments");
var scope = sheetRoot || section;
var listEl = section.querySelector("[data-comment-list]");
var moreBtn = section.querySelector("[data-comment-more]");
var composerInput =
scope.querySelector(".play-comment-composer-input") ||
scope.querySelector(".detail-comment-composer-input");
var composerCount = scope.querySelector(".detail-comment-composer-count");
var titleEl = section.querySelector("[data-comment-title]");
var sendBtn = scope.querySelector("[data-comment-send]");
var sortBtns = scope.querySelectorAll("[data-comment-sort]");
var sheetTitle = sheetRoot
? sheetRoot.querySelector("[data-play-comment-title]")
: document.querySelector("[data-play-comment-title]");
var sideCount = document.querySelector("[data-play-comment-count]");
var iconSend = !!(sendBtn && sendBtn.classList.contains("play-comment-composer-emoji"));
if (section.getAttribute("data-comment-inited") === "1") return;
section.setAttribute("data-comment-inited", "1");
var objectType = Number(section.getAttribute("data-comment-object-type") || 10);
var pageSize = Number(section.getAttribute("data-comment-size") || 10);
var sortDefault = Number(section.getAttribute("data-comment-sort-default") || 0);
var sortOrder = sortDefault === 2 || sortDefault === 1 ? sortDefault : sheetMode ? 2 : 1;
var page = 0;
var total = 0;
var loading = false;
var sending = false;
var replyTarget = null; // { parentsId, replyCommentId, replyName }
var inlineSending = false;
function getObjectId() {
return Number(section.getAttribute("data-comment-object-id") || 0);
}
function clearReplyTarget() {
replyTarget = null;
if (!scope) return;
var boxes = scope.querySelectorAll("[data-comment-inline-composer]");
Array.prototype.forEach.call(boxes, function (el) {
el.remove();
});
}
function setReplyTarget(parentsId, replyCommentId, replyName, anchorEl) {
parentsId = Number(parentsId || 0);
replyCommentId = Number(replyCommentId || 0);
if (!parentsId || !replyCommentId || !anchorEl) return;
var root =
anchorEl.closest(commentItemSelector()) ||
anchorEl.closest("[data-comment-id]");
if (!root || !scope.contains(root)) return;
// 再次点击同一条:关闭内联输入框
var existingOnRoot = root.querySelector("[data-comment-inline-composer]");
if (
existingOnRoot &&
replyTarget &&
replyTarget.replyCommentId === replyCommentId
) {
clearReplyTarget();
return;
}
clearReplyTarget();
replyTarget = {
parentsId: parentsId,
replyCommentId: replyCommentId,
replyName: replyName || "用户",
};
var meta =
root.querySelector(".detail-comment-meta") ||
root.querySelector(".play-comment-meta");
var body =
root.querySelector(".detail-comment-body") ||
root.querySelector(".play-comment-body") ||
root;
var box = document.createElement("div");
box.className = sheetMode
? "play-comment-inline-composer"
: "detail-comment-inline-composer";
box.setAttribute("data-comment-inline-composer", "");
var placeholder = "回复 @" + replyTarget.replyName + "…";
if (sheetMode) {
box.innerHTML =
'<div class="play-comment-reply-hint" data-comment-reply-hint>' +
"<span>回复 @" +
escapeHtml(replyTarget.replyName) +
"</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(placeholder) +
'" data-comment-inline-input autocomplete="off" />' +
'<button class="play-comment-inline-send" type="button" data-comment-inline-send>发送</button>' +
"</div>";
} else {
box.innerHTML =
'<div class="detail-comment-reply-hint" data-comment-reply-hint>' +
"<span>回复 @" +
escapeHtml(replyTarget.replyName) +
"</span>" +
'<button type="button" data-comment-reply-cancel>取消</button>' +
"</div>" +
'<textarea class="detail-comment-inline-input" maxlength="200" rows="2" placeholder="' +
escapeHtml(placeholder) +
'" data-comment-inline-input></textarea>' +
'<div class="detail-comment-inline-footer">' +
'<button class="detail-comment-inline-send" type="button" data-comment-inline-send>发表回复</button>' +
"</div>";
}
if (meta && meta.parentNode === body) {
if (meta.nextSibling) {
body.insertBefore(box, meta.nextSibling);
} else {
body.appendChild(box);
}
} else {
body.appendChild(box);
}
var inlineInput = box.querySelector("[data-comment-inline-input]");
if (inlineInput) {
inlineInput.focus();
}
}
function sendInlineReply(box) {
if (!box || inlineSending || !replyTarget) return;
var objectId = getObjectId();
if (!objectId) return;
var input = box.querySelector("[data-comment-inline-input]");
var btn = box.querySelector("[data-comment-inline-send]");
if (!input) return;
var text = String(input.value || "").trim();
if (!text) {
toast("请输入评论内容", "error");
return;
}
if (Array.from(text).length > 200) {
toast("评论最多 200 字", "error");
return;
}
inlineSending = true;
if (btn) {
btn.disabled = true;
btn.classList.add("is-loading");
}
input.disabled = true;
var payload = {
objectId: objectId,
objectType: objectType,
text: text,
parentsId: replyTarget.parentsId,
replyCommentId:
replyTarget.replyCommentId > 0
? replyTarget.replyCommentId
: replyTarget.parentsId,
};
window.HttpRequest.post("/api/comment/send", payload)
.then(function (res) {
if (!res || res.code !== 200) {
if (res && res.code === 401) {
var loginOpen = document.querySelector(
"[data-login-dialog-open]"
);
if (loginOpen) loginOpen.click();
}
throw new Error((res && (res.tip || res.msg)) || "发表失败");
}
clearReplyTarget();
toast("发布成功,评论审核后才会展示", "success");
})
.catch(function (err) {
toast((err && err.message) || "发表失败,请稍后重试", "error");
})
.finally(function () {
inlineSending = false;
if (btn) {
btn.disabled = false;
btn.classList.remove("is-loading");
}
if (input) input.disabled = false;
});
}
function commentItemSelector() {
return sheetMode ? ".play-comment-item" : ".detail-comment";
}
function updateTitleCount() {
if (titleEl) {
titleEl.textContent = total > 0 ? "评论 (" + total + ")" : "评论";
}
if (sheetTitle) {
sheetTitle.textContent = total > 0 ? total + "条评论" : "评论";
}
if (sideCount) {
sideCount.textContent = String(total);
}
}
function applyLikeUI(btn, isLike, countLike) {
var liked = Number(isLike) === 1;
btn.classList.toggle("detail-comment-like--active", liked);
btn.classList.toggle("play-comment-like--active", liked);
btn.setAttribute("data-liked", liked ? "1" : "2");
var img = btn.querySelector("img");
if (img) {
if (sheetMode) {
img.src = liked
? "/static/images/h5_liked.svg"
: "/static/images/like_hollow.svg";
} else {
img.src = liked
? "/static/images/like_solid.svg"
: "/static/images/like_hollow.svg";
}
}
var countEl = btn.querySelector("[data-like-count]");
if (countEl && countLike != null) {
btn.setAttribute("data-like-raw", String(countLike));
countEl.textContent = sheetMode
? formatCompactCount(countLike)
: String(countLike);
}
}
function syncSortActive() {
sortBtns.forEach(function (item) {
var active = Number(item.getAttribute("data-comment-sort")) === sortOrder;
item.classList.toggle("detail-sort-btn--active", active);
item.classList.toggle("play-comment-sort-btn--active", active);
});
}
function setMoreVisible(show) {
if (!moreBtn) return;
if (show) {
moreBtn.hidden = false;
moreBtn.removeAttribute("hidden");
moreBtn.disabled = false;
} else {
moreBtn.hidden = true;
moreBtn.setAttribute("hidden", "");
moreBtn.disabled = false;
}
}
function showEmpty(text) {
var emptyClass = sheetMode
? "section-empty play-comment-empty"
: "section-empty";
clearReplyTarget();
listEl.innerHTML =
'<div class="' +
emptyClass +
'" data-comment-empty>' +
escapeHtml(text || "暂无评论") +
"</div>";
setMoreVisible(false);
}
function fetchPage(nextPage, replace) {
var objectId = getObjectId();
if (!objectId || loading) return Promise.resolve();
// detail.js 若早于 defer 的 request.js 执行,直接 get 会同步抛错且 loading 无法复位
if (!window.HttpRequest || typeof window.HttpRequest.get !== "function") {
if (replace) showEmpty("加载失败,请稍后重试");
return Promise.resolve();
}
loading = true;
if (moreBtn) moreBtn.disabled = true;
return window.HttpRequest.get("/api/comment/list", {
params: {
objectId: objectId,
objectType: objectType,
pageNum: nextPage,
pageSize: pageSize,
sortOrder: sortOrder,
},
})
.then(function (res) {
if (!res || res.code !== 200 || !res.data) {
throw new Error((res && (res.tip || res.msg)) || "加载失败");
}
var rows = res.data.list || [];
total = Number(res.data.totalCount != null ? res.data.totalCount : total);
page = Number(res.data.pageNum || nextPage);
updateTitleCount();
if (replace) {
clearReplyTarget();
if (!rows.length) {
showEmpty("暂无评论");
return;
}
listEl.innerHTML = rows
.map(function (row) {
return renderComment(row, sheetMode, playPcMode);
})
.join("");
} else if (rows.length) {
var empty = listEl.querySelector(
"[data-comment-empty],[data-comment-loading]"
);
if (empty) empty.remove();
listEl.insertAdjacentHTML(
"beforeend",
rows
.map(function (row) {
return renderComment(row, sheetMode, playPcMode);
})
.join("")
);
}
triggerLazy(listEl);
var loaded = listEl.querySelectorAll(commentItemSelector()).length;
var hasMore =
rows.length > 0 &&
rows.length >= pageSize &&
(total <= 0 || loaded < total);
setMoreVisible(hasMore);
})
.catch(function (err) {
if (replace || !listEl.querySelector(commentItemSelector())) {
showEmpty((err && err.message) || "加载失败,请稍后重试");
} else {
toast((err && err.message) || "加载失败,请稍后重试", "error");
if (moreBtn) moreBtn.disabled = false;
}
})
.finally(function () {
loading = false;
});
}
if (composerInput && composerCount) {
var syncCount = function () {
composerCount.textContent =
Array.from(composerInput.value).length + " / 200";
};
composerInput.addEventListener("input", syncCount);
syncCount();
}
syncSortActive();
sortBtns.forEach(function (btn) {
btn.addEventListener("click", function () {
var next = Number(btn.getAttribute("data-comment-sort") || 1);
if (next === sortOrder || loading) return;
sortOrder = next;
syncSortActive();
listEl.innerHTML =
'<div class="section-empty' +
(sheetMode ? " play-comment-empty" : "") +
'" data-comment-loading>评论加载中…</div>';
setMoreVisible(false);
fetchPage(1, true);
});
});
if (sendBtn && composerInput) {
var sendBtnDefaultText = sendBtn.textContent || "发表评论";
sendBtn.addEventListener("click", function () {
var objectId = getObjectId();
if (sending || !objectId) return;
var text = String(composerInput.value || "").trim();
if (!text) {
toast("请输入评论内容", "error");
return;
}
if (Array.from(text).length > 200) {
toast("评论最多 200 字", "error");
return;
}
sending = true;
sendBtn.disabled = true;
sendBtn.classList.add("is-loading");
if (!iconSend) sendBtn.textContent = "发表中";
composerInput.disabled = true;
var payload = {
objectId: objectId,
objectType: objectType,
text: text,
parentsId: 0,
replyCommentId: 0,
};
window.HttpRequest.post("/api/comment/send", payload)
.then(function (res) {
if (!res || res.code !== 200) {
if (res && res.code === 401) {
var loginOpen = document.querySelector(
"[data-login-dialog-open]"
);
if (loginOpen) loginOpen.click();
}
throw new Error((res && (res.tip || res.msg)) || "发表失败");
}
composerInput.value = "";
if (composerCount) composerCount.textContent = "0 / 200";
toast("发布成功,评论审核后才会展示", "success");
// 与 PC 一致:发表后不强制刷新列表(审核后才可见)
})
.catch(function (err) {
toast((err && err.message) || "发表失败,请稍后重试", "error");
})
.finally(function () {
sending = false;
sendBtn.disabled = false;
sendBtn.classList.remove("is-loading");
if (!iconSend) sendBtn.textContent = sendBtnDefaultText;
composerInput.disabled = false;
});
});
if (sheetMode) {
composerInput.addEventListener("keydown", function (e) {
if (e.key === "Enter") {
e.preventDefault();
sendBtn.click();
}
});
}
}
if (scope) {
scope.addEventListener("click", function (e) {
var cancel =
e.target && e.target.closest
? e.target.closest("[data-comment-reply-cancel]")
: null;
if (cancel) {
e.preventDefault();
clearReplyTarget();
return;
}
var inlineSend =
e.target && e.target.closest
? e.target.closest("[data-comment-inline-send]")
: null;
if (inlineSend && scope.contains(inlineSend)) {
e.preventDefault();
var box = inlineSend.closest("[data-comment-inline-composer]");
sendInlineReply(box);
return;
}
var repliesToggle =
e.target && e.target.closest
? e.target.closest("[data-comment-replies-toggle]")
: null;
if (repliesToggle && scope.contains(repliesToggle)) {
e.preventDefault();
var repliesBox = repliesToggle.closest(".detail-comment-replies");
if (!repliesBox) return;
var expanded = repliesToggle.getAttribute("aria-expanded") === "true";
expanded = !expanded;
repliesToggle.setAttribute("aria-expanded", expanded ? "true" : "false");
repliesToggle.textContent = replyToggleLabel(
expanded,
repliesToggle.getAttribute("data-reply-count")
);
var hiddenItems = repliesBox.querySelectorAll("[data-reply-collapsed-item]");
Array.prototype.forEach.call(hiddenItems, function (el) {
el.hidden = !expanded;
});
return;
}
var replyBtn =
e.target && e.target.closest
? e.target.closest("[data-comment-reply]")
: null;
if (!replyBtn || !scope.contains(replyBtn)) return;
e.preventDefault();
var replyCommentId = Number(replyBtn.getAttribute("data-comment-reply") || 0);
var parentsId = Number(replyBtn.getAttribute("data-parents-id") || 0);
var replyName = replyBtn.getAttribute("data-reply-name") || "用户";
if (!parentsId) {
var root =
replyBtn.closest("[data-parents-id]") ||
replyBtn.closest("[data-comment-id]");
if (root) {
parentsId = Number(
root.getAttribute("data-parents-id") ||
root.getAttribute("data-comment-id") ||
0
);
}
}
if (!replyCommentId) return;
if (!parentsId) parentsId = replyCommentId;
setReplyTarget(parentsId, replyCommentId, replyName, replyBtn);
});
scope.addEventListener("keydown", function (e) {
if (e.key !== "Enter") return;
var input =
e.target && e.target.closest
? e.target.closest("[data-comment-inline-input]")
: null;
if (!input || !scope.contains(input)) return;
if (input.tagName === "TEXTAREA" && !e.ctrlKey && !e.metaKey) return;
e.preventDefault();
var box = input.closest("[data-comment-inline-composer]");
sendInlineReply(box);
});
}
if (moreBtn) {
moreBtn.addEventListener("click", function () {
fetchPage(page + 1, false);
});
}
if (listEl) {
listEl.addEventListener("click", function (e) {
var btn =
e.target && e.target.closest
? e.target.closest("[data-comment-like]")
: null;
if (!btn || !listEl.contains(btn)) return;
var commentId = Number(btn.getAttribute("data-comment-like") || 0);
if (!commentId) return;
if (btn.getAttribute("data-liking") === "1") return;
var likedNow = Number(btn.getAttribute("data-liked") || 2) === 1;
var nextLike = !likedNow;
var rawAttr = btn.getAttribute("data-like-raw");
var countEl = btn.querySelector("[data-like-count]");
var prevCount = Number(
rawAttr != null
? rawAttr
: countEl && countEl.textContent
? countEl.textContent
: 0
);
if (Number.isNaN(prevCount)) prevCount = 0;
btn.setAttribute("data-liking", "1");
btn.disabled = true;
applyLikeUI(
btn,
nextLike ? 1 : 2,
Math.max(0, prevCount + (nextLike ? 1 : -1))
);
window.HttpRequest.post("/api/comment/like/set", {
commentId: commentId,
isLike: nextLike,
})
.then(function (res) {
if (!res || res.code !== 200) {
if (res && res.code === 401) {
var loginOpen = document.querySelector(
"[data-login-dialog-open]"
);
if (loginOpen) loginOpen.click();
}
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 : null
);
})
.catch(function (err) {
applyLikeUI(btn, likedNow ? 1 : 2, prevCount);
toast((err && err.message) || "操作失败,请稍后重试", "error");
})
.finally(function () {
btn.removeAttribute("data-liking");
btn.disabled = false;
});
});
}
if (!listEl || !getObjectId()) {
if (listEl) showEmpty("暂无评论");
return;
}
section.addEventListener("play:reload-comments", function () {
listEl.innerHTML =
'<div class="section-empty' +
(sheetMode ? " play-comment-empty" : "") +
'" data-comment-loading>评论加载中…</div>';
setMoreVisible(false);
fetchPage(1, true);
});
fetchPage(1, true);
}
// 等 DOMContentLoaded:保证 header 中 defer 的 request.js 已执行
function bootCommentSections() {
if (!window.HttpRequest || typeof window.HttpRequest.get !== "function") {
// 极端情况:再等 window.load 一次
if (!bootCommentSections._retried) {
bootCommentSections._retried = true;
window.addEventListener("load", bootCommentSections, { once: true });
}
return;
}
document.querySelectorAll(".detail-comments").forEach(initCommentSection);
}
if (document.readyState === "loading") {
document.addEventListener("DOMContentLoaded", bootCommentSections, { once: true });
} else {
bootCommentSections();
}
})();
/** H5 返回:避免 play ↔ detail 互相压栈导致死循环 */
(function () {
var nodes = document.querySelectorAll("[data-nav-back], .play-h5-back, .detail-h5-back");
if (!nodes.length) return;
function pathOf(url) {
try {
return new URL(url, window.location.origin).pathname || "";
} catch (e) {
return "";
}
}
function isPlayPath(pathname) {
return pathname === "/play";
}
function isDetailPath(pathname) {
return pathname === "/detail";
}
function resolveFallback(el) {
var fallback = el.getAttribute("data-nav-fallback") || "";
if (fallback) return fallback;
var href = el.getAttribute("href") || "";
if (href && href.indexOf("javascript:") !== 0) return href;
return "/";
}
nodes.forEach(function (el) {
el.addEventListener("click", function (e) {
var fallback = resolveFallback(el);
var ref = document.referrer || "";
var sameOrigin = !!ref && ref.indexOf(window.location.origin) === 0;
var refPath = sameOrigin ? pathOf(ref) : "";
var here = window.location.pathname || "";
// 详情页若上一页是播放页,history.back 会回到播放页形成循环
if (isDetailPath(here) && isPlayPath(refPath)) {
e.preventDefault();
window.location.href = fallback || "/";
return;
}
// 播放页:有同站来源时回退历史(通常回到详情),否则走详情/首页兜底
if (isPlayPath(here)) {
e.preventDefault();
if (sameOrigin && refPath && ref !== window.location.href) {
window.history.back();
return;
}
window.location.href = fallback || "/";
return;
}
// 其它带 data-nav-back 的页面
if (sameOrigin && refPath && ref !== window.location.href) {
e.preventDefault();
window.history.back();
return;
}
var href = el.getAttribute("href") || "";
if (!href || href.indexOf("javascript:") === 0) {
e.preventDefault();
window.location.href = fallback || "/";
}
});
});
})();