♻️ Optimize code
This commit is contained in:
@@ -10,6 +10,9 @@
|
||||
// @ts-ignore: Unused imports
|
||||
import {Call as $Call, Create as $Create} from "@wailsio/runtime";
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
// @ts-ignore: Unused imports
|
||||
import * as application$0 from "../../../github.com/wailsapp/wails/v3/pkg/application/models.js";
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
// @ts-ignore: Unused imports
|
||||
import * as models$0 from "../models/models.js";
|
||||
@@ -38,22 +41,6 @@ export function GetConfig(): Promise<models$0.AppConfig | null> & { cancel(): vo
|
||||
return $typingPromise;
|
||||
}
|
||||
|
||||
/**
|
||||
* GetConfigDir 获取配置目录
|
||||
*/
|
||||
export function GetConfigDir(): Promise<string> & { cancel(): void } {
|
||||
let $resultPromise = $Call.ByID(2275626561) as any;
|
||||
return $resultPromise;
|
||||
}
|
||||
|
||||
/**
|
||||
* GetSettingsPath 获取设置文件路径
|
||||
*/
|
||||
export function GetSettingsPath(): Promise<string> & { cancel(): void } {
|
||||
let $resultPromise = $Call.ByID(2175583370) as any;
|
||||
return $resultPromise;
|
||||
}
|
||||
|
||||
/**
|
||||
* MigrateConfig 执行配置迁移
|
||||
*/
|
||||
@@ -78,6 +65,14 @@ export function ServiceShutdown(): Promise<void> & { cancel(): void } {
|
||||
return $resultPromise;
|
||||
}
|
||||
|
||||
/**
|
||||
* ServiceStartup initializes the service when the application starts
|
||||
*/
|
||||
export function ServiceStartup(options: application$0.ServiceOptions): Promise<void> & { cancel(): void } {
|
||||
let $resultPromise = $Call.ByID(3311949428, options) as any;
|
||||
return $resultPromise;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set 设置配置项
|
||||
*/
|
||||
|
||||
@@ -268,9 +268,6 @@ export class OSInfo {
|
||||
|
||||
/**
|
||||
* ObserverCallback 观察者回调函数
|
||||
* 参数:
|
||||
* - oldValue: 配置变更前的值
|
||||
* - newValue: 配置变更后的值
|
||||
*/
|
||||
export type ObserverCallback = any;
|
||||
|
||||
|
||||
@@ -1,175 +1,49 @@
|
||||
import { defineStore } from 'pinia';
|
||||
import { computed, readonly, ref, shallowRef, watchEffect, onScopeDispose } from 'vue';
|
||||
import type { GitBackupConfig } from '@/../bindings/voidraft/internal/models';
|
||||
import { ref, onScopeDispose } from 'vue';
|
||||
import { BackupService } from '@/../bindings/voidraft/internal/services';
|
||||
import { useConfigStore } from '@/stores/configStore';
|
||||
import { createTimerManager } from '@/common/utils/timerUtils';
|
||||
|
||||
// 备份状态枚举
|
||||
export enum BackupStatus {
|
||||
IDLE = 'idle',
|
||||
PUSHING = 'pushing',
|
||||
SUCCESS = 'success',
|
||||
ERROR = 'error'
|
||||
}
|
||||
|
||||
// 备份操作结果类型
|
||||
export interface BackupResult {
|
||||
status: BackupStatus;
|
||||
message?: string;
|
||||
timestamp?: number;
|
||||
}
|
||||
|
||||
// 类型守卫函数
|
||||
const isBackupError = (error: unknown): error is Error => {
|
||||
return error instanceof Error;
|
||||
};
|
||||
|
||||
// 工具类型:提取错误消息
|
||||
type ErrorMessage<T> = T extends Error ? string : string;
|
||||
|
||||
|
||||
export const useBackupStore = defineStore('backup', () => {
|
||||
// === 核心状态 ===
|
||||
const config = shallowRef<GitBackupConfig | null>(null);
|
||||
|
||||
// 统一的备份结果状态
|
||||
const backupResult = ref<BackupResult>({
|
||||
status: BackupStatus.IDLE
|
||||
});
|
||||
|
||||
// === 定时器管理 ===
|
||||
const statusTimer = createTimerManager();
|
||||
const isPushing = ref(false);
|
||||
const message = ref<string | null>(null);
|
||||
const isError = ref(false);
|
||||
|
||||
// 组件卸载时清理定时器
|
||||
onScopeDispose(() => {
|
||||
statusTimer.clear();
|
||||
});
|
||||
|
||||
// === 外部依赖 ===
|
||||
const timer = createTimerManager();
|
||||
const configStore = useConfigStore();
|
||||
|
||||
// === 计算属性 ===
|
||||
const isEnabled = computed(() => configStore.config.backup.enabled);
|
||||
const isConfigured = computed(() => Boolean(configStore.config.backup.repo_url?.trim()));
|
||||
|
||||
// 派生状态计算属性
|
||||
const isPushing = computed(() => backupResult.value.status === BackupStatus.PUSHING);
|
||||
const isSuccess = computed(() => backupResult.value.status === BackupStatus.SUCCESS);
|
||||
const isError = computed(() => backupResult.value.status === BackupStatus.ERROR);
|
||||
const errorMessage = computed(() =>
|
||||
backupResult.value.status === BackupStatus.ERROR ? backupResult.value.message : null
|
||||
);
|
||||
onScopeDispose(() => timer.clear());
|
||||
|
||||
// === 状态管理方法 ===
|
||||
|
||||
/**
|
||||
* 设置备份状态
|
||||
* @param status 备份状态
|
||||
* @param message 可选消息
|
||||
* @param autoHide 是否自动隐藏(毫秒)
|
||||
*/
|
||||
const setBackupStatus = <T extends BackupStatus>(
|
||||
status: T,
|
||||
message?: T extends BackupStatus.ERROR ? string : string,
|
||||
autoHide?: number
|
||||
): void => {
|
||||
statusTimer.clear();
|
||||
const pushToRemote = async () => {
|
||||
const isConfigured = Boolean(configStore.config.backup.repo_url?.trim());
|
||||
|
||||
backupResult.value = {
|
||||
status,
|
||||
message,
|
||||
timestamp: Date.now()
|
||||
};
|
||||
|
||||
// 自动隐藏逻辑
|
||||
if (autoHide && (status === BackupStatus.SUCCESS || status === BackupStatus.ERROR)) {
|
||||
statusTimer.set(() => {
|
||||
if (backupResult.value.status === status) {
|
||||
backupResult.value = { status: BackupStatus.IDLE };
|
||||
}
|
||||
}, autoHide);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* 清除当前状态
|
||||
*/
|
||||
const clearStatus = (): void => {
|
||||
statusTimer.clear();
|
||||
backupResult.value = { status: BackupStatus.IDLE };
|
||||
};
|
||||
|
||||
/**
|
||||
* 处理错误的通用方法
|
||||
*/
|
||||
const handleError = (error: unknown): void => {
|
||||
const message: ErrorMessage<typeof error> = isBackupError(error)
|
||||
? error.message
|
||||
: 'Backup operation failed';
|
||||
|
||||
setBackupStatus(BackupStatus.ERROR, message, 5000);
|
||||
};
|
||||
|
||||
// === 业务逻辑方法 ===
|
||||
|
||||
/**
|
||||
* 推送到远程仓库
|
||||
* 使用现代 async/await 和错误处理
|
||||
*/
|
||||
const pushToRemote = async (): Promise<void> => {
|
||||
// 前置条件检查
|
||||
if (isPushing.value || !isConfigured.value) {
|
||||
if (isPushing.value || !isConfigured) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
setBackupStatus(BackupStatus.PUSHING);
|
||||
isPushing.value = true;
|
||||
message.value = null;
|
||||
timer.clear();
|
||||
|
||||
await BackupService.PushToRemote();
|
||||
|
||||
setBackupStatus(BackupStatus.SUCCESS, 'Backup completed successfully', 3000);
|
||||
isError.value = false;
|
||||
message.value = 'push successful';
|
||||
timer.set(() => { message.value = null; }, 3000);
|
||||
} catch (error) {
|
||||
handleError(error);
|
||||
isError.value = true;
|
||||
message.value = error instanceof Error ? error.message : 'backup operation failed';
|
||||
timer.set(() => { message.value = null; }, 5000);
|
||||
} finally {
|
||||
isPushing.value = false;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* 重试备份操作
|
||||
*/
|
||||
const retryBackup = async (): Promise<void> => {
|
||||
if (isError.value) {
|
||||
await pushToRemote();
|
||||
}
|
||||
};
|
||||
|
||||
// === 响应式副作用 ===
|
||||
|
||||
// 监听配置变化,自动清除错误状态
|
||||
watchEffect(() => {
|
||||
if (isEnabled.value && isConfigured.value && isError.value) {
|
||||
// 配置修复后清除错误状态
|
||||
clearStatus();
|
||||
}
|
||||
});
|
||||
|
||||
// === 返回的 API ===
|
||||
return {
|
||||
// 只读状态
|
||||
config: readonly(config),
|
||||
backupResult: readonly(backupResult),
|
||||
|
||||
// 计算属性
|
||||
isEnabled,
|
||||
isConfigured,
|
||||
isPushing,
|
||||
isSuccess,
|
||||
message,
|
||||
isError,
|
||||
errorMessage,
|
||||
|
||||
// 方法
|
||||
pushToRemote,
|
||||
retryBackup,
|
||||
clearStatus
|
||||
} as const;
|
||||
pushToRemote
|
||||
};
|
||||
});
|
||||
@@ -2,6 +2,7 @@
|
||||
defineProps<{
|
||||
title: string;
|
||||
description?: string;
|
||||
descriptionType?: 'default' | 'success' | 'error';
|
||||
}>();
|
||||
</script>
|
||||
|
||||
@@ -9,7 +10,16 @@ defineProps<{
|
||||
<div class="setting-item">
|
||||
<div class="setting-info">
|
||||
<div class="setting-title">{{ title }}</div>
|
||||
<div v-if="description" class="setting-description">{{ description }}</div>
|
||||
<div
|
||||
v-if="description"
|
||||
class="setting-description"
|
||||
:class="{
|
||||
'description-success': descriptionType === 'success',
|
||||
'description-error': descriptionType === 'error'
|
||||
}"
|
||||
>
|
||||
{{ description }}
|
||||
</div>
|
||||
</div>
|
||||
<div class="setting-control">
|
||||
<slot></slot>
|
||||
@@ -48,6 +58,14 @@ defineProps<{
|
||||
font-size: 11px;
|
||||
color: var(--settings-text-secondary);
|
||||
line-height: 1.4;
|
||||
|
||||
&.description-success {
|
||||
color: #4caf50;
|
||||
}
|
||||
|
||||
&.description-error {
|
||||
color: #f44336;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
import {useConfigStore} from '@/stores/configStore';
|
||||
import {useBackupStore} from '@/stores/backupStore';
|
||||
import {useI18n} from 'vue-i18n';
|
||||
import {computed, onUnmounted} from 'vue';
|
||||
import {computed} from 'vue';
|
||||
import SettingSection from '../components/SettingSection.vue';
|
||||
import SettingItem from '../components/SettingItem.vue';
|
||||
import ToggleSwitch from '../components/ToggleSwitch.vue';
|
||||
@@ -13,18 +13,12 @@ const {t} = useI18n();
|
||||
const configStore = useConfigStore();
|
||||
const backupStore = useBackupStore();
|
||||
|
||||
onUnmounted(() => {
|
||||
backupStore.clearStatus();
|
||||
});
|
||||
|
||||
// 认证方式选项
|
||||
const authMethodOptions = computed(() => [
|
||||
{value: AuthMethod.Token, label: t('settings.backup.authMethods.token')},
|
||||
{value: AuthMethod.SSHKey, label: t('settings.backup.authMethods.sshKey')},
|
||||
{value: AuthMethod.UserPass, label: t('settings.backup.authMethods.userPass')}
|
||||
]);
|
||||
|
||||
// 备份间隔选项(分钟)
|
||||
const backupIntervalOptions = computed(() => [
|
||||
{value: 5, label: t('settings.backup.intervals.5min')},
|
||||
{value: 10, label: t('settings.backup.intervals.10min')},
|
||||
@@ -33,124 +27,11 @@ const backupIntervalOptions = computed(() => [
|
||||
{value: 60, label: t('settings.backup.intervals.1hour')}
|
||||
]);
|
||||
|
||||
// 计算属性 - 启用备份
|
||||
const enableBackup = computed({
|
||||
get: () => configStore.config.backup.enabled,
|
||||
set: (value: boolean) => configStore.setEnableBackup(value)
|
||||
});
|
||||
|
||||
// 计算属性 - 自动备份
|
||||
const autoBackup = computed({
|
||||
get: () => configStore.config.backup.auto_backup,
|
||||
set: (value: boolean) => configStore.setAutoBackup(value)
|
||||
});
|
||||
|
||||
// 仓库URL
|
||||
const repoUrl = computed({
|
||||
get: () => configStore.config.backup.repo_url,
|
||||
set: (value: string) => configStore.setRepoUrl(value)
|
||||
});
|
||||
|
||||
|
||||
// 认证方式
|
||||
const authMethod = computed({
|
||||
get: () => configStore.config.backup.auth_method,
|
||||
set: (value: AuthMethod) => configStore.setAuthMethod(value)
|
||||
});
|
||||
|
||||
// 备份间隔
|
||||
const backupInterval = computed({
|
||||
get: () => configStore.config.backup.backup_interval,
|
||||
set: (value: number) => configStore.setBackupInterval(value)
|
||||
});
|
||||
|
||||
// 用户名
|
||||
const username = computed({
|
||||
get: () => configStore.config.backup.username,
|
||||
set: (value: string) => configStore.setUsername(value)
|
||||
});
|
||||
|
||||
// 密码
|
||||
const password = computed({
|
||||
get: () => configStore.config.backup.password,
|
||||
set: (value: string) => configStore.setPassword(value)
|
||||
});
|
||||
|
||||
// 访问令牌
|
||||
const token = computed({
|
||||
get: () => configStore.config.backup.token,
|
||||
set: (value: string) => configStore.setToken(value)
|
||||
});
|
||||
|
||||
// SSH密钥路径
|
||||
const sshKeyPath = computed({
|
||||
get: () => configStore.config.backup.ssh_key_path,
|
||||
set: (value: string) => configStore.setSshKeyPath(value)
|
||||
});
|
||||
|
||||
// SSH密钥密码
|
||||
const sshKeyPassphrase = computed({
|
||||
get: () => configStore.config.backup.ssh_key_passphrase,
|
||||
set: (value: string) => configStore.setSshKeyPassphrase(value)
|
||||
});
|
||||
|
||||
// 处理输入变化
|
||||
const handleRepoUrlChange = (event: Event) => {
|
||||
const target = event.target as HTMLInputElement;
|
||||
repoUrl.value = target.value;
|
||||
};
|
||||
|
||||
|
||||
const handleUsernameChange = (event: Event) => {
|
||||
const target = event.target as HTMLInputElement;
|
||||
username.value = target.value;
|
||||
};
|
||||
|
||||
const handlePasswordChange = (event: Event) => {
|
||||
const target = event.target as HTMLInputElement;
|
||||
password.value = target.value;
|
||||
};
|
||||
|
||||
const handleTokenChange = (event: Event) => {
|
||||
const target = event.target as HTMLInputElement;
|
||||
token.value = target.value;
|
||||
};
|
||||
|
||||
const handleSshKeyPassphraseChange = (event: Event) => {
|
||||
const target = event.target as HTMLInputElement;
|
||||
sshKeyPassphrase.value = target.value;
|
||||
};
|
||||
|
||||
const handleAuthMethodChange = (event: Event) => {
|
||||
const target = event.target as HTMLSelectElement;
|
||||
authMethod.value = target.value as AuthMethod;
|
||||
};
|
||||
|
||||
const handleBackupIntervalChange = (event: Event) => {
|
||||
const target = event.target as HTMLSelectElement;
|
||||
backupInterval.value = parseInt(target.value);
|
||||
};
|
||||
|
||||
// 推送到远程
|
||||
const pushToRemote = async () => {
|
||||
await backupStore.pushToRemote();
|
||||
};
|
||||
|
||||
// 重试备份
|
||||
const retryBackup = async () => {
|
||||
await backupStore.retryBackup();
|
||||
};
|
||||
|
||||
// 选择SSH密钥文件
|
||||
const selectSshKeyFile = async () => {
|
||||
// 使用DialogService选择文件
|
||||
const selectedPath = await DialogService.SelectFile();
|
||||
// 检查用户是否取消了选择或路径为空
|
||||
if (!selectedPath.trim()) {
|
||||
return;
|
||||
if (selectedPath.trim()) {
|
||||
configStore.setSshKeyPath(selectedPath.trim());
|
||||
}
|
||||
// 更新SSH密钥路径
|
||||
sshKeyPath.value = selectedPath.trim();
|
||||
};
|
||||
</script>
|
||||
|
||||
@@ -158,34 +39,35 @@ const selectSshKeyFile = async () => {
|
||||
<div class="settings-page">
|
||||
<!-- 基本设置 -->
|
||||
<SettingSection :title="t('settings.backup.basicSettings')">
|
||||
<SettingItem
|
||||
:title="t('settings.backup.enableBackup')"
|
||||
>
|
||||
<ToggleSwitch v-model="enableBackup"/>
|
||||
<SettingItem :title="t('settings.backup.enableBackup')">
|
||||
<ToggleSwitch
|
||||
:modelValue="configStore.config.backup.enabled"
|
||||
@update:modelValue="configStore.setEnableBackup"
|
||||
/>
|
||||
</SettingItem>
|
||||
|
||||
<SettingItem
|
||||
:title="t('settings.backup.autoBackup')"
|
||||
:class="{ 'disabled-setting': !enableBackup }"
|
||||
:title="t('settings.backup.autoBackup')"
|
||||
:class="{ 'disabled-setting': !configStore.config.backup.enabled }"
|
||||
>
|
||||
<ToggleSwitch v-model="autoBackup" :disabled="!enableBackup"/>
|
||||
<ToggleSwitch
|
||||
:modelValue="configStore.config.backup.auto_backup"
|
||||
@update:modelValue="configStore.setAutoBackup"
|
||||
:disabled="!configStore.config.backup.enabled"
|
||||
/>
|
||||
</SettingItem>
|
||||
|
||||
<SettingItem
|
||||
:title="t('settings.backup.backupInterval')"
|
||||
:class="{ 'disabled-setting': !enableBackup || !autoBackup }"
|
||||
:title="t('settings.backup.backupInterval')"
|
||||
:class="{ 'disabled-setting': !configStore.config.backup.enabled || !configStore.config.backup.auto_backup }"
|
||||
>
|
||||
<select
|
||||
class="backup-interval-select"
|
||||
:value="backupInterval"
|
||||
@change="handleBackupIntervalChange"
|
||||
:disabled="!enableBackup || !autoBackup"
|
||||
class="backup-interval-select"
|
||||
:value="configStore.config.backup.backup_interval"
|
||||
@change="(e) => configStore.setBackupInterval(Number((e.target as HTMLSelectElement).value))"
|
||||
:disabled="!configStore.config.backup.enabled || !configStore.config.backup.auto_backup"
|
||||
>
|
||||
<option
|
||||
v-for="option in backupIntervalOptions"
|
||||
:key="option.value"
|
||||
:value="option.value"
|
||||
>
|
||||
<option v-for="option in backupIntervalOptions" :key="option.value" :value="option.value">
|
||||
{{ option.label }}
|
||||
</option>
|
||||
</select>
|
||||
@@ -194,110 +76,94 @@ const selectSshKeyFile = async () => {
|
||||
|
||||
<!-- 仓库配置 -->
|
||||
<SettingSection :title="t('settings.backup.repositoryConfig')">
|
||||
<SettingItem
|
||||
:title="t('settings.backup.repoUrl')"
|
||||
>
|
||||
<SettingItem :title="t('settings.backup.repoUrl')">
|
||||
<input
|
||||
type="text"
|
||||
class="repo-url-input"
|
||||
:value="repoUrl"
|
||||
@input="handleRepoUrlChange"
|
||||
:placeholder="t('settings.backup.repoUrlPlaceholder')"
|
||||
:disabled="!enableBackup"
|
||||
type="text"
|
||||
class="repo-url-input"
|
||||
:value="configStore.config.backup.repo_url"
|
||||
@input="(e) => configStore.setRepoUrl((e.target as HTMLInputElement).value)"
|
||||
:placeholder="t('settings.backup.repoUrlPlaceholder')"
|
||||
:disabled="!configStore.config.backup.enabled"
|
||||
/>
|
||||
</SettingItem>
|
||||
|
||||
|
||||
</SettingSection>
|
||||
|
||||
<!-- 认证配置 -->
|
||||
<SettingSection :title="t('settings.backup.authConfig')">
|
||||
<SettingItem
|
||||
:title="t('settings.backup.authMethod')"
|
||||
>
|
||||
<SettingItem :title="t('settings.backup.authMethod')">
|
||||
<select
|
||||
class="auth-method-select"
|
||||
:value="authMethod"
|
||||
@change="handleAuthMethodChange"
|
||||
:disabled="!enableBackup"
|
||||
class="auth-method-select"
|
||||
:value="configStore.config.backup.auth_method"
|
||||
@change="(e) => configStore.setAuthMethod((e.target as HTMLSelectElement).value as AuthMethod)"
|
||||
:disabled="!configStore.config.backup.enabled"
|
||||
>
|
||||
<option
|
||||
v-for="option in authMethodOptions"
|
||||
:key="option.value"
|
||||
:value="option.value"
|
||||
>
|
||||
<option v-for="option in authMethodOptions" :key="option.value" :value="option.value">
|
||||
{{ option.label }}
|
||||
</option>
|
||||
</select>
|
||||
</SettingItem>
|
||||
|
||||
<!-- 用户名密码认证 -->
|
||||
<template v-if="authMethod === AuthMethod.UserPass">
|
||||
<template v-if="configStore.config.backup.auth_method === AuthMethod.UserPass">
|
||||
<SettingItem :title="t('settings.backup.username')">
|
||||
<input
|
||||
type="text"
|
||||
class="username-input"
|
||||
:value="username"
|
||||
@input="handleUsernameChange"
|
||||
:placeholder="t('settings.backup.usernamePlaceholder')"
|
||||
:disabled="!enableBackup"
|
||||
type="text"
|
||||
class="username-input"
|
||||
:value="configStore.config.backup.username"
|
||||
@input="(e) => configStore.setUsername((e.target as HTMLInputElement).value)"
|
||||
:placeholder="t('settings.backup.usernamePlaceholder')"
|
||||
:disabled="!configStore.config.backup.enabled"
|
||||
/>
|
||||
</SettingItem>
|
||||
|
||||
<SettingItem :title="t('settings.backup.password')">
|
||||
<input
|
||||
type="password"
|
||||
class="password-input"
|
||||
:value="password"
|
||||
@input="handlePasswordChange"
|
||||
:placeholder="t('settings.backup.passwordPlaceholder')"
|
||||
:disabled="!enableBackup"
|
||||
type="password"
|
||||
class="password-input"
|
||||
:value="configStore.config.backup.password"
|
||||
@input="(e) => configStore.setPassword((e.target as HTMLInputElement).value)"
|
||||
:placeholder="t('settings.backup.passwordPlaceholder')"
|
||||
:disabled="!configStore.config.backup.enabled"
|
||||
/>
|
||||
</SettingItem>
|
||||
</template>
|
||||
|
||||
<!-- 访问令牌认证 -->
|
||||
<template v-if="authMethod === AuthMethod.Token">
|
||||
<SettingItem
|
||||
:title="t('settings.backup.token')"
|
||||
>
|
||||
<template v-if="configStore.config.backup.auth_method === AuthMethod.Token">
|
||||
<SettingItem :title="t('settings.backup.token')">
|
||||
<input
|
||||
type="password"
|
||||
class="token-input"
|
||||
:value="token"
|
||||
@input="handleTokenChange"
|
||||
:placeholder="t('settings.backup.tokenPlaceholder')"
|
||||
:disabled="!enableBackup"
|
||||
type="password"
|
||||
class="token-input"
|
||||
:value="configStore.config.backup.token"
|
||||
@input="(e) => configStore.setToken((e.target as HTMLInputElement).value)"
|
||||
:placeholder="t('settings.backup.tokenPlaceholder')"
|
||||
:disabled="!configStore.config.backup.enabled"
|
||||
/>
|
||||
</SettingItem>
|
||||
</template>
|
||||
|
||||
<!-- SSH密钥认证 -->
|
||||
<template v-if="authMethod === AuthMethod.SSHKey">
|
||||
<SettingItem
|
||||
:title="t('settings.backup.sshKeyPath')"
|
||||
>
|
||||
<template v-if="configStore.config.backup.auth_method === AuthMethod.SSHKey">
|
||||
<SettingItem :title="t('settings.backup.sshKeyPath')">
|
||||
<input
|
||||
type="text"
|
||||
class="ssh-key-path-input"
|
||||
:value="sshKeyPath"
|
||||
:placeholder="t('settings.backup.sshKeyPathPlaceholder')"
|
||||
:disabled="!enableBackup"
|
||||
readonly
|
||||
@click="enableBackup && selectSshKeyFile()"
|
||||
type="text"
|
||||
class="ssh-key-path-input"
|
||||
:value="configStore.config.backup.ssh_key_path"
|
||||
:placeholder="t('settings.backup.sshKeyPathPlaceholder')"
|
||||
:disabled="!configStore.config.backup.enabled"
|
||||
readonly
|
||||
@click="configStore.config.backup.enabled && selectSshKeyFile()"
|
||||
/>
|
||||
</SettingItem>
|
||||
|
||||
<SettingItem
|
||||
:title="t('settings.backup.sshKeyPassphrase')"
|
||||
>
|
||||
<SettingItem :title="t('settings.backup.sshKeyPassphrase')">
|
||||
<input
|
||||
type="password"
|
||||
class="ssh-passphrase-input"
|
||||
:value="sshKeyPassphrase"
|
||||
@input="handleSshKeyPassphraseChange"
|
||||
:placeholder="t('settings.backup.sshKeyPassphrasePlaceholder')"
|
||||
:disabled="!enableBackup"
|
||||
type="password"
|
||||
class="ssh-passphrase-input"
|
||||
:value="configStore.config.backup.ssh_key_passphrase"
|
||||
@input="(e) => configStore.setSshKeyPassphrase((e.target as HTMLInputElement).value)"
|
||||
:placeholder="t('settings.backup.sshKeyPassphrasePlaceholder')"
|
||||
:disabled="!configStore.config.backup.enabled"
|
||||
/>
|
||||
</SettingItem>
|
||||
</template>
|
||||
@@ -305,36 +171,21 @@ const selectSshKeyFile = async () => {
|
||||
|
||||
<!-- 备份操作 -->
|
||||
<SettingSection :title="t('settings.backup.backupOperations')">
|
||||
<SettingItem
|
||||
:title="t('settings.backup.pushToRemote')"
|
||||
<SettingItem
|
||||
:title="t('settings.backup.pushToRemote')"
|
||||
:description="backupStore.message || undefined"
|
||||
:descriptionType="backupStore.message ? (backupStore.isError ? 'error' : 'success') : 'default'"
|
||||
>
|
||||
<div class="backup-operation-container">
|
||||
<div class="backup-status-icons">
|
||||
<span v-if="backupStore.isSuccess" class="success-icon">✓</span>
|
||||
<span v-if="backupStore.isError" class="error-icon">✗</span>
|
||||
</div>
|
||||
<button
|
||||
class="push-button"
|
||||
@click="() => pushToRemote()"
|
||||
:disabled="!enableBackup || !repoUrl || backupStore.isPushing"
|
||||
:class="{ 'backing-up': backupStore.isPushing }"
|
||||
>
|
||||
<span v-if="backupStore.isPushing" class="loading-spinner"></span>
|
||||
{{ backupStore.isPushing ? t('settings.backup.pushing') : t('settings.backup.actions.push') }}
|
||||
</button>
|
||||
<button
|
||||
v-if="backupStore.isError"
|
||||
class="retry-button"
|
||||
@click="() => retryBackup()"
|
||||
:disabled="backupStore.isPushing"
|
||||
>
|
||||
{{ t('settings.backup.actions.retry') }}
|
||||
</button>
|
||||
</div>
|
||||
<button
|
||||
class="push-button"
|
||||
@click="backupStore.pushToRemote"
|
||||
:disabled="!configStore.config.backup.enabled || !configStore.config.backup.repo_url || backupStore.isPushing"
|
||||
:class="{ 'backing-up': backupStore.isPushing }"
|
||||
>
|
||||
<span v-if="backupStore.isPushing" class="loading-spinner"></span>
|
||||
{{ backupStore.isPushing ? t('settings.backup.pushing') : t('settings.backup.actions.push') }}
|
||||
</button>
|
||||
</SettingItem>
|
||||
<div v-if="backupStore.errorMessage" class="error-message-row">
|
||||
{{ backupStore.errorMessage }}
|
||||
</div>
|
||||
</SettingSection>
|
||||
</div>
|
||||
</template>
|
||||
@@ -405,38 +256,8 @@ const selectSshKeyFile = async () => {
|
||||
}
|
||||
}
|
||||
|
||||
// 备份操作容器
|
||||
.backup-operation-container {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
// 备份状态图标
|
||||
.backup-status-icons {
|
||||
width: 24px;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
margin-right: 8px;
|
||||
}
|
||||
|
||||
// 成功和错误图标
|
||||
.success-icon {
|
||||
color: #4caf50;
|
||||
font-size: 18px;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.error-icon {
|
||||
color: #f44336;
|
||||
font-size: 18px;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
// 按钮样式
|
||||
.push-button,
|
||||
.retry-button {
|
||||
.push-button {
|
||||
padding: 8px 16px;
|
||||
background-color: var(--settings-input-bg);
|
||||
border: 1px solid var(--settings-input-border);
|
||||
@@ -480,30 +301,6 @@ const selectSshKeyFile = async () => {
|
||||
}
|
||||
}
|
||||
|
||||
.retry-button {
|
||||
background-color: #ff9800;
|
||||
border-color: #ff9800;
|
||||
color: white;
|
||||
|
||||
&:hover:not(:disabled) {
|
||||
background-color: #f57c00;
|
||||
border-color: #f57c00;
|
||||
}
|
||||
}
|
||||
|
||||
// 错误信息行样式
|
||||
.error-message-row {
|
||||
color: #f44336;
|
||||
font-size: 11px;
|
||||
line-height: 1.4;
|
||||
word-wrap: break-word;
|
||||
margin-top: 8px;
|
||||
padding: 8px 16px;
|
||||
background-color: rgba(244, 67, 54, 0.1);
|
||||
border-left: 3px solid #f44336;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
// 禁用状态
|
||||
.disabled-setting {
|
||||
opacity: 0.5;
|
||||
|
||||
Reference in New Issue
Block a user