作业帮式错题标注:OCR 定位错误红框 + 解题思路。
- PaddleOCR 行级坐标 + AI 识别错答区域,生成标注图 - 解法拆分为「解题思路」与「详细解答」 - 详情页标注图/原图切换,列表显示标注缩略图
This commit is contained in:
@@ -3,6 +3,7 @@ import api from '../api/client'
|
||||
|
||||
interface Props {
|
||||
questionId: string
|
||||
variant?: 'original' | 'annotated'
|
||||
className?: string
|
||||
alt?: string
|
||||
style?: React.CSSProperties
|
||||
@@ -10,6 +11,7 @@ interface Props {
|
||||
|
||||
export default function AuthenticatedImage({
|
||||
questionId,
|
||||
variant = 'original',
|
||||
className,
|
||||
alt = '题目',
|
||||
style,
|
||||
@@ -21,36 +23,58 @@ export default function AuthenticatedImage({
|
||||
let objectUrl: string | null = null
|
||||
let cancelled = false
|
||||
|
||||
api
|
||||
.get(`/wrong-questions/${questionId}/image`, { responseType: 'blob' })
|
||||
.then((res) => {
|
||||
const load = async (path: string, fallback?: string) => {
|
||||
try {
|
||||
const res = await api.get(path, { responseType: 'blob' })
|
||||
if (cancelled) return
|
||||
objectUrl = URL.createObjectURL(res.data)
|
||||
setSrc(objectUrl)
|
||||
setFailed(false)
|
||||
})
|
||||
.catch(() => {
|
||||
if (!cancelled) setFailed(true)
|
||||
})
|
||||
} catch {
|
||||
if (fallback && !cancelled) {
|
||||
await load(fallback)
|
||||
} else if (!cancelled) {
|
||||
setFailed(true)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const annotatedPath = `/wrong-questions/${questionId}/annotated-image`
|
||||
const originalPath = `/wrong-questions/${questionId}/image`
|
||||
|
||||
if (variant === 'annotated') {
|
||||
load(annotatedPath, originalPath)
|
||||
} else {
|
||||
load(originalPath)
|
||||
}
|
||||
|
||||
return () => {
|
||||
cancelled = true
|
||||
if (objectUrl) URL.revokeObjectURL(objectUrl)
|
||||
}
|
||||
}, [questionId])
|
||||
}, [questionId, variant])
|
||||
|
||||
if (failed) {
|
||||
return (
|
||||
<div className={className} style={{ ...style, background: '#fafafa', color: '#999', display: 'flex', alignItems: 'center', justifyContent: 'center', fontSize: 12 }}>
|
||||
<div
|
||||
className={className}
|
||||
style={{
|
||||
...style,
|
||||
background: '#fafafa',
|
||||
color: '#999',
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
fontSize: 12,
|
||||
}}
|
||||
>
|
||||
图片加载失败
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
if (!src) {
|
||||
return (
|
||||
<div className={className} style={{ ...style, background: '#fafafa' }} />
|
||||
)
|
||||
return <div className={className} style={{ ...style, background: '#fafafa' }} />
|
||||
}
|
||||
|
||||
return <img src={src} alt={alt} className={className} style={style} />
|
||||
|
||||
@@ -38,7 +38,7 @@ export default function WrongQuestionList({
|
||||
{items.map((wq) => (
|
||||
<div key={wq.id} className="wq-card">
|
||||
<div className="wq-card-click" onClick={() => onSelect(wq.id)}>
|
||||
<AuthenticatedImage questionId={wq.id} alt="题目" className="wq-card-img" />
|
||||
<AuthenticatedImage questionId={wq.id} variant="annotated" alt="题目" className="wq-card-img" />
|
||||
<div className="wq-card-body">
|
||||
<Typography.Text strong>{wq.subject_name}</Typography.Text>
|
||||
{wq.category === 'olympiad' && (
|
||||
|
||||
Reference in New Issue
Block a user