guoyu/fronted_uniapp/utils/monitor.js
2025-12-03 18:58:36 +08:00

299 lines
8.7 KiB
JavaScript
Raw 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.

/**
* 学习监控工具类
* 实现屏幕截图和上传功能
*/
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