guoyu/fronted_uniapp/utils/monitor.js

299 lines
8.7 KiB
JavaScript
Raw Normal View History

2025-12-03 18:58:36 +08:00
/**
* 学习监控工具类
* 实现屏幕截图和上传功能
*/
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