update: del

This commit is contained in:
2026-03-13 23:53:26 +08:00
parent c72fbc9a14
commit bb543eb350
4 changed files with 93 additions and 4 deletions

View File

@@ -1,12 +1,21 @@
from uuid import UUID from uuid import UUID
from datetime import datetime, timezone
from fastapi import APIRouter, Depends, Query from fastapi import APIRouter, Depends, Query
from sqlalchemy import select
from sqlalchemy.ext.asyncio import AsyncSession from sqlalchemy.ext.asyncio import AsyncSession
from app.core.database import get_db from app.core.database import get_db
from app.models.case import Case, CaseStatus from app.models.case import Case, CaseStatus
from app.repositories.case_repo import CaseRepository 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() router = APIRouter()
@@ -41,7 +50,7 @@ async def list_cases(
async def get_case(case_id: UUID, db: AsyncSession = Depends(get_db)): async def get_case(case_id: UUID, db: AsyncSession = Depends(get_db)):
repo = CaseRepository(db) repo = CaseRepository(db)
case = await repo.get(case_id) case = await repo.get(case_id)
if not case: if not case or case.deleted_at is not None:
from fastapi import HTTPException from fastapi import HTTPException
raise HTTPException(status_code=404, detail="案件不存在") raise HTTPException(status_code=404, detail="案件不存在")
return case 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)): async def update_case(case_id: UUID, body: CaseUpdate, db: AsyncSession = Depends(get_db)):
repo = CaseRepository(db) repo = CaseRepository(db)
case = await repo.get(case_id) case = await repo.get(case_id)
if not case: if not case or case.deleted_at is not None:
from fastapi import HTTPException from fastapi import HTTPException
raise HTTPException(status_code=404, detail="案件不存在") raise HTTPException(status_code=404, detail="案件不存在")
case = await repo.update(case, body.model_dump(exclude_unset=True)) case = await repo.update(case, body.model_dump(exclude_unset=True))
return case 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)} 个案件")

View File

@@ -35,3 +35,12 @@ class CaseOut(CamelModel):
class CaseListOut(CamelModel): class CaseListOut(CamelModel):
items: list[CaseOut] items: list[CaseOut]
total: int total: int
class CaseDeleteIn(CamelModel):
case_ids: list[UUID]
class CaseDeleteOut(CamelModel):
deleted: int
message: str

View File

@@ -2,6 +2,7 @@ import React, { useState } from 'react';
import { useNavigate } from 'react-router-dom'; import { useNavigate } from 'react-router-dom';
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query'; import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';
import { import {
App,
Card, Card,
Table, Table,
Button, Button,
@@ -18,6 +19,7 @@ import {
} from 'antd'; } from 'antd';
import { import {
PlusOutlined, PlusOutlined,
DeleteOutlined,
SearchOutlined, SearchOutlined,
FolderOpenOutlined, FolderOpenOutlined,
ClockCircleOutlined, ClockCircleOutlined,
@@ -26,7 +28,7 @@ import {
} from '@ant-design/icons'; } from '@ant-design/icons';
import type { ColumnsType } from 'antd/es/table'; import type { ColumnsType } from 'antd/es/table';
import type { CaseRecord, CaseStatus } from '../../types'; import type { CaseRecord, CaseStatus } from '../../types';
import { fetchCases, createCase } from '../../services/api'; import { fetchCases, createCase, deleteCases } from '../../services/api';
const statusConfig: Record<CaseStatus, { color: string; label: string }> = { const statusConfig: Record<CaseStatus, { color: string; label: string }> = {
pending: { color: 'default', label: '待处理' }, pending: { color: 'default', label: '待处理' },
@@ -48,9 +50,11 @@ const splitDateTime = (raw: string): { date: string; time: string } => {
const CaseList: React.FC = () => { const CaseList: React.FC = () => {
const navigate = useNavigate(); const navigate = useNavigate();
const qc = useQueryClient(); const qc = useQueryClient();
const { modal } = App.useApp();
const [createOpen, setCreateOpen] = useState(false); const [createOpen, setCreateOpen] = useState(false);
const [form] = Form.useForm(); const [form] = Form.useForm();
const [search, setSearch] = useState(''); const [search, setSearch] = useState('');
const [selectedRowKeys, setSelectedRowKeys] = useState<React.Key[]>([]);
const { data, isLoading } = useQuery({ const { data, isLoading } = useQuery({
queryKey: ['cases', search], 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 totalCases = cases.length;
const pendingReview = cases.filter((c) => c.status === 'reviewing').length; const pendingReview = cases.filter((c) => c.status === 'reviewing').length;
const completedCount = cases.filter((c) => c.status === 'completed').length; const completedCount = cases.filter((c) => c.status === 'completed').length;
@@ -221,11 +237,36 @@ const CaseList: React.FC = () => {
> >
</Button> </Button>
<Button
danger
icon={<DeleteOutlined />}
disabled={selectedRowKeys.length === 0}
loading={deleteMutation.isPending}
onClick={() => {
const selectedCases = cases.filter((c) => selectedRowKeys.includes(c.id));
modal.confirm({
title: '确认删除选中案件?',
content: `已选择 ${selectedCases.length} 个案件,删除后将不再显示在案件列表中。`,
okText: '确认删除',
okButtonProps: { danger: true },
cancelText: '取消',
onOk: () => {
deleteMutation.mutate(selectedRowKeys as string[]);
},
});
}}
>
</Button>
</Space> </Space>
} }
> >
<Table <Table
rowKey="id" rowKey="id"
rowSelection={{
selectedRowKeys,
onChange: (keys) => setSelectedRowKeys(keys),
}}
columns={columns} columns={columns}
dataSource={cases} dataSource={cases}
loading={isLoading} loading={isLoading}

View File

@@ -97,6 +97,19 @@ export async function fetchCase(id: string): Promise<CaseRecord> {
return request(`${BASE}/cases/${id}`); 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 ── // ── Images ──
export async function fetchImages( export async function fetchImages(