Compare commits

..

5 Commits

Author SHA1 Message Date
Memory
9f46ccf99a
feat(tray): reverse click behavior on macOS 2025-10-22 12:49:03 +08:00
Memory
96552778e6
chore: update depends & add missing translation
* update depends

* add missing translation
2025-10-22 12:38:09 +08:00
Memory
55860af9b3
feat: backup & restore 2025-10-22 12:19:16 +08:00
Moon
f67d4150f1
fix: smartcore initial state in template.ts 2025-10-22 11:51:11 +08:00
Moon
605351a498
style: remove divider of the last setting item 2025-10-22 11:50:35 +08:00
15 changed files with 2770 additions and 2582 deletions

View File

@ -32,22 +32,22 @@
"dependencies": { "dependencies": {
"@electron-toolkit/preload": "^3.0.2", "@electron-toolkit/preload": "^3.0.2",
"@electron-toolkit/utils": "^4.0.0", "@electron-toolkit/utils": "^4.0.0",
"@heroui/react": "^2.8.4", "@heroui/react": "^2.8.5",
"@mihomo-party/sysproxy": "^2.0.8", "@mihomo-party/sysproxy": "^2.0.8",
"@mihomo-party/sysproxy-darwin-arm64": "^2.0.8", "@mihomo-party/sysproxy-darwin-arm64": "^2.0.8",
"@types/crypto-js": "^4.2.2", "@types/crypto-js": "^4.2.2",
"adm-zip": "^0.5.16", "adm-zip": "^0.5.16",
"axios": "^1.12.0", "axios": "^1.12.2",
"chart.js": "^4.5.0", "chart.js": "^4.5.1",
"chokidar": "^4.0.3", "chokidar": "^4.0.3",
"croner": "^9.1.0", "croner": "^9.1.0",
"crypto-js": "^4.2.0", "crypto-js": "^4.2.0",
"dayjs": "^1.11.18", "dayjs": "^1.11.18",
"express": "^5.1.0", "express": "^5.1.0",
"i18next": "^25.5.2", "i18next": "^25.6.0",
"iconv-lite": "^0.6.3", "iconv-lite": "^0.6.3",
"react-chartjs-2": "^5.3.0", "react-chartjs-2": "^5.3.0",
"react-i18next": "^15.7.3", "react-i18next": "^15.7.4",
"webdav": "^5.8.0", "webdav": "^5.8.0",
"ws": "^8.18.3", "ws": "^8.18.3",
"yaml": "^2.8.1" "yaml": "^2.8.1"
@ -59,49 +59,49 @@
"@electron-toolkit/eslint-config-prettier": "^3.0.0", "@electron-toolkit/eslint-config-prettier": "^3.0.0",
"@electron-toolkit/eslint-config-ts": "^3.1.0", "@electron-toolkit/eslint-config-ts": "^3.1.0",
"@electron-toolkit/tsconfig": "^1.0.1", "@electron-toolkit/tsconfig": "^1.0.1",
"@tailwindcss/vite": "^4.1.13", "@tailwindcss/vite": "^4.1.15",
"@types/adm-zip": "^0.5.7", "@types/adm-zip": "^0.5.7",
"@types/express": "^5.0.3", "@types/express": "^5.0.3",
"@types/node": "^24.3.1", "@types/node": "^24.9.1",
"@types/pubsub-js": "^1.8.6", "@types/pubsub-js": "^1.8.6",
"@types/react": "^19.1.12", "@types/react": "^19.2.2",
"@types/react-dom": "^19.1.9", "@types/react-dom": "^19.2.2",
"@types/ws": "^8.18.1", "@types/ws": "^8.18.1",
"@vitejs/plugin-react": "^4.7.0", "@vitejs/plugin-react": "^4.7.0",
"cron-validator": "^1.4.0", "cron-validator": "^1.4.0",
"driver.js": "^1.3.6", "driver.js": "^1.3.6",
"electron": "^37.5.0", "electron": "^37.7.0",
"electron-builder": "26.0.12", "electron-builder": "26.0.12",
"electron-vite": "^4.0.0", "electron-vite": "^4.0.1",
"electron-window-state": "^5.0.3", "electron-window-state": "^5.0.3",
"eslint": "9.32.0", "eslint": "9.32.0",
"eslint-plugin-react": "^7.37.5", "eslint-plugin-react": "^7.37.5",
"form-data": "^4.0.4", "form-data": "^4.0.4",
"framer-motion": "12.23.12", "framer-motion": "12.23.12",
"lodash": "^4.17.21", "lodash": "^4.17.21",
"meta-json-schema": "^1.19.13", "meta-json-schema": "^1.19.14",
"monaco-yaml": "^5.4.0", "monaco-yaml": "^5.4.0",
"nanoid": "^5.1.5", "nanoid": "^5.1.6",
"next-themes": "^0.4.6", "next-themes": "^0.4.6",
"postcss": "^8.5.6", "postcss": "^8.5.6",
"prettier": "^3.6.2", "prettier": "^3.6.2",
"pubsub-js": "^1.9.5", "pubsub-js": "^1.9.5",
"react": "^19.1.1", "react": "^19.2.0",
"react-dom": "^19.1.1", "react-dom": "^19.2.0",
"react-error-boundary": "^6.0.0", "react-error-boundary": "^6.0.0",
"react-icons": "^5.5.0", "react-icons": "^5.5.0",
"react-markdown": "^10.1.0", "react-markdown": "^10.1.0",
"react-monaco-editor": "^0.59.0", "react-monaco-editor": "^0.59.0",
"react-router-dom": "^7.8.2", "react-router-dom": "^7.9.4",
"react-virtuoso": "^4.14.0", "react-virtuoso": "^4.14.1",
"swr": "^2.3.6", "swr": "^2.3.6",
"tailwindcss": "^4.1.13", "tailwindcss": "^4.1.15",
"tar": "^7.4.3", "tar": "^7.5.1",
"tsx": "^4.20.5", "tsx": "^4.20.6",
"types-pac": "^1.0.3", "types-pac": "^1.0.3",
"typescript": "^5.9.2", "typescript": "^5.9.3",
"vite": "^7.1.5", "vite": "^7.1.11",
"vite-plugin-monaco-editor": "^1.1.0" "vite-plugin-monaco-editor": "^1.1.0"
}, },
"packageManager": "pnpm@10.15.1" "packageManager": "pnpm@10.19.0"
} }

5051
pnpm-lock.yaml generated

File diff suppressed because it is too large Load Diff

View File

@ -14,6 +14,9 @@ import {
} from '../utils/dirs' } from '../utils/dirs'
import { systemLogger } from '../utils/logger' import { systemLogger } from '../utils/logger'
import { Cron } from 'croner' import { Cron } from 'croner'
import { dialog } from 'electron'
import { existsSync } from 'fs'
import i18next from 'i18next'
let backupCronJob: Cron | null = null let backupCronJob: Cron | null = null
@ -196,3 +199,75 @@ export async function reinitScheduler(): Promise<void> {
await initWebdavBackupScheduler() await initWebdavBackupScheduler()
await systemLogger.info('WebDAV backup scheduler reinitialized successfully') await systemLogger.info('WebDAV backup scheduler reinitialized successfully')
} }
/**
*
*/
export async function exportLocalBackup(): Promise<boolean> {
const zip = new AdmZip()
if (existsSync(appConfigPath())) {
zip.addLocalFile(appConfigPath())
}
if (existsSync(controledMihomoConfigPath())) {
zip.addLocalFile(controledMihomoConfigPath())
}
if (existsSync(profileConfigPath())) {
zip.addLocalFile(profileConfigPath())
}
if (existsSync(overrideConfigPath())) {
zip.addLocalFile(overrideConfigPath())
}
if (existsSync(themesDir())) {
zip.addLocalFolder(themesDir(), 'themes')
}
if (existsSync(profilesDir())) {
zip.addLocalFolder(profilesDir(), 'profiles')
}
if (existsSync(overrideDir())) {
zip.addLocalFolder(overrideDir(), 'override')
}
if (existsSync(subStoreDir())) {
zip.addLocalFolder(subStoreDir(), 'substore')
}
const date = new Date()
const zipFileName = `clash-party-backup-${dayjs(date).format('YYYY-MM-DD_HH-mm-ss')}.zip`
const result = await dialog.showSaveDialog({
title: i18next.t('localBackup.export.title'),
defaultPath: zipFileName,
filters: [
{ name: 'ZIP Files', extensions: ['zip'] },
{ name: 'All Files', extensions: ['*'] }
]
})
if (!result.canceled && result.filePath) {
zip.writeZip(result.filePath)
await systemLogger.info(`Local backup exported to: ${result.filePath}`)
return true
}
return false
}
/**
*
*/
export async function importLocalBackup(): Promise<boolean> {
const result = await dialog.showOpenDialog({
title: i18next.t('localBackup.import.title'),
filters: [
{ name: 'ZIP Files', extensions: ['zip'] },
{ name: 'All Files', extensions: ['*'] }
],
properties: ['openFile']
})
if (!result.canceled && result.filePaths.length > 0) {
const filePath = result.filePaths[0]
const zip = new AdmZip(filePath)
zip.extractAllTo(dataDir(), true)
await systemLogger.info(`Local backup imported from: ${filePath}`)
return true
}
return false
}

View File

@ -356,10 +356,10 @@ export async function createTray(): Promise<void> {
image.setTemplateImage(true) image.setTemplateImage(true)
tray?.setImage(image) tray?.setImage(image)
}) })
tray?.addListener('right-click', async () => { tray?.addListener('click', async () => {
triggerMainWindow() triggerMainWindow()
}) })
tray?.addListener('click', async () => { tray?.addListener('right-click', async () => {
await updateTrayMenu() await updateTrayMenu()
}) })
} }

View File

@ -85,7 +85,7 @@ import {
setupFirewall setupFirewall
} from '../sys/misc' } from '../sys/misc'
import { getRuntimeConfig, getRuntimeConfigStr } from '../core/factory' import { getRuntimeConfig, getRuntimeConfigStr } from '../core/factory'
import { listWebdavBackups, webdavBackup, webdavDelete, webdavRestore } from '../resolve/backup' import { listWebdavBackups, webdavBackup, webdavDelete, webdavRestore, exportLocalBackup, importLocalBackup } from '../resolve/backup'
import { getInterfaces } from '../sys/interface' import { getInterfaces } from '../sys/interface'
import { closeTrayIcon, copyEnv, showTrayIcon, updateTrayIcon, updateTrayIconImmediate } from '../resolve/tray' import { closeTrayIcon, copyEnv, showTrayIcon, updateTrayIcon, updateTrayIconImmediate } from '../resolve/tray'
import { registerShortcut } from '../resolve/shortcut' import { registerShortcut } from '../resolve/shortcut'
@ -250,6 +250,8 @@ export function registerIpcMainHandlers(): void {
ipcMain.handle('listWebdavBackups', ipcErrorWrapper(listWebdavBackups)) ipcMain.handle('listWebdavBackups', ipcErrorWrapper(listWebdavBackups))
ipcMain.handle('webdavDelete', (_e, filename) => ipcErrorWrapper(webdavDelete)(filename)) ipcMain.handle('webdavDelete', (_e, filename) => ipcErrorWrapper(webdavDelete)(filename))
ipcMain.handle('reinitWebdavBackupScheduler', ipcErrorWrapper(reinitScheduler)) ipcMain.handle('reinitWebdavBackupScheduler', ipcErrorWrapper(reinitScheduler))
ipcMain.handle('exportLocalBackup', () => ipcErrorWrapper(exportLocalBackup)())
ipcMain.handle('importLocalBackup', () => ipcErrorWrapper(importLocalBackup)())
ipcMain.handle('registerShortcut', (_e, oldShortcut, newShortcut, action) => ipcMain.handle('registerShortcut', (_e, oldShortcut, newShortcut, action) =>
ipcErrorWrapper(registerShortcut)(oldShortcut, newShortcut, action) ipcErrorWrapper(registerShortcut)(oldShortcut, newShortcut, action)
) )

View File

@ -1,6 +1,6 @@
export const defaultConfig: IAppConfig = { export const defaultConfig: IAppConfig = {
core: 'mihomo', core: 'mihomo',
enableSmartCore: true, enableSmartCore: false,
enableSmartOverride: true, enableSmartOverride: true,
smartCoreUseLightGBM: false, smartCoreUseLightGBM: false,
smartCoreCollectData: false, smartCoreCollectData: false,

View File

@ -130,7 +130,7 @@ const EditInfoModal: React.FC<Props> = (props) => {
} }
} }
}} }}
placeholder="例如30 或 '0 * * * *'" placeholder={t('profiles.editInfo.intervalPlaceholder')}
/> />
{/* 动态提示信息 */} {/* 动态提示信息 */}
@ -142,13 +142,13 @@ const EditInfoModal: React.FC<Props> = (props) => {
: '#6b7280' : '#6b7280'
}}> }}>
{typeof values.interval === 'number' ? ( {typeof values.interval === 'number' ? (
'以分钟为单位的定时间隔' t('profiles.editInfo.intervalMinutes')
) : /^\d+$/.test(values.interval?.toString() || '') ? ( ) : /^\d+$/.test(values.interval?.toString() || '') ? (
'以分钟为单位的定时间隔' t('profiles.editInfo.intervalMinutes')
) : isValidCron(values.interval?.toString() || '', { seconds: false }) ? ( ) : isValidCron(values.interval?.toString() || '', { seconds: false }) ? (
'有效的Cron表达式' t('profiles.editInfo.intervalCron')
) : ( ) : (
'请输入数字或合法的Cron表达式0 * * * *' t('profiles.editInfo.intervalHint')
)} )}
</div> </div>
</div> </div>

View File

@ -0,0 +1,81 @@
import React, { useState } from 'react'
import SettingCard from '../base/base-setting-card'
import SettingItem from '../base/base-setting-item'
import { Button, useDisclosure } from '@heroui/react'
import { exportLocalBackup, importLocalBackup } from '@renderer/utils/ipc'
import { useTranslation } from 'react-i18next'
import BaseConfirmModal from '../base/base-confirm-modal'
const LocalBackupConfig: React.FC = () => {
const { t } = useTranslation()
const { isOpen, onOpen, onClose } = useDisclosure()
const [importing, setImporting] = useState(false)
const [exporting, setExporting] = useState(false)
const handleExport = async (): Promise<void> => {
setExporting(true)
try {
const success = await exportLocalBackup()
if (success) {
new window.Notification(t('localBackup.notification.exportSuccess.title'), {
body: t('localBackup.notification.exportSuccess.body')
})
}
} catch (e) {
alert(e)
} finally {
setExporting(false)
}
}
const handleImport = async (): Promise<void> => {
setImporting(true)
try {
const success = await importLocalBackup()
if (success) {
new window.Notification(t('localBackup.notification.importSuccess.title'), {
body: t('localBackup.notification.importSuccess.body')
})
}
} catch (e) {
alert(t('common.error.importFailed', { error: e }))
} finally {
setImporting(false)
onClose()
}
}
return (
<>
<BaseConfirmModal
isOpen={isOpen}
onCancel={onClose}
onConfirm={handleImport}
title={t('localBackup.import.confirm.title')}
content={t('localBackup.import.confirm.body')}
/>
<SettingCard title={t('localBackup.title')}>
<SettingItem title={t('localBackup.export.title')} divider>
<Button
isLoading={exporting}
size="sm"
onPress={handleExport}
>
{t('localBackup.export.button')}
</Button>
</SettingItem>
<SettingItem title={t('localBackup.import.title')}>
<Button
isLoading={importing}
size="sm"
onPress={onOpen}
>
{t('localBackup.import.button')}
</Button>
</SettingItem>
</SettingCard>
</>
)
}
export default LocalBackupConfig

View File

@ -239,6 +239,17 @@
"webdav.restore.noBackups": "No backups available", "webdav.restore.noBackups": "No backups available",
"webdav.notification.backupSuccess.title": "Backup Successful", "webdav.notification.backupSuccess.title": "Backup Successful",
"webdav.notification.backupSuccess.body": "Backup file has been uploaded to WebDAV", "webdav.notification.backupSuccess.body": "Backup file has been uploaded to WebDAV",
"localBackup.title": "Backup & Restore",
"localBackup.export.title": "Export Local Backup",
"localBackup.export.button": "Export Backup",
"localBackup.import.title": "Import Local Backup",
"localBackup.import.button": "Import Backup",
"localBackup.import.confirm.title": "Confirm Import",
"localBackup.import.confirm.body": "Importing local backup will overwrite all current configurations. Are you sure you want to continue?",
"localBackup.notification.exportSuccess.title": "Export Successful",
"localBackup.notification.exportSuccess.body": "Local backup has been exported to the specified location",
"localBackup.notification.importSuccess.title": "Import Successful",
"localBackup.notification.importSuccess.body": "Local backup has been successfully imported",
"shortcuts.title": "Keyboard Shortcuts", "shortcuts.title": "Keyboard Shortcuts",
"shortcuts.toggleWindow": "Toggle Window", "shortcuts.toggleWindow": "Toggle Window",
"shortcuts.toggleFloatingWindow": "Toggle Floating Window", "shortcuts.toggleFloatingWindow": "Toggle Floating Window",
@ -425,6 +436,11 @@
"profiles.editInfo.url": "Subscription URL", "profiles.editInfo.url": "Subscription URL",
"profiles.editInfo.useProxy": "Use Proxy to Update", "profiles.editInfo.useProxy": "Use Proxy to Update",
"profiles.editInfo.interval": "Upd. Interval", "profiles.editInfo.interval": "Upd. Interval",
"profiles.editInfo.intervalPlaceholder": "e.g.: 30 or '0 * * * *'",
"profiles.editInfo.intervalInvalid": "Invalid",
"profiles.editInfo.intervalMinutes": "Fixed interval in minutes",
"profiles.editInfo.intervalCron": "Valid Cron expression",
"profiles.editInfo.intervalHint": "Please enter a number or a valid Cron expression (e.g.: 0 * * * *)",
"profiles.editInfo.fixedInterval": "Fixed Update Interval", "profiles.editInfo.fixedInterval": "Fixed Update Interval",
"profiles.editInfo.override.title": "Override", "profiles.editInfo.override.title": "Override",
"profiles.editInfo.override.global": "Global", "profiles.editInfo.override.global": "Global",

View File

@ -217,6 +217,17 @@
"webdav.restore.noBackups": "هیچ پشتیبانی موجود نیست", "webdav.restore.noBackups": "هیچ پشتیبانی موجود نیست",
"webdav.notification.backupSuccess.title": "پشتیبان‌گیری موفق", "webdav.notification.backupSuccess.title": "پشتیبان‌گیری موفق",
"webdav.notification.backupSuccess.body": "فایل پشتیبان در WebDAV بارگذاری شد", "webdav.notification.backupSuccess.body": "فایل پشتیبان در WebDAV بارگذاری شد",
"localBackup.title": "پشتیبان‌گیری و بازیابی",
"localBackup.export.title": "صادر کردن پشتیبان محلی",
"localBackup.export.button": "صادر کردن پشتیبان",
"localBackup.import.title": "وارد کردن پشتیبان محلی",
"localBackup.import.button": "وارد کردن پشتیبان",
"localBackup.import.confirm.title": "تایید وارد کردن",
"localBackup.import.confirm.body": "وارد کردن پشتیبان محلی، تمام پیکربندی‌های فعلی را بازنویسی می‌کند. آیا مطمئن هستید که می‌خواهید ادامه دهید؟",
"localBackup.notification.exportSuccess.title": "صادر کردن موفق",
"localBackup.notification.exportSuccess.body": "پشتیبان محلی در مکان مشخص شده صادر شد",
"localBackup.notification.importSuccess.title": "وارد کردن موفق",
"localBackup.notification.importSuccess.body": "پشتیبان محلی با موفقیت وارد شد",
"shortcuts.title": "میانبرهای صفحه کلید", "shortcuts.title": "میانبرهای صفحه کلید",
"shortcuts.toggleWindow": "تغییر وضعیت پنجره", "shortcuts.toggleWindow": "تغییر وضعیت پنجره",
"shortcuts.toggleFloatingWindow": "تغییر وضعیت پنجره شناور", "shortcuts.toggleFloatingWindow": "تغییر وضعیت پنجره شناور",
@ -408,8 +419,13 @@
"profiles.editInfo.fixedInterval": "فاصله به‌روزرسانی ثابت", "profiles.editInfo.fixedInterval": "فاصله به‌روزرسانی ثابت",
"profiles.editInfo.override.title": "جایگزینی", "profiles.editInfo.override.title": "جایگزینی",
"profiles.editInfo.override.global": "جهانی", "profiles.editInfo.override.global": "جهانی",
"profiles.editInfo.override.noAvailable": "جایگزینی در دسترس نیست", "profiles.editInfo.override.noAvailable": "جایگزینی‌ای در دسترس نیست",
"profiles.editInfo.override.add": "افزودن جایگزینی", "profiles.editInfo.override.add": "افزودن جایگزینی",
"profiles.editInfo.intervalPlaceholder": "مثال: 30 یا '0 * * * *'",
"profiles.editInfo.intervalInvalid": "نامعتبر",
"profiles.editInfo.intervalMinutes": "فاصله زمانی ثابت به دقیقه",
"profiles.editInfo.intervalCron": "عبارت Cron معتبر",
"profiles.editInfo.intervalHint": "لطفاً یک عدد یا عبارت Cron معتبر وارد کنید (مثال: 0 * * * *)",
"profiles.editFile.title": "ویرایش پروفایل", "profiles.editFile.title": "ویرایش پروفایل",
"profiles.editFile.notice": "توجه: تغییرات اعمال شده در اینجا پس از به‌روزرسانی پروفایل بازنشانی می‌شوند. برای پیکربندی‌های سفارشی، لطفا از", "profiles.editFile.notice": "توجه: تغییرات اعمال شده در اینجا پس از به‌روزرسانی پروفایل بازنشانی می‌شوند. برای پیکربندی‌های سفارشی، لطفا از",
"profiles.editFile.override": "جایگزینی", "profiles.editFile.override": "جایگزینی",

View File

@ -217,6 +217,17 @@
"webdav.restore.noBackups": "Нет доступных резервных копий", "webdav.restore.noBackups": "Нет доступных резервных копий",
"webdav.notification.backupSuccess.title": "Резервное копирование успешно", "webdav.notification.backupSuccess.title": "Резервное копирование успешно",
"webdav.notification.backupSuccess.body": "Файл резервной копии загружен на WebDAV", "webdav.notification.backupSuccess.body": "Файл резервной копии загружен на WebDAV",
"localBackup.title": "Резервное копирование и восстановление",
"localBackup.export.title": "Экспорт локальной резервной копии",
"localBackup.export.button": "Экспорт резервной копии",
"localBackup.import.title": "Импорт локальной резервной копии",
"localBackup.import.button": "Импорт резервной копии",
"localBackup.import.confirm.title": "Подтвердить импорт",
"localBackup.import.confirm.body": "Импорт локальной резервной копии перезапишет все текущие конфигурации. Вы уверены, что хотите продолжить?",
"localBackup.notification.exportSuccess.title": "Экспорт успешен",
"localBackup.notification.exportSuccess.body": "Локальная резервная копия была экспортирована в указанное место",
"localBackup.notification.importSuccess.title": "Импорт успешен",
"localBackup.notification.importSuccess.body": "Локальная резервная копия была успешно импортирована",
"shortcuts.title": "Горячие клавиши", "shortcuts.title": "Горячие клавиши",
"shortcuts.toggleWindow": "Показать/скрыть окно", "shortcuts.toggleWindow": "Показать/скрыть окно",
"shortcuts.toggleFloatingWindow": "Показать/скрыть плавающее окно", "shortcuts.toggleFloatingWindow": "Показать/скрыть плавающее окно",
@ -410,6 +421,11 @@
"profiles.editInfo.override.global": "Глобальный", "profiles.editInfo.override.global": "Глобальный",
"profiles.editInfo.override.noAvailable": "Нет доступных переопределений", "profiles.editInfo.override.noAvailable": "Нет доступных переопределений",
"profiles.editInfo.override.add": "Добавить переопределение", "profiles.editInfo.override.add": "Добавить переопределение",
"profiles.editInfo.intervalPlaceholder": "например: 30 или '0 * * * *'",
"profiles.editInfo.intervalInvalid": "Недействительно",
"profiles.editInfo.intervalMinutes": "Фиксированный интервал в минутах",
"profiles.editInfo.intervalCron": "Действительное Cron выражение",
"profiles.editInfo.intervalHint": "Пожалуйста, введите число или действительное Cron выражение (например: 0 * * * *)",
"profiles.editFile.title": "Редактировать профиль", "profiles.editFile.title": "Редактировать профиль",
"profiles.editFile.notice": "Примечание: Изменения, сделанные здесь, будут сброшены после обновления профиля. Для пользовательских настроек используйте", "profiles.editFile.notice": "Примечание: Изменения, сделанные здесь, будут сброшены после обновления профиля. Для пользовательских настроек используйте",
"profiles.editFile.override": "Переопределение", "profiles.editFile.override": "Переопределение",

View File

@ -239,6 +239,17 @@
"webdav.restore.noBackups": "还没有备份", "webdav.restore.noBackups": "还没有备份",
"webdav.notification.backupSuccess.title": "备份成功", "webdav.notification.backupSuccess.title": "备份成功",
"webdav.notification.backupSuccess.body": "备份文件已上传到 WebDAV", "webdav.notification.backupSuccess.body": "备份文件已上传到 WebDAV",
"localBackup.title": "备份与恢复",
"localBackup.export.title": "导出本地备份",
"localBackup.export.button": "导出备份",
"localBackup.import.title": "导入本地备份",
"localBackup.import.button": "导入备份",
"localBackup.import.confirm.title": "确认导入",
"localBackup.import.confirm.body": "导入本地备份将会覆盖当前所有配置,确定要继续吗?",
"localBackup.notification.exportSuccess.title": "导出成功",
"localBackup.notification.exportSuccess.body": "本地备份已导出到指定位置",
"localBackup.notification.importSuccess.title": "导入成功",
"localBackup.notification.importSuccess.body": "本地备份已成功导入",
"shortcuts.title": "快捷键设置", "shortcuts.title": "快捷键设置",
"shortcuts.toggleWindow": "打开/关闭窗口", "shortcuts.toggleWindow": "打开/关闭窗口",
"shortcuts.toggleFloatingWindow": "打开/关闭悬浮窗", "shortcuts.toggleFloatingWindow": "打开/关闭悬浮窗",
@ -430,6 +441,11 @@
"profiles.editInfo.url": "订阅地址", "profiles.editInfo.url": "订阅地址",
"profiles.editInfo.useProxy": "使用代理更新", "profiles.editInfo.useProxy": "使用代理更新",
"profiles.editInfo.interval": "更新间隔", "profiles.editInfo.interval": "更新间隔",
"profiles.editInfo.intervalPlaceholder": "例如30 或 '0 * * * *'",
"profiles.editInfo.intervalInvalid": "不合法",
"profiles.editInfo.intervalMinutes": "以分钟为单位的定时间隔",
"profiles.editInfo.intervalCron": "有效的Cron表达式",
"profiles.editInfo.intervalHint": "请输入数字或合法的Cron表达式0 * * * *",
"profiles.editInfo.fixedInterval": "固定更新间隔", "profiles.editInfo.fixedInterval": "固定更新间隔",
"profiles.editInfo.override.title": "覆写", "profiles.editInfo.override.title": "覆写",
"profiles.editInfo.override.global": "全局", "profiles.editInfo.override.global": "全局",

View File

@ -1158,7 +1158,7 @@ const Mihomo: React.FC = () => {
<SelectItem key="debug">{t('mihomo.debug')}</SelectItem> <SelectItem key="debug">{t('mihomo.debug')}</SelectItem>
</Select> </Select>
</SettingItem> </SettingItem>
<SettingItem title={t('mihomo.findProcess')} divider> <SettingItem title={t('mihomo.findProcess')} >
<Select <Select
classNames={{ trigger: 'data-[hover=true]:bg-default-200' }} classNames={{ trigger: 'data-[hover=true]:bg-default-200' }}
className="w-[100px]" className="w-[100px]"

View File

@ -10,6 +10,7 @@ import ShortcutConfig from '@renderer/components/settings/shortcut-config'
import { FaTelegramPlane } from 'react-icons/fa' import { FaTelegramPlane } from 'react-icons/fa'
import SiderConfig from '@renderer/components/settings/sider-config' import SiderConfig from '@renderer/components/settings/sider-config'
import SubStoreConfig from '@renderer/components/settings/substore-config' import SubStoreConfig from '@renderer/components/settings/substore-config'
import LocalBackupConfig from '@renderer/components/settings/local-backup-config'
import { useTranslation } from 'react-i18next' import { useTranslation } from 'react-i18next'
const Settings: React.FC = () => { const Settings: React.FC = () => {
@ -65,6 +66,7 @@ const Settings: React.FC = () => {
<WebdavConfig /> <WebdavConfig />
<MihomoConfig /> <MihomoConfig />
<ShortcutConfig /> <ShortcutConfig />
<LocalBackupConfig />
<Actions /> <Actions />
</BasePage> </BasePage>
) )

View File

@ -353,6 +353,15 @@ export async function reinitWebdavBackupScheduler(): Promise<void> {
) )
} }
// 本地备份相关 IPC 调用
export async function exportLocalBackup(): Promise<boolean> {
return ipcErrorWrapper(await window.electron.ipcRenderer.invoke('exportLocalBackup'))
}
export async function importLocalBackup(): Promise<boolean> {
return ipcErrorWrapper(await window.electron.ipcRenderer.invoke('importLocalBackup'))
}
export async function setTitleBarOverlay(overlay: TitleBarOverlayOptions): Promise<void> { export async function setTitleBarOverlay(overlay: TitleBarOverlayOptions): Promise<void> {
try { try {
return ipcErrorWrapper(await window.electron.ipcRenderer.invoke('setTitleBarOverlay', overlay)) return ipcErrorWrapper(await window.electron.ipcRenderer.invoke('setTitleBarOverlay', overlay))