Files
ZhiQiXiaoYuan/utils/learningTracker.js
ChuXun eaab9a762a 1
2025-10-19 20:28:31 +08:00

279 lines
7.2 KiB
JavaScript
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.
/**
* 学习时长追踪服务
* 自动记录用户在各页面的学习时长
*/
class LearningTimeTracker {
constructor() {
this.currentPage = null;
this.startTime = null;
this.sessionData = {};
}
/**
* 开始追踪页面
* @param {String} pageName - 页面名称
*/
startTracking(pageName) {
// 如果之前有页面在追踪,先结束它
if (this.currentPage && this.startTime) {
this.endTracking();
}
this.currentPage = pageName;
this.startTime = Date.now();
console.log(`[学习追踪] 开始: ${pageName}`);
}
/**
* 结束追踪
*/
endTracking() {
if (!this.currentPage || !this.startTime) {
return;
}
const endTime = Date.now();
const duration = Math.floor((endTime - this.startTime) / 1000); // 秒
// 只记录超过5秒的有效学习时长过滤快速切换
if (duration >= 5) {
this.recordDuration(this.currentPage, duration);
console.log(`[学习追踪] 结束: ${this.currentPage}, 时长: ${duration}`);
}
this.currentPage = null;
this.startTime = null;
}
/**
* 记录学习时长
* @param {String} pageName - 页面名称
* @param {Number} duration - 时长(秒)
*/
recordDuration(pageName, duration) {
const today = this.getTodayString();
const now = new Date();
// 1. 更新总学习数据
const learningData = wx.getStorageSync('learning_data') || {
totalHours: 0,
totalSeconds: 0,
dailyRecords: []
};
learningData.totalSeconds = (learningData.totalSeconds || 0) + duration;
learningData.totalHours = parseFloat((learningData.totalSeconds / 3600).toFixed(1));
// 更新每日记录
let todayRecord = learningData.dailyRecords.find(r => r.date === today);
if (!todayRecord) {
todayRecord = {
date: today,
seconds: 0,
hours: 0,
sessions: []
};
learningData.dailyRecords.push(todayRecord);
}
todayRecord.seconds += duration;
todayRecord.hours = parseFloat((todayRecord.seconds / 3600).toFixed(2));
todayRecord.sessions.push({
page: pageName,
duration: duration,
timestamp: now.getTime()
});
// 只保留最近365天的记录
if (learningData.dailyRecords.length > 365) {
learningData.dailyRecords = learningData.dailyRecords
.sort((a, b) => new Date(b.date) - new Date(a.date))
.slice(0, 365);
}
wx.setStorageSync('learning_data', learningData);
// 2. 更新每日活跃度(用于热力图)
const dailyActivity = wx.getStorageSync('daily_activity') || {};
dailyActivity[today] = (dailyActivity[today] || 0) + Math.floor(duration / 60); // 转为分钟
wx.setStorageSync('daily_activity', dailyActivity);
// 3. 更新模块使用时长
const moduleMapping = {
'courses': 'course',
'course-detail': 'course',
'forum': 'forum',
'forum-detail': 'forum',
'post': 'forum',
'gpa': 'tools',
'schedule': 'tools',
'countdown': 'tools',
'tools': 'tools',
'ai-assistant': 'ai',
'dashboard': 'tools'
};
const module = moduleMapping[pageName] || 'other';
const moduleUsage = wx.getStorageSync('module_usage') || {
course: 0,
forum: 0,
tools: 0,
ai: 0
};
moduleUsage[module] = (moduleUsage[module] || 0) + parseFloat((duration / 3600).toFixed(2));
wx.setStorageSync('module_usage', moduleUsage);
// 4. 更新学习画像
this.updateLearningProfile(pageName, duration);
}
/**
* 更新学习画像
*/
updateLearningProfile(pageName, duration) {
const profile = wx.getStorageSync('learning_profile') || {
focus: 0, // 专注度
activity: 0, // 活跃度
duration: 0, // 学习时长
breadth: 0, // 知识广度
interaction: 0, // 互动性
persistence: 0 // 坚持度
};
// 专注度基于单次学习时长超过30分钟+10超过1小时+20
if (duration >= 3600) {
profile.focus = Math.min(100, profile.focus + 2);
} else if (duration >= 1800) {
profile.focus = Math.min(100, profile.focus + 1);
}
// 活跃度:每次学习+1
profile.activity = Math.min(100, profile.activity + 0.5);
// 学习时长:每小时+5
profile.duration = Math.min(100, profile.duration + (duration / 3600) * 5);
// 知识广度:访问课程相关页面+1
if (pageName.includes('course')) {
profile.breadth = Math.min(100, profile.breadth + 0.3);
}
// 互动性:论坛相关+2
if (pageName.includes('forum') || pageName.includes('post')) {
profile.interaction = Math.min(100, profile.interaction + 1);
}
// 坚持度:每天学习+3
const learningData = wx.getStorageSync('learning_data') || {};
const continuousDays = this.calculateContinuousDays(learningData.dailyRecords || []);
profile.persistence = Math.min(100, continuousDays * 3);
wx.setStorageSync('learning_profile', profile);
}
/**
* 计算连续学习天数
*/
calculateContinuousDays(records) {
if (!records || records.length === 0) return 0;
const sortedRecords = records.sort((a, b) => new Date(b.date) - new Date(a.date));
let continuousDays = 0;
const today = new Date();
today.setHours(0, 0, 0, 0);
for (let i = 0; i < sortedRecords.length; i++) {
const recordDate = new Date(sortedRecords[i].date);
recordDate.setHours(0, 0, 0, 0);
const daysDiff = Math.floor((today - recordDate) / (1000 * 60 * 60 * 24));
if (daysDiff === i) {
continuousDays++;
} else {
break;
}
}
return continuousDays;
}
/**
* 获取今天的日期字符串
*/
getTodayString() {
const now = new Date();
const year = now.getFullYear();
const month = String(now.getMonth() + 1).padStart(2, '0');
const day = String(now.getDate()).padStart(2, '0');
return `${year}-${month}-${day}`;
}
/**
* 获取学习统计
*/
getStats() {
const learningData = wx.getStorageSync('learning_data') || {
totalHours: 0,
totalSeconds: 0,
dailyRecords: []
};
const continuousDays = this.calculateContinuousDays(learningData.dailyRecords);
return {
totalHours: learningData.totalHours || 0,
totalSeconds: learningData.totalSeconds || 0,
continuousDays: continuousDays,
todayHours: this.getTodayHours()
};
}
/**
* 获取今天的学习时长
*/
getTodayHours() {
const today = this.getTodayString();
const learningData = wx.getStorageSync('learning_data') || { dailyRecords: [] };
const todayRecord = learningData.dailyRecords.find(r => r.date === today);
return todayRecord ? todayRecord.hours : 0;
}
}
// 创建全局实例
const tracker = new LearningTimeTracker();
module.exports = {
tracker,
/**
* 页面onShow时调用
*/
onPageShow(pageName) {
tracker.startTracking(pageName);
},
/**
* 页面onHide时调用
*/
onPageHide() {
tracker.endTracking();
},
/**
* 页面onUnload时调用
*/
onPageUnload() {
tracker.endTracking();
},
/**
* 获取统计数据
*/
getStats() {
return tracker.getStats();
}
};