tianya-shentie/app.js
刘华勇 96120711dc feat: 天涯神贴经典合集静态网站(32篇)
- 收录 32 篇经典神贴,覆盖经济金融/莲蓬鬼话/人文历史/小说连载/社会成长/玄学命理
- 支持搜索、板块筛选、热度/年代排序、详情弹窗、本地收藏
- 怀旧宣纸风设计(宋体 + 朱红印章),响应式布局
- 纯静态,无依赖,浏览器直接打开即可
- 含 README 与数据扩充说明
2026-06-26 15:56:00 +08:00

338 lines
10 KiB
JavaScript
Raw Permalink 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.

/**
* 天涯神贴 · 静态网站交互逻辑
* 依赖:data.js 中提供的 window.SHEN_TIE
*/
(function () {
'use strict';
var DATA = window.SHEN_TIE || [];
var STORAGE_KEY = 'tianya-favs';
/* ---------- 工具函数 ---------- */
function $(sel, ctx) {
return (ctx || document).querySelector(sel);
}
function $all(sel, ctx) {
return Array.prototype.slice.call((ctx || document).querySelectorAll(sel));
}
function escapeHtml(str) {
return String(str)
.replace(/&/g, '&')
.replace(/</g, '&lt;')
.replace(/>/g, '&gt;')
.replace(/"/g, '&quot;')
.replace(/'/g, '&#39;');
}
function loadFavs() {
try {
return JSON.parse(localStorage.getItem(STORAGE_KEY)) || [];
} catch (e) {
return [];
}
}
function saveFavs(list) {
try {
localStorage.setItem(STORAGE_KEY, JSON.stringify(list));
} catch (e) {
/* 隐私模式忽略 */
}
}
/* ---------- 全局状态 ---------- */
var state = {
section: '全部',
keyword: '',
sort: 'hot', // hot | year-asc | year-desc
favs: loadFavs(),
onlyFav: false,
};
/* ---------- 派生数据 ---------- */
function sections() {
var set = {};
DATA.forEach(function (t) {
set[t.section] = true;
});
return ['全部'].concat(Object.keys(set));
}
function filtered() {
var kw = state.keyword.trim().toLowerCase();
var list = DATA.filter(function (t) {
var okSection = state.section === '全部' || t.section === state.section;
var okFav = !state.onlyFav || state.favs.indexOf(t.id) > -1;
var okKw = true;
if (kw) {
var hay = [t.title, t.author, t.authorAlias, t.summary, t.section]
.concat(t.tags || [])
.join(' ')
.toLowerCase();
okKw = hay.indexOf(kw) > -1;
}
return okSection && okFav && okKw;
});
if (state.sort === 'hot') {
list.sort(function (a, b) {
return b.hot - a.hot;
});
} else if (state.sort === 'year-asc') {
list.sort(function (a, b) {
return a.year - b.year;
});
} else if (state.sort === 'year-desc') {
list.sort(function (a, b) {
return b.year - a.year;
});
}
return list;
}
/* ---------- 渲染:分类标签 ---------- */
function renderSections() {
var box = $('#sections');
box.innerHTML = sections()
.map(function (s) {
var active = s === state.section ? ' active' : '';
return (
'<button class="tag' + active + '" data-section="' + escapeHtml(s) + '">' +
escapeHtml(s) + '</button>'
);
})
.join('');
}
/* ---------- 渲染:卡片列表 ---------- */
function renderCards() {
var box = $('#cards');
var list = filtered();
if (!list.length) {
box.innerHTML =
'<div class="empty">没有找到相关神贴,换个关键词或分类试试。</div>';
$('#count').textContent = '0 篇';
return;
}
var html = list
.map(function (t) {
var isFav = state.favs.indexOf(t.id) > -1;
var tagsHtml = (t.tags || [])
.map(function (tag) {
return '<span class="chip">#' + escapeHtml(tag) + '</span>';
})
.join('');
var verifyBadge = t.verified
? '<span class="verified" title="网友整理认为预测大体应验">已应验</span>'
: '';
return (
'<article class="card" data-id="' + escapeHtml(t.id) + '">' +
'<div class="card-head">' +
'<span class="section-badge">' + escapeHtml(t.section) + '</span>' +
'<span class="year">' + escapeHtml(String(t.year)) + '</span>' +
'</div>' +
'<h3 class="title">' + escapeHtml(t.title) + '</h3>' +
'<div class="author">' +
'<span class="avatar">' + escapeHtml((t.authorAlias || t.author).slice(0, 1)) + '</span>' +
'<span class="name">' + escapeHtml(t.authorAlias || t.author) + '</span>' +
'<span class="dim">@' + escapeHtml(t.author) + '</span>' +
verifyBadge +
'</div>' +
'<p class="summary">' + escapeHtml(t.summary) + '</p>' +
'<div class="tagline">' + tagsHtml + '</div>' +
'<div class="card-foot">' +
'<span class="hot" title="热度">🔥 ' + escapeHtml(String(t.hot)) + '</span>' +
'<button class="fav-btn ' + (isFav ? 'on' : '') + '" data-fav="' + escapeHtml(t.id) + '">' +
(isFav ? '★ 已收藏' : '☆ 收藏') +
'</button>' +
'<span class="read-more">展开阅读 →</span>' +
'</div>' +
'</article>'
);
})
.join('');
box.innerHTML = html;
$('#count').textContent = list.length + ' 篇';
}
/* ---------- 渲染:详情弹窗 ---------- */
function openDetail(id) {
var t = DATA.filter(function (x) {
return x.id === id;
})[0];
if (!t) return;
var paras = (t.content || [])
.map(function (p) {
return '<p>' + escapeHtml(p) + '</p>';
})
.join('');
var tagsHtml = (t.tags || [])
.map(function (tag) {
return '<span class="chip">#' + escapeHtml(tag) + '</span>';
})
.join('');
var isFav = state.favs.indexOf(t.id) > -1;
var html =
'<div class="modal-mask" id="modalMask">' +
'<div class="modal" role="dialog" aria-modal="true">' +
'<button class="modal-close" id="modalClose" aria-label="关闭">×</button>' +
'<div class="modal-head">' +
'<span class="section-badge">' + escapeHtml(t.section) + '</span>' +
'<span class="year">' + escapeHtml(String(t.year)) + '</span>' +
(t.verified ? '<span class="verified">已应验</span>' : '') +
'</div>' +
'<h2 class="modal-title">' + escapeHtml(t.title) + '</h2>' +
'<div class="author">' +
'<span class="avatar lg">' + escapeHtml((t.authorAlias || t.author).slice(0, 1)) + '</span>' +
'<span class="name">' + escapeHtml(t.authorAlias || t.author) + '</span>' +
'<span class="dim">@' + escapeHtml(t.author) + '</span>' +
'<span class="hot">🔥 ' + escapeHtml(String(t.hot)) + '</span>' +
'</div>' +
'<p class="modal-summary">' + escapeHtml(t.summary) + '</p>' +
'<div class="tagline">' + tagsHtml + '</div>' +
'<div class="quotes">' + paras + '</div>' +
'<div class="modal-foot">' +
'<button class="fav-btn ' + (isFav ? 'on' : '') + '" data-fav="' + escapeHtml(t.id) + '">' +
(isFav ? '★ 已收藏' : '☆ 收藏') +
'</button>' +
'<a class="source-link" href="' + escapeHtml(t.source || '#') + '" target="_blank" rel="noopener noreferrer">' +
'查看原始出处 ↗' +
'</a>' +
'</div>' +
'</div>' +
'</div>';
var old = $('#modalMask');
if (old) old.remove();
document.body.insertAdjacentHTML('beforeend', html);
document.body.style.overflow = 'hidden';
}
function closeDetail() {
var mask = $('#modalMask');
if (mask) mask.remove();
document.body.style.overflow = '';
}
/* ---------- 收藏切换 ---------- */
function toggleFav(id) {
var idx = state.favs.indexOf(id);
if (idx > -1) {
state.favs.splice(idx, 1);
} else {
state.favs.push(id);
}
saveFavs(state.favs);
updateFavCount();
renderCards();
// 弹窗内的按钮也同步
var modalBtn = $('#modalMask .fav-btn');
if (modalBtn) {
var on = state.favs.indexOf(id) > -1;
modalBtn.classList.toggle('on', on);
modalBtn.textContent = on ? '★ 已收藏' : '☆ 收藏';
}
}
function updateFavCount() {
var el = $('#favCount');
if (el) el.textContent = state.favs.length;
}
/* ---------- 事件绑定 ---------- */
function bindEvents() {
// 分类
$('#sections').addEventListener('click', function (e) {
var btn = e.target.closest('[data-section]');
if (!btn) return;
state.section = btn.getAttribute('data-section');
renderSections();
renderCards();
});
// 搜索
var searchInput = $('#search');
var timer = null;
searchInput.addEventListener('input', function (e) {
clearTimeout(timer);
var val = e.target.value;
timer = setTimeout(function () {
state.keyword = val;
renderCards();
}, 150);
});
// 排序
$('#sort').addEventListener('change', function (e) {
state.sort = e.target.value;
renderCards();
});
// 只看收藏
$('#onlyFav').addEventListener('click', function (e) {
state.onlyFav = !state.onlyFav;
e.currentTarget.classList.toggle('on', state.onlyFav);
renderCards();
});
// 卡片区域:点击卡片展开 / 收藏
$('#cards').addEventListener('click', function (e) {
var favBtn = e.target.closest('[data-fav]');
if (favBtn) {
e.stopPropagation();
toggleFav(favBtn.getAttribute('data-fav'));
return;
}
var card = e.target.closest('.card');
if (card) {
openDetail(card.getAttribute('data-id'));
}
});
// 弹窗关闭(事件委托,因弹窗是后插入的)
document.addEventListener('click', function (e) {
if (e.target.id === 'modalMask' || e.target.id === 'modalClose') {
closeDetail();
return;
}
var favBtn = e.target.closest('#modalMask .fav-btn');
if (favBtn) {
toggleFav(favBtn.getAttribute('data-fav'));
}
});
// ESC 关闭弹窗
document.addEventListener('keydown', function (e) {
if (e.key === 'Escape') closeDetail();
});
// 返回顶部
$('#backTop').addEventListener('click', function () {
window.scrollTo({ top: 0, behavior: 'smooth' });
});
window.addEventListener('scroll', function () {
var btn = $('#backTop');
btn.classList.toggle('show', window.scrollY > 400);
});
}
/* ---------- 启动 ---------- */
function init() {
renderSections();
renderCards();
updateFavCount();
bindEvents();
}
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', init);
} else {
init();
}
})();