课程-章节-课时顺序调整完成
This commit is contained in:
@@ -112,6 +112,49 @@ def delete_material_chapter_lesson(material_id, chapter_name, lesson_name, teach
|
|||||||
# 如果删除操作成功,res.modified_count 应为 1
|
# 如果删除操作成功,res.modified_count 应为 1
|
||||||
return res.modified_count == 1
|
return res.modified_count == 1
|
||||||
|
|
||||||
|
def reorder_material_structure(material_id, order):
|
||||||
|
mongo = current_app.extensions["mongo"]
|
||||||
|
material = mongo.db.materials.find_one({'_id': ObjectId(material_id)})
|
||||||
|
if not material:
|
||||||
|
return False
|
||||||
|
chapter_map = {ch["chapter_name"]: ch for ch in material["chapters"]}
|
||||||
|
new_chapters = []
|
||||||
|
for chapter_order in order:
|
||||||
|
ch_name = chapter_order['chapter_name']
|
||||||
|
if ch_name not in chapter_map:
|
||||||
|
continue # 如果前端传了不存在的章节名,可以选择跳过或报错
|
||||||
|
original_ch = chapter_map[ch_name]
|
||||||
|
|
||||||
|
# 建立 lesson_name -> lesson dict
|
||||||
|
lesson_map = {ls["lesson_name"]: ls for ls in original_ch["lessons"]}
|
||||||
|
|
||||||
|
# 按顺序重排 lessons
|
||||||
|
new_lessons = []
|
||||||
|
for ls_name in chapter_order['lessons']:
|
||||||
|
if ls_name in lesson_map:
|
||||||
|
new_lessons.append(lesson_map[ls_name])
|
||||||
|
|
||||||
|
# 如果还有遗漏(前端没传的),可以追加到最后,避免丢数据
|
||||||
|
for ls_name, ls in lesson_map.items():
|
||||||
|
if ls not in new_lessons:
|
||||||
|
new_lessons.append(ls)
|
||||||
|
|
||||||
|
# 构造新的 chapter
|
||||||
|
new_chapter = {**original_ch, "lessons": new_lessons}
|
||||||
|
new_chapters.append(new_chapter)
|
||||||
|
|
||||||
|
# 更新 material
|
||||||
|
mongo.db.materials.update_one(
|
||||||
|
{"_id": ObjectId(material_id)},
|
||||||
|
{
|
||||||
|
"$set": {
|
||||||
|
"chapters": new_chapters,
|
||||||
|
"updated_at": datetime.now()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
return True
|
||||||
|
|
||||||
def save_materials_markdown_cos(
|
def save_materials_markdown_cos(
|
||||||
material_id: str,
|
material_id: str,
|
||||||
chapter_name: str,
|
chapter_name: str,
|
||||||
|
|||||||
40
Html/apps/static/css/teacher_course_setting.css
Normal file
40
Html/apps/static/css/teacher_course_setting.css
Normal file
@@ -0,0 +1,40 @@
|
|||||||
|
/* 开启排序模式时,统一卡片样式 */
|
||||||
|
#chapter-list.reorder-mode .chapter-item,
|
||||||
|
#chapter-list.reorder-mode .lesson-item {
|
||||||
|
border: 2px solid #ddd;
|
||||||
|
border-radius: 10px;
|
||||||
|
background: #fff;
|
||||||
|
cursor: grab;
|
||||||
|
transition: box-shadow .2s, border-color .2s;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 间距与内边距 */
|
||||||
|
.chapter-item { padding: 8px 10px; margin: 8px 0; }
|
||||||
|
.lesson-item { padding: 6px 10px; margin: 6px 0; }
|
||||||
|
|
||||||
|
/* hover/active 反馈 */
|
||||||
|
#chapter-list.reorder-mode .chapter-item:hover,
|
||||||
|
#chapter-list.reorder-mode .lesson-item:hover {
|
||||||
|
border-color: #4CAF50;
|
||||||
|
box-shadow: 0 2px 6px rgba(0,0,0,.1);
|
||||||
|
}
|
||||||
|
#chapter-list.reorder-mode .chapter-item:active,
|
||||||
|
#chapter-list.reorder-mode .lesson-item:active {
|
||||||
|
cursor: grabbing;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dragging { opacity: .6; }
|
||||||
|
|
||||||
|
/* 放置位置提示线(在元素顶部/底部) */
|
||||||
|
.drop-indicator-top { box-shadow: inset 0 3px 0 0 #4CAF50; }
|
||||||
|
.drop-indicator-bottom { box-shadow: inset 0 -3px 0 0 #4CAF50; }
|
||||||
|
|
||||||
|
/* 把 lesson 拖到 chapter 标题上时,高亮标题,表示会追加到该章节末尾 */
|
||||||
|
.chapter-header.drop-target {
|
||||||
|
outline: 2px dashed #4CAF50;
|
||||||
|
border-radius: 6px;
|
||||||
|
}
|
||||||
|
.chapter-header {
|
||||||
|
display: block; /* 使元素成为块级元素,占满整行 */
|
||||||
|
/* 文字默认左对齐,无需额外设置text-align: left */
|
||||||
|
}
|
||||||
11
Html/apps/static/js/course_current.js
Normal file
11
Html/apps/static/js/course_current.js
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
window.EDIT_COURSE_CURRENT = window.EDIT_COURSE_CURRENT || {
|
||||||
|
material_id: null,
|
||||||
|
outline: {
|
||||||
|
// phases: [{ text: chapterName }, ...]
|
||||||
|
phases: [],
|
||||||
|
// stepsByPhase: { [chapterName]: [lessonName, ...] }
|
||||||
|
stepsByPhase: {}
|
||||||
|
},
|
||||||
|
meta: {}
|
||||||
|
};
|
||||||
|
|
||||||
314
Html/apps/static/js/teacher_course_setting.js
Normal file
314
Html/apps/static/js/teacher_course_setting.js
Normal file
@@ -0,0 +1,314 @@
|
|||||||
|
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -122,6 +122,11 @@ function renderChapters(courseId) {
|
|||||||
const chapterList = document.getElementById('chapter-list');
|
const chapterList = document.getElementById('chapter-list');
|
||||||
chapterList.innerHTML = ""; // 清空章节列表
|
chapterList.innerHTML = ""; // 清空章节列表
|
||||||
material_id = courseId;
|
material_id = courseId;
|
||||||
|
|
||||||
|
window.material_id = courseId; // 兼容你原来的全局变量
|
||||||
|
window.EDIT_COURSE_CURRENT = window.EDIT_COURSE_CURRENT || {};
|
||||||
|
EDIT_COURSE_CURRENT.material_id = courseId; // 新的统一来源
|
||||||
|
|
||||||
fetch(`/materials/${courseId}`)
|
fetch(`/materials/${courseId}`)
|
||||||
.then(response => response.json())
|
.then(response => response.json())
|
||||||
.then(data => {
|
.then(data => {
|
||||||
@@ -137,13 +142,13 @@ function renderChapters(courseId) {
|
|||||||
|
|
||||||
data.chapters.forEach(chapter => {
|
data.chapters.forEach(chapter => {
|
||||||
const chapterItem = document.createElement('li');
|
const chapterItem = document.createElement('li');
|
||||||
chapterItem.innerHTML = `<strong>${chapter.chapter_name}</strong>`;
|
chapterItem.classList.add('chapter-item');
|
||||||
|
chapterItem.innerHTML = `<strong class="chapter-header">${chapter.chapter_name}</strong>`;
|
||||||
|
|
||||||
const lessonList = document.createElement('ul');
|
const lessonList = document.createElement('ul');
|
||||||
|
lessonList.classList.add('lessons-list');
|
||||||
chapter.lessons.forEach(lesson => {
|
chapter.lessons.forEach(lesson => {
|
||||||
const lessonItem = document.createElement('li');
|
lessonList.innerHTML += lessonTemplate(material_id,chapter.chapter_name,lesson.lesson_name);
|
||||||
lessonItem.innerHTML = lessonTemplate(material_id,chapter.chapter_name,lesson.lesson_name);
|
|
||||||
lessonList.appendChild(lessonItem);
|
|
||||||
});
|
});
|
||||||
chapterItem.appendChild(lessonList);
|
chapterItem.appendChild(lessonList);
|
||||||
const addBtn = document.createElement('button');
|
const addBtn = document.createElement('button');
|
||||||
|
|||||||
@@ -7,6 +7,7 @@
|
|||||||
<link rel="stylesheet" href="/static/css/teacherboard.css">
|
<link rel="stylesheet" href="/static/css/teacherboard.css">
|
||||||
<link rel="stylesheet" href="/static/css/dashboard.css">
|
<link rel="stylesheet" href="/static/css/dashboard.css">
|
||||||
<link rel="stylesheet" href="/static/css/lesson.css">
|
<link rel="stylesheet" href="/static/css/lesson.css">
|
||||||
|
<link rel="stylesheet" href="/static/css/teacher_course_setting.css">
|
||||||
|
|
||||||
|
|
||||||
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
|
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
|
||||||
@@ -24,7 +25,11 @@
|
|||||||
<div class="details-content" id="details-content">
|
<div class="details-content" id="details-content">
|
||||||
<p>...</p> <!-- 加载覆盖 -->
|
<p>...</p> <!-- 加载覆盖 -->
|
||||||
</div>
|
</div>
|
||||||
<h3>课程章节列表:</h3>
|
<h3>课程章节列表:
|
||||||
|
<button id="toggle-reorder" class="reorder-btn" title="设置章节顺序" aria-pressed="false">
|
||||||
|
<i class="fa-solid fa-arrow-up-short-wide"></i>
|
||||||
|
</button>
|
||||||
|
</h3>
|
||||||
<ul id="chapter-list">
|
<ul id="chapter-list">
|
||||||
<div id="loading-message"><p>加载中...</p></div>
|
<div id="loading-message"><p>加载中...</p></div>
|
||||||
</ul>
|
</ul>
|
||||||
@@ -62,7 +67,9 @@
|
|||||||
</main>
|
</main>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<script src="/static/js/course_current.js"></script>
|
||||||
<script src="/static/js/teacherboard.js"></script>
|
<script src="/static/js/teacherboard.js"></script>
|
||||||
|
<script src="/static/js/teacher_course_setting.js"></script>
|
||||||
<script src="/static/js/lesson.js"></script>
|
<script src="/static/js/lesson.js"></script>
|
||||||
<script>
|
<script>
|
||||||
window.lesson = {{ lesson|tojson|safe }};
|
window.lesson = {{ lesson|tojson|safe }};
|
||||||
|
|||||||
@@ -6,6 +6,7 @@
|
|||||||
<title>教师个人主页</title>
|
<title>教师个人主页</title>
|
||||||
<link rel="stylesheet" href="/static/css/teacherboard.css">
|
<link rel="stylesheet" href="/static/css/teacherboard.css">
|
||||||
<link rel="stylesheet" href="/static/css/dashboard.css">
|
<link rel="stylesheet" href="/static/css/dashboard.css">
|
||||||
|
<link rel="stylesheet" href="/static/css/teacher_course_setting.css">
|
||||||
|
|
||||||
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
|
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
|
||||||
</head>
|
</head>
|
||||||
@@ -62,7 +63,11 @@
|
|||||||
<button class="close-btn" onclick="closeCourseDetails()">×</button>
|
<button class="close-btn" onclick="closeCourseDetails()">×</button>
|
||||||
</div>
|
</div>
|
||||||
<div class="details-content" id="details-content"></div>
|
<div class="details-content" id="details-content"></div>
|
||||||
<h3>课程章节列表:</h3>
|
<h3>课程章节列表:
|
||||||
|
<button id="toggle-reorder" class="reorder-btn" title="设置章节顺序" aria-pressed="false">
|
||||||
|
<i class="fa-solid fa-arrow-up-short-wide"></i>
|
||||||
|
</button>
|
||||||
|
</h3>
|
||||||
<ul id="chapter-list">
|
<ul id="chapter-list">
|
||||||
<div id="loading-message">
|
<div id="loading-message">
|
||||||
<p>加载中...</p> <!-- 加载中效果 -->
|
<p>加载中...</p> <!-- 加载中效果 -->
|
||||||
@@ -122,8 +127,9 @@
|
|||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<script src="/static/js/course_current.js"></script>
|
||||||
<script src="/static/js/teacherboard.js"></script>
|
<script src="/static/js/teacherboard.js"></script>
|
||||||
|
<script src="/static/js/teacher_course_setting.js"></script>
|
||||||
|
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
from flask import Blueprint, request, jsonify, current_app, session, render_template, session
|
from flask import Blueprint, request, jsonify, current_app, session, render_template, session
|
||||||
from ..services.course_service import create_material, update_material, get_materials_by_teacher, load_material, get_materials_by_teacher_dict, add_material_chapter, add_material_chapter_lesson, delete_material_chapter_lesson
|
from ..services.course_service import create_material, update_material, get_materials_by_teacher, load_material, get_materials_by_teacher_dict, add_material_chapter, add_material_chapter_lesson, delete_material_chapter_lesson, reorder_material_structure
|
||||||
from ..auth.decorators import require_role
|
from ..auth.decorators import require_role
|
||||||
from ..services.cos_service import upload_file
|
from ..services.cos_service import upload_file
|
||||||
bp = Blueprint('material', __name__)
|
bp = Blueprint('material', __name__)
|
||||||
@@ -91,6 +91,15 @@ def delete_lesson(material_id):
|
|||||||
return jsonify({"message": "课时删除成功"}), 200
|
return jsonify({"message": "课时删除成功"}), 200
|
||||||
return jsonify({"message": "课时删除失败"}), 404
|
return jsonify({"message": "课时删除失败"}), 404
|
||||||
|
|
||||||
|
@bp.route('/materials/structure/reorder/<material_id>', methods=['POST'])
|
||||||
|
@require_role(roles="teacher")
|
||||||
|
def reorder_material(material_id):
|
||||||
|
data = request.get_json()
|
||||||
|
order = data.get('order')
|
||||||
|
if reorder_material_structure(material_id, order):
|
||||||
|
return jsonify({"message": "教材顺序更新成功"}), 200
|
||||||
|
return jsonify({"message": "教材顺序更新失败"}), 404
|
||||||
|
|
||||||
@bp.route('/teacher/materials', methods=['GET'])
|
@bp.route('/teacher/materials', methods=['GET'])
|
||||||
@require_role(roles="teacher")
|
@require_role(roles="teacher")
|
||||||
def get_materials_by_teacher_view():
|
def get_materials_by_teacher_view():
|
||||||
|
|||||||
Reference in New Issue
Block a user