/** * 学习监控工具类 * 实现屏幕截图和上传功能 */ import request from './request.js' class Monitor { constructor() { this.screenshotTimer = null this.uploadQueue = [] // 上传队列 this.isUploading = false this.interval = 30000 // 默认30秒 this.currentCourseId = null this.isEnabled = false } /** * 启动监控 * @param {Number} courseId 课程ID(可选) * @param {Number} interval 截图间隔(毫秒,默认30秒) */ start(courseId = null, interval = 30000) { if (this.isEnabled) { console.log('监控已启动') return } this.currentCourseId = courseId this.interval = interval this.isEnabled = true console.log('启动学习监控,间隔:', interval / 1000, '秒') // 立即执行一次 this.captureAndUpload() // 定时执行 this.screenshotTimer = setInterval(() => { if (this.isEnabled) { this.captureAndUpload() } }, this.interval) } /** * 停止监控 */ stop() { this.isEnabled = false if (this.screenshotTimer) { clearInterval(this.screenshotTimer) this.screenshotTimer = null } console.log('学习监控已停止') } /** * 设置当前课程ID */ setCourseId(courseId) { this.currentCourseId = courseId } /** * 截图并上传 */ async captureAndUpload() { try { // 检查网络状态 const networkType = await this.getNetworkType() if (networkType === 'none') { console.log('网络未连接,跳过截图上传') return } // 截图 const screenshotPath = await this.captureScreenshot() if (!screenshotPath) { console.log('截图失败') return } // 添加到上传队列 this.uploadQueue.push({ filePath: screenshotPath, courseId: this.currentCourseId, timestamp: Date.now() }) // 处理上传队列 this.processUploadQueue() } catch (error) { console.error('截图上传失败:', error) } } /** * 截图 */ captureScreenshot() { return new Promise((resolve, reject) => { // #ifdef APP-PLUS // App环境:使用plus API this.captureScreenshotApp(resolve, reject) // #endif // #ifdef H5 // H5环境:使用html2canvas或简化方案 this.captureScreenshotH5(resolve, reject) // #endif // #ifndef H5 || APP-PLUS // 其他平台:暂不支持或使用降级方案 console.warn('当前平台不支持截图功能,跳过截图') reject(new Error('当前平台不支持截图功能')) // #endif }) } /** * App环境截图(推荐) */ captureScreenshotApp(resolve, reject) { // #ifdef APP-PLUS try { // 使用plus.screen.capture截图 if (typeof plus !== 'undefined' && plus.screen) { plus.screen.capture((bitmap) => { // 保存为临时文件 const timestamp = Date.now() const filePath = `_doc/screenshot_${timestamp}.png` bitmap.save(filePath, (success) => { resolve(filePath) }, (error) => { console.error('保存截图失败:', error) reject(error) }) }, (error) => { console.error('截图失败:', error) reject(error) }) } else { // 降级方案:提示用户 console.warn('plus.screen不可用,跳过截图') reject(new Error('截图功能不可用')) } } catch (error) { console.error('截图异常:', error) reject(error) } // #endif // #ifndef APP-PLUS reject(new Error('App环境不可用')) // #endif } /** * H5环境截图(简化方案) * 注意:H5环境截图功能有限,主要用于开发测试 */ captureScreenshotH5(resolve, reject) { // #ifdef H5 try { // H5环境可以使用html2canvas库,但这里使用简单的canvas方式 // 注意:H5环境截图功能有限,建议在App环境中使用 const canvas = document.createElement('canvas') const ctx = canvas.getContext('2d') canvas.width = window.innerWidth canvas.height = window.innerHeight // 绘制当前页面内容(简化版,实际需要更复杂的实现) ctx.fillStyle = '#fff' ctx.fillRect(0, 0, canvas.width, canvas.height) ctx.fillStyle = '#000' ctx.font = '16px Arial' ctx.fillText('学习监控截图', 20, 40) ctx.fillText(new Date().toLocaleString(), 20, 60) ctx.fillText('H5环境截图功能有限', 20, 80) // 转换为图片 canvas.toBlob((blob) => { if (typeof URL !== 'undefined' && URL.createObjectURL) { const filePath = URL.createObjectURL(blob) resolve(filePath) } else { // Fallback: convert blob to data URL const reader = new FileReader() reader.onloadend = () => { resolve(reader.result) } reader.onerror = (error) => { reject(error) } reader.readAsDataURL(blob) } }, 'image/png', 0.8) } catch (error) { console.error('H5截图失败:', error) reject(error) } // #endif // #ifndef H5 reject(new Error('H5环境不可用')) // #endif } /** * 获取网络状态 */ getNetworkType() { return new Promise((resolve) => { uni.getNetworkType({ success: (res) => { resolve(res.networkType) }, fail: () => { resolve('unknown') } }) }) } /** * 处理上传队列 */ async processUploadQueue() { if (this.isUploading || this.uploadQueue.length === 0) { return } this.isUploading = true while (this.uploadQueue.length > 0) { const item = this.uploadQueue.shift() try { await this.uploadScreenshot(item.filePath, item.courseId) console.log('截图上传成功') } catch (error) { console.error('截图上传失败:', error) // 上传失败,重新加入队列(最多重试3次) if (item.retryCount === undefined) { item.retryCount = 0 } if (item.retryCount < 3) { item.retryCount++ this.uploadQueue.push(item) } } } this.isUploading = false } /** * 上传截图 */ async uploadScreenshot(filePath, courseId = null) { const formData = {} if (courseId) { formData.courseId = courseId } return await request.upload('/study/monitor/screenshot', filePath, formData, { name: 'file' }) } /** * 手动截图上传(用于测试) */ async manualCapture() { try { const screenshotPath = await this.captureScreenshot() if (screenshotPath) { await this.uploadScreenshot(screenshotPath, this.currentCourseId) uni.showToast({ title: '截图上传成功', icon: 'success' }) } } catch (error) { uni.showToast({ title: '截图失败', icon: 'none' }) console.error('手动截图失败:', error) } } } // 创建单例 const monitor = new Monitor() export default monitor