Files
secondary-school-grade-archive/frontend/src/components/ImageCropModal.tsx
T
2026-06-28 16:01:46 +08:00

105 lines
2.7 KiB
TypeScript
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.
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>
)
}