MediaWiki:Common.js:修订间差异
MediaWiki界面页面
更多操作
无编辑摘要 |
无编辑摘要 |
||
| (未显示同一用户的7个中间版本) | |||
| 第1行: | 第1行: | ||
/* | /* v3.1 - 植物大战僵尸杂交版Wiki */ | ||
/* 这里的任何JavaScript将为所有用户在每次页面加载时加载。 */ | /* 这里的任何JavaScript将为所有用户在每次页面加载时加载。 */ | ||
| 第83行: | 第83行: | ||
}); | }); | ||
})(); | })(); | ||
// ==================== 自定义光标开关 ==================== | // ==================== 自定义光标开关 ==================== | ||
| 第256行: | 第233行: | ||
function getTitleByEditcount(ec) { | function getTitleByEditcount(ec) { | ||
if (ec >= 10000) return ' | if (ec >= 10000) return '神话'; | ||
if (ec >= 5000) return ' | if (ec >= 5000) return '传奇'; | ||
if (ec >= 2500) return ' | if (ec >= 2500) return '大师'; | ||
if (ec >= 1000) return ' | if (ec >= 1000) return '专家'; | ||
if (ec >= 500) return ' | if (ec >= 500) return '资深'; | ||
if (ec >= 100) return ' | if (ec >= 100) return '活跃'; | ||
if (ec >= 50) return ' | if (ec >= 50) return '入门'; | ||
if (ec >= 10) return ' | if (ec >= 10) return '新手'; | ||
return ' | return '萌新'; | ||
} | } | ||
| 第1,301行: | 第1,278行: | ||
var $content = $('<div>').css({'text-align':'left','font-size':'14px','line-height':'1.8'}).html( | var $content = $('<div>').css({'text-align':'left','font-size':'14px','line-height':'1.8'}).html( | ||
'<div style="text-align:center;font-size:18px;font-weight:bold;margin-bottom:5px;"> | '<div style="text-align:center;font-size:18px;font-weight:bold;margin-bottom:5px;">【植物大战僵尸杂交版0.25.5版本更新公告】</div>' + | ||
'<div style="text-align:center;font-size:12px;color:#888;margin-bottom:15px;"> | '<div style="text-align:center;font-size:12px;color:#888;margin-bottom:15px;">植物大战僵尸杂交版(重制版)0.25.5</div>' + | ||
'<a href="https://www.bilibili.com/opus/1226688839230160904?spm_id_from=333.1365.0.0" target="_blank"> | '<a href="https://www.bilibili.com/opus/1226688839230160904?spm_id_from=333.1365.0.0" target="_blank">【杂交版0.25.5版本更新】</a><br>' + | ||
'电脑(Windows)版链接:https://pan.quark.cn/s/ | '电脑(Windows)版链接:https://pan.quark.cn/s/4259e472de02<br>' + | ||
'其他版本:https://pan.quark.cn/s/ | '其他版本:https://pan.quark.cn/s/a03f4fffc55b<br><br>' | ||
); | ); | ||
| 第1,406行: | 第1,341行: | ||
if (mw.config.get('wgUserName')) { | if (mw.config.get('wgUserName')) { | ||
var musicKey = 'wiki_bgm_enabled'; | var musicKey = 'wiki_bgm_enabled'; | ||
var musicEnabled = localStorage.getItem(musicKey) | var musicEnabled = localStorage.getItem(musicKey) === 'true'; | ||
var bgmAudio = null; | |||
function insertMusicSwitch() { | function insertMusicSwitch() { | ||
| 第1,418行: | 第1,354行: | ||
'<span class="cdx-toggle-switch__switch"><span class="cdx-toggle-switch__switch__grip"></span></span>' + | '<span class="cdx-toggle-switch__switch"><span class="cdx-toggle-switch__switch__grip"></span></span>' + | ||
'<div class="cdx-label cdx-toggle-switch__label">' + | '<div class="cdx-label cdx-toggle-switch__label">' + | ||
'<label class="cdx-label__label" for="bgm-toggle"><span class="cdx-label__label__text"> | '<label class="cdx-label__label" for="bgm-toggle"><span class="cdx-label__label__text">背景音乐</span></label>' + | ||
'<span class="cdx-label__description">开启或关闭全站背景音乐</span>' + | '<span class="cdx-label__description">开启或关闭全站背景音乐</span>' + | ||
'</div>' + | '</div>' + | ||
| 第1,434行: | 第1,370行: | ||
function toggleMusic(enable) { | function toggleMusic(enable) { | ||
if (enable) { | if (enable) { | ||
if (! | if (!bgmAudio) { | ||
bgmAudio = new Audio('https://music.163.com/song/media/outer/url?id=2692379520.mp3'); | |||
bgmAudio.loop = true; | |||
bgmAudio.volume = 0.3; | |||
} | |||
bgmAudio.play().catch(function() { | |||
$(document).one('click', function() { | |||
bgmAudio.play(); | |||
}); | }); | ||
}); | |||
} else { | |||
if (bgmAudio) { | |||
bgmAudio.pause(); | |||
bgmAudio.currentTime = 0; | |||
} | } | ||
} | } | ||
} | } | ||
// 默认关闭 | |||
if (!musicEnabled) { | |||
$('#wiki-bgm').remove(); | |||
} | |||
var musicObserver = new MutationObserver(function() { | var musicObserver = new MutationObserver(function() { | ||
| 第1,925行: | 第1,865行: | ||
})(); | })(); | ||
// ==================== 卡片自动计数 ==================== | |||
var $allCards = $('.pvzhe-card'); | |||
if ($allCards.length > 0) { | |||
var pageTitle = mw.config.get('wgTitle'); | |||
var typeName = pageTitle.indexOf('僵尸') !== -1 ? '僵尸' : '植物'; | |||
var $counter = $('<span>', { | |||
class: 'card-counter', | |||
text: '共 ' + $allCards.length + ' 个' + typeName | |||
}); | |||
var $filterBox = $('.pvzhe-filter-box').first(); | |||
if ($filterBox.length) { | |||
$filterBox.before($counter); | |||
} | |||
} | |||
// ==================== 外部链接确认 ==================== | |||
$(document).on('click', 'a', function(e) { | |||
var href = $(this).attr('href') || ''; | |||
if (href.indexOf('http') === 0 && href.indexOf(window.location.hostname) === -1) { | |||
if (!confirm('即将离开本站,确定吗?')) { | |||
e.preventDefault(); | |||
} | |||
} | |||
}); | |||
// ==================== 字体切换开关 ==================== | |||
(function() { | |||
var fontKey = 'wiki_font'; | |||
var currentFont = localStorage.getItem(fontKey) || 'standard'; | |||
function applyFont(font) { | |||
$('body').removeClass('font-cartoon font-cartoon-italic font-fzjianzhi font-handwrite font-huapop font-fzkatong font-fzcq'); | |||
if (font === 'cartoon') { | |||
$('body').addClass('font-cartoon'); | |||
} else if (font === 'cartoon-italic') { | |||
$('body').addClass('font-cartoon-italic'); | |||
} else if (font === 'fzjianzhi') { | |||
$('body').addClass('font-fzjianzhi'); | |||
} else if (font === 'handwrite') { | |||
$('body').addClass('font-handwrite'); | |||
} else if (font === 'huapop') { | |||
$('body').addClass('font-huapop'); | |||
} else if (font === 'fzkatong') { | |||
$('body').addClass('font-fzkatong'); | |||
} else if (font === 'fzcq') { | |||
$('body').addClass('font-fzcq'); | |||
} | |||
} | |||
function insertFontSwitch() { | |||
var $target = $('#skin-client-prefs-citizen-feature-custom-width'); | |||
if (!$target.length) return; | |||
if ($('.font-switch').length) return; | |||
var selectHtml = '<select class="cdx-select-vue__handle" id="font-select" style="width:100%;padding:8px;border:1px solid #ccc;border-radius:4px;">' + | |||
$('# | '<option value="standard"' + (currentFont === 'standard' ? ' selected' : '') + '>标准</option>' + | ||
'<option value="fzjianzhi"' + (currentFont === 'fzjianzhi' ? ' selected' : '') + '>方正剪纸</option>' + | |||
'<option value="huapop"' + (currentFont === 'huapop' ? ' selected' : '') + '>华康POP2体W9</option>' + | |||
'<option value="fzkatong"' + (currentFont === 'fzkatong' ? ' selected' : '') + '>方正卡通简体</option>' + | |||
'<option value="fzcq"' + (currentFont === 'fzcq' ? ' selected' : '') + '>方正粗倩</option>' + | |||
'</select>'; | |||
var $switch = $( | |||
'<fieldset class="cdx-field cdx-field--is-fieldset citizen-preferences-group font-switch">' + | |||
'<legend class="cdx-label">' + | |||
'<span class="cdx-label__label">' + | |||
'<span class="cdx-label__label__text">字体</span>' + | |||
'</span>' + | |||
'<span class="cdx-label__description">更改Wiki显示字体</span>' + | |||
'</legend>' + | |||
'<div class="cdx-field__control">' + | |||
'<div class="cdx-select-vue">' + | |||
selectHtml + | |||
'</div>' + | |||
'</div>' + | |||
'</fieldset>' | |||
); | |||
$target.after($switch); | |||
$('#font-select').on('change', function() { | |||
var font = $(this).val(); | |||
localStorage.setItem(fontKey, font); | |||
applyFont(font); | |||
}); | |||
} | } | ||
applyFont(currentFont); | |||
var observer = new MutationObserver(function() { | |||
if ( | if ($('#citizen-preferences-content').is(':visible') && !$('.font-switch').length) { | ||
insertFontSwitch(); | |||
} | } | ||
}); | }); | ||
observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['class', 'style'] }); | |||
if ($('#citizen-preferences-content').is(':visible')) { | |||
insertFontSwitch(); | |||
} | } | ||
} | })(); | ||
// ==================== 404 页面美化 ==================== | |||
if (mw.config.get('wgArticleId') === 0 && mw.config.get('wgNamespaceNumber') === 0 && mw.config.get('wgAction') === 'view') { | |||
var | var pageName = mw.config.get('wgPageName'); | ||
var $overlay = $('<div>', { | |||
css: { | |||
position: 'fixed', top: 0, left: 0, width: '100%', height: '100%', | |||
background: 'rgba(0,0,0,0.6)', zIndex: 99999, | |||
display: 'flex', alignItems: 'center', justifyContent: 'center' | |||
} | |||
}); | |||
var $box = $('<div>', { | |||
css: { | |||
background: '#fff', borderRadius: '16px', padding: '40px', | |||
textAlign: 'center', boxShadow: '0 8px 30px rgba(0,0,0,0.4)', | |||
maxWidth: '420px', width: '85%' | |||
} | } | ||
}); | |||
var $icon = $('<div>', { | |||
text: '🚧', | |||
css: { fontSize: '60px', marginBottom: '15px' } | |||
}); | |||
var $title = $('<h2>', { | |||
var | text: '敬请期待', | ||
css: { marginBottom: '10px', fontSize: '24px', color: '#333' } | |||
}); | |||
var $desc = $('<p>', { | |||
text: '「' + pageName + '」的页面还是一片荒地,快来成为第一个开拓者吧!', | |||
css: { fontSize: '14px', color: '#888', marginBottom: '25px', lineHeight: '1.6' } | |||
}); | |||
var $btnEdit = $('<a>', { | |||
href: '/w/' + encodeURIComponent(pageName) + '?action=edit', | |||
text: '✏️ 编辑此页面', | |||
css: { | |||
$(' | display: 'inline-block', padding: '10px 24px', background: '#4CAF50', | ||
color: '#fff', borderRadius: '8px', textDecoration: 'none', | |||
fontSize: '15px', marginRight: '10px', cursor: 'pointer' | |||
} | |||
}); | |||
var $btnBack = $('<button>', { | |||
text: '↩ 返回', | |||
css: { | |||
} | padding: '10px 24px', background: '#888', color: '#fff', | ||
border: 'none', borderRadius: '8px', fontSize: '15px', cursor: 'pointer' | |||
}, | |||
click: function() { | |||
history.back(); | |||
setTimeout(function() { | |||
if ($overlay.is(':visible')) $overlay.remove(); | |||
}, 500); | |||
} | |||
}); | |||
$box.append($icon, $title, $desc, $btnEdit, $btnBack); | |||
$overlay.append($box); | |||
$('body').append($overlay); | |||
// 点击遮罩关闭 | |||
' | $overlay.on('click', function(e) { | ||
if ($(e.target).is($overlay)) $overlay.remove(); | |||
}); | |||
} | |||
// ==================== 页面最后编辑时间 ==================== | |||
if (mw.config.get('wgAction') === 'view' && mw.config.get('wgIsArticle')) { | |||
var api = new mw.Api(); | |||
api.get({ | |||
action: 'query', | |||
prop: 'revisions', | |||
titles: mw.config.get('wgPageName'), | |||
rvlimit: 1, | |||
rvprop: 'timestamp|user' | |||
}).then(function(data) { | |||
var pages = data.query.pages; | |||
for (var id in pages) { | |||
if (pages[id].revisions) { | |||
var rev = pages[id].revisions[0]; | |||
var editTime = new Date(rev.timestamp); | |||
var now = new Date(); | |||
var today = new Date(now.getFullYear(), now.getMonth(), now.getDate()); | |||
var editDay = new Date(editTime.getFullYear(), editTime.getMonth(), editTime.getDate()); | |||
var diffDays = Math.floor((today - editDay) / 86400000); | |||
var timeStr; | |||
if (diffDays === 0) { | |||
timeStr = '今天 ' + editTime.toLocaleTimeString('zh-CN', {hour:'2-digit',minute:'2-digit'}); | |||
} else if (diffDays === 1) { | |||
timeStr = '昨天 ' + editTime.toLocaleTimeString('zh-CN', {hour:'2-digit',minute:'2-digit'}); | |||
} else { | |||
timeStr = diffDays + ' 天前'; | |||
} | |||
var $info = $('<div style="color:#999;font-size:12px;margin-top:20px;text-align:right;">' + | |||
'最后编辑:' + rev.user + ' 于 ' + timeStr + | |||
'</div>'); | |||
$('#mw-content-text').append($info); | |||
} | |||
' | |||
'</div>' | |||
} | } | ||
}); | }); | ||
} | |||
} | |||
// ==================== 稀有度筛选 ==================== | |||
if ( | if ($('#pvzhe-rarity-filter').length) { | ||
var | var rarityMap = { | ||
'white': '白卡', | |||
'gold': '至尊金卡', | |||
'diamond': '臻享钻卡', | |||
'rainbow': '梦幻彩卡', | |||
'star': '星光闪卡', | |||
'original': '原卡' | |||
}; | |||
// 计数 | |||
var counts = { all: $('.pvzhe-card').length }; | |||
$('.pvzhe-card-container[data-rarity]').each(function() { | |||
var r = $(this).data('rarity'); | |||
counts[r] = $(this).find('.pvzhe-card').length; | |||
}); | }); | ||
var $ | $('.pvzhe-rarity-btn').each(function() { | ||
var r = $(this).data('rarity'); | |||
var label = $(this).text().replace(/\s*\(\d+\)/, ''); | |||
var img = $(this).find('img').length ? $(this).find('img').prop('outerHTML') : ''; | |||
$(this).html(img + label + ' (' + (counts[r] || 0) + ')'); | |||
}); | |||
// 点击 | |||
$(document).on('click', '.pvzhe-rarity-btn', function() { | |||
$('.pvzhe-rarity-btn').removeClass('active'); | |||
$(this).addClass('active'); | |||
var r = $(this).data('rarity'); | |||
if (r === 'all') { | |||
$('.pvzhe-card-container').css('display', 'flex'); | |||
$('.pvzhe-card').show(); | |||
return; | |||
} | } | ||
$('.pvzhe-card-container').css('display', 'none'); | |||
$('.pvzhe-card').hide(); | |||
$('.pvzhe-card-container[data-rarity="' + r + '"]').css('display', 'flex').find('.pvzhe-card').show(); | |||
}); | }); | ||
} | |||
// ==================== 编辑贡献排行榜 ==================== | |||
if ($('#contribution-rank').length) { | |||
$.getJSON(mw.util.wikiScript('api'), { | |||
action: 'query', | |||
list: 'allusers', | |||
aulimit: 20, | |||
auprop: 'editcount', | |||
auactiveusers: 1, | |||
format: 'json' | |||
}, function(data) { | |||
var users = []; | |||
if (data.query && data.query.allusers) { | |||
data.query.allusers.forEach(function(u) { | |||
if (u.editcount > 0) users.push({ name: u.name, count: u.editcount }); | |||
}); | |||
}) | |||
if ( | |||
} | |||
} | } | ||
users.sort(function(a, b) { return b.count - a.count; }); | |||
users = users.slice(0, 10); | |||
$('#rank-loading').hide(); | |||
var $list = $('#rank-list').show().empty(); | |||
users.forEach(function(u, i) { | |||
var numClass = ''; | |||
if (i === 0) numClass = 'gold'; | |||
else if (i === 1) numClass = 'silver'; | |||
else if (i === 2) numClass = 'bronze'; | |||
$list.append( | |||
'<li class="rank-item">' + | |||
'<span class="rank-num ' + numClass + '">' + (i + 1) + '</span>' + | |||
'<span class="rank-name"><a href="/w/User:' + encodeURIComponent(u.name) + '">' + u.name + '</a></span>' + | |||
'<span class="rank-count">' + u.count + ' 次编辑</span>' + | |||
'</li>' | |||
); | |||
}); | |||
}); | }); | ||
} | |||
// ==================== 可重置排行榜 ==================== | |||
if ($('#weekly-rank-list').length) { | |||
var rankApi = new mw.Api(); | |||
rankApi.get({ | |||
// ==================== | |||
if ( | |||
var | |||
action: 'query', | action: 'query', | ||
titles: 'MediaWiki:RankStartDate', | |||
prop: 'revisions', | prop: 'revisions', | ||
rvprop: 'content', | |||
rvlimit: 1 | rvlimit: 1 | ||
}).then(function(data) { | }).then(function(data) { | ||
var pages = data.query.pages; | var pages = data.query.pages; | ||
var startDate = ''; | |||
for (var id in pages) { | for (var id in pages) { | ||
if (pages[id].revisions | if (pages[id].revisions && pages[id].revisions[0]) { | ||
startDate = pages[id].revisions[0]['*'].trim(); | |||
} | } | ||
} | } | ||
if (!startDate) { | |||
$('#weekly-rank-list').html('未设定起始日期'); | |||
if ( | |||
$(' | |||
return; | return; | ||
} | } | ||
$(' | $('#rank-period').text(startDate + ' 至今'); | ||
var start = startDate + 'T00:00:00Z'; | |||
var end = new Date().toISOString(); | |||
$.getJSON(mw.util.wikiScript('api'), { | |||
action: 'query', | |||
list: 'allusers', | |||
aulimit: 30, | |||
auactiveusers: 1, | |||
format: 'json' | |||
}, function(auData) { | |||
var activeUsers = []; | |||
if (auData.query && auData.query.allusers) { | |||
auData.query.allusers.forEach(function(u) { | |||
if (u.name) activeUsers.push(u.name); | |||
}); | |||
} | |||
var allCounts = {}; | |||
var done = 0; | var done = 0; | ||
activeUsers.forEach(function(username) { | |||
var totalCount = 0; | |||
action: ' | function fetchContribs(uccontinue) { | ||
var params = { | |||
if ( | action: 'query', | ||
list: 'usercontribs', | |||
ucuser: username, | |||
uclimit: 50, | |||
ucprop: 'timestamp', | |||
format: 'json', | |||
ucend: start | |||
}; | |||
if (uccontinue) { | |||
params.uccontinue = uccontinue; | |||
} else { | |||
params.ucstart = end; | |||
} | |||
$.getJSON(mw.util.wikiScript('api'), params, function(ucData) { | |||
if (ucData.query && ucData.query.usercontribs) { | |||
totalCount += ucData.query.usercontribs.length; | |||
if (ucData.continue && ucData.continue.uccontinue) { | |||
fetchContribs(ucData.continue.uccontinue); | |||
} else { | |||
if (totalCount > 0) allCounts[username] = totalCount; | |||
done++; | |||
if (done >= activeUsers.length) showResult(); | |||
} | |||
} else { | |||
if (totalCount > 0) allCounts[username] = totalCount; | |||
done++; | |||
if (done >= activeUsers.length) showResult(); | |||
} | |||
}); | |||
} | |||
fetchContribs(); | |||
}); | }); | ||
function showResult() { | |||
var users = []; | |||
for (var name in allCounts) users.push({ name: name, count: allCounts[name] }); | |||
users.sort(function(a, b) { return b.count - a.count; }); | |||
users = users.slice(0, 5); | |||
var medals = ['🥇', '🥈', '🥉', '4️⃣', '5️⃣']; | |||
var html = ''; | |||
users.forEach(function(u, i) { | |||
html += medals[i] + ' <b>' + u.name + '</b>:' + u.count + ' 次<br>'; | |||
}); | |||
$('#weekly-rank-list').html(html || '暂无编辑'); | |||
' | } | ||
if (activeUsers.length === 0) { | |||
$('#weekly-rank-list').html('暂无活跃用户'); | |||
} | |||
}); | }); | ||
}); | }); | ||
} | } | ||
// ==================== | // ==================== 图片懒加载 ==================== | ||
$('img').each(function() { | |||
if (!$(this).attr('loading')) { | |||
$(this).attr('loading', 'lazy'); | |||
} | |||
}); | |||
// 监听动态添加的图片(如筛选后显示的卡片) | |||
var lazyObserver = new MutationObserver(function(mutations) { | |||
mutations.forEach(function(mutation) { | |||
$(mutation.addedNodes).find('img').each(function() { | |||
if (!$(this).attr('loading')) { | |||
$(this).attr('loading', 'lazy'); | |||
if ( | |||
} | } | ||
} | }); | ||
}); | |||
}); | |||
lazyObserver.observe(document.body, { childList: true, subtree: true }); | |||
// ==================== 性能优化 ==================== | |||
// 1. DNS 预解析 | |||
$('head').append('<link rel="dns-prefetch" href="//new.pvzhe.wiki"><link rel="dns-prefetch" href="//images.pvzhe.wiki">'); | |||
$. | // ==================== 我是僵尸网格生成器 ==================== | ||
if ($('#grid-generate').length) { | |||
function generateGrid() { | |||
var raw = $('#grid-data').val().trim(); | |||
var cols = parseInt($('#grid-cols').val()) || 9; | |||
var rows = parseInt($('#grid-rows').val()) || 5; | |||
var bg = $('#grid-bg').val() || '#2d2d2d'; | |||
var cellColor = $('#grid-cell').val() || '#3a3a3a'; | |||
var border = $('#grid-border').val() || '#555555'; | |||
var dividerCol = parseInt($('#grid-divider').val()) || 4; | |||
var lines = raw.split('\n'); | |||
var data = {}; | |||
lines.forEach(function(line) { | |||
var parts = line.split(','); | |||
if (parts.length >= 3) { | |||
var name = parts[0].trim(); | |||
var col = parseInt(parts[1].trim()); | |||
var row = parseInt(parts[2].trim()); | |||
if (name && col >= 1 && row >= 1) { | |||
var key = row + '-' + col; | |||
data[key] = name; | |||
} | |||
} | |||
}); | |||
var table = '{| style="width:100%;max-width:900px;table-layout:fixed;text-align:center;background:' + bg + ';border-collapse:collapse;"\n'; | |||
table += '|-\n! !!'; | |||
for (var c = 1; c <= cols; c++) { | |||
if (c === dividerCol) { | |||
table += ' 列' + c + ' !!'; | |||
} else { | |||
table += ' 列' + c + ' !!'; | |||
} | } | ||
} | |||
table = table.slice(0, -2) + '\n'; | |||
if ( | for (var r = 1; r <= rows; r++) { | ||
table += '|-\n! 行' + r + '\n'; | |||
for (var c = 1; c <= cols; c++) { | |||
var plant = data[r + '-' + c]; | |||
var style = 'height:80px;border:2px solid ' + border + ';background:' + cellColor + ';'; | |||
if (c === dividerCol) { | |||
style += 'border-right:4px solid red;'; | |||
} | |||
if (plant) { | |||
table += '| style="' + style + '" | [[文件:' + plant + '.png|50px|link=' + plant + ']]\n'; | |||
} else { | |||
table += '| style="' + style + '" | \n'; | |||
} | |||
} | } | ||
} | } | ||
table += '|}'; | |||
$('#grid-preview').html('<pre style="background:#f9f9f9;padding:10px;border-radius:6px;font-size:12px;max-height:400px;overflow:auto;">' + table.replace(/</g,'<').replace(/>/g,'>') + '</pre>'); | |||
$('#grid-preview').data('code', table); | |||
$( | |||
} | } | ||
$('#grid-generate').click(generateGrid); | |||
$('#grid-copy').click(function() { | |||
var code = $('#grid-preview').data('code'); | |||
$( | if (code) { | ||
navigator.clipboard.writeText(code).then(function() { | |||
$('#grid-copy').text('✅ 已复制'); | |||
} | setTimeout(function() { $('#grid-copy').text('复制代码'); }, 1500); | ||
} | }); | ||
} | |||
}); | }); | ||
generateGrid(); | |||
} | |||
// ==================== 随机植物挑战 ==================== | |||
if ($('# | if ($('#pvzhe-rarity-filter').length && mw.config.get('wgPageName') === '植物') { | ||
var $box = $( | |||
'<div class="random-plant-box" style="text-align:center;padding:20px;background:#fff;border-radius:12px;border:2px solid #e0e0e0;margin:15px 0;">' + | |||
'<div style="display:flex;align-items:center;justify-content:center;gap:10px;margin-bottom:15px;flex-wrap:wrap;">' + | |||
'<button class="random-plant-btn" style="padding:10px 30px;font-size:16px;background:#4CAF50;color:#fff;border:none;border-radius:8px;cursor:pointer;">随机抽取</button>' + | |||
'<input type="number" class="random-plant-count" value="5" min="1" max="10" style="width:60px;padding:8px;border:1px solid #ccc;border-radius:8px;text-align:center;font-size:16px;">' + | |||
'<span style="font-size:14px;color:#888;">个植物</span>' + | |||
'</div>' + | |||
var | '<div class="random-plant-result" style="display:flex;flex-wrap:wrap;justify-content:center;gap:8px;"></div>' + | ||
'</div>' | |||
); | |||
$('#pvzhe-rarity-filter').after($box); | |||
$box.find('.random-plant-btn').click(function() { | |||
var count = parseInt($box.find('.random-plant-count').val()) || 5; | |||
count = Math.max(1, Math.min(10, count)); | |||
$box.find('.random-plant-count').val(count); | |||
var cards = []; | |||
var | $('.pvzhe-card').each(function() { | ||
var $card = $(this); | |||
var name = $card.find('.pvzhe-card-name').text().trim(); | |||
var img = $card.find('img').attr('src'); | |||
var bg = $card.css('background') || $card.css('background-color') || '#f9f9f9'; | |||
var border = $card.css('border-color') || '#e0e0e0'; | |||
if (name && img) { | |||
cards.push({ name: name, img: img, bg: bg, border: border }); | |||
} | } | ||
}); | }); | ||
cards = cards.sort(function() { return Math.random() - 0.5; }); | |||
var picked = cards.slice(0, count); | |||
var html = '<div style="display:flex;flex-wrap:wrap;justify-content:center;gap:15px;">'; | |||
picked.forEach(function(c) { | |||
html += '<a href="/w/' + encodeURIComponent(c.name) + '" style="text-align:center;text-decoration:none;color:#333;">' + | |||
'<div style="width:90px;height:90px;border-radius:12px;padding:4px;background:' + c.bg + ';border:2px solid ' + c.border + ';display:flex;align-items:center;justify-content:center;">' + | |||
'<img src="' + c.img + '" style="max-width:80px;max-height:80px;border-radius:8px;">' + | |||
'</div>' + | |||
'<span style="font-size:12px;margin-top:4px;display:block;">' + c.name + '</span>' + | |||
'</a>'; | |||
}); | |||
html += '</div>'; | |||
$box.find('.random-plant-result').html(html); | |||
}); | |||
} | |||
// ==================== 随机僵尸挑战 ==================== | |||
if ($('#zf-name').length && mw.config.get('wgPageName') === '僵尸') { | |||
var $zbox = $( | |||
'<div class="random-plant-box" style="text-align:center;padding:20px;background:#fff;border-radius:12px;border:2px solid #e0e0e0;margin:15px 0;">' + | |||
'<div style="display:flex;align-items:center;justify-content:center;gap:10px;margin-bottom:15px;flex-wrap:wrap;">' + | |||
'<button class="random-zombie-btn" style="padding:10px 30px;font-size:16px;background:#7E57C2;color:#fff;border:none;border-radius:8px;cursor:pointer;">随机抽取</button>' + | |||
'<input type="number" class="random-zombie-count" value="5" min="1" max="10" style="width:60px;padding:8px;border:1px solid #ccc;border-radius:8px;text-align:center;font-size:16px;">' + | |||
'<span style="font-size:14px;color:#888;">个僵尸</span>' + | |||
'</div>' + | |||
'<div class="random-zombie-result" style="display:flex;flex-wrap:wrap;justify-content:center;gap:8px;"></div>' + | |||
'</div>' | |||
); | |||
$('#zf-name').closest('.pvzhe-filter-box').before($zbox); | |||
$zbox.find('.random-zombie-btn').click(function() { | |||
$(' | var count = parseInt($zbox.find('.random-zombie-count').val()) || 5; | ||
count = Math.max(1, Math.min(10, count)); | |||
$zbox.find('.random-zombie-count').val(count); | |||
var cards = []; | |||
$('.pvzhe-card').each(function() { | |||
var $card = $(this); | |||
var name = $card.find('.pvzhe-card-name').text().trim(); | |||
var cards = []; | |||
$('.pvzhe-card').each(function() { | |||
var $card = $(this); | |||
var name = $card.find('.pvzhe-card-name').text().trim(); | |||
var img = $card.find('img').attr('src'); | var img = $card.find('img').attr('src'); | ||
var bg = $card.css('background') || $card.css('background-color') || '#f9f9f9'; | var bg = $card.css('background') || $card.css('background-color') || '#f9f9f9'; | ||
| 第2,622行: | 第2,427行: | ||
}); | }); | ||
html += '</div>'; | html += '</div>'; | ||
$ | $zbox.find('.random-zombie-result').html(html); | ||
}); | }); | ||
} | } | ||
// ==================== | // ==================== 修复浏览量文字 ==================== | ||
$(window).on('load', function() { | |||
$('body *').contents().filter(function() { | |||
return this.nodeType === 3 && this.textContent.indexOf('浏览量') !== -1; | |||
}).each(function() { | |||
this.textContent = this.textContent.replace(/浏览量/g, '浏览量'); | |||
}); | |||
}); | |||
); | |||
// ==================== 侧边栏批量上传入口 ==================== | |||
if ($('#citizen-drawer__card').length) { | |||
var $uploadLi = $('#t-upload').closest('li'); | |||
if ($uploadLi.length) { | |||
var $batchLi = $( | |||
'<li id="n-batchupload" class="mw-list-item">' + | |||
'<a href="/w/%E4%B8%8A%E4%BC%A0%E5%9B%BE%E7%89%87">' + | |||
'<span class="citizen-ui-icon mw-ui-icon-image mw-ui-icon-wikimedia-image"></span> ' + | |||
'<span>批量上传图片</span>' + | |||
'</a>' + | |||
'</li>' | |||
); | |||
$uploadLi.after($batchLi); | |||
} | |||
$ | |||
} | |||
} | } | ||
// ==================== 今日活跃用户 ==================== | |||
// ==================== 今日活跃用户 ==================== | |||
if ($('#today-active-users').length) { | if ($('#today-active-users').length) { | ||
var now = new Date(); | var now = new Date(); | ||
| 第2,837行: | 第2,596行: | ||
// 初始显示 | // 初始显示 | ||
updateCounter(); | updateCounter(); | ||
} | } | ||
}); // 结束主 $(function () { ... }) | }); // 结束主 $(function () { ... }) | ||
2026年7月25日 (六) 03:28的最新版本
/* v3.1 - 植物大战僵尸杂交版Wiki */
/* 这里的任何JavaScript将为所有用户在每次页面加载时加载。 */
// 自动加载 MediaWiki:Footer 页面内容,并插入到每个页面底部
$(document).ready(function() {
const namespace = mw.config.get('wgNamespaceNumber');
const action = mw.config.get('wgAction');
if (namespace !== 0 || action !== 'view') {
return;
}
fetch("/api.php?action=parse&page=MediaWiki:Footer&format=json")
.then(res => res.json())
.then(data => {
if (data.parse && data.parse.text) {
const html = data.parse.text['*'];
$('#mw-content-text').append('<div class="global-footer">' + html + '</div>');
}
});
});
// ==================== Ctrl+Shift+Q 切换顶栏 ====================
(function() {
var headerKey = 'wiki_header_visible';
var headerVisible = localStorage.getItem(headerKey) !== 'false';
function toggleHeader() {
var $header = $('body > header.mw-header, body > header.citizen-header');
if ($header.length === 0) return;
headerVisible = !headerVisible;
localStorage.setItem(headerKey, headerVisible ? 'true' : 'false');
if (headerVisible) {
$header.show();
// 恢复内容区上边距
$('#mw-content-wrapper, .citizen-body-container, #mw-content-text').css('margin-top', '');
} else {
$header.hide();
// 让内容区顶到最上面
$('#mw-content-wrapper, .citizen-body-container, #mw-content-text').css('margin-top', '0');
}
// 右下角提示
var msg = headerVisible ? '侧边栏已显示' : '侧边栏已隐藏';
showToast(msg);
}
function showToast(msg) {
var $toast = $('<div>', {
css: {
position: 'fixed', bottom: '20px', right: '20px',
background: 'rgba(0,0,0,0.8)', color: '#fff',
padding: '8px 16px', borderRadius: '8px',
fontSize: '14px', zIndex: 99999,
opacity: 0, transition: 'opacity 0.3s'
},
text: msg
});
$('body').append($toast);
setTimeout(function() { $toast.css('opacity', 1); }, 50);
setTimeout(function() { $toast.css('opacity', 0); setTimeout(function() { $toast.remove(); }, 400); }, 1200);
}
// 快捷键监听
$(document).on('keydown', function(e) {
if (e.ctrlKey && e.shiftKey && (e.key === 'q' || e.key === 'Q')) {
e.preventDefault();
toggleHeader();
}
});
// 页面加载时恢复状态
$(function() {
if (!headerVisible) {
var $header = $('body > header.mw-header, body > header.citizen-header');
if ($header.length) {
$header.hide();
$('#mw-content-wrapper, .citizen-body-container, #mw-content-text').css('margin-top', '0');
}
}
});
})();
// ==================== 自定义光标开关 ====================
if (mw.config.get('wgUserName') && !/Mobi|Android|iPhone/i.test(navigator.userAgent)) {
var cursorKey = 'wiki_custom_cursor';
var cursorEnabled = localStorage.getItem(cursorKey) !== 'false';
var cursorStyleEl = null;
function applyCursor(enable) {
if (enable) {
if (!cursorStyleEl) {
cursorStyleEl = document.createElement('style');
cursorStyleEl.textContent =
'body, div, span, p, h1, h2, h3, h4, h5, h6, td, th, li {' +
"cursor: url('https://new.pvzhe.wiki/images/1/11/%E5%85%89%E6%A0%873.png'), auto !important;" +
'}' +
'a, a:hover, a:active, button, .pvzhe-card, #back-to-top, .wiki-pet, .random-plant-btn, .random-zombie-btn, .friend-btn, .msg-send-btn {' +
"cursor: url('https://new.pvzhe.wiki/images/4/42/%E5%85%89%E6%A0%87%E6%9C%89%E9%93%BE%E6%8E%A5.png'), pointer !important;" +
'}' +
'input[type="text"], textarea, input[type="search"] {' +
"cursor: url('https://new.pvzhe.wiki/images/7/78/%E5%85%89%E6%A0%87%E8%BE%93%E5%85%A5.png'), text !important;" +
'}';
document.head.appendChild(cursorStyleEl);
}
} else {
if (cursorStyleEl) {
cursorStyleEl.remove();
cursorStyleEl = null;
}
}
}
function insertCursorSwitch() {
var $target = $('.citizen-preferences-section__content').last();
if (!$target.length) return;
if ($('.cursor-switch').length) return;
var $switch = $(
'<span class="cdx-toggle-switch cdx-toggle-switch--align-switch citizen-preferences-group cursor-switch">' +
'<input id="cursor-toggle" class="cdx-toggle-switch__input" type="checkbox" role="switch"' + (cursorEnabled ? ' checked' : '') + '>' +
'<span class="cdx-toggle-switch__switch"><span class="cdx-toggle-switch__switch__grip"></span></span>' +
'<div class="cdx-label cdx-toggle-switch__label">' +
'<label class="cdx-label__label" for="cursor-toggle"><span class="cdx-label__label__text">自定义光标</span></label>' +
'<span class="cdx-label__description">自定义的光标</span>' +
'</div>' +
'</span>'
);
$target.append($switch);
$('#cursor-toggle').on('change', function() {
cursorEnabled = $(this).is(':checked');
localStorage.setItem(cursorKey, cursorEnabled ? 'true' : 'false');
applyCursor(cursorEnabled);
});
}
applyCursor(cursorEnabled);
var cursorObserver = new MutationObserver(function() {
if ($('.citizen-preferences-section__content').is(':visible') && !$('.cursor-switch').length) {
insertCursorSwitch();
}
});
cursorObserver.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['class', 'style'] });
if ($('.citizen-preferences-section__content').is(':visible')) insertCursorSwitch();
}
// 用户颜色、图标、状态、师徒、昵称、里程碑、公告、植物/僵尸筛选、好友、私信 主逻辑
$(function () {
// ==================== 动态注入彩虹样式 ====================
var rainbowStyle = document.createElement('style');
rainbowStyle.textContent =
'.rainbow-user {' +
'font-weight: bold;' +
'background: repeating-linear-gradient(90deg, red 0px, orange 10px, yellow 20px, green 30px, blue 40px, indigo 50px, violet 60px);' +
'-webkit-background-clip: text;' +
'-webkit-text-fill-color: transparent;' +
'background-clip: text;' +
'}';
document.head.appendChild(rainbowStyle);
// ==================== 动态注入字体(只注入 @font-face,不绑定 CSS 类) ====================
var fontStyle = document.createElement('style');
fontStyle.textContent =
'@font-face {' +
"font-family: 'FZJianZhi';" +
"src: url('https://new.pvzhe.wiki/images/0/00/FZJZ.woff2') format('woff2');" +
'font-weight: normal;' +
'font-style: normal;' +
'font-display: swap;' +
'}' +
'@font-face {' +
"font-family: 'Huakang POP2 W9';" +
"src: url('https://new.pvzhe.wiki/images/3/37/%E5%8D%8E%E5%BA%B7POP2%E4%BD%93W9.woff2') format('woff2');" +
'font-weight: normal;' +
'font-style: normal;' +
'font-display: swap;' +
'}';
document.head.appendChild(fontStyle);
// ==================== 配置区 ====================
var mentorList = ['愤怒的郎朗', 'Yuyaabc', '虚位以待', '知更鸟头号粉丝', '凌玥'];
var newbieEditThreshold = 25;
var milestoneThresholds = [10, 50, 100, 500, 1000, 2500, 5000, 10000, 20000, 50000];
// ==================== 辅助函数 ====================
function getUserName(link) {
var $span = $(link).find('span').first();
if ($span.length) return $span.text().trim();
return $(link).text().trim();
}
function isUserLink(link) {
return $(link).is('a.mw-userlink') || $(link).find('span').length > 0;
}
function getLevelIcons(editcount) {
var totalStars = Math.floor(editcount / 100);
if (totalStars === 0) return '';
var crowns = Math.floor(totalStars / 125);
var remaining = totalStars % 125;
var suns = Math.floor(remaining / 25);
remaining %= 25;
var moons = Math.floor(remaining / 5);
var stars = remaining % 5;
var icons = '';
if (crowns > 0) icons += '👑'.repeat(crowns);
if (suns > 0) icons += '☀️'.repeat(suns);
if (moons > 0) icons += '🌙'.repeat(moons);
if (stars > 0) icons += '⭐'.repeat(stars);
return icons;
}
function updateLevelIcons($link, editcount) {
var iconStr = getLevelIcons(editcount);
var $iconSpan = $link.next('.user-level-icons');
if (iconStr === '') {
$iconSpan.remove();
return;
}
if ($iconSpan.length === 0) {
$iconSpan = $('<span class="user-level-icons"></span>');
$link.after($iconSpan);
}
$iconSpan.text(iconStr);
}
function getTitleByEditcount(ec) {
if (ec >= 10000) return '神话';
if (ec >= 5000) return '传奇';
if (ec >= 2500) return '大师';
if (ec >= 1000) return '专家';
if (ec >= 500) return '资深';
if (ec >= 100) return '活跃';
if (ec >= 50) return '入门';
if (ec >= 10) return '新手';
return '萌新';
}
function addTitleTag($link, editcount) {
if ($link.data('title-tag-added')) return;
$link.data('title-tag-added', true);
var title = getTitleByEditcount(editcount);
var $tag = $('<span class="user-title-tag">' + title + '</span>');
$link.after($tag);
}
// ==================== 核心:颜色 + 图标 + 状态 + 师徒 + 昵称 ====================
function colorizeAndStatus($links) {
if ($links.length === 0) return;
var $fresh = $links.filter(function () {
return !$(this).data('user-status-processed');
});
if ($fresh.length === 0) return;
var users = [];
$fresh.each(function () {
var name = getUserName(this);
if (name && users.indexOf(name) === -1) users.push(name);
});
if (users.length === 0) return;
$fresh.each(function () {
$(this).data('user-status-processed', true);
});
var batchSize = 50;
var batches = [];
for (var i = 0; i < users.length; i += batchSize) {
batches.push(users.slice(i, i + batchSize));
}
var processBatch = function (batch) {
var api = new mw.Api();
return api.get({
action: 'query',
list: 'users',
ususers: batch.join('|'),
usprop: 'editcount'
}).then(function (data) {
var classMap = {};
var editCountMap = {};
if (data.query && data.query.users) {
data.query.users.forEach(function (u) {
var ec = u.editcount || 0;
editCountMap[u.name] = ec;
if (ec >= 5000) classMap[u.name] = 'rainbow-user';
else if (ec >= 2500) classMap[u.name] = 'gold-user';
else if (ec >= 1000) classMap[u.name] = 'platinum-user';
else if (ec >= 500) classMap[u.name] = 'silver-user';
else if (ec >= 1) classMap[u.name] = 'bronze-user';
});
}
$fresh.each(function () {
var $this = $(this);
var name = getUserName(this);
var cls = classMap[name];
if (cls) {
$this.removeClass('bronze-user silver-user platinum-user gold-user rainbow-user');
$this.addClass(cls);
}
var ec = editCountMap[name];
if (typeof ec !== 'undefined') {
updateLevelIcons($this, ec);
var isInsideCard = $this.closest('.citizen-menu_card-content, .citizen-userMenu').length > 0;
if (!isInsideCard) {
addTitleTag($this, ec);
}
}
});
});
};
var colorPromise = $.Deferred().resolve();
batches.forEach(function (batch) {
colorPromise = colorPromise.then(function () {
return processBatch(batch);
});
});
$fresh.each(function () {
if (isUserLink(this)) {
var isInsideCard = $(this).closest('.citizen-menu_card-content, .citizen-userMenu').length > 0;
if (!isInsideCard) {
var username = getUserName(this);
addStatusDot($(this), username);
addMentorTag($(this), username);
}
}
});
}
// ==================== 状态圆点 ====================
function addStatusDot($link, username) {
if ($link.data('status-dot-added')) return;
$link.data('status-dot-added', true);
var $dot = $('<span class="user-status-dot status-offline" title="离线"></span>');
$link.after($dot);
var cacheKey = 'mw_user_status_' + mw.config.get('wgDBname') + '_' + username;
var cached = localStorage.getItem(cacheKey);
var now = Date.now();
if (cached) {
try {
var data = JSON.parse(cached);
if (now - data.timestamp < 5 * 60 * 1000) {
updateDotStyle($dot, data.lastEditTime);
return;
}
} catch (e) {}
}
var api = new mw.Api();
api.get({
action: 'query',
list: 'usercontribs',
ucuser: username,
uclimit: 1,
ucprop: 'timestamp'
}).then(function (data) {
var lastEditTime = null;
if (data.query && data.query.usercontribs && data.query.usercontribs.length > 0) {
lastEditTime = data.query.usercontribs[0].timestamp;
}
localStorage.setItem(cacheKey, JSON.stringify({
lastEditTime: lastEditTime,
timestamp: now
}));
updateDotStyle($dot, lastEditTime);
}).fail(function () {});
}
function updateDotStyle($dot, lastEditTime) {
if (!lastEditTime) {
$dot.attr('title', '离线');
$dot.removeClass('status-online status-away').addClass('status-offline');
return;
}
var last = new Date(lastEditTime).getTime();
var diffMinutes = (Date.now() - last) / 60000;
if (diffMinutes < 15) {
$dot.attr('title', '在线(15分钟内活跃)');
$dot.removeClass('status-offline status-away').addClass('status-online');
} else if (diffMinutes < 60) {
$dot.attr('title', '近期活跃(1小时内)');
$dot.removeClass('status-offline status-online').addClass('status-away');
} else {
$dot.attr('title', '离线');
$dot.removeClass('status-online status-away').addClass('status-offline');
}
}
// ==================== 师徒标签 ====================
function addMentorTag($link, username) {
if ($link.data('mentor-tag-added')) return;
if ($link.next('.user-tag').length) return;
var $tag;
if (mentorList.indexOf(username) !== -1) {
$link.data('mentor-tag-added', true);
$tag = $('<span class="user-tag user-tag-mentor">导师</span>');
$link.after($tag);
return;
}
if ($link.data('newbie-tag-checked')) return;
$link.data('newbie-tag-checked', true);
var cacheKey = 'mw_newbie_check_' + mw.config.get('wgDBname') + '_' + username;
var cached = localStorage.getItem(cacheKey);
var now = Date.now();
if (cached) {
try {
var data = JSON.parse(cached);
if (now - data.timestamp < 60 * 60 * 1000) {
if (data.editcount <= newbieEditThreshold) {
$tag = $('<span class="user-tag user-tag-newbie">新手</span>');
$link.after($tag);
}
return;
}
} catch (e) {}
}
var api = new mw.Api();
api.get({
action: 'query',
list: 'users',
ususers: username,
usprop: 'editcount'
}).then(function (data) {
var editcount = 0;
if (data.query && data.query.users && data.query.users.length > 0) {
editcount = data.query.users[0].editcount || 0;
}
localStorage.setItem(cacheKey, JSON.stringify({
editcount: editcount,
timestamp: now
}));
if (editcount <= newbieEditThreshold) {
if ($link.next('.user-tag-newbie').length === 0) {
$tag = $('<span class="user-tag user-tag-newbie">新手</span>');
$link.after($tag);
}
}
}).fail(function () {});
}
// ==================== 编辑里程碑弹窗 ====================
var currentUser = mw.config.get('wgUserName');
if (currentUser) {
var api = new mw.Api();
api.get({
action: 'query',
list: 'users',
ususers: currentUser,
usprop: 'editcount'
}).then(function(data) {
var editcount = 0;
if (data.query && data.query.users && data.query.users.length > 0) {
editcount = data.query.users[0].editcount || 0;
}
var achievedMilestone = null;
milestoneThresholds.forEach(function(m) {
if (editcount >= m) achievedMilestone = m;
});
if (achievedMilestone) {
var storageKey = 'mw_milestone_shown_' + currentUser + '_' + achievedMilestone;
if (!localStorage.getItem(storageKey)) {
localStorage.setItem(storageKey, '1');
var $overlay = $('<div>', {
css: {
position: 'fixed', top: 0, left: 0, width: '100%', height: '100%',
background: 'rgba(0,0,0,0.5)', 'z-index': 99998,
display: 'flex', 'align-items': 'center', 'justify-content': 'center'
}
});
var $box = $('<div>', {
css: {
background: '#fff', 'border-radius': '12px', padding: '30px 40px',
'text-align': 'center', 'box-shadow': '0 4px 20px rgba(0,0,0,0.3)',
'max-width': '400px', width: '80%', position: 'relative'
}
});
var $title = $('<h2>', {
text: '🎉 恭喜!',
css: { 'margin-bottom': '15px', 'font-size': '24px', color: '#333' }
});
var $msg = $('<p>', {
text: '你已达到 ' + achievedMilestone + ' 次编辑!',
css: { 'font-size': '18px', 'margin-bottom': '25px', color: '#555' }
});
var $btn = $('<button>', {
text: '太棒了!',
css: {
background: '#4CAF50', color: '#fff', border: 'none',
padding: '10px 30px', 'border-radius': '8px', 'font-size': '16px', cursor: 'pointer'
},
click: function() {
$overlay.fadeOut(300, function() { $(this).remove(); });
}
});
$box.append($title, $msg, $btn);
$overlay.append($box);
$('body').append($overlay);
}
}
}).fail(function() {});
}
// ==================== 用户链接处理器启动 ====================
var linkSelector = 'a.mw-userlink, .citizen-menu_card-content a, .citizen-userMenu a';
colorizeAndStatus($(linkSelector));
var observer = new MutationObserver(function () {
colorizeAndStatus($(linkSelector));
});
observer.observe(document.body, {
childList: true,
subtree: true
});
setInterval(function () {
colorizeAndStatus($(linkSelector));
}, 3000);
// ==================== 植物卡片筛选 ====================
if ($('#pf-name').length) {
var $cards = $('.pvzhe-card');
var $name = $('#pf-name');
var $sunMax = $('#pf-sun-max');
var $cdMax = $('#pf-cd-max');
var $type = $('#pf-type');
var $version = $('#pf-version');
var $reset = $('#pf-reset');
function filterPlants() {
var name = $name.val().toLowerCase();
var sunRaw = $sunMax.val().trim();
var cdRaw = $cdMax.val().trim();
var sunTarget = sunRaw !== '' ? parseFloat(sunRaw) : null;
var cdTarget = cdRaw !== '' ? parseFloat(cdRaw) : null;
var type = $type.val();
var version = $version.val();
$cards.each(function () {
var $card = $(this);
var n = $card.find('.pvzhe-card-name').text().toLowerCase();
var sun = parseFloat($card.data('sun')) || 0;
var cd = parseFloat($card.data('cooldown')) || 0;
var t = ($card.data('type') || '').toString();
var v = ($card.data('version') || '').toString();
var show = true;
if (name && n.indexOf(name) === -1) show = false;
if (sunTarget !== null && sun !== sunTarget) show = false;
if (cdTarget !== null && cd !== cdTarget) show = false;
if (type) {
var cardTypes = t.split(',').map(function (s) { return s.trim(); });
if (cardTypes.indexOf(type) === -1) show = false;
}
if (version && v !== version) show = false;
$card.toggleClass('hidden-card', !show);
});
}
$name.on('input', filterPlants);
$sunMax.on('input keyup', filterPlants);
$cdMax.on('input keyup', filterPlants);
$type.on('change', filterPlants);
$version.on('change', filterPlants);
$reset.on('click', function () {
$name.val('');
$sunMax.val('');
$cdMax.val('');
$type.val('');
$version.val('');
filterPlants();
});
}
// ==================== 僵尸卡片筛选 ====================
function getArmorArray(healthStr) {
if (!healthStr || healthStr.indexOf('+') === -1) return [];
var armorPart = healthStr.split('+')[0].trim();
return armorPart.split(',').map(function(s) { return s.trim(); });
}
function extractHealth(healthStr) {
var matches = healthStr.match(/\d+/g);
if (matches && matches.length > 0) {
return parseFloat(matches[matches.length - 1]) || 0;
}
return 0;
}
if ($('#zf-name').length) {
var $zcards = $('.pvzhe-card');
var $zname = $('#zf-name');
var $healthMin = $('#zf-health-min');
var $armor = $('#zf-armor');
var $speed = $('#zf-speed');
var $ztype = $('#zf-type');
var $zversion = $('#zf-version');
var $zreset = $('#zf-reset');
function filterZombies() {
var name = $zname.val().toLowerCase();
var healthRaw = $healthMin.val().trim();
var healthTarget = healthRaw !== '' ? parseFloat(healthRaw) : null;
var armor = $armor.val();
var speed = $speed.val();
var type = $ztype.val();
var version = $zversion.val();
$zcards.each(function () {
var $card = $(this);
var n = $card.find('.pvzhe-card-name').text().toLowerCase();
var t = ($card.data('type') || '').toString();
var s = ($card.data('speed') || '').toString();
var healthStr = ($card.data('health') || '').toString();
var health = extractHealth(healthStr);
var armors = getArmorArray(healthStr);
var v = ($card.data('version') || '').toString();
var show = true;
if (name && n.indexOf(name) === -1) show = false;
if (healthTarget !== null && health !== healthTarget) show = false;
if (armor === 'none') {
if (armors.length > 0) show = false;
} else if (armor) {
if (armors.indexOf(armor) === -1) show = false;
}
if (speed) {
var cardSpeeds = s.split(',').map(function (v) { return v.trim(); });
if (cardSpeeds.indexOf(speed) === -1) show = false;
}
if (type) {
var cardTypes = t.split(',').map(function (v) { return v.trim(); });
if (cardTypes.indexOf(type) === -1) show = false;
}
if (version && v !== version) show = false;
$card.toggleClass('hidden-card', !show);
});
}
$zname.on('input', filterZombies);
$healthMin.on('input keyup', filterZombies);
$armor.on('change', filterZombies);
$speed.on('change', filterZombies);
$ztype.on('change', filterZombies);
$zversion.on('change', filterZombies);
$zreset.on('click', function () {
$zname.val('');
$healthMin.val('');
$armor.val('');
$speed.val('');
$ztype.val('');
$zversion.val('');
filterZombies();
});
}
// ==================== 返回顶部按钮 ====================
$('body').append('<button id="back-to-top" title="返回顶部">⬆</button>');
var $backBtn = $('#back-to-top');
$(window).scroll(function() {
$backBtn.toggle($(this).scrollTop() > 300);
});
$backBtn.click(function() {
$('html, body').animate({ scrollTop: 0 }, 400);
});
// ==================== 好友系统 ====================
var friendApi = new mw.Api();
function getFriendPageName(username) {
return 'User:' + username + '/friends';
}
function getFriendRequestsPageName(username) {
return 'User:' + username + '/friendrequests';
}
function loadFriendList(username, callback) {
friendApi.get({
action: 'query',
titles: getFriendPageName(username),
prop: 'revisions',
rvprop: 'content',
rvlimit: 1
}).then(function(data) {
var pages = data.query.pages;
for (var id in pages) {
if (pages[id].revisions && pages[id].revisions[0]) {
try { callback(JSON.parse(pages[id].revisions[0]['*'])); return; } catch(e) {}
}
}
callback([]);
}).fail(function() { callback([]); });
}
function loadFriendRequests(username, callback) {
friendApi.get({
action: 'query',
titles: getFriendRequestsPageName(username),
prop: 'revisions',
rvprop: 'content',
rvlimit: 1
}).then(function(data) {
var pages = data.query.pages;
for (var id in pages) {
if (pages[id].revisions && pages[id].revisions[0]) {
try { callback(JSON.parse(pages[id].revisions[0]['*'])); return; } catch(e) {}
}
}
callback([]);
}).fail(function() { callback([]); });
}
function saveToPage(pageName, data, summary, callback) {
friendApi.postWithEditToken({
action: 'edit',
title: pageName,
text: JSON.stringify(data, null, 2),
summary: summary,
minor: true,
bot: true
}).then(function() {
if (callback) callback(true);
}).fail(function() {
if (callback) callback(false);
});
}
function sendFriendRequest(toUser) {
if (!toUser || toUser === currentUser) return;
loadFriendList(currentUser, function(myFriends) {
if (myFriends.indexOf(toUser) !== -1) {
alert('你们已经是好友了!');
return;
}
loadFriendRequests(toUser, function(requests) {
var alreadySent = requests.some(function(r) { return r.from === currentUser; });
if (alreadySent) {
alert('你已经发送过好友请求了,请等待对方回应。');
return;
}
requests.push({ from: currentUser, time: Date.now() });
saveToPage(getFriendRequestsPageName(toUser), requests, currentUser + ' 发送了好友请求', function(success) {
if (success) {
alert('好友请求已发送给 ' + toUser + '!');
updateFriendButton(toUser, 'pending');
} else {
alert('发送失败,请稍后重试。');
}
});
});
});
}
function acceptFriendRequest(fromUser) {
loadFriendList(currentUser, function(myFriends) {
myFriends.push(fromUser);
saveToPage(getFriendPageName(currentUser), myFriends, '添加好友: ' + fromUser, function() {
loadFriendList(fromUser, function(theirFriends) {
theirFriends.push(currentUser);
saveToPage(getFriendPageName(fromUser), theirFriends, '添加好友: ' + currentUser, function() {
loadFriendRequests(currentUser, function(requests) {
requests = requests.filter(function(r) { return r.from !== fromUser; });
saveToPage(getFriendRequestsPageName(currentUser), requests, '接受好友请求', function() {
if ($('#friend-requests-list').length) showFriendRequests();
updateFriendButton(fromUser, 'added');
});
});
});
});
});
});
}
function rejectFriendRequest(fromUser) {
loadFriendRequests(currentUser, function(requests) {
requests = requests.filter(function(r) { return r.from !== fromUser; });
saveToPage(getFriendRequestsPageName(currentUser), requests, '拒绝好友请求', function() {
if ($('#friend-requests-list').length) showFriendRequests();
});
});
}
function removeFriend(friendName) {
if (!confirm('确定要删除好友 ' + friendName + ' 吗?')) return;
loadFriendList(currentUser, function(myFriends) {
myFriends = myFriends.filter(function(f) { return f !== friendName; });
saveToPage(getFriendPageName(currentUser), myFriends, '删除好友: ' + friendName, function() {
loadFriendList(friendName, function(theirFriends) {
theirFriends = theirFriends.filter(function(f) { return f !== currentUser; });
saveToPage(getFriendPageName(friendName), theirFriends, '删除好友: ' + currentUser, function() {
if ($('#friend-list-container').length) showFriendList();
updateFriendButton(friendName, 'add');
});
});
});
});
}
function updateFriendButton(username, status) {
var $btn = $('#friend-action-btn');
if (!$btn.length) return;
$btn.removeClass('friend-add-btn friend-added-btn friend-pending-btn');
if (status === 'added') {
$btn.addClass('friend-added-btn').text('✓ 已添加');
$btn.off('click').click(function() { removeFriend(username); });
} else if (status === 'pending') {
$btn.addClass('friend-pending-btn').text('⏳ 等待确认').off('click');
} else {
$btn.addClass('friend-add-btn').text('+ 加好友');
$btn.off('click').click(function() { sendFriendRequest(username); });
}
}
function showFriendRequests() {
loadFriendRequests(currentUser, function(requests) {
var $list = $('#friend-requests-list');
if (!$list.length) return;
$list.empty();
if (requests.length === 0) {
$list.append('<p style="color:#999;">暂无好友请求</p>');
return;
}
requests.forEach(function(r) {
var $item = $('<div>', { class: 'friend-request-item' });
$item.append('<span><a href="/w/User:' + encodeURIComponent(r.from) + '">' + r.from + '</a></span>');
var $actions = $('<div>');
$actions.append('<button class="friend-accept-btn" data-from="' + r.from + '">接受</button>');
$actions.append('<button class="friend-reject-btn" data-from="' + r.from + '">拒绝</button>');
$item.append($actions);
$list.append($item);
});
$list.off('click').on('click', '.friend-accept-btn', function() {
acceptFriendRequest($(this).data('from'));
}).on('click', '.friend-reject-btn', function() {
rejectFriendRequest($(this).data('from'));
});
});
}
function showFriendList() {
loadFriendList(currentUser, function(friends) {
loadFriendRequests(currentUser, function(requests) {
var $overlay = $('<div>', { class: 'friend-overlay' });
var $dialog = $('<div>', { class: 'friend-dialog' });
var realCount = friends.length;
$dialog.append('<h3>👥 好友列表 <span class="friend-count">' + realCount + '人</span></h3>');
if (requests.length > 0) {
$dialog.append('<p style="color:#f44336;cursor:pointer;" id="friend-req-notice">📩 有 ' + requests.length + ' 条好友请求,点击查看</p>');
}
$dialog.append('<div id="friend-requests-list" style="display:none;margin:10px 0;"></div>');
if (realCount === 0) {
$dialog.append('<p style="color:#999;">还没有好友,去其他用户页面加好友吧!</p>');
} else {
var $container = $('<div>', { id: 'friend-list-container', style: 'margin:10px 0;' });
friends.forEach(function(f) {
var $item = $('<span>', { class: 'friend-list-item' });
$item.append('<a href="/w/User:' + encodeURIComponent(f) + '">' + f + '</a>');
$item.append(' <a href="javascript:void(0)" class="friend-msg-link" data-friend="' + f + '" title="发私信">✉️</a>');
$item.append('<span class="friend-remove" data-friend="' + f + '"> ×</span>');
$container.append($item);
});
$dialog.append($container);
}
// ========== 推荐用户 ==========
$dialog.append('<h4 style="margin-top:20px;border-top:1px solid #e0e0e0;padding-top:12px;">推荐用户</h4>');
$dialog.append('<div id="recommend-users" style="font-size:12px;color:#888;">加载中...</div>');
$.getJSON(mw.util.wikiScript('api'), {
action: 'query',
list: 'allusers',
aulimit: 20,
auactiveusers: 1,
format: 'json'
}, function(data) {
var allUsers = [];
if (data.query && data.query.allusers) {
data.query.allusers.forEach(function(u) {
if (u.name !== currentUser && friends.indexOf(u.name) === -1) {
allUsers.push(u.name);
}
});
}
var $rec = $('#recommend-users').empty();
if (allUsers.length === 0) {
$rec.html('<span style="color:#999;">没有推荐用户</span>');
} else {
allUsers.slice(0, 15).forEach(function(u) {
var $item = $('<span>', { class: 'friend-list-item', style: 'cursor:pointer;' });
$item.append('<a href="/w/User:' + encodeURIComponent(u) + '">' + u + '</a>');
$item.append(' <span style="color:#4CAF50;font-size:11px;cursor:pointer;" class="add-friend-btn" data-user="' + u + '">+好友</span>');
$rec.append($item);
});
}
});
$dialog.append('<button style="margin-top:10px;padding:8px 20px;cursor:pointer;background:#888;color:#fff;border:none;border-radius:6px;">关闭</button>');
$overlay.append($dialog);
$('body').append($overlay);
$overlay.on('click', function(e) {
if ($(e.target).is($overlay) || $(e.target).text() === '关闭') {
$overlay.remove();
}
});
$dialog.on('click', '#friend-req-notice', function() {
var $reqList = $('#friend-requests-list');
$reqList.toggle();
if ($reqList.is(':visible')) showFriendRequests();
});
$dialog.on('click', '.friend-remove', function() {
removeFriend($(this).data('friend'));
});
$dialog.on('click', '.friend-msg-link', function() {
var friendName = $(this).data('friend');
$overlay.remove();
sendMessage(friendName);
});
// 推荐用户快速加好友
$dialog.on('click', '.add-friend-btn', function() {
var toUser = $(this).data('user');
$overlay.remove();
sendFriendRequest(toUser);
});
});
});
}
if (mw.config.get('wgNamespaceNumber') === 2 && mw.config.get('wgTitle').indexOf('/') === -1) {
var pageUser = mw.config.get('wgTitle');
if (pageUser !== currentUser) {
var $btn = $('<button>', {
id: 'friend-action-btn',
class: 'friend-btn friend-add-btn',
text: '+ 加好友'
});
$('#firstHeading').append($btn);
loadFriendList(currentUser, function(myFriends) {
if (myFriends.indexOf(pageUser) !== -1) {
updateFriendButton(pageUser, 'added');
} else {
loadFriendRequests(pageUser, function(requests) {
var alreadySent = requests.some(function(r) { return r.from === currentUser; });
if (alreadySent) {
updateFriendButton(pageUser, 'pending');
} else {
$btn.click(function() { sendFriendRequest(pageUser); });
}
});
}
});
}
}
if (currentUser) {
var $userMenu = $('#pt-userpage, .citizen-userMenu').first();
if ($userMenu.length) {
$userMenu.after(' <a href="javascript:void(0)" id="friend-list-trigger" style="font-size:13px;" title="好友列表">👥</a>');
}
$(document).on('click', '#friend-list-trigger', function() {
showFriendList();
});
setInterval(function() {
loadFriendRequests(currentUser, function(requests) {
var $notice = $('#friend-request-count');
if (requests.length > 0) {
if (!$notice.length && $('#friend-list-trigger').length) {
$('#friend-list-trigger').after('<span id="friend-request-count" style="color:#f44336;font-size:11px;vertical-align:super;">' + requests.length + '</span>');
} else if ($notice.length) {
$notice.text(requests.length);
}
} else {
$notice.remove();
}
});
}, 60000);
}
// ==================== 私信系统 ====================
var msgApi = new mw.Api();
function getMsgPageName(username) {
return 'User:' + username + '/messages';
}
function loadMessages(callback) {
if (!currentUser) { callback([]); return; }
msgApi.get({
action: 'query',
titles: getMsgPageName(currentUser),
prop: 'revisions',
rvprop: 'content',
rvlimit: 1
}).then(function(data) {
var pages = data.query.pages;
for (var id in pages) {
if (pages[id].revisions && pages[id].revisions[0]) {
try { callback(JSON.parse(pages[id].revisions[0]['*'])); return; } catch(e) {}
}
}
callback([]);
}).fail(function() { callback([]); });
}
function saveMessages(msgs, callback) {
if (!currentUser) return;
msgApi.postWithEditToken({
action: 'edit',
title: getMsgPageName(currentUser),
text: JSON.stringify(msgs, null, 2),
summary: '更新私信',
minor: true,
bot: true
}).then(function() {
if (callback) callback(true);
}).fail(function() {
if (callback) callback(false);
});
}
function getUnreadCount(msgs) {
var count = 0;
msgs.forEach(function(m) { if (!m.read) count++; });
return count;
}
function updateMsgBadge() {
loadMessages(function(msgs) {
var count = getUnreadCount(msgs);
var $badge = $('#msg-badge');
if ($badge.length === 0) {
var $drawer = $('.citizen-drawer').first();
if ($drawer.length) {
$drawer.css('position', 'relative');
$drawer.append('<span id="msg-badge" class="msg-badge" title="新私信">0</span>');
$badge = $('#msg-badge');
}
}
if ($badge.length) {
$badge.text(count).toggle(count > 0);
}
});
}
function showInbox() {
loadMessages(function(msgs) {
var $overlay = $('<div>', { class: 'msg-overlay' });
var $box = $('<div>', { class: 'msg-box' });
$box.append('<h3 style="margin-bottom:15px;">📬 私信箱</h3>');
msgs.sort(function(a, b) { return b.time - a.time; });
if (msgs.length === 0) {
$box.append('<p style="color:#999;">暂无消息</p>');
} else {
msgs.forEach(function(m, index) {
var $item = $('<div>', { class: 'msg-item' + (m.read ? '' : ' unread') });
var timeStr = new Date(m.time).toLocaleString('zh-CN');
$item.append(
'<div><span class="msg-sender">' + m.from + '</span><span class="msg-time">' + timeStr + '</span></div>',
'<div class="msg-text">' + m.text.replace(/</g,'<').replace(/>/g,'>').replace(/\n/g,'<br>') + '</div>',
'<div class="msg-actions">' +
(m.read ? '' : '<button class="mark-read" data-index="' + index + '">已读</button>') +
'<button class="del-msg" data-index="' + index + '">删除</button>' +
'<button class="reply-msg" data-index="' + index + '" data-from="' + m.from + '">回复</button>' +
'</div>'
);
$box.append($item);
});
}
$box.append('<button style="margin-top:15px;padding:8px 20px;cursor:pointer;background:#888;color:#fff;border:none;border-radius:6px;">关闭</button>');
$overlay.append($box);
$('body').append($overlay);
$overlay.on('click', function(e) {
if ($(e.target).is($overlay) || $(e.target).text() === '关闭') {
$overlay.remove();
updateMsgBadge();
}
});
$box.on('click', '.mark-read', function() {
var idx = parseInt($(this).data('index'));
msgs[idx].read = true;
saveMessages(msgs, function() {
updateMsgBadge();
$overlay.remove();
showInbox();
});
});
$box.on('click', '.del-msg', function() {
var idx = parseInt($(this).data('index'));
msgs.splice(idx, 1);
saveMessages(msgs, function() {
$overlay.remove();
showInbox();
});
});
$box.on('click', '.reply-msg', function() {
var toUser = $(this).data('from');
$overlay.remove();
sendMessage(toUser);
});
});
}
function sendMessage(toUser) {
if (!toUser) return;
var $overlay = $('<div>', { class: 'msg-overlay' });
var $box = $('<div>', { class: 'msg-box' });
$box.append('<h3 style="margin-bottom:10px;">✉️ 发送私信给 ' + toUser + '</h3>');
$box.append('<textarea class="msg-textarea" placeholder="输入消息内容..."></textarea>');
$box.append(
'<button class="msg-send-submit" style="margin-top:10px;padding:8px 20px;cursor:pointer;background:#4CAF50;color:#fff;border:none;border-radius:6px;">发送</button>',
'<button style="margin-left:10px;padding:8px 20px;cursor:pointer;background:#888;color:#fff;border:none;border-radius:6px;">取消</button>'
);
$overlay.append($box);
$('body').append($overlay);
$overlay.on('click', function(e) {
if ($(e.target).is($overlay) || $(e.target).text() === '取消') {
$overlay.remove();
}
});
$box.on('click', '.msg-send-submit', function() {
var text = $box.find('.msg-textarea').val().trim();
if (!text) return;
$box.find('.msg-send-submit').prop('disabled', true).text('发送中...');
var tempApi = new mw.Api();
tempApi.get({
action: 'query',
titles: getMsgPageName(toUser),
prop: 'revisions',
rvprop: 'content',
rvlimit: 1
}).then(function(data) {
var pages = data.query.pages;
var msgs = [];
for (var id in pages) {
if (pages[id].revisions && pages[id].revisions[0]) {
try { msgs = JSON.parse(pages[id].revisions[0]['*']); } catch(e) {}
}
}
msgs.push({
from: currentUser,
to: toUser,
text: text,
time: Date.now(),
read: false
});
tempApi.postWithEditToken({
action: 'edit',
title: getMsgPageName(toUser),
text: JSON.stringify(msgs, null, 2),
summary: currentUser + ' 发来一条私信',
minor: false,
bot: false
}).then(function() {
$overlay.remove();
alert('私信已发送给 ' + toUser + '!');
}).fail(function(err) {
$overlay.remove();
alert('发送失败:' + (err.error && err.error.info ? err.error.info : '未知错误'));
});
}).fail(function() {
$overlay.remove();
alert('发送失败,请稍后重试。');
});
});
}
if (mw.config.get('wgNamespaceNumber') === 2 && mw.config.get('wgTitle').indexOf('/') === -1) {
var msgPageUser = mw.config.get('wgTitle');
if (msgPageUser !== currentUser) {
var $msgBtn = $('<button>', {
text: '✉️ 发送私信',
class: 'msg-send-btn',
click: function() { sendMessage(msgPageUser); }
});
$('#firstHeading').append($msgBtn);
}
}
if (currentUser) {
updateMsgBadge();
$(document).on('click', '#msg-badge', function() {
showInbox();
});
var $msgEntry = $('<a>', {
href: 'javascript:void(0)',
id: 'msg-inbox-trigger',
text: '✉️',
title: '私信箱',
css: { 'font-size': '13px', 'margin-left': '8px', 'cursor': 'pointer', 'text-decoration': 'none' }
});
var $friendTrigger = $('#friend-list-trigger');
if ($friendTrigger.length) {
$friendTrigger.after(' ');
$friendTrigger.after($msgEntry);
}
$(document).on('click', '#msg-inbox-trigger', function() {
showInbox();
});
setInterval(function() {
updateMsgBadge();
}, 30000);
}
// ==================== 版本更新公告弹窗 ====================
function checkUpdateNotice() {
var $versionEl = $('.update-version');
if ($versionEl.length === 0) return;
var currentVersion = $versionEl.text().trim();
if (!currentVersion) return;
var dismissedVersion = localStorage.getItem('mw_update_dismissed');
if (dismissedVersion !== currentVersion) {
var $overlay = $('<div>', {
css: {
position: 'fixed', top: 0, left: 0, width: '100%', height: '100%',
background: 'rgba(0,0,0,0.6)', 'z-index': 99999,
display: 'flex', 'align-items': 'center', 'justify-content': 'center'
}
});
var $box = $('<div>', {
css: {
background: '#fff', 'border-radius': '12px', padding: '30px',
'max-width': '550px', width: '90%', 'max-height': '80vh',
'overflow-y': 'auto', 'box-shadow': '0 8px 30px rgba(0,0,0,0.4)',
position: 'relative'
}
});
var $content = $('<div>').css({'text-align':'left','font-size':'14px','line-height':'1.8'}).html(
'<div style="text-align:center;font-size:18px;font-weight:bold;margin-bottom:5px;">【植物大战僵尸杂交版0.25.5版本更新公告】</div>' +
'<div style="text-align:center;font-size:12px;color:#888;margin-bottom:15px;">植物大战僵尸杂交版(重制版)0.25.5</div>' +
'<a href="https://www.bilibili.com/opus/1226688839230160904?spm_id_from=333.1365.0.0" target="_blank">【杂交版0.25.5版本更新】</a><br>' +
'电脑(Windows)版链接:https://pan.quark.cn/s/4259e472de02<br>' +
'其他版本:https://pan.quark.cn/s/a03f4fffc55b<br><br>'
);
var $closeBtn = $('<button>', {
text: '我知道了',
css: {
display: 'block', margin: '20px auto 0',
background: '#4CAF50', color: '#fff', border: 'none',
padding: '10px 30px', 'border-radius': '8px',
'font-size': '16px', cursor: 'pointer'
},
click: function() {
localStorage.setItem('mw_update_dismissed', currentVersion);
$overlay.fadeOut(300, function() { $(this).remove(); });
}
});
$box.append($content, $closeBtn);
$overlay.append($box);
$('body').append($overlay);
$overlay.on('click', function(e) {
if ($(e.target).is($overlay)) {
$overlay.fadeOut(300, function() { $(this).remove(); });
}
});
}
}
setTimeout(function() {
checkUpdateNotice();
}, 500);
(function() {
var posKey = 'mw_edit_position_' + mw.config.get('wgPageName');
var savedPos = localStorage.getItem(posKey);
// 滚动时保存位置
$(window).on('scroll', function() {
var pos = $(window).scrollTop();
if (pos > 100) {
localStorage.setItem(posKey, pos);
}
});
// 恢复位置
if (savedPos && mw.config.get('wgAction') === 'view') {
var pos = parseInt(savedPos);
if (pos > 100) {
setTimeout(function() {
$('html, body').animate({ scrollTop: pos }, 300);
}, 600);
}
}
})();
// ==================== 背景音乐开关 ====================
if (mw.config.get('wgUserName')) {
var musicKey = 'wiki_bgm_enabled';
var musicEnabled = localStorage.getItem(musicKey) === 'true';
var bgmAudio = null;
function insertMusicSwitch() {
var $target = $('.citizen-preferences-section__content').last();
if (!$target.length) return;
if ($('.bgm-switch').length) return;
var $switch = $(
'<span class="cdx-toggle-switch cdx-toggle-switch--align-switch citizen-preferences-group bgm-switch">' +
'<input id="bgm-toggle" class="cdx-toggle-switch__input" type="checkbox" role="switch"' + (musicEnabled ? ' checked' : '') + '>' +
'<span class="cdx-toggle-switch__switch"><span class="cdx-toggle-switch__switch__grip"></span></span>' +
'<div class="cdx-label cdx-toggle-switch__label">' +
'<label class="cdx-label__label" for="bgm-toggle"><span class="cdx-label__label__text">背景音乐</span></label>' +
'<span class="cdx-label__description">开启或关闭全站背景音乐</span>' +
'</div>' +
'</span>'
);
$target.append($switch);
$('#bgm-toggle').on('change', function() {
musicEnabled = $(this).is(':checked');
localStorage.setItem(musicKey, musicEnabled ? 'true' : 'false');
toggleMusic(musicEnabled);
});
}
function toggleMusic(enable) {
if (enable) {
if (!bgmAudio) {
bgmAudio = new Audio('https://music.163.com/song/media/outer/url?id=2692379520.mp3');
bgmAudio.loop = true;
bgmAudio.volume = 0.3;
}
bgmAudio.play().catch(function() {
$(document).one('click', function() {
bgmAudio.play();
});
});
} else {
if (bgmAudio) {
bgmAudio.pause();
bgmAudio.currentTime = 0;
}
}
}
// 默认关闭
if (!musicEnabled) {
$('#wiki-bgm').remove();
}
var musicObserver = new MutationObserver(function() {
if ($('.citizen-preferences-section__content').is(':visible') && !$('.bgm-switch').length) {
insertMusicSwitch();
}
});
musicObserver.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['class', 'style'] });
if ($('.citizen-preferences-section__content').is(':visible')) insertMusicSwitch();
}
// ==================== Wiki 宠物 ====================
(function() {
var pets = {
'冰瓜香蒲': {
idle: 'https://new.pvzhe.wiki/images/1/10/%E5%86%B0%E7%93%9C%E9%A6%99%E8%92%B2%E5%BE%85%E6%9C%BA.gif',
petting: 'https://new.pvzhe.wiki/images/4/47/%E5%86%B0%E7%93%9C%E9%A6%99%E8%92%B2%E6%8A%9A%E6%91%B8.gif',
size: 100
},
'小猫向日葵': {
idle: 'https://new.pvzhe.wiki/images/9/9b/%E5%B0%8F%E7%8C%AB%E5%90%91%E6%97%A5%E8%91%B5%E5%BE%85%E6%9C%BA.gif',
petting: 'https://new.pvzhe.wiki/images/8/85/%E5%B0%8F%E7%8C%AB%E5%90%91%E6%97%A5%E8%91%B5%E6%8A%9A%E6%91%B8.gif',
size: 100
}
};
var petNames = Object.keys(pets);
var currentPet = localStorage.getItem('wiki_pet_current') || '冰瓜香蒲';
if (!pets[currentPet]) currentPet = '冰瓜香蒲';
var petState = 'idle';
var petTimer = null;
var affectionKey = 'wiki_pet_affection_';
var positionKey = 'wiki_pet_position';
var affection = parseInt(localStorage.getItem(affectionKey + currentPet)) || 0;
var affectionPerPet = 5;
var affectionCooldown = 100;
var lastPetTime = 0;
var isDragging = false;
var dragStartX, dragStartY, petStartX, petStartY;
var hasMoved = false;
var levels = [
{ min: 0, title: '陌生', emoji: '🤔' },
{ min: 50, title: '认识', emoji: '👋' },
{ min: 150, title: '友好', emoji: '😊' },
{ min: 400, title: '亲密', emoji: '💚' },
{ min: 1000, title: '挚友', emoji: '💖' },
{ min: 2500, title: '灵魂伴侣', emoji: '✨' }
];
function getLevel(aff) {
var lvl = levels[0];
for (var i = levels.length - 1; i >= 0; i--) {
if (aff >= levels[i].min) { lvl = levels[i]; break; }
}
return lvl;
}
function saveAffection() { localStorage.setItem(affectionKey + currentPet, affection); }
function savePosition() {
var pos = $pet.position();
var bottom = $(window).height() - pos.top - $pet.height();
localStorage.setItem(positionKey, JSON.stringify({ left: pos.left, bottom: Math.max(0, bottom) }));
}
function showAffectionPanel() {
var lvl = getLevel(affection);
var nextLvl = null;
for (var i = 0; i < levels.length; i++) {
if (affection < levels[i].min) { nextLvl = levels[i]; break; }
}
var text = lvl.emoji + ' ' + lvl.title + ' (' + affection + ')';
if (nextLvl) {
var progress = Math.round((affection - lvl.min) / (nextLvl.min - lvl.min) * 100);
text += ' → ' + nextLvl.emoji + ' ' + progress + '%';
} else { text += ' MAX'; }
$affectionPanel.text(text).css('color', '#fff');
}
function spawnHeart(x, y) {
var hearts = ['💚', '💙', '💛', '💜', '❤️', '💖', '✨'];
var heart = hearts[Math.floor(Math.random() * hearts.length)];
var $heart = $('<div>', { class: 'pet-heart', text: heart, css: { left: x, top: y } });
$('body').append($heart);
setTimeout(function() { $heart.remove(); }, 1500);
}
function triggerLevelUp() {
$pet.addClass('level-up');
setTimeout(function() { $pet.removeClass('level-up'); }, 800);
}
function switchPet(newPet) {
if (newPet === currentPet) return;
currentPet = newPet;
localStorage.setItem('wiki_pet_current', currentPet);
affection = parseInt(localStorage.getItem(affectionKey + currentPet)) || 0;
$img.attr('src', pets[currentPet].idle);
showAffectionPanel();
setPetState('idle');
}
var savedPos = JSON.parse(localStorage.getItem(positionKey)) || { left: 20, bottom: 20 };
var $pet = $('<div>', {
class: 'wiki-pet',
css: { opacity: 0, left: savedPos.left + 'px', bottom: savedPos.bottom + 'px', top: 'auto', right: 'auto' }
});
var $img = $('<img>', {
src: pets[currentPet].idle,
alt: currentPet,
draggable: 'false',
css: { width: pets[currentPet].size + 'px', height: 'auto' }
});
var $affectionPanel = $('<div>', { class: 'pet-affection' });
var $tooltip = $('<div>', { class: 'pet-tooltip', text: '点击抚摸我~ 🐱' });
var $switchBtn = $('<button>', { class: 'pet-switch-btn', text: '🔄', title: '切换宠物' });
$pet.append($img, $affectionPanel, $tooltip, $switchBtn);
$('body').append($pet);
showAffectionPanel();
setTimeout(function() { $pet.animate({ opacity: 1 }, 500); }, 1000);
$switchBtn.on('click', function(e) {
e.stopPropagation();
e.preventDefault();
var idx = petNames.indexOf(currentPet);
var nextIdx = (idx + 1) % petNames.length;
switchPet(petNames[nextIdx]);
});
function setPetState(state) {
if (petState === state) return;
petState = state;
clearTimeout(petTimer);
$pet.removeClass('petting');
if (state === 'idle') {
$img.attr('src', pets[currentPet].idle);
$img.css({ width: pets[currentPet].size + 'px', height: 'auto' });
$tooltip.text('点击抚摸我~ ' + getLevel(affection).emoji);
} else if (state === 'petting') {
$img.attr('src', pets[currentPet].petting);
var petSize = currentPet === '小猫向日葵' ? pets[currentPet].size * 1.2 : Math.round(pets[currentPet].size * 1.2);
$img.css({ width: petSize + 'px', height: 'auto' });
$pet.addClass('petting');
$tooltip.text('好舒服~ 💚');
petTimer = setTimeout(function() { setPetState('idle'); }, 1200);
}
}
$pet.on('mousedown', function(e) {
if (e.button !== 0) return;
if ($(e.target).is($switchBtn)) return;
isDragging = true; hasMoved = false;
dragStartX = e.clientX; dragStartY = e.clientY;
petStartX = $pet.offset().left; petStartY = $pet.offset().top;
$pet.css('transition', 'none');
e.preventDefault();
});
$(document).on('mousemove', function(e) {
if (!isDragging) return;
var dx = e.clientX - dragStartX, dy = e.clientY - dragStartY;
if (Math.abs(dx) > 3 || Math.abs(dy) > 3) hasMoved = true;
var newX = Math.max(0, Math.min(petStartX + dx, window.innerWidth - pets[currentPet].size));
var newY = Math.max(0, Math.min(petStartY + dy, window.innerHeight - pets[currentPet].size));
$pet.css({ left: newX + 'px', top: newY + 'px', bottom: 'auto', right: 'auto' });
});
$(document).on('mouseup', function() {
if (!isDragging) return;
isDragging = false;
$pet.css('transition', 'transform 0.2s');
savePosition();
});
$pet.on('click', function(e) {
if ($(e.target).is($switchBtn)) return;
e.stopPropagation();
if (hasMoved) return;
var now = Date.now();
if (now - lastPetTime < affectionCooldown) return;
lastPetTime = now;
setPetState('petting');
var oldLevel = getLevel(affection).title;
affection += affectionPerPet;
saveAffection();
showAffectionPanel();
var newLevel = getLevel(affection).title;
if (newLevel !== oldLevel) {
triggerLevelUp();
$affectionPanel.text('🎉 升级!' + newLevel + '!').css('color', '#FFD700');
setTimeout(function() { showAffectionPanel(); }, 2000);
}
var offset = $pet.offset();
spawnHeart(offset.left + pets[currentPet].size / 3, offset.top - 10);
});
setInterval(function() {
if (petState === 'idle' && !isDragging && Math.random() < 0.3) {
$pet.css('transform', 'scale(1.05) translateY(-5px)');
setTimeout(function() { $pet.css('transform', 'scale(1) translateY(0)'); }, 500);
}
}, 10000);
$pet.on('touchstart', function(e) {
$pet.addClass('touching');
var touch = e.originalEvent.touches[0];
isDragging = true; hasMoved = false;
dragStartX = touch.clientX; dragStartY = touch.clientY;
petStartX = $pet.offset().left; petStartY = $pet.offset().top;
$pet.css('transition', 'none');
e.stopPropagation();
});
$pet.on('touchend', function() {
$pet.removeClass('touching');
});
$(document).on('touchmove', function(e) {
if (!isDragging) return;
var touch = e.originalEvent.touches[0];
var dx = touch.clientX - dragStartX, dy = touch.clientY - dragStartY;
if (Math.abs(dx) > 3 || Math.abs(dy) > 3) hasMoved = true;
var newX = Math.max(0, Math.min(petStartX + dx, window.innerWidth - pets[currentPet].size));
var newY = Math.max(0, Math.min(petStartY + dy, window.innerHeight - pets[currentPet].size));
$pet.css({ left: newX + 'px', top: newY + 'px', bottom: 'auto', right: 'auto' });
});
$(document).on('touchend', function() {
if (!isDragging) return;
isDragging = false;
$pet.css('transition', 'transform 0.2s');
$pet.removeClass('touching');
savePosition();
if (!hasMoved) {
var now = Date.now();
if (now - lastPetTime < affectionCooldown) return;
lastPetTime = now;
setPetState('petting');
var oldLevel = getLevel(affection).title;
affection += affectionPerPet;
saveAffection();
showAffectionPanel();
var newLevel = getLevel(affection).title;
if (newLevel !== oldLevel) {
triggerLevelUp();
$affectionPanel.text('🎉 升级!' + newLevel + '!').css('color', '#FFD700');
setTimeout(function() { showAffectionPanel(); }, 2000);
}
var offset = $pet.offset();
spawnHeart(offset.left + pets[currentPet].size / 3, offset.top - 10);
}
});
})();
// ==================== 阅读位置记忆 ====================
(function() {
var storageKey = 'mw_readposition_enabled';
var positionKey = 'mw_readposition_data';
var enabled = localStorage.getItem(storageKey) === 'true';
function restorePosition() {
if (!enabled) return;
var data = JSON.parse(localStorage.getItem(positionKey) || '{}');
var currentPage = mw.config.get('wgPageName');
if (data[currentPage]) {
var scrollTarget = data[currentPage];
$(function() {
setTimeout(function() {
$('html, body').scrollTop(scrollTarget);
}, 500);
});
}
}
function savePosition() {
if (!enabled) return;
var currentPage = mw.config.get('wgPageName');
var scrollTop = $(window).scrollTop();
if (scrollTop > 50) {
var data = JSON.parse(localStorage.getItem(positionKey) || '{}');
data[currentPage] = scrollTop;
localStorage.setItem(positionKey, JSON.stringify(data));
}
}
function insertSwitch() {
var $target = $('.citizen-preferences-section__content').last();
if (!$target.length) return;
if ($('.readposition-switch').length) return;
var $switch = $(
'<span class="cdx-toggle-switch cdx-toggle-switch--align-switch citizen-preferences-group readposition-switch">' +
'<input id="readposition-toggle" class="cdx-toggle-switch__input" type="checkbox" role="switch"' + (enabled ? ' checked' : '') + '>' +
'<span class="cdx-toggle-switch__switch">' +
'<span class="cdx-toggle-switch__switch__grip"></span>' +
'</span>' +
'<div class="cdx-label cdx-toggle-switch__label">' +
'<label class="cdx-label__label" for="readposition-toggle">' +
'<span class="cdx-label__label__text">记住阅读位置</span>' +
'</label>' +
'<span class="cdx-label__description">返回页面时恢复上次滚动位置</span>' +
'</div>' +
'</span>'
);
$target.append($switch);
$('#readposition-toggle').on('change', function() {
enabled = $(this).is(':checked');
localStorage.setItem(storageKey, enabled ? 'true' : 'false');
if (!enabled) {
localStorage.removeItem(positionKey);
}
});
}
$(window).on('beforeunload', function() {
savePosition();
});
setInterval(function() {
savePosition();
}, 5000);
restorePosition();
var observer = new MutationObserver(function(mutations) {
mutations.forEach(function(mutation) {
if ($('.citizen-preferences-section__content').is(':visible') && !$('.readposition-switch').length) {
insertSwitch();
}
});
});
observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['class', 'style'] });
if ($('.citizen-preferences-section__content').is(':visible')) {
insertSwitch();
}
})();
// ==================== 宠物显示开关 ====================
(function() {
var petVisibleKey = 'wiki_pet_visible';
var petVisible = localStorage.getItem(petVisibleKey) !== 'false';
function togglePet(show) {
if (show) {
$('.wiki-pet').fadeIn(300);
} else {
$('.wiki-pet').fadeOut(300);
}
}
function insertPetSwitch() {
var $target = $('.citizen-preferences-section__content').last();
if (!$target.length) return;
if ($('.pet-visible-switch').length) return;
var $switch = $(
'<span class="cdx-toggle-switch cdx-toggle-switch--align-switch citizen-preferences-group pet-visible-switch">' +
'<input id="pet-visible-toggle" class="cdx-toggle-switch__input" type="checkbox" role="switch"' + (petVisible ? ' checked' : '') + '>' +
'<span class="cdx-toggle-switch__switch">' +
'<span class="cdx-toggle-switch__switch__grip"></span>' +
'</span>' +
'<div class="cdx-label cdx-toggle-switch__label">' +
'<label class="cdx-label__label" for="pet-visible-toggle">' +
'<span class="cdx-label__label__text">显示宠物</span>' +
'</label>' +
'<span class="cdx-label__description">在页面显示Wiki宠物</span>' +
'</div>' +
'</span>'
);
$target.append($switch);
$('#pet-visible-toggle').on('change', function() {
petVisible = $(this).is(':checked');
localStorage.setItem(petVisibleKey, petVisible ? 'true' : 'false');
togglePet(petVisible);
});
}
if (!petVisible) {
var checkPet = setInterval(function() {
if ($('.wiki-pet').length) {
$('.wiki-pet').css({ opacity: 0, display: 'none' });
clearInterval(checkPet);
}
}, 100);
setTimeout(function() { clearInterval(checkPet); }, 5000);
}
var observer = new MutationObserver(function(mutations) {
mutations.forEach(function(mutation) {
if ($('.citizen-preferences-section__content').is(':visible') && !$('.pet-visible-switch').length) {
insertPetSwitch();
}
});
});
observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['class', 'style'] });
if ($('.citizen-preferences-section__content').is(':visible')) {
insertPetSwitch();
}
})();
// ==================== 回到顶部按钮开关 ====================
(function() {
var backToTopKey = 'wiki_backtotop_visible';
var backToTopVisible = localStorage.getItem(backToTopKey) !== 'false';
function toggleBackToTop(show) {
if (show) {
$('#back-to-top').fadeIn(300);
} else {
$('#back-to-top').fadeOut(300);
}
}
function insertBackToTopSwitch() {
var $target = $('.citizen-preferences-section__content').last();
if (!$target.length) return;
if ($('.backtotop-switch').length) return;
var $switch = $(
'<span class="cdx-toggle-switch cdx-toggle-switch--align-switch citizen-preferences-group backtotop-switch">' +
'<input id="backtotop-toggle" class="cdx-toggle-switch__input" type="checkbox" role="switch"' + (backToTopVisible ? ' checked' : '') + '>' +
'<span class="cdx-toggle-switch__switch">' +
'<span class="cdx-toggle-switch__switch__grip"></span>' +
'</span>' +
'<div class="cdx-label cdx-toggle-switch__label">' +
'<label class="cdx-label__label" for="backtotop-toggle">' +
'<span class="cdx-label__label__text">回到顶部按钮</span>' +
'</label>' +
'<span class="cdx-label__description">在页面右下角显示回到顶部按钮</span>' +
'</div>' +
'</span>'
);
$target.append($switch);
$('#backtotop-toggle').on('change', function() {
backToTopVisible = $(this).is(':checked');
localStorage.setItem(backToTopKey, backToTopVisible ? 'true' : 'false');
toggleBackToTop(backToTopVisible);
});
}
if (!backToTopVisible) {
var checkBtn = setInterval(function() {
if ($('#back-to-top').length) {
$('#back-to-top').css({ opacity: 0, display: 'none' });
clearInterval(checkBtn);
}
}, 100);
setTimeout(function() { clearInterval(checkBtn); }, 5000);
}
var observer = new MutationObserver(function(mutations) {
mutations.forEach(function(mutation) {
if ($('.citizen-preferences-section__content').is(':visible') && !$('.backtotop-switch').length) {
insertBackToTopSwitch();
}
});
});
observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['class', 'style'] });
if ($('.citizen-preferences-section__content').is(':visible')) {
insertBackToTopSwitch();
}
})();
// ==================== 卡片自动计数 ====================
var $allCards = $('.pvzhe-card');
if ($allCards.length > 0) {
var pageTitle = mw.config.get('wgTitle');
var typeName = pageTitle.indexOf('僵尸') !== -1 ? '僵尸' : '植物';
var $counter = $('<span>', {
class: 'card-counter',
text: '共 ' + $allCards.length + ' 个' + typeName
});
var $filterBox = $('.pvzhe-filter-box').first();
if ($filterBox.length) {
$filterBox.before($counter);
}
}
// ==================== 外部链接确认 ====================
$(document).on('click', 'a', function(e) {
var href = $(this).attr('href') || '';
if (href.indexOf('http') === 0 && href.indexOf(window.location.hostname) === -1) {
if (!confirm('即将离开本站,确定吗?')) {
e.preventDefault();
}
}
});
// ==================== 字体切换开关 ====================
(function() {
var fontKey = 'wiki_font';
var currentFont = localStorage.getItem(fontKey) || 'standard';
function applyFont(font) {
$('body').removeClass('font-cartoon font-cartoon-italic font-fzjianzhi font-handwrite font-huapop font-fzkatong font-fzcq');
if (font === 'cartoon') {
$('body').addClass('font-cartoon');
} else if (font === 'cartoon-italic') {
$('body').addClass('font-cartoon-italic');
} else if (font === 'fzjianzhi') {
$('body').addClass('font-fzjianzhi');
} else if (font === 'handwrite') {
$('body').addClass('font-handwrite');
} else if (font === 'huapop') {
$('body').addClass('font-huapop');
} else if (font === 'fzkatong') {
$('body').addClass('font-fzkatong');
} else if (font === 'fzcq') {
$('body').addClass('font-fzcq');
}
}
function insertFontSwitch() {
var $target = $('#skin-client-prefs-citizen-feature-custom-width');
if (!$target.length) return;
if ($('.font-switch').length) return;
var selectHtml = '<select class="cdx-select-vue__handle" id="font-select" style="width:100%;padding:8px;border:1px solid #ccc;border-radius:4px;">' +
'<option value="standard"' + (currentFont === 'standard' ? ' selected' : '') + '>标准</option>' +
'<option value="fzjianzhi"' + (currentFont === 'fzjianzhi' ? ' selected' : '') + '>方正剪纸</option>' +
'<option value="huapop"' + (currentFont === 'huapop' ? ' selected' : '') + '>华康POP2体W9</option>' +
'<option value="fzkatong"' + (currentFont === 'fzkatong' ? ' selected' : '') + '>方正卡通简体</option>' +
'<option value="fzcq"' + (currentFont === 'fzcq' ? ' selected' : '') + '>方正粗倩</option>' +
'</select>';
var $switch = $(
'<fieldset class="cdx-field cdx-field--is-fieldset citizen-preferences-group font-switch">' +
'<legend class="cdx-label">' +
'<span class="cdx-label__label">' +
'<span class="cdx-label__label__text">字体</span>' +
'</span>' +
'<span class="cdx-label__description">更改Wiki显示字体</span>' +
'</legend>' +
'<div class="cdx-field__control">' +
'<div class="cdx-select-vue">' +
selectHtml +
'</div>' +
'</div>' +
'</fieldset>'
);
$target.after($switch);
$('#font-select').on('change', function() {
var font = $(this).val();
localStorage.setItem(fontKey, font);
applyFont(font);
});
}
applyFont(currentFont);
var observer = new MutationObserver(function() {
if ($('#citizen-preferences-content').is(':visible') && !$('.font-switch').length) {
insertFontSwitch();
}
});
observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['class', 'style'] });
if ($('#citizen-preferences-content').is(':visible')) {
insertFontSwitch();
}
})();
// ==================== 404 页面美化 ====================
if (mw.config.get('wgArticleId') === 0 && mw.config.get('wgNamespaceNumber') === 0 && mw.config.get('wgAction') === 'view') {
var pageName = mw.config.get('wgPageName');
var $overlay = $('<div>', {
css: {
position: 'fixed', top: 0, left: 0, width: '100%', height: '100%',
background: 'rgba(0,0,0,0.6)', zIndex: 99999,
display: 'flex', alignItems: 'center', justifyContent: 'center'
}
});
var $box = $('<div>', {
css: {
background: '#fff', borderRadius: '16px', padding: '40px',
textAlign: 'center', boxShadow: '0 8px 30px rgba(0,0,0,0.4)',
maxWidth: '420px', width: '85%'
}
});
var $icon = $('<div>', {
text: '🚧',
css: { fontSize: '60px', marginBottom: '15px' }
});
var $title = $('<h2>', {
text: '敬请期待',
css: { marginBottom: '10px', fontSize: '24px', color: '#333' }
});
var $desc = $('<p>', {
text: '「' + pageName + '」的页面还是一片荒地,快来成为第一个开拓者吧!',
css: { fontSize: '14px', color: '#888', marginBottom: '25px', lineHeight: '1.6' }
});
var $btnEdit = $('<a>', {
href: '/w/' + encodeURIComponent(pageName) + '?action=edit',
text: '✏️ 编辑此页面',
css: {
display: 'inline-block', padding: '10px 24px', background: '#4CAF50',
color: '#fff', borderRadius: '8px', textDecoration: 'none',
fontSize: '15px', marginRight: '10px', cursor: 'pointer'
}
});
var $btnBack = $('<button>', {
text: '↩ 返回',
css: {
padding: '10px 24px', background: '#888', color: '#fff',
border: 'none', borderRadius: '8px', fontSize: '15px', cursor: 'pointer'
},
click: function() {
history.back();
setTimeout(function() {
if ($overlay.is(':visible')) $overlay.remove();
}, 500);
}
});
$box.append($icon, $title, $desc, $btnEdit, $btnBack);
$overlay.append($box);
$('body').append($overlay);
// 点击遮罩关闭
$overlay.on('click', function(e) {
if ($(e.target).is($overlay)) $overlay.remove();
});
}
// ==================== 页面最后编辑时间 ====================
if (mw.config.get('wgAction') === 'view' && mw.config.get('wgIsArticle')) {
var api = new mw.Api();
api.get({
action: 'query',
prop: 'revisions',
titles: mw.config.get('wgPageName'),
rvlimit: 1,
rvprop: 'timestamp|user'
}).then(function(data) {
var pages = data.query.pages;
for (var id in pages) {
if (pages[id].revisions) {
var rev = pages[id].revisions[0];
var editTime = new Date(rev.timestamp);
var now = new Date();
var today = new Date(now.getFullYear(), now.getMonth(), now.getDate());
var editDay = new Date(editTime.getFullYear(), editTime.getMonth(), editTime.getDate());
var diffDays = Math.floor((today - editDay) / 86400000);
var timeStr;
if (diffDays === 0) {
timeStr = '今天 ' + editTime.toLocaleTimeString('zh-CN', {hour:'2-digit',minute:'2-digit'});
} else if (diffDays === 1) {
timeStr = '昨天 ' + editTime.toLocaleTimeString('zh-CN', {hour:'2-digit',minute:'2-digit'});
} else {
timeStr = diffDays + ' 天前';
}
var $info = $('<div style="color:#999;font-size:12px;margin-top:20px;text-align:right;">' +
'最后编辑:' + rev.user + ' 于 ' + timeStr +
'</div>');
$('#mw-content-text').append($info);
}
}
});
}
// ==================== 稀有度筛选 ====================
if ($('#pvzhe-rarity-filter').length) {
var rarityMap = {
'white': '白卡',
'gold': '至尊金卡',
'diamond': '臻享钻卡',
'rainbow': '梦幻彩卡',
'star': '星光闪卡',
'original': '原卡'
};
// 计数
var counts = { all: $('.pvzhe-card').length };
$('.pvzhe-card-container[data-rarity]').each(function() {
var r = $(this).data('rarity');
counts[r] = $(this).find('.pvzhe-card').length;
});
$('.pvzhe-rarity-btn').each(function() {
var r = $(this).data('rarity');
var label = $(this).text().replace(/\s*\(\d+\)/, '');
var img = $(this).find('img').length ? $(this).find('img').prop('outerHTML') : '';
$(this).html(img + label + ' (' + (counts[r] || 0) + ')');
});
// 点击
$(document).on('click', '.pvzhe-rarity-btn', function() {
$('.pvzhe-rarity-btn').removeClass('active');
$(this).addClass('active');
var r = $(this).data('rarity');
if (r === 'all') {
$('.pvzhe-card-container').css('display', 'flex');
$('.pvzhe-card').show();
return;
}
$('.pvzhe-card-container').css('display', 'none');
$('.pvzhe-card').hide();
$('.pvzhe-card-container[data-rarity="' + r + '"]').css('display', 'flex').find('.pvzhe-card').show();
});
}
// ==================== 编辑贡献排行榜 ====================
if ($('#contribution-rank').length) {
$.getJSON(mw.util.wikiScript('api'), {
action: 'query',
list: 'allusers',
aulimit: 20,
auprop: 'editcount',
auactiveusers: 1,
format: 'json'
}, function(data) {
var users = [];
if (data.query && data.query.allusers) {
data.query.allusers.forEach(function(u) {
if (u.editcount > 0) users.push({ name: u.name, count: u.editcount });
});
}
users.sort(function(a, b) { return b.count - a.count; });
users = users.slice(0, 10);
$('#rank-loading').hide();
var $list = $('#rank-list').show().empty();
users.forEach(function(u, i) {
var numClass = '';
if (i === 0) numClass = 'gold';
else if (i === 1) numClass = 'silver';
else if (i === 2) numClass = 'bronze';
$list.append(
'<li class="rank-item">' +
'<span class="rank-num ' + numClass + '">' + (i + 1) + '</span>' +
'<span class="rank-name"><a href="/w/User:' + encodeURIComponent(u.name) + '">' + u.name + '</a></span>' +
'<span class="rank-count">' + u.count + ' 次编辑</span>' +
'</li>'
);
});
});
}
// ==================== 可重置排行榜 ====================
if ($('#weekly-rank-list').length) {
var rankApi = new mw.Api();
rankApi.get({
action: 'query',
titles: 'MediaWiki:RankStartDate',
prop: 'revisions',
rvprop: 'content',
rvlimit: 1
}).then(function(data) {
var pages = data.query.pages;
var startDate = '';
for (var id in pages) {
if (pages[id].revisions && pages[id].revisions[0]) {
startDate = pages[id].revisions[0]['*'].trim();
}
}
if (!startDate) {
$('#weekly-rank-list').html('未设定起始日期');
return;
}
$('#rank-period').text(startDate + ' 至今');
var start = startDate + 'T00:00:00Z';
var end = new Date().toISOString();
$.getJSON(mw.util.wikiScript('api'), {
action: 'query',
list: 'allusers',
aulimit: 30,
auactiveusers: 1,
format: 'json'
}, function(auData) {
var activeUsers = [];
if (auData.query && auData.query.allusers) {
auData.query.allusers.forEach(function(u) {
if (u.name) activeUsers.push(u.name);
});
}
var allCounts = {};
var done = 0;
activeUsers.forEach(function(username) {
var totalCount = 0;
function fetchContribs(uccontinue) {
var params = {
action: 'query',
list: 'usercontribs',
ucuser: username,
uclimit: 50,
ucprop: 'timestamp',
format: 'json',
ucend: start
};
if (uccontinue) {
params.uccontinue = uccontinue;
} else {
params.ucstart = end;
}
$.getJSON(mw.util.wikiScript('api'), params, function(ucData) {
if (ucData.query && ucData.query.usercontribs) {
totalCount += ucData.query.usercontribs.length;
if (ucData.continue && ucData.continue.uccontinue) {
fetchContribs(ucData.continue.uccontinue);
} else {
if (totalCount > 0) allCounts[username] = totalCount;
done++;
if (done >= activeUsers.length) showResult();
}
} else {
if (totalCount > 0) allCounts[username] = totalCount;
done++;
if (done >= activeUsers.length) showResult();
}
});
}
fetchContribs();
});
function showResult() {
var users = [];
for (var name in allCounts) users.push({ name: name, count: allCounts[name] });
users.sort(function(a, b) { return b.count - a.count; });
users = users.slice(0, 5);
var medals = ['🥇', '🥈', '🥉', '4️⃣', '5️⃣'];
var html = '';
users.forEach(function(u, i) {
html += medals[i] + ' <b>' + u.name + '</b>:' + u.count + ' 次<br>';
});
$('#weekly-rank-list').html(html || '暂无编辑');
}
if (activeUsers.length === 0) {
$('#weekly-rank-list').html('暂无活跃用户');
}
});
});
}
// ==================== 图片懒加载 ====================
$('img').each(function() {
if (!$(this).attr('loading')) {
$(this).attr('loading', 'lazy');
}
});
// 监听动态添加的图片(如筛选后显示的卡片)
var lazyObserver = new MutationObserver(function(mutations) {
mutations.forEach(function(mutation) {
$(mutation.addedNodes).find('img').each(function() {
if (!$(this).attr('loading')) {
$(this).attr('loading', 'lazy');
}
});
});
});
lazyObserver.observe(document.body, { childList: true, subtree: true });
// ==================== 性能优化 ====================
// 1. DNS 预解析
$('head').append('<link rel="dns-prefetch" href="//new.pvzhe.wiki"><link rel="dns-prefetch" href="//images.pvzhe.wiki">');
// ==================== 我是僵尸网格生成器 ====================
if ($('#grid-generate').length) {
function generateGrid() {
var raw = $('#grid-data').val().trim();
var cols = parseInt($('#grid-cols').val()) || 9;
var rows = parseInt($('#grid-rows').val()) || 5;
var bg = $('#grid-bg').val() || '#2d2d2d';
var cellColor = $('#grid-cell').val() || '#3a3a3a';
var border = $('#grid-border').val() || '#555555';
var dividerCol = parseInt($('#grid-divider').val()) || 4;
var lines = raw.split('\n');
var data = {};
lines.forEach(function(line) {
var parts = line.split(',');
if (parts.length >= 3) {
var name = parts[0].trim();
var col = parseInt(parts[1].trim());
var row = parseInt(parts[2].trim());
if (name && col >= 1 && row >= 1) {
var key = row + '-' + col;
data[key] = name;
}
}
});
var table = '{| style="width:100%;max-width:900px;table-layout:fixed;text-align:center;background:' + bg + ';border-collapse:collapse;"\n';
table += '|-\n! !!';
for (var c = 1; c <= cols; c++) {
if (c === dividerCol) {
table += ' 列' + c + ' !!';
} else {
table += ' 列' + c + ' !!';
}
}
table = table.slice(0, -2) + '\n';
for (var r = 1; r <= rows; r++) {
table += '|-\n! 行' + r + '\n';
for (var c = 1; c <= cols; c++) {
var plant = data[r + '-' + c];
var style = 'height:80px;border:2px solid ' + border + ';background:' + cellColor + ';';
if (c === dividerCol) {
style += 'border-right:4px solid red;';
}
if (plant) {
table += '| style="' + style + '" | [[文件:' + plant + '.png|50px|link=' + plant + ']]\n';
} else {
table += '| style="' + style + '" | \n';
}
}
}
table += '|}';
$('#grid-preview').html('<pre style="background:#f9f9f9;padding:10px;border-radius:6px;font-size:12px;max-height:400px;overflow:auto;">' + table.replace(/</g,'<').replace(/>/g,'>') + '</pre>');
$('#grid-preview').data('code', table);
}
$('#grid-generate').click(generateGrid);
$('#grid-copy').click(function() {
var code = $('#grid-preview').data('code');
if (code) {
navigator.clipboard.writeText(code).then(function() {
$('#grid-copy').text('✅ 已复制');
setTimeout(function() { $('#grid-copy').text('复制代码'); }, 1500);
});
}
});
generateGrid();
}
// ==================== 随机植物挑战 ====================
if ($('#pvzhe-rarity-filter').length && mw.config.get('wgPageName') === '植物') {
var $box = $(
'<div class="random-plant-box" style="text-align:center;padding:20px;background:#fff;border-radius:12px;border:2px solid #e0e0e0;margin:15px 0;">' +
'<div style="display:flex;align-items:center;justify-content:center;gap:10px;margin-bottom:15px;flex-wrap:wrap;">' +
'<button class="random-plant-btn" style="padding:10px 30px;font-size:16px;background:#4CAF50;color:#fff;border:none;border-radius:8px;cursor:pointer;">随机抽取</button>' +
'<input type="number" class="random-plant-count" value="5" min="1" max="10" style="width:60px;padding:8px;border:1px solid #ccc;border-radius:8px;text-align:center;font-size:16px;">' +
'<span style="font-size:14px;color:#888;">个植物</span>' +
'</div>' +
'<div class="random-plant-result" style="display:flex;flex-wrap:wrap;justify-content:center;gap:8px;"></div>' +
'</div>'
);
$('#pvzhe-rarity-filter').after($box);
$box.find('.random-plant-btn').click(function() {
var count = parseInt($box.find('.random-plant-count').val()) || 5;
count = Math.max(1, Math.min(10, count));
$box.find('.random-plant-count').val(count);
var cards = [];
$('.pvzhe-card').each(function() {
var $card = $(this);
var name = $card.find('.pvzhe-card-name').text().trim();
var img = $card.find('img').attr('src');
var bg = $card.css('background') || $card.css('background-color') || '#f9f9f9';
var border = $card.css('border-color') || '#e0e0e0';
if (name && img) {
cards.push({ name: name, img: img, bg: bg, border: border });
}
});
cards = cards.sort(function() { return Math.random() - 0.5; });
var picked = cards.slice(0, count);
var html = '<div style="display:flex;flex-wrap:wrap;justify-content:center;gap:15px;">';
picked.forEach(function(c) {
html += '<a href="/w/' + encodeURIComponent(c.name) + '" style="text-align:center;text-decoration:none;color:#333;">' +
'<div style="width:90px;height:90px;border-radius:12px;padding:4px;background:' + c.bg + ';border:2px solid ' + c.border + ';display:flex;align-items:center;justify-content:center;">' +
'<img src="' + c.img + '" style="max-width:80px;max-height:80px;border-radius:8px;">' +
'</div>' +
'<span style="font-size:12px;margin-top:4px;display:block;">' + c.name + '</span>' +
'</a>';
});
html += '</div>';
$box.find('.random-plant-result').html(html);
});
}
// ==================== 随机僵尸挑战 ====================
if ($('#zf-name').length && mw.config.get('wgPageName') === '僵尸') {
var $zbox = $(
'<div class="random-plant-box" style="text-align:center;padding:20px;background:#fff;border-radius:12px;border:2px solid #e0e0e0;margin:15px 0;">' +
'<div style="display:flex;align-items:center;justify-content:center;gap:10px;margin-bottom:15px;flex-wrap:wrap;">' +
'<button class="random-zombie-btn" style="padding:10px 30px;font-size:16px;background:#7E57C2;color:#fff;border:none;border-radius:8px;cursor:pointer;">随机抽取</button>' +
'<input type="number" class="random-zombie-count" value="5" min="1" max="10" style="width:60px;padding:8px;border:1px solid #ccc;border-radius:8px;text-align:center;font-size:16px;">' +
'<span style="font-size:14px;color:#888;">个僵尸</span>' +
'</div>' +
'<div class="random-zombie-result" style="display:flex;flex-wrap:wrap;justify-content:center;gap:8px;"></div>' +
'</div>'
);
$('#zf-name').closest('.pvzhe-filter-box').before($zbox);
$zbox.find('.random-zombie-btn').click(function() {
var count = parseInt($zbox.find('.random-zombie-count').val()) || 5;
count = Math.max(1, Math.min(10, count));
$zbox.find('.random-zombie-count').val(count);
var cards = [];
$('.pvzhe-card').each(function() {
var $card = $(this);
var name = $card.find('.pvzhe-card-name').text().trim();
var img = $card.find('img').attr('src');
var bg = $card.css('background') || $card.css('background-color') || '#f9f9f9';
var border = $card.css('border-color') || '#e0e0e0';
if (name && img) {
cards.push({ name: name, img: img, bg: bg, border: border });
}
});
cards = cards.sort(function() { return Math.random() - 0.5; });
var picked = cards.slice(0, count);
var html = '<div style="display:flex;flex-wrap:wrap;justify-content:center;gap:15px;">';
picked.forEach(function(c) {
html += '<a href="/w/' + encodeURIComponent(c.name) + '" style="text-align:center;text-decoration:none;color:#333;">' +
'<div style="width:90px;height:90px;border-radius:12px;padding:4px;background:' + c.bg + ';border:2px solid ' + c.border + ';display:flex;align-items:center;justify-content:center;">' +
'<img src="' + c.img + '" style="max-width:80px;max-height:80px;border-radius:8px;">' +
'</div>' +
'<span style="font-size:12px;margin-top:4px;display:block;">' + c.name + '</span>' +
'</a>';
});
html += '</div>';
$zbox.find('.random-zombie-result').html(html);
});
}
// ==================== 修复浏览量文字 ====================
$(window).on('load', function() {
$('body *').contents().filter(function() {
return this.nodeType === 3 && this.textContent.indexOf('浏览量') !== -1;
}).each(function() {
this.textContent = this.textContent.replace(/浏览量/g, '浏览量');
});
});
// ==================== 侧边栏批量上传入口 ====================
if ($('#citizen-drawer__card').length) {
var $uploadLi = $('#t-upload').closest('li');
if ($uploadLi.length) {
var $batchLi = $(
'<li id="n-batchupload" class="mw-list-item">' +
'<a href="/w/%E4%B8%8A%E4%BC%A0%E5%9B%BE%E7%89%87">' +
'<span class="citizen-ui-icon mw-ui-icon-image mw-ui-icon-wikimedia-image"></span> ' +
'<span>批量上传图片</span>' +
'</a>' +
'</li>'
);
$uploadLi.after($batchLi);
}
}
// ==================== 今日活跃用户 ====================
if ($('#today-active-users').length) {
var now = new Date();
$('#today-date').text(now.toLocaleDateString('zh-CN'));
var chinaMidnight = new Date(now.getFullYear(), now.getMonth(), now.getDate(), 0, 0, 0);
var chinaMidnightTime = chinaMidnight.getTime();
$.getJSON(mw.util.wikiScript('api'), {
action: 'query',
list: 'recentchanges',
rcprop: 'user|timestamp',
rclimit: 1000,
rcstart: now.toISOString(),
rcend: new Date(chinaMidnightTime - 8 * 60 * 60 * 1000).toISOString(),
format: 'json'
}, function(rcData) {
var userSet = {};
if (rcData.query && rcData.query.recentchanges) {
rcData.query.recentchanges.forEach(function(rc) {
var rcTime = new Date(rc.timestamp).getTime();
if (rcTime >= chinaMidnightTime && rc.user) {
if (!userSet[rc.user]) userSet[rc.user] = 0;
userSet[rc.user]++;
}
});
}
var users = Object.keys(userSet);
if (users.length === 0) {
$('#today-active-users').html('暂无编辑');
return;
}
var exactCounts = {};
var done = 0;
users.forEach(function(username) {
var count = 0;
function fetch(uccontinue) {
var params = {
action: 'query',
list: 'usercontribs',
ucuser: username,
uclimit: 50,
ucprop: 'timestamp',
format: 'json',
ucend: new Date(chinaMidnightTime - 8 * 60 * 60 * 1000).toISOString()
};
if (uccontinue) params.uccontinue = uccontinue;
$.getJSON(mw.util.wikiScript('api'), params, function(ucData) {
if (ucData.query && ucData.query.usercontribs) {
ucData.query.usercontribs.forEach(function(uc) {
if (new Date(uc.timestamp).getTime() >= chinaMidnightTime) {
count++;
}
});
if (ucData.continue && ucData.continue.uccontinue) {
fetch(ucData.continue.uccontinue);
return;
}
}
if (count > 0) exactCounts[username] = count;
done++;
if (done >= users.length) showResult();
});
}
fetch();
});
function showResult() {
var sorted = [];
for (var name in exactCounts) sorted.push({ name: name, count: exactCounts[name] });
sorted.sort(function(a, b) { return b.count - a.count; });
var html = '';
if (sorted.length === 0) {
html = '暂无编辑';
} else {
sorted.forEach(function(u) {
html += '<b>' + u.name + '</b>:' + u.count + ' 次<br>';
});
}
$('#today-active-users').html(html);
}
});
}
// ==================== 阅读计时器 ====================
if (mw.config.get('wgAction') === 'view' && mw.config.get('wgIsArticle')) {
var startTime = Date.now();
var $timer = $('<span>', {
style: 'color:#999;font-size:12px;',
text: '已阅 00:00'
});
var waitForEditInfo = setInterval(function() {
var $editInfo = $('#mw-content-text').find('div:contains("最后编辑")').last();
if ($editInfo.length) {
clearInterval(waitForEditInfo);
$editInfo.css('display', 'flex').css('justify-content', 'space-between');
$editInfo.prepend($timer);
setInterval(function() {
var elapsed = Math.floor((Date.now() - startTime) / 1000);
var h = Math.floor(elapsed / 3600);
var m = Math.floor((elapsed % 3600) / 60);
var s = elapsed % 60;
if (h > 0) {
$timer.text('已阅 ' + (h < 10 ? '0' : '') + h + ':' + (m < 10 ? '0' : '') + m + ':' + (s < 10 ? '0' : '') + s);
} else {
$timer.text('已阅 ' + (m < 10 ? '0' : '') + m + ':' + (s < 10 ? '0' : '') + s);
}
}, 1000);
}
}, 200);
setTimeout(function() { clearInterval(waitForEditInfo); }, 10000);
}
// ==================== 编辑框字数统计 ====================
if (mw.config.get('wgAction') === 'edit' || mw.config.get('wgAction') === 'submit') {
var updateCounter = function() {
var text = $('#wpTextbox1').val();
if ($('#wikiEditor-ui-toolbar').length) {
// WikiEditor 启用时,从它的 iframe 获取内容
var iframe = $('#wpTextbox1_ifr');
if (iframe.length) {
text = iframe[0].contentDocument.body.innerText || '';
}
}
if (!$('#edit-counter').length) {
$('#wpTextbox1').after('<div id="edit-counter" style="color:#888;font-size:12px;margin-top:4px;text-align:right;">字数:' + text.length + '</div>');
} else {
$('#edit-counter').text('字数:' + text.length);
}
};
// 定时更新
setInterval(updateCounter, 50);
// 初始显示
updateCounter();
}
}); // 结束主 $(function () { ... })