Common.js:修订间差异
来自卡厄思梦境WIKI
无编辑摘要 |
无编辑摘要 |
||
| 第303行: | 第303行: | ||
mw.hook('wikipage.content').add(function () { init(); }); | mw.hook('wikipage.content').add(function () { init(); }); | ||
} | } | ||
})(); | |||
/* 卡牌悬浮 */ | |||
(function() { | |||
'use strict'; | |||
// 配置 | |||
const CONFIG = { | |||
hoverDelay: 300, // 悬停延迟(ms) | |||
hideDelay: 200, // 隐藏延迟(ms) | |||
offsetX: 15, // 水平偏移 | |||
offsetY: 15, // 垂直偏移 | |||
cacheExpiry: 300000, // 缓存过期时间(5分钟) | |||
maxRetries: 2 // 最大重试次数 | |||
}; | |||
// 全局状态 | |||
let popupElement = null; | |||
let currentTrigger = null; | |||
let hoverTimer = null; | |||
let hideTimer = null; | |||
let cardCache = new Map(); | |||
function init() { | |||
if (popupElement) return; // 防止重复初始化 | |||
// 创建弹出容器 | |||
popupElement = document.createElement('div'); | |||
popupElement.className = 'card-hover-popup'; | |||
document.body.appendChild(popupElement); | |||
// 绑定事件 | |||
bindEvents(); | |||
console.log('[CardHover] 卡牌悬浮系统已初始化'); | |||
} | |||
function bindEvents() { | |||
// 使用事件委托 | |||
document.body.addEventListener('mouseenter', handleMouseEnter, true); | |||
document.body.addEventListener('mouseleave', handleMouseLeave, true); | |||
document.body.addEventListener('mousemove', handleMouseMove); | |||
// 窗口大小变化时隐藏弹出 | |||
window.addEventListener('resize', hidePopup); | |||
window.addEventListener('scroll', updatePopupPosition); | |||
} | |||
function handleMouseEnter(e) { | |||
const trigger = e.target.closest('.card-hover-trigger'); | |||
if (!trigger) return; | |||
// 清除之前的计时器 | |||
clearTimeout(hideTimer); | |||
// 设置当前触发器 | |||
currentTrigger = trigger; | |||
// 延迟显示 | |||
hoverTimer = setTimeout(() => { | |||
showPopup(trigger); | |||
}, CONFIG.hoverDelay); | |||
} | |||
function handleMouseLeave(e) { | |||
const trigger = e.target.closest('.card-hover-trigger'); | |||
if (!trigger) return; | |||
// 清除显示计时器 | |||
clearTimeout(hoverTimer); | |||
// 延迟隐藏 | |||
hideTimer = setTimeout(() => { | |||
if (currentTrigger === trigger) { | |||
hidePopup(); | |||
} | |||
}, CONFIG.hideDelay); | |||
} | |||
function handleMouseMove(e) { | |||
if (popupElement && popupElement.classList.contains('active')) { | |||
positionPopup(e.clientX, e.clientY); | |||
} | |||
} | |||
async function showPopup(trigger) { | |||
const moduleName = trigger.dataset.module; | |||
const cardName = trigger.dataset.card; | |||
const variantType = trigger.dataset.variantType || ''; | |||
const variantParam = trigger.dataset.variantParam || ''; | |||
const variantIndex = trigger.dataset.variantIndex || ''; | |||
if (!moduleName || !cardName) { | |||
console.warn('[CardHover] 缺少必要的卡牌信息', trigger); | |||
return; | |||
} | |||
// 构造缓存键 | |||
const cacheKey = `${moduleName}|${cardName}|${variantType}|${variantParam}|${variantIndex}`; | |||
// 检查缓存 | |||
const cached = cardCache.get(cacheKey); | |||
if (cached && (Date.now() - cached.timestamp < CONFIG.cacheExpiry)) { | |||
displayCard(cached.html); | |||
return; | |||
} | |||
// 显示加载状态 | |||
showLoading(); | |||
try { | |||
// 获取卡牌数据 | |||
const html = await fetchCardData(moduleName, cardName, variantType, variantParam, variantIndex); | |||
// 缓存结果 | |||
cardCache.set(cacheKey, { | |||
html: html, | |||
timestamp: Date.now() | |||
}); | |||
// 显示卡牌 | |||
displayCard(html); | |||
} catch (error) { | |||
console.error('[CardHover] 加载卡牌失败:', error); | |||
showError('无法加载卡牌信息'); | |||
} | |||
} | |||
async function fetchCardData(moduleName, cardName, variantType, variantParam, variantIndex) { | |||
const api = new mw.Api(); | |||
// 构造模板调用 | |||
let templateCall = `{{#invoke:卡牌|main|${moduleName}|${cardName}`; | |||
if (variantType) templateCall += `|${variantType}`; | |||
if (variantParam) templateCall += `|${variantParam}`; | |||
if (variantIndex) templateCall += `|${variantIndex}`; | |||
templateCall += '}}'; | |||
try { | |||
const response = await api.parse(templateCall, { | |||
contentmodel: 'wikitext', | |||
prop: 'text', | |||
disableeditsection: true, | |||
disablelimitreport: true, | |||
disabletoc: true | |||
}); | |||
if (response && response.text) { | |||
return response.text['*']; | |||
} | |||
throw new Error('Empty response'); | |||
} catch (error) { | |||
console.error('[CardHover] API调用失败:', error); | |||
throw error; | |||
} | |||
} | |||
function showLoading() { | |||
popupElement.innerHTML = '<div class="card-hover-loading">加载中</div>'; | |||
popupElement.classList.add('active'); | |||
updatePopupPosition(); | |||
} | |||
function showError(message) { | |||
popupElement.innerHTML = `<div class="card-hover-error">${mw.html.escape(message)}</div>`; | |||
popupElement.classList.add('active'); | |||
updatePopupPosition(); | |||
} | |||
function displayCard(html) { | |||
// 创建临时容器来解析HTML | |||
const temp = document.createElement('div'); | |||
temp.innerHTML = html; | |||
// 提取小卡牌(.card-small-wrapper) | |||
const smallCard = temp.querySelector('.card-small-wrapper'); | |||
if (smallCard) { | |||
// 移除点击事件(悬浮显示不需要模态框) | |||
smallCard.style.cursor = 'default'; | |||
smallCard.removeAttribute('data-card-id'); | |||
// 包装内容 | |||
const wrapper = document.createElement('div'); | |||
wrapper.className = 'card-hover-popup-content'; | |||
wrapper.appendChild(smallCard.cloneNode(true)); | |||
popupElement.innerHTML = ''; | |||
popupElement.appendChild(wrapper); | |||
popupElement.classList.add('active'); | |||
updatePopupPosition(); | |||
} else { | |||
showError('卡牌格式错误'); | |||
} | |||
} | |||
function hidePopup() { | |||
if (popupElement) { | |||
popupElement.classList.remove('active'); | |||
popupElement.innerHTML = ''; | |||
} | |||
currentTrigger = null; | |||
} | |||
function positionPopup(mouseX, mouseY) { | |||
if (!popupElement || !popupElement.classList.contains('active')) return; | |||
const popup = popupElement.firstElementChild; | |||
if (!popup) return; | |||
const rect = popup.getBoundingClientRect(); | |||
const viewportWidth = window.innerWidth; | |||
const viewportHeight = window.innerHeight; | |||
let left = mouseX + CONFIG.offsetX; | |||
let top = mouseY + CONFIG.offsetY; | |||
// 防止超出右边界 | |||
if (left + rect.width > viewportWidth - 10) { | |||
left = mouseX - rect.width - CONFIG.offsetX; | |||
} | |||
// 防止超出下边界 | |||
if (top + rect.height > viewportHeight - 10) { | |||
top = mouseY - rect.height - CONFIG.offsetY; | |||
} | |||
// 防止超出左边界 | |||
if (left < 10) { | |||
left = 10; | |||
} | |||
// 防止超出上边界 | |||
if (top < 10) { | |||
top = 10; | |||
} | |||
popupElement.style.left = left + 'px'; | |||
popupElement.style.top = top + 'px'; | |||
} | |||
function updatePopupPosition() { | |||
if (currentTrigger && popupElement.classList.contains('active')) { | |||
const rect = currentTrigger.getBoundingClientRect(); | |||
positionPopup(rect.left + rect.width / 2, rect.top + rect.height / 2); | |||
} | |||
} | |||
function cleanupCache() { | |||
const now = Date.now(); | |||
for (const [key, value] of cardCache.entries()) { | |||
if (now - value.timestamp > CONFIG.cacheExpiry) { | |||
cardCache.delete(key); | |||
} | |||
} | |||
} | |||
// 定期清理缓存 | |||
setInterval(cleanupCache, 60000); // 每分钟清理一次 | |||
// 页面加载完成后初始化 | |||
if (document.readyState === 'loading') { | |||
document.addEventListener('DOMContentLoaded', init); | |||
} else { | |||
init(); | |||
} | |||
})(); | })(); | ||
| 第476行: | 第746行: | ||
}); | }); | ||
} | } | ||
}); | }); | ||
2025年10月16日 (四) 18:00的版本
/* 这里的任何JavaScript将为所有用户在每次页面加载时加载。 */
/* 卡牌 */
(function () {
'use strict';
var initialized = false;
function show(el) {
if (el) el.style.display = 'flex';
}
function hide(el) {
if (el) el.style.display = 'none';
}
function resetModalView(modal) {
var originalView = modal.querySelector('.original-card-view');
var inspirationView = modal.querySelector('.inspiration-view');
var godView = modal.querySelector('.god-inspiration-view');
var subcardsView = modal.querySelector('.subcards-view');
var nestedSubcardsViews = modal.querySelectorAll('.nested-subcards-view');
var inspirationNestedViews = modal.querySelectorAll('.inspiration-subcards-view');
var inspirationDeeperNestedViews = modal.querySelectorAll('.inspiration-nested-subcards-view');
if (originalView) show(originalView);
if (inspirationView) hide(inspirationView);
if (godView) hide(godView);
if (subcardsView) hide(subcardsView);
nestedSubcardsViews.forEach(hide);
inspirationNestedViews.forEach(hide);
inspirationDeeperNestedViews.forEach(hide);
}
function closeModal(modal) {
modal.style.display = 'none';
document.body.style.overflow = 'auto';
resetModalView(modal);
}
function openModalFromCard(cardWrapper) {
var cardId = cardWrapper.getAttribute('data-card-id');
if (!cardId) return;
var modal = document.getElementById(cardId + '-modal');
if (!modal) return;
resetModalView(modal);
modal.style.display = 'block';
document.body.style.overflow = 'hidden';
}
function handleBackToCard(button) {
var modal = button.closest('.card-modal');
if (modal) {
resetModalView(modal);
}
}
function handleBackToSubcards(button) {
var modal = button.closest('.card-modal');
if (!modal) return;
var nestedView = button.closest('.nested-subcards-view');
var subcardsView = modal.querySelector('.subcards-view');
if (nestedView && subcardsView) {
hide(nestedView);
show(subcardsView);
}
}
function handleInspirationClick(button) {
var modal = button.closest('.card-modal');
if (!modal) return;
var originalView = modal.querySelector('.original-card-view');
var inspirationView = modal.querySelector('.inspiration-view');
if (!originalView || !inspirationView) return;
hide(originalView);
show(inspirationView);
}
function handleGodInspirationClick(button) {
var modal = button.closest('.card-modal');
if (!modal) return;
var originalView = modal.querySelector('.original-card-view');
var godView = modal.querySelector('.god-inspiration-view');
if (!originalView || !godView) return;
hide(originalView);
show(godView);
}
function handleSubcardsClick(button) {
var modal = button.closest('.card-modal');
if (!modal) return;
var originalView = modal.querySelector('.original-card-view');
var subcardsView = modal.querySelector('.subcards-view');
if (!originalView || !subcardsView) return;
hide(originalView);
show(subcardsView);
}
function handleViewNestedSubcardsClick(button) {
var modal = button.closest('.card-modal');
if (!modal) return;
var subcardIndex = button.getAttribute('data-subcard-index');
if (!subcardIndex) return;
var subcardsView = modal.querySelector('.subcards-view');
var nestedView = modal.querySelector('.nested-subcards-view[data-subcard-index="' + subcardIndex + '"]');
if (!subcardsView || !nestedView) return;
hide(subcardsView);
show(nestedView);
}
function handleInspirationSubcardsClick(button) {
var modal = button.closest('.card-modal');
if (!modal) return;
var idx = button.getAttribute('data-variant-index');
if (!idx) return;
var inspirationView = modal.querySelector('.inspiration-view');
var nestedView = modal.querySelector('.inspiration-subcards-view[data-variant-index="' + idx + '"]');
if (!inspirationView || !nestedView) return;
hide(inspirationView);
show(nestedView);
}
function handleBackToInspiration(button) {
var modal = button.closest('.card-modal');
if (!modal) return;
var nestedView = button.closest('.inspiration-subcards-view');
var inspirationView = modal.querySelector('.inspiration-view');
if (nestedView && inspirationView) {
hide(nestedView);
show(inspirationView);
}
}
function handleViewInspirationNestedSubcardsClick(button) {
var modal = button.closest('.card-modal');
if (!modal) return;
var vIdx = button.getAttribute('data-variant-index');
var sIdx = button.getAttribute('data-subcard-index');
if (!vIdx || !sIdx) return;
var subcardsView = modal.querySelector('.inspiration-subcards-view[data-variant-index="' + vIdx + '"]');
var deeperView = modal.querySelector('.inspiration-nested-subcards-view[data-variant-index="' + vIdx + '"][data-subcard-index="' + sIdx + '"]');
if (!subcardsView || !deeperView) return;
hide(subcardsView);
show(deeperView);
}
function handleBackToInspirationSubcards(button) {
var modal = button.closest('.card-modal');
if (!modal) return;
var deeperView = button.closest('.inspiration-nested-subcards-view');
var vIdx = button.getAttribute('data-variant-index');
var subcardsView = modal.querySelector('.inspiration-subcards-view[data-variant-index="' + vIdx + '"]');
if (deeperView && subcardsView) {
hide(deeperView);
show(subcardsView);
}
}
function onKeydown(event) {
if (event.key === 'Escape' || event.keyCode === 27) {
var modals = document.querySelectorAll('.card-modal');
modals.forEach(function (modal) {
if (modal.style.display === 'block') {
var inspirationView = modal.querySelector('.inspiration-view');
var godView = modal.querySelector('.god-inspiration-view');
var subcardsView = modal.querySelector('.subcards-view');
var nestedSubcardsViews = modal.querySelectorAll('.nested-subcards-view');
var inspirationNestedViews = modal.querySelectorAll('.inspiration-subcards-view');
var inspirationDeeperNestedViews = modal.querySelectorAll('.inspiration-nested-subcards-view');
var inNestedView = false;
nestedSubcardsViews.forEach(function (view) {
if (view.style.display !== 'none') {
inNestedView = true;
hide(view);
if (subcardsView) show(subcardsView);
}
});
var inInspirationNested = false;
inspirationNestedViews.forEach(function (view) {
if (view.style.display !== 'none') {
inInspirationNested = true;
hide(view);
if (inspirationView) show(inspirationView);
}
});
var inInspirationDeeperNested = false;
inspirationDeeperNestedViews.forEach(function (view) {
if (view.style.display !== 'none') {
inInspirationDeeperNested = true;
var vIdx = view.getAttribute('data-variant-index');
hide(view);
var parentSubcardsView = modal.querySelector('.inspiration-subcards-view[data-variant-index="' + vIdx + '"]');
if (parentSubcardsView) show(parentSubcardsView);
}
});
if (!inNestedView && !inInspirationNested && !inInspirationDeeperNested) {
if ((inspirationView && inspirationView.style.display !== 'none') ||
(subcardsView && subcardsView.style.display !== 'none') ||
(godView && godView.style.display !== 'none')) {
resetModalView(modal);
} else {
closeModal(modal);
}
}
}
});
}
}
function onDocumentClick(e) {
// 打开模态:点击卡片缩略图区域
var cardWrapper = e.target.closest('.card-small-wrapper');
if (cardWrapper) {
e.preventDefault();
e.stopPropagation();
openModalFromCard(cardWrapper);
return;
}
// 变体/衍生/返回/关闭
if (e.target.classList.contains('inspiration-button')) {
e.stopPropagation();
handleInspirationClick(e.target);
return;
}
if (e.target.classList.contains('god-inspiration-button')) {
e.stopPropagation();
handleGodInspirationClick(e.target);
return;
}
if (e.target.classList.contains('subcards-button')) {
e.stopPropagation();
handleSubcardsClick(e.target);
return;
}
if (e.target.classList.contains('view-nested-subcards-button')) {
e.stopPropagation();
handleViewNestedSubcardsClick(e.target);
return;
}
if (e.target.classList.contains('back-to-card-button')) {
e.stopPropagation();
handleBackToCard(e.target);
return;
}
if (e.target.classList.contains('back-to-subcards-button')) {
e.stopPropagation();
handleBackToSubcards(e.target);
return;
}
if (e.target.classList.contains('view-insp-subcards-button')) {
e.stopPropagation();
handleInspirationSubcardsClick(e.target);
return;
}
if (e.target.classList.contains('back-to-inspiration-button')) {
e.stopPropagation();
handleBackToInspiration(e.target);
return;
}
if (e.target.classList.contains('view-insp-nested-subcards-button')) {
e.stopPropagation();
handleViewInspirationNestedSubcardsClick(e.target);
return;
}
if (e.target.classList.contains('back-to-insp-subcards-button')) {
e.stopPropagation();
handleBackToInspirationSubcards(e.target);
return;
}
// 关闭按钮
var closeBtn = e.target.classList.contains('modal-close-button')
? e.target
: (e.target.parentElement && e.target.parentElement.classList.contains('modal-close-button') ? e.target.parentElement : null);
if (closeBtn) {
var modal = closeBtn.closest('.card-modal');
if (modal) closeModal(modal);
}
}
function init() {
if (initialized) return;
initialized = true;
document.addEventListener('click', onDocumentClick);
document.addEventListener('keydown', onKeydown);
}
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', init);
} else {
init();
}
if (typeof mw !== 'undefined' && mw.hook) {
// 页面增量渲染时确保已初始化(事件委托只需一次)
mw.hook('wikipage.content').add(function () { init(); });
}
})();
/* 卡牌悬浮 */
(function() {
'use strict';
// 配置
const CONFIG = {
hoverDelay: 300, // 悬停延迟(ms)
hideDelay: 200, // 隐藏延迟(ms)
offsetX: 15, // 水平偏移
offsetY: 15, // 垂直偏移
cacheExpiry: 300000, // 缓存过期时间(5分钟)
maxRetries: 2 // 最大重试次数
};
// 全局状态
let popupElement = null;
let currentTrigger = null;
let hoverTimer = null;
let hideTimer = null;
let cardCache = new Map();
function init() {
if (popupElement) return; // 防止重复初始化
// 创建弹出容器
popupElement = document.createElement('div');
popupElement.className = 'card-hover-popup';
document.body.appendChild(popupElement);
// 绑定事件
bindEvents();
console.log('[CardHover] 卡牌悬浮系统已初始化');
}
function bindEvents() {
// 使用事件委托
document.body.addEventListener('mouseenter', handleMouseEnter, true);
document.body.addEventListener('mouseleave', handleMouseLeave, true);
document.body.addEventListener('mousemove', handleMouseMove);
// 窗口大小变化时隐藏弹出
window.addEventListener('resize', hidePopup);
window.addEventListener('scroll', updatePopupPosition);
}
function handleMouseEnter(e) {
const trigger = e.target.closest('.card-hover-trigger');
if (!trigger) return;
// 清除之前的计时器
clearTimeout(hideTimer);
// 设置当前触发器
currentTrigger = trigger;
// 延迟显示
hoverTimer = setTimeout(() => {
showPopup(trigger);
}, CONFIG.hoverDelay);
}
function handleMouseLeave(e) {
const trigger = e.target.closest('.card-hover-trigger');
if (!trigger) return;
// 清除显示计时器
clearTimeout(hoverTimer);
// 延迟隐藏
hideTimer = setTimeout(() => {
if (currentTrigger === trigger) {
hidePopup();
}
}, CONFIG.hideDelay);
}
function handleMouseMove(e) {
if (popupElement && popupElement.classList.contains('active')) {
positionPopup(e.clientX, e.clientY);
}
}
async function showPopup(trigger) {
const moduleName = trigger.dataset.module;
const cardName = trigger.dataset.card;
const variantType = trigger.dataset.variantType || '';
const variantParam = trigger.dataset.variantParam || '';
const variantIndex = trigger.dataset.variantIndex || '';
if (!moduleName || !cardName) {
console.warn('[CardHover] 缺少必要的卡牌信息', trigger);
return;
}
// 构造缓存键
const cacheKey = `${moduleName}|${cardName}|${variantType}|${variantParam}|${variantIndex}`;
// 检查缓存
const cached = cardCache.get(cacheKey);
if (cached && (Date.now() - cached.timestamp < CONFIG.cacheExpiry)) {
displayCard(cached.html);
return;
}
// 显示加载状态
showLoading();
try {
// 获取卡牌数据
const html = await fetchCardData(moduleName, cardName, variantType, variantParam, variantIndex);
// 缓存结果
cardCache.set(cacheKey, {
html: html,
timestamp: Date.now()
});
// 显示卡牌
displayCard(html);
} catch (error) {
console.error('[CardHover] 加载卡牌失败:', error);
showError('无法加载卡牌信息');
}
}
async function fetchCardData(moduleName, cardName, variantType, variantParam, variantIndex) {
const api = new mw.Api();
// 构造模板调用
let templateCall = `{{#invoke:卡牌|main|${moduleName}|${cardName}`;
if (variantType) templateCall += `|${variantType}`;
if (variantParam) templateCall += `|${variantParam}`;
if (variantIndex) templateCall += `|${variantIndex}`;
templateCall += '}}';
try {
const response = await api.parse(templateCall, {
contentmodel: 'wikitext',
prop: 'text',
disableeditsection: true,
disablelimitreport: true,
disabletoc: true
});
if (response && response.text) {
return response.text['*'];
}
throw new Error('Empty response');
} catch (error) {
console.error('[CardHover] API调用失败:', error);
throw error;
}
}
function showLoading() {
popupElement.innerHTML = '<div class="card-hover-loading">加载中</div>';
popupElement.classList.add('active');
updatePopupPosition();
}
function showError(message) {
popupElement.innerHTML = `<div class="card-hover-error">${mw.html.escape(message)}</div>`;
popupElement.classList.add('active');
updatePopupPosition();
}
function displayCard(html) {
// 创建临时容器来解析HTML
const temp = document.createElement('div');
temp.innerHTML = html;
// 提取小卡牌(.card-small-wrapper)
const smallCard = temp.querySelector('.card-small-wrapper');
if (smallCard) {
// 移除点击事件(悬浮显示不需要模态框)
smallCard.style.cursor = 'default';
smallCard.removeAttribute('data-card-id');
// 包装内容
const wrapper = document.createElement('div');
wrapper.className = 'card-hover-popup-content';
wrapper.appendChild(smallCard.cloneNode(true));
popupElement.innerHTML = '';
popupElement.appendChild(wrapper);
popupElement.classList.add('active');
updatePopupPosition();
} else {
showError('卡牌格式错误');
}
}
function hidePopup() {
if (popupElement) {
popupElement.classList.remove('active');
popupElement.innerHTML = '';
}
currentTrigger = null;
}
function positionPopup(mouseX, mouseY) {
if (!popupElement || !popupElement.classList.contains('active')) return;
const popup = popupElement.firstElementChild;
if (!popup) return;
const rect = popup.getBoundingClientRect();
const viewportWidth = window.innerWidth;
const viewportHeight = window.innerHeight;
let left = mouseX + CONFIG.offsetX;
let top = mouseY + CONFIG.offsetY;
// 防止超出右边界
if (left + rect.width > viewportWidth - 10) {
left = mouseX - rect.width - CONFIG.offsetX;
}
// 防止超出下边界
if (top + rect.height > viewportHeight - 10) {
top = mouseY - rect.height - CONFIG.offsetY;
}
// 防止超出左边界
if (left < 10) {
left = 10;
}
// 防止超出上边界
if (top < 10) {
top = 10;
}
popupElement.style.left = left + 'px';
popupElement.style.top = top + 'px';
}
function updatePopupPosition() {
if (currentTrigger && popupElement.classList.contains('active')) {
const rect = currentTrigger.getBoundingClientRect();
positionPopup(rect.left + rect.width / 2, rect.top + rect.height / 2);
}
}
function cleanupCache() {
const now = Date.now();
for (const [key, value] of cardCache.entries()) {
if (now - value.timestamp > CONFIG.cacheExpiry) {
cardCache.delete(key);
}
}
}
// 定期清理缓存
setInterval(cleanupCache, 60000); // 每分钟清理一次
// 页面加载完成后初始化
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', init);
} else {
init();
}
})();
/* 切换标签 */
(function() {
'use strict';
function initTabSwitcher() {
var tabContainers = document.querySelectorAll('.resp-tabs');
tabContainers.forEach(function(container) {
var tabButtons = container.querySelectorAll('.czn-list-style');
if (tabButtons.length === 0) return;
var tabContents = container.querySelectorAll('.resp-tab-content');
// 初始化
tabButtons.forEach(function(button, index) {
button.classList.toggle('active', index === 0);
});
if (tabContents.length > 0) {
tabContents.forEach(function(content, index) {
content.style.display = index === 0 ? 'block' : 'none';
});
}
// 点击事件
tabButtons.forEach(function(button, index) {
button.addEventListener('click', function(e) {
e.preventDefault();
// 更新标签状态
tabButtons.forEach(function(btn, i) {
btn.classList.toggle('active', i === index);
});
// 切换内容
if (tabContents.length > 0) {
tabContents.forEach(function(content, i) {
content.style.display = i === index ? 'block' : 'none';
});
}
});
});
});
}
// 初始化
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', initTabSwitcher);
} else {
initTabSwitcher();
}
})();
/* 角色立绘切换 */
$(document).ready(function() {
// 使用事件委托来确保动态内容也能响应
$(document).on('click', '.character-switch-btn', function() {
// 如果点击的是已经激活的按钮,不执行任何操作
if ($(this).hasClass('active')) {
return;
}
var targetType = $(this).attr('data-target');
var container = $(this).closest('#character-container');
var imageWrapper = container.find('.character-image-wrapper');
var allImages = imageWrapper.find('.character-image');
// 先将所有图片淡出
allImages.each(function() {
$(this).css('opacity', '0');
});
// 延迟后切换显示状态
setTimeout(function() {
allImages.each(function() {
$(this).css('display', 'none');
});
// 显示目标图片
imageWrapper.find('[data-image-type="' + targetType + '"]').each(function() {
$(this).css('display', 'block');
// 强制重绘
$(this)[0].offsetHeight;
$(this).css('opacity', '1');
});
}, 300);
// 更新按钮样式
container.find('.character-switch-btn').each(function() {
$(this).removeClass('active');
$(this).css({
'background': 'rgba(0,0,0,0.5)',
'cursor': 'pointer'
});
});
// 设置当前按钮为激活状态
$(this).addClass('active');
$(this).css({
'background': 'rgba(70, 130, 255, 0.8)',
'cursor': 'default'
});
});
// 鼠标悬停效果(仅对非激活按钮有效)
$(document).on('mouseenter', '.character-switch-btn:not(.active)', function() {
$(this).css('background', 'rgba(70, 130, 255, 0.5)');
});
$(document).on('mouseleave', '.character-switch-btn:not(.active)', function() {
$(this).css('background', 'rgba(0,0,0,0.5)');
});
});
/* 悬浮目录 */
$(document).ready(function() {
// 只在有目录的页面上执行
if ($('.toc').length > 0) {
// 创建侧边目录
var $sidebar = $('<div id="toc-sidebar"><div id="toc-sidebar-trigger">展开目录</div><div id="toc-sidebar-content"></div></div>');
$('body').append($sidebar);
// 提取并修复目录内容
var tocUl = $('<ul></ul>');
// 避免重复的目录项
var processedItems = new Set();
// 从原始目录构建新目录
$('.toc ul li').each(function() {
var $link = $(this).find('a').first();
var href = $link.attr('href');
var $number = $link.find('.tocnumber').first();
var $text = $link.find('.toctext').first();
// 创建唯一标识符,避免重复添加
var itemId = $number.text() + '-' + $text.text();
if (!processedItems.has(itemId)) {
processedItems.add(itemId);
// 创建新的目录项
var $li = $('<li></li>');
var $newLink = $('').attr('href', href);
// 如果有编号,则添加编号
if ($number.length) {
$newLink.append($('<span class="tocnumber"></span>').text($number.text()));
$newLink.append(' ');
}
$newLink.append($('<span class="toctext"></span>').text($text.text()));
$li.append($newLink);
tocUl.append($li);
}
});
$('#toc-sidebar-content').append(tocUl);
// 点击展开/折叠事件处理
$('#toc-sidebar-trigger').click(function() {
$('#toc-sidebar').toggleClass('expanded');
// 根据展开/折叠状态更改按钮文字
if ($('#toc-sidebar').hasClass('expanded')) {
$('#toc-sidebar-trigger').text('隐藏目录');
} else {
$('#toc-sidebar-trigger').text('展开目录');
}
});
}
});
/* 词典Tooltip */
(function() {
var tooltipContainer = null;
var currentTerm = null;
var hideTimeout = null;
// 初始化
$(function() {
// 创建全局 tooltip 容器
tooltipContainer = $('<div class="dictionary-tooltip-container"></div>');
$('body').append(tooltipContainer);
// 鼠标进入词条
$(document).on('mouseenter', '.dictionary-term', function(e) {
var $term = $(this);
currentTerm = $term;
// 清除隐藏定时器
if (hideTimeout) {
clearTimeout(hideTimeout);
hideTimeout = null;
}
// 获取 tooltip 内容
var content = $term.attr('data-tooltip-content');
if (!content) return;
// 设置内容
tooltipContainer.html(content);
// 计算位置
updateTooltipPosition($term);
// 显示 tooltip
tooltipContainer.addClass('active');
});
// 鼠标离开词条
$(document).on('mouseleave', '.dictionary-term', function() {
hideTimeout = setTimeout(function() {
tooltipContainer.removeClass('active');
currentTerm = null;
}, 100);
});
// 鼠标进入 tooltip(防止快速隐藏)
tooltipContainer.on('mouseenter', function() {
if (hideTimeout) {
clearTimeout(hideTimeout);
hideTimeout = null;
}
});
// 鼠标离开 tooltip
tooltipContainer.on('mouseleave', function() {
hideTimeout = setTimeout(function() {
tooltipContainer.removeClass('active');
currentTerm = null;
}, 100);
});
// 窗口滚动时更新位置
$(window).on('scroll resize', function() {
if (currentTerm && tooltipContainer.hasClass('active')) {
updateTooltipPosition(currentTerm);
}
});
});
// 更新 tooltip 位置
function updateTooltipPosition($term) {
var rect = $term[0].getBoundingClientRect();
var scrollTop = $(window).scrollTop();
var scrollLeft = $(window).scrollLeft();
// 基本位置:词条下方
var top = rect.bottom + scrollTop + 2;
var left = rect.left + scrollLeft;
// 获取 tooltip 尺寸
var tooltipWidth = 250; // 固定宽度
var tooltipHeight = tooltipContainer.outerHeight();
// 获取窗口尺寸
var windowWidth = $(window).width();
var windowHeight = $(window).height();
// 检查右边界
if (left + tooltipWidth > windowWidth + scrollLeft) {
left = Math.max(scrollLeft, rect.right + scrollLeft - tooltipWidth);
}
// 检查下边界
if (rect.bottom + tooltipHeight > windowHeight) {
// 如果下方空间不足,显示在上方
if (rect.top - tooltipHeight > 0) {
top = rect.top + scrollTop - tooltipHeight - 2;
}
}
// 设置位置
tooltipContainer.css({
top: top + 'px',
left: left + 'px'
});
}
})();
/* 轮播图 */
(function () {
'use strict';
function initCarousel(root) {
if (!root || root.__inited) return;
root.__inited = true;
const wrapper = root.querySelector('.carousel-wrapper');
const slides = Array.from(root.querySelectorAll('.carousel-slide'));
const titles = Array.from(root.querySelectorAll('.carousel-title-item'));
const btnPrev = root.querySelector('.carousel-prev');
const btnNext = root.querySelector('.carousel-next');
if (!wrapper || !slides.length) {
root.style.display = 'none';
return;
}
// 单张图隐藏按钮
if (slides.length === 1) {
root.classList.add('single');
}
// 标题索引与幻灯索引对齐
titles.forEach((t, i) => {
t.dataset.slide = String(i);
});
let index = 0;
let timer = null;
const autoplay = (root.getAttribute('data-autoplay') || '1') !== '0';
const interval = Math.max(1500, parseInt(root.getAttribute('data-interval') || '4000', 10) || 4000);
function update() {
wrapper.style.transform = 'translateX(' + (-index * 100) + '%)';
titles.forEach((t, i) => {
if (i === index) t.classList.add('active');
else t.classList.remove('active');
const text = t.querySelector('.title-text');
const ind = t.querySelector('.title-indicator');
if (text) text.style.opacity = i === index ? '1' : '0.75';
if (ind) ind.style.background = i === index ? '#ff6600' : 'transparent';
});
}
function next() {
index = (index + 1) % slides.length;
update();
}
function prev() {
index = (index - 1 + slides.length) % slides.length;
update();
}
// 标题点击
titles.forEach((t) => {
t.addEventListener('click', function () {
const i = parseInt(t.dataset.slide || '0', 10) || 0;
index = Math.min(Math.max(i, 0), slides.length - 1);
update();
restartAutoplay();
});
});
// 按钮点击
if (btnPrev) {
btnPrev.addEventListener('click', function () {
prev();
restartAutoplay();
});
}
if (btnNext) {
btnNext.addEventListener('click', function () {
next();
restartAutoplay();
});
}
// 自动播放控制
function startAutoplay() {
if (!autoplay || slides.length <= 1) return;
stopAutoplay();
timer = setInterval(next, interval);
}
function stopAutoplay() {
if (timer) {
clearInterval(timer);
timer = null;
}
}
function restartAutoplay() {
stopAutoplay();
startAutoplay();
}
// 悬停/聚焦暂停
root.addEventListener('mouseenter', stopAutoplay);
root.addEventListener('mouseleave', startAutoplay);
root.addEventListener('focusin', stopAutoplay);
root.addEventListener('focusout', startAutoplay);
// 触摸滑动
let startX = 0;
let curX = 0;
let dragging = false;
const threshold = 30;
root.addEventListener(
'touchstart',
function (e) {
if (!e.touches || !e.touches.length) return;
startX = e.touches[0].clientX;
curX = startX;
dragging = true;
root.classList.add('grabbing');
stopAutoplay();
},
{ passive: true }
);
root.addEventListener(
'touchmove',
function (e) {
if (!dragging || !e.touches || !e.touches.length) return;
curX = e.touches[0].clientX;
const dx = curX - startX;
wrapper.style.transform = 'translateX(calc(' + (-index * 100) + '% + ' + dx + 'px))';
},
{ passive: true }
);
root.addEventListener('touchend', function () {
if (!dragging) return;
const dx = curX - startX;
root.classList.remove('grabbing');
dragging = false;
if (Math.abs(dx) > threshold) {
if (dx < 0) next();
else prev();
} else {
update();
}
startAutoplay();
});
// 鼠标拖动(可选)
let mouseDown = false;
let mStartX = 0;
root.addEventListener('mousedown', function (e) {
mouseDown = true;
mStartX = e.clientX;
root.classList.add('grab');
stopAutoplay();
});
window.addEventListener('mouseup', function (e) {
if (!mouseDown) return;
const dx = e.clientX - mStartX;
mouseDown = false;
root.classList.remove('grab');
if (Math.abs(dx) > threshold) {
if (dx < 0) next();
else prev();
} else {
update();
}
startAutoplay();
});
// 初始化
update();
startAutoplay();
} // <- 确保此处仅关闭 initCarousel 函数
function initAll(context) {
const scope = context && context.querySelectorAll ? context : document;
scope.querySelectorAll('.carousel-container').forEach(initCarousel);
}
// 初次加载
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', function () {
initAll();
});
} else {
initAll();
}
// 动态内容(如可视化编辑器或AJAX)再次初始化
if (window.mw && mw.hook) {
mw.hook('wikipage.content').add(function (content) {
// content 可能是 jQuery 对象,也可能是原生节点
if (content && content[0] && content.find) {
// jQuery 对象
content.find('.carousel-container').each(function () {
initCarousel(this);
});
} else if (content && content.querySelectorAll) {
// 原生节点
initAll(content);
} else {
initAll();
}
});
}
})();