mirror of
https://github.com/hicccc77/WeFlow.git
synced 2026-03-25 07:16:51 +00:00
@@ -96,6 +96,7 @@ function SettingsPage() {
|
|||||||
const [isClearingAnalyticsCache, setIsClearingAnalyticsCache] = useState(false)
|
const [isClearingAnalyticsCache, setIsClearingAnalyticsCache] = useState(false)
|
||||||
const [isClearingImageCache, setIsClearingImageCache] = useState(false)
|
const [isClearingImageCache, setIsClearingImageCache] = useState(false)
|
||||||
const [isClearingAllCache, setIsClearingAllCache] = useState(false)
|
const [isClearingAllCache, setIsClearingAllCache] = useState(false)
|
||||||
|
const saveTimersRef = useRef<Record<string, ReturnType<typeof setTimeout>>>({})
|
||||||
|
|
||||||
// 安全设置 state
|
// 安全设置 state
|
||||||
const [authEnabled, setAuthEnabled] = useState(false)
|
const [authEnabled, setAuthEnabled] = useState(false)
|
||||||
@@ -125,6 +126,9 @@ function SettingsPage() {
|
|||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
loadConfig()
|
loadConfig()
|
||||||
loadAppVersion()
|
loadAppVersion()
|
||||||
|
return () => {
|
||||||
|
Object.values(saveTimersRef.current).forEach((timer) => clearTimeout(timer))
|
||||||
|
}
|
||||||
}, [])
|
}, [])
|
||||||
|
|
||||||
// 点击外部关闭下拉框
|
// 点击外部关闭下拉框
|
||||||
@@ -334,6 +338,12 @@ function SettingsPage() {
|
|||||||
imageAesKey: imageAesKey || ''
|
imageAesKey: imageAesKey || ''
|
||||||
})
|
})
|
||||||
|
|
||||||
|
const buildKeysFromInputs = (overrides?: { decryptKey?: string; imageXorKey?: string; imageAesKey?: string }): WxidKeys => ({
|
||||||
|
decryptKey: overrides?.decryptKey ?? decryptKey ?? '',
|
||||||
|
imageXorKey: parseImageXorKey(overrides?.imageXorKey ?? imageXorKey),
|
||||||
|
imageAesKey: overrides?.imageAesKey ?? imageAesKey ?? ''
|
||||||
|
})
|
||||||
|
|
||||||
const buildKeysFromConfig = (wxidConfig: configService.WxidConfig | null): WxidKeys => ({
|
const buildKeysFromConfig = (wxidConfig: configService.WxidConfig | null): WxidKeys => ({
|
||||||
decryptKey: wxidConfig?.decryptKey || '',
|
decryptKey: wxidConfig?.decryptKey || '',
|
||||||
imageXorKey: typeof wxidConfig?.imageXorKey === 'number' ? wxidConfig.imageXorKey : null,
|
imageXorKey: typeof wxidConfig?.imageXorKey === 'number' ? wxidConfig.imageXorKey : null,
|
||||||
@@ -444,7 +454,9 @@ function SettingsPage() {
|
|||||||
try {
|
try {
|
||||||
const result = await dialog.openFile({ title: '选择微信数据库根目录', properties: ['openDirectory'] })
|
const result = await dialog.openFile({ title: '选择微信数据库根目录', properties: ['openDirectory'] })
|
||||||
if (!result.canceled && result.filePaths.length > 0) {
|
if (!result.canceled && result.filePaths.length > 0) {
|
||||||
setDbPath(result.filePaths[0])
|
const selectedPath = result.filePaths[0]
|
||||||
|
setDbPath(selectedPath)
|
||||||
|
await configService.setDbPath(selectedPath)
|
||||||
showMessage('已选择数据库目录', true)
|
showMessage('已选择数据库目录', true)
|
||||||
}
|
}
|
||||||
} catch (e: any) {
|
} catch (e: any) {
|
||||||
@@ -488,7 +500,9 @@ function SettingsPage() {
|
|||||||
try {
|
try {
|
||||||
const result = await dialog.openFile({ title: '选择缓存目录', properties: ['openDirectory'] })
|
const result = await dialog.openFile({ title: '选择缓存目录', properties: ['openDirectory'] })
|
||||||
if (!result.canceled && result.filePaths.length > 0) {
|
if (!result.canceled && result.filePaths.length > 0) {
|
||||||
setCachePath(result.filePaths[0])
|
const selectedPath = result.filePaths[0]
|
||||||
|
setCachePath(selectedPath)
|
||||||
|
await configService.setCachePath(selectedPath)
|
||||||
showMessage('已选择缓存目录', true)
|
showMessage('已选择缓存目录', true)
|
||||||
}
|
}
|
||||||
} catch (e: any) {
|
} catch (e: any) {
|
||||||
@@ -575,12 +589,25 @@ function SettingsPage() {
|
|||||||
handleAutoGetDbKey()
|
handleAutoGetDbKey()
|
||||||
}
|
}
|
||||||
|
|
||||||
// Helper to sync current keys to wxid config
|
// Debounce config writes to avoid excessive disk IO
|
||||||
const syncCurrentKeys = async () => {
|
const scheduleConfigSave = (key: string, task: () => Promise<void> | void, delay = 300) => {
|
||||||
const keys = buildKeysFromState()
|
const timers = saveTimersRef.current
|
||||||
|
if (timers[key]) {
|
||||||
|
clearTimeout(timers[key])
|
||||||
|
}
|
||||||
|
timers[key] = setTimeout(() => {
|
||||||
|
Promise.resolve(task()).catch((e) => {
|
||||||
|
console.error('保存配置失败:', e)
|
||||||
|
})
|
||||||
|
}, delay)
|
||||||
|
}
|
||||||
|
|
||||||
|
const syncCurrentKeys = async (options?: { decryptKey?: string; imageXorKey?: string; imageAesKey?: string; wxid?: string }) => {
|
||||||
|
const keys = buildKeysFromInputs(options)
|
||||||
await syncKeysToConfig(keys)
|
await syncKeysToConfig(keys)
|
||||||
if (wxid) {
|
const wxidToUse = options?.wxid ?? wxid
|
||||||
await configService.setWxidConfig(wxid, {
|
if (wxidToUse) {
|
||||||
|
await configService.setWxidConfig(wxidToUse, {
|
||||||
decryptKey: keys.decryptKey,
|
decryptKey: keys.decryptKey,
|
||||||
imageXorKey: typeof keys.imageXorKey === 'number' ? keys.imageXorKey : 0,
|
imageXorKey: typeof keys.imageXorKey === 'number' ? keys.imageXorKey : 0,
|
||||||
imageAesKey: keys.imageAesKey
|
imageAesKey: keys.imageAesKey
|
||||||
@@ -804,10 +831,11 @@ function SettingsPage() {
|
|||||||
type={showDecryptKey ? 'text' : 'password'}
|
type={showDecryptKey ? 'text' : 'password'}
|
||||||
placeholder="例如: a1b2c3d4e5f6..."
|
placeholder="例如: a1b2c3d4e5f6..."
|
||||||
value={decryptKey}
|
value={decryptKey}
|
||||||
onChange={(e) => setDecryptKey(e.target.value)}
|
onChange={(e) => {
|
||||||
onBlur={async () => {
|
const value = e.target.value
|
||||||
if (decryptKey && decryptKey.length === 64) {
|
setDecryptKey(value)
|
||||||
await syncCurrentKeys()
|
if (value && value.length === 64) {
|
||||||
|
scheduleConfigSave('keys', () => syncCurrentKeys({ decryptKey: value }))
|
||||||
// showMessage('解密密钥已保存', true)
|
// showMessage('解密密钥已保存', true)
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
@@ -839,11 +867,14 @@ function SettingsPage() {
|
|||||||
type="text"
|
type="text"
|
||||||
placeholder="例如: C:\Users\xxx\Documents\xwechat_files"
|
placeholder="例如: C:\Users\xxx\Documents\xwechat_files"
|
||||||
value={dbPath}
|
value={dbPath}
|
||||||
onChange={(e) => setDbPath(e.target.value)}
|
onChange={(e) => {
|
||||||
onBlur={async () => {
|
const value = e.target.value
|
||||||
if (dbPath) {
|
setDbPath(value)
|
||||||
await configService.setDbPath(dbPath)
|
scheduleConfigSave('dbPath', async () => {
|
||||||
|
if (value) {
|
||||||
|
await configService.setDbPath(value)
|
||||||
}
|
}
|
||||||
|
})
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
<div className="btn-row">
|
<div className="btn-row">
|
||||||
@@ -862,12 +893,15 @@ function SettingsPage() {
|
|||||||
type="text"
|
type="text"
|
||||||
placeholder="例如: wxid_xxxxxx"
|
placeholder="例如: wxid_xxxxxx"
|
||||||
value={wxid}
|
value={wxid}
|
||||||
onChange={(e) => setWxid(e.target.value)}
|
onChange={(e) => {
|
||||||
onBlur={async () => {
|
const value = e.target.value
|
||||||
if (wxid) {
|
setWxid(value)
|
||||||
await configService.setMyWxid(wxid)
|
scheduleConfigSave('wxid', async () => {
|
||||||
await syncCurrentKeys() // Sync keys to the new wxid entry
|
if (value) {
|
||||||
|
await configService.setMyWxid(value)
|
||||||
|
await syncCurrentKeys({ wxid: value }) // Sync keys to the new wxid entry
|
||||||
}
|
}
|
||||||
|
})
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
@@ -881,8 +915,14 @@ function SettingsPage() {
|
|||||||
type="text"
|
type="text"
|
||||||
placeholder="例如: 0xA4"
|
placeholder="例如: 0xA4"
|
||||||
value={imageXorKey}
|
value={imageXorKey}
|
||||||
onChange={(e) => setImageXorKey(e.target.value)}
|
onChange={(e) => {
|
||||||
onBlur={syncCurrentKeys}
|
const value = e.target.value
|
||||||
|
setImageXorKey(value)
|
||||||
|
const parsed = parseImageXorKey(value)
|
||||||
|
if (value === '' || parsed !== null) {
|
||||||
|
scheduleConfigSave('keys', () => syncCurrentKeys({ imageXorKey: value }))
|
||||||
|
}
|
||||||
|
}}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -893,8 +933,11 @@ function SettingsPage() {
|
|||||||
type="text"
|
type="text"
|
||||||
placeholder="16 位 AES 密钥"
|
placeholder="16 位 AES 密钥"
|
||||||
value={imageAesKey}
|
value={imageAesKey}
|
||||||
onChange={(e) => setImageAesKey(e.target.value)}
|
onChange={(e) => {
|
||||||
onBlur={syncCurrentKeys}
|
const value = e.target.value
|
||||||
|
setImageAesKey(value)
|
||||||
|
scheduleConfigSave('keys', () => syncCurrentKeys({ imageAesKey: value }))
|
||||||
|
}}
|
||||||
/>
|
/>
|
||||||
<button className="btn btn-secondary btn-sm" onClick={handleAutoGetImageKey} disabled={isFetchingImageKey}>
|
<button className="btn btn-secondary btn-sm" onClick={handleAutoGetImageKey} disabled={isFetchingImageKey}>
|
||||||
<Plug size={14} /> {isFetchingImageKey ? '获取中...' : '自动获取图片密钥'}
|
<Plug size={14} /> {isFetchingImageKey ? '获取中...' : '自动获取图片密钥'}
|
||||||
@@ -1009,8 +1052,11 @@ function SettingsPage() {
|
|||||||
type="text"
|
type="text"
|
||||||
placeholder="留空使用默认目录"
|
placeholder="留空使用默认目录"
|
||||||
value={whisperModelDir}
|
value={whisperModelDir}
|
||||||
onChange={(e) => setWhisperModelDir(e.target.value)}
|
onChange={(e) => {
|
||||||
onBlur={() => configService.setWhisperModelDir(whisperModelDir)}
|
const value = e.target.value
|
||||||
|
setWhisperModelDir(value)
|
||||||
|
scheduleConfigSave('whisperModelDir', () => configService.setWhisperModelDir(value))
|
||||||
|
}}
|
||||||
/>
|
/>
|
||||||
<div className="btn-row">
|
<div className="btn-row">
|
||||||
<button className="btn btn-secondary" onClick={handleSelectWhisperModelDir}><FolderOpen size={16} /> 选择目录</button>
|
<button className="btn btn-secondary" onClick={handleSelectWhisperModelDir}><FolderOpen size={16} /> 选择目录</button>
|
||||||
@@ -1252,14 +1298,23 @@ function SettingsPage() {
|
|||||||
type="text"
|
type="text"
|
||||||
placeholder="留空使用默认目录"
|
placeholder="留空使用默认目录"
|
||||||
value={cachePath}
|
value={cachePath}
|
||||||
onChange={(e) => setCachePath(e.target.value)}
|
onChange={(e) => {
|
||||||
onBlur={async () => {
|
const value = e.target.value
|
||||||
await configService.setCachePath(cachePath)
|
setCachePath(value)
|
||||||
|
scheduleConfigSave('cachePath', () => configService.setCachePath(value))
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
<div className="btn-row">
|
<div className="btn-row">
|
||||||
<button className="btn btn-secondary" onClick={handleSelectCachePath}><FolderOpen size={16} /> 浏览选择</button>
|
<button className="btn btn-secondary" onClick={handleSelectCachePath}><FolderOpen size={16} /> 浏览选择</button>
|
||||||
<button className="btn btn-secondary" onClick={() => setCachePath('')}><RotateCcw size={16} /> 恢复默认</button>
|
<button
|
||||||
|
className="btn btn-secondary"
|
||||||
|
onClick={async () => {
|
||||||
|
setCachePath('')
|
||||||
|
await configService.setCachePath('')
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<RotateCcw size={16} /> 恢复默认
|
||||||
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user