Compare commits
2 Commits
4499d7fdef
...
d83654837a
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d83654837a | ||
|
|
899adbc9d4 |
@@ -2386,202 +2386,162 @@ export class CppCodeExecutor {
|
||||
return position;
|
||||
}
|
||||
|
||||
// 字符串表达式
|
||||
if (expr.includes('"') || expr.includes("'")) {
|
||||
return expr.replace(/['"]/g, '');
|
||||
// 整个表达式是字符串字面量时才当字符串;含引号的 API 调用走后面的算术解析
|
||||
if ((expr.startsWith('"') && expr.endsWith('"')) ||
|
||||
(expr.startsWith("'") && expr.endsWith("'"))) {
|
||||
return expr.slice(1, -1);
|
||||
}
|
||||
|
||||
// 使用改进的表达式解析器处理复杂表达式
|
||||
return this.parseComplexExpression(expr);
|
||||
}
|
||||
|
||||
// 新增:改进的表达式解析器,支持括号嵌套和运算优先级
|
||||
// 递归下降:一元 +/- ,然后 * / % 优先于 + -
|
||||
parseComplexExpression(expr) {
|
||||
try {
|
||||
// 移除所有空格
|
||||
expr = expr.replace(/\s+/g, '');
|
||||
|
||||
// 处理括号
|
||||
while (expr.includes('(')) {
|
||||
const lastOpenParen = expr.lastIndexOf('(');
|
||||
const closeParen = expr.indexOf(')', lastOpenParen);
|
||||
|
||||
if (closeParen === -1) {
|
||||
debugLog(`⚠️ 括号不匹配: ${expr}`);
|
||||
return 0;
|
||||
}
|
||||
|
||||
const innerExpr = expr.substring(lastOpenParen + 1, closeParen);
|
||||
const innerResult = this.parseComplexExpression(innerExpr);
|
||||
|
||||
expr = expr.substring(0, lastOpenParen) + innerResult + expr.substring(closeParen + 1);
|
||||
}
|
||||
|
||||
// 处理一元运算符(负号)
|
||||
expr = this.handleUnaryOperators(expr);
|
||||
|
||||
// 按正确的运算优先级解析:
|
||||
// 加法和减法是最低优先级,因此在此处最先切分。
|
||||
// 切分后的每一项将交给 parseAdditionSubtraction,在其中处理更高级别的乘除法。
|
||||
return this.parseAdditionSubtraction(expr);
|
||||
|
||||
this._exprTokens = this.lexArithmetic(expr);
|
||||
this._exprPos = 0;
|
||||
if (this._exprTokens.length === 0) return 0;
|
||||
const result = this.parseAddExpr();
|
||||
return typeof result === 'number' && !Number.isNaN(result) ? result : 0;
|
||||
} catch (error) {
|
||||
debugLog(`⚠️ 表达式解析错误: ${expr}, 错误: ${error.message}`);
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
// 处理一元运算符
|
||||
handleUnaryOperators(expr) {
|
||||
// 先处理开头的负号
|
||||
if (expr.startsWith('-')) {
|
||||
const restExpr = expr.substring(1);
|
||||
if (/^\d+/.test(restExpr)) {
|
||||
// 负数
|
||||
const match = restExpr.match(/^\d+/);
|
||||
const number = match[0];
|
||||
const remaining = restExpr.substring(number.length);
|
||||
return (-parseInt(number)) + remaining;
|
||||
} else if (/^[a-zA-Z_]\w*/.test(restExpr)) {
|
||||
// 负变量
|
||||
const match = restExpr.match(/^[a-zA-Z_]\w*/);
|
||||
const varName = match[0];
|
||||
const remaining = restExpr.substring(varName.length);
|
||||
if (this.variables.has(varName)) {
|
||||
return (-this.variables.get(varName)) + remaining;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return expr;
|
||||
}
|
||||
|
||||
// 解析乘法、除法和模运算(高优先级)
|
||||
parseMultiplicationDivision(expr) {
|
||||
const tokens = this.tokenizeExpression(expr, ['*', '/', '%']);
|
||||
let result = this.evaluateToken(tokens[0]);
|
||||
|
||||
for (let i = 1; i < tokens.length; i += 2) {
|
||||
const operator = tokens[i];
|
||||
const operand = this.evaluateToken(tokens[i + 1]);
|
||||
|
||||
if (operator === '*') {
|
||||
result *= operand;
|
||||
} else if (operator === '/') {
|
||||
result = Math.floor(result / operand); // C++整数除法
|
||||
} else if (operator === '%') {
|
||||
result = result % operand; // 模运算
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
// 解析加法和减法(低优先级)
|
||||
parseAdditionSubtraction(expr) {
|
||||
const tokens = this.tokenizeExpressionSmart(expr, ['+', '-']);
|
||||
let result = this.evaluateToken(tokens[0]);
|
||||
|
||||
for (let i = 1; i < tokens.length; i += 2) {
|
||||
const operator = tokens[i];
|
||||
const operand = this.evaluateToken(tokens[i + 1]);
|
||||
|
||||
if (operator === '+') {
|
||||
result += operand;
|
||||
} else if (operator === '-') {
|
||||
result -= operand;
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
// 智能分词器,考虑到负号可能是一元运算符
|
||||
tokenizeExpressionSmart(expr, operators) {
|
||||
lexArithmetic(expr) {
|
||||
const tokens = [];
|
||||
let current = '';
|
||||
let i = 0;
|
||||
const isIdChar = (ch) => /[a-zA-Z0-9_.]/.test(ch);
|
||||
|
||||
while (i < expr.length) {
|
||||
const char = expr[i];
|
||||
|
||||
if (operators.includes(char)) {
|
||||
// 检查是否是一元负号
|
||||
if (char === '-' && (i === 0 || operators.includes(expr[i-1]) || expr[i-1] === '(')) {
|
||||
// 这是一元负号,继续收集
|
||||
current += char;
|
||||
} else {
|
||||
// 这是二元运算符
|
||||
if (current.trim()) {
|
||||
tokens.push(current.trim());
|
||||
current = '';
|
||||
}
|
||||
tokens.push(char);
|
||||
const ch = expr[i];
|
||||
if (/\s/.test(ch)) {
|
||||
i++;
|
||||
continue;
|
||||
}
|
||||
if ('+-*/%()'.includes(ch)) {
|
||||
tokens.push({ type: ch });
|
||||
i++;
|
||||
continue;
|
||||
}
|
||||
if (/\d/.test(ch)) {
|
||||
let num = '';
|
||||
while (i < expr.length && /\d/.test(expr[i])) {
|
||||
num += expr[i++];
|
||||
}
|
||||
} else {
|
||||
current += char;
|
||||
tokens.push({ type: 'num', value: parseInt(num, 10) });
|
||||
continue;
|
||||
}
|
||||
if (/[a-zA-Z_]/.test(ch)) {
|
||||
let id = '';
|
||||
while (i < expr.length && isIdChar(expr[i])) {
|
||||
id += expr[i++];
|
||||
}
|
||||
if (expr[i] === '(') {
|
||||
let depth = 0;
|
||||
let call = id;
|
||||
while (i < expr.length) {
|
||||
const c = expr[i++];
|
||||
call += c;
|
||||
if (c === '(') depth++;
|
||||
else if (c === ')') {
|
||||
depth--;
|
||||
if (depth === 0) break;
|
||||
}
|
||||
}
|
||||
tokens.push({ type: 'call', value: call });
|
||||
} else {
|
||||
tokens.push({ type: 'id', value: id });
|
||||
}
|
||||
continue;
|
||||
}
|
||||
i++;
|
||||
}
|
||||
|
||||
if (current.trim()) {
|
||||
tokens.push(current.trim());
|
||||
}
|
||||
|
||||
return tokens;
|
||||
}
|
||||
|
||||
// 将表达式分解为标记
|
||||
tokenizeExpression(expr, operators) {
|
||||
const tokens = [];
|
||||
let current = '';
|
||||
peekExprToken() {
|
||||
return this._exprTokens[this._exprPos] || { type: 'eof' };
|
||||
}
|
||||
|
||||
for (let i = 0; i < expr.length; i++) {
|
||||
const char = expr[i];
|
||||
consumeExprToken() {
|
||||
return this._exprTokens[this._exprPos++] || { type: 'eof' };
|
||||
}
|
||||
|
||||
if (operators.includes(char)) {
|
||||
if (current.trim()) {
|
||||
tokens.push(current.trim());
|
||||
current = '';
|
||||
}
|
||||
tokens.push(char);
|
||||
parseAddExpr() {
|
||||
let left = this.parseMulExpr();
|
||||
while (this.peekExprToken().type === '+' || this.peekExprToken().type === '-') {
|
||||
const op = this.consumeExprToken().type;
|
||||
const right = this.parseMulExpr();
|
||||
left = op === '+' ? left + right : left - right;
|
||||
}
|
||||
return left;
|
||||
}
|
||||
|
||||
parseMulExpr() {
|
||||
let left = this.parseUnaryExpr();
|
||||
while (['*', '/', '%'].includes(this.peekExprToken().type)) {
|
||||
const op = this.consumeExprToken().type;
|
||||
const right = this.parseUnaryExpr();
|
||||
if (op === '*') {
|
||||
left *= right;
|
||||
} else if (op === '/') {
|
||||
left = right === 0 ? 0 : Math.trunc(left / right);
|
||||
} else {
|
||||
current += char;
|
||||
left %= right;
|
||||
}
|
||||
}
|
||||
|
||||
if (current.trim()) {
|
||||
tokens.push(current.trim());
|
||||
}
|
||||
|
||||
return tokens;
|
||||
return left;
|
||||
}
|
||||
|
||||
// 计算单个标记的值
|
||||
evaluateToken(token) {
|
||||
token = token.trim();
|
||||
if (!token) return 0;
|
||||
|
||||
// 如果包含乘除法或模运算,交给乘除法解析器处理
|
||||
if (token.includes('*') || token.includes('/') || token.includes('%')) {
|
||||
return this.parseMultiplicationDivision(token);
|
||||
parseUnaryExpr() {
|
||||
const type = this.peekExprToken().type;
|
||||
if (type === '-') {
|
||||
this.consumeExprToken();
|
||||
return -this.parseUnaryExpr();
|
||||
}
|
||||
if (type === '+') {
|
||||
this.consumeExprToken();
|
||||
return this.parseUnaryExpr();
|
||||
}
|
||||
return this.parsePrimaryExpr();
|
||||
}
|
||||
|
||||
// 布尔常量
|
||||
parsePrimaryExpr() {
|
||||
const token = this.peekExprToken();
|
||||
if (token.type === 'num') {
|
||||
this.consumeExprToken();
|
||||
return token.value;
|
||||
}
|
||||
if (token.type === 'id') {
|
||||
this.consumeExprToken();
|
||||
return this.evaluateAtom(token.value);
|
||||
}
|
||||
if (token.type === 'call') {
|
||||
this.consumeExprToken();
|
||||
return this.evaluateAtom(token.value);
|
||||
}
|
||||
if (token.type === '(') {
|
||||
this.consumeExprToken();
|
||||
const value = this.parseAddExpr();
|
||||
if (this.peekExprToken().type === ')') {
|
||||
this.consumeExprToken();
|
||||
}
|
||||
return value;
|
||||
}
|
||||
debugLog(`⚠️ 无法解析标记: ${token.type}`);
|
||||
return 0;
|
||||
}
|
||||
|
||||
evaluateAtom(token) {
|
||||
if (token === 'true') return 1;
|
||||
if (token === 'false') return 0;
|
||||
|
||||
// 数字
|
||||
if (/^-?\d+$/.test(token)) {
|
||||
return parseInt(token);
|
||||
}
|
||||
|
||||
// 变量查找
|
||||
if (this.variables.has(token)) {
|
||||
return this.variables.get(token);
|
||||
}
|
||||
|
||||
// 对象属性访问 (如 pos.x, pos.y, pos.z)
|
||||
if (token.includes('.')) {
|
||||
const [objName, propName] = token.split('.');
|
||||
if (this.variables.has(objName)) {
|
||||
@@ -2592,7 +2552,6 @@ export class CppCodeExecutor {
|
||||
}
|
||||
}
|
||||
|
||||
// 游戏API调用
|
||||
if (token.includes('player_data(')) {
|
||||
const match = token.match(/player_data\s*\(\s*"(.+?)"\s*\)/);
|
||||
if (match) return window.cppGameAPI.player_data(match[1]);
|
||||
|
||||
@@ -1302,6 +1302,12 @@
|
||||
<span class="nav__pillAIText">灵丌AI</span>
|
||||
</span>
|
||||
</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 class="navbar-right">
|
||||
<a class="navbar-link" id="open-personal-info" href="javascript:void(0);">个人信息</a>
|
||||
@@ -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
|
||||
? '<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 + '">' +
|
||||
'<div class="classes-modal__main">' +
|
||||
'<div class="classes-modal__name">' + name + '</div>' +
|
||||
'<div class="classes-modal__meta">年级:' + grade + '</div>' +
|
||||
'</div>' +
|
||||
'<div class="classes-modal__aside">' +
|
||||
'<span class="classes-modal__count">' + count + ' 人</span>' +
|
||||
countHtml +
|
||||
goBtnHtml +
|
||||
'</div>' +
|
||||
'</div>';
|
||||
|
||||
@@ -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",
|
||||
|
||||
|
||||
@@ -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 '<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__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">' +
|
||||
'<span>' + meta + '</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 + '">' +
|
||||
'<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">' +
|
||||
'<div class="toc-group__name">' + escapeHtml(ch.title) + '</div>' +
|
||||
unlockCountHtml +
|
||||
@@ -1471,10 +1526,8 @@
|
||||
} catch (error) {
|
||||
console.error('[course-detail] toggle lock failed', error);
|
||||
showToast((error && error.message) || '锁定状态切换失败', 'error');
|
||||
if (btn) {
|
||||
btn.classList.remove('is-loading');
|
||||
btn.disabled = false;
|
||||
}
|
||||
} finally {
|
||||
resetLockControl(btn);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1500,7 +1553,10 @@
|
||||
if (!response.ok || (result && result.success === false)) {
|
||||
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');
|
||||
var categories = Array.isArray(data.categories) ? data.categories : [];
|
||||
var category = categories[0] || null;
|
||||
|
||||
@@ -1493,13 +1493,6 @@ top: 2032px; */
|
||||
</style>
|
||||
|
||||
<body>
|
||||
<script>
|
||||
// 首页默认清除登录态,避免访客页残留 token
|
||||
try {
|
||||
localStorage.removeItem('access_token');
|
||||
localStorage.removeItem('refresh_token');
|
||||
} catch (e) {}
|
||||
</script>
|
||||
<header class="nav">
|
||||
<div class="nav__inner">
|
||||
<img class="nav__logo" src="../images/logo.svg" alt="001CODE" />
|
||||
|
||||
Reference in New Issue
Block a user