菜单
本页目录

菜单系统

位于/theme-hao/templates/modules/layouts/layout.html文件下

<!--  手机端 按钮 -->
<div th:replace="~{modules/widgets/floating_buttons}"></div>

引用
源码位于 /themes/theme-hao/templates/modules/widgets/floating_buttons.html文件

<script>
    class FloatingButtons extends HTMLElement {
        constructor() {
            super();
            const shadow = this.attachShadow({ mode: 'open' });
            shadow.innerHTML = `
                <style>
                    /* 基础样式 */
                    :host {
                        position: fixed;
                        bottom: 0;
                        left: 0;
                        right: 0;
                        z-index: 900;
                        display: none;
                        box-sizing: border-box;
                    }
                    
                    /* 在小于768px的屏幕上显示 */
                    @media (max-width: 768px) {
                        :host {
                            display: block;
                        }
                    }
                    
                    /* 在archives路径下隐藏组件 */
                    :host([hidden-by-path]) {
                        display: none !important;
                    }
                    
                    .container {
                        display: flex;
                        justify-content: center;
                        padding: 0.5rem;
                        padding: clamp(0.5rem, 3vw, 0.75rem);
                    }
                    
                    .button-group {
                        background: white;
                        border-radius: 9999px;
                        box-shadow: 0 6px 16px rgba(0, 0, 0, 0.07);
                        display: flex;
                        padding: 0.25rem;
                        padding: clamp(0.25rem, 1vw, 0.375rem);
                        width: 85%;
                        max-width: 280px;
                        justify-content: space-around;
                    }
                    
                    .button {
                        display: flex;
                        flex-direction: column;
                        align-items: center;
                        justify-content: center;
                        padding: 0.375rem;
                        padding: clamp(0.375rem, 1.5vw, 0.5rem);
                        border-radius: 50%;
                        transition: all 0.2s ease;
                        background: none;
                        border: none;
                        cursor: pointer;
                        outline: none;
                        -webkit-tap-highlight-color: transparent;
                    }
                    
                    .button:hover,
                    .button:active {
                        transform: translateY(-1px);
                    }
                    
                    .button-icon {
                        width: 1.75rem;
                        width: clamp(1.75rem, 6vw, 2.25rem);
                        height: 1.75rem;
                        height: clamp(1.75rem, 6vw, 2.25rem);
                        border-radius: 50%;
                        display: flex;
                        align-items: center;
                        justify-content: center;
                        margin-bottom: 0.0625rem;
                        margin-bottom: clamp(0.0625rem, 0.5vw, 0.125rem);
                    }
                    
                    .button-icon svg {
                        width: 0.875rem;
                        width: clamp(0.875rem, 3vw, 1rem);
                        height: 0.875rem;
                        height: clamp(0.875rem, 3vw, 1rem);
                    }
                    
                    .button-text {
                        font-size: 0.4375rem;
                        font-size: clamp(0.4375rem, 1.5vw, 0.5625rem);
                        font-weight: 500;
                        letter-spacing: 0.025rem;
                    }
                    
                    /* 按钮颜色 */
                    .home .button-icon { background-color: rgba(59, 130, 246, 0.1); }
                    .home .button-text { color: #3b82f6; }
                    
                    .message .button-icon { background-color: rgba(139, 92, 246, 0.1); }
                    .message .button-text { color: #8b5cf6; }
                    
                    .search .button-icon { background-color: rgba(16, 185, 129, 0.1); }
                    .search .button-text { color: #10b981; }
                    
                    .profile .button-icon { background-color: rgba(249, 115, 22, 0.1); }
                    .profile .button-text { color: #f97316; }
                    
                    /* 中间大按钮 */
                    .center-button {
                        width: 2.5rem;
                        width: clamp(2.5rem, 9vw, 3rem);
                        height: 2.5rem;
                        height: clamp(2.5rem, 9vw, 3rem);
                        border-radius: 50%;
                        background: linear-gradient(135deg, #3b82f6, #6d28d9);
                        box-shadow: 0 6px 16px rgba(93, 52, 236, 0.2);
                        display: flex;
                        align-items: center;
                        justify-content: center;
                        position: relative;
                        margin-top: -0.5rem;
                        margin-top: clamp(-0.5rem, -2vw, -0.75rem);
                        transition: all 0.2s ease;
                    }
                    
                    .center-button svg {
                        width: 1rem;
                        width: clamp(1rem, 4vw, 1.25rem);
                        height: 1rem;
                        height: clamp(1rem, 4vw, 1.25rem);
                    }
                    
                    .center-button:hover,
                    .center-button:active {
                        transform: scale(1.03);
                    }
                    
                    /* 脉冲效果 */
                    .pulse {
                        position: absolute;
                        width: 100%;
                        height: 100%;
                        border-radius: 50%;
                        background-color: rgba(255, 255, 255, 0.3);
                        animation: pulse-animation 2s infinite;
                    }
                    
                    @keyframes pulse-animation {
                        0% { transform: scale(1); opacity: 1; }
                        100% { transform: scale(1.6); opacity: 0; }
                    }
                </style>
                
                <div class="container">
                    <div class="button-group">
                        <button class="button home" aria-label="首页">
                            <div class="button-icon">
                                <svg viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
                                    <path d="M9 22H15C20 22 22 20 22 15V9C22 4 20 2 15 2H9C4 2 2 4 2 9V15C2 20 4 22 9 22Z" stroke="#3b82f6" stroke-width="2"/>
                                    <path d="M2 9L12 2L22 9" stroke="#3b82f6" stroke-width="2"/>
                                </svg>
                            </div>
                            <span class="button-text">首页</span>
                        </button>
                        
                        <button class="button message" aria-label="消息">
                            <div class="button-icon">
                                <svg viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
                                    <path d="M4 4H20C21.1 4 22 4.9 22 6V18C22 19.1 21.1 20 20 20H4C2.9 20 2 19.1 2 18V6C2 4.9 2.9 4 4 4Z" stroke="#8b5cf6" stroke-width="2"/>
                                    <path d="M2 6L12 13L22 6" stroke="#8b5cf6" stroke-width="2"/>
                                </svg>
                            </div>
                            <span class="button-text">消息</span>
                        </button>
                        
                        <button class="button center-button" aria-label="添加">
                            <svg viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
                                <path d="M12 5V19" stroke="white" stroke-width="2" stroke-linecap="round"/>
                                <path d="M5 12H19" stroke="white" stroke-width="2" stroke-linecap="round"/>
                            </svg>
                            <div class="pulse"></div>
                        </button>
                        
                        <button class="button search" aria-label="搜索">
                            <div class="button-icon">
                                <svg viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
                                    <circle cx="11" cy="11" r="7" stroke="#10b981" stroke-width="2"/>
                                    <path d="M16 16L20 20" stroke="#10b981" stroke-width="2"/>
                                </svg>
                            </div>
                            <span class="button-text">搜索</span>
                        </button>
                        
                        <button class="button profile" aria-label="我的">
                            <div class="button-icon">
                                <svg viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
                                    <circle cx="12" cy="8" r="5" stroke="#f97316" stroke-width="2"/>
                                    <path d="M3 22C3 18 7 17 12 17C17 17 21 18 21 22" stroke="#f97316" stroke-width="2"/>
                                </svg>
                            </div>
                            <span class="button-text">我的</span>
                        </button>
                    </div>
                </div>
            `;
            
            // 设置事件监听
            this.setupEventListeners();
            // 初始化时检查当前路径
            this.checkCurrentPath();
        }
        
        // 获取需要隐藏的路径列表
        getHiddenPaths() {
            // 从组件属性获取隐藏路径列表,或使用默认值
            const hiddenPathsAttr = this.getAttribute('hidden-paths');
            if (hiddenPathsAttr) {
                return hiddenPathsAttr.split(',').map(path => path.trim());
            }
            
            // 默认隐藏的路径
            return [
                '/archives/',
                '/bangumis',
                '/liu-yan-ban',
                '/yin-le'
            ];
        }
        
        // 检查当前路径并设置隐藏属性
        checkCurrentPath() {
            const path = window.location.pathname;
            const hiddenPaths = this.getHiddenPaths();
            
            // 检查当前路径是否匹配任何需要隐藏的路径
            const shouldHide = hiddenPaths.some(hiddenPath => {
                // 如果隐藏路径以/开头但不以/结尾,视为精确匹配
                if (hiddenPath.startsWith('/') && !hiddenPath.endsWith('/')) {
                    return path === hiddenPath;
                }
                // 否则视为前缀匹配
                return path.startsWith(hiddenPath);
            });
            
            if (shouldHide) {
                this.setAttribute('hidden-by-path', '');
            } else {
                this.removeAttribute('hidden-by-path');
            }
        }
        
        // 设置按钮点击事件
        setupEventListeners() {
            const shadow = this.shadowRoot;
            shadow.querySelectorAll('.button').forEach(button => {
                button.addEventListener('click', (e) => {
                    e.preventDefault();
                    const action = button.getAttribute('aria-label');
                    
                    // 特殊处理搜索按钮
                    if (action === '搜索') {
                        this.openSearchWidget();
                        return;
                    }
                    
                    // 处理其他按钮链接
                    const link = this.getButtonLink(action);
                    if (link) {
                        window.location.href = link;
                    } else {
                        this.dispatchEvent(new CustomEvent('button-click', {
                            detail: { action },
                            bubbles: true,
                            composed: true
                        }));
                    }
                });
            });
        }
        
        // 打开搜索插件
        openSearchWidget() {
            // 检查搜索插件是否可用
            if (window.SearchWidget && typeof SearchWidget.open === 'function') {
                console.log('调用搜索插件...');
                SearchWidget.open(); // 调用现有搜索插件
            } else {
                console.error('搜索插件不可用,使用备用链接');
                // 备用逻辑:跳转到搜索页面
                const fallbackLink = this.getAttribute('search-link') || '/search';
                window.location.href = fallbackLink;
            }
        }
        
        // 获取按钮对应的链接
        getButtonLink(action) {
            const linkMap = {
                '首页': this.getAttribute('home-link'),
                '消息': this.getAttribute('message-link'),
                '添加': this.getAttribute('add-link'),
                '我的': this.getAttribute('profile-link')
            };
            
            return linkMap[action] || null;
        }
        
        // 元素连接到DOM时触发
        connectedCallback() {
            console.log('Floating buttons connected');
            
            // 监听路由变化
            this.setupRouteListeners();
        }
        
        // 设置路由变化监听
        setupRouteListeners() {
            // 监听原生popstate事件(后退/前进按钮)
            window.addEventListener('popstate', () => this.checkCurrentPath());
            
            // 监听pushState和replaceState方法
            const self = this;
            
            // 保存原始方法
            const originalPushState = history.pushState;
            const originalReplaceState = history.replaceState;
            
            // 重写pushState方法
            history.pushState = function(state, title, url) {
                originalPushState.apply(this, arguments);
                self.checkCurrentPath();
            };
            
            // 重写replaceState方法
            history.replaceState = function(state, title, url) {
                originalReplaceState.apply(this, arguments);
                self.checkCurrentPath();
            };
            
            // 监听hashchange事件(处理#后面的变化)
            window.addEventListener('hashchange', () => this.checkCurrentPath());
        }
        
        // 元素从DOM移除时触发
        disconnectedCallback() {
            const shadow = this.shadowRoot;
            shadow.querySelectorAll('.button').forEach(button => {
                button.removeEventListener('click', null);
            });
            
            // 移除路由监听
            window.removeEventListener('popstate', () => this.checkCurrentPath());
            window.removeEventListener('hashchange', () => this.checkCurrentPath());
            
            // 注意:无法恢复原始的pushState和replaceState方法,因为没有引用
        }
    }
    
    // 在DOMContentLoaded后定义自定义元素
    document.addEventListener('DOMContentLoaded', () => {
        if (!customElements.get('floating-buttons')) {
            customElements.define('floating-buttons', FloatingButtons);
        }
    });
</script>

<!-- 使用示例 - 自定义隐藏路径 -->
<floating-buttons 
  home-link="/#" 
  message-link="/uc/notifications" 
  add-link="/uc/posts/editor" 
  search-link="/search" 
  profile-link="/uc/profile"
  hidden-paiew="/archives/,/bangumis,/liu-yan-ban,/yin-le,/new-page,/1732350800610,/privacy,/1731941877494">
</floating-buttons>

其中hidden-paiew为添加更多隐藏菜单的路由,需要注意的的目前类似于/guan-yu-feng-jun-xiao-zhan这样的不生效