1
This commit is contained in:
350
utils/analytics.js
Normal file
350
utils/analytics.js
Normal file
@@ -0,0 +1,350 @@
|
||||
/**
|
||||
* 数据统计和分析工具
|
||||
*/
|
||||
|
||||
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
|
||||
}
|
||||
Reference in New Issue
Block a user