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

Laravel Livewire+SortableJS拖拽菜单排序视觉不生效问题

解决Laravel Livewire + SortableJS菜单排序不生效问题

核心问题定位

你遇到的视觉排序失效,根源在于两个关键点:

  • 子菜单预加载时未按order字段排序
  • Livewire更新DOM后,嵌套子菜单的Sortable实例未重新初始化(导致新生成的子菜单拖拽逻辑失效)

分步解决方案

1. 修复子菜单预加载排序

修改loadMenu方法,在关联查询中明确指定子菜单的排序规则:

public function loadMenu()
{
    $this->menuItems = Menu::whereNull('parent_id')
        ->with(['children' => function ($query) {
            // 强制子菜单按order字段升序排列
            $query->orderBy('order');
        }])
        ->orderBy('order')
        ->get();
}

2. 统一前后端排序索引值

前端序列化时order从1开始计数,但后端更新时用的是0起始的索引,导致排序逻辑不一致,统一改为从1开始:

// 递归更新菜单层级的方法修改
public function updateMenuHierarchy(array $items, $parentId = null)
{
    foreach ($items as $index => $item) {
        Menu::where('id', $item['id'])->update([
            'parent_id' => $parentId,
            'order' => $index + 1, // 和前端保持一致,从1开始计数
        ]);

        if (!empty($item['children'])) {
            $this->updateMenuHierarchy($item['children'], $item['id']);
        }
    }
}

3. 确保Livewire更新后Sortable重新初始化

Livewire更新DOM后,原有的Sortable实例会失效,需要在DOM更新完成后重新绑定:

@push('scripts')
<script src="https://cdn.jsdelivr.net/npm/sortablejs@1.15.0/Sortable.min.js"></script>
<script>
    // 封装Sortable初始化逻辑
    function initSortable() {
        let menuList = document.getElementById('menu-list');
        if (!menuList) return;

        // 初始化顶级菜单拖拽
        new Sortable(menuList, {
            animation: 150,
            handle: '.handle',
            onEnd: function (evt) {
                let order = serializeMenu(menuList);
                Livewire.emit('updateMenuOrder', order);
            }
        });

        // 初始化所有子菜单拖拽
        menuList.querySelectorAll('ul').forEach(function(childUl) {
            new Sortable(childUl, {
                group: 'nested',
                animation: 150,
                handle: '.handle',
                onEnd: function (evt) {
                    let order = serializeMenu(menuList);
                    Livewire.emit('updateMenuOrder', order);
                }
            });
        });
    }

    // 页面首次加载初始化
    document.addEventListener('livewire:load', initSortable);
    // Livewire更新DOM后重新初始化
    document.addEventListener('livewire:update', initSortable);

    function serializeMenu(el) {
        let items = [];
        el.querySelectorAll(':scope > li').forEach((li, index) => {
            let item = {
                id: li.getAttribute('data-id'),
                order: index + 1,
                children: []
            };

            let childUl = li.querySelector('ul');
            if (childUl) {
                item.children = serializeMenu(childUl);
            }

            items.push(item);
        });
        return items;
    }
</script>
@endpush

4. 优化递归渲染(可选)

将菜单递归渲染抽成Blade组件,避免重复代码,创建resources/views/components/menu-item.blade.php:

<li data-id="{{ $menu->id }}" class="mb-2 border p-2 rounded bg-gray-100">
    <div class="flex justify-between items-center">
        <span class="handle cursor-move text-black">{{ $menu->title }}</span>
        <button wire:click="removeMenu({{ $menu->id }})" class="text-red-500 hover:text-red-700 font-bold">&times;</button>
    </div>

    @if ($menu->children->count())
        <ul>
            @foreach ($menu->children as $child)
                <x-menu-item :menu="$child" />
            @endforeach
        </ul>
    @endif
</li>

主视图中调用组件:

<ul id="menu-list">
    @foreach ($menuItems as $menu)
        <x-menu-item :menu="$menu" />
    @endforeach
</ul>

验证步骤

  1. 拖拽菜单后,检查数据库parent_id和order字段是否正确更新
  2. 触发loadMenu后,确认菜单按order字段正确排序
  3. 验证嵌套子菜单拖拽功能是否正常生效

内容的提问来源于stack exchange,提问作者MicroWeb Technology Ltd

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.12 15:44:52