88 <button
99 type =" button"
1010 class =" group relative w-[40px] h-[40px] rounded-md overflow-hidden bg-gray-300 flex-shrink-0 ring-1 ring-transparent transition hover:ring-[#07b75b]/40"
11- title =" 账号信息 "
11+ : title =" avatarButtonTitle "
1212 @click =" openAccountDialog"
1313 >
1414 <img v-if =" selfAvatarUrl" :src =" selfAvatarUrl" alt =" avatar" class =" w-full h-full object-cover" />
292292 class =" account-info-dialog fixed inset-0 z-[130] flex items-center justify-center bg-black/35 px-4"
293293 @click.self =" closeAccountDialog"
294294 >
295- <div class =" account-info-dialog-panel w-full max-w-[440px ] overflow-hidden rounded-[12px] border border-[#e7e7e7] bg-white shadow-2xl" >
295+ <div class =" account-info-dialog-panel w-full max-w-[520px ] overflow-hidden rounded-[12px] border border-[#e7e7e7] bg-white shadow-2xl" >
296296 <div class =" flex items-center justify-between border-b border-[#efefef] px-4 py-3" >
297- <div class =" text-[14px] font-semibold text-[#222]" >当前账号信息 </div >
297+ <div class =" text-[14px] font-semibold text-[#222]" >账号切换 </div >
298298 <button
299299 type =" button"
300300 class =" flex h-7 w-7 items-center justify-center rounded-md text-[#888] transition hover:bg-[#f2f2f2] hover:text-[#222]"
309309 </div >
310310
311311 <div class =" space-y-3 px-4 py-4" >
312+ <div class =" rounded-[10px] border border-[#ededed] bg-[#fafafa] p-2" >
313+ <div class =" mb-2 flex items-center justify-between px-1" >
314+ <div class =" text-[12px] font-medium text-[#444]" >可切换账号</div >
315+ <button
316+ type =" button"
317+ class =" text-[11px] text-[#07b75b] hover:text-[#04994c] disabled:cursor-not-allowed disabled:opacity-50"
318+ :disabled =" chatAccounts.loading"
319+ @click =" refreshSwitchableAccounts"
320+ >
321+ {{ chatAccounts.loading ? '刷新中...' : '刷新' }}
322+ </button >
323+ </div >
324+
325+ <div v-if =" chatAccounts.loading && !switchableAccountItems.length" class =" px-1 py-2 text-[12px] text-[#7a7a7a]" >
326+ 正在加载可切换账号...
327+ </div >
328+ <div v-else-if =" !switchableAccountItems.length" class =" space-y-1 px-1 py-2" >
329+ <div class =" text-[12px] text-[#666]" >暂无可切换账号</div >
330+ <div class =" text-[11px] leading-relaxed text-[#8a8a8a]" >
331+ 只有已经获取并保存“数据库密钥 + 图片密钥”的账号会出现在这里。
332+ </div >
333+ <div v-if =" chatAccounts.error" class =" text-[11px] text-red-600" >{{ chatAccounts.error }}</div >
334+ </div >
335+ <div v-else class =" max-h-[260px] space-y-1 overflow-y-auto pr-1" >
336+ <button
337+ v-for =" item in switchableAccountItems"
338+ :key =" item.account"
339+ type =" button"
340+ class =" flex w-full items-center gap-3 rounded-[8px] border px-2.5 py-2 text-left transition"
341+ :class =" item.active ? 'border-[#07b75b]/45 bg-[#ecfff5]' : 'border-transparent bg-white hover:border-[#e4e4e4] hover:bg-[#f7f7f7]'"
342+ :disabled =" accountDeleteLoading"
343+ @click =" selectAccountFromDialog(item.account)"
344+ >
345+ <div class =" h-[38px] w-[38px] shrink-0 overflow-hidden rounded-md bg-gray-300" >
346+ <img
347+ v-if =" item.avatarUrl && !accountAvatarBroken[item.account]"
348+ :src =" item.avatarUrl"
349+ alt =" avatar"
350+ class =" h-full w-full object-cover"
351+ loading =" lazy"
352+ @error =" markAccountAvatarBroken(item.account)"
353+ />
354+ <div
355+ v-else
356+ class =" flex h-full w-full items-center justify-center text-xs font-bold text-white"
357+ :style =" { backgroundColor: '#4B5563' }"
358+ >
359+ {{ accountFallbackText(item.account) }}
360+ </div >
361+ </div >
362+ <div class =" min-w-0 flex-1" >
363+ <div class =" truncate text-[13px] font-semibold text-[#222]" >{{ item.account }}</div >
364+ </div >
365+ <div class =" flex shrink-0 items-center gap-1" >
366+ <span class =" rounded-full bg-[#eefbf4] px-1.5 py-0.5 text-[10px] font-medium text-[#07964c]" >DB</span >
367+ <span class =" rounded-full bg-[#eefbf4] px-1.5 py-0.5 text-[10px] font-medium text-[#07964c]" >图片</span >
368+ </div >
369+ </button >
370+ </div >
371+ </div >
372+
312373 <div v-if =" accountInfoLoading" class =" text-[12px] text-[#7a7a7a]" >正在加载账号信息...</div >
313374 <template v-else >
314375 <div class =" flex items-center gap-3" >
329390 </div >
330391
331392 <div class =" rounded-[8px] border border-[#ededed] bg-[#fafafa] px-3 py-2 text-[12px] text-[#5f5f5f] space-y-1.5" >
332- <div class =" flex items-start justify-between gap-3" >
333- <span class =" text-[#8a8a8a] shrink-0" >读取模式</span >
334- <span class =" font-medium text-[#333]" >{{ accountModeText }}</span >
335- </div >
336393 <div class =" flex items-start justify-between gap-3" >
337394 <span class =" text-[#8a8a8a] shrink-0" >本地解密库数量</span >
338395 <span class =" font-medium text-[#333]" >{{ accountInfo?.database_count ?? '—' }}</span >
@@ -381,7 +438,10 @@ import { useThemeStore } from '~/stores/theme'
381438const route = useRoute ()
382439
383440const chatAccounts = useChatAccountsStore ()
384- const { selectedAccount } = storeToRefs (chatAccounts)
441+ const {
442+ selectedAccount ,
443+ switchableAccounts ,
444+ } = storeToRefs (chatAccounts)
385445
386446const privacyStore = usePrivacyStore ()
387447const { privacyMode } = storeToRefs (privacyStore)
@@ -405,14 +465,9 @@ const accountDeleteLoading = ref(false)
405465const accountDeleteError = ref (' ' )
406466const accountInfoApiUnsupported = ref (false )
407467const deleteAccountApiUnsupported = ref (false )
468+ const accountAvatarBroken = ref ({})
408469
409- const accountModeText = computed (() => {
410- const mode = String (accountInfo .value ? .mode || ' ' ).trim ()
411- const realtimeAvailable = !! accountInfo .value ? .realtimeAvailable || !! accountInfo .value ? .realtime ? .available
412- if (mode === ' direct' || realtimeAvailable) return ' 原始 WCDB 实时读取'
413- if (mode === ' decrypted' || accountInfo .value ? .hasDecryptedDbs ) return ' 旧本地解密库'
414- return ' —'
415- })
470+ const normalizeAccountName = (value ) => String (value || ' ' ).trim ()
416471
417472const accountDataPath = computed (() => {
418473 const info = accountInfo .value || {}
@@ -425,6 +480,44 @@ const accountDataPath = computed(() => {
425480 )
426481})
427482
483+ const avatarButtonTitle = computed (() => {
484+ const count = Array .isArray (switchableAccounts .value ) ? switchableAccounts .value .length : 0
485+ if (count > 1 ) return ' 切换账号'
486+ return ' 账号信息'
487+ })
488+
489+ const accountAvatarUrl = (account ) => {
490+ const acc = normalizeAccountName (account)
491+ if (! acc) return ' '
492+ return ` ${ apiBase} /chat/avatar?account=${ encodeURIComponent (acc)} &username=${ encodeURIComponent (acc)} `
493+ }
494+
495+ const accountFallbackText = (account ) => {
496+ const value = normalizeAccountName (account)
497+ if (! value) return ' 我'
498+ return value .slice (0 , 1 ).toUpperCase ()
499+ }
500+
501+ const switchableAccountItems = computed (() => {
502+ const list = Array .isArray (switchableAccounts .value ) ? switchableAccounts .value : []
503+ return list
504+ .map ((account ) => normalizeAccountName (account))
505+ .filter (Boolean )
506+ .map ((account ) => {
507+ return {
508+ account,
509+ active: account === normalizeAccountName (selectedAccount .value ),
510+ avatarUrl: accountAvatarUrl (account),
511+ }
512+ })
513+ })
514+
515+ const markAccountAvatarBroken = (account ) => {
516+ const acc = normalizeAccountName (account)
517+ if (! acc) return
518+ accountAvatarBroken .value = { ... accountAvatarBroken .value , [acc]: true }
519+ }
520+
428521const sessionUpdatedAtText = computed (() => {
429522 const ts = Number (accountInfo .value ? .session_updated_at || 0 )
430523 if (! Number .isFinite (ts) || ts <= 0 ) return ' —'
@@ -553,9 +646,24 @@ const apiBase = useApiBase()
553646const selfAvatarUrl = computed (() => {
554647 const acc = String (selectedAccount .value || ' ' ).trim ()
555648 if (! acc) return ' '
556- return ` ${ apiBase } /chat/avatar?account= ${ encodeURIComponent (acc)} &username= ${ encodeURIComponent (acc) } `
649+ return accountAvatarUrl (acc)
557650})
558651
652+ const refreshSwitchableAccounts = async () => {
653+ await chatAccounts .ensureLoaded ({ force: true })
654+ }
655+
656+ const selectAccountFromDialog = async (account ) => {
657+ const next = normalizeAccountName (account)
658+ if (! next || accountDeleteLoading .value ) return
659+ if (next === normalizeAccountName (selectedAccount .value )) return
660+
661+ accountInfoError .value = ' '
662+ accountDeleteError .value = ' '
663+ accountInfo .value = null
664+ chatAccounts .setSelectedAccount (next)
665+ }
666+
559667const isChatRoute = computed (() => route .path ? .startsWith (' /chat' ))
560668const isEditsRoute = computed (() => route .path ? .startsWith (' /edits' ))
561669const isSnsRoute = computed (() => route .path ? .startsWith (' /sns' ))
0 commit comments