feat: add multi-account storage support

This commit is contained in:
ILoveBingLu
2026-04-07 11:25:55 +08:00
parent 892bb38105
commit e67640a4c4
10 changed files with 915 additions and 136 deletions
+317 -63
View File
@@ -4,6 +4,7 @@ import { useAppStore } from '../stores/appStore'
import { useThemeStore, themes } from '../stores/themeStore'
import { useActivationStore } from '../stores/activationStore'
import type { UpdateDownloadProgressPayload } from '../types/electron'
import type { AccountProfile } from '../types/account'
import { dialog } from '../services/ipc'
import * as configService from '../services/config'
import AISummarySettings from '../components/ai/AISummarySettings'
@@ -45,13 +46,16 @@ const sttModelTypeOptions = [
function SettingsPage() {
const [searchParams] = useSearchParams()
const location = useLocation()
const { setDbConnected, setLoading } = useAppStore()
const { setDbConnected, setLoading, setMyWxid: setCurrentWxid } = useAppStore()
const { currentTheme, themeMode, setTheme, setThemeMode, appIcon, setAppIcon } = useThemeStore()
const { status: activationStatus, checkStatus: checkActivationStatus } = useActivationStore()
const { isAuthEnabled, enableAuth, disableAuth, setupPassword, authMethod } = useAuthStore()
const [passwordInput, setPasswordInput] = useState('')
const [showPasswordInput, setShowPasswordInput] = useState(false)
const [accountsList, setAccountsList] = useState<AccountProfile[]>([])
const [activeAccountId, setActiveAccountId] = useState('')
const [editingAccountId, setEditingAccountId] = useState('')
// 安全设置确认弹窗状态
const [securityConfirm, setSecurityConfirm] = useState<{
@@ -143,7 +147,7 @@ function SettingsPage() {
const [closeToTray, setCloseToTray] = useState(true)
const [showAesKey, setShowAesKey] = useState(false)
const [showClearDialog, setShowClearDialog] = useState<{
type: 'images' | 'emojis' | 'databases' | 'all' | 'config'
type: 'images' | 'emojis' | 'databases' | 'all' | 'currentAccount' | 'allAccounts'
title: string
message: string
} | null>(null)
@@ -197,6 +201,41 @@ function SettingsPage() {
const isMac = platformInfo.platform === 'darwin'
const biometricLabel = isMac ? 'Touch ID' : 'Windows Hello'
const buildAccountPayload = () => ({
wxid: wxid.trim(),
dbPath: dbPath.trim(),
decryptKey: decryptKey.trim(),
cachePath: cachePath.trim(),
imageXorKey: imageXorKey.trim(),
imageAesKey: imageAesKey.trim(),
displayName: wxid.trim() || '未命名账号'
})
const applyAccountToForm = (account: AccountProfile | null) => {
setEditingAccountId(account?.id || '')
setDecryptKey(account?.decryptKey || '')
setDbPath(account?.dbPath || '')
setWxid(account?.wxid || '')
setCachePath(account?.cachePath || '')
setImageXorKey(account?.imageXorKey || '')
setImageAesKey(account?.imageAesKey || '')
setIsAccountVerified(Boolean(account?.decryptKey && account?.dbPath && account?.wxid))
}
const refreshAccountsState = async (preferredEditingId?: string) => {
const [accounts, activeAccount] = await Promise.all([
configService.listAccounts(),
configService.getActiveAccount()
])
setAccountsList(accounts)
setActiveAccountId(activeAccount?.id || '')
const editingId = preferredEditingId || editingAccountId || activeAccount?.id || accounts[0]?.id || ''
const editingAccount = accounts.find(item => item.id === editingId) || activeAccount || accounts[0] || null
applyAccountToForm(editingAccount)
return { accounts, activeAccount, editingAccount }
}
useEffect(() => {
loadConfig()
loadDefaultExportPath()
@@ -210,6 +249,7 @@ function SettingsPage() {
const loadConfig = async () => {
try {
const { activeAccount, editingAccount } = await refreshAccountsState()
const savedKey = await configService.getDecryptKey()
const savedPath = await configService.getDbPath()
const savedWxid = await configService.getMyWxid()
@@ -222,12 +262,13 @@ function SettingsPage() {
const savedSkipIntegrityCheck = await configService.getSkipIntegrityCheck()
const savedAutoUpdateDatabase = await configService.getAutoUpdateDatabase()
if (savedKey) setDecryptKey(savedKey)
if (savedPath) setDbPath(savedPath)
if (savedWxid) setWxid(savedWxid)
if (savedCachePath) setCachePath(savedCachePath)
if (savedXorKey) setImageXorKey(savedXorKey)
if (savedAesKey) setImageAesKey(savedAesKey)
if (!editingAccount && savedKey) setDecryptKey(savedKey)
if (!editingAccount && savedPath) setDbPath(savedPath)
if (!editingAccount && savedWxid) setWxid(savedWxid)
if (!editingAccount && savedCachePath) setCachePath(savedCachePath)
if (!editingAccount && savedXorKey) setImageXorKey(savedXorKey)
if (!editingAccount && savedAesKey) setImageAesKey(savedAesKey)
setIsAccountVerified(Boolean((editingAccount || activeAccount)?.decryptKey && (editingAccount || activeAccount)?.dbPath && (editingAccount || activeAccount)?.wxid))
if (savedExportPath) setExportPath(savedExportPath)
if (savedSttLanguages && savedSttLanguages.length > 0) {
setSttLanguagesState(savedSttLanguages)
@@ -308,7 +349,8 @@ function SettingsPage() {
aiCustomSystemPrompt: savedAiCustomSystemPrompt,
aiEnableThinking: savedAiEnableThinking,
aiMessageLimit: savedAiMessageLimit,
closeToTray: savedCloseToTray
closeToTray: savedCloseToTray,
editingAccountId: (editingAccount || activeAccount)?.id || ''
})
} catch (e) {
@@ -356,7 +398,8 @@ function SettingsPage() {
aiCustomSystemPrompt,
aiEnableThinking,
aiMessageLimit,
closeToTray
closeToTray,
editingAccountId
}
// 深度比较配置是否有变化
@@ -369,7 +412,7 @@ function SettingsPage() {
quoteStyle, exportDefaultDateRange, exportDefaultAvatars,
aiProvider, aiApiKey, aiModel, aiDefaultTimeRange, aiSummaryDetail,
aiSystemPromptPreset, aiCustomSystemPrompt, aiEnableThinking, aiMessageLimit,
closeToTray, initialConfig
closeToTray, editingAccountId, initialConfig
])
const loadAppVersion = async () => {
@@ -604,11 +647,19 @@ function SettingsPage() {
})
}
const handleClearConfig = () => {
const handleClearCurrentAccount = () => {
setShowClearDialog({
type: 'config',
title: '清除配置',
message: '此操作将删除所有保存的配置信息(包括密钥、路径等),清除后无法恢复。确定要继续吗?'
type: 'currentAccount',
title: '清除当前账号',
message: '此操作将清除当前账号的密钥、路径等配置,不影响其他账号。确定要继续吗?'
})
}
const handleClearAllAccounts = () => {
setShowClearDialog({
type: 'allAccounts',
title: '清空全部账号配置',
message: '此操作将删除所有账号配置和账号级密钥/路径信息,不删除全局主题、AI、MCP、HTTP API 等通用设置。确定要继续吗?'
})
}
@@ -617,31 +668,34 @@ function SettingsPage() {
try {
let result
switch (showClearDialog.type) {
case 'images':
result = await window.electronAPI.cache.clearImages()
break
switch (showClearDialog.type) {
case 'images':
result = await window.electronAPI.cache.clearImages()
break
case 'emojis':
result = await window.electronAPI.cache.clearEmojis()
break
case 'databases':
result = await window.electronAPI.cache.clearDatabases()
break
case 'all':
result = await window.electronAPI.cache.clearAll()
break
case 'config':
result = await window.electronAPI.cache.clearConfig()
break
}
if (result.success) {
showMessage(`${showClearDialog.title}成功`, true)
if (showClearDialog.type === 'config') {
await loadConfig()
} else {
await loadCacheSize()
case 'all':
result = await window.electronAPI.cache.clearAll()
break
case 'currentAccount':
result = await window.electronAPI.cache.clearCurrentAccount(false)
break
case 'allAccounts':
result = await window.electronAPI.cache.clearAllAccountConfigs()
break
}
if (result.success) {
showMessage(`${showClearDialog.title}成功`, true)
if (showClearDialog.type === 'currentAccount' || showClearDialog.type === 'allAccounts') {
await loadConfig()
} else {
await loadCacheSize()
}
} else {
showMessage(result.error || `${showClearDialog.title}失败`, false)
}
@@ -697,14 +751,12 @@ function SettingsPage() {
if (result.success && result.key) {
setDecryptKey(result.key)
await configService.setDecryptKey(result.key)
if (dbPath) {
const resolved = await window.electronAPI.wcdb.resolveValidWxid(dbPath, result.key)
if (resolved.success && resolved.wxid) {
setWxid(resolved.wxid)
setIsAccountVerified(true)
await configService.setMyWxid(resolved.wxid)
showMessage(`密钥获取成功!已验证账号: ${resolved.wxid}`, true)
setKeyStatus('')
return
@@ -714,7 +766,6 @@ function SettingsPage() {
if (result.validatedWxid) {
setWxid(result.validatedWxid)
setIsAccountVerified(true)
await configService.setMyWxid(result.validatedWxid)
showMessage(`密钥获取成功!已验证账号: ${result.validatedWxid}`, true)
setKeyStatus('')
return
@@ -730,7 +781,6 @@ function SettingsPage() {
if (accountInfo) {
setWxid(accountInfo.wxid)
setIsAccountVerified(false)
await configService.setMyWxid(accountInfo.wxid)
showMessage(`密钥获取成功!已识别候选账号: ${accountInfo.wxid},请继续验证目录。`, true)
} else {
const wxids = await window.electronAPI.dbPath.scanWxids(dbPath)
@@ -798,7 +848,6 @@ function SettingsPage() {
if (result.success && result.key) {
setDecryptKey(result.key)
await configService.setDecryptKey(result.key)
// 自动检测当前登录的微信账号
setKeyStatus('正在检测当前登录账号...')
@@ -813,7 +862,6 @@ function SettingsPage() {
if (accountInfo) {
setWxid(accountInfo.wxid)
await configService.setMyWxid(accountInfo.wxid)
showMessage(`密钥获取成功!已自动绑定账号: ${accountInfo.wxid}`, true)
} else {
showMessage('密钥获取成功,已自动保存!(未能自动检测账号,请手动输入 wxid)', true)
@@ -839,12 +887,152 @@ function SettingsPage() {
const handleOpenWelcomeWindow = async () => {
try {
await window.electronAPI.window.openWelcomeWindow()
await window.electronAPI.window.openWelcomeWindow('add-account')
} catch (e) {
showMessage('打开引导窗口失败', false)
}
}
const handleSelectAccountForEdit = (account: AccountProfile) => {
applyAccountToForm(account)
setInitialConfig((prev: any) => prev ? {
...prev,
decryptKey: account.decryptKey || '',
dbPath: account.dbPath || '',
wxid: account.wxid || '',
cachePath: account.cachePath || '',
imageXorKey: account.imageXorKey || '',
imageAesKey: account.imageAesKey || '',
editingAccountId: account.id
} : prev)
setHasUnsavedChanges(false)
}
const handleSwitchAccountAndReconnect = async () => {
if (!editingAccountId || editingAccountId === activeAccountId) {
showMessage('当前没有待切换账号', false)
return
}
if (hasUnsavedChanges) {
showMessage('请先保存当前账号表单,再执行切换', false)
return
}
const target = accountsList.find((item) => item.id === editingAccountId)
if (!target) {
showMessage('待切换账号不存在', false)
return
}
if (!target.dbPath || !target.decryptKey || !target.wxid) {
showMessage('待切换账号配置不完整,请先保存并补全账号信息', false)
return
}
setIsLoadingState(true)
setLoading(true, '正在切换账号...')
try {
const switched = await configService.setActiveAccount(target.id)
if (!switched) {
throw new Error('切换账号失败')
}
const result = await window.electronAPI.wcdb.testConnection(target.dbPath, target.decryptKey, target.wxid)
if (!result.success) {
throw new Error(result.error || '账号重连失败')
}
await window.electronAPI.chat.close()
await window.electronAPI.chat.refreshCache()
await window.electronAPI.chat.connect()
setDbConnected(true, target.dbPath)
setCurrentWxid(target.wxid)
await refreshAccountsState(target.id)
showMessage(`已切换到账号:${target.displayName}`, true)
} catch (e) {
showMessage(`切换账号失败: ${e}`, false)
} finally {
setIsLoadingState(false)
setLoading(false)
}
}
const handleDeleteAccount = (account: AccountProfile) => {
setSecurityConfirm({
show: true,
title: '删除账号',
message: `删除账号 ${account.displayName || account.wxid}?此操作仅删除配置,不删除本地解密数据。`,
onConfirm: async () => {
const result = await configService.deleteAccount(account.id, false)
if (result.success) {
await refreshAccountsState(result.nextActiveAccountId)
showMessage('账号已删除', true)
} else {
showMessage(result.error || '删除账号失败', false)
}
setSecurityConfirm(prev => ({ ...prev, show: false }))
}
})
}
const handleDeleteAccountWithLocalData = (account: AccountProfile) => {
setSecurityConfirm({
show: true,
title: '删除账号并清理本地数据',
message: `将删除账号 ${account.displayName || account.wxid} 的配置,并尝试删除该账号对应的本地解密数据库缓存。`,
onConfirm: async () => {
const result = await configService.deleteAccount(account.id, true)
if (result.success) {
await refreshAccountsState(result.nextActiveAccountId)
showMessage('账号及其本地数据已删除', true)
} else {
showMessage(result.error || '删除账号失败', false)
}
setSecurityConfirm(prev => ({ ...prev, show: false }))
}
})
}
const handleClearCurrentAccountConfig = (deleteLocalData = false) => {
setSecurityConfirm({
show: true,
title: deleteLocalData ? '清除当前账号并删除本地数据' : '清除当前账号',
message: deleteLocalData
? '将清除当前账号配置,并尝试删除该账号对应的本地解密数据库缓存。'
: '将只清除当前账号配置,不影响其他账号和全局设置。',
onConfirm: async () => {
const result = await window.electronAPI.cache.clearCurrentAccount(deleteLocalData)
if (result.success) {
await refreshAccountsState(activeAccountId)
showMessage('当前账号配置已清除', true)
} else {
showMessage(result.error || '清除当前账号失败', false)
}
setSecurityConfirm(prev => ({ ...prev, show: false }))
}
})
}
const handleClearAllAccountConfigs = () => {
setSecurityConfirm({
show: true,
title: '清空全部账号配置',
message: '将删除所有账号配置和账号级密钥/路径信息,不会删除主题、AI、MCP、HTTP API 等通用设置。',
onConfirm: async () => {
const result = await window.electronAPI.cache.clearAllAccountConfigs()
if (result.success) {
await refreshAccountsState()
await loadConfig()
showMessage('已清空全部账号配置', true)
} else {
showMessage(result.error || '清空全部账号配置失败', false)
}
setSecurityConfirm(prev => ({ ...prev, show: false }))
}
})
}
const handleSelectDbPath = async () => {
try {
const result = await dialog.openFile({ title: '选择微信数据库根目录', properties: ['openDirectory'] })
@@ -971,7 +1159,6 @@ function SettingsPage() {
const result = await window.electronAPI.wcdb.testConnection(dbPath, decryptKey, wxid)
if (result.success) {
setIsAccountVerified(true)
await configService.setMyWxid(wxid)
showMessage(`账号目录验证成功:${wxid}`, true)
} else {
setIsAccountVerified(false)
@@ -1013,15 +1200,20 @@ function SettingsPage() {
try {
// 保存数据库相关配置
if (decryptKey) await configService.setDecryptKey(decryptKey)
if (dbPath) await configService.setDbPath(dbPath)
if (wxid) await configService.setMyWxid(wxid)
await configService.setCachePath(cachePath)
let savedAccount: AccountProfile | null = null
const accountPayload = buildAccountPayload()
if (editingAccountId) {
savedAccount = await configService.updateAccount(editingAccountId, accountPayload)
} else if (accountPayload.wxid || accountPayload.dbPath || accountPayload.decryptKey || accountPayload.cachePath) {
savedAccount = await configService.saveAccount(accountPayload)
}
if (savedAccount) {
setEditingAccountId(savedAccount.id)
}
// 保存图片密钥(包括空值)
await configService.setImageXorKey(imageXorKey)
await configService.setImageAesKey(imageAesKey)
// 保存导出路径
if (exportPath) await configService.setExportPath(exportPath)
@@ -1060,6 +1252,8 @@ function SettingsPage() {
setDbConnected(true, dbPath)
}
await refreshAccountsState(savedAccount?.id || editingAccountId)
showMessage('配置保存成功', true)
// 保存成功后更新初始配置,重置变化状态
@@ -1090,7 +1284,8 @@ function SettingsPage() {
aiCustomSystemPrompt,
aiEnableThinking,
aiMessageLimit,
closeToTray
closeToTray,
editingAccountId: savedAccount?.id || editingAccountId
})
setHasUnsavedChanges(false)
} catch (e) {
@@ -1249,9 +1444,64 @@ function SettingsPage() {
{/* 引导窗口按钮 */}
<div className="form-group">
<button className="btn btn-secondary" onClick={handleOpenWelcomeWindow}>
<Zap size={16} />
<Zap size={16} />
</button>
<span className="form-hint">使</span>
<span className="form-hint">使</span>
</div>
<h3 className="section-title"></h3>
<div className="form-group">
<div className="form-hint" style={{ marginBottom: '10px' }}>
{accountsList.find(item => item.id === activeAccountId)?.displayName || '未设置'}
</div>
{accountsList.length > 0 ? (
<div className="wxid-options">
{accountsList.map((account) => (
<button
key={account.id}
className={`wxid-option ${editingAccountId === account.id ? 'is-selected' : ''}`}
onClick={() => handleSelectAccountForEdit(account)}
>
<div className="wxid-option-name">
{account.displayName}
{account.id === activeAccountId ? '(当前激活)' : ''}
</div>
<div className="field-hint">{account.wxid || '未设置 wxid'}</div>
<div className="field-hint">{account.dbPath || '未设置数据库路径'}</div>
</button>
))}
</div>
) : (
<div className="form-hint"></div>
)}
<div className="btn-row" style={{ marginTop: '12px' }}>
<button className="btn btn-secondary" onClick={handleSaveConfig} disabled={isLoading}>
<Save size={16} /> 使
</button>
<button className="btn btn-secondary" onClick={handleSwitchAccountAndReconnect} disabled={!editingAccountId || editingAccountId === activeAccountId || isLoading}>
<RefreshCw size={16} />
</button>
<button
className="btn btn-danger"
onClick={() => {
const account = accountsList.find(item => item.id === editingAccountId)
if (account) handleDeleteAccount(account)
}}
disabled={!editingAccountId || isLoading}
>
<Trash2 size={16} />
</button>
<button
className="btn btn-danger"
onClick={() => {
const account = accountsList.find(item => item.id === editingAccountId)
if (account) handleDeleteAccountWithLocalData(account)
}}
disabled={!editingAccountId || isLoading}
>
<Trash2 size={16} />
</button>
</div>
</div>
{/* 数据库解密部分 */}
@@ -1602,11 +1852,9 @@ function SettingsPage() {
if (result.xorKey !== undefined) {
const xorKeyHex = `0x${result.xorKey.toString(16).padStart(2, '0')}`
setImageXorKey(xorKeyHex)
await configService.setImageXorKey(xorKeyHex)
}
if (result.aesKey) {
setImageAesKey(result.aesKey)
await configService.setImageAesKey(result.aesKey)
}
showMessage('图片密钥获取成功!', true)
setImageKeyStatus('')
@@ -2562,12 +2810,12 @@ function SettingsPage() {
>
</button>
<button
className="btn btn-primary"
onClick={securityConfirm.onConfirm}
>
</button>
<button
className="btn btn-primary"
onClick={securityConfirm.onConfirm}
>
</button>
</div>
</div>
</div>
@@ -2698,10 +2946,16 @@ function SettingsPage() {
<span className="cache-card-label"></span>
</div>
<div className="cache-card-desc"></div>
<button type="button" className="btn btn-secondary cache-card-btn" onClick={handleClearConfig}>
<Trash2 size={14} />
</button>
</div>
<button type="button" className="btn btn-secondary cache-card-btn" onClick={handleClearCurrentAccount}>
<Trash2 size={14} />
</button>
<button type="button" className="btn btn-secondary cache-card-btn" onClick={handleClearCurrentAccountConfig.bind(null, true)}>
<Trash2 size={14} />
</button>
<button type="button" className="btn btn-danger cache-card-btn" onClick={handleClearAllAccounts}>
<Trash2 size={14} />
</button>
</div>
<div className="cache-card cache-card-total">
<div className="cache-card-header">
<Layers size={20} className="cache-card-icon" />
+24 -13
View File
@@ -1,5 +1,5 @@
import { useState, useEffect } from 'react'
import { useNavigate } from 'react-router-dom'
import { useLocation, useNavigate } from 'react-router-dom'
import { useThemeStore } from '../stores/themeStore'
import { useAppStore } from '../stores/appStore'
import { dialog } from '../services/ipc'
@@ -27,7 +27,8 @@ interface WelcomePageProps {
function WelcomePage({ standalone = false }: WelcomePageProps) {
const navigate = useNavigate()
const { isDbConnected, setDbConnected } = useAppStore()
const location = useLocation()
const { isDbConnected, setDbConnected, setMyWxid: setCurrentWxid } = useAppStore()
const appIcon = useThemeStore(state => state.appIcon)
const { enableAuth, disableAuth, isAuthEnabled } = useAuthStore()
@@ -67,6 +68,7 @@ function WelcomePage({ standalone = false }: WelcomePageProps) {
const isMac = platformInfo.platform === 'darwin'
const biometricLabel = isMac ? 'Touch ID' : 'Windows Hello'
const isAddAccountMode = new URLSearchParams(location.search).get('mode') === 'add-account'
useEffect(() => {
const removeStatus = window.electronAPI.wxKey?.onStatus?.((payload) => {
@@ -95,6 +97,7 @@ function WelcomePage({ standalone = false }: WelcomePageProps) {
// 从缓存加载配置
const loadCachedConfig = () => {
if (isAddAccountMode) return
try {
const cached = localStorage.getItem('welcomeConfig')
if (cached) {
@@ -144,7 +147,7 @@ function WelcomePage({ standalone = false }: WelcomePageProps) {
removeStatus?.()
removeImageProgress?.()
}
}, [])
}, [isAddAccountMode])
useEffect(() => {
setWxidOptions([])
@@ -182,6 +185,7 @@ function WelcomePage({ standalone = false }: WelcomePageProps) {
// 保存配置到缓存
useEffect(() => {
if (isAddAccountMode) return
const config = {
dbPath,
cachePath,
@@ -195,7 +199,7 @@ function WelcomePage({ standalone = false }: WelcomePageProps) {
} catch (e) {
console.error('保存配置到缓存失败:', e)
}
}, [dbPath, cachePath, wxid, decryptKey, imageXorKey, imageAesKey])
}, [dbPath, cachePath, wxid, decryptKey, imageXorKey, imageAesKey, isAddAccountMode])
const currentStep = steps[stepIndex]
const rootClassName = `welcome-page${isClosing ? ' is-closing' : ''}${standalone ? ' is-standalone' : ''}`
@@ -548,17 +552,23 @@ function WelcomePage({ standalone = false }: WelcomePageProps) {
try {
// 先保存配置,因为 dataManagementService 需要从配置中读取这些信息
await configService.setDbPath(dbPath)
await configService.setDecryptKey(decryptKey)
await configService.setMyWxid(wxid)
await configService.setCachePath(cachePath)
if (imageXorKey) {
await configService.setImageXorKey(imageXorKey)
}
if (imageAesKey) {
await configService.setImageAesKey(imageAesKey)
const savedAccount = await configService.saveAccount({
dbPath,
decryptKey,
wxid,
cachePath,
imageXorKey,
imageAesKey,
displayName: wxid || '未命名账号'
})
if (!savedAccount) {
throw new Error('保存账号配置失败')
}
await configService.setActiveAccount(savedAccount.id)
setCurrentWxid(wxid)
setDecryptStatus('正在测试数据库连接...')
const result = await window.electronAPI.wcdb.testConnection(dbPath, decryptKey, wxid)
@@ -625,6 +635,7 @@ function WelcomePage({ standalone = false }: WelcomePageProps) {
// 在跳转前设置连接状态
setDbConnected(true, dbPath)
setCurrentWxid(wxid)
if (standalone) {
setIsClosing(true)
+28 -1
View File
@@ -1,5 +1,6 @@
// 配置服务 - 封装 Electron Store
import { config } from './ipc'
import { accounts, config } from './ipc'
import type { AccountProfile, AccountProfileInput, AccountProfilePatch } from '../types/account'
// 配置键名
export const CONFIG_KEYS = {
@@ -37,6 +38,8 @@ export const CONFIG_KEYS = {
CLOSE_TO_TRAY: 'closeToTray'
} as const
export type { AccountProfile, AccountProfileInput, AccountProfilePatch }
// 当前协议版本 - 更新协议内容时递增此版本号
export const CURRENT_AGREEMENT_VERSION = 2
@@ -161,6 +164,30 @@ export async function setMyWxid(wxid: string): Promise<void> {
await config.set(CONFIG_KEYS.MY_WXID, wxid)
}
export async function listAccounts(): Promise<AccountProfile[]> {
return accounts.list()
}
export async function getActiveAccount(): Promise<AccountProfile | null> {
return accounts.getActive()
}
export async function setActiveAccount(accountId: string): Promise<AccountProfile | null> {
return accounts.setActive(accountId)
}
export async function saveAccount(profile: AccountProfileInput): Promise<AccountProfile | null> {
return accounts.save(profile)
}
export async function updateAccount(accountId: string, patch: AccountProfilePatch): Promise<AccountProfile | null> {
return accounts.update(accountId, patch)
}
export async function deleteAccount(accountId: string, deleteLocalData = false): Promise<{ success: boolean; error?: string; deleted?: AccountProfile | null; nextActiveAccountId?: string }> {
return accounts.delete(accountId, deleteLocalData)
}
// 获取主题
export async function getTheme(): Promise<'light' | 'dark'> {
const value = await config.get(CONFIG_KEYS.THEME)
+9
View File
@@ -6,6 +6,15 @@ export const config = {
set: (key: string, value: unknown) => window.electronAPI.config.set(key, value)
}
export const accounts = {
list: () => window.electronAPI.accounts.list(),
getActive: () => window.electronAPI.accounts.getActive(),
setActive: (accountId: string) => window.electronAPI.accounts.setActive(accountId),
save: (profile: Parameters<typeof window.electronAPI.accounts.save>[0]) => window.electronAPI.accounts.save(profile),
update: (accountId: string, patch: Parameters<typeof window.electronAPI.accounts.update>[1]) => window.electronAPI.accounts.update(accountId, patch),
delete: (accountId: string, deleteLocalData?: boolean) => window.electronAPI.accounts.delete(accountId, deleteLocalData)
}
// 数据库
export const db = {
open: (dbPath: string, key?: string) => window.electronAPI.db.open(dbPath, key),
+19
View File
@@ -0,0 +1,19 @@
export interface AccountProfile {
id: string
wxid: string
dbPath: string
decryptKey: string
cachePath: string
imageXorKey: string
imageAesKey: string
displayName: string
createdAt: number
updatedAt: number
lastUsedAt: number
}
export type AccountProfileInput = Omit<AccountProfile, 'id' | 'createdAt' | 'updatedAt' | 'lastUsedAt'>
export type AccountProfilePatch = Partial<AccountProfileInput> & {
displayName?: string
}
+12 -1
View File
@@ -1,5 +1,6 @@
import type { ChatSession, Message, Contact, ContactInfo } from './models'
import type { SummaryResult } from './ai'
import type { AccountProfile } from './account'
export interface ImageListItem {
imagePath: string
@@ -33,7 +34,7 @@ export interface ElectronAPI {
openAnnualReportWindow: (year: number) => Promise<boolean>
openAgreementWindow: () => Promise<boolean>
openPurchaseWindow: () => Promise<boolean>
openWelcomeWindow: () => Promise<boolean>
openWelcomeWindow: (mode?: 'default' | 'add-account') => Promise<boolean>
completeWelcome: () => Promise<boolean>
isChatWindowOpen: () => Promise<boolean>
closeChatWindow: () => Promise<boolean>
@@ -57,6 +58,14 @@ export interface ElectronAPI {
getTldCache: () => Promise<{ tlds: string[]; updatedAt: number } | null>
setTldCache: (tlds: string[]) => Promise<void>
}
accounts: {
list: () => Promise<AccountProfile[]>
getActive: () => Promise<AccountProfile | null>
setActive: (accountId: string) => Promise<AccountProfile | null>
save: (profile: Omit<AccountProfile, 'id' | 'createdAt' | 'updatedAt' | 'lastUsedAt'>) => Promise<AccountProfile | null>
update: (accountId: string, patch: Partial<Omit<AccountProfile, 'id' | 'createdAt' | 'updatedAt' | 'lastUsedAt'>>) => Promise<AccountProfile | null>
delete: (accountId: string, deleteLocalData?: boolean) => Promise<{ success: boolean; error?: string; deleted?: AccountProfile | null; nextActiveAccountId?: string }>
}
db: {
open: (dbPath: string, key?: string) => Promise<boolean>
query: <T = unknown>(sql: string, params?: unknown[]) => Promise<T[]>
@@ -812,6 +821,8 @@ export interface ElectronAPI {
clearDatabases: () => Promise<{ success: boolean; error?: string }>
clearAll: () => Promise<{ success: boolean; error?: string }>
clearConfig: () => Promise<{ success: boolean; error?: string }>
clearCurrentAccount: (deleteLocalData?: boolean) => Promise<{ success: boolean; error?: string }>
clearAllAccountConfigs: () => Promise<{ success: boolean; error?: string }>
getCacheSize: () => Promise<{
success: boolean;
error?: string;