diff --git a/scratch-gui/src/playground/ai.ejs b/scratch-gui/src/playground/ai.ejs
index 03322ae7..27359e0f 100644
--- a/scratch-gui/src/playground/ai.ejs
+++ b/scratch-gui/src/playground/ai.ejs
@@ -1302,6 +1302,12 @@
灵丌AI
+
个人信息
@@ -1792,6 +1798,17 @@
return !!localStorage.getItem('access_token');
}
+ function getExplorerRole() {
+ var user = window.aiExplorerUserInfo || {};
+ return String(user.role || '').trim();
+ }
+
+ function isStudentRole() {
+ var role = getExplorerRole();
+ var lower = role.toLowerCase();
+ return lower === 'student' || role === '学生';
+ }
+
function authHeaders() {
var headers = { 'Content-Type': 'application/json' };
var token = localStorage.getItem('access_token');
@@ -1894,13 +1911,16 @@
var goBtnHtml = showGoBtn
? '
'
: '';
+ var countHtml = isStudentRole()
+ ? ''
+ : '
' + count + ' 人';
return '
' +
'
' +
'
' + name + '
' +
'
年级:' + grade + '
' +
'
' +
'
' +
- '' + count + ' 人' +
+ countHtml +
goBtnHtml +
'
' +
'
';
diff --git a/scratch-gui/src/playground/config.js b/scratch-gui/src/playground/config.js
index 20627164..0e5de0c4 100644
--- a/scratch-gui/src/playground/config.js
+++ b/scratch-gui/src/playground/config.js
@@ -160,7 +160,7 @@ const CONFIG = {
// DataServerBaseUrl: "https://test-service.001code.cn", // 在这里定义服务器地址
// DataServerBaseUrl: "https://test-service.001code.cn", // 在这里定义服务器地址
// DataServerBaseUrl: "https://test-service.001code.cn",
- DataServerBaseUrl: "https://test-service.001code.cn",
+ DataServerBaseUrl: "https://service.001code.cn",
// DataServerBaseUrl: "https://test-service.001code.cn",
// DataServerBaseUrl: "https://test-service.001code.cn",
diff --git a/scratch-gui/src/playground/course-detail.ejs b/scratch-gui/src/playground/course-detail.ejs
index 67e24bdc..703bf7be 100644
--- a/scratch-gui/src/playground/course-detail.ejs
+++ b/scratch-gui/src/playground/course-detail.ejs
@@ -286,10 +286,20 @@
}
.toc-item__title {
font-size: 13px; font-weight: 600; color: #0b1a4a; line-height: 1.35;
+ display: flex; align-items: flex-start; gap: 6px;
+ }
+ .toc-item__num {
+ flex-shrink: 0; min-width: 1.15em;
+ font-variant-numeric: tabular-nums; font-weight: 800; color: #0003FF;
+ }
+ .toc-item__name {
+ flex: 1; min-width: 0;
display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden;
}
.toc-item.is-active .toc-item__title { color: #0003FF; }
+ .toc-item.is-active .toc-item__num { color: #0003FF; }
.toc-item.is-locked .toc-item__title { color: #64748b; font-weight: 500; }
+ .toc-item.is-locked .toc-item__num { color: #94a3b8; font-weight: 700; }
.toc-item__meta {
margin-top: 2px; font-size: 11px; color: #94a3b8;
display: flex; align-items: center; gap: 6px; flex-wrap: wrap;
@@ -960,9 +970,13 @@
function mapCourseware(cw, source, groupTitle) {
// 预览与去上课都尊重接口 is_locked;进度条仅在有 class_id 时展示,互不影响
var locked = cw.is_locked === true;
+ var sortOrder = Number(cw.sort_order);
+ if (!isFinite(sortOrder)) sortOrder = 0;
return {
id: cw.id,
title: cw.name || '未命名课件',
+ displayIndex: sortOrder + 1,
+ sortOrder: sortOrder,
type: String(cw.format || 'html').toLowerCase(),
typeLabel: cw.format_display || cw.format || '课件',
duration: cw.duration_minutes ? (cw.duration_minutes + '分钟') : '',
@@ -980,62 +994,91 @@
return (topics || []).filter(function(t) { return !t.isLocked; }).length;
}
- function buildChapters(category, source) {
- var coursewares = Array.isArray(category.coursewares) ? category.coursewares.slice() : [];
- coursewares.sort(function(a, b) {
+ function sortCoursewares(coursewares) {
+ return (coursewares || []).slice().sort(function(a, b) {
return (Number(a.sort_order) || 0) - (Number(b.sort_order) || 0);
});
- var groups = Array.isArray(category.groups) ? category.groups.slice() : [];
- var chapters = [];
+ }
- if (groups.length) {
- chapters = groups.map(function(g, idx) {
- var gName = g.group_name || ('分组' + (idx + 1));
- var topics = coursewares.filter(function(cw) {
- return String(cw.group_name || '') === String(g.group_name || '');
- }).map(function(cw) { return mapCourseware(cw, source, gName); });
- // 始终按课件实际锁定状态统计,避免 groups.unlocked_count 与课件列表不一致
- return {
- id: idx + 1,
- title: gName,
- count: topics.length || Number(g.course_count) || 0,
- unlocked: countUnlockedTopics(topics),
- topics: topics
- };
+ function collectCoursewares(container) {
+ if (!container) return [];
+ if (Array.isArray(container.coursewares) && container.coursewares.length) {
+ return container.coursewares.slice();
+ }
+ var list = [];
+ if (Array.isArray(container.groups)) {
+ container.groups.forEach(function(g) {
+ if (Array.isArray(g.coursewares)) {
+ g.coursewares.forEach(function(cw) { list.push(cw); });
+ }
});
+ }
+ return list;
+ }
- var groupedNames = {};
- groups.forEach(function(g) { groupedNames[String(g.group_name || '')] = true; });
- var orphans = coursewares.filter(function(cw) {
- var gn = String(cw.group_name || '');
- return !Object.prototype.hasOwnProperty.call(groupedNames, gn);
+ function chapterFromCoursewares(coursewares, opts) {
+ var title = opts.title || '课件列表';
+ var topics = sortCoursewares(coursewares).map(function(cw) {
+ return mapCourseware(cw, opts.source, title);
+ });
+ return {
+ id: opts.id,
+ indexLabel: opts.indexLabel != null ? opts.indexLabel : opts.id,
+ title: title,
+ intro: opts.intro || '',
+ count: topics.length,
+ unlocked: countUnlockedTopics(topics),
+ topics: topics
+ };
+ }
+
+ function buildChapters(category, source) {
+ var semesters = Array.isArray(category.semesters) ? category.semesters.slice() : [];
+ if (semesters.length) {
+ semesters.sort(function(a, b) {
+ return (Number(a.sort_order) || 0) - (Number(b.sort_order) || 0);
});
- if (orphans.length) {
- var orphanTopics = orphans.map(function(cw) {
- return mapCourseware(cw, source, '其他课件');
+ return semesters.map(function(sem, idx) {
+ var title = String(sem.name || '').trim() || ('第' + (idx + 1) + '学期');
+ return chapterFromCoursewares(collectCoursewares(sem), {
+ source: source,
+ id: sem.id != null ? sem.id : (idx + 1),
+ indexLabel: Number(sem.sort_order) || (idx + 1),
+ title: title,
+ intro: sem.intro || ''
});
- chapters.push({
- id: chapters.length + 1,
- title: '其他课件',
- count: orphanTopics.length,
- unlocked: countUnlockedTopics(orphanTopics),
- topics: orphanTopics
- });
- }
- } else {
- var topics = coursewares.map(function(cw) {
- return mapCourseware(cw, source, category.name || courseName);
});
- chapters = [{
- id: 1,
- title: category.name || courseName || '课件列表',
- count: topics.length,
- unlocked: countUnlockedTopics(topics),
- topics: topics
- }];
}
- return chapters;
+ var coursewares = collectCoursewares(category);
+ var groups = Array.isArray(category.groups) ? category.groups.slice() : [];
+ var namedGroups = groups.filter(function(g) {
+ return String(g.group_name || '').trim();
+ });
+
+ if (namedGroups.length) {
+ return namedGroups.map(function(g, idx) {
+ var gName = String(g.group_name || '').trim();
+ var list = Array.isArray(g.coursewares) && g.coursewares.length
+ ? g.coursewares
+ : coursewares.filter(function(cw) {
+ return String(cw.group_name || '') === String(g.group_name || '');
+ });
+ return chapterFromCoursewares(list, {
+ source: source,
+ id: idx + 1,
+ indexLabel: idx + 1,
+ title: gName
+ });
+ });
+ }
+
+ return [chapterFromCoursewares(coursewares, {
+ source: source,
+ id: 1,
+ indexLabel: 1,
+ title: category.name || courseName || '课件列表'
+ })];
}
function flattenLessons(chapters) {
@@ -1093,7 +1136,7 @@
return;
}
- stageTitle.textContent = lesson.title;
+ stageTitle.textContent = (lesson.displayIndex != null ? (lesson.displayIndex + '. ') : '') + lesson.title;
var metaParts = [];
if (lesson.groupTitle) metaParts.push(lesson.groupTitle);
if (lesson.typeLabel) metaParts.push(lesson.typeLabel);
@@ -1143,6 +1186,12 @@
document.getElementById('stageFrame').removeAttribute('src');
}
+ function resetLockControl(btn) {
+ if (!btn) return;
+ btn.classList.remove('is-loading');
+ btn.disabled = false;
+ }
+
function syncLockedPanel(lesson) {
var desc = document.getElementById('stageLockedDesc');
var unlockBtn = document.getElementById('stageUnlockBtn');
@@ -1153,6 +1202,8 @@
: '请联系老师解锁相关内容。';
}
if (!unlockBtn) return;
+ // 大按钮是常驻 DOM,上次解锁会留下 disabled,切到下一课未解锁课件时必须复位
+ resetLockControl(unlockBtn);
if (teacherCanUnlock) {
unlockBtn.classList.add('is-visible');
unlockBtn.setAttribute('data-id', String(lesson.id));
@@ -1328,15 +1379,16 @@
var keepLessonId = opts.keepLessonId != null ? opts.keepLessonId : currentLessonId;
listEl.innerHTML = chaptersCache.map(function(ch, idx) {
- var allUnlocked = ch.topics.length > 0 && ch.topics.every(function(t) { return !t.isLocked; });
+ var topics = ch.topics || [];
+ var allUnlocked = topics.length > 0 && topics.every(function(t) { return !t.isLocked; });
var isOpen = opts.preserveOpen
? openGroupIds.indexOf(String(ch.id)) !== -1
: idx === 0;
if (!opts.preserveOpen && keepLessonId) {
- var hasKeep = ch.topics.some(function(t) { return String(t.id) === String(keepLessonId); });
+ var hasKeep = topics.some(function(t) { return String(t.id) === String(keepLessonId); });
if (hasKeep) isOpen = true;
}
- var items = ch.topics.map(function(t) {
+ var items = topics.map(function(t) {
var tagClass = 'toc-item__tag--' + (t.status || 'doing');
var meta = escapeHtml(t.typeLabel || t.type || '课件');
if (t.duration) meta += ' · ' + escapeHtml(t.duration);
@@ -1354,7 +1406,10 @@
return '
' +
'
' +
'
' +
- '
' + escapeHtml(t.title) + '
' +
+ '
' +
+ '' + escapeHtml(String(t.displayIndex != null ? t.displayIndex : '')) + '' +
+ '' + escapeHtml(t.title) + '' +
+ '
' +
'