init: AI Grading Assistant MVP

This commit is contained in:
2026-07-15 21:54:21 +08:00
commit 3b1dc16489
34 changed files with 10266 additions and 0 deletions
+209
View File
@@ -0,0 +1,209 @@
<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" @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)
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;
bottom: 40px;
left: 50%;
transform: translateX(-50%);
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>