mirror of
https://github.com/hicccc77/WeFlow.git
synced 2026-03-25 07:16:51 +00:00
feat(export): refine top card copy and sns header count
This commit is contained in:
@@ -320,6 +320,13 @@
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.card-title-meta {
|
||||
color: var(--text-secondary);
|
||||
font-size: 12px;
|
||||
white-space: nowrap;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.card-refresh-hint {
|
||||
color: var(--text-tertiary);
|
||||
font-size: 11px;
|
||||
@@ -1108,6 +1115,8 @@
|
||||
}
|
||||
|
||||
.table-wrap {
|
||||
--contacts-message-col-width: 92px;
|
||||
--contacts-action-col-width: 172px;
|
||||
overflow: hidden;
|
||||
border: 1px solid var(--border-color);
|
||||
border-radius: 10px;
|
||||
@@ -1227,6 +1236,37 @@
|
||||
word-break: break-word;
|
||||
}
|
||||
|
||||
.contacts-list-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
padding: 10px 12px 8px;
|
||||
border-bottom: 1px solid color-mix(in srgb, var(--border-color) 85%, transparent);
|
||||
background: color-mix(in srgb, var(--bg-primary) 78%, var(--bg-secondary));
|
||||
font-size: 12px;
|
||||
color: var(--text-tertiary);
|
||||
font-weight: 600;
|
||||
letter-spacing: 0.01em;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.contacts-list-header-main {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.contacts-list-header-count {
|
||||
width: var(--contacts-message-col-width);
|
||||
text-align: right;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.contacts-list-header-actions {
|
||||
width: var(--contacts-action-col-width);
|
||||
text-align: right;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.contacts-list {
|
||||
flex: 1;
|
||||
min-height: 0;
|
||||
@@ -1335,21 +1375,15 @@
|
||||
}
|
||||
|
||||
.row-message-count {
|
||||
min-width: 82px;
|
||||
width: var(--contacts-message-col-width);
|
||||
min-width: var(--contacts-message-col-width);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: flex-end;
|
||||
gap: 2px;
|
||||
justify-content: center;
|
||||
flex-shrink: 0;
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
.row-message-count-label {
|
||||
font-size: 11px;
|
||||
color: var(--text-tertiary);
|
||||
line-height: 1;
|
||||
}
|
||||
|
||||
.row-message-count-value {
|
||||
margin: 0;
|
||||
font-size: 13px;
|
||||
@@ -1504,6 +1538,8 @@
|
||||
flex-direction: column;
|
||||
align-items: flex-end;
|
||||
gap: 4px;
|
||||
width: var(--contacts-action-col-width);
|
||||
flex-shrink: 0;
|
||||
|
||||
.row-action-main {
|
||||
display: inline-flex;
|
||||
@@ -2280,12 +2316,22 @@
|
||||
}
|
||||
|
||||
@media (max-width: 720px) {
|
||||
.table-wrap .row-message-count {
|
||||
min-width: 66px;
|
||||
.table-wrap {
|
||||
--contacts-message-col-width: 66px;
|
||||
--contacts-action-col-width: 148px;
|
||||
}
|
||||
|
||||
.table-wrap .row-message-count-label {
|
||||
display: none;
|
||||
.table-wrap .contacts-list-header {
|
||||
gap: 8px;
|
||||
padding: 8px 10px 6px;
|
||||
}
|
||||
|
||||
.table-wrap .contacts-list {
|
||||
padding: 0 10px 10px;
|
||||
}
|
||||
|
||||
.table-wrap .row-message-count {
|
||||
min-width: var(--contacts-message-col-width);
|
||||
}
|
||||
|
||||
.diag-panel-header {
|
||||
|
||||
@@ -2610,7 +2610,7 @@ function ExportPage() {
|
||||
...item,
|
||||
label: contentTypeLabels[item.type],
|
||||
stats: [
|
||||
{ label: '已导出', value: exported }
|
||||
{ label: '已导出', value: exported, unit: '个对话' }
|
||||
]
|
||||
}
|
||||
})
|
||||
@@ -2619,9 +2619,9 @@ function ExportPage() {
|
||||
type: 'sns' as ContentCardType,
|
||||
icon: Aperture,
|
||||
label: '朋友圈',
|
||||
headerCount: snsStats.totalPosts,
|
||||
stats: [
|
||||
{ label: '朋友圈条数', value: snsStats.totalPosts },
|
||||
{ label: '已导出', value: snsExportedCount }
|
||||
{ label: '已导出', value: snsExportedCount, unit: '条' }
|
||||
]
|
||||
}
|
||||
|
||||
@@ -3661,6 +3661,15 @@ function ExportPage() {
|
||||
<div key={card.type} className="content-card">
|
||||
<div className="card-header">
|
||||
<div className="card-title"><Icon size={16} /> {card.label}</div>
|
||||
{card.type === 'sns' && (
|
||||
<div className="card-title-meta">
|
||||
{isCardStatsLoading ? (
|
||||
<span className="count-loading">
|
||||
统计中<span className="animated-ellipsis" aria-hidden="true">...</span>
|
||||
</span>
|
||||
) : `${card.headerCount.toLocaleString()} 条`}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
<div className="card-stats">
|
||||
{card.stats.map((stat) => (
|
||||
@@ -3671,7 +3680,7 @@ function ExportPage() {
|
||||
<span className="count-loading">
|
||||
统计中<span className="animated-ellipsis" aria-hidden="true">...</span>
|
||||
</span>
|
||||
) : stat.value.toLocaleString()}
|
||||
) : `${stat.value.toLocaleString()} ${stat.unit}`}
|
||||
</strong>
|
||||
</div>
|
||||
))}
|
||||
@@ -3951,87 +3960,93 @@ function ExportPage() {
|
||||
<span>暂无联系人</span>
|
||||
</div>
|
||||
) : (
|
||||
<div className="contacts-list" ref={contactsListRef} onScroll={onContactsListScroll}>
|
||||
<div
|
||||
className="contacts-list-virtual"
|
||||
style={{ height: filteredContacts.length * CONTACTS_LIST_VIRTUAL_ROW_HEIGHT }}
|
||||
>
|
||||
{visibleContacts.map((contact, idx) => {
|
||||
const absoluteIndex = contactStartIndex + idx
|
||||
const top = absoluteIndex * CONTACTS_LIST_VIRTUAL_ROW_HEIGHT
|
||||
const matchedSession = sessionRowByUsername.get(contact.username)
|
||||
const canExport = Boolean(matchedSession?.hasSession)
|
||||
const isRunning = canExport && runningSessionIds.has(contact.username)
|
||||
const isQueued = canExport && queuedSessionIds.has(contact.username)
|
||||
const isPaused = canExport && pausedSessionIds.has(contact.username)
|
||||
const recent = canExport ? formatRecentExportTime(lastExportBySession[contact.username], nowTick) : ''
|
||||
const countedMessages = normalizeMessageCount(sessionMessageCounts[contact.username])
|
||||
const hintedMessages = normalizeMessageCount(matchedSession?.messageCountHint)
|
||||
const displayedMessageCount = countedMessages ?? hintedMessages
|
||||
const messageCountLabel = !canExport
|
||||
? '--'
|
||||
: typeof displayedMessageCount === 'number'
|
||||
? displayedMessageCount.toLocaleString('zh-CN')
|
||||
: (isLoadingSessionCounts ? '统计中…' : '--')
|
||||
return (
|
||||
<div
|
||||
key={contact.username}
|
||||
className="contact-row"
|
||||
style={{ transform: `translateY(${top}px)` }}
|
||||
>
|
||||
<div className="contact-item">
|
||||
<div className="contact-avatar">
|
||||
{contact.avatarUrl ? (
|
||||
<img src={contact.avatarUrl} alt="" loading="lazy" />
|
||||
) : (
|
||||
<span>{getAvatarLetter(contact.displayName)}</span>
|
||||
)}
|
||||
</div>
|
||||
<div className="contact-info">
|
||||
<div className="contact-name">{contact.displayName}</div>
|
||||
<div className="contact-remark">{contact.username}</div>
|
||||
</div>
|
||||
<div className="row-message-count">
|
||||
<span className="row-message-count-label">总消息</span>
|
||||
<strong className={`row-message-count-value ${typeof displayedMessageCount === 'number' ? '' : 'muted'}`}>
|
||||
{messageCountLabel}
|
||||
</strong>
|
||||
</div>
|
||||
<div className="row-action-cell">
|
||||
<div className="row-action-main">
|
||||
<button
|
||||
className={`row-detail-btn ${showSessionDetailPanel && sessionDetail?.wxid === contact.username ? 'active' : ''}`}
|
||||
onClick={() => openSessionDetail(contact.username)}
|
||||
>
|
||||
详情
|
||||
</button>
|
||||
<button
|
||||
className={`row-export-btn ${isRunning ? 'running' : ''} ${isPaused ? 'paused' : ''} ${!canExport ? 'no-session' : ''}`}
|
||||
disabled={!canExport || isRunning || isPaused}
|
||||
onClick={() => {
|
||||
if (!matchedSession || !matchedSession.hasSession) return
|
||||
openSingleExport({
|
||||
...matchedSession,
|
||||
displayName: contact.displayName || matchedSession.displayName || matchedSession.username
|
||||
})
|
||||
}}
|
||||
>
|
||||
{isRunning ? (
|
||||
<>
|
||||
<Loader2 size={14} className="spin" />
|
||||
导出中
|
||||
</>
|
||||
) : !canExport ? '暂无会话' : isPaused ? '已暂停' : isQueued ? '排队中' : '导出'}
|
||||
</button>
|
||||
<>
|
||||
<div className="contacts-list-header">
|
||||
<span className="contacts-list-header-main">联系人(头像/名称/微信号)</span>
|
||||
<span className="contacts-list-header-count">总消息</span>
|
||||
<span className="contacts-list-header-actions">操作</span>
|
||||
</div>
|
||||
<div className="contacts-list" ref={contactsListRef} onScroll={onContactsListScroll}>
|
||||
<div
|
||||
className="contacts-list-virtual"
|
||||
style={{ height: filteredContacts.length * CONTACTS_LIST_VIRTUAL_ROW_HEIGHT }}
|
||||
>
|
||||
{visibleContacts.map((contact, idx) => {
|
||||
const absoluteIndex = contactStartIndex + idx
|
||||
const top = absoluteIndex * CONTACTS_LIST_VIRTUAL_ROW_HEIGHT
|
||||
const matchedSession = sessionRowByUsername.get(contact.username)
|
||||
const canExport = Boolean(matchedSession?.hasSession)
|
||||
const isRunning = canExport && runningSessionIds.has(contact.username)
|
||||
const isQueued = canExport && queuedSessionIds.has(contact.username)
|
||||
const isPaused = canExport && pausedSessionIds.has(contact.username)
|
||||
const recent = canExport ? formatRecentExportTime(lastExportBySession[contact.username], nowTick) : ''
|
||||
const countedMessages = normalizeMessageCount(sessionMessageCounts[contact.username])
|
||||
const hintedMessages = normalizeMessageCount(matchedSession?.messageCountHint)
|
||||
const displayedMessageCount = countedMessages ?? hintedMessages
|
||||
const messageCountLabel = !canExport
|
||||
? '--'
|
||||
: typeof displayedMessageCount === 'number'
|
||||
? displayedMessageCount.toLocaleString('zh-CN')
|
||||
: (isLoadingSessionCounts ? '统计中…' : '--')
|
||||
return (
|
||||
<div
|
||||
key={contact.username}
|
||||
className="contact-row"
|
||||
style={{ transform: `translateY(${top}px)` }}
|
||||
>
|
||||
<div className="contact-item">
|
||||
<div className="contact-avatar">
|
||||
{contact.avatarUrl ? (
|
||||
<img src={contact.avatarUrl} alt="" loading="lazy" />
|
||||
) : (
|
||||
<span>{getAvatarLetter(contact.displayName)}</span>
|
||||
)}
|
||||
</div>
|
||||
<div className="contact-info">
|
||||
<div className="contact-name">{contact.displayName}</div>
|
||||
<div className="contact-remark">{contact.username}</div>
|
||||
</div>
|
||||
<div className="row-message-count">
|
||||
<strong className={`row-message-count-value ${typeof displayedMessageCount === 'number' ? '' : 'muted'}`}>
|
||||
{messageCountLabel}
|
||||
</strong>
|
||||
</div>
|
||||
<div className="row-action-cell">
|
||||
<div className="row-action-main">
|
||||
<button
|
||||
className={`row-detail-btn ${showSessionDetailPanel && sessionDetail?.wxid === contact.username ? 'active' : ''}`}
|
||||
onClick={() => openSessionDetail(contact.username)}
|
||||
>
|
||||
详情
|
||||
</button>
|
||||
<button
|
||||
className={`row-export-btn ${isRunning ? 'running' : ''} ${isPaused ? 'paused' : ''} ${!canExport ? 'no-session' : ''}`}
|
||||
disabled={!canExport || isRunning || isPaused}
|
||||
onClick={() => {
|
||||
if (!matchedSession || !matchedSession.hasSession) return
|
||||
openSingleExport({
|
||||
...matchedSession,
|
||||
displayName: contact.displayName || matchedSession.displayName || matchedSession.username
|
||||
})
|
||||
}}
|
||||
>
|
||||
{isRunning ? (
|
||||
<>
|
||||
<Loader2 size={14} className="spin" />
|
||||
导出中
|
||||
</>
|
||||
) : !canExport ? '暂无会话' : isPaused ? '已暂停' : isQueued ? '排队中' : '导出'}
|
||||
</button>
|
||||
</div>
|
||||
{recent && <span className="row-export-time">{recent}</span>}
|
||||
</div>
|
||||
{recent && <span className="row-export-time">{recent}</span>}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
})}
|
||||
)
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
|
||||
|
||||
Reference in New Issue
Block a user