Files
RongGiuangKT/html/student_analytics.html
2025-10-09 19:25:48 +08:00

1465 lines
41 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>学生数据分析 - 荣光课堂</title>
<link rel="stylesheet" href="../css/indexHome.css" />
<link rel="stylesheet" href="../css/userDisplay.css" />
<link
rel="stylesheet"
href="https://cdn.jsdelivr.net/npm/font-awesome@4.7.0/css/font-awesome.min.css"
/>
<style>
@import url("https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&display=swap");
:root {
--primary-color: #6366f1;
--primary-light: #818cf8;
--primary-dark: #4f46e5;
--secondary-color: #06b6d4;
--success-color: #10b981;
--warning-color: #f59e0b;
--error-color: #ef4444;
--purple-gradient: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
--blue-gradient: linear-gradient(135deg, #6366f1 0%, #06b6d4 100%);
--green-gradient: linear-gradient(135deg, #10b981 0%, #34d399 100%);
--orange-gradient: linear-gradient(135deg, #f59e0b 0%, #fbbf24 100%);
--red-gradient: linear-gradient(135deg, #ef4444 0%, #f87171 100%);
--purple-light-gradient: linear-gradient(
135deg,
#a855f7 0%,
#c084fc 100%
);
--background-color: #f8fafc;
--card-background: #ffffff;
--text-primary: #1e293b;
--text-secondary: #64748b;
--text-muted: #94a3b8;
--border-color: #e2e8f0;
--shadow-sm: 0 1px 2px 0 rgb(0 0 0 / 0.05);
--shadow-md: 0 4px 6px -1px rgb(0 0 0 / 0.1),
0 2px 4px -2px rgb(0 0 0 / 0.1);
--shadow-lg: 0 10px 15px -3px rgb(0 0 0 / 0.1),
0 4px 6px -4px rgb(0 0 0 / 0.1);
--shadow-xl: 0 20px 25px -5px rgb(0 0 0 / 0.1),
0 8px 10px -6px rgb(0 0 0 / 0.1);
--radius-sm: 0.375rem;
--radius-md: 0.5rem;
--radius-lg: 0.75rem;
--radius-xl: 1rem;
}
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
padding-top: 80px;
background: var(--background-color);
font-family: "Inter", "Noto Sans SC", sans-serif;
line-height: 1.6;
color: var(--text-primary);
overflow-x: hidden;
}
.analytics-container {
max-width: 1400px;
margin: 0 auto;
padding: 2rem;
animation: fadeInUp 0.6s ease-out;
}
@keyframes fadeInUp {
from {
opacity: 0;
transform: translateY(30px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
@keyframes slideInLeft {
from {
opacity: 0;
transform: translateX(-30px);
}
to {
opacity: 1;
transform: translateX(0);
}
}
@keyframes slideInRight {
from {
opacity: 0;
transform: translateX(30px);
}
to {
opacity: 1;
transform: translateX(0);
}
}
@keyframes scaleIn {
from {
opacity: 0;
transform: scale(0.9);
}
to {
opacity: 1;
transform: scale(1);
}
}
.page-header {
background: var(--purple-gradient);
color: white;
padding: 3rem 2rem;
border-radius: var(--radius-xl);
margin-bottom: 2.5rem;
text-align: center;
position: relative;
overflow: hidden;
box-shadow: var(--shadow-xl);
}
.page-header::before {
content: "";
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: url('data:image/svg+xml,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1000 100" fill="white" opacity="0.1"><polygon points="0,0 1000,100 1000,0"/></svg>');
background-size: cover;
}
.page-header-content {
position: relative;
z-index: 1;
}
.page-header h1 {
margin: 0 0 1rem 0;
font-size: 3rem;
font-weight: 700;
letter-spacing: -0.025em;
}
.page-header h1 i {
margin-right: 1rem;
background: linear-gradient(45deg, #fff, #e0e7ff);
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
background-clip: text;
}
.page-header p {
margin: 0;
font-size: 1.25rem;
opacity: 0.95;
font-weight: 400;
}
.filter-section {
background: var(--card-background);
padding: 2rem;
border-radius: var(--radius-lg);
box-shadow: var(--shadow-sm);
margin-bottom: 2.5rem;
border: 1px solid var(--border-color);
animation: slideInLeft 0.6s ease-out 0.1s both;
}
.filter-row {
display: flex;
gap: 2rem;
align-items: end;
flex-wrap: wrap;
}
.filter-group {
display: flex;
flex-direction: column;
min-width: 200px;
}
.filter-label {
font-weight: 600;
margin-bottom: 0.5rem;
color: var(--text-primary);
font-size: 0.875rem;
text-transform: uppercase;
letter-spacing: 0.05em;
}
.filter-select {
padding: 0.75rem 1rem;
border: 2px solid var(--border-color);
border-radius: var(--radius-md);
background: white;
font-size: 1rem;
transition: all 0.2s ease;
font-family: inherit;
}
.filter-select:focus {
outline: none;
border-color: var(--primary-color);
box-shadow: 0 0 0 3px rgb(99 102 241 / 0.1);
}
.refresh-btn {
background: var(--blue-gradient);
color: white;
border: none;
padding: 0.75rem 2rem;
border-radius: var(--radius-md);
cursor: pointer;
font-size: 1rem;
font-weight: 600;
transition: all 0.3s ease;
box-shadow: var(--shadow-md);
display: flex;
align-items: center;
gap: 0.5rem;
}
.refresh-btn:hover {
transform: translateY(-2px);
box-shadow: var(--shadow-lg);
}
.refresh-btn:active {
transform: translateY(0);
}
.stats-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));
gap: 2rem;
margin-bottom: 2.5rem;
}
.stat-card {
background: var(--card-background);
padding: 2rem;
border-radius: var(--radius-xl);
box-shadow: var(--shadow-sm);
border: 1px solid var(--border-color);
text-align: center;
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
position: relative;
overflow: hidden;
animation: scaleIn 0.6s ease-out var(--delay, 0s) both;
}
.stat-card:nth-child(1) {
--delay: 0.1s;
}
.stat-card:nth-child(2) {
--delay: 0.2s;
}
.stat-card:nth-child(3) {
--delay: 0.3s;
}
.stat-card:nth-child(4) {
--delay: 0.4s;
}
.stat-card::before {
content: "";
position: absolute;
top: 0;
left: 0;
right: 0;
height: 4px;
background: var(--gradient);
transition: height 0.3s ease;
}
.stat-card:hover {
transform: translateY(-8px);
box-shadow: var(--shadow-xl);
}
.stat-card:hover::before {
height: 100%;
opacity: 0.05;
}
.stat-card:nth-child(1) {
--gradient: var(--green-gradient);
}
.stat-card:nth-child(2) {
--gradient: var(--blue-gradient);
}
.stat-card:nth-child(3) {
--gradient: var(--orange-gradient);
}
.stat-card:nth-child(4) {
--gradient: var(--purple-light-gradient);
}
.stat-icon {
width: 4rem;
height: 4rem;
margin: 0 auto 1.5rem;
background: var(--gradient);
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
font-size: 1.5rem;
color: white;
position: relative;
z-index: 1;
}
.stat-number {
font-size: 3rem;
font-weight: 700;
margin-bottom: 0.5rem;
background: var(--gradient);
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
background-clip: text;
position: relative;
z-index: 1;
}
.stat-label {
color: var(--text-secondary);
font-size: 1rem;
font-weight: 500;
text-transform: uppercase;
letter-spacing: 0.05em;
position: relative;
z-index: 1;
}
.chart-container {
background: var(--card-background);
padding: 2rem;
border-radius: var(--radius-xl);
box-shadow: var(--shadow-sm);
border: 1px solid var(--border-color);
margin-bottom: 2.5rem;
animation: slideInRight 0.6s ease-out 0.2s both;
}
.chart-title {
font-size: 1.5rem;
font-weight: 700;
margin-bottom: 2rem;
color: var(--text-primary);
display: flex;
align-items: center;
gap: 0.75rem;
padding-bottom: 1rem;
border-bottom: 2px solid var(--border-color);
position: relative;
}
.chart-title::after {
content: "";
position: absolute;
bottom: -2px;
left: 0;
width: 4rem;
height: 2px;
background: var(--primary-color);
}
.chart-title i {
background: var(--blue-gradient);
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
background-clip: text;
}
.ai-recommendations {
margin-bottom: 2rem;
}
.recommendation-item {
display: flex;
align-items: center;
padding: 2rem;
margin-bottom: 1.5rem;
background: var(--card-background);
border-radius: var(--radius-lg);
border: 1px solid var(--border-color);
border-left: 4px solid var(--accent-color);
box-shadow: var(--shadow-sm);
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
position: relative;
overflow: hidden;
}
.recommendation-item::before {
content: "";
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: linear-gradient(135deg, var(--accent-color), transparent);
opacity: 0;
transition: opacity 0.3s ease;
}
.recommendation-item:hover {
transform: translateX(8px);
box-shadow: var(--shadow-lg);
}
.recommendation-item:hover::before {
opacity: 0.03;
}
.recommendation-item:nth-child(1) {
--accent-color: var(--success-color);
animation: slideInLeft 0.6s ease-out 0.3s both;
}
.recommendation-item:nth-child(2) {
--accent-color: var(--warning-color);
animation: slideInLeft 0.6s ease-out 0.4s both;
}
.recommendation-item:nth-child(3) {
--accent-color: var(--error-color);
animation: slideInLeft 0.6s ease-out 0.5s both;
}
.recommendation-icon {
width: 4rem;
height: 4rem;
background: linear-gradient(
135deg,
var(--accent-color),
var(--accent-color) dd
);
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
margin-right: 2rem;
color: white;
font-size: 1.5rem;
position: relative;
z-index: 1;
flex-shrink: 0;
}
.recommendation-content {
flex: 1;
position: relative;
z-index: 1;
}
.recommendation-title {
font-weight: 700;
color: var(--text-primary);
margin-bottom: 0.75rem;
font-size: 1.125rem;
}
.recommendation-reason {
color: var(--text-secondary);
font-size: 0.95rem;
margin-bottom: 1rem;
line-height: 1.6;
}
.recommendation-tags {
display: flex;
flex-wrap: wrap;
gap: 0.5rem;
}
.recommendation-tag {
background: linear-gradient(
135deg,
var(--accent-color) 15,
var(--accent-color) 25
);
color: var(--accent-color);
padding: 0.25rem 0.75rem;
border-radius: 9999px;
font-size: 0.8rem;
font-weight: 600;
border: 1px solid var(--accent-color) 30;
}
.recommendation-action {
margin-left: 2rem;
position: relative;
z-index: 1;
flex-shrink: 0;
}
.watch-btn {
background: linear-gradient(
135deg,
var(--accent-color),
var(--accent-color) dd
);
color: white;
border: none;
padding: 0.75rem 1.5rem;
border-radius: 9999px;
cursor: pointer;
font-weight: 600;
transition: all 0.3s ease;
display: flex;
align-items: center;
gap: 0.5rem;
box-shadow: var(--shadow-md);
}
.watch-btn:hover {
transform: translateY(-2px);
box-shadow: var(--shadow-lg);
}
.game-stats {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(320px, 1fr));
gap: 2rem;
}
.game-card {
background: linear-gradient(135deg, #f8fafc 0%, #ffffff 100%);
padding: 2rem;
border-radius: var(--radius-lg);
border: 1px solid var(--border-color);
transition: all 0.3s ease;
position: relative;
overflow: hidden;
animation: scaleIn 0.6s ease-out var(--game-delay, 0s) both;
}
.game-card:nth-child(1) {
--game-delay: 0.1s;
}
.game-card:nth-child(2) {
--game-delay: 0.2s;
}
.game-card:nth-child(3) {
--game-delay: 0.3s;
}
.game-card:nth-child(4) {
--game-delay: 0.4s;
}
.game-card::before {
content: "";
position: absolute;
top: 0;
left: 0;
right: 0;
height: 4px;
background: var(--blue-gradient);
}
.game-card:hover {
transform: translateY(-4px);
box-shadow: var(--shadow-lg);
}
.game-title {
font-weight: 700;
margin-bottom: 1.5rem;
color: var(--text-primary);
font-size: 1.125rem;
display: flex;
align-items: center;
gap: 0.5rem;
}
.game-title::before {
content: "🎮";
font-size: 1.25rem;
}
.game-metrics {
display: grid;
grid-template-columns: repeat(3, 1fr);
gap: 1rem;
}
.metric {
text-align: center;
padding: 1rem;
background: white;
border-radius: var(--radius-md);
border: 1px solid var(--border-color);
}
.metric-value {
font-size: 2rem;
font-weight: 700;
background: var(--blue-gradient);
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
background-clip: text;
margin-bottom: 0.25rem;
}
.metric-label {
font-size: 0.875rem;
color: var(--text-secondary);
font-weight: 500;
text-transform: uppercase;
letter-spacing: 0.05em;
}
.recent-activities {
background: var(--card-background);
padding: 2rem;
border-radius: var(--radius-xl);
box-shadow: var(--shadow-sm);
border: 1px solid var(--border-color);
animation: slideInUp 0.6s ease-out 0.3s both;
}
.activity-item {
display: flex;
align-items: center;
padding: 1.5rem;
border-bottom: 1px solid var(--border-color);
transition: all 0.2s ease;
border-radius: var(--radius-md);
margin-bottom: 0.5rem;
}
.activity-item:last-child {
border-bottom: none;
margin-bottom: 0;
}
.activity-item:hover {
background: #f8fafc;
transform: translateX(4px);
}
.activity-icon {
width: 3rem;
height: 3rem;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
margin-right: 1.5rem;
color: white;
font-size: 1.25rem;
background: var(--activity-color);
box-shadow: var(--shadow-sm);
flex-shrink: 0;
}
.activity-content {
flex: 1;
}
.activity-title {
font-weight: 600;
margin-bottom: 0.25rem;
color: var(--text-primary);
}
.activity-time {
color: var(--text-muted);
font-size: 0.875rem;
}
.loading {
text-align: center;
padding: 3rem;
color: var(--text-secondary);
font-size: 1.125rem;
}
.loading::after {
content: "";
display: inline-block;
width: 2rem;
height: 2rem;
border: 3px solid var(--border-color);
border-top: 3px solid var(--primary-color);
border-radius: 50%;
animation: spin 1s linear infinite;
margin-left: 1rem;
vertical-align: middle;
}
@keyframes spin {
0% {
transform: rotate(0deg);
}
100% {
transform: rotate(360deg);
}
}
.error {
background: linear-gradient(135deg, #fef2f2, #fee2e2);
color: var(--error-color);
padding: 1.5rem;
border-radius: var(--radius-lg);
margin: 2rem 0;
border: 1px solid #fecaca;
display: flex;
align-items: center;
gap: 1rem;
animation: shake 0.5s ease-in-out;
}
@keyframes shake {
0%,
100% {
transform: translateX(0);
}
25% {
transform: translateX(-5px);
}
75% {
transform: translateX(5px);
}
}
.error::before {
content: "⚠️";
font-size: 1.5rem;
}
@media (max-width: 1024px) {
.analytics-container {
padding: 1.5rem;
}
.page-header {
padding: 2rem 1.5rem;
}
.page-header h1 {
font-size: 2.5rem;
}
}
@media (max-width: 768px) {
.analytics-container {
padding: 1rem;
}
.stats-grid {
grid-template-columns: 1fr;
gap: 1rem;
}
.filter-row {
flex-direction: column;
align-items: stretch;
gap: 1rem;
}
.filter-group {
min-width: auto;
}
.game-stats {
grid-template-columns: 1fr;
}
.recommendation-item {
flex-direction: column;
text-align: center;
gap: 1rem;
}
.recommendation-action {
margin-left: 0;
}
.page-header h1 {
font-size: 2rem;
}
.page-header p {
font-size: 1rem;
}
.chart-container,
.filter-section,
.recent-activities {
padding: 1.5rem;
}
}
/* 自定义滚动条 */
::-webkit-scrollbar {
width: 8px;
}
::-webkit-scrollbar-track {
background: var(--background-color);
}
::-webkit-scrollbar-thumb {
background: var(--primary-color);
border-radius: 4px;
}
::-webkit-scrollbar-thumb:hover {
background: var(--primary-dark);
}
</style>
</head>
<body>
<div class="container-header">
<div class="container-header-logo">
<img src="../asset/logo.png" alt="logo" />
</div>
<nav class="container-header-nav">
<div data-url="indexHome">首页</div>
<div data-url="indexDetail">导航</div>
<div data-url="courseHome">课程</div>
<div class="active">数据分析</div>
</nav>
<div id="userDisplay" class="user-display"></div>
</div>
<div class="analytics-container">
<div class="page-header">
<div class="page-header-content">
<h1><i class="fa fa-bar-chart"></i> 学生数据分析</h1>
<p>🚀 全面了解学习进度和答题情况,助力个性化教学</p>
</div>
</div>
<!-- 筛选区域 -->
<div class="filter-section">
<div class="filter-row">
<div class="filter-group">
<label class="filter-label">选择学生</label>
<select id="studentSelect" class="filter-select">
<option value="">加载中...</option>
</select>
</div>
<div class="filter-group">
<label class="filter-label">时间范围</label>
<select id="timeRange" class="filter-select">
<option value="7">最近7天</option>
<option value="30" selected>最近30天</option>
<option value="90">最近90天</option>
<option value="all">全部时间</option>
</select>
</div>
<div class="filter-group">
<label class="filter-label">课程分类</label>
<select id="courseCategory" class="filter-select">
<option value="">全部分类</option>
<option value="1">生活</option>
<option value="2">语文</option>
<option value="3">数学</option>
</select>
</div>
<button class="refresh-btn" onclick="loadAnalytics()">
<i class="fa fa-refresh"></i> 刷新数据
</button>
</div>
</div>
<!-- 统计卡片 -->
<div class="stats-grid">
<div class="stat-card">
<div class="stat-icon">
<i class="fa fa-book"></i>
</div>
<div class="stat-number" id="totalCourses">0</div>
<div class="stat-label">学习课程数</div>
</div>
<div class="stat-card">
<div class="stat-icon">
<i class="fa fa-clock-o"></i>
</div>
<div class="stat-number" id="totalTime">0</div>
<div class="stat-label">学习时长(小时)</div>
</div>
<div class="stat-card">
<div class="stat-icon">
<i class="fa fa-gamepad"></i>
</div>
<div class="stat-number" id="totalGames">0</div>
<div class="stat-label">游戏次数</div>
</div>
<div class="stat-card">
<div class="stat-icon">
<i class="fa fa-trophy"></i>
</div>
<div class="stat-number" id="avgScore">0</div>
<div class="stat-label">平均得分</div>
</div>
</div>
<!-- AI智能推荐 -->
<div class="chart-container">
<h2 class="chart-title"><i class="fa fa-magic"></i> AI智能视频推荐</h2>
<div id="aiRecommendations" class="ai-recommendations">
<div class="loading">🤖 正在分析学习数据,生成个性化推荐</div>
</div>
</div>
<!-- 游戏统计 -->
<div class="chart-container">
<h2 class="chart-title">
<i class="fa fa-pie-chart"></i> 游戏答题统计
</h2>
<div class="game-stats" id="gameStats">
<div class="loading">📊 正在加载游戏数据</div>
</div>
</div>
<!-- 最近活动 -->
<div class="recent-activities">
<h2 class="chart-title"><i class="fa fa-history"></i> 最近学习活动</h2>
<div id="recentActivities">
<div class="loading">📈 正在加载活动记录</div>
</div>
</div>
</div>
<!-- 引入后端集成脚本 -->
<script src="../js/apiService.js"></script>
<script src="../js/dataManager.js"></script>
<script src="../js/userManager.js"></script>
<script src="../js/accessTracker.js"></script>
<script src="../js/gameTracker.js"></script>
<script>
let currentStudentId = null;
let analyticsData = {};
// 页面加载完成后初始化
document.addEventListener("DOMContentLoaded", function () {
initializePage();
});
// 初始化页面
async function initializePage() {
try {
// 初始化后端集成
await Promise.all([
window.userManager.init(),
window.dataManager.init(),
]);
// 创建用户显示
window.userManager.createUserDisplay();
// 加载学生列表
await loadStudents();
// 加载分析数据
await loadAnalytics();
console.log("🎉 学生数据分析页面初始化完成");
} catch (error) {
console.error("页面初始化失败:", error);
showError("页面初始化失败,请刷新重试");
}
}
// 加载学生列表
async function loadStudents() {
try {
const result = await window.apiService.getUsers();
const studentSelect = document.getElementById("studentSelect");
studentSelect.innerHTML = '<option value="">请选择学生</option>';
if (result.data && result.data.length > 0) {
result.data.forEach((user) => {
const option = document.createElement("option");
option.value = user.id;
option.textContent = `${user.realName || user.username} (${
user.className || "未知班级"
})`;
studentSelect.appendChild(option);
});
} else {
studentSelect.innerHTML = '<option value="">暂无学生数据</option>';
}
// 监听选择变化
studentSelect.addEventListener("change", function () {
currentStudentId = this.value;
if (currentStudentId) {
loadAnalytics();
}
});
} catch (error) {
console.error("加载学生列表失败:", error);
document.getElementById("studentSelect").innerHTML =
'<option value="">加载失败</option>';
}
}
// 加载分析数据
async function loadAnalytics() {
if (!currentStudentId) {
showError("请先选择学生");
return;
}
try {
showLoading();
// 获取筛选条件
const timeRange = document.getElementById("timeRange").value;
const courseCategory =
document.getElementById("courseCategory").value;
// 加载真实数据优先使用后端API失败时使用模拟数据
await loadRealData(currentStudentId, timeRange, courseCategory);
// 更新页面显示
updateStatsDisplay();
updateAIRecommendations();
updateGameStats();
updateRecentActivities();
} catch (error) {
console.error("加载分析数据失败:", error);
showError("加载数据失败,请重试");
}
}
// 加载真实数据
async function loadRealData(studentId, timeRange, category) {
try {
// 并行加载所有数据
const [statsResult, coursesResult, gamesResult, activitiesResult] =
await Promise.all([
window.apiService.getStudentStats(studentId, timeRange, category),
window.apiService.getStudentCourseProgress(
studentId,
timeRange,
category
),
window.apiService.getStudentGameStats(
studentId,
timeRange,
category
),
window.apiService.getStudentRecentActivities(studentId, 10),
]);
// 处理统计数据
if (statsResult.data) {
analyticsData.stats = statsResult.data;
}
// 处理课程数据
if (coursesResult.data && coursesResult.data.courses) {
analyticsData.courses = coursesResult.data.courses.map(
(course) => ({
id: course.id,
name: course.name,
category: course.category,
progress: course.progress,
timeSpent: course.timeSpent,
})
);
}
// 处理游戏数据
if (gamesResult.data && gamesResult.data.games) {
analyticsData.games = gamesResult.data.games.map((game) => ({
name: game.name,
correct: game.correctCount,
incorrect: game.totalAttempts - game.correctCount,
avgTime: game.avgTime,
attempts: game.totalAttempts,
}));
}
// 处理活动数据
if (activitiesResult.data && activitiesResult.data.activities) {
analyticsData.activities = activitiesResult.data.activities.map(
(activity) => ({
type: activity.type,
title: activity.title,
time: activity.timeFormatted,
icon: activity.icon,
color: activity.color,
})
);
}
// 加载AI推荐数据
try {
const recommendationsResult =
await window.apiService.getAIRecommendations(
studentId,
analyticsData.games,
analyticsData.courses
);
if (recommendationsResult.success) {
analyticsData.recommendations =
recommendationsResult.data.recommendations;
}
} catch (error) {
console.error("加载AI推荐失败:", error);
// 使用模拟推荐数据
analyticsData.recommendations =
window.apiService.getMockRecommendations(
analyticsData.games,
analyticsData.courses
).data.recommendations;
}
} catch (error) {
console.error("加载真实数据失败:", error);
// 如果真实数据加载失败,使用模拟数据
await loadMockData(studentId, timeRange, category);
}
}
// 模拟数据加载(备用方案)
async function loadMockData(studentId, timeRange, category) {
// 模拟API调用延迟
await new Promise((resolve) => setTimeout(resolve, 1000));
// 模拟数据
analyticsData = {
stats: {
totalCourses: Math.floor(Math.random() * 20) + 10,
totalTime: Math.floor(Math.random() * 50) + 20,
totalGames: Math.floor(Math.random() * 100) + 50,
avgScore: Math.floor(Math.random() * 30) + 70,
},
courses: [
{
id: 1,
name: "生活技能训练",
category: "生活",
progress: 85,
timeSpent: 120,
},
{
id: 2,
name: "语文基础",
category: "语文",
progress: 60,
timeSpent: 90,
},
{
id: 3,
name: "数学计算",
category: "数学",
progress: 45,
timeSpent: 75,
},
{
id: 4,
name: "社交技能",
category: "生活",
progress: 90,
timeSpent: 150,
},
{
id: 5,
name: "阅读理解",
category: "语文",
progress: 30,
timeSpent: 45,
},
],
games: [
{
name: "数字识别游戏",
correct: 45,
incorrect: 5,
avgTime: 12,
attempts: 50,
},
{
name: "颜色配对游戏",
correct: 38,
incorrect: 12,
avgTime: 8,
attempts: 50,
},
{
name: "形状分类游戏",
correct: 42,
incorrect: 8,
avgTime: 15,
attempts: 50,
},
{
name: "字母学习游戏",
correct: 35,
incorrect: 15,
avgTime: 20,
attempts: 50,
},
],
activities: [
{
type: "course",
title: "完成了生活技能训练课程",
time: "2小时前",
icon: "fa-book",
color: "linear-gradient(135deg, #10b981, #34d399)",
},
{
type: "game",
title: "在数字识别游戏中获得高分",
time: "3小时前",
icon: "fa-trophy",
color: "linear-gradient(135deg, #f59e0b, #fbbf24)",
},
{
type: "course",
title: "开始学习语文基础课程",
time: "1天前",
icon: "fa-play",
color: "linear-gradient(135deg, #6366f1, #06b6d4)",
},
{
type: "game",
title: "完成了颜色配对游戏",
time: "1天前",
icon: "fa-check",
color: "linear-gradient(135deg, #10b981, #34d399)",
},
{
type: "course",
title: "复习了数学计算课程",
time: "2天前",
icon: "fa-refresh",
color: "linear-gradient(135deg, #a855f7, #c084fc)",
},
],
recommendations: [
{
id: "rec_1",
type: "video",
title: "🎯 数字识别游戏 - 基础讲解视频",
reason:
"根据您的答题情况在数字识别游戏中正确率为90%,表现优秀!建议观看进阶讲解视频来进一步提升。",
tags: ["进阶讲解", "数学概念", "提升技能"],
videoUrl: "video/数学-2下-视频--认识数字/认识数字课.mp4",
priority: "low",
},
{
id: "rec_2",
type: "course",
title: "📚 语文基础 - 深入学习",
reason:
"您在语文基础课程中的学习进度为60%,建议继续深入学习相关概念。",
tags: ["深入学习", "课程内容", "巩固知识"],
videoUrl: "video/生活-什么是情绪/happy.mp4",
priority: "medium",
},
{
id: "rec_3",
type: "video",
title: "🔥 数学计算 - 基础巩固",
reason:
"您在数学计算课程中的学习进度为45%,建议观看基础讲解视频来巩固数学概念。",
tags: ["基础讲解", "数学概念", "知识巩固"],
videoUrl: "video/数学-2下-视频--饮品和是4的加法/教学片段.mp4",
priority: "high",
},
],
};
}
// 更新统计显示
function updateStatsDisplay() {
const stats = analyticsData.stats;
animateNumber(
document.getElementById("totalCourses"),
stats.totalCourses
);
animateNumber(document.getElementById("totalTime"), stats.totalTime);
animateNumber(document.getElementById("totalGames"), stats.totalGames);
animateNumber(document.getElementById("avgScore"), stats.avgScore);
}
// 数字动画效果
function animateNumber(element, targetNumber) {
const duration = 1000;
const startNumber = 0;
const increment = targetNumber / (duration / 16);
let currentNumber = startNumber;
const timer = setInterval(() => {
currentNumber += increment;
if (currentNumber >= targetNumber) {
currentNumber = targetNumber;
clearInterval(timer);
}
element.textContent = Math.floor(currentNumber);
}, 16);
}
// 更新AI推荐
function updateAIRecommendations() {
const container = document.getElementById("aiRecommendations");
const recommendations = analyticsData.recommendations || [];
container.innerHTML = "";
if (recommendations.length === 0) {
container.innerHTML = '<div class="loading">🤔 暂无推荐内容</div>';
return;
}
recommendations.forEach((rec) => {
const recItem = document.createElement("div");
recItem.className = "recommendation-item";
// 根据优先级设置不同的图标和颜色
let icon = "fa-play-circle";
if (rec.priority === "high") {
icon = "fa-exclamation-triangle";
} else if (rec.priority === "medium") {
icon = "fa-info-circle";
}
recItem.innerHTML = `
<div class="recommendation-icon">
<i class="fa ${icon}"></i>
</div>
<div class="recommendation-content">
<div class="recommendation-title">${rec.title}</div>
<div class="recommendation-reason">${rec.reason}</div>
<div class="recommendation-tags">
${rec.tags
.map(
(tag) => `<span class="recommendation-tag">${tag}</span>`
)
.join("")}
</div>
</div>
<div class="recommendation-action">
<button class="watch-btn" onclick="watchRecommendation('${
rec.videoUrl
}')">
<i class="fa fa-play"></i> 观看
</button>
</div>
`;
container.appendChild(recItem);
});
}
// 观看推荐视频
function watchRecommendation(videoUrl) {
// 记录用户点击推荐
console.log("🎯 用户点击AI推荐视频:", videoUrl);
// 跳转到视频页面
window.open(videoUrl, "_blank");
// 可以在这里添加推荐点击统计
if (window.accessTracker) {
window.accessTracker.recordAccess(null, null, "recommendation", 0);
}
}
// 更新游戏统计
function updateGameStats() {
const container = document.getElementById("gameStats");
const games = analyticsData.games;
container.innerHTML = "";
games.forEach((game, index) => {
const gameCard = document.createElement("div");
gameCard.className = "game-card";
gameCard.style.setProperty("--game-delay", `${(index + 1) * 0.1}s`);
const accuracy = Math.round((game.correct / game.attempts) * 100);
gameCard.innerHTML = `
<div class="game-title">${game.name}</div>
<div class="game-metrics">
<div class="metric">
<div class="metric-value">${accuracy}%</div>
<div class="metric-label">正确率</div>
</div>
<div class="metric">
<div class="metric-value">${game.avgTime}s</div>
<div class="metric-label">平均用时</div>
</div>
<div class="metric">
<div class="metric-value">${game.attempts}</div>
<div class="metric-label">总次数</div>
</div>
</div>
`;
container.appendChild(gameCard);
});
}
// 更新最近活动
function updateRecentActivities() {
const container = document.getElementById("recentActivities");
const activities = analyticsData.activities;
container.innerHTML = "";
activities.forEach((activity) => {
const activityItem = document.createElement("div");
activityItem.className = "activity-item";
activityItem.innerHTML = `
<div class="activity-icon" style="background: ${activity.color};">
<i class="fa ${activity.icon}"></i>
</div>
<div class="activity-content">
<div class="activity-title">${activity.title}</div>
<div class="activity-time">${activity.time}</div>
</div>
`;
container.appendChild(activityItem);
});
}
// 显示加载状态
function showLoading() {
document.getElementById("aiRecommendations").innerHTML =
'<div class="loading">🤖 正在分析学习数据,生成个性化推荐</div>';
document.getElementById("gameStats").innerHTML =
'<div class="loading">📊 正在加载游戏数据</div>';
document.getElementById("recentActivities").innerHTML =
'<div class="loading">📈 正在加载活动记录</div>';
}
// 显示错误信息
function showError(message) {
const errorDiv = document.createElement("div");
errorDiv.className = "error";
errorDiv.textContent = message;
const container = document.querySelector(".analytics-container");
container.insertBefore(errorDiv, container.firstChild);
// 3秒后自动移除错误信息
setTimeout(() => {
if (errorDiv.parentNode) {
errorDiv.parentNode.removeChild(errorDiv);
}
}, 3000);
}
</script>
</body>
</html>