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,8 +924,40 @@ export function DataSyncPage() { ...@@ -916,8 +924,40 @@ 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({
title: '删除批次数据',
body: (
<Stack spacing={2}>
<Box sx={{ display: 'flex', alignItems: 'flex-start', gap: 1.5, p: 1.5, bgcolor: 'error.50', borderRadius: 1, border: '1px solid', borderColor: 'error.200' }}>
<WarningAmberRoundedIcon color="error" sx={{ mt: 0.25, flexShrink: 0 }} />
<Box>
<Typography variant="body2" fontWeight={600} color="error.main" gutterBottom>
将删除 {batch.patient_count} 条患者数据,此操作不可撤销
</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) setDeletingBatch(batch.id)
try { try {
const result = await syncService.deleteBatch(batch.id) const result = await syncService.deleteBatch(batch.id)
...@@ -928,11 +968,30 @@ export function DataSyncPage() { ...@@ -928,11 +968,30 @@ export function DataSyncPage() {
} finally { } finally {
setDeletingBatch(null) setDeletingBatch(null)
} }
},
})
} }
// 删除同步任务 // 删除同步任务
const handleDeleteSource = async (source: SyncSource) => { const handleDeleteSource = (source: SyncSource) => {
if (!window.confirm(`确定删除同步任务「${source.name}」吗?`)) return setConfirmDialog({
title: '删除同步任务',
body: (
<Stack spacing={2}>
<Box sx={{ display: 'flex', alignItems: 'flex-start', gap: 1.5, p: 1.5, bgcolor: 'error.50', borderRadius: 1, border: '1px solid', borderColor: 'error.200' }}>
<WarningAmberRoundedIcon color="error" sx={{ mt: 0.25, flexShrink: 0 }} />
<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 { try {
await syncService.deleteSource(source.id) await syncService.deleteSource(source.id)
showSnack('success', '已删除') showSnack('success', '已删除')
...@@ -940,11 +999,30 @@ export function DataSyncPage() { ...@@ -940,11 +999,30 @@ export function DataSyncPage() {
} catch (e: any) { } catch (e: any) {
showSnack('error', '删除失败') showSnack('error', '删除失败')
} }
},
})
} }
// 删除连接配置 // 删除连接配置
const handleDeleteProfile = async (profile: ConnectionProfile) => { const handleDeleteProfile = (profile: ConnectionProfile) => {
if (!window.confirm(`确定删除连接配置「${profile.name}」吗?`)) return setConfirmDialog({
title: '删除连接配置',
body: (
<Stack spacing={2}>
<Box sx={{ display: 'flex', alignItems: 'flex-start', gap: 1.5, p: 1.5, bgcolor: 'error.50', borderRadius: 1, border: '1px solid', borderColor: 'error.200' }}>
<WarningAmberRoundedIcon color="error" sx={{ mt: 0.25, flexShrink: 0 }} />
<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 { try {
await connectionProfileService.delete(profile.id) await connectionProfileService.delete(profile.id)
showSnack('success', '已删除') showSnack('success', '已删除')
...@@ -952,6 +1030,8 @@ export function DataSyncPage() { ...@@ -952,6 +1030,8 @@ export function DataSyncPage() {
} catch (e: any) { } catch (e: any) {
showSnack('error', '删除失败') 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