@@ -21,6 +21,7 @@ from ...services.elevation_service import (
|
||||
analyze_dataset,
|
||||
create_apply_job,
|
||||
create_dataset,
|
||||
delete_dataset,
|
||||
get_job_by_id,
|
||||
list_datasets,
|
||||
list_jobs,
|
||||
@@ -71,6 +72,18 @@ def update_elevation_dataset(
|
||||
return updated
|
||||
|
||||
|
||||
@router.delete("/datasets/{dataset_id}")
|
||||
def delete_elevation_dataset(
|
||||
dataset_id: str,
|
||||
_: CurrentUser = Depends(require_permission("elevation.manage")),
|
||||
db: Session = Depends(get_db),
|
||||
) -> dict[str, bool]:
|
||||
deleted = delete_dataset(db, dataset_id)
|
||||
if not deleted:
|
||||
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="高程数据集不存在")
|
||||
return {"success": True}
|
||||
|
||||
|
||||
@router.post("/datasets/{dataset_id}/analyze", response_model=ElevationDatasetAnalyzeResponse)
|
||||
def analyze_elevation_dataset(
|
||||
dataset_id: str,
|
||||
|
||||
@@ -9,7 +9,7 @@ from tempfile import NamedTemporaryFile
|
||||
from typing import Any
|
||||
|
||||
from fastapi import HTTPException, status
|
||||
from sqlalchemy import func, select
|
||||
from sqlalchemy import delete, func, select
|
||||
from sqlalchemy.orm import Session
|
||||
|
||||
from ..core.database import SessionLocal
|
||||
@@ -261,6 +261,41 @@ def update_dataset(
|
||||
return serialize_dataset(saved)
|
||||
|
||||
|
||||
def delete_dataset(
|
||||
db: Session,
|
||||
dataset_id: str,
|
||||
) -> bool:
|
||||
item = get_dataset_by_id(db, dataset_id)
|
||||
if not item:
|
||||
return False
|
||||
|
||||
running_job_count = int(
|
||||
db.scalar(
|
||||
select(func.count())
|
||||
.select_from(ElevationApplyJob)
|
||||
.where(
|
||||
ElevationApplyJob.dataset_id == dataset_id,
|
||||
ElevationApplyJob.status == "running",
|
||||
)
|
||||
)
|
||||
or 0
|
||||
)
|
||||
if running_job_count > 0:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_409_CONFLICT,
|
||||
detail=f"该数据集存在 {running_job_count} 个运行中的回填任务,暂不能删除",
|
||||
)
|
||||
|
||||
db.execute(delete(ElevationApplyJob).where(ElevationApplyJob.dataset_id == dataset_id))
|
||||
db.delete(item)
|
||||
db.commit()
|
||||
_publish_elevation_change(
|
||||
"elevation.dataset.deleted",
|
||||
{"action": "dataset_deleted", "dataset_id": dataset_id},
|
||||
)
|
||||
return True
|
||||
|
||||
|
||||
def analyze_dataset(
|
||||
db: Session,
|
||||
*,
|
||||
|
||||
@@ -89,3 +89,31 @@
|
||||
- 提交:`556da5c`
|
||||
- 信息:`改造高程预览为地形网格渲染`
|
||||
- 已推送到 `origin/dev`。
|
||||
|
||||
## Work Log - 高程数据集支持删除(2026-05-03)
|
||||
|
||||
- 背景:
|
||||
- Issue `FL-180` 需要“高程数据集支持删除”。
|
||||
- 现有高程管理仅支持创建/更新/分析/预览,缺少删除闭环。
|
||||
|
||||
- 本次改动:
|
||||
- 后端新增数据集删除能力:
|
||||
- 文件:`api/app/services/elevation_service.py`
|
||||
- 新增 `delete_dataset(db, dataset_id)`:
|
||||
- 数据集不存在返回 `False`;
|
||||
- 存在运行中回填任务时返回 `409`,避免删除过程中任务写入异常;
|
||||
- 删除前先清理关联 `elevation_apply_job` 记录,再删除数据集;
|
||||
- 发布 `elevation.dataset.deleted` 主题事件,触发前端数据刷新。
|
||||
- 后端新增删除接口:
|
||||
- 文件:`api/app/api/v1/elevation.py`
|
||||
- 新增 `DELETE /api/v1/elevation/datasets/{dataset_id}`(权限:`elevation.manage`)。
|
||||
- 前端高程管理页新增删除入口:
|
||||
- 文件:`web/src/app/admin/elevation/page.tsx`
|
||||
- 数据集操作列新增“删除”;
|
||||
- 使用 `App.useApp().modal.confirm` 二次确认;
|
||||
- 删除成功后提示并刷新数据集/任务列表,同时清理预览弹窗状态。
|
||||
|
||||
- 风险与影响:
|
||||
- 删除数据集会同时删除其关联的回填任务记录(仅记录,不会回滚已写入杆塔的高程值)。
|
||||
- 若数据集存在运行中任务,接口会拒绝删除并提示先等待任务结束。
|
||||
|
||||
|
||||
@@ -4,6 +4,7 @@ import Link from "next/link";
|
||||
import { useCallback, useMemo, useState } from "react";
|
||||
import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
|
||||
import {
|
||||
App,
|
||||
Alert,
|
||||
Empty,
|
||||
Form,
|
||||
@@ -89,6 +90,7 @@ function formatDate(value: string | null): string {
|
||||
}
|
||||
|
||||
export default function AdminElevationPage() {
|
||||
const { modal } = App.useApp();
|
||||
const queryClient = useQueryClient();
|
||||
const { user, initializing, hasPermission, fetchWithAuth } = useAuth();
|
||||
const [messageApi, messageContextHolder] = message.useMessage();
|
||||
@@ -283,6 +285,33 @@ export default function AdminElevationPage() {
|
||||
},
|
||||
});
|
||||
|
||||
const datasetDeleteMutation = useMutation({
|
||||
mutationFn: async (datasetId: string) => {
|
||||
const response = await fetchWithAuth(`/api/v1/elevation/datasets/${datasetId}`, {
|
||||
method: "DELETE",
|
||||
});
|
||||
if (!response.ok) {
|
||||
throw new Error(await readApiError(response));
|
||||
}
|
||||
},
|
||||
onSuccess: async () => {
|
||||
setSuccess("高程数据集已删除");
|
||||
setError("");
|
||||
messageApi.success("高程数据集已删除");
|
||||
setPreviewModalOpen(false);
|
||||
setPreviewDataset(null);
|
||||
setPreviewData(null);
|
||||
setPreviewLoading(false);
|
||||
await refreshElevationData();
|
||||
},
|
||||
onError: (candidate) => {
|
||||
const nextError = candidate instanceof Error ? candidate.message : "删除高程数据集失败";
|
||||
setError(nextError);
|
||||
setSuccess("");
|
||||
messageApi.error(nextError);
|
||||
},
|
||||
});
|
||||
|
||||
const datasets = datasetsQuery.data?.items ?? [];
|
||||
const jobs = jobsQuery.data?.items ?? [];
|
||||
const lines = linesQuery.data?.items ?? [];
|
||||
@@ -383,11 +412,29 @@ export default function AdminElevationPage() {
|
||||
>
|
||||
{analyzingDatasetId === row.id ? "分析中..." : "分析"}
|
||||
</Typography.Link>
|
||||
<Typography.Link
|
||||
disabled={!canManage || datasetDeleteMutation.isPending}
|
||||
onClick={() => {
|
||||
if (!canManage || datasetDeleteMutation.isPending) return;
|
||||
modal.confirm({
|
||||
title: "删除高程数据集",
|
||||
content: `确认删除数据集「${row.code} - ${row.name}」?该操作会同时删除关联的回填任务记录,且不可恢复。`,
|
||||
okText: "确认删除",
|
||||
okButtonProps: { danger: true, loading: datasetDeleteMutation.isPending },
|
||||
cancelText: "取消",
|
||||
onOk: async () => {
|
||||
await datasetDeleteMutation.mutateAsync(row.id);
|
||||
},
|
||||
});
|
||||
}}
|
||||
>
|
||||
删除
|
||||
</Typography.Link>
|
||||
</Space>
|
||||
),
|
||||
},
|
||||
],
|
||||
[analyzeMutation, analyzingDatasetId, canManage, fetchWithAuth, messageApi],
|
||||
[analyzeMutation, analyzingDatasetId, canManage, datasetDeleteMutation, fetchWithAuth, messageApi, modal],
|
||||
);
|
||||
|
||||
const jobColumns = useMemo<ColumnsType<ElevationApplyJobSummary>>(
|
||||
|
||||
Reference in New Issue
Block a user