Files
AI-Grading-Assistant/src/components/ScreenshotOverlay.vue
T

222 lines
4.9 KiB
Vue
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.
<template>
<div
class="screenshot-overlay"
@mousedown="onMouseDown"
@mousemove="onMouseMove"
@mouseup="onMouseUp"
:class="{ 'has-selection': hasSelection }"
>
<canvas ref="canvasRef" class="screenshot-canvas"></canvas>
<div v-if="!hasSelection" class="screenshot-tip">拖动选择截图区域 · Esc取消</div>
<div v-else class="screenshot-actions" :style="actionsStyle" @mousedown.stop @mouseup.stop>
<button class="action-btn confirm" @click="confirmCapture"> 确认</button>
<button class="action-btn cancel" @click="cancel"> 取消</button>
</div>
</div>
</template>
<script setup lang="ts">
import { ref, onMounted, onUnmounted, computed } from 'vue'
const canvasRef = ref<HTMLCanvasElement | null>(null)
const state = {
isSelecting: false,
startX: 0,
startY: 0,
endX: 0,
endY: 0,
}
const hasSelection = ref(false)
const isCapturing = ref(false)
const actionsStyle = computed(() => {
if (!hasSelection.value) return {}
const x = Math.min(state.startX, state.endX)
const y = Math.min(state.startY, state.endY)
const w = Math.abs(state.endX - state.startX)
const h = Math.abs(state.endY - state.startY)
const panelW = 160
const gap = 8
let left = x + w - panelW
let top = y + h + gap
if (left < 4) left = 4
if (top + 40 > window.innerHeight) top = y - 40 - gap
return { left: left + 'px', top: top + 'px', transform: 'none', bottom: 'auto' }
})
onMounted(() => {
document.addEventListener('keydown', onKeyDown)
})
onUnmounted(() => {
document.removeEventListener('keydown', onKeyDown)
})
function onMouseDown(e: MouseEvent) {
hasSelection.value = false
state.isSelecting = true
state.startX = e.clientX
state.startY = e.clientY
state.endX = e.clientX
state.endY = e.clientY
}
function onMouseMove(e: MouseEvent) {
if (!state.isSelecting) return
state.endX = e.clientX
state.endY = e.clientY
drawOverlay()
}
function onMouseUp() {
if (!state.isSelecting) return
state.isSelecting = false
const w = Math.abs(state.endX - state.startX)
const h = Math.abs(state.endY - state.startY)
if (w < 10 || h < 10) return
hasSelection.value = true
drawOverlay()
}
function confirmCapture() {
const x = Math.min(state.startX, state.endX)
const y = Math.min(state.startY, state.endY)
const width = Math.abs(state.endX - state.startX)
const height = Math.abs(state.endY - state.startY)
if (width < 10 || height < 10) return
if (isCapturing.value) return
isCapturing.value = true
if (window.electronAPI) {
window.electronAPI.screenshot.capture({ x, y, width, height })
.catch(() => {})
}
try { window.close() } catch {}
}
function cancel() {
if (window.electronAPI) {
window.electronAPI.screenshot.cancel()
.catch(() => {})
}
isCapturing.value = false
try { window.close() } catch {}
}
function drawOverlay() {
const canvas = canvasRef.value
if (!canvas) return
const ctx = canvas.getContext('2d')
if (!ctx) return
canvas.width = window.innerWidth
canvas.height = window.innerHeight
ctx.clearRect(0, 0, canvas.width, canvas.height)
const x = Math.min(state.startX, state.endX)
const y = Math.min(state.startY, state.endY)
const w = Math.abs(state.endX - state.startX)
const h = Math.abs(state.endY - state.startY)
ctx.fillStyle = 'rgba(0, 0, 0, 0.45)'
ctx.fillRect(0, 0, canvas.width, canvas.height)
ctx.clearRect(x, y, w, h)
ctx.strokeStyle = '#409EFF'
ctx.lineWidth = 2.5
ctx.strokeRect(x, y, w, h)
ctx.fillStyle = 'rgba(64, 158, 255, 0.08)'
ctx.fillRect(x, y, w, h)
ctx.fillStyle = 'rgba(255, 255, 255, 0.9)'
ctx.font = '13px sans-serif'
ctx.fillText(`${w} × ${h}`, x + 6, y - 8)
}
function onKeyDown(e: KeyboardEvent) {
if (e.key === 'Escape') {
e.preventDefault()
e.stopPropagation()
cancel()
}
if (e.key === 'Enter' && hasSelection.value) {
e.preventDefault()
e.stopPropagation()
confirmCapture()
}
}
</script>
<style scoped>
.screenshot-overlay {
position: fixed;
inset: 0;
z-index: 99999;
cursor: crosshair;
}
.screenshot-overlay.has-selection {
cursor: default;
}
.screenshot-canvas {
position: fixed;
inset: 0;
width: 100%;
height: 100%;
}
.screenshot-tip {
position: fixed;
bottom: 40px;
left: 50%;
transform: translateX(-50%);
background: rgba(0, 0, 0, 0.7);
color: #fff;
padding: 8px 16px;
border-radius: 6px;
font-size: 13px;
pointer-events: none;
}
.screenshot-actions {
position: fixed;
display: flex;
gap: 10px;
pointer-events: auto;
}
.action-btn {
border: none;
padding: 8px 20px;
border-radius: 6px;
font-size: 13px;
cursor: pointer;
transition: all 0.15s;
}
.action-btn.confirm {
background: #409EFF;
color: #fff;
}
.action-btn.confirm:hover {
background: #66b1ff;
}
.action-btn.cancel {
background: rgba(0, 0, 0, 0.6);
color: #fff;
}
.action-btn.cancel:hover {
background: rgba(0, 0, 0, 0.8);
}
</style>