Commit fd295ed7 authored by 375242562@qq.com's avatar 375242562@qq.com

feat: 删除确认框改为 MUI Dialog 风格,与系统 UI 一致

将批次数据、同步任务、连接配置三处删除操作的 window.confirm()
替换为统一的 MUI Dialog 确认弹窗,包含警告高亮区、详情信息展示
和"确认删除"/"取消"操作按钮。
Co-Authored-By: default avatarClaude Sonnet 4.6 <noreply@anthropic.com>
parent e7e90b1d
import { useState, useEffect, useCallback } from 'react' import React, { useState, useEffect, useCallback } from 'react'
import { import {
Box, Typography, Card, CardContent, Stack, Chip, IconButton, Box, Typography, Card, CardContent, Stack, Chip, IconButton,
Dialog, DialogTitle, DialogContent, DialogActions, Button, Dialog, DialogTitle, DialogContent, DialogActions, Button,
...@@ -22,6 +22,7 @@ import CloudDownloadIcon from '@mui/icons-material/CloudDownload' ...@@ -22,6 +22,7 @@ import CloudDownloadIcon from '@mui/icons-material/CloudDownload'
import RefreshIcon from '@mui/icons-material/Refresh' import RefreshIcon from '@mui/icons-material/Refresh'
import ArrowForwardIcon from '@mui/icons-material/ArrowForward' import ArrowForwardIcon from '@mui/icons-material/ArrowForward'
import TableChartIcon from '@mui/icons-material/TableChart' import TableChartIcon from '@mui/icons-material/TableChart'
import WarningAmberRoundedIcon from '@mui/icons-material/WarningAmberRounded'
import { syncService, SyncSource, SyncSourceCreate, SyncBatch, TARGET_PATIENT_FIELDS, FieldMapping, PreviewResult } from '../services/syncService' import { syncService, SyncSource, SyncSourceCreate, SyncBatch, TARGET_PATIENT_FIELDS, FieldMapping, PreviewResult } from '../services/syncService'
import { connectionProfileService, ConnectionProfile, ConnectionProfileCreate } from '../services/connectionProfileService' import { connectionProfileService, ConnectionProfile, ConnectionProfileCreate } from '../services/connectionProfileService'
...@@ -873,6 +874,13 @@ export function DataSyncPage() { ...@@ -873,6 +874,13 @@ export function DataSyncPage() {
const [batches, setBatches] = useState<SyncBatch[]>([]) const [batches, setBatches] = useState<SyncBatch[]>([])
const [deletingBatch, setDeletingBatch] = useState<string | null>(null) const [deletingBatch, setDeletingBatch] = useState<string | null>(null)
// 确认删除弹窗
const [confirmDialog, setConfirmDialog] = useState<{
title: string
body: React.ReactNode
onConfirm: () => void
} | null>(null)
// 加载数据 // 加载数据
const loadData = useCallback(async () => { const loadData = useCallback(async () => {
try { try {
...@@ -916,42 +924,114 @@ export function DataSyncPage() { ...@@ -916,42 +924,114 @@ export function DataSyncPage() {
} }
// 按批次删除患者数据 // 按批次删除患者数据
const handleDeleteBatch = async (batch: SyncBatch) => { const handleDeleteBatch = (batch: SyncBatch) => {
if (!window.confirm(`确定删除批次「${batch.id.slice(0, 8)}...」的 ${batch.patient_count} 条患者数据吗?\n\n此操作不可撤销,与该批次相关的匹配记录不会被删除。`)) return setConfirmDialog({
setDeletingBatch(batch.id) title: '删除批次数据',
try { body: (
const result = await syncService.deleteBatch(batch.id) <Stack spacing={2}>
showSnack('success', result.message) <Box sx={{ display: 'flex', alignItems: 'flex-start', gap: 1.5, p: 1.5, bgcolor: 'error.50', borderRadius: 1, border: '1px solid', borderColor: 'error.200' }}>
loadData() <WarningAmberRoundedIcon color="error" sx={{ mt: 0.25, flexShrink: 0 }} />
} catch (e: any) { <Box>
showSnack('error', e?.response?.data?.detail || '删除失败') <Typography variant="body2" fontWeight={600} color="error.main" gutterBottom>
} finally { 将删除 {batch.patient_count} 条患者数据,此操作不可撤销
setDeletingBatch(null) </Typography>
} <Typography variant="caption" color="text.secondary">
与该批次相关的 AI 匹配记录不会被删除。
</Typography>
</Box>
</Box>
<Box>
<Typography variant="caption" color="text.secondary">批次号</Typography>
<Typography variant="body2" sx={{ fontFamily: 'monospace', mt: 0.25 }}>{batch.id}</Typography>
</Box>
<Stack direction="row" spacing={3}>
<Box>
<Typography variant="caption" color="text.secondary">来源任务</Typography>
<Typography variant="body2">{batch.source_name}</Typography>
</Box>
<Box>
<Typography variant="caption" color="text.secondary">同步时间</Typography>
<Typography variant="body2">{formatDate(batch.created_at)}</Typography>
</Box>
</Stack>
</Stack>
),
onConfirm: async () => {
setConfirmDialog(null)
setDeletingBatch(batch.id)
try {
const result = await syncService.deleteBatch(batch.id)
showSnack('success', result.message)
loadData()
} catch (e: any) {
showSnack('error', e?.response?.data?.detail || '删除失败')
} finally {
setDeletingBatch(null)
}
},
})
} }
// 删除同步任务 // 删除同步任务
const handleDeleteSource = async (source: SyncSource) => { const handleDeleteSource = (source: SyncSource) => {
if (!window.confirm(`确定删除同步任务「${source.name}」吗?`)) return setConfirmDialog({
try { title: '删除同步任务',
await syncService.deleteSource(source.id) body: (
showSnack('success', '已删除') <Stack spacing={2}>
loadData() <Box sx={{ display: 'flex', alignItems: 'flex-start', gap: 1.5, p: 1.5, bgcolor: 'error.50', borderRadius: 1, border: '1px solid', borderColor: 'error.200' }}>
} catch (e: any) { <WarningAmberRoundedIcon color="error" sx={{ mt: 0.25, flexShrink: 0 }} />
showSnack('error', '删除失败') <Typography variant="body2" color="error.main" fontWeight={600}>
} 删除后将无法恢复,历史同步批次记录不受影响
</Typography>
</Box>
<Box>
<Typography variant="caption" color="text.secondary">任务名称</Typography>
<Typography variant="body2" fontWeight={500}>{source.name}</Typography>
</Box>
</Stack>
),
onConfirm: async () => {
setConfirmDialog(null)
try {
await syncService.deleteSource(source.id)
showSnack('success', '已删除')
loadData()
} catch (e: any) {
showSnack('error', '删除失败')
}
},
})
} }
// 删除连接配置 // 删除连接配置
const handleDeleteProfile = async (profile: ConnectionProfile) => { const handleDeleteProfile = (profile: ConnectionProfile) => {
if (!window.confirm(`确定删除连接配置「${profile.name}」吗?`)) return setConfirmDialog({
try { title: '删除连接配置',
await connectionProfileService.delete(profile.id) body: (
showSnack('success', '已删除') <Stack spacing={2}>
loadData() <Box sx={{ display: 'flex', alignItems: 'flex-start', gap: 1.5, p: 1.5, bgcolor: 'error.50', borderRadius: 1, border: '1px solid', borderColor: 'error.200' }}>
} catch (e: any) { <WarningAmberRoundedIcon color="error" sx={{ mt: 0.25, flexShrink: 0 }} />
showSnack('error', '删除失败') <Typography variant="body2" color="error.main" fontWeight={600}>
} 删除后将无法恢复,关联该配置的同步任务需重新绑定
</Typography>
</Box>
<Box>
<Typography variant="caption" color="text.secondary">配置名称</Typography>
<Typography variant="body2" fontWeight={500}>{profile.name}</Typography>
</Box>
</Stack>
),
onConfirm: async () => {
setConfirmDialog(null)
try {
await connectionProfileService.delete(profile.id)
showSnack('success', '已删除')
loadData()
} catch (e: any) {
showSnack('error', '删除失败')
}
},
})
} }
return ( return (
...@@ -1270,6 +1350,35 @@ export function DataSyncPage() { ...@@ -1270,6 +1350,35 @@ export function DataSyncPage() {
snack={{ snack, show: showSnack, hide: hideSnack }} snack={{ snack, show: showSnack, hide: hideSnack }}
/> />
{/* 确认删除弹窗 */}
<Dialog
open={!!confirmDialog}
onClose={() => setConfirmDialog(null)}
maxWidth="xs"
fullWidth
>
<DialogTitle sx={{ display: 'flex', alignItems: 'center', gap: 1, pb: 1 }}>
<DeleteIcon color="error" fontSize="small" />
<Typography variant="subtitle1" fontWeight={600}>{confirmDialog?.title}</Typography>
</DialogTitle>
<DialogContent sx={{ pt: 1 }}>
{confirmDialog?.body}
</DialogContent>
<DialogActions sx={{ px: 3, pb: 2, gap: 1 }}>
<Button onClick={() => setConfirmDialog(null)} variant="outlined" color="inherit">
取消
</Button>
<Button
variant="contained"
color="error"
startIcon={<DeleteIcon />}
onClick={() => confirmDialog?.onConfirm()}
>
确认删除
</Button>
</DialogActions>
</Dialog>
{/* Snackbar */} {/* Snackbar */}
<Snackbar open={snack.open} autoHideDuration={4000} onClose={hideSnack} anchorOrigin={{ vertical: 'top', horizontal: 'center' }}> <Snackbar open={snack.open} autoHideDuration={4000} onClose={hideSnack} anchorOrigin={{ vertical: 'top', horizontal: 'center' }}>
<Alert onClose={hideSnack} severity={snack.severity} variant="filled">{snack.message}</Alert> <Alert onClose={hideSnack} severity={snack.severity} variant="filled">{snack.message}</Alert>
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment