From b2c17e3eca7facb5a831c7bed9098d57910bdf05 Mon Sep 17 00:00:00 2001 From: Developer Date: Wed, 13 May 2026 21:51:33 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E9=A2=98=E5=BA=93=E7=AE=A1=E7=90=86?= =?UTF-8?q?=E5=8A=9F=E8=83=BD=E5=AE=8C=E5=96=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - QuestionBankView: 添加删除按钮、卡片点击跳转详情页 - QuestionBankDetailView: 新建题库详情页(题目CRUD/AI生成/审核) - questionBankService: 添加generateQuestions方法 - index.tsx: 添加详情页路由 --- .../views/QuestionBankDetailView.tsx | 547 ++++++++++++++++++ web/components/views/QuestionBankView.tsx | 79 ++- web/index.tsx | 2 + web/services/questionBankService.ts | 10 + 4 files changed, 632 insertions(+), 6 deletions(-) create mode 100644 web/components/views/QuestionBankDetailView.tsx diff --git a/web/components/views/QuestionBankDetailView.tsx b/web/components/views/QuestionBankDetailView.tsx new file mode 100644 index 0000000..8d5f2bb --- /dev/null +++ b/web/components/views/QuestionBankDetailView.tsx @@ -0,0 +1,547 @@ +import React, { useState, useEffect } from 'react'; +import { useParams, useNavigate } from 'react-router-dom'; +import { + ChevronLeft, Plus, Sparkles, Send, Check, X, + Trash2, Edit2, FileText +} from 'lucide-react'; +import { questionBankService, QuestionBank, QuestionBankItem, CreateQuestionBankItemDto } from '../../services/questionBankService'; +import { templateService } from '../../services/templateService'; +import { AssessmentTemplate } from '../../types'; + +const QUESTION_TYPES = [ + { value: 'SHORT_ANSWER', label: '简答题' }, + { value: 'MULTIPLE_CHOICE', label: '选择题' }, + { value: 'TRUE_FALSE', label: '判断题' }, +]; + +const DIFFICULTIES = [ + { value: 'STANDARD', label: '标准' }, + { value: 'ADVANCED', label: '高级' }, + { value: 'SPECIALIST', label: '专家' }, +]; + +const DIMENSIONS = [ + { value: 'PROMPT', label: 'Prompt' }, + { value: 'LLM', label: 'LLM' }, + { value: 'IDE', label: 'IDE' }, + { value: 'DEV_PATTERN', label: '开发模式' }, + { value: 'WORK_CAPABILITY', label: '工作能力' }, +]; + +export default function QuestionBankDetailView() { + const { id: bankId } = useParams<{ id: string }>(); + const navigate = useNavigate(); + + if (!bankId) { + return ( +
+ +
无效的题库ID
+
+ ); + } + + const [bank, setBank] = useState(null); + const [items, setItems] = useState([]); + const [templates, setTemplates] = useState([]); + const [loading, setLoading] = useState(true); + const [saving, setSaving] = useState(false); + const [error, setError] = useState(''); + + const [showAddItem, setShowAddItem] = useState(false); + const [showGenerate, setShowGenerate] = useState(false); + const [editingItem, setEditingItem] = useState(null); + + const [itemForm, setItemForm] = useState({ + questionText: '', + questionType: 'SHORT_ANSWER', + keyPoints: [], + difficulty: 'STANDARD', + dimension: 'WORK_CAPABILITY', + }); + const [keyPointsInput, setKeyPointsInput] = useState(''); + + const [generateForm, setGenerateForm] = useState({ + count: 5, + knowledgeBaseContent: '', + }); + const [generating, setGenerating] = useState(false); + + useEffect(() => { + fetchData(); + fetchTemplates(); + }, [bankId]); + + const fetchData = async () => { + try { + setLoading(true); + const bankData = await questionBankService.getBank(bankId); + setBank(bankData); + const itemsData = await questionBankService.getBankItems(bankId); + setItems(itemsData); + } catch (err: any) { + setError(err.message || '加载失败'); + } finally { + setLoading(false); + } + }; + + const fetchTemplates = async () => { + try { + const data = await templateService.getAll(); + setTemplates(data); + } catch (err) { + console.error('加载模板失败:', err); + } + }; + + const handleCreateItem = async (e: React.FormEvent) => { + e.preventDefault(); + if (!itemForm.questionText.trim()) return; + + setSaving(true); + try { + const payload = { + ...itemForm, + keyPoints: keyPointsInput.split('\n').filter(k => k.trim()), + }; + await questionBankService.createItem(bankId, payload); + setShowAddItem(false); + setItemForm({ + questionText: '', + questionType: 'SHORT_ANSWER', + keyPoints: [], + difficulty: 'STANDARD', + dimension: 'WORK_CAPABILITY', + }); + setKeyPointsInput(''); + fetchData(); + } catch (err: any) { + alert('创建失败: ' + (err.message || '未知错误')); + } finally { + setSaving(false); + } + }; + + const handleUpdateItem = async (e: React.FormEvent) => { + e.preventDefault(); + if (!editingItem || !itemForm.questionText.trim()) return; + + setSaving(true); + try { + const payload = { + ...itemForm, + keyPoints: keyPointsInput.split('\n').filter(k => k.trim()), + }; + await questionBankService.updateItem(bankId, editingItem.id, payload); + setEditingItem(null); + setItemForm({ + questionText: '', + questionType: 'SHORT_ANSWER', + keyPoints: [], + difficulty: 'STANDARD', + dimension: 'WORK_CAPABILITY', + }); + setKeyPointsInput(''); + fetchData(); + } catch (err: any) { + alert('更新失败: ' + (err.message || '未知错误')); + } finally { + setSaving(false); + } + }; + + const handleDeleteItem = async (itemId: string) => { + if (!confirm('确定要删除这道题目吗?')) return; + try { + await questionBankService.deleteItem(bankId, itemId); + fetchData(); + } catch (err: any) { + alert('删除失败: ' + (err.message || '未知错误')); + } + }; + + const handleGenerate = async () => { + setGenerating(true); + try { + await questionBankService.generateQuestions(bankId, generateForm.count, generateForm.knowledgeBaseContent); + setShowGenerate(false); + setGenerateForm({ count: 5, knowledgeBaseContent: '' }); + fetchData(); + } catch (err: any) { + alert('生成失败: ' + (err.message || '未知错误')); + } finally { + setGenerating(false); + } + }; + + const handleSubmitForReview = async () => { + if (!confirm('确定要提交审核吗?')) return; + try { + await questionBankService.submitForReview(bankId); + fetchData(); + } catch (err: any) { + alert('提交失败: ' + (err.message || '未知错误')); + } + }; + + const handlePublish = async () => { + if (!confirm('确定要发布题库吗?')) return; + try { + await questionBankService.publishBank(bankId); + fetchData(); + } catch (err: any) { + alert('发布失败: ' + (err.message || '未知错误')); + } + }; + + const handleApproveItem = async (itemId: string) => { + try { + await questionBankService.updateItem(bankId, itemId, { status: 'PUBLISHED' as any }); + fetchData(); + } catch (err: any) { + alert('操作失败: ' + (err.message || '未知错误')); + } + }; + + const openEditItem = (item: QuestionBankItem) => { + setEditingItem(item); + setItemForm({ + questionText: item.questionText, + questionType: item.questionType, + options: item.options || [], + keyPoints: item.keyPoints, + difficulty: item.difficulty, + dimension: item.dimension, + }); + setKeyPointsInput(item.keyPoints.join('\n')); + }; + + const getStatusBadge = (status: string) => { + switch (status) { + case 'PUBLISHED': + return 已发布; + case 'PENDING_REVIEW': + return 待审核; + default: + return 草稿; + } + }; + + if (loading) { + return ( +
+
+
+ ); + } + + if (error) { + return ( +
+ +
加载失败: {error}
+
+ ); + } + + const pendingItems = items.filter(i => i.status === 'PENDING_REVIEW'); + const publishedItems = items.filter(i => i.status === 'PUBLISHED'); + + return ( +
+ + +
+
+

{bank?.name}

+

{bank?.description || '暂无描述'}

+
+ + 模板: {templates.find(t => t.id === bank?.templateId)?.name || '未关联'} + + {getStatusBadge(bank?.status || 'DRAFT')} +
+
+
+ {bank?.status === 'DRAFT' && ( + + )} + {bank?.status === 'PENDING_REVIEW' && ( + + )} + +
+
+ +
+
+
{items.length}
+
总题目数
+
+
+
{pendingItems.length}
+
待审核
+
+
+
{publishedItems.length}
+
已发布
+
+
+ +
+

题目列表

+ +
+ + {items.length === 0 ? ( +
+ +

暂无题目,点击上方按钮添加或使用AI生成

+
+ ) : ( +
+ {items.map((item) => ( +
+
+
+
+ + {QUESTION_TYPES.find(t => t.value === item.questionType)?.label} + + + {DIFFICULTIES.find(d => d.value === item.difficulty)?.label} + + + {DIMENSIONS.find(d => d.value === item.dimension)?.label} + + {getStatusBadge(item.status)} +
+

{item.questionText}

+ {item.keyPoints.length > 0 && ( +
+ 评分要点: + {item.keyPoints.map((kp, i) => ( + • {kp} + ))} +
+ )} + {item.basis && ( +
+ 依据:{item.basis} +
+ )} +
+
+ {item.status === 'PENDING_REVIEW' && ( + + )} + + +
+
+
+ ))} +
+ )} + + {showAddItem && ( +
{ setShowAddItem(false); setEditingItem(null); }} /> + )} +
+
+
+

+ {editingItem ? '编辑题目' : '添加题目'} +

+ +
+
+
+
+ +