diff --git a/src/components/question/StatusTransitionDialog.tsx b/src/components/question/StatusTransitionDialog.tsx new file mode 100644 index 0000000..7024390 --- /dev/null +++ b/src/components/question/StatusTransitionDialog.tsx @@ -0,0 +1,74 @@ +import { + AlertDialog, + AlertDialogAction, + AlertDialogCancel, + AlertDialogContent, + AlertDialogDescription, + AlertDialogFooter, + AlertDialogHeader, + AlertDialogTitle, +} from "@/components/ui/alert-dialog" +import { StatusBadge } from "@/components/question/StatusBadge" +import { TRANSITION_LABELS } from "@/lib/constants" +import type { Question, QuestionStatus } from "@/types/question" + +interface StatusTransitionDialogProps { + open: boolean + onOpenChange: (open: boolean) => void + question: Question | null + targetStatus: QuestionStatus | null + onConfirm: () => void +} + +export function StatusTransitionDialog({ + open, + onOpenChange, + question, + targetStatus, + onConfirm, +}: StatusTransitionDialogProps) { + if (!question || !targetStatus) return null + + const label = TRANSITION_LABELS[targetStatus] + const description = getDescription(question.status, targetStatus) + + return ( + + + + {label.title} + + + {description} + + + → + + + + 题目:{question.stem.length > 40 ? question.stem.slice(0, 40) + "..." : question.stem} + + + + + + 取消 + + {label.action} + + + + + ) +} + +function getDescription(from: QuestionStatus, to: QuestionStatus): string { + const descriptions: Record = { + "draft→reviewing": "提交后题目将进入审核队列,等待审核通过后才能发布。", + "reviewing→published": "审核通过后题目将对所有用户可见,请确认题目内容无误。", + "reviewing→draft": "将题目退回草稿状态,可以继续修改后重新提交。", + "published→archived": "下架后题目将对用户不可见,但数据会保留。可随时恢复为草稿。", + "archived→draft": "恢复为草稿后可以重新编辑并提交审核。", + } + return descriptions[`${from}→${to}`] ?? `确定要将题目状态从「${from}」改为「${to}」吗?` +} diff --git a/src/components/question/columns.tsx b/src/components/question/columns.tsx index 52f5380..80a75f5 100644 --- a/src/components/question/columns.tsx +++ b/src/components/question/columns.tsx @@ -14,7 +14,7 @@ import { DropdownMenuTrigger, } from "@/components/ui/dropdown-menu" import { StatusBadge } from "@/components/question/StatusBadge" -import { DIFFICULTY_LABELS, QUESTION_STATUSES } from "@/lib/constants" +import { DIFFICULTY_LABELS, QUESTION_STATUSES, TRANSITION_LABELS } from "@/lib/constants" import type { Question, QuestionStatus } from "@/types/question" import type { Category } from "@/types/category" @@ -37,6 +37,19 @@ function getQuestionStatusesForTransition(current: QuestionStatus): QuestionStat } } +function getPrimaryTransition(current: QuestionStatus): QuestionStatus | null { + switch (current) { + case "draft": + return "reviewing" + case "reviewing": + return "published" + case "published": + return "archived" + case "archived": + return "draft" + } +} + export function getColumns(ctx: ColumnContext): ColumnDef[] { return [ { @@ -79,7 +92,24 @@ export function getColumns(ctx: ColumnContext): ColumnDef[] { { accessorKey: "status", header: "状态", - cell: ({ row }) => , + cell: ({ row }) => { + const status = row.getValue("status") as QuestionStatus + const primary = getPrimaryTransition(status) + return ( + + + {primary && ( + ctx.onStatusChange(row.original, primary)} + > + {TRANSITION_LABELS[primary].title} + + )} + + ) + }, }, { accessorKey: "distractors", diff --git a/src/lib/constants.ts b/src/lib/constants.ts index cda3402..71de342 100644 --- a/src/lib/constants.ts +++ b/src/lib/constants.ts @@ -1,4 +1,5 @@ import type { CategoryStatus } from "@/types/category" +import type { QuestionStatus } from "@/types/question" export const QUESTION_STATUSES = { draft: "草稿", @@ -7,6 +8,13 @@ export const QUESTION_STATUSES = { archived: "已下架", } as const +export const TRANSITION_LABELS: Record = { + draft: { title: "退回草稿", action: "确认退回" }, + reviewing: { title: "提交审核", action: "确认提交" }, + published: { title: "发布题目", action: "确认发布" }, + archived: { title: "下架题目", action: "确认下架" }, +} + export const DIFFICULTY_LABELS: Record = { 1: "入门", 2: "简单", diff --git a/src/routes/questions/index.tsx b/src/routes/questions/index.tsx index 9d955c2..ec12b08 100644 --- a/src/routes/questions/index.tsx +++ b/src/routes/questions/index.tsx @@ -34,6 +34,7 @@ import { AlertDialogTitle, } from "@/components/ui/alert-dialog" import { getColumns } from "@/components/question/columns" +import { StatusTransitionDialog } from "@/components/question/StatusTransitionDialog" import { fetchQuestions, deleteQuestion, updateQuestionStatus } from "@/lib/api/question-api" import { fetchCategories } from "@/lib/api/category-api" import { DIFFICULTY_LABELS, QUESTION_STATUSES } from "@/lib/constants" @@ -57,6 +58,11 @@ export default function QuestionsPage() { const [deleteOpen, setDeleteOpen] = useState(false) const [deletingQuestion, setDeletingQuestion] = useState(null) + // 状态流转对话框 + const [statusDialogOpen, setStatusDialogOpen] = useState(false) + const [statusTarget, setStatusTarget] = useState(null) + const [statusTargetState, setStatusTargetState] = useState(null) + const totalPages = Math.max(1, Math.ceil(total / PAGE_SIZE)) // 加载分类列表(用于筛选和列显示) @@ -99,7 +105,17 @@ export default function QuestionsPage() { } async function handleStatusChange(question: Question, status: QuestionStatus) { - await updateQuestionStatus(question.id, status) + setStatusTarget(question) + setStatusTargetState(status) + setStatusDialogOpen(true) + } + + async function confirmStatusChange() { + if (!statusTarget || !statusTargetState) return + await updateQuestionStatus(statusTarget.id, statusTargetState) + setStatusDialogOpen(false) + setStatusTarget(null) + setStatusTargetState(null) await loadQuestions() } @@ -312,6 +328,15 @@ export default function QuestionsPage() { + + {/* 状态流转确认 */} + ) }
{description}
+ 题目:{question.stem.length > 40 ? question.stem.slice(0, 40) + "..." : question.stem} +