|
|
@@ -1,17 +1,22 @@
|
|
|
-import React, { useState } from 'react';
|
|
|
+import React, { useState, useMemo } from 'react';
|
|
|
import { useQuery } from '@tanstack/react-query';
|
|
|
import { format } from 'date-fns';
|
|
|
import { zhCN } from 'date-fns/locale';
|
|
|
import { zodResolver } from '@hookform/resolvers/zod';
|
|
|
import { useForm } from 'react-hook-form';
|
|
|
import { toast } from 'sonner';
|
|
|
-import { Plus, Search, Edit, Trash2 } from 'lucide-react';
|
|
|
+import { Plus, Search, Edit, Trash2, GitCompare } from 'lucide-react';
|
|
|
|
|
|
import { submissionRecordsClient } from '@/client/api';
|
|
|
-import type { InferResponseType, InferRequestType } from 'hono/client';
|
|
|
+import type {
|
|
|
+ SubmissionRecordsItem,
|
|
|
+ CreateSubmissionRecordsRequest,
|
|
|
+ UpdateSubmissionRecordsRequest
|
|
|
+} from '@/client/admin/types/submission';
|
|
|
import { CreateSubmissionRecordsDto, UpdateSubmissionRecordsDto } from '@/server/modules/submission/submission-records.schema';
|
|
|
|
|
|
import { Button } from '@/client/components/ui/button';
|
|
|
+import { Checkbox } from '@/client/components/ui/checkbox';
|
|
|
import { Input } from '@/client/components/ui/input';
|
|
|
import { Card, CardContent, CardHeader, CardTitle } from '@/client/components/ui/card';
|
|
|
import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from '@/client/components/ui/table';
|
|
|
@@ -21,11 +26,8 @@ import { Skeleton } from '@/client/components/ui/skeleton';
|
|
|
import { AlertDialog, AlertDialogAction, AlertDialogCancel, AlertDialogContent, AlertDialogDescription, AlertDialogFooter, AlertDialogHeader, AlertDialogTitle } from '@/client/components/ui/alert-dialog';
|
|
|
|
|
|
import { DataTablePagination } from '@/client/admin/components/DataTablePagination';
|
|
|
+import { SubmissionComparisonDialog } from '@/client/admin/components/SubmissionComparisonDialog';
|
|
|
|
|
|
-type SubmissionRecordsListResponse = InferResponseType<typeof submissionRecordsClient.$get, 200>;
|
|
|
-type SubmissionRecordsItem = SubmissionRecordsListResponse['data'][0];
|
|
|
-type CreateSubmissionRecordsRequest = InferRequestType<typeof submissionRecordsClient.$post>['json'];
|
|
|
-type UpdateSubmissionRecordsRequest = InferRequestType<typeof submissionRecordsClient[':id']['$put']>['json'];
|
|
|
|
|
|
// 表单schema
|
|
|
const createFormSchema = CreateSubmissionRecordsDto;
|
|
|
@@ -43,6 +45,8 @@ export const SubmissionRecordsPage = () => {
|
|
|
const [editingRecord, setEditingRecord] = useState<any>(null);
|
|
|
const [deleteDialogOpen, setDeleteDialogOpen] = useState(false);
|
|
|
const [recordToDelete, setRecordToDelete] = useState<number | null>(null);
|
|
|
+ const [selectedRecords, setSelectedRecords] = useState<number[]>([]);
|
|
|
+ const [compareDialogOpen, setCompareDialogOpen] = useState(false);
|
|
|
|
|
|
// 创建表单
|
|
|
const createForm = useForm<CreateSubmissionRecordsRequest>({
|
|
|
@@ -209,6 +213,11 @@ export const SubmissionRecordsPage = () => {
|
|
|
}
|
|
|
};
|
|
|
|
|
|
+ // 获取选中的记录数据
|
|
|
+ const selectedRecordsData = useMemo(() => {
|
|
|
+ return records.filter(record => selectedRecords.includes(record.id));
|
|
|
+ }, [records, selectedRecords]);
|
|
|
+
|
|
|
// 加载状态
|
|
|
if (isLoading) {
|
|
|
return (
|
|
|
@@ -244,10 +253,20 @@ export const SubmissionRecordsPage = () => {
|
|
|
{/* 页面标题 */}
|
|
|
<div className="flex justify-between items-center">
|
|
|
<h1 className="text-2xl font-bold">提交记录管理</h1>
|
|
|
- <Button onClick={showCreateModal}>
|
|
|
- <Plus className="mr-2 h-4 w-4" />
|
|
|
- 添加记录
|
|
|
- </Button>
|
|
|
+ <div className="flex gap-2">
|
|
|
+ <Button
|
|
|
+ variant="outline"
|
|
|
+ onClick={() => setCompareDialogOpen(true)}
|
|
|
+ disabled={selectedRecords.length < 2}
|
|
|
+ >
|
|
|
+ <GitCompare className="mr-2 h-4 w-4" />
|
|
|
+ 对比({selectedRecords.length})
|
|
|
+ </Button>
|
|
|
+ <Button onClick={showCreateModal}>
|
|
|
+ <Plus className="mr-2 h-4 w-4" />
|
|
|
+ 添加记录
|
|
|
+ </Button>
|
|
|
+ </div>
|
|
|
</div>
|
|
|
|
|
|
{/* 搜索区域 */}
|
|
|
@@ -276,6 +295,18 @@ export const SubmissionRecordsPage = () => {
|
|
|
<Table>
|
|
|
<TableHeader>
|
|
|
<TableRow>
|
|
|
+ <TableHead className="w-12">
|
|
|
+ <Checkbox
|
|
|
+ checked={selectedRecords.length === records.length && records.length > 0}
|
|
|
+ onCheckedChange={(checked) => {
|
|
|
+ if (checked) {
|
|
|
+ setSelectedRecords(records.map(record => record.id));
|
|
|
+ } else {
|
|
|
+ setSelectedRecords([]);
|
|
|
+ }
|
|
|
+ }}
|
|
|
+ />
|
|
|
+ </TableHead>
|
|
|
<TableHead>ID</TableHead>
|
|
|
<TableHead>教室号</TableHead>
|
|
|
<TableHead>用户</TableHead>
|
|
|
@@ -291,6 +322,18 @@ export const SubmissionRecordsPage = () => {
|
|
|
<TableBody>
|
|
|
{records.map((record) => (
|
|
|
<TableRow key={record.id}>
|
|
|
+ <TableCell>
|
|
|
+ <Checkbox
|
|
|
+ checked={selectedRecords.includes(record.id)}
|
|
|
+ onCheckedChange={(checked) => {
|
|
|
+ if (checked) {
|
|
|
+ setSelectedRecords(prev => [...prev, record.id]);
|
|
|
+ } else {
|
|
|
+ setSelectedRecords(prev => prev.filter(id => id !== record.id));
|
|
|
+ }
|
|
|
+ }}
|
|
|
+ />
|
|
|
+ </TableCell>
|
|
|
<TableCell className="font-medium">{record.id}</TableCell>
|
|
|
<TableCell>{record.classroomNo || '-'}</TableCell>
|
|
|
<TableCell>
|
|
|
@@ -909,6 +952,13 @@ export const SubmissionRecordsPage = () => {
|
|
|
</AlertDialogFooter>
|
|
|
</AlertDialogContent>
|
|
|
</AlertDialog>
|
|
|
+
|
|
|
+ {/* 对比对话框 */}
|
|
|
+ <SubmissionComparisonDialog
|
|
|
+ open={compareDialogOpen}
|
|
|
+ onOpenChange={setCompareDialogOpen}
|
|
|
+ selectedRecords={selectedRecordsData}
|
|
|
+ />
|
|
|
</div>
|
|
|
);
|
|
|
};
|