fix: add status guards to prevent data loss

- create: auto-delete REJECTED→throw error; add tenantId filter
- remove: forbid PUBLISHED bank deletion
- removeItem: forbid PUBLISHED item deletion
- generateQuestions: restrict to DRAFT status only
- frontend: render MULTIPLE_CHOICE options/judgment/followupHints
- frontend: add judgment and followupHints to QuestionBankItem type
- add 12 service guard tests (109 total)
This commit is contained in:
Developer
2026-05-21 08:55:35 +08:00
parent e782d180d7
commit 57898f939c
4 changed files with 210 additions and 5 deletions
@@ -335,6 +335,33 @@ export default function QuestionBankDetailView() {
<span className={`inline-flex items-center gap-1 px-2.5 py-1 text-[10px] font-black uppercase tracking-widest rounded-full border ${itemStat.bg} ${itemStat.text} ${itemStat.border}`}>{itemStat.icon}{itemStat.label}</span>
</div>
<p className="font-bold text-slate-900 leading-relaxed">{item.questionText}</p>
{item.questionType === 'MULTIPLE_CHOICE' && item.options && item.options.length > 0 && (
<div className="mt-3 space-y-1.5 pl-1 border-l-2 border-blue-200">
{item.options.map((opt, i) => {
const letter = String.fromCharCode(65 + i);
const isCorrect = item.correctAnswer === letter;
return (
<div key={i} className={`flex items-center gap-2 px-3 py-2 rounded-xl text-sm ${isCorrect ? 'bg-emerald-50 border border-emerald-200' : 'bg-slate-50'}`}>
<span className={`inline-flex items-center justify-center w-6 h-6 rounded-lg text-[10px] font-black shrink-0 ${isCorrect ? 'bg-emerald-500 text-white' : 'bg-slate-200 text-slate-500'}`}>{letter}</span>
<span className={`font-medium ${isCorrect ? 'text-emerald-700' : 'text-slate-600'}`}>{opt}</span>
{isCorrect && <Check size={14} className="text-emerald-500 shrink-0 ml-auto" />}
</div>
);
})}
</div>
)}
{item.judgment && (
<div className="mt-3 bg-blue-50/50 border border-blue-100 rounded-xl p-3">
<span className="text-[10px] font-black text-blue-400 uppercase tracking-widest">{item.questionType === 'MULTIPLE_CHOICE' ? '解析' : '判定依据'}</span>
<p className="text-xs text-slate-600 mt-1 leading-relaxed">{item.judgment}</p>
</div>
)}
{item.questionType === 'SHORT_ANSWER' && item.followupHints && item.followupHints.length > 0 && (
<div className="mt-2 flex flex-wrap gap-1.5 items-center">
<span className="text-[10px] font-black text-purple-400 uppercase tracking-widest"></span>
{item.followupHints.map((hint, i) => <span key={i} className="px-2.5 py-1 bg-purple-50 text-purple-600 text-[10px] font-medium rounded-lg border border-purple-100/50">#{i + 1} {hint}</span>)}
</div>
)}
{item.keyPoints.length > 0 && (
<div className="mt-3 flex flex-wrap gap-1.5 items-center">
<span className="text-[10px] font-black text-slate-400 uppercase tracking-widest mr-1">{t('gradingPoints')}</span>