Files
RGKT/rg-09112127/html/student_analytics.html

1465 lines
41 KiB
HTML
Raw Normal View History

2025-10-10 19:35:04 +08:00
<!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>