fix: 卸载时通过注册表同步自定义数据目录,确保完整清理

- 新增 syncDataDirToRegistry:设置/重置数据目录时同步写入 Windows 注册表
- NSIS 卸载脚本通过注册表读取自定义目录路径并删除
- 清理 NSIS 废弃的安装器 DataDir 选择页(已移至应用内 onboarding)
- onboarding 页面添加 overflow-hidden 消除滚动条
This commit is contained in:
钟明原 committed 2026-03-16 21:37:19 +08:00
1 parent b479b3d6d8
commit 623cf72555
4 files changed
+34 -51

No files matched your search

-48
View File
@@ -8,63 +8,15 @@
; 因为 electron-builder 的 installSection.nsh 会在 section 开头设 SetDetailsPrint none,
; 但 SHOW 回调在 section 执行前触发,此时写入的文字会保留在日志面板中。
Var DataDirDialog
Var DataDirLabel
Var DataDirText
Var DataDirBrowseBtn
Var DataDirPath
!macro customHeader
ShowInstDetails nevershow
ShowUnInstDetails show
!macroend
!macro customPageAfterChangeDir
Page custom DataDirPage DataDirPageLeave
!macroend
Function DataDirPage
nsDialogs::Create 1018
Pop $DataDirDialog
${If} $DataDirDialog == error
Abort
${EndIf}
${NSD_CreateLabel} 0 0 100% 24u "选择数据存储目录(OpenClaw 运行时数据、缓存等):"
Pop $DataDirLabel
${NSD_CreateText} 0 28u 85% 12u "$APPDATA\easiest-claw-desktop"
Pop $DataDirText
${NSD_CreateButton} 87% 27u 13% 14u "浏览..."
Pop $DataDirBrowseBtn
${NSD_OnClick} $DataDirBrowseBtn DataDirBrowse
nsDialogs::Show
FunctionEnd
Function DataDirBrowse
nsDialogs::SelectFolderDialog "选择数据存储目录" "$APPDATA"
Pop $0
${If} $0 != error
${NSD_SetText} $DataDirText $0
${EndIf}
FunctionEnd
Function DataDirPageLeave
${NSD_GetText} $DataDirText $DataDirPath
FunctionEnd
!macro customInstall
SetDetailsPrint both
DetailPrint "程序文件安装完成。"
; 写入用户选择的数据目录到注册表
${If} $DataDirPath != ""
WriteRegStr HKCU "Software\EasiestClaw" "DataDir" $DataDirPath
DetailPrint "数据目录已设置为: $DataDirPath"
${EndIf}
DetailPrint "正在配置 Windows 防火墙规则..."
DetailPrint "删除旧规则(如存在)..."
nsExec::ExecToStack 'netsh advfirewall firewall delete rule name="EasiestClaw"'
+2
View File
@@ -4,6 +4,7 @@ import { spawn } from 'child_process'
import { existsSync, writeFileSync, readFileSync } from 'fs'
import { electronApp, optimizer, is } from '@electron-toolkit/utils'
import { registerAllIpcHandlers } from './ipc'
import { syncDataDirToRegistry } from './ipc/settings'
import { startRuntime, stopRuntime } from './gateway/runtime'
import { autoSpawnBundledOpenclaw, addGatewayLogListener, getGatewayLogBuffer } from './gateway/bundled-process'
import { extractOpenClawIfNeeded, getExtractState, confirmUpgrade, skipUpgrade } from './openclaw-init'
@@ -167,6 +168,7 @@ app.whenReady().then(async () => {
})
if (result.canceled || !result.filePaths[0]) return { ok: false }
patchSettings({ customDataDir: result.filePaths[0], dataLocationSelected: true } as Record<string, unknown>)
syncDataDirToRegistry(result.filePaths[0])
return { ok: true, dir: result.filePaths[0] }
})
+29
View File
@@ -2,9 +2,36 @@ import type { IpcMain } from 'electron'
import { app } from 'electron'
import fs from 'node:fs'
import path from 'node:path'
import { execFile } from 'node:child_process'
import { loadSettings, patchSettings, loadOpenclawDefaults } from '../gateway/settings'
import { restartRuntime } from '../gateway/runtime'
import { getDataDir } from '../lib/data-dir'
import { logger } from '../lib/logger'
const REG_KEY = 'HKCU\\Software\\EasiestClaw'
/**
* 同步自定义数据目录到 Windows 注册表(供 NSIS 卸载脚本读取)。
* 传 undefined 时删除整个注册表键。
*/
export function syncDataDirToRegistry(dir: string | undefined): void {
if (process.platform !== 'win32') return
try {
if (dir) {
execFile('reg', ['add', REG_KEY, '/v', 'DataDir', '/t', 'REG_SZ', '/d', dir, '/f'],
{ windowsHide: true }, (err) => {
if (err) logger.warn(`[Registry] failed to write DataDir: ${err.message}`)
})
} else {
execFile('reg', ['delete', REG_KEY, '/f'],
{ windowsHide: true }, (err) => {
if (err) logger.warn(`[Registry] failed to delete key: ${err.message}`)
})
}
} catch {
// non-critical, ignore
}
}
export const registerSettingsHandlers = (ipcMain: IpcMain): void => {
// Load current app settings (gateway URL/token, avatars)
@@ -94,6 +121,7 @@ export const registerSettingsHandlers = (ipcMain: IpcMain): void => {
patchSettings({ customDataDir: dir })
}
syncDataDirToRegistry(dir)
return { ok: true, needRestart: true }
})
@@ -116,6 +144,7 @@ export const registerSettingsHandlers = (ipcMain: IpcMain): void => {
} else {
patchSettings({ customDataDir: undefined })
}
syncDataDirToRegistry(undefined)
return { ok: true, needRestart: true }
})
}
@@ -516,7 +516,7 @@ function ProfileSetupStep({ onDone }: { onDone: () => void }) {
export function GatewayLoadingScreen() {
return (
<div className="h-screen relative flex items-center justify-center bg-background"
<div className="h-screen relative flex items-center justify-center bg-background overflow-hidden"
style={{ WebkitAppRegion: "drag" } as React.CSSProperties}
>
<div className="absolute top-4 right-4 flex items-center gap-1 z-10"
@@ -575,14 +575,14 @@ export function OnboardingFlow({ onDone }: OnboardingFlowProps) {
if (checkingDataLocation) {
return (
<div className="h-screen relative flex items-center justify-center bg-background">
<div className="h-screen relative flex items-center justify-center bg-background overflow-hidden">
<Loader2 className="h-8 w-8 text-primary animate-spin" />
</div>
)
}
return (
<div className="h-screen relative flex items-center justify-center bg-background"
<div className="h-screen relative flex items-center justify-center bg-background overflow-hidden"
style={{ WebkitAppRegion: "drag" } as React.CSSProperties}
>
{/* 窗口控件 — 始终可见 */}