forked from hangshuo652/aurak
feat: 题库管理功能完善
- QuestionBankView: 添加删除按钮、卡片点击跳转详情页 - QuestionBankDetailView: 新建题库详情页(题目CRUD/AI生成/审核) - questionBankService: 添加generateQuestions方法 - index.tsx: 添加详情页路由
This commit is contained in:
@@ -1,9 +1,15 @@
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import { Plus, BookOpen, ChevronRight } from 'lucide-react';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
import { Plus, BookOpen, ChevronRight, Trash2, Edit2 } from 'lucide-react';
|
||||
import { apiClient } from '../../services/apiClient';
|
||||
import { templateService } from '../../services/templateService';
|
||||
import { questionBankService } from '../../services/questionBankService';
|
||||
import { AssessmentTemplate } from '../../types';
|
||||
|
||||
interface QuestionBankViewProps {
|
||||
isAdmin?: boolean;
|
||||
}
|
||||
|
||||
interface QuestionBank {
|
||||
id: string;
|
||||
name: string;
|
||||
@@ -13,7 +19,8 @@ interface QuestionBank {
|
||||
createdAt: string;
|
||||
}
|
||||
|
||||
export default function QuestionBankView() {
|
||||
export default function QuestionBankView({ isAdmin }: QuestionBankViewProps) {
|
||||
const navigate = useNavigate();
|
||||
const [banks, setBanks] = useState<QuestionBank[]>([]);
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [error, setError] = useState('');
|
||||
@@ -26,6 +33,7 @@ export default function QuestionBankView() {
|
||||
const [saving, setSaving] = useState(false);
|
||||
const [templates, setTemplates] = useState<AssessmentTemplate[]>([]);
|
||||
const [loadingTemplates, setLoadingTemplates] = useState(false);
|
||||
const [deletingId, setDeletingId] = useState<string | null>(null);
|
||||
|
||||
useEffect(() => {
|
||||
fetchData();
|
||||
@@ -86,6 +94,26 @@ export default function QuestionBankView() {
|
||||
}
|
||||
};
|
||||
|
||||
const handleDelete = async (e: React.MouseEvent, bankId: string, bankName: string) => {
|
||||
e.stopPropagation();
|
||||
if (!confirm(`确定要删除题库"${bankName}"吗?此操作不可恢复。`)) return;
|
||||
|
||||
setDeletingId(bankId);
|
||||
try {
|
||||
await questionBankService.deleteBank(bankId);
|
||||
fetchData();
|
||||
} catch (err: any) {
|
||||
console.error('删除失败:', err);
|
||||
alert('删除失败: ' + (err.message || '未知错误'));
|
||||
} finally {
|
||||
setDeletingId(null);
|
||||
}
|
||||
};
|
||||
|
||||
const handleCardClick = (bank: QuestionBank) => {
|
||||
navigate(`/question-banks/${bank.id}`);
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="p-6 bg-white min-h-screen">
|
||||
<div className="flex items-center justify-between mb-6">
|
||||
@@ -111,10 +139,49 @@ export default function QuestionBankView() {
|
||||
) : (
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4">
|
||||
{banks.map((bank) => (
|
||||
<div key={bank.id} className="border rounded-lg p-4">
|
||||
<h3 className="font-semibold">{bank.name}</h3>
|
||||
<p className="text-sm text-gray-500">{bank.description}</p>
|
||||
<p className="text-xs text-gray-400 mt-2">状态: {bank.status}</p>
|
||||
<div
|
||||
key={bank.id}
|
||||
className="border rounded-lg p-4 hover:shadow-md transition-shadow cursor-pointer group relative"
|
||||
onClick={() => handleCardClick(bank)}
|
||||
>
|
||||
<div className="absolute top-3 right-3 flex gap-1 opacity-0 group-hover:opacity-100 transition-opacity">
|
||||
<button
|
||||
onClick={(e) => { e.stopPropagation(); handleCardClick(bank); }}
|
||||
className="p-1.5 text-gray-400 hover:text-blue-600 rounded-md bg-white border shadow-sm"
|
||||
title="编辑"
|
||||
>
|
||||
<Edit2 size={14} />
|
||||
</button>
|
||||
<button
|
||||
onClick={(e) => handleDelete(e, bank.id, bank.name)}
|
||||
disabled={deletingId === bank.id}
|
||||
className="p-1.5 text-gray-400 hover:text-red-600 rounded-md bg-white border shadow-sm disabled:opacity-50"
|
||||
title="删除"
|
||||
>
|
||||
{deletingId === bank.id ? (
|
||||
<span className="w-3.5 h-3.5 border-2 border-red-500 border-t-transparent rounded-full animate-spin block"></span>
|
||||
) : (
|
||||
<Trash2 size={14} />
|
||||
)}
|
||||
</button>
|
||||
</div>
|
||||
<h3 className="font-semibold pr-16">{bank.name}</h3>
|
||||
<p className="text-sm text-gray-500 mt-1">{bank.description || '暂无描述'}</p>
|
||||
<div className="flex items-center justify-between mt-3 pt-3 border-t">
|
||||
<span className={`text-xs px-2 py-0.5 rounded-full ${
|
||||
bank.status === 'PUBLISHED' ? 'bg-green-100 text-green-700' :
|
||||
bank.status === 'PENDING_REVIEW' ? 'bg-yellow-100 text-yellow-700' :
|
||||
bank.status === 'REJECTED' ? 'bg-red-100 text-red-700' :
|
||||
'bg-gray-100 text-gray-600'
|
||||
}`}>
|
||||
{bank.status === 'PUBLISHED' ? '已发布' :
|
||||
bank.status === 'PENDING_REVIEW' ? '待审核' :
|
||||
bank.status === 'REJECTED' ? '已否决' : '草稿'}
|
||||
</span>
|
||||
<span className="text-xs text-gray-400">
|
||||
{new Date(bank.createdAt).toLocaleDateString()}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user