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

Laravel下拉导航无法显示分类筛选文章问题求助

问题描述

我正在使用laravel/framework": "^10.10"版本,项目中已定义文章(Post)和分类(Category)模块,尝试实现下拉导航功能以展示对应分类下的文章,但遇到两个问题:

  • 使用@foreach($posts->items() as $post)会显示所有分类的文章,无法实现分类筛选;
  • 使用@foreach($category->posts as $post)时,点击下拉菜单无任何内容显示。

导航代码如下:

<nav class="w-full py-4 border-t border-b bg-gray-100" x-data="{ open: false }">

    <div class="block sm:hidden">
        <a href="#" class="block md:hidden text-base font-bold uppercase text-center flex justify-center items-center" @click="open = !open">
            Topics <i :class="open ? 'fa-chevron-down': 'fa-chevron-up'" class="fas ml-2"></i>
        </a>
    </div>
    <div :class="open ? 'block': 'hidden'" class="w-full flex-grow sm:flex sm:items-center sm:w-auto">
        <div class="w-full container mx-auto flex flex-col sm:flex-row items-center justify-center text-sm font-bold uppercase mt-0 px-6 py-2">
            <a href="{{ route('home') }}" class="hover:bg-gray-400 rounded py-2 px-4 mx-2">Home</a>
            @foreach($categories as $category)
                <div class="relative inline-block text-left" x-data="{ openDropdown: false }">
                    <button @click="openDropdown = !openDropdown" type="button" class="hover:bg-gray-400 rounded py-2 px-4 mx-2">
                        {{ $category->title }}  <i :class="openDropdown ? 'fa-chevron-up': 'fa-chevron-down'" class="fas ml-2"></i>
                    </button>

                    <div x-show="openDropdown" @click.away="openDropdown = false" class="absolute right-0 w-40 mt-2 origin-top-right bg-white border border-gray-300 rounded-md shadow-lg">

                        <!-- Dropdown content -->
                        @foreach($category->posts as $post)
                            <a href="{{ route('view', $post->slug) }}" class="block px-4 py-2 text-sm text-gray-700 hover:bg-gray-100">{{ $post->title }}</a>
                        @endforeach
                    </div>
                </div>
            @endforeach
            <a href="#" class="hover:bg-gray-400 rounded py-2 px-4 mx-2">About us</a>
        </div>
    </div>
</nav>

我已检查模型和控制器,分类与文章的关联关系看似配置正确,但仍无法正常显示对应分类的文章。以下是模型和控制器代码示例:

Category.php

<?php

namespace App\Models;

use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsToMany;

class Category extends Model
{
    use HasFactory;

    protected $fillable = ['title', 'slug'];

    public function posts(): BelongsToMany
    {
        return $this->belongsToMany(Post::class);
    }
}

Post.php

<?php

namespace App\Models;

use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
use Illuminate\Database\Eloquent\Relations\BelongsToMany;

class Post extends Model
{
    use HasFactory;
    protected $fillable = ['title', 'slug', 'thumbnail', 'body', 'active', 'published_at', 'user_id'];
    protected $casts = [
        'published_at' => 'datetime'
    ];
    
    public function user(): BelongsTo
    {
        return $this->belongsTo(User::class);
    }

    public function categories(): BelongsToMany
    {
        return $this->belongsToMany(Category::class);
    } 
}

PostController.php

<?php

namespace App\Http\Controllers;

use App\Models\Category;
use App\Models\Post;
use Carbon\Carbon;
use Illuminate\View\View;
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;

class PostController extends Controller
{
    public function index(): View 
    {
        $posts = Post::query()
            ->where('active', '=', 1)
            ->whereDate('published_at', '<', Carbon::now())
            ->orderBy('published_at', 'desc')
            ->paginate(10);
    
        $categories = Category::query()
            ->leftJoin('category_post', 'categories.id', '=', 'category_post.category_id')
            ->select('categories.title', 'categories.slug', \DB::raw('count(*) as total'))
            ->groupBy('categories.id', 'categories.title', 'categories.slug')
            ->orderByDesc('total')
            ->limit(5)
            ->get();
    
        return view('home', compact('posts', 'categories'));
    }

    public function show(Post $post)
    {
        if(!$post->active || $post->published_at > Carbon::now()){
            throw new NotFoundHttpException();
        }

        $next = Post::query()
            ->where('active', true)
            ->whereDate('published_at', '<=', Carbon::now())
            ->whereDate('published_at', '<', $post->published_at)
            ->orderBy('published_at', 'desc')
            ->limit(1)
            ->first();

        $prev = Post::query()
            ->where('active', true)
            ->whereDate('published_at', '<=', Carbon::now())
            ->whereDate('published_at', '<', $post->published_at)
            ->orderBy('published_at', 'asc')
            ->limit(1)
            ->first();

        return view('post.view', compact('post', 'prev', 'next'));
    }

    public function byCategory(Category $category)
    {
        $posts = Post::query()
            ->join('category_post', 'posts.id', '=', 'category_post.post_id')
            ->where('category_post.category_id', '=', $category->id)
            ->where('active', '=', true)
            ->whereDate('published_at', '<=', Carbon::now())
            ->orderBy('published_at', 'desc')
            ->paginate(10);

        return view('home', compact('posts'));
    }
}

问题分析与解决方案

1. 核心问题:关联数据未预加载且未筛选有效文章

你在控制器index方法中获取$categories时,没有预加载关联的posts,也没有对关联文章添加active和published_at的筛选条件,导致:

  • 调用$category->posts时触发N+1查询问题;
  • 即使有关联数据,也会包含未激活或未发布的文章,而这类文章你本来不需要展示。

修复方案:修改index方法中的分类查询逻辑

$categories = Category::query()
    ->leftJoin('category_post', 'categories.id', '=', 'category_post.category_id')
    ->select('categories.*', \DB::raw('count(category_post.post_id) as total'))
    ->groupBy('categories.id')
    ->orderByDesc('total')
    ->limit(5)
    // 预加载并筛选有效文章
    ->with(['posts' => function ($query) {
        $query->where('active', 1)
              ->whereDate('published_at', '<=', Carbon::now())
              ->orderBy('published_at', 'desc');
    }])
    ->get();

2. 多对多关联表验证(可选)

Laravel默认多对多中间表命名为两个模型名按字母顺序拼接,你的category_post符合规则,但如果表名或字段有自定义,需在关联中明确指定:

// Category模型中
public function posts(): BelongsToMany
{
    return $this->belongsToMany(Post::class, 'category_post', 'category_id', 'post_id');
}

// Post模型中
public function categories(): BelongsToMany
{
    return $this->belongsToMany(Category::class, 'category_post', 'post_id', 'category_id');
}

3. 视图空状态优化

为避免下拉菜单为空时用户困惑,添加空状态提示:

<!-- Dropdown content -->
@forelse($category->posts as $post)
    <a href="{{ route('view', $post->slug) }}" class="block px-4 py-2 text-sm text-gray-700 hover:bg-gray-100">{{ $post->title }}</a>
@empty
    <span class="block px-4 py-2 text-sm text-gray-500">暂无文章</span>
@endforelse

4. byCategory方法简化

利用关联关系替代手动join,代码更简洁:

public function byCategory(Category $category)
{
    $posts = $category->posts()
        ->where('active', true)
        ->whereDate('published_at', '<=', Carbon::now())
        ->orderBy('published_at', 'desc')
        ->paginate(10);

    // 记得传递categories给视图,否则导航会缺失
    $categories = Category::query()
        ->leftJoin('category_post', 'categories.id', '=', 'category_post.category_id')
        ->select('categories.*', \DB::raw('count(category_post.post_id) as total'))
        ->groupBy('categories.id')
        ->orderByDesc('total')
        ->limit(5)
        ->get();

    return view('home', compact('posts', 'categories', 'category'));
}

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.14 01:47:32