Files
hsa/Html/apps/static/js/teacher_course_setting.js

457 lines
16 KiB
JavaScript
Raw Permalink 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() {
setRenameMode(false);
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';
});
}
const chapterList = document.getElementById('chapter-list');
const btnReorder = document.getElementById('toggle-reorder');
const btnRename = document.getElementById('toggle-rename');
function setReorderMode(enabled) {
if (enabled) {
enableReorderMode();
} else {
disableReorderMode();
}
}
function setRenameMode(enabled) {
btnRename?.setAttribute('aria-pressed', String(enabled));
chapterList.classList.toggle('chapter-rename-mode', enabled);
// const chapterContainers = document.querySelectorAll('li.chapter-item');
// const lessonContainers = document.querySelectorAll('li.lesson-item');
// chapterContainers.forEach(container => {
// container.classList.toggle('chapter-rename-mode', enabled);
// });
// lessonContainers.forEach(container => {
// container.classList.toggle('chapter-rename-mode', enabled);
// });
if (enabled) {
document.querySelectorAll('.add-lesson-btn').forEach(btn => {
btn.style.display = 'none';
});
document.querySelectorAll('.add-chapter-btn').forEach(btn => {
btn.style.display = 'none';
});
} else {
document.querySelectorAll('.add-lesson-btn').forEach(btn => {
btn.style.display = 'block';
});
document.querySelectorAll('.add-chapter-btn').forEach(btn => {
btn.style.display = 'block';
});
}
}
// —— 点击“改名模式”按钮 —— //
btnRename?.addEventListener('click', (e) => {
e.preventDefault();
const willEnable = btnRename.getAttribute('aria-pressed') !== 'true';
if (willEnable && btnReorder?.getAttribute('aria-pressed') === 'true') {
setReorderMode(false);
}
setRenameMode(willEnable);
});
// 获取展示文字chapter-item 用 <strong>lesson-item 用 <span>
function getDisplayText(el) {
if (el.classList.contains('chapter-item')) {
return (el.querySelector('strong')?.textContent ?? '').trim();
}
if (el.classList.contains('lesson-item')) {
return (el.querySelector('span')?.textContent ?? '').trim();
}
// 兜底
return '';
}
// 设置展示文字chapter-item 用 <strong>lesson-item 用 <span>
function setDisplayText(el, text) {
if (el.classList.contains('chapter-item')) {
let node = el.querySelector('strong');
if (!node) {
node = document.createElement('strong');
el.prepend(node);
}
node.textContent = text;
return;
}
if (el.classList.contains('lesson-item')) {
let node = el.querySelector('span');
if (!node) {
node = document.createElement('span');
el.prepend(node);
}
node.textContent = text;
return;
}
}
// —— 事件代理:改名模式下点击 li 触发重命名 —— //
chapterList.addEventListener('click', (e) => {
if (btnRename?.getAttribute('aria-pressed') !== 'true') return; // 非改名模式
const li = e.target.closest?.('li.chapter-item, li.lesson-item');
if (!li) return;
e.preventDefault();
e.stopPropagation();
e.stopImmediatePropagation(); // 若你担心同层还有其他捕获监听,也可用这个更狠的
const isChapter = li.classList.contains('chapter-item');
const isLesson = li.classList.contains('lesson-item');
// 读取旧名(章节:显示文字;课时:优先 data-lesson-name
const oldName = isLesson
? (li.dataset.lessonName || getDisplayText(li))
: getDisplayText(li);
openTitleEditor({
title: isChapter ? '修改章节名称' : '修改课时名称',
initialValue: oldName,
onSubmit: async (value) => {
const res = validateTitle(value);
// 先持久化,成功后在本地同步
const resSave = await fetch(`/materials/structure/rename/${encodeURIComponent(EDIT_COURSE_CURRENT.material_id)}`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ is_chapter: isChapter, chapter_name: isChapter ? oldName : li.dataset.chapterName, lesson_name: isChapter ? "" : li.dataset.lessonName, new_name: value })
})
const data = await resSave.json();
if (!data.ok) return { ok: false, message: data.message };
const newName = res.value;
if (isChapter) { // 将章节改名同步到本地
setDisplayText(li, newName);
if (li.dataset.chapterName !== undefined) {
li.dataset.chapterName = newName;
}
const scope = li.querySelectorAll?.('li.lesson-item');
if (scope && scope.length) {
scope.forEach(item => {
item.dataset.chapterName = newName;
});
}
}
if (isLesson) { // 将课时改名同步到本地
setDisplayText(li, newName);
li.dataset.lessonName = newName;
}
return { ok: true };
}
});
});
/* 工具选择器 */
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('已保存顺序')
console.log(payload);
} catch (err) {
console.error(err);
}
}