|
|
| 第2行: |
第2行: |
|
| |
|
| // 自动加载 MediaWiki:Footer 页面内容,并插入到每个页面底部 | | // 自动加载 MediaWiki:Footer 页面内容,并插入到每个页面底部 |
| | |
| $(document).ready(function() { | | $(document).ready(function() { |
| const namespace = mw.config.get('wgNamespaceNumber'); | | const namespace = mw.config.get('wgNamespaceNumber'); |
| 第19行: |
第20行: |
| }); | | }); |
| }); | | }); |
|
| |
| // 用户颜色、图标、状态、师徒、昵称、里程碑、公告、植物/僵尸筛选、好友、私信 主逻辑
| |
| $(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('<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);
| |
| });
| |
| });
| |
| });
| |
| }
| |
|
| |
| 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.23版本更新公告】</div>' +
| |
| '<div style="text-align:center;font-size:12px;color:#888;margin-bottom:15px;">全新的0.23版本来啦!端午节快乐!</div>' +
| |
| '电脑(Windows)版链接:https://pan.quark.cn/s/15cd64273868<br>' +
| |
| '其他版本:https://pan.quark.cn/s/d09f0ba38bc8<br><br>' +
| |
| '<span style="color:red;font-weight:bold;">全新的0.23版本来啦!端午节快乐!</span><br><br>' +
| |
| '<b>🌱 植物更新</b><br>全新的植物加入了我们,我们的实力将更加强悍!<br>' +
| |
| '<b>白卡:</b>南瓜灯、蹦极窝瓜、大海菇、促销坚果、促销花盆、内卷投手、寒冰炸弹<br>' +
| |
| '<b>至尊金卡:</b>寒冰加农炮<br>' +
| |
| '<b>臻享钻卡:</b>寒冰菇王<br>' +
| |
| '<b>升级紫卡:</b>冰冰凉凉大喷菇、爆米花投手、卷心菜重炮手、玉米旋转机、南瓜堡垒、僵尸机枪射手<br><br>' +
| |
| '<b>🧟 僵尸/障碍物更新</b><br>' +
| |
| '僵尸:垃圾桶蹦极僵尸、云小鬼僵尸<br>' +
| |
| '障碍物:端午特别角色 - 粽子<br><br>' +
| |
| '<b>🗺️ 关卡更新</b><br>' +
| |
| '冒险:第八章 鬼灵村庄 PART.2(关卡 8-5 ~ 8-20)<br>' +
| |
| '金卡挑战:寒冰加农炮 1~3<br>' +
| |
| '钻卡挑战:寒冰菇王 1~6<br>' +
| |
| '植物试玩:冰冰凉凉大喷菇、爆米花投手、玉米旋转机、卷心菜重炮手、南瓜堡垒、僵尸机枪射手<br><br>' +
| |
| '<b>🎨 杂项更新</b><br>' +
| |
| '植物皮肤:寒冰加农炮-雪顶冰糕、寒冰菇王-时间之神(商店购买)<br>' +
| |
| '每日挑战皮肤:豌豆炸弹-圣诞金铃、魅惑大蒜-甜蜜陷阱、增压大喷菇-高压消防栓、章鱼忧郁菇-丸子串烧<br><br>' +
| |
| '<b>⚙️ 全新功能</b><br>' +
| |
| '新增竞技场机制:连胜12次可抽取一株植物(重复返还水晶)<br><br>' +
| |
| '<b>⚖️ 平衡性调整</b><br>' +
| |
| '宝藏树桩:价格250→175、宝箱伤害200→100、击退距离0.25→0.15<br>' +
| |
| '太阳神咖啡豆:初始冷却0s→50s<br>' +
| |
| '全息卡牌投影:初始冷却0s→50s<br>' +
| |
| '益智模式我是僵尸关卡全面调整<br><br>' +
| |
| '<b>🛠️ 游戏优化与BUG修复</b><br>' +
| |
| '优化游戏内文本<br>' +
| |
| '底层语言从GDScript切换至C#,性能大幅提升(预计0.23.5实装)<br>' +
| |
| '修复辣椒无法烧掉僵王冰球、板斧无法被磁力吸走、魅惑植物不能转变、骄阳豌豆BUG、花盆无法升级机枪、大嘴花自制关卡属性覆盖异常、植物血量不显示等问题<br><br>' +
| |
| '<b>💎 水晶兑换商店</b><br>' +
| |
| '大海菇 5 | 促销坚果 5 | 促销花盆 5 | 内卷投手 5 | 寒冰炸弹 15<br><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);
| |
|
| |
| // ==================== 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');
| |
| 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');
| |
| }
| |
| }
| |
|
| |
| 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="cartoon"' + (currentFont === 'cartoon' ? ' selected' : '') + '>卡通体</option>' +
| |
| '<option value="cartoon-italic"' + (currentFont === 'cartoon-italic' ? ' selected' : '') + '>卡通斜体</option>' +
| |
| '<option value="fzjianzhi"' + (currentFont === 'fzjianzhi' ? ' selected' : '') + '>方正剪纸</option>' +
| |
| '<option value="huapop"' + (currentFont === 'huapop' ? ' selected' : '') + '>华康POP2体W9</option>' +
| |
| '<option value="handwrite"' + (currentFont === 'handwrite' ? ' selected' : '') + '>手写体</option>' +
| |
| '<option value="fzkatong"' + (currentFont === 'fzkatong' ? ' 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">');
| |
|
| |
| // 2. 页面预加载(鼠标悬停链接时预加载)
| |
| $(document).on('mouseenter', 'a[href^="/w/"]', function() {
| |
| var link = document.createElement('link');
| |
| link.rel = 'prefetch';
| |
| link.href = this.href;
| |
| document.head.appendChild(link);
| |
| });
| |
|
| |
| // ==================== 随机植物挑战 ====================
| |
| if ($('#pvzhe-rarity-filter').length && mw.config.get('wgPageName').indexOf('植物') !== -1) {
| |
| 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').indexOf('僵尸') !== -1) {
| |
| 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);
| |
| });
| |
| }
| |
|
| |
| // ==================== 隐藏未翻译的浏览量标签 ====================
| |
| $('.citizen-page-info').each(function() {
| |
| if ($(this).text().indexOf('citizen-page-info-viewcount') !== -1) {
| |
| $(this).text($(this).text().replace('⧼citizen-page-info-viewcount⧽', '浏览量'));
| |
| }
| |
| });
| |
|
| |
| }); // 结束主 $(function () { ... })
| |