study-analysis.xiaoban/output/study_report_12345_L1_U1_20260402161243.html

402 lines
22 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>学情分析报告 - 用户12345 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">
用户ID12345 &nbsp;|&nbsp; Level 1 &nbsp;|&nbsp; Unit 1 &nbsp;|&nbsp; 生成时间2026-04-02 16:12:43
</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 = {"role_id": 12345, "level": 1, "unit": 1, "unit_name": "Unit 1: Hello World", "lessons": [{"lesson_id": 1, "lesson_name": "Lesson 1: Greetings", "entry_time": "2024-01-15T09:00:00Z", "completion_time": "2024-01-15T09:25:30Z", "knowledge_points": {"words": [{"word": "hello", "meaning": "你好", "pos": "int."}, {"word": "hi", "meaning": "嗨", "pos": "int."}, {"word": "goodbye", "meaning": "再见", "pos": "int."}, {"word": "name", "meaning": "名字", "pos": "n."}], "sentence_patterns": [{"pattern": "Hello, my name is [name].", "meaning": "你好,我的名字是[名字]。", "example": "Hello, my name is Tom."}, {"pattern": "Nice to meet you.", "meaning": "很高兴认识你。", "example": "Nice to meet you too."}]}, "interactive_components": [{"component_id": "ic_001", "title": "单词跟读 - hello", "detail": "请跟读单词 hello注意发音准确", "user_result": "perfect"}, {"component_id": "ic_002", "title": "单词跟读 - hi", "detail": "请跟读单词 hi注意语调自然", "user_result": "good"}, {"component_id": "ic_003", "title": "单词听选 - goodbye", "detail": "听录音,选择正确的单词 goodbye", "user_result": "perfect"}, {"component_id": "ic_004", "title": "单词拼写 - name", "detail": "根据发音,拼写出单词 name", "user_result": "good"}, {"component_id": "ic_005", "title": "句型跟读 - Hello, my name is...", "detail": "请跟读句型Hello, my name is [name]", "user_result": "perfect"}, {"component_id": "ic_006", "title": "句型替换练习", "detail": "替换名字完成句子Hello, my name is ______", "user_result": "oops"}, {"component_id": "ic_007", "title": "情景对话 - 打招呼", "detail": "在情景中与角色进行打招呼对话", "user_result": "good"}, {"component_id": "ic_008", "title": "听力理解 - 问候语", "detail": "听录音,选择正确的问候回应", "user_result": "perfect"}, {"component_id": "ic_009", "title": "语音识别 - hi", "detail": "请说出单词 hi", "user_result": "good"}, {"component_id": "ic_010", "title": "图文匹配 - 单词", "detail": "将单词与对应的图片进行匹配", "user_result": "perfect"}, {"component_id": "ic_011", "title": "句型听选 - Nice to meet you", "detail": "听录音,选择正确的回应", "user_result": "good"}, {"component_id": "ic_012", "title": "角色扮演 - 自我介绍", "detail": "扮演角色进行自我介绍对话", "user_result": "perfect"}, {"component_id": "ic_013", "title": "单词排序 - 句子组成", "detail": "将单词按正确顺序排列成句子", "user_result": "oops"}, {"component_id": "ic_014", "title": "发音练习 - 语调训练", "detail": "练习疑问句和陈述句的语调区别", "user_result": "good"}, {"component_id": "ic_015", "title": "单词填空 - 补全句子", "detail": "在句子中填入正确的单词", "user_result": "perfect"}, {"component_id": "ic_016", "title": "情景判断 - 选择回应", "detail": "根据情景选择最合适的回应", "user_result": "good"}, {"component_id": "ic_017", "title": "综合对话 - 完整对话", "detail": "完成完整的打招呼和自我介绍对话", "user_result": "perfect"}, {"component_id": "ic_018", "title": "复习测验 - 本课回顾", "detail": "完成本课知识点的综合复习测验", "user_result": "good"}], "consolidation_exercises": [{"exercise_id": "ce_001", "type": "单词听选", "detail": "听录音从A、B、C三个选项中选择听到的单词", "is_correct": true}, {"exercise_id": "ce_002", "type": "单词拼写", "detail": "根据中文意思,写出对应的英文单词:你好", "is_correct": true}, {"exercise_id": "ce_003", "type": "图文匹配", "detail": "将单词 goodbye 与对应的图片匹配", "is_correct": false}, {"exercise_id": "ce_004", "type": "句型填空", "detail": "用正确的单词填空Hello, my ______ is Tom.", "is_correct": true}, {"exercise_id": "ce_005", "type": "情景选择", "detail": "初次见面时应该说A. Hello B. Goodbye C. Hi", "is_correct": true}, {"exercise_id": "ce_006", "type": "句子排序", "detail": "将下列单词排列成正确的句子name / my / is / Hello", "is_correct": true}, {"exercise_id": "ce_007", "type": "听力理解", "detail": "听对话,回答问题:他们正在做什么?", "is_correct": false}, {"exercise_id": "ce_008", "type": "发音选择", "detail": "听录音,选择包含 /eɪ/ 音的单词", "is_correct": true}, {"exercise_id": "ce_009", "type": "翻译练习", "detail": "将下列句子翻译成英文:很高兴认识你。", "is_correct": true}, {"exercise_id": "ce_010", "type": "对话补全", "detail": "补全对话A: Hello! B: ______!", "is_correct": true}, {"exercise_id": "ce_011", "type": "单词分类", "detail": "将单词按问候语和非问候语分类", "is_correct": false}, {"exercise_id": "ce_012", "type": "句型转换", "detail": "将 Hello 转换为同义词", "is_correct": true}, {"exercise_id": "ce_013", "type": "连线匹配", "detail": "将单词与对应的中文意思连线", "is_correct": true}, {"exercise_id": "ce_014", "type": "判断正误", "detail": "判断句子是否正确Goodbye 用于告别时说。", "is_correct": true}, {"exercise_id": "ce_015", "type": "选词填空", "detail": "选择合适的单词填空______ to meet you. (Nice/Fine/Good)", "is_correct": true}, {"exercise_id": "ce_016", "type": "语音辨识", "detail": "听录音,判断是哪个单词", "is_correct": false}, {"exercise_id": "ce_017", "type": "完形填空", "detail": "阅读短文,选择合适的问候语填入空格", "is_correct": true}, {"exercise_id": "ce_018", "type": "综合测验", "detail": "完成本课知识的综合测验题", "is_correct": true}]}], "summary": {"total_lessons": 5, "completed_lessons": 1, "total_interactive_components": 18, "total_consolidation_exercises": 18, "mastery_rate": 0.85, "study_duration_minutes": 25}};
// 兼容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>