mirror of
https://github.com/VirtualHotBar/NetMount.git
synced 2026-05-07 05:38:52 +08:00
feat: 更新配置导入导出描述,优化设置页面的导入导出功能
This commit is contained in:
@@ -694,12 +694,13 @@
|
||||
"saving": "Saving...",
|
||||
"data_management": "Data Management",
|
||||
"export_config": "Export Configuration",
|
||||
"export_config_description": "Package current configuration into a ZIP file (excluding logs)",
|
||||
"export_config_description": "Package current configuration into a ZIP file",
|
||||
"export": "Export",
|
||||
"config_exported": "Configuration exported",
|
||||
"import_config": "Import Configuration",
|
||||
"import_config_description": "Restore configuration from ZIP file (requires restart)",
|
||||
"import_config_description": "Restore configuration from ZIP file",
|
||||
"import": "Import",
|
||||
"confirm_import": "Confirm Import",
|
||||
"export_import_config_description": "Export current configuration or restore configuration from file",
|
||||
"confirm_import_description": "Importing configuration will overwrite all current settings and automatically restart the software. Continue?"
|
||||
}
|
||||
|
||||
@@ -701,5 +701,6 @@
|
||||
"import_config_description": "从 ZIP 文件恢复配置",
|
||||
"import": "导入",
|
||||
"confirm_import": "确认导入",
|
||||
"export_import_config_description": "导出当前配置或者从文件恢复配置",
|
||||
"confirm_import_description": "导入配置将覆盖当前所有配置并自动重启软件,是否继续?"
|
||||
}
|
||||
|
||||
@@ -694,12 +694,13 @@
|
||||
"saving": "保存中...",
|
||||
"data_management": "數據管理",
|
||||
"export_config": "導出配置",
|
||||
"export_config_description": "將當前配置打包為 ZIP 文件(排除日誌)",
|
||||
"export_config_description": "將當前配置打包為 ZIP 文件",
|
||||
"export": "導出",
|
||||
"config_exported": "配置已導出",
|
||||
"import_config": "導入配置",
|
||||
"import_config_description": "從 ZIP 文件恢復配置(需要重啓)",
|
||||
"import_config_description": "從 ZIP 文件恢復配置",
|
||||
"import": "導入",
|
||||
"confirm_import": "確認導入",
|
||||
"export_import_config_description": "導出當前配置或者從文件恢復配置",
|
||||
"confirm_import_description": "導入配置將覆蓋當前所有配置並自動重啓軟件,是否繼續?"
|
||||
}
|
||||
|
||||
@@ -245,6 +245,25 @@ pub fn init() -> anyhow::Result<()> {
|
||||
};
|
||||
app.update_app_config()?;
|
||||
|
||||
// 主窗口默认从 tauri.conf 设置为 visible=false,避免前端再隐藏造成闪屏。
|
||||
// 在后端读取配置后再决定是否显示窗口。
|
||||
let start_hide = app.with_app_state::<Config, _>(|config| {
|
||||
config
|
||||
.0
|
||||
.get("settings")
|
||||
.and_then(|s| s.get("startHide"))
|
||||
.and_then(|v| v.as_bool())
|
||||
.unwrap_or(false)
|
||||
});
|
||||
if let Some(window) = app.app_main_window() {
|
||||
if start_hide {
|
||||
let _ = window.hide();
|
||||
} else {
|
||||
let _ = window.show();
|
||||
let _ = window.set_focus();
|
||||
}
|
||||
}
|
||||
|
||||
//开发者工具
|
||||
#[cfg(debug_assertions)]
|
||||
if let Some(window) = app.app_main_window() {
|
||||
|
||||
@@ -68,7 +68,8 @@
|
||||
"minHeight": 450,
|
||||
"minWidth": 700,
|
||||
"transparent": true,
|
||||
"decorations": false
|
||||
"decorations": false,
|
||||
"visible": false
|
||||
}
|
||||
],
|
||||
"trayIcon": {
|
||||
|
||||
@@ -5,7 +5,7 @@ import { RcloneVersion } from "../type/rclone/rcloneInfo"
|
||||
import { startUpdateCont } from "./stats/continue"
|
||||
import { reupMount } from "./storage/mount/mount"
|
||||
import { reupStorage } from "./storage/storage"
|
||||
import { listenWindow, windowsHide } from "./window"
|
||||
import { listenWindow } from "./window"
|
||||
import { sleep } from "../utils/utils"
|
||||
import { t } from "i18next"
|
||||
import { restartRclone, startRclone, stopRclone } from "../utils/rclone/process"
|
||||
@@ -44,10 +44,6 @@ async function init(setStartStr: SetStartStrFn) {
|
||||
setStartStr(t('read_config'))
|
||||
await readNmConfig()
|
||||
|
||||
if (nmConfig.settings.startHide) {
|
||||
windowsHide()
|
||||
}
|
||||
|
||||
//设置语言
|
||||
if (nmConfig.settings.language) {
|
||||
await setLocalized(nmConfig.settings.language);
|
||||
|
||||
@@ -139,7 +139,7 @@ function Home_page() {
|
||||
<div style={{ width: '100%', display: 'flex', justifyContent: 'center', alignItems: 'center' }}>
|
||||
<Space style={{ height: '100%' }}>
|
||||
<Card style={{ width: '10rem', height: '6rem' }} hoverable >
|
||||
<strong ><IconCloud /> {t('storage')}</strong>({isStorageInitPending ? '...' : (isStorageInitFailed ? '--' : storageCount)})<br />
|
||||
<strong ><IconCloud /> {t('storage')}</strong>({isStorageInitPending ? '_' : (isStorageInitFailed ? '-' : storageCount)})<br />
|
||||
<div style={{ paddingTop: '1.3rem', width: '100%', textAlign: 'center' }}>
|
||||
<Space>
|
||||
<Button type='text' onClick={() => { hooks.navigate('/storage/manage/add') }}> {t('add')} </Button>
|
||||
@@ -148,7 +148,7 @@ function Home_page() {
|
||||
</div>
|
||||
</Card>
|
||||
<Card style={{ width: '10rem', height: '6rem' }} hoverable>
|
||||
<strong ><IconStorage /> {t('mount')}</strong>({isStorageInitPending ? '...' : (isStorageInitFailed ? '--' : rcloneInfo.mountList.length)})
|
||||
<strong ><IconStorage /> {t('mount')}</strong>({isStorageInitPending ? '_' : (isStorageInitFailed ? '-' : rcloneInfo.mountList.length)})
|
||||
<div style={{ paddingTop: '1.3rem', width: '100%', textAlign: 'center' }}>
|
||||
<Space>
|
||||
<Button type='text' onClick={() => { hooks.navigate('/mount/add') }} > {t('add')} </Button>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { useEffect, useReducer, useState } from 'react'
|
||||
import { Button, Card, Form, Grid, Input, Link, Message, Modal, Select, Space, Switch } from '@arco-design/web-react'
|
||||
import { Button, Card, Form, Grid, Input, Link, Message, Modal, Select, Space, Switch, Tooltip } from '@arco-design/web-react'
|
||||
import { nmConfig, osInfo, roConfig, saveNmConfig } from '../../services/config';
|
||||
import { getAutostartState, setAutostartState, setThemeMode } from '../../controller/setting/setting';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
@@ -156,113 +156,97 @@ export default function Setting_page() {
|
||||
|
||||
</Card>
|
||||
<Card title={t('data_management')} style={{}} size='small'>
|
||||
<Space direction="vertical" style={{ width: '100%' }}>
|
||||
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}>
|
||||
<div>
|
||||
<div style={{ fontWeight: 500, marginBottom: '0.5rem' }}>{t('export_config')}</div>
|
||||
<div style={{ fontSize: '0.85rem', color: '#86909c' }}>
|
||||
{t('export_config_description')}
|
||||
</div>
|
||||
</div>
|
||||
<Button type="primary" status="success" onClick={async () => {
|
||||
try {
|
||||
const ts = new Date().toISOString().replace(/[:.]/g, '-')
|
||||
const path = await dialog.save({
|
||||
title: t('export_config'),
|
||||
defaultPath: `netmount-config-${ts}.zip`,
|
||||
filters: [{ name: 'Zip', extensions: ['zip'] }],
|
||||
})
|
||||
if (!path) return
|
||||
const out = await invoke<string>('export_config', { outPath: path })
|
||||
Message.success(`${t('config_exported')}: ${out}`)
|
||||
} catch (e) {
|
||||
const msg = (() => {
|
||||
if (typeof e === 'string') return e
|
||||
if (e && typeof e === 'object' && 'message' in e && typeof (e as { message?: unknown }).message === 'string') {
|
||||
return (e as { message: string }).message
|
||||
}
|
||||
try {
|
||||
return JSON.stringify(e)
|
||||
} catch {
|
||||
return String(e)
|
||||
}
|
||||
})()
|
||||
Message.error(msg)
|
||||
}
|
||||
}}>{t('export')}</Button>
|
||||
|
||||
<Space style={{ width: '100%', justifyContent: 'space-between' }} align='center'>
|
||||
<div style={{ display: 'flex', alignItems: 'center' }}>
|
||||
<span style={{ fontWeight: 500 }}>{t('export_import_config_description')}</span>
|
||||
</div>
|
||||
|
||||
<div style={{ height: '1px', backgroundColor: '#e5e6eb', margin: '0.5rem 0' }} />
|
||||
|
||||
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}>
|
||||
<div>
|
||||
<div style={{ fontWeight: 500, marginBottom: '0.5rem' }}>{t('import_config')}</div>
|
||||
<div style={{ fontSize: '0.85rem', color: '#86909c' }}>
|
||||
{t('import_config_description')}
|
||||
</div>
|
||||
</div>
|
||||
<Button type="primary" status="warning" onClick={async () => {
|
||||
try {
|
||||
const path = await dialog.open({
|
||||
title: t('import_config'),
|
||||
multiple: false,
|
||||
filters: [{ name: 'Zip', extensions: ['zip'] }],
|
||||
})
|
||||
if (!path) return
|
||||
|
||||
Modal.confirm({
|
||||
title: t('confirm_import'),
|
||||
content: t('confirm_import_description'),
|
||||
okButtonProps: { status: 'warning' },
|
||||
onOk: async () => {
|
||||
try {
|
||||
// 先停止 rclone 和 openlist,避免文件占用
|
||||
await invoke('stop_components')
|
||||
|
||||
// 短暂等待,确保进程完全停止
|
||||
await new Promise(resolve => setTimeout(resolve, 500))
|
||||
|
||||
// 导入配置
|
||||
const result = await invoke<string>('import_config', { zipPath: path })
|
||||
|
||||
Message.success(result)
|
||||
|
||||
// 延迟重启(不保存当前配置,直接使用导入的配置)
|
||||
setTimeout(() => {
|
||||
invoke('restart_self')
|
||||
}, 1000)
|
||||
} catch (e) {
|
||||
const msg = (() => {
|
||||
if (typeof e === 'string') return e
|
||||
if (e && typeof e === 'object' && 'message' in e && typeof (e as { message?: unknown }).message === 'string') {
|
||||
return (e as { message: string }).message
|
||||
}
|
||||
try {
|
||||
return JSON.stringify(e)
|
||||
} catch {
|
||||
return String(e)
|
||||
}
|
||||
})()
|
||||
Message.error(msg)
|
||||
<Space >
|
||||
<Tooltip content={t('export_config_description')}>
|
||||
<Button type='text' status='success' onClick={async () => {
|
||||
try {
|
||||
const ts = new Date().toISOString().replace(/[:.]/g, '-')
|
||||
const path = await dialog.save({
|
||||
title: t('export_config'),
|
||||
defaultPath: `netmount-config-${ts}.zip`,
|
||||
filters: [{ name: 'Zip', extensions: ['zip'] }],
|
||||
})
|
||||
if (!path) return
|
||||
const out = await invoke<string>('export_config', { outPath: path })
|
||||
Message.success(`${t('config_exported')}: ${out}`)
|
||||
} catch (e) {
|
||||
const msg = (() => {
|
||||
if (typeof e === 'string') return e
|
||||
if (e && typeof e === 'object' && 'message' in e && typeof (e as { message?: unknown }).message === 'string') {
|
||||
return (e as { message: string }).message
|
||||
}
|
||||
},
|
||||
})
|
||||
} catch (e) {
|
||||
const msg = (() => {
|
||||
if (typeof e === 'string') return e
|
||||
if (e && typeof e === 'object' && 'message' in e && typeof (e as { message?: unknown }).message === 'string') {
|
||||
return (e as { message: string }).message
|
||||
}
|
||||
try {
|
||||
return JSON.stringify(e)
|
||||
} catch {
|
||||
return String(e)
|
||||
}
|
||||
})()
|
||||
Message.error(msg)
|
||||
}
|
||||
}}>{t('import')}</Button>
|
||||
</div>
|
||||
try {
|
||||
return JSON.stringify(e)
|
||||
} catch {
|
||||
return String(e)
|
||||
}
|
||||
})()
|
||||
Message.error(msg)
|
||||
}
|
||||
}}>{t('export')}</Button>
|
||||
</Tooltip>
|
||||
<Tooltip content={t('import_config_description')}>
|
||||
<Button type='text' status='warning' onClick={async () => {
|
||||
try {
|
||||
const path = await dialog.open({
|
||||
title: t('import_config'),
|
||||
multiple: false,
|
||||
filters: [{ name: 'Zip', extensions: ['zip'] }],
|
||||
})
|
||||
if (!path) return
|
||||
|
||||
Modal.confirm({
|
||||
title: t('confirm_import'),
|
||||
content: t('confirm_import_description'),
|
||||
okButtonProps: { status: 'warning' },
|
||||
onOk: async () => {
|
||||
try {
|
||||
await invoke('stop_components')
|
||||
await new Promise(resolve => setTimeout(resolve, 500))
|
||||
const result = await invoke<string>('import_config', { zipPath: path })
|
||||
Message.success(result)
|
||||
setTimeout(() => {
|
||||
invoke('restart_self')
|
||||
}, 1000)
|
||||
} catch (e) {
|
||||
const msg = (() => {
|
||||
if (typeof e === 'string') return e
|
||||
if (e && typeof e === 'object' && 'message' in e && typeof (e as { message?: unknown }).message === 'string') {
|
||||
return (e as { message: string }).message
|
||||
}
|
||||
try {
|
||||
return JSON.stringify(e)
|
||||
} catch {
|
||||
return String(e)
|
||||
}
|
||||
})()
|
||||
Message.error(msg)
|
||||
}
|
||||
},
|
||||
})
|
||||
} catch (e) {
|
||||
const msg = (() => {
|
||||
if (typeof e === 'string') return e
|
||||
if (e && typeof e === 'object' && 'message' in e && typeof (e as { message?: unknown }).message === 'string') {
|
||||
return (e as { message: string }).message
|
||||
}
|
||||
try {
|
||||
return JSON.stringify(e)
|
||||
} catch {
|
||||
return String(e)
|
||||
}
|
||||
})()
|
||||
Message.error(msg)
|
||||
}
|
||||
}}>{t('import')}</Button>
|
||||
</Tooltip>
|
||||
</Space>
|
||||
|
||||
</Space>
|
||||
</Card>
|
||||
<Card title={t('components')} style={{}} size='small'>
|
||||
|
||||
Reference in New Issue
Block a user