Add exam score import and management workflows
This commit is contained in:
1 parent
71ee21b063
commit
24d2a96144
26 files changed
+3865
No files matched your search
@@ -0,0 +1,11 @@
|
||||
interface BrandMarkProps {
|
||||
large?: boolean
|
||||
}
|
||||
|
||||
export default function BrandMark({ large = false }: BrandMarkProps) {
|
||||
return (
|
||||
<span className={`brand-mark${large ? ' brand-mark--large' : ''}`} aria-hidden="true">
|
||||
{Array.from({ length: 9 }, (_, index) => <span key={index} />)}
|
||||
</span>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,107 @@
|
||||
import { useCallback, useEffect, useState } from 'react'
|
||||
import { Alert, Button, Input, Skeleton } from 'antd'
|
||||
import http from '../api/http'
|
||||
|
||||
interface CaptchaChoice {
|
||||
id: string
|
||||
label: string
|
||||
}
|
||||
|
||||
interface CaptchaChallenge {
|
||||
captchaId: string
|
||||
type: 'TextImage' | 'MathImage' | 'Selection' | 'OneClick'
|
||||
prompt?: string
|
||||
choices?: CaptchaChoice[]
|
||||
imageData?: string
|
||||
}
|
||||
|
||||
interface LoginCaptchaProps {
|
||||
refreshKey: number
|
||||
onChange: (captchaId: string, code: string) => void
|
||||
}
|
||||
|
||||
export default function LoginCaptcha({ refreshKey, onChange }: LoginCaptchaProps) {
|
||||
const [captcha, setCaptcha] = useState<CaptchaChallenge | null>(null)
|
||||
const [code, setCode] = useState('')
|
||||
const [loading, setLoading] = useState(false)
|
||||
const [loadError, setLoadError] = useState(false)
|
||||
|
||||
const refresh = useCallback(async () => {
|
||||
setLoading(true)
|
||||
setLoadError(false)
|
||||
setCode('')
|
||||
onChange('', '')
|
||||
try {
|
||||
const { data } = await http.post<CaptchaChallenge>('/auth/login/captcha', {}, { skipAuthSession: true })
|
||||
setCaptcha(data)
|
||||
} catch {
|
||||
setCaptcha(null)
|
||||
setLoadError(true)
|
||||
} finally {
|
||||
setLoading(false)
|
||||
}
|
||||
}, [onChange])
|
||||
|
||||
useEffect(() => { void refresh() }, [refresh, refreshKey])
|
||||
useEffect(() => { onChange(captcha?.captchaId ?? '', code) }, [captcha, code, onChange])
|
||||
|
||||
const isImageCaptcha = captcha?.type === 'TextImage' || captcha?.type === 'MathImage'
|
||||
|
||||
return (
|
||||
<section className="captcha" aria-label="验证码">
|
||||
<div className="captcha__heading">
|
||||
<span>验证码</span>
|
||||
<button className="captcha__refresh" type="button" disabled={loading} onClick={() => void refresh()}>
|
||||
换一张
|
||||
</button>
|
||||
</div>
|
||||
{loadError ? (
|
||||
<Alert
|
||||
type="warning"
|
||||
showIcon
|
||||
message="验证码暂时无法加载"
|
||||
action={<Button size="small" onClick={() => void refresh()}>重新加载</Button>}
|
||||
/>
|
||||
) : loading && !captcha ? <Skeleton.Input active block style={{ height: 50 }} /> : (
|
||||
<div className="captcha__body" aria-busy={loading}>
|
||||
{isImageCaptcha && (
|
||||
<>
|
||||
{captcha?.imageData && (
|
||||
<img className="captcha__image" src={captcha.imageData} alt="验证码图片,点击可更换" onClick={() => void refresh()} />
|
||||
)}
|
||||
<Input
|
||||
value={code}
|
||||
maxLength={8}
|
||||
autoComplete="off"
|
||||
placeholder="输入图中字符"
|
||||
size="large"
|
||||
onChange={(event) => setCode(event.target.value)}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
{captcha && !isImageCaptcha && (
|
||||
<div className="captcha__question">
|
||||
<p>{captcha.prompt}</p>
|
||||
{captcha.imageData && (
|
||||
<img className="captcha__image" src={captcha.imageData} alt="验证码图片,点击可更换" onClick={() => void refresh()} />
|
||||
)}
|
||||
{captcha.type === 'Selection' ? (
|
||||
<div className="captcha__choices" role="group" aria-label="验证码选项">
|
||||
{(captcha.choices ?? []).map((choice) => (
|
||||
<Button key={choice.id} type={code === choice.id ? 'primary' : 'default'} onClick={() => setCode(choice.id)}>
|
||||
{choice.label}
|
||||
</Button>
|
||||
))}
|
||||
</div>
|
||||
) : (
|
||||
<Button type={code === 'CONFIRM' ? 'primary' : 'default'} onClick={() => setCode('CONFIRM')}>
|
||||
我确认是本人操作
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</section>
|
||||
)
|
||||
}
|
||||
Reference in new issue
Block a user