1
This commit is contained in:
278
utils/learningTracker.js
Normal file
278
utils/learningTracker.js
Normal file
@@ -0,0 +1,278 @@
|
||||
/**
|
||||
* 学习时长追踪服务
|
||||
* 自动记录用户在各页面的学习时长
|
||||
*/
|
||||
|
||||
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();
|
||||
}
|
||||
};
|
||||
Reference in New Issue
Block a user