打开/关闭搜索
搜索
打开/关闭菜单
933
1525
112
1.4万
植物大战僵尸杂交版Wiki
导航
首页
最近更改
随机页面
MediaWiki帮助
特殊页面
上传文件
打开/关闭外观设置菜单
notifications
打开/关闭个人菜单
未登录
未登录用户的IP地址会在进行任意编辑后公开展示。
user-interface-preferences
个人工具
创建账号
登录
查看“︁MediaWiki:Common.js”︁的源代码
MediaWiki界面页面
查看
阅读
查看源代码
查看历史
associated-pages
系统消息
讨论
更多操作
←
MediaWiki:Common.js
因为以下原因,您没有权限编辑该页面:
您请求的操作仅限属于该用户组的用户执行:
用户
此页面为本wiki上的软件提供界面文本,并受到保护以防止滥用。 如欲修改所有wiki的翻译,请访问
translatewiki.net
上的MediaWiki本地化项目。
您无权编辑此JavaScript页面,因为编辑此页面可能会影响所有访问者。
您可以查看和复制此页面的源代码。
/* v2.7.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'); } } }); })(); // ==================== 隐藏彩蛋:快速编辑入口 ==================== (function() { var clicks = 0; var timer = null; $('#firstHeading').on('click', function(e) { // 按 Alt 时点击标题才计数 if (!e.altKey) return; clicks++; clearTimeout(timer); timer = setTimeout(function() { clicks = 0; }, 2000); if (clicks >= 5) { clicks = 0; var page = prompt('快速编辑页面(输入页面名):', mw.config.get('wgPageName')); if (page) { window.location.href = '/w/' + encodeURIComponent(page) + '?action=edit&summary=快速编辑'; } } }); })(); // ==================== 自定义光标开关 ==================== 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;">【植物大战僵尸杂交版V0.25.0版本更新公告】</div>' + '<div style="text-align:center;font-size:12px;color:#888;margin-bottom:15px;">大规模优化游玩体验,全新植物与僵尸登场!</div>' + '<a href="https://www.bilibili.com/opus/1226688839230160904?spm_id_from=333.1365.0.0" target="_blank">【杂交版V0.25.0版本更新】</a><br>' + '电脑(Windows)版链接:https://pan.quark.cn/s/515639f38eea<br>' + '其他版本:https://pan.quark.cn/s/15ae40bfc5f4<br><br>' + '<span style="color:red;font-weight:bold;">V0.25.0版本来啦!大规模优化游玩体验,全新植物与僵尸登场!</span><br><br>' + '<b>🌱 全新植物</b><br>' + '<b>白卡:</b>狐尾草<br>' + '<b>至尊金卡:</b>僵尸加农炮、骄阳仙人掌<br>' + '<b>臻享钻卡:</b>鬼影魅惑菇、自费盒子<br>' + '<b>升级紫卡:</b>红眼毁灭菇<br>' + '<b>梦幻彩卡:</b>花园套装<br><br>' + '<b>🧟 新僵尸</b><br>外星飞船僵尸、外星僵尸<br><br>' + '<b>🗺️ 关卡更新</b><br>' + '钻卡挑战:鬼影魅惑菇 1~6<br>' + '金卡挑战:僵尸加农炮 1~3、骄阳仙人掌 1~3<br>' + '植物试玩:花园套装、红眼毁灭菇<br>' + '迷你游戏射击系列:野火燎原、胆小奇迹、荆棘丛生、双鬼拍门、千变万化<br>' + '娱乐生存系列:自费盒子生存(基础/进阶/无尽)<br><br>' + '<b>🎨 杂项更新</b><br>' + '植物获取:幸运四叶草通关十轮无尽解锁<br>' + '商店皮肤:骄阳仙人掌 - 皓焰戎装<br><br>' + '<b>⚖️ 平衡性调整</b><br>' + '鼠尾草:阳光125→50、子弹4→2、冷却30s→15s<br>' + '杨桃树桩:子弹伤害<20→1枚星星子弹,>20→5方向各1枚<br>' + '紫卡升级线路增加:三线玉米投手→爆米花投手、三线玉米投手→玉米旋转机<br>' + '传送门僵尸防具可被磁力植物吸取<br>' + '调整益智模式我是僵尸系列整体难度<br>' + '调整月神咖啡豆挑战3整体难度<br>' + '自制关卡通关率不受中途退出影响<br><br>' + '<b>🛠️ 游戏优化</b><br>' + '支持120Hz/144Hz高刷新率屏幕<br>' + '修复安卓高帧率下游戏变慢和动画卡顿问题<br>' + '暂停菜单可调帧率限制<br>' + '游戏启动速度变快,背景音乐不卡启动<br>' + '重要资源提前加载,减少游戏中卡顿<br>' + '内存占用更少,缩略图缓存自动清理<br>' + '动画资源、血条、卡牌预览不再反复创建销毁<br>' + '大量僵尸同时计算/受伤时更流畅<br>' + '机枪射手等高频子弹植物更流畅<br>' + '大量阳光掉落时不再卡顿<br><br>' + '<b>💎 水晶兑换商店</b><br>' + '<table style="border-collapse:collapse;width:100%;max-width:400px;">' + '<tr style="background:#f0f0f0;"><th style="border:1px solid #ccc;padding:6px;">兑换项目</th><th style="border:1px solid #ccc;padding:6px;">所需水晶</th></tr>' + '<tr><td style="border:1px solid #ccc;padding:6px;">狐尾草</td><td style="border:1px solid #ccc;padding:6px;">20</td></tr>' + '</table><br>' + '<span style="color:#888;">结语:我们会持续建立与玩家社群的紧密联系,您的支持就是对我们工作最大的认可!</span>' ); 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'; 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 (!$('#wiki-bgm').length) { $('body').append('<iframe id="wiki-bgm" src="//music.163.com/outchain/player?type=2&id=2692379520&auto=1&height=0" style="position:fixed;bottom:-100px;left:-100px;width:1px;height:1px;border:none;" allow="autoplay"></iframe>'); } else { // 已存在则重新加载 document.getElementById('wiki-bgm').src = '//music.163.com/outchain/player?type=2&id=2692379520&auto=1&height=0'; } } else { $('#wiki-bgm').remove(); } } // 默认关闭 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(); } })(); // ==================== 砸罐子生成器 ==================== 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 defaultJar = $('#grid-jar').val() || '问号罐'; 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()); var jar = parts.length >= 4 ? parts[3].trim() : defaultJar; if (name && col >= 1 && row >= 1) { var key = row + '-' + col; var entry = name + '|||' + jar; if (data[key]) { data[key] = data[key] + '|' + entry; } else { data[key] = entry; } } } }); 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++) { 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 cellData = data[r + '-' + c]; if (cellData) { var entries = cellData.split('|'); var imgHtml = ''; entries.forEach(function(entry) { var ep = entry.split('|||'); var p = ep[0]; var jar = ep[1] || defaultJar; imgHtml += '{{JarPlant|jar=' + jar + '|plant=' + p + '|size=40}} '; }); table += '| style="height:90px;border:2px solid ' + border + ';background:' + cellColor + ';" | ' + imgHtml + '\n'; } else { table += '| style="height:90px;border:2px solid ' + border + ';background:' + cellColor + ';" | \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(); } // ==================== 卡片自动计数 ==================== 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 ($('#deadlink-scanner').length) { var $scanBtn = $('#scan-start'); var $progress = $('#scan-progress'); var $progressBar = $('#scan-progress-bar'); var $result = $('#scan-result'); var allPages = []; var deadLinks = []; $scanBtn.click(function() { $scanBtn.prop('disabled', true).text('获取页面列表中...'); $progress.show(); $progressBar.css('width', '0%'); $result.html(''); allPages = []; deadLinks = []; function fetchPages(apcontinue) { $.getJSON(mw.util.wikiScript('api'), { action: 'query', list: 'allpages', apnamespace: 0, aplimit: 500, apcontinue: apcontinue || undefined, format: 'json' }, function(data) { if (data.query && data.query.allpages) { data.query.allpages.forEach(function(p) { allPages.push(p.title); }); if (data.continue && data.continue.apcontinue) { fetchPages(data.continue.apcontinue); } else { $scanBtn.text('扫描死链中... (' + allPages.length + ' 页)'); checkAllLinks(); } } }); } var batchSize = 5; var batchIndex = 0; var totalPages = 0; function checkAllLinks() { totalPages = allPages.length; processBatch(); } function processBatch() { var batch = allPages.slice(batchIndex, batchIndex + batchSize); if (batch.length === 0) { showResults(); return; } var done = 0; batch.forEach(function(page) { $.getJSON(mw.util.wikiScript('api'), { action: 'parse', page: page, prop: 'links', format: 'json' }, function(data) { if (data.parse && data.parse.links) { var links = data.parse.links.filter(function(l) { return l.ns === 0; }); if (links.length > 0) { var titles = links.map(function(l) { return l.title; }).join('|'); $.getJSON(mw.util.wikiScript('api'), { action: 'query', titles: titles, format: 'json' }, function(checkData) { if (checkData.query && checkData.query.pages) { for (var id in checkData.query.pages) { if (parseInt(id) < 0) deadLinks.push({ from: page, to: checkData.query.pages[id].title }); } } done++; if (done >= batch.length) finishBatch(); }); } else { done++; if (done >= batch.length) finishBatch(); } } else { done++; if (done >= batch.length) finishBatch(); } }); }); } function finishBatch() { batchIndex += batchSize; var progress = Math.round((batchIndex / totalPages) * 100); $progressBar.css('width', progress + '%'); $scanBtn.text('扫描中... ' + Math.min(batchIndex, totalPages) + '/' + totalPages); setTimeout(processBatch, 500); } function showResults() { $scanBtn.prop('disabled', false).text('开始扫描'); $progress.hide(); if (deadLinks.length === 0) { $result.html('<p style="color:#4CAF50;">✅ 未发现死链!</p>'); return; } var grouped = {}; deadLinks.forEach(function(item) { if (!grouped[item.from]) grouped[item.from] = []; grouped[item.from].push(item.to); }); var html = '<p style="color:#f44336;">⚠️ 发现 <b>' + deadLinks.length + '</b> 条死链:</p>'; for (var from in grouped) { html += '<div style="padding:6px 0;border-bottom:1px solid #eee;"><b><a href="/w/' + encodeURIComponent(from) + '" target="_blank">' + from + '</a></b> → '; grouped[from].forEach(function(to) { html += '<a href="/w/' + encodeURIComponent(to) + '?action=edit" target="_blank" style="color:#f44336;margin-right:8px;">' + to + '</a>'; }); html += '</div>'; } $result.html(html); } fetchPages(); }); } // ==================== 编辑贡献排行榜 ==================== 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() { // 配置:需要密码的页面和对应密码 var protectedPages = { '测试页面': '123456' }; var pageName = mw.config.get('wgPageName'); if (!protectedPages[pageName]) return; var correctPwd = protectedPages[pageName]; var storageKey = 'wiki_pwd_' + pageName; var savedPwd = localStorage.getItem(storageKey); // 密码正确,放行 if (savedPwd === correctPwd) return; // 弹窗输入密码 var $overlay = $('<div>', { css: { position: 'fixed', top: 0, left: 0, width: '100%', height: '100%', background: 'rgba(0,0,0,0.7)', zIndex: 999999, display: 'flex', alignItems: 'center', justifyContent: 'center' } }); var $box = $('<div>', { css: { background: '#fff', borderRadius: '16px', padding: '30px', textAlign: 'center', boxShadow: '0 8px 30px rgba(0,0,0,0.4)', maxWidth: '360px', width: '85%' } }); $box.append('<h3 style="margin-bottom:8px;">此页面已加密</h3>'); $box.append('<p style="color:#888;font-size:13px;margin-bottom:15px;">请输入访问密码</p>'); $box.append('<input type="password" id="page-pwd-input" style="width:100%;padding:10px;border:2px solid #ccc;border-radius:8px;text-align:center;font-size:16px;box-sizing:border-box;" placeholder="输入密码" autofocus>'); $box.append('<p id="pwd-error" style="color:#f44336;font-size:12px;margin-top:8px;display:none;">密码错误,请重试</p>'); $box.append('<button id="page-pwd-submit" style="margin-top:12px;padding:10px 30px;background:#4CAF50;color:#fff;border:none;border-radius:8px;font-size:15px;cursor:pointer;">确认</button>'); $overlay.append($box); $('body').append($overlay); // 隐藏页面内容 $('#mw-content-text').css('visibility', 'hidden'); $('.citizen-header, #citizen-drawer__card').css('visibility', 'hidden'); function checkPassword() { var input = $('#page-pwd-input').val().trim(); if (input === correctPwd) { localStorage.setItem(storageKey, correctPwd); $overlay.remove(); $('#mw-content-text').css('visibility', 'visible'); $('.citizen-header, #citizen-drawer__card').css('visibility', 'visible'); } else { $('#pwd-error').show(); $('#page-pwd-input').val('').focus(); } } $('#page-pwd-submit').click(checkPassword); $('#page-pwd-input').on('keypress', function(e) { if (e.which === 13) checkPassword(); }); })(); // ==================== 未使用图片扫描器 ==================== if ($('#unused-scan-btn').length) { var allImages = []; var unusedImages = []; var usedTitles = {}; // 获取全站所有页面引用的图片 function scanUsedImages(apcontinue) { $.getJSON(mw.util.wikiScript('api'), { action: 'query', list: 'allpages', apnamespace: 0, aplimit: 500, apcontinue: apcontinue || undefined, format: 'json' }, function(data) { if (data.query && data.query.allpages) { var pages = data.query.allpages; var done = 0; pages.forEach(function(page) { $.getJSON(mw.util.wikiScript('api'), { action: 'parse', page: page.title, prop: 'images', format: 'json' }, function(parseData) { if (parseData.parse && parseData.parse.images) { parseData.parse.images.forEach(function(img) { usedTitles[img] = true; }); } done++; if (done >= pages.length) { // 开始扫描所有图片 scanAllImages(); } }); }); if (data.continue && data.continue.apcontinue) { scanUsedImages(data.continue.apcontinue); } } }); } // 获取所有上传的图片 function scanAllImages(aicontinue) { $.getJSON(mw.util.wikiScript('api'), { action: 'query', list: 'allimages', ailimit: 500, aicontinue: aicontinue || undefined, format: 'json' }, function(data) { if (data.query && data.query.allimages) { allImages = allImages.concat(data.query.allimages); if (data.continue && data.continue.aicontinue) { scanAllImages(data.continue.aicontinue); } else { // 筛选未使用的 unusedImages = allImages.filter(function(img) { return !usedTitles[img.name]; }); showResults(); } } }); } function showResults() { var $result = $('#unused-result'); $result.empty(); if (unusedImages.length === 0) { $result.html('<p style="color:#4CAF50;">✅ 所有图片都在使用中!</p>'); return; } $result.append('<p style="color:#f44336;">⚠️ 发现 ' + unusedImages.length + ' 张未使用图片:</p>'); $('#unused-delete-all-btn').show(); unusedImages.forEach(function(img) { var $item = $('<div class="unused-item"></div>'); $item.append('<img src="' + img.url + '" alt="' + img.name + '">'); $item.append('<div class="file-info"><b>' + img.name + '</b><br><span style="color:#888;">' + new Date(img.timestamp).toLocaleDateString('zh-CN') + '</span></div>'); $item.append('<button class="del-btn" data-file="' + img.name + '">删除</button>'); $result.append($item); }); } // 删除单张图片 $('#unused-result').on('click', '.del-btn', function() { var fileName = $(this).data('file'); var $btn = $(this); if (!confirm('确定删除 ' + fileName + ' 吗?')) return; $btn.prop('disabled', true).text('删除中...'); new mw.Api().postWithEditToken({ action: 'delete', title: 'File:' + fileName, reason: '未使用图片清理' }).then(function() { $btn.closest('.unused-item').fadeOut(); }).fail(function() { alert('删除失败,可能没有权限'); $btn.prop('disabled', false).text('删除'); }); }); // 全部删除 $('#unused-delete-all-btn').click(function() { if (!confirm('确定删除全部 ' + unusedImages.length + ' 张未使用图片吗?此操作不可恢复!')) return; var $btn = $(this); $btn.prop('disabled', true).text('删除中...'); var deleteQueue = Promise.resolve(); unusedImages.forEach(function(img) { deleteQueue = deleteQueue.then(function() { return new mw.Api().postWithEditToken({ action: 'delete', title: 'File:' + img.name, reason: '批量清理未使用图片' }); }); }); deleteQueue.then(function() { $('#unused-result').html('<p style="color:#4CAF50;">✅ 已删除全部未使用图片!</p>'); $btn.hide(); }); }); // 开始扫描 $('#unused-scan-btn').click(function() { var $btn = $(this); $btn.prop('disabled', true).text('扫描中...'); $('#unused-result').html('<p style="color:#888;">正在扫描全站图片引用,请稍候...</p>'); allImages = []; unusedImages = []; usedTitles = {}; scanUsedImages(); }); } }); // 结束主 $(function () { ... })
返回
MediaWiki:Common.js
。
查看“︁MediaWiki:Common.js”︁的源代码
MediaWiki界面页面