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
相关产品推荐
相关产品推荐

