上传前人工裁剪错题区域,OCR 原文排除手写作答。
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -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>
|
||||
)
|
||||
}
|
||||
Reference in New Issue
Block a user