351 lines
7.8 KiB
JavaScript
351 lines
7.8 KiB
JavaScript
/**
|
||
* 数据统计和分析工具
|
||
*/
|
||
|
||
const { Storage } = require('./storage.js')
|
||
const { logger } = require('./logger.js')
|
||
|
||
class Analytics {
|
||
/**
|
||
* 获取学习统计数据
|
||
*/
|
||
static getStudyStats() {
|
||
const gpaCourses = Storage.get('gpaCourses', [])
|
||
const countdowns = Storage.get('countdowns', [])
|
||
const favoriteCourses = Storage.get('favoriteCourses', [])
|
||
|
||
const stats = {
|
||
// GPA统计
|
||
gpa: {
|
||
total: gpaCourses.length,
|
||
average: this._calculateAverageGPA(gpaCourses),
|
||
highest: this._getHighestScore(gpaCourses),
|
||
lowest: this._getLowestScore(gpaCourses),
|
||
trend: this._calculateGPATrend(gpaCourses)
|
||
},
|
||
|
||
// 课程统计
|
||
courses: {
|
||
total: favoriteCourses.length,
|
||
byCategory: this._groupByCategory(gpaCourses),
|
||
completionRate: this._calculateCompletionRate(gpaCourses)
|
||
},
|
||
|
||
// 考试倒计时
|
||
exams: {
|
||
total: countdowns.length,
|
||
upcoming: countdowns.filter(c => c.days > 0).length,
|
||
thisWeek: countdowns.filter(c => c.days <= 7 && c.days > 0).length
|
||
},
|
||
|
||
// 学习时长统计(模拟数据)
|
||
studyTime: {
|
||
today: 0,
|
||
week: 0,
|
||
month: 0
|
||
}
|
||
}
|
||
|
||
return stats
|
||
}
|
||
|
||
/**
|
||
* 计算平均GPA
|
||
*/
|
||
static _calculateAverageGPA(courses) {
|
||
if (courses.length === 0) return 0
|
||
|
||
let totalPoints = 0
|
||
let totalCredits = 0
|
||
|
||
courses.forEach(course => {
|
||
const gradePoint = this._scoreToGradePoint(course.score)
|
||
totalPoints += gradePoint * course.credit
|
||
totalCredits += course.credit
|
||
})
|
||
|
||
return totalCredits > 0 ? (totalPoints / totalCredits).toFixed(2) : 0
|
||
}
|
||
|
||
/**
|
||
* 分数转绩点
|
||
*/
|
||
static _scoreToGradePoint(score) {
|
||
if (score >= 60) {
|
||
return parseFloat((score / 10 - 5).toFixed(2))
|
||
}
|
||
return 0
|
||
}
|
||
|
||
/**
|
||
* 获取最高分
|
||
*/
|
||
static _getHighestScore(courses) {
|
||
if (courses.length === 0) return 0
|
||
return Math.max(...courses.map(c => c.score))
|
||
}
|
||
|
||
/**
|
||
* 获取最低分
|
||
*/
|
||
static _getLowestScore(courses) {
|
||
if (courses.length === 0) return 0
|
||
return Math.min(...courses.map(c => c.score))
|
||
}
|
||
|
||
/**
|
||
* 计算GPA趋势
|
||
*/
|
||
static _calculateGPATrend(courses) {
|
||
if (courses.length < 2) return []
|
||
|
||
// 按时间排序(假设ID越大时间越新)
|
||
const sorted = [...courses].sort((a, b) => a.id - b.id)
|
||
|
||
const trend = []
|
||
let runningTotal = 0
|
||
let runningCredits = 0
|
||
|
||
sorted.forEach(course => {
|
||
const gradePoint = this._scoreToGradePoint(course.score)
|
||
runningTotal += gradePoint * course.credit
|
||
runningCredits += course.credit
|
||
|
||
const gpa = runningCredits > 0 ? (runningTotal / runningCredits).toFixed(2) : 0
|
||
|
||
trend.push({
|
||
name: course.name,
|
||
gpa: parseFloat(gpa),
|
||
score: course.score
|
||
})
|
||
})
|
||
|
||
return trend
|
||
}
|
||
|
||
/**
|
||
* 按分类分组
|
||
*/
|
||
static _groupByCategory(courses) {
|
||
const grouped = {}
|
||
|
||
courses.forEach(course => {
|
||
const category = course.category || '其他'
|
||
if (!grouped[category]) {
|
||
grouped[category] = {
|
||
count: 0,
|
||
totalScore: 0,
|
||
avgScore: 0
|
||
}
|
||
}
|
||
|
||
grouped[category].count++
|
||
grouped[category].totalScore += course.score
|
||
})
|
||
|
||
// 计算平均分
|
||
Object.keys(grouped).forEach(key => {
|
||
grouped[key].avgScore = (grouped[key].totalScore / grouped[key].count).toFixed(1)
|
||
})
|
||
|
||
return grouped
|
||
}
|
||
|
||
/**
|
||
* 计算完成率
|
||
*/
|
||
static _calculateCompletionRate(courses) {
|
||
if (courses.length === 0) return 0
|
||
const passed = courses.filter(c => c.score >= 60).length
|
||
return ((passed / courses.length) * 100).toFixed(1)
|
||
}
|
||
|
||
/**
|
||
* 生成学习报告
|
||
*/
|
||
static generateReport() {
|
||
const stats = this.getStudyStats()
|
||
|
||
const report = {
|
||
summary: {
|
||
gpa: stats.gpa.average,
|
||
totalCourses: stats.courses.total,
|
||
passRate: stats.courses.completionRate
|
||
},
|
||
highlights: [],
|
||
suggestions: []
|
||
}
|
||
|
||
// 添加亮点
|
||
if (parseFloat(stats.gpa.average) >= 3.5) {
|
||
report.highlights.push('GPA优秀,继续保持!')
|
||
}
|
||
|
||
if (parseFloat(stats.courses.completionRate) === 100) {
|
||
report.highlights.push('所有课程全部通过,非常棒!')
|
||
}
|
||
|
||
// 添加建议
|
||
if (parseFloat(stats.gpa.average) < 2.5) {
|
||
report.suggestions.push('建议加强学习,提高GPA')
|
||
}
|
||
|
||
if (stats.exams.thisWeek > 0) {
|
||
report.suggestions.push(`本周有${stats.exams.thisWeek}场考试,请做好准备`)
|
||
}
|
||
|
||
logger.info('生成学习报告', report)
|
||
|
||
return report
|
||
}
|
||
|
||
/**
|
||
* 导出数据
|
||
*/
|
||
static exportData() {
|
||
const data = {
|
||
gpaCourses: Storage.get('gpaCourses', []),
|
||
favoriteCourses: Storage.get('favoriteCourses', []),
|
||
schedule: Storage.get('schedule', {}),
|
||
countdowns: Storage.get('countdowns', []),
|
||
exportTime: new Date().toISOString()
|
||
}
|
||
|
||
logger.info('导出数据', { recordCount: data.gpaCourses.length })
|
||
|
||
return data
|
||
}
|
||
|
||
/**
|
||
* 导入数据
|
||
*/
|
||
static importData(data) {
|
||
try {
|
||
if (data.gpaCourses) {
|
||
Storage.set('gpaCourses', data.gpaCourses)
|
||
}
|
||
|
||
if (data.favoriteCourses) {
|
||
Storage.set('favoriteCourses', data.favoriteCourses)
|
||
}
|
||
|
||
if (data.schedule) {
|
||
Storage.set('schedule', data.schedule)
|
||
}
|
||
|
||
if (data.countdowns) {
|
||
Storage.set('countdowns', data.countdowns)
|
||
}
|
||
|
||
logger.info('导入数据成功', { recordCount: data.gpaCourses?.length || 0 })
|
||
|
||
return { success: true }
|
||
} catch (error) {
|
||
logger.error('导入数据失败', error)
|
||
return { success: false, error }
|
||
}
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 智能推荐系统
|
||
*/
|
||
class RecommendationEngine {
|
||
/**
|
||
* 推荐课程
|
||
*/
|
||
static recommendCourses(allCourses, userCourses) {
|
||
const recommendations = []
|
||
|
||
// 基于用户已选课程推荐相关课程
|
||
const userCategories = [...new Set(userCourses.map(c => c.category))]
|
||
|
||
allCourses.forEach(course => {
|
||
let score = 0
|
||
|
||
// 相同分类加分
|
||
if (userCategories.includes(course.category)) {
|
||
score += 3
|
||
}
|
||
|
||
// 热门课程加分
|
||
if (course.enrolled / course.capacity > 0.8) {
|
||
score += 2
|
||
}
|
||
|
||
// 高评分课程加分
|
||
if (course.rating && course.rating >= 4.5) {
|
||
score += 2
|
||
}
|
||
|
||
// 还有名额的课程加分
|
||
if (course.enrolled < course.capacity) {
|
||
score += 1
|
||
}
|
||
|
||
if (score > 0) {
|
||
recommendations.push({
|
||
course,
|
||
score,
|
||
reason: this._getRecommendReason(score)
|
||
})
|
||
}
|
||
})
|
||
|
||
// 按分数排序
|
||
recommendations.sort((a, b) => b.score - a.score)
|
||
|
||
return recommendations.slice(0, 5)
|
||
}
|
||
|
||
/**
|
||
* 获取推荐理由
|
||
*/
|
||
static _getRecommendReason(score) {
|
||
if (score >= 6) return '强烈推荐'
|
||
if (score >= 4) return '推荐'
|
||
return '可以考虑'
|
||
}
|
||
|
||
/**
|
||
* 学习建议
|
||
*/
|
||
static getStudySuggestions(stats) {
|
||
const suggestions = []
|
||
|
||
// GPA建议
|
||
if (parseFloat(stats.gpa.average) < 2.0) {
|
||
suggestions.push({
|
||
type: 'gpa',
|
||
level: 'warning',
|
||
message: '当前GPA较低,建议加强学习',
|
||
action: '查看学习计划'
|
||
})
|
||
} else if (parseFloat(stats.gpa.average) >= 3.5) {
|
||
suggestions.push({
|
||
type: 'gpa',
|
||
level: 'success',
|
||
message: 'GPA优秀,继续保持!',
|
||
action: '分享经验'
|
||
})
|
||
}
|
||
|
||
// 考试提醒
|
||
if (stats.exams.thisWeek > 0) {
|
||
suggestions.push({
|
||
type: 'exam',
|
||
level: 'info',
|
||
message: `本周有${stats.exams.thisWeek}场考试`,
|
||
action: '查看倒计时'
|
||
})
|
||
}
|
||
|
||
return suggestions
|
||
}
|
||
}
|
||
|
||
module.exports = {
|
||
Analytics,
|
||
RecommendationEngine
|
||
}
|