study-analysis.xiaoban/output/study_report_10293_L1_U1_20260402160508.html

402 lines
17 KiB
HTML
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.

<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>学情分析报告 - 用户10293 Level1 Unit1</title>
<script src="https://cdn.jsdelivr.net/npm/echarts@5.4.3/dist/echarts.min.js"></script>
<style>
* { margin: 0; padding: 0; box-sizing: border-box; }
body {
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, "PingFang SC", "Microsoft YaHei", sans-serif;
background-color: #f0f2f5;
padding: 24px;
color: #333;
}
.container { max-width: 1200px; margin: 0 auto; }
.header {
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
color: white;
padding: 32px;
border-radius: 16px;
margin-bottom: 24px;
}
.header h1 { font-size: 28px; margin-bottom: 8px; }
.header .meta { font-size: 14px; opacity: 0.9; }
.card {
background: white;
border-radius: 12px;
padding: 24px;
margin-bottom: 20px;
box-shadow: 0 2px 12px rgba(0,0,0,0.06);
}
.card h2 {
font-size: 18px;
color: #333;
margin-bottom: 20px;
border-left: 4px solid #667eea;
padding-left: 12px;
}
.stats-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(160px, 1fr));
gap: 16px;
margin-bottom: 24px;
}
.stat-item {
background: linear-gradient(135deg, #f5f7fa 0%, #e8ecf1 100%);
padding: 20px;
border-radius: 12px;
text-align: center;
}
.stat-item .label { font-size: 13px; color: #888; margin-bottom: 8px; }
.stat-item .value { font-size: 28px; font-weight: 700; color: #333; }
.stat-item .value.green { color: #52c41a; }
.stat-item .value.orange { color: #fa8c16; }
.stat-item .value.blue { color: #1890ff; }
.stat-item .value.purple { color: #722ed1; }
.chart-container { height: 400px; width: 100%; }
.chart-row {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 20px;
}
@media (max-width: 768px) {
.chart-row { grid-template-columns: 1fr; }
}
.lesson-tab {
display: flex;
gap: 8px;
margin-bottom: 20px;
flex-wrap: wrap;
}
.lesson-tab .tab-btn {
padding: 8px 20px;
border: 2px solid #667eea;
border-radius: 20px;
background: white;
color: #667eea;
cursor: pointer;
font-size: 14px;
font-weight: 500;
transition: all 0.2s;
}
.lesson-tab .tab-btn.active, .lesson-tab .tab-btn:hover {
background: #667eea;
color: white;
}
.lesson-detail { display: none; }
.lesson-detail.active { display: block; }
.knowledge-section { margin-bottom: 20px; }
.knowledge-section h3 {
font-size: 15px;
color: #555;
margin-bottom: 12px;
}
.word-list, .pattern-list {
display: flex;
flex-wrap: wrap;
gap: 10px;
}
.word-tag {
background: #e6f7ff;
border: 1px solid #91d5ff;
border-radius: 8px;
padding: 8px 16px;
font-size: 14px;
}
.word-tag .en { font-weight: 600; color: #1890ff; }
.word-tag .cn { color: #666; margin-left: 6px; }
.word-tag .pos { color: #999; font-size: 12px; margin-left: 4px; }
.pattern-tag {
background: #fff7e6;
border: 1px solid #ffd591;
border-radius: 8px;
padding: 10px 16px;
font-size: 14px;
flex: 1 1 300px;
}
.pattern-tag .en { font-weight: 600; color: #fa8c16; }
.pattern-tag .cn { color: #666; display: block; margin-top: 4px; font-size: 13px; }
table {
width: 100%;
border-collapse: collapse;
font-size: 14px;
}
table th {
background: #f5f7fa;
padding: 12px;
text-align: left;
font-weight: 600;
color: #555;
border-bottom: 2px solid #e8e8e8;
}
table td {
padding: 10px 12px;
border-bottom: 1px solid #f0f0f0;
}
table tr:hover td { background: #fafafa; }
.badge {
display: inline-block;
padding: 2px 10px;
border-radius: 10px;
font-size: 12px;
font-weight: 600;
}
.badge.perfect { background: #f6ffed; color: #52c41a; border: 1px solid #b7eb8f; }
.badge.good { background: #e6f7ff; color: #1890ff; border: 1px solid #91d5ff; }
.badge.oops { background: #fff2e8; color: #fa541c; border: 1px solid #ffbb96; }
.badge.correct { background: #f6ffed; color: #52c41a; border: 1px solid #b7eb8f; }
.badge.wrong { background: #fff1f0; color: #f5222d; border: 1px solid #ffa39e; }
.time-info {
display: flex;
gap: 20px;
margin-bottom: 16px;
font-size: 14px;
color: #666;
}
.time-info span { display: flex; align-items: center; gap: 6px; }
.raw-data {
background: #f8f9fa;
padding: 16px;
border-radius: 8px;
font-family: "SF Mono", "Fira Code", monospace;
font-size: 12px;
max-height: 400px;
overflow-y: auto;
white-space: pre-wrap;
word-break: break-all;
}
</style>
</head>
<body>
<div class="container">
<div class="header">
<h1>📚 学情分析报告</h1>
<div class="meta">
用户ID10293 &nbsp;|&nbsp; Level 1 &nbsp;|&nbsp; Unit 1 &nbsp;|&nbsp; 生成时间2026-04-02 16:05:08
</div>
</div>
<div class="card">
<h2>📊 总览</h2>
<div class="stats-grid" id="stats-grid"></div>
</div>
<div class="chart-row">
<div class="card">
<h2>📈 互动组件表现</h2>
<div id="interactive-chart" class="chart-container"></div>
</div>
<div class="card">
<h2>📝 巩固练习正确率</h2>
<div id="exercise-chart" class="chart-container"></div>
</div>
</div>
<div class="card">
<h2>📖 课程详情</h2>
<div class="lesson-tab" id="lesson-tabs"></div>
<div id="lesson-details"></div>
</div>
<div class="card">
<h2>🔍 原始数据</h2>
<div class="raw-data" id="raw-data"></div>
</div>
</div>
<script>
const data = {"code": 200, "data": {"data": "hello world"}, "msg": "", "traceID": "bfc13d6b1b5669affffa521d675d7141"};
// 兼容data可能直接就是lessons数组也可能有外层包装
const lessons = data.lessons || data.data?.lessons || (Array.isArray(data) ? data : []);
const summary = data.summary || data.data?.summary || null;
// ==================== 总览统计 ====================
const statsGrid = document.getElementById('stats-grid');
const totalLessons = lessons.length;
let totalInteractive = 0, totalPerfect = 0, totalGood = 0, totalOops = 0;
let totalExercises = 0, totalCorrect = 0;
lessons.forEach(lesson => {
const ics = lesson.interactive_components || lesson.interactiveComponents || [];
const ces = lesson.consolidation_exercises || lesson.consolidationExercises || [];
totalInteractive += ics.length;
ics.forEach(ic => {
const r = (ic.user_result || ic.userResult || '').toLowerCase();
if (r === 'perfect') totalPerfect++;
else if (r === 'good') totalGood++;
else if (r === 'oops') totalOops++;
});
totalExercises += ces.length;
ces.forEach(ce => {
if (ce.is_correct === true || ce.isCorrect === true) totalCorrect++;
});
});
const exerciseAccuracy = totalExercises > 0 ? Math.round(totalCorrect / totalExercises * 100) : 0;
const interactiveScore = totalInteractive > 0 ? Math.round((totalPerfect * 3 + totalGood * 2 + totalOops * 1) / (totalInteractive * 3) * 100) : 0;
const statsItems = [
{ label: '课程数', value: totalLessons, cls: 'blue' },
{ label: '互动组件总数', value: totalInteractive, cls: 'purple' },
{ label: 'Perfect 次数', value: totalPerfect, cls: 'green' },
{ label: 'Good 次数', value: totalGood, cls: 'blue' },
{ label: 'Oops 次数', value: totalOops, cls: 'orange' },
{ label: '巩固题总数', value: totalExercises, cls: 'purple' },
{ label: '巩固正确率', value: exerciseAccuracy + '%', cls: 'green' },
{ label: '互动综合得分', value: interactiveScore + '%', cls: 'blue' }
];
statsItems.forEach(item => {
const div = document.createElement('div');
div.className = 'stat-item';
div.innerHTML = `<div class="label">${item.label}</div><div class="value ${item.cls}">${item.value}</div>`;
statsGrid.appendChild(div);
});
// ==================== 互动组件表现饼图 ====================
const interactiveChart = echarts.init(document.getElementById('interactive-chart'));
interactiveChart.setOption({
tooltip: { trigger: 'item', formatter: '{b}: {c} ({d}%)' },
legend: { bottom: 10 },
color: ['#52c41a', '#1890ff', '#fa541c'],
series: [{
type: 'pie',
radius: ['40%', '65%'],
avoidLabelOverlap: true,
itemStyle: { borderRadius: 8, borderColor: '#fff', borderWidth: 2 },
label: { show: true, fontSize: 14 },
data: [
{ value: totalPerfect, name: 'Perfect' },
{ value: totalGood, name: 'Good' },
{ value: totalOops, name: 'Oops' }
]
}]
});
// ==================== 巩固练习柱状图(按课程) ====================
const exerciseChart = echarts.init(document.getElementById('exercise-chart'));
const lessonLabels = lessons.map((l, i) => l.lesson_name || l.lessonName || `Lesson ${i + 1}`);
const correctCounts = [];
const wrongCounts = [];
lessons.forEach(lesson => {
const ces = lesson.consolidation_exercises || lesson.consolidationExercises || [];
let correct = 0, wrong = 0;
ces.forEach(ce => {
if (ce.is_correct === true || ce.isCorrect === true) correct++;
else wrong++;
});
correctCounts.push(correct);
wrongCounts.push(wrong);
});
exerciseChart.setOption({
tooltip: { trigger: 'axis', axisPointer: { type: 'shadow' } },
legend: { bottom: 10 },
grid: { left: '3%', right: '4%', bottom: '15%', top: '5%', containLabel: true },
xAxis: { type: 'category', data: lessonLabels, axisLabel: { rotate: 20, fontSize: 12 } },
yAxis: { type: 'value' },
color: ['#52c41a', '#f5222d'],
series: [
{ name: '正确', type: 'bar', stack: 'total', data: correctCounts, itemStyle: { borderRadius: [4, 4, 0, 0] } },
{ name: '错误', type: 'bar', stack: 'total', data: wrongCounts, itemStyle: { borderRadius: [4, 4, 0, 0] } }
]
});
// ==================== 课程详情 tabs ====================
const tabContainer = document.getElementById('lesson-tabs');
const detailContainer = document.getElementById('lesson-details');
lessons.forEach((lesson, idx) => {
// tab 按钮
const btn = document.createElement('button');
btn.className = 'tab-btn' + (idx === 0 ? ' active' : '');
btn.textContent = lesson.lesson_name || lesson.lessonName || `Lesson ${idx + 1}`;
btn.onclick = () => switchTab(idx);
tabContainer.appendChild(btn);
// 详情区域
const detail = document.createElement('div');
detail.className = 'lesson-detail' + (idx === 0 ? ' active' : '');
detail.id = `lesson-${idx}`;
const entryTime = lesson.entry_time || lesson.entryTime || '-';
const completionTime = lesson.completion_time || lesson.completionTime || '-';
const kp = lesson.knowledge_points || lesson.knowledgePoints || {};
const words = kp.words || [];
const patterns = kp.sentence_patterns || kp.sentencePatterns || [];
const ics = lesson.interactive_components || lesson.interactiveComponents || [];
const ces = lesson.consolidation_exercises || lesson.consolidationExercises || [];
let html = `
<div class="time-info">
<span>🕐 进入时间:${formatTime(entryTime)}</span>
<span>✅ 完成时间:${formatTime(completionTime)}</span>
</div>
<div class="knowledge-section">
<h3>📗 单词 (${words.length}个)</h3>
<div class="word-list">
${words.map(w => `<div class="word-tag"><span class="en">${w.word}</span><span class="cn">${w.meaning}</span><span class="pos">${w.pos || ''}</span></div>`).join('')}
</div>
</div>
<div class="knowledge-section">
<h3>📘 句型 (${patterns.length}个)</h3>
<div class="pattern-list">
${patterns.map(p => `<div class="pattern-tag"><span class="en">${p.pattern}</span><span class="cn">${p.meaning}</span></div>`).join('')}
</div>
</div>
<div class="knowledge-section">
<h3>🎯 互动组件 (${ics.length}个)</h3>
<table>
<thead><tr><th>#</th><th>标题</th><th>详情</th><th>结果</th></tr></thead>
<tbody>
${ics.map((ic, i) => {
const result = (ic.user_result || ic.userResult || '').toLowerCase();
const cls = result === 'perfect' ? 'perfect' : result === 'good' ? 'good' : 'oops';
return `<tr><td>${i + 1}</td><td>${ic.title}</td><td>${ic.detail}</td><td><span class="badge ${cls}">${result}</span></td></tr>`;
}).join('')}
</tbody>
</table>
</div>
<div class="knowledge-section">
<h3>📝 学习巩固 (${ces.length}题)</h3>
<table>
<thead><tr><th>#</th><th>题型</th><th>详情</th><th>结果</th></tr></thead>
<tbody>
${ces.map((ce, i) => {
const correct = ce.is_correct === true || ce.isCorrect === true;
return `<tr><td>${i + 1}</td><td>${ce.type}</td><td>${ce.detail}</td><td><span class="badge ${correct ? 'correct' : 'wrong'}">${correct ? '✓ 正确' : '✗ 错误'}</span></td></tr>`;
}).join('')}
</tbody>
</table>
</div>
`;
detail.innerHTML = html;
detailContainer.appendChild(detail);
});
function switchTab(idx) {
document.querySelectorAll('.tab-btn').forEach((b, i) => b.classList.toggle('active', i === idx));
document.querySelectorAll('.lesson-detail').forEach((d, i) => d.classList.toggle('active', i === idx));
}
function formatTime(t) {
if (!t || t === '-') return '-';
try {
const d = new Date(t);
return d.toLocaleString('zh-CN', { year: 'numeric', month: '2-digit', day: '2-digit', hour: '2-digit', minute: '2-digit', second: '2-digit' });
} catch { return t; }
}
// 原始数据
document.getElementById('raw-data').textContent = JSON.stringify(data, null, 2);
// 响应式
window.addEventListener('resize', () => {
interactiveChart.resize();
exerciseChart.resize();
});
</script>
</body>
</html>