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