上传前人工裁剪错题区域,OCR 原文排除手写作答。

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
dekun
2026-06-28 16:01:46 +08:00
parent 23be608521
commit acfe002fbf
18 changed files with 975 additions and 448 deletions
+104
View File
@@ -0,0 +1,104 @@
import { Button, Modal, Slider, Space } from 'antd'
import { useCallback, useState } from 'react'
import Cropper, { type Area } from 'react-easy-crop'
import { blobToFile, cropImageToBlob } from '../utils/cropImage'
interface Props {
open: boolean
imageSrc: string | null
filename: string
onCancel: () => void
onConfirm: (file: File) => void
onSkip?: (file: File) => void
originalFile?: File | null
}
export default function ImageCropModal({
open,
imageSrc,
filename,
onCancel,
onConfirm,
onSkip,
originalFile,
}: Props) {
const [crop, setCrop] = useState({ x: 0, y: 0 })
const [zoom, setZoom] = useState(1)
const [croppedArea, setCroppedArea] = useState<Area | null>(null)
const [submitting, setSubmitting] = useState(false)
const onCropComplete = useCallback((_area: Area, pixels: Area) => {
setCroppedArea(pixels)
}, [])
const handleConfirm = async () => {
if (!imageSrc || !croppedArea) return
setSubmitting(true)
try {
const blob = await cropImageToBlob(imageSrc, croppedArea)
onConfirm(blobToFile(blob, filename.replace(/\.\w+$/, '') + '.jpg'))
} finally {
setSubmitting(false)
}
}
const handleSkip = () => {
if (originalFile && onSkip) {
onSkip(originalFile)
}
}
return (
<Modal
title="裁剪题目区域"
open={open}
onCancel={onCancel}
width="92%"
style={{ maxWidth: 560 }}
destroyOnHidden
footer={
<Space wrap>
<Button onClick={onCancel}></Button>
{onSkip && originalFile && (
<Button onClick={handleSkip} disabled={submitting}>
</Button>
)}
<Button type="primary" loading={submitting} onClick={handleConfirm}>
</Button>
</Space>
}
>
<p style={{ margin: '0 0 12px', color: '#666', fontSize: 13 }}>
</p>
<div
style={{
position: 'relative',
width: '100%',
height: 320,
background: '#111',
borderRadius: 8,
overflow: 'hidden',
}}
>
{imageSrc && (
<Cropper
image={imageSrc}
crop={crop}
zoom={zoom}
aspect={undefined}
onCropChange={setCrop}
onZoomChange={setZoom}
onCropComplete={onCropComplete}
/>
)}
</div>
<div style={{ marginTop: 12 }}>
<span style={{ fontSize: 12, color: '#666' }}></span>
<Slider min={1} max={3} step={0.05} value={zoom} onChange={setZoom} />
</div>
</Modal>
)
}