Files
hsa/Html/apps/static/js/teacher_course_setting.js
2025-09-03 20:33:23 +08:00

314 lines
11 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

const chapterListEl = document.getElementById('chapter-list');
const toggleBtn = document.getElementById('toggle-reorder');
toggleBtn.addEventListener('click', () => {
const active = toggleBtn.getAttribute('aria-pressed') === 'true';
active ? disableReorderMode() : enableReorderMode();
});
function enableReorderMode() {
toggleBtn.setAttribute('aria-pressed', 'true');
chapterListEl.classList.add('reorder-mode');
makeChaptersDraggable(true);
makeLessonsDraggable(true);
document.querySelectorAll('.add-lesson-btn').forEach(btn => {
btn.style.display = 'none';
});
document.querySelectorAll('.add-chapter-btn').forEach(btn => {
btn.style.display = 'none';
});
}
function disableReorderMode() {
toggleBtn.setAttribute('aria-pressed', 'false');
chapterListEl.classList.remove('reorder-mode');
makeChaptersDraggable(false);
makeLessonsDraggable(false);
document.querySelectorAll('.add-lesson-btn').forEach(btn => {
btn.style.display = 'block';
});
document.querySelectorAll('.add-chapter-btn').forEach(btn => {
btn.style.display = 'block';
});
}
/* 工具选择器 */
function getChapterItems() {
return Array.from(chapterListEl.querySelectorAll(':scope > li.chapter-item'));
}
function getLessonItems(chapterLi) {
const list = chapterLi.querySelector(':scope > ul.lessons-list');
return list ? Array.from(list.children).filter(el => el.classList.contains('lesson-item')) : [];
}
/* —— 章节拖拽 —— */
let draggingEl = null; // 当前拖拽的元素chapter or lesson
let draggingType = null; // 'chapter' | 'lesson'
function makeChaptersDraggable(enable) {
getChapterItems().forEach(chLi => {
const header = chLi.querySelector(':scope > .chapter-header');
if (!header) return;
header.draggable = enable; // 只让 header 可拖
if (enable) {
header.addEventListener('dragstart', e => onChapterHeaderDragStart(e, chLi));
header.addEventListener('dragend', onDragEndCommon);
} else {
header.removeEventListener('dragstart', e => onChapterHeaderDragStart(e, chLi));
header.removeEventListener('dragend', onDragEndCommon);
}
// 章节 li 仍作为“放置目标”
chLi.addEventListener('dragover', onChapterDragOver);
chLi.addEventListener('dragleave', onDragLeaveCommon);
chLi.addEventListener('drop', onChapterDrop);
});
}
function onChapterHeaderDragStart(e, chLi) {
// 若从 lesson 起手,直接拦截(双保险)
if (e.target.closest('.lesson-item')) { e.preventDefault(); return; }
draggingEl = chLi; // 拖动的是整个章节 li
draggingType = 'chapter';
chLi.classList.add('dragging');
e.dataTransfer.effectAllowed = 'move';
e.dataTransfer.setData('text/plain', chLi.dataset.chapterName || '');
}
function onChapterDragStart(e) {
draggingEl = this;
draggingType = 'chapter';
this.classList.add('dragging');
e.dataTransfer.effectAllowed = 'move';
e.dataTransfer.setData('text/plain', this.dataset.chapterName || '');
}
function onChapterDragOver(e) {
e.preventDefault();
if (draggingType !== 'chapter' || draggingEl === this) return;
const rect = this.getBoundingClientRect();
const before = e.clientY < rect.top + rect.height / 2;
this.classList.toggle('drop-indicator-top', before);
this.classList.toggle('drop-indicator-bottom', !before);
}
function onChapterDrop(e) {
e.preventDefault();
this.classList.remove('dragging')
if (draggingType !== 'chapter' || draggingEl === this) return;
const rect = this.getBoundingClientRect();
const before = e.clientY < rect.top + rect.height / 2;
const parent = chapterListEl;
if (before) parent.insertBefore(draggingEl, this);
else parent.insertBefore(draggingEl, this.nextSibling);
clearIndicators();
syncOutlineFromDOM();
}
/* —— 课时拖拽(支持跨章节)—— */
function makeLessonsDraggable(enable) {
getChapterItems().forEach(chLi => {
// 子列表作为 drop 容器
let ul = chLi.querySelector(':scope > ul.lessons-list');
if (!ul) {
ul = document.createElement('ul');
ul.className = 'lessons-list';
chLi.appendChild(ul);
}
// lessons 本身可拖拽
getLessonItems(chLi).forEach(li => {
li.draggable = enable;
if (enable) {
li.addEventListener('dragstart', onLessonDragStart);
li.addEventListener('dragend', onDragEndCommon);
li.addEventListener('dragover', onLessonDragOver);
li.addEventListener('dragleave', onDragLeaveCommon);
li.addEventListener('drop', onLessonDrop);
} else {
li.removeEventListener('dragstart', onLessonDragStart);
li.removeEventListener('dragend', onDragEndCommon);
li.removeEventListener('dragover', onLessonDragOver);
li.removeEventListener('dragleave', onDragLeaveCommon);
li.removeEventListener('drop', onLessonDrop);
li.classList.remove('dragging','drop-indicator-top','drop-indicator-bottom');
}
});
// 容器允许作为 drop 目标:把 lesson 拖入空列表时可追加
if (enable) {
ul.addEventListener('dragover', onLessonsListDragOver);
ul.addEventListener('drop', onLessonsListDrop);
} else {
ul.removeEventListener('dragover', onLessonsListDragOver);
ul.removeEventListener('drop', onLessonsListDrop);
}
});
}
function onLessonDragStart(e) {
draggingEl = this;
draggingType = 'lesson';
this.classList.add('dragging');
e.dataTransfer.effectAllowed = 'move';
e.dataTransfer.setData('text/plain', this.dataset.lessonName || '');
}
function onLessonDragOver(e) {
e.preventDefault();
if (draggingType !== 'lesson' || draggingEl === this) return;
const rect = this.getBoundingClientRect();
const before = e.clientY < rect.top + rect.height / 2;
this.classList.toggle('drop-indicator-top', before);
this.classList.toggle('drop-indicator-bottom', !before);
}
function onLessonDrop(e) {
e.preventDefault();
if (draggingType !== 'lesson' || draggingEl === this) return;
const rect = this.getBoundingClientRect();
const before = e.clientY < rect.top + rect.height / 2;
const parent = this.parentElement; // lessons-list
if (before) parent.insertBefore(draggingEl, this);
else parent.insertBefore(draggingEl, this.nextSibling);
clearIndicators();
syncOutlineFromDOM();
}
/* 把 lesson 拖到某章节“标题”上:表示追加到该章节末尾 */
function onChapterHeaderDragOver(e) {
if (draggingType !== 'lesson') return;
e.preventDefault();
this.classList.add('drop-target');
}
function onChapterHeaderDragLeave() {
this.classList.remove('drop-target');
}
function onChapterHeaderDrop(e) {
if (draggingType !== 'lesson') return;
e.preventDefault();
const chapterLi = this.closest('li.chapter-item');
const ul = chapterLi.querySelector(':scope > ul.lessons-list');
ul.appendChild(draggingEl); // 追加到末尾
this.classList.remove('drop-target');
clearIndicators();
syncOutlineFromDOM();
}
/* 拖到空 lessons-list 上:也允许追加 */
function onLessonsListDragOver(e) {
if (draggingType !== 'lesson') return;
e.preventDefault();
}
function onLessonsListDrop(e) {
if (draggingType !== 'lesson') return;
e.preventDefault();
this.appendChild(draggingEl);
clearIndicators();
syncOutlineFromDOM();
}
/* 通用 */
function onDragEndCommon() {
// 确保结束时移除 'dragging' 状态
if (draggingEl) {
draggingEl.classList.remove('dragging'); // 移除 'dragging' 状态
draggingEl = null;
draggingType = null;
}
getChapterItems().forEach(chLi => {
const ul = chLi.querySelector(':scope > ul.lessons-list');
if (!ul) return;
console.log(getLessonItems(chLi).length);
// 如果课时数量大于 2移除虚拟的课时元素
if (getLessonItems(chLi).length >= 1) {
const placeholder = ul.querySelector('.placeholder');
if (placeholder) {
placeholder.remove();
}
}
// 如果章节为空,添加一个虚拟的课时
if (getLessonItems(chLi).length == 0) {
const placeholderLesson = document.createElement('li');
placeholderLesson.classList.add('lesson-item', 'placeholder');
placeholderLesson.textContent = '拖拽课时到这里';
ul.appendChild(placeholderLesson);
}
});
}
function onDragLeaveCommon() {
this.classList.remove('drop-indicator-top','drop-indicator-bottom');
}
function clearIndicators() {
chapterListEl.querySelectorAll('.drop-indicator-top, .drop-indicator-bottom')
.forEach(el => el.classList.remove('drop-indicator-top','drop-indicator-bottom'));
chapterListEl.querySelectorAll('.chapter-header.drop-target')
.forEach(el => el.classList.remove('drop-target'));
}
function syncOutlineFromDOM() {
const chapters = getChapterItems().map(chLi => {
const name =
chLi.dataset.chapterName ||
chLi.querySelector(':scope > .chapter-header')?.textContent.trim();
const lessons = getLessonItems(chLi)
.filter(li => !li.classList.contains('placeholder'))
.map(li => li.dataset.lessonName || li.textContent.trim());
return { chapter_name: name, lessons };
});
// 写入 CURRENT
if (window.EDIT_COURSE_CURRENT) {
EDIT_COURSE_CURRENT.outline.phases = chapters.map(c => ({ text: c.chapter_name }));
EDIT_COURSE_CURRENT.outline.stepsByPhase = {};
chapters.forEach(c => {
EDIT_COURSE_CURRENT.outline.stepsByPhase[c.chapter_name] = c.lessons;
});
}
// 立即持久化
schedulePersistOrder(chapters);
}
// 定义一个全局/模块级变量,用来存放定时器 id
let persistOrderTimer = null;
function schedulePersistOrder(chapters) {
// 如果已有定时器,先清掉
if (persistOrderTimer) {
clearTimeout(persistOrderTimer);
}
// 设置新的定时器1 秒后真正执行
persistOrderTimer = setTimeout(() => {
persistOrderNow(chapters);
persistOrderTimer = null; // 清理引用
}, 1000);
}
// 真正的持久化逻辑
async function persistOrderNow(chapters) {
try {
const payload = chapters || getChapterItems().map(chLi => ({
chapter_name:
chLi.dataset.chapterName ||
chLi.querySelector(':scope > .chapter-header')?.textContent.trim(),
lessons: getLessonItems(chLi).map(
li => li.dataset.lessonName || li.textContent.trim()
)
}));
const res = await fetch(`/materials/structure/reorder/${encodeURIComponent(EDIT_COURSE_CURRENT.material_id)}`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
credentials: 'include',
body: JSON.stringify({ order: payload })
});
if (!res.ok) throw new Error('保存新顺序失败');
console.log('已保存顺序')
} catch (err) {
console.error(err);
}
}