You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Livewire 3.x+AlpineJS 3.x递归分类下拉保持展开问题求助

问题:递归分类菜单点击子分类时父分类自动收起

我用Livewire 3.x和AlpineJS 3.x实现了数据库存储的递归分类展示功能,其他功能都正常,但存在一个问题:打开父分类后点击其内部的子分类,父分类会自动收起。需要修改代码让父分类保持展开状态。


源代码

resources\views\livewire\category-navigation.blade.php

<div x-data="sidebar" class="sidebar z-50 transition-all duration-300">
    <div class="bg-white">
        <ul class="p-4 py-0" x-data="{ activeDropdown: null }">
        @foreach ($categories as $category)
            @if ($category->children->count() > 0)
                <li class="nav-item">
                    <ul>
                        <li class="nav-item">
                            <button type="button" class="nav-link group"
                            :class="{ 'active': activeDropdown === '{{ $category->slug }}' }"
                                @click="activeDropdown === '{{ $category->slug }}' ? activeDropdown = null : activeDropdown = '{{ $category->slug }}'">
                                <div class="flex items-center">
                                    <span class="text-black">
                                        {{ $category->name }}
                                    </span>
                                </div>
                                <div class="" :class="{ '!rotate-90': activeDropdown === '{{ $category->slug }}' }">
                                    <svg width="16" height="16" viewBox="0 0 24 24" fill="none"
                                        xmlns="http://www.w3.org/2000/svg">
                                        <path d="M9 5L15 12L9 19" stroke="currentColor" stroke-width="1.5"
                                            stroke-linecap="round" stroke-linejoin="round" />
                                    </svg>
                                </div>
                            </button>
                            <ul x-cloak x-show="activeDropdown === '{{ $category->slug }}'" x-collapse class="sub-menu text-gray-500" @click.away="activeDropdown = null" @click.stop>
                                @foreach ($category->children as $child)
                                    @include('livewire.category-item', ['category' => $child])
                                @endforeach
                            </ul>
                        </li>
                    </ul>
                </li>
            @else
                <li class="nav-item">
                    <a href="{{ url($category->full_path) }}" wire:click.prevent="navigateToCategory('{{ $category->full_path }}')" class="nav-link group"
                        :class="{ 'active': '{{ request()->routeIs("products.byCategory") && request()->route("slug") == $category->slug ? "active" : "" }}'}">
                        <div class="flex items-center">
                            <span class="text-black">{{ $category->name }} ({{ $category->products()->count() }})</span>
                        </div>
                    </a>
                </li>
            @endif
        @endforeach
        </ul>
    </div>
</div>

resources\views\livewire\category-item.blade.php

@if ($category->children->count() > 0)
    <li x-data="{ subActive: null }">
        <button type="button"
            class="before:bg-gray-300 before:w-[5px] before:h-[5px] before:rounded before:mr-2 hover:bg-gray-100"
            :class="{ 'active': subActive === '{{ $category->slug }}' }"
            @click.stop="subActive === '{{ $category->id }}' ? subActive = null : subActive = '{{ $category->slug }}'">

            {{ $category->name }}

            <div class="ml-auto" :class="{ '!rotate-90': subActive === '{{ $category->slug }}' }">
                <svg width="16" height="16" viewBox="0 0 24 24" fill="none"
                    xmlns="http://www.w3.org/2000/svg">
                    <path d="M9 5L15 12L9 19" stroke="currentColor" stroke-width="1.5"
                        stroke-linecap="round" stroke-linejoin="round" />
                </svg>
            </div>
        </button>
        <ul class="sub-menu text-gray-500 ml-4" x-show="subActive === '{{ $category->slug }}'" x-collapse>
            @foreach ($category->children as $child)
                @include('livewire.category-item', ['category' => $child]) <!-- REKURZÍV hívás -->
            @endforeach
        </ul>
    </li>
@else
    <li>
        <a href="{{ url($category->full_path) }}" wire:click.prevent="navigateToCategory('{{ $category->full_path }}')">{{ $category->name }} ({{ $category->products()->count() }})</a>
    </li>
@endif

JS代码

document.addEventListener("alpine:init", () => {
    Alpine.data("sidebar", () => ({
        init() {
            const selector = document.querySelector('.sidebar ul a[href="' + window.location.href + '"]');
            if (selector) {
                selector.classList.add('active');
                const ul = selector.closest('ul.sub-menu');
                if (ul) {
                    let ele = ul.closest('li.menu').querySelectorAll('.nav-link');
                    if (ele) {
                        ele = ele[0];
                        setTimeout(() => {
                            ele.click();
                        });
                    }
                }
            }
        },
    }));
});

解决方案

1. 移除父分类子菜单的@click.away逻辑

父分类子菜单上的@click.away="activeDropdown = null"会把点击子分类的行为判定为"点击外部",导致父分类收起。只保留@click.stop阻止事件冒泡即可。

修改category-navigation.blade.php中的子菜单标签:

<ul x-cloak x-show="activeDropdown === '{{ $category->slug }}'" x-collapse class="sub-menu text-gray-500" @click.stop>

2. 修复子分类按钮的状态判断错误

category-item.blade.php中按钮的点击逻辑里,判断条件用了$category->id但赋值用了$category->slug,导致状态切换逻辑混乱,统一改成slug:

<button type="button"
    class="before:bg-gray-300 before:w-[5px] before:h-[5px] before:rounded before:mr-2 hover:bg-gray-100"
    :class="{ 'active': subActive === '{{ $category->slug }}' }"
    @click.stop="subActive === '{{ $category->slug }}' ? subActive = null : subActive = '{{ $category->slug }}'">

3. 修正页面初始化的父分类选择器

原JS代码中ul.closest('li.menu')选择器错误,父级元素实际是li.nav-item,修改后确保页面加载时能正确展开当前分类的父菜单:

document.addEventListener("alpine:init", () => {
    Alpine.data("sidebar", () => ({
        init() {
            const selector = document.querySelector('.sidebar ul a[href="' + window.location.href + '"]');
            if (selector) {
                selector.classList.add('active');
                const ul = selector.closest('ul.sub-menu');
                if (ul) {
                    let ele = ul.closest('li.nav-item').querySelector('.nav-link');
                    if (ele) {
                        setTimeout(() => {
                            ele.click();
                        });
                    }
                }
            }
        },
    }));
});

可选:统一外部点击收起逻辑

如果需要点击菜单外部区域才收起所有分类,把@click.away移到最外层容器上,避免影响子分类点击:

<div x-data="sidebar" class="sidebar z-50 transition-all duration-300" @click.away="activeDropdown = null">

内容的提问来源于stack exchange,提问作者Fzol

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.13 22:56:00