diff --git a/src/composables/useSystemConfig.ts b/src/composables/useSystemConfig.ts index 5b8ec87..1d27030 100644 --- a/src/composables/useSystemConfig.ts +++ b/src/composables/useSystemConfig.ts @@ -18,39 +18,78 @@ type ConfigFlagKey = 'enableChunk' | 's3_proxy' | 'openUpload' export function useSystemConfig() { const alertStore = useAlertStore() const configStore = useConfigStore() - + // 状态管理 const config = ref({ ...DEFAULT_CONFIG_STATE }) - const isLoading = ref(false) + const isRefreshing = ref(false) + const isSaving = ref(false) + const savedPayloadSnapshot = ref('') const fileSize = ref(1) const sizeUnit = ref('MB') const saveTime = ref(1) const saveTimeUnit = ref('天') - + + const isLoading = computed(() => isRefreshing.value || isSaving.value) + + const buildSubmitPayload = (): Partial => { + const payload: Partial = buildConfigSubmitPayload( + config.value, + { value: fileSize.value, unit: sizeUnit.value }, + { value: saveTime.value, unit: saveTimeUnit.value } + ) + + if (!payload.admin_token) { + delete payload.admin_token + } + + return payload + } + + const snapshotPayload = (payload: Partial) => JSON.stringify(payload) + + const normalizeEditableConfig = (nextConfig: Partial): ConfigState => ({ + ...DEFAULT_CONFIG_STATE, + ...nextConfig, + admin_token: '' + }) + + const markConfigSaved = () => { + savedPayloadSnapshot.value = snapshotPayload(buildSubmitPayload()) + } + + const isDirty = computed(() => { + if (!savedPayloadSnapshot.value) { + return false + } + + return snapshotPayload(buildSubmitPayload()) !== savedPayloadSnapshot.value + }) + // 从本地存储获取配置 const getStoredConfig = (): ConfigState | null => { - return readStoredConfig() + const storedConfig = readStoredConfig>() + return storedConfig ? normalizeEditableConfig(storedConfig) : null } - + // 保存配置到本地存储 const saveConfigToStorage = (configData: ConfigState) => { configStore.updateConfig(configData) } - + // 获取系统配置 const fetchConfig = async (): Promise => { try { - isLoading.value = true - + isRefreshing.value = true + const response = await ConfigService.getConfig() - + if (response.code === 200 && response.detail) { - config.value = { ...DEFAULT_CONFIG_STATE, ...response.detail } + config.value = normalizeEditableConfig(response.detail) const notifyMessage = configStore.applyRemoteConfig(config.value) if (notifyMessage) { alertStore.showAlert(notifyMessage, 'success') } - + return config.value } else { throw new Error(response.message || '获取配置失败') @@ -62,23 +101,25 @@ export function useSystemConfig() { config.value = storedConfig return config.value } - + alertStore.showAlert(getErrorMessage(error, '获取配置失败'), 'error') return null } finally { - isLoading.value = false + isRefreshing.value = false } } - + // 更新系统配置 const updateConfig = async (newConfig: Partial): Promise => { try { - isLoading.value = true - + isSaving.value = true + const response = await ConfigService.updateConfig(newConfig) - + if (response.code === 200) { - config.value = { ...config.value, ...newConfig } + config.value = normalizeEditableConfig({ ...config.value, ...newConfig }) + syncConfigForm(config.value) + markConfigSaved() saveConfigToStorage(config.value) alertStore.showAlert('配置更新成功!', 'success') return true @@ -89,7 +130,7 @@ export function useSystemConfig() { alertStore.showAlert(getErrorMessage(error, '更新配置失败'), 'error') return false } finally { - isLoading.value = false + isSaving.value = false } } @@ -111,52 +152,57 @@ export function useSystemConfig() { const latestConfig = await fetchConfig() if (latestConfig) { syncConfigForm(latestConfig) + markConfigSaved() } } - const submitConfig = () => - updateConfig( - buildConfigSubmitPayload( - config.value, - { value: fileSize.value, unit: sizeUnit.value }, - { value: saveTime.value, unit: saveTimeUnit.value } - ) - ) - + const submitConfig = () => { + if (!isDirty.value || isSaving.value) { + return Promise.resolve(false) + } + + return updateConfig(buildSubmitPayload()) + } + // 初始化配置 const initConfig = async () => { // 先尝试从本地存储加载 const storedConfig = getStoredConfig() if (storedConfig) { config.value = storedConfig + syncConfigForm(storedConfig) + markConfigSaved() } - + // 然后从服务器获取最新配置 - await fetchConfig() + await refreshConfig() } - + // 计算属性 const maxFileSizeMB = computed(() => { return Math.round(config.value.uploadSize / 1024 / 1024) }) - + const isConfigLoaded = computed(() => { return config.value.name !== DEFAULT_CONFIG_STATE.name || !isLoading.value }) - + return { // 状态 config, isLoading, + isRefreshing, + isSaving, + isDirty, fileSize, sizeUnit, saveTime, saveTimeUnit, - + // 计算属性 maxFileSizeMB, isConfigLoaded, - + // 方法 fetchConfig, updateConfig, diff --git a/src/i18n/locales/en-US.ts b/src/i18n/locales/en-US.ts index 39150eb..7a1d55d 100644 --- a/src/i18n/locales/en-US.ts +++ b/src/i18n/locales/en-US.ts @@ -446,7 +446,13 @@ export default { mb: 'MB', gb: 'GB' }, - saveChanges: 'Save Settings' + saveChanges: 'Save Settings', + refreshConfig: 'Refresh Config', + refreshing: 'Refreshing', + saving: 'Saving', + unsavedChanges: 'Unsaved configuration changes', + allChangesSaved: 'All configuration changes are saved', + refreshBlocked: 'Save current changes before refreshing' }, systemSettings: { title: 'System Settings', diff --git a/src/i18n/locales/zh-CN.ts b/src/i18n/locales/zh-CN.ts index 680b1ca..0e5d958 100644 --- a/src/i18n/locales/zh-CN.ts +++ b/src/i18n/locales/zh-CN.ts @@ -477,7 +477,13 @@ export default { errorLimits: '访问保护', errorPerMinute: '检测时间窗口(在此时间内统计错误次数)', errorCountLimit: '允许错误次数(超过后临时封禁)', - saveChanges: '保存设置' + saveChanges: '保存设置', + refreshConfig: '刷新配置', + refreshing: '刷新中', + saving: '保存中', + unsavedChanges: '有未保存的配置变更', + allChangesSaved: '所有配置已保存', + refreshBlocked: '请先保存当前变更再刷新' }, dashboard: { title: '仪表盘', diff --git a/src/views/manage/SystemSettingsView.vue b/src/views/manage/SystemSettingsView.vue index e2b7153..317ed4c 100644 --- a/src/views/manage/SystemSettingsView.vue +++ b/src/views/manage/SystemSettingsView.vue @@ -1,6 +1,8 @@