(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 () { // 付费剧集:不再站内解锁,统一引导下载 APP var lockedEpisodes = document.querySelectorAll("[data-episode-locked]"); if (!lockedEpisodes.length) return; lockedEpisodes.forEach(function (el) { el.addEventListener("click", function (e) { e.preventDefault(); if (typeof window.openDownloadApp === "function") { window.openDownloadApp(); } }); }); })(); (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, "&") .replace(//g, ">") .replace(/"/g, """); } 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 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 avatar = avatarURL(item.userAvatar); var avatarHtml = item.userAvatar ? '' +
              escapeHtml((item.userName || ' : '匿名用户头像'; var textHtml = escapeHtml(item.text || ""); if (item.replyUserName) { textHtml = '回复 @' + escapeHtml(item.replyUserName) + " " + 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 = '
' + children .map(function (child, index) { var childText = escapeHtml(child.text || ""); if (child.replyUserName) { childText = '@' + escapeHtml(child.replyUserName) + " " + childText; } return ( '
0 ? ' data-reply-collapsed-item hidden' : "") + ">" + '' + escapeHtml(child.userName || "匿名用户") + ":" + '' + childText + "" + "
" ); }) .join("") + (replyCount > 1 ? '" : "") + "
"; } else if (playPcMode && replyCount > 1) { repliesHtml = '
"; } return ( '
' + avatarHtml + '
' + '
' + escapeHtml(item.userName || "匿名用户") + "
" + '
' + textHtml + "
" + '
' + '' + escapeHtml(formatCommentTime(item.createdAt)) + "" + "
" + repliesHtml + "
" ); } function renderPlayComment(item) { var avatar = avatarURL(item.userAvatar); var commentId = item.id; var children = Array.isArray(item.children) ? item.children : []; var replyCount = Number(item.commentCount || children.length || 0); var avatarHtml = item.userAvatar ? '' +
              escapeHtml((item.userName || ' : '匿名用户头像'; var textHtml = escapeHtml(item.text || ""); if (item.replyUserName) { textHtml = '回复 @' + escapeHtml(item.replyUserName) + " " + textHtml; } var repliesHtml = ""; if (children.length) { repliesHtml = '
' + children .map(function (child) { return ( '
' + '' + escapeHtml(child.userName || "匿名用户") + "" + (child.replyUserName ? '回复' + escapeHtml(child.replyUserName) + "" : "") + '' + escapeHtml(child.text || "") + "" + "
" ); }) .join("") + (replyCount > children.length ? '" : "") + "
"; } else if (replyCount > 0) { repliesHtml = ''; } return ( '
' + avatarHtml + '
' + '
' + escapeHtml(item.userName || "匿名用户") + "
" + '
' + textHtml + "
" + '
' + '
' + '' + escapeHtml(formatRelativeTime(item.createdAt)) + "" + "
" + "
" + repliesHtml + "
" ); } 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 titleEl = section.querySelector("[data-comment-title]"); 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]"); 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; function getObjectId() { return Number(section.getAttribute("data-comment-object-id") || 0); } 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 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"; listEl.innerHTML = '
' + escapeHtml(text || "暂无评论") + "
"; 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) { 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; }); } 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 = '
评论加载中…
'; setMoreVisible(false); fetchPage(1, true); }); }); if (scope) { scope.addEventListener("click", function (e) { var repliesToggle = e.target && e.target.closest ? e.target.closest("[data-comment-replies-toggle]") : null; if (!repliesToggle || !scope.contains(repliesToggle)) return; 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; }); }); } if (moreBtn) { moreBtn.addEventListener("click", function () { fetchPage(page + 1, false); }); } if (!listEl || !getObjectId()) { if (listEl) showEmpty("暂无评论"); return; } section.addEventListener("play:reload-comments", function () { listEl.innerHTML = '
评论加载中…
'; 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 || "/"; } }); }); })();