From bb543eb350e7dbd62a5d7b8759a8b10edb8e414b Mon Sep 17 00:00:00 2001 From: ntnt Date: Fri, 13 Mar 2026 23:53:26 +0800 Subject: [PATCH] update: del --- backend/app/api/v1/cases.py | 32 ++++++++++++++++++-- backend/app/schemas/case.py | 9 ++++++ frontend/src/pages/cases/CaseList.tsx | 43 ++++++++++++++++++++++++++- frontend/src/services/api.ts | 13 ++++++++ 4 files changed, 93 insertions(+), 4 deletions(-) diff --git a/backend/app/api/v1/cases.py b/backend/app/api/v1/cases.py index 5ee1839..821231b 100644 --- a/backend/app/api/v1/cases.py +++ b/backend/app/api/v1/cases.py @@ -1,12 +1,21 @@ from uuid import UUID +from datetime import datetime, timezone from fastapi import APIRouter, Depends, Query +from sqlalchemy import select from sqlalchemy.ext.asyncio import AsyncSession from app.core.database import get_db from app.models.case import Case, CaseStatus from app.repositories.case_repo import CaseRepository -from app.schemas.case import CaseCreate, CaseUpdate, CaseOut, CaseListOut +from app.schemas.case import ( + CaseCreate, + CaseUpdate, + CaseOut, + CaseListOut, + CaseDeleteIn, + CaseDeleteOut, +) router = APIRouter() @@ -41,7 +50,7 @@ async def list_cases( async def get_case(case_id: UUID, db: AsyncSession = Depends(get_db)): repo = CaseRepository(db) case = await repo.get(case_id) - if not case: + if not case or case.deleted_at is not None: from fastapi import HTTPException raise HTTPException(status_code=404, detail="案件不存在") return case @@ -51,8 +60,25 @@ async def get_case(case_id: UUID, db: AsyncSession = Depends(get_db)): async def update_case(case_id: UUID, body: CaseUpdate, db: AsyncSession = Depends(get_db)): repo = CaseRepository(db) case = await repo.get(case_id) - if not case: + if not case or case.deleted_at is not None: from fastapi import HTTPException raise HTTPException(status_code=404, detail="案件不存在") case = await repo.update(case, body.model_dump(exclude_unset=True)) return case + + +@router.delete("", response_model=CaseDeleteOut) +async def delete_cases(body: CaseDeleteIn, db: AsyncSession = Depends(get_db)): + if not body.case_ids: + return CaseDeleteOut(deleted=0, message="未选择需要删除的案件") + + result = await db.execute( + select(Case).where(Case.id.in_(body.case_ids), Case.deleted_at.is_(None)) + ) + cases = list(result.scalars().all()) + now = datetime.now(timezone.utc) + for c in cases: + c.deleted_at = now + + await db.flush() + return CaseDeleteOut(deleted=len(cases), message=f"已删除 {len(cases)} 个案件") diff --git a/backend/app/schemas/case.py b/backend/app/schemas/case.py index 506668d..52941d9 100644 --- a/backend/app/schemas/case.py +++ b/backend/app/schemas/case.py @@ -35,3 +35,12 @@ class CaseOut(CamelModel): class CaseListOut(CamelModel): items: list[CaseOut] total: int + + +class CaseDeleteIn(CamelModel): + case_ids: list[UUID] + + +class CaseDeleteOut(CamelModel): + deleted: int + message: str diff --git a/frontend/src/pages/cases/CaseList.tsx b/frontend/src/pages/cases/CaseList.tsx index 0dd6899..42f03bf 100644 --- a/frontend/src/pages/cases/CaseList.tsx +++ b/frontend/src/pages/cases/CaseList.tsx @@ -2,6 +2,7 @@ import React, { useState } from 'react'; import { useNavigate } from 'react-router-dom'; import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query'; import { + App, Card, Table, Button, @@ -18,6 +19,7 @@ import { } from 'antd'; import { PlusOutlined, + DeleteOutlined, SearchOutlined, FolderOpenOutlined, ClockCircleOutlined, @@ -26,7 +28,7 @@ import { } from '@ant-design/icons'; import type { ColumnsType } from 'antd/es/table'; import type { CaseRecord, CaseStatus } from '../../types'; -import { fetchCases, createCase } from '../../services/api'; +import { fetchCases, createCase, deleteCases } from '../../services/api'; const statusConfig: Record = { pending: { color: 'default', label: '待处理' }, @@ -48,9 +50,11 @@ const splitDateTime = (raw: string): { date: string; time: string } => { const CaseList: React.FC = () => { const navigate = useNavigate(); const qc = useQueryClient(); + const { modal } = App.useApp(); const [createOpen, setCreateOpen] = useState(false); const [form] = Form.useForm(); const [search, setSearch] = useState(''); + const [selectedRowKeys, setSelectedRowKeys] = useState([]); const { data, isLoading } = useQuery({ queryKey: ['cases', search], @@ -69,6 +73,18 @@ const CaseList: React.FC = () => { }, }); + const deleteMutation = useMutation({ + mutationFn: (caseIds: string[]) => deleteCases(caseIds), + onSuccess: (res) => { + message.success(res.message || `已删除 ${res.deleted} 个案件`); + setSelectedRowKeys([]); + qc.invalidateQueries({ queryKey: ['cases'] }); + }, + onError: () => { + message.error('删除案件失败'); + }, + }); + const totalCases = cases.length; const pendingReview = cases.filter((c) => c.status === 'reviewing').length; const completedCount = cases.filter((c) => c.status === 'completed').length; @@ -221,11 +237,36 @@ const CaseList: React.FC = () => { > 新建案件 + } > setSelectedRowKeys(keys), + }} columns={columns} dataSource={cases} loading={isLoading} diff --git a/frontend/src/services/api.ts b/frontend/src/services/api.ts index 8813253..2152574 100644 --- a/frontend/src/services/api.ts +++ b/frontend/src/services/api.ts @@ -97,6 +97,19 @@ export async function fetchCase(id: string): Promise { return request(`${BASE}/cases/${id}`); } +export async function deleteCases(caseIds: string[]): Promise<{ deleted: number; message: string }> { + if (!(await isBackendUp())) { + return { + deleted: caseIds.length, + message: `Mock 模式:模拟删除 ${caseIds.length} 个案件`, + }; + } + return request(`${BASE}/cases`, { + method: 'DELETE', + body: JSON.stringify({ case_ids: caseIds }), + }); +} + // ── Images ── export async function fetchImages(