// ============================================================================= // 企微IT智能服务台 — 屏幕截图组合函数 // ============================================================================= // 说明:实现屏幕截图功能,优先使用浏览器 Screen Capture API, // 在不支持或有问题(如企微桌面端限制)时降级到系统截图方案。 // // 两个方案: // 方案A(优先): navigator.mediaDevices.getDisplayMedia() // - 优点:可直接在浏览器内完成截图,体验好 // - 缺点:企微桌面端可能限制此 API(非 HTTPS 或 localhost 外不可用) // 方案B(降级):提示用户用系统截图工具(Win+Shift+S / Cmd+Shift+4) // 然后 Ctrl+V 粘贴到输入框(已有 handlePaste 实现) // // 使用方式: // const { captureScreen, isCapturing, isScreenCaptureSupported, captureFallback } // = useScreenCapture() // - captureScreen():尝试方案A,失败时不自动降级(由调用方决定是否提示) // - isScreenCaptureSupported():检测是否支持方案A // - 调用方在 captureScreen() 返回 null 时,自行提示用户用系统截图 // ============================================================================= import { ref } from 'vue' /** 是否正在截图(用于 UI 状态展示) */ const isCapturing = ref(false) /** * 检测浏览器是否支持 Screen Capture API * @returns 是否支持 */ export function isScreenCaptureSupported(): boolean { return !!(navigator.mediaDevices && navigator.mediaDevices.getDisplayMedia) } /** * 截取屏幕/窗口/标签页 * * 做什么:调用浏览器 Screen Capture API,让用户选择要截取的目标, * 然后从视频流中捕获一帧画面,转为 Blob 返回。 * * 为什么用 Screen Capture API: * - 浏览器原生支持,无需第三方库 * - 可以截取整个屏幕、应用窗口或浏览器标签页 * - 企微桌面端基于 Chromium 内核,完全支持 * * @returns 截图的 Blob 对象(PNG 格式),失败返回 null */ export async function captureScreen(): Promise { // 不支持 Screen Capture API → 提示用户用系统截图 if (!isScreenCaptureSupported()) { console.warn('[useScreenCapture] 浏览器不支持 Screen Capture API') return null } isCapturing.value = true try { // ------------------------------------------------------------------ // 1. 请求屏幕共享权限(浏览器弹出选择器:屏幕/窗口/标签页) // ------------------------------------------------------------------ // video: true — 只请求视频流(不需要音频) // @ts-ignore — Chrome 支持 preferLabel 等非标准选项 const stream = await navigator.mediaDevices.getDisplayMedia({ video: true, audio: false, }) // ------------------------------------------------------------------ // 2. 从视频流中获取第一帧画面 // ------------------------------------------------------------------ const track = stream.getVideoTracks()[0] if (!track) { console.warn('[useScreenCapture] 没有获取到视频轨道') return null } // 使用 ImageCapture API(Chrome 59+)获取高质量截图 // 如果不支持 ImageCapture,则回退到 Canvas 绘制方案 let blob: Blob | null = null if ('ImageCapture' in window) { try { // ImageCapture API — 直接从视频轨道抓帧,质量更高 const imageCapture = new ImageCapture(track) // @ts-ignore — grabFrame 是 ImageCapture 标准方法,但 TS 类型定义可能不完整 const bitmap = await imageCapture.grabFrame() // 绘制到 Canvas → 导出 PNG Blob const canvas = document.createElement('canvas') canvas.width = bitmap.width canvas.height = bitmap.height const ctx = canvas.getContext('2d') if (ctx) { ctx.drawImage(bitmap, 0, 0) blob = await new Promise((resolve) => { canvas.toBlob((b) => resolve(b), 'image/png') }) } bitmap.close() // 释放位图资源 } catch (imageCaptureError) { console.warn('[useScreenCapture] ImageCapture 失败,回退到 Canvas 方案:', imageCaptureError) } } // ImageCapture 失败或不可用 → Canvas 方案 if (!blob) { const video = document.createElement('video') video.srcObject = stream video.muted = true // 静音播放(避免系统声音输出) // 等待视频元数据加载完成 await new Promise((resolve, reject) => { video.onloadedmetadata = () => resolve() video.onerror = () => reject(new Error('视频加载失败')) video.play() // 必须调用 play 才能获取帧 }) // 短暂等待确保有画面帧可用 await new Promise((r) => setTimeout(r, 200)) // 绘制当前帧到 Canvas const canvas = document.createElement('canvas') canvas.width = video.videoWidth canvas.height = video.videoHeight const ctx = canvas.getContext('2d') if (ctx) { ctx.drawImage(video, 0, 0) blob = await new Promise((resolve) => { canvas.toBlob((b) => resolve(b), 'image/png') }) } // 清理 video 元素 video.pause() video.srcObject = null } // ------------------------------------------------------------------ // 3. 停止屏幕共享(关闭视频流) // ------------------------------------------------------------------ stream.getTracks().forEach((t) => t.stop()) return blob } catch (error: any) { // 用户取消屏幕选择 → 不是错误,静默处理 if (error?.name === 'NotAllowedError' || error?.name === 'AbortError') { console.log('[useScreenCapture] 用户取消了屏幕选择') return null } console.error('[useScreenCapture] 截图失败:', error) return null } finally { isCapturing.value = false } } /** * 组合函数返回值 */ export function useScreenCapture() { return { /** 是否正在截图 */ isCapturing, /** 截取屏幕 */ captureScreen, /** 检测浏览器支持 */ isScreenCaptureSupported, } }