feat: ai功能优化

This commit is contained in:
Evan
2026-09-15 17:55:05 +08:00
parent 4499d7fdef
commit 899adbc9d4
4 changed files with 135 additions and 66 deletions

View File

@@ -1302,6 +1302,12 @@
<span class="nav__pillAIText">灵丌AI</span> <span class="nav__pillAIText">灵丌AI</span>
</span> </span>
</div> </div>
<div class="nav-link-group">
<a class="nav-link" href="/competition_list.html">赛事详情</a>
</div>
<div class="nav-link-group">
<a class="nav-link" href="/aboutus.html">关于我们</a>
</div>
</div> </div>
<div class="navbar-right"> <div class="navbar-right">
<a class="navbar-link" id="open-personal-info" href="javascript:void(0);">个人信息</a> <a class="navbar-link" id="open-personal-info" href="javascript:void(0);">个人信息</a>
@@ -1792,6 +1798,17 @@
return !!localStorage.getItem('access_token'); 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() { function authHeaders() {
var headers = { 'Content-Type': 'application/json' }; var headers = { 'Content-Type': 'application/json' };
var token = localStorage.getItem('access_token'); var token = localStorage.getItem('access_token');
@@ -1894,13 +1911,16 @@
var goBtnHtml = showGoBtn var goBtnHtml = showGoBtn
? '<button type="button" class="classes-modal__go-btn" data-class-id="' + id + '">' + escapeHtml(actionLabel) + '</button>' ? '<button type="button" class="classes-modal__go-btn" data-class-id="' + id + '">' + escapeHtml(actionLabel) + '</button>'
: ''; : '';
var countHtml = isStudentRole()
? ''
: '<span class="classes-modal__count">' + count + ' 人</span>';
return '<div class="classes-modal__item" data-class-id="' + id + '">' + return '<div class="classes-modal__item" data-class-id="' + id + '">' +
'<div class="classes-modal__main">' + '<div class="classes-modal__main">' +
'<div class="classes-modal__name">' + name + '</div>' + '<div class="classes-modal__name">' + name + '</div>' +
'<div class="classes-modal__meta">年级:' + grade + '</div>' + '<div class="classes-modal__meta">年级:' + grade + '</div>' +
'</div>' + '</div>' +
'<div class="classes-modal__aside">' + '<div class="classes-modal__aside">' +
'<span class="classes-modal__count">' + count + ' 人</span>' + countHtml +
goBtnHtml + goBtnHtml +
'</div>' + '</div>' +
'</div>'; '</div>';

View File

@@ -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://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",
// DataServerBaseUrl: "https://test-service.001code.cn", // DataServerBaseUrl: "https://test-service.001code.cn",

View File

@@ -286,10 +286,20 @@
} }
.toc-item__title { .toc-item__title {
font-size: 13px; font-weight: 600; color: #0b1a4a; line-height: 1.35; 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; 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__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__title { color: #64748b; font-weight: 500; }
.toc-item.is-locked .toc-item__num { color: #94a3b8; font-weight: 700; }
.toc-item__meta { .toc-item__meta {
margin-top: 2px; font-size: 11px; color: #94a3b8; margin-top: 2px; font-size: 11px; color: #94a3b8;
display: flex; align-items: center; gap: 6px; flex-wrap: wrap; display: flex; align-items: center; gap: 6px; flex-wrap: wrap;
@@ -960,9 +970,13 @@
function mapCourseware(cw, source, groupTitle) { function mapCourseware(cw, source, groupTitle) {
// 预览与去上课都尊重接口 is_locked进度条仅在有 class_id 时展示,互不影响 // 预览与去上课都尊重接口 is_locked进度条仅在有 class_id 时展示,互不影响
var locked = cw.is_locked === true; var locked = cw.is_locked === true;
var sortOrder = Number(cw.sort_order);
if (!isFinite(sortOrder)) sortOrder = 0;
return { return {
id: cw.id, id: cw.id,
title: cw.name || '未命名课件', title: cw.name || '未命名课件',
displayIndex: sortOrder + 1,
sortOrder: sortOrder,
type: String(cw.format || 'html').toLowerCase(), type: String(cw.format || 'html').toLowerCase(),
typeLabel: cw.format_display || cw.format || '课件', typeLabel: cw.format_display || cw.format || '课件',
duration: cw.duration_minutes ? (cw.duration_minutes + '分钟') : '', duration: cw.duration_minutes ? (cw.duration_minutes + '分钟') : '',
@@ -980,62 +994,91 @@
return (topics || []).filter(function(t) { return !t.isLocked; }).length; return (topics || []).filter(function(t) { return !t.isLocked; }).length;
} }
function buildChapters(category, source) { function sortCoursewares(coursewares) {
var coursewares = Array.isArray(category.coursewares) ? category.coursewares.slice() : []; return (coursewares || []).slice().sort(function(a, b) {
coursewares.sort(function(a, b) {
return (Number(a.sort_order) || 0) - (Number(b.sort_order) || 0); 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) { function collectCoursewares(container) {
chapters = groups.map(function(g, idx) { if (!container) return [];
var gName = g.group_name || ('分组' + (idx + 1)); if (Array.isArray(container.coursewares) && container.coursewares.length) {
var topics = coursewares.filter(function(cw) { return container.coursewares.slice();
return String(cw.group_name || '') === String(g.group_name || ''); }
}).map(function(cw) { return mapCourseware(cw, source, gName); }); var list = [];
// 始终按课件实际锁定状态统计,避免 groups.unlocked_count 与课件列表不一致 if (Array.isArray(container.groups)) {
return { container.groups.forEach(function(g) {
id: idx + 1, if (Array.isArray(g.coursewares)) {
title: gName, g.coursewares.forEach(function(cw) { list.push(cw); });
count: topics.length || Number(g.course_count) || 0, }
unlocked: countUnlockedTopics(topics),
topics: topics
};
}); });
}
return list;
}
var groupedNames = {}; function chapterFromCoursewares(coursewares, opts) {
groups.forEach(function(g) { groupedNames[String(g.group_name || '')] = true; }); var title = opts.title || '课件列表';
var orphans = coursewares.filter(function(cw) { var topics = sortCoursewares(coursewares).map(function(cw) {
var gn = String(cw.group_name || ''); return mapCourseware(cw, opts.source, title);
return !Object.prototype.hasOwnProperty.call(groupedNames, gn); });
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) { return semesters.map(function(sem, idx) {
var orphanTopics = orphans.map(function(cw) { var title = String(sem.name || '').trim() || ('第' + (idx + 1) + '学期');
return mapCourseware(cw, source, '其他课件'); 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) { function flattenLessons(chapters) {
@@ -1093,7 +1136,7 @@
return; return;
} }
stageTitle.textContent = lesson.title; stageTitle.textContent = (lesson.displayIndex != null ? (lesson.displayIndex + '. ') : '') + lesson.title;
var metaParts = []; var metaParts = [];
if (lesson.groupTitle) metaParts.push(lesson.groupTitle); if (lesson.groupTitle) metaParts.push(lesson.groupTitle);
if (lesson.typeLabel) metaParts.push(lesson.typeLabel); if (lesson.typeLabel) metaParts.push(lesson.typeLabel);
@@ -1143,6 +1186,12 @@
document.getElementById('stageFrame').removeAttribute('src'); document.getElementById('stageFrame').removeAttribute('src');
} }
function resetLockControl(btn) {
if (!btn) return;
btn.classList.remove('is-loading');
btn.disabled = false;
}
function syncLockedPanel(lesson) { function syncLockedPanel(lesson) {
var desc = document.getElementById('stageLockedDesc'); var desc = document.getElementById('stageLockedDesc');
var unlockBtn = document.getElementById('stageUnlockBtn'); var unlockBtn = document.getElementById('stageUnlockBtn');
@@ -1153,6 +1202,8 @@
: '请联系老师解锁相关内容。'; : '请联系老师解锁相关内容。';
} }
if (!unlockBtn) return; if (!unlockBtn) return;
// 大按钮是常驻 DOM上次解锁会留下 disabled切到下一课未解锁课件时必须复位
resetLockControl(unlockBtn);
if (teacherCanUnlock) { if (teacherCanUnlock) {
unlockBtn.classList.add('is-visible'); unlockBtn.classList.add('is-visible');
unlockBtn.setAttribute('data-id', String(lesson.id)); unlockBtn.setAttribute('data-id', String(lesson.id));
@@ -1328,15 +1379,16 @@
var keepLessonId = opts.keepLessonId != null ? opts.keepLessonId : currentLessonId; var keepLessonId = opts.keepLessonId != null ? opts.keepLessonId : currentLessonId;
listEl.innerHTML = chaptersCache.map(function(ch, idx) { 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 var isOpen = opts.preserveOpen
? openGroupIds.indexOf(String(ch.id)) !== -1 ? openGroupIds.indexOf(String(ch.id)) !== -1
: idx === 0; : idx === 0;
if (!opts.preserveOpen && keepLessonId) { 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; 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 tagClass = 'toc-item__tag--' + (t.status || 'doing');
var meta = escapeHtml(t.typeLabel || t.type || '课件'); var meta = escapeHtml(t.typeLabel || t.type || '课件');
if (t.duration) meta += ' · ' + escapeHtml(t.duration); if (t.duration) meta += ' · ' + escapeHtml(t.duration);
@@ -1354,7 +1406,10 @@
return '<div class="' + itemClass + '" data-id="' + t.id + '" role="button" tabindex="0"' + itemTitle + '>' + return '<div class="' + itemClass + '" data-id="' + t.id + '" role="button" tabindex="0"' + itemTitle + '>' +
'<div class="toc-item__icon"><img src="../images/aigc/icon_book.png" alt="" /></div>' + '<div class="toc-item__icon"><img src="../images/aigc/icon_book.png" alt="" /></div>' +
'<div class="toc-item__body">' + '<div class="toc-item__body">' +
'<div class="toc-item__title">' + escapeHtml(t.title) + '</div>' + '<div class="toc-item__title">' +
'<span class="toc-item__num">' + escapeHtml(String(t.displayIndex != null ? t.displayIndex : '')) + '</span>' +
'<span class="toc-item__name">' + escapeHtml(t.title) + '</span>' +
'</div>' +
'<div class="toc-item__meta">' + '<div class="toc-item__meta">' +
'<span>' + meta + '</span>' + '<span>' + meta + '</span>' +
'<span class="toc-item__tag ' + tagClass + '">' + escapeHtml(statusLabels[t.status] || '') + '</span>' + '<span class="toc-item__tag ' + tagClass + '">' + escapeHtml(statusLabels[t.status] || '') + '</span>' +
@@ -1370,7 +1425,7 @@
: ''; : '';
return '<div class="toc-group' + (isOpen ? ' is-open' : '') + '" data-group-id="' + ch.id + '">' + return '<div class="toc-group' + (isOpen ? ' is-open' : '') + '" data-group-id="' + ch.id + '">' +
'<button type="button" class="toc-group__head" data-toggle-group="' + ch.id + '">' + '<button type="button" class="toc-group__head" data-toggle-group="' + ch.id + '">' +
'<span class="toc-group__idx' + (allUnlocked ? ' is-done' : '') + '">' + (allUnlocked ? '✓' : ch.id) + '</span>' + '<span class="toc-group__idx' + (allUnlocked ? ' is-done' : '') + '">' + (allUnlocked ? '✓' : escapeHtml(String(ch.indexLabel || ch.id))) + '</span>' +
'<span class="toc-group__meta">' + '<span class="toc-group__meta">' +
'<div class="toc-group__name">' + escapeHtml(ch.title) + '</div>' + '<div class="toc-group__name">' + escapeHtml(ch.title) + '</div>' +
unlockCountHtml + unlockCountHtml +
@@ -1471,10 +1526,8 @@
} catch (error) { } catch (error) {
console.error('[course-detail] toggle lock failed', error); console.error('[course-detail] toggle lock failed', error);
showToast((error && error.message) || '锁定状态切换失败', 'error'); showToast((error && error.message) || '锁定状态切换失败', 'error');
if (btn) { } finally {
btn.classList.remove('is-loading'); resetLockControl(btn);
btn.disabled = false;
}
} }
} }
@@ -1500,7 +1553,10 @@
if (!response.ok || (result && result.success === false)) { if (!response.ok || (result && result.success === false)) {
throw new Error((result && result.message) || ('HTTP ' + response.status)); throw new Error((result && result.message) || ('HTTP ' + response.status));
} }
var data = (result && result.data) || {}; var data = result || {};
if (data.data && (Array.isArray(data.data.categories) || data.data.source)) {
data = data.data;
}
courseSource = data.source || (classId ? 'class' : 'all'); courseSource = data.source || (classId ? 'class' : 'all');
var categories = Array.isArray(data.categories) ? data.categories : []; var categories = Array.isArray(data.categories) ? data.categories : [];
var category = categories[0] || null; var category = categories[0] || null;

View File

@@ -1493,13 +1493,6 @@ top: 2032px; */
</style> </style>
<body> <body>
<script>
// 首页默认清除登录态,避免访客页残留 token
try {
localStorage.removeItem('access_token');
localStorage.removeItem('refresh_token');
} catch (e) {}
</script>
<header class="nav"> <header class="nav">
<div class="nav__inner"> <div class="nav__inner">
<img class="nav__logo" src="../images/logo.svg" alt="001CODE" /> <img class="nav__logo" src="../images/logo.svg" alt="001CODE" />