首次使用AJAX在Laravel中删除项目标签遇404错误求助
Laravel AJAX 删除标签时404错误排查
我正在用Laravel构建作品集项目,需要实现AJAX无刷新删除项目表单中的已有标签功能,这是我第一次使用AJAX。我自认为逻辑正确,但一直收到404错误,应该是请求找不到对应的路由。我核对过删除路由和请求路径,也确认了URL中的ID在数据库中存在,但始终找不到问题所在。
以下是相关代码片段:
视图代码
@section('title', 'Portfolio') @section('content') <div class="admincontainers"> <div class="mobilemenubar"> @include('components.dropdown_menu') </div> <div class="column1"> @include('components.admin_menu') </div> <div class="admin_column2"> @include('components.messages') <form action="{{ route('update-project', $project->id) }}" class="projectForm" method="POST" enctype="multipart/form-data"> @csrf @method('PUT') <div> <label for="name">Projet:</label> <input type="text" name="name" required value="{{ $project->name ?? '' }}"> </div> <div> @if (isset($project->image_1)) <img width="100px" src="{{ asset("/storage/images/$project->image_1") }}" alt=""> @endif <label for="image_1">Image 1:</label> <input type="file" name="image_1" accept="image/*"> </div> <div> @if (isset($project->image_2)) <img width="100px" src="{{ asset("/storage/images/$project->image_2") }}" alt=""> @endif <label for="image_2">Image 2:</label> <input type="file" name="image_2" accept="image/*"> </div> <div> @if (isset($project->image_3)) <img width="100px" src="{{ asset("/storage/images/$project->image_3") }}" alt=""> @endif <label for="image_3">Image 3:</label> <input type="file" name="image_3" accept="image/*"> </div> <div> <label for="description">Description:</label> <textarea name="description" required>{{ $project->description }}</textarea> </div> <div> <label for="url">URL:</label> <input type="text" name="url" required value="{{ $project->url ?? '' }}"> </div> <div> <label for="customer">Client:</label> <input type="text" name="customer" required value="{{ $project->customer ?? '' }}"> </div> <div class="TagSection"> <div> <label for="tags[]">Nouveaux Tags:</label> <select name="tags[]" class="form-control" multiple> @foreach ($tags as $tag) <option value="{{ $tag->id }}">{{ $tag->name }}</option> @endforeach </select> </div> <div> <label for="tags[]">Tags Existants:</label> <select name="existingTags[]" id="existingTags" multiple> @foreach ($project->tags as $tag) <option value="{{ $tag->id }}" selected>{{ $tag->name }}</option> @endforeach </select> </div> <button type="button" id="removeTag">Supprimer</button> </div> <div> <label for="mission">Mission Réalisée:</label> <select name="mission" class="form-control" multiple> @foreach ($categorie as $category) <option value="{{ $category->id }}">{{ $category->name }}</option> @endforeach </select> </div> <input type="hidden" name="user_id" value="{{ Auth::id() }}"> <div> <button type="submit" class="Edit">Editer le projet </button> </div> </form> <script> let editButton = document.querySelectorAll('.Edit'); for (let i = 0; i < editButton.length; i++) { editButton[i].addEventListener('click', (e) => { if (confirm("Souhaitez-vous réellement modifier ce projet ?")) { window.location.href = e.target.firstElementChild.getAttribute('href'); } else { e.preventDefault(); } }); } let removeTagButton = document.getElementById('removeTag'); removeTagButton.addEventListener('click', function() { console.log('delete button clicked!'); let selectedTags = document.getElementById('existingTags').selectedOptions; let tagIds = []; console.log(tagIds); for (let i = 0; i < selectedTags.length; i++) { tagIds.push(selectedTags[i].value); } let projectId = "{{ $project->id }}"; let csrfToken = document.querySelector('meta[name="csrf-token"]').getAttribute('content'); let csrfTokenElement = document.querySelector('meta[name="csrf-token"]'); let deleteRequest = new XMLHttpRequest(); deleteRequest.open('DELETE', '/projects/' + projectId + '/tags', true); deleteRequest.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded'); deleteRequest.setRequestHeader('X-CSRF-Token', csrfTokenElement.getAttribute('content')); deleteRequest.onreadystatechange = function() { if (deleteRequest.readyState === XMLHttpRequest.DONE) { if (deleteRequest.status === 200) { tagIds.forEach(function(tagId) { let optionToRemove = document.querySelector('#existingTags option[value="' + tagId + '"]'); optionToRemove.remove(); }); } else { console.error(deleteRequest.status); } } }; deleteRequest.send('tags=' + JSON.stringify(tagIds)); }); </script> @endsection
路由代码
Route::group(['prefix' => 'admin', 'middleware' => ['auth']], function () { Route::delete('/projects/{id}/tags', [ProjectController::class, 'deleteTags'])->name('delete-tags'); });
控制器代码
public function deleteTags(Request $request, Projet $id) { dd($request->input('tags'), $id); $tags = json_decode($request->input('tags')); $id->tags()->detach(); return response()->json(['message' => 'Tags mis à jour avec succès'], 200); }
问题原因及修复方案
路由前缀缺失:路由定义在
admin前缀分组下,但AJAX请求的URL是/projects/{id}/tags,缺少admin前缀,导致路由不匹配触发404。- 修复:改用Laravel的
route()函数生成正确URL,避免硬编码:deleteRequest.open('DELETE', "{{ route('delete-tags', ['id' => $project->id]) }}", true);
- 修复:改用Laravel的
路由模型绑定参数优化:控制器方法参数是
Projet $id,建议将参数名与模型变量名统一为Projet $project,增强代码可读性,同时可以指定要分离的标签ID,无需全部分离:public function deleteTags(Request $request, Projet $project) { $tags = json_decode($request->input('tags')); $project->tags()->detach($tags); return response()->json(['message' => 'Tags mis à jour avec succès'], 200); }CSRF头标准化:Laravel标准的CSRF请求头是
X-CSRF-TOKEN,当前代码中是X-CSRF-Token,虽然不区分大小写,但建议改成标准写法:deleteRequest.setRequestHeader('X-CSRF-TOKEN', csrfToken);请求数据格式优化:如果发送JSON数据,建议设置正确的
Content-Type并发送JSON字符串,后端无需额外解码:deleteRequest.setRequestHeader('Content-Type', 'application/json'); deleteRequest.send(JSON.stringify({ tags: tagIds }));后端对应修改:
$tags = $request->input('tags'); $project->tags()->detach($tags);
内容的提问来源于stack exchange,提问作者Alexis P.
相关产品推荐
相关产品推荐

