
本文详解如何在 Laravel 8 中结合 Blade 模板、AJAX 和控制器,安全高效地触发用户模型中的 bookmark() 方法,实现前端一键收藏/取消收藏文章的功能。
本文详解如何在 laravel 8 中结合 blade 模板、ajax 和控制器,安全高效地触发用户模型中的 `bookmark()` 方法,实现前端一键收藏/取消收藏文章的功能。
在 Laravel 应用中,Laravel 本身不提供类似 ——onclick 是前端 JavaScript 行为,而非 Laravel 框架特性。但我们可以无缝集成:通过 Blade 渲染动态 ID,配合 AJAX 请求调用后端路由,最终在控制器中调用 Auth::user()->bookmark($post) 完成逻辑。
✅ 正确实现步骤
1. 路由定义(routes/web.php)
使用 CSRF 保护的 POST 路由,并为认证用户限定中间件:
use Illuminate\Support\Facades\Route;
Route::post('/bookmarks/toggle', [BookmarkController::class, 'toggle'])
->name('bookmarks.toggle')
->middleware('auth');
2. 控制器处理(app/Http/Controllers/BookmarkController.php)
避免直接在模型中暴露业务逻辑给前端调用,应在控制器中统一校验与响应:
<?php namespace App\Http\Controllers;
use App\Models\Post;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
class BookmarkController extends Controller
{
public function toggle(Request $request)
{
$request->validate(['post_id' => 'required|exists:posts,id']);
$post = Post::findOrFail($request->post_id);
$user = Auth::user();
if ($user->isBookmarked($post)) {
$user->bookmarks()
->where('post_id', $post->id)
->delete();
return response()->json(['status' => 'removed', 'count' => $post->bookmarks()->count()]);
} else {
$user->bookmarks()->create(['post_id' => $post->id]);
return response()->json(['status' => 'added', 'count' => $post->bookmarks()->count()]);
}
}
}
⚠️ 注意:确保 User 模型中已正确定义 bookmarks() 关系(如 belongsToMany(Post::class, 'bookmarks')),且 isBookmarked($post) 方法存在并返回布尔值。
3. Blade 模板(resources/views/posts/show.blade.php 或 bookmarks.blade.php)
使用 @csrf 保障 AJAX POST 安全性,并动态注入文章 ID:
<button type="button" class="bookmark-btn" data-post-id="{{ $post->id }}" aria-label="{{ $user->isBookmarked($post) ? 'Remove bookmark' : 'Add bookmark' }}">
<i class="bi bi-bookmark-heart-fill text-2xl ml-4 {{ $user->isBookmarked($post) ? 'text-red-500' : 'text-gray-500' }}"></i>
</button>
@push('scripts')
<script>
function toggleBookmark(postId) {
const button = document.querySelector(`.bookmark-btn[data-post-id="${postId}"]`);
const icon = button.querySelector('i');
fetch("{{ route('bookmarks.toggle') }}", {
method: 'POST',
headers: {
'X-CSRF-TOKEN': '{{ csrf_token() }}',
'Content-Type': 'application/json',
},
body: JSON.stringify({ post_id: postId })
})
.then(response => response.json())
.then(data => {
if (data.status === 'added') {
icon.classList.remove('text-gray-500');
icon.classList.add('text-red-500');
button.setAttribute('aria-label', 'Remove bookmark');
} else {
icon.classList.remove('text-red-500');
icon.classList.add('text-gray-500');
button.setAttribute('aria-label', 'Add bookmark');
}
// 可选:更新收藏数显示
const countEl = document.getElementById('bookmark-count');
if (countEl) countEl.textContent = data.count;
})
.catch(error => {
console.error('Bookmark failed:', error);
alert('操作失败,请重试');
});
}
// 绑定点击事件(推荐使用事件委托或单独绑定)
document.querySelectorAll('.bookmark-btn').forEach(btn => {
btn.addEventListener('click', () => {
toggleBookmark(btn.dataset.postId);
});
});
</script>
@endpush
4. 前端增强建议(可选)
- 添加加载状态(如禁用按钮 + loading 图标);
- 使用 axios 替代原生 fetch 提升开发体验;
- 在 Post 模型中添加访问器(如 getIsBookmarkedAttribute())简化 Blade 判断;
- 服务端返回完整状态(如是否已收藏、当前总数),便于前端精准更新 UI。
? 安全提醒
- ❌ 不要将 onclick="addBookmark({{ $blog->id }})" 直接拼接未转义变量(虽本例 ID 为整型较安全,但仍建议统一用 data-* 属性);
- ✅ 所有写操作必须校验 CSRF Token;
- ✅ 必须验证 post_id 存在性及用户登录态(auth 中间件已覆盖);
- ✅ 避免在模型方法中直接接收 $object 并调用 ->id —— 应由控制器传入明确 ID,提升可测性与安全性。
通过以上结构化实现,你将获得一个响应迅速、安全可靠、用户体验良好的 Laravel 收藏功能,真正践行「关注分离」与「前后端职责清晰」的最佳实践。










