Files
shencaisuan/html/mianfeishoucang.html

752 lines
27 KiB
HTML

<!doctype html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title id="page-title">神彩算 - 我的收藏</title>
<script src="../public/css/layui/layui.js"></script>
<script src="../config.js"></script>
<script src="../utils/ApiClient.js"></script>
<script src="../utils/DateUtil.js"></script>
<script src="../utils/CommonUtil.js"></script>
<link rel="stylesheet" href="../public/css/layui/css/layui.css" />
<link rel="stylesheet" href="../public/css/public.css" />
<link rel="stylesheet" href="../public/css/layout-fix.css" />
<link rel="stylesheet" href="../public/css/mianfei.css" />
<link rel="stylesheet" href="../public/css/publish.css" />
<link rel="stylesheet" href="../public/css/mianfeishoucang.css" />
</head>
<body>
<!-- Header -->
<nav class="topbar" id="navbar">
<div class="topbar__inner">
<a href="index.html" class="topbar__brand">
<img
src="../images/home-logo.png"
alt="神彩算"
class="topbar__logo"
/>
</a>
<div class="topbar__nav" id="navLotteryItems"></div>
<div class="topbar__user">
<div id="notLoggedInBox" class="topbar__guest">
<a href="login.html" class="topbar__btn topbar__btn--ghost">登录</a>
<a href="reg.html" class="topbar__btn topbar__btn--primary"
>免费注册</a
>
</div>
<div id="loggedInBox" class="topbar__logged sc-hidden">
<a href="usercenter.html" class="topbar__profile">
<img
id="userAvatar"
src="../public/img/userimg.png"
alt=""
class="topbar__avatar"
/>
<div class="topbar__meta">
<span id="userName" class="topbar__uname">用户</span>
<span id="userLevel" class="topbar__ubadge">会员</span>
</div>
</a>
<button class="topbar__btn topbar__btn--logout" onclick="logout()">
退出登录
</button>
</div>
</div>
</div>
</nav>
<!-- Main -->
<main class="cxz-main">
<div class="cxz-breadcrumb">
<a href="index.html">首页</a><span class="sep">></span>
<a href="usercenter.html">个人中心</a><span class="sep">></span>
<span>我的收藏</span>
</div>
<!-- Stats Summary Cards (also act as tab switcher) -->
<div class="sc-stats">
<div
class="sc-stat-card active"
data-tab="free"
onclick="switchTab('free')"
>
<div class="sc-stat-icon gold">
<i class="layui-icon layui-icon-star-fill"></i>
</div>
<div class="sc-stat-info">
<div class="sc-stat-value" id="statFree">0</div>
<div class="sc-stat-label">免费文章收藏</div>
</div>
</div>
<div class="sc-stat-card" data-tab="paid" onclick="switchTab('paid')">
<div class="sc-stat-icon orange">
<i class="layui-icon layui-icon-rmb"></i>
</div>
<div class="sc-stat-info">
<div class="sc-stat-value" id="statPaid">0</div>
<div class="sc-stat-label">付费文章收藏</div>
</div>
</div>
<div
class="sc-stat-card"
data-tab="expert"
onclick="switchTab('expert')"
>
<div class="sc-stat-icon blue">
<i class="layui-icon layui-icon-friends"></i>
</div>
<div class="sc-stat-info">
<div class="sc-stat-value" id="statExpert">0</div>
<div class="sc-stat-label">专家收藏</div>
</div>
</div>
</div>
<!-- Panel: Free Article Collection -->
<div class="sc-panel active" id="panelFree">
<div class="cxz-collect-grid" id="freeGrid"></div>
<div class="cxz-empty sc-hidden" id="freeEmpty">
<div class="cxz-empty-icon">
<i class="layui-icon layui-icon-star-fill sc-empty-icon-symbol"></i>
</div>
<div class="cxz-empty-text">暂无免费文章收藏</div>
<a href="mianfeilist.html" class="cxz-empty-action">去浏览文章</a>
</div>
<div class="cxz-pagination" id="freePager"></div>
</div>
<!-- Panel: Paid Article Collection -->
<div class="sc-panel" id="panelPaid">
<div class="sc-paid-table" id="paidList"></div>
<div class="cxz-empty sc-hidden" id="paidEmpty">
<div class="cxz-empty-icon">
<i class="layui-icon layui-icon-rmb sc-empty-icon-symbol"></i>
</div>
<div class="cxz-empty-text">暂无付费文章收藏</div>
<a href="cai.html" class="cxz-empty-action">去浏览预测</a>
</div>
<div class="cxz-pagination" id="paidPager"></div>
</div>
<!-- Panel: Expert Collection -->
<div class="sc-panel" id="panelExpert">
<div class="sc-expert-grid" id="expertGrid"></div>
<div class="cxz-empty sc-hidden" id="expertEmpty">
<div class="cxz-empty-icon">
<i class="layui-icon layui-icon-friends sc-empty-icon-symbol"></i>
</div>
<div class="cxz-empty-text">暂无专家收藏</div>
<a href="cai.html" class="cxz-empty-action">去浏览专家</a>
</div>
<div class="cxz-pagination" id="expertPager"></div>
</div>
</main>
<!-- Footer -->
<footer class="site-footer">
<div class="site-footer__inner">
<img
src="../images/home-logo.png"
alt="神彩算"
class="site-footer__logo"
/>
<p id="copyright-info">神彩算以开奖数据、免费文章、资讯推荐为核心</p>
<div class="site-footer__meta">
<a
id="beian"
href="https://beian.miit.gov.cn/"
target="_blank"
class="site-footer__record"
>工信部备案号</a
>
<span id="additional-info">本站仅供数据分析参考</span>
</div>
</div>
</footer>
<button
class="gotop"
id="backTop"
type="button"
onclick="window.scrollTo({ top: 0, behavior: 'smooth' })"
>
</button>
<script>
const $ = layui.$;
const pageSize = 12;
let currentTab = getInitialTab();
let freePage = 1;
let paidPage = 1;
let expertPage = 1;
const collectTabs = {
free: {
stat: "#statFree",
panel: "#panelFree",
grid: "#freeGrid",
empty: "#freeEmpty",
pager: "freePager",
endpoint: getApiPath("mineFreeCollectList", "/api/web/mine/free-collect/list"),
cancelText: "确定要取消收藏该免费文章吗?",
},
paid: {
stat: "#statPaid",
panel: "#panelPaid",
grid: "#paidList",
empty: "#paidEmpty",
pager: "paidPager",
endpoint: getApiPath("minePayCollectList", "/api/web/mine/pay-collect/list"),
cancelText: "确定要取消收藏该付费文章吗?",
},
expert: {
stat: "#statExpert",
panel: "#panelExpert",
grid: "#expertGrid",
empty: "#expertEmpty",
pager: "expertPager",
endpoint: getApiPath("mineExpertCollectList", "/api/web/mine/expert-collect/list"),
cancelText: "确定要取消收藏该专家吗?",
},
};
function getApiPath(key, fallback) {
return ApiClient.API && ApiClient.API[key] ? ApiClient.API[key] : fallback;
}
function logout() {
localStorage.removeItem("token");
window.location.reload();
}
function checkLogin() {
if (!CommonUtil.getAuthToken()) {
NavigationUtil.open("login.html");
return;
}
$("#notLoggedInBox").hide();
$("#loggedInBox").css("display", "flex");
CommonUtil.getCurrentUserDetail(
function (data) {
$("#userName").text(data.nickName || data.nickname || data.userName || "用户");
$("#userLevel").text(data.isExpert === "1" || data.expertStatus ? "专家" : "会员");
if (data.avatar) $("#userAvatar").attr("src", data.avatar);
},
function () {
$("#notLoggedInBox").show();
$("#loggedInBox").hide();
},
);
}
function renderNav() {
ApiClient.loadMenuCompat().then(function (res) {
if (!ApiClient.isSuccess(res) || !res.data) return;
var $nav = $("#navLotteryItems");
$nav.find(".topbar__nav-link").not(":first").remove();
$.each(res.data, function (i, item) {
$nav.append(
'<a href="cai.html?id=' +
encodeURIComponent(item.suoxie || item.code || item.id || "") +
'" class="topbar__nav-link">' +
escapeHtml(item.name || item.menuName || "彩种") +
"</a>",
);
});
});
}
$(window).on("scroll", function () {
$(window).scrollTop() > 320
? $("#backTop").addClass("is-visible")
: $("#backTop").removeClass("is-visible");
});
function switchTab(tab) {
if (!collectTabs[tab]) return;
currentTab = tab;
syncTabToUrl(tab);
$(".sc-stat-card").removeClass("active");
$('.sc-stat-card[data-tab="' + tab + '"]').addClass("active");
$(".sc-panel").removeClass("active");
$(collectTabs[tab].panel).addClass("active");
loadCurrentTab();
}
function getInitialTab() {
const tab = new URLSearchParams(window.location.search).get("tab");
return tab === "paid" || tab === "expert" ? tab : "free";
}
function syncTabToUrl(tab) {
const url = new URL(window.location.href);
if (tab === "free") {
url.searchParams.delete("tab");
} else {
url.searchParams.set("tab", tab);
}
window.history.replaceState(null, "", url.pathname + url.search + url.hash);
}
function applyActiveTab() {
$(".sc-stat-card").removeClass("active");
$('.sc-stat-card[data-tab="' + currentTab + '"]').addClass("active");
$(".sc-panel").removeClass("active");
$(collectTabs[currentTab].panel).addClass("active");
}
function loadCurrentTab() {
if (currentTab === "free") loadFreeArticles();
else if (currentTab === "paid") loadPaidArticles();
else loadExperts();
}
function refreshCollectStats() {
Object.keys(collectTabs).forEach(function (tab) {
const config = collectTabs[tab];
safeGet(config.endpoint, {
pageNum: 1,
pageSize: 1,
})
.then(function (res) {
if (!ApiClient.isSuccess(res)) return;
const pageData = normalizePageData(res);
$(config.stat).text(pageData.total);
})
.catch(function (error) {
console.warn("获取收藏数量失败:", tab, error);
});
});
}
function loadCollectPage(tab, page) {
const config = collectTabs[tab];
setPanelLoading(tab);
return safeGet(config.endpoint, {
pageNum: page,
pageSize: pageSize,
})
.then(function (res) {
if (!ApiClient.isSuccess(res)) {
throw new Error(res && res.msg ? res.msg : "获取收藏列表失败");
}
const pageData = normalizePageData(res);
$(config.stat).text(pageData.total);
return pageData;
})
.catch(function (error) {
console.error("获取收藏列表失败:", error);
$(config.stat).text("0");
showEmpty(tab, true);
layui.layer.msg(error.message || "获取收藏列表失败", { icon: 2 });
return { records: [], total: 0 };
});
}
function safeGet(url, params) {
if (!url) {
return Promise.reject(new Error("缺少接口地址"));
}
try {
return ApiClient.get(url, params);
} catch (error) {
return Promise.reject(error);
}
}
function normalizePageData(res) {
const data = res.data && typeof res.data === "object" ? res.data : res;
const records = ApiClient.asArray(
data.records || data.rows || data.list || res.rows || data,
);
const total = Number(data.total || data.totalRow || res.total || records.length || 0);
return { records: records, total: total };
}
function loadFreeArticles() {
loadCollectPage("free", freePage).then(function (pageData) {
renderFreeArticles(pageData.records);
renderPager("free", pageData.total, freePage, function (page) {
freePage = page;
loadFreeArticles();
});
});
}
function renderFreeArticles(list) {
const $grid = $("#freeGrid");
$grid.empty();
showEmpty("free", !list.length);
if (!list.length) return;
$.each(list, function (i, item) {
const articleId = getArticleId(item);
const title = getField(item, ["title", "articleTitle", "name"], "未命名文章");
const author = CommonUtil.getFreeArticleIssuer
? CommonUtil.getFreeArticleIssuer(item)
: getField(item, ["issuer", "author", "authorName", "nickName", "nickname"], "--");
const displayDate = formatTime(getField(item, ["collectTime", "createTime", "createdTime"], ""));
const href = buildFreeArticleHref(item, articleId);
$grid.append(
'<div class="cxz-collect-card" data-article-id="' +
escapeAttr(articleId) +
'">' +
'<div class="cxz-card-body">' +
'<div class="cxz-card-title"><a href="' +
escapeAttr(href) +
'">' +
escapeHtml(title) +
"</a></div>" +
'<div class="cxz-card-meta">' +
"<span>" +
escapeHtml(author || "--") +
"</span>" +
"<span>" +
escapeHtml(displayDate || "--") +
"</span>" +
'<button class="cxz-card-remove" type="button" onclick="removeCollect(this, \'free\', \''
+ escapeJs(articleId) +
'\')">取消收藏</button>' +
"</div>" +
"</div>" +
"</div>",
);
});
}
function loadPaidArticles() {
loadCollectPage("paid", paidPage).then(function (pageData) {
renderPaidArticles(pageData.records);
renderPager("paid", pageData.total, paidPage, function (page) {
paidPage = page;
loadPaidArticles();
});
});
}
function renderPaidArticles(list) {
const $list = $("#paidList");
$list.empty();
showEmpty("paid", !list.length);
if (!list.length) return;
$.each(list, function (i, item) {
const articleId = getArticleId(item);
const expertId = getField(item, ["expertId", "createBy", "userId"], "");
const expertName = getField(item, ["expertName", "nickName", "nickname", "authorName"], "专家");
const avatar = getField(item, ["avatar", "headImg", "headImage"], "");
const title = getField(item, ["title", "articleTitle", "name"], "未命名方案");
const issue = getField(item, ["issue", "expect", "period"], "--");
const price = getField(item, ["price", "amount", "points", "score"], 0);
const collectTime = formatTime(getField(item, ["collectTime", "createTime", "createdTime"], ""));
const href = buildPayArticleHref(item);
const avatarHtml = avatar
? '<img class="sc-expert-avatar" src="' + escapeAttr(avatar) + '" />'
: '<div class="sc-expert-avatar-fallback">' + escapeHtml(String(expertName).charAt(0) || "专") + "</div>";
$list.append(
'<div class="sc-paid-row" data-article-id="' +
escapeAttr(articleId) +
'">' +
'<a class="sc-expert-cell" href="' +
escapeAttr(buildExpertHref(item, expertId, getField(item, ["accountType"], 1))) +
'">' +
avatarHtml +
'<span class="sc-expert-name">' +
escapeHtml(expertName) +
"</span></a>" +
'<span class="sc-paid-period">第' +
escapeHtml(issue) +
"期</span>" +
'<span class="sc-paid-title"><a href="' +
escapeAttr(href) +
'">' +
escapeHtml(title) +
"</a></span>" +
'<span class="sc-paid-price">' +
escapeHtml(price) +
"积分</span>" +
'<span class="sc-paid-time">' +
escapeHtml(collectTime || "--") +
"</span>" +
'<a class="sc-paid-view" href="' +
escapeAttr(href) +
'">查看</a>' +
'<button class="sc-paid-remove" type="button" onclick="removeCollect(this, \'paid\', \''
+ escapeJs(articleId) +
'\')">取消收藏</button>' +
"</div>",
);
});
}
function loadExperts() {
loadCollectPage("expert", expertPage).then(function (pageData) {
renderExperts(pageData.records);
renderPager("expert", pageData.total, expertPage, function (page) {
expertPage = page;
loadExperts();
});
});
}
function renderExperts(list) {
const $grid = $("#expertGrid");
$grid.empty();
showEmpty("expert", !list.length);
if (!list.length) return;
$.each(list, function (i, item) {
const expertId = getField(item, ["expertId", "id", "userId", "createBy"], "");
const accountType = getField(item, ["accountType"], 1);
const name = getField(item, ["expertName", "name", "nickName", "nickname"], "未知专家");
const avatar = getField(item, ["avatar", "headImg", "headImage"], "");
const collectTime = formatTime(getField(item, ["collectTime", "createTime", "createdTime"], ""));
const typeText = isTruthy(getField(item, ["regularExpert", "isRegularExpert"], false))
? "正式专家"
: "实习专家";
const href = buildExpertHref(item, expertId, accountType);
const avatarHtml = avatar
? '<img class="sc-expert-card-avatar" src="' + escapeAttr(avatar) + '" />'
: '<div class="sc-expert-card-avatar-fallback">' + escapeHtml(String(name).charAt(0) || "专") + "</div>";
$grid.append(
'<div class="sc-expert-card" data-expert-id="' +
escapeAttr(expertId) +
'">' +
avatarHtml +
'<div class="sc-expert-card-name">' +
escapeHtml(name) +
"</div>" +
'<div class="sc-expert-card-type">' +
escapeHtml(typeText) +
"</div>" +
'<div class="sc-expert-card-stats">' +
'<div class="sc-expert-card-stat"><div class="val">' +
escapeHtml(collectTime ? collectTime.slice(0, 10) : "--") +
'</div><div class="lbl">收藏时间</div></div>' +
"</div>" +
'<div class="sc-expert-card-actions">' +
'<a class="sc-paid-view" href="' +
escapeAttr(href) +
'">查看</a>' +
'<button class="sc-paid-remove" type="button" onclick="removeCollect(this, \'expert\', \''
+ escapeJs(expertId) +
"\', \'" +
escapeJs(accountType) +
'\')">取消收藏</button>' +
"</div>" +
"</div>",
);
});
}
function removeCollect(btn, type, id, accountType) {
if (!id) {
layui.layer.msg("缺少收藏对象ID,暂不能操作", { icon: 2 });
return;
}
const config = collectTabs[type] || collectTabs[currentTab];
layui.layer.confirm(
config.cancelText,
{ icon: 3, title: "提示" },
function (index) {
layui.layer.close(index);
getCancelRequest(type, id, accountType).then(function (res) {
if (ApiClient.isSuccess(res)) {
layui.layer.msg("已取消收藏", { icon: 1 });
removeCard(btn, type);
return;
}
layui.layer.msg(res.msg || "取消收藏失败", { icon: 2 });
}).catch(function (error) {
console.error("取消收藏失败:", error);
layui.layer.msg("网络异常,请稍后重试", { icon: 2 });
});
},
);
}
function getCancelRequest(type, id, accountType) {
if (type === "expert") {
const url = ApiClient.API && ApiClient.API.expertCollect
? ApiClient.API.expertCollect(id, accountType || 1)
: "/api/web/pay-article/expert-collect?" +
new URLSearchParams({
expertId: String(id || ""),
accountType: String(accountType || 1),
}).toString();
return ApiClient.post(url, {});
}
if (type === "paid") {
const url = ApiClient.API && ApiClient.API.payArticleCollect
? ApiClient.API.payArticleCollect(id)
: "/api/web/pay-article/article-collect/" + encodeURIComponent(id);
return ApiClient.post(url, {});
}
const url = ApiClient.API && ApiClient.API.freeArticleCollect
? ApiClient.API.freeArticleCollect(id)
: "/api/web/free-article/" + encodeURIComponent(id) + "/collect";
return ApiClient.post(url, {});
}
function removeCard(btn, type) {
const selector = type === "expert" ? ".sc-expert-card" : type === "paid" ? ".sc-paid-row" : ".cxz-collect-card";
const $card = $(btn).closest(selector);
$card.addClass("removing");
setTimeout(function () {
$card.remove();
const config = collectTabs[type];
const left = $(config.grid).find(selector).length;
const total = Math.max(0, Number($(config.stat).text()) - 1);
$(config.stat).text(total);
if (left === 0) loadCurrentTab();
}, 300);
}
function setPanelLoading(tab) {
const config = collectTabs[tab];
$(config.grid)
.empty()
.html('<div class="sc-loading">正在加载收藏数据...</div>');
$(config.empty).addClass("sc-hidden").hide();
$("#" + config.pager).empty().hide();
}
function showEmpty(tab, isEmpty) {
const config = collectTabs[tab];
if (isEmpty) {
$(config.grid).empty();
$(config.empty).removeClass("sc-hidden").css("display", "flex");
} else {
$(config.empty).addClass("sc-hidden").hide();
}
$("#" + config.pager).toggle(!isEmpty);
}
function renderPager(tab, total, curr, onJump) {
const config = collectTabs[tab];
const $pager = $("#" + config.pager);
if (!total || total <= pageSize) {
$pager.empty().hide();
return;
}
$pager.show();
layui.laypage.render({
elem: config.pager,
count: total,
limit: pageSize,
curr: curr,
layout: ["prev", "page", "next"],
jump: function (obj, first) {
if (!first) onJump(obj.curr);
},
});
}
function getArticleId(item) {
return getField(item, ["articleId", "payArticleId", "freeArticleId", "id", "collectArticleId"], "");
}
function buildFreeArticleHref(item, articleId) {
return safeHref(
getField(item, ["url", "link"], "") ||
CommonUtil.freeArticleHref(articleId),
);
}
function buildPayArticleHref(item) {
const expertId = getField(item, ["expertId", "createBy", "userId"], "");
const articleId = getArticleId(item);
const code = getField(item, ["code", "lotteryCode", "menuCode"], "");
const menuId = getField(item, ["menuId", "parentId", "lotteryId"], "");
const accountType = getField(item, ["accountType"], 1);
const params = new URLSearchParams();
if (expertId) params.set("expertId", expertId);
if (articleId) params.set("articleId", articleId);
if (code) params.set("code", code);
if (menuId) params.set("menuId", menuId);
params.set("accountType", accountType || 1);
return params.toString() ? siteHref("/caiinfo.html?" + params.toString()) : "#";
}
function buildExpertHref(item, expertId, accountType) {
const code = getField(item, ["code", "lotteryCode", "menuCode"], "");
const menuId = getField(item, ["menuId", "parentId", "lotteryId"], "");
const params = new URLSearchParams();
if (expertId) params.set("expertId", expertId);
if (code) params.set("code", code);
if (menuId) params.set("menuId", menuId);
params.set("accountType", accountType || 1);
return siteHref("/caiinfo.html?" + params.toString());
}
function siteHref(path) {
if (CommonUtil.siteHref) return CommonUtil.siteHref(path);
const cleanPath = String(path || "");
if (/^https?:\/\//i.test(cleanPath) || cleanPath.indexOf("../") === 0) return cleanPath;
return cleanPath.charAt(0) === "/" ? "." + cleanPath : cleanPath;
}
function safeHref(value) {
if (CommonUtil.safeHref) return CommonUtil.safeHref(value);
const href = String(value || "#").trim();
if (!href || /^javascript:/i.test(href)) return "#";
return href;
}
function getField(item, keys, fallback) {
for (let i = 0; i < keys.length; i += 1) {
const value = item && item[keys[i]];
if (value !== undefined && value !== null && value !== "") return value;
}
return fallback || "";
}
function formatTime(value) {
if (!value) return "";
if (typeof DateUtil !== "undefined" && DateUtil.formatDateTimeToYMDHM) {
return DateUtil.formatDateTimeToYMDHM(value);
}
return String(value).slice(0, 16);
}
function isTruthy(value) {
return value === true || value === 1 || value === "1" || String(value).toLowerCase() === "true";
}
function escapeHtml(value) {
return CommonUtil.escapeHtml(String(value == null ? "" : value));
}
function escapeAttr(value) {
return escapeHtml(value).replace(/"/g, "&quot;");
}
function escapeJs(value) {
return String(value == null ? "" : value).replace(/\\/g, "\\\\").replace(/'/g, "\\'");
}
$(function () {
currentTab = getInitialTab();
checkLogin();
CommonUtil.loadPageConfig();
renderNav();
applyActiveTab();
refreshCollectStats();
loadCurrentTab();
});
</script>
</body>
</html>