mirror of
https://github.com/hicccc77/WeFlow.git
synced 2026-03-25 07:16:51 +00:00
feat(sidebar): add account data clear action and detail feedback
This commit is contained in:
@@ -10,8 +10,11 @@
|
||||
&.collapsed {
|
||||
width: 64px;
|
||||
|
||||
.sidebar-user-card {
|
||||
.sidebar-user-card-wrap {
|
||||
margin: 0 8px 8px;
|
||||
}
|
||||
|
||||
.sidebar-user-card {
|
||||
padding: 8px 0;
|
||||
justify-content: center;
|
||||
|
||||
@@ -37,8 +40,39 @@
|
||||
}
|
||||
}
|
||||
|
||||
.sidebar-user-card {
|
||||
.sidebar-user-card-wrap {
|
||||
position: relative;
|
||||
margin: 0 12px 10px;
|
||||
}
|
||||
|
||||
.sidebar-user-clear-trigger {
|
||||
position: absolute;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: calc(100% + 8px);
|
||||
z-index: 12;
|
||||
border: 1px solid rgba(255, 59, 48, 0.28);
|
||||
border-radius: 10px;
|
||||
background: var(--bg-secondary);
|
||||
color: #d93025;
|
||||
padding: 8px 10px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
font-size: 12px;
|
||||
font-weight: 600;
|
||||
cursor: pointer;
|
||||
box-shadow: 0 8px 20px rgba(15, 23, 42, 0.12);
|
||||
transition: background 0.2s ease, color 0.2s ease, border-color 0.2s ease;
|
||||
|
||||
&:hover {
|
||||
background: rgba(255, 59, 48, 0.08);
|
||||
border-color: rgba(255, 59, 48, 0.46);
|
||||
}
|
||||
}
|
||||
|
||||
.sidebar-user-card {
|
||||
width: 100%;
|
||||
padding: 10px;
|
||||
border: 1px solid var(--border-color);
|
||||
border-radius: 12px;
|
||||
@@ -47,6 +81,18 @@
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
min-height: 56px;
|
||||
cursor: pointer;
|
||||
transition: border-color 0.2s ease, background 0.2s ease, box-shadow 0.2s ease;
|
||||
|
||||
&:hover {
|
||||
border-color: rgba(99, 102, 241, 0.32);
|
||||
background: var(--bg-tertiary);
|
||||
}
|
||||
|
||||
&.menu-open {
|
||||
border-color: rgba(99, 102, 241, 0.44);
|
||||
box-shadow: 0 0 0 2px rgba(99, 102, 241, 0.12);
|
||||
}
|
||||
|
||||
.user-avatar {
|
||||
width: 36px;
|
||||
@@ -74,6 +120,7 @@
|
||||
|
||||
.user-meta {
|
||||
min-width: 0;
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.user-name {
|
||||
@@ -93,6 +140,17 @@
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
.user-menu-caret {
|
||||
color: var(--text-tertiary);
|
||||
display: inline-flex;
|
||||
transition: transform 0.2s ease, color 0.2s ease;
|
||||
|
||||
&.open {
|
||||
transform: rotate(180deg);
|
||||
color: var(--text-secondary);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.nav-menu {
|
||||
@@ -206,6 +264,82 @@
|
||||
}
|
||||
}
|
||||
|
||||
.sidebar-clear-dialog-overlay {
|
||||
position: fixed;
|
||||
inset: 0;
|
||||
background: rgba(15, 23, 42, 0.3);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
z-index: 1100;
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
.sidebar-clear-dialog {
|
||||
width: min(460px, 100%);
|
||||
background: var(--bg-secondary);
|
||||
border: 1px solid var(--border-color);
|
||||
border-radius: 16px;
|
||||
box-shadow: 0 18px 40px rgba(15, 23, 42, 0.24);
|
||||
padding: 18px 18px 16px;
|
||||
|
||||
h3 {
|
||||
margin: 0;
|
||||
font-size: 16px;
|
||||
color: var(--text-primary);
|
||||
}
|
||||
|
||||
p {
|
||||
margin: 10px 0 0;
|
||||
font-size: 13px;
|
||||
line-height: 1.6;
|
||||
color: var(--text-secondary);
|
||||
}
|
||||
}
|
||||
|
||||
.sidebar-clear-options {
|
||||
margin-top: 14px;
|
||||
display: flex;
|
||||
gap: 14px;
|
||||
flex-wrap: wrap;
|
||||
|
||||
label {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
font-size: 13px;
|
||||
color: var(--text-primary);
|
||||
}
|
||||
}
|
||||
|
||||
.sidebar-clear-actions {
|
||||
margin-top: 18px;
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
gap: 10px;
|
||||
|
||||
button {
|
||||
border: 1px solid var(--border-color);
|
||||
border-radius: 10px;
|
||||
padding: 8px 14px;
|
||||
font-size: 13px;
|
||||
cursor: pointer;
|
||||
background: var(--bg-secondary);
|
||||
color: var(--text-primary);
|
||||
}
|
||||
|
||||
button:disabled {
|
||||
cursor: not-allowed;
|
||||
opacity: 0.6;
|
||||
}
|
||||
|
||||
.danger {
|
||||
border-color: #ef4444;
|
||||
background: #ef4444;
|
||||
color: #fff;
|
||||
}
|
||||
}
|
||||
|
||||
// 繁花如梦主题:侧边栏毛玻璃 + 激活项用主品牌色
|
||||
[data-theme="blossom-dream"] .sidebar {
|
||||
background: rgba(255, 255, 255, 0.6);
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { useState, useEffect } from 'react'
|
||||
import { useState, useEffect, useRef } from 'react'
|
||||
import { NavLink, useLocation } from 'react-router-dom'
|
||||
import { Home, MessageSquare, BarChart3, Users, FileText, Database, Settings, ChevronLeft, ChevronRight, Download, Aperture, UserCircle, Lock } from 'lucide-react'
|
||||
import { Home, MessageSquare, BarChart3, Users, FileText, Settings, ChevronLeft, ChevronRight, Download, Aperture, UserCircle, Lock, ChevronUp, Trash2 } from 'lucide-react'
|
||||
import { useAppStore } from '../stores/appStore'
|
||||
import * as configService from '../services/config'
|
||||
import { onExportSessionStatus, requestExportSessionStatus } from '../services/exportBridge'
|
||||
@@ -69,12 +69,30 @@ function Sidebar() {
|
||||
wxid: '',
|
||||
displayName: '未识别用户'
|
||||
})
|
||||
const [isAccountMenuOpen, setIsAccountMenuOpen] = useState(false)
|
||||
const [showClearAccountDialog, setShowClearAccountDialog] = useState(false)
|
||||
const [shouldClearCacheData, setShouldClearCacheData] = useState(false)
|
||||
const [shouldClearExportData, setShouldClearExportData] = useState(false)
|
||||
const [isClearingAccountData, setIsClearingAccountData] = useState(false)
|
||||
const accountCardWrapRef = useRef<HTMLDivElement | null>(null)
|
||||
const setLocked = useAppStore(state => state.setLocked)
|
||||
|
||||
useEffect(() => {
|
||||
window.electronAPI.auth.verifyEnabled().then(setAuthEnabled)
|
||||
}, [])
|
||||
|
||||
useEffect(() => {
|
||||
const handleClickOutside = (event: MouseEvent) => {
|
||||
if (!isAccountMenuOpen) return
|
||||
const target = event.target as Node | null
|
||||
if (accountCardWrapRef.current && target && !accountCardWrapRef.current.contains(target)) {
|
||||
setIsAccountMenuOpen(false)
|
||||
}
|
||||
}
|
||||
document.addEventListener('mousedown', handleClickOutside)
|
||||
return () => document.removeEventListener('mousedown', handleClickOutside)
|
||||
}, [isAccountMenuOpen])
|
||||
|
||||
useEffect(() => {
|
||||
const unsubscribe = onExportSessionStatus((payload) => {
|
||||
const countFromPayload = typeof payload?.activeTaskCount === 'number'
|
||||
@@ -235,6 +253,68 @@ function Sidebar() {
|
||||
return location.pathname === path || location.pathname.startsWith(`${path}/`)
|
||||
}
|
||||
const exportTaskBadge = activeExportTaskCount > 99 ? '99+' : `${activeExportTaskCount}`
|
||||
const canConfirmClear = shouldClearCacheData || shouldClearExportData
|
||||
|
||||
const resetClearDialogState = () => {
|
||||
setShouldClearCacheData(false)
|
||||
setShouldClearExportData(false)
|
||||
setShowClearAccountDialog(false)
|
||||
}
|
||||
|
||||
const openClearAccountDialog = () => {
|
||||
setIsAccountMenuOpen(false)
|
||||
setShouldClearCacheData(false)
|
||||
setShouldClearExportData(false)
|
||||
setShowClearAccountDialog(true)
|
||||
}
|
||||
|
||||
const handleConfirmClearAccountData = async () => {
|
||||
if (!canConfirmClear || isClearingAccountData) return
|
||||
setIsClearingAccountData(true)
|
||||
try {
|
||||
const result = await window.electronAPI.chat.clearCurrentAccountData({
|
||||
clearCache: shouldClearCacheData,
|
||||
clearExports: shouldClearExportData
|
||||
})
|
||||
if (!result.success) {
|
||||
window.alert(result.error || '清理失败,请稍后重试。')
|
||||
return
|
||||
}
|
||||
window.localStorage.removeItem(SIDEBAR_USER_PROFILE_CACHE_KEY)
|
||||
setUserProfile({ wxid: '', displayName: '未识别用户' })
|
||||
window.dispatchEvent(new Event('wxid-changed'))
|
||||
|
||||
const removedPaths = Array.isArray(result.removedPaths) ? result.removedPaths : []
|
||||
const selectedScopes = [
|
||||
shouldClearCacheData ? '缓存数据' : '',
|
||||
shouldClearExportData ? '导出数据' : ''
|
||||
].filter(Boolean)
|
||||
const detailLines: string[] = [
|
||||
`清理范围:${selectedScopes.join('、') || '未选择'}`,
|
||||
`已清理项目:${removedPaths.length} 项`
|
||||
]
|
||||
if (removedPaths.length > 0) {
|
||||
detailLines.push('', '清理明细(最多显示 8 项):')
|
||||
for (const [index, path] of removedPaths.slice(0, 8).entries()) {
|
||||
detailLines.push(`${index + 1}. ${path}`)
|
||||
}
|
||||
if (removedPaths.length > 8) {
|
||||
detailLines.push(`... 其余 ${removedPaths.length - 8} 项已省略`)
|
||||
}
|
||||
}
|
||||
if (result.warning) {
|
||||
detailLines.push('', `注意:${result.warning}`)
|
||||
}
|
||||
window.alert(`账号数据清理完成。\n\n${detailLines.join('\n')}\n\n为保障数据安全,WeFlow 已清除该账号本地缓存/导出相关数据。若需再次获取数据,请手动登录微信客户端并重新在 WeFlow 完成配置。`)
|
||||
resetClearDialogState()
|
||||
window.location.reload()
|
||||
} catch (error) {
|
||||
console.error('清理账号数据失败:', error)
|
||||
window.alert('清理失败,请稍后重试。')
|
||||
} finally {
|
||||
setIsClearingAccountData(false)
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<aside className={`sidebar ${collapsed ? 'collapsed' : ''}`}>
|
||||
@@ -331,16 +411,42 @@ function Sidebar() {
|
||||
</nav>
|
||||
|
||||
<div className="sidebar-footer">
|
||||
<div
|
||||
className="sidebar-user-card"
|
||||
title={collapsed ? `${userProfile.displayName}${userProfile.wxid ? `\n${userProfile.wxid}` : ''}` : undefined}
|
||||
>
|
||||
<div className="user-avatar">
|
||||
{userProfile.avatarUrl ? <img src={userProfile.avatarUrl} alt="" /> : <span>{getAvatarLetter(userProfile.displayName)}</span>}
|
||||
</div>
|
||||
<div className="user-meta">
|
||||
<div className="user-name">{userProfile.displayName}</div>
|
||||
<div className="user-wxid">{userProfile.wxid || 'wxid 未识别'}</div>
|
||||
<div className="sidebar-user-card-wrap" ref={accountCardWrapRef}>
|
||||
{isAccountMenuOpen && (
|
||||
<button
|
||||
className="sidebar-user-clear-trigger"
|
||||
onClick={openClearAccountDialog}
|
||||
type="button"
|
||||
>
|
||||
<Trash2 size={14} />
|
||||
<span>清除此账号所有数据</span>
|
||||
</button>
|
||||
)}
|
||||
<div
|
||||
className={`sidebar-user-card ${isAccountMenuOpen ? 'menu-open' : ''}`}
|
||||
title={collapsed ? `${userProfile.displayName}${userProfile.wxid ? `\n${userProfile.wxid}` : ''}` : undefined}
|
||||
onClick={() => setIsAccountMenuOpen(prev => !prev)}
|
||||
role="button"
|
||||
tabIndex={0}
|
||||
onKeyDown={(event) => {
|
||||
if (event.key === 'Enter' || event.key === ' ') {
|
||||
event.preventDefault()
|
||||
setIsAccountMenuOpen(prev => !prev)
|
||||
}
|
||||
}}
|
||||
>
|
||||
<div className="user-avatar">
|
||||
{userProfile.avatarUrl ? <img src={userProfile.avatarUrl} alt="" /> : <span>{getAvatarLetter(userProfile.displayName)}</span>}
|
||||
</div>
|
||||
<div className="user-meta">
|
||||
<div className="user-name">{userProfile.displayName}</div>
|
||||
<div className="user-wxid">{userProfile.wxid || 'wxid 未识别'}</div>
|
||||
</div>
|
||||
{!collapsed && (
|
||||
<span className={`user-menu-caret ${isAccountMenuOpen ? 'open' : ''}`}>
|
||||
<ChevronUp size={14} />
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -374,6 +480,49 @@ function Sidebar() {
|
||||
{collapsed ? <ChevronRight size={18} /> : <ChevronLeft size={18} />}
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{showClearAccountDialog && (
|
||||
<div className="sidebar-clear-dialog-overlay" onClick={() => !isClearingAccountData && resetClearDialogState()}>
|
||||
<div className="sidebar-clear-dialog" role="dialog" aria-modal="true" onClick={(event) => event.stopPropagation()}>
|
||||
<h3>清除此账号所有数据</h3>
|
||||
<p>
|
||||
操作后可将该账户在 weflow 下产生的所有缓存文件、导出文件等彻底清除。
|
||||
清除后必须手动登录微信客户端 weflow 才能再次获取,保障你的数据安全。
|
||||
</p>
|
||||
<div className="sidebar-clear-options">
|
||||
<label>
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={shouldClearCacheData}
|
||||
onChange={(event) => setShouldClearCacheData(event.target.checked)}
|
||||
disabled={isClearingAccountData}
|
||||
/>
|
||||
缓存数据
|
||||
</label>
|
||||
<label>
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={shouldClearExportData}
|
||||
onChange={(event) => setShouldClearExportData(event.target.checked)}
|
||||
disabled={isClearingAccountData}
|
||||
/>
|
||||
导出数据
|
||||
</label>
|
||||
</div>
|
||||
<div className="sidebar-clear-actions">
|
||||
<button type="button" onClick={resetClearDialogState} disabled={isClearingAccountData}>取消</button>
|
||||
<button
|
||||
type="button"
|
||||
className="danger"
|
||||
disabled={!canConfirmClear || isClearingAccountData}
|
||||
onClick={handleConfirmClearAccountData}
|
||||
>
|
||||
{isClearingAccountData ? '清除中...' : '确认清除'}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</aside>
|
||||
)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user