fix(frontend): replace browser confirm dialogs with AlertDialog

统一删除、撤销与未保存离开等确认操作为 shadcn AlertDialog,避免
window.confirm/alert 打断界面风格;同步更新 WAF 编辑器相关单测与 changelog。
This commit is contained in:
ryan
2026-08-06 13:08:43 +08:00
parent cffa009b8c
commit 484b49d79d
9 changed files with 429 additions and 100 deletions
@@ -37,6 +37,16 @@ import {
DialogHeader,
DialogTitle,
} from '@/components/ui/dialog';
import {
AlertDialog,
AlertDialogAction,
AlertDialogCancel,
AlertDialogContent,
AlertDialogDescription,
AlertDialogFooter,
AlertDialogHeader,
AlertDialogTitle,
} from '@/components/ui/alert-dialog';
import {
Breadcrumb,
BreadcrumbItem,
@@ -50,6 +60,8 @@ import { UserService } from '@/lib/services/user';
import { useAuth } from '@/components/providers/auth-provider';
import { toast } from 'sonner';
type ConfirmTarget = { id: number; name: string };
export function AccessTokenMain() {
const { user } = useAuth();
const queryClient = useQueryClient();
@@ -60,6 +72,12 @@ export function AccessTokenMain() {
const [copiedId, setCopiedId] = React.useState<number | null>(null);
const [newCreatedToken, setNewCreatedToken] =
React.useState<CreateTokenResponse | null>(null);
const [deleteTarget, setDeleteTarget] = React.useState<ConfirmTarget | null>(
null,
);
const [rotateTarget, setRotateTarget] = React.useState<ConfirmTarget | null>(
null,
);
// 获取 Token 列表
const accessTokensQuery = useQuery({
@@ -91,6 +109,7 @@ export function AccessTokenMain() {
const deleteTokenMutation = useMutation({
mutationFn: (id: number) => UserService.deleteAccessToken(id),
onSuccess: () => {
setDeleteTarget(null);
void queryClient.invalidateQueries({
queryKey: ['user', 'access-tokens'],
});
@@ -105,6 +124,7 @@ export function AccessTokenMain() {
const rotateTokenMutation = useMutation({
mutationFn: (id: number) => UserService.rotateAccessToken(id),
onSuccess: (data) => {
setRotateTarget(null);
setNewCreatedToken(data);
setViewDialogOpen(true);
void queryClient.invalidateQueries({
@@ -129,26 +149,6 @@ export function AccessTokenMain() {
});
};
const handleDeleteToken = (id: number, name: string) => {
if (
window.confirm(
`确定要删除并撤销令牌「${name}」吗?删除后此令牌将立即失效且不可恢复。`,
)
) {
deleteTokenMutation.mutate(id);
}
};
const handleRotateToken = (id: number, name: string) => {
if (
window.confirm(
`确定要轮换令牌「${name}」的密钥吗?轮换后系统将生成全新密钥,原令牌密钥将立即失效。`,
)
) {
rotateTokenMutation.mutate(id);
}
};
const handleCopyText = async (text: string, id: number) => {
try {
await navigator.clipboard.writeText(text);
@@ -289,22 +289,8 @@ export function AccessTokenMain() {
size='sm'
className='text-xs border-dashed text-muted-foreground hover:text-primary hover:bg-primary/5 rounded-lg h-8 px-2.5'
onClick={() =>
handleCopyText(token.masked_token, token.id)
setRotateTarget({ id: token.id, name: token.name })
}
>
{copiedId === token.id ? (
<Check className='size-3.5 mr-1 text-emerald-500' />
) : (
<Copy className='size-3.5 mr-1' />
)}
复制
</Button>
<Button
type='button'
variant='outline'
size='sm'
className='text-xs border-dashed text-muted-foreground hover:text-primary hover:bg-primary/5 rounded-lg h-8 px-2.5'
onClick={() => handleRotateToken(token.id, token.name)}
disabled={rotateTokenMutation.isPending}
>
<RefreshCw
@@ -317,7 +303,9 @@ export function AccessTokenMain() {
variant='outline'
size='sm'
className='text-xs border-dashed text-muted-foreground hover:text-rose-500 hover:bg-rose-500/10 hover:border-rose-500/20 rounded-lg h-8 px-2.5'
onClick={() => handleDeleteToken(token.id, token.name)}
onClick={() =>
setDeleteTarget({ id: token.id, name: token.name })
}
disabled={deleteTokenMutation.isPending}
>
<Trash2 className='size-3.5 mr-1' />
@@ -491,6 +479,62 @@ export function AccessTokenMain() {
</DialogFooter>
</DialogContent>
</Dialog>
<AlertDialog
open={Boolean(deleteTarget)}
onOpenChange={(open) => !open && setDeleteTarget(null)}
>
<AlertDialogContent>
<AlertDialogHeader>
<AlertDialogTitle>确认撤销访问令牌</AlertDialogTitle>
<AlertDialogDescription>
确定要删除并撤销令牌「{deleteTarget?.name}
」吗?删除后此令牌将立即失效且不可恢复。
</AlertDialogDescription>
</AlertDialogHeader>
<AlertDialogFooter>
<AlertDialogCancel disabled={deleteTokenMutation.isPending}>
取消
</AlertDialogCancel>
<AlertDialogAction
disabled={deleteTokenMutation.isPending}
onClick={() =>
deleteTarget && deleteTokenMutation.mutate(deleteTarget.id)
}
>
{deleteTokenMutation.isPending ? '撤销中...' : '确认撤销'}
</AlertDialogAction>
</AlertDialogFooter>
</AlertDialogContent>
</AlertDialog>
<AlertDialog
open={Boolean(rotateTarget)}
onOpenChange={(open) => !open && setRotateTarget(null)}
>
<AlertDialogContent>
<AlertDialogHeader>
<AlertDialogTitle>确认轮换访问令牌</AlertDialogTitle>
<AlertDialogDescription>
确定要轮换令牌「{rotateTarget?.name}
」的密钥吗?轮换后系统将生成全新密钥,原令牌密钥将立即失效。
</AlertDialogDescription>
</AlertDialogHeader>
<AlertDialogFooter>
<AlertDialogCancel disabled={rotateTokenMutation.isPending}>
取消
</AlertDialogCancel>
<AlertDialogAction
disabled={rotateTokenMutation.isPending}
onClick={() =>
rotateTarget && rotateTokenMutation.mutate(rotateTarget.id)
}
>
{rotateTokenMutation.isPending ? '轮换中...' : '确认轮换'}
</AlertDialogAction>
</AlertDialogFooter>
</AlertDialogContent>
</AlertDialog>
</motion.div>
);
}