This commit is contained in:
ChuXun
2025-10-19 20:28:31 +08:00
parent c81f8a8b03
commit eaab9a762a
100 changed files with 23416 additions and 0 deletions

278
utils/learningTracker.js Normal file
View 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();
}
};