如何用Laravel、Bootstrap和JS实现课程场景支持自动切换的单开手风琴?

实现步骤
依赖确认
先确保项目已引入Bootstrap 5,Laravel项目可以直接用npm安装,或者在Blade模板引入官方CDN即可,不用额外安装其他UI组件。
1. 后端逻辑准备
控制器中取出当前用户的课程列表,按学习顺序排序,带上完成状态:
// CourseController.php public function index() { $courses = auth()->user()->courses() ->orderBy('sort', 'asc') ->get(['id', 'title', 'study_content', 'is_completed']); return view('courses.index', compact('courses')); }
新增标记课程完成的接口:
// routes/web.php Route::post('/courses/{course}/finish', [CourseController::class, 'finish'])->name('courses.finish'); // CourseController.php public function finish(Course $course) { // 可自行补充权限校验逻辑,确保是当前用户的课程 $course->update(['is_completed' => 1]); return response()->json(['success' => 1]); }
2. Blade模板渲染手风琴
直接用Bootstrap原生手风琴结构,自带data-bs-parent属性就可以实现同一时间只展开一个面板的需求,不需要额外写JS控制:
<!-- 记得在head标签中加csrf meta标签 --> <meta name="csrf-token" content="{{ csrf_token() }}"> <div class="accordion" id="courseAccordion"> @foreach($courses as $k => $course) <div class="accordion-item course-card" data-id="{{ $course->id }}" data-is-completed="{{ $course->is_completed ? 1 : 0 }}"> <h2 class="accordion-header" id="heading{{ $course->id }}"> <button class="accordion-button {{ $course->is_completed ? 'collapsed' : ($k > 0 ? 'collapsed' : '') }}" type="button" data-bs-toggle="collapse" data-bs-target="#collapse{{ $course->id }}" aria-expanded="{{ $course->is_completed ? 'false' : ($k == 0 ? 'true' : 'false') }}" aria-controls="collapse{{ $course->id }}"> {{ $course->title }} @if($course->is_completed) <span class="ms-2 badge bg-success">已完成</span> @endif </button> </h2> <div id="collapse{{ $course->id }}" class="accordion-collapse collapse {{ $course->is_completed ? '' : ($k == 0 ? 'show' : '') }}" aria-labelledby="heading{{ $course->id }}" data-bs-parent="#courseAccordion"> <div class="accordion-body"> {{ $course->study_content }} <button class="btn btn-primary mt-3 finish-btn" data-id="{{ $course->id }}">标记完成</button> </div> </div> </div> @endforeach </div>
3. JS交互逻辑
监听完成按钮点击,请求接口成功后自动折叠当前面板,展开下一节未完成的课程:
document.addEventListener('DOMContentLoaded', () => { // 绑定所有完成按钮的点击事件 document.querySelectorAll('.finish-btn').forEach(btn => { btn.addEventListener('click', e => { const courseId = e.target.dataset.id; const currentCard = document.querySelector(`.course-card[data-id="${courseId}"]`); const currentCollapse = bootstrap.Collapse.getInstance(document.getElementById(`collapse${courseId}`)); // 请求后端标记课程完成 fetch("{{ route('courses.finish', '') }}/" + courseId, { method: 'POST', headers: { 'X-CSRF-TOKEN': document.querySelector('meta[name="csrf-token"]').content, 'Content-Type': 'application/json' } }).then(res => res.json()).then(res => { if (!res.success) return; // 折叠当前已完成面板 currentCollapse.hide(); // 新增已完成标记 currentCard.dataset.isCompleted = '1'; currentCard.querySelector('.accordion-button').insertAdjacentHTML('beforeend', '<span class="ms-2 badge bg-success">已完成</span>'); // 查找下一个未完成的课程并展开 const nextCard = currentCard.nextElementSibling; if (nextCard && nextCard.dataset.isCompleted == '0') { new bootstrap.Collapse(document.getElementById(`collapse${nextCard.dataset.id}`), { toggle: true }); } }) }) }) })
内容的提问来源于stack exchange,提问作者Hasib Shanto
相关产品推荐
相关产品推荐

