update: del
This commit is contained in:
@@ -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)} 个案件")
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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<CaseStatus, { color: string; label: string }> = {
|
||||
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<React.Key[]>([]);
|
||||
|
||||
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 = () => {
|
||||
>
|
||||
新建案件
|
||||
</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>
|
||||
}
|
||||
>
|
||||
<Table
|
||||
rowKey="id"
|
||||
rowSelection={{
|
||||
selectedRowKeys,
|
||||
onChange: (keys) => setSelectedRowKeys(keys),
|
||||
}}
|
||||
columns={columns}
|
||||
dataSource={cases}
|
||||
loading={isLoading}
|
||||
|
||||
@@ -97,6 +97,19 @@ export async function fetchCase(id: string): Promise<CaseRecord> {
|
||||
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(
|
||||
|
||||
Reference in New Issue
Block a user