Hi, Ant Design Component Dashboard (5.x)🚀

Hi, Ant Design Component Dashboard (5.x)

目前為 2025-03-27 提交的版本,檢視 最新版本

// ==UserScript==
// @name         Hi, Ant Design Component Dashboard (5.x)🚀
// @namespace    https://github.com/xianghongai/Tampermonkey-UserScript
// @version      1.0.2
// @description  Hi, Ant Design Component Dashboard (5.x)
// @author       Nicholas Hsiang
// @match        *://ant.design/*
// @icon         https://gw.alipayobjects.com/zos/rmsportal/rlpTLlbMzTNYuZGGCVYM.png
// @grant        GM_addStyle
// @grant        GM_info
// @run-at       document-end
// @grant        unsafeWindow
// @license      MIT
// ==/UserScript==

(function () {
  'use strict';
  console.log(GM_info.script.name);

  const logoSelector = 'img[alt="logo"]';
  const navSelector = '.ant-app>header';
  const menuSelector = '.ant-app>main>.ant-col:nth-child(1)';
  const groupSelector = '.ant-menu>.ant-menu-item-group';
  const componentItemSelector = '.ant-menu-item';
  const titleSelector = '.ant-menu-item-group-title';

  let wrapperElement = null;

  main();

  /**
   * Main function to execute when the script is loaded.
   */
  function main() {
    ready(() => {
      poll(menuSelector, handler, 500);
    });
  }

  function handler() {
    const toggleElement = document.createElement('span');
    toggleElement.className = 'x-toggle';
    toggleElement.innerHTML = icon();

    toggleElement.addEventListener('click', (event) => {
      // hold shift key to reset
      if (event.shiftKey) {
        wrapperElement.removeAttribute('id');
        wrapperElement.style.display = 'block';
        return;
      }

      // init
      if (!wrapperElement || wrapperElement.id !== 'x-menu-wrapper') {
        wrapperElement = setMenuWrapper();
        // add event listener to component item
        componentItemClickEventListener(wrapperElement, componentItemSelector);
        // handle component page class (hide 'overview' menu item)
        handleComponentPageClass(wrapperElement);
        return;
      }
      wrapperElement.style.display = wrapperElement.style.display === 'none' ? 'block' : 'none';
    });

    document.body.appendChild(toggleElement);
    // add event listener to navbar
    navClickEventListener();
  }

  /**
   * Click the navbar element, handle the component page (hide 'overview' menu item).
   * @param {Element} wrapperEl - The wrapper element
   */
  function navClickEventListener() {
    const navElement = document.querySelector(navSelector);

    if (navElement) {
      navElement.addEventListener('click', () => {
        wrapperElement = document.querySelector(menuSelector);
        setTimeout(() => {
          if (wrapperElement) {
            handleComponentPageClass(wrapperElement);
          }
        }, 100);
      });
    }
  }

  function handleComponentPageClass(wrapperElement) {
    if (window.location.href.includes('component')) {
      wrapperElement.classList.add('x-dashboard-component');
    } else {
      wrapperElement.classList.remove('x-dashboard-component');
    }
  }

  /**
   * Click the target element.
   * @param {Element} componentItemElement - The target element
   * @param {string} selector - The selector of the target element
   */
  function componentItemClickEventListener(componentItemElement, selector) {
    componentItemElement.addEventListener('click', (event) => {
      if (matches(event.target, selector)) {
        componentItemElement.style.display = 'none';
      }
    });
  }

  /**
   * Create the dashboard element.
   * @returns {Element} - The dashboard element
   */
  function setMenuWrapper() {
    wrapperElement = document.querySelector(menuSelector);
    wrapperElement.setAttribute('id', 'x-menu-wrapper');

    const groupElements = Array.from(wrapperElement.querySelectorAll(groupSelector));
    const componentCounts = [];

    groupElements.forEach((item) => {
      const itemElements = Array.from(item.querySelectorAll(componentItemSelector));
      const length = itemElements.length;
      const titleElement = item.querySelector(titleSelector);
      const title = titleElement.textContent;
      titleElement.textContent = `${title} (${length})`;
      componentCounts.push(length);
    });

    const totalCount = componentCounts.reduce((acc, curr) => acc + curr, 0);
    const totalText = `🚀 共有组件 ${totalCount} 个`;
    const logoElement = document.querySelector(logoSelector);
    if (logoElement) {
      logoElement.title = totalText;
    }
    console.log(totalText);
    return wrapperElement;
  }

  /**
   * Execute a function when the document is ready.
   * @param {function} eventHandler - Function to execute when the document is ready
   */
  function ready(eventHandler) {
    if (document.readyState !== 'loading') {
      eventHandler();
    } else {
      document.addEventListener('DOMContentLoaded', eventHandler);
    }
  }

  /**
   * Wait for an element to be found on the page using polling.
   * @param {string} selector - CSS selector for the element to wait for
   * @param {function} callback - Function to execute when the element is found
   * @param {number} maxAttempts - Maximum number of attempts to find the element
   * @returns {number} intervalId - ID of the interval used to poll for the element
   */
  function poll(selector, callback, maxAttempts = 10) {
    let attempts = 0;

    const intervalId = setInterval(() => {
      attempts++;
      const element = document.querySelector(selector);

      if (element) {
        clearInterval(intervalId);
        if (callback && typeof callback === 'function') {
          callback(element);
        }
      } else if (attempts >= maxAttempts) {
        clearInterval(intervalId);
        console.log(`Element ${selector} not found after ${maxAttempts} attempts.`);
      }
    }, 1000);

    return intervalId;
  }

  /**
   * Check if an element matches a CSS selector.
   * @param {Element} currentElement - The element to check for a match
   * @param {string} selector - CSS selector to match against
   * @returns {boolean} - True if the selector matches, false otherwise
   */
  function matches(currentElement, selector) {
    while (currentElement !== null && currentElement !== document.body) {
      if (currentElement.matches(selector)) {
        return true;
      }
      currentElement = currentElement.parentElement;
    }

    // 检查 body 元素
    return document.body.matches(selector);
  }

  function icon() {
    return `<?xml version="1.0" encoding="UTF-8"?><svg width="18" height="18" viewBox="0 0 48 48" fill="none" xmlns="http://www.w3.org/2000/svg"><path d="M18 4H6C4.89543 4 4 4.89543 4 6V18C4 19.1046 4.89543 20 6 20H18C19.1046 20 20 19.1046 20 18V6C20 4.89543 19.1046 4 18 4Z" fill="#2F88FF" stroke="#333" stroke-width="3" stroke-linejoin="round"/><path d="M18 28H6C4.89543 28 4 28.8954 4 30V42C4 43.1046 4.89543 44 6 44H18C19.1046 44 20 43.1046 20 42V30C20 28.8954 19.1046 28 18 28Z" fill="#2F88FF" stroke="#333" stroke-width="3" stroke-linejoin="round"/><path d="M42 4H30C28.8954 4 28 4.89543 28 6V18C28 19.1046 28.8954 20 30 20H42C43.1046 20 44 19.1046 44 18V6C44 4.89543 43.1046 4 42 4Z" fill="#2F88FF" stroke="#333" stroke-width="3" stroke-linejoin="round"/><path d="M28 28H44" stroke="#333" stroke-width="3" stroke-linecap="round" stroke-linejoin="round"/><path d="M36 36H44" stroke="#333" stroke-width="3" stroke-linecap="round" stroke-linejoin="round"/><path d="M28 44H44" stroke="#333" stroke-width="3" stroke-linecap="round" stroke-linejoin="round"/></svg>`;
  }

  const style = `
  .x-toggle {
    position: fixed;
    top: 24px;
    right: 680px;
    z-index: 99999;
    cursor: pointer;
    opacity: 0.8;
    transition: opacity 0.3s ease-in-out;
  }

  .x-toggle:hover {
    opacity: 1;
  }

  #x-menu-wrapper {
    position: fixed !important;
    top: 64px !important;
    right: 0 !important;
    bottom: 0 !important;
    left: 0 !important;
    z-index: 9999 !important;
    max-width: 100% !important;
    max-height: calc(100vh - 64px) !important;
    padding: 16px !important;
    background: #fff !important;
    border-block-start: 1px solid rgba(5, 5, 5, 0.06) !important;
  }

  #x-menu-wrapper>.ant-menu {
    display: grid !important;
    grid-auto-flow: column !important;
    grid-auto-columns: max-content !important;
    max-width: max-content !important;
    gap: 16px !important;
    overflow: auto;
    margin-inline: auto !important;
    border-inline-end: none !important;
  }

  #x-menu-wrapper.x-dashboard-component>.ant-menu > .ant-menu-item-only-child {
    display: none !important;
  }

  #x-menu-wrapper>.ant-menu > .ant-menu-item-group {
    display: grid !important;
    grid-template-rows: auto 1fr;
    overflow: hidden !important;
  }

  #x-menu-wrapper>.ant-menu > .ant-menu-item-group > .ant-menu-item-group-list {
    overflow: auto !important;
  }

  #x-menu-wrapper .ant-menu > .ant-menu-item-group > .ant-menu-item-group-list > .ant-menu-item {
    height: 28px !important;
    line-height: 28px !important;
  }

  #x-menu-wrapper>.ant-menu > .ant-menu-item-group .ant-menu-item-group-title {
    margin-block-start: 0 !important;
  }

  `;
  GM_addStyle(style);
})();

QingJ © 2025

镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址