From 15de400f2f94ef5e28f7333b13d37c074c1eaa66 Mon Sep 17 00:00:00 2001 From: wonfen Date: Wed, 20 May 2026 10:24:11 +0800 Subject: [PATCH] fix: align backup dialogs with in-app modal behavior --- .gitignore | 1 + Changelog.md | 2 + package.json | 1 - pnpm-lock.yaml | 40 ------ src/components/base/base-dialog.tsx | 5 +- src/components/profile/confirm-viewer.tsx | 46 ------- src/components/profile/profile-item.tsx | 17 ++- .../setting/mods/backup-history-viewer.tsx | 120 +++++++++++++----- src/components/setting/mods/backup-viewer.tsx | 13 +- .../setting/mods/clash-core-viewer.tsx | 10 +- src/pages/profiles.tsx | 5 +- 11 files changed, 115 insertions(+), 145 deletions(-) delete mode 100644 src/components/profile/confirm-viewer.tsx diff --git a/.gitignore b/.gitignore index c82526c3c..e7d53e65f 100644 --- a/.gitignore +++ b/.gitignore @@ -17,3 +17,4 @@ CLAUDE.md .vfox.toml .vfox/ .claude +AGENTS.md diff --git a/Changelog.md b/Changelog.md index 269e8683c..bf03ad64c 100644 --- a/Changelog.md +++ b/Changelog.md @@ -2,6 +2,8 @@ ### 🐞 修复问题 +- 备份设置功能异常 +
✨ 新增功能 diff --git a/package.json b/package.json index 177eb679c..596be69bd 100644 --- a/package.json +++ b/package.json @@ -42,7 +42,6 @@ "@juggle/resize-observer": "^3.4.0", "@monaco-editor/react": "^4.7.0", "@mui/icons-material": "^9.0.0", - "@mui/lab": "9.0.0-beta.2", "@mui/material": "^9.0.0", "@tanstack/react-query": "^5.96.1", "@tanstack/react-table": "^8.21.3", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 42685707f..da42f0e45 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -32,9 +32,6 @@ importers: '@mui/icons-material': specifier: ^9.0.0 version: 9.0.0(@mui/material@9.0.0(@emotion/react@11.14.0(@types/react@19.2.14)(react@19.2.5))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.2.14)(react@19.2.5))(@types/react@19.2.14)(react@19.2.5))(@types/react@19.2.14)(react-dom@19.2.5(react@19.2.5))(react@19.2.5))(@types/react@19.2.14)(react@19.2.5) - '@mui/lab': - specifier: 9.0.0-beta.2 - version: 9.0.0-beta.2(@emotion/react@11.14.0(@types/react@19.2.14)(react@19.2.5))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.2.14)(react@19.2.5))(@types/react@19.2.14)(react@19.2.5))(@mui/material@9.0.0(@emotion/react@11.14.0(@types/react@19.2.14)(react@19.2.5))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.2.14)(react@19.2.5))(@types/react@19.2.14)(react@19.2.5))(@types/react@19.2.14)(react-dom@19.2.5(react@19.2.5))(react@19.2.5))(@types/react@19.2.14)(react-dom@19.2.5(react@19.2.5))(react@19.2.5) '@mui/material': specifier: ^9.0.0 version: 9.0.0(@emotion/react@11.14.0(@types/react@19.2.14)(react@19.2.5))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.2.14)(react@19.2.5))(@types/react@19.2.14)(react@19.2.5))(@types/react@19.2.14)(react-dom@19.2.5(react@19.2.5))(react@19.2.5) @@ -1068,27 +1065,6 @@ packages: '@types/react': optional: true - '@mui/lab@9.0.0-beta.2': - resolution: {integrity: sha512-UqykXQCn7HNSExyYBvrFpaaEEmUwHbEgjFDBApEkawVPcBILyYNFhpXoqkwkafiZy+WsvcxIeRF0z4tCgisTRg==} - engines: {node: '>=14.0.0'} - peerDependencies: - '@emotion/react': ^11.5.0 - '@emotion/styled': ^11.3.0 - '@mui/material': ^9.0.0 - '@mui/material-pigment-css': ^9.0.0 - '@types/react': ^17.0.0 || ^18.0.0 || ^19.0.0 - react: ^17.0.0 || ^18.0.0 || ^19.0.0 - react-dom: ^17.0.0 || ^18.0.0 || ^19.0.0 - peerDependenciesMeta: - '@emotion/react': - optional: true - '@emotion/styled': - optional: true - '@mui/material-pigment-css': - optional: true - '@types/react': - optional: true - '@mui/material@9.0.0': resolution: {integrity: sha512-+VP/oQCDhDR87NQQgXnNBG8dwy6GNuQLnenS1pZvkbn2dKFSxRSRMybTpH9xUxXP+316mlYDy5CSbYtusnCWtw==} engines: {node: '>=14.0.0'} @@ -4651,22 +4627,6 @@ snapshots: optionalDependencies: '@types/react': 19.2.14 - '@mui/lab@9.0.0-beta.2(@emotion/react@11.14.0(@types/react@19.2.14)(react@19.2.5))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.2.14)(react@19.2.5))(@types/react@19.2.14)(react@19.2.5))(@mui/material@9.0.0(@emotion/react@11.14.0(@types/react@19.2.14)(react@19.2.5))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.2.14)(react@19.2.5))(@types/react@19.2.14)(react@19.2.5))(@types/react@19.2.14)(react-dom@19.2.5(react@19.2.5))(react@19.2.5))(@types/react@19.2.14)(react-dom@19.2.5(react@19.2.5))(react@19.2.5)': - dependencies: - '@babel/runtime': 7.29.2 - '@mui/material': 9.0.0(@emotion/react@11.14.0(@types/react@19.2.14)(react@19.2.5))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.2.14)(react@19.2.5))(@types/react@19.2.14)(react@19.2.5))(@types/react@19.2.14)(react-dom@19.2.5(react@19.2.5))(react@19.2.5) - '@mui/system': 9.0.0(@emotion/react@11.14.0(@types/react@19.2.14)(react@19.2.5))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.2.14)(react@19.2.5))(@types/react@19.2.14)(react@19.2.5))(@types/react@19.2.14)(react@19.2.5) - '@mui/types': 9.0.0(@types/react@19.2.14) - '@mui/utils': 9.0.0(@types/react@19.2.14)(react@19.2.5) - clsx: 2.1.1 - prop-types: 15.8.1 - react: 19.2.5 - react-dom: 19.2.5(react@19.2.5) - optionalDependencies: - '@emotion/react': 11.14.0(@types/react@19.2.14)(react@19.2.5) - '@emotion/styled': 11.14.1(@emotion/react@11.14.0(@types/react@19.2.14)(react@19.2.5))(@types/react@19.2.14)(react@19.2.5) - '@types/react': 19.2.14 - '@mui/material@9.0.0(@emotion/react@11.14.0(@types/react@19.2.14)(react@19.2.5))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.2.14)(react@19.2.5))(@types/react@19.2.14)(react@19.2.5))(@types/react@19.2.14)(react-dom@19.2.5(react@19.2.5))(react@19.2.5)': dependencies: '@babel/runtime': 7.29.2 diff --git a/src/components/base/base-dialog.tsx b/src/components/base/base-dialog.tsx index 5be2a038a..6f7bd5373 100644 --- a/src/components/base/base-dialog.tsx +++ b/src/components/base/base-dialog.tsx @@ -1,4 +1,3 @@ -import { LoadingButton } from '@mui/lab' import { Button, Dialog, @@ -66,9 +65,9 @@ export const BaseDialog: React.FC = ({ )} {!disableOk && ( - + )} )} diff --git a/src/components/profile/confirm-viewer.tsx b/src/components/profile/confirm-viewer.tsx deleted file mode 100644 index 98e80b97a..000000000 --- a/src/components/profile/confirm-viewer.tsx +++ /dev/null @@ -1,46 +0,0 @@ -import { - Button, - Dialog, - DialogActions, - DialogContent, - DialogTitle, -} from '@mui/material' -import { useEffect } from 'react' -import { useTranslation } from 'react-i18next' - -interface Props { - open: boolean - title: string - message: string - onClose: () => void - onConfirm: () => void -} - -export const ConfirmViewer = (props: Props) => { - const { open, title, message, onClose, onConfirm } = props - - const { t } = useTranslation() - - useEffect(() => { - if (!open) return - }, [open]) - - return ( - - {title} - - - {message} - - - - - - - - ) -} diff --git a/src/components/profile/profile-item.tsx b/src/components/profile/profile-item.tsx index 0c4d960a9..4438831c1 100644 --- a/src/components/profile/profile-item.tsx +++ b/src/components/profile/profile-item.tsx @@ -22,7 +22,7 @@ import dayjs from 'dayjs' import { useCallback, useEffect, useReducer, useRef, useState } from 'react' import { useTranslation } from 'react-i18next' -import { ConfirmViewer } from '@/components/profile/confirm-viewer' +import { BaseDialog } from '@/components/base' import { EditorViewer } from '@/components/profile/editor-viewer' import { GroupsEditorViewer } from '@/components/profile/groups-editor-viewer' import { RulesEditorViewer } from '@/components/profile/rules-editor-viewer' @@ -990,16 +990,23 @@ export const ProfileItem = (props: Props) => { /> )} - setConfirmOpen(false)} onClose={() => setConfirmOpen(false)} - onConfirm={() => { + onOk={() => { onDelete() setConfirmOpen(false) }} - /> + > + + {t('profiles.modals.confirmDelete.message')} + + {qrOpen && itemData.url && ( { - const fn = window.confirm as (msg?: string) => boolean - return fn(message) -} - export const BackupHistoryViewer = ({ open, source, @@ -86,6 +86,9 @@ export const BackupHistoryViewer = ({ const [loading, setLoading] = useState(false) const [isRestoring, setIsRestoring] = useState(false) const [isRestarting, setIsRestarting] = useState(false) + const [isConfirming, setIsConfirming] = useState(false) + const [pendingConfirmation, setPendingConfirmation] = + useState(null) const isLocal = source === 'local' const isWebDavConfigured = Boolean( verge?.webdav_url && verge?.webdav_username && verge?.webdav_password, @@ -94,7 +97,7 @@ export const BackupHistoryViewer = ({ const webdavStatus = getWebdavStatus(webdavSignature) const shouldSkipWebDav = !isLocal && !isWebDavConfigured const pageSize = 8 - const isBusy = loading || isRestoring || isRestarting + const isBusy = loading || isRestoring || isRestarting || isConfirming const buildRow = useCallback( (item: ILocalBackupFile | IWebDavFile): BackupRow | null => { @@ -204,45 +207,54 @@ export const BackupHistoryViewer = ({ }) }, [isLocal, rows, shouldSkipWebDav, t, total, webdavStatus]) - const handleDelete = useLockFn(async (filename: string) => { + const handleDelete = (filename: string) => { if (isRestarting) return - if ( - !(await confirmAsync(t('settings.modals.backup.messages.confirmDelete'))) - ) - return - if (isLocal) { - await deleteLocalBackup(filename) - } else { - await deleteWebdavBackup(filename) - } - await fetchRows() - }) + setPendingConfirmation({ action: 'delete', filename, source }) + } - const handleRestore = useLockFn(async (filename: string) => { + const handleRestore = (filename: string) => { if (isRestoring || isRestarting) return - if ( - !(await confirmAsync(t('settings.modals.backup.messages.confirmRestore'))) - ) - return - setIsRestoring(true) + setPendingConfirmation({ action: 'restore', filename, source }) + } + + const handleConfirmAction = useLockFn(async () => { + if (!pendingConfirmation) return + const { action, filename, source: actionSource } = pendingConfirmation + const actionIsLocal = actionSource === 'local' + setIsConfirming(true) + if (action === 'restore') { + setIsRestoring(true) + } try { - if (isLocal) { - await restoreLocalBackup(filename) + if (action === 'delete') { + if (actionIsLocal) { + await deleteLocalBackup(filename) + } else { + await deleteWebdavBackup(filename) + } + setPendingConfirmation(null) + await fetchRows() } else { - await restoreWebDavBackup(filename) + if (actionIsLocal) { + await restoreLocalBackup(filename) + } else { + await restoreWebDavBackup(filename) + } + setPendingConfirmation(null) + showNotice.success('settings.modals.backup.messages.restoreSuccess') + setIsRestarting(true) + window.setTimeout(() => { + void restartApp().catch((err: unknown) => { + setIsRestarting(false) + showNotice.error(err) + }) + }, 1000) } - showNotice.success('settings.modals.backup.messages.restoreSuccess') - setIsRestarting(true) - window.setTimeout(() => { - void restartApp().catch((err: unknown) => { - setIsRestarting(false) - showNotice.error(err) - }) - }, 1000) } catch (error) { console.error(error) showNotice.error(error) } finally { + setIsConfirming(false) setIsRestoring(false) } }) @@ -267,6 +279,20 @@ export const BackupHistoryViewer = ({ void fetchRows({ force: true }) } + const closeConfirmDialog = () => { + if (isConfirming) return + setPendingConfirmation(null) + } + + const confirmTitle = + pendingConfirmation?.action === 'delete' + ? t('settings.modals.backup.actions.deleteBackup') + : t('settings.modals.backup.actions.restoreBackup') + const confirmMessage = + pendingConfirmation?.action === 'delete' + ? t('settings.modals.backup.messages.confirmDelete') + : t('settings.modals.backup.messages.confirmRestore') + return ( + + + {confirmMessage} + + {pendingConfirmation?.filename && ( + + {pendingConfirmation.filename} + + )} + ) } diff --git a/src/components/setting/mods/backup-viewer.tsx b/src/components/setting/mods/backup-viewer.tsx index 6074e3b26..dcc368dc5 100644 --- a/src/components/setting/mods/backup-viewer.tsx +++ b/src/components/setting/mods/backup-viewer.tsx @@ -1,4 +1,3 @@ -import { LoadingButton } from '@mui/lab' import { Button, List, @@ -156,7 +155,7 @@ export function BackupViewer({ ref }: { ref?: Ref }) { title: t('settings.modals.backup.tabs.local'), description: t('settings.modals.backup.manual.local'), actions: [ - }) { onClick={() => handleBackup('local')} > {t('settings.modals.backup.actions.backup')} - , + , , - }) { onClick={() => handleImport()} > {t('settings.modals.backup.actions.importBackup')} - , + , ], }, { @@ -192,7 +191,7 @@ export function BackupViewer({ ref }: { ref?: Ref }) { title: t('settings.modals.backup.tabs.webdav'), description: t('settings.modals.backup.manual.webdav'), actions: [ - }) { onClick={() => handleBackup('webdav')} > {t('settings.modals.backup.actions.backup')} - , + , } diff --git a/src/pages/profiles.tsx b/src/pages/profiles.tsx index 78743cff4..455be5585 100644 --- a/src/pages/profiles.tsx +++ b/src/pages/profiles.tsx @@ -20,7 +20,6 @@ import { RefreshRounded, TextSnippetOutlined, } from '@mui/icons-material' -import { LoadingButton } from '@mui/lab' import { Box, Button, Divider, Grid, IconButton, Stack } from '@mui/material' import { useQuery } from '@tanstack/react-query' import { listen, TauriEvent } from '@tauri-apps/api/event' @@ -970,7 +969,7 @@ const ProfilePage = () => { }, }} /> - { onClick={onImport} > {t('profiles.page.actions.import')} - +